diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09a3311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg + +buildout-script.py +buildout.exe +digsby-venv/* + +# visual studio artifacts +*.vcproj.*.user +*.ncb +*.pdb +*.suo + +# generated source and scripts +digsby/ext/src/BuddyList/generated/ +digsby/ext/src/BuddyList/msvc2008/Release/ + +# tenjin cache files +*.tenjin.cache +*.py.xml.cache diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f746aa --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +Required tools: +=============== + * [msysgit](http://code.google.com/p/msysgit/downloads/list) + * [cygwin](http://svn.webkit.org/repository/webkit/trunk/Tools/CygwinDownloader/cygwin-downloader.zip) + * Note: on windows with UAC enabled, it may fail to run setup.exe after downloading packages. You'll have to run it manually. + * after installing, open a cygwin shell (cygwin.bat) and execute `perl -e "use Switch;"` + * If you don't get an error, cygwin is configured properly. Otherwise, execute the following: + * `curl -L http://search.cpan.org/CPAN/authors/id/R/RG/RGARCIA/Switch-2.16.tar.gz -o Switch-2.16.tar.gz` + * `tar -xzf Switch-2.16.tar.gz` + * `cd Switch-2.16` + * `perl Makefile.PL && make && make install` + * Microsoft Visual C++ and components + * [Visual C++ Express](http://www.microsoft.com/visualstudio/en-us/products/2008-editions/express) + * [.NET Framework 4](https://www.microsoft.com/en-us/download/details.aspx?id=17851) + * [Windows SDK 7.1](http://www.microsoft.com/en-us/download/details.aspx?id=8279) + * After installing these components, run `"c:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -version:v7.1` + * [SWIG](http://sourceforge.net/projects/swig/files/swigwin/swigwin-2.0.7/swigwin-2.0.7.zip/download) + * You'll have to put this on your `PATH` manually. + * An SVN client ([Tortoise](http://tortoisesvn.net/downloads.html), [Slik](http://www.sliksvn.com/en/download/), [CollabNet](http://www.collab.net/downloads/subversion)) + * Make sure the executing `svn` in your cmd.exe shell succeeds. If not, you'll have to add your client's `bin` directory to your `PATH` manually. + * [Python 2.6.*](http://www.python.org/download/releases/) + * As of 2012-Jul-25, the latest binary release is [2.6.6](http://www.python.org/ftp/python/2.6.6/python-2.6.6.msi) + * Also unpack the [source distribution](http://www.python.org/ftp/python/2.6.6/Python-2.6.6.tgz) on top of your install directory. This will make the headers (\*.h) and object files (\*.lib) for components that will be compiled. + * [setuptools](http://pypi.python.org/packages/2.6/s/setuptools/setuptools-0.6c11.win32-py2.6.exe#md5=1509752c3c2e64b5d0f9589aafe053dc) + * [bakefile](http://iweb.dl.sourceforge.net/project/bakefile/bakefile/0.2.9/bakefile-0.2.9-setup.exe) + * pip - install with `c:\Python26\Scripts\easy_install pip` + * virtualenv - install with `c:\Python26\Scripts\pip install virtualenv` + + +Dependencies +============ +Assumptions - replace where necessary: + * Your digsby checkout is in `c:\digsby` + * You python install is in `c:\Python26` + +1. Open the 'Microsoft Visual Studio Command Prompt' +2. `cd c:\digsby` +3. `c:\Python26\Scripts\virtualenv -p c:\python26\python.exe --distribute digsby-venv` +4. `digsby-venv\Scripts\activate.bat` +5. `python bootstrap.py` +6. `deactivate` +7. `digsby-venv\Scripts\activate.bat` + * bootstrap modifies the activate script. +8. Run MSYS from within your command prompt by executing `Git Bash.lnk` from your msysgit install directory + * You should now be inside of an MSYS shell with the correct Visual Studio environment variables set. +9. `buildout` +10. `python sanity.py -_xmlextra -blist -cgui -sip -wx -wx.calendar -wx.lib -wx.py -wx.stc -wx.webview` + * All components should be `OK`. If not, see `Dependency Troubleshooting`, below. +11. `python digsby\build\msw\build_all.py` +12. `python sanity.py` + * All components should be `OK`. If not, see `Dependency Troubleshooting`, below. + +Dependency Troubleshooting +========================== +TODO + +Running Digsby +============== +`python Digsby.py` diff --git a/bootstrap.py b/bootstrap.py new file mode 100644 index 0000000..3cd95e8 --- /dev/null +++ b/bootstrap.py @@ -0,0 +1,319 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os, os.path, shutil, sys, tempfile, urllib, urllib2, subprocess +from optparse import OptionParser + +if __name__ == '__main__': + if sys.platform == 'win32': + def quote(c): + if ' ' in c: + return '"%s"' % c # work around spawn lamosity on windows + else: + return c + else: + quote = str + + # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. + stdout, stderr = subprocess.Popen( + [sys.executable, '-Sc', + 'try:\n' + ' import ConfigParser\n' + 'except ImportError:\n' + ' print 1\n' + 'else:\n' + ' print 0\n'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + has_broken_dash_S = bool(int(stdout.strip())) + + # In order to be more robust in the face of system Pythons, we want to + # run without site-packages loaded. This is somewhat tricky, in + # particular because Python 2.6's distutils imports site, so starting + # with the -S flag is not sufficient. However, we'll start with that: + if not has_broken_dash_S and 'site' in sys.modules: + # We will restart with python -S. + args = sys.argv[:] + args[0:0] = [sys.executable, '-S'] + args = map(quote, args) + os.execv(sys.executable, args) + # Now we are running with -S. We'll get the clean sys.path, import site + # because distutils will do it later, and then reset the path and clean + # out any namespace packages from site-packages that might have been + # loaded by .pth files. + clean_path = sys.path[:] + import site # imported because of its side effects + sys.path[:] = clean_path + for k, v in sys.modules.items(): + if k in ('setuptools', 'pkg_resources') or ( + hasattr(v, '__path__') and + len(v.__path__) == 1 and + not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))): + # This is a namespace package. Remove it. + sys.modules.pop(k) + + is_jython = sys.platform.startswith('java') + + setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' + distribute_source = 'http://python-distribute.org/distribute_setup.py' + + + # parsing arguments + def normalize_to_url(option, opt_str, value, parser): + if value: + if '://' not in value: # It doesn't smell like a URL. + value = 'file://%s' % ( + urllib.pathname2url( + os.path.abspath(os.path.expanduser(value))),) + if opt_str == '--download-base' and not value.endswith('/'): + # Download base needs a trailing slash to make the world happy. + value += '/' + else: + value = None + name = opt_str[2:].replace('-', '_') + setattr(parser.values, name, value) + + usage = '''\ + [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + + Bootstraps a buildout-based project. + + Simply run this script in a directory containing a buildout.cfg, using the + Python that you want bin/buildout to use. + + Note that by using --setup-source and --download-base to point to + local resources, you can keep this script from going over the network. + ''' + + parser = OptionParser(usage=usage) + parser.add_option("-v", "--version", dest="version", + help="use a specific zc.buildout version") + parser.add_option("-d", "--distribute", + action="store_true", dest="use_distribute", default=False, + help="Use Distribute rather than Setuptools.") + parser.add_option("--setup-source", action="callback", dest="setup_source", + callback=normalize_to_url, nargs=1, type="string", + help=("Specify a URL or file location for the setup file. " + "If you use Setuptools, this will default to " + + setuptools_source + "; if you use Distribute, this " + "will default to " + distribute_source + ".")) + parser.add_option("--download-base", action="callback", dest="download_base", + callback=normalize_to_url, nargs=1, type="string", + help=("Specify a URL or directory for downloading " + "zc.buildout and either Setuptools or Distribute. " + "Defaults to PyPI.")) + parser.add_option("--eggs", + help=("Specify a directory for storing eggs. Defaults to " + "a temporary directory that is deleted when the " + "bootstrap script completes.")) + parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --version, the " + "bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) + parser.add_option("-c", None, action="store", dest="config_file", + help=("Specify the path to the buildout configuration " + "file to be used.")) + + options, args = parser.parse_args() + + # if -c was provided, we push it back into args for buildout's main function + if options.config_file is not None: + args += ['-c', options.config_file] + + if options.eggs: + eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) + else: + eggs_dir = tempfile.mkdtemp() + + if options.setup_source is None: + if options.use_distribute: + options.setup_source = distribute_source + else: + options.setup_source = setuptools_source + + if options.accept_buildout_test_releases: + args.append('buildout:accept-buildout-test-releases=true') + args.append('bootstrap') + + try: + import pkg_resources + import setuptools # A flag. Sometimes pkg_resources is installed alone. + if not hasattr(pkg_resources, '_distribute'): + raise ImportError + except ImportError: + ez_code = urllib2.urlopen( + options.setup_source).read().replace('\r\n', '\n') + ez = {} + exec ez_code in ez + setup_args = dict(to_dir=eggs_dir, download_delay=0) + if options.download_base: + setup_args['download_base'] = options.download_base + if options.use_distribute: + setup_args['no_fake'] = True + ez['use_setuptools'](**setup_args) + if 'pkg_resources' in sys.modules: + reload(sys.modules['pkg_resources']) + import pkg_resources + # This does not (always?) update the default working set. We will + # do it. + for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + + cmd = [quote(sys.executable), + '-c', + quote('from setuptools.command.easy_install import main; main()'), + '-mqNxd', + quote(eggs_dir)] + + if not has_broken_dash_S: + cmd.insert(1, '-S') + + find_links = options.download_base + if not find_links: + find_links = os.environ.get('bootstrap-testing-find-links') + if find_links: + cmd.extend(['-f', quote(find_links)]) + + if options.use_distribute: + setup_requirement = 'distribute' + else: + setup_requirement = 'setuptools' + ws = pkg_resources.working_set + setup_requirement_path = ws.find( + pkg_resources.Requirement.parse(setup_requirement)).location + env = dict( + os.environ, + PYTHONPATH=setup_requirement_path) + + requirement = 'zc.buildout' + version = options.version + if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + + def _final_version(parsed_version): + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + index = setuptools.package_index.PackageIndex( + search_path=[setup_requirement_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version + if version: + requirement = '=='.join((requirement, version)) + cmd.append(requirement) + + if is_jython: + import subprocess + exitcode = subprocess.Popen(cmd, env=env).wait() + else: # Windows prefers this, apparently; otherwise we would prefer subprocess + exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) + if exitcode != 0: + sys.stdout.flush() + sys.stderr.flush() + print ("An error occurred when trying to install zc.buildout. " + "Look above this message for any errors that " + "were output by easy_install.") + sys.exit(exitcode) + + ws.add_entry(eggs_dir) + ws.require(requirement) + import zc.buildout.buildout + zc.buildout.buildout.main(args) + if not options.eggs: # clean up temporary egg directory + shutil.rmtree(eggs_dir) + + + env_types = { + 'windows': { + 'make_var': lambda varname: '%%%s%%' % varname, + 'pathsep' : ';', + 'set_cmd' : 'set %(varname)s=%(val)s%(pathsep)s%(env_var)s\n' + }, + # TODO: will this work correctly with cygwin and msys? + # when running this script correctly in windows, paths are in windows format + 'unix' : { + 'make_var': lambda varname: '$%s' % varname, + 'pathsep' : ':', + 'set_cmd' : 'export %(varname)s=%(val)s%(pathsep)s%(env_var)s\n' + } + } + + virtual_env = os.environ.get('VIRTUAL_ENV', None) + if virtual_env is not None: + import _sqlite3 + python_dlls_location = os.path.dirname(_sqlite3.__file__) + environment_additions = { + 'PATH' : [ + python_dlls_location, + os.path.join(virtual_env, 'DLLs'), + os.path.join(virtual_env, 'digsby', 'build', 'platlib_win32_26') + ], + 'PYTHONPATH' : [ + os.path.join(virtual_env, 'digsby', 'build', 'platlib_win32_26') + ], + 'LIB' : [ + os.path.join(virtual_env, 'PCBuild') + ], + 'INCLUDE' : [ + os.path.join(virtual_env, 'Include') + ], + } + + activate_scripts = [ + ('unix', os.path.join(virtual_env, 'Scripts', 'activate')), + ('windows', os.path.join(virtual_env, 'Scripts', 'activate.bat')), + ] + for script_type, script_path in activate_scripts: + lines = ['\n'] + for env_var, paths in environment_additions.items(): + for path in paths: + lines.append( + env_types[script_type]['set_cmd'] % + dict(varname = env_var, + env_var = env_types[script_type]['make_var'](env_var), + val = path, + pathsep = env_types[script_type]['pathsep'] + ) + ) + with open(script_path, 'a') as f: + f.writelines(lines) diff --git a/builddeps/msvc2008/pysyck_msvc2008_26.zip b/builddeps/msvc2008/pysyck_msvc2008_26.zip new file mode 100644 index 0000000..b8775f4 Binary files /dev/null and b/builddeps/msvc2008/pysyck_msvc2008_26.zip differ diff --git a/builddeps/msvc2008/zlib-1.2.3.zip b/builddeps/msvc2008/zlib-1.2.3.zip new file mode 100644 index 0000000..a6323c6 Binary files /dev/null and b/builddeps/msvc2008/zlib-1.2.3.zip differ diff --git a/buildout.cfg b/buildout.cfg new file mode 100644 index 0000000..2aad7f8 --- /dev/null +++ b/buildout.cfg @@ -0,0 +1,524 @@ +[buildout] +develop = cmdrunner +virtualenv = digsby-venv +bin-directory = . + +home-dir = ${buildout:directory}\${buildout:virtualenv} +develop-eggs-directory = ${buildout:home-dir}\src +downloads-directory = ${buildout:home-dir}\downloads +eggs-directory = ${buildout:home-dir}\lib\site-packages +installed = ${buildout:home-dir}\.installed.cfg +parts-directory = ${buildout:home-dir}\parts +include-dir = ${buildout:home-dir}\PC +lib-dir = ${buildout:home-dir}\PCBuild +dlls-dir = ${buildout:home-dir}\DLLs + +python_eggs-parts = python_eggs +gnu-libs-parts = libiconv zlib freetype jpeg +OpenSSL-parts = OpenSSL-checkout OpenSSL-install +socks-parts = socks-checkout socks-install +oauth-parts = oauth-checkout oauth-install +simplejson-parts= simplejson-checkout simplejson-install +protocols-parts = protocols-checkout protocols-install +path_py-parts = path_py-checkout path_py-install +M2Crypto-parts = M2Crypto-checkout M2Crypto-install +ZSI-parts = ZSI-checkout ZSI-install +pysyck-parts = pysyck-install +libxml2-parts = libxml2-checkout libxml2-build libxml2-install +libxslt-parts = libxslt-checkout libxslt-patch libxslt-build libxslt-install +lxml-parts = lxml-checkout lxml-patch lxml-install +jpeg-parts = jpeg-checkout +PIL-parts = PIL +py2exe-parts = py2exe +comtypes-parts = comtypes-checkout comtypes-patch comtypes-install +peak-parts = peak + +python-parts = +# Pure python stuff + ${buildout:python_eggs-parts} + ${buildout:pysyck-parts} + ${buildout:socks-parts} + ${buildout:path_py-parts} + ${buildout:ZSI-parts} + +parts = + cmdrunner-install + makedirs + copy-python-libs + ${buildout:python-parts} + ${buildout:oauth-parts} + ${buildout:gnu-libs-parts} + ${buildout:PIL-parts} + ${buildout:OpenSSL-parts} + ${buildout:M2Crypto-parts} + ${buildout:libxml2-parts} + ${buildout:libxslt-parts} + ${buildout:lxml-parts} + ${buildout:py2exe-parts} + ${buildout:comtypes-parts} + ${buildout:simplejson-parts} + ${buildout:protocols-parts} + ${buildout:peak-parts} + +#TODO: curl wx webkit wxpy cgui speedups blist + +[cmdrunner-install] +recipe = cmdrunner +path = cmdrunner_install + +[makedirs] +recipe = cmdrunner +check_return_code = false +on_install = true +on_update = true +cmds = + mkdir "${buildout:downloads-directory}" + mkdir "${buildout:include-dir}" + mkdir "${buildout:lib-dir}" + mkdir "${buildout:dlls-dir}" + mkdir "${buildout:parts-directory}" + +[copy-python-libs] +recipe = cmdrunner:py +on_install = true +on_update = true +cmds = + >>> import distutils.sysconfig, shutil, os, os.path, glob + >>> libs_dir = os.path.join(os.path.dirname(distutils.sysconfig.get_python_inc()), 'libs') + >>> libs = glob.glob(os.path.join(libs_dir, '*.lib')) + >>> for lib in libs: + ... shutil.copy(lib, buildout.get('buildout').get('lib-dir')) + +[python_eggs] +recipe = cmdrunner +on_install = true +on_update = true +cmds = + pip install distribute requests ClientForm Babel tenjin==0.8.1 certifi dnspython rauth feedparser==4.1 + +[libiconv] +recipe = cmdrunner +name = libiconv +version = 1.9.2-1 +on_update = true +on_install = true + +parts-dir = ${buildout:parts-directory}\${libiconv:name} + +lib_foldername = ${libiconv:name}-${libiconv:version}-lib +lib_zipname = ${libiconv:lib_foldername}.zip +lib_download-path = "${buildout:downloads-directory}/${libiconv:lib_zipname}" +lib_url = "http://downloads.sourceforge.net/project/gnuwin32/${libiconv:name}/${libiconv:version}/${libiconv:lib_zipname}" + +bin_foldername = ${libiconv:name}-${libiconv:version}-bin +bin_zipname = ${libiconv:bin_foldername}.zip +bin_download-path = "${buildout:downloads-directory}/${libiconv:bin_zipname}" +bin_url = "http://downloads.sourceforge.net/project/gnuwin32/${libiconv:name}/${libiconv:version}/${libiconv:bin_zipname}" + +cmds = curl -L ${libiconv:lib_url} --output ${libiconv:lib_download-path} + curl -L ${libiconv:bin_url} --output ${libiconv:bin_download-path} + unzip -o ${libiconv:lib_download-path} -d ${libiconv:parts-dir} + unzip -o ${libiconv:bin_download-path} -d ${libiconv:parts-dir} + cd ${libiconv:parts-dir} + xcopy .\lib\* ${buildout:lib-dir} /S /i /y + xcopy .\bin\* ${buildout:dlls-dir} /S /i /y + xcopy .\include\* ${buildout:include-dir} /S /i /y + cd ${buildout:dlls-dir} + cp libiconv2.dll libiconv.dll + cp libiconv2.dll iconv.dll + cd ${buildout:lib-dir} + cp libiconv.lib libiconv2.lib + cp libiconv.lib iconv.lib + +[zlib] +recipe = cmdrunner +name = zlib +version = 1.2.3 +on_update = true +on_install = true + +parts-dir = ${buildout:parts-directory}\${zlib:name} + +lib_foldername = ${zlib:name}-${zlib:version}-lib +lib_zipname = ${zlib:lib_foldername}.zip +lib_download-path = "${buildout:downloads-directory}/${zlib:lib_zipname}" +lib_url = "http://downloads.sourceforge.net/project/gnuwin32/${zlib:name}/${zlib:version}/${zlib:lib_zipname}" + +bin_foldername = ${zlib:name}-${zlib:version}-bin +bin_zipname = ${zlib:bin_foldername}.zip +bin_download-path = "${buildout:downloads-directory}/${zlib:bin_zipname}" +bin_url = "http://downloads.sourceforge.net/project/gnuwin32/${zlib:name}/${zlib:version}/${zlib:bin_zipname}" + +cmds = curl -L ${zlib:lib_url} --output ${zlib:lib_download-path} + curl -L ${zlib:bin_url} --output ${zlib:bin_download-path} + unzip -o ${zlib:lib_download-path} -d ${zlib:parts-dir} + unzip -o ${zlib:bin_download-path} -d ${zlib:parts-dir} + cd ${zlib:parts-dir} + echo f | xcopy .\lib\zlib.lib .\lib\zlib1.lib /y + xcopy .\lib\* ${buildout:lib-dir} /S /i /y + xcopy .\bin\* ${buildout:dlls-dir} /S /i /y + xcopy .\include\* ${buildout:include-dir} /S /i /y + +[freetype] +recipe = cmdrunner +name = freetype +version = 2.3.5-1 +on_update = true +on_install = true + +parts-dir = ${buildout:parts-directory}\${freetype:name} + +lib_foldername = ${freetype:name}-${freetype:version}-lib +lib_zipname = ${freetype:lib_foldername}.zip +lib_download-path = "${buildout:downloads-directory}/${freetype:lib_zipname}" +lib_url = "http://downloads.sourceforge.net/project/gnuwin32/${freetype:name}/${freetype:version}/${freetype:lib_zipname}" + +bin_foldername = ${freetype:name}-${freetype:version}-bin +bin_zipname = ${freetype:bin_foldername}.zip +bin_download-path = "${buildout:downloads-directory}/${freetype:bin_zipname}" +bin_url = "http://downloads.sourceforge.net/project/gnuwin32/${freetype:name}/${freetype:version}/${freetype:bin_zipname}" + +cmds = curl -L ${freetype:lib_url} --output ${freetype:lib_download-path} + curl -L ${freetype:bin_url} --output ${freetype:bin_download-path} + unzip -o ${freetype:lib_download-path} -d ${freetype:parts-dir} + unzip -o ${freetype:bin_download-path} -d ${freetype:parts-dir} + cd ${freetype:parts-dir} + xcopy .\lib\* ${buildout:lib-dir} /S /i /y + xcopy .\bin\* ${buildout:dlls-dir} /S /i /y + xcopy .\include\* ${buildout:include-dir} /S /i /y + +[jpeg] +recipe = cmdrunner +name = jpeg +version = 6b-4 +on_update = true +on_install = true + +parts-dir = ${buildout:parts-directory}\${jpeg:name} + +lib_foldername = ${jpeg:name}-${jpeg:version}-lib +lib_zipname = ${jpeg:lib_foldername}.zip +lib_download-path = "${buildout:downloads-directory}/${jpeg:lib_zipname}" +lib_url = "http://downloads.sourceforge.net/project/gnuwin32/${jpeg:name}/${jpeg:version}/${jpeg:lib_zipname}" + +bin_foldername = ${jpeg:name}-${jpeg:version}-bin +bin_zipname = ${jpeg:bin_foldername}.zip +bin_download-path = "${buildout:downloads-directory}/${jpeg:bin_zipname}" +bin_url = "http://downloads.sourceforge.net/project/gnuwin32/${jpeg:name}/${jpeg:version}/${jpeg:bin_zipname}" + +cmds = curl -L ${jpeg:lib_url} --output ${jpeg:lib_download-path} + curl -L ${jpeg:bin_url} --output ${jpeg:bin_download-path} + unzip -o ${jpeg:lib_download-path} -d ${jpeg:parts-dir} + unzip -o ${jpeg:bin_download-path} -d ${jpeg:parts-dir} + cd ${jpeg:parts-dir} + xcopy .\lib\* ${buildout:lib-dir} /S /i /y + xcopy .\bin\* ${buildout:dlls-dir} /S /i /y + xcopy .\include\* ${buildout:include-dir} /S /i /y + +[OpenSSL-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/kevinw/openssl-setup-py.git +rev = origin/master + +[OpenSSL-install] +recipe = cmdrunner +on_install = true +on_update = true +cmds = cd ${buildout:parts-directory}/OpenSSL-checkout + python setup.py install + cd ${buildout:home-dir} + xcopy libs ${buildout:lib-dir} /i /y /S + +[path_py-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/path.py.git +ref = origin/master + +[path_py-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/path_py-checkout + python setup.py install + +[ZSI-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/ZSI.git +ref = origin/master + +[ZSI-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/ZSI-checkout + python setup.py install + +[socks-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/SocksiPy.git +ref = origin/master + +[socks-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/socks-checkout + python setup.py install + +[oauth-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/oauth.py.git +ref = origin/master + +[oauth-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/oauth-checkout + python setup.py install + +[simplejson-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/simplejson.git +branch = digsby + +[simplejson-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/simplejson-checkout + python setup.py install + +[M2Crypto-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/M2Crypto.git +ref = origin/master + +[M2Crypto-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/M2Crypto-checkout + python setup.py install + +[pysyck-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:directory}/builddeps/msvc2008 + unzip -o pysyck_msvc2008_26.zip + xcopy .\syck ${buildout:eggs-directory}\syck /i /y /S + xcopy .\_syck.pyd ${buildout:dlls-dir} /y /S /i + +[libxml2-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/libxml2.git +ref = origin/master + +[libxml2-build] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/libxml2-checkout/win32 + cscript configure.js //E:Javascript vcmanifest=yes python=yes include="${buildout:include-dir}" lib="${buildout:lib-dir}" + nmake -f Makefile.msvc + xcopy bin.msvc ..\bin /S /i /y + cd .. + echo f | xcopy ${buildout:dlls-dir}\libiconv.dll .\bin\iconv.dll /S /y + xcopy .\bin\*.lib ${buildout:lib-dir} /S /i /y + xcopy .\bin\*.dll ${buildout:dlls-dir} /S /i /y + xcopy .\include ${buildout:include-dir} /S /i /y + xcopy .\bin .\python\bin /S /i /y + +[libxml2-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/libxml2-checkout/python + set INCLUDE=${buildout:include-dir} + set LIB=${buildout:lib-dir} + python setup.py build_ext install --install-lib="${buildout:eggs-directory}" + +[libxslt-checkout] +recipe = cmdrunner +foldername = libxslt-1.1.24 +filename = ${libxslt-checkout:foldername}.tar.gz +download-path = ${buildout:downloads-directory}/${libxslt-checkout:filename} +url = http://xmlsoft.org/sources/${libxslt-checkout:filename} +parts-dir = ${buildout:parts-directory}\${libxslt-checkout:foldername} +on_update = true +on_install = true +cmds = cd ${buildout:downloads-directory} + curl -L ${libxslt-checkout:url} --output ${libxslt-checkout:download-path} + tar -xzf ${libxslt-checkout:filename} + xcopy .\${libxslt-checkout:foldername} ${libxslt-checkout:parts-dir} /S /i /y + cd ${libxslt-checkout:parts-dir}\win32 + cscript configure.js //E:Javascript vcmanifest=yes include="${buildout:include-dir}" lib="${buildout:lib-dir}" + +[libxslt-patch] +recipe = collective.recipe.patch +path = ${buildout:parts-directory} +patches = ${buildout:directory}\digsby\build\patches\libxslt-processOneNode.patch + +[libxslt-build] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${libxslt-checkout:parts-dir}\win32 + nmake -f Makefile.msvc + +[libxslt-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${libxslt-checkout:parts-dir}\win32 + xcopy bin.msvc\*.lib ${buildout:lib-dir} /S /i /y + xcopy bin.msvc\*.dll ${buildout:dlls-dir} /S /i /y + cd .. + xcopy libxslt\*.h ${buildout:include-dir}\libxslt /S /i /y + xcopy libexslt\*.h ${buildout:include-dir}\libexslt /S /i /y + +[protocols-checkout] +recipe = zerokspot.recipe.git +repository = https://github.com/mikedougherty/PyProtocols.git +rev = origin/master + +[protocols-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = cd ${buildout:parts-directory}/protocols-checkout + python setup.py install + +[jpeg-checkout] +recipe = cmdrunner +on_update = true +on_install = true +foldername = jpegsrc.v7 +filename = ${jpeg-checkout:foldername}.tar.gz +download-path = ${buildout:downloads-directory}/${jpeg-checkout:filename} +url = http://www.ijg.org/files/${jpeg-checkout:filename} +parts-dir = ${buildout:parts-directory}\${jpeg-checkout:foldername} + +[PIL] +recipe = cmdrunner +on_update = true +on_install = true +foldername = Imaging-1.1.6 +filename = ${PIL:foldername}.tar.gz +download-path = ${buildout:downloads-directory}/${PIL:filename} +url = http://effbot.org/media/downloads/${PIL:filename} +parts-dir = ${buildout:parts-directory}\${PIL:foldername} + +cmds = cd ${buildout:downloads-directory} + curl -L ${PIL:url} --output ${PIL:download-path} + tar -xzf ${PIL:filename} + touch ${buildout:include-dir}\unistd.h + xcopy .\${PIL:foldername} ${PIL:parts-dir} /S /i /y + cd ${PIL:parts-dir} + python setup.py install --install-lib="${buildout:eggs-directory}" --install-scripts="${buildout:home-dir}\Scripts" + xcopy .\build\lib.win32-2.6\*.pyd ${buildout:dlls-dir} /S /i /y + +[lxml-checkout] +recipe = cmdrunner +on_update = true +on_install = true + +foldername = lxml-2.2 +filename = ${lxml-checkout:foldername}.tar.gz +download-path = ${buildout:downloads-directory}/${lxml-checkout:filename} +url = http://pypi.python.org/packages/source/l/lxml/${lxml-checkout:filename} +parts-dir = ${buildout:parts-directory}\${lxml-checkout:foldername} + +cmds = + cd ${buildout:downloads-directory} + curl -L ${lxml-checkout:url} --output ${lxml-checkout:download-path} + tar -xzf ${lxml-checkout:filename} + xcopy .\${lxml-checkout:foldername} ${lxml-checkout:parts-dir} /S /i /y + +[lxml-patch] +recipe = cmdrunner +on_install = true +on_update = true +cmds = + cd ${lxml-checkout:parts-dir} + patch -p0 -f -i ${buildout:directory}\digsby\build\patches\lxml.patch + +[lxml-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = + cd ${lxml-checkout:parts-dir} + python setup.py build + xcopy .\build\lib.win32-2.6\lxml\*.pyd ${buildout:dlls-dir} /S /i /y + python setup.py install --install-lib="${buildout:eggs-directory}" + +[py2exe] +recipe = cmdrunner +name = py2exe +version = 0.6.9 +on_update = true +on_install = true + +parts-dir = ${buildout:parts-directory}\${py2exe:name} +foldername = ${py2exe:name}-${py2exe:version} +zipname = ${py2exe:foldername}.zip +download-path = "${buildout:downloads-directory}/${py2exe:zipname}" +url = "http://downloads.sourceforge.net/project/py2exe/${py2exe:name}/${py2exe:version}/${py2exe:zipname}" + +cmds = curl -L ${py2exe:url} --output ${py2exe:download-path} + unzip -o ${py2exe:download-path} -d ${py2exe:parts-dir} + cd ${py2exe:parts-dir}\${py2exe:foldername} + python setup.py install + +[comtypes-checkout] +recipe = cmdrunner +name = comtypes +version = 0.6.0 +on_update = true +on_install = true + +parts-dir = ${buildout:parts-directory}\${comtypes-checkout:name} +foldername = ${comtypes-checkout:name}-${comtypes-checkout:version} +zipname = ${comtypes-checkout:foldername}.zip +download-path = "${buildout:downloads-directory}/${comtypes-checkout:zipname}" +url = "http://downloads.sourceforge.net/project/${comtypes-checkout:name}/${comtypes-checkout:name}/${comtypes-checkout:version}/${comtypes-checkout:zipname}" +cmds = + curl -L ${comtypes-checkout:url} --output ${comtypes-checkout:download-path} + unzip -o ${comtypes-checkout:download-path} -d ${comtypes-checkout:parts-dir} + +[comtypes-patch] +recipe = cmdrunner +on_update = true +on_install = true +cmds = + cd ${comtypes-checkout:parts-dir}\${comtypes-checkout:foldername} + patch -p0 -f -i ${buildout:directory}\digsby\build\msw\comtypes.0.6.0.patch + +[comtypes-install] +recipe = cmdrunner +on_update = true +on_install = true +cmds = + cd ${comtypes-checkout:parts-dir}\${comtypes-checkout:foldername} + ls + python setup.py install + +[peak] +recipe = cmdrunner +name = peak +on_update = true +on_install = true + +parts-dir = ${buildout:parts-directory}\${peak:name} +# TODO: how to get this foldername out of here? +foldername = mikedougherty-peak-1b70df9 +zipname = ${peak:name}.zip +download-path = "${buildout:downloads-directory}/${peak:zipname}" +url = https://github.com/mikedougherty/peak/zipball/digsby + +cmds = + curl -L ${peak:url} --output ${peak:download-path} + unzip -o ${peak:download-path} -d ${peak:parts-dir} + xcopy ${peak:parts-dir}\${peak:foldername} ${buildout:eggs-directory}\peak /S /i /y diff --git a/cmdrunner/README.txt b/cmdrunner/README.txt new file mode 100644 index 0000000..e69de29 diff --git a/cmdrunner/cmdrunner.py b/cmdrunner/cmdrunner.py new file mode 100644 index 0000000..12e2ff0 --- /dev/null +++ b/cmdrunner/cmdrunner.py @@ -0,0 +1,109 @@ +import os.path +import shutil +import subprocess +import sys +import tempfile + +def as_bool(value): + if value.lower() in ('1', 'true'): + return True + return False + +def run_commands(cmds, shell, check=True): + cmds = cmds.strip() + if not cmds: + return + if cmds: + lines = cmds.split('\n') + lines = [l.strip() for l in lines] + dirname = tempfile.mkdtemp() + + print '----------' + print '\n'.join(lines) + print '----------' + + if sys.platform == 'win32': + tmpfile = os.path.join(dirname, 'run.bat') + + if check: + # add an error handler after each line so that we bail on a non-zero return code + # (batch files are shit and don't do this by default) + reallines = [] + for line in lines: + reallines.append('echo CMDRUNNER --> "' + line + '"') + reallines.append(line) + reallines.append('if %errorlevel% neq 0 exit /b %errorlevel%') + lines = reallines + + lines.insert(0, '@echo off') + else: + tmpfile = os.path.join(dirname, 'run') + open(tmpfile, 'w').write('\n'.join(lines)) + if sys.platform == 'win32': + retcode = subprocess.call(tmpfile, shell=True) + else: + retcode = subprocess.call('%s %s' % (shell, tmpfile), shell=True) + shutil.rmtree(dirname) + if check and retcode != 0: + raise Exception('non-zero return code for cmds:\n' + cmds) + + +class Cmd(object): + def __init__(self, buildout, name, options): + self.buildout = buildout + self.name = name + self.options = options + + self.on_install = as_bool(options.get('on_install', 'false')) + self.on_update = as_bool(options.get('on_update', 'false')) + self.check_return_code = as_bool(options.get('check_return_code', 'true')) + self.shell = options.get('shell', 'sh') + + def install(self): + if self.on_install: + self.execute() + return tuple() + + def update(self): + """updater""" + if self.on_update: + self.execute() + return tuple() + + def execute(self): + """run the commands + """ + cmds = self.options.get('cmds', '') + run_commands(cmds, self.shell, self.check_return_code) + +def uninstallCmd(name, options): + cmds = options.get('uninstall_cmds', '') + shell = options.get('shell', 'sh') + run_commands(cmds, shell) + +class Python(Cmd): + + def execute(self): + """run python code + """ + cmds = self.options.get('cmds', '') + cmds = cmds.strip() + def undoc(l): + l = l.strip() + l = l.replace('>>> ', '') + l = l.replace('... ', '') + return l + + if not cmds: + return + if cmds: + name = self.name + buildout = self.buildout + options = self.options + lines = cmds.split('\n') + lines = [undoc(line) for line in lines if line.strip()] + dirname = tempfile.mkdtemp() + tmpfile = os.path.join(dirname, 'run.py') + open(tmpfile, 'w').write('\n'.join(lines)) + execfile(tmpfile) + shutil.rmtree(dirname) diff --git a/cmdrunner/setup.py b/cmdrunner/setup.py new file mode 100644 index 0000000..f0e77d5 --- /dev/null +++ b/cmdrunner/setup.py @@ -0,0 +1,7 @@ +from setuptools import setup + +setup( + name = "cmdrunner", + entry_points = {'zc.buildout': ['default = cmdrunner:Cmd', 'py = cmdrunner:Python'], + 'zc.buildout.uninstall': ['default = cmdrunner:uninstallCmd']} +) diff --git a/digsby/.gitignore b/digsby/.gitignore new file mode 100644 index 0000000..171f2c6 --- /dev/null +++ b/digsby/.gitignore @@ -0,0 +1,13 @@ +.svn +*.pyc +lib/AutoUpdate/ +lib/pyxmpp/ +build/platlib_mac32_26/ +ext/build/ +ext/src/generated/ +logs/ +denv +dist/ +.pydevproject +svnrev.py +debug/ diff --git a/digsby/.project b/digsby/.project new file mode 100644 index 0000000..643c1bb --- /dev/null +++ b/digsby/.project @@ -0,0 +1,18 @@ + + + digsby + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + org.eclipse.cdt.core.ccnature + + diff --git a/digsby/Digsby.py b/digsby/Digsby.py new file mode 100644 index 0000000..16309c3 --- /dev/null +++ b/digsby/Digsby.py @@ -0,0 +1,72 @@ +import time; time.clock() #startup time. + +import sys +import os.path + +from digsbypaths import get_platlib_dir, platformName + +if __debug__: + os.chdir(os.path.abspath(os.path.dirname(__file__))) + + extra_paths = [ + './src', + './lib', + './platlib/' + platformName, + ] + + platlib_dir = get_platlib_dir() + if platlib_dir not in sys.path: + sys.path.insert(0, platlib_dir) + + sys.path[0:0] = map(os.path.abspath, extra_paths) + + if os.name == 'nt': + os.environ['PATH'] = os.pathsep.join([os.environ['PATH'], get_platlib_dir()]) + +else: + launcher_name = 'digsby.exe' + __libdir, __myname = os.path.split(sys.executable) + __mydir, __ = os.path.split(__libdir) + os.chdir(__mydir) + + # Vista doesn't find ssleay32.dll without this PATH modification (ssleay32.dll + # is one of the binaries that is depended on by PYDs in more than one directory + # location). + os.environ['PATH'] = os.path.pathsep.join((__libdir, os.environ.get('PATH', ''))) + + sys.prefix = __libdir + sys._real_exe, sys.executable = sys.executable, os.path.join(__mydir, launcher_name) + +# need this really early +if __name__ == '__main__': + import options + sys.opts, _args = options.parser.parse_args() + +# Monkeypatches that used to be in DPython +import netextensions + +# imported for its side effects +import digsbysite +import logextensions +del digsbysite + +import socks +sys.modules['socket'] = socks +if platformName == 'win' and hasattr(socks._socket, '_GLOBAL_DEFAULT_TIMEOUT'): + # for python 2.6 compatability + socks._GLOBAL_DEFAULT_TIMEOUT = socks._socket._GLOBAL_DEFAULT_TIMEOUT + +def main(): + # guard against util being imported too early + assert not getattr(sys, '_util_loaded', False) + sys.util_allowed = False + + import main + main.main() + + + print >>sys.stderr, "Digsby.py main is done." + +if __name__ == '__main__': + main() + diff --git a/digsby/LICENSE b/digsby/LICENSE new file mode 100644 index 0000000..262ceb2 --- /dev/null +++ b/digsby/LICENSE @@ -0,0 +1 @@ +#__LICENSE_GOES_HERE__ diff --git a/digsby/README.md b/digsby/README.md new file mode 100644 index 0000000..d8992f8 --- /dev/null +++ b/digsby/README.md @@ -0,0 +1,4 @@ +Digsby +====== + +Digsby is an open-source instant messenger client. diff --git a/digsby/b.bat b/digsby/b.bat new file mode 100644 index 0000000..9701994 --- /dev/null +++ b/digsby/b.bat @@ -0,0 +1,4 @@ +@echo off +pushd build\msw +call b.bat %* +popd diff --git a/digsby/build/README.txt b/digsby/build/README.txt new file mode 100644 index 0000000..9bd2f9a --- /dev/null +++ b/digsby/build/README.txt @@ -0,0 +1,16 @@ +building-on-mac-instructions +---------------------------- + +install python from python.org +make sure "python" on the PATH is python 2.6 + +svn co http://svn.python.org/projects/sandbox/trunk/setuptools + cd setuptools + python setup.py install + +install git +install bakefile + +cd build +./build-deps.py --wx_trunk --debug --python_deps + diff --git a/digsby/build/build-deps.py b/digsby/build/build-deps.py new file mode 100755 index 0000000..c4fa6fb --- /dev/null +++ b/digsby/build/build-deps.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python +#__LICENSE_GOES_HERE__ + +from buildutil import * +from build_wxpy import * +from compiledeps import * + +print "Digsby dir is: %s" % digsbyDir + +import commands +import optparse +import os +import string +import sys +import types +from constants import WXWIDGETS_28_SVN_DIR, WXPYTHON_28_SVN_DIR, WXWIDGETS_TRUNK_SVN_DIR, WXPYTHON_TRUNK_SVN_DIR, WXWEBKIT_GIT_REPO + +import config + +build_wx = False +build_webkit = False +rebake = False + +py_version = sys.version[:3] +is64bit = False + +path_extras = [] + +option_dict = { + "clean" : (False, "Clean all files from build directories"), + "debug" : (False, "Build wxPython with debug symbols"), + "rebake" : (False, "Force regeneration of wx makefiles."), + "swig" : (False, "Use SWIG bindings instead of SIP"), + "reswig" : (False, "Re-generate the SWIG wrappers"), + "update" : (False, "Do an svn update on all dependencies."), + "wx" : (False, "Force wx build"), + "webkit" : (False, "Force wxWebKit build"), + "wx_trunk" : (False, "Build against latest wx trunk."), + "python_deps": (False, "Compile Python dependencies."), + } + +parser = optparse.OptionParser(usage="usage: %prog [options]", version="%prog 1.0") + +for opt in option_dict: + default = option_dict[opt][0] + + action = "store" + if type(default) == types.BooleanType: + action = "store_true" + parser.add_option("--" + opt, default=default, action=action, dest=opt, help=option_dict[opt][1]) + +options, arguments = parser.parse_args() + +if options.wx or options.update: + build_wx = True + +if options.webkit or options.update: + build_webkit = True + +if options.rebake: + rebake = True + +if options.wx_trunk: + wx_version = "2.9.0" + WXWIDGETS_SVN_DIR = WXWIDGETS_TRUNK_SVN_DIR + WXPYTHON_SVN_DIR = WXPYTHON_TRUNK_SVN_DIR +else: + wx_version = "2.8" + WXWIDGETS_SVN_DIR = WXWIDGETS_28_SVN_DIR + WXPYTHON_SVN_DIR = WXPYTHON_28_SVN_DIR + +# Automatically update if Digsby needs a revision of wx that is > what is currently +# in the tree. +MIN_WX_REVISION = 55272 + +if config.platformName == "win": + # TODO: Can we just build the Windows side with this script too? + print "On Windows, use the pre-compiled binaries in DPython instead of this script." + sys.exit(1) + +depsDir = os.path.join(homeDir, "digsby-deps", "py" + py_version + "-wx" + wx_version[:3]) +buildDirs.initBuildDirs(depsDir) + +checkForDeps(swig=options.swig) + +if not os.path.exists(depsDir): + os.makedirs(depsDir) + +os.chdir(depsDir) + +if sys.platform.startswith('darwin'): + # use the 10.4 SDK to make sure the C/C++ libs we compile work on older systems + os.environ['CC'] = 'gcc-4.0' + os.environ['CXX'] = 'g++-4.0' + +if not options.clean and options.python_deps: + # TODO: Determine if we can get these via packages on Linux + build_m2crypto() + build_syck() + build_pil() + + if sys.platform.startswith('darwin'): + build_libxml2_mac() + build_pyobjc() + else: + build_libxml2() + +# download and build sip +sip() + +sipPath = os.path.join(depsDir, "sip", "sipgen") +path_extras.append(sipPath) +os.environ["PATH"] = sipPath + os.pathsep + os.environ["PATH"] + +# build boost +boost = tardep('http://mini/mirror/', 'boost_1_42_0', '.tar.gz', 40932853, dirname = buildDirs.boostDir) +#boost.get() + +if not os.path.exists(buildDirs.wxWidgetsDir): + run("svn checkout %s wxWidgets" % WXWIDGETS_SVN_DIR) + build_wx = True + +os.chdir(buildDirs.wxWidgetsDir) + +# FIXME: write a wrapper for commands.getoutput that works on Windows +if not sys.platform.startswith("win"): + revision = commands.getoutput("svnversion .") + revision = min(int(r) for r in revision.rstrip('MSP').split(':')) + +if options.update or (not sys.platform.startswith("win") and revision < MIN_WX_REVISION): + run(["svn", "update"]) + +if options.swig: + if not os.path.exists(buildDirs.wxPythonDir): + run(["svn", "checkout", WXPYTHON_SVN_DIR, "wxPython"]) + build_wx = True + + if wx_version == "2.8": + os.chdir("wxPython") + run(["patch", "-p0", "<", os.path.join(scriptDir, "patches", "wxpy_popupwin.patch")]) + run(["patch", "-p0", "<", os.path.join(scriptDir, "patches", "build-wxpython.patch")]) + + os.chdir(buildDirs.wxPythonDir) + + if options.update or (not sys.platform.startswith("win") and revision < MIN_WX_REVISION): + run(["svn", "update"]) + +os.chdir(depsDir) +if not os.path.exists(buildDirs.wxWebKitDir): + run(["git", "clone", WXWEBKIT_GIT_REPO, "wxWebKit"]) + build_webkit = True + os.chdir(buildDirs.wxWebKitDir) + run("git checkout -b digsby origin/digsby".split()) + run("git pull origin digsby".split()) + +os.chdir(buildDirs.wxWebKitDir) +if options.update: + run("git pull origin digsby".split()) + +# Now let's do the build +if sys.platform.startswith("win"): + homeDir = "C:" # TODO: What would be a better option here? +else: + homeDir = os.environ["HOME"] + +installDir = "%s/wxpython-%s" % (homeDir, wx_version[:3]) +os.environ["PATH"] = installDir + "/bin" + os.pathsep + os.environ["PATH"] + +if options.clean: + if options.swig: + os.chdir(buildDirs.wxPythonDir) + run([sys.executable] + "build-wxpython.py --clean".split()) + os.chdir(digsbyDir) + run([sys.executable] + "ext/do_buildext.py clean".split(), env = os.environ) + else: + os.chdir(buildDirs.wxWidgetsDir) + run([sys.executable] + "build/tools/build-wxwidgets.py --clean".split()) + + os.chdir(buildDirs.wxWebKitDir) + run("WebKitTools/Scripts/build-webkit --wx --clean".split()) + + sys.exit(0) + +os.chdir(buildDirs.wxWidgetsDir) +if rebake: + os.chdir(os.path.join(buildDirs.wxWidgetsDir, "build/bakefiles")) + + run("bakefile_gen") + + # hack below is due to wxWidgets storing generated files in the tree. Since that tends + # to create huge diffs, they have a lot of special tricks which reduce the diffs by + # keeping certain files for certain versions of Bakefile, etc. in the tree. Since + # we don't use that Bakefile version, the hacks break our build system, and so we have to + # copy over the 'real' version of autoconf bakefile tools ourselves and then + # re-configure, making sure to touch configure.in to force the rebuild. Ick. :( + if not sys.platform.startswith("win"): + bakefilePrefix = commands.getoutput("which bakefile").replace("/bin/bakefile", "") + + os.chdir(buildDirs.wxWidgetsDir) + run(["cp", "%s/share/aclocal/bakefile.m4" % bakefilePrefix, "build/aclocal/"]) + + run("touch configure.in; make -f build/autogen.mk".split()) + + # end rebake hacks + +pythonPath = "" + +if build_wx: + wx_options = [] + if options.debug: + wx_options.append("--debug") + + if options.wx_trunk and sys.platform.startswith("darwin"): + wx_options.append("--osx_cocoa") + + if options.swig: + if options.reswig: + wx_options.append("--reswig") + + os.chdir(buildDirs.wxPythonDir) + run([sys.executable] + "build-wxpython.py --unicode --install".split() + wx_options) + pythonPath = "%s/wxPython/lib/python%s/site-packages/wx-%s-%s-unicode" % (installDir, py_version, wx_version, platform) + os.environ["PYTHONPATH"] = pythonPath + else: + wx_options.append('--prefix=%s' % installDir) + if "--debug" in wx_options: + configure_flags.remove("--enable-debug_flag") + configure_flags.append("--disable-debug_flag") + if sys.platform.startswith('darwin'): + # 10.5 sends mouse motion events on inactive / non-focused windows, so we want that :) + configure_flags.append("--with-macosx-version-min=10.5") + wx_options.append('--features=%s' % string.join(configure_flags, " ")) + os.chdir(buildDirs.wxWidgetsDir) + run([sys.executable] + "build/tools/build-wxwidgets.py --unicode --install".split() + wx_options) + +os.environ["WXDIR"] = os.environ["WXWIN"] = buildDirs.wxWidgetsDir +os.environ["WXPYDIR"] = buildDirs.wxPythonDir +os.environ["PATH"] = installDir + "/bin" + os.pathsep + os.environ["PATH"] +platform = config.platformName +if options.wx_trunk and platform == "mac": + platform = "osx_cocoa" + +configDir = "Release" +if options.debug: + configDir = "Debug" + +configDir += ".digsby" + +extra = "" +libPath = "" +if platform == "gtk": + platform = "gtk2" + libPath = "%s/wxpython-%s/lib:%s/wxWebKit/WebKitBuild/%s" % (depsDir, wx_version[:3], scriptDir, configDir) + os.environ["LD_LIBRARY_PATH"] = libPath + os.pathsep + os.environ["LD_LIBRARY_PATH"] + +if build_webkit: + os.chdir(os.path.join(depsDir, "wxWebKit")) + + if options.debug: + run(["WebKitTools/Scripts/set-webkit-configuration", "--wx", "--debug"]) + else: + run(["WebKitTools/Scripts/set-webkit-configuration", "--wx", "--release"]) + + run(["WebKitTools/Scripts/build-webkit", "--wx", "--makeargs=--macosx-version=10.5"], env = os.environ) + +if not options.swig: + os.chdir(depsDir) + branch = "master" + if options.wx_trunk: + branch = "wx_trunk" + wxpy(branch = branch) + +os.chdir(digsbyDir) + +pythonPath += os.pathsep + "%s/wxWebKit/WebKitBuild/%s" % (depsDir, configDir) +if not options.swig: + pythonPath += os.pathsep + "%s/wxpy" % depsDir + os.pathsep + "%s/sip" % depsDir + os.pathsep + "%s/sip/siplib" % depsDir + +os.environ["PYTHONPATH"] = pythonPath +if options.swig: + run([sys.executable, "ext/do_buildext.py", "WX_CONFIG=%s/bin/wx-config" % installDir], env = os.environ) +else: + os.chdir(os.path.join(digsbyDir, "ext")) + run([sys.executable, 'buildbkl.py', + '--wx=%s' % buildDirs.wxWidgetsDir, + '--webkit=%s' % buildDirs.wxWebKitDir], + env = os.environ) + os.chdir(digsbyDir) + +bits = '32' +if is64bit: + bits = '64' + +pythonPath += os.pathsep + os.path.join(digsbyDir, "build", "platlib_" + config.platformName + bits + "_" + py_version.replace(".", "")) + +ext = "" +if sys.platform.startswith("win"): + ext = ".bat" +scriptPath = os.path.join(digsbyDir, "denv" + ext) +script = open(scriptPath, "w") +if sys.platform.startswith("win"): + script.write('set DIGSBY_DIR="%s"\r\n' % digsbyDir) + script.write('set PYTHONPATH="$DIGSBY_DIR/lib;%s:$DIGSBY_DIR/src;$DIGSBY_DIR;$DIGSBY_DIR/ext/%s;$DIGSBY_DIR/build/deps\r\n' % (pythonPath, config.platformName)) +else: + script.write("export DIGSBY_DIR=%s\n" % digsbyDir) + script.write("export LD_LIBRARY_PATH=%s\n" % libPath) + script.write("export PYTHONPATH=$DIGSBY_DIR/lib:%s:$DIGSBY_DIR/src:$DIGSBY_DIR:$DIGSBY_DIR/ext/%s:$DIGSBY_DIR/build/deps\n" % (pythonPath, config.platformName)) + +script.close() + +print "=========== BUILD COMPLETE ===========" +print "" + +print "To run Digsby:" +print "- run . denv to setup the environment for Digsby" +print "- Run Digsby.py" + diff --git a/digsby/build/build-mac-deps.sh b/digsby/build/build-mac-deps.sh new file mode 100755 index 0000000..46f57b4 --- /dev/null +++ b/digsby/build/build-mac-deps.sh @@ -0,0 +1,3 @@ +#!/usr/bin/bash + +python compiledeps.py syck pil libxml2 diff --git a/digsby/build/build_ppmd7.py b/digsby/build/build_ppmd7.py new file mode 100644 index 0000000..9f00022 --- /dev/null +++ b/digsby/build/build_ppmd7.py @@ -0,0 +1,30 @@ +#__LICENSE_GOES_HERE__ +from buildutil import tardep, DEBUG, git, cd, run, DEPS_DIR, copy_different +from os.path import isdir +import os.path +thisdir = os.path.dirname(os.path.abspath(__file__)) + +lzma_sdk = tardep('http://mini/mirror/', 'lzma920', '.tar.bz2', 534077, indir='lzma920') + +ppmd7_git_url = 'http://mini/git/pyppmd.git' +ppmd7_dir = 'pyppmd' + +def build(): + with cd(thisdir): + lzma_dir = lzma_sdk.get() + + if not isdir(ppmd7_dir): + git.run(['clone', ppmd7_git_url]) + assert isdir(ppmd7_dir) + + with cd(ppmd7_dir): + libname = 'ppmd7' + config = 'Release' if not DEBUG else 'Debug' + run(['vcbuild', 'libppmd7.sln', '%s|Win32' % config]) + for ext in ('.dll', '.pdb'): + copy_different(os.path.join(config, libname + ext), DEPS_DIR) + +if __name__ == '__main__': + build() + + diff --git a/digsby/build/build_protocols_speedups.py b/digsby/build/build_protocols_speedups.py new file mode 100644 index 0000000..9222531 --- /dev/null +++ b/digsby/build/build_protocols_speedups.py @@ -0,0 +1,31 @@ +#__LICENSE_GOES_HERE__ +''' +builds the speedups module for PyProtocols (a dependency of Twitter) + +should work on any platform distutils does +''' +from buildutil import dpy, DEPS_DIR, DEBUG + + +if DEBUG: + cargs = "['/Zi', '/Od']" +else: + cargs = "['/GL', '/Zi', '/GS-']" + +protocols_setup = ( +"from distutils.core import setup, Extension; " +"setup(name = '_speedups', ext_modules = " +"[Extension('_speedups', ['../lib/protocols/_speedups.c'], extra_compile_args = %s, extra_link_args = ['/DEBUG', '/OPT:REF', '/OPT:ICF'])])" +% cargs +) + + +def build_protocols_speedups(): + dpy(['-c', protocols_setup, 'clean']) + dpy(['-c', protocols_setup, + 'build_ext'] + (['--debug'] if DEBUG else []) + + ['install', '--install-lib', DEPS_DIR] + ) + +if __name__ == '__main__': + build_protocols_speedups() diff --git a/digsby/build/build_wxpy.py b/digsby/build/build_wxpy.py new file mode 100644 index 0000000..bb918c5 --- /dev/null +++ b/digsby/build/build_wxpy.py @@ -0,0 +1,156 @@ +#__LICENSE_GOES_HERE__ +from __future__ import with_statement + +import os.path +import shutil +import sys +sys.path.append('..') if '..' not in sys.path else None + +from buildutil import run, fatal, inform, cd, git, dpy, copytree_filter, DEBUG_POSTFIX +from buildutil.common import * +from constants import * + +from os.path import isdir, abspath, isfile, join as pathjoin + +py_ext = "so" +if sys.platform.startswith("win"): + py_ext = "pyd" + +# local SIP info +sip_path = 'sip' +sip_exe = pathjoin(sip_path, 'sipgen', 'sip') +if sys.platform.startswith("win"): + sip_exe += ".exe" +sip_pyd = pathjoin(sip_path, 'siplib', 'sip%s.%s' % (DEBUG_POSTFIX, py_ext)) +sip_pdb = pathjoin(sip_path, 'siplib', 'sip%s.pdb' % DEBUG_POSTFIX) + +# local WXPY info +WXPYDIR = wxpy_path = 'wxpy' + +comtypes_url = 'https://comtypes.svn.sourceforge.net/svnroot/comtypes/tags/comtypes-0.6.0/' + +# see below for notes on this patch +comtypes_patch = 'comtypes.0.6.0.patch' + +def sip(): + sip_path_parent, sip_dir = os.path.split(os.path.abspath(sip_path)) + + # Get SIP + needs_build = True + with cd(sip_path_parent): + if not isdir(sip_dir): + inform('Could not find SIP directory at %r, downloading...' % sip_path) + git.run(['clone', SIP_GIT_REPO, sip_dir]) + + if SIP_GIT_BRANCH != 'master': + with cd(sip_dir): + git.run(['checkout', '-b', SIP_GIT_BRANCH, SIP_GIT_REMOTE + '/' + SIP_GIT_BRANCH]) + else: + pass +# inform('SIP found at %r, updating...' % sip_path) +# with cd(sip_dir): +# git.run(['pull']) +# if not git.sha_changed() and isfile(sip_exe) and isfile(sip_pyd): +# inform('skipping SIP build') +# needs_build = False + + # Build SIP + if needs_build and 'nosip' not in sys.argv: + with cd(sip_path): + if not sys.platform.startswith("win"): + dpy(['configure.py', '-b', 'sipgen', '-d', 'siplib', '-e', 'siplib', '-v', 'siplib']) + # sip sets CC and CXX directly to cc and c++ rather than pulling the values + # from the environment, which we don't want if we're forcing a 32-bit build + # by using gcc 4.0. + env = os.environ + run(['make', 'CC=%s' % env['CC'], + 'CXX=%s' % env['CXX'], + 'LINK=%s' % env['CXX']]) + + else: + dpy(['setup.py', 'build']) + + assert isfile(sip_exe), "setup.py did not create %s" % sip_exe + + if sys.platform.startswith("win"): + from buildutil import copy_different, DEPS_DIR + copy_different(sip_pyd, DEPS_DIR) + copy_different(sip_pdb, DEPS_DIR) + +def wxpy(rebuild = False, branch="master"): + wxpy_path_parent, wxpy_dir = os.path.split(os.path.abspath(wxpy_path)) + + # must have wx and webkit directories + wx_dir = buildDirs.wxWidgetsDir + webkit_dir = buildDirs.wxWebKitDir + + sip_dir = os.path.abspath(sip_path) + + with cd(wxpy_path_parent): + if not isdir(wxpy_dir): + inform('Could not find wxpy directory at %r, downloading...' % wxpy_path) + git.run(['clone', WXPY_GIT_REPO, wxpy_dir]) + if branch != "master": + with cd(wxpy_dir): + git.run(['checkout', '-b', branch, 'origin/%s' % branch]) + + # else: + # with cd(wxpy_dir): + # git.run(['pull']) + + # wipe out the "wxpy/build" directory if we're rebuilding + + if rebuild: + inform("rebuilding...removing old build directory") + with cd(wxpy_dir): + if os.path.isdir('build'): + shutil.rmtree('build') + + sip_pyd_loc = pathjoin(sip_dir, 'siplib') + + sip_exe = 'sip%s.%s' % (DEBUG_POSTFIX, py_ext) + assert os.path.isfile(pathjoin(sip_pyd_loc, sip_exe)), \ + "could not find %s at %r" % (sip_exe, sip_pyd_loc) + pp = os.pathsep.join([sip_dir, sip_pyd_loc]) + + os.environ['PYTHONPATH'] = pp + + print 'PYTHONPATH=' + pp + + with cd(wxpy_dir): + dpy(['setup.py', + '--wx=%s' % wx_dir, + '--webkit=%s' % webkit_dir] + + sys.argv[1:]) + + + install_wxpy() + +def install_wxpy(): + # install to dependencies dir + from buildutil import DEPS_DIR + install_dir = pathjoin(DEPS_DIR, 'wx') + inform('\n\ninstalling wxpy to %s' % install_dir) + + def ffilter(f): + return not any(map(f.endswith, ('.exp', '.idb', '.pyc', '.lib'))) + + with cd(wxpy_path): + copytree_filter('wx', install_dir, filefilter = ffilter, only_different = True) + + +def all(): + sip() + wxpy(rebuild = 'rebuild' in sys.argv) + #print 'skipping comtypes' + +def main(): + if 'sip' in sys.argv: + sip() + elif 'copy' in sys.argv: + install_wxpy() + else: + all() + +if __name__ == '__main__': + main() diff --git a/digsby/build/buildbotstatus.py b/digsby/build/buildbotstatus.py new file mode 100644 index 0000000..4784258 --- /dev/null +++ b/digsby/build/buildbotstatus.py @@ -0,0 +1,52 @@ +#__LICENSE_GOES_HERE__ +''' +commandline interface to buildbot status +''' + +import sys +import time +from optparse import OptionParser +from xmlrpclib import ServerProxy + +DEFAULT_XMLRPC_URL = 'http://mini/buildbot/xmlrpc' + +def green(xmlrpc = DEFAULT_XMLRPC_URL): + ''' + Returns true if all buildbots are green. + ''' + + return BuildBotClient(xmlrpc).recent_builds_status() + +def main(): + parser = OptionParser() + parser.add_option('--xmlrpc-url', dest='rpcurl') + parser.set_defaults(rpcurl=DEFAULT_XMLRPC_URL) + + opts, args = parser.parse_args() + + client = BuildBotClient(opts.rpcurl) + sys.exit(-1 if not client.recent_builds_status() else 0) + +class BuildBotClient(object): + def __init__(self, uri): + self.server = ServerProxy(uri) + + def recent_builds_status(self): + success = True + builders = self.server.getAllBuilders() + + print 'builder results:' + for builder in builders: + result = self.server.getLastBuildResults(builder) + print ' %s - %s' % (builder, result) + success = success and result != 'failure' + + return success + +def asunix(d): + return time.mktime(d.timetuple()) + + + +if __name__ == '__main__': + main() diff --git a/digsby/build/buildutil/__init__.py b/digsby/build/buildutil/__init__.py new file mode 100644 index 0000000..788af2b --- /dev/null +++ b/digsby/build/buildutil/__init__.py @@ -0,0 +1,5 @@ +#__LICENSE_GOES_HERE__ +from common import * +from buildfileutils import * + +import git diff --git a/digsby/build/buildutil/bloattrack/bloat.py b/digsby/build/buildutil/bloattrack/bloat.py new file mode 100644 index 0000000..b76d4cc --- /dev/null +++ b/digsby/build/buildutil/bloattrack/bloat.py @@ -0,0 +1,289 @@ +#!/usr/bin/python + +import fileinput +import optparse +import os +import pprint +import re +import sys +import json + +def format_bytes(bytes): + """Pretty-print a number of bytes.""" + if bytes > 1e6: + bytes = bytes / 1.0e6 + return '%.1fm' % bytes + if bytes > 1e3: + bytes = bytes / 1.0e3 + return '%.1fk' % bytes + return str(bytes) + + +def symbol_type_to_human(type): + """Convert a symbol type as printed by nm into a human-readable name.""" + return { + 'b': 'bss', + 'd': 'data', + 'r': 'read-only data', + 't': 'code', + 'w': 'weak symbol', + 'v': 'weak symbol' + }[type] + + +def parse_nm(input): + """Parse nm output. + + Argument: an iterable over lines of nm output. + + Yields: (symbol name, symbol type, symbol size, source file path). + Path may be None if nm couldn't figure out the source file. + """ + + # Match lines with size + symbol + optional filename. + sym_re = re.compile(r'^[0-9a-f]+ ([0-9a-f]+) (.) ([^\t]+)(?:\t(.*):\d+)?$') + + # Match lines with addr but no size. + addr_re = re.compile(r'^[0-9a-f]+ (.) ([^\t]+)(?:\t.*)?$') + # Match lines that don't have an address at all -- typically external symbols. + noaddr_re = re.compile(r'^ + (.) (.*)$') + + for line in input: + line = line.rstrip() + match = sym_re.match(line) + if match: + size, type, sym = match.groups()[0:3] + size = int(size, 16) + type = type.lower() + if type == 'v': + type = 'w' # just call them all weak + if type == 'b': + continue # skip all BSS for now + path = match.group(4) + yield sym, type, size, path + continue + match = addr_re.match(line) + if match: + type, sym = match.groups()[0:2] + # No size == we don't care. + continue + match = noaddr_re.match(line) + if match: + type, sym = match.groups() + if type in ('U', 'w'): + # external or weak symbol + continue + + print >>sys.stderr, 'unparsed:', repr(line) + + +def filter_syms(types, symbols): + for sym, type, size, path in symbols: + if type in types: + yield sym, type, size, path + + +def treeify_syms(symbols, strip_prefix=None): + dirs = {} + for sym, type, size, path in symbols: + if path: + path = os.path.normpath(path) + if strip_prefix and path.startswith(strip_prefix): + path = path[len(strip_prefix):] + elif path.startswith('/usr/include'): + path = path.replace('/usr/include', 'usrinclude') + elif path.startswith('/'): + path = path[1:] + + parts = None + # TODO: make segmenting by namespace work. + if False and '::' in sym: + if sym.startswith('vtable for '): + sym = sym[len('vtable for '):] + parts = sym.split('::') + parts.append('[vtable]') + else: + parts = sym.split('::') + parts[0] = '::' + parts[0] + elif path and os.sep in path: + parts = path.split(os.sep) + + if parts: + key = parts.pop() + tree = dirs + try: + for part in parts: + assert part != '', path + if part not in tree: + tree[part] = {} + tree = tree[part] + tree[key] = tree.get(key, 0) + size + except: + print >>sys.stderr, sym, parts, key + raise + else: + key = 'symbols without paths' + if key not in dirs: + dirs[key] = {} + tree = dirs[key] + subkey = 'misc' + if (sym.endswith('::__FUNCTION__') or + sym.endswith('::__PRETTY_FUNCTION__')): + subkey = '__FUNCTION__' + elif sym.startswith('CSWTCH.'): + subkey = 'CSWTCH' + elif '::' in sym: + subkey = sym[0:sym.find('::') + 2] + else: + print >>sys.stderr, 'unbucketed (no path?):', sym, type, size, path + tree[subkey] = tree.get(subkey, 0) + size + return dirs + + +def jsonify_tree(tree, name): + children = [] + total = 0 + files = 0 + + for key, val in tree.iteritems(): + if isinstance(val, dict): + subtree = jsonify_tree(val, key) + total += subtree['data']['$area'] + children.append(subtree) + else: + total += val + children.append({ + 'name': key + ' ' + format_bytes(val), + 'data': { '$area': val } + }) + + children.sort(key=lambda child: -child['data']['$area']) + + return { + 'name': name + ' ' + format_bytes(total), + 'data': { + '$area': total, + }, + 'children': children, + } + + +def dump_nm(nmfile, strip_prefix): + dirs = treeify_syms(parse_nm(nmfile), strip_prefix) + print 'var kTree = ' + json.dumps(jsonify_tree(dirs, '/'), indent=2) + + +def parse_objdump(input): + """Parse objdump -h output.""" + sec_re = re.compile('^\d+ (\S+) +([0-9a-z]+)') + sections = [] + debug_sections = [] + + for line in input: + line = line.strip() + match = sec_re.match(line) + if match: + name, size = match.groups() + if name.startswith('.'): + name = name[1:] + if name.startswith('debug_'): + name = name[len('debug_'):] + debug_sections.append((name, int(size, 16))) + else: + sections.append((name, int(size, 16))) + continue + return sections, debug_sections + + +def jsonify_sections(name, sections): + children = [] + total = 0 + for section, size in sections: + children.append({ + 'name': section + ' ' + format_bytes(size), + 'data': { '$area': size } + }) + total += size + + children.sort(key=lambda child: -child['data']['$area']) + + return { + 'name': name + ' ' + format_bytes(total), + 'data': { '$area': total }, + 'children': children + } + + +def dump_sections(): + sections, debug_sections = parse_objdump(open('objdump.out')) + sections = jsonify_sections('sections', sections) + debug_sections = jsonify_sections('debug', debug_sections) + print 'var kTree = ' + json.dumps({ + 'name': 'top', + 'data': { '$area': sections['data']['$area'] + + debug_sections['data']['$area'] }, + 'children': [ debug_sections, sections ]}) + + +usage="""%prog [options] MODE + +Modes are: + syms: output symbols json suitable for a treemap + dump: print symbols sorted by size (pipe to head for best output) + sections: output binary sections json suitable for a treemap + +nm output passed to --nm-output should from running a command +like the following (note, can take a long time -- 30 minutes): + nm -C -S -l /path/to/binary > nm.out + +objdump output passed to --objdump-output should be from a command +like: + objdump -h /path/to/binary > objdump.out""" + +def main(): + parser = optparse.OptionParser(usage=usage) + parser.add_option('--nm-output', action='store', dest='nmpath', + metavar='PATH', default='nm.out', + help='path to nm output [default=nm.out]') + parser.add_option('--objdump-output', action='store', dest='objdump', + metavar='PATH', default='objdump.out', + help='path to objdump output [default=objdump.out]') + parser.add_option('--strip-prefix', metavar='PATH', action='store', + help='strip PATH prefix from paths; e.g. /path/to/src/root') + parser.add_option('--filter', action='store', + help='include only symbols/files matching FILTER') + opts, args = parser.parse_args() + + if len(args) != 1: + parser.print_usage() + sys.exit(1) + + mode = args[0] + if mode == 'syms': + nmfile = open(opts.nmpath, 'r') + dump_nm(nmfile, strip_prefix=opts.strip_prefix) + elif mode == 'sections': + dump_sections() + elif mode == 'dump': + nmfile = open(opts.nmpath, 'r') + syms = list(parse_nm(nmfile)) + # a list of (sym, type, size, path); sort by size. + syms.sort(key=lambda x: -x[2]) + total = 0 + for sym, type, size, path in syms: + if type in ('b', 'w'): + continue # skip bss and weak symbols + if path is None: + path = '' + if opts.filter and not (opts.filter in sym or opts.filter in path): + continue + print '%6s %s (%s) %s' % (format_bytes(size), sym, + symbol_type_to_human(type), path) + total += size + print '%6s %s' % (format_bytes(total), 'total'), + else: + print 'unknown mode' + parser.print_usage() + +if __name__ == '__main__': + main() diff --git a/digsby/build/buildutil/bloattrack/bloattrack.py b/digsby/build/buildutil/bloattrack/bloattrack.py new file mode 100644 index 0000000..bed6230 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/bloattrack.py @@ -0,0 +1,96 @@ +import os.path +import sys +import json +import subprocess +import re +from cache import file_contents, cache + +thisdir = os.path.abspath(os.path.dirname(__file__)) +SIZER_EXE = os.path.join(thisdir, 'sizer', 'Sizer.exe') + +def run(*args): + proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = proc.communicate() + if proc.returncode != 0: + print >> sys.stderr, stderr + raise Exception('Error running %r' % (args,)) + return stdout + +if os.name == 'nt': + import ctypes + UnDecorateSymbolName = ctypes.windll.dbghelp.UnDecorateSymbolName + N = 1000 + _outstr = ctypes.create_string_buffer(N) + def unmangle(symbol): + if not UnDecorateSymbolName(symbol, ctypes.byref(_outstr), N, 0): + raise ctypes.WinError() + return _outstr.value + +def get_win32_symbols(filepath): + assert os.path.isfile(SIZER_EXE) + return cache(lambda: run(SIZER_EXE, filepath), + dirname='symanalysis', + hashelems=('get_win32_symbols', + file_contents(SIZER_EXE), + file_contents(filepath)))['val'] + +# pattern for matching dumpbin /EXPORTS output +dumpbin_export_pattern = re.compile('''\s* + (?P\d+) \s+ + (?P\S+) \s+ + (?P\S+) \s+ + (?P\?\S+) + ''', re.VERBOSE) + +# sizer.exe output matcher +sizer_export_pattern = re.compile(''' + (?P\d+) \s+ + (?P\S+) \s+ + (?P\S+) +''', re.VERBOSE) + +def parse_sizer(sizer_output): + datatype = None + for line in sizer_output.split('\n'): + line = line.strip() + if not line: + continue + + if line == 'CODE:': + datatype = 't' + elif line == 'DATA:': + datatype = 'd' + else: + m = sizer_export_pattern.match(line) + if m is not None: + assert datatype is not None + yield m.group('symbol'), datatype, int(m.group('size')), m.group('file') + else: + print >> sys.stderr, 'warning: ignoring line %r' % line + +def json_tree(binary): + import bloat + dirs = bloat.treeify_syms(parse_sizer(get_win32_symbols(binary))) + + json_txt = 'var kTree = ' + json.dumps(bloat.jsonify_tree(dirs, '/'), indent=2) + return json_txt + +def json_for_svn_url(url, rev): + from svntools import svn_download + info = svn_download(url, rev) + local_file = info['path'] + assert os.path.isfile(local_file), 'file was not created: %r' % local_file + lowerurl = url.lower() + if lowerurl.endswith('.dll') or lowerurl.endswith('.exe'): + svn_download(url[:-4] + '.pdb') + + return dict(json=json_tree(local_file), rev=info['rev']) + +def main(): + binary = sys.argv[1] + json_txt = json_tree(binary) + open(os.path.join(thisdir, 'demo.json'), 'wb').write(json_txt) + +if __name__ == '__main__': + main() + diff --git a/digsby/build/buildutil/bloattrack/cache.py b/digsby/build/buildutil/bloattrack/cache.py new file mode 100644 index 0000000..2467e80 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/cache.py @@ -0,0 +1,66 @@ + +import os.path +from hashlib import sha1 + +thisdir = os.path.abspath(os.path.dirname(__file__)) + +def file_contents(f): + return open(f, 'rb').read() + +class DiskCache(object): + def __init__(self, cachedir=None): + if cachedir is None: + cachedir = os.path.join(thisdir, '.cache') + self.cachedir = cachedir + if not os.path.isdir(cachedir): + os.makedirs(cachedir) + + def cachepath(self, key): + pth = os.path.abspath(os.path.join(self.cachedir, key)) + return pth + + def get(self, key): + cachepath = self.cachepath(key) + if os.path.isfile(cachepath): + return file_contents(cachepath) + + def set(self, key, value): + cachepath = self.cachepath(key) + ensure_dir_exists(cachepath) + open(cachepath, 'wb').write(value) + +def ensure_dir_exists(path): + dirname = os.path.dirname(path) + assert not os.path.isfile(dirname) + if not os.path.isdir(dirname): + os.makedirs(dirname) + assert os.path.isdir(dirname) + +_disk_cache = DiskCache() + +def sha(*elems): + return sha1(''.join(elems)).hexdigest() + +def _make_cachekey(dirname, hashelems, filename=None): + path = os.path.join(dirname, + sha(*hashelems)) + + if filename is not None: + path = os.path.join(path, filename) + + return path + + +def cache(val_func, dirname, hashelems, filename=None): + + assert callable(val_func) + + cachekey = _make_cachekey(dirname, hashelems, filename) + val = _disk_cache.get(cachekey) + if val is None: + val = val_func() + _disk_cache.set(cachekey, val) + + pth = _disk_cache.cachepath(cachekey) + return dict(val=val, path=pth) + diff --git a/digsby/build/buildutil/bloattrack/demo.json b/digsby/build/buildutil/bloattrack/demo.json new file mode 100644 index 0000000..981b4af --- /dev/null +++ b/digsby/build/buildutil/bloattrack/demo.json @@ -0,0 +1,3362 @@ +var kTree = { + "data": { + "$area": 2645535 + }, + "name": "/ 2.6m", + "children": [ + { + "data": { + "$area": 2378473 + }, + "name": "default 2.4m", + "children": [ + { + "data": { + "$area": 354156 + }, + "name": "rendering 354.2k", + "children": [ + { + "data": { + "$area": 138273 + }, + "name": "RenderLayer_1.o 138.3k" + }, + { + "data": { + "$area": 26023 + }, + "name": "RenderBlock_1.o 26.0k" + }, + { + "data": { + "$area": 21203 + }, + "name": "RenderBlockLineLayout_1.o 21.2k" + }, + { + "data": { + "$area": 16017 + }, + "name": "RenderBoxModelObject_1.o 16.0k" + }, + { + "data": { + "$area": 14436 + }, + "name": "RenderBox_1.o 14.4k" + }, + { + "data": { + "$area": 13719 + }, + "name": "RenderObject_1.o 13.7k" + }, + { + "data": { + "$area": 8584 + }, + "name": "InlineFlowBox_1.o 8.6k" + }, + { + "data": { + "$area": 8365 + }, + "name": "InlineTextBox_1.o 8.4k" + }, + { + "data": { + "$area": 8237 + }, + "name": "RenderListMarker_1.o 8.2k" + }, + { + "data": { + "$area": 8171 + }, + "name": "RenderFlexibleBox_1.o 8.2k" + }, + { + "data": { + "$area": 6895 + }, + "name": "RenderText_1.o 6.9k" + }, + { + "data": { + "$area": 6354 + }, + "name": "RenderTreeAsText_1.o 6.4k" + }, + { + "data": { + "$area": 6313 + }, + "name": "RenderTableCell_1.o 6.3k" + }, + { + "data": { + "$area": 5860 + }, + "name": "RenderTableSection_1.o 5.9k" + }, + { + "data": { + "$area": 5607 + }, + "name": "AutoTableLayout_1.o 5.6k" + }, + { + "data": { + "$area": 5313 + }, + "name": "RenderEmbeddedObject_1.o 5.3k" + }, + { + "data": { + "$area": 4894 + }, + "name": "RenderInline_1.o 4.9k" + }, + { + "data": { + "$area": 4536 + }, + "name": "style 4.5k", + "children": [ + { + "data": { + "$area": 3847 + }, + "name": "RenderStyle_1.o 3.8k" + }, + { + "data": { + "$area": 689 + }, + "name": "FillLayer_1.o 689" + } + ] + }, + { + "data": { + "$area": 4494 + }, + "name": "RenderTable_1.o 4.5k" + }, + { + "data": { + "$area": 3845 + }, + "name": "RenderView_1.o 3.8k" + }, + { + "data": { + "$area": 2968 + }, + "name": "RenderCounter_1.o 3.0k" + }, + { + "data": { + "$area": 2710 + }, + "name": "RenderListBox_1.o 2.7k" + }, + { + "data": { + "$area": 2600 + }, + "name": "RenderFrameSet_1.o 2.6k" + }, + { + "data": { + "$area": 2564 + }, + "name": "RenderImage_1.o 2.6k" + }, + { + "data": { + "$area": 2260 + }, + "name": "RenderFileUploadControl_1.o 2.3k" + }, + { + "data": { + "$area": 2225 + }, + "name": "RenderFieldset_1.o 2.2k" + }, + { + "data": { + "$area": 2132 + }, + "name": "RenderTextControl_1.o 2.1k" + }, + { + "data": { + "$area": 2104 + }, + "name": "RenderTextControlSingleLine_1.o 2.1k" + }, + { + "data": { + "$area": 1900 + }, + "name": "RenderTheme_1.o 1.9k" + }, + { + "data": { + "$area": 1802 + }, + "name": "FixedTableLayout_1.o 1.8k" + }, + { + "data": { + "$area": 1801 + }, + "name": "RenderMenuList_1.o 1.8k" + }, + { + "data": { + "$area": 1417 + }, + "name": "RenderScrollbar_1.o 1.4k" + }, + { + "data": { + "$area": 1407 + }, + "name": "RenderWidget_1.o 1.4k" + }, + { + "data": { + "$area": 1231 + }, + "name": "RenderObjectChildList_1.o 1.2k" + }, + { + "data": { + "$area": 1139 + }, + "name": "RenderSlider_1.o 1.1k" + }, + { + "data": { + "$area": 1138 + }, + "name": "RenderLineBoxList_1.o 1.1k" + }, + { + "data": { + "$area": 1125 + }, + "name": "RenderMarquee_1.o 1.1k" + }, + { + "data": { + "$area": 787 + }, + "name": "EllipsisBox_1.o 787" + }, + { + "data": { + "$area": 714 + }, + "name": "RenderReplaced_1.o 714" + }, + { + "data": { + "$area": 677 + }, + "name": "RenderListItem_1.o 677" + }, + { + "data": { + "$area": 651 + }, + "name": "RenderTableRow_1.o 651" + }, + { + "data": { + "$area": 595 + }, + "name": "RenderApplet_1.o 595" + }, + { + "data": { + "$area": 541 + }, + "name": "LayoutState_1.o 541" + }, + { + "data": { + "$area": 529 + }, + "name": "TextControlInnerElements_1.o 529" + } + ] + }, + { + "data": { + "$area": 345399 + }, + "name": "css 345.4k", + "children": [ + { + "data": { + "$area": 161900 + }, + "name": "CSSParser_1.o 161.9k" + }, + { + "data": { + "$area": 82159 + }, + "name": "CSSStyleSelector_1.o 82.2k" + }, + { + "data": { + "$area": 31320 + }, + "name": "CSSComputedStyleDeclaration_1.o 31.3k" + }, + { + "data": { + "$area": 14523 + }, + "name": "CSSSelector_1.o 14.5k" + }, + { + "data": { + "$area": 11513 + }, + "name": "MediaQuery_1.o 11.5k" + }, + { + "data": { + "$area": 7446 + }, + "name": "CSSMutableStyleDeclaration_1.o 7.4k" + }, + { + "data": { + "$area": 6000 + }, + "name": "CSSFontSelector_1.o 6.0k" + }, + { + "data": { + "$area": 4789 + }, + "name": "CSSPrimitiveValue_1.o 4.8k" + }, + { + "data": { + "$area": 3023 + }, + "name": "CSSGradientValue_1.o 3.0k" + }, + { + "data": { + "$area": 2060 + }, + "name": "MediaQueryEvaluator_1.o 2.1k" + }, + { + "data": { + "$area": 2032 + }, + "name": "CSSPropertyLonghand_1.o 2.0k" + }, + { + "data": { + "$area": 1759 + }, + "name": "MediaList_1.o 1.8k" + }, + { + "data": { + "$area": 1580 + }, + "name": "MediaQueryExp_1.o 1.6k" + }, + { + "data": { + "$area": 1416 + }, + "name": "MediaFeatureNames_1.o 1.4k" + }, + { + "data": { + "$area": 1220 + }, + "name": "WebKitCSSTransformValue_1.o 1.2k" + }, + { + "data": { + "$area": 1115 + }, + "name": "CSSBorderImageValue_1.o 1.1k" + }, + { + "data": { + "$area": 1068 + }, + "name": "FontValue_1.o 1.1k" + }, + { + "data": { + "$area": 1003 + }, + "name": "CSSParserValues_1.o 1.0k" + }, + { + "data": { + "$area": 999 + }, + "name": "ShadowValue_1.o 999" + }, + { + "data": { + "$area": 835 + }, + "name": "CSSImportRule_1.o 835" + }, + { + "data": { + "$area": 812 + }, + "name": "WebKitCSSMatrix_1.o 812" + }, + { + "data": { + "$area": 716 + }, + "name": "CSSHelper_1.o 716" + }, + { + "data": { + "$area": 704 + }, + "name": "CSSTimingFunctionValue_1.o 704" + }, + { + "data": { + "$area": 702 + }, + "name": "WebKitCSSKeyframeRule_1.o 702" + }, + { + "data": { + "$area": 679 + }, + "name": "CSSMediaRule_1.o 679" + }, + { + "data": { + "$area": 637 + }, + "name": "CSSFontFaceSource_1.o 637" + }, + { + "data": { + "$area": 610 + }, + "name": "CSSSegmentedFontFace_1.o 610" + }, + { + "data": { + "$area": 593 + }, + "name": "CSSStyleSheet_1.o 593" + }, + { + "data": { + "$area": 592 + }, + "name": "StyleMedia_1.o 592" + }, + { + "data": { + "$area": 535 + }, + "name": "WebKitCSSKeyframesRule_1.o 535" + }, + { + "data": { + "$area": 532 + }, + "name": "CSSReflectValue_1.o 532" + }, + { + "data": { + "$area": 527 + }, + "name": "CSSStyleDeclaration_1.o 527" + } + ] + }, + { + "data": { + "$area": 211266 + }, + "name": "editing 211.3k", + "children": [ + { + "data": { + "$area": 27443 + }, + "name": "ApplyStyleCommand_1.o 27.4k" + }, + { + "data": { + "$area": 19202 + }, + "name": "ReplaceSelectionCommand_1.o 19.2k" + }, + { + "data": { + "$area": 16697 + }, + "name": "CompositeEditCommand_1.o 16.7k" + }, + { + "data": { + "$area": 16597 + }, + "name": "SelectionController_1.o 16.6k" + }, + { + "data": { + "$area": 16001 + }, + "name": "markup_1.o 16.0k" + }, + { + "data": { + "$area": 15457 + }, + "name": "Editor_1.o 15.5k" + }, + { + "data": { + "$area": 13842 + }, + "name": "DeleteSelectionCommand_1.o 13.8k" + }, + { + "data": { + "$area": 10763 + }, + "name": "visible_units_1.o 10.8k" + }, + { + "data": { + "$area": 8217 + }, + "name": "htmlediting_1.o 8.2k" + }, + { + "data": { + "$area": 7689 + }, + "name": "IndentOutdentCommand_1.o 7.7k" + }, + { + "data": { + "$area": 7530 + }, + "name": "InsertListCommand_1.o 7.5k" + }, + { + "data": { + "$area": 6540 + }, + "name": "VisibleSelection_1.o 6.5k" + }, + { + "data": { + "$area": 5881 + }, + "name": "TextIterator_1.o 5.9k" + }, + { + "data": { + "$area": 5863 + }, + "name": "TypingCommand_1.o 5.9k" + }, + { + "data": { + "$area": 5158 + }, + "name": "VisiblePosition_1.o 5.2k" + }, + { + "data": { + "$area": 4541 + }, + "name": "DeleteButtonController_1.o 4.5k" + }, + { + "data": { + "$area": 4515 + }, + "name": "InsertTextCommand_1.o 4.5k" + }, + { + "data": { + "$area": 4219 + }, + "name": "InsertParagraphSeparatorCommand_1.o 4.2k" + }, + { + "data": { + "$area": 3779 + }, + "name": "FormatBlockCommand_1.o 3.8k" + }, + { + "data": { + "$area": 3300 + }, + "name": "BreakBlockquoteCommand_1.o 3.3k" + }, + { + "data": { + "$area": 2453 + }, + "name": "InsertLineBreakCommand_1.o 2.5k" + }, + { + "data": { + "$area": 2101 + }, + "name": "HTMLInterchange_1.o 2.1k" + }, + { + "data": { + "$area": 1624 + }, + "name": "EditorCommand_1.o 1.6k" + }, + { + "data": { + "$area": 733 + }, + "name": "CreateLinkCommand_1.o 733" + }, + { + "data": { + "$area": 592 + }, + "name": "SmartReplaceICU_1.o 592" + }, + { + "data": { + "$area": 529 + }, + "name": "MergeIdenticalElementsCommand_1.o 529" + } + ] + }, + { + "data": { + "$area": 188280 + }, + "name": "DerivedSources 188.3k", + "children": [ + { + "data": { + "$area": 60151 + }, + "name": "Grammar_1.o 60.2k" + }, + { + "data": { + "$area": 20320 + }, + "name": "CSSGrammar_1.o 20.3k" + }, + { + "data": { + "$area": 15098 + }, + "name": "JSDOMWindow_1.o 15.1k" + }, + { + "data": { + "$area": 11609 + }, + "name": "UserAgentStyleSheetsData_1.o 11.6k" + }, + { + "data": { + "$area": 11220 + }, + "name": "JSInspectorBackend_1.o 11.2k" + }, + { + "data": { + "$area": 10189 + }, + "name": "HTMLNames_1.o 10.2k" + }, + { + "data": { + "$area": 7525 + }, + "name": "JSDocument_1.o 7.5k" + }, + { + "data": { + "$area": 4750 + }, + "name": "JSElement_1.o 4.8k" + }, + { + "data": { + "$area": 4734 + }, + "name": "JSHTMLElementWrapperFactory_1.o 4.7k" + }, + { + "data": { + "$area": 4476 + }, + "name": "HTMLElementFactory_1.o 4.5k" + }, + { + "data": { + "$area": 2650 + }, + "name": "JSNode_1.o 2.6k" + }, + { + "data": { + "$area": 2444 + }, + "name": "JSXMLHttpRequest_1.o 2.4k" + }, + { + "data": { + "$area": 2306 + }, + "name": "JSWebKitCSSMatrix_1.o 2.3k" + }, + { + "data": { + "$area": 1935 + }, + "name": "JSCanvasRenderingContext2D_1.o 1.9k" + }, + { + "data": { + "$area": 1605 + }, + "name": "JSMouseEvent_1.o 1.6k" + }, + { + "data": { + "$area": 1360 + }, + "name": "JSNavigator_1.o 1.4k" + }, + { + "data": { + "$area": 1340 + }, + "name": "JSCSSPrimitiveValue_1.o 1.3k" + }, + { + "data": { + "$area": 1224 + }, + "name": "JSEvent_1.o 1.2k" + }, + { + "data": { + "$area": 1175 + }, + "name": "JSMutationEvent_1.o 1.2k" + }, + { + "data": { + "$area": 1174 + }, + "name": "JSKeyboardEvent_1.o 1.2k" + }, + { + "data": { + "$area": 1146 + }, + "name": "JSWheelEvent_1.o 1.1k" + }, + { + "data": { + "$area": 1144 + }, + "name": "JSWorkerContext_1.o 1.1k" + }, + { + "data": { + "$area": 1138 + }, + "name": "JSDOMImplementation_1.o 1.1k" + }, + { + "data": { + "$area": 1108 + }, + "name": "JSRange_1.o 1.1k" + }, + { + "data": { + "$area": 1108 + }, + "name": "JSCharacterData_1.o 1.1k" + }, + { + "data": { + "$area": 1108 + }, + "name": "JSDOMCoreException_1.o 1.1k" + }, + { + "data": { + "$area": 1080 + }, + "name": "JSAbstractWorker_1.o 1.1k" + }, + { + "data": { + "$area": 1078 + }, + "name": "JSMessagePort_1.o 1.1k" + }, + { + "data": { + "$area": 940 + }, + "name": "JSErrorEvent_1.o 940" + }, + { + "data": { + "$area": 814 + }, + "name": "JSWebKitAnimationEvent_1.o 814" + }, + { + "data": { + "$area": 814 + }, + "name": "JSWebKitTransitionEvent_1.o 814" + }, + { + "data": { + "$area": 793 + }, + "name": "JSProgressEvent_1.o 793" + }, + { + "data": { + "$area": 741 + }, + "name": "JSCompositionEvent_1.o 741" + }, + { + "data": { + "$area": 741 + }, + "name": "JSTextEvent_1.o 741" + }, + { + "data": { + "$area": 692 + }, + "name": "JSBeforeLoadEvent_1.o 692" + }, + { + "data": { + "$area": 685 + }, + "name": "JSUIEvent_1.o 685" + }, + { + "data": { + "$area": 668 + }, + "name": "JSPopStateEvent_1.o 668" + }, + { + "data": { + "$area": 662 + }, + "name": "JSCSSStyleSheet_1.o 662" + }, + { + "data": { + "$area": 656 + }, + "name": "JSDOMSelection_1.o 656" + }, + { + "data": { + "$area": 616 + }, + "name": "JSCSSStyleDeclaration_1.o 616" + }, + { + "data": { + "$area": 578 + }, + "name": "JSPageTransitionEvent_1.o 578" + }, + { + "data": { + "$area": 549 + }, + "name": "JSClipboard_1.o 549" + }, + { + "data": { + "$area": 536 + }, + "name": "JSHTMLAllCollection_1.o 536" + }, + { + "data": { + "$area": 536 + }, + "name": "JSXMLHttpRequestUpload_1.o 536" + }, + { + "data": { + "$area": 536 + }, + "name": "JSHTMLCollection_1.o 536" + }, + { + "data": { + "$area": 528 + }, + "name": "JSNamedNodeMap_1.o 528" + } + ] + }, + { + "data": { + "$area": 158188 + }, + "name": "html 158.2k", + "children": [ + { + "data": { + "$area": 30259 + }, + "name": "HTMLDocumentParser_1.o 30.3k" + }, + { + "data": { + "$area": 19216 + }, + "name": "HTMLDocument_1.o 19.2k" + }, + { + "data": { + "$area": 13380 + }, + "name": "HTMLInputElement_1.o 13.4k" + }, + { + "data": { + "$area": 12339 + }, + "name": "LegacyHTMLTreeConstructor_1.o 12.3k" + }, + { + "data": { + "$area": 11124 + }, + "name": "HTML5Lexer_1.o 11.1k" + }, + { + "data": { + "$area": 10352 + }, + "name": "HTMLElement_1.o 10.4k" + }, + { + "data": { + "$area": 7772 + }, + "name": "HTMLViewSourceDocument_1.o 7.8k" + }, + { + "data": { + "$area": 7720 + }, + "name": "PreloadScanner_1.o 7.7k" + }, + { + "data": { + "$area": 5521 + }, + "name": "HTMLTableElement_1.o 5.5k" + }, + { + "data": { + "$area": 4774 + }, + "name": "HTMLFormElement_1.o 4.8k" + }, + { + "data": { + "$area": 3044 + }, + "name": "HTMLBodyElement_1.o 3.0k" + }, + { + "data": { + "$area": 2944 + }, + "name": "HTMLAnchorElement_1.o 2.9k" + }, + { + "data": { + "$area": 2448 + }, + "name": "HTMLLinkElement_1.o 2.4k" + }, + { + "data": { + "$area": 2354 + }, + "name": "canvas 2.4k", + "children": [ + { + "data": { + "$area": 2354 + }, + "name": "CanvasRenderingContext2D_1.o 2.4k" + } + ] + }, + { + "data": { + "$area": 1838 + }, + "name": "HTMLTextAreaElement_1.o 1.8k" + }, + { + "data": { + "$area": 1793 + }, + "name": "HTMLFrameSetElement_1.o 1.8k" + }, + { + "data": { + "$area": 1730 + }, + "name": "HTML5TreeBuilder_1.o 1.7k" + }, + { + "data": { + "$area": 1532 + }, + "name": "HTML5EntityParser_1.o 1.5k" + }, + { + "data": { + "$area": 1473 + }, + "name": "HTMLImageElement_1.o 1.5k" + }, + { + "data": { + "$area": 1456 + }, + "name": "HTMLEmbedElement_1.o 1.5k" + }, + { + "data": { + "$area": 1343 + }, + "name": "HTMLCollection_1.o 1.3k" + }, + { + "data": { + "$area": 1294 + }, + "name": "HTMLSelectElement_1.o 1.3k" + }, + { + "data": { + "$area": 1293 + }, + "name": "HTMLFormCollection_1.o 1.3k" + }, + { + "data": { + "$area": 1021 + }, + "name": "HTMLHRElement_1.o 1.0k" + }, + { + "data": { + "$area": 972 + }, + "name": "HTMLAreaElement_1.o 972" + }, + { + "data": { + "$area": 965 + }, + "name": "HTMLObjectElement_1.o 965" + }, + { + "data": { + "$area": 927 + }, + "name": "HTMLFrameElementBase_1.o 927" + }, + { + "data": { + "$area": 860 + }, + "name": "HTMLTablePartElement_1.o 860" + }, + { + "data": { + "$area": 826 + }, + "name": "HTMLAppletElement_1.o 826" + }, + { + "data": { + "$area": 722 + }, + "name": "HTML5DocumentParser_1.o 722" + }, + { + "data": { + "$area": 706 + }, + "name": "HTMLNameCollection_1.o 706" + }, + { + "data": { + "$area": 696 + }, + "name": "ValidityState_1.o 696" + }, + { + "data": { + "$area": 672 + }, + "name": "HTMLOptionElement_1.o 672" + }, + { + "data": { + "$area": 619 + }, + "name": "HTMLMarqueeElement_1.o 619" + }, + { + "data": { + "$area": 602 + }, + "name": "HTMLMapElement_1.o 602" + }, + { + "data": { + "$area": 543 + }, + "name": "HTMLButtonElement_1.o 543" + }, + { + "data": { + "$area": 532 + }, + "name": "DateComponents_1.o 532" + }, + { + "data": { + "$area": 526 + }, + "name": "HTMLParamElement_1.o 526" + } + ] + }, + { + "data": { + "$area": 154335 + }, + "name": "yarr 154.3k", + "children": [ + { + "data": { + "$area": 135943 + }, + "name": "RegexCompiler_1.o 135.9k" + }, + { + "data": { + "$area": 18392 + }, + "name": "RegexJIT_1.o 18.4k" + } + ] + }, + { + "data": { + "$area": 131096 + }, + "name": "API 131.1k", + "children": [ + { + "data": { + "$area": 131096 + }, + "name": "JSCallbackConstructor_1.o 131.1k" + } + ] + }, + { + "data": { + "$area": 127277 + }, + "name": "runtime 127.3k", + "children": [ + { + "data": { + "$area": 19402 + }, + "name": "ArrayPrototype_1.o 19.4k" + }, + { + "data": { + "$area": 18474 + }, + "name": "StringPrototype_1.o 18.5k" + }, + { + "data": { + "$area": 11629 + }, + "name": "JSGlobalObject_1.o 11.6k" + }, + { + "data": { + "$area": 9699 + }, + "name": "JSONObject_1.o 9.7k" + }, + { + "data": { + "$area": 7743 + }, + "name": "NumberPrototype_1.o 7.7k" + }, + { + "data": { + "$area": 7023 + }, + "name": "ObjectConstructor_1.o 7.0k" + }, + { + "data": { + "$area": 5268 + }, + "name": "LiteralParser_1.o 5.3k" + }, + { + "data": { + "$area": 4876 + }, + "name": "JSGlobalData_1.o 4.9k" + }, + { + "data": { + "$area": 4723 + }, + "name": "JSArray_1.o 4.7k" + }, + { + "data": { + "$area": 4609 + }, + "name": "JSGlobalObjectFunctions_1.o 4.6k" + }, + { + "data": { + "$area": 3655 + }, + "name": "JSObject_1.o 3.7k" + }, + { + "data": { + "$area": 3585 + }, + "name": "DateConstructor_1.o 3.6k" + }, + { + "data": { + "$area": 2836 + }, + "name": "Operations_1.o 2.8k" + }, + { + "data": { + "$area": 2746 + }, + "name": "JSString_1.o 2.7k" + }, + { + "data": { + "$area": 2727 + }, + "name": "DatePrototype_1.o 2.7k" + }, + { + "data": { + "$area": 1606 + }, + "name": "Executable_1.o 1.6k" + }, + { + "data": { + "$area": 1561 + }, + "name": "RegExpPrototype_1.o 1.6k" + }, + { + "data": { + "$area": 1499 + }, + "name": "FunctionConstructor_1.o 1.5k" + }, + { + "data": { + "$area": 1453 + }, + "name": "CommonIdentifiers_1.o 1.5k" + }, + { + "data": { + "$area": 1411 + }, + "name": "ExceptionHelpers_1.o 1.4k" + }, + { + "data": { + "$area": 1398 + }, + "name": "FunctionPrototype_1.o 1.4k" + }, + { + "data": { + "$area": 1288 + }, + "name": "Arguments_1.o 1.3k" + }, + { + "data": { + "$area": 1087 + }, + "name": "RegExpConstructor_1.o 1.1k" + }, + { + "data": { + "$area": 1071 + }, + "name": "MathObject_1.o 1.1k" + }, + { + "data": { + "$area": 1021 + }, + "name": "RegExpObject_1.o 1.0k" + }, + { + "data": { + "$area": 924 + }, + "name": "JSFunction_1.o 924" + }, + { + "data": { + "$area": 851 + }, + "name": "ObjectPrototype_1.o 851" + }, + { + "data": { + "$area": 724 + }, + "name": "UString_1.o 724" + }, + { + "data": { + "$area": 652 + }, + "name": "SmallStrings_1.o 652" + }, + { + "data": { + "$area": 622 + }, + "name": "Structure_1.o 622" + }, + { + "data": { + "$area": 591 + }, + "name": "Error_1.o 591" + }, + { + "data": { + "$area": 523 + }, + "name": "DateConversion_1.o 523" + } + ] + }, + { + "data": { + "$area": 118994 + }, + "name": "platform 119.0k", + "children": [ + { + "data": { + "$area": 36976 + }, + "name": "graphics 37.0k", + "children": [ + { + "data": { + "$area": 9190 + }, + "name": "transforms 9.2k", + "children": [ + { + "data": { + "$area": 6325 + }, + "name": "TransformationMatrix_1.o 6.3k" + }, + { + "data": { + "$area": 1022 + }, + "name": "RotateTransformOperation_1.o 1.0k" + }, + { + "data": { + "$area": 656 + }, + "name": "MatrixTransformOperation_1.o 656" + }, + { + "data": { + "$area": 627 + }, + "name": "TranslateTransformOperation_1.o 627" + }, + { + "data": { + "$area": 560 + }, + "name": "PerspectiveTransformOperation_1.o 560" + } + ] + }, + { + "data": { + "$area": 6226 + }, + "name": "wx 6.2k", + "children": [ + { + "data": { + "$area": 4044 + }, + "name": "UniscribeController_1.o 4.0k" + }, + { + "data": { + "$area": 2182 + }, + "name": "ImageWx_1.o 2.2k" + } + ] + }, + { + "data": { + "$area": 3450 + }, + "name": "GraphicsContext_1.o 3.5k" + }, + { + "data": { + "$area": 3358 + }, + "name": "win 3.4k", + "children": [ + { + "data": { + "$area": 3358 + }, + "name": "FontCacheWin_1.o 3.4k" + } + ] + }, + { + "data": { + "$area": 3304 + }, + "name": "Color_1.o 3.3k" + }, + { + "data": { + "$area": 2296 + }, + "name": "FontCache_1.o 2.3k" + }, + { + "data": { + "$area": 2115 + }, + "name": "Path_1.o 2.1k" + }, + { + "data": { + "$area": 1805 + }, + "name": "GlyphPageTreeNode_1.o 1.8k" + }, + { + "data": { + "$area": 1697 + }, + "name": "WidthIterator_1.o 1.7k" + }, + { + "data": { + "$area": 1536 + }, + "name": "FontFastPath_1.o 1.5k" + }, + { + "data": { + "$area": 1145 + }, + "name": "Image_1.o 1.1k" + }, + { + "data": { + "$area": 854 + }, + "name": "BitmapImage_1.o 854" + } + ] + }, + { + "data": { + "$area": 20972 + }, + "name": "network 21.0k", + "children": [ + { + "data": { + "$area": 8719 + }, + "name": "curl 8.7k", + "children": [ + { + "data": { + "$area": 8194 + }, + "name": "ResourceHandleManager_1.o 8.2k" + }, + { + "data": { + "$area": 525 + }, + "name": "FormDataStreamCurl_1.o 525" + } + ] + }, + { + "data": { + "$area": 5273 + }, + "name": "ResourceResponseBase_1.o 5.3k" + }, + { + "data": { + "$area": 2117 + }, + "name": "HTTPParsers_1.o 2.1k" + }, + { + "data": { + "$area": 2015 + }, + "name": "FormData_1.o 2.0k" + }, + { + "data": { + "$area": 1667 + }, + "name": "ResourceRequestBase_1.o 1.7k" + }, + { + "data": { + "$area": 1181 + }, + "name": "ResourceHandle_1.o 1.2k" + } + ] + }, + { + "data": { + "$area": 10742 + }, + "name": "KURL_1.o 10.7k" + }, + { + "data": { + "$area": 9302 + }, + "name": "image-decoders 9.3k", + "children": [ + { + "data": { + "$area": 4203 + }, + "name": "gif 4.2k", + "children": [ + { + "data": { + "$area": 3450 + }, + "name": "GIFImageReader_1.o 3.5k" + }, + { + "data": { + "$area": 753 + }, + "name": "GIFImageDecoder_1.o 753" + } + ] + }, + { + "data": { + "$area": 1810 + }, + "name": "bmp 1.8k", + "children": [ + { + "data": { + "$area": 1810 + }, + "name": "BMPImageReader_1.o 1.8k" + } + ] + }, + { + "data": { + "$area": 1451 + }, + "name": "ico 1.5k", + "children": [ + { + "data": { + "$area": 1451 + }, + "name": "ICOImageDecoder_1.o 1.5k" + } + ] + }, + { + "data": { + "$area": 1281 + }, + "name": "jpeg 1.3k", + "children": [ + { + "data": { + "$area": 1281 + }, + "name": "JPEGImageDecoder_1.o 1.3k" + } + ] + }, + { + "data": { + "$area": 557 + }, + "name": "png 557", + "children": [ + { + "data": { + "$area": 557 + }, + "name": "PNGImageDecoder_1.o 557" + } + ] + } + ] + }, + { + "data": { + "$area": 8242 + }, + "name": "text 8.2k", + "children": [ + { + "data": { + "$area": 2504 + }, + "name": "TextCodecICU_1.o 2.5k" + }, + { + "data": { + "$area": 1796 + }, + "name": "TextBreakIteratorICU_1.o 1.8k" + }, + { + "data": { + "$area": 1380 + }, + "name": "transcoder 1.4k", + "children": [ + { + "data": { + "$area": 1380 + }, + "name": "FontTranscoder_1.o 1.4k" + } + ] + }, + { + "data": { + "$area": 868 + }, + "name": "TextEncoding_1.o 868" + }, + { + "data": { + "$area": 591 + }, + "name": "BidiContext_1.o 591" + }, + { + "data": { + "$area": 554 + }, + "name": "TextCodecLatin1_1.o 554" + }, + { + "data": { + "$area": 549 + }, + "name": "Base64_1.o 549" + } + ] + }, + { + "data": { + "$area": 8215 + }, + "name": "ContextMenu_1.o 8.2k" + }, + { + "data": { + "$area": 6195 + }, + "name": "wx 6.2k", + "children": [ + { + "data": { + "$area": 2734 + }, + "name": "KeyboardEventWx_1.o 2.7k" + }, + { + "data": { + "$area": 1141 + }, + "name": "wxcode 1.1k", + "children": [ + { + "data": { + "$area": 1141 + }, + "name": "win 1.1k", + "children": [ + { + "data": { + "$area": 1141 + }, + "name": "scrollbar_render_1.o 1.1k" + } + ] + } + ] + }, + { + "data": { + "$area": 920 + }, + "name": "ContextMenuWx_1.o 920" + }, + { + "data": { + "$area": 763 + }, + "name": "PopupMenuWx_1.o 763" + }, + { + "data": { + "$area": 637 + }, + "name": "PasteboardWx_1.o 637" + } + ] + }, + { + "data": { + "$area": 4874 + }, + "name": "ThreadGlobalData_1.o 4.9k" + }, + { + "data": { + "$area": 4313 + }, + "name": "sql 4.3k", + "children": [ + { + "data": { + "$area": 3708 + }, + "name": "SQLiteDatabase_1.o 3.7k" + }, + { + "data": { + "$area": 605 + }, + "name": "SQLiteFileSystem_1.o 605" + } + ] + }, + { + "data": { + "$area": 2194 + }, + "name": "ScrollView_1.o 2.2k" + }, + { + "data": { + "$area": 2134 + }, + "name": "ScrollbarThemeComposite_1.o 2.1k" + }, + { + "data": { + "$area": 1821 + }, + "name": "Logging_1.o 1.8k" + }, + { + "data": { + "$area": 1058 + }, + "name": "BlobItem_1.o 1.1k" + }, + { + "data": { + "$area": 752 + }, + "name": "animation 752", + "children": [ + { + "data": { + "$area": 752 + }, + "name": "AnimationList_1.o 752" + } + ] + }, + { + "data": { + "$area": 604 + }, + "name": "LinkHash_1.o 604" + }, + { + "data": { + "$area": 600 + }, + "name": "Length_1.o 600" + } + ] + }, + { + "data": { + "$area": 90776 + }, + "name": "dom 90.8k", + "children": [ + { + "data": { + "$area": 22641 + }, + "name": "Document_1.o 22.6k" + }, + { + "data": { + "$area": 10250 + }, + "name": "Range_1.o 10.3k" + }, + { + "data": { + "$area": 8395 + }, + "name": "Node_1.o 8.4k" + }, + { + "data": { + "$area": 7895 + }, + "name": "XMLDocumentParserLibxml2_1.o 7.9k" + }, + { + "data": { + "$area": 6052 + }, + "name": "ContainerNode_1.o 6.1k" + }, + { + "data": { + "$area": 4573 + }, + "name": "Element_1.o 4.6k" + }, + { + "data": { + "$area": 4458 + }, + "name": "Position_1.o 4.5k" + }, + { + "data": { + "$area": 3975 + }, + "name": "EventNames_1.o 4.0k" + }, + { + "data": { + "$area": 3306 + }, + "name": "TreeWalker_1.o 3.3k" + }, + { + "data": { + "$area": 2845 + }, + "name": "ScriptElement_1.o 2.8k" + }, + { + "data": { + "$area": 2745 + }, + "name": "SelectElement_1.o 2.7k" + }, + { + "data": { + "$area": 2700 + }, + "name": "DOMImplementation_1.o 2.7k" + }, + { + "data": { + "$area": 1505 + }, + "name": "Text_1.o 1.5k" + }, + { + "data": { + "$area": 1264 + }, + "name": "NodeIterator_1.o 1.3k" + }, + { + "data": { + "$area": 1135 + }, + "name": "XMLDocumentParser_1.o 1.1k" + }, + { + "data": { + "$area": 1122 + }, + "name": "ProcessingInstruction_1.o 1.1k" + }, + { + "data": { + "$area": 1113 + }, + "name": "NamedNodeMap_1.o 1.1k" + }, + { + "data": { + "$area": 1034 + }, + "name": "MessagePort_1.o 1.0k" + }, + { + "data": { + "$area": 988 + }, + "name": "StyleElement_1.o 988" + }, + { + "data": { + "$area": 757 + }, + "name": "ViewportArguments_1.o 757" + }, + { + "data": { + "$area": 720 + }, + "name": "PositionIterator_1.o 720" + }, + { + "data": { + "$area": 680 + }, + "name": "StyledElement_1.o 680" + }, + { + "data": { + "$area": 623 + }, + "name": "SelectorNodeList_1.o 623" + } + ] + }, + { + "data": { + "$area": 80917 + }, + "name": "jit 80.9k", + "children": [ + { + "data": { + "$area": 30728 + }, + "name": "JITArithmetic32_64_1.o 30.7k" + }, + { + "data": { + "$area": 16248 + }, + "name": "JITOpcodes32_64_1.o 16.2k" + }, + { + "data": { + "$area": 12750 + }, + "name": "JITPropertyAccess32_64_1.o 12.8k" + }, + { + "data": { + "$area": 9075 + }, + "name": "JITStubs_1.o 9.1k" + }, + { + "data": { + "$area": 7795 + }, + "name": "JIT_1.o 7.8k" + }, + { + "data": { + "$area": 2604 + }, + "name": "JITCall32_64_1.o 2.6k" + }, + { + "data": { + "$area": 1068 + }, + "name": "ThunkGenerators_1.o 1.1k" + }, + { + "data": { + "$area": 649 + }, + "name": "JITOpcodes_1.o 649" + } + ] + }, + { + "data": { + "$area": 76193 + }, + "name": "bindings 76.2k", + "children": [ + { + "data": { + "$area": 76193 + }, + "name": "js 76.2k", + "children": [ + { + "data": { + "$area": 19388 + }, + "name": "SerializedScriptValue_1.o 19.4k" + }, + { + "data": { + "$area": 9395 + }, + "name": "JSDOMWindowCustom_1.o 9.4k" + }, + { + "data": { + "$area": 8087 + }, + "name": "JSCanvasRenderingContext2DCustom_1.o 8.1k" + }, + { + "data": { + "$area": 2443 + }, + "name": "JSDatabaseCustom_1.o 2.4k" + }, + { + "data": { + "$area": 2341 + }, + "name": "JSWorkerContextCustom_1.o 2.3k" + }, + { + "data": { + "$area": 2317 + }, + "name": "JSHistoryCustom_1.o 2.3k" + }, + { + "data": { + "$area": 1971 + }, + "name": "JSDOMWindowBase_1.o 2.0k" + }, + { + "data": { + "$area": 1814 + }, + "name": "JSSQLTransactionCustom_1.o 1.8k" + }, + { + "data": { + "$area": 1801 + }, + "name": "JSXMLHttpRequestCustom_1.o 1.8k" + }, + { + "data": { + "$area": 1658 + }, + "name": "JSCSSStyleDeclarationCustom_1.o 1.7k" + }, + { + "data": { + "$area": 1376 + }, + "name": "JSClipboardCustom_1.o 1.4k" + }, + { + "data": { + "$area": 1256 + }, + "name": "JSSQLTransactionSyncCustom_1.o 1.3k" + }, + { + "data": { + "$area": 1233 + }, + "name": "JSMessageEventCustom_1.o 1.2k" + }, + { + "data": { + "$area": 1232 + }, + "name": "JSOptionConstructor_1.o 1.2k" + }, + { + "data": { + "$area": 1220 + }, + "name": "ScriptController_1.o 1.2k" + }, + { + "data": { + "$area": 1162 + }, + "name": "JSEventListener_1.o 1.2k" + }, + { + "data": { + "$area": 1160 + }, + "name": "JSLocationCustom_1.o 1.2k" + }, + { + "data": { + "$area": 1142 + }, + "name": "WorkerScriptController_1.o 1.1k" + }, + { + "data": { + "$area": 1092 + }, + "name": "JSDOMBinding_1.o 1.1k" + }, + { + "data": { + "$area": 1090 + }, + "name": "JSInspectorFrontendHostCustom_1.o 1.1k" + }, + { + "data": { + "$area": 1060 + }, + "name": "JSLazyEventListener_1.o 1.1k" + }, + { + "data": { + "$area": 977 + }, + "name": "JSHTMLCollectionCustom_1.o 977" + }, + { + "data": { + "$area": 834 + }, + "name": "JSWorkerCustom_1.o 834" + }, + { + "data": { + "$area": 802 + }, + "name": "JSInjectedScriptHostCustom_1.o 802" + }, + { + "data": { + "$area": 789 + }, + "name": "JSDOMWindowShell_1.o 789" + }, + { + "data": { + "$area": 703 + }, + "name": "JSDatabaseSyncCustom_1.o 703" + }, + { + "data": { + "$area": 702 + }, + "name": "JSEventCustom_1.o 702" + }, + { + "data": { + "$area": 690 + }, + "name": "JSDOMGlobalObject_1.o 690" + }, + { + "data": { + "$area": 690 + }, + "name": "JSHTMLDocumentCustom_1.o 690" + }, + { + "data": { + "$area": 679 + }, + "name": "JSSQLResultSetRowListCustom_1.o 679" + }, + { + "data": { + "$area": 665 + }, + "name": "JSWorkerContextErrorHandler_1.o 665" + }, + { + "data": { + "$area": 616 + }, + "name": "JSImageDataCustom_1.o 616" + }, + { + "data": { + "$area": 588 + }, + "name": "JSCallbackData_1.o 588" + }, + { + "data": { + "$area": 576 + }, + "name": "JSImageConstructor_1.o 576" + }, + { + "data": { + "$area": 537 + }, + "name": "ScriptFunctionCall_1.o 537" + }, + { + "data": { + "$area": 533 + }, + "name": "JSCustomSQLStatementErrorCallback_1.o 533" + }, + { + "data": { + "$area": 532 + }, + "name": "ScheduledAction_1.o 532" + }, + { + "data": { + "$area": 529 + }, + "name": "ScriptCallStack_1.o 529" + }, + { + "data": { + "$area": 513 + }, + "name": "JSJavaScriptCallFrameCustom_1.o 513" + } + ] + } + ] + }, + { + "data": { + "$area": 67319 + }, + "name": "page 67.3k", + "children": [ + { + "data": { + "$area": 15467 + }, + "name": "animation 15.5k", + "children": [ + { + "data": { + "$area": 10931 + }, + "name": "AnimationBase_1.o 10.9k" + }, + { + "data": { + "$area": 3333 + }, + "name": "CompositeAnimation_1.o 3.3k" + }, + { + "data": { + "$area": 1203 + }, + "name": "AnimationController_1.o 1.2k" + } + ] + }, + { + "data": { + "$area": 12946 + }, + "name": "EventHandler_1.o 12.9k" + }, + { + "data": { + "$area": 7609 + }, + "name": "Frame_1.o 7.6k" + }, + { + "data": { + "$area": 3715 + }, + "name": "FrameView_1.o 3.7k" + }, + { + "data": { + "$area": 3670 + }, + "name": "DOMWindow_1.o 3.7k" + }, + { + "data": { + "$area": 3559 + }, + "name": "SecurityOrigin_1.o 3.6k" + }, + { + "data": { + "$area": 3017 + }, + "name": "Page_1.o 3.0k" + }, + { + "data": { + "$area": 2901 + }, + "name": "XSSAuditor_1.o 2.9k" + }, + { + "data": { + "$area": 2866 + }, + "name": "ContextMenuController_1.o 2.9k" + }, + { + "data": { + "$area": 2559 + }, + "name": "DragController_1.o 2.6k" + }, + { + "data": { + "$area": 1818 + }, + "name": "FocusController_1.o 1.8k" + }, + { + "data": { + "$area": 1384 + }, + "name": "DOMSelection_1.o 1.4k" + }, + { + "data": { + "$area": 1014 + }, + "name": "FrameTree_1.o 1.0k" + }, + { + "data": { + "$area": 839 + }, + "name": "Chrome_1.o 839" + }, + { + "data": { + "$area": 779 + }, + "name": "UserContentURLPattern_1.o 779" + }, + { + "data": { + "$area": 775 + }, + "name": "WindowFeatures_1.o 775" + }, + { + "data": { + "$area": 696 + }, + "name": "Console_1.o 696" + }, + { + "data": { + "$area": 604 + }, + "name": "OriginAccessEntry_1.o 604" + }, + { + "data": { + "$area": 570 + }, + "name": "Location_1.o 570" + }, + { + "data": { + "$area": 531 + }, + "name": "Navigator_1.o 531" + } + ] + }, + { + "data": { + "$area": 62200 + }, + "name": "loader 62.2k", + "children": [ + { + "data": { + "$area": 28241 + }, + "name": "FrameLoader_1.o 28.2k" + }, + { + "data": { + "$area": 4630 + }, + "name": "icon 4.6k", + "children": [ + { + "data": { + "$area": 4630 + }, + "name": "IconDatabase_1.o 4.6k" + } + ] + }, + { + "data": { + "$area": 4201 + }, + "name": "TextResourceDecoder_1.o 4.2k" + }, + { + "data": { + "$area": 2736 + }, + "name": "DocumentThreadableLoader_1.o 2.7k" + }, + { + "data": { + "$area": 2707 + }, + "name": "loader_1.o 2.7k" + }, + { + "data": { + "$area": 2572 + }, + "name": "MainResourceLoader_1.o 2.6k" + }, + { + "data": { + "$area": 2442 + }, + "name": "HistoryController_1.o 2.4k" + }, + { + "data": { + "$area": 2309 + }, + "name": "ImageDocument_1.o 2.3k" + }, + { + "data": { + "$area": 1856 + }, + "name": "DocumentLoader_1.o 1.9k" + }, + { + "data": { + "$area": 1717 + }, + "name": "DocumentWriter_1.o 1.7k" + }, + { + "data": { + "$area": 1230 + }, + "name": "CrossOriginAccessControl_1.o 1.2k" + }, + { + "data": { + "$area": 1220 + }, + "name": "TextDocument_1.o 1.2k" + }, + { + "data": { + "$area": 1219 + }, + "name": "PluginDocument_1.o 1.2k" + }, + { + "data": { + "$area": 1091 + }, + "name": "DocLoader_1.o 1.1k" + }, + { + "data": { + "$area": 973 + }, + "name": "SubresourceLoader_1.o 973" + }, + { + "data": { + "$area": 899 + }, + "name": "ImageLoader_1.o 899" + }, + { + "data": { + "$area": 570 + }, + "name": "PolicyChecker_1.o 570" + }, + { + "data": { + "$area": 540 + }, + "name": "CachedResource_1.o 540" + }, + { + "data": { + "$area": 526 + }, + "name": "Cache_1.o 526" + }, + { + "data": { + "$area": 521 + }, + "name": "ProgressTracker_1.o 521" + } + ] + }, + { + "data": { + "$area": 45503 + }, + "name": "pcre 45.5k", + "children": [ + { + "data": { + "$area": 23704 + }, + "name": "pcre_ucp_searchfuncs_1.o 23.7k" + }, + { + "data": { + "$area": 12592 + }, + "name": "pcre_exec_1.o 12.6k" + }, + { + "data": { + "$area": 9207 + }, + "name": "pcre_compile_1.o 9.2k" + } + ] + }, + { + "data": { + "$area": 33158 + }, + "name": "inspector 33.2k", + "children": [ + { + "data": { + "$area": 19697 + }, + "name": "InspectorDOMAgent_1.o 19.7k" + }, + { + "data": { + "$area": 5321 + }, + "name": "InspectorController_1.o 5.3k" + }, + { + "data": { + "$area": 2955 + }, + "name": "InspectorValues_1.o 3.0k" + }, + { + "data": { + "$area": 2070 + }, + "name": "InspectorCSSStore_1.o 2.1k" + }, + { + "data": { + "$area": 1822 + }, + "name": "InspectorResource_1.o 1.8k" + }, + { + "data": { + "$area": 756 + }, + "name": "ConsoleMessage_1.o 756" + }, + { + "data": { + "$area": 537 + }, + "name": "InspectorTimelineAgent_1.o 537" + } + ] + }, + { + "data": { + "$area": 32139 + }, + "name": "bytecompiler 32.1k", + "children": [ + { + "data": { + "$area": 19516 + }, + "name": "BytecodeGenerator_1.o 19.5k" + }, + { + "data": { + "$area": 12623 + }, + "name": "NodesCodegen_1.o 12.6k" + } + ] + }, + { + "data": { + "$area": 23109 + }, + "name": "wtf 23.1k", + "children": [ + { + "data": { + "$area": 15053 + }, + "name": "dtoa_1.o 15.1k" + }, + { + "data": { + "$area": 3517 + }, + "name": "FastMalloc_1.o 3.5k" + }, + { + "data": { + "$area": 2324 + }, + "name": "text 2.3k", + "children": [ + { + "data": { + "$area": 1812 + }, + "name": "StringImpl_1.o 1.8k" + }, + { + "data": { + "$area": 512 + }, + "name": "WTFString_1.o 512" + } + ] + }, + { + "data": { + "$area": 1695 + }, + "name": "DateMath_1.o 1.7k" + }, + { + "data": { + "$area": 520 + }, + "name": "unicode 520", + "children": [ + { + "data": { + "$area": 520 + }, + "name": "UTF8_1.o 520" + } + ] + } + ] + }, + { + "data": { + "$area": 21939 + }, + "name": "plugins 21.9k", + "children": [ + { + "data": { + "$area": 7103 + }, + "name": "PluginView_1.o 7.1k" + }, + { + "data": { + "$area": 6219 + }, + "name": "win 6.2k", + "children": [ + { + "data": { + "$area": 3005 + }, + "name": "PluginDatabaseWin_1.o 3.0k" + }, + { + "data": { + "$area": 1712 + }, + "name": "PluginViewWin_1.o 1.7k" + }, + { + "data": { + "$area": 1502 + }, + "name": "PluginPackageWin_1.o 1.5k" + } + ] + }, + { + "data": { + "$area": 3942 + }, + "name": "PluginDatabase_1.o 3.9k" + }, + { + "data": { + "$area": 3098 + }, + "name": "PluginStream_1.o 3.1k" + }, + { + "data": { + "$area": 952 + }, + "name": "wx 952", + "children": [ + { + "data": { + "$area": 952 + }, + "name": "PluginDataWx_1.o 952" + } + ] + }, + { + "data": { + "$area": 625 + }, + "name": "P...624 625" + } + ] + }, + { + "data": { + "$area": 18896 + }, + "name": "storage 18.9k", + "children": [ + { + "data": { + "$area": 5358 + }, + "name": "DatabaseTracker_1.o 5.4k" + }, + { + "data": { + "$area": 5176 + }, + "name": "Database_1.o 5.2k" + }, + { + "data": { + "$area": 3788 + }, + "name": "DatabaseAuthorizer_1.o 3.8k" + }, + { + "data": { + "$area": 1748 + }, + "name": "SQLStatement_1.o 1.7k" + }, + { + "data": { + "$area": 1541 + }, + "name": "SQLTransaction_1.o 1.5k" + }, + { + "data": { + "$area": 664 + }, + "name": "DatabaseTh...663 664" + }, + { + "data": { + "$area": 621 + }, + "name": "OriginUsageRecord_1.o 621" + } + ] + }, + { + "data": { + "$area": 11072 + }, + "name": "parser 11.1k", + "children": [ + { + "data": { + "$area": 11072 + }, + "name": "Lexer_1.o 11.1k" + } + ] + }, + { + "data": { + "$area": 8257 + }, + "name": "xml 8.3k", + "children": [ + { + "data": { + "$area": 8257 + }, + "name": "XMLHttpRequest_1.o 8.3k" + } + ] + }, + { + "data": { + "$area": 6710 + }, + "name": "interpreter 6.7k", + "children": [ + { + "data": { + "$area": 6710 + }, + "name": "Interpreter_1.o 6.7k" + } + ] + }, + { + "data": { + "$area": 5476 + }, + "name": "bridge 5.5k", + "children": [ + { + "data": { + "$area": 3092 + }, + "name": "c 3.1k", + "children": [ + { + "data": { + "$area": 1783 + }, + "name": "c_instance_1.o 1.8k" + }, + { + "data": { + "$area": 1309 + }, + "name": "c_utility_1.o 1.3k" + } + ] + }, + { + "data": { + "$area": 1311 + }, + "name": "runtime_object_1.o 1.3k" + }, + { + "data": { + "$area": 548 + }, + "name": "runtime_root_1.o 548" + }, + { + "data": { + "$area": 525 + }, + "name": "NP_jsobject_1.o 525" + } + ] + }, + { + "data": { + "$area": 1871 + }, + "name": "profiler 1.9k", + "children": [ + { + "data": { + "$area": 1229 + }, + "name": "Profiler_1.o 1.2k" + }, + { + "data": { + "$area": 642 + }, + "name": "ProfileGenerator_1.o 642" + } + ] + }, + { + "data": { + "$area": 1278 + }, + "name": "history 1.3k", + "children": [ + { + "data": { + "$area": 642 + }, + "name": "BackForwardList_1.o 642" + }, + { + "data": { + "$area": 636 + }, + "name": "HistoryItem_1.o 636" + } + ] + }, + { + "data": { + "$area": 1065 + }, + "name": "bytecode 1.1k", + "children": [ + { + "data": { + "$area": 1065 + }, + "name": "CodeBlock_1.o 1.1k" + } + ] + }, + { + "data": { + "$area": 1017 + }, + "name": "workers 1.0k", + "children": [ + { + "data": { + "$area": 1017 + }, + "name": "WorkerContext_1.o 1.0k" + } + ] + }, + { + "data": { + "$area": 587 + }, + "name": "debugger 587", + "children": [ + { + "data": { + "$area": 587 + }, + "name": "Debugger_1.o 587" + } + ] + } + ] + }, + { + "data": { + "$area": 231246 + }, + "name": "symbols without paths 231.2k", + "children": [ + { + "data": { + "$area": 207216 + }, + "name": "misc 207.2k" + }, + { + "data": { + "$area": 24030 + }, + "name": "WTF:: 24.0k" + } + ] + }, + { + "data": { + "$area": 17832 + }, + "name": "C: 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "Users 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "Kevin 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "src 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "digsby 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "build 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "msw 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "WebKit 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "WebKit 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "wx 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "build 17.8k", + "children": [ + { + "data": { + "$area": 17832 + }, + "name": "default 17.8k", + "children": [ + { + "data": { + "$area": 8250 + }, + "name": "WebBrowserShell_1.o 8.3k" + }, + { + "data": { + "$area": 6060 + }, + "name": "WebView_1.o 6.1k" + }, + { + "data": { + "$area": 2621 + }, + "name": "WebFrame_1.o 2.6k" + }, + { + "data": { + "$area": 901 + }, + "name": "WebKitSupport 901", + "children": [ + { + "data": { + "$area": 901 + }, + "name": "ChromeClientWx_1.o 901" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "data": { + "$area": 17434 + }, + "name": "vc_mswuhdll 17.4k", + "children": [ + { + "data": { + "$area": 14428 + }, + "name": "wxzlib_inflate.obj 14.4k" + }, + { + "data": { + "$area": 1264 + }, + "name": "wxzlib_inftrees.obj 1.3k" + }, + { + "data": { + "$area": 1120 + }, + "name": "wxzlib_inffast.obj 1.1k" + }, + { + "data": { + "$area": 622 + }, + "name": "wxzlib_adler32.obj 622" + } + ] + }, + { + "data": { + "$area": 550 + }, + "name": "f: 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "dd 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "vctools 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "crt_bld 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "SELF_X86 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "crt 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "src 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "build 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "INTEL 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "dll_obj 550", + "children": [ + { + "data": { + "$area": 550 + }, + "name": "crtdll.obj 550" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/digsby/build/buildutil/bloattrack/server.py b/digsby/build/buildutil/bloattrack/server.py new file mode 100644 index 0000000..16b6382 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/server.py @@ -0,0 +1,28 @@ +import web +import bloattrack + +urls = ( + '/(.*?)/(.*)', 'bloatserver' +) +app = web.application(urls, globals()) +render = web.template.render('templates/') + +class bloatserver(object): + def GET(self, view, url): + url = 'http://mini/svn/' + url + query = web.input(rev=None) + + if view == 'treemapjson': + web.header('Content-Type', 'text/json') + return bloattrack.json_for_svn_url(url=url, rev=query.rev)['json'] + + elif view == 'treemap': + info = bloattrack.json_for_svn_url(url=url, rev=query.rev) + return render.treemap(url, info['rev'], info['json']) + else: + return web.notfound() + #return 'your url is wack: %r' % url + +if __name__ == "__main__": + app.run() + diff --git a/digsby/build/buildutil/bloattrack/sizer/Sizer.sln b/digsby/build/buildutil/bloattrack/sizer/Sizer.sln new file mode 100644 index 0000000..6c0c6e6 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/Sizer.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Sizer", "Sizer.vcproj", "{891BC203-DE60-4E21-A2A3-D6D43A0C7A53}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {891BC203-DE60-4E21-A2A3-D6D43A0C7A53}.Debug|Win32.ActiveCfg = Debug|Win32 + {891BC203-DE60-4E21-A2A3-D6D43A0C7A53}.Debug|Win32.Build.0 = Debug|Win32 + {891BC203-DE60-4E21-A2A3-D6D43A0C7A53}.Release|Win32.ActiveCfg = Release|Win32 + {891BC203-DE60-4E21-A2A3-D6D43A0C7A53}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/digsby/build/buildutil/bloattrack/sizer/Sizer.vcproj b/digsby/build/buildutil/bloattrack/sizer/Sizer.vcproj new file mode 100644 index 0000000..09debbc --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/Sizer.vcproj @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/build/buildutil/bloattrack/sizer/b.bat b/digsby/build/buildutil/bloattrack/sizer/b.bat new file mode 100644 index 0000000..c2f759a --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/b.bat @@ -0,0 +1 @@ +vcbuild %~d0%~p0\sizer.sln "Release|Win32" diff --git a/digsby/build/buildutil/bloattrack/sizer/changelog.txt b/digsby/build/buildutil/bloattrack/sizer/changelog.txt new file mode 100644 index 0000000..6e29465 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/changelog.txt @@ -0,0 +1,21 @@ +0.1.4, 2010 10 14 + lucas: convert projectfile to vs2008 + lucas: add support for reading vs2008 generated pdb's + +0.1.3, 2008 01 17 + aras: fixed a crash on some executables (IDiaSymbol2::get_type may return S_ERROR). Reported by Ivan-Assen Ivanov. + aras: print a dot for each 1000 symbols read. Some executables spend ages inside DIA dlls. + +0.1.2, 2008 01 14 + ryg: added support for VC8.0 DIA DLL + ryg: added support for loading DIA DLLs that are not registered (drop msdia*.dll into app folder) + ryg: split up "data" report into data and BSS (uninitialized data) sections + ryg: fixed some size computations + aras: strip whitespace from symbol names (often happens with templates) + +0.1.1, 2008 01 13 + aras: improved error messages + aras: removed unused source files + +0.1, 2008 01 13 + initial release diff --git a/digsby/build/buildutil/bloattrack/sizer/readme.txt b/digsby/build/buildutil/bloattrack/sizer/readme.txt new file mode 100644 index 0000000..034df36 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/readme.txt @@ -0,0 +1,4 @@ +Sizer - an executable size report utility. + +Aras Pranckevicius, http://aras-p.info/projSizer.html +Based on code by Fabian "ryg" Giesen, http://farbrausch.com/~fg/ diff --git a/digsby/build/buildutil/bloattrack/sizer/src/debuginfo.cpp b/digsby/build/buildutil/bloattrack/sizer/src/debuginfo.cpp new file mode 100644 index 0000000..fd16dbb --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/src/debuginfo.cpp @@ -0,0 +1,597 @@ +// Executable size report utility. +// Aras Pranckevicius, http://aras-p.info/projSizer.html +// Based on code by Fabian "ryg" Giesen, http://farbrausch.com/~fg/ + +#include "types.hpp" +#include "debuginfo.hpp" +#include +#include +#include + +/****************************************************************************/ + +sU32 DebugInfo::CountSizeInClass(sInt type) const +{ + sU32 size = 0; + for(sInt i=0;isecond; + + sInt index = m_IndexByString.size(); + m_IndexByString.insert( std::make_pair(str,index) ); + m_StringByIndex.push_back( str ); + return index; +} + +bool virtAddressComp(const DISymbol &a,const DISymbol &b) +{ + return a.VA < b.VA; +} + +static bool StripTemplateParams( std::string& str ) +{ + bool isTemplate = false; + int start = str.find( '<', 0 ); + while( start != std::string::npos ) + { + isTemplate = true; + // scan to matching closing '>' + int i = start + 1; + int depth = 1; + while( i < str.size() ) + { + char ch = str[i]; + if( ch == '<' ) + ++depth; + if( ch == '>' ) + { + --depth; + if( depth == 0 ) + break; + } + ++i; + } + if( depth != 0 ) + return isTemplate; // no matching '>', just return + + str = str.erase( start, i-start+1 ); + + start = str.find( '<', start ); + } + + return isTemplate; +} + +void DebugInfo::FinishedReading() +{ + // fix strings and aggregate templates + typedef std::map StringIntMap; + StringIntMap templateToIndex; + + for(sInt i=0;iname ); + bool isTemplate = StripTemplateParams( templateName ); + if( isTemplate ) + { + StringIntMap::iterator it = templateToIndex.find( templateName ); + int index; + if( it != templateToIndex.end() ) + { + index = it->second; + Templates[index].size += sym->Size; + Templates[index].count++; + } + else + { + index = Templates.size(); + templateToIndex.insert( std::make_pair(templateName, index) ); + TemplateSymbol tsym; + tsym.name = templateName; + tsym.count = 1; + tsym.size = sym->Size; + Templates.push_back( tsym ); + } + } + } + + // sort symbols by virtual address + std::sort(Symbols.begin(),Symbols.end(),virtAddressComp); + + // remove address double-covers + sInt symCount = Symbols.size(); + DISymbol *syms = new DISymbol[symCount]; + sCopyMem(syms,&Symbols[0],symCount * sizeof(DISymbol)); + + Symbols.clear(); + sU32 oldVA = 0; + sInt oldSize = 0; + + for(sInt i=0;iVA; + sU32 newSize = in->Size; + + if(oldVA != 0) + { + sInt adjust = newVA - oldVA; + if(adjust < 0) // we have to shorten + { + newVA = oldVA; + if(newSize >= -adjust) + newSize += adjust; + } + } + + if(newSize || in->Class == DIC_END) + { + Symbols.push_back(DISymbol()); + DISymbol *out = &Symbols.back(); + *out = *in; + out->VA = newVA; + out->Size = newSize; + + oldVA = newVA + newSize; + oldSize = newSize; + } + } + + delete[] syms; +} + +sInt DebugInfo::GetFile( sInt fileName ) +{ + for( sInt i=0;ifileName = fileName; + file->codeSize = file->dataSize = 0; + + return m_Files.size() - 1; +} + +sInt DebugInfo::GetFileByName( sChar *objName ) +{ + sChar *p; + + // skip path seperators + while(0 && (p = (sChar *) sFindString(objName,"\\"))) + objName = p + 1; + + while(0 && (p = (sChar *) sFindString(objName,"/"))) + objName = p + 1; + + return GetFile( MakeString(objName) ); +} + +sInt DebugInfo::GetNameSpace(sInt name) +{ + for(sInt i=0;i"); + + return GetNameSpace(cname); +} + +void DebugInfo::StartAnalyze() +{ + sInt i; + + for(i=0;i= Symbols[x].VA + Symbols[x].Size) + l = x + 1; // continue in left half + else + { + *sym = &Symbols[x]; // we found a match + return true; + } + } + + *sym = (l + 1 < Symbols.size()) ? &Symbols[l+1] : 0; + return false; +} + +static bool symSizeComp(const DISymbol &a,const DISymbol &b) +{ + return a.Size > b.Size; +} + +static bool templateSizeComp(const TemplateSymbol& a, const TemplateSymbol& b) +{ + return a.size > b.size; +} + +static bool nameCodeSizeComp( const DISymNameSp &a,const DISymNameSp &b ) +{ + return a.codeSize > b.codeSize; +} + +static bool fileCodeSizeComp(const DISymFile &a,const DISymFile &b) +{ + return a.codeSize > b.codeSize; +} + +static void sAppendPrintF(std::string &str,const char *format,...) +{ + static const int bufferSize = 512; // cut off after this + char buffer[bufferSize]; + va_list arg; + + va_start(arg,format); + _vsnprintf(buffer,bufferSize-1,format,arg); + va_end(arg); + + strcpy(&buffer[bufferSize-4],"..."); + str += buffer; +} + +std::string DebugInfo::WriteReportOld() +{ + const int kMinSymbolSize = 512; + const int kMinTemplateSize = 512; + const int kMinDataSize = 1024; + const int kMinClassSize = 2048; + const int kMinFileSize = 2048; + + std::string Report; + sInt i; //,j; + sU32 size; + + Report.reserve(16384); // start out with 16k space + + // symbols + sAppendPrintF(Report,"Functions by size (kilobytes):\n"); + std::sort(Symbols.begin(),Symbols.end(),symSizeComp); + + for(i=0;i0;j--) + { + sInt f1 = Symbols[j].FileNum; + sInt f2 = Symbols[j-1].FileNum; + + if(f1 == -1 || f2 != -1 && sCmpStringI(Files[f1].Name.String,Files[f2].Name.String) < 0) + sSwap(Symbols[j],Symbols[j-1]); + } + + for(i=0;i0;j--) + { + sInt f1 = Symbols[j].FileNum; + sInt f2 = Symbols[j-1].FileNum; + + if(f1 == -1 || f2 != -1 && sCmpStringI(Files[f1].Name.String,Files[f2].Name.String) < 0) + sSwap(Symbols[j],Symbols[j-1]); + } + + for(i=0;i + +using std::string; + +/****************************************************************************/ + +#define DIC_END 0 +#define DIC_CODE 1 +#define DIC_DATA 2 +#define DIC_BSS 3 // uninitialized data +#define DIC_UNKNOWN 4 + +struct DISymFile // File +{ + sInt fileName; + sU32 codeSize; + sU32 dataSize; +}; + +struct DISymNameSp // Namespace +{ + sInt name; + sU32 codeSize; + sU32 dataSize; +}; + +struct DISymbol +{ + sInt name; + sInt mangledName; + sInt NameSpNum; + sInt objFileNum; + sU32 VA; + sU32 Size; + sInt Class; +}; + +struct TemplateSymbol +{ + string name; + sU32 size; + sU32 count; +}; + +class DebugInfo +{ + typedef std::vector StringByIndexVector; + typedef std::map IndexByStringMap; + + StringByIndexVector m_StringByIndex; + IndexByStringMap m_IndexByString; + sU32 BaseAddress; + + sU32 CountSizeInClass(sInt type) const; + +public: + sArray Symbols; + sArray Templates; + sArray m_Files; + sArray NameSps; + + void Init(); + void Exit(); + + // only use those before reading is finished!! + sInt MakeString(sChar *s); + const char* GetStringPrep( sInt index ) const { return m_StringByIndex[index].c_str(); } + void SetBaseAddress(sU32 base) { BaseAddress = base; } + + void FinishedReading(); + + sInt GetFile( sInt fileName ); + sInt GetFileByName( sChar *objName ); + + sInt GetNameSpace(sInt name); + sInt GetNameSpaceByName(sChar *name); + + void StartAnalyze(); + void FinishAnalyze(); + sBool FindSymbol(sU32 VA,DISymbol **sym); + + std::string WriteReportOld(); + std::string WriteReport(); +}; + +class DebugInfoReader +{ +public: + virtual sBool ReadDebugInfo(sChar *fileName,DebugInfo &to) = 0; +}; + + +#endif diff --git a/digsby/build/buildutil/bloattrack/sizer/src/main.cpp b/digsby/build/buildutil/bloattrack/sizer/src/main.cpp new file mode 100644 index 0000000..9a76b08 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/src/main.cpp @@ -0,0 +1,50 @@ +// Executable size report utility. +// Aras Pranckevicius, http://aras-p.info/projSizer.html +// Based on code by Fabian "ryg" Giesen, http://farbrausch.com/~fg/ + +#include "pdbfile.hpp" +#include +#include + +int main( int argc, char** argv ) +{ + if( argc < 2 ) { + fprintf( stderr, "Usage: Sizer \n" ); + return 1; + } + + DebugInfo info; + + clock_t time1 = clock(); + + info.Init(); + PDBFileReader pdb; + fprintf( stderr, "Reading debug info file %s ...\n", argv[1] ); + bool pdbok = pdb.ReadDebugInfo( argv[1], info ); + if( !pdbok ) { + fprintf( stderr, "ERROR reading file via PDB\n" ); + return 1; + } + fprintf( stderr, "\nProcessing info...\n" ); + info.FinishedReading(); + info.StartAnalyze(); + info.FinishAnalyze(); + + fprintf( stderr, "Generating report...\n" ); + + std::string report; + if (argc == 2) + report = info.WriteReport(); + else + report = info.WriteReportOld(); + + clock_t time2 = clock(); + float secs = float(time2-time1) / CLOCKS_PER_SEC; + + fprintf( stderr, "Printing...\n" ); + puts( report.c_str() ); + fprintf( stderr, "Done in %.2f seconds!\n", secs ); + + + return 0; +} diff --git a/digsby/build/buildutil/bloattrack/sizer/src/pdbfile.cpp b/digsby/build/buildutil/bloattrack/sizer/src/pdbfile.cpp new file mode 100644 index 0000000..75f9cdd --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/src/pdbfile.cpp @@ -0,0 +1,661 @@ +// Executable size report utility. +// Aras Pranckevicius, http://aras-p.info/projSizer.html +// Based on code by Fabian "ryg" Giesen, http://farbrausch.com/~fg/ + +#include "types.hpp" +#include "debuginfo.hpp" +#include "pdbfile.hpp" + +#include +#include +#include + +/****************************************************************************/ + +// I don't want to use the complete huge dia2.h headers (>350k), so here +// goes the minimal version... + +enum SymTagEnum +{ + SymTagNull, + SymTagExe, + SymTagCompiland, + SymTagCompilandDetails, + SymTagCompilandEnv, + SymTagFunction, + SymTagBlock, + SymTagData, + SymTagAnnotation, + SymTagLabel, + SymTagPublicSymbol, + SymTagUDT, + SymTagEnum, + SymTagFunctionType, + SymTagPointerType, + SymTagArrayType, + SymTagBaseType, + SymTagTypedef, + SymTagBaseClass, + SymTagFriend, + SymTagFunctionArgType, + SymTagFuncDebugStart, + SymTagFuncDebugEnd, + SymTagUsingNamespace, + SymTagVTableShape, + SymTagVTable, + SymTagCustom, + SymTagThunk, + SymTagCustomType, + SymTagManagedType, + SymTagDimension, + SymTagMax +}; + +class IDiaEnumSymbols; +class IDiaEnumSymbolsByAddr; +class IDiaEnumTables; + +class IDiaDataSource; +class IDiaSession; + +class IDiaSymbol; +class IDiaSectionContrib; + +class IDiaTable; + +// not transcribed here: +class IDiaSourceFile; + +class IDiaEnumSourceFiles; +class IDiaEnumLineNumbers; +class IDiaEnumDebugStreams; +class IDiaEnumInjectedSources; + +class IDiaEnumSymbols : public IUnknown +{ +public: + virtual HRESULT __stdcall get__NewEnum(IUnknown **ret) = 0; + virtual HRESULT __stdcall get_Count(LONG *ret) = 0; + + virtual HRESULT __stdcall Item(DWORD index,IDiaSymbol **symbol) = 0; + virtual HRESULT __stdcall Next(ULONG celt,IDiaSymbol **rgelt,ULONG *pceltFetched) = 0; + virtual HRESULT __stdcall Skip(ULONG celt) = 0; + virtual HRESULT __stdcall Reset() = 0; + + virtual HRESULT __stdcall Clone(IDiaEnumSymbols **penum) = 0; +}; + +class IDiaEnumSymbolsByAddr : public IUnknown +{ +public: + virtual HRESULT __stdcall symbolByAddr(DWORD isect,DWORD offset,IDiaSymbol** ppSymbol) = 0; + virtual HRESULT __stdcall symbolByRVA(DWORD relativeVirtualAddress,IDiaSymbol** ppSymbol) = 0; + virtual HRESULT __stdcall symbolByVA(ULONGLONG virtualAddress,IDiaSymbol** ppSymbol) = 0; + + virtual HRESULT __stdcall Next(ULONG celt,IDiaSymbol ** rgelt,ULONG* pceltFetched) = 0; + virtual HRESULT __stdcall Prev(ULONG celt,IDiaSymbol ** rgelt,ULONG * pceltFetched) = 0; + + virtual HRESULT __stdcall Clone(IDiaEnumSymbolsByAddr **ppenum) = 0; +}; + +class IDiaEnumTables : public IUnknown +{ +public: + virtual HRESULT __stdcall get__NewEnum(IUnknown **ret) = 0; + virtual HRESULT __stdcall get_Count(LONG *ret) = 0; + + virtual HRESULT __stdcall Item(VARIANT index,IDiaTable **table) = 0; + virtual HRESULT __stdcall Next(ULONG celt,IDiaTable ** rgelt,ULONG *pceltFetched) = 0; + virtual HRESULT __stdcall Skip(ULONG celt) = 0; + virtual HRESULT __stdcall Reset() = 0; + + virtual HRESULT __stdcall Clone(IDiaEnumTables **ppenum) = 0; +}; + +class IDiaDataSource : public IUnknown +{ +public: + virtual HRESULT __stdcall get_lastError(BSTR *ret) = 0; + + virtual HRESULT __stdcall loadDataFromPdb(LPCOLESTR pdbPath) = 0; + virtual HRESULT __stdcall loadAndValidateDataFromPdb(LPCOLESTR pdbPath,GUID *pcsig70,DWORD sig,DWORD age) = 0; + virtual HRESULT __stdcall loadDataForExe(LPCOLESTR executable,LPCOLESTR searchPath,IUnknown *pCallback) = 0; + virtual HRESULT __stdcall loadDataFromIStream(IStream *pIStream) = 0; + + virtual HRESULT __stdcall openSession(IDiaSession **ppSession) = 0; +}; + +class IDiaSession : public IUnknown +{ +public: + virtual HRESULT __stdcall get_loadAddress(ULONGLONG *ret) = 0; + virtual HRESULT __stdcall put_loadAddress(ULONGLONG val) = 0; + virtual HRESULT __stdcall get_globalScape(IDiaSymbol **sym) = 0; + + virtual HRESULT __stdcall getEnumTables(IDiaEnumTables** ppEnumTables) = 0; + virtual HRESULT __stdcall getSymbolsByAddr(IDiaEnumSymbolsByAddr** ppEnumbyAddr) = 0; + + virtual HRESULT __stdcall findChildren(IDiaSymbol* parent,enum SymTagEnum symtag,LPCOLESTR name,DWORD compareFlags,IDiaEnumSymbols** ppResult) = 0; + virtual HRESULT __stdcall findSymbolByAddr(DWORD isect,DWORD offset,enum SymTagEnum symtag,IDiaSymbol** ppSymbol) = 0; + virtual HRESULT __stdcall findSymbolByRVA(DWORD rva,enum SymTagEnum symtag,IDiaSymbol** ppSymbol) = 0; + virtual HRESULT __stdcall findSymbolByVA(ULONGLONG va,enum SymTagEnum symtag,IDiaSymbol** ppSymbol) = 0; + virtual HRESULT __stdcall findSymbolByToken(ULONG token,enum SymTagEnum symtag,IDiaSymbol** ppSymbol) = 0; + virtual HRESULT __stdcall symsAreEquiv(IDiaSymbol* symbolA,IDiaSymbol* symbolB) = 0; + virtual HRESULT __stdcall symbolById(DWORD id,IDiaSymbol** ppSymbol) = 0; + virtual HRESULT __stdcall findSymbolByRVAEx(DWORD rva,enum SymTagEnum symtag,IDiaSymbol** ppSymbol,long* displacement) = 0; + virtual HRESULT __stdcall findSymbolByVAEx(ULONGLONG va,enum SymTagEnum symtag,IDiaSymbol** ppSymbol,long* displacement) = 0; + + virtual HRESULT __stdcall findFile(IDiaSymbol* pCompiland,LPCOLESTR name,DWORD compareFlags,IDiaEnumSourceFiles** ppResult) = 0; + virtual HRESULT __stdcall findFileById(DWORD uniqueId,IDiaSourceFile** ppResult) = 0; + + virtual HRESULT __stdcall findLines(IDiaSymbol* compiland,IDiaSourceFile* file,IDiaEnumLineNumbers** ppResult) = 0; + virtual HRESULT __stdcall findLinesByAddr(DWORD seg,DWORD offset,DWORD length,IDiaEnumLineNumbers** ppResult) = 0; + virtual HRESULT __stdcall findLinesByRVA(DWORD rva,DWORD length,IDiaEnumLineNumbers** ppResult) = 0; + virtual HRESULT __stdcall findLinesByVA(ULONGLONG va,DWORD length,IDiaEnumLineNumbers** ppResult) = 0; + virtual HRESULT __stdcall findLinesByLinenum(IDiaSymbol* compiland,IDiaSourceFile* file,DWORD linenum,DWORD column,IDiaEnumLineNumbers** ppResult) = 0; + + virtual HRESULT __stdcall findInjectedSource(LPCOLESTR srcFile,IDiaEnumInjectedSources** ppResult) = 0; + virtual HRESULT __stdcall getEnumDebugStreams(IDiaEnumDebugStreams** ppEnumDebugStreams) = 0; +}; + +class IDiaSymbol : public IUnknown +{ +public: + virtual HRESULT __stdcall get_symIndexId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_symTag(DWORD *ret) = 0; + virtual HRESULT __stdcall get_name(BSTR *ret) = 0; + virtual HRESULT __stdcall get_lexicalParent(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_classParent(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_type(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_dataKind(DWORD *ret) = 0; + virtual HRESULT __stdcall get_locationType(DWORD *ret) = 0; + virtual HRESULT __stdcall get_addressSection(DWORD *ret) = 0; + virtual HRESULT __stdcall get_addressOffset(DWORD *ret) = 0; + virtual HRESULT __stdcall get_relativeVirtualAddress(DWORD *ret) = 0; + virtual HRESULT __stdcall get_virtualAddress(ULONGLONG *ret) = 0; + virtual HRESULT __stdcall get_registerId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_offset(LONG *ret) = 0; + virtual HRESULT __stdcall get_length(ULONGLONG *ret) = 0; + virtual HRESULT __stdcall get_slot(DWORD *ret) = 0; + virtual HRESULT __stdcall get_volatileType(BOOL *ret) = 0; + virtual HRESULT __stdcall get_constType(BOOL *ret) = 0; + virtual HRESULT __stdcall get_unalignedType(BOOL *ret) = 0; + virtual HRESULT __stdcall get_access(DWORD *ret) = 0; + virtual HRESULT __stdcall get_libraryName(BSTR *ret) = 0; + virtual HRESULT __stdcall get_platform(DWORD *ret) = 0; + virtual HRESULT __stdcall get_language(DWORD *ret) = 0; + virtual HRESULT __stdcall get_editAndContinueEnabled(BOOL *ret) = 0; + virtual HRESULT __stdcall get_frontEndMajor(DWORD *ret) = 0; + virtual HRESULT __stdcall get_frontEndMinor(DWORD *ret) = 0; + virtual HRESULT __stdcall get_frontEndBuild(DWORD *ret) = 0; + virtual HRESULT __stdcall get_backEndMajor(DWORD *ret) = 0; + virtual HRESULT __stdcall get_backEndMinor(DWORD *ret) = 0; + virtual HRESULT __stdcall get_backEndBuild(DWORD *ret) = 0; + virtual HRESULT __stdcall get_sourceFileName(BSTR *ret) = 0; + virtual HRESULT __stdcall get_unused(BSTR *ret) = 0; + virtual HRESULT __stdcall get_thunkOrdinal(DWORD *ret) = 0; + virtual HRESULT __stdcall get_thisAdjust(LONG *ret) = 0; + virtual HRESULT __stdcall get_virtualBaseOffset(DWORD *ret) = 0; + virtual HRESULT __stdcall get_virtual(BOOL *ret) = 0; + virtual HRESULT __stdcall get_intro(BOOL *ret) = 0; + virtual HRESULT __stdcall get_pure(BOOL *ret) = 0; + virtual HRESULT __stdcall get_callingConvention(DWORD *ret) = 0; + virtual HRESULT __stdcall get_value(VARIANT *ret) = 0; + virtual HRESULT __stdcall get_baseType(DWORD *ret) = 0; + virtual HRESULT __stdcall get_token(DWORD *ret) = 0; + virtual HRESULT __stdcall get_timeStamp(DWORD *ret) = 0; + virtual HRESULT __stdcall get_guid(GUID *ret) = 0; + virtual HRESULT __stdcall get_symbolsFileName(BSTR *ret) = 0; + virtual HRESULT __stdcall get_reference(BOOL *ret) = 0; + virtual HRESULT __stdcall get_count(DWORD *ret) = 0; + virtual HRESULT __stdcall get_bitPosition(DWORD *ret) = 0; + virtual HRESULT __stdcall get_arrayIndexType(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_packed(BOOL *ret) = 0; + virtual HRESULT __stdcall get_constructor(BOOL *ret) = 0; + virtual HRESULT __stdcall get_overloadedOperator(BOOL *ret) = 0; + virtual HRESULT __stdcall get_nested(BOOL *ret) = 0; + virtual HRESULT __stdcall get_hasNestedTypes(BOOL *ret) = 0; + virtual HRESULT __stdcall get_hasAssignmentOperator(BOOL *ret) = 0; + virtual HRESULT __stdcall get_hasCastOperator(BOOL *ret) = 0; + virtual HRESULT __stdcall get_scoped(BOOL *ret) = 0; + virtual HRESULT __stdcall get_virtualBaseClass(BOOL *ret) = 0; + virtual HRESULT __stdcall get_indirectVirtualBaseClass(BOOL *ret) = 0; + virtual HRESULT __stdcall get_virtualBasePointerOffset(LONG *ret) = 0; + virtual HRESULT __stdcall get_virtualTableShape(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_lexicalParentId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_classParentId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_typeId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_arrayIndexTypeId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_virtualTableShapeId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_code(BOOL *ret) = 0; + virtual HRESULT __stdcall get_function(BOOL *ret) = 0; + virtual HRESULT __stdcall get_managed(BOOL *ret) = 0; + virtual HRESULT __stdcall get_msil(BOOL *ret) = 0; + virtual HRESULT __stdcall get_virtualBaseDispIndex(DWORD *ret) = 0; + virtual HRESULT __stdcall get_undecoratedName(BSTR *ret) = 0; + virtual HRESULT __stdcall get_age(DWORD *ret) = 0; + virtual HRESULT __stdcall get_signature(DWORD *ret) = 0; + virtual HRESULT __stdcall get_compilerGenerated(BOOL *ret) = 0; + virtual HRESULT __stdcall get_addressTaken(BOOL *ret) = 0; + virtual HRESULT __stdcall get_rank(DWORD *ret) = 0; + virtual HRESULT __stdcall get_lowerBound(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_upperBound(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_lowerBoundId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_upperBoundId(DWORD *ret) = 0; + + virtual HRESULT __stdcall get_dataBytes(DWORD cbData,DWORD *pcbData,BYTE data[]) = 0; + virtual HRESULT __stdcall findChildren(enum SymTagEnum symtag,LPCOLESTR name,DWORD compareFlags,IDiaEnumSymbols** ppResult) = 0; + + virtual HRESULT __stdcall get_targetSection(DWORD *ret) = 0; + virtual HRESULT __stdcall get_targetOffset(DWORD *ret) = 0; + virtual HRESULT __stdcall get_targetRelativeVirtualAddress(DWORD *ret) = 0; + virtual HRESULT __stdcall get_targetVirtualAddress(ULONGLONG *ret) = 0; + virtual HRESULT __stdcall get_machineType(DWORD *ret) = 0; + virtual HRESULT __stdcall get_oemId(DWORD *ret) = 0; + virtual HRESULT __stdcall get_oemSymbolId(DWORD *ret) = 0; + + virtual HRESULT __stdcall get_types(DWORD cTypes,DWORD *pcTypes,IDiaSymbol* types[]) = 0; + virtual HRESULT __stdcall get_typeIds(DWORD cTypes,DWORD *pcTypeIds,DWORD typeIds[]) = 0; + + virtual HRESULT __stdcall get_objectPointerType(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_udtKind(DWORD *ret) = 0; + + virtual HRESULT __stdcall get_undecoratedNameEx(DWORD undecorateOptions,BSTR *name) = 0; +}; + +class IDiaSectionContrib : public IUnknown +{ +public: + virtual HRESULT __stdcall get_compiland(IDiaSymbol **ret) = 0; + virtual HRESULT __stdcall get_addressSection(DWORD *ret) = 0; + virtual HRESULT __stdcall get_addressOffset(DWORD *ret) = 0; + virtual HRESULT __stdcall get_relativeVirtualAddress(DWORD *ret) = 0; + virtual HRESULT __stdcall get_virtualAddress(ULONGLONG *ret) = 0; + virtual HRESULT __stdcall get_length(DWORD *ret) = 0; + + virtual HRESULT __stdcall get_notPaged(BOOL *ret) = 0; + virtual HRESULT __stdcall get_code(BOOL *ret) = 0; + virtual HRESULT __stdcall get_initializedData(BOOL *ret) = 0; + virtual HRESULT __stdcall get_uninitializedData(BOOL *ret) = 0; + virtual HRESULT __stdcall get_remove(BOOL *ret) = 0; + virtual HRESULT __stdcall get_comdat(BOOL *ret) = 0; + virtual HRESULT __stdcall get_discardable(BOOL *ret) = 0; + virtual HRESULT __stdcall get_notCached(BOOL *ret) = 0; + virtual HRESULT __stdcall get_share(BOOL *ret) = 0; + virtual HRESULT __stdcall get_execute(BOOL *ret) = 0; + virtual HRESULT __stdcall get_read(BOOL *ret) = 0; + virtual HRESULT __stdcall get_write(BOOL *ret) = 0; + virtual HRESULT __stdcall get_dataCrc(DWORD *ret) = 0; + virtual HRESULT __stdcall get_relocationsaCrc(DWORD *ret) = 0; + virtual HRESULT __stdcall get_compilandId(DWORD *ret) = 0; +}; + +class IDiaTable : public IEnumUnknown +{ +public: + virtual HRESULT __stdcall get__NewEnum(IUnknown **ret) = 0; + virtual HRESULT __stdcall get_name(BSTR *ret) = 0; + virtual HRESULT __stdcall get_Count(LONG *ret) = 0; + + virtual HRESULT __stdcall Item(DWORD index,IUnknown **element) = 0; +}; + +class DECLSPEC_UUID("e60afbee-502d-46ae-858f-8272a09bd707") DiaSource71; +class DECLSPEC_UUID("bce36434-2c24-499e-bf49-8bd99b0eeb68") DiaSource80; +class DECLSPEC_UUID("4C41678E-887B-4365-A09E-925D28DB33C2") DiaSource90; +class DECLSPEC_UUID("79f1bb5f-b66e-48e5-b6a9-1545c323ca3d") IDiaDataSource; + +/****************************************************************************/ + +struct PDBFileReader::SectionContrib +{ + DWORD Section; + DWORD Offset; + DWORD Length; + DWORD Compiland; + sInt Type; + sInt ObjFile; +}; + +const PDBFileReader::SectionContrib *PDBFileReader::ContribFromSectionOffset(sU32 sec,sU32 offs) +{ + sInt l,r,x; + + l = 0; + r = nContribs; + + while(l < r) + { + x = (l + r) / 2; + const SectionContrib &cur = Contribs[x]; + + if(sec < cur.Section || sec == cur.Section && offs < cur.Offset) + r = x; + else if(sec > cur.Section || sec == cur.Section && offs >= cur.Offset + cur.Length) + l = x+1; + else if(sec == cur.Section && offs >= cur.Offset && offs < cur.Offset + cur.Length) // we got a winner + return &cur; + else + break; // there's nothing here! + } + + // normally, this shouldn't happen! + return 0; +} + +// helpers +static sChar *BStrToString( BSTR str, sChar *defString = "", bool stripWhitespace = false ) +{ + if(!str) + { + sInt len = sGetStringLen(defString); + sChar *buffer = new sChar[len+1]; + sCopyString(buffer,defString,len+1); + + return buffer; + } + else + { + sInt len = SysStringLen(str); + sChar *buffer = new sChar[len+1]; + + sInt j = 0; + for( sInt i=0;i= 32 && str[i] < 128) ? str[i] : '?'; + ++j; + } + + buffer[j] = 0; + + return buffer; + } +} + +static sInt GetBStr(BSTR str,sChar *defString,DebugInfo &to) +{ + sChar *normalStr = BStrToString(str); + sInt result = to.MakeString(normalStr); + delete[] normalStr; + + return result; +} + +void PDBFileReader::ProcessSymbol(IDiaSymbol *symbol,DebugInfo &to) +{ + // print a dot for each 1000 symbols processed + static int counter = 0; + ++counter; + if( counter == 1000 ) { + fputc( '.', stderr ); + counter = 0; + } + + DWORD section,offset,rva; + enum SymTagEnum tag; + ULONGLONG length = 0; + BSTR name = 0, srcFileName = 0; + + symbol->get_symTag((DWORD *) &tag); + symbol->get_relativeVirtualAddress(&rva); + symbol->get_length(&length); + symbol->get_addressSection(§ion); + symbol->get_addressOffset(&offset); + + // get length from type for data + if( tag == SymTagData ) + { + IDiaSymbol *type = NULL; + if( symbol->get_type(&type) == S_OK ) // no SUCCEEDED test as may return S_FALSE! + { + if( FAILED(type->get_length(&length)) ) + length = 0; + type->Release(); + } + else + length = 0; + } + + const SectionContrib *contrib = ContribFromSectionOffset(section,offset); + sInt objFile = 0; + sInt sectionType = DIC_UNKNOWN; + + if(contrib) + { + objFile = contrib->ObjFile; + sectionType = contrib->Type; + } + + symbol->get_name(&name); + + // fill out structure + sChar *nameStr = BStrToString( name, "", true); + + to.Symbols.push_back( DISymbol() ); + DISymbol *outSym = &to.Symbols.back(); + outSym->name = outSym->mangledName = to.MakeString(nameStr); + outSym->objFileNum = objFile; + outSym->VA = rva; + outSym->Size = (sU32) length; + outSym->Class = sectionType; + outSym->NameSpNum = to.GetNameSpaceByName(nameStr); + + // clean up + delete[] nameStr; + if(name) SysFreeString(name); +} + +void PDBFileReader::ReadEverything(DebugInfo &to) +{ + ULONG celt; + + Contribs = 0; + nContribs = 0; + + // read section table + IDiaEnumTables *enumTables; + if(Session->getEnumTables(&enumTables) == S_OK) + { + VARIANT vIndex; + vIndex.vt = VT_BSTR; + vIndex.bstrVal = SysAllocString(L"Sections"); + + IDiaTable *secTable; + if(enumTables->Item(vIndex,&secTable) == S_OK) + { + LONG count; + + secTable->get_Count(&count); + Contribs = new SectionContrib[count]; + nContribs = 0; + + IDiaSectionContrib *item; + while(SUCCEEDED(secTable->Next(1,(IUnknown **)&item,&celt)) && celt == 1) + { + SectionContrib &contrib = Contribs[nContribs++]; + + item->get_addressOffset(&contrib.Offset); + item->get_addressSection(&contrib.Section); + item->get_length(&contrib.Length); + item->get_compilandId(&contrib.Compiland); + + BOOL code=FALSE,initData=FALSE,uninitData=FALSE; + item->get_code(&code); + item->get_initializedData(&initData); + item->get_uninitializedData(&uninitData); + + if(code && !initData && !uninitData) + contrib.Type = DIC_CODE; + else if(!code && initData && !uninitData) + contrib.Type = DIC_DATA; + else if(!code && !initData && uninitData) + contrib.Type = DIC_BSS; + else + contrib.Type = DIC_UNKNOWN; + + BSTR objFileName = 0; + + IDiaSymbol *compiland = 0; + item->get_compiland(&compiland); + if(compiland) + { + compiland->get_name(&objFileName); + compiland->Release(); + } + + sChar *objFileStr = BStrToString(objFileName,""); + contrib.ObjFile = to.GetFileByName(objFileStr); + + delete[] objFileStr; + if(objFileName) + SysFreeString(objFileName); + + item->Release(); + } + + secTable->Release(); + } + + SysFreeString(vIndex.bstrVal); + enumTables->Release(); + } + + // enumerate symbols by (virtual) address + IDiaEnumSymbolsByAddr *enumByAddr; + if(SUCCEEDED(Session->getSymbolsByAddr(&enumByAddr))) + { + IDiaSymbol *symbol; + // get first symbol to get first RVA (argh) + if(SUCCEEDED(enumByAddr->symbolByAddr(1,0,&symbol))) + { + DWORD rva; + if(symbol->get_relativeVirtualAddress(&rva) == S_OK) + { + symbol->Release(); + + // now, enumerate by rva. + if(SUCCEEDED(enumByAddr->symbolByRVA(rva,&symbol))) + { + do + { + ProcessSymbol(symbol,to); + symbol->Release(); + + if(FAILED(enumByAddr->Next(1,&symbol,&celt))) + break; + } + while(celt == 1); + } + } + else + symbol->Release(); + } + + enumByAddr->Release(); + } + + // clean up + delete[] Contribs; +} + +/****************************************************************************/ + +sBool PDBFileReader::ReadDebugInfo(sChar *fileName,DebugInfo &to) +{ + static const struct DLLDesc + { + const char *Filename; + IID UseCLSID; + } DLLs[] = { + "msdia71.dll", __uuidof(DiaSource71), + "msdia80.dll", __uuidof(DiaSource80), + "msdia90.dll", __uuidof(DiaSource90), + // add more here as new versions appear (as long as they're backwards-compatible) + 0 + }; + + sBool readOk = false; + + if(FAILED(CoInitialize(0))) + { + fprintf(stderr, " failed to initialize COM\n"); + return false; + } + + IDiaDataSource *source = 0; + HRESULT hr = E_FAIL; + + // Try creating things "the official way" + for(sInt i=0;DLLs[i].Filename;i++) + { + hr = CoCreateInstance(DLLs[i].UseCLSID,0,CLSCTX_INPROC_SERVER, + __uuidof(IDiaDataSource),(void**) &source); + + if(SUCCEEDED(hr)) + break; + } + + if(FAILED(hr)) + { + // None of the classes are registered, but most programmers will have the + // DLLs on their system anyway and can copy it over; try loading it directly. + + for(sInt i=0;DLLs[i].Filename;i++) + { + HMODULE hDIADll = LoadLibrary(DLLs[i].Filename); + if(hDIADll) + { + typedef HRESULT (__stdcall *PDllGetClassObject)(REFCLSID rclsid,REFIID riid,void** ppvObj); + PDllGetClassObject DllGetClassObject = (PDllGetClassObject) GetProcAddress(hDIADll,"DllGetClassObject"); + if(DllGetClassObject) + { + // first create a class factory + IClassFactory *classFactory; + hr = DllGetClassObject(DLLs[i].UseCLSID,IID_IClassFactory,(void**) &classFactory); + if(SUCCEEDED(hr)) + { + hr = classFactory->CreateInstance(0,__uuidof(IDiaDataSource),(void**) &source); + classFactory->Release(); + } + } + + if(SUCCEEDED(hr)) + break; + else + FreeLibrary(hDIADll); + } + } + } + + if(source) + { + wchar_t wideFileName[260]; + mbstowcs(wideFileName,fileName,260); + if(SUCCEEDED(source->loadDataForExe(wideFileName,0,0))) + { + if(SUCCEEDED(source->openSession(&Session))) + { + ReadEverything(to); + + readOk = true; + Session->Release(); + } + else + fprintf(stderr," failed to open DIA session\n"); + } + else + fprintf(stderr," failed to load debug symbols (PDB not found)\n"); + + source->Release(); + } + else + fprintf(stderr," couldn't find (or properly initialize) any DIA dll, copying msdia*.dll to app dir might help.\n"); + + CoUninitialize(); + + return readOk; +} + +/****************************************************************************/ diff --git a/digsby/build/buildutil/bloattrack/sizer/src/pdbfile.hpp b/digsby/build/buildutil/bloattrack/sizer/src/pdbfile.hpp new file mode 100644 index 0000000..8d541ed --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/src/pdbfile.hpp @@ -0,0 +1,33 @@ +// Executable size report utility. +// Aras Pranckevicius, http://aras-p.info/projSizer.html +// Based on code by Fabian "ryg" Giesen, http://farbrausch.com/~fg/ + +#ifndef __PDBFILE_HPP_ +#define __PDBFILE_HPP_ + +#include "debuginfo.hpp" + +/****************************************************************************/ + +class IDiaSession; + +class PDBFileReader : public DebugInfoReader +{ + struct SectionContrib; + + SectionContrib *Contribs; + sInt nContribs; + + IDiaSession *Session; + + const SectionContrib *ContribFromSectionOffset(sU32 section,sU32 offset); + void ProcessSymbol(class IDiaSymbol *symbol,DebugInfo &to); + void ReadEverything(DebugInfo &to); + +public: + sBool ReadDebugInfo(sChar *fileName,DebugInfo &to); +}; + +/****************************************************************************/ + +#endif \ No newline at end of file diff --git a/digsby/build/buildutil/bloattrack/sizer/src/types.hpp b/digsby/build/buildutil/bloattrack/sizer/src/types.hpp new file mode 100644 index 0000000..9bbe7a0 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/sizer/src/types.hpp @@ -0,0 +1,36 @@ +// Executable size report utility. +// Aras Pranckevicius, http://aras-p.info/projSizer.html +// Based on code by Fabian "ryg" Giesen, http://farbrausch.com/~fg/ + +#pragma once +#include +#include +#include +#include + +#pragma warning(disable:4018) +#pragma warning(disable:4267) +#pragma warning(disable:4244) + +typedef signed int sInt; +typedef char sChar; +typedef float sF32; +typedef double sF64; +typedef unsigned int sU32; +typedef bool sBool; + +#define sArray std::vector + +inline sChar* sCopyString( sChar* a, const sChar* b, int len ) +{ + return strncpy( a, b, len ); +} + +#define sCopyMem memcpy +#define sFindString strstr +#define sSPrintF _snprintf +#define sGetStringLen strlen +#define sCmpStringI stricmp +#define sAppendString strncat +#define sSwap std::swap +#define sVERIFY assert \ No newline at end of file diff --git a/digsby/build/buildutil/bloattrack/static/webtreemap.css b/digsby/build/buildutil/bloattrack/static/webtreemap.css new file mode 100644 index 0000000..38a40d5 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/static/webtreemap.css @@ -0,0 +1,48 @@ +.webtreemap-node { + /* Required attributes. */ + position: absolute; + overflow: hidden; /* To hide overlong captions. */ + background: white; /* Nodes must be opaque for zIndex layering. */ + border: solid 1px black; /* Calculations assume 1px border. */ + + /* Optional: CSS animation. */ + -webkit-transition: top 0.3s, + left 0.3s, + width 0.3s, + height 0.3s; +} + +/* Optional: highlight nodes on mouseover. */ +.webtreemap-node:hover { + background: #eee; +} + +/* Optional: Different borders depending on level. */ +.webtreemap-level0 { + border: solid 1px #444; +} +.webtreemap-level1 { + border: solid 1px #666; +} +.webtreemap-level2 { + border: solid 1px #888; +} +.webtreemap-level3 { + border: solid 1px #aaa; +} +.webtreemap-level4 { + border: solid 1px #ccc; +} + +/* Optional: styling on node captions. */ +.webtreemap-caption { + font-family: sans-serif; + font-size: 11px; + padding: 2px; + text-align: center; +} + +/* Optional: styling on captions on mouse hover. */ +/*.webtreemap-node:hover > .webtreemap-caption { + text-decoration: underline; +}*/ diff --git a/digsby/build/buildutil/bloattrack/static/webtreemap.js b/digsby/build/buildutil/bloattrack/static/webtreemap.js new file mode 100644 index 0000000..c2dec93 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/static/webtreemap.js @@ -0,0 +1,209 @@ +// Size of border around nodes. +// We could support arbitrary borders using getComputedStyle(), but I am +// skeptical the extra complexity (and performance hit) is worth it. +var kBorderWidth = 1; + +// Padding around contents. +// TODO: do this with a nested div to allow it to be CSS-styleable. +var kPadding = 4; + +var focused = null; + +function focus(tree) { + focused = tree; + + // Hide all visible siblings of all our ancestors by lowering them. + var level = 0; + var root = tree; + while (root.parent) { + root = root.parent; + level += 1; + for (var i = 0, sibling; sibling = root.children[i]; ++i) { + if (sibling.dom) + sibling.dom.style.zIndex = 0; + } + } + var width = root.dom.offsetWidth; + var height = root.dom.offsetHeight; + // Unhide (raise) and maximize us and our ancestors. + for (var t = tree; t.parent; t = t.parent) { + // Shift off by border so we don't get nested borders. + // TODO: actually make nested borders work (need to adjust width/height). + position(t.dom, -kBorderWidth, -kBorderWidth, width, height); + t.dom.style.zIndex = 1; + } + // And layout into the topmost box. + layout(tree, level, width, height); +} + +function makeDom(tree, level) { + var dom = document.createElement('div'); + dom.style.zIndex = 1; + dom.className = 'webtreemap-node webtreemap-level' + Math.min(level, 4); + + dom.onmousedown = function(e) { + if (e.button == 0) { + if (focused && tree == focused && focused.parent) { + focus(focused.parent); + } else { + focus(tree); + } + } + e.stopPropagation(); + return true; + }; + + var caption = document.createElement('div'); + caption.className = 'webtreemap-caption'; + caption.innerHTML = tree.name; + dom.appendChild(caption); + + tree.dom = dom; + return dom; +} + +function position(dom, x, y, width, height) { + // CSS width/height does not include border. + width -= kBorderWidth*2; + height -= kBorderWidth*2; + + dom.style.left = x + 'px'; + dom.style.top = y + 'px'; + dom.style.width = Math.max(width, 0) + 'px'; + dom.style.height = Math.max(height, 0) + 'px'; +} + +// Given a list of rectangles |nodes|, the 1-d space available +// |space|, and a starting rectangle index |start|, compute an span of +// rectangles that optimizes a pleasant aspect ratio. +// +// Returns [end, sum], where end is one past the last rectangle and sum is the +// 2-d sum of the rectangles' areas. +function selectSpan(nodes, space, start) { + // Add rectangle one by one, stopping when aspect ratios begin to go + // bad. Result is [start,end) covering the best run for this span. + // http://scholar.google.com/scholar?cluster=5972512107845615474 + var node = nodes[start]; + var rmin = node.data['$area']; // Smallest seen child so far. + var rmax = rmin; // Largest child. + var rsum = 0; // Sum of children in this span. + var last_score = 0; // Best score yet found. + for (var end = start; node = nodes[end]; ++end) { + var size = node.data['$area']; + if (size < rmin) + rmin = size; + if (size > rmax) + rmax = size; + rsum += size; + + // This formula is from the paper, but you can easily prove to + // yourself it's taking the larger of the x/y aspect ratio or the + // y/x aspect ratio. The additional magic fudge constant of 5 + // makes us prefer wider rectangles to taller ones. + var score = Math.max(5*space*space*rmax / (rsum*rsum), + 1*rsum*rsum / (space*space*rmin)); + if (last_score && score > last_score) { + rsum -= size; // Undo size addition from just above. + break; + } + last_score = score; + } + return [end, rsum]; +} + +function layout(tree, level, width, height) { + if (!('children' in tree)) + return; + + var total = tree.data['$area']; + + // XXX why do I need an extra -1/-2 here for width/height to look right? + var x1 = 0, y1 = 0, x2 = width - 1, y2 = height - 2; + x1 += kPadding; y1 += kPadding; + x2 -= kPadding; y2 -= kPadding; + y1 += 14; // XXX get first child height for caption spacing + + var pixels_to_units = Math.sqrt(total / ((x2 - x1) * (y2 - y1))); + + for (var start = 0, child; child = tree.children[start]; ++start) { + if (x2 - x1 < 60 || y2 - y1 < 40) { + if (child.dom) { + child.dom.style.zIndex = 0; + position(child.dom, -2, -2, 0, 0); + } + continue; + } + + // In theory we can dynamically decide whether to split in x or y based + // on aspect ratio. In practice, changing split direction with this + // layout doesn't look very good. + // var ysplit = (y2 - y1) > (x2 - x1); + var ysplit = true; + + var space; // Space available along layout axis. + if (ysplit) + space = (y2 - y1) * pixels_to_units; + else + space = (x2 - x1) * pixels_to_units; + + var span = selectSpan(tree.children, space, start); + var end = span[0], rsum = span[1]; + + // Now that we've selected a span, lay out rectangles [start,end) in our + // available space. + var x = x1, y = y1; + for (var i = start; i < end; ++i) { + child = tree.children[i]; + if (!child.dom) { + child.parent = tree; + child.dom = makeDom(child, level + 1); + tree.dom.appendChild(child.dom); + } else { + child.dom.style.zIndex = 1; + } + var size = child.data['$area']; + var frac = size / rsum; + if (ysplit) { + width = rsum / space; + height = size / width; + } else { + height = rsum / space; + width = size / height; + } + width /= pixels_to_units; + height /= pixels_to_units; + width = Math.round(width); + height = Math.round(height); + position(child.dom, x, y, width, height); + if ('children' in child) { + layout(child, level + 1, width, height); + } + if (ysplit) + y += height; + else + x += width; + } + + // Shrink our available space based on the amount we used. + if (ysplit) + x1 += Math.round((rsum / space) / pixels_to_units); + else + y1 += Math.round((rsum / space) / pixels_to_units); + + // end points one past where we ended, which is where we want to + // begin the next iteration, but subtract one to balance the ++ in + // the loop. + start = end - 1; + } +} + +function appendTreemap(dom, data) { + var style = getComputedStyle(dom, null); + var width = parseInt(style.width); + var height = parseInt(style.height); + if (!data.dom) + makeDom(data, 0); + dom.appendChild(data.dom); + position(data.dom, 0, 0, width, height); + layout(data, 0, width, height); +} diff --git a/digsby/build/buildutil/bloattrack/svntools.py b/digsby/build/buildutil/bloattrack/svntools.py new file mode 100644 index 0000000..9adbeff --- /dev/null +++ b/digsby/build/buildutil/bloattrack/svntools.py @@ -0,0 +1,96 @@ +import sys +from bloattrack import run +from cache import cache +import os.path + +binary_checkout_dir = '.bloatbins' + +def svn(*args): + return run(*('svn',) + args) + +class Changeset(object): + def __repr__(self): + return '' % (self.revision, self.author, self.message[:40]) + def __init__(self, revision, author, message, date): + self.revision = revision + self.author = author + self.message = message + self.date = date + +def checkout_revision(filename, revision): + run('svn', 'checkout', '--revision', revision, filename) + +def _parse_svn_output(colon_sep_key_val_txt): + info = dict() + for line in colon_sep_key_val_txt.split('\n'): + line = line.strip() + if not line: continue + + index = line.find(':') + if index == -1: continue + + key, value = line[:index], line[index+2:] + info[key] = value + + return info + +def svn_file_info(url, key=None): + output = svn('info', url) + infodict = _parse_svn_output(output) + if key is not None: + return infodict[key] + else: + return infodict + +def url_for_file(local_svn_file): + return svn_file_info(local_svn_file)['URL'] + +def log_xml_for_file(local_svn_file): + url = url_for_file(local_svn_file) + xml = svn('log', url, '--xml') + + doc = lxml.etree.fromstring(xml) + revisions = [] + for entry in doc.xpath('//logentry'): + revision = int(entry.attrib.get('revision')) + author = entry.findall('author')[0].text + msg = entry.findall('msg')[0].text + date = entry.findall('date')[0].text + revisions.append(Changeset( + revision=revision, + author=author, + message=msg, + date=date)) + + return revisions + +def svn_download(url, rev=None): + if rev is None: + rev = svn_file_info(url, 'Last Changed Rev') + + def getfile(): + temppath = '.svndownload' + if os.path.isfile(temppath): + os.remove(temppath) + assert not os.path.isfile(temppath) + svn('export', '-r', rev, url, temppath) + contents = open(temppath, 'rb').read() + os.remove(temppath) + return contents + + urlelems = url.split('/') + filename = urlelems[-1] + urldir = '/'.join(urlelems[:-1]) + + local_path = cache(getfile, + dirname='svn', + hashelems=('export', urldir, rev), + filename=filename)['path'] + + return dict(path=local_path, rev=rev) + +def main(): + print svn_download(sys.argv[1]) + +if __name__ == '__main__': + main() diff --git a/digsby/build/buildutil/bloattrack/templates/treemap.html b/digsby/build/buildutil/bloattrack/templates/treemap.html new file mode 100644 index 0000000..c455cc0 --- /dev/null +++ b/digsby/build/buildutil/bloattrack/templates/treemap.html @@ -0,0 +1,37 @@ +$def with (path, rev, json) + + +webtreemap demo (Chrome binary size) + + + + +
+ + + + diff --git a/digsby/build/buildutil/buildfileutils.py b/digsby/build/buildutil/buildfileutils.py new file mode 100644 index 0000000..c27e0e7 --- /dev/null +++ b/digsby/build/buildutil/buildfileutils.py @@ -0,0 +1,517 @@ +#__LICENSE_GOES_HERE__ +''' +build util +''' + +from __future__ import with_statement + +from contextlib import contextmanager + +import commands +import fnmatch +import hashlib +import os.path +import shlex +import shutil +import subprocess +import sys +import distutils.sysconfig + +pathjoin = os.path.join + +from constants import * + +if sys.platform == 'win32': + platformName = 'win' + build_dir = os.path.split(os.path.abspath(__file__))[0] + assert os.path.isdir(build_dir) + + class buildpaths: + platform_deps = pathjoin(build_dir, 'msw', 'dependencies') +elif sys.platform == 'darwin': + platformName = 'mac' +elif 'linux' in sys.platform: + platformName = 'gtk' +else: + raise AssertionError('Help! Unknown platform!') + +if platformName == "win": + # TODO: 64 bit? + platname = 'win32' +else: + platname = platformName + +python_version = '26' + +try: + import digsbypaths +except ImportError: + path_to_digsby_paths = os.path.normpath(pathjoin(os.path.dirname(os.path.abspath(__file__)), '..', '..')) + sys.path.append(path_to_digsby_paths) + import digsbypaths + +DEPS_DIR = distutils.sysconfig.get_python_lib() # digsbypaths.get_platlib_dir(DEBUG) + +if not os.path.isdir(DEPS_DIR): + os.makedirs(DEPS_DIR) + + +stars = '*'*80 +if '--quiet' in sys.argv: + def inform(*a, **k): pass +else: + def inform(*a, **k): + if 'banner' in k: + print '\n%s\n %s\n\n%s\n' % (stars, k['banner'], stars) + else: + for arg in a: + print arg, + print + +def fatal(msg, return_code = 1): + print >> sys.stderr, msg + sys.exit(return_code) + +class tardep(object): + def __init__(self, url, tar, ext, size, dirname = None, md5 = None, indir=None): + self.url = url + tar + ext + self.filename = self.tar = tar + ext + self.dirname = dirname if dirname is not None else tar + self.size = size + self.md5 = md5 + self.indir = indir + if indir: self.dirname = indir + + def get(self): + if not os.path.isdir(self.dirname): + wget_cached(self.tar, self.size, self.url) + + if self.md5 is not None and md5_file(self.tar, hex=True) != self.md5: + raise AssertionError('md5 did not match: %r' % self.tar) + if self.tar.endswith('.zip'): + unzip(self.tar, indir=self.indir) + else: + untar(self.tar, indir=self.indir) + + if self.indir is None: + assert os.path.isdir(self.dirname), self.dirname + else: + assert os.path.isdir(self.indir), self.indir + else: + inform('exists: %s' % self.dirname) + + return self.dirname + +@contextmanager +def timed(name=''): + 'Shows the time something takes.' + + from time import time + + before = time() + try: + yield + finally: + msg = 'took %s secs' % (time() - before) + if name: + msg = name + ' ' + msg + inform(msg) + +@contextmanager +def cd(*path): + ''' + chdirs to path, always restoring the cwd + + >>> with cd('mydir'): + >>> do_stuff() + ''' + original_cwd = os.getcwd() + try: + new_cwd = pathjoin(*path) + #inform('cd %s' % os.path.abspath(new_cwd)) + os.chdir(new_cwd) + yield + finally: + #inform('cd %s' % os.path.abspath(original_cwd)) + os.chdir(original_cwd) + +def which(cmd, default=None): + if platformName == "win": + for adir in os.environ["PATH"].split(os.pathsep): + cmd_path = os.path.join(adir, cmd) + for ext in [''] + os.environ['PATHEXT'].split(os.pathsep): + if os.path.exists(cmd_path + ext): + return cmd_path + ext + else: + return commands.getoutput('which ' + cmd) + + return default + +def run(cmd, checkret = True, expect_return_code = 0, capture_stdout = False, env = None, shell=False, + executable=None, include_stderr=False, print_stdout = None): + ''' + Runs cmd. + + If the process returns 0, returns the contents of stdout. + Otherwise, raises an exception showing the error code and stderr. + ''' + inform(cmd) + if print_stdout is None: + print_stdout = capture_stdout + + try: + if isinstance(cmd, basestring): + args = shlex.split(cmd.replace('\\', '\\\\')) + else: + args = cmd + + if capture_stdout: + process = subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.PIPE, env = env, shell=shell) + else: + process = subprocess.Popen(args, env = env, shell=shell) + except OSError: + print >>sys.stderr, 'Error using Popen: args were %r' % args + raise + + # grab any stdout, stderr -- this means we block until the process is finished! + stdout, stderr = process.communicate() + + # also grab the return code + retcode = process.returncode + + # fail if the return code was an error + if checkret and retcode != expect_return_code: + print stderr + print "Error running %s" % cmd + sys.exit(retcode) + + txt = stdout.strip() if stdout is not None else None + if txt is not None and include_stderr: + stdout += stderr + + if print_stdout and stdout: + print stdout, + + return stdout.strip() if stdout is not None else None + +def downloaded(path, size): + 'Checks to see if file at path exists and has a certain size.' + + return os.path.exists(path) and os.path.getsize(path) == size + +if os.name == 'nt': + def wget(url): + 'wget for windows, implemented with urllib' + + i = url.rfind('/') + file = url[i+1:] + def reporthook(*a): print file, a + + print url, "->", file + import urllib + urllib.urlretrieve(url, file, reporthook) +else: + def wget(url): + 'Downloads the file at url.' + + run(['curl', '-O', url]) + +def unzip(path, verbose = False, indir=None): + "Unzip using Python's zipfile module." + + import zipfile + makedirs, dirname = os.makedirs, os.path.dirname + + f = zipfile.ZipFile(path, 'r') + unzip_dir = os.path.splitext(os.path.basename(path))[0] + try: + makedirs(unzip_dir) + except Exception: + pass + + for info in f.infolist(): + on_disk_filename = filename = info.filename + + if indir is not None: + on_disk_filename = os.path.join(indir, filename) + + try: makedirs(dirname(on_disk_filename)) + except Exception: pass + + if not filename.endswith('/'): + open(on_disk_filename, 'wb').write(f.read(filename)) + +import tarfile +makedirs, dirname = os.makedirs, os.path.dirname + +def untar(path, verbose = False, indir=None): + 'A wimpy untar for tar impoverished systems.' + + if sys.platform.startswith("win"): + print 'untar %s' % path + fileobj = open(path, 'rb') + try: + # try gzipped first + tf = tarfile.open(path, "r:gz", fileobj=fileobj) + except: + fileobj = open(path, 'rb') + tf = tarfile.open(path, 'r', fileobj=fileobj) + + for oInfo in tf: + if verbose: print oInfo.name + + if oInfo.isfile(): + strFile = oInfo.name + if indir is not None: + strFile = os.path.join(indir, strFile) + + try: + makedirs(dirname(strFile)) + except: + pass + + open(strFile, "wb").write(tf.extractfile(oInfo).read()) + else: + flags = "xf" + if path.endswith(".tar.gz") or path.endswith(".tgz"): + flags += "z" + elif path.endswith(".tar.bz2"): + flags += "j" + + if verbose: + flags += "v" + run(['tar', flags, path]) + +def wget_cached(filename, size, url): + 'wget a url, unless filename with size already exists' + + if not downloaded(filename, size): + wget(url) + else: + inform('Already downloaded:', filename) + + assert os.path.isfile(filename), filename + +def mkdirs(p): + 'Recursively make directory path p unless it already exists.' + if not os.path.exists(p): + inform('making directory %r' % p) + os.makedirs(p) + +def locate(pattern, root = None): + if root is None: + root = os.getcwd() + + for path, dirs, files in os.walk(root): + for filename in (os.path.abspath(pathjoin(path, filename)) for filename in files if fnmatch.fnmatch(filename, pattern)): + yield filename + +def filerepl(filename, old, new): + ''' + Replaces all instances of the string "out" with the string "new" in the + specified file. + ''' + with open(filename, 'rb') as fin: + inbytes = fin.read() + outbytes = inbytes.replace(old, new) + if outbytes != inbytes: + with open(filename, 'wb') as fout: + fout.write(outbytes) + return True + +# +# on windows, we build a custom optimized Python. using the "dpy" function +# to run python scripts ensures that this custom Python is the one used to +# launch. +# +# on platforms where we're using stock python, it just calls "python" +# +if False and os.name == 'nt': + def dpy(cmd, platlib = False, addenv=None): + PYTHON_EXE = os.environ.get('PYTHON_EXE') + PYTHON_VER = os.environ.get('PYTHON_VER') + if PYTHON_EXE is None: + try: + from build_python import PYTHON_EXE, PYTHON_VER + except ImportError: + sys.path.append(os.path.abspath(pathjoin(__file__, '../../msw'))) + from build_python import PYTHON_EXE, PYTHON_VER + + env = python_env(platlib, addenv=addenv) + return run(['python'] + cmd, env=env) + if PYTHON_VER == '25': + # some hacks to get Python 2.5's distutils to build with MSVC2008. + env.update(DISTUTILS_USE_SDK='1', + MSSdk='1') + + run([PYTHON_EXE] + cmd, env=env) + + import distutils.msvccompiler + + def list_repl(l, old, new): + if old in l: + l.remove(old) + l.append(new) + + def initialize(self, old_init=distutils.msvccompiler.MSVCCompiler.initialize): + # also on windows, hack distutils to use /EHsc instead of /GX (which is + # deprecated) + res = old_init() + list_repl(self.compile_options, '/GX', '/EHsc') + list_repl(self.compile_options_debug, '/GX', '/EHsc') + return res + + distutils.msvccompiler.MSVCCompiler.initialize = initialize + + +else: + def dpy(cmd, platlib = False): + run(['python'] + cmd, env=python_env(platlib)) + +def python_env(platlib = False, addenv=None): + ''' + Returns an environment mapping for running DPython. + + platlib: Add DEPS_DIR to PYTHONPATH if True + ''' + + env = dict(os.environ) + + if platlib: + env['PYTHONPATH'] = os.path.pathsep.join([env.get('PYTHONPATH', ''), DEPS_DIR]) + + if addenv is not None: + env.update(addenv) + + return env + +def copytree_filter(src, dst, symlinks=False, filefilter=None, only_different = False): + ''' + Copies a tree of files, testing an optional predicate function (filefilter) + against each file. If the predicate returns True, the file is copied. + ''' + from shutil import Error, copy2, copystat + + if filefilter is None: + filefilter = lambda filename: True + + if only_different: + copy_func = copy_different + else: + def copy_func(src, dest): + print 'copy', src, '->', dst + return copy2(src, dest) + + names = os.listdir(src) + if not os.path.isdir(dst): + os.makedirs(dst) + errors = [] + for name in names: + srcname = pathjoin(src, name) + dstname = pathjoin(dst, name) + try: + if symlinks and os.path.islink(srcname): + linkto = os.readlink(srcname) + os.symlink(linkto, dstname) + elif os.path.isdir(srcname): + copytree_filter(srcname, dstname, symlinks, filefilter) + else: + if filefilter(srcname): + copy_func(srcname, dstname) + # XXX What about devices, sockets etc.? + except (IOError, os.error), why: + errors.append((srcname, dstname, str(why))) + # catch the Error from the recursive copytree so that we can + # continue with other files + except Error, err: + errors.extend(err.args[0]) + + if platformName == 'win': + try: + copystat(src, dst) + except WindowsError: + pass # can't copy file access times on Windows + except OSError, why: + errors.extend((src, dst, str(why))) + else: + try: + copystat(src, dst) + except OSError, why: + errors.extend((src, dst, str(why))) + + if errors: + raise Error, errors + +def _hash_file(hashobj, fileobj, chunksize = 1024): + read, update = fileobj.read, hashobj.update + chunk = read(chunksize) + while chunk: + update(chunk) + chunk = read(chunksize) + +def md5_file(f, hex = False): + with open(f, 'rb') as fobj: + md5obj = hashlib.md5() + _hash_file(md5obj, fobj) + + return md5obj.hexdigest() if hex else md5obj.digest() + +def files_different(src, dest): + from os.path import isfile, split as pathsplit, getsize, isdir + + srcparent, destparent = pathsplit(src)[0], pathsplit(dest)[0] + + # TODO: mtime? + if not isdir(srcparent) or not isdir(destparent): + return True + if not isfile(src) or not isfile(dest): + return True + if getsize(src) != getsize(dest): + return True + + if md5_file(src) != md5_file(dest): + return True + + return False + +def copy_with_prompt(src, dest): + ''' + Tries to copy src to dest. + + If an IOError is raised, prompt for a retry. (This helps greatly when copying + binaries on Windows that might be running.) + ''' + try_again = True + while try_again: + try: + shutil.copy2(src, dest) + except IOError, e: + print e + inp = raw_input('Retry? [Y|n] ') + + if inp and not inp.lower().startswith('y'): + raise SystemExit(1) + else: + try_again = True + else: + try_again = False + +def copy_different(src, dest, prompt_on_deny = True, destname=None): + ''' + Copy src to dest, but only if it is different (the files are hashed). + + If prompt_on_deny is True, a prompt will occur on an IOError (like copy_with_prompt). + ''' + if not os.path.isfile(src): + raise AssertionError(src + " not found, cwd is " + os.getcwd()) + + srcname = os.path.split(src)[1] + if os.path.isdir(dest): + dest = pathjoin(dest, srcname if destname is None else destname) + + if files_different(src, dest): + inform('* %s -> %s' % (srcname, dest)) + + copy_with_prompt(src, dest) + else: + inform('X %s -> %s (skipped)' % (srcname, dest)) diff --git a/digsby/build/buildutil/common.py b/digsby/build/buildutil/common.py new file mode 100644 index 0000000..b410e29 --- /dev/null +++ b/digsby/build/buildutil/common.py @@ -0,0 +1,83 @@ +#__LICENSE_GOES_HERE__ +import commands +import os +import sys + +scriptDir = os.path.abspath(sys.path[0]) +digsbyDir = os.path.abspath(os.path.join(scriptDir, "..")) +sys.path += [digsbyDir] + +startDir = os.getcwd() +homeDir = None + +from .buildfileutils import which + +if sys.platform.startswith("win"): + homeDir = os.environ['USERPROFILE'] +else: + homeDir = os.environ["HOME"] + +assert os.path.exists(homeDir) + +def get_patch_cmd(): + if os.name != "nt": + return 'patch' + + unix_tools = os.environ.get('UNIX_TOOLS') + if unix_tools is not None: + patch_cmd = os.path.join(unix_tools, 'patch') + elif os.path.isdir(r'c:\Program Files (x86)\Git\bin'): + patch_cmd = r'c:\Program Files (x86)\Git\bin\patch' + else: + patch_cmd = which('patch', r'c:\cygwin\bin\patch') + + return patch_cmd + +class BuildDirs: + def __init__(self): + self.depsDir = None + self.wxWidgetsDir = None + self.wxPythonDir = None + self.wxWebKitDir = None + self.sipDir = None + self.wxpyDir = None + self.boostDir = None + + def initBuildDirs(self, depsDir, **overrides): + self.depsDir = depsDir + self.wxWidgetsDir = os.path.join(self.depsDir, "wxWidgets") + self.wxPythonDir = os.path.join(self.wxWidgetsDir, "wxPython") + self.wxWebKitDir = os.path.join(self.depsDir, overrides.get("wxWebKit", "wxWebKit")) + self.sipDir = os.path.join(self.depsDir, "sip") + self.wxpyDir = os.path.join(self.depsDir, "wxpy") + self.boostDir = os.path.join(self.depsDir, 'boost_1_42_0') + +buildDirs = BuildDirs() + +if sys.platform.startswith('win'): + common_dir = os.path.dirname(os.path.abspath(__file__)) + buildDirs.initBuildDirs(os.path.join(os.path.abspath(os.path.join(common_dir, '..')), 'msw'), wxWebKit='WebKit') + # build boost + from buildfileutils import tardep + boost = tardep('http://mini/mirror/', 'boost_1_42_0', '.tar.gz', 40932853, dirname = buildDirs.boostDir) + #boost.get() + #^ copied from build-deps.py + + +def checkForDeps(swig=False): + retVal = which("which bakefile") + + if retVal != 0: + print "ERROR: You must have Bakefile (http://bakefile.org) installed to continue. Exiting..." + sys.exit(1) + + if swig: + retVal = which("which swig") + + if retVal != 0: + print "ERROR: You must have Robin's SWIG (http://wxpython.wxcommunity.com/tools/) installed to continue. Exiting..." + sys.exit(1) + + if not sys.platform.startswith("win") and commands.getoutput("swig -version").find("1.3.29") == -1: + print "ERROR: Wrong SWIG. You must install Robin's SWIG (http://wxpython.wxcommunity.com/tools/). Exiting..." + sys.exit(1) diff --git a/digsby/build/buildutil/git.py b/digsby/build/buildutil/git.py new file mode 100644 index 0000000..24f6e8c --- /dev/null +++ b/digsby/build/buildutil/git.py @@ -0,0 +1,20 @@ +#__LICENSE_GOES_HERE__ +import os +import os.path +import buildutil + +GIT_BIN = ['git'] + +## look up msys-git installation in %ProgramFiles% (%ProgramFiles(x86)% in case of 64bit python) +if os.name == 'nt': + progfiles = os.environ.get('ProgramFiles(x86)', os.environ.get('ProgramFiles', None)) + if progfiles is not None: + git_bin = buildutil.which('git', os.path.join(progfiles, 'Git', 'cmd', 'git.cmd')) + GIT_BIN = [git_bin] + if git_bin.lower().endswith('.cmd'): + cmd = buildutil.which('cmd', buildutil.which('command')) + if cmd is not None: + GIT_BIN[:0] = [cmd, '/c'] + +def run(cmd): + buildutil.run(GIT_BIN + cmd) diff --git a/digsby/build/buildutil/promptlib.py b/digsby/build/buildutil/promptlib.py new file mode 100644 index 0000000..6ce2fad --- /dev/null +++ b/digsby/build/buildutil/promptlib.py @@ -0,0 +1,200 @@ +#__LICENSE_GOES_HERE__ +_pre_functions = [] +_post_functions = [] + +def register_pre_handler(pred, pre): + _pre_functions.insert(0, (pred, pre)) + +def register_post_handler(pred, post): + _post_functions.insert(0, (pred, post)) + +def register_prompt_handler(f): + funcs = f() + pred = funcs.get('pred') + pre = funcs.get('pre') + post = funcs.get('post') + if pred is None: + raise Exception('%r doesn\'t seem to be the right sort of function for a prompt handler', f) + + if pre is not None: + register_pre_handler(pred, pre) + if post is not None: + register_post_handler(pred, post) + + return f + +@register_prompt_handler +def bool_type(): + def pred(o, d): + return o is bool + + def pre(o,d): + if d is True: + return '', '(Y/n)' + elif d is False: + return '', '(y/N)' + else: + return '', '(y/n)' + + def post(t, o, d): + t = t.lower() + if t in ('y', 'n'): + return True if t == 'y' else False + return d + + return locals() + +@register_prompt_handler +def confirm_type(): + def pred(o, d): + return o == 'confirm' and d is not None + def pre(o, d): + return '', ': type "%s" (or "CANCEL" to cancel)' % d + def post(t, o, d): + if t == d: return True + elif t == 'CANCEL': return False + else: return None + return locals() + +@register_prompt_handler +def str_type(): + def pred(o, d): + return o is str + def pre(o, d): + return ('(default = %r)' % (d,)), '' + def post(t, o, d): + return t or d + return locals() + +@register_prompt_handler +def strlist_type(): + def pred(o, d): + return o is list + def pre(o, d): + return ('(default = %r)' % (d,)), '(comma-separated)' + def post(t, o, d): + if t: + return map(str.strip, t.strip(',').split(',')) + else: + return d + return locals() + +@register_prompt_handler +def list_type(): + def pred(o, d): + return type(o) is list + def pre(o, d): + options_str = '\n\t' + '\n\t'.join('(%d) %s' % (i+1, s) for i,s in enumerate(o)) + default_str = '\n(default = %r)' % d + return default_str, options_str + def post(t, o, d): + if not t: + return d + try: + idx = int(t) + except Exception: + return None + else: + if idx == 0: + return None + try: + return o[idx-1] + except IndexError: + return None + return locals() + +def pre_prompt(prompt_str, options, default): + pre_func = find_pre_function(options, default) + if pre_func is None: + raise NotImplementedError("Don't know what to do for options = %r, default = %r", options, default) + + default_str, options_str = pre_func(options, default) + + full_prompt_str = ("%s %s %s" % (prompt_str, options_str, default_str)).strip() + ": " + return full_prompt_str, options, default + +def prompt(prompt_str, options = None, default = None, input_func = raw_input): + prompt_str, options, default = pre_prompt(prompt_str, options, default) + result = None + while result is None: + try: + text = input_func(prompt_str).strip() + except Exception, e: + raise e + else: + result = post_prompt(text, options, default) + if default is None and result is None: + break + + return result + +def find_match_for(pred_func_list, *args): + for pred, func in pred_func_list: + if pred(*args): + return func + +def find_pre_function(options, default): + return find_match_for(_pre_functions, options, default) + +def find_post_function(options, default): + return find_match_for(_post_functions, options, default) + +def post_prompt(text, options, default): + post_func = find_post_function(options, default) + if post_func is None: + raise NotImplementedError("Don't know what to do for options = %r, default = %r", options, default) + + return post_func(text, options, default) + + +def _main(): + 'bool confirm str strlist list' + booltests = ( + (bool, True, ['n'], False), + (bool, False, ['y'], True), + (bool, None, ['', 'y'], True), + (bool, True, [''], True), + (bool, False, [''], False), + ) + confirmtests = ( + ('confirm', 'b', ['b'], True), + ('confirm', 'b', ['CANCEL'], False), + ('confirm', 'b', ['a', 'CANCEL'], False), + ('confirm', 'b', ['a', 'b'], True), + ) + strtests = ( + (str, 'a', ['b'], 'b'), + (str, 'a', [''], 'a'), + (str, None, ['b'], 'b'), + (str, None, ['', 'a'], 'a'), + ) + strlisttests = ( + (list('abcd'), 'a', [''], 'a'), + (list('abcd'), 'a', [''], 'a'), + (list('abcd'), 'a', [''], 'a'), + (list('abcd'), 'a', [''], 'a'), + ) + listtests = ( + #(), + ) + + for tests in (booltests, confirmtests, strtests, strlisttests, listtests): + for opts, default, inputs, expected in tests: + def input(): + yield None + for input in inputs: + yield input + while True: + yield '' + + input_gen = input() + input_gen.next() + result = prompt(repr(opts), opts, default, input_gen.send) + input_gen.close() + if result == expected: + print ' O' + else: + print ' X (%r != %r)' % (result, expected) + +if __name__ == '__main__': + _main() diff --git a/digsby/build/buildutil/signing.py b/digsby/build/buildutil/signing.py new file mode 100644 index 0000000..3c5908c --- /dev/null +++ b/digsby/build/buildutil/signing.py @@ -0,0 +1,22 @@ +#__LICENSE_GOES_HERE__ +from buildutil.buildfileutils import run +from buildutil.promptlib import prompt + +def Authenticode(exe): + print '*** signing executable %r ***' % exe + # signtool should be in a path like: C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin + # Add the directory to your %PATH% + + # You may also need the latest CAPICOM SDK, which was last seen at + # http://www.microsoft.com/download/en/details.aspx?DisplayLang=en&id=25281 + try: + run(["signtool", "sign", "/a", "/t", "http://timestamp.verisign.com/scripts/timstamp.dll", str(exe)]) + except Exception, e: + print '\t\tError signing executable %r: %r' % (exe, e) + keep_going = prompt("Continue?", bool, False) + + if not keep_going: + raise e + else: + print '*** signed %r ***' % exe + diff --git a/digsby/build/compiledeps.py b/digsby/build/compiledeps.py new file mode 100755 index 0000000..f20a950 --- /dev/null +++ b/digsby/build/compiledeps.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python +#__LICENSE_GOES_HERE__ + +''' + +builds the following external dependencies for Digsby: + - libxml2 (with Python bindings + - PySyck + - PIL (http://www.pythonware.com/products/pil/) + - TODO: lacking JPEG and TrueType support + - M2Crypto + +all binaries go into DEPS_DIR, defined below + +''' + +from __future__ import with_statement + +import os +import sys +from distutils.sysconfig import get_python_lib + +compiledeps_dir = os.path.dirname(os.path.abspath(__file__)) + +deps = 'libxml2 m2crypto syck pil'.split() + +usage = '''\ +compile_deps.py + +Downloads, builds, and installs dependencies for Digsby. + + $ python compile_deps.py all builds all libraries + $ python compile_deps.py libxml2 builds just libxml2 + +Available libraries are: +%s +''' % '\n'.join(' ' + dep for dep in deps) + +from os.path import exists, join as pathjoin, abspath, isdir, split as pathsplit +from buildutil import * +from constants import MINI_LOCAL, MINI_HTTP +MINI_LOCAL = None +MINI_HTTP = None + +def inform(text): + print text + +patch_cmd = get_patch_cmd() + +if not os.path.exists(DEPS_DIR): + os.makedirs(DEPS_DIR) + +# Point PYTHONPATH here so that we can test libraries after +# building/installing them here. +sys.path.insert(0, DEPS_DIR) + +# +# LIBXML2 +# +libxml2_tarball, libxml2_tarball_size = 'libxml2-2.7.7.tar.gz', 4868502 +libxml2_url = 'http://xmlsoft.org/sources/' + libxml2_tarball +libxml2_dirname = libxml2_tarball.replace('.tar.gz', '') +libxml2_setup_py_patch = abspath('./libxml2.setup.py.patch') + +libxslt = tardep('http://xmlsoft.org/sources/', 'libxslt-1.1.24', '.tar.gz', 3363961) + + +# +# LXML +# +lxml = tardep('http://pypi.python.org/packages/source/l/lxml/', 'lxml-2.2', '.tar.gz', 2931993, + md5 = 'b3f12344291aa0d393915e7d8358b480') + +# +# PyObjC +# +pyobjc = tardep('http://pypi.python.org/packages/source/p/pyobjc/', 'pyobjc-2.2b2', '.tar.gz', 6231, + md5 = '9613a79b3d883e26f92b310604568e1f') + +# +# SYCK +# +syck_tarball, syck_tarball_size = 'syck-0.61+svn231+patches.tar.gz', 311647 +syck_url = 'http://pyyaml.org/download/pysyck/' + syck_tarball +syck_dirname = syck_tarball.replace('.tar.gz', '') + +pysyck_tarball, pysyck_tarball_size = 'PySyck-0.61.2.tar.gz', 44949 +pysyck_url = 'http://pyyaml.org/download/pysyck/' + pysyck_tarball +pysyck_dirname = pysyck_tarball.replace('.tar.gz', '') +pysyck_cfg = '''\ +[build_ext] +include_dirs=%s/include +library_dirs=%s/lib +''' % (DEPS_DIR, DEPS_DIR) + +# +# PIL +# +pil_tarball, pil_tarball_size = 'Imaging-1.1.6.tar.gz', 435854 +pil_url = 'http://effbot.org/media/downloads/' + pil_tarball +pil_dirname = pil_tarball.replace('.tar.gz', '') + +freetype_tar = tardep('http://voxel.dl.sourceforge.net/sourceforge/freetype/', + 'freetype-2.3.7', '.tar.gz', 1781554) +freetype_win_lib = 'freetype237MT.lib' if not DEBUG else 'freetype237MT_D.lib' + +# +# M2CRYPTO +# +if MINI_LOCAL: + m2crypto_svn_base = '%s/svn/m2crypto-mirror/' % MINI_HTTP +else: + m2crypto_svn_base = 'http://svn.osafoundation.org/m2crypto/' +m2crypto_svn_url = m2crypto_svn_base + 'tags/0.19.1/' +m2crypto_dir = 'm2crypto' + +# +# LIBJPEG +# + +libjpeg_tarball, libjpeg_size= 'jpegsrc.v6b.tar.gz', 613261 +libjpeg_url= 'http://wxwebkit.wxcommunity.com/downloads/deps/' + libjpeg_tarball +libjpeg_dirname = 'jpeg-6b' + + +# +# Mac OS X SDK flags support +# + +sdk = '/Developer/SDKs/MacOSX10.5.sdk' +sdkflags = "-isysroot %s -arch i386 -arch ppc" % sdk +sdkcflags = sdkflags + " -I" + sdk + "/usr/include" # for Python since it overrides its own sdk setting... +sdkldflags = sdkflags + " -L" + sdk + "/usr/lib -Wl,-search_paths_first" # ditto +makeflags = [] +configureflags = [] + +if sys.platform.startswith('darwin'): + configureflags= ['--disable-dependency-tracking'] + makeflags = ["CFLAGS=%s" % sdkflags, "LDFLAGS=%s" % sdkflags] + +def download_libxml2(): + if not isdir(libxml2_dirname): + wget_cached(libxml2_tarball, libxml2_tarball_size, libxml2_url) + untar(libxml2_tarball) + return libxml2_dirname + +def build_libxml2_mac(): + banner('libxml2') + + # + # NOTE: mac only--this links against the system libxml2 + # + if not os.path.exists('libxml2-2.6.16'): + run('curl -O ftp://ftp.gnome.org/pub/GNOME/sources/libxml2/2.6/libxml2-2.6.16.tar.gz'.split()) + run('tar xvfz libxml2-2.6.16.tar.gz'.split()) + + with cd('libxml2-2.6.16/python'): + dpy('setup.py build'.split()) + dpy(['setup.py', 'install', '--install-lib', DEPS_DIR]) + + if not os.path.exists(lxml.dirname): + lxml.get() + if not os.path.exists(libxslt.dirname): + libxslt.get() + # LXML needs newer versions than what are provided by the OS, but since we have some low-level + # components that rely on the OS version, we need to statically link libxml2 here so as not + # to have conflicts loading two different versions of libxml which are not compatible. + with cd(lxml.dirname): + dpy(['setup.py', 'install', + '--install-lib', DEPS_DIR, + 'build', '--static-deps', '--libxml2-version=2.7.3', '--libxslt-version=1.1.24'], + platlib=True) + + +def build_libxml2(): + download_libxml2() + + with cd(libxml2_dirname): + # build libxml2 + + # TODO: disable unneeded libxml2 features--right now disabling + # things appears to confuse the Python bindings. + #disabled_features = 'ftp http docbook valid'.split() + #config_args = ' '.join('--without-%s' % feature for feature in disabled_features) + + if os.name == 'nt': + return + else: + run(['./configure', + '--prefix=%s' % DEPS_DIR, + '--exec-prefix=%s' % DEPS_DIR, + ] + + configureflags) + run(['make'] + makeflags) + run(['make', 'install']) + #run('rm -rf %s/share %s/bin %/include' % (3*(DEPS_DIR,))) + + with cd('python'): + # make sure libxml2's setup.py finds the headers for the libxml2 + # we just downloaded and built. + + # t is for "batch" and means don't return an error if the patch + # has already been applied + + os.system(r'%s -p0 -N -i %s/../libxml2-rootfirst.patch' % (patch_cmd, DEPS_DIR)) + + # run('bash -c "patch -t -p0 setup.py < %s"' % libxml2_setup_py_patch) + + dpy(['setup.py', 'install', '--install-lib', DEPS_DIR]) + if not os.path.exists(libxslt.dirname): + libxslt.get() + with cd(libxslt.dirname): + + run(['./configure', + '--prefix=%s' % DEPS_DIR, + '--exec-prefix=%s' % DEPS_DIR, + '--with-libxml-prefix=%s' % DEPS_DIR] + configureflags) + run(['make'] + makeflags) + run(['make', 'install']) + with cd(lxml.dirname): + dpy(['setup.py' + 'install', '--install-lib', DEPS_DIR, + '--with-xml2-config=%s/bin/xml2-config' % DEPS_DIR, + '--with-xslt-config=%s/bin/xslt-config' % DEPS_DIR, + ]) + +def fix_symlinks(openssl_dir): + with cd(openssl_dir, 'include', 'openssl'): + for header in locate('*.h'): + with open(header, 'r') as f: + text = f.read() + + if text.startswith('link '): + target = text[5:] + + print 'fixing symlink in %s to %s' % (header, target) + with open(target, 'r') as target_file: + with open(header, 'w') as f: + f.write(target_file.read()) + +def build_m2crypto(): + banner('M2Crypto') + + if not exists(m2crypto_dir): + run(['svn', 'co', m2crypto_svn_url, m2crypto_dir]) + + with cd(m2crypto_dir): + from buildutil import common + run([patch_cmd, '-p0', '-i', os.path.join(compiledeps_dir, 'm2crypto-ssl-wanterrors-zero-return.diff')]) + + if os.name == 'nt': + openssl = abspath(r'C:\OpenSSL-Win32') + assert exists(openssl), 'cannot find openssl' + + # svn puts "link PATH" for symlinks... + fix_symlinks(openssl) + + # copy a fixed setup.py + from buildutil import copy_different + dbg_ext = '.debug' if DEBUG else '' + copy_different(pathjoin('msw', 'm2crypto.setup.py.msvc2008' + dbg_ext), pathjoin(m2crypto_dir, 'setup.py')) + + with cd(m2crypto_dir): + # Fix M2Crypto so that it doesn't monkeypatch urllib. + # do it before running setup.py because that creates an egg now + # so it's a lot easier to patch before it's eggified + f = os.path.join('M2Crypto', 'm2urllib.py') + assert os.path.isfile(f), f + + lines = [] + with open(f) as infile: + for line in infile.readlines(): + if line == 'URLopener.open_https = open_https\n': + line = '# ' + line + lines.append(line) + + with open(f, 'w') as out: + out.writelines(lines) + + if os.name == 'nt': + libdirs = pathjoin(openssl, 'lib') + debug_build_flag = ['--debug'] if DEBUG else [] + dpy(['setup.py', 'build_ext', '-o', openssl, '-L' + libdirs] + debug_build_flag) + + dpy(['setup.py', 'install', '--install-lib', get_python_lib()]) + else: + if sys.platform.startswith('darwin'): + os.environ['CFLAGS'] = sdkcflags + os.environ['LDFLAGS'] = sdkldflags + dpy(['setup.py', '--verbose', 'build_ext', 'install', + '-O2', '--install-lib', DEPS_DIR], platlib=True) + + if sys.platform.startswith('darwin'): + os.environ['CFLAGS'] = '' + os.environ['LDFLAGS'] = '' + +if False and os.name == 'nt': + # prebuilt PySyck for MSVC2008 + def build_syck(): + from buildutil import wget_cached, unzip + with cd(DEPS_DIR): + dbgpostfix = '_d' if DEBUG else '' + zip = 'pysyck_msvc2008_%s%s.zip' % (sys.version[:3].replace('.', ''), dbgpostfix) + + wget_cached(zip, 42443, 'http://symbolsystem.nfshost.com/build/msvc2008/' + zip) + unzip(zip) +# os.remove(zip) +else: + def build_syck(): + banner('syck') + + if not isdir(syck_dirname): + wget_cached(syck_tarball, syck_tarball_size, syck_url) + untar(syck_tarball) + + with cd(syck_dirname): + global sdkflags + sdkflags_syck = "" + if sys.platform.startswith('darwin'): + sdkflags_syck = sdkflags + makeflags = ["CFLAGS=%s" % sdkflags_syck, + "LDFLAGS=%s -L%s/lib" % (sdkflags_syck, os.path.abspath('.'))] + + run(['./configure', + '--prefix=%s' % DEPS_DIR, + '--exec-prefix=%s' % DEPS_DIR] + + configureflags) + run(['make'] + makeflags) + run(['make', 'install']) + + banner('pysyck') + + if not isdir(pysyck_dirname): + wget_cached(pysyck_tarball, pysyck_tarball_size, pysyck_url) + untar(pysyck_tarball) + + with cd(pysyck_dirname): + # write out a small .cfg file to tell PySyck's setup.py where to + # find syck includes and libraries + with open('setup.cfg', 'w') as setup_cfg: + setup_cfg.write(pysyck_cfg) + dpy(['setup.py', 'install', '--install-lib', DEPS_DIR]) + +def build_pil(): + banner('PIL') + + if not isdir(pil_dirname): + wget_cached(pil_tarball, pil_tarball_size, pil_url) + untar(pil_tarball) + + jpeg = libjpeg_dirname + zlib = '/usr/lib' + freetype = None + + if sys.platform == 'darwin': + freetype = '/Developer/SDKs/MacOSX10.5.sdk/usr/X11R6' + if not isdir(libjpeg_dirname): + wget_cached(libjpeg_tarball, libjpeg_size, libjpeg_url) + untar(libjpeg_tarball) + + with cd(libjpeg_dirname): + bindir = os.path.join(DEPS_DIR, "bin") + if not os.path.exists(bindir): + os.makedirs(bindir) + mandir = os.path.join(DEPS_DIR, "man", "man1") + if not os.path.exists(mandir): + os.makedirs(mandir) + run(['./configure', + '--prefix=%s' % DEPS_DIR, + '--exec-prefix=%s' % DEPS_DIR, + ] + + configureflags) + run(['make'] + makeflags) + run(['make', 'install']) + + jpeg = abspath(libjpeg_dirname) + assert jpeg and isdir(jpeg) + elif sys.platform == 'win32': + jpeg = abspath(pathjoin('msw', 'jpeg-7')) + zlib = abspath(pathjoin('msw', 'zlib-1.2.3')) + freetype = build_freetype_msw() + + assert isdir(jpeg), jpeg + assert isdir(zlib), zlib + assert all(isdir(d) for d in freetype), freetype + else: + jpeg = None + + with cd(pil_dirname): + if jpeg is not None: + filerepl('setup.py', 'JPEG_ROOT = None', 'JPEG_ROOT = %r' % jpeg) + if zlib is not None: + filerepl('setup.py', + 'elif sys.platform == "win32" and find_library_file(self, "zlib"):', + 'elif sys.platform == "win32" and find_library_file(self, "zlib1"):') + filerepl('setup.py', + ' feature.zlib = "zlib" # alternative name', + ' feature.zlib = "zlib1" # alternative name') + filerepl('setup.py', 'ZLIB_ROOT = None', 'ZLIB_ROOT = %r' % zlib) + if freetype is not None: + if isinstance(freetype, type(())): + filerepl('setup.py', 'FREETYPE_ROOT = None', 'FREETYPE_ROOT = %r' % (freetype,)) + else: + filerepl('setup.py', 'FREETYPE_ROOT = None', 'FREETYPE_ROOT = libinclude(%r)' % (freetype,)) + + dpy(['setup.py', 'clean']) + + debug_flag = ' --debug' if DEBUG else '' + + # TODO: is there a way to pass --debug to "build" other than through setup.cfg + # and still have install work correctly? + with open('setup.cfg', 'w') as f: f.write('[build]\ndebug=%d' % DEBUG) + + dpy(['setup.py', 'install', '--install-lib', DEPS_DIR, '--install-scripts', '.']) + +def build_freetype_msw(): + # get/build freetype + with cd('msw'): + ftype_dir = freetype_tar.get() + with cd(ftype_dir, 'builds', 'win32', 'visualc'): + + # use /upgrade to upgrade old VC6 build scripts + with open('freetype.dsw') as f: + needs_upgrade = 'Format Version 6.00' in f.read() + + run(['vcbuild', + '/nologo', '/time'] + + (['/upgrade'] if needs_upgrade else []) + + ['freetype.sln', '%s Multithreaded|Win32' % ('Debug' if DEBUG else 'Release')] + ) + + with cd(ftype_dir, 'objs'): + # copy the lib to a name PIL is looking for + from buildutil import copy_different + copy_different(freetype_win_lib, 'freetype.lib') + + freetype = (abspath(pathjoin(ftype_dir, 'objs')), + abspath(pathjoin(ftype_dir, 'include'))) + + return freetype + +def build_pyobjc(): + banner('PyObjC') + + try: + import objc + except: + pyobjc.get() + + with cd(pyobjc.dirname): + run(['python', 'setup.py', 'install']) + +def install_deps(): + # strip off the script name + argv = sys.argv + if argv[0] == globals()['__file__']: + argv = argv[1:] + + startdir = os.getcwd() + if not sys.platform.startswith('win'): + depsDir = os.path.join(homeDir, "digsby-deps", "py" + sys.version[:3]) + if not os.path.exists(depsDir): + os.makedirs(depsDir) + os.chdir(depsDir) + + if not argv: + print usage + return 0 + + if 'all' in argv: + argv = deps + + invalid = set(argv) - set(deps) + if invalid: + print >> sys.stderr, 'invalid arguments:', ', '.join(a for a in argv if a in invalid) + return 1 + + for dep in argv: globals()['build_' + dep]() + + os.chdir(startdir) + +def banner(txt): + print '\n' + '\n'.join(['*' * 80, ' ' + txt, '*' * 80]) + +if __name__ == '__main__': + original_working_dir = os.getcwd() + try: + sys.exit(install_deps()) + finally: + os.chdir(original_working_dir) diff --git a/digsby/build/constants.py b/digsby/build/constants.py new file mode 100644 index 0000000..f7de8f9 --- /dev/null +++ b/digsby/build/constants.py @@ -0,0 +1,199 @@ +#__LICENSE_GOES_HERE__ +import sys + +#set this to "True" to prefer mini's mirror repositories when they exist +MINI_LOCAL = True +#in case of differing hostname/ip/port/etc. +MINI_LOCATION = 'mini' +MINI_HTTP = 'http://%s' % MINI_LOCATION + +if MINI_LOCAL: + PYTHON_PROJECTS_SVN = '%s/svn/python-mirror' % MINI_HTTP +else: + PYTHON_PROJECTS_SVN = 'http://svn.python.org/projects' + +# remote SIP info +SIP_GIT_REPO = 'https://github.com/kevinw/sip.git' +SIP_GIT_REMOTE = 'origin' +SIP_GIT_BRANCH = 'master' + +# location of some extra tarballs +TARBALL_URL = '%s/deps/' % MINI_HTTP + +WXWEBKIT_GIT_REPO = 'https://github.com/mikedougherty/webkit.git' + +WXWEBKIT_SVN_DIR = "http://svn.webkit.org/repository/webkit/trunk" + +if MINI_LOCAL: + WXWIDGETS_BASE_SVN = "%s/svn/wx-mirror/" % MINI_HTTP +else: + WXWIDGETS_BASE_SVN = "http://svn.wxwidgets.org/svn/wx/" + +WXWIDGETS_28_SVN_DIR = WXWIDGETS_BASE_SVN + "wxWidgets/branches/wxWebKitBranch-2.8" +WXPYTHON_28_SVN_DIR = WXWIDGETS_BASE_SVN + "wxPython/branches/WX_2_8_BRANCH" + +WXWIDGETS_TRUNK_SVN_DIR = WXWIDGETS_BASE_SVN + "wxWidgets/trunk" +WXPYTHON_TRUNK_SVN_DIR = WXWIDGETS_BASE_SVN + "wxPython/trunk" + +# remote WXPY info +WXPY_GIT_REPO = 'https://github.com/kevinw/wxpy.git' + +DEBUG_ARG = '--DEBUG' +DEBUG = False + +if DEBUG_ARG in sys.argv: + sys.argv.remove(DEBUG_ARG) + DEBUG = True +elif hasattr(sys, 'gettotalrefcount'): + DEBUG = True + +DEBUG_POSTFIX = '_d' if DEBUG else '' + +if DEBUG: + class wxconfig: + debug_runtime = True + debug_assertions = True + debug_symbols = True + whole_program_optimization = False + exceptions = False + disable_all_optimization = True + html = False +else: + class wxconfig: + debug_runtime = False # /MDd + debug_assertions = False # (/D__WXDEBUG__) + debug_symbols = True # (/Zi, /DEBUG) if True, PDB symbol files will be generated alongside teh DLLs + whole_program_optimization = True # (/GL, /LTCG) enable whole program optimization + exceptions = False # (/EHa) enables C++ exceptions + disable_all_optimization = False # (/Od) disables all optimizations + html = False + +# +# These entries correspond to wxUSE_XXX entries and are edited +# into wxWidgets/include/msw/setup.h. +# +# The goal is to make the binaries as small as possible! +# +setup_h_use_flags = dict( + EXCEPTIONS = int(wxconfig.exceptions), + UNICODE = 1, + CMDLINE_PARSER = 0, + FSVOLUME = 0, + DIALUP_MANAGER = 0, + FS_ZIP = 0, + FS_ARCHIVE = 0, + FS_INET = 0, + ARCHIVE_STREAMS = 0, + ZIPSTREAM = 0, + TARSTREAM = 0, + JOYSTICK = 0, + PROTOCOL_FTP = 0, + PROTOCOL_HTTP = 0, + VARIANT = 0, + REGEX = 0, + MEDIACTRL = 0, + XRC = 0, + + AUI = 0, + GRAPHICS_CONTEXT = 1, + ANIMATIONCTRL = 0, + COLLPANE = 0, + DATAVIEWCTRL = 0, + DATEPICKCTRL = 0, + LISTBOOK = 0, + CHOICEBOOK = 0, + TREEBOOK = 0, + TOOLBOOK = 0, + GRID = 0, + FINDREPLDLG = 0, + PROGRESSDLG = 0, + STARTUP_TIPS = 0, + SPLASH = 0, + WIZARDDLG = 0, + ABOUTDLG = 0, + METAFILE = 0, + ENH_METAFILE = 0, + MDI = 0, + DOC_VIEW_ARCHITECTURE = 0, + MDI_ARCHITECTURE = 0, + PRINTING_ARCHITECTURE = 0, + RICHTEXT = 0, + HELP = 0, + HTML = 1 if wxconfig.html else 0, + WXHTML_HELP = 1 if wxconfig.html else 0, + CONSTRAINTS = 0, + + PNM = 0, # image formats + IFF = 0, + PCX = 0, + + POSTSCRIPT_ARCHITECTURE_IN_MSW = 0, + + CONFIG_NATIVE = 0, + + #CAIRO = 1, + + #STL = 0, causeswebkit compiliation errors? + + #INKEDIT = 1, FIX tablet support first! + #SOCKET = 0, makes wxURL fail -- needed by wxHTML + XML = 0, +) + +configure_flags = [ + "--enable-graphics_ctx", + "--disable-cmdline", + "--disable-fs_archive", + "--disable-fs_inet", + "--disable-fs_zip", + "--disable-dialupman", + "--disable-arcstream", + "--disable-tarstream", + "--disable-zipstream", + "--disable-joystick", + "--disable-protocol", + "--disable-protocols", + "--disable-variant", + "--without-regex", + "--disable-mediactrl", + "--disable-xrc", + "--disable-aui", + "--disable-animatectrl", + "--disable-collpane", + "--enable-logwin", + "--disable-logdialog", + "--disable-dataviewctrl", + "--disable-datepick", + "--disable-listbook", + "--disable-choicebook", + "--disable-treebook", + "--disable-toolbook", + "--disable-grid", + "--disable-finddlg", + "--disable-progressdlg", + "--disable-tipdlg", + "--disable-splash", + "--disable-wizarddlg", + "--disable-aboutdlg", + "--disable-metafiles", + "--disable-mdi", + "--disable-docview", + "--disable-mdidoc", + "--disable-printarch", + "--disable-richtext", + "--disable-help", + "--disable-htmlhelp", + "--disable-html", + "--disable-constraints", + "--disable-propgrid", + "--enable-config", + "--enable-debug_flag", + "--enable-std_string", + "--enable-stl" +] + +if sys.platform.startswith('darwin'): + configure_flags += [ + '--with-macosx-version-min=10.4', + # '--with-macosx-sdk=/Developer/SDKs/MacOSX10.4u.sdk', + ] diff --git a/digsby/build/cp2s3.rb b/digsby/build/cp2s3.rb new file mode 100644 index 0000000..af32a42 --- /dev/null +++ b/digsby/build/cp2s3.rb @@ -0,0 +1,173 @@ +#!/bin/env ruby + +# This file is released under the MIT license. +# Copyright (c) Famundo LLC, 2007. http://www.famundo.com +# Author: Guy Naor - http://devblog.famundo.com + +require 'optparse' + +# Parse the options +@buckets = [] +@compress = [] +@verbose = 0 +opts = OptionParser.new do |opts| + opts.banner = "Usage: cp2s3.rb [options] FILE_SPEC" + opts.separator "Copy files and directories from the local machine into Amazon's S3. Keep the directory structure intact." + opts.separator "Empty directories will be skipped." + opts.separator "" + opts.separator "FILE_SPEC List of files/directories. Accepts wildcards." + opts.separator " If given the -g option, interpret FILE_SPEC as a Ruby Dir::Glob style regular expressions." + opts.separator " With -g option, '' needed around the pattern to protect it from shell parsing." + opts.separator "" + opts.separator "Required:" + opts.on("-k", "--key ACCESS_KEY" , "Your S3 access key. You can also set the environment variable AWS_ACCESS_KEY_ID instead") { |o| @access_key = o } + opts.on("-s", "--secret SECRET_KEY" , "Your S3 secret key. You can also set the environment variable AWS_SECRET_ACCESS_KEY instead") { |o| @secret_key = o } + opts.on("-b", "--bucket BUCKET_NAME", "The S3 bucket you want the files to go into. Repeat for multiple buckets.") { |o| @buckets << o } + + opts.separator "" + opts.separator "Optional:" + + opts.on("-x", "--remote-prefix PREFIX", "A prefix to add to each file as it's uploaded") { |o| @prefix = o } + opts.on("-v", "--verbose", "Print the file names as they are being copied. Repeat for more details") { |o| @verbose += 1 } + opts.on("-p", "--public-read", "Set the copied files permission to be public readable.") { |o| @public = true } + opts.on("-c", "--compress EXT", "Compress files with given EXT before uploading (ususally css and js),", "setting the HTTP headers for delivery accordingly. Repeat for multiple extensions") { |o| @compress << ".#{o}" } + opts.on("-a", "--compress-all", "Compress all files. Using this option will use the compress option (-c, --compress) as a black list of files to compress (instead of a whitelist).") { |o| @compressall = true } + opts.on("-d", "--digest", "Save the sha1 digest of the file, to the S3 metadata. Require sha1sum to be installed") { |o| @save_hash = true } + opts.on("-t", "--time", "Save modified time of the file, to the S3 metadata") { |o| @save_time = true } + opts.on("-z", "--size", "Save size of the file, to the S3 metadata ") { |o| @save_size = true } + opts.on("-r", "--recursive", "If using file system based FILE_SPEC, recurse into sub-directories") { |o| @fs_recurse = true } + opts.on("-g", "--glob-ruby", "Interpret FILE_SPEC as a Ruby Dir::Glob. Make sure to put it in ''") { |o| @ruby_glob = true } + opts.on("-m", "--modified-only", "Only upload files that were modified must have need uploaded with the digest option.", "Will force digest, size and time modes on") { |o| @modified_only = @save_hash = @save_time = @save_size = true; } + opts.on("-y", "--dry-run", "Simulate only - do not upload any file to S3") { |o| @dry_run = true } + opts.on("-h", "--help", "Show this instructions") { |o| @help_exit = true } + opts.separator "" + opts.banner = "Copyright(c) Famundo LLC, 2007 (www.famundo.com). Released under the MIT license." +end + +@file_spec = opts.parse!(ARGV) + +@access_key ||= ENV['AWS_ACCESS_KEY_ID'] +@secret_key ||= ENV['AWS_SECRET_ACCESS_KEY'] +@prefix ||= '' + +if @help_exit || !@access_key || !@secret_key || @buckets.empty? || !@file_spec || @file_spec.empty? + puts opts.to_s + exit +end + +# Now we start working for real +require 'rubygems' +require 'aws/s3' +include AWS::S3 +require 'fileutils' +require 'stringio' +require 'zlib' + +# Log to stderr according to verbosity +def log message, for_level + puts(message) if @verbose >= for_level +end + + +# Connect to s3 +log "Connecting to S3", 3 +AWS::S3::Base.establish_connection!(:access_key_id => @access_key, :secret_access_key => @secret_key) +log "Connected!", 3 + +# Copy one file to amazon, compressing and setting metadata as needed +def copy_one_file file, fstat + compressed = nil + content_encoding = nil + log_prefix = '' + + # Store it! + options = {} + options[:access] = :public_read if @public + options["x-amz-meta-sha1_hash"] = `sha1sum #{file}`.split[0] if @save_hash + options["x-amz-meta-mtime"] = fstat.mtime.getutc.to_i if @save_time + options["x-amz-meta-size"] = fstat.size if @save_size + + sent_it = !@modified_only + @buckets.each do |b| + # Check if it was modified + if @modified_only + begin + if S3Object.find("#{@prefix}#{file}", b).metadata["x-amz-meta-sha1_hash"] == options["x-amz-meta-sha1_hash"] + # No change - go on + log("Skipping: #{file} in #{b}", 3) + next + end + rescue AWS::S3::NoSuchKey => ex + # This file isn't there yet, so we need to send it + end + end + + # We compress only if we need to compredd and we didn't compress yet + if @compressall || (!@compress.empty? && compressed.nil?) + if (@compressall && !@compress.include?(File.extname(file))) || (!@compressall && @compress.include?(File.extname(file))) + # Compress it + log "Compressing #{file}", 3 + strio = StringIO.open('', 'wb') + gz = Zlib::GzipWriter.new(strio) + gz.write(open(file, 'rb').read) + gz.close + compressed = strio.string + options["Content-Encoding"] = 'gzip' + log_prefix = '[c] ' if @verbose == 2 # Mark as compressed + elsif @verbose == 2 + log_prefix = '[-] ' # So the file names align... + end + end + + log("Sending #{file} to #{b}...", 3) + the_real_data = compressed.nil? ? open(file).read : compressed + S3Object.store("#{@prefix}#{file}", the_real_data, b, options) unless @dry_run + sent_it = true + end + log("#{log_prefix}#{file}", 1) if sent_it +end + +# Copy one file/dir from the system, recurssing if needed. Used for non-Ruby style globs +def copy_one_file_or_dir name, base_dir + return if name[0,1] == '.' + file_name = "#{base_dir}#{name}" + fstat = File.stat(file_name) + copy_one_file(file_name, fstat) if fstat.file? || fstat.symlink? + # See if we need to recurse... + if @fs_recurse && fstat.directory? + my_base = file_name + '/' + Dir.foreach(my_base) { + |e| + i = 0 + error = false + success = false + until i == 5 or success + begin + copy_one_file_or_dir(e, my_base) + rescue StandardError => error + i += 1 + log("#{e} failed #{i} times", 0) + else + success = true + end + end + raise error unless success + } + end +end + + +# Glob all the dirs for the files to upload - we expect a ruby like glob format or file system list from the command line +@file_spec.each do |spec| + if @ruby_glob + # Ruby style + Dir.glob(spec) do |file| + fstat = File.stat(file) + copy_one_file(file, fstat) if fstat.file? || fstat.symlink? + end + else + # File system style + copy_one_file_or_dir(spec, '') + end +end + diff --git a/digsby/build/gtk/build-deps.py b/digsby/build/gtk/build-deps.py new file mode 100755 index 0000000..52d4aca --- /dev/null +++ b/digsby/build/gtk/build-deps.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +#__LICENSE_GOES_HERE__ + +import sys +import os +import commands + +# command line options +update = False +clean = False +debug = False +debug_str = "" + +if "update" in sys.argv: + update = True + +if "clean" in sys.argv: + clean = True + +if "debug" in sys.argv: + debug = True + debug_str = " debug" + +WXWIDGETS_28_SVN_DIR = "http://svn.wxwidgets.org/svn/wx/wxWidgets/branches/wxWebKitBranch-2.8" +WXPYTHON_28_SVN_DIR = "http://svn.wxwidgets.org/svn/wx/wxPython/branches/WX_2_8_BRANCH" +WXWEBKIT_SVN_DIR = "http://svn.webkit.org/repository/webkit/trunk" + +# Automatically update if Digsby needs a revision of wx that is > what is currently +# in the tree. +MIN_WX_REVISION = 53385 + +scriptDir = os.path.abspath(sys.path[0]) +digsbyDir = os.path.abspath(os.path.join(scriptDir, "..", "..")) + +sys.path += [digsbyDir] + +#import config + +depsDir = os.path.join(digsbyDir, "build", "gtk", "dependencies") + +wxWidgetsDir = os.path.join(depsDir, "wxWebKitBranch-2.8") +wxPythonDir = os.path.join(wxWidgetsDir, "wxPython") +wxWebKitDir = os.path.join(depsDir, "wxWebKit") + +startDir = os.getcwd() + +def runCommand(command, exitOnError=True): + print "Running %s" % command + retval = os.system(command) + print "Command result = %d" % retval + if retval != 0: + print "Error running `%s`." % command + if exitOnError: + sys.exit(1) + + return retval == 0 + +def checkForDeps(): + retVal = os.system("which bakefile") + + if retVal != 0: + print "ERROR: You must have Bakefile (http://bakefile.org) installed to continue. Exiting..." + sys.exit(1) + + retVal = os.system("which swig") + + if retVal != 0: + print "ERROR: You must have Robin's SWIG (http://wxpython.wxcommunity.com/tools/) installed to continue. Exiting..." + sys.exit(1) + + if commands.getoutput("swig -version").find("1.3.29") == -1: + print "ERROR: Wrong SWIG. You must install Robin's SWIG (http://wxpython.wxcommunity.com/tools/). Exiting..." + sys.exit(1) + + + +checkForDeps() + +if not os.path.exists(depsDir): + os.mkdir(depsDir) + +os.chdir(depsDir) +if not os.path.exists(wxWidgetsDir): + runCommand("svn checkout %s" % WXWIDGETS_28_SVN_DIR) + +os.chdir(wxWidgetsDir) + +revision = commands.getoutput("svnversion .") +revision = min(int(r) for r in revision.rstrip('MSP').split(':')) + +if update or revision < MIN_WX_REVISION: + runCommand("svn update") + +if not os.path.exists(wxPythonDir): + runCommand("svn checkout %s wxPython" % WXPYTHON_28_SVN_DIR) + os.chdir("wxPython") + #runCommand("patch -p0 < %s" % (os.path.join(scriptDir, "patches", "wxpy_popupwin.patch"))) + #runCommand("patch -p0 < %s" % (os.path.join(scriptDir, "patches", "build-wxpython.patch"))) + +os.chdir(wxPythonDir) + +if update or revision < MIN_WX_REVISION: + runCommand("svn update") + +os.chdir(depsDir) + +if not os.path.exists(wxWebKitDir): + runCommand("svn checkout %s wxWebKit" % WXWEBKIT_SVN_DIR) + os.chdir(wxWebKitDir) + +os.chdir(wxWebKitDir) +if update: + runCommand("svn update") + + +# Now let's do the build +homeDir = os.environ["HOME"] + +if clean: + os.chdir(wxPythonDir) + runCommand("./build-wxpython.sh 25 clean") + os.chdir(os.path.join(depsDir, "wxWebKit")) + runCommand("export PATH=%s/wxpython-2.8/bin:${PATH}; WebKitTools/Scripts/build-webkit --wx --clean" % homeDir) + os.chdir(digsbyDir) + runCommand("/usr/bin/python ext/do_buildext.py clean") + + sys.exit(0) + +os.chdir(os.path.join(wxWidgetsDir, "build/bakefiles")) + +runCommand("bakefile_gen") + +os.chdir(wxWidgetsDir) + +# hack below is due to wxWidgets storing generated files in the tree. Since that tends +# to create huge diffs, they have a lot of special tricks which reduce the diffs by +# keeping certain files for certain versions of Bakefile, etc. in the tree. Since +# we don't use that Bakefile version, the hacks break our build system, and so we have to +# copy over the 'real' version of autoconf bakefile tools ourselves and then +# re-configure, making sure to touch configure.in to force the rebuild. Ick. :( + +bakefilePrefix = commands.getoutput("which bakefile").replace("bin/bakefile", "") + +runCommand("cp %sshare/aclocal/bakefile.m4 build/aclocal/" % bakefilePrefix) + +runCommand("touch configure.in; make -f build/autogen.mk") + +# end rebake hacks + +os.chdir(wxPythonDir) + +runCommand("./build-wxpython.sh 25 unicode") + +if not os.path.exists(os.path.join(homeDir, "wxpython-2.8.4", "bin", "wx-config")): + print "Error while building or installing wxWidgets." + sys.exit(1) + +os.chdir(os.path.join(depsDir, "wxWebKit")) + +runCommand("export PATH=%s/wxpython-2.8.4/bin:${PATH}; WebKitTools/Scripts/build-webkit --wx wxgc wxpython" % homeDir) + +os.chdir(digsbyDir) + +runCommand("/usr/bin/python ext/do_buildext.py WX_CONFIG=%s/wxpython-2.8.4/bin/wx-config" % homeDir) + +pythonPath="%s/wxpython-2.8.4/wxPython/wx-2.8-gtk-unicode:%s/dependencies/wxWebKit/WebKitBuild/Release" % (homeDir, scriptDir) + +print "=========== BUILD COMPLETE ===========" +print "" + +print "To run Digsby:" +print "- Set your PYTHONPATH to %s" % pythonPath +print "- Apply %s/digsbymac.patch (with unfinished Mac changes) if you haven't already." % os.path.join(scriptDir, "patches") +print "- Run Digsby.py" + diff --git a/digsby/build/gtk/fedora9-install-deps b/digsby/build/gtk/fedora9-install-deps new file mode 100755 index 0000000..b012703 --- /dev/null +++ b/digsby/build/gtk/fedora9-install-deps @@ -0,0 +1,8 @@ +#!/bin/bash + +sudo yum -y groupinstall 'Development Tools' +sudo yum -y install python-devel gtk2-devel mesa-libGL-devel mesalibGLU-devel +sudo yum -y install syck-python libxml2-python m2crypto python-imaging python-protocols +sudo yum -y install libtiff-devel libjpeg-devel libpng-devel giflib-devel +sudo yum -y install libgnomeprint22-devel libgnomeprintui22-devel libcurl-devel +sudo yum -y install flex bison gperf libicu-devel libxml2-devel libxslt-devel libsqlite-devel diff --git a/digsby/build/gtk/loggedindiff.patch b/digsby/build/gtk/loggedindiff.patch new file mode 100644 index 0000000..1bddd94 --- /dev/null +++ b/digsby/build/gtk/loggedindiff.patch @@ -0,0 +1,37 @@ +=================================================================== +--- src/gui/buddylist/buddylistframe.py (revision 16930) ++++ src/gui/buddylist/buddylistframe.py (working copy) +@@ -264,7 +264,7 @@ + 'socialpanel' + ) + +- self.clist = ConnectionsPanel(self) ++ #self.clist = ConnectionsPanel(self) + + + self.infobox.Befriend(self.blist) +@@ -277,12 +277,13 @@ + + @bind('BuddyList.Accounts.ToggleShow') + def ToggleConnPanel(self): ++ return + self.clist.ToggleState() + + def gui_layout(self, layoutNow = True): +- elems = ['status', 'blist','clist', 'slist', 'elist'] ++ elems = ['status', 'blist', 'slist', 'elist'] + +- panel_order = pref('buddylist.order', elems) ++ panel_order = elems #pref('buddylist.order', elems) + email_view = pref('buddylist.show_email_as', 'panel') in ('panel', 'both') and len(self.elist.active) + social_view = pref('buddylist.show_social_as', 'panel') in ('panel', 'both') and len(self.slist.active) + status_view = pref('buddylist.show_status', True) +@@ -305,7 +306,7 @@ + viewable.status= (self.status, 0, EXPAND) + + viewable.blist = (self.blist, 1, EXPAND) +- viewable.clist = (self.clist, 0, EXPAND) ++ #viewable.clist = (self.clist, 0, EXPAND) + + if email_view: + viewable.elist = (self.elist, 0, EXPAND) diff --git a/digsby/build/gtk/make_bakefile b/digsby/build/gtk/make_bakefile new file mode 100755 index 0000000..7d9f28c --- /dev/null +++ b/digsby/build/gtk/make_bakefile @@ -0,0 +1,10 @@ +#!/bin/bash + +wget http://voxel.dl.sourceforge.net/sourceforge/bakefile/bakefile-0.2.5.tar.gz +tar -xzvf bakefile-0.2.5.tar.gz +cd bakefile-0.2.5/ +./configure +make +sudo make install +cd .. +rm -rf bakefile-0.2.5 diff --git a/digsby/build/gtk/make_swig b/digsby/build/gtk/make_swig new file mode 100755 index 0000000..6ee5e43 --- /dev/null +++ b/digsby/build/gtk/make_swig @@ -0,0 +1,10 @@ +#!/bin/bash + +wget http://wxpython.wxcommunity.com/tools/SWIG-1.3.29-wx.tar.gz +tar -xzvf SWIG-1.3.29-wx.tar.gz +cd SWIG-1.3.29/ +./configure +make +sudo make install +cd .. +rm -rf SWIG-1.3.29 diff --git a/digsby/build/gtk/ubuntu-install-deps b/digsby/build/gtk/ubuntu-install-deps new file mode 100755 index 0000000..35a6205 --- /dev/null +++ b/digsby/build/gtk/ubuntu-install-deps @@ -0,0 +1,14 @@ +#!/bin/bash + +#bootstrap +sudo apt-get -y install subversion git +sudo apt-get -y install build-essential automake libgtk2.0-dev libgl1-mesa-dev libglu1-mesa-dev +sudo apt-get -y install flex bison gperf +#libraries +sudo apt-get -y install libtiff-dev libjpeg-dev libpng-dev libgif-dev +sudo apt-get -y install libgnomeprint2.2-dev libgnomeprintui2.2-dev +sudo apt-get -y install libicu-dev +sudo apt-get -y install libcurl4-openssl-dev libxml2-dev libxslt1-dev libsqlite3-dev +#python libraries +sudo apt-get -y install python-dev +sudo apt-get -y install python-syck python-libxml2 python-m2crypto python-imaging python-profiler python-protocols diff --git a/digsby/build/langtools.py b/digsby/build/langtools.py new file mode 100644 index 0000000..00d6882 --- /dev/null +++ b/digsby/build/langtools.py @@ -0,0 +1,25 @@ +#__LICENSE_GOES_HERE__ +from buildutil import dpy, cd, tardep +import os.path + +DIGSBY_ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) +assert os.path.isdir(DIGSBY_ROOT) + +i18bin = tardep('http://mini/deps/i18n/win/', 'i18nbin', '.zip', 1037011) + +def download_i18n_tools(): + with cd(DIGSBY_ROOT, 'build', 'msw'): + d = i18bin.get() + return os.path.abspath(d) + +def make_po_files(): + toolsdir = download_i18n_tools() + path = os.pathsep.join((toolsdir, os.environ.get('PATH', ''))) + + with cd(DIGSBY_ROOT): + dpy(['mki18n.py', '-m', '-v', '--domain=Digsby', DIGSBY_ROOT], + addenv=dict(PATH=path)) + +if __name__ == '__main__': + make_po_files() + diff --git a/digsby/build/m2crypto-ssl-wanterrors-zero-return.diff b/digsby/build/m2crypto-ssl-wanterrors-zero-return.diff new file mode 100644 index 0000000..b86ad00 --- /dev/null +++ b/digsby/build/m2crypto-ssl-wanterrors-zero-return.diff @@ -0,0 +1,266 @@ +Index: M2Crypto/m2urllib.py +=================================================================== +--- M2Crypto/m2urllib.py (revision 719) ++++ M2Crypto/m2urllib.py (working copy) +@@ -65,6 +65,6 @@ + # Stop again. + + # Minor brain surgery. +-URLopener.open_https = open_https ++# URLopener.open_https = open_https + + +Index: setup.py +=================================================================== +--- setup.py (revision 719) ++++ setup.py (working copy) +@@ -40,6 +40,9 @@ + self.libraries = ['ssl', 'crypto'] + self.openssl = '/usr' + ++ sdkdir = '/Developer/SDKs/MacOSX10.4u.sdk' ++ if sys.platform.startswith('darwin') and os.path.exists(sdkdir): ++ self.openssl = sdkdir + '/usr' + + def finalize_options(self): + '''Overloaded build_ext implementation to append custom openssl +@@ -124,10 +127,20 @@ + + build_ext.build_ext.swig_sources = swig_sources + ++compile_args =['-DTHREADING'] ++link_args = [] ++libs = [] ++ ++if os.name == 'nt': ++ compile_args += ['/GL', '/Zi', '/GS-', '/Os'] ++ link_args += ['/GL', '/Zi', '/GS-', '/Os'] ++ libs += ['User32', 'Advapi32', 'Gdi32', 'Ws2_32'] + + m2crypto = Extension(name = 'M2Crypto.__m2crypto', + sources = ['SWIG/_m2crypto.i'], +- extra_compile_args = ['-DTHREADING'], ++ extra_compile_args = compile_args, ++ extra_link_args = link_args, ++ libraries=libs, + #extra_link_args = ['-Wl,-search_paths_first'], # Uncomment to build Universal Mac binaries + ) + +Index: SWIG/_ssl.i +=================================================================== +--- SWIG/_ssl.i (revision 719) ++++ SWIG/_ssl.i (working copy) +@@ -391,28 +391,38 @@ + return ret; + } + ++void SetWantRWError(int which) { ++ PyObject *o = Py_BuildValue("(is)", which, ERR_reason_error_string(ERR_get_error())); ++ ++ if (o != NULL) { ++ PyErr_SetObject(_ssl_err, o); ++ Py_DECREF(o); ++ } ++} ++ + PyObject *ssl_accept(SSL *ssl) { + PyObject *obj = NULL; +- int r, err; ++ int r, err, which; + + Py_BEGIN_ALLOW_THREADS + r = SSL_accept(ssl); + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: +- case SSL_ERROR_ZERO_RETURN: + obj = PyInt_FromLong((long)1); + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: +- obj = PyInt_FromLong((long)0); ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + obj = NULL; + break; ++ case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + err = ERR_get_error(); + if (err) +@@ -431,26 +441,27 @@ + + PyObject *ssl_connect(SSL *ssl) { + PyObject *obj = NULL; +- int r, err; ++ int r, err, which; + + Py_BEGIN_ALLOW_THREADS + r = SSL_connect(ssl); + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: +- case SSL_ERROR_ZERO_RETURN: + obj = PyInt_FromLong((long)1); + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: +- obj = PyInt_FromLong((long)0); ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + obj = NULL; + break; ++ case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + err = ERR_get_error(); + if (err) +@@ -474,7 +485,7 @@ + PyObject *ssl_read(SSL *ssl, int num) { + PyObject *obj = NULL; + void *buf; +- int r, err; ++ int r, err, which; + + if (!(buf = PyMem_Malloc(num))) { + PyErr_SetString(PyExc_MemoryError, "ssl_read"); +@@ -487,22 +498,22 @@ + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: +- case SSL_ERROR_ZERO_RETURN: + buf = PyMem_Realloc(buf, r); + obj = PyString_FromStringAndSize(buf, r); + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_X509_LOOKUP: +- Py_INCREF(Py_None); +- obj = Py_None; ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + obj = NULL; + break; ++ case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + err = ERR_get_error(); + if (err) +@@ -523,7 +534,7 @@ + PyObject *ssl_read_nbio(SSL *ssl, int num) { + PyObject *obj = NULL; + void *buf; +- int r, err; ++ int r, err, which; + + + if (!(buf = PyMem_Malloc(num))) { +@@ -537,22 +548,22 @@ + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: +- case SSL_ERROR_ZERO_RETURN: + buf = PyMem_Realloc(buf, r); + obj = PyString_FromStringAndSize(buf, r); + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_X509_LOOKUP: +- Py_INCREF(Py_None); +- obj = Py_None; ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + obj = NULL; + break; ++ case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + err = ERR_get_error(); + if (err) +@@ -572,7 +583,7 @@ + + int ssl_write(SSL *ssl, PyObject *blob) { + const void *buf; +- int len, r, err, ret; ++ int len, r, err, ret, which; + + + if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) { +@@ -585,13 +596,14 @@ + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: +- case SSL_ERROR_ZERO_RETURN: + ret = r; + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: ++ SetWantRWError(which); ++ return -1; + case SSL_ERROR_WANT_X509_LOOKUP: + ret = -1; + break; +@@ -599,6 +611,7 @@ + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + ret = -1; + break; ++ case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + err = ERR_get_error(); + if (err) +@@ -617,7 +630,7 @@ + + int ssl_write_nbio(SSL *ssl, PyObject *blob) { + const void *buf; +- int len, r, err, ret; ++ int len, r, err, ret, which; + + + if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) { +@@ -630,19 +643,21 @@ + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: +- case SSL_ERROR_ZERO_RETURN: + ret = r; + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: ++ SetWantRWError(which); ++ return -1; + case SSL_ERROR_WANT_X509_LOOKUP: + ret = -1; + break; + case SSL_ERROR_SSL: + ret = -1; + break; ++ case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + err = ERR_get_error(); + if (err) diff --git a/digsby/build/m2crypto-ssl-wanterrors.diff b/digsby/build/m2crypto-ssl-wanterrors.diff new file mode 100644 index 0000000..1ed2cb4 --- /dev/null +++ b/digsby/build/m2crypto-ssl-wanterrors.diff @@ -0,0 +1,277 @@ +Index: SWIG/_ssl.i +=================================================================== +--- SWIG/_ssl.i (revision 609) ++++ SWIG/_ssl.i (working copy) +@@ -215,7 +215,7 @@ + + int ssl_ctx_use_cert(SSL_CTX *ctx, char *file) { + int i; +- ++ + if (!(i = SSL_CTX_use_certificate_file(ctx, file, SSL_FILETYPE_PEM))) { + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + return -1; +@@ -236,7 +236,7 @@ + + int ssl_ctx_use_privkey(SSL_CTX *ctx, char *file) { + int i; +- ++ + if (!(i = SSL_CTX_use_PrivateKey_file(ctx, file, SSL_FILETYPE_PEM))) { + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + return -1; +@@ -246,7 +246,7 @@ + + int ssl_ctx_check_privkey(SSL_CTX *ctx) { + int ret; +- ++ + if (!(ret = SSL_CTX_check_private_key(ctx))) { + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + return -1; +@@ -350,7 +350,7 @@ + + int ssl_set_fd(SSL *ssl, int fd) { + int ret; +- ++ + if (!(ret = SSL_set_fd(ssl, fd))) { + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); + return -1; +@@ -358,23 +358,33 @@ + return ret; + } + ++void SetWantRWError(int which) { ++ PyObject *o = Py_BuildValue("(is)", which, ERR_reason_error_string(ERR_get_error())); ++ ++ if (o != NULL) { ++ PyErr_SetObject(_ssl_err, o); ++ Py_DECREF(o); ++ } ++} ++ + PyObject *ssl_accept(SSL *ssl) { + PyObject *obj = NULL; +- int r, err; ++ int r, err, which; + + Py_BEGIN_ALLOW_THREADS + r = SSL_accept(ssl); + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + obj = PyInt_FromLong((long)1); + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: +- obj = PyInt_FromLong((long)0); ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); +@@ -398,21 +408,22 @@ + + PyObject *ssl_connect(SSL *ssl) { + PyObject *obj = NULL; +- int r, err; ++ int r, err, which; + + Py_BEGIN_ALLOW_THREADS + r = SSL_connect(ssl); + Py_END_ALLOW_THREADS + +- +- switch (SSL_get_error(ssl, r)) { ++ ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + obj = PyInt_FromLong((long)1); + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: +- obj = PyInt_FromLong((long)0); ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); +@@ -429,8 +440,8 @@ + obj = NULL; + break; + } +- +- ++ ++ + return obj; + } + +@@ -441,7 +452,7 @@ + PyObject *ssl_read(SSL *ssl, int num) { + PyObject *obj = NULL; + void *buf; +- int r, err; ++ int r, err, which; + + if (!(buf = PyMem_Malloc(num))) { + PyErr_SetString(PyExc_MemoryError, "ssl_read"); +@@ -454,7 +465,7 @@ + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + buf = PyMem_Realloc(buf, r); +@@ -463,8 +474,8 @@ + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_X509_LOOKUP: +- Py_INCREF(Py_None); +- obj = Py_None; ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); +@@ -490,21 +501,19 @@ + PyObject *ssl_read_nbio(SSL *ssl, int num) { + PyObject *obj = NULL; + void *buf; +- int r, err; +- ++ int r, err, which; + + if (!(buf = PyMem_Malloc(num))) { + PyErr_SetString(PyExc_MemoryError, "ssl_read"); + return NULL; + } +- +- ++ + Py_BEGIN_ALLOW_THREADS + r = SSL_read(ssl, buf, num); + Py_END_ALLOW_THREADS +- +- +- switch (SSL_get_error(ssl, r)) { ++ ++ ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + buf = PyMem_Realloc(buf, r); +@@ -513,8 +522,8 @@ + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_X509_LOOKUP: +- Py_INCREF(Py_None); +- obj = Py_None; ++ SetWantRWError(which); ++ obj = NULL; + break; + case SSL_ERROR_SSL: + PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error())); +@@ -532,33 +541,35 @@ + break; + } + PyMem_Free(buf); +- +- ++ ++ + return obj; + } + + int ssl_write(SSL *ssl, PyObject *blob) { + const void *buf; +- int len, r, err, ret; ++ int len, r, err, ret, which; + + + if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) { + return -1; + } + +- ++ + Py_BEGIN_ALLOW_THREADS + r = SSL_write(ssl, buf, len); + Py_END_ALLOW_THREADS + + +- switch (SSL_get_error(ssl, r)) { ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + ret = r; + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: ++ SetWantRWError(which); ++ return -1; + case SSL_ERROR_WANT_X509_LOOKUP: + ret = -1; + break; +@@ -577,33 +588,35 @@ + default: + ret = -1; + } +- +- ++ ++ + return ret; + } + + int ssl_write_nbio(SSL *ssl, PyObject *blob) { + const void *buf; +- int len, r, err, ret; ++ int len, r, err, ret, which; + + + if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) { + return -1; + } + +- ++ + Py_BEGIN_ALLOW_THREADS + r = SSL_write(ssl, buf, len); + Py_END_ALLOW_THREADS +- +- +- switch (SSL_get_error(ssl, r)) { ++ ++ ++ switch ((which = SSL_get_error(ssl, r))) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + ret = r; + break; + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: ++ SetWantRWError(which); ++ return -1; + case SSL_ERROR_WANT_X509_LOOKUP: + ret = -1; + break; +@@ -621,8 +634,8 @@ + default: + ret = -1; + } +- +- ++ ++ + return ret; + } + diff --git a/digsby/build/mac/build-appbundle.py b/digsby/build/mac/build-appbundle.py new file mode 100644 index 0000000..3fd8fd6 --- /dev/null +++ b/digsby/build/mac/build-appbundle.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +#__LICENSE_GOES_HERE__ +""" +To run, use: + +build-appbundle.py py2app --no-strip (we probably shouldn't strip until we have it working everywhere) +""" + +from distutils.core import setup +import py2app +import glob +import os +import shutil +import sys +import py_compile + +myplist = dict( + CFBundleIdentifier='com.dotsintax.digsby', + CFBundleDisplayName="Digsby", + CFBundleVersion="0.1" # FIXME: Get the version from Digsby + ) + +rootdir = os.path.abspath(os.path.join(sys.path[0], "../..")) + +distDir = os.path.join(rootdir, "dist") +bundleRoot = os.path.join(distDir, "Digsby.app") + +# building a new bundle over an old one sometimes causes weird problems +if os.path.exists(distDir): + shutil.rmtree(distDir) + +os.system("rm -rf %s" % os.path.join(rootdir, "build", "bdist.macosx*")) + +sys.path += [rootdir] + +from config import * + +sys.path += [rootdir + '/src', rootdir + '/ext/' + platformName, rootdir + '/lib', rootdir + '/platlib/' + platformName, rootdir + '/thirdparty'] + +import AutoUpdate.AutoUpdate as update + +py2app_options = dict( + iconfile=os.path.join(rootdir, "res/digsby.icns"), + argv_emulation=True, + plist=myplist, + #optimize=1, + # these modules need included because py2app doesn't find dynamically loaded + # modules + includes = [ + "AppKit", + "Authorization", + "common.AchievementMixin", + "common.asynchttp", + "common.asynchttp.server", + "common.oauth_util", + "decimal", + "email.iterators", + "email.generator", + "feedparser", + "Foundation", + "gtalk", + "gtalk.gtalk", + "gtalk.gtalkStream", + "gui.autocomplete", + "gui.browser.webkit.imageloader", + "gui.native.mac", + "gui.native.mac.macdocking", + "gui.native.mac.maceffects", + "gui.native.mac.macextensions", + "gui.native.mac.machelpers", + "gui.native.mac.macpaths", + "gui.native.mac.macsysinfo", + "gui.native.mac.toplevel", + "gui.pref.*", + "jabber", + "jabber.objects.gmail", + "lxml._elementpath", + "lxml.objectify", + "mail.*", + "mail.hotmail", + "msn", + "msn.p8", + "msn.p9", + "msn.p10", + "msn.p11", + "msn.p12", + "msn.p13", + "msn.p14", + "msn.p15", + "msn.SOAP", + "oauth", + "oauth.oauth", + "objc", + "oscar", + "svnrev", + "util.hook_util", + "util.httptools", + "yahoo", + + ], +) + +def get_svn_rev(): + appbundle_dir, __ = os.path.split(__file__) + f = open(os.path.join(appbundle_dir, '.svn', 'entries')) + rev = 'XXXX' + seen_dir = False + for line in f: + if not seen_dir: + if 'dir' in line: + seen_dir = True + else: + continue + else: + try: + rev = int(line) + except Exception: + continue + else: + break + + f.close() + return rev + +f = open('svnrev.py', 'w') +f.write('REVISION = %d' % get_svn_rev()) +f.close() + +py_compile.compile("lib/AutoUpdate/mac_updater.py") + +setup( + name="Digsby", + install_requires=["pyobjc"], + app=[os.path.join(rootdir,'Digsby.py')], + data_files = [ + ('', ['res']), + ('lib/python2.6/site-packages', [os.path.join(rootdir, 'src/plugins')] + + [os.path.join(rootdir, "lib/AutoUpdate/mac_updater.pyc")] + ), + ], + options=dict(py2app=py2app_options) +) + +# Now we get to undo a lot of what py2app did... +resourceDir = os.path.join(bundleRoot, "Contents", "Resources") +sitePackageDir = os.path.join(resourceDir, "lib", "python2.6") + +os.chdir(sitePackageDir) +os.system("unzip -d site-packages site-packages.zip") + +os.remove("site-packages.zip") + +# excludes do not seem to work as well as includes do :( +if os.path.exists("site-packages/devmode.pyc"): + os.remove("site-packages/devmode.pyc") + +# Something is pulling in Tk and Tcl. I tried setting tkinter in the exclude modules, +# but it didn't make a difference. (well, I don't think tkinter itself is included, +# just the C++ libs. So we will remove Tk and Tcl ourselves... + +frameworksDir = os.path.join(bundleRoot, "Contents", "Frameworks") +tclDir = os.path.join(frameworksDir, "Tcl.framework") +if os.path.exists(tclDir): + shutil.rmtree(tclDir) + +tkDir = os.path.join(frameworksDir, "Tk.framework") +if os.path.exists(tkDir): + shutil.rmtree(tkDir) + +# The wx libs get messed up, so we have to fix them manually... +os.chdir(frameworksDir) +files = glob.glob("libwx*2.9.dylib") + +for afile in files: + base = afile.replace("2.9.dylib", "") + libs = glob.glob(base + "*") + if len(libs) == 2: + os.remove(afile) + reallib = glob.glob(base + "*")[0] + os.symlink(reallib, afile) + + +# clean up files we don't want in the package +ignorePatterns = [".svn", ".git"] +for ignore in ignorePatterns: + os.system("find %s -name %s -exec rm -rf {} \;" % (os.path.join(rootdir, "dist", "Digsby.app"), ignore)) + +for root, subFolders, files in os.walk(os.path.join(rootdir, "dist")): + for file in files: + add = True + fullpath = os.path.join(root, file) + if file.startswith("."): + print "removing %s" % fullpath + os.remove(fullpath) + elif root.find("plugins") != -1 and file.endswith(".py"): + py_compile.compile(fullpath) + os.remove(fullpath) + + +manifest = update.generate_manifest(os.path.join(rootdir, "dist", "Digsby.app")) +manifestFile = os.path.join(rootdir, "dist", "manifest.mac") +f = open(manifestFile, "w") +f.write(manifest.encode('utf8')) +f.close() + +shutil.copyfile(manifestFile, os.path.join(resourceDir, "manifest.mac")) + diff --git a/digsby/build/msw/DigsbyPython25.diff b/digsby/build/msw/DigsbyPython25.diff new file mode 100644 index 0000000..b334f62 --- /dev/null +++ b/digsby/build/msw/DigsbyPython25.diff @@ -0,0 +1,799 @@ +Index: Modules/socketmodule.c +=================================================================== +--- Modules/socketmodule.c (revision 66572) ++++ Modules/socketmodule.c (working copy) +@@ -296,10 +296,14 @@ + # include "addrinfo.h" + #endif + ++#include ++ + #ifndef HAVE_INET_PTON ++#if !defined(NTDDI_VERSION) || (NTDDI_VERSION < NTDDI_LONGHORN) + int inet_pton(int af, const char *src, void *dst); + const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); + #endif ++#endif + + #ifdef __APPLE__ + /* On OS X, getaddrinfo returns no error indication of lookup +@@ -5059,6 +5063,7 @@ + + + #ifndef HAVE_INET_PTON ++#if !defined(NTDDI_VERSION) || (NTDDI_VERSION < NTDDI_LONGHORN) + + /* Simplistic emulation code for inet_pton that only works for IPv4 */ + /* These are not exposed because they do not set errno properly */ +@@ -5094,3 +5099,4 @@ + } + + #endif ++#endif +\ No newline at end of file +Index: Objects/fileobject.c +=================================================================== +--- Objects/fileobject.c (revision 66572) ++++ Objects/fileobject.c (working copy) +@@ -48,6 +48,30 @@ + #define NEWLINE_LF 2 /* \n newline seen */ + #define NEWLINE_CRLF 4 /* \r\n newline seen */ + ++/* ++ * These macros release the GIL while preventing the f_close() function being ++ * called in the interval between them. For that purpose, a running total of ++ * the number of currently running unlocked code sections is kept in ++ * the unlocked_count field of the PyFileObject. The close() method raises ++ * an IOError if that field is non-zero. See issue #815646, #595601. ++ */ ++ ++#define FILE_BEGIN_ALLOW_THREADS(fobj) \ ++{ \ ++ fobj->unlocked_count++; \ ++ Py_BEGIN_ALLOW_THREADS ++ ++#define FILE_END_ALLOW_THREADS(fobj) \ ++ Py_END_ALLOW_THREADS \ ++ fobj->unlocked_count--; \ ++ assert(fobj->unlocked_count >= 0); \ ++} ++ ++#define FILE_ABORT_ALLOW_THREADS(fobj) \ ++ Py_BLOCK_THREADS \ ++ fobj->unlocked_count--; \ ++ assert(fobj->unlocked_count >= 0); ++ + #ifdef __cplusplus + extern "C" { + #endif +@@ -61,6 +85,17 @@ + return ((PyFileObject *)f)->f_fp; + } + ++void PyFile_IncUseCount(PyFileObject *fobj) ++{ ++ fobj->unlocked_count++; ++} ++ ++void PyFile_DecUseCount(PyFileObject *fobj) ++{ ++ fobj->unlocked_count--; ++ assert(fobj->unlocked_count >= 0); ++} ++ + PyObject * + PyFile_Name(PyObject *f) + { +@@ -70,6 +105,19 @@ + return ((PyFileObject *)f)->f_name; + } + ++/* This is a safe wrapper around PyObject_Print to print to the FILE ++ of a PyFileObject. PyObject_Print releases the GIL but knows nothing ++ about PyFileObject. */ ++static int ++file_PyObject_Print(PyObject *op, PyFileObject *f, int flags) ++{ ++ int result; ++ PyFile_IncUseCount(f); ++ result = PyObject_Print(op, f->f_fp, flags); ++ PyFile_DecUseCount(f); ++ return result; ++} ++ + /* On Unix, fopen will succeed for directories. + In Python, there should be no file objects referring to + directories, so we need a check. */ +@@ -224,20 +272,20 @@ + PyObject *wmode; + wmode = PyUnicode_DecodeASCII(newmode, strlen(newmode), NULL); + if (f->f_name && wmode) { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + /* PyUnicode_AS_UNICODE OK without thread + lock as it is a simple dereference. */ + f->f_fp = _wfopen(PyUnicode_AS_UNICODE(f->f_name), + PyUnicode_AS_UNICODE(wmode)); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + } + Py_XDECREF(wmode); + } + #endif + if (NULL == f->f_fp && NULL != name) { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + f->f_fp = fopen(name, newmode); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + } + + if (f->f_fp == NULL) { +@@ -259,9 +307,8 @@ + /* EINVAL is returned when an invalid filename or + * an invalid mode is supplied. */ + if (errno == EINVAL) +- PyErr_Format(PyExc_IOError, +- "invalid filename: %s or mode: %s", +- name, mode); ++ PyErr_Format(PyExc_IOError, "invalid filename: %s or mode: %s", ++ (name ? name : ""), mode); + else + PyErr_SetFromErrnoWithFilenameObject(PyExc_IOError, f->f_name); + f = NULL; +@@ -275,6 +322,48 @@ + return (PyObject *)f; + } + ++static PyObject * ++close_the_file(PyFileObject *f) ++{ ++ int sts = 0; ++ int (*local_close)(FILE *); ++ FILE *local_fp = f->f_fp; ++ if (local_fp != NULL) { ++ local_close = f->f_close; ++ if (local_close != NULL && f->unlocked_count > 0) { ++ if (f->ob_refcnt > 0) { ++ PyErr_SetString(PyExc_IOError, ++ "close() called during concurrent " ++ "operation on the same file object."); ++ } else { ++ /* This should not happen unless someone is ++ * carelessly playing with the PyFileObject ++ * struct fields and/or its associated FILE ++ * pointer. */ ++ PyErr_SetString(PyExc_SystemError, ++ "PyFileObject locking error in " ++ "destructor (refcnt <= 0 at close)."); ++ } ++ return NULL; ++ } ++ /* NULL out the FILE pointer before releasing the GIL, because ++ * it will not be valid anymore after the close() function is ++ * called. */ ++ f->f_fp = NULL; ++ if (local_close != NULL) { ++ Py_BEGIN_ALLOW_THREADS ++ errno = 0; ++ sts = (*local_close)(local_fp); ++ Py_END_ALLOW_THREADS ++ if (sts == EOF) ++ return PyErr_SetFromErrno(PyExc_IOError); ++ if (sts != 0) ++ return PyInt_FromLong((long)sts); ++ } ++ } ++ Py_RETURN_NONE; ++} ++ + PyObject * + PyFile_FromFile(FILE *fp, char *name, char *mode, int (*close)(FILE *)) + { +@@ -390,19 +479,16 @@ + static void + file_dealloc(PyFileObject *f) + { +- int sts = 0; ++ PyObject *ret; + if (f->weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *) f); +- if (f->f_fp != NULL && f->f_close != NULL) { +- Py_BEGIN_ALLOW_THREADS +- sts = (*f->f_close)(f->f_fp); +- Py_END_ALLOW_THREADS +- if (sts == EOF) +-#ifdef HAVE_STRERROR +- PySys_WriteStderr("close failed: [Errno %d] %s\n", errno, strerror(errno)); +-#else +- PySys_WriteStderr("close failed: [Errno %d]\n", errno); +-#endif ++ ret = close_the_file(f); ++ if (!ret) { ++ PySys_WriteStderr("close failed in file object destructor:\n"); ++ PyErr_Print(); ++ } ++ else { ++ Py_DECREF(ret); + } + PyMem_Free(f->f_setbuf); + Py_XDECREF(f->f_name); +@@ -440,24 +526,10 @@ + static PyObject * + file_close(PyFileObject *f) + { +- int sts = 0; +- if (f->f_fp != NULL) { +- if (f->f_close != NULL) { +- Py_BEGIN_ALLOW_THREADS +- errno = 0; +- sts = (*f->f_close)(f->f_fp); +- Py_END_ALLOW_THREADS +- } +- f->f_fp = NULL; +- } ++ PyObject *sts = close_the_file(f); + PyMem_Free(f->f_setbuf); + f->f_setbuf = NULL; +- if (sts == EOF) +- return PyErr_SetFromErrno(PyExc_IOError); +- if (sts != 0) +- return PyInt_FromLong((long)sts); +- Py_INCREF(Py_None); +- return Py_None; ++ return sts; + } + + +@@ -561,10 +633,10 @@ + if (PyErr_Occurred()) + return NULL; + +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + ret = _portable_fseek(f->f_fp, offset, whence); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + + if (ret != 0) { + PyErr_SetFromErrno(PyExc_IOError); +@@ -598,10 +670,10 @@ + * then at least on Windows). The easiest thing is to capture + * current pos now and seek back to it at the end. + */ +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + initialpos = _portable_ftell(f->f_fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (initialpos == -1) + goto onioerror; + +@@ -626,10 +698,10 @@ + * I/O, and a flush may be necessary to synch both platform views + * of the current file state. + */ +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + ret = fflush(f->f_fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (ret != 0) + goto onioerror; + +@@ -640,15 +712,15 @@ + HANDLE hFile; + + /* Have to move current pos to desired endpoint on Windows. */ +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + ret = _portable_fseek(f->f_fp, newsize, SEEK_SET) != 0; +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (ret) + goto onioerror; + + /* Truncate. Note that this may grow the file! */ +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + hFile = (HANDLE)_get_osfhandle(fileno(f->f_fp)); + ret = hFile == (HANDLE)-1; +@@ -657,24 +729,24 @@ + if (ret) + errno = EACCES; + } +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (ret) + goto onioerror; + } + #else +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + ret = ftruncate(fileno(f->f_fp), newsize); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (ret != 0) + goto onioerror; + #endif /* !MS_WINDOWS */ + + /* Restore original file position. */ +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + ret = _portable_fseek(f->f_fp, initialpos, SEEK_SET) != 0; +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (ret) + goto onioerror; + +@@ -695,10 +767,11 @@ + + if (f->f_fp == NULL) + return err_closed(); +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + pos = _portable_ftell(f->f_fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) ++ + if (pos == -1) { + PyErr_SetFromErrno(PyExc_IOError); + clearerr(f->f_fp); +@@ -734,10 +807,10 @@ + + if (f->f_fp == NULL) + return err_closed(); +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + res = fflush(f->f_fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (res != 0) { + PyErr_SetFromErrno(PyExc_IOError); + clearerr(f->f_fp); +@@ -753,9 +826,9 @@ + long res; + if (f->f_fp == NULL) + return err_closed(); +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + res = isatty((int)fileno(f->f_fp)); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + return PyBool_FromLong(res); + } + +@@ -855,11 +928,11 @@ + return NULL; + bytesread = 0; + for (;;) { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + chunksize = Py_UniversalNewlineFread(BUF(v) + bytesread, + buffersize - bytesread, f->f_fp, (PyObject *)f); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (chunksize == 0) { + if (!ferror(f->f_fp)) + break; +@@ -911,11 +984,11 @@ + return NULL; + ndone = 0; + while (ntodo > 0) { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + nnow = Py_UniversalNewlineFread(ptr+ndone, ntodo, f->f_fp, + (PyObject *)f); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (nnow == 0) { + if (!ferror(f->f_fp)) + break; +@@ -980,7 +1053,7 @@ + + #ifdef USE_FGETS_IN_GETLINE + static PyObject* +-getline_via_fgets(FILE *fp) ++getline_via_fgets(PyFileObject *f, FILE *fp) + { + /* INITBUFSIZE is the maximum line length that lets us get away with the fast + * no-realloc, one-fgets()-call path. Boosting it isn't free, because we have +@@ -1013,13 +1086,13 @@ + total_v_size = INITBUFSIZE; /* start small and pray */ + pvfree = buf; + for (;;) { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + pvend = buf + total_v_size; + nfree = pvend - pvfree; + memset(pvfree, '\n', nfree); + assert(nfree < INT_MAX); /* Should be atmost MAXBUFSIZE */ + p = fgets(pvfree, (int)nfree, fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + + if (p == NULL) { + clearerr(fp); +@@ -1088,13 +1161,13 @@ + * the code above for detailed comments about the logic. + */ + for (;;) { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + pvend = BUF(v) + total_v_size; + nfree = pvend - pvfree; + memset(pvfree, '\n', nfree); + assert(nfree < INT_MAX); + p = fgets(pvfree, (int)nfree, fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + + if (p == NULL) { + clearerr(fp); +@@ -1165,7 +1238,7 @@ + + #if defined(USE_FGETS_IN_GETLINE) + if (n <= 0 && !univ_newline ) +- return getline_via_fgets(fp); ++ return getline_via_fgets(f, fp); + #endif + total_v_size = n > 0 ? n : 100; + v = PyString_FromStringAndSize((char *)NULL, total_v_size); +@@ -1175,7 +1248,7 @@ + end = buf + total_v_size; + + for (;;) { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + FLOCKFILE(fp); + if (univ_newline) { + c = 'x'; /* Shut up gcc warning */ +@@ -1210,7 +1283,7 @@ + buf != end) + ; + FUNLOCKFILE(fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + f->f_newlinetypes = newlinetypes; + f->f_skipnextlf = skipnextlf; + if (c == '\n') +@@ -1375,7 +1448,7 @@ + file_readlines(PyFileObject *f, PyObject *args) + { + long sizehint = 0; +- PyObject *list; ++ PyObject *list = NULL; + PyObject *line; + char small_buffer[SMALLCHUNK]; + char *buffer = small_buffer; +@@ -1403,11 +1476,11 @@ + if (shortread) + nread = 0; + else { +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + nread = Py_UniversalNewlineFread(buffer+nfilled, + buffersize-nfilled, f->f_fp, (PyObject *)f); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + shortread = (nread < buffersize-nfilled); + } + if (nread == 0) { +@@ -1416,10 +1489,7 @@ + break; + PyErr_SetFromErrno(PyExc_IOError); + clearerr(f->f_fp); +- error: +- Py_DECREF(list); +- list = NULL; +- goto cleanup; ++ goto error; + } + totalread += nread; + p = (char *)memchr(buffer+nfilled, '\n', nread); +@@ -1493,9 +1563,14 @@ + if (err != 0) + goto error; + } +- cleanup: ++ ++cleanup: + Py_XDECREF(big_buffer); + return list; ++ ++error: ++ Py_CLEAR(list); ++ goto cleanup; + } + + static PyObject * +@@ -1508,10 +1583,10 @@ + if (!PyArg_ParseTuple(args, f->f_binary ? "s#" : "t#", &s, &n)) + return NULL; + f->f_softspace = 0; +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + n2 = fwrite(s, 1, n, f->f_fp); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (n2 != n) { + PyErr_SetFromErrno(PyExc_IOError); + clearerr(f->f_fp); +@@ -1609,8 +1684,8 @@ + + /* Since we are releasing the global lock, the + following code may *not* execute Python code. */ +- Py_BEGIN_ALLOW_THREADS + f->f_softspace = 0; ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + for (i = 0; i < j; i++) { + line = PyList_GET_ITEM(list, i); +@@ -1618,13 +1693,13 @@ + nwritten = fwrite(PyString_AS_STRING(line), + 1, len, f->f_fp); + if (nwritten != len) { +- Py_BLOCK_THREADS ++ FILE_ABORT_ALLOW_THREADS(f) + PyErr_SetFromErrno(PyExc_IOError); + clearerr(f->f_fp); + goto error; + } + } +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + + if (j < CHUNKSIZE) + break; +@@ -1857,11 +1932,11 @@ + PyErr_NoMemory(); + return -1; + } +- Py_BEGIN_ALLOW_THREADS ++ FILE_BEGIN_ALLOW_THREADS(f) + errno = 0; + chunksize = Py_UniversalNewlineFread( + f->f_buf, bufsize, f->f_fp, (PyObject *)f); +- Py_END_ALLOW_THREADS ++ FILE_END_ALLOW_THREADS(f) + if (chunksize == 0) { + if (ferror(f->f_fp)) { + PyErr_SetFromErrno(PyExc_IOError); +@@ -1970,6 +2045,7 @@ + Py_INCREF(Py_None); + ((PyFileObject *)self)->f_encoding = Py_None; + ((PyFileObject *)self)->weakreflist = NULL; ++ ((PyFileObject *)self)->unlocked_count = 0; + } + return self; + } +@@ -2157,12 +2233,12 @@ + return -1; + } + else if (PyFile_Check(f)) { +- FILE *fp = PyFile_AsFile(f); ++ PyFileObject *fobj = (PyFileObject *) f; + #ifdef Py_USING_UNICODE +- PyObject *enc = ((PyFileObject*)f)->f_encoding; ++ PyObject *enc = fobj->f_encoding; + int result; + #endif +- if (fp == NULL) { ++ if (fobj->f_fp == NULL) { + err_closed(); + return -1; + } +@@ -2177,11 +2253,11 @@ + value = v; + Py_INCREF(value); + } +- result = PyObject_Print(value, fp, flags); ++ result = file_PyObject_Print(value, fobj, flags); + Py_DECREF(value); + return result; + #else +- return PyObject_Print(v, fp, flags); ++ return file_PyObject_Print(v, fobj, flags); + #endif + } + writer = PyObject_GetAttrString(f, "write"); +@@ -2219,6 +2295,7 @@ + int + PyFile_WriteString(const char *s, PyObject *f) + { ++ + if (f == NULL) { + /* Should be caused by a pre-existing error */ + if (!PyErr_Occurred()) +@@ -2227,12 +2304,15 @@ + return -1; + } + else if (PyFile_Check(f)) { ++ PyFileObject *fobj = (PyFileObject *) f; + FILE *fp = PyFile_AsFile(f); + if (fp == NULL) { + err_closed(); + return -1; + } ++ FILE_BEGIN_ALLOW_THREADS(fobj) + fputs(s, fp); ++ FILE_END_ALLOW_THREADS(fobj) + return 0; + } + else if (!PyErr_Occurred()) { +Index: Python/pythonrun.c +=================================================================== +--- Python/pythonrun.c (revision 66572) ++++ Python/pythonrun.c (working copy) +@@ -1554,9 +1554,9 @@ + OutputDebugString("Fatal Python error: "); + OutputDebugString(msg); + OutputDebugString("\n"); +-#ifdef _DEBUG ++//#ifdef _DEBUG + DebugBreak(); +-#endif ++//#endif + #endif /* MS_WINDOWS */ + abort(); + } +Index: Include/fileobject.h +=================================================================== +--- Include/fileobject.h (revision 66572) ++++ Include/fileobject.h (working copy) +@@ -25,6 +25,8 @@ + int f_skipnextlf; /* Skip next \n */ + PyObject *f_encoding; + PyObject *weakreflist; /* List of weak references */ ++ int unlocked_count; /* Num. currently running sections of code ++ using f_fp with the GIL released. */ + } PyFileObject; + + PyAPI_DATA(PyTypeObject) PyFile_Type; +@@ -38,6 +40,8 @@ + PyAPI_FUNC(PyObject *) PyFile_FromFile(FILE *, char *, char *, + int (*)(FILE *)); + PyAPI_FUNC(FILE *) PyFile_AsFile(PyObject *); ++PyAPI_FUNC(void) PyFile_IncUseCount(PyFileObject *); ++PyAPI_FUNC(void) PyFile_DecUseCount(PyFileObject *); + PyAPI_FUNC(PyObject *) PyFile_Name(PyObject *); + PyAPI_FUNC(PyObject *) PyFile_GetLine(PyObject *, int); + PyAPI_FUNC(int) PyFile_WriteObject(PyObject *, PyObject *, int); +Index: Include/pydebug.h +=================================================================== +--- Include/pydebug.h (revision 66572) ++++ Include/pydebug.h (working copy) +@@ -28,6 +28,40 @@ + + PyAPI_FUNC(void) Py_FatalError(const char *message); + ++#define ENABLE_RELEASE_ASSERTS 1 ++ ++#if ENABLE_RELEASE_ASSERTS ++/* a release assert */ ++#define rassert(x) \ ++ do { \ ++ if (!(x)) \ ++ Py_FatalError("rassert(" #x ") " __FUNCTION__ " in " __FUNCTION__); \ ++ } while (0) ++ ++ ++#else ++#define rassert assert ++#endif ++ ++#if ENABLE_THREADCHECKING ++ ++#define THREADCHECK_START static long __private_threadcheck = -1; \ ++ if (-1 != __private_threadcheck && PyThread_get_thread_ident() != __private_threadcheck) { \ ++ char* errmsg = malloc(300); \ ++ sprintf(errmsg, "Multiple threads are accessing %s (%s:%d): %d and %d", __FUNCTION__, __FILE__, __LINE__, PyThread_get_thread_ident(), __private_threadcheck); \ ++ Py_FatalError(errmsg); \ ++ } else \ ++ __private_threadcheck = PyThread_get_thread_ident(); ++ ++#define THREADCHECK_END __private_threadcheck = -1; ++ ++#else ++ ++#define THREADCHECK_START ++#define THREADCHECK_END ++ ++#endif ++ + #ifdef __cplusplus + } + #endif +Index: PCbuild/_ssl.mak +=================================================================== +--- PCbuild/_ssl.mak (revision 66572) ++++ PCbuild/_ssl.mak (working copy) +@@ -7,10 +7,12 @@ + !ELSE + SUFFIX=.pyd + TEMP=x86-temp-release/ +-CFLAGS=/Ox /MD /LD /DWIN32 +-SSL_LIB_DIR=$(SSL_DIR)/out32 ++CFLAGS=/Ox /MD /LD /DWIN32 /Zi ++SSL_LIB_DIR=$(SSL_DIR)/out32dll + !ENDIF + ++LDFLAGS=/DEBUG ++ + INCLUDES=-I ../Include -I ../PC -I $(SSL_DIR)/inc32 + + SSL_LIBS=gdi32.lib wsock32.lib user32.lib advapi32.lib /LIBPATH:$(SSL_LIB_DIR) libeay32.lib ssleay32.lib +@@ -26,12 +28,12 @@ + @if not exist "$(TEMP)/_ssl/." mkdir "$(TEMP)/_ssl" + cl /nologo /c $(SSL_SOURCE) $(CFLAGS) /Fo$(TEMP)\_ssl\$*.obj $(INCLUDES) + link /nologo @<< +- /dll /out:_ssl$(SUFFIX) $(TEMP)\_ssl\$*.obj $(SSL_LIBS) ++ /dll /out:_ssl$(SUFFIX) $(TEMP)\_ssl\$*.obj $(SSL_LIBS) $(LDFLAGS) + << + + _hashlib$(SUFFIX): $(HASH_SOURCE) $(SSL_LIB_DIR)/libeay32.lib ../PC/*.h ../Include/*.h + @if not exist "$(TEMP)/_hashlib/." mkdir "$(TEMP)/_hashlib" + cl /nologo /c $(HASH_SOURCE) $(CFLAGS) $(EXTRA_CFLAGS) /Fo$(TEMP)\_hashlib\$*.obj $(INCLUDES) + link /nologo @<< +- /dll /out:_hashlib$(SUFFIX) $(HASH_LIBS) $(TEMP)\_hashlib\$*.obj ++ /dll /out:_hashlib$(SUFFIX) $(HASH_LIBS) $(TEMP)\_hashlib\$*.obj $(LDFLAGS) + << +Index: Lib/distutils/msvccompiler.py +=================================================================== +--- Lib/distutils/msvccompiler.py (revision 66572) ++++ Lib/distutils/msvccompiler.py (working copy) +@@ -125,7 +125,10 @@ + net = r"Software\Microsoft\.NETFramework" + self.set_macro("FrameworkDir", net, "installroot") + try: +- if version > 7.0: ++ if version > 7.09: ++ os.environ.update(DISTUTILS_USE_SDK='1', ++ MSSdk='1') ++ elif version > 7.0: + self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1") + else: + self.set_macro("FrameworkSDKDir", net, "sdkinstallroot") +Index: PCbuild/build_ssl.py +=================================================================== +--- PCbuild/build_ssl.py (revision 66572) ++++ PCbuild/build_ssl.py (working copy) +@@ -97,7 +97,7 @@ + debug = False + configure = "VC-WIN32" + do_script = "ms\\do_masm" +- makefile = "ms\\nt.mak" ++ makefile = "ms\\ntdll.mak" + elif sys.argv[1] == "Debug": + arch = "x86" + debug = True +@@ -109,14 +109,14 @@ + debug = False + configure = "VC-WIN64I" + do_script = "ms\\do_win64i" +- makefile = "ms\\nt.mak" ++ makefile = "ms\\ntdll.mak" + os.environ["VSEXTCOMP_USECL"] = "MS_ITANIUM" + elif sys.argv[1] == "ReleaseAMD64": + arch="amd64" + debug=False + configure = "VC-WIN64A" + do_script = "ms\\do_win64a" +- makefile = "ms\\nt.mak" ++ makefile = "ms\\ntdll.mak" + os.environ["VSEXTCOMP_USECL"] = "MS_OPTERON" + make_flags = "" + if build_all: diff --git a/digsby/build/msw/README b/digsby/build/msw/README new file mode 100644 index 0000000..5f05aea --- /dev/null +++ b/digsby/build/msw/README @@ -0,0 +1,71 @@ + +building Digsby dependencies on Windows +======================================= + +Known Issues +------------ + +MSVC9: python/pcbuild/pcbuild.sln is not msvc9 express compatible, fails + need to script "vcbuild /upgrade pcbuild.sln" process + +Prerequisites +------------- + +- Visual Studio 2008 +- Windows 7 SDK (first google result; you can leave .NET components unchecked in the installer) + - make sure to run the "Windows SDK Configuration Tool" and set the latest version as active +- Python 2.6 (installed to C:\python26) + - http://python.org/download/ +- perl (ActiveState Perl is easy to install) + - http://www.activestate.com/activeperl/downloads/ +- svn (command-line binaries) + - http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=8100 +- swig +- git (msys-git, in c:\Program Files\Git\bin\git.exe) + - http://code.google.com/p/msysgit/ + - your public key needs to be stored in mini:/home/git/.ssh/authorized_keys2 + (ssh-keygen can create an ~/.ssh/id_rsa.pub file for you that git will use + automatically) + - Make sure to choose "leave line endings as they are" when installing. If you + didn't, open Git Bash and run the following command to prevent problems: + git config --global core.autocrlf false + - make sure to run these commands to get your username/email configured + so that if you need to push commits back they have your name. + git config --global user.name "Your Name Comes Here" + git config --global user.email you@yourdomain.example.com +- bakefile + - http://www.bakefile.org/download.html +- for building CGUI: + - need boost: unzip http://sourceforge.net/projects/boost/files/boost/1.42.0/ somewhere and set BOOST_DIR to that directory. + +Bootstrapped +------------ + +- svn (command-line binaries) + - http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=8100 +- nasm.exe + - required by OpenSSL build process + - binaries at sourceforge: http://sourceforge.net/project/showfiles.php?group_id=6208&package_id=47034 +- for building webkit, a cygwin install from http://webkit.org/building/tools.html + + +Instructions +------------ + +Get Digsby: + svn co http://DIGSBYSVN/digsby/trunk digsby + +Build all dependencies: + cd digsby\build\msw + python build_all.py + +To run: + cd ..\.. (back into the root Digsby directory) + digsby.bat + + +--- +git fails w/ FAT32 + +lxml must be built after PIL (needs zlib) which must come after webkit (needs jpeg) + diff --git a/digsby/build/msw/_ssl.mak.25.2008 b/digsby/build/msw/_ssl.mak.25.2008 new file mode 100644 index 0000000..d5b8b6c --- /dev/null +++ b/digsby/build/msw/_ssl.mak.25.2008 @@ -0,0 +1,39 @@ + +!IFDEF DEBUG +SUFFIX=_d.pyd +TEMP=x86-temp-debug/ +CFLAGS=/Od /Zi /MDd /LDd /DDEBUG /D_DEBUG /DWIN32 +SSL_LIB_DIR=$(SSL_DIR)/out32.dbg +!ELSE +SUFFIX=.pyd +TEMP=x86-temp-release/ +CFLAGS=/Ox /MD /LD /DWIN32 /Zi +SSL_LIB_DIR=$(SSL_DIR)/out32dll +!ENDIF + +LDFLAGS=/DEBUG + +INCLUDES=-I ../Include -I ../PC -I $(SSL_DIR)/inc32 + +SSL_LIBS=gdi32.lib wsock32.lib user32.lib advapi32.lib /LIBPATH:$(SSL_LIB_DIR) libeay32.lib ssleay32.lib +SSL_SOURCE=../Modules/_ssl.c + +HASH_LIBS=gdi32.lib user32.lib advapi32.lib /libpath:$(SSL_LIB_DIR) libeay32.lib +HASH_SOURCE=../Modules/_hashopenssl.c + +all: _ssl$(SUFFIX) _hashlib$(SUFFIX) + +# Split compile/link into two steps to better support VSExtComp +_ssl$(SUFFIX): $(SSL_SOURCE) $(SSL_LIB_DIR)/libeay32.lib $(SSL_LIB_DIR)/ssleay32.lib ../PC/*.h ../Include/*.h + @if not exist "$(TEMP)/_ssl/." mkdir "$(TEMP)/_ssl" + cl /nologo /c $(SSL_SOURCE) $(CFLAGS) /Fo$(TEMP)\_ssl\$*.obj $(INCLUDES) + link /nologo @<< + /dll /out:_ssl$(SUFFIX) $(TEMP)\_ssl\$*.obj $(SSL_LIBS) $(LDFLAGS) +<< + +_hashlib$(SUFFIX): $(HASH_SOURCE) $(SSL_LIB_DIR)/libeay32.lib ../PC/*.h ../Include/*.h + @if not exist "$(TEMP)/_hashlib/." mkdir "$(TEMP)/_hashlib" + cl /nologo /c $(HASH_SOURCE) $(CFLAGS) $(EXTRA_CFLAGS) /Fo$(TEMP)\_hashlib\$*.obj $(INCLUDES) + link /nologo @<< + /dll /out:_hashlib$(SUFFIX) $(HASH_LIBS) $(TEMP)\_hashlib\$*.obj $(LDFLAGS) +<< diff --git a/digsby/build/msw/b.bat b/digsby/build/msw/b.bat new file mode 100644 index 0000000..4436838 --- /dev/null +++ b/digsby/build/msw/b.bat @@ -0,0 +1,10 @@ +@echo off + +if "%1" == "" goto error +python build_%1.py %2 %3 %4 %5 %6 %7 %8 %9 +goto done + +:error +python build_all.py --help + +:done diff --git a/digsby/build/msw/binaries.txt b/digsby/build/msw/binaries.txt new file mode 100644 index 0000000..8970556 --- /dev/null +++ b/digsby/build/msw/binaries.txt @@ -0,0 +1,58 @@ +#__LICENSE_GOES_HERE__ +# these files and their PDBs are sent to the symbol server by +# sendsymbols.py + +# cgui +$PLATLIB\cgui.pyd +$PLATLIB\blist.pyd +$PLATLIB\buddylist$_DEBUG.dll + +# python +$DPYTHON\python$_DEBUG.exe +$DPYTHON\pythonw$_DEBUG.exe +$DPYTHON\python26$_DEBUG.dll + +$DPYTHON\DLLs\_ctypes.pyd +$DPYTHON\DLLs\_elementtree.pyd +$DPYTHON\DLLs\_hashlib.pyd +$DPYTHON\DLLs\_multiprocessing.pyd +$DPYTHON\DLLs\_socket.pyd +$DPYTHON\DLLs\_ssl.pyd +$DPYTHON\DLLs\bz2.pyd +$DPYTHON\DLLs\pyexpat.pyd +$DPYTHON\DLLs\select.pyd +$DPYTHON\DLLs\unicodedata.pyd +$DPYTHON\DLLs\winsound.pyd + +# wx +$PLATLIB\wxbase28${WX_POSTFIX}_net_vc.dll +$PLATLIB\wxbase28${WX_POSTFIX}_vc.dll + +$PLATLIB\wxmsw28${WX_POSTFIX}_adv_vc.dll +$PLATLIB\wxmsw28${WX_POSTFIX}_core_vc.dll +$PLATLIB\wxmsw28${WX_POSTFIX}_stc_vc.dll + +# webkit +$PLATLIB\wxwebkit.dll +$PLATLIB\icuuc42.dll +$PLATLIB\icuuc.pdb +$PLATLIB\icuin42.dll +$PLATLIB\icuin.pdb +$PLATLIB\icudt42.dll + +$PLATLIB\sip.pyd +$PLATLIB\libxml2.dll +$PLATLIB\_syck.pyd + +# wxpy +$PLATLIB\wx\_webview.pyd +$PLATLIB\wx\_wxcalendar.pyd +$PLATLIB\wx\_wxcore.pyd +$PLATLIB\wx\_wxstc.pyd + +# speedups +$PLATLIB\_jsonspeedups.pyd +$PLATLIB\_speedups.pyd +$PLATLIB\_xmlextra.pyd + +$PLATLIB\libcurl${DEBUG}.dll diff --git a/digsby/build/msw/bootstrap.py b/digsby/build/msw/bootstrap.py new file mode 100644 index 0000000..c10572c --- /dev/null +++ b/digsby/build/msw/bootstrap.py @@ -0,0 +1,40 @@ +#__LICENSE_GOES_HERE__ +''' +Create a set of binaries needed by the rest of the build process +''' + +assert False, "use webkit's cygwin installer" +from __future__ import with_statement +import sys +sys.path.append('..') +from buildutil import wget, cd, unzip + +prefix = 'http://gnuwin32.sourceforge.net/downlinks/' +zip_php = 'zip.php' +types = {'b' : 'bin', + 'd' : 'dep'} +needed = { + 'autoconf' : 'b', + 'automake' : 'b', + 'bison' : 'bd', + 'flex' : 'b', + 'gperf' : 'b', + 'make' : 'bd', + 'wget' : 'bd', + 'grep' : 'bd', + 'unzip' : 'b', + 'which' : 'b', + 'tar' : 'bd', + 'unrar' : 'b', + 'zip' : 'bd', + 'coreutils' : 'bd', + } +# 'http://downloads.sourceforge.net/mingw/binutils-2.19.1-mingw32-bin.tar.gz' : None, +#http://subversion.tigris.org/files/documents/15/45222/svn-win32-1.5.6.zip +with cd('downloads'): + for package, zips in needed.iteritems(): + for letter in zips: + kind = types[letter] + name = package + '-' + kind + '-' + zip_php + wget(prefix + name) + unzip(name) diff --git a/digsby/build/msw/build_all.py b/digsby/build/msw/build_all.py new file mode 100644 index 0000000..b997566 --- /dev/null +++ b/digsby/build/msw/build_all.py @@ -0,0 +1,82 @@ +#__LICENSE_GOES_HERE__ +''' +builds all windows digsby dependencies +''' + +from __future__ import with_statement +import sys; sys.path.append('..') +import os.path + +if __name__ == '__main__': + os.chdir(os.path.abspath(os.path.dirname(__file__))) + +from buildutil import run, cd, dpy, DEBUG +from datetime import datetime + +def help(): + def i(msg): + print >> sys.stderr, msg + + i(''' +Usage: b [what-to-build], where what-to-build is + all all dependencies + python DPython + libxml2 libxml2, libxslt, and lxml + wx wxWidgets + webkit wxWebKit + wxpy wxpy bindings + cgui Digsby extensions + speedups speedup modules for pyxmpp, simplejson, and protocols + blist buddylist module + package tools for packaging Digsby + +''') + +def build_all_deps(): + check_windows_sdk_version() + + #run(['python', 'build_python.py', 'all'] + (['--DEBUG'] if DEBUG else [])) + + #with cd('..'): + #dpy(['compiledeps.py', 'm2crypto']) + #dpy(['compiledeps.py', 'syck']) + + #dpy(['build_libxml2.py', 'libxml2']) + dpy(['build_wx.py', 'all']) + dpy(['build_webkit.py', ('debug' if DEBUG else 'release')]) #requires libxml2, wx + + #with cd('..'): + #dpy(['compiledeps.py', 'pil']) # uses JPEG lib from wx/webkit + + #dpy(['build_libxml2.py', 'lxml']) #depends on zlib from PIL + + dpy(['../build_wxpy.py']) #depends on webkit. + dpy(['build_speedups.py']) + dpy(['build_cgui.py']) #cgui depends on wx, wxpy + dpy(['build_blist.py']) + +def show_time(func): + before = datetime.now() + res = func() + print '\nbuild_all took %s' % format_td(datetime.now() - before) + return res + +def format_td(td): + hours = td.seconds // 3600 + minutes = (td.seconds % 3600) // 60 + seconds = td.seconds % 60 + return '%sh:%sm:%ss' % (hours, minutes, seconds) + +def check_windows_sdk_version(): + import _winreg + for hive in (_winreg.HKEY_LOCAL_MACHINE, _winreg.HKEY_CURRENT_USER): + # not checking 64bit mode. + key = _winreg.OpenKey(hive, r'SOFTWARE\Microsoft\Microsoft SDKs\Windows', 0, _winreg.KEY_READ) + ver, _tp = _winreg.QueryValueEx(key, 'CurrentVersion') + assert int(ver[1]) >= 7, 'Microsoft Windows SDK v7.0 or higher is required, found: %s' % ver + +if __name__ == '__main__': + if '--help' in sys.argv: + help() + else: + show_time(build_all_deps) diff --git a/digsby/build/msw/build_blist.py b/digsby/build/msw/build_blist.py new file mode 100644 index 0000000..66549bb --- /dev/null +++ b/digsby/build/msw/build_blist.py @@ -0,0 +1,60 @@ +#__LICENSE_GOES_HERE__ +''' +builds buddylist.dll and blist.pyd and installs them to DEPS_DIR +''' + +import sys +import os.path +import distutils.sysconfig + +from subprocess import call +blist_dir = os.path.dirname(__file__) +if '..' not in sys.path: sys.path.append(os.path.join(blist_dir, '..')) +from buildutil import dpy, DEBUG, DEPS_DIR, copy_different + +buddylist_root = os.path.join(blist_dir, '../..', r'ext/src/BuddyList') + +def build(): + project_dir = os.path.join(buddylist_root, 'msvc2008') + solution_file = os.path.normpath(os.path.abspath(os.path.join(project_dir, 'BuddyListSort.sln'))) + + config_name = 'Debug' if DEBUG else 'Release' + configuration = '%s|Win32' % config_name + + # the MSVC project files use the DIGSBY_PYTHON environment variable to find + # Python.h and python2x.lib + env = dict(os.environ) + + SIP_INCLUDE = os.path.normpath(os.path.abspath('../../build/msw/sip/siplib')) + SIP_BIN = os.path.normpath(os.path.abspath('../../build/msw/sip/sipgen/sip.exe')) + + assert os.path.isdir(SIP_INCLUDE), 'expected a siplib directory at ' + SIP_BIN + assert os.path.isfile(SIP_BIN), 'expected a sip binary at ' + SIP_BIN + + env.update( + SIP_INCLUDE=SIP_INCLUDE, + SIP_BIN=SIP_BIN, + DIGSBY_PYTHON=os.path.dirname(distutils.sysconfig.get_python_inc())) + + # Invoke vcbuild + ret = call(['vcbuild', '/nologo', solution_file, configuration], env=env) + if ret: sys.exit(ret) + + # Install to DEPS_DIR + binaries = 'blist{d}.pyd blist{d}.pdb buddylist{d}.dll buddylist{d}.pdb' + for f in binaries.format(d='_d' if DEBUG else '').split(): + binary = os.path.join(project_dir, config_name, f) + copy_different(binary, DEPS_DIR) + +def test(): + sorter_test_file = os.path.join(buddylist_root, 'test/sorter_test.py') + dpy([sorter_test_file], platlib = True) + +def main(): + if 'test' in sys.argv: + test() + else: + build() + +if __name__ == '__main__': + main() diff --git a/digsby/build/msw/build_cgui.py b/digsby/build/msw/build_cgui.py new file mode 100644 index 0000000..ee274be --- /dev/null +++ b/digsby/build/msw/build_cgui.py @@ -0,0 +1,40 @@ +#__LICENSE_GOES_HERE__ +''' +builds Digsby's CGUI extension +''' +from __future__ import with_statement + +import sys +sys.path.append('..') if not '..' in sys.path else None +from buildutil import cd, dpy +from os.path import isdir, abspath +import os + +def build(): + # find WX directory + from build_wx import WXDIR + wxdir = abspath(WXDIR) + + # find WXPY directory, and SIP directory + from build_wxpy import wxpy_path, sip_path + sipdir, wxpydir = abspath(sip_path), abspath(wxpy_path) + + assert isdir(wxdir) + assert isdir(sipdir) + assert isdir(wxpydir) + + from buildutil import tardep + boost = tardep('http://iweb.dl.sourceforge.net/project/boost/boost/1.42.0/', 'boost_1_42_0', '.tar.gz', 40932853) + boost.get() + + # place these directories on the PYTHONPATH + os.environ['PYTHONPATH'] = os.pathsep.join([wxpydir, sipdir]) + + with cd('../../ext'): + dpy(['buildbkl.py', '--wx=%s' % wxdir] + sys.argv[1:]) + +def main(): + build() + +if __name__ == '__main__': + main() diff --git a/digsby/build/msw/build_insttools.py b/digsby/build/msw/build_insttools.py new file mode 100644 index 0000000..defc1e2 --- /dev/null +++ b/digsby/build/msw/build_insttools.py @@ -0,0 +1,51 @@ +#__LICENSE_GOES_HERE__ +''' + +- nsis +- py2exe + +''' +from __future__ import with_statement + +import sys +if not '..' in sys.path: + sys.path.append('..') + +from buildutil import run, fatal, tardep, cd, dpy +from os import environ +from os.path import isdir, abspath + +# overridable from environment +NSIS_PATH = environ.get('NSIS_PATH', r'c:\Program Files\NSIS') +DOTSYNTAX_SVN = environ.get('DOTSYNTAX_SVN', 'http://mini') + +DIGSBY_INSTALLER_SVN = DOTSYNTAX_SVN + '/svn/dotSyntax/DigsbyInstaller/trunk' +PY2EXE_DIR = '/py2exe' +PY2EXE = DIGSBY_INSTALLER_SVN + PY2EXE_DIR + +def patch_nsis(): + if not isdir(NSIS_PATH): + fatal('Cannot find NSIS installation at %s') + + print '\nPlease copy the contents of\n%s into %s' % (abspath('NSIS'), NSIS_PATH) + +def build_py2exe(): + from buildutil import DEPS_DIR + assert isdir(DEPS_DIR) + + with cd('DigsbyInstaller' + PY2EXE_DIR): + # -i means the PYD will sit next to the files in py2exe/py2exe + dpy(['setup.py', 'build', 'install', '--install-lib', str(DEPS_DIR)]) + print + print 'py2exe source and PYDs are in:' + print abspath('.') + print + +def main(): + run('svn co %s' % DIGSBY_INSTALLER_SVN) + + patch_nsis() + build_py2exe() + +if __name__ == '__main__': + main() diff --git a/digsby/build/msw/build_libxml2.py b/digsby/build/msw/build_libxml2.py new file mode 100644 index 0000000..edbf9f1 --- /dev/null +++ b/digsby/build/msw/build_libxml2.py @@ -0,0 +1,220 @@ +#__LICENSE_GOES_HERE__ +from __future__ import with_statement + +import os +import os.path +import shutil +import sys + +sys.path.append('..') if '..' not in sys.path else None +from buildutil import run, fatal, inform, cd, wget_cached, untar, unzip, mkdirs, \ + filerepl, dpy, copy_different, DEBUG, DEBUG_POSTFIX, get_patch_cmd +from os.path import isdir, abspath, join as pathjoin + +from compiledeps import libxml2_dirname, download_libxml2, libxslt + +iconv_zipfile, iconv_zipfile_size = 'libiconv-1.9.2-1-lib.zip', 731496 +iconv_url = 'http://downloads.sourceforge.net/project/gnuwin32/libiconv/1.9.2-1/' + iconv_zipfile +iconv_dirname = iconv_zipfile.replace('.zip', '') + +patch_cmd = get_patch_cmd() +lxml_patch = r'..\patches\lxml.patch' +libxml2_patch = r'..\patches\libxml2.patch' + +from buildutil import DEPS_DIR + +CLEAN = 'clean' in sys.argv + +def patch_libxml2_h(*path): + # msvc 2008 defines _vsnprintf as an intrinsic (or something like that) + # but libxml2 wants to #define it as something else, and things go + # boom + + with cd(*path): + with open('win32config.h') as f: + lines = [l for l in f if not l.startswith('#define vsnprintf')] + + with open('win32config.h', 'w') as f: + f.write(''.join(lines)) + +def build_libxslt(): + 'needed by webkit and lxml' + + from compiledeps import libxml2_dirname + + libxmlabs = abspath(libxml2_dirname) + libxmlinc = pathjoin(libxmlabs, 'include') + libxmllib = pathjoin(libxmlabs, 'win32', 'bin.msvc') + iconvinc = pathjoin(abspath(iconv_dirname), 'include') + + libxslt_dir = libxslt.get() + with cd(libxslt_dir): + with cd('libxslt'): + ensure_processonenode_is_public() + + with cd('win32'): + patch_libxml2_h('..', 'libxslt') + debug_flag = ['debug=yes'] if DEBUG else [] + run(['cscript', 'configure.js', '//E:JavaScript', 'vcmanifest=yes'] + + debug_flag + + ['include=%s' % os.pathsep.join([libxmlinc, iconvinc]), + 'lib=%s' % libxmllib]) + + filerepl('Makefile.msvc', '/O2', '/Os /GL /GS- /Zi') + filerepl('Makefile.msvc', 'LDFLAGS = /nologo', 'LDFLAGS = /OPT:REF /OPT:ICF /nologo /DEBUG') + + run(['nmake', '-f', 'Makefile.msvc'] + (['clean'] if CLEAN else [])) + + return libxslt_dir + +def ensure_processonenode_is_public(): + ''' + libxslt doesn't export xsltProcessOneNode but lxml expects it. + + rewrites libxslt/transform.h to export the function. + ''' + + with open('transform.h', 'r') as f: + transform_h = f.read() + + if not 'xsltProcessOneNode' in transform_h: + to_repl = 'XSLTPUBFUN xsltTransformContextPtr XSLTCALL' + assert to_repl in transform_h + transform_h = transform_h.replace( + to_repl, + 'XSLTPUBFUN void XSLTCALL xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node, xsltStackElemPtr params);\n\n' + 'XSLTPUBFUN xsltTransformContextPtr XSLTCALL') + assert 'xsltProcessOneNode' in transform_h + with open('transform.h', 'w') as f: + f.write(transform_h) + +def build_lxml(): + from compiledeps import libxml2_dirname + + libxml2_dir = abspath(libxml2_dirname) + iconv_dir = abspath(iconv_dirname) + + lxml2_libs = pathjoin(libxml2_dir, 'win32', 'bin.msvc') + if not isdir(lxml2_libs): + fatal('could not find libxml2.lib in %s' % lxml2_libs) + + lxml2_inc = pathjoin(libxml2_dir, 'include') + if not isdir(lxml2_inc): + fatal('could not find libxml2 includes directory at %s' % lxml2_inc) + + if not isdir(iconv_dir): + fatal('could not find iconv at %s' % iconv_dir) + + libxslt_dir = abspath(libxslt.dirname) + if not isdir(libxslt_dir): + fatal('could not find libxslt at %s' % libxslt_dir) + libxslt_lib = pathjoin(libxslt_dir, 'win32', 'bin.msvc') + + zlib_dir = abspath('zlib-1.2.3') + + from compiledeps import lxml + new = not os.path.exists(lxml.dirname) + with cd(lxml.get()): + if os.name == 'nt' and new: + # after pulling a fresh tarball, apply the patch pointed to by lxml_patch. + run([patch_cmd, '--ignore-whitespace', '-p0', '-i', '../%s' % lxml_patch]) + dpy(['setup.py', 'build_ext', + '-I' + os.pathsep.join((lxml2_inc, libxslt_dir, pathjoin(iconv_dir, 'include'))), + '-L' + os.pathsep.join((libxslt_lib, lxml2_libs, pathjoin(iconv_dir, 'lib'), zlib_dir))] + + (['--debug'] if DEBUG else []) + + ['install', '--install-lib', DEPS_DIR]) + build_libxml2 + +def build_libxml2(): + from compiledeps import libxml2_dirname + new = not os.path.exists(libxml2_dirname) + libxml2_dir = download_libxml2() + + with cd(libxml2_dir): + if os.name == 'nt' and new: + # after pulling a fresh tarball, apply the patch pointed to by lxml_patch. + print os.getcwd() + run([patch_cmd, '--ignore-whitespace', '-p0', '-i', os.path.abspath(os.path.join('..', libxml2_patch))]) + + inform(banner = 'libiconv') + if not isdir(iconv_dirname): + # has a .lib compiled statically to msvcrt.dll + wget_cached(iconv_zipfile, iconv_zipfile_size, iconv_url) + unzip(iconv_zipfile) + else: + inform('libiconv directory already exists') + + patch_libxml2_h(libxml2_dir, 'include') + + iconv = abspath(iconv_dirname) + + inform(banner = 'libxml2') + + print 'passing libiconv path to configure.js as %r' % iconv + + # copy the fixed setup.py.in + print 'copying libxml2.setup.py.msvc2008', pathjoin(libxml2_dir, 'python') + patched_setup = 'libxml2.setup.py.msvc2008' + assert os.path.exists(patched_setup) + copy_different(patched_setup, pathjoin(libxml2_dir, 'python', 'setup.py.in')) + + with cd(libxml2_dir, 'win32'): + debug_flag = ['debug=yes'] if DEBUG else [] + run(['cscript', 'configure.js', '//E:JavaScript', 'vcmanifest=yes', 'python=yes'] + debug_flag + [ + 'include=%s' % pathjoin(iconv, 'include'), + 'lib=%s' % pathjoin(iconv, 'lib')]) + + makefile = 'Makefile.msvc' + + # customize the Makefile... + with open(makefile) as f: + lines = [] + for line in f: + # 1) optimize a bit more than just /O2 + line = line.replace('/O2', '/Os /GS- /GL /Zi') + line = line.replace('/nologo /VERSION', '/nologo /OPT:REF /OPT:ICF /DEBUG /VERSION') + + lines.append(line) + + with open(makefile, 'w') as f: + f.write(''.join(lines)) + + + with cd(libxml2_dir, 'win32'): + run(['nmake', '-f', makefile] + (['clean'] if CLEAN else [])) + + # All finished files go to DEPS_DIR: + mkdirs(DEPS_DIR) + deps = os.path.abspath(DEPS_DIR) + + inform(banner='libxml2 python bindings') + with cd(libxml2_dir, 'python'): + # installs libxml2 python files to deps directory' + #post commit hook test line, git failed to catch the last one. + dpy(['setup.py', 'build_ext'] + (['--debug'] if DEBUG else []) + + ['install', '--install-lib', deps]) + + # but we still need libxml2.dll + libxml2_bindir = pathjoin(libxml2_dir, 'win32', 'bin.msvc') + copy_different(pathjoin(libxml2_bindir, 'libxml2.dll'), deps) + copy_different(pathjoin(libxml2_bindir, 'libxml2.pdb'), deps) + + # and iconv.dll + copy_different(os.path.join(iconv, 'iconv.dll'), deps) + + # show which Python was used to build the PYD + dpy(['-c', "import sys; print 'libxml2 python bindings built with %s' % sys.executable"]) + + with cd(DEPS_DIR): + dpy(['-c', "import libxml2"]) + + # build and install libxslt + libxslt_dir = build_libxslt() + copy_different(os.path.join(libxslt_dir, 'win32', 'bin.msvc', 'libxslt.dll'), deps) + copy_different(os.path.join(libxslt_dir, 'win32', 'bin.msvc', 'libexslt.dll'), deps) + +if __name__ == '__main__': + if 'libxml2' in sys.argv or '--libxml2' in sys.argv: + build_libxml2() + if 'lxml' in sys.argv or '--lxml' in sys.argv: + build_lxml() diff --git a/digsby/build/msw/build_msw.py b/digsby/build/msw/build_msw.py new file mode 100644 index 0000000..abcf86d --- /dev/null +++ b/digsby/build/msw/build_msw.py @@ -0,0 +1,28 @@ +#__LICENSE_GOES_HERE__ +from __future__ import with_statement + +import os +import sys +sys.path.append('..') if '..' not in sys.path else None + +from buildutil import run, fatal, inform, DEBUG_POSTFIX +from os.path import abspath, isdir, split as pathsplit, join as pathjoin + +def check_msvc(): + 'Ensures that Microsoft Visual Studio 2008 paths are setup correctly.' + + try: + devenvdir = os.environ['DevEnvDir'] + except KeyError: + fatal('DevEnvDir environment variable not set -- did you start a Visual Studio 2008 Command Prompt?') + + if '9.0' not in devenvdir: + fatal('error: not visual studio 2008') + + inform('Microsoft Visual Studio 2008 check: OK') + +def main(): + check_msvc() + +if __name__ == '__main__': + main() diff --git a/digsby/build/msw/build_python.py b/digsby/build/msw/build_python.py new file mode 100644 index 0000000..0a4ed7c --- /dev/null +++ b/digsby/build/msw/build_python.py @@ -0,0 +1,479 @@ +#__LICENSE_GOES_HERE__ +''' +downloads, builds, and tests Python +''' +from __future__ import with_statement + +USAGE_MSG = 'usage: python build_python.py [all] [build] [rebuild] [test] [copylib] [checkout]' + +USE_PYTHON_26 = True + +import os +from os.path import exists, join as pathjoin, abspath, normpath, split as pathsplit + +# if True, try to build _ssl.pyd against OpenSSL dynamically instead +# of statically, so that we don't have duplicate code (webkit uses openssl too) +USE_OPENSSL_DLL = True + +def is_vsexpress(): + try: + vsinstalldir = os.environ['vsinstalldir'] + except KeyError: + raise AssertionError('You must run build scripts from the Visual Studio command prompt.') + + # If we have devenv.exe, then it is VS pro + devenv = os.path.join(vsinstalldir, 'common7', 'ide', 'devenv.exe') + return not os.path.isfile(devenv) + +VS_EXPRESS = is_vsexpress() + +import sys +if not '..' in sys.path: sys.path.append('..') # for buildutil +from traceback import print_exc + +# where to place Python sources +PYTHON_DIR = 'python' + +# where to get Python sources: + +from buildutil import DEBUG, get_patch_cmd +from constants import PYTHON_PROJECTS_SVN + +DEBUG_POSTFIX = '_d' if DEBUG else '' + +USE_DEVENV = not VS_EXPRESS # devenv.exe does not come with the free compiler + + + +makepath = lambda *p: normpath(pathjoin(abspath(p[0]), *p[1:])) + +if USE_PYTHON_26: + # + # Python 2.6 Trunk + # + PYTHON_SVN_REVISION = '72606' + PYTHON_SVN_URL = '%s/python/branches/release26-maint@%s' % (PYTHON_PROJECTS_SVN, PYTHON_SVN_REVISION) + PCBUILD_DIR = 'PCBuild' + PYTHON_LIBDIR = normpath(pathjoin(abspath(pathsplit(__file__)[0]), PYTHON_DIR, PCBUILD_DIR)) + PYTHON_PGO_DIR = 'Win32-pgo' + PYTHON_PGI_LIBDIR = normpath(pathjoin(abspath(pathsplit(__file__)[0]), PYTHON_DIR, PCBUILD_DIR, 'Win32-pgi')) + PYTHON_PGO_LIBDIR = normpath(pathjoin(abspath(pathsplit(__file__)[0]), PYTHON_DIR, PCBUILD_DIR, PYTHON_PGO_DIR)) + PYTHON_EXE = normpath(pathjoin(PYTHON_LIBDIR, r'python%s.exe' % DEBUG_POSTFIX)) + PYTHON_EXE_PGI = normpath(pathjoin(PYTHON_PGI_LIBDIR, r'python.exe')) + PYTHON_EXE_PGO = normpath(pathjoin(PYTHON_PGO_LIBDIR, r'python.exe')) + PYTHON_VER = '26' + PYTHON_BZIP = ('bzip2-1.0.5', '%s/external/bzip2-1.0.5' % PYTHON_PROJECTS_SVN) + PYTHON_SQLITE = ('sqlite-3.5.9', '%s/external/sqlite-3.5.9/' % PYTHON_PROJECTS_SVN) +else: + # + # Python 2.5 Maintenance Branch + # + PYTHON_SVN_REVISION = 'HEAD' + PYTHON_SVN_URL = '%s/python/branches/release25-maint@%s' % (PYTHON_PROJECTS_SVN, PYTHON_SVN_REVISION) + PCBUILD_DIR = 'PCBuild' + PYTHON_LIBDIR = normpath(pathjoin(abspath(pathsplit(__file__)[0]), PYTHON_DIR, PCBUILD_DIR)) + PYTHON_EXE = normpath(pathjoin(PYTHON_LIBDIR, r'python%s.exe' % DEBUG_POSTFIX)) + PYTHON_VER = '25' + PYTHON_BZIP = ('bzip2-1.0.3', '%s/external/bzip2-1.0.3' % PYTHON_PROJECTS_SVN) + +print +print 'PYTHON_EXE', PYTHON_EXE +print 'DEBUG ', DEBUG +print + +from buildutil import run, cd, inform, fatal, filerepl, which + +def svn_version(path = '.'): + 'Returns the current revision for a given working copy directory.' + + v = run(['svnversion', '.'], capture_stdout = True) + if v.endswith('M'): v = v[:-1] + return int(v) + +def checkout(): + 'Checks out Python source.' + + run(['svn', 'co', PYTHON_SVN_URL, PYTHON_DIR]) + + patch_cmd = get_patch_cmd() + + def banner_msg(s): + print + print s + print + + def apply_patch(patchfile, strip_prefixes=0): + assert os.path.isfile(patchfile) + run([patch_cmd, '-p%d' % strip_prefixes, '-i', patchfile]) + + with cd(PYTHON_DIR): + if sys.opts.use_computed_goto: + banner_msg('applying computed goto patch') + apply_patch('../python26-computed-goto.patch') + + banner_msg('applying assert mode patch') + # this patch restores c assert()s and is made with + # svn diff http://svn.python.org/projects/python/trunk@69494 http://svn.python.org/projects/python/trunk@69495 + apply_patch('../python26-assert-mode.patch') + + banner_msg('applying file object close bug patch') + # http://bugs.python.org/issue7079 -- remove once we update to a newer version of 2.6 + apply_patch('../python26-file-object-close-7079.patch') + + if USE_DEVENV and sys.opts.intel: + banner_msg('applying intel project patch') + apply_patch('../vs2008+intel.patch', 3) + + banner_msg('applying common controls patch') + apply_patch('../python-common-controls.patch') + + +def update(): + 'Updates the Python source tree.' + + with cd(PYTHON_DIR): + if PYTHON_SVN_REVISION == 'HEAD' or svn_version('.') != int(PYTHON_SVN_REVISION): + inform('updating python') + run(['svn', 'update', '-r', PYTHON_SVN_REVISION]) + return True + else: + return False + +def get_deps(): + ''' + Gets external dependencies needed to build Python. + + (From http://svn.python.org/view/python/branches/release25-maint/Tools/buildbot/external.bat?rev=51340&view=markup) + + intentionally missing: + - tcl/tk + - Sleepycat db + ''' + + import shutil + bzip_dir, bzip_checkout = PYTHON_BZIP + + if not exists(bzip_dir): + run(['svn', 'export', bzip_checkout]) + else: + inform(bzip_dir + ': already exists') + + sqlite_dir, sqlite_checkout = PYTHON_SQLITE + + if not exists(sqlite_dir): + run(['svn', 'export', sqlite_checkout]) + else: + inform(bzip_dir + ': already exists') + + makefile = os.path.join(bzip_dir, 'makefile.msc') + debug_makefile = os.path.join(bzip_dir, 'makefile.debug.msc') + release_makefile = os.path.join(bzip_dir, 'makefile.release.msc') + + if not os.path.isfile(debug_makefile): + shutil.copy2(makefile, release_makefile) + + with open(makefile, 'r') as f: lines = f.readlines() + + with open(debug_makefile, 'w') as f: + for line in lines: + if line.strip() == 'CFLAGS= -DWIN32 -MD -Ox -D_FILE_OFFSET_BITS=64 -nologo': + line = 'CFLAGS= -DWIN32 -MDd -Od -D_FILE_OFFSET_BITS=64 -nologo -Zi\n' + f.write(line) + + assert os.path.isfile(release_makefile) + assert os.path.isfile(debug_makefile) + + src = release_makefile if not DEBUG else debug_makefile + dest = makefile + + shutil.copy2(src, dest) + + if not exists('openssl-0.9.8g'): + run(['svn', 'export', 'http://svn.python.org/projects/external/openssl-0.9.8g']) + else: + inform('openssl-0.9.8g: already exists') + os.environ['PATH'] = os.pathsep.join([os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), 'openssl-0.9.8g'))), os.environ['PATH']]) + +def check_tools(): + + # perl - required for _ssl module + try: + run(['perl', '--version']) + except Exception: + fatal('Missing perl!\nPlease install ActiveState Perl from ' + 'http://www.activestate.com/Products/activeperl/') + + # nasm - required for _ssl module + try: + run(['nasmw', '-version']) + except Exception: + fatal('Missing NASM.\nPlease place binaries from ' + 'http://www.nasm.us/ ' + 'on your PATH. (copy nasm.exe to nasmw.exe)') + + +def fix_build_ssl_py(): + # Python's build files aren't quite up to linking against OpenSSL + # dynamically, but a few string substitutions are enough to do the + # trick. + + dllbin, staticbin = '$(opensslDir)\\out32dll\\', '$(opensslDir)\\out32\\' + + hashlib = '_hashlib.vcproj' + + build_repl = lambda *a: filerepl('build_ssl.py', *a) + vcproj_repl = lambda *a: filerepl('_ssl.vcproj', *a) + + if USE_OPENSSL_DLL: + build_repl('nt.mak', 'ntdll.mak') + vcproj_repl(staticbin, dllbin) + + if os.path.isfile(hashlib): + filerepl(hashlib, staticbin, dllbin) + else: + build_repl('ntdll.mak', 'nt.mak') + vcproj_repl(dllbin, staticbin) + if os.path.isfile(hashlib): # 2.6 + filerepl(hashlib, dllbin, staticbin) + + conf_flags = ['VC-WIN32'] + # fix idea compilation problems + conf_flags.append('disable-idea') + conf_flags.extend(['no-idea', 'no-mdc2', 'no-rc5']) + + build_repl('configure = "VC-WIN32"', 'configure = "%s"' % ' '.join(conf_flags)) + build_repl('if not os.path.isfile(makefile) or os.path.getsize(makefile)==0:', 'if True:') + + if PYTHON_VER == '25': + from buildutil import copy_different + copy_different('../../_ssl.mak.25.2008', '_ssl.mak') + else: + print >> sys.stderr, 'WARNING: _ssl.pyd will be built without debugging symbols' + +def clean(): + with cd(PYTHON_DIR, PCBUILD_DIR): + config = 'Debug' if DEBUG else 'Release' + if USE_DEVENV: + run(['devenv', 'pcbuild.sln', '/clean', "%s|Win32" % config, '/project', '_ssl'], checkret = False) + run(['devenv', 'pcbuild.sln', '/clean', "%s|Win32" % config], checkret = False) + else: + run(['vcbuild', 'pcbuild.sln', '/clean', '%s|Win32' % config], checkret = False) + +def build(): + check_tools() + with cd(PYTHON_DIR, PCBUILD_DIR): + + # fix up files for SSL stuff + fix_build_ssl_py() + + config = 'Debug' if DEBUG else 'Release' + if USE_DEVENV: + run(['devenv', 'pcbuild.sln', '/build', "%s|Win32" % config, '/project', '_ssl'], checkret = False) + run(['devenv', 'pcbuild.sln', '/build', "%s|Win32" % config], checkret = False) + else: + run(['vcbuild', 'pcbuild.sln', '%s|Win32' % config], checkret = False) + + +def bench(pgo = None): + if pgo is None: + pgo = getattr(getattr(sys, 'opts', None), 'pgo', None) + if not DEBUG and pgo: + with cd(PYTHON_DIR): + run([PYTHON_EXE, 'Tools/pybench/pybench.py', '-f', '26.bench.txt']) + with cd(PCBUILD_DIR): + run(['devenv', 'pcbuild.sln', '/build', "PGInstrument|Win32", '/project', '_ssl'], checkret = False) + run(['devenv', 'pcbuild.sln', '/build', "PGInstrument|Win32"], checkret = False) + run([PYTHON_EXE_PGI, 'Tools/pybench/pybench.py', '-f', '26pgi.bench.txt']) + with cd(PCBUILD_DIR): + run(['devenv', 'pcbuild.sln', '/build', "PGUpdate|Win32", '/project', '_ssl'], checkret = False) + run(['devenv', 'pcbuild.sln', '/build', "PGUpdate|Win32"], checkret = False) + run([PYTHON_EXE_PGO, 'Tools/pybench/pybench.py', '-f', '26pgo.bench.txt']) + + +DPYTHON_DIR = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), 'dpython'))) + +def post_build(): + # copy pyconfig.h from PC/ to /Include and Python26.lib to make scripts + # using distutils work with our PGO build without modification + with cd(PYTHON_DIR): + from buildutil import copy_different + copy_different('PC/pyconfig.h', 'Include/pyconfig.h') + + + install_stdlib() + + with cd(PYTHON_DIR): + + # install python executables + with cd(PYTHON_LIBDIR): + pydir = DPYTHON_DIR + assert os.path.isdir(pydir), pydir + + def copylibs(): + for f in ('''python%(dbg)s.exe python%(dbg)s.pdb + pythonw%(dbg)s.exe pythonw%(dbg)s.pdb + python%(ver)s%(dbg)s.dll python%(ver)s%(dbg)s.pdb + sqlite3%(dbg)s.dll sqlite3%(dbg)s.pdb''' % dict(dbg=DEBUG_POSTFIX, + ver=PYTHON_VER)).split(): + copy_different(f, pydir) + if not DEBUG and getattr(getattr(sys, 'opts', None), 'pgo', None): + with cd(PYTHON_PGO_DIR): + copylibs() + else: + copylibs() + + dlls_dir = os.path.join(pydir, 'DLLs') + if not os.path.isdir(dlls_dir): + os.mkdir(dlls_dir) + + libs = '''_ctypes _elementtree _hashlib _socket _sqlite3 + _ssl bz2 pyexpat select unicodedata winsound'''.split() + + if int(PYTHON_VER) >= 26: + libs.append('_multiprocessing') + + for f in libs: + f += DEBUG_POSTFIX + copy_different(f + '.pyd', dlls_dir) + try: + copy_different(f + '.pdb', dlls_dir) + except Exception: + print 'WARNING: could not copy %s.pdb' % f + +def install_stdlib(): + 'Copies all the .py files in python/Lib' + + from buildutil import copy_different + + with cd(PYTHON_DIR): + exclude = '.xcopy.exclude' # have to specify xcopy excludes in a file + + with open(exclude, 'w') as fout: + fout.write('\n'.join(['.pyc', '.pyo', '.svn'])) + + try: + run(['xcopy', 'Lib', r'%s\lib' % DPYTHON_DIR, '/EXCLUDE:%s' % exclude, '/I','/E','/D','/Y']) + run(['xcopy', 'Include', r'%s\Include' % DPYTHON_DIR, '/EXCLUDE:%s' % exclude, '/I','/E','/D','/Y']) + if not os.path.isdir(r'%s\libs' % DPYTHON_DIR): + os.makedirs(r'%s\libs' % DPYTHON_DIR) + for f in os.listdir(r'PCBuild'): + if f.endswith('.lib'): + copy_different(os.path.join('PCBuild', f), os.path.join(r'%s\libs' % DPYTHON_DIR, f)) + finally: + os.remove(exclude) + +def test(): + # check that python is there + + print + print 'using python', PYTHON_EXE + print 'current working directory is', os.getcwd() + print + + try: + run([PYTHON_EXE, '-c', 'import sys; sys.exit(0)']) + except Exception: + print_exc() + fatal('Error building Python executable') + + # check that we can import ssl + try: + run([PYTHON_EXE, '-c', 'import socket; socket.ssl; import sys; sys.exit(0)']) + except Exception: + fatal('error building SSL module') + + inform('\nPython built successfully!') + +def checkout_or_update(): + if not exists(PYTHON_DIR): + need_build = True + checkout() + else: + need_build = update() + + return need_build + +def main(): + parse_opts() + + usage = True + + if 'all' in sys._args: + need_build = checkout_or_update() + get_deps() + if need_build: + build() + bench() + + post_build() + test() + return 0 + + elif 'build' in sys._args: + usage = False + need_build = checkout_or_update() + get_deps() + if need_build: + build() + bench() + post_build() + + elif 'rebuild' in sys._args: + usage = False + clean() + checkout_or_update() + get_deps() + build() + bench() + post_build() + + elif 'test' in sys._args: + usage = False + test() + bench() + + elif 'install' in sys._args: + usage = False + post_build() + + elif 'copylib' in sys._args: + usage = False + install_stdlib() + elif 'checkout' in sys._args: + usage = False + checkout_or_update() + get_deps() + elif 'clean' in sys._args: + usage = False + clean() + + if usage: + print >> sys.stderr, USAGE_MSG + return 1 + + return 0 + +def parse_opts(): + import optparse + parser = optparse.OptionParser() + + optimimize_group = optparse.OptionGroup(parser, 'Optimization Options') + optimimize_group.add_option('--no-computed-goto','--no-computed_goto','--no_computed-goto','--no_computed_goto', + help = 'do not apply computed goto', + dest = 'use_computed_goto', action='store_false') + optimimize_group.add_option('--no-intel', '--no_intel', dest = 'intel', action='store_false', + help = 'do not attempt use of the intel compiler') + optimimize_group.add_option('--no-pgo', '--no_pgo', dest = 'pgo', action='store_false', + help = 'do not run PGO') + + optimized_build = not VS_EXPRESS + parser.set_defaults(use_computed_goto = optimized_build, + intel = optimized_build, + pgo = optimized_build) + + parser.add_option_group(optimimize_group) + + sys.opts, sys._args = parser.parse_args() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/digsby/build/msw/build_speedups.py b/digsby/build/msw/build_speedups.py new file mode 100644 index 0000000..82bd92e --- /dev/null +++ b/digsby/build/msw/build_speedups.py @@ -0,0 +1,36 @@ +#__LICENSE_GOES_HERE__ +from __future__ import with_statement +import sys +sys.path.append('..') +from buildutil import dpy, fatal, cd, DEPS_DIR, DEBUG +from os.path import join as pathjoin, abspath, isdir, dirname + +EXTDIR = pathjoin(dirname(__file__), '../../ext') + +from compiledeps import libxml2_dirname +from build_libxml2 import iconv_dirname + +distutils_debug = '--debug' if DEBUG else '' + +def build_xmlextra(): + '''Builds _xmlextra.pyd, a speedups module for pyxmpp.''' + + libxml2_dir = abspath(libxml2_dirname) + iconv_dir = abspath(iconv_dirname) + + lxml2_libs = pathjoin(libxml2_dir, 'win32', 'bin.msvc') + lxml2_inc = pathjoin(libxml2_dir, 'include') + + with cd(EXTDIR): + import os + dpy(['build_xmlextra.py', 'clean']) + dpy(['build_xmlextra.py', 'build_ext'] + ([distutils_debug] if distutils_debug else []) + [ + '-L' + os.pathsep.join(filter(None, [lxml2_libs] + os.environ.get('LIB', '').split(os.pathsep))), + '-I' + os.pathsep.join(filter(None, [iconv_dir, lxml2_inc] + os.environ.get('INCLUDE', '').split(os.pathsep))), + 'install', '--install-lib', DEPS_DIR]) + +def main(): + build_xmlextra() + +if __name__ == '__main__': + main() diff --git a/digsby/build/msw/build_updater.py b/digsby/build/msw/build_updater.py new file mode 100644 index 0000000..3e8bca8 --- /dev/null +++ b/digsby/build/msw/build_updater.py @@ -0,0 +1,108 @@ +#__LICENSE_GOES_HERE__ +''' +builds Digsby's windows autoupdater, and places resulting +binaries in digsby/ext/msw +''' + +from __future__ import with_statement +import sys +sys.path.append('..') # for buildutil +sys.path.append('../../lib') # for path +from buildutil import run, cd +from path import path +from buildutil.signing import Authenticode + +# +# configuration +# + +# location of digsby source +DIGSBY_DIR = path('../../').abspath() + +# location of updater project files +UPDATER_PROJECT_DIR = DIGSBY_DIR / 'lib/AutoUpdate/src/Digsby Update/msvc2008' +assert UPDATER_PROJECT_DIR.isdir(), 'could not find %s' % UPDATER_PROJECT_DIR +UPDATER_SOLUTION = 'Digsby Update MSVC2008.sln' + +# exes and pdbs end up here +BINARIES_DEST_DIR = DIGSBY_DIR / 'ext/msw' + +# check that DIGSBY_DIR is defined correctly +assert (DIGSBY_DIR / 'res').isdir() and \ + (DIGSBY_DIR / 'src').isdir() and \ + (DIGSBY_DIR / 'Digsby.py').isfile(), "invalid Digsby dir" + +# check that the solution files are where we said they are +UPDATER_PATH = (UPDATER_PROJECT_DIR / UPDATER_SOLUTION).abspath() +assert UPDATER_PATH.isfile(), 'could not find solution %s' % UPDATER_PATH + +artifacts = ['Digsby PreUpdater.exe', 'Digsby PreUpdater.pdb', + 'Digsby Updater.exe', 'Digsby Updater.pdb'] + +def update(): + 'Updates digsby to trunk' + + with cd(DIGSBY_DIR): + run(['svn', 'update']) + +def build(): + # output the version of mt.exe--should be > 6 + # run('mt /', checkret = False) + + 'Builds and deploys executables and debugging symbol files.' + with cd(UPDATER_PROJECT_DIR): + # remove the old Release directory + run(['cmd', '/c', 'rmdir', '/s', '/q', 'Release'], checkret = False) + + # invoke MSVC2008 + print run(['devenv', '/build', 'release', UPDATER_SOLUTION], checkret = False) + + bindir = UPDATER_PROJECT_DIR / 'Release' + if not (bindir / 'Digsby PreUpdater.exe').isfile(): + raise Exception('Visual Studio did not build the executables') + +def deploy(): + with cd(UPDATER_PROJECT_DIR / 'Release'): + dest = BINARIES_DEST_DIR.abspath() + + # deploy EXEs and PDBs to BINARIES_DEST_DIR + for a in artifacts: + p = path(a) + print 'copying %s to %s' % (p, dest) + + destfile = dest / p.name + if destfile.isfile(): + print 'removing old %r' % destfile + destfile.remove() + assert not destfile.isfile() + + p.copy2(dest) + destfile = dest / p.name + assert destfile.isfile() + + # sign all executables + if a.lower().endswith('.exe'): + Authenticode(destfile) + + return dest + +def commit(): + print '\nEnter a commit message, or press enter to skip:', + message = raw_input() + + if message: + with cd(BINARIES_DEST_DIR): + run(['svn', 'commit'] + artifacts + ['-m', message]) + +def all(): + update() + build() + dest = deploy() + + print '\nSuccess!' + print '\nNew binaries are in %s' % dest + + commit() + +if __name__ == '__main__': + all() diff --git a/digsby/build/msw/build_webkit.py b/digsby/build/msw/build_webkit.py new file mode 100644 index 0000000..a25247e --- /dev/null +++ b/digsby/build/msw/build_webkit.py @@ -0,0 +1,321 @@ +#__LICENSE_GOES_HERE__ +''' + +builds webkit (and dependencies) + +''' + +from __future__ import with_statement + +import sys +if __name__ == '__main__': + try: + cfg = sys.argv.pop(1) + except IndexError: + cfg = 'release' + + if cfg not in ('release', 'debug', 'curl'): + print >> sys.stderr, 'Usage: python build_webkit.py release|debug [OPTS]' + sys.exit(1) + + if cfg == 'debug': + sys._build_debug = True + + + +import shutil +import os +sys.path.append('..') if '..' not in sys.path else None +from buildutil import run, cd, tardep, unzip, filerepl, git, inform, timed, DEBUG, copy_different, DEPS_DIR, get_patch_cmd +from os import makedirs, environ +from os.path import isdir, abspath, exists, join as pathjoin, isfile +from constants import TARBALL_URL, WXWEBKIT_GIT_REPO +from shutil import move + +thisdir = abspath(os.path.dirname(__file__)) + +zlib = tardep('http://symbolsystem.com/build/msvc2008/', 'zlib-1.2.3', '.zip', 629830) + +patch_cmd = get_patch_cmd() + +icu = tardep('http://download.icu-project.org/files/icu4c/49.1.2/', 'icu4c-49_1_2-Win32-msvc10', '.zip', 9327628, dirname='icu') + +# WARNING: if the cURL version changes, make sure that the accompanying patch file (curl_patch) is updated +curl = tardep('http://curl.haxx.se/download/', 'curl-7.18.1', '.tar.bz2', 1700966) + +# this patch hacks cURL to assume all URLS are UTF-8 encoded (which is true when they are coming from webkit, +# our only user of cURL. this fixes problems when accessing file:/// paths on the hard disk under non-english +# locales). applied only for windows. +curl_patch = 'curl_utf8_urls.diff' + +jpeg = tardep('http://www.ijg.org/files/', 'jpegsrc.v7', '.tar.gz', 960379, dirname = 'jpeg-7') + +WEBKITDIR = 'WebKit' +WEBKIT_GIT_REMOTE = 'mini' +WEBKIT_GIT_REPO = WXWEBKIT_GIT_REPO +WEBKIT_GIT_BRANCH = 'master' + +WEBKIT_USE_GC = False # whether or not to use the wxGraphicsContext path + +def get_wxdir(): + "WebKit's build scripts need a WXWIN environment variable to find wxWidgets." + + from build_wx import WXDIR + WXDIR = abspath(WXDIR) + return WXDIR + + if 'WXWIN' in environ: + # already set in the enviornment + inform('WXWIN is %s' % environ['WXWIN']) + return environ + else: + # wasn't already set--use the path set in build_wx.py + from build_wx import WXDIR + WXDIR = abspath(WXDIR) + inform('setting environment variable WXWIN=%s' % WXDIR) + environ['WXWIN'] = WXDIR + +def checkout(): + if isdir(WEBKITDIR): + inform('skipping checkout, %s already exists' % WEBKITDIR) + else: + inform('cloning WebKit') + + makedirs(WEBKITDIR) + with cd(WEBKITDIR): + git.run(['init']) + + # turn off auto CRLF conversion, so that cygwin bash + # doesn't complain about line endings + git.run(['config', 'core.autocrlf', 'false']) + + # Add our remote source + git.run(['remote', 'add', WEBKIT_GIT_REMOTE, WEBKIT_GIT_REPO]) + git.run(['fetch', WEBKIT_GIT_REMOTE, '--depth', '1']) + + # checkout a local tracking branch of the remote branch we're interested in + with timed(): + git.run(['checkout', '-b', WEBKIT_GIT_BRANCH, WEBKIT_GIT_REMOTE + '/' + WEBKIT_GIT_BRANCH]) + +def update(): + print 'skipping webkit update' + return + + with cd(WEBKITDIR): + git.run(['pull', WEBKIT_GIT_REMOTE, WEBKIT_GIT_BRANCH]) + + +def build_zlib(): + copy_different('../../../builddeps/msvc2008/' + zlib.filename, '.') + unzip(zlib.filename) + + with cd('zlib-1.2.3'): + lib_dest_path = os.getcwd() + with cd('projects/visualc9-x86'): + configname = 'DLL ASM %s' % ('Debug' if DEBUG else 'Release') + run(['vcbuild', 'zlib.vcproj', configname]) + + # install the ZLIB dll and pdb + print 'DEPS_DIR here', DEPS_DIR + with cd('Win32/' + configname): + debug_flag = 'd' if DEBUG else '' + + for dest in (DEPS_DIR, lib_dest_path): + copy_different('zlib1%s.dll' % debug_flag, dest) + copy_different('zlib1%s.pdb' % debug_flag, dest) + copy_different('zlib1%s.lib' % debug_flag, dest) + +def build_png(): + pass + +def build_jpeg(): + with cd(jpeg.get()): + # setup for nmake build + shutil.copy2('jconfig.vc', 'jconfig.h') + + # make sure libjpeg is built against the multithreaded C runtime + # library. + run(['nmake', '-f', 'makefile.vc', 'NODEBUG=' + ('0' if DEBUG else '1')]) + + lib = abspath('libjpeg.lib') + assert exists(lib) + return lib + +curl_pfix = 'd' if DEBUG else '' + +def build_curl(): + # relies on openssl-0.9.8g being in msw/ (should be there from + # building Python) + + new = not os.path.exists(curl.dirname) + + with cd(curl.get()): + if os.name == 'nt' and new: + # after pulling a fresh tarball, apply the patch pointed to by curl_patch. + # see the note above for an explanation. + run([patch_cmd, '-p0', '-i', '../' + curl_patch]) + + filerepl('lib/Makefile.vc6', + '/O2 /DNDEBUG', + '/Zi /Ox /GL /GS- /GR- /DNDEBUG') + + filerepl('lib/Makefile.vc6', + 'LFLAGS = /nologo /machine:$(MACHINE)', + 'LFLAGS = /DEBUG /nologo /machine:$(MACHINE)') + + openssl_includes = "../../../../../digsby-venv" + + # point at includes + filerepl('lib/Makefile.vc6', + '/DUSE_SSLEAY /I "$(OPENSSL_PATH)/inc32"', + '/DUSE_SSLEAY /I "%s/PC" /I "%s/PC/openssl" /I "$(OPENSSL_PATH)/inc32"' % (openssl_includes, openssl_includes)) + + # point at .libs + filerepl('lib/Makefile.vc6', + 'LFLAGSSSL = "/LIBPATH:$(OPENSSL_PATH)\out32dll"', + 'LFLAGSSSL = "/LIBPATH:%s/libs"' % openssl_includes) + + with cd('lib'): + run(['nmake', '/nologo', '/E', '/f', 'Makefile.vc6', 'cfg=%s-dll-ssl-dll-zlib-dll' % ('debug' if DEBUG else 'release'), + 'ZLIBLIBSDLL=zlib1%s.lib' % ('d' if DEBUG else ''), + 'IMPLIB_NAME=libcurl', + 'IMPLIB_NAME_DEBUG=libcurld'] + ) + + # copy libcurl.dll to digsby/build/msw/dependencies + from buildutil import copy_different, DEPS_DIR + + copy_different('libcurl%s.dll' % curl_pfix, DEPS_DIR) + copy_different('libcurl%s.pdb' % curl_pfix, DEPS_DIR) + +CYGWIN_PATH = 'c:\\cygwin' + +def cygwin(cmd, env=None): + run([CYGWIN_PATH + '\\bin\\bash', '--login', '-c', cmd], env=env) + +def cygfix(path): + return r'"`cygpath -d \"' + path + r'\"`"' + +from contextlib import contextmanager + +@contextmanager +def _cygwin_env(): + # cygwin's link.exe and python.exe can get in the way of webkit's build + # process so move them out of the way temporarily. + + bad_files = [ + '\\bin\\link.exe' + ] + + badfiles = [] + for f in bad_files: + badfile = CYGWIN_PATH + f + if isfile(badfile): + badfile_moved = badfile + '.webkitbuilding' + move(badfile, badfile_moved) + badfiles.append(badfile) + + try: + yield + finally: + for badfile in badfiles: + move(badfile + '.webkitbuilding', badfile) + +def _setup_icu(): + # ICU + icudir = icu.get() + + # copy icu binaries into DEPS_DIR + icu_bindir = os.path.join(icudir, 'bin') + for f in os.listdir(icu_bindir): + copy_different(os.path.join(icu_bindir, f), DEPS_DIR) + + return icudir + +def build_webkit(cfg): + wxdir = get_wxdir() + assert isdir(wxdir) + assert cfg in ('debug', 'release', 'curl') + + webkit_scripts = abspath(WEBKITDIR + '/WebKitTools/Scripts') + assert isdir(webkit_scripts), "not a directory: " + webkit_scripts + + # passing things through to cygwin will mangle backslashes, so fix them + # here. + webkit_scripts = cygfix(webkit_scripts) + wxdir = cygfix(wxdir) + + libdir = os.path.join(abspath(WEBKITDIR), 'WebKitLibraries/msvc2008/win/lib') + incdir = os.path.join(abspath(WEBKITDIR), 'WebKitLibraries/msvc2008/win/include') + + icudir = _setup_icu() + + os.environ['WEBKIT_ICU_REPLACEMENT'] = os.path.join(thisdir, icudir) + os.environ['WEBKIT_CURL_REPLACEMENT'] = abspath(os.path.join(curl.dirname, 'lib', 'libcurl%s.lib' % curl_pfix)) + os.environ['WEBKIT_JPEG_REPLACEMENT'] = abspath(os.path.join(jpeg.dirname)) + + with _cygwin_env(): + cygwin('cd %s && ./set-webkit-configuration --%s' % (webkit_scripts, cfg)) + + cygprefix = 'cd %s && PATH="%s:$PATH" WXWIN=%s' % (webkit_scripts, '/cygdrive/c/python26', wxdir) + + wkopts = 'wxgc' if WEBKIT_USE_GC else '' + + with timed('building webkit'): + build_scripts_py = os.path.join(abspath(WEBKITDIR), 'WebKitTools/wx/build') + assert os.path.isdir(build_scripts_py), build_scripts_py + env=dict(os.environ) + env.update(PYTHONPATH=cygfix(build_scripts_py), PATH=';'.join([r'c:\python26',os.environ['PATH']])) + + def run_cygwin(s): + cygwin(cygprefix + ' ' + s + (' --wx %s' % wkopts), env=env) + + if '--test' in sys.argv: + run_cygwin('./run-webkit-tests --wx') + elif '--clean' in sys.argv: + run_cygwin('./build-webkit --wx --clean') + else: + run_cygwin('./build-webkit --wx wxpython') + + +def install(cfg): + 'Installs all necessary binaries.' + + if False: + copy_different('openssl-0.9.8g/out32dll/libeay32.dll', DEPS_DIR) + copy_different('openssl-0.9.8g/out32dll/ssleay32.dll', DEPS_DIR) + + bindir = pathjoin(WEBKITDIR, 'WebKitBuild', 'Release' if cfg == 'release' else 'Debug') + + for dll in 'wxwebkit'.split(): + for ext in ('.pdb', '.dll'): + src, dest = pathjoin(bindir, dll + ext), DEPS_DIR + if ext == '.pdb' and not isfile(src): + continue + copy_different(src, dest) + + copy_different(pathjoin(WEBKITDIR, 'WebKitLibraries/win/lib', 'pthreadVC2.dll'), DEPS_DIR) + + print 'wxwebkit.dll is %.2fMB' % (os.path.getsize(pathjoin(bindir, 'wxwebkit.dll')) / 1024.0 / 1024.0) + + +def build(cfg): + if not isdir(WEBKITDIR): + checkout() + update() + + if cfg == 'curl': + build_curl() + else: + build_zlib() + build_curl() + build_jpeg() + build_webkit(cfg) + if not '--clean' in sys.argv: + install(cfg) + +def main(): + build(cfg) + +if __name__ == '__main__': + main() diff --git a/digsby/build/msw/build_wx.py b/digsby/build/msw/build_wx.py new file mode 100644 index 0000000..b033dcd --- /dev/null +++ b/digsby/build/msw/build_wx.py @@ -0,0 +1,303 @@ +#__LICENSE_GOES_HERE__ +''' + +builds wxWidgets with our customizations + +''' + +from __future__ import with_statement + +from os.path import abspath + +import sys; sys.path.append('..') + +WX_GIT_REPO = 'https://github.com/kevinw/wx.git' +WX_GIT_BRANCH = 'cairo' + +WXWIDGETS_28_SVN_DIR = "http://svn.wxwidgets.org/svn/wx/wxWidgets/branches/wxWebKitBranch-2.8" + +WXDIR = 'wxWidgets' + +WXPYTHON_28_SVN_DIR = "http://svn.wxwidgets.org/svn/wx/wxPython/branches/WX_2_8_BRANCH" +WXPYDIR = 'wxPython' + +WXWEBKIT_SVN_DIR = "http://svn.webkit.org/repository/webkit/trunk" + +from buildutil import DEBUG, git, timed +from constants import wxconfig, setup_h_use_flags + + +CONTRIB = dict( + STC = True +) + + +WX_MAKEFILE_ARGS = dict( + BUILD = 'debug' if wxconfig.debug_runtime else 'release', + OFFICIAL_BUILD = 1, # don't append _custom to DLLs + SHARED = 1, # make DLLs + MONOLITHIC = 0, # split build into several DLLs + + USE_RTTI = 1, + USE_HTML = 1 if wxconfig.html else 0, + + USE_EXCEPTIONS = int(wxconfig.exceptions), + + USE_GDIPLUS = 1, # wxGraphicsContext support + USE_OPENGL = 0, # OpenGL canvas support + USE_MEDIA = 0, + USE_XRC = 0, + USE_QA = 0, + USE_AUI = 0, + USE_RICHTEXT = 0, + #USE_CAIRO = 1, + + UNICODE = 1, + MSLU = 0, + + DEBUG_FLAG = 1 if wxconfig.debug_assertions else 0, + WXDEBUGFLAG = 'd' if wxconfig.debug_runtime else 'h', + CXXFLAGS = ['/D__NO_VC_CRTDBG__'] if wxconfig.debug_assertions and not wxconfig.debug_runtime else [], + LDFLAGS = ['/OPT:REF', '/OPT:ICF'], +) + +wxargs = WX_MAKEFILE_ARGS + +wxargs['CXXFLAGS'].extend(['/GS-']) + +if wxconfig.debug_symbols: + wxargs['CXXFLAGS'].append('/Zi') + wxargs['LDFLAGS'].extend(['/DEBUG']) + +if wxconfig.whole_program_optimization: + wxargs['CXXFLAGS'].append('/GL') + wxargs['LDFLAGS'].append('/LTCG') + +if wxconfig.exceptions: + wxargs['CXXFLAGS'].append('/EHa') + +if wxconfig.disable_all_optimization: + wxargs['CXXFLAGS'].append('/Od') + +CYGWIN_DIR = 'c:\\cygwin' + +SWIG_MSG = 'Wrong or missing SWIG on PATH: please install from http://wxpython.wxcommunity.com/tools/' + +import os +import sys +if not '..' in sys.path: + sys.path.append('..') # for buildutil +from buildutil import run, cd, inform, fatal +from os.path import exists, isdir, isfile, join as pathjoin + +def checkout(): + if exists(WXDIR): + inform('already exists: %s' % WXDIR) + else: + #inform('checking out wxWebKitBranch-2.8...') + #run(['svn', 'checkout', WXWIDGETS_28_SVN_DIR, WXDIR]) + + inform('cloning wxWebKitBranch-2.8') + git.run(['clone', WX_GIT_REPO, abspath(WXDIR).replace('\\', '/'), '--depth=1']) + with cd(WXDIR): + # don't change newlines + git.run(['config', 'core.autocrlf', 'false']) + + if WX_GIT_BRANCH != 'master': + git.run(['fetch', 'origin']) + # checkout our branch. + git.run(['checkout', '-b', WX_GIT_BRANCH, 'origin/%s' % WX_GIT_BRANCH]) + assert exists(WXDIR) + +def update(): + with cd(WXDIR): + inform('updating wxWidgets...') + #run(['svn', 'up']) + git.run(['checkout', WX_GIT_BRANCH]) + git.run(['pull', 'origin', WX_GIT_BRANCH]) + +def copy_setup_h(): + inform('copying include/wx/msw/setup0.h to include/wx/msw/setup.h with modifications') + + with cd(WXDIR, 'include', 'wx', 'msw'): + + # setup0.h is the "template" for setup.h. we use the setup_h_use_flags + # dictionary above to set certain values in it for things we want to + # customize (results in smaller binaries, since about 50% of wx we don't + # use or need) + f = open('setup0.h', 'rU') + out = open('setup.h', 'w') + + flags = dict(setup_h_use_flags) + define_use = '#define wxUSE_' + for line in f: + i = line.find(define_use) + if i != -1: + use_name = line[i+len(define_use):].split()[0] + if use_name in flags: + line = '%s%s %s\n' % (define_use, use_name, flags.pop(use_name)) + + out.write(line) + + # make sure there are no leftover flags + if flags: + leftover = '\n'.join(' wxUSE_%s' % key for key in flags.iterkeys()) + raise AssertionError('invalid wxUSE_XXX flags (were not found in setup0.h):\n%s' % leftover) + + out.close() + f.close() + +def bakefile(): + with cd(WXDIR, 'build', 'bakefiles'): + run(['bakefile_gen', '-f', 'msvc']) + +def build(force_bakefile = False): + abs_wxdir = abspath(WXDIR) + + os.environ.update( + WXWIN = abspath(WXDIR), + WXDIR = abspath(WXDIR), + #CAIRO_ROOT = abspath(pathjoin(WXDIR, 'external', 'cairo-dev')), + ) + + copy_setup_h() + check_bakefile() + + msw_makefile = pathjoin(WXDIR, 'build', 'msw', 'makefile.vc') + + do_bakefile = False + if force_bakefile: + inform('forcing bakefile_gen') + do_bakefile = True + elif not isfile(msw_makefile): + inform('makefile.vc missing, running bakefile') + do_bakefile = True + + if do_bakefile: + bakefile() + assert isfile(msw_makefile), "running bakefile_gen did not create %s" % msw_makefile + + make_cmd = ['nmake', '-f', 'makefile.vc'] + stropts(WX_MAKEFILE_ARGS) + + # build WX main libraries + with cd(WXDIR, 'build', 'msw'): + run(make_cmd) + + # build contribs + if CONTRIB['STC']: + with cd(WXDIR, 'contrib', 'build', 'stc'): + run(make_cmd) + + inform('wxWidgets built successfully in %s' % abspath(WXDIR)) + + # install + from buildutil import copy_different, DEPS_DIR + + bindir = pathjoin(abspath(WXDIR), 'lib', 'vc_dll') + + for dll in ('base28%s_net_vc base28%s_vc msw28%s_adv_vc ' + 'msw28%s_core_vc msw28%s_stc_vc').split(): + + dll = dll % ('u' + WX_MAKEFILE_ARGS['WXDEBUGFLAG']) + for ext in ('.dll', '.pdb'): + src, dest = pathjoin(bindir, 'wx' + dll + ext), DEPS_DIR + copy_different(src, dest) + +def check_swig(): + 'Checks for the correct version of SWIG.' + + try: + run(['swig', '-version'], checkret = False) + except Exception: + fatal(SWIG_MSG) + + if not 'SWIG_LIB' in os.environ: + fatal("ERROR: no SWIG_LIB\n" + "please set SWIG_LIB in your environment to the Lib folder " + "underneath Swig's installation folder") + +def check_bakefile(): + 'Checks for bakefile on PATH.' + + try: + run(['bakefile', '--version']) + except Exception: + fatal('bakefile is not on PATH: please fix, or download and install from http://www.bakefile.org/download.html') + +def fix_newlines(filename): + "Makes a file's newlines UNIX style." + + with open(filename, 'rb') as f: + s = f.read() + + s = s.replace('\r\n', '\n').replace('\r', '\n') + + with open(filename, 'wb') as f: + f.write(s) + +def stropts(opts): + elems = [] + for k, v in opts.iteritems(): + if isinstance(v, (list, tuple)): + v = '%s' % (' '.join('%s' % e for e in v)) + + elems.append('%s=%s' % (k, v)) + + return elems + +def clean(): + if os.name == 'nt': + def rmdir(p): + p = os.path.abspath(p) + if os.path.isdir(p): + run(['cmd', '/c', 'rmdir', '/s', '/q', p], checkret=False) + + with cd(WXDIR, 'lib'): + rmdir('vc_dll') + + # TODO: don't hardcode this debug flag + dir = 'vc_mswu%sdll' % WX_MAKEFILE_ARGS['WXDEBUGFLAG'] + + with cd(WXDIR, 'build', 'msw'): + rmdir(dir) + + with cd(WXDIR, 'contrib', 'build', 'stc'): + rmdir(dir) + else: + raise AssertionError('clean not implemented for %s' % os.name) + +def all(force_bakefile = False): + if not isdir(WXDIR): + checkout() + + update() + build(force_bakefile) + +def main(): + force_bakefile = False + if '--force-bakefile' in sys.argv: + sys.argv.remove('--force-bakefile') + force_bakefile = True + + funcs = dict(clean = clean, + rebuild = lambda: (clean(), build(force_bakefile)), + checkout = checkout, + update = update, + build = build, + all = lambda: all(force_bakefile)) + + + if not len(sys.argv) == 2 or sys.argv[1] not in funcs: + print >> sys.stderr, 'python build_wx.py [--DEBUG] [--force-bakefile] [checkout|update|build|rebuild|all|clean]' + return 1 + + funcname = sys.argv[1] + + with timed(funcname): + funcs[funcname]() + +if __name__ == '__main__': + from traceback import print_exc + try: sys.exit(main()) + except SystemExit: raise + except: print_exc(); sys.exit(1) diff --git a/digsby/build/msw/comtypes.0.6.0.patch b/digsby/build/msw/comtypes.0.6.0.patch new file mode 100644 index 0000000..878dccc --- /dev/null +++ b/digsby/build/msw/comtypes.0.6.0.patch @@ -0,0 +1,80 @@ +Index: comtypes/__init__.py +=================================================================== +--- comtypes/__init__.py (.../comtypes) (revision 21063) ++++ comtypes/__init__.py (.../comtypes) (revision 21063) +@@ -144,14 +144,15 @@ + logger.debug("CoInitializeEx(None, %s)", flags) + _ole32.CoInitializeEx(None, flags) + +-# COM is initialized automatically for the thread that imports this ++# COM is NOT initialized automatically for the thread that imports this + # module for the first time. sys.coinit_flags is passed as parameter + # to CoInitializeEx, if defined, otherwise COINIT_APARTMENTTHREADED + # (COINIT_MULTITHREADED on Windows CE) is used. + # + # A shutdown function is registered with atexit, so that + # CoUninitialize is called when Python is shut down. +-CoInitializeEx() ++def initialize(flags=None): ++ CoInitializeEx() + + # We need to have CoUninitialize for multithreaded model where we have + # to initialize and uninitialize COM for every new thread (except main) +Index: comtypes/_comobject.py +=================================================================== +--- comtypes/_comobject.py (.../comtypes) (revision 21063) ++++ comtypes/_comobject.py (.../comtypes) (revision 21063) +@@ -51,7 +51,7 @@ + def _do_implement(interface_name, method_name): + def _not_implemented(*args): + """Return E_NOTIMPL because the method is not implemented.""" +- _debug("unimplemented method %s_%s called", interface_name, method_name) ++ # removed log: _debug("unimplemented method %s_%s called", interface_name, method_name) + return E_NOTIMPL + return _not_implemented + +@@ -426,8 +426,8 @@ + if result == 1: + # keep reference to the object in a class variable. + COMObject._instances_[self] = None +- _debug("%d active COM objects: Added %r", len(COMObject._instances_), self) +- _debug("%r.AddRef() -> %s", self, result) ++ # removed log: _debug("%d active COM objects: Added %r", len(COMObject._instances_), self) ++ # removed log: _debug("%r.AddRef() -> %s", self, result) + return result + + def IUnknown_Release(self, this, +@@ -438,16 +438,18 @@ + # have been deleted already - so we supply it as default + # argument. + result = __InterlockedDecrement(self._refcnt) +- _debug("%r.Release() -> %s", self, result) ++ #_debug("%r.Release() -> %s", self, result) + if result == 0: + # For whatever reasons, at cleanup it may be that + # COMObject is already cleaned (set to None) + try: + del COMObject._instances_[self] + except AttributeError: +- _debug("? active COM objects: Removed %r", self) ++ # removed log: _debug("? active COM objects: Removed %r", self) ++ pass + else: +- _debug("%d active COM objects: Removed %r", len(COMObject._instances_), self) ++ # removed log: _debug("%d active COM objects: Removed %r", len(COMObject._instances_), self) ++ pass + if self._factory is not None: + self._factory.LockServer(None, 0) + return result +@@ -460,9 +462,9 @@ + ptr = self._com_pointers_.get(iid, None) + if ptr is not None: + # CopyComPointer(src, dst) calls AddRef! +- _debug("%r.QueryInterface(%s) -> S_OK", self, iid) ++ #_debug("%r.QueryInterface(%s) -> S_OK", self, iid) + return CopyComPointer(ptr, ppvObj) +- _debug("%r.QueryInterface(%s) -> E_NOINTERFACE", self, iid) ++ #_debug("%r.QueryInterface(%s) -> E_NOINTERFACE", self, iid) + return E_NOINTERFACE + + def QueryInterface(self, interface): diff --git a/digsby/build/msw/curl_utf8_urls.diff b/digsby/build/msw/curl_utf8_urls.diff new file mode 100644 index 0000000..ae8f4af --- /dev/null +++ b/digsby/build/msw/curl_utf8_urls.diff @@ -0,0 +1,43 @@ +--- lib/file.c 2009-03-17 17:05:16.677172800 -0400 ++++ lib/newfile.c 2009-03-17 17:19:33.654549300 -0400 +@@ -35,6 +35,7 @@ + #include + #include + #include ++#include + #else + #ifdef HAVE_SYS_SOCKET_H + #include +@@ -188,7 +189,9 @@ + int fd; + #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) + int i; ++ int wchar_len; + char *actual_path; ++ wchar_t* outbuf = 0; + #endif + + if(!real_path) +@@ -246,7 +249,21 @@ + if(actual_path[i] == '/') + actual_path[i] = '\\'; + +- fd = open(actual_path, O_RDONLY | O_BINARY); /* no CR/LF translation! */ ++ /* assume URL encoded paths are UTF-8 and use _wopen if possible */ ++ wchar_len = sizeof(wchar_t) * MultiByteToWideChar(CP_UTF8, 0, actual_path, -1, NULL, 0); ++ ++ if (wchar_len && (outbuf = malloc(wchar_len)) && ++ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, actual_path, -1, outbuf, wchar_len)) ++ { ++ /* replace the old string with a new wchar_t one */ ++ curl_free(real_path); ++ ++ real_path = actual_path = (char*)outbuf; ++ fd = _wopen(outbuf, O_RDONLY | O_BINARY); ++ } else { ++ fd = open(actual_path, O_RDONLY | O_BINARY); ++ } ++ + file->path = actual_path; + #else + fd = open(real_path, O_RDONLY); diff --git a/digsby/build/msw/dotSyntax-AuthenticodeCert.pfx b/digsby/build/msw/dotSyntax-AuthenticodeCert.pfx new file mode 100644 index 0000000..2c4c978 Binary files /dev/null and b/digsby/build/msw/dotSyntax-AuthenticodeCert.pfx differ diff --git a/digsby/build/msw/imaplib_ragged_eofs.patch b/digsby/build/msw/imaplib_ragged_eofs.patch new file mode 100644 index 0000000..876cb2e --- /dev/null +++ b/digsby/build/msw/imaplib_ragged_eofs.patch @@ -0,0 +1,13 @@ +Index: Lib/imaplib.py +=================================================================== +--- Lib/imaplib.py (revision 22028) ++++ Lib/imaplib.py (working copy) +@@ -1147,7 +1147,7 @@ + self.port = port + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((host, port)) +- self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile) ++ self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, suppress_ragged_eofs=False) + + + def read(self, size): diff --git a/digsby/build/msw/libxml2.setup.py.msvc2008 b/digsby/build/msw/libxml2.setup.py.msvc2008 new file mode 100644 index 0000000..2f67036 --- /dev/null +++ b/digsby/build/msw/libxml2.setup.py.msvc2008 @@ -0,0 +1,245 @@ +#!/usr/bin/python -u +# +# Setup script for libxml2 and libxslt if found +# +import sys, os, os.path +from distutils.core import setup, Extension + +# Below ROOT, we expect to find include, include/libxml2, lib and bin. +# On *nix, it is not needed (but should not harm), +# on Windows, it is set by configure.js. +ROOT = r'@prefix@' + +# Thread-enabled libxml2 +with_threads = @WITH_THREADS@ + +# If this flag is set (windows only), +# a private copy of the dlls are included in the package. +# If this flag is not set, the libxml2 and libxslt +# dlls must be found somewhere in the PATH at runtime. +WITHDLLS = 1 and sys.platform.startswith('win') + +def missing(file): + if os.access(file, os.R_OK) == 0: + return 1 + return 0 + +try: + HOME = os.environ['HOME'] +except: + HOME="C:" + +if WITHDLLS: + # libxml dlls (expected in ROOT/bin) + dlls = [ 'iconv.dll','libxml2.dll','libxslt.dll','libexslt.dll' ] + dlls = map(lambda dll: os.path.join(ROOT,'bin',dll),dlls) + + # create __init__.py for the libxmlmods package + if not os.path.exists("libxmlmods"): + os.mkdir("libxmlmods") + open("libxmlmods/__init__.py","w").close() + + def altImport(s): + s = s.replace("import libxml2mod","from libxmlmods import libxml2mod") + s = s.replace("import libxsltmod","from libxmlmods import libxsltmod") + return s + +if sys.platform.startswith('win'): + libraryPrefix = 'lib' + platformLibs = [] +else: + libraryPrefix = '' + platformLibs = ["m","z"] + +# those are examined to find +# - libxml2/libxml/tree.h +# - iconv.h +# - libxslt/xsltconfig.h +includes_dir = [ +"/usr/include", +"/usr/local/include", +"/opt/include", +os.path.join(ROOT,'include'), +HOME +]; + +xml_includes=os.path.abspath('../include') +for dir in includes_dir: + if not missing(dir + "/libxml2/libxml/tree.h"): + xml_includes=dir + "/libxml2" + break; + +if xml_includes == "": + print "failed to find headers for libxml2: update includes_dir" + sys.exit(1) + +iconv_includes=os.path.abspath('../../libiconv') +for dir in includes_dir: + if not missing(dir + "/iconv.h"): + iconv_includes=dir + break; + +if iconv_includes == "": + print "failed to find headers for libiconv: update includes_dir" + sys.exit(1) + +# those are added in the linker search path for libraries +libdirs = [ +os.path.join(ROOT,'lib'), +os.path.abspath('../win32/bin.msvc'), +] + +xml_files = ["libxml2-api.xml", "libxml2-python-api.xml", + "libxml.c", "libxml.py", "libxml_wrap.h", "types.c", + "xmlgenerator.py", "README", "TODO", "drv_libxml2.py"] + +xslt_files = ["libxslt-api.xml", "libxslt-python-api.xml", + "libxslt.c", "libxsl.py", "libxslt_wrap.h", + "xsltgenerator.py"] + +if missing("libxml2-py.c") or missing("libxml2.py"): + try: + try: + import xmlgenerator + except: + import generator + except: + print "failed to find and generate stubs for libxml2, aborting ..." + print sys.exc_type, sys.exc_value + sys.exit(1) + + head = open("libxml.py", "r") + generated = open("libxml2class.py", "r") + result = open("libxml2.py", "w") + for line in head.readlines(): + if WITHDLLS: + result.write(altImport(line)) + else: + result.write(line) + for line in generated.readlines(): + result.write(line) + head.close() + generated.close() + result.close() + +with_xslt=0 +if missing("libxslt-py.c") or missing("libxslt.py"): + if missing("xsltgenerator.py") or missing("libxslt-api.xml"): + print "libxslt stub generator not found, libxslt not built" + else: + try: + import xsltgenerator + except: + print "failed to generate stubs for libxslt, aborting ..." + print sys.exc_type, sys.exc_value + else: + head = open("libxsl.py", "r") + generated = open("libxsltclass.py", "r") + result = open("libxslt.py", "w") + for line in head.readlines(): + if WITHDLLS: + result.write(altImport(line)) + else: + result.write(line) + for line in generated.readlines(): + result.write(line) + head.close() + generated.close() + result.close() + with_xslt=1 +else: + with_xslt=1 + +if with_xslt == 1: + xslt_includes="" + for dir in includes_dir: + if not missing(dir + "/libxslt/xsltconfig.h"): + xslt_includes=dir + "/libxslt" + break; + + if xslt_includes == "": + print "failed to find headers for libxslt: update includes_dir" + with_xslt = 0 + + +descr = "libxml2 package" +modules = [ 'libxml2', 'drv_libxml2' ] +if WITHDLLS: + modules.append('libxmlmods.__init__') +c_files = ['libxml2-py.c', 'libxml.c', 'types.c' ] +includes= [xml_includes, iconv_includes] +libs = [libraryPrefix + "xml2"] + platformLibs +macros = [] +if with_threads: + macros.append(('_REENTRANT','1')) +if with_xslt == 1: + descr = "libxml2 and libxslt package" + if not sys.platform.startswith('win'): + # + # We are gonna build 2 identical shared libs with merge initializing + # both libxml2mod and libxsltmod + # + c_files = c_files + ['libxslt-py.c', 'libxslt.c'] + xslt_c_files = c_files + macros.append(('MERGED_MODULES', '1')) + else: + # + # On windows the MERGED_MODULE option is not needed + # (and does not work) + # + xslt_c_files = ['libxslt-py.c', 'libxslt.c', 'types.c'] + libs.insert(0, libraryPrefix + 'exslt') + libs.insert(0, libraryPrefix + 'xslt') + includes.append(xslt_includes) + modules.append('libxslt') + + +extens=[Extension('libxmlmods.libxml2mod', c_files, include_dirs=includes, + library_dirs=libdirs, + libraries=libs, define_macros=macros, + extra_compile_args=['/Zi', '/GL'], + extra_link_args=['/DEBUG', '/LTCG'])] +if with_xslt == 1: + extens.append(Extension('libxsltmod', xslt_c_files, include_dirs=includes, + library_dirs=libdirs, + libraries=libs, define_macros=macros)) + +if missing("MANIFEST"): + + manifest = open("MANIFEST", "w") + manifest.write("setup.py\n") + for file in xml_files: + manifest.write(file + "\n") + if with_xslt == 1: + for file in xslt_files: + manifest.write(file + "\n") + manifest.close() + +if WITHDLLS: + ext_package = "libxmlmods" + if sys.version >= "2.2": + base = "lib/site-packages/" + else: + base = "" + data_files = []#[(base+"libxmlmods",dlls)] +else: + ext_package = None + data_files = [] + +setup (name = "libxml2-python", + # On *nix, the version number is created from setup.py.in + # On windows, it is set by configure.js + version = "@LIBXML_VERSION@", + description = descr, + author = "Daniel Veillard", + author_email = "veillard@redhat.com", + url = "http://xmlsoft.org/python.html", + licence="MIT Licence", + py_modules=modules, + ext_modules=extens, + ext_package=ext_package, + data_files=data_files, + ) + +sys.exit(0) + diff --git a/digsby/build/msw/m2crypto.setup.py.msvc2008 b/digsby/build/msw/m2crypto.setup.py.msvc2008 new file mode 100644 index 0000000..d4ccc81 --- /dev/null +++ b/digsby/build/msw/m2crypto.setup.py.msvc2008 @@ -0,0 +1,152 @@ +#!/usr/bin/env python + +""" +Distutils/setuptools installer for M2Crypto. + +Copyright (c) 1999-2004, Ng Pheng Siong. All rights reserved. + +Portions created by Open Source Applications Foundation (OSAF) are +Copyright (C) 2004-2007 OSAF. All Rights Reserved. +""" + +import os, sys +try: + from setuptools import setup + from setuptools.command import build_ext +except ImportError: + from distutils.core import setup + from distutils.command import build_ext + +from distutils.core import Extension + + +class _M2CryptoBuildExt(build_ext.build_ext): + '''Specialization of build_ext to enable swig_opts to inherit any + include_dirs settings made at the command line or in a setup.cfg file''' + user_options = build_ext.build_ext.user_options + \ + [('openssl=', 'o', 'Prefix for openssl installation location')] + + def initialize_options(self): + '''Overload to enable custom openssl settings to be picked up''' + + build_ext.build_ext.initialize_options(self) + + # openssl is the attribute corresponding to openssl directory prefix + # command line option + if os.name == 'nt': + self.libraries = ['ssleay32', 'libeay32'] + self.openssl = 'c:\\pkg' + else: + self.libraries = ['ssl', 'crypto'] + self.openssl = '/usr' + + + def finalize_options(self): + '''Overloaded build_ext implementation to append custom openssl + include file and library linking options''' + + build_ext.build_ext.finalize_options(self) + + opensslIncludeDir = os.path.join(self.openssl, 'include') + opensslLibraryDir = os.path.join(self.openssl, 'lib') + + self.swig_opts = ['-I%s' % i for i in self.include_dirs + \ + [opensslIncludeDir]] + self.swig_opts.append('-includeall') + #self.swig_opts.append('-D__i386__') # Uncomment for early OpenSSL 0.9.7 versions, or on Fedora Core if build fails + #self.swig_opts.append('-DOPENSSL_NO_EC') # Try uncommenting if you can't build with EC disabled + + self.include_dirs += [os.path.join(self.openssl, opensslIncludeDir), + os.path.join(os.getcwd(), 'SWIG')] + + if sys.platform == 'cygwin': + # Cygwin SHOULD work (there's code in distutils), but + # if one first starts a Windows command prompt, then bash, + # the distutils code does not seem to work. If you start + # Cygwin directly, then it would work even without this change. + # Someday distutils will be fixed and this won't be needed. + self.library_dirs += [os.path.join(self.openssl, 'bin')] + + self.library_dirs += [os.path.join(self.openssl, opensslLibraryDir)] + + +if sys.version_info < (2,4): + + # This copy of swig_sources is from Python 2.2. + + def swig_sources (self, sources): + + """Walk the list of source files in 'sources', looking for SWIG + interface (.i) files. Run SWIG on all that are found, and + return a modified 'sources' list with SWIG source files replaced + by the generated C (or C++) files. + """ + + new_sources = [] + swig_sources = [] + swig_targets = {} + + # XXX this drops generated C/C++ files into the source tree, which + # is fine for developers who want to distribute the generated + # source -- but there should be an option to put SWIG output in + # the temp dir. + + if self.swig_cpp: + target_ext = '.cpp' + else: + target_ext = '.c' + + for source in sources: + (base, ext) = os.path.splitext(source) + if ext == ".i": # SWIG interface file + new_sources.append(base + target_ext) + swig_sources.append(source) + swig_targets[source] = new_sources[-1] + else: + new_sources.append(source) + + if not swig_sources: + return new_sources + + swig = self.find_swig() + swig_cmd = [swig, "-python", "-ISWIG"] + if self.swig_cpp: + swig_cmd.append("-c++") + + swig_cmd += self.swig_opts + + for source in swig_sources: + target = swig_targets[source] + self.announce("swigging %s to %s" % (source, target)) + self.spawn(swig_cmd + ["-o", target, source]) + + return new_sources + + build_ext.build_ext.swig_sources = swig_sources + + +m2crypto = Extension(name = 'M2Crypto.__m2crypto', + sources = ['SWIG/_m2crypto.i'], + extra_compile_args = ['-DTHREADING', '/GL', '/Zi', '/GS-', '/Os'], + extra_link_args = ['/DEBUG', '/OPT:REF', '/OPT:ICF'], + libraries=['User32', 'Advapi32', 'Gdi32', 'Ws2_32'], + #extra_link_args = ['-Wl,-search_paths_first'], # Uncomment to build Universal Mac binaries + + ) + +setup(name = 'M2Crypto', + version = '0.18.2', + description = 'M2Crypto: A Python crypto and SSL toolkit', + long_description = 'M2Crypto is a wrapper for OpenSSL using SWIG.', + license = 'BSD-style license', + platforms = ['any'], + author = 'Ng Pheng Siong', + author_email = 'ngps@netmemetic.com', + maintainer = 'Heikki Toivonen', + maintainer_email = 'heikki@osafoundation.org', + url = 'http://wiki.osafoundation.org/bin/view/Projects/MeTooCrypto', + packages = ['M2Crypto', 'M2Crypto.SSL', 'M2Crypto.PGP'], + ext_modules = [m2crypto], + test_suite='tests.alltests.suite', + cmdclass = {'build_ext': _M2CryptoBuildExt} + ) diff --git a/digsby/build/msw/m2crypto.setup.py.msvc2008.debug b/digsby/build/msw/m2crypto.setup.py.msvc2008.debug new file mode 100644 index 0000000..fe0fc9c --- /dev/null +++ b/digsby/build/msw/m2crypto.setup.py.msvc2008.debug @@ -0,0 +1,157 @@ +#!/usr/bin/env python + +""" +Distutils/setuptools installer for M2Crypto. + +Copyright (c) 1999-2004, Ng Pheng Siong. All rights reserved. + +Portions created by Open Source Applications Foundation (OSAF) are +Copyright (C) 2004-2007 OSAF. All Rights Reserved. +""" + +import os, sys +try: + from setuptools import setup + from setuptools.command import build_ext +except ImportError: + from distutils.core import setup + from distutils.command import build_ext + +from distutils.core import Extension + + +class _M2CryptoBuildExt(build_ext.build_ext): + '''Specialization of build_ext to enable swig_opts to inherit any + include_dirs settings made at the command line or in a setup.cfg file''' + user_options = build_ext.build_ext.user_options + \ + [('openssl=', 'o', 'Prefix for openssl installation location')] + + def initialize_options(self): + '''Overload to enable custom openssl settings to be picked up''' + + build_ext.build_ext.initialize_options(self) + + # openssl is the attribute corresponding to openssl directory prefix + # command line option + if os.name == 'nt': + self.libraries = ['ssleay32', 'libeay32'] + self.openssl = '..\\msw\\openssl-0.9.8g' + else: + self.libraries = ['ssl', 'crypto'] + self.openssl = '/usr' + + + def finalize_options(self): + '''Overloaded build_ext implementation to append custom openssl + include file and library linking options''' + + build_ext.build_ext.finalize_options(self) + + opensslIncludeDir = os.path.abspath(os.path.join(self.openssl, 'include')) + opensslLibraryDir = os.path.abspath(os.path.join(self.openssl, 'out32dll')) + + print + print opensslIncludeDir + print opensslLibraryDir + print + + self.swig_opts = ['-I%s' % i for i in self.include_dirs + \ + [opensslIncludeDir]] + self.swig_opts.append('-includeall') + #self.swig_opts.append('-D__i386__') # Uncomment for early OpenSSL 0.9.7 versions, or on Fedora Core if build fails + #self.swig_opts.append('-DOPENSSL_NO_EC') # Try uncommenting if you can't build with EC disabled + + self.include_dirs += [os.path.join(self.openssl, opensslIncludeDir), + os.path.join(os.getcwd(), 'SWIG')] + + if sys.platform == 'cygwin': + # Cygwin SHOULD work (there's code in distutils), but + # if one first starts a Windows command prompt, then bash, + # the distutils code does not seem to work. If you start + # Cygwin directly, then it would work even without this change. + # Someday distutils will be fixed and this won't be needed. + self.library_dirs += [os.path.join(self.openssl, 'bin')] + + self.library_dirs += [os.path.join(self.openssl, opensslLibraryDir)] + + +if sys.version_info < (2,4): + + # This copy of swig_sources is from Python 2.2. + + def swig_sources (self, sources): + + """Walk the list of source files in 'sources', looking for SWIG + interface (.i) files. Run SWIG on all that are found, and + return a modified 'sources' list with SWIG source files replaced + by the generated C (or C++) files. + """ + + new_sources = [] + swig_sources = [] + swig_targets = {} + + # XXX this drops generated C/C++ files into the source tree, which + # is fine for developers who want to distribute the generated + # source -- but there should be an option to put SWIG output in + # the temp dir. + + if self.swig_cpp: + target_ext = '.cpp' + else: + target_ext = '.c' + + for source in sources: + (base, ext) = os.path.splitext(source) + if ext == ".i": # SWIG interface file + new_sources.append(base + target_ext) + swig_sources.append(source) + swig_targets[source] = new_sources[-1] + else: + new_sources.append(source) + + if not swig_sources: + return new_sources + + swig = self.find_swig() + swig_cmd = [swig, "-python", "-ISWIG"] + if self.swig_cpp: + swig_cmd.append("-c++") + + swig_cmd += self.swig_opts + + for source in swig_sources: + target = swig_targets[source] + self.announce("swigging %s to %s" % (source, target)) + self.spawn(swig_cmd + ["-o", target, source]) + + return new_sources + + build_ext.build_ext.swig_sources = swig_sources + + +m2crypto = Extension(name = 'M2Crypto.__m2crypto', + sources = ['SWIG/_m2crypto.i'], + extra_compile_args = ['-DTHREADING', '/Zi', '/Od', '/D_DEBUG'], + extra_link_args = ['/DEBUG'], + libraries=['User32', 'Advapi32', 'Gdi32', 'Ws2_32'], + #extra_link_args = ['-Wl,-search_paths_first'], # Uncomment to build Universal Mac binaries + + ) + +setup(name = 'M2Crypto', + version = '0.18.2', + description = 'M2Crypto: A Python crypto and SSL toolkit', + long_description = 'M2Crypto is a wrapper for OpenSSL using SWIG.', + license = 'BSD-style license', + platforms = ['any'], + author = 'Ng Pheng Siong', + author_email = 'ngps@netmemetic.com', + maintainer = 'Heikki Toivonen', + maintainer_email = 'heikki@osafoundation.org', + url = 'http://wiki.osafoundation.org/bin/view/Projects/MeTooCrypto', + packages = ['M2Crypto', 'M2Crypto.SSL', 'M2Crypto.PGP'], + ext_modules = [m2crypto], + test_suite='tests.alltests.suite', + cmdclass = {'build_ext': _M2CryptoBuildExt} + ) diff --git a/digsby/build/msw/python-common-controls.patch b/digsby/build/msw/python-common-controls.patch new file mode 100644 index 0000000..b3a7194 --- /dev/null +++ b/digsby/build/msw/python-common-controls.patch @@ -0,0 +1,24 @@ +Index: Modules/python.c +=================================================================== +--- Modules/python.c (revision 72606) ++++ Modules/python.c (working copy) +@@ -1,5 +1,7 @@ + /* Minimal main program -- everything is loaded from the library */ + ++#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") ++ + #include "Python.h" + + #ifdef __FreeBSD__ +Index: PC/WinMain.c +=================================================================== +--- PC/WinMain.c (revision 72606) ++++ PC/WinMain.c (working copy) +@@ -1,5 +1,7 @@ + /* Minimal main program -- everything is loaded from the library. */ + ++#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") ++ + #include "Python.h" + + #define WIN32_LEAN_AND_MEAN diff --git a/digsby/build/msw/python26-assert-mode.patch b/digsby/build/msw/python26-assert-mode.patch new file mode 100644 index 0000000..42cb2e1 --- /dev/null +++ b/digsby/build/msw/python26-assert-mode.patch @@ -0,0 +1,307 @@ +Index: Include/fileobject.h +=================================================================== +--- Include/fileobject.h (revision 69494) ++++ Include/fileobject.h (revision 69495) +@@ -68,6 +68,17 @@ + */ + int _PyFile_SanitizeMode(char *mode); + ++#if defined _MSC_VER && _MSC_VER >= 1400 ++/* A routine to check if a file descriptor is valid on Windows. Returns 0 ++ * and sets errno to EBADF if it isn't. This is to avoid Assertions ++ * from various functions in the Windows CRT beginning with ++ * Visual Studio 2005 ++ */ ++int _PyVerify_fd(int fd); ++#else ++#define _PyVerify_fd(A) (1) /* dummy */ ++#endif ++ + #ifdef __cplusplus + } + #endif +Index: Objects/exceptions.c +=================================================================== +--- Objects/exceptions.c (revision 69494) ++++ Objects/exceptions.c (revision 69495) +@@ -1972,29 +1972,7 @@ + if (PyDict_SetItemString(bdict, # TYPE, PyExc_ ## TYPE)) \ + Py_FatalError("Module dictionary insertion problem."); + +-#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) +-/* crt variable checking in VisualStudio .NET 2005 */ +-#include + +-static int prevCrtReportMode; +-static _invalid_parameter_handler prevCrtHandler; +- +-/* Invalid parameter handler. Sets a ValueError exception */ +-static void +-InvalidParameterHandler( +- const wchar_t * expression, +- const wchar_t * function, +- const wchar_t * file, +- unsigned int line, +- uintptr_t pReserved) +-{ +- /* Do nothing, allow execution to continue. Usually this +- * means that the CRT will set errno to EINVAL +- */ +-} +-#endif +- +- + PyMODINIT_FUNC + _PyExc_Init(void) + { +@@ -2153,13 +2131,6 @@ + } + + Py_DECREF(bltinmod); +- +-#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) +- /* Set CRT argument error handler */ +- prevCrtHandler = _set_invalid_parameter_handler(InvalidParameterHandler); +- /* turn off assertions in debug mode */ +- prevCrtReportMode = _CrtSetReportMode(_CRT_ASSERT, 0); +-#endif + } + + void +@@ -2167,9 +2138,4 @@ + { + Py_XDECREF(PyExc_MemoryErrorInst); + PyExc_MemoryErrorInst = NULL; +-#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) +- /* reset CRT error handling */ +- _set_invalid_parameter_handler(prevCrtHandler); +- _CrtSetReportMode(_CRT_ASSERT, prevCrtReportMode); +-#endif + } +Index: Modules/_fileio.c +=================================================================== +--- Modules/_fileio.c (revision 69494) ++++ Modules/_fileio.c (revision 69495) +@@ -127,7 +127,7 @@ + { + #if defined(HAVE_FSTAT) + struct stat buf; +- if (fstat(fd, &buf) < 0 && errno == EBADF) { ++ if (!_PyVerify_fd(fd) || (fstat(fd, &buf) < 0 && errno == EBADF)) { + PyObject *exc; + char *msg = strerror(EBADF); + exc = PyObject_CallFunction(PyExc_OSError, "(is)", +Index: Modules/posixmodule.c +=================================================================== +--- Modules/posixmodule.c (revision 69494) ++++ Modules/posixmodule.c (revision 69495) +@@ -343,6 +343,109 @@ + #endif + #endif + ++#if defined _MSC_VER && _MSC_VER >= 1400 ++/* Microsoft CRT in VS2005 and higher will verify that a filehandle is ++ * valid and throw an assertion if it isn't. ++ * Normally, an invalid fd is likely to be a C program error and therefore ++ * an assertion can be useful, but it does contradict the POSIX standard ++ * which for write(2) states: ++ * "Otherwise, -1 shall be returned and errno set to indicate the error." ++ * "[EBADF] The fildes argument is not a valid file descriptor open for ++ * writing." ++ * Furthermore, python allows the user to enter any old integer ++ * as a fd and should merely raise a python exception on error. ++ * The Microsoft CRT doesn't provide an official way to check for the ++ * validity of a file descriptor, but we can emulate its internal behaviour ++ * by using the exported __pinfo data member and knowledge of the ++ * internal structures involved. ++ * The structures below must be updated for each version of visual studio ++ * according to the file internal.h in the CRT source, until MS comes ++ * up with a less hacky way to do this. ++ * (all of this is to avoid globally modifying the CRT behaviour using ++ * _set_invalid_parameter_handler() and _CrtSetReportMode()) ++ */ ++#if _MSC_VER >= 1500 /* VS 2008 */ ++typedef struct { ++ intptr_t osfhnd; ++ char osfile; ++ char pipech; ++ int lockinitflag; ++ CRITICAL_SECTION lock; ++#ifndef _SAFECRT_IMPL ++ char textmode : 7; ++ char unicode : 1; ++ char pipech2[2]; ++ __int64 startpos; ++ BOOL utf8translations; ++ char dbcsBuffer; ++ BOOL dbcsBufferUsed; ++#endif /* _SAFECRT_IMPL */ ++ } ioinfo; ++#elif _MSC_VER >= 1400 /* VS 2005 */ ++typedef struct { ++ intptr_t osfhnd; ++ char osfile; ++ char pipech; ++ int lockinitflag; ++ CRITICAL_SECTION lock; ++#ifndef _SAFECRT_IMPL ++ char textmode : 7; ++ char unicode : 1; ++ char pipech2[2]; ++ __int64 startpos; ++ BOOL utf8translations; ++#endif /* _SAFECRT_IMPL */ ++ } ioinfo; ++#endif ++ ++extern __declspec(dllimport) ioinfo * __pioinfo[]; ++#define IOINFO_L2E 5 ++#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E) ++#define IOINFO_ARRAYS 64 ++#define _NHANDLE_ (IOINFO_ARRAYS * IOINFO_ARRAY_ELTS) ++#define FOPEN 0x01 ++#define _NO_CONSOLE_FILENO (intptr_t)-2 ++ ++/* This function emulates what the windows CRT does to validate file handles */ ++int ++_PyVerify_fd(int fd) ++{ ++ const int i1 = fd >> IOINFO_L2E; ++ const int i2 = fd & ((1 << IOINFO_L2E) - 1); ++ ++ /* See that it isn't a special CLEAR fileno */ ++ if (fd != _NO_CONSOLE_FILENO) { ++ /* Microsoft CRT would check that 0<=fd<_nhandle but we can't do that. Instead ++ * we check pointer validity and other info ++ */ ++ if (0 <= i1 && i1 < IOINFO_ARRAYS && __pioinfo[i1] != NULL) { ++ /* finally, check that the file is open */ ++ if (__pioinfo[i1][i2].osfile & FOPEN) ++ return 1; ++ } ++ } ++ errno = EBADF; ++ return 0; ++} ++ ++/* the special case of checking dup2. The target fd must be in a sensible range */ ++static int ++_PyVerify_fd_dup2(int fd1, int fd2) ++{ ++ if (!_PyVerify_fd(fd1)) ++ return 0; ++ if (fd2 == _NO_CONSOLE_FILENO) ++ return 0; ++ if ((unsigned)fd2 < _NHANDLE_) ++ return 1; ++ else ++ return 0; ++} ++#else ++/* dummy version. _PyVerify_fd() is already defined in fileobject.h */ ++#define _PyVerify_fd_dup2(A, B) (1) ++#endif ++ + /* Return a dictionary corresponding to the POSIX environment table */ + #ifdef WITH_NEXT_FRAMEWORK + /* On Darwin/MacOSX a shared library or framework has no access to +@@ -581,6 +684,8 @@ + fd = PyObject_AsFileDescriptor(fdobj); + if (fd < 0) + return NULL; ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + res = (*func)(fd); + Py_END_ALLOW_THREADS +@@ -6188,6 +6293,8 @@ + int fd, res; + if (!PyArg_ParseTuple(args, "i:close", &fd)) + return NULL; ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + res = close(fd); + Py_END_ALLOW_THREADS +@@ -6210,7 +6317,8 @@ + return NULL; + Py_BEGIN_ALLOW_THREADS + for (i = fd_from; i < fd_to; i++) +- close(i); ++ if (_PyVerify_fd(i)) ++ close(i); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; + } +@@ -6226,6 +6334,8 @@ + int fd; + if (!PyArg_ParseTuple(args, "i:dup", &fd)) + return NULL; ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + fd = dup(fd); + Py_END_ALLOW_THREADS +@@ -6245,6 +6355,8 @@ + int fd, fd2, res; + if (!PyArg_ParseTuple(args, "ii:dup2", &fd, &fd2)) + return NULL; ++ if (!_PyVerify_fd_dup2(fd, fd2)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + res = dup2(fd, fd2); + Py_END_ALLOW_THREADS +@@ -6289,6 +6401,8 @@ + if (PyErr_Occurred()) + return NULL; + ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + #if defined(MS_WIN64) || defined(MS_WINDOWS) + res = _lseeki64(fd, pos, how); +@@ -6325,6 +6439,8 @@ + buffer = PyString_FromStringAndSize((char *)NULL, size); + if (buffer == NULL) + return NULL; ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + n = read(fd, PyString_AsString(buffer), size); + Py_END_ALLOW_THREADS +@@ -6351,6 +6467,8 @@ + + if (!PyArg_ParseTuple(args, "is*:write", &fd, &pbuf)) + return NULL; ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + size = write(fd, pbuf.buf, (size_t)pbuf.len); + Py_END_ALLOW_THREADS +@@ -6377,6 +6495,8 @@ + /* on OpenVMS we must ensure that all bytes are written to the file */ + fsync(fd); + #endif ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + res = FSTAT(fd, &st); + Py_END_ALLOW_THREADS +@@ -6419,6 +6539,8 @@ + PyMem_FREE(mode); + return NULL; + } ++ if (!_PyVerify_fd(fd)) ++ return posix_error(); + Py_BEGIN_ALLOW_THREADS + #if !defined(MS_WINDOWS) && defined(HAVE_FCNTL_H) + if (mode[0] == 'a') { +@@ -6458,6 +6580,8 @@ + int fd; + if (!PyArg_ParseTuple(args, "i:isatty", &fd)) + return NULL; ++ if (!_PyVerify_fd(fd)) ++ return PyBool_FromLong(0); + return PyBool_FromLong(isatty(fd)); + } + diff --git a/digsby/build/msw/python26-computed-goto.patch b/digsby/build/msw/python26-computed-goto.patch new file mode 100644 index 0000000..bf8631c --- /dev/null +++ b/digsby/build/msw/python26-computed-goto.patch @@ -0,0 +1,1749 @@ +Index: Python/makeopcodetargets.py +=================================================================== +--- Python/makeopcodetargets.py (revision 0) ++++ Python/makeopcodetargets.py (revision 0) +@@ -0,0 +1,44 @@ ++#! /usr/bin/env python ++"""Generate C code for the jump table of the threaded code interpreter ++(GCC only). ++""" ++ ++import imp ++import os ++ ++ ++def find_module(modname): ++ """Finds and returns a module in the local dist/checkout. ++ """ ++ modpath = os.path.join( ++ os.path.dirname(os.path.dirname(__file__)), "Lib") ++ return imp.load_module(modname, *imp.find_module(modname, [modpath])) ++ ++def write_contents(f): ++ """Write C code contents to the target file object. ++ """ ++ opcode = find_module("opcode") ++ targets = ['_unknown_opcode'] * 256 ++ for opname, op in opcode.opmap.items(): ++ if opname == "STOP_CODE": ++ # XXX opcode not implemented ++ continue ++ targets[op] = "TARGET_%s" % opname ++ f.write("static void *opcode_targets[256] = {\n") ++ f.write(",\n".join("\t&&%s" % s for s in targets)) ++ f.write("\n};\n") ++ ++ ++if __name__ == "__main__": ++ import sys ++ assert len(sys.argv) < 3, "Too many arguments" ++ if len(sys.argv) == 2: ++ target = sys.argv[1] ++ else: ++ target = "Python/opcode_targets.h" ++ f = open(target, "w") ++ try: ++ write_contents(f) ++ finally: ++ f.close() ++ + +Property changes on: Python\makeopcodetargets.py +___________________________________________________________________ +Added: svn:eol-style + + native + +Index: Python/opcode_targets.h +=================================================================== +--- Python/opcode_targets.h (revision 0) ++++ Python/opcode_targets.h (revision 0) +@@ -0,0 +1,258 @@ ++static void *opcode_targets[256] = { ++ &&_unknown_opcode, ++ &&TARGET_POP_TOP, ++ &&TARGET_ROT_TWO, ++ &&TARGET_ROT_THREE, ++ &&TARGET_DUP_TOP, ++ &&TARGET_ROT_FOUR, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_NOP, ++ &&TARGET_UNARY_POSITIVE, ++ &&TARGET_UNARY_NEGATIVE, ++ &&TARGET_UNARY_NOT, ++ &&TARGET_UNARY_CONVERT, ++ &&_unknown_opcode, ++ &&TARGET_UNARY_INVERT, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_LIST_APPEND, ++ &&TARGET_BINARY_POWER, ++ &&TARGET_BINARY_MULTIPLY, ++ &&TARGET_BINARY_DIVIDE, ++ &&TARGET_BINARY_MODULO, ++ &&TARGET_BINARY_ADD, ++ &&TARGET_BINARY_SUBTRACT, ++ &&TARGET_BINARY_SUBSCR, ++ &&TARGET_BINARY_FLOOR_DIVIDE, ++ &&TARGET_BINARY_TRUE_DIVIDE, ++ &&TARGET_INPLACE_FLOOR_DIVIDE, ++ &&TARGET_INPLACE_TRUE_DIVIDE, ++ &&TARGET_SLICE, ++ &&TARGET_SLICE_1, ++ &&TARGET_SLICE_2, ++ &&TARGET_SLICE_3, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_STORE_SLICE, ++ &&TARGET_STORE_SLICE_1, ++ &&TARGET_STORE_SLICE_2, ++ &&TARGET_STORE_SLICE_3, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_DELETE_SLICE, ++ &&TARGET_DELETE_SLICE_1, ++ &&TARGET_DELETE_SLICE_2, ++ &&TARGET_DELETE_SLICE_3, ++ &&TARGET_STORE_MAP, ++ &&TARGET_INPLACE_ADD, ++ &&TARGET_INPLACE_SUBTRACT, ++ &&TARGET_INPLACE_MULTIPLY, ++ &&TARGET_INPLACE_DIVIDE, ++ &&TARGET_INPLACE_MODULO, ++ &&TARGET_STORE_SUBSCR, ++ &&TARGET_DELETE_SUBSCR, ++ &&TARGET_BINARY_LSHIFT, ++ &&TARGET_BINARY_RSHIFT, ++ &&TARGET_BINARY_AND, ++ &&TARGET_BINARY_XOR, ++ &&TARGET_BINARY_OR, ++ &&TARGET_INPLACE_POWER, ++ &&TARGET_GET_ITER, ++ &&_unknown_opcode, ++ &&TARGET_PRINT_EXPR, ++ &&TARGET_PRINT_ITEM, ++ &&TARGET_PRINT_NEWLINE, ++ &&TARGET_PRINT_ITEM_TO, ++ &&TARGET_PRINT_NEWLINE_TO, ++ &&TARGET_INPLACE_LSHIFT, ++ &&TARGET_INPLACE_RSHIFT, ++ &&TARGET_INPLACE_AND, ++ &&TARGET_INPLACE_XOR, ++ &&TARGET_INPLACE_OR, ++ &&TARGET_BREAK_LOOP, ++ &&TARGET_WITH_CLEANUP, ++ &&TARGET_LOAD_LOCALS, ++ &&TARGET_RETURN_VALUE, ++ &&TARGET_IMPORT_STAR, ++ &&TARGET_EXEC_STMT, ++ &&TARGET_YIELD_VALUE, ++ &&TARGET_POP_BLOCK, ++ &&TARGET_END_FINALLY, ++ &&TARGET_BUILD_CLASS, ++ &&TARGET_STORE_NAME, ++ &&TARGET_DELETE_NAME, ++ &&TARGET_UNPACK_SEQUENCE, ++ &&TARGET_FOR_ITER, ++ &&TARGET_LIST_APPEND, ++ &&TARGET_STORE_ATTR, ++ &&TARGET_DELETE_ATTR, ++ &&TARGET_STORE_GLOBAL, ++ &&TARGET_DELETE_GLOBAL, ++ &&TARGET_DUP_TOPX, ++ &&TARGET_LOAD_CONST, ++ &&TARGET_LOAD_NAME, ++ &&TARGET_BUILD_TUPLE, ++ &&TARGET_BUILD_LIST, ++ &&TARGET_BUILD_MAP, ++ &&TARGET_LOAD_ATTR, ++ &&TARGET_COMPARE_OP, ++ &&TARGET_IMPORT_NAME, ++ &&TARGET_IMPORT_FROM, ++ &&_unknown_opcode, ++ &&TARGET_JUMP_FORWARD, ++ &&TARGET_JUMP_IF_FALSE, ++ &&TARGET_JUMP_IF_TRUE, ++ &&TARGET_JUMP_ABSOLUTE, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_LOAD_GLOBAL, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_CONTINUE_LOOP, ++ &&TARGET_SETUP_LOOP, ++ &&TARGET_SETUP_EXCEPT, ++ &&TARGET_SETUP_FINALLY, ++ &&_unknown_opcode, ++ &&TARGET_LOAD_FAST, ++ &&TARGET_STORE_FAST, ++ &&TARGET_DELETE_FAST, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_RAISE_VARARGS, ++ &&TARGET_CALL_FUNCTION, ++ &&TARGET_MAKE_FUNCTION, ++ &&TARGET_BUILD_SLICE, ++ &&TARGET_MAKE_CLOSURE, ++ &&TARGET_LOAD_CLOSURE, ++ &&TARGET_LOAD_DEREF, ++ &&TARGET_STORE_DEREF, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&TARGET_CALL_FUNCTION_VAR, ++ &&TARGET_CALL_FUNCTION_KW, ++ &&TARGET_CALL_FUNCTION_VAR_KW, ++ &&TARGET_EXTENDED_ARG, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode, ++ &&_unknown_opcode ++}; + +Property changes on: Python\opcode_targets.h +___________________________________________________________________ +Added: svn:eol-style + + native + +Index: Makefile.pre.in +=================================================================== +--- Makefile.pre.in (revision 69125) ++++ Makefile.pre.in (working copy) +@@ -248,6 +248,16 @@ + + ########################################################################## + # Python ++ ++OPCODETARGETS_H= \ ++ $(srcdir)/Python/opcode_targets.h ++ ++OPCODETARGETGEN= \ ++ $(srcdir)/Python/makeopcodetargets.py ++ ++OPCODETARGETGEN_FILES= \ ++ $(OPCODETARGETGEN) $(srcdir)/Lib/opcode.py ++ + PYTHON_OBJS= \ + Python/_warnings.o \ + Python/Python-ast.o \ +@@ -571,6 +581,11 @@ + Objects/stringobject.o: $(srcdir)/Objects/stringobject.c \ + $(STRINGLIB_HEADERS) + ++$(OPCODETARGETS_H): $(OPCODETARGETGEN_FILES) ++ $(OPCODETARGETGEN) $(OPCODETARGETS_H) ++ ++Python/ceval.o: $(OPCODETARGETS_H) ++ + Python/formatter_unicode.o: $(srcdir)/Python/formatter_unicode.c \ + $(STRINGLIB_HEADERS) + +Index: Include/opcode.h +=================================================================== +--- Include/opcode.h (revision 69125) ++++ Include/opcode.h (working copy) +@@ -38,12 +38,21 @@ + + #define SLICE 30 + /* Also uses 31-33 */ ++#define SLICE_1 31 ++#define SLICE_2 32 ++#define SLICE_3 33 + + #define STORE_SLICE 40 + /* Also uses 41-43 */ ++#define STORE_SLICE_1 41 ++#define STORE_SLICE_2 42 ++#define STORE_SLICE_3 43 + + #define DELETE_SLICE 50 + /* Also uses 51-53 */ ++#define DELETE_SLICE_1 51 ++#define DELETE_SLICE_2 52 ++#define DELETE_SLICE_3 53 + + #define STORE_MAP 54 + #define INPLACE_ADD 55 +Index: Python/ceval.c +=================================================================== +--- Python/ceval.c (revision 69125) ++++ Python/ceval.c (working copy) +@@ -578,6 +578,102 @@ + char *filename; + #endif + ++/* Computed GOTOs, or ++ the-optimization-commonly-but-improperly-known-as-"threaded code" ++ using gcc's labels-as-values extension ++ (http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html). ++ ++ The traditional bytecode evaluation loop uses a "switch" statement, which ++ decent compilers will optimize as a single indirect branch instruction ++ combined with a lookup table of jump addresses. However, since the ++ indirect jump instruction is shared by all opcodes, the CPU will have a ++ hard time making the right prediction for where to jump next (actually, ++ it will be always wrong except in the uncommon case of a sequence of ++ several identical opcodes). ++ ++ "Threaded code" in contrast, uses an explicit jump table and an explicit ++ indirect jump instruction at the end of each opcode. Since the jump ++ instruction is at a different address for each opcode, the CPU will make a ++ separate prediction for each of these instructions, which is equivalent to ++ predicting the second opcode of each opcode pair. These predictions have ++ a much better chance to turn out valid, especially in small bytecode loops. ++ ++ A mispredicted branch on a modern CPU flushes the whole pipeline and ++ can cost several CPU cycles (depending on the pipeline depth), ++ and potentially many more instructions (depending on the pipeline width). ++ A correctly predicted branch, however, is nearly free. ++ ++ At the time of this writing, the "threaded code" version is up to 15-20% ++ faster than the normal "switch" version, depending on the compiler and the ++ CPU architecture. ++ ++ We enable the optimization on compilers that are known to support it, but ++ disable it if LLTRACE or DYNAMIC_EXECUTION_PROFILE is defined, because ++ it would render the measurements invalid. ++ ++ ++ NOTE: care must be taken that the compiler doesn't try to "optimize" the ++ indirect jumps by sharing them between all opcodes. If needed, gcc has a ++ dedicated flag for that, "-fno-crossjumping". ++*/ ++ ++#if (defined(__GNUC__) || defined (__SUNPRO_C)) \ ++ && !defined(DYNAMIC_EXECUTION_PROFILE) \ ++ && !defined(LLTRACE) ++#define USE_COMPUTED_GOTOS ++#endif ++ ++#ifdef USE_COMPUTED_GOTOS ++/* Import the static jump table */ ++#include "opcode_targets.h" ++ ++/* This macro is used when several opcodes defer to the same implementation ++ (e.g. SETUP_LOOP, SETUP_FINALLY) */ ++#define TARGET_WITH_IMPL(op, impl) \ ++ TARGET_##op: \ ++ opcode = op; \ ++ if (HAS_ARG(op)) \ ++ oparg = NEXTARG(); \ ++ case op: \ ++ goto impl; \ ++ ++#define TARGET(op) \ ++ TARGET_##op: \ ++ opcode = op; \ ++ if (HAS_ARG(op)) \ ++ oparg = NEXTARG(); \ ++ case op: ++ ++ ++#define DISPATCH() \ ++ { \ ++ /* Avoid multiple loads from _Py_Ticker despite `volatile` */ \ ++ int _tick = _Py_Ticker - 1; \ ++ _Py_Ticker = _tick; \ ++ if (_tick >= 0) { \ ++ FAST_DISPATCH(); \ ++ } \ ++ continue; \ ++ } ++#define FAST_DISPATCH() \ ++ { \ ++ if (!_Py_TracingPossible) { \ ++ f->f_lasti = INSTR_OFFSET(); \ ++ goto *opcode_targets[*next_instr++]; \ ++ } \ ++ goto fast_next_opcode; \ ++ } ++ ++#else ++#define TARGET(op) \ ++ case op: ++#define TARGET_WITH_IMPL(op, impl) \ ++ case op: ++#define DISPATCH() continue ++#define FAST_DISPATCH() goto fast_next_opcode ++#endif ++ ++ + /* Tuple access macros */ + + #ifndef Py_DEBUG +@@ -656,16 +752,23 @@ + predictions turned-on and interpret the results as if some opcodes + had been combined or turn-off predictions so that the opcode frequency + counter updates for both opcodes. ++ ++ Opcode prediction is disabled with threaded code, since the latter allows ++ the CPU to record separate branch prediction information for each ++ opcode. ++ + */ + +-#ifdef DYNAMIC_EXECUTION_PROFILE ++#if defined(DYNAMIC_EXECUTION_PROFILE) || defined(USE_COMPUTED_GOTOS) + #define PREDICT(op) if (0) goto PRED_##op ++#define PREDICTED(op) PRED_##op: ++#define PREDICTED_WITH_ARG(op) PRED_##op: + #else + #define PREDICT(op) if (*next_instr == op) goto PRED_##op +-#endif +- + #define PREDICTED(op) PRED_##op: next_instr++ + #define PREDICTED_WITH_ARG(op) PRED_##op: oparg = PEEKARG(); next_instr += 3 ++#endif ++ + + /* Stack manipulation macros */ + +@@ -958,56 +1061,56 @@ + + /* case STOP_CODE: this is an error! */ + +- case NOP: +- goto fast_next_opcode; ++ TARGET(NOP) ++ FAST_DISPATCH(); + +- case LOAD_FAST: ++ TARGET(LOAD_FAST) + x = GETLOCAL(oparg); + if (x != NULL) { + Py_INCREF(x); + PUSH(x); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + } + format_exc_check_arg(PyExc_UnboundLocalError, + UNBOUNDLOCAL_ERROR_MSG, + PyTuple_GetItem(co->co_varnames, oparg)); + break; + +- case LOAD_CONST: ++ TARGET(LOAD_CONST) + x = GETITEM(consts, oparg); + Py_INCREF(x); + PUSH(x); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + + PREDICTED_WITH_ARG(STORE_FAST); +- case STORE_FAST: ++ TARGET(STORE_FAST) + v = POP(); + SETLOCAL(oparg, v); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + + PREDICTED(POP_TOP); +- case POP_TOP: ++ TARGET(POP_TOP) + v = POP(); + Py_DECREF(v); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + +- case ROT_TWO: ++ TARGET(ROT_TWO) + v = TOP(); + w = SECOND(); + SET_TOP(w); + SET_SECOND(v); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + +- case ROT_THREE: ++ TARGET(ROT_THREE) + v = TOP(); + w = SECOND(); + x = THIRD(); + SET_TOP(w); + SET_SECOND(x); + SET_THIRD(v); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + +- case ROT_FOUR: ++ TARGET(ROT_FOUR) + u = TOP(); + v = SECOND(); + w = THIRD(); +@@ -1016,15 +1119,15 @@ + SET_SECOND(w); + SET_THIRD(x); + SET_FOURTH(u); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + +- case DUP_TOP: ++ TARGET(DUP_TOP) + v = TOP(); + Py_INCREF(v); + PUSH(v); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + +- case DUP_TOPX: ++ TARGET(DUP_TOPX) + if (oparg == 2) { + x = TOP(); + Py_INCREF(x); +@@ -1033,7 +1136,7 @@ + STACKADJ(2); + SET_TOP(x); + SET_SECOND(w); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + } else if (oparg == 3) { + x = TOP(); + Py_INCREF(x); +@@ -1045,84 +1148,84 @@ + SET_TOP(x); + SET_SECOND(w); + SET_THIRD(v); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + } + Py_FatalError("invalid argument to DUP_TOPX" + " (bytecode corruption?)"); + /* Never returns, so don't bother to set why. */ + break; + +- case UNARY_POSITIVE: ++ TARGET(UNARY_POSITIVE) + v = TOP(); + x = PyNumber_Positive(v); + Py_DECREF(v); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case UNARY_NEGATIVE: ++ TARGET(UNARY_NEGATIVE) + v = TOP(); + x = PyNumber_Negative(v); + Py_DECREF(v); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case UNARY_NOT: ++ TARGET(UNARY_NOT) + v = TOP(); + err = PyObject_IsTrue(v); + Py_DECREF(v); + if (err == 0) { + Py_INCREF(Py_True); + SET_TOP(Py_True); +- continue; ++ DISPATCH(); + } + else if (err > 0) { + Py_INCREF(Py_False); + SET_TOP(Py_False); + err = 0; +- continue; ++ DISPATCH(); + } + STACKADJ(-1); + break; + +- case UNARY_CONVERT: ++ TARGET(UNARY_CONVERT) + v = TOP(); + x = PyObject_Repr(v); + Py_DECREF(v); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case UNARY_INVERT: ++ TARGET(UNARY_INVERT) + v = TOP(); + x = PyNumber_Invert(v); + Py_DECREF(v); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_POWER: ++ TARGET(BINARY_POWER) + w = POP(); + v = TOP(); + x = PyNumber_Power(v, w, Py_None); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_MULTIPLY: ++ TARGET(BINARY_MULTIPLY) + w = POP(); + v = TOP(); + x = PyNumber_Multiply(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_DIVIDE: ++ TARGET(BINARY_DIVIDE) + if (!_Py_QnewFlag) { + w = POP(); + v = TOP(); +@@ -1130,42 +1233,42 @@ + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + } + /* -Qnew is in effect: fall through to + BINARY_TRUE_DIVIDE */ +- case BINARY_TRUE_DIVIDE: ++ TARGET(BINARY_TRUE_DIVIDE) + w = POP(); + v = TOP(); + x = PyNumber_TrueDivide(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_FLOOR_DIVIDE: ++ TARGET(BINARY_FLOOR_DIVIDE) + w = POP(); + v = TOP(); + x = PyNumber_FloorDivide(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_MODULO: ++ TARGET(BINARY_MODULO) + w = POP(); + v = TOP(); + x = PyNumber_Remainder(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_ADD: ++ TARGET(BINARY_ADD) + w = POP(); + v = TOP(); + if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) { +@@ -1192,10 +1295,10 @@ + skip_decref_vx: + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_SUBTRACT: ++ TARGET(BINARY_SUBTRACT) + w = POP(); + v = TOP(); + if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) { +@@ -1215,10 +1318,10 @@ + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_SUBSCR: ++ TARGET(BINARY_SUBSCR) + w = POP(); + v = TOP(); + if (PyList_CheckExact(v) && PyInt_CheckExact(w)) { +@@ -1239,60 +1342,60 @@ + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_LSHIFT: ++ TARGET(BINARY_LSHIFT) + w = POP(); + v = TOP(); + x = PyNumber_Lshift(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_RSHIFT: ++ TARGET(BINARY_RSHIFT) + w = POP(); + v = TOP(); + x = PyNumber_Rshift(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_AND: ++ TARGET(BINARY_AND) + w = POP(); + v = TOP(); + x = PyNumber_And(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_XOR: ++ TARGET(BINARY_XOR) + w = POP(); + v = TOP(); + x = PyNumber_Xor(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case BINARY_OR: ++ TARGET(BINARY_OR) + w = POP(); + v = TOP(); + x = PyNumber_Or(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case LIST_APPEND: ++ TARGET(LIST_APPEND) + w = POP(); + v = POP(); + err = PyList_Append(v, w); +@@ -1300,31 +1403,31 @@ + Py_DECREF(w); + if (err == 0) { + PREDICT(JUMP_ABSOLUTE); +- continue; ++ DISPATCH(); + } + break; + +- case INPLACE_POWER: ++ TARGET(INPLACE_POWER) + w = POP(); + v = TOP(); + x = PyNumber_InPlacePower(v, w, Py_None); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_MULTIPLY: ++ TARGET(INPLACE_MULTIPLY) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceMultiply(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_DIVIDE: ++ TARGET(INPLACE_DIVIDE) + if (!_Py_QnewFlag) { + w = POP(); + v = TOP(); +@@ -1332,42 +1435,42 @@ + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + } + /* -Qnew is in effect: fall through to + INPLACE_TRUE_DIVIDE */ +- case INPLACE_TRUE_DIVIDE: ++ TARGET(INPLACE_TRUE_DIVIDE) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceTrueDivide(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_FLOOR_DIVIDE: ++ TARGET(INPLACE_FLOOR_DIVIDE) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceFloorDivide(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_MODULO: ++ TARGET(INPLACE_MODULO) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceRemainder(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_ADD: ++ TARGET(INPLACE_ADD) + w = POP(); + v = TOP(); + if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) { +@@ -1394,10 +1497,10 @@ + skip_decref_v: + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_SUBTRACT: ++ TARGET(INPLACE_SUBTRACT) + w = POP(); + v = TOP(); + if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) { +@@ -1417,63 +1520,64 @@ + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_LSHIFT: ++ TARGET(INPLACE_LSHIFT) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceLshift(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_RSHIFT: ++ TARGET(INPLACE_RSHIFT) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceRshift(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_AND: ++ TARGET(INPLACE_AND) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceAnd(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_XOR: ++ TARGET(INPLACE_XOR) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceXor(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case INPLACE_OR: ++ TARGET(INPLACE_OR) + w = POP(); + v = TOP(); + x = PyNumber_InPlaceOr(v, w); + Py_DECREF(v); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case SLICE+0: +- case SLICE+1: +- case SLICE+2: +- case SLICE+3: ++ TARGET_WITH_IMPL(SLICE, _slice) ++ TARGET_WITH_IMPL(SLICE_1, _slice) ++ TARGET_WITH_IMPL(SLICE_2, _slice) ++ TARGET_WITH_IMPL(SLICE_3, _slice) ++ _slice: + if ((opcode-SLICE) & 2) + w = POP(); + else +@@ -1488,13 +1592,14 @@ + Py_XDECREF(v); + Py_XDECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case STORE_SLICE+0: +- case STORE_SLICE+1: +- case STORE_SLICE+2: +- case STORE_SLICE+3: ++ TARGET_WITH_IMPL(STORE_SLICE, _store_slice) ++ TARGET_WITH_IMPL(STORE_SLICE_1, _store_slice) ++ TARGET_WITH_IMPL(STORE_SLICE_2, _store_slice) ++ TARGET_WITH_IMPL(STORE_SLICE_3, _store_slice) ++ _store_slice: + if ((opcode-STORE_SLICE) & 2) + w = POP(); + else +@@ -1510,13 +1615,14 @@ + Py_DECREF(u); + Py_XDECREF(v); + Py_XDECREF(w); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case DELETE_SLICE+0: +- case DELETE_SLICE+1: +- case DELETE_SLICE+2: +- case DELETE_SLICE+3: ++ TARGET_WITH_IMPL(DELETE_SLICE, _delete_slice) ++ TARGET_WITH_IMPL(DELETE_SLICE_1, _delete_slice) ++ TARGET_WITH_IMPL(DELETE_SLICE_2, _delete_slice) ++ TARGET_WITH_IMPL(DELETE_SLICE_3, _delete_slice) ++ _delete_slice: + if ((opcode-DELETE_SLICE) & 2) + w = POP(); + else +@@ -1531,10 +1637,10 @@ + Py_DECREF(u); + Py_XDECREF(v); + Py_XDECREF(w); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case STORE_SUBSCR: ++ TARGET(STORE_SUBSCR) + w = TOP(); + v = SECOND(); + u = THIRD(); +@@ -1544,10 +1650,10 @@ + Py_DECREF(u); + Py_DECREF(v); + Py_DECREF(w); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case DELETE_SUBSCR: ++ TARGET(DELETE_SUBSCR) + w = TOP(); + v = SECOND(); + STACKADJ(-2); +@@ -1555,10 +1661,10 @@ + err = PyObject_DelItem(v, w); + Py_DECREF(v); + Py_DECREF(w); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case PRINT_EXPR: ++ TARGET(PRINT_EXPR) + v = POP(); + w = PySys_GetObject("displayhook"); + if (w == NULL) { +@@ -1582,11 +1688,11 @@ + Py_XDECREF(x); + break; + +- case PRINT_ITEM_TO: ++ TARGET(PRINT_ITEM_TO) + w = stream = POP(); + /* fall through to PRINT_ITEM */ + +- case PRINT_ITEM: ++ TARGET(PRINT_ITEM) + v = POP(); + if (stream == NULL || stream == Py_None) { + w = PySys_GetObject("stdout"); +@@ -1633,14 +1739,14 @@ + Py_XDECREF(stream); + stream = NULL; + if (err == 0) +- continue; ++ DISPATCH(); + break; + +- case PRINT_NEWLINE_TO: ++ TARGET(PRINT_NEWLINE_TO) + w = stream = POP(); + /* fall through to PRINT_NEWLINE */ + +- case PRINT_NEWLINE: ++ TARGET(PRINT_NEWLINE) + if (stream == NULL || stream == Py_None) { + w = PySys_GetObject("stdout"); + if (w == NULL) { +@@ -1666,7 +1772,7 @@ + #ifdef CASE_TOO_BIG + default: switch (opcode) { + #endif +- case RAISE_VARARGS: ++ TARGET(RAISE_VARARGS) + u = v = w = NULL; + switch (oparg) { + case 3: +@@ -1688,27 +1794,27 @@ + } + break; + +- case LOAD_LOCALS: ++ TARGET(LOAD_LOCALS) + if ((x = f->f_locals) != NULL) { + Py_INCREF(x); + PUSH(x); +- continue; ++ DISPATCH(); + } + PyErr_SetString(PyExc_SystemError, "no locals"); + break; + +- case RETURN_VALUE: ++ TARGET(RETURN_VALUE) + retval = POP(); + why = WHY_RETURN; + goto fast_block_end; + +- case YIELD_VALUE: ++ TARGET(YIELD_VALUE) + retval = POP(); + f->f_stacktop = stack_pointer; + why = WHY_YIELD; + goto fast_yield; + +- case EXEC_STMT: ++ TARGET(EXEC_STMT) + w = TOP(); + v = SECOND(); + u = THIRD(); +@@ -1721,7 +1827,7 @@ + Py_DECREF(w); + break; + +- case POP_BLOCK: ++ TARGET(POP_BLOCK) + { + PyTryBlock *b = PyFrame_BlockPop(f); + while (STACK_LEVEL() > b->b_level) { +@@ -1729,10 +1835,10 @@ + Py_DECREF(v); + } + } +- continue; ++ DISPATCH(); + + PREDICTED(END_FINALLY); +- case END_FINALLY: ++ TARGET(END_FINALLY) + v = POP(); + if (PyInt_Check(v)) { + why = (enum why_code) PyInt_AS_LONG(v); +@@ -1757,7 +1863,7 @@ + Py_DECREF(v); + break; + +- case BUILD_CLASS: ++ TARGET(BUILD_CLASS) + u = TOP(); + v = SECOND(); + w = THIRD(); +@@ -1769,7 +1875,7 @@ + Py_DECREF(w); + break; + +- case STORE_NAME: ++ TARGET(STORE_NAME) + w = GETITEM(names, oparg); + v = POP(); + if ((x = f->f_locals) != NULL) { +@@ -1778,7 +1884,7 @@ + else + err = PyObject_SetItem(x, w, v); + Py_DECREF(v); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + } + PyErr_Format(PyExc_SystemError, +@@ -1786,7 +1892,7 @@ + PyObject_REPR(w)); + break; + +- case DELETE_NAME: ++ TARGET(DELETE_NAME) + w = GETITEM(names, oparg); + if ((x = f->f_locals) != NULL) { + if ((err = PyObject_DelItem(x, w)) != 0) +@@ -1801,7 +1907,7 @@ + break; + + PREDICTED_WITH_ARG(UNPACK_SEQUENCE); +- case UNPACK_SEQUENCE: ++ TARGET(UNPACK_SEQUENCE) + v = POP(); + if (PyTuple_CheckExact(v) && + PyTuple_GET_SIZE(v) == oparg) { +@@ -1813,7 +1919,7 @@ + PUSH(w); + } + Py_DECREF(v); +- continue; ++ DISPATCH(); + } else if (PyList_CheckExact(v) && + PyList_GET_SIZE(v) == oparg) { + PyObject **items = \ +@@ -1833,7 +1939,7 @@ + Py_DECREF(v); + break; + +- case STORE_ATTR: ++ TARGET(STORE_ATTR) + w = GETITEM(names, oparg); + v = TOP(); + u = SECOND(); +@@ -1841,10 +1947,10 @@ + err = PyObject_SetAttr(v, w, u); /* v.w = u */ + Py_DECREF(v); + Py_DECREF(u); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case DELETE_ATTR: ++ TARGET(DELETE_ATTR) + w = GETITEM(names, oparg); + v = POP(); + err = PyObject_SetAttr(v, w, (PyObject *)NULL); +@@ -1852,22 +1958,22 @@ + Py_DECREF(v); + break; + +- case STORE_GLOBAL: ++ TARGET(STORE_GLOBAL) + w = GETITEM(names, oparg); + v = POP(); + err = PyDict_SetItem(f->f_globals, w, v); + Py_DECREF(v); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case DELETE_GLOBAL: ++ TARGET(DELETE_GLOBAL) + w = GETITEM(names, oparg); + if ((err = PyDict_DelItem(f->f_globals, w)) != 0) + format_exc_check_arg( + PyExc_NameError, GLOBAL_NAME_ERROR_MSG, w); + break; + +- case LOAD_NAME: ++ TARGET(LOAD_NAME) + w = GETITEM(names, oparg); + if ((v = f->f_locals) == NULL) { + PyErr_Format(PyExc_SystemError, +@@ -1903,9 +2009,9 @@ + Py_INCREF(x); + } + PUSH(x); +- continue; ++ DISPATCH(); + +- case LOAD_GLOBAL: ++ TARGET(LOAD_GLOBAL) + w = GETITEM(names, oparg); + if (PyString_CheckExact(w)) { + /* Inline the PyDict_GetItem() calls. +@@ -1925,7 +2031,7 @@ + if (x != NULL) { + Py_INCREF(x); + PUSH(x); +- continue; ++ DISPATCH(); + } + d = (PyDictObject *)(f->f_builtins); + e = d->ma_lookup(d, w, hash); +@@ -1937,7 +2043,7 @@ + if (x != NULL) { + Py_INCREF(x); + PUSH(x); +- continue; ++ DISPATCH(); + } + goto load_global_error; + } +@@ -1956,13 +2062,13 @@ + } + Py_INCREF(x); + PUSH(x); +- continue; ++ DISPATCH(); + +- case DELETE_FAST: ++ TARGET(DELETE_FAST) + x = GETLOCAL(oparg); + if (x != NULL) { + SETLOCAL(oparg, NULL); +- continue; ++ DISPATCH(); + } + format_exc_check_arg( + PyExc_UnboundLocalError, +@@ -1971,19 +2077,19 @@ + ); + break; + +- case LOAD_CLOSURE: ++ TARGET(LOAD_CLOSURE) + x = freevars[oparg]; + Py_INCREF(x); + PUSH(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case LOAD_DEREF: ++ TARGET(LOAD_DEREF) + x = freevars[oparg]; + w = PyCell_Get(x); + if (w != NULL) { + PUSH(w); +- continue; ++ DISPATCH(); + } + err = -1; + /* Don't stomp existing exception */ +@@ -2004,14 +2110,14 @@ + } + break; + +- case STORE_DEREF: ++ TARGET(STORE_DEREF) + w = POP(); + x = freevars[oparg]; + PyCell_Set(x, w); + Py_DECREF(w); +- continue; ++ DISPATCH(); + +- case BUILD_TUPLE: ++ TARGET(BUILD_TUPLE) + x = PyTuple_New(oparg); + if (x != NULL) { + for (; --oparg >= 0;) { +@@ -2019,11 +2125,11 @@ + PyTuple_SET_ITEM(x, oparg, w); + } + PUSH(x); +- continue; ++ DISPATCH(); + } + break; + +- case BUILD_LIST: ++ TARGET(BUILD_LIST) + x = PyList_New(oparg); + if (x != NULL) { + for (; --oparg >= 0;) { +@@ -2031,17 +2137,17 @@ + PyList_SET_ITEM(x, oparg, w); + } + PUSH(x); +- continue; ++ DISPATCH(); + } + break; + +- case BUILD_MAP: ++ TARGET(BUILD_MAP) + x = _PyDict_NewPresized((Py_ssize_t)oparg); + PUSH(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case STORE_MAP: ++ TARGET(STORE_MAP) + w = TOP(); /* key */ + u = SECOND(); /* value */ + v = THIRD(); /* dict */ +@@ -2050,19 +2156,19 @@ + err = PyDict_SetItem(v, w, u); /* v[w] = u */ + Py_DECREF(u); + Py_DECREF(w); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case LOAD_ATTR: ++ TARGET(LOAD_ATTR) + w = GETITEM(names, oparg); + v = TOP(); + x = PyObject_GetAttr(v, w); + Py_DECREF(v); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case COMPARE_OP: ++ TARGET(COMPARE_OP) + w = POP(); + v = TOP(); + if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) { +@@ -2095,9 +2201,9 @@ + if (x == NULL) break; + PREDICT(JUMP_IF_FALSE); + PREDICT(JUMP_IF_TRUE); +- continue; ++ DISPATCH(); + +- case IMPORT_NAME: ++ TARGET(IMPORT_NAME) + w = GETITEM(names, oparg); + x = PyDict_GetItemString(f->f_builtins, "__import__"); + if (x == NULL) { +@@ -2138,10 +2244,10 @@ + READ_TIMESTAMP(intr1); + Py_DECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case IMPORT_STAR: ++ TARGET(IMPORT_STAR) + v = POP(); + PyFrame_FastToLocals(f); + if ((x = f->f_locals) == NULL) { +@@ -2154,33 +2260,33 @@ + READ_TIMESTAMP(intr1); + PyFrame_LocalsToFast(f, 0); + Py_DECREF(v); +- if (err == 0) continue; ++ if (err == 0) DISPATCH(); + break; + +- case IMPORT_FROM: ++ TARGET(IMPORT_FROM) + w = GETITEM(names, oparg); + v = TOP(); + READ_TIMESTAMP(intr0); + x = import_from(v, w); + READ_TIMESTAMP(intr1); + PUSH(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case JUMP_FORWARD: ++ TARGET(JUMP_FORWARD) + JUMPBY(oparg); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + + PREDICTED_WITH_ARG(JUMP_IF_FALSE); +- case JUMP_IF_FALSE: ++ TARGET(JUMP_IF_FALSE) + w = TOP(); + if (w == Py_True) { + PREDICT(POP_TOP); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + } + if (w == Py_False) { + JUMPBY(oparg); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + } + err = PyObject_IsTrue(w); + if (err > 0) +@@ -2189,18 +2295,18 @@ + JUMPBY(oparg); + else + break; +- continue; ++ DISPATCH(); + + PREDICTED_WITH_ARG(JUMP_IF_TRUE); +- case JUMP_IF_TRUE: ++ TARGET(JUMP_IF_TRUE) + w = TOP(); + if (w == Py_False) { + PREDICT(POP_TOP); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + } + if (w == Py_True) { + JUMPBY(oparg); +- goto fast_next_opcode; ++ FAST_DISPATCH(); + } + err = PyObject_IsTrue(w); + if (err > 0) { +@@ -2211,10 +2317,10 @@ + ; + else + break; +- continue; ++ DISPATCH(); + + PREDICTED_WITH_ARG(JUMP_ABSOLUTE); +- case JUMP_ABSOLUTE: ++ TARGET(JUMP_ABSOLUTE) + JUMPTO(oparg); + #if FAST_LOOPS + /* Enabling this path speeds-up all while and for-loops by bypassing +@@ -2222,14 +2328,14 @@ + because it prevents detection of a control-break in tight loops like + "while 1: pass". Compile with this option turned-on when you need + the speed-up and do not need break checking inside tight loops (ones +- that contain only instructions ending with goto fast_next_opcode). ++ that contain only instructions ending with FAST_DISPATCH). + */ +- goto fast_next_opcode; ++ FAST_DISPATCH(); + #else +- continue; ++ DISPATCH(); + #endif + +- case GET_ITER: ++ TARGET(GET_ITER) + /* before: [obj]; after [getiter(obj)] */ + v = TOP(); + x = PyObject_GetIter(v); +@@ -2237,13 +2343,13 @@ + if (x != NULL) { + SET_TOP(x); + PREDICT(FOR_ITER); +- continue; ++ DISPATCH(); + } + STACKADJ(-1); + break; + + PREDICTED_WITH_ARG(FOR_ITER); +- case FOR_ITER: ++ TARGET(FOR_ITER) + /* before: [iter]; after: [iter, iter()] *or* [] */ + v = TOP(); + x = (*v->ob_type->tp_iternext)(v); +@@ -2251,7 +2357,7 @@ + PUSH(x); + PREDICT(STORE_FAST); + PREDICT(UNPACK_SEQUENCE); +- continue; ++ DISPATCH(); + } + if (PyErr_Occurred()) { + if (!PyErr_ExceptionMatches( +@@ -2263,13 +2369,13 @@ + x = v = POP(); + Py_DECREF(v); + JUMPBY(oparg); +- continue; ++ DISPATCH(); + +- case BREAK_LOOP: ++ TARGET(BREAK_LOOP) + why = WHY_BREAK; + goto fast_block_end; + +- case CONTINUE_LOOP: ++ TARGET(CONTINUE_LOOP) + retval = PyInt_FromLong(oparg); + if (!retval) { + x = NULL; +@@ -2278,9 +2384,10 @@ + why = WHY_CONTINUE; + goto fast_block_end; + +- case SETUP_LOOP: +- case SETUP_EXCEPT: +- case SETUP_FINALLY: ++ TARGET_WITH_IMPL(SETUP_LOOP, _setup_finally) ++ TARGET_WITH_IMPL(SETUP_EXCEPT, _setup_finally) ++ TARGET(SETUP_FINALLY) ++ _setup_finally: + /* NOTE: If you add any new block-setup opcodes that + are not try/except/finally handlers, you may need + to update the PyGen_NeedsFinalizing() function. +@@ -2288,9 +2395,9 @@ + + PyFrame_BlockSetup(f, opcode, INSTR_OFFSET() + oparg, + STACK_LEVEL()); +- continue; ++ DISPATCH(); + +- case WITH_CLEANUP: ++ TARGET(WITH_CLEANUP) + { + /* At the top of the stack are 1-3 values indicating + how/why we entered the finally clause: +@@ -2378,7 +2485,7 @@ + break; + } + +- case CALL_FUNCTION: ++ TARGET(CALL_FUNCTION) + { + PyObject **sp; + PCALL(PCALL_ALL); +@@ -2391,13 +2498,14 @@ + stack_pointer = sp; + PUSH(x); + if (x != NULL) +- continue; ++ DISPATCH(); + break; + } + +- case CALL_FUNCTION_VAR: +- case CALL_FUNCTION_KW: +- case CALL_FUNCTION_VAR_KW: ++ TARGET_WITH_IMPL(CALL_FUNCTION_VAR, _call_function_var_kw) ++ TARGET_WITH_IMPL(CALL_FUNCTION_KW, _call_function_var_kw) ++ TARGET(CALL_FUNCTION_VAR_KW) ++ _call_function_var_kw: + { + int na = oparg & 0xff; + int nk = (oparg>>8) & 0xff; +@@ -2437,11 +2545,11 @@ + } + PUSH(x); + if (x != NULL) +- continue; ++ DISPATCH(); + break; + } + +- case MAKE_FUNCTION: ++ TARGET(MAKE_FUNCTION) + v = POP(); /* code object */ + x = PyFunction_New(v, f->f_globals); + Py_DECREF(v); +@@ -2463,7 +2571,7 @@ + PUSH(x); + break; + +- case MAKE_CLOSURE: ++ TARGET(MAKE_CLOSURE) + { + v = POP(); /* code object */ + x = PyFunction_New(v, f->f_globals); +@@ -2498,7 +2606,7 @@ + break; + } + +- case BUILD_SLICE: ++ TARGET(BUILD_SLICE) + if (oparg == 3) + w = POP(); + else +@@ -2510,14 +2618,17 @@ + Py_DECREF(v); + Py_XDECREF(w); + SET_TOP(x); +- if (x != NULL) continue; ++ if (x != NULL) DISPATCH(); + break; + +- case EXTENDED_ARG: ++ TARGET(EXTENDED_ARG) + opcode = NEXTOP(); + oparg = oparg<<16 | NEXTARG(); + goto dispatch_opcode; + ++#ifdef USE_COMPUTED_GOTOS ++ _unknown_opcode: ++#endif + default: + fprintf(stderr, + "XXX lineno: %d, opcode: %d\n", diff --git a/digsby/build/msw/python26-file-object-close-7079.patch b/digsby/build/msw/python26-file-object-close-7079.patch new file mode 100644 index 0000000..dc026e8 --- /dev/null +++ b/digsby/build/msw/python26-file-object-close-7079.patch @@ -0,0 +1,17 @@ +Index: Objects/fileobject.c +=================================================================== +--- Objects/fileobject.c (revision 72606) ++++ Objects/fileobject.c (working copy) +@@ -554,8 +554,10 @@ + file_close(PyFileObject *f) + { + PyObject *sts = close_the_file(f); +- PyMem_Free(f->f_setbuf); +- f->f_setbuf = NULL; ++ if (sts) { ++ PyMem_Free(f->f_setbuf); ++ f->f_setbuf = NULL; ++ } + return sts; + } + diff --git a/digsby/build/msw/pythoncore.icproj b/digsby/build/msw/pythoncore.icproj new file mode 100644 index 0000000..d1e3573 --- /dev/null +++ b/digsby/build/msw/pythoncore.icproj @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/build/msw/sendsymbols.py b/digsby/build/msw/sendsymbols.py new file mode 100644 index 0000000..e7f61ce --- /dev/null +++ b/digsby/build/msw/sendsymbols.py @@ -0,0 +1,218 @@ +#__LICENSE_GOES_HERE__ +''' +sendsymbols.py + + stores binaries and PDBs on the symbol server + + Without arguments, reads a list of binaries to send from binaries.txt. + With arguments, sends the files specified on the commandline, like: + + > python sendsymbols.py mybinary.pyd myotherbinary.pyd ... + + If there is a PDB file adjacent to a DLL or PYD it will be sent as well. +''' + +import sys; sys.path.append('..') +from buildutil import DEPS_DIR, DEBUG +import os.path + +# where to find symstore.exe +dbugtools_paths = [ + r'c:\Program Files\Debugging Tools for Windows (x86)', + r'c:\Program Files (x86)\Debugging Tools for Windows (x86)', + r'd:\Program Files\Debugging Tools for Windows (x64)', +] + +# Ensure we can find symstore.exe +for p in dbugtools_paths: + if os.path.isdir(p): + dbugtools_path = p + break +else: + print >> sys.stderr, "missing symstore.exe: Please download Debugging Tools for Windows." + raise Exception("missing symstore.exe: Please download Debugging Tools for Windows.") + +class SymStoreException(Exception): + pass + +symstore_exe = dbugtools_path + r'\symstore.exe' + +# where to store symbols +symbol_server = r'\\mini\symbols\digsby' + +# stored as an identifier with each transaction +appname = 'Digsby' + +# pass /compress to symstore.exe? +compress = False + +# filesnames are checked for these extensions before being passed to symstore.exe +known_binary_extensions = frozenset(('.exe', '.pyd', '.dll', '.pdb')) + + +import os, sys +from hashlib import sha1 +from subprocess import Popen +from os.path import isfile, splitext, normpath, join as pathjoin, dirname, \ + abspath, basename +from string import Template + +def storesym(binary): + ''' + Calls storesym.exe on binary and its corresponding PDB, which is assumed to + be next to it. If the PDB is not found a warning is printed to the console. + ''' + + filename = binary + + filebase, ext = splitext(filename) + if not ext.lower() in known_binary_extensions: + fatal('not a valid binary extension: %r' % filename) + + if not isfile(filename): + fatal('file does not exist: %r' % filename) + + _leaf_func(binary) + + if not binary.lower().endswith('.pdb'): + pdb = filebase + '.pdb' + if isfile(pdb): + print 'Also sending %r' % pdb + _leaf_func(pdb) + elif MODE == 'sendsymbols': + warn('PDB missing: %r' % pdb) + +from shutil import copy2 + +def _storesym(filename): + if isinstance(filename, tuple): + # send the file as it's packaged Py2EXE name + filename, distname = filename + assert not isfile(distname) + copy2(filename, distname) + + try: + _runstoresym(distname) + finally: + os.remove(distname) + else: + _runstoresym(filename) + +def _runstoresym(filename): + args = [symstore_exe, 'add', + '/f', filename, + '/s', symbol_server] + + if compress: + args.append('/compress') + + args.extend(['/t', appname]) + + run(*args) + +def _compare_to_installed(filename): + install_lib = r'c:\Program Files\Digsby\Lib' + + installed_binary = pathjoin(install_lib, basename(filename)) + + if not isfile(installed_binary): + return + + installed_sha, workspace_sha = sha(installed_binary), sha(filename) + + okstr = ' OK ' if installed_sha == workspace_sha else ' X ' + print okstr, installed_sha[:5], workspace_sha[:5], basename(filename) + +MODE = 'compare' if '--compare' in sys.argv else 'sendsymbols' + +if MODE == 'compare': + VERB = 'Checking' + _leaf_func = _compare_to_installed +elif MODE == 'sendsymbols': + VERB = 'Sending' + _leaf_func = _storesym +else: + raise AssertionError(MODE) + +def _send_symbols(files): + total = len(files) + for i, filename in enumerate(files): + if MODE == 'sendsymbols': + + filebase, ext = splitext(filename) + + # PYDs have _d postfix in debug mode + if DEBUG and ext == '.pyd': + filename = filebase + '_d' + ext + + space = ' ' if i < 10 else ' ' + print '\n%s (%d/%d):%s%s' % (VERB, i + 1, total, space, filename) + + storesym(filename) + +def run(*cmd): + 'Run a command, exiting if the return code is not 0.' + + process = Popen(cmd) + stdout, stdin = process.communicate() + + # exit with non-zero return code + if process.returncode: + raise Exception(SymStoreException(process.returncode, stdout)) + + return stdout + +def sha(filename): + return sha1(open(filename, 'rb').read()).hexdigest() + +def fatal(msg): + print >> sys.stderr, msg + sys.exit(1) + +def warn(msg): + print >> sys.stderr, msg + +def send_default_symbols(): + # a list of all binaries we want to store is in binaries.txt + + # variables which appear in binaries.txt, like ${DPYTHON} + DPYTHON_DIR = dirname(abspath(sys.executable)) + binaries_vars = dict(DPYTHON = DPYTHON_DIR, + PLATLIB = DEPS_DIR, + DEBUG = 'd' if DEBUG else '', + _DEBUG = '_d' if DEBUG else '', + WX_POSTFIX = 'ud' if DEBUG else 'uh') + + def template(s): + return Template(s).safe_substitute(binaries_vars) + + working_dir = os.getcwd() + digsby_dir = normpath(pathjoin(dirname(abspath(__file__)), '../..')) + + try: + os.chdir(digsby_dir) + + files = [] + for line in open('build/msw/binaries.txt'): + line = template(line).strip() + + if line and not line.startswith('#'): + files.append(line) + + _send_symbols(files) + finally: + os.chdir(working_dir) + +def main(): + # if files to send were specified on the command line, send them + cmdline_files = sys.argv[1:] + if cmdline_files: + return _send_symbols(cmdline_files) + + # otherwise get from binaries.txt + # change to the correct directory + send_default_symbols() + + +if __name__ == '__main__': + main() diff --git a/digsby/build/msw/sign_exe.bat b/digsby/build/msw/sign_exe.bat new file mode 100644 index 0000000..5a73e97 --- /dev/null +++ b/digsby/build/msw/sign_exe.bat @@ -0,0 +1,3 @@ +rem -- Requires a valid code signing certificate. will use the "best available" so make sure you only have the one you want. +signtool sign /a /t http://timestamp.comodoca.com/authenticode %1 +pause \ No newline at end of file diff --git a/digsby/build/msw/ss.bat b/digsby/build/msw/ss.bat new file mode 100644 index 0000000..a17eef7 --- /dev/null +++ b/digsby/build/msw/ss.bat @@ -0,0 +1 @@ +@dpy %~d0%~p0\sendsymbols.py %1 %2 %3 %4 %5 %6 %7 %8 %9 diff --git a/digsby/build/msw/test_ext/build_test_extension.py b/digsby/build/msw/test_ext/build_test_extension.py new file mode 100644 index 0000000..93cf5f8 --- /dev/null +++ b/digsby/build/msw/test_ext/build_test_extension.py @@ -0,0 +1,66 @@ +''' + +builds a small python test extension + +''' + +from __future__ import with_statement + +import sys +import os + +from shutil import rmtree +from os.path import exists +from traceback import print_exc + +def clean(): + 'Cleans results of previous builds.' + + if not exists('./spam.c'): + raise Exception('wrong working directory: expected to be run from digsby\build\msw\test_ext') + + if exists('build'): rmtree('build') + if exists('spam.pyd'): os.remove('spam.pyd') + if exists('spam.pyd.manifest'): os.remove('spam.pyd.manifest') + +def build(): + 'Builds a small test Python extension.' + + from distutils.core import setup, Extension + setup(name = 'spam', + version='1.0', + ext_modules=[Extension('spam', ['spam.c'])]) + +def run(): + 'Tests the functionality of the extension.' + + import spam + + # spam should have a "system" function...try it out + if spam.system('echo test') != 0 or spam.system('this_command_should_cause_an_error') != 1: + raise AssertionError('test module did not function correctly') + + # unload + del sys.modules['spam'] + + print + print 'Success!' + print + +def main(): + print 'Python executable is', sys.executable + + try: + clean() + build() + run() + except Exception: + # return 1 on any kind of error + print_exc() + return 1 + else: + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/digsby/build/msw/test_ext/spam.c b/digsby/build/msw/test_ext/spam.c new file mode 100644 index 0000000..f54ed6b --- /dev/null +++ b/digsby/build/msw/test_ext/spam.c @@ -0,0 +1,32 @@ +/* + a minimal python extension example + + thanks http://www.python.org/doc/ext/intro.html +*/ + +#include + +static PyObject * +spam_system(PyObject *self, PyObject *args) +{ + const char *command; + int sts; + + if (!PyArg_ParseTuple(args, "s", &command)) + return NULL; + sts = system(command); + return Py_BuildValue("i", sts); +} + +static PyMethodDef SpamMethods[] = { + {"system", spam_system, METH_VARARGS, + "Execute a shell command."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +PyMODINIT_FUNC +initspam(void) +{ + (void) Py_InitModule("spam", SpamMethods); +} + diff --git a/digsby/build/msw/vs2008+intel.patch b/digsby/build/msw/vs2008+intel.patch new file mode 100644 index 0000000..be7d0d7 --- /dev/null +++ b/digsby/build/msw/vs2008+intel.patch @@ -0,0 +1,482 @@ +### Eclipse Workspace Patch 1.0 +#P Digsby +Index: build/msw/python/PCbuild/pcbuild.sln +=================================================================== +--- build/msw/python/PCbuild/pcbuild.sln (revision 70358) ++++ build/msw/python/PCbuild/pcbuild.sln (working copy) +@@ -2,13 +2,13 @@ + # Visual Studio 2008 + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python", "python.vcproj", "{B11D750F-CD1F-4A96-85CE-E69A5C5259F9}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + {E9E0A1F6-0009-4E8C-B8F8-1B8F5D49A058} = {E9E0A1F6-0009-4E8C-B8F8-1B8F5D49A058} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "make_versioninfo", "make_versioninfo.vcproj", "{F0E0541E-F17D-430B-97C4-93ADF0DD284E}" + EndProject +-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythoncore", "pythoncore.vcproj", "{CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}" ++Project("{EAF909A5-FA59-4C3D-9431-0FCC20D5BCF9}") = "pythoncore", "pythoncore.icproj", "{A89B81E6-3B74-4D41-9809-71E959C1AC10}" + ProjectSection(ProjectDependencies) = postProject + {F0E0541E-F17D-430B-97C4-93ADF0DD284E} = {F0E0541E-F17D-430B-97C4-93ADF0DD284E} + {6DE10744-E396-40A5-B4E2-1B69AA7C8D31} = {6DE10744-E396-40A5-B4E2-1B69AA7C8D31} +@@ -17,7 +17,7 @@ + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythonw", "pythonw.vcproj", "{F4229CC3-873C-49AE-9729-DD308ED4CD4A}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "w9xpopen", "w9xpopen.vcproj", "{E9E0A1F6-0009-4E8C-B8F8-1B8F5D49A058}" +@@ -35,43 +35,43 @@ + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winsound", "winsound.vcproj", "{28B5D777-DDF2-4B6B-B34F-31D938813856}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_bsddb", "_bsddb.vcproj", "{B4D38F3F-68FB-42EC-A45D-E00657BB3627}" + ProjectSection(ProjectDependencies) = postProject + {6DE10744-E396-40A5-B4E2-1B69AA7C8D31} = {6DE10744-E396-40A5-B4E2-1B69AA7C8D31} +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_ctypes", "_ctypes.vcproj", "{0E9791DB-593A-465F-98BC-681011311618}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_ctypes_test", "_ctypes_test.vcproj", "{9EC7190A-249F-4180-A900-548FDCF3055F}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_elementtree", "_elementtree.vcproj", "{17E1E049-C309-4D79-843F-AE483C264AEA}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_msi", "_msi.vcproj", "{31FFC478-7B4A-43E8-9954-8D03E2187E9C}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_socket", "_socket.vcproj", "{86937F53-C189-40EF-8CE8-8759D8E7D480}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_sqlite3", "_sqlite3.vcproj", "{13CECB97-4119-4316-9D42-8534019A5A44}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + {A1A295E5-463C-437F-81CA-1F32367685DA} = {A1A295E5-463C-437F-81CA-1F32367685DA} + EndProjectSection + EndProject +@@ -79,37 +79,37 @@ + ProjectSection(ProjectDependencies) = postProject + {B11D750F-CD1F-4A96-85CE-E69A5C5259F9} = {B11D750F-CD1F-4A96-85CE-E69A5C5259F9} + {86937F53-C189-40EF-8CE8-8759D8E7D480} = {86937F53-C189-40EF-8CE8-8759D8E7D480} +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testcapi", "_testcapi.vcproj", "{6901D91C-6E48-4BB7-9FEC-700C8131DF1D}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_tkinter", "_tkinter.vcproj", "{4946ECAC-2E69-4BF8-A90A-F5136F5094DF}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bz2", "bz2.vcproj", "{73FCD2BD-F133-46B7-8EC1-144CD82A59D5}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "select", "select.vcproj", "{18CAE28C-B454-46C1-87A0-493D91D97F03}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unicodedata", "unicodedata.vcproj", "{ECC7CEAC-A5E5-458E-BB9E-2413CC847881}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pyexpat", "pyexpat.vcproj", "{D06B6426-4762-44CC-8BAD-D79052507F2F}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bdist_wininst", "bdist_wininst.vcproj", "{EB1C19C1-1F18-421E-9735-CAEE69DC6A3C}" +@@ -117,7 +117,7 @@ + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_hashlib", "_hashlib.vcproj", "{447F05A8-F581-4CAC-A466-5AC7936E207E}" + ProjectSection(ProjectDependencies) = postProject + {B11D750F-CD1F-4A96-85CE-E69A5C5259F9} = {B11D750F-CD1F-4A96-85CE-E69A5C5259F9} +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sqlite3", "sqlite3.vcproj", "{A1A295E5-463C-437F-81CA-1F32367685DA}" +@@ -127,7 +127,7 @@ + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_multiprocessing", "_multiprocessing.vcproj", "{9E48B300-37D1-11DD-8C41-005056C00008}" + ProjectSection(ProjectDependencies) = postProject +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} = {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26} ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10} = {A89B81E6-3B74-4D41-9809-71E959C1AC10} + EndProjectSection + EndProject + Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kill_python", "kill_python.vcproj", "{6DE10744-E396-40A5-B4E2-1B69AA7C8D31}" +@@ -176,22 +176,22 @@ + {F0E0541E-F17D-430B-97C4-93ADF0DD284E}.Release|Win32.Build.0 = Release|Win32 + {F0E0541E-F17D-430B-97C4-93ADF0DD284E}.Release|x64.ActiveCfg = Release|Win32 + {F0E0541E-F17D-430B-97C4-93ADF0DD284E}.Release|x64.Build.0 = Release|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Debug|Win32.ActiveCfg = Debug|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Debug|Win32.Build.0 = Debug|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Debug|x64.ActiveCfg = Debug|x64 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Debug|x64.Build.0 = Debug|x64 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGInstrument|x64.Build.0 = PGInstrument|x64 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGUpdate|x64.Build.0 = PGUpdate|x64 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Release|Win32.ActiveCfg = Release|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Release|Win32.Build.0 = Release|Win32 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Release|x64.ActiveCfg = Release|x64 +- {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Release|x64.Build.0 = Release|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Debug|Win32.ActiveCfg = Debug|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Debug|Win32.Build.0 = Debug|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Debug|x64.ActiveCfg = Debug|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Debug|x64.Build.0 = Debug|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGInstrument|x64.Build.0 = PGInstrument|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.PGUpdate|x64.Build.0 = PGUpdate|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Release|Win32.ActiveCfg = Release|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Release|Win32.Build.0 = Release|Win32 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Release|x64.ActiveCfg = Release|x64 ++ {A89B81E6-3B74-4D41-9809-71E959C1AC10}.Release|x64.Build.0 = Release|x64 + {F4229CC3-873C-49AE-9729-DD308ED4CD4A}.Debug|Win32.ActiveCfg = Debug|Win32 + {F4229CC3-873C-49AE-9729-DD308ED4CD4A}.Debug|Win32.Build.0 = Debug|Win32 + {F4229CC3-873C-49AE-9729-DD308ED4CD4A}.Debug|x64.ActiveCfg = Debug|x64 +@@ -552,6 +552,14 @@ + {6DE10744-E396-40A5-B4E2-1B69AA7C8D31}.Release|Win32.Build.0 = Release|Win32 + {6DE10744-E396-40A5-B4E2-1B69AA7C8D31}.Release|x64.ActiveCfg = Release|x64 + {6DE10744-E396-40A5-B4E2-1B69AA7C8D31}.Release|x64.Build.0 = Release|x64 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Debug|Win32.ActiveCfg = Debug|Win32 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Debug|x64.ActiveCfg = Debug|x64 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Release|Win32.ActiveCfg = Release|Win32 ++ {CF7AC3D1-E2DF-41D2-BEA6-1E2556CDEA26}.Release|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE +Index: build/msw/python/PCbuild/pythoncore.vcproj +=================================================================== +--- build/msw/python/PCbuild/pythoncore.vcproj (revision 70358) ++++ build/msw/python/PCbuild/pythoncore.vcproj (working copy) +@@ -1,7 +1,7 @@ + + + + +
+ + + + + + + ++ ++ + +@@ -999,10 +1018,6 @@ + > + + +- +- + +@@ -1057,6 +1072,14 @@ + ++ ++ ++ + + ++ ++ ++ + + + + +- +- + + + + + + ++ ++ + +@@ -1613,6 +1645,14 @@ + ++ ++ ++ + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --git a/digsby/build/msw/wx.bat b/digsby/build/msw/wx.bat new file mode 100644 index 0000000..32206ae --- /dev/null +++ b/digsby/build/msw/wx.bat @@ -0,0 +1 @@ +python build_wx.py %1 %2 %3 %4 %5 %6 %7 %8 %9 diff --git a/digsby/build/patches/build-wxpython.patch b/digsby/build/patches/build-wxpython.patch new file mode 100644 index 0000000..5afc83f --- /dev/null +++ b/digsby/build/patches/build-wxpython.patch @@ -0,0 +1,31 @@ +Index: build-wxpython.sh +=================================================================== +--- build-wxpython.sh (revision 52902) ++++ build-wxpython.sh (working copy) +@@ -117,7 +117,7 @@ + fi + + if [ "$WXPY_INSTALL_DIR" = "" ]; then +- WXPY_INSTALL_DIR=$HOME/wxpython-2.8.4 ++ WXPY_INSTALL_DIR=$HOME/wxpython-2.8 + fi + + if [ $clean = yes ]; then +@@ -143,7 +143,7 @@ + export INSTALLDIR=$WXPY_INSTALL_DIR + + if [ "${OSTYPE:0:6}" = "darwin" ]; then +- $WXWIN/distrib/scripts/mac/macbuild-lipo wxpython $UNICODE_OPT $DEBUG_OPT ++ $WXWIN/distrib/scripts/mac/macbuild wxpython $UNICODE_OPT $DEBUG_OPT + else + $WXWIN/distrib/scripts/unix/unixbuild wxpython $UNICODE_OPT $DEBUG_OPT + fi +@@ -166,6 +166,8 @@ + + cd $scriptDir + python ./setup.py build_ext --inplace WX_CONFIG=$WXPY_INSTALL_DIR/bin/wx-config USE_SWIG=$USE_SWIG SWIG=$SWIG_BIN UNICODE=$UNICODE_WXPY_OPT ++ python ./setup.py install --install-headers=$WXPY_INSTALL_DIR/include --root=$WXPY_INSTALL_DIR --install-platlib=wxPython --install-scripts=wxPython --install-purelib=wxPython WX_CONFIG=$WXPY_INSTALL_DIR/bin/wx-config USE_SWIG=$USE_SWIG SWIG=$SWIG_BIN UNICODE=$UNICODE_WXPY_OPT ++ + if [ $? != 0 ]; then + exit $? + fi diff --git a/digsby/build/patches/libxml2.patch b/digsby/build/patches/libxml2.patch new file mode 100644 index 0000000..ae3033b --- /dev/null +++ b/digsby/build/patches/libxml2.patch @@ -0,0 +1,25 @@ +--- python/generator.py Thu Nov 27 09:11:48 2008 ++++ python/generator.py Mon Jun 15 19:26:15 2009 +@@ -1061,8 +1061,8 @@ + if classes_ancestor[classname] == "xmlCore" or \ + classes_ancestor[classname] == "xmlNode": + classes.write(" def __repr__(self):\n") +- format = "<%s (%%s) object at 0x%%x>" % (classname) +- classes.write(" return \"%s\" %% (self.name, long(pos_id (self)))\n\n" % ( ++ format = "<%s object at 0x%%x>" % (classname) ++ classes.write(" return \"%s\" %% (long(pos_id (self)),)\n\n" % ( + format)) + else: + txt.write("Class %s()\n" % (classname)) + +--- win32/Makefile.msvc Mon Jul 9 13:05:01 2012 ++++ win32/Makefile.msvc Mon Jul 9 13:04:51 2012 +@@ -69,7 +69,7 @@ + LIBS = $(LIBS) wsock32.lib ws2_32.lib + !endif + !if "$(WITH_ICONV)" == "1" +-LIBS = $(LIBS) iconv.lib ++LIBS = $(LIBS) libiconv.lib + !endif + !if "$(WITH_ZLIB)" == "1" + LIBS = $(LIBS) zdll.lib diff --git a/digsby/build/patches/libxslt-processOneNode.patch b/digsby/build/patches/libxslt-processOneNode.patch new file mode 100644 index 0000000..b3cbf22 --- /dev/null +++ b/digsby/build/patches/libxslt-processOneNode.patch @@ -0,0 +1,33 @@ +diff -ru libxslt-1.1.24/libxslt/transform.h libxslt-1.1.24/libxslt/transform.h +--- libxslt-1.1.24/libxslt/transform.h Mon Jan 15 04:54:32 2007 ++++ libxslt-1.1.24/libxslt/transform.h Thu Jul 12 11:42:26 2012 +@@ -32,6 +32,8 @@ + /** + * Export context to users. + */ ++XSLTPUBFUN void XSLTCALL xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node, xsltStackElemPtr params); ++ + XSLTPUBFUN xsltTransformContextPtr XSLTCALL + xsltNewTransformContext (xsltStylesheetPtr style, + xmlDocPtr doc); +diff -ru libxslt-1.1.24/win32/Makefile.msvc libxslt-1.1.24/win32/Makefile.msvc +--- libxslt-1.1.24/win32/Makefile.msvc Fri Aug 3 06:41:02 2007 ++++ libxslt-1.1.24/win32/Makefile.msvc Thu Jul 12 11:43:17 2012 +@@ -56,7 +56,7 @@ + + # The linker and its options. + LD = link.exe +-LDFLAGS = /nologo ++LDFLAGS = /OPT:REF /OPT:ICF /nologo /DEBUG + LDFLAGS = $(LDFLAGS) /LIBPATH:$(BINDIR) /LIBPATH:$(LIBPREFIX) + LIBS = wsock32.lib + +@@ -69,7 +69,7 @@ + CFLAGS = $(CFLAGS) /D "_DEBUG" /Od /Z7 + LDFLAGS = $(LDFLAGS) /DEBUG + !else +-CFLAGS = $(CFLAGS) /D "NDEBUG" /O2 ++CFLAGS = $(CFLAGS) /D "NDEBUG" /Os /GL /GS- /Zi + LDFLAGS = $(LDFLAGS) /OPT:NOWIN98 + !endif + diff --git a/digsby/build/patches/lxml.patch b/digsby/build/patches/lxml.patch new file mode 100644 index 0000000..c72ef97 --- /dev/null +++ b/digsby/build/patches/lxml.patch @@ -0,0 +1,25 @@ +Index: src/lxml/html/__init__.py +=================================================================== +--- src/lxml/html/__init__.py ++++ src/lxml/html/__init__.py +@@ -43,4 +43,6 @@ + def __fix_docstring(s): + import sys ++ if s is None: ++ return None + if sys.version_info[0] >= 3: + sub = re.compile(r"^(\s*)u'", re.M).sub + else: +Index: setupinfo.py +=================================================================== +--- setupinfo.py ++++ setupinfo.py +@@ -149,7 +149,7 @@ + libs = ['libxslt', 'libexslt', 'libxml2', 'iconv'] + if OPTION_STATIC: + libs = ['%s_a' % lib for lib in libs] +- libs.extend(['zlib', 'WS2_32']) ++ libs.extend(['zlib1', 'WS2_32']) + elif OPTION_STATIC: + libs = ['z', 'm'] + else: diff --git a/digsby/build/patches/wxpy_popupwin.patch b/digsby/build/patches/wxpy_popupwin.patch new file mode 100644 index 0000000..52d87f0 --- /dev/null +++ b/digsby/build/patches/wxpy_popupwin.patch @@ -0,0 +1,52 @@ +Index: src/_popupwin.i +=================================================================== +--- src/_popupwin.i (revision 52902) ++++ src/_popupwin.i (working copy) +@@ -20,7 +20,6 @@ + %} + + //--------------------------------------------------------------------------- +-#ifndef __WXMAC__ + %newgroup; + + MustHaveApp(wxPopupWindow); +@@ -103,39 +102,4 @@ + + //--------------------------------------------------------------------------- + +- +- +-#else // On Mac we need to provide dummy classes to keep the renamers in sync +-%{ +-class wxPopupWindow : public wxWindow { +-public: +- wxPopupWindow(wxWindow *, int) { wxPyRaiseNotImplemented(); } +- wxPopupWindow() { wxPyRaiseNotImplemented(); } +-}; +- +-class wxPyPopupTransientWindow : public wxPopupWindow +-{ +-public: +- wxPyPopupTransientWindow(wxWindow *, int) { wxPyRaiseNotImplemented(); } +- wxPyPopupTransientWindow() { wxPyRaiseNotImplemented(); } +-}; +-%} +- +- +-class wxPopupWindow : public wxWindow { +-public: +- wxPopupWindow(wxWindow *parent, int flags = wxBORDER_NONE); +- %RenameCtor(PrePopupWindow, wxPopupWindow()); +-}; +- +-%rename(PopupTransientWindow) wxPyPopupTransientWindow; +-class wxPyPopupTransientWindow : public wxPopupWindow +-{ +-public: +- wxPyPopupTransientWindow(wxWindow *parent, int style = wxBORDER_NONE); +- %RenameCtor(PrePopupTransientWindow, wxPyPopupTransientWindow()); +-}; +- +- +-#endif + //--------------------------------------------------------------------------- diff --git a/digsby/build/pyflakes.cfg b/digsby/build/pyflakes.cfg new file mode 100644 index 0000000..1e7f73f --- /dev/null +++ b/digsby/build/pyflakes.cfg @@ -0,0 +1 @@ +builtins = ['Sentinel', 'sentinel', '_'] diff --git a/digsby/build/s3/S3.py b/digsby/build/s3/S3.py new file mode 100644 index 0000000..77691e6 --- /dev/null +++ b/digsby/build/s3/S3.py @@ -0,0 +1,617 @@ +#!/usr/bin/env python + +# This software code is made available "AS IS" without warranties of any +# kind. You may copy, display, modify and redistribute the software +# code either by itself or as incorporated into your code; provided that +# you do not remove any proprietary notices. Your use of this software +# code is at your own risk and you waive any claim against Amazon +# Digital Services, Inc. or its affiliates with respect to your use of +# this software code. (c) 2006-2007 Amazon Digital Services, Inc. or its +# affiliates. + +import base64 +import hmac +import httplib +import re +import sha +import sys +import time +import urllib +import urlparse +import xml.sax + +DEFAULT_HOST = 's3.amazonaws.com' +PORTS_BY_SECURITY = { True: 443, False: 80 } +METADATA_PREFIX = 'x-amz-meta-' +AMAZON_HEADER_PREFIX = 'x-amz-' + +# generates the aws canonical string for the given parameters +def canonical_string(method, bucket="", key="", query_args={}, headers={}, expires=None): + interesting_headers = {} + for header_key in headers: + lk = header_key.lower() + if lk in ['content-md5', 'content-type', 'date'] or lk.startswith(AMAZON_HEADER_PREFIX): + interesting_headers[lk] = headers[header_key].strip() + + # these keys get empty strings if they don't exist + if not interesting_headers.has_key('content-type'): + interesting_headers['content-type'] = '' + if not interesting_headers.has_key('content-md5'): + interesting_headers['content-md5'] = '' + + # just in case someone used this. it's not necessary in this lib. + if interesting_headers.has_key('x-amz-date'): + interesting_headers['date'] = '' + + # if you're using expires for query string auth, then it trumps date + # (and x-amz-date) + if expires: + interesting_headers['date'] = str(expires) + + sorted_header_keys = interesting_headers.keys() + sorted_header_keys.sort() + + buf = "%s\n" % method + for header_key in sorted_header_keys: + if header_key.startswith(AMAZON_HEADER_PREFIX): + buf += "%s:%s\n" % (header_key, interesting_headers[header_key]) + else: + buf += "%s\n" % interesting_headers[header_key] + + # append the bucket if it exists + if bucket != "": + buf += "/%s" % bucket + + # add the key. even if it doesn't exist, add the slash + buf += "/%s" % urllib.quote_plus(key) + + # handle special query string arguments + + if query_args.has_key("acl"): + buf += "?acl" + elif query_args.has_key("torrent"): + buf += "?torrent" + elif query_args.has_key("logging"): + buf += "?logging" + elif query_args.has_key("location"): + buf += "?location" + + return buf + +# computes the base64'ed hmac-sha hash of the canonical string and the secret +# access key, optionally urlencoding the result +def encode(aws_secret_access_key, str, urlencode=False): + b64_hmac = base64.encodestring(hmac.new(aws_secret_access_key, str, sha).digest()).strip() + if urlencode: + return urllib.quote_plus(b64_hmac) + else: + return b64_hmac + +def merge_meta(headers, metadata): + final_headers = headers.copy() + for k in metadata.keys(): + final_headers[METADATA_PREFIX + k] = metadata[k] + + return final_headers + +# builds the query arg string +def query_args_hash_to_string(query_args): + query_string = "" + pairs = [] + for k, v in query_args.items(): + piece = k + if v != None: + piece += "=%s" % urllib.quote_plus(str(v)) + pairs.append(piece) + + return '&'.join(pairs) + + +class CallingFormat: + PATH = 1 + SUBDOMAIN = 2 + VANITY = 3 + + def build_url_base(protocol, server, port, bucket, calling_format): + url_base = '%s://' % protocol + + if bucket == '': + url_base += server + elif calling_format == CallingFormat.SUBDOMAIN: + url_base += "%s.%s" % (bucket, server) + elif calling_format == CallingFormat.VANITY: + url_base += bucket + else: + url_base += server + + url_base += ":%s" % port + + if (bucket != '') and (calling_format == CallingFormat.PATH): + url_base += "/%s" % bucket + + return url_base + + build_url_base = staticmethod(build_url_base) + + + +class Location: + DEFAULT = None + EU = 'EU' + + + +class AWSAuthConnection: + def __init__(self, aws_access_key_id, aws_secret_access_key, is_secure=True, + server=DEFAULT_HOST, port=None, calling_format=CallingFormat.SUBDOMAIN): + + if not port: + port = PORTS_BY_SECURITY[is_secure] + + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + self.is_secure = is_secure + self.server = server + self.port = port + self.calling_format = calling_format + + def create_bucket(self, bucket, headers={}): + return Response(self._make_request('PUT', bucket, '', {}, headers)) + + def create_located_bucket(self, bucket, location=Location.DEFAULT, headers={}): + if location == Location.DEFAULT: + body = "" + else: + body = "" + \ + location + \ + "" + return Response(self._make_request('PUT', bucket, '', {}, headers, body)) + + def check_bucket_exists(self, bucket): + return self._make_request('HEAD', bucket, '', {}, {}) + + def list_bucket(self, bucket, options={}, headers={}): + return ListBucketResponse(self._make_request('GET', bucket, '', options, headers)) + + def delete_bucket(self, bucket, headers={}): + return Response(self._make_request('DELETE', bucket, '', {}, headers)) + + def put(self, bucket, key, object, headers={}): + if not isinstance(object, S3Object): + object = S3Object(object) + + return Response( + self._make_request( + 'PUT', + bucket, + key, + {}, + headers, + object.data, + object.metadata)) + + def get(self, bucket, key, headers={}): + return GetResponse( + self._make_request('GET', bucket, key, {}, headers)) + + def delete(self, bucket, key, headers={}): + return Response( + self._make_request('DELETE', bucket, key, {}, headers)) + + def get_bucket_logging(self, bucket, headers={}): + return GetResponse(self._make_request('GET', bucket, '', { 'logging': None }, headers)) + + def put_bucket_logging(self, bucket, logging_xml_doc, headers={}): + return Response(self._make_request('PUT', bucket, '', { 'logging': None }, headers, logging_xml_doc)) + + def get_bucket_acl(self, bucket, headers={}): + return self.get_acl(bucket, '', headers) + + def get_acl(self, bucket, key, headers={}): + return GetResponse( + self._make_request('GET', bucket, key, { 'acl': None }, headers)) + + def put_bucket_acl(self, bucket, acl_xml_document, headers={}): + return self.put_acl(bucket, '', acl_xml_document, headers) + + def put_acl(self, bucket, key, acl_xml_document, headers={}): + return Response( + self._make_request( + 'PUT', + bucket, + key, + { 'acl': None }, + headers, + acl_xml_document)) + + def list_all_my_buckets(self, headers={}): + return ListAllMyBucketsResponse(self._make_request('GET', '', '', {}, headers)) + + def get_bucket_location(self, bucket): + return LocationResponse(self._make_request('GET', bucket, '', {'location' : None})) + + # end public methods + + def _make_request(self, method, bucket='', key='', query_args={}, headers={}, data='', metadata={}): + + server = '' + if bucket == '': + server = self.server + elif self.calling_format == CallingFormat.SUBDOMAIN: + server = "%s.%s" % (bucket, self.server) + elif self.calling_format == CallingFormat.VANITY: + server = bucket + else: + server = self.server + + path = '' + + if (bucket != '') and (self.calling_format == CallingFormat.PATH): + path += "/%s" % bucket + + # add the slash after the bucket regardless + # the key will be appended if it is non-empty + path += "/%s" % urllib.quote_plus(key) + + + # build the path_argument string + # add the ? in all cases since + # signature and credentials follow path args + if len(query_args): + path += "?" + query_args_hash_to_string(query_args) + + is_secure = self.is_secure + host = "%s:%d" % (server, self.port) + while True: + if (is_secure): + connection = httplib.HTTPSConnection(host) + else: + connection = httplib.HTTPConnection(host) + + final_headers = merge_meta(headers, metadata); + # add auth header + self._add_aws_auth_header(final_headers, method, bucket, key, query_args) + + connection.request(method, path, data, final_headers) + resp = connection.getresponse() + if resp.status < 300 or resp.status >= 400: + return resp + # handle redirect + location = resp.getheader('location') + if not location: + return resp + # (close connection) + resp.read() + scheme, host, path, params, query, fragment \ + = urlparse.urlparse(location) + if scheme == "http": is_secure = True + elif scheme == "https": is_secure = False + else: raise invalidURL("Not http/https: " + location) + if query: path += "?" + query + # retry with redirect + + def _add_aws_auth_header(self, headers, method, bucket, key, query_args): + if not headers.has_key('Date'): + headers['Date'] = time.strftime("%a, %d %b %Y %X GMT", time.gmtime()) + + c_string = canonical_string(method, bucket, key, query_args, headers) + headers['Authorization'] = \ + "AWS %s:%s" % (self.aws_access_key_id, encode(self.aws_secret_access_key, c_string)) + + +class QueryStringAuthGenerator: + # by default, expire in 1 minute + DEFAULT_EXPIRES_IN = 60 + + def __init__(self, aws_access_key_id, aws_secret_access_key, is_secure=True, + server=DEFAULT_HOST, port=None, calling_format=CallingFormat.SUBDOMAIN): + + if not port: + port = PORTS_BY_SECURITY[is_secure] + + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + if (is_secure): + self.protocol = 'https' + else: + self.protocol = 'http' + + self.is_secure = is_secure + self.server = server + self.port = port + self.calling_format = calling_format + self.__expires_in = QueryStringAuthGenerator.DEFAULT_EXPIRES_IN + self.__expires = None + + # for backwards compatibility with older versions + self.server_name = "%s:%s" % (self.server, self.port) + + def set_expires_in(self, expires_in): + self.__expires_in = expires_in + self.__expires = None + + def set_expires(self, expires): + self.__expires = expires + self.__expires_in = None + + def create_bucket(self, bucket, headers={}): + return self.generate_url('PUT', bucket, '', {}, headers) + + def list_bucket(self, bucket, options={}, headers={}): + return self.generate_url('GET', bucket, '', options, headers) + + def delete_bucket(self, bucket, headers={}): + return self.generate_url('DELETE', bucket, '', {}, headers) + + def put(self, bucket, key, object, headers={}): + if not isinstance(object, S3Object): + object = S3Object(object) + + return self.generate_url( + 'PUT', + bucket, + key, + {}, + merge_meta(headers, object.metadata)) + + def get(self, bucket, key, headers={}): + return self.generate_url('GET', bucket, key, {}, headers) + + def delete(self, bucket, key, headers={}): + return self.generate_url('DELETE', bucket, key, {}, headers) + + def get_bucket_logging(self, bucket, headers={}): + return self.generate_url('GET', bucket, '', { 'logging': None }, headers) + + def put_bucket_logging(self, bucket, logging_xml_doc, headers={}): + return self.generate_url('PUT', bucket, '', { 'logging': None }, headers) + + def get_bucket_acl(self, bucket, headers={}): + return self.get_acl(bucket, '', headers) + + def get_acl(self, bucket, key='', headers={}): + return self.generate_url('GET', bucket, key, { 'acl': None }, headers) + + def put_bucket_acl(self, bucket, acl_xml_document, headers={}): + return self.put_acl(bucket, '', acl_xml_document, headers) + + # don't really care what the doc is here. + def put_acl(self, bucket, key, acl_xml_document, headers={}): + return self.generate_url('PUT', bucket, key, { 'acl': None }, headers) + + def list_all_my_buckets(self, headers={}): + return self.generate_url('GET', '', '', {}, headers) + + def make_bare_url(self, bucket, key=''): + full_url = self.generate_url(self, bucket, key) + return full_url[:full_url.index('?')] + + def generate_url(self, method, bucket='', key='', query_args={}, headers={}): + expires = 0 + if self.__expires_in != None: + expires = int(time.time() + self.__expires_in) + elif self.__expires != None: + expires = int(self.__expires) + else: + raise "Invalid expires state" + + canonical_str = canonical_string(method, bucket, key, query_args, headers, expires) + encoded_canonical = encode(self.aws_secret_access_key, canonical_str) + + url = CallingFormat.build_url_base(self.protocol, self.server, self.port, bucket, self.calling_format) + + url += "/%s" % urllib.quote_plus(key) + + query_args['Signature'] = encoded_canonical + query_args['Expires'] = expires + query_args['AWSAccessKeyId'] = self.aws_access_key_id + + url += "?%s" % query_args_hash_to_string(query_args) + + return url + + +class S3Object: + def __init__(self, data, metadata={}): + self.data = data + self.metadata = metadata + +class Owner: + def __init__(self, id='', display_name=''): + self.id = id + self.display_name = display_name + +class ListEntry: + def __init__(self, key='', last_modified=None, etag='', size=0, storage_class='', owner=None): + self.key = key + self.last_modified = last_modified + self.etag = etag + self.size = size + self.storage_class = storage_class + self.owner = owner + +class CommonPrefixEntry: + def __init(self, prefix=''): + self.prefix = prefix + +class Bucket: + def __init__(self, name='', creation_date=''): + self.name = name + self.creation_date = creation_date + +class Response: + def __init__(self, http_response): + self.http_response = http_response + # you have to do this read, even if you don't expect a body. + # otherwise, the next request fails. + self.body = http_response.read() + if http_response.status >= 300 and self.body: + self.message = self.body + else: + self.message = "%03d %s" % (http_response.status, http_response.reason) + + + +class ListBucketResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + if http_response.status < 300: + handler = ListBucketHandler() + xml.sax.parseString(self.body, handler) + self.entries = handler.entries + self.common_prefixes = handler.common_prefixes + self.name = handler.name + self.marker = handler.marker + self.prefix = handler.prefix + self.is_truncated = handler.is_truncated + self.delimiter = handler.delimiter + self.max_keys = handler.max_keys + self.next_marker = handler.next_marker + else: + self.entries = [] + +class ListAllMyBucketsResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + if http_response.status < 300: + handler = ListAllMyBucketsHandler() + xml.sax.parseString(self.body, handler) + self.entries = handler.entries + else: + self.entries = [] + +class GetResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + response_headers = http_response.msg # older pythons don't have getheaders + metadata = self.get_aws_metadata(response_headers) + self.object = S3Object(self.body, metadata) + + def get_aws_metadata(self, headers): + metadata = {} + for hkey in headers.keys(): + if hkey.lower().startswith(METADATA_PREFIX): + metadata[hkey[len(METADATA_PREFIX):]] = headers[hkey] + del headers[hkey] + + return metadata + +class LocationResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + if http_response.status < 300: + handler = LocationHandler() + xml.sax.parseString(self.body, handler) + self.location = handler.location + +class ListBucketHandler(xml.sax.ContentHandler): + def __init__(self): + self.entries = [] + self.curr_entry = None + self.curr_text = '' + self.common_prefixes = [] + self.curr_common_prefix = None + self.name = '' + self.marker = '' + self.prefix = '' + self.is_truncated = False + self.delimiter = '' + self.max_keys = 0 + self.next_marker = '' + self.is_echoed_prefix_set = False + + def startElement(self, name, attrs): + if name == 'Contents': + self.curr_entry = ListEntry() + elif name == 'Owner': + self.curr_entry.owner = Owner() + elif name == 'CommonPrefixes': + self.curr_common_prefix = CommonPrefixEntry() + + + def endElement(self, name): + if name == 'Contents': + self.entries.append(self.curr_entry) + elif name == 'CommonPrefixes': + self.common_prefixes.append(self.curr_common_prefix) + elif name == 'Key': + self.curr_entry.key = self.curr_text + elif name == 'LastModified': + self.curr_entry.last_modified = self.curr_text + elif name == 'ETag': + self.curr_entry.etag = self.curr_text + elif name == 'Size': + self.curr_entry.size = int(self.curr_text) + elif name == 'ID': + self.curr_entry.owner.id = self.curr_text + elif name == 'DisplayName': + self.curr_entry.owner.display_name = self.curr_text + elif name == 'StorageClass': + self.curr_entry.storage_class = self.curr_text + elif name == 'Name': + self.name = self.curr_text + elif name == 'Prefix' and self.is_echoed_prefix_set: + self.curr_common_prefix.prefix = self.curr_text + elif name == 'Prefix': + self.prefix = self.curr_text + self.is_echoed_prefix_set = True + elif name == 'Marker': + self.marker = self.curr_text + elif name == 'IsTruncated': + self.is_truncated = self.curr_text == 'true' + elif name == 'Delimiter': + self.delimiter = self.curr_text + elif name == 'MaxKeys': + self.max_keys = int(self.curr_text) + elif name == 'NextMarker': + self.next_marker = self.curr_text + + self.curr_text = '' + + def characters(self, content): + self.curr_text += content + + +class ListAllMyBucketsHandler(xml.sax.ContentHandler): + def __init__(self): + self.entries = [] + self.curr_entry = None + self.curr_text = '' + + def startElement(self, name, attrs): + if name == 'Bucket': + self.curr_entry = Bucket() + + def endElement(self, name): + if name == 'Name': + self.curr_entry.name = self.curr_text + elif name == 'CreationDate': + self.curr_entry.creation_date = self.curr_text + elif name == 'Bucket': + self.entries.append(self.curr_entry) + + def characters(self, content): + self.curr_text = content + + +class LocationHandler(xml.sax.ContentHandler): + def __init__(self): + self.location = None + self.state = 'init' + + def startElement(self, name, attrs): + if self.state == 'init': + if name == 'LocationConstraint': + self.state = 'tag_location' + self.location = '' + else: self.state = 'bad' + else: self.state = 'bad' + + def endElement(self, name): + if self.state == 'tag_location' and name == 'LocationConstraint': + self.state = 'done' + else: self.state = 'bad' + + def characters(self, content): + if self.state == 'tag_location': + self.location += content diff --git a/digsby/build/s3/cp2s3.py b/digsby/build/s3/cp2s3.py new file mode 100644 index 0000000..82e7556 --- /dev/null +++ b/digsby/build/s3/cp2s3.py @@ -0,0 +1,221 @@ +#__LICENSE_GOES_HERE__ +''' +copies files and directories to an S3 bucket +''' + +from time import time, mktime +from threading import Thread +from cStringIO import StringIO +from traceback import print_exc +from datetime import datetime +from gzip import GzipFile +from optparse import OptionParser +from Queue import Queue + +import mimetypes +import os +import os.path + +import S3 + +mimetypes.add_type("application/x-xpinstall", ".xpi") + +DEFAULT_NUM_THREADS = 30 + +# Note: On Unix platforms, sockets are also file handles, +# so we'll end up getting a 'too many open file handles' error +# if the number of threads is set to high +if not os.name == 'nt': + DEFAULT_NUM_THREADS = 5 + +def options_parser(): + parser = OptionParser() + opt = parser.add_option + + opt('-v', '--verbose', action = 'store_true') + opt('-y', '--dry-run', action = 'store_true') + + opt('-d', '--digest', action = 'store_true', dest = 'save_hash') + opt('-k', '--key') + opt('-s', '--secret') + opt('-b', '--bucket') + + opt('-c', '--compress', action = 'append') + opt('-a', '--compress-all', action = 'store_true') + opt('-m', '--mimetypes', action = 'store_true') + opt('-p', '--public-read', action = 'store_true') + opt('-z', '--size', action = 'store_true', dest = 'save_size') + opt('-t', '--time', action = 'store_true', dest = 'save_time') + opt('-r', '--recursive', action = 'store_true') + + opt('--threads', type = 'int') + + parser.set_defaults(threads = DEFAULT_NUM_THREADS) + return parser + +def check_opts(parser, opts, args): + if opts.bucket is None: + parser.error('please specify a bucket with -b or --bucket') + if opts.key is None: + parser.error('please specify an API key with -k or --key') + if opts.secret is None: + parser.error('please specify an API secret with -s or --secret') + + # rest of the args are paths to files or directories to upload + if not args: + parser.error('please specify one or more files or directories to upload') + else: + for filepath in args: + if not os.path.exists(filepath): + parser.error('file or directory does not exist: %s' % filepath) + +def utc_mtime(filename): + file_mtime = os.path.getmtime(filename) + return int(mktime(datetime.utcfromtimestamp(file_mtime).timetuple())) + +def should_compress(filename): + '''With --compress-all, --compress "extension" means "don't compress."''' + + filepart, extension = os.path.splitext(filename) + if extension.startswith('.'): + extension = extension[1:] + + return bool(opts.compress_all) ^ (extension in opts.compress) + +def _upload_file(conn, bucket, filename): + filedata = open(filename, 'rb').read() + headers = {} + + if opts.save_size: + headers.update({'x-amz-meta-size': str(os.path.getsize(filename))}) + + if opts.save_hash: + headers.update({'x-amz-meta-sha1_hash': hashlib.sha1(filedata).hexdigest()}) + + if opts.public_read: + headers.update({'x-amz-acl': 'public-read'}) + + if opts.save_time: + headers.update({'x-amz-meta-mtime': str(utc_mtime(filename))}) + + if opts.mimetypes: + mimetype, encoding = mimetypes.guess_type(filename) + if mimetype is not None: + headers.update({'Content-Type': mimetype}) + + compressed = should_compress(filename) + if compressed: + headers.update({'Content-Encoding': 'gzip'}) + origdata, filedata = filedata, gzip(filedata) + + compressed_info = (' compressed from %d bytes' % len(origdata)) if compressed else '' + log(' -> %s (%d bytes%s)' % (filename, len(filedata), compressed_info)) + + if not opts.dry_run: + for x in xrange(5): + try: + resp = conn.put(bucket, filename, filedata, headers) + except Exception, e: + log(' Error on %r: %r' % (filename, e)) + continue + + log(' <- %s %s' % (filename, resp.message)) + if resp.http_response.status == 200: + break + else: + raise AssertionError('could not upload %r' % filename) + +def enqueue(queue, filename): + count = 0 + if os.path.isdir(filename): + if opts.recursive: + for f in os.listdir(filename): + count += enqueue(queue, os.path.join(filename, f).replace('\\', '/')) + elif os.path.isfile(filename): + queue.put(filename) + count += 1 + else: + raise AssertionError('not a file and not a dir: %r' % filename) + + return count + +def log(msg): + if opts.verbose: + print msg + +def gzip(s): + f = StringIO() + g = GzipFile(mode='wb', fileobj=f) + g.write(s) + g.close() + return f.getvalue() + +def handle_options(): + global opts + parser = options_parser() + opts, args = parser.parse_args() + + check_opts(parser, opts, args) + opts.compress = set(opts.compress or []) + return args + +class S3UploadWorker(Thread): + def __init__(self, queue): + Thread.__init__(self) + self.queue = queue + self.error = None + + @property + def conn(self): + try: + return self._conn + except AttributeError: + self._conn = S3.AWSAuthConnection(opts.key, opts.secret) + return self._conn + + def run(self): + try: + while True: + filepath = self.queue.get() + try: + _upload_file(self.conn, opts.bucket, filepath) + finally: + self.queue.task_done() + except Exception, e: + print repr(e) + print_exc() + os._exit(1) # quit on any errors + +def upload_file(file): + return upload_files([file]) + +def upload_files(files): + requests = Queue() + + num_files = sum(enqueue(requests, filepath) for filepath in files) + log('uploading %d files' % num_files) + S3UploadWorker.total = num_files + + pool = [] + for x in xrange(min(requests.qsize(), opts.threads)): + t = S3UploadWorker(requests) + t.setDaemon(True) + pool.append(t) + + for t in pool: + t.start() + + requests.join() + +def main(): + args = handle_options() + timetook = timeit(lambda: upload_files(args)) + print 'took %ss' % timetook + +def timeit(func): + before = time() + func() + return time() - before + +if __name__ == '__main__': + main() diff --git a/digsby/config.py b/digsby/config.py new file mode 100755 index 0000000..4882783 --- /dev/null +++ b/digsby/config.py @@ -0,0 +1,20 @@ +''' +Not sure if this is the best name, but we need a place for getting the platform dir +that won't indirectly import cgui, as we need this to determine where to look for it. +''' + +import sys + +if sys.platform.startswith("win"): + platformName = "win" +elif sys.platform.startswith("darwin"): + platformName = "mac" +else: + platformName = "gtk" + +newMenubar = 0 +nativeIMWindow = (platformName == "mac") + + +platform = platformName # phase out camelCase + diff --git a/digsby/devplugins/digsby_gntp/__init__.py b/digsby/devplugins/digsby_gntp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/devplugins/digsby_gntp/gntp/__init__.py b/digsby/devplugins/digsby_gntp/gntp/__init__.py new file mode 100644 index 0000000..0e98bb7 --- /dev/null +++ b/digsby/devplugins/digsby_gntp/gntp/__init__.py @@ -0,0 +1,460 @@ +#__LICENSE_GOES_HERE__ + +import re +import hashlib +import time +import platform + +__version__ = '0.2' + +class BaseError(Exception): + pass + +class ParseError(BaseError): + def gntp_error(self): + error = GNTPError(errorcode=500,errordesc='Error parsing the message') + return error.encode() + +class AuthError(BaseError): + def gntp_error(self): + error = GNTPError(errorcode=400,errordesc='Error with authorization') + return error.encode() + +class UnsupportedError(BaseError): + def gntp_error(self): + error = GNTPError(errorcode=500,errordesc='Currently unsupported by gntp.py') + return error.encode() + +class _GNTPBase(object): + def __init__(self,messagetype): + self.info = { + 'version':'1.0', + 'messagetype':messagetype, + 'encryptionAlgorithmID':None + } + self.requiredHeaders = [] + self.headers = {} + def add_origin_info(self): + self.add_header('Origin-Machine-Name',platform.node()) + self.add_header('Origin-Software-Name','gntp.py') + self.add_header('Origin-Software-Version',__version__) + self.add_header('Origin-Platform-Name',platform.system()) + self.add_header('Origin-Platform-Version',platform.platform()) + def send(self): + print self.encode() + def __str__(self): + return self.encode() + def parse_info(self,data): + ''' + Parse the first line of a GNTP message to get security and other info values + @param data: GNTP Message + @return: GNTP Message information in a dictionary + ''' + #GNTP/ [:][ :.] + match = re.match('GNTP/(?P\d+\.\d+) (?PREGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)'+ + ' (?P[A-Z0-9]+(:(?P[A-F0-9]+))?) ?'+ + '((?P[A-Z0-9]+):(?P[A-F0-9]+).(?P[A-F0-9]+))?\r\n', data,re.IGNORECASE) + + if not match: + raise ParseError('ERROR_PARSING_INFO_LINE') + + info = match.groupdict() + if info['encryptionAlgorithmID'] == 'NONE': + info['encryptionAlgorithmID'] = None + + return info + def set_password(self,password,encryptAlgo='MD5'): + ''' + Set a password for a GNTP Message + @param password: Null to clear password + @param encryptAlgo: Supports MD5,SHA1,SHA256,SHA512 + @todo: Support other hash functions + ''' + hash = { + 'MD5': hashlib.md5, + 'SHA1': hashlib.sha1, + 'SHA256': hashlib.sha256, + 'SHA512': hashlib.sha512, + } + + self.password = password + self.encryptAlgo = encryptAlgo.upper() + if not password: + self.info['encryptionAlgorithmID'] = None + self.info['keyHashAlgorithm'] = None; + return + if not self.encryptAlgo in hash.keys(): + raise UnsupportedError('INVALID HASH "%s"'%self.encryptAlgo) + + hashfunction = hash.get(self.encryptAlgo) + + password = password.encode('utf8') + seed = time.ctime() + salt = hashfunction(seed).hexdigest() + saltHash = hashfunction(seed).digest() + keyBasis = password+saltHash + key = hashfunction(keyBasis).digest() + keyHash = hashfunction(key).hexdigest() + + self.info['keyHashAlgorithmID'] = self.encryptAlgo + self.info['keyHash'] = keyHash.upper() + self.info['salt'] = salt.upper() + def _decode_hex(self,value): + ''' + Helper function to decode hex string to `proper` hex string + @param value: Value to decode + @return: Hex string + ''' + result = '' + for i in range(0,len(value),2): + tmp = int(value[i:i+2],16) + result += chr(tmp) + return result + def _decode_binary(self,rawIdentifier,identifier): + rawIdentifier += '\r\n\r\n' + dataLength = int(identifier['Length']) + pointerStart = self.raw.find(rawIdentifier)+len(rawIdentifier) + pointerEnd = pointerStart + dataLength + data = self.raw[pointerStart:pointerEnd] + if not len(data) == dataLength: + raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s'%(dataLength,len(data))) + return data + def validate_password(self,password): + ''' + Validate GNTP Message against stored password + ''' + self.password = password + if password == None: raise BaseError() + keyHash = self.info.get('keyHash',None) + if keyHash is None and self.password is None: + return True + if keyHash is None: + raise AuthError('Invalid keyHash') + if self.password is None: + raise AuthError('Missing password') + + password = self.password.encode('utf8') + saltHash = self._decode_hex(self.info['salt']) + + keyBasis = password+saltHash + key = hashlib.md5(keyBasis).digest() + keyHash = hashlib.md5(key).hexdigest() + + if not keyHash.upper() == self.info['keyHash'].upper(): + raise AuthError('Invalid Hash') + return True + def validate(self): + ''' + Verify required headers + ''' + for header in self.requiredHeaders: + if not self.headers.get(header,False): + raise ParseError('Missing Notification Header: '+header) + + def format_info(self): + ''' + Generate info line for GNTP Message + @return: Info line string + ''' + info = u'GNTP/%s %s'%( + self.info.get('version'), + self.info.get('messagetype'), + ) + if self.info.get('encryptionAlgorithmID',None): + info += ' %s:%s'%( + self.info.get('encryptionAlgorithmID'), + self.info.get('ivValue'), + ) + else: + info+=' NONE' + + if self.info.get('keyHashAlgorithmID',None): + info += ' %s:%s.%s'%( + self.info.get('keyHashAlgorithmID'), + self.info.get('keyHash'), + self.info.get('salt') + ) + + return info + def parse_dict(self,data): + ''' + Helper function to parse blocks of GNTP headers into a dictionary + @param data: + @return: Dictionary of headers + ''' + dict = {} + for line in data.split('\r\n'): + match = re.match('([\w-]+):(.+)', line) + if not match: continue + + key = match.group(1).strip() + val = match.group(2).strip() + dict[key] = val + #print key,'\t\t\t',val + return dict + def add_header(self,key,value): + if isinstance(value, unicode): + self.headers[key] = value + else: + self.headers[key] = unicode('%s'%value,'utf8','replace') + def decode(self,data,password=None): + ''' + Decode GNTP Message + @param data: + ''' + self.password = password + self.raw = data + parts = self.raw.split('\r\n\r\n') + self.info = self.parse_info(data) + self.headers = self.parse_dict(parts[0]) + def encode(self): + ''' + Encode a GNTP Message + @return: GNTP Message ready to be sent + ''' + self.validate() + EOL = u'\r\n' + + message = self.format_info() + EOL + #Headers + for k,v in self.headers.iteritems(): + message += u'%s: %s%s'%(k,v,EOL) + + message += EOL + return message.encode('utf8') +class GNTPRegister(_GNTPBase): + ''' + GNTP Registration Message + ''' + def __init__(self,data=None,password=None): + ''' + @param data: (Optional) See decode() + @param password: (Optional) Password to use while encoding/decoding messages + ''' + _GNTPBase.__init__(self,'REGISTER') + self.notifications = [] + self.resources = {} + + self.requiredHeaders = [ + 'Application-Name', + 'Notifications-Count' + ] + self.requiredNotification = [ + 'Notification-Name', + ] + if data: + self.decode(data,password) + else: + self.set_password(password) + self.add_header('Application-Name', 'pygntp') + self.add_header('Notification-Count', 0) + self.add_origin_info() + def validate(self): + ''' + Validate required headers and validate notification headers + ''' + for header in self.requiredHeaders: + if not self.headers.get(header,False): + raise ParseError('Missing Registration Header: '+header) + for notice in self.notifications: + for header in self.requiredNotification: + if not notice.get(header,False): + raise ParseError('Missing Notification Header: '+header) + def decode(self,data,password): + ''' + Decode existing GNTP Registration message + @param data: Message to decode. + ''' + self.raw = data + parts = self.raw.split('\r\n\r\n') + self.info = self.parse_info(data) + if self.info.get('encryptionAlgorithmID', None) is not None: + self.validate_password(password) + self.headers = self.parse_dict(parts[0]) + + for i,part in enumerate(parts): + if i==0: continue #Skip Header + if part.strip()=='': continue + notice = self.parse_dict(part) + if notice.get('Notification-Name',False): + self.notifications.append(notice) + elif notice.get('Identifier',False): + notice['Data'] = self._decode_binary(part,notice) + #open('register.png','wblol').write(notice['Data']) + self.resources[ notice.get('Identifier') ] = notice + + def add_notification(self,name,enabled=True): + ''' + Add new Notification to Registration message + @param name: Notification Name + @param enabled: Default Notification to Enabled + ''' + notice = {} + notice['Notification-Name'] = u'%s'%name + notice['Notification-Enabled'] = u'%s'%enabled + + self.notifications.append(notice) + self.add_header('Notifications-Count', len(self.notifications)) + def encode(self): + ''' + Encode a GNTP Registration Message + @return: GNTP Registration Message ready to be sent + ''' + self.validate() + EOL = u'\r\n' + + message = self.format_info() + EOL + #Headers + for k,v in self.headers.iteritems(): + message += u'%s: %s%s'%(k,v,EOL) + + #Notifications + if len(self.notifications)>0: + for notice in self.notifications: + message += EOL + for k,v in notice.iteritems(): + message += u'%s: %s%s'%(k,v,EOL) + + message += EOL + return message + +class GNTPNotice(_GNTPBase): + ''' + GNTP Notification Message + ''' + def __init__(self,data=None,app=None,name=None,title=None,password=None): + ''' + + @param data: (Optional) See decode() + @param app: (Optional) Set Application-Name + @param name: (Optional) Set Notification-Name + @param title: (Optional) Set Notification Title + @param password: (Optional) Password to use while encoding/decoding messages + ''' + _GNTPBase.__init__(self,'NOTIFY') + self.resources = {} + + self.requiredHeaders = [ + 'Application-Name', + 'Notification-Name', + 'Notification-Title' + ] + if data: + self.decode(data,password) + else: + self.set_password(password) + if app: + self.add_header('Application-Name', app) + if name: + self.add_header('Notification-Name', name) + if title: + self.add_header('Notification-Title', title) + self.add_origin_info() + def decode(self,data,password): + ''' + Decode existing GNTP Notification message + @param data: Message to decode. + ''' + self.raw = data + parts = self.raw.split('\r\n\r\n') + self.info = self.parse_info(data) + if self.info.get('encryptionAlgorithmID', None) is not None: + self.validate_password(password) + self.headers = self.parse_dict(parts[0]) + + for i,part in enumerate(parts): + if i==0: continue #Skip Header + if part.strip()=='': continue + notice = self.parse_dict(part) + if notice.get('Identifier',False): + notice['Data'] = self._decode_binary(part,notice) + #open('notice.png','wblol').write(notice['Data']) + self.resources[ notice.get('Identifier') ] = notice + def encode(self): + ''' + Encode a GNTP Notification Message + @return: GNTP Notification Message ready to be sent + ''' + self.validate() + EOL = u'\r\n' + + message = self.format_info() + EOL + #Headers + for k,v in self.headers.iteritems(): + message += u'%s: %s%s'%(k,v,EOL) + + message += EOL + return message.encode('utf8') + +class GNTPSubscribe(_GNTPBase): + def __init__(self,data=None,password=None): + _GNTPBase.__init__(self, 'SUBSCRIBE') + self.requiredHeaders = [ + 'Subscriber-ID', + 'Subscriber-Name', + ] + if data: + self.decode(data,password) + else: + self.set_password(password) + self.add_origin_info() + +class GNTPOK(_GNTPBase): + def __init__(self,data=None,action=None): + ''' + @param data: (Optional) See _GNTPResponse.decode() + @param action: (Optional) Set type of action the OK Response is for + ''' + _GNTPBase.__init__(self,'-OK') + self.requiredHeaders = ['Response-Action'] + if data: + self.decode(data) + if action: + self.add_header('Response-Action', action) + self.add_origin_info() + +class GNTPError(_GNTPBase): + def __init__(self,data=None,errorcode=None,errordesc=None): + ''' + @param data: (Optional) See _GNTPResponse.decode() + @param errorcode: (Optional) Error code + @param errordesc: (Optional) Error Description + ''' + _GNTPBase.__init__(self,'-ERROR') + self.requiredHeaders = ['Error-Code','Error-Description'] + if data: + self.decode(data) + if errorcode: + self.add_header('Error-Code', errorcode) + self.add_header('Error-Description', errordesc) + self.add_origin_info() + def error(self): + return self.headers['Error-Code'],self.headers['Error-Description'] + +def parse_gntp(data,password=None,debug=False): + ''' + Attempt to parse a message as a GNTP message + @param data: Message to be parsed + @param password: Optional password to be used to verify the message + @param debug: Print out extra debugging information + ''' + match = re.match('GNTP/(?P\d+\.\d+) (?PREGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',data,re.IGNORECASE) + if not match: + if debug: + print '----' + print self.data + print '----' + raise ParseError('INVALID_GNTP_INFO') + info = match.groupdict() + if info['messagetype'] == 'REGISTER': + return GNTPRegister(data,password=password) + elif info['messagetype'] == 'NOTIFY': + return GNTPNotice(data,password=password) + elif info['messagetype'] == 'SUBSCRIBE': + return GNTPSubscribe(data,password=password) + elif info['messagetype'] == '-OK': + return GNTPOK(data) + elif info['messagetype'] == '-ERROR': + return GNTPError(data) + if debug: print info + raise ParseError('INVALID_GNTP_MESSAGE') diff --git a/digsby/devplugins/digsby_gntp/gntp/notifier.py b/digsby/devplugins/digsby_gntp/gntp/notifier.py new file mode 100644 index 0000000..12f9d42 --- /dev/null +++ b/digsby/devplugins/digsby_gntp/gntp/notifier.py @@ -0,0 +1,130 @@ +#__LICENSE_GOES_HERE__ + +""" +A Python module that uses GNTP to post messages +Mostly mirrors the Growl.py file that comes with Mac Growl +http://code.google.com/p/growl/source/browse/Bindings/python/Growl.py +""" +import gntp +import socket + +class GrowlNotifier(object): + applicationName = 'Python GNTP' + notifications = [] + defaultNotifications = [] + applicationIcon = None + passwordHash = 'MD5' + + #GNTP Specific + debug = False + password = None + hostname = None + port = 23053 + + def __init__(self, applicationName=None, notifications=None, defaultNotifications=None, applicationIcon=None, hostname=None, password=None, port=None, debug=False): + if applicationName: + self.applicationName = applicationName + assert self.applicationName, 'An application name is required.' + + if notifications: + self.notifications = list(notifications) + assert self.notifications, 'A sequence of one or more notification names is required.' + + if defaultNotifications is not None: + self.defaultNotifications = list(defaultNotifications) + elif not self.defaultNotifications: + self.defaultNotifications = list(self.notifications) + + if applicationIcon is not None: + self.applicationIcon = self._checkIcon(applicationIcon) + elif self.applicationIcon is not None: + self.applicationIcon = self._checkIcon(self.applicationIcon) + + #GNTP Specific + if password: + self.password = password + + if hostname: + self.hostname = hostname + assert self.hostname, 'Requires valid hostname' + + if port: + self.port = int(port) + assert isinstance(self.port,int), 'Requires valid port' + + if debug: + self.debug = debug + + def _checkIcon(self, data): + ''' + Check the icon to see if it's valid + @param data: + @todo Consider checking for a valid URL + ''' + return data + + def register(self): + ''' + Send GNTP Registration + ''' + register = gntp.GNTPRegister() + register.add_header('Application-Name',self.applicationName) + for notification in self.notifications: + enabled = notification in self.defaultNotifications + register.add_notification(notification,enabled) + if self.applicationIcon: + register.add_header('Application-Icon',self.applicationIcon) + if self.password: + register.set_password(self.password,self.passwordHash) + response = self.send('register',register.encode()) + if isinstance(response,gntp.GNTPOK): return True + return response.error() + + def notify(self, noteType, title, description, icon=None, sticky=False, priority=None): + ''' + Send a GNTP notifications + ''' + assert noteType in self.notifications + notice = gntp.GNTPNotice() + notice.add_header('Application-Name',self.applicationName) + notice.add_header('Notification-Name',noteType) + notice.add_header('Notification-Title',title) + if self.password: + notice.set_password(self.password,self.passwordHash) + if sticky: + notice.add_header('Notification-Sticky',sticky) + if priority: + notice.add_header('Notification-Priority',priority) + if icon: + notice.add_header('Notification-Icon',self._checkIcon(icon)) + if description: + notice.add_header('Notification-Text',description) + response = self.send('notify',notice.encode()) + if isinstance(response,gntp.GNTPOK): return True + return response.error() + def subscribe(self,id,name,port): + sub = gntp.GNTPSubscribe() + sub.add_header('Subscriber-ID',id) + sub.add_header('Subscriber-Name',name) + sub.add_header('Subscriber-Port',port) + if self.password: + sub.set_password(self.password,self.passwordHash) + response = self.send('subscribe',sub.encode()) + if isinstance(response,gntp.GNTPOK): return True + return response.error() + def send(self,type,data): + ''' + Send the GNTP Packet + ''' + if self.debug: + print 'To: %s:%s <%s>'%(self.hostname,self.port,type) + print '\n',data,'\n' + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((self.hostname,self.port)) + s.send(data) + response = gntp.parse_gntp(s.recv(1024)) + s.close() + if self.debug: + print 'From: %s:%s <%s>'%(self.hostname,self.port,response.__class__) + print '\n',response,'\n' + return response diff --git a/digsby/devplugins/digsby_gntp/info.yaml b/digsby/devplugins/digsby_gntp/info.yaml new file mode 100644 index 0000000..90b43a2 --- /dev/null +++ b/digsby/devplugins/digsby_gntp/info.yaml @@ -0,0 +1,8 @@ +name: 'Digsby-GNTP' +path: 'digsby_gntp' +type: 'pure' + +entry_points: + digsby.profile.addons: + growl_recv: digsby_gntp.receiver:GrowlReceiver + growl_send: digsby_gntp.sender:GrowlSender diff --git a/digsby/devplugins/digsby_gntp/receiver.py b/digsby/devplugins/digsby_gntp/receiver.py new file mode 100644 index 0000000..04e735e --- /dev/null +++ b/digsby/devplugins/digsby_gntp/receiver.py @@ -0,0 +1,178 @@ +#__LICENSE_GOES_HERE__ + +import traceback +import logging +import codecs +import common.AsyncSocket as AS +import peak.util.addons as addons +import lxml.etree as etree + +import gntp + +import wx +import email +import util +import common +import gui.toast as toast + +log = logging.getLogger("gntp.recv") + +# Cross domain policy in case flash shows up and wants to give us notifications +# Accepts incoming on port 23053 (growl port) +cross_domain_policy = '''\ + + + + + +''' + +def get_cross_domain_policy(): + return cross_domain_policy % common.pref("gntp.port", type = int, default = 23053) + +class GrowlServerSocket(AS.AsyncSocket): + terminator = '\r\n\r\n' + expected_chunks = 1 + received_chunks = 0 + + def collect_incoming_data(self, data): + retval = super(GrowlServerSocket, self).collect_incoming_data(data) + + if self.data.endswith('\0'): + #some kind of data from flash. most likely a request for cross domain policy + self.found_terminator(flash = True) + + return retval + + def handle_flash_data(self): + ''' + The GNTP project has a flash-based JS connector for allowing + javascript apps to send growl notifies to localhost. The only + known "flash-only" thing they do is request a cross-domain policy. + Handle that here. + ''' + data, self.data = self.data, '' + data = data.strip('\0') + log.debug("got flash data: %r", data) + try: + doc = etree.fromstring(data) + except Exception, e: + raise e + + if doc.tag == 'policy-file-request': + self.push(codecs.BOM_UTF8 + get_cross_domain_policy() + '\0') + + def found_terminator(self, flash = False): + self.set_terminator('\r\n\r\n') + + if flash: + return self.handle_flash_data() + else: + if len(self.data) == 0: + return + + self.data += self.terminator + + if self.received_chunks == 0: + try: + headers = email.message_from_string(self.data.split('\r\n', 1)[1]) + except Exception as e: + self.push(str(gntp.GNTPError(errorcode=500, errordesc='Unknown server error: %r' % repr(e)))) + return + else: + self.expected_chunks = int(headers.get('Notifications-Count', '0')) + 1 + + self.received_chunks += 1 + if self.expected_chunks > self.received_chunks: + return + + self.received_chunks = 0 + + data, self.data = self.data, '' + log.debug("received growl message: %r / %r", self, data) + + result = None + try: + result = on_growl_data(data) + except gntp.BaseError as e: + traceback.print_exc() + self.push(e.gntp_error()) + except Exception as e: + traceback.print_exc() + self.push(str(gntp.GNTPError(errorcode=500, errordesc='Unknown server error: %r' % repr(e)))) + else: + if result is not None: + self.push(result) + + self.close_when_done() + + def push(self, data): + log.debug("Sending: %r", data) + return super(GrowlServerSocket, self).push(data) + + def handle_close(self): + super(GrowlServerSocket, self).handle_close() + self.close() + +class GrowlListenServer(AS.AsyncServer): + SocketClass = GrowlServerSocket + +class GrowlReceiver(addons.AddOn): + _did_setup = False + def setup(self): + if self._did_setup: + return + self._did_setup = True + + try: + self.server = GrowlListenServer() + self.server.bind(common.pref("gntp.host", type = str, default = ''), + common.pref("gntp.port", type = int, default = 23053)) + + self.server.listen(5) + except Exception as e: + log.error("Error setting up growl listen server: %r", e) + self.server = None + self._did_setup = False + else: + log.info("Growl server listening on %s:%d", *self.server.getsockname()) + +def on_growl_data(data): + gmsg = e = None + for password in common.pref("gntp.password_list", type=list, default = []) + [None]: + try: + gmsg = gntp.parse_gntp(data, password) + except gntp.BaseError as e: + continue + else: + e = None + break + else: + if e is not None: + raise e + + headers = {} + for key, value in gmsg.headers.items(): +# value_chunks = email.Header.decode_header(value) +# value = ''.join(chunk.decode(charset) for (chunk, charset) in value_chunks) + headers[key.lower()] = value.decode('utf8') + + on_growl_message(util.Storage(info = gmsg.info, headers = headers, resources = gmsg.resources)) + + return str(gntp.GNTPOK(action = gmsg.info['messagetype'])) + +def on_growl_message(msg): + if msg.info['messagetype'] != 'NOTIFY': + return + @wx.CallAfter + def guithread(): + url = None + + toast.popup(header=msg.headers.get('notification-title', ''), + minor=msg.headers.get('notification-text', ''), + sticky = (msg.headers.get('notification-sticky', 'False') == 'True' or + common.pref('gntp.force_sticky', type=bool, default = False)), + _growlinfo=(msg.info, msg.headers, msg.resources), + popupid = (msg.headers.get('application-name', ''), msg.headers.get('notification-name')), + update = 'paged', + onclick=url) diff --git a/digsby/devplugins/digsby_gntp/sender.py b/digsby/devplugins/digsby_gntp/sender.py new file mode 100644 index 0000000..ef49965 --- /dev/null +++ b/digsby/devplugins/digsby_gntp/sender.py @@ -0,0 +1,5 @@ +#__LICENSE_GOES_HERE__ + +import peak.util.addons as addons +class GrowlSender(addons.AddOn): + pass diff --git a/digsby/devplugins/growl/__init__.py b/digsby/devplugins/growl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/devplugins/growl/growl.py b/digsby/devplugins/growl/growl.py new file mode 100644 index 0000000..a3c27ac --- /dev/null +++ b/digsby/devplugins/growl/growl.py @@ -0,0 +1,126 @@ +#__LICENSE_GOES_HERE__ + +from util.packable import Packable +from util.primitives.error_handling import traceguard +from util.primitives import Storage +from gui.toast import popup +from threading import Thread, currentThread +from peak.util.addons import AddOn +from logging import getLogger; log = getLogger('growl') + +GROWL_UDP_PORT = 9887 +GROWL_TYPE_REGISTRATION = 0 # The packet type of registration packets with MD5 authentication. +GROWL_PREF = 'growl.popups' + +def on_packet(packet): + import wx + @wx.CallAfter + def guithread(): + url = None + + extra = getattr(packet, 'extra', None) + if extra: + url = extra.get('url', None) + + popup(header=packet.title, + minor=packet.description, + onclick=url) + +class GrowlAddon(AddOn): + _server_thread = None + + def __init__(self, subject): + self.profile = subject + + did_setup = False + def setup(self): + if self.did_setup: + return + self.did_setup = True + + self.profile.prefs.add_observer(self.on_pref_change, GROWL_PREF) + self.on_pref_change() + + def on_pref_change(self, *a): + enabled = self.profile.prefs.get(GROWL_PREF, False) + if enabled: + self.start_server_thread() + else: + self.stop_server_thread() + + def _server_thread_running(self): + return self._server_thread is not None and self._server_thread.is_alive() + + def start_server_thread(self): + if not self._server_thread_running(): + self._server_thread = thread = Thread(target=_udp_server_loop) + thread.daemon = True + thread.start() + + def stop_server_thread(self): + if self._server_thread_running(): + self._server_thread.die_please = True + self._server_thread = None + +class GrowlPacketHeader(Packable): + fmt = ('protocol_version', 'B', + 'type_notification', 'B', + 'flags', 'H', + 'len_notification', 'H', + 'len_title', 'H', + 'len_description', 'H', + 'len_appname', 'H') + +def unpack_packet(data): + packet_header, data = GrowlPacketHeader.unpack(data) + packet = Storage(extra=Storage()) + + for attr in ('notification', 'title', 'description', 'appname'): + value, data = readstring(data, getattr(packet_header, 'len_' + attr)) + packet[attr] = value + + #md5, data = data[:16], data[16:] + + if packet.description.find('\0') != -1: + packet.description, extra = packet.description.split('\0') + + import simplejson + packet.extra = simplejson.loads(extra) + + return packet, data + + +def readstring(d, slen): + return d[:slen], d[slen:] + +def _udp_server_loop(): + import socket + s = socket.socket(2, 2) + + try: + s.bind(('', GROWL_UDP_PORT)) + except socket.error: + log.error('cannot initialize growl server loop: could not bind to port %r', GROWL_UDP_PORT) + return + + while not getattr(currentThread(), 'die_please', False): + with traceguard: + try: + data, addr = s.recvfrom(1024 * 10) + except socket.timeout: + continue + + if data[0] != chr(1): + # print 'first byte was not 1: %r %s', (data[0], ord(data[0])) + continue + if data[1] == chr(GROWL_TYPE_REGISTRATION): + # print 'ignoring registration packet' + continue + + packet, data = unpack_packet(data) + # assert not data + + on_packet(packet) + +_server_thread = None + diff --git a/digsby/devplugins/growl/info.yaml b/digsby/devplugins/growl/info.yaml new file mode 100644 index 0000000..0988a0d --- /dev/null +++ b/digsby/devplugins/growl/info.yaml @@ -0,0 +1,9 @@ +name: 'Growl' +path: 'growl' +type: 'pure' + + +entry_points: + digsby.profile.addons: + growl_addon: growl.growl:GrowlAddon + diff --git a/digsby/devplugins/instant_gmail/__init__.py b/digsby/devplugins/instant_gmail/__init__.py new file mode 100644 index 0000000..58a045a --- /dev/null +++ b/digsby/devplugins/instant_gmail/__init__.py @@ -0,0 +1,32 @@ +#__LICENSE_GOES_HERE__ + +from logging import getLogger +from peak.util.addons import AddOn +import traceback +import jabber.objects.gmail as gobjs +log = getLogger('plugins.instant_gmail') + +class Digsby_IGmail(AddOn): + def __init__(self, subject): + self.protocol = subject + super(Digsby_IGmail, self).__init__(subject) + + def setup(self, stream): + self.stream = stream + log.debug('setting up geoip') + stream.set_iq_set_handler('new-mail', gobjs.GOOGLE_MAIL_NOTIFY_NS, self.gmail_notify) + self.stream.send(gobjs.make_get(self.protocol)) + + def gmail_notify(self, _stanza): + try: + from services.service_provider import get_provider_for_account + p = get_provider_for_account(self.protocol.account) + e = p.accounts.get('email') + if e and e.enabled: + e.update_now() + except Exception: + traceback.print_exc() + +def session_started(protocol, stream, *_a, **_k): + Digsby_IGmail(protocol).setup(stream) + diff --git a/digsby/devplugins/instant_gmail/info.yaml b/digsby/devplugins/instant_gmail/info.yaml new file mode 100644 index 0000000..23f2937 --- /dev/null +++ b/digsby/devplugins/instant_gmail/info.yaml @@ -0,0 +1,7 @@ +name: 'Instant Gmail' +path: 'instant_igmail' +type: 'pure' + +entry_points: + digsby.jabber.session_started: + instant_gmail: instant_gmail:session_started diff --git a/digsby/devplugins/irc/__init__.py b/digsby/devplugins/irc/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/digsby/devplugins/irc/__init__.py @@ -0,0 +1 @@ + diff --git a/digsby/devplugins/irc/info.yaml b/digsby/devplugins/irc/info.yaml new file mode 100644 index 0000000..e449a35 --- /dev/null +++ b/digsby/devplugins/irc/info.yaml @@ -0,0 +1,16 @@ +name: IRC +name_truncated: irc +popularity: 1 +path: irc.ircclient.IRCClient +username_desc: Nick +form: im +type: im + +skin: + serviceicons: + irc: "res/irc.png" + +defaults: + server: ["irc.freenode.net", 6667] + #has_added_friends: no + #autologin: no diff --git a/digsby/devplugins/irc/ircclient.py b/digsby/devplugins/irc/ircclient.py new file mode 100644 index 0000000..cc95a06 --- /dev/null +++ b/digsby/devplugins/irc/ircclient.py @@ -0,0 +1,364 @@ +''' +Digsby IRC client. + +Here's what IRC traffic from the server looks like: + +:user!hostname.com PRIVMSG yourname :some text + +origin command destination text + +''' +import common +from common import netcall, caps, profile +from irc.ircconversation import RoomConvo, PrivConvo +from util import no_case_dict +from util.Events import EventMixin +from contacts import Group +from util.observe import ObservableDict + +from logging import getLogger +log = getLogger('irc') +rawin, rawout = getLogger('irc.raw.in'), getLogger('irc.raw.out') + +CR, LF = '\x0d', '\x0a' +CRLF = CR + LF +IRC_ENCODING = 'utf8' + +from irccmds import IRCCommander + +class IrcBuddy(common.buddy): + __slots__ = ['modes'] + + service = 'irc' + buddy_icon = icon = None + increase_log_size = Null + history = property(lambda self: iter([])) + status_orb = 'available' + online = True + idle = None + @property + def serviceicon(self): + return self.protocol.serviceicon + caps = [caps.IM] + blocked = False + sms = False + status_message = '' + + + def __init__(self, name, protocol): + self.status = 'available' + common.buddy.__init__(self, name, protocol) + self.modes = {} + +class IrcBuddies(ObservableDict):#no_case_dict): + 'dict subclass that creates IrcBuddy objects on demand' + + def __init__(self, protocol): + ObservableDict.__init__(self) + self.protocol = protocol + + def __getitem__(self, buddy_name): + if not isinstance(buddy_name, (str,unicode)): + raise TypeError('buddy name must be a string (you gave a %s)' % \ + (type(buddy_name))) + + buddy_name = str(buddy_name).lower() + + try: + return ObservableDict.__getitem__(self, buddy_name) + except (KeyError,): + return self.setdefault(buddy_name, IrcBuddy(buddy_name, self.protocol)) + +def _origin_user(origin_string): + return origin_string.split('!',1)[0] + +class ProtocolCommon(object): + def set_message(self, *a, **k): + pass + + def set_buddy_icon(self, *a, **k): + pass + + def set_idle(self, *a, **k): + pass + +class IRCClient(common.protocol, ProtocolCommon): + 'One connection to an IRC server.' + + name = 'irc' + + codes = { + 353: 'name_list', + 366: 'end_name_list', + 372: 'MOTD', + 433: 'nick_used' + } + + user_modes = [ + '@', 'Channel Operator', + '+', 'Voice', + '%', 'Half-operator', + '.', 'Owner', + '~', 'Owner', + '&', 'Protected user', + '!', 'Administrator', + '*', 'Administrator', + ] + + def __init__(self, username, password, msg_hub, + server=('irc.penultimatefire.com',6667)): + common.protocol.__init__(self, username, password, msg_hub) + + assert isinstance(server, tuple) + self.server = server + + self.nick = username + + self.rooms = dict() + self.privates = dict() + + self.buddies = IrcBuddies(self) + + self.cmds = IRCCommander(self) + + self.socket = IrcSocket() + self.socket.bind_event('connected', self._on_connected) + self.socket.bind_event('incoming_line', self.incoming_line) + + self.root_group = Group('Root', self, 'Root') + + def _on_connected(self): + self.self_buddy = self.buddies[self.username] + self.sendraw(self.cmds.nick(self.username)) + self.sendraw('USER %s +iw %s' % (self.username, 'Digsby Dragon')) + self.change_state(self.Statuses.ONLINE) + + def chat_with(self, buddy): + if hasattr(buddy,'name'): buddy = buddy.name + return self.convo_for(buddy) + + def Connect(self, invisible=False): + if invisible: + log.warning('Connect ignoring invisbile argument') + + netcall(lambda: self.socket.connect(self.server)) + + def Disconnect(self): + self.socket.close() + + # + # the following methods handle incoming IRC traffic + # + + def NOTICE(self, *args): + pass + + def PING(self, msg, text, origin): + self.sendraw('PONG :' + text) + + def MOTD(self, msg,text,origin): + pass + + def KICK(self, origin, target, text=None): + pass + + def PRIVMSG(self, dest, text, origin): + from_user = _origin_user(origin) + if dest.lower() == self.username.lower(): + # this is a private message + convo = self.convo_for(from_user) + else: + # this is a message to a room + convo = self.convo_for(dest) + + profile.on_message(convo=convo) + convo.incoming_message(self.buddies[from_user], text) + + def MODE(self, msg, text, origin): + print 'YOu are now ' + text + '! Rawkz' + + def PART(self, room_name, user, origin): + 'Buddy is leaving a room.' + + self.convo_for(room_name).buddy_leave( self.buddies[user] ) + + def QUIT(self, _msg, quit_msg, origin): + 'Buddy is quiting the server.' + + buddy = self.buddies[_origin_user(origin)] + for room in self.rooms.values(): + if buddy in room: + room.buddy_leave(buddy, quit_msg) + + if buddy.name in self.buddies: + del self.buddies[buddy.name] + + + def nick_used(self, msg, text, origin): + self.hub.on_error(self, 'Nickname is already being used.') + + def convo_for(self, name): + if hasattr(name, 'name'): + name = name.name.lower() + + if name.startswith('#'): + try: return self.rooms[name] + except KeyError: + return self.rooms.setdefault(name, RoomConvo(self,name)) + else: + try: return self.privates[name] + except KeyError: + return self.privates.setdefault(name, PrivConvo(self,name)) + + def JOIN(self, args, room_name, origin): + buddy_joining = _origin_user(origin) + + # is this the ack for us joining a room? + if buddy_joining.lower() == self.self_buddy.name.lower(): + profile.on_entered_chat(self.convo_for(room_name)) + else: + # no, it's another user joining a room we're in + self.convo_for(room_name).buddy_join(self.buddies[buddy_joining]) + + def INVITE(self, msg, _notext, fromuser): + 'Someone has invited you to a channel.' + fromuser = _origin_user(fromuser) + + for elem in msg.split(' '): + if elem.startswith('#'): + self.hub.on_invite(self, self.buddies[fromuser], elem) + + def name_list(self, room_name, names, origin): + 'An incoming room list.' + + room_idx = room_name.find('#') + if room_idx != -1: + room_name = room_name[room_idx:] + else: + return log.error('name_list cannot handle ' + room_name) + + log.info('name_list: %s [%s]' % (room_name, names)) + + convo = self.convo_for(room_name) + + # for each name... + for name in names.split(' '): + + modeset = set() + + # find any special "op" characters + for symbol in self.user_modes[::2]: + if name.find(symbol) != -1: + # add user privilege + modeset.add(symbol) + + # replace the special character with nothing + name = name.replace(symbol, '') + + buddy = self.buddies[name] + buddy.modes.setdefault(room_name,set()).clear() + buddy.modes[room_name].update(modeset) + + convo.buddy_join(buddy) + + def NICK(self, none, new_nick, origin): + ''' + An IRC buddy has changed his or her nickname. + + We must be careful to replace the buddy's name, and also the hash + key for the buddy. + ''' + old_nick = _origin_user(origin) + if not old_nick: return + + buddy = self.buddies.pop(old_nick) + buddy.name = new_nick + self.buddies[new_nick] = buddy + + + def join_chat_room(self, chat_room_name, server=None): + if isinstance(chat_room_name, unicode): + room_name = chat_room_name.encode(IRC_ENCODING) + + self.sendraw(self.cmds.join(chat_room_name)) + + def sendcmd(self, func, *args, **kw): + txt = func(*args, **kw) + assert isinstance(txt, str) + rawout.info(txt) + self.push(txt + CRLF) + + def sendraw(self, rawtext): + 'Sends raw text out to the socket.' + + rawout.info(rawtext) + data = rawtext + CRLF + + if isinstance(data, unicode): + data = data.encode(IRC_ENCODING) + self.socket.push(data) + + def incoming_line(self, line): + # log the raw IRC + rawin.info(line) + + if line[0] == ':': + origin, line = line[1:].split(' ', 1) + else: + origin = None + + try: args, text = line.split(' :', 1) + except ValueError: + args = line + text = '' + + # grab function and any arguments + split = args.split(' ', 1) + func = split[0] + message = len(split) > 1 and split[1] or '' + + if hasattr(self, func): + return getattr(self,func)(message,text,origin) + else: + try: + n = int(func) + except: + # make this catch the right error, not all of them! + raise + return + + if n in self.codes: + func = self.codes[n] + if hasattr(self, func): + return getattr(self, func)(message,text,origin) + +class IrcSocket(common.socket, EventMixin): + events = set(( + 'connected', + 'incoming_line', + )) + + def __init__(self): + common.socket.__init__(self) + EventMixin.__init__(self) + + self.set_terminator(CRLF) + self.data = [] + + # + # asyncore methods + # + + def handle_connect(self): + self.event('connected') + common.socket.handle_connect(self) + + def collect_incoming_data(self, data): + self.data.append(data) + + def found_terminator(self): + line = ''.join(self.data) + self.data = [] + + self.event('incoming_line', line) + diff --git a/digsby/devplugins/irc/irccmds.py b/digsby/devplugins/irc/irccmds.py new file mode 100644 index 0000000..c4d3d72 --- /dev/null +++ b/digsby/devplugins/irc/irccmds.py @@ -0,0 +1,133 @@ +#__LICENSE_GOES_HERE__ + +''' +Commands exposed as IRC slash commands. + +Any functions not beginning with an underscore in this module can be called +from the command-line, so be careful! +''' + +class IRCCommander(object): + def __init__(self, irc): self._irc = irc + _echomarker = '~~!echo!~~' + + def part(self, room, exit_message = ''): + 'Leaves a room with an optional bye message.' + + return 'PART %s %s' % (_room_name(room), exit_message) + + def quit(self, quit_message = ''): + 'Disconnects from the server, with an optional goodbye message.' + + return 'QUIT %s' % quit_message + + def nick(self, nickname): + 'Changes your nickname.' + + return 'NICK %s' % nickname + + def join(self, room): + 'Joins a channel.' + + return 'JOIN %s' % _room_name(room) + + j = join + + def invite(self, nickname, channel='$room'): + 'Invites a user to a channel.' + + return 'INVITE %s %s' % (nickname, channel) + + def privmsg(self, name, message): + 'Sends a private message.' + + return 'PRIVMSG %s :%s' % (name, message) + + def kick(self, room, name, message=''): + 'Kick a user with.' + + return 'KICK %s %s%s' % (_room_name(room), name, + ' :' + message if message else '') + + def me(self, message): + 'Sends an action to a room. Crazy IRC.' + + return 'PRIVMSG $room :%sACTION %s%s' % (chr(1), message, chr(1)) + + def leave(self, room='$room', message=''): + 'Leave a channel, with an optional goodbye message.' + room = room if room=='$room' else _room_name(room) + + if message: return 'PART %s :%s' % room, message + else: return 'PART %s' % room + + def raw(self, text): + 'Sends raw text to the server.' + + self._irc.sendraw(text) + + def slap(self, buddy): + if hasattr(buddy, 'name'): + buddy = buddy.name + + return self.me('slaps ' + buddy + ' around a bit with a large trout') + + def help(self, helpon=None): + msg = '' + if helpon: + msg += '\n'.join(['/%s: %s'% (arg, getattr(self,arg.lower()).__doc__) + for arg in helpon.split(' ') if hasattr(self, arg.lower())]) + else: + msg += ' '.join([elem.upper() for elem in dir(self) + if not elem.startswith('_')]) + + return self._echomarker + msg + +# +## +# + def __call__(self, command): + return self._slash_to_irc(command) + + def _slash_to_irc(self, command ): + if not isinstance(command, (str, unicode)): + raise TypeError('exec_slash needs a string') + + command = str(command) + + if not command.startswith('/'): + raise ValueError("Slash command (%s) doesn't start with" + "command" % command) + + if len(command) < 2: + return None + + # do we have a command? + split = command[1:].split(' ',1) + cmd = split[0].lower() + + # for saftey + if cmd.startswith('_') or cmd == 'slash_to_irc': + return None + + if hasattr(self, cmd): + func = getattr(self, cmd) + numargs = func.func_code.co_argcount-1 + args = command[1:].split(' ', numargs)[1:] + print func.func_name, args + try: + return func(*args) + except TypeError: + return self._echomarker + '/%s: Not enough arguments.' % func.__name__ + + +def _room_name(room): + if not isinstance(room, basestring): + room = room.name + + if not room.startswith('#'): + room = '#' + room + + return room + + diff --git a/digsby/devplugins/irc/ircconversation.py b/digsby/devplugins/irc/ircconversation.py new file mode 100644 index 0000000..e7db772 --- /dev/null +++ b/digsby/devplugins/irc/ircconversation.py @@ -0,0 +1,200 @@ +#__LICENSE_GOES_HERE__ + +from common.Conversation import Conversation +import common +from util import callsback + +import string +import re + +from logging import getLogger +log = getLogger('irc') + +mirc_formatting = { + 2: 'b', + 31: 'u', +} + +mirc_colors = ('000000 000000 00007b 009200 ' + 'ff0000 7b0000 9c009c ff7d00 ' + 'ffff00 00ff00 929400 00ffff ' + '0000ff ff00ff 7b7d7b d6d3d6').split(' ') +assert len(mirc_colors) == 16 + +color_code_re = re.compile(r'(\d+)(,\d+)?') + +def mirc_to_html(msg): + 'Converts mIRC formatting codes to HTML.' + + new_msg = '' + toggle = set() + + open_tag, close_tag = '<%s>', '' + + idx = 0 + while idx < len(msg): + ch = msg[idx] + + if ord(ch) in mirc_formatting: + elem = mirc_formatting[ord(ch)] + if elem not in toggle: + new_msg += open_tag % elem + toggle.add(elem) + else: + new_msg += close_tag % elem.split(' ',1)[0] + toggle.remove(elem) + elif ord(ch) == 3: + # colors come as \003foreground,background + col_codes = msg[idx+1:] + match = color_code_re.match(col_codes) + if match: + fg, bg = match.groups() + elem = 'font color="#%s"' % mirc_colors[int(fg)] + if elem not in toggle: + new_msg += open_tag % elem + toggle.add(elem) + else: + new_msg += close_tag % elem.split(' ',1)[0] + toggle.remove(elem) + idx = match.end() + else: + new_msg += ch + idx += 1 + + if len(toggle): + for elem in toggle: + new_msg += close_tag % elem.split(' ',1)[0] + toggle.clear() + + return new_msg + +class IRCConversation(Conversation): + def __init__(self, irc): + Conversation.__init__(self, irc) + self.irc = irc + self.buddies = irc.buddies + self.cmds = irc.cmds + self.self_buddy = self.irc.self_buddy + + def _tobuddy(self, bname): + if isinstance(bname, (str, unicode)): + return self.irc.buddies[str(bname)] + return bname + + def buddy_join(self, buddy): + buddy = self._tobuddy(buddy) + if buddy not in self: + self.system_message(u'%s has joined the room' % buddy.name) + + assert isinstance(buddy, common.buddy) + self.room_list.append(buddy) + + def buddy_leave(self, buddy, leave_msg = ''): + buddy = self._tobuddy(buddy) + if buddy in self.room_list: + self.room_list.remove(buddy) + + msg = '%s left the room' % buddy.name + if leave_msg: + msg += ": " + leave_msg + self.system_message(unicode(msg)) + + def ctcpcmd(self, msg): + sp = chr(1) + assert msg.find(sp) != 1 + + lidx, ridx = msg.find(chr(1)), msg.rfind(chr(1)) + + msg = msg[lidx+1:ridx] + special = msg.split(' ',1) + assert len(special) <= 2 + + cmd = special[0] + msg = special[1] if len(special) == 2 else None + return cmd, msg + + def incoming_message(self, buddy, message): + message = mirc_to_html(message) + if message.find(chr(1)) != -1: + cmd, msg = self.ctcpcmd(message) + if cmd == 'ACTION': + self.system_message(buddy.name + ' ' + msg) + else: + self.received_message(buddy, unicode(message)) + + def send_typing_status(self, status=None): + # um. don't. + pass + + def send(self, *args, **kws): + self.irc.sendraw(*args, **kws) + + def buddy_display_name(self, buddy): + if self.name in buddy.modes: + name = buddy.name + modeset = buddy.modes[self.name] + usermodes = self.irc.user_modes[::2] + for char in usermodes: + if char in modeset: + name = char + name + + return name + else: + return buddy.name + + @callsback + def _send_message(self, message, callback=None): + if not isinstance(message, basestring): + message = message.format_as('plaintext') + + if message.startswith('/') and len(message) > 1: + cmd = self.cmds(message) + + + if cmd: + if cmd.find('$') != -1: + cmd = string.Template(cmd).safe_substitute( + room = self.name + ) + + if cmd.startswith(self.cmds._echomarker): + cmd = cmd[len(self.cmds._echomarker):] + self.system_message(cmd) + return + + return self.irc.sendraw(cmd) + else: + self.send(message[1:]) + return + + self.send(self.cmds.privmsg(self.destination, message)) + + # IRC does not echo + self.buddy_says(self.irc.self_buddy, unicode(message)) + + def exit(self): + self.room_list[:] = [] + Conversation.exit(self) + +class RoomConvo(IRCConversation): + ischat = True + def __init__(self, irc, name): + IRCConversation.__init__(self, irc) + self.destination = self.name = name + self.exit_message = '' + + def exit(self): + self.send(self.cmds.part(self.name, self.exit_message)) + IRCConversation.exit(self) + + +class PrivConvo(IRCConversation): + ischat = False + def __init__(self, irc, name): + IRCConversation.__init__(self, irc) + self.destination = self.name = name + + me, other = self.irc.self_buddy, self.irc.buddies[name] + self.room_list.extend([me, other]) + + diff --git a/digsby/devplugins/irc/ircgui.py b/digsby/devplugins/irc/ircgui.py new file mode 100644 index 0000000..023eff5 --- /dev/null +++ b/digsby/devplugins/irc/ircgui.py @@ -0,0 +1,11 @@ +#__LICENSE_GOES_HERE__ + +from gui.toolbox import GetTextFromUser + +def join_chat_room(irc, join): + val = GetTextFromUser(_('Enter a room name:'), + caption = _('Join IRC Room')) + + if val is not None: + join(val) + diff --git a/digsby/devplugins/irc/res/irc.png b/digsby/devplugins/irc/res/irc.png new file mode 100644 index 0000000..be571bd Binary files /dev/null and b/digsby/devplugins/irc/res/irc.png differ diff --git a/digsby/devplugins/l33t_language/digsby-lt.po b/digsby/devplugins/l33t_language/digsby-lt.po new file mode 100644 index 0000000..1b4e8bf --- /dev/null +++ b/digsby/devplugins/l33t_language/digsby-lt.po @@ -0,0 +1,6443 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Aaron Costello , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: Digsby\n" +"Report-Msgid-Bugs-To: https://translations.launchpad.net/digsby\n" +"POT-Creation-Date: 2011-04-14 17:17-0400\n" +"PO-Revision-Date: 2011-04-14 17:20-0500\n" +"Last-Translator: Aaron Costello \n" +"Language-Team: Digsby Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: L33T\n" +"X-Poedit-Country: USA\n" +"X-Poedit-Basepath: ..\\\n" + +#. $Rev$ +msgctxt "__metadata__" +msgid "__version__" +msgstr "" + +#: ext/src/LoginWindow.cpp:276 +msgid "Digsby Login" +msgstr "D165|3'/ |_061|\\|" + +#: ext/src/LoginWindow.cpp:277 +msgid "Digsby &Username:" +msgstr "|)1G5|3'/ &U53R|\\|4|\\/|3" + +#: ext/src/LoginWindow.cpp:278 +#: src/gui/proxydialog.py:105 +msgid "&Password:" +msgstr "&P455\\/\\/0|2|):" + +#: ext/src/LoginWindow.cpp:279 +msgid "&Save password" +msgstr "&S4\\/3 P455\\/\\/0|2|)" + +#: ext/src/LoginWindow.cpp:280 +#: src/gui/accountdialog.py:354 +msgid "&Auto login" +msgstr "&4|_|70 |_061|\\|" + +#: ext/src/LoginWindow.cpp:281 +msgid "Sign In" +msgstr "&516|\\| 1|\\|" + +#: ext/src/LoginWindow.cpp:282 +msgid "Forgot?" +msgstr "|=0|2607?" + +#: ext/src/LoginWindow.cpp:283 +#: src/plugins/digsby_service_editor/default_ui.py:150 +msgid "New User?" +msgstr "|\\|3\\/\\/ |_|53|2?" + +#: res/actions.yaml:13 +#: res/actions.yaml:126 +msgid "&Buddy Info" +msgstr "&B|_||)|)'/ 1|\\||=0" + +#: res/actions.yaml:16 +#: res/actions.yaml:129 +msgid "&Send IM" +msgstr "&53|\\||) 1|\\/|" + +#: res/actions.yaml:19 +#: res/actions.yaml:132 +msgid "Send &File" +msgstr "53|\\||) &F1|_3" + +#: res/actions.yaml:22 +#: res/actions.yaml:135 +msgid "Send &Email" +msgstr "53|\\||) &3|\\/|41|_" + +#: res/actions.yaml:25 +#: res/actions.yaml:138 +msgid "Send &SMS" +msgstr "53|\\||) &5|\\/|5" + +#: res/actions.yaml:30 +#: res/actions.yaml:58 +#: res/actions.yaml:74 +#: res/actions.yaml:92 +#: res/actions.yaml:108 +#: src/gui/imwin/imwinmenu.py:65 +msgid "&View Past Chats" +msgstr "&V13\\/\\/ P457 (#472" + +#: res/actions.yaml:33 +#: res/actions.yaml:143 +msgid "&Alert Me When..." +msgstr "&4|_3|27 |\\/|3 \\/\\/#3|\\|..." + +#: res/actions.yaml:38 +msgid "Re&name" +msgstr "&R3&N4|\\/|3" + +#: res/actions.yaml:41 +#: res/actions.yaml:61 +#: res/actions.yaml:77 +#: res/actions.yaml:117 +msgid "Bl&ock" +msgstr "|3|_&0(|<" + +#: res/actions.yaml:45 +#: res/actions.yaml:65 +#: res/actions.yaml:81 +#: res/actions.yaml:121 +msgid "Unbl&ock" +msgstr "|_||\\|&B|_0(|<" + +#: res/actions.yaml:48 +#: res/actions.yaml:313 +#: res/actions.yaml:325 +#: res/actions.yaml:335 +#: res/actions.yaml:346 +#: res/actions.yaml:352 +#: res/actions.yaml:360 +#: src/gui/widgetlist.py:43 +#: src/plugins/twitter/twitter_gui.py:1383 +msgid "&Delete" +msgstr "&D3|_373" + +#: res/actions.yaml:97 +msgid "Resend authorization" +msgstr "|2353|\\||) 4|_|7|-|0|2124710|\\|" + +#: res/actions.yaml:100 +msgid "Re-request authorization" +msgstr "|23-|23Q|_|357 4|_|7|-|0|2124710|\\|" + +#: res/actions.yaml:103 +msgid "Remove authorization" +msgstr "|23|\\/|0\\/3 4|_|7|-|0|2124710|\\|" + +#: res/actions.yaml:111 +msgid "Appear offline" +msgstr "4PP34|2 0|=|=|_1|\\|3" + +#: res/actions.yaml:114 +msgid "Appear online" +msgstr "4PP34|2 0|\\||_1|\\|3" + +#: res/actions.yaml:148 +msgid "Re&name && Rearrange..." +msgstr "|23&N4|\\/|3 |\\| R34|2|24|\\|63" + +#: res/actions.yaml:151 +msgid "Split Merged Contact..." +msgstr "5P|_17 |\\/|3|263|) (0|\\|74(7..." + +#: res/actions.yaml:162 +#: src/gui/buddylist/buddylist.py:1011 +msgid "&Rename Group" +msgstr "&R3|\\|4|\\/|3 6|20|_|P" + +#: res/actions.yaml:165 +#: src/gui/buddylist/buddylist.py:1012 +msgid "&Delete Group" +msgstr "&D3|_373 6|20|_|p" + +#: res/actions.yaml:169 +#: src/gui/buddylist/buddylist.py:1013 +msgid "Add &Group" +msgstr "4|)|) &6|20|_|P" + +#: res/actions.yaml:174 +#: src/gui/accountslist.py:136 +#: src/gui/uberwidgets/connectionlist.py:462 +msgid "&Connect" +msgstr "&(0|\\||\\|3(7" + +#: res/actions.yaml:177 +msgid "&Disconnect" +msgstr "&D15(0|\\||\\|3(7" + +#: res/actions.yaml:186 +msgid "&Format Screen Name" +msgstr "&F0|2|\\/|47 5(|233|\\| |\\|4|\\/|3" + +#: res/actions.yaml:190 +msgid "&Update Email Address" +msgstr "&UP|)473 3|\\/|41|_ 4|)|)|2355" + +#: res/actions.yaml:194 +msgid "&Confirm Account" +msgstr "&C0|\\||=1|2|\\/| 4((0|_||\\|7" + +#: res/actions.yaml:197 +msgid "Change &Password (URL)" +msgstr "(#4|\\|63 &P455\\/\\/0|2|) (URL)" + +#: res/actions.yaml:200 +msgid "&IM Forwarding (URL)" +msgstr "&1|\\/| |=0|2\\/\\/0|2|)1|\\|6 (URL)" + +#: res/actions.yaml:203 +msgid "&AOL Alerts (URL)" +msgstr "&40|_ 4|_3|272 (|_||2|_)" + +#: res/actions.yaml:206 +msgid "&My Page on ICQ.com (URL)" +msgstr "&|\\/|'/ P463 0|\\| ICQ.com (|_||2|_)" + +#: res/actions.yaml:218 +msgid "Set Priority" +msgstr "537 P|210|217'/" + +#: res/actions.yaml:234 +msgid "Change &Password" +msgstr "(#4|\\|63 &P455\\/\\/0|2|)" + +#: res/actions.yaml:244 +msgid "&Search for a Contact" +msgstr "&534|3(|-| 4 (0|\\|74(7" + +#: res/actions.yaml:247 +msgid "Address Book" +msgstr "4|)|)|2355 |300|<" + +#: res/actions.yaml:250 +msgid "Mobile Settings" +msgstr "|\\/|0|31|_3 53771|\\|62" + +#: res/actions.yaml:253 +msgid "My Account" +msgstr "|\\/|'/ 4((0|_||\\|7" + +#: res/actions.yaml:256 +#: src/msn/msngui.py:35 +msgid "Create Group Chat" +msgstr "(|23473 6|20|_|P (#47" + +#: res/actions.yaml:276 +msgid "My Account Info (URL)" +msgstr "|\\/|'/ 4((0|_||\\|7 1|\\||=0 (URL)" + +#: res/actions.yaml:279 +msgid "My Web Profile (URL)" +msgstr "|\\/|'/ \\/\\/3|3 P|20|=1|_3 (|_||2|_)" + +#: res/actions.yaml:283 +msgid "Update presence" +msgstr "|_|P|)473 P|2353|\\|(3" + +#: res/actions.yaml:287 +msgid "Open &Inbox" +msgstr "0P3|\\| &1|\\||30><" + +#: res/actions.yaml:290 +msgid "C&ompose..." +msgstr "(&0|\\/|P053..." + +#: res/actions.yaml:295 +#: src/plugins/facebook/actions.yaml:17 +#: src/plugins/linkedin/actions.yaml:13 +#: src/plugins/myspace/actions.yaml:16 +#: src/plugins/twitter/twitter_gui.py:2044 +msgid "&Rename" +msgstr "&R3|\\|4|\\/|3" + +#: res/actions.yaml:300 +#: src/plugins/linkedin/actions.yaml:2 +msgid "&Check Now" +msgstr "(#3(|<1|\\|' |\\|0\\/\\/..." + +#: res/actions.yaml:303 +#: src/plugins/facebook/actions.yaml:4 +msgid "&Tell Me Again" +msgstr "&73|_|_ |\\/|3 4641|\\|" + +#: res/actions.yaml:307 +#: res/actions.yaml:319 +#: res/actions.yaml:331 +#: res/actions.yaml:342 +#: res/actions.yaml:350 +#: res/actions.yaml:356 +#: src/gui/filetransfer/filetransferlist.py:473 +msgid "&Open" +msgstr "&0P3|\\|" + +#: res/actions.yaml:309 +#: res/actions.yaml:321 +#: res/actions.yaml:333 +#: res/actions.yaml:344 +#: res/actions.yaml:358 +msgid "Mark as &Read" +msgstr "|\\/|4|2|< 42 &R34|)" + +#: res/actions.yaml:311 +#: res/actions.yaml:323 +msgid "&Archive" +msgstr "&4|2(#1\\/3" + +#: res/actions.yaml:315 +#: res/actions.yaml:327 +#: res/actions.yaml:337 +#: res/actions.yaml:362 +msgid "Report &Spam" +msgstr "|23P0|27 &5P4|\\/|" + +#: res/actions.yaml:366 +#: src/plugins/facebook/actions.yaml:2 +#: src/plugins/myspace/actions.yaml:2 +#: src/plugins/twitter/twitter_gui.py:1499 +msgid "&Update Now" +msgstr "&|_|P|)473 |\\|0\\/\\/" + +#: res/actions.yaml:369 +#: src/plugins/facebook/actions.yaml:6 +#: src/plugins/linkedin/actions.yaml:4 +#: src/plugins/myspace/actions.yaml:5 +msgid "&Home" +msgstr "#0|\\/|3" + +#: res/actions.yaml:372 +#: src/plugins/linkedin/actions.yaml:10 +msgid "&Profile" +msgstr "P|20|=1|_3" + +#: res/actions.yaml:375 +#: src/plugins/linkedin/actions.yaml:6 +#: src/plugins/myspace/actions.yaml:7 +msgid "&Inbox" +msgstr "1|\\||30><" + +#: res/actions.yaml:378 +#: src/plugins/facebook/actions.yaml:10 +#: src/plugins/myspace/actions.yaml:9 +msgid "&Friends" +msgstr "|=|213|\\||)2:" + +#: res/actions.yaml:381 +#: src/plugins/myspace/actions.yaml:11 +msgid "&Blog" +msgstr "&B|_06" + +#: res/actions.yaml:384 +msgid "B&ulletins" +msgstr "&B|_||_|_371|\\|2" + +#: res/actions.yaml:387 +#: src/plugins/facebook/actions.yaml:20 +#: src/plugins/linkedin/actions.yaml:16 +#: src/plugins/myspace/actions.yaml:19 +msgid "&Set Status" +msgstr "537 5747|_|5" + +#: res/actions.yaml:391 +#: src/gui/imwin/imwin_email.py:51 +#: src/gui/imwin/imwin_email.py:57 +#: src/gui/imwin/imwin_email.py:115 +msgid "Edit..." +msgstr "3|)17..." + +#: res/actions.yaml:398 +#: src/gui/status.py:744 +#: src/gui/widgetlist.py:61 +msgid "Delete" +msgstr "|)3|_373" + +#: res/notificationview.yaml:1 +msgid "Contact Signs On" +msgstr "(0|\\|74(72 516|\\|2 0|=|=" + +#: res/notificationview.yaml:6 +msgid "Contact Signs Off" +msgstr "(0|\\|74(72 516|\\|2 0|\\|" + +#: res/notificationview.yaml:10 +msgid "Contact Becomes Available" +msgstr "|_|P|)473 4\\/41|_4|3|_3" + +#: res/notificationview.yaml:15 +msgid "Contact Goes Away" +msgstr "(0|\\|74(7 |_4'/0|_|7" + +#: res/notificationview.yaml:20 +msgid "Contact Changes Status Message" +msgstr "|)3|_373 S747|_|2 |\\/|355463" + +#: res/notificationview.yaml:25 +msgid "Contact Returns From Idle" +msgstr "(0|\\|74(7 |237|_||2|\\|2 |=|20|\\/| 1|)|_3" + +#: res/notificationview.yaml:29 +msgid "Contact Becomes Idle" +msgstr "(0|\\|74(7 |\\|4|\\/|3:" + +#: res/notificationview.yaml:33 +msgid "IM Received" +msgstr "|23(31\\/3|) " + +#: res/notificationview.yaml:39 +msgid "IM Received (not in focus)" +msgstr "1|\\/| |23(31\\/3|) (|\\|07 1|\\| |=0(|_|5)" + +#: res/notificationview.yaml:45 +msgid "IM Received (hidden to tray)" +msgstr "1|\\/| |23(31\\/3|) (#1|)|)3|\\| 2 7|24'/)" + +#: res/notificationview.yaml:53 +msgid "IM Received (first message)" +msgstr "1|\\/| |23(31\\/3|) (|=1|257 |\\/|355463)" + +#: res/notificationview.yaml:59 +msgid "IM Sent" +msgstr "53|\\|7 " + +#: res/notificationview.yaml:64 +msgid "Group Chat Invite" +msgstr "(#47 1|\\|\\/173" + +#: res/notificationview.yaml:74 +msgid "File Transfer Request" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: res/notificationview.yaml:85 +msgid "File Transfer Complete" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: res/notificationview.yaml:91 +msgid "File Transfer Error" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: res/notificationview.yaml:97 +msgid "New Email Arrives" +msgstr "3|\\/|41|_ 4||)|)|255" + +#: res/notificationview.yaml:105 +msgid "Error Occurs" +msgstr "3|2|20|2 0((|_||22" + +#: res/notificationview.yaml:112 +msgid "Downloading dictionary" +msgstr "|)0\\/\\/|\\||_04|) |)1(710|\\|4|2Y?" + +#: src/AccountManager.py:266 +msgid "Digsby is running in \"Local Mode\"" +msgstr "|)165|3'/ B |2|_||\\||\\|1|\\|6 1|\\| \"|_0(4|_ |\\/|0|)3\"" + +#: src/AccountManager.py:268 +msgid "Changes to Digsby preferences may not synchronize to your other PCs right away" +msgstr "(#4|\\|632 2 |)165|3'/ P|23|=3|23|\\|(32 |\\/|4'/ |\\|07 5'/|\\|(#|20|\\|123 2 '/0 07#3|2 P(2 |216#7 4\\/\\/4Y" + +#: src/AccountManager.py:800 +#: src/gui/social_status_dialog.py:592 +#: src/imaccount.py:517 +#: src/plugins/twitter/twitter.py:1210 +#: src/social/network.py:69 +#: src/social/network.py:74 +#: src/social/network.py:76 +#: src/social/network.py:77 +msgid "Retry" +msgstr "|237|2'/" + +#: src/AccountManager.py:801 +#: src/imaccount.py:518 +#: src/imaccount.py:519 +#: src/imaccount.py:520 +#: src/social/network.py:75 +msgid "Reconnect" +msgstr "|23(0|\\||\\|3(7" + +#: src/AccountManager.py:802 +#: src/hub.py:80 +#: src/jabber/JabberChat.py:263 +#: src/plugins/digsby_status/status_tag_urls.py:113 +#: src/plugins/digsby_updater/updater.py:577 +#: src/plugins/facebook/fbacct.py:370 +#: src/plugins/myspace/MyspaceProtocol.py:436 +#: src/plugins/researchdriver/researchtoast.py:37 +#: src/plugins/twitter/twitter.py:1212 +msgid "Close" +msgstr "(|_053" + +#: src/AccountManager.py:803 +#: src/imaccount.py:516 +#: src/social/network.py:67 +msgid "Edit Account" +msgstr "3|)17 4((0|_||\\|7" + +#: src/AccountManager.py:814 +#: src/common/Protocol.py:35 +#: src/digsbysplash.py:39 +#: src/digsbysplash.py:658 +#: src/digsbysplash.py:662 +#: src/digsbysplash.py:732 +msgid "Authentication Error" +msgstr "0# |\\|035! 4|_|7|-|3|\\|71(4710|\\| 3|2|20|2" + +#: src/AccountManager.py:817 +msgid "Mailbox does not exist" +msgstr "|\\/|41|_|30>< |)032 |\\|07 ><157" + +#: src/AccountManager.py:820 +msgid "This account has been signed on from another location" +msgstr "|)12 4((0|_||\\|7 #42 |333|\\| 516|\\|3|) 0|\\| |=|20|\\/| 4|\\|07#3|2 |_0(4710|\\|" + +#: src/AccountManager.py:823 +msgid "Connection to server lost" +msgstr "(0|\\||\\|3(710|\\| 2 53|2\\/3|2 |_057" + +#: src/AccountManager.py:826 +msgid "Failed to connect to server" +msgstr "|=41|_3|) 2 (0|\\||\\|3(7 2 53|2\\/3|2" + +#: src/AccountManager.py:829 +msgid "Could not connect because you are signing on too often. Please wait 20 minutes before trying to log in again." +msgstr "(0|_||_|) |\\|07 (0|\\||\\|3(7 (02 J00 12 516|\\|1|\\|6 0|\\| 700 0|=73|\\|. P|_3453 \\/\\/417 20 |\\/|1|\\||_|732 |33|=0|23 7|2'/1|\\|6 2 |_06 1|\\| 4641|\\|." + +#: src/AccountManager.py:848 +msgid "{protocol_name} Error" +msgstr "{protocol_name} 3|2|20|2" + +#: src/common/AchievementMixin.py:105 +#, python-format +msgid "%(num_accounts)d account" +msgid_plural "%(num_accounts)d accounts" +msgstr[0] "%(num_accounts)d 4((0|_||\\|7" +msgstr[1] "%(num_accounts)d 4((0|_||\\|72" + +#: src/common/Conversation.py:12 +msgid "[Auto-Response] {message}" +msgstr "[Auto-Response] {message}" + +#: src/common/Conversation.py:46 +msgid "Disconnected" +msgstr "|)15(0|\\||\\|3(73|)" + +#: src/common/Conversation.py:114 +msgid "Some of the messages you sent may not have been received." +msgstr "50|\\/|3 0|= 73# |\\/|3554632 J00 53|\\|7 |\\/|4'/ |\\|07 #4\\/3 |333|\\| |23(31\\/3|)." + +#: src/common/Conversation.py:349 +msgid "{name} joined the group chat" +msgstr "{name} (|-|47 J01|\\|" + +#: src/common/Conversation.py:353 +msgid "{name} left the group chat" +msgstr "{name} |_3|=7 73|-| 6|20|_|P" + +#: src/common/Conversation.py:371 +#, python-format +msgid "%(name)s wants to have an Audio/Video chat. Send them an invite." +msgstr "%(name)s \\/\\/4|\\|72 2 #4\\/3 |\\| 4|_||)10/\\/1|)30 (#47. 53|\\||) 7#3|\\/| |\\| 1|\\|\\/173." + +#: src/common/Protocol.py:25 +msgid "Online" +msgstr "0|\\||_1|\\|3" + +#: src/common/Protocol.py:26 +#: src/digsbysplash.py:520 +msgid "Connecting..." +msgstr "(0|\\||\\|3(71|\\/|4(471|\\|'..." + +#: src/common/Protocol.py:27 +msgid "Authenticating..." +msgstr "4|_|7#3|\\|71(471|\\|6..." + +#: src/common/Protocol.py:28 +msgid "Loading Contact List..." +msgstr "|_04|)1|\\|6 (0|\\|74(7 |_157..." + +#: src/common/Protocol.py:29 +#: src/common/statusmessage.py:35 +#: src/common/statusmessage.py:226 +#: src/contacts/buddylistfilters.py:16 +#: src/gui/statuscombo.py:476 +#: src/oscar/OscarBuddies.py:47 +msgid "Offline" +msgstr "0|=|=|_1|\\|3" + +#: src/common/Protocol.py:30 +msgid "Checking Mail..." +msgstr "(#3(|<1|\\|' |\\/|41|_..." + +#: src/common/Protocol.py:31 +msgid "Initializing..." +msgstr "1|\\|1714|_121|\\|6..." + +#: src/common/Protocol.py:36 +msgid "You have signed in from another location" +msgstr "|)1|) |_| 516|\\| 1|\\| |=|20|\\/| 4|\\|07#3|2 |_0(4710|\\|" + +#: src/common/Protocol.py:37 +#: src/digsbysplash.py:40 +#: src/digsbysplash.py:734 +msgid "Connection Lost" +msgstr "0# |\\|035 (0|\\||\\|3(710|\\| |_057" + +#: src/common/Protocol.py:38 +#: src/digsbysplash.py:654 +#: src/digsbysplash.py:671 +#: src/digsbysplash.py:726 +#: src/digsbysplash.py:736 +#: src/digsbysplash.py:745 +msgid "Failed to Connect" +msgstr "(0|\\||\\|3(7 |=41|_!!!110|\\|33|_3\\/3|\\|7'/" + +#: src/common/Protocol.py:39 +msgid "You are signing in too often" +msgstr "|_| |2 516|\\|1|\\|6 1|\\| 2 0|=73|\\|" + +#: src/common/Protocol.py:40 +msgid "This account does not have a mailbox" +msgstr "7|-|15 4((0|_||\\|7 |)032 |\\|07 |-|4\\/34 |\\/|41|_|30><" + +#: src/common/Protocol.py:41 +msgid "Failed to Initialize" +msgstr "|=41|_3|) 2 1|\\|1714|_123" + +#: src/common/Protocol.py:42 +#, python-format +msgid "Connection Failed. Retry in %s" +msgstr "(0|\\||\\|3(710|\\| |=41|_3|). |237|2'/ 1 |\\| %s" + +#: src/common/Protocol.py:43 +msgid "Internal Server Error" +msgstr "1|\\|73|2|\\|4|_ 53|2\\/3|2 3|2|20|2" + +#: src/common/Protocol.py:457 +msgid "Error inviting {name}: they are offline" +msgstr "3|2|20|2 1|\\|\\/171|\\| {name}: 7#3'/ 12 0|=|=|_1|\\|3" + +#: src/common/Protocol.py:459 +msgid "Error inviting {name}: they do not support group chat" +msgstr "3|2|20|2 1|\\|\\/171|\\|6 {name}: 7#3'/ |)0 |\\|07 5|_|PP0|27 6|20|_|P (#47" + +# %s +#: src/common/accountbase.py:45 +#: src/contacts/Contact.py:96 +#, python-format +msgid "Enter an alias for %s:" +msgstr "3|\\|73|2 |\\| 4|_142 4 %s:" + +#: src/common/accountbase.py:46 +#: src/contacts/Contact.py:97 +#, python-format +msgid "Rename %s" +msgstr "|23|\\|4|\\/|3 %s" + +#: src/common/actions.py:149 +msgid "No actions" +msgstr "|\\|0 4(710|\\|2" + +#: src/common/emailaccount.py:298 +msgid "Email Client" +msgstr "3|\\/|41|_(|_13|\\|7" + +#: src/common/emailaccount.py:476 +msgid "No unread mail" +msgstr "|\\|0 |_||\\||234|) |\\/|41|_" + +#: src/common/emailaccount.py:557 +msgid "No System Email Client" +msgstr "|\\|0 5'/573|\\/| 3|\\/|41|_ (|_13|\\|7" + +#: src/common/emailaccount.py:558 +msgid "No system email client is configured." +msgstr "|\\|0 5'/573|\\/| 3|\\/|41|_ (|_13|\\|7 |3 (0|\\||=16|_||23|)." + +#: src/common/emailaccount.py:563 +msgid "Could not start system mail client." +msgstr "(0|_||_|) |\\|07 574|27 5'/573|\\/| |\\/|41|_ (|_13|\\|7." + +#: src/common/emailaccount.py:565 +msgid "Error launching system mail client" +msgstr "3|2|20|2 |_4|_||\\|(#1|\\|6 5Y573|\\/| |\\/|41|_ (|_13|\\|7" + +#: src/common/emailaccount.py:628 +#, python-format +msgid "Could not start system mail client %r." +msgstr "(0|_||_|) |\\|07 574|27 5'/573|\\/| |\\/|41|_ (|_13|\\|7 %r." + +#: src/common/filetransfer.py:73 +msgid "{completed} of {size} ({speed}/sec) -- {time} remain" +msgstr "{completed} 0|= {size} ({speed}/sec) -- {time} |23|\\/|41|\\|" + +#: src/common/filetransfer.py:74 +msgid "Received from {name}" +msgstr "|23(31\\/3|0 |=|20|\\/| {name}" + +#: src/common/filetransfer.py:74 +msgid "Sent to {name}" +msgstr "53|\\|7 2 {name}" + +#: src/common/filetransfer.py:75 +msgid "Connecting to {name}..." +msgstr "(0|\\||\\|3(71|\\|' 2 {name}..." + +#: src/common/filetransfer.py:76 +#: src/common/filetransfer.py:79 +msgid "Waiting for {name} to accept file" +msgstr "\\/\\/4171|\\|' 4 {name} 2 4((3P7 |=1|_3" + +#: src/common/filetransfer.py:78 +msgid "Canceled by {name}" +msgstr "(4|\\|(3|_|_3|) |3'/ {name}" + +#: src/common/filetransfer.py:80 +msgid "Failed during transfer from {name}" +msgstr "|=41|_3d |)|_||21|\\|6 7|24|\\|5|=3|2 |=|20|\\/| {name}" + +#: src/common/filetransfer.py:80 +msgid "Failed during transfer to {name}" +msgstr "|=41|_3d |)|_||21|\\|6 7|24|\\|5|=3|2 2 {name}" + +#: src/common/filetransfer.py:81 +msgid "Failed to connect to {name}" +msgstr "|=41|_3|) 2 (0|\\||\\|3(7 2 {name}" + +#: src/common/filetransfer.py:82 +msgid "File size too big for proxy transfer" +msgstr "|=1|_3 5123 700 |316 4 P|20><'/ 7|24|\\|5|=3|2" + +#: src/common/filetransfer.py:99 +msgid "Open" +msgstr "0P3|\\|" + +#: src/common/filetransfer.py:100 +#: src/gui/imagedialog.py:12 +#: src/imaccount.py:521 +#: src/imaccount.py:524 +#: src/imaccount.py:525 +#: src/imaccount.py:526 +#: src/plugins/digsby_updater/UpdateProgress.py:147 +#: src/plugins/twitter/twitter_gui.py:1621 +msgid "Cancel" +msgstr "(4|\\|(3|_" + +#: src/common/filetransfer.py:101 +#: src/gui/accountslist.py:40 +#: src/gui/imwin/imwin_tofrom.py:341 +#: src/gui/pref/pg_accounts.py:631 +#: src/plugins/digsby_updater/UpdateProgress.py:144 +#: src/plugins/twitter/twitter_gui.py:1948 +#: src/tests/testgui/uberdemos/UberComboXTDemo.py:32 +msgid "Remove" +msgstr "|23|\\/|0\\/3" + +#: src/common/filetransfer.py:104 +msgid "Save" +msgstr "54\\/3" + +#: src/common/filetransfer.py:105 +msgid "Save as..." +msgstr "54\\/3 45..." + +#: src/common/filetransfer.py:106 +msgid "Reject" +msgstr "|23J3(7" + +#: src/common/filetransfer.py:243 +msgid "Directory does not exist" +msgstr "|)1|23(70|2'/ |)032 |\\|07 ><157" + +#: src/common/filetransfer.py:273 +msgid "Choose a location to save" +msgstr "(#0053 @ |_0(4710|\\| 2 54\\/3" + +#: src/common/filetransfer.py:361 +msgid "Failed to receive {filename} from {name}" +msgstr "|=41|_3|) 2 |23(31\\/3 {filename} |=|20|\\/| {name}" + +#: src/common/filetransfer.py:361 +msgid "Failed to send {filename} to {name}" +msgstr "|=41|_3|) 2 53|\\||) {filename} 2 {name}" + +#: src/common/protocolmeta.py:42 +#: src/common/statusmessage.py:31 +#: src/common/statusmessage.py:32 +#: src/common/statusmessage.py:222 +#: src/gui/native/win/jumplist.py:129 +#: src/gui/status.py:59 +#: src/gui/statuscombo.py:501 +#: src/jabber/JabberResource.py:115 +#: src/plugins/component_gtalk/info.yaml:6 +#: src/plugins/fbchat/info.yaml:17 +#: src/plugins/provider_aol/info.yaml:92 +#: src/plugins/provider_aol/info.yaml:137 +#: src/plugins/provider_jabber/info.yaml:48 +#: src/plugins/provider_windows_live/info.yaml:30 +msgid "Available" +msgstr "4\\/41|_4|3|_3" + +#: src/common/protocolmeta.py:42 +#: src/plugins/provider_aol/info.yaml:138 +#: src/plugins/provider_jabber/info.yaml:49 +msgid "Free For Chat" +msgstr "|=|233 4 (#47" + +#: src/common/protocolmeta.py:43 +#: src/common/statusmessage.py:33 +#: src/common/statusmessage.py:223 +#: src/gui/native/win/jumplist.py:130 +#: src/gui/status.py:60 +#: src/gui/statuscombo.py:517 +#: src/gui/statuscombo.py:522 +#: src/oscar/OscarProtocol.py:1092 +#: src/plugins/component_gtalk/info.yaml:7 +#: src/plugins/provider_aol/info.yaml:93 +#: src/plugins/provider_aol/info.yaml:139 +#: src/plugins/provider_jabber/info.yaml:50 +#: src/plugins/provider_windows_live/info.yaml:31 +msgid "Away" +msgstr "4\\/\\/4'/" + +#: src/common/protocolmeta.py:43 +#: src/jabber/JabberResource.py:14 +#: src/plugins/provider_aol/info.yaml:140 +#: src/plugins/provider_jabber/info.yaml:51 +msgid "Do Not Disturb" +msgstr "|)0 |\\|07 |)157|_||2|3" + +#: src/common/protocolmeta.py:43 +#: src/jabber/JabberResource.py:16 +#: src/plugins/provider_jabber/info.yaml:52 +msgid "Extended Away" +msgstr "><73|\\||)3|) 4\\/\\/4'/" + +#: src/common/spelling/spellcheck.py:471 +msgid "You need to download the {langname} dictionary to use it. Would you like to download this dictionary now?" +msgstr "J00 |\\|33|) 2 |)0\\/\\/|\\||_04|) 73# {langname} |)1(710|\\|4|2'/ 2 |_|53 17. \\/\\/0|_||_|) J00 \\/\\/|_||3 2 |)0\\/\\/|\\||_04|) |)12 |)1(710|\\|4|2'/ |\\|0\\/\\/?" + +#: src/common/spelling/spellcheck.py:472 +msgid "Download Dictionary?" +msgstr "|)0\\/\\/|\\||_04|) |)1(710|\\|4|2Y?" + +#: src/common/spelling/spellcheck.py:482 +msgid "Dictionary not downloaded." +msgstr "|)1(710|\\|2|2'/ |\\|07 |)0\\/\\/|\\||_04|)3|)" + +#: src/common/spelling/spellcheck.py:483 +msgid "To download it later, select it in the Conversation Preferences." +msgstr "2 |)0\\/\\/|\\||_04|) 17 |_8|2, 53|_397 17 1|\\| 73# (0|\\|\\/3|254710|\\| P|23|=3|23|\\|(32." + +#: src/common/spelling/spellcheck.py:486 +msgid "Download Dictionary Canceled" +msgstr "|)0\\/\\/|\\||_04|) |)1(710|\\|4|2Y (4|\\|(3|_3|)" + +#: src/common/spelling/spellcheck.py:505 +msgid "Dictionary Set" +msgstr "|)1(710|\\|4|2'/ 537" + +#: src/common/spelling/spellcheck.py:506 +msgid "Spellcheck language has been set to {langname}." +msgstr "5P3|_|_(#3(|< |_4|\\|6|_|463 #42 |333|\\| 537 2 {langname}." + +#: src/common/spelling/spellcheck.py:511 +msgid "Spellcheck error" +msgstr "5P3|_|_(#3(|< 3|2|20|2" + +#: src/common/spelling/spellcheck.py:512 +msgid "Failed setting up dictionary. Try reselecting desired language in the preferences." +msgstr "|=41|_3|) 53771|\\|6 |_|P |)1(710|\\|4|2'/. 7|2'/ |2353|_3(71|\\|6 |)351|23|) |_4|\\|6|_|463 1|\\| 73# P|23|=3|23|\\|(32." + +#: src/common/spelling/spellcheck.py:518 +#: src/common/spelling/spellcheck.py:676 +msgid "Dictionary Installed" +msgstr "|)1(710|\\|2|2'/ 1|\\|s74|_|_3|)" + +#: src/common/spelling/spellcheck.py:519 +msgid "You can set this language in the conversation preferences." +msgstr "J00 (4|\\| 537 |)12 |_4|\\|6|_|463 1|\\| 73# (0|\\|\\/3|254710|\\| P|23|=3|23|\\|(32." + +#: src/common/spelling/spellcheck.py:563 +msgid "Dictionary will be activated after install completes." +msgstr "|)1(710|\\|4|2'/ \\/\\/1|_|_ |3 4(71\\/473|) 4|=73|2 1|\\|574|_|_ (0|\\/|P|_3732." + +#: src/common/spelling/spellcheck.py:563 +msgid "Installing Dictionary" +msgstr "1|\\|574|_|_1|\\|' |)1(710|\\|4|2'/" + +#: src/common/spelling/spellcheck.py:676 +msgid "Setting spellcheck language..." +msgstr "53771|\\|' 5P3|_|_(#3(|< |_4|\\|6|_|463..." + +#: src/common/spelling/spellcheck.py:689 +msgid "{langname} Dictionary" +msgstr "{langname} |)1(710|\\|4|2'/" + +#: src/common/statusmessage.py:34 +#: src/common/statusmessage.py:225 +#: src/gui/native/win/jumplist.py:131 +#: src/gui/statuscombo.py:544 +msgid "Invisible" +msgstr "1|\\|\\/151|3|_3" + +#: src/common/statusmessage.py:138 +msgid "{truncatedtitle}..." +msgstr "{truncatedtitle}..." + +#: src/common/statusmessage.py:224 +msgid "Idle" +msgstr "1|)|_3" + +#: src/contacts/Group.py:187 +msgid "That buddy is already part of metacontact \"{alias}\" in group \"{group}.\"" +msgstr "|)47 |3|_||)|)'/ |3 4|_|234|)'/ P4|27 0|= |\\/|374(0|\\|74(7 \"{alias}\" 1|\\| 6|20|_|P \"{group}.\"" + +#: src/contacts/Group.py:189 +#: src/gui/addcontactdialog.py:36 +#: src/gui/addcontactdialog.py:203 +#: src/gui/capabilitiesbar.py:118 +#: src/gui/contactdialogs.py:252 +msgid "Add Contact" +msgstr "4|)|) (0|\\|74(7" + +#: src/contacts/Group.py:221 +msgid "Enter a new name for {name}:" +msgstr "3|\\|73|2 @ |\\|3\\/\\/ |\\|4|\\/|3 4 {name}:" + +#: src/contacts/Group.py:222 +msgid "Rename Group" +msgstr "|23|\\|4|\\/|3 6|20|_|P" + +#: src/contacts/buddylistfilters.py:29 +#: src/contacts/buddyliststore.py:97 +#: src/contacts/buddyliststore.py:384 +#: src/contacts/buddyliststore.py:645 +#: src/contacts/metacontacts.py:43 +#: src/plugins/linkedin/LinkedInAccount.py:84 +msgid "Contacts" +msgstr "(0|\\|74(72" + +#: src/contacts/metacontacts.py:65 +msgid "Unknown" +msgstr "|_||\\||<|\\|0\\/\\/|\\|" + +#: src/contacts/metacontacts.py:820 +msgid "Are you sure you want to split up this merged contact?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 5P|_17 |_|P |)12 |\\/|3|263|) (0|\\|74(7?" + +#: src/contacts/metacontacts.py:821 +msgid "Split Contacts" +msgstr "5P|_17 (0|\\|74(72" + +#: src/contacts/sort_model.py:144 +msgid "None (custom)" +msgstr "|\\|0|\\|3 ((|_|570|\\/|)" + +#: src/contacts/sort_model.py:145 +#: src/contacts/sort_model.py:152 +#: src/gui/pref/pg_contact_list.py:405 +#: src/gui/pref/prefstrings.py:20 +#: src/gui/status.py:374 +#: src/plugins/myspace/res/status.tenjin:13 +#: src/plugins/twitter/res/status.tenjin:11 +msgid "Status" +msgstr "5747|_|5" + +#: src/contacts/sort_model.py:146 +#: src/contacts/sort_model.py:153 +msgid "Service" +msgstr "53|2\\/1(3" + +#: src/contacts/sort_model.py:149 +msgid "None" +msgstr "|\\|0|\\|3" + +#: src/contacts/sort_model.py:150 +msgid "Log Size" +msgstr "|_06 5123" + +#: src/contacts/sort_model.py:151 +#: src/gui/vcard/vcardgui.py:18 +msgid "Name" +msgstr "|\\|4|\\/|3" + +#: src/crashgui.py:11 +msgid "Digsby Crash Report" +msgstr "|)165|3'/ (|245# |23P0|27" + +#: src/crashgui.py:12 +msgid "Digsby appears to have crashed." +msgstr "|)165|3'/ 4PP34|22 2 |-|4\\/3 |245|-|3|)." + +#: src/crashgui.py:13 +msgid "If you can, please describe what you were doing before it crashed." +msgstr "1|= J00 (4|\\|, P|_3453 |)35(|21B3 \\/\\/07 J00 \\/\\/|_|2 |)01|\\|6 |33|=0|23 17 (|245#3|)." + +#: src/crashgui.py:15 +msgid "&Send Crash Report" +msgstr "&53|\\||) (|245# |23P0|27" + +#: src/digsby/DigsbyProtocol.py:55 +msgid "Synchronizing Preferences..." +msgstr "5'/|\\|(#|20|\\|121|\\|' P|23|=3|23|\\|(32..." + +#: src/digsby/DigsbyProtocol.py:57 +msgid "Synchronizing..." +msgstr "5'/|\\|(#|20|\\|121|\\|'..." + +#: src/digsby/videochat.py:28 +msgid "Audio/Video Call with {name}" +msgstr "4|_||)10/\\/1|)30 (4|_|_ \\/\\/17# {name}" + +#: src/digsby/videochat.py:91 +#, python-format +msgid "Join me in an audio/video call: %s" +msgstr "J01|\\| |\\/|3 1|\\| |\\| 4|_||)10/\\/1|)30 (4|_|_: %s" + +#: src/digsby/videochat.py:96 +msgid "You have invited {name} to an audio/video chat." +msgstr "J00 #4\\/3 1|\\|\\/173|) {name} 2 |\\| 4|_||)10/\\/1|)30 (#47." + +#: src/digsby/videochat.py:111 +msgid "Audio/Video chat is currently unavailable." +msgstr "4|_||)10/\\/1|)30 (#47 |3 (|_||2|23|\\|7|_'/ |_||\\|4\\/41|_4|3|_3." + +#: src/digsby/videochat.py:179 +msgid "Audio/Video call ended by other party." +msgstr "4|_||)10/\\/1|)30 (4|_|_ 3|\\||)3|) |3'/ 07#3|2 P4|27'/." + +#: src/digsbysplash.py:27 +msgid "&Sign In" +msgstr "&516|\\| 1|\\| 637" + +#: src/digsbysplash.py:41 +#: src/digsbysplash.py:739 +msgid "We are upgrading Digsby. Please try connecting again in a few minutes." +msgstr "0|-| |\\|035! |)1G5|3'/ |_3\\/3|_ |_|P G37! 7RY L473R D|_|D3" + +#: src/digsbysplash.py:42 +#: src/digsbysplash.py:741 +msgid "Could not contact remote server. Check your network configuration." +msgstr "(0|_||_|) |\\|07 (0|\\|74(7 |23|\\/|073 53|2\\/3|2. (|-|3(|< |_||2 1|\\|73|2\\/\\/3|32 (0|\\||=16." + +#: src/digsbysplash.py:46 +msgid "Invalid Digsby Username" +msgstr "1|\\|\\/4|_1|) |)1G5|3'/ |_|53R|\\|4|\\/|3" + +#: src/digsbysplash.py:322 +msgid "Update Successful" +msgstr "607 73|-| |31663|2 6|3s" + +#: src/digsbysplash.py:499 +#: src/digsbysplash.py:779 +msgid "Loading..." +msgstr "|\\|40 |_04|)2..." + +#: src/digsbysplash.py:541 +msgid "Login error!" +msgstr "1 4|\\/| 3|2|20|2! |_0650|\\|" + +#: src/digsbysplash.py:595 +msgid "Your password will be saved on your portable device." +msgstr "'/0|_||2 P455\\/\\/0|2|) \\/\\/1|_|_ |33 54\\/3|) 0|\\| '/0|_||2 P0|274|3|_3 |)3\\/1(e." + +#: src/digsbysplash.py:596 +msgid "Anyone with access to this device will be able to log into your Digsby account. Are you sure you want to save your password?" +msgstr "|_| |33 7RY1|\\| 50|\\/|3 R1Z|<><0|2 |=4><><0|22" + +#: src/digsbysplash.py:660 +msgid "Please make sure you have entered your Digsby username and password correctly." +msgstr "|_|53|2|\\|4|\\/|3 || P455\\/\\/0|2|)2 |=41|_! RTFM |_061|\\| 5(|233|\\| |_1|\\||<463" + +#: src/digsbysplash.py:661 +msgid "If you need an account or forgot your password, use the links on the login screen." +msgstr "|=0|2637 P455\\/\\/4|2 0|2 |\\|33|0 4((7? |_|53 |_1|\\||<2 0|\\| |_061|\\| 5(|233|\\|." + +#: src/digsbysplash.py:667 +msgid "" +"Please check your Internet connection and make sure a firewall isn't blocking Digsby.\n" +"If you connect to the Internet through a proxy server,\n" +"click the \"Connection Settings\" link to set up the proxy.\n" +"If you are still unable to connect, email bugs@digsby.com for technical support." +msgstr "" +"1 4|\\/| 3|2|20|2\n" +"TL;DT\n" +"5|\\/|0371|\\| |30|_|T F1|23|34|_|_Z || P0><><132" + +#: src/digsbysplash.py:769 +msgid "Register a Digsby Account" +msgstr "51|\\|6 |_|P |\\|40!" + +#: src/gui/accountdialog.py:38 +#: src/plugins/digsby_service_editor/default_ui.py:583 +msgid "System Default" +msgstr "5'/573|\\/| |)3|=4|_||_7" + +#: src/gui/accountdialog.py:39 +#: src/plugins/digsby_service_editor/default_ui.py:584 +msgid "Other Mail Client..." +msgstr "07#3|2 |\\/|41|_ (|_13|\\|7..." + +#: src/gui/accountdialog.py:40 +#: src/plugins/digsby_service_editor/default_ui.py:585 +msgid "Launch URL..." +msgstr "|_4|_||\\|(# |_||2|_..." + +#: src/gui/accountdialog.py:310 +#: src/gui/accountdialog.py:393 +#: src/gui/contactdialogs.py:172 +#: src/gui/pref/iconeditor.py:552 +#: src/gui/status.py:539 +#: src/plugins/digsby_service_editor/service_editor.py:87 +#: src/plugins/twitter/twitter_gui.py:120 +#: src/plugins/twitter/twitter_gui.py:356 +msgid "&Save" +msgstr "&54\\/3" + +#: src/gui/accountdialog.py:315 +#: src/gui/contactdialogs.py:91 +#: src/gui/contactdialogs.py:180 +#: src/gui/imwin/imtabs.py:125 +#: src/gui/pref/iconeditor.py:554 +#: src/gui/proxydialog.py:234 +#: src/gui/status.py:543 +#: src/plugins/digsby_service_editor/service_editor.py:90 +#: src/plugins/twitter/twitter_gui.py:129 +msgid "&Cancel" +msgstr "&(4|\\|(3|_" + +#: src/gui/accountdialog.py:321 +#: src/plugins/digsby_service_editor/service_editor.py:222 +msgid "That account already exists." +msgstr "|)47 4((0|_||\\|7 4|_|234|)'/ ><1572." + +#: src/gui/accountdialog.py:355 +msgid "If checked, this account will automatically sign in when Digsby starts." +msgstr "1|= (#3(|<3|), |)12 4((0|_||\\|7 \\/\\/1|_|_ 4|_|70|\\/|471(4|_|_'/ 516|\\| 1|\\| \\/\\/#3|\\| |)165|3'/ 574|272." + +#: src/gui/accountdialog.py:361 +#: src/plugins/digsby_service_editor/default_ui.py:211 +msgid "&Register New Account" +msgstr "&R361573|2 |\\|3\\/\\/ 4((0|_||\\|7" + +#: src/gui/accountdialog.py:393 +msgid "&Register" +msgstr "&R361573|2" + +#: src/gui/accountdialog.py:580 +msgid "Check for new mail every {n} minutes" +msgstr "(#3(|< 4 |\\|3\\/\\/ |\\/|41|_ 3\\/3|2'/ {n} minutes" + +#: src/gui/accountdialog.py:591 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "|\\/|1|\\||_|73" +msgstr[1] "|\\/|1|\\||_|732" + +#: src/gui/accountdialog.py:607 +#: src/plugins/digsby_service_editor/default_ui.py:73 +#: src/plugins/digsby_service_editor/default_ui.py:385 +msgid "Mail Client:" +msgstr "|\\/|41|_ (|_13|\\|7:" + +#: src/gui/accountdialog.py:626 +#: src/plugins/digsby_service_editor/default_ui.py:397 +msgid "SMTP username/password are the same as {servertype}" +msgstr "5|\\/|7P |_|s3r|\\|4|\\/|3/P455\\/\\/0|2|) 12 73# 54|\\/|3 42 {servertype}" + +#: src/gui/accountdialog.py:627 +#: src/plugins/digsby_service_editor/default_ui.py:398 +msgid "Log on using:" +msgstr "|_06 0|\\| |_|51|\\|':" + +#: src/gui/accountdialog.py:753 +msgid "{show_topic}:" +msgstr "{show_topic}:" + +#: src/gui/accountdialog.py:783 +msgid "Custom ({mailclient_name})" +msgstr "(|_|570|\\/| ({mailclient_name})" + +#: src/gui/accountdialog.py:812 +#: src/plugins/digsby_service_editor/default_ui.py:629 +msgid "Please choose a mail client" +msgstr "P|_3453 (#0053 @ |\\/|41|_ (|_13|\\|7" + +#: src/gui/accountdialog.py:860 +#: src/plugins/digsby_service_editor/default_ui.py:406 +msgid "Username:" +msgstr "|_|53|2|\\|4|\\/|3:" + +#: src/gui/accountdialog.py:862 +#: src/plugins/digsby_service_editor/default_ui.py:407 +msgid "Password:" +msgstr "P455\\/\\/0|2|):" + +#: src/gui/accountdialog.py:890 +#: src/plugins/facebook/info.yaml:9 +#: src/plugins/fbchat/info.yaml:11 +#: src/plugins/linkedin/info.yaml:8 +#: src/plugins/msim/info.yaml:7 +#: src/plugins/myspace/info.yaml:9 +#: src/plugins/provider_linkedin/info.yaml:10 +#: src/plugins/provider_myspace/info.yaml:10 +#: src/plugins/provider_windows_live/info.yaml:22 +#: src/plugins/provider_windows_live/info.yaml:37 +#: src/plugins/provider_windows_live/info.yaml:81 +msgid "Email Address" +msgstr "3|\\/|41|_ 4||)|)|255" + +#: src/gui/accountdialog.py:921 +msgid "&Display Name:" +msgstr "&D15P|_4'/ |\\|4|\\/|3:" + +#: src/gui/accountdialog.py:1003 +#: src/plugins/digsby_service_editor/default_ui.py:265 +msgid "SMTP Server:" +msgstr "5|\\/|7P 53|2\\/3|2:" + +#: src/gui/accountdialog.py:1005 +#: src/gui/accountdialog.py:1026 +#: src/plugins/digsby_service_editor/default_ui.py:96 +msgid "Port:" +msgstr "P0|27:" + +#: src/gui/accountdialog.py:1024 +msgid "Host:" +msgstr "#057:" + +#: src/gui/accountdialog.py:1031 +#: src/gui/infobox/htmlgeneration.py:237 +#: src/plugins/digsby_service_editor/default_ui.py:333 +msgid "Resource:" +msgstr "|2350|_||2(3:" + +#: src/gui/accountdialog.py:1033 +#: src/plugins/digsby_service_editor/default_ui.py:333 +msgid "Priority:" +msgstr "P|210|217'/:" + +#: src/gui/accountdialog.py:1038 +#: src/plugins/digsby_service_editor/default_ui.py:338 +msgid "Data Proxy:" +msgstr "|)474 P|20XY:" + +#: src/gui/accountdialog.py:1119 +#: src/plugins/digsby_service_editor/default_ui.py:665 +msgid "Enter the URL that will be launched when you click \"Inbox\" for this email account." +msgstr "3|\\|73|2 73# |_||2|_ |)47 \\/\\/1|_|_ |3 |_4|_||\\|(#3|) \\/\\/#3|\\| J00 (|_1(|< \"1|\\||30><\" 4 |)12 3|\\/|41|_ 4((0|_||\\|7." + +#: src/gui/accountdialog.py:1120 +#: src/plugins/digsby_service_editor/default_ui.py:666 +msgid "Enter the URL that will be launched when you click \"Compose\" for this email account." +msgstr "3|\\|73|2 73# |_||2|_ |)47 \\/\\/1|_|_ |3 |_4|_||\\|(#3|) \\/\\/#3|\\| J00 (|_1(|< \"(0|\\/|P053\" 4 |)12 3|\\/|41|_ 4((0|_||\\|7." + +#: src/gui/accountdialog.py:1123 +#: src/plugins/digsby_service_editor/default_ui.py:669 +msgid "Launch URL" +msgstr "|_4|_||\\|(# |_||2|_" + +#: src/gui/accountdialog.py:1136 +#: src/plugins/digsby_service_editor/default_ui.py:682 +msgid "Enter a URL for the Inbox" +msgstr "3|\\|73|2 @ |_||2|_ 4 73# 1|\\||30><" + +#: src/gui/accountdialog.py:1139 +#: src/plugins/digsby_service_editor/default_ui.py:685 +msgid "Enter a URL for the Compose window" +msgstr "3|\\|73|2 @ |_||2|_ 4 73# (0|\\/|P053 \\/\\/1|\\||)0\\/\\/" + +#: src/gui/accountslist.py:36 +#: src/gui/pref/pg_accounts.py:627 +#: src/gui/pref/pg_privacy.py:269 +#: src/gui/pref/pg_privacy.py:544 +#: src/gui/pref/pg_privacy.py:718 +#: src/gui/pref/pg_privacy.py:720 +#: src/gui/status.py:740 +#: src/gui/widgetlist.py:56 +#: src/plugins/twitter/twitter_gui.py:1947 +msgid "Edit" +msgstr "3|)17" + +#: src/gui/accountslist.py:128 +#: src/gui/accountslist.py:309 +#: src/gui/accountslist.py:406 +#: src/gui/anylists.py:308 +#: src/gui/status.py:728 +#: src/gui/uberwidgets/connectionlist.py:455 +#: src/gui/widgetlist.py:42 +#: src/plugins/twitter/twitter_gui.py:1382 +msgid "&Edit" +msgstr "&3|)17" + +#: src/gui/accountslist.py:129 +#: src/gui/accountslist.py:310 +#: src/gui/accountslist.py:407 +#: src/gui/anylists.py:309 +#: src/gui/filetransfer/filetransferlist.py:476 +#: src/gui/status.py:729 +msgid "&Remove" +msgstr "&R3|\\/|0\\/3" + +#: src/gui/accountslist.py:241 +#: src/plugins/digsby_service_editor/service_editor.py:211 +#: src/plugins/provider_jabber/jabber_gui.py:85 +msgid "Are you sure you want to delete account \"{name}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 4((0|_||\\|7 \"{name}\"?" + +#: src/gui/accountslist.py:242 +#: src/plugins/digsby_service_editor/service_editor.py:212 +#: src/plugins/provider_jabber/jabber_gui.py:86 +msgid "Delete Account" +msgstr "|)3|_373 4((0|_||\\|7" + +#: src/gui/accountslist.py:340 +msgid "Are you sure you want to delete social network account \"{name}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 50(14|_ |\\|37\\/\\/0|2|< 4((0|_||\\|7 \"{name}\"?" + +#: src/gui/accountslist.py:341 +msgid "Delete Social Network Account" +msgstr "|)3|_373 S0(14|_ |\\|37\\/\\/0|2|< 4((0|_||\\|7" + +#: src/gui/accountslist.py:440 +msgid "Are you sure you want to delete email account \"{account_name}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 3|\\/|41|_ 4((0|_||\\|7 \"{account_name}\"?" + +#: src/gui/accountslist.py:441 +msgid "Delete Email Account" +msgstr "|)3|_373 3|\\/|41|_ 4((0|_||\\|7" + +#: src/gui/accountwizard.py:31 +msgid "Digsby Setup Wizard" +msgstr "|)165|3'/ 537|_|P \\/\\/124|2|)" + +#: src/gui/accountwizard.py:40 +msgid "Welcome to Digsby!" +msgstr "\\/\\/3|_(0|\\/|3 2 |)165|3'/!" + +#: src/gui/accountwizard.py:116 +msgid "Quick Access to Newsfeeds" +msgstr "Q|_|1(|< 4((355 2 |\\|3\\/\\/s|=33|)2" + +#: src/gui/accountwizard.py:117 +msgid "" +"\n" +"You can access social network and email newsfeeds by clicking their icons in the tray.\n" +"\n" +"Double click to update your status (social networks) or launch your inbox (email accounts).\n" +" \n" +msgstr "" +"\n" +"J00 (4|\\| 4((352 50(14|_ |\\|37\\/\\/0|2|< |\\| 3|\\/|41|_ |\\|3\\/\\/5|=33|)2 |3'/ (|_1(|<1|\\|' 7#31|2 1(0|\\|2 1|\\| 73# 7|24'/.\n" +"\n" +"|)0|_|b|_3 (|_1(|< 2 |_|P|)473 '/0 5747|_|2 (50(14|_ |\\|37\\/\\/0|2|<2) 0|2 |_4|_||\\|(# '/0 1|\\||30>< (3|\\/|41|_ 4((0|_||\\|72).\n" +"\n" + +#: src/gui/addcontactdialog.py:200 +#: src/gui/chatgui.py:15 +#: src/gui/imdialogs.py:68 +msgid "No Connections" +msgstr "|\\|0 (0|\\||\\|3(710|\\|2" + +#: src/gui/addcontactdialog.py:214 +#: src/gui/pref/prefcontrols.py:1050 +#: src/tests/testgui/uberdemos/UberComboXTDemo.py:34 +msgid "Add" +msgstr "4|)|)" + +#: src/gui/addcontactdialog.py:228 +msgid "Contact Type:" +msgstr "(0|\\|74(7 7'/P3:" + +#: src/gui/addcontactdialog.py:232 +msgid "Screen Name:" +msgstr "5(|233|\\| |\\|4|\\/|3:" + +#: src/gui/addcontactdialog.py:240 +#: src/gui/contactdialogs.py:164 +msgid "Alias:" +msgstr "4|_142:" + +#: src/gui/addcontactdialog.py:244 +msgid "In Group:" +msgstr "1|\\| 6|20|_|P:" + +#: src/gui/addcontactdialog.py:249 +msgid "On Accounts:" +msgstr "0|\\| 4((0|_||\\|72:" + +#: src/gui/app/mainMenuEvents.py:46 +#: src/gui/app/menubar.py:38 +#: src/gui/buddylist/buddylistmenu.py:178 +#: src/gui/buddylist/buddylistmenu.py:307 +msgid "Status Panel" +msgstr "5747|_|2 P4|\\|3|_" + +#: src/gui/app/mainMenuEvents.py:47 +#: src/gui/buddylist/buddylistframe.py:595 +#: src/gui/buddylist/buddylistmenu.py:308 +#: src/gui/pref/prefstrings.py:17 +#: src/main.py:504 +msgid "Buddy List" +msgstr "|3|_||)|)'/ |_157" + +#: src/gui/app/mainMenuEvents.py:48 +#: src/gui/buddylist/buddylistmenu.py:309 +msgid "Email Accounts" +msgstr "3|\\/|41|_ 4((0|_||\\|72" + +#: src/gui/app/mainMenuEvents.py:49 +#: src/gui/buddylist/buddylistmenu.py:310 +msgid "Social Networks" +msgstr "50(14|_ |\\|37\\/\\/0|2|<2" + +#: src/gui/app/mainMenuEvents.py:50 +#: src/gui/buddylist/buddylistmenu.py:311 +msgid "Connections" +msgstr "(0|\\||\\|3(710|\\|2" + +#: src/gui/app/mainMenuEvents.py:150 +#: src/gui/app/menubar.py:11 +#: src/gui/buddylist/buddylistmenu.py:297 +msgid "&Digsby" +msgstr "&D165|3'/" + +#: src/gui/app/mainMenuEvents.py:152 +#: src/gui/app/menubar.py:56 +#: src/gui/buddylist/buddylistmenu.py:243 +msgid "&Sort By" +msgstr "&50|27 |3'/" + +#: src/gui/app/mainMenuEvents.py:154 +#: src/gui/app/menubar.py:32 +#: src/gui/buddylist/buddylistmenu.py:302 +msgid "&View" +msgstr "&V13\\/\\/" + +#: src/gui/app/mainMenuEvents.py:175 +#: src/gui/app/menubar.py:24 +#: src/gui/buddylist/buddylistmenu.py:133 +#: src/gui/buddylist/buddylistmenu.py:150 +msgid "&Rename Selection" +msgstr "&R3|\\|4|\\/|3 53|_3(710|\\|" + +#: src/gui/app/mainMenuEvents.py:176 +#: src/gui/buddylist/buddylistmenu.py:151 +msgid "&Delete Selection" +msgstr "&D3|_373 53|_3(710|\\|" + +#: src/gui/app/mainMenuEvents.py:180 +#: src/gui/buddylist/buddylistmenu.py:155 +msgid "&Rename {name}" +msgstr "&R3|\\|4|\\/|3 {name}" + +#: src/gui/app/mainMenuEvents.py:181 +#: src/gui/buddylist/buddylistmenu.py:156 +msgid "&Delete {name}" +msgstr "&D3|_373 {name}" + +#: src/gui/app/mainMenuEvents.py:312 +#: src/gui/buddylist/buddylistmenu.py:172 +msgid "You can bring back the menubar by right clicking on the digsby icon in the task tray." +msgstr "J00 (4|\\| |3|21|\\|' |34(|< 73# |\\/|3|\\||_||34|2 |3'/ |216#7 (|_1(|<1|\\|' 0|\\| 73# |)165'/ 1(0|\\| 1|\\| 73# 745|< 7|24'/." + +#: src/gui/app/mainMenuEvents.py:314 +#: src/gui/buddylist/buddylistmenu.py:174 +msgid "Hide Menu Bar" +msgstr "#1|)3 |\\/|3|\\||_| |34|2" + +#: src/gui/app/mainMenuEvents.py:330 +#: src/gui/buddylist/buddylistmenu.py:322 +#: src/gui/visuallisteditor.py:286 +msgid "Arrange Panels" +msgstr "4|2|24|\\|63 &P4|\\|3|_2" + +#: src/gui/app/menubar.py:14 +#: src/gui/buddylist/buddylistmenu.py:109 +msgid "&New Status Message..." +msgstr "&N3\\/\\/ 5747|_|2 |\\/|355463..." + +#: src/gui/app/menubar.py:15 +#: src/gui/buddylist/buddylistmenu.py:110 +msgid "&Edit Status Messages..." +msgstr "&3|)17 5747|_|2 |\\/|3554632..." + +#: src/gui/app/menubar.py:17 +#: src/gui/buddylist/buddylistmenu.py:122 +msgid "My Status" +msgstr "|\\/|'/ 5747|_|5" + +#: src/gui/app/menubar.py:18 +#: src/gui/buddylist/buddylistmenu.py:123 +msgid "My &Accounts..." +msgstr "|\\/|'/ &A((0|_||\\|72..." + +#: src/gui/app/menubar.py:21 +msgid "&New IM..." +msgstr "&N3\\/\\/ 1|\\/|..." + +#: src/gui/app/menubar.py:22 +msgid "Add &Contact..." +msgstr "4|)|) &(0|\\|74(7..." + +#: src/gui/app/menubar.py:23 +msgid "Add &Group..." +msgstr "4|)|) &6|20|_|P..." + +#: src/gui/app/menubar.py:25 +#: src/gui/buddylist/buddylistmenu.py:134 +msgid "&Delete Selection..." +msgstr "&D3|_373 53|_3(710|\\|..." + +#: src/gui/app/menubar.py:27 +msgid "Sign &Off Digsby" +msgstr "516|\\| &O|=|= |)165|3'/" + +#: src/gui/app/menubar.py:29 +#: src/gui/buddylist/buddylistmenu.py:138 +#: src/gui/trayicons.py:197 +msgid "E&xit Digsby" +msgstr "3&><17 |)165|3'/" + +#: src/gui/app/menubar.py:33 +#: src/gui/buddylist/buddylistmenu.py:165 +msgid "&Always On Top" +msgstr "&4|_\\/\\/4'/2 0|\\| 70P" + +#: src/gui/app/menubar.py:34 +msgid "Skins..." +msgstr "5|<1|\\|2..." + +#: src/gui/app/menubar.py:37 +#: src/gui/buddylist/buddylistmenu.py:168 +msgid "&Menu Bar" +msgstr "&M3|\\||_| |34|2" + +#: src/gui/app/menubar.py:39 +#: src/gui/buddylist/buddylistmenu.py:179 +msgid "Arrange &Panels..." +msgstr "4|2|24|\\|63 &P4|\\|3|_2..." + +#: src/gui/app/menubar.py:41 +msgid "Show &Mobile Contacts" +msgstr "5#0\\/\\/ &M0|31|_3 (0|\\|74(72" + +#: src/gui/app/menubar.py:42 +msgid "Show &Offline Contacts" +msgstr "5#0\\/\\/ &O|=|=|_1|\\|3 (0|\\|74(72" + +#: src/gui/app/menubar.py:43 +msgid "&Group Offline Contacts" +msgstr "&6|20|_|P 0|=|=|_1|\\|3 (0|\\|74(72" + +#: src/gui/app/menubar.py:44 +#: src/gui/buddylist/buddylistmenu.py:185 +msgid "&Hide Offline Groups" +msgstr "&H1|)3 0|=|=|_1|\\|3 6|20|_|P2" + +#: src/gui/app/menubar.py:49 +#: src/gui/buddylist/buddylistmenu.py:208 +msgid "&None" +msgstr "&N0|\\|3" + +#: src/gui/app/menubar.py:50 +#: src/gui/buddylist/buddylistmenu.py:209 +msgid "&Status" +msgstr "&5747|_|2" + +#: src/gui/app/menubar.py:51 +#: src/gui/buddylist/buddylistmenu.py:210 +msgid "N&ame" +msgstr "|\\|&A|\\/|3" + +#: src/gui/app/menubar.py:52 +#: src/gui/buddylist/buddylistmenu.py:211 +msgid "&Log Size" +msgstr "&L06 5123" + +#: src/gui/app/menubar.py:53 +#: src/gui/buddylist/buddylistmenu.py:212 +msgid "Ser&vice" +msgstr "53|2&V1(3" + +#: src/gui/app/menubar.py:54 +#: src/gui/buddylist/buddylistmenu.py:222 +#: src/gui/buddylist/buddylistmenu.py:230 +msgid "Advan&ced..." +msgstr "4|)\\/4|\\|&(3|)..." + +#: src/gui/app/menubar.py:59 +#: src/gui/buddylist/buddylistmenu.py:303 +msgid "&Tools" +msgstr "&700|_2" + +#: src/gui/app/menubar.py:60 +#: src/gui/trayicons.py:192 +msgid "&Preferences..." +msgstr "&P|23|=3|23|\\|(32..." + +#: src/gui/app/menubar.py:61 +msgid "&File Transfer History" +msgstr "&|=1|_3 7|24|\\|5|=3|2 #1570|2'/" + +#: src/gui/app/menubar.py:62 +msgid "&Chat History" +msgstr "&(#47 #1570|2'/" + +#: src/gui/app/menubar.py:65 +#: src/gui/buddylist/buddylistmenu.py:304 +msgid "&Help" +msgstr "&H3|_P" + +#: src/gui/app/menubar.py:66 +#: src/gui/buddylist/buddylistmenu.py:265 +msgid "&Documentation" +msgstr "&D0(|_||\\/|3|\\|74710|\\|" + +#: src/gui/app/menubar.py:67 +#: src/gui/buddylist/buddylistmenu.py:266 +msgid "Support &Forums" +msgstr "5|_|PP0|27 &F0|2|_||\\/|2" + +#: src/gui/app/menubar.py:69 +#: src/gui/buddylist/buddylistmenu.py:268 +msgid "&Submit Bug Report" +msgstr "&5|_||3|\\/|17 |3|_|6 |23P0|27" + +#: src/gui/app/menubar.py:70 +#: src/gui/buddylist/buddylistmenu.py:269 +msgid "Su&ggest a Feature" +msgstr "5|_|&G6357 @ |=347|_||23" + +#: src/gui/app/menubar.py:78 +msgid "&Invite Your Friends" +msgstr "&1|\\|\\/173 '/0 |=|213|\\||)2" + +#: src/gui/app/menubar.py:79 +#: src/plugins/digsby_about/aboutdialog.py:132 +msgid "&About Digsby" +msgstr "&4|30|_|7 |)165|3'/" + +#: src/gui/authorizationdialog.py:21 +msgid "Authorize Contact" +msgstr "4|_|7#0|2123 (0|\\|74(7" + +#: src/gui/authorizationdialog.py:41 +msgid "Authorize and Add" +msgstr "4|_|7#0|2123 |\\| 4|)|)" + +#: src/gui/authorizationdialog.py:42 +msgid "Authorize" +msgstr "4|_|7#0|2123" + +#: src/gui/authorizationdialog.py:43 +msgid "Deny" +msgstr "|)3|\\|'/" + +#: src/gui/browser/jsconsole.py:15 +msgid "Javascript Console" +msgstr "J4\\/45(|21P7 (0|\\|50|_3" + +#: src/gui/browser/jsconsole.py:47 +#: src/gui/browser/jsconsole.py:48 +msgid "Clear" +msgstr "(|_34|2" + +#: src/gui/browser/webkit/webkiteditsource.py:41 +msgid "&Copy" +msgstr "&(0P'/" + +#: src/gui/browser/webkit/webkiteditsource.py:72 +msgid "&Open in Browser" +msgstr "&0P3|\\| 1|\\| |3|20\\/\\/53|2" + +#: src/gui/browser/webkit/webkiteditsource.py:74 +msgid "Launches browser in pref \"debug.message_area.debug_browser\"" +msgstr "|_4|_||\\|(#32 |3|20\\/\\/53|2 1|\\| P|23|= \"debug.message_area.debug_browser\"" + +#: src/gui/buddylist/accounttray.py:231 +msgid "{account.offline_reason} ({account.email_address})" +msgstr "{account.offline_reason} ({account.email_address})" + +#: src/gui/buddylist/accounttray.py:238 +msgid "{count} unread message ({account.email_address})" +msgid_plural "{count} unread messages ({account.email_address})" +msgstr[0] "{count} |_||\\||234|) |\\/|355463 ({account.email_address})" +msgstr[1] "{count} |_||\\||234|) |\\/|3554635 ({account.email_address})" + +#: src/gui/buddylist/accounttray.py:250 +msgid "{account.offline_reason} ({account.name})" +msgstr "{account.offline_reason} ({account.name})" + +#: src/gui/buddylist/accounttray.py:252 +msgid "{count} new alert ({account.name})" +msgid_plural "{count} new alerts ({account.name})" +msgstr[0] "{count} |\\|3\\/\\/ 4|_3|27 ({account.name})" +msgstr[1] "{count} |\\|3\\/\\/ 4|_3|272 ({account.name})" + +#: src/gui/buddylist/buddylist.py:190 +msgid "BuddyList Popup" +msgstr "|3|_||)|)'/|_1t7 P0P|_|P" + +#: src/gui/buddylist/buddylist.py:240 +#: src/gui/pref/pg_accounts.py:789 +msgid "Add Accounts" +msgstr "4|)|) 4((0|_||\\|72" + +#: src/gui/buddylist/buddylist.py:253 +msgid "Need Help?" +msgstr "|\\|33|) #3|_P?" + +#: src/gui/buddylist/buddylist.py:405 +msgid "&Add Group" +msgstr "&4|)|) 6|20|_|P" + +#: src/gui/buddylist/buddylist.py:658 +#: src/gui/buddylist/buddylist.py:671 +#: src/gui/contactdialogs.py:279 +#: src/gui/contactdialogs.py:306 +msgid "Merge Contacts" +msgstr "|\\/|3|263 (0|\\|74(72" + +#: src/gui/buddylist/buddylist.py:941 +msgid "This buddy can't accept file transfers" +msgstr "|)12 |3|_||)|)'/ (4|\\|'7 4((3P7 |=1|_3 7|24|\\|5|=3|22" + +#: src/gui/buddylist/buddylist.py:942 +#: src/gui/capabilitiesbar.py:81 +#: src/gui/capabilitiesbar.py:351 +#: src/gui/imwin/imwin_native.py:378 +#: src/gui/imwin/imwinmenu.py:14 +msgid "Send File" +msgstr "53|\\||) |=1|_3" + +#: src/gui/buddylist/buddylist.py:1010 +msgid "&Add Contact" +msgstr "&4|)|) (0|\\|74(7" + +#: src/gui/buddylist/buddylistmenu.py:125 +msgid "&New IM...\tCtrl+N" +msgstr "&N3\\/\\/ 1|\\/|...\tCtrl+N" + +#: src/gui/buddylist/buddylistmenu.py:127 +msgid "New Group C&hat...\tCtrl+Shift+N" +msgstr "|\\|3\\/\\/ 6|20|_|P (&H47...\tCtrl+Shift+N" + +#: src/gui/buddylist/buddylistmenu.py:131 +msgid "Add &Contact...\tCtrl+A" +msgstr "4|)|) &(0|\\|74(7...\tCtrl+A" + +#: src/gui/buddylist/buddylistmenu.py:132 +msgid "Add &Group...\tCtrl+Shift+A" +msgstr "4|)|) &6|20|_|P...\tCtrl+Shift+A" + +#: src/gui/buddylist/buddylistmenu.py:136 +#, python-format +msgid "Sign &Off Digsby (%s)" +msgstr "516|\\| &O|=|= |)165|3'/ (%s)" + +#: src/gui/buddylist/buddylistmenu.py:166 +msgid "Skins...\tCtrl+S" +msgstr "5|<1|\\|2...\tCtrl+S" + +#: src/gui/buddylist/buddylistmenu.py:181 +msgid "Show &Mobile Contacts\tCtrl+M" +msgstr "5#0\\/\\/ &M0|31|_3 (0|\\|74(72\tCtrl+M" + +#: src/gui/buddylist/buddylistmenu.py:182 +msgid "Show &Offline Contacts\tCtrl+O" +msgstr "5#0\\/\\/ &O|=|=|_1|\\|3 (0|\\|74(72\tCtrl+O" + +#: src/gui/buddylist/buddylistmenu.py:183 +msgid "&Group Offline Contacts\tCtrl+G" +msgstr "&6|20|_|p 0|=|=|_1|\\|3 (0|\\|74(72\tCtrl+G" + +#: src/gui/buddylist/buddylistmenu.py:242 +msgid "&Group By" +msgstr "&6|20|_|p |3'/" + +#: src/gui/buddylist/buddylistmenu.py:250 +msgid "&Preferences...\tCtrl+P" +msgstr "&P|23|=3|23|\\|(32...\tCtrl+P" + +#: src/gui/buddylist/buddylistmenu.py:251 +msgid "Buddy List &Search\tCtrl+F" +msgstr "|3|_||)|)'/ |_157 &534|2(#\tCtrl+F" + +#: src/gui/buddylist/buddylistmenu.py:252 +msgid "&File Transfer History\tCtrl+J" +msgstr "&|=1|_3 7|24|\\|5|=3|2 #1570|2'/\tCtrl+J" + +#: src/gui/buddylist/buddylistmenu.py:258 +msgid "&Chat History\tCtrl+H" +msgstr "&(#47 #1570|2'/\tCtrl+H" + +#: src/gui/buddylist/buddylistmenu.py:273 +msgid "Show Debug Console" +msgstr "5#0\\/\\/ |)3|3|_|6 (0|\\|50|_3" + +#: src/gui/buddylist/buddylistmenu.py:276 +msgid "Su&pport Digsby" +msgstr "5|_|&PP0|27 |)165|3'/" + +#: src/gui/buddylist/buddylistmenu.py:299 +#: src/gui/pref/iconeditor.py:546 +msgid "&File" +msgstr "&|=1|_3" + +#: src/gui/buddylist/buddylistmenu.py:331 +msgid "&Sign Off" +msgstr "&516|\\| 0|=|=" + +#: src/gui/buddylist/buddylistmenu.py:332 +msgid "&Sign On" +msgstr "&516|\\| 0|\\|" + +#: src/gui/buddylist/buddylistmenu.py:335 +msgid "&Edit Account..." +msgstr "&3|)17 4((0|_||\\|7..." + +#: src/gui/buddylist/buddylistmenu.py:405 +msgid "&Stealth Settings" +msgstr "&5734|_7# 53771|\\|62" + +#: src/gui/buddylist/buddylistmenu.py:411 +msgid "Selection" +msgstr "53|_3(710|\\|" + +#: src/gui/buddylist/buddylistmenu.py:414 +msgid "Appear Online to {name}" +msgstr "4PP34|2 0|\\||_1|\\|3 2 {name}" + +#: src/gui/buddylist/buddylistmenu.py:416 +msgid "Appear Offline to {name}" +msgstr "4PP34|2 0|=|=|_1|\\|3 2 {name}" + +#: src/gui/buddylist/buddylistmenu.py:418 +msgid "Appear Permanently Offline to {name}" +msgstr "4PP34r P3|2|\\/|4|\\|3|\\|7|_'/ 0|=|=|_1|\\|3 2 {name}" + +#: src/gui/buddylist/buddylistmenu.py:423 +msgid "Learn More (URL)" +msgstr "|_34|2|\\| |\\/|0|23 (|_||2|_)" + +#: src/gui/buddylist/buddylistmenu.py:463 +msgid "Chat with Resource" +msgstr "(#47 \\/\\/17# |2350|_||2(3" + +#: src/gui/buddylist/buddylistmenu.py:480 +msgid "&Remove from Merged Contact" +msgstr "&R3|\\/|0\\/3 |=|20|\\/| |\\/|3|263|) (0|\\|74(7" + +#: src/gui/buddylist/renderers.py:831 +#: src/gui/searchgui.py:122 +msgid "Options..." +msgstr "0P710|\\|2..." + +#: src/gui/bugreporter/bugreportergui.py:41 +msgid "Use this tool to submit a diagnostic log right after you experience a bug" +msgstr "|_|53 |)12 700|_ 2 5|_||3|\\/|17 @ |)146|\\|0571O( |_06 |216#7 4|=73|2 J00 ><(3P7 \\/\\/#3|23 17 |)1|23(7|_'/ P3|2741|\\|2 2 |\\| 3|2|20|2." + +#: src/gui/bugreporter/bugreportergui.py:51 +#: src/gui/bugreporter/bugreportergui.py:83 +msgid "Please describe the bug in as much detail as possible. Include information such as what you were doing when the bug occurred and exactly what goes wrong." +msgstr "P|_3453 |)35(|21|33 73# |3|_|6 1|\\| 42 |\\/||_|(# ||)3741|_ 42 P0551|3|_3. 1|\\|(|_|_||)3 1|\\||=0|2|\\/|4710|\\| 5|_|(# 42 \\/\\/07 J00 \\/\\/|_|2 |)01|\\|' \\/\\/#3|\\| 73# |3|_|6 0((|_||2|23|) |\\| ><4(7|_'/ \\/\\/07 6032 \\/\\/|20|\\|6." + +#: src/gui/bugreporter/bugreportergui.py:61 +msgid "Can you consistently reproduce this bug?" +msgstr "(4|\\| J00 (0|\\|51573|\\|7|_'/ |23P|20|)|_|(3 |)12 |3|_|6?" + +#: src/gui/bugreporter/bugreportergui.py:63 +msgid "&Yes" +msgstr "&Y32" + +#: src/gui/bugreporter/bugreportergui.py:64 +msgid "&No" +msgstr "&N0" + +#: src/gui/bugreporter/bugreportergui.py:65 +msgid "&Don't Know" +msgstr "&D0|\\|'7 |<|\\|0\\/\\/" + +#: src/gui/bugreporter/bugreportergui.py:68 +msgid "If this is a visual bug, please attach a screenshot to this report." +msgstr "1|= |)12 |3 @ \\/15|_|4|_ |3|_|6, P|_3453 4774(# @ 5(|233|\\|5#07 2 |)12 |23P0|27." + +#: src/gui/bugreporter/bugreportergui.py:69 +#: src/gui/bugreporter/bugreportergui.py:104 +#: src/gui/bugreporter/bugreportergui.py:141 +msgid "Take Screenshot" +msgstr "74|<3 5(|233|\\|5#07" + +#: src/gui/bugreporter/bugreportergui.py:73 +msgid "Taking Screenshot in {secs}" +msgstr "74|<1|\\|' 5(|233|\\|5#07 1|\\| {secs}" + +#: src/gui/bugreporter/bugreportergui.py:139 +msgid "Remove Screenshot" +msgstr "|23|\\/|0\\/3 5(|233|\\|5#07" + +#: src/gui/bugreporter/bugreportergui.py:199 +msgid "Submit Bug Report - Digsby" +msgstr "5|_||3|\\/|17 |3|_|6 |23p0|27 - |)165|3'/" + +#: src/gui/bugreporter/bugreportergui.py:212 +msgid "Submit" +msgstr "5|_||3|\\/|17" + +#: src/gui/bugreporter/bugreportergui.py:225 +msgid "Please enter a description of the bug." +msgstr "P|_3453 3|\\|73|2 |)35(|21P710|\\| 0|= 73|-| |3|_|6" + +#: src/gui/bugreporter/bugreportergui.py:226 +msgid "Include as much information as possible about what went wrong and how to reproduce the issue." +msgstr "" +"P|_3453 3|\\|73|2 @ |)35(|21P710|\\| 0|= 73# |3|_|6.\n" +"\n" +"1|\\|c|_|_||)3 42 |\\/||_|(# 1|\\||=0r|\\/|4710|\\| 42 P0551|3|_3 4|30|_|7\n" +"\\/\\/07 \\/\\/3|\\|7 \\/\\/|20|\\|6 |\\| #0\\/\\/ 2 |23P|20|0|_|(3 73# 155|_|3." + +#: src/gui/bugreporter/bugreportergui.py:230 +#: src/gui/tracebackdialog.py:14 +msgid "Send Bug Report" +msgstr "53|\\||) |3|_|6 |23P0|27" + +#: src/gui/bugreporter/bugreportergui.py:255 +msgid "Submit Bug Report - Screenshot - Digsby" +msgstr "5|_||3|\\/|17 |3|_|6 |23p0|27 - 5(|233|\\|5#07 - |)165|3'/" + +#: src/gui/bugreporter/bugreportergui.py:277 +msgid "Blank out non Digsby windows" +msgstr "|3|_4|\\||< 0|_|7 |\\|0|\\| |)165|3'/ \\/\\/1|\\||)0\\/\\/2" + +#: src/gui/bugreporter/bugreportergui.py:284 +msgid "Is it OK to send this screenshot to digsby.com?" +msgstr "|3 17 0|< 2 53|\\||) |)12 5(|233|\\|5#07 2 |)165|3'/.(0|\\/|?" + +#: src/gui/capabilitiesbar.py:24 +msgid "Info" +msgstr "1|\\||=0" + +#: src/gui/capabilitiesbar.py:24 +msgid "View buddy information" +msgstr "\\/13\\/\\/ |3|_||)|)'/ 1|\\||=0|2|\\/|4710|\\|" + +#: src/gui/capabilitiesbar.py:25 +#: src/gui/imdialogs.py:43 +msgid "IM" +msgstr "1|\\/|" + +#: src/gui/capabilitiesbar.py:25 +msgid "Instant message this buddy" +msgstr "1|\\|574|\\|7 |\\/|355463 |)12 |3|_||)|)'/" + +#: src/gui/capabilitiesbar.py:26 +msgid "Start an audio/video chat" +msgstr "574|27 |\\| 4|_||)10/\\/1|)30 (#47" + +#: src/gui/capabilitiesbar.py:26 +msgid "Video" +msgstr "\\/1|)30" + +#: src/gui/capabilitiesbar.py:27 +msgid "Files" +msgstr "|=1|_32" + +#: src/gui/capabilitiesbar.py:27 +msgid "Send files to this buddy" +msgstr "53|\\||) |=1|_32 2 |)12 |3|_||)|)'/" + +#: src/gui/capabilitiesbar.py:29 +#: src/gui/vcard/vcardgui.py:138 +msgid "Email" +msgstr "3|\\/|41|_" + +#: src/gui/capabilitiesbar.py:29 +msgid "Send email" +msgstr "53|\\||) 3|\\/|41|_" + +#: src/gui/capabilitiesbar.py:30 +msgid "SMS" +msgstr "5|\\/|5" + +#: src/gui/capabilitiesbar.py:30 +msgid "Send SMS messages" +msgstr "53|\\||) 5|\\/|5 |\\/|3554632" + +#: src/gui/capabilitiesbar.py:107 +msgid "Group Chat" +msgstr "6|20|_|P (#47" + +#: src/gui/capabilitiesbar.py:110 +msgid "View Past Chats" +msgstr "\\/13\\/\\/ P457 (#472" + +#: src/gui/capabilitiesbar.py:114 +msgid "Alert Me When..." +msgstr "4|_3|27 |\\/|3 \\/\\/#3|\\|..." + +#: src/gui/capabilitiesbar.py:115 +msgid "Block" +msgstr "|3|_0(|<" + +#: src/gui/capabilitiesbar.py:138 +msgid "To:" +msgstr "2:" + +#: src/gui/capabilitiesbar.py:141 +msgid "From:" +msgstr "|=|20|\\/|:" + +#: src/gui/capabilitiesbar.py:300 +msgid "Unblock {name}" +msgstr "|_||\\||3|_0(|< {name}" + +#: src/gui/capabilitiesbar.py:302 +msgid "Block {name}" +msgstr "|3|_0(|< {name}" + +#: src/gui/capabilitiesbar.py:354 +#: src/gui/imwin/imwin_native.py:379 +msgid "Transfer History" +msgstr "7|24|\\|5|=3|2 #1570|2'/" + +#: src/gui/capabilitiesbar.py:377 +msgid "Icons Only" +msgstr "1(0|\\|2 0|\\||_'/" + +#: src/gui/capabilitiesbar.py:378 +msgid "Text Only" +msgstr "73><7 0|\\||_'/" + +#: src/gui/capabilitiesbar.py:379 +msgid "Icons Next to Text" +msgstr "1(0|\\|2 |\\|3><7 2 73><7" + +#: src/gui/capabilitiesbar.py:380 +msgid "Icons Above Text" +msgstr "1(0|\\|2 4|30\\/3 73><7" + +#: src/gui/capabilitiesbar.py:389 +msgid "Hide Actions Bar" +msgstr "#1|)3 4(710|\\|2 |34|2" + +#: src/gui/chatgui.py:90 +msgid "Join Chat Room" +msgstr "J01|\\| (#47 R00|\\/|" + +#: src/gui/chatgui.py:98 +#: src/hub.py:155 +msgid "Join Chat" +msgstr "J01|\\| (#47" + +#: src/gui/chatgui.py:111 +msgid "Account:" +msgstr "4((0|_||\\|7:" + +#: src/gui/chatgui.py:117 +msgid "Invite:" +msgstr "1|\\|\\/173:" + +#: src/gui/chatgui.py:155 +msgid "Server:" +msgstr "53|2\\/3|2:" + +#: src/gui/chatgui.py:160 +msgid "Room Name:" +msgstr "|200|\\/| |\\|4|\\/|3:" + +#: src/gui/contactdialogs.py:37 +msgid "Would you like to send \"{files[0]}\" to {buddy_name:s}?" +msgid_plural "Would you like to send {num_files:d} files to {buddy_name:s}?" +msgstr[0] "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 53|\\||) \"{files[0]}\" 2 {buddy_name:s}?" +msgstr[1] "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 53|\\||) {num_files:d} files 2 {buddy_name:s}?" + +#: src/gui/contactdialogs.py:41 +msgid "Send Files" +msgstr "53|\\||) |=1|_32" + +#: src/gui/contactdialogs.py:78 +msgid "Contact &Name:" +msgstr "(0|\\|74(7 &N4|\\/|3:" + +#: src/gui/contactdialogs.py:81 +msgid "Accoun&t:" +msgstr "4((0|_||\\|&7:" + +#: src/gui/contactdialogs.py:87 +msgid "&Add" +msgstr "&4|)|)" + +#: src/gui/contactdialogs.py:158 +msgid "Would you like to merge these contacts?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 |\\/|3|263 7#353 (0|\\|74(72?" + +#: src/gui/contactdialogs.py:161 +msgid "They will appear as one item on your buddy list." +msgstr "7#3'/ \\/\\/1|_|_ 4PP34|2 42 0|\\|3 173|\\/| 0|\\| '/0 |3|_||)|)'/ |_157." + +#: src/gui/contactdialogs.py:182 +msgid "Drag and drop to rearrange:" +msgstr "|)|246 |\\| |)|20P 2 |234|2|24|\\|63:" + +#: src/gui/filetransfer/filetransferlist.py:234 +#: src/gui/filetransfer/filetransferlist.py:243 +msgid "File not found" +msgstr "|=1|_3 |\\|07 |=0|_||\\||)" + +#: src/gui/filetransfer/filetransferlist.py:461 +#: src/gui/filetransfer/filetransferlist.py:599 +#: src/gui/pref/pg_files.py:11 +#: src/gui/pref/prefstrings.py:19 +msgid "File Transfers" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: src/gui/filetransfer/filetransferlist.py:474 +msgid "Open &Containing Folder" +msgstr "0P3|\\| &C0|\\|741|\\|1|\\|' |=0|_|)3|2" + +#: src/gui/filetransfer/filetransferlist.py:542 +msgid "Clean Up" +msgstr "(|_34|\\| |_|P" + +#: src/gui/filetransfer/filetransferlist.py:642 +msgid "file" +msgid_plural "files" +msgstr[0] "|=1|_3" +msgstr[1] "|=1|_32" + +#: src/gui/helpdigsby.py:4 +msgid "Keep Digsby Free" +msgstr "|<33P |)165|3'/ |=|233" + +#: src/gui/helpdigsby.py:5 +msgid "Digsby will use your computer's free time using it to conduct both free and paid research." +msgstr "|)165|3'/ \\/\\/1|_|_ |_|53 '/0 (0|\\/|P|_|73|2'2 |=|233 71|\\/|3 |_|51|\\|6 17 2 (0|\\||)|_|(7 |307# |=|233 |\\| P41|) |23534|2(#." + +#: src/gui/helpdigsby.py:14 +#: src/gui/pref/pg_privacy.py:342 +#: src/gui/pref/pg_research.py:129 +msgid "Options" +msgstr "0P710|\\|2" + +#: src/gui/helpdigsby.py:15 +#: src/gui/imagedialog.py:11 +msgid "OK" +msgstr "0|<" + +#: src/gui/imagedialog.py:71 +msgid "Send Image" +msgstr "53|\\||) 1|\\/|463" + +#: src/gui/imagedialog.py:72 +msgid "&Send Image" +msgstr "&53|\\||) 1|\\/|463" + +#: src/gui/imagedialog.py:73 +msgid "&Don't Send" +msgstr "&D0|\\|'7 53|\\||)" + +#: src/gui/imdialogs.py:19 +msgid "New IM" +msgstr "|\\|3\\/\\/ 1|\\/|" + +#: src/gui/imdialogs.py:30 +msgid "To" +msgstr "2" + +#: src/gui/imdialogs.py:36 +msgid "From" +msgstr "|=|20|\\/|" + +#: src/gui/imwin/imhub.py:483 +msgid "Group Chat ({chat.chat_member_count:d})" +msgstr "6|20|_|P (|-|47 ({chat.chat_member_count:d})" + +#: src/gui/imwin/imhub.py:484 +msgid "{alias:s}: {message:s}" +msgstr "{alias:s}: {message:s}" + +#: src/gui/imwin/imtabs.py:57 +msgid "Close IM Window" +msgstr "(|_053 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/imwin/imtabs.py:58 +msgid "Warn me when I attempt to close multiple conversations" +msgstr "\\/\\/4|2|\\| |\\/|3 \\/\\/#3|\\| 1 4773|\\/|P7 2 (|_053 |\\/||_||_71P|_3 (0|\\|\\/3|254710|\\|2" + +#: src/gui/imwin/imtabs.py:59 +msgid "You are about to close {num_tabs} conversations. Are you sure you want to continue?" +msgstr "J00 12 4|30|_|7 2 (|_053 {num_tabs} (0|\\|\\/3|254710|\\|2. 12 J00 5|_||23 J00 \\/\\/4|\\|7 2 (0|\\|71|\\||_|3?" + +#: src/gui/imwin/imtabs.py:60 +msgid "Close &tabs" +msgstr "(|_043 &74|32" + +#: src/gui/imwin/imtabs.py:757 +msgid "IM Windows" +msgstr "1|\\/| \\/\\/1|\\||)0\\/\\/2" + +#: src/gui/imwin/imwin_ctrl.py:209 +msgid "Please add an email address for this buddy by clicking the \"To:\" box." +msgstr "P|_3453 4|)|) |\\| 3|\\/|41|_ 4|)|)|2352 4 |)12 |3|_||)|)'/ |3'/ (|_1(|<1|\\|' 73# \"2:\" |30><." + +#: src/gui/imwin/imwin_ctrl.py:211 +msgid "Compose email to {name}" +msgstr "(0|\\/|P053 3|\\/|41|_ 2 {name}" + +#: src/gui/imwin/imwin_ctrl.py:221 +msgid "Message Sent" +msgstr "|\\/|355463 53|\\|7" + +#: src/gui/imwin/imwin_ctrl.py:230 +msgid "Failed to Send Email" +msgstr "|=41|_3|) 2 53|\\||) 3|\\/|41|_" + +#: src/gui/imwin/imwin_ctrl.py:234 +msgid "Sending..." +msgstr "53|\\||)1|\\|'..." + +#: src/gui/imwin/imwin_ctrl.py:270 +msgid "Please add an SMS number first." +msgstr "P|_3453 4|)|) |\\| 5|\\/|5 |\\||_||\\/||33|2 |=1|257." + +#: src/gui/imwin/imwin_ctrl.py:271 +#: src/gui/imwin/imwin_ctrl.py:274 +msgid "Send SMS Message" +msgstr "53|\\||) 5|\\/|5 |\\/|355463" + +#: src/gui/imwin/imwin_ctrl.py:273 +msgid "You are not signed in to any accounts which can send SMS messages." +msgstr "J00 12 |\\|07 516|\\|3|) 1|\\| 2 4|\\|'/ 4((0|_||\\|72 \\/\\/#1(# (4|\\| 53|\\||) 5|\\/|5 |\\/|3554632." + +#: src/gui/imwin/imwin_ctrl.py:288 +msgid "The error message received was:" +msgstr "1 4|\\/| 3|2|20|2:" + +#: src/gui/imwin/imwin_ctrl.py:292 +msgid "There was an error in sending your SMS message." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 1|\\| 53|\\||)1|\\|' '/0 5|\\/|5 |\\/|355463." + +#: src/gui/imwin/imwin_ctrl.py:293 +msgid "Send SMS Message Error" +msgstr "53|\\||) 5|\\/|5 |\\/|355463 3|2|20|2" + +#: src/gui/imwin/imwin_ctrl.py:299 +msgid "Only the first {max_length:d} characters of your message can be sent over SMS:" +msgstr "0|\\||_'/ 73|-| |=1|257 {max_length:d} (|-|4|22 0 |_| |\\/|355463 (4|\\| |33 53|\\|7 0\\/3|2 5|\\/|5:" + +#: src/gui/imwin/imwin_ctrl.py:300 +msgid "Do you want to send this message now?" +msgstr "53|\\||) |\\/|355463 |\\|40?" + +#: src/gui/imwin/imwin_ctrl.py:303 +msgid "Send SMS - Character Limit" +msgstr "53|\\||) 5|\\/|5 - (#4|24(73|2 |_1|\\/|17" + +#: src/gui/imwin/imwin_ctrl.py:461 +msgid "Reconnected" +msgstr "|23(0|\\||\\|3(73|)" + +#: src/gui/imwin/imwin_ctrl.py:689 +msgid "You can only have one audio/video call at a time." +msgstr "J00 (4|\\| 0|\\||_'/ #4\\/3 0|\\|3 4|_||)10/\\/1|)30 (4|_|_ 47 @ 71|\\/|3." + +#: src/gui/imwin/imwin_email.py:49 +msgid "Edit in {client}..." +msgstr "3|)17 |\\| {client}..." + +#: src/gui/imwin/imwin_email.py:95 +msgid "Subject:" +msgstr "5|_||3J3(7:" + +#: src/gui/imwin/imwin_email.py:116 +#: src/gui/uberwidgets/formattedinput2/iminput.py:67 +msgid "Send" +msgstr "53|\\||)" + +#: src/gui/imwin/imwin_gui.py:43 +msgid "Typing" +msgstr "7'/P1|\\|'" + +#: src/gui/imwin/imwin_gui.py:44 +msgid "Entered Text" +msgstr "3|\\|73|23|)" + +#: src/gui/imwin/imwin_gui.py:75 +msgid "Digsby Announcement" +msgstr "|)165|3'/ 4|\\||\\|0|_||\\|(3|\\/|3|\\|7" + +#: src/gui/imwin/imwin_gui.py:265 +#, python-format +msgid "Would you like to send this image to %s?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 53|\\||) |)12 1|\\/|463 2 %s" + +#: src/gui/imwin/imwin_native.py:417 +#: src/gui/uberwidgets/formattedinput.py:758 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:137 +msgid "Choose a background color" +msgstr "(#0053 @ |34(|<6|20|_||\\||) (0|_0|2" + +#: src/gui/imwin/imwin_tofrom.py:318 +msgid "{username} disconnected" +msgstr "{username} |)15(0|\\||\\|3(73|)" + +#: src/gui/imwin/imwin_tofrom.py:321 +msgid "now sending from: {username}" +msgstr "|\\|0\\/\\/ 53|\\||)1|\\|' |=|20|\\/|: {username}" + +#: src/gui/imwin/imwin_tofrom.py:342 +msgid "Add..." +msgstr "4|)|)..." + +#: src/gui/imwin/imwin_tofrom.py:584 +msgid "Please enter a valid SMS number (ie: 555-555-5555 or 5555555555)" +msgstr "P|_3453 3|\\|73|2 @ \\/4|_1|) 5|\\/|5 |\\||_||\\/||33|2 (ie: 555-555-5555 or 5555555555)" + +#: src/gui/imwin/imwin_tofrom.py:585 +msgid "Invalid SMS Number" +msgstr "1|\\|\\/4|_1|) 5|\\/|5 |\\||_||\\/||33|2" + +#: src/gui/imwin/imwin_tofrom.py:593 +msgid "That SMS number is already in this buddy's list." +msgstr "|)47 5|\\/|5 |\\||_||\\/||33|2 |3 4|_|234|)'/ 1|\\| |)12 |3|_||)|)'/'2 |_157." + +#: src/gui/imwin/imwin_tofrom.py:594 +msgid "Add SMS Number" +msgstr "4|)|) 5|\\/|5 |\\||_||\\/||33|2" + +#: src/gui/imwin/imwin_tofrom.py:634 +msgid "Accounts..." +msgstr "4((0|_||\\|75..." + +#: src/gui/imwin/imwin_tofrom.py:673 +msgid "Please enter a valid email address (ie: john123@digsby.com)" +msgstr "P|_3453 3|\\|73|2 @ \\/4|_1|) 3|\\/|41|_ 4|)|)|2355 (ie: john123@digsby.com)" + +#: src/gui/imwin/imwin_tofrom.py:674 +msgid "Invalid Email Address" +msgstr "1|\\|\\/4|_1|) 3|\\/|41|_ 4|)|)|2355" + +#: src/gui/imwin/imwin_tofrom.py:677 +msgid "That email is already registered with this buddy." +msgstr "|)47 3|\\/|41|_ |3 4|_|234|)'/ |2361573|23|) \\/\\/17# |)12 |3|_||)|)'/." + +#: src/gui/imwin/imwin_tofrom.py:678 +msgid "Add Email Address" +msgstr "4|)|) 3|\\/|41|_ 4|)|)|2355" + +#: src/gui/imwin/imwin_tofrom.py:729 +msgid "Add Email Account" +msgstr "4|)|) 3|\\/|41|_ 4((0|_||\\|7" + +#: src/gui/imwin/imwinmenu.py:12 +msgid "Buddy Info" +msgstr "|3|_||)|)'/ 1|\\||=0" + +#: src/gui/imwin/imwinmenu.py:13 +msgid "Send IM" +msgstr "53|\\||) 1|\\/|" + +#: src/gui/imwin/imwinmenu.py:16 +msgid "Send Email" +msgstr "53|\\||) 3|\\/|41|_" + +#: src/gui/imwin/imwinmenu.py:17 +msgid "Send SMS" +msgstr "53|\\||) 5|\\/|5" + +#: src/gui/imwin/imwinmenu.py:40 +#: src/gui/pastbrowser.py:414 +#: src/gui/toolbox/toolbox.py:1448 +#: src/gui/uberwidgets/formattedinput.py:95 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:105 +#: src/plugins/twitter/twitter.py:1211 +#: src/plugins/twitter/twitter_gui.py:1496 +msgid "Copy" +msgstr "(0PY" + +#: src/gui/imwin/imwinmenu.py:41 +#: src/gui/toolbox/toolbox.py:1451 +#: src/gui/uberwidgets/formattedinput.py:96 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:106 +msgid "Paste" +msgstr "P4573" + +#: src/gui/imwin/imwinmenu.py:44 +#: src/gui/imwin/imwinmenu.py:123 +msgid "Show &Actions Bar" +msgstr "5#0\\/\\/ &A(710|\\|2 |34|2" + +#: src/gui/imwin/imwinmenu.py:45 +#: src/gui/imwin/imwinmenu.py:124 +msgid "Show &Formatting Bar" +msgstr "5#0\\/\\/ &F0|2|\\/|4771|\\|' |34|2" + +#: src/gui/imwin/imwinmenu.py:46 +msgid "Show Send Button" +msgstr "5#0\\/\\/ 53|\\|d |3|_|770|\\|" + +#: src/gui/imwin/imwinmenu.py:64 +#: src/plugins/twitter/twitter_gui.py:50 +msgid "&Keep on Top" +msgstr "&K33P 0|\\| 70P" + +#: src/gui/imwin/imwinmenu.py:85 +msgid "Copy\tCtrl+C" +msgstr "(0P'/\tCtrl+C" + +#: src/gui/imwin/imwinmenu.py:94 +msgid "Copy &Link" +msgstr "(0P'/ &L1|\\||<" + +#: src/gui/imwin/imwinmenu.py:96 +msgid "Paste\tCtrl+V" +msgstr "P4573\tCtrl+V" + +#: src/gui/imwin/imwinmenu.py:100 +msgid "Edit Source" +msgstr "3|)17 S0|_||2(3" + +#: src/gui/imwin/imwinmenu.py:103 +msgid "&Javascript Console" +msgstr "&J4\\/45(|21P7 (0|\\|50|_3" + +#: src/gui/imwin/imwinmenu.py:108 +#: src/plugins/twitter/twitter_gui.py:1507 +msgid "&Increase Text Size\tCtrl+=" +msgstr "&1|\\|(|23453 73><7 5123" + +#: src/gui/imwin/imwinmenu.py:110 +#: src/plugins/twitter/twitter_gui.py:1508 +msgid "&Decrease Text Size\tCtrl+-" +msgstr "&D3(|23453 73><7 5123\tCtrl+-" + +#: src/gui/imwin/imwinmenu.py:113 +#: src/plugins/twitter/twitter_gui.py:1510 +msgid "&Reset Text Size\tCtrl+0" +msgstr "&R3537 73><7 5123\tCtrl+0" + +#: src/gui/imwin/imwinmenu.py:116 +msgid "&Text Size" +msgstr "&73><7 5123" + +#: src/gui/imwin/imwinmenu.py:122 +msgid "Show &Room List" +msgstr "5#0\\/\\/ &R00|\\/| |_157" + +#: src/gui/imwin/roomlist.py:405 +msgid "Invite Buddy" +msgstr "1|\\|\\/173 B|_||)|)'/" + +#: src/gui/imwin/roomlist.py:524 +msgid "Do you want to invite {name} to this chat?" +msgstr "|)0 J00 \\/\\/4|\\|7 2 1|\\|\\/173 {name} 2 |)12 (#47?" + +#: src/gui/imwin/roomlist.py:525 +msgid "Chat Invite" +msgstr "(#47 1|\\|\\/173" + +#: src/gui/imwin/styles/adiummsgstyles.py:193 +#: src/gui/skin/skintree.py:427 +#: src/msn/MSNBuddy.py:563 +msgid "(none)" +msgstr "(|\\|0|\\|3)" + +#: src/gui/infobox/emailpanels.py:596 +msgid "(No Subject)" +msgstr "(|\\|0 5|_||3J3(7)" + +#: src/gui/infobox/emailpanels.py:604 +msgid "(No Preview)" +msgstr "(|\\|0 P|23\\/13\\/\\/)" + +#: src/gui/infobox/htmlgeneration.py:137 +msgid "No Profile" +msgstr "|\\|0 P|20|=1|_3" + +#: src/gui/infobox/htmlgeneration.py:235 +msgid "Subscription:" +msgstr "5|_||35(|21P710|\\|:" + +#: src/gui/infobox/htmlgeneration.py:238 +#: src/gui/infobox/htmlgeneration.py:395 +#: src/plugins/facebook/res/status.py.xml:3 +msgid "Status:" +msgstr "5747|_|5:" + +#: src/gui/infobox/htmlgeneration.py:339 +#: src/gui/pref/pg_text_conversations.py:191 +#: src/yahoo/yahoobuddy.py:154 +msgid "Location:" +msgstr "|_0(4710|\\|:" + +#: src/gui/infobox/htmlgeneration.py:344 +msgid "IP Address:" +msgstr "1P 4||)|)|255:" + +#: src/gui/infobox/htmlgeneration.py:351 +msgid "Time on Page:" +msgstr "71|\\/|3 0|\\| P463:" + +#: src/gui/infobox/htmlgeneration.py:374 +msgid "Online:" +msgstr "0|\\||_1|\\|3:" + +#: src/gui/infobox/htmlgeneration.py:381 +msgid "Idle:" +msgstr "1|)|_3:" + +#: src/gui/infobox/htmlgeneration.py:387 +msgid "Away:" +msgstr "4\\/\\/4'/:" + +#: src/gui/infobox/htmlgeneration.py:396 +msgid "{status} + Idle" +msgstr "{status} + 1|)|_3" + +#: src/gui/infobox/htmlgeneration.py:416 +#: src/plugins/msim/res/content.tenjin:77 +msgid "Hide Profile" +msgstr "|-|1|)3 P|20|=1|_3" + +#: src/gui/infobox/htmlgeneration.py:416 +#: src/plugins/msim/res/content.tenjin:79 +msgid "Show Profile" +msgstr "5|-|0\\/\\/ P|20|=1|_3" + +#: src/gui/infobox/infobox.py:1062 +#, python-format +msgid "Unblock %s" +msgstr "|_||\\||3|_0(|< %s" + +#: src/gui/infobox/infobox.py:1064 +#, python-format +msgid "Block %s" +msgstr "|3|_0(|< %s" + +#: src/gui/infobox/infobox.py:1365 +msgid "Error generating content" +msgstr "3|2|20|2 63|\\|3|2473 (0|\\|73|\\|7" + +#: src/gui/infobox/infobox.py:1564 +msgid "No additional information" +msgstr "\\/13\\/\\/ |3|_||)|)'/ 1|\\||=0|2|\\/|4710|\\|" + +#: src/gui/infobox/infobox.py:1792 +msgid "InfoBox" +msgstr "1|\\||=0|30><" + +#: src/gui/input/inputmanager.py:391 +msgid "Key Debugger" +msgstr "|<3'/ |)3|3|_|663|2" + +#: src/gui/native/win/jumplist.py:51 +msgid "Chat with {name}" +msgstr "(#47 \\/\\/17# {name}" + +#: src/gui/native/win/jumplist.py:124 +msgid "Change IM Status to {status}" +msgstr "(#4|\\|63 1|\\/| 5747|_|2 2 {status}" + +#: src/gui/native/win/jumplist.py:139 +#: src/gui/social_status_dialog.py:830 +msgid "Set Global Status" +msgstr "537 6|_0|34|_ 5747|_|2" + +#: src/gui/native/win/jumplist.py:139 +msgid "Set your status on multiple networks" +msgstr "537 '/0 5747|_|2 0|\\| |\\/||_||_71P|_3 |\\|37\\/\\/0|2|<2" + +#: src/gui/native/win/jumplist.py:140 +msgid "New IM..." +msgstr "|\\|3\\/\\/ 1|\\/|..." + +#: src/gui/native/win/jumplist.py:140 +msgid "Open the New IM window" +msgstr "0P3|\\| 73# |\\|3\\/\\/ 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/native/win/jumplist.py:141 +msgid "Open the Digsby Preferences window" +msgstr "0P3|\\| 73# |)165|3'/ P|23|=3|23|\\|(32 \\/\\/1|\\||)0\\/\\/" + +#: src/gui/native/win/jumplist.py:141 +msgid "Preferences..." +msgstr "P|23|=3|23|\\|(32..." + +#: src/gui/native/win/jumplist.py:150 +msgid "Close all windows and exit" +msgstr "(|_053 4|_|_ \\/\\/1|\\||)0\\/\\/2 |\\| ><17" + +#: src/gui/native/win/jumplist.py:150 +msgid "Exit Digsby" +msgstr "><17 |)165|3'/" + +#: src/gui/native/win/jumplist.py:164 +msgid "Enable Chat Logs" +msgstr "3|\\|4|3|_3 C#47 |_062" + +#: src/gui/native/win/jumplist.py:164 +msgid "Opens the Preferences Window where you can enable logging" +msgstr "0P3|\\|" + +#: src/gui/notifications/notificationlist.py:68 +msgid "Sound" +msgstr "50|_||\\||)" + +#: src/gui/notifications/notificationlist.py:69 +msgid "Popup" +msgstr "P0P|_|P" + +#: src/gui/pastbrowser.py:89 +msgid "Group Chats" +msgstr "6|20|_|P (#472" + +#: src/gui/pastbrowser.py:301 +msgid "Find" +msgstr "|=1|\\||)" + +#: src/gui/pastbrowser.py:304 +msgid "Next" +msgstr "|\\|><7" + +#: src/gui/pastbrowser.py:305 +msgid "Prev" +msgstr "P|23\\/" + +#: src/gui/pastbrowser.py:386 +#: src/gui/status.py:374 +msgid "Account" +msgstr "4((0|_||\\|7" + +#: src/gui/pastbrowser.py:392 +#: src/gui/pastbrowser.py:470 +msgid "Buddy" +msgstr "|3|_||)|)'/" + +#: src/gui/pastbrowser.py:402 +msgid "Date" +msgstr "|)473" + +#: src/gui/pastbrowser.py:449 +msgid "Conversation Log" +msgstr "(0|\\|\\/3|254710|\\| |_06" + +#: src/gui/pastbrowser.py:466 +msgid "Chats" +msgstr "(#472" + +#: src/gui/pastbrowser.py:558 +msgid "{month}, {day}, {year}" +msgstr "{month}, {day}, {year}" + +#: src/gui/pastbrowser.py:561 +msgid "There are no chat logs for {specific_day_string}." +msgstr "7|-|3|23 |\\|0 (|-|47 |_062 4 {specific_day_string}." + +#: src/gui/pastbrowser.py:563 +msgid "There are no chat logs for {specific_day_string} with {name}." +msgstr "7|-|3|23 |\\|0 (|-|47 |_062 4 {specific_day_string} \\/\\/17|-| {name}." + +#: src/gui/pastbrowser.py:664 +msgid "There is no chat history for {name} on {service} with {acct}." +msgstr "7#3|23 |3 |\\|0 (#47 #1570|2'/ 4 {name} 0|\\| {service} \\/\\/17# {acct}." + +#: src/gui/pastbrowser.py:673 +msgid "Past Chat Browser" +msgstr "P457 (#47 |3|20\\/\\/53|2" + +#: src/gui/pref/iconeditor.py:34 +msgid "Scree&n" +msgstr "5(|233&N" + +#: src/gui/pref/iconeditor.py:35 +msgid "Capt&ure" +msgstr "(4P7|_||23" + +#: src/gui/pref/iconeditor.py:48 +msgid "Set Buddy Icon" +msgstr "537 |3|_||)|)'/ 1(0|\\|" + +#: src/gui/pref/iconeditor.py:430 +msgid "Choose an icon" +msgstr "(#0053 |\\| 1(0|\\|" + +#: src/gui/pref/iconeditor.py:439 +msgid "Not a valid image:" +msgstr "|\\|07 \\/4|_1|) 1|\\/|463:" + +#: src/gui/pref/iconeditor.py:442 +msgid "Invalid Image" +msgstr "1|\\|\\/4|_1|) 1|\\/|463" + +#: src/gui/pref/iconeditor.py:547 +msgid "Cli&pboard" +msgstr "(|_1&P|304|2|)" + +#: src/gui/pref/iconeditor.py:553 +#: src/gui/protocols/jabbergui.py:289 +msgid "C&lear" +msgstr "(&L34|2" + +#: src/gui/pref/pg_accounts.py:617 +msgid "(auto login)" +msgstr "(4|_|70 |_061|\\|)" + +#: src/gui/pref/pg_accounts.py:828 +msgid "My Accounts" +msgstr "|\\/|'/ 4((0|_||\\|72..." + +#: src/gui/pref/pg_accounts.py:832 +msgid "Account Options" +msgstr "4((0|_||\\|7 0P710|\\|2" + +#: src/gui/pref/pg_accounts.py:863 +msgid "buddy list" +msgstr "|3|_||)|)'/ |_157" + +#: src/gui/pref/pg_accounts.py:864 +msgid "icon tray" +msgstr "1(0|\\| 7|24'/" + +#: src/gui/pref/pg_accounts.py:865 +msgid "buddy list and icon tray" +msgstr "|3|_||)|)'/ |_157 |\\| 1(0|\\| 7|24'/" + +#: src/gui/pref/pg_accounts.py:901 +msgid "Show email accounts in:" +msgstr "5#0\\/\\/ 3|\\/|41|_ 4((0|_||\\|72 1|\\|:" + +#: src/gui/pref/pg_accounts.py:902 +msgid "Show social networks in:" +msgstr "5#0\\/\\/ 50(14|_ |\\|37\\/\\/0|2|<2 1|\\|:" + +#: src/gui/pref/pg_advanced.py:14 +msgid "IM Window" +msgstr "1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_advanced.py:60 +#: src/gui/pref/pg_advanced.py:94 +msgid "&Use Proxy Server" +msgstr "&|_|53 P|20><'/ 53|2\\/3|2" + +#: src/gui/pref/pg_advanced.py:68 +msgid "Host" +msgstr "#057" + +#: src/gui/pref/pg_advanced.py:69 +msgid "Port" +msgstr "P0|27" + +#: src/gui/pref/pg_advanced.py:70 +#: src/plugins/digsby_email/info.yaml:18 +#: src/plugins/digsby_email/info.yaml:76 +#: src/plugins/digsby_service_editor/default_ui.py:149 +#: src/plugins/digsby_service_editor/default_ui.py:154 +#: src/plugins/msim/res/content.tenjin:19 +msgid "Username" +msgstr "|_|53|2|\\|4|\\/|3" + +#: src/gui/pref/pg_advanced.py:71 +#: src/plugins/component_gtalk/info.yaml:60 +#: src/plugins/digsby_email/info.yaml:19 +#: src/plugins/digsby_email/info.yaml:77 +#: src/plugins/digsby_service_editor/default_ui.py:166 +#: src/plugins/digsby_service_editor/default_ui.py:173 +#: src/plugins/provider_aol/info.yaml:14 +#: src/plugins/provider_aol/info.yaml:110 +#: src/plugins/provider_google/info.yaml:11 +#: src/plugins/provider_jabber/info.yaml:16 +#: src/plugins/provider_windows_live/info.yaml:23 +#: src/plugins/provider_yahoo/info.yaml:11 +msgid "Password" +msgstr "P455\\/\\/0|2|)" + +#: src/gui/pref/pg_advanced.py:94 +msgid "&Use Global Settings" +msgstr "&|_|53 6|_0|34|_ 53771|\\|62" + +#: src/gui/pref/pg_advanced.py:130 +msgid "&Protocol:" +msgstr "&P|2070(0|_:" + +#: src/gui/pref/pg_appearance.py:39 +msgid "Application Skin" +msgstr "4PP|_1(4710|\\| 5|<1|\\|" + +#: src/gui/pref/pg_appearance.py:41 +msgid "Conversation Theme" +msgstr "(0|\\|\\/3|254710|\\| 7#3|\\/|3" + +#: src/gui/pref/pg_appearance.py:44 +msgid "Conversation Preview" +msgstr "(0|\\|\\/3|254710|\\| P|23\\/13\\/\\/" + +#: src/gui/pref/pg_appearance.py:115 +msgid "Skin:" +msgstr "5|<1|\\|:" + +#: src/gui/pref/pg_appearance.py:115 +#: src/gui/pref/pg_appearance.py:203 +msgid "Variant:" +msgstr "\\/4|214|\\|7:" + +#: src/gui/pref/pg_appearance.py:187 +msgid "Show header" +msgstr "5#0\\/\\/ #34|)3|2" + +#: src/gui/pref/pg_appearance.py:192 +msgid "Show message fonts" +msgstr "5#0\\/\\/ |\\/|355463 |=0|\\|72" + +#: src/gui/pref/pg_appearance.py:194 +msgid "Show message colors" +msgstr "5#0\\/\\/ |\\/|355463 (0|_0|22" + +#: src/gui/pref/pg_appearance.py:201 +msgid "Theme:" +msgstr "7#3|\\/|3:" + +#: src/gui/pref/pg_appearance.py:270 +msgid "Fonts:" +msgstr "|=0|\\|72:" + +#: src/gui/pref/pg_appearance.py:275 +msgid "Colors:" +msgstr "(0|_0|22:" + +#: src/gui/pref/pg_contact_list.py:27 +msgid "Sorting and Groups" +msgstr "50|271|\\|' |\\| 6|20|_|P2" + +#: src/gui/pref/pg_contact_list.py:46 +msgid "Autohide when not in &focus" +msgstr "4|_|70#1|)3 \\/\\/#3|\\| |\\|07 1|\\| &F0(|_|5" + +#: src/gui/pref/pg_contact_list.py:51 +msgid "Automatically &dock when near edge of screen" +msgstr "4|_|70|\\/|471(4|_|_'/ &D0(|< \\/\\/#3|\\| |\\|34|2 3|)63 0|= 5(|233|\\|" + +#: src/gui/pref/pg_contact_list.py:57 +#: src/gui/pref/pg_text_conversations.py:42 +msgid "&Keep on top of other applications" +msgstr "&K33P 0|\\| 70P 0|= 07#3|2 4PP|_1(4710|\\|2" + +#: src/gui/pref/pg_contact_list.py:58 +msgid "Show in taskbar" +msgstr "5#0\\/\\/ 1|\\| 745|<|34|2" + +#: src/gui/pref/pg_contact_list.py:64 +#: src/gui/pref/pg_text_conversations.py:61 +msgid "Window Options" +msgstr "\\/\\/1|\\||)0\\/\\/ 0P710|\\|2" + +#: src/gui/pref/pg_contact_list.py:81 +msgid "Contact Layout" +msgstr "(0|\\|74(7 |_4'/0|_|7" + +#: src/gui/pref/pg_contact_list.py:104 +#: src/gui/pref/pg_contact_list.py:110 +#: src/plugins/digsby_service_editor/service_editor.py:58 +msgid "Advanced" +msgstr "4|)\\/4|\\|&(3|)..." + +#: src/gui/pref/pg_contact_list.py:104 +#: src/gui/pref/pg_contact_list.py:110 +msgid "Basic" +msgstr "|3451(" + +#: src/gui/pref/pg_contact_list.py:243 +msgid "Group by:" +msgstr "6|20|_|P |3'/:" + +#: src/gui/pref/pg_contact_list.py:244 +msgid "Sort by:" +msgstr "50|27 |3'/:" + +#: src/gui/pref/pg_contact_list.py:245 +msgid "Then by:" +msgstr "7#3|\\| |3'/:" + +#: src/gui/pref/pg_contact_list.py:353 +#: src/gui/pref/pg_sandbox.py:79 +msgid "Far Left" +msgstr "|=4|2 |_3|=7" + +#: src/gui/pref/pg_contact_list.py:354 +#: src/gui/pref/pg_sandbox.py:80 +msgid "Left" +msgstr "|_3|=7" + +#: src/gui/pref/pg_contact_list.py:355 +#: src/gui/pref/pg_sandbox.py:81 +msgid "Badge (Lower Left)" +msgstr "|34|)63 (|_0\\/\\/3|2 |_3|=7)" + +#: src/gui/pref/pg_contact_list.py:356 +#: src/gui/pref/pg_sandbox.py:82 +msgid "Badge (Lower Right)" +msgstr "|34|)63 (|_0\\/\\/3|2 |216#7)" + +#: src/gui/pref/pg_contact_list.py:357 +#: src/gui/pref/pg_sandbox.py:83 +msgid "Right" +msgstr "|216#7" + +#: src/gui/pref/pg_contact_list.py:358 +#: src/gui/pref/pg_sandbox.py:84 +msgid "Far Right" +msgstr "|=4|2 |216#7" + +#: src/gui/pref/pg_contact_list.py:377 +msgid "Show service icon on:" +msgstr "5#0\\/\\/ 53|2\\/1(3 1(0|\\| 0|\\|:" + +#: src/gui/pref/pg_contact_list.py:379 +msgid "Show status icon on:" +msgstr "5#0\\/\\/ 5747|_|2 1(0|\\| 0|\\|:" + +#: src/gui/pref/pg_contact_list.py:381 +msgid "Show buddy icon on the:" +msgstr "5#0\\/\\/ |3|_||)|)'/ 1(0|\\| 0|\\| 73#:" + +#: src/gui/pref/pg_contact_list.py:390 +msgid "Buddy icon size:" +msgstr "|3|_||)|)'/ 1(0|\\| 5123:" + +#: src/gui/pref/pg_contact_list.py:396 +msgid "Buddy padding:" +msgstr "|3|_||)|)'/ P4|)|)1|\\|6:" + +#: src/gui/pref/pg_contact_list.py:401 +msgid "Contact name:" +msgstr "(0|\\|74(7 |\\|4|\\/|3:" + +#: src/gui/pref/pg_contact_list.py:403 +msgid "&Show extra info:" +msgstr "&5#0\\/\\/ ><7|24 1|\\||=0:" + +#: src/gui/pref/pg_contact_list.py:406 +msgid "Idle Time" +msgstr "1|)|_3 71|\\/|3" + +#: src/gui/pref/pg_contact_list.py:407 +msgid "Idle Time + Status" +msgstr "1|)|_3 71|\\/|3 + 5747|_|2" + +#: src/gui/pref/pg_files.py:13 +msgid "Save &files to:" +msgstr "54\\/3 &F1|_32 2:" + +#: src/gui/pref/pg_files.py:15 +msgid "Create s&ubfolders for each IM account" +msgstr "(|23473 5&U|3|=0|_|)3|22 4 34(# 1|\\/| 4((0|_||\\|7" + +#: src/gui/pref/pg_files.py:16 +msgid "&Auto-accept all file transfers from contacts on my contact list" +msgstr "&4|_|70-4((3P7 4|_|_ |=1|_3 7|24|\\|5|=3|22 |=|20|\\/| (0|\\|74(72 0|\\| |\\/|'/ (0|\\|74(7 |_157" + +#: src/gui/pref/pg_general_profile.py:108 +msgid "&Launch Digsby when this computer starts" +msgstr "&L4|_||\\|c# |)165|3'/ \\/\\/#3|\\| |)12 (0|\\/|P|_|73|2 574|272" + +#: src/gui/pref/pg_general_profile.py:125 +msgid "&Automatically download software updates" +msgstr "&4|_|70|\\/|471(4|_|_'/ |)0\\/\\/|\\||_04|) \\/\\/4|232 |_|P|)4732" + +#: src/gui/pref/pg_general_profile.py:126 +msgid "&If connection to IM service is lost, automatically attempt to reconnect" +msgstr "&1|= (0|\\||\\|3(710|\\| 2 1|\\/| 53|2\\/1(3 |3 |_057, 4|_|70|\\/|471(4|_|_'/ 4773|\\/|P7 2 |23(0|\\||\\|3(7" + +#: src/gui/pref/pg_general_profile.py:127 +msgid "Show trending news articles in social network feeds (powered by OneRiot)" +msgstr "5#0\\/\\/ 7|23|\\||)1|\\|' |\\|3\\/\\/2 4|271(|_32 1|\\| 50(14|_ |\\|37\\/\\/0|2|< |=33|)2 (P0\\/\\/3|23|) |3'/ 0|\\|3|2107)" + +#: src/gui/pref/pg_general_profile.py:131 +msgid "General Options" +msgstr "63|\\|3|24|_ 0P710|\\|2" + +#: src/gui/pref/pg_general_profile.py:143 +msgid "Profile (AIM Only)" +msgstr "P|20|=1|_3 (41|\\/| 0|\\||_'/)" + +#: src/gui/pref/pg_general_profile.py:147 +msgid "Buddy Icon" +msgstr "|3|_||)|)'/ 1(0|\\|" + +#: src/gui/pref/pg_general_profile.py:163 +msgid "Language" +msgstr "|_4|\\|6|_|463" + +#: src/gui/pref/pg_general_profile.py:216 +msgid "Change" +msgstr "(#4|\\|63" + +#: src/gui/pref/pg_general_profile.py:252 +msgid "&Promote Digsby in my AIM profile" +msgstr "&P|20|\\/|073 |)165|3'/ 1|\\| |\\/|'/ 41|\\/| P|20|=1|_3" + +#: src/gui/pref/pg_helpdigsby.py:11 +msgid "&Allow Digsby to conduct research during idle itme" +msgstr "&4|_|_0\\/\\/ |)165|3'/ 2 (0|\\||)|_|(7 |23534|2(# |)|_||21|\\|' 1|)|_3 17|\\/|3" + +#: src/gui/pref/pg_helpdigsby.py:15 +msgid "Help Digsby" +msgstr "#3|_P |)165|3'/" + +#: src/gui/pref/pg_notifications.py:23 +msgid "bottom right corner" +msgstr "|30770|\\/| |216#7 (0|2|\\|3|2" + +#: src/gui/pref/pg_notifications.py:24 +msgid "bottom left corner" +msgstr "|30770|\\/| |_3|=7 (0|2|\\|3|2" + +#: src/gui/pref/pg_notifications.py:25 +msgid "top right corner" +msgstr "70P |216#7 (0|2|\\|3|2" + +#: src/gui/pref/pg_notifications.py:26 +msgid "top left corner" +msgstr "70P |_3|=7 (0|2|\\|3|2" + +#: src/gui/pref/pg_notifications.py:38 +msgid "Enable &pop-up notifications in the {location_dropdown} on monitor {monitor_dropdown}" +msgstr "3|\\|4|3|_3 &P0P-|_|P |\\|071|=1(4710|\\|2 |\\| 73|-| {location_dropdown} 0|\\| |\\//|0|\\|170|2 {monitor_dropdown}" + +#: src/gui/pref/pg_notifications.py:69 +msgid "Enable &sounds" +msgstr "3|\\|4|3|_3 &50|_||\\||)2" + +#: src/gui/pref/pg_notifications.py:70 +#: src/gui/pref/prefstrings.py:22 +#: src/plugins/facebook/fbgui.py:49 +msgid "Notifications" +msgstr "|\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_notifications.py:77 +#: src/plugins/myspace/msgui.py:49 +msgid "Events" +msgstr "3\\/3|\\|7" + +#: src/gui/pref/pg_notifications.py:79 +msgid "Restore Defaults" +msgstr "|23570|23 |)3|=4|_||_72" + +#: src/gui/pref/pg_notifications.py:93 +msgid "" +"Are you sure you want to restore the default notification set?\n" +"\n" +"All of your notification settings will be lost." +msgstr "" +"12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |23570|23 73# |)3|=4|_||_7 |\\|071|=1(4710|\\| 537?\n" +"\n" +"4|_|_ 0|= '/0 |\\|071|=1(4710|\\| 53771|\\|62 \\/\\/1|_|_ |3 |_057." + +#: src/gui/pref/pg_notifications.py:96 +msgid "Restore Default Notifications" +msgstr "|23570|23 |)3|=4|_||_7 |\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_privacy.py:17 +msgid "&Let others know that I am typing" +msgstr "&L37 07#3|22 |<|\\|0\\/\\/ |)47 1 |3 7YP1|\\|6" + +#: src/gui/pref/pg_privacy.py:18 +msgid "&Automatically sign me into websites (e.g., Yahoo! Mail)" +msgstr "&4|_|70|\\/|471(4|_|_'/ 516|\\| |\\/|3 1|\\|70 \\/\\/3|351732 (3.6., '/4#00! |\\/|41|_)" + +#: src/gui/pref/pg_privacy.py:21 +msgid "Global Privacy Options" +msgstr "6|_0|34|_ P|21\\/4('/ 0P710|\\|2" + +#: src/gui/pref/pg_privacy.py:144 +msgid "You must be signed in to modify privacy settings." +msgstr "J00 |\\/||_|57 |3 516|\\|3|) 1|\\| 2 |\\/|0|)1|='/ P|21\\/4('/ 53771|\\|62." + +#: src/gui/pref/pg_privacy.py:145 +#, python-format +msgid "Sign in with \"%s\"" +msgstr "516|\\| 1|\\| \\/\\/17# \"%s\"" + +#: src/gui/pref/pg_privacy.py:234 +msgid "{title} ({username})" +msgstr "{title} ({username})" + +#: src/gui/pref/pg_privacy.py:295 +#: src/gui/pref/pg_privacy.py:518 +msgid "Allow all users to contact me" +msgstr "4|_|_0\\/\\/ 4|_|_ |_|53|22 2 (0|\\|74(7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:296 +#: src/gui/pref/pg_privacy.py:519 +#: src/gui/pref/pg_privacy.py:884 +msgid "Allow only users on my contact list" +msgstr "4|_|_0\\/\\/ 0|\\||_'/ |_|53|22 0|\\| |\\/|'/ (0|\\|74(7 |_157" + +#: src/gui/pref/pg_privacy.py:297 +#: src/gui/pref/pg_privacy.py:675 +msgid "Allow only users on 'Allow List'" +msgstr "4|_|_0\\/\\/ 0|\\||_'/ |_|53r2 0|\\| '4|_|_0\\/\\/ |_157'" + +#: src/gui/pref/pg_privacy.py:298 +msgid "Block all users" +msgstr "|3|_0(|< 4|_|_ |_|53|22" + +#: src/gui/pref/pg_privacy.py:299 +#: src/gui/pref/pg_privacy.py:676 +#: src/gui/pref/pg_privacy.py:885 +msgid "Block only users on 'Block List'" +msgstr "|3|_0(|< 0|\\||_'/ |_|53|22 0|\\| '|3|_0(|< |_157" + +#: src/gui/pref/pg_privacy.py:301 +msgid "Only my screen name" +msgstr "0|\\||_'/ |\\/|'/ 5(|233|\\| |\\|4|\\/|3" + +#: src/gui/pref/pg_privacy.py:302 +msgid "Only that I have an account" +msgstr "0|\\||_'/ |)47 1 #4\\/3 |\\| 4((0|_||\\|7" + +#: src/gui/pref/pg_privacy.py:303 +msgid "Nothing about me" +msgstr "|\\|07#1|\\|' 4|30|_|7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:330 +#: src/gui/pref/pg_privacy.py:556 +#: src/gui/pref/pg_privacy.py:727 +#: src/gui/pref/pg_privacy.py:900 +#: src/gui/pref/pg_privacy.py:948 +msgid "Permissions" +msgstr "P3|2|\\/|15510|\\|2" + +#: src/gui/pref/pg_privacy.py:337 +msgid "Allow users who know my email address to find:" +msgstr "4|_|_0\\/\\/ |_|53|25 |)47 |\\|0 |\\/|'/ 3|\\/|41|_ 4|)|)|2355 2 |=1|\\||):" + +#: src/gui/pref/pg_privacy.py:411 +#: src/gui/pref/pg_privacy.py:470 +#: src/gui/pref/pg_privacy.py:542 +#: src/gui/pref/pg_privacy.py:643 +#: src/gui/pref/pg_privacy.py:719 +#: src/gui/pref/pg_privacy.py:840 +msgid "Block List" +msgstr "|3|_0(|< |_157" + +#: src/gui/pref/pg_privacy.py:494 +#: src/gui/pref/pg_privacy.py:542 +msgid "Visible List" +msgstr "\\/151|3|_3 |_157" + +#: src/gui/pref/pg_privacy.py:498 +#: src/gui/pref/pg_privacy.py:542 +msgid "Invisible List" +msgstr "1|\\|\\/151|3|_3 |_157" + +#: src/gui/pref/pg_privacy.py:530 +#: src/gui/pref/pg_privacy.py:934 +msgid "Allow only users on my buddy list to contact me" +msgstr "4|_|_0\\/\\/ 0|\\||_'/ |_|53|22 0|\\| |\\/|'/ |3|_||)|)'/ |_157 2 (0|\\|74(7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:531 +msgid "Require authorization before users can add me to their contact list" +msgstr "|23Q|_|1|23 4|_|7#0|2124710|\\| |33|=0|23 |_|532 (4|\\| 4|)|) |\\/|3 2 7#31|2 (0|\\|74(7 |_157" + +#: src/gui/pref/pg_privacy.py:532 +msgid "Block authorization requests with URLs in them" +msgstr "|3|_0(|< 4|_|7#0|2124710|\\| |23Q|_|3572 \\/\\/17# |_||2|_2 1|\\| 7#3|\\/|" + +#: src/gui/pref/pg_privacy.py:533 +msgid "Allow others to view my online status from the web" +msgstr "4|_|_0\\/\\/ 07#3|22 2 \\/13\\/\\/ |\\/|'/ 0|\\||_1|\\|3 5747|_|2 |=|20|\\/| 73# \\/\\/3|3" + +#: src/gui/pref/pg_privacy.py:658 +#: src/gui/pref/pg_privacy.py:717 +msgid "Allow List" +msgstr "4|_|_0\\/\\/ |_157" + +#: src/gui/pref/pg_privacy.py:674 +#: src/gui/pref/pg_privacy.py:710 +msgid "Allow unknown users to contact me" +msgstr "4|_|_0\\/\\/ |_||\\||<|\\|0\\/\\/|\\| |_|53|22 2 (0|\\|74(7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:862 +msgid "Could not block {item:s}" +msgstr "(0|_||_|) |\\|07 |3|_0(|< {item:s}" + +#: src/gui/pref/pg_privacy.py:870 +msgid "{item:s} is on your buddylist. Do you want to remove {item:s} from your buddylist and block the user?" +msgstr "{item:s} |=|231|\\||) |-|45. |)0 |\\|07 \\/\\/4|\\|7 && |3|_0(|< {item:s}" + +#: src/gui/pref/pg_privacy.py:872 +msgid "Confirm Block and Remove" +msgstr "(0|\\||=1|2|\\/| |3|_0(|< |\\| |23|\\/|0\\/3" + +#: src/gui/pref/pg_privacy.py:939 +msgid "Hide operating system from other users" +msgstr "|-|1|)3 05 |=|20|\\/| 07|-|3|2 |_|53|25" + +#: src/gui/pref/pg_research.py:12 +msgid "" +"Help Digsby stay free for all users. Allow Digsby to use part of your computer's idle processing power to contribute to commercial grid computing projects by enabling the Research Module.\n" +"

\n" +"This module turns on after your computer has been completely idle (no mouse or keyboard movement) for a period of time. It turns off the instant you move your mouse or press a key, so it has no effect on your PC's performance when you're using it. The research module also runs as a low priority, sandboxed Java process, so that other tasks your computer is doing will get done first, and so that it is completely secure.\n" +"

\n" +"For more details, see the Research Module FAQ.\n" +msgstr "TL;DT Research Module FAQ\n" + +#: src/gui/pref/pg_research.py:98 +#: src/gui/pref/prefstrings.py:25 +msgid "Research Module" +msgstr "|23534|2(# |\\/|0|)|_||_3" + +#: src/gui/pref/pg_research.py:105 +msgid "Allow Digsby to use CPU time to conduct research after %2(research.idle_time_min)d minutes of idle time" +msgstr "4|_|_0\\/\\/ |)165|3'/ 2 |_|53 (P|_| 71|\\/|3 2 (0|\\||)|_|(7 |23534|2(# 4|=73|2 %2(research.idle_time_min)d |\\/|1|\\||_|732 0|= 1|)|_3 71|\\/|3" + +#: src/gui/pref/pg_research.py:111 +msgid "Maximum CPU Usage:" +msgstr "|\\/|4><1|\\/||_||\\/| (P|_| |_|5463:" + +#: src/gui/pref/pg_research.py:116 +#: src/gui/pref/pg_research.py:123 +msgid "{val}%" +msgstr "{val}%" + +#: src/gui/pref/pg_research.py:118 +msgid "Maximum Bandwidth Usage:" +msgstr "|\\/|4><1|\\/||_||\\/| |34|\\||)\\/\\/1|)7# |_|5463:" + +#: src/gui/pref/pg_status.py:19 +msgid "Status Options" +msgstr "5747|_|2 0P710|\\|2" + +#: src/gui/pref/pg_status.py:21 +msgid "Promote Digsby in my IM status messages" +msgstr "P|20|\\/|073 |)165|3'/ 1|\\| |\\/|'/ 1|\\/| 5747|_|2 |\\/|3554632" + +#: src/gui/pref/pg_status.py:24 +msgid "Help Digsby by linking album when sharing \"Listening to...\" as status" +msgstr "|-|3|_P |)165|3'/ |3'/ |_1|\\||<1|\\|6 4|_|3|_||\\/| \\/\\/|-|3|\\| 5|-|4|21|\\|6 \"|_1573|\\|6 2\" 45 5747|_|5" + +#: src/gui/pref/pg_status.py:26 +msgid "Let others know that I am idle after %2(messaging.idle_after)d minutes of inactivity" +msgstr "|_37 07#3|22 |<|\\|0\\/\\/ |)47 1 |3 1|)|_3 4|=73|2 %2(messaging.idle_after)d |\\/|1|\\||_|732 0|= 1|\\|4(71\\/17'/" + +#: src/gui/pref/pg_status.py:33 +msgid "Autorespond with status message" +msgstr "4|_|70|235P0|\\||) \\/\\/17# 5747|_|2 |\\/|355463" + +#: src/gui/pref/pg_status.py:34 +msgid "Disable sounds" +msgstr "|)154|3|_3 50|_||\\||)2" + +#: src/gui/pref/pg_status.py:35 +msgid "Disable pop-up notifications" +msgstr "|)154B|_3 P0P-|_|P |\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_status.py:38 +msgid "When away..." +msgstr "\\/\\/#3|\\| 4\\/\\/4'/..." + +#: src/gui/pref/pg_status.py:45 +msgid "&Hide new conversation windows" +msgstr "&H1|)3 |\\|3\\/\\/ (0|\\|\\/3|254710|\\| \\/\\/1|\\||)0\\/\\/2" + +#: src/gui/pref/pg_status.py:46 +msgid "&Disable sounds" +msgstr "&D154|3|_3 50|_||\\||)2" + +#: src/gui/pref/pg_status.py:47 +msgid "Disable &pop-up notifications" +msgstr "|)154B|_3 &P0P-|_|P |\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_status.py:50 +msgid "When running full screen applications..." +msgstr "\\/\\/#3|\\| |2|_||\\||\\|1|\\|' |=|_||_|_ 5(|233|\\| 4PP|_1(4710|\\|2..." + +#: src/gui/pref/pg_status.py:60 +#: src/gui/status.py:607 +msgid "New Status Message" +msgstr "|\\|3\\/\\/ 5747|_|2 |\\/|355463" + +#: src/gui/pref/pg_status.py:60 +msgid "Status Messages" +msgstr "5747|_|2 |\\/|3554632" + +#: src/gui/pref/pg_supportdigsby.py:12 +#: src/gui/supportdigsby/supportdialog.py:120 +msgid "Support Digsby" +msgstr "5|_|PP0|27 |)165|3'/" + +#: src/gui/pref/pg_text_conversations.py:21 +msgid "automatically take focus" +msgstr "4|_|70|\\/|461(4|_|_'/ 74|<3 |=0(|_|5" + +#: src/gui/pref/pg_text_conversations.py:22 +msgid "start minimized in taskbar" +msgstr "574|27 |\\/|1|\\|1|\\/|123|) 1|\\| 745|<|34|2" + +#: src/gui/pref/pg_text_conversations.py:23 +msgid "start hidden (tray icon blinks)" +msgstr "574|27 #1|)|)3|\\| (7|24'/ 1(0|\\| |3|_1|\\||<2)" + +#: src/gui/pref/pg_text_conversations.py:27 +msgid "bottom" +msgstr "|30770|\\/|" + +#: src/gui/pref/pg_text_conversations.py:28 +msgid "top" +msgstr "2" + +#: src/gui/pref/pg_text_conversations.py:29 +msgid "left" +msgstr "|_3|=7" + +#: src/gui/pref/pg_text_conversations.py:30 +msgid "right" +msgstr "|216#7" + +#: src/gui/pref/pg_text_conversations.py:46 +msgid "Group multiple conversations into one tabbed window" +msgstr "6|20|_|P |\\/||_||_71P|_3 (0|\\|\\/3|254710|\\|2 1|\\|70 0|\\|3 74|3|33|) \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_text_conversations.py:52 +msgid "New conversation windows: " +msgstr "|\\|3\\/\\/ (0|\\|\\/3|254710|\\| \\/\\/1|\\||)0\\/\\/2:" + +#: src/gui/pref/pg_text_conversations.py:54 +msgid "buddy icon" +msgstr "|3|_||)|)'/ 1(0|\\|" + +#: src/gui/pref/pg_text_conversations.py:55 +msgid "service icon" +msgstr "53|2\\/1(3 1(0|\\|" + +#: src/gui/pref/pg_text_conversations.py:56 +msgid "status icon" +msgstr "5747|_|2 1(0|\\|" + +#: src/gui/pref/pg_text_conversations.py:57 +msgid "Identify conversations with the contact's: " +msgstr "1|)3|\\|71|='/ (0|\\|\\/3|254710|\\|2 \\/\\/17# 73# (0|\\|7497'5: " + +#: src/gui/pref/pg_text_conversations.py:64 +msgid "Conversation Options" +msgstr "(0|\\|\\/3|254710|\\| 0P710|\\|2" + +#: src/gui/pref/pg_text_conversations.py:66 +msgid "Don't show flash ads" +msgstr "|)0|\\|'7 5#0\\/\\/ |=|_45# 4|)2" + +#: src/gui/pref/pg_text_conversations.py:70 +msgid "" +"Help keep Digsby free by showing an\n" +"advertisement in the IM window." +msgstr "5|_|PP0|27 |)165|3'/ |)3\\/3|_0P|\\/|3|\\|7 |3'/ 5#0\\/\\/1|\\|' |\\| 4|) 1|\\| 73# 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_text_conversations.py:71 +msgid "Support Digsby development with an ad" +msgstr "5|_|PP0|27 |)165|3'/ |)3\\/3|_0P|\\/|3|\\|7 |3'/ 5#0\\/\\/1|\\|' |\\| 4|) 1|\\| 73# 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_text_conversations.py:73 +msgid "Location of ad in IM window: " +msgstr "|_0(4710|\\| 0|= 4|) 1|\\| 1|\\/| \\/\\/1|\\||)0\\/\\/: " + +#: src/gui/pref/pg_text_conversations.py:77 +msgid "Ad Options" +msgstr "0P710|\\|2" + +#: src/gui/pref/pg_text_conversations.py:86 +msgid "Text Formatting" +msgstr "73><7 |=0|2|\\/|4771|\\|'" + +#: src/gui/pref/pg_text_conversations.py:106 +msgid "Your messages will look like this." +msgstr "'/0 |\\/|3554632 \\/\\/1|_|_ |_00|< 45 |)12." + +#: src/gui/pref/pg_text_conversations.py:157 +msgid "Show last %2(conversation_window.num_lines)d lines in IM window" +msgstr "5#0\\/\\/ |_457 %2(conversation_window.num_lines)d |_1|\\|32 0|= #1570|2'/ 1|\\| (0|\\|\\/3|54710|\\| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_text_conversations.py:160 +msgid "&Display timestamp:" +msgstr "&D15P|_4'/ 71|\\/|3574|\\/|P: " + +#: src/gui/pref/pg_text_conversations.py:168 +msgid "Spell check:" +msgstr "5P3|_|_(#3(|< 3|2|20|2" + +#: src/gui/pref/pg_text_conversations.py:187 +msgid "Log IM conversations to hard drive" +msgstr "|_06 1|\\/| (0|\\|\\/3|254710|\\|2 2 #4|2|) |)|21\\/3" + +#: src/gui/pref/pg_text_conversations.py:219 +msgid "Show &emoticons:" +msgstr "3|\\|4|3|_3 &E|\\/|071(0|\\|2: " + +#: src/gui/pref/pg_widgets.py:41 +msgid "Widget Preview" +msgstr "\\/\\/1|)637 P|23\\/13\\/\\/" + +#: src/gui/pref/pg_widgets.py:49 +msgid "&Copy To Clipboard" +msgstr "&(0P'/ 2 C|_1P|304|2d" + +#: src/gui/pref/pg_widgets.py:88 +msgid "New Widget" +msgstr "|\\|3\\/\\/ \\/\\/1|)637" + +#: src/gui/pref/pg_widgets.py:88 +#: src/gui/pref/prefstrings.py:23 +msgid "Widgets" +msgstr "\\/\\/1|)6372" + +#: src/gui/pref/pg_widgets.py:90 +msgid "Embed Tag" +msgstr "3|\\/||33|) 746" + +#: src/gui/pref/prefcontrols.py:584 +msgid "Custom ({prefval})" +msgstr "(|_|570|\\/| ({prefval})" + +#: src/gui/pref/prefcontrols.py:643 +msgid "Choose a folder" +msgstr "(#0053 4 |=0|_|)3|2" + +#: src/gui/pref/prefcontrols.py:841 +msgid "{val}px" +msgstr "{val}P><" + +#: src/gui/pref/prefsdialog.py:170 +msgid "Digsby Preferences" +msgstr "|)165|3'/ P|23|=3|23|\\|(32" + +#: src/gui/pref/prefsdialog.py:263 +msgid "&Done" +msgstr "&D0|\\|3" + +#: src/gui/pref/prefsdialog.py:289 +msgid "Search" +msgstr "&534|2(#" + +#: src/gui/pref/prefstrings.py:14 +msgid "Accounts" +msgstr "4((0|_||\\|75" + +#: src/gui/pref/prefstrings.py:15 +msgid "General & Profile" +msgstr "63|\\|3|24|_ & P|20|=1|_3" + +#: src/gui/pref/prefstrings.py:16 +msgid "Skins" +msgstr "5|<1|\\|2" + +#: src/gui/pref/prefstrings.py:18 +msgid "Conversations" +msgstr "(0|\\|\\/3|254710|\\|2" + +#: src/gui/pref/prefstrings.py:21 +#: src/plugins/msim/myspacegui/privacy.py:88 +msgid "Privacy" +msgstr "P|21\\/4('/" + +#: src/gui/pref/prefstrings.py:26 +#: src/gui/proxydialog.py:222 +msgid "Connection Settings" +msgstr "(0|\\||\\|3(710|\\| 53771|\\|62" + +#: src/gui/pref/prefstrings.py:42 +msgid "Developer" +msgstr "|)3\\/3|_0P3|2" + +#: src/gui/protocols/__init__.py:7 +msgid "Enter a new password for {username}:" +msgstr "3|\\|73|2 @ |\\|3\\/\\/ P455\\/\\/0|2|) 4 {username}::" + +#: src/gui/protocols/__init__.py:8 +#: src/gui/protocols/jabbergui.py:139 +msgid "Change Password" +msgstr "(#4|\\|63 P455\\/\\/0|2|)" + +#: src/gui/protocols/__init__.py:22 +msgid "Are you sure you want to delete contact {name}?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 c0|\\|74c7 {name}?" + +#: src/gui/protocols/__init__.py:23 +msgid "Delete Contact" +msgstr "|)3|_373 (0|\\|74c7" + +#: src/gui/protocols/__init__.py:36 +msgid "WARNING!" +msgstr "\\/\\/4|2|\\|1|\\|6!!12eleventy" + +#: src/gui/protocols/__init__.py:37 +msgid "All your contacts in this group will be deleted locally AND on the server." +msgstr "4|_|_ |_| (0|\\|74(72 |\\| |015 6|20|_|P \\/\\/1|_|_ |3 |)3|_373|) |_0(4|_|_'/ |\\| 0|\\| 53|2\\/3|2." + +#: src/gui/protocols/__init__.py:38 +msgid "Are you sure you want to remove {groupname}?" +msgstr "|_| 5|_||23 |_| \\/\\/4|\\|4 2 |23|\\/|0\\/3 {groupname}?" + +#: src/gui/protocols/__init__.py:42 +msgid "Delete Group" +msgstr "|)3|_373 6|20|_|P" + +#: src/gui/protocols/__init__.py:47 +msgid "Add Group" +msgstr "4|)|) 6|20|_|P" + +#: src/gui/protocols/__init__.py:47 +msgid "Please enter a group name:" +msgstr "P|_3453 3|\\|73|2 @ 6|20|_|P |\\|4|\\/|3:" + +#: src/gui/protocols/__init__.py:60 +#, python-format +msgid "Are you sure you want to block %s?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |3|_0(|< %s?" + +#: src/gui/protocols/__init__.py:61 +msgid "Block Buddy" +msgstr "|3|_0(|< B|_||)|)Y" + +# %s +#: src/gui/protocols/jabbergui.py:18 +#, python-format +msgid "Enter a priority for %s:" +msgstr "3|\\|73|2 @ P|210|217'/ 4 %s:" + +#: src/gui/protocols/jabbergui.py:19 +msgid "Set Jabber Priority" +msgstr "537 J4|3|33|2 P|210|217'/" + +#: src/gui/protocols/jabbergui.py:105 +msgid "Incorrect Password" +msgstr "1|\\|(0|2|23(7 P455\\/\\/0|2|)" + +#: src/gui/protocols/jabbergui.py:111 +msgid "Failed to delete account from the server." +msgstr "|=41|_3|) 2 |)3|_373 4((0|_||\\|7 |=|20|\\/| 73# 53|2\\/3|2." + +#: src/gui/protocols/jabbergui.py:112 +msgid "Delete Account - Failed" +msgstr "|)3|_373 4((0|_||\\|7 - |=41|_3|)" + +#: src/gui/protocols/jabbergui.py:150 +msgid "Old Password: " +msgstr "0|_|) P455\\/\\/0|2|): " + +#: src/gui/protocols/jabbergui.py:152 +msgid "New Password: " +msgstr "|\\|3\\/\\/ P455\\/\\/0|2|): " + +#: src/gui/protocols/jabbergui.py:154 +msgid "Confirm New Password: " +msgstr "(0|\\||=1|2|\\/| |\\|3\\/\\/ P455\\/\\/0|2|): " + +#: src/gui/protocols/jabbergui.py:217 +msgid "Incorrect Old Password" +msgstr "1|\\|(0|2|23(7 0|_|) P455\\/\\/0|2|)" + +#: src/gui/protocols/jabbergui.py:219 +msgid "Passwords do not match" +msgstr "P455\\/\\/0|2|) |)0 |\\|07 |\\/|47(#" + +#: src/gui/protocols/jabbergui.py:219 +msgid "Paswords do not match" +msgstr "P45\\/\\/0|2|)2 |)0 |\\|07 |\\/|47(#" + +#: src/gui/protocols/jabbergui.py:280 +msgid "&Enabled" +msgstr "&3|\\|4|3|_3|)" + +#: src/gui/protocols/jabbergui.py:283 +msgid "&Scroll Lock" +msgstr "&5(|20|_|_ |_0(|<" + +#: src/gui/protocols/jabbergui.py:290 +msgid "&Close" +msgstr "&(|_053" + +#: src/gui/protocols/oscargui.py:10 +msgid "Enter a formatted screenname for {username}" +msgstr "3|)17 |=0|2|\\/|4773|) 5(|233|\\||\\|4|\\/|3 |=0|2 {username}" + +#: src/gui/protocols/oscargui.py:11 +msgid "The new screenname must be the same as the old one, except for changes in capitalization and spacing." +msgstr "73# |\\|3\\/\\/ 5(|233|\\||\\|4|\\/|3 |\\/||_|57 |3 73# 54|\\/|3 42 73# 0|_|) 0|\\|3, ><(3P7 4 (#4|\\|632 1|\\| (4P174|_124710|\\| |\\| 5P4(1|\\|6." + +#: src/gui/protocols/oscargui.py:14 +msgid "Edit Formatted Screenname" +msgstr "3|)17 |=0|2|\\/|4773|) 5(|233|\\||\\|4|\\/|3" + +#: src/gui/protocols/oscargui.py:26 +msgid "Enter an email address:" +msgstr "3|\\|73|2 |\\| 3|\\/|41|_ 4|)|)|2352:" + +#: src/gui/protocols/oscargui.py:27 +msgid "Edit Account Email: {username}" +msgstr "3|)17 4((0|_||\\|7 3|\\/|41|_: {username}" + +#: src/gui/proxydialog.py:40 +msgid "&No proxy" +msgstr "&N0 P|20><'/" + +#: src/gui/proxydialog.py:41 +msgid "Use &default system settings" +msgstr "|_|53 &D3|=4|_||_7 5'/573|\\/| 53771|\\|62" + +#: src/gui/proxydialog.py:42 +msgid "&Specify proxy settings" +msgstr "&5P3(1|=Y P|20><'/ 53771|\\|62" + +#: src/gui/proxydialog.py:59 +msgid "&Host:" +msgstr "&H057:" + +#: src/gui/proxydialog.py:66 +msgid "P&ort:" +msgstr "P&0|27:" + +#: src/gui/proxydialog.py:96 +msgid "&Username:" +msgstr "&|_|53|2|\\|4|\\/|3:" + +#: src/gui/proxydialog.py:117 +msgid "Proxy Server" +msgstr "P|20><'/ 53|2\\/3|2" + +#: src/gui/proxydialog.py:118 +msgid "Protocol" +msgstr "P|2070(0|_" + +#: src/gui/proxydialog.py:121 +msgid "How to Connect" +msgstr "#0\\/\\/ 2 (0|\\||\\|3(7" + +#: src/gui/proxydialog.py:123 +msgid "Authentication" +msgstr "4|_|7#3|\\|71(4710|\\|" + +#: src/gui/proxydialog.py:232 +msgid "&OK" +msgstr "&0|<" + +#: src/gui/searchgui.py:43 +msgid "Arrange Searches" +msgstr "4|2|24|\\|63 534|2(#32" + +#: src/gui/searchgui.py:89 +msgid "Search {search_engine_name:s} for \"{search_string:s}\"" +msgstr "534|2(|-| {search_engine_name:s} 4 \"{search_string:s}\"" + +#: src/gui/searchgui.py:132 +msgid "Web Search" +msgstr "\\/\\/3|3 534|2(#" + +#: src/gui/social_status_dialog.py:103 +msgid "Set IM Status" +msgstr "537 1|\\/| 5747|_|2" + +#: src/gui/social_status_dialog.py:380 +msgid "Choose Accounts:" +msgstr "(#0053 4((0|_||\\|72:" + +#: src/gui/social_status_dialog.py:438 +msgid "Insert Link" +msgstr "1|\\|53|27 |_1|\\||<" + +#: src/gui/social_status_dialog.py:451 +msgid "Website URL:" +msgstr "\\/\\/3|35173 |_||2|_:" + +#: src/gui/social_status_dialog.py:461 +msgid "Insert &Link" +msgstr "1|\\|53|27 &L1|\\||<" + +#: src/gui/social_status_dialog.py:507 +msgid "Share Image" +msgstr "5#4|23 1|\\/|463" + +#: src/gui/social_status_dialog.py:582 +msgid "Uploading Image..." +msgstr "|_|P|_04|)1|\\|' 1|\\/|463..." + +#: src/gui/social_status_dialog.py:777 +msgid "Errors Encountered:" +msgstr "3|2|20|2 3|\\|(0|_||\\|73|23|):" + +#: src/gui/social_status_dialog.py:882 +#: src/plugins/msim/myspacegui/editstatus.py:6 +msgid "Your Status:" +msgstr "'/0 5747|_|5:" + +#: src/gui/social_status_dialog.py:891 +msgid "&Update Status" +msgstr "&|_|P|)473 5747|_|2" + +#: src/gui/social_status_dialog.py:1081 +#, python-format +msgid "Network error (%r)" +msgstr "|\\|37\\/\\/0|2|< 3|2|20|2 (%r)" + +#: src/gui/social_status_dialog.py:1083 +#, python-format +msgid "Network error (%s)" +msgstr "|\\|37\\/\\/0|2|< 3|2|20|2 (%s)" + +#: src/gui/social_status_dialog.py:1085 +msgid "Network error ({error!r} - {error!s})" +msgstr "|\\|37\\/\\/0|2|< 3|2|20|2 " + +#: src/gui/social_status_dialog.py:1087 +#: src/gui/social_status_dialog.py:1090 +#, python-format +msgid "%s" +msgstr "%s" + +#: src/gui/social_status_dialog.py:1092 +#, python-format +msgid "Unknown error (%s)" +msgstr "|_||\\||<|\\|0\\/\\/|\\| 3|2|20|2 %s" + +#: src/gui/social_status_dialog.py:1094 +msgid "Unknown error" +msgstr "|_||\\||<|\\|0\\/\\/|\\| 3|2|20|2" + +#: src/gui/spellchecktextctrlmixin.py:550 +msgid "Add to Dictionary" +msgstr "4|)|) 2 |)1(710|\\|4|2'/" + +#. Translators: Separator when listing multiple accounts, ex: MSN/ICQ only +#: src/gui/status.py:118 +msgid "/" +msgstr "/" + +#: src/gui/status.py:119 +msgid "{status} ({account_types} Only)" +msgstr "{status} ({account_types} 0|\\||_'/)" + +#: src/gui/status.py:374 +msgid "Message" +msgstr "|\\/|355463" + +#: src/gui/status.py:496 +msgid "&Title:" +msgstr "&717|_3:" + +#: src/gui/status.py:501 +msgid "&State:" +msgstr "&57473:" + +#: src/gui/status.py:509 +msgid "&Status message:" +msgstr "&5747|_|2 |\\/|355463:" + +#: src/gui/status.py:535 +msgid "&Use a different status for some accounts" +msgstr "&|_|53 @ |)1|=|=3|23|\\|7 5747|_|2 4 50|\\/|3 4((0|_||\\|72" + +#: src/gui/status.py:539 +msgid "&Set" +msgstr "&537" + +#: src/gui/status.py:548 +msgid "Save for &later" +msgstr "54\\/3 |=0|2 |_473|2" + +#: src/gui/status.py:608 +msgid "New Status for {name}" +msgstr "|\\|3\\/\\/ 5747|_|2 |=0|2 {name}" + +#: src/gui/status.py:633 +msgid "Edit Status Message" +msgstr "3|)17 5747|_|2 |\\/|355463" + +#: src/gui/status.py:636 +msgid "Edit Status for {account}" +msgstr "3|)17 5747|_|2 4 {account}" + +#: src/gui/status.py:793 +msgid "Are you sure you want to delete status message \"{title}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 5747|_|2 |\\/|355463 \"{title}\"?" + +#: src/gui/status.py:794 +msgid "Delete Status Message" +msgstr "|)3|_373 S747|_|2 |\\/|355463" + +#: src/gui/statuscombo.py:87 +#: src/gui/statuscombo.py:165 +#: src/gui/statuscombo.py:529 +#: src/plugins/twitter/twitter_gui.py:615 +msgid "Global Status" +msgstr "6|_0|34|_ 5747|_|5" + +#: src/gui/statuscombo.py:105 +#: src/gui/statuscombo.py:540 +msgid "Promote Digsby!" +msgstr "P|20|\\/|073 |)165|3'/!" + +#: src/gui/statuscombo.py:125 +#: src/gui/statuscombo.py:160 +#: src/gui/statuscombo.py:526 +msgid "Custom..." +msgstr "(|_|570|\\/|..." + +#: src/gui/statuscombo.py:639 +msgid "Press 'Ctrl+F' to Search List" +msgstr "P|2355 'Ctrl+F' 2 534|2(# |_157" + +#: src/gui/supportdigsby/supportdialog.py:96 +#: src/gui/supportdigsby/supportoptions.py:71 +msgid "Are you sure?" +msgstr "12 j00 5|_||23?" + +#: src/gui/supportdigsby/supportdialog.py:97 +msgid "Are you sure you want do this?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 |)0 |)12?" + +#: src/gui/supportdigsby/supportdialog.py:111 +msgid "Thank you for supporting Digsby." +msgstr "7'/ 4 5|_|PP0|271|\\|' |)165|3'/." + +#: src/gui/supportdigsby/supportdialog.py:111 +msgid "Thank you!" +msgstr "7'/!" + +#: src/gui/supportdigsby/supportoptions.py:22 +#: src/gui/supportdigsby/supportoptions.py:32 +msgid "Set" +msgstr "537" + +#: src/gui/supportdigsby/supportoptions.py:23 +msgid "Make Google Powered Digsby Search your homepage" +msgstr "|\\/|4|<3 6006|_3 P0\\/\\/3|23|) |)165|3'/ 534|2(# '/0 |)3|=4|_||_7 534|2(# #0|\\/|3P463" + +#: src/gui/supportdigsby/supportoptions.py:33 +msgid "Make Google Powered Digsby Search your default search engine" +msgstr "|\\/|4|<3 6006|_3 P0\\/\\/3|23|) |)165|3'/ 534|2(# '/0 |)3|=4|_||_7 534|2(# 3|\\|61|\\|3" + +#: src/gui/supportdigsby/supportoptions.py:42 +#: src/gui/supportdigsby/supportoptions.py:47 +msgid "Invite" +msgstr "1|\\|\\/173" + +#: src/gui/supportdigsby/supportoptions.py:43 +msgid "Invite your friends via Email" +msgstr "1|\\|\\/173 '/0 |=|213|\\||)2 \\/14 3|\\/|41|_" + +#: src/gui/supportdigsby/supportoptions.py:48 +msgid "Invite your friends via Facebook" +msgstr "1|\\|\\/173 '/0 |=|213|\\||)2 \\/14 |=4(3|300|<" + +#: src/gui/supportdigsby/supportoptions.py:52 +#: src/gui/supportdigsby/supportoptions.py:57 +msgid "Join Group" +msgstr "J01|\\| 6|20|_|P" + +#: src/gui/supportdigsby/supportoptions.py:53 +msgid "Become a fan on Facebook" +msgstr "|33(0|\\/|3 @ |=4|\\| 0|\\| |=4(3|300|<" + +#: src/gui/supportdigsby/supportoptions.py:58 +msgid "Become a fan on LinkedIn" +msgstr "|33(0|\\/|3 @ |=4|\\| 0|\\| |_1|\\||<3|)1|\\|" + +#: src/gui/supportdigsby/supportoptions.py:62 +msgid "Follow" +msgstr "|=0|_|_0\\/\\/" + +#: src/gui/supportdigsby/supportoptions.py:63 +msgid "Follow us on Twitter" +msgstr "|=0|_|_0\\/\\/ |_|2 0|\\| 7\\/\\/1773|2" + +#: src/gui/supportdigsby/supportoptions.py:67 +msgid "Help Digsby conduct research" +msgstr "#3|_P |)165|3'/ (0|\\||)|_|(7 |23534|2(#" + +#: src/gui/supportdigsby/supportoptions.py:70 +msgid "Helping us conduct research keeps Digsby free and ad-free. Are you sure you want to disable this option?" +msgstr "#3|_P1|\\|' |_|2 (0|\\||)|_|(7 |23534|2(# |<33P2 D165|3'/ |=|233 |\\| 4|)-|=r33. 12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)154|3|_3 |)12 0P710|\\|?" + +#: src/gui/supportdigsby/supportoptions.py:83 +msgid "Disable" +msgstr "|)154B|_3" + +#: src/gui/supportdigsby/supportoptions.py:85 +msgid "Enable" +msgstr "3|\\|4|3|_3" + +#: src/gui/supportdigsby/supportoptions.py:94 +msgid "Subscribe" +msgstr "5|_||35(|21|33" + +#: src/gui/supportdigsby/supportoptions.py:95 +msgid "Subscribe to our Blog" +msgstr "5|_||35(|21|33 2 0|_|r |3|_06" + +#: src/gui/supportdigsby/supportoptions.py:99 +msgid "Create" +msgstr "(|23473" + +#: src/gui/supportdigsby/supportoptions.py:100 +msgid "Create a Digsby widget for your blog or website" +msgstr "(|23473 @ |)165|3'/ \\/\\/1|)637 4 J00 |3|_06 0|2 \\/\\/3|35173" + +#: src/gui/toast/toasthelp.py:25 +msgid "TIP: Popup Notifications" +msgstr "71P: P0P|_|P |\\|071|=1(4710|\\|2" + +#: src/gui/toast/toasthelp.py:27 +msgid "You can right click popups to close them right away instead of waiting for them to fade out." +msgstr "J00 (4|\\| |216#7 (|_1(|< P0P|_|P2 2 (|_053 7#3|\\/| |216#7 4\\/\\/4'/ 1|\\|5734|) 0|= \\/\\/4171|\\|' 4 7#3|\\/| 2 |=4|)3 0|_|7." + +#: src/gui/toolbox/toolbox.py:427 +msgid "Confirm" +msgstr "(0|\\||=1|2|\\/|" + +#: src/gui/toolbox/toolbox.py:1225 +#: src/gui/toolbox/toolbox.py:1252 +msgid "Right To Left" +msgstr "|216#7 2 |_3|=7" + +#: src/gui/toolbox/toolbox.py:1336 +msgid "Use Long URL" +msgstr "|_|53 |_0|\\|6 |_||2|_" + +#: src/gui/toolbox/toolbox.py:1346 +msgid "Shorten URL" +msgstr "5#0|273|\\| |_||2|_" + +#: src/gui/toolbox/toolbox.py:1437 +msgid "Undo" +msgstr "|_||\\||)0" + +#: src/gui/toolbox/toolbox.py:1440 +msgid "Redo" +msgstr "|23|)0" + +#: src/gui/toolbox/toolbox.py:1445 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:104 +msgid "Cut" +msgstr "(|_|7" + +#: src/gui/toolbox/toolbox.py:1455 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:108 +msgid "Select All" +msgstr "53|_3(7 4|_|_" + +#: src/gui/toolbox/toolbox.py:1461 +msgid "Select an image file" +msgstr "53|_3(7 |\\| 1|\\/|463 |=1|_3" + +#: src/gui/trayicons.py:172 +msgid "Set &Global Status..." +msgstr "537 &G|_0|34|_ 5747|_|2..." + +#: src/gui/trayicons.py:175 +msgid "Set IM &Status" +msgstr "537 1|\\/| &S747|_|5" + +#: src/gui/trayicons.py:179 +#: src/gui/trayicons.py:185 +msgid "Show &Buddy List" +msgstr "5#0\\/\\/ &B|_||)|)'/ |_157" + +#: src/gui/trayicons.py:185 +msgid "Hide &Buddy List" +msgstr "#1|)3 &B|_||)|)'/ |_157" + +#: src/gui/trayicons.py:190 +msgid "Show Menu Bar" +msgstr "5#0\\/\\/ |\\/|3|\\||_| |34|2" + +#: src/gui/trayicons.py:214 +msgid "Digsby" +msgstr "D165|3'/" + +#: src/gui/trayicons.py:225 +msgid "{msgs} message" +msgid_plural "{msgs} messages" +msgstr[0] "{msgs} |\\/|355463" +msgstr[1] "{msgs} |\\/|3554632" + +#: src/gui/trayicons.py:230 +msgid "{alias} ({service}) - {message}" +msgstr "{alias} ({service}) - {message}" + +#: src/gui/trayicons.py:298 +msgid "Show All" +msgstr "5#0\\/\\/ 4|_|_" + +#: src/gui/uberwidgets/formattedinput.py:423 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:103 +msgid "Hide Formatting Bar" +msgstr "#1|)3 |=0|2|\\/|4771|\\|' B4|2" + +#: src/gui/uberwidgets/formattedinput.py:726 +#: src/gui/uberwidgets/formattedinput2/formattedinput.py:206 +msgid "Set Font..." +msgstr "537 |=0|\\|7..." + +#: src/gui/uberwidgets/formattedinput.py:739 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:133 +msgid "Choose a foreground color" +msgstr "(#0053 @ |=0|236|20|_||\\||) (0|_0|2" + +#: src/gui/vcard/vcardgui.py:19 +msgid "Unit" +msgstr "|_||\\|17" + +#: src/gui/vcard/vcardgui.py:48 +msgid "Last Name" +msgstr "|_457 |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:49 +msgid "First Name" +msgstr "|=1|257 |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:50 +msgid "Middle Name" +msgstr "|\\/|1|)|)|_3 |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:51 +msgid "Prefix" +msgstr "P|23|=1><" + +#: src/gui/vcard/vcardgui.py:52 +msgid "Suffix" +msgstr "S|_||=|=1><" + +#: src/gui/vcard/vcardgui.py:108 +msgid "Number" +msgstr "|\\||_||\\/||33|2" + +#: src/gui/vcard/vcardgui.py:173 +msgid "Street" +msgstr "57|2337" + +#: src/gui/vcard/vcardgui.py:176 +msgid "City" +msgstr "(17'/" + +#: src/gui/vcard/vcardgui.py:177 +msgid "State" +msgstr "57473" + +#: src/gui/vcard/vcardgui.py:178 +msgid "Postal Code" +msgstr "P0574|_ (0|)3" + +#: src/gui/vcard/vcardgui.py:179 +msgid "Country" +msgstr "(0|_||\\|7|2'/" + +#: src/gui/vcard/vcardgui.py:263 +msgid "vCard Editor for {username}" +msgstr "\\/(4|2|) 3|)170|2 4 {username}" + +#: src/gui/vcard/vcardgui.py:263 +msgid "vCard Viewer" +msgstr "\\/(4|2|) \\/13\\/\\/3|2" + +#: src/gui/vcard/vcardgui.py:274 +msgid "Retreive" +msgstr "|237|231\\/3" + +#: src/gui/vcard/vcardgui.py:334 +msgid "General" +msgstr "63|\\|3|24|_" + +#: src/gui/vcard/vcardgui.py:337 +msgid "Full Name" +msgstr "|=|_||_|_ |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:338 +#: src/plugins/msim/res/content.tenjin:22 +msgid "Nickname" +msgstr "|\\|1(|<|\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:339 +#: src/oscar/OscarBuddies.py:292 +msgid "Birthday" +msgstr "|31|27#|)4'/" + +#: src/gui/vcard/vcardgui.py:341 +msgid "Homepage" +msgstr "#0|\\/|3P463" + +#: src/gui/vcard/vcardgui.py:358 +msgid "Work" +msgstr "\\/\\/0|2|<" + +#: src/gui/vcard/vcardgui.py:363 +#: src/oscar/OscarBuddies.py:340 +msgid "Position" +msgstr "P051710|\\|" + +#: src/gui/vcard/vcardgui.py:364 +msgid "Role" +msgstr "|20|_3" + +#: src/gui/vcard/vcardgui.py:377 +#: src/plugins/msim/res/content.tenjin:26 +msgid "Location" +msgstr "|_0(4710|\\|" + +#: src/gui/vcard/vcardgui.py:390 +msgid "About" +msgstr "4|30|_|7" + +#: src/gui/video/webvideo.py:19 +msgid "{name} has invited you to an audio/video chat." +msgstr "{name} 1|\\|\\/173|) |_| 2 |\\| 4|_||)10/\\/1|)30 (#47." + +#: src/gui/video/webvideo.py:20 +#: src/hub.py:122 +msgid "Would you like to join?" +msgstr "J01|\\| 637?" + +#: src/gui/video/webvideo.py:24 +msgid "Audio/Video Chat Invitation" +msgstr "4|_||)10/\\/1|)30 (#47 1|\\|\\/174710|\\|" + +#: src/gui/visuallisteditor.py:114 +msgid "(Unnamed Panel)" +msgstr "(|_||\\||\\|4|\\/|3|) P4|\\|3|_)" + +#: src/gui/visuallisteditor.py:298 +msgid "Drag and drop to reorder" +msgstr "|)|246 |\\| |)|20P 2 |230|2|)3|2" + +#: src/gui/visuallisteditor.py:308 +msgid "Done" +msgstr "|)0|\\|3" + +#: src/gui/widgetlist.py:124 +msgid "Are you sure you want to delete widget \"{widgetname}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 \\/\\/1|)637 \"{widgetname}\"?" + +#: src/gui/widgetlist.py:125 +msgid "Delete Widget" +msgstr "|)3|_373 \\/\\/1|)637" + +#: src/hub.py:107 +msgid "{protocolname} DirectIM" +msgstr "{protocolname} |)1|23(71|\\/|" + +#: src/hub.py:108 +msgid "{name} wants to directly connect with you. (Your IP address will be revealed.)" +msgstr "{name} \\/\\/4|\\|75 |)1|23>< (0|\\||\\|3><" + +#: src/hub.py:127 +msgid "{name} has invited you to a group chat." +msgstr "{name} \\/\\/4|\\|75 70 P4|27'/ |_|P." + +#: src/hub.py:130 +msgid "You have been invited to a group chat." +msgstr "J00 #4\\/3 |333|\\| 1|\\|\\/173|) 2 @ 6|20|_|P (#47." + +#: src/hub.py:140 +msgid "Join" +msgstr "J01|\\|" + +#: src/hub.py:141 +msgid "Ignore" +msgstr "16|\\|0|23" + +#: src/hub.py:151 +msgid "{protocol_name:s} Invite" +msgstr "{protocol_name:s} 1|\\|\\/173" + +#: src/hub.py:157 +msgid "Ignore Invite" +msgstr "16|\\|0|23 1|\\|\\/173" + +#: src/hub.py:176 +msgid "Allow {buddy} to add you ({you}) as a buddy on {protocol}?" +msgstr "4|_|_0\\/\\/ {buddy} 2 4|)|) J00 ({you}) 42 @ |3|_||)|)'/ 0|\\| {protocol}?" + +#: src/imaccount.py:515 +#: src/imaccount.py:522 +msgid "Connect" +msgstr "(0|\\||\\|3(7" + +#: src/imaccount.py:523 +msgid "Disconnect" +msgstr "|)15(0|\\||\\|3(7" + +#: src/jabber/JabberBuddy.py:176 +#: src/oscar/OscarBuddies.py:280 +msgid "Full Name:" +msgstr "|=|_||_|_ |\\|4|\\/|3:" + +#: src/jabber/JabberBuddy.py:187 +msgid "Birthday:" +msgstr "|31|27#|)4'/:" + +#: src/jabber/JabberBuddy.py:187 +#: src/plugins/digsby_service_editor/default_ui.py:143 +msgid "Email Address:" +msgstr "3|\\/|41|_ 4||)|)|255:" + +#: src/jabber/JabberBuddy.py:187 +msgid "Phone:" +msgstr "P|-|0|\\|3:" + +#: src/jabber/JabberBuddy.py:196 +msgid "Website" +msgstr "\\/\\/3|35173" + +#: src/jabber/JabberBuddy.py:203 +#: src/oscar/OscarBuddies.py:314 +msgid "Additional Information:" +msgstr "4|)|)1710|\\|4|_ 1|\\||=0|2|\\/|4710|\\|" + +#: src/jabber/JabberBuddy.py:210 +msgid "{street} {extra_adress} {locality} {region} {postal_code} {country}" +msgstr "{street} {extra_adress} {locality} {region} {postal_code} {country}" + +#: src/jabber/JabberBuddy.py:256 +#: src/oscar/OscarBuddies.py:320 +msgid "Home Address:" +msgstr "|-|0|\\/|3 4||)|)|255:" + +#: src/jabber/JabberChat.py:298 +msgid "You have been invited to {roomname}" +msgstr "J00 #4\\/3 |333|\\| 1|\\|\\/173|) 2 {roomname}" + +#: src/jabber/JabberConversation.py:133 +msgid "{name} has left the conversation." +msgstr "{name} |24|\\| 4\\/\\/4'/." + +#: src/jabber/JabberResource.py:15 +#: src/oscar/OscarBuddies.py:45 +msgid "Free for Chat" +msgstr "|=|233 4 (#47" + +#: src/main.py:379 +msgid "Your digsby password has been changed. Please log back in with the new password." +msgstr "'/0 |)165b'/ P455\\/\\/0|2|) #42 |333|\\| (#4|\\|63|). P|_3453 |_06 |34(|< 1|\\| \\/\\/17# 73# |\\|3\\/\\/ P455\\/\\/0|2|)." + +#: src/main.py:381 +msgid "Password Changed" +msgstr "P455\\/\\/0|2|) (#4|\\|63|)" + +#: src/main.py:562 +msgid "Advanced Preferences" +msgstr "4|)\\/4|\\|(3|) P|23|=3|23|\\|(32" + +#: src/main.py:562 +msgid "Please login first." +msgstr "P|_3453 |_061|\\| |=1|257." + +#: src/main.py:816 +msgid "Global Shortcuts" +msgstr "6|_0|34|_ 5#0|27(|_|72" + +#: src/main.py:820 +msgid "Debug Global Shortcuts" +msgstr "|)3|3|_|6 6|_0|34|_ 5#0|27(|_|72" + +#: src/main.py:823 +msgid "Text Controls" +msgstr "73><7 (0|\\|7|20|_2" + +#: src/main.py:1345 +msgid "There was an error submitting your crash report." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 5|_||3|\\/|1771|\\|' '/0 (|245# |23P0|27." + +#: src/main.py:1358 +msgid "Crash report submitted successfully." +msgstr "(|245# |23P0|27 5|_||3|\\/|1773|) 5|_|((355|=|_||_|_'/." + +#: src/main.py:1365 +msgid "Would you like to restart Digsby now?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 |23574|27 |)165|3'/ |\\|0\\/\\/?" + +#: src/main.py:1367 +msgid "Crash Report" +msgstr "(|245# |23P0|27" + +#: src/msn/MSNBuddy.py:26 +#: src/plugins/provider_windows_live/info.yaml:32 +msgid "Be Right Back" +msgstr "|3 |216#7 |34(|<" + +#: src/msn/MSNBuddy.py:27 +#: src/plugins/provider_windows_live/info.yaml:34 +msgid "On the Phone" +msgstr "0|\\| 73# P#0|\\|3" + +#: src/msn/MSNBuddy.py:28 +#: src/plugins/provider_windows_live/info.yaml:35 +msgid "Out to Lunch" +msgstr "0|_|7 2 |_|_||\\|(#" + +#: src/msn/MSNBuddy.py:284 +#: src/oscar/OscarBuddies.py:242 +msgid "Mobile" +msgstr "|\\/|0|31|_3" + +#: src/msn/MSNBuddy.py:488 +#: src/plugins/digsby_service_editor/default_ui.py:207 +msgid "Display Name:" +msgstr "|)15P|_4Y |\\|4|\\/|3:" + +#: src/msn/MSNBuddy.py:559 +msgid "Administrators:" +msgstr "4|)|\\/|1|\\|157|2470|22:" + +#: src/msn/MSNBuddy.py:561 +msgid "Assistant Administrators:" +msgstr "45515574|\\|7 4|)|\\/|1|\\|157|2470|22:" + +#: src/msn/MSNBuddy.py:563 +msgid "Members:" +msgstr "M3|\\/||33|22" + +#: src/msn/MSNBuddy.py:565 +msgid "Invited:" +msgstr "1|\\|\\/173|):" + +#: src/msn/MSNBuddy.py:802 +msgid "{song:s} by {artist:s}" +msgstr "{song:s}|3'/ {artist:s}" + +#: src/msn/MSNConversation.py:287 +msgid "There was a network error creating a chat session." +msgstr "7#3|23 \\/\\/|_|2 @ |\\|37\\/\\/0|2|< 3|2|20|2 (|23471|\\|' @ (#47 535510|\\|." + +#: src/msn/MSNConversation.py:466 +msgid "Your messages could not be sent because too many conversation sessions have been requested." +msgstr "'/0 |\\/|3554632 (0|_||_|) |\\|07 |3 53|\\|7 (02 700 |\\/|4|\\|'/ (0|\\|\\/3|254710|\\| 535510|\\|2 #4\\/3 |333|\\| |23Q|_|3573|)." + +#. Translators: ex: Frank nudged you! +#: src/msn/MSNConversation.py:599 +#: src/msn/p21/MSNP21Conversation.py:260 +msgid "{name} {action}" +msgstr "{name} {action}" + +#: src/msn/MSNConversation.py:603 +#: src/msn/p21/MSNP21Conversation.py:264 +msgid "{name} winked at you!" +msgstr "{name} \\/\\/1|\\||<3|) 47 J00!" + +#: src/msn/MSNConversation.py:604 +#: src/msn/p21/MSNP21Conversation.py:265 +msgid "{name} nudged you!" +msgstr "{name} |\\||_||)63|) |_|!" + +#: src/msn/msngui.py:15 +msgid "Group Invite" +msgstr "6|20|_|P 1|\\|\\/173" + +#: src/msn/msngui.py:20 +#: src/plugins/digsby_updater/updater.py:487 +#: src/plugins/digsby_updater/updater.py:499 +msgid "Yes" +msgstr "'/32" + +#: src/msn/msngui.py:21 +#: src/plugins/digsby_updater/updater.py:488 +#: src/plugins/digsby_updater/updater.py:500 +msgid "No" +msgstr "|\\|0" + +#: src/msn/msngui.py:25 +msgid "{inviter_email} says: \"{invite_message}\"" +msgstr "{inviter_email} 54'/2: \"{invite_message}\"" + +#: src/msn/msngui.py:28 +msgid "" +"{inviter_email} has invited you to join the group {circle_name}.{invite_segment}\n" +"Would you like to join?" +msgstr "" +"{inviter_email} #42 1|\\|\\/173|) J00 2 J01|\\| 73# 6|20|_|P {circle_name}.{invite_segment}\n" +"\\/\\/0|_||_|) J00 \\/\\/|_||3 2 J01|\\|?" + +#: src/msn/msngui.py:36 +msgid "Please enter a name for your group chat:" +msgstr "P|_3453 3|\\|73|2 4 |\\|4|\\/|3 6|20|_|P 4 J00 6|20|_|P (|-|47:" + +#: src/oscar/OscarBuddies.py:46 +#: src/plugins/provider_aol/info.yaml:142 +msgid "Occupied" +msgstr "0((|_|P13|)" + +#: src/oscar/OscarBuddies.py:265 +#: src/oscar/OscarBuddies.py:356 +msgid "Profile URL:" +msgstr "P|20|=1|_3 |_||2|_:" + +#: src/oscar/OscarBuddies.py:291 +#: src/plugins/msim/res/content.tenjin:25 +msgid "Gender" +msgstr "63|\\||)3|2" + +#: src/oscar/OscarBuddies.py:295 +#: src/oscar/OscarBuddies.py:343 +msgid "{label}:" +msgstr "{label}:" + +#: src/oscar/OscarBuddies.py:301 +msgid "Website:" +msgstr "\\/\\/3|35173:" + +#: src/oscar/OscarBuddies.py:333 +msgid "Work Address:" +msgstr "\\/\\/0|2|< 4||)|)|255:" + +#: src/oscar/OscarBuddies.py:340 +msgid "Company" +msgstr "(0|\\/|P4|\\|'/" + +#: src/oscar/OscarBuddies.py:340 +msgid "Department" +msgstr "|)3P4|27|\\/|3|\\|7" + +#: src/oscar/OscarBuddies.py:348 +msgid "Work Website:" +msgstr "\\/\\/0|2|< \\/\\/3|35173:" + +#: src/oscar/snac/family_x07.py:165 +msgid "You should receive an email with confirmation instructions shortly." +msgstr "J00 5#0|_||_|) |23(31\\/3 |\\| 3|\\/|41|_ \\/\\/17# (0|\\||=1|2|\\/|4710|\\| 1|\\|57|2|_|9710|\\|2 5#0|27|_'/." + +#: src/oscar/snac/family_x07.py:166 +msgid "Your account is already confirmed." +msgstr "'/0 4((0|_||\\|7 |3 4|_|234|)'/ (0|\\||=1|2|\\/|3|)." + +#: src/oscar/snac/family_x07.py:167 +msgid "There was an unknown server error." +msgstr "7#3|23 \\/\\/|_|2 |\\| |_||\\||<|\\|0\\/\\/|\\| 53|2\\/3|2 3|2|20|2." + +#: src/oscar/snac/family_x07.py:180 +msgid "Confirm Account: {username}" +msgstr "(0|\\||=1|2|\\/| 4((0|_||\\|7: {username}" + +#: src/oscar/snac/family_x15.py:636 +msgid "Female" +msgstr "|=3|\\/|4|_3" + +#: src/oscar/snac/family_x15.py:636 +msgid "Male" +msgstr "|\\/|4|_3" + +#: src/plugins/component_gmail/gmail.py:123 +msgid "Mark as Read" +msgstr "|\\/|4|2|< 42 |234|)" + +#: src/plugins/component_gmail/gmail.py:124 +msgid "Archive" +msgstr "4|2(#1\\/3" + +#: src/plugins/component_gmail/info.yaml:2 +msgid "Gmail" +msgstr "6|\\/|41|_" + +#: src/plugins/component_gmail/info.yaml:6 +msgid "Gmail Account" +msgstr "3|\\/|41|_ 4((0|_||\\|72" + +#: src/plugins/component_gtalk/info.yaml:0 +msgid "Google Talk" +msgstr "6006|_3 74|_|<" + +#: src/plugins/component_gtalk/info.yaml:59 +#: src/plugins/provider_google/info.yaml:0 +#: src/plugins/provider_google/info.yaml:10 +msgid "Google Account" +msgstr "(#0053 4((0|_||\\|72:" + +#: src/plugins/component_yahooim/info.yaml:1 +msgid "Yahoo" +msgstr "'/4#00" + +#: src/plugins/component_yahooim/info.yaml:3 +msgid "Yahoo Messenger" +msgstr "'/4|-|00 |\\/|355463|2" + +#: src/plugins/component_yahooim/info.yaml:7 +#: src/plugins/component_ymail/info.yaml:7 +#: src/plugins/provider_yahoo/info.yaml:10 +msgid "Yahoo! ID" +msgstr "'/4#00! 1|)" + +#: src/plugins/component_ymail/info.yaml:2 +msgid "Yahoo! Mail" +msgstr "'/4#00 |\\/|41|_" + +#: src/plugins/component_ymail/info.yaml:3 +msgid "Yahoo Mail" +msgstr "'/4#00 |\\/|41|_" + +#: src/plugins/digsby_about/aboutdialog.py:48 +msgid "About Digsby" +msgstr "4|30|_|7 |)165|3'/" + +#: src/plugins/digsby_about/res/content.tenjin:14 +msgid "Checking for updates..." +msgstr "(#3(|< 4 |_|P|)4732..." + +#: src/plugins/digsby_about/res/content.tenjin:20 +msgid "Digsby is up to date" +msgstr "|)165|3'/ 15 |_|P 2 |)473" + +#: src/plugins/digsby_about/res/content.tenjin:26 +#: src/plugins/digsby_updater/updater.py:484 +#: src/plugins/digsby_updater/updater.py:496 +msgid "Update Available" +msgstr "|_|P|)473 4\\/41|_4|3|_3" + +#: src/plugins/digsby_about/res/content.tenjin:31 +msgid "Copyright © 2010 dotSyntax, LLC." +msgstr "(0p'/|216#7 © 2010 |)07S'/|\\|74><, |_|_(." + +#: src/plugins/digsby_about/res/content.tenjin:33 +msgid "All Rights Reserved." +msgstr "4|_|_ |216#72 |2353|2\\/3|)." + +#: src/plugins/digsby_email/info.yaml:1 +#: src/plugins/digsby_email/info.yaml:26 +#: src/plugins/digsby_email/info.yaml:116 +#: src/plugins/myspace/MyspaceProtocol.py:40 +msgid "Mail" +msgstr "|\\/|41|_" + +#: src/plugins/digsby_email/info.yaml:10 +msgid "POP Account" +msgstr "P0P 4((0|_||\\|7" + +#: src/plugins/digsby_email/info.yaml:30 +msgid "POP" +msgstr "P0P" + +#: src/plugins/digsby_email/info.yaml:68 +msgid "IMAP Account" +msgstr "1|\\/|4P 4((0|_||\\|72" + +#: src/plugins/digsby_email/info.yaml:101 +msgid "IMAP" +msgstr "1|\\/|4P" + +#: src/plugins/digsby_service_editor/default_ui.py:62 +msgid "{account_name:s} - {service_name:s} Settings" +msgstr "{account_name:s} - {service_name:s} 53771|\\|62" + +#: src/plugins/digsby_service_editor/default_ui.py:167 +msgid "Forgot Password?" +msgstr "|=0|2607 P455\\/\\/0|2|)?" + +#: src/plugins/digsby_service_editor/default_ui.py:247 +msgid "&{server_type} Server:" +msgstr "&{server_type} 53|2\\/3|2:" + +#: src/plugins/digsby_service_editor/default_ui.py:248 +msgid "&This server requires SSL" +msgstr "&|)12 53|2\\/3|2 |23Q|_|1|232 55|_" + +#: src/plugins/digsby_service_editor/default_ui.py:266 +msgid "This server requires SSL" +msgstr "|)12 53|2\\/3|2 |23Q|_|1|232 55|_" + +#: src/plugins/digsby_service_editor/default_ui.py:326 +msgid "IM Server:" +msgstr "1|\\/| 53|2\\/3|2:" + +#: src/plugins/digsby_service_editor/default_ui.py:348 +msgid "Always connect over HTTP" +msgstr "4|_\\/\\/4'/2 (0|\\||\\|3(7 0\\/3|2 #77P" + +#: src/plugins/digsby_service_editor/default_ui.py:363 +msgid "Check for new mail every {minutes_input} minutes" +msgstr "(#3(|< 4 |\\|3\\/\\/ |\\/|41|_ 3\\/3|2'/ {minutes_input} minutes" + +#: src/plugins/digsby_service_editor/default_ui.py:600 +msgid "Custom ({mailclient})" +msgstr "(|_|570|\\/| ({mailclient})" + +#: src/plugins/digsby_status/status_tag_urls.py:56 +#: src/plugins/facebook/fbgui.py:10 +#: src/plugins/linkedin/ligui.py:8 +#: src/plugins/myspace/msgui.py:10 +#: src/plugins/researchdriver/researchtoast.py:36 +#: src/plugins/twitter/twgui.py:14 +msgid "Learn More" +msgstr "|_34|2|\\| |\\/|0|23" + +#: src/plugins/digsby_status/status_tag_urls.py:67 +msgid "Yes, Spread the Word!" +msgstr "'/32, 5P|234|) 73# \\/\\/0|2|)!" + +#: src/plugins/digsby_status/status_tag_urls.py:68 +#: src/plugins/facebook/fbgui.py:19 +#: src/plugins/linkedin/ligui.py:17 +#: src/plugins/msim/myspacegui/prompts.py:11 +#: src/plugins/myspace/msgui.py:19 +#: src/plugins/twitter/twgui.py:23 +msgid "No Thanks" +msgstr "|\\|0 7#4|\\||<2" + +#: src/plugins/digsby_status/status_tag_urls.py:106 +msgid "Spread the Word!" +msgstr "5P|234|) 73# \\/\\/0|2|)!" + +#: src/plugins/digsby_status/status_tag_urls.py:109 +msgid "We've made it easier to spread the word about Digsby by adding a link to your IM status. You can disable this option in Preferences > Status." +msgstr "\\/\\/3'\\/3 |\\/|4|)3 17 34513|2 2 5P|234|) 73# \\/\\/0|2|) 4|30|_|7 |)165|3'/ |3'/ 4|)|)1|\\|' @ |_1|\\||< 2 '/0 1|\\/| 5747|_|2. J00 (4|\\| |)154|3|_3 |)12 0P710|\\| 1|\\| P|23|=3|23|\\|(32 > 5747|_|2." + +#: src/plugins/digsby_updater/UpdateProgress.py:55 +msgid "Digsby Update" +msgstr "|)165|3'/ |_|P|)473" + +#: src/plugins/digsby_updater/UpdateProgress.py:63 +msgid "{state}: {completed} of {size} -- {time} remain" +msgstr "{state}: {completed} 0|= {size} -- {time} |23|\\/|41|\\|" + +#: src/plugins/digsby_updater/UpdateProgress.py:145 +msgid "Slower" +msgstr "5|_0\\/\\/3|2" + +#: src/plugins/digsby_updater/UpdateProgress.py:146 +msgid "Faster!" +msgstr "|=4573|2!" + +#: src/plugins/digsby_updater/machelpers.py:18 +msgid "Error while updating Digsby. Please restart and try again, or grab the latest version from digsby.com. Digsby will now shut down." +msgstr "3|2|20|2 \\/\\/#1|_3 |_|P|)471|\\|6 |)165|3'/. P|_3453 |23574|27 |\\| 7|2'/ 4641|\\|, 0|2 6|24|3 73# |_47357 \\/3|2510|\\| |=|20|\\/| |)165|3'/.(0|\\/|. |)165|3'/ \\/\\/1|_|_ |\\|0\\/\\/ 5#|_|7 |)0\\/\\/|\\|." + +#: src/plugins/digsby_updater/machelpers.py:25 +msgid "Updated successfully. Digsby now needs to restart." +msgstr "|_|P|)473|) 5|_|((355|=|_||_|_'/. |)165|3'/ |\\|0\\/\\/ |\\|33|)2 2 |23574|27." + +#: src/plugins/digsby_updater/machelpers.py:31 +msgid "Unable to authenticate. Please restart and try again." +msgstr "|_||\\|4|3|_3 2 4|_|7#3|\\|71(473. P|_3453 |23574|27 |\\| 7|2'/ 4641|\\|." + +#: src/plugins/digsby_updater/updater.py:202 +msgid "Check for {tag} updates" +msgstr "(#3(|< 4 {tag} |_|P|)4732" + +#: src/plugins/digsby_updater/updater.py:204 +msgid "Check for Updates" +msgstr "(#3(|< 4 |_|P|)4732" + +#: src/plugins/digsby_updater/updater.py:485 +#: src/plugins/digsby_updater/updater.py:497 +msgid "" +"A Digsby update is available to download.\n" +"Would you like to begin downloading it now?" +msgstr "@ |)165|3'/ |_|P|)473 |3 4\\/41|_4|3|_3 2 |)0\\/\\/|\\||_04|).\\|\\|\\/\\/0|_||_|) J00 \\/\\/|_||3 2 |3361|\\| |)0\\/\\/|\\||_04|)1|\\|6 17 |\\|0\\/\\/?" + +#: src/plugins/digsby_updater/updater.py:573 +msgid "Update Failed" +msgstr "|_|P|)473 |=41|_3|)" + +#: src/plugins/digsby_updater/updater.py:574 +msgid "Digsby was unable to complete the download. This update will be attempted again later." +msgstr " \\/\\/|_|2 |_||\\|4B|_3 2 (0|\\/|P|_373 73# |)0\\/\\/|\\||_04|). |)12 |_|P|)473 \\/\\/1|_|_ |3 4773|\\/|P73|) 4641|\\| |_8|2." + +#: src/plugins/digsby_updater/updater.py:576 +msgid "Manual Update" +msgstr "|\\/|4|\\||_|4|_ |_|P|)473" + +#: src/plugins/digsby_updater/updater.py:606 +msgid "Update Ready" +msgstr "|_|P|)473 |234|)'/" + +#: src/plugins/digsby_updater/updater.py:607 +msgid "A new version of Digsby is ready to install. Restart Digsby to apply the update." +msgstr "@ |\\|3\\/\\/ \\/3|2510|\\| 0|= |)165|3'/ |3 |234|)'/ 2 1|\\|574|_|_. |23574|27 |)165|3'/ 2 4PP|_'/ 73# |_|P|)473." + +#: src/plugins/digsby_updater/updater.py:609 +msgid "Restart Now" +msgstr "|23574|27 |\\|40" + +#: src/plugins/digsby_updater/updater.py:610 +msgid "Restart Later" +msgstr "|23574|27 |_8|2" + +#: src/plugins/facebook/actions.yaml:8 +msgid "Pro&file" +msgstr "P|20|=1|_3" + +#: src/plugins/facebook/actions.yaml:12 +msgid "&Photos" +msgstr "P#0702" + +#: src/plugins/facebook/actions.yaml:14 +msgid "&Messages" +msgstr "|\\/|3554632" + +#: src/plugins/facebook/fbacct.py:310 +#: src/plugins/facebook/res/comment_link.py.xml:19 +#: src/plugins/facebook/res/comment_link.py.xml:20 +#: src/plugins/linkedin/LinkedInAccount.py:235 +#: src/plugins/linkedin/res/comment_link.tenjin:12 +#: src/plugins/linkedin/res/comment_link.tenjin:13 +#: src/plugins/myspace/MyspaceProtocol.py:417 +#: src/plugins/myspace/res/comment_link.tenjin:7 +#: src/plugins/myspace/res/comment_link.tenjin:9 +msgid "Comment" +msgstr "(0|\\/||\\/|3|\\|7" + +#: src/plugins/facebook/fbacct.py:318 +#: src/plugins/facebook/res/like_link.py.xml:11 +#: src/plugins/linkedin/res/like_dislike_link.tenjin:20 +#: src/plugins/myspace/res/like_dislike_link.tenjin:17 +msgid "Like" +msgstr "\\/\\/|_||3" + +#: src/plugins/facebook/fbacct.py:318 +#: src/plugins/facebook/res/like_link.py.xml:16 +msgid "Unlike" +msgstr "|_||\\||_1|<3" + +#: src/plugins/facebook/fbacct.py:329 +#: src/plugins/myspace/MyspaceProtocol.py:425 +msgid "comment" +msgstr "(0|\\/||\\/|3|\\|7" + +#: src/plugins/facebook/fbacct.py:349 +#: src/plugins/facebook/fbacct.py:353 +msgid "like" +msgstr "\\/\\/|_||3" + +#: src/plugins/facebook/fbacct.py:366 +msgid "Facebook Error" +msgstr "|=4(3|300|< 3|2|20|2" + +#: src/plugins/facebook/fbacct.py:367 +#: src/plugins/myspace/MyspaceProtocol.py:433 +msgid "Error posting {thing}" +msgstr "3|2|20|2 P0571|\\|6 {thing}" + +#: src/plugins/facebook/fbacct.py:368 +msgid "Digsby does not have permission to publish to your stream." +msgstr "|)165|3'/ |)032 |\\|07 #4\\/3 P3|2|\\/|15510|\\| 2 P|_||3|_15# 2 '/0 57|234|\\/|." + +#: src/plugins/facebook/fbacct.py:369 +#: src/plugins/myspace/MyspaceProtocol.py:435 +msgid "Grant Permissions" +msgstr "6|24|\\|7 P3|2|\\/|15510|\\|2" + +#: src/plugins/facebook/fbacct.py:389 +msgid "Facebook Alerts" +msgstr "|=4(3b00|< 4|_3|272" + +#: src/plugins/facebook/fbacct.py:396 +msgid "You have new messages." +msgstr "J00 #4\\/3 |\\|3\\/\\/ |\\/|3554632." + +#: src/plugins/facebook/fbacct.py:399 +msgid "You have new pokes." +msgstr "J00 #4\\/3 |\\|3\\/\\/ P0|<32." + +#: src/plugins/facebook/fbacct.py:402 +msgid "You have new shares." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 5#4|232." + +#: src/plugins/facebook/fbacct.py:405 +msgid "You have new friend requests." +msgstr "J00 #4\\/3 |\\|3\\/\\/ |=|213|\\||) |23Q|_|3572." + +#: src/plugins/facebook/fbacct.py:408 +msgid "You have new group invites." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 6|20|_|P 1|\\|\\/1732." + +#: src/plugins/facebook/fbacct.py:411 +msgid "You have new event invites." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/fbacct.py:416 +msgid "You have new notifications." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/fbgui.py:18 +#: src/plugins/linkedin/ligui.py:16 +#: src/plugins/myspace/msgui.py:18 +#: src/plugins/twitter/twgui.py:22 +msgid "Post Achievements" +msgstr "P057 4(#13\\/3|\\/|3|\\|72" + +#: src/plugins/facebook/fbgui.py:24 +msgid "Post achievements to my Wall" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ \\/\\/4|_|_" + +#: src/plugins/facebook/fbgui.py:40 +#: src/plugins/myspace/msgui.py:69 +msgid "Show Alerts:" +msgstr "5#0\\/\\/ 4|_3|272:" + +#: src/plugins/facebook/fbgui.py:43 +msgid "Messages" +msgstr "|\\/|3554632" + +#: src/plugins/facebook/fbgui.py:44 +msgid "Pokes" +msgstr "P0|<32" + +#: src/plugins/facebook/fbgui.py:45 +msgid "Shares" +msgstr "5#4|232" + +#: src/plugins/facebook/fbgui.py:46 +#: src/plugins/myspace/MyspaceProtocol.py:34 +#: src/plugins/myspace/msgui.py:79 +msgid "Friend Requests" +msgstr "|=|213|\\||) |239|_|3572" + +#: src/plugins/facebook/fbgui.py:47 +msgid "Group Invitations" +msgstr "6|20|_|P 1|\\|\\/174710|\\|2" + +#: src/plugins/facebook/fbgui.py:48 +#: src/plugins/myspace/MyspaceProtocol.py:33 +msgid "Event Invitations" +msgstr "3\\/3|\\|7 1|\\|\\/174710|\\|2" + +#: src/plugins/facebook/info.yaml:0 +msgid "Facebook" +msgstr "|=4(3|300|<" + +#: src/plugins/facebook/info.yaml:4 +msgid "News Feed" +msgstr "|\\|3\\/\\/5 |=33|)" + +#: src/plugins/facebook/info.yaml:26 +#: src/plugins/myspace/info.yaml:47 +#: src/plugins/twitter/res/content.tenjin:25 +msgid "Show Alerts" +msgstr "5#0\\/\\/ 4|_3|272:" + +#: src/plugins/facebook/notifications.yaml:1 +msgid "Facebook Alert" +msgstr "|=4(3b00|< 4|_3|272" + +#: src/plugins/facebook/notifications.yaml:10 +#: src/plugins/facebook/notifications.yaml:11 +msgid "Facebook Notification" +msgstr "|\\|071|=1(4710|\\|2" + +#: src/plugins/facebook/notifications.yaml:21 +msgid "Facebook Newsfeed" +msgstr "|\\/|'/5P4(3 |\\|3\\/\\/5|=33|)" + +#: src/plugins/facebook/res/alerts.py.xml:2 +#: src/plugins/myspace/res/indicators.tenjin:2 +#: src/plugins/twitter/res/content.tenjin:8 +msgid "Alerts" +msgstr "5#0\\/\\/ 4|_3|272:" + +#: src/plugins/facebook/res/alerts.py.xml:4 +msgid "No Alerts" +msgstr "|\\|0 4|_3|272" + +#: src/plugins/facebook/res/alerts.py.xml:6 +#, python-format +msgid "%d new message" +msgid_plural "%d new messages" +msgstr[0] "%d |\\|3\\/\\/ |\\/|355463." +msgstr[1] "%d |\\|3\\/\\/ |\\/|3554632." + +#: src/plugins/facebook/res/alerts.py.xml:7 +#, python-format +msgid "%d new friend request" +msgid_plural "%d new friend requests" +msgstr[0] "%d |\\|3\\/\\/ |=|213|\\||) |23Q|_|357" +msgstr[1] "%d |\\|3\\/\\/ |=|213|\\||) |23Q|_|3572" + +#: src/plugins/facebook/res/alerts.py.xml:8 +#, python-format +msgid "%d new poke" +msgid_plural "%d new pokes" +msgstr[0] "%d |\\|3\\/\\/ P0|<3" +msgstr[1] "%d |\\|3\\/\\/ P0|<32" + +#: src/plugins/facebook/res/alerts.py.xml:9 +#, python-format +msgid "%d new share" +msgid_plural "%d new shares" +msgstr[0] "%d |\\|3\\/\\/ 5#4|23." +msgstr[1] "%d |\\|3\\/\\/ 5#4|232." + +#: src/plugins/facebook/res/alerts.py.xml:10 +#, python-format +msgid "%d new group invite" +msgid_plural "%d new group invites" +msgstr[0] "%d |\\|3\\/\\/ 6|20|_|P 1|\\|\\/173." +msgstr[1] "%d |\\|3\\/\\/ 6|20|_|P 1|\\|\\/1732." + +#: src/plugins/facebook/res/alerts.py.xml:11 +#, python-format +msgid "%d new event invite" +msgid_plural "%d new event invites" +msgstr[0] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/173." +msgstr[1] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/res/alerts.py.xml:12 +#, python-format +msgid "%d new notification" +msgid_plural "%d new notifications" +msgstr[0] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/173." +msgstr[1] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/res/birthdays.py.xml:9 +msgid "Upcoming Birthdays" +msgstr "|_|P(0|\\/|1|\\|6 |31|27#|)4'/5" + +#: src/plugins/facebook/res/comment.py.xml:11 +#: src/plugins/facebook/res/comment.py.xml:13 +#: src/plugins/facebook/res/dislikes.py.xml:28 +#: src/plugins/facebook/res/dislikes.py.xml:30 +#: src/plugins/facebook/res/dislikes.py.xml:39 +#: src/plugins/facebook/res/dislikes.py.xml:41 +#: src/plugins/facebook/res/dislikes.py.xml:55 +#: src/plugins/facebook/res/dislikes.py.xml:57 +#: src/plugins/facebook/res/dislikes.py.xml:63 +#: src/plugins/facebook/res/dislikes.py.xml:65 +#: src/plugins/facebook/res/dislikes.py.xml:71 +#: src/plugins/facebook/res/dislikes.py.xml:73 +#: src/plugins/facebook/res/likes.py.xml:42 +#: src/plugins/facebook/res/likes.py.xml:46 +#: src/plugins/facebook/res/likes.py.xml:55 +#: src/plugins/facebook/res/likes.py.xml:62 +#: src/plugins/facebook/res/likes.py.xml:80 +#: src/plugins/facebook/res/likes.py.xml:85 +msgid "Facebook User" +msgstr "|=4(3b00|< |_|53|2" + +#: src/plugins/facebook/res/comment_link.py.xml:2 +#: src/plugins/facebook/res/comment_post_link.py.xml:2 +#: src/plugins/facebook/res/comments.py.xml:2 +#: src/plugins/facebook/res/dislike_link.py.xml:2 +#: src/plugins/facebook/res/dislikes.py.xml:2 +msgid "Dislike! (via http://digsby.com/fb)" +msgstr "|)15|_1|<3! (via http://digsby.com)" + +#: src/plugins/facebook/res/comment_post_link.py.xml:15 +#, python-format +msgid "See all %d comments" +msgstr "533 4|_|_ %d (0|\\/||\\/|3|\\|72" + +#: src/plugins/facebook/res/comment_post_link.py.xml:17 +msgid "See all comments" +msgstr "533 4|_|_ (0|\\/||\\/|3|\\|72" + +#: src/plugins/facebook/res/content.tenjin:35 +msgid "Back to News Feed" +msgstr "|34(|< 2 |\\|3\\/\\/5|=33|):" + +#: src/plugins/facebook/res/dislike_link.py.xml:18 +msgid "Dislike" +msgstr "|)15|_1|<3" + +#: src/plugins/facebook/res/dislike_link.py.xml:23 +msgid "Undislike" +msgstr "|_||\\||)15|_1|<3" + +#: src/plugins/facebook/res/dislike_link.py.xml:32 +msgid "Dislikes" +msgstr "|)154B|_32" + +#: src/plugins/facebook/res/like_link.py.xml:25 +#: src/plugins/linkedin/res/like_dislike_link.tenjin:29 +#: src/plugins/myspace/res/like_dislike_link.tenjin:26 +msgid "Likes" +msgstr "|_1|<35" + +#: src/plugins/facebook/res/likes.py.xml:14 +msgid "You liked this" +msgstr "|_| |_1|<3|) |)15" + +#: src/plugins/facebook/res/likes.py.xml:16 +msgid "You and {startlink_other}one other{endlink_other} liked this" +msgid_plural "You and {startlink_other}{num_others} others{endlink_other} liked this" +msgstr[0] "|_| |\\| {startlink_other}1 07|-|3|2{endlink_other} |_1|<3|) |)15" +msgstr[1] "|_| |\\| {startlink_other}{num_others} 07|-|3|22{endlink_other} |_1|<3|) |)15" + +#: src/plugins/facebook/res/likes.py.xml:40 +msgid "{startlink_name}{name}{endlink_name} liked this" +msgstr "{startlink_name}{name}{endlink_name} |_1|<3|) |)15" + +#: src/plugins/facebook/res/likes.py.xml:51 +msgid "{startlink_name}{name}{endlink_name} and {startlink_other}one other{endlink_other} liked this" +msgid_plural "{startlink_name}{name}{endlink_name} and {startlink_other}{num_others} others{endlink_other} liked this" +msgstr[0] "{startlink_name}{name}{endlink_name} |\\| {startlink_other}1 07|-|3|2{endlink_other} liked this" +msgstr[1] "{startlink_name}{name}{endlink_name} |\\| {startlink_other} 07|-|3|22{endlink_other} liked this" + +#: src/plugins/facebook/res/likes.py.xml:76 +msgid "{startlink_other}one user{endlink_other} liked this" +msgid_plural "{startlink_other}{num_users} users{endlink_other} liked this" +msgstr[0] "{startlink_other}1 |_|53|2{endlink_other} \\/\\/|_||33|) |)1s" +msgstr[1] "{startlink_other}{num_users} |_|53|22{endlink_other} \\/\\/|_||33|) |)12" + +#: src/plugins/facebook/res/post.py.xml:63 +msgid "See Friendship" +msgstr "533 |=|213|\\||)2" + +#: src/plugins/facebook/res/status.py.xml:7 +#: src/plugins/linkedin/res/content.tenjin:3 +msgid "You have no status message" +msgstr "J00 #4\\/3 |\\|0 5747|_|5 |\\/|3554632." + +#: src/plugins/facebook/res/status.py.xml:9 +#: src/plugins/myspace/res/status.tenjin:18 +#: src/plugins/twitter/res/status.tenjin:14 +#: src/plugins/twitter/res/status.tenjin:21 +msgid "update" +msgstr "|_|P|)4732" + +#: src/plugins/fbchat/info.yaml:5 +msgid "Facebook Chat" +msgstr "|=4(3b00|< (|-|47" + +#: src/plugins/fbchat/xmppfb.py:116 +msgid "Send Message" +msgstr "53|\\||) |\\/|355463" + +#: src/plugins/fbchat/xmppfb.py:117 +msgid "Send Poke" +msgstr "53|\\||) P0|<3" + +#: src/plugins/fbchat/xmppfb.py:118 +msgid "View Photos" +msgstr "\\/13\\/\\/ P#0702" + +#: src/plugins/fbchat/xmppfb.py:122 +#: src/yahoo/yahoobuddy.py:133 +msgid "View Profile" +msgstr "\\/13\\/\\/ P|20|=1|_3" + +#: src/plugins/fbchat/xmppfb.py:123 +msgid "Write on Wall" +msgstr "\\/\\/|2173 0|\\| \\/\\/4|_|_" + +#: src/plugins/fbchat/xmppfb.py:132 +#: src/yahoo/yahoobuddy.py:164 +#: src/yahoo/yahoobuddy.py:181 +msgid "Links:" +msgstr "|_1|\\||<2:" + +#: src/plugins/feed_trends/feed_trends.py:702 +msgid "Trending News" +msgstr "7|23|\\||)1|\\|' |\\|3\\/\\/2" + +#: src/plugins/feed_trends/feed_trends.py:973 +msgid "read more..." +msgstr "|234|) |\\/|0|23..." + +#: src/plugins/feed_trends/feed_trends.py:1010 +msgid "Feed Trends Debug" +msgstr "|=33|) 7|23|\\||)2 |)3|3|_|6" + +#: src/plugins/feed_trends/geo_trends.py:259 +msgid "Featured Content" +msgstr "|=347|_||23|) (0|\\|73|\\|7" + +#: src/plugins/feed_trends/res/trend_details.tenjin:7 +msgid "get address" +msgstr "637 4|)|)|2355" + +#: src/plugins/feed_trends/res/trend_details.tenjin:12 +msgid "phone number" +msgstr "P#0|\\|3 |\\||_||\\/||33|2" + +#: src/plugins/feed_trends/res/trend_details.tenjin:25 +msgid "reviews" +msgstr "|23\\/13\\/\\/2" + +#: src/plugins/linkedin/LinkedInAccount.py:66 +msgid "Checking now..." +msgstr "(#3(|<1|\\|' |\\|0\\/\\/..." + +#: src/plugins/linkedin/LinkedInAccount.py:82 +#: src/plugins/twitter/twitter.py:214 +msgid "Home" +msgstr "#0|\\/|3" + +#: src/plugins/linkedin/LinkedInAccount.py:83 +msgid "Inbox" +msgstr "1|\\||30><" + +#: src/plugins/linkedin/LinkedInAccount.py:85 +#: src/plugins/twitter/twitter.py:215 +msgid "Profile" +msgstr "P|20|=1|_3" + +#: src/plugins/linkedin/LinkedInAccount.py:176 +msgid "API request limit has been reached." +msgstr "4P1 |23Q|_|357 |_1|\\/|17 #42 |333|\\| |234(#3|)." + +#: src/plugins/linkedin/LinkedInAccount.py:378 +#: src/plugins/myspace/MyspaceAccount.py:334 +msgid "Please set your computer clock to the correct time / timezone." +msgstr "P|_3453 537 '/0 (0|\\/|P|_|73|2 (|_0(|< 2 73# (0|2|23(7 71|\\/|3 / 71|\\/|320|\\|3." + +#: src/plugins/linkedin/LinkedInObjects.py:138 +msgid "Like! (via http://digsby.com)" +msgstr "\\/\\/|_||3! (via http://digsby.com)" + +#: src/plugins/linkedin/LinkedInObjects.py:139 +msgid "Dislike! (via http://digsby.com)" +msgstr "|)15|_1|<3! (via http://digsby.com)" + +#: src/plugins/linkedin/LinkedInObjects.py:294 +#, python-format +msgid "answered the question \"%s\"" +msgstr "4|\\|5\\/\\/3|23|) 73# Q|_|35710|\\| \"%s\"" + +# LATERMARK: %s replacement, fragment, formatting +#: src/plugins/linkedin/LinkedInObjects.py:356 +#: src/plugins/linkedin/LinkedInObjects.py:435 +#, python-format +msgid "%s and " +msgstr "%s 4|\\||) " + +#: src/plugins/linkedin/LinkedInObjects.py:360 +#, python-format +msgid "is now connected to %s" +msgstr "|3 |\\|0\\/\\/ (0|\\||\\|3(73|) 2 %s" + +#: src/plugins/linkedin/LinkedInObjects.py:371 +#: src/plugins/linkedin/res/ncon.tenjin:1 +msgid "is now a connection" +msgstr "|3 |\\|0\\/\\/ @ (0|\\||\\|3(710|\\|" + +#: src/plugins/linkedin/LinkedInObjects.py:385 +msgid "joined LinkedIn" +msgstr "J01|\\|3|) |_1|\\||<3|)1|\\|" + +#: src/plugins/linkedin/LinkedInObjects.py:412 +msgid "posted a job: {position_title:s} at {company_name:s}" +msgstr "P0573|) 4 J0|3: {position_title:s} @ {company_name:s}" + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:439 +#, python-format +msgid "is now a member of %s" +msgstr "|3 |\\|0\\/\\/ @ |\\/|3|\\/||33|2 0|= %s" + +# MARK: Fragment +#: src/plugins/linkedin/LinkedInObjects.py:459 +#: src/plugins/linkedin/res/picu.tenjin:1 +msgid "has a new profile photo" +msgstr "#42 @ |\\|3\\/\\/ P|20|=1|_3 P#070" + +# MARK: Fragmnet, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:498 +#, python-format +msgid "has recommended %s" +msgstr "#42 |23(0|\\/||\\/|3|\\||)3|) %s" + +# MARK: Fragment, %s replacemnet +#: src/plugins/linkedin/LinkedInObjects.py:500 +#, python-format +msgid "was recommended by %s" +msgstr "\\/\\/|_|2 |23(0|\\/||\\/|3|\\||)3|) |3'/ %s" + +# MARK: Fragment +#: src/plugins/linkedin/LinkedInObjects.py:512 +msgid "has an updated profile" +msgstr "#42 |\\| |_|P|)473|) P|20|=1|_3" + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:536 +#, python-format +msgid "asked a question: %s" +msgstr "45|<3|) @ Q|_|35710|\\|: %s" + +#: src/plugins/linkedin/actions.yaml:8 +msgid "C&ontacts" +msgstr "(0|\\|74(72" + +#: src/plugins/linkedin/info.yaml:0 +msgid "LinkedIn" +msgstr "|_1|\\||<3|)1|\\|" + +#: src/plugins/linkedin/info.yaml:46 +msgid "LinkedIn Newsfeed" +msgstr "|\\/|'/5P4(3 |\\|3\\/\\/5|=33|)" + +#: src/plugins/linkedin/ligui.py:28 +msgid "Post achievements to my LinkedIn Network" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ |_1|\\||<3|)1|\\| |\\|37\\/\\/0|2|<" + +# MARK: Fragment +#: src/plugins/linkedin/res/ccem.tenjin:1 +msgid "joined" +msgstr "J01|\\|3|)" + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/res/conn.tenjin:1 +msgid "is now connected to" +msgstr "|3 |\\|0\\/\\/ (0|\\||\\|3(73|) 2" + +#: src/plugins/linkedin/res/content.tenjin:11 +msgid "Network Updates" +msgstr "(#3(|< 4 |_|P|)4732" + +#: src/plugins/linkedin/res/content.tenjin:14 +msgid "No news today." +msgstr "|\\|0 |\\|3\\/\\/2 70|)4'/." + +# MARK: Fragment, Should exist? +#: src/plugins/linkedin/res/dislikes.tenjin:15 +#: src/plugins/linkedin/res/dislikes.tenjin:33 +#: src/plugins/myspace/res/dislikes.tenjin:15 +#: src/plugins/myspace/res/dislikes.tenjin:30 +#: src/plugins/myspace/res/likes.tenjin:15 +#: src/plugins/myspace/res/likes.tenjin:29 +msgid "and" +msgstr "|\\|" + +# MARK: Fragment +#: src/plugins/linkedin/res/dislikes.tenjin:16 +#: src/plugins/linkedin/res/dislikes.tenjin:34 +#: src/plugins/myspace/res/dislikes.tenjin:17 +#: src/plugins/myspace/res/dislikes.tenjin:31 +#: src/plugins/myspace/res/likes.tenjin:16 +#: src/plugins/myspace/res/likes.tenjin:30 +msgid "other" +msgid_plural "others" +msgstr[0] "07#3|2" +msgstr[1] "07#3|22" + +#: src/plugins/linkedin/res/dislikes.tenjin:18 +#: src/plugins/myspace/res/dislikes.tenjin:19 +#: src/plugins/myspace/res/dislikes.tenjin:33 +msgid "disliked this" +msgstr "|)15|_1|<3|) |)15" + +# MARK: Fragment +#: src/plugins/linkedin/res/jgrp.tenjin:1 +msgid "has joined" +msgstr "#42 J01|\\|3|)" + +# MARK: Fragment, I don't know what this is +#: src/plugins/linkedin/res/jobp.tenjin:1 +msgid "at" +msgstr "@ " + +# Mark: Fragment, Multiple %s replacements +#: src/plugins/linkedin/res/jobp.tenjin:1 +msgid "posted a job" +msgstr "P0573|) @ J0|3" + +#: src/plugins/linkedin/res/like_dislike_link.tenjin:18 +#: src/plugins/myspace/res/like_dislike_link.tenjin:15 +msgid "Liked" +msgstr "|_1|<3|)" + +#: src/plugins/msim/MSIMConversation.py:35 +msgid "{name} zapped you" +msgstr "{name} 24PP3|) J00" + +#: src/plugins/msim/MSIMConversation.py:36 +msgid "You zapped {name}" +msgstr "J00 P3\\/\\/3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:37 +msgid "Zap" +msgstr "P3\\/\\/" + +#: src/plugins/msim/MSIMConversation.py:41 +msgid "{name} whacked you" +msgstr "{name} \\/\\/#4(|<3|) J00" + +#: src/plugins/msim/MSIMConversation.py:42 +msgid "You whacked {name}" +msgstr "J00 \\/\\/#4(|<3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:43 +msgid "Whack" +msgstr "\\/\\/#4(|<" + +#: src/plugins/msim/MSIMConversation.py:47 +msgid "{name} torched you" +msgstr "{name} 70|2(|-|3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:48 +msgid "You torched {name}" +msgstr "J00 70|2(#3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:49 +msgid "Torch" +msgstr "70|2(#" + +#: src/plugins/msim/MSIMConversation.py:53 +msgid "{name} smooched you" +msgstr "{name} 5|\\/|00(|-|3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:54 +msgid "You smooched {name}" +msgstr "J00 5|\\/|00(#3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:55 +msgid "Smooch" +msgstr "5|\\/|00(#" + +#: src/plugins/msim/MSIMConversation.py:59 +msgid "{name} slapped you" +msgstr "{name} 5|_4PP3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:60 +msgid "You slapped {name}" +msgstr "J00 5|_4PP3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:61 +msgid "Slap" +msgstr "5|_4P" + +#: src/plugins/msim/MSIMConversation.py:65 +msgid "{name} goosed you" +msgstr "{name} 5|_|P|2153 |3|_|753(|<53|) |_|!" + +#: src/plugins/msim/MSIMConversation.py:66 +msgid "You goosed {name}" +msgstr "J00 60053|) {name}" + +#: src/plugins/msim/MSIMConversation.py:67 +msgid "Goose" +msgstr "60053" + +#: src/plugins/msim/MSIMConversation.py:71 +msgid "{name} high-fived you" +msgstr "{name} |-|16|-| |=1\\/3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:72 +msgid "You high-fived {name}" +msgstr "J00 #16#-|=1\\/3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:73 +msgid "High-Five" +msgstr "#16#-|=1\\/3" + +#: src/plugins/msim/MSIMConversation.py:77 +msgid "{name} punk'd you" +msgstr "{name} P|_||\\||<3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:78 +msgid "You punk'd {name}" +msgstr "J00 7|20|_|_3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:79 +msgid "Punk" +msgstr "P|_||\\||<" + +#: src/plugins/msim/MSIMConversation.py:83 +msgid "{name} raspberry'd you" +msgstr "{name} |245P|33|2|2Y'|) |_|" + +#: src/plugins/msim/MSIMConversation.py:84 +msgid "You raspberry'd {name}" +msgstr "J00 |245P|33|2|2'/'|) {name}" + +#: src/plugins/msim/MSIMConversation.py:85 +msgid "Raspberry" +msgstr "|245P|33|2|2'/" + +#: src/plugins/msim/info.yaml:0 +msgid "MySpace IM" +msgstr "|\\/|'/5P4(3 1|\\/|" + +#: src/plugins/msim/info.yaml:4 +msgid "MySpaceIM" +msgstr "|\\/|'/5P4(31|\\/|" + +#: src/plugins/msim/myspacegui/editstatus.py:6 +msgid "MySpace Status" +msgstr "|\\/|'/5P4(3 5747|_|5" + +#: src/plugins/msim/myspacegui/privacy.py:20 +msgid "Everyone" +msgstr "3\\/3|2Y0|\\|3" + +#: src/plugins/msim/myspacegui/privacy.py:21 +msgid "Only people on my contact list" +msgstr "0|\\||_'/ P30P|_3 0|\\| |\\/|'/ (0|\\|74(7 |_157" + +#: src/plugins/msim/myspacegui/privacy.py:22 +msgid "No one" +msgstr "|\\|0" + +#: src/plugins/msim/myspacegui/privacy.py:63 +msgid "Only people on my contact list can see my status" +msgstr "0|\\||_'/ P30P|_3 0|\\| |\\/|'/ (0|\\|74(7 |_157 (4|\\| 533 |\\/|'/ 5747|_|5" + +#: src/plugins/msim/myspacegui/privacy.py:67 +msgid "Only people on my contact list can send me messages" +msgstr "0|\\||_'/ P30P|_3 0|\\| |\\/|'/ (0|\\|74(7 |_157 (4|\\| 53|\\||) |\\/|3 |\\/|3554632" + +#: src/plugins/msim/myspacegui/privacy.py:74 +msgid "When I'm offline, receive and store messages from:" +msgstr "\\/\\/#3|\\| 1'|\\/| 0|=|=|_1|\\|3, |23c31\\/3 |\\| 570|23 |\\/|3554632 |=|20|\\/|:" + +#: src/plugins/msim/myspacegui/privacy.py:80 +msgid "Offline Messages" +msgstr "0|=|=|_1|\\|3 |\\/|3554632" + +#: src/plugins/msim/myspacegui/prompts.py:8 +msgid "Would you like to automatically add your MySpace friends to your MySpace IM contact list?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 4|_|70|\\/|471(4|_|_'/ 4|)|) '/0 |\\/|'/5P4(3 |=|213|\\||)2 2 '/0 |\\/|'/5P4(3 1|\\/| (0|\\|74(7 |_157?" + +#: src/plugins/msim/myspacegui/prompts.py:9 +msgid "Add Top Friends" +msgstr "4|)|) 70P |=|213|\\||)2" + +#: src/plugins/msim/myspacegui/prompts.py:10 +msgid "Add All Friends" +msgstr "4|)|) 4|_|_ |=|213|\\||)2" + +#: src/plugins/msim/myspacegui/prompts.py:13 +msgid "Add Friends" +msgstr "4|)|) |=|213|\\||)2" + +#: src/plugins/msim/res/content.tenjin:20 +msgid "IM Name" +msgstr "1|\\/| |\\|4|\\/|3" + +#: src/plugins/msim/res/content.tenjin:21 +msgid "Display Name" +msgstr "|)15P|_4'/ |\\|4|\\/|3:" + +#: src/plugins/msim/res/content.tenjin:23 +msgid "Real Name" +msgstr "|234|_ |\\|4|\\/|3" + +#: src/plugins/msim/res/content.tenjin:24 +msgid "Age" +msgstr "463" + +#: src/plugins/msim/res/content.tenjin:27 +msgid "IM Headline" +msgstr "1|\\/| #34|)|_1|\\|3" + +# MARK: Fragment +#: src/plugins/myspace/MyspaceAccount.py:557 +msgid "added" +msgstr "4|)|)3|)" + +# MARK: %s replacement +#: src/plugins/myspace/MyspaceAccount.py:558 +#, python-format +msgid "%s account" +msgstr "%s 4((0|_||\\|7" + +# MARK: Fragment +#: src/plugins/myspace/MyspaceAccount.py:559 +msgid "to" +msgstr "2" + +#: src/plugins/myspace/MyspaceProtocol.py:29 +#: src/plugins/myspace/msgui.py:83 +msgid "Birthdays" +msgstr "|31|27#|)4'/5" + +#: src/plugins/myspace/MyspaceProtocol.py:30 +#: src/plugins/myspace/msgui.py:73 +msgid "Blog Comments" +msgstr "|3|_06 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:31 +#: src/plugins/myspace/msgui.py:74 +msgid "Blog Subscriptions" +msgstr "|3|_06 5|_||35(|21P710|\\|2" + +#: src/plugins/myspace/MyspaceProtocol.py:32 +msgid "Comments" +msgstr "(0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:35 +#: src/plugins/myspace/msgui.py:81 +msgid "Group Notifications" +msgstr "6|20|_|P |\\|071|=1(4710|\\|2" + +#: src/plugins/myspace/MyspaceProtocol.py:36 +#: src/plugins/myspace/msgui.py:78 +msgid "Photo Tag Approvals" +msgstr "P#070 746 4PP|20\\/4|_2" + +#: src/plugins/myspace/MyspaceProtocol.py:37 +#: src/plugins/myspace/msgui.py:75 +msgid "Picture Comments" +msgstr "P1(7|_||23 C0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:38 +#: src/plugins/myspace/msgui.py:82 +msgid "Recently Added Friends" +msgstr "|23(3|\\|7|_'/ 4|)|)3|) |=|213|\\||)2" + +#: src/plugins/myspace/MyspaceProtocol.py:39 +#: src/plugins/myspace/msgui.py:80 +msgid "Video Comments" +msgstr "\\/1|)30 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:74 +msgid "Mail ({mailcount})" +msgstr "|\\/|41|_ ({mailcount})" + +#: src/plugins/myspace/MyspaceProtocol.py:395 +#: src/plugins/myspace/notifications.yaml:9 +msgid "MySpace Newsfeed" +msgstr "|\\/|'/5P4(3 |\\|3\\/\\/5|=33|)" + +#: src/plugins/myspace/MyspaceProtocol.py:432 +msgid "MySpace Error" +msgstr "|\\/|'/5P4(3 3|2|20|2" + +#: src/plugins/myspace/MyspaceProtocol.py:434 +msgid "Digsby does not have permission to perform this action." +msgstr "|)165|3'/ |)032 |\\|07 #4\\/3 P3|2|\\/|15510|\\| 2 P3|2|=0|2|\\/| |)12 4(710|\\|." + +#: src/plugins/myspace/MyspaceProtocol.py:566 +msgid "MySpace Alerts" +msgstr "|\\/|'/5P4(3 4|_3|272" + +#: src/plugins/myspace/MyspaceProtocol.py:567 +#, python-format +msgid "You have new %s" +msgstr "J00 #4\\/3 |\\|3\\/\\/ %s" + +#: src/plugins/myspace/MyspaceProtocol.py:684 +msgid "Activity stream item not found. It may have been removed by its creator." +msgstr "4(71\\/17'/ 57|234|\\/| 173|\\/| |\\|07 |=0|_||\\||). 17 |\\/|4Y #4\\/3 |333|\\| |23|\\/|0\\/3|) |3'/ 172 (|23470|2." + +#: src/plugins/myspace/actions.yaml:13 +msgid "&Post Bulletin" +msgstr "&P057 |3|_||_|_371|\\|" + +#: src/plugins/myspace/info.yaml:63 +msgid "Show Newsfeed" +msgstr "5#0\\/\\/ |\\|3\\/\\/5|=33|)" + +#: src/plugins/myspace/msgui.py:24 +msgid "Post achievements to my Activity Stream" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ 4(71\\/17'/ 57|234|\\/|" + +#: src/plugins/myspace/msgui.py:38 +msgid "Show Newsfeed:" +msgstr "5#0\\/\\/ |\\|3\\/\\/5|=33|):" + +#: src/plugins/myspace/msgui.py:42 +msgid "Status Updates" +msgstr "5747|_|2 |_|P|)4732" + +#: src/plugins/myspace/msgui.py:43 +msgid "Friend Updates" +msgstr "|=|213|\\||) |_|P|)4732" + +#: src/plugins/myspace/msgui.py:44 +msgid "Blog/Forum Posts" +msgstr "|3|_06/|=0|2|_||\\/| P0572" + +#: src/plugins/myspace/msgui.py:45 +msgid "Group Updates" +msgstr "6|20|_|P |_|P|)4732" + +#: src/plugins/myspace/msgui.py:46 +msgid "Photos" +msgstr "P#0702" + +#: src/plugins/myspace/msgui.py:47 +msgid "Music" +msgstr "|\\/||_|51(" + +#: src/plugins/myspace/msgui.py:48 +msgid "Videos" +msgstr "\\/1|)302" + +#: src/plugins/myspace/msgui.py:50 +msgid "Applications" +msgstr "4PP|_1(4710|\\|2" + +#: src/plugins/myspace/msgui.py:76 +msgid "Event Invites" +msgstr "3\\/3|\\|7 1|\\|\\/1732" + +#: src/plugins/myspace/msgui.py:77 +msgid "Profile Comments" +msgstr "P|20|=1|_3 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/notifications.yaml:1 +msgid "MySpace Alert" +msgstr "|\\/|'/5P4(3 4|_3|272" + +#: src/plugins/myspace/objects.py:301 +msgid "Private user" +msgstr "P|21\\/473 |_|53|2" + +#: src/plugins/myspace/objects.py:355 +msgid "Like! (via http://lnk.ms/C5dls)" +msgstr "\\/\\/|_||3! (via http://lnk.ms/C5dls)" + +#: src/plugins/myspace/objects.py:356 +msgid "Dislike! (via http://lnk.ms/C5dls)" +msgstr "|)15|_1|<3! (via http://lnk.ms/C5dls)" + +#: src/plugins/myspace/res/content.tenjin:15 +msgid "Activity Stream" +msgstr "4(71\\/17'/ 57|234|\\/|" + +#: src/plugins/myspace/res/indicators.tenjin:6 +msgid "New Mail" +msgstr "|\\|3\\/\\/ |\\/|41|_" + +#: src/plugins/myspace/res/indicators.tenjin:12 +msgid "New Birthdays" +msgstr "|\\|3\\/\\/ |31|27#|)4'/5" + +#: src/plugins/myspace/res/indicators.tenjin:19 +msgid "New Blog Comments" +msgstr "|\\|3\\/\\/ |3|_06 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/res/indicators.tenjin:26 +msgid "New Blog Subscriptions" +msgstr "|\\|3\\/\\/ |3|_06 5|_||35(|21P710|\\|2" + +#: src/plugins/myspace/res/indicators.tenjin:33 +msgid "New Comments" +msgstr "|\\|3\\/\\/ (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/res/indicators.tenjin:40 +msgid "New Event Invitations" +msgstr "|\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/174710|\\|2" + +#: src/plugins/myspace/res/indicators.tenjin:47 +msgid "New Friend Requests" +msgstr "|\\|3\\/\\/ |=|213|\\||) |239|_|3572" + +#: src/plugins/myspace/res/indicators.tenjin:54 +msgid "New Group Notifications" +msgstr "|\\|3\\/\\/ 6|20|_|P |\\|071|=1(4710|\\|2" + +#: src/plugins/myspace/res/indicators.tenjin:61 +msgid "New Photo Tag Approvals" +msgstr "|\\|3\\/\\/ P#070 746 4PP|20\\/4|_2" + +#: src/plugins/myspace/res/indicators.tenjin:68 +msgid "New Picture Comments" +msgstr "|\\|3\\/\\/ P1(7|_||23 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/res/indicators.tenjin:75 +msgid "New Recently Added Friends" +msgstr "|\\|3\\/\\/ |23(3|\\|7|_'/ 4|)|)3|) |=|213|\\||)2" + +#: src/plugins/myspace/res/indicators.tenjin:82 +msgid "New Video Comments" +msgstr "|\\|3\\/\\/ \\/1|)30 (0|\\/||\\/|3|\\|72" + +# MARK: Fragment +#: src/plugins/myspace/res/likes.tenjin:18 +#: src/plugins/myspace/res/likes.tenjin:32 +msgid "liked this" +msgstr "|_1|<3|) |)15" + +#: src/plugins/nowplaying/nowplaying.py:29 +msgid "Listening to music" +msgstr "|_1573|\\|1|\\|' 2 |\\/||_|51(" + +#: src/plugins/nowplaying/nowplaying.py:182 +msgid "Listening To..." +msgstr "|_1573|\\|1|\\|' 2 |\\/||_|51(" + +#: src/plugins/provider_aol/aol_sp.py:79 +msgid "Password should be 8 characters or less." +msgstr "P455\\/\\/0|2|) 5#0|_||_|) |3 8 (#4|24(73|22 0|2 |_355." + +#: src/plugins/provider_aol/info.yaml:5 +msgid "AOL/AIM Account" +msgstr "40|_/41|\\/| 4((0|_||\\|72" + +#: src/plugins/provider_aol/info.yaml:13 +#: src/plugins/provider_aol/info.yaml:56 +#: src/plugins/provider_aol/info.yaml:73 +msgid "Screen Name" +msgstr "5(|233|\\| |\\|4|\\/|3" + +#: src/plugins/provider_aol/info.yaml:63 +msgid "AOL Mail" +msgstr "40|_ |\\/|41|_" + +#: src/plugins/provider_aol/info.yaml:72 +msgid "AIM" +msgstr "41|\\/|" + +#: src/plugins/provider_aol/info.yaml:99 +msgid "ICQ Account" +msgstr "1(Q 4((0|_||\\|7" + +#: src/plugins/provider_aol/info.yaml:109 +msgid "UIN or Email" +msgstr "|_|1|\\| 0|2 3|\\/|41|_" + +#: src/plugins/provider_aol/info.yaml:117 +#: src/plugins/provider_jabber/info.yaml:25 +msgid "Chat" +msgstr "(#47" + +#: src/plugins/provider_aol/info.yaml:141 +msgid "Not Available" +msgstr "|\\|07 4\\/41|_4|3|_3" + +#: src/plugins/provider_aol/info.yaml:143 +msgid "ICQ Number" +msgstr "1(Q |\\||_||\\/||33|2" + +#: src/plugins/provider_facebook/info.yaml:0 +#: src/plugins/provider_facebook/info.yaml:10 +msgid "Facebook Account" +msgstr "|=4(3|300|< 4((0|_||\\|7" + +#: src/plugins/provider_jabber/info.yaml:5 +msgid "Jabber Account" +msgstr "J4|3|33|2 4((0|_||\\|72" + +#: src/plugins/provider_jabber/info.yaml:15 +#: src/plugins/provider_jabber/info.yaml:28 +msgid "Jabber ID" +msgstr "J4|3|33|2 1||)" + +#: src/plugins/provider_jabber/jabber_gui.py:33 +msgid "Use TLS if Possible" +msgstr "|_|53 7|_5 1|= P0551|3|_3" + +#: src/plugins/provider_jabber/jabber_gui.py:34 +msgid "Require TLS" +msgstr "|23Q|_|1|23 7|_5" + +#: src/plugins/provider_jabber/jabber_gui.py:35 +msgid "Force SSL" +msgstr "|=0|2(3 55|_" + +# MARK: Should be translated? +#: src/plugins/provider_jabber/jabber_gui.py:36 +msgid "No Encryption" +msgstr "|\\|0 3|\\|(|21P710|\\|" + +#: src/plugins/provider_jabber/jabber_gui.py:50 +msgid "Ignore SSL Warnings" +msgstr "16|\\|0|23 55|_ \\/\\/4|2|\\|1|\\|62" + +#: src/plugins/provider_jabber/jabber_gui.py:54 +msgid "Allow Plaintext Login" +msgstr "4|_|_0\\/\\/ P|_41|\\|73><7 |_061|\\|" + +#: src/plugins/provider_linkedin/info.yaml:0 +msgid "LinkedIn Account" +msgstr "|_1|\\||<3|)1|\\| 4((0|_||\\|7" + +#: src/plugins/provider_myspace/info.yaml:0 +msgid "MySpace Account" +msgstr "|\\/|'/5P4(3 4((0|_||\\|72..." + +#: src/plugins/provider_twitter/info.yaml:0 +msgid "Twitter Account" +msgstr "7\\/\\/1773|2 4((0|_||\\|7" + +#: src/plugins/provider_twitter/info.yaml:10 +#: src/plugins/twitter/info.yaml:10 +msgid "Twitter ID" +msgstr "7\\/\\/1773|2 1 |)" + +#: src/plugins/provider_windows_live/info.yaml:1 +msgid "Windows Live" +msgstr "\\/\\/1|\\||)0\\/\\/2 |_1\\/3" + +#: src/plugins/provider_windows_live/info.yaml:10 +msgid "Windows Live Account" +msgstr "\\/\\/1|\\||)0\\/\\/2 |_1\\/3 4((0|_||\\|7" + +#: src/plugins/provider_windows_live/info.yaml:33 +msgid "Busy" +msgstr "|3|_|5'/" + +#: src/plugins/provider_windows_live/info.yaml:54 +msgid "Live Messenger" +msgstr "|_1\\/3 |\\/|355463|2" + +#: src/plugins/provider_windows_live/info.yaml:77 +msgid "Hotmail" +msgstr "|-|07|\\/|41|_" + +#: src/plugins/provider_windows_live/wl_sp.py:17 +msgid "Password should be 16 characters or less." +msgstr "P455\\/\\/0|2|) 5#0|_||_|) |3 16 (#4|24(73|22 0|2 |_355." + +#: src/plugins/provider_windows_live/wl_sp.py:22 +msgid "You can't have your password as your display name." +msgstr "J00 (4|\\|'7 #4\\/3 '/0 P455\\/\\/0|2|) 42 '/0 |)15P|_4'/ |\\|4|\\/|3." + +#: src/plugins/provider_yahoo/info.yaml:0 +msgid "Yahoo Account" +msgstr "'/4#00 4((0|_||\\|7" + +#: src/plugins/researchdriver/researchtoast.py:30 +msgid "Help Digsby Stay Free" +msgstr "#3|_P |)165|3'/ 574'/ |=|233" + +#: src/plugins/researchdriver/researchtoast.py:32 +msgid "You are helping Digsby stay free by allowing Digsby to use your PC's idle time." +msgstr "J00 12 #3|_P1|\\|' |)165|3'/ 574'/ |=|233 |3'/ 4|_|_0\\/\\/1|\\|' |)165b'/ 2 |_|53 '/0 P('2 1|)|_3 71|\\/|3." + +#: src/plugins/twitter/info.yaml:0 +msgid "Twitter" +msgstr "7\\/\\/1773|2" + +#: src/plugins/twitter/info.yaml:3 +msgid "Timeline" +msgstr "71|\\/||_1|\\|3" + +#: src/plugins/twitter/notifications.yaml:1 +msgid "Twitter Update" +msgstr "7\\/\\/1773|2 |_|P|)473" + +#: src/plugins/twitter/notifications.yaml:9 +msgid "Twitter Direct Message" +msgstr "7\\/\\/1773|2 |)1|23(7 |\\/|355463" + +#: src/plugins/twitter/res/alerts.tenjin:6 +msgid "Search: " +msgstr "534|2(#" + +# MARK: Partial +#: src/plugins/twitter/res/alerts.tenjin:8 +msgid "Group: " +msgstr "6|20|_|P:" + +#: src/plugins/twitter/res/alerts.tenjin:25 +msgid "No alerts" +msgstr "|\\|0 4|_3|272:" + +#: src/plugins/twitter/res/content.tenjin:34 +msgid "Trending Topics" +msgstr "7|23|\\||)1|\\|' 70P1(2:" + +#: src/plugins/twitter/res/status.tenjin:20 +msgid "What are you doing?" +msgstr "\\/\\/07 12 J00 |)01|\\|'?" + +#: src/plugins/twitter/res/tweet.tenjin:80 +msgid "Share" +msgstr "5#4|23" + +#: src/plugins/twitter/twgui.py:31 +msgid "Follow Digsby on Twitter" +msgstr "|=0|_|_0\\/\\/ |)165|3'/ 0|\\| 7\\/\\/1773|2" + +#: src/plugins/twitter/twgui.py:37 +msgid "Post achievements to my feed" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ |=33|)" + +#: src/plugins/twitter/twitter.py:216 +msgid "Followers" +msgstr "|=0|_|_0\\/\\/3|22" + +#: src/plugins/twitter/twitter.py:217 +msgid "Following" +msgstr "|=0|_|_0\\/\\/1|\\|'" + +#: src/plugins/twitter/twitter.py:606 +#: src/plugins/twitter/twitter_gui.py:702 +msgid "Favorites" +msgstr "|=4\\/0|21732" + +#: src/plugins/twitter/twitter.py:607 +#: src/plugins/twitter/twitter_gui.py:703 +msgid "History" +msgstr "#1570|2'/" + +#: src/plugins/twitter/twitter.py:851 +msgid "Reply" +msgstr "|23P|_'/" + +#: src/plugins/twitter/twitter.py:852 +msgid "Retweet" +msgstr "|237\\/\\/337" + +#: src/plugins/twitter/twitter.py:853 +msgid "Direct" +msgstr "|)1|23(7" + +#: src/plugins/twitter/twitter.py:1207 +msgid "Twitter Error" +msgstr "7\\/\\/1773|2 3|2|23|2" + +#: src/plugins/twitter/twitter.py:1208 +msgid "Send Tweet Failed" +msgstr "53|\\||) 7\\/\\/337 |=41|_3|)" + +#: src/plugins/twitter/twitter_account_gui.py:9 +msgid "Twitter allows for 150 requests per hour. Make sure to leave room for manual updates and other actions." +msgstr "7\\/\\/1773|2 4|_|_0\\/\\/2 4 150 |23Q|_|3572 P3|2 #0|_||2. |\\/|4|<3 5|_||23 2 |_34\\/3 |200|\\/| 4 |\\/|4|\\||_|4|_ |_|P|)4732 |\\| 07#3|2 4(710|\\|2." + +#: src/plugins/twitter/twitter_account_gui.py:14 +msgid "Friends:" +msgstr "|=|213|\\||)2:" + +#: src/plugins/twitter/twitter_account_gui.py:15 +msgid "Mentions:" +msgstr "|\\/|3|\\|710|\\|2:" + +#: src/plugins/twitter/twitter_account_gui.py:16 +msgid "Directs:" +msgstr "|)1|23(72:" + +#: src/plugins/twitter/twitter_account_gui.py:17 +msgid "Searches:" +msgstr "534|2(#35:" + +#: src/plugins/twitter/twitter_account_gui.py:35 +msgid "{mins} minute" +msgid_plural "{mins} minutes" +msgstr[0] "{mins} |\\/|1|\\||_|7 |_||\\|17" +msgstr[1] "{mins} |\\/|1|\\||_|7 |_||\\|175" + +#: src/plugins/twitter/twitter_account_gui.py:38 +msgid "Never" +msgstr "|\\|3\\/3|2" + +#: src/plugins/twitter/twitter_account_gui.py:94 +msgid "Update Frequency:" +msgstr "|_|P|)473 |=|23Q|_|3|\\|('/:" + +#: src/plugins/twitter/twitter_account_gui.py:111 +msgid "Auto-throttle when Twitter lowers the rate limit." +msgstr "4|_|70-7#|2077|_3 \\/\\/#3|\\| 7\\/\\/1773|2 |_0\\/\\/3|22 73# |2473 |_1|\\/|17." + +#: src/plugins/twitter/twitter_account_gui.py:114 +msgid "Server" +msgstr "53|2\\/3|2:" + +#: src/plugins/twitter/twitter_account_gui.py:185 +msgid "{total_updates} / hour" +msgstr "{total_updates} / |-|0|_||2" + +#: src/plugins/twitter/twitter_gui.py:123 +msgid "&Search" +msgstr "&534|2(#" + +#: src/plugins/twitter/twitter_gui.py:127 +msgid "Twitter Search" +msgstr "7\\/\\/1773|2 534|2(#" + +#: src/plugins/twitter/twitter_gui.py:187 +msgid "Search For:" +msgstr "534|2(# |=0|2:" + +#: src/plugins/twitter/twitter_gui.py:195 +msgid "Title:" +msgstr "717|_3:" + +#: src/plugins/twitter/twitter_gui.py:201 +msgid "Trending Topics:" +msgstr "7|23|\\||)1|\\|' 70P1(2:" + +#: src/plugins/twitter/twitter_gui.py:211 +msgid "Search &Options" +msgstr "534|2(# &OP710|\\|2" + +#: src/plugins/twitter/twitter_gui.py:212 +msgid "Merge search results into Timeline view" +msgstr "|\\/|3|263 534|2(# |235|_||_72 1|\\|70 71|\\/|3|_1|\\|3 \\/13\\/\\/" + +#: src/plugins/twitter/twitter_gui.py:214 +msgid "Popup notifications for new search results" +msgstr "P0P|_|P |\\|071|=1(4710|\\|2 4 |\\|3\\/\\/ 534|2(# |235|_||_72" + +#: src/plugins/twitter/twitter_gui.py:355 +msgid "Twitter Group" +msgstr "7\\/\\/1773|2 6|20|_|P" + +#: src/plugins/twitter/twitter_gui.py:408 +msgid "&Group Name" +msgstr "&6|20|_|p |\\|4|\\/|3" + +#: src/plugins/twitter/twitter_gui.py:413 +msgid "Group &Members" +msgstr "6|20|_|P &M3|\\/||33|22" + +#: src/plugins/twitter/twitter_gui.py:426 +msgid "Group &Options" +msgstr "6|20|_|P &0P710|\\|2" + +#: src/plugins/twitter/twitter_gui.py:427 +msgid "&Filter this group's tweets out of the Timeline view" +msgstr "&|=1|_73|2 |)12 6|20|_|P'2 7\\/\\/3372 0|_|7 0|= 73# 71|\\/|3|_1|\\|3 \\/13\\/\\/" + +#: src/plugins/twitter/twitter_gui.py:429 +msgid "Show &popup notifications for this group's tweets" +msgstr "5#0\\/\\/ &P0P|_|P |\\|071|=1(4710|\\|2 4 |)12 6|20|_|P'5 7\\/\\/3372" + +#: src/plugins/twitter/twitter_gui.py:606 +msgid "&Hide Toolbar" +msgstr "&H1|)3 700|_|34|2" + +#: src/plugins/twitter/twitter_gui.py:612 +msgid "Shorten &Links" +msgstr "5#0|273|\\| &L1|\\||<2" + +#: src/plugins/twitter/twitter_gui.py:613 +msgid "Image" +msgstr "1|\\/|463" + +#: src/plugins/twitter/twitter_gui.py:614 +msgid "Shrink" +msgstr "5#|21|\\||<" + +#: src/plugins/twitter/twitter_gui.py:710 +msgid "New Group..." +msgstr "|\\|3\\/\\/ 6|20|_|P" + +#: src/plugins/twitter/twitter_gui.py:711 +msgid "New Search..." +msgstr "|\\|3\\/\\/ 534|2(#..." + +#: src/plugins/twitter/twitter_gui.py:712 +msgid "Edit and Rearrange..." +msgstr "3|)17 |\\| R34|2|24|\\|63" + +#: src/plugins/twitter/twitter_gui.py:1006 +msgid "Shortening..." +msgstr "5#0|273|\\|1|\\|'" + +#: src/plugins/twitter/twitter_gui.py:1102 +msgid "Are you sure you want to upload the image in your clipboard?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |_|P|_04|) 73# 1|\\/|463 1|\\| '/0 (|_1P|304|2|)?" + +#: src/plugins/twitter/twitter_gui.py:1103 +msgid "Image Upload" +msgstr "1|\\/|463 |_|P|_04|)" + +#: src/plugins/twitter/twitter_gui.py:1385 +msgid "&Rearrange" +msgstr "&R34|2|24|\\|63" + +#: src/plugins/twitter/twitter_gui.py:1387 +msgid "Edit and &Rearrange" +msgstr "3|)17 |\\| &R34|2|24|\\|63" + +#: src/plugins/twitter/twitter_gui.py:1390 +msgid "&Mark As Read" +msgstr "&M4|2|< 42 |234|)" + +#: src/plugins/twitter/twitter_gui.py:1393 +msgid "&Adds to Unread Count" +msgstr "&4|)|)2 2 |_||\\||234|) (0|_||\\|7" + +#: src/plugins/twitter/twitter_gui.py:1511 +msgid "Text Size" +msgstr "73><7 5123" + +#: src/plugins/twitter/twitter_gui.py:1601 +msgid "Shorten URLs\tCtrl+L" +msgstr "5#0|273|\\| |_||2|_\tCtrl+L" + +#: src/plugins/twitter/twitter_gui.py:1602 +msgid "Share Picture\tCtrl+P" +msgstr "5#4|23 P1(7|_||23\tCtrl+P" + +#: src/plugins/twitter/twitter_gui.py:1603 +msgid "TweetShrink\tCtrl+S" +msgstr "7\\/\\/3375#|21|\\||<\tCtrl+S" + +#: src/plugins/twitter/twitter_gui.py:1605 +msgid "Set Global Status\tCtrl+G" +msgstr "537 6|_0|34|_ 5747|_|2\tCtrl+G" + +#: src/plugins/twitter/twitter_gui.py:1607 +msgid "Auto Shorten Pasted URLs" +msgstr "4|_|70 5#0|273|\\| P4573|) |_||2|_2" + +#: src/plugins/twitter/twitter_gui.py:1608 +msgid "Auto Upload Pasted Images" +msgstr "4|_|70 |_|P|_04|) P4573|) 1|\\/|4632" + +#: src/plugins/twitter/twitter_gui.py:1609 +msgid "Auto Scroll When At Bottom" +msgstr "4|_|70 5(|20|_|_ \\/\\/#3|\\| 47 |30770|\\/|" + +#: src/plugins/twitter/twitter_gui.py:1730 +msgid "Your tweet has spelling errors." +msgstr "7\\/\\/337 5P3|_|_1|\\|' 3|2|20|22" + +#: src/plugins/twitter/twitter_gui.py:1731 +msgid "Are you sure you'd like to send it?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 |)0 |)12?" + +#: src/plugins/twitter/twitter_gui.py:1735 +msgid "Tweet spelling errors" +msgstr "7\\/\\/337 5P3|_|_1|\\|' 3|2|20|22" + +#: src/plugins/twitter/twitter_gui.py:1738 +msgid "Send Anyways" +msgstr "53|\\||) 4|\\|'/\\/\\/4'/2" + +#: src/plugins/twitter/twitter_gui.py:1996 +msgid "Twitter Groups and Searches" +msgstr "7\\/\\/1773|2 6|20|_|P5 |\\| 534|2(#32" + +#: src/plugins/twitter/twitter_gui.py:2020 +msgid "Refresh Now" +msgstr "|23|=|235# |\\|0\\/\\/" + +#: src/plugins/twitter/twitter_gui.py:2021 +msgid "Mark All As Read" +msgstr "|\\/|4|2|< 4|_|_ 42 |234|)" + +#: src/plugins/twitter/twitter_gui.py:2046 +msgid "Set Status" +msgstr "537 5747|_|5" + +#: src/plugins/twitter/twitter_gui.py:2064 +#, python-format +msgid "Twitter (%s)" +msgstr "7\\/\\/1773|2 (%s)" + +#: src/plugins/twitter/twitter_gui.py:2290 +msgid "&Invite Followers" +msgstr "&1|\\|\\/173 |=0|_|_0\\/\\/3|22" + +#: src/plugins/twitter/twitter_gui.py:2291 +msgid "&No Thanks" +msgstr "&N0 7#4|\\||<2" + +#: src/plugins/twitter/twitter_notifications.py:76 +#, python-format +msgid "Twitter - %(feed_label)s (%(username)s)" +msgstr "7\\/\\/1773|2 - %(feed_label)s (%(username)s)" + +#: src/plugins/twitter/twitter_notifications.py:78 +#, python-format +msgid "Twitter - Group: %(feed_label)s (%(username)s)" +msgstr "7\\/\\/1773|2 - 6|20|_|P: %(feed_label)s (%(username)s)" + +#: src/plugins/twitter/twitter_util.py:83 +msgid "about a minute ago" +msgstr "4|30|_|7 @ |\\/|1|\\||_|73 460" + +#: src/plugins/twitter/twitter_util.py:85 +msgid "about {minutes} minutes ago" +msgstr "4|30|_|7 {minutes} |\\/|1|\\||_|732 460" + +#: src/plugins/twitter/twitter_util.py:87 +msgid "about an hour ago" +msgstr "4|30|_|7 |\\| #0|_||2 460" + +#: src/plugins/twitter/twitter_util.py:89 +msgid "about {hours} hours ago" +msgstr "4|30|_|7 {hours} #0|_||22 460" + +#: src/plugins/twitter/twitter_util.py:91 +msgid "about a day ago" +msgstr "4|30|_|7 @ |)4'/ 460" + +#: src/plugins/twitter/twitter_util.py:93 +msgid "about {days} days ago" +msgstr "4|30|_|7 {days} |)4'/2 460" + +#: src/util/diagnostic.py:584 +#: src/util/diagnostic.py:941 +msgid "Submit Bug Report" +msgstr "5|_||3|\\/|17 |3|_|6 |23p0|27" + +#: src/util/diagnostic.py:587 +msgid "Bug report submitted successfully." +msgstr "|3|_|6 |23P0|27 5|_||3|\\/|1773|) 5|_|((355|=|_||_|_'/." + +#: src/util/diagnostic.py:589 +msgid "Bug report submission failed." +msgstr "|3|_|6 |23P0|27 5|_||3|\\/|15510|\\| |=41|_!" + +#: src/util/diagnostic.py:916 +msgid "Please wait while we process the diagnostic information." +msgstr "P|_3453 \\/\\/417 \\/\\/#1|_3 \\/\\/3 P|20(352 73# |)146|\\|0571( 1|\\||=0|2|\\/|4710|\\|." + +#: src/util/diagnostic.py:917 +msgid "Thanks for your patience!" +msgstr "7#4|\\||<2 4 '/0 P4713|\\|(3!" + +#: src/util/diagnostic.py:919 +msgid "Processing Diagnostic Info" +msgstr "P|20(3551|\\|' |)146|\\|0571( 1|\\||=0" + +#: src/util/diagnostic.py:922 +msgid "There was a problem submitting your bug report." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 5|_||3|\\/|1771|\\|' '/0 (|245# |23P0|27." + +#: src/util/diagnostic.py:923 +msgid "If the problem persists, please email bugs@digsby.com" +msgstr "1|= 73# P|20|3|_3|\\/| P3|251572, P|_3453 3|\\/|41|_ bugs@digsby.com" + +#: src/util/diagnostic.py:930 +msgid "Bug report sent successfully." +msgstr "|3|_|6 |23P0|27 53|\\|7 5|_|((355|=|_||_|_Y." + +#: src/util/httplib2/httplib2.py:343 +#, python-format +msgid "Content purported to be compressed with %s but failed to decompress." +msgstr "(0|\\|73|\\|7 P|_||2P0|273|) 2 |3 (0|\\/|P|23553|) \\/\\/17# %s |3|_|7 |=41|_3|) 2 |)3(0|\\/|P|2355." + +#: src/util/httplib2/httplib2.py:502 +msgid "The challenge doesn't contain a server nonce, or this one is empty." +msgstr "73# (#4|_|_3|\\|63 |)03s|\\|'7 (0|\\|741|\\| @ 53|2\\/3|2 |\\|0|\\|(3, 0|2 |)12 0|\\|3 |3 3|\\/|P7'/." + +#: src/util/httplib2/httplib2.py:946 +msgid "Redirected but the response is missing a Location: header." +msgstr "|23|)1|23(73|) |3|_|7 73# |235P0|\\|53 |3 |\\/|1551|\\|' @ |_0(4710|\\|: #34|)3|2." + +#: src/util/httplib2/httplib2.py:973 +msgid "Redirected more times than rediection_limit allows." +msgstr "|23|)1|23(73|) |\\/|0|23 71|\\/|32 7#4|\\| |23|)13(710|\\|_|_1|\\/|17 4|_|_0\\/\\/2." + +#: src/util/perfmon.py:127 +msgid "" +"A log of the problem has been sent to digsby.com.\n" +"\n" +"Thanks for helping!" +msgstr "" +"@ |_06 0|= 73# P|20|3|_3|\\/| #42 |333|\\| 53|\\|7 2 |)165|3'/.(0|\\/|.\n" +"\n" +"7#4|\\||<2 4 #3|_P1|\\|'!" + +#: src/util/perfmon.py:128 +msgid "Diagnostic Log" +msgstr "|)146|\\|0571( |_06" + +#: src/util/perfmon.py:132 +msgid "There was an error when submitting the diagnostic log." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 \\/\\/#3|\\| 5|_||3|\\/|1771|\\|' 73# |)146|\\|0571( |_06." + +#: src/util/perfmon.py:146 +msgid "Digsby appears to be running slowly." +msgstr "|)165|3'/ |331|\\| 5|_0\\/\\/?" + +#: src/util/perfmon.py:147 +msgid "Do you want to capture diagnostic information and send it to digsby.com?" +msgstr "(4P7|_||2 |)146|\\|0571(2 |\\| 53|\\||) 2 |)165|3'/?" + +#: src/util/perfmon.py:151 +msgid "Digsby CPU Usage" +msgstr "|)165|3'/ (P|_| |_|5463" + +# MARK: Shuold be translated, not actually correct, may not exist in other languages +#: src/util/primitives/strings.py:587 +msgid "aeiou" +msgstr "aeiou" + +# MARK: Fragment, Should exist? +#: src/util/primitives/strings.py:588 +msgid "an" +msgstr "|\\|" + +# MARK: Fragment, I don't know what this is +#: src/util/primitives/strings.py:590 +msgid "a" +msgstr "@ " + +# MARK: Key replacement +#: src/util/primitives/strings.py:592 +#, python-format +msgid "%(article)s %(s)s" +msgstr "%(article)s %(s)s" + +#: src/yahoo/YahooProtocol.py:1163 +msgid "May I add you to my contact list?" +msgstr "|\\/|4'/ 1 4|)|) J00 2 |\\/|'/ (0|\\|74(7 |_157?" + +#: src/yahoo/YahooProtocol.py:1323 +msgid "There was an error modifying stealth settings for {name}." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 |\\/|0|)1|='/1|\\|' 5734|_7# 53771|\\|62 4 {name}" + +#: src/yahoo/login.py:35 +msgid "Bad Password" +msgstr "|34|) P455\\/\\/0|2|)" + +#: src/yahoo/login.py:36 +msgid "There is a security lock on your account. Log in to http://my.yahoo.com and try again." +msgstr "7#3|23 |3 @ 53(|_||217'/ |_0(|< 0|\\| '/0 4((0|_||\\|7. |_06 1|\\| 2 http://my.yahoo.com |\\| 7|2'/ 4641|\\|." + +#: src/yahoo/login.py:37 +msgid "Account Not Set Up" +msgstr "4((0|_||\\|7 |\\|07 537 |_|P" + +#: src/yahoo/login.py:38 +msgid "Bad Username" +msgstr "|34|) |_|53|2|\\|4|\\/|3" + +#: src/yahoo/login.py:39 +msgid "Rate Limit" +msgstr "|2473 |_1|\\/|17" + +#: src/yahoo/yahoobuddy.py:130 +msgid "No Updates" +msgstr "|\\|0 |_|P|)4732" + +#: src/yahoo/yahoobuddy.py:134 +#: src/yahoo/yahoobuddy.py:137 +#: src/yahoo/yahoobuddy.py:151 +#: src/yahoo/yahoobuddy.py:173 +msgid "Yahoo! 360:" +msgstr "'/4#00! 360:" + +#: src/yahoo/yahoobuddy.py:145 +#: src/yahoo/yahoobuddy.py:167 +#: src/yahoo/yahoobuddy.py:174 +#: src/yahoo/yahoobuddy.py:195 +#: src/yahoo/yahoobuddy.py:203 +msgid "Directory URL:" +msgstr "|)1|23(70|2'/ |_||2|_:" + +#: src/yahoo/yahoobuddy.py:152 +msgid "Real Name:" +msgstr "|234|_ |\\|4|\\/|3:" + +#: src/yahoo/yahoobuddy.py:153 +msgid "Nickname:" +msgstr "|\\|1(|<|\\|4|\\/|3:" + +#: src/yahoo/yahoobuddy.py:155 +msgid "Age:" +msgstr "463:" + +#: src/yahoo/yahoobuddy.py:156 +msgid "Sex:" +msgstr "53><:" + +#: src/yahoo/yahoobuddy.py:157 +msgid "Marital Status:" +msgstr "|\\/|4|2174|_ 5747|_|5:" + +#: src/yahoo/yahoobuddy.py:158 +msgid "Occupation:" +msgstr "0((|_|P4710|\\|:" + +#: src/yahoo/yahoobuddy.py:159 +msgid "Email:" +msgstr "3|\\/|41|_:" + +#: src/yahoo/yahoobuddy.py:160 +msgid "Home Page:" +msgstr "#0|\\/|3P463:" + +#: src/yahoo/yahoobuddy.py:161 +#: src/yahoo/yahoobuddy.py:181 +msgid "Hobbies:" +msgstr "|-|0|3|3132:" + +#: src/yahoo/yahoobuddy.py:162 +#: src/yahoo/yahoobuddy.py:181 +msgid "Latest News:" +msgstr "|_47357 |\\|4\\/\\/32" + +#: src/yahoo/yahoobuddy.py:163 +#: src/yahoo/yahoobuddy.py:181 +msgid "Favorite Quote:" +msgstr "|=4\\/0|2173 Q|_|073:" + +#: src/yahoo/yahoobuddy.py:165 +#: src/yahoo/yahoobuddy.py:195 +msgid "Member Since " +msgstr "|\\/|3|\\/||33|2 51|\\|(3" + +#: src/yahoo/yahoobuddy.py:166 +#: src/yahoo/yahoobuddy.py:195 +msgid "Last Update: " +msgstr "|_457 |_|P|)473:" + +#~ msgid "Invite Twitter Followers" +#~ msgstr "1|\\|\\/173 7\\/\\/1773|2 |=0|_|_0\\/\\/3|22" + +# MARK: Fragment +#~ msgid "minutes" +#~ msgstr "|\\/|1|\\||_|732" +# MARKED %s Fragment +#~ msgid " for %s" +#~ msgstr "4 %s" +# MARKED: Fragment +#~ msgid " on monitor " +#~ msgstr "0|\\| |\\//|0|\\|170|2" +# MARK: Fragment, Posible multilingual issue +#~ msgid "from" +#~ msgstr "|=|20|\\/|" +#~ msgid "blog subscriptions" +#~ msgstr "|3|_06 5|_||35(|21P710|\\|2" +#, fuzzy +#~ msgid "\"Available\"], []]" +#~ msgstr "4\\/41|_4|3|_3" + +#~ msgid "" +#~ "Please support Digsby by helping us spread the word!\n" +#~ "Would you like to invite your address book contacts to Digsby?" +#~ msgstr "" +#~ "P|_3453 5|_|PP0|27 |)165|3'/ |3'/ #3|_P1|\\|' |_|2 5P|234|) 73# \\/\\/0|2|)!\n" +#~ "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 1|\\|\\/173 '/0 4|)|)|2355 |300|< (0|\\|74(72 2 |)165|3'/?" + +# MARK: Fragment, %s replacement +#~ msgid "to %s" +#~ msgstr "2 %s" +#~ msgid "&Submit" +#~ msgstr "&5|_||3|\\/|17" +# MARK: %r replacement +#~ msgid "Set to %r" +#~ msgstr "537 2 %r" +# MARK: %s replacement +#~ msgid "&%s Server:" +#~ msgstr "&%s 53|2\\/3|2:" +#~ msgid "Check for new mail every" +#~ msgstr "(#3(|< 4 |\\|3\\/\\/ |\\/|41|_ 3\\/3|2'/" +#~ msgid "Send Invites" +#~ msgstr "53|\\||) 1|\\|\\/1732" +#~ msgid "Sent " +#~ msgstr "53|\\|7 " +# MARK: Formatting, Reproduction +#~ msgid "" +#~ "Please support Digsby by helping us spread the word. Would\n" +#~ "you like to send a direct message to your Twitter followers\n" +#~ "inviting them to Digsby?" +#~ msgstr "" +#~ "P|_3453 5|_|PP0|27 |)165|3'/ |3'/ #3|_P1|\\|' |_|2 5P|234|) 73# \\/\\/0|2|)! \\/\\/0|_||_|)\n" +#~ "J00 \\/\\/|_||3 2 53|\\||) @ |)1|23(7 |\\/|355463 2 '/0 7\\/\\/1773|2 |=0|_|_0\\/\\/3r2\n" +#~ "1|\\|\\/171|\\|' 7#3|\\/| 2 |)165|3'/?" +# MARK: Fragment +#~ msgid "winked at you!" +#~ msgstr "\\/\\/1|\\||<3|) 47 J00!" +# MARK: Fragment +#~ msgid "Enable &pop-up notifications in the " +#~ msgstr "3|\\|4|3|_3 &P0P-|_|P |\\|071|=1(4710|\\|2 1|\\| 73# " +# MARK: Should not be translated... +#~ msgid "test" +#~ msgstr "7357" +#, fuzzy +#~ msgid "'Available']," +#~ msgstr "4\\/41|_4|3|_3" + +#~ msgid "Editing Alerts for {name}" +#~ msgstr "3|)17 4|_3|272 4 {name}" + +#, fuzzy +#~ msgid "'Away']]" +#~ msgstr "4\\/\\/4'/" diff --git a/digsby/devplugins/l33t_language/info.yaml b/digsby/devplugins/l33t_language/info.yaml new file mode 100644 index 0000000..4a91a67 --- /dev/null +++ b/digsby/devplugins/l33t_language/info.yaml @@ -0,0 +1,8 @@ +--- +name: !python/unicode l33t +language: !python/unicode lt +catalog_format: po +domain: !python/unicode digsby +shortname: !python/unicode digsby-i18n-digsby-lt +pot_version: !python/unicode 29983 +type: lang diff --git a/digsby/devplugins/skype/SkyLib.py b/digsby/devplugins/skype/SkyLib.py new file mode 100644 index 0000000..808bc4a --- /dev/null +++ b/digsby/devplugins/skype/SkyLib.py @@ -0,0 +1,3452 @@ +#__LICENSE_GOES_HERE__ + +# -*- coding: utf-8 -*- +# module skylib +from skypekit import * + +class ContactGroup(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "ContactGroup %s" % (self.object_id, ) + module_id = 10 + def OnPropertyChange(self, property_name): pass + TYPE= { + 1 :'ALL_KNOWN_CONTACTS', + 2 :'ALL_BUDDIES', + 3 :'SKYPE_BUDDIES', + 4 :'SKYPEOUT_BUDDIES', + 5 :'ONLINE_BUDDIES', + 6 :'UNKNOWN_OR_PENDINGAUTH_BUDDIES', + 7 :'RECENTLY_CONTACTED_CONTACTS', + 8 :'CONTACTS_WAITING_MY_AUTHORIZATION', + 9 :'CONTACTS_AUTHORIZED_BY_ME', + 10:'CONTACTS_BLOCKED_BY_ME', + 11:'UNGROUPED_BUDDIES', + 12:'CUSTOM_GROUP', + 13:'PROPOSED_SHARED_GROUP', + 14:'SHARED_GROUP', + 15:'EXTERNAL_CONTACTS', + 'ALL_KNOWN_CONTACTS' : 1, + 'ALL_BUDDIES' : 2, + 'SKYPE_BUDDIES' : 3, + 'SKYPEOUT_BUDDIES' : 4, + 'ONLINE_BUDDIES' : 5, + 'UNKNOWN_OR_PENDINGAUTH_BUDDIES' : 6, + 'RECENTLY_CONTACTED_CONTACTS' : 7, + 'CONTACTS_WAITING_MY_AUTHORIZATION' : 8, + 'CONTACTS_AUTHORIZED_BY_ME' : 9, + 'CONTACTS_BLOCKED_BY_ME' :10, + 'UNGROUPED_BUDDIES' :11, + 'CUSTOM_GROUP' :12, + 'PROPOSED_SHARED_GROUP' :13, + 'SHARED_GROUP' :14, + 'EXTERNAL_CONTACTS' :15 + } + + def _Gettype(self): + return ContactGroup.TYPE[self._Property("ZG\233\001]\012",155, True)] + type = property(_Gettype) + propid2label[155] = "type" + def _Getcustom_group_id(self): + return self._Property("ZG\232\001]\012",154, True) + custom_group_id = property(_Getcustom_group_id) + propid2label[154] = "custom_group_id" + def _Getgiven_displayname(self): + return self._Property("ZG\227\001]\012",151, True) + given_displayname = property(_Getgiven_displayname) + propid2label[151] = "given_displayname" + def _Getnrofcontacts(self): + return self._Property("ZG\230\001]\012",152, True) + nrofcontacts = property(_Getnrofcontacts) + propid2label[152] = "nrofcontacts" + def _Getnrofcontacts_online(self): + return self._Property("ZG\231\001]\012",153, True) + nrofcontacts_online = property(_Getnrofcontacts_online) + propid2label[153] = "nrofcontacts_online" + + def GiveDisplayName( + self, + name + ): + request = XCallRequest("ZR\012\001",10,1) + request.AddParm('O',0,self) + request.AddParm('S',1,name) + response = self.transport.Xcall(request) + def Delete(self): + request = XCallRequest("ZR\012\002",10,2) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def GetConversations(self): + request = XCallRequest("ZR\012\003",10,3) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = [module_id2classes[18](oid,self.transport) for oid in response.get(1,[])] + return result + def CanAddConversation( + self, + conversation + ): + request = XCallRequest("ZR\012\004",10,4) + request.AddParm('O',0,self) + request.AddParm('O',1,conversation) + response = self.transport.Xcall(request) + def AddConversation( + self, + conversation + ): + request = XCallRequest("ZR\012\005",10,5) + request.AddParm('O',0,self) + request.AddParm('O',1,conversation) + response = self.transport.Xcall(request) + def CanRemoveConversation(self): + request = XCallRequest("ZR\012\006",10,6) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def RemoveConversation( + self, + conversation + ): + request = XCallRequest("ZR\012\007",10,7) + request.AddParm('O',0,self) + request.AddParm('O',1,conversation) + response = self.transport.Xcall(request) + def OnChangeConversation( + self, + conversation + ): pass + event_handlers[1] = "OnChangeConversationDispatch" + def OnChangeConversationDispatch(self, parms): + cleanparms = module_id2classes[18](parms.get(1),self.transport) + self.OnChangeConversation(cleanparms) + def GetContacts(self): + request = XCallRequest("ZR\012\010",10,8) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = [module_id2classes[2](oid,self.transport) for oid in response.get(1,[])] + return result + def CanAddContact( + self, + contact + ): + request = XCallRequest("ZR\012\011",10,9) + request.AddParm('O',0,self) + request.AddParm('O',1,contact) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def AddContact( + self, + contact + ): + request = XCallRequest("ZR\012\012",10,10) + request.AddParm('O',0,self) + request.AddParm('O',1,contact) + response = self.transport.Xcall(request) + def CanRemoveContact(self): + request = XCallRequest("ZR\012\013",10,11) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def RemoveContact( + self, + contact + ): + request = XCallRequest("ZR\012\014",10,12) + request.AddParm('O',0,self) + request.AddParm('O',1,contact) + response = self.transport.Xcall(request) + def OnChange( + self, + contact + ): pass + event_handlers[2] = "OnChangeDispatch" + def OnChangeDispatch(self, parms): + cleanparms = module_id2classes[2](parms.get(1),self.transport) + self.OnChange(cleanparms) +module_id2classes[10] = ContactGroup + +class Contact(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Contact %s" % (self.object_id, ) + module_id = 2 + def OnPropertyChange(self, property_name): pass + TYPE= {0:'UNRECOGNIZED', 'UNRECOGNIZED':0, 1:'SKYPE', 'SKYPE':1, 2:'PSTN', 'PSTN':2, 3:'EMERGENCY_PSTN', 'EMERGENCY_PSTN':3, 4:'FREE_PSTN', 'FREE_PSTN':4, 5:'UNDISCLOSED_PSTN', 'UNDISCLOSED_PSTN':5, 6:'EXTERNAL', 'EXTERNAL':6} + AUTHLEVEL= {0:'NONE', 'NONE':0, 1:'AUTHORIZED_BY_ME', 'AUTHORIZED_BY_ME':1, 2:'BLOCKED_BY_ME', 'BLOCKED_BY_ME':2} + AVAILABILITY= { + 0 :'UNKNOWN', + 8 :'PENDINGAUTH', + 9 :'BLOCKED', + 11:'BLOCKED_SKYPEOUT', + 10:'SKYPEOUT', + 1 :'OFFLINE', + 12:'OFFLINE_BUT_VM_ABLE', + 13:'OFFLINE_BUT_CF_ABLE', + 2 :'ONLINE', + 3 :'AWAY', + 4 :'NOT_AVAILABLE', + 5 :'DO_NOT_DISTURB', + 7 :'SKYPE_ME', + 6 :'INVISIBLE', + 14:'CONNECTING', + 15:'ONLINE_FROM_MOBILE', + 16:'AWAY_FROM_MOBILE', + 17:'NOT_AVAILABLE_FROM_MOBILE', + 18:'DO_NOT_DISTURB_FROM_MOBILE', + 20:'SKYPE_ME_FROM_MOBILE', + 'UNKNOWN' : 0, + 'PENDINGAUTH' : 8, + 'BLOCKED' : 9, + 'BLOCKED_SKYPEOUT' :11, + 'SKYPEOUT' :10, + 'OFFLINE' : 1, + 'OFFLINE_BUT_VM_ABLE' :12, + 'OFFLINE_BUT_CF_ABLE' :13, + 'ONLINE' : 2, + 'AWAY' : 3, + 'NOT_AVAILABLE' : 4, + 'DO_NOT_DISTURB' : 5, + 'SKYPE_ME' : 7, + 'INVISIBLE' : 6, + 'CONNECTING' :14, + 'ONLINE_FROM_MOBILE' :15, + 'AWAY_FROM_MOBILE' :16, + 'NOT_AVAILABLE_FROM_MOBILE' :17, + 'DO_NOT_DISTURB_FROM_MOBILE' :18, + 'SKYPE_ME_FROM_MOBILE' :20 + } + CAPABILITY= { + 0 :'CAPABILITY_VOICEMAIL', + 1 :'CAPABILITY_SKYPEOUT', + 2 :'CAPABILITY_SKYPEIN', + 3 :'CAPABILITY_CAN_BE_SENT_VM', + 4 :'CAPABILITY_CALL_FORWARD', + 5 :'CAPABILITY_VIDEO', + 6 :'CAPABILITY_TEXT', + 7 :'CAPABILITY_SERVICE_PROVIDER', + 8 :'CAPABILITY_LARGE_CONFERENCE', + 9 :'CAPABILITY_COMMERCIAL_CONTACT', + 10:'CAPABILITY_PSTN_TRANSFER', + 11:'CAPABILITY_TEXT_EVER', + 12:'CAPABILITY_VOICE_EVER', + 13:'CAPABILITY_MOBILE_DEVICE', + 14:'CAPABILITY_PUBLIC_CONTACT', + 'CAPABILITY_VOICEMAIL' : 0, + 'CAPABILITY_SKYPEOUT' : 1, + 'CAPABILITY_SKYPEIN' : 2, + 'CAPABILITY_CAN_BE_SENT_VM' : 3, + 'CAPABILITY_CALL_FORWARD' : 4, + 'CAPABILITY_VIDEO' : 5, + 'CAPABILITY_TEXT' : 6, + 'CAPABILITY_SERVICE_PROVIDER' : 7, + 'CAPABILITY_LARGE_CONFERENCE' : 8, + 'CAPABILITY_COMMERCIAL_CONTACT' : 9, + 'CAPABILITY_PSTN_TRANSFER' :10, + 'CAPABILITY_TEXT_EVER' :11, + 'CAPABILITY_VOICE_EVER' :12, + 'CAPABILITY_MOBILE_DEVICE' :13, + 'CAPABILITY_PUBLIC_CONTACT' :14 + } + CAPABILITYSTATUS= {0:'NO_CAPABILITY', 'NO_CAPABILITY':0, 1:'CAPABILITY_MIXED', 'CAPABILITY_MIXED':1, 2:'CAPABILITY_EXISTS', 'CAPABILITY_EXISTS':2} + + def _Gettype(self): + return Contact.TYPE[self._Property("ZG\312\001]\002",202, True)] + type = property(_Gettype) + propid2label[202] = "type" + def _Getskypename(self): + return self._Property("ZG\004]\002",4, True) + skypename = property(_Getskypename) + propid2label[4] = "skypename" + def _Getpstnnumber(self): + return self._Property("ZG\006]\002",6, True) + pstnnumber = property(_Getpstnnumber) + propid2label[6] = "pstnnumber" + def _Getfullname(self): + return self._Property("ZG\005]\002",5, True) + fullname = property(_Getfullname) + propid2label[5] = "fullname" + def _Getbirthday(self): + return self._Property("ZG\007]\002",7, True) + birthday = property(_Getbirthday) + propid2label[7] = "birthday" + def _Getgender(self): + return self._Property("ZG\010]\002",8, True) + gender = property(_Getgender) + propid2label[8] = "gender" + def _Getlanguages(self): + return self._Property("ZG\011]\002",9, True) + languages = property(_Getlanguages) + propid2label[9] = "languages" + def _Getcountry(self): + return self._Property("ZG\012]\002",10, True) + country = property(_Getcountry) + propid2label[10] = "country" + def _Getprovince(self): + return self._Property("ZG\013]\002",11, True) + province = property(_Getprovince) + propid2label[11] = "province" + def _Getcity(self): + return self._Property("ZG\014]\002",12, True) + city = property(_Getcity) + propid2label[12] = "city" + def _Getphone_home(self): + return self._Property("ZG\015]\002",13, True) + phone_home = property(_Getphone_home) + propid2label[13] = "phone_home" + def _Getphone_office(self): + return self._Property("ZG\016]\002",14, True) + phone_office = property(_Getphone_office) + propid2label[14] = "phone_office" + def _Getphone_mobile(self): + return self._Property("ZG\017]\002",15, True) + phone_mobile = property(_Getphone_mobile) + propid2label[15] = "phone_mobile" + def _Getemails(self): + return self._Property("ZG\020]\002",16, True) + emails = property(_Getemails) + propid2label[16] = "emails" + def _Gethomepage(self): + return self._Property("ZG\021]\002",17, True) + homepage = property(_Gethomepage) + propid2label[17] = "homepage" + def _Getabout(self): + return self._Property("ZG\022]\002",18, True) + about = property(_Getabout) + propid2label[18] = "about" + def _Getavatar_image(self): + return self._Property("ZG%]\002",37, True) + avatar_image = property(_Getavatar_image) + propid2label[37] = "avatar_image" + def _Getmood_text(self): + return self._Property("ZG\032]\002",26, True) + mood_text = property(_Getmood_text) + propid2label[26] = "mood_text" + def _Getrich_mood_text(self): + return self._Property("ZG\315\001]\002",205, True) + rich_mood_text = property(_Getrich_mood_text) + propid2label[205] = "rich_mood_text" + def _Gettimezone(self): + return self._Property("ZG\033]\002",27, True) + timezone = property(_Gettimezone) + propid2label[27] = "timezone" + def _Getcapabilities(self): + return self._Property("ZG$]\002",36, True) + capabilities = property(_Getcapabilities) + propid2label[36] = "capabilities" + def _Getprofile_timestamp(self): + return self._Property("ZG\023]\002",19, True) + profile_timestamp = property(_Getprofile_timestamp) + propid2label[19] = "profile_timestamp" + def _Getnrof_authed_buddies(self): + return self._Property("ZG\034]\002",28, True) + nrof_authed_buddies = property(_Getnrof_authed_buddies) + propid2label[28] = "nrof_authed_buddies" + def _Getipcountry(self): + return self._Property("ZG\035]\002",29, True) + ipcountry = property(_Getipcountry) + propid2label[29] = "ipcountry" + def _Getavatar_timestamp(self): + return self._Property("ZG\266\001]\002",182, True) + avatar_timestamp = property(_Getavatar_timestamp) + propid2label[182] = "avatar_timestamp" + def _Getmood_timestamp(self): + return self._Property("ZG\267\001]\002",183, True) + mood_timestamp = property(_Getmood_timestamp) + propid2label[183] = "mood_timestamp" + def _Getreceived_authrequest(self): + return self._Property("ZG\024]\002",20, True) + received_authrequest = property(_Getreceived_authrequest) + propid2label[20] = "received_authrequest" + def _Getauthreq_timestamp(self): + return self._Property("ZG\031]\002",25, True) + authreq_timestamp = property(_Getauthreq_timestamp) + propid2label[25] = "authreq_timestamp" + def _Getlastonline_timestamp(self): + return self._Property("ZG#]\002",35, True) + lastonline_timestamp = property(_Getlastonline_timestamp) + propid2label[35] = "lastonline_timestamp" + def _Getavailability(self): + return Contact.AVAILABILITY[self._Property("ZG\042]\002",34, True)] + availability = property(_Getavailability) + propid2label[34] = "availability" + def _Getdisplayname(self): + return self._Property("ZG\025]\002",21, True) + displayname = property(_Getdisplayname) + propid2label[21] = "displayname" + def _Getrefreshing(self): + return self._Property("ZG\026]\002",22, True) + refreshing = property(_Getrefreshing) + propid2label[22] = "refreshing" + def _Getgiven_authlevel(self): + return Contact.AUTHLEVEL[self._Property("ZG\027]\002",23, True)] + given_authlevel = property(_Getgiven_authlevel) + propid2label[23] = "given_authlevel" + def _Getgiven_displayname(self): + return self._Property("ZG!]\002",33, True) + given_displayname = property(_Getgiven_displayname) + propid2label[33] = "given_displayname" + def _Getassigned_comment(self): + return self._Property("ZG\264\001]\002",180, True) + assigned_comment = property(_Getassigned_comment) + propid2label[180] = "assigned_comment" + def _Getlastused_timestamp(self): + return self._Property("ZG']\002",39, True) + lastused_timestamp = property(_Getlastused_timestamp) + propid2label[39] = "lastused_timestamp" + def _Getauthrequest_count(self): + return self._Property("ZG)]\002",41, True) + authrequest_count = property(_Getauthrequest_count) + propid2label[41] = "authrequest_count" + def _Getassigned_phone1(self): + return self._Property("ZG\270\001]\002",184, True) + assigned_phone1 = property(_Getassigned_phone1) + propid2label[184] = "assigned_phone1" + def _Getassigned_phone1_label(self): + return self._Property("ZG\271\001]\002",185, True) + assigned_phone1_label = property(_Getassigned_phone1_label) + propid2label[185] = "assigned_phone1_label" + def _Getassigned_phone2(self): + return self._Property("ZG\272\001]\002",186, True) + assigned_phone2 = property(_Getassigned_phone2) + propid2label[186] = "assigned_phone2" + def _Getassigned_phone2_label(self): + return self._Property("ZG\273\001]\002",187, True) + assigned_phone2_label = property(_Getassigned_phone2_label) + propid2label[187] = "assigned_phone2_label" + def _Getassigned_phone3(self): + return self._Property("ZG\274\001]\002",188, True) + assigned_phone3 = property(_Getassigned_phone3) + propid2label[188] = "assigned_phone3" + def _Getassigned_phone3_label(self): + return self._Property("ZG\275\001]\002",189, True) + assigned_phone3_label = property(_Getassigned_phone3_label) + propid2label[189] = "assigned_phone3_label" + + def GetType(self): + request = XCallRequest("ZR\002\001",2,1) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = Contact.TYPE[response.get(1)] + return result + def GetIdentity(self): + request = XCallRequest("ZR\002\002",2,2) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def GetAvatar(self): + request = XCallRequest("ZR\002\004",2,4) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = (response.get(1,False)), + result += (response.get(2,'')), + return result + def IsMemberOf( + self, + group + ): + request = XCallRequest("ZR\002\006",2,6) + request.AddParm('O',0,self) + request.AddParm('O',1,group) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def IsMemberOfHardwiredGroup( + self, + groupType + ): + request = XCallRequest("ZR\002\007",2,7) + request.AddParm('O',0,self) + request.AddParm('e',1,ContactGroup.TYPE[groupType]) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def SetBlocked( + self, + blocked, + abuse + ): + request = XCallRequest("ZR\002\315\002",2,333) + request.AddParm('O',0,self) + request.AddParm('b',1,blocked) + request.AddParm('b',2,abuse) + response = self.transport.Xcall(request) + def IgnoreAuthRequest(self): + request = XCallRequest("ZR\002\025",2,21) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def GiveDisplayName( + self, + name + ): + request = XCallRequest("ZR\002\012",2,10) + request.AddParm('O',0,self) + request.AddParm('S',1,name) + response = self.transport.Xcall(request) + def SetBuddyStatus( + self, + isMyBuddy, + syncAuth + ): + request = XCallRequest("ZR\002\014",2,12) + request.AddParm('O',0,self) + request.AddParm('b',1,isMyBuddy) + request.AddParm('b',2,syncAuth) + response = self.transport.Xcall(request) + def SendAuthRequest( + self, + message + ): + request = XCallRequest("ZR\002\015",2,13) + request.AddParm('O',0,self) + request.AddParm('S',1,message) + response = self.transport.Xcall(request) + def HasAuthorizedMe(self): + request = XCallRequest("ZR\002\016",2,14) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def SetPhoneNumber( + self, + num, + label, + number + ): + request = XCallRequest("ZR\002\017",2,15) + request.AddParm('O',0,self) + request.AddParm('u',1,num) + request.AddParm('S',2,label) + request.AddParm('S',3,number) + response = self.transport.Xcall(request) + def OpenConversation(self): + request = XCallRequest("ZR\002\021",2,17) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = module_id2classes[18](response.get(1),self.transport) + return result + def HasCapability( + self, + capability, + queryServer + ): + request = XCallRequest("ZR\002\022",2,18) + request.AddParm('O',0,self) + request.AddParm('e',1,Contact.CAPABILITY[capability]) + request.AddParm('b',2,queryServer) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def GetCapabilityStatus( + self, + capability, + queryServer + ): + request = XCallRequest("ZR\002\023",2,19) + request.AddParm('O',0,self) + request.AddParm('e',1,Contact.CAPABILITY[capability]) + request.AddParm('b',2,queryServer) + response = self.transport.Xcall(request) + result = Contact.CAPABILITYSTATUS[response.get(1)] + return result + def RefreshProfile(self): + request = XCallRequest("ZR\002\024",2,20) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def mget_profile(self): + self.multiget("ZG\004,\006,\005,\032,\020,\015,\016,\017,\007,\010,\011,\012,\013,\014,\021,\022,\033]\002") +module_id2classes[2] = Contact + +class ContactSearch(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "ContactSearch %s" % (self.object_id, ) + module_id = 1 + def OnPropertyChange(self, property_name): pass + STATUS= {1:'CONSTRUCTION', 'CONSTRUCTION':1, 2:'PENDING', 'PENDING':2, 3:'EXTENDABLE', 'EXTENDABLE':3, 4:'FINISHED', 'FINISHED':4, 5:'FAILED', 'FAILED':5} + CONDITION= { + 0:'EQ', + 1:'GT', + 2:'GE', + 3:'LT', + 4:'LE', + 5:'PREFIX_EQ', + 6:'PREFIX_GE', + 7:'PREFIX_LE', + 8:'CONTAINS_WORDS', + 9:'CONTAINS_WORD_PREFIXES', + 'EQ' :0, + 'GT' :1, + 'GE' :2, + 'LT' :3, + 'LE' :4, + 'PREFIX_EQ' :5, + 'PREFIX_GE' :6, + 'PREFIX_LE' :7, + 'CONTAINS_WORDS' :8, + 'CONTAINS_WORD_PREFIXES' :9 + } + + def _Getcontact_search_status(self): + return ContactSearch.STATUS[self._Property("ZG\310\001]\001",200, True)] + contact_search_status = property(_Getcontact_search_status) + propid2label[200] = "contact_search_status" + + def AddMinAgeTerm( + self, + min_age_in_years, + add_to_subs + ): + request = XCallRequest("ZR\001\001",1,1) + request.AddParm('O',0,self) + request.AddParm('u',1,min_age_in_years) + request.AddParm('b',2,add_to_subs) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def AddMaxAgeTerm( + self, + max_age_in_years, + add_to_subs + ): + request = XCallRequest("ZR\001\002",1,2) + request.AddParm('O',0,self) + request.AddParm('u',1,max_age_in_years) + request.AddParm('b',2,add_to_subs) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def AddEmailTerm( + self, + email, + add_to_subs + ): + request = XCallRequest("ZR\001\003",1,3) + request.AddParm('O',0,self) + request.AddParm('S',1,email) + request.AddParm('b',2,add_to_subs) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def AddLanguageTerm( + self, + language, + add_to_subs + ): + request = XCallRequest("ZR\001\004",1,4) + request.AddParm('O',0,self) + request.AddParm('S',1,language) + request.AddParm('b',2,add_to_subs) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def AddStrTerm( + self, + prop, + cond, + value, + add_to_subs + ): + request = XCallRequest("ZR\001\005",1,5) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(prop,1)) + request.AddParm('e',2,ContactSearch.CONDITION[cond]) + request.AddParm('S',3,value) + request.AddParm('b',4,add_to_subs) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def AddIntTerm( + self, + prop, + cond, + value, + add_to_subs + ): + request = XCallRequest("ZR\001\006",1,6) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(prop,1)) + request.AddParm('e',2,ContactSearch.CONDITION[cond]) + request.AddParm('u',3,value) + request.AddParm('b',4,add_to_subs) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def AddOr(self): + request = XCallRequest("ZR\001\007",1,7) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def IsValid(self): + request = XCallRequest("ZR\001\010",1,8) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def Submit(self): + request = XCallRequest("ZR\001\011",1,9) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Extend(self): + request = XCallRequest("ZR\001\012",1,10) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Release(self): + request = XCallRequest("ZR\001\014",1,12) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def GetResults( + self, + from_, + count + ): + request = XCallRequest("ZR\001\013",1,11) + request.AddParm('O',0,self) + request.AddParm('u',1,from_) + request.AddParm('u',2,count) + response = self.transport.Xcall(request) + result = [module_id2classes[2](oid,self.transport) for oid in response.get(1,[])] + return result + def OnNewResult( + self, + contact, + rankValue + ): pass + event_handlers[1] = "OnNewResultDispatch" + def OnNewResultDispatch(self, parms): + cleanparms = (module_id2classes[2](parms.get(1),self.transport)), + cleanparms += (parms.get(2,0)), + self.OnNewResult(*cleanparms) +module_id2classes[1] = ContactSearch + +class Participant(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Participant %s" % (self.object_id, ) + module_id = 19 + def OnPropertyChange(self, property_name): pass + RANK= {1:'CREATOR', 'CREATOR':1, 2:'ADMIN', 'ADMIN':2, 3:'SPEAKER', 'SPEAKER':3, 4:'WRITER', 'WRITER':4, 5:'SPECTATOR', 'SPECTATOR':5, 6:'APPLICANT', 'APPLICANT':6, 7:'RETIRED', 'RETIRED':7, 8:'OUTLAW', 'OUTLAW':8} + TEXT_STATUS= {0:'TEXT_UNKNOWN', 'TEXT_UNKNOWN':0, 1:'TEXT_NA', 'TEXT_NA':1, 2:'READING', 'READING':2, 3:'WRITING', 'WRITING':3, 4:'WRITING_AS_ANGRY', 'WRITING_AS_ANGRY':4, 5:'WRITING_AS_CAT', 'WRITING_AS_CAT':5} + VOICE_STATUS= { + 0:'VOICE_UNKNOWN', + 1:'VOICE_NA', + 2:'VOICE_AVAILABLE', + 3:'VOICE_CONNECTING', + 4:'RINGING', + 5:'EARLY_MEDIA', + 6:'LISTENING', + 7:'SPEAKING', + 8:'VOICE_ON_HOLD', + 9:'VOICE_STOPPED', + 'VOICE_UNKNOWN' :0, + 'VOICE_NA' :1, + 'VOICE_AVAILABLE' :2, + 'VOICE_CONNECTING' :3, + 'RINGING' :4, + 'EARLY_MEDIA' :5, + 'LISTENING' :6, + 'SPEAKING' :7, + 'VOICE_ON_HOLD' :8, + 'VOICE_STOPPED' :9 + } + VIDEO_STATUS= {0:'VIDEO_UNKNOWN', 'VIDEO_UNKNOWN':0, 1:'VIDEO_NA', 'VIDEO_NA':1, 2:'VIDEO_AVAILABLE', 'VIDEO_AVAILABLE':2, 3:'VIDEO_CONNECTING', 'VIDEO_CONNECTING':3, 4:'STREAMING', 'STREAMING':4, 5:'VIDEO_ON_HOLD', 'VIDEO_ON_HOLD':5} + DTMF= { + 0 :'DTMF_0', + 1 :'DTMF_1', + 2 :'DTMF_2', + 3 :'DTMF_3', + 4 :'DTMF_4', + 5 :'DTMF_5', + 6 :'DTMF_6', + 7 :'DTMF_7', + 8 :'DTMF_8', + 9 :'DTMF_9', + 10:'DTMF_STAR', + 11:'DTMF_POUND', + 'DTMF_0' : 0, + 'DTMF_1' : 1, + 'DTMF_2' : 2, + 'DTMF_3' : 3, + 'DTMF_4' : 4, + 'DTMF_5' : 5, + 'DTMF_6' : 6, + 'DTMF_7' : 7, + 'DTMF_8' : 8, + 'DTMF_9' : 9, + 'DTMF_STAR' :10, + 'DTMF_POUND' :11 + } + + def _Getconvo_id(self): #@IndentOk + return module_id2classes[18](self._Property("ZG\242\007]\023",930, True),self.transport) + convo_id = property(_Getconvo_id) + propid2label[930] = "convo_id" + def _Getidentity(self): + return self._Property("ZG\243\007]\023",931, True) + identity = property(_Getidentity) + propid2label[931] = "identity" + def _Getrank(self): + return Participant.RANK[self._Property("ZG\244\007]\023",932, True)] + rank = property(_Getrank) + propid2label[932] = "rank" + def _Getrequested_rank(self): + return Participant.RANK[self._Property("ZG\245\007]\023",933, True)] + requested_rank = property(_Getrequested_rank) + propid2label[933] = "requested_rank" + def _Gettext_status(self): + return Participant.TEXT_STATUS[self._Property("ZG\246\007]\023",934, True)] + text_status = property(_Gettext_status) + propid2label[934] = "text_status" + def _Getvoice_status(self): + return Participant.VOICE_STATUS[self._Property("ZG\247\007]\023",935, True)] + voice_status = property(_Getvoice_status) + propid2label[935] = "voice_status" + def _Getvideo_status(self): + return Participant.VIDEO_STATUS[self._Property("ZG\250\007]\023",936, True)] + video_status = property(_Getvideo_status) + propid2label[936] = "video_status" + def _Getlive_identity(self): + return self._Property("ZG\257\007]\023",943, False) + live_identity = property(_Getlive_identity) + propid2label[943] = "live_identity" + def _Getlive_price_for_me(self): + return self._Property("ZG\252\007]\023",938, True) + live_price_for_me = property(_Getlive_price_for_me) + propid2label[938] = "live_price_for_me" + def _Getlive_fwd_identities(self): + return self._Property("ZG\264\007]\023",948, True) + live_fwd_identities = property(_Getlive_fwd_identities) + propid2label[948] = "live_fwd_identities" + def _Getlive_start_timestamp(self): + return self._Property("ZG\253\007]\023",939, True) + live_start_timestamp = property(_Getlive_start_timestamp) + propid2label[939] = "live_start_timestamp" + def _Getsound_level(self): + return self._Property("ZG\255\007]\023",941, True) + sound_level = property(_Getsound_level) + propid2label[941] = "sound_level" + def _Getdebuginfo(self): + return self._Property("ZG\256\007]\023",942, False) + debuginfo = property(_Getdebuginfo) + propid2label[942] = "debuginfo" + def _Getlast_voice_error(self): + return self._Property("ZG\263\007]\023",947, True) + last_voice_error = property(_Getlast_voice_error) + propid2label[947] = "last_voice_error" + def _Getquality_problems(self): + return self._Property("ZG\265\007]\023",949, True) + quality_problems = property(_Getquality_problems) + propid2label[949] = "quality_problems" + def _Getlive_type(self): + return SkyLib.IDENTITYTYPE[self._Property("ZG\266\007]\023",950, True)] + live_type = property(_Getlive_type) + propid2label[950] = "live_type" + def _Getlive_country(self): + return self._Property("ZG\267\007]\023",951, False) + live_country = property(_Getlive_country) + propid2label[951] = "live_country" + def _Gettransferred_by(self): + return self._Property("ZG\270\007]\023",952, True) + transferred_by = property(_Gettransferred_by) + propid2label[952] = "transferred_by" + def _Gettransferred_to(self): + return self._Property("ZG\271\007]\023",953, True) + transferred_to = property(_Gettransferred_to) + propid2label[953] = "transferred_to" + def _Getadder(self): + return self._Property("ZG\272\007]\023",954, True) + adder = property(_Getadder) + propid2label[954] = "adder" + + def CanSetRankTo( + self, + rank + ): + request = XCallRequest("ZR\023\001",19,1) + request.AddParm('O',0,self) + request.AddParm('e',1,Participant.RANK[rank]) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def SetRankTo( + self, + rank + ): + request = XCallRequest("ZR\023\002",19,2) + request.AddParm('O',0,self) + request.AddParm('e',1,Participant.RANK[rank]) + response = self.transport.Xcall(request) + def Ring( + self, + identityToUse, + videoCall, + nrofRedials, + redialPeriod, + autoStartVM, + origin + ): + request = XCallRequest("ZR\023\003",19,3) + request.AddParm('O',0,self) + request.AddParm('S',1,identityToUse) + request.AddParm('b',2,videoCall) + request.AddParm('u',3,nrofRedials) + request.AddParm('u',4,redialPeriod) + request.AddParm('b',5,autoStartVM) + request.AddParm('S',6,origin) + response = self.transport.Xcall(request) + def GetVideo(self): + request = XCallRequest("ZR\023\004",19,4) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = module_id2classes[11](response.get(1),self.transport) + return result + def Hangup(self): + request = XCallRequest("ZR\023\005",19,5) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Retire(self): + request = XCallRequest("ZR\023\006",19,6) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def OnIncomingDTMF( + self, + dtmf + ): pass + event_handlers[1] = "OnIncomingDTMFDispatch" + def OnIncomingDTMFDispatch(self, parms): + cleanparms = Participant.DTMF[parms.get(1)] + self.OnIncomingDTMF(cleanparms) +module_id2classes[19] = Participant + +class Conversation(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Conversation %s" % (self.object_id, ) + module_id = 18 + def OnPropertyChange(self, property_name): pass + TYPE= {1:'DIALOG', 'DIALOG':1, 2:'CONFERENCE', 'CONFERENCE':2, 3:'TERMINATED_CONFERENCE', 'TERMINATED_CONFERENCE':3, 4:'LEGACY_VOICE_CONFERENCE', 'LEGACY_VOICE_CONFERENCE':4, 5:'LEGACY_SHAREDGROUP', 'LEGACY_SHAREDGROUP':5} + MY_STATUS= { + 1 :'CONNECTING', + 2 :'RETRY_CONNECTING', + 3 :'DOWNLOADING_MESSAGES', + 4 :'QUEUED_TO_ENTER', + 5 :'APPLICANT', + 6 :'APPLICATION_DENIED', + 7 :'INVALID_ACCESS_TOKEN', + 8 :'CONSUMER', + 9 :'RETIRED_FORCEFULLY', + 10:'RETIRED_VOLUNTARILY', + 'CONNECTING' : 1, + 'RETRY_CONNECTING' : 2, + 'DOWNLOADING_MESSAGES' : 3, + 'QUEUED_TO_ENTER' : 4, + 'APPLICANT' : 5, + 'APPLICATION_DENIED' : 6, + 'INVALID_ACCESS_TOKEN' : 7, + 'CONSUMER' : 8, + 'RETIRED_FORCEFULLY' : 9, + 'RETIRED_VOLUNTARILY' :10 + } + LOCAL_LIVESTATUS= { + 0 :'NONE', + 1 :'STARTING', + 2 :'RINGING_FOR_ME', + 3 :'IM_LIVE', + 5 :'ON_HOLD_LOCALLY', + 6 :'ON_HOLD_REMOTELY', + 7 :'OTHERS_ARE_LIVE', + 11:'OTHERS_ARE_LIVE_FULL', + 8 :'PLAYING_VOICE_MESSAGE', + 9 :'RECORDING_VOICE_MESSAGE', + 10:'RECENTLY_LIVE', + 12:'TRANSFERRING', + 'NONE' : 0, + 'STARTING' : 1, + 'RINGING_FOR_ME' : 2, + 'IM_LIVE' : 3, + 'ON_HOLD_LOCALLY' : 5, + 'ON_HOLD_REMOTELY' : 6, + 'OTHERS_ARE_LIVE' : 7, + 'OTHERS_ARE_LIVE_FULL' :11, + 'PLAYING_VOICE_MESSAGE' : 8, + 'RECORDING_VOICE_MESSAGE' : 9, + 'RECENTLY_LIVE' :10, + 'TRANSFERRING' :12 + } + ALLOWED_ACTIVITY= {1:'SET_META', 'SET_META':1, 2:'ADD_CONSUMERS', 'ADD_CONSUMERS':2, 4:'SPEAK', 'SPEAK':4, 8:'SPEAK_AND_WRITE', 'SPEAK_AND_WRITE':8} + PARTICIPANTFILTER= {0:'ALL', 'ALL':0, 1:'CONSUMERS', 'CONSUMERS':1, 2:'APPLICANTS', 'APPLICANTS':2, 3:'CONSUMERS_AND_APPLICANTS', 'CONSUMERS_AND_APPLICANTS':3, 4:'MYSELF', 'MYSELF':4, 5:'OTHER_CONSUMERS', 'OTHER_CONSUMERS':5} + LIST_TYPE= {0:'ALL_CONVERSATIONS', 'ALL_CONVERSATIONS':0, 1:'INBOX_CONVERSATIONS', 'INBOX_CONVERSATIONS':1, 2:'BOOKMARKED_CONVERSATIONS', 'BOOKMARKED_CONVERSATIONS':2, 3:'LIVE_CONVERSATIONS', 'LIVE_CONVERSATIONS':3} + + def _Getidentity(self): + return self._Property("ZG\314\007]\022",972, True) + identity = property(_Getidentity) + propid2label[972] = "identity" + def _Gettype(self): + return Conversation.TYPE[self._Property("ZG\206\007]\022",902, True)] + type = property(_Gettype) + propid2label[902] = "type" + def _Getlive_host(self): + return self._Property("ZG\226\007]\022",918, True) + live_host = property(_Getlive_host) + propid2label[918] = "live_host" + def _Getlive_start_timestamp(self): + return self._Property("ZG\316\007]\022",974, True) + live_start_timestamp = property(_Getlive_start_timestamp) + propid2label[974] = "live_start_timestamp" + def _Getlive_is_muted(self): + return self._Property("ZG\344\007]\022",996, True) + live_is_muted = property(_Getlive_is_muted) + propid2label[996] = "live_is_muted" + def _Getalert_string(self): + return self._Property("ZG\230\007]\022",920, True) + alert_string = property(_Getalert_string) + propid2label[920] = "alert_string" + def _Getis_bookmarked(self): + return self._Property("ZG\231\007]\022",921, True) + is_bookmarked = property(_Getis_bookmarked) + propid2label[921] = "is_bookmarked" + def _Getgiven_displayname(self): + return self._Property("ZG\235\007]\022",925, True) + given_displayname = property(_Getgiven_displayname) + propid2label[925] = "given_displayname" + def _Getdisplayname(self): + return self._Property("ZG\234\007]\022",924, True) + displayname = property(_Getdisplayname) + propid2label[924] = "displayname" + def _Getlocal_livestatus(self): + return Conversation.LOCAL_LIVESTATUS[self._Property("ZG\237\007]\022",927, True)] + local_livestatus = property(_Getlocal_livestatus) + propid2label[927] = "local_livestatus" + def _Getinbox_timestamp(self): + return self._Property("ZG\240\007]\022",928, True) + inbox_timestamp = property(_Getinbox_timestamp) + propid2label[928] = "inbox_timestamp" + def _Getinbox_message_id(self): + return module_id2classes[9](self._Property("ZG\315\007]\022",973, True),self.transport) + inbox_message_id = property(_Getinbox_message_id) + propid2label[973] = "inbox_message_id" + def _Getunconsumed_suppressed_messages(self): + return self._Property("ZG\317\007]\022",975, True) + unconsumed_suppressed_messages = property(_Getunconsumed_suppressed_messages) + propid2label[975] = "unconsumed_suppressed_messages" + def _Getunconsumed_normal_messages(self): + return self._Property("ZG\320\007]\022",976, True) + unconsumed_normal_messages = property(_Getunconsumed_normal_messages) + propid2label[976] = "unconsumed_normal_messages" + def _Getunconsumed_elevated_messages(self): + return self._Property("ZG\321\007]\022",977, True) + unconsumed_elevated_messages = property(_Getunconsumed_elevated_messages) + propid2label[977] = "unconsumed_elevated_messages" + def _Getunconsumed_messages_voice(self): + return self._Property("ZG\312\007]\022",970, True) + unconsumed_messages_voice = property(_Getunconsumed_messages_voice) + propid2label[970] = "unconsumed_messages_voice" + def _Getactive_vm_id(self): + return module_id2classes[7](self._Property("ZG\313\007]\022",971, True),self.transport) + active_vm_id = property(_Getactive_vm_id) + propid2label[971] = "active_vm_id" + def _Getconsumption_horizon(self): + return self._Property("ZG\323\007]\022",979, True) + consumption_horizon = property(_Getconsumption_horizon) + propid2label[979] = "consumption_horizon" + def _Getlast_activity_timestamp(self): + return self._Property("ZG\325\007]\022",981, True) + last_activity_timestamp = property(_Getlast_activity_timestamp) + propid2label[981] = "last_activity_timestamp" + def _Getspawned_from_convo_id(self): + return module_id2classes[18](self._Property("ZG\223\007]\022",915, True),self.transport) + spawned_from_convo_id = property(_Getspawned_from_convo_id) + propid2label[915] = "spawned_from_convo_id" + def _Getcreator(self): + return self._Property("ZG\207\007]\022",903, True) + creator = property(_Getcreator) + propid2label[903] = "creator" + def _Getcreation_timestamp(self): + return self._Property("ZG\210\007]\022",904, True) + creation_timestamp = property(_Getcreation_timestamp) + propid2label[904] = "creation_timestamp" + def _Getmy_status(self): + return Conversation.MY_STATUS[self._Property("ZG\227\007]\022",919, True)] + my_status = property(_Getmy_status) + propid2label[919] = "my_status" + def _Getopt_joining_enabled(self): + return self._Property("ZG\232\007]\022",922, True) + opt_joining_enabled = property(_Getopt_joining_enabled) + propid2label[922] = "opt_joining_enabled" + def _Getopt_entry_level_rank(self): + return Participant.RANK[self._Property("ZG\212\007]\022",906, True)] + opt_entry_level_rank = property(_Getopt_entry_level_rank) + propid2label[906] = "opt_entry_level_rank" + def _Getopt_disclose_history(self): + return self._Property("ZG\213\007]\022",907, True) + opt_disclose_history = property(_Getopt_disclose_history) + propid2label[907] = "opt_disclose_history" + def _Getopt_admin_only_activities(self): + return self._Property("ZG\215\007]\022",909, True) + opt_admin_only_activities = property(_Getopt_admin_only_activities) + propid2label[909] = "opt_admin_only_activities" + def _Getpasswordhint(self): + return self._Property("ZG\324\007]\022",980, True) + passwordhint = property(_Getpasswordhint) + propid2label[980] = "passwordhint" + def _Getmeta_name(self): + return self._Property("ZG\216\007]\022",910, True) + meta_name = property(_Getmeta_name) + propid2label[910] = "meta_name" + def _Getmeta_topic(self): + return self._Property("ZG\217\007]\022",911, True) + meta_topic = property(_Getmeta_topic) + propid2label[911] = "meta_topic" + def _Getmeta_guidelines(self): + return self._Property("ZG\221\007]\022",913, True) + meta_guidelines = property(_Getmeta_guidelines) + propid2label[913] = "meta_guidelines" + def _Getmeta_picture(self): + return self._Property("ZG\222\007]\022",914, True) + meta_picture = property(_Getmeta_picture) + propid2label[914] = "meta_picture" + + SETUPKEY_ENABLE_BIRTHDAY_NOTIFICATION="Lib/Conversation/EnableBirthday" + SETUPKEY_INBOX_UPDATE_TIMEOUT="Lib/Conversation/InboxUpdateTimeout" + SETUPKEY_RECENTLY_LIVE_TIMEOUT="Lib/Conversation/RecentlyLiveTimeout" + SETUPKEY_DISABLE_CHAT="*Lib/Chat/DisableChat" + SETUPKEY_DISABLE_CHAT_HISTORY="Lib/Message/DisableHistory" + SETUPKEY_CHAT_HISTORY_DAYS="Lib/Chat/HistoryDays" + SETUPKEY_DISABLE_CHAT_ACTIVITY_INDICATION="Lib/Chat/DisableActivityIndication" + SETUPKEY_CALL_NOANSWER_TIMEOUT="Lib/Call/NoAnswerTimeout" + SETUPKEY_CALL_SEND_TO_VM="Lib/Call/SendToVM" + SETUPKEY_CALL_APPLY_CF="Lib/Call/ApplyCF" + SETUPKEY_CALL_EMERGENCY_COUNTRY="Lib/Call/EmergencyCountry" + def SetOption( + self, + propKey, + value + ): + request = XCallRequest("ZR\022\001",18,1) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(propKey,1)) + request.AddParm('u',2,value) + response = self.transport.Xcall(request) + def SetTopic( + self, + topic, + isXML + ): + request = XCallRequest("ZR\022\002",18,2) + request.AddParm('O',0,self) + request.AddParm('S',1,topic) + request.AddParm('b',2,isXML) + response = self.transport.Xcall(request) + def SetGuidelines( + self, + guidelines, + isXML + ): + request = XCallRequest("ZR\022\003",18,3) + request.AddParm('O',0,self) + request.AddParm('S',1,guidelines) + request.AddParm('b',2,isXML) + response = self.transport.Xcall(request) + def SetPicture( + self, + jpeg + ): + request = XCallRequest("ZR\022\004",18,4) + request.AddParm('O',0,self) + request.AddParm('B',1,jpeg) + response = self.transport.Xcall(request) + def SpawnConference( + self, + identitiesToAdd + ): + request = XCallRequest("ZR\022\006",18,6) + request.AddParm('O',0,self) + request.AddParm('S',1,identitiesToAdd) + response = self.transport.Xcall(request) + result = module_id2classes[18](response.get(1),self.transport) + return result + def AddConsumers( + self, + identities + ): + request = XCallRequest("ZR\022\007",18,7) + request.AddParm('O',0,self) + request.AddParm('S',1,identities) + response = self.transport.Xcall(request) + def Assimilate( + self, + otherConversation + ): + request = XCallRequest("ZR\022\011",18,9) + request.AddParm('O',0,self) + request.AddParm('O',1,otherConversation) + response = self.transport.Xcall(request) + result = module_id2classes[18](response.get(1),self.transport) + return result + def JoinLiveSession( + self, + accessToken + ): + request = XCallRequest("ZR\022\012",18,10) + request.AddParm('O',0,self) + request.AddParm('S',1,accessToken) + response = self.transport.Xcall(request) + def MuteMyMicrophone(self): + request = XCallRequest("ZR\022\013",18,11) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def UnmuteMyMicrophone(self): + request = XCallRequest("ZR\022\014",18,12) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def HoldMyLiveSession(self): + request = XCallRequest("ZR\022\015",18,13) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def ResumeMyLiveSession(self): + request = XCallRequest("ZR\022\016",18,14) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def LeaveLiveSession( + self, + postVoiceAutoresponse + ): + request = XCallRequest("ZR\022\017",18,15) + request.AddParm('O',0,self) + request.AddParm('b',1,postVoiceAutoresponse) + response = self.transport.Xcall(request) + def StartVoiceMessage(self): + request = XCallRequest("ZR\022-",18,45) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def TransferLiveSession( + self, + identities, + transferTopic + ): + request = XCallRequest("ZR\022(",18,40) + request.AddParm('O',0,self) + request.AddParm('S',1,identities) + request.AddParm('S',2,transferTopic) + response = self.transport.Xcall(request) + def CanTransferLiveSession( + self, + identity + ): + request = XCallRequest("ZR\022.",18,46) + request.AddParm('O',0,self) + request.AddParm('S',1,identity) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def SendDTMF( + self, + dtmf, + lengthInMS + ): + request = XCallRequest("ZR\022\020",18,16) + request.AddParm('O',0,self) + request.AddParm('e',1,Participant.DTMF[dtmf]) + request.AddParm('u',2,lengthInMS) + response = self.transport.Xcall(request) + def StopSendDTMF(self): + request = XCallRequest("ZR\022\060",18,48) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def SetMyTextStatusTo( + self, + status + ): + request = XCallRequest("ZR\022\022",18,18) + request.AddParm('O',0,self) + request.AddParm('e',1,Participant.TEXT_STATUS[status]) + response = self.transport.Xcall(request) + def PostText( + self, + text, + isXML + ): + request = XCallRequest("ZR\022\023",18,19) + request.AddParm('O',0,self) + request.AddParm('S',1,text) + request.AddParm('b',2,isXML) + response = self.transport.Xcall(request) + result = module_id2classes[9](response.get(1),self.transport) + return result + def PostContacts( + self, + contacts + ): + request = XCallRequest("ZR\022\024",18,20) + request.AddParm('O',0,self) + request.AddParm('O',1,contacts) + response = self.transport.Xcall(request) + def PostFiles( + self, + paths, + body + ): + request = XCallRequest("ZR\022\025",18,21) + request.AddParm('O',0,self) + request.AddParm('f',1,paths) + request.AddParm('S',2,body) + response = self.transport.Xcall(request) + result = (SkyLib.TRANSFER_SENDFILE_ERROR[response.get(1)]), + result += (response.get(2,'')), + return result + def PostVoiceMessage( + self, + voicemail, + body + ): + request = XCallRequest("ZR\022\026",18,22) + request.AddParm('O',0,self) + request.AddParm('O',1,voicemail) + request.AddParm('S',2,body) + response = self.transport.Xcall(request) + def PostSMS( + self, + sms, + body + ): + request = XCallRequest("ZR\022\027",18,23) + request.AddParm('O',0,self) + request.AddParm('O',1,sms) + request.AddParm('S',2,body) + response = self.transport.Xcall(request) + def GetJoinBlob(self): + request = XCallRequest("ZR\022\030",18,24) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def Join(self): + request = XCallRequest("ZR\022\031",18,25) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def EnterPassword( + self, + password + ): + request = XCallRequest("ZR\022\032",18,26) + request.AddParm('O',0,self) + request.AddParm('S',1,password) + response = self.transport.Xcall(request) + def SetPassword( + self, + password, + hint + ): + request = XCallRequest("ZR\022\033",18,27) + request.AddParm('O',0,self) + request.AddParm('S',1,password) + request.AddParm('S',2,hint) + response = self.transport.Xcall(request) + def RetireFrom(self): + request = XCallRequest("ZR\022\034",18,28) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Delete(self): + request = XCallRequest("ZR\022/",18,47) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def RenameTo( + self, + name + ): + request = XCallRequest("ZR\022\035",18,29) + request.AddParm('O',0,self) + request.AddParm('S',1,name) + response = self.transport.Xcall(request) + def SetBookmark( + self, + bookmark + ): + request = XCallRequest("ZR\022\036",18,30) + request.AddParm('O',0,self) + request.AddParm('b',1,bookmark) + response = self.transport.Xcall(request) + def SetAlertString( + self, + alertString + ): + request = XCallRequest("ZR\022\037",18,31) + request.AddParm('O',0,self) + request.AddParm('S',1,alertString) + response = self.transport.Xcall(request) + def RemoveFromInbox(self): + request = XCallRequest("ZR\022 ",18,32) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def AddToInbox(self): + request = XCallRequest("ZR\022!",18,33) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def SetConsumedHorizon( + self, + timestamp, + also_unconsume + ): + request = XCallRequest("ZR\022\042",18,34) + request.AddParm('O',0,self) + request.AddParm('u',1,timestamp) + request.AddParm('b',2,also_unconsume) + response = self.transport.Xcall(request) + def MarkUnread(self): + request = XCallRequest("ZR\022#",18,35) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def IsMemberOf( + self, + group + ): + request = XCallRequest("ZR\022%",18,37) + request.AddParm('O',0,self) + request.AddParm('O',1,group) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def GetParticipants( + self, + filter + ): + request = XCallRequest("ZR\022&",18,38) + request.AddParm('O',0,self) + request.AddParm('e',1,Conversation.PARTICIPANTFILTER[filter]) + response = self.transport.Xcall(request) + result = [module_id2classes[19](oid,self.transport) for oid in response.get(1,[])] + return result + def GetLastMessages( + self, + requireTimestamp + ): + request = XCallRequest("ZR\022'",18,39) + request.AddParm('O',0,self) + request.AddParm('u',1,requireTimestamp) + response = self.transport.Xcall(request) + result = ([module_id2classes[9](oid,self.transport) for oid in response.get(1,[])]), + result += ([module_id2classes[9](oid,self.transport) for oid in response.get(2,[])]), + return result + def FindMessage( + self, + text, + fromTimestampUp + ): + request = XCallRequest("ZR\022)",18,41) + request.AddParm('O',0,self) + request.AddParm('S',1,text) + request.AddParm('u',2,fromTimestampUp) + response = self.transport.Xcall(request) + result = module_id2classes[9](response.get(1),self.transport) + return result + def OnParticipantListChange(self): pass + def OnParticipantListChangeDispatch(self, parms): self.OnParticipantListChange() + event_handlers[1] = "OnParticipantListChangeDispatch" + def OnMessage( + self, + message + ): pass + event_handlers[2] = "OnMessageDispatch" + def OnMessageDispatch(self, parms): + cleanparms = module_id2classes[9](parms.get(1),self.transport) + self.OnMessage(cleanparms) + def OnSpawnConference( + self, + spawned + ): pass + event_handlers[3] = "OnSpawnConferenceDispatch" + def OnSpawnConferenceDispatch(self, parms): + cleanparms = module_id2classes[18](parms.get(1),self.transport) + self.OnSpawnConference(cleanparms) +module_id2classes[18] = Conversation + +class Message(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Message %s" % (self.object_id, ) + module_id = 9 + def OnPropertyChange(self, property_name): pass + TYPE= { + 2 :'SET_METADATA', + 4 :'SPAWNED_CONFERENCE', + 10 :'ADDED_CONSUMERS', + 11 :'ADDED_APPLICANTS', + 12 :'RETIRED_OTHERS', + 13 :'RETIRED', + 21 :'SET_RANK', + 30 :'STARTED_LIVESESSION', + 39 :'ENDED_LIVESESSION', + 50 :'REQUESTED_AUTH', + 51 :'GRANTED_AUTH', + 53 :'BLOCKED', + 61 :'POSTED_TEXT', + 60 :'POSTED_EMOTE', + 63 :'POSTED_CONTACTS', + 64 :'POSTED_SMS', + 65 :'POSTED_ALERT', + 67 :'POSTED_VOICE_MESSAGE', + 68 :'POSTED_FILES', + 69 :'POSTED_INVOICE', + 110:'HAS_BIRTHDAY', + 'SET_METADATA' : 2, + 'SPAWNED_CONFERENCE' : 4, + 'ADDED_CONSUMERS' : 10, + 'ADDED_APPLICANTS' : 11, + 'RETIRED_OTHERS' : 12, + 'RETIRED' : 13, + 'SET_RANK' : 21, + 'STARTED_LIVESESSION' : 30, + 'ENDED_LIVESESSION' : 39, + 'REQUESTED_AUTH' : 50, + 'GRANTED_AUTH' : 51, + 'BLOCKED' : 53, + 'POSTED_TEXT' : 61, + 'POSTED_EMOTE' : 60, + 'POSTED_CONTACTS' : 63, + 'POSTED_SMS' : 64, + 'POSTED_ALERT' : 65, + 'POSTED_VOICE_MESSAGE' : 67, + 'POSTED_FILES' : 68, + 'POSTED_INVOICE' : 69, + 'HAS_BIRTHDAY' :110 + } + SENDING_STATUS= {1:'SENDING', 'SENDING':1, 2:'SENT', 'SENT':2, 3:'FAILED_TO_SEND', 'FAILED_TO_SEND':3} + CONSUMPTION_STATUS= {0:'CONSUMED', 'CONSUMED':0, 1:'UNCONSUMED_SUPPRESSED', 'UNCONSUMED_SUPPRESSED':1, 2:'UNCONSUMED_NORMAL', 'UNCONSUMED_NORMAL':2, 3:'UNCONSUMED_ELEVATED', 'UNCONSUMED_ELEVATED':3} + SET_METADATA_KEY= {3640:'SET_META_NAME', 'SET_META_NAME':3640, 3644:'SET_META_TOPIC', 'SET_META_TOPIC':3644, 3652:'SET_META_GUIDELINES', 'SET_META_GUIDELINES':3652, 3658:'SET_META_PICTURE', 'SET_META_PICTURE':3658} + SET_OPTION_KEY= {3689:'SET_OPTION_JOINING_ENABLED', 'SET_OPTION_JOINING_ENABLED':3689, 3625:'SET_OPTION_ENTRY_LEVEL_RANK', 'SET_OPTION_ENTRY_LEVEL_RANK':3625, 3629:'SET_OPTION_DISCLOSE_HISTORY', 'SET_OPTION_DISCLOSE_HISTORY':3629, 3633:'SET_OPTION_HISTORY_LIMIT_IN_DAYS', 'SET_OPTION_HISTORY_LIMIT_IN_DAYS':3633, 3637:'SET_OPTION_ADMIN_ONLY_ACTIVITIES', 'SET_OPTION_ADMIN_ONLY_ACTIVITIES':3637} + LEAVEREASON= {2:'USER_INCAPABLE', 'USER_INCAPABLE':2, 3:'ADDER_MUST_BE_FRIEND', 'ADDER_MUST_BE_FRIEND':3, 4:'ADDER_MUST_BE_AUTHORIZED', 'ADDER_MUST_BE_AUTHORIZED':4, 5:'DECLINE_ADD', 'DECLINE_ADD':5, 6:'UNSUBSCRIBE', 'UNSUBSCRIBE':6} + + def _Getconvo_id(self): + return module_id2classes[18](self._Property("ZG\300\007]\011",960, True),self.transport) + convo_id = property(_Getconvo_id) + propid2label[960] = "convo_id" + def _Getconvo_guid(self): + return self._Property("ZGx]\011",120, True) + convo_guid = property(_Getconvo_guid) + propid2label[120] = "convo_guid" + def _Getauthor(self): + return self._Property("ZGz]\011",122, True) + author = property(_Getauthor) + propid2label[122] = "author" + def _Getauthor_displayname(self): + return self._Property("ZG{]\011",123, True) + author_displayname = property(_Getauthor_displayname) + propid2label[123] = "author_displayname" + def _Getguid(self): + return self._Property("ZG\230\006]\011",792, True) + guid = property(_Getguid) + propid2label[792] = "guid" + def _Getoriginally_meant_for(self): + return self._Property("ZG\226\006]\011",790, True) + originally_meant_for = property(_Getoriginally_meant_for) + propid2label[790] = "originally_meant_for" + def _Gettimestamp(self): + return self._Property("ZGy]\011",121, True) + timestamp = property(_Gettimestamp) + propid2label[121] = "timestamp" + def _Gettype(self): + return Message.TYPE[self._Property("ZG\301\007]\011",961, True)] + type = property(_Gettype) + propid2label[961] = "type" + def _Getsending_status(self): + return Message.SENDING_STATUS[self._Property("ZG\302\007]\011",962, True)] + sending_status = property(_Getsending_status) + propid2label[962] = "sending_status" + def _Getconsumption_status(self): + return Message.CONSUMPTION_STATUS[self._Property("ZG\310\007]\011",968, True)] + consumption_status = property(_Getconsumption_status) + propid2label[968] = "consumption_status" + def _Getedited_by(self): + return self._Property("ZG\336\001]\011",222, True) + edited_by = property(_Getedited_by) + propid2label[222] = "edited_by" + def _Getedit_timestamp(self): + return self._Property("ZG\337\001]\011",223, True) + edit_timestamp = property(_Getedit_timestamp) + propid2label[223] = "edit_timestamp" + def _Getparam_key(self): + return self._Property("ZG\303\007]\011",963, True) + param_key = property(_Getparam_key) + propid2label[963] = "param_key" + def _Getparam_value(self): + return self._Property("ZG\304\007]\011",964, True) + param_value = property(_Getparam_value) + propid2label[964] = "param_value" + def _Getbody_xml(self): + return self._Property("ZG\177]\011",127, True) + body_xml = property(_Getbody_xml) + propid2label[127] = "body_xml" + def _Getidentities(self): + return self._Property("ZG}]\011",125, True) + identities = property(_Getidentities) + propid2label[125] = "identities" + def _Getreason(self): + return self._Property("ZG\306\007]\011",966, True) + reason = property(_Getreason) + propid2label[966] = "reason" + def _Getleavereason(self): + return Message.LEAVEREASON[self._Property("ZG~]\011",126, True)] + leavereason = property(_Getleavereason) + propid2label[126] = "leavereason" + def _Getparticipant_count(self): + return self._Property("ZG\326\007]\011",982, True) + participant_count = property(_Getparticipant_count) + propid2label[982] = "participant_count" + + def CanEdit(self): + request = XCallRequest("ZR\011\001",9,1) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def Edit( + self, + newText, + isXML + ): + request = XCallRequest("ZR\011\002",9,2) + request.AddParm('O',0,self) + request.AddParm('S',1,newText) + request.AddParm('b',2,isXML) + response = self.transport.Xcall(request) + def GetContacts(self): + request = XCallRequest("ZR\011\003",9,3) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = [module_id2classes[2](oid,self.transport) for oid in response.get(1,[])] + return result + def GetTransfers(self): + request = XCallRequest("ZR\011\004",9,4) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = [module_id2classes[6](oid,self.transport) for oid in response.get(1,[])] + return result + def GetVoiceMessage(self): + request = XCallRequest("ZR\011\005",9,5) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = module_id2classes[7](response.get(1),self.transport) + return result + def GetSMS(self): + request = XCallRequest("ZR\011\006",9,6) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = module_id2classes[12](response.get(1),self.transport) + return result + def DeleteLocally(self): + request = XCallRequest("ZR\011\010",9,8) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) +module_id2classes[9] = Message + +class Video(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Video %s" % (self.object_id, ) + module_id = 11 + def OnPropertyChange(self, property_name): pass + STATUS= { + 0 :'NOT_AVAILABLE', + 1 :'AVAILABLE', + 2 :'STARTING', + 3 :'REJECTED', + 4 :'RUNNING', + 5 :'STOPPING', + 6 :'PAUSED', + 7 :'NOT_STARTED', + 8 :'HINT_IS_VIDEOCALL_RECEIVED', + 9 :'UNKNOWN', + 10:'RENDERING', + 'NOT_AVAILABLE' : 0, + 'AVAILABLE' : 1, + 'STARTING' : 2, + 'REJECTED' : 3, + 'RUNNING' : 4, + 'STOPPING' : 5, + 'PAUSED' : 6, + 'NOT_STARTED' : 7, + 'HINT_IS_VIDEOCALL_RECEIVED' : 8, + 'UNKNOWN' : 9, + 'RENDERING' :10 + } + MEDIATYPE= {1:'MEDIA_SCREENSHARING', 'MEDIA_SCREENSHARING':1, 0:'MEDIA_VIDEO', 'MEDIA_VIDEO':0} + VIDEO_DEVICE_CAPABILITY= {0:'VIDEOCAP_HQ_CAPABLE', 'VIDEOCAP_HQ_CAPABLE':0, 1:'VIDEOCAP_HQ_CERTIFIED', 'VIDEOCAP_HQ_CERTIFIED':1, 2:'VIDEOCAP_REQ_DRIVERUPDATE', 'VIDEOCAP_REQ_DRIVERUPDATE':2, 3:'VIDEOCAP_USB_HIGHSPEED', 'VIDEOCAP_USB_HIGHSPEED':3} + + def _Getstatus(self): + return Video.STATUS[self._Property("ZG\202\001]\013",130, True)] + status = property(_Getstatus) + propid2label[130] = "status" + def _Geterror(self): + return self._Property("ZG\203\001]\013",131, True) + error = property(_Geterror) + propid2label[131] = "error" + def _Getdebuginfo(self): + return self._Property("ZG\204\001]\013",132, True) + debuginfo = property(_Getdebuginfo) + propid2label[132] = "debuginfo" + def _Getdimensions(self): + return self._Property("ZG\205\001]\013",133, True) + dimensions = property(_Getdimensions) + propid2label[133] = "dimensions" + def _Getmedia_type(self): + return Video.MEDIATYPE[self._Property("ZG\206\001]\013",134, True)] + media_type = property(_Getmedia_type) + propid2label[134] = "media_type" + def _Getconvo_id(self): + return self._Property("ZG\320\010]\013",1104, True) + convo_id = property(_Getconvo_id) + propid2label[1104] = "convo_id" + def _Getdevice_path(self): + return self._Property("ZG\321\010]\013",1105, True) + device_path = property(_Getdevice_path) + propid2label[1105] = "device_path" + + SETUPKEY_VIDEO_DEVICE="Lib/Video/Device" + SETUPKEY_VIDEO_DEVICE_PATH="Lib/Video/DevicePath" + SETUPKEY_VIDEO_AUTOSEND="Lib/Video/AutoSend" + SETUPKEY_VIDEO_DISABLE="*Lib/Video/Disable" + SETUPKEY_VIDEO_RECVPOLICY="Lib/Video/RecvPolicy" + SETUPKEY_VIDEO_ADVERTPOLICY="Lib/Video/AdvertPolicy" + def SetScreen( + self, + windowh + ): + request = XCallRequest("ZR\013\001",11,1) + request.AddParm('O',0,self) + request.AddParm('u',1,windowh) + response = self.transport.Xcall(request) + def Start(self): + request = XCallRequest("ZR\013\002",11,2) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Stop(self): + request = XCallRequest("ZR\013\003",11,3) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def SubmitCaptureRequest(self): + request = XCallRequest("ZR\013\013",11,11) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = (response.get(1,False)), + result += (response.get(2,0)), + return result + def OnCaptureRequestCompleted( + self, + requestId, + isSuccessful, + image, + width, + height + ): pass + event_handlers[2] = "OnCaptureRequestCompletedDispatch" + def OnCaptureRequestCompletedDispatch(self, parms): + cleanparms = (parms.get(1,0)), + cleanparms += (parms.get(2,False)), + cleanparms += (parms.get(3,'')), + cleanparms += (parms.get(4,0)), + cleanparms += (parms.get(5,0)), + self.OnCaptureRequestCompleted(*cleanparms) + def SetScreenCaptureRectangle( + self, + x0, + y0, + width, + height, + monitorNumber, + windowHandle + ): + request = XCallRequest("ZR\013\005",11,5) + request.AddParm('O',0,self) + request.AddParm('i',1,x0) + request.AddParm('i',2,y0) + request.AddParm('u',3,width) + request.AddParm('u',4,height) + request.AddParm('i',5,monitorNumber) + request.AddParm('u',6,windowHandle) + response = self.transport.Xcall(request) + def SetRenderRectangle( + self, + x0, + y0, + width, + height + ): + request = XCallRequest("ZR\013\006",11,6) + request.AddParm('O',0,self) + request.AddParm('i',1,x0) + request.AddParm('i',2,y0) + request.AddParm('u',3,width) + request.AddParm('u',4,height) + response = self.transport.Xcall(request) + def SelectVideoSource( + self, + mediaType, + webcamName, + devicePath, + updateSetup + ): + request = XCallRequest("ZR\013\007",11,7) + request.AddParm('O',0,self) + request.AddParm('e',1,Video.MEDIATYPE[mediaType]) + request.AddParm('S',2,webcamName) + request.AddParm('S',3,devicePath) + request.AddParm('b',4,updateSetup) + response = self.transport.Xcall(request) + def GetCurrentVideoDevice(self): + request = XCallRequest("ZR\013\012",11,10) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = (Video.MEDIATYPE[response.get(1)]), + result += (response.get(2,'')), + result += (response.get(3,'')), + return result + def OnLastFrameCapture( + self, + image, + width, + height + ): pass + event_handlers[1] = "OnLastFrameCaptureDispatch" + def OnLastFrameCaptureDispatch(self, parms): + cleanparms = (parms.get(1,'')), + cleanparms += (parms.get(2,0)), + cleanparms += (parms.get(3,0)), + self.OnLastFrameCapture(*cleanparms) +module_id2classes[11] = Video + +class Voicemail(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Voicemail %s" % (self.object_id, ) + module_id = 7 + def OnPropertyChange(self, property_name): pass + TYPE= {1:'INCOMING', 'INCOMING':1, 4:'DEFAULT_GREETING', 'DEFAULT_GREETING':4, 2:'CUSTOM_GREETING', 'CUSTOM_GREETING':2, 3:'OUTGOING', 'OUTGOING':3} + STATUS= { + 1 :'NOTDOWNLOADED', + 2 :'DOWNLOADING', + 3 :'UNPLAYED', + 4 :'BUFFERING', + 5 :'PLAYING', + 6 :'PLAYED', + 7 :'BLANK', + 8 :'RECORDING', + 9 :'RECORDED', + 10:'UPLOADING', + 11:'UPLOADED', + 12:'DELETING', + 13:'FAILED', + 14:'DELETING_FAILED', + 15:'CHECKING', + 16:'CANCELLED', + 'NOTDOWNLOADED' : 1, + 'DOWNLOADING' : 2, + 'UNPLAYED' : 3, + 'BUFFERING' : 4, + 'PLAYING' : 5, + 'PLAYED' : 6, + 'BLANK' : 7, + 'RECORDING' : 8, + 'RECORDED' : 9, + 'UPLOADING' :10, + 'UPLOADED' :11, + 'DELETING' :12, + 'FAILED' :13, + 'DELETING_FAILED' :14, + 'CHECKING' :15, + 'CANCELLED' :16 + } + FAILUREREASON= { + 1 :'MISC_ERROR', + 2 :'CONNECT_ERROR', + 3 :'NO_VOICEMAIL_CAPABILITY', + 4 :'NO_SUCH_VOICEMAIL', + 5 :'FILE_READ_ERROR', + 6 :'FILE_WRITE_ERROR', + 7 :'RECORDING_ERROR', + 8 :'PLAYBACK_ERROR', + 9 :'NO_PERMISSION', + 10:'RECEIVER_DISABLED_VOICEMAIL', + 11:'SENDER_NOT_AUTHORIZED', + 12:'SENDER_BLOCKED', + 'MISC_ERROR' : 1, + 'CONNECT_ERROR' : 2, + 'NO_VOICEMAIL_CAPABILITY' : 3, + 'NO_SUCH_VOICEMAIL' : 4, + 'FILE_READ_ERROR' : 5, + 'FILE_WRITE_ERROR' : 6, + 'RECORDING_ERROR' : 7, + 'PLAYBACK_ERROR' : 8, + 'NO_PERMISSION' : 9, + 'RECEIVER_DISABLED_VOICEMAIL' :10, + 'SENDER_NOT_AUTHORIZED' :11, + 'SENDER_BLOCKED' :12 + } + + def _Gettype(self): + return Voicemail.TYPE[self._Property("ZGd]\007",100, True)] + type = property(_Gettype) + propid2label[100] = "type" + def _Getpartner_handle(self): + return self._Property("ZGe]\007",101, True) + partner_handle = property(_Getpartner_handle) + propid2label[101] = "partner_handle" + def _Getpartner_dispname(self): + return self._Property("ZGf]\007",102, True) + partner_dispname = property(_Getpartner_dispname) + propid2label[102] = "partner_dispname" + def _Getstatus(self): + return Voicemail.STATUS[self._Property("ZGg]\007",103, True)] + status = property(_Getstatus) + propid2label[103] = "status" + def _Getfailurereason(self): + return Voicemail.FAILUREREASON[self._Property("ZGh]\007",104, True)] + failurereason = property(_Getfailurereason) + propid2label[104] = "failurereason" + def _Getsubject(self): + return self._Property("ZGi]\007",105, True) + subject = property(_Getsubject) + propid2label[105] = "subject" + def _Gettimestamp(self): + return self._Property("ZGj]\007",106, True) + timestamp = property(_Gettimestamp) + propid2label[106] = "timestamp" + def _Getduration(self): + return self._Property("ZGk]\007",107, True) + duration = property(_Getduration) + propid2label[107] = "duration" + def _Getallowed_duration(self): + return self._Property("ZGl]\007",108, True) + allowed_duration = property(_Getallowed_duration) + propid2label[108] = "allowed_duration" + def _Getplayback_progress(self): + return self._Property("ZGm]\007",109, True) + playback_progress = property(_Getplayback_progress) + propid2label[109] = "playback_progress" + def _Getconvo_id(self): + return module_id2classes[18](self._Property("ZG\276\006]\007",830, True),self.transport) + convo_id = property(_Getconvo_id) + propid2label[830] = "convo_id" + def _Getchatmsg_guid(self): + return self._Property("ZG\277\006]\007",831, True) + chatmsg_guid = property(_Getchatmsg_guid) + propid2label[831] = "chatmsg_guid" + + def StartRecording(self): + request = XCallRequest("ZR\007\003",7,3) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def StopRecording(self): + request = XCallRequest("ZR\007\004",7,4) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def StartPlayback(self): + request = XCallRequest("ZR\007\005",7,5) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def StopPlayback(self): + request = XCallRequest("ZR\007\006",7,6) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Delete(self): + request = XCallRequest("ZR\007\007",7,7) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Cancel(self): + request = XCallRequest("ZR\007\010",7,8) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def CheckPermission(self): + request = XCallRequest("ZR\007\015",7,13) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result +module_id2classes[7] = Voicemail + +class Sms(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Sms %s" % (self.object_id, ) + module_id = 12 + def OnPropertyChange(self, property_name): pass + TYPE= {1:'INCOMING', 'INCOMING':1, 2:'OUTGOING', 'OUTGOING':2, 3:'CONFIRMATION_CODE_REQUEST', 'CONFIRMATION_CODE_REQUEST':3, 4:'CONFIRMATION_CODE_SUBMIT', 'CONFIRMATION_CODE_SUBMIT':4} + STATUS= {1:'RECEIVED', 'RECEIVED':1, 2:'READ', 'READ':2, 3:'COMPOSING', 'COMPOSING':3, 4:'SENDING_TO_SERVER', 'SENDING_TO_SERVER':4, 5:'SENT_TO_SERVER', 'SENT_TO_SERVER':5, 6:'DELIVERED', 'DELIVERED':6, 7:'SOME_TARGETS_FAILED', 'SOME_TARGETS_FAILED':7, 8:'FAILED', 'FAILED':8} + FAILUREREASON= { + 1:'MISC_ERROR', + 2:'SERVER_CONNECT_FAILED', + 3:'NO_SMS_CAPABILITY', + 4:'INSUFFICIENT_FUNDS', + 5:'INVALID_CONFIRMATION_CODE', + 6:'USER_BLOCKED', + 7:'IP_BLOCKED', + 8:'NODE_BLOCKED', + 9:'NO_SENDERID_CAPABILITY', + 'MISC_ERROR' :1, + 'SERVER_CONNECT_FAILED' :2, + 'NO_SMS_CAPABILITY' :3, + 'INSUFFICIENT_FUNDS' :4, + 'INVALID_CONFIRMATION_CODE' :5, + 'USER_BLOCKED' :6, + 'IP_BLOCKED' :7, + 'NODE_BLOCKED' :8, + 'NO_SENDERID_CAPABILITY' :9 + } + TARGETSTATUS= {1:'TARGET_ANALYZING', 'TARGET_ANALYZING':1, 2:'TARGET_UNDEFINED', 'TARGET_UNDEFINED':2, 3:'TARGET_ACCEPTABLE', 'TARGET_ACCEPTABLE':3, 4:'TARGET_NOT_ROUTABLE', 'TARGET_NOT_ROUTABLE':4, 5:'TARGET_DELIVERY_PENDING', 'TARGET_DELIVERY_PENDING':5, 6:'TARGET_DELIVERY_SUCCESSFUL', 'TARGET_DELIVERY_SUCCESSFUL':6, 7:'TARGET_DELIVERY_FAILED', 'TARGET_DELIVERY_FAILED':7} + SETBODYRESULT= {0:'BODY_INVALID', 'BODY_INVALID':0, 1:'BODY_TRUNCATED', 'BODY_TRUNCATED':1, 2:'BODY_OK', 'BODY_OK':2, 3:'BODY_LASTCHAR_IGNORED', 'BODY_LASTCHAR_IGNORED':3} + + def _Gettype(self): + return Sms.TYPE[self._Property("ZG\276\001]\014",190, True)] + type = property(_Gettype) + propid2label[190] = "type" + def _Getstatus(self): + return Sms.STATUS[self._Property("ZG\277\001]\014",191, True)] + status = property(_Getstatus) + propid2label[191] = "status" + def _Getfailurereason(self): + return Sms.FAILUREREASON[self._Property("ZG\300\001]\014",192, True)] + failurereason = property(_Getfailurereason) + propid2label[192] = "failurereason" + def _Getis_failed_unseen(self): + return self._Property("ZG0]\014",48, True) + is_failed_unseen = property(_Getis_failed_unseen) + propid2label[48] = "is_failed_unseen" + def _Gettimestamp(self): + return self._Property("ZG\306\001]\014",198, True) + timestamp = property(_Gettimestamp) + propid2label[198] = "timestamp" + def _Getprice(self): + return self._Property("ZG\301\001]\014",193, True) + price = property(_Getprice) + propid2label[193] = "price" + def _Getprice_precision(self): + return self._Property("ZG1]\014",49, True) + price_precision = property(_Getprice_precision) + propid2label[49] = "price_precision" + def _Getprice_currency(self): + return self._Property("ZG\302\001]\014",194, True) + price_currency = property(_Getprice_currency) + propid2label[194] = "price_currency" + def _Getreply_to_number(self): + return self._Property("ZG\307\001]\014",199, True) + reply_to_number = property(_Getreply_to_number) + propid2label[199] = "reply_to_number" + def _Gettarget_numbers(self): + return self._Property("ZG\303\001]\014",195, True) + target_numbers = property(_Gettarget_numbers) + propid2label[195] = "target_numbers" + def _Gettarget_statuses(self): + return self._Property("ZG\304\001]\014",196, True) + target_statuses = property(_Gettarget_statuses) + propid2label[196] = "target_statuses" + def _Getbody(self): + return self._Property("ZG\305\001]\014",197, True) + body = property(_Getbody) + propid2label[197] = "body" + def _Getchatmsg_id(self): + return module_id2classes[9](self._Property("ZG\310\006]\014",840, True),self.transport) + chatmsg_id = property(_Getchatmsg_id) + propid2label[840] = "chatmsg_id" + + def GetTargetStatus( + self, + target + ): + request = XCallRequest("ZR\014\004",12,4) + request.AddParm('O',0,self) + request.AddParm('S',1,target) + response = self.transport.Xcall(request) + result = Sms.TARGETSTATUS[response.get(1)] + return result + def GetTargetPrice( + self, + target + ): + request = XCallRequest("ZR\014\015",12,13) + request.AddParm('O',0,self) + request.AddParm('S',1,target) + response = self.transport.Xcall(request) + result = response.get(1,0) + return result + def SetReplyTo( + self, + number + ): + request = XCallRequest("ZR\014\005",12,5) + request.AddParm('O',0,self) + request.AddParm('S',1,number) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def SetTargets( + self, + numbers + ): + request = XCallRequest("ZR\014\006",12,6) + request.AddParm('O',0,self) + request.AddParm('S',1,numbers) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def SetBody( + self, + text + ): + request = XCallRequest("ZR\014\007",12,7) + request.AddParm('O',0,self) + request.AddParm('S',1,text) + response = self.transport.Xcall(request) + result = (Sms.SETBODYRESULT[response.get(1)]), + result += (response.get(2,[])), + result += (response.get(3,0)), + return result + def GetBodyChunks(self): + request = XCallRequest("ZR\014\010",12,8) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = (response.get(1,[])), + result += (response.get(2,0)), + return result + def SetOrigin( + self, + origin + ): + request = XCallRequest("ZR\014\016",12,14) + request.AddParm('O',0,self) + request.AddParm('S',1,origin) + response = self.transport.Xcall(request) +module_id2classes[12] = Sms + +class Transfer(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Transfer %s" % (self.object_id, ) + module_id = 6 + def OnPropertyChange(self, property_name): pass + TYPE= {1:'INCOMING', 'INCOMING':1, 2:'OUTGOING', 'OUTGOING':2} + STATUS= { + 0 :'NEW', + 1 :'CONNECTING', + 2 :'WAITING_FOR_ACCEPT', + 3 :'TRANSFERRING', + 4 :'TRANSFERRING_OVER_RELAY', + 5 :'PAUSED', + 6 :'REMOTELY_PAUSED', + 7 :'CANCELLED', + 8 :'COMPLETED', + 9 :'FAILED', + 10:'PLACEHOLDER', + 11:'OFFER_FROM_OTHER_INSTANCE', + 12:'CANCELLED_BY_REMOTE', + 'NEW' : 0, + 'CONNECTING' : 1, + 'WAITING_FOR_ACCEPT' : 2, + 'TRANSFERRING' : 3, + 'TRANSFERRING_OVER_RELAY' : 4, + 'PAUSED' : 5, + 'REMOTELY_PAUSED' : 6, + 'CANCELLED' : 7, + 'COMPLETED' : 8, + 'FAILED' : 9, + 'PLACEHOLDER' :10, + 'OFFER_FROM_OTHER_INSTANCE' :11, + 'CANCELLED_BY_REMOTE' :12 + } + FAILUREREASON= { + 1 :'SENDER_NOT_AUTHORISED', + 2 :'REMOTELY_CANCELLED', + 3 :'FAILED_READ', + 4 :'FAILED_REMOTE_READ', + 5 :'FAILED_WRITE', + 6 :'FAILED_REMOTE_WRITE', + 7 :'REMOTE_DOES_NOT_SUPPORT_FT', + 8 :'REMOTE_OFFLINE_FOR_TOO_LONG', + 9 :'TOO_MANY_PARALLEL', + 10:'PLACEHOLDER_TIMEOUT', + 'SENDER_NOT_AUTHORISED' : 1, + 'REMOTELY_CANCELLED' : 2, + 'FAILED_READ' : 3, + 'FAILED_REMOTE_READ' : 4, + 'FAILED_WRITE' : 5, + 'FAILED_REMOTE_WRITE' : 6, + 'REMOTE_DOES_NOT_SUPPORT_FT' : 7, + 'REMOTE_OFFLINE_FOR_TOO_LONG' : 8, + 'TOO_MANY_PARALLEL' : 9, + 'PLACEHOLDER_TIMEOUT' :10 + } + + def _Gettype(self): + return Transfer.TYPE[self._Property("ZGP]\006",80, True)] + type = property(_Gettype) + propid2label[80] = "type" + def _Getpartner_handle(self): + return self._Property("ZGQ]\006",81, True) + partner_handle = property(_Getpartner_handle) + propid2label[81] = "partner_handle" + def _Getpartner_dispname(self): + return self._Property("ZGR]\006",82, True) + partner_dispname = property(_Getpartner_dispname) + propid2label[82] = "partner_dispname" + def _Getstatus(self): + return Transfer.STATUS[self._Property("ZGS]\006",83, True)] + status = property(_Getstatus) + propid2label[83] = "status" + def _Getfailurereason(self): + return Transfer.FAILUREREASON[self._Property("ZGT]\006",84, True)] + failurereason = property(_Getfailurereason) + propid2label[84] = "failurereason" + def _Getstarttime(self): + return self._Property("ZGU]\006",85, True) + starttime = property(_Getstarttime) + propid2label[85] = "starttime" + def _Getfinishtime(self): + return self._Property("ZGV]\006",86, True) + finishtime = property(_Getfinishtime) + propid2label[86] = "finishtime" + def _Getfilepath(self): + return self._Property("ZGW]\006",87, True) + filepath = property(_Getfilepath) + propid2label[87] = "filepath" + def _Getfilename(self): + return self._Property("ZGX]\006",88, True) + filename = property(_Getfilename) + propid2label[88] = "filename" + def _Getfilesize(self): + return self._Property("ZGY]\006",89, True) + filesize = property(_Getfilesize) + propid2label[89] = "filesize" + def _Getbytestransferred(self): + return self._Property("ZGZ]\006",90, True) + bytestransferred = property(_Getbytestransferred) + propid2label[90] = "bytestransferred" + def _Getbytespersecond(self): + return self._Property("ZG[]\006",91, True) + bytespersecond = property(_Getbytespersecond) + propid2label[91] = "bytespersecond" + def _Getchatmsg_guid(self): + return self._Property("ZG\134]\006",92, True) + chatmsg_guid = property(_Getchatmsg_guid) + propid2label[92] = "chatmsg_guid" + def _Getchatmsg_index(self): + return self._Property("ZG]]\006",93, True) + chatmsg_index = property(_Getchatmsg_index) + propid2label[93] = "chatmsg_index" + def _Getconvo_id(self): + return module_id2classes[18](self._Property("ZGb]\006",98, True),self.transport) + convo_id = property(_Getconvo_id) + propid2label[98] = "convo_id" + + def Accept( + self, + filenameWithPath + ): + request = XCallRequest("ZR\006\003",6,3) + request.AddParm('O',0,self) + request.AddParm('f',1,filenameWithPath) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def Pause(self): + request = XCallRequest("ZR\006\004",6,4) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Resume(self): + request = XCallRequest("ZR\006\005",6,5) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def Cancel(self): + request = XCallRequest("ZR\006\006",6,6) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) +module_id2classes[6] = Transfer + +class Account(Object): + event_handlers = {} + propid2label = {} + def _Init(self, object_id, transport): + Object._Init(self, object_id, transport) + def __str__(self): + return "Account %s" % (self.object_id, ) + module_id = 5 + def OnPropertyChange(self, property_name): pass + STATUS= {1:'LOGGED_OUT', 'LOGGED_OUT':1, 2:'LOGGED_OUT_AND_PWD_SAVED', 'LOGGED_OUT_AND_PWD_SAVED':2, 3:'CONNECTING_TO_P2P', 'CONNECTING_TO_P2P':3, 4:'CONNECTING_TO_SERVER', 'CONNECTING_TO_SERVER':4, 5:'LOGGING_IN', 'LOGGING_IN':5, 6:'INITIALIZING', 'INITIALIZING':6, 7:'LOGGED_IN', 'LOGGED_IN':7, 8:'LOGGING_OUT', 'LOGGING_OUT':8} + CBLSYNCSTATUS= {0:'CBL_INITIALIZING', 'CBL_INITIALIZING':0, 1:'CBL_INITIAL_SYNC_PENDING', 'CBL_INITIAL_SYNC_PENDING':1, 2:'CBL_SYNC_PENDING', 'CBL_SYNC_PENDING':2, 3:'CBL_SYNC_IN_PROGRESS', 'CBL_SYNC_IN_PROGRESS':3, 4:'CBL_IN_SYNC', 'CBL_IN_SYNC':4, 5:'CBL_SYNC_FAILED', 'CBL_SYNC_FAILED':5, 6:'CBL_REMOTE_SYNC_PENDING', 'CBL_REMOTE_SYNC_PENDING':6} + LOGOUTREASON= { + 1 :'LOGOUT_CALLED', + 2 :'HTTPS_PROXY_AUTH_FAILED', + 3 :'SOCKS_PROXY_AUTH_FAILED', + 4 :'P2P_CONNECT_FAILED', + 5 :'SERVER_CONNECT_FAILED', + 6 :'SERVER_OVERLOADED', + 7 :'DB_IN_USE', + 8 :'INVALID_SKYPENAME', + 9 :'INVALID_EMAIL', + 10:'UNACCEPTABLE_PASSWORD', + 11:'SKYPENAME_TAKEN', + 12:'REJECTED_AS_UNDERAGE', + 13:'NO_SUCH_IDENTITY', + 14:'INCORRECT_PASSWORD', + 15:'TOO_MANY_LOGIN_ATTEMPTS', + 16:'PASSWORD_HAS_CHANGED', + 17:'PERIODIC_UIC_UPDATE_FAILED', + 18:'DB_DISK_FULL', + 19:'DB_IO_ERROR', + 20:'DB_CORRUPT', + 21:'DB_FAILURE', + 22:'INVALID_APP_ID', + 23:'APP_ID_BLACKLISTED', + 24:'UNSUPPORTED_VERSION', + 'LOGOUT_CALLED' : 1, + 'HTTPS_PROXY_AUTH_FAILED' : 2, + 'SOCKS_PROXY_AUTH_FAILED' : 3, + 'P2P_CONNECT_FAILED' : 4, + 'SERVER_CONNECT_FAILED' : 5, + 'SERVER_OVERLOADED' : 6, + 'DB_IN_USE' : 7, + 'INVALID_SKYPENAME' : 8, + 'INVALID_EMAIL' : 9, + 'UNACCEPTABLE_PASSWORD' :10, + 'SKYPENAME_TAKEN' :11, + 'REJECTED_AS_UNDERAGE' :12, + 'NO_SUCH_IDENTITY' :13, + 'INCORRECT_PASSWORD' :14, + 'TOO_MANY_LOGIN_ATTEMPTS' :15, + 'PASSWORD_HAS_CHANGED' :16, + 'PERIODIC_UIC_UPDATE_FAILED' :17, + 'DB_DISK_FULL' :18, + 'DB_IO_ERROR' :19, + 'DB_CORRUPT' :20, + 'DB_FAILURE' :21, + 'INVALID_APP_ID' :22, + 'APP_ID_BLACKLISTED' :23, + 'UNSUPPORTED_VERSION' :24 + } + PWDCHANGESTATUS= {0:'PWD_OK', 'PWD_OK':0, 1:'PWD_CHANGING', 'PWD_CHANGING':1, 2:'PWD_INVALID_OLD_PASSWORD', 'PWD_INVALID_OLD_PASSWORD':2, 3:'PWD_SERVER_CONNECT_FAILED', 'PWD_SERVER_CONNECT_FAILED':3, 4:'PWD_OK_BUT_CHANGE_SUGGESTED', 'PWD_OK_BUT_CHANGE_SUGGESTED':4, 5:'PWD_MUST_DIFFER_FROM_OLD', 'PWD_MUST_DIFFER_FROM_OLD':5, 6:'PWD_INVALID_NEW_PWD', 'PWD_INVALID_NEW_PWD':6, 7:'PWD_MUST_LOG_IN_TO_CHANGE', 'PWD_MUST_LOG_IN_TO_CHANGE':7} + COMMITSTATUS= {1:'COMMITTED', 'COMMITTED':1, 2:'COMMITTING_TO_SERVER', 'COMMITTING_TO_SERVER':2, 3:'COMMIT_FAILED', 'COMMIT_FAILED':3} + CHATPOLICY= {0:'EVERYONE_CAN_ADD', 'EVERYONE_CAN_ADD':0, 2:'BUDDIES_OR_AUTHORIZED_CAN_ADD', 'BUDDIES_OR_AUTHORIZED_CAN_ADD':2} + SKYPECALLPOLICY= {0:'EVERYONE_CAN_CALL', 'EVERYONE_CAN_CALL':0, 2:'BUDDIES_OR_AUTHORIZED_CAN_CALL', 'BUDDIES_OR_AUTHORIZED_CAN_CALL':2} + PSTNCALLPOLICY= {0:'ALL_NUMBERS_CAN_CALL', 'ALL_NUMBERS_CAN_CALL':0, 1:'DISCLOSED_NUMBERS_CAN_CALL', 'DISCLOSED_NUMBERS_CAN_CALL':1, 2:'BUDDY_NUMBERS_CAN_CALL', 'BUDDY_NUMBERS_CAN_CALL':2} + AVATARPOLICY= {0:'BUDDIES_OR_AUTHORIZED_CAN_SEE', 'BUDDIES_OR_AUTHORIZED_CAN_SEE':0, 2:'EVERYONE_CAN_SEE', 'EVERYONE_CAN_SEE':2} + BUDDYCOUNTPOLICY= {0:'DISCLOSE_TO_AUTHORIZED', 'DISCLOSE_TO_AUTHORIZED':0, 1:'DISCLOSE_TO_NOONE', 'DISCLOSE_TO_NOONE':1} + TIMEZONEPOLICY= {0:'TZ_AUTOMATIC', 'TZ_AUTOMATIC':0, 1:'TZ_MANUAL', 'TZ_MANUAL':1, 2:'TZ_UNDISCLOSED', 'TZ_UNDISCLOSED':2} + WEBPRESENCEPOLICY= {0:'WEBPRESENCE_DISABLED', 'WEBPRESENCE_DISABLED':0, 1:'WEBPRESENCE_ENABLED', 'WEBPRESENCE_ENABLED':1} + PHONENUMBERSPOLICY= {0:'PHONENUMBERS_VISIBLE_FOR_BUDDIES', 'PHONENUMBERS_VISIBLE_FOR_BUDDIES':0, 1:'PHONENUMBERS_VISIBLE_FOR_EVERYONE', 'PHONENUMBERS_VISIBLE_FOR_EVERYONE':1} + VOICEMAILPOLICY= {0:'VOICEMAIL_ENABLED', 'VOICEMAIL_ENABLED':0, 1:'VOICEMAIL_DISABLED', 'VOICEMAIL_DISABLED':1} + AUTHREQUESTPOLICY= {0:'AUTHREQUEST_ENABLED', 'AUTHREQUEST_ENABLED':0, 5:'CHAT_PARTICIPANTS_CAN_AUTHREQ', 'CHAT_PARTICIPANTS_CAN_AUTHREQ':5, 9:'AUTHREQUEST_DISABLED', 'AUTHREQUEST_DISABLED':9} + CAPABILITYSTATUS= {0:'NO_CAPABILITY', 'NO_CAPABILITY':0, 1:'CAPABILITY_EXISTS', 'CAPABILITY_EXISTS':1, 2:'FIRST_EXPIRY_WARNING', 'FIRST_EXPIRY_WARNING':2, 3:'SECOND_EXPIRY_WARNING', 'SECOND_EXPIRY_WARNING':3, 4:'FINAL_EXPIRY_WARNING', 'FINAL_EXPIRY_WARNING':4} + + def _Getstatus(self): + return Account.STATUS[self._Property("ZGF]\005",70, True)] + status = property(_Getstatus) + propid2label[70] = "status" + def _Getpwdchangestatus(self): + return Account.PWDCHANGESTATUS[self._Property("ZGG]\005",71, True)] + pwdchangestatus = property(_Getpwdchangestatus) + propid2label[71] = "pwdchangestatus" + def _Getlogoutreason(self): + return Account.LOGOUTREASON[self._Property("ZGI]\005",73, True)] + logoutreason = property(_Getlogoutreason) + propid2label[73] = "logoutreason" + def _Getcommitstatus(self): + return Account.COMMITSTATUS[self._Property("ZGN]\005",78, True)] + commitstatus = property(_Getcommitstatus) + propid2label[78] = "commitstatus" + def _Getsuggested_skypename(self): + return self._Property("ZGH]\005",72, True) + suggested_skypename = property(_Getsuggested_skypename) + propid2label[72] = "suggested_skypename" + def _Getskypeout_balance_currency(self): + return self._Property("ZGJ]\005",74, True) + skypeout_balance_currency = property(_Getskypeout_balance_currency) + propid2label[74] = "skypeout_balance_currency" + def _Getskypeout_balance(self): + return self._Property("ZGK]\005",75, True) + skypeout_balance = property(_Getskypeout_balance) + propid2label[75] = "skypeout_balance" + def _Getskypeout_precision(self): + return self._Property("ZG\244\006]\005",804, True) + skypeout_precision = property(_Getskypeout_precision) + propid2label[804] = "skypeout_precision" + def _Getskypein_numbers(self): + return self._Property("ZGL]\005",76, True) + skypein_numbers = property(_Getskypein_numbers) + propid2label[76] = "skypein_numbers" + def _Getcblsyncstatus(self): + return Account.CBLSYNCSTATUS[self._Property("ZGO]\005",79, True)] + cblsyncstatus = property(_Getcblsyncstatus) + propid2label[79] = "cblsyncstatus" + def _Getoffline_callforward(self): + return self._Property("ZGM]\005",77, True) + offline_callforward = property(_Getoffline_callforward) + propid2label[77] = "offline_callforward" + def _Getchat_policy(self): + return Account.CHATPOLICY[self._Property("ZG\240\001]\005",160, True)] + chat_policy = property(_Getchat_policy) + propid2label[160] = "chat_policy" + def _Getskype_call_policy(self): + return Account.SKYPECALLPOLICY[self._Property("ZG\241\001]\005",161, True)] + skype_call_policy = property(_Getskype_call_policy) + propid2label[161] = "skype_call_policy" + def _Getpstn_call_policy(self): + return Account.PSTNCALLPOLICY[self._Property("ZG\242\001]\005",162, True)] + pstn_call_policy = property(_Getpstn_call_policy) + propid2label[162] = "pstn_call_policy" + def _Getavatar_policy(self): + return Account.AVATARPOLICY[self._Property("ZG\243\001]\005",163, True)] + avatar_policy = property(_Getavatar_policy) + propid2label[163] = "avatar_policy" + def _Getbuddycount_policy(self): + return Account.BUDDYCOUNTPOLICY[self._Property("ZG\244\001]\005",164, True)] + buddycount_policy = property(_Getbuddycount_policy) + propid2label[164] = "buddycount_policy" + def _Gettimezone_policy(self): + return Account.TIMEZONEPOLICY[self._Property("ZG\245\001]\005",165, True)] + timezone_policy = property(_Gettimezone_policy) + propid2label[165] = "timezone_policy" + def _Getwebpresence_policy(self): + return Account.WEBPRESENCEPOLICY[self._Property("ZG\246\001]\005",166, True)] + webpresence_policy = property(_Getwebpresence_policy) + propid2label[166] = "webpresence_policy" + def _Getphonenumbers_policy(self): + return Account.PHONENUMBERSPOLICY[self._Property("ZG\250\001]\005",168, True)] + phonenumbers_policy = property(_Getphonenumbers_policy) + propid2label[168] = "phonenumbers_policy" + def _Getvoicemail_policy(self): + return Account.VOICEMAILPOLICY[self._Property("ZG\251\001]\005",169, True)] + voicemail_policy = property(_Getvoicemail_policy) + propid2label[169] = "voicemail_policy" + def _Getauthrequest_policy(self): + return Account.AUTHREQUESTPOLICY[self._Property("ZG\260\001]\005",176, True)] + authrequest_policy = property(_Getauthrequest_policy) + propid2label[176] = "authrequest_policy" + def _Getpartner_optedout(self): + return self._Property("ZG\205\006]\005",773, True) + partner_optedout = property(_Getpartner_optedout) + propid2label[773] = "partner_optedout" + def _Getservice_provider_info(self): + return self._Property("ZG\240\006]\005",800, True) + service_provider_info = property(_Getservice_provider_info) + propid2label[800] = "service_provider_info" + def _Getregistration_timestamp(self): + return self._Property("ZG\241\006]\005",801, True) + registration_timestamp = property(_Getregistration_timestamp) + propid2label[801] = "registration_timestamp" + def _Getnr_of_other_instances(self): + return self._Property("ZG\242\006]\005",802, True) + nr_of_other_instances = property(_Getnr_of_other_instances) + propid2label[802] = "nr_of_other_instances" + def _Getskypename(self): + return self._Property("ZG\004]\005",4, True) + skypename = property(_Getskypename) + propid2label[4] = "skypename" + def _Getfullname(self): + return self._Property("ZG\005]\005",5, True) + fullname = property(_Getfullname) + propid2label[5] = "fullname" + def _Getbirthday(self): + return self._Property("ZG\007]\005",7, True) + birthday = property(_Getbirthday) + propid2label[7] = "birthday" + def _Getgender(self): + return self._Property("ZG\010]\005",8, True) + gender = property(_Getgender) + propid2label[8] = "gender" + def _Getlanguages(self): + return self._Property("ZG\011]\005",9, True) + languages = property(_Getlanguages) + propid2label[9] = "languages" + def _Getcountry(self): + return self._Property("ZG\012]\005",10, True) + country = property(_Getcountry) + propid2label[10] = "country" + def _Getprovince(self): + return self._Property("ZG\013]\005",11, True) + province = property(_Getprovince) + propid2label[11] = "province" + def _Getcity(self): + return self._Property("ZG\014]\005",12, True) + city = property(_Getcity) + propid2label[12] = "city" + def _Getphone_home(self): + return self._Property("ZG\015]\005",13, True) + phone_home = property(_Getphone_home) + propid2label[13] = "phone_home" + def _Getphone_office(self): + return self._Property("ZG\016]\005",14, True) + phone_office = property(_Getphone_office) + propid2label[14] = "phone_office" + def _Getphone_mobile(self): + return self._Property("ZG\017]\005",15, True) + phone_mobile = property(_Getphone_mobile) + propid2label[15] = "phone_mobile" + def _Getemails(self): + return self._Property("ZG\020]\005",16, True) + emails = property(_Getemails) + propid2label[16] = "emails" + def _Gethomepage(self): + return self._Property("ZG\021]\005",17, True) + homepage = property(_Gethomepage) + propid2label[17] = "homepage" + def _Getabout(self): + return self._Property("ZG\022]\005",18, True) + about = property(_Getabout) + propid2label[18] = "about" + def _Getprofile_timestamp(self): + return self._Property("ZG\023]\005",19, True) + profile_timestamp = property(_Getprofile_timestamp) + propid2label[19] = "profile_timestamp" + def _Getmood_text(self): + return self._Property("ZG\032]\005",26, True) + mood_text = property(_Getmood_text) + propid2label[26] = "mood_text" + def _Gettimezone(self): + return self._Property("ZG\033]\005",27, True) + timezone = property(_Gettimezone) + propid2label[27] = "timezone" + def _Getnrof_authed_buddies(self): + return self._Property("ZG\034]\005",28, True) + nrof_authed_buddies = property(_Getnrof_authed_buddies) + propid2label[28] = "nrof_authed_buddies" + def _Getavailability(self): + return Contact.AVAILABILITY[self._Property("ZG\042]\005",34, True)] + availability = property(_Getavailability) + propid2label[34] = "availability" + def _Getavatar_image(self): + return self._Property("ZG%]\005",37, True) + avatar_image = property(_Getavatar_image) + propid2label[37] = "avatar_image" + def _Getavatar_timestamp(self): + return self._Property("ZG\266\001]\005",182, True) + avatar_timestamp = property(_Getavatar_timestamp) + propid2label[182] = "avatar_timestamp" + def _Getmood_timestamp(self): + return self._Property("ZG\267\001]\005",183, True) + mood_timestamp = property(_Getmood_timestamp) + propid2label[183] = "mood_timestamp" + def _Getrich_mood_text(self): + return self._Property("ZG\315\001]\005",205, True) + rich_mood_text = property(_Getrich_mood_text) + propid2label[205] = "rich_mood_text" + + def GetStatusWithProgress(self): + request = XCallRequest("ZR\005\001",5,1) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = (Account.STATUS[response.get(1)]), + result += (response.get(2,0)), + return result + def Login( + self, + setAvailabilityTo + ): + request = XCallRequest("ZR\005\005",5,5) + request.AddParm('O',0,self) + request.AddParm('e',1,Contact.AVAILABILITY[setAvailabilityTo]) + response = self.transport.Xcall(request) + def LoginWithPassword( + self, + password, + savePwd, + saveDataLocally + ): + request = XCallRequest("ZR\005\006",5,6) + request.AddParm('O',0,self) + request.AddParm('S',1,password) + request.AddParm('b',2,savePwd) + request.AddParm('b',3,saveDataLocally) + response = self.transport.Xcall(request) + def Register( + self, + password, + savePwd, + saveDataLocally, + email, + allowSpam + ): + request = XCallRequest("ZR\005\007",5,7) + request.AddParm('O',0,self) + request.AddParm('S',1,password) + request.AddParm('b',2,savePwd) + request.AddParm('b',3,saveDataLocally) + request.AddParm('S',4,email) + request.AddParm('b',5,allowSpam) + response = self.transport.Xcall(request) + def Logout( + self, + clearSavedPwd + ): + request = XCallRequest("ZR\005\010",5,8) + request.AddParm('O',0,self) + request.AddParm('b',1,clearSavedPwd) + response = self.transport.Xcall(request) + def ChangePassword( + self, + oldPassword, + newPassword, + savePwd + ): + request = XCallRequest("ZR\005\013",5,11) + request.AddParm('O',0,self) + request.AddParm('S',1,oldPassword) + request.AddParm('S',2,newPassword) + request.AddParm('b',3,savePwd) + response = self.transport.Xcall(request) + def SetPasswordSaved( + self, + savePwd + ): + request = XCallRequest("ZR\005\031",5,25) + request.AddParm('O',0,self) + request.AddParm('b',1,savePwd) + response = self.transport.Xcall(request) + def SetServersideIntProperty( + self, + propKey, + value + ): + request = XCallRequest("ZR\005\014",5,12) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(propKey,1)) + request.AddParm('u',2,value) + response = self.transport.Xcall(request) + def SetServersideStrProperty( + self, + propKey, + value + ): + request = XCallRequest("ZR\005\015",5,13) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(propKey,0)) + request.AddParm('S',2,value) + response = self.transport.Xcall(request) + def CancelServerCommit(self): + request = XCallRequest("ZR\005\017",5,15) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def SetIntProperty( + self, + propKey, + value + ): + request = XCallRequest("ZR\005\020",5,16) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(propKey,1)) + request.AddParm('u',2,value) + response = self.transport.Xcall(request) + def SetStrProperty( + self, + propKey, + value + ): + request = XCallRequest("ZR\005\021",5,17) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(propKey,0)) + request.AddParm('S',2,value) + response = self.transport.Xcall(request) + def SetBinProperty( + self, + propKey, + value + ): + request = XCallRequest("ZR\005\022",5,18) + request.AddParm('O',0,self) + request.AddParm('e',1,self._propkey(propKey,2)) + request.AddParm('B',2,value) + response = self.transport.Xcall(request) + def SetAvailability( + self, + availability + ): + request = XCallRequest("ZR\005\023",5,19) + request.AddParm('O',0,self) + request.AddParm('e',1,Contact.AVAILABILITY[availability]) + response = self.transport.Xcall(request) + def SetStandby( + self, + standby + ): + request = XCallRequest("ZR\005\220\001",5,144) + request.AddParm('O',0,self) + request.AddParm('b',1,standby) + response = self.transport.Xcall(request) + def GetCapabilityStatus( + self, + capability + ): + request = XCallRequest("ZR\005\025",5,21) + request.AddParm('O',0,self) + request.AddParm('e',1,Contact.CAPABILITY[capability]) + response = self.transport.Xcall(request) + result = (Account.CAPABILITYSTATUS[response.get(1)]), + result += (response.get(2,0)), + return result + def GetSkypenameHash(self): + request = XCallRequest("ZR\005\026",5,22) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def Delete(self): + request = XCallRequest("ZR\005\030",5,24) + request.AddParm('O',0,self) + response = self.transport.Xcall(request) + def mget_profile(self): + self.multiget("ZG\004,\005,\032,\020,\015,\016,\017,\007,\010,\011,\012,\013,\014,\021,\022,\033]\005") +module_id2classes[5] = Account +class SkyLib(object): + module_id = 0 + event_handlers = {} + def __init__(self, transport): + self.object_id = 0 + self.transport = transport + transport.setRoot(self) + ''' Stop background operations (background threads). Might take some time. + ''' + def Stop(self): + self.transport.Stop() + + def _propkey(self, propname, t): + for p,l in Contact.propid2label.items(): + if l == propname: return p*4+t + for p,l in Account.propid2label.items(): + if l == propname: return p*4+t + raise Error('Unknown ' + propname) + def GetHardwiredContactGroup( + self, + type + ): + request = XCallRequest("ZR\000\001",0,1) + request.AddParm('e',1,ContactGroup.TYPE[type]) + response = self.transport.Xcall(request) + result = module_id2classes[10](response.get(1),self.transport) + return result + def GetCustomContactGroups(self): + request = XCallRequest("ZR\000\002",0,2) + response = self.transport.Xcall(request) + result = [module_id2classes[10](oid,self.transport) for oid in response.get(1,[])] + return result + def CreateCustomContactGroup(self): + request = XCallRequest("ZR\000\003",0,3) + response = self.transport.Xcall(request) + result = module_id2classes[10](response.get(1),self.transport) + return result + def OnNewCustomContactGroup( + self, + group + ): pass + event_handlers[1] = "OnNewCustomContactGroupDispatch" + def OnNewCustomContactGroupDispatch(self, parms): + cleanparms = module_id2classes[10](parms.get(1),self.transport) + self.OnNewCustomContactGroup(cleanparms) + def mget_info_from_Contacts(self, objects): + self.transport.multiget("ZG\042,\025]\002",objects) + def GetContactType( + self, + identity + ): + request = XCallRequest("ZR\000\005",0,5) + request.AddParm('S',1,identity) + response = self.transport.Xcall(request) + result = Contact.TYPE[response.get(1)] + return result + def GetContact( + self, + identity + ): + request = XCallRequest("ZR\000\006",0,6) + request.AddParm('S',1,identity) + response = self.transport.Xcall(request) + result = module_id2classes[2](response.get(2),self.transport) + return result + def FindContactByPstnNumber( + self, + number + ): + request = XCallRequest("ZR\000\010",0,8) + request.AddParm('S',1,number) + response = self.transport.Xcall(request) + result = (response.get(1,False)), + result += (module_id2classes[2](response.get(2),self.transport)), + result += (response.get(3,0)), + return result + IDENTITYTYPE= { + 0:'UNRECOGNIZED', + 1:'SKYPE', + 2:'SKYPE_MYSELF', + 3:'SKYPE_UNDISCLOSED', + 4:'PSTN', + 5:'PSTN_EMERGENCY', + 6:'PSTN_FREE', + 7:'PSTN_UNDISCLOSED', + 8:'CONFERENCE', + 9:'EXTERNAL', + 'UNRECOGNIZED' :0, + 'SKYPE' :1, + 'SKYPE_MYSELF' :2, + 'SKYPE_UNDISCLOSED' :3, + 'PSTN' :4, + 'PSTN_EMERGENCY' :5, + 'PSTN_FREE' :6, + 'PSTN_UNDISCLOSED' :7, + 'CONFERENCE' :8, + 'EXTERNAL' :9 + } + def GetIdentityType( + self, + identity + ): + request = XCallRequest("ZR\000\023",0,19) + request.AddParm('S',1,identity) + response = self.transport.Xcall(request) + result = SkyLib.IDENTITYTYPE[response.get(1)] + return result + NORMALIZERESULT= {0:'IDENTITY_OK', 'IDENTITY_OK':0, 1:'IDENTITY_EMPTY', 'IDENTITY_EMPTY':1, 2:'IDENTITY_TOO_LONG', 'IDENTITY_TOO_LONG':2, 3:'IDENTITY_CONTAINS_INVALID_CHAR', 'IDENTITY_CONTAINS_INVALID_CHAR':3, 4:'PSTN_NUMBER_TOO_SHORT', 'PSTN_NUMBER_TOO_SHORT':4, 5:'PSTN_NUMBER_HAS_INVALID_PREFIX', 'PSTN_NUMBER_HAS_INVALID_PREFIX':5, 6:'SKYPENAME_STARTS_WITH_NONALPHA', 'SKYPENAME_STARTS_WITH_NONALPHA':6, 7:'SKYPENAME_SHORTER_THAN_6_CHARS', 'SKYPENAME_SHORTER_THAN_6_CHARS':7} + def NormalizeIdentity( + self, + original, + isNewSkypeName + ): + request = XCallRequest("ZR\000\011",0,9) + request.AddParm('S',1,original) + request.AddParm('b',2,isNewSkypeName) + response = self.transport.Xcall(request) + result = (SkyLib.NORMALIZERESULT[response.get(1)]), + result += (response.get(2,'')), + return result + def NormalizePSTNWithCountry( + self, + original, + countryPrefix + ): + request = XCallRequest("ZR\000\315\001",0,205) + request.AddParm('S',1,original) + request.AddParm('u',2,countryPrefix) + response = self.transport.Xcall(request) + result = (SkyLib.NORMALIZERESULT[response.get(1)]), + result += (response.get(2,'')), + return result + def OnContactOnlineAppearance( + self, + contact + ): pass + event_handlers[2] = "OnContactOnlineAppearanceDispatch" + def OnContactOnlineAppearanceDispatch(self, parms): + cleanparms = module_id2classes[2](parms.get(1),self.transport) + self.OnContactOnlineAppearance(cleanparms) + def OnContactGoneOffline( + self, + contact + ): pass + event_handlers[3] = "OnContactGoneOfflineDispatch" + def OnContactGoneOfflineDispatch(self, parms): + cleanparms = module_id2classes[2](parms.get(1),self.transport) + self.OnContactGoneOffline(cleanparms) + def GetOptimalAgeRanges(self): + request = XCallRequest("ZR\000M",0,77) + response = self.transport.Xcall(request) + result = response.get(1,[]) + return result + def CreateContactSearch(self): + request = XCallRequest("ZR\000\012",0,10) + response = self.transport.Xcall(request) + result = module_id2classes[1](response.get(1),self.transport) + return result + def CreateBasicContactSearch( + self, + text + ): + request = XCallRequest("ZR\000\013",0,11) + request.AddParm('S',1,text) + response = self.transport.Xcall(request) + result = module_id2classes[1](response.get(1),self.transport) + return result + def CreateIdentitySearch( + self, + identity + ): + request = XCallRequest("ZR\000\014",0,12) + request.AddParm('S',1,identity) + response = self.transport.Xcall(request) + result = module_id2classes[1](response.get(1),self.transport) + return result + TRANSFER_SENDFILE_ERROR= {0:'TRANSFER_OPEN_SUCCESS', 'TRANSFER_OPEN_SUCCESS':0, 1:'TRANSFER_BAD_FILENAME', 'TRANSFER_BAD_FILENAME':1, 2:'TRANSFER_OPEN_FAILED', 'TRANSFER_OPEN_FAILED':2, 3:'TRANSFER_TOO_MANY_PARALLEL', 'TRANSFER_TOO_MANY_PARALLEL':3} + def mget_info_from_Participants(self, objects): + self.transport.multiget("ZG\247\007,\246\007,\250\007,\244\007,\266\007,\252\007]\023",objects) + def mget_info_from_Conversations(self, objects): + self.transport.multiget("ZG\234\007,\320\007,\240\007]\022",objects) + def CreateConference(self): + request = XCallRequest("ZR\000\015",0,13) + response = self.transport.Xcall(request) + result = module_id2classes[18](response.get(1),self.transport) + return result + def GetConversationByIdentity( + self, + convoIdentity + ): + request = XCallRequest("ZR\000\017",0,15) + request.AddParm('S',1,convoIdentity) + response = self.transport.Xcall(request) + result = module_id2classes[18](response.get(1),self.transport) + return result + def GetConversationByParticipants( + self, + participantIdentities, + createIfNonExisting, + ignoreBookmarkedOrNamed + ): + request = XCallRequest("ZR\000\020",0,16) + request.AddParm('S',1,participantIdentities) + request.AddParm('b',2,createIfNonExisting) + request.AddParm('b',3,ignoreBookmarkedOrNamed) + response = self.transport.Xcall(request) + result = module_id2classes[18](response.get(1),self.transport) + return result + def GetConversationByBlob( + self, + joinBlob, + alsoJoin + ): + request = XCallRequest("ZR\000\021",0,17) + request.AddParm('S',1,joinBlob) + request.AddParm('b',2,alsoJoin) + response = self.transport.Xcall(request) + result = module_id2classes[18](response.get(1),self.transport) + return result + def GetConversationList( + self, + type + ): + request = XCallRequest("ZR\000\022",0,18) + request.AddParm('e',1,Conversation.LIST_TYPE[type]) + response = self.transport.Xcall(request) + result = [module_id2classes[18](oid,self.transport) for oid in response.get(1,[])] + return result + def OnConversationListChange( + self, + conversation, + type, + added + ): pass + event_handlers[4] = "OnConversationListChangeDispatch" + def OnConversationListChangeDispatch(self, parms): + cleanparms = (module_id2classes[18](parms.get(1),self.transport)), + cleanparms += (Conversation.LIST_TYPE[parms.get(2)]), + cleanparms += (parms.get(3,False)), + self.OnConversationListChange(*cleanparms) + def mget_info_from_Messages(self, objects): + self.transport.multiget("ZG\300\007,{,\301\007,\177,y]\011",objects) + def GetMessageByGuid( + self, + guid + ): + request = XCallRequest("ZR\000\025",0,21) + request.AddParm('B',1,guid) + response = self.transport.Xcall(request) + result = module_id2classes[9](response.get(1),self.transport) + return result + def OnMessage( + self, + message, + changesInboxTimestamp, + supersedesHistoryMessage, + conversation + ): pass + event_handlers[5] = "OnMessageDispatch" + def OnMessageDispatch(self, parms): + cleanparms = (module_id2classes[9](parms.get(1),self.transport)), + cleanparms += (parms.get(2,False)), + cleanparms += (module_id2classes[9](parms.get(3),self.transport)), + cleanparms += (module_id2classes[18](parms.get(4),self.transport)), + self.OnMessage(*cleanparms) + cleanparms[3].OnMessage(cleanparms[0]) + def GetAvailableVideoDevices(self): + request = XCallRequest("ZR\000P",0,80) + response = self.transport.Xcall(request) + result = (response.get(1,[])), + result += (response.get(2,[])), + result += (response.get(3,0)), + return result + def HasVideoDeviceCapability( + self, + deviceName, + devicePath, + cap + ): + request = XCallRequest("ZR\000!",0,33) + request.AddParm('S',1,deviceName) + request.AddParm('S',2,devicePath) + request.AddParm('e',3,Video.VIDEO_DEVICE_CAPABILITY[cap]) + response = self.transport.Xcall(request) + def DisplayVideoDeviceTuningDialog( + self, + deviceName, + devicePath + ): + request = XCallRequest("ZR\000\042",0,34) + request.AddParm('S',1,deviceName) + request.AddParm('S',2,devicePath) + response = self.transport.Xcall(request) + def GetLocalVideo( + self, + type, + deviceName, + devicePath + ): + request = XCallRequest("ZR\000\203\001",0,131) + request.AddParm('e',1,Video.MEDIATYPE[type]) + request.AddParm('S',2,deviceName) + request.AddParm('S',3,devicePath) + response = self.transport.Xcall(request) + result = module_id2classes[11](response.get(1),self.transport) + return result + def GetPreviewVideo( + self, + type, + deviceName, + devicePath + ): + request = XCallRequest("ZR\000#",0,35) + request.AddParm('e',1,Video.MEDIATYPE[type]) + request.AddParm('S',2,deviceName) + request.AddParm('S',3,devicePath) + response = self.transport.Xcall(request) + result = module_id2classes[11](response.get(1),self.transport) + return result + def VideoCommand( + self, + command + ): + request = XCallRequest("ZR\000;",0,59) + request.AddParm('S',1,command) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def OnAvailableVideoDeviceListChange(self): pass + def OnAvailableVideoDeviceListChangeDispatch(self, parms): self.OnAvailableVideoDeviceListChange() + event_handlers[7] = "OnAvailableVideoDeviceListChangeDispatch" + def GetGreeting( + self, + skypeName + ): + request = XCallRequest("ZR\000-",0,45) + request.AddParm('S',1,skypeName) + response = self.transport.Xcall(request) + result = module_id2classes[7](response.get(1),self.transport) + return result + PREPARESOUNDRESULT= {0:'PREPARESOUND_SUCCESS', 'PREPARESOUND_SUCCESS':0, 1:'PREPARESOUND_MISC_ERROR', 'PREPARESOUND_MISC_ERROR':1, 2:'PREPARESOUND_FILE_NOT_FOUND', 'PREPARESOUND_FILE_NOT_FOUND':2, 3:'PREPARESOUND_FILE_TOO_BIG', 'PREPARESOUND_FILE_TOO_BIG':3, 4:'PREPARESOUND_FILE_READ_ERROR', 'PREPARESOUND_FILE_READ_ERROR':4, 5:'PREPARESOUND_UNSUPPORTED_FILE_FORMAT', 'PREPARESOUND_UNSUPPORTED_FILE_FORMAT':5, 6:'PREPARESOUND_PLAYBACK_NOT_SUPPORTED', 'PREPARESOUND_PLAYBACK_NOT_SUPPORTED':6} + AUDIODEVICE_CAPABILIES= { + 1 :'HAS_VIDEO_CAPTURE', + 2 :'HAS_USB_INTERFACE', + 4 :'POSSIBLY_HEADSET', + 8 :'HAS_AUDIO_CAPTURE', + 16 :'HAS_AUDIO_RENDERING', + 32 :'HAS_LOWBANDWIDTH_CAPTURE', + 64 :'IS_WEBCAM', + 128 :'IS_HEADSET', + 256 :'POSSIBLY_WEBCAM', + 2048:'HAS_VIDEO_RENDERING', + 4096:'HAS_BLUETOOTH_INTERFACE', + 'HAS_VIDEO_CAPTURE' : 1, + 'HAS_USB_INTERFACE' : 2, + 'POSSIBLY_HEADSET' : 4, + 'HAS_AUDIO_CAPTURE' : 8, + 'HAS_AUDIO_RENDERING' : 16, + 'HAS_LOWBANDWIDTH_CAPTURE' : 32, + 'IS_WEBCAM' : 64, + 'IS_HEADSET' : 128, + 'POSSIBLY_WEBCAM' : 256, + 'HAS_VIDEO_RENDERING' :2048, + 'HAS_BLUETOOTH_INTERFACE' :4096 + } + def PlayStart( + self, + soundid, + sound, + loop, + useCallOutDevice + ): + request = XCallRequest("ZR\000\060",0,48) + request.AddParm('u',1,soundid) + request.AddParm('B',2,sound) + request.AddParm('b',3,loop) + request.AddParm('b',4,useCallOutDevice) + response = self.transport.Xcall(request) + def PlayStartFromFile( + self, + soundid, + datafile, + loop, + useCallOutDevice + ): + request = XCallRequest("ZR\000\324\001",0,212) + request.AddParm('u',1,soundid) + request.AddParm('f',2,datafile) + request.AddParm('b',3,loop) + request.AddParm('b',4,useCallOutDevice) + response = self.transport.Xcall(request) + result = SkyLib.PREPARESOUNDRESULT[response.get(1)] + return result + def PlayStop( + self, + soundid + ): + request = XCallRequest("ZR\000\061",0,49) + request.AddParm('u',1,soundid) + response = self.transport.Xcall(request) + def StartRecordingTest( + self, + recordAndPlaybackData + ): + request = XCallRequest("ZR\000\062",0,50) + request.AddParm('b',1,recordAndPlaybackData) + response = self.transport.Xcall(request) + def StopRecordingTest(self): + request = XCallRequest("ZR\000\063",0,51) + response = self.transport.Xcall(request) + def GetAvailableOutputDevices(self): + request = XCallRequest("ZR\000\065",0,53) + response = self.transport.Xcall(request) + result = (response.get(1,[])), + result += (response.get(2,[])), + result += (response.get(3,[])), + return result + def GetAvailableRecordingDevices(self): + request = XCallRequest("ZR\000\066",0,54) + response = self.transport.Xcall(request) + result = (response.get(1,[])), + result += (response.get(2,[])), + result += (response.get(3,[])), + return result + def SelectSoundDevices( + self, + callInDevice, + callOutDevice, + waveOutDevice + ): + request = XCallRequest("ZR\000\067",0,55) + request.AddParm('S',1,callInDevice) + request.AddParm('S',2,callOutDevice) + request.AddParm('S',3,waveOutDevice) + response = self.transport.Xcall(request) + def GetAudioDeviceCapabilities( + self, + deviceHandle + ): + request = XCallRequest("ZR\000\070",0,56) + request.AddParm('S',1,deviceHandle) + response = self.transport.Xcall(request) + result = (response.get(1,'')), + result += (response.get(2,0)), + return result + def GetNrgLevels(self): + request = XCallRequest("ZR\000\071",0,57) + response = self.transport.Xcall(request) + result = (response.get(1,0)), + result += (response.get(2,0)), + return result + def VoiceCommand( + self, + command + ): + request = XCallRequest("ZR\000:",0,58) + request.AddParm('S',1,command) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def GetSpeakerVolume(self): + request = XCallRequest("ZR\000<",0,60) + response = self.transport.Xcall(request) + result = response.get(1,0) + return result + def SetSpeakerVolume( + self, + volume + ): + request = XCallRequest("ZR\000=",0,61) + request.AddParm('u',1,volume) + response = self.transport.Xcall(request) + def GetMicVolume(self): + request = XCallRequest("ZR\000>",0,62) + response = self.transport.Xcall(request) + result = response.get(1,0) + return result + def SetMicVolume( + self, + volume + ): + request = XCallRequest("ZR\000?",0,63) + request.AddParm('u',1,volume) + response = self.transport.Xcall(request) + def IsSpeakerMuted(self): + request = XCallRequest("ZR\000@",0,64) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def IsMicrophoneMuted(self): + request = XCallRequest("ZR\000\101",0,65) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def MuteSpeakers( + self, + mute + ): + request = XCallRequest("ZR\000\102",0,66) + request.AddParm('b',1,mute) + response = self.transport.Xcall(request) + def MuteMicrophone( + self, + mute + ): + request = XCallRequest("ZR\000\103",0,67) + request.AddParm('b',1,mute) + response = self.transport.Xcall(request) + def OnAvailableDeviceListChange(self): pass + def OnAvailableDeviceListChangeDispatch(self, parms): self.OnAvailableDeviceListChange() + event_handlers[10] = "OnAvailableDeviceListChangeDispatch" + def OnNrgLevelsChange(self): pass + def OnNrgLevelsChangeDispatch(self, parms): self.OnNrgLevelsChange() + event_handlers[11] = "OnNrgLevelsChangeDispatch" + def CreateOutgoingSms(self): + request = XCallRequest("ZR\000\106",0,70) + response = self.transport.Xcall(request) + result = module_id2classes[12](response.get(1),self.transport) + return result + def GetValidatedSmsNumbers(self): + request = XCallRequest("ZR\000H",0,72) + response = self.transport.Xcall(request) + result = response.get(1,[]) + return result + SETUPKEY_FT_AUTOACCEPT="Lib/FileTransfer/AutoAccept" + SETUPKEY_FT_SAVEPATH="Lib/FileTransfer/SavePath" + SETUPKEY_FT_INCOMING_LIMIT="Lib/FileTransfer/IncomingLimit" + SETUPKEY_IDLE_TIME_FOR_AWAY="Lib/Account/IdleTimeForAway" + SETUPKEY_IDLE_TIME_FOR_NA="Lib/Account/IdleTimeForNA" + def GetAccount( + self, + identity + ): + request = XCallRequest("ZR\000s",0,115) + request.AddParm('S',1,identity) + response = self.transport.Xcall(request) + result = module_id2classes[5](response.get(1),self.transport) + return result + def GetExistingAccounts(self): + request = XCallRequest("ZR\000q",0,113) + response = self.transport.Xcall(request) + result = response.get(1,[]) + return result + def GetDefaultAccountName(self): + request = XCallRequest("ZR\000r",0,114) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def GetSuggestedSkypename( + self, + fullname + ): + request = XCallRequest("ZR\000t",0,116) + request.AddParm('S',1,fullname) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + VALIDATERESULT= { + 0 :'NOT_VALIDATED', + 1 :'VALIDATED_OK', + 2 :'TOO_SHORT', + 3 :'TOO_LONG', + 4 :'CONTAINS_INVALID_CHAR', + 5 :'CONTAINS_SPACE', + 6 :'SAME_AS_USERNAME', + 7 :'INVALID_FORMAT', + 8 :'CONTAINS_INVALID_WORD', + 9 :'TOO_SIMPLE', + 10:'STARTS_WITH_INVALID_CHAR', + 'NOT_VALIDATED' : 0, + 'VALIDATED_OK' : 1, + 'TOO_SHORT' : 2, + 'TOO_LONG' : 3, + 'CONTAINS_INVALID_CHAR' : 4, + 'CONTAINS_SPACE' : 5, + 'SAME_AS_USERNAME' : 6, + 'INVALID_FORMAT' : 7, + 'CONTAINS_INVALID_WORD' : 8, + 'TOO_SIMPLE' : 9, + 'STARTS_WITH_INVALID_CHAR' :10 + } + def ValidateAvatar( + self, + value + ): + request = XCallRequest("ZR\000w",0,119) + request.AddParm('B',1,value) + response = self.transport.Xcall(request) + result = (SkyLib.VALIDATERESULT[response.get(1)]), + result += (response.get(2,0)), + return result + def ValidateProfileString( + self, + propKey, + strValue, + forRegistration + ): + request = XCallRequest("ZR\000f",0,102) + request.AddParm('e',1,self._propkey(propKey,0)) + request.AddParm('S',2,strValue) + request.AddParm('b',3,forRegistration) + response = self.transport.Xcall(request) + result = (SkyLib.VALIDATERESULT[response.get(1)]), + result += (response.get(2,0)), + return result + def ValidatePassword( + self, + username, + password + ): + request = XCallRequest("ZR\000G",0,71) + request.AddParm('S',1,username) + request.AddParm('S',2,password) + response = self.transport.Xcall(request) + result = SkyLib.VALIDATERESULT[response.get(1)] + return result + def SetApplicationToken( + self, + applicationToken + ): + request = XCallRequest("ZR\000\201\001",0,129) + request.AddParm('S',1,applicationToken) + response = self.transport.Xcall(request) + def GetStr( + self, + key + ): + request = XCallRequest("ZR\000x",0,120) + request.AddParm('S',1,key) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def GetInt( + self, + key + ): + request = XCallRequest("ZR\000y",0,121) + request.AddParm('S',1,key) + response = self.transport.Xcall(request) + result = response.get(1,0) + return result + def GetBin( + self, + key + ): + request = XCallRequest("ZR\000z",0,122) + request.AddParm('S',1,key) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + def SetStr( + self, + key, + value + ): + request = XCallRequest("ZR\000{",0,123) + request.AddParm('S',1,key) + request.AddParm('S',2,value) + response = self.transport.Xcall(request) + def SetInt( + self, + key, + value + ): + request = XCallRequest("ZR\000|",0,124) + request.AddParm('S',1,key) + request.AddParm('i',2,value) + response = self.transport.Xcall(request) + def SetBin( + self, + key, + value + ): + request = XCallRequest("ZR\000}",0,125) + request.AddParm('S',1,key) + request.AddParm('B',2,value) + response = self.transport.Xcall(request) + def IsDefined( + self, + key + ): + request = XCallRequest("ZR\000~",0,126) + request.AddParm('S',1,key) + response = self.transport.Xcall(request) + result = response.get(1,False) + return result + def Delete( + self, + key + ): + request = XCallRequest("ZR\000\177",0,127) + request.AddParm('S',1,key) + response = self.transport.Xcall(request) + def GetSubKeys( + self, + key + ): + request = XCallRequest("ZR\000\200\001",0,128) + request.AddParm('S',1,key) + response = self.transport.Xcall(request) + result = response.get(1,[]) + return result + def GetISOLanguageInfo(self): + request = XCallRequest("ZR\000\317\001",0,207) + response = self.transport.Xcall(request) + result = (response.get(1,[])), + result += (response.get(2,[])), + return result + def GetISOCountryInfo(self): + request = XCallRequest("ZR\000\320\001",0,208) + response = self.transport.Xcall(request) + result = (response.get(1,[])), + result += (response.get(2,[])), + result += (response.get(3,[])), + result += (response.get(4,[])), + return result + def GetISOCountryCodebyPhoneNo( + self, + number + ): + request = XCallRequest("ZR\000\323\001",0,211) + request.AddParm('S',1,number) + response = self.transport.Xcall(request) + result = response.get(1,'') + return result + +def GetSkyLib(has_event_thread = True, host = '127.0.0.1', port = 8963, logging_level = logging.NOTSET, logging_file = LOG_FILENAME,logtransport=False): + return SkyLib(SkypeKit(has_event_thread, host, port, logging_level, logging_file, "SKYPEKIT[19.943 19.942 19.951]",logtransport)) diff --git a/digsby/devplugins/skype/__init__.py b/digsby/devplugins/skype/__init__.py new file mode 100644 index 0000000..a8fad7d --- /dev/null +++ b/digsby/devplugins/skype/__init__.py @@ -0,0 +1,2 @@ +#__LICENSE_GOES_HERE__ + diff --git a/digsby/devplugins/skype/info.yaml b/digsby/devplugins/skype/info.yaml new file mode 100644 index 0000000..3f4ede2 --- /dev/null +++ b/digsby/devplugins/skype/info.yaml @@ -0,0 +1,17 @@ +name: "Skype IM" +name_truncated: "skyp" +popularity: 0 +path: skype.skylibprotocol.SkyLibProtocol +username_desc: "Skype Identity" +form: im +type: im +whitelist_opts: ['has_added_friends'] + +skin: + serviceicons: + skype: "res/skype.png" + +defaults: + autologin: no + +hostport: no diff --git a/digsby/devplugins/skype/res/skype.png b/digsby/devplugins/skype/res/skype.png new file mode 100644 index 0000000..f8aac44 Binary files /dev/null and b/digsby/devplugins/skype/res/skype.png differ diff --git a/digsby/devplugins/skype/res/svn-U4ZhBV b/digsby/devplugins/skype/res/svn-U4ZhBV new file mode 100644 index 0000000..fd49ad2 Binary files /dev/null and b/digsby/devplugins/skype/res/svn-U4ZhBV differ diff --git a/digsby/devplugins/skype/res/windows-x86-skypekit.exe b/digsby/devplugins/skype/res/windows-x86-skypekit.exe new file mode 100644 index 0000000..fe02c74 Binary files /dev/null and b/digsby/devplugins/skype/res/windows-x86-skypekit.exe differ diff --git a/digsby/devplugins/skype/skylibchat.py b/digsby/devplugins/skype/skylibchat.py new file mode 100644 index 0000000..8691161 --- /dev/null +++ b/digsby/devplugins/skype/skylibchat.py @@ -0,0 +1,66 @@ +#__LICENSE_GOES_HERE__ + +''' +http://developer.skype.com/skypekit/development-guide/conversation-class-overview +''' + + +import common +import skylibprotocol +from util.callbacks import callsback +from util.primitives.fmtstr import fmtstr + +class SkyLibConversation(common.Conversation): + ischat = False + + def __init__(self, protocol, buddy): + buddies = [buddy] + if not buddies or not all(isinstance(buddy, skylibprotocol.SkypeBuddy) + for buddy in buddies): + raise TypeError + super(SkyLibConversation, self).__init__(protocol) + self.c = buddy.skycontact.OpenConversation() + self.c.OnMessage = self.OnMessage + self.buddy_to = buddy + + def OnMessage(self, message): + self.message = message + if message.type == 'POSTED_TEXT': + if message.author != self.protocol.self_buddy.name: + self.buddy_says(self.protocol.get_buddy(message.author), + message.body_xml, + content_type='text/xml') + + @property + def name(self): + return self.buddy.name + + @property + def self_buddy(self): + return self.protocol.self_buddy + + @property + def buddy(self): + return self.buddy_to + + @callsback + def _send_message(self, message, auto = False, callback=None, **opts): + assert isinstance(message, fmtstr) + self.c.PostText(message.format_as('xhtml'), True) + + def send_typing_status(self, status): + return + + def buddy_join(self, buddy): + if buddy not in self.room_list: + self.room_list.append(buddy) + self.typing_status[buddy] = None + + @property + def id(self): + return self.buddy_to + + def exit(self): + self.c.RemoveFromInbox() + self.protocol.conversations.pop(self.id, None) + super(SkyLibConversation, self).exit() diff --git a/digsby/devplugins/skype/skylibdriver.py b/digsby/devplugins/skype/skylibdriver.py new file mode 100644 index 0000000..2217c6e --- /dev/null +++ b/digsby/devplugins/skype/skylibdriver.py @@ -0,0 +1,21 @@ +#__LICENSE_GOES_HERE__ + +import gui.native +import path +import subprocess + +__all__ = ['start'] + +PROCNAME = u'windows-x86-skypekit.exe' + +#HAX: replace with real process management, ports, etc. +def start(protocol = None): + if PROCNAME not in gui.native.process.process_list(): + # don't show a console window for the skype process. + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE + + subprocess.Popen(path.path(__file__).dirname() / + 'res' / PROCNAME, startupinfo=startupinfo) + return 8963 diff --git a/digsby/devplugins/skype/skylibprotocol.py b/digsby/devplugins/skype/skylibprotocol.py new file mode 100644 index 0000000..141f624 --- /dev/null +++ b/digsby/devplugins/skype/skylibprotocol.py @@ -0,0 +1,256 @@ +#__LICENSE_GOES_HERE__ + +import common +import logging +from skypekit import SkypeKit +import SkyLib +from contacts import Group, Contact +from util.observe import observable_dict +from common.Protocol import OfflineReason +from common.Buddy import Buddy +from .skylibchat import SkyLibConversation + +APP_TOKEN = ''.join(''' +AAAgBobH4q2OPAaCX6vWseC82MmHZ1Cpayj61rbYlh0uenHxFByJ/lLu9HSN5nT3TjS91/2RQMAS +lCmZUCM5zINkR3nQ1240JpB0yNfYfzxXm8EyE9p9gWAGU7spUMvuROxoQR0042VUR4dCRW/kYr3y +eYiYOXW0poxxwg+esEbX8W1tqing25kfjUVsij6+T+dxtV8t/B1yGpTiT1okj9FoBvZgnwDoEGEy +wG5xeJTGLuFtHGALqa7gwvj9rulf7TuM1Q== +'''.split()) + +def GetSkypeKit(port): + kit = SkypeKit(has_event_thread = True, host = '127.0.0.1', + port = port, logging_level = logging.NOTSET, logtransport = False) + kit.logger = logging.getLogger('SkypeKitLogger') + kit.logger.setLevel(logging.INFO) + return kit + +libs = {} + +def GetSkyLib(port): + global libs + if port in libs: + return libs[port] + lib = SkyLib.SkyLib(GetSkypeKit(port)) + lib.SetApplicationToken(APP_TOKEN) + libs[port] = lib + return lib + +class SkyLibOfflineReasons(OfflineReason): + pass + +DIGSBY_REASON_MAP = { + 'LOGOUT_CALLED' : 'NONE', + 'HTTPS_PROXY_AUTH_FAILED' : 'CONN_FAIL', + 'SOCKS_PROXY_AUTH_FAILED' : 'CONN_FAIL', + 'P2P_CONNECT_FAILED' : 'CONN_FAIL', + 'SERVER_CONNECT_FAILED' : 'CONN_FAIL', + 'SERVER_OVERLOADED' : 'CONN_LOST', + 'DB_IN_USE' : 'CONN_FAIL', + 'INVALID_SKYPENAME' : 'BAD_PASSWORD', + 'INVALID_EMAIL' : 'BAD_PASSWORD', + 'UNACCEPTABLE_PASSWORD' : 'BAD_PASSWORD', + 'SKYPENAME_TAKEN' : 'BAD_PASSWORD', + 'REJECTED_AS_UNDERAGE' : 'BAD_PASSWORD', + 'NO_SUCH_IDENTITY' : 'BAD_PASSWORD', + 'INCORRECT_PASSWORD' : 'BAD_PASSWORD', + 'TOO_MANY_LOGIN_ATTEMPTS' : 'RATE_LIMIT', + 'PASSWORD_HAS_CHANGED' : 'BAD_PASSWORD', + 'PERIODIC_UIC_UPDATE_FAILED' : 'CONN_LOST', + 'DB_DISK_FULL' : 'CONN_FAIL', + 'DB_IO_ERROR' : 'CONN_FAIL', + 'DB_CORRUPT' : 'CONN_FAIL', + 'DB_FAILURE' : 'CONN_FAIL', + 'INVALID_APP_ID' : 'CONN_FAIL', + 'APP_ID_BLACKLISTED' : 'CONN_FAIL', + 'UNSUPPORTED_VERSION' : 'CONN_FAIL', + } + +for key in SkyLib.Account.LOGOUTREASON: + if isinstance(key, bytes): + setattr(SkyLibOfflineReasons, key, key) + for k,v in DIGSBY_REASON_MAP.items(): + setattr(SkyLibOfflineReasons, k, getattr(OfflineReason, v)) + +class SkyLibProtocol(common.protocol): + name = service = protocol = 'skype' + Reasons = SkyLibOfflineReasons + + @property + def caps(self): + from common import caps + return [caps.INFO, caps.IM] + + def set_buddy_icon(self, *a, **k): + pass + + def __init__(self, username, password, msgHub, server=None, + login_as='online', *a, **k): + super(SkyLibProtocol, self).__init__(username, password, msgHub) + self.skylib = None + self.skyacct = None + self.root_group = Group('Root', self, 'Root') + self.buddies = observable_dict() + self.conversations = observable_dict() + + def Connect(self, invisible=False): + import skylibdriver + port = skylibdriver.start(self) + import time; time.sleep(2) + self.skylib = GetSkyLib(port) + self.skyacct = self.skylib.GetAccount(self.username) + self.skyacct.OnPropertyChange = self.OnPropertyChange + self.skyacct.LoginWithPassword(self.password, False, False) + + def OnConversationListChange(self, conversation, type, added): + if type != 'DIALOG': + return + if not added: + return + for p in conversation.GetParticipants(): + b = self.get_buddy(p.identity) + if b is not self.self_buddy and b not in self.conversations: + convo = self.conversations.setdefault(b, + SkyLibConversation(self, b)) + convo.buddy_join(self.self_buddy) + convo.buddy_join(b) + + def OnPropertyChange(self, prop): + print self, prop, getattr(self.skyacct, prop) + if prop == 'status' and self.skyacct.status in ('LOGGED_IN', 7): + self.change_state(self.Statuses.LOADING_CONTACT_LIST) + self.self_buddy = SkypeBuddy(self.skylib.GetContact( + self.skyacct.skypename), self) + d = {self.self_buddy.name: self.self_buddy} + g = [] + for c in self.skylib.GetHardwiredContactGroup('SKYPE_BUDDIES').GetContacts(): + b = SkypeBuddy(c, self) + d[b.name] = b + g.append(Contact(b, b.name)) + self.buddies.update(d) + with self.root_group.frozen(): + self.root_group[:] = g + self.cs = [] + for c in self.skylib.GetConversationList('ALL_CONVERSATIONS'): + c.OnMessage = self.a_message + self.cs.append(c) + self.change_state(self.Statuses.ONLINE) + if prop == 'status' and self.skyacct.status == 'LOGGED_OUT': + print self.skyacct.logoutreason + self.set_offline(getattr(self.Reasons, self.skyacct.logoutreason)) + + def Disconnect(self): + self.skyacct.Logout(True) + super(SkyLibProtocol, self).Disconnect() + + def get_buddy(self, name): + return self.buddies[name] + + def convo_for(self, buddy): + if not isinstance(buddy, SkypeBuddy): + buddy = buddy.buddy + if buddy in self.conversations: + convo = self.conversations[buddy] + else: + convo = self.conversations.setdefault(buddy, + SkyLibConversation(self, buddy)) + convo.buddy_join(self.self_buddy) + convo.buddy_join(buddy) + return convo + + def set_invisible(self, invisible): + self.invisible = invisible + self.set_message(None, 'invisible') + + def set_message(self, message, status, format = None, default_status='away'): +# state = self.status_state_map.get(status.lower(), default_status) + if status.lower() == 'away': + a = 'AWAY' + else: + a = 'ONLINE' + if getattr(self, 'invisible', False): + a = 'INVISIBLE' + self.skyacct.SetAvailability(a) + if message is not None: + self.skyacct.SetStrProperty('mood_text', message) + + def a_message(self, message): + print message +# if message.type != 'POSTED_TEXT': +# return + buddy = self.get_buddy(message.author) + assert buddy not in self.conversations + convo = self.convo_for(buddy) + convo.OnMessage(message) + +DIGSBY_AVAILABLE_MAP = { + 'UNKNOWN' : 'unknown', + 'PENDINGAUTH' : 'unknown', + 'BLOCKED' : 'unknown', + 'BLOCKED_SKYPEOUT' : 'unknown', + 'SKYPEOUT' : 'available', + 'OFFLINE' : 'offline', + 'OFFLINE_BUT_VM_ABLE' : 'offline', + 'OFFLINE_BUT_CF_ABLE' : 'offline', + 'ONLINE' : 'available', + 'AWAY' : 'away', + 'NOT_AVAILABLE' : 'away', + 'DO_NOT_DISTURB' : 'away', + 'SKYPE_ME' : 'available', + 'INVISIBLE' : 'invisible', + 'CONNECTING' : 'available', + 'ONLINE_FROM_MOBILE' : 'available', + 'AWAY_FROM_MOBILE' : 'away', + 'NOT_AVAILABLE_FROM_MOBILE' : 'away', + 'DO_NOT_DISTURB_FROM_MOBILE' : 'away', + 'SKYPE_ME_FROM_MOBILE' : 'available', + } + +class SkypeContact(Contact): + pass + +class SkypeBuddy(Buddy): + service = 'skype' + + def __init__(self, skylib_contact, protocol): + self.skycontact = skylib_contact + self.skycontact.OnPropertyChange = self.OnPropertyChange + self._status_message = None + super(SkypeBuddy, self).__init__(self.skycontact.skypename, protocol) + + def OnPropertyChange(self, prop): + if prop == 'availability': + self.notify('status') + + @property + def status(self): + return DIGSBY_AVAILABLE_MAP[self.skycontact.availability] + + @property + def status_message(self): + if self._status_message is not None: + return self._status_message + return self.skycontact.mood_text or '' + + @status_message.setter + def status_message(self, val): + self._status_message = val + + @property + def idle(self): + return False + + @property + def online(self): + return self.status not in ('offline', 'unknown') + + @property + def mobile(self): + return 'MOBILE' in self.skycontact.availability + + @property + def away(self): + return self.status == 'away' + + @property + def blocked(self): + return 'BLOCKED' in self.skycontact.availability diff --git a/digsby/devplugins/skype/skypekit.py b/digsby/devplugins/skype/skypekit.py new file mode 100644 index 0000000..d6d6534 --- /dev/null +++ b/digsby/devplugins/skype/skypekit.py @@ -0,0 +1,633 @@ +#!/usr/bin/python +#__LICENSE_GOES_HERE__ + +import Queue +import array +from collections import deque +import socket +import sys +import weakref +import threading +from new import instancemethod +import glob +import logging +import logging.handlers +import time + +class StatLogger: + def __init__(self): + self.log = open("pyskypekit.stats", "w") + def CreateObj(self, modid, oid): + self.log.write('CreateObj(%f,%d,%d)\n' % (time.time(), modid, oid)) + def ReachObj(self, modid, oid, hit, reached_from): + self.log.write('ReachObj(%f,%d,%d,%s,"%s")\n' % (time.time(), modid, oid, hit, reached_from)) + def ReachProp(self, modid, oid, propid, hit, reached_from): + self.log.write('ReachProp(%f,%d,%d,%d,%s,"%s")\n' % (time.time(), modid, oid, propid, hit, reached_from)) + def XcallBegin(self, mid, method, rid, oid): + self.log.write('XcallBegin(%f,%d,%d,%d,%d)\n' % (time.time(), mid, method, rid, oid)) + def XcallEnd(self, rid): + self.log.write('XcallEnd(%f,%d)\n' % (time.time(), rid)) + def Event(self, mid, method, oid, dispatched): + self.log.write('DispatchEvent(%f,%d,%d,%d)\n' % (time.time(), mid, method, oid)) +stat_logger = None #StatLogger() + +LOG_FILENAME = 'pyskypekit.out' + +''' Protocol error. +''' +class Error(Exception): + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return self.msg + +class ConnectionClosed(Error): + def __init__(self): + Error.__init__(self, "Connection closed") + +class ResponseError(Error): + def __init__(self): + Error.__init__(self, "response error (either invalid in parameters for the call or call isn't allowed or call failed") + + +class InvalidObjectIdError(Error): + def __init__(self): + Error.__init__(self, "object id is 0") + +module_id2classes = { } + +class ScopedLock(object): + def __init__(self, mutex): + self.mutex = mutex + self.mutex.acquire(); + def __del__(self): + self.mutex.release(); + +class Cached(object): + '''Base class for all cached objects. + + Every object is identified by an Id specified as first parameter of the constructor. + Trying to create two objects with same Id yields the same object. Uses weak references + to allow the objects to be deleted normally. + + @warning: C{__init__()} is always called, don't use it to prevent initializing an already + initialized object. Use C{_Init()} instead, it is called only once. + ''' + _lock_ = threading.Lock() + _cache_ = weakref.WeakValueDictionary() + def __new__(cls, Id, *args, **kwargs): + if Id == 0: return False # invalid id, still return something not to shift parameters + sl = ScopedLock(cls._lock_) + h = cls, Id + hit = True + o = None + try: + o = cls._cache_[h] + except KeyError: + #stat_logger.CreateObj(cls.module_id, Id) + o = object.__new__(cls) + h = cls, Id + cls._cache_[h] = o + if hasattr(o, '_Init'): + o._Init(Id, *args, **kwargs) + return o + @staticmethod + def exists(cls, Id, src): + if Id == 0: return None # invalid id + sl = ScopedLock(cls._lock_) + h = cls, Id + try: + return cls._cache_[h] + except KeyError: + return None + def __copy__(self): + return self + +class Object(Cached): + rwlock = threading.Lock() + def _Init(self, object_id, transport): + self.transport = transport + self.object_id = object_id + self.properties= {} + if transport.logger: transport.logger.info('INSTANTIATING mod=%d oid=%d' % (self.module_id,object_id)) + ''' Retrieve given property id. + ''' + def _Property(self, header, prop_id, cached): + hit = cached #True + val = 0 + try: + self.rwlock.acquire() + if hit: val = self.properties[prop_id] + self.rwlock.release() + except KeyError: + self.rwlock.release() + hit=False + if not hit: + val = self.transport.Get(GetRequest(header, self.object_id)) + #stat_logger.ReachProp(self.module_id, self.object_id, prop_id, hit, 'Get') + return val + def _propkey(self, propname, t): + for p,l in self.propid2label.items(): + # don't set the property value, as it shall be notified by a property change event + if l == propname: + if p in self.properties: del self.properties[p] # clean it... + return p*4+t + return None + def multiget(self, header): + self.transport.Get(GetRequest(header, self.object_id)) +''' Connection class that implements Skype IPC. +''' +class SkypeKit: + decoders={} + + class EventDispatcher(threading.Thread): + def __init__(self, connection): + self.connection = connection + threading.Thread.__init__(self) + self.setName('event thread') + def run(self): + try: + self.connection.EventThread() + except: + self.connection.Stop() + raise + + class ResponseListener(threading.Thread): + def __init__(self, connection): + self.connection = connection + threading.Thread.__init__(self) + self.setName('responser listener thread') + def run(self): + try: + self.connection.Start() + except: + self.connection.Stop() + raise + + def __init__(self, has_event_thread = True, host = '127.0.0.1', port = 8963, logging_level = logging.NOTSET, logging_file = LOG_FILENAME, noncacheable = 'z', logtransport=False): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, True) + sock.settimeout(30.5) + self.pending_requests = {} + self.pending_gets = deque() + self.pending_lock = threading.Lock() + self.encoding_lock = threading.Lock() + self.decoded = threading.Event() + self.event_queue = Queue.Queue() + self.inlog = False + self.outlog = False + if logtransport: + try: + self.inlog = open(logtransport+'_log_in.1', 'wb') + except IOError: + self.inlog = False + try: + self.outlog = open(logtransport+'_log_out.1', 'wb') + except IOError: + self.outlog = False + self.logger = False + if logging_level != logging.NOTSET: + self.logger = logging.getLogger('SkypeKitLogger') + self.logger.setLevel(logging_level) + handler = logging.handlers.RotatingFileHandler(logging_file, maxBytes=2**29, backupCount=5) + self.logger.addHandler(handler) + self.stopped = False + self.socket = sock + retry = 3 + while retry > 0: + try: + sock.connect((host, port)) + retry = -1 + except: + retry = retry - 1 + if retry == 0: + raise + time.sleep(5) + self.read_buffer = '' + sock.sendall('z') + if has_event_thread: + self.event_dispatcher = SkypeKit.EventDispatcher(self) + self.event_dispatcher.start() + self.listener = SkypeKit.ResponseListener(self) + self.listener.start() + + def setRoot(self, root): self.root = root + + def __del__(self): + if self.socket != None: + self.socket.close() + if self.inlog: self.inlog.close() + if self.outlog: self.outlog.close() + + def ReadByte(self, num_bytes_to_read = 1, timeout = True): + def isprint(c): + if ord(c) >= 32 and ord(c) <= 127: return c + return ' ' + result = self.read_buffer + while not self.stopped and len(result) < num_bytes_to_read: + try: + read = self.socket.recv(4096) + if not read: + self.Stop() + raise ConnectionClosed + result += read + if self.inlog: + try: + self.inlog.write(read) + except IOError: + self.inlog.close() + self.inlog = False + except socket.timeout: + pass # retry always: if connection is closed we will see it at next read + except: + self.Stop() + raise + if self.stopped: return None + self.read_buffer = result[num_bytes_to_read:] + result = result[:num_bytes_to_read] + if self.logger: + if len(result) == 1: + self.logger.debug('reading %s %3d %2x' % (isprint(result), ord(result), ord(result))) + else: + self.logger.debug('reading %3d bytes ' % (len(result))) + return result + + def EventThread(self): + while True: + e = self.event_queue.get() + if self.stopped: return + e.dispatch() + + def DispatchEvent(self): + while not self.event_queue.empty(): + e = self.event_queue.get() + if self.stopped: return + e.dispatch() + + def Xcall(self, request): + ev = threading.Event() + self.encoding_lock.acquire() + self.pending_lock.acquire() + self.pending_requests[request.rid] = ev + self.pending_lock.release() + if self.logger: self.logger.info('CALLING mod=%d mid=%d rid=%d' % (request.module_id, request.method_id, request.rid)) + #stat_logger.XcallBegin(request.module_id, request.method_id, request.rid, request.oid) + rq = request.Send() + self.socket.sendall(rq) + if self.outlog: + try: + self.outlog.write(rq) + except IOError: + self.outlog.close() + self.outlog = False + self.encoding_lock.release() + ev.wait() # no need to clear: one shot + if self.stopped: raise ConnectionClosed() + response = self.decode_parms() + #stat_logger.XcallEnd(request.rid) + return response + + def multiget(self, header, objects): + if len(objects) > 0: self.Get(MultiGetRequest(header, objects)) + + def Get(self, request): + ev = threading.Event() + self.encoding_lock.acquire() + self.pending_lock.acquire() + self.pending_gets.append(ev) + self.pending_lock.release() + rq = request.Send() + self.socket.sendall(rq) + if self.outlog: + try: + self.outlog.write(rq) + except IOError: + self.outlog.close() + self.outlog = False + self.encoding_lock.release() # wait until response is here + ev.wait() + if self.stopped: raise ConnectionClosed() + # process the response with patching the instances... + response = None + mresponse = {} + continue_sign = ',' + count = 0 + while continue_sign == ',': + modid = self.decode_varuint() # modid + while continue_sign == ',': + oid = self.decode_varuint() # oid + o = module_id2classes[modid](oid,self) + if not o in mresponse: mresponse[o] = {} + kind = self.ReadByte() + while kind != ']': + propid = self.decode_varuint() # propid + if kind != 'N': + response = self.decoders[kind](self) + mresponse[o][propid] = response + o.rwlock.acquire() + o.properties[propid] = response + o.rwlock.release() + count = count + 1 + kind = self.ReadByte() # ] finish the list + if kind != ']': raise ResponseError() + continue_sign = self.ReadByte() + if continue_sign != ']': raise ResponseError() + continue_sign = self.ReadByte() + if continue_sign != ']': raise ResponseError() + if self.ReadByte() != 'z': raise ResponseError() + self.decoded.set() + if count > 1: response = mresponse + return response + + def GetResponse(self): + self.pending_lock.acquire() + self.pending_gets.popleft().set() + self.pending_lock.release() + decoders['g'] = GetResponse + + def decode_varuint(self): + shift = 0 + result = 0 + while 1: + value = ord(self.ReadByte()) & 0xff + result = result | ((value & 0x7f) << shift) + shift = shift + 7 + if not (value & 0x80): break + return result + decoders['u'] = decode_varuint + decoders['O'] = decode_varuint + decoders['e'] = decode_varuint + + def decode_varint(self): + value = self.decode_varuint() + if not value & 0x1: + return value >> 1 + return (value >> 1) ^ (~0) + decoders['i'] = decode_varint + + def decode_true(self): + return True + decoders['T'] = decode_true + + def decode_false(self): + return False + decoders['F'] = decode_false + + def decode_list(self): + l = [] + while True: + b = self.ReadByte() + if b == ']': return l + m = self.decoders[b] + if m: l.append(m(self)) + decoders['['] = decode_list + + def decode_binary(self): + length = self.decode_varuint() + v = '' + if length>0: + v = self.ReadByte(length) + return v + + def decode_string(self): + s = self.decode_binary() + return s.decode('utf-8') + decoders['f'] = decode_string + decoders['B'] = decode_binary + decoders['S'] = decode_string + decoders['X'] = decode_string + + class Parms(dict): + def get(self, index, defval = None): + try: + return self[index] + except: + if defval == None: defval = 0 + return defval + + def decode_parms(self): + parms = self.Parms() + m = True + while m != None: + b = self.ReadByte() + if self.stopped or b == 'z': break + if b != 'N': + m = self.decoders[b] + tag = self.decode_varuint() + if m: parms[tag] = m(self) + else: + #print "response error ", self.ReadByte() # shall be z + self.decoded.set() + raise ResponseError + self.decoded.set() + return parms + + class Event(object): + def __init__(self, transport): + self.module_id = transport.decode_varuint() + self.target = transport.root + self.event_id = transport.decode_varuint() + self.transport = transport + self.parms = transport.decode_parms() + def dispatch(self): + target = self.target + if self.module_id != 0: + target = Cached.exists(module_id2classes[self.module_id],self.parms[0], 'Event') + if target == None: + if self.transport.logger: self.transport.logger.debug('IGNORE EVENT mod=%d ev=%d oid=%d' % (self.module_id,self.event_id,self.parms[0])) + return + #else: + # stat_logger.ReachObj(0, 0, True, 'Event') + if self.transport.logger: self.transport.logger.info('DISPATCH EVENT mod=%d ev=%d oid=%d' % (self.module_id,self.event_id,target.object_id)) + getattr(target,target.event_handlers[self.event_id])(self.parms) + + def decode_event(self): + # push the event in the event queue + self.event_queue.put(SkypeKit.Event(self)) + decoders['E'] = decode_event + + class PropertyChange(object): + def __init__(self, transport): + self.logger = transport.logger + self.transport = transport + self.modid = transport.decode_varuint() + self.oid = transport.decode_varuint() # obj id + kind = transport.ReadByte() # prop kind + self.propid = transport.decode_varuint() # prop id + if kind != 'N': self.val = transport.decoders[kind](transport) + transport.ReadByte(4) # ]]]z + transport.decoded.set() + def dispatch(self): + o = Cached.exists(module_id2classes[self.modid],self.oid, 'PropertyChangeEvent') + if o == None: + if self.logger: self.logger.debug('IGNORE CHANGE PROPERTY mod=%d oid=%d prop=%d' % (self.modid, self.oid, self.propid)) + return + try: + propname = o.propid2label[self.propid] + except KeyError: + return + o.rwlock.acquire() + o.properties[self.propid] = self.val + o.rwlock.release() + if self.logger: self.logger.info('CHANGED PROPERTY mod=%d oid=%d prop=%s' % (self.modid, self.oid, propname)) + #stat_logger.ReachProp(self.modid, self.oid, self.propid, self.propid in o.properties, 'PropertyChangeEvent') + print o,'.OnPropertyChange(',propname,'): ',repr(self.val) + o.OnPropertyChange(propname) + + def decode_property_change(self): + # push the event in the event queue + self.event_queue.put(SkypeKit.PropertyChange(self)) + decoders['C'] = decode_property_change + + def XCallResponse(self): + rid = self.decode_varuint() + self.pending_lock.acquire() + ev = self.pending_requests[rid] + del self.pending_requests[rid] + self.pending_lock.release() + ev.set() + decoders['r'] = XCallResponse + + def Start(self): + while not self.stopped: + if self.ReadByte(1,False) == 'Z': + if self.stopped: return + cmd = self.ReadByte() + if self.stopped: return + if self.logger: self.logger.debug('Processing %c' % cmd) + m = self.decoders[cmd] + if m: + m(self) + self.decoded.wait() + self.decoded.clear() # shall be done immediatly after set? + if self.logger: self.logger.debug('Done processing %c' % cmd) + + def Stop(self): + if not self.stopped: + self.stopped = True + if self.logger: self.logger.info('stopping...') + self.decoded.set() # ensure that Listener thread resumes + self.event_queue.put({}) # ensure that event thread resumes + for e in self.pending_gets: e.set() + for k in self.pending_requests: self.pending_requests[k].set() + if self.socket: + self.socket.close() + self.socket = None + + +class Request: + def __init__(self): + self.tokens = array.array('B') + #self.tokens.append(ord('Z')) + def Send(self): + tok = self.tokens + tok.append(ord('z')) + self.tokens = None + return tok + encoders = { } + + def encode_varint(self, number): + if number >= 0: number = number << 1 + else: number = (number << 1) ^ (~0) + self.encode_varuint(number) + encoders['i'] = encode_varint + + def encode_varuint(self, number): + tok = self.tokens + while 1: + towrite = number & 0x7f + number = number >> 7 + if number == 0: + tok.append(towrite) + break + tok.append(0x80|towrite) + encoders['u'] = encode_varuint + encoders['e'] = encode_varuint + encoders['o'] = encode_varuint + + def encode_objectid(self, val): + self.encode_varuint(val.object_id) + encoders['O'] = encode_objectid + + def encode_string(self, val): + tok = self.tokens + if isinstance(val, unicode): + val=val.encode('utf-8') + length = len(val) + self.encode_varuint(length) + if length>0: + tok.fromstring(val) + encoders['S'] = encode_string + encoders['X'] = encode_string + encoders['f'] = encode_string + encoders['B'] = encode_string + + def AddParm(self, kind, tag, val): + tok = self.tokens + if isinstance(val, list): + tok.append(ord('[')) + self.encode_varuint(tag) + encoder = self.encoders[kind] + for v in val: + if kind != 'b': + tok.append(ord(kind)) + encoder(self, v) + else: + if v: tok.append(ord('T')) + else: tok.append(ord('F')) + tok.append(ord(']')) + elif kind != 'b': + tok.append(ord(kind)) + if tag == 0: self.oid = val.object_id + self.encode_varuint(tag) + self.encoders[kind](self, val) + else: + if val: tok.append(ord('T')) + else: tok.append(ord('F')) + self.encode_varuint(tag) + return self + +class XCallRequest(Request): + __request_id = 0 + def __init__(self, header, module_id, method_id): + Request.__init__(self) + self.tokens.fromstring(header) + self.module_id = module_id + self.method_id = method_id + self.oid = 0 + tok = self.tokens + #tok.append(ord('R')) + self.rid = XCallRequest.__request_id + XCallRequest.__request_id = XCallRequest.__request_id + 1 # mutexed? + #self.encode_varuint(module_id) + #self.encode_varuint(method_id) + self.encode_varuint(self.rid) + +class GetRequest(Request): + def __init__(self, header, object_id): + Request.__init__(self) + tok = self.tokens + tok.fromstring(header) + self.encode_varuint(object_id) + tok.fromstring(']]z') + def Send(self): + tok = self.tokens + self.tokens = None + return tok + +class MultiGetRequest(Request): + def __init__(self, header, objects): + Request.__init__(self) + tok = self.tokens + tok.fromstring(header) + pref = '' + for o in objects: + tok.fromstring(pref) + self.encode_varuint(o.object_id) + pref = ',' + tok.fromstring(']]z') + def Send(self): + tok = self.tokens + self.tokens = None + return tok diff --git a/digsby/digsby-pylint.rc b/digsby/digsby-pylint.rc new file mode 100644 index 0000000..04d69cb --- /dev/null +++ b/digsby/digsby-pylint.rc @@ -0,0 +1,309 @@ +# lint Python modules using external checkers. +# +# This is the main checker controlling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= + +# Enable all messages in the listed categories (IRCWEF). +#enable-msg-cat= + +# Disable all messages in the listed categories (IRCWEF). +#disable-msg-cat= + +# Enable the message(s) with the given id(s). +#enable-msg= + +# Disable the message(s) with the given id(s). +disable-msg=R0201 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells wether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Enable the report(s) with the given id(s). +#enable-report= + +# Disable the report(s) with the given id(s). +#disable-report= + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamicaly set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. +generated-members=REQUEST,acl_users,aq_parent + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Tells wether we should check for unused import in __init__ files. +init-import=yes + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins=_ + + +# checks for : +# * methods without self as first argument +# * overridden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes diff --git a/digsby/digsby_nocmd.bat b/digsby/digsby_nocmd.bat new file mode 100644 index 0000000..5b538e7 --- /dev/null +++ b/digsby/digsby_nocmd.bat @@ -0,0 +1,4 @@ +@echo off +@set PYTHONPATH=./src;..;./lib;./ext +start pythonw Digsby.py +exit diff --git a/digsby/digsbycrust.bat b/digsby/digsbycrust.bat new file mode 100644 index 0000000..6492973 --- /dev/null +++ b/digsby/digsbycrust.bat @@ -0,0 +1,3 @@ +@echo off +@set PYTHONPATH=./src;./lib;./ext +start ../dpython/pythonw src/digsbycrust.py %1 %2 %3 %4 %5 %6 %7 %8 %9 diff --git a/digsby/digsbypaths.py b/digsby/digsbypaths.py new file mode 100644 index 0000000..e191c7e --- /dev/null +++ b/digsby/digsbypaths.py @@ -0,0 +1,10 @@ +import os.path +import sys + +platformName = dict(darwin = 'mac', + linux2 = 'gtk', + win32 = 'win')[sys.platform] + +def get_platlib_dir(debug=None): + import distutils.sysconfig as sysconfig + return sysconfig.get_python_lib() diff --git a/digsby/ext/build_xmlextra.py b/digsby/ext/build_xmlextra.py new file mode 100644 index 0000000..8b9cc53 --- /dev/null +++ b/digsby/ext/build_xmlextra.py @@ -0,0 +1,34 @@ +#__LICENSE_GOES_HERE__ +''' +Use -I to point to include directories for libxml2 and iconv, and -L to point +to the libxml2 library. + +example: + +C:\dev\digsby\ext>python build_xmlextra.py build_ext -L c:\dev\digsby\build\msw\ +libxml2-2.6.31\win32\bin.msvc -I c:\dev\digsby\build\msw\libxml2-2.6.31\i +nclude -I c:\dev\digsby\build\msw\libiconv install --install-lib=win +''' + +sources = ['src/xmlextra/xmlextra.c'] +libraries = ['libxml2'] + +from distutils.core import setup, Extension +import os + +if os.name == 'nt': + # include debugging information + cflags = ['/GL', '/Zi', '/GS-'] + ldflags = ['/DEBUG', '/OPT:REF', '/OPT:ICF'] +else: + cflags = [] + ldflags = [] + +setup(name = '_xmlextra', ext_modules = [ + Extension('_xmlextra', + sources, + libraries = libraries, + extra_compile_args = cflags, + extra_link_args = ldflags) + +]) diff --git a/digsby/ext/buildbkl.py b/digsby/ext/buildbkl.py new file mode 100644 index 0000000..7b9a4d7 --- /dev/null +++ b/digsby/ext/buildbkl.py @@ -0,0 +1,107 @@ +#__LICENSE_GOES_HERE__ +import os.path +import shutil +import sys +import time + +mydir = os.path.dirname(os.path.abspath(__file__)) +print 'thisdir is', mydir +sys.path.append(os.path.abspath(os.path.join(mydir, '..', 'build'))) + +from buildutil import * +from buildutil.common import * + +# FIXME: We need to not hardcode these! +if os.name != 'nt': + wx_version = "2.9" + py_version = sys.version[:3] + + depsDir = os.path.join(homeDir, "digsby-deps", "py" + py_version + "-wx" + wx_version[:3]) + buildDirs.initBuildDirs(depsDir) + +def build(): + from wxpybuild.wxpyext import build_extension + import cguisetup + + cgui_modules = [ + ('cgui', [os.path.abspath(os.path.join(mydir,'src', 'cgui.sip'))] + cguisetup.sources), + ] + + libs = [] + if os.name == 'nt': + libs.extend(['comsuppw', 'shlwapi']) # COM support library for _com_ptr_t in WinJumpList.cpp + print 'buildDirs.boostDir', buildDirs.boostDir + build_extension('cgui%s' % DEBUG_POSTFIX, + cgui_modules, + includes = cguisetup.include_dirs + [buildDirs.boostDir], + libs = libs, + libdirs = os.environ.get('LIB', '').split(os.pathsep)) + + if sys.platform.startswith('win'): + return + + buddylist_sources = '''\ +sip/blist.sip +Account.cpp +Buddy.cpp +BuddyListSorter.cpp +Contact.cpp +ContactData.cpp +FileUtils.cpp +Group.cpp +Node.cpp +PythonInterface.cpp +Sorters.cpp +Status.cpp +StringUtils.cpp +'''.split() + + blist_modules = [ + ('blist', ['%s/src/BuddyList/%s' % (mydir, f) for f in buddylist_sources]), + ] + #return + build_extension('blist', + blist_modules, + defines=['BUILDING_BUDDYLIST_DLL=1'], + includes = cguisetup.include_dirs + [buildDirs.boostDir] + ) + +def install(): + pth = os.path.abspath('.').replace('\\', '/') + assert pth.lower().endswith('/ext'), pth + + # todo: get these paths from wxpybuild + format = 'msvs2008prj' + ext = 'pyd' + if not sys.platform.startswith('win'): + format = 'gnu' + ext = 'so' + + builddir = 'build/obj-%s%s' % (format, DEBUG_POSTFIX) + + # FIXME: On Mac, I'm getting obj-gnu_d, even though + # DEBUG_POSTFIX is empty. + if not sys.platform.startswith('win'): + builddir = builddir + '_d' + + src, dest = '%s/cgui%s.%s' % (builddir, DEBUG_POSTFIX, ext), DEPS_DIR + + shutil.copy(src, dest) + try: + shutil.copy('%s/blist.%s' % (builddir, ext), DEPS_DIR) + except: + pass + + if sys.platform.startswith('win'): + shutil.copy(r'%s/cgui%s.pdb' % (builddir, DEBUG_POSTFIX), DEPS_DIR) + +def run(cmd): + print cmd + os.system(cmd) + +def main(): + build() + install() + +if __name__ == '__main__': + main() diff --git a/digsby/ext/buildexts.py b/digsby/ext/buildexts.py new file mode 100644 index 0000000..f7fb323 --- /dev/null +++ b/digsby/ext/buildexts.py @@ -0,0 +1,116 @@ +#__LICENSE_GOES_HERE__ +# +# builds "cgui", Digsby's native code extension +# +# use "python buildexts.py build_ext" from the command line +# to build cgui.pyd or cgui.so +# + +from wx.build.config import * +import wx + +import cguisetup +import sys, os +from os import environ as env +from os.path import join as pj +from pprint import pprint + +def build(): + global swig_args, includes, defines, cflags, lflags + + if os.name == "nt": + WXDIR = env['WXDIR'] + WXPY_SRC = env['WXPYDIR'] + + def die(msg): + print msg + sys.exit(-1) + + def sanity_checks(): + from path import path + if not path(WXPY_SRC).files('_core.py'): + die(WXPY_SRC + ' does not have _core.py -- is it a valid wxPython?') + + swig_args += ['-v'] + + print 'swig_args:' + pprint(swig_args) + + includes += cguisetup.include_dirs + + if os.name == 'nt': + includes += [ + '%s\\include' % WXPY_SRC, + '%s\\..\\include' % WXPY_SRC, + pj(WXPY_SRC, 'lib', 'vc_dll', 'mswuh'), + ] + + sources = cguisetup.sources + + # WIN32 + if os.name == 'nt': + # Unlike Win, on Unix/Mac the wxPy developer package is not separate, so we do + # not need this sanity check there; import wx above should fail on Unix/Mac + # if we've got an invalid wxPython.svn diff + sanity_checks() + + sources.append('src/debugapp.cpp') + + # add some include dirs for SWIG + swig_args += ['-I' + pj(*([WXPY_SRC] + paths)) for paths in ( + ['src'], + # ['..', 'include'], + # ['..', 'include', 'wx', 'msw'], + # ['include', 'wx', 'wxPython', 'i_files'], + )] + + cflags += ['/Zi', # generates PDBs (debugging symbols files) + '/D_UNICODE'] # use unicode Win32 functions + + lflags = lflags or [] + lflags += ['/DEBUG', + '/LTCG'] + + for include in cguisetup.include_dirs: + swig_args += ['-I' + include] + + exts = [('cgui', sources + ["src/cgui_wrap.cpp"])] + + # common args to distuils.Extension + extopts = dict(include_dirs = includes, + define_macros = defines, + library_dirs = libdirs, + libraries = libs, + + extra_compile_args = cflags, + extra_link_args = lflags, + + swig_opts = swig_args, + language = 'c++',) + + ext_modules = [] + + for extension_name, sources in exts: + swig_sources = run_swig(files = ['./src/%s.i' % extension_name], + dir = '', + gendir = '.', + package = '.', + USE_SWIG = True, + force = True, + swig_args = swig_args, + swig_deps = swig_deps) + + print + print 'building extension %r' % extension_name + print + print 'sources:' + pprint(sources) + print + + ext = Extension('_' + extension_name, sources, **extopts) + ext_modules.append(ext) + + setup(ext_modules = ext_modules, scripts=['src/cgui.py']) + +if __name__ == '__main__': + build() diff --git a/digsby/ext/buildsip.py b/digsby/ext/buildsip.py new file mode 100644 index 0000000..0a9fa0d --- /dev/null +++ b/digsby/ext/buildsip.py @@ -0,0 +1,32 @@ +#__LICENSE_GOES_HERE__ +''' + +build Digsby's cgui native extension (SIP version) + +''' + +import os +import cguisetup +import wxpysetup + +from distutils.core import setup + +def build(): + if os.name == 'nt': + libs = ['User32.lib', 'Gdi32.lib', 'shell32.lib'] + else: + libs = [] + + extensions = [wxpysetup.make_sip_ext('cgui', + ['src/cgui.sip'] + cguisetup.sources, + include = './src', + libs = libs)] + + setup(name = 'cgui', + version = '1.0', + ext_modules = extensions, + cmdclass = {'build_ext': wxpysetup.wxpy_build_ext}) + +if __name__ == '__main__': + build() + diff --git a/digsby/ext/cguisetup.py b/digsby/ext/cguisetup.py new file mode 100644 index 0000000..3333e9b --- /dev/null +++ b/digsby/ext/cguisetup.py @@ -0,0 +1,95 @@ +''' +defines all the sources necessary for building cgui.pyd +''' +import os + +BUILD_BUDDYLIST_GUI = False +thisdir = os.path.dirname(os.path.abspath(__file__)) + +sources = ''' + src/ctextutil.cpp + src/SplitImage4.cpp + src/ScrollWindow.cpp + src/skinvlist.cpp + src/pyutils.cpp + src/cwindowfx.cpp + src/SkinSplitter.cpp + src/alphaborder.cpp + + src/skin/skinobjects.cpp + src/skin/SkinBitmap.cpp + + src/LoginWindow.cpp + src/DragMixin.cpp + src/MiscUI.cpp + + src/SelectionEvent.cpp + src/InputBox.cpp + src/ExpandoTextCtrl.cpp + src/ExpandEvent.cpp + src/GettextPython.cpp +'''.split() + + +include_dirs = ''' + src + src/skin + src/Animation + src/Animation/Platform + src/Animation/Platform/wx + src/BuddyList +'''.split() + +boost_env_dir = os.getenv('BOOST_DIR') +if boost_env_dir is not None: + include_dirs.append(boost_env_dir) + +# rtf +rtf_files = \ +''' +DebugUtil.cpp +HTMLEncoder.cpp +MSIMEncoder.cpp +MSNEncoder.cpp +RTFToX.cpp +StyleDesc.cpp +StringUtil.cpp +XHTMLEncoder.cpp +YahooEncoder.cpp +'''.split() + +sources.extend('src/RTFToX/%s' % s for s in rtf_files) +include_dirs.append('src/RTFToX') + +import sys +if sys.platform == 'win32': + sources.extend(''' + src/alphaborder_win.cpp + src/win/PlatformMessagesWin.cpp + + src/win/WindowSnapperWin.cpp + src/WindowSnapper.cpp + + src/win/FullscreenWin.cpp + src/win/WinUtils.cpp + src/win/WinTaskbar.cpp + src/win/WinJumpList.cpp + + src/win/RichEditUtils.cpp + + src/TransparentFrame.cpp + src/Statistics.cpp + + src/IconUtils.cpp +'''.split()) + + include_dirs.extend([ + 'src/win', + ]) + +if BUILD_BUDDYLIST_GUI: + sources.extend(''' + src/TreeList.cpp + src/BuddyList.cpp +'''.split()) + diff --git a/digsby/ext/do_buildext.py b/digsby/ext/do_buildext.py new file mode 100644 index 0000000..7fe2af7 --- /dev/null +++ b/digsby/ext/do_buildext.py @@ -0,0 +1,39 @@ +#__LICENSE_GOES_HERE__ +''' +A script that drives buildexts.py and installs the built extension into a platform +subdir of the main ext dir. This way we won't have cgui.py or cgui.pyd/so for various +platforms overwriting each other. +''' + +import sys, os, glob +scriptDir = os.path.abspath(sys.path[0]) +opj = os.path.join +digsbyDir = opj(scriptDir, "..") +sys.path += [digsbyDir] +clean = False + +from config import * + +platDir = opj(scriptDir, platformName) + +if "clean" in sys.argv: + os.system("cd %s;%s buildexts.py clean" % + (scriptDir, sys.executable)) + + for afile in glob.glob(os.path.join(platDir, "*")): + os.remove(afile) + + sys.exit(0) + +argsAsString = "" + +for arg in sys.argv[1:]: + argsAsString += " " + arg + +if "PYTHONPATH" in os.environ: + print "PYTHONPATH is %s" % os.environ["PYTHONPATH"] +else: + print "PYTHONPATH not set!" + +os.system("cd %s;%s buildexts.py install --install-platlib=%s --install-scripts=%s%s" % + (scriptDir, sys.executable, platDir, platDir, argsAsString)) diff --git a/digsby/ext/jsonspeedups.py b/digsby/ext/jsonspeedups.py new file mode 100644 index 0000000..1715720 --- /dev/null +++ b/digsby/ext/jsonspeedups.py @@ -0,0 +1,15 @@ +#__LICENSE_GOES_HERE__ +''' + +python jsonspeedups.py build_ext install --install-lib=platform_dir + +where platform_dir is win/mac/linux + +''' + +from distutils.core import setup, Extension + +setup(name = '_jsonspeedups', + ext_modules = [Extension('_jsonspeedups', ['../lib/simplejson/_speedups.c'])]) + + diff --git a/digsby/ext/lib/include/gtest/gtest-death-test.h b/digsby/ext/lib/include/gtest/gtest-death-test.h new file mode 100644 index 0000000..dcb2b66 --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest-death-test.h @@ -0,0 +1,262 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for death tests. It is +// #included by gtest.h so a user doesn't need to include this +// directly. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ + +#include + +namespace testing { + +// This flag controls the style of death tests. Valid values are "threadsafe", +// meaning that the death test child process will re-execute the test binary +// from the start, running only a single death test, or "fast", +// meaning that the child process will execute the test logic immediately +// after forking. +GTEST_DECLARE_string_(death_test_style); + +#if GTEST_HAS_DEATH_TEST + +// The following macros are useful for writing death tests. + +// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is +// executed: +// +// 1. It generates a warning if there is more than one active +// thread. This is because it's safe to fork() or clone() only +// when there is a single thread. +// +// 2. The parent process clone()s a sub-process and runs the death +// test in it; the sub-process exits with code 0 at the end of the +// death test, if it hasn't exited already. +// +// 3. The parent process waits for the sub-process to terminate. +// +// 4. The parent process checks the exit code and error message of +// the sub-process. +// +// Examples: +// +// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number"); +// for (int i = 0; i < 5; i++) { +// EXPECT_DEATH(server.ProcessRequest(i), +// "Invalid request .* in ProcessRequest()") +// << "Failed to die on request " << i); +// } +// +// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting"); +// +// bool KilledBySIGHUP(int exit_code) { +// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP; +// } +// +// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!"); +// +// On the regular expressions used in death tests: +// +// On POSIX-compliant systems (*nix), we use the library, +// which uses the POSIX extended regex syntax. +// +// On other platforms (e.g. Windows), we only support a simple regex +// syntax implemented as part of Google Test. This limited +// implementation should be enough most of the time when writing +// death tests; though it lacks many features you can find in PCRE +// or POSIX extended regex syntax. For example, we don't support +// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and +// repetition count ("x{5,7}"), among others. +// +// Below is the syntax that we do support. We chose it to be a +// subset of both PCRE and POSIX extended regex, so it's easy to +// learn wherever you come from. In the following: 'A' denotes a +// literal character, period (.), or a single \\ escape sequence; +// 'x' and 'y' denote regular expressions; 'm' and 'n' are for +// natural numbers. +// +// c matches any literal character c +// \\d matches any decimal digit +// \\D matches any character that's not a decimal digit +// \\f matches \f +// \\n matches \n +// \\r matches \r +// \\s matches any ASCII whitespace, including \n +// \\S matches any character that's not a whitespace +// \\t matches \t +// \\v matches \v +// \\w matches any letter, _, or decimal digit +// \\W matches any character that \\w doesn't match +// \\c matches any literal character c, which must be a punctuation +// . matches any single character except \n +// A? matches 0 or 1 occurrences of A +// A* matches 0 or many occurrences of A +// A+ matches 1 or many occurrences of A +// ^ matches the beginning of a string (not that of each line) +// $ matches the end of a string (not that of each line) +// xy matches x followed by y +// +// If you accidentally use PCRE or POSIX extended regex features +// not implemented by us, you will get a run-time failure. In that +// case, please try to rewrite your regular expression within the +// above syntax. +// +// This implementation is *not* meant to be as highly tuned or robust +// as a compiled regex library, but should perform well enough for a +// death test, which already incurs significant overhead by launching +// a child process. +// +// Known caveats: +// +// A "threadsafe" style death test obtains the path to the test +// program from argv[0] and re-executes it in the sub-process. For +// simplicity, the current implementation doesn't search the PATH +// when launching the sub-process. This means that the user must +// invoke the test program via a path that contains at least one +// path separator (e.g. path/to/foo_test and +// /absolute/path/to/bar_test are fine, but foo_test is not). This +// is rarely a problem as people usually don't put the test binary +// directory in PATH. +// +// TODO(wan@google.com): make thread-safe death tests search the PATH. + +// Asserts that a given statement causes the program to exit, with an +// integer exit status that satisfies predicate, and emitting error output +// that matches regex. +#define ASSERT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) + +// Like ASSERT_EXIT, but continues on to successive tests in the +// test case, if any: +#define EXPECT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) + +// Asserts that a given statement causes the program to exit, either by +// explicitly exiting with a nonzero exit code or being killed by a +// signal, and emitting error output that matches regex. +#define ASSERT_DEATH(statement, regex) \ + ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Like ASSERT_DEATH, but continues on to successive tests in the +// test case, if any: +#define EXPECT_DEATH(statement, regex) \ + EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: + +// Tests that an exit code describes a normal exit with a given exit code. +class ExitedWithCode { + public: + explicit ExitedWithCode(int exit_code); + bool operator()(int exit_status) const; + private: + const int exit_code_; +}; + +#if !GTEST_OS_WINDOWS +// Tests that an exit code describes an exit due to termination by a +// given signal. +class KilledBySignal { + public: + explicit KilledBySignal(int signum); + bool operator()(int exit_status) const; + private: + const int signum_; +}; +#endif // !GTEST_OS_WINDOWS + +// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. +// The death testing framework causes this to have interesting semantics, +// since the sideeffects of the call are only visible in opt mode, and not +// in debug mode. +// +// In practice, this can be used to test functions that utilize the +// LOG(DFATAL) macro using the following style: +// +// int DieInDebugOr12(int* sideeffect) { +// if (sideeffect) { +// *sideeffect = 12; +// } +// LOG(DFATAL) << "death"; +// return 12; +// } +// +// TEST(TestCase, TestDieOr12WorksInDgbAndOpt) { +// int sideeffect = 0; +// // Only asserts in dbg. +// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death"); +// +// #ifdef NDEBUG +// // opt-mode has sideeffect visible. +// EXPECT_EQ(12, sideeffect); +// #else +// // dbg-mode no visible sideeffect. +// EXPECT_EQ(0, sideeffect); +// #endif +// } +// +// This will assert that DieInDebugReturn12InOpt() crashes in debug +// mode, usually due to a DCHECK or LOG(DFATAL), but returns the +// appropriate fallback value (12 in this case) in opt mode. If you +// need to test that a function has appropriate side-effects in opt +// mode, include assertions against the side-effects. A general +// pattern for this is: +// +// EXPECT_DEBUG_DEATH({ +// // Side-effects here will have an effect after this statement in +// // opt mode, but none in debug mode. +// EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); +// }, "death"); +// +#ifdef NDEBUG + +#define EXPECT_DEBUG_DEATH(statement, regex) \ + do { statement; } while (false) + +#define ASSERT_DEBUG_DEATH(statement, regex) \ + do { statement; } while (false) + +#else + +#define EXPECT_DEBUG_DEATH(statement, regex) \ + EXPECT_DEATH(statement, regex) + +#define ASSERT_DEBUG_DEATH(statement, regex) \ + ASSERT_DEATH(statement, regex) + +#endif // NDEBUG for EXPECT_DEBUG_DEATH +#endif // GTEST_HAS_DEATH_TEST +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ diff --git a/digsby/ext/lib/include/gtest/gtest-message.h b/digsby/ext/lib/include/gtest/gtest-message.h new file mode 100644 index 0000000..99ae454 --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest-message.h @@ -0,0 +1,224 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the Message class. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! + +#ifndef GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +#define GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ + +#include +#include + +namespace testing { + +// The Message class works like an ostream repeater. +// +// Typical usage: +// +// 1. You stream a bunch of values to a Message object. +// It will remember the text in a StrStream. +// 2. Then you stream the Message object to an ostream. +// This causes the text in the Message to be streamed +// to the ostream. +// +// For example; +// +// testing::Message foo; +// foo << 1 << " != " << 2; +// std::cout << foo; +// +// will print "1 != 2". +// +// Message is not intended to be inherited from. In particular, its +// destructor is not virtual. +// +// Note that StrStream behaves differently in gcc and in MSVC. You +// can stream a NULL char pointer to it in the former, but not in the +// latter (it causes an access violation if you do). The Message +// class hides this difference by treating a NULL char pointer as +// "(null)". +class Message { + private: + // The type of basic IO manipulators (endl, ends, and flush) for + // narrow streams. + typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&); + + public: + // Constructs an empty Message. + // We allocate the StrStream separately because it otherwise each use of + // ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's + // stack frame leading to huge stack frames in some cases; gcc does not reuse + // the stack space. + Message() : ss_(new internal::StrStream) {} + + // Copy constructor. + Message(const Message& msg) : ss_(new internal::StrStream) { // NOLINT + *ss_ << msg.GetString(); + } + + // Constructs a Message from a C-string. + explicit Message(const char* str) : ss_(new internal::StrStream) { + *ss_ << str; + } + + ~Message() { delete ss_; } +#if GTEST_OS_SYMBIAN + // Streams a value (either a pointer or not) to this object. + template + inline Message& operator <<(const T& value) { + StreamHelper(typename internal::is_pointer::type(), value); + return *this; + } +#else + // Streams a non-pointer value to this object. + template + inline Message& operator <<(const T& val) { + ::GTestStreamToHelper(ss_, val); + return *this; + } + + // Streams a pointer value to this object. + // + // This function is an overload of the previous one. When you + // stream a pointer to a Message, this definition will be used as it + // is more specialized. (The C++ Standard, section + // [temp.func.order].) If you stream a non-pointer, then the + // previous definition will be used. + // + // The reason for this overload is that streaming a NULL pointer to + // ostream is undefined behavior. Depending on the compiler, you + // may get "0", "(nil)", "(null)", or an access violation. To + // ensure consistent result across compilers, we always treat NULL + // as "(null)". + template + inline Message& operator <<(T* const& pointer) { // NOLINT + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + ::GTestStreamToHelper(ss_, pointer); + } + return *this; + } +#endif // GTEST_OS_SYMBIAN + + // Since the basic IO manipulators are overloaded for both narrow + // and wide streams, we have to provide this specialized definition + // of operator <<, even though its body is the same as the + // templatized version above. Without this definition, streaming + // endl or other basic IO manipulators to Message will confuse the + // compiler. + Message& operator <<(BasicNarrowIoManip val) { + *ss_ << val; + return *this; + } + + // Instead of 1/0, we want to see true/false for bool values. + Message& operator <<(bool b) { + return *this << (b ? "true" : "false"); + } + + // These two overloads allow streaming a wide C string to a Message + // using the UTF-8 encoding. + Message& operator <<(const wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); + } + Message& operator <<(wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); + } + +#if GTEST_HAS_STD_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::std::wstring& wstr); +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::wstring& wstr); +#endif // GTEST_HAS_GLOBAL_WSTRING + + // Gets the text streamed to this object so far as a String. + // Each '\0' character in the buffer is replaced with "\\0". + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::String GetString() const { + return internal::StrStreamToString(ss_); + } + + private: +#if GTEST_OS_SYMBIAN + // These are needed as the Nokia Symbian Compiler cannot decide between + // const T& and const T* in a function template. The Nokia compiler _can_ + // decide between class template specializations for T and T*, so a + // tr1::type_traits-like is_pointer works, and we can overload on that. + template + inline void StreamHelper(internal::true_type dummy, T* pointer) { + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + ::GTestStreamToHelper(ss_, pointer); + } + } + template + inline void StreamHelper(internal::false_type dummy, const T& value) { + ::GTestStreamToHelper(ss_, value); + } +#endif // GTEST_OS_SYMBIAN + + // We'll hold the text streamed to this object here. + internal::StrStream* const ss_; + + // We declare (but don't implement) this to prevent the compiler + // from implementing the assignment operator. + void operator=(const Message&); +}; + +// Streams a Message to an ostream. +inline std::ostream& operator <<(std::ostream& os, const Message& sb) { + return os << sb.GetString(); +} + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ diff --git a/digsby/ext/lib/include/gtest/gtest-param-test.h b/digsby/ext/lib/include/gtest/gtest-param-test.h new file mode 100644 index 0000000..421517d --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest-param-test.h @@ -0,0 +1,1385 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: vladl@google.com (Vlad Losev) +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing Framework (Google Test) +// +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It must be derived from testing::TestWithParam, where T is +// the type of your parameter values. TestWithParam is itself derived +// from testing::Test. T can be any copyable type. If it's a raw pointer, +// you are responsible for managing the lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test case +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_CASE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more then once) the first argument to the +// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the +// actual test case name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests +// in the given test case, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_CASE_P statement. +// +// Please also note that generator expressions are evaluated in +// RUN_ALL_TESTS(), after main() has started. This allows evaluation of +// parameter list based on command line parameters. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. + +#endif // 0 + + +#include + +#include + +#if GTEST_HAS_PARAM_TEST + +#include +#include +#include + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test case is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test case FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test case StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_CASE_P(StringSequence, SrtingTest, ValuesIn(strings)); +// +// This instantiates tests from test case StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_CASE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_CASE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename ::std::iterator_traits::value_type> ValuesIn( + ForwardIterator begin, + ForwardIterator end) { + typedef typename ::std::iterator_traits::value_type + ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test case BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three")); +// +// This instantiates tests from test case BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// Currently, Values() supports from 1 to 50 parameters. +// +template +internal::ValueArray1 Values(T1 v1) { + return internal::ValueArray1(v1); +} + +template +internal::ValueArray2 Values(T1 v1, T2 v2) { + return internal::ValueArray2(v1, v2); +} + +template +internal::ValueArray3 Values(T1 v1, T2 v2, T3 v3) { + return internal::ValueArray3(v1, v2, v3); +} + +template +internal::ValueArray4 Values(T1 v1, T2 v2, T3 v3, T4 v4) { + return internal::ValueArray4(v1, v2, v3, v4); +} + +template +internal::ValueArray5 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5) { + return internal::ValueArray5(v1, v2, v3, v4, v5); +} + +template +internal::ValueArray6 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6) { + return internal::ValueArray6(v1, v2, v3, v4, v5, v6); +} + +template +internal::ValueArray7 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7) { + return internal::ValueArray7(v1, v2, v3, v4, v5, + v6, v7); +} + +template +internal::ValueArray8 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8) { + return internal::ValueArray8(v1, v2, v3, v4, + v5, v6, v7, v8); +} + +template +internal::ValueArray9 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9) { + return internal::ValueArray9(v1, v2, v3, + v4, v5, v6, v7, v8, v9); +} + +template +internal::ValueArray10 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10) { + return internal::ValueArray10(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10); +} + +template +internal::ValueArray11 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) { + return internal::ValueArray11(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); +} + +template +internal::ValueArray12 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) { + return internal::ValueArray12(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); +} + +template +internal::ValueArray13 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) { + return internal::ValueArray13(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13); +} + +template +internal::ValueArray14 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) { + return internal::ValueArray14(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14); +} + +template +internal::ValueArray15 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) { + return internal::ValueArray15(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15); +} + +template +internal::ValueArray16 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16) { + return internal::ValueArray16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); +} + +template +internal::ValueArray17 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17) { + return internal::ValueArray17(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17); +} + +template +internal::ValueArray18 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18) { + return internal::ValueArray18(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18); +} + +template +internal::ValueArray19 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19) { + return internal::ValueArray19(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19); +} + +template +internal::ValueArray20 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20) { + return internal::ValueArray20(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20); +} + +template +internal::ValueArray21 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21) { + return internal::ValueArray21(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21); +} + +template +internal::ValueArray22 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22) { + return internal::ValueArray22(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22); +} + +template +internal::ValueArray23 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23) { + return internal::ValueArray23(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23); +} + +template +internal::ValueArray24 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24) { + return internal::ValueArray24(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24); +} + +template +internal::ValueArray25 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25) { + return internal::ValueArray25(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25); +} + +template +internal::ValueArray26 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) { + return internal::ValueArray26(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26); +} + +template +internal::ValueArray27 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) { + return internal::ValueArray27(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27); +} + +template +internal::ValueArray28 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) { + return internal::ValueArray28(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28); +} + +template +internal::ValueArray29 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) { + return internal::ValueArray29(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29); +} + +template +internal::ValueArray30 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) { + return internal::ValueArray30(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30); +} + +template +internal::ValueArray31 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) { + return internal::ValueArray31(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31); +} + +template +internal::ValueArray32 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32) { + return internal::ValueArray32(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32); +} + +template +internal::ValueArray33 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33) { + return internal::ValueArray33(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33); +} + +template +internal::ValueArray34 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34) { + return internal::ValueArray34(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34); +} + +template +internal::ValueArray35 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35) { + return internal::ValueArray35(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35); +} + +template +internal::ValueArray36 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36) { + return internal::ValueArray36(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36); +} + +template +internal::ValueArray37 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37) { + return internal::ValueArray37(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37); +} + +template +internal::ValueArray38 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38) { + return internal::ValueArray38(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, + v33, v34, v35, v36, v37, v38); +} + +template +internal::ValueArray39 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38, T39 v39) { + return internal::ValueArray39(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, + v32, v33, v34, v35, v36, v37, v38, v39); +} + +template +internal::ValueArray40 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, + T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, + T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) { + return internal::ValueArray40(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, + v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40); +} + +template +internal::ValueArray41 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41) { + return internal::ValueArray41(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, + v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41); +} + +template +internal::ValueArray42 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) { + return internal::ValueArray42(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, + v42); +} + +template +internal::ValueArray43 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) { + return internal::ValueArray43(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, + v41, v42, v43); +} + +template +internal::ValueArray44 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) { + return internal::ValueArray44(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, + v40, v41, v42, v43, v44); +} + +template +internal::ValueArray45 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41, T42 v42, T43 v43, T44 v44, T45 v45) { + return internal::ValueArray45(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, + v39, v40, v41, v42, v43, v44, v45); +} + +template +internal::ValueArray46 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) { + return internal::ValueArray46(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46); +} + +template +internal::ValueArray47 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) { + return internal::ValueArray47(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46, v47); +} + +template +internal::ValueArray48 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, + T48 v48) { + return internal::ValueArray48(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, + v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48); +} + +template +internal::ValueArray49 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, + T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, + T47 v47, T48 v48, T49 v49) { + return internal::ValueArray49(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, + v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49); +} + +template +internal::ValueArray50 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, + T38 v38, T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, + T46 v46, T47 v47, T48 v48, T49 v49, T50 v50) { + return internal::ValueArray50(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, + v48, v49, v50); +} + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test case FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { + return Values(false, true); +} + +#if GTEST_HAS_COMBINE +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Combine can have up to 10 arguments. This number is currently limited +// by the maximum number of elements in the tuple implementation used by Google +// Test. +// +// Example: +// +// This will instantiate tests in test case AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +template +internal::CartesianProductHolder2 Combine( + const Generator1& g1, const Generator2& g2) { + return internal::CartesianProductHolder2( + g1, g2); +} + +template +internal::CartesianProductHolder3 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3) { + return internal::CartesianProductHolder3( + g1, g2, g3); +} + +template +internal::CartesianProductHolder4 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4) { + return internal::CartesianProductHolder4( + g1, g2, g3, g4); +} + +template +internal::CartesianProductHolder5 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5) { + return internal::CartesianProductHolder5( + g1, g2, g3, g4, g5); +} + +template +internal::CartesianProductHolder6 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6) { + return internal::CartesianProductHolder6( + g1, g2, g3, g4, g5, g6); +} + +template +internal::CartesianProductHolder7 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7) { + return internal::CartesianProductHolder7( + g1, g2, g3, g4, g5, g6, g7); +} + +template +internal::CartesianProductHolder8 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8) { + return internal::CartesianProductHolder8( + g1, g2, g3, g4, g5, g6, g7, g8); +} + +template +internal::CartesianProductHolder9 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9) { + return internal::CartesianProductHolder9( + g1, g2, g3, g4, g5, g6, g7, g8, g9); +} + +template +internal::CartesianProductHolder10 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9, + const Generator10& g10) { + return internal::CartesianProductHolder10( + g1, g2, g3, g4, g5, g6, g7, g8, g9, g10); +} +#endif // GTEST_HAS_COMBINE + + + +#define TEST_P(test_case_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + : public test_case_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \ + virtual void TestBody(); \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + return 0; \ + } \ + static int gtest_registering_dummy_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_case_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + ::testing::internal::ParamGenerator \ + gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ + int gtest_##prefix##test_case_name##_dummy_ = \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + __FILE__, __LINE__) + +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ diff --git a/digsby/ext/lib/include/gtest/gtest-param-test.h.pump b/digsby/ext/lib/include/gtest/gtest-param-test.h.pump new file mode 100644 index 0000000..c761f12 --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest-param-test.h.pump @@ -0,0 +1,453 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of Values arguments we want to support. +$var maxtuple = 10 $$ Maximum number of Combine arguments we want to support. +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: vladl@google.com (Vlad Losev) +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing Framework (Google Test) +// +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It must be derived from testing::TestWithParam, where T is +// the type of your parameter values. TestWithParam is itself derived +// from testing::Test. T can be any copyable type. If it's a raw pointer, +// you are responsible for managing the lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test case +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_CASE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more then once) the first argument to the +// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the +// actual test case name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests +// in the given test case, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_CASE_P statement. +// +// Please also note that generator expressions are evaluated in +// RUN_ALL_TESTS(), after main() has started. This allows evaluation of +// parameter list based on command line parameters. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. + +#endif // 0 + + +#include + +#include + +#if GTEST_HAS_PARAM_TEST + +#include +#include +#include + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test case is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test case FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test case StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_CASE_P(StringSequence, SrtingTest, ValuesIn(strings)); +// +// This instantiates tests from test case StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_CASE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_CASE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename ::std::iterator_traits::value_type> ValuesIn( + ForwardIterator begin, + ForwardIterator end) { + typedef typename ::std::iterator_traits::value_type + ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test case BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three")); +// +// This instantiates tests from test case BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// Currently, Values() supports from 1 to $n parameters. +// +$range i 1..n +$for i [[ +$range j 1..i + +template <$for j, [[typename T$j]]> +internal::ValueArray$i<$for j, [[T$j]]> Values($for j, [[T$j v$j]]) { + return internal::ValueArray$i<$for j, [[T$j]]>($for j, [[v$j]]); +} + +]] + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test case FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { + return Values(false, true); +} + +#if GTEST_HAS_COMBINE +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Combine can have up to $maxtuple arguments. This number is currently limited +// by the maximum number of elements in the tuple implementation used by Google +// Test. +// +// Example: +// +// This will instantiate tests in test case AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +$range i 2..maxtuple +$for i [[ +$range j 1..i + +template <$for j, [[typename Generator$j]]> +internal::CartesianProductHolder$i<$for j, [[Generator$j]]> Combine( + $for j, [[const Generator$j& g$j]]) { + return internal::CartesianProductHolder$i<$for j, [[Generator$j]]>( + $for j, [[g$j]]); +} + +]] +#endif // GTEST_HAS_COMBINE + + + +#define TEST_P(test_case_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + : public test_case_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \ + virtual void TestBody(); \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + return 0; \ + } \ + static int gtest_registering_dummy_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_case_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + ::testing::internal::ParamGenerator \ + gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ + int gtest_##prefix##test_case_name##_dummy_ = \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + __FILE__, __LINE__) + +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ diff --git a/digsby/ext/lib/include/gtest/gtest-spi.h b/digsby/ext/lib/include/gtest/gtest-spi.h new file mode 100644 index 0000000..a4e387a --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest-spi.h @@ -0,0 +1,221 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Utilities for testing Google Test itself and code that uses Google Test +// (e.g. frameworks built on top of Google Test). + +#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_ +#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_ + +#include + +namespace testing { + +// This helper class can be used to mock out Google Test failure reporting +// so that we can test Google Test or code that builds on Google Test. +// +// An object of this class appends a TestPartResult object to the +// TestPartResultArray object given in the constructor whenever a Google Test +// failure is reported. It can either intercept only failures that are +// generated in the same thread that created this object or it can intercept +// all generated failures. The scope of this mock object can be controlled with +// the second argument to the two arguments constructor. +class ScopedFakeTestPartResultReporter + : public TestPartResultReporterInterface { + public: + // The two possible mocking modes of this object. + enum InterceptMode { + INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. + INTERCEPT_ALL_THREADS // Intercepts all failures. + }; + + // The c'tor sets this object as the test part result reporter used + // by Google Test. The 'result' parameter specifies where to report the + // results. This reporter will only catch failures generated in the current + // thread. DEPRECATED + explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); + + // Same as above, but you can choose the interception scope of this object. + ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, + TestPartResultArray* result); + + // The d'tor restores the previous test part result reporter. + virtual ~ScopedFakeTestPartResultReporter(); + + // Appends the TestPartResult object to the TestPartResultArray + // received in the constructor. + // + // This method is from the TestPartResultReporterInterface + // interface. + virtual void ReportTestPartResult(const TestPartResult& result); + private: + void Init(); + + const InterceptMode intercept_mode_; + TestPartResultReporterInterface* old_reporter_; + TestPartResultArray* const result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); +}; + +namespace internal { + +// A helper class for implementing EXPECT_FATAL_FAILURE() and +// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +class SingleFailureChecker { + public: + // The constructor remembers the arguments. + SingleFailureChecker(const TestPartResultArray* results, + TestPartResultType type, + const char* substr); + ~SingleFailureChecker(); + private: + const TestPartResultArray* const results_; + const TestPartResultType type_; + const String substr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); +}; + +} // namespace internal + +} // namespace testing + +// A set of macros for testing Google Test assertions or code that's expected +// to generate Google Test fatal failures. It verifies that the given +// statement will cause exactly one fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_FATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - 'statement' cannot reference local non-static variables or +// non-static members of the current object. +// - 'statement' cannot return a value. +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_FATAL_FAILURE(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TPRT_FATAL_FAILURE, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (false) + +#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TPRT_FATAL_FAILURE, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ALL_THREADS, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (false) + +// A macro for testing Google Test assertions or code that's expected to +// generate Google Test non-fatal failures. It asserts that the given +// statement will cause exactly one non-fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// 'statement' is allowed to reference local variables and members of +// the current object. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_NONFATAL_FAILURE(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TPRT_NONFATAL_FAILURE, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + statement;\ + }\ + } while (false) + +#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TPRT_NONFATAL_FAILURE, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS,\ + >est_failures);\ + statement;\ + }\ + } while (false) + +#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_ diff --git a/digsby/ext/lib/include/gtest/gtest-test-part.h b/digsby/ext/lib/include/gtest/gtest-test-part.h new file mode 100644 index 0000000..1a281af --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest-test-part.h @@ -0,0 +1,179 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: mheule@google.com (Markus Heule) +// + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ + +#include +#include +#include + +namespace testing { + +// The possible outcomes of a test part (i.e. an assertion or an +// explicit SUCCEED(), FAIL(), or ADD_FAILURE()). +enum TestPartResultType { + TPRT_SUCCESS, // Succeeded. + TPRT_NONFATAL_FAILURE, // Failed but the test can continue. + TPRT_FATAL_FAILURE // Failed and the test should be terminated. +}; + +// A copyable object representing the result of a test part (i.e. an +// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()). +// +// Don't inherit from TestPartResult as its destructor is not virtual. +class TestPartResult { + public: + // C'tor. TestPartResult does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestPartResult object. + TestPartResult(TestPartResultType type, + const char* file_name, + int line_number, + const char* message) + : type_(type), + file_name_(file_name), + line_number_(line_number), + summary_(ExtractSummary(message)), + message_(message) { + } + + // Gets the outcome of the test part. + TestPartResultType type() const { return type_; } + + // Gets the name of the source file where the test part took place, or + // NULL if it's unknown. + const char* file_name() const { return file_name_.c_str(); } + + // Gets the line in the source file where the test part took place, + // or -1 if it's unknown. + int line_number() const { return line_number_; } + + // Gets the summary of the failure message. + const char* summary() const { return summary_.c_str(); } + + // Gets the message associated with the test part. + const char* message() const { return message_.c_str(); } + + // Returns true iff the test part passed. + bool passed() const { return type_ == TPRT_SUCCESS; } + + // Returns true iff the test part failed. + bool failed() const { return type_ != TPRT_SUCCESS; } + + // Returns true iff the test part non-fatally failed. + bool nonfatally_failed() const { return type_ == TPRT_NONFATAL_FAILURE; } + + // Returns true iff the test part fatally failed. + bool fatally_failed() const { return type_ == TPRT_FATAL_FAILURE; } + private: + TestPartResultType type_; + + // Gets the summary of the failure message by omitting the stack + // trace in it. + static internal::String ExtractSummary(const char* message); + + // The name of the source file where the test part took place, or + // NULL if the source file is unknown. + internal::String file_name_; + // The line in the source file where the test part took place, or -1 + // if the line number is unknown. + int line_number_; + internal::String summary_; // The test failure summary. + internal::String message_; // The test failure message. +}; + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result); + +// An array of TestPartResult objects. +// +// We define this class as we cannot use STL containers when compiling +// Google Test with MSVC 7.1 and exceptions disabled. +// +// Don't inherit from TestPartResultArray as its destructor is not +// virtual. +class TestPartResultArray { + public: + TestPartResultArray(); + ~TestPartResultArray(); + + // Appends the given TestPartResult to the array. + void Append(const TestPartResult& result); + + // Returns the TestPartResult at the given index (0-based). + const TestPartResult& GetTestPartResult(int index) const; + + // Returns the number of TestPartResult objects in the array. + int size() const; + private: + // Internally we use a list to simulate the array. Yes, this means + // that random access is O(N) in time, but it's OK for its purpose. + internal::List* const list_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray); +}; + +// This interface knows how to report a test part result. +class TestPartResultReporterInterface { + public: + virtual ~TestPartResultReporterInterface() {} + + virtual void ReportTestPartResult(const TestPartResult& result) = 0; +}; + +namespace internal { + +// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a +// statement generates new fatal failures. To do so it registers itself as the +// current test part result reporter. Besides checking if fatal failures were +// reported, it only delegates the reporting to the former result reporter. +// The original result reporter is restored in the destructor. +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +class HasNewFatalFailureHelper : public TestPartResultReporterInterface { + public: + HasNewFatalFailureHelper(); + virtual ~HasNewFatalFailureHelper(); + virtual void ReportTestPartResult(const TestPartResult& result); + bool has_new_fatal_failure() const { return has_new_fatal_failure_; } + private: + bool has_new_fatal_failure_; + TestPartResultReporterInterface* original_reporter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper); +}; + +} // namespace internal + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ diff --git a/digsby/ext/lib/include/gtest/gtest-typed-test.h b/digsby/ext/lib/include/gtest/gtest-typed-test.h new file mode 100644 index 0000000..519edfe --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest-typed-test.h @@ -0,0 +1,253 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +// This header implements typed tests and type-parameterized tests. + +// Typed (aka type-driven) tests repeat the same test for types in a +// list. You must know which types you want to test with when writing +// typed tests. Here's how you do it: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + public: + ... + typedef std::list List; + static T shared_; + T value_; +}; + +// Next, associate a list of types with the test case, which will be +// repeated for each type in the list. The typedef is necessary for +// the macro to parse correctly. +typedef testing::Types MyTypes; +TYPED_TEST_CASE(FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// TYPED_TEST_CASE(FooTest, int); + +// Then, use TYPED_TEST() instead of TEST_F() to define as many typed +// tests for this test case as you want. +TYPED_TEST(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + // Since we are inside a derived class template, C++ requires use to + // visit the members of FooTest via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the TestFixture:: + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the "typename + // TestFixture::" prefix. + typename TestFixture::List values; + values.push_back(n); + ... +} + +TYPED_TEST(FooTest, HasPropertyA) { ... } + +#endif // 0 + +// Type-parameterized tests are abstract test patterns parameterized +// by a type. Compared with typed tests, type-parameterized tests +// allow you to define the test pattern without knowing what the type +// parameters are. The defined pattern can be instantiated with +// different types any number of times, in any number of translation +// units. +// +// If you are designing an interface or concept, you can define a +// suite of type-parameterized tests to verify properties that any +// valid implementation of the interface/concept should have. Then, +// each implementation can easily instantiate the test suite to verify +// that it conforms to the requirements, without having to write +// similar tests repeatedly. Here's an example: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + ... +}; + +// Next, declare that you will define a type-parameterized test case +// (the _P suffix is for "parameterized" or "pattern", whichever you +// prefer): +TYPED_TEST_CASE_P(FooTest); + +// Then, use TYPED_TEST_P() to define as many type-parameterized tests +// for this type-parameterized test case as you want. +TYPED_TEST_P(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + TypeParam n = 0; + ... +} + +TYPED_TEST_P(FooTest, HasPropertyA) { ... } + +// Now the tricky part: you need to register all test patterns before +// you can instantiate them. The first argument of the macro is the +// test case name; the rest are the names of the tests in this test +// case. +REGISTER_TYPED_TEST_CASE_P(FooTest, + DoesBlah, HasPropertyA); + +// Finally, you are free to instantiate the pattern with the types you +// want. If you put the above code in a header file, you can #include +// it in multiple C++ source files and instantiate it multiple times. +// +// To distinguish different instances of the pattern, the first +// argument to the INSTANTIATE_* macro is a prefix that will be added +// to the actual test case name. Remember to pick unique prefixes for +// different instances. +typedef testing::Types MyTypes; +INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, int); + +#endif // 0 + +#include +#include + +// Implements typed tests. + +#if GTEST_HAS_TYPED_TEST + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the typedef for the type parameters of the +// given test case. +#define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_ + +#define TYPED_TEST_CASE(CaseName, Types) \ + typedef ::testing::internal::TypeList::type \ + GTEST_TYPE_PARAMS_(CaseName) + +#define TYPED_TEST(CaseName, TestName) \ + template \ + class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ + : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + bool gtest_##CaseName##_##TestName##_registered_ = \ + ::testing::internal::TypeParameterizedTest< \ + CaseName, \ + ::testing::internal::TemplateSel< \ + GTEST_TEST_CLASS_NAME_(CaseName, TestName)>, \ + GTEST_TYPE_PARAMS_(CaseName)>::Register(\ + "", #CaseName, #TestName, 0); \ + template \ + void GTEST_TEST_CLASS_NAME_(CaseName, TestName)::TestBody() + +#endif // GTEST_HAS_TYPED_TEST + +// Implements type-parameterized tests. + +#if GTEST_HAS_TYPED_TEST_P + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the namespace name that the type-parameterized tests for +// the given type-parameterized test case are defined in. The exact +// name of the namespace is subject to change without notice. +#define GTEST_CASE_NAMESPACE_(TestCaseName) \ + gtest_case_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the variable used to remember the names of +// the defined tests in the given test case. +#define GTEST_TYPED_TEST_CASE_P_STATE_(TestCaseName) \ + gtest_typed_test_case_p_state_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. +// +// Expands to the name of the variable used to remember the names of +// the registered tests in the given test case. +#define GTEST_REGISTERED_TEST_NAMES_(TestCaseName) \ + gtest_registered_test_names_##TestCaseName##_ + +// The variables defined in the type-parameterized test macros are +// static as typically these macros are used in a .h file that can be +// #included in multiple translation units linked together. +#define TYPED_TEST_CASE_P(CaseName) \ + static ::testing::internal::TypedTestCasePState \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName) + +#define TYPED_TEST_P(CaseName, TestName) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + template \ + class TestName : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + static bool gtest_##TestName##_defined_ = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).AddTestName(\ + __FILE__, __LINE__, #CaseName, #TestName); \ + } \ + template \ + void GTEST_CASE_NAMESPACE_(CaseName)::TestName::TestBody() + +#define REGISTER_TYPED_TEST_CASE_P(CaseName, ...) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_; \ + } \ + static const char* const GTEST_REGISTERED_TEST_NAMES_(CaseName) = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).VerifyRegisteredTestNames(\ + __FILE__, __LINE__, #__VA_ARGS__) + +#define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types) \ + bool gtest_##Prefix##_##CaseName = \ + ::testing::internal::TypeParameterizedTestCase::type>::Register(\ + #Prefix, #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName)) + +#endif // GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ diff --git a/digsby/ext/lib/include/gtest/gtest.h b/digsby/ext/lib/include/gtest/gtest.h new file mode 100644 index 0000000..9b72b63 --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest.h @@ -0,0 +1,1415 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for Google Test. It should be +// included by any test program that uses Google Test. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! +// +// Acknowledgment: Google Test borrowed the idea of automatic test +// registration from Barthelemy Dagenais' (barthelemy@prologique.com) +// easyUnit framework. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_H_ + +// The following platform macros are used throughout Google Test: +// _WIN32_WCE Windows CE (set in project files) +// +// Note that even though _MSC_VER and _WIN32_WCE really indicate a compiler +// and a Win32 implementation, respectively, we use them to indicate the +// combination of compiler - Win 32 API - C library, since the code currently +// only supports: +// Windows proper with Visual C++ and MS C library (_MSC_VER && !_WIN32_WCE) and +// Windows Mobile with Visual C++ and no C library (_WIN32_WCE). + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Depending on the platform, different string classes are available. +// On Windows, ::std::string compiles only when exceptions are +// enabled. On Linux, in addition to ::std::string, Google also makes +// use of class ::string, which has the same interface as +// ::std::string, but has a different implementation. +// +// The user can tell us whether ::std::string is available in his +// environment by defining the macro GTEST_HAS_STD_STRING to either 1 +// or 0 on the compiler command line. He can also define +// GTEST_HAS_GLOBAL_STRING to 1 to indicate that ::string is available +// AND is a distinct type to ::std::string, or define it to 0 to +// indicate otherwise. +// +// If the user's ::std::string and ::string are the same class due to +// aliasing, he should define GTEST_HAS_STD_STRING to 1 and +// GTEST_HAS_GLOBAL_STRING to 0. +// +// If the user doesn't define GTEST_HAS_STD_STRING and/or +// GTEST_HAS_GLOBAL_STRING, they are defined heuristically. + +namespace testing { + +// Declares the flags. + +// This flag temporary enables the disabled tests. +GTEST_DECLARE_bool_(also_run_disabled_tests); + +// This flag brings the debugger on an assertion failure. +GTEST_DECLARE_bool_(break_on_failure); + +// This flag controls whether Google Test catches all test-thrown exceptions +// and logs them as failures. +GTEST_DECLARE_bool_(catch_exceptions); + +// This flag enables using colors in terminal output. Available values are +// "yes" to enable colors, "no" (disable colors), or "auto" (the default) +// to let Google Test decide. +GTEST_DECLARE_string_(color); + +// This flag sets up the filter to select by name using a glob pattern +// the tests to run. If the filter is not given all tests are executed. +GTEST_DECLARE_string_(filter); + +// This flag causes the Google Test to list tests. None of the tests listed +// are actually run if the flag is provided. +GTEST_DECLARE_bool_(list_tests); + +// This flag controls whether Google Test emits a detailed XML report to a file +// in addition to its normal textual output. +GTEST_DECLARE_string_(output); + +// This flags control whether Google Test prints the elapsed time for each +// test. +GTEST_DECLARE_bool_(print_time); + +// This flag sets how many times the tests are repeated. The default value +// is 1. If the value is -1 the tests are repeating forever. +GTEST_DECLARE_int32_(repeat); + +// This flag controls whether Google Test includes Google Test internal +// stack frames in failure stack traces. +GTEST_DECLARE_bool_(show_internal_stack_frames); + +// This flag specifies the maximum number of stack frames to be +// printed in a failure message. +GTEST_DECLARE_int32_(stack_trace_depth); + +// When this flag is specified, a failed assertion will throw an +// exception if exceptions are enabled, or exit the program with a +// non-zero code otherwise. +GTEST_DECLARE_bool_(throw_on_failure); + +// The upper limit for valid stack trace depths. +const int kMaxStackTraceDepth = 100; + +namespace internal { + +class GTestFlagSaver; + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +// Declared in gtest-internal.h but defined here, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable) { + return (Message() << streamable).GetString(); +} + +} // namespace internal + +// A class for indicating whether an assertion was successful. When +// the assertion wasn't successful, the AssertionResult object +// remembers a non-empty message that described how it failed. +// +// This class is useful for defining predicate-format functions to be +// used with predicate assertions (ASSERT_PRED_FORMAT*, etc). +// +// The constructor of AssertionResult is private. To create an +// instance of this class, use one of the factory functions +// (AssertionSuccess() and AssertionFailure()). +// +// For example, in order to be able to write: +// +// // Verifies that Foo() returns an even number. +// EXPECT_PRED_FORMAT1(IsEven, Foo()); +// +// you just need to define: +// +// testing::AssertionResult IsEven(const char* expr, int n) { +// if ((n % 2) == 0) return testing::AssertionSuccess(); +// +// Message msg; +// msg << "Expected: " << expr << " is even\n" +// << " Actual: it's " << n; +// return testing::AssertionFailure(msg); +// } +// +// If Foo() returns 5, you will see the following message: +// +// Expected: Foo() is even +// Actual: it's 5 +class AssertionResult { + public: + // Declares factory functions for making successful and failed + // assertion results as friends. + friend AssertionResult AssertionSuccess(); + friend AssertionResult AssertionFailure(const Message&); + + // Returns true iff the assertion succeeded. + operator bool() const { return failure_message_.c_str() == NULL; } // NOLINT + + // Returns the assertion's failure message. + const char* failure_message() const { return failure_message_.c_str(); } + + private: + // The default constructor. It is used when the assertion succeeded. + AssertionResult() {} + + // The constructor used when the assertion failed. + explicit AssertionResult(const internal::String& failure_message); + + // Stores the assertion's failure message. + internal::String failure_message_; +}; + +// Makes a successful assertion result. +AssertionResult AssertionSuccess(); + +// Makes a failed assertion result with the given failure message. +AssertionResult AssertionFailure(const Message& msg); + +// The abstract class that all tests inherit from. +// +// In Google Test, a unit test program contains one or many TestCases, and +// each TestCase contains one or many Tests. +// +// When you define a test using the TEST macro, you don't need to +// explicitly derive from Test - the TEST macro automatically does +// this for you. +// +// The only time you derive from Test is when defining a test fixture +// to be used a TEST_F. For example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { ... } +// virtual void TearDown() { ... } +// ... +// }; +// +// TEST_F(FooTest, Bar) { ... } +// TEST_F(FooTest, Baz) { ... } +// +// Test is not copyable. +class Test { + public: + friend class internal::TestInfoImpl; + + // Defines types for pointers to functions that set up and tear down + // a test case. + typedef internal::SetUpTestCaseFunc SetUpTestCaseFunc; + typedef internal::TearDownTestCaseFunc TearDownTestCaseFunc; + + // The d'tor is virtual as we intend to inherit from Test. + virtual ~Test(); + + // Sets up the stuff shared by all tests in this test case. + // + // Google Test will call Foo::SetUpTestCase() before running the first + // test in test case Foo. Hence a sub-class can define its own + // SetUpTestCase() method to shadow the one defined in the super + // class. + static void SetUpTestCase() {} + + // Tears down the stuff shared by all tests in this test case. + // + // Google Test will call Foo::TearDownTestCase() after running the last + // test in test case Foo. Hence a sub-class can define its own + // TearDownTestCase() method to shadow the one defined in the super + // class. + static void TearDownTestCase() {} + + // Returns true iff the current test has a fatal failure. + static bool HasFatalFailure(); + + // Logs a property for the current test. Only the last value for a given + // key is remembered. + // These are public static so they can be called from utility functions + // that are not members of the test fixture. + // The arguments are const char* instead strings, as Google Test is used + // on platforms where string doesn't compile. + // + // Note that a driving consideration for these RecordProperty methods + // was to produce xml output suited to the Greenspan charting utility, + // which at present will only chart values that fit in a 32-bit int. It + // is the user's responsibility to restrict their values to 32-bit ints + // if they intend them to be used with Greenspan. + static void RecordProperty(const char* key, const char* value); + static void RecordProperty(const char* key, int value); + + protected: + // Creates a Test object. + Test(); + + // Sets up the test fixture. + virtual void SetUp(); + + // Tears down the test fixture. + virtual void TearDown(); + + private: + // Returns true iff the current test has the same fixture class as + // the first test in the current test case. + static bool HasSameFixtureClass(); + + // Runs the test after the test fixture has been set up. + // + // A sub-class must implement this to define the test logic. + // + // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM. + // Instead, use the TEST or TEST_F macro. + virtual void TestBody() = 0; + + // Sets up, executes, and tears down the test. + void Run(); + + // Uses a GTestFlagSaver to save and restore all Google Test flags. + const internal::GTestFlagSaver* const gtest_flag_saver_; + + // Often a user mis-spells SetUp() as Setup() and spends a long time + // wondering why it is never called by Google Test. The declaration of + // the following method is solely for catching such an error at + // compile time: + // + // - The return type is deliberately chosen to be not void, so it + // will be a conflict if a user declares void Setup() in his test + // fixture. + // + // - This method is private, so it will be another compiler error + // if a user calls it from his test fixture. + // + // DO NOT OVERRIDE THIS FUNCTION. + // + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } + + // We disallow copying Tests. + GTEST_DISALLOW_COPY_AND_ASSIGN_(Test); +}; + + +// A TestInfo object stores the following information about a test: +// +// Test case name +// Test name +// Whether the test should be run +// A function pointer that creates the test object when invoked +// Test result +// +// The constructor of TestInfo registers itself with the UnitTest +// singleton such that the RUN_ALL_TESTS() macro knows which tests to +// run. +class TestInfo { + public: + // Destructs a TestInfo object. This function is not virtual, so + // don't inherit from TestInfo. + ~TestInfo(); + + // Returns the test case name. + const char* test_case_name() const; + + // Returns the test name. + const char* name() const; + + // Returns the test case comment. + const char* test_case_comment() const; + + // Returns the test comment. + const char* comment() const; + + // Returns true if this test should run. + // + // Google Test allows the user to filter the tests by their full names. + // The full name of a test Bar in test case Foo is defined as + // "Foo.Bar". Only the tests that match the filter will run. + // + // A filter is a colon-separated list of glob (not regex) patterns, + // optionally followed by a '-' and a colon-separated list of + // negative patterns (tests to exclude). A test is run if it + // matches one of the positive patterns and does not match any of + // the negative patterns. + // + // For example, *A*:Foo.* is a filter that matches any string that + // contains the character 'A' or starts with "Foo.". + bool should_run() const; + + // Returns the result of the test. + const internal::TestResult* result() const; + private: +#if GTEST_HAS_DEATH_TEST + friend class internal::DefaultDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + friend class internal::TestInfoImpl; + friend class internal::UnitTestImpl; + friend class Test; + friend class TestCase; + friend TestInfo* internal::MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* test_case_comment, const char* comment, + internal::TypeId fixture_class_id, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + internal::TestFactoryBase* factory); + + // Increments the number of death tests encountered in this test so + // far. + int increment_death_test_count(); + + // Accessors for the implementation object. + internal::TestInfoImpl* impl() { return impl_; } + const internal::TestInfoImpl* impl() const { return impl_; } + + // Constructs a TestInfo object. The newly constructed instance assumes + // ownership of the factory object. + TestInfo(const char* test_case_name, const char* name, + const char* test_case_comment, const char* comment, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory); + + // An opaque implementation object. + internal::TestInfoImpl* impl_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo); +}; + +// An Environment object is capable of setting up and tearing down an +// environment. The user should subclass this to define his own +// environment(s). +// +// An Environment object does the set-up and tear-down in virtual +// methods SetUp() and TearDown() instead of the constructor and the +// destructor, as: +// +// 1. You cannot safely throw from a destructor. This is a problem +// as in some cases Google Test is used where exceptions are enabled, and +// we may want to implement ASSERT_* using exceptions where they are +// available. +// 2. You cannot use ASSERT_* directly in a constructor or +// destructor. +class Environment { + public: + // The d'tor is virtual as we need to subclass Environment. + virtual ~Environment() {} + + // Override this to define how to set up the environment. + virtual void SetUp() {} + + // Override this to define how to tear down the environment. + virtual void TearDown() {} + private: + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } +}; + +// A UnitTest consists of a list of TestCases. +// +// This is a singleton class. The only instance of UnitTest is +// created when UnitTest::GetInstance() is first called. This +// instance is never deleted. +// +// UnitTest is not copyable. +// +// This class is thread-safe as long as the methods are called +// according to their specification. +class UnitTest { + public: + // Gets the singleton UnitTest object. The first time this method + // is called, a UnitTest object is constructed and returned. + // Consecutive calls will return the same object. + static UnitTest* GetInstance(); + + // Registers and returns a global test environment. When a test + // program is run, all global test environments will be set-up in + // the order they were registered. After all tests in the program + // have finished, all global test environments will be torn-down in + // the *reverse* order they were registered. + // + // The UnitTest object takes ownership of the given environment. + // + // This method can only be called from the main thread. + Environment* AddEnvironment(Environment* env); + + // Adds a TestPartResult to the current TestResult object. All + // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) + // eventually call this to report their results. The user code + // should use the assertion macros instead of calling this directly. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + void AddTestPartResult(TestPartResultType result_type, + const char* file_name, + int line_number, + const internal::String& message, + const internal::String& os_stack_trace); + + // Adds a TestProperty to the current TestResult object. If the result already + // contains a property with the same key, the value will be updated. + void RecordPropertyForCurrentTest(const char* key, const char* value); + + // Runs all tests in this UnitTest object and prints the result. + // Returns 0 if successful, or 1 otherwise. + // + // This method can only be called from the main thread. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + int Run() GTEST_MUST_USE_RESULT_; + + // Returns the working directory when the first TEST() or TEST_F() + // was executed. The UnitTest object owns the string. + const char* original_working_dir() const; + + // Returns the TestCase object for the test that's currently running, + // or NULL if no test is running. + const TestCase* current_test_case() const; + + // Returns the TestInfo object for the test that's currently running, + // or NULL if no test is running. + const TestInfo* current_test_info() const; + +#if GTEST_HAS_PARAM_TEST + // Returns the ParameterizedTestCaseRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + internal::ParameterizedTestCaseRegistry& parameterized_test_registry(); +#endif // GTEST_HAS_PARAM_TEST + + // Accessors for the implementation object. + internal::UnitTestImpl* impl() { return impl_; } + const internal::UnitTestImpl* impl() const { return impl_; } + private: + // ScopedTrace is a friend as it needs to modify the per-thread + // trace stack, which is a private member of UnitTest. + friend class internal::ScopedTrace; + + // Creates an empty UnitTest. + UnitTest(); + + // D'tor + virtual ~UnitTest(); + + // Pushes a trace defined by SCOPED_TRACE() on to the per-thread + // Google Test trace stack. + void PushGTestTrace(const internal::TraceInfo& trace); + + // Pops a trace from the per-thread Google Test trace stack. + void PopGTestTrace(); + + // Protects mutable state in *impl_. This is mutable as some const + // methods need to lock it too. + mutable internal::Mutex mutex_; + + // Opaque implementation object. This field is never changed once + // the object is constructed. We don't mark it as const here, as + // doing so will cause a warning in the constructor of UnitTest. + // Mutable state in *impl_ is protected by mutex_. + internal::UnitTestImpl* impl_; + + // We disallow copying UnitTest. + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest); +}; + +// A convenient wrapper for adding an environment for the test +// program. +// +// You should call this before RUN_ALL_TESTS() is called, probably in +// main(). If you use gtest_main, you need to call this before main() +// starts for it to take effect. For example, you can define a global +// variable like this: +// +// testing::Environment* const foo_env = +// testing::AddGlobalTestEnvironment(new FooEnvironment); +// +// However, we strongly recommend you to write your own main() and +// call AddGlobalTestEnvironment() there, as relying on initialization +// of global variables makes the code harder to read and may cause +// problems when you register multiple environments from different +// translation units and the environments have dependencies among them +// (remember that the compiler doesn't guarantee the order in which +// global variables from different translation units are initialized). +inline Environment* AddGlobalTestEnvironment(Environment* env) { + return UnitTest::GetInstance()->AddEnvironment(env); +} + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +void InitGoogleTest(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleTest(int* argc, wchar_t** argv); + +namespace internal { + +// These overloaded versions handle ::std::string and ::std::wstring. +#if GTEST_HAS_STD_STRING +inline String FormatForFailureMessage(const ::std::string& str) { + return (Message() << '"' << str << '"').GetString(); +} +#endif // GTEST_HAS_STD_STRING + +#if GTEST_HAS_STD_WSTRING +inline String FormatForFailureMessage(const ::std::wstring& wstr) { + return (Message() << "L\"" << wstr << '"').GetString(); +} +#endif // GTEST_HAS_STD_WSTRING + +// These overloaded versions handle ::string and ::wstring. +#if GTEST_HAS_GLOBAL_STRING +inline String FormatForFailureMessage(const ::string& str) { + return (Message() << '"' << str << '"').GetString(); +} +#endif // GTEST_HAS_GLOBAL_STRING + +#if GTEST_HAS_GLOBAL_WSTRING +inline String FormatForFailureMessage(const ::wstring& wstr) { + return (Message() << "L\"" << wstr << '"').GetString(); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) +// operand to be used in a failure message. The type (but not value) +// of the other operand may affect the format. This allows us to +// print a char* as a raw pointer when it is compared against another +// char*, and print it as a C string when it is compared against an +// std::string object, for example. +// +// The default implementation ignores the type of the other operand. +// Some specialized versions are used to handle formatting wide or +// narrow C strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +String FormatForComparisonFailureMessage(const T1& value, + const T2& /* other_operand */) { + return FormatForFailureMessage(value); +} + +// The helper function for {ASSERT|EXPECT}_EQ. +template +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { +#ifdef _MSC_VER +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4389) // Temporarily disables warning on + // signed/unsigned mismatch. +#endif + + if (expected == actual) { + return AssertionSuccess(); + } + +#ifdef _MSC_VER +#pragma warning(pop) // Restores the warning state. +#endif + + return EqFailure(expected_expression, + actual_expression, + FormatForComparisonFailureMessage(expected, actual), + FormatForComparisonFailureMessage(actual, expected), + false); +} + +// With this overloaded version, we allow anonymous enums to be used +// in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous enums +// can be implicitly cast to BiggestInt. +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual); + +// The helper class for {ASSERT|EXPECT}_EQ. The template argument +// lhs_is_null_literal is true iff the first argument to ASSERT_EQ() +// is a null pointer literal. The following default implementation is +// for lhs_is_null_literal being false. +template +class EqHelper { + public: + // This templatized version is for the general case. + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // With this overloaded version, we allow anonymous enums to be used + // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous + // enums can be implicitly cast to BiggestInt. + // + // Even though its body looks the same as the above version, we + // cannot merge the two, as it will make anonymous enums unhappy. + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } +}; + +// This specialization is used when the first argument to ASSERT_EQ() +// is a null pointer literal. +template <> +class EqHelper { + public: + // We define two overloaded versions of Compare(). The first + // version will be picked when the second argument to ASSERT_EQ() is + // NOT a pointer, e.g. ASSERT_EQ(0, AnIntFunction()) or + // EXPECT_EQ(false, a_bool). + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // This version will be picked when the second argument to + // ASSERT_EQ() is a pointer, e.g. ASSERT_EQ(NULL, a_pointer). + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& /* expected */, + T2* actual) { + // We already know that 'expected' is a null pointer. + return CmpHelperEQ(expected_expression, actual_expression, + static_cast(NULL), actual); + } +}; + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste +// of similar code. +// +// For each templatized helper function, we also define an overloaded +// version for BiggestInt in order to reduce code bloat and allow +// anonymous enums to be used with {ASSERT|EXPECT}_?? when compiled +// with gcc 4. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +template \ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + const T1& val1, const T2& val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + Message msg;\ + msg << "Expected: (" << expr1 << ") " #op " (" << expr2\ + << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ + << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + return AssertionFailure(msg);\ + }\ +}\ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + BiggestInt val1, BiggestInt val2); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// Implements the helper function for {ASSERT|EXPECT}_NE +GTEST_IMPL_CMP_HELPER_(NE, !=) +// Implements the helper function for {ASSERT|EXPECT}_LE +GTEST_IMPL_CMP_HELPER_(LE, <=) +// Implements the helper function for {ASSERT|EXPECT}_LT +GTEST_IMPL_CMP_HELPER_(LT, < ) +// Implements the helper function for {ASSERT|EXPECT}_GE +GTEST_IMPL_CMP_HELPER_(GE, >=) +// Implements the helper function for {ASSERT|EXPECT}_GT +GTEST_IMPL_CMP_HELPER_(GT, > ) + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRNE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + + +// Helper function for *_STREQ on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const wchar_t* expected, + const wchar_t* actual); + +// Helper function for *_STRNE on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2); + +} // namespace internal + +// IsSubstring() and IsNotSubstring() are intended to be used as the +// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by +// themselves. They check whether needle is a substring of haystack +// (NULL is considered a substring of itself only), and return an +// appropriate error message when they fail. +// +// The {needle,haystack}_expr arguments are the stringified +// expressions that generated the two real arguments. +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +#if GTEST_HAS_STD_STRING +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); +#endif // GTEST_HAS_STD_STRING + +#if GTEST_HAS_STD_WSTRING +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +// Helper template function for comparing floating-points. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +AssertionResult CmpHelperFloatingPointEQ(const char* expected_expression, + const char* actual_expression, + RawType expected, + RawType actual) { + const FloatingPoint lhs(expected), rhs(actual); + + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + StrStream expected_ss; + expected_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << expected; + + StrStream actual_ss; + actual_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << actual; + + return EqFailure(expected_expression, + actual_expression, + StrStreamToString(&expected_ss), + StrStreamToString(&actual_ss), + false); +} + +// Helper function for implementing ASSERT_NEAR. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// A class that enables one to stream messages to assertion macros +class AssertHelper { + public: + // Constructor. + AssertHelper(TestPartResultType type, const char* file, int line, + const char* message); + // Message assignment is a semantic trick to enable assertion + // streaming; see the GTEST_MESSAGE_ macro below. + void operator=(const Message& message) const; + private: + TestPartResultType const type_; + const char* const file_; + int const line_; + String const message_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper); +}; + +} // namespace internal + +#if GTEST_HAS_PARAM_TEST +// The abstract base class that all value-parameterized tests inherit from. +// +// This class adds support for accessing the test parameter value via +// the GetParam() method. +// +// Use it with one of the parameter generator defining functions, like Range(), +// Values(), ValuesIn(), Bool(), and Combine(). +// +// class FooTest : public ::testing::TestWithParam { +// protected: +// FooTest() { +// // Can use GetParam() here. +// } +// virtual ~FooTest() { +// // Can use GetParam() here. +// } +// virtual void SetUp() { +// // Can use GetParam() here. +// } +// virtual void TearDown { +// // Can use GetParam() here. +// } +// }; +// TEST_P(FooTest, DoesBar) { +// // Can use GetParam() method here. +// Foo foo; +// ASSERT_TRUE(foo.DoesBar(GetParam())); +// } +// INSTANTIATE_TEST_CASE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); + +template +class TestWithParam : public Test { + public: + typedef T ParamType; + + // The current parameter value. Is also available in the test fixture's + // constructor. + const ParamType& GetParam() const { return *parameter_; } + + private: + // Sets parameter value. The caller is responsible for making sure the value + // remains alive and unchanged throughout the current test. + static void SetParam(const ParamType* parameter) { + parameter_ = parameter; + } + + // Static value used for accessing parameter during a test lifetime. + static const ParamType* parameter_; + + // TestClass must be a subclass of TestWithParam. + template friend class internal::ParameterizedTestFactory; +}; + +template +const T* TestWithParam::parameter_ = NULL; + +#endif // GTEST_HAS_PARAM_TEST + +// Macros for indicating success/failure in test code. + +// ADD_FAILURE unconditionally adds a failure to the current test. +// SUCCEED generates a success - it doesn't automatically make the +// current test successful, as a test is only successful when it has +// no failure. +// +// EXPECT_* verifies that a certain condition is satisfied. If not, +// it behaves like ADD_FAILURE. In particular: +// +// EXPECT_TRUE verifies that a Boolean condition is true. +// EXPECT_FALSE verifies that a Boolean condition is false. +// +// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except +// that they will also abort the current function on failure. People +// usually want the fail-fast behavior of FAIL and ASSERT_*, but those +// writing data-driven tests often find themselves using ADD_FAILURE +// and EXPECT_* more. +// +// Examples: +// +// EXPECT_TRUE(server.StatusIsOK()); +// ASSERT_FALSE(server.HasPendingRequest(port)) +// << "There are still pending requests " << "on port " << port; + +// Generates a nonfatal failure with a generic message. +#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") + +// Generates a fatal failure with a generic message. +#define FAIL() GTEST_FATAL_FAILURE_("Failed") + +// Generates a success with a generic message. +#define SUCCEED() GTEST_SUCCESS_("Succeeded") + +// Macros for testing exceptions. +// +// * {ASSERT|EXPECT}_THROW(statement, expected_exception): +// Tests that the statement throws the expected exception. +// * {ASSERT|EXPECT}_NO_THROW(statement): +// Tests that the statement doesn't throw any exception. +// * {ASSERT|EXPECT}_ANY_THROW(statement): +// Tests that the statement throws an exception. + +#define EXPECT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_) +#define EXPECT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define EXPECT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_) +#define ASSERT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_) +#define ASSERT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_) + +// Boolean assertions. +#define EXPECT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_NONFATAL_FAILURE_) +#define EXPECT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_NONFATAL_FAILURE_) +#define ASSERT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_FATAL_FAILURE_) +#define ASSERT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_FATAL_FAILURE_) + +// Includes the auto-generated header that implements a family of +// generic predicate assertion macros. +#include + +// Macros for testing equalities and inequalities. +// +// * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual +// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 +// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 +// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 +// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 +// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 +// +// When they are not, Google Test prints both the tested expressions and +// their actual values. The values must be compatible built-in types, +// or you will get a compiler error. By "compatible" we mean that the +// values can be compared by the respective operator. +// +// Note: +// +// 1. It is possible to make a user-defined type work with +// {ASSERT|EXPECT}_??(), but that requires overloading the +// comparison operators and is thus discouraged by the Google C++ +// Usage Guide. Therefore, you are advised to use the +// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are +// equal. +// +// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on +// pointers (in particular, C strings). Therefore, if you use it +// with two C strings, you are testing how their locations in memory +// are related, not how their content is related. To compare two C +// strings by content, use {ASSERT|EXPECT}_STR*(). +// +// 3. {ASSERT|EXPECT}_EQ(expected, actual) is preferred to +// {ASSERT|EXPECT}_TRUE(expected == actual), as the former tells you +// what the actual value is when it fails, and similarly for the +// other comparisons. +// +// 4. Do not depend on the order in which {ASSERT|EXPECT}_??() +// evaluate their arguments, which is undefined. +// +// 5. These macros evaluate their arguments exactly once. +// +// Examples: +// +// EXPECT_NE(5, Foo()); +// EXPECT_EQ(NULL, a_pointer); +// ASSERT_LT(i, array_size); +// ASSERT_GT(records.size(), 0) << "There is no record left."; + +#define EXPECT_EQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define EXPECT_NE(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, expected, actual) +#define EXPECT_LE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define EXPECT_LT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define EXPECT_GE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define EXPECT_GT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +#define ASSERT_EQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define ASSERT_NE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define ASSERT_LE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define ASSERT_LT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define ASSERT_GE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define ASSERT_GT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +// C String Comparisons. All tests treat NULL and any non-NULL string +// as different. Two NULLs are equal. +// +// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2 +// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2 +// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case +// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case +// +// For wide or narrow string objects, you can use the +// {ASSERT|EXPECT}_??() macros. +// +// Don't depend on the order in which the arguments are evaluated, +// which is undefined. +// +// These macros evaluate their arguments exactly once. + +#define EXPECT_STREQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define EXPECT_STRNE(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define EXPECT_STRCASEEQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define EXPECT_STRCASENE(s1, s2)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +#define ASSERT_STREQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define ASSERT_STRNE(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define ASSERT_STRCASEEQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define ASSERT_STRCASENE(s1, s2)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +// Macros for comparing floating-point numbers. +// +// * {ASSERT|EXPECT}_FLOAT_EQ(expected, actual): +// Tests that two float values are almost equal. +// * {ASSERT|EXPECT}_DOUBLE_EQ(expected, actual): +// Tests that two double values are almost equal. +// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): +// Tests that v1 and v2 are within the given distance to each other. +// +// Google Test uses ULP-based comparison to automatically pick a default +// error bound that is appropriate for the operands. See the +// FloatingPoint template class in gtest-internal.h if you are +// interested in the implementation details. + +#define EXPECT_FLOAT_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_DOUBLE_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_FLOAT_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_DOUBLE_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_NEAR(val1, val2, abs_error)\ + EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +#define ASSERT_NEAR(val1, val2, abs_error)\ + ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +// These predicate format functions work on floating-point values, and +// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g. +// +// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0); + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2); +AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2); + + +#if GTEST_OS_WINDOWS + +// Macros that test for HRESULT failure and success, these are only useful +// on Windows, and rely on Windows SDK macros and APIs to compile. +// +// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr) +// +// When expr unexpectedly fails or succeeds, Google Test prints the +// expected result and the actual result with both a human-readable +// string representation of the error, if available, as well as the +// hex result code. +#define EXPECT_HRESULT_SUCCEEDED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +#define ASSERT_HRESULT_SUCCEEDED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +#define EXPECT_HRESULT_FAILED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#define ASSERT_HRESULT_FAILED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#endif // GTEST_OS_WINDOWS + +// Macros that execute statement and check that it doesn't generate new fatal +// failures in the current thread. +// +// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement); +// +// Examples: +// +// EXPECT_NO_FATAL_FAILURE(Process()); +// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed"; +// +#define ASSERT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_) +#define EXPECT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_) + +// Causes a trace (including the source file path, the current line +// number, and the given message) to be included in every test failure +// message generated by code in the current scope. The effect is +// undone when the control leaves the current scope. +// +// The message argument can be anything streamable to std::ostream. +// +// In the implementation, we include the current line number as part +// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s +// to appear in the same block - as long as they are on different +// lines. +#define SCOPED_TRACE(message) \ + ::testing::internal::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\ + __FILE__, __LINE__, ::testing::Message() << (message)) + +namespace internal { + +// This template is declared, but intentionally undefined. +template +struct StaticAssertTypeEqHelper; + +template +struct StaticAssertTypeEqHelper {}; + +} // namespace internal + +// Compile-time assertion for type equality. +// StaticAssertTypeEq() compiles iff type1 and type2 are +// the same type. The value it returns is not interesting. +// +// Instead of making StaticAssertTypeEq a class template, we make it a +// function template that invokes a helper class template. This +// prevents a user from misusing StaticAssertTypeEq by +// defining objects of that type. +// +// CAVEAT: +// +// When used inside a method of a class template, +// StaticAssertTypeEq() is effective ONLY IF the method is +// instantiated. For example, given: +// +// template class Foo { +// public: +// void Bar() { testing::StaticAssertTypeEq(); } +// }; +// +// the code: +// +// void Test1() { Foo foo; } +// +// will NOT generate a compiler error, as Foo::Bar() is never +// actually instantiated. Instead, you need: +// +// void Test2() { Foo foo; foo.Bar(); } +// +// to cause a compiler error. +template +bool StaticAssertTypeEq() { + internal::StaticAssertTypeEqHelper(); + return true; +} + +// Defines a test. +// +// The first parameter is the name of the test case, and the second +// parameter is the name of the test within the test case. +// +// The convention is to end the test case name with "Test". For +// example, a test case for the Foo class can be named FooTest. +// +// The user should put his test code between braces after using this +// macro. Example: +// +// TEST(FooTest, InitializesCorrectly) { +// Foo foo; +// EXPECT_TRUE(foo.StatusIsOK()); +// } + +// Note that we call GetTestTypeId() instead of GetTypeId< +// ::testing::Test>() here to get the type ID of testing::Test. This +// is to work around a suspected linker bug when using Google Test as +// a framework on Mac OS X. The bug causes GetTypeId< +// ::testing::Test>() to return different values depending on whether +// the call is from the Google Test framework itself or from user test +// code. GetTestTypeId() is guaranteed to always return the same +// value, as it always calls GetTypeId<>() from the Google Test +// framework. +#define TEST(test_case_name, test_name)\ + GTEST_TEST_(test_case_name, test_name, \ + ::testing::Test, ::testing::internal::GetTestTypeId()) + + +// Defines a test that uses a test fixture. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test case name. The second parameter is the +// name of the test within the test case. +// +// A test fixture class must be declared earlier. The user should put +// his test code between braces after using this macro. Example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { b_.AddElement(3); } +// +// Foo a_; +// Foo b_; +// }; +// +// TEST_F(FooTest, InitializesCorrectly) { +// EXPECT_TRUE(a_.StatusIsOK()); +// } +// +// TEST_F(FooTest, ReturnsElementCountCorrectly) { +// EXPECT_EQ(0, a_.size()); +// EXPECT_EQ(1, b_.size()); +// } + +#define TEST_F(test_fixture, test_name)\ + GTEST_TEST_(test_fixture, test_name, test_fixture, \ + ::testing::internal::GetTypeId()) + +// Use this macro in main() to run all tests. It returns 0 if all +// tests are successful, or 1 otherwise. +// +// RUN_ALL_TESTS() should be invoked after the command line has been +// parsed by InitGoogleTest(). + +#define RUN_ALL_TESTS()\ + (::testing::UnitTest::GetInstance()->Run()) + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ diff --git a/digsby/ext/lib/include/gtest/gtest_pred_impl.h b/digsby/ext/lib/include/gtest/gtest_pred_impl.h new file mode 100644 index 0000000..e1e2f8c --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest_pred_impl.h @@ -0,0 +1,368 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file is AUTOMATICALLY GENERATED on 10/02/2008 by command +// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND! +// +// Implements a family of generic predicate assertion macros. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + +// Makes sure this header is not included before gtest.h. +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +#error Do not include gtest_pred_impl.h directly. Include gtest.h instead. +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ + +// This header implements a family of generic predicate assertion +// macros: +// +// ASSERT_PRED_FORMAT1(pred_format, v1) +// ASSERT_PRED_FORMAT2(pred_format, v1, v2) +// ... +// +// where pred_format is a function or functor that takes n (in the +// case of ASSERT_PRED_FORMATn) values and their source expression +// text, and returns a testing::AssertionResult. See the definition +// of ASSERT_EQ in gtest.h for an example. +// +// If you don't care about formatting, you can use the more +// restrictive version: +// +// ASSERT_PRED1(pred, v1) +// ASSERT_PRED2(pred, v1, v2) +// ... +// +// where pred is an n-ary function or functor that returns bool, +// and the values v1, v2, ..., must support the << operator for +// streaming to std::ostream. +// +// We also define the EXPECT_* variations. +// +// For now we only support predicates whose arity is at most 5. +// Please email googletestframework@googlegroups.com if you need +// support for higher arities. + +// GTEST_ASSERT_ is the basic statement to which all of the assertions +// in this file reduce. Don't use this in your code. + +#define GTEST_ASSERT_(expression, on_failure) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar = (expression)) \ + ; \ + else \ + on_failure(gtest_ar.failure_message()) + + +// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +template +AssertionResult AssertPred1Helper(const char* pred_text, + const char* e1, + Pred pred, + const T1& v1) { + if (pred(v1)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. +// Don't use this in your code. +#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, v1),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +#define GTEST_PRED1_(pred, v1, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \ + #v1, \ + pred, \ + v1), on_failure) + +// Unary predicate assertion macros. +#define EXPECT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +template +AssertionResult AssertPred2Helper(const char* pred_text, + const char* e1, + const char* e2, + Pred pred, + const T1& v1, + const T2& v2) { + if (pred(v1, v2)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. +// Don't use this in your code. +#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +#define GTEST_PRED2_(pred, v1, v2, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \ + #v1, \ + #v2, \ + pred, \ + v1, \ + v2), on_failure) + +// Binary predicate assertion macros. +#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +template +AssertionResult AssertPred3Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3) { + if (pred(v1, v2, v3)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. +// Don't use this in your code. +#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + pred, \ + v1, \ + v2, \ + v3), on_failure) + +// Ternary predicate assertion macros. +#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +template +AssertionResult AssertPred4Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4) { + if (pred(v1, v2, v3, v4)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. +// Don't use this in your code. +#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4), on_failure) + +// 4-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +template +AssertionResult AssertPred5Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + const char* e5, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4, + const T5& v5) { + if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); + + Message msg; + msg << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ", " + << e5 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4 + << "\n" << e5 << " evaluates to " << v5; + return AssertionFailure(msg); +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. +// Don't use this in your code. +#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + #v5, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4, \ + v5), on_failure) + +// 5-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) + + + +#endif // GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ diff --git a/digsby/ext/lib/include/gtest/gtest_prod.h b/digsby/ext/lib/include/gtest/gtest_prod.h new file mode 100644 index 0000000..da80ddc --- /dev/null +++ b/digsby/ext/lib/include/gtest/gtest_prod.h @@ -0,0 +1,58 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Google C++ Testing Framework definitions useful in production code. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PROD_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PROD_H_ + +// When you need to test the private or protected members of a class, +// use the FRIEND_TEST macro to declare your tests as friends of the +// class. For example: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST(MyClassTest, MyMethod); +// }; +// +// class MyClassTest : public testing::Test { +// // ... +// }; +// +// TEST_F(MyClassTest, MyMethod) { +// // Can call MyClass::MyMethod() here. +// } + +#define FRIEND_TEST(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +#endif // GTEST_INCLUDE_GTEST_GTEST_PROD_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-death-test-internal.h b/digsby/ext/lib/include/gtest/internal/gtest-death-test-internal.h new file mode 100644 index 0000000..ff2e490 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-death-test-internal.h @@ -0,0 +1,239 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines internal utilities needed for implementing +// death tests. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + +#include + +#if GTEST_HAS_DEATH_TEST && GTEST_OS_WINDOWS +#include +#endif // GTEST_HAS_DEATH_TEST && GTEST_OS_WINDOWS + +namespace testing { +namespace internal { + +GTEST_DECLARE_string_(internal_run_death_test); + +// Names of the flags (needed for parsing Google Test flags). +const char kDeathTestStyleFlag[] = "death_test_style"; +const char kDeathTestUseFork[] = "death_test_use_fork"; +const char kInternalRunDeathTestFlag[] = "internal_run_death_test"; + +#if GTEST_HAS_DEATH_TEST + +// DeathTest is a class that hides much of the complexity of the +// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method +// returns a concrete class that depends on the prevailing death test +// style, as defined by the --gtest_death_test_style and/or +// --gtest_internal_run_death_test flags. + +// In describing the results of death tests, these terms are used with +// the corresponding definitions: +// +// exit status: The integer exit information in the format specified +// by wait(2) +// exit code: The integer code passed to exit(3), _exit(2), or +// returned from main() +class DeathTest { + public: + // Create returns false if there was an error determining the + // appropriate action to take for the current death test; for example, + // if the gtest_death_test_style flag is set to an invalid value. + // The LastMessage method will return a more detailed message in that + // case. Otherwise, the DeathTest pointer pointed to by the "test" + // argument is set. If the death test should be skipped, the pointer + // is set to NULL; otherwise, it is set to the address of a new concrete + // DeathTest object that controls the execution of the current test. + static bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); + DeathTest(); + virtual ~DeathTest() { } + + // A helper class that aborts a death test when it's deleted. + class ReturnSentinel { + public: + explicit ReturnSentinel(DeathTest* test) : test_(test) { } + ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); } + private: + DeathTest* const test_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel); + } GTEST_ATTRIBUTE_UNUSED_; + + // An enumeration of possible roles that may be taken when a death + // test is encountered. EXECUTE means that the death test logic should + // be executed immediately. OVERSEE means that the program should prepare + // the appropriate environment for a child process to execute the death + // test, then wait for it to complete. + enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; + + // An enumeration of the two reasons that a test might be aborted. + enum AbortReason { TEST_ENCOUNTERED_RETURN_STATEMENT, TEST_DID_NOT_DIE }; + + // Assumes one of the above roles. + virtual TestRole AssumeRole() = 0; + + // Waits for the death test to finish and returns its status. + virtual int Wait() = 0; + + // Returns true if the death test passed; that is, the test process + // exited during the test, its exit status matches a user-supplied + // predicate, and its stderr output matches a user-supplied regular + // expression. + // The user-supplied predicate may be a macro expression rather + // than a function pointer or functor, or else Wait and Passed could + // be combined. + virtual bool Passed(bool exit_status_ok) = 0; + + // Signals that the death test did not die as expected. + virtual void Abort(AbortReason reason) = 0; + + // Returns a human-readable outcome message regarding the outcome of + // the last death test. + static const char* LastMessage(); + + static void set_last_death_test_message(const String& message); + + private: + // A string containing a description of the outcome of the last death test. + static String last_death_test_message_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest); +}; + +// Factory interface for death tests. May be mocked out for testing. +class DeathTestFactory { + public: + virtual ~DeathTestFactory() { } + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test) = 0; +}; + +// A concrete DeathTestFactory implementation for normal use. +class DefaultDeathTestFactory : public DeathTestFactory { + public: + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); +}; + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +bool ExitedUnsuccessfully(int exit_status); + +// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, +// ASSERT_EXIT*, and EXPECT_EXIT*. +#define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (true) { \ + const ::testing::internal::RE& gtest_regex = (regex); \ + ::testing::internal::DeathTest* gtest_dt; \ + if (!::testing::internal::DeathTest::Create(#statement, >est_regex, \ + __FILE__, __LINE__, >est_dt)) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + if (gtest_dt != NULL) { \ + ::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \ + gtest_dt_ptr(gtest_dt); \ + switch (gtest_dt->AssumeRole()) { \ + case ::testing::internal::DeathTest::OVERSEE_TEST: \ + if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + break; \ + case ::testing::internal::DeathTest::EXECUTE_TEST: { \ + ::testing::internal::DeathTest::ReturnSentinel \ + gtest_sentinel(gtest_dt); \ + GTEST_HIDE_UNREACHABLE_CODE_(statement); \ + gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ + break; \ + } \ + } \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__): \ + fail(::testing::internal::DeathTest::LastMessage()) +// The symbol "fail" here expands to something into which a message +// can be streamed. + +// A class representing the parsed contents of the +// --gtest_internal_run_death_test flag, as it existed when +// RUN_ALL_TESTS was called. +class InternalRunDeathTestFlag { + public: + InternalRunDeathTestFlag(const String& file, + int line, + int index, + int status_fd) + : file_(file), line_(line), index_(index), status_fd_(status_fd) {} + + ~InternalRunDeathTestFlag() { + if (status_fd_ >= 0) +// Suppress MSVC complaints about POSIX functions. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4996) +#endif // _MSC_VER + close(status_fd_); +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + } + + String file() const { return file_; } + int line() const { return line_; } + int index() const { return index_; } + int status_fd() const { return status_fd_; } + + private: + String file_; + int line_; + int index_; + int status_fd_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag); +}; + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-filepath.h b/digsby/ext/lib/include/gtest/internal/gtest-filepath.h new file mode 100644 index 0000000..1b2f586 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-filepath.h @@ -0,0 +1,201 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: keith.ray@gmail.com (Keith Ray) +// +// Google Test filepath utilities +// +// This header file declares classes and functions used internally by +// Google Test. They are subject to change without notice. +// +// This file is #included in . +// Do not include this header file separately! + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ + +#include + +namespace testing { +namespace internal { + +// FilePath - a class for file and directory pathname manipulation which +// handles platform-specific conventions (like the pathname separator). +// Used for helper functions for naming files in a directory for xml output. +// Except for Set methods, all methods are const or static, which provides an +// "immutable value object" -- useful for peace of mind. +// A FilePath with a value ending in a path separator ("like/this/") represents +// a directory, otherwise it is assumed to represent a file. In either case, +// it may or may not represent an actual file or directory in the file system. +// Names are NOT checked for syntax correctness -- no checking for illegal +// characters, malformed paths, etc. + +class FilePath { + public: + FilePath() : pathname_("") { } + FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { } + + explicit FilePath(const char* pathname) : pathname_(pathname) { + Normalize(); + } + + explicit FilePath(const String& pathname) : pathname_(pathname) { + Normalize(); + } + + FilePath& operator=(const FilePath& rhs) { + Set(rhs); + return *this; + } + + void Set(const FilePath& rhs) { + pathname_ = rhs.pathname_; + } + + String ToString() const { return pathname_; } + const char* c_str() const { return pathname_.c_str(); } + + // Returns the current working directory, or "" if unsuccessful. + static FilePath GetCurrentDir(); + + // Given directory = "dir", base_name = "test", number = 0, + // extension = "xml", returns "dir/test.xml". If number is greater + // than zero (e.g., 12), returns "dir/test_12.xml". + // On Windows platform, uses \ as the separator rather than /. + static FilePath MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension); + + // Given directory = "dir", relative_path = "test.xml", + // returns "dir/test.xml". + // On Windows, uses \ as the separator rather than /. + static FilePath ConcatPaths(const FilePath& directory, + const FilePath& relative_path); + + // Returns a pathname for a file that does not currently exist. The pathname + // will be directory/base_name.extension or + // directory/base_name_.extension if directory/base_name.extension + // already exists. The number will be incremented until a pathname is found + // that does not already exist. + // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. + // There could be a race condition if two or more processes are calling this + // function at the same time -- they could both pick the same filename. + static FilePath GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension); + + // Returns true iff the path is NULL or "". + bool IsEmpty() const { return c_str() == NULL || *c_str() == '\0'; } + + // If input name has a trailing separator character, removes it and returns + // the name, otherwise return the name string unmodified. + // On Windows platform, uses \ as the separator, other platforms use /. + FilePath RemoveTrailingPathSeparator() const; + + // Returns a copy of the FilePath with the directory part removed. + // Example: FilePath("path/to/file").RemoveDirectoryName() returns + // FilePath("file"). If there is no directory part ("just_a_file"), it returns + // the FilePath unmodified. If there is no file part ("just_a_dir/") it + // returns an empty FilePath (""). + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveDirectoryName() const; + + // RemoveFileName returns the directory path with the filename removed. + // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". + // If the FilePath is "a_file" or "/a_file", RemoveFileName returns + // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does + // not have a file, like "just/a/dir/", it returns the FilePath unmodified. + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveFileName() const; + + // Returns a copy of the FilePath with the case-insensitive extension removed. + // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns + // FilePath("dir/file"). If a case-insensitive extension is not + // found, returns a copy of the original FilePath. + FilePath RemoveExtension(const char* extension) const; + + // Creates directories so that path exists. Returns true if successful or if + // the directories already exist; returns false if unable to create + // directories for any reason. Will also return false if the FilePath does + // not represent a directory (that is, it doesn't end with a path separator). + bool CreateDirectoriesRecursively() const; + + // Create the directory so that path exists. Returns true if successful or + // if the directory already exists; returns false if unable to create the + // directory for any reason, including if the parent directory does not + // exist. Not named "CreateDirectory" because that's a macro on Windows. + bool CreateFolder() const; + + // Returns true if FilePath describes something in the file-system, + // either a file, directory, or whatever, and that something exists. + bool FileOrDirectoryExists() const; + + // Returns true if pathname describes a directory in the file-system + // that exists. + bool DirectoryExists() const; + + // Returns true if FilePath ends with a path separator, which indicates that + // it is intended to represent a directory. Returns false otherwise. + // This does NOT check that a directory (or file) actually exists. + bool IsDirectory() const; + + // Returns true if pathname describes a root directory. (Windows has one + // root directory per disk drive.) + bool IsRootDirectory() const; + + // Returns true if pathname describes an absolute path. + bool IsAbsolutePath() const; + + private: + // Replaces multiple consecutive separators with a single separator. + // For example, "bar///foo" becomes "bar/foo". Does not eliminate other + // redundancies that might be in a pathname involving "." or "..". + // + // A pathname with multiple consecutive separators may occur either through + // user error or as a result of some scripts or APIs that generate a pathname + // with a trailing separator. On other platforms the same API or script + // may NOT generate a pathname with a trailing "/". Then elsewhere that + // pathname may have another "/" and pathname components added to it, + // without checking for the separator already being there. + // The script language and operating system may allow paths like "foo//bar" + // but some of the functions in FilePath will not handle that correctly. In + // particular, RemoveTrailingPathSeparator() only removes one separator, and + // it is called in CreateDirectoriesRecursively() assuming that it will change + // a pathname from directory syntax (trailing separator) to filename syntax. + + void Normalize(); + + String pathname_; +}; // class FilePath + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-internal.h b/digsby/ext/lib/include/gtest/internal/gtest-internal.h new file mode 100644 index 0000000..f61d502 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-internal.h @@ -0,0 +1,886 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares functions and macros used internally by +// Google Test. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ + +#include + +#if GTEST_OS_LINUX +#include +#include +#include +#include +#endif // GTEST_OS_LINUX + +#include +#include +#include +#include +#include + +#include +#include +#include + +// Due to C++ preprocessor weirdness, we need double indirection to +// concatenate two tokens when one of them is __LINE__. Writing +// +// foo ## __LINE__ +// +// will result in the token foo__LINE__, instead of foo followed by +// the current line number. For more details, see +// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6 +#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar) +#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar + +// Google Test defines the testing::Message class to allow construction of +// test messages via the << operator. The idea is that anything +// streamable to std::ostream can be streamed to a testing::Message. +// This allows a user to use his own types in Google Test assertions by +// overloading the << operator. +// +// util/gtl/stl_logging-inl.h overloads << for STL containers. These +// overloads cannot be defined in the std namespace, as that will be +// undefined behavior. Therefore, they are defined in the global +// namespace instead. +// +// C++'s symbol lookup rule (i.e. Koenig lookup) says that these +// overloads are visible in either the std namespace or the global +// namespace, but not other namespaces, including the testing +// namespace which Google Test's Message class is in. +// +// To allow STL containers (and other types that has a << operator +// defined in the global namespace) to be used in Google Test assertions, +// testing::Message must access the custom << operator from the global +// namespace. Hence this helper function. +// +// Note: Jeffrey Yasskin suggested an alternative fix by "using +// ::operator<<;" in the definition of Message's operator<<. That fix +// doesn't require a helper function, but unfortunately doesn't +// compile with MSVC. +template +inline void GTestStreamToHelper(std::ostream* os, const T& val) { + *os << val; +} + +namespace testing { + +// Forward declaration of classes. + +class Message; // Represents a failure message. +class Test; // Represents a test. +class TestCase; // A collection of related tests. +class TestPartResult; // Result of a test part. +class TestInfo; // Information about a test. +class UnitTest; // A collection of test cases. +class UnitTestEventListenerInterface; // Listens to Google Test events. +class AssertionResult; // Result of an assertion. + +namespace internal { + +struct TraceInfo; // Information about a trace point. +class ScopedTrace; // Implements scoped trace. +class TestInfoImpl; // Opaque implementation of TestInfo +class TestResult; // Result of a single Test. +class UnitTestImpl; // Opaque implementation of UnitTest + +template class List; // A generic list. +template class ListNode; // A node in a generic list. + +// How many times InitGoogleTest() has been called. +extern int g_init_gtest_count; + +// The text used in failure messages to indicate the start of the +// stack trace. +extern const char kStackTraceMarker[]; + +// A secret type that Google Test users don't know about. It has no +// definition on purpose. Therefore it's impossible to create a +// Secret object, which is what we want. +class Secret; + +// Two overloaded helpers for checking at compile time whether an +// expression is a null pointer literal (i.e. NULL or any 0-valued +// compile-time integral constant). Their return values have +// different sizes, so we can use sizeof() to test which version is +// picked by the compiler. These helpers have no implementations, as +// we only need their signatures. +// +// Given IsNullLiteralHelper(x), the compiler will pick the first +// version if x can be implicitly converted to Secret*, and pick the +// second version otherwise. Since Secret is a secret and incomplete +// type, the only expression a user can write that has type Secret* is +// a null pointer literal. Therefore, we know that x is a null +// pointer literal if and only if the first version is picked by the +// compiler. +char IsNullLiteralHelper(Secret* p); +char (&IsNullLiteralHelper(...))[2]; // NOLINT + +// A compile-time bool constant that is true if and only if x is a +// null pointer literal (i.e. NULL or any 0-valued compile-time +// integral constant). +#ifdef GTEST_ELLIPSIS_NEEDS_COPY_ +// Passing non-POD classes through ellipsis (...) crashes the ARM +// compiler. The Nokia Symbian and the IBM XL C/C++ compiler try to +// instantiate a copy constructor for objects passed through ellipsis +// (...), failing for uncopyable objects. Hence we define this to +// false (and lose support for NULL detection). +#define GTEST_IS_NULL_LITERAL_(x) false +#else +#define GTEST_IS_NULL_LITERAL_(x) \ + (sizeof(::testing::internal::IsNullLiteralHelper(x)) == 1) +#endif // GTEST_ELLIPSIS_NEEDS_COPY_ + +// Appends the user-supplied message to the Google-Test-generated message. +String AppendUserMessage(const String& gtest_msg, + const Message& user_msg); + +// A helper class for creating scoped traces in user programs. +class ScopedTrace { + public: + // The c'tor pushes the given source file location and message onto + // a trace stack maintained by Google Test. + ScopedTrace(const char* file, int line, const Message& message); + + // The d'tor pops the info pushed by the c'tor. + // + // Note that the d'tor is not virtual in order to be efficient. + // Don't inherit from ScopedTrace! + ~ScopedTrace(); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace); +} GTEST_ATTRIBUTE_UNUSED_; // A ScopedTrace object does its job in its + // c'tor and d'tor. Therefore it doesn't + // need to be used otherwise. + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +// Declared here but defined in gtest.h, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable); + +// Formats a value to be used in a failure message. + +#ifdef GTEST_NEEDS_IS_POINTER_ + +// These are needed as the Nokia Symbian and IBM XL C/C++ compilers +// cannot decide between const T& and const T* in a function template. +// These compilers _can_ decide between class template specializations +// for T and T*, so a tr1::type_traits-like is_pointer works, and we +// can overload on that. + +// This overload makes sure that all pointers (including +// those to char or wchar_t) are printed as raw pointers. +template +inline String FormatValueForFailureMessage(internal::true_type dummy, + T* pointer) { + return StreamableToString(static_cast(pointer)); +} + +template +inline String FormatValueForFailureMessage(internal::false_type dummy, + const T& value) { + return StreamableToString(value); +} + +template +inline String FormatForFailureMessage(const T& value) { + return FormatValueForFailureMessage( + typename internal::is_pointer::type(), value); +} + +#else + +// These are needed as the above solution using is_pointer has the +// limitation that T cannot be a type without external linkage, when +// compiled using MSVC. + +template +inline String FormatForFailureMessage(const T& value) { + return StreamableToString(value); +} + +// This overload makes sure that all pointers (including +// those to char or wchar_t) are printed as raw pointers. +template +inline String FormatForFailureMessage(T* pointer) { + return StreamableToString(static_cast(pointer)); +} + +#endif // GTEST_NEEDS_IS_POINTER_ + +// These overloaded versions handle narrow and wide characters. +String FormatForFailureMessage(char ch); +String FormatForFailureMessage(wchar_t wchar); + +// When this operand is a const char* or char*, and the other operand +// is a ::std::string or ::string, we print this operand as a C string +// rather than a pointer. We do the same for wide strings. + +// This internal macro is used to avoid duplicated code. +#define GTEST_FORMAT_IMPL_(operand2_type, operand1_printer)\ +inline String FormatForComparisonFailureMessage(\ + operand2_type::value_type* str, const operand2_type& /*operand2*/) {\ + return operand1_printer(str);\ +}\ +inline String FormatForComparisonFailureMessage(\ + const operand2_type::value_type* str, const operand2_type& /*operand2*/) {\ + return operand1_printer(str);\ +} + +#if GTEST_HAS_STD_STRING +GTEST_FORMAT_IMPL_(::std::string, String::ShowCStringQuoted) +#endif // GTEST_HAS_STD_STRING +#if GTEST_HAS_STD_WSTRING +GTEST_FORMAT_IMPL_(::std::wstring, String::ShowWideCStringQuoted) +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_STRING +GTEST_FORMAT_IMPL_(::string, String::ShowCStringQuoted) +#endif // GTEST_HAS_GLOBAL_STRING +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_FORMAT_IMPL_(::wstring, String::ShowWideCStringQuoted) +#endif // GTEST_HAS_GLOBAL_WSTRING + +#undef GTEST_FORMAT_IMPL_ + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true iff the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const String& expected_value, + const String& actual_value, + bool ignoring_case); + + +// This template class represents an IEEE floating-point number +// (either single-precision or double-precision, depending on the +// template parameters). +// +// The purpose of this class is to do more sophisticated number +// comparison. (Due to round-off error, etc, it's very unlikely that +// two floating-points will be equal exactly. Hence a naive +// comparison by the == operation often doesn't work.) +// +// Format of IEEE floating-point: +// +// The most-significant bit being the leftmost, an IEEE +// floating-point looks like +// +// sign_bit exponent_bits fraction_bits +// +// Here, sign_bit is a single bit that designates the sign of the +// number. +// +// For float, there are 8 exponent bits and 23 fraction bits. +// +// For double, there are 11 exponent bits and 52 fraction bits. +// +// More details can be found at +// http://en.wikipedia.org/wiki/IEEE_floating-point_standard. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +template +class FloatingPoint { + public: + // Defines the unsigned integer type that has the same size as the + // floating point number. + typedef typename TypeWithSize::UInt Bits; + + // Constants. + + // # of bits in a number. + static const size_t kBitCount = 8*sizeof(RawType); + + // # of fraction bits in a number. + static const size_t kFractionBitCount = + std::numeric_limits::digits - 1; + + // # of exponent bits in a number. + static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; + + // The mask for the sign bit. + static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1); + + // The mask for the fraction bits. + static const Bits kFractionBitMask = + ~static_cast(0) >> (kExponentBitCount + 1); + + // The mask for the exponent bits. + static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); + + // How many ULP's (Units in the Last Place) we want to tolerate when + // comparing two numbers. The larger the value, the more error we + // allow. A 0 value means that two numbers must be exactly the same + // to be considered equal. + // + // The maximum error of a single floating-point operation is 0.5 + // units in the last place. On Intel CPU's, all floating-point + // calculations are done with 80-bit precision, while double has 64 + // bits. Therefore, 4 should be enough for ordinary use. + // + // See the following article for more details on ULP: + // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm. + static const size_t kMaxUlps = 4; + + // Constructs a FloatingPoint from a raw floating-point number. + // + // On an Intel CPU, passing a non-normalized NAN (Not a Number) + // around may change its bits, although the new value is guaranteed + // to be also a NAN. Therefore, don't expect this constructor to + // preserve the bits in x when x is a NAN. + explicit FloatingPoint(const RawType& x) : value_(x) {} + + // Static methods + + // Reinterprets a bit pattern as a floating-point number. + // + // This function is needed to test the AlmostEquals() method. + static RawType ReinterpretBits(const Bits bits) { + FloatingPoint fp(0); + fp.bits_ = bits; + return fp.value_; + } + + // Returns the floating-point number that represent positive infinity. + static RawType Infinity() { + return ReinterpretBits(kExponentBitMask); + } + + // Non-static methods + + // Returns the bits that represents this number. + const Bits &bits() const { return bits_; } + + // Returns the exponent bits of this number. + Bits exponent_bits() const { return kExponentBitMask & bits_; } + + // Returns the fraction bits of this number. + Bits fraction_bits() const { return kFractionBitMask & bits_; } + + // Returns the sign bit of this number. + Bits sign_bit() const { return kSignBitMask & bits_; } + + // Returns true iff this is NAN (not a number). + bool is_nan() const { + // It's a NAN if the exponent bits are all ones and the fraction + // bits are not entirely zeros. + return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); + } + + // Returns true iff this number is at most kMaxUlps ULP's away from + // rhs. In particular, this function: + // + // - returns false if either number is (or both are) NAN. + // - treats really large numbers as almost equal to infinity. + // - thinks +0.0 and -0.0 are 0 DLP's apart. + bool AlmostEquals(const FloatingPoint& rhs) const { + // The IEEE standard says that any comparison operation involving + // a NAN must return false. + if (is_nan() || rhs.is_nan()) return false; + + return DistanceBetweenSignAndMagnitudeNumbers(bits_, rhs.bits_) <= kMaxUlps; + } + + private: + // Converts an integer from the sign-and-magnitude representation to + // the biased representation. More precisely, let N be 2 to the + // power of (kBitCount - 1), an integer x is represented by the + // unsigned number x + N. + // + // For instance, + // + // -N + 1 (the most negative number representable using + // sign-and-magnitude) is represented by 1; + // 0 is represented by N; and + // N - 1 (the biggest number representable using + // sign-and-magnitude) is represented by 2N - 1. + // + // Read http://en.wikipedia.org/wiki/Signed_number_representations + // for more details on signed number representations. + static Bits SignAndMagnitudeToBiased(const Bits &sam) { + if (kSignBitMask & sam) { + // sam represents a negative number. + return ~sam + 1; + } else { + // sam represents a positive number. + return kSignBitMask | sam; + } + } + + // Given two numbers in the sign-and-magnitude representation, + // returns the distance between them as an unsigned number. + static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1, + const Bits &sam2) { + const Bits biased1 = SignAndMagnitudeToBiased(sam1); + const Bits biased2 = SignAndMagnitudeToBiased(sam2); + return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); + } + + union { + RawType value_; // The raw floating-point number. + Bits bits_; // The bits that represent the number. + }; +}; + +// Typedefs the instances of the FloatingPoint template class that we +// care to use. +typedef FloatingPoint Float; +typedef FloatingPoint Double; + +// In order to catch the mistake of putting tests that use different +// test fixture classes in the same test case, we need to assign +// unique IDs to fixture classes and compare them. The TypeId type is +// used to hold such IDs. The user should treat TypeId as an opaque +// type: the only operation allowed on TypeId values is to compare +// them for equality using the == operator. +typedef const void* TypeId; + +template +class TypeIdHelper { + public: + // dummy_ must not have a const type. Otherwise an overly eager + // compiler (e.g. MSVC 7.1 & 8.0) may try to merge + // TypeIdHelper::dummy_ for different Ts as an "optimization". + static bool dummy_; +}; + +template +bool TypeIdHelper::dummy_ = false; + +// GetTypeId() returns the ID of type T. Different values will be +// returned for different types. Calling the function twice with the +// same type argument is guaranteed to return the same ID. +template +TypeId GetTypeId() { + // The compiler is required to allocate a different + // TypeIdHelper::dummy_ variable for each T used to instantiate + // the template. Therefore, the address of dummy_ is guaranteed to + // be unique. + return &(TypeIdHelper::dummy_); +} + +// Returns the type ID of ::testing::Test. Always call this instead +// of GetTypeId< ::testing::Test>() to get the type ID of +// ::testing::Test, as the latter may give the wrong result due to a +// suspected linker bug when compiling Google Test as a Mac OS X +// framework. +TypeId GetTestTypeId(); + +// Defines the abstract factory interface that creates instances +// of a Test object. +class TestFactoryBase { + public: + virtual ~TestFactoryBase() {} + + // Creates a test instance to run. The instance is both created and destroyed + // within TestInfoImpl::Run() + virtual Test* CreateTest() = 0; + + protected: + TestFactoryBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase); +}; + +// This class provides implementation of TeastFactoryBase interface. +// It is used in TEST and TEST_F macros. +template +class TestFactoryImpl : public TestFactoryBase { + public: + virtual Test* CreateTest() { return new TestClass; } +}; + +#if GTEST_OS_WINDOWS + +// Predicate-formatters for implementing the HRESULT checking macros +// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED} +// We pass a long instead of HRESULT to avoid causing an +// include dependency for the HRESULT type. +AssertionResult IsHRESULTSuccess(const char* expr, long hr); // NOLINT +AssertionResult IsHRESULTFailure(const char* expr, long hr); // NOLINT + +#endif // GTEST_OS_WINDOWS + +// Formats a source file path and a line number as they would appear +// in a compiler error message. +inline String FormatFileLocation(const char* file, int line) { + const char* const file_name = file == NULL ? "unknown file" : file; + if (line < 0) { + return String::Format("%s:", file_name); + } +#ifdef _MSC_VER + return String::Format("%s(%d):", file_name, line); +#else + return String::Format("%s:%d:", file_name, line); +#endif // _MSC_VER +} + +// Types of SetUpTestCase() and TearDownTestCase() functions. +typedef void (*SetUpTestCaseFunc)(); +typedef void (*TearDownTestCaseFunc)(); + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_case_name: name of the test case +// name: name of the test +// test_case_comment: a comment on the test case that will be included in +// the test output +// comment: a comment on the test that will be included in the +// test output +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +TestInfo* MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* test_case_comment, const char* comment, + TypeId fixture_class_id, + SetUpTestCaseFunc set_up_tc, + TearDownTestCaseFunc tear_down_tc, + TestFactoryBase* factory); + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// State of the definition of a type-parameterized test case. +class TypedTestCasePState { + public: + TypedTestCasePState() : registered_(false) {} + + // Adds the given test name to defined_test_names_ and return true + // if the test case hasn't been registered; otherwise aborts the + // program. + bool AddTestName(const char* file, int line, const char* case_name, + const char* test_name) { + if (registered_) { + fprintf(stderr, "%s Test %s must be defined before " + "REGISTER_TYPED_TEST_CASE_P(%s, ...).\n", + FormatFileLocation(file, line).c_str(), test_name, case_name); + fflush(stderr); + abort(); + } + defined_test_names_.insert(test_name); + return true; + } + + // Verifies that registered_tests match the test names in + // defined_test_names_; returns registered_tests if successful, or + // aborts the program otherwise. + const char* VerifyRegisteredTestNames( + const char* file, int line, const char* registered_tests); + + private: + bool registered_; + ::std::set defined_test_names_; +}; + +// Skips to the first non-space char after the first comma in 'str'; +// returns NULL if no comma is found in 'str'. +inline const char* SkipComma(const char* str) { + const char* comma = strchr(str, ','); + if (comma == NULL) { + return NULL; + } + while (isspace(*(++comma))) {} + return comma; +} + +// Returns the prefix of 'str' before the first comma in it; returns +// the entire string if it contains no comma. +inline String GetPrefixUntilComma(const char* str) { + const char* comma = strchr(str, ','); + return comma == NULL ? String(str) : String(str, comma - str); +} + +// TypeParameterizedTest::Register() +// registers a list of type-parameterized tests with Google Test. The +// return value is insignificant - we just need to return something +// such that we can call this function in a namespace scope. +// +// Implementation note: The GTEST_TEMPLATE_ macro declares a template +// template parameter. It's defined in gtest-type-util.h. +template +class TypeParameterizedTest { + public: + // 'index' is the index of the test in the type list 'Types' + // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase, + // Types). Valid values for 'index' are [0, N - 1] where N is the + // length of Types. + static bool Register(const char* prefix, const char* case_name, + const char* test_names, int index) { + typedef typename Types::Head Type; + typedef Fixture FixtureClass; + typedef typename GTEST_BIND_(TestSel, Type) TestClass; + + // First, registers the first type-parameterized test in the type + // list. + MakeAndRegisterTestInfo( + String::Format("%s%s%s/%d", prefix, prefix[0] == '\0' ? "" : "/", + case_name, index).c_str(), + GetPrefixUntilComma(test_names).c_str(), + String::Format("TypeParam = %s", GetTypeName().c_str()).c_str(), + "", + GetTypeId(), + TestClass::SetUpTestCase, + TestClass::TearDownTestCase, + new TestFactoryImpl); + + // Next, recurses (at compile time) with the tail of the type list. + return TypeParameterizedTest + ::Register(prefix, case_name, test_names, index + 1); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTest { + public: + static bool Register(const char* /*prefix*/, const char* /*case_name*/, + const char* /*test_names*/, int /*index*/) { + return true; + } +}; + +// TypeParameterizedTestCase::Register() +// registers *all combinations* of 'Tests' and 'Types' with Google +// Test. The return value is insignificant - we just need to return +// something such that we can call this function in a namespace scope. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* prefix, const char* case_name, + const char* test_names) { + typedef typename Tests::Head Head; + + // First, register the first test in 'Test' for each type in 'Types'. + TypeParameterizedTest::Register( + prefix, case_name, test_names, 0); + + // Next, recurses (at compile time) with the tail of the test list. + return TypeParameterizedTestCase + ::Register(prefix, case_name, SkipComma(test_names)); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* prefix, const char* case_name, + const char* test_names) { + return true; + } +}; + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// Returns the current OS stack trace as a String. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +String GetCurrentOsStackTraceExceptTop(UnitTest* unit_test, int skip_count); + +// Returns the number of failed test parts in the given test result object. +int GetFailedPartCount(const TestResult* result); + +// A helper for suppressing warnings on unreachable code in some macros. +bool AlwaysTrue(); + +} // namespace internal +} // namespace testing + +#define GTEST_MESSAGE_(message, result_type) \ + ::testing::internal::AssertHelper(result_type, __FILE__, __LINE__, message) \ + = ::testing::Message() + +#define GTEST_FATAL_FAILURE_(message) \ + return GTEST_MESSAGE_(message, ::testing::TPRT_FATAL_FAILURE) + +#define GTEST_NONFATAL_FAILURE_(message) \ + GTEST_MESSAGE_(message, ::testing::TPRT_NONFATAL_FAILURE) + +#define GTEST_SUCCESS_(message) \ + GTEST_MESSAGE_(message, ::testing::TPRT_SUCCESS) + +// Suppresses MSVC warnings 4072 (unreachable code) for the code following +// statement if it returns or throws (or doesn't return or throw in some +// situations). +#define GTEST_HIDE_UNREACHABLE_CODE_(statement) \ + if (::testing::internal::AlwaysTrue()) { statement; } + +#define GTEST_TEST_THROW_(statement, expected_exception, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + bool gtest_caught_expected = false; \ + try { \ + GTEST_HIDE_UNREACHABLE_CODE_(statement); \ + } \ + catch (expected_exception const&) { \ + gtest_caught_expected = true; \ + } \ + catch (...) { \ + gtest_msg = "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws a different " \ + "type."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + if (!gtest_caught_expected) { \ + gtest_msg = "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws nothing."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \ + fail(gtest_msg) + +#define GTEST_TEST_NO_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + try { \ + GTEST_HIDE_UNREACHABLE_CODE_(statement); \ + } \ + catch (...) { \ + gtest_msg = "Expected: " #statement " doesn't throw an exception.\n" \ + " Actual: it throws."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \ + fail(gtest_msg) + +#define GTEST_TEST_ANY_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + bool gtest_caught_any = false; \ + try { \ + GTEST_HIDE_UNREACHABLE_CODE_(statement); \ + } \ + catch (...) { \ + gtest_caught_any = true; \ + } \ + if (!gtest_caught_any) { \ + gtest_msg = "Expected: " #statement " throws an exception.\n" \ + " Actual: it doesn't."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \ + fail(gtest_msg) + + +#define GTEST_TEST_BOOLEAN_(boolexpr, booltext, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (boolexpr) \ + ; \ + else \ + fail("Value of: " booltext "\n Actual: " #actual "\nExpected: " #expected) + +#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const char* gtest_msg = "") { \ + ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \ + GTEST_HIDE_UNREACHABLE_CODE_(statement); \ + if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ + gtest_msg = "Expected: " #statement " doesn't generate new fatal " \ + "failures in the current thread.\n" \ + " Actual: it does."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \ + fail(gtest_msg) + +// Expands to the name of the class that implements the given test. +#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + test_case_name##_##test_name##_Test + +// Helper macro for defining tests. +#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\ +class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ + public:\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\ + private:\ + virtual void TestBody();\ + static ::testing::TestInfo* const test_info_;\ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\ +};\ +\ +::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\ + ::test_info_ =\ + ::testing::internal::MakeAndRegisterTestInfo(\ + #test_case_name, #test_name, "", "", \ + (parent_id), \ + parent_class::SetUpTestCase, \ + parent_class::TearDownTestCase, \ + new ::testing::internal::TestFactoryImpl<\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ +void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-linked_ptr.h b/digsby/ext/lib/include/gtest/internal/gtest-linked_ptr.h new file mode 100644 index 0000000..f98af0b --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-linked_ptr.h @@ -0,0 +1,242 @@ +// Copyright 2003 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: Dan Egnor (egnor@google.com) +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is assigned, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Bill Gibbons suggested we use something like this. +// +// Thread Safety: +// Unlike other linked_ptr implementations, in this implementation +// a linked_ptr object is thread-safe in the sense that: +// - it's safe to copy linked_ptr objects concurrently, +// - it's safe to copy *from* a linked_ptr and read its underlying +// raw pointer (e.g. via get()) concurrently, and +// - it's safe to write to two linked_ptrs that point to the same +// shared object concurrently. +// TODO(wan@google.com): rename this to safe_linked_ptr to avoid +// confusion with normal linked_ptr. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ + +#include +#include + +#include + +namespace testing { +namespace internal { + +// Protects copying of all linked_ptr objects. +extern Mutex g_linked_ptr_mutex; + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr(obj) vs linked_ptr(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Many linked_ptr operations may change p.link_ for some linked_ptr + // variable p in the same circle as this object. Therefore we need + // to prevent two such operations from occurring concurrently. + // + // Note that different types of linked_ptr objects can coexist in a + // circle (e.g. linked_ptr, linked_ptr, and + // linked_ptr). Therefore we must use a single mutex to + // protect all linked_ptr objects. This can create serious + // contention in production code, but is acceptable in a testing + // framework. + + // Join an existing circle. + // L < g_linked_ptr_mutex + void join(linked_ptr_internal const* ptr) { + MutexLock lock(&g_linked_ptr_mutex); + + linked_ptr_internal const* p = ptr; + while (p->next_ != ptr) p = p->next_; + p->next_ = this; + next_ = ptr; + } + + // Leave whatever circle we're part of. Returns true if we were the + // last member of the circle. Once this is done, you can join() another. + // L < g_linked_ptr_mutex + bool depart() { + MutexLock lock(&g_linked_ptr_mutex); + + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template linked_ptr(linked_ptr const& ptr) { copy(&ptr); } + linked_ptr(linked_ptr const& ptr) { // NOLINT + assert(&ptr != this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template linked_ptr& operator=(linked_ptr const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + // Release ownership of the pointed object and returns it. + // Sole ownership by this linked_ptr object is required. + T* release() { + bool last = link_.depart(); + assert(last); + T* v = value_; + value_ = NULL; + return v; + } + + bool operator==(T* p) const { return value_ == p; } + bool operator!=(T* p) const { return value_ != p; } + template + bool operator==(linked_ptr const& ptr) const { + return value_ == ptr.get(); + } + template + bool operator!=(linked_ptr const& ptr) const { + return value_ != ptr.get(); + } + + private: + template + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template void copy(linked_ptr const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template inline +bool operator==(T* ptr, const linked_ptr& x) { + return ptr == x.get(); +} + +template inline +bool operator!=(T* ptr, const linked_ptr& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr +// Doing e.g. make_linked_ptr(new FooBarBaz(arg)) is a shorter notation +// for linked_ptr >(new FooBarBaz(arg)) +template +linked_ptr make_linked_ptr(T* ptr) { + return linked_ptr(ptr); +} + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-param-util-generated.h b/digsby/ext/lib/include/gtest/internal/gtest-param-util-generated.h new file mode 100644 index 0000000..ad06e02 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-param-util-generated.h @@ -0,0 +1,4572 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently Google Test supports at most 50 arguments in Values, +// and at most 10 arguments in Combine. Please contact +// googletestframework@googlegroups.com if you need more. +// Please note that the number of arguments to Combine is limited +// by the maximum arity of the implementation of tr1::tuple which is +// currently set at 10. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +#include + +#if GTEST_HAS_PARAM_TEST + +#include + +namespace testing { +namespace internal { + +// Used in the Values() function to provide polymorphic capabilities. +template +class ValueArray1 { + public: + explicit ValueArray1(T1 v1) : v1_(v1) {} + + template + operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + + private: + const T1 v1_; +}; + +template +class ValueArray2 { + public: + ValueArray2(T1 v1, T2 v2) : v1_(v1), v2_(v2) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; +}; + +template +class ValueArray3 { + public: + ValueArray3(T1 v1, T2 v2, T3 v3) : v1_(v1), v2_(v2), v3_(v3) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; +}; + +template +class ValueArray4 { + public: + ValueArray4(T1 v1, T2 v2, T3 v3, T4 v4) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; +}; + +template +class ValueArray5 { + public: + ValueArray5(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; +}; + +template +class ValueArray6 { + public: + ValueArray6(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; +}; + +template +class ValueArray7 { + public: + ValueArray7(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; +}; + +template +class ValueArray8 { + public: + ValueArray8(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; +}; + +template +class ValueArray9 { + public: + ValueArray9(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; +}; + +template +class ValueArray10 { + public: + ValueArray10(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; +}; + +template +class ValueArray11 { + public: + ValueArray11(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; +}; + +template +class ValueArray12 { + public: + ValueArray12(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; +}; + +template +class ValueArray13 { + public: + ValueArray13(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; +}; + +template +class ValueArray14 { + public: + ValueArray14(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; +}; + +template +class ValueArray15 { + public: + ValueArray15(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; +}; + +template +class ValueArray16 { + public: + ValueArray16(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; +}; + +template +class ValueArray17 { + public: + ValueArray17(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; +}; + +template +class ValueArray18 { + public: + ValueArray18(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; +}; + +template +class ValueArray19 { + public: + ValueArray19(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; +}; + +template +class ValueArray20 { + public: + ValueArray20(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; +}; + +template +class ValueArray21 { + public: + ValueArray21(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; +}; + +template +class ValueArray22 { + public: + ValueArray22(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; +}; + +template +class ValueArray23 { + public: + ValueArray23(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, + v23_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; +}; + +template +class ValueArray24 { + public: + ValueArray24(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; +}; + +template +class ValueArray25 { + public: + ValueArray25(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; +}; + +template +class ValueArray26 { + public: + ValueArray26(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; +}; + +template +class ValueArray27 { + public: + ValueArray27(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; +}; + +template +class ValueArray28 { + public: + ValueArray28(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; +}; + +template +class ValueArray29 { + public: + ValueArray29(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; +}; + +template +class ValueArray30 { + public: + ValueArray30(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; +}; + +template +class ValueArray31 { + public: + ValueArray31(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; +}; + +template +class ValueArray32 { + public: + ValueArray32(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; +}; + +template +class ValueArray33 { + public: + ValueArray33(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; +}; + +template +class ValueArray34 { + public: + ValueArray34(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; +}; + +template +class ValueArray35 { + public: + ValueArray35(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, + v35_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; +}; + +template +class ValueArray36 { + public: + ValueArray36(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; +}; + +template +class ValueArray37 { + public: + ValueArray37(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; +}; + +template +class ValueArray38 { + public: + ValueArray38(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; +}; + +template +class ValueArray39 { + public: + ValueArray39(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; +}; + +template +class ValueArray40 { + public: + ValueArray40(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; +}; + +template +class ValueArray41 { + public: + ValueArray41(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; +}; + +template +class ValueArray42 { + public: + ValueArray42(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; +}; + +template +class ValueArray43 { + public: + ValueArray43(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), + v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; +}; + +template +class ValueArray44 { + public: + ValueArray44(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), + v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), + v43_(v43), v44_(v44) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; +}; + +template +class ValueArray45 { + public: + ValueArray45(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), + v42_(v42), v43_(v43), v44_(v44), v45_(v45) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; +}; + +template +class ValueArray46 { + public: + ValueArray46(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; +}; + +template +class ValueArray47 { + public: + ValueArray47(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46), + v47_(v47) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, + v47_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; +}; + +template +class ValueArray48 { + public: + ValueArray48(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), + v46_(v46), v47_(v47), v48_(v48) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; +}; + +template +class ValueArray49 { + public: + ValueArray49(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, + T49 v49) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_, v49_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; +}; + +template +class ValueArray50 { + public: + ValueArray50(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, T49 v49, + T50 v50) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49), v50_(v50) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_, v49_, v50_}; + return ValuesIn(array); + } + + private: + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; + const T50 v50_; +}; + +#if GTEST_HAS_COMBINE +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Generates values from the Cartesian product of values produced +// by the argument generators. +// +template +class CartesianProductGenerator2 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator2(const ParamGenerator& g1, + const ParamGenerator& g2) + : g1_(g1), g2_(g2) {} + virtual ~CartesianProductGenerator2() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current2_; + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; +}; + + +template +class CartesianProductGenerator3 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator3(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + virtual ~CartesianProductGenerator3() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current3_; + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; +}; + + +template +class CartesianProductGenerator4 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator4(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + virtual ~CartesianProductGenerator4() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current4_; + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; +}; + + +template +class CartesianProductGenerator5 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator5(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + virtual ~CartesianProductGenerator5() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current5_; + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; +}; + + +template +class CartesianProductGenerator6 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator6(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + virtual ~CartesianProductGenerator6() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current6_; + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; +}; + + +template +class CartesianProductGenerator7 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator7(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + virtual ~CartesianProductGenerator7() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current7_; + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; +}; + + +template +class CartesianProductGenerator8 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator8(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + virtual ~CartesianProductGenerator8() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current8_; + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; +}; + + +template +class CartesianProductGenerator9 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator9(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + virtual ~CartesianProductGenerator9() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current9_; + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; +}; + + +template +class CartesianProductGenerator10 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator10(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9, + const ParamGenerator& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + virtual ~CartesianProductGenerator10() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin(), g10_, g10_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end(), g10_, g10_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9, + const ParamGenerator& g10, + const typename ParamGenerator::iterator& current10) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9), + begin10_(g10.begin()), end10_(g10.end()), current10_(current10) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current10_; + if (current10_ == end10_) { + current10_ = begin10_; + ++current9_; + } + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_ && + current10_ == typed_other->current10_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_), + begin10_(other.begin10_), + end10_(other.end10_), + current10_(other.current10_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_, *current10_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_ || + current10_ == end10_; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + const typename ParamGenerator::iterator begin10_; + const typename ParamGenerator::iterator end10_; + typename ParamGenerator::iterator current10_; + ParamType current_value_; + }; + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; + const ParamGenerator g10_; +}; + + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Helper classes providing Combine() with polymorphic features. They allow +// casting CartesianProductGeneratorN to ParamGenerator if T is +// convertible to U. +// +template +class CartesianProductHolder2 { + public: +CartesianProductHolder2(const Generator1& g1, const Generator2& g2) + : g1_(g1), g2_(g2) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator2( + static_cast >(g1_), + static_cast >(g2_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; +}; + +template +class CartesianProductHolder3 { + public: +CartesianProductHolder3(const Generator1& g1, const Generator2& g2, + const Generator3& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator3( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; +}; + +template +class CartesianProductHolder4 { + public: +CartesianProductHolder4(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator4( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; +}; + +template +class CartesianProductHolder5 { + public: +CartesianProductHolder5(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator5( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; +}; + +template +class CartesianProductHolder6 { + public: +CartesianProductHolder6(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator6( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; +}; + +template +class CartesianProductHolder7 { + public: +CartesianProductHolder7(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator7( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; +}; + +template +class CartesianProductHolder8 { + public: +CartesianProductHolder8(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator8( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; +}; + +template +class CartesianProductHolder9 { + public: +CartesianProductHolder9(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator9( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; +}; + +template +class CartesianProductHolder10 { + public: +CartesianProductHolder10(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9, const Generator10& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator10( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_), + static_cast >(g10_))); + } + + private: + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; + const Generator10 g10_; +}; + +#endif // GTEST_HAS_COMBINE + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-param-util-generated.h.pump b/digsby/ext/lib/include/gtest/internal/gtest-param-util-generated.h.pump new file mode 100644 index 0000000..54b2dc1 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-param-util-generated.h.pump @@ -0,0 +1,269 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of Values arguments we want to support. +$var maxtuple = 10 $$ Maximum number of Combine arguments we want to support. +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently Google Test supports at most $n arguments in Values, +// and at most $maxtuple arguments in Combine. Please contact +// googletestframework@googlegroups.com if you need more. +// Please note that the number of arguments to Combine is limited +// by the maximum arity of the implementation of tr1::tuple which is +// currently set at $maxtuple. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +#include + +#if GTEST_HAS_PARAM_TEST + +#include + +namespace testing { +namespace internal { + +// Used in the Values() function to provide polymorphic capabilities. +template +class ValueArray1 { + public: + explicit ValueArray1(T1 v1) : v1_(v1) {} + + template + operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + + private: + const T1 v1_; +}; + +$range i 2..n +$for i [[ +$range j 1..i + +template <$for j, [[typename T$j]]> +class ValueArray$i { + public: + ValueArray$i($for j, [[T$j v$j]]) : $for j, [[v$(j)_(v$j)]] {} + + template + operator ParamGenerator() const { + const T array[] = {$for j, [[v$(j)_]]}; + return ValuesIn(array); + } + + private: +$for j [[ + + const T$j v$(j)_; +]] + +}; + +]] + +#if GTEST_HAS_COMBINE +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Generates values from the Cartesian product of values produced +// by the argument generators. +// +$range i 2..maxtuple +$for i [[ +$range j 1..i +$range k 2..i + +template <$for j, [[typename T$j]]> +class CartesianProductGenerator$i + : public ParamGeneratorInterface< ::std::tr1::tuple<$for j, [[T$j]]> > { + public: + typedef ::std::tr1::tuple<$for j, [[T$j]]> ParamType; + + CartesianProductGenerator$i($for j, [[const ParamGenerator& g$j]]) + : $for j, [[g$(j)_(g$j)]] {} + virtual ~CartesianProductGenerator$i() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, $for j, [[g$(j)_, g$(j)_.begin()]]); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, $for j, [[g$(j)_, g$(j)_.end()]]); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, $for j, [[ + + const ParamGenerator& g$j, + const typename ParamGenerator::iterator& current$(j)]]) + : base_(base), +$for j, [[ + + begin$(j)_(g$j.begin()), end$(j)_(g$j.end()), current$(j)_(current$j) +]] { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current$(i)_; + +$for k [[ + if (current$(i+2-k)_ == end$(i+2-k)_) { + current$(i+2-k)_ = begin$(i+2-k)_; + ++current$(i+2-k-1)_; + } + +]] + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ($for j && [[ + + current$(j)_ == typed_other->current$(j)_ +]]); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), $for j, [[ + + begin$(j)_(other.begin$(j)_), + end$(j)_(other.end$(j)_), + current$(j)_(other.current$(j)_) +]] { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType($for j, [[*current$(j)_]]); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return +$for j || [[ + + current$(j)_ == end$(j)_ +]]; + } + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. +$for j [[ + + const typename ParamGenerator::iterator begin$(j)_; + const typename ParamGenerator::iterator end$(j)_; + typename ParamGenerator::iterator current$(j)_; +]] + + ParamType current_value_; + }; + + +$for j [[ + const ParamGenerator g$(j)_; + +]] +}; + + +]] + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Helper classes providing Combine() with polymorphic features. They allow +// casting CartesianProductGeneratorN to ParamGenerator if T is +// convertible to U. +// +$range i 2..maxtuple +$for i [[ +$range j 1..i + +template <$for j, [[class Generator$j]]> +class CartesianProductHolder$i { + public: +CartesianProductHolder$i($for j, [[const Generator$j& g$j]]) + : $for j, [[g$(j)_(g$j)]] {} + template <$for j, [[typename T$j]]> + operator ParamGenerator< ::std::tr1::tuple<$for j, [[T$j]]> >() const { + return ParamGenerator< ::std::tr1::tuple<$for j, [[T$j]]> >( + new CartesianProductGenerator$i<$for j, [[T$j]]>( +$for j,[[ + + static_cast >(g$(j)_) +]])); + } + + private: + +$for j [[ + const Generator$j g$(j)_; + +]] +}; + +]] + +#endif // GTEST_HAS_COMBINE + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-param-util.h b/digsby/ext/lib/include/gtest/internal/gtest-param-util.h new file mode 100644 index 0000000..5559ab4 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-param-util.h @@ -0,0 +1,629 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ + +#include +#include +#include + +#include + +#if GTEST_HAS_PARAM_TEST + +#if GTEST_HAS_RTTI +#include +#endif // GTEST_HAS_RTTI + +#include +#include + +namespace testing { +namespace internal { + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Outputs a message explaining invalid registration of different +// fixture class for the same test case. This may happen when +// TEST_P macro is used to define two tests with the same name +// but in different namespaces. +void ReportInvalidTestCaseType(const char* test_case_name, + const char* file, int line); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Downcasts the pointer of type Base to Derived. +// Derived must be a subclass of Base. The parameter MUST +// point to a class of type Derived, not any subclass of it. +// When RTTI is available, the function performs a runtime +// check to enforce this. +template +Derived* CheckedDowncastToActualType(Base* base) { +#if GTEST_HAS_RTTI + GTEST_CHECK_(typeid(*base) == typeid(Derived)); + Derived* derived = dynamic_cast(base); // NOLINT +#else + Derived* derived = static_cast(base); // Poor man's downcast. +#endif // GTEST_HAS_RTTI + return derived; +} + +template class ParamGeneratorInterface; +template class ParamGenerator; + +// Interface for iterating over elements provided by an implementation +// of ParamGeneratorInterface. +template +class ParamIteratorInterface { + public: + virtual ~ParamIteratorInterface() {} + // A pointer to the base generator instance. + // Used only for the purposes of iterator comparison + // to make sure that two iterators belong to the same generator. + virtual const ParamGeneratorInterface* BaseGenerator() const = 0; + // Advances iterator to point to the next element + // provided by the generator. The caller is responsible + // for not calling Advance() on an iterator equal to + // BaseGenerator()->End(). + virtual void Advance() = 0; + // Clones the iterator object. Used for implementing copy semantics + // of ParamIterator. + virtual ParamIteratorInterface* Clone() const = 0; + // Dereferences the current iterator and provides (read-only) access + // to the pointed value. It is the caller's responsibility not to call + // Current() on an iterator equal to BaseGenerator()->End(). + // Used for implementing ParamGenerator::operator*(). + virtual const T* Current() const = 0; + // Determines whether the given iterator and other point to the same + // element in the sequence generated by the generator. + // Used for implementing ParamGenerator::operator==(). + virtual bool Equals(const ParamIteratorInterface& other) const = 0; +}; + +// Class iterating over elements provided by an implementation of +// ParamGeneratorInterface. It wraps ParamIteratorInterface +// and implements the const forward iterator concept. +template +class ParamIterator { + public: + typedef T value_type; + typedef const T& reference; + typedef ptrdiff_t difference_type; + + // ParamIterator assumes ownership of the impl_ pointer. + ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {} + ParamIterator& operator=(const ParamIterator& other) { + if (this != &other) + impl_.reset(other.impl_->Clone()); + return *this; + } + + const T& operator*() const { return *impl_->Current(); } + const T* operator->() const { return impl_->Current(); } + // Prefix version of operator++. + ParamIterator& operator++() { + impl_->Advance(); + return *this; + } + // Postfix version of operator++. + ParamIterator operator++(int /*unused*/) { + ParamIteratorInterface* clone = impl_->Clone(); + impl_->Advance(); + return ParamIterator(clone); + } + bool operator==(const ParamIterator& other) const { + return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_); + } + bool operator!=(const ParamIterator& other) const { + return !(*this == other); + } + + private: + friend class ParamGenerator; + explicit ParamIterator(ParamIteratorInterface* impl) : impl_(impl) {} + scoped_ptr > impl_; +}; + +// ParamGeneratorInterface is the binary interface to access generators +// defined in other translation units. +template +class ParamGeneratorInterface { + public: + typedef T ParamType; + + virtual ~ParamGeneratorInterface() {} + + // Generator interface definition + virtual ParamIteratorInterface* Begin() const = 0; + virtual ParamIteratorInterface* End() const = 0; +}; + +// Wraps ParamGeneratorInetrface and provides general generator syntax +// compatible with the STL Container concept. +// This class implements copy initialization semantics and the contained +// ParamGeneratorInterface instance is shared among all copies +// of the original object. This is possible because that instance is immutable. +template +class ParamGenerator { + public: + typedef ParamIterator iterator; + + explicit ParamGenerator(ParamGeneratorInterface* impl) : impl_(impl) {} + ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {} + + ParamGenerator& operator=(const ParamGenerator& other) { + impl_ = other.impl_; + return *this; + } + + iterator begin() const { return iterator(impl_->Begin()); } + iterator end() const { return iterator(impl_->End()); } + + private: + ::testing::internal::linked_ptr > impl_; +}; + +// Generates values from a range of two comparable values. Can be used to +// generate sequences of user-defined types that implement operator+() and +// operator<(). +// This class is used in the Range() function. +template +class RangeGenerator : public ParamGeneratorInterface { + public: + RangeGenerator(T begin, T end, IncrementT step) + : begin_(begin), end_(end), + step_(step), end_index_(CalculateEndIndex(begin, end, step)) {} + virtual ~RangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, begin_, 0, step_); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, end_, end_index_, step_); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, T value, int index, + IncrementT step) + : base_(base), value_(value), index_(index), step_(step) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + value_ = value_ + step_; + index_++; + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const T* Current() const { return &value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const int other_index = + CheckedDowncastToActualType(&other)->index_; + return index_ == other_index; + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), value_(other.value_), index_(other.index_), + step_(other.step_) {} + + const ParamGeneratorInterface* const base_; + T value_; + int index_; + const IncrementT step_; + }; // class RangeGenerator::Iterator + + static int CalculateEndIndex(const T& begin, + const T& end, + const IncrementT& step) { + int end_index = 0; + for (T i = begin; i < end; i = i + step) + end_index++; + return end_index; + } + + const T begin_; + const T end_; + const IncrementT step_; + // The index for the end() iterator. All the elements in the generated + // sequence are indexed (0-based) to aid iterator comparison. + const int end_index_; +}; // class RangeGenerator + + +// Generates values from a pair of STL-style iterators. Used in the +// ValuesIn() function. The elements are copied from the source range +// since the source can be located on the stack, and the generator +// is likely to persist beyond that stack frame. +template +class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { + public: + template + ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end) + : container_(begin, end) {} + virtual ~ValuesInIteratorRangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, container_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, container_.end()); + } + + private: + typedef typename ::std::vector ContainerType; + + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + typename ContainerType::const_iterator iterator) + : base_(base), iterator_(iterator) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + ++iterator_; + value_.reset(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + // We need to use cached value referenced by iterator_ because *iterator_ + // can return a temporary object (and of type other then T), so just + // having "return &*iterator_;" doesn't work. + // value_ is updated here and not in Advance() because Advance() + // can advance iterator_ beyond the end of the range, and we cannot + // detect that fact. The client code, on the other hand, is + // responsible for not calling Current() on an out-of-range iterator. + virtual const T* Current() const { + if (value_.get() == NULL) + value_.reset(new T(*iterator_)); + return value_.get(); + } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + return iterator_ == + CheckedDowncastToActualType(&other)->iterator_; + } + + private: + Iterator(const Iterator& other) + // The explicit constructor call suppresses a false warning + // emitted by gcc when supplied with the -Wextra option. + : ParamIteratorInterface(), + base_(other.base_), + iterator_(other.iterator_) {} + + const ParamGeneratorInterface* const base_; + typename ContainerType::const_iterator iterator_; + // A cached value of *iterator_. We keep it here to allow access by + // pointer in the wrapping iterator's operator->(). + // value_ needs to be mutable to be accessed in Current(). + // Use of scoped_ptr helps manage cached value's lifetime, + // which is bound by the lifespan of the iterator itself. + mutable scoped_ptr value_; + }; + + const ContainerType container_; +}; // class ValuesInIteratorRangeGenerator + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Stores a parameter value and later creates tests parameterized with that +// value. +template +class ParameterizedTestFactory : public TestFactoryBase { + public: + typedef typename TestClass::ParamType ParamType; + explicit ParameterizedTestFactory(ParamType parameter) : + parameter_(parameter) {} + virtual Test* CreateTest() { + TestClass::SetParam(¶meter_); + return new TestClass(); + } + + private: + const ParamType parameter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactoryBase is a base class for meta-factories that create +// test factories for passing into MakeAndRegisterTestInfo function. +template +class TestMetaFactoryBase { + public: + virtual ~TestMetaFactoryBase() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactory creates test factories for passing into +// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives +// ownership of test factory pointer, same factory object cannot be passed +// into that method twice. But ParameterizedTestCaseInfo is going to call +// it for each Test/Parameter value combination. Thus it needs meta factory +// creator class. +template +class TestMetaFactory + : public TestMetaFactoryBase { + public: + typedef typename TestCase::ParamType ParamType; + + TestMetaFactory() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) { + return new ParameterizedTestFactory(parameter); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfoBase is a generic interface +// to ParameterizedTestCaseInfo classes. ParameterizedTestCaseInfoBase +// accumulates test information provided by TEST_P macro invocations +// and generators provided by INSTANTIATE_TEST_CASE_P macro invocations +// and uses that information to register all resulting test instances +// in RegisterTests method. The ParameterizeTestCaseRegistry class holds +// a collection of pointers to the ParameterizedTestCaseInfo objects +// and calls RegisterTests() on each of them when asked. +class ParameterizedTestCaseInfoBase { + public: + virtual ~ParameterizedTestCaseInfoBase() {} + + // Base part of test case name for display purposes. + virtual const String& GetTestCaseName() const = 0; + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const = 0; + // UnitTest class invokes this method to register tests in this + // test case right before running them in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + virtual void RegisterTests() = 0; + + protected: + ParameterizedTestCaseInfoBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfoBase); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfo accumulates tests obtained from TEST_P +// macro invocations for a particular test case and generators +// obtained from INSTANTIATE_TEST_CASE_P macro invocations for that +// test case. It registers tests with all values generated by all +// generators when asked. +template +class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { + public: + // ParamType and GeneratorCreationFunc are private types but are required + // for declarations of public methods AddTestPattern() and + // AddTestCaseInstantiation(). + typedef typename TestCase::ParamType ParamType; + // A function that returns an instance of appropriate generator type. + typedef ParamGenerator(GeneratorCreationFunc)(); + + explicit ParameterizedTestCaseInfo(const char* name) + : test_case_name_(name) {} + + // Test case base name for display purposes. + virtual const String& GetTestCaseName() const { return test_case_name_; } + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const { return GetTypeId(); } + // TEST_P macro uses AddTestPattern() to record information + // about a single test in a LocalTestInfo structure. + // test_case_name is the base name of the test case (without invocation + // prefix). test_base_name is the name of an individual test without + // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is + // test case base name and DoBar is test base name. + void AddTestPattern(const char* test_case_name, + const char* test_base_name, + TestMetaFactoryBase* meta_factory) { + tests_.push_back(linked_ptr(new TestInfo(test_case_name, + test_base_name, + meta_factory))); + } + // INSTANTIATE_TEST_CASE_P macro uses AddGenerator() to record information + // about a generator. + int AddTestCaseInstantiation(const char* instantiation_name, + GeneratorCreationFunc* func, + const char* file, + int line) { + instantiations_.push_back(::std::make_pair(instantiation_name, func)); + return 0; // Return value used only to run this method in namespace scope. + } + // UnitTest class invokes this method to register tests in this test case + // test cases right before running tests in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + // UnitTest has a guard to prevent from calling this method more then once. + virtual void RegisterTests() { + for (typename TestInfoContainer::iterator test_it = tests_.begin(); + test_it != tests_.end(); ++test_it) { + linked_ptr test_info = *test_it; + for (typename InstantiationContainer::iterator gen_it = + instantiations_.begin(); gen_it != instantiations_.end(); + ++gen_it) { + const String& instantiation_name = gen_it->first; + ParamGenerator generator((*gen_it->second)()); + + Message test_case_name_stream; + if ( !instantiation_name.empty() ) + test_case_name_stream << instantiation_name.c_str() << "/"; + test_case_name_stream << test_info->test_case_base_name.c_str(); + + int i = 0; + for (typename ParamGenerator::iterator param_it = + generator.begin(); + param_it != generator.end(); ++param_it, ++i) { + Message test_name_stream; + test_name_stream << test_info->test_base_name.c_str() << "/" << i; + ::testing::internal::MakeAndRegisterTestInfo( + test_case_name_stream.GetString().c_str(), + test_name_stream.GetString().c_str(), + "", // test_case_comment + "", // comment; TODO(vladl@google.com): provide parameter value + // representation. + GetTestCaseTypeId(), + TestCase::SetUpTestCase, + TestCase::TearDownTestCase, + test_info->test_meta_factory->CreateTestFactory(*param_it)); + } // for param_it + } // for gen_it + } // for test_it + } // RegisterTests + + private: + // LocalTestInfo structure keeps information about a single test registered + // with TEST_P macro. + struct TestInfo { + TestInfo(const char* test_case_base_name, + const char* test_base_name, + TestMetaFactoryBase* test_meta_factory) : + test_case_base_name(test_case_base_name), + test_base_name(test_base_name), + test_meta_factory(test_meta_factory) {} + + const String test_case_base_name; + const String test_base_name; + const scoped_ptr > test_meta_factory; + }; + typedef ::std::vector > TestInfoContainer; + // Keeps pairs of + // received from INSTANTIATE_TEST_CASE_P macros. + typedef ::std::vector > + InstantiationContainer; + + const String test_case_name_; + TestInfoContainer tests_; + InstantiationContainer instantiations_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfo); +}; // class ParameterizedTestCaseInfo + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseRegistry contains a map of ParameterizedTestCaseInfoBase +// classes accessed by test case names. TEST_P and INSTANTIATE_TEST_CASE_P +// macros use it to locate their corresponding ParameterizedTestCaseInfo +// descriptors. +class ParameterizedTestCaseRegistry { + public: + ParameterizedTestCaseRegistry() {} + ~ParameterizedTestCaseRegistry() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + delete *it; + } + } + + // Looks up or creates and returns a structure containing information about + // tests and instantiations of a particular test case. + template + ParameterizedTestCaseInfo* GetTestCasePatternHolder( + const char* test_case_name, + const char* file, + int line) { + ParameterizedTestCaseInfo* typed_test_info = NULL; + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + if ((*it)->GetTestCaseName() == test_case_name) { + if ((*it)->GetTestCaseTypeId() != GetTypeId()) { + // Complain about incorrect usage of Google Test facilities + // and terminate the program since we cannot guaranty correct + // test case setup and tear-down in this case. + ReportInvalidTestCaseType(test_case_name, file, line); + abort(); + } else { + // At this point we are sure that the object we found is of the same + // type we are looking for, so we downcast it to that type + // without further checks. + typed_test_info = CheckedDowncastToActualType< + ParameterizedTestCaseInfo >(*it); + } + break; + } + } + if (typed_test_info == NULL) { + typed_test_info = new ParameterizedTestCaseInfo(test_case_name); + test_case_infos_.push_back(typed_test_info); + } + return typed_test_info; + } + void RegisterTests() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + (*it)->RegisterTests(); + } + } + + private: + typedef ::std::vector TestCaseInfoContainer; + + TestCaseInfoContainer test_case_infos_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseRegistry); +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-port.h b/digsby/ext/lib/include/gtest/internal/gtest-port.h new file mode 100644 index 0000000..11798a1 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-port.h @@ -0,0 +1,894 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan) +// +// Low-level types and utilities for porting Google Test to various +// platforms. They are subject to change without notice. DO NOT USE +// THEM IN USER CODE. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +// The user can define the following macros in the build script to +// control Google Test's behavior. If the user doesn't define a macro +// in this list, Google Test will define it. +// +// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) +// is/isn't available. +// GTEST_HAS_GLOBAL_STRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::string, which is different to std::string). +// GTEST_HAS_GLOBAL_WSTRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::wstring, which is different to std::wstring). +// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that +// is/isn't available. +// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't +// enabled. +// GTEST_HAS_STD_STRING - Define it to 1/0 to indicate that +// std::string does/doesn't work (Google Test can +// be used where std::string is unavailable). +// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that +// std::wstring does/doesn't work (Google Test can +// be used where std::wstring is unavailable). +// GTEST_HAS_TR1_TUPLE 1 - Define it to 1/0 to indicate tr1::tuple +// is/isn't available. + +// This header defines the following utilities: +// +// Macros indicating the current platform (defined to 1 if compiled on +// the given platform; otherwise undefined): +// GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_LINUX - Linux +// GTEST_OS_MAC - Mac OS X +// GTEST_OS_SOLARIS - Sun Solaris +// GTEST_OS_SYMBIAN - Symbian +// GTEST_OS_WINDOWS - Windows +// GTEST_OS_ZOS - z/OS +// +// Among the platforms, Cygwin, Linux, Max OS X, and Windows have the +// most stable support. Since core members of the Google Test project +// don't have access to other platforms, support for them may be less +// stable. If you notice any problems on your platform, please notify +// googletestframework@googlegroups.com (patches for fixing them are +// even more welcome!). +// +// Note that it is possible that none of the GTEST_OS_* macros are defined. +// +// Macros indicating available Google Test features (defined to 1 if +// the corresponding feature is supported; otherwise undefined): +// GTEST_HAS_COMBINE - the Combine() function (for value-parameterized +// tests) +// GTEST_HAS_DEATH_TEST - death tests +// GTEST_HAS_PARAM_TEST - value-parameterized tests +// GTEST_HAS_TYPED_TEST - typed tests +// GTEST_HAS_TYPED_TEST_P - type-parameterized tests +// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. +// GTEST_USES_SIMPLE_RE - our own simple regex is used; +// the above two are mutually exclusive. +// +// Macros for basic C++ coding: +// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. +// GTEST_ATTRIBUTE_UNUSED_ - declares that a class' instances don't have to +// be used. +// GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=. +// GTEST_MUST_USE_RESULT_ - declares that a function's result must be used. +// +// Synchronization: +// Mutex, MutexLock, ThreadLocal, GetThreadCount() +// - synchronization primitives. +// GTEST_IS_THREADSAFE - defined to 1 to indicate that the above +// synchronization primitives have real implementations +// and Google Test is thread-safe; or 0 otherwise. +// +// Template meta programming: +// is_pointer - as in TR1; needed on Symbian and IBM XL C/C++ only. +// +// Smart pointers: +// scoped_ptr - as in TR2. +// +// Regular expressions: +// RE - a simple regular expression class using the POSIX +// Extended Regular Expression syntax. Not available on +// Windows. +// +// Logging: +// GTEST_LOG_() - logs messages at the specified severity level. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. +// +// Stderr capturing: +// CaptureStderr() - starts capturing stderr. +// GetCapturedStderr() - stops capturing stderr and returns the captured +// string. +// +// Integer types: +// TypeWithSize - maps an integer to a int type. +// Int32, UInt32, Int64, UInt64, TimeInMillis +// - integers of known sizes. +// BiggestInt - the biggest signed integer type. +// +// Command-line utilities: +// GTEST_FLAG() - references a flag. +// GTEST_DECLARE_*() - declares a flag. +// GTEST_DEFINE_*() - defines a flag. +// GetArgvs() - returns the command line as a vector of strings. +// +// Environment variable utilities: +// GetEnv() - gets the value of an environment variable. +// BoolFromGTestEnv() - parses a bool environment variable. +// Int32FromGTestEnv() - parses an Int32 environment variable. +// StringFromGTestEnv() - parses a string environment variable. + +#include +#include +#include // Used for GTEST_CHECK_ + +#define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" +#define GTEST_FLAG_PREFIX_ "gtest_" +#define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" +#define GTEST_NAME_ "Google Test" +#define GTEST_PROJECT_URL_ "http://code.google.com/p/googletest/" + +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +#define GTEST_GCC_VER_ \ + (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + +// Determines the platform on which Google Test is compiled. +#ifdef __CYGWIN__ +#define GTEST_OS_CYGWIN 1 +#elif __SYMBIAN32__ +#define GTEST_OS_SYMBIAN 1 +#elif defined _MSC_VER +// TODO(kenton@google.com): GTEST_OS_WINDOWS is currently used to mean +// both "The OS is Windows" and "The compiler is MSVC". These +// meanings really should be separated in order to better support +// Windows compilers other than MSVC. +#define GTEST_OS_WINDOWS 1 +#elif defined __APPLE__ +#define GTEST_OS_MAC 1 +#elif defined __linux__ +#define GTEST_OS_LINUX 1 +#elif defined __MVS__ +#define GTEST_OS_ZOS 1 +#elif defined(__sun) && defined(__SVR4) +#define GTEST_OS_SOLARIS 1 +#endif // _MSC_VER + +#if GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC + +// On some platforms, needs someone to define size_t, and +// won't compile otherwise. We can #include it here as we already +// included , which is guaranteed to define size_t through +// . +#include // NOLINT +#define GTEST_USES_POSIX_RE 1 + +#else + +// may not be available on this platform. Use our own +// simple regex implementation instead. +#define GTEST_USES_SIMPLE_RE 1 + +#endif // GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC + +// Defines GTEST_HAS_EXCEPTIONS to 1 if exceptions are enabled, or 0 +// otherwise. + +#ifdef _MSC_VER // Compiled by MSVC? +// Assumes that exceptions are enabled by default. +#ifndef _HAS_EXCEPTIONS // MSVC uses this macro to enable exceptions. +#define _HAS_EXCEPTIONS 1 +#endif // _HAS_EXCEPTIONS +#define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +#else // The compiler is not MSVC. +// gcc defines __EXCEPTIONS to 1 iff exceptions are enabled. For +// other compilers, we assume exceptions are disabled to be +// conservative. +#if defined(__GNUC__) && __EXCEPTIONS +#define GTEST_HAS_EXCEPTIONS 1 +#else +#define GTEST_HAS_EXCEPTIONS 0 +#endif // defined(__GNUC__) && __EXCEPTIONS +#endif // _MSC_VER + +// Determines whether ::std::string and ::string are available. + +#ifndef GTEST_HAS_STD_STRING +// The user didn't tell us whether ::std::string is available, so we +// need to figure it out. The only environment that we know +// ::std::string is not available is MSVC 7.1 or lower with exceptions +// disabled. +#if defined(_MSC_VER) && (_MSC_VER < 1400) && !GTEST_HAS_EXCEPTIONS +#define GTEST_HAS_STD_STRING 0 +#else +#define GTEST_HAS_STD_STRING 1 +#endif +#endif // GTEST_HAS_STD_STRING + +#ifndef GTEST_HAS_GLOBAL_STRING +// The user didn't tell us whether ::string is available, so we need +// to figure it out. + +#define GTEST_HAS_GLOBAL_STRING 0 + +#endif // GTEST_HAS_GLOBAL_STRING + +#ifndef GTEST_HAS_STD_WSTRING +// The user didn't tell us whether ::std::wstring is available, so we need +// to figure it out. +// TODO(wan@google.com): uses autoconf to detect whether ::std::wstring +// is available. + +#if GTEST_OS_CYGWIN || GTEST_OS_SOLARIS +// Cygwin 1.5 and below doesn't support ::std::wstring. +// Cygwin 1.7 might add wstring support; this should be updated when clear. +// Solaris' libc++ doesn't support it either. +#define GTEST_HAS_STD_WSTRING 0 +#else +#define GTEST_HAS_STD_WSTRING GTEST_HAS_STD_STRING +#endif // GTEST_OS_CYGWIN || GTEST_OS_SOLARIS + +#endif // GTEST_HAS_STD_WSTRING + +#ifndef GTEST_HAS_GLOBAL_WSTRING +// The user didn't tell us whether ::wstring is available, so we need +// to figure it out. +#define GTEST_HAS_GLOBAL_WSTRING \ + (GTEST_HAS_STD_WSTRING && GTEST_HAS_GLOBAL_STRING) +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_STRING || GTEST_HAS_GLOBAL_STRING || \ + GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING +#include // NOLINT +#endif // GTEST_HAS_STD_STRING || GTEST_HAS_GLOBAL_STRING || + // GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_STRING +#include // NOLINT +#else +#include // NOLINT +#endif // GTEST_HAS_STD_STRING + +// Determines whether RTTI is available. +#ifndef GTEST_HAS_RTTI +// The user didn't tell us whether RTTI is enabled, so we need to +// figure it out. + +#ifdef _MSC_VER + +#ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif // _CPPRTTI + +#elif defined(__GNUC__) + +// Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled. +#if GTEST_GCC_VER_ >= 40302 +#ifdef __GXX_RTTI +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif // __GXX_RTTI +#else +// For gcc versions smaller than 4.3.2, we assume RTTI is enabled. +#define GTEST_HAS_RTTI 1 +#endif // GTEST_GCC_VER >= 40302 + +#else + +// Unknown compiler - assume RTTI is enabled. +#define GTEST_HAS_RTTI 1 + +#endif // _MSC_VER + +#endif // GTEST_HAS_RTTI + +// Determines whether is available. +#ifndef GTEST_HAS_PTHREAD +// The user didn't tell us, so we need to figure it out. +#define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC) +#endif // GTEST_HAS_PTHREAD + +// Determines whether tr1/tuple is available. If you have tr1/tuple +// on your platform, define GTEST_HAS_TR1_TUPLE=1 for both the Google +// Test project and your tests. If you would like Google Test to detect +// tr1/tuple on your platform automatically, please open an issue +// ticket at http://code.google.com/p/googletest. +#ifndef GTEST_HAS_TR1_TUPLE +// The user didn't tell us, so we need to figure it out. + +// GCC provides since 4.0.0. +#if defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000) +#define GTEST_HAS_TR1_TUPLE 1 +#else +#define GTEST_HAS_TR1_TUPLE 0 +#endif // __GNUC__ +#endif // GTEST_HAS_TR1_TUPLE + +// To avoid conditional compilation everywhere, we make it +// gtest-port.h's responsibility to #include the header implementing +// tr1/tuple. +#if GTEST_HAS_TR1_TUPLE +#if defined(__GNUC__) +// GCC implements tr1/tuple in the header. This does not +// conform to the TR1 spec, which requires the header to be . +#include +#else +// If the compiler is not GCC, we assume the user is using a +// spec-conforming TR1 implementation. +#include +#endif // __GNUC__ +#endif // GTEST_HAS_TR1_TUPLE + +// Determines whether clone(2) is supported. +// Usually it will only be available on Linux, excluding +// Linux on the Itanium architecture. +// Also see http://linux.die.net/man/2/clone. +#ifndef GTEST_HAS_CLONE +// The user didn't tell us, so we need to figure it out. + +#if GTEST_OS_LINUX && !defined(__ia64__) +#define GTEST_HAS_CLONE 1 +#else +#define GTEST_HAS_CLONE 0 +#endif // GTEST_OS_LINUX && !defined(__ia64__) + +#endif // GTEST_HAS_CLONE + +// Determines whether to support death tests. +// Google Test does not support death tests for VC 7.1 and earlier for +// these reasons: +// 1. std::vector does not build in VC 7.1 when exceptions are disabled. +// 2. std::string does not build in VC 7.1 when exceptions are disabled +// (this is covered by GTEST_HAS_STD_STRING guard). +// 3. abort() in a VC 7.1 application compiled as GUI in debug config +// pops up a dialog window that cannot be suppressed programmatically. +#if GTEST_HAS_STD_STRING && (GTEST_OS_LINUX || \ + GTEST_OS_MAC || \ + GTEST_OS_CYGWIN || \ + (GTEST_OS_WINDOWS && _MSC_VER >= 1400)) +#define GTEST_HAS_DEATH_TEST 1 +#include +#endif + +// Determines whether to support value-parameterized tests. + +#if defined(__GNUC__) || (_MSC_VER >= 1400) +// TODO(vladl@google.com): get the implementation rid of vector and list +// to compile on MSVC 7.1. +#define GTEST_HAS_PARAM_TEST 1 +#endif // defined(__GNUC__) || (_MSC_VER >= 1400) + +// Determines whether to support type-driven tests. + +// Typed tests need and variadic macros, which gcc and VC +// 8.0+ support. +#if defined(__GNUC__) || (_MSC_VER >= 1400) +#define GTEST_HAS_TYPED_TEST 1 +#define GTEST_HAS_TYPED_TEST_P 1 +#endif // defined(__GNUC__) || (_MSC_VER >= 1400) + +// Determines whether to support Combine(). This only makes sense when +// value-parameterized tests are enabled. +#if GTEST_HAS_PARAM_TEST && GTEST_HAS_TR1_TUPLE +#define GTEST_HAS_COMBINE 1 +#endif // GTEST_HAS_PARAM_TEST && GTEST_HAS_TR1_TUPLE + +// Determines whether the system compiler uses UTF-16 for encoding wide strings. +#define GTEST_WIDE_STRING_USES_UTF16_ \ + (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_SYMBIAN) + +// Defines some utility macros. + +// The GNU compiler emits a warning if nested "if" statements are followed by +// an "else" statement and braces are not used to explicitly disambiguate the +// "else" binding. This leads to problems with code like: +// +// if (gate) +// ASSERT_*(condition) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +#ifdef __INTEL_COMPILER +#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ +#else +#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: // NOLINT +#endif + +// Use this annotation at the end of a struct / class definition to +// prevent the compiler from optimizing away instances that are never +// used. This is useful when all interesting logic happens inside the +// c'tor and / or d'tor. Example: +// +// struct Foo { +// Foo() { ... } +// } GTEST_ATTRIBUTE_UNUSED_; +#if defined(__GNUC__) && !defined(COMPILER_ICC) +#define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +#else +#define GTEST_ATTRIBUTE_UNUSED_ +#endif + +// A macro to disallow the evil copy constructor and operator= functions +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type)\ + type(const type &);\ + void operator=(const type &) + +// Tell the compiler to warn about unused return values for functions declared +// with this macro. The macro should be used on function declarations +// following the argument list: +// +// Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_; +#if defined(__GNUC__) && (GTEST_GCC_VER_ >= 30400) && !defined(COMPILER_ICC) +#define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) +#else +#define GTEST_MUST_USE_RESULT_ +#endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC + +namespace testing { + +class Message; + +namespace internal { + +class String; + +// std::strstream is deprecated. However, we have to use it on +// Windows as std::stringstream won't compile on Windows when +// exceptions are disabled. We use std::stringstream on other +// platforms to avoid compiler warnings there. +#if GTEST_HAS_STD_STRING +typedef ::std::stringstream StrStream; +#else +typedef ::std::strstream StrStream; +#endif // GTEST_HAS_STD_STRING + +// Defines scoped_ptr. + +// This implementation of scoped_ptr is PARTIAL - it only contains +// enough stuff to satisfy Google Test's need. +template +class scoped_ptr { + public: + explicit scoped_ptr(T* p = NULL) : ptr_(p) {} + ~scoped_ptr() { reset(); } + + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + T* get() const { return ptr_; } + + T* release() { + T* const ptr = ptr_; + ptr_ = NULL; + return ptr; + } + + void reset(T* p = NULL) { + if (p != ptr_) { + if (sizeof(T) > 0) { // Makes sure T is a complete type. + delete ptr_; + } + ptr_ = p; + } + } + private: + T* ptr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(scoped_ptr); +}; + +// Defines RE. + +// A simple C++ wrapper for . It uses the POSIX Enxtended +// Regular Expression syntax. +class RE { + public: + // Constructs an RE from a string. +#if GTEST_HAS_STD_STRING + RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT +#endif // GTEST_HAS_STD_STRING + +#if GTEST_HAS_GLOBAL_STRING + RE(const ::string& regex) { Init(regex.c_str()); } // NOLINT +#endif // GTEST_HAS_GLOBAL_STRING + + RE(const char* regex) { Init(regex); } // NOLINT + ~RE(); + + // Returns the string representation of the regex. + const char* pattern() const { return pattern_; } + + // FullMatch(str, re) returns true iff regular expression re matches + // the entire str. + // PartialMatch(str, re) returns true iff regular expression re + // matches a substring of str (including str itself). + // + // TODO(wan@google.com): make FullMatch() and PartialMatch() work + // when str contains NUL characters. +#if GTEST_HAS_STD_STRING + static bool FullMatch(const ::std::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::std::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } +#endif // GTEST_HAS_STD_STRING + +#if GTEST_HAS_GLOBAL_STRING + static bool FullMatch(const ::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } +#endif // GTEST_HAS_GLOBAL_STRING + + static bool FullMatch(const char* str, const RE& re); + static bool PartialMatch(const char* str, const RE& re); + + private: + void Init(const char* regex); + + // We use a const char* instead of a string, as Google Test may be used + // where string is not available. We also do not use Google Test's own + // String type here, in order to simplify dependencies between the + // files. + const char* pattern_; + bool is_valid_; +#if GTEST_USES_POSIX_RE + regex_t full_regex_; // For FullMatch(). + regex_t partial_regex_; // For PartialMatch(). +#else // GTEST_USES_SIMPLE_RE + const char* full_pattern_; // For FullMatch(); +#endif + + GTEST_DISALLOW_COPY_AND_ASSIGN_(RE); +}; + +// Defines logging utilities: +// GTEST_LOG_() - logs messages at the specified severity level. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. + +enum GTestLogSeverity { + GTEST_INFO, + GTEST_WARNING, + GTEST_ERROR, + GTEST_FATAL +}; + +void GTestLog(GTestLogSeverity severity, const char* file, + int line, const char* msg); + +#define GTEST_LOG_(severity, msg)\ + ::testing::internal::GTestLog(\ + ::testing::internal::GTEST_##severity, __FILE__, __LINE__, \ + (::testing::Message() << (msg)).GetString().c_str()) + +inline void LogToStderr() {} +inline void FlushInfoLog() { fflush(NULL); } + +// Defines the stderr capturer: +// CaptureStderr - starts capturing stderr. +// GetCapturedStderr - stops capturing stderr and returns the captured string. + +#if GTEST_HAS_STD_STRING +void CaptureStderr(); +::std::string GetCapturedStderr(); +#endif // GTEST_HAS_STD_STRING + +#if GTEST_HAS_DEATH_TEST + +// A copy of all command line arguments. Set by InitGoogleTest(). +extern ::std::vector g_argvs; + +// GTEST_HAS_DEATH_TEST implies we have ::std::string. +const ::std::vector& GetArgvs(); + +#endif // GTEST_HAS_DEATH_TEST + +// Defines synchronization primitives. + +// A dummy implementation of synchronization primitives (mutex, lock, +// and thread-local variable). Necessary for compiling Google Test where +// mutex is not supported - using Google Test in multiple threads is not +// supported on such platforms. + +class Mutex { + public: + Mutex() {} + explicit Mutex(int /*unused*/) {} + void AssertHeld() const {} + enum { NO_CONSTRUCTOR_NEEDED_FOR_STATIC_MUTEX = 0 }; +}; + +// We cannot call it MutexLock directly as the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex*) {} // NOLINT +}; + +typedef GTestMutexLock MutexLock; + +template +class ThreadLocal { + public: + ThreadLocal() : value_() {} + explicit ThreadLocal(const T& value) : value_(value) {} + T* pointer() { return &value_; } + const T* pointer() const { return &value_; } + const T& get() const { return value_; } + void set(const T& value) { value_ = value; } + private: + T value_; +}; + +// There's no portable way to detect the number of threads, so we just +// return 0 to indicate that we cannot detect it. +inline size_t GetThreadCount() { return 0; } + +// The above synchronization primitives have dummy implementations. +// Therefore Google Test is not thread-safe. +#define GTEST_IS_THREADSAFE 0 + +#if defined(__SYMBIAN32__) || defined(__IBMCPP__) + +// Passing non-POD classes through ellipsis (...) crashes the ARM +// compiler. The Nokia Symbian and the IBM XL C/C++ compiler try to +// instantiate a copy constructor for objects passed through ellipsis +// (...), failing for uncopyable objects. We define this to indicate +// the fact. +#define GTEST_ELLIPSIS_NEEDS_COPY_ 1 + +// The Nokia Symbian and IBM XL C/C++ compilers cannot decide between +// const T& and const T* in a function template. These compilers +// _can_ decide between class template specializations for T and T*, +// so a tr1::type_traits-like is_pointer works. +#define GTEST_NEEDS_IS_POINTER_ 1 + +#endif // defined(__SYMBIAN32__) || defined(__IBMCPP__) + +template +struct bool_constant { + typedef bool_constant type; + static const bool value = bool_value; +}; +template const bool bool_constant::value; + +typedef bool_constant false_type; +typedef bool_constant true_type; + +template +struct is_pointer : public false_type {}; + +template +struct is_pointer : public true_type {}; + +#if GTEST_OS_WINDOWS +#define GTEST_PATH_SEP_ "\\" +#else +#define GTEST_PATH_SEP_ "/" +#endif // GTEST_OS_WINDOWS + +// Defines BiggestInt as the biggest signed integer type the compiler +// supports. +#if GTEST_OS_WINDOWS +typedef __int64 BiggestInt; +#else +typedef long long BiggestInt; // NOLINT +#endif // GTEST_OS_WINDOWS + +// The maximum number a BiggestInt can represent. This definition +// works no matter BiggestInt is represented in one's complement or +// two's complement. +// +// We cannot rely on numeric_limits in STL, as __int64 and long long +// are not part of standard C++ and numeric_limits doesn't need to be +// defined for them. +const BiggestInt kMaxBiggestInt = + ~(static_cast(1) << (8*sizeof(BiggestInt) - 1)); + +// This template class serves as a compile-time function from size to +// type. It maps a size in bytes to a primitive type with that +// size. e.g. +// +// TypeWithSize<4>::UInt +// +// is typedef-ed to be unsigned int (unsigned integer made up of 4 +// bytes). +// +// Such functionality should belong to STL, but I cannot find it +// there. +// +// Google Test uses this class in the implementation of floating-point +// comparison. +// +// For now it only handles UInt (unsigned int) as that's all Google Test +// needs. Other types can be easily added in the future if need +// arises. +template +class TypeWithSize { + public: + // This prevents the user from using TypeWithSize with incorrect + // values of N. + typedef void UInt; +}; + +// The specialization for size 4. +template <> +class TypeWithSize<4> { + public: + // unsigned int has size 4 in both gcc and MSVC. + // + // As base/basictypes.h doesn't compile on Windows, we cannot use + // uint32, uint64, and etc here. + typedef int Int; + typedef unsigned int UInt; +}; + +// The specialization for size 8. +template <> +class TypeWithSize<8> { + public: +#if GTEST_OS_WINDOWS + typedef __int64 Int; + typedef unsigned __int64 UInt; +#else + typedef long long Int; // NOLINT + typedef unsigned long long UInt; // NOLINT +#endif // GTEST_OS_WINDOWS +}; + +// Integer types of known sizes. +typedef TypeWithSize<4>::Int Int32; +typedef TypeWithSize<4>::UInt UInt32; +typedef TypeWithSize<8>::Int Int64; +typedef TypeWithSize<8>::UInt UInt64; +typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds. + +// Utilities for command line flags and environment variables. + +// A wrapper for getenv() that works on Linux, Windows, and Mac OS. +inline const char* GetEnv(const char* name) { +#ifdef _WIN32_WCE // We are on Windows CE. + // CE has no environment variables. + return NULL; +#elif GTEST_OS_WINDOWS // We are on Windows proper. + // MSVC 8 deprecates getenv(), so we want to suppress warning 4996 + // (deprecated function) there. +#pragma warning(push) // Saves the current warning state. +#pragma warning(disable:4996) // Temporarily disables warning 4996. + return getenv(name); +#pragma warning(pop) // Restores the warning state. +#else // We are on Linux or Mac OS. + return getenv(name); +#endif +} + +#ifdef _WIN32_WCE +// Windows CE has no C library. The abort() function is used in +// several places in Google Test. This implementation provides a reasonable +// imitation of standard behaviour. +void abort(); +#else +inline void abort() { ::abort(); } +#endif // _WIN32_WCE + +// INTERNAL IMPLEMENTATION - DO NOT USE. +// +// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition +// is not satisfied. +// Synopsys: +// GTEST_CHECK_(boolean_condition); +// or +// GTEST_CHECK_(boolean_condition) << "Additional message"; +// +// This checks the condition and if the condition is not satisfied +// it prints message about the condition violation, including the +// condition itself, plus additional message streamed into it, if any, +// and then it aborts the program. It aborts the program irrespective of +// whether it is built in the debug mode or not. +class GTestCheckProvider { + public: + GTestCheckProvider(const char* condition, const char* file, int line) { + FormatFileLocation(file, line); + ::std::cerr << " ERROR: Condition " << condition << " failed. "; + } + ~GTestCheckProvider() { + ::std::cerr << ::std::endl; + abort(); + } + void FormatFileLocation(const char* file, int line) { + if (file == NULL) + file = "unknown file"; + if (line < 0) { + ::std::cerr << file << ":"; + } else { +#if _MSC_VER + ::std::cerr << file << "(" << line << "):"; +#else + ::std::cerr << file << ":" << line << ":"; +#endif + } + } + ::std::ostream& GetStream() { return ::std::cerr; } +}; +#define GTEST_CHECK_(condition) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (condition) \ + ; \ + else \ + ::testing::internal::GTestCheckProvider(\ + #condition, __FILE__, __LINE__).GetStream() + +// Macro for referencing flags. +#define GTEST_FLAG(name) FLAGS_gtest_##name + +// Macros for declaring flags. +#define GTEST_DECLARE_bool_(name) extern bool GTEST_FLAG(name) +#define GTEST_DECLARE_int32_(name) \ + extern ::testing::internal::Int32 GTEST_FLAG(name) +#define GTEST_DECLARE_string_(name) \ + extern ::testing::internal::String GTEST_FLAG(name) + +// Macros for defining flags. +#define GTEST_DEFINE_bool_(name, default_val, doc) \ + bool GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_int32_(name, default_val, doc) \ + ::testing::internal::Int32 GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_string_(name, default_val, doc) \ + ::testing::internal::String GTEST_FLAG(name) = (default_val) + +// Parses 'str' for a 32-bit signed integer. If successful, writes the result +// to *value and returns true; otherwise leaves *value unchanged and returns +// false. +// TODO(chandlerc): Find a better way to refactor flag and environment parsing +// out of both gtest-port.cc and gtest.cc to avoid exporting this utility +// function. +bool ParseInt32(const Message& src_text, const char* str, Int32* value); + +// Parses a bool/Int32/string from the environment variable +// corresponding to the given Google Test flag. +bool BoolFromGTestEnv(const char* flag, bool default_val); +Int32 Int32FromGTestEnv(const char* flag, Int32 default_val); +const char* StringFromGTestEnv(const char* flag, const char* default_val); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-string.h b/digsby/ext/lib/include/gtest/internal/gtest-string.h new file mode 100644 index 0000000..566a6b5 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-string.h @@ -0,0 +1,335 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares the String class and functions used internally by +// Google Test. They are subject to change without notice. They should not used +// by code external to Google Test. +// +// This header file is #included by . +// It should not be #included by other files. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ + +#include +#include + +#if GTEST_HAS_GLOBAL_STRING || GTEST_HAS_STD_STRING +#include +#endif // GTEST_HAS_GLOBAL_STRING || GTEST_HAS_STD_STRING + +namespace testing { +namespace internal { + +// String - a UTF-8 string class. +// +// We cannot use std::string as Microsoft's STL implementation in +// Visual C++ 7.1 has problems when exception is disabled. There is a +// hack to work around this, but we've seen cases where the hack fails +// to work. +// +// Also, String is different from std::string in that it can represent +// both NULL and the empty string, while std::string cannot represent +// NULL. +// +// NULL and the empty string are considered different. NULL is less +// than anything (including the empty string) except itself. +// +// This class only provides minimum functionality necessary for +// implementing Google Test. We do not intend to implement a full-fledged +// string class here. +// +// Since the purpose of this class is to provide a substitute for +// std::string on platforms where it cannot be used, we define a copy +// constructor and assignment operators such that we don't need +// conditional compilation in a lot of places. +// +// In order to make the representation efficient, the d'tor of String +// is not virtual. Therefore DO NOT INHERIT FROM String. +class String { + public: + // Static utility methods + + // Returns the input if it's not NULL, otherwise returns "(null)". + // This function serves two purposes: + // + // 1. ShowCString(NULL) has type 'const char *', instead of the + // type of NULL (which is int). + // + // 2. In MSVC, streaming a null char pointer to StrStream generates + // an access violation, so we need to convert NULL to "(null)" + // before streaming it. + static inline const char* ShowCString(const char* c_str) { + return c_str ? c_str : "(null)"; + } + + // Returns the input enclosed in double quotes if it's not NULL; + // otherwise returns "(null)". For example, "\"Hello\"" is returned + // for input "Hello". + // + // This is useful for printing a C string in the syntax of a literal. + // + // Known issue: escape sequences are not handled yet. + static String ShowCStringQuoted(const char* c_str); + + // Clones a 0-terminated C string, allocating memory using new. The + // caller is responsible for deleting the return value using + // delete[]. Returns the cloned string, or NULL if the input is + // NULL. + // + // This is different from strdup() in string.h, which allocates + // memory using malloc(). + static const char* CloneCString(const char* c_str); + +#ifdef _WIN32_WCE + // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be + // able to pass strings to Win32 APIs on CE we need to convert them + // to 'Unicode', UTF-16. + + // Creates a UTF-16 wide string from the given ANSI string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the wide string, or NULL if the + // input is NULL. + // + // The wide string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static LPCWSTR AnsiToUtf16(const char* c_str); + + // Creates an ANSI string from the given wide string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the ANSI string, or NULL if the + // input is NULL. + // + // The returned string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static const char* Utf16ToAnsi(LPCWSTR utf16_str); +#endif + + // Compares two C strings. Returns true iff they have the same content. + // + // Unlike strcmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CStringEquals(const char* lhs, const char* rhs); + + // Converts a wide C string to a String using the UTF-8 encoding. + // NULL will be converted to "(null)". If an error occurred during + // the conversion, "(failed to convert from wide string)" is + // returned. + static String ShowWideCString(const wchar_t* wide_c_str); + + // Similar to ShowWideCString(), except that this function encloses + // the converted string in double quotes. + static String ShowWideCStringQuoted(const wchar_t* wide_c_str); + + // Compares two wide C strings. Returns true iff they have the same + // content. + // + // Unlike wcscmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs); + + // Compares two C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike strcasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CaseInsensitiveCStringEquals(const char* lhs, + const char* rhs); + + // Compares two wide C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. + static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs); + + // Formats a list of arguments to a String, using the same format + // spec string as for printf. + // + // We do not use the StringPrintf class as it is not universally + // available. + // + // The result is limited to 4096 characters (including the tailing + // 0). If 4096 characters are not enough to format the input, + // "" is returned. + static String Format(const char* format, ...); + + // C'tors + + // The default c'tor constructs a NULL string. + String() : c_str_(NULL) {} + + // Constructs a String by cloning a 0-terminated C string. + String(const char* c_str) : c_str_(NULL) { // NOLINT + *this = c_str; + } + + // Constructs a String by copying a given number of chars from a + // buffer. E.g. String("hello", 3) will create the string "hel". + String(const char* buffer, size_t len); + + // The copy c'tor creates a new copy of the string. The two + // String objects do not share content. + String(const String& str) : c_str_(NULL) { + *this = str; + } + + // D'tor. String is intended to be a final class, so the d'tor + // doesn't need to be virtual. + ~String() { delete[] c_str_; } + + // Allows a String to be implicitly converted to an ::std::string or + // ::string, and vice versa. Converting a String containing a NULL + // pointer to ::std::string or ::string is undefined behavior. + // Converting a ::std::string or ::string containing an embedded NUL + // character to a String will result in the prefix up to the first + // NUL character. +#if GTEST_HAS_STD_STRING + String(const ::std::string& str) : c_str_(NULL) { *this = str.c_str(); } + + operator ::std::string() const { return ::std::string(c_str_); } +#endif // GTEST_HAS_STD_STRING + +#if GTEST_HAS_GLOBAL_STRING + String(const ::string& str) : c_str_(NULL) { *this = str.c_str(); } + + operator ::string() const { return ::string(c_str_); } +#endif // GTEST_HAS_GLOBAL_STRING + + // Returns true iff this is an empty string (i.e. ""). + bool empty() const { + return (c_str_ != NULL) && (*c_str_ == '\0'); + } + + // Compares this with another String. + // Returns < 0 if this is less than rhs, 0 if this is equal to rhs, or > 0 + // if this is greater than rhs. + int Compare(const String& rhs) const; + + // Returns true iff this String equals the given C string. A NULL + // string and a non-NULL string are considered not equal. + bool operator==(const char* c_str) const { + return CStringEquals(c_str_, c_str); + } + + // Returns true iff this String is less than the given C string. A NULL + // string is considered less than "". + bool operator<(const String& rhs) const { return Compare(rhs) < 0; } + + // Returns true iff this String doesn't equal the given C string. A NULL + // string and a non-NULL string are considered not equal. + bool operator!=(const char* c_str) const { + return !CStringEquals(c_str_, c_str); + } + + // Returns true iff this String ends with the given suffix. *Any* + // String is considered to end with a NULL or empty suffix. + bool EndsWith(const char* suffix) const; + + // Returns true iff this String ends with the given suffix, not considering + // case. Any String is considered to end with a NULL or empty suffix. + bool EndsWithCaseInsensitive(const char* suffix) const; + + // Returns the length of the encapsulated string, or -1 if the + // string is NULL. + int GetLength() const { + return c_str_ ? static_cast(strlen(c_str_)) : -1; + } + + // Gets the 0-terminated C string this String object represents. + // The String object still owns the string. Therefore the caller + // should NOT delete the return value. + const char* c_str() const { return c_str_; } + + // Sets the 0-terminated C string this String object represents. + // The old string in this object is deleted, and this object will + // own a clone of the input string. This function copies only up to + // length bytes (plus a terminating null byte), or until the first + // null byte, whichever comes first. + // + // This function works even when the c_str parameter has the same + // value as that of the c_str_ field. + void Set(const char* c_str, size_t length); + + // Assigns a C string to this object. Self-assignment works. + const String& operator=(const char* c_str); + + // Assigns a String object to this object. Self-assignment works. + const String& operator=(const String &rhs) { + *this = rhs.c_str_; + return *this; + } + + private: + const char* c_str_; +}; + +// Streams a String to an ostream. +inline ::std::ostream& operator <<(::std::ostream& os, const String& str) { + // We call String::ShowCString() to convert NULL to "(null)". + // Otherwise we'll get an access violation on Windows. + return os << String::ShowCString(str.c_str()); +} + +// Gets the content of the StrStream's buffer as a String. Each '\0' +// character in the buffer is replaced with "\\0". +String StrStreamToString(StrStream* stream); + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". + +// Declared here but defined in gtest.h, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-type-util.h b/digsby/ext/lib/include/gtest/internal/gtest-type-util.h new file mode 100644 index 0000000..1ea7d18 --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-type-util.h @@ -0,0 +1,3319 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Type utilities needed for implementing typed and type-parameterized +// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently we support at most 50 types in a list, and at most 50 +// type-parameterized tests in one type-parameterized test case. +// Please contact googletestframework@googlegroups.com if you need +// more. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +#include +#include + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +#ifdef __GNUC__ +#include +#endif // __GNUC__ + +#include + +namespace testing { +namespace internal { + +// AssertyTypeEq::type is defined iff T1 and T2 are the same +// type. This can be used as a compile-time assertion to ensure that +// two types are equal. + +template +struct AssertTypeEq; + +template +struct AssertTypeEq { + typedef bool type; +}; + +// GetTypeName() returns a human-readable name of type T. +template +String GetTypeName() { +#if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +#ifdef __GNUC__ + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. + char* const readable_name = abi::__cxa_demangle(name, 0, 0, &status); + const String name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +#else + return name; +#endif // __GNUC__ + +#else + return ""; +#endif // GTEST_HAS_RTTI +} + +// A unique type used as the default value for the arguments of class +// template Types. This allows us to simulate variadic templates +// (e.g. Types, Type, and etc), which C++ doesn't +// support directly. +struct None {}; + +// The following family of struct and struct templates are used to +// represent type lists. In particular, TypesN +// represents a type list with N types (T1, T2, ..., and TN) in it. +// Except for Types0, every struct in the family has two member types: +// Head for the first type in the list, and Tail for the rest of the +// list. + +// The empty type list. +struct Types0 {}; + +// Type lists of length 1, 2, 3, and so on. + +template +struct Types1 { + typedef T1 Head; + typedef Types0 Tail; +}; +template +struct Types2 { + typedef T1 Head; + typedef Types1 Tail; +}; + +template +struct Types3 { + typedef T1 Head; + typedef Types2 Tail; +}; + +template +struct Types4 { + typedef T1 Head; + typedef Types3 Tail; +}; + +template +struct Types5 { + typedef T1 Head; + typedef Types4 Tail; +}; + +template +struct Types6 { + typedef T1 Head; + typedef Types5 Tail; +}; + +template +struct Types7 { + typedef T1 Head; + typedef Types6 Tail; +}; + +template +struct Types8 { + typedef T1 Head; + typedef Types7 Tail; +}; + +template +struct Types9 { + typedef T1 Head; + typedef Types8 Tail; +}; + +template +struct Types10 { + typedef T1 Head; + typedef Types9 Tail; +}; + +template +struct Types11 { + typedef T1 Head; + typedef Types10 Tail; +}; + +template +struct Types12 { + typedef T1 Head; + typedef Types11 Tail; +}; + +template +struct Types13 { + typedef T1 Head; + typedef Types12 Tail; +}; + +template +struct Types14 { + typedef T1 Head; + typedef Types13 Tail; +}; + +template +struct Types15 { + typedef T1 Head; + typedef Types14 Tail; +}; + +template +struct Types16 { + typedef T1 Head; + typedef Types15 Tail; +}; + +template +struct Types17 { + typedef T1 Head; + typedef Types16 Tail; +}; + +template +struct Types18 { + typedef T1 Head; + typedef Types17 Tail; +}; + +template +struct Types19 { + typedef T1 Head; + typedef Types18 Tail; +}; + +template +struct Types20 { + typedef T1 Head; + typedef Types19 Tail; +}; + +template +struct Types21 { + typedef T1 Head; + typedef Types20 Tail; +}; + +template +struct Types22 { + typedef T1 Head; + typedef Types21 Tail; +}; + +template +struct Types23 { + typedef T1 Head; + typedef Types22 Tail; +}; + +template +struct Types24 { + typedef T1 Head; + typedef Types23 Tail; +}; + +template +struct Types25 { + typedef T1 Head; + typedef Types24 Tail; +}; + +template +struct Types26 { + typedef T1 Head; + typedef Types25 Tail; +}; + +template +struct Types27 { + typedef T1 Head; + typedef Types26 Tail; +}; + +template +struct Types28 { + typedef T1 Head; + typedef Types27 Tail; +}; + +template +struct Types29 { + typedef T1 Head; + typedef Types28 Tail; +}; + +template +struct Types30 { + typedef T1 Head; + typedef Types29 Tail; +}; + +template +struct Types31 { + typedef T1 Head; + typedef Types30 Tail; +}; + +template +struct Types32 { + typedef T1 Head; + typedef Types31 Tail; +}; + +template +struct Types33 { + typedef T1 Head; + typedef Types32 Tail; +}; + +template +struct Types34 { + typedef T1 Head; + typedef Types33 Tail; +}; + +template +struct Types35 { + typedef T1 Head; + typedef Types34 Tail; +}; + +template +struct Types36 { + typedef T1 Head; + typedef Types35 Tail; +}; + +template +struct Types37 { + typedef T1 Head; + typedef Types36 Tail; +}; + +template +struct Types38 { + typedef T1 Head; + typedef Types37 Tail; +}; + +template +struct Types39 { + typedef T1 Head; + typedef Types38 Tail; +}; + +template +struct Types40 { + typedef T1 Head; + typedef Types39 Tail; +}; + +template +struct Types41 { + typedef T1 Head; + typedef Types40 Tail; +}; + +template +struct Types42 { + typedef T1 Head; + typedef Types41 Tail; +}; + +template +struct Types43 { + typedef T1 Head; + typedef Types42 Tail; +}; + +template +struct Types44 { + typedef T1 Head; + typedef Types43 Tail; +}; + +template +struct Types45 { + typedef T1 Head; + typedef Types44 Tail; +}; + +template +struct Types46 { + typedef T1 Head; + typedef Types45 Tail; +}; + +template +struct Types47 { + typedef T1 Head; + typedef Types46 Tail; +}; + +template +struct Types48 { + typedef T1 Head; + typedef Types47 Tail; +}; + +template +struct Types49 { + typedef T1 Head; + typedef Types48 Tail; +}; + +template +struct Types50 { + typedef T1 Head; + typedef Types49 Tail; +}; + + +} // namespace internal + +// We don't want to require the users to write TypesN<...> directly, +// as that would require them to count the length. Types<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Types +// will appear as Types in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Types, and Google Test will translate +// that to TypesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Types template. +template +struct Types { + typedef internal::Types50 type; +}; + +template <> +struct Types { + typedef internal::Types0 type; +}; +template +struct Types { + typedef internal::Types1 type; +}; +template +struct Types { + typedef internal::Types2 type; +}; +template +struct Types { + typedef internal::Types3 type; +}; +template +struct Types { + typedef internal::Types4 type; +}; +template +struct Types { + typedef internal::Types5 type; +}; +template +struct Types { + typedef internal::Types6 type; +}; +template +struct Types { + typedef internal::Types7 type; +}; +template +struct Types { + typedef internal::Types8 type; +}; +template +struct Types { + typedef internal::Types9 type; +}; +template +struct Types { + typedef internal::Types10 type; +}; +template +struct Types { + typedef internal::Types11 type; +}; +template +struct Types { + typedef internal::Types12 type; +}; +template +struct Types { + typedef internal::Types13 type; +}; +template +struct Types { + typedef internal::Types14 type; +}; +template +struct Types { + typedef internal::Types15 type; +}; +template +struct Types { + typedef internal::Types16 type; +}; +template +struct Types { + typedef internal::Types17 type; +}; +template +struct Types { + typedef internal::Types18 type; +}; +template +struct Types { + typedef internal::Types19 type; +}; +template +struct Types { + typedef internal::Types20 type; +}; +template +struct Types { + typedef internal::Types21 type; +}; +template +struct Types { + typedef internal::Types22 type; +}; +template +struct Types { + typedef internal::Types23 type; +}; +template +struct Types { + typedef internal::Types24 type; +}; +template +struct Types { + typedef internal::Types25 type; +}; +template +struct Types { + typedef internal::Types26 type; +}; +template +struct Types { + typedef internal::Types27 type; +}; +template +struct Types { + typedef internal::Types28 type; +}; +template +struct Types { + typedef internal::Types29 type; +}; +template +struct Types { + typedef internal::Types30 type; +}; +template +struct Types { + typedef internal::Types31 type; +}; +template +struct Types { + typedef internal::Types32 type; +}; +template +struct Types { + typedef internal::Types33 type; +}; +template +struct Types { + typedef internal::Types34 type; +}; +template +struct Types { + typedef internal::Types35 type; +}; +template +struct Types { + typedef internal::Types36 type; +}; +template +struct Types { + typedef internal::Types37 type; +}; +template +struct Types { + typedef internal::Types38 type; +}; +template +struct Types { + typedef internal::Types39 type; +}; +template +struct Types { + typedef internal::Types40 type; +}; +template +struct Types { + typedef internal::Types41 type; +}; +template +struct Types { + typedef internal::Types42 type; +}; +template +struct Types { + typedef internal::Types43 type; +}; +template +struct Types { + typedef internal::Types44 type; +}; +template +struct Types { + typedef internal::Types45 type; +}; +template +struct Types { + typedef internal::Types46 type; +}; +template +struct Types { + typedef internal::Types47 type; +}; +template +struct Types { + typedef internal::Types48 type; +}; +template +struct Types { + typedef internal::Types49 type; +}; + +namespace internal { + +#define GTEST_TEMPLATE_ template class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +#define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind::type + +// A unique struct template used as the default value for the +// arguments of class template Templates. This allows us to simulate +// variadic templates (e.g. Templates, Templates, +// and etc), which C++ doesn't support directly. +template +struct NoneT {}; + +// The following family of struct and struct templates are used to +// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except +// for Templates0, every struct in the family has two member types: +// Head for the selector of the first template in the list, and Tail +// for the rest of the list. + +// The empty template list. +struct Templates0 {}; + +// Template lists of length 1, 2, 3, and so on. + +template +struct Templates1 { + typedef TemplateSel Head; + typedef Templates0 Tail; +}; +template +struct Templates2 { + typedef TemplateSel Head; + typedef Templates1 Tail; +}; + +template +struct Templates3 { + typedef TemplateSel Head; + typedef Templates2 Tail; +}; + +template +struct Templates4 { + typedef TemplateSel Head; + typedef Templates3 Tail; +}; + +template +struct Templates5 { + typedef TemplateSel Head; + typedef Templates4 Tail; +}; + +template +struct Templates6 { + typedef TemplateSel Head; + typedef Templates5 Tail; +}; + +template +struct Templates7 { + typedef TemplateSel Head; + typedef Templates6 Tail; +}; + +template +struct Templates8 { + typedef TemplateSel Head; + typedef Templates7 Tail; +}; + +template +struct Templates9 { + typedef TemplateSel Head; + typedef Templates8 Tail; +}; + +template +struct Templates10 { + typedef TemplateSel Head; + typedef Templates9 Tail; +}; + +template +struct Templates11 { + typedef TemplateSel Head; + typedef Templates10 Tail; +}; + +template +struct Templates12 { + typedef TemplateSel Head; + typedef Templates11 Tail; +}; + +template +struct Templates13 { + typedef TemplateSel Head; + typedef Templates12 Tail; +}; + +template +struct Templates14 { + typedef TemplateSel Head; + typedef Templates13 Tail; +}; + +template +struct Templates15 { + typedef TemplateSel Head; + typedef Templates14 Tail; +}; + +template +struct Templates16 { + typedef TemplateSel Head; + typedef Templates15 Tail; +}; + +template +struct Templates17 { + typedef TemplateSel Head; + typedef Templates16 Tail; +}; + +template +struct Templates18 { + typedef TemplateSel Head; + typedef Templates17 Tail; +}; + +template +struct Templates19 { + typedef TemplateSel Head; + typedef Templates18 Tail; +}; + +template +struct Templates20 { + typedef TemplateSel Head; + typedef Templates19 Tail; +}; + +template +struct Templates21 { + typedef TemplateSel Head; + typedef Templates20 Tail; +}; + +template +struct Templates22 { + typedef TemplateSel Head; + typedef Templates21 Tail; +}; + +template +struct Templates23 { + typedef TemplateSel Head; + typedef Templates22 Tail; +}; + +template +struct Templates24 { + typedef TemplateSel Head; + typedef Templates23 Tail; +}; + +template +struct Templates25 { + typedef TemplateSel Head; + typedef Templates24 Tail; +}; + +template +struct Templates26 { + typedef TemplateSel Head; + typedef Templates25 Tail; +}; + +template +struct Templates27 { + typedef TemplateSel Head; + typedef Templates26 Tail; +}; + +template +struct Templates28 { + typedef TemplateSel Head; + typedef Templates27 Tail; +}; + +template +struct Templates29 { + typedef TemplateSel Head; + typedef Templates28 Tail; +}; + +template +struct Templates30 { + typedef TemplateSel Head; + typedef Templates29 Tail; +}; + +template +struct Templates31 { + typedef TemplateSel Head; + typedef Templates30 Tail; +}; + +template +struct Templates32 { + typedef TemplateSel Head; + typedef Templates31 Tail; +}; + +template +struct Templates33 { + typedef TemplateSel Head; + typedef Templates32 Tail; +}; + +template +struct Templates34 { + typedef TemplateSel Head; + typedef Templates33 Tail; +}; + +template +struct Templates35 { + typedef TemplateSel Head; + typedef Templates34 Tail; +}; + +template +struct Templates36 { + typedef TemplateSel Head; + typedef Templates35 Tail; +}; + +template +struct Templates37 { + typedef TemplateSel Head; + typedef Templates36 Tail; +}; + +template +struct Templates38 { + typedef TemplateSel Head; + typedef Templates37 Tail; +}; + +template +struct Templates39 { + typedef TemplateSel Head; + typedef Templates38 Tail; +}; + +template +struct Templates40 { + typedef TemplateSel Head; + typedef Templates39 Tail; +}; + +template +struct Templates41 { + typedef TemplateSel Head; + typedef Templates40 Tail; +}; + +template +struct Templates42 { + typedef TemplateSel Head; + typedef Templates41 Tail; +}; + +template +struct Templates43 { + typedef TemplateSel Head; + typedef Templates42 Tail; +}; + +template +struct Templates44 { + typedef TemplateSel Head; + typedef Templates43 Tail; +}; + +template +struct Templates45 { + typedef TemplateSel Head; + typedef Templates44 Tail; +}; + +template +struct Templates46 { + typedef TemplateSel Head; + typedef Templates45 Tail; +}; + +template +struct Templates47 { + typedef TemplateSel Head; + typedef Templates46 Tail; +}; + +template +struct Templates48 { + typedef TemplateSel Head; + typedef Templates47 Tail; +}; + +template +struct Templates49 { + typedef TemplateSel Head; + typedef Templates48 Tail; +}; + +template +struct Templates50 { + typedef TemplateSel Head; + typedef Templates49 Tail; +}; + + +// We don't want to require the users to write TemplatesN<...> directly, +// as that would require them to count the length. Templates<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Templates +// will appear as Templates in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Templates, and Google Test will translate +// that to TemplatesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Templates template. +template +struct Templates { + typedef Templates50 type; +}; + +template <> +struct Templates { + typedef Templates0 type; +}; +template +struct Templates { + typedef Templates1 type; +}; +template +struct Templates { + typedef Templates2 type; +}; +template +struct Templates { + typedef Templates3 type; +}; +template +struct Templates { + typedef Templates4 type; +}; +template +struct Templates { + typedef Templates5 type; +}; +template +struct Templates { + typedef Templates6 type; +}; +template +struct Templates { + typedef Templates7 type; +}; +template +struct Templates { + typedef Templates8 type; +}; +template +struct Templates { + typedef Templates9 type; +}; +template +struct Templates { + typedef Templates10 type; +}; +template +struct Templates { + typedef Templates11 type; +}; +template +struct Templates { + typedef Templates12 type; +}; +template +struct Templates { + typedef Templates13 type; +}; +template +struct Templates { + typedef Templates14 type; +}; +template +struct Templates { + typedef Templates15 type; +}; +template +struct Templates { + typedef Templates16 type; +}; +template +struct Templates { + typedef Templates17 type; +}; +template +struct Templates { + typedef Templates18 type; +}; +template +struct Templates { + typedef Templates19 type; +}; +template +struct Templates { + typedef Templates20 type; +}; +template +struct Templates { + typedef Templates21 type; +}; +template +struct Templates { + typedef Templates22 type; +}; +template +struct Templates { + typedef Templates23 type; +}; +template +struct Templates { + typedef Templates24 type; +}; +template +struct Templates { + typedef Templates25 type; +}; +template +struct Templates { + typedef Templates26 type; +}; +template +struct Templates { + typedef Templates27 type; +}; +template +struct Templates { + typedef Templates28 type; +}; +template +struct Templates { + typedef Templates29 type; +}; +template +struct Templates { + typedef Templates30 type; +}; +template +struct Templates { + typedef Templates31 type; +}; +template +struct Templates { + typedef Templates32 type; +}; +template +struct Templates { + typedef Templates33 type; +}; +template +struct Templates { + typedef Templates34 type; +}; +template +struct Templates { + typedef Templates35 type; +}; +template +struct Templates { + typedef Templates36 type; +}; +template +struct Templates { + typedef Templates37 type; +}; +template +struct Templates { + typedef Templates38 type; +}; +template +struct Templates { + typedef Templates39 type; +}; +template +struct Templates { + typedef Templates40 type; +}; +template +struct Templates { + typedef Templates41 type; +}; +template +struct Templates { + typedef Templates42 type; +}; +template +struct Templates { + typedef Templates43 type; +}; +template +struct Templates { + typedef Templates44 type; +}; +template +struct Templates { + typedef Templates45 type; +}; +template +struct Templates { + typedef Templates46 type; +}; +template +struct Templates { + typedef Templates47 type; +}; +template +struct Templates { + typedef Templates48 type; +}; +template +struct Templates { + typedef Templates49 type; +}; + +// The TypeList template makes it possible to use either a single type +// or a Types<...> list in TYPED_TEST_CASE() and +// INSTANTIATE_TYPED_TEST_CASE_P(). + +template +struct TypeList { typedef Types1 type; }; + +template +struct TypeList > { + typedef typename Types::type type; +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ diff --git a/digsby/ext/lib/include/gtest/internal/gtest-type-util.h.pump b/digsby/ext/lib/include/gtest/internal/gtest-type-util.h.pump new file mode 100644 index 0000000..27821ac --- /dev/null +++ b/digsby/ext/lib/include/gtest/internal/gtest-type-util.h.pump @@ -0,0 +1,287 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of type lists we want to support. +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Type utilities needed for implementing typed and type-parameterized +// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently we support at most $n types in a list, and at most $n +// type-parameterized tests in one type-parameterized test case. +// Please contact googletestframework@googlegroups.com if you need +// more. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +#include +#include + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +#ifdef __GNUC__ +#include +#endif // __GNUC__ + +#include + +namespace testing { +namespace internal { + +// AssertyTypeEq::type is defined iff T1 and T2 are the same +// type. This can be used as a compile-time assertion to ensure that +// two types are equal. + +template +struct AssertTypeEq; + +template +struct AssertTypeEq { + typedef bool type; +}; + +// GetTypeName() returns a human-readable name of type T. +template +String GetTypeName() { +#if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +#ifdef __GNUC__ + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. + char* const readable_name = abi::__cxa_demangle(name, 0, 0, &status); + const String name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +#else + return name; +#endif // __GNUC__ + +#else + return ""; +#endif // GTEST_HAS_RTTI +} + +// A unique type used as the default value for the arguments of class +// template Types. This allows us to simulate variadic templates +// (e.g. Types, Type, and etc), which C++ doesn't +// support directly. +struct None {}; + +// The following family of struct and struct templates are used to +// represent type lists. In particular, TypesN +// represents a type list with N types (T1, T2, ..., and TN) in it. +// Except for Types0, every struct in the family has two member types: +// Head for the first type in the list, and Tail for the rest of the +// list. + +// The empty type list. +struct Types0 {}; + +// Type lists of length 1, 2, 3, and so on. + +template +struct Types1 { + typedef T1 Head; + typedef Types0 Tail; +}; + +$range i 2..n + +$for i [[ +$range j 1..i +$range k 2..i +template <$for j, [[typename T$j]]> +struct Types$i { + typedef T1 Head; + typedef Types$(i-1)<$for k, [[T$k]]> Tail; +}; + + +]] + +} // namespace internal + +// We don't want to require the users to write TypesN<...> directly, +// as that would require them to count the length. Types<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Types +// will appear as Types in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Types, and Google Test will translate +// that to TypesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Types template. + +$range i 1..n +template <$for i, [[typename T$i = internal::None]]> +struct Types { + typedef internal::Types$n<$for i, [[T$i]]> type; +}; + +template <> +struct Types<$for i, [[internal::None]]> { + typedef internal::Types0 type; +}; + +$range i 1..n-1 +$for i [[ +$range j 1..i +$range k i+1..n +template <$for j, [[typename T$j]]> +struct Types<$for j, [[T$j]]$for k[[, internal::None]]> { + typedef internal::Types$i<$for j, [[T$j]]> type; +}; + +]] + +namespace internal { + +#define GTEST_TEMPLATE_ template class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +#define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind::type + +// A unique struct template used as the default value for the +// arguments of class template Templates. This allows us to simulate +// variadic templates (e.g. Templates, Templates, +// and etc), which C++ doesn't support directly. +template +struct NoneT {}; + +// The following family of struct and struct templates are used to +// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except +// for Templates0, every struct in the family has two member types: +// Head for the selector of the first template in the list, and Tail +// for the rest of the list. + +// The empty template list. +struct Templates0 {}; + +// Template lists of length 1, 2, 3, and so on. + +template +struct Templates1 { + typedef TemplateSel Head; + typedef Templates0 Tail; +}; + +$range i 2..n + +$for i [[ +$range j 1..i +$range k 2..i +template <$for j, [[GTEST_TEMPLATE_ T$j]]> +struct Templates$i { + typedef TemplateSel Head; + typedef Templates$(i-1)<$for k, [[T$k]]> Tail; +}; + + +]] + +// We don't want to require the users to write TemplatesN<...> directly, +// as that would require them to count the length. Templates<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Templates +// will appear as Templates in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Templates, and Google Test will translate +// that to TemplatesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Templates template. + +$range i 1..n +template <$for i, [[GTEST_TEMPLATE_ T$i = NoneT]]> +struct Templates { + typedef Templates$n<$for i, [[T$i]]> type; +}; + +template <> +struct Templates<$for i, [[NoneT]]> { + typedef Templates0 type; +}; + +$range i 1..n-1 +$for i [[ +$range j 1..i +$range k i+1..n +template <$for j, [[GTEST_TEMPLATE_ T$j]]> +struct Templates<$for j, [[T$j]]$for k[[, NoneT]]> { + typedef Templates$i<$for j, [[T$j]]> type; +}; + +]] + +// The TypeList template makes it possible to use either a single type +// or a Types<...> list in TYPED_TEST_CASE() and +// INSTANTIATE_TYPED_TEST_CASE_P(). + +template +struct TypeList { typedef Types1 type; }; + + +$range i 1..n +template <$for i, [[typename T$i]]> +struct TypeList > { + typedef typename Types<$for i, [[T$i]]>::type type; +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ diff --git a/digsby/ext/lib/msvc2008/Release/gtest.lib b/digsby/ext/lib/msvc2008/Release/gtest.lib new file mode 100644 index 0000000..692ff87 Binary files /dev/null and b/digsby/ext/lib/msvc2008/Release/gtest.lib differ diff --git a/digsby/ext/lib/msvc2008/Release/gtest_main.lib b/digsby/ext/lib/msvc2008/Release/gtest_main.lib new file mode 100644 index 0000000..0729110 Binary files /dev/null and b/digsby/ext/lib/msvc2008/Release/gtest_main.lib differ diff --git a/digsby/ext/msvc2008/cguitest.sln b/digsby/ext/msvc2008/cguitest.sln new file mode 100644 index 0000000..aef9ad0 --- /dev/null +++ b/digsby/ext/msvc2008/cguitest.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cguitest", "cguitest.vcproj", "{1EE2980C-DE5C-4560-8977-E274620833C8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1EE2980C-DE5C-4560-8977-E274620833C8}.Debug|Win32.ActiveCfg = Debug|Win32 + {1EE2980C-DE5C-4560-8977-E274620833C8}.Debug|Win32.Build.0 = Debug|Win32 + {1EE2980C-DE5C-4560-8977-E274620833C8}.Release|Win32.ActiveCfg = Release|Win32 + {1EE2980C-DE5C-4560-8977-E274620833C8}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/digsby/ext/msvc2008/cguitest.vcproj b/digsby/ext/msvc2008/cguitest.vcproj new file mode 100644 index 0000000..28db374 --- /dev/null +++ b/digsby/ext/msvc2008/cguitest.vcproj @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/ext/msw/Digsby PreUpdater.exe b/digsby/ext/msw/Digsby PreUpdater.exe new file mode 100644 index 0000000..1cbe59d Binary files /dev/null and b/digsby/ext/msw/Digsby PreUpdater.exe differ diff --git a/digsby/ext/msw/Digsby PreUpdater.pdb b/digsby/ext/msw/Digsby PreUpdater.pdb new file mode 100644 index 0000000..86b659f Binary files /dev/null and b/digsby/ext/msw/Digsby PreUpdater.pdb differ diff --git a/digsby/ext/msw/Digsby Updater.exe b/digsby/ext/msw/Digsby Updater.exe new file mode 100644 index 0000000..61deb39 Binary files /dev/null and b/digsby/ext/msw/Digsby Updater.exe differ diff --git a/digsby/ext/msw/Digsby Updater.pdb b/digsby/ext/msw/Digsby Updater.pdb new file mode 100644 index 0000000..8ebf609 Binary files /dev/null and b/digsby/ext/msw/Digsby Updater.pdb differ diff --git a/digsby/ext/msw/DigsbyLauncher.exe b/digsby/ext/msw/DigsbyLauncher.exe new file mode 100644 index 0000000..f9cd7ee Binary files /dev/null and b/digsby/ext/msw/DigsbyLauncher.exe differ diff --git a/digsby/ext/msw/DigsbyLauncher.pdb b/digsby/ext/msw/DigsbyLauncher.pdb new file mode 100644 index 0000000..133360e Binary files /dev/null and b/digsby/ext/msw/DigsbyLauncher.pdb differ diff --git a/digsby/ext/src/Animation/Animation.cpp b/digsby/ext/src/Animation/Animation.cpp new file mode 100644 index 0000000..1cfe8cb --- /dev/null +++ b/digsby/ext/src/Animation/Animation.cpp @@ -0,0 +1,320 @@ +#include "Animation.h" +#include "Layer.h" +#include "TextLayer.h" +#include "TimingFunction.h" +#include "Transaction.h" + + +MediaTiming::MediaTiming() + : m_beginTime(0) + , m_autoreverses(false) + , m_duration(0.0) + , m_speed(1.0f) + , m_effectiveSpeed(1.0f) + , m_timeOffset(0.0) + , m_repeatCount(0.0f) + , m_repeatDuration(0.0f) + , m_fillMode(FillModeRemoved) +{ +} + +static TimingFunction* standardTimingFunction(StandardTimingFunction timingFunction) +{ + switch (timingFunction) + { + case TimingFunctionLinear: + return &LinearTimingFunction::ms_instance; + case TimingFunctionEaseIn: + return &EaseInTimingFunction::ms_instance; + case TimingFunctionEaseOut: + return &EaseOutTimingFunction::ms_instance; + case TimingFunctionEaseInEaseOut: + return &EaseInEaseOutTimingFunction::ms_instance; + default: + ANIM_ASSERT(false); + } +} + +void Animation::setTimingFunction(StandardTimingFunction timingFunction) +{ + setTimingFunction(standardTimingFunction(timingFunction)); +} + +void Animation::setTimingFunction(TimingFunction* timingFunction) +{ + m_timingFunction = timingFunction; +} + +void MediaTiming::setDuration(Time duration) +{ + m_duration = duration; +} + +Animation::Animation() + : m_removedOnCompletion(true) + , m_timingFunction(&EaseInEaseOutTimingFunction::ms_instance) +{ + gs_animations.push_back(this); + m_startedTime = GetCurrentTimeSeconds(); +} + +vector Animation::gs_animations; + +void Animation::tickAll(Time timeMS) +{ + vector animations(gs_animations); + vector::iterator i; + for (i = animations.begin(); i != animations.end(); i++) + (*i)->tick(timeMS); +} + +Animation::~Animation() +{ + vector::iterator i; + for (i = gs_animations.begin(); i != gs_animations.end(); i++) { + if (*i == this) { + gs_animations.erase(i); + return; + } + } + + ANIM_ASSERT(false); +} + +void Animation::setSpeed(float speed) +{ + m_effectiveSpeed = speed * m_layer->effectiveSpeed(); + m_speed = speed; +} + +void Animation::setLayer(Layer* layer) +{ + m_layer = layer; +} + +template +class LinearAnimator : public Animator +{ +public: + LinearAnimator(Layer* layer, const T& a, const T& b) + : Animator(layer) + , m_from(a) + , m_to(b) + {} + + T interpolate(double delta) + { + return m_from + (m_to - m_from) * delta; + } + +protected: + T m_from; + T m_to; +}; + +class PositionAnimator : public LinearAnimator +{ +public: + PositionAnimator(Layer* layer, const Point& a, const Point& b) + : LinearAnimator(layer, a, b) + {} + + virtual void apply(double delta) + { + layer()->_setPosition(interpolate(delta)); + } +}; + +class ColorAnimator : public Animator +{ +public: + ColorAnimator(Layer* layer, const Color& a, const Color& b) + : Animator(layer) + , m_from(a) + , m_to(b) + {} + + Color interpolate(double delta) + { + return Color(m_from.Red() + (m_to.Red() - m_from.Red()) * delta, + m_from.Green() + (m_to.Green() - m_from.Green()) * delta, + m_from.Blue() + (m_to.Blue() - m_from.Blue()) * delta, + m_from.Alpha() + (m_to.Alpha() - m_from.Alpha()) * delta); + } + + virtual void apply(double delta) = 0; +protected: + Color m_from; + Color m_to; +}; + +class BackgroundColorAnimator : public ColorAnimator +{ +public: + BackgroundColorAnimator(Layer* layer, const Color& a, const Color& b) + : ColorAnimator(layer, a, b) + {} + + virtual void apply(double delta) + { + layer()->_setBackgroundColor(interpolate(delta)); + } +}; + +#include + +class OpacityAnimator : public LinearAnimator +{ +public: + OpacityAnimator(Layer* layer, float a, float b) + : LinearAnimator(layer, a, b) + {} + + virtual void apply(double delta) + { + layer()->_setOpacity(interpolate(delta)); + } +}; + +Animation* Animation::defaultAnimation(AnimationKeyPath animationKeyPath, Layer* layer) +{ + PropertyAnimation* animation = PropertyAnimation::animationForKeyPath(animationKeyPath); + + Time duration = Transaction::animationDuration(); + if (duration == 0.0) + duration = Animation::defaultDuration(animationKeyPath); + + animation->setDuration(duration); + + animation->createAnimator(animationKeyPath, layer); + + // may remove the old animation + layer->addAnimation(animation, animationKeyPath); + + return animation; +} + +Time Animation::defaultDuration(AnimationKeyPath animationType) +{ + return .50; + + /* + switch (animationType) + { + default: + return .25; + } + */ +} + +void PropertyAnimation::createAnimator(AnimationKeyPath keyPath, Layer* layer) +{ + Layer* pLayer = layer->presentationLayer(); + + switch (keyPath) + { + case AnimatePosition: + m_animator = new PositionAnimator(pLayer, pLayer->position(), layer->position()); + break; + case AnimateOpacity: + m_animator = new OpacityAnimator(pLayer, pLayer->opacity(), layer->opacity()); + break; + default: + ANIM_ASSERT(false); + } +} + + +PropertyAnimation::~PropertyAnimation() +{ + if (m_animator) + delete m_animator; +} + +PropertyAnimation* PropertyAnimation::animationForKeyPath(AnimationKeyPath keyPath) +{ + return new PropertyAnimation(keyPath); +} + +PropertyAnimation::PropertyAnimation(AnimationKeyPath keyPath) + : m_additive(false) + , m_cumulative(false) + , m_keyPath(keyPath) + , m_animator(0) +{ +} + +// direction the animation is currently playing (if the autoreverses +// property is true, it may be going backwards +enum Direction +{ + DirectionForwards, + DirectionBackwards, +}; + +Time Animation::absoluteDuration() const +{ + return duration() / m_effectiveSpeed / m_layer->effectiveSpeed(); +} + +Time Animation::absoluteStart() const +{ + // TODO: transform by beginTime and timeOffset properties + return m_startedTime; +} + +void Animation::tick(Time time) +{ + bool deleteAnimation = false; + double duration = absoluteDuration(); + Direction direction = DirectionForwards; + + if (autoreverses()) + duration *= 2; + + double start = absoluteStart(); + + float repeated = (time - start) / duration; + float repcount = repeatCount(); + if (repcount == 0.0f) + repcount = 1.0f; + + double delta; + + if (repeated < 0.0f) { + // TODO: obey FillMode + delta = 0.0; + } else if (repeated > repcount) { + // TODO: obey FillMode + delta = 1.0; + deleteAnimation = true; + } else { + // find out if we are currently going backwards + if (autoreverses()) + if (int(repeated / absoluteDuration()) % 2 == 0) + direction = DirectionBackwards; + + delta = ((time - start) / absoluteDuration()); + + if (autoreverses()) + delta = 1.0 - delta; + + delta = m_timingFunction->interpolate(delta); + } + + ANIM_ASSERT(delta >= 0.0); + ANIM_ASSERT(delta <= 1.0); + + applyDeltaToLayer(delta); + + // TODO: this deletes 'this'. is that ok? + if (deleteAnimation) + layer()->removeAnimation(this); +} + + +void PropertyAnimation::applyDeltaToLayer(double delta) +{ + m_animator->apply(delta); +} + diff --git a/digsby/ext/src/Animation/Animation.h b/digsby/ext/src/Animation/Animation.h new file mode 100644 index 0000000..e45d6f0 --- /dev/null +++ b/digsby/ext/src/Animation/Animation.h @@ -0,0 +1,235 @@ +#ifndef __CGUI_ANIMATION_H__ +#define __CGUI_ANIMATION_H__ + +#include +using std::vector; + +#define ANIM_ASSERT(x) \ + do { if (!(x)) DebugBreak(); } while(0); + +#include "AnimationPlatform.h" +#include "TimingFunction.h" + +enum FillMode +{ + FillModeRemoved, + FillModeForwards, + FillModeBackwards, + FillModeBoth +}; + +class Layer; +class Transaction; + +class MediaTiming +{ +public: + bool autoreverses() const { return m_autoreverses; } + void setAutoreverses(bool autoreverses); + + Time duration() const { return m_duration; } + void setDuration(Time duration); + + Time beginTime() const { return m_beginTime; } + void setBeginTime(Time beginTime); + + Time timeOffset() const { return m_timeOffset; } + void setTimeOffset(Time timeOffset); + + float speed() const { return m_speed; } + float effectiveSpeed() const { return m_effectiveSpeed; } + + float repeatCount() const { return m_repeatCount; } + void setRepeatCount(float repeatCount); + + float repeatDuration() const { return m_repeatDuration; } + void setRepeatDuration(float repeatDuration); + + FillMode fillMode() const { return m_fillMode; } + void setFillMode(FillMode fillMode); + +protected: + MediaTiming(); + + FillMode m_fillMode; + Time m_duration; + Time m_beginTime; + Time m_timeOffset; + float m_speed; + float m_effectiveSpeed; + float m_repeatCount; + float m_repeatDuration; + bool m_autoreverses:1; +}; + +// autoResizingMask property +enum AutoResizeMode +{ + LayerHeightSizable = 1<<0, + LayerWidthSizable = 1<<1, + LayerMinXMargin = 1<<2, + LayerMaxXMargin = 1<<2, + LayerMinYMargin = 1<<3, + LayerMaxYMargin = 1<<4 +}; + +// contentsGravity property +enum Gravity +{ + GravityTopLeft = 1<<0, + GravityTop = 1<<1, + GravityTopRight = 1<<2, + GravityLeft = 1<<3, + GravityCenter = 1<<4, + GravityRight = 1<<5, + GravityBottomLeft = 1<<6, + GravityBottom = 1<<7, + GravityBottomRight = 1<<8, + GravityResize = 1<<9, + GravityResizeAspect = 1<<10 +}; + +enum AnimationKeyPath +{ + AnimatePosition, + AnimateForegroundColor, + AnimateOpacity +}; + + +class Animation : public MediaTiming +{ +public: + void setTimingFunction(StandardTimingFunction timingFunction); + void setTimingFunction(TimingFunction* timingFunction); + void setSpeed(float speed); + TimingFunction* getTimingFunction() const { return m_timingFunction; } + + static Time defaultDuration(AnimationKeyPath animationType); + + bool removedOnCompletion() const { return m_removedOnCompletion; } + + void setLayer(Layer* layer); + Layer* layer() const { return m_layer; } + ~Animation(); + + static void tickAll(Time time); + +protected: + friend class Transaction; + + Animation(); + + static Animation* defaultAnimation(AnimationKeyPath animationKeyPath, Layer* layer); + TimingFunction* m_timingFunction; + + virtual void applyDeltaToLayer(double delta) = 0; + + void tick(Time time); + + Time absoluteDuration() const; + Time absoluteStart() const; + + // + + bool m_removedOnCompletion:1; + Time m_startedTime; + Layer* m_layer; + + static vector gs_animations; +}; + +// a virtual subclass exists for each AnimationType +class Animator +{ +public: + Animator(Layer* layer) + : m_layer(layer) + {} + + Layer* layer() const { return m_layer; } + + virtual void apply(double delta) = 0; + +protected: + Layer* m_layer; +}; + + +class PropertyAnimation : public Animation +{ +public: + virtual ~PropertyAnimation(); + + static PropertyAnimation* animationForKeyPath(AnimationKeyPath keyPath); + + bool setAdditive(bool additive); + bool additive() const { return m_additive; } + + void setCumulative(bool cumulative); + bool cumulative() const { return m_cumulative; } + + AnimationKeyPath keyPath() const { return m_keyPath; } + +protected: + friend class Animation; + + virtual void applyDeltaToLayer(double delta); + PropertyAnimation(AnimationKeyPath keyPath); + void createAnimator(AnimationKeyPath keyPath, Layer* layer); + + AnimationKeyPath m_keyPath; + bool m_additive:1; + bool m_cumulative:1; + + Layer* m_layer; + + Animator* m_animator; +}; + +class BasicAnimation : public Animation +{ +public: + void setRepeatCount(size_t repeatCount); + size_t getRepeatCount() const { return m_repeatCount; } + + void setFromValue(double value); + double getFromValue() const { return m_fromValue; } + + void setToValue(double value); + double getToValue() const { return m_toValue; } + +protected: + size_t m_repeatCount; + + double m_fromValue; + double m_toValue; +}; + +enum CalculationMode +{ + AnimationLinear, + AnimationDiscrete, + AnimationPaced, +}; + +class KeyframeAnimation +{ +public: + void setCalculationMode(CalculationMode calculationMode); + CalculationMode calculationMode() const { return m_calculationMode; } +protected: + CalculationMode m_calculationMode; +}; + +class Transition +{ +}; + +class AnimationGroup +{ +}; + + +#endif // __CGUI_ANIMATION_H__ + diff --git a/digsby/ext/src/Animation/EventLoop.cpp b/digsby/ext/src/Animation/EventLoop.cpp new file mode 100644 index 0000000..e36186d --- /dev/null +++ b/digsby/ext/src/Animation/EventLoop.cpp @@ -0,0 +1,12 @@ +#include "EventLoop.h" +#include "Transaction.h" + +void processEvents() +{ + // commit all pending transactions + Transaction::commitAll(); + + // tick all animations + Animation::tickAll(GetCurrentTimeSeconds()); +} + diff --git a/digsby/ext/src/Animation/EventLoop.h b/digsby/ext/src/Animation/EventLoop.h new file mode 100644 index 0000000..cec2b46 --- /dev/null +++ b/digsby/ext/src/Animation/EventLoop.h @@ -0,0 +1,6 @@ +#ifndef EventLoop_h +#define EventLoop_h + +void processEvents(); + +#endif // EventLoop_h diff --git a/digsby/ext/src/Animation/Interpolation.cpp b/digsby/ext/src/Animation/Interpolation.cpp new file mode 100644 index 0000000..47bce41 --- /dev/null +++ b/digsby/ext/src/Animation/Interpolation.cpp @@ -0,0 +1 @@ +#include "Interpolation.h" diff --git a/digsby/ext/src/Animation/Interpolation.h b/digsby/ext/src/Animation/Interpolation.h new file mode 100644 index 0000000..c4e2823 --- /dev/null +++ b/digsby/ext/src/Animation/Interpolation.h @@ -0,0 +1,79 @@ +#ifndef _CGUI_INTERPOLATION_H_ +#define _CGUI_INTERPOLATION_H_ + +#include + +#include +using std::vector; + +/* + things to interpolate + - numbers + - colors + - points + - rectangles + - images + + interpolation methods + - linear + - polynomial, etc. + - strobe +*/ + +static wxColour interpolate(wxColour c1, wxColour c2, double factor) +{ + return wxColour( + (c2.Red() - c1.Red()) * factor + c1.Red(), + (c2.Green() - c1.Green()) * factor + c1.Green(), + (c2.Blue() - c1.Blue()) * factor + c1.Blue(), + (c2.Alpha() - c1.Alpha()) * factor + c1.Alpha() + ); +} + +static double interpolate(double d1, double d2, double factor) +{ + return (d2 - d1) * factor + d1; +} + +template +class Fader : Interpolator +{ +}; + +template +class SineWave : Interpolator +{ +public: + + SineWave(T peak, T valley, double period) + : m_peak(peak) + , m_valley(valley) + , m_period(period) + { + } + + T peak() const { return peak; } + T valley() const { return m_valley; } + + double period() const { return m_period; } + +protected: + T peak; + T valley; + double m_period; +}; + +/* +usage: + +// oscillate between red and blue every 1.5 seconds. + +vector colors; +colors.push_back(wxRED); +colors.push_back(wxBLUE); + +m_colour = new SineWaveInterpolator(colors, 1.5); +*/ + +#endif // _CGUI_INTERPOLATION_H_ + diff --git a/digsby/ext/src/Animation/Layer.cpp b/digsby/ext/src/Animation/Layer.cpp new file mode 100644 index 0000000..8fa6a32 --- /dev/null +++ b/digsby/ext/src/Animation/Layer.cpp @@ -0,0 +1,331 @@ +#include "Layer.h" +#include "Transaction.h" +#include "WindowTarget.h" + +// this constructor creates a model layer +Layer::Layer(WindowTarget* windowTarget /* = 0*/) +{ + _initModel(); + + modelImpl()->m_windowTarget = windowTarget; + if (windowTarget) + windowTarget->setRootLayer(this); +} + +// this constructor creates a presentation layer +Layer::Layer(Layer* modelLayer) +{ + _initPresentation(modelLayer); +} + +void Layer::_initModel() +{ + m_isModel = true; + m_impl = new ModelLayerImpl(); + m_siblingLayer = createPresentationLayer(this); + _init(); +} + +void Layer::_initPresentation(Layer* modelLayer) +{ + m_isModel = false; + m_impl = new PresentationLayerImpl(); + m_siblingLayer = modelLayer; + _init(); +} + +void Layer::_init() +{ + m_contents = 0; + m_autoresizingMask = LayerHeightSizable; + m_masksToBounds = false; + m_opacity = 1.0f; + m_speed = 1.0f; + m_effectiveSpeed = 1.0f; + m_superLayer = 0; + + cairo_matrix_init_identity(&m_transform); +} + +Layer::~Layer() +{ + delete m_impl; +} + +Point Layer::position() const +{ + Point p; + cairo_matrix_transform_point(&m_transform, &p.x, &p.y); + return p; +} + +void Layer::setSpeed(float speed) +{ + float parentMultiplier = superlayer() ? superlayer()->effectiveSpeed() : 1.0f; + m_effectiveSpeed = speed * parentMultiplier; + m_speed = speed; + + // update effective speeds of all animations + AnimationMap::iterator i; + for (i = animationMap()->begin(); i != animationMap()->end(); i++) + i->second->setSpeed(i->second->speed()); + + // update effective speeds of all children (and their children...) + for (size_t j = 0; j < m_sublayers.size(); ++j) { + Layer* sublayer = subLayer(j); + sublayer->setSpeed(sublayer->speed()); + } +} + +void Layer::addSublayer(Layer* sublayer) +{ + ANIM_ASSERT(isModel()); + + // need to hook up both model and presentation layers + Layer* pSubLayer = sublayer->presentationLayer(); + + sublayer->m_superLayer = this; + pSubLayer->m_superLayer = m_superLayer ? m_superLayer->presentationLayer() : 0; + + m_sublayers.push_back(sublayer); + presentationLayer()->m_sublayers.push_back(pSubLayer); +} + +void Layer::drawInContext(GraphicsContext* context) +{ + ANIM_ASSERT(isModel()); + presentationLayer()->_drawInContext(context); +} + +void Layer::addAnimation(Animation* animation, AnimationKeyPath key) +{ + ANIM_ASSERT(isModel()); + AnimationMap::iterator i = animationMap()->find(key); + + if (i != animationMap()->end()) + delete i->second; + + (*animationMap())[key] = animation; + + animation->setLayer(this); +} + +void Layer::removeAnimation(Animation* animation) +{ + ANIM_ASSERT(isModel()); + AnimationMap::iterator i; + for (i = animationMap()->begin(); i != animationMap()->end(); i++) { + if (i->second == animation) { + delete i->second; + animationMap()->erase(i); + break; + } + } +} + +bool Layer::removeAnimationForKey(AnimationKeyPath key) +{ + ANIM_ASSERT(isModel()); + AnimationMap::iterator i = animationMap()->find(key); + if (i != animationMap()->end()) { + delete i->second; + animationMap()->erase(i); + } +} + +void Layer::removeAllAnimations() +{ + ANIM_ASSERT(isModel()); + AnimationMap::iterator i; + for (i = animationMap()->begin(); i != animationMap()->end(); i++) + delete i->second; + + animationMap()->clear(); +} + +void Layer::setPosition(const Point& point) +{ + ANIM_ASSERT(isModel()); + ImplicitTransaction t(this, AnimatePosition); + m_transform.x0 = point.x; + m_transform.y0 = point.y; +} + +void Layer::setOpacity(float opacity) +{ + ANIM_ASSERT(isModel()); + ImplicitTransaction(this, AnimateOpacity); + m_opacity = opacity; +} + +void Layer::setContents(Bitmap contents) +{ + // TODO: animatable + + if (m_contents) + cairo_surface_destroy(m_contents); + + m_contents = contents; + + if (m_contents) + cairo_surface_reference(m_contents); +} + +/////// +// Presentation layer implementation +////// + + +void Layer::paintBackingStore(GraphicsContext* context) +{ + ANIM_ASSERT(isPresentation()); + cairo_surface_t* surface = backingStore(); + cairo_set_source_surface(context, surface, 0, 0); + cairo_paint_with_alpha(context, m_opacity); +} + +void Layer::_renderContentAndChildren(GraphicsContext* context) +{ + renderContent(context); + + for (size_t i = 0; i < numLayers(); ++i) + subLayer(i)->_drawInContext(context); +} + +void Layer::_drawInContext(GraphicsContext* context) +{ + ANIM_ASSERT(isPresentation()); + cairo_save(context); + + if (0 && !cacheContents()) { + cairo_transform(context, &m_transform); + _renderContentAndChildren(context); + } else { + if (1 || invalidated() || !backingStoreValid()) { + cairo_push_group(context); + _renderContentAndChildren(context); + + if (backingStore()) + cairo_surface_destroy(backingStore()); + + cairo_surface_t* surface = cairo_get_group_target(context); + cairo_surface_reference(surface); + setBackingStore(surface); + + cairo_pattern_t* pattern = cairo_pop_group(context); + cairo_pattern_destroy(pattern); + + presentationImpl()->m_invalidated = false; + } + + cairo_transform(context, &m_transform); + paintBackingStore(context); + } + + cairo_restore(context); +} + +static void render_empty_layer_content(GraphicsContext* cr) +{ + cairo_set_line_width (cr, 15.0); + cairo_move_to (cr, 76.8, 84.48); + cairo_rel_line_to (cr, 51.2, -51.2); + cairo_rel_line_to (cr, 51.2, 51.2); + cairo_stroke (cr); +} + +static void GraphicsContext_DrawBitmap(GraphicsContext* cr, Bitmap contents, double x, double y, double w, double h) +{ + cairo_rectangle(cr, x, y, w, h); + cairo_fill(cr); +} + +static void GraphicsContext_DrawBitmap(GraphicsContext* cr, Bitmap contents, double x, double y) +{ + cairo_set_source_surface(cr, contents, 0, 0); + cairo_surface_t* image = cairo_win32_surface_get_image(contents); + int width = cairo_image_surface_get_width(image); + int height = cairo_image_surface_get_height(image); + + GraphicsContext_DrawBitmap(cr, contents, x, y, width, height); +} + +static void Transform_SetTranslation(Transform& t, double x, double y) +{ + t.x0 = x; + t.y0 = y; +} + +static void Transform_SetTranslation(Transform& t, const Point& p) +{ + Transform_SetTranslation(t, p.x, p.y); +} + + +void Layer::renderContent(GraphicsContext* cr) +{ + ANIM_ASSERT(isPresentation()); + + cairo_surface_t* contents = modelLayer()->contents(); + + if (contents) { + GraphicsContext_DrawBitmap(cr, contents, 0, 0); + } else { + //render_empty_layer_content(cr); + } +} + +void Layer::_setPosition(const Point& point) +{ + ANIM_ASSERT(isPresentation()); + Transform_SetTranslation(m_transform, point); + invalidate(); +} + +void Layer::_setOpacity(float opacity) +{ + m_opacity = opacity; + invalidate(); +} + +void Layer::_setBackgroundColor(const Color& color) +{ + m_backgroundColor = color; + invalidate(); +} + +void Layer::invalidate() +{ + ANIM_ASSERT(isPresentation()); + presentationImpl()->m_invalidated = true; + Layer* l = superlayer(); + if (l) { + ANIM_ASSERT(l != this); + l->invalidate(); + } else { + Layer* m = modelLayer(); + ANIM_ASSERT(!m->superlayer()); + + WindowTarget* windowTarget = m->windowTarget(); + ANIM_ASSERT(windowTarget); + + windowTarget->invalidate(); + } +} + +// debug helpers + +void print_transform(FILE *fp, const Transform& t) +{ + fprintf(fp, " %3.1f %3.1f\n %3.1f %3.1f\n %3.1f %3.1f\n", t.xx, t.yx, t.xy, t.yy, t.x0, t.y0); +} + +void print_layers(FILE* fp, Layer* layer, int indent) +{ + fprintf(fp, "Layer at %p with contents(%p):\n", layer, layer->contents()); + print_transform(fp, layer->transform()); + + for (size_t i = 0; i < layer->numLayers(); ++i) + print_layers(fp, layer->subLayer(i), indent + 4); +} + diff --git a/digsby/ext/src/Animation/Layer.h b/digsby/ext/src/Animation/Layer.h new file mode 100644 index 0000000..1d8cbcd --- /dev/null +++ b/digsby/ext/src/Animation/Layer.h @@ -0,0 +1,205 @@ +#ifndef Animation_Layer_h +#define Animation_Layer_h + +#include "Animation.h" + +class Layer; +class WindowTarget; + +// TODO: hash/eq functions not from WX +typedef sparse_hash_map AnimationMap; + +class LayerImpl +{ +public: + virtual ~LayerImpl() {} +}; + +class PresentationLayerImpl : public LayerImpl +{ +public: + PresentationLayerImpl() + : m_invalidated(true) + , m_cacheContents(true) + , m_backingStore(0) + {} + + virtual ~PresentationLayerImpl() + { + } + + Bitmap m_backingStore; + bool m_invalidated:1; + bool m_cacheContents:1; +}; + +class ModelLayerImpl : public LayerImpl +{ +public: + ModelLayerImpl() + : m_windowTarget(0) + {} + + virtual ~ModelLayerImpl() + {} + + AnimationMap m_animationMap; + WindowTarget* m_windowTarget; +}; + +class Layer +{ +public: + Layer(Layer*); + Layer(WindowTarget* windowTarget = 0); + virtual ~Layer(); + + Point position() const; + + void setSpeed(float speed); + float speed() const { return m_speed; } + float effectiveSpeed() const { return m_effectiveSpeed; } + + Rect bounds() const; + void setBounds(const Rect& bounds); + + Point anchorPoint() const; + void setAnchorPoint(const Point& anchorPoint); + + void setZPosition(float zPosition); + float zPosition() const { return m_zPosition; } + + const Transform& transform() const { return m_transform; } + + void setBackgroundColor(const Color& backgroundColor); + Color backgroundColor() const { return m_backgroundColor; } + + void setBorderColor(const Color& borderColor); + Color borderColor() const { return m_borderColor; } + + void setAutoresizingMask(int autoresizingMask); + int autoresizingMask() const { return m_autoresizingMask; } + + void setMasksToBounds(bool masksToBounds); + bool masksToBounds() const { return m_masksToBounds; } + + void setMask(Layer* layer); + Layer* mask() const { return m_mask; } + + void setContents(Bitmap bitmapContents); + Bitmap contents() const { return m_contents; } + + float opacity() const { return m_opacity; } + + void setContentsGravity(int contentsGravity); + int contentsGravity() const { return m_contentsGravity; } + + void setName(const String& name); + String name() const { return m_name; } + + Layer* hitTest(const Point& point) const; + + void drawInContext(GraphicsContext* context); + + Layer* presentationLayer() { return isModel() ? m_siblingLayer : 0; } + Layer* modelLayer() const { return isModel() ? 0 : m_siblingLayer; } + + Layer* subLayer(size_t index) const { + ANIM_ASSERT(index < m_sublayers.size()); + return m_sublayers[index]; + } + + WindowTarget* windowTarget() const { + ANIM_ASSERT(isModel()); + return modelImpl()->m_windowTarget; + } + + Layer* superlayer() const { return m_superLayer; } + size_t numLayers() const { return m_sublayers.size(); } + + bool isModel() const { return m_isModel; } + bool isPresentation() const { return !m_isModel; } + ModelLayerImpl* modelImpl() const { return (ModelLayerImpl*)m_impl; } + PresentationLayerImpl* presentationImpl() const { return (PresentationLayerImpl*)m_impl; } + + ///// Model layer specific methods + + void addAnimation(Animation* animation, AnimationKeyPath key); + bool removeAnimationForKey(AnimationKeyPath key); + void removeAllAnimations(); + void removeAnimation(Animation* animation); + + void addSublayer(Layer* sublayer); + void insertSublayer(Layer* sublayer, size_t index); + + void setPosition(const Point& position); + void setOpacity(float opacity); + + ///// Presentation layer specific methods + + void _drawInContext(GraphicsContext* context); + void paintBackingStore(GraphicsContext* context); + + bool backingStoreValid() const { return presentationImpl()->m_backingStore != 0; } + Bitmap backingStore() const { return presentationImpl()->m_backingStore; } + + bool invalidated() const { return presentationImpl()->m_invalidated; } + void invalidate(); + + void _setPosition(const Point& position); + void _setOpacity(float opacity); + void _setBackgroundColor(const Color& color); + +protected: + virtual Layer* createPresentationLayer(Layer* modelLayer) { return new Layer(modelLayer); } + virtual void _init(); + void _initModel(); + void _initPresentation(Layer* modelLayer); + + // Model only + AnimationMap* animationMap() const { return &modelImpl()->m_animationMap; } + + // Presentation only + bool cacheContents() const { return presentationImpl()->m_cacheContents; } + void setBackingStore(Bitmap bitmap) { presentationImpl()->m_backingStore = bitmap; } + virtual void _renderContentAndChildren(GraphicsContext*); + virtual void renderContent(GraphicsContext* context); + + Layer* m_superLayer; + Layer* m_siblingLayer; + Layer* m_mask; + + String m_name; + Bitmap m_contents; + + Transform m_transform; + + LayerImpl* m_impl; + + int m_autoresizingMask; + int m_contentsGravity; + float m_zPosition; + float m_opacity; + + Color m_borderColor; + Color m_backgroundColor; + + unsigned short int m_borderWidth; + + vector m_sublayers; + + float m_speed; + float m_effectiveSpeed; + + bool m_masksToBounds:1; + bool m_isModel:1; + +private: + Layer(const Layer&); +}; + +void print_transform(FILE *fp, const Transform& t); +void print_layers(FILE* fp, Layer* layer, int indent = 0); + +#endif // Animation_Layer_h + diff --git a/digsby/ext/src/Animation/Platform/AnimationPlatform.h b/digsby/ext/src/Animation/Platform/AnimationPlatform.h new file mode 100644 index 0000000..d9dc127 --- /dev/null +++ b/digsby/ext/src/Animation/Platform/AnimationPlatform.h @@ -0,0 +1,6 @@ +#if 1// ANIMATION_PLATFORM_WX +#include "AnimationPlatformWx.h" +#elif +#error "Animation platform is not supported" +#endif + diff --git a/digsby/ext/src/Animation/Platform/WindowTarget.h b/digsby/ext/src/Animation/Platform/WindowTarget.h new file mode 100644 index 0000000..aa8632e --- /dev/null +++ b/digsby/ext/src/Animation/Platform/WindowTarget.h @@ -0,0 +1 @@ +#include "WindowTargetWx.h" diff --git a/digsby/ext/src/Animation/Platform/wx/AnimationPlatformWx.h b/digsby/ext/src/Animation/Platform/wx/AnimationPlatformWx.h new file mode 100644 index 0000000..c3f0818 --- /dev/null +++ b/digsby/ext/src/Animation/Platform/wx/AnimationPlatformWx.h @@ -0,0 +1,75 @@ +#ifndef AnimationPlatformWx_h +#define AnimationPlatformWx_h + +#include +#include +#include +#include +#include +#include + +#define ANIMATION_PLATFORM_WX + +#include +using google::sparse_hash_map; +#include + +#include +#include + +typedef double Time; + +struct Point { + double x; + double y; + + Point() + : x(0) + , y(0) + {} + + Point(const wxPoint& pt) + { + x = pt.x; + y = pt.y; + } + + Point(double x_val, double y_val) + : x(x_val) + , y(y_val) + {} + + Point operator*(double f) + { + return Point(x*f, y*f); + } + + Point operator+(const Point& p) + { + return Point(x + p.x, y + p.y); + } + + Point operator-(const Point& p) + { + return Point(x - p.x, y - p.y); + } +}; + +typedef cairo_matrix_t Transform; +typedef cairo_surface_t* Bitmap; +typedef cairo_t GraphicsContext; +typedef cairo_matrix_t Matrix; +typedef cairo_font_face_t* Font; + +typedef wxString String; +typedef wxRect2DDouble Rect; +typedef wxColour Color; + +inline Time GetCurrentTimeSeconds() +{ + return static_cast

"); +} + +wxString +MSIMEncoder::GetFooter() const{ + return wxString(L"

"); +} + +unsigned int +MSIMEncoder::GetTerminalFlags(const StyleDesc& /*styleDesc*/, unsigned char dirtyMask) const{ + return dirtyMask & (STYLEFONT | STYLESIZE | STYLEITAL | STYLEBOLD | STYLEUNDL | STYLECOLOR | STYLEBGCLR); +} + +wxString +MSIMEncoder::FormatUnicodeChar(unsigned long unichar) const{ + return wxString::Format(L"%c", unichar); +} diff --git a/digsby/ext/src/RTFToX/MSIMEncoder.h b/digsby/ext/src/RTFToX/MSIMEncoder.h new file mode 100644 index 0000000..afda71c --- /dev/null +++ b/digsby/ext/src/RTFToX/MSIMEncoder.h @@ -0,0 +1,19 @@ +#ifndef MSIMENCODER_H +#define MSIMENCODER_H + +#include "wx/String.h" +#include "StyleDescs.h" +#include "Encoder.h" + +class MSIMEncoder: public Encoder{ + public: + MarkupDesc* GetMarkup(const StyleDesc& styleDesc, unsigned char dirtyMask, bool base) const; + wxString FormatString(const wxString& originalString) const; + wxString FormatLink(const wxString& target, const wxString& text) const; + wxString GetHeader() const; + wxString GetFooter() const; + unsigned int GetTerminalFlags(const StyleDesc& styleDesc, unsigned char dirtyMask) const; + wxString FormatUnicodeChar(unsigned long unichar) const; +}; + +#endif //MSIMENCODER_H \ No newline at end of file diff --git a/digsby/ext/src/RTFToX/MSNEncoder.cpp b/digsby/ext/src/RTFToX/MSNEncoder.cpp new file mode 100644 index 0000000..610bd58 --- /dev/null +++ b/digsby/ext/src/RTFToX/MSNEncoder.cpp @@ -0,0 +1,62 @@ +#include "MSNEncoder.h" + +MarkupDesc* +MSNEncoder::GetMarkup(const StyleDesc& styleDesc, unsigned char dirtyMask, bool base) const{ + //"X-MMS-IM-Format: FN=%s; EF=%s; CO=%s; CS=%d; PF=%sd" + MarkupDesc *md = new MarkupDesc; + + if(!base) + return md; + + wxString formatString(L"X-MMS-IM-Format:"); + + if(dirtyMask & STYLEFONT) + //TODO: Set CS to Character Set and PF to Font Family + formatString.Append(wxString::Format(L" FN=%s; CS=%d; PF=%d", URLEncode(styleDesc.font->name), 0, 22)); + + if(dirtyMask & (STYLEBOLD | STYLEITAL | STYLEUNDL)) + formatString << L"; EF=" << wxString::Format(L"%s%s%s", + styleDesc.styleflags & STYLEBOLD ? L"B" : L"", + styleDesc.styleflags & STYLEITAL ? L"I" : L"", + styleDesc.styleflags & STYLEUNDL ? L"U" : L""); + + if(dirtyMask & STYLECOLOR) + formatString << L"; CO=" << wxString::Format(L"%s", styleDesc.color->ToHex(L"bgr")); + + formatString.Append(L"\r\n\r\n"); + + md->initmarkup = formatString; + md->formatMask = dirtyMask; + + return md; +} + +wxString +MSNEncoder::FormatString(const wxString& originalString) const{ + return originalString; +} + +wxString +MSNEncoder::FormatLink(const wxString& target, const wxString& text) const{ + return FormatString(wxString::Format(L"%s(%s)", text, target)); +} + +wxString +MSNEncoder::GetHeader() const{ + return wxString(L"MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n"); +} + +wxString +MSNEncoder::GetFooter() const{ + return wxEmptyString; +} + +unsigned int +MSNEncoder::GetTerminalFlags(const StyleDesc& /*styleDesc*/, unsigned char /*dirtyMask*/) const{ + return 0; +} + +wxString +MSNEncoder::FormatUnicodeChar(unsigned long unichar) const{ + return wxString::Format(L"%c", unichar); +} diff --git a/digsby/ext/src/RTFToX/MSNEncoder.h b/digsby/ext/src/RTFToX/MSNEncoder.h new file mode 100644 index 0000000..9f3b5df --- /dev/null +++ b/digsby/ext/src/RTFToX/MSNEncoder.h @@ -0,0 +1,20 @@ +#ifndef MSNENCODER_H +#define MSNENCODER_H + +#include "wx/String.h" +#include "StringUtil.h" +#include "StyleDescs.h" +#include "Encoder.h" + +class MSNEncoder: public Encoder{ + public: + MarkupDesc* GetMarkup(const StyleDesc& styleDesc, unsigned char dirtyMask, bool base) const; + wxString FormatString(const wxString& originalString) const; + wxString FormatLink(const wxString& target, const wxString& text) const; + wxString GetHeader() const; + wxString GetFooter() const; + unsigned int GetTerminalFlags(const StyleDesc& styleDesc, unsigned char dirtyMask) const; + wxString FormatUnicodeChar(unsigned long unichar) const; +}; + +#endif //MSNENCODER_H \ No newline at end of file diff --git a/digsby/ext/src/RTFToX/RTFToX.cpp b/digsby/ext/src/RTFToX/RTFToX.cpp new file mode 100644 index 0000000..bf01d63 --- /dev/null +++ b/digsby/ext/src/RTFToX/RTFToX.cpp @@ -0,0 +1,888 @@ +#include "RTFToX.h" + +#if __WXMSW__ +#include +#endif + +#if DBG_TIMER + #include "wx/stopwatch.h" + wxStopWatch sw; +#endif + +#include + +#include +#include +using std::map; + +typedef map SpecialCharMap; + +static inline SpecialCharMap& getSpecialCharMap() +{ + static bool initializedSpecialCharMap = false; + static SpecialCharMap specialchars; + static wxMutex specialCharMutex; + + wxMutexLocker lock(specialCharMutex); + if (!initializedSpecialCharMap) { + initializedSpecialCharMap = true; + + specialchars[L"tab"] = 0x0009; + specialchars[L"emdash"] = 0x2014; + specialchars[L"endash"] = 0x2013; + specialchars[L"emspace"] = 0x2003; + specialchars[L"enspace"] = 0x2002; + specialchars[L"bullet"] = 0x2022; + specialchars[L"lquote"] = 0x2018; + specialchars[L"rquote"] = 0x2019; + specialchars[L"ldblquote"] = 0x201C; + specialchars[L"rdblquote"] = 0x201D; + specialchars[L"~"] = 0x00A0; + specialchars[L"-"] = 0x002D; + specialchars[L"_"] = 0x2011; + } + + return specialchars; +} + +static inline wchar_t getSpecialChar(const wxString& lookup) { + const SpecialCharMap& map = getSpecialCharMap(); + wxWCharBuffer buf(lookup.wc_str()); + SpecialCharMap::const_iterator i = map.find(buf.data()); + if (i == map.end()) + return 0; + else + return i->second; +} + +//Lookup codepage from charset +//GetCodePage from http://www.codeproject.com/KB/recipes/RtfConverter.aspx +static unsigned int GetCodePage( long charSet ){ + switch(charSet){ + case 0: + return 1252; //ANSI + case 1: + return 0; //Default + case 2: + return 42; //Symbol + case 77: + return 10000; //Mac Roman + case 78: + return 10001; //Mac Shift Jis + case 79: + return 10003; //Mac Hangul + case 80: + return 10008; //Mac GB2312 + case 81: + return 10002; //Mac Big5 + case 82: + return 0; //Mac Johab (old) + case 83: + return 10005; //Mac Hebrew + case 84: + return 10004; //Mac Arabic + case 85: + return 10006; //Mac Greek + case 86: + return 10081; //Mac Turkish + case 87: + return 10021; //Mac Thai + case 88: + return 10029; //Mac East Europe + case 89: + return 10007; //Mac Russian + case 128: + return 932; //Shift JIS + case 129: + return 949; //Hangul + case 130: + return 1361; //Johab + case 134: + return 936; //GB2312 + case 136: + return 950; //Big5 + case 161: + return 1253; //Greek + case 162: + return 1254; //Turkish + case 163: + return 1258; //Vietnamese + case 177: + return 1255; //Hebrew + case 178: + return 1256; //Arabic + case 179: + return 0; //Arabic Traditional (old) + case 180: + return 0; //Arabic user (old) + case 181: + return 0; //Hebrew user (old) + case 186: + return 1257; //Baltic + case 204: + return 1251; //Russian + case 222: + return 874; //Thai + case 238: + return 1250; //Eastern European + case 254: + return 437; //PC 437 + case 255: + return 850; //OEM + } + + return 0; +} + +static bool IsMultiByte(unsigned char byte, int codepage){ + switch(codepage){ + case 932: + return (byte >= 0x81 && byte <= 0x9F) || byte >= 0xE0; + case 936: + case 949: + case 950: + return byte >= 0x81; + default: + return false; + } +} + + +static wxString GetFontFamilyStr(int wxfamily){ + switch(wxfamily){ + case wxFONTFAMILY_DECORATIVE: + return L"fdecor"; + case wxFONTFAMILY_ROMAN: + return L"froman"; + case wxFONTFAMILY_SCRIPT: + return L"fscript"; + case wxFONTFAMILY_SWISS: + return L"fswiss"; + case wxFONTFAMILY_MODERN: + return L"fmodern"; + case wxFONTFAMILY_TELETYPE: + return L"ftech"; + default: + return L"fnil"; + + } +} + + + +RTFToX::RTFToX(){ + Reset(); +} + + +void +RTFToX::Reset(){ + //Reset the system to default state + + currentStyle.font = 0; + currentStyle.size = 0; + currentStyle.color = 0; + currentStyle.bgcolor = 0; + currentStyle.styleflags = 0; + formatMask = 0; + + fonts.clear(); + colors.clear(); + + convertedString.Clear(); + bufferString.Clear(); + + squirrellyDepth = 0; + ignoreSpace = false; + acceptToken = true; + basemarkup = true; +} + +void +RTFToX::ProcessCommand(const wxString &token, wxStringTokenizer &toker, const Encoder &encoder){ + //Process RTF format commands, these are denoted by a leading '/' + + wxString command; + wxString arg; + +#if __WXMSW__ + // FIXME: Do we need to run similar code under other platforms? + if(token.StartsWith(L"'", &arg)){ + //Grrr, Exetended ASCII, needs to be converted to unicode then processed with the encoder + + + ProcessFormat(encoder); + + wxString hexstring; + wxString extstring; + unsigned long ansiint; + unsigned char ansichar[2]; + unsigned long unichar = 0; + int bytecount = 1; + + hexstring = arg.Left(2); + hexstring.ToULong(&ansiint, 16); + ansichar[0] = (unsigned char)ansiint; + + if(IsMultiByte(ansichar[0], currentStyle.font->codepage)){ + + if(toker.GetNextToken().StartsWith(L"'", &arg)){ + bytecount = 2; + hexstring = arg.Left(2); + hexstring.ToULong(&ansiint, 16); + ansichar[1] = (unsigned char)ansiint; + }else{ + + bufferString.Append(encoder.FormatString(L"?")); + bufferString.Append(encoder.FormatString(extstring)); + ignoreSpace = false; + acceptToken = true; + + return; + } + } + + extstring = arg.Right(arg.Len()-2); + + MultiByteToWideChar(currentStyle.font->codepage, //UINT CodePage, + 0, //DWORD dwFlags, + (LPCSTR)&ansichar, //LPCSTR lpMultiByteStr, + bytecount, //int cbMultiByte, + (LPWSTR)&unichar, //LPWSTR lpWideCharStr, + 2); //int cchWideChar + + + bufferString.Append(encoder.FormatUnicodeChar(unichar)); + bufferString.Append(encoder.FormatString(extstring)); + + ignoreSpace = false; + acceptToken = true; + + return; + + + } else +#endif + + if(token.StartsWith(L"~", &arg)){ + + ProcessFormat(encoder); + + bufferString.Append(encoder.FormatString(L"\x00A0")); + bufferString.Append(encoder.FormatString(arg)); + + ignoreSpace = false; + acceptToken = true; + + return; + + }else if(GetDigits(token, command, arg)){ + //Command contains numbers, so check certain supset + + if(token==L"b0"){ + //Trun off bold + + currentStyle.styleflags = currentStyle.styleflags & ~STYLEBOLD; + formatMask |= STYLEBOLD; + + }else if(token==L"i0"){ + //Turn off italics + + currentStyle.styleflags = currentStyle.styleflags & ~STYLEITAL; + formatMask |= STYLEITAL; + + }else if(token==L"ul0"){ + //Turn off Underline + + currentStyle.styleflags = currentStyle.styleflags & ~STYLEUNDL; + formatMask |= STYLEUNDL; + + }else if(command==L"ansicpg"){ + //Set the ANSI code page + arg.ToULong((unsigned long*)&defaultCodepage); + + }else if(command==L"uc"){ + //Set the unicode character code leangth + arg.ToULong(&uniAltLen); + + }else if(command==L"u"){ + //Unicode character, process with encoder + + unsigned long unichar; + arg.ToULong(&unichar); + + ProcessFormat(encoder); + bufferString.Append(encoder.FormatUnicodeChar(unichar)); + + wxString numstring; + numstring.Printf(L"%d", unichar); + + wxString leftovers(arg.substr(numstring.Length() + 1)); + + bufferString.Append(leftovers); + + ignoreSpace = false; + acceptToken = true; + + + return; + + }else if(command==L"fs"){ + //Set Font Size, this number is 2x pt size + + unsigned long size; + arg.ToULong(&size); + currentStyle.size = size; + formatMask |= STYLESIZE; + + }else if(command==L"f"){ + //Set font from font table + + unsigned long i; + arg.ToULong(&i); + currentStyle.font = &fonts[i]; + formatMask |= STYLEFONT; + + }else if(command==L"cf"){ + //Set font color from color table + + unsigned long i; + arg.ToULong(&i); + currentStyle.color = (i >= 0 && i < colors.size()) ? &colors[i] : Color::BLACK(); + formatMask |= STYLECOLOR; + + }else if(command==L"highlight"){ + //Set font background color from color table + + unsigned long i; + arg.ToULong(&i); + currentStyle.bgcolor = i ? &colors[i] : Color::WHITE(); + formatMask |= STYLEBGCLR; + + } + }else{ + //command doesn't contain numbers + + if(token==L"*"){ + //Editor specific meta info and commands, ignoreable + IgnoreLoop(toker); + } + + if(token==L"fonttbl"){ + //Make a font table + ProcessFontTable(toker); + + }else if(token==L"colortbl"){ + //Make a color table + ProcessColorTable(toker); + + }else if(token==L"b"){ + //Turn bold on + currentStyle.styleflags |= STYLEBOLD; + formatMask |= STYLEBOLD; + + }else if(token==L"i"){ + //Turn Italics on + currentStyle.styleflags |= STYLEITAL; + formatMask |= STYLEITAL; + + }else if(token==L"ul"){ + //Turn Underline on + currentStyle.styleflags |= STYLEUNDL; + formatMask |= STYLEUNDL; + + }else if(token==L"ulnone"){ + //Turn underline off + currentStyle.styleflags = currentStyle.styleflags & ~STYLEUNDL; + formatMask |= STYLEUNDL; + + }else if(token==L"line" || token==L"par"){ + //Insert NewLine + ProcessFormat(encoder); + bufferString.Append(encoder.FormatString(wxString(L"\n"))); + acceptToken = true; + return; + + }else if(token==L"hl"){ + ProcessHyperlink(toker, encoder); + }else if(wchar_t unichr = getSpecialChar(token)) { + ProcessFormat(encoder); + bufferString.Append(encoder.FormatUnicodeChar(unichr)); + } + } + + acceptToken = false; + return; +} + +void +RTFToX::ProcessFontTable(wxStringTokenizer &toker){ + //Makes the font table + + wxString token; + wxString delimiter; + + wxString fontarg; + + wxString fontname; + wxString fontfamily; + + long charset = 0; + unsigned int codepage = 0; + + short depth = squirrellyDepth; + + //Font table ends when the squirrelly depths drops out of where the table starts + while(squirrellyDepth >= depth && toker.HasMoreTokens()){ + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + + if(delimiter == L"\\"){ + if(token.StartsWith(L"fcharset", &fontarg)){ + fontarg.ToLong(&charset); + codepage = GetCodePage(charset); + }else if(wxString(L"fnil froman fswiss fmodern fscript fdecor ftech fbidi").Contains(token)){ + //Oh look, it's a Font Family + + fontfamily = token; + //Font Families: + //fnil Unknown or default fonts (the default) + //froman Roman, proportionally spaced serif fonts Times New Roman, Palatino + //fswiss Swiss, proportionally spaced sans serif fonts Arial + //fmodern Fixed-pitch serif and sans serif fonts Courier New, Pica + //fscript Script fonts Cursive + //fdecor Decorative fonts Old English, ITC Zapf Chancery + //ftech Technical, symbol, and mathematical fonts Symbol + //fbidi Arabic, Hebrew, or other bidirectional font Miriam + } + }else if(delimiter == L" "){ + //Space means we're looking at the Font Name + if(fontname.Len() != 0) fontname.Append(L" "); + fontname.Append(token); + + }else if(delimiter == L";"){ + //This means we're done with the entry + FontDesc font; + + font.name = fontname; + font.family = fontfamily; + font.codepage = codepage; + font.charset = charset; + + fonts.push_back(font); + + fontname.Clear(); + codepage = defaultCodepage; + + + + + }else if(delimiter == L"{"){ + ++squirrellyDepth; + + }else if(delimiter == L"}"){ + --squirrellyDepth; + } + } + + #if DBG_PRNT_TBLS + //Print the table for debugging + PrintFontTable(fonts); + #endif + +} + +void +RTFToX::ProcessColorTable(wxStringTokenizer &toker){ + //Makes the color table + + wxString token; + wxString delimiter; + wxString command; + wxString colorarg; + long r=0, g=0, b=0; + + const short thisDepth = squirrellyDepth; + + while(squirrellyDepth >= thisDepth && toker.HasMoreTokens()){ + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + + if(delimiter == L"\\"){ + //This means it's time to do a color channel + + GetDigits(token, command, colorarg); + + if(command == L"red"){ + colorarg.ToLong(&r); + }else if(command == L"green"){ + colorarg.ToLong(&g); + }else if(command == L"blue"){ + colorarg.ToLong(&b); + } + + }else if(delimiter == L";"){ + //This means the color is done + + Color color(r, g, b); + + colors.push_back(color); + r = g = b = 0; + + + + }else if(delimiter == L"}"){ + --squirrellyDepth; + //printf("--squirrellyDepth == %d\n", squirrellyDepth); + + } + + } + + #if DBG_PRNT_TBLS + PrintColorTable(colors); + #endif +} + +void +RTFToX::ProcessHyperlink(wxStringTokenizer &toker, const Encoder &encoder){ + + wxString token; + wxString delimiter; + wxString command; + + wxString source; + wxString location; + wxString text; + wxString extra; + + wxString *field = &extra; + + const short depth = squirrellyDepth; + + while(squirrellyDepth >= depth && toker.HasMoreTokens()){ + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + + if(delimiter == L"\\"){ + if(token == L"hlloc") + field = &location; + else if(token == L"hlfr") + field = &text; + else if(token == L"hlsrc") + field = &source; + + }else if(delimiter == L" " && !token.IsEmpty() && squirrellyDepth > depth){ + while(delimiter != L"}"){ + if(delimiter == L"\\"){ + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + } + + field->Append(delimiter); + field->Append(token); + + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + + } + + --squirrellyDepth; + + field = &extra; + + }else if(delimiter == L"{"){ + ++squirrellyDepth; + }else if(delimiter == L"}"){ + --squirrellyDepth; + } + + } + + bufferString.Append(encoder.FormatLink(location, text)); + + acceptToken = true; + +} + +void +RTFToX::IgnoreLoop(wxStringTokenizer &toker){ + //Just continues parsing the RTF until it dorps out of the current squirelly depth + + wxString token; + wxString delimiter; + + const short thisDepth = squirrellyDepth; + while(squirrellyDepth >= thisDepth && toker.HasMoreTokens()){ + + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + + if(delimiter == L"{"){ + ++squirrellyDepth; + }else if(delimiter == L"}"){ + --squirrellyDepth; + acceptToken = true; + } + } +} + +void +RTFToX::ProcessFormat(const Encoder &encoder){ + //We're about to process some more text so should take care of the markup we have queued up + + if(formatMask){ + + //Using the new markup, what previous markup types should we end + unsigned int tflags = encoder.GetTerminalFlags(currentStyle, formatMask); + + if(!markupStack.empty()){ + list::iterator pastMarkup = markupStack.begin(); + unsigned int pastCFlags; + unsigned int lastCFlags; + + //Scan over the markup stack + while(pastMarkup != markupStack.end()){ + + pastCFlags = pastMarkup->contentflags; + + //If content type of something in the stack is in the terminal flags + if(pastMarkup->contentflags & tflags){ + + //Terminate all markup between top and detected node + do{ + formatMask |= markupStack.front().formatMask; + convertedString.Append(markupStack.front().termmarkup); + lastCFlags = markupStack.front().contentflags; + markupStack.pop_front(); + }while(!markupStack.empty() && (lastCFlags != pastCFlags)); + + pastMarkup = markupStack.begin(); + + continue; + } + + ++pastMarkup; + } + } + + + MarkupDesc *mdp = encoder.GetMarkup(currentStyle, formatMask, basemarkup); + MarkupDesc *md0 = mdp; + + basemarkup = false; + + //Could be multiple MarkupDescs chained, apply each and push onto stack + do{ + + convertedString.Append(mdp->initmarkup); + markupStack.push_front(*mdp); + markupStack.front().next = 0; + mdp = mdp->next; + + }while(mdp != 0); + + delete md0; + + //New format applied, reset mask + formatMask = 0; + + + } +} + +void +RTFToX::ProcessToken(const wxString &token, const wxString &delimiter, const Encoder &encoder){ + //Push token and/or delimiter to the buffer string + + ProcessFormat(encoder); + + if(!ignoreSpace){ + bufferString.append(encoder.FormatString(delimiter)); + }else{ + ignoreSpace = false; + } + + acceptToken = true; + + + if(!token.IsEmpty()){ + bufferString.Append(encoder.FormatString(token)); + } +} + +void +RTFToX::FlushFormat(){ + while(!markupStack.empty()){ + //Close open markup and clear the stack + formatMask |= markupStack.front().formatMask; + convertedString.Append(markupStack.front().termmarkup); + markupStack.pop_front(); + } +} + +wxString +RTFToX::ParseRTF(wxString &rtf, const Encoder &encoder){ + + //all desired newlines are marked with \par or \line no need for these + rtf.Replace(L"\n", L" "); + + + //remove trailing "/par "} + rtf.RemoveLast(rtf.Len() - rtf.rfind(L"\\par")); + rtf.Append(L"}"); + + + + wxString token; + wxString delimiter; + + wxStringTokenizer toker(rtf, L"\\;{} \t"); + + while(toker.HasMoreTokens()){ + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + + if(delimiter == L"\\"){ + //next token is either a command or a escaped character + + //Space used to denote the end of a command, so ignore the next one + ignoreSpace = true; + + if(!token.IsEmpty()){ + //if Token isn't empty, send it off to command procesing + + ProcessCommand(token, toker, encoder); + + + if(formatMask && bufferString.Len()){ + //a command just got processed + //dump the buffered data into the converted string before processing more + + convertedString.Append(bufferString); + bufferString.Clear(); + } + + }else{ + //Token is empty, so it must be escaping the next delimiter + + delimiter = toker.GetLastDelimiter(); + token = toker.GetNextToken(); + + ProcessToken(delimiter + token, delimiter, encoder); + } + + + + }else if(delimiter == L";"){ + //if we're in a command then this is ignored + //otherwise it's part of the string and should be appended + + if(acceptToken){ + ProcessToken(token, delimiter, encoder); + } + + acceptToken = true; + + }else if(delimiter == L"{"){ + ++squirrellyDepth; + + }else if(delimiter == L"}"){ + --squirrellyDepth; + + }else if(delimiter == L" "){ + //Space means we're back in the string, process it + ProcessToken(token, delimiter, encoder); + + } + + } + + //Out of tokens to process + + if(bufferString.Len()){ + //If there's text in the buffer, now is a good time to flush + + convertedString.Append(bufferString); + bufferString.Clear(); + } + + FlushFormat(); + + return convertedString; + +} + +wxString +RTFToX::ApplyStyleToText(const wxString text, const Encoder &encoder, const wxTextAttr &style){ + + wxFont wxfont = style.GetFont(); + + FontDesc font = {wxfont.GetFaceName(), GetFontFamilyStr(wxfont.GetFamily()), 0, 0}; + Color fgcolor(style.GetTextColour()); + Color bgcolor(style.GetBackgroundColour()); + + short styleflags = 0; + if(wxfont.GetWeight() == wxFONTWEIGHT_BOLD) styleflags |= STYLEBOLD; + if(wxfont.GetStyle() == wxFONTSTYLE_ITALIC) styleflags |= STYLEITAL; + if(wxfont.GetUnderlined()) styleflags |= STYLEUNDL; + + currentStyle.font = &font; + currentStyle.size = wxfont.GetPointSize()*2; + currentStyle.styleflags = styleflags; + currentStyle.color = &fgcolor; + currentStyle.bgcolor = &bgcolor; + + formatMask = 0xff; + ProcessFormat(encoder); + convertedString.Append(encoder.FormatString(text)); + FlushFormat(); + + return convertedString; + +} + +wxString +RTFToX::Convert(const wxString &text, const Encoder &encoder, const wxString &inputFormat, const wxTextAttr *style){ + + #if DBG_TIMER + sw.Start(); + #endif + + wxString text2(text); + wxString output; + + //let's just use unix style newlines. + text2.Replace(L"\r", L""); + + output.Append(encoder.GetHeader()); + + + //TODO: Replace with type lookup map later for pluginable input types + //TODO: Also abstarct all RTF specific code + if(inputFormat == L"rtf"){ + output.Append(ParseRTF(text2, encoder)); + }else if(style){ + output.Append(ApplyStyleToText(text2, encoder, *style)); + }else{ + output.Append(encoder.FormatString(text2)); + } + + + //Add the footer if there is one + output.Append(encoder.GetFooter()); + + + Reset(); + + + #if DBG_TIMER + sw.Pause(); + printf("Time to process: %dms;\n\n\n", sw.Time()); + #endif + + return output; + + +} + diff --git a/digsby/ext/src/RTFToX/RTFToX.h b/digsby/ext/src/RTFToX/RTFToX.h new file mode 100644 index 0000000..f46634f --- /dev/null +++ b/digsby/ext/src/RTFToX/RTFToX.h @@ -0,0 +1,55 @@ +#ifndef RTFTOX_H +#define RTFTOX_H + +#include "wx/String.h" +#include +#include + + + +#include "wx/tokenzr.h" + +#include "StringUtil.h" +#include "StyleDescs.h" +#include "Encoder.h" +#include "Debugutil.h" + +using namespace std; + +class RTFToX{ + private: + StyleDesc currentStyle; + unsigned char formatMask; //See StyleDesc.h for constants + vector fonts; + vector colors; + unsigned int defaultCodepage; + short squirrellyDepth; //How many {} nested are we? + unsigned long uniAltLen; //Unicode chars have an alternate ASCII string in RTF, how many chars? + + wxString convertedString; + wxString bufferString; + bool basemarkup; //Is this the first peice if markup? + + list markupStack; //All the markup already applied to the string, stores how to end each + + bool ignoreSpace; //Ignore the next space processed as it's part of a command + bool acceptToken; //Next token is not part of a command + + void Reset(); + void ProcessCommand(const wxString &token, wxStringTokenizer &toker, const Encoder &encoder); + void ProcessFontTable(wxStringTokenizer &toker); + void ProcessColorTable(wxStringTokenizer &toker); + void ProcessHyperlink(wxStringTokenizer &toker, const Encoder &encoder); + void IgnoreLoop(wxStringTokenizer &toker); + void ProcessToken(const wxString &token, const wxString &delimiter, const Encoder &encoder); + void ProcessFormat(const Encoder &encoder); + void FlushFormat(); + wxString ParseRTF(wxString &rtf, const Encoder &encoder); + + public: + RTFToX(); + wxString Convert(const wxString &rtf, const Encoder &encoder, const wxString &inputFormat = wxT("rtf"), const wxTextAttr *style = 0); + wxString ApplyStyleToText(const wxString text, const Encoder &encoder, const wxTextAttr &style); +}; + +#endif //RTFTOX_H diff --git a/digsby/ext/src/RTFToX/StringUtil.cpp b/digsby/ext/src/RTFToX/StringUtil.cpp new file mode 100644 index 0000000..29422ea --- /dev/null +++ b/digsby/ext/src/RTFToX/StringUtil.cpp @@ -0,0 +1,75 @@ +#include "StringUtil.h" + +wxString URLEncode(wxString input){ + + wxString output; + wxString temp; + wxChar c; + + for(unsigned int i = 0; i < input.Length(); ++i){ + c = input[i]; + if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')){ + output.Append(c); + }else if(c < 256){ + temp.Printf(L"%%%02x",c); + output.Append(temp); + }else{ + + temp.Printf(L"%04x",c); + + for(unsigned int j = 0; j < temp.Length(); j += 2){ + output << L"%" << temp.Mid(j, 2); + } + + } + } + + return output; + +} + +bool HasDigits(const wxString &string){ + for (size_t i = 0; i < string.Len(); i++) + if (wxIsdigit(string[i])) + return true; + + return false; +} + +bool GetDigits(const wxString &string, wxString &command, wxString &arg){ + + unsigned int l = string.Len(); + for(unsigned int i = 0; i < l; ++i){ + if(wxIsdigit(string[i])){ + + command = string.Mid(0, i); + arg = string.Mid(i, l-i); + return true; + } + } + + return false; +} + + +wxString EscapeUnicode(const wxString &string, wxString (*FormatUnicodeChar)(unsigned long)){ + + if(string.IsEmpty()) return string; + + wxString output; + output.Alloc(string.size()); + + for (size_t i = 0; i < string.size(); ++i) { + wxChar c = string.GetChar(i); + if (c > 0x80) + output += FormatUnicodeChar(c); + else + output += c; + } + + return output; +} + +wxString HTMLEscapeUnicode(unsigned long unichar){ + return wxString::Format(L"&#%d;", unichar); +} diff --git a/digsby/ext/src/RTFToX/StringUtil.h b/digsby/ext/src/RTFToX/StringUtil.h new file mode 100644 index 0000000..f8bee5c --- /dev/null +++ b/digsby/ext/src/RTFToX/StringUtil.h @@ -0,0 +1,25 @@ +#ifndef STRING_UTIL_H +#define STRING_UTIL_H + +#include "wx/String.h" +#include "wx/tokenzr.h" + +//Make a wxString internet safe +wxString URLEncode(wxString input); + +//Test to see if a wxString contains any digits +bool HasDigits(const wxString &string); + +//If the string contains digits returns True and modifies command and arg so that: +// command contains the first section of the string +// arg contains the second section of the string +// split before the first digit in the string +bool GetDigits(const wxString &string, wxString &command, wxString &arg); + +bool IsRTF(const wxString &string); + +wxString EscapeUnicode(const wxString &string, wxString (*FormatUnicodeChar)(unsigned long)); + +wxString HTMLEscapeUnicode(unsigned long unichar); + +#endif //STRING_UTIL_H \ No newline at end of file diff --git a/digsby/ext/src/RTFToX/StyleDesc.cpp b/digsby/ext/src/RTFToX/StyleDesc.cpp new file mode 100644 index 0000000..91cd56a --- /dev/null +++ b/digsby/ext/src/RTFToX/StyleDesc.cpp @@ -0,0 +1,89 @@ +#include "StyleDescs.h" + + +MarkupDesc::MarkupDesc() + : next(0) + , contentflags(0) + , formatMask(0) +{} + +MarkupDesc::~MarkupDesc(){ + if (next) + delete next; +} + +Color::Color(long red, long green, long blue) + : r(red) + , g(green) + , b(blue) +{} + +Color::Color(const wxColor &wxcolor) : r(wxcolor.Red()), g(wxcolor.Green()), b(wxcolor.Blue()){} + +wxString +Color::ToHex(const wxString format){ + + if(format == this->format){ + return hex; + } + + hex.Clear(); + long c; + + for(unsigned int i = 0; i < format.Len(); ++i){ + + switch((const char)format[i]){ + + case 'r': + c = r; + break; + + case 'g': + c = g; + break; + + case 'b': + c = b; + break; + + case 'a': + c = 0xff; + break; + + default: + continue; + } + + hex.Append(wxString::Format(wxT("%02x"), c)); + + } + + this->format = format; + + return hex; +} + +MarkupDesc* MarkupDesc::NewDesc(const wxString& initMarkup, const wxString& termMarkup, unsigned int contentflags, unsigned char formatMask){ + MarkupDesc* mdp; + if (initmarkup.Len()) + mdp = next = new MarkupDesc; + else + mdp = this; + + mdp->initmarkup = initMarkup; + mdp->termmarkup = termMarkup; + mdp->contentflags |= contentflags; + mdp->formatMask = formatMask; + + return mdp; +} + +MarkupDesc* MarkupDesc::GetLast(){ + MarkupDesc *mdp = this; + + while(mdp->next){ + mdp = mdp->next; + } + + return mdp; +} diff --git a/digsby/ext/src/RTFToX/StyleDescs.h b/digsby/ext/src/RTFToX/StyleDescs.h new file mode 100644 index 0000000..b3f815d --- /dev/null +++ b/digsby/ext/src/RTFToX/StyleDescs.h @@ -0,0 +1,89 @@ +#ifndef STYLEDESCS_H +#define STYLEDESCS_H + +#include "wx/wx.h" +#include "wx/String.h" + +#define STYLECLEAR 0 +#define STYLEBOLD 1 +#define STYLEITAL 2 +#define STYLEUNDL 4 +#define STYLEFONT 8 +#define STYLESIZE 16 +#define STYLECOLOR 32 +#define STYLEBGCLR 64 + +//Stores a color as RGB +class Color +{ + private: + wxString hex, format; + public: + long r, g, b; + Color(long red, long green, long blue); + Color(const wxColor &wxcolor); + + //Generates a string containing the hex digits in the order diplayed in format + //r,g,b, and a are valid in the format string + wxString ToHex(const wxString format = L"rgb"); + + bool IsWhite(){ + return r == 255 && g == 255 && b == 255; + } + + static Color* WHITE() + { + static Color white(255L, 255L, 255L); + return &white; + } + + static Color* BLACK() + { + static Color black(0L, 0L, 0L); + return &black; + } +}; + +//Structured interpretation of a RTF fonttable entry +struct FontDesc{ + wxString name; + wxString family; + int codepage; + int charset; +}; + +//Structured interpretation of RTF format state +struct StyleDesc{ + FontDesc *font; + unsigned long size; + short styleflags; + Color *color; + Color *bgcolor; +}; + +//Container for the: +// -start markup +// -end markup +// -flags for the content types the markup contains that can be ended +// -flags for markup types that where changed +// -pointer to a posible nested set of markup +class MarkupDesc{ + public: + unsigned int contentflags; + wxString initmarkup; + wxString termmarkup; + MarkupDesc *next; + unsigned char formatMask; + + MarkupDesc(); + ~MarkupDesc(); + + MarkupDesc* NewDesc(const wxString& initMarkup = wxEmptyString, + const wxString& termMarkup = wxEmptyString, + unsigned int contentflags = 0, + unsigned char formatMask = 0); + + MarkupDesc* GetLast(); +}; + +#endif //STYLEDESCS_H diff --git a/digsby/ext/src/RTFToX/Test.cpp b/digsby/ext/src/RTFToX/Test.cpp new file mode 100644 index 0000000..d392d1f --- /dev/null +++ b/digsby/ext/src/RTFToX/Test.cpp @@ -0,0 +1,463 @@ +//ExpandoTextCtrl test splitter edition + + +#define ENCODER HTMLEncoder + +#define USEEXPANDO 1 + +#if USEEXPANDO + #define INPUTCTRL ExpandoTextCtrl +#else + #define INPUTCTRL InputBox +#endif //USEEXPANDO + + +#include "wx/wx.h" +#include "wx/fontdlg.h" +#include "wx/numdlg.h" +#include "wx/colordlg.h" +#include "wx/splitter.h" + +#include "DebugUtil.h" +#include "ExpandoTextCtrl.h" +#include "InputBox.h" +#include "ExpandEvent.h" +#include "SelectionEvent.h" +#include "StyleDescs.h" + +#include "RTFToX.h" + +#include +#include + +#include "HTMLEncoder.h" + +//#define RTFLINK = L"{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Comic Sans MS;}{\\f1\\fnil\\fcharset0 MS Shell Dlg 2;}}\r\n{\\colortbl ;\\red255\\green0\\blue255;\\red0\\green255\\blue255;}\r\n{\\*\\generator Msftedit 5.41.21.2509;}\\viewkind4\\uc1\\pard\\cf1\\highlight2\\ul\\b\\i\\f0\\fs44 test 123\\cf0\\highlight0\\ulnone\\b0\\i0\\f1\\fs17\\par\\parI use {\\hl {\\hlloc http://www.digsby.com/?utm_source=aim&utm_medium=aim&utm_campaign=aimprofilelink } {\\hlfr digsby} }\\par\r\n}" + +class ExpandoApp: public wxApp{ + virtual bool OnInit(); +}; + +void CreateTestWindow(ExpandoApp *app, wxString title); + +//Format IDs +int ID_FONT = wxNewId(); +int ID_SIZE = wxNewId(); +int ID_BOLD = wxNewId(); +int ID_ITAL = wxNewId(); +int ID_UNDR = wxNewId(); +int ID_COLR = wxNewId(); +int ID_BCLR = wxNewId(); +int ID_RTLT = wxNewId(); + +//Debug IDs +int ID_GRTF = wxNewId(); +int ID_SEPR = wxNewId(); +int ID_RTFG = wxNewId(); +int ID_RSIZ = wxNewId(); +int ID_SETV = wxNewId(); +int ID_GVAL = wxNewId(); +int ID_LINK = wxNewId(); +int ID_NEW = wxNewId(); + +RTFToX converter; +ENCODER encoder(false); +wxTextCtrl *output, *input; + +wxString rtfTemp; + + +class DebugBar : public wxPanel{ + public: + + INPUTCTRL *textctrl; + + DebugBar(wxWindow *parent, wxWindowID id, INPUTCTRL *textctrl) : wxPanel(parent, id){ + SetMinSize(wxSize(-1, 30)); + wxBoxSizer *ts = new wxBoxSizer(wxHORIZONTAL); + SetSizer(ts); + + this->textctrl = textctrl; + + Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(DebugBar::OnButton)); + + + wxButton *brtf = new wxButton(this, ID_GRTF, _T("Convert")); + wxButton *bsep = new wxButton(this, ID_SEPR, _T("===")); + wxButton *bresize = new wxButton(this, ID_RSIZ, _T("ReSize")); + wxButton *brtfstore = new wxButton(this, ID_RTFG, _T("Get/Set")); + wxButton *bsetval = new wxButton(this, ID_SETV, _T("Set")); + wxButton *bptxt = new wxButton(this, ID_GVAL, _T("Plaintext")); + wxButton *blink = new wxButton(this, ID_LINK, _T("Link")); + wxButton *bnew = new wxButton(this, ID_NEW , _T("New")); + + + + brtf->SetMinSize(wxSize(50,30)); + bsep->SetMinSize(wxSize(30,30)); + brtfstore->SetMinSize(wxSize(50,30)); + bresize->SetMinSize(wxSize(50,30)); + bsetval->SetMinSize(wxSize(30,30)); + bptxt->SetMinSize(wxSize(50,30)); + blink->SetMinSize(wxSize(30,30)); + bnew->SetMinSize(wxSize(30,30)); + + + ts->Add(brtf, 0, 0); + ts->Add(bsep, 0, 0); + ts->Add(brtfstore, 0, 0); + ts->Add(bresize, 0, 0); + ts->Add(bsetval, 0, 0); + ts->Add(bptxt, 0, 0); + ts->Add(blink, 0, 0); + ts->Add(bnew, 0, 0); + } + + void OnButton(wxCommandEvent &event){ + + + int id = event.GetId(); + long flags = 0; + + if(id == ID_GRTF){ + wxString rtf = textctrl->GetRTF(); + printf("%s\n\n", rtf.ToAscii()); + output->SetValue(converter.Convert(rtf, encoder)); + input->SetValue(rtf); + }else if(id == ID_GVAL){ + wxString text = textctrl->GetValue(); + wxTextAttr style; + textctrl->GetStyle(0, style); + output->SetValue(converter.Convert(text, encoder, L"plaintext", &style)); + input->SetValue(text); + }else if(id == ID_SEPR){ + printf("\n========\n\n"); + }else if(id == ID_RSIZ){ +#if USEEXPANDO + textctrl->SetMinHeight(wxGetNumberFromUser(_T("Input Height:"), _T(""), _T("Set Input Height"), textctrl->GetMinHeight())); + printf("%d\n", textctrl->GetMinSize().GetHeight()); +#endif //USEEXPANDO + }else if(id == ID_RTFG){ + if(rtfTemp.IsEmpty()){ + rtfTemp = textctrl->GetRTF(); + textctrl->Clear(); + }else{ + textctrl->SetRTF(rtfTemp); + rtfTemp.Clear(); + } + }else if(id == ID_SETV){ + wxString v = wxGetTextFromUser(L"Text?"); + textctrl->SetValue(v); + }else if(id == ID_LINK){ + wxString rtf(L"{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Comic Sans MS;}{\\f1\\fnil\\fcharset0 MS Shell Dlg 2;}}\r\n{\\colortbl ;\\red255\\green0\\blue255;\\red0\\green255\\blue255;}\r\n{\\*\\generator Msftedit 5.41.21.2509;}\\viewkind4\\uc1\\pard\\cf1\\highlight2\\ul\\b\\i\\f0\\fs44 test 123\\cf0\\highlight0\\ulnone\\b0\\i0\\f1\\fs17\\par\\par I use {\\hl {\\hlloc http://www.digsby.com/?utm_source=aim&utm_medium=aim&utm_campaign=aimprofilelink } {\\hlfr digsby} }\\par\r\n}"); + //printf("%s\n\n", rtf.ToAscii()); + output->SetValue(converter.Convert(rtf, encoder)); + input->SetValue(rtf); + }else if(id == ID_NEW){ + CreateTestWindow((ExpandoApp*)wxTheApp, L"Spawn"); + } + + textctrl->SetFocus(); + } + +}; + + +class ToolBar : public wxPanel{ + public: + + INPUTCTRL *textctrl; + + ToolBar(wxWindow *parent, wxWindowID id, INPUTCTRL *textctrl) : wxPanel(parent, id){ + SetMinSize(wxSize(-1, 30)); + wxBoxSizer *ts = new wxBoxSizer(wxHORIZONTAL); + SetSizer(ts); + + this->textctrl = textctrl; + + Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ToolBar::OnButton)); + + wxButton *bfont = new wxButton(this, ID_FONT, _T("F" )); + wxButton *bsize = new wxButton(this, ID_SIZE, _T("S")); + wxButton *bbold = new wxButton(this, ID_BOLD, _T("B")); + wxButton *bitalic = new wxButton(this, ID_ITAL, _T("I")); + wxButton *bunderline = new wxButton(this, ID_UNDR, _T("U")); + wxButton *bcolor = new wxButton(this, ID_COLR, _T("C")); + wxButton *bbgcolor = new wxButton(this, ID_BCLR, _T("BC")); + wxButton *brtl = new wxButton(this, ID_RTLT, _T("RTL")); + + + + bfont->SetMinSize(wxSize(30,30)); + bsize->SetMinSize(wxSize(30,30)); + bbold->SetMinSize(wxSize(30,30)); + bitalic->SetMinSize(wxSize(30,30)); + bunderline->SetMinSize(wxSize(30,30)); + bcolor->SetMinSize(wxSize(30,30)); + bbgcolor->SetMinSize(wxSize(30,30)); + brtl->SetMinSize(wxSize(30,30)); + + + + + ts->Add(bfont, 0, 0); + ts->Add(bsize, 0, 0); + ts->Add(bbold, 0, 0); + ts->Add(bitalic, 0, 0); + ts->Add(bunderline, 0, 0); + ts->Add(bcolor, 0, 0); + ts->Add(bbgcolor, 0, 0); + ts->Add(brtl, 0, 0); + + } + + void OnButton(wxCommandEvent &event){ + + wxTextAttr textattr; + int i = textctrl->GetInsertionPoint(); + textctrl->GetStyle(i, textattr); + + + + wxFont font = textattr.GetFont(); + + int id = event.GetId(); + long flags = 0; + + if(id == ID_FONT){ + font = wxGetFontFromUser(this, font); + flags = wxTEXT_ATTR_FONT_FACE; + }else if(id == ID_SIZE){ + font.SetPointSize(wxGetNumberFromUser(_T("Size:"), _T(""), _T("Set Font Size"), font.GetPointSize())); + flags = wxTEXT_ATTR_FONT_SIZE; + }else if(id == ID_BOLD){ + font.SetWeight(font.GetWeight() != wxFONTWEIGHT_BOLD? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL); + flags = wxTEXT_ATTR_FONT_WEIGHT; + }else if(id == ID_ITAL){ + font.SetStyle(font.GetStyle() != wxFONTSTYLE_ITALIC? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL); + flags = wxTEXT_ATTR_FONT_ITALIC; + }else if(id == ID_UNDR){ + font.SetUnderlined(!font.GetUnderlined()); + flags = wxTEXT_ATTR_FONT_UNDERLINE; + }else if(id == ID_COLR){ + textattr.SetTextColour(wxGetColourFromUser(this, textattr.GetTextColour())); + flags = wxTEXT_ATTR_TEXT_COLOUR; + }else if(id == ID_BCLR){ + textattr.SetBackgroundColour(wxGetColourFromUser(this, textattr.GetBackgroundColour())); + flags = wxTEXT_ATTR_BACKGROUND_COLOUR; + }else if(id == ID_RTLT){ + //textctrl->SetSelection(0, textctrl->GetLastPosition()); + + textctrl->SetRTL(!textctrl->GetRTL()); + + textctrl->SetFocus(); + return; + + } + + long start, end; + textctrl->GetSelection(&start, &end); + + //printf("TextAttr flags: %x\n", textattr.GetFlags()); + textattr.SetFont(font); + + textattr.SetFlags(flags); + //printf("TextAttr flags: %x\n", textattr.GetFlags()); + + textctrl->SetStyle(start, end, textattr); + + textctrl->SetFocus(); + } + +}; + +class LayoutPanel: public wxPanel{ + public: + LayoutPanel(wxWindow* parent, wxWindowID id) : wxPanel(parent, id){ + Connect(wxEVT_ETC_LAYOUT_NEEDED, wxCommandEventHandler(LayoutPanel::OnLayoutNeeded)); + } + + void OnLayoutNeeded(wxCommandEvent &event){ + GetSizer()->Layout(); + event.Skip(); + } +}; + +class ExpandoSplitter : public wxSplitterWindow{ + public: + + bool resizing; + ExpandoTextCtrl *expando; + + + + + ExpandoSplitter(wxWindow* parent, wxWindowID id, const wxPoint& point = wxDefaultPosition, + const wxSize& size = wxDefaultSize, long style=wxSP_3D, + const wxString& name = L"splitterWindow") + : wxSplitterWindow(parent, id, point, size, style, name){ + + resizing = false; + + SetSashGravity(1); + SetMinimumPaneSize(10); + + Connect(wxEVT_ETC_LAYOUT_NEEDED, wxCommandEventHandler(ExpandoSplitter::OnExpandEvent)); + Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(ExpandoSplitter::OnSplitterStart)); + Connect(wxEVT_LEFT_UP, wxCommandEventHandler(ExpandoSplitter::OnSplitterSet)); + + } + + + void OnSplitterStart(wxCommandEvent& event){ + + resizing = true; + + expando->SetMinHeight(expando->GetNatHeight()); + + event.Skip(); + + } + + void OnSplitterSet(wxCommandEvent& event){ + + resizing = false; + event.Skip(); + + int natHeight = expando->GetNatHeight(); + int setHeight = expando->GetSize().GetHeight(); + int height = setHeight <= natHeight? -1 : setHeight; + expando->SetMinHeight(height); + + + } + + void OnExpandEvent(wxCommandEvent& event){ + if(resizing) return; + int h = GetSashSize() + expando->GetMinSize().GetHeight(); + //printf("Expando Event resize: %d\n", h); + //printf("MaxHeight: %d\tMaxSize.Height: %d\n", expando->GetMaxHeight(), expando->GetMaxSize().GetHeight()); + //printf("MinHeight: %d\tMinSize.Height: %d\n\n", expando->GetMinHeight(), expando->GetMinSize().GetHeight()); + + SetSashPosition(GetSize().GetHeight() - h); + + //SetSashPosition(GetSize().GetHeight() - GetSashSize() - expando->GetMinSize().GetHeight()); + } + + void RegisterExpando(ExpandoTextCtrl *expando){ + this->expando = expando; + + } + + void OnDoubleClickSash(int x, int y){ + return; + } + + +}; + + + +void CreateTestWindow(ExpandoApp *app, wxString title){ + wxFrame *frame = new wxFrame(NULL, -1, title, wxPoint(200,100), wxSize(400,400)); + wxBoxSizer *fs = new wxBoxSizer(wxVERTICAL); + frame->SetSizer(fs); + LayoutPanel *panel = new LayoutPanel(frame, -1); + wxBoxSizer *s = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(s); + fs->Add(panel, 1, wxEXPAND); + + + ExpandoSplitter *splitter = new ExpandoSplitter(panel, -1, wxDefaultPosition, wxDefaultSize, 512|256|wxSP_LIVE_UPDATE); + s->Add(splitter, 1, wxEXPAND); + + + wxPanel *top = new wxPanel(splitter, -1); + //wxPanel *bottom = new wxPanel(splitter, -1); + wxBoxSizer *st = new wxBoxSizer(wxVERTICAL); + //wxBoxSizer *sb = new wxBoxSizer(wxVERTICAL); + top->SetSizer(st); + //bottom->SetSizer(sb); + + //splitter->SplitHorizontally(top,bottom); + + //s->Add(top, 1, wxEXPAND); + //s->Add(bottom, 0, wxEXPAND); + + output = new wxTextCtrl(top, -1, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE); + input = new wxTextCtrl(top, -1, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE); + + st->Add(output, 1, wxEXPAND); + st->Add(input, 1, wxEXPAND); + +//============================================================================= + + INPUTCTRL *etc = new INPUTCTRL(splitter, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE); + etc->AutoSetRTL(); + + #ifdef TEST + etc->SetStuff(&encoder, &converter, output, input); + #endif + + splitter->RegisterExpando(etc); + + //etc->SetRTF(L"{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Segoe UI;}}\r\n{\\colortbl ;\\red0\\green0\\blue0;\\red255\\green255\\blue255;}\r\n{\\*\\generator Msftedit 5.41.21.2509;}\\viewkind4\\uc1\\pard\\cf1\\highlight2\\f0\\fs18 test \\b bold\\b0 \\i italic\\i0 \\b\\i bolditalic\\b0 \\ul italicunderline\\i0 \\b underline bold\\par\r\n}\r\n"); + + DebugBar *debugbar = new DebugBar(top,-1,etc); + ToolBar *toolbar = new ToolBar(top, -1, etc); + + st->Add(debugbar, 0, wxEXPAND); + st->Add(toolbar, 0, wxEXPAND); + //sb->Add(etc, 0, wxEXPAND); + + //wxFont font(22, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Segoe UI"); + //wxTextAttr attr(wxColor(255,0,255), *wxWHITE, font); + + wxFont font(22, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, true, L"Comic Sans MS"); + wxTextAttr attr(wxColor(255,0,255), wxColor(0,255,255), font); + + etc->SetStyle(0, 0, attr); + +#if USEEXPANDO + etc->SetMaxHeight(100); +#else + etc->SetMinSize(wxSize(etc->GetMinSize().GetX(), 60)); +#endif //USEEXPANDO + + //etc->SetBackgroundColour(wxColor(255,255,0)); + + + frame->Layout(); + //top->Layout(); + //bottom->Layout(); + splitter->SplitHorizontally(top,etc); + frame->Show(true); + app->SetTopWindow(frame); + + + wxExpandEvent evt(wxEVT_ETC_LAYOUT_NEEDED, etc->GetId()); + evt.SetEventObject(etc); + evt.height = etc->GetMinSize().GetHeight(); + evt.numLines = etc->GetNumberOfLines(); + etc->GetEventHandler()->ProcessEvent(evt); + + + etc->SetFocus(); +} + +bool ExpandoApp::OnInit(){ + + #if DBG_CONSOLE + AllocConsole(); + freopen("CONIN$", "rb", stdin); + freopen("CONOUT$", "wb", stdout); + freopen("CONOUT$", "wb", stderr); + #endif + + CreateTestWindow(this, L"Test"); + + return true; +} + +IMPLEMENT_APP(ExpandoApp) + diff --git a/digsby/ext/src/RTFToX/XHTMLEncoder.cpp b/digsby/ext/src/RTFToX/XHTMLEncoder.cpp new file mode 100644 index 0000000..067f79c --- /dev/null +++ b/digsby/ext/src/RTFToX/XHTMLEncoder.cpp @@ -0,0 +1,135 @@ +#include "XHTMLEncoder.h" + + +MarkupDesc* +XHTMLEncoder::GetMarkup(const StyleDesc& styleDesc, unsigned char dirtyMask, bool base) const{ + + MarkupDesc *md = new MarkupDesc; + + if((dirtyMask == STYLEBGCLR) && styleDesc.bgcolor->IsWhite()){ + return md; + } + + wxString stylestring; + unsigned int contentflags = 0; + + if(dirtyMask & STYLEFONT){ + stylestring.Append(wxString::Format(L" font-family: %s;", styleDesc.font->name)); + contentflags |= STYLEFONT; + } + + if(dirtyMask & STYLESIZE){ + stylestring.Append(wxString::Format(L" font-size: %dpt;", styleDesc.size/2)); + contentflags |= STYLESIZE; + } + + if(dirtyMask & STYLEITAL){ + if(styleDesc.styleflags & STYLEITAL){ + stylestring.Append(L" font-style: italic;"); + }else{ + stylestring.Append(L" font-style: normal;"); + } + + contentflags |= STYLEITAL; + + } + + if(dirtyMask & STYLEBOLD){ + if(styleDesc.styleflags & STYLEBOLD){ + stylestring.Append(L" font-weight: bold;"); + }else{ + stylestring.Append(L" font-weight: normal;"); + } + + contentflags |= STYLEBOLD; + + } + + + + if(dirtyMask & STYLECOLOR){ + stylestring.Append(wxString::Format(L" color: #%s;", styleDesc.color->ToHex())); + contentflags |= STYLECOLOR; + } + + if(dirtyMask & STYLEBGCLR && !styleDesc.bgcolor->IsWhite()){ + if(base){ + md->GetLast()->next = GetMarkup(styleDesc, STYLEBGCLR, false); + dirtyMask = dirtyMask & ~STYLEBGCLR; + }else{ + stylestring.Append(wxString::Format(L" background-color: #%s;", styleDesc.bgcolor->ToHex())); + contentflags |= STYLEBGCLR; + } + } + + if((dirtyMask & STYLEUNDL) && (styleDesc.styleflags & STYLEUNDL)){ + if(contentflags){ + md->GetLast()->next = GetMarkup(styleDesc, STYLEUNDL, false); + dirtyMask = dirtyMask & ~STYLEUNDL; + }else{ + + stylestring.Append(L" text-decoration: underline;"); + + contentflags |= STYLEUNDL; + } + } + + + + if(!stylestring.IsEmpty()){ + + if (stylestring.StartsWith(L" ")){ + stylestring.Trim(false); // from left + } + + md->initmarkup.Printf(L"", stylestring); + md->contentflags = base? 0 : contentflags; + md->termmarkup = L""; + md->formatMask = dirtyMask; + } + + + return md; + +} + +wxString +XHTMLEncoder::FormatString(const wxString& originalString) const{ + wxString workingString(originalString); + + workingString.Replace(L"&", L"&"); + workingString.Replace(L"<", L"<"); + workingString.Replace(L">", L">"); + workingString.Replace(L"\x00A0", L" "); + + workingString.Replace(L"\n", L"
"); + + workingString = EscapeUnicode(workingString, &HTMLEscapeUnicode); + + return workingString; +} + +wxString +XHTMLEncoder::FormatLink(const wxString& target, const wxString& text) const{ + return wxString::Format(L"%s", FormatString(target), FormatString(text)); +} + +wxString +XHTMLEncoder::GetHeader() const{ + return wxEmptyString; +} + +wxString +XHTMLEncoder::GetFooter() const{ + return wxEmptyString; +} + +unsigned int +XHTMLEncoder::GetTerminalFlags(const StyleDesc& /*styleDesc*/, unsigned char dirtyMask) const{ + return dirtyMask & (STYLEFONT | STYLESIZE | STYLEITAL | STYLEBOLD | STYLEUNDL | STYLECOLOR | STYLEBGCLR); +} + +wxString +XHTMLEncoder::FormatUnicodeChar(unsigned long unichar) const{ + return HTMLEscapeUnicode(unichar); +} diff --git a/digsby/ext/src/RTFToX/XHTMLEncoder.h b/digsby/ext/src/RTFToX/XHTMLEncoder.h new file mode 100644 index 0000000..8016b70 --- /dev/null +++ b/digsby/ext/src/RTFToX/XHTMLEncoder.h @@ -0,0 +1,22 @@ +#ifndef XHTMLENCODER_H +#define XHTMLENCODER_H + +#include "wx/String.h" +#include "StyleDescs.h" +#include "Encoder.h" +#include "StringUtil.h" + +//#define HTMLFONT (STYLEFONT | STYLESIZE | STYLEBOLD | STYLEITAL) + +class XHTMLEncoder: public Encoder{ + public: + MarkupDesc* GetMarkup(const StyleDesc& styleDesc, unsigned char dirtyMask, bool base) const; + wxString FormatString(const wxString& originalString) const; + wxString FormatLink(const wxString& target, const wxString& text) const; + wxString GetHeader() const; + wxString GetFooter() const; + unsigned int GetTerminalFlags(const StyleDesc& styleDesc, unsigned char dirtyMask) const; + wxString FormatUnicodeChar(unsigned long unichar) const; +}; + +#endif //CHTMLENCODER_H \ No newline at end of file diff --git a/digsby/ext/src/RTFToX/YahooEncoder.cpp b/digsby/ext/src/RTFToX/YahooEncoder.cpp new file mode 100644 index 0000000..e61656b --- /dev/null +++ b/digsby/ext/src/RTFToX/YahooEncoder.cpp @@ -0,0 +1,97 @@ +#include "YahooEncoder.h" + +#define STYLEFTAG (STYLEFONT | STYLESIZE) + +MarkupDesc* +YahooEncoder::GetMarkup(const StyleDesc &styleDesc, unsigned char dirtyMask, bool base) const{ + + MarkupDesc *md = new MarkupDesc; + + MarkupDesc *mdp = md; + + unsigned char currentMask = dirtyMask; + + if(currentMask & STYLEFTAG){ + + + wxString fonttag(L"name)); + if(!base) mdp->contentflags |= STYLEFONT; + } + + if(currentMask & STYLESIZE){ + fonttag.Append(wxString::Format(L" size=\"%d\"", (int)styleDesc.size/2)); + if(!base) mdp->contentflags |= STYLESIZE; + } + + fonttag.Append(L">"); + + mdp->initmarkup = fonttag; + mdp->termmarkup = L""; + mdp->formatMask = currentMask & STYLEFTAG; + + currentMask = currentMask & ~STYLEFTAG; + } + + if(currentMask & STYLEBOLD && styleDesc.styleflags & STYLEBOLD){ + mdp = mdp->NewDesc(L"\x1b[1m", L"\x1b[x1m", STYLEBOLD, STYLEBOLD); + currentMask = currentMask & ~STYLEBOLD; + } + + if(currentMask & STYLEITAL && styleDesc.styleflags & STYLEITAL){ + mdp = mdp->NewDesc(L"\x1b[2m", L"\x1b[x2m", STYLEITAL, STYLEITAL); + currentMask = currentMask & ~STYLEITAL; + } + + if(currentMask & STYLEUNDL && styleDesc.styleflags & STYLEUNDL){ + mdp = mdp->NewDesc(L"\x1b[4m", L"\x1b[x4m", STYLEUNDL, STYLEUNDL); + currentMask = currentMask & ~STYLEUNDL; + } + + if(dirtyMask & STYLECOLOR){ + mdp = mdp->NewDesc(wxString::Format(L"\x1b[#%sm", styleDesc.color->ToHex()), + wxEmptyString, + STYLECOLOR, STYLECOLOR); + } + + //STYLEBGCLR not supported + + return md; + +} + +wxString +YahooEncoder::FormatString(const wxString& originalString) const{ + //TODO: Can escape special chars in yahoo? + return originalString; +} + +wxString +YahooEncoder::FormatLink(const wxString& target, const wxString& text) const{ + return FormatString(wxString::Format(L"%s(%s)", text, target)); +} + +wxString +YahooEncoder::GetHeader() const{ + return wxEmptyString; +} + + +wxString +YahooEncoder::GetFooter() const{ + return wxEmptyString; +} + +unsigned int +YahooEncoder::GetTerminalFlags(const StyleDesc& /*styleDesc*/, unsigned char dirtyMask) const{ + return dirtyMask & (STYLEFONT | STYLESIZE | STYLEITAL | STYLEBOLD | STYLEUNDL | STYLECOLOR); +} + +wxString +YahooEncoder::FormatUnicodeChar(unsigned long unichar) const{ + + //TODO: Is this what Yahoo wants? + return wxString::Format(L"%c", unichar); +} diff --git a/digsby/ext/src/RTFToX/YahooEncoder.h b/digsby/ext/src/RTFToX/YahooEncoder.h new file mode 100644 index 0000000..6acaecc --- /dev/null +++ b/digsby/ext/src/RTFToX/YahooEncoder.h @@ -0,0 +1,19 @@ +#ifndef YAHOOENCODER_H +#define YAHOOENCODER_H + +#include "wx/String.h" +#include "StyleDescs.h" +#include "Encoder.h" + +class YahooEncoder: public Encoder{ + public: + MarkupDesc* GetMarkup(const StyleDesc& styleDesc, unsigned char dirtyMask, bool base) const; + wxString FormatString(const wxString& originalString) const; + wxString FormatLink(const wxString& target, const wxString& text) const; + wxString GetHeader() const; + wxString GetFooter() const; + unsigned int GetTerminalFlags(const StyleDesc& styleDesc, unsigned char dirtyMask) const; + wxString FormatUnicodeChar(unsigned long unichar) const; +}; + +#endif //YAHOOENCODER_H \ No newline at end of file diff --git a/digsby/ext/src/RTFToX/test.py b/digsby/ext/src/RTFToX/test.py new file mode 100644 index 0000000..018b7a8 --- /dev/null +++ b/digsby/ext/src/RTFToX/test.py @@ -0,0 +1,102 @@ +import sys; +sys.path.append('C:\\Users\\Aaron\\workspace\\DigsbyTrunk\\digsby'); + +#import os +#os.chdir('C:\\Users\\Aaron\\workspace\\DigsbyTrunk\\digsby') + +from ctypes import windll +windll.comctl32.InitCommonControls() + +import wx +from tests.testapp import testapp +from win32events import bindwin32 +from gui.uberwidgets.formattedinput2.FormattedExpandoTextCtrl import FormattedExpandoTextCtrl +FormattedExpandoTextCtrl.BindWin32 = bindwin32 + +from cgui import ExpandoTextCtrl + +from gui.uberwidgets.formattedinput2.fontutil import FontFromFacename +from cgui import EVT_ETC_LAYOUT_NEEDED + +def _(text): + return text +__builtins__._ = _ + +from gui.uberwidgets.formattedinput2.formattedinput import FormattedInput + + + + + +def NewInput(): + + + f = wx.Frame(None) + + f.Sizer = wx.BoxSizer(wx.VERTICAL) + + font = wx.Font(22, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, True, "Comic Sans MS"); + textattr = wx.TextAttr(wx.Color(255,0,255), wx.Color(0,255,255), font); + + #font = FontFromFacename('Arial') + #font.SetPointSize(10) + #textattr = wx.TextAttr(wx.Color(255,0,255), wx.WHITE, font) #@UndefinedVariable + + output = wx.TextCtrl(f, -1, style = wx.TE_READONLY | wx.TE_MULTILINE) + output.SetMinSize(wx.Size(200, 200)) + f.Sizer.Add(output, 1, wx.EXPAND) + + +# fo = {'default': False, +# 'italic': True} + + input = ExpandoTextCtrl(f, wx.ID_ANY) + input.SetStyle(0,0,textattr) + + #input = FormattedExpandoTextCtrl(f, multiFormat = True, format = textattr) + #input = FormattedInput(f, multiFormat = True, format = textattr)#, formatOptions = fo) + + def OnExpandEvent(event): + try: + height = (input.fbar.Size.height if input.FormattingBarIsShown() else 0) + input.tc.MinSize.height + except: + height = input.MinSize.height + + input.MinSize = wx.Size(-1, height) + f.Fit() + + def OnEnterKey(event): + + if event.KeyCode in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and event.Modifiers == wx.MOD_SHIFT: + try: + output.Value = input.tc.GetRTF() + except: + output.Value = input.GetRTF() + input.Clear() + + return + + event.Skip() + + + input.Bind(EVT_ETC_LAYOUT_NEEDED, OnExpandEvent) + try: + input.tc.Bind(wx.EVT_KEY_DOWN, OnEnterKey) + except: + input.Bind(wx.EVT_KEY_DOWN, OnEnterKey) + + f.Sizer.Add(input, 0, wx.EXPAND) + f.MinSize = f.BestSize + + f.Show() + f.Fit() + + input.SetFocus() + +if __name__ == '__main__': + app = testapp(plugins = False) + + NewInput() + + app.MainLoop() + diff --git a/digsby/ext/src/RingBuffer.h b/digsby/ext/src/RingBuffer.h new file mode 100644 index 0000000..dfef99b --- /dev/null +++ b/digsby/ext/src/RingBuffer.h @@ -0,0 +1,58 @@ +#ifndef RingBuffer_h +#define RingBuffer_h + +#include "Noncopyable.h" + +template +class RingBuffer : Noncopyable +{ +private: + T m_buffer[BufferSize]; + int m_index; + size_t m_size; + +public: + RingBuffer() + : m_index(-1) + , m_size(0) + {} + + ~RingBuffer() + { + } + + void append(const T& value) + { + if (++m_index >= BufferSize) + m_index = 0; + m_buffer[m_index] = value; + if (++m_size > BufferSize) m_size = BufferSize; + } + + /** + * Copies this RingBuffer's data into buffer as one contiguous block, in + * the "right" order. buffer will be filled with (and must be allocated + * with enough length to hold) this->size() items. + */ + void data(const T* buffer) + { + if (m_size < BufferSize) + memcpy((void*)buffer, &m_buffer, sizeof(T) * m_size); + else { + size_t next = m_index + 1; + if (next >= BufferSize) + next = 0; + + const size_t numAfterNext = BufferSize - next; + memcpy((void*)buffer, &m_buffer[next], numAfterNext * sizeof(T)); + memcpy((void*)(buffer + numAfterNext), &m_buffer, next * sizeof(T)); + } + } + + size_t index() const { return m_index; } + int bufferSize() const { return BufferSize; } + size_t size() const { return m_size; } +}; + +#endif // RingBuffer_h + diff --git a/digsby/ext/src/Sample.h b/digsby/ext/src/Sample.h new file mode 100644 index 0000000..482f23c --- /dev/null +++ b/digsby/ext/src/Sample.h @@ -0,0 +1,42 @@ +#ifndef EXT_Sample_h +#define EXT_Sample_h + +#include + +typedef time_t tick_t; + +#define SAMPLE_VERSION 1 + +#pragma pack(push) // all structs in this header should be packed +#pragma pack(1) + +struct SampleBase +{ + tick_t time; +}; + +struct Event : public SampleBase +{ + unsigned char eventId; +}; + +#ifdef WIN32 + +struct Sample : public SampleBase +{ + SIZE_T pagefileUsage; +}; + +#else +struct Sample : SampleBase +{ + tick_t time; + // TODO +} +#endif // WIN32 + +#pragma pack(pop) + + + +#endif diff --git a/digsby/ext/src/ScrollWindow.cpp b/digsby/ext/src/ScrollWindow.cpp new file mode 100644 index 0000000..3a7a3b1 --- /dev/null +++ b/digsby/ext/src/ScrollWindow.cpp @@ -0,0 +1,262 @@ +#include "ScrollWindow.h" + +#include +using namespace std; + +IMPLEMENT_CLASS(SkinScrollWindow, wxWindow); + +BEGIN_EVENT_TABLE(SkinScrollWindow, wxWindow) + EVT_SIZE(SkinScrollWindow::OnSize) + EVT_SCROLLWIN(SkinScrollWindow::OnScroll) + EVT_MOUSEWHEEL(SkinScrollWindow::OnMouseWheel) +END_EVENT_TABLE() + +#define DEBUG(_x) ((void) 0) +//#define DEBUG(_x) _x + + +SkinScrollWindow::SkinScrollWindow(wxWindow* parent, wxWindowID id, int style) + : wxWindow(parent, id, wxDefaultPosition, wxDefaultSize, style) + , physicalScrolling(true) + , smoothScroll(false) +{ +} + +SkinScrollWindow::~SkinScrollWindow() +{ +} + +void SkinScrollWindow::AdjustScrollbars(int x, int y) +{ + wxRect r(GetRect()); + wxRect virtualRect(GetVirtualSize()); + + // x and y default to current position + if (x == -1) x = GetScrollPos(wxHORIZONTAL); + if (y == -1) y = GetScrollPos(wxVERTICAL); + + DEBUG(cout << "Setting scrollbars to (" << x << ", " << y << ")" << endl); + SetScrollbar(wxHORIZONTAL, x, r.width, virtualRect.width); + SetScrollbar(wxVERTICAL, y, r.height, virtualRect.height); +} + +#ifdef __WXMSW__ + +inline ostream& operator<< (ostream& o, const RECT& r) +{ + o << "RECT(" << r.left << ", " << r.top << ", " << r.right << ", " << r.bottom << ")"; + return o; +} + +void SkinScrollWindow::ScrollWindow(int dx, int dy, const wxRect* prect) +{ + if ( !smoothScroll ) + { + return wxWindow::ScrollWindow(dx, dy, prect); + } + + RECT *pr; + pr = NULL; + DEBUG(cout << "ScrollWindowEx " << dx << " " << dy << " " << "NULL"); + + + + int res = ::ScrollWindowEx(GetHwnd(), dx, dy, pr, pr, NULL, NULL, + SW_INVALIDATE); + // MAKELONG(SW_SMOOTHSCROLL, 100)); + ::UpdateWindow(GetHwnd()); + + switch(res) + { + case SIMPLEREGION: + DEBUG(cout << " -> SIMPLEREGION" << endl); + break; + case COMPLEXREGION: + DEBUG(cout << " -> COMPLEXREGION" << endl); + break; + case NULLREGION: + DEBUG(cout << " -> NULLREGION" << endl); + break; + case ERROR: + DEBUG(cout << " -> ERROR: " << GetLastError() << endl); + break; + }; + +} +#endif + +#define EIF(type, str) if ( t == type ) return wxString(wxT(str)); + +void SkinScrollWindow::OnScroll(wxScrollWinEvent& e) +{ + OnHandleScroll(e); +} + +void SkinScrollWindow::OnHandleScroll(wxScrollWinEvent& e) +{ + DEBUG(cout << "OnHandleScroll " << GetEventTypeString(e.GetEventType()).mb_str() << endl); + + e.Skip(); + wxPoint lineSize(10, 10); // TODO: make accessor + + wxRect clientRect(GetClientRect()); + wxRect virtualRect(GetVirtualSize()); + wxEventType scrollType(e.GetEventType()); + int orientation = e.GetOrientation(); + bool h = orientation == wxHORIZONTAL; + + int x = GetScrollPos(wxHORIZONTAL), + y = GetScrollPos(wxVERTICAL); + + wxPoint newPos(x, y); + wxPoint pageSize(clientRect.width, clientRect.height); + + // THUMBTRACK: dragging the scroll thumb + if ( scrollType == wxEVT_SCROLLWIN_THUMBTRACK ) { + if (h) newPos.x = e.GetPosition(); + else newPos.y = e.GetPosition(); + + // THUMBRELEASE: mouse up on the thumb + } else if ( scrollType == wxEVT_SCROLLWIN_THUMBRELEASE ) { + if (h) newPos.x = e.GetPosition(); + else newPos.y = e.GetPosition(); + + // LINEDOWN: clicking the down arrow + } else if ( scrollType == wxEVT_SCROLLWIN_LINEDOWN ) { + if (h) newPos.x += lineSize.x; + else newPos.y += lineSize.y; + + // LINEUP: clicking the up arrow + } else if ( scrollType == wxEVT_SCROLLWIN_LINEUP ) { + if (h) newPos.x -= lineSize.x; + else newPos.y -= lineSize.y; + + // PAGEDOWN: clicking below the scroll thumb + } else if ( scrollType == wxEVT_SCROLLWIN_PAGEDOWN ) { + // self.RefreshRect() // why is this necessary? + if (h) newPos.x += pageSize.x; + else newPos.y += pageSize.y; + + // PAGEUP: clicking above the scroll thumb + } else if ( scrollType == wxEVT_SCROLLWIN_PAGEUP ) { + // self.Refresh() + if (h) newPos.x -= pageSize.x; + else newPos.y -= pageSize.y; + } + + // keep scroll position within bounds + int maxx = virtualRect.width - clientRect.width, + maxy = virtualRect.height - clientRect.height; + + if (newPos.x < 0) newPos.x = 0; + else if (newPos.x > maxx) newPos.x = maxx; + + if (newPos.y < 0) newPos.y = 0; + else if (newPos.y > maxy) newPos.y = maxy; + + if ( physicalScrolling ) + SkinScrollWindow::ScrollWindow(-(newPos.x - x), -(newPos.y - y)); + else + Refresh(); + + // readjust scrollbars + AdjustScrollbars(newPos.x, newPos.y); +} + +void SkinScrollWindow::Scroll(int x, int y) +{ + DEBUG(cout << "SkinScrollWindow::Scroll(" << x << ", " << y << ")" << endl); + + int oldx = GetScrollPos(wxHORIZONTAL); + int oldy = GetScrollPos(wxVERTICAL); + + wxPoint oldPos(oldx, oldy); + + // -1 for either argument means no change in that direction + wxPoint newPos((x < 0 ? oldx : x), (y < 0 ? oldy : y)); + + if ( newPos != oldPos ) { + if ( physicalScrolling ) { + wxPoint relative(oldPos - newPos); + ScrollWindow(relative.x, relative.y); + } else { + Refresh(); + } + + // readjust scrollbars + AdjustScrollbars(newPos.x, newPos.y); + } + +} + +void SkinScrollWindow::OnMouseWheel(wxMouseEvent& event) +{ + int wheelRotation = 0; + wheelRotation += event.GetWheelRotation(); + int lines = wheelRotation / event.GetWheelDelta(); + wheelRotation -= lines * event.GetWheelDelta(); + + if (!lines) + return; + + wxScrollWinEvent newEvent(0, 0, wxVERTICAL); + newEvent.SetEventObject(this); + + if (event.IsPageScroll()) { + newEvent.SetEventType(lines > 0 ? wxEVT_SCROLLWIN_PAGEUP : wxEVT_SCROLLWIN_PAGEDOWN); + GetEventHandler()->ProcessEvent(newEvent); + } else { + lines *= event.GetLinesPerAction(); + newEvent.SetEventType(lines > 0 ? wxEVT_SCROLLWIN_LINEUP : wxEVT_SCROLLWIN_LINEDOWN); + + for (int times = abs(lines); times > 0; --times) + GetEventHandler()->ProcessEvent(newEvent); + } +} + +void SkinScrollWindow::EnablePhysicalScrolling(bool enable) +{ + physicalScrolling = enable; +} + +void SkinScrollWindow::PrepareDC(wxDC& dc) +{ + wxPoint pt(dc.GetDeviceOrigin()); + int x = GetScrollPos(wxHORIZONTAL), + y = GetScrollPos(wxVERTICAL); + + dc.SetDeviceOrigin(pt.x - x, pt.y - y); +} + +// override SetVirtualSize so we can AdjustScrollbars +void SkinScrollWindow::SetVirtualSize(int width, int height) +{ + wxWindow::SetVirtualSize(width, height); + AdjustScrollbars(); + Refresh(); +} + +void SkinScrollWindow::SetVirtualSize(const wxSize& size) +{ + SetVirtualSize(size.x, size.y); +} + +void SkinScrollWindow::OnSize(wxSizeEvent& event) +{ + event.Skip(); + AdjustScrollbars(); +} + +int SkinScrollWindow::GetViewStart() const +{ + return GetScrollPos(wxVERTICAL); +} + +wxRect SkinScrollWindow::GetViewRect() const +{ + int y = GetViewStart(); + wxRect rect(GetClientRect()); + rect.Offset(0, y); + + return rect; +} diff --git a/digsby/ext/src/ScrollWindow.h b/digsby/ext/src/ScrollWindow.h new file mode 100644 index 0000000..be253c2 --- /dev/null +++ b/digsby/ext/src/ScrollWindow.h @@ -0,0 +1,76 @@ +/** + ScrollWindow.h + + Like wxScrolledWindow, except uses the pixel vaulues set by + wxWindow::SetVirtualSize as the only units to scroll by. +*/ + +#ifndef _SCROLLWINDOW_INCLUDED__H_ +#define _SCROLLWINDOW_INCLUDED__H_ + +#include "wx/wxprec.h" +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + + +class SkinScrollWindow : public wxWindow { +public: + SkinScrollWindow(wxWindow* parent, wxWindowID = -1, int style = wxBORDER_NONE); + virtual ~SkinScrollWindow(); + + void AdjustScrollbars(int x = -1, int y = -1); + void PrepareDC(wxDC& dc); + + void Scroll(int x, int y); + void EnablePhysicalScrolling(bool enable); + bool IsPhysicalScrollingEnabled() const { return physicalScrolling; } + + void SetVirtualSize(const wxSize& size); + void SetVirtualSize(int width, int height); + + int GetViewStart() const; + wxRect GetViewRect() const; + + + bool CanSmoothScroll() const + { +#ifdef __WXMSW__ + return true; +#else + return false; +#endif + } + + bool SetSmoothScrolling(bool useSmooth) + { +#ifdef __WXMSW__ + return smoothScroll = useSmooth; +#else + return false; +#endif + } + +#ifdef __WXMSW__ + void ScrollWindow(int dx, int dy, const wxRect* prect = NULL); +#endif + +#ifndef SWIG +protected: + bool physicalScrolling; + bool smoothScroll; + + // event handling callbacks. + void OnScroll(wxScrollWinEvent& event); + virtual void OnHandleScroll(wxScrollWinEvent& event); + + void OnSize(wxSizeEvent& event); + void OnMouseWheel(wxMouseEvent& event); + + DECLARE_CLASS(SkinScrollWindow) + DECLARE_EVENT_TABLE() +#endif + +}; + +#endif diff --git a/digsby/ext/src/ScrollWindow.i b/digsby/ext/src/ScrollWindow.i new file mode 100644 index 0000000..8ff970d --- /dev/null +++ b/digsby/ext/src/ScrollWindow.i @@ -0,0 +1,31 @@ +%module scrollwindow + +%{ + +#include "wx/wxPython/wxPython.h" +#include "wx/wxPython/pyclasses.h" +#include "scrollwindow.h" + +%} + +%import typemaps.i +%import my_typemaps.i + +%import core.i +%import windows.i + +class ScrollWindow : public wxWindow { +public: + ScrollWindow(wxWindow* parent, wxWindowID = -1); + virtual ~ScrollWindow(); + + void AdjustScrollbars(int x = -1, int y = -1); + void PrepareDC(wxDC& dc); + + void SetVirtualSize(const wxSize& size); + void SetVirtualSize(int width, int height); + +protected: + void handleScrollEvent(wxScrollWinEvent& e); + +}; diff --git a/digsby/ext/src/SelectionEvent.cpp b/digsby/ext/src/SelectionEvent.cpp new file mode 100644 index 0000000..e2d7daf --- /dev/null +++ b/digsby/ext/src/SelectionEvent.cpp @@ -0,0 +1,14 @@ +#include "SelectionEvent.h" + +DEFINE_EVENT_TYPE(wxEVT_SELECTION_CHANGED); +IMPLEMENT_DYNAMIC_CLASS(wxSelectionEvent, wxEvent) + +wxSelectionEvent::wxSelectionEvent(WXTYPE commandEventType, int id, long selectionStart, long selectionEnd) : wxCommandEvent(commandEventType, id){ + this->selectionStart = selectionStart; + this->selectionEnd = selectionEnd == -1? selectionStart : selectionEnd; +} + +wxSelectionEvent::wxSelectionEvent(const wxSelectionEvent &event) : wxCommandEvent(event){ + this->selectionStart = event.selectionStart; + this->selectionEnd = event.selectionEnd; +} \ No newline at end of file diff --git a/digsby/ext/src/SelectionEvent.h b/digsby/ext/src/SelectionEvent.h new file mode 100644 index 0000000..5060fe1 --- /dev/null +++ b/digsby/ext/src/SelectionEvent.h @@ -0,0 +1,26 @@ +#ifndef _SELECTION_CHANGED_EVENT_ +#define _SELECTION_CHANGED_EVENT_ + +#include "wx/Event.h" + +DECLARE_EVENT_TYPE(wxEVT_SELECTION_CHANGED, 7778) + +#define EVT_SELECTION_CHANGED(fn) \ + DECLARE_EVENT_TABLE_ENTRY( wxEVT_SELECTION_CHANGED, wxID_ANY, wxID_ANY, \ + (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction)&fn, (wxObject*) NULL ), + +class wxSelectionEvent : public wxCommandEvent{ + public: + wxSelectionEvent(WXTYPE commandEventType = 0, int id = 0, long selectionStart = 0, long selectionEnd = -1); + wxSelectionEvent( const wxSelectionEvent &event ); + + virtual wxEvent *Clone() const { return new wxSelectionEvent(*this); } + + long selectionStart; + long selectionEnd; + + DECLARE_DYNAMIC_CLASS(wxSelectionEvent) +}; + + +#endif //_SELECTION_CHANGED_EVENT_ \ No newline at end of file diff --git a/digsby/ext/src/SkinSplitter.cpp b/digsby/ext/src/SkinSplitter.cpp new file mode 100644 index 0000000..b7eca57 --- /dev/null +++ b/digsby/ext/src/SkinSplitter.cpp @@ -0,0 +1,124 @@ +#include "skinsplitter.h" +#include +#include +#include +#include + +#if WXPY + +#else +#include "wx/wxPython/wxPython.h" +#endif + +BEGIN_EVENT_TABLE(SkinSplitter, wxSplitterWindow) + EVT_LEFT_DOWN(SkinSplitter::OnLeftDown) + EVT_SPLITTER_DCLICK(wxID_ANY, SkinSplitter::OnDoubleClick) +END_EVENT_TABLE() + +SkinSplitter::SkinSplitter(wxWindow* parent, long style) + : wxSplitterWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style) + , native(true) +{ + m_permitUnsplitAlways = false; +} + +SkinSplitter::~SkinSplitter() {} + +void SkinSplitter::DrawSash(wxDC& dc) +{ + // don't draw sash if we're not split + if ( m_sashPosition == 0 || !m_windowTwo ) + return; + + // nor if we're configured to not show it + if ( HasFlag(wxSP_NOSASH) ) + return; + + wxRect rect(GetClientRect()); + int sashSize = GetSashSize(); + + if ( m_splitMode == wxSPLIT_VERTICAL ) { + rect.x = m_sashPosition; + rect.width = sashSize; + } else { + rect.y = m_sashPosition; + rect.height = sashSize; + } + + if ( !native ) { + // not in native mode--draw the rectangle with our splitter brush + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(brushes[m_isHot ? (wxGetMouseState().LeftDown() ? ACTIVE : HOVER) : NORMAL]); + dc.DrawRectangle(rect); + } else { + // from wx/generic/splitter.cpp + wxRendererNative::Get().DrawSplitterSash(this, dc, GetClientSize(), m_sashPosition, + m_splitMode == wxSPLIT_VERTICAL ? wxVERTICAL + : wxHORIZONTAL, + m_isHot ? (int)wxCONTROL_CURRENT : 0); + } +} + +void SkinSplitter::SetSplitterColors(const wxColour& normal, const wxColor& active, const wxColor& hover) +{ + brushes[NORMAL] = wxBrush(normal); + brushes[ACTIVE] = wxBrush(active); + brushes[HOVER] = wxBrush(hover); + + + Refresh(); +} + +void SkinSplitter::SetNative(bool native) { + this->native = native; + Refresh(); +} + +bool SkinSplitter::GetNative() const { + return native; +} + +void SkinSplitter::OnEnterSash() +{ + if ( native ) { + return wxSplitterWindow::OnEnterSash(); + } else { + SetResizeCursor(); + RedrawSplitter(true); + } +} + +void SkinSplitter::OnLeaveSash() +{ + if (native) { + return wxSplitterWindow::OnLeaveSash(); + } else { + SetCursor(*wxSTANDARD_CURSOR); + RedrawSplitter(false); + } +} + +void SkinSplitter::RedrawSplitter(bool isHot) +{ + m_isHot = isHot; +#ifndef __WXMAC__ + wxClientDC dc(this); + DrawSash(dc); +#endif +} + +void SkinSplitter::OnLeftDown(wxMouseEvent& event) +{ + event.Skip(); + + if (!native) { + Refresh(); + } +} + + +void SkinSplitter::OnDoubleClick(wxSplitterEvent& event) +{ + // don't allow double click to unsplit + event.Veto(); +} diff --git a/digsby/ext/src/SkinSplitter.h b/digsby/ext/src/SkinSplitter.h new file mode 100644 index 0000000..2a8e07b --- /dev/null +++ b/digsby/ext/src/SkinSplitter.h @@ -0,0 +1,47 @@ +#ifndef _SKINSPLITTER_H_ +#define _SKINSPLITTER_H_ + +#include +#include +#include "Python.h" + +enum SplitterStates { + NORMAL = 0, + ACTIVE = 1, + HOVER = 2 +}; + +class SkinSplitter : public wxSplitterWindow { +public: +#if SWIG + %pythonAppend SkinSplitter "self._setOORInfo(self)" +#endif + SkinSplitter(wxWindow* parent, long style = wxSP_LIVE_UPDATE | wxNO_BORDER); + virtual ~SkinSplitter(); + + // Set/get the color of the splitter. + void SetSplitterColors(const wxColour& normal, const wxColour& active, const wxColour& hover); + + // Set/get native mode + void SetNative(bool native); + bool GetNative() const; + +protected: + bool native; + wxBrush brushes[3]; + + void DrawSash(wxDC& dc); + void RedrawSplitter(bool isHot); + + void OnEnterSash(); + void OnLeaveSash(); + + void OnLeftDown(wxMouseEvent& event); + void OnDoubleClick(wxSplitterEvent& event); + +#ifndef SWIG + DECLARE_EVENT_TABLE() +#endif +}; + +#endif diff --git a/digsby/ext/src/SkinSplitter.i b/digsby/ext/src/SkinSplitter.i new file mode 100644 index 0000000..d90bd62 --- /dev/null +++ b/digsby/ext/src/SkinSplitter.i @@ -0,0 +1,22 @@ + +%{ +#include "wx/wxPython/wxPython.h" +#include "wx/wxPython/pyclasses.h" +#include "wx/wxPython/pyistream.h" + +#include +#include +#include +#include +#include "skinsplitter.h" +%} + +%import typemaps.i +%import my_typemaps.i + +%import core.i +%import windows.i +%import misc.i + + +%include skinsplitter.h \ No newline at end of file diff --git a/digsby/ext/src/SplitImage4.cpp b/digsby/ext/src/SplitImage4.cpp new file mode 100644 index 0000000..ad270cf --- /dev/null +++ b/digsby/ext/src/SplitImage4.cpp @@ -0,0 +1,563 @@ +#include "SplitImage4.h" +#include + +#if __WXMSW__ +#include +#endif + +Extend extendCopy(const Extend& e) +{ + return Extend(e.up, e.down, e.left, e.right); +} + +/* +static void print_rect(const wxRect& r) { + fprintf(stderr, "(%d, %d, %d, %d)\n", r.x, r.y, r.width, r.height); +} +*/ + +long getObjectRef(wxObject* obj) { + return (long)obj->GetRefData(); +} + + +int max(const int& val1, const int& val2){ + if(val1>val2) return val1; + return val2; +} + + +SplitImage4::SplitImage4(const ImageData& idata){ + SetImage(idata); +} + +//break up the image and store the pieces, also figures out any statistics it can early +void SplitImage4::SetImage(const ImageData& idata){ + + // Zero out all the pointers + splitimage.c1 = 0; + splitimage.c2 = 0; + splitimage.c3 = 0; + splitimage.c4 = 0; + splitimage.top = 0; + splitimage.bottom = 0; + splitimage.left = 0; + splitimage.right = 0; + splitimage.center = 0; + + path = idata.source; + + //Load the image + wxImage image(idata.source); + + if (!image.HasAlpha()) + image.InitAlpha(); + + //Get the base height and width + const int width = image.GetWidth() - 1; + const int height = image.GetHeight() - 1; + + Size = wxSize(width+1, height+1); + + //Localize the cuts for adjustment + int x1 = idata.x1; + int x2 = idata.x2; + int y1 = idata.y1; + int y2 = idata.y2; + + left = idata.x1; + top = idata.y1; + right = -idata.x2; + bottom = -idata.y2; + + //Associate the cuts to (0,0) + if (x1<0) + x1 = width+x1+1; + if (x2<0) + x2 = width+x2+1; + if (y1<0) + y1 = height+y1+1; + if (y2<0) + y2 = height+y2+1; + + //Determine the minimum size based on the cut positions + MinSize=wxSize(x1+(x2>0? width-x2 : 0),y1+(y2>0? height-y2 : 0)); + + //Determine left right top and bottom edges based of center extends and cuts + bool L = x1 && !idata.center.extends.left; + bool R = x2 && !idata.center.extends.right; + bool T = y1 && !idata.center.extends.up; + bool B = y2 && !idata.center.extends.down; + + // fprintf(stderr, "Edges: %d %d %d %d\n", L, R, T, B); + + //determine if each corner exists + bool c1 = !(L && idata.left.extends.up) && !(T && idata.top.extends.left) && (x1 && y1); + bool c2 = !(R && idata.right.extends.up) && !(T && idata.top.extends.right) && (x2 && y1); + bool c3 = !(L && idata.left.extends.down) && !(B && idata.bottom.extends.left) && (x1 && y2); + bool c4 = !(R && idata.left.extends.down) && !(B && idata.top.extends.right) && (x2 && y2); + + //creates the ratio of each edge region to the minsize of the image + ratio[0] = c1&&c2? (float)x1/(float)MinSize.GetWidth() : !c1?0:1; + ratio[1]= 1.0-ratio[0]; + ratio[2]= c1&&c3? (float)y1/(float)MinSize.GetHeight() : !c1?0:1; + ratio[3]= 1.0-ratio[2]; + + //cut each corner and store them in the ImageCluster + if(c1) + splitimage.c1 = new wxImage(image.GetSubImage(wxRect(wxPoint(0,0),wxPoint(x1-1,y1-1)))); + if(c2) + splitimage.c2 = new wxImage(image.GetSubImage(wxRect(wxPoint(x2,0),wxPoint(width,y1-1)))); + if(c3) + splitimage.c3 = new wxImage(image.GetSubImage(wxRect(wxPoint(0,y2),wxPoint(x1-1,height)))); + if(c4) + splitimage.c4 = new wxImage(image.GetSubImage(wxRect(wxPoint(x2,y2),wxPoint(width,height)))); + + //cut each side and store them in the ImageCluster + wxPoint pos1, pos2; + + if(T) + { + splitimage.top=new Slice; + pos1 = splitimage.top->pos = c1?wxPoint(x1,0):wxPoint(0,0); + pos2 = c2?wxPoint(x2-1,y1-1):wxPoint(width,y1-1); + splitimage.top->image=image.GetSubImage(wxRect(pos1,pos2)); + splitimage.top->hstyle = idata.top.hstyle; + splitimage.top->vstyle = idata.top.vstyle; + splitimage.top->offset = idata.top.offset; + splitimage.top->align = idata.top.align; + } + + if(L) + { + splitimage.left=new Slice; + pos1 = splitimage.left->pos=c1?wxPoint(0,y1):wxPoint(0,0); + pos2 = c3?wxPoint(x1-1,y2-1):wxPoint(x1,height); + splitimage.left->image=image.GetSubImage(wxRect(pos1,pos2)); + // fprintf(stderr, "L rect: "); + // print_rect(wxRect(pos1, pos2)); + splitimage.left->hstyle = idata.left.hstyle; + splitimage.left->vstyle = idata.left.vstyle; + splitimage.left->offset = idata.left.offset; + splitimage.left->align = idata.left.align; + } + + if(R) + { + splitimage.right=new Slice; + pos1 = splitimage.right->pos = c2?wxPoint(x2,y1):wxPoint(x2,0); + pos2 = c4?wxPoint(width,y2-1):wxPoint(width,height); + splitimage.right->image=image.GetSubImage(wxRect(pos1,pos2)); + splitimage.right->hstyle = idata.right.hstyle; + splitimage.right->vstyle = idata.right.vstyle; + splitimage.right->offset = idata.right.offset; + splitimage.right->align = idata.right.align; + + } + + if(B) + { + splitimage.bottom=new Slice; + pos1 = splitimage.bottom->pos = c3?wxPoint(x1,y2):wxPoint(0,y2); + + pos2 = c4 ? wxPoint(x2 - 1, height) : wxPoint(width, height); + splitimage.bottom->image = image.GetSubImage(wxRect(pos1,pos2)); + splitimage.bottom->hstyle = idata.bottom.hstyle; + splitimage.bottom->vstyle = idata.bottom.vstyle; + splitimage.bottom->offset = idata.bottom.offset; + splitimage.bottom->align = idata.bottom.align; + + } + + //The center too + splitimage.center = new Slice; + + // fprintf(stderr, "cuts: %d %d %d %d\n", x1, y1, x2, y2); + + pos1 = splitimage.center->pos = wxPoint((L ? x1 : 0),(T ? y1 : 0)); + pos2 = wxPoint(R?x2-1:width,B?y2-1:height); + splitimage.center->image = image.GetSubImage(wxRect(pos1,pos2)); + + // fprintf(stderr, "center rect"); + // print_rect(wxRect(pos1, pos2)); + + splitimage.center->hstyle = idata.center.hstyle; + splitimage.center->vstyle = idata.center.vstyle; + splitimage.center->offset = idata.center.offset; + splitimage.center->align = idata.center.align; +} + +void SplitImage4::Draw(wxDC* dc, const wxRect& rect, int){ + //Just forword to Render + + //Get the width and the height for the render + int w = rect.GetWidth() == -1 ? Size.GetWidth() : rect.GetWidth(); + int h = rect.GetHeight() == -1 ? Size.GetHeight() : rect.GetHeight(); + + //if width or hight is less than 1 pixel abort + if (w < 1 || h < 1) + return; + + Render(dc, w, h, rect.x, rect.y); +} + +//return bitmap +wxBitmap SplitImage4::GetBitmap(const wxSize& size, bool center){ + + //Get the width and the height for the render + int w = size.GetWidth() == -1 ? Size.GetWidth() : size.GetWidth(); + int h = size.GetHeight() == -1 ? Size.GetHeight() : size.GetHeight(); + + //if width or hight is less than 1 pixel abort + if (w < 1 || h < 1) + return wxBitmap(1,1); + + wxImage image(w,h); + if (!image.HasAlpha()) + image.InitAlpha(); + memset(image.GetAlpha(), 0, w * h); + wxMemoryDC dc; + wxBitmap returner(image); + dc.SelectObject(returner); + + Render(&dc, w, h, 0, 0, center); + + dc.SelectObject(wxNullBitmap); + + return returner; +} + +//Draw each piece to the DC +void SplitImage4::Render(wxDC* dc, const int &w, const int &h, const int& x, const int& y, const bool& center){ + +#ifdef SPLIT_IMAGE_DEBUG + linen=0; +#endif + + // Prepping variables + int edge, ox, oy, width, height; + int widthl = 0, widthr = 0, heightt = 0, heightb = 0; + + // if the size of the render is less than the splitimage's minsize + bool underw = w < MinSize.GetWidth(); + bool underh = h < MinSize.GetHeight(); + + //if undersized figure out the sizes based on ratio + if (underw) + { + widthl = (int)((float)w * (float)ratio[0]); + widthr = (int)((float)w * (float)ratio[1]); + if ( w-(widthl+widthr) )widthr += (w-(widthl+widthr)); + } + + if (underh) + { + heightt = (int)((float)h * (float)ratio[2]); + heightb = (int)((float)h * (float)ratio[3]); + if ( h-(heightt+heightb) )heightb += (h-(heightt+heightb)); + } + + // create a memory DC for the blits + wxMemoryDC pmdc; + + // create an array of pointers to the coreners for iterating + wxImage* c[] = {splitimage.c1, splitimage.c2, splitimage.c3, splitimage.c4}; + + // stamp each corner in place if it exists + for (int i=0; i < 4; ++i) + { + if (c[i]) + { + width = underw? (i==0||i==2? widthl:widthr) : (c[i]->GetWidth()); + height = underh? (i==0||i==1? heightt:heightb) : (c[i]->GetHeight()); + ox = i == 0 || i == 2 ? 0 : w - width; + oy = i < 2 ? 0 : h - height; + + if (width < 1 || height < 1) + continue; + + //dc->DrawBitmap(wxBitmap(c[i]->Scale(width,height)),ox+x,oy+y,true); + wxBitmap bmp = wxBitmap(c[i]->Scale(width,height)); + pmdc.SelectObject(bmp); + dc->Blit(ox+x,oy+y,width,height,&pmdc,0,0, wxCOPY, true); + } + } + //calculate the position and size of each edge and the center then prerender and stamp in place + int rwidth = 0, bheight = 0; + if(splitimage.top/* && !underw*/) + { + edge= splitimage.c2? w-splitimage.c2->GetWidth() : w; + width= edge-splitimage.top->pos.x; + height= underh? heightt: max(1,splitimage.top->image.GetHeight()); + PreRender(dc,*splitimage.top,splitimage.top->pos.x+x,splitimage.top->pos.y+y,width,height); + } + + if(splitimage.bottom/* && !underw*/) + { + edge= splitimage.c4?w-splitimage.c4->GetWidth():w; + width= edge-splitimage.bottom->pos.x; + bheight = height= underh? heightb: splitimage.bottom->image.GetHeight(); + PreRender(dc,*splitimage.bottom,splitimage.bottom->pos.x+x,h-height+y,width,height); + } + + if(splitimage.left/* && !underh*/) + { + edge= splitimage.c3?h-splitimage.c3->GetHeight():h; + width= underw? widthl: splitimage.left->image.GetWidth(); + height= edge-splitimage.left->pos.y; + PreRender(dc,*splitimage.left,splitimage.left->pos.x+x,splitimage.left->pos.y+y,width,height); + } + + if(splitimage.right/* && !underh*/) + { + edge= splitimage.c4?h-splitimage.c4->GetHeight():h; + rwidth =width= underw? widthr: splitimage.right->image.GetWidth(); + height= edge-splitimage.right->pos.y; + PreRender(dc,*splitimage.right,w-width+x,splitimage.right->pos.y+y,width,height); + } + + if(splitimage.center && center) + { + width= splitimage.right?w-rwidth-splitimage.center->pos.x:w-splitimage.center->pos.x; + height= splitimage.bottom? h-bheight-splitimage.center->pos.y : h-splitimage.center->pos.y; + PreRender(dc,*splitimage.center,splitimage.center->pos.x+x,splitimage.center->pos.y+y,width,height); + } + + //Release the bitmap in the memory DC + pmdc.SelectObject(wxNullBitmap); +} + +//Prerender each peice before drawing to the DC +void SplitImage4::PreRender(wxDC* dc, const Slice& slice, const int& posx, const int& posy, const int& width, const int& height){ + + //Escapes if the region is undrawable + if (width<1||height<1) return; + + //create a DC to draw from + wxMemoryDC smdc; + + //set base sizes and position + int iwidth = slice.image.GetWidth(); + int iheight = slice.image.GetHeight(); + + //declare and init all variables used inthe math + int xc,xb,xa,xo,yc,ya,yo,sl,st,sr,sb,x,y,w,h; + xc=xb=xa=xo=yc=ya=yo=sl=st=sr=sb=x=y=w=h=0; + + //Find x position based off of horizontal alignment if static + if (slice.hstyle==0){ + if (slice.align & wxALIGN_RIGHT) + xa=width-iwidth; + else if(slice.align & wxALIGN_CENTER_HORIZONTAL) + xa=width/2-iwidth/2; + } + + //Find y position based off of vertical alignment if static + if(slice.vstyle==0){ + if (slice.align & wxALIGN_BOTTOM) + ya=height-iheight; + else if(slice.align & wxALIGN_CENTER_VERTICAL) + ya=height/2-iheight/2; + } + + //offset the x and y cursers from the alignment if static + xc=xa + (slice.hstyle == 0? slice.offset.x : 0); + yc=ya + (slice.vstyle == 0? slice.offset.y : 0); + + //If the canvas is off the stage to the left, reposition it at 0 and offset the origin x coord to make up for it + if(xc<0){ + xo=-xc; + xc=0; + if(!(slice.align & wxALIGN_CENTER_HORIZONTAL) || slice.offset.x) + sl=xo; + } + //If the canvas is off the stage to the right resizethe canvas + if(xc+iwidth-sl>width) + sr=xc+iwidth-width-sl; + + //If the canvas is off the stage to the top, reposition it at 0 and offset the origin y coord to make up for it + if(yc<0){ + yo=-yc; + yc=0; + if(!(slice.align & wxALIGN_CENTER_VERTICAL) || slice.offset.y) + st=yo; + } + //If the canvas is off the stage to the bottom resizethe canvas + if(yc+iheight-st>height) + sb=yc+iheight-height-st; + + + //style 0 is where the actual image isn't resized + if (slice.hstyle == 0 && slice.vstyle == 0) { + + wxBitmap stamp = wxBitmap(slice.image); + + + smdc.SelectObject(stamp); + + //set position and size of canvas + x = posx + xc; + y = posy + yc; + w = iwidth - sr - sl; + h = iheight - sb - st; + + //Draw, this only hit if tiled on X axis + dc->Blit(x, y, w, h, &smdc, xo, yo, wxCOPY, true); + + + //style 2 fills the extra area with a tile of the image + } else if (slice.hstyle == 2 || slice.vstyle == 2) { + + //create a stamp source + wxBitmap stamp; + + //scale the image if necisary andconvert the bitmap + if (slice.hstyle == 1) + stamp = wxBitmap(slice.image.Scale(width,iheight,wxIMAGE_QUALITY_NORMAL)); + else if (slice.vstyle == 1) + stamp = wxBitmap(slice.image.Scale(iwidth,height,wxIMAGE_QUALITY_NORMAL)); + else + stamp = wxBitmap(slice.image); + + smdc.SelectObject(stamp); + + //while there's space fill the area with a tile of the image + xb=xc;//set the xcurser base position + while(yciwidth? iwidth : width-xc; + if(slice.vstyle == 2) + h = height-yc>iheight? iheight: height-yc; + else if(slice.vstyle == 0) + h = iheight - sb - st; + else if (slice.vstyle == 1) + h=stamp.GetHeight(); + + //Draw, only if tiled on the x axis + dc->Blit(x, y, w, h, &smdc, xo, yo, wxCOPY, true); + xc += iwidth;//incriment x cursor for next stamp + } + + //If not tileing vertically escape the loop + if (!(slice.vstyle == 2)) + break; + + //Else if not tiling horizontally, draw here + else if(!(slice.hstyle == 2)){ + + //find position and size of canvas on stage + x = posx + xc; + y = posy + yc; + if(slice.hstyle==0) + w = iwidth - sr - sl; + else if(slice.hstyle==1) + w = stamp.GetWidth(); + else if (slice.hstyle==2) + w = width-xb>stamp.GetWidth()? stamp.GetWidth() : width-xb; + + h = height-yc>iheight? iheight : height-yc; + + //Draw, Only if not tiles on the X axis + dc->Blit(x, y, w, h, &smdc, xo, yo, wxCOPY, true); + } + + xc=xb;//reset x cursor to base position for next pass + yc += iheight;//incriment y cursor for next pass + } + + + //style 1 Scales the image to fit into the area + } else if (slice.hstyle == 1 || slice.vstyle == 1) { + + //find scale for stamp + w = slice.hstyle == 1? width : iwidth; + h = slice.vstyle == 1? height : iheight; + + //scale the stamp + wxBitmap stamp = wxBitmap(slice.image.Scale(w,h,wxIMAGE_QUALITY_NORMAL)); + + //find position and size of canvas on stage + x = slice.hstyle == 1? posx : posx + xc; + y = slice.vstyle == 1? posy : posy + yc; + w = slice.hstyle == 1? width : iwidth - sr - sl; + h = slice.vstyle == 1? height : iheight - sb - st; + + smdc.SelectObject(stamp); + + //draw to canvas + dc->Blit(x, y, w, h, &smdc, xo, yo, wxCOPY, true); + } + + //cleanup + smdc.SelectObject(wxNullBitmap); + + +#ifdef SPLIT_IMAGE_DEBUG + //dc->SetTextForeground(wxColour(_("black"))); + wxString blarg; + blarg<DrawText(blarg,2,15*linen); + blarg.Clear(); + blarg<DrawText(blarg,posx,posy); + ++linen; +#endif + +} + +wxString SplitImage4::GetPath() const +{ + return wxString(path); +} + +//Cleanup +SplitImage4::~SplitImage4(void){ + + if (splitimage.center) + delete splitimage.center; + if (splitimage.left) + delete splitimage.left; + if (splitimage.top) + delete splitimage.top; + if (splitimage.bottom) + delete splitimage.bottom; + if (splitimage.right) + delete splitimage.right; + + if (splitimage.c1) + delete splitimage.c1; + if (splitimage.c2) + delete splitimage.c2; + if (splitimage.c3) + delete splitimage.c3; + if (splitimage.c4) + delete splitimage.c4; +} diff --git a/digsby/ext/src/SplitImage4.h b/digsby/ext/src/SplitImage4.h new file mode 100644 index 0000000..b33b422 --- /dev/null +++ b/digsby/ext/src/SplitImage4.h @@ -0,0 +1,130 @@ +#ifndef __SPLITIMAGE4__ +#define __SPLITIMAGE4__ + +#include "wx/wx.h" + +long getObjectRef(wxObject* obj); + + +class Extend{ +public: + Extend(bool up = false, bool down = false, bool left = false, bool right = false): + up(up), + down(down), + left(left), + right(right) {} + + + ~Extend() {} + + bool up, down, left, right; +}; + + +Extend extendCopy(const Extend& e); + +class Region{ +public: + Region(const Extend& extends, int hstyle, int vstyle, int align, wxPoint offset): + extends(extends), + hstyle(hstyle), + vstyle(vstyle), + align(align), + offset(offset) {} + + Region(const Region& r) + : extends(extendCopy(r.extends)) + , hstyle(r.hstyle) + , vstyle(r.vstyle) + , align(r.align) + , offset(r.offset) {} + + + Region() {} + ~Region() {} + + Extend extends; + int hstyle; + int vstyle; + int align; + wxPoint offset; +}; + +class ImageData{ +public: + ImageData(const wxString& source): + source(source) {} + + ImageData(const ImageData& idata) + : source(idata.source) + , x1(idata.x1) + , y1(idata.y1) + , x2(idata.x2) + , y2(idata.y2) + , left(idata.left) + , right(idata.right) + , top(idata.top) + , bottom(idata.bottom) + , center(idata.center) {} + + ImageData() {} + ~ImageData() {} + + wxString source; + int x1,y1,x2,y2; + Region left,right,top,bottom,center; +}; + +class Slice{ +public: + Slice() {} + ~Slice() {} + + wxImage image; + int hstyle,vstyle; + wxPoint pos,offset; + int align; +}; + +class ImageCluster{ +public: + ImageCluster() {} + ~ImageCluster() {} + + Slice *center,*left,*right,*top,*bottom; + wxImage *c1,*c2,*c3,*c4; +}; + +class SplitImage4{ +private: + wxString path; + +public: + ImageCluster splitimage; + + wxSize Size; + wxSize MinSize; + + wxString GetPath() const; + + int top, bottom, left, right; + +#ifdef SPLIT_IMAGE_DEBUG + int linen; +#endif + + float ratio[8]; + + SplitImage4(const ImageData& idata); + ~SplitImage4(); + + void SetImage(const ImageData& idata); + + void Draw(wxDC* dc, const wxRect& rect, int n = 0); + wxBitmap GetBitmap(const wxSize& size, bool center = true); + void PreRender(wxDC* dc,const Slice& slice, const int& posx, const int& posy, const int& width, const int& height); + void Render(wxDC* dc, const int &w, const int &h, const int& x=0, const int& y=0, const bool& center=true); + +}; + +#endif //__SPLITIMAGE4__ diff --git a/digsby/ext/src/Statistics.cpp b/digsby/ext/src/Statistics.cpp new file mode 100644 index 0000000..d3c109b --- /dev/null +++ b/digsby/ext/src/Statistics.cpp @@ -0,0 +1,279 @@ +#include "Statistics.h" + +#include "Noncopyable.h" +#include "RingBuffer.h" +#include "Sample.h" + +#include +#include + +// todo: BOOST_NO_EXCEPTIONS wasn't working, why? +namespace boost{ template void throw_exception(E const & e) { throw e; } } +#include + +static inline void recordTime(SampleBase& sample) +{ + time(&sample.time); +} + +#define SAMPLE_BUFFER_SIZE_BYTES 300 * 1024 +#define SAMPLE_BUFFER_SIZE_ELEMS SAMPLE_BUFFER_SIZE_BYTES / sizeof(Sample) + +#define EVENT_BUFFER_SIZE_BYTES 100 * 1024 +#define EVENT_BUFFER_SIZE_ELEMS EVENT_BUFFER_SIZE_BYTES / sizeof(Event) + +typedef unsigned char StatsEventId; + +class StatsImplBase : Noncopyable +{ +public: + + StatsImplBase() + : m_nextEventId(0) + , m_samples(SAMPLE_BUFFER_SIZE_ELEMS) + , m_events(EVENT_BUFFER_SIZE_ELEMS) + {} + + ~StatsImplBase() {} + + const SampleBuffer& samples() const { return m_samples; } + const EventBuffer& events() const { return m_events; } + const EventNameMap& eventNames() const { return m_eventNames; } + + void event(const char* eventName) + { + event(eventIdForName(eventName)); + } + + void event(StatsEventId eventId) + { + Event event; + recordTime(event); + event.eventId = eventId; + + wxMutexLocker locker(m_eventsMutex); + m_events.push_back(event); + } + + StatsEventId eventIdForName(const char* eventName) + { + wxMutexLocker locker(m_eventNamesMutex); + EventNameMap::iterator i = m_eventNames.find(std::string(eventName)); + if (i != m_eventNames.end()) + return i->second; + + StatsEventId eventId = m_nextEventId++; + m_eventNames[eventName] = eventId; + return eventId; + } + +protected: + + SampleBuffer m_samples; + EventBuffer m_events; + + wxMutex m_eventsMutex; + wxMutex m_eventNamesMutex; + + EventNameMap m_eventNames; + StatsEventId m_nextEventId; +}; + +#ifdef WIN32 +typedef struct { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; +} PROCESS_MEMORY_COUNTERS; + +typedef BOOL (WINAPI *GetProcessMemoryInfo_t) (HANDLE, PROCESS_MEMORY_COUNTERS*, DWORD); +static GetProcessMemoryInfo_t GetProcessMemoryInfo = (GetProcessMemoryInfo_t)-1; + +static inline void loadSymbols() +{ + if (GetProcessMemoryInfo == (GetProcessMemoryInfo_t)-1) { + GetProcessMemoryInfo = 0; + if (HMODULE dll = ::LoadLibrary(L"psapi.dll")) + GetProcessMemoryInfo = (GetProcessMemoryInfo_t)::GetProcAddress(dll, "GetProcessMemoryInfo"); + } +} + +#ifndef NDEBUG +static void printPMC(const PROCESS_MEMORY_COUNTERS& pmc) +{ + printf( "\tPageFaultCount: 0x%08X\n", pmc.PageFaultCount ); + printf( "\tPeakWorkingSetSize: 0x%08X\n", + pmc.PeakWorkingSetSize ); + printf( "\tWorkingSetSize: 0x%08X\n", pmc.WorkingSetSize ); + printf( "\tQuotaPeakPagedPoolUsage: 0x%08X\n", + pmc.QuotaPeakPagedPoolUsage ); + printf( "\tQuotaPagedPoolUsage: 0x%08X\n", + pmc.QuotaPagedPoolUsage ); + printf( "\tQuotaPeakNonPagedPoolUsage: 0x%08X\n", + pmc.QuotaPeakNonPagedPoolUsage ); + printf( "\tQuotaNonPagedPoolUsage: 0x%08X\n", + pmc.QuotaNonPagedPoolUsage ); + printf( "\tPagefileUsage: 0x%08X\n", pmc.PagefileUsage ); + printf( "\tPeakPagefileUsage: 0x%08X\n", + pmc.PeakPagefileUsage ); +} +#endif + +class StatsImpl : public StatsImplBase +{ +public: + StatsImpl() + : m_hProcess(0) + , m_errorOpeningProcess(false) + {} + + ~StatsImpl() + { + if (m_hProcess) + ::CloseHandle(m_hProcess); + } + + void measure() + { + Sample sample; + recordTime(sample); + measureMemoryUsage(sample); + m_samples.push_back(sample); + } + + bool measureMemoryUsage(Sample& sample) + { + PROCESS_MEMORY_COUNTERS pmc; + + bool success = false; + loadSymbols(); + if (openProcess() && GetProcessMemoryInfo) { + success = GetProcessMemoryInfo(m_hProcess, &pmc, sizeof(pmc)) == TRUE; + if (success) + sample.pagefileUsage = pmc.PagefileUsage; + } + + return success; + } + +protected: + bool openProcess() + { + if (m_hProcess) + return true; + + if (m_errorOpeningProcess) + return false; + + m_hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); + if (!m_hProcess) { + m_errorOpeningProcess = true; + return false; + } + + return true; + } + + HANDLE m_hProcess; + bool m_errorOpeningProcess; +}; +#else +class StatsImpl : StatsImplBase +{ + // TODO +public: + StatsImpl() {} + ~StatsImpl() {} + void measure() {} +}; +#endif + +class StatisticsTimer : public wxTimer +{ +public: + StatisticsTimer(Statistics* stats) + : m_stats(stats) + {} + + virtual void Notify() + { + if (m_stats) + m_stats->timerFired(); + } + + virtual void Stop() + { + m_stats = 0; + wxTimer::Stop(); + } + +protected: + Statistics* m_stats; +}; + +Statistics::Statistics(unsigned int intervalMs) + : m_timer(new StatisticsTimer(this)) + , m_interval(intervalMs) + , m_impl(new StatsImpl()) +{ +} + +Statistics::~Statistics() +{ + m_timer->Stop(); + delete m_timer; + m_timer = 0; + + delete m_impl; +} + +const SampleBuffer& Statistics::samples() const +{ + return m_impl->samples(); +} + +const EventBuffer& Statistics::events() const +{ + return m_impl->events(); +} + +const EventNameMap& Statistics::eventNames() const +{ + return m_impl->eventNames(); +} + +bool Statistics::start() +{ + if (!m_timer->IsRunning()) { + timerFired(); + m_timer->Start(m_interval); + return true; + } else + return false; +} + +bool Statistics::stop() +{ + bool wasRunning = m_timer->IsRunning(); + m_timer->Stop(); + return wasRunning; +} + +void Statistics::timerFired() +{ + m_impl->measure(); +} + +void Statistics::event(const char* eventName) +{ + m_impl->event(eventName); +} + + diff --git a/digsby/ext/src/Statistics.h b/digsby/ext/src/Statistics.h new file mode 100644 index 0000000..0fd3ebb --- /dev/null +++ b/digsby/ext/src/Statistics.h @@ -0,0 +1,44 @@ +#ifndef _CGUI_STATISTICS_H_ +#define _CGUI_STATISTICS_H_ + +#include "Sample.h" + +// todo: BOOST_NO_EXCEPTIONS wasn't working, why? +namespace boost{ template void throw_exception(E const & e); } +#include + +#include +using stdext::hash_map; + +typedef boost::circular_buffer SampleBuffer; +typedef boost::circular_buffer EventBuffer; +typedef hash_map EventNameMap; + + +class StatsImpl; +class StatisticsTimer; + +class Statistics +{ +public: + Statistics(unsigned int intervalMs = 1000 * 10); + ~Statistics(); + void timerFired(); + + void event(const char* eventName); + + bool start(); + bool stop(); + + const SampleBuffer& samples() const; + const EventBuffer& events() const; + const EventNameMap& eventNames() const; + +protected: + StatsImpl* m_impl; + StatisticsTimer* m_timer; + unsigned int m_interval; +}; + +#endif // _CGUI_STATISTICS_H_ + diff --git a/digsby/ext/src/StringRank.cpp b/digsby/ext/src/StringRank.cpp new file mode 100644 index 0000000..d0911cb --- /dev/null +++ b/digsby/ext/src/StringRank.cpp @@ -0,0 +1,77 @@ +// +// quicksilver string matching algorithm +// +// thanks http://rails-oceania.googlecode.com/svn/lachiecox/qs_score/trunk/qs_score.js +// +// +#include +using std::wstring; + +float stringRank(const wstring& s, const wstring& abbreviation) +{ + if (abbreviation.size() == 0) + return 0.9; + else if (abbreviation.size() > s.size()) + return 0.0; + + for (size_t i = abbreviation.size(); i > 0; --i) { + wstring sub_abbreviation(abbreviation.substr(0, i)); + size_t index = s.find(sub_abbreviation); + + if (index == std::string::npos) + continue; + else if (index + abbreviation.size() > s.size()) + continue; + + wstring next_string = s.substr(index + sub_abbreviation.size()); + wstring next_abbreviation; + + if (i < abbreviation.size()) + next_abbreviation = abbreviation.substr(i, std::string::npos); + + float remaining_score = stringRank(next_string, next_abbreviation); + + if (remaining_score > 0) { + float score = s.size() - next_string.size(); + + if (index != 0) { + long int c = s[index - 1]; + if (c == 32 || c == 9) { + for (int j = index - 2; j >= 0; --j) { + c = s[j]; + score -= ((c == 32 || c == 9) ? 1 : 0.15); + } + } else { + score -= index; + } + } + + score += remaining_score * next_string.size(); + score /= static_cast(s.size()); + return score; + } + } + + return 0.0; +} + +#ifdef TEST_QS_ALGO +int wmain( int argc, wchar_t *argv[ ], wchar_t *envp[ ] ) +{ + if (argc < 3) { + printf("Usage: %ws [ ...]\n", + argv[0]); + return -1; + } + + wstring s(argv[1]); + + for (int c = 2; c < argc; ++c) { + wchar_t* arg = argv[c]; + printf("%ws <-> %ws: %f\n", s.c_str(), arg, stringRank(s, arg)); + } + + return 0; +} +#endif + diff --git a/digsby/ext/src/TestExpando.cpp b/digsby/ext/src/TestExpando.cpp new file mode 100644 index 0000000..5b374bd --- /dev/null +++ b/digsby/ext/src/TestExpando.cpp @@ -0,0 +1,83 @@ +#include "wx/wxprec.h" + +#if __WXMSW__ +#define EXPANDO 1 +#else +#define EXPANDO 0 +#endif + +#if EXPANDO + #include "ExpandoTextCtrl.h" + #define TC ExpandoTextCtrl +#else + #define TC wxTextCtrl +#endif + +class ExpandoTest : public wxApp{ + public: + virtual bool OnInit(); +}; + +class TheFrame : public wxFrame{ + public: + TheFrame(wxWindow *parent, wxWindowID id, const wxString &title); + private: + TC *etc; + void OnButton(wxCommandEvent &evt); + void OnText(wxCommandEvent &evt); +}; + + +#define ID_BUTTON 1773 +#define ID_EXPANDO 1774 + + +TheFrame::TheFrame(wxWindow *parent, wxWindowID id, const wxString &title):wxFrame(parent, id, title){ + wxBoxSizer *szr = new wxBoxSizer(wxVERTICAL); + SetSizer(szr); + + #if EXPANDO + etc = new ExpandoTextCtrl(this,ID_EXPANDO,L"ZOMG oh hia thar!"); + etc->SetMaxHeight(100); + #else + etc = new wxTextCtrl(this,ID_EXPANDO,L"",wxDefaultPosition,wxDefaultSize); + etc->SetSize(etc->GetMinSize()); + #endif + + szr->Add(etc); + + wxButton *b = new wxButton(this,ID_BUTTON,L"Change"); + szr->Add(b); + + Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TheFrame::OnButton)); + Connect(ID_EXPANDO, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(TheFrame::OnText )); +} + +DECLARE_APP(ExpandoTest) + +IMPLEMENT_APP(ExpandoTest) + +bool ExpandoTest::OnInit(){ + + AllocConsole(); + freopen("CONIN$", "rb", stdin); + freopen("CONOUT$", "wb", stdout); + freopen("CONOUT$", "wb", stderr); + + TheFrame *frame = new TheFrame((wxFrame*) NULL, -1, _T("Expando Test")); + + + frame->Show(true); + SetTopWindow(frame); + return true; +} + +void TheFrame::OnButton(wxCommandEvent &evt){ + static bool turn = true; + etc->SetFont(wxFont((turn?20:10), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)); + turn = !turn; +} + +void TheFrame::OnText(wxCommandEvent &evt){ + printf("hey! it's a text event!\n"); +} diff --git a/digsby/ext/src/TransparentFrame.cpp b/digsby/ext/src/TransparentFrame.cpp new file mode 100644 index 0000000..002ce52 --- /dev/null +++ b/digsby/ext/src/TransparentFrame.cpp @@ -0,0 +1,47 @@ +#include "TransparentFrame.h" +#include "alphaborder_win.h" +#include "cwindowfx.h" + +#include + +IMPLEMENT_CLASS(TransparentFrame, wxFrame); + +BEGIN_EVENT_TABLE(TransparentFrame, wxFrame) + EVT_PAINT(TransparentFrame::OnPaint) +END_EVENT_TABLE() + +#define DEFAULT_TRANSPARENTFRAME_STYLE \ + (wxNO_BORDER | wxFRAME_SHAPED | wxFRAME_NO_TASKBAR) +TransparentFrame::TransparentFrame(wxWindow* parent) + : wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, + DEFAULT_TRANSPARENTFRAME_STYLE) + , m_alpha(255) +{ +} + +TransparentFrame::~TransparentFrame() +{ +} + +void TransparentFrame::OnPaint(wxPaintEvent& e) +{ + wxPaintDC dc(this); + dc.Clear(); + wxBitmap bmp(GetBitmap()); + Unpremultiply(bmp); + ApplyAlpha(this, bmp, m_alpha); +} + +wxBitmap TransparentFrame::GetBitmap() +{ + wxBitmap bmp(GetClientSize().x, GetClientSize().y, 32); + + wxMemoryDC dc; + dc.SelectObject(bmp); + dc.SetBrush(*wxRED_BRUSH); + dc.DrawRectangle(20, 20, 50, 50); + dc.SelectObject(wxNullBitmap); + + return bmp; +} + diff --git a/digsby/ext/src/TransparentFrame.h b/digsby/ext/src/TransparentFrame.h new file mode 100644 index 0000000..de70d28 --- /dev/null +++ b/digsby/ext/src/TransparentFrame.h @@ -0,0 +1,29 @@ +#ifndef TRANSPARENTFRAME_H__ +#define TRANSPARENTFRAME_H__ + +#include +#include + +void Unpremultiply(wxBitmap& bitmap); + +class TransparentFrame : public wxFrame +{ +public: + TransparentFrame(wxWindow* parent); + virtual ~TransparentFrame(); + + void SetAlpha(int alpha) { m_alpha = alpha; } + int GetAlpha() const { return m_alpha; } + virtual wxBitmap GetBitmap(); + + void OnPaint(wxPaintEvent&); + +protected: + int m_alpha; + + DECLARE_CLASS(TransparentFrame) + DECLARE_EVENT_TABLE() +}; + +#endif // TRANSPARENTFRAME_H__ + diff --git a/digsby/ext/src/TreeList.cpp b/digsby/ext/src/TreeList.cpp new file mode 100644 index 0000000..41e4954 --- /dev/null +++ b/digsby/ext/src/TreeList.cpp @@ -0,0 +1,205 @@ +#include "TreeList.h" + +#include "BuddyList/config.h" +#include "BuddyList/Group.h" + + +///// +// all Elem* specific functionality goes here +//// + +bool _isExpandable(TreeListNode n) +{ + return n->asGroup() != 0; +} + +static bool _hasChildren(TreeListNode n) +{ + return n->asGroup() != 0; +} + +static const NodeVector& _getChildren(TreeListNode n) +{ + return n->asGroup()->children(); +} + +///////// + +bool TreeListModel::expand(N node) +{ + bool expanded; + if (m_collapsed.find(node) != m_collapsed.end()) { + m_collapsed.erase(node); + expanded = true; + } else + expanded = false; + + invalidateRange(indexOf(node), IndexLast); + emitExpansionStateChangedEvent(node, true); + + return expanded; +} + +void TreeListModel::cacheIndices() +{ + m_indices.clear(); + + int c = 0; + for(NodeVector::const_iterator i = m_flattenedList.begin(); + i != m_flattenedList.end(); ++i) + m_indices[*i] = c++; +} + +void TreeListModel::_flatten(N root, NodeVector& lst, DepthMap& depths, int depth /* = 0*/) +{ + if (hideRoot() && depth) // don't show root in flattened list + lst.push_back(root); + + if (_hasChildren(root)) { + const NodeVector& children = _getChildren(root); + for(NodeVector::const_iterator i = children.begin(); i != children.end(); ++i) { + N el = *i; + depths[el] = depth; + if (isExpandable(el) && isExpanded(el)) + _flatten(el, lst, depths, depth + 1); + else + lst.push_back(el); + } + } +} + + +TreeListSaveSelection::TreeListSaveSelection(TreeList* treelist) + : m_treelist(treelist) +{ + m_elem = m_treelist->GetSelectedItem(); +} + +TreeListSaveSelection::~TreeListSaveSelection() +{ + if (m_elem) { + int index = m_treelist->model()->indexOf(m_elem); + m_treelist->SetSelection(index, false); + } +} + +void TreeList::OnRightDown(wxMouseEvent& e) +{ + // This is to provide slightly more native behavior--a right click down + // means select an item. This does not prevent a popup. + SetSelection(HitTest(e.GetPosition())); + e.Skip(true); +} + +void TreeList::OnKeyDown(wxKeyEvent& e) +{ + switch (e.GetKeyCode()) + { + // TODO: make keybindings configurable + case WXK_LEFT: + GoLeft(e); + break; + case WXK_RIGHT: + GoRight(e); + break; + case WXK_UP: + GoUp(); + return e.Skip(false); + case WXK_DOWN: + GoDown(); + return e.Skip(false); + case WXK_PAGEUP: + PageUp(); + SetSelection(GetFirstVisibleLine()); + break; + case WXK_PAGEDOWN: + PageDown(); + SetSelection(GetFirstVisibleLine()); + break; + } + + e.Skip(true); +} + +void TreeList::GoLeft(wxKeyEvent& e) +{ + N obj = GetSelectedItem(); + + if (e.GetModifiers() == wxMOD_SHIFT) { + // shift + left: collapse all groups + TreeListSaveSelection save(this); + model()->collapseAll(); + } else if (obj && e.GetModifiers() == wxMOD_NONE) + if (model()->isExpandable(obj) && model()->isExpanded(obj)) + model()->toggleExpand(obj); + else + selectParent(obj); +} + +void TreeList::GoRight(wxKeyEvent& e) +{ + int i = GetSelection(); + N obj = GetSelectedItem(); + + if (e.GetModifiers() == wxMOD_SHIFT) { + // shift + right: expand all groups + TreeListSaveSelection save(this); + model()->expandAll(); + } else if (obj && e.GetModifiers() == wxMOD_NONE) + if (model()->isExpandable(obj)) { + if (!model()->isExpanded(obj)) + model()->toggleExpand(obj); + else if (i + 1 < (int)GetItemCount() && GetParent(model()->itemAt(i + 1)) == obj) + // right on a group: go to first child + SetSelection(GetSelection() + 1); + } +} + +void TreeList::GoUp() +{ + int sel = GetSelection() - 1; + if (sel >= 0) SetSelection(sel); +} + +void TreeList::GoDown() +{ + int sel = GetSelection() + 1; + if (sel < (int)GetItemCount()) SetSelection(sel); +} + +bool TreeList::selectParent(N obj) +{ + N parent = GetParent(obj); + if (parent) { + int i = model()->indexOf(parent); + if (i != -1) { + SetSelection(i); + return true; + } + } + + return false; +} + +int TreeList::HitTestParent(const wxPoint& pos, float* percent) +{ + int i = HitTestEx(pos.x, pos.y, 0); + if (i == -1) + return -1; + + N parent = model()->parentOf(model()->itemAt(i)); + int j = model()->indexOf(parent); + + wxRect rect; + if (j != -1) { + rect = GetItemRect(j); + i = j; + } else + rect = GetItemRect(i); + + if (percent) + *percent = (float)(pos.y - rect.y) / (float)rect.height; + + return i; +} + diff --git a/digsby/ext/src/TreeList.h b/digsby/ext/src/TreeList.h new file mode 100644 index 0000000..414c3c3 --- /dev/null +++ b/digsby/ext/src/TreeList.h @@ -0,0 +1,277 @@ +#ifndef TreeList_h +#define TreeList_h + +#include "SkinVList.h" + +#include +#include +#include +using std::vector; +using std::set; +using stdext::hash_map; + +#include "BuddyList/config.h" +#include "BuddyList/BuddyListCommon.h" + +typedef Elem* TreeListNode; +typedef vector NodeVector; + +class TreeList; + +bool _isExpandable(TreeListNode n); + +/** + * Used by TreeList to display a tree in list form. + * + * Provides mapping between tree and list data structures. + */ +class TreeListModel +{ + typedef TreeListNode N; +protected: + typedef set NodeSet; + typedef hash_map IndicesMap; + typedef hash_map DepthMap; + typedef vector NodeVector; + +public: + static const int IndexLast = -1; + + TreeListModel(N root = 0) + : m_hideRoot(true) + , m_root(root) + { + } + + void setRoot() const; + + N root() const { return m_root; } + + /** + * Returns true if the root node will be hidden. + */ + bool hideRoot() const { return m_hideRoot; } + + /** + * Sets whether to hide the root node or not. + */ + void setHideRoot(bool hideRoot) { m_hideRoot = hideRoot; } + + /** + * Expands the given Node so that its children are visible. + */ + bool expand(N node); + + /** + * Expands all container Nodes so that their children are visible. + */ + bool expandAll() + { + wxLogMessage(wxT("TODO: expandAll")); + return true; + } + + /** + * Collapses the given Node so that its children are hidden. + */ + bool collapse(N node) + { + bool collapsed = m_collapsed.insert(node).second; + invalidateRange(indexOf(node), IndexLast); + emitExpansionStateChangedEvent(node, false); + return collapsed; + } + + /** + * Collapses all container Nodes so that their children are hidden. + */ + int collapseAll() + { + wxLogMessage(wxT("TODO: collapseAll")); + return 0; + } + + bool toggleExpand(N node) + { + if (!isExpandable(node)) + return false; + else if (isExpanded(node)) + return collapse(node); + else + return expand(node); + } + + N itemAt(size_t i) const + { + BL_ASSERT(i < m_flattenedList.size()); + return m_flattenedList[i]; + } + + int indexOf(N child) + { + IndicesMap::const_iterator i = m_indices.find(child); + if (i != m_indices.end()) + return i->second; + else + return -1; + } + + bool isExpandable(N node) + { + return _isExpandable(node); + } + + bool isExpanded(N child) + { + return m_collapsed.find(child) == m_collapsed.end(); + } + + void _flatten(N root, NodeVector& lst, DepthMap& depths, int depth = 0); + + void updateList() + { + m_depths.clear(); + m_flattenedList.clear(); + _flatten(root(), m_flattenedList, m_depths); + cacheIndices(); + } + + /* + * Stores a map of Node -> index + */ + void cacheIndices(); + + N parentOf(N child) + { + BL_ASSERT(false && "need to implement"); + (void*)child; + return 0; // return child->parent(); + } + + void emitExpansionStateChangedEvent(N node, bool expanded) + { + // TODO + (void*)node; + (void*)expanded; + } + + void invalidateRange(int a, int b) + { + // TODO + (void*)a; + (void*)b; + } + + void setRoot(N node) + { + m_root = node; + updateList(); + } + +protected: + + NodeSet m_collapsed; + IndicesMap m_indices; + DepthMap m_depths; + NodeVector m_flattenedList; + + N m_root; + bool m_hideRoot; + +}; + +/** + * A stack object helper that saves a treelist's selection on construction and + * restores it on destruction. + */ +class TreeListSaveSelection +{ +public: + TreeListSaveSelection(TreeList* treelist); + ~TreeListSaveSelection(); + +protected: + TreeListNode m_elem; + TreeList* m_treelist; +}; + + +/** + * Displays hierarchical data in a list. + */ +class TreeList : public SkinVList +{ +public: + typedef TreeListNode N; + typedef std::vector CellHeightVector; + + TreeList(wxWindow* parent, wxWindowID id = wxID_ANY, int style = wxLB_SINGLE) + : SkinVList(parent, id, style) + { + m_model = new TreeListModel(); + } + + virtual ~TreeList() + {} + + TreeListModel* model() const { return m_model; } + + /** + * Returns the selected node, or NULL. + */ + N GetSelectedItem() const + { + int i = GetSelection(); + return i != -1 ? GetItemAt(i) : NULL; + } + + /** + * Returns the Node at the given index. + */ + N GetItemAt(int i) const + { + return model()->itemAt(i); + } + + /** + * Returns a Node's parent. + */ + N GetParent(N obj) const + { + return model()->parentOf(obj); + } + + /** + * For an (x, y) coordinate, returns the parent Node and percentage + * over the parent and its children. + */ + int HitTestParent(const wxPoint& pos, float* percent); + + /** + * Given a Node, selects that Node's parent. + */ + bool selectParent(N obj); + + /** + * Sets the new root node. + */ + void SetRoot(N root) + { + model()->setRoot(root); + } + +protected: + void OnRightDown(wxMouseEvent& e); + void OnKeyDown(wxKeyEvent& e); + + void GoLeft(wxKeyEvent& e); + void GoRight(wxKeyEvent& e); + void GoUp(); + void GoDown(); + + CellHeightVector m_heights; + TreeListModel* m_model; +}; + +#endif // TreeList_h + diff --git a/digsby/ext/src/TreeList.sip b/digsby/ext/src/TreeList.sip new file mode 100644 index 0000000..67e773f --- /dev/null +++ b/digsby/ext/src/TreeList.sip @@ -0,0 +1,23 @@ +%Import Buddylist/sip/blist.sip + +class TreeList : SkinVList +{ +%TypeHeaderCode +#include "TreeList.h" +%End + +private: + TreeList(const TreeList&); + +public: + TreeList(wxWindow* parent, wxWindowID id = wxID_ANY, int style = wxLB_SINGLE) /Transfer/; + virtual ~TreeList(); + + void SetRoot(Elem* root); + Elem* GetSelectedItem() const; + Elem* GetItemAt(int i) const; + Elem* GetParent(Elem* obj) const; + int HitTestParent(const wxPoint& pos, float* percent); +}; + + diff --git a/digsby/ext/src/WindowSnapper.cpp b/digsby/ext/src/WindowSnapper.cpp new file mode 100644 index 0000000..a0927ff --- /dev/null +++ b/digsby/ext/src/WindowSnapper.cpp @@ -0,0 +1,197 @@ +#include "WindowSnapper.h" + +#if 0 +#define DBG(x) x +#else +#define DBG(x) +#endif + +WindowSnapper::WindowSnapper(wxWindow* win, int snapMargin, bool enable) +{ + DBG(fprintf(stderr, "WindowSnapper(%p, snapMargin = %d, enable = %d)\n", win, snapMargin, enable)); + Init(win, snapMargin, enable); +} + +WindowSnapper::~WindowSnapper() +{ + DBG(fprintf(stderr, "~WindowSnapper(%p)\n", this)); + wxASSERT_MSG(wxIsMainThread(), wxT("WindowSnapper must be destroyed on the main thread")); + + if (win) { + SetEnabled(false); + win = 0; + } +} + +void WindowSnapper::Init(wxWindow* win, int snapMargin, bool enable) +{ + snap_margin = snapMargin; + + this->win = win; + this->binder = 0; + this->snap_to_screen = true; + m_docked = false; + + cx = cy = 0; + cs = wxSize(0, 0); + + capturePositionOnNextMove = false; + + SetEnabled(enable); +} + +// catches EVT_DESTROY and unbinds from window messages +void WindowSnapper::OnWindowDestroyed() +{ + DBG(fprintf(stderr, "WindowSnapper::OnWindowDestroyed(this=%p, win=%p, GetWindow()=%p)\n", this, win)); + SetEnabled(false); + win = 0; + + delete this; +} + +bool WindowSnapper::SetEnabled(bool enable) +{ + if (enabled == enable) + return false; + + enabled = enable; + PlatformSetEnabled(enable); + return true; +} + +static inline bool insideIntervalX(const wxRect& rect, const wxRect& bound) +{ + return (rect.x >= bound.x && rect.x < bound.GetRight()) + || (bound.x >= rect.x && bound.x < rect.GetRight()); +} + +static inline bool insideIntervalY(const wxRect& rect, const wxRect& bound) +{ + return (rect.y >= bound.y && rect.y < bound.GetBottom()) + || (bound.y >= rect.y && bound.y < rect.GetBottom()); +} + +void WindowSnapper::HandleMoveOrSize(wxRect& rect, WindowOp op) +{ + DBG(fprintf(stderr, "HandleMoveOrSize (%d, %d, %d, %d) %s\n", rect.x, rect.y, rect.width, rect.height, op == Sizing ? "Sizing" : "Moving")); + + vector snap_rects = GetSnapRects(); + int margin = snap_margin; + +#define isClose(a, b) (abs((a) - (b)) < margin) + + if (capturePositionOnNextMove) { + capturePositionOnNextMove = false; + CapturePosition(); + } + + if (op == Moving) { + wxPoint mouse(wxGetMousePosition()); + rect.Offset(mouse.x - (rect.x + cx), mouse.y - (rect.y + cy)); + } + + // shift ignores snapping behavior + if (wxGetKeyState(WXK_SHIFT)) + return; + + // Loop through all the possible rectangles we could snap to, and determine + // if we should modify rect's position. + for (size_t i = 0; i < snap_rects.size(); ++i) { + SnapRect snap_rect = snap_rects[i]; + wxRect bound = snap_rect.rect; + + if (snap_rect.area == Inside) { + if (isClose(rect.x, bound.x)) { + int right = rect.GetRight(); + rect.x = bound.x; + if (op == Sizing) rect.SetRight(right); + } + else if (isClose(bound.GetRight(), rect.GetRight() + 1)) { + if (op == Sizing) + rect.width += bound.GetRight() - rect.GetRight(); + else + rect.x = bound.GetRight() - rect.width + 1; + } + + if (isClose(rect.y, bound.y)) { + int bottom = rect.GetBottom(); + rect.y = bound.y; + if (op == Sizing) rect.SetBottom(bottom); + } else if (isClose(bound.GetBottom(), rect.GetBottom() + 1)) { + if (op == Sizing) + rect.height += bound.GetBottom() - rect.GetBottom(); + else + rect.y = bound.GetBottom() - rect.height + 1; + } + } else { // snap_rect.area == Outside + if (insideIntervalY(rect, bound)) { + if (isClose(rect.x, bound.GetRight() + 1)) { + int right = rect.GetRight(); + rect.x = bound.GetRight() + 1; + if (op == Sizing) rect.SetRight(right); + } else if (isClose(bound.x - 1, rect.GetRight())) { + if (op == Sizing) + rect.width += bound.x - 1 - rect.GetRight(); + else + rect.x += bound.x - 1 - rect.GetRight(); + } + } + if (insideIntervalX(rect, bound)) { + if (isClose(rect.y, bound.GetBottom() + 1)) { + int bottom = rect.GetBottom(); + rect.y = bound.GetBottom() + 1; + if (op == Sizing) rect.SetBottom(bottom); + } else if (isClose(bound.y - 1, rect.GetBottom())) { + if (op == Sizing) + rect.height += bound.y - 1 - rect.GetBottom(); + else + rect.y += bound.y - 1 - rect.GetBottom(); + } + } + } + } +} + +void WindowSnapper::CapturePosition() +{ + wxRect window(win->GetRect()); + wxPoint mouse(wxGetMousePosition()); + + cx = mouse.x - window.x; + cy = mouse.y - window.y; +} + +// when starting a move, marks the initial position at (cx, cy) and the size at cs +void WindowSnapper::HandleMoveStart() +{ + DBG(fprintf(stderr, "HandleMoveStart()\n")); + + CapturePosition(); + cs = win->GetRect().GetSize(); +} + +// returns the rectangles that our window will snap to +vector WindowSnapper::GetSnapRects() const +{ + vector rects; + + // if snap_to_screen is true, then include the rectangle of the monitor + // the window is on. + if (snap_to_screen) + rects.push_back(SnapRect(Inside, GetMonitorClientArea(win))); + + // append a rect for each top level window + for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(), + end = wxTopLevelWindows.end(); + i != end; + ++i ) + { + wxTopLevelWindow * const tlw = wx_static_cast(wxTopLevelWindow *, *i); + if (win != tlw && tlw->IsShown() && !(tlw->GetWindowStyle() & wxFRAME_SHAPED)) + rects.push_back(SnapRect(Outside, tlw->GetRect())); + } + + return rects; +} + diff --git a/digsby/ext/src/WindowSnapper.h b/digsby/ext/src/WindowSnapper.h new file mode 100644 index 0000000..428a1d5 --- /dev/null +++ b/digsby/ext/src/WindowSnapper.h @@ -0,0 +1,94 @@ +#ifndef _WINDOWSNAPPER_H__ +#define _WINDOWSNAPPER_H__ + +#include "PlatformMessages.h" +#include +#include +using std::vector; + +enum WindowOp +{ + Moving, + Sizing +}; + +enum RectArea +{ + Inside, + Outside +}; + +struct SnapRect +{ + SnapRect(RectArea a, const wxRect& r) + : area(a) + , rect(r) + {} + + RectArea area; + wxRect rect; +}; + +// must be implemented by the platform +wxRect GetMonitorClientArea(wxWindow* win); + +/** + * Provides a "snapping" behavior when dragging a window. + * + * The window will stick to other windows in this application, and to the edges of the screen. + */ +#ifdef __WXMSW__ +class WindowSnapper : public NativeMsgHandler +#else +class WindowSnapper +#endif +{ +public: + WindowSnapper(wxWindow* win, int snapMargin = 12, bool enable = true); + virtual ~WindowSnapper(); + + bool SetEnabled(bool enable); + bool IsEnabled() const { return enabled; } + + int GetSnapMargin() const { return snap_margin; } + void SetSnapMargin(int snapMargin) { snap_margin = snapMargin; } + + bool GetSnapToScreen() const { return snap_to_screen; } + void SetSnapToScreen(bool snapToScreen) { snap_to_screen = snapToScreen; } + + void SetDocked(bool docked) { m_docked = docked; } + + vector WindowSnapper::GetSnapRects() const; + +protected: // these methods must be implemented by the platform: +#if __WXMSW__ + virtual LRESULT handleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); +#endif + void PlatformSetEnabled(bool enable); + +protected: + + void HandleMoveOrSize(wxRect& rect, WindowOp op); + void HandleMoveStart(); + void CapturePosition(); + + void OnWindowDestroyed(); + + void Init(wxWindow* win, int snapMargin, bool enable); + + int snap_margin; + bool enabled; + bool snap_to_screen; + bool m_docked; + + wxWindow* win; + PlatformMessageBinder* binder; + + int cx; + int cy; + wxSize cs; + bool capturePositionOnNextMove; +}; + + +#endif // _WINDOWSNAPPER_H__ diff --git a/digsby/ext/src/alphaborder.cpp b/digsby/ext/src/alphaborder.cpp new file mode 100644 index 0000000..f054c00 --- /dev/null +++ b/digsby/ext/src/alphaborder.cpp @@ -0,0 +1,301 @@ +// +// alphaborder.cpp +// + +#include "alphaborder.h" + +#if __WXMSW__ +#include "alphaborder_win.h" +#endif + +#include +#include +#include + +IMPLEMENT_DYNAMIC_CLASS(AlphaBorder, wxFrame); + +BEGIN_EVENT_TABLE(AlphaBorder, wxFrame) + EVT_PAINT(AlphaBorder::OnPaint) + EVT_MOUSE_EVENTS(AlphaBorder::OnMouseEvents) +END_EVENT_TABLE() + +AlphaBorder::AlphaBorder(wxWindow* parent, SplitImage4* border, const vector& frameSize, int style) + : wxFrame(parent, -1, _T(""), wxDefaultPosition, wxDefaultSize, + wxNO_BORDER | wxFRAME_SHAPED | wxFRAME_NO_TASKBAR | style) + , m_border(border) + , m_alpha(255) + , cacheValid(false) +{ + // fprintf(stderr, "AlphaBorder::m_border %d %d %d %d\n", border->left, border->top, border->right, border->bottom); + + SetFrameSize(frameSize); + SetBackgroundStyle(wxBG_STYLE_CUSTOM); + + parent->Connect(wxEVT_SHOW, wxShowEventHandler(AlphaBorder::OnParentShown), NULL, this); +} + + +void AlphaBorder::SetBackground(SplitImage4* background) +{ + m_border = background; + cacheValid = false; +} + +AlphaBorder::~AlphaBorder() +{ +} + +void AlphaBorder::OnParentShown(wxShowEvent& e) +{ + e.Skip(); + Show(e.GetShow()); +} + +void AlphaBorder::OnParentSizing(wxSizeEvent& e) +{ + e.Skip(); + cacheValid = false; + UpdatePosition(wxRect(GetParent()->GetPosition(), e.GetSize())); +} + +void AlphaBorder::OnParentMoving(wxMoveEvent& e) +{ + e.Skip(); + UpdatePosition(wxRect(e.GetPosition(), GetParent()->GetSize())); +} + +void AlphaBorder::SetFrameSize(const vector& frameSize) +{ + SetFrameSize(frameSize[0], frameSize[1], frameSize[2], frameSize[3]); +} + +void AlphaBorder::SetFrameSize(int left, int top, int right, int bottom) +{ + this->left = left; + this->top = top; + this->right = right; + this->bottom = bottom; + cacheValid = false; +} + +void AlphaBorder::UpdatePosition(const wxRect& r) +{ + wxRect before(GetRect()); + +#if __WXMSW__ + SetWindowPos((HWND)GetHWND(), + (HWND)GetParent()->GetHWND(), + r.x - this->left, + r.y - this->top, + r.width + this->left + this->right, + r.height + this->top + this->bottom, + SWP_NOACTIVATE | SWP_NOOWNERZORDER); +#else + Move(r.x - this->left, r.y - this->top); + SetSize(r.width + this->left + this->right, r.height + this->top + this->bottom); +#endif + if (GetRect() != before) + PaintAlphaBackground(); +} + +void AlphaBorder::OnPaint(wxPaintEvent&) +{ + wxPaintDC dc(this); + PaintAlphaBackground(); +} + + +void AlphaBorder::PaintAlphaBackground() +{ + wxBitmap bmp; + if ( cacheValid && cachedSize == GetClientSize() && cachedBitmap.IsOk() ) { + bmp = cachedBitmap; + } else { + wxSize csize(GetClientSize()); + bmp = m_border->GetBitmap(csize); + wxRect clip(GetClipRect()); + + // clip out the framesize + wxMemoryDC memdc(bmp); + memdc.SetLogicalFunction(wxCLEAR); + memdc.DrawRectangle(GetClipRect()); + + cachedBitmap = bmp; + cacheValid = true; + cachedSize = csize; + } + +#if __WXMSW__ + ApplyAlpha(this, bmp, m_alpha); +#endif +} + +void AlphaBorder::OnMouseEvents(wxMouseEvent& e) +{ + if ( GetClipRect().Contains(e.GetPosition()) ) { + if (e.Moving()) + SetCursor(GetParent()->GetCursor()); + GetParent()->GetEventHandler()->ProcessEvent(e); + } else { + SetCursor(wxNullCursor); + } +} + +void AlphaBorder::SetAlpha(unsigned char alpha, bool refresh) +{ + m_alpha = alpha; + if (refresh) + PaintAlphaBackground(); +} + + +IMPLEMENT_DYNAMIC_CLASS(BorderedFrame, wxFrame); + +BEGIN_EVENT_TABLE(BorderedFrame, wxFrame) + EVT_PAINT(BorderedFrame::OnPaint) +END_EVENT_TABLE() + +BorderedFrame::BorderedFrame(wxWindow* parent, SplitImage4* background, SplitImage4* border, const vector& frameSize, int style) + + : wxFrame(parent, wxID_ANY, _T(""), + wxDefaultPosition, wxSize(200,200), +// FIXME: Why is wxFRAME_SHAPED causing the frame not to display on Mac? +#ifndef __WXMAC__ + wxFRAME_SHAPED | +#endif + wxNO_BORDER | wxFRAME_NO_TASKBAR | style) + , splitBg(background) + , cacheValid(false) +{ + //splitBg = new SplitImage4(background); + + SetBackgroundStyle(wxBG_STYLE_CUSTOM); +#if __WXMSW__ + alphaBorder = new AlphaBorder(this, border, frameSize, style); +#else + alphaBorder = NULL; +#endif +} + +bool BorderedFrame::SetBackground(SplitImage4* background, const vector& frameSize) +{ + cacheValid = false; + + if (splitBg == background) + return false; + + splitBg = background; +#if __WXMSW__ + alphaBorder->SetBackground(background); + alphaBorder->SetFrameSize(frameSize); + + alphaBorder->UpdatePosition(GetScreenRect()); +#endif + Refresh(); + return true; +} + +bool BorderedFrame::SetTransparent(int alpha) +{ + return SetTransparent((wxByte)alpha); +} + +bool BorderedFrame::SetTransparent(wxByte alpha) +{ +#if __WXMSW__ + alphaBorder->SetAlpha(alpha, true); +#endif + return wxFrame::SetTransparent(alpha); +} + +int BorderedFrame::GetAlpha() const +{ +#if __WXMSW__ + return alphaBorder->m_alpha; +#else + return 0; +#endif +} + +BorderedFrame::~BorderedFrame() +{ + // all resources belong to the skin tree +} + +void BorderedFrame::PaintBackground(wxDC& dc) +{ + wxSize csize(GetClientSize()); + + if (!cacheValid || cachedSize != csize || !cachedBackground.Ok()) + { + wxImage img = splitBg->splitimage.center->image.Scale(csize.x, csize.y); + cachedBackground = wxBitmap(img); + cachedSize = csize; + cacheValid = true; + } + + dc.DrawBitmap(cachedBackground, 0, 0, true); +} + +void BorderedFrame::SetRect(const wxRect& rect) +{ +#if __WXMSW__ + // this code uses DeferWindowPos, which allows you to move several + // windows to new positions all at the same time, as part of a + // "transaction" -- this is to keep the semi transparent border + // exactly around the inner content window + + HDWP hdwp = BeginDeferWindowPos(2); + + if (hdwp) + hdwp = DeferWindowPos(hdwp, + (HWND)GetHWND(), + 0, + rect.x, + rect.y, + rect.width, + rect.height, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + else + fprintf(stderr, "error beginning begindefer\n"); + + if (hdwp) + hdwp = DeferWindowPos(hdwp, + (HWND)alphaBorder->GetHWND(), + (HWND)GetHWND(), + rect.x - alphaBorder->left, + rect.y - alphaBorder->top, + rect.width + alphaBorder->left + alphaBorder->right, + rect.height + alphaBorder->top + alphaBorder->bottom, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW); + else + fprintf(stderr, "error positioning middle\n"); + + if (hdwp) { + EndDeferWindowPos(hdwp); + alphaBorder->PaintAlphaBackground(); + } else + fprintf(stderr, "error positioning border\n"); +#else + SetSize(rect); +#endif +} + +void BorderedFrame::OnPaint(wxPaintEvent&) +{ + wxAutoBufferedPaintDC pdc(this); + PaintBackground(pdc); +} + + +void BorderedFrame::SetFrameSize(const vector& frameSize) +{ +#if __WXMSW__ + alphaBorder->SetFrameSize(frameSize); +#endif +} + +bool BorderedFrame::SetCursor(const wxCursor& cursor) +{ + return wxFrame::SetCursor(cursor); +} diff --git a/digsby/ext/src/alphaborder.h b/digsby/ext/src/alphaborder.h new file mode 100644 index 0000000..316ba25 --- /dev/null +++ b/digsby/ext/src/alphaborder.h @@ -0,0 +1,119 @@ +// +// alphaborder.h +// + +#ifndef _ALPHABORDER_H_ +#define _ALPHABORDER_H_ + +// NOTE: You should always include at least this first to set up wx defines. +#include +#include +#include "SplitImage4.h" +#include + +using std::vector; + +// +#if __WXMSW__ +bool ApplyAlpha(wxWindow* window, wxBitmap& bitmap, unsigned char alpha /* = 255*/); +#endif + +typedef std::vector FrameSize; + +class AlphaBorder : public wxFrame +{ +public: +#if SWIG + %pythonAppend AlphaBorder "self._setOORInfo(self)" +#else + DECLARE_DYNAMIC_CLASS( AlphaBorder ) +#endif + AlphaBorder() {} + AlphaBorder(wxWindow* parent, SplitImage4* border, const FrameSize& frameSize, int style = 0); + virtual ~AlphaBorder(); + + void SetBackground(SplitImage4* background); + void UpdatePosition(const wxRect& r); + void PaintAlphaBackground(); + + SplitImage4* m_border; + + + wxRect GetClipRect() const { + wxRect clip(GetClientSize()); + clip.x += left; + clip.width -= right + left; + clip.y += top; + clip.height -= top + bottom; + return clip; + } + + + void SetFrameSize(const FrameSize& frameSize); + void SetFrameSize(int left, int top, int right, int bottom); + void SetAlpha(unsigned char alpha, bool refresh = false); + int m_alpha; + + int left, top, right, bottom; + + +#ifndef SWIG +protected: + wxBitmap cachedBitmap; + wxSize cachedSize; + bool cacheValid; + + void OnParentShown(wxShowEvent& e); + void OnParentSizing(wxSizeEvent& e); + void OnParentMoving(wxMoveEvent& e); + void OnPaint(wxPaintEvent& e); + void OnMouseEvents(wxMouseEvent& e); + + DECLARE_EVENT_TABLE() +#endif +}; + +// + +class BorderedFrame : public wxFrame +{ +public: +#if SWIG + %pythonAppend BorderedFrame "self._setOORInfo(self)" +#else + DECLARE_DYNAMIC_CLASS( BorderedFrame ) +#endif + + BorderedFrame() {} + BorderedFrame(wxWindow* parent, SplitImage4* background, SplitImage4* border, const FrameSize& frameSize, int style = 0); + ~BorderedFrame(); + + bool SetBackground(SplitImage4* background, const vector& frameSize); + void SetFrameSize(const FrameSize& frameSize); + void SetRect(const wxRect& rect); + + + virtual bool SetTransparent(wxByte alpha); + bool SetTransparent(int alpha); + int GetAlpha() const; + + virtual bool SetCursor(const wxCursor& cursor); + + void PaintBackground(wxDC& dc); + void OnPaint(wxPaintEvent& dc); + + SplitImage4* splitBg; + AlphaBorder* alphaBorder; + +#ifndef SWIG +protected: + + wxBitmap cachedBackground; + wxSize cachedSize; + bool cacheValid; + + DECLARE_EVENT_TABLE() +#endif +}; + +#endif diff --git a/digsby/ext/src/alphaborder.i b/digsby/ext/src/alphaborder.i new file mode 100644 index 0000000..cde8201 --- /dev/null +++ b/digsby/ext/src/alphaborder.i @@ -0,0 +1,21 @@ +// not a %module + +%{ +#include "wx/wxPython/wxPython.h" +#include "alphaborder.h" +#include "wx/wxPython/pyclasses.h" +#include "wx/wxPython/pseudodc.h" + +#include +%} + +%include "std_vector.i" +namespace std +{ + %template(FrameSize) vector; +} + +%import core.i +%import windows.i + +%include "alphaborder.h" diff --git a/digsby/ext/src/alphaborder.sip b/digsby/ext/src/alphaborder.sip new file mode 100644 index 0000000..77926d4 --- /dev/null +++ b/digsby/ext/src/alphaborder.sip @@ -0,0 +1,74 @@ +%ModuleHeaderCode +#include "alphaborder.h" +%End + +typedef std::vector FrameSize; + +class AlphaBorder : wxFrame +{ +public: +%ConvertToSubClassCode +wxClassInfo* c = sipCpp->GetClassInfo(); +if(0) {} +#define C(clz) else if (c == CLASSINFO(clz)) { sipClass = sipClass_ ## clz; } + C(AlphaBorder) +else + sipClass = NULL; +#undef C +%End + + AlphaBorder(wxWindow* parent, SplitImage4* border, const FrameSize& frameSize, int style = 0) /Transfer/; + virtual ~AlphaBorder(); + + void SetBackground(SplitImage4* background /TransferBack/); + void UpdatePosition(const wxRect& r); + void PaintAlphaBackground(); + + wxRect GetClipRect() const; + + void SetFrameSize(const FrameSize& frameSize); + void SetFrameSize(int left, int top, int right, int bottom); + void SetAlpha(unsigned char alpha, bool refresh = false); + + int m_alpha; + int left; + int top; + int right; + int bottom; +}; + +class BorderedFrame : wxFrame +{ +public: +%ConvertToSubClassCode +wxClassInfo* c = sipCpp->GetClassInfo(); +if(0) {} +#define C(clz) else if (c == CLASSINFO(clz)) { sipClass = sipClass_ ## clz; } + C(BorderedFrame) +else + sipClass = NULL; +#undef C +%End + + BorderedFrame(wxWindow* parent, SplitImage4* background, SplitImage4* border, const FrameSize& frameSize, int style = 0) /Transfer/; + virtual ~BorderedFrame(); + + bool SetBackground(SplitImage4* background /TransferBack/, const FrameSize& frameSize); + void SetFrameSize(const FrameSize& frameSize); + void SetRect(const wxRect& rect); + + bool SetTransparent(int alpha); + int GetAlpha() const; + + bool SetCursor(const wxCursor& cursor); + + void PaintBackground(wxDC& dc); + void OnPaint(wxPaintEvent& dc); + + SplitImage4* splitBg; + AlphaBorder* alphaBorder; +}; + +%If (WXMSW) +bool ApplyAlpha(wxWindow* window, wxBitmap& bitmap, unsigned char alpha = 255); +%End diff --git a/digsby/ext/src/alphaborder_win.cpp b/digsby/ext/src/alphaborder_win.cpp new file mode 100644 index 0000000..1044d10 --- /dev/null +++ b/digsby/ext/src/alphaborder_win.cpp @@ -0,0 +1,102 @@ +// +// alphaborder_win.cpp +// + +#include +#include + +#include + +#ifndef WS_EX_LAYERED +#define WS_EX_LAYERED 0x80000 +#endif + +#ifndef ULW_ALPHA +#define ULW_ALPHA 0x00000002 +#endif + +typedef BOOL (WINAPI *lpfnUpdateLayeredWindow)(HWND, HDC, POINT *, SIZE *, HDC, POINT *, COLORREF, BLENDFUNCTION *, DWORD); + +#include "alphaborder_win.h" + +bool SetLayered(HWND hwnd, bool layered) +{ + LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + bool oldLayered = (WS_EX_LAYERED & style) != 0; + + if (layered == oldLayered) + return false; + + if (layered) + style |= WS_EX_LAYERED; + else + style &= ~WS_EX_LAYERED; + + return SetWindowLong(hwnd, GWL_EXSTYLE, style) != 0; +} + +bool SetLayered(wxWindow* window, bool layered) +{ + return SetLayered((HWND)window->GetHWND(), layered); +} + +bool ApplyAlpha(wxWindow* window, wxBitmap& bitmap, unsigned char alpha /* = 255*/) +{ + static lpfnUpdateLayeredWindow UpdateLayeredWindow = 0; + if (UpdateLayeredWindow == 0) + { + HMODULE hUser32 = GetModuleHandle(_T("USER32.DLL")); + UpdateLayeredWindow = (lpfnUpdateLayeredWindow)GetProcAddress(hUser32, "UpdateLayeredWindow"); + } + + SetLayered(window, true); + + wxRect r(window->GetRect()); + + POINT pos = {r.x, r.y}; + SIZE size = {r.width, r.height}; + POINT imgpos = {0, 0}; + + BLENDFUNCTION blendFunc; + blendFunc.BlendOp = AC_SRC_OVER; + blendFunc.BlendFlags = 0; + blendFunc.SourceConstantAlpha = alpha; + blendFunc.AlphaFormat = AC_SRC_ALPHA; + + MemoryHDC dcSrc; + SelectInHDC selectInDC(dcSrc, bitmap.GetHBITMAP()); + + if (!UpdateLayeredWindow((HWND)window->GetHWND(), + ScreenHDC(), + &pos, + &size, + dcSrc, + &imgpos, + 0, + &blendFunc, + ULW_ALPHA)) + { + wxLogApiError(wxT("UpdateLayeredWindow failed"), ::GetLastError()); + return false; + } + + return true; +} + +#ifndef NDEBUG +DbgGuiLeak::DbgGuiLeak(const char* funcname, const char* file, int line) + : _funcname(funcname) + , _file(file) + , _line(line) +{ + _guiResCount = ::GetGuiResources (::GetCurrentProcess(), GR_GDIOBJECTS); +} + +DbgGuiLeak::~DbgGuiLeak() +{ + int leaks = ::GetGuiResources (::GetCurrentProcess(), GR_GDIOBJECTS) - _guiResCount; + if (leaks != 0) + fprintf(stderr, "GDI leak %d object in %s (%s:%d)\n", leaks, _funcname, _file, _line); +} +#endif + diff --git a/digsby/ext/src/alphaborder_win.h b/digsby/ext/src/alphaborder_win.h new file mode 100644 index 0000000..6f8c04d --- /dev/null +++ b/digsby/ext/src/alphaborder_win.h @@ -0,0 +1,29 @@ +// +// alphaborder_win.h +// + +#ifndef _ALPHABORDER_WIN_H_ +#define _ALPHABORDER_WIN_H_ + +#include + +bool SetLayered(wxWindow* window, bool layered); +bool ApplyAlpha(wxWindow* window, wxBitmap& bitmap, unsigned char alpha = 255); + +#ifndef NDEBUG +class DbgGuiLeak +{ +public: + explicit DbgGuiLeak (const char* funcname, const char* file, int line); + ~DbgGuiLeak (); +private: + const char* _funcname; + const char* _file; + const int _line; + unsigned _guiResCount; +}; + +#define GDITRACK DbgGuiLeak __dbgGuiLeak(__FUNCTION__, __FILE__, __LINE__); +#endif + +#endif diff --git a/digsby/ext/src/cgui.i b/digsby/ext/src/cgui.i new file mode 100644 index 0000000..ca3e22f --- /dev/null +++ b/digsby/ext/src/cgui.i @@ -0,0 +1,17 @@ +%module cgui + +%include splitimage4.i +%include ctextutil.i +%include cskin.i +%include cwindowfx.i +%include skinsplitter.i +%include alphaborder.i + +%include LoginWindow.h +%{ +#include "LoginWindow.h" +%} + +#if __WXMSW__ +%include win/win32.i +#endif diff --git a/digsby/ext/src/cgui.sip b/digsby/ext/src/cgui.sip new file mode 100644 index 0000000..125ad7a --- /dev/null +++ b/digsby/ext/src/cgui.sip @@ -0,0 +1,41 @@ +%Module cgui + +%Import wx.sip + +%ModuleHeaderCode +#include +%End + +%Include ctextutil.sip +%Include splitimage4.sip +%Include cwindowfx.sip +%Include scrollwindow.sip +%Include skinvlist.sip +%Include alphaborder.sip +%Include skinsplitter.sip +%Include expando.sip + +%Include sip/InputBox.sip +%Include sip/rtf.sip +%Include sip/SelectionEvent.sip + +// skin +%Include skin/skinobjects.sip +%Include skin/SkinBitmap.sip + +%If (WXMSW) +%Include cgui_win.sip +%Include sip/PlatformMessages.sip +%Include sip/WindowSnapper.sip +%Include sip/Fullscreen.sip +%Include sip/win/WinUtils.sip +%Include sip/win/RichEditUtils.sip +%Include sip/MiscUI.sip +%Include sip/TransparentFrame.sip +%Include sip/Statistics.sip +%Include sip/win/WinTaskbar.sip +%Include sip/win/WinJumpList.sip +%Include sip/IconUtils.sip +%End + +%Include sip/LoginWindow.sip diff --git a/digsby/ext/src/cgui_win.sip b/digsby/ext/src/cgui_win.sip new file mode 100644 index 0000000..c57ea77 --- /dev/null +++ b/digsby/ext/src/cgui_win.sip @@ -0,0 +1,75 @@ +%ModuleHeaderCode +PyObject* RECT_to_tuple(LPRECT r); +%End + +%ModuleCode +PyObject* RECT_to_tuple(LPRECT r) +{ + PyObject* left = PyInt_FromLong(r->left); + PyObject* top = PyInt_FromLong(r->top); + PyObject* right = PyInt_FromLong(r->right); + PyObject* bottom = PyInt_FromLong(r->bottom); + + PyObject* obj = PyTuple_Pack(4, left, top, right, bottom); + + Py_DECREF(left); + Py_DECREF(top); + Py_DECREF(right); + Py_DECREF(bottom); + + return obj; +} +%End + +SIP_PYTUPLE GetMonitorInfo(long hmonitor); +%MethodCode + MONITORINFOEX info; + info.cbSize = sizeof(info); + + if (!GetMonitorInfo((HMONITOR)a0, &info)) { + PyErr_SetFromWindowsErr(0); + sipIsErr = 1; + } else { + PyObject* work = RECT_to_tuple(&info.rcWork); + PyObject* monitor = RECT_to_tuple(&info.rcMonitor); + PyObject* name = PyUnicode_FromWideChar(info.szDevice, wcslen(info.szDevice)); + + if (work && monitor && name) { + sipRes = PyTuple_Pack(3, work, monitor, name); + Py_DECREF(work); + Py_DECREF(monitor); + Py_DECREF(name); + } else + sipIsErr = 1; + } +%End + + +%ModuleHeaderCode +BOOL CALLBACK MultimonEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData); +%End + +%ModuleCode +BOOL CALLBACK MultimonEnumProc(HMONITOR hMonitor, HDC /*hdcMonitor*/, LPRECT /*lprcMonitor*/, LPARAM dwData) +{ + PyObject* intobj = PyLong_FromLong((long)hMonitor); + PyList_Append((PyObject*)dwData, intobj); + Py_DECREF(intobj); + + return TRUE; +} +%End + +SIP_PYLIST GetHMONITORs(); +%MethodCode + PyObject* monitorList = PyList_New(0); + if (!monitorList) + sipIsErr = 1; + else { + if (!EnumDisplayMonitors(NULL, NULL, MultimonEnumProc, (LPARAM)monitorList)) { + sipIsErr = 1; + Py_DECREF(monitorList); + } else + sipRes = monitorList; + } +%End diff --git a/digsby/ext/src/cskin.i b/digsby/ext/src/cskin.i new file mode 100644 index 0000000..63b97e2 --- /dev/null +++ b/digsby/ext/src/cskin.i @@ -0,0 +1,22 @@ +%{ +#include "wx/wxPython/wxPython.h" +#include "wx/wxPython/pyclasses.h" + +#include "skinobjects.h" +#include "skinvlist.h" +#include "scrollwindow.h" +#include +%} + +%include "std_vector.i" +%import windows.i +%include "typemaps.i" + +namespace std { + %template(ColorList) vector; + %template(UintList) vector; +} + +%include "scrollwindow.h" +%include "skinvlist.h" +%include "skinobjects.h" \ No newline at end of file diff --git a/digsby/ext/src/ctextutil.cpp b/digsby/ext/src/ctextutil.cpp new file mode 100644 index 0000000..8f20b24 --- /dev/null +++ b/digsby/ext/src/ctextutil.cpp @@ -0,0 +1,437 @@ +#include "wx/wxprec.h" +#include "wx/string.h" +#include "wx/font.h" +#include +#include "wx/dc.h" +#include +#include +#include +#include +#include +#include + +#ifdef __WXMSW__ +#include "wx/msw/private.h" +#endif //__WXMSW__ + +#ifndef WX_PRECOMP +#endif + +#include "ctextutil.h" + +#include +using namespace std; + +wxRect Subtract(const wxRect& rect, int left, int right, int up, int down) +{ + wxRect r(rect); + r.Offset(left, up); + r.SetSize(wxSize(r.width - left - right, + r.height - up - down)); + return r; +} + + +bool GetFontHeight(short& lineHeight, wxFont* font, wxDC* dc, bool line_height, bool descent) +{ + bool needsDelete = false; + if (!dc) { + if (!font) + return false; + else { + dc = new wxMemoryDC(); + needsDelete = true; + } + } + + static const wxString asciidigits = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + int w, h, font_descent, externalLeading; + dc->GetTextExtent(asciidigits, &w, &h, &font_descent, &externalLeading, font); + + if (line_height) + lineHeight = h; + else if (descent) + lineHeight = descent; + else + lineHeight = h - descent + externalLeading; + + if (needsDelete) + delete dc; + + return true; +} + +wxPoint RectPosPoint(const wxRect& rect, const wxPoint& point) +{ + int x, y; + int px = point.x; + int py = point.y; + + if ( px < 0 ) + x = rect.x + rect.width + px; + else + x = rect.x + px; + + if ( py < 0 ) + y = rect.y + rect.height + py; + else + y = rect.y + py; + + return wxPoint(x, y); +} + +wxRect RectAddMargins(const wxRect& rect, const wxRect& margins) { + wxRect r(rect); + r.x += margins.x; + r.width -= margins.x; + + r.y += margins.y; + r.height -= margins.y; + + r.width -= margins.width; + r.height -= margins.height; + + return r; +} + +#ifdef __WXMSW__ +static int optionsForWxAlignment(int alignment) +{ + int options = 0; + + if (wxALIGN_LEFT & alignment) + options |= DT_LEFT; + if (wxALIGN_CENTER_HORIZONTAL & alignment) + options |= DT_CENTER; + if (wxALIGN_RIGHT & alignment) + options |= DT_RIGHT; + + if (wxALIGN_TOP & alignment) + options |= DT_TOP; + if (wxALIGN_CENTER_VERTICAL & alignment) + options |= DT_VCENTER; + if (wxALIGN_BOTTOM & alignment) + options |= DT_BOTTOM; + + return options; +} +#endif + +void DrawTruncated(wxDC& dc, + const wxString& text, + const wxRect& rect, + int alignment, + bool drawAccels, + wxFont* font) +{ +#ifdef __WXMSW__ + if (font) + dc.SetFont(*font); + + + HDC hdc = (HDC)dc.GetHDC(); + + + ::SetTextColor(hdc, dc.GetTextForeground().GetPixel()); + ::SetBkMode(hdc, TRANSPARENT); + + int textLen = text.Len(); + + RECT r; + r.left = rect.x; + r.top = rect.y; + r.right = rect.x + rect.width; + r.bottom = rect.y + rect.height; + + int format = optionsForWxAlignment(alignment); + + if (!drawAccels) + format |= DT_NOPREFIX; + + format |= DT_END_ELLIPSIS | DT_NOCLIP; + + if (!DrawTextEx(hdc, (LPWSTR)text.wc_str(), textLen, &r, format, 0)) + fprintf(stderr, "DrawTextEx error %d\n", GetLastError()); +#else + + wxFAIL_MSG(wxT("drawtruncated not implemented on this platform")); + +#endif +} + + +/** + Truncates the given text to a specified width, in pixels. + + The string is truncated so that "thepostfix" can be appended at the end + (by default "...") + + If a font is not given the DC's current font is used. +*/ +wxString truncateText(wxString& text, + int size, + wxFont* font /* = NULL */, + wxDC* dc /* = NULL */, + const wxString& thepostfix /* = wxT("...") */) +{ + // unconstify + wxString postfix(thepostfix); + + bool destroy = false; + if ( dc == NULL ) { +#ifdef __WXMAC__ + dc = new wxScreenDC(); +#else + dc = new wxMemoryDC(); +#endif + destroy = true; + } + + wxFont dcFont(dc->GetFont()); + if (font == NULL) + font = &dcFont; + + // Early exit if the string is small enough to fit. + int textSize, _y; + dc->GetTextExtent(text, &textSize, &_y, NULL, NULL, font); + if (textSize <= size) + { + if (destroy && dc) + delete dc; + + return text; + } + + // Now make sure that our postfix string fits. + int postfixSize; + dc->GetTextExtent(postfix, &postfixSize, &_y, NULL, NULL, font); + + while (postfixSize > size) { + postfix.Truncate(postfix.Len() - 1); + dc->GetTextExtent(postfix, &postfixSize, &_y, NULL, NULL, font); + if (postfixSize < size || postfix.Len() == 0) + return postfix; + } + + size -= postfixSize; + + + wxString substr; + int low = 0, high = text.Len(), mid = 0; + int oldMid; + int tempWidth; + substr = text(low, high); + + // Do a binary search to find the best number of characters that will fit + // in the desired space. + if (textSize > size) { + while (textSize > size) { + oldMid = mid; + mid = (low + high) / 2; + substr = text(0, mid); + + if (oldMid == mid) { + text = substr; + break; + } + + dc->GetTextExtent(substr, &tempWidth, &_y, NULL, NULL, font); + + if (tempWidth > size) + high = mid - 1; + else + low = mid + 1; + } + } + + // clean up the wxMemoryDC + if (destroy && dc) + delete dc; + + return text + postfix; +} + +void TextWrapper::DoWrap(wxDC *dc, wxFont* font, const wxString& text, int widthMax, int maxlines) +{ + bool needsDelete = false; + + if (!dc) { +#ifdef __WXMAC__ + dc = new wxScreenDC(); +#else + dc = new wxMemoryDC(); +#endif + needsDelete = true; + } + + if (font) + dc->SetFont(*font); + + if (!maxlines) + maxlines = 32767; + + const wxChar *lastSpace = NULL; + wxString line; + + const wxChar *lineStart = text.c_str(); + for ( const wxChar *p = lineStart; ; ++p ) + { + if ( IsStartOfNewLine() ) + { + OnNewLine(); + + lastSpace = NULL; + line.clear(); + lineStart = p; + } + + if ( *p == _T('\n') || *p == _T('\0') ) + { + DoOutputLine(line); + + if ( *p == _T('\0') ) + break; + } + else // not EOL + { + if ( *p == _T(' ') ) + lastSpace = p; + + line += *p; + + if ( widthMax >= 0 && lastSpace ) + { + int width; + dc->GetTextExtent(line, &width, NULL); + + if ( width > widthMax ) + { + // remove the last word from this line + line.erase(lastSpace - lineStart, p + 1 - lineStart); + DoOutputLine(line); + if (m_linecount > maxlines) + break; + + // go back to the last word of this line which we didn't + // output yet + p = lastSpace; + } + } + //else: no wrapping at all or impossible to wrap + } + } + + if (needsDelete) + delete dc; +} + +wxString Wrap(const wxString& line, int width, wxFont* font, wxDC* dc, int maxlines) +{ + return TextWrapper().Wrap(dc, font, line, width, maxlines); +} + + +#ifdef __WXMSW__ + +static inline void wxRectToRECT(const wxRect& wxrect, RECT* rect) +{ + rect->left = wxrect.x; + rect->top = wxrect.y; + rect->right = wxrect.GetRight(); + rect->bottom = wxrect.GetBottom(); +} + +//Fits a given string, in a given font, into a givin rectangle returning the new font. useWidth determins if the rectangles width is ignored or not +//original code blatently coppied from http://www.codeguru.com/forum/showthread.php?t=379565 +//thanks Pravin Kumar +//Aaron added the useWidth logics +//Kevin converted it to return a wxFont instead of drawing the text itself +static wxFont FitFontToRectWin(HFONT hFont, LPRECT lprect, LPWSTR lpstr, BOOL useWidth = TRUE) +{ + // Gets LOGFONT structure corresponding to current font + LOGFONT LogFont; + if (GetObject(hFont, sizeof(LOGFONT), &LogFont) == 0) + return wxNullFont; + + // Calculates span of the string sets rough font width and height + int Len = wcslen(lpstr); + int Width = 0xfffffff; + + MemoryHDC hdc; + + if (useWidth) { + Width = lprect->right - lprect->left; + LogFont.lfWidth = -MulDiv(Width / Len, GetDeviceCaps(hdc, LOGPIXELSX), 72); + } else { + LogFont.lfWidth = 0; + } + + int Height = lprect->bottom - lprect->top; + LogFont.lfHeight = -MulDiv(Height, GetDeviceCaps(hdc, LOGPIXELSY), 72); + + // Creates and sets font to device context + hFont = CreateFontIndirect(&LogFont); + HFONT hOldFont = (HFONT) SelectObject(hdc, hFont); + + // Gets the string span and text metrics with current font + SIZE Size; + GetTextExtentExPoint(hdc, lpstr, Len, Width, NULL, NULL, &Size); + TEXTMETRIC TextMetric; + GetTextMetrics(hdc, &TextMetric); + int RowSpace = TextMetric.tmExternalLeading; + + // Deselects and deletes rough font + SelectObject(hdc, hOldFont); + DeleteObject(hFont); + + // Updates font width and height with new information of string span + LogFont.lfWidth = useWidth ? MulDiv(LogFont.lfWidth, Width, Size.cx) : 0; + LogFont.lfHeight = MulDiv(LogFont.lfHeight, Height, Size.cy - RowSpace); + + // Creates and selects font of actual span filling the rectangle + hFont = CreateFontIndirect(&LogFont); + + DeleteObject(hOldFont); + + // create a wxFont + wxNativeFontInfo info; + info.lf = LogFont; + return wxFont(info, (WXHFONT)hFont); +} + +// +// returns the best fitting wxFont that can fit in the given +// rectangle. if useWidth is false, only the height is used. +// +wxFont FitFontToRect(const wxFont& font, const wxRect& rect, const wxString& str, bool useWidth /*= true*/) +{ + RECT winrect; + wxRectToRECT(rect, &winrect); + wxWCharBuffer buf(str.wc_str()); + return FitFontToRectWin((HFONT)font.GetHFONT(), &winrect, buf.data(), useWidth); +} + +bool +IsRTLLang(HKL langID){ + + int plid = (int)langID & 0xFF; + + switch(plid){ + // TODO: this cannot possibly cover all the possible RTL cases. + // + // find out if there's a Windows function to get this info? + // also, ICU can provide this information: + // http://icu-project.org/apiref/icu4c/uloc_8h.html#badccaf9f7e7221cd2366c02f78bf5a9 + case LANG_ARABIC: + case LANG_HEBREW: + case LANG_SYRIAC: + case LANG_URDU: + return true; + default: + return false; + + } +} + +#endif // __WXMSW__ + diff --git a/digsby/ext/src/ctextutil.h b/digsby/ext/src/ctextutil.h new file mode 100644 index 0000000..649effa --- /dev/null +++ b/digsby/ext/src/ctextutil.h @@ -0,0 +1,112 @@ +#ifndef _CTEXTUTIL_H_ +#define _CTEXTUTIL_H_ + +#include "wx/defs.h" +#include +#include "wx/dc.h" +#include +#include +#include +#include +#include + +wxRect Subtract(const wxRect& r, int left = 0, int right = 0, int up = 0, int down = 0); + +void DrawTruncated(wxDC& dc, + const wxString& text, + const wxRect& rect, + int alignment = wxALIGN_LEFT | wxALIGN_TOP, + bool drawAccels = false, + wxFont* font = 0); + + +wxString truncateText(wxString& text, + int size, + wxFont* font = NULL, + wxDC* dc = NULL, + const wxString& thepostfix = wxT("...")); + +wxPoint RectPosPoint(const wxRect& rect, const wxPoint& point); + +wxRect RectAddMargins(const wxRect& r, const wxRect& margins); + +bool GetFontHeight(short& lineHeight, wxFont* font = 0, wxDC* dc = 0, bool line_height = false, bool descent = false); + + + +class TextWrapper +{ +public: + TextWrapper() + : m_eol(false) + , m_linecount(0) + {} + + wxString Wrap(wxDC *dc, wxFont* font, const wxString& text, int widthMax, int maxlines = -1) + { + m_text.clear(); + DoWrap(dc, font, text, widthMax, maxlines); + return m_text; + } + + // win is used for getting the font, text is the text to wrap, width is the + // max line width or -1 to disable wrapping + void DoWrap(wxDC *win, wxFont* font, const wxString& text, int widthMax, int maxlines); + + // we don't need it, but just to avoid compiler warnings + virtual ~TextWrapper() { } + +protected: + // line may be empty + virtual void OnOutputLine(const wxString& line) + { + m_text += line; + } + + // called at the start of every new line (except the very first one) + virtual void OnNewLine() + { + m_text += _T('\n'); + } + +private: + // call OnOutputLine() and set m_eol to true + void DoOutputLine(const wxString& line) + { + OnOutputLine(line); + + ++m_linecount; + m_eol = true; + } + + // this function is a destructive inspector: when it returns true it also + // resets the flag to false so calling it again woulnd't return true any + // more + bool IsStartOfNewLine() + { + if ( !m_eol ) + return false; + + m_eol = false; + + return true; + } + + + bool m_eol; + int m_linecount; + + +private: + wxString m_text; +}; + +wxString Wrap(const wxString& line, int width, wxFont* font = 0, wxDC* dc = 0, int maxlines = 0); + +#ifdef __WXMSW__ +wxFont FitFontToRect(const wxFont& font, const wxRect& rect, const wxString& str, bool useWidth = true); +bool IsRTLLang(HKL langID); +#endif + + +#endif diff --git a/digsby/ext/src/ctextutil.i b/digsby/ext/src/ctextutil.i new file mode 100644 index 0000000..89e3c96 --- /dev/null +++ b/digsby/ext/src/ctextutil.i @@ -0,0 +1,14 @@ +// not a %module + +%{ +#include "wx/wxPython/wxPython.h" +#include "ctextutil.h" +#include "wx/wxPython/pyclasses.h" +#include "wx/wxPython/pseudodc.h" + +#include +%} + +%import core.i + +%include "ctextutil.h" diff --git a/digsby/ext/src/ctextutil.sip b/digsby/ext/src/ctextutil.sip new file mode 100644 index 0000000..2bcb032 --- /dev/null +++ b/digsby/ext/src/ctextutil.sip @@ -0,0 +1,31 @@ +%ModuleHeaderCode +#include "ctextutil.h" +%End + +wxRect Subtract(const wxRect& r, int left = 0, int right = 0, int up = 0, int down = 0); + +void DrawTruncated(wxDC& dc, + const wxString& text, + const wxRect& rect, + int alignment = wxALIGN_LEFT | wxALIGN_TOP, + bool drawAccels = false, + wxFont* font = 0); + + +wxString truncateText(wxString& text, + int size, + wxFont* font = NULL, + wxDC* dc = NULL, + const wxString& thepostfix = wxT("...")); + +//SIP_PYOBJECT RectPos(const wxRect& rect, const wxPoint& point); +wxPoint RectPosPoint(const wxRect& rect, const wxPoint& point); + +wxRect RectAddMargins(const wxRect& r, const wxRect& margins); + +wxString Wrap(const wxString& line, int width, wxFont* font = 0, wxDC* dc = 0, int maxlines = 0); + +%If (WXMSW) +wxFont FitFontToRect(const wxFont& font, const wxRect& rect, const wxString& str, bool useWidth = true); +%End + diff --git a/digsby/ext/src/cwindowfx.cpp b/digsby/ext/src/cwindowfx.cpp new file mode 100644 index 0000000..8b0f990 --- /dev/null +++ b/digsby/ext/src/cwindowfx.cpp @@ -0,0 +1,1091 @@ +// +// cwindowfx.cpp +// + +#include "cwindowfx.h" +#include "Python.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +//2.8, in 2.9 this is a redirect to "wx/crt.h" +#include + +#define HAS_SHOW_NOACTIVATE 0 + +#ifdef __WXMSW__ + +#include +#include + +// Visual Studio 2008: SetLayeredWindowAttributes is in windows.h +#if (_MSC_VER < 1500) +typedef DWORD (WINAPI *PSLWA)(HWND, DWORD, BYTE, DWORD); +static PSLWA SetLayeredWindowAttributes = NULL; +static bool slwa_initialized = false; +#endif +#endif // __WXMSW__ + +void redirectStderr(const wxString& filename) { + wxFreopen(filename, L"w", stderr); +} + +void printToStderr(const wxString& s) +{ + fprintf(stderr, "%ws", s.wc_str()); +} + +bool checkWindows(DWORD major, DWORD minor) +{ + bool isMatch = false; + +#if __WXMSW__ + DWORD dwVersion = GetVersion(); + DWORD dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion))); + DWORD dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion))); + + isMatch = dwMajorVersion > major || (dwMajorVersion == major && dwMinorVersion >= minor); +#endif // __WXMSW__ + + return isMatch; +} + +bool isWin7OrHigher() { return checkWindows(6, 1); } +bool isVistaOrHigher() { return checkWindows(6, 0); } + +#include +#include +#include "ctextutil.h" +#include "pyutils.h" + + +using namespace std; + +#define WS_EX_LAYERED 0x00080000 + +bool IsMainThread() +{ + return wxIsMainThread(); +} + +wxIconBundle createIconBundle(const wxBitmap& bitmap) +{ + return createIconBundle(bitmap.ConvertToImage()); +} + +wxIconBundle createIconBundle(const wxImage& image) +{ + // TODO: platforms where IconBundles might need other sizes? + // i.e., Mac 256px PNGs? + + // get system sizes +#ifdef __WXMAC__ + // Unfortunately Mac doesn't give proper values for the icon constants, + // and PIL throwing MemoryErrors isn't fun, so just go back to the hardcoded + // variants here. + wxSize big(32, 32); + wxSize sm(16, 16); +#else + wxSize big(wxSystemSettings::GetMetric(wxSYS_ICON_X), + wxSystemSettings::GetMetric(wxSYS_ICON_Y)); + + wxSize sm(wxSystemSettings::GetMetric(wxSYS_SMALLICON_X), + wxSystemSettings::GetMetric(wxSYS_SMALLICON_Y)); +#endif + + // make bitmaps + wxBitmap bigBitmap(image.Scale(big.x, big.y, wxIMAGE_QUALITY_HIGH)); + wxBitmap smallBitmap(image.Scale(sm.x, sm.y, wxIMAGE_QUALITY_HIGH)); + + // make icons + wxIcon bigIcon; + wxIcon smallIcon; + bigIcon.CopyFromBitmap(bigBitmap); + smallIcon.CopyFromBitmap(smallBitmap); + + // add icons to bundles + wxIconBundle bundle; + bundle.AddIcon(bigIcon); + bundle.AddIcon(smallIcon); + return bundle; +} + +void SetFrameIcon(wxTopLevelWindow* win, const wxImage& image) +{ + // set the frame icon + win->SetIcons(createIconBundle(image)); +} + +wxFont ModifiedFont(const wxFont& font_, int weight, int pointSize, const wxString& faceName, int underline) +{ + wxFont font(font_); + + if (weight != -1) + font.SetWeight(weight); + if (pointSize != -1) + font.SetPointSize(pointSize); + if (faceName.length()) + font.SetFaceName(faceName); + if (underline != -1) + font.SetUnderlined(underline != 0); + + return font; +} + +void ModifyFont(wxWindow* ctrl, int weight, int pointSize, const wxString& faceName, int underline) +{ + ctrl->SetFont(ModifiedFont(ctrl->GetFont(), weight, pointSize, faceName, underline)); +} + +void SetBold(wxWindow* window, bool bold /* = true */) +{ + wxFont f(window->GetFont()); + f.SetWeight(bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL); + window->SetFont(f); +} + +wxRect RectClamp(const wxRect& self, const wxRect& r2, int flags /*= wxALL */) +{ + wxRect r(r2); + + if ((flags & (wxLEFT | wxRIGHT)) && r.width > self.width) { + r.width = self.width; + r.x = self.x; + } else { + if (wxLEFT & flags) + r.x = max(self.x, r.x); + + if (wxRIGHT & flags) { + int dx = r.GetRight() - self.GetRight(); + if (dx > 0) + r.x -= dx; + } + } + + if ((flags & (wxTOP | wxBOTTOM)) && r.height > self.height) { + r.height = self.height; + r.y = self.y; + } else { + if (wxTOP & flags) + r.y = max(self.y, r.y); + + if (wxBOTTOM & flags) { + int dy = r.GetBottom() - self.GetBottom(); + if (dy > 0) + r.y -= dy; + } + } + + return r; +} + +// returns the number of the primary display (which can be given to wxDisplay) +unsigned int GetPrimaryDisplay() +{ + for (unsigned int i = 0; i < wxDisplay::GetCount(); ++i) + if (wxDisplay(i).IsPrimary()) + return i; + + return 0; +} + +// ensures that window is visible in a monitor +void FitInMonitors(wxWindow* window, const wxPoint& defaultPosition) +{ + int display = wxDisplay::GetFromWindow(window); + wxRect windowRect(window->GetRect()); + + // if the window isn't on any visible monitor, place it in the primary + if (display == wxNOT_FOUND) { + display = GetPrimaryDisplay(); + wxPoint rescuePoint(wxDisplay(display).GetClientArea().GetTopLeft()); + + // offset by defaultPosition if given + if (defaultPosition != wxDefaultPosition) + rescuePoint += defaultPosition; + windowRect.SetPosition(rescuePoint); + } + + // brin window in from the edges of the screen so that it's entirely visible + wxRect clientArea(wxDisplay(display).GetClientArea()); + if (!clientArea.Contains(windowRect)) + windowRect = RectClamp(clientArea, windowRect); + + // move the window + window->SetSize(windowRect); +} + +wxPoint RectClampPoint(const wxRect& self, const wxPoint& pt, int /*flags = wxALL */) +{ + wxRect rect(pt.x, pt.y, 0, 0); + return RectClamp(self, rect).GetPosition(); +} + +void Bitmap_Draw(const wxBitmap& bitmap, wxDC& dc, const wxRect& rect, int alignment) +{ + wxRect r(0, 0, bitmap.GetWidth(), bitmap.GetHeight()); + + if ( alignment & wxALIGN_CENTER_HORIZONTAL ) + r = r.CenterIn(rect, wxHORIZONTAL); + else + r.x = RectPosPoint(rect, wxPoint((wxALIGN_RIGHT & alignment) ? -bitmap.GetWidth() : 0, 0)).x; + + if ( alignment & wxALIGN_CENTER_VERTICAL ) + r = r.CenterIn(rect, wxVERTICAL); + else + r.y = RectPosPoint(rect, wxPoint(0, (wxALIGN_BOTTOM & alignment) ? -bitmap.GetHeight() : 0)).y; + + dc.DrawBitmap(bitmap, r.x, r.y, true); +} + + + +int getCacheKey(wxBitmap* bitmap) +{ + return (int)bitmap->GetRefData() +#ifdef __WXMSW__ + + (int)bitmap->GetResourceHandle(); +#endif + ; +} + +unsigned long djb2_hash(unsigned char *str) +{ + // dumb hash. thanks interwebs + unsigned long hash = 5381; + int c; + + c = *str++; + while (c) { + hash = ((hash << 5) + hash) + c; + c = *str++; + } + + return hash; +} + +int getCacheKey(wxImage* image) +{ + // warning: evil + static unsigned char buf[11]; + memcpy(buf, image->GetRefData(), 10); + buf[10] = 0; + + return djb2_hash((unsigned char*)&buf); +} + +NotifyWindow::NotifyWindow(wxWindow* window, int id, const wxString& title, long style) + : wxFrame(window, id, title, wxDefaultPosition, wxDefaultSize, style) +{ +} + +NotifyWindow::~NotifyWindow() +{ +} + +#ifdef __WXMSW__ + +wxRect GetNormalRect(wxTopLevelWindow* win) +{ + wxRect rect; + + if (win) { + WINDOWPLACEMENT p = { sizeof(WINDOWPLACEMENT) }; + HWND hwnd = (HWND)win->GetHWND(); + if (!GetWindowPlacement(hwnd, &p)) + wxLogApiError(_T("GetWindowPlacement"), ::GetLastError()); + else { + wxCopyRECTToRect(p.rcNormalPosition, rect); + + // rcNormalPosition is in coordinates relative to the client area of + // the monitor the window is on--convert this to virtual screen coordinates + HMONITOR hmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if (!hmonitor) + wxLogApiError(_T("MonitorFromWindow"), ::GetLastError()); + else { + MONITORINFO minfo = {sizeof(MONITORINFO)}; + if (!GetMonitorInfo(hmonitor, &minfo)) + wxLogApiError(_T("GetMonitorInfo"), ::GetLastError()); + else + rect.Offset(minfo.rcMonitor.left + (minfo.rcWork.left - minfo.rcMonitor.left), + minfo.rcMonitor.top + (minfo.rcWork.top - minfo.rcMonitor.top)); + } + } + } + + return rect; +} + + +#if (_MSC_VER < 1500) +typedef struct tagWCRANGE +{ + WCHAR wcLow; + USHORT cGlyphs; +} WCRANGE, *PWCRANGE,FAR *LPWCRANGE; + +typedef struct tagGLYPHSET +{ + DWORD cbThis; + DWORD flAccel; + DWORD cGlyphsSupported; + DWORD cRanges; + WCRANGE ranges[1]; +} GLYPHSET, *PGLYPHSET, FAR *LPGLYPHSET; +#endif + + + +static bool gs_gfur = false; +typedef DWORD (WINAPI *PSGFUR)(HDC, GLYPHSET*); +PSGFUR GetFontUnicodeRangesWin32 = 0; +#endif + +PyObject* PyGetFontUnicodeRanges(const wxFont& font) +{ +#ifdef __WXMSW__ + if (!gs_gfur) { + HMODULE hDLL = LoadLibrary(L"gdi32"); + GetFontUnicodeRangesWin32 = (PSGFUR)GetProcAddress(hDLL, "GetFontUnicodeRanges"); + gs_gfur = true; + } + + if (!GetFontUnicodeRangesWin32) { + PyErr_SetString(PyExc_WindowsError, "Cannot get function pointer to GetFontUnicodeRanges"); + return 0; + } + + GLYPHSET* glyphs = 0; + DWORD count = 0; + PyObject* ranges = 0; + + HDC hdc = GetDC(0); + HFONT prevFont = (HFONT)SelectObject(hdc, (HFONT)font.GetHFONT()); + + count = GetFontUnicodeRangesWin32(hdc, 0); + + if (!count) { + PyErr_SetString(PyExc_WindowsError, "Unspecified error calling GetFontUnicodeRangesWin32"); + return 0; + } + + glyphs = (GLYPHSET*)alloca(count); + + if (count != GetFontUnicodeRangesWin32(hdc, glyphs)) { + PyErr_SetString(PyExc_WindowsError, "GetFontUnicodeRanges returned inconsistent values"); + Py_DECREF(ranges); + return 0; + } + + // initialize the python list we will return + ranges = PyList_New(glyphs->cRanges); + + if (glyphs->flAccel) + for (count = 0; count < glyphs->cRanges; ++count) + PyList_SET_ITEM(ranges, count, Py_BuildValue("ll", glyphs->ranges[count].wcLow & 0xff, glyphs->ranges[count].cGlyphs)); + else + for (count = 0; count < glyphs->cRanges; ++count) + PyList_SET_ITEM(ranges, count, Py_BuildValue("ll", glyphs->ranges[count].wcLow, glyphs->ranges[count].cGlyphs)); + + SelectObject(hdc, prevFont); + ReleaseDC(0, hdc); + return ranges; +#else + return 0; +#endif +} + +bool NotifyWindow::Show(bool show) +{ + if ( m_isShown == show ) + return false; +#ifdef __WXMSW__ + if ( show ) { + HWND hwnd = (HWND)GetHWND(); + ShowWindow(hwnd, SW_SHOWNOACTIVATE); + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE); + m_isShown = true; + return true; + } else +#endif + { + return wxFrame::Show(false); + } +} + +void NotifyWindow::SetRect(const wxRect& rect) +{ +#ifdef __WXMSW__ + int x = rect.GetX(), + y = rect.GetY(), + w = rect.GetWidth(), + h = rect.GetHeight(); + + SetWindowPos((HWND)GetHWND(), HWND_TOPMOST, x, y, w, h, SWP_NOACTIVATE); +#else + SetRect(rect); +#endif +} + + + + + +BEGIN_EVENT_TABLE(SimplePanel, wxPanel) + EVT_ERASE_BACKGROUND(SimplePanel::OnEraseBackground) +END_EVENT_TABLE() + +SimplePanel::SimplePanel(wxWindow* parent, int style) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style, wxT("Panel")) +{ + SetBackgroundStyle(wxBG_STYLE_CUSTOM); +} + +SimplePanel::~SimplePanel() {} + +void SimplePanel::OnEraseBackground(wxEraseEvent&) +{ + // do nothing. +} + +void setalpha(wxTopLevelWindow* window, int alpha) +{ +#ifdef __WXMSW__ + +#if (_MSC_VER < 1500) + if (!slwa_initialized) { + HMODULE hDLL = LoadLibrary(L"user32"); + SetLayeredWindowAttributes = (PSLWA)GetProcAddress(hDLL, "SetLayeredWindowAttributes"); + slwa_initialized = true; + } +#endif + + HWND hwnd = (HWND)window->GetHWND(); + + LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE); + LONG_PTR oldStyle = style; + + if (alpha == 255) + style &= ~WS_EX_LAYERED; + else + style |= WS_EX_LAYERED; + + if (alpha != 255 && SetLayeredWindowAttributes != NULL) + SetLayeredWindowAttributes(hwnd, 0, alpha, 2); + + if (style != oldStyle) + SetWindowLongPtr(hwnd, GWL_EXSTYLE, style); + + if (alpha > 0) + window->Show(false); +#endif +} + +Fader::Fader(wxTopLevelWindow* windowTarget, int fromAlpha, int toAlpha, + unsigned int step, + PyObject* onDone) + : window(windowTarget) + , to(toAlpha) +{ + + //fprintf(stderr, "Fader<%p> created with window %p\n", this, window); + + PyGILState_STATE state = PyGILState_Ensure(); + Py_XINCREF(onDone); + this->onDoneCallback = onDone; + PyGILState_Release(state); + + // Listen for EVT_DESTROY from the window. + windowTarget->Connect(-1, -1, wxEVT_DESTROY, wxWindowDestroyEventHandler(Fader::OnWindowDestroyed)); + + this->step = step * (fromAlpha > toAlpha ? -1 : 1); + const int tick = 8; + + value = fromAlpha; + Start(tick, false); +} + +void Fader::OnWindowDestroyed(wxWindowDestroyEvent& e) +{ + //fprintf(stderr, "Fader::OnWindowDestroyed(%p =? %p)...", e.GetEventObject(), window); + + if (window && e.GetEventObject() == window) { + //fprintf(stderr, "stopping.\n"); + // window is destroying + window = 0; + Stop(); + }// else + //fprintf(stderr, "ignore\n"); + +} + +static bool inTopLevelWindowsList(wxTopLevelWindow* tlw) +{ + wxWindowList::compatibility_iterator node = wxTopLevelWindows.GetLast(); + while (node) + { + wxWindow* win = node->GetData(); + if (tlw == win) + return true; + node = node->GetPrevious(); + } + + return false; +} + +void Fader::Notify() +{ + if (!IsRunning()) + { + fprintf(stderr, "Fader::Notify() was called but !IsRunning()\n"); + return; + } + + //fprintf(stderr, "Fader<%p>::Notify()\n", this); + bool done = false; + if (window) { + if (!inTopLevelWindowsList(window)) { + fprintf(stderr, "warning: fader window %p is not in TLW list\n", window); + done = true; + } else { + value += step; + + if (step > 0) { + if (value >= to) { + value = to; + done = true; + } + } else if (step < 0) { + if (value <= to) { + value = to; + done = true; + } + } else + done = true; + + window->SetTransparent(value); + } + } else + done = true; + + if (done) + Stop(); +} + +void Fader::Stop(bool runStopCallback /* = true */) +{ + //fprintf(stderr, "Fader<%p>::Stop()\n", this); + bool isRunning = IsRunning(); + wxTimer::Stop(); + wxASSERT(!IsRunning()); + if (!isRunning) + return; + + // notify the Python callback + PyGILState_STATE state = PyGILState_Ensure(); + if (runStopCallback && onDoneCallback) { + if ( PyCallable_Check( onDoneCallback ) ) { + PyObject* result = PyObject_CallObject(onDoneCallback, NULL); + if (!result && PyErr_Occurred()) { + fprintf(stderr, "an error occurred\n"); + PyErr_Print(); + } else + Py_DECREF(result); + } else + fprintf(stderr, "error: onDoneCallback was not callable\n"); + + Py_CLEAR(onDoneCallback); + } + PyGILState_Release(state); +} + +Fader::~Fader() +{ + //fprintf(stderr, "~Fader<%p>\n", this); + window = 0; + Stop(); + + PyGILState_STATE state = PyGILState_Ensure(); + Py_CLEAR(onDoneCallback); + PyGILState_Release(state); +} + + +Fader* fadein(wxTopLevelWindow* window, int fromAlpha, int toAlpha, unsigned int step, PyObject* onDone) +{ + window->SetTransparent(fromAlpha); +#if wxCHECK_VERSION(2, 9, 1) + window->ShowWithoutActivating(); +#elif defined(__WXMSW__) && HAS_SHOW_NOACTIVATE + window->Show(true, false); +#else + window->Show(true); +#endif + + return new Fader(window, fromAlpha, toAlpha, step, onDone); +} + +/** + * Returns the alpha transparency of a top level window, as an int from 0-255. + */ +int GetTransparent(wxTopLevelWindow* tlw) +{ +#ifdef __WXMSW__ + HWND hwnd = (HWND)tlw->GetHWND(); + + // without the WS_EX_LAYERED bit, the window cannot be transparent + if ((GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) == 0) + return 255; + + // grab GetLayeredWindowAttributes from user32.dll + typedef BOOL (WINAPI *PGETLAYEREDWINDOWATTR)(HWND, COLORREF*, BYTE*, DWORD*); + static PGETLAYEREDWINDOWATTR pGetLayeredWindowAttributes = 0; + static bool getLayeredInit = false; + + if (!getLayeredInit) { + getLayeredInit = true; + wxDynamicLibrary dllUser32(_T("user32.dll")); + pGetLayeredWindowAttributes = (PGETLAYEREDWINDOWATTR) + dllUser32.GetSymbol(wxT("GetLayeredWindowAttributes")); + } + + if (pGetLayeredWindowAttributes) { + BYTE alpha; + if (pGetLayeredWindowAttributes(hwnd, 0, &alpha, 0)) + return static_cast(alpha); + } +#endif + return 255; +} + +Fader* fadeout(wxTopLevelWindow* window, int fromAlpha, int toAlpha, unsigned int step, PyObject* onDone) +{ + // -1 means "the window's current opacity" + if (fromAlpha == -1) + fromAlpha = GetTransparent(window); + + return new Fader(window, fromAlpha, toAlpha, step, onDone); +} + +#if defined(__WXMAC__) || defined(__WXGTK__) +wxRect GetTrayRect() +{ + return wxRect(); +} + +wxBitmap* getScreenBitmap(const wxRect& rect) +{ + return new wxBitmap(1,1); +} + +bool WindowVisible(wxTopLevelWindow* win) +{ + return true; +} +#endif + + + +#ifdef __WXMSW__ + +#define DEFAULT_RECT_WIDTH 150 +#define DEFAULT_RECT_HEIGHT 30 + +static wxRect RectFromRECT(RECT r) { + return wxRect(r.left, r.top, r.right - r.left, r.bottom - r.top); +} + +bool GetTaskBarAutoHide() +{ + static APPBARDATA appbardata = { sizeof(APPBARDATA) }; + return (SHAppBarMessage(ABM_GETSTATE, &appbardata) & ABS_AUTOHIDE) != 0; +} + +wxRect GetTaskbarRect() +{ + wxRect wxrect; + RECT rect; + + HWND trayHwnd = ::FindWindowEx(0, 0, wxT("Shell_TrayWnd"), 0); + if (trayHwnd && ::GetWindowRect(trayHwnd, &rect)) + wxrect = RectFromRECT(rect); + + return wxrect; +} + +wxRect GetTrayRect() +{ + RECT rect; + LPRECT lpTrayRect = ▭ + + // lookup by name + HWND hShellTrayWnd = FindWindowEx(NULL, NULL, TEXT("Shell_TrayWnd"), NULL); + if (hShellTrayWnd) + { + HWND hTrayNotifyWnd = FindWindowEx(hShellTrayWnd, NULL, TEXT("TrayNotifyWnd"), NULL); + if(hTrayNotifyWnd && GetWindowRect(hTrayNotifyWnd, lpTrayRect)) + return RectFromRECT(rect); + } + + // try the APPBARDATA api instead + APPBARDATA appBarData; + appBarData.cbSize = sizeof(appBarData); + + if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) { + + // We know the edge the taskbar is connected to, so guess the rect of the + // system tray. Use various fudge factor to make it look good + switch(appBarData.uEdge) + { + case ABE_LEFT: + case ABE_RIGHT: + // We want to minimize to the bottom of the taskbar + rect.top=appBarData.rc.bottom-100; + rect.bottom=appBarData.rc.bottom-16; + rect.left=appBarData.rc.left; + rect.right=appBarData.rc.right; + break; + + case ABE_TOP: + case ABE_BOTTOM: + // We want to minimize to the right of the taskbar + rect.top=appBarData.rc.top; + rect.bottom=appBarData.rc.bottom; + rect.left=appBarData.rc.right-100; + rect.right=appBarData.rc.right-16; + break; + } + + return RectFromRECT(rect); + } + + hShellTrayWnd = FindWindowEx(NULL, NULL, TEXT("Shell_TrayWnd"), NULL); + + if (hShellTrayWnd && GetWindowRect(hShellTrayWnd, lpTrayRect)) { + if(lpTrayRect->right-rect.left > DEFAULT_RECT_WIDTH) + lpTrayRect->left=rect.right - DEFAULT_RECT_WIDTH; + if(lpTrayRect->bottom-rect.top > DEFAULT_RECT_HEIGHT) + lpTrayRect->top=rect.bottom - DEFAULT_RECT_HEIGHT; + + return RectFromRECT(rect); + } + + // OK. Haven't found a thing. Provide a default rect based on the current work + // area + SystemParametersInfo(SPI_GETWORKAREA, 0, lpTrayRect, 0); + lpTrayRect->left = lpTrayRect->right-DEFAULT_RECT_WIDTH; + lpTrayRect->top = lpTrayRect->bottom-DEFAULT_RECT_HEIGHT; + return RectFromRECT(rect); +} + +#include + +wxBitmap* getScreenBitmap(const wxRect& rect) +{ + wxBitmap* bmp = 0; + + HDC mainWinDC = GetDC(GetDesktopWindow()); + HDC memDC = CreateCompatibleDC(mainWinDC); + + HBITMAP bitmap = CreateCompatibleBitmap(mainWinDC, rect.width, rect.height); + + if (bitmap) { + HGDIOBJ hOld = SelectObject(memDC,bitmap); + BitBlt(memDC, 0, 0, rect.width, rect.height, mainWinDC, rect.x, rect.y, SRCCOPY); + SelectObject(memDC, hOld); + DeleteDC(memDC); + ReleaseDC(GetDesktopWindow(), mainWinDC); + bmp = new wxBitmap(rect.width, rect.height, 32); + bmp->SetHBITMAP((WXHBITMAP)bitmap); + } + + return bmp; +} + +static bool win32ontop(HWND hwnd) +{ + return (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0; +} + +bool WindowVisible(wxTopLevelWindow* win) +{ + if (!win->IsShown() || win->IsIconized()) + return false; + + HWND winhandle = (HWND)win->GetHWND(); + bool winontop = win32ontop(winhandle); + wxRect winrect(win->GetPosition(), win->GetSize()); + RECT r; + + HWND hwnd = GetTopWindow(0); + + while (hwnd) { + if (!GetWindowRect(hwnd, &r)) { + fprintf(stderr, "GetWindowRect retuned error\n"); + break; + } + + if (hwnd == winhandle) + // if we're down to our window, return True--its visible + return true; + + // check: + // 1) that the window's "on top" state is the same as ours + // 2) that the window is visible + // if these are true and the window's rect intsects ours, return False + if (win32ontop(hwnd) == winontop && IsWindowVisible(hwnd) && wxRect(r.left, r.top, r.right - r.left, r.bottom - r.top).Intersects(winrect)) + return false; + + hwnd = GetWindow(hwnd, GW_HWNDNEXT); + } + + return true; +} + +void ApplySmokeAndMirrors(wxWindow* win, const wxBitmap& shape, int ox /*= 0*/, int oy /*= 0*/) +{ + + if (!shape.IsOk()) + PyErr_SetString(PyExc_AssertionError, "shape bitmap is not OK"); + else { + wxBitmap regionBitmap = shape; + + // if the bitmap doesn't already have a mask, create one by clamping + // the alpha values + if (!shape.GetMask()) { + wxImage img(shape.ConvertToImage()); + img.ConvertAlphaToMask(200); + regionBitmap = wxBitmap(img); + } + + wxRegion region(regionBitmap); + if ((ox || oy) && !region.Offset(ox, oy)) { + PyErr_SetString(PyExc_AssertionError, "could not offset region"); + return; + } + + ApplySmokeAndMirrors(win, region); + } +} + +// shape is a wxRegion object +void ApplySmokeAndMirrors(wxWindow* win, const wxRegion& shape) +{ + ApplySmokeAndMirrors(win, (long)shape.GetHRGN()); +} + +// shape is just an HRGN (a handle to a windows region object) +void ApplySmokeAndMirrors(wxWindow* win, long shape /* = 0*/) +{ + if (shape) { + HRGN hrgn = (HRGN)shape; + + // have to make a copy of the region + DWORD numbytes = ::GetRegionData(hrgn, 0, NULL); + if (!numbytes) { + PyErr_SetFromWindowsErr(0); + return; + } + + RGNDATA *rgnData = (RGNDATA*) alloca(numbytes); + ::GetRegionData(hrgn, numbytes, rgnData); + shape = (long)::ExtCreateRegion(NULL, numbytes, rgnData); + + if (!shape) { + PyErr_SetString(PyExc_AssertionError, "could not copy region"); + return; + } + } + + if (!::SetWindowRgn((HWND)win->GetHWND(), (HRGN)shape, true)) + PyErr_SetFromWindowsErr(0); +} + +#endif // __WXMSW__ + +bool LeftDown() +{ + return wxGetMouseState().LeftDown(); +} + +// returns the topmost parent of the given window +wxWindow* FindTopLevelWindow(wxWindow* window) +{ + wxWindow* top = window; + wxWindow* parent; + + while(top) { + if (top->IsTopLevel()) + break; + else { + parent = top->GetParent(); + if (parent) + top = parent; + else + break; + } + } + + return top; +} + +#define wxPy_premultiply(p, a) ((p) * (a) / 0xff) +#define wxPy_unpremultiply(p, a) ((a) ? ((p) * 0xff / (a)) : (p)) + +void Premultiply(wxBitmap& bmp) +{ + int w = bmp.GetWidth(); + int h = bmp.GetHeight(); + wxAlphaPixelData pixData(bmp, wxPoint(0, 0), wxSize(w, h)); + + wxAlphaPixelData::Iterator p(pixData); + for (int y = 0; y < h; ++y) { + wxAlphaPixelData::Iterator rowStart = p; + for (int x = 0; x < w; ++x) { + unsigned char a = p.Alpha(); + p.Red() = wxPy_premultiply(p.Red(), a); + p.Green() = wxPy_premultiply(p.Green(), a); + p.Blue() = wxPy_premultiply(p.Blue(), a); + ++p; + } + + p = rowStart; + p.OffsetY(pixData, 1); + } +} + +void Unpremultiply(wxBitmap& bmp) +{ + int w = bmp.GetWidth(); + int h = bmp.GetHeight(); + wxAlphaPixelData pixData(bmp, wxPoint(0, 0), wxSize(w, h)); + + wxAlphaPixelData::Iterator p(pixData); + for (int y = 0; y < h; ++y) { + wxAlphaPixelData::Iterator rowStart = p; + for (int x = 0; x < w; ++x) { + unsigned char a = p.Alpha(); + p.Red() = wxPy_unpremultiply(p.Red(), a); + p.Green() = wxPy_unpremultiply(p.Green(), a); + p.Blue() = wxPy_unpremultiply(p.Blue(), a); + //p.Alpha() = a; + ++p; + } + + p = rowStart; + p.OffsetY(pixData, 1); + } +} + +PyObject* RectPos(const wxRect& rect, const wxPoint& point) +{ + wxPoint p(RectPosPoint(rect, point)); + + PyObject* tup = PyTuple_New(2); + PyTuple_SET_ITEM(tup, 0, PyInt_FromLong(p.x)); + PyTuple_SET_ITEM(tup, 1, PyInt_FromLong(p.y)); + return tup; +} + +/* +IMPLEMENT_DYNAMIC_CLASS(Animation, wxWindow) + +BEGIN_EVENT_TABLE(Animation, wxWindow) + EVT_PAINT(Animation::OnPaint) +END_EVENT_TABLE() + + +Animation::Animation() +{ + timer = new AnimationTimer(this); +} + +Animation::Animation(wxWindow* parent, int id, const AnimationFrameVector& frames, bool repeating) + : wxWindow(parent, id) + , playing(true) + , currentFrame(0) +{ + timer = new AnimationTimer(this); + SetFrames(frames, repeating); +} + +Animation::~Animation() +{ + timer->Stop(); + delete timer; +} + +void Animation::SetFrames(const AnimationFrameVector& frames, bool repeating) +{ + wxASSERT(frames.size() > 0); + this->frames = frames; + repeating = repeating; + CalcMinSize(); +} + + + +void Animation::OnPaint(wxPaintEvent& e) +{ + wxAutoBufferedPaintDC dc(this); + + if (repeating && !timer->IsRunning()) + timer->Start(GetFrameDuration(GetCurrentFrame()), true); + + // draw background + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(wxBrush(GetBackgroundColour())); + dc.DrawRectangle(GetClientRect()); + + // draw the bitmap + dc.DrawBitmap(GetFrameBitmap(GetCurrentFrame()), 0, 0, true); +} + +void Animation::OnTimer() +{ + NextFrame(); + Refresh(); +} + +void Animation::NextFrame() +{ + if (repeating) { + currentFrame = (currentFrame + 1) % frames.size(); + timer->Start(GetFrameDuration(GetCurrentFrame()), true); + } else { + currentFrame += 1; + if (currentFrame >= frames.size()) { + timer->Stop(); + currentFrame = frames.size() - 1; + } else + timer->Start(GetFrameDuration(GetCurrentFrame()), true); + } +} + +void Animation::CalcMinSize() +{ + wxBitmap firstFrame = GetFrameBitmap(0); + wxSize minSize(firstFrame.GetWidth(), firstFrame.GetHeight()); + + for (size_t i = 1; i < frames.size(); ++i) { + wxBitmap b = GetFrameBitmap(i); + minSize.x = max(b.GetWidth(), minSize.x); + minSize.y = max(b.GetHeight(), minSize.y); + } + + SetMinSize(minSize); + SetSize(minSize); +} + +void AnimationTimer::Notify() +{ + if (animation) + animation->OnTimer(); +} +*/ diff --git a/digsby/ext/src/cwindowfx.h b/digsby/ext/src/cwindowfx.h new file mode 100644 index 0000000..1728b90 --- /dev/null +++ b/digsby/ext/src/cwindowfx.h @@ -0,0 +1,246 @@ +// +// cwindowfx.h +// + +#ifndef _CWINDOWFX_H_ +#define _CWINDOWFX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "Python.h" + +bool IsMainThread(); +void redirectStderr(const wxString& filename); +void printToStderr(const wxString& s); +void FitInMonitors(wxWindow* window, const wxPoint& defaultPosition = wxDefaultPosition); +wxIconBundle createIconBundle(const wxBitmap& bitmap); +wxIconBundle createIconBundle(const wxImage& image); +void SetFrameIcon(wxTopLevelWindow* win, const wxImage& image); + +// font utility functions +void ModifyFont(wxWindow* ctrl, int weight = -1, int pointSize = -1, const wxString& faceName = wxEmptyString, int underline = -1); +wxFont ModifiedFont(const wxFont& font_, int weight = -1, int pointSize = -1, const wxString& faceName = wxEmptyString, int underline = -1); +void SetBold(wxWindow* window, bool bold = true); + +/** + * A panel that disables painting of the background. + */ +class SimplePanel : public wxPanel +{ +public: +#if SWIG + %pythonAppend SimplePanel "self._setOORInfo(self)" +#endif + SimplePanel(wxWindow* parent, int style = wxTAB_TRAVERSAL); + void OnEraseBackground(wxEraseEvent& e); + virtual ~SimplePanel(); +#ifndef SWIG +private: + DECLARE_EVENT_TABLE() +#endif +}; + +/** + * A borderless frame whose show method doesn't steal focus. + */ +class NotifyWindow : public wxFrame +{ +public: +#if SWIG + %pythonAppend NotifyWindow "self._setOORInfo(self)" +#endif + NotifyWindow(wxWindow* window, int id, const wxString& title, long style = wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP | wxFRAME_SHAPED | wxNO_BORDER); + virtual ~NotifyWindow(); + virtual bool Show(bool show = true); + void SetRect(const wxRect& rect); +}; + +class Fader : public wxTimer +{ +public: + Fader(wxTopLevelWindow* window, int fromAlpha = 255, int toAlpha = 0, unsigned int step = 8, PyObject* onDone = NULL); + virtual ~Fader(); + virtual void Notify(); + void Stop(bool runStopCallback = true); + PyObject* onDoneCallback; + +protected: + void OnWindowDestroyed(wxWindowDestroyEvent& e); + + wxTopLevelWindow* window; + int to; + int value; + int step; +}; + +#ifdef SWIG +%newobject fadein; +%newobject fadeout; +#endif + +int GetTransparent(wxTopLevelWindow* tlw); + +Fader* fadein(wxTopLevelWindow* window, int fromAlpha = 1, int toAlpha = 255, unsigned int step = 8, PyObject* onDone = NULL); +Fader* fadeout(wxTopLevelWindow* window, int fromAlpha = 255, int toAlpha = 0, unsigned int step = 8, PyObject* onDone = NULL); + +void setalpha(wxTopLevelWindow* window, int alpha); + +void Bitmap_Draw(const wxBitmap& bitmap, wxDC& dc, const wxRect& rect, int alignment = 0); + +int getCacheKey(wxBitmap* bitmap); +int getCacheKey(wxImage* image); + +wxRect RectClamp(const wxRect& self, const wxRect& r, int flags = wxALL); +wxPoint RectClampPoint(const wxRect& self, const wxPoint& pt, int flags = wxALL); + +/** + * Returns true if a top level window is shown, not minimized, and not obscured + * by any other top level window (including windows from other applications). + */ +bool WindowVisible(wxTopLevelWindow* window); + +/** + * Returns a wxBitmap containing the entire display area. + */ +wxBitmap* getScreenBitmap(const wxRect& rect); + +/** + * Returns a wxRect with screen coordinates for the taskbar. + */ +wxRect GetTaskbarRect(); + +/** + * Returns a wxRect with screen coordinates for the system tray area. + */ +wxRect GetTrayRect(); + +/** + * Returns whether the Win version is 7 or higher, false on non-Win platforms. + */ +bool isWin7OrHigher(); + +/** + * Returns whether the Win version is Vista or higher, false on non-Win platforms. + */ +bool isVistaOrHigher(); + +#ifdef __WXMSW__ +/** + * Returns true if the taskbar is set to autohide. + */ +bool GetTaskBarAutoHide(); + +/** + * Returns the normal size a window would be (even if it's maximized). + */ +wxRect GetNormalRect(wxTopLevelWindow* win); + +/** + Returns a series of Unicode code point ranges for the given font. + + Return value is a Python list with [(start, len), (start, len), ...] + */ +PyObject* PyGetFontUnicodeRanges(const wxFont& font); + +void ApplySmokeAndMirrors(wxWindow* win, const wxBitmap& shape, int ox = 0, int oy = 0); +void ApplySmokeAndMirrors(wxWindow* win, const wxRegion& shape); +void ApplySmokeAndMirrors(wxWindow* win, long shape = 0); + + +#endif // __WXMSW__ + +bool LeftDown(); +wxWindow* FindTopLevelWindow(wxWindow* window); + +void Premultiply(wxBitmap& bmp); +void Unpremultiply(wxBitmap& bitmap); + +PyObject* RectPos(const wxRect& rect, const wxPoint& point); + +/* +struct AnimationFrame +{ + AnimationFrame(const wxBitmap bitmap, float duration) + : m_bitmap(bitmap) + , m_duration(duration) + {} + + wxBitmap m_bitmap; + float m_duration; +}; + +typedef std::vector AnimationFrameVector; + +class Animation; + +class AnimationTimer : public wxTimer +{ +public: + AnimationTimer(Animation* anim) + : animation(anim) + {} + + ~AnimationTimer() + { + animation = 0; + } + + virtual void Notify(); + +protected: + Animation* animation; +}; + +class Animation : public wxWindow +{ +public: + Animation(); + Animation(wxWindow* parent, int id = wxID_ANY, const AnimationFrameVector& frames, bool repeating); + ~Animation(); + void SetFrames(const AnimationFrameVector& frames, bool repeating); + + wxBitmap GetFrameBitmap(size_t frameIndex) const + { + wxASSERT(frameIndex < frames.size()); + return frames[frameIndex].m_bitmap; + } + + float GetFrameDuration(size_t frameIndex) const + { + wxASSERT(frameIndex < frames.size()); + return frames[frameIndex].m_duration; + } + + size_t GetCurrentFrame() const + { + return currentFrame; + } + + void OnTimer(); + +protected: + void OnPaint(wxPaintEvent& e); + void NextFrame(); + void CalcMinSize(); + +#ifndef SWIG + DECLARE_EVENT_TABLE() + DECLARE_DYNAMIC_CLASS(Animation) +#endif + + AnimationFrameVector frames; + AnimationTimer* timer; + size_t currentFrame; + bool playing; + bool repeating; +}; + + + +*/ +#endif // _CWINDOWFX_H_ diff --git a/digsby/ext/src/cwindowfx.i b/digsby/ext/src/cwindowfx.i new file mode 100644 index 0000000..f7095a9 --- /dev/null +++ b/digsby/ext/src/cwindowfx.i @@ -0,0 +1,25 @@ +// not a %module + +%{ +#include "wx/wxPython/wxPython.h" +#include "wx/wxPython/pyclasses.h" +#include "wx/wxPython/pyistream.h" +#include +#include +#include +#include +#include + + +#include "cwindowfx.h" +%} + +%import typemaps.i +%import my_typemaps.i + +%import core.i +%import windows.i +%import misc.i + + +%include cwindowfx.h diff --git a/digsby/ext/src/cwindowfx.sip b/digsby/ext/src/cwindowfx.sip new file mode 100644 index 0000000..833fd9b --- /dev/null +++ b/digsby/ext/src/cwindowfx.sip @@ -0,0 +1,124 @@ +%ModuleHeaderCode +#include +#include +#include +#include +#include +#include + +#include "cwindowfx.h" +%End + +bool IsMainThread(); +bool isWin7OrHigher(); +void redirectStderr(const wxString& filename); +void printToStderr(const wxString& s); +void FitInMonitors(wxWindow* window, const wxPoint& defaultPosition = wxDefaultPosition); +void SetFrameIcon(wxTopLevelWindow* win, const wxImage& image); +void SetBold(wxWindow* window, bool bold = true); + +class SimplePanel : wxPanel +{ +public: + SimplePanel(wxWindow* parent /TransferThis/, int style = wxTAB_TRAVERSAL); + //void OnEraseBackground(wxEraseEvent& e); + virtual ~SimplePanel(); +}; + +class NotifyWindow : wxFrame +{ +public: + NotifyWindow(wxWindow* window /TransferThis/, int id, const wxString& title, long style = wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP | wxFRAME_SHAPED | wxNO_BORDER); + virtual ~NotifyWindow(); + virtual bool Show(bool show = true); + void SetRect(const wxRect& rect); +}; + +class Fader : wxTimer +{ +public: + Fader(wxTopLevelWindow* window, int fromAlpha = 255, int toAlpha = 0, unsigned int step = 8, SIP_PYCALLABLE onDone = NULL); + virtual ~Fader(); + virtual void Notify(); + void Stop(bool runStopCallback = true); + + +// let the GC know about onDoneCallback +%GCClearCode + Py_CLEAR(sipCpp->onDoneCallback); + sipRes = 0; +%End + +%GCTraverseCode + PyObject *obj = sipCpp->onDoneCallback; + sipRes = obj ? sipVisit(obj, sipArg) : 0; +%End + +}; + +int GetTransparent(wxTopLevelWindow* tlw); + +Fader* fadein(wxTopLevelWindow* window, int fromAlpha = 1, int toAlpha = 255, unsigned int step = 8, SIP_PYCALLABLE onDone = NULL) + /Factory/; +Fader* fadeout(wxTopLevelWindow* window, int fromAlpha = 255, int toAlpha = 0, unsigned int step = 8, SIP_PYCALLABLE onDone = NULL) + /Factory/; + +void setalpha(wxTopLevelWindow* window, int alpha); + +void Bitmap_Draw(const wxBitmap& bitmap, wxDC& dc, const wxRect& rect, int alignment = 0); + +int getCacheKey(wxBitmap* bitmap); +int getCacheKey(wxImage* image); + +wxRect RectClamp(const wxRect& self, const wxRect& r, int flags = wxALL); +wxPoint RectClampPoint(const wxRect& self, const wxPoint& pt, int flags = wxALL); + +bool WindowVisible(wxTopLevelWindow* window); + +wxBitmap* getScreenBitmap(const wxRect& rect); + + +wxRect GetTrayRect(); + +%If (WXMSW) +bool GetTaskBarAutoHide(); +wxRect GetTaskbarRect(); +wxRect GetNormalRect(wxTopLevelWindow* win); +SIP_PYOBJECT PyGetFontUnicodeRanges(const wxFont& font); +void ApplySmokeAndMirrors(wxWindow* win, const wxBitmap& shape, int ox = 0, int oy = 0); +void ApplySmokeAndMirrors(wxWindow* win, const wxRegion& shape); +void ApplySmokeAndMirrors(wxWindow* win, long shape = 0); +%End + +bool LeftDown(); +wxWindow* FindTopLevelWindow(wxWindow* window); + +void Premultiply(wxBitmap& bmp); +void Unpremultiply(wxBitmap& bitmap); + +/* +struct AnimationFrame +{ + AnimationFrame(const wxBitmap bitmap, float duration); + wxBitmap m_bitmap; + float m_duration; +}; + +typedef std::vector AnimationFrameVector; + +class Animation : wxWindow +{ +private: + Animation(const Animation&); +public: + Animation(wxWindow* parent, int id = wxID_ANY, const AnimationFrameVector& frames, bool repeating); + ~Animation(); + + void SetFrames(const AnimationFrameVector& frames, bool repeating); + wxBitmap GetFrameBitmap(size_t frameIndex) const; + float GetFrameDuration(size_t frameIndex) const; + size_t GetCurrentFrame() const; +}; + + +*/ diff --git a/digsby/ext/src/debugapp.cpp b/digsby/ext/src/debugapp.cpp new file mode 100644 index 0000000..8bdb891 --- /dev/null +++ b/digsby/ext/src/debugapp.cpp @@ -0,0 +1,48 @@ +// +// debugapp.cpp +// + +#ifdef __WXMSW__ + +#include +#include +#include + +#include + +/** + * Returns a std::vector of all thread IDs for a given process. + * + * If pid is 0 (the default) your current process ID is used. + */ +std::vector get_thread_ids(unsigned long pid = 0) +{ + std::vector threadIds; + THREADENTRY32 threadEntry; + threadEntry.dwSize = sizeof(THREADENTRY32); + + // if not specified, default to THIS process + if (!pid) pid = GetCurrentProcessId(); + + // toolhelp: m$ft's most poorly named library? + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if(snapshot == INVALID_HANDLE_VALUE) + return threadIds; + + if(!Thread32First(snapshot, &threadEntry)) { + fprintf(stderr, "Thread32First: err code %d", GetLastError()); + CloseHandle(snapshot); + return threadIds; + } + + // find all threads matching pid + do { + if (threadEntry.th32OwnerProcessID == pid) + threadIds.push_back(threadEntry.th32ThreadID); + } while(Thread32Next(snapshot, &threadEntry)); + + CloseHandle(snapshot); + return threadIds; +} + +#endif \ No newline at end of file diff --git a/digsby/ext/src/debugapp.h b/digsby/ext/src/debugapp.h new file mode 100644 index 0000000..7aae148 --- /dev/null +++ b/digsby/ext/src/debugapp.h @@ -0,0 +1,19 @@ +// +// debugapp.h +// + +#ifndef __DEBUG_APP_H__ +#define __DEBUG_APP_H__ + +#ifdef __WXMSW__ + +/** + * Returns a std::vector of all thread IDs for a given process. + * + * If pid is 0 (the default) your current process ID is used. + */ +std::vector get_thread_ids(unsigned long pid = 0); + +#endif // __WXMSW__ + +#endif diff --git a/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher.sln b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher.sln new file mode 100644 index 0000000..6b953a6 --- /dev/null +++ b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DigsbyLauncher", "DigsbyLauncher\DigsbyLauncher.vcproj", "{F1299581-95CA-40E1-BC78-1A7DDA83E2CD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F1299581-95CA-40E1-BC78-1A7DDA83E2CD}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1299581-95CA-40E1-BC78-1A7DDA83E2CD}.Debug|Win32.Build.0 = Debug|Win32 + {F1299581-95CA-40E1-BC78-1A7DDA83E2CD}.Release|Win32.ActiveCfg = Release|Win32 + {F1299581-95CA-40E1-BC78-1A7DDA83E2CD}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.aps b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.aps new file mode 100644 index 0000000..38621b4 Binary files /dev/null and b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.aps differ diff --git a/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.rc b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.rc new file mode 100644 index 0000000..b149254 --- /dev/null +++ b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.rc @@ -0,0 +1,72 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "..\\..\\digsby.ico" +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.vcproj b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.vcproj new file mode 100644 index 0000000..5436d14 --- /dev/null +++ b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/DigsbyLauncher.vcproj @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/digsby_launcher.cpp b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/digsby_launcher.cpp new file mode 100644 index 0000000..fe4f325 --- /dev/null +++ b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/digsby_launcher.cpp @@ -0,0 +1,51 @@ +#include + +#define DIGSBYAPP_EXE L"lib\\digsby-app.exe" + +LPWSTR get_cwd(){ + DWORD workingDirLen = GetCurrentDirectory(0,NULL); + WCHAR *workingDir = (WCHAR*)malloc(sizeof(WCHAR)*(workingDirLen+1)); + GetCurrentDirectory(workingDirLen,workingDir); + return workingDir; +} + +LPWSTR get_exe_name(){ + DWORD exeNameLen = 1024; + WCHAR* filename = (WCHAR*)malloc(sizeof(WCHAR)*exeNameLen); + GetModuleFileName(NULL, filename, exeNameLen); + + return filename; +} + +LPWSTR get_parent_dir(LPWSTR filename) { + int sz = 1024; + LPTSTR path = (WCHAR*)malloc(sizeof(WCHAR)*sz); + LPTSTR file = 0; //(WCHAR*)malloc(sizeof(WCHAR)*sz); + GetFullPathName(filename, sz, path, &file); + + int parentDirLen = wcslen(path)-wcslen(file); + size_t parentDirSz = sizeof(WCHAR)*(parentDirLen+1); + LPTSTR parentDir = (WCHAR*)malloc(parentDirSz); + wcsncpy(parentDir, filename, parentDirLen); + parentDir[parentDirLen] = 0; + free(path); path = 0; file = 0; + return parentDir; +} + +int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpwCmdLine, int nCmdShow){ + LPWSTR cmdBuffer, exename, parentdir; + exename = get_exe_name(); + parentdir = get_parent_dir(exename); + + size_t cmdBufferLen = wcslen(parentdir) + wcslen(DIGSBYAPP_EXE) + 4 + wcslen(lpwCmdLine); + cmdBuffer = (WCHAR*)malloc(sizeof(WCHAR)*cmdBufferLen); + + wsprintf(cmdBuffer, L"\"%ws%ws\" %ws", parentdir, DIGSBYAPP_EXE, lpwCmdLine); + + PROCESS_INFORMATION pi; + STARTUPINFO si = { sizeof(STARTUPINFO) }; + CreateProcessW( NULL, cmdBuffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + free(exename); exename = 0; + free(parentdir); parentdir = 0; + return 0; +} diff --git a/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/resource.h b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/resource.h new file mode 100644 index 0000000..473e55d --- /dev/null +++ b/digsby/ext/src/digsby-launcher/DigsbyLauncher/DigsbyLauncher/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by DigsbyLauncher.rc +// +#define IDI_ICON1 102 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/digsby/ext/src/digsby-launcher/build.bat b/digsby/ext/src/digsby-launcher/build.bat new file mode 100644 index 0000000..e238b65 --- /dev/null +++ b/digsby/ext/src/digsby-launcher/build.bat @@ -0,0 +1 @@ +vcbuild DigsbyLauncher\DigsbyLauncher.sln "Release|Win32" diff --git a/digsby/ext/src/digsby-launcher/digsby.ico b/digsby/ext/src/digsby-launcher/digsby.ico new file mode 100644 index 0000000..63b2b4f Binary files /dev/null and b/digsby/ext/src/digsby-launcher/digsby.ico differ diff --git a/digsby/ext/src/expando.sip b/digsby/ext/src/expando.sip new file mode 100644 index 0000000..6ef2ee6 --- /dev/null +++ b/digsby/ext/src/expando.sip @@ -0,0 +1,54 @@ +%ModuleHeaderCode +#include "ExpandoTextCtrl.h" +%End + +typedef int WXTYPE; + +enum +{ + wxEVT_ETC_LAYOUT_NEEDED +}; + +class wxExpandEvent : wxCommandEvent +{ +%ConvertToSubClassCode +wxClassInfo* c = sipCpp->GetClassInfo(); + +if(0) ; +#define C(clz) else if (c == CLASSINFO(clz)) sipClass = sipClass_ ## clz; + C(wxExpandEvent) +else + sipClass = NULL; +#undef C +%End + +public: + wxExpandEvent(WXTYPE commandEventType = 0, int id = 0); + wxExpandEvent(const wxExpandEvent &event); + + int height; + int numLines; +}; + +class ExpandoTextCtrl : InputBox +{ +public: + ExpandoTextCtrl(wxWindow *parent, wxWindowID id = wxID_ANY, + const wxString& value = wxEmptyString, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxTextCtrlNameStr); + virtual ~ExpandoTextCtrl(); + + bool SetStyle(long start, long end, const wxTextAttr& style); + void SetMinHeight(const int& h); + int GetMinHeight() const; + void SetMaxHeight(const int& h); + int GetMaxHeight() const; + int GetDecHeight() const; + int GetNatHeight() const; + void RequestResize(); + void AdjustCtrl(long height); +}; diff --git a/digsby/ext/src/jangle/GNUmakefile b/digsby/ext/src/jangle/GNUmakefile new file mode 100644 index 0000000..582c479 --- /dev/null +++ b/digsby/ext/src/jangle/GNUmakefile @@ -0,0 +1,37 @@ +TARGET_ROOT = jangle +TARGET = $(TARGET_ROOT).pyd + +.PHONY: sip clean mostlyclean distclean + +all: Makefile sipAPI$(TARGET_ROOT).h + +Makefile sipAPI$(TARGET_ROOT).h sip: configure.py make_all_sip.py + @../../../../dpython/python.exe configure.py + +install: $(TARGET) + @cmd //C if not exist "../../../build/platlib_win32_26" mkdir "../../../build/platlib_win32_26" + -cp -f libexpat.dll ../../../build/platlib_win32_26/ + -cp -f $(TARGET) "../../../build/platlib_win32_26/$(TARGET)" + +clean: + -rm *.pyc + -rm sip*.obj + -rm *.pdb + -rm $(TARGET) + -rm $(TARGET).manifest + -rm $(TARGET_ROOT).lib + -rm $(TARGET_ROOT).exp + -rm $(TARGET_ROOT).sbf + -rm all.sip + +distclean: clean + -rm sip*.cpp + -rm sip*.h + -rm Makefile + -rm -rf cache + +clobber: distclean mostlyclean clean + +%: force + -@$(MAKE) -f Makefile $@ +force: ; diff --git a/digsby/ext/src/jangle/base/asyncfile.sip b/digsby/ext/src/jangle/base/asyncfile.sip new file mode 100644 index 0000000..f4d6062 --- /dev/null +++ b/digsby/ext/src/jangle/base/asyncfile.sip @@ -0,0 +1,29 @@ + +namespace talk_base { + + +// Provides the ability to perform file I/O asynchronously. +// TODO: Create a common base class with AsyncSocket. +class AsyncFile { + +%TypeHeaderCode +#include "talk/base/asyncfile.h" +%End + +public: + virtual ~AsyncFile(); + + // Determines whether the file will receive read events. + virtual bool readable() = 0; + virtual void set_readable(bool value) = 0; + + // Determines whether the file will receive write events. + virtual bool writable() = 0; + virtual void set_writable(bool value) = 0; + +// sigslot::signal1 SignalReadEvent; +// sigslot::signal1 SignalWriteEvent; +// sigslot::signal2 SignalCloseEvent; +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/asyncpacketsocket.sip b/digsby/ext/src/jangle/base/asyncpacketsocket.sip new file mode 100644 index 0000000..12ac47f --- /dev/null +++ b/digsby/ext/src/jangle/base/asyncpacketsocket.sip @@ -0,0 +1,35 @@ + + +namespace talk_base { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncPacketSocket /* : sigslot::has_slots<> */ { +%TypeHeaderCode +#include "talk/base/asyncpacketsocket.h" +%End +public: + AsyncPacketSocket(talk_base::AsyncSocket* socket); + virtual ~AsyncPacketSocket(); + + // Relevant socket methods: + virtual talk_base::SocketAddress GetLocalAddress() const; + virtual talk_base::SocketAddress GetRemoteAddress() const; + virtual int Bind(const talk_base::SocketAddress& addr); + virtual int Connect(const talk_base::SocketAddress& addr); + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const talk_base::SocketAddress& addr); + virtual int Close(); + virtual int SetOption(talk_base::Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + // Emitted each time a packet is read. +// sigslot::signal4 SignalReadPacket; +/* +protected: + AsyncSocket* socket_; +*/ +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/asyncsocket.sip b/digsby/ext/src/jangle/base/asyncsocket.sip new file mode 100644 index 0000000..c701b71 --- /dev/null +++ b/digsby/ext/src/jangle/base/asyncsocket.sip @@ -0,0 +1,64 @@ + + +namespace talk_base { + +// Provides the ability to perform socket I/O asynchronously. +class AsyncSocket : /*public*/ talk_base::Socket /*, public sigslot::has_slots<> */ { + +%TypeHeaderCode +#include "talk/base/asyncsocket.h" +%End + +public: + virtual ~AsyncSocket(); //{} +/* + sigslot::signal1 SignalReadEvent; // ready to read + sigslot::signal1 SignalWriteEvent; // ready to write + sigslot::signal1 SignalConnectEvent; // connected + sigslot::signal2 SignalCloseEvent; // closed + // TODO: error +*/ +}; + +class AsyncSocketAdapter : /* public */ talk_base::AsyncSocket { + +%TypeHeaderCode +#include "talk/base/asyncsocket.h" +%End + +public: + AsyncSocketAdapter(talk_base::Socket * socket); + AsyncSocketAdapter(talk_base::AsyncSocket * socket); + virtual ~AsyncSocketAdapter(); + + virtual talk_base::SocketAddress GetLocalAddress() const; + virtual talk_base::SocketAddress GetRemoteAddress() const; + + virtual int Bind(const talk_base::SocketAddress& addr); + virtual int Connect(const talk_base::SocketAddress& addr); + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const talk_base::SocketAddress& addr); + virtual int Recv(void *pv, size_t cb); + virtual int RecvFrom(void *pv, size_t cb, talk_base::SocketAddress *paddr); + virtual int Listen(int backlog); + virtual talk_base::Socket *Accept(talk_base::SocketAddress *paddr); + virtual int Close(); + virtual int GetError() const; + virtual void SetError(int error); + + virtual ConnState GetState() const; + + virtual int EstimateMTU(uint16* mtu); + virtual int SetOption(Option opt, int value); +/* +protected: + virtual void OnConnectEvent(AsyncSocket * socket) { SignalConnectEvent(this); } + virtual void OnReadEvent(AsyncSocket * socket) { SignalReadEvent(this); } + virtual void OnWriteEvent(AsyncSocket * socket) { SignalWriteEvent(this); } + virtual void OnCloseEvent(AsyncSocket * socket, int err) { SignalCloseEvent(this, err); } + + Socket * socket_; +*/ +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/asynctcpsocket.sip b/digsby/ext/src/jangle/base/asynctcpsocket.sip new file mode 100644 index 0000000..45631f5 --- /dev/null +++ b/digsby/ext/src/jangle/base/asynctcpsocket.sip @@ -0,0 +1,43 @@ +namespace talk_base { + +// Simulates UDP semantics over TCP. Send and Recv packet sizes +// are preserved, and drops packets silently on Send, rather than +// buffer them in user space. +class AsyncTCPSocket : /* public */ talk_base::AsyncPacketSocket { + +%TypeHeaderCode +#include "talk/base/asynctcpsocket.h" +%End + +public: + AsyncTCPSocket(talk_base::AsyncSocket* socket); + virtual ~AsyncTCPSocket(); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const talk_base::SocketAddress& addr); + +/* + sigslot::signal1 SignalConnect; + sigslot::signal2 SignalClose; +*/ +/* +protected: + int SendRaw(const void * pv, size_t cb); + virtual void ProcessInput(char * data, size_t& len); +*/ +/* +private: + char* inbuf_, * outbuf_; + size_t insize_, inpos_, outsize_, outpos_; + + int Flush(); + + // Called by the underlying socket + void OnConnectEvent(AsyncSocket* socket); + void OnReadEvent(AsyncSocket* socket); + void OnWriteEvent(AsyncSocket* socket); + void OnCloseEvent(AsyncSocket* socket, int error); +*/ +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/asyncudpsocket.sip b/digsby/ext/src/jangle/base/asyncudpsocket.sip new file mode 100644 index 0000000..207256b --- /dev/null +++ b/digsby/ext/src/jangle/base/asyncudpsocket.sip @@ -0,0 +1,33 @@ + +namespace talk_base { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncUDPSocket : /*public*/ talk_base::AsyncPacketSocket { + +%TypeHeaderCode +#include "talk/base/asyncudpsocket.h" +%End + +public: + AsyncUDPSocket(talk_base::AsyncSocket* socket); + virtual ~AsyncUDPSocket(); +/* +private: + char* buf_; + size_t size_; + + // Called when the underlying socket is ready to be read from. + void OnReadEvent(AsyncSocket* socket); +*/ +}; + +/* +// Creates a new socket for sending asynchronous UDP packets using an +// asynchronous socket from the given factory. +inline AsyncUDPSocket* CreateAsyncUDPSocket(SocketFactory* factory) { + return new AsyncUDPSocket(factory->CreateAsyncSocket(SOCK_DGRAM)); +} +*/ + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/base64.sip b/digsby/ext/src/jangle/base/base64.sip new file mode 100644 index 0000000..bd46537 --- /dev/null +++ b/digsby/ext/src/jangle/base/base64.sip @@ -0,0 +1,18 @@ + +namespace talk_base { + +class Base64 +{ + +%TypeHeaderCode +#include "talk/base/base64.h" +%End + +public: + static std::string encode(const std::string & data); + static std::string decode(const std::string & data); + static std::string encodeFromArray(const char * data, size_t len); + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/basictypes.sip b/digsby/ext/src/jangle/base/basictypes.sip new file mode 100644 index 0000000..b0b2219 --- /dev/null +++ b/digsby/ext/src/jangle/base/basictypes.sip @@ -0,0 +1,18 @@ + +//#ifdef COMPILER_MSVC +//typedef unsigned __int64 uint64; +//typedef __int64 int64; +// #else +typedef unsigned long long uint64; +typedef long long int64; +//#endif /* COMPILER_MSVC */ +//%End + + +typedef long int32; +typedef short int16; +typedef char int8; + +typedef unsigned long uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; diff --git a/digsby/ext/src/jangle/base/bytebuffer.sip b/digsby/ext/src/jangle/base/bytebuffer.sip new file mode 100644 index 0000000..076faab --- /dev/null +++ b/digsby/ext/src/jangle/base/bytebuffer.sip @@ -0,0 +1,44 @@ + +namespace talk_base { + +class ByteBuffer { + +%TypeHeaderCode +#include "talk/base/bytebuffer.h" +%End + +public: + ByteBuffer(); + ByteBuffer(const char* bytes, size_t len); + ByteBuffer(const char* bytes); // uses strlen + ~ByteBuffer(); + + const char* Data() const; + size_t Length(); + size_t Capacity(); + + bool ReadUInt8(uint8& val); + bool ReadUInt16(uint16& val); + bool ReadUInt32(uint32& val); + bool ReadString(std::string& val, size_t len); // append to val + bool ReadBytes(char* val, size_t len); + + void WriteUInt8(uint8 val); + void WriteUInt16(uint16 val); + void WriteUInt32(uint32 val); + void WriteString(const std::string& val); + void WriteBytes(const char* val, size_t len); + + void Resize(size_t size); + void Shift(size_t size); + +/* +private: + char* bytes_; + size_t size_; + size_t start_; + size_t end_; +*/ +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/byteorder.sip b/digsby/ext/src/jangle/base/byteorder.sip new file mode 100644 index 0000000..e019992 --- /dev/null +++ b/digsby/ext/src/jangle/base/byteorder.sip @@ -0,0 +1,27 @@ +%ModuleHeaderCode +#include "talk/base/byteorder.h" +%End + +namespace talk_base { + +uint16 HostToNetwork16(uint16 n); +%MethodCode + sipRes = htons(a0); +%End + +uint32 HostToNetwork32(uint32 n); +%MethodCode + sipRes = htonl(a0); +%End + +uint16 NetworkToHost16(uint16 n); +%MethodCode + sipRes = ntohs(a0); +%End + +uint32 NetworkToHost32(uint32 n); +%MethodCode + sipRes = ntohl(a0); +%End + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/cryptstring.sip b/digsby/ext/src/jangle/base/cryptstring.sip new file mode 100644 index 0000000..e47ee32 --- /dev/null +++ b/digsby/ext/src/jangle/base/cryptstring.sip @@ -0,0 +1,87 @@ + +%ModuleHeaderCode +#include "talk/base/cryptstring.h" +%End + +namespace talk_base { + +class CryptStringImpl { +public: + virtual ~CryptStringImpl(); + virtual size_t GetLength() const = 0; + virtual void CopyTo(char * dest, bool nullterminate) const = 0; + virtual std::string UrlEncode() const = 0; + virtual talk_base::CryptStringImpl * Copy() const = 0; +}; + +class EmptyCryptStringImpl : talk_base::CryptStringImpl { +public: + virtual ~EmptyCryptStringImpl(); + virtual size_t GetLength() const; + virtual void CopyTo(char * dest, bool nullterminate) const; + virtual std::string UrlEncode() const; + virtual talk_base::CryptStringImpl * Copy() const; +}; + +class CryptString { +public: + CryptString(); + size_t GetLength() const; + void CopyTo(char * dest, bool nullterminate) const; + CryptString(const talk_base::CryptString & other); + explicit CryptString(const talk_base::CryptStringImpl & impl); +// CryptString & operator=(const talk_base::CryptString & other); + void Clear(); + std::string UrlEncode() const; + +}; + + +// Used for constructing strings where a password is involved and we +// need to ensure that we zero memory afterwards +class FormatCryptString { +public: + FormatCryptString(); + + void Append(const std::string & text); + + void Append(const char * data, size_t length); + + void Append(const talk_base::CryptString * password); + + size_t GetLength(); + + const char * GetData(); + + + // Ensures storage of at least n bytes + void EnsureStorage(size_t n); + + ~FormatCryptString(); +}; + +class InsecureCryptStringImpl : talk_base::CryptStringImpl { + public: + //std::string& password(); + //const std::string& password() const; + std::string password; +%GetCode + sipPy = PyString_FromStringAndSize(sipCpp->password().c_str(), sipCpp->password().size()); +%End +%SetCode + if (PyBytes_Check(sipPy)) { + sipCpp->password() = std::string(PyString_AsString(sipPy), PyString_GET_SIZE(sipPy)); + } else { + PyErr_SetString(PyExc_ValueError, "expected str"); + sipErr = true; + } +%End + + virtual ~InsecureCryptStringImpl(); + virtual size_t GetLength() const; + virtual void CopyTo(char * dest, bool nullterminate); + virtual std::string UrlEncode() const; + virtual talk_base::CryptStringImpl * Copy(); +}; + +}; diff --git a/digsby/ext/src/jangle/base/helpers.sip b/digsby/ext/src/jangle/base/helpers.sip new file mode 100644 index 0000000..18d8cf3 --- /dev/null +++ b/digsby/ext/src/jangle/base/helpers.sip @@ -0,0 +1,24 @@ +%ModuleHeaderCode +#include "talk/base/helpers.h" +%End + +namespace cricket { + +// srand initializer +void InitRandom(const char *client_unique, size_t len); + +// For testing, the random seed can be directly accessed. +long GetRandomSeed(); +void SetRandomSeed(unsigned long seed); + +// Generates a (cryptographically) random string of the given length. +std::string CreateRandomString(int length); + +// Generates a random id +uint32 CreateRandomId(); + +// Determines whether the given string consists entirely of valid base64 +// encoded characters. +bool IsBase64Encoded(const std::string& str); + +}; // namespace cricket diff --git a/digsby/ext/src/jangle/base/host.sip b/digsby/ext/src/jangle/base/host.sip new file mode 100644 index 0000000..a1272d3 --- /dev/null +++ b/digsby/ext/src/jangle/base/host.sip @@ -0,0 +1,31 @@ +/* +namespace talk_base { + +// Provides information about a host in the network. +class Host { + +%TypeHeaderCode +#include "talk/base/host.h" +%End + +public: + Host(const std::string& name, std::vector* networks); + + const std::string& name() const; + const std::vector& networks() const; + +private: + std::string name_; + std::vector* networks_; + +}; + +// Returns a reference to the description of the local host. +const talk_base::Host& LocalHost(); + +// Returns the name of the local host. +std::string GetHostName(); + +}; // namespace talk_base + +*/ diff --git a/digsby/ext/src/jangle/base/messagequeue.sip b/digsby/ext/src/jangle/base/messagequeue.sip new file mode 100644 index 0000000..c2cc0cf --- /dev/null +++ b/digsby/ext/src/jangle/base/messagequeue.sip @@ -0,0 +1,178 @@ +namespace talk_base { + +// MessageQueueManager does cleanup of of message queues + +class MessageQueueManager { + +%TypeHeaderCode +#include "talk/base/messagequeue.h" +%End + +public: + static talk_base::MessageQueueManager* Instance(); + + void Add(talk_base::MessageQueue *message_queue); + void Remove(talk_base::MessageQueue *message_queue); + void Clear(talk_base::MessageHandler *handler); + +private: + MessageQueueManager(); + ~MessageQueueManager(); +/* + static MessageQueueManager* instance_; + // This list contains 'active' MessageQueues. + std::vector message_queues_; + CriticalSection crit_; +*/ +}; + +// Messages get dispatched to a MessageHandler + +class MessageHandler { + +%TypeHeaderCode +#include "talk/base/messagequeue.h" +%End + +public: + virtual ~MessageHandler(); + + virtual void OnMessage(talk_base::Message *pmsg) = 0; +}; + +// Derive from this for specialized data +// App manages lifetime, except when messages are purged + +class MessageData { + +%TypeHeaderCode +#include "talk/base/messagequeue.h" +%End + +public: + MessageData(); + virtual ~MessageData(); +}; +/* +template +class TypedMessageData : public MessageData { +public: + TypedMessageData(const T& data) : data_(data) { } + const T& data() const { return data_; } + T& data() { return data_; } +private: + T data_; +}; + +template +inline MessageData* WrapMessageData(const T& data) { + return new TypedMessageData(data); +} + +template +inline const T& UseMessageData(MessageData* data) { + return static_cast< TypedMessageData* >(data)->data(); +} + +template +class DisposeData : public MessageData { +public: + DisposeData(T* data) : data_(data) { } + virtual ~DisposeData() { delete data_; } +private: + T* data_; +}; +*/ + +const uint32 MQID_ANY; +const uint32 MQID_DISPOSE; + +// No destructor + +struct Message { + Message(); + talk_base::MessageHandler *phandler; + uint32 message_id; + talk_base::MessageData *pdata; + uint32 ts_sensitive; +}; + +// DelayedMessage goes into a priority queue, sorted by trigger time + +class DelayedMessage { + +%TypeHeaderCode +#include "talk/base/messagequeue.h" +%End + + +public: + DelayedMessage(int cmsDelay, talk_base::Message *pmsg); + + bool operator< (const talk_base::DelayedMessage& dmsg) const; + + int cmsDelay_; // for debugging + uint32 msTrigger_; + talk_base::Message msg_; +}; + +class MessageQueue { + +%TypeHeaderCode +#include "talk/base/messagequeue.h" +%End + +public: + MessageQueue(talk_base::SocketServer* ss = 0); + virtual ~MessageQueue(); + + talk_base::SocketServer* socketserver(); + void set_socketserver(talk_base::SocketServer* ss); + + // Note: The behavior of MessageQueue has changed. When a MQ is stopped, + // futher Posts and Sends will fail. However, any pending Sends and *ready* + // Posts (as opposed to unexpired delayed Posts) will be delivered before + // Get (or Peek) returns false. By guaranteeing delivery of those messages, + // we eliminate the race condition when an MessageHandler and MessageQueue + // may be destroyed independently of each other. + + virtual void Stop(); + virtual bool IsStopping(); + virtual void Restart(); + + // Get() will process I/O until: + // 1) A message is available (returns true) + // 2) cmsWait seconds have elapsed (returns false) + // 3) Stop() is called (returns false) + virtual bool Get(talk_base::Message *pmsg, int cmsWait = talk_base::kForever); + virtual bool Peek(talk_base::Message *pmsg, int cmsWait = 0); + virtual void Post(talk_base::MessageHandler *phandler, uint32 id = 0, + talk_base::MessageData *pdata = NULL, bool time_sensitive = false); + virtual void PostDelayed(int cmsDelay, talk_base::MessageHandler *phandler, + uint32 id = 0, talk_base::MessageData *pdata = NULL); + virtual void Clear(talk_base::MessageHandler *phandler, uint32 id = talk_base::MQID_ANY); + virtual void Dispatch(talk_base::Message *pmsg); + virtual void ReceiveSends(); + virtual int GetDelay(); + + // Internally posts a message which causes the doomed object to be deleted + //template void Dispose(T* doomed); + +protected: + void EnsureActive(); +/* + SocketServer* ss_; + bool new_ss; + bool fStop_; + bool fPeekKeep_; + Message msgPeek_; + // A message queue is active if it has ever had a message posted to it. + // This also corresponds to being in MessageQueueManager's global list. + bool active_; + std::queue msgq_; + std::priority_queue dmsgq_; + CriticalSection crit_; +*/ +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/network.sip b/digsby/ext/src/jangle/base/network.sip new file mode 100644 index 0000000..2bbdb45 --- /dev/null +++ b/digsby/ext/src/jangle/base/network.sip @@ -0,0 +1,111 @@ + +namespace talk_base { + +/* +// Keeps track of the available network interfaces over time so that quality +// information can be aggregated and recorded. +class NetworkManager { + +%TypeHeaderCode +#include "talk/base/network.h" +%End +public: + + // Updates and returns the current list of networks available on this machine. + // This version will make sure that repeated calls return the same object for + // a given network, so that quality is tracked appropriately. + void GetNetworks(std::vector& networks); + + // Reads and writes the state of the quality database in a string format. + std::string GetState(); + void SetState(std::string str); + + // Creates a network object for each network available on the machine. + static void CreateNetworks(std::vector& networks); + +private: + typedef std::map NetworkMap; + + NetworkMap networks_; + +}; + +// Represents a Unix-type network interface, with a name and single address. +// It also includes the ability to track and estimate quality. +class Network { + +%TypeHeaderCode +#include "talk/base/network.h" +%End + +public: + Network(const std::string& name, uint32 ip); + + // Returns the OS name of this network. This is considered the primary key + // that identifies each network. + const std::string& name() const; + + // Identifies the current IP address used by this network. + uint32 ip() const; + void set_ip(uint32 ip); + + // Updates the list of sessions that are ongoing. + void StartSession(NetworkSession* session); + void StopSession(NetworkSession* session); + + // Re-computes the estimate of near-future quality based on the information + // as of this exact moment. + void EstimateQuality(); + + // Returns the current estimate of the near-future quality of connections + // that use this local interface. + double quality(); + + // Debugging description of this network + std::string ToString() const; + +private: + typedef std::vector SessionList; + + std::string name_; + uint32 ip_; + SessionList sessions_; + double uniform_numerator_; + double uniform_denominator_; + double exponential_numerator_; + double exponential_denominator_; + uint32 last_data_time_; + double quality_; + + // Updates the statistics maintained to include the given estimate. + void AddDataPoint(uint32 time, double quality); + + // Converts the internal state to and from a string. This is used to record + // quality information into a permanent store. + void SetState(std::string str); + std::string GetState(); + + friend class NetworkManager; + +}; + +// Represents a session that is in progress using a particular network and can +// provide data about the quality of the network at any given moment. +class NetworkSession { +public: + // Determines whether this session has an estimate at this moment. We will + // only call GetCurrentQuality when this returns true. + virtual bool HasQuality() = 0; + + // Returns an estimate of the quality at this exact moment. The result should + // be a MOS (mean opinion score) value. + virtual float GetCurrentQuality() = 0; + +}; +*/ + +const double QUALITY_BAD; +const double QUALITY_FAIR; +const double QUALITY_GOOD; + +}; // namespace talk_base diff --git a/digsby/ext/src/jangle/base/physicalsocketserver.sip b/digsby/ext/src/jangle/base/physicalsocketserver.sip new file mode 100644 index 0000000..23a2bca --- /dev/null +++ b/digsby/ext/src/jangle/base/physicalsocketserver.sip @@ -0,0 +1,58 @@ +%Platforms {WIN32_PLATFORM POSIX_PLATFORM MACOS_PLATFORM} + +%If(WIN32_PLATFORM) +//WinSock2.h +/* + * The new type to be used in all + * instances which refer to sockets. + */ +typedef unsigned int UINT_PTR; +typedef UINT_PTR SOCKET; +%End + +%If(POSIX_PLATFORM || MACOS_PLATFORM) +typedef int SOCKET; +%End // POSIX + +namespace talk_base { + +// A socket server that provides the real sockets of the underlying OS. +class PhysicalSocketServer : /*public*/ talk_base::SocketServer { + +%TypeHeaderCode +#include "talk/base/physicalsocketserver.h" +%End + +public: + PhysicalSocketServer(); + virtual ~PhysicalSocketServer(); + + // SocketFactory: + virtual talk_base::Socket* CreateSocket(int type); + virtual talk_base::AsyncSocket* CreateAsyncSocket(int type); + + // Internal Factory for Accept + talk_base::AsyncSocket* WrapSocket(SOCKET s); + + // SocketServer: + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); +/* + void Add(Dispatcher* dispatcher); + void Remove(Dispatcher* dispatcher); +*/ +%If(POSIX_PLATFORM || MACOS_PLATFORM) + AsyncFile* CreateFile(int fd); +%End +/* +private: + std::vector dispatchers_; + Signaler* signal_wakeup_; + CriticalSection crit_; + bool fWait_; + uint32 last_tick_tracked_; + int last_tick_dispatch_count_; +*/ +}; + +}; // namespace talk_base diff --git a/digsby/ext/src/jangle/base/proxydetect.sip b/digsby/ext/src/jangle/base/proxydetect.sip new file mode 100644 index 0000000..7e16a57 --- /dev/null +++ b/digsby/ext/src/jangle/base/proxydetect.sip @@ -0,0 +1,8 @@ + +%ModuleHeaderCode +#include "talk/base/proxydetect.h" +%End + +bool GetProxySettingsForUrl(const char* agent, const char* url, + talk_base::ProxyInfo& proxy /Out/, + bool long_operation = false); \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/proxyinfo.sip b/digsby/ext/src/jangle/base/proxyinfo.sip new file mode 100644 index 0000000..fd72519 --- /dev/null +++ b/digsby/ext/src/jangle/base/proxyinfo.sip @@ -0,0 +1,18 @@ + + + +namespace talk_base { + +enum ProxyType { PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN }; +const char * ProxyToString(talk_base::ProxyType proxy); + +struct ProxyInfo { + talk_base::ProxyType type; + talk_base::SocketAddress address; + std::string username; + talk_base::CryptString password; + + ProxyInfo(); +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/schanneladapter.sip b/digsby/ext/src/jangle/base/schanneladapter.sip new file mode 100644 index 0000000..f378ba9 --- /dev/null +++ b/digsby/ext/src/jangle/base/schanneladapter.sip @@ -0,0 +1,70 @@ + +struct _SecBufferDesc; + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// + +class SChannelAdapter : /*public*/ talk_base::SSLAdapter, /*public*/ talk_base::MessageHandler { + +%TypeHeaderCode +#define SECURITY_WIN32 +#include +#include +#include "talk/base/schanneladapter.h" +%End + +public: + SChannelAdapter(talk_base::AsyncSocket* socket); + virtual ~SChannelAdapter(); + + virtual int StartSSL(const char* hostname, bool restartable); + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + virtual int Close(); + + // Note that the socket returns ST_CONNECTING while SSL is being negotiated. + virtual ConnState GetState() const; + + struct SSLImpl; +protected: + enum SSLState { + SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR + }; + + virtual void OnConnectEvent(talk_base::AsyncSocket* socket); + virtual void OnReadEvent(talk_base::AsyncSocket* socket); + virtual void OnWriteEvent(talk_base::AsyncSocket* socket); + virtual void OnCloseEvent(talk_base::AsyncSocket* socket, int err); + virtual void OnMessage(talk_base::Message* pmsg); + + int BeginSSL(); + int ContinueSSL(); + int ProcessContext(long status, _SecBufferDesc* sbd_in, + _SecBufferDesc* sbd_out); + int DecryptData(); + + int Read(); + int Flush(); + void Error(const char* context, int err, bool signal = true); + void Cleanup(); + + void PostEvent(); + +/* +private: + SSLState state_; + std::string ssl_host_name_; + // If true, socket will retain SSL configuration after Close. + bool restartable_; + // If true, we are delaying signalling close until all data is read. + bool signal_close_; + // If true, we are waiting to be woken up to signal readability or closure. + bool message_pending_; + SSLImpl* impl_; +*/ +}; + +///////////////////////////////////////////////////////////////////////////// + +}; // namespace talk_base diff --git a/digsby/ext/src/jangle/base/socket.sip b/digsby/ext/src/jangle/base/socket.sip new file mode 100644 index 0000000..95880e6 --- /dev/null +++ b/digsby/ext/src/jangle/base/socket.sip @@ -0,0 +1,63 @@ + + +namespace talk_base { + + +// General interface for the socket implementations of various networks. The +// methods match those of normal UNIX sockets very closely. +class Socket { + +%TypeHeaderCode +#include "talk/base/socket.h" +%End + +public: + virtual ~Socket(); + + // Returns the address to which the socket is bound. If the socket is not + // bound, then the any-address is returned. + virtual talk_base::SocketAddress GetLocalAddress() const = 0; + + // Returns the address to which the socket is connected. If the socket is + // not connected, then the any-address is returned. + virtual talk_base::SocketAddress GetRemoteAddress() const = 0; + + virtual int Bind(const talk_base::SocketAddress& addr) = 0; + virtual int Connect(const talk_base::SocketAddress& addr) = 0; + virtual int Send(const void *pv, size_t cb) = 0; + virtual int SendTo(const void *pv, size_t cb, const talk_base::SocketAddress& addr) = 0; + virtual int Recv(void *pv, size_t cb) = 0; + virtual int RecvFrom(void *pv, size_t cb, talk_base::SocketAddress *paddr) = 0; + virtual int Listen(int backlog) = 0; + virtual talk_base::Socket *Accept(talk_base::SocketAddress *paddr) = 0; + virtual int Close() = 0; + virtual int GetError() const = 0; + virtual void SetError(int error) = 0; + bool IsBlocking() const; + + enum ConnState { + CS_CLOSED, + CS_CONNECTING, + CS_CONNECTED + }; + virtual ConnState GetState() const = 0; + + // Fills in the given uint16 with the current estimate of the MTU along the + // path to the address to which this socket is connected. + virtual int EstimateMTU(uint16* mtu) = 0; + + enum Option { + OPT_DONTFRAGMENT + }; + virtual int SetOption(talk_base::Socket::Option opt, int value) = 0; + +protected: + Socket(); + +private: + Socket(const talk_base::Socket&); +// void operator=(const Socket&); + +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/socketaddress.sip b/digsby/ext/src/jangle/base/socketaddress.sip new file mode 100644 index 0000000..2f287d9 --- /dev/null +++ b/digsby/ext/src/jangle/base/socketaddress.sip @@ -0,0 +1,150 @@ + +namespace talk_base { + +%TypeHeaderCode +#include "talk/base/socketaddress.h" +#undef SetPort +%End + +// Records an IP address and port, which are 32 and 16 bit integers, +// respectively, both in host byte-order. +class SocketAddress { + +public: + // Creates a missing / unknown address. + SocketAddress(); + + // Creates the address with the given host and port. If use_dns is true, + // the hostname will be immediately resolved to an IP (which may block for + // several seconds if DNS is not available). Alternately, set use_dns to + // false, and then call Resolve() to complete resolution later, or use + // SetResolvedIP to set the IP explictly. + SocketAddress(const std::string& hostname, int port = 0, bool use_dns = true); + + // Creates the address with the given IP and port. + SocketAddress(uint32 ip, int port); + + // Creates a copy of the given address. + SocketAddress(const talk_base::SocketAddress& addr); + + // Resets to missing / unknown address. + void Clear(); + + // Replaces our address with the given one. +// SocketAddress& operator =(const talk_base::SocketAddress& addr); + + // Changes the IP of this address to the given one, and clears the hostname. + void SetIP(uint32 ip); + + // Changes the hostname of this address to the given one. + // Calls Resolve and returns the result. + bool SetIP(const std::string& hostname, bool use_dns = true); + + // Sets the IP address while retaining the hostname. Useful for bypassing + // DNS for a pre-resolved IP. + void SetResolvedIP(uint32 ip); + + // Changes the port of this address to the given one. + void SetPort(int port); + + // Returns the IP address. + uint32 ip() const; + + // Returns the port part of this address. + uint16 port() const; + + // Returns the hostname + const std::string& hostname() const; + + // Returns the IP address in dotted form. + std::string IPAsString() const; + + // Returns the port as a string + std::string PortAsString() const; + + // Returns a display version of the IP/port. + std::string ToString() const; + + // Determines whether this represents a missing / any address. + bool IsAny() const; + + // Synomym for missing / any. + bool IsNil() const; + + // Determines whether the IP address refers to the local host, i.e. within + // the range 127.0.0.0/8. + bool IsLocalIP() const; + + // Determines whether the IP address is in one of the private ranges: + // 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12. + bool IsPrivateIP() const; + + // Determines whether the hostname has been resolved to an IP + bool IsUnresolved() const; + + // Attempt to resolve a hostname to IP address. + // Returns false if resolution is required but failed. + // 'force' will cause re-resolution of hostname. + // + bool Resolve(bool force = false, bool use_dns = true); + + // Determines whether this address is identical to the given one. + bool operator ==(const talk_base::SocketAddress& addr) const; + + bool operator !=(const talk_base::SocketAddress& addr) const; + + // Compares based on IP and then port. + bool operator <(const talk_base::SocketAddress& addr) const; + + // Determines whether this address has the same IP as the one given. + bool EqualIPs(const talk_base::SocketAddress& addr) const; + + // Deteremines whether this address has the same port as the one given. + bool EqualPorts(const talk_base::SocketAddress& addr) const; + + // Hashes this address into a small number. + size_t Hash() const; + + // Returns the size of this address when written. + size_t Size_() const; + + // Writes this address into the given buffer. + void Write_(char* buf, int len) const; + + // Reads this address from the given buffer. + void Read_(const char* buf, int len); + + // Convert to and from sockaddr_in +// void ToSockAddr(sockaddr_in* saddr) const; +// void FromSockAddr(const sockaddr_in& saddr); + + // Converts the IP address given in compact form into dotted form. + static std::string IPToString(uint32 ip); + + // Converts the IP address given in dotted form into compact form. + // Without 'use_dns', only dotted names (A.B.C.D) are resolved. + static uint32 StringToIP(const std::string& str, bool use_dns = true); + + // Get local machine's hostname + static std::string GetHostname(); + + // Get a list of the local machine's ip addresses +// static bool GetLocalIPs(std::vector& ips); + SIP_PYLIST GetLocalIPs(); +%MethodCode + std::vector ips; + if (!sipCpp->GetLocalIPs(ips)) { + PyErr_SetString(PyExc_ValueError, "couldn't retrieve ips"); + sipIsErr = 1; + } else { + // stuff + sipRes = PyList_New(ips.size()); + for (int i = 0; i < ips.size(); i++) { + PyList_SET_ITEM(sipRes, i, PyInt_FromLong(ips[i])); + } + } +%End + +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/socketfactory.sip b/digsby/ext/src/jangle/base/socketfactory.sip new file mode 100644 index 0000000..54d4fa5 --- /dev/null +++ b/digsby/ext/src/jangle/base/socketfactory.sip @@ -0,0 +1,21 @@ +namespace talk_base { + +class SocketFactory { + +%TypeHeaderCode +#include "talk/base/socketfactory.h" +%End + +public: + virtual ~SocketFactory(); + + // Returns a new socket for blocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual talk_base::Socket* CreateSocket(int type) = 0; + + // Returns a new socket for nonblocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual talk_base::AsyncSocket* CreateAsyncSocket(int type) = 0; +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/socketserver.sip b/digsby/ext/src/jangle/base/socketserver.sip new file mode 100644 index 0000000..4a5ede8 --- /dev/null +++ b/digsby/ext/src/jangle/base/socketserver.sip @@ -0,0 +1,28 @@ +namespace talk_base { + +const int kForever; + +// Provides the ability to wait for activity on a set of sockets. The Thread +// class provides a nice wrapper on a socket server. +// +// The server is also a socket factory. The sockets it creates will be +// notified of asynchronous I/O from this server's Wait method. +class SocketServer : /*public*/ talk_base::SocketFactory { + +%TypeHeaderCode +#include "talk/base/socketserver.h" +%End + +public: + + // Sleeps until: + // 1) cms milliseconds have elapsed (unless cms == kForever) + // 2) WakeUp() is called + // While sleeping, I/O is performed if process_io is true. + virtual bool Wait(int cms, bool process_io) = 0; + + // Causes the current wait (if one is in progress) to wake up. + virtual void WakeUp() = 0; +}; + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/ssladapter.sip b/digsby/ext/src/jangle/base/ssladapter.sip new file mode 100644 index 0000000..cadad38 --- /dev/null +++ b/digsby/ext/src/jangle/base/ssladapter.sip @@ -0,0 +1,45 @@ +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// + +class SSLAdapter : /*public*/ talk_base::AsyncSocketAdapter { + +%TypeHeaderCode +#include "talk/base/ssladapter.h" +%End + +public: + SSLAdapter(talk_base::AsyncSocket* socket); + + bool ignore_bad_cert() const; + void set_ignore_bad_cert(bool ignore); + + // StartSSL returns 0 if successful. + // If StartSSL is called while the socket is closed or connecting, the SSL + // negotiation will begin as soon as the socket connects. + virtual int StartSSL(const char* hostname, bool restartable) = 0; + + // Create the default SSL adapter for this platform + static talk_base::SSLAdapter* Create(talk_base::AsyncSocket* socket); +/* +private: + // If true, the server certificate need not match the configured hostname. + bool ignore_bad_cert_; +*/ +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Call this on the main thread, before using SSL. +// Call CleanupSSLThread when finished with SSL. +bool InitializeSSL(); + +// Call to initialize additional threads. +bool InitializeSSLThread(); + +// Call to cleanup additional threads, and also the main thread. +bool CleanupSSL(); + +/////////////////////////////////////////////////////////////////////////////// + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/task.sip b/digsby/ext/src/jangle/base/task.sip new file mode 100644 index 0000000..12c55a1 --- /dev/null +++ b/digsby/ext/src/jangle/base/task.sip @@ -0,0 +1,53 @@ + +%Include basictypes.sip + +namespace talk_base { + +class Task /Abstract/ { + +%TypeHeaderCode +#include "talk/base/sigslot.h" +#include "talk/base/task.h" +%End + + private: + Task(const talk_base::Task &); + Task(); + + public: + Task(talk_base::Task *parent /TransferThis/); + + int32 get_unique_id(); + + void Start(); + void Step(); + int GetState() const; + bool HasError() const; + bool Blocked() const; + bool IsDone() const; + int64 ElapsedTime(); + + talk_base::Task *GetParent(); + talk_base::TaskRunner *GetRunner(); + virtual talk_base::Task *GetParent(int code); + + // Called from outside to stop task without any more callbacks + void Abort(bool nowake = false); + + // For managing children + bool AllChildrenDone(); + bool AnyChildError(); + + bool TimedOut(); + + int64 get_timeout_time(); + void set_timeout_seconds(int timeout_seconds); + +// sigslot::signal0<> SignalTimeout; + + // Called inside the task to signal that the task may be unblocked + void Wake(); + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/taskrunner.sip b/digsby/ext/src/jangle/base/taskrunner.sip new file mode 100644 index 0000000..09593c5 --- /dev/null +++ b/digsby/ext/src/jangle/base/taskrunner.sip @@ -0,0 +1,37 @@ + +%Include basictypes.sip + +namespace talk_base { + +%TypeHeaderCode +#include "talk/base/sigslot.h" +#include "talk/base/taskrunner.h" +%End + +const int64 kSecToMsec; +const int64 kMsecTo100ns; +const int64 kSecTo100ns; + +class TaskRunner : talk_base::Task { + public: + TaskRunner(); + virtual ~TaskRunner(); + + virtual void WakeTasks() = 0; + + // This method returns the current time in 100ns units since 1/1/1601. This + // Is the GetSystemTimeAsFileTime method on windows. + virtual int64 CurrentTime() = 0 ; + + void StartTask(talk_base::Task *task); + void RunTasks(); + void PollTasks(); + + void UpdateTaskTimeout(talk_base::Task *task); + + // dummy state machine - never run. + virtual int ProcessStart(); + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/base/time.sip b/digsby/ext/src/jangle/base/time.sip new file mode 100644 index 0000000..5a153fb --- /dev/null +++ b/digsby/ext/src/jangle/base/time.sip @@ -0,0 +1,23 @@ + +%Include basictypes.sip + +%ModuleHeaderCode +#include "talk/base/time.h" +%End + +namespace talk_base { + +// Returns the current time in milliseconds. + uint32 Time(); + +// Approximate time when the program started. + uint32 StartTime(); + +// Elapsed milliseconds since StartTime() + uint32 ElapsedTime(); + +// Comparisons between time values, which can wrap around. + bool TimeIsBetween(uint32 later, uint32 middle, uint32 earlier); + int32 TimeDiff(uint32 later, uint32 earlier); + +}; // namespace talk_base \ No newline at end of file diff --git a/digsby/ext/src/jangle/cache.py b/digsby/ext/src/jangle/cache.py new file mode 100644 index 0000000..60ff424 --- /dev/null +++ b/digsby/ext/src/jangle/cache.py @@ -0,0 +1,55 @@ +from __future__ import with_statement +import shutil +import sys +import sipconfig + +sys.path.append('../../../lib') +from path import path + +def different(file1, file2, start = 0): + if not file1.isfile() or not file2.isfile(): + return True + + if file1.size != file2.size or file1.bytes() != file2.bytes(): + return True + + +def manage_cache(gendir, show_diffs = True): + """ + This function keeps a cache of all sip-generated *.cpp and *.h files + and restores the stats of the newly generated set whenever the content + is unchanged + """ + sipconfig.inform("Managing the module cache: %s" % gendir) + + gendir = path(gendir) + cache = gendir / 'cache' + if not cache.isdir(): + cache.makedirs() + + if 'clean' in sys.argv: + cache.rmtree() + + changed_count = 0 + for newfile in gendir.files('*.cpp') + gendir.files('*.h'): + oldfile = cache / newfile.name + if different(newfile, oldfile): + changed_count += 1 + + if oldfile.isfile(): + assert newfile.mtime > oldfile.mtime + + + shutil.copy2(newfile, oldfile) # src, dest + a, b = newfile.stat().st_mtime, oldfile.stat().st_mtime + #assert a == b, "copy2 failed: mtimes are different! (%s and %s)" % (a, b) + + sipconfig.inform("--> changed: %s" % newfile.name) + else: + #sipconfig.inform("--> same: %s" % newfile.name) + shutil.copystat(oldfile, newfile) + + sipconfig.inform('%d file%s changed.' % + (changed_count, 's' if changed_count != 1 else '')) + + sys.stdout.flush() diff --git a/digsby/ext/src/jangle/configure.py b/digsby/ext/src/jangle/configure.py new file mode 100644 index 0000000..ee60433 --- /dev/null +++ b/digsby/ext/src/jangle/configure.py @@ -0,0 +1,46 @@ + +import os +import sipconfig + +# The name of the SIP build file generated by SIP and used by the build +# system. +build_file = "jangle.sbf" + +import make_all_sip +make_all_sip.main() + +# Get the SIP configuration information. +config = sipconfig.Configuration() + +# Run SIP to generate the code. +os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, '-t', 'WIN32_PLATFORM', "jangle.sip"])) + +# Create the Makefile. +makefile = sipconfig.SIPModuleMakefile(config, build_file) + +# Add the library we are wrapping. The name doesn't include any platform +# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the +# ".dll" extension on Windows). +makefile.extra_libs = ['libjingle', 'libexpat', + 'ws2_32', #winsock 2 + 'advapi32', #? needed for talk/base/helpers.sip + 'shell32', + 'wininet', + 'secur32', + 'Crypt32', + ] +#comsupp.lib secur32.lib + +makefile.extra_cxxflags.append('//EHsc') +makefile.extra_include_dirs.extend([r'C:\dev\libjingle', + r'C:\Program Files (x86)\Expat 2.0.1\Source\lib']) +makefile.extra_lib_dirs.extend([r'C:\dev\libjingle\talk\Release', + r'C:\Program Files (x86)\Expat 2.0.1\Bin']) + +#makefile.generator = "MINGW" +# Generate the Makefile itself. +makefile.generate() + +import cache +cache.manage_cache('.', show_diffs=True) + diff --git a/digsby/ext/src/jangle/jangle.sip b/digsby/ext/src/jangle/jangle.sip new file mode 100644 index 0000000..8f4285f --- /dev/null +++ b/digsby/ext/src/jangle/jangle.sip @@ -0,0 +1,4 @@ + +%Module jangle + +%Include all.sip diff --git a/digsby/ext/src/jangle/libexpat.dll b/digsby/ext/src/jangle/libexpat.dll new file mode 100644 index 0000000..408a3da Binary files /dev/null and b/digsby/ext/src/jangle/libexpat.dll differ diff --git a/digsby/ext/src/jangle/make_all_sip.py b/digsby/ext/src/jangle/make_all_sip.py new file mode 100644 index 0000000..36df9fa --- /dev/null +++ b/digsby/ext/src/jangle/make_all_sip.py @@ -0,0 +1,20 @@ +#__LICENSE_GOES_HERE__ + +import sys +import os +sys.path.append('../../../lib') +from path import path + +def main(): + self = path(__file__) + + dot_sips = sorted(set(self.parent.glob('*/*.sip') + self.parent.glob('*/*/*.sip'))) + + with open(self.parent / 'all.sip', 'w') as f: + f.write('\n') + for sip_file in dot_sips: + f.write('%Include ' + os.path.relpath(sip_file, self.parent).replace('\\', '/') + '\n') + f.write('\n') + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/digsby/ext/src/jangle/p2p/base/port.sip b/digsby/ext/src/jangle/p2p/base/port.sip new file mode 100644 index 0000000..3490da7 --- /dev/null +++ b/digsby/ext/src/jangle/p2p/base/port.sip @@ -0,0 +1,14 @@ + +%ModuleHeaderCode +#include "talk/p2p/base/port.h" +%End +namespace cricket { + +enum ProtocolType { + PROTO_UDP, + PROTO_TCP, + PROTO_SSLTCP, + PROTO_LAST = PROTO_SSLTCP +}; + +}; diff --git a/digsby/ext/src/jangle/util/expat.sip b/digsby/ext/src/jangle/util/expat.sip new file mode 100644 index 0000000..3bee621 --- /dev/null +++ b/digsby/ext/src/jangle/util/expat.sip @@ -0,0 +1,62 @@ +enum XML_Error { + XML_ERROR_NONE, + XML_ERROR_NO_MEMORY, + XML_ERROR_SYNTAX, + XML_ERROR_NO_ELEMENTS, + XML_ERROR_INVALID_TOKEN, + XML_ERROR_UNCLOSED_TOKEN, + XML_ERROR_PARTIAL_CHAR, + XML_ERROR_TAG_MISMATCH, + XML_ERROR_DUPLICATE_ATTRIBUTE, + XML_ERROR_JUNK_AFTER_DOC_ELEMENT, + XML_ERROR_PARAM_ENTITY_REF, + XML_ERROR_UNDEFINED_ENTITY, + XML_ERROR_RECURSIVE_ENTITY_REF, + XML_ERROR_ASYNC_ENTITY, + XML_ERROR_BAD_CHAR_REF, + XML_ERROR_BINARY_ENTITY_REF, + XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF, + XML_ERROR_MISPLACED_XML_PI, + XML_ERROR_UNKNOWN_ENCODING, + XML_ERROR_INCORRECT_ENCODING, + XML_ERROR_UNCLOSED_CDATA_SECTION, + XML_ERROR_EXTERNAL_ENTITY_HANDLING, + XML_ERROR_NOT_STANDALONE, + XML_ERROR_UNEXPECTED_STATE, + XML_ERROR_ENTITY_DECLARED_IN_PE, + XML_ERROR_FEATURE_REQUIRES_XML_DTD, + XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING, + /* Added in 1.95.7. */ + XML_ERROR_UNBOUND_PREFIX, + /* Added in 1.95.8. */ + XML_ERROR_UNDECLARING_PREFIX, + XML_ERROR_INCOMPLETE_PE, + XML_ERROR_XML_DECL, + XML_ERROR_TEXT_DECL, + XML_ERROR_PUBLICID, + XML_ERROR_SUSPENDED, + XML_ERROR_NOT_SUSPENDED, + XML_ERROR_ABORTED, + XML_ERROR_FINISHED, + XML_ERROR_SUSPEND_PE, + /* Added in 2.0. */ + XML_ERROR_RESERVED_PREFIX_XML, + XML_ERROR_RESERVED_PREFIX_XMLNS, + XML_ERROR_RESERVED_NAMESPACE_URI +}; + +enum XML_Content_Type { + XML_CTYPE_EMPTY = 1, + XML_CTYPE_ANY, + XML_CTYPE_MIXED, + XML_CTYPE_NAME, + XML_CTYPE_CHOICE, + XML_CTYPE_SEQ +}; + +enum XML_Content_Quant { + XML_CQUANT_NONE, + XML_CQUANT_OPT, + XML_CQUANT_REP, + XML_CQUANT_PLUS +}; diff --git a/digsby/ext/src/jangle/util/string.sip b/digsby/ext/src/jangle/util/string.sip new file mode 100644 index 0000000..e6ee797 --- /dev/null +++ b/digsby/ext/src/jangle/util/string.sip @@ -0,0 +1,45 @@ +// +// automatic conversion between python unicode/str objects and std::wstring +// + +%MappedType std::string +{ +%TypeHeaderCode +#include +using std::string; +%End + +%ConvertFromTypeCode + PyObject * s = PyString_FromStringAndSize(sipCpp->c_str(), sipCpp->size()); +// if (!s) +// sipIsErr = 1; + PyObject * u = PyUnicode_FromEncodedObject(s, "utf-8", "strict"); + Py_DECREF(s); + return u; +%End + +// python unicode/str objects -> string +%ConvertToTypeCode + // just a type check + if (!sipIsErr) + return PyUnicode_Check(sipPy) || PyString_Check(sipPy); + + PyObject* str = sipPy; + bool needsDeref = false; + + if (PyUnicode_Check(sipPy)) { + str = PyUnicode_AsEncodedString(sipPy, "utf-8", "strict"); + if (!str) + return 0; + needsDeref = true; + } + + *sipCppPtr = new string(PyString_AsString(str), PyString_GET_SIZE(str)); + + if (needsDeref) + Py_DECREF(str); + + return sipGetState(sipTransferObj); +%End +}; + diff --git a/digsby/ext/src/jangle/util/types.sip b/digsby/ext/src/jangle/util/types.sip new file mode 100644 index 0000000..2bba1c8 --- /dev/null +++ b/digsby/ext/src/jangle/util/types.sip @@ -0,0 +1 @@ +typedef unsigned int size_t; \ No newline at end of file diff --git a/digsby/ext/src/jangle/util/vector.sip b/digsby/ext/src/jangle/util/vector.sip new file mode 100644 index 0000000..ee82808 --- /dev/null +++ b/digsby/ext/src/jangle/util/vector.sip @@ -0,0 +1,82 @@ +template +%MappedType std::vector +{ +%TypeHeaderCode +#include +%End + +%ConvertFromTypeCode + PyObject *l = PyList_New(sipCpp -> size()); + + // Create the Python list of the correct length. + if (!l) + return NULL; + + // Go through each element in the C++ instance and convert it to a + // wrapped P2d. + for (int i = 0; i < (int)sipCpp->size(); ++i) { + TYPE *cpp = new TYPE(sipCpp->at(i)); + PyObject *pobj = sipConvertFromType(cpp, sipType_TYPE, sipTransferObj); + + // Get the Python wrapper for the Type instance, creating a new + // one if necessary, and handle any ownership transfer. + if (!pobj) { + // There was an error so garbage collect the Python list. + Py_DECREF(l); + return NULL; + } + + // Add the wrapper to the list. + PyList_SET_ITEM(l, i, pobj); + } + + // Return the Python list. + return l; +%End + +%ConvertToTypeCode + // Check if type is compatible + if (!sipIsErr) { + // Must be any iterable + PyObject *i = PyObject_GetIter(sipPy); + bool iterable = (i != NULL); + Py_XDECREF(i); + return iterable; + } + + // Iterate over the object + PyObject *iterator = PyObject_GetIter(sipPy); + PyObject *item; + + std::vector *V = new std::vector(); + + while ((item = PyIter_Next(iterator))) + { + if (!sipCanConvertToType(item, sipType_TYPE, SIP_NOT_NONE)) { + PyErr_Format(PyExc_TypeError, "object in iterable cannot be converted to TYPE"); + *sipIsErr = 1; + break; + } + + int state; + TYPE* p = reinterpret_cast( + sipConvertToType(item, sipType_TYPE, 0, SIP_NOT_NONE, &state, sipIsErr)); + + if (!*sipIsErr) + V->push_back(*p); + + sipReleaseType(p, sipType_TYPE, state); + Py_DECREF(item); + } + + Py_DECREF(iterator); + + if (*sipIsErr) { + delete V; + return 0; + } + + *sipCppPtr = V; + return sipGetState(sipTransferObj); +%End +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmllite/qname.sip b/digsby/ext/src/jangle/xmllite/qname.sip new file mode 100644 index 0000000..8cb38c2 --- /dev/null +++ b/digsby/ext/src/jangle/xmllite/qname.sip @@ -0,0 +1,54 @@ + + +namespace buzz { + + +class QName +{ + +%TypeHeaderCode +#include "talk/xmllite/qname.h" +%End + +public: + explicit QName(); + QName(const buzz::QName & qname); + explicit QName(bool add, const std::string & ns, const char * local); + explicit QName(bool add, const std::string & ns, const std::string & local); + explicit QName(const std::string & ns, const char * local); + explicit QName(const std::string & mergedOrLocal); +// QName & operator=(const buzz::QName & qn); + ~QName(); + + const std::string & Namespace() const; + const std::string & LocalPart() const; + std::string Merged() const; + int Compare(const buzz::QName & other) const; + bool operator==(const buzz::QName & other) const; + bool operator!=(const buzz::QName & other) const; + bool operator<(const buzz::QName & other) const; + + class Data { + public: + Data(const std::string & ns, const std::string & local); + + Data(); + + std::string namespace_; + std::string localPart_; + void AddRef(); + void Release(); + bool Occupied(); + /* + private: + int refcount_; + */ + }; +/* +private: + Data * data_; +*/ +}; + + +}; diff --git a/digsby/ext/src/jangle/xmllite/xmlbuilder.sip b/digsby/ext/src/jangle/xmllite/xmlbuilder.sip new file mode 100644 index 0000000..c110dde --- /dev/null +++ b/digsby/ext/src/jangle/xmllite/xmlbuilder.sip @@ -0,0 +1,39 @@ +namespace buzz { + +%TypeHeaderCode +#include "talk/xmllite/xmlbuilder.h" +%End + +class XmlBuilder : /*public*/ buzz::XmlParseHandler { +public: + XmlBuilder(); + + static buzz::XmlElement * BuildElement(buzz::XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void StartElement(buzz::XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void EndElement(buzz::XmlParseContext * pctx, const char * name); + virtual void CharacterData(buzz::XmlParseContext * pctx, + const char * text, int len); + virtual void Error(buzz::XmlParseContext * pctx, XML_Error); + virtual ~XmlBuilder(); + + void Reset(); + + // Take ownership of the built element; second call returns NULL + buzz::XmlElement * CreateElement(); + + // Peek at the built element without taking ownership + buzz::XmlElement * BuiltElement(); + +private: + XmlBuilder(const buzz::XmlBuilder &); +/* + XmlElement * pelCurrent_; + scoped_ptr pelRoot_; + scoped_ptr > > + pvParents_; +*/ +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmllite/xmlconstants.sip b/digsby/ext/src/jangle/xmllite/xmlconstants.sip new file mode 100644 index 0000000..78aa9c1 --- /dev/null +++ b/digsby/ext/src/jangle/xmllite/xmlconstants.sip @@ -0,0 +1,14 @@ +namespace buzz { + +class XmlConstants { + public: + static const std::string & str_empty(); + static const std::string & ns_xml(); + static const std::string & ns_xmlns(); + static const std::string & str_xmlns(); + static const std::string & str_xml(); + static const std::string & str_version(); + static const std::string & str_encoding(); +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmllite/xmlelement.sip b/digsby/ext/src/jangle/xmllite/xmlelement.sip new file mode 100644 index 0000000..7cd74e9 --- /dev/null +++ b/digsby/ext/src/jangle/xmllite/xmlelement.sip @@ -0,0 +1,188 @@ + + +namespace buzz { + +class XmlChild { + +%TypeHeaderCode +#include "talk/xmllite/xmlelement.h" +%End + +//friend class XmlElement; + +public: + + buzz::XmlChild * NextChild(); // { return pNextChild_; } +// const buzz::XmlChild * NextChild() const { return pNextChild_; } + + bool IsText() const; + + buzz::XmlElement * AsElement(); // { return AsElementImpl(); } +// const XmlElement * AsElement() const { return AsElementImpl(); } + + buzz::XmlText * AsText(); // { return AsTextImpl(); } +// const XmlText * AsText() const { return AsTextImpl(); } + + +protected: + + XmlChild(); + + virtual bool IsTextImpl() const = 0; + virtual buzz::XmlElement * AsElementImpl() const = 0; + virtual buzz::XmlText * AsTextImpl() const = 0; + + + virtual ~XmlChild(); +private: + XmlChild(const buzz::XmlChild & noimpl); + +/* + XmlChild * pNextChild_; +*/ + +}; + +class XmlText : /*public*/ buzz::XmlChild { + +%TypeHeaderCode +#include "talk/xmllite/xmlelement.h" +%End + +public: + explicit XmlText(const std::string & text); + explicit XmlText(const buzz::XmlText & t); + explicit XmlText(const char * cstr, size_t len); + virtual ~XmlText(); + + const std::string & Text() const; + void SetText(const std::string & text); + void AddParsedText(const char * buf, int len); + void AddText(const std::string & text); + +protected: + virtual bool IsTextImpl() const; + virtual buzz::XmlElement * AsElementImpl() const; + virtual buzz::XmlText * AsTextImpl() const; +/* +private: + std::string text_; +*/ +}; + +class XmlAttr { + +%TypeHeaderCode +#include "talk/xmllite/xmlelement.h" +%End + +//friend class XmlElement; + +public: + buzz::XmlAttr * NextAttr() const; + const buzz::QName & Name() const; + const std::string & Value() const; + +private: + explicit XmlAttr(const buzz::QName & name, const std::string & value); + explicit XmlAttr(const buzz::XmlAttr & att); +/* + buzz::XmlAttr * pNextAttr_; + QName name_; + std::string value_; +*/ +}; + +class XmlElement : /*public*/ buzz::XmlChild { + +%TypeHeaderCode +#include +#include "talk/xmllite/xmlelement.h" +%End + +public: + explicit XmlElement(const buzz::QName & name); + explicit XmlElement(const buzz::QName & name, bool useDefaultNs); + explicit XmlElement(const buzz::XmlElement & elt); + + virtual ~XmlElement(); + + const buzz::QName& Name() const; + void SetName(const buzz::QName& name); + + const std::string & BodyText() const; + void SetBodyText(const std::string & text); + + const buzz::QName & FirstElementName() const; + + buzz::XmlAttr * FirstAttr(); +// const buzz::XmlAttr * FirstAttr() const; + + //! Attr will return STR_EMPTY if the attribute isn't there: + //! use HasAttr to test presence of an attribute. + const std::string & Attr(const buzz::QName & name) const; + bool HasAttr(const buzz::QName & name) const; + void SetAttr(const buzz::QName & name, const std::string & value); + void ClearAttr(const buzz::QName & name); + + buzz::XmlChild * FirstChild(); +// const buzz::XmlChild * FirstChild() const; + + buzz::XmlElement * FirstElement(); +// const buzz::XmlElement * FirstElement() const; + + buzz::XmlElement * NextElement(); +// const buzz::XmlElement * NextElement() const; + + buzz::XmlElement * FirstWithNamespace(const std::string & ns); +// const buzz::XmlElement * FirstWithNamespace(const std::string & ns) const; + + buzz::XmlElement * NextWithNamespace(const std::string & ns); +// const buzz::XmlElement * NextWithNamespace(const std::string & ns) const; + + buzz::XmlElement * FirstNamed(const buzz::QName & name); +// const buzz::XmlElement * FirstNamed(const buzz::QName & name) const; + + buzz::XmlElement * NextNamed(const buzz::QName & name); +// const buzz::XmlElement * NextNamed(const buzz::QName & name) const; + + // Finds the first element named 'name'. If that element can't be found then + // adds one and returns it. + buzz::XmlElement* FindOrAddNamedChild(const buzz::QName& name); + + const std::string & TextNamed(const buzz::QName & name) const; + + void InsertChildAfter(buzz::XmlChild * pPredecessor, buzz::XmlChild * pNewChild); + void RemoveChildAfter(buzz::XmlChild * pPredecessor); + + void AddParsedText(const char * buf, int len); + void AddText(const std::string & text); + void AddText(const std::string & text, int depth); + void AddElement(buzz::XmlElement * pelChild); + void AddElement(buzz::XmlElement * pelChild, int depth); + void AddAttr(const buzz::QName & name, const std::string & value); + void AddAttr(const buzz::QName & name, const std::string & value, int depth); + void ClearNamedChildren(const buzz::QName & name); + void ClearChildren(); + + static buzz::XmlElement * ForStr(const std::string & str); + std::string Str() const; + +// void Print(std::ostream * pout, std::string * xmlns, int xmlnsCount) const; + +protected: + virtual bool IsTextImpl() const; + virtual buzz::XmlElement * AsElementImpl() const; + virtual buzz::XmlText * AsTextImpl() const; +private: + XmlElement(); +/* + QName name_; + XmlAttr * pFirstAttr_; + XmlAttr * pLastAttr_; + XmlChild * pFirstChild_; + XmlChild * pLastChild_; +*/ +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmllite/xmlnsstack.sip b/digsby/ext/src/jangle/xmllite/xmlnsstack.sip new file mode 100644 index 0000000..30c0993 --- /dev/null +++ b/digsby/ext/src/jangle/xmllite/xmlnsstack.sip @@ -0,0 +1,34 @@ +namespace buzz { + +class XmlnsStack { +public: + XmlnsStack(); + ~XmlnsStack(); + + void AddXmlns(const std::string & prefix, const std::string & ns); + void RemoveXmlns(); + void PushFrame(); + void PopFrame(); + void Reset(); + + const std::string * NsForPrefix(const std::string & prefix); + bool PrefixMatchesNs(const std::string & prefix, const std::string & ns); + + ////////////////////////////////////////////////////////////// + /* + std::pair PrefixForNs(const std::string & ns, bool isAttr); + std::pair AddNewPrefix(const std::string & ns, bool isAttr); + */ + ///////////////////////////////////////////////////////////// + + std::string FormatQName(const buzz::QName & name, bool isAttr); + +private: + XmlnsStack(const buzz::XmlnsStack &); +/* + + scoped_ptr > > pxmlnsStack_; + scoped_ptr > > pxmlnsDepthStack_; +*/ +}; +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmllite/xmlparser.sip b/digsby/ext/src/jangle/xmllite/xmlparser.sip new file mode 100644 index 0000000..4e197d0 --- /dev/null +++ b/digsby/ext/src/jangle/xmllite/xmlparser.sip @@ -0,0 +1,82 @@ +namespace buzz { + +class XmlParseContext { + +%TypeHeaderCode +#include "talk/xmllite/xmlparser.h" +%End + +public: + virtual buzz::QName ResolveQName(const char * qname, bool isAttr) = 0; + virtual void RaiseError(XML_Error err) = 0; +}; + +class XmlParseHandler { + +%TypeHeaderCode +#include "talk/xmllite/xmlparser.h" +%End + +public: + virtual void StartElement(buzz::XmlParseContext * pctx, + const char * name, const char ** atts) = 0; + virtual void EndElement(buzz::XmlParseContext * pctx, + const char * name) = 0; + virtual void CharacterData(buzz::XmlParseContext * pctx, + const char * text, int len) = 0; + virtual void Error(buzz::XmlParseContext * pctx, + XML_Error errorCode) = 0; +}; + +class XmlParser { + +%TypeHeaderCode +#include "talk/xmllite/xmlparser.h" +%End + +public: + static void ParseXml(buzz::XmlParseHandler * pxph, std::string text); + + explicit XmlParser(buzz::XmlParseHandler * pxph); + bool Parse(const char * data, size_t len, bool isFinal); + void Reset(); + virtual ~XmlParser(); + + // expat callbacks + void ExpatStartElement(const char * name, const char ** atts); + void ExpatEndElement(const char * name); + void ExpatCharacterData(const char * text, int len); + void ExpatXmlDecl(const char * ver, const char * enc, int standalone); + +private: + XmlParser(); + XmlParser(const buzz::XmlParser &); + +/* + class ParseContext : public XmlParseContext { + public: + ParseContext(XmlParser * parser); + virtual ~ParseContext(); + virtual QName ResolveQName(const char * qname, bool isAttr); + virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; } + XML_Error RaisedError() { return raised_; } + void Reset(); + + void StartElement(); + void EndElement(); + void StartNamespace(const char * prefix, const char * ns); + + private: + const XmlParser * parser_; + XmlnsStack xmlnsstack_; + XML_Error raised_; + }; + + ParseContext context_; + XML_Parser expat_; + XmlParseHandler * pxph_; + bool sentError_; +*/ +}; + +}; diff --git a/digsby/ext/src/jangle/xmllite/xmlprinter.sip b/digsby/ext/src/jangle/xmllite/xmlprinter.sip new file mode 100644 index 0000000..0971ca8 --- /dev/null +++ b/digsby/ext/src/jangle/xmllite/xmlprinter.sip @@ -0,0 +1,20 @@ +namespace buzz { + +class XmlPrinter { + +%TypeHeaderCode +#include "talk/xmllite/xmlprinter.h" +%End + +public: +/* + static void PrintXml(std::ostream * pout, const buzz::XmlElement * pelt); + + static void PrintXml(std::ostream * pout, + const buzz::XmlElement * pelt, + const std::string * const xmlns, + int xmlnsCount); +*/ +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/asyncsocket.sip b/digsby/ext/src/jangle/xmpp/asyncsocket.sip new file mode 100644 index 0000000..ebc56e0 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/asyncsocket.sip @@ -0,0 +1,41 @@ + + +namespace buzz { + +class AsyncSocket { + +%TypeHeaderCode +#include "talk/xmpp/asyncsocket.h" +%End + +public: + enum State { + STATE_CLOSED = 0, //!< Socket is not open. + STATE_CLOSING, //!< Socket is closing but can have buffered data + STATE_CONNECTING, //!< In the process of + STATE_OPEN, //!< Socket is connected +// STATE_TLS_CONNECTING, //!< Establishing TLS connection +// STATE_TLS_OPEN, //!< TLS connected + }; + + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_WINSOCK, //!< Winsock error + ERROR_DNS, //!< Couldn't resolve host name + ERROR_WRONGSTATE, //!< Call made while socket is in the wrong state +// ERROR_SSL, //!< Something went wrong with OpenSSL + }; + + virtual ~AsyncSocket(); + virtual buzz::AsyncSocket::State state() = 0; + virtual buzz::AsyncSocket::Error error() = 0; + virtual int GetError() = 0; // winsock error code + + virtual bool Connect(const talk_base::SocketAddress& addr) = 0; + virtual bool Read(char * data, size_t len, size_t* len_read) = 0; + virtual bool Write(const char * data, size_t len) = 0; + virtual bool Close() = 0; + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/constants.sip b/digsby/ext/src/jangle/xmpp/constants.sip new file mode 100644 index 0000000..a223f84 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/constants.sip @@ -0,0 +1,310 @@ +%ModuleHeaderCode +#include "talk/xmpp/constants.h" +%End + +namespace buzz { + +const buzz::Jid JID_EMPTY; + +class Constants { + +%TypeHeaderCode +#include "talk/xmpp/constants.h" +%End + + public: + static const std::string & ns_client(); + static const std::string & ns_server(); + static const std::string & ns_stream(); + static const std::string & ns_xstream(); + static const std::string & ns_tls(); + static const std::string & ns_sasl(); + static const std::string & ns_bind(); + static const std::string & ns_dialback(); + static const std::string & ns_session(); + static const std::string & ns_stanza(); + static const std::string & ns_privacy(); + static const std::string & ns_roster(); + static const std::string & ns_vcard(); + static const std::string & ns_avatar_hash(); + static const std::string & ns_vcard_update(); + + static const std::string & str_client(); + static const std::string & str_server(); + static const std::string & str_stream(); +}; + +const std::string STR_GET; +const std::string STR_SET; +const std::string STR_RESULT; +const std::string STR_ERROR; + + +const std::string STR_FROM; +const std::string STR_TO; +const std::string STR_BOTH; +const std::string STR_REMOVE; + +//const std::string STR_MESSAGE; +//const std::string STR_BODY; +//const std::string STR_PRESENCE; +//const std::string STR_STATUS; +//const std::string STR_SHOW; +//const std::string STR_PRIOIRTY; +//const std::string STR_IQ; + +const std::string STR_TYPE; +const std::string STR_NAME; +const std::string STR_ID; +const std::string STR_JID; +const std::string STR_SUBSCRIPTION; +const std::string STR_ASK; +const std::string STR_X; +const std::string STR_GOOGLE_COM; +const std::string STR_GMAIL_COM; +const std::string STR_GOOGLEMAIL_COM; +const std::string STR_DEFAULT_DOMAIN; +const std::string STR_TALK_GOOGLE_COM; +const std::string STR_TALKX_L_GOOGLE_COM; + +//#ifdef FEATURE_ENABLE_VOICEMAIL +//const std::string STR_VOICEMAIL; +//const std::string STR_OUTGOINGVOICEMAIL; +//#endif + +const std::string STR_UNAVAILABLE; + +const buzz::QName QN_STREAM_STREAM; +const buzz::QName QN_STREAM_FEATURES; +const buzz::QName QN_STREAM_ERROR; + +const buzz::QName QN_XSTREAM_BAD_FORMAT; +const buzz::QName QN_XSTREAM_BAD_NAMESPACE_PREFIX; +const buzz::QName QN_XSTREAM_CONFLICT; +const buzz::QName QN_XSTREAM_CONNECTION_TIMEOUT; +const buzz::QName QN_XSTREAM_HOST_GONE; +const buzz::QName QN_XSTREAM_HOST_UNKNOWN; +const buzz::QName QN_XSTREAM_IMPROPER_ADDRESSIING; +const buzz::QName QN_XSTREAM_INTERNAL_SERVER_ERROR; +const buzz::QName QN_XSTREAM_INVALID_FROM; +const buzz::QName QN_XSTREAM_INVALID_ID; +const buzz::QName QN_XSTREAM_INVALID_NAMESPACE; +const buzz::QName QN_XSTREAM_INVALID_XML; +const buzz::QName QN_XSTREAM_NOT_AUTHORIZED; +const buzz::QName QN_XSTREAM_POLICY_VIOLATION; +const buzz::QName QN_XSTREAM_REMOTE_CONNECTION_FAILED; +const buzz::QName QN_XSTREAM_RESOURCE_CONSTRAINT; +const buzz::QName QN_XSTREAM_RESTRICTED_XML; +const buzz::QName QN_XSTREAM_SEE_OTHER_HOST; +const buzz::QName QN_XSTREAM_SYSTEM_SHUTDOWN; +const buzz::QName QN_XSTREAM_UNDEFINED_CONDITION; +const buzz::QName QN_XSTREAM_UNSUPPORTED_ENCODING; +const buzz::QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE; +const buzz::QName QN_XSTREAM_UNSUPPORTED_VERSION; +const buzz::QName QN_XSTREAM_XML_NOT_WELL_FORMED; +const buzz::QName QN_XSTREAM_TEXT; + +const buzz::QName QN_TLS_STARTTLS; +const buzz::QName QN_TLS_REQUIRED; +const buzz::QName QN_TLS_PROCEED; +const buzz::QName QN_TLS_FAILURE; + +const buzz::QName QN_SASL_MECHANISMS; +const buzz::QName QN_SASL_MECHANISM; +const buzz::QName QN_SASL_AUTH; +const buzz::QName QN_SASL_CHALLENGE; +const buzz::QName QN_SASL_RESPONSE; +const buzz::QName QN_SASL_ABORT; +const buzz::QName QN_SASL_SUCCESS; +const buzz::QName QN_SASL_FAILURE; +const buzz::QName QN_SASL_ABORTED; +const buzz::QName QN_SASL_INCORRECT_ENCODING; +const buzz::QName QN_SASL_INVALID_AUTHZID; +const buzz::QName QN_SASL_INVALID_MECHANISM; +const buzz::QName QN_SASL_MECHANISM_TOO_WEAK; +const buzz::QName QN_SASL_NOT_AUTHORIZED; +const buzz::QName QN_SASL_TEMPORARY_AUTH_FAILURE; + +const std::string NS_GOOGLE_AUTH; +const buzz::QName QN_MISSING_USERNAME; + +const buzz::QName QN_DIALBACK_RESULT; +const buzz::QName QN_DIALBACK_VERIFY; + +const buzz::QName QN_STANZA_BAD_REQUEST; +const buzz::QName QN_STANZA_CONFLICT; +const buzz::QName QN_STANZA_FEATURE_NOT_IMPLEMENTED; +const buzz::QName QN_STANZA_FORBIDDEN; +const buzz::QName QN_STANZA_GONE; +const buzz::QName QN_STANZA_INTERNAL_SERVER_ERROR; +const buzz::QName QN_STANZA_ITEM_NOT_FOUND; +const buzz::QName QN_STANZA_JID_MALFORMED; +const buzz::QName QN_STANZA_NOT_ACCEPTABLE; +const buzz::QName QN_STANZA_NOT_ALLOWED; +const buzz::QName QN_STANZA_PAYMENT_REQUIRED; +const buzz::QName QN_STANZA_RECIPIENT_UNAVAILABLE; +const buzz::QName QN_STANZA_REDIRECT; +const buzz::QName QN_STANZA_REGISTRATION_REQUIRED; +const buzz::QName QN_STANZA_REMOTE_SERVER_NOT_FOUND; +const buzz::QName QN_STANZA_REMOTE_SERVER_TIMEOUT; +const buzz::QName QN_STANZA_RESOURCE_CONSTRAINT; +const buzz::QName QN_STANZA_SERVICE_UNAVAILABLE; +const buzz::QName QN_STANZA_SUBSCRIPTION_REQUIRED; +const buzz::QName QN_STANZA_UNDEFINED_CONDITION; +const buzz::QName QN_STANZA_UNEXPECTED_REQUEST; +const buzz::QName QN_STANZA_TEXT; + +const buzz::QName QN_BIND_BIND; +const buzz::QName QN_BIND_RESOURCE; +const buzz::QName QN_BIND_JID; + +const buzz::QName QN_MESSAGE; +const buzz::QName QN_BODY; +const buzz::QName QN_SUBJECT; +const buzz::QName QN_THREAD; +const buzz::QName QN_PRESENCE; +const buzz::QName QN_SHOW; +const buzz::QName QN_STATUS; +const buzz::QName QN_LANG; +const buzz::QName QN_PRIORITY; +const buzz::QName QN_IQ; +const buzz::QName QN_ERROR; + +const buzz::QName QN_SERVER_MESSAGE; +const buzz::QName QN_SERVER_BODY; +const buzz::QName QN_SERVER_SUBJECT; +const buzz::QName QN_SERVER_THREAD; +const buzz::QName QN_SERVER_PRESENCE; +const buzz::QName QN_SERVER_SHOW; +const buzz::QName QN_SERVER_STATUS; +const buzz::QName QN_SERVER_LANG; +const buzz::QName QN_SERVER_PRIORITY; +const buzz::QName QN_SERVER_IQ; +const buzz::QName QN_SERVER_ERROR; + +const buzz::QName QN_SESSION_SESSION; + +const buzz::QName QN_PRIVACY_QUERY; +const buzz::QName QN_PRIVACY_ACTIVE; +const buzz::QName QN_PRIVACY_DEFAULT; +const buzz::QName QN_PRIVACY_LIST; +const buzz::QName QN_PRIVACY_ITEM; +const buzz::QName QN_PRIVACY_IQ; +const buzz::QName QN_PRIVACY_MESSAGE; +const buzz::QName QN_PRIVACY_PRESENCE_IN; +const buzz::QName QN_PRIVACY_PRESENCE_OUT; + +const buzz::QName QN_ROSTER_QUERY; +const buzz::QName QN_ROSTER_ITEM; +const buzz::QName QN_ROSTER_GROUP; + +const buzz::QName QN_VCARD; +const buzz::QName QN_VCARD_FN; +const buzz::QName QN_VCARD_PHOTO; +const buzz::QName QN_VCARD_PHOTO_BINVAL; +const buzz::QName QN_VCARD_AVATAR_HASH; +const buzz::QName QN_VCARD_AVATAR_HASH_MODIFIED; + + +const buzz::QName QN_XML_LANG; + +const buzz::QName QN_ENCODING; +const buzz::QName QN_VERSION; +const buzz::QName QN_TO; +const buzz::QName QN_FROM; +const buzz::QName QN_TYPE; +const buzz::QName QN_ID; +const buzz::QName QN_CODE; +const buzz::QName QN_NAME; +const buzz::QName QN_VALUE; +const buzz::QName QN_ACTION; +const buzz::QName QN_ORDER; +const buzz::QName QN_MECHANISM; +const buzz::QName QN_ASK; +const buzz::QName QN_JID; +const buzz::QName QN_SUBSCRIPTION; +const buzz::QName QN_TITLE1; +const buzz::QName QN_TITLE2; + + +const buzz::QName QN_XMLNS_CLIENT; +const buzz::QName QN_XMLNS_SERVER; +const buzz::QName QN_XMLNS_STREAM; + +// Presence +const std::string STR_SHOW_AWAY; +const std::string STR_SHOW_CHAT; +const std::string STR_SHOW_DND; +const std::string STR_SHOW_XA; +const std::string STR_SHOW_OFFLINE; + +// Subscription +const std::string STR_SUBSCRIBE; +const std::string STR_SUBSCRIBED; +const std::string STR_UNSUBSCRIBE; +const std::string STR_UNSUBSCRIBED; + + +// JEP 0030 +const buzz::QName QN_NODE; +const buzz::QName QN_CATEGORY; +const buzz::QName QN_VAR; +const std::string NS_DISCO_INFO; +const std::string NS_DISCO_ITEMS; + +const buzz::QName QN_DISCO_INFO_QUERY; +const buzz::QName QN_DISCO_IDENTITY; +const buzz::QName QN_DISCO_FEATURE; + +const buzz::QName QN_DISCO_ITEMS_QUERY; +const buzz::QName QN_DISCO_ITEM; + + +// JEP 0115 +const std::string NS_CAPS; +const buzz::QName QN_CAPS_C; +const buzz::QName QN_VER; +const buzz::QName QN_EXT; + + +// Avatar - JEP 0153 +const std::string kNSVCard; +const buzz::QName kQnVCardX; +const buzz::QName kQnVCardPhoto; + +// JEP 0172 User Nickname +const std::string kNSNickname; +const buzz::QName kQnNickname; + + +// JEP 0085 chat state +const std::string NS_CHATSTATE; +const buzz::QName QN_CS_ACTIVE; +const buzz::QName QN_CS_COMPOSING; +const buzz::QName QN_CS_PAUSED; +const buzz::QName QN_CS_INACTIVE; +const buzz::QName QN_CS_GONE; + +// JEP 0091 Delayed Delivery +const std::string kNSDelay; +const buzz::QName kQnDelayX; +const buzz::QName kQnStamp; + +// Google time stamping (higher resolution) +const std::string kNSTimestamp; +const buzz::QName kQnTime; +const buzz::QName kQnMilliseconds; + + +const std::string NS_JINGLE_INFO; +const buzz::QName QN_JINGLE_INFO_QUERY; +const buzz::QName QN_JINGLE_INFO_STUN; +const buzz::QName QN_JINGLE_INFO_RELAY; +const buzz::QName QN_JINGLE_INFO_SERVER; +const buzz::QName QN_JINGLE_INFO_TOKEN; +const buzz::QName QN_JINGLE_INFO_HOST; +const buzz::QName QN_JINGLE_INFO_TCP; +const buzz::QName QN_JINGLE_INFO_UDP; +const buzz::QName QN_JINGLE_INFO_TCPSSL; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/jid.sip b/digsby/ext/src/jangle/xmpp/jid.sip new file mode 100644 index 0000000..18d4db0 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/jid.sip @@ -0,0 +1,48 @@ + +namespace buzz { + +class Jid { + +%TypeHeaderCode +#include "talk/xmpp/jid.h" +%End + +public: + explicit Jid(); + explicit Jid(const std::string & jid_string); + explicit Jid(const std::string & node_name, + const std::string & domain_name, + const std::string & resource_name); + explicit Jid(bool special, const std::string & special_string); + Jid(const buzz::Jid & jid); + +// Jid & operator=(const buzz::Jid & jid); + + const std::string & node(); + const std::string & domain(); + const std::string & resource(); + + std::string Str(); + + bool IsValid() const; + bool IsBare() const; + bool IsFull() const; + + bool BareEquals(const buzz::Jid & other) const; + + bool operator==(const buzz::Jid & other) const; + bool operator!=(const buzz::Jid & other) const; + + bool operator<(const buzz::Jid & other) const; + bool operator>(const buzz::Jid & other) const; + + int Compare(const buzz::Jid & other) const; + + // A quick and dirty hash. Don't count on this producing a great + // distribution. + uint32 ComputeLameHash() const; + +}; + +}; + diff --git a/digsby/ext/src/jangle/xmpp/plainsaslhandler.sip b/digsby/ext/src/jangle/xmpp/plainsaslhandler.sip new file mode 100644 index 0000000..82c31d0 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/plainsaslhandler.sip @@ -0,0 +1,33 @@ +namespace buzz { + +class PlainSaslHandler : /*public*/ buzz::SaslHandler { + +%TypeHeaderCode +#include "talk/xmpp/saslplainmechanism.h" +#include "talk/xmpp/plainsaslhandler.h" +%End + +public: + PlainSaslHandler(const buzz::Jid & jid, const talk_base::CryptString & password, + bool allow_plain); + + virtual ~PlainSaslHandler(); + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted); + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). If not handled, return NULL. + virtual buzz::SaslMechanism * CreateSaslMechanism(const std::string & mechanism); + +/* +private: + Jid jid_; + talk_base::CryptString password_; + bool allow_plain_; +*/ +}; + + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/prexmppauth.sip b/digsby/ext/src/jangle/xmpp/prexmppauth.sip new file mode 100644 index 0000000..eb59ffa --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/prexmppauth.sip @@ -0,0 +1,53 @@ +namespace buzz { + +class CaptchaChallenge { + +%TypeHeaderCode +#include "talk/xmpp/prexmppauth.h" +%End + + public: + CaptchaChallenge(); + CaptchaChallenge(const std::string& token, const std::string& url); + + bool captcha_needed() const; + const std::string& captcha_token() const; + + // This url is relative to the gaia server. Once we have better tools + // for cracking URLs, we should probably make this a full URL + const std::string& captcha_image_url() const; + +/* + private: + bool captcha_needed_; + std::string captcha_token_; + std::string captcha_image_url_; +*/ +}; + +class PreXmppAuth : /*public*/ buzz::SaslHandler { + +%TypeHeaderCode +#include "talk/xmpp/prexmppauth.h" +%End + +public: + virtual ~PreXmppAuth(); + + virtual void StartPreXmppAuth( + const buzz::Jid & jid, + const talk_base::SocketAddress & server, + const talk_base::CryptString & pass, + const std::string & auth_cookie) = 0; + +// sigslot::signal0<> SignalAuthDone; + + virtual bool IsAuthDone() = 0; + virtual bool IsAuthorized() = 0; + virtual bool HadError() = 0; + virtual int GetError() = 0; + virtual buzz::CaptchaChallenge GetCaptchaChallenge() = 0; + virtual std::string GetAuthCookie() = 0; +}; + +}; diff --git a/digsby/ext/src/jangle/xmpp/ratelimitmanager.sip b/digsby/ext/src/jangle/xmpp/ratelimitmanager.sip new file mode 100644 index 0000000..65e92fe --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/ratelimitmanager.sip @@ -0,0 +1,99 @@ +namespace buzz { + +///////////////////////////////////////////////////////////////////// +// +// RATELIMITMANAGER +// +///////////////////////////////////////////////////////////////////// +// +// RateLimitManager imposes client-side rate limiting for xmpp tasks and +// other events. It ensures that no more than i events with a given name +// can occur within k seconds. +// +// A buffer tracks the previous max_count events. Before an event is allowed +// to occur, it can check its rate limit with a call to VerifyRateLimit. +// VerifyRateLimit will look up the i-th to last event and if more than +// k seconds have passed since then, it will return true and update the +// appropriate rate limits. Else, it will return false. +// +///////////////////////////////////////////////////////////////////// + +class RateLimitManager { + +%TypeHeaderCode +#include "talk/xmpp/ratelimitmanager.h" +%End + + public: + + RateLimitManager(); + ~RateLimitManager(); + + // Checks if the event is under the defined rate limit and updates the + // rate limit if so. Returns true if it's under the rate limit. + bool VerifyRateLimit(const std::string event_name, int max_count, + int per_x_seconds); + + // Checks if the event is under the defined rate limit and updates the + // rate limit if so *or* if always_update = true. + bool VerifyRateLimit(const std::string event_name, int max_count, + int per_x_seconds, bool always_update); + + private: + +/* + class RateLimit { + public: + RateLimit(int max, int per_x_secs) : counter_(0), max_count_(max), + per_x_seconds_(per_x_secs) { + event_times_ = new uint32[max_count_]; + for (int i = 0; i < max_count_; i++) { + event_times_[i] = 0; + } + } + + ~RateLimit() { + if (event_times_) { + delete[] event_times_; + } + } + + // True iff the current time >= to the next song allowed time + bool IsWithinRateLimit(); + + // Updates time and counter for rate limit + void UpdateRateLimit(); + + private: + + // The time at which the i-th (where i = max_count) event occured + uint32 PreviousTimeAtCounter(); + + // The time that the next event is allowed to occur + uint32 NextTimeAllowedForCounter(); + + int counter_; // count modulo max_count of the current event + int max_count_; // max number of events that can occur within per_x_seconds + int per_x_seconds_; // interval size for rate limit + uint32* event_times_; // buffer of previous max_count event +*/ + }; +/* + typedef std::map RateLimitMap; + + // Maps from event name to its rate limit + RateLimitMap rate_limits_; + // Returns rate limit for event with specified name + buzz::RateLimit* GetRateLimit(const std::string event_name); + + // True iff the current time >= to the next song allowed time + bool IsWithinRateLimit(const std::string event_name); + + // Updates time and counter for rate limit + void UpdateRateLimit(const std::string event_name, int max_count, + int per_x_seconds); +*/ + +//}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/saslcookiemechanism.sip b/digsby/ext/src/jangle/xmpp/saslcookiemechanism.sip new file mode 100644 index 0000000..2ef9a7f --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/saslcookiemechanism.sip @@ -0,0 +1,24 @@ +namespace buzz { + +class SaslCookieMechanism : /*public*/ buzz::SaslMechanism { + +%TypeHeaderCode +#include "talk/xmpp/saslcookiemechanism.h" +%End + +public: + SaslCookieMechanism(const std::string & mechanism, const std::string & username, const std::string & cookie); + + virtual std::string GetMechanismName(); + + virtual buzz::XmlElement * StartSaslAuth(); + +/* +private: + std::string mechanism_; + std::string username_; + std::string cookie_; +*/ +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/saslhandler.sip b/digsby/ext/src/jangle/xmpp/saslhandler.sip new file mode 100644 index 0000000..54cb1cd --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/saslhandler.sip @@ -0,0 +1,29 @@ +%ModuleHeaderCode +#include +%End + +namespace buzz { + +class SaslHandler { + +%TypeHeaderCode +#include "talk/xmpp/saslhandler.h" +%End + +public: + + // Intended to be subclassed + virtual ~SaslHandler(); + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) = 0; + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). + // If not handled, return NULL. + virtual buzz::SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0; + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/saslmechanism.sip b/digsby/ext/src/jangle/xmpp/saslmechanism.sip new file mode 100644 index 0000000..981552d --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/saslmechanism.sip @@ -0,0 +1,44 @@ + + + +namespace buzz { + +// Defines a mechnanism to do SASL authentication. +// Subclass instances should have a self-contained way to present +// credentials. +class SaslMechanism { + +%TypeHeaderCode +#include "talk/xmpp/saslmechanism.h" +%End + +public: + + // Intended to be subclassed + virtual ~SaslMechanism(); + + // Should return the name of the SASL mechanism, e.g., "PLAIN" + virtual std::string GetMechanismName() = 0; + + // Should generate the initial "auth" request. Default is just . + virtual buzz::XmlElement * StartSaslAuth(); + + // Should respond to a SASL "" request. Default is + // to abort (for mechanisms that do not do challenge-response) + virtual buzz::XmlElement * HandleSaslChallenge(const buzz::XmlElement * challenge); + + // Notification of a SASL "". Sometimes information + // is passed on success. + virtual void HandleSaslSuccess(const buzz::XmlElement * success); + + // Notification of a SASL "". Sometimes information + // for the user is passed on failure. + virtual void HandleSaslFailure(const buzz::XmlElement * failure); + +protected: + static std::string Base64Encode(const std::string & plain); + static std::string Base64Decode(const std::string & encoded); + static std::string Base64EncodeFromArray(const char * plain, size_t length); +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/saslplainmechanism.sip b/digsby/ext/src/jangle/xmpp/saslplainmechanism.sip new file mode 100644 index 0000000..39b5c61 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/saslplainmechanism.sip @@ -0,0 +1,22 @@ +namespace buzz { + +class SaslPlainMechanism : /*public*/ buzz::SaslMechanism { + +%TypeHeaderCode +#include "talk/xmpp/saslplainmechanism.h" +%End + +public: + SaslPlainMechanism(const buzz::Jid user_jid, const talk_base::CryptString & password); + + virtual std::string GetMechanismName(); + + virtual buzz::XmlElement * StartSaslAuth(); +/* +private: + Jid user_jid_; + talk_base::CryptString password_; +*/ +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/xmppclient.sip b/digsby/ext/src/jangle/xmpp/xmppclient.sip new file mode 100644 index 0000000..2dcde88 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/xmppclient.sip @@ -0,0 +1,70 @@ + + + +namespace buzz { + +class XmppClient : talk_base::Task +{ + +%TypeHeaderCode +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppengine.h" +%End + +public: + XmppClient(talk_base::Task * parent); + ~XmppClient(); + + buzz::XmppReturnStatus Connect(const buzz::XmppClientSettings & settings, + const std::string & lang, + buzz::AsyncSocket * socket, + buzz::PreXmppAuth * preauth); + + virtual talk_base::Task* GetParent(int code); + virtual int ProcessStart(); + virtual int ProcessResponse(); + buzz::XmppReturnStatus Disconnect(); + + const buzz::Jid & jid(); +%MethodCode + if (!sipCpp->engine()) { + PyErr_SetString(PyExc_AssertionError, "No engine"); + sipIsErr = 1; + } else { + // stuff + sipRes = new buzz::Jid(sipCpp->jid()); + } +%End + +// sigslot::signal1 SignalStateChange; + + buzz::XmppEngine::State GetState(); + buzz::XmppEngine::Error GetError(int *subcode); + + // When there is a stanza, return the stanza + // so that they can be handled. + const buzz::XmlElement *GetStreamError(); + + // When there is an authentication error, we may have captcha info + // that the user can use to unlock their account + buzz::CaptchaChallenge GetCaptchaChallenge(); + + // When authentication is successful, this returns the service cookie + // (if we used GAIA authentication) + std::string GetAuthCookie(); + + std::string NextId(); + buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza); + buzz::XmppReturnStatus SendRaw(const std::string & text); + buzz::XmppReturnStatus SendStanzaError(const buzz::XmlElement * pelOriginal, + buzz::XmppStanzaError code, + const std::string & text); + + buzz::XmppEngine* engine(); + +// sigslot::signal2 SignalLogInput; +// sigslot::signal2 SignalLogOutput; + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/xmppclientsettings.sip b/digsby/ext/src/jangle/xmpp/xmppclientsettings.sip new file mode 100644 index 0000000..0f4150b --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/xmppclientsettings.sip @@ -0,0 +1,48 @@ + + +namespace buzz { + +class XmppClientSettings { + +%TypeHeaderCode +#include "talk/xmpp/xmppclientsettings.h" +%End + +public: + XmppClientSettings(); + + void set_user(const std::string & user); + void set_host(const std::string & host); + void set_pass(const talk_base::CryptString & pass); + void set_auth_cookie(const std::string & cookie); + void set_resource(const std::string & resource); + void set_use_tls(bool use_tls); + void set_server(const talk_base::SocketAddress & server); + void set_protocol(cricket::ProtocolType protocol); + void set_proxy(talk_base::ProxyType f); + void set_proxy_host(const std::string & host); + void set_proxy_port(int port); + void set_use_proxy_auth(bool f); + void set_proxy_user(const std::string & user); + void set_proxy_pass(const talk_base::CryptString & pass); + void set_allow_plain(bool f); + + const std::string & user() const; + const std::string & host() const; + const talk_base::CryptString & pass() const; + const std::string & auth_cookie() const; + const std::string & resource() const; + bool use_tls() const; + const talk_base::SocketAddress & server() const; + cricket::ProtocolType protocol() const; + talk_base::ProxyType proxy() const; + const std::string & proxy_host() const; + int proxy_port() const; + bool use_proxy_auth() const; + const std::string & proxy_user() const; + const talk_base::CryptString & proxy_pass() const; + bool allow_plain() const; + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/xmppengine.sip b/digsby/ext/src/jangle/xmpp/xmppengine.sip new file mode 100644 index 0000000..2e5b1e1 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/xmppengine.sip @@ -0,0 +1,276 @@ + +%ModuleHeaderCode +#include "talk/xmpp/xmppengine.h" +%End + + +namespace buzz { +typedef void * XmppIqCookie; + +//! XMPP stanza error codes. +//! Used in XmppEngine.SendStanzaError(). +enum XmppStanzaError { + XSE_BAD_REQUEST, + XSE_CONFLICT, + XSE_FEATURE_NOT_IMPLEMENTED, + XSE_FORBIDDEN, + XSE_GONE, + XSE_INTERNAL_SERVER_ERROR, + XSE_ITEM_NOT_FOUND, + XSE_JID_MALFORMED, + XSE_NOT_ACCEPTABLE, + XSE_NOT_ALLOWED, + XSE_PAYMENT_REQUIRED, + XSE_RECIPIENT_UNAVAILABLE, + XSE_REDIRECT, + XSE_REGISTRATION_REQUIRED, + XSE_SERVER_NOT_FOUND, + XSE_SERVER_TIMEOUT, + XSE_RESOURCE_CONSTRAINT, + XSE_SERVICE_UNAVAILABLE, + XSE_SUBSCRIPTION_REQUIRED, + XSE_UNDEFINED_CONDITION, + XSE_UNEXPECTED_REQUEST, +}; + +// XmppReturnStatus +// This is used by API functions to synchronously return status. +enum XmppReturnStatus { + XMPP_RETURN_OK, + XMPP_RETURN_BADARGUMENT, + XMPP_RETURN_BADSTATE, + XMPP_RETURN_PENDING, + XMPP_RETURN_UNEXPECTED, + XMPP_RETURN_NOTYETIMPLEMENTED, +}; + +class XmppOutputHandler { +public: + + //! Deliver the specified bytes to the XMPP socket. + virtual void WriteOutput(const char * bytes, size_t len) = 0; + + //! Initiate TLS encryption on the socket. + //! The implementation must verify that the SSL + //! certificate matches the given domainname. + virtual void StartTls(const std::string & domainname) = 0; + + //! Called when engine wants the connecton closed. + virtual void CloseConnection() = 0; +}; + +//! Callback to deliver engine state change notifications +//! to the object managing the engine. +class XmppSessionHandler { +public: + //! Called when engine changes state. Argument is new state. + virtual void OnStateChange(int state) = 0; +}; + +//! Callback to deliver stanzas to an Xmpp application module. +//! Register via XmppEngine.SetDefaultSessionHandler or via +//! XmppEngine.AddSessionHAndler. +class XmppStanzaHandler { +public: + + //! Process the given stanza. + //! The handler must return true if it has handled the stanza. + //! A false return value causes the stanza to be passed on to + //! the next registered handler. + virtual bool HandleStanza(const buzz::XmlElement * stanza) = 0; +}; + +//! Callback to deliver iq responses (results and errors). +//! Register while sending an iq via XmppEngine.SendIq. +//! Iq responses are routed to matching XmppIqHandlers in preference +//! to sending to any registered SessionHandlers. +class XmppIqHandler { +public: + //! Called to handle the iq response. + //! The response may be either a result or an error, and will have + //! an 'id' that matches the request and a 'from' that matches the + //! 'to' of the request. Called no more than once; once this is + //! called, the handler is automatically unregistered. + virtual void IqResponse(buzz::XmppIqCookie cookie, const buzz::XmlElement * pelStanza) = 0; +}; + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngine { + +%TypeHeaderCode +#include "talk/xmpp/xmppengine.h" +%End + +public: + static buzz::XmppEngine * Create(); + virtual ~XmppEngine(); + + //! Error codes. See GetError(). + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_XML, //!< Malformed XML or encoding error + ERROR_STREAM, //!< XMPP stream error - see GetStreamError() + ERROR_VERSION, //!< XMPP version error + ERROR_UNAUTHORIZED, //!< User is not authorized (rejected credentials) + ERROR_TLS, //!< TLS could not be negotiated + ERROR_AUTH, //!< Authentication could not be negotiated + ERROR_BIND, //!< Resource or session binding could not be negotiated + ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler. + ERROR_DOCUMENT_CLOSED, //!< Closed by + ERROR_SOCKET, //!< Socket error + ERROR_NETWORK_TIMEOUT, //!< Some sort of timeout (eg., we never got the roster) + ERROR_MISSING_USERNAME //!< User has a Google Account but no nickname + }; + + //! States. See GetState(). + enum State { + STATE_NONE = 0, //!< Nonexistent state + STATE_START, //!< Initial state. + STATE_OPENING, //!< Exchanging stream headers, authenticating and so on. + STATE_OPEN, //!< Authenticated and bound. + STATE_CLOSED, //!< Session closed, possibly due to error. + }; + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual buzz::XmppReturnStatus SetOutputHandler(buzz::XmppOutputHandler *pxoh) = 0; + + //! Provides socket input to the engine + virtual buzz::XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0; + + //! Advises the engine that the socket has closed + virtual buzz::XmppReturnStatus ConnectionClosed(int subcode) = 0; + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual buzz::XmppReturnStatus SetUser(const buzz::Jid & jid)= 0; + + //! Get the login (bare) JID. + virtual const buzz::Jid & GetUser() = 0; + + //! Provides different methods for credentials for login. + //! Takes ownership of this object; deletes when login is done + virtual buzz::XmppReturnStatus SetSaslHandler(buzz::SaslHandler * h) = 0; + + //! Sets whether TLS will be used within the connection (default true). + virtual buzz::XmppReturnStatus SetUseTls(bool useTls) = 0; + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual buzz::XmppReturnStatus SetTlsServer(const std::string & proxy_hostname, + const std::string & proxy_domain) = 0; + + //! Gets whether TLS will be used within the connection. + virtual bool GetUseTls() = 0; + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual buzz::XmppReturnStatus SetRequestedResource(const std::string& resource) = 0; + + //! Gets the request resource name. + virtual const std::string & GetRequestedResource() = 0; + + //! Sets language + virtual void SetLanguage(const std::string & lang) = 0; + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual buzz::XmppReturnStatus SetSessionHandler(buzz::XmppSessionHandler* handler) = 0; + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual buzz::XmppReturnStatus Connect() = 0; + + //! The current engine state. + virtual State GetState() = 0; + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted() = 0; + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError(int *subcode) = 0; + + //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const buzz::XmlElement * GetStreamError() = 0; + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual buzz::XmppReturnStatus Disconnect() = 0; + + // APPLICATION USE ------------------------------------------------------- + + enum HandlerLevel { + HL_NONE = 0, + HL_PEEK, //!< Sees messages before all other processing; cannot abort + HL_SINGLE, //!< Watches for a single message, e.g., by id and sender + HL_SENDER, //!< Watches for a type of message from a specific sender + HL_TYPE, //!< Watches a type of message, e.g., all groupchat msgs + HL_ALL, //!< Watches all messages - gets last shot + HL_COUNT, //!< Count of handler levels + }; + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual buzz::XmppReturnStatus AddStanzaHandler(buzz::XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0; + + //! Removes a listener for session events. + virtual buzz::XmppReturnStatus RemoveStanzaHandler(buzz::XmppStanzaHandler* handler) = 0; + + //! Sends a stanza to the server. + virtual buzz::XmppReturnStatus SendStanza(const buzz::XmlElement * pelStanza) = 0; + + //! Sends raw text to the server + virtual buzz::XmppReturnStatus SendRaw(const std::string & text) = 0; + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual buzz::XmppReturnStatus SendIq(const buzz::XmlElement* pelStanza, + buzz::XmppIqHandler* iq_handler, + buzz::XmppIqCookie* cookie) = 0; + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual buzz::XmppReturnStatus RemoveIqHandler(buzz::XmppIqCookie cookie, + buzz::XmppIqHandler** iq_handler) = 0; + + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual buzz::XmppReturnStatus SendStanzaError(const buzz::XmlElement * pelOriginal, + buzz::XmppStanzaError code, + const std::string & text) = 0; + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const buzz::Jid & FullJid() = 0; + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId() = 0; + +}; + +}; \ No newline at end of file diff --git a/digsby/ext/src/jangle/xmpp/xmppengineimpl.sip b/digsby/ext/src/jangle/xmpp/xmppengineimpl.sip new file mode 100644 index 0000000..1280877 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/xmppengineimpl.sip @@ -0,0 +1,231 @@ +namespace buzz { + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngineImpl : /*public*/ buzz::XmppEngine { + +%TypeHeaderCode +#include "talk/xmpp/xmppengineimpl.h" +%End + +public: + XmppEngineImpl(); + virtual ~XmppEngineImpl(); + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual buzz::XmppReturnStatus SetOutputHandler(buzz::XmppOutputHandler *pxoh); + + //! Provides socket input to the engine + virtual buzz::XmppReturnStatus HandleInput(const char * bytes, size_t len); + + //! Advises the engine that the socket has closed + virtual buzz::XmppReturnStatus ConnectionClosed(int subcode); + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual buzz::XmppReturnStatus SetUser(const buzz::Jid & jid); + + //! Get the login (bare) JID. + virtual const buzz::Jid & GetUser(); + + //! Indicates the autentication to use. Takes ownership of the object. + virtual buzz::XmppReturnStatus SetSaslHandler(buzz::SaslHandler * sasl_handler); + + //! Sets whether TLS will be used within the connection (default true). + virtual buzz::XmppReturnStatus SetUseTls(bool useTls); + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual buzz::XmppReturnStatus SetTlsServer(const std::string & proxy_hostname, + const std::string & proxy_domain); + + //! Gets whether TLS will be used within the connection. + virtual bool GetUseTls(); + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual buzz::XmppReturnStatus SetRequestedResource(const std::string& resource); + + //! Gets the request resource name. + virtual const std::string & GetRequestedResource(); + + //! Sets language + virtual void SetLanguage(const std::string & lang); + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual buzz::XmppReturnStatus SetSessionHandler(buzz::XmppSessionHandler* handler); + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual buzz::XmppReturnStatus Connect(); + + //! The current engine state. + virtual State GetState(); + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted(); + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError(int *subcode); + + //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const buzz::XmlElement * GetStreamError(); + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual buzz::XmppReturnStatus Disconnect(); + + // APPLICATION USE ------------------------------------------------------- + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual buzz::XmppReturnStatus AddStanzaHandler(buzz::XmppStanzaHandler* handler, + buzz::XmppEngine::HandlerLevel level); + + //! Removes a listener for session events. + virtual buzz::XmppReturnStatus RemoveStanzaHandler(buzz::XmppStanzaHandler* handler); + + //! Sends a stanza to the server. + virtual buzz::XmppReturnStatus SendStanza(const buzz::XmlElement * pelStanza); + + //! Sends raw text to the server + virtual buzz::XmppReturnStatus SendRaw(const std::string & text); + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual buzz::XmppReturnStatus SendIq(const buzz::XmlElement* pelStanza, + buzz::XmppIqHandler* iq_handler, + buzz::XmppIqCookie* cookie); + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual buzz::XmppReturnStatus RemoveIqHandler(buzz::XmppIqCookie cookie, + buzz::XmppIqHandler** iq_handler); + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual buzz::XmppReturnStatus SendStanzaError(const buzz::XmlElement * pelOriginal, + buzz::XmppStanzaError code, + const std::string & text); + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const buzz::Jid & FullJid(); + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId(); + +private: + XmppEngineImpl(const buzz::XmppEngineImpl &); +/* + friend class XmppLoginTask; + friend class XmppIqEntry; +*/ + void IncomingStanza(const buzz::XmlElement *pelStanza); + void IncomingStart(const buzz::XmlElement *pelStanza); + void IncomingEnd(bool isError); + + void InternalSendStart(const std::string & domainName); + void InternalSendStanza(const buzz::XmlElement * pelStanza); + std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted); + buzz::SaslMechanism * GetSaslMechanism(const std::string & name); + void SignalBound(const buzz::Jid & fullJid); + void SignalStreamError(const buzz::XmlElement * pelStreamError); + void SignalError(Error errorCode, int subCode); + bool HasError(); + void DeleteIqCookies(); + bool HandleIqResponse(const buzz::XmlElement * element); + void StartTls(const std::string & domain); + void RaiseReset(); + /* + class StanzaParseHandler : public XmppStanzaParseHandler { + public: + StanzaParseHandler(XmppEngineImpl * outer); + virtual void StartStream(const XmlElement * pelStream); + virtual void Stanza(const XmlElement * pelStanza); + virtual void EndStream(); + virtual void XmlError(); + private: + XmppEngineImpl * const outer_; + }; + */ +/* + class EnterExit { + public: + EnterExit(XmppEngineImpl* engine); + ~EnterExit(); + private: + XmppEngineImpl* engine_; + State state_; + Error error_; + + }; + friend class StanzaParseHandler; + friend class EnterExit; +*/ + +/* + StanzaParseHandler stanzaParseHandler_; + XmppStanzaParser stanzaParser_; + + + // state + int engine_entered_; + Jid user_jid_; + std::string password_; + std::string requested_resource_; + bool tls_needed_; + std::string tls_server_hostname_; + std::string tls_server_domain_; + scoped_ptr login_task_; + std::string lang_; + + int next_id_; + Jid bound_jid_; + State state_; + bool encrypted_; + Error error_code_; + int subcode_; + scoped_ptr stream_error_; + bool raised_reset_; + XmppOutputHandler* output_handler_; + XmppSessionHandler* session_handler_; + + typedef STD_VECTOR(XmppStanzaHandler*) StanzaHandlerVector; + scoped_ptr stanza_handlers_[HL_COUNT]; + + typedef STD_VECTOR(XmppIqEntry*) IqEntryVector; + scoped_ptr iq_entries_; + + scoped_ptr sasl_handler_; + + scoped_ptr output_; +*/ +}; + +}; diff --git a/digsby/ext/src/jangle/xmpp/xmpplogintask.sip b/digsby/ext/src/jangle/xmpp/xmpplogintask.sip new file mode 100644 index 0000000..dca3aac --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/xmpplogintask.sip @@ -0,0 +1,65 @@ +namespace buzz { + +class XmppLoginTask { + +%TypeHeaderCode +#include "talk/xmpp/xmpplogintask.h" +%End + +public: + XmppLoginTask(buzz::XmppEngineImpl *pctx); + ~XmppLoginTask(); + + bool IsDone(); + void IncomingStanza(const buzz::XmlElement * element, bool isStart); + void OutgoingStanza(const buzz::XmlElement *element); + +private: + XmppLoginTask(); + XmppLoginTask(const buzz::XmppLoginTask &); +/* + enum LoginTaskState { + LOGINSTATE_INIT = 0, + LOGINSTATE_STREAMSTART_SENT, + LOGINSTATE_STARTED_XMPP, + LOGINSTATE_TLS_INIT, + LOGINSTATE_AUTH_INIT, + LOGINSTATE_BIND_INIT, + LOGINSTATE_TLS_REQUESTED, + LOGINSTATE_SASL_RUNNING, + LOGINSTATE_BIND_REQUESTED, + LOGINSTATE_SESSION_REQUESTED, + LOGINSTATE_DONE, + }; +*/ + const buzz::XmlElement * NextStanza(); + bool Advance(); + bool HandleStartStream(const buzz::XmlElement * element); + bool HandleFeatures(const buzz::XmlElement * element); + const buzz::XmlElement * GetFeature(const buzz::QName & name); + bool Failure(buzz::XmppEngine::Error reason); + void FlushQueuedStanzas(); + +/* + XmppEngineImpl * pctx_; + bool authNeeded_; + LoginTaskState state_; + const XmlElement * pelStanza_; + bool isStart_; + std::string iqId_; + scoped_ptr pelFeatures_; + Jid fullJid_; + std::string streamId_; + scoped_ptr > > pvecQueuedStanzas_; + + scoped_ptr sasl_mech_; + +#ifdef _DEBUG + static const talk_base::ConstantLabel LOGINTASK_STATES[]; +#endif // _DEBUG + +*/ +}; + +}; diff --git a/digsby/ext/src/jangle/xmpp/xmppstanzaparser.sip b/digsby/ext/src/jangle/xmpp/xmppstanzaparser.sip new file mode 100644 index 0000000..b4c7889 --- /dev/null +++ b/digsby/ext/src/jangle/xmpp/xmppstanzaparser.sip @@ -0,0 +1,70 @@ +namespace buzz { + + +class XmppStanzaParseHandler { + +%TypeHeaderCode +#include "talk/xmpp/xmppstanzaparser.h" +%End + +public: + virtual void StartStream(const buzz::XmlElement * pelStream) = 0; + virtual void Stanza(const buzz::XmlElement * pelStanza) = 0; + virtual void EndStream() = 0; + virtual void XmlError() = 0; +}; + +class XmppStanzaParser { + +%TypeHeaderCode +#include "talk/xmpp/xmppstanzaparser.h" +%End + +public: + XmppStanzaParser(buzz::XmppStanzaParseHandler *psph); + bool Parse(const char * data, size_t len, bool isFinal); + void Reset(); + +private: + XmppStanzaParser(); + XmppStanzaParser(const buzz::XmppStanzaParser &); +/* + class ParseHandler : public XmlParseHandler { + public: + ParseHandler(XmppStanzaParser * outer) : outer_(outer) {} + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) + { outer_->IncomingStartElement(pctx, name, atts); } + virtual void EndElement(XmlParseContext * pctx, + const char * name) + { outer_->IncomingEndElement(pctx, name); } + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) + { outer_->IncomingCharacterData(pctx, text, len); } + virtual void Error(XmlParseContext * pctx, + XML_Error errCode) + { outer_->IncomingError(pctx, errCode); } + private: + XmppStanzaParser * const outer_; + }; + + friend class ParseHandler; +*/ + void IncomingStartElement(buzz::XmlParseContext * pctx, + const char * name, const char ** atts); + void IncomingEndElement(buzz::XmlParseContext * pctx, + const char * name); + void IncomingCharacterData(buzz::XmlParseContext * pctx, + const char * text, int len); + void IncomingError(buzz::XmlParseContext * pctx, + XML_Error errCode); +/* + XmppStanzaParseHandler * psph_; + ParseHandler innerHandler_; + XmlParser parser_; + int depth_; + XmlBuilder builder_; +*/ + }; + +}; diff --git a/digsby/ext/src/oscarutil.cpp b/digsby/ext/src/oscarutil.cpp new file mode 100644 index 0000000..fd6b79b --- /dev/null +++ b/digsby/ext/src/oscarutil.cpp @@ -0,0 +1,66 @@ +#define CHECKSUM_EMPTY 0xffff0000L + + +class OFTChecksum { + + +}; + + /** The checksum value. */ + private long checksum; + + { // init + reset(); + } + + /** + * Creates a new file transfer checksum computer object. + */ + public FileTransferChecksum() { } + + public void update(int value) { + update(new byte[] { (byte) value }, 0, 1); + } + + public void update(final byte[] input, final int offset, final int len) { + DefensiveTools.checkNull(input, "input"); + + assert checksum >= 0; + + long check = (checksum >> 16) & 0xffffL; + + for (int i = 0; i < len; i++) { + final long oldcheck = check; + + final int byteVal = input[offset + i] & 0xff; + + final int val; + if ((i & 1) != 0) val = byteVal; + else val = byteVal << 8; + + check -= val; + + if (check > oldcheck) check--; + } + + check = ((check & 0x0000ffff) + (check >> 16)); + check = ((check & 0x0000ffff) + (check >> 16)); + + checksum = check << 16 & 0xffffffffL; + assert checksum >= 0; + } + + public long getValue() { + assert checksum >= 0; + return checksum; + } + + public void reset() { + checksum = CHECKSUM_EMPTY; + assert checksum >= 0; + } + + public String toString() { + return "FileTransferChecksum: " + checksum; + } +} \ No newline at end of file diff --git a/digsby/ext/src/pyutils.cpp b/digsby/ext/src/pyutils.cpp new file mode 100644 index 0000000..ed13c84 --- /dev/null +++ b/digsby/ext/src/pyutils.cpp @@ -0,0 +1,23 @@ +#include "pyutils.h" + +#if !WXPY +#include "wx/wxPython/wxPython.h" + +wxString getstring(PyObject* obj, const char* attr, wxString def) +{ + wxString result(def); + + PyGILState_STATE state = PyGILState_Ensure(); + + PyObject* valobj = PyObject_GetAttrString(obj, attr); + if ( valobj ) + { + result = Py2wxString(valobj); + Py_DECREF(valobj); + } + + PyGILState_Release(state); + + return result; +} +#endif \ No newline at end of file diff --git a/digsby/ext/src/pyutils.h b/digsby/ext/src/pyutils.h new file mode 100644 index 0000000..470797f --- /dev/null +++ b/digsby/ext/src/pyutils.h @@ -0,0 +1,33 @@ +#ifndef ___PYUTILS_H___ +#define ___PYUTILS_H___ + +#include + +#include "wx/wxprec.h" +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#if !WXPY +wxString getstring(PyObject* obj, const char* attr, wxString def = wxT("")); +#endif +// +// macro to block/unblock threads +// +#if WXPY + +#include +#ifndef SIP_BLOCK_THREADS +#error "WXPY is 1, but SIP_BLOCK_THREADS is not defined" +#endif + +#define PY_BLOCK SIP_BLOCK_THREADS +#define PY_UNBLOCK SIP_UNBLOCK_THREADS + +#else // SWIG + +#define PY_BLOCK PyGILState_STATE __python_threadstate = PyGILState_Ensure(); +#define PY_UNBLOCK PyGILState_Release(__python_threadstate); +#endif + +#endif diff --git a/digsby/ext/src/scrollwindow.sip b/digsby/ext/src/scrollwindow.sip new file mode 100644 index 0000000..72b628a --- /dev/null +++ b/digsby/ext/src/scrollwindow.sip @@ -0,0 +1,30 @@ +%ModuleHeaderCode +#include "scrollwindow.h" +%End +class SkinScrollWindow : wxWindow +{ +public: + SkinScrollWindow(wxWindow* parent /TransferThis/, wxWindowID = -1, int style = wxBORDER_NONE); + virtual ~SkinScrollWindow(); + + void AdjustScrollbars(int x = -1, int y = -1); + void PrepareDC(wxDC& dc); + + void Scroll(int x, int y); + void EnablePhysicalScrolling(bool enable); + bool IsPhysicalScrollingEnabled() const; + + void SetVirtualSize(const wxSize& size); + void SetVirtualSize(int width, int height); + + int GetViewStart() const; + wxRect GetViewRect() const; + + bool CanSmoothScroll() const; + bool SetSmoothScrolling(bool useSmooth); + +%If (WXMSW) + void ScrollWindow(int dx, int dy, const wxRect* prect = NULL); +%End + +}; \ No newline at end of file diff --git a/digsby/ext/src/sip/BuddyList.sip b/digsby/ext/src/sip/BuddyList.sip new file mode 100644 index 0000000..6d64806 --- /dev/null +++ b/digsby/ext/src/sip/BuddyList.sip @@ -0,0 +1,12 @@ + +class BuddyList : SkinVList +{ +%TypeHeaderCode +#include "BuddyList.h" +%End + +public: + BuddyList(wxWindow* parent); + virtual ~BuddyList(); +}; + diff --git a/digsby/ext/src/sip/Fullscreen.sip b/digsby/ext/src/sip/Fullscreen.sip new file mode 100644 index 0000000..8ef3a75 --- /dev/null +++ b/digsby/ext/src/sip/Fullscreen.sip @@ -0,0 +1,6 @@ +%ModuleHeaderCode +#include "Fullscreen.h" +%End + +long FullscreenAppHWND(); +bool FullscreenApp(); diff --git a/digsby/ext/src/sip/IconUtils.sip b/digsby/ext/src/sip/IconUtils.sip new file mode 100644 index 0000000..893ff2e --- /dev/null +++ b/digsby/ext/src/sip/IconUtils.sip @@ -0,0 +1,6 @@ +%ModuleHeaderCode +#include "IconUtils.h" +%End + +bool tempImageFile(const wxString& path, const wxString& prefix, const wxBitmap& bitmap, wxString& filename /Out/, wxBitmapType = wxBITMAP_TYPE_ICO); + diff --git a/digsby/ext/src/sip/InputBox.sip b/digsby/ext/src/sip/InputBox.sip new file mode 100644 index 0000000..8828aab --- /dev/null +++ b/digsby/ext/src/sip/InputBox.sip @@ -0,0 +1,46 @@ +class InputBox : wxTextCtrl +{ + +%TypeHeaderCode +#include "InputBox.h" +%End + +public: + void ShowDefaultColors(); + bool SetDefaultColors(const wxColour& fg, const wxColour& bg); + + //InputBox() /Transfer/; + InputBox(wxWindow* parent, wxWindowID id = wxID_ANY, + const wxString& value = wxEmptyString, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxTextCtrlNameStr) /Transfer/; + + virtual ~InputBox(); + + virtual bool CanPaste() const; + + virtual wxString GetValue(); + + bool SetStyle(long start, long end, const wxTextAttr& style); + bool GetStyle(long position, wxTextAttr &style /Out/); + + bool SetRTL(bool rtl); + bool GetRTL(); + + int GetReqHeight() const; + int GetNatHeight() const; + + wxString GetRTF(); + void SetRTF(const wxString& rtf); + + bool AutoSetRTL(); + +private: + InputBox(const InputBox&); +}; + +//bool TextCtrlAutoComplete(wxTextCtrl* ctrl, const wxArrayString& choices); + diff --git a/digsby/ext/src/sip/LoginWindow.sip b/digsby/ext/src/sip/LoginWindow.sip new file mode 100644 index 0000000..1589c6a --- /dev/null +++ b/digsby/ext/src/sip/LoginWindow.sip @@ -0,0 +1,80 @@ +%ModuleHeaderCode +#include "LoginWindow.h" +%End + +class DragMixin : wxEvtHandler +{ +%TypeHeaderCode +#include "DragMixin.h" +%End +public: + DragMixin(wxWindow* win); + virtual ~DragMixin(); +private: + DragMixin(const DragMixin&); +}; + +class LoginWindowBitmaps +{ +public: + LoginWindowBitmaps(); + ~LoginWindowBitmaps(); + + wxBitmap logo; + wxBitmap help; + wxBitmap settings; + wxBitmap language; +}; + +class LoginWindow : wxDialog +{ +%TypeHeaderCode +#include "LoginWindow.h" +%End +public: + LoginWindow(wxWindow* window, const wxPoint& pos, LoginWindowBitmaps& bitmaps, const wxString& revision, bool showLanguages); + virtual ~LoginWindow(); + +%ConvertToSubClassCode +wxClassInfo* c = sipCpp->GetClassInfo(); + +if(0) {} +#define C(clz) else if (c == CLASSINFO(clz)) { sipClass = sipClass_ ## clz; } + C(LoginWindow) +else + sipClass = NULL; +#undef C +%End + + enum { + SAVEPASSWORD, + AUTOLOGIN, + FORGOTPASSWORD, + NOACCOUNT, + CONNSETTINGS, + CLOSE, + USERNAME, + PASSWORD, + HELPBUTTON, + LANGUAGE, + }; + + wxChoice* GetLanguageChoice() const; + + void EnableControls(bool enable, const wxString& label, int buttonEnable = -1); + void UpdateUIStrings(); + void SetStatus(const wxString& status, const wxString& windowTitle = wxEmptyString); + + void SetUsername(const wxString&); + wxString GetUsername() const; + void SetPassword(const wxString&); + wxString GetPassword() const; + bool GetSaveInfo() const; + void SetSaveInfo(bool); + bool GetAutoLogin() const; + void SetAutoLogin(bool); + + void setShowRevision(bool show, bool repaint = true); + bool showRevision() const; +}; + diff --git a/digsby/ext/src/sip/MiscUI.sip b/digsby/ext/src/sip/MiscUI.sip new file mode 100644 index 0000000..92560ac --- /dev/null +++ b/digsby/ext/src/sip/MiscUI.sip @@ -0,0 +1,6 @@ +%ModuleHeaderCode +#include "MiscUI.h" +%End + +unsigned int GetUserIdleTime(); + diff --git a/digsby/ext/src/sip/PlatformMessages.sip b/digsby/ext/src/sip/PlatformMessages.sip new file mode 100644 index 0000000..c4dcef6 --- /dev/null +++ b/digsby/ext/src/sip/PlatformMessages.sip @@ -0,0 +1,19 @@ +typedef int UINT; + +class PlatformMessageBinder /MainThreadDestructor/ +{ +%TypeHeaderCode + #include "PlatformMessages.h" +%End + +private: + PlatformMessageBinder(PlatformMessageBinder&); + +public: + static PlatformMessageBinder* ForWindow(wxWindow* win); + PlatformMessageBinder(wxWindow* win); + virtual ~PlatformMessageBinder(); + + void Bind(UINT message, SIP_PYOBJECT callback); + bool Unbind(UINT message); +}; diff --git a/digsby/ext/src/sip/SelectionEvent.sip b/digsby/ext/src/sip/SelectionEvent.sip new file mode 100644 index 0000000..6a03f5e --- /dev/null +++ b/digsby/ext/src/sip/SelectionEvent.sip @@ -0,0 +1,31 @@ +%ModuleHeaderCode +#include "SelectionEvent.h" +%End + +enum +{ + wxEVT_SELECTION_CHANGED +}; + +class wxSelectionEvent : wxCommandEvent +{ + +%ConvertToSubClassCode +wxClassInfo* c = sipCpp->GetClassInfo(); + +if(0) ; +#define C(clz) else if (c == CLASSINFO(clz)) sipClass = sipClass_ ## clz; + C(wxSelectionEvent) +else + sipClass = NULL; +#undef C +%End + +public: + wxSelectionEvent(int commandEventType = 0, int id = 0, long selectionStart = 0, long selectionEnd = -1); + wxSelectionEvent(const wxSelectionEvent &event); + + long selectionStart; + long selectionEnd; +}; + diff --git a/digsby/ext/src/sip/Statistics.sip b/digsby/ext/src/sip/Statistics.sip new file mode 100644 index 0000000..18b96ce --- /dev/null +++ b/digsby/ext/src/sip/Statistics.sip @@ -0,0 +1,83 @@ + +class Statistics +{ +%TypeHeaderCode +#include "Statistics.h" +%End + +public: + Statistics(unsigned int intervalMs = 1000 * 10); + ~Statistics(); + bool start(); + bool stop(); + + void event(SIP_PYOBJECT eventName); +%MethodCode + char* eventName = PyString_AsString(a0); + if (!eventName) + sipIsErr = 1; + else + sipCpp->event(eventName); +%End + + SIP_PYOBJECT samples(); +%MethodCode + const SampleBuffer& b = sipCpp->samples(); + + sipRes = PyList_New(b.size()); + if (!sipRes) + sipIsErr = 1; + else { + size_t j = 0; + for (SampleBuffer::const_iterator i = b.begin(); i != b.end(); ++i) { + PyObject* tuple = Py_BuildValue("(Kn)", + i->time, + i->pagefileUsage); + + PyList_SetItem(sipRes, j++, tuple); + } + } +%End + + SIP_PYOBJECT sampleNames(); +%MethodCode + sipRes = Py_BuildValue("(ss)", "time", "pagefileUsage"); +%End + + SIP_PYOBJECT events(); +%MethodCode + const EventBuffer& b = sipCpp->events(); + + sipRes = PyList_New(b.size()); + + if (!sipRes) + sipIsErr = 1; + else { + size_t j = 0; + for (EventBuffer::const_iterator i = b.begin(); i != b.end(); ++i) { + if (PyList_SetItem(sipRes, j++, Py_BuildValue("(KB)", i->time, i->eventId)) == -1) { + sipIsErr = 1; + break; + } + } + } +%End + + SIP_PYOBJECT eventNames(); +%MethodCode + const EventNameMap& map = sipCpp->eventNames(); + sipRes = PyDict_New(); + if (!sipRes) + sipIsErr = 1; + else { + for (EventNameMap::const_iterator i = map.begin(); i != map.end(); ++i) { + PyObject* key = PyString_FromStringAndSize(i->first.c_str(), i->first.size()); + PyObject* val = PyInt_FromLong(i->second); + PyDict_SetItem(sipRes, key, val); + Py_DECREF(key); + Py_DECREF(val); + } + } +%End + +}; diff --git a/digsby/ext/src/sip/TransparentFrame.sip b/digsby/ext/src/sip/TransparentFrame.sip new file mode 100644 index 0000000..576902a --- /dev/null +++ b/digsby/ext/src/sip/TransparentFrame.sip @@ -0,0 +1,16 @@ +class TransparentFrame : wxFrame +{ +%TypeHeaderCode +#include "TransparentFrame.h" +%End + +public: + TransparentFrame(wxWindow* parent); + virtual ~TransparentFrame(); + + void SetAlpha(int alpha); + int GetAlpha() const; + virtual wxBitmap GetBitmap(); + void OnPaint(wxPaintEvent&); +}; + diff --git a/digsby/ext/src/sip/WindowSnapper.sip b/digsby/ext/src/sip/WindowSnapper.sip new file mode 100644 index 0000000..574f797 --- /dev/null +++ b/digsby/ext/src/sip/WindowSnapper.sip @@ -0,0 +1,26 @@ +class WindowSnapper /MainThreadDestructor/ +{ +%TypeHeaderCode +#include "WindowSnapper.h" +%End + +private: + WindowSnapper(const WindowSnapper&); + +public: + WindowSnapper(wxWindow* win, int snapMargin = 12, bool enable = true) /Transfer/; + virtual ~WindowSnapper(); + + bool SetEnabled(bool enable); + bool IsEnabled() const; + + int GetSnapMargin() const; + void SetSnapMargin(int snapMargin); + + bool GetSnapToScreen() const; + void SetSnapToScreen(bool snapToScreen); + + void SetDocked(bool docked); + + // vector WindowSnapper::GetSnapRects() const; +}; diff --git a/digsby/ext/src/sip/rtf.sip b/digsby/ext/src/sip/rtf.sip new file mode 100644 index 0000000..05260c6 --- /dev/null +++ b/digsby/ext/src/sip/rtf.sip @@ -0,0 +1,56 @@ + + +class RTFToX +{ +%TypeHeaderCode +#include "RTFToX.h" +#include "HTMLEncoder.h" +#include "MSIMEncoder.h" +#include "MSNEncoder.h" +#include "XHTMLEncoder.h" +#include "YahooEncoder.h" +%End + +public: + + static SIP_PYTUPLE EncoderTypes(); +%MethodCode + sipRes = Py_BuildValue("(sssss)", + "html", + "msim", + "msn", + "xhtml", + "yahoo"); +%End + + SIP_PYOBJECT Convert(const wxString& rtf, const wxString& encoder, const wxString& format, const wxTextAttr* style = 0); +%MethodCode + + Encoder* encoder = 0; + +#ifdef ENC +#error enc already defined!! +#endif + + if (0); +#define ENC(type, encoderType) else if (*a1 == type) encoder = new encoderType() + ENC(L"html", HTMLEncoder); + ENC(L"msim", MSIMEncoder); + ENC(L"msn", MSNEncoder); + ENC(L"xhtml", XHTMLEncoder); + ENC(L"yahoo", YahooEncoder); +#undef ENC + + wxString res; + if (encoder) { + res = sipCpp->Convert(*a0, *encoder, *a2, a3); + sipRes = PyUnicode_FromWideChar((const wchar_t*)res.c_str(), res.Len()); + delete encoder; + } else { + sipIsErr = 1; + PyErr_SetString(PyExc_ValueError, "Invalid encoder type"); + } + +%End + +}; diff --git a/digsby/ext/src/sip/win/RichEditUtils.sip b/digsby/ext/src/sip/win/RichEditUtils.sip new file mode 100644 index 0000000..86c0ec0 --- /dev/null +++ b/digsby/ext/src/sip/win/RichEditUtils.sip @@ -0,0 +1,7 @@ +%ModuleHeaderCode +#include "RichEditUtils.h" +%End + +int GetRichEditParagraphAlignment(wxTextCtrl* textCtrl); +bool SetRichEditParagraphAlignment(wxTextCtrl* textCtrl, int alignment); + diff --git a/digsby/ext/src/sip/win/WinJumpList.sip b/digsby/ext/src/sip/win/WinJumpList.sip new file mode 100644 index 0000000..fdcbc9f --- /dev/null +++ b/digsby/ext/src/sip/win/WinJumpList.sip @@ -0,0 +1,109 @@ +%ModuleHeaderCode +#include "WinJumpList.h" +%End + +%ModuleHeaderCode +bool pyGetTask(JumpListTask& task, PyObject* elem); +%End + +%ModuleCode +bool pyGetTask(PyObject* elem, JumpListTask& task) +{ + if (elem == Py_None) + return true; // an empty task is a separator; from Python None means separator + + PyObject* seq = PySequence_Fast(elem, "elements of jump list must be sequences"); + if (!seq) + return false; + + wchar_t* args; + wchar_t* description; + wchar_t* title; + wxBitmap* bitmap = 0; + + int state = 0; + if (sipParseResult(NULL, NULL, seq, "(xxxD0)", &args, &description, &title, sipType_wxBitmap, &state, &bitmap)) { + Py_DECREF(seq); + return false; + } + + task.bitmap = bitmap ? *bitmap : wxNullBitmap; + + if (bitmap) + sipReleaseType(bitmap, sipType_wxBitmap, state); + + task.args = args; + task.description = description; + task.title = title; + + Py_DECREF(seq); + return true; +} + +static bool getTasksFromSeq(vector& tasks, PyObject* a1) +{ + PyObject* seq = PySequence_Fast(a1, "arguments to SetUpJumpList must be sequences"); + if (!seq) + return false; + + bool success = true; + + const Py_ssize_t n = PySequence_Fast_GET_SIZE(seq); + PyObject** elems = PySequence_Fast_ITEMS(seq); + for (Py_ssize_t i = 0; i < n; ++i) { + PyObject* elem = *elems++; + + JumpListTask task; + if (!pyGetTask(elem, task)) { + success = false; + break; + } + tasks.push_back(task); + } + + Py_DECREF(seq); + return success; +} + +static bool getTaskGroup(JumpListTaskGroup& group, PyObject* taskGroup) +{ + Py_UNICODE* category; + PyObject* tasksSeq; + if (!PyArg_ParseTuple(taskGroup, "uO", &category, &tasksSeq)) + return false; + + if (!getTasksFromSeq(group.tasks, tasksSeq)) + return false; + + group.category = category; + return true; +} + +%End + +bool SetUpJumpList(const wchar_t* appID, SIP_PYOBJECT); +%MethodCode + PyObject* seq = PySequence_Fast(a1, "arguments to SetUpJumpList must be sequences"); + if (!seq) + sipIsErr = 1; + else { + const Py_ssize_t n = PySequence_Fast_GET_SIZE(seq); + PyObject** elems = PySequence_Fast_ITEMS(seq); + + TaskGroupVector groups; + for (Py_ssize_t i = 0; i < n; ++i) { + JumpListTaskGroup group; + if (!getTaskGroup(group, *elems++)) { + sipIsErr = 1; + break; + } + groups.push_back(group); + } + + Py_DECREF(seq); + + if (!sipIsErr) + sipRes = SetUpJumpList(a0, groups); + } +%End + diff --git a/digsby/ext/src/sip/win/WinTaskbar.sip b/digsby/ext/src/sip/win/WinTaskbar.sip new file mode 100644 index 0000000..35b62ab --- /dev/null +++ b/digsby/ext/src/sip/win/WinTaskbar.sip @@ -0,0 +1,118 @@ +/* +%ModuleHeaderCode +#include "WinTaskbar.h" +%End + +*/ + +class TabController /Supertype=sip.wrapper/ +{ +public: + TabController(); + virtual ~TabController(); + + virtual void* GetIconicHBITMAP(TaskbarWindow* window, int width, int height); + virtual wxBitmap GetIconicBitmap(TaskbarWindow* window, int width, int height); + virtual wxBitmap GetLivePreview(TabWindow* window, const wxRect& clientSize); + virtual wxIcon GetSmallIcon(TabWindow* window); + + virtual void OnTabActivated(TaskbarWindow* window); + virtual void OnTabClosed(TaskbarWindow* window); +}; + +class SimpleTabController : TabController +{ +public: + SimpleTabController(); + virtual ~SimpleTabController(); + + wxBitmap GetIconicBitmap(TabWindow* window, int width, int height); + + void SetIconicBitmap(const wxBitmap& bitmap); +}; + +typedef unsigned long HWND; + +class TabNotebook /Supertype=sip.wrapper/ +{ +%TypeHeaderCode +#include "WinTaskbar.h" +%End + +public: + TabNotebook(wxWindow* window); + TabNotebook(HWND); + virtual ~TabNotebook(); + + void CreateTab(wxWindow* window, TabController* controller /Transfer/); + bool DestroyTab(wxWindow* window); + bool RearrangeTab(wxWindow* tabWindow, wxWindow* before); + unsigned long GetTabHWND(wxWindow* window /NotNone/) const; +%MethodCode + sipRes = (unsigned long)sipCpp->GetTabHWND(a0); +%End + + bool SetTabTitle(wxWindow* window, const wxString& title); + bool SetTabActive(wxWindow* window); + bool SetTabIcon(wxWindow* window, const wxIconBundle& bundle); + bool SetTabIcon(wxWindow* window, const wxBitmap& bundle); + bool SetOverlayIcon(const wxBitmap& bitmap, const wxString& description = wxEmptyString); + bool UnregisterTab(wxWindow*); + bool InvalidateThumbnails(wxWindow* window); + + bool SetProgressValue(int completed, int total); +%MethodCode + sipRes = sipCpp->SetProgressValue((unsigned long long)a0, (unsigned long long)a1); +%End + + bool SetProgressValue(unsigned long long completed, unsigned long long total); + bool SetProgressState(int flags); + +private: + TabNotebook(const TabNotebook&); +}; + +class TaskbarWindow /Supertype=sip.wrapper, NoDefaultCtors/ +{ +%TypeHeaderCode +#include "WinTaskbar.h" +%End +public: + wxWindow* GetWindow() const; +}; + +class TabWindow : TaskbarWindow /NoDefaultCtors/ +{ +public: +%TypeHeaderCode +#include "WinTaskbar.h" +%End + + wxWindow* GetWindow() const; +}; + +class HiddenTabControlWindow /Supertype=sip.wrapper/ +{ +%TypeHeaderCode +#include "WinTaskbar.h" +%End + +public: + HiddenTabControlWindow(const wxString& title, const wxIconBundle& bundle, TabController* controller /Transfer/ = 0) /Transfer/; + void Show(bool show=true); + void Hide(); + void Destroy(); + void SetIconFile(const wxString&); + + size_t hwnd(); +%MethodCode + sipRes = (size_t)sipCpp->hwnd(); +%End +private: + HiddenTabControlWindow(); + ~HiddenTabControlWindow(); + HiddenTabControlWindow(const HiddenTabControlWindow&); +}; + +void* getBuddyPreview(const wxSize& size, const wxBitmap& icon, const wxBitmap& highlight); + diff --git a/digsby/ext/src/sip/win/WinUtils.sip b/digsby/ext/src/sip/win/WinUtils.sip new file mode 100644 index 0000000..0c55288 --- /dev/null +++ b/digsby/ext/src/sip/win/WinUtils.sip @@ -0,0 +1,8 @@ +%ModuleHeaderCode +#include "WinUtils.h" +%End + +bool glassExtendInto(wxWindow* win, int left = -1, int right =-1, int top = -1, int bottom = -1); +bool isGlassEnabled(); +void setThreadName(unsigned long dwThreadID, const wxString& threadName); +void cls(); diff --git a/digsby/ext/src/skin/SkinBitmap.cpp b/digsby/ext/src/skin/SkinBitmap.cpp new file mode 100644 index 0000000..cd165a7 --- /dev/null +++ b/digsby/ext/src/skin/SkinBitmap.cpp @@ -0,0 +1,73 @@ +#include "SkinBitmap.h" + +SkinBitmap::SkinBitmap(const wxImage& img) + : cache_limit(5) + , bitmap(0) + , image(img) +{ +} + +SkinBitmap::~SkinBitmap() +{ + for (size_t i = 0; i < bitmap_cache.size(); ++i) + delete bitmap_cache[i].second; + + bitmap_cache.clear(); +} + +void SkinBitmap::Draw(wxDC& dc, const wxRect& rect, int /*n*/) +{ + wxSize draw_size(rect.GetSize()); + + /* if the size is our original size, just draw it */ + if (draw_size.x == image.GetWidth() && draw_size.y == image.GetHeight()) { + Draw(dc, rect.GetTopLeft()); + return; + } + + fprintf(stderr, "resizing image to (%d, %d)\n", draw_size.x, draw_size.y); + + for (size_t i = 0; i < bitmap_cache.size(); ++i) { + BitmapCacheEntry e = bitmap_cache[i]; + if (e.first == draw_size) { + dc.DrawBitmap(*e.second, rect.x, rect.y, true); + return; + } + } + + wxBitmap resized_bitmap = *CacheResizedBitmap(draw_size); + dc.DrawBitmap(resized_bitmap, rect.x, rect.y, true); +} + +void SkinBitmap::Draw(wxDC& dc, const wxPoint& point) +{ + if (!bitmap) + bitmap = new wxBitmap(image); + + dc.DrawBitmap(*bitmap, point.x, point.y, true); +} + +const wxBitmap* SkinBitmap::CacheResizedBitmap(const wxSize& size) +{ + wxImage resized_img(image.Scale(size.x, size.y, wxIMAGE_QUALITY_HIGH)); + wxBitmap* resized_bitmap = new wxBitmap(resized_img); + + /* don't let the cache overflow past cacheLimit() */ + if (bitmap_cache.size() > cacheLimit()) { + for (size_t i = 0; i < bitmap_cache.size() - 1; ++i) + bitmap_cache[i] = bitmap_cache[i + 1]; + + delete bitmap_cache[bitmap_cache.size() - 1].second; + bitmap_cache.pop_back(); + } + + BitmapCacheEntry entry; + entry.first = size; + entry.second = resized_bitmap; + bitmap_cache.push_back(entry); + + return resized_bitmap; +} + + + diff --git a/digsby/ext/src/skin/SkinBitmap.h b/digsby/ext/src/skin/SkinBitmap.h new file mode 100644 index 0000000..dce0886 --- /dev/null +++ b/digsby/ext/src/skin/SkinBitmap.h @@ -0,0 +1,38 @@ +#ifndef __SKINBITMAP_H__ +#define __SKINBITMAP_H__ + +#include +#include +#include + +#include "skinobjects.h" + +typedef std::pair BitmapCacheEntry; +typedef std::vector BitmapCache; + +class SkinBitmap : public SkinBase +{ +private: + SkinBitmap(const SkinBitmap&); + +public: + SkinBitmap(const wxImage& image); + virtual ~SkinBitmap(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + virtual void Draw(wxDC& dc, const wxPoint& point); + + size_t cacheLimit() const { return static_cast(cache_limit); } + +protected: + const wxBitmap* CacheResizedBitmap(const wxSize& size); + + wxImage image; + wxBitmap* bitmap; + + BitmapCache bitmap_cache; + + unsigned char cache_limit; +}; + +#endif // __SKINBITMAP_H__ diff --git a/digsby/ext/src/skin/SkinBitmap.sip b/digsby/ext/src/skin/SkinBitmap.sip new file mode 100644 index 0000000..ad63c65 --- /dev/null +++ b/digsby/ext/src/skin/SkinBitmap.sip @@ -0,0 +1,20 @@ +class SkinBitmap : SkinBase +{ +%TypeHeaderCode +#include "SkinBitmap.h" +%End + +private: + SkinBitmap(const SkinBitmap&); + +public: + SkinBitmap(const wxImage& image); + virtual ~SkinBitmap(); + + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + virtual void Draw(wxDC& dc, const wxPoint& point); + + size_t cacheLimit() const; +}; + diff --git a/digsby/ext/src/skin/skinbrush.cpp b/digsby/ext/src/skin/skinbrush.cpp new file mode 100644 index 0000000..b690480 --- /dev/null +++ b/digsby/ext/src/skin/skinbrush.cpp @@ -0,0 +1,26 @@ +#include "skinbrush.h" + +SkinBrush::SkinBrush() {} +SkinBrush::~SkinBrush() {} + +void SkinBrush::SetBorder(const wxPen& borderPen) { + pen = borderPen; +} + +void SkinBrush::Draw(wxDC& dc, const wxRect& rect, int n) +{ + +} + +SkinColor::SkinColor(const wxColour& color) + : brush(wxBrush(color)) +{ +} + +SkinColor::~SkinColor() {} + +void SkinColor::Draw(wxDC& dc, const wxRect& rect, int n) { + dc.SetBrush(brush); + dc.SetPen(pen); + dc.DrawRectangle(rect); +} diff --git a/digsby/ext/src/skin/skinbrush.h b/digsby/ext/src/skin/skinbrush.h new file mode 100644 index 0000000..cd397ae --- /dev/null +++ b/digsby/ext/src/skin/skinbrush.h @@ -0,0 +1,34 @@ +#ifndef _SKINBRUSH_H_ +#define _SKINBRUSH_H_ + +#include +#include +#include + +#include "wx/renderer.h" + + +class SkinBrush { +public: + SkinBrush(); + virtual ~SkinBrush(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + virtual void SetBorder(const wxPen& borderPen); + +protected: + wxPen pen; +}; + +class SkinColor : public SkinBrush { +public: + SkinColor(const wxColour& color); + virtual ~SkinColor(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + +protected: + wxBrush brush; +}; + +#endif \ No newline at end of file diff --git a/digsby/ext/src/skin/skinbrush.i b/digsby/ext/src/skin/skinbrush.i new file mode 100644 index 0000000..00a39d6 --- /dev/null +++ b/digsby/ext/src/skin/skinbrush.i @@ -0,0 +1,49 @@ + +%{ +#include "wx/wxPython/wxPython.h" +#include "wx/wxPython/pyclasses.h" +#include "wx/wxPython/pyistream.h" + +#include +#include +#include + +#include "skinbrush.h" +%} + +%import typemaps.i +%import my_typemaps.i + +%import core.i +%import windows.i +%import misc.i +%import _button.i +%import _dc.i +%import _colour.i + +%pythoncode { wx = wx._core } +%pythoncode { __docfilter__ = wx.__DocFilter(globals()) } + + +class SkinBrush { +public: + SkinBrush(); + virtual ~SkinBrush(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + virtual void SetBorder(const wxPen& borderPen); + +protected: + wxPen pen; +}; + +class SkinColor : public SkinBrush { +public: + SkinColor(const wxColour& color); + virtual ~SkinColor(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + +protected: + wxBrush brush; +}; diff --git a/digsby/ext/src/skin/skinobjects.cpp b/digsby/ext/src/skin/skinobjects.cpp new file mode 100644 index 0000000..3ff03d0 --- /dev/null +++ b/digsby/ext/src/skin/skinobjects.cpp @@ -0,0 +1,244 @@ +#include "skinobjects.h" +#include +using namespace std; + +#include +#include + +// +// SkinBase +// + +wxBitmap SkinBase::GetBitmap(const wxSize& size, int /*n*/) +{ + // TODO + return wxBitmap(size.x, size.y); +} + +SkinBase::~SkinBase() {} + +// +// SkinStack +// + +SkinStack::SkinStack(const vector skinregions) + : regions(skinregions) +{ + +} + +SkinStack::~SkinStack() +{ +} + +void SkinStack::Draw(wxDC& dc, const wxRect& rect, int n) +{ + for (size_t i = 0; i < regions.size(); ++i) + regions[i]->Draw(dc, rect, n); +} + +// +// SkinRegion +// + +SkinRegion::SkinRegion(const wxPen& borderPen) + : ytile(true) + , rounded(false) + , radius(3) + , highlight(false) + , shadow(false) + , border(borderPen) +{ + has_border = borderPen != *wxTRANSPARENT_PEN; +} + +SkinRegion::~SkinRegion() +{ +} + +void SkinRegion::SetOutline(const wxPen& pen, int rounded, bool highlight, bool shadow) +{ + this->border = pen; + this->has_border = pen != *wxTRANSPARENT_PEN; + this->rounded = rounded != 0; + this->highlight = highlight; + this->shadow = shadow; +} + +void SkinRegion::Stroke(wxDC& dc, wxGraphicsContext* gc, const wxRect& rect, int /*n*/) +{ + if (!has_border) + return; + + int penw = border.GetWidth() / 2.0f; + + wxRect r(rect); + r.Deflate(penw, penw); + //border.SetCap(wxCAP_PROJECTING); + + if (rounded) { + bool needsDelete = false; + if (!gc) { + gc = wxGraphicsContext::Create((wxWindowDC&)dc); + needsDelete = true; + } + + gc->SetBrush(*wxTRANSPARENT_BRUSH); + gc->SetPen(border); + gc->DrawRoundedRectangle(rect.x, rect.y, rect.width, rect.height, rounded * .97); + + rect.Inflate(penw, penw); + + if (needsDelete) + delete gc; + } else { + dc.SetPen(border); + + int offset = (int)(border.GetWidth() % 2 == 0); + wxPoint x(offset, 0); + wxPoint y(0, offset); + + dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft() + y); + dc.DrawLine(rect.GetBottomLeft() + y, rect.GetBottomRight() + y + x); + dc.DrawLine(rect.GetBottomRight() + y + x, rect.GetTopRight() + x); + dc.DrawLine(rect.GetTopRight() + x, rect.GetTopLeft()); + } +} + +void SkinRegion::Draw(wxDC& /*dc*/, const wxRect& /*rect*/, int /*n*/) +{ + +} + + +// +// SkinColor +// + +SkinColor::SkinColor(const wxColour& c) + : wxColour(c) + , SkinRegion(*wxTRANSPARENT_PEN) +{ + m_opaque = c.Alpha() == 255; +} + +SkinColor::~SkinColor() +{ +} + +void SkinColor::Draw(wxDC& dc, const wxRect& rect, int n) +{ + dc.SetBrush(wxBrush(*this)); + dc.SetPen(*wxTRANSPARENT_PEN); + + Fill(dc, rect); + Stroke(dc, 0, rect, n); +} + +void SkinColor::Fill(wxDC& dc, const wxRect& rect) +{ + if (IsOpaque()) { + if (rounded) + dc.DrawRoundedRectangle(rect.x, rect.y, rect.width, rect.height, rounded); + else + dc.DrawRectangle(rect); + } else { +#if __WXMSW__ + wxGraphicsContext* gc = wxGraphicsContext::Create(dc); + gc->SetBrush(dc.GetBrush()); + gc->SetPen(*wxTRANSPARENT_PEN); + if (rounded) + gc->DrawRoundedRectangle(rect.x, rect.y, rect.width, rect.height, rounded); + else + gc->DrawRectangle(rect.x, rect.y, rect.width, rect.height); + + delete gc; +#endif + } + +} + +// +// SkinGradient +// + +SkinGradient::SkinGradient(int dir, const vector& cols) + : SkinRegion(*wxTRANSPARENT_PEN) + , direction(dir) + , colors(cols) +{ + wxASSERT_MSG(dir == wxHORIZONTAL || dir == wxVERTICAL, + _T("SkinGradient's direction argument must be wxHORIZONTAL or wxVERTICAL") ); + + ytile = direction == wxHORIZONTAL; +} + +SkinGradient::~SkinGradient() +{ +} + +void SkinGradient::GenRects(wxGraphicsContext* gc, const wxRect& therect) +{ + if (0 && therect == oldRect) { + // use already cached rectangles. + // + // TODO: this caching falls down with scrolling, since the position of each + // incoming rectangle is different. + return; + } + + float x = therect.x, y = therect.y; + float w = therect.width; + float h = therect.height; + bool vert = direction == wxVERTICAL; + + float p1 = vert? therect.GetTop() : therect.GetLeft(); + + size_t lc = colors.size() - 1; + float dx = (vert ? h : w) / float(lc); + + rects.clear(); + for (size_t i = 0; i < lc; ++i) { + wxColour c1(colors[i]); + wxColour c2(colors[i+1]); + + float delta = i == 0 || i == lc ? 1.0 : 0.0; + if (vert) + rects.push_back(BrushRect( + gc->CreateLinearGradientBrush(x, p1 - delta, x, p1 + dx + delta*2, c1, c2), + wxRect2DDouble(x, p1, w, dx + delta - 1))); + else + rects.push_back(BrushRect( + gc->CreateLinearGradientBrush(p1 - delta, y, p1 + dx + delta*2, y, c1, c2), + wxRect2DDouble(p1, y, dx + delta, h))); + p1 += dx; + } + + oldRect = therect; +} + +void SkinGradient::Draw(wxDC& dc, const wxRect& rect, int n) +{ + wxGraphicsContext* gc = wxGraphicsContext::Create((wxWindowDC&)dc); + gc->SetPen(*wxTRANSPARENT_PEN); + gc->Clip(rect); + + GenRects(gc, rect); + + for (size_t i = 0; i < rects.size(); ++i) { + gc->SetBrush(rects[i].brush); + Fill(dc, gc, rects[i].rect); + } + + Stroke(dc, gc, rect, n); + + delete gc; +} + +void SkinGradient::Fill(wxDC& /*dc*/, wxGraphicsContext* gc, const wxRect2DDouble& rect) +{ + if (rounded) + gc->DrawRoundedRectangle(rect.m_x, rect.m_y, rect.m_width, rect.m_height, radius); + else + gc->DrawRectangle(rect.m_x, rect.m_y, rect.m_width, rect.m_height); +} diff --git a/digsby/ext/src/skin/skinobjects.h b/digsby/ext/src/skin/skinobjects.h new file mode 100644 index 0000000..ae03f12 --- /dev/null +++ b/digsby/ext/src/skin/skinobjects.h @@ -0,0 +1,114 @@ +#ifndef _SKINOBJECTS_H_ +#define _SKINOBJECTS_H_ + +#include "wx/wxprec.h" +#ifndef WX_PRECOMP +#include "wx/wx.h" +#include "wx/graphics.h" +#endif + +#include + +// +// The interface for all skin elements. +// +class SkinBase +{ +public: + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0) = 0; + virtual wxBitmap GetBitmap(const wxSize& size, int n = 0); + + virtual ~SkinBase(); +}; + + +class SkinStack : public SkinBase +{ +public: + SkinStack(const std::vector skinregions); + virtual ~SkinStack(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + const std::vector GetRegions() const { return regions; } + +protected: + std::vector regions; +}; + + +class SkinRegion : public SkinBase +{ +public: + SkinRegion(const wxPen& borderPen); + virtual ~SkinRegion(); + + void SetOutline(const wxPen& pen, int rounded = 0, bool highlight = false, bool shadow = false); + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + bool ytile; + + wxPen GetBorder() const { return border; } + +protected: + void Stroke(wxDC& dc, wxGraphicsContext* gc, const wxRect& rect, int n = 0); + + bool rounded; + unsigned char radius; + + bool highlight; + bool shadow; + wxPen border; + bool has_border; +}; + + +class SkinColor : public wxColour, public SkinRegion +{ +public: + SkinColor(const wxColour& c); + virtual ~SkinColor(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + + bool IsOpaque() const { return m_opaque; } + +protected: + void Fill(wxDC& dc, const wxRect& rect); + + bool m_opaque; +}; + +struct BrushRect +{ + BrushRect(const wxGraphicsBrush& b, const wxRect2DDouble& r) + : brush(b) + , rect(r) + { + } + + wxGraphicsBrush brush; + wxRect2DDouble rect; +}; + + +class SkinGradient : public SkinRegion +{ +public: + SkinGradient(int dir, const std::vector& cols); + virtual ~SkinGradient(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); + +protected: + void GenRects(wxGraphicsContext* gc, const wxRect& therect); + void Fill(wxDC& dc, wxGraphicsContext* gc, const wxRect2DDouble& rect); + + + + std::vector colors; + int direction; + + wxRect oldRect; + std::vector rects; +}; + +#endif diff --git a/digsby/ext/src/skin/skinobjects.sip b/digsby/ext/src/skin/skinobjects.sip new file mode 100644 index 0000000..432c671 --- /dev/null +++ b/digsby/ext/src/skin/skinobjects.sip @@ -0,0 +1,86 @@ +%ModuleHeaderCode +#include "skinobjects.h" +%End + +class SkinBase +{ +private: + SkinBase(); + SkinBase(const SkinBase&); +public: + virtual ~SkinBase(); + + void Draw(wxDC& dc, const wxRect& rect, int n = 0) = 0; + wxBitmap GetBitmap(const wxSize& size, int n = 0); +}; + + +class SkinStack : SkinBase +{ +private: + SkinStack(); + SkinStack(const SkinStack&); +public: + //SkinStack(const std::vector skinregions); + virtual ~SkinStack(); + + void Draw(wxDC& dc, const wxRect& rect, int n = 0); + //const std::vector GetRegions() const; +}; + + +class SkinRegion : SkinBase +{ +private: + SkinRegion(const SkinRegion&); + SkinRegion(); + +public: + SkinRegion(const wxPen& borderPen); + virtual ~SkinRegion(); + + void SetOutline(const wxPen& pen, int rounded = 0, bool highlight = false, bool shadow = false); + void Draw(wxDC& dc, const wxRect& rect, int n = 0); + bool ytile; + wxPen GetBorder() const; +}; + + +class SkinColor : SkinRegion, wxColour +{ +private: + SkinColor(); + SkinColor(const SkinColor&); +public: + SkinColor(const wxColour& c); + virtual ~SkinColor(); + + void Draw(wxDC& dc, const wxRect& rect, int n = 0); + bool IsOpaque() const; + + int __getitem__(int i); +%MethodCode + switch (a0) { + case 0: sipRes = sipCpp->Red(); break; + case 1: sipRes = sipCpp->Green(); break; + case 2: sipRes = sipCpp->Blue(); break; + case 3: sipRes = sipCpp->Alpha(); break; + default: + PyErr_Format(PyExc_IndexError, "index out of range: %d", a0); + sipIsErr = 1; + } +%End +}; + + +class SkinGradient : SkinRegion +{ +private: + SkinGradient(); + SkinGradient(const SkinGradient&); +public: + SkinGradient(int dir, const std::vector& cols); + virtual ~SkinGradient(); + + virtual void Draw(wxDC& dc, const wxRect& rect, int n = 0); +}; diff --git a/digsby/ext/src/skinsplitter.sip b/digsby/ext/src/skinsplitter.sip new file mode 100644 index 0000000..6853212 --- /dev/null +++ b/digsby/ext/src/skinsplitter.sip @@ -0,0 +1,20 @@ +%ModuleHeaderCode +#include "skinsplitter.h" +%End + +enum SplitterStates +{ + NORMAL = 0, + ACTIVE = 1, + HOVER = 2 +}; + +class SkinSplitter : wxSplitterWindow +{ +public: + SkinSplitter(wxWindow* parent /TransferThis/, long style = wxSP_LIVE_UPDATE | wxNO_BORDER); + virtual ~SkinSplitter(); + void SetSplitterColors(const wxColour& normal, const wxColour& active, const wxColour& hover); + void SetNative(bool native); + bool GetNative() const; +}; diff --git a/digsby/ext/src/skinvlist.cpp b/digsby/ext/src/skinvlist.cpp new file mode 100644 index 0000000..208ddf2 --- /dev/null +++ b/digsby/ext/src/skinvlist.cpp @@ -0,0 +1,746 @@ +// +// SkinVList is like wxVListBox but with smooth scrolling. +// +// Instead of storing list items itself, this class only asks elements to +// be drawn at a specified location. This allows the list to have an arbitrarily +// huge number of elements, or for the element data to be stored elsewhere, etc. +// +#include +#include +#include + +#include "skinvlist.h" + +#include +#include +#include +#include + +#include "pyutils.h" + +#if WXPY +#include + +// used to translate C++ objects -> python and vice versa +const sipAPIDef *get_sip_api() +{ + /* accessing the SIP API from other modules is not allowed except through + the pointer obtained by this function */ + PyObject *sip_module; + PyObject *sip_module_dict; + PyObject *c_api; + + /* Import the SIP module. */ + sip_module = PyImport_ImportModule("sip"); + + if (sip_module == NULL) + return NULL; + + /* Get the module's dictionary. */ + sip_module_dict = PyModule_GetDict(sip_module); + + /* Get the "_C_API" attribute. */ + c_api = PyDict_GetItemString(sip_module_dict, "_C_API"); + + if (c_api == NULL) + return NULL; + + /* Sanity check that it is the right type. */ + if (!PyCObject_Check(c_api)) + return NULL; + + /* Get the actual pointer from the object. */ + return (const sipAPIDef *)PyCObject_AsVoidPtr(c_api); +} + +#else +#include "wx/wxPython/wxPython.h" +#endif + +#include "skinobjects.h" + +#define DBG(_x) ((void) 0) +//#define DBG(_x) _x + +// with DEBUG_REGIONS on, colored rectangles will be drawn in invalidated regions +// to show where the list is painting. for example, when scrolling down, only +// the bottom part of the list should become colored. +#define DEBUG_REGIONS 0 + +using namespace std; + +wxGraphicsContext* createGC(wxDC& dc) +{ + return wxGraphicsContext::Create((wxWindowDC&)dc); +} + +IMPLEMENT_CLASS(SkinVList, SkinScrollWindow); + +BEGIN_EVENT_TABLE(SkinVList, SkinScrollWindow) + EVT_PAINT(SkinVList::OnPaint) + EVT_ERASE_BACKGROUND(SkinVList::OnEraseBackground) + EVT_SIZE(SkinVList::OnSize) + EVT_LEFT_DOWN(SkinVList::OnLeftDown) + EVT_LEFT_DCLICK(SkinVList::OnLeftDClick) +END_EVENT_TABLE() + +SkinVList::SkinVList(wxWindow* parent, wxWindowID id, int style) + : SkinScrollWindow(parent, id, style | wxWANTS_CHARS) + , totalHeight(0) + , firstVisible(0) + , selStore(NULL) + , background(NULL) + , current(wxNOT_FOUND) + , drawCallback(0) + , paintCallback(0) +{ + SetBackgroundStyle(wxBG_STYLE_CUSTOM); + + if (style & wxLB_MULTIPLE) + selStore = new wxSelectionStore(); +} + + +SkinVList::~SkinVList() +{ + if (selStore) + delete selStore; + + PY_BLOCK + Py_CLEAR(drawCallback); + Py_CLEAR(paintCallback); + Py_CLEAR(background); + PY_UNBLOCK +} + +void SkinVList::SetSelection(int selection, bool keepVisible /* = true*/) +{ + if (HasMultipleSelection()) + // TODO: implement multiple selection + assert(false); + else + DoSetCurrent(selection, keepVisible); +} + +bool SkinVList::IsSelected(size_t row) const +{ + return selStore ? selStore->IsSelected(row) : (int)row == current; +} + +bool SkinVList::DoSetCurrent(int i, bool keepVisible /* = true */) +{ + if (i == current) + return false; + else if (current != wxNOT_FOUND) + RefreshLine(current); + + current = i; + if (current > (int)heights.size()) { + wxLogWarning(wxT("DoSetCurrent got %d but list is only %d elements long"), i, heights.size()); + current = wxNOT_FOUND; + } + + if (current != wxNOT_FOUND) { + if (keepVisible && !IsVisible(current)) { + DBG(cout << "was not visible, scrolling to line " << current << endl); + ScrollToLine(current); + } else + RefreshLine(current); + } + + DBG(cout << "selection changed: " << current << endl); + return true; +} + +wxRect SkinVList::GetItemRect(size_t i) const { + wxASSERT_MSG(i >= 0 && i < heights.size(), wxT("invalid index")); + + int y2 = heightSums[i]; + int height = heights[i]; + + return wxRect(0, y2 - height, GetClientRect().width, height); +} + +wxCoord SkinVList::OnGetLineHeight(size_t n) const +{ + return n < heights.size() ? heights[n] : 0; +} + +int SkinVList::GetItemY(size_t i) const +{ + return i < heights.size() ? (heightSums[i] - heights[i] - GetScrollPos(wxVERTICAL)) : 0; +} + +bool SkinVList::IsVisible(size_t i) const +{ + return i < heights.size() && GetViewRect().Contains(GetItemRect(i)); +} + +void SkinVList::OnHandleScroll(wxScrollWinEvent& e) +{ + SkinScrollWindow::OnHandleScroll(e); + CalcVisible(); +} + +size_t SkinVList::GetLastVisibleLine() const +{ + int y = GetClientRect().GetBottom() + GetViewStart(); + size_t len = heightSums.size(); + + if (!len) { + DBG(cout << "GetLastVisibleLine(1) -> " << firstVisible << endl); + return firstVisible; + } + + size_t i = firstVisible; + while (i < len && (int)heightSums[i] < y) + ++i; + + DBG(cout << "GetLastVisibleLine(2) -> " << i-1 << endl); + return i; +} + +void SkinVList::RefreshLine(size_t row) +{ + if (row < heights.size()) { + wxRect rect(GetClientRect()); + rect.height = heights[row]; + rect.y = heightSums[row] - rect.height; + + rect.Offset(0, -GetViewStart()); + + RefreshRect(rect); + } +} + +void SkinVList::RefreshLines(size_t a, size_t b) +{ + if (b < a) { + size_t temp = a; + a = b; + b = temp; + } + + wxRect refreshRect; + wxRect crect(GetClientRect()); + int viewStart = GetViewStart(); + + for (size_t i = a; i <= b; ++i) { + wxRect rect(crect); + rect.height = heights[i]; + rect.y = heightSums[i] - rect.height - viewStart; + refreshRect.Union(rect); + } + + RefreshRect(refreshRect); +} + +// sets "firstVisible" to the index of the first row that is at least partially +// visible +void SkinVList::CalcVisible() +{ + unsigned int vy = max(0, GetViewStart()); + + // TODO: this is a dumb algorithm for finding the first row visible. + // a bisection algorithm would perform much better for large lists. + + DBG(cout << "CalcVisible" << endl); + + if (firstVisible > heightSums.size() - 1) + firstVisible = heightSums.size() - 1; + + if (!heightSums.size()) { + return; + + } else if (firstVisible > 0 && vy < heightSums[firstVisible-1]) { + DBG(cout << " going up" << endl); + while (--firstVisible > 0) + if (firstVisible == 0 || heightSums[firstVisible-1] <= vy) + break; + + } else if (heightSums.size() > firstVisible && heightSums[firstVisible] < vy) { + DBG(cout << " going down" << endl); + while (++firstVisible < heightSums.size()) + if (heightSums.size() > firstVisible && heightSums[firstVisible] >= vy) + break; + } + + DBG(cout << " firstVisible: " << firstVisible << endl); + + +} + +inline ostream& operator<< (ostream& o, const wxRect& r) +{ + o << "wxRect(" << r.x << ", " << r.y << ", " << r.width << ", " << r.height << ")"; + return o; +} + +// sets the background for the list, drawn behind all elements +void SkinVList::SetBackground(PyObject* background) +{ + PY_BLOCK + Py_XDECREF(this->background); + Py_XINCREF(background); + this->background = background; + + DBG(cout << "SkinVList::SetBackground:" << endl); + DBG(PyObject_Print(background, stdout, 0)); + + // If the background object has a ".ytile" attribute that evaluates + // to false, then it is assumed that it does not "tile" in the Y direction, + // and that we cannot scroll vertically just by blitting unchanged pixels + // (this is called physical scrolling here). + // + // With .ytile = false we have to repaint the background with every update. + bool physical = true; + if (background) { + DBG(cout << "physical scrolling is "); + PyObject* ytileobj = PyObject_GetAttrString(background, "ytile"); + + if (ytileobj) { + physical = 0 != PyObject_IsTrue(ytileobj); + DBG(cout << physical << endl); + Py_DECREF(ytileobj); + } else + PyErr_Clear(); // no .ytile attribute is OK + } + + EnablePhysicalScrolling(physical); + PY_UNBLOCK +} + +PyObject* SkinVList::GetBackground() const +{ + return background; +} + +int SkinVList::HitTest(const wxPoint& pt) const +{ + return HitTest(pt.x, pt.y); +} + +int SkinVList::HitTest(wxCoord x, wxCoord y) const +{ + float percent; + return HitTestEx(x, y, &percent); +} + +// Returns the row the mouse is over, or wxNOT_FOUND. Also sets the value of +// percent to be the percentage down in the row the mouse is. +int SkinVList::HitTestEx(wxCoord x, wxCoord y, float* percent) const +{ + if (!GetClientRect().Contains(x, y)) + return wxNOT_FOUND; + + // offset by view position + y += GetViewStart(); + + // find the first visible item whose lower boundary is beneath the mouse click + for (unsigned int n = firstVisible; n < heights.size(); ++n) { + if (y < (int)heightSums[n]) { + float height = (float)heights[n]; + if (height) + *percent = float(y - (n > 0 ? heightSums[n-1] : 0)) / height; + else + *percent = 0.0f; + + DBG(cout << "percent: " << *percent << endl); + return n; + } + } + + return wxNOT_FOUND; +} + +#if WXPY +static sipAPIDef* sip = NULL; +static bool sip_initialized = false; +static bool sip_found_classes = false; + +static const sipTypeDef* sipType_wxAutoBufferedPaintDC; +static const sipTypeDef* sipType_wxRect; +static const sipTypeDef* sipType_wxString; + +static bool sip_init() +{ + if (sip_found_classes) + return true; + + if (!sip_initialized) { + sip_initialized = true; + sip = (sipAPIDef*)get_sip_api(); + if (sip == NULL) { + PyErr_SetString(PyExc_AssertionError, "could not obtain SIP api pointer"); + PyErr_Print(); + return false; + } + + sipType_wxAutoBufferedPaintDC = sip->api_find_type("wxAutoBufferedPaintDC"); + sipType_wxRect = sip->api_find_type("wxRect"); + sipType_wxString = sip->api_find_type("wxString"); + } + + if (!sip || !sipType_wxAutoBufferedPaintDC || !sipType_wxRect || !sipType_wxString) { + PyErr_SetString(PyExc_AssertionError, "could not find SIP types"); + PyErr_Print(); + return false; + } + + sip_found_classes = true; + return true; +} +#endif + +static void PySkinDraw(PyObject* bg, wxDC& dc, const wxRect& rect) +{ + PY_BLOCK +#if WXPY + if (!sip_init()) + return; + + PyObject* pydc = sip->api_convert_from_type((void*)&dc, sipType_wxAutoBufferedPaintDC, NULL); + PyObject* pyrect = sip->api_convert_from_type((void*)&rect, sipType_wxRect, NULL); +#else + PyObject* pydc = wxPyConstructObject((void*)&dc, wxT("wxAutoBufferedPaintDC"), false); + PyObject* pyrect = wxPyConstructObject((void*)&rect, wxT("wxRect"), false); +#endif //WXPY + PyObject* pyn = PyInt_FromLong(0); + + if ( !pydc ) + cout << "error: DC was null" << endl; + else if ( !pyrect ) + cout << "error: rect was null" << endl; + else if ( !pyn ) + cout << "error: n was null" << endl; + else if ( !bg ) + cout << "error: bg was null" << endl; + else { + DBG(PyObject_Print(pydc, stdout, 0); cout << endl); + DBG(PyObject_Print(pyrect, stdout, 0); cout << endl); + DBG(PyObject_Print(pyn, stdout, 0); cout << endl); + +#if WXPY + const wxString drawStr_wx(wxT("Draw")); + PyObject* DrawStr = sip->api_convert_from_type((void*)&drawStr_wx, sipType_wxString, NULL); +#else + PyObject* DrawStr = wx2PyString(wxT("Draw")); +#endif + PyObject* drawMethod = PyObject_GetAttr(bg, DrawStr); + + if (drawMethod) { + if (PyCallable_Check(drawMethod)) { + PyObject* result = PyObject_CallFunctionObjArgs(drawMethod, pydc, pyrect, pyn, NULL); + Py_XDECREF(result); + } else + cout << "background.Draw is not callable" << endl; + } else + cout << "background has no Draw attribute" << endl; + + Py_XDECREF(drawMethod); + Py_DECREF(DrawStr); + + if (PyErr_Occurred()) + PyErr_Print(); + } + + Py_XDECREF(pydc); + Py_XDECREF(pyrect); + Py_XDECREF(pyn); + + PY_UNBLOCK +} + +void SkinVList::OnPaint(wxPaintEvent&) +{ + wxAutoBufferedPaintDC dc(this); + PrepareDC(dc); + + int vy = GetViewStart(); + wxRect crect(GetClientRect()); + wxRegion updateRegion(GetUpdateRegion()); + wxRect updateBox; + + if (!updateRegion.IsOk()) { + wxLogWarning(wxT("SkinVList::OnPaint -- invalid update region")); + updateBox = wxRect(GetVirtualSize()); + } else { + updateRegion.Offset(0, vy); + updateBox = updateRegion.GetBox(); + } + + crect.Offset(0, vy); + + if (background) + PySkinDraw(background, dc, crect); + else { + dc.SetBrush(*wxWHITE_BRUSH); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.DrawRectangle(crect); + } + +#if DEBUG_REGIONS +#define RR rand() * 255 / RAND_MAX + dc.SetBrush(wxBrush(wxColor(RR, RR, RR, 128))); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.DrawRectangle(updateBox); + // DBG(fprintf(stdout, "updateBox(%d %d %d %d)\n", updateBox.x, updateBox.y, updateBox.width, updateBox.height)); +#endif + + wxRect rect(0, 0, crect.width, 0); + + int lastVisiblePixel = min(updateBox.GetBottom(), crect.GetBottom()); + + for (size_t n = firstVisible; n < heights.size(); ++n) { + rect.y = heightSums[n] - heights[n]; + rect.height = heights[n]; + + if (rect.y + rect.height >= updateBox.GetTop()) { + // call virtual subclass method to draw each individual item + wxRect drawRect(rect); + OnDrawItem(dc, drawRect, n); + } + + if (rect.GetBottom() > lastVisiblePixel) + break; + } + + if (paintCallback) { + PY_BLOCK +#if WXPY + if(!sip_init()) return; + PyObject* pydc = sip->api_convert_from_type((void*)&dc, sipType_wxAutoBufferedPaintDC, NULL); +#else + PyObject* pydc = wxPyConstructObject((void*)&dc, wxT("wxAutoBufferedPaintDC"), false); +#endif + + if (pydc) { + PyObject* result = PyObject_CallFunctionObjArgs(paintCallback, pydc, NULL); + Py_XDECREF(result); + Py_DECREF(pydc); + } + PY_UNBLOCK + } +} + +void SkinVList::DoHandleItemClick(int item, int) +{ + // has anything worth telling the client code about happened? + bool notify = false; + + // in any case the item should become the current one + if (DoSetCurrent(item) && !HasMultipleSelection()) + notify = true; // this has also changed the selection for single selection case + + // notify the user about the selection change + if (notify) + SendSelectedEvent(); + + // else: nothing changed at all +} + +void SkinVList::SetDrawCallback(PyObject* drawCallback) +{ + PY_BLOCK + if (PyCallable_Check(drawCallback)) { + Py_XDECREF(this->drawCallback); + Py_INCREF(drawCallback); + this->drawCallback = drawCallback; + } else { + PyErr_SetString(PyExc_TypeError, "SetDrawCallback takes a callable argument"); + } + PY_UNBLOCK +} + +void SkinVList::SetPaintCallback(PyObject* paintCallback) +{ + PY_BLOCK + if (PyCallable_Check(paintCallback)) { + Py_XDECREF(this->paintCallback); + Py_INCREF(paintCallback); + this->paintCallback = paintCallback; + } else { + PyErr_SetString(PyExc_TypeError, "SetPaintCallback takes a callable argument"); + } + PY_UNBLOCK +} + +void SkinVList::OnLeftDown(wxMouseEvent& event) +{ + SetFocus(); + + int item = HitTest(event.GetPosition()); + + if (item != wxNOT_FOUND) { + DBG(cout << "Clicked on item " << item << endl); + + int flags = 0; + if (event.ShiftDown()) + flags |= ItemClick_Shift; + + // under Mac Apple-click is used in the same way as Ctrl-click + // elsewhere +#ifdef __WXMAC__ + if (event.MetaDown()) +#else + if (event.ControlDown()) +#endif + flags |= ItemClick_Ctrl; + + DoHandleItemClick(item, flags); + } +} + +void SkinVList::SendSelectedEvent() +{ + wxASSERT_MSG( current != wxNOT_FOUND, + _T("SendSelectedEvent() shouldn't be called") ); + + wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId()); + event.SetEventObject(this); + event.SetInt(current); + + (void)GetEventHandler()->ProcessEvent(event); +} + +bool SkinVList::SetHeights(const std::vector& newHeights) +{ + if (!wxIsMainThread()) { + wxLogError(wxT("SkinVList::SetHeights called from the wrong thread")); + return false; + } + + heights = newHeights; + heightSums.clear(); + heightSums.reserve(heights.size()); + + // + // "heightSums" is a parallel list containing the y position of the bottom of each element + // + unsigned int total = 0; + for (size_t i = 0; i < heights.size(); ++i) { + total += heights[i]; + heightSums.push_back(total); + } + + totalHeight = total; + UpdateVirtualSize(); + + if (heightSums.size() != heights.size()) + wxLogWarning(wxT("heightSums and heights have different sizes")); + + return true; +} + + +void SkinVList::OnEraseBackground(wxEraseEvent& WXUNUSED(event)) +{ + // do nothing. +} + +void SkinVList::OnSize(wxSizeEvent& event) +{ + event.Skip(); + UpdateVirtualSize(); +} + +void SkinVList::UpdateVirtualSize() +{ + SetVirtualSize(GetClientSize().GetWidth(), totalHeight); + CalcVisible(); +} + +void SkinVList::OnLeftDClick(wxMouseEvent& eventMouse) +{ + int item = HitTest(eventMouse.GetPosition()); + if (item != wxNOT_FOUND) { + // if item double-clicked was not yet selected, then treat + // this event as a left-click instead + if (item == current) { + wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, GetId()); + event.SetEventObject(this); + event.SetInt(item); + + DBG(cout << "DOUBLE CLICK" << item); + (void)GetEventHandler()->ProcessEvent(event); + } else + OnLeftDown(eventMouse); + } +} + +void SkinVList::OnDrawItem(wxDC& dc, const wxRect& rect, int n) +{ + if ( !drawCallback ) { + // TODO: raise an exception here. + cout << "error: no drawCallback given to SkinVList" << endl; + return; + } + + PY_BLOCK +#if WXPY + if (!sip_init()) + return; +#endif + + DBG(PyObject_Print(drawCallback, stdout, 0)); + + // OnDrawItem(wxDC, wxRect, n) +#if WXPY + PyObject* pydc = sip->api_convert_from_type((void*)&dc, sipType_wxAutoBufferedPaintDC, NULL); + PyObject* pyrect = sip->api_convert_from_type((void*)&rect, sipType_wxRect, NULL); +#else + PyObject* pydc = wxPyConstructObject((void*)&dc, wxT("wxAutoBufferedPaintDC"), false); + PyObject* pyrect = wxPyConstructObject((void*)&rect, wxT("wxRect"), false); +#endif //WXPY + PyObject* pyn = PyInt_FromLong(n); + + // pack into an args tuple + PyObject* argtuple = PyTuple_Pack(3, pydc, pyrect, pyn); + + // invoke the callable + PyObject* result = PyObject_CallObject(drawCallback, argtuple); + + if (PyErr_Occurred()) // print (and clear) any exceptions + PyErr_Print(); + + // clean up references. + Py_XDECREF(pydc); + Py_XDECREF(pyrect); + Py_XDECREF(pyn); + Py_XDECREF(argtuple); + Py_XDECREF(result); + + PY_UNBLOCK + return; +} + +PyObject* SkinVList::GetDrawCallback() const +{ + return drawCallback; +} + + +bool SkinVList::ScrollToLine(size_t line) +{ + if (line >= heights.size() || line >= heightSums.size()) { + wxLogWarning(wxT("ScrollToLine got invalid line %d"), line); + return false; + } + + wxRect view(GetViewRect()); + + int y2 = heightSums[line]; + int itemHeight = heights[line]; + int y1 = y2 - itemHeight; + + if (y1 < view.GetTop()) // the item is above the current scroll position + Scroll(-1, y1); + else if (y2 > view.GetBottom()) // the item is beneath the current scroll position + Scroll(-1, y1 - (view.GetHeight() - itemHeight)); + else + return false; + + CalcVisible(); + RefreshLine(line); + return true; +} diff --git a/digsby/ext/src/skinvlist.h b/digsby/ext/src/skinvlist.h new file mode 100644 index 0000000..f23ba0b --- /dev/null +++ b/digsby/ext/src/skinvlist.h @@ -0,0 +1,131 @@ +/** + skinvlist.h + + Like wxVListBox, but smooth scrolling. +*/ +#ifndef _SKINVLIST_H_ +#define _SKINVLIST_H_ + +#include "wx/wxprec.h" +#ifndef WX_PRECOMP +#include "wx/wx.h" +#include "wx/graphics.h" +#endif + +class wxSelectionStore; + +#include "ScrollWindow.h" +#include "Python.h" + +#include + +class SkinRegion; + +wxGraphicsContext* createGC(wxDC& dc); + + +class SkinVList : public SkinScrollWindow { +public: + +#if SWIG + %pythonAppend SkinVList "self._setOORInfo(self)" +#endif + SkinVList(wxWindow* parent, wxWindowID id = wxID_ANY, int style = wxLB_SINGLE); + +#ifndef SWIG + virtual ~SkinVList(); +#endif + + bool SetHeights(const std::vector& newHeights); + + int HitTest(wxCoord x, wxCoord y) const; + int HitTest(const wxPoint& pt) const; + +#if SWIG + %apply float *OUTPUT { float *percent }; // make percent in HitTextEx an extra function output +#endif + int HitTestEx(wxCoord x, wxCoord y, float* percent) const; + + bool ScrollToLine(size_t line); + + void RefreshLine(size_t line); + void RefreshLines(size_t a, size_t b); + void RefreshAll() { Refresh(); } + + size_t GetRowCount() const { return heights.size(); } + size_t GetItemCount() const { return heights.size(); } + + void SetSelection(int selection, bool keepVisible = true); + bool HasMultipleSelection() const { return selStore != NULL; } + int GetSelection() const + { + wxASSERT_MSG( !HasMultipleSelection(), _T("GetSelection() can't be used with wxLB_MULTIPLE") ); + return current; + } + bool IsSelected(size_t row) const; + + + size_t GetFirstVisibleLine() const { return firstVisible; } + size_t GetLastVisibleLine() const; + bool IsVisible(size_t i) const; + wxRect GetItemRect(size_t i) const; + int GetItemY(size_t i) const; + + + //void SetBackground(SkinRegion* background); + void SetBackground(PyObject* background); + PyObject* GetBackground() const; +#ifdef SWIG + %property(Background, GetBackground, SetBackground); +#endif + + void OnDrawItem(wxDC& dc, const wxRect& rect, int n); + void SetDrawCallback(PyObject* drawCallback); + void SetPaintCallback(PyObject* paintCallback); + PyObject* GetDrawCallback() const; + +#ifndef SWIG +protected: + std::vector heights, heightSums; + unsigned int totalHeight; + + PyObject* background; + PyObject* drawCallback; + PyObject* paintCallback; + + void CalcVisible(); + + bool DoSetCurrent(int i, bool keepVisible = true); + void DoHandleItemClick(int item, int flags); + void SendSelectedEvent(); + void UpdateVirtualSize(); + + void OnHandleScroll(wxScrollWinEvent& event); + + size_t firstVisible, lastVisible; + int current; + wxSelectionStore* selStore; + + // event handlers + void OnPaint(wxPaintEvent&); + void OnEraseBackground(wxEraseEvent&); + void OnSize(wxSizeEvent&); + void OnLeftDown(wxMouseEvent&); + void OnKeyDown(wxKeyEvent& event); + void OnLeftDClick(wxMouseEvent& eventMouse); + + virtual wxCoord OnGetLineHeight(size_t n) const; + + enum + { + ItemClick_Shift = 1, // item shift-clicked + ItemClick_Ctrl = 2, // ctrl + ItemClick_Kbd = 4 // item selected from keyboard + }; + + DECLARE_CLASS(SkinVList) + DECLARE_EVENT_TABLE() +#endif +}; + +#endif diff --git a/digsby/ext/src/skinvlist.i b/digsby/ext/src/skinvlist.i new file mode 100644 index 0000000..22753c5 --- /dev/null +++ b/digsby/ext/src/skinvlist.i @@ -0,0 +1,234 @@ + +%{ +#include "wx/wxPython/wxPython.h" +#include "wx/wxPython/pyclasses.h" +#include "wx/wxPython/pyistream.h" + +#include +#include +#include +#include + +#include "skinbrush.h" +#include "skinvlist.h" +%} + +%import typemaps.i +%import my_typemaps.i + +%import core.i +%import windows.i +%import misc.i +%import _dc.i +%import _colour.i +%import _vscroll.i +%import skinbrush.i + + +%pythoncode { wx = wx._core } +%pythoncode { __docfilter__ = wx.__DocFilter(globals()) } + +%{ +#include +%} + +namespace std { + %template(UintList) vector; +} + +// First, the C++ version +%{ +class SkinVListBox : public wxVListBox +{ + DECLARE_ABSTRACT_CLASS(SkinVListBox) +public: + SkinVListBox() : wxVListBox() {} + + SkinVListBox(wxWindow *parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0) + : wxVListBox(parent, id, pos, size, style) + {} + + // Overridable virtuals + + // the derived class must implement this function to actually draw the item + // with the given index on the provided DC + // virtual void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const = 0; + DEC_PYCALLBACK__DCRECTSIZET_constpure(OnDrawItem); + + + // the derived class must implement this method to return the height of the + // specified item + // virtual wxCoord OnMeasureItem(size_t n) const = 0; + DEC_PYCALLBACK_COORD_SIZET_constpure(OnMeasureItem); + + + // this method may be used to draw separators between the lines; note that + // the rectangle may be modified, typically to deflate it a bit before + // passing to OnDrawItem() + // + // the base class version doesn't do anything + // virtual void OnDrawSeparator(wxDC& dc, wxRect& rect, size_t n) const; + DEC_PYCALLBACK__DCRECTSIZET2_const(OnDrawSeparator); + + + // this method is used to draw the items background and, maybe, a border + // around it + // + // the base class version implements a reasonable default behaviour which + // consists in drawing the selected item with the standard background + // colour and drawing a border around the item if it is either selected or + // current + // virtual void OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const; + DEC_PYCALLBACK__DCRECTSIZET_const(OnDrawBackground); + + + PYPRIVATE; +}; + +IMPLEMENT_ABSTRACT_CLASS(wxPyVListBox, wxVListBox); + +IMP_PYCALLBACK__DCRECTSIZET_constpure(PySkinVListBox, SkinVListBox, OnDrawItem); +IMP_PYCALLBACK_COORD_SIZET_constpure (PySkinVListBox, SkinVListBox, OnMeasureItem); +IMP_PYCALLBACK__DCRECTSIZET2_const (PySkinVListBox, SkinVListBox, OnDrawSeparator); +IMP_PYCALLBACK__DCRECTSIZET_const (PySkinVListBox, SkinVListBox, OnDrawBackground); + +%} + + + +// Now define this class for SWIG + +/* + This class has two main differences from a regular listbox: it can have an + arbitrarily huge number of items because it doesn't store them itself but + uses OnDrawItem() callback to draw them and its items can have variable + height as determined by OnMeasureItem(). + + It emits the same events as wxListBox and the same event macros may be used + with it. + */ +//MustHaveApp(wxPyVListBox); + +//%rename(SkinVListBox) PySkinVListBox; + + +class PySkinVListBox : public wxPyVListBox +{ +public: + PySkinVListBox(wxWindow *parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0); + +// %RenameCtor(PreVListBox, wxPyVListBox()); + + void _setCallbackInfo(PyObject* self, PyObject* _class); + + bool Create(wxWindow *parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0); + + // get the number of items in the control + size_t GetItemCount() const; + + // does this control use multiple selection? + bool HasMultipleSelection() const; + + // get the currently selected item or wxNOT_FOUND if there is no selection + // + // this method is only valid for the single selection listboxes + int GetSelection() const; + + // is this item the current one? + bool IsCurrent(size_t item) const; + + // is this item selected? + bool IsSelected(size_t item) const; + + // get the number of the selected items (maybe 0) + // + // this method is valid for both single and multi selection listboxes + size_t GetSelectedCount() const; + + // get the margins around each item + wxPoint GetMargins() const; + + // get the background colour of selected cells + const wxColour& GetSelectionBackground() const; + + + // set the number of items to be shown in the control + // + // this is just a synonym for wxVScrolledWindow::SetLineCount() + void SetItemCount(size_t count); + + // delete all items from the control + void Clear(); + + // set the selection to the specified item, if it is wxNOT_FOUND the + // selection is unset + // + // this function is only valid for the single selection listboxes + void SetSelection(int selection); + + // selects or deselects the specified item which must be valid (i.e. not + // equal to wxNOT_FOUND) + // + // return True if the items selection status has changed or False + // otherwise + // + // this function is only valid for the multiple selection listboxes + bool Select(size_t item, bool select = true); + + // selects the items in the specified range whose end points may be given + // in any order + // + // return True if any items selection status has changed, False otherwise + // + // this function is only valid for the single selection listboxes + bool SelectRange(size_t from, size_t to); + + // toggle the selection of the specified item (must be valid) + // + // this function is only valid for the multiple selection listboxes + void Toggle(size_t item); + + // select all items in the listbox + // + // the return code indicates if any items were affected by this operation + // (True) or if nothing has changed (False) + bool SelectAll(); + + // unselect all items in the listbox + // + // the return code has the same meaning as for SelectAll() + bool DeselectAll(); + + // set the margins: horizontal margin is the distance between the window + // border and the item contents while vertical margin is half of the + // distance between items + // + // by default both margins are 0 + void SetMargins(const wxPoint& pt); + %Rename(SetMarginsXY, void, SetMargins(wxCoord x, wxCoord y)); + + // change the background colour of the selected cells + void SetSelectionBackground(const wxColour& col); + + virtual void OnDrawSeparator(wxDC& dc, wxRect& rect, size_t n) const; + virtual void OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const; + + %property(FirstSelected, GetFirstSelected, doc="See `GetFirstSelected`"); + %property(ItemCount, GetItemCount, SetItemCount, doc="See `GetItemCount` and `SetItemCount`"); + %property(Margins, GetMargins, SetMargins, doc="See `GetMargins` and `SetMargins`"); + %property(SelectedCount, GetSelectedCount, doc="See `GetSelectedCount`"); + %property(Selection, GetSelection, SetSelection, doc="See `GetSelection` and `SetSelection`"); + %property(SelectionBackground, GetSelectionBackground, SetSelectionBackground, doc="See `GetSelectionBackground` and `SetSelectionBackground`"); +}; diff --git a/digsby/ext/src/skinvlist.sip b/digsby/ext/src/skinvlist.sip new file mode 100644 index 0000000..b761042 --- /dev/null +++ b/digsby/ext/src/skinvlist.sip @@ -0,0 +1,50 @@ +%ModuleHeaderCode +#include "scrollwindow.h" +#include "skinvlist.h" +#include +%End + +class SkinVList : SkinScrollWindow +{ +public: + SkinVList(wxWindow* parent /TransferThis/, wxWindowID id = wxID_ANY, int style = wxLB_SINGLE); + virtual ~SkinVList(); + + bool SetHeights(const std::vector& newHeights); + + int HitTest(wxCoord x, wxCoord y) const; + int HitTest(const wxPoint& pt) const; + + int HitTestEx(wxCoord x, wxCoord y, float* percent) const; + + bool ScrollToLine(size_t line); + + void RefreshLine(size_t line); + void RefreshLines(size_t a, size_t b); + void RefreshAll(); + + size_t GetRowCount() const; + size_t GetItemCount() const; + + void SetSelection(int selection, bool keepVisible = true); + bool HasMultipleSelection() const; + int GetSelection() const; + bool IsSelected(size_t row) const; + + + size_t GetFirstVisibleLine() const; + size_t GetLastVisibleLine() const; + bool IsVisible(size_t i) const; + wxRect GetItemRect(size_t i) const; + int GetItemY(size_t i) const; + + //void SetBackground(SkinRegion* background); + //SIP_PYOBJECT GetBackground() const; + + void OnDrawItem(wxDC& dc, const wxRect& rect, int n); + + void SetBackground(SIP_PYOBJECT background); + void SetDrawCallback(SIP_PYOBJECT drawCallback); + void SetPaintCallback(SIP_PYOBJECT paintCallback); + //SIP_PYOBJECT GetDrawCallback() const; +}; diff --git a/digsby/ext/src/splitimage4.i b/digsby/ext/src/splitimage4.i new file mode 100644 index 0000000..8a2f00b --- /dev/null +++ b/digsby/ext/src/splitimage4.i @@ -0,0 +1,20 @@ +// not a %module + +%{ +#include "wx/wxPython/wxPython.h" +#include "wx/wxPython/pyclasses.h" +#include "wx/wxPython/pyistream.h" + +#include "SplitImage4.h" +#include +#include +%} + +%import typemaps.i +%import my_typemaps.i + +%import core.i +%import windows.i +%import misc.i + +%include SplitImage4.h diff --git a/digsby/ext/src/splitimage4.sip b/digsby/ext/src/splitimage4.sip new file mode 100644 index 0000000..aafd4a4 --- /dev/null +++ b/digsby/ext/src/splitimage4.sip @@ -0,0 +1,121 @@ +%ModuleHeaderCode +#include "wx/wx.h" +#include "splitimage4.h" +%End + +long getObjectRef(wxObject* obj); + +class Extend +{ +public: + Extend(bool up = false, bool down = false, bool left = false, bool right = false); + ~Extend(); + + bool up; + bool down; + bool left; + bool right; +}; + + +//Extend extendCopy(const Extend& e); + +class Region /PyName=SIRegion/ +{ +public: + Region(const Extend& extends, int hstyle, int vstyle, int align, wxPoint offset); + Region(const Region& r); + Region(); + + ~Region(); + + Extend extends; + int hstyle; + int vstyle; + int align; + wxPoint offset; +}; + + +ImageData ImageDataFromSource(const wxString& source); +%MethodCode + sipRes = new ImageData(*a0); +%End + +class ImageData +{ +public: + //ImageData(const wxString& source); + ImageData(const ImageData& idata); + ImageData(); + ~ImageData(); + + wxString source; + int x1; + int y1; + int x2; + int y2; + Region left; + Region right; + Region top; + Region bottom; + Region center; +}; + +class Slice +{ +public: + Slice(); + ~Slice(); + + wxImage image; + int hstyle; + int vstyle; + wxPoint pos; + wxPoint offset; + int align; +}; + +class ImageCluster +{ +public: + ImageCluster(); + ~ImageCluster(); + + Slice *center; + Slice *left; + Slice *right; + Slice *top; + Slice *bottom; + wxImage *c1; + wxImage *c2; + wxImage *c3; + wxImage *c4; +}; + +class SplitImage4 /MainThreadDestructor/ +{ +public: + ImageCluster splitimage; + wxSize Size; + wxSize MinSize; + + wxString GetPath() const; + + int top; + int bottom; + int left; + int right; + + //float ratio[8]; + + SplitImage4(const ImageData& idata); + ~SplitImage4(); + + void SetImage(const ImageData& idata); + + void Draw(wxDC* dc, const wxRect& rect, int n = 0); + wxBitmap GetBitmap(const wxSize& size, bool center = true); + void PreRender(wxDC* dc,const Slice& slice, const int& posx, const int& posy, const int& width, const int& height); + void Render(wxDC* dc, const int &w, const int &h, const int& x=0, const int& y=0, const bool& center=true); +}; diff --git a/digsby/ext/src/test/testcgui.cpp b/digsby/ext/src/test/testcgui.cpp new file mode 100644 index 0000000..c9da777 --- /dev/null +++ b/digsby/ext/src/test/testcgui.cpp @@ -0,0 +1,50 @@ + +#include +#include "RingBuffer.h" + +class CGUITest : public testing::Test +{ +protected: + virtual ~CGUITest() {} +}; + +#define EQ_BUFFER(ringbuffer_, ...) \ + { \ + static const int expected[] = { __VA_ARGS__ }; \ + int len_ = sizeof(expected) / sizeof(int); \ + int* _temp_buffer = (int*)malloc(sizeof(int) * len_); \ + ringbuffer_.data(_temp_buffer); \ + ASSERT_EQ(0, memcmp(_temp_buffer, &expected, len_)); \ + delete _temp_buffer; \ + } + + + +TEST_F(CGUITest, TestRingBuffer) +{ + RingBuffer b; + b.append(3); + EQ_BUFFER(b, 3); + ASSERT_EQ(1, b.size()); + + RingBuffer b2; + b2.append(5); + EQ_BUFFER(b2, 5, 1); + b2.append(6); + EQ_BUFFER(b2, 6, 1); + b2.append(7); + EQ_BUFFER(b2, 7); + ASSERT_EQ(1, b2.size()); + + RingBuffer b3; + b3.append(42); b3.append(42); b3.append(42); + ASSERT_EQ(3, b3.size()); + b3.append(50); + ASSERT_EQ(3, b3.size()); + EQ_BUFFER(b3, 42, 42, 50); + b3.append(60); + EQ_BUFFER(b3, 42, 50, 60); + b3.append(70); + EQ_BUFFER(b3, 50, 60, 70); + ASSERT_EQ(3, b3.size()); +} diff --git a/digsby/ext/src/testcallback.cpp b/digsby/ext/src/testcallback.cpp new file mode 100644 index 0000000..c0cf28e --- /dev/null +++ b/digsby/ext/src/testcallback.cpp @@ -0,0 +1 @@ +#include "testcallback.h" \ No newline at end of file diff --git a/digsby/ext/src/testcallback.h b/digsby/ext/src/testcallback.h new file mode 100644 index 0000000..5f29c6e --- /dev/null +++ b/digsby/ext/src/testcallback.h @@ -0,0 +1,14 @@ +#include +using namespace std; + +class MyTest +{ +public: + MyTest() {} + virtual ~MyTest() {} + + virtual void Callback() + { + cout << "Callback from C++"; + } +}; \ No newline at end of file diff --git a/digsby/ext/src/win/FullscreenWin.cpp b/digsby/ext/src/win/FullscreenWin.cpp new file mode 100644 index 0000000..0e29e30 --- /dev/null +++ b/digsby/ext/src/win/FullscreenWin.cpp @@ -0,0 +1,108 @@ + +#include +#include + +/* SHQueryUserNotificationState() return values */ +#if (WINVER < 0x0600) +// windows.h on less than vista doesn't have QUERY_USER_NOTIFICATION_STATE +typedef enum { + QUNS_NOT_PRESENT = 1, + QUNS_BUSY = 2, + QUNS_RUNNING_D3D_FULL_SCREEN = 3, + QUNS_PRESENTATION_MODE = 4, + QUNS_ACCEPTS_NOTIFICATIONS = 5 +} QUERY_USER_NOTIFICATION_STATE; +#endif + +typedef HRESULT (WINAPI *qunFunc)(QUERY_USER_NOTIFICATION_STATE*); + +class wxOnceOnlyDLLLoader +{ +public: + // ctor argument must be a literal string as we don't make a copy of it! + wxOnceOnlyDLLLoader(const wxChar *dllName) + : m_dllName(dllName) + {} + + // return the symbol with the given name or NULL if the DLL not loaded + // or symbol not present + void *GetSymbol(const wxChar *name) { + // we're prepared to handle errors here + wxLogNull noLog; + + if (m_dllName) { + m_dll.Load(m_dllName); + + // reset the name whether we succeeded or failed so that we don't + // try again the next time + m_dllName = NULL; + } + + return m_dll.IsLoaded() ? m_dll.GetSymbol(name) : NULL; + } + +private: + wxDynamicLibrary m_dll; + const wxChar *m_dllName; +}; + +static wxOnceOnlyDLLLoader wxSHELL32DLL(_T("shell32")); + + +// returns the HWND of any foreground app taking the full area of the primary +// monitor +long FullscreenAppHWND() +{ + HWND fg = GetForegroundWindow(); + + // GetForegroundWindow can return 0 + if (!fg) + return 0; + + HWND desktop = GetDesktopWindow(); + HWND shell = GetShellWindow(); + + // the desktop or shell windows cannot be fullscreen + if (fg == desktop || fg == shell) { + return 0; + } + + RECT rect; + if (!GetClientRect(fg, &rect)) { + return 0; + } + + // match the window's client size with the size of the primary monitor. + if ((rect.right - rect.left == GetSystemMetrics(SM_CXSCREEN)) && + (rect.bottom - rect.top == GetSystemMetrics(SM_CYSCREEN))) { + + // the size matches--make sure that it's the primary monitor + HMONITOR hMonitor = MonitorFromWindow(fg, MONITOR_DEFAULTTONULL); + if (hMonitor) { + MONITORINFO mInfo = { sizeof(MONITORINFO) }; + if (GetMonitorInfo(hMonitor, &mInfo)) + if (mInfo.dwFlags & MONITORINFOF_PRIMARY) + return (long)fg; + } + } + + return 0; +} + +// returns true if the foreground application is taking the entire screen +bool FullscreenApp() +{ + // SHQueryUserNotificationState is Vista only + static qunFunc qun = (qunFunc)wxSHELL32DLL.GetSymbol(wxT("SHQueryUserNotificationState")); + if (qun) { + QUERY_USER_NOTIFICATION_STATE state; + if (qun(&state) == S_OK) + return state == QUNS_NOT_PRESENT || + state == QUNS_BUSY || + state == QUNS_RUNNING_D3D_FULL_SCREEN || + state == QUNS_PRESENTATION_MODE; + } + + return FullscreenAppHWND() != 0; +} + diff --git a/digsby/ext/src/win/PlatformMessagesWin.cpp b/digsby/ext/src/win/PlatformMessagesWin.cpp new file mode 100644 index 0000000..31d40af --- /dev/null +++ b/digsby/ext/src/win/PlatformMessagesWin.cpp @@ -0,0 +1,206 @@ +#include "PlatformMessages.h" +#include "pyutils.h" + +#include + +#include + +#if 0 +#define DBG(x) x +#else +#define DBG(x) ((void*)0) +#endif + +typedef map WinMsgHandlers; +static WinMsgHandlers gs_handlers; +static int gs_binderCount = 0; + +IMPLEMENT_DYNAMIC_CLASS(PlatformMessageBinder, wxEvtHandler) + +// global callback function for all PlatformMessageBinder GWL_WNDPROC hooks +LRESULT APIENTRY PlatformMessageBinderProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PlatformMessageBinder* msgBinder = 0; + { + WinMsgHandlers::iterator i = gs_handlers.find(hWnd); + if (i != gs_handlers.end()) + msgBinder = i->second; + + // make sure the iterator goes out of scope before we call HandleMessage, + // since the map may be cleared (and all its iterators made invalid) + // as a result. + } + + if (msgBinder) + return msgBinder->HandleMessage(message, wParam, lParam); + else { + wxLogWarning(wxT("PlatformMessageBinderProc got unknown HWND %d"), hWnd); + return DefWindowProc(hWnd, message, wParam, lParam); + } +} + +PlatformMessageBinder::PlatformMessageBinder(wxWindow* window) +{ + DBG(++gs_binderCount); + DBG(fprintf(stderr, "PlatformMessageBinder(%p, window=%p) - now %d\n", this, window, gs_binderCount)); + Init(window); +} + +PlatformMessageBinder::PlatformMessageBinder() +{ + DBG(++gs_binderCount); + DBG(fprintf(stderr, "PlatformMessageBinder(%p) - now %d\n", this, gs_binderCount)); +} + +PlatformMessageBinder* PlatformMessageBinder::ForWindow(wxWindow* window) +{ + DBG(fprintf(stderr, "ForWindow(%p)\n", window)); + + HWND hwnd = (HWND)window->GetHWND(); + WinMsgHandlers::iterator i = gs_handlers.find(hwnd); + if (i != gs_handlers.end()) { + DBG(fprintf(stderr, " found existing: %p\n", i->second)); + return i->second; + } + + PlatformMessageBinder* pmb = new PlatformMessageBinder(window); + DBG(fprintf(stderr, " returning new: %p\n", pmb)); + return pmb; +} + +void PlatformMessageBinder::Init(wxWindow* window) +{ + m_hwnd = (HWND)window->GetHWND(); + m_oldProc = (WNDPROC)SetWindowLongPtr(m_hwnd, GWL_WNDPROC, (LONG_PTR)&PlatformMessageBinderProc); + + wxASSERT(gs_handlers.find(m_hwnd) == gs_handlers.end()); + gs_handlers[m_hwnd] = this; +} + +void PlatformMessageBinder::Bind(UINT message, PyObject* callback) +{ + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable"); + } else { + // make sure to DECREF any old callback for this message + // TODO: allow multiple callbacks per message + if (Unbind(message)) + fprintf(stderr, "WARNING: Bind(%d) clobbered an old callback\n", message); + + Py_INCREF(callback); + m_callbacks[message] = callback; + } +} + +void PlatformMessageBinder::BindNative(UINT message, NativeMsgHandler* cb) +{ + m_nativeCallbacks[message].push_back(cb); +} + +bool PlatformMessageBinder::Unbind(UINT message) +{ + PythonCallbackMap::iterator i = m_callbacks.find(message); + if (i != m_callbacks.end()) { + PyObject* obj = i->second; + m_callbacks.erase(message); + Py_DECREF(obj); + return true; + } + + return false; +} + +bool PlatformMessageBinder::UnbindNative(UINT message, NativeMsgHandler* cb) +{ + NativeCallbackMap::iterator i = m_nativeCallbacks.find(message); + if (i != m_nativeCallbacks.end()) { + vector cbs = i->second; + cbs.erase(std::remove(cbs.begin(), cbs.end(), cb), cbs.end()); + m_nativeCallbacks.erase(message); + return true; + } + + return false; +} + +LRESULT PlatformMessageBinder::HandleMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + bool execDefaultProc = true; + + // TODO: the point here is not to acquire python's GIL for every windows + // message. however there should be another lock here around m_callbacks. + + // execute the python callback + { + PythonCallbackMap::iterator i = m_callbacks.find(message); + if (i != m_callbacks.end()) { + PY_BLOCK + PyObject* cb = i->second; + PyObject* result = PyObject_CallFunction(cb, "IIII", m_hwnd, message, wParam, lParam); + if (result) { + // if the callback returns False, don't run the original WndProc + if (result == Py_False) + execDefaultProc = false; + + Py_DECREF(result); + } else + PyErr_Print(); + PY_UNBLOCK + } + } + + if (execDefaultProc) { + // execute any native callbacks + NativeCallbackMap::iterator i2 = m_nativeCallbacks.find(message); + if (i2 != m_nativeCallbacks.end()) { + vector handlers = i2->second; + for (size_t n = 0; n < handlers.size(); ++n) { + NativeMsgHandler* handler = handlers[n]; + handler->handleMessage(m_hwnd, message, wParam, lParam); + } + } + } + + WNDPROC oldProc = m_oldProc; + HWND hwnd = m_hwnd; + + // If necessary, run the old message handler. + LRESULT res; + if (execDefaultProc) + res = CallWindowProc(oldProc, hwnd, message, wParam, lParam); + else + res = 1; + + if (message == WM_DESTROY) + delete this; + + return res; +} + +PlatformMessageBinder::~PlatformMessageBinder() +{ + DBG(--gs_binderCount); + DBG(fprintf(stderr, "~PlatformMessageBinder(%p) - now %d\n", this, gs_binderCount)); + + // Unregister our custom WM_MESSAGE hook + SetWindowLongPtr(m_hwnd, GWL_WNDPROC, (LONG_PTR)m_oldProc); + + // Unhook from the global map of PlatformMessageBinders + gs_handlers.erase(m_hwnd); + + // Release refcounts for all Python callbacks. + PY_BLOCK + + PythonCallbackMap callbacks(m_callbacks); + m_callbacks.clear(); + + for(PythonCallbackMap::iterator i = callbacks.begin(); i != callbacks.end(); ++i) + Py_DECREF(i->second); + + callbacks.clear(); + + PY_UNBLOCK + + m_nativeCallbacks.clear(); +} + diff --git a/digsby/ext/src/win/RichEditUtils.cpp b/digsby/ext/src/win/RichEditUtils.cpp new file mode 100644 index 0000000..5911379 --- /dev/null +++ b/digsby/ext/src/win/RichEditUtils.cpp @@ -0,0 +1,63 @@ +#include +#include + +#include +#include + +bool GetRichEditParaFormat(HWND handle, PARAFORMAT2* pf) +{ + pf->cbSize = sizeof(PARAFORMAT2); + + if (!::SendMessage(handle, EM_GETPARAFORMAT, 0, (LPARAM)pf)) { + fprintf(stderr, "SendMessage(EM_GETPARAFORMAT) failed"); + return false; + } + + return true; +} + +bool SetRichEditParaFormat(HWND handle, PARAFORMAT2* pf) +{ + if (!::SendMessage(handle, EM_SETPARAFORMAT, 0, (LPARAM)pf)) { + fprintf(stderr, "SendMessage(EM_SETPARAFORMAT) failed"); + return false; + } + + return true; +} + +int GetRichEditParagraphAlignment(HWND handle) +{ + PARAFORMAT2 pf; + if (!GetRichEditParaFormat(handle, &pf)) + return 0; + + return pf.wAlignment; +} + +bool SetRichEditParagraphAlignment(HWND handle, int alignment) +{ + PARAFORMAT2 pf; + pf.cbSize = sizeof(PARAFORMAT2); + pf.dwMask = PFM_ALIGNMENT | PFM_RTLPARA; + + /* affects position/alignment of text */ + pf.wAlignment = alignment; + + /* affects sentence direction */ + pf.wEffects = (alignment & PFA_RIGHT ? PFE_RTLPARA : 0); + + return SetRichEditParaFormat(handle, &pf); +} + +int GetRichEditParagraphAlignment(wxTextCtrl* textCtrl) +{ + return GetRichEditParagraphAlignment((HWND)textCtrl->GetHWND()); +} + +bool SetRichEditParagraphAlignment(wxTextCtrl* textCtrl, int alignment) +{ + return SetRichEditParagraphAlignment((HWND)textCtrl->GetHWND(), alignment); +} + + diff --git a/digsby/ext/src/win/RichEditUtils.h b/digsby/ext/src/win/RichEditUtils.h new file mode 100644 index 0000000..f030b25 --- /dev/null +++ b/digsby/ext/src/win/RichEditUtils.h @@ -0,0 +1,10 @@ +#ifndef __CGUI_RICHEDIT_UTILS_H__ +#define __CGUI_RICHEDIT_UTILS_H__ + +#include + +int GetRichEditParagraphAlignment(wxTextCtrl* textCtrl); +bool SetRichEditParagraphAlignment(wxTextCtrl* textCtrl, int alignment); + +#endif // __CGUI_RICHEDIT_UTILS_H__ + diff --git a/digsby/ext/src/win/WinJumpList.cpp b/digsby/ext/src/win/WinJumpList.cpp new file mode 100644 index 0000000..125725f --- /dev/null +++ b/digsby/ext/src/win/WinJumpList.cpp @@ -0,0 +1,264 @@ +#include + +#include +#include +#include +#include +#include + +#include +using std::vector; + +#ifdef __GNUC__ +#include +#else +#include +#endif +using stdext::hash_map; + +#include +#include + +typedef hash_map IconCache; + +#include "WinJumpList.h" +#include "IconUtils.h" + +typedef _com_ptr_t<_com_IIID> ObjArrPtr; +typedef _com_ptr_t<_com_IIID> IShellLinkWPtr; +typedef _com_ptr_t<_com_IIID> ICustomDestinationListPtr; +typedef _com_ptr_t<_com_IIID> IObjectCollectionPtr; +typedef _com_ptr_t<_com_IIID> IPropertyStorePtr; + +static bool addBitmap(const wxBitmap& bitmap, IShellLinkWPtr& link, IconCache& iconCache) +{ + // Jump list icons must be files on disk or resources in executables, so + // save out the bitmap to a temporary location. + if (bitmap.IsOk()) { + wxString filename; + + IconCache::iterator i = iconCache.find(bitmap.GetRefData()); + if (i != iconCache.end()) + filename = i->second; + else { + if (!tempImageFile(wxStandardPaths::Get().GetTempDir() + L"\\digsby_jumplist", + wxString::Format(L"_icon_%d.bmp", iconCache.size()), + bitmap, filename, wxBITMAP_TYPE_ICO)) + return false; + } + + iconCache[bitmap.GetRefData()] = filename; + if (FAILED(link->SetIconLocation(filename, 0))) + return false; + } + + return true; +} + +// returns true if removedItems has an IShellLink object with the same value for +// GetArguments as given by "arguments" +bool itemInArray(const wstring& arguments, const ObjArrPtr& removedItems) +{ + if (arguments.size() == 0) + return false; + + size_t n; + if (FAILED(removedItems->GetCount(&n))) + return false; + + for (size_t i = 0; i < n; ++i) { + IShellLinkWPtr removedLink; + if (FAILED(removedItems->GetAt(i, IID_PPV_ARGS(&removedLink)))) + return false; + + wchar_t s[MAX_PATH+1]; + removedLink->GetArguments(&s[0], MAX_PATH); + if (arguments == s) + return true; + } + + return false; +} + +static bool isSeparator(const JumpListTask& task) +{ + return task.args.empty() && task.description.empty() && task.title.empty(); +} + +static bool AddJumpListSeparator(IObjectCollection* objColl) +{ + // Create a shell link COM object. + HRESULT hr; + IShellLinkWPtr link; + + if (FAILED(hr = link.CreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER))) + return false; + + // Get an IPropertyStore interface. + IPropertyStorePtr propStore = link; + PROPVARIANT pv; + + if (!propStore || FAILED(hr = InitPropVariantFromBoolean(TRUE, &pv))) + return false; + + // Set the property that makes the task a separator. + hr = propStore->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv); + PropVariantClear(&pv); + if (FAILED(hr)) + return false; + + // Save the property changes. + if (FAILED(propStore->Commit())) + return false; + + // Add this shell link to the object collection. + return SUCCEEDED(objColl->AddObject(link)); +} + +bool AddJumpListTask(IObjectCollection* objColl, const JumpListTask& task, LPCTSTR exePath, IconCache& iconCache, const ObjArrPtr& removedItems) +{ + if (isSeparator(task)) + return AddJumpListSeparator(objColl); + + // Create a shell link COM object. + IShellLinkWPtr link; + + if (FAILED(link.CreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER))) + return false; + + // Set the executable path + if (FAILED(link->SetPath(exePath))) + return false; + + if (FAILED(link->SetShowCmd(SW_SHOWMINNOACTIVE))) + return false; + + // Set the arguments + if (FAILED(link->SetArguments(task.args.c_str()))) + return false; + + if (!addBitmap(task.bitmap, link, iconCache)) + return false; + + // Set the working directory + wchar_t workingDir[MAX_PATH]; + if (!_wgetcwd(workingDir, MAX_PATH)) + return false; + + if (FAILED(link->SetWorkingDirectory(workingDir))) + return false; + + // Set the link description (tooltip on the jump list item) + if (FAILED(link->SetDescription(task.description.c_str()))) + return false; + + // Set the link title (the text of the jump list item). This is kept in the + // object's property store, so QI for that interface. + IPropertyStorePtr propStore = link; + PROPVARIANT pv; + + if (!propStore) + return false; + + if (FAILED(InitPropVariantFromString(task.title.c_str(), &pv))) + return false; + + // Set the title property. + HRESULT hr = propStore->SetValue(PKEY_Title, pv); + PropVariantClear(&pv); + if (FAILED(hr)) + return false; + + // Save the property changes. + if (FAILED(propStore->Commit())) + return false; + + // Don't add items disallowed by Windows via the removedItems list + if (itemInArray(task.args, removedItems)) + return true; + + // Add this shell link to the object collection. + return SUCCEEDED(objColl->AddObject(link)); +} + +bool AddJumpListTasks(IObjectCollection* objColl, const vector& tasks, const ObjArrPtr& removedItems) +{ + // Get the path to the EXE, which we use as the path and icon path for each jump list task. + TCHAR exePath[MAX_PATH]; + GetModuleFileName(NULL, exePath, _countof(exePath)); + + IconCache iconCache; + + for (vector::const_iterator i = tasks.begin(); + i != tasks.end(); ++i) { + // Add the next task to the object collection. + if (!AddJumpListTask(objColl, *i, exePath, iconCache, removedItems)) + return false; + } + + return true; +} + +static bool AddTaskGroup(ICustomDestinationListPtr& destList, const vector& tasks, const wxString& category, const ObjArrPtr& removedItems) +{ + if (!tasks.size()) + return true; + + // Create an object collection to hold the custom tasks. + IObjectCollectionPtr objColl; + if (FAILED(objColl.CreateInstance(CLSID_EnumerableObjectCollection, NULL, CLSCTX_INPROC_SERVER))) + return false; + + // Add our custom tasks to the collection. + if (!AddJumpListTasks(objColl, tasks, removedItems)) + return false; + + // Get an IObjectArray interface for AddUserTasks. + ObjArrPtr tasksArray = objColl; + if (!tasksArray) + return false; + + // Add the tasks to the jump list. + if (category.size()) { + if (FAILED(destList->AppendCategory(category, tasksArray))) + return false; + } else { + if (FAILED(destList->AddUserTasks(tasksArray))) + return false; + } + + return true; +} + +bool SetUpJumpList(LPCWSTR appID, const vector& taskGroups) +{ + ICustomDestinationListPtr destList; + + // Create a jump list COM object. + if (FAILED(destList.CreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER ))) + return false; + + // Tell the jump list our AppID. + if (FAILED(destList->SetAppID(appID))) + return false; + + // Create a new, empty jump list. We aren't adding custom destinations, + // so cMaxSlots and removedItems aren't used. + UINT cMaxSlots; + ObjArrPtr removedItems; + + if (FAILED(destList->BeginList(&cMaxSlots, IID_PPV_ARGS(&removedItems)))) + return false; + + for (vector::const_iterator i = taskGroups.begin(); + i != taskGroups.end(); + ++i) { + + const JumpListTaskGroup& taskGroup = *i; + AddTaskGroup(destList, taskGroup.tasks, taskGroup.category, removedItems); + } + + // Save the jump list. + return SUCCEEDED(destList->CommitList()); +} + diff --git a/digsby/ext/src/win/WinJumpList.h b/digsby/ext/src/win/WinJumpList.h new file mode 100644 index 0000000..6b339d0 --- /dev/null +++ b/digsby/ext/src/win/WinJumpList.h @@ -0,0 +1,24 @@ +#include +#include +#include +#include +using std::wstring; +using std::vector; + +struct JumpListTask +{ + wstring args, description, title; + wxBitmap bitmap; +}; + +struct JumpListTaskGroup +{ + wstring category; // may be empty + vector tasks; +}; + +typedef vector TaskGroupVector; + +bool SetUpJumpList(LPCWSTR appID, const TaskGroupVector& taskGroups); + + diff --git a/digsby/ext/src/win/WinTaskbar.cpp b/digsby/ext/src/win/WinTaskbar.cpp new file mode 100644 index 0000000..a353752 --- /dev/null +++ b/digsby/ext/src/win/WinTaskbar.cpp @@ -0,0 +1,847 @@ +#include "WinTaskbar.h" + +#ifdef WIN7_TASKBAR + +#include +#include +#include + +#include "WinUtils.h" +#include "cwindowfx.h" + +static DLLLoader wxDWMAPI(wxT("dwmapi")); +static DLLLoader user32(wxT("user32")); + +#define DWMWA_FORCE_ICONIC_REPRESENTAITON 7 +#define DWMWA_HAS_ICONIC_BITMAP 10 + +#ifdef NDEBUG +#define DBG(x) +#else +#define DBG(x) x +#endif + +void initializeTaskbar() +{ + static bool didInitializeTaskbar = false; + if (didInitializeTaskbar) + return; + + didInitializeTaskbar = true; + + typedef BOOL (WINAPI *ChangeWindowMessageFilter_t)(UINT message, DWORD dwFlag); + static ChangeWindowMessageFilter_t pChangeWindowMessageFilter = (ChangeWindowMessageFilter_t)user32.GetSymbol(wxT("ChangeWindowMessageFilter")); + if (pChangeWindowMessageFilter) { + // Allow DWM messages to reach us even when running as administrator + pChangeWindowMessageFilter(WM_DWMSENDICONICTHUMBNAIL, MSGFLT_ADD); + pChangeWindowMessageFilter(WM_DWMSENDICONICLIVEPREVIEWBITMAP, MSGFLT_ADD); + } +} + +// load all dwmapi.dll functions lazily, so that cgui.pyd remains compatible +// with older versions of windows. +// +// TODO: let the linker do this for us with lazy DLL dependencies + +typedef HRESULT (WINAPI *DwmSetIconicThumbnail_t)(HWND, HBITMAP, DWORD); +typedef HRESULT (WINAPI *DwmSetIconicLivePreviewBitmap_t)(HWND, HBITMAP, POINT*, DWORD); +typedef HRESULT (WINAPI *DwmSetWindowAttribute_t)(HWND, DWORD, LPCVOID, DWORD); +typedef HRESULT (WINAPI *DwmInvalidateIconicBitmaps_t)(HWND); + +TabController::TabController() +{ + DBG(printf("TabController(%p)\n", this)); +} + +TabController::~TabController() +{ + DBG(printf("~TabController(%p)\n", this)); +} + +void* TabController::GetIconicHBITMAP(TaskbarWindow*, int, int) +{ + return 0; +} + +wxBitmap TabController::GetIconicBitmap(TaskbarWindow* /*tab*/, int /*width*/, int /*height*/) +{ + return wxNullBitmap; +} + +wxBitmap TabController::GetLivePreview(TabWindow* /*tab*/, const wxRect& /*clientSize*/) +{ + return wxNullBitmap; +} + +wxIcon TabController::GetSmallIcon(TabWindow*) +{ + return wxNullIcon; +} + +SimpleTabController::SimpleTabController() +{} + +SimpleTabController::~SimpleTabController() +{} + +void SimpleTabController::SetIconicBitmap(const wxBitmap& bitmap) +{ + m_bitmap = bitmap; +} + +wxBitmap SimpleTabController::GetIconicBitmap(TaskbarWindow* /*window*/, int width, int height) +{ + wxImage img(m_bitmap.ConvertToImage()); + + // TODO: why does this make the iconic bitmap not display + //wxSize newSize(width, height); + //wxPoint pastePos(width/2.0 - img.GetWidth()/2.0, height/2.0 - img.GetHeight()/2.0); + //img.Resize(newSize, pastePos); + + img.Rescale(width, height); + + return wxBitmap(img); +} + +bool TabNotebook::SetOverlayIcon(const wxBitmap& bitmap, const wxString& description) +{ + if (!initialized()) + return false; + + const static wxSize overlaySize(16, 16); + + // passing wxNullBitmap clears the icon + if (bitmap.IsSameAs(wxNullBitmap)) + return SetOverlayIcon(static_cast(0), description); + + wxIcon icon; + if (bitmap.GetWidth() != overlaySize.x || bitmap.GetHeight() != overlaySize.y) { + wxImage img(bitmap.ConvertToImage()); + img.Rescale(overlaySize.x, overlaySize.y); + icon.CopyFromBitmap(wxBitmap(img)); + } else + icon.CopyFromBitmap(bitmap); + + return SetOverlayIcon(icon, description); +} + +bool TabNotebook::InvalidateThumbnails(wxWindow* window) +{ + if (!initialized()) + return false; + + if (TabWindow* tab = tabForWindow(window)) { + static DwmInvalidateIconicBitmaps_t pDwmInvalidateIconicBitmaps = (DwmInvalidateIconicBitmaps_t)wxDWMAPI.GetSymbol(wxT("DwmInvalidateIconicBitmaps")); + if (pDwmInvalidateIconicBitmaps) + return SUCCEEDED(pDwmInvalidateIconicBitmaps(tab->hwnd())); + } + + return false; +} + +bool TabNotebook::SetOverlayIcon(const wxIcon& icon, const wxString& description) +{ + if (!icon.Ok()) + return false; + + HICON hicon = static_cast(icon.GetHICON()); + + return SetOverlayIcon(hicon, description); +} + +bool TabNotebook::SetOverlayIcon(HICON hicon, const wxString& description) +{ + return m_taskbarList && SUCCEEDED(m_taskbarList->SetOverlayIcon(hwnd(), hicon, description.wc_str())); +} + +TabNotebook::TabNotebook(wxWindow* window) + : m_initialized(false) + , m_taskbarList(NULL) +{ + _InitFromHwnd(static_cast(window->GetHWND())); + if (isWin7OrHigher() && m_initialized) + window->Connect(-1, -1, wxEVT_DESTROY, wxWindowDestroyEventHandler(TabNotebook::OnWindowDestroyed)); +} + +TabNotebook::TabNotebook(HWND hwnd) +{ + _InitFromHwnd(hwnd); +} + +void TabNotebook::_InitFromHwnd(HWND hwnd) +{ + m_hwnd = hwnd; + + if (isWin7OrHigher()) { + initializeTaskbar(); + if (SUCCEEDED(CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_taskbarList)))) + m_initialized = SUCCEEDED(m_taskbarList->HrInit()); + } +} + + +void TabNotebook::OnWindowDestroyed(wxWindowDestroyEvent& e) +{ + e.Skip(); + if (e.GetWindow() && static_cast(e.GetWindow()->GetHWND()) == m_hwnd) { + DBG(printf("OnWindowDestroyed!\n")); + Destroy(); + } +} + +void TabNotebook::Destroy() +{ + DBG(printf("TabNotebook::Destroy(%p), size is %d\n", this, m_tabMap.empty())); + + while (!m_tabMap.empty()) { + TabMap::iterator i = m_tabMap.begin(); + + if (i != m_tabMap.end()) { + wxWindow* window = i->first; + DBG(printf("window: %p\n", window)); + bool result = DestroyTab(window); + DBG(printf("result: %d\n", result)); + (void)result; + } + } + if (m_taskbarList) { + m_taskbarList->Release(); + m_taskbarList = NULL; + } +} + +TabNotebook::~TabNotebook() +{ + DBG(printf("~TabNotebook(%p)\n", this)); + Destroy(); +} + +TabWindow* TabNotebook::CreateTab(wxWindow* window, TabController* controller /*=NULL*/) +{ + if (initialized()) { + TabWindow* tab = new TabWindow(this, window, controller); + DBG(printf("CreateTab(%p)\n", window)); + m_tabMap[window] = tab; + DBG(printf(" m_tabMap[%p] = %p", window, m_tabMap[window])); + m_tabs.push_back(tab); + return tab; + } + + return NULL; +} + +bool TabNotebook::RearrangeTab(wxWindow* tabWindow, wxWindow* beforeWindow) +{ + if (!initialized()) + return false; + + if (TabWindow* tab = tabForWindow(tabWindow)) { + TabWindow* before = NULL; + if (beforeWindow) + before = tabForWindow(beforeWindow); + + HRESULT res = m_taskbarList->SetTabOrder(tab->hwnd(), before ? before->hwnd() : NULL); + return SUCCEEDED(res); + } + + return false; +} + +TabWindow* TabNotebook::tabForWindow(wxWindow* window) const +{ + TabMap::const_iterator i = m_tabMap.find(window); + if (i != m_tabMap.end()) + return i->second; + else + return NULL; +} + +HWND TabNotebook::GetTabHWND(wxWindow* window) const +{ + if (initialized()) + if (TabWindow* tab = tabForWindow(window)) + return tab->hwnd(); + + return 0; +} + +bool TabNotebook::SetTabTitle(wxWindow* window, const wxString& title) +{ + if (initialized()) + if (TabWindow* tab = tabForWindow(window)) + return tab->SetTitle(title); + + return false; +} + +bool TabNotebook::SetTabActive(wxWindow* window) +{ + if (initialized()) + if (TabWindow* tab = tabForWindow(window)) + return SUCCEEDED(m_taskbarList->SetTabActive(tab->hwnd(), hwnd(), 0)); + + return false; +} + +bool _setIcon(HWND hwnd, const wxIconBundle& bundle, int smX, int smY, int iconType) +{ + const wxSize size(::GetSystemMetrics(smX), ::GetSystemMetrics(smY)); + const wxIcon icon = bundle.GetIcon(size); + if (icon.Ok() && icon.GetWidth() == size.x && icon.GetHeight() == size.y) { + DBG(printf("WM_SETICON %p %d\n", hwnd, iconType)); + ::SendMessage(hwnd, WM_SETICON, iconType, (LPARAM)GetHiconOf(icon)); + return true; + } + + return false; +} + +bool TabNotebook::SetTabIcon(wxWindow* window, const wxBitmap& bitmap) +{ + return SetTabIcon(window, createIconBundle(bitmap)); +} + +bool TabNotebook::SetTabIcon(wxWindow* window, const wxIconBundle& bundle) +{ + if (initialized()) { + if (TabWindow* tab = tabForWindow(window)) { + bool success = true; + success &= _setIcon(tab->hwnd(), bundle, SM_CXSMICON, SM_CYSMICON, ICON_SMALL); + success &= _setIcon(tab->hwnd(), bundle, SM_CXICON, SM_CYICON, ICON_BIG); + return success; + } + } + + return false; +} + + +bool TabNotebook::SetProgressValue(unsigned long long completed, unsigned long long total) +{ + if (m_taskbarList) + return SUCCEEDED(m_taskbarList->SetProgressValue(hwnd(), completed, total)); + + return false; +} + +bool TabNotebook::SetProgressState(int flags) +{ + if (m_taskbarList) + return SUCCEEDED(m_taskbarList->SetProgressState(hwnd(), static_cast(flags))); + + return false; +} + +void TabController::OnTabActivated(TaskbarWindow* /*window*/) +{ +} + +void TabController::OnTabClosed(TaskbarWindow* /*window*/) +{ +} + +static bool MyDwmSetWindowAttribute(HWND hwnd, DWORD attrib, LPCVOID val, DWORD valSize) +{ + static DwmSetWindowAttribute_t pDwmSetWindowAttribute = (DwmSetWindowAttribute_t)wxDWMAPI.GetSymbol(wxT("DwmSetWindowAttribute")); + return pDwmSetWindowAttribute && SUCCEEDED(pDwmSetWindowAttribute(hwnd, attrib, val, valSize)); +} + +// Calls the DwmSetWindowAttribute function for attributes which take a BOOL. +static bool SetDwmBool(HWND hwnd, DWORD attrib, bool value) +{ + BOOL val = value; + return MyDwmSetWindowAttribute(hwnd, attrib, &val, sizeof(val)); +} + +void TabNotebook::RegisterTab(TabWindow* tab) +{ + if (m_taskbarList) { + m_taskbarList->RegisterTab(tab->hwnd(), hwnd()); + m_taskbarList->SetTabOrder(tab->hwnd(), NULL); + SetDwmBool(hwnd(), DWMWA_DISALLOW_PEEK, true); + } +} + +bool TabNotebook::UnregisterTab(TabWindow* tab) +{ + DBG(printf("UnregisterTab(%p)\n", tab)); + return m_taskbarList && SUCCEEDED(m_taskbarList->UnregisterTab(tab->hwnd())); +} + +bool TabNotebook::UnregisterTab(wxWindow* win) +{ + HWND hwnd = static_cast(win->GetHWND()); + return m_taskbarList && SUCCEEDED(m_taskbarList->UnregisterTab(hwnd)); +} + +bool TabNotebook::DestroyTab(wxWindow* window) +{ + if (!initialized()) + return false; + + DBG(printf("TabNotebook::DestroyTab(%p)\n", window)); + + if (const TabWindow* tab = tabForWindow(window)) { + m_tabs.erase(std::remove(m_tabs.begin(), m_tabs.end(), tab), m_tabs.end()); + m_tabMap.erase(window); + delete tab; + return true; + } + + return false; +} + +LRESULT CALLBACK TabPreviewWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + TabWindow* wnd = reinterpret_cast(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + LRESULT result = 0; + if (!wnd && msg == WM_NCCREATE) { + LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam; + wnd = static_cast(lpcs->lpCreateParams); + wnd->m_hwnd = hwnd; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)wnd); + result = ::DefWindowProc(hwnd, msg, wParam, lParam); + } else if (wnd) + result = wnd->WndProc(msg, wParam, lParam); + else + result = ::DefWindowProc(hwnd, msg, wParam, lParam); + + return result; +} + +static WCHAR const windowClassName[] = L"TabPreviewWindow"; + +static void registerClass(WNDPROC wndProc, LPCWSTR className) +{ + WNDCLASSEX wcex = {0}; + wcex.cbSize = sizeof(wcex); + wcex.lpfnWndProc = wndProc; + wcex.hInstance = wxGetInstance(); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.lpszClassName = className; + + ::RegisterClassEx(&wcex); +} + +static void registerClassOnce() +{ + // creates the window class we'll use for offscreen hidden windows that + // provide tab thumbnail previews + static bool didRegisterClass = false; + if (didRegisterClass) return; + didRegisterClass = true; + + registerClass(TabPreviewWndProc, windowClassName); +} + +TaskbarWindow::TaskbarWindow(TabController* controller) + : m_controller(controller) +{ +} + +TabWindow::TabWindow(TabNotebook* notebook, wxWindow* window, TabController* controller) + : TaskbarWindow(controller) + , m_notebook(notebook) + , m_window(window) +{ + DBG(printf("TabWindow::TabWindow(%p)\n", this)); + registerClassOnce(); + + ::CreateWindowEx( + WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, + windowClassName, + window->GetName(), + WS_POPUP | WS_BORDER | WS_SYSMENU | WS_CAPTION, + -32000, + -32000, + 10, + 10, + NULL, + NULL, + wxGetInstance(), + (LPVOID)this); +} + +TabWindow::~TabWindow() +{ + if (m_controller) + delete m_controller; + + if (!m_hwnd) return; + + m_notebook->UnregisterTab(this); + + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0); + HWND hwnd = m_hwnd; + m_hwnd = NULL; + + DBG(printf("~TabWindow(%p) calling DestroyWindow(%p)\n", hwnd)); + ::DestroyWindow(hwnd); +} + +bool TabWindow::SetTitle(const wxString& title) +{ + return ::SetWindowText(hwnd(), title.c_str()) != 0; +} + +LRESULT TabWindow::WndProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + LRESULT result = 0; + + switch (message) { + case WM_CREATE: { + SetDwmBool(m_hwnd, DWMWA_FORCE_ICONIC_REPRESENTAITON, true); + SetDwmBool(m_hwnd, DWMWA_HAS_ICONIC_BITMAP, true); + m_notebook->RegisterTab(this); + break; + } + + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_ACTIVE) + if (TabController* ctrl = controller()) + ctrl->OnTabActivated(this); + break; + + case WM_SYSCOMMAND: + // All syscommands except for close will be passed along to the tab window + // outer frame. This allows functions such as move/size to occur properly. + if (wParam != SC_CLOSE) + result = SendMessage(m_notebook->hwnd(), WM_SYSCOMMAND, wParam, lParam); + else + result = ::DefWindowProc(m_hwnd, message, wParam, lParam); + break; + + case WM_CLOSE: + if (TabController* ctrl = controller()) + ctrl->OnTabClosed(this); + else + m_notebook->DestroyTab(this->GetWindow()); + break; + + case WM_DWMSENDICONICTHUMBNAIL: + _SendIconicThumbnail(HIWORD(lParam), LOWORD(lParam)); + break; + + case WM_DWMSENDICONICLIVEPREVIEWBITMAP: + _SendLivePreviewBitmap(); + break; + + case WM_GETICON: + if (wParam == ICON_SMALL) { + if (TabController* ctrl = controller()) { + // hold a reference to the icon data in a member variable + // so the shell has a chance to copy the data before it is + // destroyed. + m_smallIcon = ctrl->GetSmallIcon(this); + if (m_smallIcon.IsOk()) { + static wxSize expected(::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON)); + if (m_smallIcon.GetWidth() != expected.x || m_smallIcon.GetHeight() != expected.y) + fprintf(stderr, "WARNING: icon doesn't match expected size: (%d, %d)\n", expected.x, expected.y); + + result = reinterpret_cast(m_smallIcon.GetHICON()); + break; + } + } + } + + // fallthrough + + default: + result = ::DefWindowProc(m_hwnd, message, wParam, lParam); + break; + } + + return result; +} + +void TaskbarWindow::_SendIconicThumbnail(int width, int height) +{ + static DwmSetIconicThumbnail_t pDwmSetIconicThumbnail = (DwmSetIconicThumbnail_t)wxDWMAPI.GetSymbol(wxT("DwmSetIconicThumbnail")); + if (!pDwmSetIconicThumbnail) + return; + + if (TabController* ctrl = controller()) { + HBITMAP hbitmap = 0; + + bool needsDelete = true; + if (!(hbitmap = static_cast(ctrl->GetIconicHBITMAP(this, width, height)))) { + needsDelete = false; + wxBitmap bitmap(ctrl->GetIconicBitmap(this, width, height)); + hbitmap = static_cast(bitmap.GetHBITMAP()); + } + + if (FAILED(pDwmSetIconicThumbnail(m_hwnd, hbitmap, 0))) + fprintf(stderr, "error calling DwmSetIconicThumbnail\n"); + + if (needsDelete) + ::DeleteObject(hbitmap); + } +} + +static void GetClientArea(HWND hwndTabFrame, RECT* rcClient); + +void TabWindow::_SendLivePreviewBitmap() +{ + static DwmSetIconicLivePreviewBitmap_t pDwmSetIconicLivePreviewBitmap = (DwmSetIconicLivePreviewBitmap_t)wxDWMAPI.GetSymbol(wxT("DwmSetIconicLivePreviewBitmap")); + if (!pDwmSetIconicLivePreviewBitmap) + return; + + if (TabController* ctrl = controller()) { + RECT r; + GetClientArea(m_notebook->hwnd(), &r); + wxRect clientRect; + wxCopyRECTToRect(r, clientRect); + wxBitmap bitmap(ctrl->GetLivePreview(this, clientRect)); + POINT p = {0, 0}; + if (FAILED(pDwmSetIconicLivePreviewBitmap(m_hwnd, static_cast(bitmap.GetHBITMAP()), &p, 0))) + fprintf(stderr, "error calling DwmSetIconicLivePreviewBitmap\n"); + } +} + +static void GetClientArea(HWND hwndTabFrame, RECT* rcClient) +{ + DWORD dwStyle = GetWindowLong(hwndTabFrame, GWL_STYLE); + DWORD dwStyleEx = GetWindowLong(hwndTabFrame, GWL_EXSTYLE); + + // Compute the actual size the thumbnail will occupy on-screen in order to + // render the live preview bitmap. We use the tab window outer frame window + // to compute this. In case that window is minimized, we use GetWindowPlacement + // to give the correct information. + RECT rcNCA = {}; + WINDOWPLACEMENT wp; + if (AdjustWindowRectEx(&rcNCA, dwStyle, FALSE, dwStyleEx) != 0 && + GetWindowPlacement(hwndTabFrame, &wp) != 0) + { + if (wp.flags & WPF_RESTORETOMAXIMIZED) + { + HMONITOR hmon = MonitorFromRect(&wp.rcNormalPosition, MONITOR_DEFAULTTONULL); + if (hmon) + { + MONITORINFO monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFO); + if (GetMonitorInfo(hmon, &monitorInfo)) + { + *rcClient = monitorInfo.rcWork; + } + } + } + else + { + CopyRect(rcClient, &wp.rcNormalPosition); + } + + rcClient->right -= (-rcNCA.left + rcNCA.right); + rcClient->bottom -= (-rcNCA.top + rcNCA.bottom); + } + +} + +static WCHAR const hiddenTabControlName[] = L"HiddenTabControlWindow"; +static WCHAR const hiddenParentName[] = L"HiddenTabControlParent"; + +HiddenTabControlWindow::HiddenTabControlWindow(const wxString& title, const wxIconBundle& bundle, TabController* controller /* = 0*/) + : TaskbarWindow(controller) + , m_bundle(bundle) +{ + static bool registered = false; + if (!registered) { + registered = true; + registerClass(HiddenTabControlWindowProc, hiddenTabControlName); + } + + ::CreateWindowEx( + 0x00000100, + hiddenTabControlName, + title.c_str(), + 0x1cc00000, + -32000, + -32000, + 10, + 10, + NULL, + NULL, + wxGetInstance(), + (LPVOID)this); +} + +void HiddenTabControlWindow::Destroy() +{ + if (m_hwnd) + ::DestroyWindow(m_hwnd); + + delete this; +} + +HiddenTabControlWindow::~HiddenTabControlWindow() +{} + + + +LRESULT HiddenTabControlWindow::WndProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + LRESULT result = 0; + switch (message) { + case WM_DWMSENDICONICTHUMBNAIL: + _SendIconicThumbnail(HIWORD(lParam), LOWORD(lParam)); + break; + + case WM_SYSCOMMAND: + if (wParam == SC_CLOSE) + if (TabController* ctrl = controller()) + ctrl->OnTabClosed(this); + + case WM_GETICON: { + wxSize size; + if (wParam == ICON_BIG) + size = wxSize(::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON)); + else + size = wxSize(::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON)); + + result = reinterpret_cast(m_bundle.GetIcon(size).GetHICON()); + break; + } + + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_ACTIVE) + if (TabController* ctrl = controller()) + ctrl->OnTabActivated(this); + break; + + default: + result = ::DefWindowProc(m_hwnd, message, wParam, lParam); + break; + } + return result; +} + +void HiddenTabControlWindow::Show(bool show /*=true*/) +{ + ::ShowWindow(m_hwnd, show ? SW_SHOWNOACTIVATE : SW_HIDE); +} + +void HiddenTabControlWindow::SetIconFile(const wxString& iconFilename) +{ + wxIconBundle bundle(iconFilename, wxBITMAP_TYPE_ICO); + _setIcon(hwnd(), bundle, SM_CXSMICON, SM_CYSMICON, ICON_SMALL); + _setIcon(hwnd(), bundle, SM_CXICON, SM_CYICON, ICON_BIG); +} + +static void setHiddenControlDwmAttribs(HWND hwnd) +{ + SetDwmBool(hwnd, DWMWA_HAS_ICONIC_BITMAP, true); + SetDwmBool(hwnd, DWMWA_FORCE_ICONIC_REPRESENTAITON, true); + SetDwmBool(hwnd, DWMWA_DISALLOW_PEEK, true); + + // Disable this window in Windows+Tab Aero Peek. + DWMFLIP3DWINDOWPOLICY flipPolicy = DWMFLIP3D_EXCLUDEBELOW; + MyDwmSetWindowAttribute(hwnd, DWMWA_FLIP3D_POLICY, &flipPolicy, sizeof(flipPolicy)); +} + +LRESULT CALLBACK HiddenTabControlWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HiddenTabControlWindow* wnd = reinterpret_cast(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + LRESULT result = 0; + if (!wnd && msg == WM_NCCREATE) { + LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam; + wnd = static_cast(lpcs->lpCreateParams); + wnd->m_hwnd = hwnd; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)wnd); + + setHiddenControlDwmAttribs(hwnd); + + result = ::DefWindowProc(hwnd, msg, wParam, lParam); + } else if (wnd) + result = wnd->WndProc(msg, wParam, lParam); + else + result = ::DefWindowProc(hwnd, msg, wParam, lParam); + + return result; +} + +static HBITMAP CreateDIBSection32(HDC hdc, int width, int height, void*& pb) +{ + BITMAPINFO bi; + ::ZeroMemory(&bi.bmiHeader, sizeof bi.bmiHeader); + bi.bmiHeader.biSize = sizeof bi.bmiHeader; + bi.bmiHeader.biWidth = width; + bi.bmiHeader.biHeight = -height; + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + HBITMAP hbitmap = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &pb, 0, 0); + if (hbitmap) + ::ZeroMemory(pb, 4 * width * height); + return hbitmap; +} + +typedef BOOL (WINAPI *AlphaBlend_t)(HDC,int,int,int,int, + HDC,int,int,int,int, + BLENDFUNCTION); + +static DLLLoader MSIMG32DLL(wxT("msimg32")); + +static const AlphaBlend_t getAlphaBlendFunc() +{ + static AlphaBlend_t + pfnAlphaBlend = (AlphaBlend_t)MSIMG32DLL.GetSymbol(_T("AlphaBlend")); + return pfnAlphaBlend; +} + +static HBRUSH brushFromColor(const wxColor& c) +{ + return ::CreateSolidBrush(RGB(c.Red(), c.Green(), c.Blue())); +} + +bool AlphaBlend(HDC hdc, int xoriginDest, int yoriginDest, int wDest, int hDest, + HDC hdcSrc, int xoriginSrc, int yoriginSrc, int wSrc, int hSrc) +{ + static BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + + if (AlphaBlend_t AlphaBlendFunc = getAlphaBlendFunc()) + return TRUE == AlphaBlendFunc(hdc, xoriginDest, yoriginDest, wDest, hDest, + hdcSrc, xoriginSrc, yoriginSrc, wSrc, hSrc, + bf); + + return false; +} + +static bool blitBitmap(HDC hdc, const wxBitmap& bitmap, int x, int y, int destWidth=-1, int destHeight=-1) +{ + HDC bitmapHdc = ::CreateCompatibleDC(hdc); + ::SelectObject(bitmapHdc, static_cast(bitmap.GetHBITMAP())); + + if (destWidth == -1) + destWidth = bitmap.GetWidth(); + if (destHeight == -1) + destHeight = bitmap.GetHeight(); + + bool result = AlphaBlend(hdc, x, y, destWidth, destHeight, + bitmapHdc, 0, 0, bitmap.GetWidth(), bitmap.GetHeight()); + + ::DeleteDC(bitmapHdc); + + return result; +} + +HBITMAP getBuddyPreview(const wxSize& size, const wxBitmap& icon, const wxBitmap& highlight) +{ + HDC hdc = ::CreateCompatibleDC(0); + void* bits; + + HBITMAP destBitmap = ::CreateDIBSection32(hdc, size.x, size.y, bits); + ::SelectObject(hdc, destBitmap); + + if (highlight.Ok()) + blitBitmap(hdc, highlight, 0, 0, size.x, size.y); + + if (icon.Ok()) + blitBitmap(hdc, icon, size.x/2-icon.GetWidth()/2, size.y/2-icon.GetHeight()/2); + + ::DeleteDC(hdc); + + return destBitmap; +} + +#endif // WIN7_TASKBAR + diff --git a/digsby/ext/src/win/WinTaskbar.h b/digsby/ext/src/win/WinTaskbar.h new file mode 100644 index 0000000..c9701fd --- /dev/null +++ b/digsby/ext/src/win/WinTaskbar.h @@ -0,0 +1,157 @@ +#ifndef __WINTASKBAR_H__ +#define __WINTASKBAR_H__ + +#define WIN7_TASKBAR + +#include + +#include "PlatformMessages.h" +#include + +#include +#include +#include +using std::map; +using std::vector; + +class TabWindow; +class TaskbarWindow; + +class TabController +{ +public: + TabController(); + virtual ~TabController(); + + virtual void* GetIconicHBITMAP(TaskbarWindow* window, int width, int height); + virtual wxBitmap GetIconicBitmap(TaskbarWindow* window, int width, int height); + virtual wxBitmap GetLivePreview(TabWindow* window, const wxRect& clientSize); + virtual wxIcon GetSmallIcon(TabWindow* window); + + virtual void OnTabActivated(TaskbarWindow* window); + virtual void OnTabClosed(TaskbarWindow* window); +}; + +class SimpleTabController : public TabController +{ +public: + SimpleTabController(); + virtual ~SimpleTabController(); + + virtual wxBitmap GetIconicBitmap(TaskbarWindow* window, int width, int height); + void SetIconicBitmap(const wxBitmap& bitmap); + +protected: + wxBitmap m_bitmap; +}; + +class TabNotebook : public wxEvtHandler +{ +public: + TabNotebook(wxWindow* window); + TabNotebook(HWND); + virtual ~TabNotebook(); + + TabWindow* CreateTab(wxWindow* window, TabController* controller = NULL); + bool DestroyTab(wxWindow*); + bool RearrangeTab(wxWindow* tabWindow, wxWindow* before); + + bool SetOverlayIcon(const wxBitmap& bitmap, const wxString& description = wxEmptyString); + bool SetOverlayIcon(const wxIcon& icon, const wxString& description = wxEmptyString); + bool SetOverlayIcon(HICON icon, const wxString& description = wxEmptyString); + + HWND GetTabHWND(wxWindow* window) const; + + bool SetTabTitle(wxWindow* window, const wxString& title); + bool SetTabActive(wxWindow* window); + bool SetTabIcon(wxWindow* window, const wxIconBundle& bundle); + bool SetTabIcon(wxWindow* window, const wxBitmap& bundle); + TabWindow* tabForWindow(wxWindow* window) const; + + bool SetProgressValue(unsigned long long completed, unsigned long long total); + bool SetProgressState(int flags); + + void RegisterTab(TabWindow*); + bool UnregisterTab(TabWindow*); + bool UnregisterTab(wxWindow* win); + + bool InvalidateThumbnails(wxWindow* window); + + bool initialized() const { return m_initialized; } + HWND hwnd() const { return m_hwnd; } + +protected: + void _InitFromHwnd(HWND hwnd); + + void Destroy(); + void OnWindowDestroyed(wxWindowDestroyEvent&); + + HWND m_hwnd; + bool m_initialized; + ITaskbarList4* m_taskbarList; + + typedef map TabMap; + TabMap m_tabMap; + + typedef vector TabVector; + TabVector m_tabs; + +private: + TabNotebook(const TabNotebook&); +}; + +class TaskbarWindow +{ +public: + TaskbarWindow(TabController* controller); + TabController* controller() const { return m_controller; } + virtual wxWindow* GetWindow() const { return NULL; } + HWND hwnd() const { return m_hwnd; } + +protected: + void _SendIconicThumbnail(int width, int height); + TabController* m_controller; + HWND m_hwnd; +}; + +class TabWindow : public TaskbarWindow +{ +public: + TabWindow(TabNotebook* notebook, wxWindow* win, TabController* controller = NULL); + virtual ~TabWindow(); + virtual wxWindow* GetWindow() const { return m_window; } + bool SetTitle(const wxString& title); + +protected: + friend LRESULT CALLBACK TabPreviewWndProc(HWND, UINT, WPARAM, LPARAM); + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam); + + void _SendLivePreviewBitmap(); + + TabNotebook* m_notebook; + wxWindow* m_window; + wxIcon m_smallIcon; +}; + +LRESULT CALLBACK HiddenTabControlWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +class HiddenTabControlWindow : public TaskbarWindow +{ +public: + HiddenTabControlWindow(const wxString& title, const wxIconBundle& bundle, TabController* controller = 0); + void Show(bool show=true); + void Hide() { Show(false); } + void Destroy(); + void SetIconFile(const wxString&); + +protected: + ~HiddenTabControlWindow(); + friend LRESULT CALLBACK HiddenTabControlWindowProc(HWND, UINT, WPARAM, LPARAM); + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam); + wxIconBundle m_bundle; +}; + +HBITMAP getBuddyPreview(const wxSize& size, const wxBitmap& icon, const wxBitmap& highlight); + +#endif // __WINTASKBAR_H__ + diff --git a/digsby/ext/src/win/WinUtils.cpp b/digsby/ext/src/win/WinUtils.cpp new file mode 100644 index 0000000..8e81caf --- /dev/null +++ b/digsby/ext/src/win/WinUtils.cpp @@ -0,0 +1,129 @@ +#include "WinUtils.h" + +#include +#include + +DLLLoader::DLLLoader(const wxChar *dllName) + : m_dllName(dllName) +{} + +// return the symbol with the given name or NULL if the DLL not loaded +// or symbol not present +void *DLLLoader::GetSymbol(const wxChar *name) +{ + // we're prepared to handle errors here + wxLogNull noLog; + + if (m_dllName) { + m_dll.Load(m_dllName); + + // reset the name whether we succeeded or failed so that we don't + // try again the next time + m_dllName = NULL; + } + + return m_dll.IsLoaded() ? m_dll.GetSymbol(name) : NULL; +} + +// vista only desktop window manager API +static DLLLoader wxDWMAPI(wxT("dwmapi")); + +typedef struct _MARGINS { + int cxLeftWidth; + int cxRightWidth; + int cyTopHeight; + int cyBottomHeight; +} MARGINS, *PMARGINS; + +typedef HRESULT (CALLBACK *DwmIsCompositionEnabled_t)(BOOL *); +typedef HRESULT (CALLBACK *DwmExtendFrameIntoClientArea_t)(HWND, PMARGINS); + +bool isGlassEnabled() +{ + static DwmIsCompositionEnabled_t + pfnDwmIsCompositionEnabled = (DwmIsCompositionEnabled_t)wxDWMAPI.GetSymbol(wxT("DwmIsCompositionEnabled")); + + if (pfnDwmIsCompositionEnabled) { + BOOL enabled; + HRESULT hr = pfnDwmIsCompositionEnabled(&enabled); + if (FAILED(hr)) + wxLogApiError(wxT("DwmIsCompositionEnabled"), hr); + else + return enabled == TRUE; + } + + return false; +} + +bool glassExtendInto(wxWindow* win, int left, int right, int top, int bottom) +{ + static DwmExtendFrameIntoClientArea_t + pfnDwmExtendFrameIntoClientArea = (DwmExtendFrameIntoClientArea_t)wxDWMAPI.GetSymbol(wxT("DwmExtendFrameIntoClientArea")); + + if (pfnDwmExtendFrameIntoClientArea) { + MARGINS margins = {left, right, top, bottom}; + HWND hwnd = (HWND)win->GetHWND(); + HRESULT hr = pfnDwmExtendFrameIntoClientArea(hwnd, &margins); + if (FAILED(hr)) + wxLogApiError(wxT("DwmExtendFrameIntoClientArea"), hr); + else + return true; + } + + return false; +} + + +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in user addr space) + DWORD dwThreadID; // thread ID (-1=caller thread) + DWORD dwFlags; // reserved for future use, must be zero +} THREADNAME_INFO; + +// +// sets the thread name; visible in the debugger. if dwThreadId is 0, +// sets the current thread name +// +void setThreadName(unsigned long dwThreadID, const wxString& threadName) +{ + if (dwThreadID == 0) + dwThreadID = ::GetCurrentThreadId(); + + THREADNAME_INFO info; + info.dwType = 0x1000; + wxCharBuffer buf(threadName.ToAscii()); + info.szName = buf.data(); + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + // secret MSDN voodoo to set thread names: + // http://msdn.microsoft.com/en-us/library/xcb2z8hs(vs.71).aspx + __try { + ::RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info ); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + } +} + +// clears the windows console +void cls() +{ + HANDLE hConsole; + if (!(hConsole = GetStdHandle(STD_OUTPUT_HANDLE))) + return; + + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) + return; + + COORD coordScreen = {0, 0}; + DWORD dwConSize = csbi.dwSize.X * csbi.dwSize.Y; + DWORD cCharsWritten; + FillConsoleOutputCharacter(hConsole, (TCHAR) ' ', dwConSize, coordScreen, &cCharsWritten); + GetConsoleScreenBufferInfo(hConsole, &csbi); + FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten); + SetConsoleCursorPosition(hConsole, coordScreen); +} diff --git a/digsby/ext/src/win/WinUtils.h b/digsby/ext/src/win/WinUtils.h new file mode 100644 index 0000000..1b92272 --- /dev/null +++ b/digsby/ext/src/win/WinUtils.h @@ -0,0 +1,33 @@ +#ifndef __CGUI_WINUTIL_H_ +#define __CGUI_WINUTIL_H_ + +#include + +// +// loads a DLL only once, even if there's an error +// +// example: +// static wxOnceOnlyDLLLoader wxGDI32DLL(_T("gdi32")); +// +class DLLLoader +{ + // stolen from src/msw/dc.cpp + +public: + // ctor argument must be a literal string as we don't make a copy of it! + DLLLoader(const wxChar *dllName); + void *GetSymbol(const wxChar *name); + +private: + wxDynamicLibrary m_dll; + const wxChar *m_dllName; +}; + +bool glassExtendInto(wxWindow* win, int left = -1, int right =-1, int top = -1, int bottom = -1); +bool isGlassEnabled(); + +void setThreadName(unsigned long dwThreadID, const wxString& threadName); + +void cls(); + +#endif // __CGUI_WINUTIL_H_ diff --git a/digsby/ext/src/win/WindowSnapperWin.cpp b/digsby/ext/src/win/WindowSnapperWin.cpp new file mode 100644 index 0000000..3de26e6 --- /dev/null +++ b/digsby/ext/src/win/WindowSnapperWin.cpp @@ -0,0 +1,119 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include "WindowSnapper.h" +#include "cwindowfx.h" + +static bool GetMonitorClientArea(HWND hwnd, RECT* rect) +{ + HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + + MONITORINFO mInfo = { sizeof(MONITORINFO), 0, 0, 0 }; + + if (!GetMonitorInfo(hMonitor, &mInfo)) + return false; + + *rect = mInfo.rcWork; + return true; +} + +// returns the client area of the monitor that the specified window is on. +wxRect GetMonitorClientArea(wxWindow* win) +{ + RECT rcWork; + if (!GetMonitorClientArea(static_cast(win->GetHWND()), &rcWork)) + return wxRect(); + + return wxRect(rcWork.left, rcWork.top, + rcWork.right - rcWork.left, + rcWork.bottom - rcWork.top); +} + +// in charge of platform specific setup +void WindowSnapper::PlatformSetEnabled(bool enable) +{ + if (!binder) + if (!enable) + return; + else + binder = PlatformMessageBinder::ForWindow(win); + + if (enable) { + binder->BindNative(WM_DESTROY, this); + binder->BindNative(WM_MOVING, this); + binder->BindNative(WM_SIZING, this); + binder->BindNative(WM_ENTERSIZEMOVE, this); + } else { + binder->UnbindNative(WM_DESTROY, this); + binder->UnbindNative(WM_MOVING, this); + binder->UnbindNative(WM_SIZING, this); + binder->UnbindNative(WM_ENTERSIZEMOVE, this); + } +} + +static bool aeroSnapped(HWND hwnd) +{ + // No Windows 7, no Aero Snap. + if (!isWin7OrHigher()) + return false; + + // Win7's "Aero Snap" feature sends WM_ENTERSIZEMOVE while maximized-- + // don't snap to that position. + if (::IsZoomed(hwnd)) + return true; + + // Otherwise we might be snapped to the edge of a monitor. + RECT rect, monitor; + if (::GetWindowRect(hwnd, &rect) && GetMonitorClientArea(hwnd, &monitor)) { + if (rect.top == monitor.top && + rect.bottom == monitor.bottom) + return true; + } + + return false; +} + +// responds to Win32 messages, calling HandleMoveStart and HandleMoveOrSize as necessary +LRESULT WindowSnapper::handleMessage(HWND hwnd, UINT message, WPARAM /* wParam */, LPARAM lParam) +{ + switch (message) { + case WM_ENTERSIZEMOVE: + if (m_docked || !aeroSnapped(hwnd)) + HandleMoveStart(); + else + capturePositionOnNextMove = true; + break; + case WM_SIZING: + case WM_MOVING: { + RECT* r = (LPRECT)lParam; + wxRect rect(r->left, r->top, r->right - r->left, r->bottom - r->top); + + // allow HandleMoveOrSize to modify the rectangle + HandleMoveOrSize(rect, message == WM_SIZING ? Sizing : Moving); + + // now copy the necessary values back into the RECT at lParam + r->left = rect.x; + r->top = rect.y; + + if (message == WM_MOVING) { + // when moving the window, always maintain the same size + if (cs.x > 0 || cs.y > 0) { + r->right = rect.x + cs.x; + r->bottom = rect.y + cs.y; + } + } else { + // when sizing the window, use the new size given in rect + r->right = rect.GetRight() + 1; + r->bottom = rect.GetBottom() + 1; + } + break; + } + case WM_DESTROY: + OnWindowDestroyed(); + break; + } + + return 0; +} + diff --git a/digsby/ext/src/xmlextra/Copyright-libxml2 b/digsby/ext/src/xmlextra/Copyright-libxml2 new file mode 100644 index 0000000..cef4843 --- /dev/null +++ b/digsby/ext/src/xmlextra/Copyright-libxml2 @@ -0,0 +1,30 @@ +Below is the original copyright notice from libxml2. Files in this directory +are highly-modified parts of libxml2 library and its python bindings. + + +Except where otherwise noted in the source code (trio files, hash.c and list.c) +covered by a similar licence but with different Copyright notices: + + Copyright (C) 1998-2002 Daniel Veillard. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- +NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Daniel Veillard shall not +be used in advertising or otherwise to promote the sale, use or other deal- +ings in this Software without prior written authorization from him. + diff --git a/digsby/ext/src/xmlextra/xmlextra.c b/digsby/ext/src/xmlextra/xmlextra.c new file mode 100644 index 0000000..e8008eb --- /dev/null +++ b/digsby/ext/src/xmlextra/xmlextra.c @@ -0,0 +1,1513 @@ +#include +#include +#include +#include +#include +#include + +static PyObject *MyError; + +/* + * Code borrowed from libxml2 python bindings + * Copyright (C) 1998-2002 Daniel Veillard. All Rights Reserved. + * (see Copyright-libxml2 for copyright details) + */ + +#define PyxmlNode_Get(v) (((v) == Py_None) ? NULL : \ + (((PyxmlNode_Object *)(v))->obj)) + +typedef struct { + PyObject_HEAD + xmlNodePtr obj; +} PyxmlNode_Object; + +PyObject * libxml_xmlDocPtrWrap(xmlDocPtr doc) { + PyObject *ret; + +#ifdef DEBUG + printf("libxml_xmlDocPtrWrap: doc = %p\n", doc); +#endif + if (doc == NULL) { + Py_INCREF(Py_None); + return (Py_None); + } + /* TODO: look at deallocation */ + ret = + PyCObject_FromVoidPtrAndDesc((void *) doc, (char *) "xmlDocPtr", + NULL); + return (ret); +} + +PyObject * libxml_xmlNodePtrWrap(xmlNodePtr node) { + PyObject *ret; + +#ifdef DEBUG + printf("libxml_xmlNodePtrWrap: node = %p\n", node); +#endif + if (node == NULL) { + Py_INCREF(Py_None); + return (Py_None); + } + ret = + PyCObject_FromVoidPtrAndDesc((void *) node, (char *) "xmlNodePtr", + NULL); + return (ret); +} + +/* + * End of code borrowed from libxml2 + */ + +/* Tree manipulation functions */ + +static PyObject * remove_ns(ATTRIBUTE_UNUSED PyObject *self, PyObject *args) { +PyObject *pyobj_tree,*pyobj_ns; +xmlNsPtr nsDef,prev; +xmlNodePtr node; +xmlNodePtr declNode = NULL; +xmlAttrPtr attr; +xmlNodePtr tree; +xmlNsPtr ns; + + if (!PyArg_ParseTuple(args, "OO", &pyobj_tree,&pyobj_ns)) return NULL; + tree = (xmlNodePtr) PyxmlNode_Get(pyobj_tree); + ns = (xmlNsPtr) PyxmlNode_Get(pyobj_ns); + node = tree; + + if (ns == NULL) { + PyErr_SetString(MyError,"remove_ns: NULL namespace"); + return NULL; + } + + while (node != NULL) { + /* + * Check if the namespace is in use by the node + */ + if (node->ns == ns) { + PyErr_SetString(MyError,"remove_ns: NULL namespace"); + return NULL; + } + + /* + * now check for namespace hold by attributes on the node. + */ + attr = node->properties; + while (attr != NULL) { + if (attr->ns == ns) { + PyErr_SetString(MyError,"remove_ns: NULL namespace"); + return NULL; + } + attr = attr->next; + } + + /* + * Check if the namespace is declared in the node + */ + nsDef=node->nsDef; + while(nsDef != NULL) { + if (nsDef == ns) { + declNode = node; + break; + } + nsDef=nsDef->next; + } + + /* + * Browse the full subtree, deep first + */ + if (node->children != NULL) { + /* deep first */ + node = node->children; + } else if ((node != tree) && (node->next != NULL)) { + /* then siblings */ + node = node->next; + } else if (node != tree) { + /* go up to parents->next if needed */ + while (node != tree) { + if (node->parent != NULL) + node = node->parent; + if ((node != tree) && (node->next != NULL)) { + node = node->next; + break; + } + if (node->parent == NULL) { + node = NULL; + break; + } + } + /* exit condition */ + if (node == tree) node = NULL; + } else break; + if (node == tree) break; /* should not happen... but happens somehow */ + } + + /* there is no such namespace declared here */ + if (declNode == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + + prev=NULL; + nsDef=declNode->nsDef; + while(nsDef != NULL) { + if (nsDef == ns) { + if (prev == NULL) declNode->nsDef=nsDef->next; + else prev->next=nsDef->next; + xmlFreeNs(ns); + break; + } + prev=nsDef; + nsDef=nsDef->next; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * replace_ns(ATTRIBUTE_UNUSED PyObject *self, PyObject *args) { +PyObject *pyobj_tree,*pyobj_old_ns,*pyobj_new_ns; +xmlNodePtr tree,node; +xmlAttrPtr attr; +xmlNsPtr new_ns,old_ns; +xmlNsPtr nsDef; + + if (!PyArg_ParseTuple(args, "OOO", &pyobj_tree,&pyobj_old_ns,&pyobj_new_ns)) return NULL; + tree = (xmlNodePtr) PyxmlNode_Get(pyobj_tree); + old_ns = (xmlNsPtr) PyxmlNode_Get(pyobj_old_ns); + new_ns = (xmlNsPtr) PyxmlNode_Get(pyobj_new_ns); + node = tree; + + while (node != NULL) { + + /* + * If old_ns is None and default namespace is redefined here, then skip this node and its children. + */ + if (old_ns == NULL) { + nsDef=node->nsDef; + while(nsDef != NULL) { + if (nsDef->prefix == NULL) break; + nsDef=nsDef->next; + } + if (nsDef != NULL) { + node = node->next; + continue; + } + } + + /* + * Check if the namespace is in use by the node + */ + if (node->ns == old_ns) { + node->ns = new_ns; + } + + /* + * now check for namespace hold by attributes on the node. + */ + attr = node->properties; + while (attr != NULL) { + if (attr->ns == old_ns) { + node->ns = new_ns; + } + attr = attr->next; + } + + /* + * Browse the full subtree, deep first + */ + if (node->children != NULL) { + /* deep first */ + node = node->children; + } else if ((node != tree) && (node->next != NULL)) { + /* then siblings */ + node = node->next; + } else if (node != tree) { + /* go up to parents->next if needed */ + while (node != tree) { + if (node->parent != NULL) node = node->parent; + if ((node != tree) && (node->next != NULL)) { + node = node->next; + break; + } + if (node->parent == NULL) { + node = NULL; + break; + } + } + /* exit condition */ + if (node == tree) node = NULL; + } else break; + if (node == tree) break; /* should not happen... but happens somehow */ + } + + Py_INCREF(Py_None); + return Py_None; +} + +/* + * Stream reader functions + */ + +#if 1 +/* + * SAX-based stream reader + */ + +staticforward PyTypeObject SaxReaderType; + +typedef struct _sax_reader{ + PyObject_HEAD + + xmlParserCtxtPtr ctxt; + xmlSAXHandler sax; + + startElementSAXFunc startElement; + endElementSAXFunc endElement; + charactersSAXFunc characters; + cdataBlockSAXFunc cdataBlock; + processingInstructionSAXFunc processingInstruction; + + errorSAXFunc error; + fatalErrorSAXFunc fatalError; + + warningSAXFunc warning; + + PyObject *handler; + + int eof; + int exception; +}SaxReaderObject; + +void myStartElement(void *ctx,const xmlChar *name,const xmlChar **atts){ +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; +PyObject *obj; + + reader->startElement(ctx,name,atts); + if (ctxt->nodeNr==1){ + obj=PyObject_CallMethod(reader->handler,"_stream_start","O", + libxml_xmlDocPtrWrap(ctxt->myDoc)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); + } + else if (ctxt->nodeNr==2){ + /*obj=PyObject_CallMethod(reader->handler,"_stanza_start","OO", + libxml_xmlDocPtrWrap(ctxt->myDoc), + libxml_xmlNodePtrWrap(ctxt->node)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj);*/ + } +} + +void myEndElement(void *ctx,const xmlChar *name){ +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; +PyObject *obj; +xmlNodePtr node; + + node=ctxt->node; + reader->endElement(ctx,name); + if (ctxt->nodeNr==0){ + reader->eof=1; + obj=PyObject_CallMethod(reader->handler,"_stream_end","O", + libxml_xmlDocPtrWrap(ctxt->myDoc)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); + } + else if (ctxt->nodeNr==1 && node){ + obj=PyObject_CallMethod(reader->handler,"_stanza","OO", + libxml_xmlDocPtrWrap(ctxt->myDoc), + libxml_xmlNodePtrWrap(node)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); + xmlUnlinkNode(node); + xmlFreeNode(node); + } +} + +void myCharacters(void *ctx,const xmlChar *ch,int len){ +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; + + if (ctxt->nodeNr>1){ + reader->characters(ctx,ch,len); + } +} + +void myCdataBlock(void *ctx,const xmlChar *value,int len){ +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; + + if (ctxt->nodeNr>1){ + reader->cdataBlock(ctx,value,len); + } +} + +void myProcessingInstruction(void *ctx,const xmlChar *target,const xmlChar *data){ +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; + + if (ctxt->nodeNr==0){ + reader->processingInstruction(ctx,target,data); + } +} + +static void myError(void *ctx, const char *msg, ...){ +va_list vargs; +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; +PyObject *str,*obj; + + va_start (vargs, msg); + str=PyString_FromFormatV(msg,vargs); + va_end (vargs); + if (str==NULL) { + reader->exception=1; + return; + } + obj=PyObject_CallMethod(reader->handler,"error","O",str); + Py_DECREF(str); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); +} + +static void myFatalError(void *ctx, const char *msg, ...){ +va_list vargs; +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; +PyObject *str,*obj; + + va_start (vargs, msg); + str=PyString_FromFormatV(msg,vargs); + va_end (vargs); + if (str==NULL) { + reader->exception=1; + return; + } + obj=PyObject_CallMethod(reader->handler,"error","O",str); + Py_DECREF(str); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); +} + +static void myWarning(void *ctx, const char *msg, ...){ +va_list vargs; +xmlParserCtxtPtr ctxt=(xmlParserCtxtPtr) ctx; +SaxReaderObject *reader=(SaxReaderObject *)ctxt->_private; +PyObject *str,*obj; + + va_start (vargs, msg); + str=PyString_FromFormatV(msg,vargs); + va_end (vargs); + if (str==NULL) { + reader->exception=1; + return; + } + obj=PyObject_CallMethod(reader->handler,"warning","O",str); + Py_DECREF(str); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); +} + +static PyObject * sax_reader_new(ATTRIBUTE_UNUSED PyObject *self, PyObject *args) { +SaxReaderObject *reader; +PyObject *handler; + + if (!PyArg_ParseTuple(args, "O", &handler)) return NULL; + + reader=PyObject_New(SaxReaderObject,&SaxReaderType); + if (reader==NULL) return NULL; + + memcpy(&reader->sax,&xmlDefaultSAXHandler,sizeof(xmlSAXHandler)); + + /* custom handlers */ + reader->startElement=reader->sax.startElement; + reader->sax.startElement=myStartElement; + reader->endElement=reader->sax.endElement; + reader->sax.endElement=myEndElement; + reader->error=reader->sax.error; + reader->sax.error=myError; + reader->fatalError=reader->sax.fatalError; + reader->sax.fatalError=myFatalError; + reader->warning=reader->sax.warning; + reader->sax.warning=myWarning; + + /* things processed only at specific levels */ + reader->characters=reader->sax.characters; + reader->sax.characters=myCharacters; + reader->cdataBlock=reader->sax.cdataBlock; + reader->sax.cdataBlock=myCdataBlock; + reader->processingInstruction=reader->sax.processingInstruction; + reader->sax.processingInstruction=myProcessingInstruction; + + /* unused in XMPP */ + reader->sax.resolveEntity=NULL; + reader->sax.getEntity=NULL; + reader->sax.entityDecl=NULL; + reader->sax.notationDecl=NULL; + reader->sax.attributeDecl=NULL; + reader->sax.elementDecl=NULL; + reader->sax.unparsedEntityDecl=NULL; + reader->sax.comment=NULL; + reader->sax.externalSubset=NULL; + + reader->eof=0; + reader->exception=0; + reader->handler=handler; + Py_INCREF(handler); + + reader->ctxt=xmlCreatePushParserCtxt(&reader->sax,NULL,"",0,"test.xml"); + reader->ctxt->_private=reader; + + return (PyObject *)reader; +} + +static void sax_reader_free(PyObject *self) { +SaxReaderObject *reader=(SaxReaderObject *)self; + + xmlFreeDoc(reader->ctxt->myDoc); + reader->ctxt->myDoc = NULL; + xmlFreeParserCtxt(reader->ctxt); + Py_DECREF(reader->handler); + PyObject_Del(self); +} + +static PyObject * sax_reader_feed(PyObject *self, PyObject *args) { +SaxReaderObject *reader=(SaxReaderObject *)self; +char *str; +int len; +int ret; + + if (!PyArg_ParseTuple(args, "s#", &str, &len)) return NULL; + + reader->exception=0; + + ret=xmlParseChunk(reader->ctxt,str,len,len==0); + + if (reader->exception) return NULL; + + if (ret==0){ + return Py_BuildValue("i", 0); + /* Py_INCREF(Py_None); + return Py_None; */ + } + + PyErr_Format(MyError,"Parser error #%d.",ret); + return NULL; +} + +static PyObject * sax_reader_doc(PyObject *self, PyObject *args) { + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef sax_reader_methods[] = { + {(char *)"feed", sax_reader_feed, METH_VARARGS, NULL}, + {(char *)"doc", sax_reader_doc, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyObject * sax_reader_getattr(PyObject *obj, char *name) { +SaxReaderObject *reader=(SaxReaderObject *)obj; + + return Py_FindMethod(sax_reader_methods, (PyObject *)reader, name); +} + +static int sax_reader_setattr(PyObject *obj, char *name, PyObject *v) { + (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name); + return -1; +} + +static PyTypeObject SaxReaderType = { + PyObject_HEAD_INIT(NULL) + 0, + "_Reader", + sizeof(SaxReaderObject), + 0, + sax_reader_free, /*tp_dealloc*/ + 0, /*tp_print*/ + sax_reader_getattr, /*tp_getattr*/ + sax_reader_setattr, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; +#else + +/* + * Preparsing stream reader + */ + +staticforward PyTypeObject PreparsingReaderType; + +/* stream lexical state */ +typedef enum { + LS_OUTER=0, /* outside of a tag */ + + LS_TSTART, /* start tag/end tag/comment/pi/CDATA start */ + + /* basic start tag content */ + LS_TCONT, /* tag content */ + LS_TSLASH, /* slash in tag (may be empty element) */ + LS_TQVAL, /* quoted value in a tag content */ + + /* CDATA in a start tag tag */ + LS_TCDATASTART1, /* '<' */ + LS_TCDATASTART2, /* 'depth=0; + reader->buffer=PyMem_New(char,1024); + if (reader->buffer==NULL) return NULL; + reader->lex_state=LS_OUTER; + reader->buffer_len=1024; + reader->buffer_pos=0; + reader->buffer_end=0; + reader->document_start=NULL; + reader->document_start_len=0; + reader->current_stanza=NULL; + reader->current_stanza_len=0; + reader->current_stanza_end=0; + reader->doc=NULL; + reader->eof=0; + reader->exception=0; + reader->handler=handler; + Py_INCREF(handler); + + return (PyObject *)reader; +} + +static void preparsing_reader_clear(PreparsingReaderObject *reader) { + + PyMem_Del(reader->buffer); + reader->buffer=NULL; + PyMem_Del(reader->document_start); + reader->document_start=NULL; + PyMem_Del(reader->current_stanza); + if (reader->doc) { + xmlFreeDoc(reader->doc); + reader->doc=NULL; + } + reader->current_stanza=NULL; + reader->depth=0; + reader->lex_state=LS_OUTER; + reader->buffer_len=0; + reader->buffer_pos=0; + reader->buffer_end=0; + reader->document_start=NULL; + reader->document_start_len=0; + reader->current_stanza=NULL; + reader->current_stanza_len=0; + reader->current_stanza_end=0; + reader->doc=NULL; + reader->eof=0; + reader->exception=0; +} + +static void preparsing_reader_free(PyObject *self) { +PreparsingReaderObject *reader=(PreparsingReaderObject *)self; + + preparsing_reader_clear(reader); + Py_DECREF(reader->handler); + PyObject_Del(self); +} + +static MarkupType parse_markup(PreparsingReaderObject *reader,size_t *len){ +LexicalState new_state; +MarkupType current_markup; +char c; + + current_markup=MT_NONE_YET; + while(reader->buffer_posbuffer_end && current_markup==MT_NONE_YET) { + c=reader->buffer[reader->buffer_pos++]; +#ifdef STREAM_DEBUG + putc(c,stdout); +#endif + new_state=reader->lex_state; + switch(reader->lex_state) { + case LS_OUTER: + if (c=='<') { + new_state=LS_TSTART; + if (reader->buffer_pos>1) { + current_markup=MT_CDATA; + *len=reader->buffer_pos-1; + } + } + break; + case LS_TSTART: + switch(c){ + case '/': + new_state=LS_ETCONT; + break; + case '?': + new_state=LS_PI; + break; + case '!': + new_state=LS_DSTART; + break; + case '>': + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + default: + new_state=LS_TCONT; + break; + } + break; + case LS_TCONT: + switch(c){ + case '/': + new_state=LS_TSLASH; + break; + case '>': + new_state=LS_OUTER; + current_markup=MT_START_TAG; + *len=reader->buffer_pos; + break; + case '"': + case '\'': + new_state=LS_TQVAL; + reader->lex_quote=c; + break; + default: + break; + } + break; + case LS_TSLASH: + switch(c){ + case '>': + new_state=LS_OUTER; + current_markup=MT_EMPTY_ELEMENT; + *len=reader->buffer_pos; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TQVAL: + if (c==reader->lex_quote) + new_state=LS_TCONT; + else if (c=='<') + new_state=LS_TCDATASTART1; + break; + case LS_TCDATASTART1: + switch(c){ + case '!': + new_state=LS_TCDATASTART2; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATASTART2: + switch(c){ + case '[': + new_state=LS_TCDATASTART3; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATASTART3: + switch(c){ + case 'C': + new_state=LS_TCDATASTART4; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATASTART4: + switch(c){ + case 'D': + new_state=LS_TCDATASTART5; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATASTART5: + switch(c){ + case 'A': + new_state=LS_TCDATASTART6; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATASTART6: + switch(c){ + case 'T': + new_state=LS_TCDATASTART7; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATASTART7: + switch(c){ + case 'A': + new_state=LS_TCDATASTART8; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATASTART8: + switch(c){ + case '[': + new_state=LS_TCDATA; + break; + default: + new_state=LS_TQVAL; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_TCDATA: + switch(c){ + case ']': + new_state=LS_TCDATAEND1; + break; + default: + break; + } + break; + case LS_TCDATAEND1: + switch(c){ + case ']': + new_state=LS_TCDATAEND2; + break; + default: + new_state=LS_TCDATA; + break; + } + break; + case LS_TCDATAEND2: + switch(c){ + case '!': + new_state=LS_TCDATAEND3; + break; + default: + new_state=LS_TCDATA; + break; + } + break; + case LS_TCDATAEND3: + switch(c){ + case '>': + new_state=LS_TQVAL; + break; + default: + new_state=LS_TCDATA; + break; + } + break; + case LS_ETCONT: + switch(c){ + case '>': + new_state=LS_OUTER; + current_markup=MT_END_TAG; + *len=reader->buffer_pos; + break; + default: + break; + } + break; + case LS_PI: + switch(c){ + case '?': + new_state=LS_PIEND; + break; + default: + break; + } + break; + case LS_PIEND: + switch(c){ + case '>': + new_state=LS_OUTER; + current_markup=MT_IGNORE; + *len=reader->buffer_pos; + break; + default: + new_state=LS_PI; + break; + } + break; + case LS_DSTART: + switch(c){ + case '-': + new_state=LS_CSTART; + break; + case '[': + new_state=LS_CDATASTART3; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CSTART: + switch(c){ + case '-': + new_state=LS_COMMENT; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_COMMENT: + switch(c){ + case '-': + new_state=LS_COMMENTEND1; + break; + default: + break; + } + break; + case LS_COMMENTEND1: + switch(c){ + case '-': + new_state=LS_COMMENTEND2; + break; + default: + new_state=LS_COMMENT; + break; + } + break; + case LS_COMMENTEND2: + switch(c){ + case '>': + new_state=LS_OUTER; + current_markup=MT_IGNORE; + *len=reader->buffer_pos; + break; + default: + new_state=LS_COMMENT; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CDATASTART3: + switch(c){ + case 'C': + new_state=LS_CDATASTART4; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CDATASTART4: + switch(c){ + case 'D': + new_state=LS_CDATASTART5; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CDATASTART5: + switch(c){ + case 'A': + new_state=LS_CDATASTART6; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CDATASTART6: + switch(c){ + case 'T': + new_state=LS_CDATASTART7; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CDATASTART7: + switch(c){ + case 'A': + new_state=LS_CDATASTART8; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CDATASTART8: + switch(c){ + case '[': + new_state=LS_CDATA; + break; + default: + new_state=LS_OUTER; + current_markup=MT_ERROR; + *len=reader->buffer_pos; + break; + } + break; + case LS_CDATA: + switch(c){ + case ']': + new_state=LS_CDATAEND1; + break; + default: + break; + } + break; + case LS_CDATAEND1: + switch(c){ + case ']': + new_state=LS_CDATAEND2; + break; + default: + new_state=LS_CDATA; + break; + } + break; + case LS_CDATAEND2: + switch(c){ + case '!': + new_state=LS_CDATAEND3; + break; + default: + new_state=LS_CDATA; + break; + } + break; + case LS_CDATAEND3: + switch(c){ + case '>': + new_state=LS_OUTER; + break; + default: + new_state=LS_CDATA; + break; + } + break; + } +#ifdef STREAM_DEBUG + if (new_state!=reader->lex_state) { + printf("(new state: %i)",(int)new_state); + } +#endif + reader->lex_state=new_state; + } + return current_markup; +} + +static xmlDocPtr parse_fragment(PreparsingReaderObject *reader,const char *f,size_t flen){ +char *buf; +char c; +int i; +xmlDocPtr doc; + + buf=PyMem_New(char,reader->document_start_len*2+flen+1); + if (buf==NULL) return NULL; + memcpy(buf,reader->document_start,reader->document_start_len); + memcpy(buf+reader->document_start_len,f,flen); + buf[reader->document_start_len+flen]='<'; + buf[reader->document_start_len+flen+1]='/'; + for(i=1;idocument_start_len;i++){ + c=reader->document_start[i]; + if (c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='>') break; + buf[reader->document_start_len+flen+i+1]=c; + } + buf[reader->document_start_len+flen+i+1]='>'; + doc=xmlParseMemory(buf,reader->document_start_len+flen+i+2); + PyMem_Del(buf); + return doc; +} + +static int stream_start(PreparsingReaderObject *reader,MarkupType mtype,int len){ +PyObject *obj; + + if (reader->doc) xmlFreeDoc(reader->doc); + PyMem_Del(reader->document_start); + if (mtype==MT_START_TAG){ + reader->document_start=PyMem_New(char,len); + if (reader->document_start) { + memcpy(reader->document_start,reader->buffer,len); + reader->document_start_len=len; + reader->doc=parse_fragment(reader,"",0); + } + else reader->doc=NULL; + } + else { + reader->document_start=NULL; + reader->doc=xmlParseMemory(reader->buffer,len); + } + if (reader->doc==NULL) { + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"XML not well-formed."); + return -1; + } + obj=PyObject_CallMethod(reader->handler,"_stream_start","O", + libxml_xmlDocPtrWrap(reader->doc)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); + if (mtype==MT_EMPTY_ELEMENT){ + obj=PyObject_CallMethod(reader->handler,"_stream_end","O", + libxml_xmlDocPtrWrap(reader->doc)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); + reader->eof=1; + } + reader->current_stanza_end=0; + return 0; +} + +static int stream_end(PreparsingReaderObject *reader,int len){ +PyObject *obj; +char *buf; + + if (reader->doc) xmlFreeDoc(reader->doc); + reader->doc=NULL; + buf=PyMem_New(char,len+reader->document_start_len); + if (buf==NULL) { + PyErr_Format(MyError,"Out of memory? Couldn't allocate %d bytes in stream_end()",(int)(len+reader->document_start_len)); + return -1; + } + memcpy(buf,reader->document_start,reader->document_start_len); + memcpy(buf+reader->document_start_len,reader->buffer,len); + reader->doc=xmlParseMemory(buf,len+reader->document_start_len); + if (reader->doc==NULL) { + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"XML not well-formed."); + return -1; + } + obj=PyObject_CallMethod(reader->handler,"_stream_end","O", + libxml_xmlDocPtrWrap(reader->doc)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); + reader->eof=1; + return 0; +} + + +static int append_to_current_stanza(PreparsingReaderObject *reader,int len){ + + if (reader->current_stanza_end+len>reader->current_stanza_len) { + reader->current_stanza_len+=(len/1024+1)*1024; + reader->current_stanza=PyMem_Resize(reader->current_stanza,char, + reader->current_stanza_len); + if (reader->current_stanza==NULL) { + preparsing_reader_clear(reader); + PyErr_Format(MyError,"Out of memory? Couldn't allocate %d bytes in append_to_current_stanza()",(int)reader->current_stanza_len); + return -1; + } + } + memcpy(reader->current_stanza+reader->current_stanza_end,reader->buffer,len); + reader->current_stanza_end+=len; + return 0; +} + +static int process_stanza(PreparsingReaderObject *reader){ +PyObject *obj; +xmlDocPtr doc; +xmlNodePtr node; +xmlNodePtr stanza; + + doc=parse_fragment(reader,reader->current_stanza,reader->current_stanza_end); + if (doc==NULL) { + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"XML not well-formed."); + return -1; + } + + node=xmlDocGetRootElement(doc); + node=node->children; + while(node!=NULL && node->type!=XML_ELEMENT_NODE) node=node->next; + + if (node==NULL) { + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"Unexpected XML stream parsing error."); + return -1; + } + + stanza=xmlDocCopyNode(node,reader->doc,1); + + xmlAddChild(xmlDocGetRootElement(reader->doc),stanza); + xmlFreeDoc(doc); + + obj=PyObject_CallMethod(reader->handler,"_stanza","OO", + libxml_xmlDocPtrWrap(reader->doc),libxml_xmlNodePtrWrap(stanza)); + if (obj==NULL) reader->exception=1; + else Py_DECREF(obj); + + xmlUnlinkNode(stanza); + xmlFreeNode(stanza); + + reader->current_stanza_end=0; + return 0; +} + + +static PyObject * preparsing_reader_feed(PyObject *self, PyObject *args) { +PreparsingReaderObject *reader=(PreparsingReaderObject *)self; +char *str; +size_t len; +int tmp_len; +MarkupType mtype; +int depth_change; +size_t i; + + if (reader->eof){ + Py_INCREF(Py_None); + return Py_None; + } + + if (!PyArg_ParseTuple(args, "s#", &str, &tmp_len)) return NULL; + len=(size_t)tmp_len; + reader->exception=0; + + if (reader->buffer_end+len>reader->buffer_len) { + reader->buffer_len+=(len/1024+1)*1024; + reader->buffer=PyMem_Resize(reader->buffer,char,reader->buffer_len); + if (reader->buffer==NULL) { + /* out of memory */ + preparsing_reader_clear(reader); + PyErr_Format(MyError,"Out of memory? Couldn't allocate %d bytes in preparsing_reader_feed()",(int)reader->buffer_len); + return NULL; + } + } + memcpy(reader->buffer+reader->buffer_end,str,len); + reader->buffer_end+=len; + + if (reader->buffer_pos==reader->buffer_end){ + reader->eof=1; + Py_INCREF(Py_None); + return Py_None; + } + + while(reader->buffer_posbuffer_end){ + mtype=parse_markup(reader,&len); + if (mtype==MT_ERROR) { + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"XML not well-formed or unsupported XML feature."); + return NULL; + } + if (mtype==MT_NONE_YET) continue; +#ifdef STREAM_DEBUG + printf("{[%i]",reader->depth); + if (mtype==MT_IGNORE){ + printf("ignoring: "); + } + else if (mtype==MT_START_TAG){ + printf("start tag: "); + } + else if (mtype==MT_END_TAG){ + printf("end tag: "); + } + else if (mtype==MT_EMPTY_ELEMENT){ + printf("empty element: "); + } + else if (mtype==MT_CDATA){ + printf("cdata: "); + } + printf("'"); + fwrite(reader->buffer,1,len,stdout); + printf("'}"); +#endif + depth_change=0; + if (reader->depth==0) { + switch(mtype){ + case MT_START_TAG: + depth_change=1; + case MT_EMPTY_ELEMENT: + if (stream_start(reader,mtype,len)) + return NULL; + break; + case MT_IGNORE: + break; + case MT_CDATA: + for(i=0;ibuffer[i]!=' ' + && reader->buffer[i]!='\t' + && reader->buffer[i]!='\r' + && reader->buffer[i]!='\n') break; + } + if (i==len) break; + default: + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"XML not well-formed " + "or unsupported XML feature."); + return NULL; + } + } + else if (reader->depth==1) { + switch(mtype){ + case MT_START_TAG: + reader->current_stanza_end=0; + if (append_to_current_stanza(reader,len)) + return NULL; + depth_change=1; + break; + case MT_END_TAG: + if (stream_end(reader,len)) + return NULL; + depth_change=-1; + break; + case MT_EMPTY_ELEMENT: + reader->current_stanza_end=0; + if (append_to_current_stanza(reader,len)) + return NULL; + if (process_stanza(reader)) + return NULL; + break; + case MT_IGNORE: + case MT_CDATA: + break; + default: + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"XML not well-formed " + "or unsupported XML feature."); + return NULL; + } + } + else { + switch(mtype){ + case MT_START_TAG: + case MT_END_TAG: + case MT_EMPTY_ELEMENT: + case MT_CDATA: + if (append_to_current_stanza(reader,len)) + return NULL; + break; + case MT_IGNORE: + break; + default: + preparsing_reader_clear(reader); + PyErr_SetString(MyError,"XML not well-formed " + "or unsupported XML feature."); + return NULL; + } + switch(mtype){ + case MT_START_TAG: + depth_change=1; + break; + case MT_END_TAG: + if (reader->depth==2 && process_stanza(reader)) + return NULL; + depth_change=-1; + break; + default: + break; + } + } + reader->depth+=depth_change; + memmove(reader->buffer,reader->buffer+len,reader->buffer_end-len); + reader->buffer_end-=len; + reader->buffer_pos-=len; + if (reader->exception) return NULL; + } + + return Py_BuildValue("i", reader->buffer_posbuffer_end); +} + +static PyObject * preparsing_reader_doc(PyObject *self, PyObject *args) { + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef preparsing_reader_methods[] = { + {(char *)"feed", preparsing_reader_feed, METH_VARARGS, NULL}, + {(char *)"doc", preparsing_reader_doc, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyObject * preparsing_reader_getattr(PyObject *obj, char *name) { +PreparsingReaderObject *reader=(PreparsingReaderObject *)obj; + + return Py_FindMethod(preparsing_reader_methods, (PyObject *)reader, name); +} + +static int preparsing_reader_setattr(PyObject *obj, char *name, PyObject *v) { + (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name); + return -1; +} + +static PyTypeObject PreparsingReaderType = { + PyObject_HEAD_INIT(NULL) + 0, + "_Reader", + sizeof(PreparsingReaderObject), + 0, + preparsing_reader_free, /*tp_dealloc*/ + 0, /*tp_print*/ + preparsing_reader_getattr, /*tp_getattr*/ + preparsing_reader_setattr, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; +#endif + +static PyMethodDef xmlextraMethods[] = { + {(char *)"replace_ns", replace_ns, METH_VARARGS, NULL }, + {(char *)"remove_ns", remove_ns, METH_VARARGS, NULL }, +#if 1 + {(char *)"sax_reader_new", sax_reader_new, METH_VARARGS, NULL}, +#else + {(char *)"preparsing_reader_new", preparsing_reader_new, METH_VARARGS, NULL}, +#endif + {NULL, NULL, 0, NULL} +}; + +void init_xmlextra(void) { +static int initialized = 0; +PyObject *m, *d; + + if (initialized != 0) return; +#if 1 + SaxReaderType.ob_type = &PyType_Type; +#else + PreparsingReaderType.ob_type = &PyType_Type; +#endif + m = Py_InitModule((char *) "_xmlextra", xmlextraMethods); + d = PyModule_GetDict(m); + MyError = PyErr_NewException("_xmlextra.error", NULL, NULL); + PyDict_SetItemString(d, "error", MyError); + PyDict_SetItemString(d, "__revision__", PyString_FromString("$Id: xmlextra.c,v 1.6 2004/10/04 13:01:18 jajcus Exp $")); + PyDict_SetItemString(d, "__docformat__", PyString_FromString("restructuredtext en")); + PyDict_SetItemString(d, "__doc__", + PyString_FromString("Special libxml2 extensions for PyXMPP internal use.")); + initialized = 1; +} diff --git a/digsby/i18n/__init__.py b/digsby/i18n/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/i18n/analyze.py b/digsby/i18n/analyze.py new file mode 100644 index 0000000..0954bbb --- /dev/null +++ b/digsby/i18n/analyze.py @@ -0,0 +1,103 @@ +#__LICENSE_GOES_HERE__ +# -*- coding: utf-8 -*- +from __future__ import with_statement #@UnresolvedImport +from i18n.generate_monolithic import POT_DIR +import sys +import os.path +from babel.messages.pofile import read_po +from path import path + + +final_pot = path(POT_DIR) / 'digsby' / 'digsby.pot' +digsby_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +leet_pot = os.path.join(digsby_root, 'devplugins', 'l33t_language', 'digsby-lt.po') + +interesting = [' ', '\n', '\t'] +def teststr(s): + ''' + return bool(s is interesting) + ''' + for i in interesting: + if s.endswith(i) or s.startswith(i) or i*2 in s: + return True + +def _test(message): + ''' + helper function for plural forms; delegates to teststr() + ''' + if isinstance(message.id, tuple): + return teststr(message.id[0]) or teststr(message.id[1]) + return teststr(message.id) + +def remove_template_problems(fpath): + ''' + babel doesn't like the template values for the Plural-Forms, + nor for the charset in Content-Type. + since the values for templates are the defaults, + we filter/correct those + ''' + from StringIO import StringIO + with open(fpath) as final: + lines = final.readlines() + lines = filter(lambda l: not l.startswith('"Plural-Forms: '), lines) + lines2 = [] + for line in lines: + if line == '"Content-Type: text/plain; charset=CHARSET\\n"\n': + lines2.append('"Content-Type: text/plain; charset=UTF-8\\n"\n') + continue + lines2.append(line) + lines = lines2 + return StringIO(''.join(lines)) + +def eclipse_path(loc): + path, lineno = loc + return ' File "%s", line %s' % (os.path.join(digsby_root, path), lineno) + +def _show_message(message): + print >> sys.stderr, message.id + for loc in message.locations: + print >> sys.stderr, eclipse_path(loc) + print >> sys.stderr, '-'*80 + +def show_interesting(): + catalog = read_po(remove_template_problems(final_pot)) + for key, message in catalog._messages.items(): + if _test(message): + _show_message(message) +# if not message.context: +# continue +# if len(message.locations) < 2: +# continue +# print repr(key)#, repr(message.id), message.locations + +def _messages_with_mark(pofile_path): + catalog = read_po(remove_template_problems(pofile_path)) + for message in catalog: + if 'MARK' in ''.join(comment for comment in message.user_comments): + yield message + +def show_marks(pofile_path): + ''' + parses the PO file at the given path, and prints to stderr each message + commented with a MARK + ''' + count = 0 + for message in _messages_with_mark(pofile_path): + print >> sys.stderr, '(%s) %s' % ('\n'.join(message.user_comments).replace('MARK: ', ''), message.lineno) + _show_message(message) + count += 1 + print count + +def save_marks(pofile_path): + + count = 0 + + with open('marksfile.txt', 'wb') as marksfile: + for message in _messages_with_mark(pofile_path): + marksfile.write('%s: %s %s\n\t\t"%s"\n\n' % (message.lineno, '\n'.join(message.user_comments).replace('MARK: ', ''), message.locations, message.id)) + count += 1 + + print count +if __name__ == "__main__": + #show_interesting() + save_marks(leet_pot) diff --git a/digsby/i18n/argparse.py b/digsby/i18n/argparse.py new file mode 100644 index 0000000..f9279aa --- /dev/null +++ b/digsby/i18n/argparse.py @@ -0,0 +1,2311 @@ +# Author: Steven J. Bethard . + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.1' +__all__ = [ + 'ArgumentParser', + 'ArgumentError', + 'Namespace', + 'Action', + 'FileType', + 'HelpFormatter', + 'RawDescriptionHelpFormatter', + 'RawTextHelpFormatter', + 'ArgumentDefaultsHelpFormatter', +] + + +import copy as _copy +import os as _os +import re as _re +import sys as _sys +import textwrap as _textwrap + +from gettext import gettext as _ + + +def _callable(obj): + return hasattr(obj, '__call__') or hasattr(obj, '__bases__') + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = 'A...' +REMAINDER = '...' + +# ============================= +# Utility functions and classes +# ============================= + +class _AttributeHolder(object): + """Abstract base class that provides __repr__. + + The __repr__ method returns a string in the format:: + ClassName(attr=name, attr=name, ...) + The attributes are determined either by a class-level attribute, + '_kwarg_names', or by inspecting the instance __dict__. + """ + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + for arg in self._get_args(): + arg_strings.append(repr(arg)) + for name, value in self._get_kwargs(): + arg_strings.append('%s=%r' % (name, value)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get_kwargs(self): + return sorted(self.__dict__.items()) + + def _get_args(self): + return [] + + +def _ensure_value(namespace, name, value): + if getattr(namespace, name, None) is None: + setattr(namespace, name, value) + return getattr(namespace, name) + + +# =============== +# Formatting Help +# =============== + +class HelpFormatter(object): + """Formatter for generating usage messages and argument help strings. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def __init__(self, + prog, + indent_increment=2, + max_help_position=24, + width=None): + + # default setting for width + if width is None: + try: + width = int(_os.environ['COLUMNS']) + except (KeyError, ValueError): + width = 80 + width -= 2 + + self._prog = prog + self._indent_increment = indent_increment + self._max_help_position = max_help_position + self._width = width + + self._current_indent = 0 + self._level = 0 + self._action_max_length = 0 + + self._root_section = self._Section(self, None) + self._current_section = self._root_section + + self._whitespace_matcher = _re.compile(r'\s+') + self._long_break_matcher = _re.compile(r'\n\n\n+') + + # =============================== + # Section and indentation methods + # =============================== + def _indent(self): + self._current_indent += self._indent_increment + self._level += 1 + + def _dedent(self): + self._current_indent -= self._indent_increment + assert self._current_indent >= 0, 'Indent decreased below 0.' + self._level -= 1 + + class _Section(object): + + def __init__(self, formatter, parent, heading=None): + self.formatter = formatter + self.parent = parent + self.heading = heading + self.items = [] + + def format_help(self): + # format the indented section + if self.parent is not None: + self.formatter._indent() + join = self.formatter._join_parts + for func, args in self.items: + func(*args) + item_help = join([func(*args) for func, args in self.items]) + if self.parent is not None: + self.formatter._dedent() + + # return nothing if the section was empty + if not item_help: + return '' + + # add the heading if the section was non-empty + if self.heading is not SUPPRESS and self.heading is not None: + current_indent = self.formatter._current_indent + heading = '%*s%s:\n' % (current_indent, '', self.heading) + else: + heading = '' + + # join the section-initial newline, the heading and the help + return join(['\n', heading, item_help, '\n']) + + def _add_item(self, func, args): + self._current_section.items.append((func, args)) + + # ======================== + # Message building methods + # ======================== + def start_section(self, heading): + self._indent() + section = self._Section(self, self._current_section, heading) + self._add_item(section.format_help, []) + self._current_section = section + + def end_section(self): + self._current_section = self._current_section.parent + self._dedent() + + def add_text(self, text): + if text is not SUPPRESS and text is not None: + self._add_item(self._format_text, [text]) + + def add_usage(self, usage, actions, groups, prefix=None): + if usage is not SUPPRESS: + args = usage, actions, groups, prefix + self._add_item(self._format_usage, args) + + def add_argument(self, action): + if action.help is not SUPPRESS: + + # find all invocations + get_invocation = self._format_action_invocation + invocations = [get_invocation(action)] + for subaction in self._iter_indented_subactions(action): + invocations.append(get_invocation(subaction)) + + # update the maximum item length + invocation_length = max([len(s) for s in invocations]) + action_length = invocation_length + self._current_indent + self._action_max_length = max(self._action_max_length, + action_length) + + # add the item to the list + self._add_item(self._format_action, [action]) + + def add_arguments(self, actions): + for action in actions: + self.add_argument(action) + + # ======================= + # Help-formatting methods + # ======================= + def format_help(self): + help = self._root_section.format_help() + if help: + help = self._long_break_matcher.sub('\n\n', help) + help = help.strip('\n') + '\n' + return help + + def _join_parts(self, part_strings): + return ''.join([part + for part in part_strings + if part and part is not SUPPRESS]) + + def _format_usage(self, usage, actions, groups, prefix): + if prefix is None: + prefix = _('usage: ') + + # if usage is specified, use that + if usage is not None: + usage = usage % dict(prog=self._prog) + + # if no optionals or positionals are available, usage is just prog + elif usage is None and not actions: + usage = '%(prog)s' % dict(prog=self._prog) + + # if optionals and positionals are available, calculate usage + elif usage is None: + prog = '%(prog)s' % dict(prog=self._prog) + + # split optionals from positionals + optionals = [] + positionals = [] + for action in actions: + if action.option_strings: + optionals.append(action) + else: + positionals.append(action) + + # build full usage string + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + usage = ' '.join([s for s in [prog, action_usage] if s]) + + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + pos_usage = format(positionals, groups) + opt_parts = _re.findall(part_regexp, opt_usage) + pos_parts = _re.findall(part_regexp, pos_usage) + assert ' '.join(opt_parts) == opt_usage + assert ' '.join(pos_parts) == pos_usage + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) + + # prefix with 'usage:' + return '%s%s\n\n' % (prefix, usage) + + def _format_actions_usage(self, actions, groups): + # find group indices and identify actions in groups + group_actions = set() + inserts = {} + for group in groups: + try: + start = actions.index(group._group_actions[0]) + except ValueError: + continue + else: + end = start + len(group._group_actions) + if actions[start:end] == group._group_actions: + for action in group._group_actions: + group_actions.add(action) + if not group.required: + inserts[start] = '[' + inserts[end] = ']' + else: + inserts[start] = '(' + inserts[end] = ')' + for i in range(start + 1, end): + inserts[i] = '|' + + # collect all actions format strings + parts = [] + for i, action in enumerate(actions): + + # suppressed arguments are marked with None + # remove | separators for suppressed arguments + if action.help is SUPPRESS: + parts.append(None) + if inserts.get(i) == '|': + inserts.pop(i) + elif inserts.get(i + 1) == '|': + inserts.pop(i + 1) + + # produce all arg strings + elif not action.option_strings: + part = self._format_args(action, action.dest) + + # if it's in a group, strip the outer [] + if action in group_actions: + if part[0] == '[' and part[-1] == ']': + part = part[1:-1] + + # add the action string to the list + parts.append(part) + + # produce the first way to invoke the option in brackets + else: + option_string = action.option_strings[0] + + # if the Optional doesn't take a value, format is: + # -s or --long + if action.nargs == 0: + part = '%s' % option_string + + # if the Optional takes a value, format is: + # -s ARGS or --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + part = '%s %s' % (option_string, args_string) + + # make it look optional if it's not required or in a group + if not action.required and action not in group_actions: + part = '[%s]' % part + + # add the action string to the list + parts.append(part) + + # insert things at the necessary indices + for i in sorted(inserts, reverse=True): + parts[i:i] = [inserts[i]] + + # join all the action items with spaces + text = ' '.join([item for item in parts if item is not None]) + + # clean up separators for mutually exclusive groups + open = r'[\[(]' + close = r'[\])]' + text = _re.sub(r'(%s) ' % open, r'\1', text) + text = _re.sub(r' (%s)' % close, r'\1', text) + text = _re.sub(r'%s *%s' % (open, close), r'', text) + text = _re.sub(r'\(([^|]*)\)', r'\1', text) + text = text.strip() + + # return the text + return text + + def _format_text(self, text): + if '%(prog)' in text: + text = text % dict(prog=self._prog) + text_width = self._width - self._current_indent + indent = ' ' * self._current_indent + return self._fill_text(text, text_width, indent) + '\n\n' + + def _format_action(self, action): + # determine the required width and the entry label + help_position = min(self._action_max_length + 2, + self._max_help_position) + help_width = self._width - help_position + action_width = help_position - self._current_indent - 2 + action_header = self._format_action_invocation(action) + + # ho nelp; start on same line and add a final newline + if not action.help: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + + # short action name; start on the same line and pad two spaces + elif len(action_header) <= action_width: + tup = self._current_indent, '', action_width, action_header + action_header = '%*s%-*s ' % tup + indent_first = 0 + + # long action name; start on the next line + else: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + indent_first = help_position + + # collect the pieces of the action help + parts = [action_header] + + # if there was help for the action, add lines of help text + if action.help: + help_text = self._expand_help(action) + help_lines = self._split_lines(help_text, help_width) + parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) + for line in help_lines[1:]: + parts.append('%*s%s\n' % (help_position, '', line)) + + # or add a newline if the description doesn't end with one + elif not action_header.endswith('\n'): + parts.append('\n') + + # if there are any sub-actions, add their help as well + for subaction in self._iter_indented_subactions(action): + parts.append(self._format_action(subaction)) + + # return a single string + return self._join_parts(parts) + + def _format_action_invocation(self, action): + if not action.option_strings: + metavar, = self._metavar_formatter(action, action.dest)(1) + return metavar + + else: + parts = [] + + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + for option_string in action.option_strings: + parts.append('%s %s' % (option_string, args_string)) + + return ', '.join(parts) + + def _metavar_formatter(self, action, default_metavar): + if action.metavar is not None: + result = action.metavar + elif action.choices is not None: + choice_strs = [str(choice) for choice in action.choices] + result = '{%s}' % ','.join(choice_strs) + else: + result = default_metavar + + def format(tuple_size): + if isinstance(result, tuple): + return result + else: + return (result, ) * tuple_size + return format + + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + if action.nargs is None: + result = '%s' % get_metavar(1) + elif action.nargs == OPTIONAL: + result = '[%s]' % get_metavar(1) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [%s ...]]' % get_metavar(2) + elif action.nargs == ONE_OR_MORE: + result = '%s [%s ...]' % get_metavar(2) + elif action.nargs == REMAINDER: + result = '...' + elif action.nargs == PARSER: + result = '%s ...' % get_metavar(1) + else: + formats = ['%s' for _ in range(action.nargs)] + result = ' '.join(formats) % get_metavar(action.nargs) + return result + + def _expand_help(self, action): + params = dict(vars(action), prog=self._prog) + for name in list(params): + if params[name] is SUPPRESS: + del params[name] + for name in list(params): + if hasattr(params[name], '__name__'): + params[name] = params[name].__name__ + if params.get('choices') is not None: + choices_str = ', '.join([str(c) for c in params['choices']]) + params['choices'] = choices_str + return self._get_help_string(action) % params + + def _iter_indented_subactions(self, action): + try: + get_subactions = action._get_subactions + except AttributeError: + pass + else: + self._indent() + for subaction in get_subactions(): + yield subaction + self._dedent() + + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.wrap(text, width) + + def _fill_text(self, text, width, indent): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.fill(text, width, initial_indent=indent, + subsequent_indent=indent) + + def _get_help_string(self, action): + return action.help + + +class RawDescriptionHelpFormatter(HelpFormatter): + """Help message formatter which retains any formatting in descriptions. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _fill_text(self, text, width, indent): + return ''.join([indent + line for line in text.splitlines(True)]) + + +class RawTextHelpFormatter(RawDescriptionHelpFormatter): + """Help message formatter which retains formatting of all help text. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _split_lines(self, text, width): + return text.splitlines() + + +class ArgumentDefaultsHelpFormatter(HelpFormatter): + """Help message formatter which adds default values to argument help. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_help_string(self, action): + help = action.help + if '%(default)' not in action.help: + if action.default is not SUPPRESS: + defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + help += ' (default: %(default)s)' + return help + + +# ===================== +# Options and Arguments +# ===================== + +def _get_action_name(argument): + if argument is None: + return None + elif argument.option_strings: + return '/'.join(argument.option_strings) + elif argument.metavar not in (None, SUPPRESS): + return argument.metavar + elif argument.dest not in (None, SUPPRESS): + return argument.dest + else: + return None + + +class ArgumentError(Exception): + """An error from creating or using an argument (optional or positional). + + The string value of this exception is the message, augmented with + information about the argument that caused it. + """ + + def __init__(self, argument, message): + self.argument_name = _get_action_name(argument) + self.message = message + + def __str__(self): + if self.argument_name is None: + format = '%(message)s' + else: + format = 'argument %(argument_name)s: %(message)s' + return format % dict(message=self.message, + argument_name=self.argument_name) + + +class ArgumentTypeError(Exception): + """An error from trying to convert a command line string to a type.""" + pass + + +# ============== +# Action classes +# ============== + +class Action(_AttributeHolder): + """Information about how to convert command line strings to Python objects. + + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The keyword arguments to the Action constructor are also + all attributes of Action instances. + + Keyword Arguments: + + - option_strings -- A list of command-line option strings which + should be associated with this action. + + - dest -- The name of the attribute to hold the created object(s) + + - nargs -- The number of command-line arguments that should be + consumed. By default, one argument will be consumed and a single + value will be produced. Other values include: + - N (an integer) consumes N arguments (and produces a list) + - '?' consumes zero or one arguments + - '*' consumes zero or more arguments (and produces a list) + - '+' consumes one or more arguments (and produces a list) + Note that the difference between the default and nargs=1 is that + with the default, a single value will be produced, while with + nargs=1, a list containing a single value will be produced. + + - const -- The value to be produced if the option is specified and the + option uses an action that takes no values. + + - default -- The value to be produced if the option is not specified. + + - type -- The type which the command-line arguments should be converted + to, should be one of 'string', 'int', 'float', 'complex' or a + callable object that accepts a single string argument. If None, + 'string' is assumed. + + - choices -- A container of values that should be allowed. If not None, + after a command-line argument has been converted to the appropriate + type, an exception will be raised if it is not a member of this + collection. + + - required -- True if the action must always be specified at the + command line. This is only meaningful for optional command-line + arguments. + + - help -- The help string describing the argument. + + - metavar -- The name to be used for the option's argument with the + help string. If None, the 'dest' value will be used as the name. + """ + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + self.option_strings = option_strings + self.dest = dest + self.nargs = nargs + self.const = const + self.default = default + self.type = type + self.choices = choices + self.required = required + self.help = help + self.metavar = metavar + + def _get_kwargs(self): + names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar', + ] + return [(name, getattr(self, name)) for name in names] + + def __call__(self, parser, namespace, values, option_string=None): + raise NotImplementedError(_('.__call__() not defined')) + + +class _StoreAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for store actions must be > 0; if you ' + 'have nothing to store, actions such as store ' + 'true or store const may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_StoreAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + +class _StoreConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_StoreConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + +class _StoreTrueAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=False, + required=False, + help=None): + super(_StoreTrueAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + required=required, + help=help) + + +class _StoreFalseAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=True, + required=False, + help=None): + super(_StoreFalseAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=False, + default=default, + required=required, + help=help) + + +class _AppendAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for append actions must be > 0; if arg ' + 'strings are not supplying the value to append, ' + 'the append const action may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_AppendAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(values) + setattr(namespace, self.dest, items) + + +class _AppendConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_AppendConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(self.const) + setattr(namespace, self.dest, items) + + +class _CountAction(Action): + + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None): + super(_CountAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + new_count = _ensure_value(namespace, self.dest, 0) + 1 + setattr(namespace, self.dest, new_count) + + +class _HelpAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + parser.exit() + + +class _VersionAction(Action): + + def __init__(self, + option_strings, + version=None, + dest=SUPPRESS, + default=SUPPRESS, + help="show program's version number and exit"): + super(_VersionAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + self.version = version + + def __call__(self, parser, namespace, values, option_string=None): + version = self.version + if version is None: + version = parser.version + formatter = parser._get_formatter() + formatter.add_text(version) + parser.exit(message=formatter.format_help()) + + +class _SubParsersAction(Action): + + class _ChoicesPseudoAction(Action): + + def __init__(self, name, help): + sup = super(_SubParsersAction._ChoicesPseudoAction, self) + sup.__init__(option_strings=[], dest=name, help=help) + + def __init__(self, + option_strings, + prog, + parser_class, + dest=SUPPRESS, + help=None, + metavar=None): + + self._prog_prefix = prog + self._parser_class = parser_class + self._name_parser_map = {} + self._choices_actions = [] + + super(_SubParsersAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=PARSER, + choices=self._name_parser_map, + help=help, + metavar=metavar) + + def add_parser(self, name, **kwargs): + # set prog from the existing prefix + if kwargs.get('prog') is None: + kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + + # create a pseudo-action to hold the choice help + if 'help' in kwargs: + help = kwargs.pop('help') + choice_action = self._ChoicesPseudoAction(name, help) + self._choices_actions.append(choice_action) + + # create the parser and add it to the map + parser = self._parser_class(**kwargs) + self._name_parser_map[name] = parser + return parser + + def _get_subactions(self): + return self._choices_actions + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest is not SUPPRESS: + setattr(namespace, self.dest, parser_name) + + # select the parser + try: + parser = self._name_parser_map[parser_name] + except KeyError: + tup = parser_name, ', '.join(self._name_parser_map) + msg = _('unknown parser %r (choices: %s)' % tup) + raise ArgumentError(self, msg) + + # parse all the remaining options into the namespace + parser.parse_args(arg_strings, namespace) + + +# ============== +# Type classes +# ============== + +class FileType(object): + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + """ + + def __init__(self, mode='r', bufsize=None): + self._mode = mode + self._bufsize = bufsize + + def __call__(self, string): + # the special argument "-" means sys.std{in,out} + if string == '-': + if 'r' in self._mode: + return _sys.stdin + elif 'w' in self._mode: + return _sys.stdout + else: + msg = _('argument "-" with mode %r' % self._mode) + raise ValueError(msg) + + # all other arguments are used as file names + if self._bufsize: + return open(string, self._mode, self._bufsize) + else: + return open(string, self._mode) + + def __repr__(self): + args = [self._mode, self._bufsize] + args_str = ', '.join([repr(arg) for arg in args if arg is not None]) + return '%s(%s)' % (type(self).__name__, args_str) + +# =========================== +# Optional and Positional Parsing +# =========================== + +class Namespace(_AttributeHolder): + """Simple object for storing attributes. + + Implements equality by attribute names and values, and provides a simple + string representation. + """ + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + __hash__ = None + + def __eq__(self, other): + return vars(self) == vars(other) + + def __ne__(self, other): + return not (self == other) + + def __contains__(self, key): + return key in self.__dict__ + + +class _ActionsContainer(object): + + def __init__(self, + description, + prefix_chars, + argument_default, + conflict_handler): + super(_ActionsContainer, self).__init__() + + self.description = description + self.argument_default = argument_default + self.prefix_chars = prefix_chars + self.conflict_handler = conflict_handler + + # set up registries + self._registries = {} + + # register actions + self.register('action', None, _StoreAction) + self.register('action', 'store', _StoreAction) + self.register('action', 'store_const', _StoreConstAction) + self.register('action', 'store_true', _StoreTrueAction) + self.register('action', 'store_false', _StoreFalseAction) + self.register('action', 'append', _AppendAction) + self.register('action', 'append_const', _AppendConstAction) + self.register('action', 'count', _CountAction) + self.register('action', 'help', _HelpAction) + self.register('action', 'version', _VersionAction) + self.register('action', 'parsers', _SubParsersAction) + + # raise an exception if the conflict handler is invalid + self._get_handler() + + # action storage + self._actions = [] + self._option_string_actions = {} + + # groups + self._action_groups = [] + self._mutually_exclusive_groups = [] + + # defaults storage + self._defaults = {} + + # determines whether an "option" looks like a negative number + self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') + + # whether or not there are any optionals that look like negative + # numbers -- uses a list so it can be shared and edited + self._has_negative_number_optionals = [] + + # ==================== + # Registration methods + # ==================== + def register(self, registry_name, value, object): + registry = self._registries.setdefault(registry_name, {}) + registry[value] = object + + def _registry_get(self, registry_name, value, default=None): + return self._registries[registry_name].get(value, default) + + # ================================== + # Namespace default accessor methods + # ================================== + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + + # if these defaults match any existing arguments, replace + # the previous default on the object with the new one + for action in self._actions: + if action.dest in kwargs: + action.default = kwargs[action.dest] + + def get_default(self, dest): + for action in self._actions: + if action.dest == dest and action.default is not None: + return action.default + return self._defaults.get(dest, None) + + + # ======================= + # Adding argument actions + # ======================= + def add_argument(self, *args, **kwargs): + """ + add_argument(dest, ..., name=value, ...) + add_argument(option_string, option_string, ..., name=value, ...) + """ + + # if no positional args are supplied or only one is supplied and + # it doesn't look like an option string, parse a positional + # argument + chars = self.prefix_chars + if not args or len(args) == 1 and args[0][0] not in chars: + if args and 'dest' in kwargs: + raise ValueError('dest supplied twice for positional argument') + kwargs = self._get_positional_kwargs(*args, **kwargs) + + # otherwise, we're adding an optional argument + else: + kwargs = self._get_optional_kwargs(*args, **kwargs) + + # if no default was supplied, use the parser-level default + if 'default' not in kwargs: + dest = kwargs['dest'] + if dest in self._defaults: + kwargs['default'] = self._defaults[dest] + elif self.argument_default is not None: + kwargs['default'] = self.argument_default + + # create the action object, and add it to the parser + action_class = self._pop_action_class(kwargs) + if not _callable(action_class): + raise ValueError('unknown action "%s"' % action_class) + action = action_class(**kwargs) + + # raise an error if the action type is not callable + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + raise ValueError('%r is not callable' % type_func) + + return self._add_action(action) + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _add_action(self, action): + # resolve any conflicts + self._check_conflict(action) + + # add to actions list + self._actions.append(action) + action.container = self + + # index the action by any option strings it has + for option_string in action.option_strings: + self._option_string_actions[option_string] = action + + # set the flag if any option strings look like negative numbers + for option_string in action.option_strings: + if self._negative_number_matcher.match(option_string): + if not self._has_negative_number_optionals: + self._has_negative_number_optionals.append(True) + + # return the created action + return action + + def _remove_action(self, action): + self._actions.remove(action) + + def _add_container_actions(self, container): + # collect groups by titles + title_group_map = {} + for group in self._action_groups: + if group.title in title_group_map: + msg = _('cannot merge actions - two groups are named %r') + raise ValueError(msg % (group.title)) + title_group_map[group.title] = group + + # map each action to its group + group_map = {} + for group in container._action_groups: + + # if a group with the title exists, use that, otherwise + # create a new group matching the container's group + if group.title not in title_group_map: + title_group_map[group.title] = self.add_argument_group( + title=group.title, + description=group.description, + conflict_handler=group.conflict_handler) + + # map the actions to their new group + for action in group._group_actions: + group_map[action] = title_group_map[group.title] + + # add container's mutually exclusive groups + # NOTE: if add_mutually_exclusive_group ever gains title= and + # description= then this code will need to be expanded as above + for group in container._mutually_exclusive_groups: + mutex_group = self.add_mutually_exclusive_group( + required=group.required) + + # map the actions to their new mutex group + for action in group._group_actions: + group_map[action] = mutex_group + + # add all actions to this container or their group + for action in container._actions: + group_map.get(action, self)._add_action(action) + + def _get_positional_kwargs(self, dest, **kwargs): + # make sure required is not specified + if 'required' in kwargs: + msg = _("'required' is an invalid argument for positionals") + raise TypeError(msg) + + # mark positional arguments as required if at least one is + # always required + if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: + kwargs['required'] = True + if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + kwargs['required'] = True + + # return the keyword arguments with no option strings + return dict(kwargs, dest=dest, option_strings=[]) + + def _get_optional_kwargs(self, *args, **kwargs): + # determine short and long option strings + option_strings = [] + long_option_strings = [] + for option_string in args: + # error on strings that don't start with an appropriate prefix + if not option_string[0] in self.prefix_chars: + msg = _('invalid option string %r: ' + 'must start with a character %r') + tup = option_string, self.prefix_chars + raise ValueError(msg % tup) + + # strings starting with two prefix characters are long options + option_strings.append(option_string) + if option_string[0] in self.prefix_chars: + if len(option_string) > 1: + if option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) + + # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + dest = kwargs.pop('dest', None) + if dest is None: + if long_option_strings: + dest_option_string = long_option_strings[0] + else: + dest_option_string = option_strings[0] + dest = dest_option_string.lstrip(self.prefix_chars) + if not dest: + msg = _('dest= is required for options like %r') + raise ValueError(msg % option_string) + dest = dest.replace('-', '_') + + # return the updated keyword arguments + return dict(kwargs, dest=dest, option_strings=option_strings) + + def _pop_action_class(self, kwargs, default=None): + action = kwargs.pop('action', default) + return self._registry_get('action', action, action) + + def _get_handler(self): + # determine function from conflict handler string + handler_func_name = '_handle_conflict_%s' % self.conflict_handler + try: + return getattr(self, handler_func_name) + except AttributeError: + msg = _('invalid conflict_resolution value: %r') + raise ValueError(msg % self.conflict_handler) + + def _check_conflict(self, action): + + # find all options that conflict with this option + confl_optionals = [] + for option_string in action.option_strings: + if option_string in self._option_string_actions: + confl_optional = self._option_string_actions[option_string] + confl_optionals.append((option_string, confl_optional)) + + # resolve any conflicts + if confl_optionals: + conflict_handler = self._get_handler() + conflict_handler(action, confl_optionals) + + def _handle_conflict_error(self, action, conflicting_actions): + message = _('conflicting option string(s): %s') + conflict_string = ', '.join([option_string + for option_string, action + in conflicting_actions]) + raise ArgumentError(action, message % conflict_string) + + def _handle_conflict_resolve(self, action, conflicting_actions): + + # remove all conflicting options + for option_string, action in conflicting_actions: + + # remove the conflicting option + action.option_strings.remove(option_string) + self._option_string_actions.pop(option_string, None) + + # if the option now has no option string, remove it from the + # container holding it + if not action.option_strings: + action.container._remove_action(action) + + +class _ArgumentGroup(_ActionsContainer): + + def __init__(self, container, title=None, description=None, **kwargs): + # add any missing keyword arguments by checking the container + update = kwargs.setdefault + update('conflict_handler', container.conflict_handler) + update('prefix_chars', container.prefix_chars) + update('argument_default', container.argument_default) + super_init = super(_ArgumentGroup, self).__init__ + super_init(description=description, **kwargs) + + # group attributes + self.title = title + self._group_actions = [] + + # share most attributes with the container + self._registries = container._registries + self._actions = container._actions + self._option_string_actions = container._option_string_actions + self._defaults = container._defaults + self._has_negative_number_optionals = \ + container._has_negative_number_optionals + + def _add_action(self, action): + action = super(_ArgumentGroup, self)._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + super(_ArgumentGroup, self)._remove_action(action) + self._group_actions.remove(action) + + +class _MutuallyExclusiveGroup(_ArgumentGroup): + + def __init__(self, container, required=False): + super(_MutuallyExclusiveGroup, self).__init__(container) + self.required = required + self._container = container + + def _add_action(self, action): + if action.required: + msg = _('mutually exclusive arguments must be optional') + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + self._container._remove_action(action) + self._group_actions.remove(action) + + +class ArgumentParser(_AttributeHolder, _ActionsContainer): + """Object for parsing command line strings into Python objects. + + Keyword Arguments: + - prog -- The name of the program (default: sys.argv[0]) + - usage -- A usage message (default: auto-generated from arguments) + - description -- A description of what the program does + - epilog -- Text following the argument descriptions + - parents -- Parsers whose arguments should be copied into this one + - formatter_class -- HelpFormatter class for printing help messages + - prefix_chars -- Characters that prefix optional arguments + - fromfile_prefix_chars -- Characters that prefix files containing + additional arguments + - argument_default -- The default value for all arguments + - conflict_handler -- String indicating how to handle conflicts + - add_help -- Add a -h/-help option + """ + + def __init__(self, + prog=None, + usage=None, + description=None, + epilog=None, + version=None, + parents=[], + formatter_class=HelpFormatter, + prefix_chars='-', + fromfile_prefix_chars=None, + argument_default=None, + conflict_handler='error', + add_help=True): + + if version is not None: + import warnings + warnings.warn( + """The "version" argument to ArgumentParser is deprecated. """ + """Please use """ + """"add_argument(..., action='version', version="N", ...)" """ + """instead""", DeprecationWarning) + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) + + # default setting for prog + if prog is None: + prog = _os.path.basename(_sys.argv[0]) + + self.prog = prog + self.usage = usage + self.epilog = epilog + self.version = version + self.formatter_class = formatter_class + self.fromfile_prefix_chars = fromfile_prefix_chars + self.add_help = add_help + + add_group = self.add_argument_group + self._positionals = add_group(_('positional arguments')) + self._optionals = add_group(_('optional arguments')) + self._subparsers = None + + # register types + def identity(string): + return string + self.register('type', None, identity) + + # add help and version arguments if necessary + # (using explicit default to override global argument_default) + if self.add_help: + self.add_argument( + '-h', '--help', action='help', default=SUPPRESS, + help=_('show this help message and exit')) + if self.version: + self.add_argument( + '-v', '--version', action='version', default=SUPPRESS, + version=self.version, + help=_("show program's version number and exit")) + + # add parent arguments and defaults + for parent in parents: + self._add_container_actions(parent) + try: + defaults = parent._defaults + except AttributeError: + pass + else: + self._defaults.update(defaults) + + # ======================= + # Pretty __repr__ methods + # ======================= + def _get_kwargs(self): + names = [ + 'prog', + 'usage', + 'description', + 'version', + 'formatter_class', + 'conflict_handler', + 'add_help', + ] + return [(name, getattr(self, name)) for name in names] + + # ================================== + # Optional/Positional adding methods + # ================================== + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def _add_action(self, action): + if action.option_strings: + self._optionals._add_action(action) + else: + self._positionals._add_action(action) + return action + + def _get_optional_actions(self): + return [action + for action in self._actions + if action.option_strings] + + def _get_positional_actions(self): + return [action + for action in self._actions + if not action.option_strings] + + # ===================================== + # Command line argument parsing methods + # ===================================== + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = _('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + return args + + def parse_known_args(self, args=None, namespace=None): + # args default to the system args + if args is None: + args = _sys.argv[1:] + + # default Namespace built from parser defaults + if namespace is None: + namespace = Namespace() + + # add any action defaults that aren't present + for action in self._actions: + if action.dest is not SUPPRESS: + if not hasattr(namespace, action.dest): + if action.default is not SUPPRESS: + default = action.default + if isinstance(action.default, basestring): + default = self._get_value(action, default) + setattr(namespace, action.dest, default) + + # add any parser defaults that aren't present + for dest in self._defaults: + if not hasattr(namespace, dest): + setattr(namespace, dest, self._defaults[dest]) + + # parse the arguments and exit if there are any errors + try: + return self._parse_known_args(args, namespace) + except ArgumentError: + err = _sys.exc_info()[1] + self.error(str(err)) + + def _parse_known_args(self, arg_strings, namespace): + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts = {} + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1:]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions = set() + seen_non_default_actions = set() + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = _('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS: + action(self, namespace, argument_values, option_string) + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples = [] + while True: + + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + for char in self.prefix_chars: + option_string = char + explicit_arg[0] + explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + break + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index: start_index + arg_count] + start_index += arg_count + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts):] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + + # consume any Positionals preceding the next option + next_option_string_index = min([ + index + for index in option_string_indices + if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # if we didn't use all the Positional objects, there were too few + # arg strings supplied. + if positionals: + self.error(_('too few arguments')) + + # make sure all required actions were present + for action in self._actions: + if action.required: + if action not in seen_actions: + name = _get_action_name(action) + self.error(_('argument %s is required') % name) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [_get_action_name(action) + for action in group._group_actions + if action.help is not SUPPRESS] + msg = _('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras + + def _read_args_from_files(self, arg_strings): + # expand arguments referencing files + new_arg_strings = [] + for arg_string in arg_strings: + + # for regular arguments, just add them back into the list + if arg_string[0] not in self.fromfile_prefix_chars: + new_arg_strings.append(arg_string) + + # replace arguments referencing files with the file content + else: + try: + args_file = open(arg_string[1:]) + try: + arg_strings = [] + for arg_line in args_file.read().splitlines(): + for arg in self.convert_arg_line_to_args(arg_line): + arg_strings.append(arg) + arg_strings = self._read_args_from_files(arg_strings) + new_arg_strings.extend(arg_strings) + finally: + args_file.close() + except IOError: + err = _sys.exc_info()[1] + self.error(str(err)) + + # return the modified argument list + return new_arg_strings + + def convert_arg_line_to_args(self, arg_line): + return [arg_line] + + def _match_argument(self, action, arg_strings_pattern): + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = _re.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: _('expected one argument'), + OPTIONAL: _('expected at most one argument'), + ONE_OR_MORE: _('expected at least one argument'), + } + default = _('expected %s argument(s)') % action.nargs + msg = nargs_errors.get(action.nargs, default) + raise ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) + + def _match_arguments_partial(self, actions, arg_strings_pattern): + # progressively shorten the actions list by slicing off the + # final actions until we find a match + result = [] + for i in range(len(actions), 0, -1): + actions_slice = actions[:i] + pattern = ''.join([self._get_nargs_pattern(action) + for action in actions_slice]) + match = _re.match(pattern, arg_strings_pattern) + if match is not None: + result.extend([len(string) for string in match.groups()]) + break + + # return the list of arg string counts + return result + + def _parse_optional(self, arg_string): + # if it's an empty string, it was meant to be a positional + if not arg_string: + return None + + # if it doesn't start with a prefix, it was meant to be positional + if not arg_string[0] in self.prefix_chars: + return None + + # if the option string is present in the parser, return the action + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + + # if it's just a single character, it was meant to be positional + if len(arg_string) == 1: + return None + + # if the option string before the "=" is present, return the action + if '=' in arg_string: + option_string, explicit_arg = arg_string.split('=', 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) + + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + tup = arg_string, options + self.error(_('ambiguous option: %s could match %s') % tup) + + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple + + # if it was not found as an option, but it looks like a negative + # number, it was meant to be positional + # unless there are negative-number-like options + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + + # if it contains a space, it was meant to be a positional + if ' ' in arg_string: + return None + + # it was meant to be an optional but there is no such option + # in this parser (though it might be a valid option in a subparser) + return None, arg_string, None + + def _get_option_tuples(self, option_string): + result = [] + + # option strings starting with two prefix characters are only + # split at the '=' + chars = self.prefix_chars + if option_string[0] in chars and option_string[1] in chars: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # single character options can be concatenated with their arguments + # but multiple character options always have to have their argument + # separate + elif option_string[0] in chars and option_string[1] not in chars: + option_prefix = option_string + explicit_arg = None + short_option_prefix = option_string[:2] + short_explicit_arg = option_string[2:] + + for option_string in self._option_string_actions: + if option_string == short_option_prefix: + action = self._option_string_actions[option_string] + tup = action, option_string, short_explicit_arg + result.append(tup) + elif option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # shouldn't ever get here + else: + self.error(_('unexpected option string: %s') % option_string) + + # return the collected option tuples + return result + + def _get_nargs_pattern(self, action): + # in all examples below, we have to allow for '--' args + # which are represented as '-' in the pattern + nargs = action.nargs + + # the default (None) is assumed to be a single argument + if nargs is None: + nargs_pattern = '(-*A-*)' + + # allow zero or one arguments + elif nargs == OPTIONAL: + nargs_pattern = '(-*A?-*)' + + # allow zero or more arguments + elif nargs == ZERO_OR_MORE: + nargs_pattern = '(-*[A-]*)' + + # allow one or more arguments + elif nargs == ONE_OR_MORE: + nargs_pattern = '(-*A[A-]*)' + + # allow any number of options or arguments + elif nargs == REMAINDER: + nargs_pattern = '([-AO]*)' + + # allow one argument followed by any number of options or arguments + elif nargs == PARSER: + nargs_pattern = '(-*A[-AO]*)' + + # all others should be integers + else: + nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) + + # if this is an optional action, -- is not allowed + if action.option_strings: + nargs_pattern = nargs_pattern.replace('-*', '') + nargs_pattern = nargs_pattern.replace('-', '') + + # return the pattern + return nargs_pattern + + # ======================== + # Value conversion methods + # ======================== + def _get_values(self, action, arg_strings): + # for everything but PARSER args, strip out '--' + if action.nargs not in [PARSER, REMAINDER]: + arg_strings = [s for s in arg_strings if s != '--'] + + # optional argument produces a default when not present + if not arg_strings and action.nargs == OPTIONAL: + if action.option_strings: + value = action.const + else: + value = action.default + if isinstance(value, basestring): + value = self._get_value(action, value) + self._check_value(action, value) + + # when nargs='*' on a positional, if there were no command-line + # args, use the default if it is anything other than None + elif (not arg_strings and action.nargs == ZERO_OR_MORE and + not action.option_strings): + if action.default is not None: + value = action.default + else: + value = arg_strings + self._check_value(action, value) + + # single argument or optional argument produces a single value + elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: + arg_string, = arg_strings + value = self._get_value(action, arg_string) + self._check_value(action, value) + + # REMAINDER arguments convert all values, checking none + elif action.nargs == REMAINDER: + value = [self._get_value(action, v) for v in arg_strings] + + # PARSER arguments convert all values, but check only the first + elif action.nargs == PARSER: + value = [self._get_value(action, v) for v in arg_strings] + self._check_value(action, value[0]) + + # all other types of nargs produce a list + else: + value = [self._get_value(action, v) for v in arg_strings] + for v in value: + self._check_value(action, v) + + # return the converted value + return value + + def _get_value(self, action, arg_string): + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # ArgumentTypeErrors indicate errors + except ArgumentTypeError: + name = getattr(action.type, '__name__', repr(action.type)) + msg = str(_sys.exc_info()[1]) + raise ArgumentError(action, msg) + + # TypeErrors or ValueErrors also indicate errors + except (TypeError, ValueError): + name = getattr(action.type, '__name__', repr(action.type)) + msg = _('invalid %s value: %r') + raise ArgumentError(action, msg % (name, arg_string)) + + # return the converted value + return result + + def _check_value(self, action, value): + # converted value must be one of the choices (if specified) + if action.choices is not None and value not in action.choices: + tup = value, ', '.join(map(repr, action.choices)) + msg = _('invalid choice: %r (choose from %s)') % tup + raise ArgumentError(action, msg) + + # ======================= + # Help-formatting methods + # ======================= + def format_usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + return formatter.format_help() + + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + + def format_version(self): + import warnings + warnings.warn( + 'The format_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + formatter = self._get_formatter() + formatter.add_text(self.version) + return formatter.format_help() + + def _get_formatter(self): + return self.formatter_class(prog=self.prog) + + # ===================== + # Help-printing methods + # ===================== + def print_usage(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_help(), file) + + def print_version(self, file=None): + import warnings + warnings.warn( + 'The print_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + self._print_message(self.format_version(), file) + + def _print_message(self, message, file=None): + if message: + if file is None: + file = _sys.stderr + file.write(message) + + # =============== + # Exiting methods + # =============== + def exit(self, status=0, message=None): + if message: + self._print_message(message, _sys.stderr) + _sys.exit(status) + + def error(self, message): + """error(message: string) + + Prints a usage message incorporating the message to stderr and + exits. + + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(_sys.stderr) + self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/digsby/i18n/buildout/setup.py b/digsby/i18n/buildout/setup.py new file mode 100644 index 0000000..d32b9fa --- /dev/null +++ b/digsby/i18n/buildout/setup.py @@ -0,0 +1,213 @@ +#__LICENSE_GOES_HERE__ +# -*- coding: utf-8 -*- +from babel.messages.mofile import write_mo +from babel.messages.pofile import read_po +from path import path +import sys +import zipfile +import syck +import babel +from package import cd +from util.net import wget +import traceback + +sys.path.append('..') +import argparse +from babel_mod import babelgen_ext +sys.path.append('../../build') +from buildutil.buildfileutils import tardep + +bzr = tardep(url='http://launchpad.net/bzr/2.3/2.3.1/+download/', + tar='bzr-2.3.1', + ext='.tar.gz', + size=7026390, + md5='1a4367ce59a2880f321ecb882e195856') + +try: + import bzrlib +except ImportError: + sys.path.append('./' + bzr.dirname) + try: + import bzrlib + except ImportError: + bzr.get() + import bzrlib +import bzrlib.workingtree + +def scan_po(dir): + for p in path(dir).walk('*.po'): + domain = p.parent.basename() + locale = p.namebase + yield domain, locale, p + +def read_pos(dir): + for domain, locale, pofile in scan_po(dir): + with pofile.open('r') as f: + yield domain, locale, pofile, read_po(f)#, domain=domain, locale=locale) + +def versioned_pos(dir): + for domain, locale, pofile, catalog in read_pos(dir): + version_message = catalog.get('__version__', '__metadata__') + if version_message: + for c in version_message.auto_comments: + if c.startswith('$Rev: '): + yield domain, locale, pofile, catalog, c[len('$Rev: '):-len(' $')] + break + +def enumerate_revisions(src): + wt = bzrlib.workingtree.WorkingTree.open_containing(src)[0] + b = wt.branch + wt.lock_read() + basis_tree = wt.basis_tree() + basis_tree.lock_read() + revisions = {} + revnos = {} + try: + for info in basis_tree.list_files(include_root=True): + rev_id = info[-1].revision + if rev_id not in revnos: + revnos[rev_id] = b.revision_id_to_revno(rev_id) + revno = revnos[rev_id] + revisions[path(info[0]).expand()] = (rev_id, revno) + finally: + basis_tree.unlock() + wt.unlock() + return revisions + +def info_yaml(feature, domain, locale): + d = {} + d['type'] = 'lang' + d['language'] = locale.split('_')[0] +# d['country'] = resolve alias + d['shortname'] = '-'.join([feature, domain, locale]) +# d['name'] = '-'.join() + d['name'] = d['shortname'] + d['domain'] = domain + return d + +INFOYAML = 'info.yaml' +ZIP_DIST = ['zip'] + +def httprelpath(pth): + return '/'.join(['.'] + pth.splitall()[1:]) + +''' +how do we encode the catalog for distribution? +we only need to arrive at a valid Translations class, +since that is where all translations are coming from. +we have the routines for turning po-files into mo-files +and mo-files into Translations instances, so we can definitely +go directly from po to Translations +Advantage to using POs: + no extra step/extra file. + user can edit. (good for translators) +Disadvantage: + Slower. Significantly? unlikely. + user can edit. (bad for users) +Possible Solution: + Build both, potentially give translators the fast path. + Will also allow Digsby to run updated translations w/o compile. + ^probably good for devs. +''' +def run(args): + src = path(args.src) + revs = enumerate_revisions(src) + dist = path(args.dist) + + feature_pth = dist / args.feature + + from StringIO import StringIO + from collections import defaultdict + from util.primitives.structures import oset + versions = oset() + groups = defaultdict(list) + for domain, locale, pofile, catalog, template_version in versioned_pos('.'): + versions.add(template_version) + groups[template_version].append((domain, locale, pofile, catalog)) + + for template_version in versions: + plugins = {} + template_root = feature_pth / template_version + for domain, locale, pofile, catalog in groups[template_version]: + revid, revno = revs[src.relpathto(pofile).expand()] + out_zip = template_root / locale / '-'.join([domain, template_version, locale, str(revno)]) + '.zip' + if not out_zip.parent.isdir(): + out_zip.parent.makedirs() + mobuf = StringIO() + write_mo(mobuf, catalog) + zbuf = StringIO() + z = zipfile.ZipFile(zbuf, 'w', zipfile.ZIP_DEFLATED) + z.writestr('-'.join([domain, locale]) + '.mo', mobuf.getvalue()) + infoyaml = info_yaml(args.feature, domain, locale) + try: + infoyaml['name'] = u'%s (%s)' % (babel.Locale(locale).get_display_name('en'), + babel.Locale(locale).get_display_name(locale)) + except Exception: + pass + infoyaml['pot_version'] = template_version + infoyaml['bzr_revno'] = revno + infoyaml['bzr_revid'] = revid + infoyaml['catalog_format'] = 'mo' + infoyaml_bin = syck.dump(infoyaml) + z.writestr(INFOYAML, infoyaml_bin) + z.close() + zout = zbuf.getvalue() + with out_zip.open('wb') as out: + out.write(zout) + infoyaml_pth =(out_zip.parent/INFOYAML) + with infoyaml_pth.open('wb') as infoyaml_out: + infoyaml_out.write(infoyaml_bin) + plugins[infoyaml['shortname']] = dict( + meta = httprelpath(template_root.relpathto(infoyaml_pth)), + dist_types = ZIP_DIST, + zip = dict( + location = httprelpath(template_root.relpathto(out_zip)) + ) + ) + idxyaml = template_root / 'index.yaml' + idxbin = syck.dump(dict(plugins=plugins)) + with idxyaml.open('wb') as idx_out: + idx_out.write(idxbin) + update_pth = feature_pth / 'update.yaml' + with open(update_pth, 'wb') as update_out: + update_out.write(syck.dump({'all':{'release':httprelpath(feature_pth.relpathto(idxyaml))}})) + try: + site_d = syck.load(wget('http://s3.amazonaws.com/update.digsby.com/' + dist.name + '/site.yaml')) + except Exception: + traceback.print_exc() + site_d = {} + try: + featurs = site_d['features'] + except KeyError: + featurs = site_d['features'] = {} + featurs[args.feature]= { + 'name':args.name, + 'url': httprelpath(dist.relpathto(update_pth)), + } + with open(dist / 'site.yaml', 'wb') as site_out: + site_out.write(syck.dump(site_d)) +# with cd(dist.parent): +# import package +# package.upload_dir_to_s3(dist.name, +# compress = False, +# mimetypes = True) + +class Src(argparse.Action): + def __call__(self, parser, ns, value, option_string): + if not path(value).isdir(): + raise argparse.ArgumentError(self, '{path!r} is not a valid path, try "bzr co lp:~/digsby/digsby/translation-export {path!s}"'.format(path=value)) + ns.src = value + +def arguments(): + parser = argparse.ArgumentParser(description='i18n catalog compiler') + parser.add_argument('--dist', action='store', nargs='?', default='dist') + parser.add_argument('--src', action=Src, nargs='?', default='translation-export') + parser.add_argument('--feature', action='store', default='digsby-i18n') + parser.add_argument('--name', action='store', default="Digsby Internationalization") + return parser.parse_args() + +if __name__ == '__main__': + import sys + sys.argv.extend(['--dist', 'D:\\digsby-1_0_0']) + args = arguments() + run(args) diff --git a/digsby/i18n/digsby_en_LT.po b/digsby/i18n/digsby_en_LT.po new file mode 100644 index 0000000..acbdc72 --- /dev/null +++ b/digsby/i18n/digsby_en_LT.po @@ -0,0 +1,6232 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Aaron Costello , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: Digsby\n" +"Report-Msgid-Bugs-To: https://translations.launchpad.net/digsby\n" +"POT-Creation-Date: 2011-03-29 11:04-0400\n" +"PO-Revision-Date: 2011-03-29 11:03-0500\n" +"Last-Translator: Aaron Costello \n" +"Language-Team: Digsby Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: L33T\n" +"X-Poedit-Country: USA\n" +"X-Poedit-Basepath: ..\\\n" + +#. $Rev$ +msgctxt "__metadata__" +msgid "__version__" +msgstr "" + +#: res/actions.yaml:13 res/actions.yaml:126 +msgid "&Buddy Info" +msgstr "&B|_||)|)'/ 1|\\||=0" + +#: res/actions.yaml:16 res/actions.yaml:129 +msgid "&Send IM" +msgstr "&53|\\||) 1|\\/|" + +#: res/actions.yaml:19 res/actions.yaml:132 +msgid "Send &File" +msgstr "53|\\||) &F1|_3" + +#: res/actions.yaml:22 res/actions.yaml:135 +msgid "Send &Email" +msgstr "53|\\||) &3|\\/|41|_" + +#: res/actions.yaml:25 res/actions.yaml:138 +msgid "Send &SMS" +msgstr "53|\\||) &5|\\/|5" + +#: res/actions.yaml:30 res/actions.yaml:58 res/actions.yaml:74 +#: res/actions.yaml:92 res/actions.yaml:108 src/gui/imwin/imwinmenu.py:65 +msgid "&View Past Chats" +msgstr "&V13\\/\\/ P457 (#472" + +#: res/actions.yaml:33 res/actions.yaml:143 +msgid "&Alert Me When..." +msgstr "&4|_3|27 |\\/|3 \\/\\/#3|\\|..." + +#: res/actions.yaml:38 +msgid "Re&name" +msgstr "&R3&N4|\\/|3" + +#: res/actions.yaml:41 res/actions.yaml:61 res/actions.yaml:77 +#: res/actions.yaml:117 +msgid "Bl&ock" +msgstr "|3|_&0(|<" + +#: res/actions.yaml:45 res/actions.yaml:65 res/actions.yaml:81 +#: res/actions.yaml:121 +msgid "Unbl&ock" +msgstr "|_||\\|&B|_0(|<" + +#: res/actions.yaml:48 res/actions.yaml:313 res/actions.yaml:325 +#: res/actions.yaml:335 res/actions.yaml:346 res/actions.yaml:352 +#: res/actions.yaml:360 src/gui/widgetlist.py:43 +#: src/plugins/twitter/twitter_gui.py:1383 +msgid "&Delete" +msgstr "&D3|_373" + +#: res/actions.yaml:97 +msgid "Resend authorization" +msgstr "|2353|\\||) 4|_|7|-|0|2124710|\\|" + +#: res/actions.yaml:100 +msgid "Re-request authorization" +msgstr "|23-|23Q|_|357 4|_|7|-|0|2124710|\\|" + +#: res/actions.yaml:103 +msgid "Remove authorization" +msgstr "|23|\\/|0\\/3 4|_|7|-|0|2124710|\\|" + +#: res/actions.yaml:111 +msgid "Appear offline" +msgstr "4PP34|2 0|=|=|_1|\\|3" + +#: res/actions.yaml:114 +msgid "Appear online" +msgstr "4PP34|2 0|\\||_1|\\|3" + +#: res/actions.yaml:148 +msgid "Re&name && Rearrange..." +msgstr "|23&N4|\\/|3 |\\| R34|2|24|\\|63" + +#: res/actions.yaml:151 +msgid "Split Merged Contact..." +msgstr "5P|_17 |\\/|3|263|) (0|\\|74(7..." + +#: res/actions.yaml:162 src/gui/buddylist/buddylist.py:1011 +msgid "&Rename Group" +msgstr "&R3|\\|4|\\/|3 6|20|_|P" + +#: res/actions.yaml:165 src/gui/buddylist/buddylist.py:1012 +msgid "&Delete Group" +msgstr "&D3|_373 6|20|_|p" + +#: res/actions.yaml:169 src/gui/buddylist/buddylist.py:1013 +msgid "Add &Group" +msgstr "4|)|) &6|20|_|P" + +#: res/actions.yaml:174 src/gui/accountslist.py:136 +#: src/gui/uberwidgets/connectionlist.py:462 +msgid "&Connect" +msgstr "&(0|\\||\\|3(7" + +#: res/actions.yaml:177 +msgid "&Disconnect" +msgstr "&D15(0|\\||\\|3(7" + +#: res/actions.yaml:186 +msgid "&Format Screen Name" +msgstr "&F0|2|\\/|47 5(|233|\\| |\\|4|\\/|3" + +#: res/actions.yaml:190 +msgid "&Update Email Address" +msgstr "&UP|)473 3|\\/|41|_ 4|)|)|2355" + +#: res/actions.yaml:194 +msgid "&Confirm Account" +msgstr "&C0|\\||=1|2|\\/| 4((0|_||\\|7" + +#: res/actions.yaml:197 +msgid "Change &Password (URL)" +msgstr "(#4|\\|63 &P455\\/\\/0|2|) (URL)" + +#: res/actions.yaml:200 +msgid "&IM Forwarding (URL)" +msgstr "&1|\\/| |=0|2\\/\\/0|2|)1|\\|6 (URL)" + +#: res/actions.yaml:203 +msgid "&AOL Alerts (URL)" +msgstr "&40|_ 4|_3|272 (|_||2|_)" + +#: res/actions.yaml:206 +msgid "&My Page on ICQ.com (URL)" +msgstr "&|\\/|'/ P463 0|\\| ICQ.com (|_||2|_)" + +#: res/actions.yaml:218 +msgid "Set Priority" +msgstr "537 P|210|217'/" + +#: res/actions.yaml:234 +msgid "Change &Password" +msgstr "(#4|\\|63 &P455\\/\\/0|2|)" + +#: res/actions.yaml:244 +msgid "&Search for a Contact" +msgstr "&534|3(|-| 4 (0|\\|74(7" + +#: res/actions.yaml:247 +msgid "Address Book" +msgstr "4|)|)|2355 |300|<" + +#: res/actions.yaml:250 +msgid "Mobile Settings" +msgstr "|\\/|0|31|_3 53771|\\|62" + +#: res/actions.yaml:253 +msgid "My Account" +msgstr "|\\/|'/ 4((0|_||\\|7" + +#: res/actions.yaml:256 src/msn/msngui.py:35 +msgid "Create Group Chat" +msgstr "(|23473 6|20|_|P (#47" + +#: res/actions.yaml:276 +msgid "My Account Info (URL)" +msgstr "|\\/|'/ 4((0|_||\\|7 1|\\||=0 (URL)" + +#: res/actions.yaml:279 +msgid "My Web Profile (URL)" +msgstr "|\\/|'/ \\/\\/3|3 P|20|=1|_3 (|_||2|_)" + +#: res/actions.yaml:283 +msgid "Update presence" +msgstr "|_|P|)473 P|2353|\\|(3" + +#: res/actions.yaml:287 +msgid "Open &Inbox" +msgstr "0P3|\\| &1|\\||30><" + +#: res/actions.yaml:290 +msgid "C&ompose..." +msgstr "(&0|\\/|P053..." + +#: res/actions.yaml:295 src/plugins/facebook/actions.yaml:17 +#: src/plugins/linkedin/actions.yaml:13 src/plugins/myspace/actions.yaml:16 +#: src/plugins/twitter/twitter_gui.py:2044 +msgid "&Rename" +msgstr "&R3|\\|4|\\/|3" + +#: res/actions.yaml:300 src/plugins/linkedin/actions.yaml:2 +msgid "&Check Now" +msgstr "(#3(|<1|\\|' |\\|0\\/\\/..." + +#: res/actions.yaml:303 src/plugins/facebook/actions.yaml:4 +msgid "&Tell Me Again" +msgstr "&73|_|_ |\\/|3 4641|\\|" + +#: res/actions.yaml:307 res/actions.yaml:319 res/actions.yaml:331 +#: res/actions.yaml:342 res/actions.yaml:350 res/actions.yaml:356 +#: src/gui/filetransfer/filetransferlist.py:473 +msgid "&Open" +msgstr "&0P3|\\|" + +#: res/actions.yaml:309 res/actions.yaml:321 res/actions.yaml:333 +#: res/actions.yaml:344 res/actions.yaml:358 +msgid "Mark as &Read" +msgstr "|\\/|4|2|< 42 &R34|)" + +#: res/actions.yaml:311 res/actions.yaml:323 +msgid "&Archive" +msgstr "&4|2(#1\\/3" + +#: res/actions.yaml:315 res/actions.yaml:327 res/actions.yaml:337 +#: res/actions.yaml:362 +msgid "Report &Spam" +msgstr "|23P0|27 &5P4|\\/|" + +#: res/actions.yaml:366 src/plugins/facebook/actions.yaml:2 +#: src/plugins/myspace/actions.yaml:2 src/plugins/twitter/twitter_gui.py:1499 +msgid "&Update Now" +msgstr "&|_|P|)473 |\\|0\\/\\/" + +#: res/actions.yaml:369 src/plugins/facebook/actions.yaml:6 +#: src/plugins/linkedin/actions.yaml:4 src/plugins/myspace/actions.yaml:5 +msgid "&Home" +msgstr "#0|\\/|3" + +#: res/actions.yaml:372 src/plugins/linkedin/actions.yaml:10 +msgid "&Profile" +msgstr "P|20|=1|_3" + +#: res/actions.yaml:375 src/plugins/linkedin/actions.yaml:6 +#: src/plugins/myspace/actions.yaml:7 +msgid "&Inbox" +msgstr "1|\\||30><" + +#: res/actions.yaml:378 src/plugins/facebook/actions.yaml:10 +#: src/plugins/myspace/actions.yaml:9 +msgid "&Friends" +msgstr "|=|213|\\||)2:" + +#: res/actions.yaml:381 src/plugins/myspace/actions.yaml:11 +msgid "&Blog" +msgstr "&B|_06" + +#: res/actions.yaml:384 +msgid "B&ulletins" +msgstr "&B|_||_|_371|\\|2" + +#: res/actions.yaml:387 src/plugins/facebook/actions.yaml:20 +#: src/plugins/linkedin/actions.yaml:16 src/plugins/myspace/actions.yaml:19 +msgid "&Set Status" +msgstr "537 5747|_|5" + +#: res/actions.yaml:391 src/gui/imwin/imwin_email.py:51 +#: src/gui/imwin/imwin_email.py:57 src/gui/imwin/imwin_email.py:115 +msgid "Edit..." +msgstr "3|)17..." + +#: res/actions.yaml:398 src/gui/status.py:742 src/gui/widgetlist.py:61 +msgid "Delete" +msgstr "|)3|_373" + +#: res/notificationview.yaml:1 +msgid "Contact Signs On" +msgstr "(0|\\|74(72 516|\\|2 0|=|=" + +#: res/notificationview.yaml:6 +msgid "Contact Signs Off" +msgstr "(0|\\|74(72 516|\\|2 0|\\|" + +#: res/notificationview.yaml:10 +msgid "Contact Becomes Available" +msgstr "|_|P|)473 4\\/41|_4|3|_3" + +#: res/notificationview.yaml:15 +msgid "Contact Goes Away" +msgstr "(0|\\|74(7 |_4'/0|_|7" + +#: res/notificationview.yaml:20 +msgid "Contact Changes Status Message" +msgstr "|)3|_373 S747|_|2 |\\/|355463" + +#: res/notificationview.yaml:25 +msgid "Contact Returns From Idle" +msgstr "(0|\\|74(7 |237|_||2|\\|2 |=|20|\\/| 1|)|_3" + +#: res/notificationview.yaml:29 +msgid "Contact Becomes Idle" +msgstr "(0|\\|74(7 |\\|4|\\/|3:" + +#: res/notificationview.yaml:33 +msgid "IM Received" +msgstr "|23(31\\/3|) " + +#: res/notificationview.yaml:39 +msgid "IM Received (not in focus)" +msgstr "1|\\/| |23(31\\/3|) (|\\|07 1|\\| |=0(|_|5)" + +#: res/notificationview.yaml:45 +msgid "IM Received (hidden to tray)" +msgstr "1|\\/| |23(31\\/3|) (#1|)|)3|\\| 2 7|24'/)" + +#: res/notificationview.yaml:53 +msgid "IM Received (first message)" +msgstr "1|\\/| |23(31\\/3|) (|=1|257 |\\/|355463)" + +#: res/notificationview.yaml:59 +msgid "IM Sent" +msgstr "53|\\|7 " + +#: res/notificationview.yaml:64 +msgid "Group Chat Invite" +msgstr "(#47 1|\\|\\/173" + +#: res/notificationview.yaml:74 +msgid "File Transfer Request" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: res/notificationview.yaml:85 +msgid "File Transfer Complete" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: res/notificationview.yaml:91 +msgid "File Transfer Error" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: res/notificationview.yaml:97 +msgid "New Email Arrives" +msgstr "3|\\/|41|_ 4||)|)|255" + +#: res/notificationview.yaml:105 +msgid "Error Occurs" +msgstr "3|2|20|2 0((|_||22" + +#: res/notificationview.yaml:112 +msgid "Downloading dictionary" +msgstr "|)0\\/\\/|\\||_04|) |)1(710|\\|4|2Y?" + +#: src/AccountManager.py:266 +msgid "Digsby is running in \"Local Mode\"" +msgstr "|)165|3'/ B |2|_||\\||\\|1|\\|6 1|\\| \"|_0(4|_ |\\/|0|)3\"" + +#: src/AccountManager.py:268 +msgid "Changes to Digsby preferences may not synchronize to your other PCs right away" +msgstr "(#4|\\|632 2 |)165|3'/ P|23|=3|23|\\|(32 |\\/|4'/ |\\|07 5'/|\\|(#|20|\\|123 2 '/0 07#3|2 P(2 |216#7 4\\/\\/4Y" + +#: src/AccountManager.py:800 src/gui/social_status_dialog.py:592 +#: src/imaccount.py:517 src/plugins/twitter/twitter.py:1210 +#: src/social/network.py:69 src/social/network.py:74 src/social/network.py:76 +#: src/social/network.py:77 +msgid "Retry" +msgstr "|237|2'/" + +#: src/AccountManager.py:801 src/imaccount.py:518 src/imaccount.py:519 +#: src/imaccount.py:520 src/social/network.py:75 +msgid "Reconnect" +msgstr "|23(0|\\||\\|3(7" + +#: src/AccountManager.py:802 src/hub.py:80 src/jabber/JabberChat.py:263 +#: src/plugins/digsby_status/status_tag_urls.py:113 +#: src/plugins/digsby_updater/updater.py:577 +#: src/plugins/facebook/fbacct.py:370 +#: src/plugins/myspace/MyspaceProtocol.py:435 +#: src/plugins/researchdriver/researchtoast.py:37 +#: src/plugins/twitter/twitter.py:1212 +msgid "Close" +msgstr "(|_053" + +#: src/AccountManager.py:803 src/imaccount.py:516 src/social/network.py:67 +msgid "Edit Account" +msgstr "3|)17 4((0|_||\\|7" + +#: src/AccountManager.py:814 src/common/Protocol.py:35 src/digsbysplash.py:39 +#: src/digsbysplash.py:655 src/digsbysplash.py:659 src/digsbysplash.py:729 +msgid "Authentication Error" +msgstr "0# |\\|035! 4|_|7|-|3|\\|71(4710|\\| 3|2|20|2" + +#: src/AccountManager.py:817 +msgid "Mailbox does not exist" +msgstr "|\\/|41|_|30>< |)032 |\\|07 ><157" + +#: src/AccountManager.py:820 +msgid "This account has been signed on from another location" +msgstr "|)12 4((0|_||\\|7 #42 |333|\\| 516|\\|3|) 0|\\| |=|20|\\/| 4|\\|07#3|2 |_0(4710|\\|" + +#: src/AccountManager.py:823 +msgid "Connection to server lost" +msgstr "(0|\\||\\|3(710|\\| 2 53|2\\/3|2 |_057" + +#: src/AccountManager.py:826 +msgid "Failed to connect to server" +msgstr "|=41|_3|) 2 (0|\\||\\|3(7 2 53|2\\/3|2" + +#: src/AccountManager.py:829 +msgid "Could not connect because you are signing on too often. Please wait 20 minutes before trying to log in again." +msgstr "(0|_||_|) |\\|07 (0|\\||\\|3(7 (02 J00 12 516|\\|1|\\|6 0|\\| 700 0|=73|\\|. P|_3453 \\/\\/417 20 |\\/|1|\\||_|732 |33|=0|23 7|2'/1|\\|6 2 |_06 1|\\| 4641|\\|." + +#: src/AccountManager.py:848 +msgid "{protocol_name} Error" +msgstr "{protocol_name} 3|2|20|2" + +#: src/common/AchievementMixin.py:105 +#, python-format +msgid "%(num_accounts)d account" +msgid_plural "%(num_accounts)d accounts" +msgstr[0] "%(num_accounts)d 4((0|_||\\|7" +msgstr[1] "%(num_accounts)d 4((0|_||\\|72" + +#: src/common/Conversation.py:12 +msgid "[Auto-Response] {message}" +msgstr "[Auto-Response] {message}" + +#: src/common/Conversation.py:46 +msgid "Disconnected" +msgstr "|)15(0|\\||\\|3(73|)" + +#: src/common/Conversation.py:114 +msgid "Some of the messages you sent may not have been received." +msgstr "50|\\/|3 0|= 73# |\\/|3554632 J00 53|\\|7 |\\/|4'/ |\\|07 #4\\/3 |333|\\| |23(31\\/3|)." + +#: src/common/Conversation.py:349 +msgid "{name} joined the group chat" +msgstr "{name} (|-|47 J01|\\|" + +#: src/common/Conversation.py:353 +msgid "{name} left the group chat" +msgstr "{name} |_3|=7 73|-| 6|20|_|P" + +#: src/common/Conversation.py:371 +#, python-format +msgid "%(name)s wants to have an Audio/Video chat. Send them an invite." +msgstr "%(name)s \\/\\/4|\\|72 2 #4\\/3 |\\| 4|_||)10/\\/1|)30 (#47. 53|\\||) 7#3|\\/| |\\| 1|\\|\\/173." + +#: src/common/Protocol.py:25 +msgid "Online" +msgstr "0|\\||_1|\\|3" + +#: src/common/Protocol.py:26 src/digsbysplash.py:517 +msgid "Connecting..." +msgstr "(0|\\||\\|3(71|\\/|4(471|\\|'..." + +#: src/common/Protocol.py:27 +msgid "Authenticating..." +msgstr "4|_|7#3|\\|71(471|\\|6..." + +#: src/common/Protocol.py:28 +msgid "Loading Contact List..." +msgstr "|_04|)1|\\|6 (0|\\|74(7 |_157..." + +#: src/common/Protocol.py:29 src/common/statusmessage.py:35 +#: src/common/statusmessage.py:226 src/contacts/buddylistfilters.py:16 +#: src/gui/statuscombo.py:476 src/oscar/OscarBuddies.py:47 +msgid "Offline" +msgstr "0|=|=|_1|\\|3" + +#: src/common/Protocol.py:30 +msgid "Checking Mail..." +msgstr "(#3(|<1|\\|' |\\/|41|_..." + +#: src/common/Protocol.py:31 +msgid "Initializing..." +msgstr "1|\\|1714|_121|\\|6..." + +#: src/common/Protocol.py:36 +msgid "You have signed in from another location" +msgstr "|)1|) |_| 516|\\| 1|\\| |=|20|\\/| 4|\\|07#3|2 |_0(4710|\\|" + +#: src/common/Protocol.py:37 src/digsbysplash.py:40 src/digsbysplash.py:731 +msgid "Connection Lost" +msgstr "0# |\\|035 (0|\\||\\|3(710|\\| |_057" + +#: src/common/Protocol.py:38 src/digsbysplash.py:651 src/digsbysplash.py:668 +#: src/digsbysplash.py:723 src/digsbysplash.py:733 src/digsbysplash.py:742 +msgid "Failed to Connect" +msgstr "(0|\\||\\|3(7 |=41|_!!!110|\\|33|_3\\/3|\\|7'/" + +#: src/common/Protocol.py:39 +msgid "You are signing in too often" +msgstr "|_| |2 516|\\|1|\\|6 1|\\| 2 0|=73|\\|" + +#: src/common/Protocol.py:40 +msgid "This account does not have a mailbox" +msgstr "7|-|15 4((0|_||\\|7 |)032 |\\|07 |-|4\\/34 |\\/|41|_|30><" + +#: src/common/Protocol.py:41 +msgid "Failed to Initialize" +msgstr "|=41|_3|) 2 1|\\|1714|_123" + +#: src/common/Protocol.py:42 +#, python-format +msgid "Connection Failed. Retry in %s" +msgstr "(0|\\||\\|3(710|\\| |=41|_3|). |237|2'/ 1 |\\| %s" + +#: src/common/Protocol.py:43 +msgid "Internal Server Error" +msgstr "1|\\|73|2|\\|4|_ 53|2\\/3|2 3|2|20|2" + +#: src/common/Protocol.py:457 +msgid "Error inviting {name}: they are offline" +msgstr "3|2|20|2 1|\\|\\/171|\\| {name}: 7#3'/ 12 0|=|=|_1|\\|3" + +#: src/common/Protocol.py:459 +msgid "Error inviting {name}: they do not support group chat" +msgstr "3|2|20|2 1|\\|\\/171|\\|6 {name}: 7#3'/ |)0 |\\|07 5|_|PP0|27 6|20|_|P (#47" + +# %s +#: src/common/accountbase.py:45 src/contacts/Contact.py:96 +#, python-format +msgid "Enter an alias for %s:" +msgstr "3|\\|73|2 |\\| 4|_142 4 %s:" + +#: src/common/accountbase.py:46 src/contacts/Contact.py:97 +#, python-format +msgid "Rename %s" +msgstr "|23|\\|4|\\/|3 %s" + +#: src/common/actions.py:149 +msgid "No actions" +msgstr "|\\|0 4(710|\\|2" + +#: src/common/emailaccount.py:296 +msgid "Email Client" +msgstr "3|\\/|41|_(|_13|\\|7" + +#: src/common/emailaccount.py:474 +msgid "No unread mail" +msgstr "|\\|0 |_||\\||234|) |\\/|41|_" + +#: src/common/emailaccount.py:555 +msgid "No System Email Client" +msgstr "|\\|0 5'/573|\\/| 3|\\/|41|_ (|_13|\\|7" + +#: src/common/emailaccount.py:556 +msgid "No system email client is configured." +msgstr "|\\|0 5'/573|\\/| 3|\\/|41|_ (|_13|\\|7 |3 (0|\\||=16|_||23|)." + +#: src/common/emailaccount.py:561 +msgid "Could not start system mail client." +msgstr "(0|_||_|) |\\|07 574|27 5'/573|\\/| |\\/|41|_ (|_13|\\|7." + +#: src/common/emailaccount.py:563 +msgid "Error launching system mail client" +msgstr "3|2|20|2 |_4|_||\\|(#1|\\|6 5Y573|\\/| |\\/|41|_ (|_13|\\|7" + +#: src/common/emailaccount.py:626 +#, python-format +msgid "Could not start system mail client %r." +msgstr "(0|_||_|) |\\|07 574|27 5'/573|\\/| |\\/|41|_ (|_13|\\|7 %r." + +#: src/common/filetransfer.py:73 +msgid "{completed} of {size} ({speed}/sec) -- {time} remain" +msgstr "{completed} 0|= {size} ({speed}/sec) -- {time} |23|\\/|41|\\|" + +#: src/common/filetransfer.py:74 +msgid "Received from {name}" +msgstr "|23(31\\/3|0 |=|20|\\/| {name}" + +#: src/common/filetransfer.py:74 +msgid "Sent to {name}" +msgstr "53|\\|7 2 {name}" + +#: src/common/filetransfer.py:75 +msgid "Connecting to {name}..." +msgstr "(0|\\||\\|3(71|\\|' 2 {name}..." + +#: src/common/filetransfer.py:76 src/common/filetransfer.py:79 +msgid "Waiting for {name} to accept file" +msgstr "\\/\\/4171|\\|' 4 {name} 2 4((3P7 |=1|_3" + +#: src/common/filetransfer.py:78 +msgid "Canceled by {name}" +msgstr "(4|\\|(3|_|_3|) |3'/ {name}" + +#: src/common/filetransfer.py:80 +msgid "Failed during transfer from {name}" +msgstr "|=41|_3d |)|_||21|\\|6 7|24|\\|5|=3|2 |=|20|\\/| {name}" + +#: src/common/filetransfer.py:80 +msgid "Failed during transfer to {name}" +msgstr "|=41|_3d |)|_||21|\\|6 7|24|\\|5|=3|2 2 {name}" + +#: src/common/filetransfer.py:81 +msgid "Failed to connect to {name}" +msgstr "|=41|_3|) 2 (0|\\||\\|3(7 2 {name}" + +#: src/common/filetransfer.py:82 +msgid "File size too big for proxy transfer" +msgstr "|=1|_3 5123 700 |316 4 P|20><'/ 7|24|\\|5|=3|2" + +#: src/common/filetransfer.py:99 +msgid "Open" +msgstr "0P3|\\|" + +#: src/common/filetransfer.py:100 src/gui/imagedialog.py:12 +#: src/imaccount.py:521 src/imaccount.py:524 src/imaccount.py:525 +#: src/imaccount.py:526 src/plugins/digsby_updater/UpdateProgress.py:147 +#: src/plugins/twitter/twitter_gui.py:1621 +msgid "Cancel" +msgstr "(4|\\|(3|_" + +#: src/common/filetransfer.py:101 src/gui/accountslist.py:40 +#: src/gui/imwin/imwin_tofrom.py:341 src/gui/pref/pg_accounts.py:631 +#: src/plugins/digsby_updater/UpdateProgress.py:144 +#: src/plugins/twitter/twitter_gui.py:1948 +#: src/tests/testgui/uberdemos/UberComboXTDemo.py:32 +msgid "Remove" +msgstr "|23|\\/|0\\/3" + +#: src/common/filetransfer.py:104 +msgid "Save" +msgstr "54\\/3" + +#: src/common/filetransfer.py:105 +msgid "Save as..." +msgstr "54\\/3 45..." + +#: src/common/filetransfer.py:106 +msgid "Reject" +msgstr "|23J3(7" + +#: src/common/filetransfer.py:243 +msgid "Directory does not exist" +msgstr "|)1|23(70|2'/ |)032 |\\|07 ><157" + +#: src/common/filetransfer.py:273 +msgid "Choose a location to save" +msgstr "(#0053 @ |_0(4710|\\| 2 54\\/3" + +#: src/common/filetransfer.py:356 +msgid "Failed to receive {filename} from {name}" +msgstr "|=41|_3|) 2 |23(31\\/3 {filename} |=|20|\\/| {name}" + +#: src/common/filetransfer.py:356 +msgid "Failed to send {filename} to {name}" +msgstr "|=41|_3|) 2 53|\\||) {filename} 2 {name}" + +#: src/common/protocolmeta.py:42 src/common/statusmessage.py:31 +#: src/common/statusmessage.py:32 src/common/statusmessage.py:222 +#: src/gui/native/win/jumplist.py:129 src/gui/status.py:59 +#: src/gui/statuscombo.py:501 src/jabber/JabberResource.py:115 +#: src/plugins/component_gtalk/info.yaml:6 src/plugins/fbchat/info.yaml:17 +#: src/plugins/provider_aol/info.yaml:92 +#: src/plugins/provider_aol/info.yaml:137 +#: src/plugins/provider_jabber/info.yaml:48 +#: src/plugins/provider_windows_live/info.yaml:30 +msgid "Available" +msgstr "4\\/41|_4|3|_3" + +#: src/common/protocolmeta.py:42 src/plugins/provider_aol/info.yaml:138 +#: src/plugins/provider_jabber/info.yaml:49 +msgid "Free For Chat" +msgstr "|=|233 4 (#47" + +#: src/common/protocolmeta.py:43 src/common/statusmessage.py:33 +#: src/common/statusmessage.py:223 src/gui/native/win/jumplist.py:130 +#: src/gui/status.py:60 src/gui/statuscombo.py:517 src/gui/statuscombo.py:522 +#: src/oscar/OscarProtocol.py:1088 src/plugins/component_gtalk/info.yaml:7 +#: src/plugins/provider_aol/info.yaml:93 +#: src/plugins/provider_aol/info.yaml:139 +#: src/plugins/provider_jabber/info.yaml:50 +#: src/plugins/provider_windows_live/info.yaml:31 +msgid "Away" +msgstr "4\\/\\/4'/" + +#: src/common/protocolmeta.py:43 src/jabber/JabberResource.py:14 +#: src/plugins/provider_aol/info.yaml:140 +#: src/plugins/provider_jabber/info.yaml:51 +msgid "Do Not Disturb" +msgstr "|)0 |\\|07 |)157|_||2|3" + +#: src/common/protocolmeta.py:43 src/jabber/JabberResource.py:16 +#: src/plugins/provider_jabber/info.yaml:52 +msgid "Extended Away" +msgstr "><73|\\||)3|) 4\\/\\/4'/" + +#: src/common/spelling/spellcheck.py:471 +msgid "You need to download the {langname} dictionary to use it. Would you like to download this dictionary now?" +msgstr "J00 |\\|33|) 2 |)0\\/\\/|\\||_04|) 73# {langname} |)1(710|\\|4|2'/ 2 |_|53 17. \\/\\/0|_||_|) J00 \\/\\/|_||3 2 |)0\\/\\/|\\||_04|) |)12 |)1(710|\\|4|2'/ |\\|0\\/\\/?" + +#: src/common/spelling/spellcheck.py:472 +msgid "Download Dictionary?" +msgstr "|)0\\/\\/|\\||_04|) |)1(710|\\|4|2Y?" + +#: src/common/spelling/spellcheck.py:482 +msgid "Dictionary not downloaded." +msgstr "|)1(710|\\|2|2'/ |\\|07 |)0\\/\\/|\\||_04|)3|)" + +#: src/common/spelling/spellcheck.py:483 +msgid "To download it later, select it in the Conversation Preferences." +msgstr "2 |)0\\/\\/|\\||_04|) 17 |_8|2, 53|_397 17 1|\\| 73# (0|\\|\\/3|254710|\\| P|23|=3|23|\\|(32." + +#: src/common/spelling/spellcheck.py:486 +msgid "Download Dictionary Canceled" +msgstr "|)0\\/\\/|\\||_04|) |)1(710|\\|4|2Y (4|\\|(3|_3|)" + +#: src/common/spelling/spellcheck.py:505 +msgid "Dictionary Set" +msgstr "|)1(710|\\|4|2'/ 537" + +#: src/common/spelling/spellcheck.py:506 +msgid "Spellcheck language has been set to {langname}." +msgstr "5P3|_|_(#3(|< |_4|\\|6|_|463 #42 |333|\\| 537 2 {langname}." + +#: src/common/spelling/spellcheck.py:511 +msgid "Spellcheck error" +msgstr "5P3|_|_(#3(|< 3|2|20|2" + +#: src/common/spelling/spellcheck.py:512 +msgid "Failed setting up dictionary. Try reselecting desired language in the preferences." +msgstr "|=41|_3|) 53771|\\|6 |_|P |)1(710|\\|4|2'/. 7|2'/ |2353|_3(71|\\|6 |)351|23|) |_4|\\|6|_|463 1|\\| 73# P|23|=3|23|\\|(32." + +#: src/common/spelling/spellcheck.py:518 src/common/spelling/spellcheck.py:676 +msgid "Dictionary Installed" +msgstr "|)1(710|\\|2|2'/ 1|\\|s74|_|_3|)" + +#: src/common/spelling/spellcheck.py:519 +msgid "You can set this language in the conversation preferences." +msgstr "J00 (4|\\| 537 |)12 |_4|\\|6|_|463 1|\\| 73# (0|\\|\\/3|254710|\\| P|23|=3|23|\\|(32." + +#: src/common/spelling/spellcheck.py:563 +msgid "Dictionary will be activated after install completes." +msgstr "|)1(710|\\|4|2'/ \\/\\/1|_|_ |3 4(71\\/473|) 4|=73|2 1|\\|574|_|_ (0|\\/|P|_3732." + +#: src/common/spelling/spellcheck.py:563 +msgid "Installing Dictionary" +msgstr "1|\\|574|_|_1|\\|' |)1(710|\\|4|2'/" + +#: src/common/spelling/spellcheck.py:676 +msgid "Setting spellcheck language..." +msgstr "53771|\\|' 5P3|_|_(#3(|< |_4|\\|6|_|463..." + +#: src/common/spelling/spellcheck.py:689 +msgid "{langname} Dictionary" +msgstr "{langname} |)1(710|\\|4|2'/" + +#: src/common/statusmessage.py:34 src/common/statusmessage.py:225 +#: src/gui/native/win/jumplist.py:131 src/gui/statuscombo.py:544 +msgid "Invisible" +msgstr "1|\\|\\/151|3|_3" + +#: src/common/statusmessage.py:138 +msgid "{truncatedtitle}..." +msgstr "{truncatedtitle}..." + +#: src/common/statusmessage.py:224 +msgid "Idle" +msgstr "1|)|_3" + +#: src/contacts/Group.py:187 +msgid "That buddy is already part of metacontact \"{alias}\" in group \"{group}.\"" +msgstr "|)47 |3|_||)|)'/ |3 4|_|234|)'/ P4|27 0|= |\\/|374(0|\\|74(7 \"{alias}\" 1|\\| 6|20|_|P \"{group}.\"" + +#: src/contacts/Group.py:189 src/gui/addcontactdialog.py:36 +#: src/gui/addcontactdialog.py:203 src/gui/capabilitiesbar.py:118 +#: src/gui/contactdialogs.py:252 +msgid "Add Contact" +msgstr "4|)|) (0|\\|74(7" + +#: src/contacts/Group.py:221 +msgid "Enter a new name for {name}:" +msgstr "3|\\|73|2 @ |\\|3\\/\\/ |\\|4|\\/|3 4 {name}:" + +#: src/contacts/Group.py:222 +msgid "Rename Group" +msgstr "|23|\\|4|\\/|3 6|20|_|P" + +#: src/contacts/buddylistfilters.py:29 src/contacts/buddyliststore.py:97 +#: src/contacts/buddyliststore.py:384 src/contacts/buddyliststore.py:645 +#: src/contacts/metacontacts.py:43 src/plugins/linkedin/LinkedInAccount.py:84 +msgid "Contacts" +msgstr "(0|\\|74(72" + +#: src/contacts/metacontacts.py:65 +msgid "Unknown" +msgstr "|_||\\||<|\\|0\\/\\/|\\|" + +#: src/contacts/metacontacts.py:820 +msgid "Are you sure you want to split up this merged contact?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 5P|_17 |_|P |)12 |\\/|3|263|) (0|\\|74(7?" + +#: src/contacts/metacontacts.py:821 +msgid "Split Contacts" +msgstr "5P|_17 (0|\\|74(72" + +#: src/contacts/sort_model.py:144 +msgid "None (custom)" +msgstr "|\\|0|\\|3 ((|_|570|\\/|)" + +#: src/contacts/sort_model.py:145 src/contacts/sort_model.py:152 +#: src/gui/pref/pg_contact_list.py:405 src/gui/pref/prefstrings.py:20 +#: src/gui/status.py:372 src/plugins/myspace/res/status.tenjin:13 +#: src/plugins/twitter/res/status.tenjin:11 +msgid "Status" +msgstr "5747|_|5" + +#: src/contacts/sort_model.py:146 src/contacts/sort_model.py:153 +msgid "Service" +msgstr "53|2\\/1(3" + +#: src/contacts/sort_model.py:149 +msgid "None" +msgstr "|\\|0|\\|3" + +#: src/contacts/sort_model.py:150 +msgid "Log Size" +msgstr "|_06 5123" + +#: src/contacts/sort_model.py:151 src/gui/vcard/vcardgui.py:18 +msgid "Name" +msgstr "|\\|4|\\/|3" + +#: src/crashgui.py:11 +msgid "Digsby Crash Report" +msgstr "|)165|3'/ (|245# |23P0|27" + +#: src/crashgui.py:12 +msgid "Digsby appears to have crashed." +msgstr "|)165|3'/ 4PP34|22 2 |-|4\\/3 |245|-|3|)." + +#: src/crashgui.py:13 +msgid "If you can, please describe what you were doing before it crashed." +msgstr "1|= J00 (4|\\|, P|_3453 |)35(|21B3 \\/\\/07 J00 \\/\\/|_|2 |)01|\\|6 |33|=0|23 17 (|245#3|)." + +#: src/crashgui.py:15 +msgid "&Send Crash Report" +msgstr "&53|\\||) (|245# |23P0|27" + +#: src/digsby/DigsbyProtocol.py:55 +msgid "Synchronizing Preferences..." +msgstr "5'/|\\|(#|20|\\|121|\\|' P|23|=3|23|\\|(32..." + +#: src/digsby/DigsbyProtocol.py:57 +msgid "Synchronizing..." +msgstr "5'/|\\|(#|20|\\|121|\\|'..." + +#: src/digsby/videochat.py:28 +msgid "Audio/Video Call with {name}" +msgstr "4|_||)10/\\/1|)30 (4|_|_ \\/\\/17# {name}" + +#: src/digsby/videochat.py:91 +#, python-format +msgid "Join me in an audio/video call: %s" +msgstr "J01|\\| |\\/|3 1|\\| |\\| 4|_||)10/\\/1|)30 (4|_|_: %s" + +#: src/digsby/videochat.py:96 +msgid "You have invited {name} to an audio/video chat." +msgstr "J00 #4\\/3 1|\\|\\/173|) {name} 2 |\\| 4|_||)10/\\/1|)30 (#47." + +#: src/digsby/videochat.py:111 +msgid "Audio/Video chat is currently unavailable." +msgstr "4|_||)10/\\/1|)30 (#47 |3 (|_||2|23|\\|7|_'/ |_||\\|4\\/41|_4|3|_3." + +#: src/digsby/videochat.py:179 +msgid "Audio/Video call ended by other party." +msgstr "4|_||)10/\\/1|)30 (4|_|_ 3|\\||)3|) |3'/ 07#3|2 P4|27'/." + +#: src/digsbysplash.py:27 +msgid "&Sign In" +msgstr "&516|\\| 1|\\| 637" + +#: src/digsbysplash.py:41 src/digsbysplash.py:736 +msgid "We are upgrading Digsby. Please try connecting again in a few minutes." +msgstr "0|-| |\\|035! |)1G5|3'/ |_3\\/3|_ |_|P G37! 7RY L473R D|_|D3" + +#: src/digsbysplash.py:42 src/digsbysplash.py:738 +msgid "Could not contact remote server. Check your network configuration." +msgstr "(0|_||_|) |\\|07 (0|\\|74(7 |23|\\/|073 53|2\\/3|2. (|-|3(|< |_||2 1|\\|73|2\\/\\/3|32 (0|\\||=16." + +#: src/digsbysplash.py:46 +msgid "Invalid Digsby Username" +msgstr "1|\\|\\/4|_1|) |)1G5|3'/ |_|53R|\\|4|\\/|3" + +#: src/digsbysplash.py:322 +msgid "Update Successful" +msgstr "607 73|-| |31663|2 6|3s" + +#: src/digsbysplash.py:496 src/digsbysplash.py:776 +msgid "Loading..." +msgstr "|\\|40 |_04|)2..." + +#: src/digsbysplash.py:538 +msgid "Login error!" +msgstr "1 4|\\/| 3|2|20|2! |_0650|\\|" + +#: src/digsbysplash.py:592 +msgid "Your password will be saved on your portable device." +msgstr "'/0|_||2 P455\\/\\/0|2|) \\/\\/1|_|_ |33 54\\/3|) 0|\\| '/0|_||2 P0|274|3|_3 |)3\\/1(e." + +#: src/digsbysplash.py:593 +msgid "Anyone with access to this device will be able to log into your Digsby account. Are you sure you want to save your password?" +msgstr "|_| |33 7RY1|\\| 50|\\/|3 R1Z|<><0|2 |=4><><0|22" + +#: src/digsbysplash.py:657 +msgid "Please make sure you have entered your Digsby username and password correctly." +msgstr "|_|53|2|\\|4|\\/|3 || P455\\/\\/0|2|)2 |=41|_! RTFM |_061|\\| 5(|233|\\| |_1|\\||<463" + +#: src/digsbysplash.py:658 +msgid "If you need an account or forgot your password, use the links on the login screen." +msgstr "|=0|2637 P455\\/\\/4|2 0|2 |\\|33|0 4((7? |_|53 |_1|\\||<2 0|\\| |_061|\\| 5(|233|\\|." + +#: src/digsbysplash.py:664 +msgid "" +"Please check your Internet connection and make sure a firewall isn't blocking Digsby.\n" +"If you connect to the Internet through a proxy server,\n" +"click the \"Connection Settings\" link to set up the proxy.\n" +"If you are still unable to connect, email bugs@digsby.com for technical support." +msgstr "" +"1 4|\\/| 3|2|20|2\n" +"TL;DT\n" +"5|\\/|0371|\\| |30|_|T F1|23|34|_|_Z || P0><><132" + +#: src/digsbysplash.py:766 +msgid "Register a Digsby Account" +msgstr "51|\\|6 |_|P |\\|40!" + +#: src/gui/accountdialog.py:38 +#: src/plugins/digsby_service_editor/default_ui.py:580 +msgid "System Default" +msgstr "5'/573|\\/| |)3|=4|_||_7" + +#: src/gui/accountdialog.py:39 +#: src/plugins/digsby_service_editor/default_ui.py:581 +msgid "Other Mail Client..." +msgstr "07#3|2 |\\/|41|_ (|_13|\\|7..." + +#: src/gui/accountdialog.py:40 +#: src/plugins/digsby_service_editor/default_ui.py:582 +msgid "Launch URL..." +msgstr "|_4|_||\\|(# |_||2|_..." + +#: src/gui/accountdialog.py:310 src/gui/accountdialog.py:393 +#: src/gui/contactdialogs.py:172 src/gui/notificationview.py:537 +#: src/gui/pref/iconeditor.py:552 src/gui/status.py:537 +#: src/plugins/digsby_service_editor/service_editor.py:87 +#: src/plugins/twitter/twitter_gui.py:120 +#: src/plugins/twitter/twitter_gui.py:356 +msgid "&Save" +msgstr "&54\\/3" + +#: src/gui/accountdialog.py:315 src/gui/contactdialogs.py:91 +#: src/gui/contactdialogs.py:180 src/gui/imwin/imtabs.py:125 +#: src/gui/notificationview.py:94 src/gui/notificationview.py:538 +#: src/gui/pref/iconeditor.py:554 src/gui/proxydialog.py:234 +#: src/gui/status.py:541 +#: src/plugins/digsby_service_editor/service_editor.py:90 +#: src/plugins/twitter/twitter_gui.py:129 +msgid "&Cancel" +msgstr "&(4|\\|(3|_" + +#: src/gui/accountdialog.py:321 +#: src/plugins/digsby_service_editor/service_editor.py:222 +msgid "That account already exists." +msgstr "|)47 4((0|_||\\|7 4|_|234|)'/ ><1572." + +#: src/gui/accountdialog.py:354 +msgid "&Auto login" +msgstr "&4|_|70 |_061|\\|" + +#: src/gui/accountdialog.py:355 +msgid "If checked, this account will automatically sign in when Digsby starts." +msgstr "1|= (#3(|<3|), |)12 4((0|_||\\|7 \\/\\/1|_|_ 4|_|70|\\/|471(4|_|_'/ 516|\\| 1|\\| \\/\\/#3|\\| |)165|3'/ 574|272." + +#: src/gui/accountdialog.py:361 +#: src/plugins/digsby_service_editor/default_ui.py:211 +msgid "&Register New Account" +msgstr "&R361573|2 |\\|3\\/\\/ 4((0|_||\\|7" + +#: src/gui/accountdialog.py:393 +msgid "&Register" +msgstr "&R361573|2" + +#: src/gui/accountdialog.py:580 +msgid "Check for new mail every {n} minutes" +msgstr "(#3(|< 4 |\\|3\\/\\/ |\\/|41|_ 3\\/3|2'/ {n} minutes" + +#: src/gui/accountdialog.py:591 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "|\\/|1|\\||_|73" +msgstr[1] "|\\/|1|\\||_|732" + +#: src/gui/accountdialog.py:607 +#: src/plugins/digsby_service_editor/default_ui.py:73 +#: src/plugins/digsby_service_editor/default_ui.py:382 +msgid "Mail Client:" +msgstr "|\\/|41|_ (|_13|\\|7:" + +#: src/gui/accountdialog.py:626 +#: src/plugins/digsby_service_editor/default_ui.py:394 +msgid "SMTP username/password are the same as {servertype}" +msgstr "5|\\/|7P |_|s3r|\\|4|\\/|3/P455\\/\\/0|2|) 12 73# 54|\\/|3 42 {servertype}" + +#: src/gui/accountdialog.py:627 +#: src/plugins/digsby_service_editor/default_ui.py:395 +msgid "Log on using:" +msgstr "|_06 0|\\| |_|51|\\|':" + +#: src/gui/accountdialog.py:753 +msgid "{show_topic}:" +msgstr "{show_topic}:" + +#: src/gui/accountdialog.py:783 +msgid "Custom ({mailclient_name})" +msgstr "(|_|570|\\/| ({mailclient_name})" + +#: src/gui/accountdialog.py:812 +#: src/plugins/digsby_service_editor/default_ui.py:626 +msgid "Please choose a mail client" +msgstr "P|_3453 (#0053 @ |\\/|41|_ (|_13|\\|7" + +#: src/gui/accountdialog.py:860 +#: src/plugins/digsby_service_editor/default_ui.py:403 +msgid "Username:" +msgstr "|_|53|2|\\|4|\\/|3:" + +#: src/gui/accountdialog.py:862 +#: src/plugins/digsby_service_editor/default_ui.py:404 +msgid "Password:" +msgstr "P455\\/\\/0|2|):" + +#: src/gui/accountdialog.py:890 src/plugins/facebook/info.yaml:9 +#: src/plugins/fbchat/info.yaml:11 src/plugins/linkedin/info.yaml:8 +#: src/plugins/msim/info.yaml:7 src/plugins/myspace/info.yaml:9 +#: src/plugins/provider_linkedin/info.yaml:10 +#: src/plugins/provider_myspace/info.yaml:10 +#: src/plugins/provider_windows_live/info.yaml:22 +#: src/plugins/provider_windows_live/info.yaml:37 +#: src/plugins/provider_windows_live/info.yaml:81 +msgid "Email Address" +msgstr "3|\\/|41|_ 4||)|)|255" + +#: src/gui/accountdialog.py:921 +msgid "&Display Name:" +msgstr "&D15P|_4'/ |\\|4|\\/|3:" + +#: src/gui/accountdialog.py:1003 +#: src/plugins/digsby_service_editor/default_ui.py:265 +msgid "SMTP Server:" +msgstr "5|\\/|7P 53|2\\/3|2:" + +#: src/gui/accountdialog.py:1005 src/gui/accountdialog.py:1026 +#: src/plugins/digsby_service_editor/default_ui.py:96 +msgid "Port:" +msgstr "P0|27:" + +#: src/gui/accountdialog.py:1024 +msgid "Host:" +msgstr "#057:" + +#: src/gui/accountdialog.py:1031 src/gui/infobox/htmlgeneration.py:237 +#: src/plugins/digsby_service_editor/default_ui.py:333 +msgid "Resource:" +msgstr "|2350|_||2(3:" + +#: src/gui/accountdialog.py:1033 +#: src/plugins/digsby_service_editor/default_ui.py:333 +msgid "Priority:" +msgstr "P|210|217'/:" + +#: src/gui/accountdialog.py:1038 +#: src/plugins/digsby_service_editor/default_ui.py:338 +msgid "Data Proxy:" +msgstr "|)474 P|20XY:" + +#: src/gui/accountdialog.py:1119 +#: src/plugins/digsby_service_editor/default_ui.py:662 +msgid "Enter the URL that will be launched when you click \"Inbox\" for this email account." +msgstr "3|\\|73|2 73# |_||2|_ |)47 \\/\\/1|_|_ |3 |_4|_||\\|(#3|) \\/\\/#3|\\| J00 (|_1(|< \"1|\\||30><\" 4 |)12 3|\\/|41|_ 4((0|_||\\|7." + +#: src/gui/accountdialog.py:1120 +#: src/plugins/digsby_service_editor/default_ui.py:663 +msgid "Enter the URL that will be launched when you click \"Compose\" for this email account." +msgstr "3|\\|73|2 73# |_||2|_ |)47 \\/\\/1|_|_ |3 |_4|_||\\|(#3|) \\/\\/#3|\\| J00 (|_1(|< \"(0|\\/|P053\" 4 |)12 3|\\/|41|_ 4((0|_||\\|7." + +#: src/gui/accountdialog.py:1123 +#: src/plugins/digsby_service_editor/default_ui.py:666 +msgid "Launch URL" +msgstr "|_4|_||\\|(# |_||2|_" + +#: src/gui/accountdialog.py:1136 +#: src/plugins/digsby_service_editor/default_ui.py:679 +msgid "Enter a URL for the Inbox" +msgstr "3|\\|73|2 @ |_||2|_ 4 73# 1|\\||30><" + +#: src/gui/accountdialog.py:1139 +#: src/plugins/digsby_service_editor/default_ui.py:682 +msgid "Enter a URL for the Compose window" +msgstr "3|\\|73|2 @ |_||2|_ 4 73# (0|\\/|P053 \\/\\/1|\\||)0\\/\\/" + +#: src/gui/accountslist.py:36 src/gui/pref/pg_accounts.py:627 +#: src/gui/pref/pg_privacy.py:269 src/gui/pref/pg_privacy.py:544 +#: src/gui/pref/pg_privacy.py:718 src/gui/pref/pg_privacy.py:720 +#: src/gui/status.py:738 src/gui/widgetlist.py:56 +#: src/plugins/twitter/twitter_gui.py:1947 +msgid "Edit" +msgstr "3|)17" + +#: src/gui/accountslist.py:128 src/gui/accountslist.py:309 +#: src/gui/accountslist.py:406 src/gui/anylists.py:308 src/gui/status.py:726 +#: src/gui/uberwidgets/connectionlist.py:455 src/gui/widgetlist.py:42 +#: src/plugins/twitter/twitter_gui.py:1382 +msgid "&Edit" +msgstr "&3|)17" + +#: src/gui/accountslist.py:129 src/gui/accountslist.py:310 +#: src/gui/accountslist.py:407 src/gui/anylists.py:309 +#: src/gui/filetransfer/filetransferlist.py:476 src/gui/status.py:727 +msgid "&Remove" +msgstr "&R3|\\/|0\\/3" + +#: src/gui/accountslist.py:241 +#: src/plugins/digsby_service_editor/service_editor.py:211 +#: src/plugins/provider_jabber/jabber_gui.py:85 +msgid "Are you sure you want to delete account \"{name}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 4((0|_||\\|7 \"{name}\"?" + +#: src/gui/accountslist.py:242 +#: src/plugins/digsby_service_editor/service_editor.py:212 +#: src/plugins/provider_jabber/jabber_gui.py:86 +msgid "Delete Account" +msgstr "|)3|_373 4((0|_||\\|7" + +#: src/gui/accountslist.py:340 +msgid "Are you sure you want to delete social network account \"{name}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 50(14|_ |\\|37\\/\\/0|2|< 4((0|_||\\|7 \"{name}\"?" + +#: src/gui/accountslist.py:341 +msgid "Delete Social Network Account" +msgstr "|)3|_373 S0(14|_ |\\|37\\/\\/0|2|< 4((0|_||\\|7" + +#: src/gui/accountslist.py:440 +msgid "Are you sure you want to delete email account \"{account_name}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 3|\\/|41|_ 4((0|_||\\|7 \"{account_name}\"?" + +#: src/gui/accountslist.py:441 +msgid "Delete Email Account" +msgstr "|)3|_373 3|\\/|41|_ 4((0|_||\\|7" + +#: src/gui/accountwizard.py:31 +msgid "Digsby Setup Wizard" +msgstr "|)165|3'/ 537|_|P \\/\\/124|2|)" + +#: src/gui/accountwizard.py:40 +msgid "Welcome to Digsby!" +msgstr "\\/\\/3|_(0|\\/|3 2 |)165|3'/!" + +#: src/gui/accountwizard.py:116 +msgid "Quick Access to Newsfeeds" +msgstr "Q|_|1(|< 4((355 2 |\\|3\\/\\/s|=33|)2" + +#: src/gui/accountwizard.py:117 +msgid "" +"\n" +"You can access social network and email newsfeeds by clicking their icons in the tray.\n" +"\n" +"Double click to update your status (social networks) or launch your inbox (email accounts).\n" +" \n" +msgstr "" +"\n" +"J00 (4|\\| 4((352 50(14|_ |\\|37\\/\\/0|2|< |\\| 3|\\/|41|_ |\\|3\\/\\/5|=33|)2 |3'/ (|_1(|<1|\\|' 7#31|2 1(0|\\|2 1|\\| 73# 7|24'/.\n" +"\n" +"|)0|_|b|_3 (|_1(|< 2 |_|P|)473 '/0 5747|_|2 (50(14|_ |\\|37\\/\\/0|2|<2) 0|2 |_4|_||\\|(# '/0 1|\\||30>< (3|\\/|41|_ 4((0|_||\\|72).\n" +"\n" + +#: src/gui/addcontactdialog.py:200 src/gui/chatgui.py:15 +#: src/gui/imdialogs.py:68 +msgid "No Connections" +msgstr "|\\|0 (0|\\||\\|3(710|\\|2" + +#: src/gui/addcontactdialog.py:214 src/gui/pref/prefcontrols.py:1008 +#: src/tests/testgui/uberdemos/UberComboXTDemo.py:34 +msgid "Add" +msgstr "4|)|)" + +#: src/gui/addcontactdialog.py:228 +msgid "Contact Type:" +msgstr "(0|\\|74(7 7'/P3:" + +#: src/gui/addcontactdialog.py:232 +msgid "Screen Name:" +msgstr "5(|233|\\| |\\|4|\\/|3:" + +#: src/gui/addcontactdialog.py:240 src/gui/contactdialogs.py:164 +msgid "Alias:" +msgstr "4|_142:" + +#: src/gui/addcontactdialog.py:244 +msgid "In Group:" +msgstr "1|\\| 6|20|_|P:" + +#: src/gui/addcontactdialog.py:249 +msgid "On Accounts:" +msgstr "0|\\| 4((0|_||\\|72:" + +#: src/gui/app/mainMenuEvents.py:46 src/gui/app/menubar.py:38 +#: src/gui/buddylist/buddylistmenu.py:178 +#: src/gui/buddylist/buddylistmenu.py:307 +msgid "Status Panel" +msgstr "5747|_|2 P4|\\|3|_" + +#: src/gui/app/mainMenuEvents.py:47 src/gui/buddylist/buddylistframe.py:591 +#: src/gui/buddylist/buddylistmenu.py:308 src/gui/pref/prefstrings.py:17 +#: src/main.py:504 +msgid "Buddy List" +msgstr "|3|_||)|)'/ |_157" + +#: src/gui/app/mainMenuEvents.py:48 src/gui/buddylist/buddylistmenu.py:309 +msgid "Email Accounts" +msgstr "3|\\/|41|_ 4((0|_||\\|72" + +#: src/gui/app/mainMenuEvents.py:49 src/gui/buddylist/buddylistmenu.py:310 +msgid "Social Networks" +msgstr "50(14|_ |\\|37\\/\\/0|2|<2" + +#: src/gui/app/mainMenuEvents.py:50 src/gui/buddylist/buddylistmenu.py:311 +msgid "Connections" +msgstr "(0|\\||\\|3(710|\\|2" + +#: src/gui/app/mainMenuEvents.py:150 src/gui/app/menubar.py:11 +#: src/gui/buddylist/buddylistmenu.py:297 +msgid "&Digsby" +msgstr "&D165|3'/" + +#: src/gui/app/mainMenuEvents.py:152 src/gui/app/menubar.py:56 +#: src/gui/buddylist/buddylistmenu.py:243 +msgid "&Sort By" +msgstr "&50|27 |3'/" + +#: src/gui/app/mainMenuEvents.py:154 src/gui/app/menubar.py:32 +#: src/gui/buddylist/buddylistmenu.py:302 +msgid "&View" +msgstr "&V13\\/\\/" + +#: src/gui/app/mainMenuEvents.py:175 src/gui/app/menubar.py:24 +#: src/gui/buddylist/buddylistmenu.py:133 +#: src/gui/buddylist/buddylistmenu.py:150 +msgid "&Rename Selection" +msgstr "&R3|\\|4|\\/|3 53|_3(710|\\|" + +#: src/gui/app/mainMenuEvents.py:176 src/gui/buddylist/buddylistmenu.py:151 +msgid "&Delete Selection" +msgstr "&D3|_373 53|_3(710|\\|" + +#: src/gui/app/mainMenuEvents.py:180 src/gui/buddylist/buddylistmenu.py:155 +msgid "&Rename {name}" +msgstr "&R3|\\|4|\\/|3 {name}" + +#: src/gui/app/mainMenuEvents.py:181 src/gui/buddylist/buddylistmenu.py:156 +msgid "&Delete {name}" +msgstr "&D3|_373 {name}" + +#: src/gui/app/mainMenuEvents.py:312 src/gui/buddylist/buddylistmenu.py:172 +msgid "You can bring back the menubar by right clicking on the digsby icon in the task tray." +msgstr "J00 (4|\\| |3|21|\\|' |34(|< 73# |\\/|3|\\||_||34|2 |3'/ |216#7 (|_1(|<1|\\|' 0|\\| 73# |)165'/ 1(0|\\| 1|\\| 73# 745|< 7|24'/." + +#: src/gui/app/mainMenuEvents.py:314 src/gui/buddylist/buddylistmenu.py:174 +msgid "Hide Menu Bar" +msgstr "#1|)3 |\\/|3|\\||_| |34|2" + +#: src/gui/app/mainMenuEvents.py:330 src/gui/buddylist/buddylistmenu.py:322 +#: src/gui/visuallisteditor.py:286 +msgid "Arrange Panels" +msgstr "4|2|24|\\|63 &P4|\\|3|_2" + +#: src/gui/app/menubar.py:14 src/gui/buddylist/buddylistmenu.py:109 +msgid "&New Status Message..." +msgstr "&N3\\/\\/ 5747|_|2 |\\/|355463..." + +#: src/gui/app/menubar.py:15 src/gui/buddylist/buddylistmenu.py:110 +msgid "&Edit Status Messages..." +msgstr "&3|)17 5747|_|2 |\\/|3554632..." + +#: src/gui/app/menubar.py:17 src/gui/buddylist/buddylistmenu.py:122 +msgid "My Status" +msgstr "|\\/|'/ 5747|_|5" + +#: src/gui/app/menubar.py:18 src/gui/buddylist/buddylistmenu.py:123 +msgid "My &Accounts..." +msgstr "|\\/|'/ &A((0|_||\\|72..." + +#: src/gui/app/menubar.py:21 +msgid "&New IM..." +msgstr "&N3\\/\\/ 1|\\/|..." + +#: src/gui/app/menubar.py:22 +msgid "Add &Contact..." +msgstr "4|)|) &(0|\\|74(7..." + +#: src/gui/app/menubar.py:23 +msgid "Add &Group..." +msgstr "4|)|) &6|20|_|P..." + +#: src/gui/app/menubar.py:25 src/gui/buddylist/buddylistmenu.py:134 +msgid "&Delete Selection..." +msgstr "&D3|_373 53|_3(710|\\|..." + +#: src/gui/app/menubar.py:27 +msgid "Sign &Off Digsby" +msgstr "516|\\| &O|=|= |)165|3'/" + +#: src/gui/app/menubar.py:29 src/gui/buddylist/buddylistmenu.py:138 +#: src/gui/trayicons.py:197 +msgid "E&xit Digsby" +msgstr "3&><17 |)165|3'/" + +#: src/gui/app/menubar.py:33 src/gui/buddylist/buddylistmenu.py:165 +msgid "&Always On Top" +msgstr "&4|_\\/\\/4'/2 0|\\| 70P" + +#: src/gui/app/menubar.py:34 +msgid "Skins..." +msgstr "5|<1|\\|2..." + +#: src/gui/app/menubar.py:37 src/gui/buddylist/buddylistmenu.py:168 +msgid "&Menu Bar" +msgstr "&M3|\\||_| |34|2" + +#: src/gui/app/menubar.py:39 src/gui/buddylist/buddylistmenu.py:179 +msgid "Arrange &Panels..." +msgstr "4|2|24|\\|63 &P4|\\|3|_2..." + +#: src/gui/app/menubar.py:41 +msgid "Show &Mobile Contacts" +msgstr "5#0\\/\\/ &M0|31|_3 (0|\\|74(72" + +#: src/gui/app/menubar.py:42 +msgid "Show &Offline Contacts" +msgstr "5#0\\/\\/ &O|=|=|_1|\\|3 (0|\\|74(72" + +#: src/gui/app/menubar.py:43 +msgid "&Group Offline Contacts" +msgstr "&6|20|_|P 0|=|=|_1|\\|3 (0|\\|74(72" + +#: src/gui/app/menubar.py:44 src/gui/buddylist/buddylistmenu.py:185 +msgid "&Hide Offline Groups" +msgstr "&H1|)3 0|=|=|_1|\\|3 6|20|_|P2" + +#: src/gui/app/menubar.py:49 src/gui/buddylist/buddylistmenu.py:208 +msgid "&None" +msgstr "&N0|\\|3" + +#: src/gui/app/menubar.py:50 src/gui/buddylist/buddylistmenu.py:209 +msgid "&Status" +msgstr "&5747|_|2" + +#: src/gui/app/menubar.py:51 src/gui/buddylist/buddylistmenu.py:210 +msgid "N&ame" +msgstr "|\\|&A|\\/|3" + +#: src/gui/app/menubar.py:52 src/gui/buddylist/buddylistmenu.py:211 +msgid "&Log Size" +msgstr "&L06 5123" + +#: src/gui/app/menubar.py:53 src/gui/buddylist/buddylistmenu.py:212 +msgid "Ser&vice" +msgstr "53|2&V1(3" + +#: src/gui/app/menubar.py:54 src/gui/buddylist/buddylistmenu.py:222 +#: src/gui/buddylist/buddylistmenu.py:230 +msgid "Advan&ced..." +msgstr "4|)\\/4|\\|&(3|)..." + +#: src/gui/app/menubar.py:59 src/gui/buddylist/buddylistmenu.py:303 +msgid "&Tools" +msgstr "&700|_2" + +#: src/gui/app/menubar.py:60 src/gui/trayicons.py:192 +msgid "&Preferences..." +msgstr "&P|23|=3|23|\\|(32..." + +#: src/gui/app/menubar.py:61 +msgid "&File Transfer History" +msgstr "&|=1|_3 7|24|\\|5|=3|2 #1570|2'/" + +#: src/gui/app/menubar.py:62 +msgid "&Chat History" +msgstr "&(#47 #1570|2'/" + +#: src/gui/app/menubar.py:65 src/gui/buddylist/buddylistmenu.py:304 +msgid "&Help" +msgstr "&H3|_P" + +#: src/gui/app/menubar.py:66 src/gui/buddylist/buddylistmenu.py:265 +msgid "&Documentation" +msgstr "&D0(|_||\\/|3|\\|74710|\\|" + +#: src/gui/app/menubar.py:67 src/gui/buddylist/buddylistmenu.py:266 +msgid "Support &Forums" +msgstr "5|_|PP0|27 &F0|2|_||\\/|2" + +#: src/gui/app/menubar.py:69 src/gui/buddylist/buddylistmenu.py:268 +msgid "&Submit Bug Report" +msgstr "&5|_||3|\\/|17 |3|_|6 |23P0|27" + +#: src/gui/app/menubar.py:70 src/gui/buddylist/buddylistmenu.py:269 +msgid "Su&ggest a Feature" +msgstr "5|_|&G6357 @ |=347|_||23" + +#: src/gui/app/menubar.py:78 +msgid "&Invite Your Friends" +msgstr "&1|\\|\\/173 '/0 |=|213|\\||)2" + +#: src/gui/app/menubar.py:79 src/plugins/digsby_about/aboutdialog.py:132 +msgid "&About Digsby" +msgstr "&4|30|_|7 |)165|3'/" + +#: src/gui/authorizationdialog.py:21 +msgid "Authorize Contact" +msgstr "4|_|7#0|2123 (0|\\|74(7" + +#: src/gui/authorizationdialog.py:41 +msgid "Authorize and Add" +msgstr "4|_|7#0|2123 |\\| 4|)|)" + +#: src/gui/authorizationdialog.py:42 +msgid "Authorize" +msgstr "4|_|7#0|2123" + +#: src/gui/authorizationdialog.py:43 +msgid "Deny" +msgstr "|)3|\\|'/" + +#: src/gui/browser/jsconsole.py:15 +msgid "Javascript Console" +msgstr "J4\\/45(|21P7 (0|\\|50|_3" + +#: src/gui/browser/jsconsole.py:47 src/gui/browser/jsconsole.py:48 +msgid "Clear" +msgstr "(|_34|2" + +#: src/gui/browser/webkit/webkiteditsource.py:41 +msgid "&Copy" +msgstr "&(0P'/" + +#: src/gui/browser/webkit/webkiteditsource.py:72 +msgid "&Open in Browser" +msgstr "&0P3|\\| 1|\\| |3|20\\/\\/53|2" + +#: src/gui/browser/webkit/webkiteditsource.py:74 +msgid "Launches browser in pref \"debug.message_area.debug_browser\"" +msgstr "|_4|_||\\|(#32 |3|20\\/\\/53|2 1|\\| P|23|= \"debug.message_area.debug_browser\"" + +#: src/gui/buddylist/accounttray.py:231 +msgid "{account.offline_reason} ({account.email_address})" +msgstr "{account.offline_reason} ({account.email_address})" + +#: src/gui/buddylist/accounttray.py:238 +msgid "{count} unread message ({account.email_address})" +msgid_plural "{count} unread messages ({account.email_address})" +msgstr[0] "{count} |_||\\||234|) |\\/|355463 ({account.email_address})" +msgstr[1] "{count} |_||\\||234|) |\\/|3554635 ({account.email_address})" + +#: src/gui/buddylist/accounttray.py:250 +msgid "{account.offline_reason} ({account.name})" +msgstr "{account.offline_reason} ({account.name})" + +#: src/gui/buddylist/accounttray.py:252 +msgid "{count} new alert ({account.name})" +msgid_plural "{count} new alerts ({account.name})" +msgstr[0] "{count} |\\|3\\/\\/ 4|_3|27 ({account.name})" +msgstr[1] "{count} |\\|3\\/\\/ 4|_3|272 ({account.name})" + +#: src/gui/buddylist/buddylist.py:190 +msgid "BuddyList Popup" +msgstr "|3|_||)|)'/|_1t7 P0P|_|P" + +#: src/gui/buddylist/buddylist.py:240 src/gui/pref/pg_accounts.py:789 +msgid "Add Accounts" +msgstr "4|)|) 4((0|_||\\|72" + +#: src/gui/buddylist/buddylist.py:253 +msgid "Need Help?" +msgstr "|\\|33|) #3|_P?" + +#: src/gui/buddylist/buddylist.py:405 +msgid "&Add Group" +msgstr "&4|)|) 6|20|_|P" + +#: src/gui/buddylist/buddylist.py:658 src/gui/buddylist/buddylist.py:671 +#: src/gui/contactdialogs.py:279 src/gui/contactdialogs.py:306 +msgid "Merge Contacts" +msgstr "|\\/|3|263 (0|\\|74(72" + +#: src/gui/buddylist/buddylist.py:941 +msgid "This buddy can't accept file transfers" +msgstr "|)12 |3|_||)|)'/ (4|\\|'7 4((3P7 |=1|_3 7|24|\\|5|=3|22" + +#: src/gui/buddylist/buddylist.py:942 src/gui/capabilitiesbar.py:81 +#: src/gui/capabilitiesbar.py:351 src/gui/imwin/imwin_native.py:378 +#: src/gui/imwin/imwinmenu.py:14 +msgid "Send File" +msgstr "53|\\||) |=1|_3" + +#: src/gui/buddylist/buddylist.py:1010 +msgid "&Add Contact" +msgstr "&4|)|) (0|\\|74(7" + +#: src/gui/buddylist/buddylistmenu.py:125 +msgid "&New IM...\tCtrl+N" +msgstr "&N3\\/\\/ 1|\\/|...\tCtrl+N" + +#: src/gui/buddylist/buddylistmenu.py:127 +msgid "New Group C&hat...\tCtrl+Shift+N" +msgstr "|\\|3\\/\\/ 6|20|_|P (&H47...\tCtrl+Shift+N" + +#: src/gui/buddylist/buddylistmenu.py:131 +msgid "Add &Contact...\tCtrl+A" +msgstr "4|)|) &(0|\\|74(7...\tCtrl+A" + +#: src/gui/buddylist/buddylistmenu.py:132 +msgid "Add &Group...\tCtrl+Shift+A" +msgstr "4|)|) &6|20|_|P...\tCtrl+Shift+A" + +#: src/gui/buddylist/buddylistmenu.py:136 +#, python-format +msgid "Sign &Off Digsby (%s)" +msgstr "516|\\| &O|=|= |)165|3'/ (%s)" + +#: src/gui/buddylist/buddylistmenu.py:166 +msgid "Skins...\tCtrl+S" +msgstr "5|<1|\\|2...\tCtrl+S" + +#: src/gui/buddylist/buddylistmenu.py:181 +msgid "Show &Mobile Contacts\tCtrl+M" +msgstr "5#0\\/\\/ &M0|31|_3 (0|\\|74(72\tCtrl+M" + +#: src/gui/buddylist/buddylistmenu.py:182 +msgid "Show &Offline Contacts\tCtrl+O" +msgstr "5#0\\/\\/ &O|=|=|_1|\\|3 (0|\\|74(72\tCtrl+O" + +#: src/gui/buddylist/buddylistmenu.py:183 +msgid "&Group Offline Contacts\tCtrl+G" +msgstr "&6|20|_|p 0|=|=|_1|\\|3 (0|\\|74(72\tCtrl+G" + +#: src/gui/buddylist/buddylistmenu.py:242 +msgid "&Group By" +msgstr "&6|20|_|p |3'/" + +#: src/gui/buddylist/buddylistmenu.py:250 +msgid "&Preferences...\tCtrl+P" +msgstr "&P|23|=3|23|\\|(32...\tCtrl+P" + +#: src/gui/buddylist/buddylistmenu.py:251 +msgid "Buddy List &Search\tCtrl+F" +msgstr "|3|_||)|)'/ |_157 &534|2(#\tCtrl+F" + +#: src/gui/buddylist/buddylistmenu.py:252 +msgid "&File Transfer History\tCtrl+J" +msgstr "&|=1|_3 7|24|\\|5|=3|2 #1570|2'/\tCtrl+J" + +#: src/gui/buddylist/buddylistmenu.py:258 +msgid "&Chat History\tCtrl+H" +msgstr "&(#47 #1570|2'/\tCtrl+H" + +#: src/gui/buddylist/buddylistmenu.py:273 +msgid "Show Debug Console" +msgstr "5#0\\/\\/ |)3|3|_|6 (0|\\|50|_3" + +#: src/gui/buddylist/buddylistmenu.py:276 +msgid "Su&pport Digsby" +msgstr "5|_|&PP0|27 |)165|3'/" + +#: src/gui/buddylist/buddylistmenu.py:299 src/gui/pref/iconeditor.py:546 +msgid "&File" +msgstr "&|=1|_3" + +#: src/gui/buddylist/buddylistmenu.py:331 +msgid "&Sign Off" +msgstr "&516|\\| 0|=|=" + +#: src/gui/buddylist/buddylistmenu.py:332 +msgid "&Sign On" +msgstr "&516|\\| 0|\\|" + +#: src/gui/buddylist/buddylistmenu.py:335 +msgid "&Edit Account..." +msgstr "&3|)17 4((0|_||\\|7..." + +#: src/gui/buddylist/buddylistmenu.py:405 +msgid "&Stealth Settings" +msgstr "&5734|_7# 53771|\\|62" + +#: src/gui/buddylist/buddylistmenu.py:411 +msgid "Selection" +msgstr "53|_3(710|\\|" + +#: src/gui/buddylist/buddylistmenu.py:414 +msgid "Appear Online to {name}" +msgstr "4PP34|2 0|\\||_1|\\|3 2 {name}" + +#: src/gui/buddylist/buddylistmenu.py:416 +msgid "Appear Offline to {name}" +msgstr "4PP34|2 0|=|=|_1|\\|3 2 {name}" + +#: src/gui/buddylist/buddylistmenu.py:418 +msgid "Appear Permanently Offline to {name}" +msgstr "4PP34r P3|2|\\/|4|\\|3|\\|7|_'/ 0|=|=|_1|\\|3 2 {name}" + +#: src/gui/buddylist/buddylistmenu.py:423 +msgid "Learn More (URL)" +msgstr "|_34|2|\\| |\\/|0|23 (|_||2|_)" + +#: src/gui/buddylist/buddylistmenu.py:463 +msgid "Chat with Resource" +msgstr "(#47 \\/\\/17# |2350|_||2(3" + +#: src/gui/buddylist/buddylistmenu.py:480 +msgid "&Remove from Merged Contact" +msgstr "&R3|\\/|0\\/3 |=|20|\\/| |\\/|3|263|) (0|\\|74(7" + +#: src/gui/buddylist/renderers.py:831 src/gui/searchgui.py:122 +msgid "Options..." +msgstr "0P710|\\|2..." + +#: src/gui/bugreporter/bugreportergui.py:41 +msgid "Use this tool to submit a diagnostic log right after you experience a bug" +msgstr "|_|53 |)12 700|_ 2 5|_||3|\\/|17 @ |)146|\\|0571O( |_06 |216#7 4|=73|2 J00 ><(3P7 \\/\\/#3|23 17 |)1|23(7|_'/ P3|2741|\\|2 2 |\\| 3|2|20|2." + +# MARK: Reproduced multiple times... +#: src/gui/bugreporter/bugreportergui.py:51 +#: src/gui/bugreporter/bugreportergui.py:83 +msgid "Please describe the bug in as much detail as possible. Include information such as what you were doing when the bug occurred and exactly what goes wrong." +msgstr "P|_3453 |)35(|21|33 73# |3|_|6 1|\\| 42 |\\/||_|(# ||)3741|_ 42 P0551|3|_3. 1|\\|(|_|_||)3 1|\\||=0|2|\\/|4710|\\| 5|_|(# 42 \\/\\/07 J00 \\/\\/|_|2 |)01|\\|' \\/\\/#3|\\| 73# |3|_|6 0((|_||2|23|) |\\| ><4(7|_'/ \\/\\/07 6032 \\/\\/|20|\\|6." + +#: src/gui/bugreporter/bugreportergui.py:61 +msgid "Can you consistently reproduce this bug?" +msgstr "(4|\\| J00 (0|\\|51573|\\|7|_'/ |23P|20|)|_|(3 |)12 |3|_|6?" + +#: src/gui/bugreporter/bugreportergui.py:63 +msgid "&Yes" +msgstr "&Y32" + +#: src/gui/bugreporter/bugreportergui.py:64 +msgid "&No" +msgstr "&N0" + +#: src/gui/bugreporter/bugreportergui.py:65 +msgid "&Don't Know" +msgstr "&D0|\\|'7 |<|\\|0\\/\\/" + +#: src/gui/bugreporter/bugreportergui.py:68 +msgid "If this is a visual bug, please attach a screenshot to this report." +msgstr "1|= |)12 |3 @ \\/15|_|4|_ |3|_|6, P|_3453 4774(# @ 5(|233|\\|5#07 2 |)12 |23P0|27." + +#: src/gui/bugreporter/bugreportergui.py:69 +#: src/gui/bugreporter/bugreportergui.py:104 +#: src/gui/bugreporter/bugreportergui.py:141 +msgid "Take Screenshot" +msgstr "74|<3 5(|233|\\|5#07" + +#: src/gui/bugreporter/bugreportergui.py:73 +msgid "Taking Screenshot in {secs}" +msgstr "74|<1|\\|' 5(|233|\\|5#07 1|\\| {secs}" + +#: src/gui/bugreporter/bugreportergui.py:139 +msgid "Remove Screenshot" +msgstr "|23|\\/|0\\/3 5(|233|\\|5#07" + +#: src/gui/bugreporter/bugreportergui.py:199 +msgid "Submit Bug Report - Digsby" +msgstr "5|_||3|\\/|17 |3|_|6 |23p0|27 - |)165|3'/" + +#: src/gui/bugreporter/bugreportergui.py:212 +msgid "Submit" +msgstr "5|_||3|\\/|17" + +#: src/gui/bugreporter/bugreportergui.py:225 +msgid "Please enter a description of the bug." +msgstr "P|_3453 3|\\|73|2 |)35(|21P710|\\| 0|= 73|-| |3|_|6" + +#: src/gui/bugreporter/bugreportergui.py:226 +msgid "Include as much information as possible about what went wrong and how to reproduce the issue." +msgstr "" +"P|_3453 3|\\|73|2 @ |)35(|21P710|\\| 0|= 73# |3|_|6.\n" +"\n" +"1|\\|c|_|_||)3 42 |\\/||_|(# 1|\\||=0r|\\/|4710|\\| 42 P0551|3|_3 4|30|_|7\n" +"\\/\\/07 \\/\\/3|\\|7 \\/\\/|20|\\|6 |\\| #0\\/\\/ 2 |23P|20|0|_|(3 73# 155|_|3." + +#: src/gui/bugreporter/bugreportergui.py:230 src/gui/tracebackdialog.py:14 +msgid "Send Bug Report" +msgstr "53|\\||) |3|_|6 |23P0|27" + +#: src/gui/bugreporter/bugreportergui.py:255 +msgid "Submit Bug Report - Screenshot - Digsby" +msgstr "5|_||3|\\/|17 |3|_|6 |23p0|27 - 5(|233|\\|5#07 - |)165|3'/" + +#: src/gui/bugreporter/bugreportergui.py:277 +msgid "Blank out non Digsby windows" +msgstr "|3|_4|\\||< 0|_|7 |\\|0|\\| |)165|3'/ \\/\\/1|\\||)0\\/\\/2" + +#: src/gui/bugreporter/bugreportergui.py:284 +msgid "Is it OK to send this screenshot to digsby.com?" +msgstr "|3 17 0|< 2 53|\\||) |)12 5(|233|\\|5#07 2 |)165|3'/.(0|\\/|?" + +#: src/gui/capabilitiesbar.py:24 +msgid "Info" +msgstr "1|\\||=0" + +#: src/gui/capabilitiesbar.py:24 +msgid "View buddy information" +msgstr "\\/13\\/\\/ |3|_||)|)'/ 1|\\||=0|2|\\/|4710|\\|" + +#: src/gui/capabilitiesbar.py:25 src/gui/imdialogs.py:43 +msgid "IM" +msgstr "1|\\/|" + +#: src/gui/capabilitiesbar.py:25 +msgid "Instant message this buddy" +msgstr "1|\\|574|\\|7 |\\/|355463 |)12 |3|_||)|)'/" + +#: src/gui/capabilitiesbar.py:26 +msgid "Start an audio/video chat" +msgstr "574|27 |\\| 4|_||)10/\\/1|)30 (#47" + +#: src/gui/capabilitiesbar.py:26 +msgid "Video" +msgstr "\\/1|)30" + +#: src/gui/capabilitiesbar.py:27 +msgid "Files" +msgstr "|=1|_32" + +#: src/gui/capabilitiesbar.py:27 +msgid "Send files to this buddy" +msgstr "53|\\||) |=1|_32 2 |)12 |3|_||)|)'/" + +#: src/gui/capabilitiesbar.py:29 src/gui/vcard/vcardgui.py:138 +msgid "Email" +msgstr "3|\\/|41|_" + +#: src/gui/capabilitiesbar.py:29 +msgid "Send email" +msgstr "53|\\||) 3|\\/|41|_" + +#: src/gui/capabilitiesbar.py:30 +msgid "SMS" +msgstr "5|\\/|5" + +#: src/gui/capabilitiesbar.py:30 +msgid "Send SMS messages" +msgstr "53|\\||) 5|\\/|5 |\\/|3554632" + +#: src/gui/capabilitiesbar.py:107 +msgid "Group Chat" +msgstr "6|20|_|P (#47" + +#: src/gui/capabilitiesbar.py:110 +msgid "View Past Chats" +msgstr "\\/13\\/\\/ P457 (#472" + +#: src/gui/capabilitiesbar.py:114 +msgid "Alert Me When..." +msgstr "4|_3|27 |\\/|3 \\/\\/#3|\\|..." + +#: src/gui/capabilitiesbar.py:115 +msgid "Block" +msgstr "|3|_0(|<" + +#: src/gui/capabilitiesbar.py:138 +msgid "To:" +msgstr "2:" + +#: src/gui/capabilitiesbar.py:141 +msgid "From:" +msgstr "|=|20|\\/|:" + +#: src/gui/capabilitiesbar.py:300 +msgid "Unblock {name}" +msgstr "|_||\\||3|_0(|< {name}" + +#: src/gui/capabilitiesbar.py:302 +msgid "Block {name}" +msgstr "|3|_0(|< {name}" + +#: src/gui/capabilitiesbar.py:354 src/gui/imwin/imwin_native.py:379 +msgid "Transfer History" +msgstr "7|24|\\|5|=3|2 #1570|2'/" + +#: src/gui/capabilitiesbar.py:377 +msgid "Icons Only" +msgstr "1(0|\\|2 0|\\||_'/" + +#: src/gui/capabilitiesbar.py:378 +msgid "Text Only" +msgstr "73><7 0|\\||_'/" + +#: src/gui/capabilitiesbar.py:379 +msgid "Icons Next to Text" +msgstr "1(0|\\|2 |\\|3><7 2 73><7" + +#: src/gui/capabilitiesbar.py:380 +msgid "Icons Above Text" +msgstr "1(0|\\|2 4|30\\/3 73><7" + +#: src/gui/capabilitiesbar.py:389 +msgid "Hide Actions Bar" +msgstr "#1|)3 4(710|\\|2 |34|2" + +#: src/gui/chatgui.py:90 +msgid "Join Chat Room" +msgstr "J01|\\| (#47 R00|\\/|" + +#: src/gui/chatgui.py:98 src/hub.py:158 +msgid "Join Chat" +msgstr "J01|\\| (#47" + +#: src/gui/chatgui.py:111 +msgid "Account:" +msgstr "4((0|_||\\|7:" + +#: src/gui/chatgui.py:117 +msgid "Invite:" +msgstr "1|\\|\\/173:" + +#: src/gui/chatgui.py:155 +msgid "Server:" +msgstr "53|2\\/3|2:" + +#: src/gui/chatgui.py:160 +msgid "Room Name:" +msgstr "|200|\\/| |\\|4|\\/|3:" + +#: src/gui/contactdialogs.py:37 +msgid "Would you like to send \"{files[0]}\" to {buddy_name:s}?" +msgid_plural "Would you like to send {num_files:d} files to {buddy_name:s}?" +msgstr[0] "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 53|\\||) \"{files[0]}\" 2 {buddy_name:s}?" +msgstr[1] "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 53|\\||) {num_files:d} files 2 {buddy_name:s}?" + +#: src/gui/contactdialogs.py:41 +msgid "Send Files" +msgstr "53|\\||) |=1|_32" + +#: src/gui/contactdialogs.py:78 +msgid "Contact &Name:" +msgstr "(0|\\|74(7 &N4|\\/|3:" + +#: src/gui/contactdialogs.py:81 +msgid "Accoun&t:" +msgstr "4((0|_||\\|&7:" + +#: src/gui/contactdialogs.py:87 +msgid "&Add" +msgstr "&4|)|)" + +#: src/gui/contactdialogs.py:158 +msgid "Would you like to merge these contacts?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 |\\/|3|263 7#353 (0|\\|74(72?" + +#: src/gui/contactdialogs.py:161 +msgid "They will appear as one item on your buddy list." +msgstr "7#3'/ \\/\\/1|_|_ 4PP34|2 42 0|\\|3 173|\\/| 0|\\| '/0 |3|_||)|)'/ |_157." + +# MARK: Simular to libe from visuallisteditor: "Drag and drop to reorder" +#: src/gui/contactdialogs.py:182 +msgid "Drag and drop to rearrange:" +msgstr "|)|246 |\\| |)|20P 2 |234|2|24|\\|63:" + +#: src/gui/filetransfer/filetransferlist.py:234 +#: src/gui/filetransfer/filetransferlist.py:243 +msgid "File not found" +msgstr "|=1|_3 |\\|07 |=0|_||\\||)" + +#: src/gui/filetransfer/filetransferlist.py:461 +#: src/gui/filetransfer/filetransferlist.py:599 src/gui/pref/pg_files.py:11 +#: src/gui/pref/prefstrings.py:19 +msgid "File Transfers" +msgstr "|=1|_3 7|24|\\|5|=3|22" + +#: src/gui/filetransfer/filetransferlist.py:474 +msgid "Open &Containing Folder" +msgstr "0P3|\\| &C0|\\|741|\\|1|\\|' |=0|_|)3|2" + +#: src/gui/filetransfer/filetransferlist.py:542 +msgid "Clean Up" +msgstr "(|_34|\\| |_|P" + +#: src/gui/filetransfer/filetransferlist.py:642 +msgid "file" +msgid_plural "files" +msgstr[0] "|=1|_3" +msgstr[1] "|=1|_32" + +#: src/gui/helpdigsby.py:4 +msgid "Keep Digsby Free" +msgstr "|<33P |)165|3'/ |=|233" + +#: src/gui/helpdigsby.py:5 +msgid "Digsby will use your computer's free time using it to conduct both free and paid research." +msgstr "|)165|3'/ \\/\\/1|_|_ |_|53 '/0 (0|\\/|P|_|73|2'2 |=|233 71|\\/|3 |_|51|\\|6 17 2 (0|\\||)|_|(7 |307# |=|233 |\\| P41|) |23534|2(#." + +#: src/gui/helpdigsby.py:14 src/gui/pref/pg_privacy.py:342 +#: src/gui/pref/pg_research.py:129 +msgid "Options" +msgstr "0P710|\\|2" + +#: src/gui/helpdigsby.py:15 src/gui/imagedialog.py:11 +msgid "OK" +msgstr "0|<" + +#: src/gui/imagedialog.py:71 +msgid "Send Image" +msgstr "53|\\||) 1|\\/|463" + +#: src/gui/imagedialog.py:72 +msgid "&Send Image" +msgstr "&53|\\||) 1|\\/|463" + +#: src/gui/imagedialog.py:73 +msgid "&Don't Send" +msgstr "&D0|\\|'7 53|\\||)" + +#: src/gui/imdialogs.py:19 +msgid "New IM" +msgstr "|\\|3\\/\\/ 1|\\/|" + +#: src/gui/imdialogs.py:30 +msgid "To" +msgstr "2" + +#: src/gui/imdialogs.py:36 +msgid "From" +msgstr "|=|20|\\/|" + +#: src/gui/imwin/imhub.py:483 +msgid "Group Chat ({chat.chat_member_count:d})" +msgstr "6|20|_|P (|-|47 ({chat.chat_member_count:d})" + +#: src/gui/imwin/imhub.py:484 +msgid "{alias:s}: {message:s}" +msgstr "{alias:s}: {message:s}" + +#: src/gui/imwin/imtabs.py:57 +msgid "Close IM Window" +msgstr "(|_053 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/imwin/imtabs.py:58 +msgid "Warn me when I attempt to close multiple conversations" +msgstr "\\/\\/4|2|\\| |\\/|3 \\/\\/#3|\\| 1 4773|\\/|P7 2 (|_053 |\\/||_||_71P|_3 (0|\\|\\/3|254710|\\|2" + +#: src/gui/imwin/imtabs.py:59 +msgid "You are about to close {num_tabs} conversations. Are you sure you want to continue?" +msgstr "J00 12 4|30|_|7 2 (|_053 {num_tabs} (0|\\|\\/3|254710|\\|2. 12 J00 5|_||23 J00 \\/\\/4|\\|7 2 (0|\\|71|\\||_|3?" + +#: src/gui/imwin/imtabs.py:60 +msgid "Close &tabs" +msgstr "(|_043 &74|32" + +#: src/gui/imwin/imtabs.py:757 +msgid "IM Windows" +msgstr "1|\\/| \\/\\/1|\\||)0\\/\\/2" + +#: src/gui/imwin/imwin_ctrl.py:209 +msgid "Please add an email address for this buddy by clicking the \"To:\" box." +msgstr "P|_3453 4|)|) |\\| 3|\\/|41|_ 4|)|)|2352 4 |)12 |3|_||)|)'/ |3'/ (|_1(|<1|\\|' 73# \"2:\" |30><." + +#: src/gui/imwin/imwin_ctrl.py:211 +msgid "Compose email to {name}" +msgstr "(0|\\/|P053 3|\\/|41|_ 2 {name}" + +#: src/gui/imwin/imwin_ctrl.py:221 +msgid "Message Sent" +msgstr "|\\/|355463 53|\\|7" + +#: src/gui/imwin/imwin_ctrl.py:230 +msgid "Failed to Send Email" +msgstr "|=41|_3|) 2 53|\\||) 3|\\/|41|_" + +#: src/gui/imwin/imwin_ctrl.py:234 +msgid "Sending..." +msgstr "53|\\||)1|\\|'..." + +#: src/gui/imwin/imwin_ctrl.py:270 +msgid "Please add an SMS number first." +msgstr "P|_3453 4|)|) |\\| 5|\\/|5 |\\||_||\\/||33|2 |=1|257." + +#: src/gui/imwin/imwin_ctrl.py:271 src/gui/imwin/imwin_ctrl.py:274 +msgid "Send SMS Message" +msgstr "53|\\||) 5|\\/|5 |\\/|355463" + +#: src/gui/imwin/imwin_ctrl.py:273 +msgid "You are not signed in to any accounts which can send SMS messages." +msgstr "J00 12 |\\|07 516|\\|3|) 1|\\| 2 4|\\|'/ 4((0|_||\\|72 \\/\\/#1(# (4|\\| 53|\\||) 5|\\/|5 |\\/|3554632." + +#: src/gui/imwin/imwin_ctrl.py:288 +msgid "The error message received was:" +msgstr "1 4|\\/| 3|2|20|2:" + +#: src/gui/imwin/imwin_ctrl.py:292 +msgid "There was an error in sending your SMS message." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 1|\\| 53|\\||)1|\\|' '/0 5|\\/|5 |\\/|355463." + +#: src/gui/imwin/imwin_ctrl.py:293 +msgid "Send SMS Message Error" +msgstr "53|\\||) 5|\\/|5 |\\/|355463 3|2|20|2" + +#: src/gui/imwin/imwin_ctrl.py:299 +msgid "Only the first {max_length:d} characters of your message can be sent over SMS:" +msgstr "0|\\||_'/ 73|-| |=1|257 {max_length:d} (|-|4|22 0 |_| |\\/|355463 (4|\\| |33 53|\\|7 0\\/3|2 5|\\/|5:" + +#: src/gui/imwin/imwin_ctrl.py:300 +msgid "Do you want to send this message now?" +msgstr "53|\\||) |\\/|355463 |\\|40?" + +#: src/gui/imwin/imwin_ctrl.py:303 +msgid "Send SMS - Character Limit" +msgstr "53|\\||) 5|\\/|5 - (#4|24(73|2 |_1|\\/|17" + +#: src/gui/imwin/imwin_ctrl.py:461 +msgid "Reconnected" +msgstr "|23(0|\\||\\|3(73|)" + +#: src/gui/imwin/imwin_ctrl.py:689 +msgid "You can only have one audio/video call at a time." +msgstr "J00 (4|\\| 0|\\||_'/ #4\\/3 0|\\|3 4|_||)10/\\/1|)30 (4|_|_ 47 @ 71|\\/|3." + +#: src/gui/imwin/imwin_email.py:49 +msgid "Edit in {client}..." +msgstr "3|)17 |\\| {client}..." + +#: src/gui/imwin/imwin_email.py:95 +msgid "Subject:" +msgstr "5|_||3J3(7:" + +#: src/gui/imwin/imwin_email.py:116 +#: src/gui/uberwidgets/formattedinput2/iminput.py:67 +msgid "Send" +msgstr "53|\\||)" + +#: src/gui/imwin/imwin_gui.py:43 +msgid "Typing" +msgstr "7'/P1|\\|'" + +#: src/gui/imwin/imwin_gui.py:44 +msgid "Entered Text" +msgstr "3|\\|73|23|)" + +#: src/gui/imwin/imwin_gui.py:75 +msgid "Digsby Announcement" +msgstr "|)165|3'/ 4|\\||\\|0|_||\\|(3|\\/|3|\\|7" + +#: src/gui/imwin/imwin_gui.py:265 +#, python-format +msgid "Would you like to send this image to %s?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 53|\\||) |)12 1|\\/|463 2 %s" + +#: src/gui/imwin/imwin_native.py:417 src/gui/uberwidgets/formattedinput.py:758 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:137 +msgid "Choose a background color" +msgstr "(#0053 @ |34(|<6|20|_||\\||) (0|_0|2" + +#: src/gui/imwin/imwin_tofrom.py:318 +msgid "{username} disconnected" +msgstr "{username} |)15(0|\\||\\|3(73|)" + +#: src/gui/imwin/imwin_tofrom.py:321 +msgid "now sending from: {username}" +msgstr "|\\|0\\/\\/ 53|\\||)1|\\|' |=|20|\\/|: {username}" + +#: src/gui/imwin/imwin_tofrom.py:342 +msgid "Add..." +msgstr "4|)|)..." + +#: src/gui/imwin/imwin_tofrom.py:584 +msgid "Please enter a valid SMS number (ie: 555-555-5555 or 5555555555)" +msgstr "P|_3453 3|\\|73|2 @ \\/4|_1|) 5|\\/|5 |\\||_||\\/||33|2 (ie: 555-555-5555 or 5555555555)" + +#: src/gui/imwin/imwin_tofrom.py:585 +msgid "Invalid SMS Number" +msgstr "1|\\|\\/4|_1|) 5|\\/|5 |\\||_||\\/||33|2" + +#: src/gui/imwin/imwin_tofrom.py:593 +msgid "That SMS number is already in this buddy's list." +msgstr "|)47 5|\\/|5 |\\||_||\\/||33|2 |3 4|_|234|)'/ 1|\\| |)12 |3|_||)|)'/'2 |_157." + +#: src/gui/imwin/imwin_tofrom.py:594 +msgid "Add SMS Number" +msgstr "4|)|) 5|\\/|5 |\\||_||\\/||33|2" + +#: src/gui/imwin/imwin_tofrom.py:634 +msgid "Accounts..." +msgstr "4((0|_||\\|75..." + +#: src/gui/imwin/imwin_tofrom.py:673 +msgid "Please enter a valid email address (ie: john123@digsby.com)" +msgstr "P|_3453 3|\\|73|2 @ \\/4|_1|) 3|\\/|41|_ 4|)|)|2355 (ie: john123@digsby.com)" + +#: src/gui/imwin/imwin_tofrom.py:674 +msgid "Invalid Email Address" +msgstr "1|\\|\\/4|_1|) 3|\\/|41|_ 4|)|)|2355" + +#: src/gui/imwin/imwin_tofrom.py:677 +msgid "That email is already registered with this buddy." +msgstr "|)47 3|\\/|41|_ |3 4|_|234|)'/ |2361573|23|) \\/\\/17# |)12 |3|_||)|)'/." + +#: src/gui/imwin/imwin_tofrom.py:678 +msgid "Add Email Address" +msgstr "4|)|) 3|\\/|41|_ 4|)|)|2355" + +#: src/gui/imwin/imwin_tofrom.py:729 +msgid "Add Email Account" +msgstr "4|)|) 3|\\/|41|_ 4((0|_||\\|7" + +#: src/gui/imwin/imwinmenu.py:12 +msgid "Buddy Info" +msgstr "|3|_||)|)'/ 1|\\||=0" + +#: src/gui/imwin/imwinmenu.py:13 +msgid "Send IM" +msgstr "53|\\||) 1|\\/|" + +#: src/gui/imwin/imwinmenu.py:16 +msgid "Send Email" +msgstr "53|\\||) 3|\\/|41|_" + +#: src/gui/imwin/imwinmenu.py:17 +msgid "Send SMS" +msgstr "53|\\||) 5|\\/|5" + +#: src/gui/imwin/imwinmenu.py:40 src/gui/pastbrowser.py:414 +#: src/gui/toolbox/toolbox.py:1448 src/gui/uberwidgets/formattedinput.py:95 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:105 +#: src/plugins/twitter/twitter.py:1211 src/plugins/twitter/twitter_gui.py:1496 +msgid "Copy" +msgstr "(0PY" + +#: src/gui/imwin/imwinmenu.py:41 src/gui/toolbox/toolbox.py:1451 +#: src/gui/uberwidgets/formattedinput.py:96 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:106 +msgid "Paste" +msgstr "P4573" + +#: src/gui/imwin/imwinmenu.py:44 src/gui/imwin/imwinmenu.py:123 +msgid "Show &Actions Bar" +msgstr "5#0\\/\\/ &A(710|\\|2 |34|2" + +#: src/gui/imwin/imwinmenu.py:45 src/gui/imwin/imwinmenu.py:124 +msgid "Show &Formatting Bar" +msgstr "5#0\\/\\/ &F0|2|\\/|4771|\\|' |34|2" + +#: src/gui/imwin/imwinmenu.py:46 +msgid "Show Send Button" +msgstr "5#0\\/\\/ 53|\\|d |3|_|770|\\|" + +#: src/gui/imwin/imwinmenu.py:64 src/plugins/twitter/twitter_gui.py:50 +msgid "&Keep on Top" +msgstr "&K33P 0|\\| 70P" + +#: src/gui/imwin/imwinmenu.py:85 +msgid "Copy\tCtrl+C" +msgstr "(0P'/\tCtrl+C" + +#: src/gui/imwin/imwinmenu.py:94 +msgid "Copy &Link" +msgstr "(0P'/ &L1|\\||<" + +#: src/gui/imwin/imwinmenu.py:96 +msgid "Paste\tCtrl+V" +msgstr "P4573\tCtrl+V" + +#: src/gui/imwin/imwinmenu.py:100 +msgid "Edit Source" +msgstr "3|)17 S0|_||2(3" + +#: src/gui/imwin/imwinmenu.py:103 +msgid "&Javascript Console" +msgstr "&J4\\/45(|21P7 (0|\\|50|_3" + +#: src/gui/imwin/imwinmenu.py:108 src/plugins/twitter/twitter_gui.py:1507 +msgid "&Increase Text Size\tCtrl+=" +msgstr "&1|\\|(|23453 73><7 5123" + +#: src/gui/imwin/imwinmenu.py:110 src/plugins/twitter/twitter_gui.py:1508 +msgid "&Decrease Text Size\tCtrl+-" +msgstr "&D3(|23453 73><7 5123\tCtrl+-" + +#: src/gui/imwin/imwinmenu.py:113 src/plugins/twitter/twitter_gui.py:1510 +msgid "&Reset Text Size\tCtrl+0" +msgstr "&R3537 73><7 5123\tCtrl+0" + +#: src/gui/imwin/imwinmenu.py:116 +msgid "&Text Size" +msgstr "&73><7 5123" + +#: src/gui/imwin/imwinmenu.py:122 +msgid "Show &Room List" +msgstr "5#0\\/\\/ &R00|\\/| |_157" + +#: src/gui/imwin/roomlist.py:405 +msgid "Invite Buddy" +msgstr "1|\\|\\/173 B|_||)|)'/" + +#: src/gui/imwin/roomlist.py:524 +msgid "Do you want to invite {name} to this chat?" +msgstr "|)0 J00 \\/\\/4|\\|7 2 1|\\|\\/173 {name} 2 |)12 (#47?" + +#: src/gui/imwin/roomlist.py:525 +msgid "Chat Invite" +msgstr "(#47 1|\\|\\/173" + +#: src/gui/imwin/styles/adiummsgstyles.py:193 src/gui/skin/skintree.py:427 +#: src/msn/MSNBuddy.py:559 +msgid "(none)" +msgstr "(|\\|0|\\|3)" + +#: src/gui/infobox/emailpanels.py:596 +msgid "(No Subject)" +msgstr "(|\\|0 5|_||3J3(7)" + +#: src/gui/infobox/emailpanels.py:604 +msgid "(No Preview)" +msgstr "(|\\|0 P|23\\/13\\/\\/)" + +#: src/gui/infobox/htmlgeneration.py:137 +msgid "No Profile" +msgstr "|\\|0 P|20|=1|_3" + +#: src/gui/infobox/htmlgeneration.py:235 +msgid "Subscription:" +msgstr "5|_||35(|21P710|\\|:" + +#: src/gui/infobox/htmlgeneration.py:238 src/gui/infobox/htmlgeneration.py:395 +#: src/plugins/facebook/res/status.py.xml:3 +msgid "Status:" +msgstr "5747|_|5:" + +#: src/gui/infobox/htmlgeneration.py:339 src/yahoo/yahoobuddy.py:154 +msgid "Location:" +msgstr "|_0(4710|\\|:" + +#: src/gui/infobox/htmlgeneration.py:344 +msgid "IP Address:" +msgstr "1P 4||)|)|255:" + +#: src/gui/infobox/htmlgeneration.py:351 +msgid "Time on Page:" +msgstr "71|\\/|3 0|\\| P463:" + +#: src/gui/infobox/htmlgeneration.py:374 +msgid "Online:" +msgstr "0|\\||_1|\\|3:" + +#: src/gui/infobox/htmlgeneration.py:381 +msgid "Idle:" +msgstr "1|)|_3:" + +#: src/gui/infobox/htmlgeneration.py:387 +msgid "Away:" +msgstr "4\\/\\/4'/:" + +#: src/gui/infobox/htmlgeneration.py:396 +msgid "{status} + Idle" +msgstr "{status} + 1|)|_3" + +#: src/gui/infobox/htmlgeneration.py:416 +#: src/plugins/msim/res/content.tenjin:77 +msgid "Hide Profile" +msgstr "|-|1|)3 P|20|=1|_3" + +#: src/gui/infobox/htmlgeneration.py:416 +#: src/plugins/msim/res/content.tenjin:79 +msgid "Show Profile" +msgstr "5|-|0\\/\\/ P|20|=1|_3" + +#: src/gui/infobox/infobox.py:1062 +#, python-format +msgid "Unblock %s" +msgstr "|_||\\||3|_0(|< %s" + +#: src/gui/infobox/infobox.py:1064 +#, python-format +msgid "Block %s" +msgstr "|3|_0(|< %s" + +#: src/gui/infobox/infobox.py:1365 +msgid "Error generating content" +msgstr "3|2|20|2 63|\\|3|2473 (0|\\|73|\\|7" + +#: src/gui/infobox/infobox.py:1564 +msgid "No additional information" +msgstr "\\/13\\/\\/ |3|_||)|)'/ 1|\\||=0|2|\\/|4710|\\|" + +#: src/gui/infobox/infobox.py:1792 +msgid "InfoBox" +msgstr "1|\\||=0|30><" + +#: src/gui/input/inputmanager.py:391 +msgid "Key Debugger" +msgstr "|<3'/ |)3|3|_|663|2" + +#: src/gui/native/win/jumplist.py:51 +msgid "Chat with {name}" +msgstr "(#47 \\/\\/17# {name}" + +#: src/gui/native/win/jumplist.py:124 +msgid "Change IM Status to {status}" +msgstr "(#4|\\|63 1|\\/| 5747|_|2 2 {status}" + +#: src/gui/native/win/jumplist.py:139 src/gui/social_status_dialog.py:830 +msgid "Set Global Status" +msgstr "537 6|_0|34|_ 5747|_|2" + +#: src/gui/native/win/jumplist.py:139 +msgid "Set your status on multiple networks" +msgstr "537 '/0 5747|_|2 0|\\| |\\/||_||_71P|_3 |\\|37\\/\\/0|2|<2" + +#: src/gui/native/win/jumplist.py:140 +msgid "New IM..." +msgstr "|\\|3\\/\\/ 1|\\/|..." + +#: src/gui/native/win/jumplist.py:140 +msgid "Open the New IM window" +msgstr "0P3|\\| 73# |\\|3\\/\\/ 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/native/win/jumplist.py:141 +msgid "Open the Digsby Preferences window" +msgstr "0P3|\\| 73# |)165|3'/ P|23|=3|23|\\|(32 \\/\\/1|\\||)0\\/\\/" + +#: src/gui/native/win/jumplist.py:141 +msgid "Preferences..." +msgstr "P|23|=3|23|\\|(32..." + +#: src/gui/native/win/jumplist.py:150 +msgid "Close all windows and exit" +msgstr "(|_053 4|_|_ \\/\\/1|\\||)0\\/\\/2 |\\| ><17" + +#: src/gui/native/win/jumplist.py:150 +msgid "Exit Digsby" +msgstr "><17 |)165|3'/" + +#: src/gui/native/win/jumplist.py:164 +msgid "Enable Chat Logs" +msgstr "3|\\|4|3|_3 C#47 |_062" + +#: src/gui/native/win/jumplist.py:164 +msgid "Opens the Preferences Window where you can enable logging" +msgstr "0P3|\\|" + +#: src/gui/notifications/notificationlist.py:68 +msgid "Sound" +msgstr "50|_||\\||)" + +#: src/gui/notifications/notificationlist.py:69 +msgid "Popup" +msgstr "P0P|_|P" + +# MARK: Fragment +#: src/gui/notificationview.py:46 +msgid "a Contact" +msgstr "@ (0|\\|74(7" + +# MARK: Fragment? & WTF +#: src/gui/notificationview.py:55 +msgid "Contact" +msgstr "(0|\\|74(7" + +# MARK: Fragment +#: src/gui/notificationview.py:55 +msgid "When " +msgstr "\\/\\/#3|\\| " + +#: src/gui/notificationview.py:87 +msgid "&Action:" +msgstr "&4(710|\\|" + +#: src/gui/notificationview.py:92 src/gui/proxydialog.py:232 +msgid "&OK" +msgstr "&0|<" + +#: src/gui/notificationview.py:198 +msgid "Edit Event for \"{desc}\"" +msgstr "3|)17 3\\/3|\\|7 4 \"{desc}\"" + +#: src/gui/notificationview.py:200 +msgid "Add Event for \"{desc}\"" +msgstr "4|)|) 3\\/3|\\|7 4 \"{desc}\"" + +# MARKED %s Fragment +#: src/gui/notificationview.py:242 +#, python-format +msgid " for %s" +msgstr "4 %s" + +#: src/gui/notificationview.py:502 +#, python-format +msgid "Are you sure you want to remove %d event?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |23|\\/|0\\/3 %d 3\\/3|\\|7?" + +#: src/gui/notificationview.py:503 +#, python-format +msgid "Are you sure you want to remove %d events?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |23|\\/|0\\/3 %d 3\\/3|\\|72?" + +#: src/gui/notificationview.py:505 +msgid "Remove Events: {notification_desc}" +msgstr "|23570|23 3\\/3|\\|72: {notification_desc}" + +#: src/gui/notificationview.py:516 +msgid "Editing Alerts for {name}" +msgstr "3|)17 4|_3|272 4 {name}" + +#: src/gui/pastbrowser.py:89 +msgid "Group Chats" +msgstr "6|20|_|P (#472" + +#: src/gui/pastbrowser.py:301 +msgid "Find" +msgstr "|=1|\\||)" + +#: src/gui/pastbrowser.py:304 +msgid "Next" +msgstr "|\\|><7" + +#: src/gui/pastbrowser.py:305 +msgid "Prev" +msgstr "P|23\\/" + +#: src/gui/pastbrowser.py:386 src/gui/status.py:372 +msgid "Account" +msgstr "4((0|_||\\|7" + +#: src/gui/pastbrowser.py:392 src/gui/pastbrowser.py:470 +msgid "Buddy" +msgstr "|3|_||)|)'/" + +#: src/gui/pastbrowser.py:402 +msgid "Date" +msgstr "|)473" + +#: src/gui/pastbrowser.py:449 +msgid "Conversation Log" +msgstr "(0|\\|\\/3|254710|\\| |_06" + +#: src/gui/pastbrowser.py:466 +msgid "Chats" +msgstr "(#472" + +#: src/gui/pastbrowser.py:558 +msgid "{month}, {day}, {year}" +msgstr "{month}, {day}, {year}" + +#: src/gui/pastbrowser.py:561 +msgid "There are no chat logs for {specific_day_string}." +msgstr "7|-|3|23 |\\|0 (|-|47 |_062 4 {specific_day_string}." + +#: src/gui/pastbrowser.py:563 +msgid "There are no chat logs for {specific_day_string} with {name}." +msgstr "7|-|3|23 |\\|0 (|-|47 |_062 4 {specific_day_string} \\/\\/17|-| {name}." + +#: src/gui/pastbrowser.py:664 +msgid "There is no chat history for {name} on {service} with {acct}." +msgstr "7#3|23 |3 |\\|0 (#47 #1570|2'/ 4 {name} 0|\\| {service} \\/\\/17# {acct}." + +#: src/gui/pastbrowser.py:673 +msgid "Past Chat Browser" +msgstr "P457 (#47 |3|20\\/\\/53|2" + +#: src/gui/pref/iconeditor.py:34 +msgid "Scree&n" +msgstr "5(|233&N" + +#: src/gui/pref/iconeditor.py:35 +msgid "Capt&ure" +msgstr "(4P7|_||23" + +#: src/gui/pref/iconeditor.py:48 +msgid "Set Buddy Icon" +msgstr "537 |3|_||)|)'/ 1(0|\\|" + +#: src/gui/pref/iconeditor.py:430 +msgid "Choose an icon" +msgstr "(#0053 |\\| 1(0|\\|" + +#: src/gui/pref/iconeditor.py:439 +msgid "Not a valid image:" +msgstr "|\\|07 \\/4|_1|) 1|\\/|463:" + +#: src/gui/pref/iconeditor.py:442 +msgid "Invalid Image" +msgstr "1|\\|\\/4|_1|) 1|\\/|463" + +#: src/gui/pref/iconeditor.py:547 +msgid "Cli&pboard" +msgstr "(|_1&P|304|2|)" + +#: src/gui/pref/iconeditor.py:553 src/gui/protocols/jabbergui.py:289 +msgid "C&lear" +msgstr "(&L34|2" + +#: src/gui/pref/pg_accounts.py:617 +msgid "(auto login)" +msgstr "(4|_|70 |_061|\\|)" + +#: src/gui/pref/pg_accounts.py:828 +msgid "My Accounts" +msgstr "|\\/|'/ 4((0|_||\\|72..." + +#: src/gui/pref/pg_accounts.py:832 +msgid "Account Options" +msgstr "4((0|_||\\|7 0P710|\\|2" + +#: src/gui/pref/pg_accounts.py:863 +msgid "buddy list" +msgstr "|3|_||)|)'/ |_157" + +#: src/gui/pref/pg_accounts.py:864 +msgid "icon tray" +msgstr "1(0|\\| 7|24'/" + +#: src/gui/pref/pg_accounts.py:865 +msgid "buddy list and icon tray" +msgstr "|3|_||)|)'/ |_157 |\\| 1(0|\\| 7|24'/" + +#: src/gui/pref/pg_accounts.py:901 +msgid "Show email accounts in:" +msgstr "5#0\\/\\/ 3|\\/|41|_ 4((0|_||\\|72 1|\\|:" + +#: src/gui/pref/pg_accounts.py:902 +msgid "Show social networks in:" +msgstr "5#0\\/\\/ 50(14|_ |\\|37\\/\\/0|2|<2 1|\\|:" + +#: src/gui/pref/pg_advanced.py:14 +msgid "IM Window" +msgstr "1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_advanced.py:60 src/gui/pref/pg_advanced.py:94 +msgid "&Use Proxy Server" +msgstr "&|_|53 P|20><'/ 53|2\\/3|2" + +#: src/gui/pref/pg_advanced.py:68 +msgid "Host" +msgstr "#057" + +#: src/gui/pref/pg_advanced.py:69 +msgid "Port" +msgstr "P0|27" + +#: src/gui/pref/pg_advanced.py:70 src/plugins/digsby_email/info.yaml:18 +#: src/plugins/digsby_email/info.yaml:76 +#: src/plugins/digsby_service_editor/default_ui.py:149 +#: src/plugins/digsby_service_editor/default_ui.py:154 +#: src/plugins/msim/res/content.tenjin:19 +msgid "Username" +msgstr "|_|53|2|\\|4|\\/|3" + +#: src/gui/pref/pg_advanced.py:71 src/plugins/component_gtalk/info.yaml:60 +#: src/plugins/digsby_email/info.yaml:19 src/plugins/digsby_email/info.yaml:77 +#: src/plugins/digsby_service_editor/default_ui.py:166 +#: src/plugins/digsby_service_editor/default_ui.py:173 +#: src/plugins/provider_aol/info.yaml:14 +#: src/plugins/provider_aol/info.yaml:110 +#: src/plugins/provider_google/info.yaml:11 +#: src/plugins/provider_jabber/info.yaml:16 +#: src/plugins/provider_windows_live/info.yaml:23 +#: src/plugins/provider_yahoo/info.yaml:11 +msgid "Password" +msgstr "P455\\/\\/0|2|)" + +#: src/gui/pref/pg_advanced.py:94 +msgid "&Use Global Settings" +msgstr "&|_|53 6|_0|34|_ 53771|\\|62" + +#: src/gui/pref/pg_advanced.py:130 +msgid "&Protocol:" +msgstr "&P|2070(0|_:" + +#: src/gui/pref/pg_appearance.py:39 +msgid "Application Skin" +msgstr "4PP|_1(4710|\\| 5|<1|\\|" + +#: src/gui/pref/pg_appearance.py:41 +msgid "Conversation Theme" +msgstr "(0|\\|\\/3|254710|\\| 7#3|\\/|3" + +#: src/gui/pref/pg_appearance.py:44 +msgid "Conversation Preview" +msgstr "(0|\\|\\/3|254710|\\| P|23\\/13\\/\\/" + +#: src/gui/pref/pg_appearance.py:115 +msgid "Skin:" +msgstr "5|<1|\\|:" + +#: src/gui/pref/pg_appearance.py:115 src/gui/pref/pg_appearance.py:203 +msgid "Variant:" +msgstr "\\/4|214|\\|7:" + +#: src/gui/pref/pg_appearance.py:187 +msgid "Show header" +msgstr "5#0\\/\\/ #34|)3|2" + +#: src/gui/pref/pg_appearance.py:192 +msgid "Show message fonts" +msgstr "5#0\\/\\/ |\\/|355463 |=0|\\|72" + +#: src/gui/pref/pg_appearance.py:194 +msgid "Show message colors" +msgstr "5#0\\/\\/ |\\/|355463 (0|_0|22" + +#: src/gui/pref/pg_appearance.py:201 +msgid "Theme:" +msgstr "7#3|\\/|3:" + +#: src/gui/pref/pg_appearance.py:270 +msgid "Fonts:" +msgstr "|=0|\\|72:" + +#: src/gui/pref/pg_appearance.py:275 +msgid "Colors:" +msgstr "(0|_0|22:" + +#: src/gui/pref/pg_contact_list.py:27 +msgid "Sorting and Groups" +msgstr "50|271|\\|' |\\| 6|20|_|P2" + +#: src/gui/pref/pg_contact_list.py:46 +msgid "Autohide when not in &focus" +msgstr "4|_|70#1|)3 \\/\\/#3|\\| |\\|07 1|\\| &F0(|_|5" + +#: src/gui/pref/pg_contact_list.py:51 +msgid "Automatically &dock when near edge of screen" +msgstr "4|_|70|\\/|471(4|_|_'/ &D0(|< \\/\\/#3|\\| |\\|34|2 3|)63 0|= 5(|233|\\|" + +#: src/gui/pref/pg_contact_list.py:57 src/gui/pref/pg_text_conversations.py:41 +msgid "&Keep on top of other applications" +msgstr "&K33P 0|\\| 70P 0|= 07#3|2 4PP|_1(4710|\\|2" + +#: src/gui/pref/pg_contact_list.py:58 +msgid "Show in taskbar" +msgstr "5#0\\/\\/ 1|\\| 745|<|34|2" + +#: src/gui/pref/pg_contact_list.py:64 src/gui/pref/pg_text_conversations.py:60 +msgid "Window Options" +msgstr "\\/\\/1|\\||)0\\/\\/ 0P710|\\|2" + +#: src/gui/pref/pg_contact_list.py:81 +msgid "Contact Layout" +msgstr "(0|\\|74(7 |_4'/0|_|7" + +#: src/gui/pref/pg_contact_list.py:104 src/gui/pref/pg_contact_list.py:110 +#: src/plugins/digsby_service_editor/service_editor.py:58 +msgid "Advanced" +msgstr "4|)\\/4|\\|&(3|)..." + +#: src/gui/pref/pg_contact_list.py:104 src/gui/pref/pg_contact_list.py:110 +msgid "Basic" +msgstr "|3451(" + +#: src/gui/pref/pg_contact_list.py:243 +msgid "Group by:" +msgstr "6|20|_|P |3'/:" + +#: src/gui/pref/pg_contact_list.py:244 +msgid "Sort by:" +msgstr "50|27 |3'/:" + +#: src/gui/pref/pg_contact_list.py:245 +msgid "Then by:" +msgstr "7#3|\\| |3'/:" + +#: src/gui/pref/pg_contact_list.py:353 src/gui/pref/pg_sandbox.py:79 +msgid "Far Left" +msgstr "|=4|2 |_3|=7" + +#: src/gui/pref/pg_contact_list.py:354 src/gui/pref/pg_sandbox.py:80 +msgid "Left" +msgstr "|_3|=7" + +#: src/gui/pref/pg_contact_list.py:355 src/gui/pref/pg_sandbox.py:81 +msgid "Badge (Lower Left)" +msgstr "|34|)63 (|_0\\/\\/3|2 |_3|=7)" + +#: src/gui/pref/pg_contact_list.py:356 src/gui/pref/pg_sandbox.py:82 +msgid "Badge (Lower Right)" +msgstr "|34|)63 (|_0\\/\\/3|2 |216#7)" + +#: src/gui/pref/pg_contact_list.py:357 src/gui/pref/pg_sandbox.py:83 +msgid "Right" +msgstr "|216#7" + +#: src/gui/pref/pg_contact_list.py:358 src/gui/pref/pg_sandbox.py:84 +msgid "Far Right" +msgstr "|=4|2 |216#7" + +#: src/gui/pref/pg_contact_list.py:377 +msgid "Show service icon on:" +msgstr "5#0\\/\\/ 53|2\\/1(3 1(0|\\| 0|\\|:" + +#: src/gui/pref/pg_contact_list.py:379 +msgid "Show status icon on:" +msgstr "5#0\\/\\/ 5747|_|2 1(0|\\| 0|\\|:" + +#: src/gui/pref/pg_contact_list.py:381 +msgid "Show buddy icon on the:" +msgstr "5#0\\/\\/ |3|_||)|)'/ 1(0|\\| 0|\\| 73#:" + +#: src/gui/pref/pg_contact_list.py:390 +msgid "Buddy icon size:" +msgstr "|3|_||)|)'/ 1(0|\\| 5123:" + +#: src/gui/pref/pg_contact_list.py:396 +msgid "Buddy padding:" +msgstr "|3|_||)|)'/ P4|)|)1|\\|6:" + +#: src/gui/pref/pg_contact_list.py:401 +msgid "Contact name:" +msgstr "(0|\\|74(7 |\\|4|\\/|3:" + +#: src/gui/pref/pg_contact_list.py:403 +msgid "&Show extra info:" +msgstr "&5#0\\/\\/ ><7|24 1|\\||=0:" + +#: src/gui/pref/pg_contact_list.py:406 +msgid "Idle Time" +msgstr "1|)|_3 71|\\/|3" + +#: src/gui/pref/pg_contact_list.py:407 +msgid "Idle Time + Status" +msgstr "1|)|_3 71|\\/|3 + 5747|_|2" + +#: src/gui/pref/pg_files.py:13 +msgid "Save &files to:" +msgstr "54\\/3 &F1|_32 2:" + +#: src/gui/pref/pg_files.py:15 +msgid "Create s&ubfolders for each IM account" +msgstr "(|23473 5&U|3|=0|_|)3|22 4 34(# 1|\\/| 4((0|_||\\|7" + +#: src/gui/pref/pg_files.py:16 +msgid "&Auto-accept all file transfers from contacts on my contact list" +msgstr "&4|_|70-4((3P7 4|_|_ |=1|_3 7|24|\\|5|=3|22 |=|20|\\/| (0|\\|74(72 0|\\| |\\/|'/ (0|\\|74(7 |_157" + +#: src/gui/pref/pg_general_profile.py:108 +msgid "&Launch Digsby when this computer starts" +msgstr "&L4|_||\\|c# |)165|3'/ \\/\\/#3|\\| |)12 (0|\\/|P|_|73|2 574|272" + +#: src/gui/pref/pg_general_profile.py:125 +msgid "&Automatically download software updates" +msgstr "&4|_|70|\\/|471(4|_|_'/ |)0\\/\\/|\\||_04|) \\/\\/4|232 |_|P|)4732" + +#: src/gui/pref/pg_general_profile.py:126 +msgid "&If connection to IM service is lost, automatically attempt to reconnect" +msgstr "&1|= (0|\\||\\|3(710|\\| 2 1|\\/| 53|2\\/1(3 |3 |_057, 4|_|70|\\/|471(4|_|_'/ 4773|\\/|P7 2 |23(0|\\||\\|3(7" + +#: src/gui/pref/pg_general_profile.py:127 +msgid "Show trending news articles in social network feeds (powered by OneRiot)" +msgstr "5#0\\/\\/ 7|23|\\||)1|\\|' |\\|3\\/\\/2 4|271(|_32 1|\\| 50(14|_ |\\|37\\/\\/0|2|< |=33|)2 (P0\\/\\/3|23|) |3'/ 0|\\|3|2107)" + +#: src/gui/pref/pg_general_profile.py:131 +msgid "General Options" +msgstr "63|\\|3|24|_ 0P710|\\|2" + +#: src/gui/pref/pg_general_profile.py:143 +msgid "Profile (AIM Only)" +msgstr "P|20|=1|_3 (41|\\/| 0|\\||_'/)" + +#: src/gui/pref/pg_general_profile.py:147 +msgid "Buddy Icon" +msgstr "|3|_||)|)'/ 1(0|\\|" + +#: src/gui/pref/pg_general_profile.py:163 +msgid "Language" +msgstr "|_4|\\|6|_|463" + +#: src/gui/pref/pg_general_profile.py:216 +msgid "Change" +msgstr "(#4|\\|63" + +#: src/gui/pref/pg_general_profile.py:252 +msgid "&Promote Digsby in my AIM profile" +msgstr "&P|20|\\/|073 |)165|3'/ 1|\\| |\\/|'/ 41|\\/| P|20|=1|_3" + +#: src/gui/pref/pg_helpdigsby.py:11 +msgid "&Allow Digsby to conduct research during idle itme" +msgstr "&4|_|_0\\/\\/ |)165|3'/ 2 (0|\\||)|_|(7 |23534|2(# |)|_||21|\\|' 1|)|_3 17|\\/|3" + +#: src/gui/pref/pg_helpdigsby.py:15 +msgid "Help Digsby" +msgstr "#3|_P |)165|3'/" + +#: src/gui/pref/pg_notifications.py:23 +msgid "bottom right corner" +msgstr "|30770|\\/| |216#7 (0|2|\\|3|2" + +#: src/gui/pref/pg_notifications.py:24 +msgid "bottom left corner" +msgstr "|30770|\\/| |_3|=7 (0|2|\\|3|2" + +#: src/gui/pref/pg_notifications.py:25 +msgid "top right corner" +msgstr "70P |216#7 (0|2|\\|3|2" + +#: src/gui/pref/pg_notifications.py:26 +msgid "top left corner" +msgstr "70P |_3|=7 (0|2|\\|3|2" + +# MARK: Fragment +#: src/gui/pref/pg_notifications.py:35 +msgid "Enable &pop-up notifications in the " +msgstr "3|\\|4|3|_3 &P0P-|_|P |\\|071|=1(4710|\\|2 1|\\| 73# " + +# MARKED: Fragment +#: src/gui/pref/pg_notifications.py:40 +msgid " on monitor " +msgstr "0|\\| |\\//|0|\\|170|2" + +#: src/gui/pref/pg_notifications.py:51 +msgid "Enable &sounds" +msgstr "3|\\|4|3|_3 &50|_||\\||)2" + +#: src/gui/pref/pg_notifications.py:52 src/gui/pref/prefstrings.py:22 +#: src/plugins/facebook/fbgui.py:49 +msgid "Notifications" +msgstr "|\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_notifications.py:59 src/plugins/myspace/msgui.py:49 +msgid "Events" +msgstr "3\\/3|\\|7" + +#: src/gui/pref/pg_notifications.py:61 +msgid "Restore Defaults" +msgstr "|23570|23 |)3|=4|_||_72" + +#: src/gui/pref/pg_notifications.py:75 +msgid "" +"Are you sure you want to restore the default notification set?\n" +"\n" +"All of your notification settings will be lost." +msgstr "" +"12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |23570|23 73# |)3|=4|_||_7 |\\|071|=1(4710|\\| 537?\n" +"\n" +"4|_|_ 0|= '/0 |\\|071|=1(4710|\\| 53771|\\|62 \\/\\/1|_|_ |3 |_057." + +#: src/gui/pref/pg_notifications.py:78 +msgid "Restore Default Notifications" +msgstr "|23570|23 |)3|=4|_||_7 |\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_privacy.py:17 +msgid "&Let others know that I am typing" +msgstr "&L37 07#3|22 |<|\\|0\\/\\/ |)47 1 |3 7YP1|\\|6" + +#: src/gui/pref/pg_privacy.py:18 +msgid "&Automatically sign me into websites (e.g., Yahoo! Mail)" +msgstr "&4|_|70|\\/|471(4|_|_'/ 516|\\| |\\/|3 1|\\|70 \\/\\/3|351732 (3.6., '/4#00! |\\/|41|_)" + +#: src/gui/pref/pg_privacy.py:21 +msgid "Global Privacy Options" +msgstr "6|_0|34|_ P|21\\/4('/ 0P710|\\|2" + +#: src/gui/pref/pg_privacy.py:144 +msgid "You must be signed in to modify privacy settings." +msgstr "J00 |\\/||_|57 |3 516|\\|3|) 1|\\| 2 |\\/|0|)1|='/ P|21\\/4('/ 53771|\\|62." + +#: src/gui/pref/pg_privacy.py:145 +#, python-format +msgid "Sign in with \"%s\"" +msgstr "516|\\| 1|\\| \\/\\/17# \"%s\"" + +#: src/gui/pref/pg_privacy.py:234 +msgid "{title} ({username})" +msgstr "{title} ({username})" + +#: src/gui/pref/pg_privacy.py:295 src/gui/pref/pg_privacy.py:518 +msgid "Allow all users to contact me" +msgstr "4|_|_0\\/\\/ 4|_|_ |_|53|22 2 (0|\\|74(7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:296 src/gui/pref/pg_privacy.py:519 +#: src/gui/pref/pg_privacy.py:884 +msgid "Allow only users on my contact list" +msgstr "4|_|_0\\/\\/ 0|\\||_'/ |_|53|22 0|\\| |\\/|'/ (0|\\|74(7 |_157" + +# MARK: Quotes +#: src/gui/pref/pg_privacy.py:297 src/gui/pref/pg_privacy.py:675 +msgid "Allow only users on 'Allow List'" +msgstr "4|_|_0\\/\\/ 0|\\||_'/ |_|53r2 0|\\| '4|_|_0\\/\\/ |_157'" + +#: src/gui/pref/pg_privacy.py:298 +msgid "Block all users" +msgstr "|3|_0(|< 4|_|_ |_|53|22" + +#: src/gui/pref/pg_privacy.py:299 src/gui/pref/pg_privacy.py:676 +#: src/gui/pref/pg_privacy.py:885 +msgid "Block only users on 'Block List'" +msgstr "|3|_0(|< 0|\\||_'/ |_|53|22 0|\\| '|3|_0(|< |_157" + +#: src/gui/pref/pg_privacy.py:301 +msgid "Only my screen name" +msgstr "0|\\||_'/ |\\/|'/ 5(|233|\\| |\\|4|\\/|3" + +#: src/gui/pref/pg_privacy.py:302 +msgid "Only that I have an account" +msgstr "0|\\||_'/ |)47 1 #4\\/3 |\\| 4((0|_||\\|7" + +#: src/gui/pref/pg_privacy.py:303 +msgid "Nothing about me" +msgstr "|\\|07#1|\\|' 4|30|_|7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:330 src/gui/pref/pg_privacy.py:556 +#: src/gui/pref/pg_privacy.py:727 src/gui/pref/pg_privacy.py:900 +#: src/gui/pref/pg_privacy.py:948 +msgid "Permissions" +msgstr "P3|2|\\/|15510|\\|2" + +# MARK: Posible grammer issue +#: src/gui/pref/pg_privacy.py:337 +msgid "Allow users who know my email address to find" +msgstr "4|_|_0\\/\\/ |_|53|25 |)47 |\\|0 |\\/|'/ 3|\\/|41|_ 4|)|)|2355 2 |=1|\\||)" + +#: src/gui/pref/pg_privacy.py:411 src/gui/pref/pg_privacy.py:470 +#: src/gui/pref/pg_privacy.py:542 src/gui/pref/pg_privacy.py:643 +#: src/gui/pref/pg_privacy.py:719 src/gui/pref/pg_privacy.py:840 +msgid "Block List" +msgstr "|3|_0(|< |_157" + +#: src/gui/pref/pg_privacy.py:494 src/gui/pref/pg_privacy.py:542 +msgid "Visible List" +msgstr "\\/151|3|_3 |_157" + +#: src/gui/pref/pg_privacy.py:498 src/gui/pref/pg_privacy.py:542 +msgid "Invisible List" +msgstr "1|\\|\\/151|3|_3 |_157" + +#: src/gui/pref/pg_privacy.py:530 src/gui/pref/pg_privacy.py:934 +msgid "Allow only users on my buddy list to contact me" +msgstr "4|_|_0\\/\\/ 0|\\||_'/ |_|53|22 0|\\| |\\/|'/ |3|_||)|)'/ |_157 2 (0|\\|74(7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:531 +msgid "Require authorization before users can add me to their contact list" +msgstr "|23Q|_|1|23 4|_|7#0|2124710|\\| |33|=0|23 |_|532 (4|\\| 4|)|) |\\/|3 2 7#31|2 (0|\\|74(7 |_157" + +#: src/gui/pref/pg_privacy.py:532 +msgid "Block authorization requests with URLs in them" +msgstr "|3|_0(|< 4|_|7#0|2124710|\\| |23Q|_|3572 \\/\\/17# |_||2|_2 1|\\| 7#3|\\/|" + +#: src/gui/pref/pg_privacy.py:533 +msgid "Allow others to view my online status from the web" +msgstr "4|_|_0\\/\\/ 07#3|22 2 \\/13\\/\\/ |\\/|'/ 0|\\||_1|\\|3 5747|_|2 |=|20|\\/| 73# \\/\\/3|3" + +#: src/gui/pref/pg_privacy.py:658 src/gui/pref/pg_privacy.py:717 +msgid "Allow List" +msgstr "4|_|_0\\/\\/ |_157" + +#: src/gui/pref/pg_privacy.py:674 src/gui/pref/pg_privacy.py:710 +msgid "Allow unknown users to contact me" +msgstr "4|_|_0\\/\\/ |_||\\||<|\\|0\\/\\/|\\| |_|53|22 2 (0|\\|74(7 |\\/|3" + +#: src/gui/pref/pg_privacy.py:862 +msgid "Could not block {item:s}" +msgstr "(0|_||_|) |\\|07 |3|_0(|< {item:s}" + +#: src/gui/pref/pg_privacy.py:870 +msgid "{item:s} is on your buddylist. Do you want to remove {item:s} from your buddylist and block the user?" +msgstr "{item:s} |=|231|\\||) |-|45. |)0 |\\|07 \\/\\/4|\\|7 && |3|_0(|< {item:s}" + +#: src/gui/pref/pg_privacy.py:872 +msgid "Confirm Block and Remove" +msgstr "(0|\\||=1|2|\\/| |3|_0(|< |\\| |23|\\/|0\\/3" + +#: src/gui/pref/pg_privacy.py:939 +msgid "Hide operating system from other users" +msgstr "|-|1|)3 05 |=|20|\\/| 07|-|3|2 |_|53|25" + +# MARK: Should be translated? +#: src/gui/pref/pg_privacy.py:984 +msgid "Nothing to see here. If you do see this, this is either a special dev-only account, or something is broken." +msgstr "|\\|07#1|\\|' 2 533 #3|23. 1|= J00 |)0 533 |)12, |)12 |3 317#3|2 @ 5P3(14|_ |)3\\/-0|\\||_'/ 4((0|_||\\|7, 0|2 50|\\/|37#1|\\|' |3 |3|20|<3|\\|." + +# MARK: Formatting +#: src/gui/pref/pg_research.py:12 +msgid "" +"Help Digsby stay free for all users. Allow Digsby to use part of your computer's idle processing power to contribute to commercial grid computing projects by enabling the Research Module.\n" +"

\n" +"This module turns on after your computer has been completely idle (no mouse or keyboard movement) for a period of time. It turns off the instant you move your mouse or press a key, so it has no effect on your PC's performance when you're using it. The research module also runs as a low priority, sandboxed Java process, so that other tasks your computer is doing will get done first, and so that it is completely secure.\n" +"

\n" +"For more details, see the Research Module FAQ.\n" +msgstr "TL;DT Research Module FAQ\n" + +#: src/gui/pref/pg_research.py:98 src/gui/pref/prefstrings.py:25 +msgid "Research Module" +msgstr "|23534|2(# |\\/|0|)|_||_3" + +# MARK: Object reference in string +#: src/gui/pref/pg_research.py:105 +msgid "Allow Digsby to use CPU time to conduct research after %2(research.idle_time_min)d minutes of idle time" +msgstr "4|_|_0\\/\\/ |)165|3'/ 2 |_|53 (P|_| 71|\\/|3 2 (0|\\||)|_|(7 |23534|2(# 4|=73|2 %2(research.idle_time_min)d |\\/|1|\\||_|732 0|= 1|)|_3 71|\\/|3" + +#: src/gui/pref/pg_research.py:111 +msgid "Maximum CPU Usage:" +msgstr "|\\/|4><1|\\/||_||\\/| (P|_| |_|5463:" + +#: src/gui/pref/pg_research.py:116 src/gui/pref/pg_research.py:123 +msgid "{val}%" +msgstr "{val}%" + +#: src/gui/pref/pg_research.py:118 +msgid "Maximum Bandwidth Usage:" +msgstr "|\\/|4><1|\\/||_||\\/| |34|\\||)\\/\\/1|)7# |_|5463:" + +#: src/gui/pref/pg_status.py:19 +msgid "Status Options" +msgstr "5747|_|2 0P710|\\|2" + +#: src/gui/pref/pg_status.py:21 +msgid "Promote Digsby in my IM status messages" +msgstr "P|20|\\/|073 |)165|3'/ 1|\\| |\\/|'/ 1|\\/| 5747|_|2 |\\/|3554632" + +#: src/gui/pref/pg_status.py:24 +msgid "Let others know that I am idle after %2(messaging.idle_after)d minutes of inactivity" +msgstr "|_37 07#3|22 |<|\\|0\\/\\/ |)47 1 |3 1|)|_3 4|=73|2 %2(messaging.idle_after)d |\\/|1|\\||_|732 0|= 1|\\|4(71\\/17'/" + +#: src/gui/pref/pg_status.py:31 +msgid "Autorespond with status message" +msgstr "4|_|70|235P0|\\||) \\/\\/17# 5747|_|2 |\\/|355463" + +#: src/gui/pref/pg_status.py:32 +msgid "Disable sounds" +msgstr "|)154|3|_3 50|_||\\||)2" + +#: src/gui/pref/pg_status.py:33 +msgid "Disable pop-up notifications" +msgstr "|)154B|_3 P0P-|_|P |\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_status.py:36 +msgid "When away..." +msgstr "\\/\\/#3|\\| 4\\/\\/4'/..." + +#: src/gui/pref/pg_status.py:43 +msgid "&Hide new conversation windows" +msgstr "&H1|)3 |\\|3\\/\\/ (0|\\|\\/3|254710|\\| \\/\\/1|\\||)0\\/\\/2" + +#: src/gui/pref/pg_status.py:44 +msgid "&Disable sounds" +msgstr "&D154|3|_3 50|_||\\||)2" + +#: src/gui/pref/pg_status.py:45 +msgid "Disable &pop-up notifications" +msgstr "|)154B|_3 &P0P-|_|P |\\|071|=1(4710|\\|2" + +#: src/gui/pref/pg_status.py:48 +msgid "When running full screen applications..." +msgstr "\\/\\/#3|\\| |2|_||\\||\\|1|\\|' |=|_||_|_ 5(|233|\\| 4PP|_1(4710|\\|2..." + +#: src/gui/pref/pg_status.py:58 src/gui/status.py:605 +msgid "New Status Message" +msgstr "|\\|3\\/\\/ 5747|_|2 |\\/|355463" + +#: src/gui/pref/pg_status.py:58 +msgid "Status Messages" +msgstr "5747|_|2 |\\/|3554632" + +#: src/gui/pref/pg_supportdigsby.py:12 +#: src/gui/supportdigsby/supportdialog.py:120 +msgid "Support Digsby" +msgstr "5|_|PP0|27 |)165|3'/" + +#: src/gui/pref/pg_text_conversations.py:20 +msgid "automatically take focus" +msgstr "4|_|70|\\/|461(4|_|_'/ 74|<3 |=0(|_|5" + +#: src/gui/pref/pg_text_conversations.py:21 +msgid "start minimized in taskbar" +msgstr "574|27 |\\/|1|\\|1|\\/|123|) 1|\\| 745|<|34|2" + +#: src/gui/pref/pg_text_conversations.py:22 +msgid "start hidden (tray icon blinks)" +msgstr "574|27 #1|)|)3|\\| (7|24'/ 1(0|\\| |3|_1|\\||<2)" + +#: src/gui/pref/pg_text_conversations.py:26 +msgid "bottom" +msgstr "|30770|\\/|" + +#: src/gui/pref/pg_text_conversations.py:27 +msgid "top" +msgstr "2" + +#: src/gui/pref/pg_text_conversations.py:28 +msgid "left" +msgstr "|_3|=7" + +#: src/gui/pref/pg_text_conversations.py:29 +msgid "right" +msgstr "|216#7" + +#: src/gui/pref/pg_text_conversations.py:45 +msgid "Group multiple conversations into one tabbed window" +msgstr "6|20|_|P |\\/||_||_71P|_3 (0|\\|\\/3|254710|\\|2 1|\\|70 0|\\|3 74|3|33|) \\/\\/1|\\||)0\\/\\/" + +# MARK: Format +#: src/gui/pref/pg_text_conversations.py:51 +msgid "New conversation windows: " +msgstr "|\\|3\\/\\/ (0|\\|\\/3|254710|\\| \\/\\/1|\\||)0\\/\\/2:" + +#: src/gui/pref/pg_text_conversations.py:53 +msgid "buddy icon" +msgstr "|3|_||)|)'/ 1(0|\\|" + +#: src/gui/pref/pg_text_conversations.py:54 +msgid "service icon" +msgstr "53|2\\/1(3 1(0|\\|" + +#: src/gui/pref/pg_text_conversations.py:55 +msgid "status icon" +msgstr "5747|_|2 1(0|\\|" + +# MARK: Fragment +#: src/gui/pref/pg_text_conversations.py:56 +msgid "Identify conversations with the contact's: " +msgstr "1|)3|\\|71|='/ (0|\\|\\/3|254710|\\|2 \\/\\/17# 73# (0|\\|7497'5: " + +#: src/gui/pref/pg_text_conversations.py:63 +msgid "Conversation Options" +msgstr "(0|\\|\\/3|254710|\\| 0P710|\\|2" + +#: src/gui/pref/pg_text_conversations.py:65 +msgid "Don't show flash ads" +msgstr "|)0|\\|'7 5#0\\/\\/ |=|_45# 4|)2" + +#: src/gui/pref/pg_text_conversations.py:69 +msgid "" +"Help keep Digsby free by showing an\n" +"advertisement in the IM window." +msgstr "5|_|PP0|27 |)165|3'/ |)3\\/3|_0P|\\/|3|\\|7 |3'/ 5#0\\/\\/1|\\|' |\\| 4|) 1|\\| 73# 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_text_conversations.py:70 +msgid "Support Digsby development with an ad" +msgstr "5|_|PP0|27 |)165|3'/ |)3\\/3|_0P|\\/|3|\\|7 |3'/ 5#0\\/\\/1|\\|' |\\| 4|) 1|\\| 73# 1|\\/| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_text_conversations.py:72 +msgid "Location of ad in IM window: " +msgstr "|_0(4710|\\| 0|= 4|) 1|\\| 1|\\/| \\/\\/1|\\||)0\\/\\/: " + +#: src/gui/pref/pg_text_conversations.py:76 +msgid "Ad Options" +msgstr "0P710|\\|2" + +#: src/gui/pref/pg_text_conversations.py:85 +msgid "Text Formatting" +msgstr "73><7 |=0|2|\\/|4771|\\|'" + +#: src/gui/pref/pg_text_conversations.py:105 +msgid "Your messages will look like this." +msgstr "'/0 |\\/|3554632 \\/\\/1|_|_ |_00|< 45 |)12." + +# MARK: Key replacement +#: src/gui/pref/pg_text_conversations.py:156 +msgid "Show last %2(conversation_window.num_lines)d lines in IM window" +msgstr "5#0\\/\\/ |_457 %2(conversation_window.num_lines)d |_1|\\|32 0|= #1570|2'/ 1|\\| (0|\\|\\/3|54710|\\| \\/\\/1|\\||)0\\/\\/" + +#: src/gui/pref/pg_text_conversations.py:159 +msgid "&Display timestamp:" +msgstr "&D15P|_4'/ 71|\\/|3574|\\/|P: " + +#: src/gui/pref/pg_text_conversations.py:167 +msgid "Spell check:" +msgstr "5P3|_|_(#3(|< 3|2|20|2" + +#: src/gui/pref/pg_text_conversations.py:186 +msgid "Log IM conversations to hard drive" +msgstr "|_06 1|\\/| (0|\\|\\/3|254710|\\|2 2 #4|2|) |)|21\\/3" + +#: src/gui/pref/pg_text_conversations.py:216 +msgid "Show &emoticons:" +msgstr "3|\\|4|3|_3 &E|\\/|071(0|\\|2: " + +#: src/gui/pref/pg_widgets.py:41 +msgid "Widget Preview" +msgstr "\\/\\/1|)637 P|23\\/13\\/\\/" + +#: src/gui/pref/pg_widgets.py:49 +msgid "&Copy To Clipboard" +msgstr "&(0P'/ 2 C|_1P|304|2d" + +#: src/gui/pref/pg_widgets.py:88 +msgid "New Widget" +msgstr "|\\|3\\/\\/ \\/\\/1|)637" + +#: src/gui/pref/pg_widgets.py:88 src/gui/pref/prefstrings.py:23 +msgid "Widgets" +msgstr "\\/\\/1|)6372" + +#: src/gui/pref/pg_widgets.py:90 +msgid "Embed Tag" +msgstr "3|\\/||33|) 746" + +#: src/gui/pref/prefcontrols.py:584 +msgid "Custom ({prefval})" +msgstr "(|_|570|\\/| ({prefval})" + +#: src/gui/pref/prefcontrols.py:799 +msgid "{val}px" +msgstr "{val}P><" + +#: src/gui/pref/prefsdialog.py:170 +msgid "Digsby Preferences" +msgstr "|)165|3'/ P|23|=3|23|\\|(32" + +#: src/gui/pref/prefsdialog.py:263 +msgid "&Done" +msgstr "&D0|\\|3" + +#: src/gui/pref/prefsdialog.py:289 +msgid "Search" +msgstr "&534|2(#" + +#: src/gui/pref/prefstrings.py:14 +msgid "Accounts" +msgstr "4((0|_||\\|75" + +#: src/gui/pref/prefstrings.py:15 +msgid "General & Profile" +msgstr "63|\\|3|24|_ & P|20|=1|_3" + +#: src/gui/pref/prefstrings.py:16 +msgid "Skins" +msgstr "5|<1|\\|2" + +#: src/gui/pref/prefstrings.py:18 +msgid "Conversations" +msgstr "(0|\\|\\/3|254710|\\|2" + +#: src/gui/pref/prefstrings.py:21 src/plugins/msim/myspacegui/privacy.py:88 +msgid "Privacy" +msgstr "P|21\\/4('/" + +#: src/gui/pref/prefstrings.py:26 src/gui/proxydialog.py:222 +msgid "Connection Settings" +msgstr "(0|\\||\\|3(710|\\| 53771|\\|62" + +#: src/gui/pref/prefstrings.py:42 +msgid "Developer" +msgstr "|)3\\/3|_0P3|2" + +#: src/gui/protocols/__init__.py:7 +msgid "Enter a new password for {username}:" +msgstr "3|\\|73|2 @ |\\|3\\/\\/ P455\\/\\/0|2|) 4 {username}::" + +#: src/gui/protocols/__init__.py:8 src/gui/protocols/jabbergui.py:139 +msgid "Change Password" +msgstr "(#4|\\|63 P455\\/\\/0|2|)" + +#: src/gui/protocols/__init__.py:22 +msgid "Are you sure you want to delete contact {name}?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 c0|\\|74c7 {name}?" + +#: src/gui/protocols/__init__.py:23 +msgid "Delete Contact" +msgstr "|)3|_373 (0|\\|74c7" + +#: src/gui/protocols/__init__.py:36 +msgid "WARNING!" +msgstr "\\/\\/4|2|\\|1|\\|6!!12eleventy" + +#: src/gui/protocols/__init__.py:37 +msgid "All your contacts in this group will be deleted locally AND on the server." +msgstr "4|_|_ |_| (0|\\|74(72 |\\| |015 6|20|_|P \\/\\/1|_|_ |3 |)3|_373|) |_0(4|_|_'/ |\\| 0|\\| 53|2\\/3|2." + +#: src/gui/protocols/__init__.py:38 +msgid "Are you sure you want to remove {groupname}?" +msgstr "|_| 5|_||23 |_| \\/\\/4|\\|4 2 |23|\\/|0\\/3 {groupname}?" + +#: src/gui/protocols/__init__.py:42 +msgid "Delete Group" +msgstr "|)3|_373 6|20|_|P" + +#: src/gui/protocols/__init__.py:47 +msgid "Add Group" +msgstr "4|)|) 6|20|_|P" + +#: src/gui/protocols/__init__.py:47 +msgid "Please enter a group name:" +msgstr "P|_3453 3|\\|73|2 @ 6|20|_|P |\\|4|\\/|3:" + +#: src/gui/protocols/__init__.py:60 +#, python-format +msgid "Are you sure you want to block %s?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |3|_0(|< %s?" + +#: src/gui/protocols/__init__.py:61 +msgid "Block Buddy" +msgstr "|3|_0(|< B|_||)|)Y" + +# %s +#: src/gui/protocols/jabbergui.py:18 +#, python-format +msgid "Enter a priority for %s:" +msgstr "3|\\|73|2 @ P|210|217'/ 4 %s:" + +#: src/gui/protocols/jabbergui.py:19 +msgid "Set Jabber Priority" +msgstr "537 J4|3|33|2 P|210|217'/" + +#: src/gui/protocols/jabbergui.py:105 +msgid "Incorrect Password" +msgstr "1|\\|(0|2|23(7 P455\\/\\/0|2|)" + +#: src/gui/protocols/jabbergui.py:111 +msgid "Failed to delete account from the server." +msgstr "|=41|_3|) 2 |)3|_373 4((0|_||\\|7 |=|20|\\/| 73# 53|2\\/3|2." + +#: src/gui/protocols/jabbergui.py:112 +msgid "Delete Account - Failed" +msgstr "|)3|_373 4((0|_||\\|7 - |=41|_3|)" + +#: src/gui/protocols/jabbergui.py:150 +msgid "Old Password: " +msgstr "0|_|) P455\\/\\/0|2|): " + +#: src/gui/protocols/jabbergui.py:152 +msgid "New Password: " +msgstr "|\\|3\\/\\/ P455\\/\\/0|2|): " + +#: src/gui/protocols/jabbergui.py:154 +msgid "Confirm New Password: " +msgstr "(0|\\||=1|2|\\/| |\\|3\\/\\/ P455\\/\\/0|2|): " + +#: src/gui/protocols/jabbergui.py:217 +msgid "Incorrect Old Password" +msgstr "1|\\|(0|2|23(7 0|_|) P455\\/\\/0|2|)" + +#: src/gui/protocols/jabbergui.py:219 +msgid "Passwords do not match" +msgstr "P455\\/\\/0|2|) |)0 |\\|07 |\\/|47(#" + +#: src/gui/protocols/jabbergui.py:219 +msgid "Paswords do not match" +msgstr "P45\\/\\/0|2|)2 |)0 |\\|07 |\\/|47(#" + +#: src/gui/protocols/jabbergui.py:280 +msgid "&Enabled" +msgstr "&3|\\|4|3|_3|)" + +#: src/gui/protocols/jabbergui.py:283 +msgid "&Scroll Lock" +msgstr "&5(|20|_|_ |_0(|<" + +#: src/gui/protocols/jabbergui.py:290 +msgid "&Close" +msgstr "&(|_053" + +#: src/gui/protocols/oscargui.py:10 +msgid "Enter a formatted screenname for {username}" +msgstr "3|)17 |=0|2|\\/|4773|) 5(|233|\\||\\|4|\\/|3 |=0|2 {username}" + +#: src/gui/protocols/oscargui.py:11 +msgid "The new screenname must be the same as the old one, except for changes in capitalization and spacing." +msgstr "73# |\\|3\\/\\/ 5(|233|\\||\\|4|\\/|3 |\\/||_|57 |3 73# 54|\\/|3 42 73# 0|_|) 0|\\|3, ><(3P7 4 (#4|\\|632 1|\\| (4P174|_124710|\\| |\\| 5P4(1|\\|6." + +#: src/gui/protocols/oscargui.py:14 +msgid "Edit Formatted Screenname" +msgstr "3|)17 |=0|2|\\/|4773|) 5(|233|\\||\\|4|\\/|3" + +#: src/gui/protocols/oscargui.py:26 +msgid "Enter an email address:" +msgstr "3|\\|73|2 |\\| 3|\\/|41|_ 4|)|)|2352:" + +#: src/gui/protocols/oscargui.py:27 +msgid "Edit Account Email: {username}" +msgstr "3|)17 4((0|_||\\|7 3|\\/|41|_: {username}" + +#: src/gui/proxydialog.py:40 +msgid "&No proxy" +msgstr "&N0 P|20><'/" + +#: src/gui/proxydialog.py:41 +msgid "Use &default system settings" +msgstr "|_|53 &D3|=4|_||_7 5'/573|\\/| 53771|\\|62" + +#: src/gui/proxydialog.py:42 +msgid "&Specify proxy settings" +msgstr "&5P3(1|=Y P|20><'/ 53771|\\|62" + +#: src/gui/proxydialog.py:59 +msgid "&Host:" +msgstr "&H057:" + +#: src/gui/proxydialog.py:66 +msgid "P&ort:" +msgstr "P&0|27:" + +#: src/gui/proxydialog.py:96 +msgid "&Username:" +msgstr "&|_|53|2|\\|4|\\/|3:" + +#: src/gui/proxydialog.py:105 +msgid "&Password:" +msgstr "&P455\\/\\/0|2|):" + +#: src/gui/proxydialog.py:117 +msgid "Proxy Server" +msgstr "P|20><'/ 53|2\\/3|2" + +#: src/gui/proxydialog.py:118 +msgid "Protocol" +msgstr "P|2070(0|_" + +#: src/gui/proxydialog.py:121 +msgid "How to Connect" +msgstr "#0\\/\\/ 2 (0|\\||\\|3(7" + +#: src/gui/proxydialog.py:123 +msgid "Authentication" +msgstr "4|_|7#3|\\|71(4710|\\|" + +#: src/gui/searchgui.py:43 +msgid "Arrange Searches" +msgstr "4|2|24|\\|63 534|2(#32" + +#: src/gui/searchgui.py:89 +msgid "Search {search_engine_name:s} for \"{search_string:s}\"" +msgstr "534|2(|-| {search_engine_name:s} 4 \"{search_string:s}\"" + +#: src/gui/searchgui.py:132 +msgid "Web Search" +msgstr "\\/\\/3|3 534|2(#" + +#: src/gui/social_status_dialog.py:103 +msgid "Set IM Status" +msgstr "537 1|\\/| 5747|_|2" + +#: src/gui/social_status_dialog.py:380 +msgid "Choose Accounts:" +msgstr "(#0053 4((0|_||\\|72:" + +#: src/gui/social_status_dialog.py:438 +msgid "Insert Link" +msgstr "1|\\|53|27 |_1|\\||<" + +#: src/gui/social_status_dialog.py:451 +msgid "Website URL:" +msgstr "\\/\\/3|35173 |_||2|_:" + +#: src/gui/social_status_dialog.py:461 +msgid "Insert &Link" +msgstr "1|\\|53|27 &L1|\\||<" + +#: src/gui/social_status_dialog.py:507 +msgid "Share Image" +msgstr "5#4|23 1|\\/|463" + +#: src/gui/social_status_dialog.py:582 +msgid "Uploading Image..." +msgstr "|_|P|_04|)1|\\|' 1|\\/|463..." + +#: src/gui/social_status_dialog.py:777 +msgid "Errors Encountered:" +msgstr "3|2|20|2 3|\\|(0|_||\\|73|23|):" + +#: src/gui/social_status_dialog.py:882 +#: src/plugins/msim/myspacegui/editstatus.py:6 +msgid "Your Status:" +msgstr "'/0 5747|_|5:" + +#: src/gui/social_status_dialog.py:891 +msgid "&Update Status" +msgstr "&|_|P|)473 5747|_|2" + +# MARK: %r replacement +#: src/gui/social_status_dialog.py:1081 +#, python-format +msgid "Network error (%r)" +msgstr "|\\|37\\/\\/0|2|< 3|2|20|2 (%r)" + +# MARK: %s replacement +#: src/gui/social_status_dialog.py:1083 +#, python-format +msgid "Network error (%s)" +msgstr "|\\|37\\/\\/0|2|< 3|2|20|2 (%s)" + +# MARK: I don't know what that means +#: src/gui/social_status_dialog.py:1085 +msgid "Network error ({error!r} - {error!s})" +msgstr "|\\|37\\/\\/0|2|< 3|2|20|2 " + +# MARK: Ambiguous +#: src/gui/social_status_dialog.py:1087 src/gui/social_status_dialog.py:1090 +#, python-format +msgid "%s" +msgstr "%s" + +# MARK: %s replacement +#: src/gui/social_status_dialog.py:1092 +#, python-format +msgid "Unknown error (%s)" +msgstr "|_||\\||<|\\|0\\/\\/|\\| 3|2|20|2 %s" + +#: src/gui/social_status_dialog.py:1094 +msgid "Unknown error" +msgstr "|_||\\||<|\\|0\\/\\/|\\| 3|2|20|2" + +#: src/gui/spellchecktextctrlmixin.py:550 +msgid "Add to Dictionary" +msgstr "4|)|) 2 |)1(710|\\|4|2'/" + +# MARK: Needs context +#: src/gui/status.py:116 +msgid "/" +msgstr "/" + +#: src/gui/status.py:117 +msgid "{status} ({account_types} Only)" +msgstr "{status} ({account_types} 0|\\||_'/)" + +# MARK: unsure? +#: src/gui/status.py:372 +msgid "Message" +msgstr "|\\/|355463" + +#: src/gui/status.py:494 +msgid "&Title:" +msgstr "&717|_3:" + +#: src/gui/status.py:499 +msgid "&State:" +msgstr "&57473:" + +#: src/gui/status.py:507 +msgid "&Status message:" +msgstr "&5747|_|2 |\\/|355463:" + +#: src/gui/status.py:533 +msgid "&Use a different status for some accounts" +msgstr "&|_|53 @ |)1|=|=3|23|\\|7 5747|_|2 4 50|\\/|3 4((0|_||\\|72" + +#: src/gui/status.py:537 +msgid "&Set" +msgstr "&537" + +#: src/gui/status.py:546 +msgid "Save for &later" +msgstr "54\\/3 |=0|2 |_473|2" + +#: src/gui/status.py:606 +msgid "New Status for {name}" +msgstr "|\\|3\\/\\/ 5747|_|2 |=0|2 {name}" + +#: src/gui/status.py:631 +msgid "Edit Status Message" +msgstr "3|)17 5747|_|2 |\\/|355463" + +#: src/gui/status.py:634 +msgid "Edit Status for {account}" +msgstr "3|)17 5747|_|2 4 {account}" + +#: src/gui/status.py:791 +msgid "Are you sure you want to delete status message \"{title}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 5747|_|2 |\\/|355463 \"{title}\"?" + +#: src/gui/status.py:792 +msgid "Delete Status Message" +msgstr "|)3|_373 S747|_|2 |\\/|355463" + +#: src/gui/statuscombo.py:87 src/gui/statuscombo.py:165 +#: src/gui/statuscombo.py:529 src/plugins/twitter/twitter_gui.py:615 +msgid "Global Status" +msgstr "6|_0|34|_ 5747|_|5" + +#: src/gui/statuscombo.py:105 src/gui/statuscombo.py:540 +msgid "Promote Digsby!" +msgstr "P|20|\\/|073 |)165|3'/!" + +#: src/gui/statuscombo.py:125 src/gui/statuscombo.py:160 +#: src/gui/statuscombo.py:526 +msgid "Custom..." +msgstr "(|_|570|\\/|..." + +#: src/gui/statuscombo.py:639 +msgid "Press 'Ctrl+F' to Search List" +msgstr "P|2355 'Ctrl+F' 2 534|2(# |_157" + +#: src/gui/supportdigsby/supportdialog.py:96 +#: src/gui/supportdigsby/supportoptions.py:71 +msgid "Are you sure?" +msgstr "12 j00 5|_||23?" + +#: src/gui/supportdigsby/supportdialog.py:97 +msgid "Are you sure you want do this?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 |)0 |)12?" + +#: src/gui/supportdigsby/supportdialog.py:111 +msgid "Thank you for supporting Digsby." +msgstr "7'/ 4 5|_|PP0|271|\\|' |)165|3'/." + +#: src/gui/supportdigsby/supportdialog.py:111 +msgid "Thank you!" +msgstr "7'/!" + +#: src/gui/supportdigsby/supportoptions.py:22 +#: src/gui/supportdigsby/supportoptions.py:32 +msgid "Set" +msgstr "537" + +#: src/gui/supportdigsby/supportoptions.py:23 +msgid "Make Google Powered Digsby Search your homepage" +msgstr "|\\/|4|<3 6006|_3 P0\\/\\/3|23|) |)165|3'/ 534|2(# '/0 |)3|=4|_||_7 534|2(# #0|\\/|3P463" + +#: src/gui/supportdigsby/supportoptions.py:33 +msgid "Make Google Powered Digsby Search your default search engine" +msgstr "|\\/|4|<3 6006|_3 P0\\/\\/3|23|) |)165|3'/ 534|2(# '/0 |)3|=4|_||_7 534|2(# 3|\\|61|\\|3" + +#: src/gui/supportdigsby/supportoptions.py:42 +#: src/gui/supportdigsby/supportoptions.py:47 +msgid "Invite" +msgstr "1|\\|\\/173" + +#: src/gui/supportdigsby/supportoptions.py:43 +msgid "Invite your friends via Email" +msgstr "1|\\|\\/173 '/0 |=|213|\\||)2 \\/14 3|\\/|41|_" + +#: src/gui/supportdigsby/supportoptions.py:48 +msgid "Invite your friends via Facebook" +msgstr "1|\\|\\/173 '/0 |=|213|\\||)2 \\/14 |=4(3|300|<" + +#: src/gui/supportdigsby/supportoptions.py:52 +#: src/gui/supportdigsby/supportoptions.py:57 +msgid "Join Group" +msgstr "J01|\\| 6|20|_|P" + +#: src/gui/supportdigsby/supportoptions.py:53 +msgid "Become a fan on Facebook" +msgstr "|33(0|\\/|3 @ |=4|\\| 0|\\| |=4(3|300|<" + +#: src/gui/supportdigsby/supportoptions.py:58 +msgid "Become a fan on LinkedIn" +msgstr "|33(0|\\/|3 @ |=4|\\| 0|\\| |_1|\\||<3|)1|\\|" + +#: src/gui/supportdigsby/supportoptions.py:62 +msgid "Follow" +msgstr "|=0|_|_0\\/\\/" + +#: src/gui/supportdigsby/supportoptions.py:63 +msgid "Follow us on Twitter" +msgstr "|=0|_|_0\\/\\/ |_|2 0|\\| 7\\/\\/1773|2" + +#: src/gui/supportdigsby/supportoptions.py:67 +msgid "Help Digsby conduct research" +msgstr "#3|_P |)165|3'/ (0|\\||)|_|(7 |23534|2(#" + +#: src/gui/supportdigsby/supportoptions.py:70 +msgid "Helping us conduct research keeps Digsby free and ad-free. Are you sure you want to disable this option?" +msgstr "#3|_P1|\\|' |_|2 (0|\\||)|_|(7 |23534|2(# |<33P2 D165|3'/ |=|233 |\\| 4|)-|=r33. 12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)154|3|_3 |)12 0P710|\\|?" + +#: src/gui/supportdigsby/supportoptions.py:83 +msgid "Disable" +msgstr "|)154B|_3" + +#: src/gui/supportdigsby/supportoptions.py:85 +msgid "Enable" +msgstr "3|\\|4|3|_3" + +#: src/gui/supportdigsby/supportoptions.py:94 +msgid "Subscribe" +msgstr "5|_||35(|21|33" + +#: src/gui/supportdigsby/supportoptions.py:95 +msgid "Subscribe to our Blog" +msgstr "5|_||35(|21|33 2 0|_|r |3|_06" + +#: src/gui/supportdigsby/supportoptions.py:99 +msgid "Create" +msgstr "(|23473" + +#: src/gui/supportdigsby/supportoptions.py:100 +msgid "Create a Digsby widget for your blog or website" +msgstr "(|23473 @ |)165|3'/ \\/\\/1|)637 4 J00 |3|_06 0|2 \\/\\/3|35173" + +# MARK: %r replacement +#: src/gui/supportdigsby/supportoptions.py:105 +#, python-format +msgid "Set to %r" +msgstr "537 2 %r" + +# MARK: Is this supposed to be translated? +#: src/gui/supportdigsby/supportoptions.py:108 +#, python-format +msgid "Increment this number: %r" +msgstr "1|\\|(|23|\\/|3|\\|7 |)12 |\\||_||\\/||33|2: %r" + +#: src/gui/toast/toasthelp.py:25 +msgid "TIP: Popup Notifications" +msgstr "71P: P0P|_|P |\\|071|=1(4710|\\|2" + +#: src/gui/toast/toasthelp.py:27 +msgid "You can right click popups to close them right away instead of waiting for them to fade out." +msgstr "J00 (4|\\| |216#7 (|_1(|< P0P|_|P2 2 (|_053 7#3|\\/| |216#7 4\\/\\/4'/ 1|\\|5734|) 0|= \\/\\/4171|\\|' 4 7#3|\\/| 2 |=4|)3 0|_|7." + +#: src/gui/toolbox/toolbox.py:427 +msgid "Confirm" +msgstr "(0|\\||=1|2|\\/|" + +#: src/gui/toolbox/toolbox.py:1225 src/gui/toolbox/toolbox.py:1252 +msgid "Right To Left" +msgstr "|216#7 2 |_3|=7" + +#: src/gui/toolbox/toolbox.py:1336 +msgid "Use Long URL" +msgstr "|_|53 |_0|\\|6 |_||2|_" + +#: src/gui/toolbox/toolbox.py:1346 +msgid "Shorten URL" +msgstr "5#0|273|\\| |_||2|_" + +#: src/gui/toolbox/toolbox.py:1437 +msgid "Undo" +msgstr "|_||\\||)0" + +#: src/gui/toolbox/toolbox.py:1440 +msgid "Redo" +msgstr "|23|)0" + +#: src/gui/toolbox/toolbox.py:1445 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:104 +msgid "Cut" +msgstr "(|_|7" + +#: src/gui/toolbox/toolbox.py:1455 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:108 +msgid "Select All" +msgstr "53|_3(7 4|_|_" + +#: src/gui/toolbox/toolbox.py:1461 +msgid "Select an image file" +msgstr "53|_3(7 |\\| 1|\\/|463 |=1|_3" + +#: src/gui/trayicons.py:172 +msgid "Set &Global Status..." +msgstr "537 &G|_0|34|_ 5747|_|2..." + +#: src/gui/trayicons.py:175 +msgid "Set IM &Status" +msgstr "537 1|\\/| &S747|_|5" + +#: src/gui/trayicons.py:179 src/gui/trayicons.py:185 +msgid "Show &Buddy List" +msgstr "5#0\\/\\/ &B|_||)|)'/ |_157" + +#: src/gui/trayicons.py:185 +msgid "Hide &Buddy List" +msgstr "#1|)3 &B|_||)|)'/ |_157" + +#: src/gui/trayicons.py:190 +msgid "Show Menu Bar" +msgstr "5#0\\/\\/ |\\/|3|\\||_| |34|2" + +#: src/gui/trayicons.py:214 +msgid "Digsby" +msgstr "D165|3'/" + +#: src/gui/trayicons.py:225 +msgid "{msgs} message" +msgid_plural "{msgs} messages" +msgstr[0] "{msgs} |\\/|355463" +msgstr[1] "{msgs} |\\/|3554632" + +# MARK: Should be translated? +#: src/gui/trayicons.py:230 +msgid "{alias} ({service}) - {message}" +msgstr "{alias} ({service}) - {message}" + +#: src/gui/trayicons.py:298 +msgid "Show All" +msgstr "5#0\\/\\/ 4|_|_" + +#: src/gui/uberwidgets/formattedinput.py:423 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:103 +msgid "Hide Formatting Bar" +msgstr "#1|)3 |=0|2|\\/|4771|\\|' B4|2" + +#: src/gui/uberwidgets/formattedinput.py:726 +#: src/gui/uberwidgets/formattedinput2/formattedinput.py:206 +msgid "Set Font..." +msgstr "537 |=0|\\|7..." + +#: src/gui/uberwidgets/formattedinput.py:739 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:133 +msgid "Choose a foreground color" +msgstr "(#0053 @ |=0|236|20|_||\\||) (0|_0|2" + +#: src/gui/vcard/vcardgui.py:19 +msgid "Unit" +msgstr "|_||\\|17" + +#: src/gui/vcard/vcardgui.py:48 +msgid "Last Name" +msgstr "|_457 |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:49 +msgid "First Name" +msgstr "|=1|257 |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:50 +msgid "Middle Name" +msgstr "|\\/|1|)|)|_3 |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:51 +msgid "Prefix" +msgstr "P|23|=1><" + +#: src/gui/vcard/vcardgui.py:52 +msgid "Suffix" +msgstr "S|_||=|=1><" + +#: src/gui/vcard/vcardgui.py:108 +msgid "Number" +msgstr "|\\||_||\\/||33|2" + +#: src/gui/vcard/vcardgui.py:173 +msgid "Street" +msgstr "57|2337" + +#: src/gui/vcard/vcardgui.py:176 +msgid "City" +msgstr "(17'/" + +#: src/gui/vcard/vcardgui.py:177 +msgid "State" +msgstr "57473" + +#: src/gui/vcard/vcardgui.py:178 +msgid "Postal Code" +msgstr "P0574|_ (0|)3" + +#: src/gui/vcard/vcardgui.py:179 +msgid "Country" +msgstr "(0|_||\\|7|2'/" + +#: src/gui/vcard/vcardgui.py:263 +#, python-format +msgid "vCard Editor for %(username)s" +msgstr "\\/(4|2|) 3|)170|2 4 %(username)s" + +# MARK: Should be translated? +#: src/gui/vcard/vcardgui.py:263 +msgid "vCard Viewer" +msgstr "\\/(4|2|) \\/13\\/\\/3|2" + +#: src/gui/vcard/vcardgui.py:274 +msgid "Retreive" +msgstr "|237|231\\/3" + +#: src/gui/vcard/vcardgui.py:334 +msgid "General" +msgstr "63|\\|3|24|_" + +#: src/gui/vcard/vcardgui.py:337 +msgid "Full Name" +msgstr "|=|_||_|_ |\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:338 src/plugins/msim/res/content.tenjin:22 +msgid "Nickname" +msgstr "|\\|1(|<|\\|4|\\/|3" + +#: src/gui/vcard/vcardgui.py:339 src/oscar/OscarBuddies.py:282 +msgid "Birthday" +msgstr "|31|27#|)4'/" + +#: src/gui/vcard/vcardgui.py:341 +msgid "Homepage" +msgstr "#0|\\/|3P463" + +#: src/gui/vcard/vcardgui.py:358 +msgid "Work" +msgstr "\\/\\/0|2|<" + +#: src/gui/vcard/vcardgui.py:363 src/oscar/OscarBuddies.py:330 +msgid "Position" +msgstr "P051710|\\|" + +#: src/gui/vcard/vcardgui.py:364 +msgid "Role" +msgstr "|20|_3" + +#: src/gui/vcard/vcardgui.py:377 src/plugins/msim/res/content.tenjin:26 +msgid "Location" +msgstr "|_0(4710|\\|" + +#: src/gui/vcard/vcardgui.py:390 +msgid "About" +msgstr "4|30|_|7" + +#: src/gui/video/webvideo.py:19 +msgid "{name} has invited you to an audio/video chat." +msgstr "{name} 1|\\|\\/173|) |_| 2 |\\| 4|_||)10/\\/1|)30 (#47." + +#: src/gui/video/webvideo.py:20 src/hub.py:125 +msgid "Would you like to join?" +msgstr "J01|\\| 637?" + +#: src/gui/video/webvideo.py:24 +msgid "Audio/Video Chat Invitation" +msgstr "4|_||)10/\\/1|)30 (#47 1|\\|\\/174710|\\|" + +#: src/gui/visuallisteditor.py:114 +msgid "(Unnamed Panel)" +msgstr "(|_||\\||\\|4|\\/|3|) P4|\\|3|_)" + +# MARK: Simular to line in contact dialog: "Drag and drop to rearrange:" +#: src/gui/visuallisteditor.py:298 +msgid "Drag and drop to reorder" +msgstr "|)|246 |\\| |)|20P 2 |230|2|)3|2" + +#: src/gui/visuallisteditor.py:308 +msgid "Done" +msgstr "|)0|\\|3" + +#: src/gui/widgetlist.py:124 +msgid "Are you sure you want to delete widget \"{widgetname}\"?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |)3|_373 \\/\\/1|)637 \"{widgetname}\"?" + +#: src/gui/widgetlist.py:125 +msgid "Delete Widget" +msgstr "|)3|_373 \\/\\/1|)637" + +#: src/hub.py:110 +msgid "{protocolname} DirectIM" +msgstr "{protocolname} |)1|23(71|\\/|" + +#: src/hub.py:111 +msgid "{name} wants to directly connect with you. (Your IP address will be revealed.)" +msgstr "{name} \\/\\/4|\\|75 |)1|23>< (0|\\||\\|3><" + +#: src/hub.py:130 +msgid "{name} has invited you to a group chat." +msgstr "{name} \\/\\/4|\\|75 70 P4|27'/ |_|P." + +#: src/hub.py:133 +msgid "You have been invited to a group chat." +msgstr "J00 #4\\/3 |333|\\| 1|\\|\\/173|) 2 @ 6|20|_|P (#47." + +#: src/hub.py:143 +msgid "Join" +msgstr "J01|\\|" + +#: src/hub.py:144 +msgid "Ignore" +msgstr "16|\\|0|23" + +#: src/hub.py:154 +msgid "{protocol_name:s} Invite" +msgstr "{protocol_name:s} 1|\\|\\/173" + +#: src/hub.py:160 +msgid "Ignore Invite" +msgstr "16|\\|0|23 1|\\|\\/173" + +#: src/hub.py:179 +msgid "Allow {buddy} to add you ({you}) as a buddy on {protocol}?" +msgstr "4|_|_0\\/\\/ {buddy} 2 4|)|) J00 ({you}) 42 @ |3|_||)|)'/ 0|\\| {protocol}?" + +#: src/imaccount.py:515 src/imaccount.py:522 +msgid "Connect" +msgstr "(0|\\||\\|3(7" + +#: src/imaccount.py:523 +msgid "Disconnect" +msgstr "|)15(0|\\||\\|3(7" + +#: src/jabber/JabberBuddy.py:176 src/oscar/OscarBuddies.py:270 +msgid "Full Name:" +msgstr "|=|_||_|_ |\\|4|\\/|3:" + +#: src/jabber/JabberBuddy.py:187 +msgid "Birthday:" +msgstr "|31|27#|)4'/:" + +#: src/jabber/JabberBuddy.py:187 +#: src/plugins/digsby_service_editor/default_ui.py:143 +msgid "Email Address:" +msgstr "3|\\/|41|_ 4||)|)|255:" + +#: src/jabber/JabberBuddy.py:187 +msgid "Phone:" +msgstr "P|-|0|\\|3:" + +#: src/jabber/JabberBuddy.py:196 +msgid "Website" +msgstr "\\/\\/3|35173" + +#: src/jabber/JabberBuddy.py:203 src/oscar/OscarBuddies.py:304 +msgid "Additional Information:" +msgstr "4|)|)1710|\\|4|_ 1|\\||=0|2|\\/|4710|\\|" + +#: src/jabber/JabberBuddy.py:210 +msgid "{street} {extra_adress} {locality} {region} {postal_code} {country}" +msgstr "{street} {extra_adress} {locality} {region} {postal_code} {country}" + +#: src/jabber/JabberBuddy.py:256 src/oscar/OscarBuddies.py:310 +msgid "Home Address:" +msgstr "|-|0|\\/|3 4||)|)|255:" + +#: src/jabber/JabberChat.py:298 +msgid "You have been invited to {roomname}" +msgstr "J00 #4\\/3 |333|\\| 1|\\|\\/173|) 2 {roomname}" + +#: src/jabber/JabberConversation.py:133 +msgid "{name} has left the conversation." +msgstr "{name} |24|\\| 4\\/\\/4'/." + +#: src/jabber/JabberResource.py:15 src/oscar/OscarBuddies.py:45 +msgid "Free for Chat" +msgstr "|=|233 4 (#47" + +#: src/main.py:379 +msgid "Your digsby password has been changed. Please log back in with the new password." +msgstr "'/0 |)165b'/ P455\\/\\/0|2|) #42 |333|\\| (#4|\\|63|). P|_3453 |_06 |34(|< 1|\\| \\/\\/17# 73# |\\|3\\/\\/ P455\\/\\/0|2|)." + +#: src/main.py:381 +msgid "Password Changed" +msgstr "P455\\/\\/0|2|) (#4|\\|63|)" + +#: src/main.py:562 +msgid "Advanced Preferences" +msgstr "4|)\\/4|\\|(3|) P|23|=3|23|\\|(32" + +#: src/main.py:562 +msgid "Please login first." +msgstr "P|_3453 |_061|\\| |=1|257." + +#: src/main.py:816 +msgid "Global Shortcuts" +msgstr "6|_0|34|_ 5#0|27(|_|72" + +#: src/main.py:820 +msgid "Debug Global Shortcuts" +msgstr "|)3|3|_|6 6|_0|34|_ 5#0|27(|_|72" + +#: src/main.py:823 +msgid "Text Controls" +msgstr "73><7 (0|\\|7|20|_2" + +#: src/main.py:1321 +msgid "There was an error submitting your crash report." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 5|_||3|\\/|1771|\\|' '/0 (|245# |23P0|27." + +#: src/main.py:1334 +msgid "Crash report submitted successfully." +msgstr "(|245# |23P0|27 5|_||3|\\/|1773|) 5|_|((355|=|_||_|_'/." + +#: src/main.py:1341 +msgid "Would you like to restart Digsby now?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 |23574|27 |)165|3'/ |\\|0\\/\\/?" + +#: src/main.py:1343 +msgid "Crash Report" +msgstr "(|245# |23P0|27" + +#: src/msn/MSNBuddy.py:26 src/plugins/provider_windows_live/info.yaml:32 +msgid "Be Right Back" +msgstr "|3 |216#7 |34(|<" + +#: src/msn/MSNBuddy.py:27 src/plugins/provider_windows_live/info.yaml:34 +msgid "On the Phone" +msgstr "0|\\| 73# P#0|\\|3" + +#: src/msn/MSNBuddy.py:28 src/plugins/provider_windows_live/info.yaml:35 +msgid "Out to Lunch" +msgstr "0|_|7 2 |_|_||\\|(#" + +#: src/msn/MSNBuddy.py:284 src/oscar/OscarBuddies.py:232 +msgid "Mobile" +msgstr "|\\/|0|31|_3" + +#: src/msn/MSNBuddy.py:488 src/plugins/digsby_service_editor/default_ui.py:207 +msgid "Display Name:" +msgstr "|)15P|_4Y |\\|4|\\/|3:" + +#: src/msn/MSNBuddy.py:555 +msgid "Administrators:" +msgstr "4|)|\\/|1|\\|157|2470|22:" + +#: src/msn/MSNBuddy.py:557 +msgid "Assistant Administrators:" +msgstr "45515574|\\|7 4|)|\\/|1|\\|157|2470|22:" + +#: src/msn/MSNBuddy.py:559 +msgid "Members:" +msgstr "M3|\\/||33|22" + +#: src/msn/MSNBuddy.py:561 +msgid "Invited:" +msgstr "1|\\|\\/173|):" + +#: src/msn/MSNBuddy.py:797 +msgid "{song:s} by {artist:s}" +msgstr "{song:s}|3'/ {artist:s}" + +#: src/msn/MSNConversation.py:287 +msgid "There was a network error creating a chat session." +msgstr "7#3|23 \\/\\/|_|2 @ |\\|37\\/\\/0|2|< 3|2|20|2 (|23471|\\|' @ (#47 535510|\\|." + +#: src/msn/MSNConversation.py:466 +msgid "Your messages could not be sent because too many conversation sessions have been requested." +msgstr "'/0 |\\/|3554632 (0|_||_|) |\\|07 |3 53|\\|7 (02 700 |\\/|4|\\|'/ (0|\\|\\/3|254710|\\| 535510|\\|2 #4\\/3 |333|\\| |23Q|_|3573|)." + +# MARK: Fragment +#: src/msn/MSNConversation.py:597 src/msn/p21/MSNP21Conversation.py:259 +msgid "winked at you!" +msgstr "\\/\\/1|\\||<3|) 47 J00!" + +# MARK: Fragment +#: src/msn/MSNConversation.py:598 src/msn/p21/MSNP21Conversation.py:260 +msgid "nudged you!" +msgstr "|\\||_||)63|) J00!" + +#: src/msn/msngui.py:15 +msgid "Group Invite" +msgstr "6|20|_|P 1|\\|\\/173" + +#: src/msn/msngui.py:20 src/plugins/digsby_updater/updater.py:487 +#: src/plugins/digsby_updater/updater.py:499 +msgid "Yes" +msgstr "'/32" + +#: src/msn/msngui.py:21 src/plugins/digsby_updater/updater.py:488 +#: src/plugins/digsby_updater/updater.py:500 +msgid "No" +msgstr "|\\|0" + +# MARK: Formatting +#: src/msn/msngui.py:25 +#, python-format +msgid "" +"\n" +"%(inviter_email)s says: \"%(invite_message)s\"" +msgstr "" +"\n" +"%(inviter_email)s says: \"%(invite_message)s\"" + +# MARK: %s replacement +#: src/msn/msngui.py:28 +#, python-format +msgid "" +"%(inviter_email)s has invited you to join the group %(circle_name)s.%(invite_segment)s\n" +"Would you like to join?" +msgstr "" +"%(inviter_email)s #42 1|\\|\\/173|) J00 2 J01|\\| 73# 6|20|_|P %(circle_name)s.%(invite_segment)s\n" +"\\/\\/0|_||_|) J00 \\/\\/|_||3 2 J01|\\|?" + +#: src/msn/msngui.py:36 +msgid "Please enter a name for your group chat:" +msgstr "P|_3453 3|\\|73|2 4 |\\|4|\\/|3 6|20|_|P 4 J00 6|20|_|P (|-|47:" + +#: src/oscar/OscarBuddies.py:46 src/plugins/provider_aol/info.yaml:142 +msgid "Occupied" +msgstr "0((|_|P13|)" + +#: src/oscar/OscarBuddies.py:255 src/oscar/OscarBuddies.py:346 +msgid "Profile URL:" +msgstr "P|20|=1|_3 |_||2|_:" + +#: src/oscar/OscarBuddies.py:281 src/plugins/msim/res/content.tenjin:25 +msgid "Gender" +msgstr "63|\\||)3|2" + +#: src/oscar/OscarBuddies.py:285 src/oscar/OscarBuddies.py:333 +msgid "{label}:" +msgstr "{label}:" + +#: src/oscar/OscarBuddies.py:291 +msgid "Website:" +msgstr "\\/\\/3|35173:" + +#: src/oscar/OscarBuddies.py:323 +msgid "Work Address:" +msgstr "\\/\\/0|2|< 4||)|)|255:" + +#: src/oscar/OscarBuddies.py:330 +msgid "Company" +msgstr "(0|\\/|P4|\\|'/" + +#: src/oscar/OscarBuddies.py:330 +msgid "Department" +msgstr "|)3P4|27|\\/|3|\\|7" + +#: src/oscar/OscarBuddies.py:338 +msgid "Work Website:" +msgstr "\\/\\/0|2|< \\/\\/3|35173:" + +#: src/oscar/snac/family_x07.py:165 +msgid "You should receive an email with confirmation instructions shortly." +msgstr "J00 5#0|_||_|) |23(31\\/3 |\\| 3|\\/|41|_ \\/\\/17# (0|\\||=1|2|\\/|4710|\\| 1|\\|57|2|_|9710|\\|2 5#0|27|_'/." + +#: src/oscar/snac/family_x07.py:166 +msgid "Your account is already confirmed." +msgstr "'/0 4((0|_||\\|7 |3 4|_|234|)'/ (0|\\||=1|2|\\/|3|)." + +#: src/oscar/snac/family_x07.py:167 +msgid "There was an unknown server error." +msgstr "7#3|23 \\/\\/|_|2 |\\| |_||\\||<|\\|0\\/\\/|\\| 53|2\\/3|2 3|2|20|2." + +#: src/oscar/snac/family_x07.py:180 +msgid "Confirm Account: {username}" +msgstr "(0|\\||=1|2|\\/| 4((0|_||\\|7: {username}" + +#: src/oscar/snac/family_x15.py:636 +msgid "Female" +msgstr "|=3|\\/|4|_3" + +#: src/oscar/snac/family_x15.py:636 +msgid "Male" +msgstr "|\\/|4|_3" + +#: src/plugins/component_gmail/gmail.py:123 +msgid "Mark as Read" +msgstr "|\\/|4|2|< 42 |234|)" + +#: src/plugins/component_gmail/gmail.py:124 +msgid "Archive" +msgstr "4|2(#1\\/3" + +#: src/plugins/component_gmail/info.yaml:2 +msgid "Gmail" +msgstr "6|\\/|41|_" + +#: src/plugins/component_gmail/info.yaml:6 +msgid "Gmail Account" +msgstr "3|\\/|41|_ 4((0|_||\\|72" + +#: src/plugins/component_gtalk/info.yaml:0 +msgid "Google Talk" +msgstr "6006|_3 74|_|<" + +#: src/plugins/component_gtalk/info.yaml:59 +#: src/plugins/provider_google/info.yaml:0 +#: src/plugins/provider_google/info.yaml:10 +msgid "Google Account" +msgstr "(#0053 4((0|_||\\|72:" + +#: src/plugins/component_yahooim/info.yaml:1 +msgid "Yahoo" +msgstr "'/4#00" + +#: src/plugins/component_yahooim/info.yaml:3 +msgid "Yahoo Messenger" +msgstr "'/4|-|00 |\\/|355463|2" + +#: src/plugins/component_yahooim/info.yaml:7 +#: src/plugins/component_ymail/info.yaml:7 +#: src/plugins/provider_yahoo/info.yaml:10 +msgid "Yahoo! ID" +msgstr "'/4#00! 1|)" + +#: src/plugins/component_ymail/info.yaml:2 +msgid "Yahoo! Mail" +msgstr "'/4#00 |\\/|41|_" + +#: src/plugins/component_ymail/info.yaml:3 +msgid "Yahoo Mail" +msgstr "'/4#00 |\\/|41|_" + +#: src/plugins/digsby_about/aboutdialog.py:48 +msgid "About Digsby" +msgstr "4|30|_|7 |)165|3'/" + +#: src/plugins/digsby_about/res/content.tenjin:14 +msgid "Checking for updates..." +msgstr "(#3(|< 4 |_|P|)4732..." + +#: src/plugins/digsby_about/res/content.tenjin:20 +msgid "Digsby is up to date" +msgstr "|)165|3'/ 15 |_|P 2 |)473" + +#: src/plugins/digsby_about/res/content.tenjin:26 +#: src/plugins/digsby_updater/updater.py:484 +#: src/plugins/digsby_updater/updater.py:496 +msgid "Update Available" +msgstr "|_|P|)473 4\\/41|_4|3|_3" + +#: src/plugins/digsby_about/res/content.tenjin:31 +msgid "Copyright © 2010 dotSyntax, LLC." +msgstr "(0p'/|216#7 © 2010 |)07S'/|\\|74><, |_|_(." + +#: src/plugins/digsby_about/res/content.tenjin:33 +msgid "All Rights Reserved." +msgstr "4|_|_ |216#72 |2353|2\\/3|)." + +#: src/plugins/digsby_email/info.yaml:1 src/plugins/digsby_email/info.yaml:26 +#: src/plugins/digsby_email/info.yaml:116 +#: src/plugins/myspace/MyspaceProtocol.py:39 +msgid "Mail" +msgstr "|\\/|41|_" + +#: src/plugins/digsby_email/info.yaml:10 +msgid "POP Account" +msgstr "P0P 4((0|_||\\|7" + +#: src/plugins/digsby_email/info.yaml:68 +msgid "IMAP Account" +msgstr "1|\\/|4P 4((0|_||\\|72" + +# MARK: Formating, Reproduction +#: src/plugins/digsby_inviter/inviter.py:28 +#: src/plugins/digsby_inviter/inviter.py:39 +msgid "" +"Please support Digsby by helping us spread the word!\n" +"Would you like to invite your address book contacts to Digsby?" +msgstr "" +"P|_3453 5|_|PP0|27 |)165|3'/ |3'/ #3|_P1|\\|' |_|2 5P|234|) 73# \\/\\/0|2|)!\n" +"\\/\\/0|_||_|) J00 \\/\\/|_||3 2 1|\\|\\/173 '/0 4|)|)|2355 |300|< (0|\\|74(72 2 |)165|3'/?" + +#: src/plugins/digsby_inviter/inviter.py:54 +msgid "Send Invites" +msgstr "53|\\||) 1|\\|\\/1732" + +#: src/plugins/digsby_inviter/inviter.py:55 +#: src/plugins/digsby_status/status_tag_urls.py:68 +#: src/plugins/facebook/fbgui.py:19 src/plugins/linkedin/ligui.py:17 +#: src/plugins/msim/myspacegui/prompts.py:11 src/plugins/myspace/msgui.py:19 +#: src/plugins/twitter/twgui.py:23 +msgid "No Thanks" +msgstr "|\\|0 7#4|\\||<2" + +#: src/plugins/digsby_service_editor/default_ui.py:62 +msgid "{account_name:s} - {service_name:s} Settings" +msgstr "{account_name:s} - {service_name:s} 53771|\\|62" + +#: src/plugins/digsby_service_editor/default_ui.py:150 +msgid "New User?" +msgstr "|\\|3\\/\\/ |_|53|2?" + +#: src/plugins/digsby_service_editor/default_ui.py:167 +msgid "Forgot Password?" +msgstr "|=0|2607 P455\\/\\/0|2|)?" + +# MARK: %s replacement +#: src/plugins/digsby_service_editor/default_ui.py:247 +#, python-format +msgid "&%s Server:" +msgstr "&%s 53|2\\/3|2:" + +#: src/plugins/digsby_service_editor/default_ui.py:248 +msgid "&This server requires SSL" +msgstr "&|)12 53|2\\/3|2 |23Q|_|1|232 55|_" + +#: src/plugins/digsby_service_editor/default_ui.py:266 +msgid "This server requires SSL" +msgstr "|)12 53|2\\/3|2 |23Q|_|1|232 55|_" + +#: src/plugins/digsby_service_editor/default_ui.py:326 +msgid "IM Server:" +msgstr "1|\\/| 53|2\\/3|2:" + +#: src/plugins/digsby_service_editor/default_ui.py:348 +msgid "Always connect over HTTP" +msgstr "4|_\\/\\/4'/2 (0|\\||\\|3(7 0\\/3|2 #77P" + +#: src/plugins/digsby_service_editor/default_ui.py:364 +msgid "Check for new mail every" +msgstr "(#3(|< 4 |\\|3\\/\\/ |\\/|41|_ 3\\/3|2'/" + +# MARK: Fragment +#: src/plugins/digsby_service_editor/default_ui.py:366 +msgid "minutes" +msgstr "|\\/|1|\\||_|732" + +#: src/plugins/digsby_service_editor/default_ui.py:597 +msgid "Custom ({mailclient})" +msgstr "(|_|570|\\/| ({mailclient})" + +#: src/plugins/digsby_status/status_tag_urls.py:56 +#: src/plugins/facebook/fbgui.py:10 src/plugins/linkedin/ligui.py:8 +#: src/plugins/myspace/msgui.py:10 +#: src/plugins/researchdriver/researchtoast.py:36 +#: src/plugins/twitter/twgui.py:14 +msgid "Learn More" +msgstr "|_34|2|\\| |\\/|0|23" + +#: src/plugins/digsby_status/status_tag_urls.py:67 +msgid "Yes, Spread the Word!" +msgstr "'/32, 5P|234|) 73# \\/\\/0|2|)!" + +#: src/plugins/digsby_status/status_tag_urls.py:106 +msgid "Spread the Word!" +msgstr "5P|234|) 73# \\/\\/0|2|)!" + +#: src/plugins/digsby_status/status_tag_urls.py:109 +msgid "We've made it easier to spread the word about Digsby by adding a link to your IM status. You can disable this option in Preferences > Status." +msgstr "\\/\\/3'\\/3 |\\/|4|)3 17 34513|2 2 5P|234|) 73# \\/\\/0|2|) 4|30|_|7 |)165|3'/ |3'/ 4|)|)1|\\|' @ |_1|\\||< 2 '/0 1|\\/| 5747|_|2. J00 (4|\\| |)154|3|_3 |)12 0P710|\\| 1|\\| P|23|=3|23|\\|(32 > 5747|_|2." + +#: src/plugins/digsby_updater/UpdateProgress.py:55 +msgid "Digsby Update" +msgstr "|)165|3'/ |_|P|)473" + +#: src/plugins/digsby_updater/UpdateProgress.py:63 +msgid "{state}: {completed} of {size} -- {time} remain" +msgstr "{state}: {completed} 0|= {size} -- {time} |23|\\/|41|\\|" + +#: src/plugins/digsby_updater/UpdateProgress.py:145 +msgid "Slower" +msgstr "5|_0\\/\\/3|2" + +#: src/plugins/digsby_updater/UpdateProgress.py:146 +msgid "Faster!" +msgstr "|=4573|2!" + +#: src/plugins/digsby_updater/machelpers.py:18 +msgid "Error while updating Digsby. Please restart and try again, or grab the latest version from digsby.com. Digsby will now shut down." +msgstr "3|2|20|2 \\/\\/#1|_3 |_|P|)471|\\|6 |)165|3'/. P|_3453 |23574|27 |\\| 7|2'/ 4641|\\|, 0|2 6|24|3 73# |_47357 \\/3|2510|\\| |=|20|\\/| |)165|3'/.(0|\\/|. |)165|3'/ \\/\\/1|_|_ |\\|0\\/\\/ 5#|_|7 |)0\\/\\/|\\|." + +#: src/plugins/digsby_updater/machelpers.py:25 +msgid "Updated successfully. Digsby now needs to restart." +msgstr "|_|P|)473|) 5|_|((355|=|_||_|_'/. |)165|3'/ |\\|0\\/\\/ |\\|33|)2 2 |23574|27." + +#: src/plugins/digsby_updater/machelpers.py:31 +msgid "Unable to authenticate. Please restart and try again." +msgstr "|_||\\|4|3|_3 2 4|_|7#3|\\|71(473. P|_3453 |23574|27 |\\| 7|2'/ 4641|\\|." + +#: src/plugins/digsby_updater/updater.py:202 +msgid "Check for {tag} updates" +msgstr "(#3(|< 4 {tag} |_|P|)4732" + +#: src/plugins/digsby_updater/updater.py:204 +msgid "Check for Updates" +msgstr "(#3(|< 4 |_|P|)4732" + +#: src/plugins/digsby_updater/updater.py:485 +#: src/plugins/digsby_updater/updater.py:497 +msgid "" +"A Digsby update is available to download.\n" +"Would you like to begin downloading it now?" +msgstr "@ |)165|3'/ |_|P|)473 |3 4\\/41|_4|3|_3 2 |)0\\/\\/|\\||_04|).\\|\\|\\/\\/0|_||_|) J00 \\/\\/|_||3 2 |3361|\\| |)0\\/\\/|\\||_04|)1|\\|6 17 |\\|0\\/\\/?" + +#: src/plugins/digsby_updater/updater.py:573 +msgid "Update Failed" +msgstr "|_|P|)473 |=41|_3|)" + +#: src/plugins/digsby_updater/updater.py:574 +msgid "Digsby was unable to complete the download. This update will be attempted again later." +msgstr " \\/\\/|_|2 |_||\\|4B|_3 2 (0|\\/|P|_373 73# |)0\\/\\/|\\||_04|). |)12 |_|P|)473 \\/\\/1|_|_ |3 4773|\\/|P73|) 4641|\\| |_8|2." + +#: src/plugins/digsby_updater/updater.py:576 +msgid "Manual Update" +msgstr "|\\/|4|\\||_|4|_ |_|P|)473" + +#: src/plugins/digsby_updater/updater.py:606 +msgid "Update Ready" +msgstr "|_|P|)473 |234|)'/" + +#: src/plugins/digsby_updater/updater.py:607 +msgid "A new version of Digsby is ready to install. Restart Digsby to apply the update." +msgstr "@ |\\|3\\/\\/ \\/3|2510|\\| 0|= |)165|3'/ |3 |234|)'/ 2 1|\\|574|_|_. |23574|27 |)165|3'/ 2 4PP|_'/ 73# |_|P|)473." + +#: src/plugins/digsby_updater/updater.py:609 +msgid "Restart Now" +msgstr "|23574|27 |\\|40" + +#: src/plugins/digsby_updater/updater.py:610 +msgid "Restart Later" +msgstr "|23574|27 |_8|2" + +#: src/plugins/facebook/actions.yaml:8 +msgid "Pro&file" +msgstr "P|20|=1|_3" + +#: src/plugins/facebook/actions.yaml:12 +msgid "&Photos" +msgstr "P#0702" + +#: src/plugins/facebook/actions.yaml:14 +msgid "&Messages" +msgstr "|\\/|3554632" + +#: src/plugins/facebook/fbacct.py:310 +#: src/plugins/facebook/res/comment_link.py.xml:19 +#: src/plugins/facebook/res/comment_link.py.xml:20 +#: src/plugins/linkedin/LinkedInAccount.py:235 +#: src/plugins/linkedin/res/comment_link.tenjin:12 +#: src/plugins/linkedin/res/comment_link.tenjin:13 +#: src/plugins/myspace/MyspaceProtocol.py:416 +#: src/plugins/myspace/res/comment_link.tenjin:7 +#: src/plugins/myspace/res/comment_link.tenjin:9 +msgid "Comment" +msgstr "(0|\\/||\\/|3|\\|7" + +#: src/plugins/facebook/fbacct.py:318 +#: src/plugins/facebook/res/like_link.py.xml:11 +#: src/plugins/linkedin/res/like_dislike_link.tenjin:20 +#: src/plugins/myspace/res/like_dislike_link.tenjin:17 +msgid "Like" +msgstr "\\/\\/|_||3" + +#: src/plugins/facebook/fbacct.py:318 +#: src/plugins/facebook/res/like_link.py.xml:16 +msgid "Unlike" +msgstr "|_||\\||_1|<3" + +#: src/plugins/facebook/fbacct.py:329 +#: src/plugins/myspace/MyspaceProtocol.py:424 +msgid "comment" +msgstr "(0|\\/||\\/|3|\\|7" + +#: src/plugins/facebook/fbacct.py:349 src/plugins/facebook/fbacct.py:353 +msgid "like" +msgstr "\\/\\/|_||3" + +#: src/plugins/facebook/fbacct.py:366 +msgid "Facebook Error" +msgstr "|=4(3|300|< 3|2|20|2" + +#: src/plugins/facebook/fbacct.py:367 +#: src/plugins/myspace/MyspaceProtocol.py:432 +msgid "Error posting {thing}" +msgstr "3|2|20|2 P0571|\\|6 {thing}" + +#: src/plugins/facebook/fbacct.py:368 +msgid "Digsby does not have permission to publish to your stream." +msgstr "|)165|3'/ |)032 |\\|07 #4\\/3 P3|2|\\/|15510|\\| 2 P|_||3|_15# 2 '/0 57|234|\\/|." + +#: src/plugins/facebook/fbacct.py:369 +#: src/plugins/myspace/MyspaceProtocol.py:434 +msgid "Grant Permissions" +msgstr "6|24|\\|7 P3|2|\\/|15510|\\|2" + +#: src/plugins/facebook/fbacct.py:389 +msgid "Facebook Alerts" +msgstr "|=4(3b00|< 4|_3|272" + +#: src/plugins/facebook/fbacct.py:396 +msgid "You have new messages." +msgstr "J00 #4\\/3 |\\|3\\/\\/ |\\/|3554632." + +#: src/plugins/facebook/fbacct.py:399 +msgid "You have new pokes." +msgstr "J00 #4\\/3 |\\|3\\/\\/ P0|<32." + +#: src/plugins/facebook/fbacct.py:402 +msgid "You have new shares." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 5#4|232." + +#: src/plugins/facebook/fbacct.py:405 +msgid "You have new friend requests." +msgstr "J00 #4\\/3 |\\|3\\/\\/ |=|213|\\||) |23Q|_|3572." + +#: src/plugins/facebook/fbacct.py:408 +msgid "You have new group invites." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 6|20|_|P 1|\\|\\/1732." + +#: src/plugins/facebook/fbacct.py:411 +msgid "You have new event invites." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/fbacct.py:416 +msgid "You have new notifications." +msgstr "J00 #4\\/3 |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/fbgui.py:18 src/plugins/linkedin/ligui.py:16 +#: src/plugins/myspace/msgui.py:18 src/plugins/twitter/twgui.py:22 +msgid "Post Achievements" +msgstr "P057 4(#13\\/3|\\/|3|\\|72" + +#: src/plugins/facebook/fbgui.py:24 +msgid "Post achievements to my Wall" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ \\/\\/4|_|_" + +#: src/plugins/facebook/fbgui.py:40 src/plugins/myspace/msgui.py:69 +msgid "Show Alerts:" +msgstr "5#0\\/\\/ 4|_3|272:" + +#: src/plugins/facebook/fbgui.py:43 +msgid "Messages" +msgstr "|\\/|3554632" + +#: src/plugins/facebook/fbgui.py:44 +msgid "Pokes" +msgstr "P0|<32" + +#: src/plugins/facebook/fbgui.py:45 +msgid "Shares" +msgstr "5#4|232" + +#: src/plugins/facebook/fbgui.py:46 src/plugins/myspace/MyspaceProtocol.py:33 +#: src/plugins/myspace/msgui.py:79 +msgid "Friend Requests" +msgstr "|=|213|\\||) |239|_|3572" + +#: src/plugins/facebook/fbgui.py:47 +msgid "Group Invitations" +msgstr "6|20|_|P 1|\\|\\/174710|\\|2" + +#: src/plugins/facebook/fbgui.py:48 src/plugins/myspace/MyspaceProtocol.py:32 +msgid "Event Invitations" +msgstr "3\\/3|\\|7 1|\\|\\/174710|\\|2" + +#: src/plugins/facebook/info.yaml:0 +msgid "Facebook" +msgstr "|=4(3|300|<" + +#: src/plugins/facebook/info.yaml:4 +msgid "News Feed" +msgstr "|\\|3\\/\\/5 |=33|)" + +#: src/plugins/facebook/info.yaml:26 src/plugins/myspace/info.yaml:47 +#: src/plugins/twitter/res/content.tenjin:25 +msgid "Show Alerts" +msgstr "5#0\\/\\/ 4|_3|272:" + +#: src/plugins/facebook/notifications.yaml:1 +msgid "Facebook Alert" +msgstr "|=4(3b00|< 4|_3|272" + +#: src/plugins/facebook/notifications.yaml:10 +#: src/plugins/facebook/notifications.yaml:11 +msgid "Facebook Notification" +msgstr "|\\|071|=1(4710|\\|2" + +#: src/plugins/facebook/notifications.yaml:21 +msgid "Facebook Newsfeed" +msgstr "|\\/|'/5P4(3 |\\|3\\/\\/5|=33|)" + +#: src/plugins/facebook/res/alerts.py.xml:2 +#: src/plugins/myspace/res/indicators.tenjin:2 +#: src/plugins/twitter/res/content.tenjin:8 +msgid "Alerts" +msgstr "5#0\\/\\/ 4|_3|272:" + +#: src/plugins/facebook/res/alerts.py.xml:4 +msgid "No Alerts" +msgstr "|\\|0 4|_3|272" + +#: src/plugins/facebook/res/alerts.py.xml:6 +#, python-format +msgid "%d new message" +msgid_plural "%d new messages" +msgstr[0] "%d |\\|3\\/\\/ |\\/|355463." +msgstr[1] "%d |\\|3\\/\\/ |\\/|3554632." + +#: src/plugins/facebook/res/alerts.py.xml:7 +#, python-format +msgid "%d new friend request" +msgid_plural "%d new friend requests" +msgstr[0] "%d |\\|3\\/\\/ |=|213|\\||) |23Q|_|357" +msgstr[1] "%d |\\|3\\/\\/ |=|213|\\||) |23Q|_|3572" + +#: src/plugins/facebook/res/alerts.py.xml:8 +#, python-format +msgid "%d new poke" +msgid_plural "%d new pokes" +msgstr[0] "%d |\\|3\\/\\/ P0|<3" +msgstr[1] "%d |\\|3\\/\\/ P0|<32" + +#: src/plugins/facebook/res/alerts.py.xml:9 +#, python-format +msgid "%d new share" +msgid_plural "%d new shares" +msgstr[0] "%d |\\|3\\/\\/ 5#4|23." +msgstr[1] "%d |\\|3\\/\\/ 5#4|232." + +#: src/plugins/facebook/res/alerts.py.xml:10 +#, python-format +msgid "%d new group invite" +msgid_plural "%d new group invites" +msgstr[0] "%d |\\|3\\/\\/ 6|20|_|P 1|\\|\\/173." +msgstr[1] "%d |\\|3\\/\\/ 6|20|_|P 1|\\|\\/1732." + +#: src/plugins/facebook/res/alerts.py.xml:11 +#, python-format +msgid "%d new event invite" +msgid_plural "%d new event invites" +msgstr[0] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/173." +msgstr[1] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/res/alerts.py.xml:12 +#, python-format +msgid "%d new notification" +msgid_plural "%d new notifications" +msgstr[0] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/173." +msgstr[1] "%d |\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/1732." + +#: src/plugins/facebook/res/birthdays.py.xml:9 +msgid "Upcoming Birthdays" +msgstr "|_|P(0|\\/|1|\\|6 |31|27#|)4'/5" + +#: src/plugins/facebook/res/comment.py.xml:11 +#: src/plugins/facebook/res/comment.py.xml:13 +#: src/plugins/facebook/res/dislikes.py.xml:28 +#: src/plugins/facebook/res/dislikes.py.xml:30 +#: src/plugins/facebook/res/dislikes.py.xml:39 +#: src/plugins/facebook/res/dislikes.py.xml:41 +#: src/plugins/facebook/res/dislikes.py.xml:55 +#: src/plugins/facebook/res/dislikes.py.xml:57 +#: src/plugins/facebook/res/dislikes.py.xml:63 +#: src/plugins/facebook/res/dislikes.py.xml:65 +#: src/plugins/facebook/res/dislikes.py.xml:71 +#: src/plugins/facebook/res/dislikes.py.xml:73 +#: src/plugins/facebook/res/likes.py.xml:42 +#: src/plugins/facebook/res/likes.py.xml:46 +#: src/plugins/facebook/res/likes.py.xml:55 +#: src/plugins/facebook/res/likes.py.xml:62 +#: src/plugins/facebook/res/likes.py.xml:80 +#: src/plugins/facebook/res/likes.py.xml:85 +msgid "Facebook User" +msgstr "|=4(3b00|< |_|53|2" + +#: src/plugins/facebook/res/comment_link.py.xml:2 +#: src/plugins/facebook/res/comment_post_link.py.xml:2 +#: src/plugins/facebook/res/comments.py.xml:2 +#: src/plugins/facebook/res/dislike_link.py.xml:2 +#: src/plugins/facebook/res/dislikes.py.xml:2 +msgid "Dislike! (via http://digsby.com/fb)" +msgstr "|)15|_1|<3! (via http://digsby.com)" + +#: src/plugins/facebook/res/comment_post_link.py.xml:15 +#, python-format +msgid "See all %d comments" +msgstr "533 4|_|_ %d (0|\\/||\\/|3|\\|72" + +#: src/plugins/facebook/res/comment_post_link.py.xml:17 +msgid "See all comments" +msgstr "533 4|_|_ (0|\\/||\\/|3|\\|72" + +#: src/plugins/facebook/res/content.tenjin:35 +msgid "Back to News Feed" +msgstr "|34(|< 2 |\\|3\\/\\/5|=33|):" + +# MARK: Shuold be translated? +#: src/plugins/facebook/res/dislike_link.py.xml:18 +msgid "Dislike" +msgstr "|)15|_1|<3" + +#: src/plugins/facebook/res/dislike_link.py.xml:23 +msgid "Undislike" +msgstr "|_||\\||)15|_1|<3" + +#: src/plugins/facebook/res/dislike_link.py.xml:32 +msgid "Dislikes" +msgstr "|)154B|_32" + +#: src/plugins/facebook/res/like_link.py.xml:25 +#: src/plugins/linkedin/res/like_dislike_link.tenjin:29 +#: src/plugins/myspace/res/like_dislike_link.tenjin:26 +msgid "Likes" +msgstr "|_1|<35" + +#: src/plugins/facebook/res/likes.py.xml:14 +msgid "You liked this" +msgstr "|_| |_1|<3|) |)15" + +#: src/plugins/facebook/res/likes.py.xml:16 +msgid "You and {startlink_other}one other{endlink_other} liked this" +msgid_plural "You and {startlink_other}{num_others} others{endlink_other} liked this" +msgstr[0] "|_| |\\| {startlink_other}1 07|-|3|2{endlink_other} |_1|<3|) |)15" +msgstr[1] "|_| |\\| {startlink_other}{num_others} 07|-|3|22{endlink_other} |_1|<3|) |)15" + +#: src/plugins/facebook/res/likes.py.xml:40 +msgid "{startlink_name}{name}{endlink_name} liked this" +msgstr "{startlink_name}{name}{endlink_name} |_1|<3|) |)15" + +#: src/plugins/facebook/res/likes.py.xml:51 +msgid "{startlink_name}{name}{endlink_name} and {startlink_other}one other{endlink_other} liked this" +msgid_plural "{startlink_name}{name}{endlink_name} and {startlink_other}{num_others} others{endlink_other} liked this" +msgstr[0] "{startlink_name}{name}{endlink_name} |\\| {startlink_other}1 07|-|3|2{endlink_other} liked this" +msgstr[1] "{startlink_name}{name}{endlink_name} |\\| {startlink_other} 07|-|3|22{endlink_other} liked this" + +#: src/plugins/facebook/res/likes.py.xml:76 +msgid "{startlink_other}one user{endlink_other} liked this" +msgid_plural "{startlink_other}{num_users} users{endlink_other} liked this" +msgstr[0] "{startlink_other}1 |_|53|2{endlink_other} \\/\\/|_||33|) |)1s" +msgstr[1] "{startlink_other}{num_users} |_|53|22{endlink_other} \\/\\/|_||33|) |)12" + +#: src/plugins/facebook/res/post.py.xml:63 +msgid "See Friendship" +msgstr "533 |=|213|\\||)2" + +#: src/plugins/facebook/res/status.py.xml:7 +#: src/plugins/linkedin/res/content.tenjin:3 +msgid "You have no status message" +msgstr "J00 #4\\/3 |\\|0 5747|_|5 |\\/|3554632." + +#: src/plugins/facebook/res/status.py.xml:9 +#: src/plugins/myspace/res/status.tenjin:18 +#: src/plugins/twitter/res/status.tenjin:14 +#: src/plugins/twitter/res/status.tenjin:21 +msgid "update" +msgstr "|_|P|)4732" + +#: src/plugins/fbchat/info.yaml:5 +msgid "Facebook Chat" +msgstr "|=4(3b00|< (|-|47" + +#: src/plugins/fbchat/xmppfb.py:116 +msgid "Send Message" +msgstr "53|\\||) |\\/|355463" + +#: src/plugins/fbchat/xmppfb.py:117 +msgid "Send Poke" +msgstr "53|\\||) P0|<3" + +#: src/plugins/fbchat/xmppfb.py:118 +msgid "View Photos" +msgstr "\\/13\\/\\/ P#0702" + +#: src/plugins/fbchat/xmppfb.py:122 src/yahoo/yahoobuddy.py:133 +msgid "View Profile" +msgstr "\\/13\\/\\/ P|20|=1|_3" + +#: src/plugins/fbchat/xmppfb.py:123 +msgid "Write on Wall" +msgstr "\\/\\/|2173 0|\\| \\/\\/4|_|_" + +#: src/plugins/fbchat/xmppfb.py:132 src/yahoo/yahoobuddy.py:164 +#: src/yahoo/yahoobuddy.py:181 +msgid "Links:" +msgstr "|_1|\\||<2:" + +#: src/plugins/feed_trends/feed_trends.py:702 +msgid "Trending News" +msgstr "7|23|\\||)1|\\|' |\\|3\\/\\/2" + +#: src/plugins/feed_trends/feed_trends.py:973 +msgid "read more..." +msgstr "|234|) |\\/|0|23..." + +#: src/plugins/feed_trends/feed_trends.py:1010 +msgid "Feed Trends Debug" +msgstr "|=33|) 7|23|\\||)2 |)3|3|_|6" + +#: src/plugins/feed_trends/geo_trends.py:260 +msgid "Featured Content" +msgstr "|=347|_||23|) (0|\\|73|\\|7" + +# MARK: Fragment? +#: src/plugins/feed_trends/res/trend_details.tenjin:7 +msgid "get address" +msgstr "637 4|)|)|2355" + +# MARK: Fragment? +#: src/plugins/feed_trends/res/trend_details.tenjin:12 +msgid "phone number" +msgstr "P#0|\\|3 |\\||_||\\/||33|2" + +#: src/plugins/feed_trends/res/trend_details.tenjin:25 +msgid "reviews" +msgstr "|23\\/13\\/\\/2" + +#: src/plugins/linkedin/LinkedInAccount.py:66 +msgid "Checking now..." +msgstr "(#3(|<1|\\|' |\\|0\\/\\/..." + +#: src/plugins/linkedin/LinkedInAccount.py:82 +#: src/plugins/twitter/twitter.py:214 +msgid "Home" +msgstr "#0|\\/|3" + +#: src/plugins/linkedin/LinkedInAccount.py:83 +msgid "Inbox" +msgstr "1|\\||30><" + +#: src/plugins/linkedin/LinkedInAccount.py:85 +#: src/plugins/twitter/twitter.py:215 +msgid "Profile" +msgstr "P|20|=1|_3" + +#: src/plugins/linkedin/LinkedInAccount.py:176 +msgid "API request limit has been reached." +msgstr "4P1 |23Q|_|357 |_1|\\/|17 #42 |333|\\| |234(#3|)." + +#: src/plugins/linkedin/LinkedInAccount.py:378 +#: src/plugins/myspace/MyspaceAccount.py:334 +msgid "Please set your computer clock to the correct time / timezone." +msgstr "P|_3453 537 '/0 (0|\\/|P|_|73|2 (|_0(|< 2 73# (0|2|23(7 71|\\/|3 / 71|\\/|320|\\|3." + +#: src/plugins/linkedin/LinkedInObjects.py:138 +msgid "Like! (via http://digsby.com)" +msgstr "\\/\\/|_||3! (via http://digsby.com)" + +#: src/plugins/linkedin/LinkedInObjects.py:139 +msgid "Dislike! (via http://digsby.com)" +msgstr "|)15|_1|<3! (via http://digsby.com)" + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:294 +#, python-format +msgid "answered the question \"%s\"" +msgstr "4|\\|5\\/\\/3|23|) 73# Q|_|35710|\\| \"%s\"" + +# MARK: %s replacement, fragment, formatting +#: src/plugins/linkedin/LinkedInObjects.py:356 +#: src/plugins/linkedin/LinkedInObjects.py:435 +#, python-format +msgid "%s and " +msgstr "%s 4|\\||) " + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:360 +#, python-format +msgid "is now connected to %s" +msgstr "|3 |\\|0\\/\\/ (0|\\||\\|3(73|) 2 %s" + +# MARK: Fragment +#: src/plugins/linkedin/LinkedInObjects.py:371 +#: src/plugins/linkedin/res/ncon.tenjin:1 +msgid "is now a connection" +msgstr "|3 |\\|0\\/\\/ @ (0|\\||\\|3(710|\\|" + +# MARK: Fragment? +#: src/plugins/linkedin/LinkedInObjects.py:385 +msgid "joined LinkedIn" +msgstr "J01|\\|3|) |_1|\\||<3|)1|\\|" + +#: src/plugins/linkedin/LinkedInObjects.py:412 +msgid "posted a job: {position_title:s} at {company_name:s}" +msgstr "P0573|) 4 J0|3: {position_title:s} @ {company_name:s}" + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:439 +#, python-format +msgid "is now a member of %s" +msgstr "|3 |\\|0\\/\\/ @ |\\/|3|\\/||33|2 0|= %s" + +# MARK: Fragment +#: src/plugins/linkedin/LinkedInObjects.py:459 +#: src/plugins/linkedin/res/picu.tenjin:1 +msgid "has a new profile photo" +msgstr "#42 @ |\\|3\\/\\/ P|20|=1|_3 P#070" + +# MARK: Fragmnet, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:498 +#, python-format +msgid "has recommended %s" +msgstr "#42 |23(0|\\/||\\/|3|\\||)3|) %s" + +# MARK: Fragment, %s replacemnet +#: src/plugins/linkedin/LinkedInObjects.py:500 +#, python-format +msgid "was recommended by %s" +msgstr "\\/\\/|_|2 |23(0|\\/||\\/|3|\\||)3|) |3'/ %s" + +# MARK: Fragment +#: src/plugins/linkedin/LinkedInObjects.py:512 +msgid "has an updated profile" +msgstr "#42 |\\| |_|P|)473|) P|20|=1|_3" + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/LinkedInObjects.py:536 +#, python-format +msgid "asked a question: %s" +msgstr "45|<3|) @ Q|_|35710|\\|: %s" + +# MARK: Should be translated? +#: src/plugins/linkedin/LinkedInProtocol.py:121 +msgid "Throttle" +msgstr "7#|2077|_3" + +#: src/plugins/linkedin/actions.yaml:8 +msgid "C&ontacts" +msgstr "(0|\\|74(72" + +#: src/plugins/linkedin/info.yaml:0 +msgid "LinkedIn" +msgstr "|_1|\\||<3|)1|\\|" + +#: src/plugins/linkedin/info.yaml:46 +msgid "LinkedIn Newsfeed" +msgstr "|\\/|'/5P4(3 |\\|3\\/\\/5|=33|)" + +#: src/plugins/linkedin/ligui.py:28 +msgid "Post achievements to my LinkedIn Network" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ |_1|\\||<3|)1|\\| |\\|37\\/\\/0|2|<" + +# MARK: Fragment +#: src/plugins/linkedin/res/ccem.tenjin:1 +msgid "joined" +msgstr "J01|\\|3|)" + +# MARK: Fragment, %s replacement +#: src/plugins/linkedin/res/conn.tenjin:1 +msgid "is now connected to" +msgstr "|3 |\\|0\\/\\/ (0|\\||\\|3(73|) 2" + +#: src/plugins/linkedin/res/content.tenjin:11 +msgid "Network Updates" +msgstr "(#3(|< 4 |_|P|)4732" + +#: src/plugins/linkedin/res/content.tenjin:14 +msgid "No news today." +msgstr "|\\|0 |\\|3\\/\\/2 70|)4'/." + +# MARK: Fragment, Should exist? +#: src/plugins/linkedin/res/dislikes.tenjin:15 +#: src/plugins/linkedin/res/dislikes.tenjin:33 +#: src/plugins/myspace/res/dislikes.tenjin:15 +#: src/plugins/myspace/res/dislikes.tenjin:30 +#: src/plugins/myspace/res/likes.tenjin:15 +#: src/plugins/myspace/res/likes.tenjin:29 +msgid "and" +msgstr "|\\|" + +# MARK: Fragment +#: src/plugins/linkedin/res/dislikes.tenjin:16 +#: src/plugins/linkedin/res/dislikes.tenjin:34 +#: src/plugins/myspace/res/dislikes.tenjin:17 +#: src/plugins/myspace/res/dislikes.tenjin:31 +#: src/plugins/myspace/res/likes.tenjin:16 +#: src/plugins/myspace/res/likes.tenjin:30 +msgid "other" +msgid_plural "others" +msgstr[0] "07#3|2" +msgstr[1] "07#3|22" + +#: src/plugins/linkedin/res/dislikes.tenjin:18 +#: src/plugins/myspace/res/dislikes.tenjin:19 +#: src/plugins/myspace/res/dislikes.tenjin:33 +msgid "disliked this" +msgstr "|)15|_1|<3|) |)15" + +# MARK: Fragment +#: src/plugins/linkedin/res/jgrp.tenjin:1 +msgid "has joined" +msgstr "#42 J01|\\|3|)" + +# MARK: Fragment, I don't know what this is +#: src/plugins/linkedin/res/jobp.tenjin:1 +msgid "at" +msgstr "@ " + +# Mark: Fragment, Multiple %s replacements +#: src/plugins/linkedin/res/jobp.tenjin:1 +msgid "posted a job" +msgstr "P0573|) @ J0|3" + +#: src/plugins/linkedin/res/like_dislike_link.tenjin:18 +#: src/plugins/myspace/res/like_dislike_link.tenjin:15 +msgid "Liked" +msgstr "|_1|<3|)" + +#: src/plugins/msim/MSIMConversation.py:35 +msgid "{name} zapped you" +msgstr "{name} 24PP3|) J00" + +#: src/plugins/msim/MSIMConversation.py:36 +msgid "You zapped {name}" +msgstr "J00 P3\\/\\/3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:37 +msgid "Zap" +msgstr "P3\\/\\/" + +#: src/plugins/msim/MSIMConversation.py:41 +msgid "{name} whacked you" +msgstr "{name} \\/\\/#4(|<3|) J00" + +#: src/plugins/msim/MSIMConversation.py:42 +msgid "You whacked {name}" +msgstr "J00 \\/\\/#4(|<3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:43 +msgid "Whack" +msgstr "\\/\\/#4(|<" + +#: src/plugins/msim/MSIMConversation.py:47 +msgid "{name} torched you" +msgstr "{name} 70|2(|-|3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:48 +msgid "You torched {name}" +msgstr "J00 70|2(#3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:49 +msgid "Torch" +msgstr "70|2(#" + +#: src/plugins/msim/MSIMConversation.py:53 +msgid "{name} smooched you" +msgstr "{name} 5|\\/|00(|-|3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:54 +msgid "You smooched {name}" +msgstr "J00 5|\\/|00(#3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:55 +msgid "Smooch" +msgstr "5|\\/|00(#" + +#: src/plugins/msim/MSIMConversation.py:59 +msgid "{name} slapped you" +msgstr "{name} 5|_4PP3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:60 +msgid "You slapped {name}" +msgstr "J00 5|_4PP3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:61 +msgid "Slap" +msgstr "5|_4P" + +#: src/plugins/msim/MSIMConversation.py:65 +msgid "{name} goosed you" +msgstr "{name} 5|_|P|2153 |3|_|753(|<53|) |_|!" + +#: src/plugins/msim/MSIMConversation.py:66 +msgid "You goosed {name}" +msgstr "J00 60053|) {name}" + +#: src/plugins/msim/MSIMConversation.py:67 +msgid "Goose" +msgstr "60053" + +#: src/plugins/msim/MSIMConversation.py:71 +msgid "{name} high-fived you" +msgstr "{name} |-|16|-| |=1\\/3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:72 +msgid "You high-fived {name}" +msgstr "J00 #16#-|=1\\/3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:73 +msgid "High-Five" +msgstr "#16#-|=1\\/3" + +#: src/plugins/msim/MSIMConversation.py:77 +msgid "{name} punk'd you" +msgstr "{name} P|_||\\||<3|) |_|" + +#: src/plugins/msim/MSIMConversation.py:78 +msgid "You punk'd {name}" +msgstr "J00 7|20|_|_3|) {name}" + +#: src/plugins/msim/MSIMConversation.py:79 +msgid "Punk" +msgstr "P|_||\\||<" + +#: src/plugins/msim/MSIMConversation.py:83 +msgid "{name} raspberry'd you" +msgstr "{name} |245P|33|2|2Y'|) |_|" + +#: src/plugins/msim/MSIMConversation.py:84 +msgid "You raspberry'd {name}" +msgstr "J00 |245P|33|2|2'/'|) {name}" + +#: src/plugins/msim/MSIMConversation.py:85 +msgid "Raspberry" +msgstr "|245P|33|2|2'/" + +#: src/plugins/msim/info.yaml:0 +msgid "MySpace IM" +msgstr "|\\/|'/5P4(3 1|\\/|" + +#: src/plugins/msim/info.yaml:4 +msgid "MySpaceIM" +msgstr "|\\/|'/5P4(31|\\/|" + +#: src/plugins/msim/myspacegui/editstatus.py:6 +msgid "MySpace Status" +msgstr "|\\/|'/5P4(3 5747|_|5" + +#: src/plugins/msim/myspacegui/privacy.py:20 +msgid "Everyone" +msgstr "3\\/3|2Y0|\\|3" + +#: src/plugins/msim/myspacegui/privacy.py:21 +msgid "Only people on my contact list" +msgstr "0|\\||_'/ P30P|_3 0|\\| |\\/|'/ (0|\\|74(7 |_157" + +#: src/plugins/msim/myspacegui/privacy.py:22 +msgid "No one" +msgstr "|\\|0" + +#: src/plugins/msim/myspacegui/privacy.py:63 +msgid "Only people on my contact list can see my status" +msgstr "0|\\||_'/ P30P|_3 0|\\| |\\/|'/ (0|\\|74(7 |_157 (4|\\| 533 |\\/|'/ 5747|_|5" + +#: src/plugins/msim/myspacegui/privacy.py:67 +msgid "Only people on my contact list can send me messages" +msgstr "0|\\||_'/ P30P|_3 0|\\| |\\/|'/ (0|\\|74(7 |_157 (4|\\| 53|\\||) |\\/|3 |\\/|3554632" + +#: src/plugins/msim/myspacegui/privacy.py:74 +msgid "When I'm offline, receive and store messages from:" +msgstr "\\/\\/#3|\\| 1'|\\/| 0|=|=|_1|\\|3, |23c31\\/3 |\\| 570|23 |\\/|3554632 |=|20|\\/|:" + +#: src/plugins/msim/myspacegui/privacy.py:80 +msgid "Offline Messages" +msgstr "0|=|=|_1|\\|3 |\\/|3554632" + +#: src/plugins/msim/myspacegui/prompts.py:8 +msgid "Would you like to automatically add your MySpace friends to your MySpace IM contact list?" +msgstr "\\/\\/0|_||_|) J00 \\/\\/|_||3 2 4|_|70|\\/|471(4|_|_'/ 4|)|) '/0 |\\/|'/5P4(3 |=|213|\\||)2 2 '/0 |\\/|'/5P4(3 1|\\/| (0|\\|74(7 |_157?" + +#: src/plugins/msim/myspacegui/prompts.py:9 +msgid "Add Top Friends" +msgstr "4|)|) 70P |=|213|\\||)2" + +#: src/plugins/msim/myspacegui/prompts.py:10 +msgid "Add All Friends" +msgstr "4|)|) 4|_|_ |=|213|\\||)2" + +#: src/plugins/msim/myspacegui/prompts.py:13 +msgid "Add Friends" +msgstr "4|)|) |=|213|\\||)2" + +#: src/plugins/msim/res/content.tenjin:20 +msgid "IM Name" +msgstr "1|\\/| |\\|4|\\/|3" + +#: src/plugins/msim/res/content.tenjin:21 +msgid "Display Name" +msgstr "|)15P|_4'/ |\\|4|\\/|3:" + +#: src/plugins/msim/res/content.tenjin:23 +msgid "Real Name" +msgstr "|234|_ |\\|4|\\/|3" + +#: src/plugins/msim/res/content.tenjin:24 +msgid "Age" +msgstr "463" + +#: src/plugins/msim/res/content.tenjin:27 +msgid "IM Headline" +msgstr "1|\\/| #34|)|_1|\\|3" + +# MARK: Fragment +#: src/plugins/myspace/MyspaceAccount.py:557 +msgid "added" +msgstr "4|)|)3|)" + +# MARK: %s replacement +#: src/plugins/myspace/MyspaceAccount.py:558 +#, python-format +msgid "%s account" +msgstr "%s 4((0|_||\\|7" + +# MARK: Fragment +#: src/plugins/myspace/MyspaceAccount.py:559 +msgid "to" +msgstr "2" + +#: src/plugins/myspace/MyspaceProtocol.py:28 src/plugins/myspace/msgui.py:83 +msgid "Birthdays" +msgstr "|31|27#|)4'/5" + +#: src/plugins/myspace/MyspaceProtocol.py:29 src/plugins/myspace/msgui.py:73 +msgid "Blog Comments" +msgstr "|3|_06 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:30 src/plugins/myspace/msgui.py:74 +msgid "Blog Subscriptions" +msgstr "|3|_06 5|_||35(|21P710|\\|2" + +#: src/plugins/myspace/MyspaceProtocol.py:31 +msgid "Comments" +msgstr "(0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:34 src/plugins/myspace/msgui.py:81 +msgid "Group Notifications" +msgstr "6|20|_|P |\\|071|=1(4710|\\|2" + +#: src/plugins/myspace/MyspaceProtocol.py:35 src/plugins/myspace/msgui.py:78 +msgid "Photo Tag Approvals" +msgstr "P#070 746 4PP|20\\/4|_2" + +#: src/plugins/myspace/MyspaceProtocol.py:36 src/plugins/myspace/msgui.py:75 +msgid "Picture Comments" +msgstr "P1(7|_||23 C0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:37 src/plugins/myspace/msgui.py:82 +msgid "Recently Added Friends" +msgstr "|23(3|\\|7|_'/ 4|)|)3|) |=|213|\\||)2" + +#: src/plugins/myspace/MyspaceProtocol.py:38 src/plugins/myspace/msgui.py:80 +msgid "Video Comments" +msgstr "\\/1|)30 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/MyspaceProtocol.py:73 +msgid "Mail ({mailcount})" +msgstr "|\\/|41|_ ({mailcount})" + +#: src/plugins/myspace/MyspaceProtocol.py:394 +#: src/plugins/myspace/notifications.yaml:9 +msgid "MySpace Newsfeed" +msgstr "|\\/|'/5P4(3 |\\|3\\/\\/5|=33|)" + +#: src/plugins/myspace/MyspaceProtocol.py:431 +msgid "MySpace Error" +msgstr "|\\/|'/5P4(3 3|2|20|2" + +#: src/plugins/myspace/MyspaceProtocol.py:433 +msgid "Digsby does not have permission to perform this action." +msgstr "|)165|3'/ |)032 |\\|07 #4\\/3 P3|2|\\/|15510|\\| 2 P3|2|=0|2|\\/| |)12 4(710|\\|." + +#: src/plugins/myspace/MyspaceProtocol.py:565 +msgid "MySpace Alerts" +msgstr "|\\/|'/5P4(3 4|_3|272" + +#: src/plugins/myspace/MyspaceProtocol.py:566 +#, python-format +msgid "You have new %s" +msgstr "J00 #4\\/3 |\\|3\\/\\/ %s" + +#: src/plugins/myspace/MyspaceProtocol.py:683 +msgid "Activity stream item not found. It may have been removed by its creator." +msgstr "4(71\\/17'/ 57|234|\\/| 173|\\/| |\\|07 |=0|_||\\||). 17 |\\/|4Y #4\\/3 |333|\\| |23|\\/|0\\/3|) |3'/ 172 (|23470|2." + +#: src/plugins/myspace/actions.yaml:13 +msgid "&Post Bulletin" +msgstr "&P057 |3|_||_|_371|\\|" + +#: src/plugins/myspace/info.yaml:63 +msgid "Show Newsfeed" +msgstr "5#0\\/\\/ |\\|3\\/\\/5|=33|)" + +#: src/plugins/myspace/msgui.py:24 +msgid "Post achievements to my Activity Stream" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ 4(71\\/17'/ 57|234|\\/|" + +#: src/plugins/myspace/msgui.py:38 +msgid "Show Newsfeed:" +msgstr "5#0\\/\\/ |\\|3\\/\\/5|=33|):" + +#: src/plugins/myspace/msgui.py:42 +msgid "Status Updates" +msgstr "5747|_|2 |_|P|)4732" + +#: src/plugins/myspace/msgui.py:43 +msgid "Friend Updates" +msgstr "|=|213|\\||) |_|P|)4732" + +#: src/plugins/myspace/msgui.py:44 +msgid "Blog/Forum Posts" +msgstr "|3|_06/|=0|2|_||\\/| P0572" + +#: src/plugins/myspace/msgui.py:45 +msgid "Group Updates" +msgstr "6|20|_|P |_|P|)4732" + +#: src/plugins/myspace/msgui.py:46 +msgid "Photos" +msgstr "P#0702" + +#: src/plugins/myspace/msgui.py:47 +msgid "Music" +msgstr "|\\/||_|51(" + +#: src/plugins/myspace/msgui.py:48 +msgid "Videos" +msgstr "\\/1|)302" + +#: src/plugins/myspace/msgui.py:50 +msgid "Applications" +msgstr "4PP|_1(4710|\\|2" + +#: src/plugins/myspace/msgui.py:76 +msgid "Event Invites" +msgstr "3\\/3|\\|7 1|\\|\\/1732" + +#: src/plugins/myspace/msgui.py:77 +msgid "Profile Comments" +msgstr "P|20|=1|_3 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/notifications.yaml:1 +msgid "MySpace Alert" +msgstr "|\\/|'/5P4(3 4|_3|272" + +#: src/plugins/myspace/objects.py:301 +msgid "Private user" +msgstr "P|21\\/473 |_|53|2" + +#: src/plugins/myspace/objects.py:354 +msgid "Like! (via http://lnk.ms/C5dls)" +msgstr "\\/\\/|_||3! (via http://lnk.ms/C5dls)" + +#: src/plugins/myspace/objects.py:355 +msgid "Dislike! (via http://lnk.ms/C5dls)" +msgstr "|)15|_1|<3! (via http://lnk.ms/C5dls)" + +#: src/plugins/myspace/res/content.tenjin:15 +msgid "Activity Stream" +msgstr "4(71\\/17'/ 57|234|\\/|" + +#: src/plugins/myspace/res/indicators.tenjin:6 +msgid "New Mail" +msgstr "|\\|3\\/\\/ |\\/|41|_" + +#: src/plugins/myspace/res/indicators.tenjin:12 +msgid "New Birthdays" +msgstr "|\\|3\\/\\/ |31|27#|)4'/5" + +#: src/plugins/myspace/res/indicators.tenjin:19 +msgid "New Blog Comments" +msgstr "|\\|3\\/\\/ |3|_06 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/res/indicators.tenjin:26 +msgid "New Blog Subscriptions" +msgstr "|\\|3\\/\\/ |3|_06 5|_||35(|21P710|\\|2" + +#: src/plugins/myspace/res/indicators.tenjin:33 +msgid "New Comments" +msgstr "|\\|3\\/\\/ (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/res/indicators.tenjin:40 +msgid "New Event Invitations" +msgstr "|\\|3\\/\\/ 3\\/3|\\|7 1|\\|\\/174710|\\|2" + +#: src/plugins/myspace/res/indicators.tenjin:47 +msgid "New Friend Requests" +msgstr "|\\|3\\/\\/ |=|213|\\||) |239|_|3572" + +#: src/plugins/myspace/res/indicators.tenjin:54 +msgid "New Group Notifications" +msgstr "|\\|3\\/\\/ 6|20|_|P |\\|071|=1(4710|\\|2" + +#: src/plugins/myspace/res/indicators.tenjin:61 +msgid "New Photo Tag Approvals" +msgstr "|\\|3\\/\\/ P#070 746 4PP|20\\/4|_2" + +#: src/plugins/myspace/res/indicators.tenjin:68 +msgid "New Picture Comments" +msgstr "|\\|3\\/\\/ P1(7|_||23 (0|\\/||\\/|3|\\|72" + +#: src/plugins/myspace/res/indicators.tenjin:75 +msgid "New Recently Added Friends" +msgstr "|\\|3\\/\\/ |23(3|\\|7|_'/ 4|)|)3|) |=|213|\\||)2" + +#: src/plugins/myspace/res/indicators.tenjin:82 +msgid "New Video Comments" +msgstr "|\\|3\\/\\/ \\/1|)30 (0|\\/||\\/|3|\\|72" + +# MARK: Fragment +#: src/plugins/myspace/res/likes.tenjin:18 +#: src/plugins/myspace/res/likes.tenjin:32 +msgid "liked this" +msgstr "|_1|<3|) |)15" + +#: src/plugins/nowplaying/nowplaying.py:29 +msgid "Listening to music" +msgstr "|_1573|\\|1|\\|' 2 |\\/||_|51(" + +#: src/plugins/nowplaying/nowplaying.py:182 +msgid "Listening To..." +msgstr "|_1573|\\|1|\\|' 2 |\\/||_|51(" + +#: src/plugins/provider_aol/aol_sp.py:79 +msgid "Password should be 8 characters or less." +msgstr "P455\\/\\/0|2|) 5#0|_||_|) |3 8 (#4|24(73|22 0|2 |_355." + +#: src/plugins/provider_aol/info.yaml:5 +msgid "AOL/AIM Account" +msgstr "40|_/41|\\/| 4((0|_||\\|72" + +#: src/plugins/provider_aol/info.yaml:13 src/plugins/provider_aol/info.yaml:56 +#: src/plugins/provider_aol/info.yaml:73 +msgid "Screen Name" +msgstr "5(|233|\\| |\\|4|\\/|3" + +#: src/plugins/provider_aol/info.yaml:63 +msgid "AOL Mail" +msgstr "40|_ |\\/|41|_" + +#: src/plugins/provider_aol/info.yaml:72 +msgid "AIM" +msgstr "41|\\/|" + +#: src/plugins/provider_aol/info.yaml:99 +msgid "ICQ Account" +msgstr "1(Q 4((0|_||\\|7" + +#: src/plugins/provider_aol/info.yaml:109 +msgid "UIN or Email" +msgstr "|_|1|\\| 0|2 3|\\/|41|_" + +#: src/plugins/provider_aol/info.yaml:117 +#: src/plugins/provider_jabber/info.yaml:25 +msgid "Chat" +msgstr "(#47" + +#: src/plugins/provider_aol/info.yaml:141 +msgid "Not Available" +msgstr "|\\|07 4\\/41|_4|3|_3" + +#: src/plugins/provider_aol/info.yaml:143 +msgid "ICQ Number" +msgstr "1(Q |\\||_||\\/||33|2" + +#: src/plugins/provider_facebook/info.yaml:0 +#: src/plugins/provider_facebook/info.yaml:10 +msgid "Facebook Account" +msgstr "|=4(3|300|< 4((0|_||\\|7" + +#: src/plugins/provider_jabber/info.yaml:5 +msgid "Jabber Account" +msgstr "J4|3|33|2 4((0|_||\\|72" + +#: src/plugins/provider_jabber/info.yaml:15 +#: src/plugins/provider_jabber/info.yaml:28 +msgid "Jabber ID" +msgstr "J4|3|33|2 1||)" + +#: src/plugins/provider_jabber/jabber_gui.py:33 +msgid "Use TLS if Possible" +msgstr "|_|53 7|_5 1|= P0551|3|_3" + +#: src/plugins/provider_jabber/jabber_gui.py:34 +msgid "Require TLS" +msgstr "|23Q|_|1|23 7|_5" + +#: src/plugins/provider_jabber/jabber_gui.py:35 +msgid "Force SSL" +msgstr "|=0|2(3 55|_" + +# MARK: Should be translated? +#: src/plugins/provider_jabber/jabber_gui.py:36 +msgid "No Encryption" +msgstr "|\\|0 3|\\|(|21P710|\\|" + +#: src/plugins/provider_jabber/jabber_gui.py:50 +msgid "Ignore SSL Warnings" +msgstr "16|\\|0|23 55|_ \\/\\/4|2|\\|1|\\|62" + +#: src/plugins/provider_jabber/jabber_gui.py:54 +msgid "Allow Plaintext Login" +msgstr "4|_|_0\\/\\/ P|_41|\\|73><7 |_061|\\|" + +#: src/plugins/provider_linkedin/info.yaml:0 +msgid "LinkedIn Account" +msgstr "|_1|\\||<3|)1|\\| 4((0|_||\\|7" + +#: src/plugins/provider_myspace/info.yaml:0 +msgid "MySpace Account" +msgstr "|\\/|'/5P4(3 4((0|_||\\|72..." + +#: src/plugins/provider_twitter/info.yaml:0 +msgid "Twitter Account" +msgstr "7\\/\\/1773|2 4((0|_||\\|7" + +#: src/plugins/provider_twitter/info.yaml:10 src/plugins/twitter/info.yaml:10 +msgid "Twitter ID" +msgstr "7\\/\\/1773|2 1 |)" + +#: src/plugins/provider_windows_live/info.yaml:1 +msgid "Windows Live" +msgstr "\\/\\/1|\\||)0\\/\\/2 |_1\\/3" + +#: src/plugins/provider_windows_live/info.yaml:10 +msgid "Windows Live Account" +msgstr "\\/\\/1|\\||)0\\/\\/2 |_1\\/3 4((0|_||\\|7" + +#: src/plugins/provider_windows_live/info.yaml:33 +msgid "Busy" +msgstr "|3|_|5'/" + +#: src/plugins/provider_windows_live/info.yaml:54 +msgid "Live Messenger" +msgstr "|_1\\/3 |\\/|355463|2" + +#: src/plugins/provider_windows_live/info.yaml:77 +msgid "Hotmail" +msgstr "|-|07|\\/|41|_" + +#: src/plugins/provider_windows_live/wl_sp.py:17 +msgid "Password should be 16 characters or less." +msgstr "P455\\/\\/0|2|) 5#0|_||_|) |3 16 (#4|24(73|22 0|2 |_355." + +#: src/plugins/provider_windows_live/wl_sp.py:22 +msgid "You can't have your password as your display name." +msgstr "J00 (4|\\|'7 #4\\/3 '/0 P455\\/\\/0|2|) 42 '/0 |)15P|_4'/ |\\|4|\\/|3." + +#: src/plugins/provider_yahoo/info.yaml:0 +msgid "Yahoo Account" +msgstr "'/4#00 4((0|_||\\|7" + +#: src/plugins/researchdriver/researchtoast.py:30 +msgid "Help Digsby Stay Free" +msgstr "#3|_P |)165|3'/ 574'/ |=|233" + +#: src/plugins/researchdriver/researchtoast.py:32 +msgid "You are helping Digsby stay free by allowing Digsby to use your PC's idle time." +msgstr "J00 12 #3|_P1|\\|' |)165|3'/ 574'/ |=|233 |3'/ 4|_|_0\\/\\/1|\\|' |)165b'/ 2 |_|53 '/0 P('2 1|)|_3 71|\\/|3." + +#: src/plugins/twitter/info.yaml:0 +msgid "Twitter" +msgstr "7\\/\\/1773|2" + +#: src/plugins/twitter/info.yaml:3 +msgid "Timeline" +msgstr "71|\\/||_1|\\|3" + +#: src/plugins/twitter/notifications.yaml:1 +msgid "Twitter Update" +msgstr "7\\/\\/1773|2 |_|P|)473" + +#: src/plugins/twitter/notifications.yaml:9 +msgid "Twitter Direct Message" +msgstr "7\\/\\/1773|2 |)1|23(7 |\\/|355463" + +#: src/plugins/twitter/res/alerts.tenjin:6 +msgid "Search: " +msgstr "534|2(#" + +# MARK: Partial +#: src/plugins/twitter/res/alerts.tenjin:8 +msgid "Group: " +msgstr "6|20|_|P:" + +#: src/plugins/twitter/res/alerts.tenjin:25 +msgid "No alerts" +msgstr "|\\|0 4|_3|272:" + +#: src/plugins/twitter/res/content.tenjin:34 +msgid "Trending Topics" +msgstr "7|23|\\||)1|\\|' 70P1(2:" + +#: src/plugins/twitter/res/status.tenjin:20 +msgid "What are you doing?" +msgstr "\\/\\/07 12 J00 |)01|\\|'?" + +#: src/plugins/twitter/res/tweet.tenjin:80 +msgid "Share" +msgstr "5#4|23" + +#: src/plugins/twitter/twgui.py:31 +msgid "Follow Digsby on Twitter" +msgstr "|=0|_|_0\\/\\/ |)165|3'/ 0|\\| 7\\/\\/1773|2" + +#: src/plugins/twitter/twgui.py:37 +msgid "Post achievements to my feed" +msgstr "P057 4(#13\\/3|\\/|3|\\|72 2 |\\/|'/ |=33|)" + +#: src/plugins/twitter/twitter.py:216 +msgid "Followers" +msgstr "|=0|_|_0\\/\\/3|22" + +#: src/plugins/twitter/twitter.py:217 +msgid "Following" +msgstr "|=0|_|_0\\/\\/1|\\|'" + +#: src/plugins/twitter/twitter.py:231 +msgid "Invite Friends" +msgstr "1|\\|\\/173 |=|213|\\||)2" + +#: src/plugins/twitter/twitter.py:606 src/plugins/twitter/twitter_gui.py:702 +msgid "Favorites" +msgstr "|=4\\/0|21732" + +#: src/plugins/twitter/twitter.py:607 src/plugins/twitter/twitter_gui.py:703 +msgid "History" +msgstr "#1570|2'/" + +#: src/plugins/twitter/twitter.py:851 +msgid "Reply" +msgstr "|23P|_'/" + +#: src/plugins/twitter/twitter.py:852 +msgid "Retweet" +msgstr "|237\\/\\/337" + +#: src/plugins/twitter/twitter.py:853 +msgid "Direct" +msgstr "|)1|23(7" + +#: src/plugins/twitter/twitter.py:1207 +msgid "Twitter Error" +msgstr "7\\/\\/1773|2 3|2|23|2" + +#: src/plugins/twitter/twitter.py:1208 +msgid "Send Tweet Failed" +msgstr "53|\\||) 7\\/\\/337 |=41|_3|)" + +# MARK: Used? Formatting +#: src/plugins/twitter/twitter_account_gui.py:7 +msgid "" +"Twitter allows for 150 requests per hour. Make sure to\n" +"leave room for manual updates and other actions." +msgstr "" +"7\\/\\/1773|2 4|_|_0\\/\\/2 4 150 |23Q|_|3572 P3|2 #0|_||2. |\\/|4|<3 5|_||23 2\n" +"|_34\\/3 |200|\\/| 4 |\\/|4|\\||_|4|_ |_|P|)4732 |\\| 07#3|2 4(710|\\|2." + +#: src/plugins/twitter/twitter_account_gui.py:13 +msgid "Friends:" +msgstr "|=|213|\\||)2:" + +#: src/plugins/twitter/twitter_account_gui.py:14 +msgid "Mentions:" +msgstr "|\\/|3|\\|710|\\|2:" + +#: src/plugins/twitter/twitter_account_gui.py:15 +msgid "Directs:" +msgstr "|)1|23(72:" + +#: src/plugins/twitter/twitter_account_gui.py:16 +msgid "Searches:" +msgstr "534|2(#35:" + +#: src/plugins/twitter/twitter_account_gui.py:34 +msgid "{mins} minute" +msgid_plural "{mins} minutes" +msgstr[0] "{mins} |\\/|1|\\||_|7 |_||\\|17" +msgstr[1] "{mins} |\\/|1|\\||_|7 |_||\\|175" + +#: src/plugins/twitter/twitter_account_gui.py:37 +msgid "Never" +msgstr "|\\|3\\/3|2" + +#: src/plugins/twitter/twitter_account_gui.py:93 +msgid "Update Frequency:" +msgstr "|_|P|)473 |=|23Q|_|3|\\|('/:" + +#: src/plugins/twitter/twitter_account_gui.py:109 +msgid "Auto-throttle when Twitter lowers the rate limit." +msgstr "4|_|70-7#|2077|_3 \\/\\/#3|\\| 7\\/\\/1773|2 |_0\\/\\/3|22 73# |2473 |_1|\\/|17." + +#: src/plugins/twitter/twitter_account_gui.py:112 +msgid "Server" +msgstr "53|2\\/3|2:" + +#: src/plugins/twitter/twitter_account_gui.py:181 +msgid "{total_updates} / hour" +msgstr "{total_updates} / |-|0|_||2" + +#: src/plugins/twitter/twitter_gui.py:123 +msgid "&Search" +msgstr "&534|2(#" + +#: src/plugins/twitter/twitter_gui.py:127 +msgid "Twitter Search" +msgstr "7\\/\\/1773|2 534|2(#" + +#: src/plugins/twitter/twitter_gui.py:187 +msgid "Search For:" +msgstr "534|2(# |=0|2:" + +#: src/plugins/twitter/twitter_gui.py:195 +msgid "Title:" +msgstr "717|_3:" + +#: src/plugins/twitter/twitter_gui.py:201 +msgid "Trending Topics:" +msgstr "7|23|\\||)1|\\|' 70P1(2:" + +#: src/plugins/twitter/twitter_gui.py:211 +msgid "Search &Options" +msgstr "534|2(# &OP710|\\|2" + +#: src/plugins/twitter/twitter_gui.py:212 +msgid "Merge search results into Timeline view" +msgstr "|\\/|3|263 534|2(# |235|_||_72 1|\\|70 71|\\/|3|_1|\\|3 \\/13\\/\\/" + +#: src/plugins/twitter/twitter_gui.py:214 +msgid "Popup notifications for new search results" +msgstr "P0P|_|P |\\|071|=1(4710|\\|2 4 |\\|3\\/\\/ 534|2(# |235|_||_72" + +#: src/plugins/twitter/twitter_gui.py:355 +msgid "Twitter Group" +msgstr "7\\/\\/1773|2 6|20|_|P" + +#: src/plugins/twitter/twitter_gui.py:408 +msgid "&Group Name" +msgstr "&6|20|_|p |\\|4|\\/|3" + +#: src/plugins/twitter/twitter_gui.py:413 +msgid "Group &Members" +msgstr "6|20|_|P &M3|\\/||33|22" + +#: src/plugins/twitter/twitter_gui.py:426 +msgid "Group &Options" +msgstr "6|20|_|P &0P710|\\|2" + +#: src/plugins/twitter/twitter_gui.py:427 +msgid "&Filter this group's tweets out of the Timeline view" +msgstr "&|=1|_73|2 |)12 6|20|_|P'2 7\\/\\/3372 0|_|7 0|= 73# 71|\\/|3|_1|\\|3 \\/13\\/\\/" + +#: src/plugins/twitter/twitter_gui.py:429 +msgid "Show &popup notifications for this group's tweets" +msgstr "5#0\\/\\/ &P0P|_|P |\\|071|=1(4710|\\|2 4 |)12 6|20|_|P'5 7\\/\\/3372" + +#: src/plugins/twitter/twitter_gui.py:606 +msgid "&Hide Toolbar" +msgstr "&H1|)3 700|_|34|2" + +#: src/plugins/twitter/twitter_gui.py:612 +msgid "Shorten &Links" +msgstr "5#0|273|\\| &L1|\\||<2" + +#: src/plugins/twitter/twitter_gui.py:613 +msgid "Image" +msgstr "1|\\/|463" + +#: src/plugins/twitter/twitter_gui.py:614 +msgid "Shrink" +msgstr "5#|21|\\||<" + +#: src/plugins/twitter/twitter_gui.py:710 +msgid "New Group..." +msgstr "|\\|3\\/\\/ 6|20|_|P" + +#: src/plugins/twitter/twitter_gui.py:711 +msgid "New Search..." +msgstr "|\\|3\\/\\/ 534|2(#..." + +#: src/plugins/twitter/twitter_gui.py:712 +msgid "Edit and Rearrange..." +msgstr "3|)17 |\\| R34|2|24|\\|63" + +#: src/plugins/twitter/twitter_gui.py:1006 +msgid "Shortening..." +msgstr "5#0|273|\\|1|\\|'" + +#: src/plugins/twitter/twitter_gui.py:1102 +msgid "Are you sure you want to upload the image in your clipboard?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 2 |_|P|_04|) 73# 1|\\/|463 1|\\| '/0 (|_1P|304|2|)?" + +#: src/plugins/twitter/twitter_gui.py:1103 +msgid "Image Upload" +msgstr "1|\\/|463 |_|P|_04|)" + +#: src/plugins/twitter/twitter_gui.py:1385 +msgid "&Rearrange" +msgstr "&R34|2|24|\\|63" + +#: src/plugins/twitter/twitter_gui.py:1387 +msgid "Edit and &Rearrange" +msgstr "3|)17 |\\| &R34|2|24|\\|63" + +#: src/plugins/twitter/twitter_gui.py:1390 +msgid "&Mark As Read" +msgstr "&M4|2|< 42 |234|)" + +#: src/plugins/twitter/twitter_gui.py:1393 +msgid "&Adds to Unread Count" +msgstr "&4|)|)2 2 |_||\\||234|) (0|_||\\|7" + +#: src/plugins/twitter/twitter_gui.py:1511 +msgid "Text Size" +msgstr "73><7 5123" + +#: src/plugins/twitter/twitter_gui.py:1601 +msgid "Shorten URLs\tCtrl+L" +msgstr "5#0|273|\\| |_||2|_\tCtrl+L" + +#: src/plugins/twitter/twitter_gui.py:1602 +msgid "Share Picture\tCtrl+P" +msgstr "5#4|23 P1(7|_||23\tCtrl+P" + +#: src/plugins/twitter/twitter_gui.py:1603 +msgid "TweetShrink\tCtrl+S" +msgstr "7\\/\\/3375#|21|\\||<\tCtrl+S" + +#: src/plugins/twitter/twitter_gui.py:1605 +msgid "Set Global Status\tCtrl+G" +msgstr "537 6|_0|34|_ 5747|_|2\tCtrl+G" + +#: src/plugins/twitter/twitter_gui.py:1607 +msgid "Auto Shorten Pasted URLs" +msgstr "4|_|70 5#0|273|\\| P4573|) |_||2|_2" + +#: src/plugins/twitter/twitter_gui.py:1608 +msgid "Auto Upload Pasted Images" +msgstr "4|_|70 |_|P|_04|) P4573|) 1|\\/|4632" + +#: src/plugins/twitter/twitter_gui.py:1609 +msgid "Auto Scroll When At Bottom" +msgstr "4|_|70 5(|20|_|_ \\/\\/#3|\\| 47 |30770|\\/|" + +#: src/plugins/twitter/twitter_gui.py:1730 +msgid "Your tweet has spelling errors." +msgstr "7\\/\\/337 5P3|_|_1|\\|' 3|2|20|22" + +#: src/plugins/twitter/twitter_gui.py:1731 +msgid "Are you sure you'd like to send it?" +msgstr "12 J00 5|_||23 J00 \\/\\/4|\\|7 |)0 |)12?" + +#: src/plugins/twitter/twitter_gui.py:1735 +msgid "Tweet spelling errors" +msgstr "7\\/\\/337 5P3|_|_1|\\|' 3|2|20|22" + +#: src/plugins/twitter/twitter_gui.py:1738 +msgid "Send Anyways" +msgstr "53|\\||) 4|\\|'/\\/\\/4'/2" + +#: src/plugins/twitter/twitter_gui.py:1996 +msgid "Twitter Groups and Searches" +msgstr "7\\/\\/1773|2 6|20|_|P5 |\\| 534|2(#32" + +#: src/plugins/twitter/twitter_gui.py:2020 +msgid "Refresh Now" +msgstr "|23|=|235# |\\|0\\/\\/" + +#: src/plugins/twitter/twitter_gui.py:2021 +msgid "Mark All As Read" +msgstr "|\\/|4|2|< 4|_|_ 42 |234|)" + +#: src/plugins/twitter/twitter_gui.py:2046 +msgid "Set Status" +msgstr "537 5747|_|5" + +#: src/plugins/twitter/twitter_gui.py:2064 +#, python-format +msgid "Twitter (%s)" +msgstr "7\\/\\/1773|2 (%s)" + +#: src/plugins/twitter/twitter_gui.py:2290 +msgid "&Invite Followers" +msgstr "&1|\\|\\/173 |=0|_|_0\\/\\/3|22" + +#: src/plugins/twitter/twitter_gui.py:2291 +msgid "&No Thanks" +msgstr "&N0 7#4|\\||<2" + +#: src/plugins/twitter/twitter_gui.py:2304 +msgid "Invite Twitter Followers" +msgstr "1|\\|\\/173 7\\/\\/1773|2 |=0|_|_0\\/\\/3|22" + +# MARK: Formatting, Reproduction +#: src/plugins/twitter/twitter_gui.py:2305 +msgid "" +"Please support Digsby by helping us spread the word. Would\n" +"you like to send a direct message to your Twitter followers\n" +"inviting them to Digsby?" +msgstr "" +"P|_3453 5|_|PP0|27 |)165|3'/ |3'/ #3|_P1|\\|' |_|2 5P|234|) 73# \\/\\/0|2|)! \\/\\/0|_||_|)\n" +"J00 \\/\\/|_||3 2 53|\\||) @ |)1|23(7 |\\/|355463 2 '/0 7\\/\\/1773|2 |=0|_|_0\\/\\/3r2\n" +"1|\\|\\/171|\\|' 7#3|\\/| 2 |)165|3'/?" + +#: src/plugins/twitter/twitter_notifications.py:76 +#, python-format +msgid "Twitter - %(feed_label)s (%(username)s)" +msgstr "7\\/\\/1773|2 - %(feed_label)s (%(username)s)" + +#: src/plugins/twitter/twitter_notifications.py:78 +#, python-format +msgid "Twitter - Group: %(feed_label)s (%(username)s)" +msgstr "7\\/\\/1773|2 - 6|20|_|P: %(feed_label)s (%(username)s)" + +#: src/plugins/twitter/twitter_util.py:83 +msgid "about a minute ago" +msgstr "4|30|_|7 @ |\\/|1|\\||_|73 460" + +#: src/plugins/twitter/twitter_util.py:85 +msgid "about {minutes} minutes ago" +msgstr "4|30|_|7 {minutes} |\\/|1|\\||_|732 460" + +#: src/plugins/twitter/twitter_util.py:87 +msgid "about an hour ago" +msgstr "4|30|_|7 |\\| #0|_||2 460" + +#: src/plugins/twitter/twitter_util.py:89 +msgid "about {hours} hours ago" +msgstr "4|30|_|7 {hours} #0|_||22 460" + +#: src/plugins/twitter/twitter_util.py:91 +msgid "about a day ago" +msgstr "4|30|_|7 @ |)4'/ 460" + +#: src/plugins/twitter/twitter_util.py:93 +msgid "about {days} days ago" +msgstr "4|30|_|7 {days} |)4'/2 460" + +#: src/util/diagnostic.py:584 src/util/diagnostic.py:941 +msgid "Submit Bug Report" +msgstr "5|_||3|\\/|17 |3|_|6 |23p0|27" + +#: src/util/diagnostic.py:587 +msgid "Bug report submitted successfully." +msgstr "|3|_|6 |23P0|27 5|_||3|\\/|1773|) 5|_|((355|=|_||_|_'/." + +#: src/util/diagnostic.py:589 +msgid "Bug report submission failed." +msgstr "|3|_|6 |23P0|27 5|_||3|\\/|15510|\\| |=41|_!" + +#: src/util/diagnostic.py:916 +msgid "Please wait while we process the diagnostic information." +msgstr "P|_3453 \\/\\/417 \\/\\/#1|_3 \\/\\/3 P|20(352 73# |)146|\\|0571( 1|\\||=0|2|\\/|4710|\\|." + +#: src/util/diagnostic.py:917 +msgid "Thanks for your patience!" +msgstr "7#4|\\||<2 4 '/0 P4713|\\|(3!" + +#: src/util/diagnostic.py:919 +msgid "Processing Diagnostic Info" +msgstr "P|20(3551|\\|' |)146|\\|0571( 1|\\||=0" + +#: src/util/diagnostic.py:922 +msgid "There was a problem submitting your bug report." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 5|_||3|\\/|1771|\\|' '/0 (|245# |23P0|27." + +#: src/util/diagnostic.py:923 +msgid "If the problem persists, please email bugs@digsby.com" +msgstr "1|= 73# P|20|3|_3|\\/| P3|251572, P|_3453 3|\\/|41|_ bugs@digsby.com" + +#: src/util/diagnostic.py:930 +msgid "Bug report sent successfully." +msgstr "|3|_|6 |23P0|27 53|\\|7 5|_|((355|=|_||_|_Y." + +#: src/util/httplib2/httplib2.py:343 +#, python-format +msgid "Content purported to be compressed with %s but failed to decompress." +msgstr "(0|\\|73|\\|7 P|_||2P0|273|) 2 |3 (0|\\/|P|23553|) \\/\\/17# %s |3|_|7 |=41|_3|) 2 |)3(0|\\/|P|2355." + +#: src/util/httplib2/httplib2.py:502 +msgid "The challenge doesn't contain a server nonce, or this one is empty." +msgstr "73# (#4|_|_3|\\|63 |)03s|\\|'7 (0|\\|741|\\| @ 53|2\\/3|2 |\\|0|\\|(3, 0|2 |)12 0|\\|3 |3 3|\\/|P7'/." + +#: src/util/httplib2/httplib2.py:946 +msgid "Redirected but the response is missing a Location: header." +msgstr "|23|)1|23(73|) |3|_|7 73# |235P0|\\|53 |3 |\\/|1551|\\|' @ |_0(4710|\\|: #34|)3|2." + +#: src/util/httplib2/httplib2.py:973 +msgid "Redirected more times than rediection_limit allows." +msgstr "|23|)1|23(73|) |\\/|0|23 71|\\/|32 7#4|\\| |23|)13(710|\\|_|_1|\\/|17 4|_|_0\\/\\/2." + +#: src/util/perfmon.py:127 +msgid "" +"A log of the problem has been sent to digsby.com.\n" +"\n" +"Thanks for helping!" +msgstr "" +"@ |_06 0|= 73# P|20|3|_3|\\/| #42 |333|\\| 53|\\|7 2 |)165|3'/.(0|\\/|.\n" +"\n" +"7#4|\\||<2 4 #3|_P1|\\|'!" + +#: src/util/perfmon.py:128 +msgid "Diagnostic Log" +msgstr "|)146|\\|0571( |_06" + +#: src/util/perfmon.py:132 +msgid "There was an error when submitting the diagnostic log." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 \\/\\/#3|\\| 5|_||3|\\/|1771|\\|' 73# |)146|\\|0571( |_06." + +#: src/util/perfmon.py:146 +msgid "Digsby appears to be running slowly." +msgstr "|)165|3'/ |331|\\| 5|_0\\/\\/?" + +#: src/util/perfmon.py:147 +msgid "Do you want to capture diagnostic information and send it to digsby.com?" +msgstr "(4P7|_||2 |)146|\\|0571(2 |\\| 53|\\||) 2 |)165|3'/?" + +#: src/util/perfmon.py:151 +msgid "Digsby CPU Usage" +msgstr "|)165|3'/ (P|_| |_|5463" + +# MARK: Shuold be translated, not actually correct, may not exist in other languages +#: src/util/primitives/strings.py:587 +msgid "aeiou" +msgstr "aeiou" + +# MARK: Fragment, Should exist? +#: src/util/primitives/strings.py:588 +msgid "an" +msgstr "|\\|" + +# MARK: Fragment, I don't know what this is +#: src/util/primitives/strings.py:590 +msgid "a" +msgstr "@ " + +# MARK: Key replacement +#: src/util/primitives/strings.py:592 +#, python-format +msgid "%(article)s %(s)s" +msgstr "%(article)s %(s)s" + +#: src/yahoo/YahooProtocol.py:1163 +msgid "May I add you to my contact list?" +msgstr "|\\/|4'/ 1 4|)|) J00 2 |\\/|'/ (0|\\|74(7 |_157?" + +#: src/yahoo/YahooProtocol.py:1323 +msgid "There was an error modifying stealth settings for {name}." +msgstr "7#3|23 \\/\\/|_|2 |\\| 3|2|20|2 |\\/|0|)1|='/1|\\|' 5734|_7# 53771|\\|62 4 {name}" + +#: src/yahoo/login.py:35 +msgid "Bad Password" +msgstr "|34|) P455\\/\\/0|2|)" + +#: src/yahoo/login.py:36 +msgid "There is a security lock on your account. Log in to http://my.yahoo.com and try again." +msgstr "7#3|23 |3 @ 53(|_||217'/ |_0(|< 0|\\| '/0 4((0|_||\\|7. |_06 1|\\| 2 http://my.yahoo.com |\\| 7|2'/ 4641|\\|." + +#: src/yahoo/login.py:37 +msgid "Account Not Set Up" +msgstr "4((0|_||\\|7 |\\|07 537 |_|P" + +#: src/yahoo/login.py:38 +msgid "Bad Username" +msgstr "|34|) |_|53|2|\\|4|\\/|3" + +#: src/yahoo/login.py:39 +msgid "Rate Limit" +msgstr "|2473 |_1|\\/|17" + +#: src/yahoo/yahoobuddy.py:130 +msgid "No Updates" +msgstr "|\\|0 |_|P|)4732" + +#: src/yahoo/yahoobuddy.py:134 src/yahoo/yahoobuddy.py:137 +#: src/yahoo/yahoobuddy.py:151 src/yahoo/yahoobuddy.py:173 +msgid "Yahoo! 360:" +msgstr "'/4#00! 360:" + +#: src/yahoo/yahoobuddy.py:145 src/yahoo/yahoobuddy.py:167 +#: src/yahoo/yahoobuddy.py:174 src/yahoo/yahoobuddy.py:195 +#: src/yahoo/yahoobuddy.py:203 +msgid "Directory URL:" +msgstr "|)1|23(70|2'/ |_||2|_:" + +#: src/yahoo/yahoobuddy.py:152 +msgid "Real Name:" +msgstr "|234|_ |\\|4|\\/|3:" + +#: src/yahoo/yahoobuddy.py:153 +msgid "Nickname:" +msgstr "|\\|1(|<|\\|4|\\/|3:" + +#: src/yahoo/yahoobuddy.py:155 +msgid "Age:" +msgstr "463:" + +#: src/yahoo/yahoobuddy.py:156 +msgid "Sex:" +msgstr "53><:" + +#: src/yahoo/yahoobuddy.py:157 +msgid "Marital Status:" +msgstr "|\\/|4|2174|_ 5747|_|5:" + +#: src/yahoo/yahoobuddy.py:158 +msgid "Occupation:" +msgstr "0((|_|P4710|\\|:" + +#: src/yahoo/yahoobuddy.py:159 +msgid "Email:" +msgstr "3|\\/|41|_:" + +#: src/yahoo/yahoobuddy.py:160 +msgid "Home Page:" +msgstr "#0|\\/|3P463:" + +#: src/yahoo/yahoobuddy.py:161 src/yahoo/yahoobuddy.py:181 +msgid "Hobbies:" +msgstr "|-|0|3|3132:" + +#: src/yahoo/yahoobuddy.py:162 src/yahoo/yahoobuddy.py:181 +msgid "Latest News:" +msgstr "|_47357 |\\|4\\/\\/32" + +#: src/yahoo/yahoobuddy.py:163 src/yahoo/yahoobuddy.py:181 +msgid "Favorite Quote:" +msgstr "|=4\\/0|2173 Q|_|073:" + +#: src/yahoo/yahoobuddy.py:165 src/yahoo/yahoobuddy.py:195 +msgid "Member Since " +msgstr "|\\/|3|\\/||33|2 51|\\|(3" + +#: src/yahoo/yahoobuddy.py:166 src/yahoo/yahoobuddy.py:195 +msgid "Last Update: " +msgstr "|_457 |_|P|)473:" + +#, fuzzy +#~ msgid "'Away']]" +#~ msgstr "4\\/\\/4'/" + +#~ msgid "Newsfeed" +#~ msgstr "5#0\\/\\/ |\\|3\\/\\/5|=33|):" + +#, fuzzy +#~ msgid "\"Available\"], []]" +#~ msgstr "4\\/41|_4|3|_3" + +#, fuzzy +#~ msgid "'Available']," +#~ msgstr "4\\/41|_4|3|_3" + +# MARK: Fragment, Posible multilingual issue +#~ msgid "from" +#~ msgstr "|=|20|\\/|" + +# MARK: Should not be translated... +#~ msgid "test" +#~ msgstr "7357" + +# MARK: Fragment, %s replacement +#~ msgid "to %s" +#~ msgstr "2 %s" + +#~ msgid "&Submit" +#~ msgstr "&5|_||3|\\/|17" + +#~ msgid "Sent " +#~ msgstr "53|\\|7 " + +# MARK: %s replacement +#~ msgid "Show %s:" +#~ msgstr "5#0\\/\\/ %s:" + +# MARK: Reproduced multiple times... +#~ msgid "Please describe the bug in as much detail as possible. Include information such as what you were doing when the bug occured and exactly what goes wrong." +#~ msgstr "P|_3453 |)35(|21|33 73# |3|_|6 1|\\| 42 |\\/||_|(# ||)3741|_ 42 P0551|3|_3. 1|\\|(|_|_||)3 1|\\||=0|2|\\/|4710|\\| 5|_|(# 42 \\/\\/07 J00 \\/\\/|_|2 |)01|\\|' \\/\\/#3|\\| 73# |3|_|6 0((|_||2|23|) |\\| ><4(7|_'/ \\/\\/07 6032 \\/\\/|20|\\|6." + +# MARK: Fragment +#~ msgid "..." +#~ msgstr "..." + +# MARK: Formatting, duplicated +#~ msgid "" +#~ "This diagnostic log file does not contain personal data such as the content of sent/received IMs,\n" +#~ "the content of emails, and the content of social network newsfeeds except where it directly pertains to an error." +#~ msgstr "" +#~ "|)12 |)146|\\|0571( |_06 |=1|_3 |)032 |\\|07 (0|\\|741|\\| P3|250|\\|4|_ |)474 5|_|(# 42 73# (0|\\|73|\\|7 0|= 53|\\|7/|23(31\\/3|) 1|\\/|2,\n" +#~ "73# (0|\\|73|\\|7 0|= 3|\\/|41|_2, |\\| 73# (0|\\|73|\\|7 0|= 50(14|_ |\\|37\\/\\/0|2|< |\\|3\\/\\/5|=33|)2 ><(3P7 \\/\\/#3|23 17 |)1|23(7|_'/ P3|2741|\\|2 2 |\\| 3|2|20|2." + +# MARK: Fragment +#~ msgid "receive" +#~ msgstr "|23(31\\/3" + +#~ msgid "blog subscriptions" +#~ msgstr "|3|_06 5|_||35(|21P710|\\|2" + +# MARK: Fragment +#~ msgid "send" +#~ msgstr "53|\\||)" diff --git a/digsby/i18n/filter.py b/digsby/i18n/filter.py new file mode 100644 index 0000000..36d2a13 --- /dev/null +++ b/digsby/i18n/filter.py @@ -0,0 +1,52 @@ +#__LICENSE_GOES_HERE__ +from collections import defaultdict +def mkdict(lines): + d = defaultdict(list) + state = 0 + for line in lines: + if line.startswith("#: "): + d['file'].append(line) + elif line.startswith("#"): + d['comments'].append(line) + elif line.startswith("msgid"): + d["msgid"].append(line) + state = "msgid" + elif line.startswith("msgstr"): + d["msgstr"].append(line) + state = "msgstr" + elif state=="msgid": + d["msgid"].append(line) + else: + assert state=="msgstr" + d["msgstr"].append(line) + return dict(d) + +def build(lines): + r = [] + cur = [] + for line in lines: + if line == '\n': + r.append(mkdict(cur)) + cur = [] + else: + cur.append(line) + return r + +def run(filename): + with open(filename, 'r') as f: + r = build(f.readlines()) + r = [d for d in r if d.get('comments') and d.get('comments') != ["#, fuzzy\n"]] + return sorted(r, key=lambda d: d.get('file')) + +if __name__ == "__main__": + r = run('digsby_en_LT.po') + with open('commented.po', 'w') as f: + def p(l): + f.write(l) + for d in r: + for k in ('comments', 'file', 'msgid', 'msgstr'): + if d.get(k): + for line in d[k]: + p(line) + p('\n') + diff --git a/digsby/i18n/genargspec.py b/digsby/i18n/genargspec.py new file mode 100644 index 0000000..ff24b3e --- /dev/null +++ b/digsby/i18n/genargspec.py @@ -0,0 +1,38 @@ +#__LICENSE_GOES_HERE__ +# -*- coding: utf-8 -*- + +def get_argspec(encodings = ['', 'l', 'u'], + domains = ['', 'd'], + plurals = ['', 'n'], + contexts = ['', 'p'], + postfix='gettext', + spec=True): + out = [] + for context in contexts: + for domain in domains: + for plural in plurals: + for encoding in encodings: + positions = ['1based', 'domain', 'context', 'm1', 'm2'] + if not plural: + positions.remove('m2') + if not domain: + positions.remove('domain') + if not context: + positions.remove('context') + argspec = [] + if context: + argspec.append('%dc' % positions.index('context')) + argspec.append('%d' % positions.index('m1')) + if plural: + argspec.append('%d' % positions.index('m2')) + out.append(encoding + domain + plural + context + postfix + \ + (':' + ','.join(argspec) if spec else '')) + return out + +if __name__ == "__main__": + print "base:" + print get_argspec(encodings=[''], spec=False) + print "local:" + print get_argspec(encodings=['l'], spec=False) + print "unicode:" + print get_argspec(encodings=['u'], spec=False) diff --git a/digsby/i18n/generate.py b/digsby/i18n/generate.py new file mode 100644 index 0000000..35fa16e --- /dev/null +++ b/digsby/i18n/generate.py @@ -0,0 +1,68 @@ +#__LICENSE_GOES_HERE__ +# -*- coding: utf-8 -*- +from generate_monolithic import xgettext +from mkappfill import generate_fil_file +from path import path +import os +import shutil +from babel.messages.pofile import read_po + +root = path(__file__).parent.parent +os.chdir(root) +paths = { + 'ext/src':{'cmd':'walkfiles', + 'ignore':['src\\generated']}, + 'src/':'files', + 'src/plugins':{'cmd':'walkdirs', + 'ignore':['.svn']}, + 'src':{'cmd':'walkdirs', + 'ignore':['src\\plugins', '.svn']}, + 'res':{'cmd':'walkfiles'} + } + +def walkfiles(pth, ignore, exts): + root = path(__file__).parent.parent + pthin = (root / pth).abspath().normpath() + pthout = path(__file__).parent / 'segments' / pth + if not pthout.isdir(): + pthout.makedirs() + fil = (pthout / 'app.fil').abspath().normpath() + generate_fil_file(fil, [pthin]) + outfile =fil.parent / (fil.parent.name + '.pot') + xgettext(fil, outfile, '--omit-header') + +def files(pth, ignore, exts): + print 'files', pth + +segments = (path(__file__).parent / 'segments') +if False: + shutil.rmtree(segments, True) + + for pth, val in paths.items(): + pthin = (root / pth).abspath().normpath() + if isinstance(val, basestring): + cmd = val + ignore = [] + else: + cmd = val['cmd'] + ignore = val.get('ignore', []) + + if cmd == 'walkfiles': + walkfiles(pth, ignore, None) + elif cmd == 'walkdirs': + for dpth in path(pthin).dirs(): + for ign in ignore: + if ign in dpth: + break + else: + walkfiles(root.relpathto(dpth), ignore, None) + continue + elif cmd == 'files': + files(pth, ignore, ['.py']) + +for file in segments.walkfiles('*.pot'): + print file + catalog = read_po(file.open()) + for key, message in catalog._messages.items(): + print repr(key), repr(message.string) + break diff --git a/digsby/i18n/generate_monolithic.py b/digsby/i18n/generate_monolithic.py new file mode 100644 index 0000000..b211448 --- /dev/null +++ b/digsby/i18n/generate_monolithic.py @@ -0,0 +1,287 @@ +#__LICENSE_GOES_HERE__ +import babel_mod.babelgen_ext +from util.primitives.mapping import odict +import babel.messages.pofile as pofile +import argparse +write_po = pofile.write_po +from babel.messages.catalog import Catalog, Message +from babel.messages.pofile import read_po +from genargspec import get_argspec +from path import path +import sys +import os.path + +thisdir = path(__file__).abspath().dirname() +DIGSBYROOT = thisdir.parent.normpath() +sys.path.insert(0, thisdir) +sys.path.insert(0, DIGSBYROOT / 'build') + +import buildutil + +import mkappfill +import mki18n +import langtools +from path import path + +# directories with code containing strings to be translated. +SOURCE_DIRS = [DIGSBYROOT / p for p in [ + 'src', + 'ext/src', +]] + +YAML_SOURCE_DIRS = [DIGSBYROOT / p for p in [ + 'src', + 'ext/src', + 'res', +]] + +DOMAIN = 'digsby' + +def download_i18n_tools(): + pass + +def check_for_i18n_tools(): + def _check(): + stdout = buildutil.run(['xgettext'], + expect_return_code=1, + capture_stdout=True, + include_stderr=True) + if not 'no input file given' in stdout: + raise Exception('unexpected output') + + try: + _check() + except Exception: + dir = os.path.abspath(langtools.download_i18n_tools()) + assert os.path.isdir(dir), dir + os.environ['PATH'] = os.environ['PATH'] + os.pathsep + dir + _check() + +MO_DIR = None # default ./locale +PO_DIR = DIGSBYROOT / 'devplugins' / 'l33t_language' +POT_DIR = DIGSBYROOT / 'i18n' / 'templates' +TEMP_DIR = DIGSBYROOT / 'i18n' / 'temp' +if not POT_DIR.isdir(): + POT_DIR.makedirs() +if not TEMP_DIR.isdir(): + TEMP_DIR.makedirs() + +def POT_path(name): + return os.path.join(POT_DIR, name, name + '.pot') + +def PO_path(name, lang): + return os.path.join(POT_DIR, name, name + '-' + lang + '.po') + +def TEMP_path(name): + return os.path.join(TEMP_DIR, name) + +FIL_PATH = TEMP_path('app.fil') +FIL_SIP_PATH = TEMP_path('appsip.fil') +FIL_YAML_PATH = TEMP_path('appyaml.fil') +FIL_TENJIN_PATH = TEMP_path('apptenjin.fil') + +def rename_new_pofiles(POT_DIR): + with buildutil.cd(POT_DIR): + for p in path('.').files('*.po.new'): + pofile = path(p.namebase) + if pofile.isfile(): + oldfile = pofile+'.old' + if oldfile.isfile(): + oldfile.remove() + print 'renaming', pofile, 'to', pofile+'.old' + pofile.rename(pofile + '.old') + print 'renaming', p, 'to', pofile + p.rename(pofile) + +def yield_translatable_yaml_strings(filename): + # TODO: this is a hack. how can we construct a syck loader that retains line number information? + import re + pattern = re.compile(r'''\!N?\_ +?("|')((?:.+?)[^\\])(?:\1)''') + for lineno, line in enumerate(open(filename, 'rb').readlines()): + m = pattern.search(line) + if m is not None: + if line.lstrip().startswith('#'): # skip comments + continue + + text = m.group(2).strip() +# if (text.startswith('"') and text.endswith('"')) or \ +# (text.startswith("'") and text.endswith("'")): +# text = text[1:-1] + + yield text, lineno + +def xgettext_yaml(filelist, outputfilename): + output = open(outputfilename, 'wb') + print output, outputfilename + cat = Catalog() + for f in open(filelist, 'rb').readlines(): + f = f.strip() + assert os.path.isfile(f), f + for s, lineno in yield_translatable_yaml_strings(f): + cat[s] = Message(s, locations=[(f, lineno)]) + write_po(output, cat, width=None, sort_by_file=True) + +header_comment = '''\ +# Translations for the Digsby client. +# Copyright (C) 2011 dotSyntax, LLC +# This file is distributed under the BSD License. +# +# TOS for Digsby is located at http://www.digsby.com/tos.php +# +''' + +HEADER = [ +# '--package-name', 'digsby', +# '--msgid-bugs-address', 'https://translations.launchpad.net/digsby', +# '--copyright-holder', 'dotSyntax, LLC', + ] + +def KEYWORDS(): + return ['--keyword=' + spec for spec in \ + (get_argspec() + + [ + '_', + 'N_', + ] + ) + ] + +def xgettext(input_file, output_file, *args): + buildutil.run(['xgettext', + ] + + HEADER + + KEYWORDS() + [ + '--from-code', 'UTF-8', + '--no-wrap', '--add-comments=Translators:'] + + list(args) + + ['--files-from', input_file, + '--output', output_file]) + +def pot_read_clean(f): + from analyze import remove_template_problems + cat = read_po(remove_template_problems(f)) + write_pot(f, cat) + return cat + +def write_pot(f, cat, version=False): + cat.header_comment = header_comment + cat.fuzzy = False + cat.project = 'digsby' + cat.version = '$Rev$' + cat.msgid_bugs_address = 'https://translations.launchpad.net/digsby' + cat.copyright_holder = 'dotSyntax, LLC' + + for m in cat: + m.locations = sorted(m.locations) + cat._messages = odict(sorted(cat._messages.iteritems(), key = lambda x: x[1].locations)) + if version: + cat.add('__version__', auto_comments=['$Rev$'], context='__metadata__') + return write_po(open(f, 'w'), cat, width=None) + +class All(argparse.Action): + def __call__(self, parser, ns, value, option_string): + all = value or not any((ns.po, ns.mo, ns.appfil)) + print 'running All:', all + ns.all = all + if all: + Appfil(None, None)(None, self, True, None) + Po(None, None)(None, self, True, None) + Mo(None, None)(None, self, True, None) + +class Appfil(argparse.Action): + def __call__(self, parser, ns, value, option_string): + if not value: + return + print 'running Appfil', id(self) + # TODO: instead of N walks over the filesystem tree, do one walk, with multiple visitors + mkappfill.generate_fil_file(FIL_PATH, SOURCE_DIRS) +# mkappfill.generate_fil_file(FIL_SIP_PATH, SOURCE_DIRS, extensions=['.sip']) + mkappfill.generate_fil_file(FIL_YAML_PATH, YAML_SOURCE_DIRS, extensions=['.yaml']) + mkappfill.generate_fil_file(FIL_TENJIN_PATH, SOURCE_DIRS, extensions=['.tenjin', '.py.xml']) + +class Po(argparse.Action): + def __call__(self, parser, ns, value, option_string): + if not value: + return + print 'running Po', id(self) + with buildutil.cd(DIGSBYROOT): + default_pot = TEMP_path('digsby_default.pot') + sip_pot = TEMP_path('digsby_sip.pot') + yaml_pot = TEMP_path('digsby_yaml.pot') + tenjin_pot = TEMP_path('digsby_tenjin.pot') + input_pots = [default_pot, sip_pot, yaml_pot, tenjin_pot] + final_pot = POT_path('digsby') + for pot in input_pots: + if os.path.isfile(pot): + os.remove(pot) + + xgettext(FIL_PATH, default_pot) +# xgettext(FIL_SIP_PATH, sip_pot, '-C') + xgettext_yaml(FIL_YAML_PATH, yaml_pot) + xgettext(FIL_TENJIN_PATH, tenjin_pot, '-L', 'python') + + input_pots = filter(lambda pot: os.path.isfile(pot), input_pots) + + cat = pot_read_clean(input_pots[0]) + for potfile in input_pots[1:]: + cat_u = pot_read_clean(potfile) + for msg in cat_u: + cat[msg.id] = msg + write_pot(final_pot, cat, version=True) + + pofiles = [os.path.join(PO_DIR, f) for f in os.listdir(PO_DIR) if f.endswith('.po')] + for pofile in pofiles: + buildutil.run(['msgmerge', '-F', '--no-wrap', + pofile, final_pot, '-o', pofile]) +# for potfile in input_pots: +# os.remove(potfile) + +class Mo(argparse.Action): + def __call__(self, parser, ns, value, option_string): + if not value: + return + print 'running Mo', id(self) + mki18n.makeMO(DIGSBYROOT, MO_DIR, DOMAIN, True, poDir=PO_DIR) + +def arguments(): + import argparse + parser = argparse.ArgumentParser(description='i18n template builder') + group = parser.add_argument_group() + group.add_argument('--all', action=All, nargs='?', const=True, default=False) + group.add_argument('-a', '--appfil', action=Appfil, nargs='?', const=True) + group.add_argument('-p', '--po', action=Po, nargs='?', const=True) + group.add_argument('-m', '--mo', action=Mo, nargs='?', const=True) + return parser.parse_args() + +def coalesce(): + default_pot = PO_path('digsby_default', 'tr') + sip_pot = PO_path('digsby_sip', 'tr') + yaml_pot = PO_path('digsby_yaml', 'tr') + tenjin_pot = PO_path('digsby_tenjin', 'tr') + input_pots = [default_pot, sip_pot, yaml_pot, tenjin_pot] + final_pot = POT_path('digsby') + final_po = PO_path('digsby', 'tr') + input_pots = filter(lambda pot: os.path.isfile(pot), input_pots) + from analyze import remove_template_problems + cat = read_po(remove_template_problems(final_pot)) + for potfile in input_pots: + cat_u = read_po(open(potfile)) + for msg in cat_u: + new = msg.string + if msg.id in cat: + cat[msg.id].string = new + write_po(open(final_po, 'w'), cat, width=77, sort_by_file=False) + +def main(): + origdir = os.getcwd() + os.chdir(DIGSBYROOT) + try: + check_for_i18n_tools() + arguments() +# coalesce() + finally: + os.chdir(origdir) + +if __name__ == '__main__': + main() + diff --git a/digsby/i18n/mkappfill.py b/digsby/i18n/mkappfill.py new file mode 100644 index 0000000..c1902fe --- /dev/null +++ b/digsby/i18n/mkappfill.py @@ -0,0 +1,48 @@ +#__LICENSE_GOES_HERE__ +from __future__ import with_statement +import os.path +from path import path + +EXTENSIONS = [ + '.py', + '.cpp', + '.c', + '.h', +] + +blacklist = [ + 'ext/src/generated', + 'gui/pref/pg_dev.py', # don't include translated strings from dev pref pane + 'gui/bugreporter/bugreporterguiold.py', + 'gui/notificationview.py' +] +blacklist = [os.path.normpath(b) for b in blacklist] + +def blacklisted(f): + f = os.path.normpath(f) + for b in blacklist: + if b in f: + return True + +def generate_fil_file(filpath='app.fil', dirs=None, extensions=None): + if dirs is None: + dirs = ['../src'] + + if extensions is None: + extensions = EXTENSIONS + assert not isinstance(extensions, str), 'extensions must be a seq of strings like [".cpp", ".h"]' + + with open(filpath, 'w') as appfile: + for dir in dirs: + for file in path(dir).walkfiles(): + f = file.normcase() + for ext in extensions: + if f.endswith(os.path.normcase(ext)): + if not blacklisted(f): + appfile.write(file.relpath()+'\n') + +def main(): + generate_fil_file() + +if __name__ == '__main__': + main() diff --git a/digsby/i18n/mki18n.py b/digsby/i18n/mki18n.py new file mode 100644 index 0000000..49958f0 --- /dev/null +++ b/digsby/i18n/mki18n.py @@ -0,0 +1,471 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# +# PYTHON MODULE: MKI18N.PY +# ========= +# +# Abstract: Make Internationalization (i18n) files for an application. +# +# Copyright Pierre Rouleau. 2003. Released to public domain. +# +# Last update: Saturday, November 8, 2003. @ 15:55:18. +# +# File: ROUP2003N01::C:/dev/python/mki18n.py +# +# RCS $Header: //software/official/MKS/MKS_SI/TV_NT/dev/Python/rcs/mki18n.py 1.5 2003/11/05 19:40:04 PRouleau Exp $ +# +# Update history: +# +# - File created: Saturday, June 7, 2003. by Pierre Rouleau +# - 10/06/03 rcs : RCS Revision 1.1 2003/06/10 10:06:12 PRouleau +# - 10/06/03 rcs : RCS Initial revision +# - 23/08/03 rcs : RCS Revision 1.2 2003/06/10 10:54:27 PRouleau +# - 23/08/03 P.R.: [code:fix] : The strings encoded in this file are encode in iso-8859-1 format. Added the encoding +# notification to Python to comply with Python's 2.3 PEP 263. +# - 23/08/03 P.R.: [feature:new] : Added the '-e' switch which is used to force the creation of the empty English .mo file. +# - 22/10/03 P.R.: [code] : incorporated utility functions in here to make script self sufficient. +# - 05/11/03 rcs : RCS Revision 1.4 2003/10/22 06:39:31 PRouleau +# - 05/11/03 P.R.: [code:fix] : included the unixpath() in this file. +# - 08/11/03 rcs : RCS Revision 1.5 2003/11/05 19:40:04 PRouleau +# +# RCS $Log: $ +# +# +# ----------------------------------------------------------------------------- +""" +mki18n allows you to internationalize your software. You can use it to +create the GNU .po files (Portable Object) and the compiled .mo files +(Machine Object). + +mki18n module can be used from the command line or from within a script (see +the Usage at the end of this page). + + Table of Contents + ----------------- + + makePO() -- Build the Portable Object file for the application -- + catPO() -- Concatenate one or several PO files with the application domain files. -- + makeMO() -- Compile the Portable Object files into the Machine Object stored in the right location. -- + printUsage -- Displays how to use this script from the command line -- + + Scriptexecution -- Runs when invoked from the command line -- + + +NOTE: this module uses GNU gettext utilities. + +You can get the gettext tools from the following sites: + + - `GNU FTP site for gettetx`_ where several versions (0.10.40, 0.11.2, 0.11.5 and 0.12.1) are available. + Note that you need to use `GNU libiconv`_ to use this. Get it from the `GNU + libiconv ftp site`_ and get version 1.9.1 or later. Get the Windows .ZIP + files and install the packages inside c:/gnu. All binaries will be stored + inside c:/gnu/bin. Just put c:/gnu/bin inside your PATH. You will need + the following files: + + - `gettext-runtime-0.12.1.bin.woe32.zip`_ + - `gettext-tools-0.12.1.bin.woe32.zip`_ + - `libiconv-1.9.1.bin.woe32.zip`_ + + +.. _GNU libiconv: http://www.gnu.org/software/libiconv/ +.. _GNU libiconv ftp site: http://www.ibiblio.org/pub/gnu/libiconv/ +.. _gettext-runtime-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-runtime-0.12.1.bin.woe32.zip +.. _gettext-tools-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.12.1.bin.woe32.zip +.. _libiconv-1.9.1.bin.woe32.zip: http://www.ibiblio.org/pub/gnu/libiconv/libiconv-1.9.1.bin.woe32.zip + +""" +# ----------------------------------------------------------------------------- +# Module Import +# ------------- +# +import os +import sys +import wx +# ----------------------------------------------------------------------------- +# Global variables +# ---------------- +# + +__author__ = "Pierre Rouleau" +__version__= "$Revision: 1.5 $" + +# ----------------------------------------------------------------------------- + +#def getlanguageDict(): +# languageDict = {} +# +# for lang in [x for x in dir(wx) if x.startswith("LANGUAGE")]: +# i = wx.Locale(wx.LANGUAGE_DEFAULT).GetLanguageInfo(getattr(wx, lang)) +# if i: +# languageDict[i.CanonicalName] = i.Description +# +# return languageDict + +# ----------------------------------------------------------------------------- +# m a k e P O ( ) -- Build the Portable Object file for the application -- +# ^^^^^^^^^^^^^^^ +# +def makePO(applicationDirectoryPath, applicationDomain=None, verbose=0, poDir=None, appFil=None) : + """Build the Portable Object Template file for the application. + + makePO builds the .pot file for the application stored inside + a specified directory by running xgettext for all application source + files. It finds the name of all files by looking for a file called 'app.fil'. + If this file does not exists, makePo raises an IOError exception. + By default the application domain (the application + name) is the same as the directory name but it can be overridden by the + 'applicationDomain' argument. + + makePO always creates a new file called messages.pot. If it finds files + of the form app_xx.po where 'app' is the application name and 'xx' is one + of the ISO 639 two-letter language codes, makePO resynchronizes those + files with the latest extracted strings (now contained in messages.pot). + This process updates all line location number in the language-specific + .po files and may also create new entries for translation (or comment out + some). The .po file is not changed, instead a new file is created with + the .new extension appended to the name of the .po file. + + By default the function does not display what it is doing. Set the + verbose argument to 1 to force it to print its commands. + """ + + if applicationDomain is None: + applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) + else: + applicationName = applicationDomain + currentDir = os.getcwd() + os.chdir(applicationDirectoryPath) + + if appFil is None: + appFil = 'app.fil' + if not os.path.exists(appFil): + raise IOError(2,'No module file: %r' % appFil) + + if poDir is None: + poDir = os.getcwd() + + messagesPot = os.path.join(poDir, 'messages.pot') + + # Steps: + # Use xgettext to parse all application modules + # The following switches are used: + # + # -s : sort output by string content (easier to use when we need to merge several .po files) + # --files-from=app.fil : The list of files is taken from the file: app.fil + # --output= : specifies the name of the output file (using a .pot extension) + cmd = 'xgettext -s -k_ --no-wrap --files-from="%s" --output=%s' % (appFil, messagesPot) + if verbose: print cmd + os.system(cmd) + + pofiles = [os.path.join(poDir, file) for file in os.listdir(poDir) if file.endswith('.po')] + + for pofile in pofiles: + cmd = 'msgmerge -s --no-wrap "%s" "%s" > "%s.new"' % (pofile, messagesPot, pofile) + if verbose: print cmd + os.system(cmd) + os.chdir(currentDir) + +# ----------------------------------------------------------------------------- +# c a t P O ( ) -- Concatenate one or several PO files with the application domain files. -- +# ^^^^^^^^^^^^^ +# +#def catPO(applicationDirectoryPath, listOf_extraPo, applicationDomain=None, targetDir=None, verbose=0) : +# """Concatenate one or several PO files with the application domain files. +# """ +# +# if applicationDomain is None: +# applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) +# else: +# applicationName = applicationDomain +# currentDir = os.getcwd() +# os.chdir(applicationDirectoryPath) +# +# languageDict = getlanguageDict() +# +# for langCode in languageDict.keys(): +# if langCode == 'en': +# pass +# else: +# langPOfileName = "%s_%s.po" % (applicationName , langCode) +# if os.path.exists(langPOfileName): +# fileList = '' +# for fileName in listOf_extraPo: +# fileList += ("%s_%s.po " % (fileName,langCode)) +# cmd = "msgcat -s --no-wrap %s %s > %s.cat" % (langPOfileName, fileList, langPOfileName) +# if verbose: print cmd +# os.system(cmd) +# if targetDir is None: +# pass +# else: +# mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) +# cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po.cat" % (mo_targetDir,applicationName,applicationName,langCode) +# if verbose: print cmd +# os.system(cmd) +# os.chdir(currentDir) + +# ----------------------------------------------------------------------------- +# m a k e M O ( ) -- Compile the Portable Object files into the Machine Object stored in the right location. -- +# ^^^^^^^^^^^^^^^ +# +def makeMO(applicationDirectoryPath,targetDir='./locale',applicationDomain=None, verbose=0, forceEnglish=0,poDir=None) : + """Compile the Portable Object files into the Machine Object stored in the right location. + + makeMO converts all translated language-specific PO files located inside + the application directory into the binary .MO files stored inside the + LC_MESSAGES sub-directory for the found locale files. + + makeMO searches for all files that have a name of the form 'app_xx.po' + inside the application directory specified by the first argument. The + 'app' is the application domain name (that can be specified by the + applicationDomain argument or is taken from the directory name). The 'xx' + corresponds to one of the ISO 639 two-letter language codes. + + makeMo stores the resulting files inside a sub-directory of `targetDir` + called xx/LC_MESSAGES where 'xx' corresponds to the 2-letter language + code. + """ + if targetDir is None: + targetDir = './locale' + targetDir = os.path.abspath(targetDir) + if verbose: + print "Target directory for .mo files is: %s" % targetDir + + if applicationDomain is None: + applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) + else: + applicationName = applicationDomain + currentDir = os.getcwd() + os.chdir(applicationDirectoryPath) + +# languageDict = getlanguageDict() +# +# for langCode in languageDict.keys(): + + if poDir is None: + poDir = os.getcwd() + os.chdir(poDir) + pofiles = [file for file in os.listdir(poDir) if file.endswith('.po')] + + import re + + langCodeRE = re.compile('_'.join([applicationName, '(\w\w(?:_\w\w)?)\.po'])) + + for pofile in pofiles: + match = langCodeRE.match(pofile) + if match is not None: + langCode = match.group(1) + mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) + if not os.path.exists(mo_targetDir): + mkdir(mo_targetDir) + cmd = 'msgfmt.exe --output-file="%s/%s.mo" "%s"' % (mo_targetDir,applicationName,pofile) + if verbose: print cmd + os.system(cmd) + os.chdir(currentDir) + +# ----------------------------------------------------------------------------- +# p r i n t U s a g e -- Displays how to use this script from the command line -- +# ^^^^^^^^^^^^^^^^^^^ +# +def printUsage(errorMsg=None) : + """Displays how to use this script from the command line.""" + print """ + ################################################################################## + # mki18n : Make internationalization files. # + # Uses the GNU gettext system to create PO (Portable Object) files # + # from source code, coimpile PO into MO (Machine Object) files. # + # Supports C,C++,Python source files. # + # # + # Usage: mki18n {OPTION} [appDirPath] # + # # + # Options: # + # -e : When -m is used, forces English .mo file creation # + # -h : prints this help # + # -m : make MO from existing PO files # + # -p : make PO, update PO files: Creates a new messages.pot # + # file. Creates a dom_xx.po.new for every existing # + # language specific .po file. ('xx' stands for the ISO639 # + # two-letter language code and 'dom' stands for the # + # application domain name). mki18n requires that you # + # write a 'app.fil' file which contains the list of all # + # source code to parse. # + # -v : verbose (prints comments while running) # + # --domain=appName : specifies the application domain name. By default # + # the directory name is used. # + # --moTarget=dir : specifies the directory where .mo files are stored. # + # If not specified, the target is './locale' # + # # + # You must specify one of the -p or -m option to perform the work. You can # + # specify the path of the target application. If you leave it out mki18n # + # will use the current directory as the application main directory. # + # # + ##################################################################################""" + if errorMsg: + print "\n ERROR: %s" % errorMsg + +# ----------------------------------------------------------------------------- +# f i l e B a s e O f ( ) -- Return base name of filename -- +# ^^^^^^^^^^^^^^^^^^^^^^^ +# +def fileBaseOf(filename,withPath=0) : + """fileBaseOf(filename,withPath) ---> string + + Return base name of filename. The returned string never includes the extension. + Use os.path.basename() to return the basename with the extension. The + second argument is optional. If specified and if set to 'true' (non zero) + the string returned contains the full path of the file name. Otherwise the + path is excluded. + + [Example] + >>> fn = 'd:/dev/telepath/tvapp/code/test.html' + >>> fileBaseOf(fn) + 'test' + >>> fileBaseOf(fn) + 'test' + >>> fileBaseOf(fn,1) + 'd:/dev/telepath/tvapp/code/test' + >>> fileBaseOf(fn,0) + 'test' + >>> fn = 'abcdef' + >>> fileBaseOf(fn) + 'abcdef' + >>> fileBaseOf(fn,1) + 'abcdef' + >>> fn = "abcdef." + >>> fileBaseOf(fn) + 'abcdef' + >>> fileBaseOf(fn,1) + 'abcdef' + """ + pos = filename.rfind('.') + if pos > 0: + filename = filename[:pos] + if withPath: + return filename + else: + return os.path.basename(filename) +# ----------------------------------------------------------------------------- +# m k d i r ( ) -- Create a directory (and possibly the entire tree) -- +# ^^^^^^^^^^^^^ +# +def mkdir(directory) : + """Create a directory (and possibly the entire tree). + + The os.mkdir() will fail to create a directory if one of the + directory in the specified path does not exist. mkdir() + solves this problem. It creates every intermediate directory + required to create the final path. Under Unix, the function + only supports forward slash separator, but under Windows and MacOS + the function supports the forward slash and the OS separator (backslash + under windows). + """ + + # translate the path separators + directory = unixpath(directory) + # build a list of all directory elements + aList = filter(lambda x: len(x)>0, directory.split('/')) + theLen = len(aList) + # if the first element is a Windows-style disk drive + # concatenate it with the first directory + if aList[0].endswith(':'): + if theLen > 1: + aList[1] = aList[0] + '/' + aList[1] + del aList[0] + theLen -= 1 + # if the original directory starts at root, + # make sure the first element of the list + # starts at root too + if directory[0] == '/': + aList[0] = '/' + aList[0] + # Now iterate through the list, check if the + # directory exists and if not create it + theDir = '' + for i in range(theLen): + theDir += aList[i] + if not os.path.exists(theDir): + os.mkdir(theDir) + theDir += '/' + +# ----------------------------------------------------------------------------- +# u n i x p a t h ( ) -- Return a path name that contains Unix separator. -- +# ^^^^^^^^^^^^^^^^^^^ +# +def unixpath(thePath) : + r"""Return a path name that contains Unix separator. + + [Example] + >>> unixpath(r"d:\test") + 'd:/test' + >>> unixpath("d:/test/file.txt") + 'd:/test/file.txt' + >>> + """ + thePath = os.path.normpath(thePath) + if os.sep == '/': + return thePath + else: + return thePath.replace(os.sep,'/') + +# ----------------------------------------------------------------------------- + +# S c r i p t e x e c u t i o n -- Runs when invoked from the command line -- +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +if __name__ == "__main__": + + a = wx.App() + + import getopt # command line parsing + argc = len(sys.argv) + if argc == 1: + printUsage('Missing argument: specify at least one of -m or -p (or both).') + sys.exit(1) + # If there is some arguments, parse the command line + validOptions = "ehmpv" + validLongOptions = ['domain=', 'moTarget='] + option = {} + option['forceEnglish'] = 0 + option['mo'] = 0 + option['po'] = 0 + option['verbose'] = 0 + option['domain'] = None + option['moTarget'] = None + try: + optionList,pargs = getopt.getopt(sys.argv[1:],validOptions,validLongOptions) + except getopt.GetoptError, e: + printUsage(e[0]) + sys.exit(1) + for (opt,val) in optionList: + if (opt == '-h'): + printUsage() + sys.exit(0) + elif (opt == '-e'): option['forceEnglish'] = 1 + elif (opt == '-m'): option['mo'] = 1 + elif (opt == '-p'): option['po'] = 1 + elif (opt == '-v'): option['verbose'] = 1 + elif (opt == '--domain'): option['domain'] = val + elif (opt == '--moTarget'): option['moTarget'] = val + if len(pargs) == 0: + appDirPath = os.getcwd() + if option['verbose']: + print "No project directory given. Using current one: %s" % appDirPath + elif len(pargs) == 1: + appDirPath = pargs[0] + else: + printUsage('Too many arguments (%u). Use double quotes if you have space in directory name' % len(pargs)) + sys.exit(1) + if option['domain'] is None: + # If no domain specified, use the name of the target directory + option['domain'] = fileBaseOf(appDirPath) + if option['verbose']: + print "Application domain used is: '%s'" % option['domain'] + if option['po']: + try: + makePO(appDirPath,option['domain'],option['verbose']) + except IOError, e: + printUsage(e[1] + '\n You must write a file app.fil that contains the list of all files to parse.') + if option['mo']: + makeMO(appDirPath,option['moTarget'],option['domain'],option['verbose'],option['forceEnglish']) + sys.exit(1) + + +# ----------------------------------------------------------------------------- diff --git a/digsby/i18n/templates/digsby/digsby.pot b/digsby/i18n/templates/digsby/digsby.pot new file mode 100644 index 0000000..370ea55 --- /dev/null +++ b/digsby/i18n/templates/digsby/digsby.pot @@ -0,0 +1,5992 @@ +# Translations for the Digsby client. +# Copyright (C) 2011 dotSyntax, LLC +# This file is distributed under the BSD License. +# +# TOS for Digsby is located at http://www.digsby.com/tos.php +# +msgid "" +msgstr "" +"Project-Id-Version: digsby $Rev: 30062 $\n" +"Report-Msgid-Bugs-To: https://translations.launchpad.net/digsby\n" +"POT-Creation-Date: 2011-04-14 17:17-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit" + +#: ext/src/LoginWindow.cpp:276 +msgid "Digsby Login" +msgstr "" + +#: ext/src/LoginWindow.cpp:277 +msgid "Digsby &Username:" +msgstr "" + +#: ext/src/LoginWindow.cpp:278 src/gui/proxydialog.py:105 +msgid "&Password:" +msgstr "" + +#: ext/src/LoginWindow.cpp:279 +msgid "&Save password" +msgstr "" + +#: ext/src/LoginWindow.cpp:280 src/gui/accountdialog.py:354 +msgid "&Auto login" +msgstr "" + +#: ext/src/LoginWindow.cpp:281 +msgid "Sign In" +msgstr "" + +#: ext/src/LoginWindow.cpp:282 +msgid "Forgot?" +msgstr "" + +#: ext/src/LoginWindow.cpp:283 +#: src/plugins/digsby_service_editor/default_ui.py:150 +msgid "New User?" +msgstr "" + +#: res/actions.yaml:13 res/actions.yaml:126 +msgid "&Buddy Info" +msgstr "" + +#: res/actions.yaml:16 res/actions.yaml:129 +msgid "&Send IM" +msgstr "" + +#: res/actions.yaml:19 res/actions.yaml:132 +msgid "Send &File" +msgstr "" + +#: res/actions.yaml:22 res/actions.yaml:135 +msgid "Send &Email" +msgstr "" + +#: res/actions.yaml:25 res/actions.yaml:138 +msgid "Send &SMS" +msgstr "" + +#: res/actions.yaml:30 res/actions.yaml:58 res/actions.yaml:74 +#: res/actions.yaml:92 res/actions.yaml:108 src/gui/imwin/imwinmenu.py:65 +msgid "&View Past Chats" +msgstr "" + +#: res/actions.yaml:33 res/actions.yaml:143 +msgid "&Alert Me When..." +msgstr "" + +#: res/actions.yaml:38 +msgid "Re&name" +msgstr "" + +#: res/actions.yaml:41 res/actions.yaml:61 res/actions.yaml:77 +#: res/actions.yaml:117 +msgid "Bl&ock" +msgstr "" + +#: res/actions.yaml:45 res/actions.yaml:65 res/actions.yaml:81 +#: res/actions.yaml:121 +msgid "Unbl&ock" +msgstr "" + +#: res/actions.yaml:48 res/actions.yaml:313 res/actions.yaml:325 +#: res/actions.yaml:335 res/actions.yaml:346 res/actions.yaml:352 +#: res/actions.yaml:360 src/gui/widgetlist.py:43 +#: src/plugins/twitter/twitter_gui.py:1383 +msgid "&Delete" +msgstr "" + +#: res/actions.yaml:97 +msgid "Resend authorization" +msgstr "" + +#: res/actions.yaml:100 +msgid "Re-request authorization" +msgstr "" + +#: res/actions.yaml:103 +msgid "Remove authorization" +msgstr "" + +#: res/actions.yaml:111 +msgid "Appear offline" +msgstr "" + +#: res/actions.yaml:114 +msgid "Appear online" +msgstr "" + +#: res/actions.yaml:148 +msgid "Re&name && Rearrange..." +msgstr "" + +#: res/actions.yaml:151 +msgid "Split Merged Contact..." +msgstr "" + +#: res/actions.yaml:162 src/gui/buddylist/buddylist.py:1011 +msgid "&Rename Group" +msgstr "" + +#: res/actions.yaml:165 src/gui/buddylist/buddylist.py:1012 +msgid "&Delete Group" +msgstr "" + +#: res/actions.yaml:169 src/gui/buddylist/buddylist.py:1013 +msgid "Add &Group" +msgstr "" + +#: res/actions.yaml:174 src/gui/accountslist.py:136 +#: src/gui/uberwidgets/connectionlist.py:462 +msgid "&Connect" +msgstr "" + +#: res/actions.yaml:177 +msgid "&Disconnect" +msgstr "" + +#: res/actions.yaml:186 +msgid "&Format Screen Name" +msgstr "" + +#: res/actions.yaml:190 +msgid "&Update Email Address" +msgstr "" + +#: res/actions.yaml:194 +msgid "&Confirm Account" +msgstr "" + +#: res/actions.yaml:197 +msgid "Change &Password (URL)" +msgstr "" + +#: res/actions.yaml:200 +msgid "&IM Forwarding (URL)" +msgstr "" + +#: res/actions.yaml:203 +msgid "&AOL Alerts (URL)" +msgstr "" + +#: res/actions.yaml:206 +msgid "&My Page on ICQ.com (URL)" +msgstr "" + +#: res/actions.yaml:218 +msgid "Set Priority" +msgstr "" + +#: res/actions.yaml:234 +msgid "Change &Password" +msgstr "" + +#: res/actions.yaml:244 +msgid "&Search for a Contact" +msgstr "" + +#: res/actions.yaml:247 +msgid "Address Book" +msgstr "" + +#: res/actions.yaml:250 +msgid "Mobile Settings" +msgstr "" + +#: res/actions.yaml:253 +msgid "My Account" +msgstr "" + +#: res/actions.yaml:256 src/msn/msngui.py:35 +msgid "Create Group Chat" +msgstr "" + +#: res/actions.yaml:276 +msgid "My Account Info (URL)" +msgstr "" + +#: res/actions.yaml:279 +msgid "My Web Profile (URL)" +msgstr "" + +#: res/actions.yaml:283 +msgid "Update presence" +msgstr "" + +#: res/actions.yaml:287 +msgid "Open &Inbox" +msgstr "" + +#: res/actions.yaml:290 +msgid "C&ompose..." +msgstr "" + +#: res/actions.yaml:295 src/plugins/facebook/actions.yaml:17 +#: src/plugins/linkedin/actions.yaml:13 src/plugins/myspace/actions.yaml:16 +#: src/plugins/twitter/twitter_gui.py:2044 +msgid "&Rename" +msgstr "" + +#: res/actions.yaml:300 src/plugins/linkedin/actions.yaml:2 +msgid "&Check Now" +msgstr "" + +#: res/actions.yaml:303 src/plugins/facebook/actions.yaml:4 +msgid "&Tell Me Again" +msgstr "" + +#: res/actions.yaml:307 res/actions.yaml:319 res/actions.yaml:331 +#: res/actions.yaml:342 res/actions.yaml:350 res/actions.yaml:356 +#: src/gui/filetransfer/filetransferlist.py:473 +msgid "&Open" +msgstr "" + +#: res/actions.yaml:309 res/actions.yaml:321 res/actions.yaml:333 +#: res/actions.yaml:344 res/actions.yaml:358 +msgid "Mark as &Read" +msgstr "" + +#: res/actions.yaml:311 res/actions.yaml:323 +msgid "&Archive" +msgstr "" + +#: res/actions.yaml:315 res/actions.yaml:327 res/actions.yaml:337 +#: res/actions.yaml:362 +msgid "Report &Spam" +msgstr "" + +#: res/actions.yaml:366 src/plugins/facebook/actions.yaml:2 +#: src/plugins/myspace/actions.yaml:2 src/plugins/twitter/twitter_gui.py:1499 +msgid "&Update Now" +msgstr "" + +#: res/actions.yaml:369 src/plugins/facebook/actions.yaml:6 +#: src/plugins/linkedin/actions.yaml:4 src/plugins/myspace/actions.yaml:5 +msgid "&Home" +msgstr "" + +#: res/actions.yaml:372 src/plugins/linkedin/actions.yaml:10 +msgid "&Profile" +msgstr "" + +#: res/actions.yaml:375 src/plugins/linkedin/actions.yaml:6 +#: src/plugins/myspace/actions.yaml:7 +msgid "&Inbox" +msgstr "" + +#: res/actions.yaml:378 src/plugins/facebook/actions.yaml:10 +#: src/plugins/myspace/actions.yaml:9 +msgid "&Friends" +msgstr "" + +#: res/actions.yaml:381 src/plugins/myspace/actions.yaml:11 +msgid "&Blog" +msgstr "" + +#: res/actions.yaml:384 +msgid "B&ulletins" +msgstr "" + +#: res/actions.yaml:387 src/plugins/facebook/actions.yaml:20 +#: src/plugins/linkedin/actions.yaml:16 src/plugins/myspace/actions.yaml:19 +msgid "&Set Status" +msgstr "" + +#: res/actions.yaml:391 src/gui/imwin/imwin_email.py:51 +#: src/gui/imwin/imwin_email.py:57 src/gui/imwin/imwin_email.py:115 +msgid "Edit..." +msgstr "" + +#: res/actions.yaml:398 src/gui/status.py:744 src/gui/widgetlist.py:61 +msgid "Delete" +msgstr "" + +#: res/notificationview.yaml:1 +msgid "Contact Signs On" +msgstr "" + +#: res/notificationview.yaml:6 +msgid "Contact Signs Off" +msgstr "" + +#: res/notificationview.yaml:10 +msgid "Contact Becomes Available" +msgstr "" + +#: res/notificationview.yaml:15 +msgid "Contact Goes Away" +msgstr "" + +#: res/notificationview.yaml:20 +msgid "Contact Changes Status Message" +msgstr "" + +#: res/notificationview.yaml:25 +msgid "Contact Returns From Idle" +msgstr "" + +#: res/notificationview.yaml:29 +msgid "Contact Becomes Idle" +msgstr "" + +#: res/notificationview.yaml:33 +msgid "IM Received" +msgstr "" + +#: res/notificationview.yaml:39 +msgid "IM Received (not in focus)" +msgstr "" + +#: res/notificationview.yaml:45 +msgid "IM Received (hidden to tray)" +msgstr "" + +#: res/notificationview.yaml:53 +msgid "IM Received (first message)" +msgstr "" + +#: res/notificationview.yaml:59 +msgid "IM Sent" +msgstr "" + +#: res/notificationview.yaml:64 +msgid "Group Chat Invite" +msgstr "" + +#: res/notificationview.yaml:74 +msgid "File Transfer Request" +msgstr "" + +#: res/notificationview.yaml:85 +msgid "File Transfer Complete" +msgstr "" + +#: res/notificationview.yaml:91 +msgid "File Transfer Error" +msgstr "" + +#: res/notificationview.yaml:97 +msgid "New Email Arrives" +msgstr "" + +#: res/notificationview.yaml:105 +msgid "Error Occurs" +msgstr "" + +#: res/notificationview.yaml:112 +msgid "Downloading dictionary" +msgstr "" + +#: src/plugins/component_gmail/info.yaml:2 +msgid "Gmail" +msgstr "" + +#: src/plugins/component_gmail/info.yaml:6 +msgid "Gmail Account" +msgstr "" + +#: src/plugins/component_gtalk/info.yaml:0 +msgid "Google Talk" +msgstr "" + +#: src/plugins/component_gtalk/info.yaml:6 src/plugins/fbchat/info.yaml:17 +#: src/plugins/provider_aol/info.yaml:92 src/plugins/provider_aol/info.yaml:137 +#: src/plugins/provider_jabber/info.yaml:48 +#: src/plugins/provider_windows_live/info.yaml:30 src/common/protocolmeta.py:42 +#: src/common/statusmessage.py:31 src/common/statusmessage.py:32 +#: src/common/statusmessage.py:222 src/gui/native/win/jumplist.py:129 +#: src/gui/status.py:59 src/gui/statuscombo.py:501 +#: src/jabber/JabberResource.py:115 +msgid "Available" +msgstr "" + +#: src/plugins/component_gtalk/info.yaml:7 +#: src/plugins/provider_aol/info.yaml:93 src/plugins/provider_aol/info.yaml:139 +#: src/plugins/provider_jabber/info.yaml:50 +#: src/plugins/provider_windows_live/info.yaml:31 src/common/protocolmeta.py:43 +#: src/common/statusmessage.py:33 src/common/statusmessage.py:223 +#: src/gui/native/win/jumplist.py:130 src/gui/status.py:60 +#: src/gui/statuscombo.py:517 src/gui/statuscombo.py:522 +#: src/oscar/OscarProtocol.py:1092 +msgid "Away" +msgstr "" + +#: src/plugins/component_gtalk/info.yaml:59 +#: src/plugins/provider_google/info.yaml:0 +#: src/plugins/provider_google/info.yaml:10 +msgid "Google Account" +msgstr "" + +#: src/plugins/component_gtalk/info.yaml:60 +#: src/plugins/digsby_email/info.yaml:19 src/plugins/digsby_email/info.yaml:77 +#: src/plugins/provider_aol/info.yaml:14 src/plugins/provider_aol/info.yaml:110 +#: src/plugins/provider_google/info.yaml:11 +#: src/plugins/provider_jabber/info.yaml:16 +#: src/plugins/provider_windows_live/info.yaml:23 +#: src/plugins/provider_yahoo/info.yaml:11 src/gui/pref/pg_advanced.py:71 +#: src/plugins/digsby_service_editor/default_ui.py:166 +#: src/plugins/digsby_service_editor/default_ui.py:173 +msgid "Password" +msgstr "" + +#: src/plugins/component_yahooim/info.yaml:1 +msgid "Yahoo" +msgstr "" + +#: src/plugins/component_yahooim/info.yaml:3 +msgid "Yahoo Messenger" +msgstr "" + +#: src/plugins/component_yahooim/info.yaml:7 +#: src/plugins/component_ymail/info.yaml:7 +#: src/plugins/provider_yahoo/info.yaml:10 +msgid "Yahoo! ID" +msgstr "" + +#: src/plugins/component_ymail/info.yaml:2 +msgid "Yahoo! Mail" +msgstr "" + +#: src/plugins/component_ymail/info.yaml:3 +msgid "Yahoo Mail" +msgstr "" + +#: src/plugins/digsby_email/info.yaml:1 src/plugins/digsby_email/info.yaml:26 +#: src/plugins/digsby_email/info.yaml:116 +#: src/plugins/myspace/MyspaceProtocol.py:40 +msgid "Mail" +msgstr "" + +#: src/plugins/digsby_email/info.yaml:10 +msgid "POP Account" +msgstr "" + +#: src/plugins/digsby_email/info.yaml:18 src/plugins/digsby_email/info.yaml:76 +#: src/gui/pref/pg_advanced.py:70 +#: src/plugins/digsby_service_editor/default_ui.py:149 +#: src/plugins/digsby_service_editor/default_ui.py:154 +#: src/plugins/msim/res/content.tenjin:19 +msgid "Username" +msgstr "" + +#: src/plugins/digsby_email/info.yaml:30 +msgid "POP" +msgstr "" + +#: src/plugins/digsby_email/info.yaml:68 +msgid "IMAP Account" +msgstr "" + +#: src/plugins/digsby_email/info.yaml:101 +msgid "IMAP" +msgstr "" + +#: src/plugins/facebook/actions.yaml:8 +msgid "Pro&file" +msgstr "" + +#: src/plugins/facebook/actions.yaml:12 +msgid "&Photos" +msgstr "" + +#: src/plugins/facebook/actions.yaml:14 +msgid "&Messages" +msgstr "" + +#: src/plugins/facebook/info.yaml:0 +msgid "Facebook" +msgstr "" + +#: src/plugins/facebook/info.yaml:4 +msgid "News Feed" +msgstr "" + +#: src/plugins/facebook/info.yaml:9 src/plugins/fbchat/info.yaml:11 +#: src/plugins/linkedin/info.yaml:8 src/plugins/msim/info.yaml:7 +#: src/plugins/myspace/info.yaml:9 src/plugins/provider_linkedin/info.yaml:10 +#: src/plugins/provider_myspace/info.yaml:10 +#: src/plugins/provider_windows_live/info.yaml:22 +#: src/plugins/provider_windows_live/info.yaml:37 +#: src/plugins/provider_windows_live/info.yaml:81 src/gui/accountdialog.py:890 +msgid "Email Address" +msgstr "" + +#: src/plugins/facebook/info.yaml:26 src/plugins/myspace/info.yaml:47 +#: src/plugins/twitter/res/content.tenjin:25 +msgid "Show Alerts" +msgstr "" + +#: src/plugins/facebook/notifications.yaml:1 +msgid "Facebook Alert" +msgstr "" + +#: src/plugins/facebook/notifications.yaml:10 +#: src/plugins/facebook/notifications.yaml:11 +msgid "Facebook Notification" +msgstr "" + +#: src/plugins/facebook/notifications.yaml:21 +msgid "Facebook Newsfeed" +msgstr "" + +#: src/plugins/fbchat/info.yaml:5 +msgid "Facebook Chat" +msgstr "" + +#: src/plugins/linkedin/actions.yaml:8 +msgid "C&ontacts" +msgstr "" + +#: src/plugins/linkedin/info.yaml:0 +msgid "LinkedIn" +msgstr "" + +#: src/plugins/linkedin/info.yaml:46 +msgid "LinkedIn Newsfeed" +msgstr "" + +#: src/plugins/msim/info.yaml:0 +msgid "MySpace IM" +msgstr "" + +#: src/plugins/msim/info.yaml:4 +msgid "MySpaceIM" +msgstr "" + +#: src/plugins/myspace/actions.yaml:13 +msgid "&Post Bulletin" +msgstr "" + +#: src/plugins/myspace/info.yaml:63 +msgid "Show Newsfeed" +msgstr "" + +#: src/plugins/myspace/notifications.yaml:1 +msgid "MySpace Alert" +msgstr "" + +#: src/plugins/myspace/notifications.yaml:9 +#: src/plugins/myspace/MyspaceProtocol.py:395 +msgid "MySpace Newsfeed" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:5 +msgid "AOL/AIM Account" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:13 src/plugins/provider_aol/info.yaml:56 +#: src/plugins/provider_aol/info.yaml:73 +msgid "Screen Name" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:63 +msgid "AOL Mail" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:72 +msgid "AIM" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:99 +msgid "ICQ Account" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:109 +msgid "UIN or Email" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:117 +#: src/plugins/provider_jabber/info.yaml:25 +msgid "Chat" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:138 +#: src/plugins/provider_jabber/info.yaml:49 src/common/protocolmeta.py:42 +msgid "Free For Chat" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:140 +#: src/plugins/provider_jabber/info.yaml:51 src/common/protocolmeta.py:43 +#: src/jabber/JabberResource.py:14 +msgid "Do Not Disturb" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:141 +msgid "Not Available" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:142 src/oscar/OscarBuddies.py:46 +msgid "Occupied" +msgstr "" + +#: src/plugins/provider_aol/info.yaml:143 +msgid "ICQ Number" +msgstr "" + +#: src/plugins/provider_facebook/info.yaml:0 +#: src/plugins/provider_facebook/info.yaml:10 +msgid "Facebook Account" +msgstr "" + +#: src/plugins/provider_jabber/info.yaml:5 +msgid "Jabber Account" +msgstr "" + +#: src/plugins/provider_jabber/info.yaml:15 +#: src/plugins/provider_jabber/info.yaml:28 +msgid "Jabber ID" +msgstr "" + +#: src/plugins/provider_jabber/info.yaml:52 src/common/protocolmeta.py:43 +#: src/jabber/JabberResource.py:16 +msgid "Extended Away" +msgstr "" + +#: src/plugins/provider_linkedin/info.yaml:0 +msgid "LinkedIn Account" +msgstr "" + +#: src/plugins/provider_myspace/info.yaml:0 +msgid "MySpace Account" +msgstr "" + +#: src/plugins/provider_twitter/info.yaml:0 +msgid "Twitter Account" +msgstr "" + +#: src/plugins/provider_twitter/info.yaml:10 src/plugins/twitter/info.yaml:10 +msgid "Twitter ID" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:1 +msgid "Windows Live" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:10 +msgid "Windows Live Account" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:32 src/msn/MSNBuddy.py:26 +msgid "Be Right Back" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:33 +msgid "Busy" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:34 src/msn/MSNBuddy.py:27 +msgid "On the Phone" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:35 src/msn/MSNBuddy.py:28 +msgid "Out to Lunch" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:54 +msgid "Live Messenger" +msgstr "" + +#: src/plugins/provider_windows_live/info.yaml:77 +msgid "Hotmail" +msgstr "" + +#: src/plugins/provider_yahoo/info.yaml:0 +msgid "Yahoo Account" +msgstr "" + +#: src/plugins/twitter/info.yaml:0 +msgid "Twitter" +msgstr "" + +#: src/plugins/twitter/info.yaml:3 +msgid "Timeline" +msgstr "" + +#: src/plugins/twitter/notifications.yaml:1 +msgid "Twitter Update" +msgstr "" + +#: src/plugins/twitter/notifications.yaml:9 +msgid "Twitter Direct Message" +msgstr "" + +#: src/AccountManager.py:266 +msgid "Digsby is running in \"Local Mode\"" +msgstr "" + +#: src/AccountManager.py:268 +msgid "Changes to Digsby preferences may not synchronize to your other PCs right away" +msgstr "" + +#: src/AccountManager.py:800 src/gui/social_status_dialog.py:592 +#: src/imaccount.py:517 src/plugins/twitter/twitter.py:1210 +#: src/social/network.py:69 src/social/network.py:74 src/social/network.py:76 +#: src/social/network.py:77 +msgid "Retry" +msgstr "" + +#: src/AccountManager.py:801 src/imaccount.py:518 src/imaccount.py:519 +#: src/imaccount.py:520 src/social/network.py:75 +msgid "Reconnect" +msgstr "" + +#: src/AccountManager.py:802 src/hub.py:80 src/jabber/JabberChat.py:263 +#: src/plugins/digsby_status/status_tag_urls.py:113 +#: src/plugins/digsby_updater/updater.py:577 src/plugins/facebook/fbacct.py:370 +#: src/plugins/myspace/MyspaceProtocol.py:436 +#: src/plugins/researchdriver/researchtoast.py:37 +#: src/plugins/twitter/twitter.py:1212 +msgid "Close" +msgstr "" + +#: src/AccountManager.py:803 src/imaccount.py:516 src/social/network.py:67 +msgid "Edit Account" +msgstr "" + +#: src/AccountManager.py:814 src/common/Protocol.py:35 src/digsbysplash.py:39 +#: src/digsbysplash.py:658 src/digsbysplash.py:662 src/digsbysplash.py:732 +msgid "Authentication Error" +msgstr "" + +#: src/AccountManager.py:817 +msgid "Mailbox does not exist" +msgstr "" + +#: src/AccountManager.py:820 +msgid "This account has been signed on from another location" +msgstr "" + +#: src/AccountManager.py:823 +msgid "Connection to server lost" +msgstr "" + +#: src/AccountManager.py:826 +msgid "Failed to connect to server" +msgstr "" + +#: src/AccountManager.py:829 +msgid "Could not connect because you are signing on too often. Please wait 20 minutes before trying to log in again." +msgstr "" + +#: src/AccountManager.py:848 +msgid "{protocol_name} Error" +msgstr "" + +#: src/common/AchievementMixin.py:105 +#, python-format +msgid "%(num_accounts)d account" +msgid_plural "%(num_accounts)d accounts" +msgstr[0] "" +msgstr[1] "" + +#: src/common/Conversation.py:12 +msgid "[Auto-Response] {message}" +msgstr "" + +#: src/common/Conversation.py:46 +msgid "Disconnected" +msgstr "" + +#: src/common/Conversation.py:114 +msgid "Some of the messages you sent may not have been received." +msgstr "" + +#: src/common/Conversation.py:349 +msgid "{name} joined the group chat" +msgstr "" + +#: src/common/Conversation.py:353 +msgid "{name} left the group chat" +msgstr "" + +#: src/common/Conversation.py:371 +#, python-format +msgid "%(name)s wants to have an Audio/Video chat. Send them an invite." +msgstr "" + +#: src/common/Protocol.py:25 +msgid "Online" +msgstr "" + +#: src/common/Protocol.py:26 src/digsbysplash.py:520 +msgid "Connecting..." +msgstr "" + +#: src/common/Protocol.py:27 +msgid "Authenticating..." +msgstr "" + +#: src/common/Protocol.py:28 +msgid "Loading Contact List..." +msgstr "" + +#: src/common/Protocol.py:29 src/common/statusmessage.py:35 +#: src/common/statusmessage.py:226 src/contacts/buddylistfilters.py:16 +#: src/gui/statuscombo.py:476 src/oscar/OscarBuddies.py:47 +msgid "Offline" +msgstr "" + +#: src/common/Protocol.py:30 +msgid "Checking Mail..." +msgstr "" + +#: src/common/Protocol.py:31 +msgid "Initializing..." +msgstr "" + +#: src/common/Protocol.py:36 +msgid "You have signed in from another location" +msgstr "" + +#: src/common/Protocol.py:37 src/digsbysplash.py:40 src/digsbysplash.py:734 +msgid "Connection Lost" +msgstr "" + +#: src/common/Protocol.py:38 src/digsbysplash.py:654 src/digsbysplash.py:671 +#: src/digsbysplash.py:726 src/digsbysplash.py:736 src/digsbysplash.py:745 +msgid "Failed to Connect" +msgstr "" + +#: src/common/Protocol.py:39 +msgid "You are signing in too often" +msgstr "" + +#: src/common/Protocol.py:40 +msgid "This account does not have a mailbox" +msgstr "" + +#: src/common/Protocol.py:41 +msgid "Failed to Initialize" +msgstr "" + +#: src/common/Protocol.py:42 +#, python-format +msgid "Connection Failed. Retry in %s" +msgstr "" + +#: src/common/Protocol.py:43 +msgid "Internal Server Error" +msgstr "" + +#: src/common/Protocol.py:457 +msgid "Error inviting {name}: they are offline" +msgstr "" + +#: src/common/Protocol.py:459 +msgid "Error inviting {name}: they do not support group chat" +msgstr "" + +#: src/common/accountbase.py:45 src/contacts/Contact.py:96 +#, python-format +msgid "Enter an alias for %s:" +msgstr "" + +#: src/common/accountbase.py:46 src/contacts/Contact.py:97 +#, python-format +msgid "Rename %s" +msgstr "" + +#: src/common/actions.py:149 +msgid "No actions" +msgstr "" + +#: src/common/emailaccount.py:298 +msgid "Email Client" +msgstr "" + +#: src/common/emailaccount.py:476 +msgid "No unread mail" +msgstr "" + +#: src/common/emailaccount.py:557 +msgid "No System Email Client" +msgstr "" + +#: src/common/emailaccount.py:558 +msgid "No system email client is configured." +msgstr "" + +#: src/common/emailaccount.py:563 +msgid "Could not start system mail client." +msgstr "" + +#: src/common/emailaccount.py:565 +msgid "Error launching system mail client" +msgstr "" + +#: src/common/emailaccount.py:628 +#, python-format +msgid "Could not start system mail client %r." +msgstr "" + +#: src/common/filetransfer.py:73 +msgid "{completed} of {size} ({speed}/sec) -- {time} remain" +msgstr "" + +#: src/common/filetransfer.py:74 +msgid "Received from {name}" +msgstr "" + +#: src/common/filetransfer.py:74 +msgid "Sent to {name}" +msgstr "" + +#: src/common/filetransfer.py:75 +msgid "Connecting to {name}..." +msgstr "" + +#: src/common/filetransfer.py:76 src/common/filetransfer.py:79 +msgid "Waiting for {name} to accept file" +msgstr "" + +#: src/common/filetransfer.py:78 +msgid "Canceled by {name}" +msgstr "" + +#: src/common/filetransfer.py:80 +msgid "Failed during transfer from {name}" +msgstr "" + +#: src/common/filetransfer.py:80 +msgid "Failed during transfer to {name}" +msgstr "" + +#: src/common/filetransfer.py:81 +msgid "Failed to connect to {name}" +msgstr "" + +#: src/common/filetransfer.py:82 +msgid "File size too big for proxy transfer" +msgstr "" + +#: src/common/filetransfer.py:99 +msgid "Open" +msgstr "" + +#: src/common/filetransfer.py:100 src/gui/imagedialog.py:12 +#: src/imaccount.py:521 src/imaccount.py:524 src/imaccount.py:525 +#: src/imaccount.py:526 src/plugins/digsby_updater/UpdateProgress.py:147 +#: src/plugins/twitter/twitter_gui.py:1621 +msgid "Cancel" +msgstr "" + +#: src/common/filetransfer.py:101 src/gui/accountslist.py:40 +#: src/gui/imwin/imwin_tofrom.py:341 src/gui/pref/pg_accounts.py:631 +#: src/plugins/digsby_updater/UpdateProgress.py:144 +#: src/plugins/twitter/twitter_gui.py:1948 +#: src/tests/testgui/uberdemos/UberComboXTDemo.py:32 +msgid "Remove" +msgstr "" + +#: src/common/filetransfer.py:104 +msgid "Save" +msgstr "" + +#: src/common/filetransfer.py:105 +msgid "Save as..." +msgstr "" + +#: src/common/filetransfer.py:106 +msgid "Reject" +msgstr "" + +#: src/common/filetransfer.py:243 +msgid "Directory does not exist" +msgstr "" + +#: src/common/filetransfer.py:273 +msgid "Choose a location to save" +msgstr "" + +#: src/common/filetransfer.py:361 +msgid "Failed to send {filename} to {name}" +msgstr "" + +#: src/common/filetransfer.py:361 +msgid "Failed to receive {filename} from {name}" +msgstr "" + +#: src/common/spelling/spellcheck.py:471 +msgid "You need to download the {langname} dictionary to use it. Would you like to download this dictionary now?" +msgstr "" + +#: src/common/spelling/spellcheck.py:472 +msgid "Download Dictionary?" +msgstr "" + +#: src/common/spelling/spellcheck.py:482 +msgid "Dictionary not downloaded." +msgstr "" + +#: src/common/spelling/spellcheck.py:483 +msgid "To download it later, select it in the Conversation Preferences." +msgstr "" + +#: src/common/spelling/spellcheck.py:486 +msgid "Download Dictionary Canceled" +msgstr "" + +#: src/common/spelling/spellcheck.py:505 +msgid "Dictionary Set" +msgstr "" + +#: src/common/spelling/spellcheck.py:506 +msgid "Spellcheck language has been set to {langname}." +msgstr "" + +#: src/common/spelling/spellcheck.py:511 +msgid "Spellcheck error" +msgstr "" + +#: src/common/spelling/spellcheck.py:512 +msgid "Failed setting up dictionary. Try reselecting desired language in the preferences." +msgstr "" + +#: src/common/spelling/spellcheck.py:518 src/common/spelling/spellcheck.py:676 +msgid "Dictionary Installed" +msgstr "" + +#: src/common/spelling/spellcheck.py:519 +msgid "You can set this language in the conversation preferences." +msgstr "" + +#: src/common/spelling/spellcheck.py:563 +msgid "Installing Dictionary" +msgstr "" + +#: src/common/spelling/spellcheck.py:563 +msgid "Dictionary will be activated after install completes." +msgstr "" + +#: src/common/spelling/spellcheck.py:676 +msgid "Setting spellcheck language..." +msgstr "" + +#: src/common/spelling/spellcheck.py:689 +msgid "{langname} Dictionary" +msgstr "" + +#: src/common/statusmessage.py:34 src/common/statusmessage.py:225 +#: src/gui/native/win/jumplist.py:131 src/gui/statuscombo.py:544 +msgid "Invisible" +msgstr "" + +#: src/common/statusmessage.py:138 +msgid "{truncatedtitle}..." +msgstr "" + +#: src/common/statusmessage.py:224 +msgid "Idle" +msgstr "" + +#: src/contacts/Group.py:187 +msgid "That buddy is already part of metacontact \"{alias}\" in group \"{group}.\"" +msgstr "" + +#: src/contacts/Group.py:189 src/gui/addcontactdialog.py:36 +#: src/gui/addcontactdialog.py:203 src/gui/capabilitiesbar.py:118 +#: src/gui/contactdialogs.py:252 +msgid "Add Contact" +msgstr "" + +#: src/contacts/Group.py:221 +msgid "Enter a new name for {name}:" +msgstr "" + +#: src/contacts/Group.py:222 +msgid "Rename Group" +msgstr "" + +#: src/contacts/buddylistfilters.py:29 src/contacts/buddyliststore.py:97 +#: src/contacts/buddyliststore.py:384 src/contacts/buddyliststore.py:645 +#: src/contacts/metacontacts.py:43 src/plugins/linkedin/LinkedInAccount.py:84 +msgid "Contacts" +msgstr "" + +#: src/contacts/metacontacts.py:65 +msgid "Unknown" +msgstr "" + +#: src/contacts/metacontacts.py:820 +msgid "Are you sure you want to split up this merged contact?" +msgstr "" + +#: src/contacts/metacontacts.py:821 +msgid "Split Contacts" +msgstr "" + +#: src/contacts/sort_model.py:144 +msgid "None (custom)" +msgstr "" + +#: src/contacts/sort_model.py:145 src/contacts/sort_model.py:152 +#: src/gui/pref/pg_contact_list.py:405 src/gui/pref/prefstrings.py:20 +#: src/gui/status.py:374 src/plugins/myspace/res/status.tenjin:13 +#: src/plugins/twitter/res/status.tenjin:11 +msgid "Status" +msgstr "" + +#: src/contacts/sort_model.py:146 src/contacts/sort_model.py:153 +msgid "Service" +msgstr "" + +#: src/contacts/sort_model.py:149 +msgid "None" +msgstr "" + +#: src/contacts/sort_model.py:150 +msgid "Log Size" +msgstr "" + +#: src/contacts/sort_model.py:151 src/gui/vcard/vcardgui.py:18 +msgid "Name" +msgstr "" + +#: src/crashgui.py:11 +msgid "Digsby Crash Report" +msgstr "" + +#: src/crashgui.py:12 +msgid "Digsby appears to have crashed." +msgstr "" + +#: src/crashgui.py:13 +msgid "If you can, please describe what you were doing before it crashed." +msgstr "" + +#: src/crashgui.py:15 +msgid "&Send Crash Report" +msgstr "" + +#: src/digsby/DigsbyProtocol.py:55 +msgid "Synchronizing Preferences..." +msgstr "" + +#: src/digsby/DigsbyProtocol.py:57 +msgid "Synchronizing..." +msgstr "" + +#: src/digsby/videochat.py:28 +msgid "Audio/Video Call with {name}" +msgstr "" + +#: src/digsby/videochat.py:91 +#, python-format +msgid "Join me in an audio/video call: %s" +msgstr "" + +#: src/digsby/videochat.py:96 +msgid "You have invited {name} to an audio/video chat." +msgstr "" + +#: src/digsby/videochat.py:111 +msgid "Audio/Video chat is currently unavailable." +msgstr "" + +#: src/digsby/videochat.py:179 +msgid "Audio/Video call ended by other party." +msgstr "" + +#: src/digsbysplash.py:27 +msgid "&Sign In" +msgstr "" + +#: src/digsbysplash.py:41 src/digsbysplash.py:739 +msgid "We are upgrading Digsby. Please try connecting again in a few minutes." +msgstr "" + +#: src/digsbysplash.py:42 src/digsbysplash.py:741 +msgid "Could not contact remote server. Check your network configuration." +msgstr "" + +#: src/digsbysplash.py:46 +msgid "Invalid Digsby Username" +msgstr "" + +#: src/digsbysplash.py:322 +msgid "Update Successful" +msgstr "" + +#: src/digsbysplash.py:499 src/digsbysplash.py:779 +msgid "Loading..." +msgstr "" + +#: src/digsbysplash.py:541 +msgid "Login error!" +msgstr "" + +#: src/digsbysplash.py:595 +msgid "Your password will be saved on your portable device." +msgstr "" + +#: src/digsbysplash.py:596 +msgid "Anyone with access to this device will be able to log into your Digsby account. Are you sure you want to save your password?" +msgstr "" + +#: src/digsbysplash.py:601 +msgid "Security Information" +msgstr "" + +#: src/digsbysplash.py:660 +msgid "Please make sure you have entered your Digsby username and password correctly." +msgstr "" + +#: src/digsbysplash.py:661 +msgid "If you need an account or forgot your password, use the links on the login screen." +msgstr "" + +#: src/digsbysplash.py:667 +msgid "" +"Please check your Internet connection and make sure a firewall isn't blocking Digsby.\n" +"If you connect to the Internet through a proxy server,\n" +"click the \"Connection Settings\" link to set up the proxy.\n" +"If you are still unable to connect, email bugs@digsby.com for technical support." +msgstr "" + +#: src/digsbysplash.py:769 +msgid "Register a Digsby Account" +msgstr "" + +#: src/gui/accountdialog.py:38 +#: src/plugins/digsby_service_editor/default_ui.py:583 +msgid "System Default" +msgstr "" + +#: src/gui/accountdialog.py:39 +#: src/plugins/digsby_service_editor/default_ui.py:584 +msgid "Other Mail Client..." +msgstr "" + +#: src/gui/accountdialog.py:40 +#: src/plugins/digsby_service_editor/default_ui.py:585 +msgid "Launch URL..." +msgstr "" + +#: src/gui/accountdialog.py:310 src/gui/accountdialog.py:393 +#: src/gui/contactdialogs.py:172 src/gui/pref/iconeditor.py:552 +#: src/gui/status.py:539 src/plugins/digsby_service_editor/service_editor.py:87 +#: src/plugins/twitter/twitter_gui.py:120 +#: src/plugins/twitter/twitter_gui.py:356 +msgid "&Save" +msgstr "" + +#: src/gui/accountdialog.py:315 src/gui/contactdialogs.py:91 +#: src/gui/contactdialogs.py:180 src/gui/imwin/imtabs.py:125 +#: src/gui/pref/iconeditor.py:554 src/gui/proxydialog.py:234 +#: src/gui/status.py:543 src/plugins/digsby_service_editor/service_editor.py:90 +#: src/plugins/twitter/twitter_gui.py:129 +msgid "&Cancel" +msgstr "" + +#: src/gui/accountdialog.py:321 +#: src/plugins/digsby_service_editor/service_editor.py:222 +msgid "That account already exists." +msgstr "" + +#: src/gui/accountdialog.py:355 +msgid "If checked, this account will automatically sign in when Digsby starts." +msgstr "" + +#: src/gui/accountdialog.py:361 +#: src/plugins/digsby_service_editor/default_ui.py:211 +msgid "&Register New Account" +msgstr "" + +#: src/gui/accountdialog.py:393 +msgid "&Register" +msgstr "" + +#: src/gui/accountdialog.py:580 +msgid "Check for new mail every {n} minutes" +msgstr "" + +#: src/gui/accountdialog.py:591 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "" +msgstr[1] "" + +#: src/gui/accountdialog.py:607 +#: src/plugins/digsby_service_editor/default_ui.py:73 +#: src/plugins/digsby_service_editor/default_ui.py:385 +msgid "Mail Client:" +msgstr "" + +#: src/gui/accountdialog.py:626 +#: src/plugins/digsby_service_editor/default_ui.py:397 +msgid "SMTP username/password are the same as {servertype}" +msgstr "" + +#: src/gui/accountdialog.py:627 +#: src/plugins/digsby_service_editor/default_ui.py:398 +msgid "Log on using:" +msgstr "" + +#: src/gui/accountdialog.py:753 +msgid "{show_topic}:" +msgstr "" + +#: src/gui/accountdialog.py:783 +msgid "Custom ({mailclient_name})" +msgstr "" + +#: src/gui/accountdialog.py:812 +#: src/plugins/digsby_service_editor/default_ui.py:629 +msgid "Please choose a mail client" +msgstr "" + +#: src/gui/accountdialog.py:860 +#: src/plugins/digsby_service_editor/default_ui.py:406 +msgid "Username:" +msgstr "" + +#: src/gui/accountdialog.py:862 +#: src/plugins/digsby_service_editor/default_ui.py:407 +msgid "Password:" +msgstr "" + +#: src/gui/accountdialog.py:921 +msgid "&Display Name:" +msgstr "" + +#: src/gui/accountdialog.py:1003 +#: src/plugins/digsby_service_editor/default_ui.py:265 +msgid "SMTP Server:" +msgstr "" + +#: src/gui/accountdialog.py:1005 src/gui/accountdialog.py:1026 +#: src/plugins/digsby_service_editor/default_ui.py:96 +msgid "Port:" +msgstr "" + +#: src/gui/accountdialog.py:1024 +msgid "Host:" +msgstr "" + +#: src/gui/accountdialog.py:1031 src/gui/infobox/htmlgeneration.py:237 +#: src/plugins/digsby_service_editor/default_ui.py:333 +msgid "Resource:" +msgstr "" + +#: src/gui/accountdialog.py:1033 +#: src/plugins/digsby_service_editor/default_ui.py:333 +msgid "Priority:" +msgstr "" + +#: src/gui/accountdialog.py:1038 +#: src/plugins/digsby_service_editor/default_ui.py:338 +msgid "Data Proxy:" +msgstr "" + +#: src/gui/accountdialog.py:1119 +#: src/plugins/digsby_service_editor/default_ui.py:665 +msgid "Enter the URL that will be launched when you click \"Inbox\" for this email account." +msgstr "" + +#: src/gui/accountdialog.py:1120 +#: src/plugins/digsby_service_editor/default_ui.py:666 +msgid "Enter the URL that will be launched when you click \"Compose\" for this email account." +msgstr "" + +#: src/gui/accountdialog.py:1123 +#: src/plugins/digsby_service_editor/default_ui.py:669 +msgid "Launch URL" +msgstr "" + +#: src/gui/accountdialog.py:1136 +#: src/plugins/digsby_service_editor/default_ui.py:682 +msgid "Enter a URL for the Inbox" +msgstr "" + +#: src/gui/accountdialog.py:1139 +#: src/plugins/digsby_service_editor/default_ui.py:685 +msgid "Enter a URL for the Compose window" +msgstr "" + +#: src/gui/accountslist.py:36 src/gui/pref/pg_accounts.py:627 +#: src/gui/pref/pg_privacy.py:269 src/gui/pref/pg_privacy.py:544 +#: src/gui/pref/pg_privacy.py:718 src/gui/pref/pg_privacy.py:720 +#: src/gui/status.py:740 src/gui/widgetlist.py:56 +#: src/plugins/twitter/twitter_gui.py:1947 +msgid "Edit" +msgstr "" + +#: src/gui/accountslist.py:128 src/gui/accountslist.py:309 +#: src/gui/accountslist.py:406 src/gui/anylists.py:308 src/gui/status.py:728 +#: src/gui/uberwidgets/connectionlist.py:455 src/gui/widgetlist.py:42 +#: src/plugins/twitter/twitter_gui.py:1382 +msgid "&Edit" +msgstr "" + +#: src/gui/accountslist.py:129 src/gui/accountslist.py:310 +#: src/gui/accountslist.py:407 src/gui/anylists.py:309 +#: src/gui/filetransfer/filetransferlist.py:476 src/gui/status.py:729 +msgid "&Remove" +msgstr "" + +#: src/gui/accountslist.py:241 +#: src/plugins/digsby_service_editor/service_editor.py:211 +#: src/plugins/provider_jabber/jabber_gui.py:85 +msgid "Are you sure you want to delete account \"{name}\"?" +msgstr "" + +#: src/gui/accountslist.py:242 +#: src/plugins/digsby_service_editor/service_editor.py:212 +#: src/plugins/provider_jabber/jabber_gui.py:86 +msgid "Delete Account" +msgstr "" + +#: src/gui/accountslist.py:340 +msgid "Are you sure you want to delete social network account \"{name}\"?" +msgstr "" + +#: src/gui/accountslist.py:341 +msgid "Delete Social Network Account" +msgstr "" + +#: src/gui/accountslist.py:440 +msgid "Are you sure you want to delete email account \"{account_name}\"?" +msgstr "" + +#: src/gui/accountslist.py:441 +msgid "Delete Email Account" +msgstr "" + +#: src/gui/accountwizard.py:31 +msgid "Digsby Setup Wizard" +msgstr "" + +#: src/gui/accountwizard.py:40 +msgid "Welcome to Digsby!" +msgstr "" + +#: src/gui/accountwizard.py:116 +msgid "Quick Access to Newsfeeds" +msgstr "" + +#: src/gui/accountwizard.py:117 +msgid "" +"\n" +"You can access social network and email newsfeeds by clicking their icons in the tray.\n" +"\n" +"Double click to update your status (social networks) or launch your inbox (email accounts).\n" +" \n" +msgstr "" + +#: src/gui/addcontactdialog.py:200 src/gui/chatgui.py:15 +#: src/gui/imdialogs.py:68 +msgid "No Connections" +msgstr "" + +#: src/gui/addcontactdialog.py:214 src/gui/pref/prefcontrols.py:1050 +#: src/tests/testgui/uberdemos/UberComboXTDemo.py:34 +msgid "Add" +msgstr "" + +#: src/gui/addcontactdialog.py:228 +msgid "Contact Type:" +msgstr "" + +#: src/gui/addcontactdialog.py:232 +msgid "Screen Name:" +msgstr "" + +#: src/gui/addcontactdialog.py:240 src/gui/contactdialogs.py:164 +msgid "Alias:" +msgstr "" + +#: src/gui/addcontactdialog.py:244 +msgid "In Group:" +msgstr "" + +#: src/gui/addcontactdialog.py:249 +msgid "On Accounts:" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:46 src/gui/app/menubar.py:38 +#: src/gui/buddylist/buddylistmenu.py:178 +#: src/gui/buddylist/buddylistmenu.py:307 +msgid "Status Panel" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:47 src/gui/buddylist/buddylistframe.py:595 +#: src/gui/buddylist/buddylistmenu.py:308 src/gui/pref/prefstrings.py:17 +#: src/main.py:504 +msgid "Buddy List" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:48 src/gui/buddylist/buddylistmenu.py:309 +msgid "Email Accounts" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:49 src/gui/buddylist/buddylistmenu.py:310 +msgid "Social Networks" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:50 src/gui/buddylist/buddylistmenu.py:311 +msgid "Connections" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:150 src/gui/app/menubar.py:11 +#: src/gui/buddylist/buddylistmenu.py:297 +msgid "&Digsby" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:152 src/gui/app/menubar.py:56 +#: src/gui/buddylist/buddylistmenu.py:243 +msgid "&Sort By" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:154 src/gui/app/menubar.py:32 +#: src/gui/buddylist/buddylistmenu.py:302 +msgid "&View" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:175 src/gui/app/menubar.py:24 +#: src/gui/buddylist/buddylistmenu.py:133 +#: src/gui/buddylist/buddylistmenu.py:150 +msgid "&Rename Selection" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:176 src/gui/buddylist/buddylistmenu.py:151 +msgid "&Delete Selection" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:180 src/gui/buddylist/buddylistmenu.py:155 +msgid "&Rename {name}" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:181 src/gui/buddylist/buddylistmenu.py:156 +msgid "&Delete {name}" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:312 src/gui/buddylist/buddylistmenu.py:172 +msgid "You can bring back the menubar by right clicking on the digsby icon in the task tray." +msgstr "" + +#: src/gui/app/mainMenuEvents.py:314 src/gui/buddylist/buddylistmenu.py:174 +msgid "Hide Menu Bar" +msgstr "" + +#: src/gui/app/mainMenuEvents.py:330 src/gui/buddylist/buddylistmenu.py:322 +#: src/gui/visuallisteditor.py:286 +msgid "Arrange Panels" +msgstr "" + +#: src/gui/app/menubar.py:14 src/gui/buddylist/buddylistmenu.py:109 +msgid "&New Status Message..." +msgstr "" + +#: src/gui/app/menubar.py:15 src/gui/buddylist/buddylistmenu.py:110 +msgid "&Edit Status Messages..." +msgstr "" + +#: src/gui/app/menubar.py:17 src/gui/buddylist/buddylistmenu.py:122 +msgid "My Status" +msgstr "" + +#: src/gui/app/menubar.py:18 src/gui/buddylist/buddylistmenu.py:123 +msgid "My &Accounts..." +msgstr "" + +#: src/gui/app/menubar.py:21 +msgid "&New IM..." +msgstr "" + +#: src/gui/app/menubar.py:22 +msgid "Add &Contact..." +msgstr "" + +#: src/gui/app/menubar.py:23 +msgid "Add &Group..." +msgstr "" + +#: src/gui/app/menubar.py:25 src/gui/buddylist/buddylistmenu.py:134 +msgid "&Delete Selection..." +msgstr "" + +#: src/gui/app/menubar.py:27 +msgid "Sign &Off Digsby" +msgstr "" + +#: src/gui/app/menubar.py:29 src/gui/buddylist/buddylistmenu.py:138 +#: src/gui/trayicons.py:197 +msgid "E&xit Digsby" +msgstr "" + +#: src/gui/app/menubar.py:33 src/gui/buddylist/buddylistmenu.py:165 +msgid "&Always On Top" +msgstr "" + +#: src/gui/app/menubar.py:34 +msgid "Skins..." +msgstr "" + +#: src/gui/app/menubar.py:37 src/gui/buddylist/buddylistmenu.py:168 +msgid "&Menu Bar" +msgstr "" + +#: src/gui/app/menubar.py:39 src/gui/buddylist/buddylistmenu.py:179 +msgid "Arrange &Panels..." +msgstr "" + +#: src/gui/app/menubar.py:41 +msgid "Show &Mobile Contacts" +msgstr "" + +#: src/gui/app/menubar.py:42 +msgid "Show &Offline Contacts" +msgstr "" + +#: src/gui/app/menubar.py:43 +msgid "&Group Offline Contacts" +msgstr "" + +#: src/gui/app/menubar.py:44 src/gui/buddylist/buddylistmenu.py:185 +msgid "&Hide Offline Groups" +msgstr "" + +#: src/gui/app/menubar.py:49 src/gui/buddylist/buddylistmenu.py:208 +msgid "&None" +msgstr "" + +#: src/gui/app/menubar.py:50 src/gui/buddylist/buddylistmenu.py:209 +msgid "&Status" +msgstr "" + +#: src/gui/app/menubar.py:51 src/gui/buddylist/buddylistmenu.py:210 +msgid "N&ame" +msgstr "" + +#: src/gui/app/menubar.py:52 src/gui/buddylist/buddylistmenu.py:211 +msgid "&Log Size" +msgstr "" + +#: src/gui/app/menubar.py:53 src/gui/buddylist/buddylistmenu.py:212 +msgid "Ser&vice" +msgstr "" + +#: src/gui/app/menubar.py:54 src/gui/buddylist/buddylistmenu.py:222 +#: src/gui/buddylist/buddylistmenu.py:230 +msgid "Advan&ced..." +msgstr "" + +#: src/gui/app/menubar.py:59 src/gui/buddylist/buddylistmenu.py:303 +msgid "&Tools" +msgstr "" + +#: src/gui/app/menubar.py:60 src/gui/trayicons.py:192 +msgid "&Preferences..." +msgstr "" + +#: src/gui/app/menubar.py:61 +msgid "&File Transfer History" +msgstr "" + +#: src/gui/app/menubar.py:62 +msgid "&Chat History" +msgstr "" + +#: src/gui/app/menubar.py:65 src/gui/buddylist/buddylistmenu.py:304 +msgid "&Help" +msgstr "" + +#: src/gui/app/menubar.py:66 src/gui/buddylist/buddylistmenu.py:265 +msgid "&Documentation" +msgstr "" + +#: src/gui/app/menubar.py:67 src/gui/buddylist/buddylistmenu.py:266 +msgid "Support &Forums" +msgstr "" + +#: src/gui/app/menubar.py:69 src/gui/buddylist/buddylistmenu.py:268 +msgid "&Submit Bug Report" +msgstr "" + +#: src/gui/app/menubar.py:70 src/gui/buddylist/buddylistmenu.py:269 +msgid "Su&ggest a Feature" +msgstr "" + +#: src/gui/app/menubar.py:78 +msgid "&Invite Your Friends" +msgstr "" + +#: src/gui/app/menubar.py:79 src/plugins/digsby_about/aboutdialog.py:132 +msgid "&About Digsby" +msgstr "" + +#: src/gui/authorizationdialog.py:21 +msgid "Authorize Contact" +msgstr "" + +#: src/gui/authorizationdialog.py:41 +msgid "Authorize and Add" +msgstr "" + +#: src/gui/authorizationdialog.py:42 +msgid "Authorize" +msgstr "" + +#: src/gui/authorizationdialog.py:43 +msgid "Deny" +msgstr "" + +#: src/gui/browser/jsconsole.py:15 +msgid "Javascript Console" +msgstr "" + +#: src/gui/browser/jsconsole.py:47 src/gui/browser/jsconsole.py:48 +msgid "Clear" +msgstr "" + +#: src/gui/browser/webkit/webkiteditsource.py:41 +msgid "&Copy" +msgstr "" + +#: src/gui/browser/webkit/webkiteditsource.py:72 +msgid "&Open in Browser" +msgstr "" + +#: src/gui/browser/webkit/webkiteditsource.py:74 +msgid "Launches browser in pref \"debug.message_area.debug_browser\"" +msgstr "" + +#: src/gui/buddylist/accounttray.py:231 +msgid "{account.offline_reason} ({account.email_address})" +msgstr "" + +#: src/gui/buddylist/accounttray.py:238 +msgid "{count} unread message ({account.email_address})" +msgid_plural "{count} unread messages ({account.email_address})" +msgstr[0] "" +msgstr[1] "" + +#: src/gui/buddylist/accounttray.py:250 +msgid "{account.offline_reason} ({account.name})" +msgstr "" + +#: src/gui/buddylist/accounttray.py:252 +msgid "{count} new alert ({account.name})" +msgid_plural "{count} new alerts ({account.name})" +msgstr[0] "" +msgstr[1] "" + +#: src/gui/buddylist/buddylist.py:190 +msgid "BuddyList Popup" +msgstr "" + +#: src/gui/buddylist/buddylist.py:240 src/gui/pref/pg_accounts.py:789 +msgid "Add Accounts" +msgstr "" + +#: src/gui/buddylist/buddylist.py:253 +msgid "Need Help?" +msgstr "" + +#: src/gui/buddylist/buddylist.py:405 +msgid "&Add Group" +msgstr "" + +#: src/gui/buddylist/buddylist.py:658 src/gui/buddylist/buddylist.py:671 +#: src/gui/contactdialogs.py:279 src/gui/contactdialogs.py:306 +msgid "Merge Contacts" +msgstr "" + +#: src/gui/buddylist/buddylist.py:941 +msgid "This buddy can't accept file transfers" +msgstr "" + +#: src/gui/buddylist/buddylist.py:942 src/gui/capabilitiesbar.py:81 +#: src/gui/capabilitiesbar.py:351 src/gui/imwin/imwin_native.py:378 +#: src/gui/imwin/imwinmenu.py:14 +msgid "Send File" +msgstr "" + +#: src/gui/buddylist/buddylist.py:1010 +msgid "&Add Contact" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:125 +msgid "&New IM...\tCtrl+N" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:127 +msgid "New Group C&hat...\tCtrl+Shift+N" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:131 +msgid "Add &Contact...\tCtrl+A" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:132 +msgid "Add &Group...\tCtrl+Shift+A" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:136 +#, python-format +msgid "Sign &Off Digsby (%s)" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:166 +msgid "Skins...\tCtrl+S" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:181 +msgid "Show &Mobile Contacts\tCtrl+M" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:182 +msgid "Show &Offline Contacts\tCtrl+O" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:183 +msgid "&Group Offline Contacts\tCtrl+G" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:242 +msgid "&Group By" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:250 +msgid "&Preferences...\tCtrl+P" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:251 +msgid "Buddy List &Search\tCtrl+F" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:252 +msgid "&File Transfer History\tCtrl+J" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:258 +msgid "&Chat History\tCtrl+H" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:273 +msgid "Show Debug Console" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:276 +msgid "Su&pport Digsby" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:299 src/gui/pref/iconeditor.py:546 +msgid "&File" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:331 +msgid "&Sign Off" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:332 +msgid "&Sign On" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:335 +msgid "&Edit Account..." +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:405 +msgid "&Stealth Settings" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:411 +msgid "Selection" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:414 +msgid "Appear Online to {name}" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:416 +msgid "Appear Offline to {name}" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:418 +msgid "Appear Permanently Offline to {name}" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:423 +msgid "Learn More (URL)" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:463 +msgid "Chat with Resource" +msgstr "" + +#: src/gui/buddylist/buddylistmenu.py:480 +msgid "&Remove from Merged Contact" +msgstr "" + +#: src/gui/buddylist/renderers.py:831 src/gui/searchgui.py:122 +msgid "Options..." +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:41 +msgid "Use this tool to submit a diagnostic log right after you experience a bug" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:44 +#: src/gui/bugreporter/bugreportergui.py:80 +msgid "This diagnostic log file does not contain personal data such as the content of sent/received IMs, the content of emails, and the content of social network newsfeeds except where it directly pertains to an error." +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:51 +#: src/gui/bugreporter/bugreportergui.py:83 +msgid "Please describe the bug in as much detail as possible. Include information such as what you were doing when the bug occurred and exactly what goes wrong." +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:61 +msgid "Can you consistently reproduce this bug?" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:63 +msgid "&Yes" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:64 +msgid "&No" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:65 +msgid "&Don't Know" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:68 +msgid "If this is a visual bug, please attach a screenshot to this report." +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:69 +#: src/gui/bugreporter/bugreportergui.py:104 +#: src/gui/bugreporter/bugreportergui.py:141 +msgid "Take Screenshot" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:73 +msgid "Taking Screenshot in {secs}" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:139 +msgid "Remove Screenshot" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:199 +msgid "Submit Bug Report - Digsby" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:212 +msgid "Submit" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:225 +msgid "Please enter a description of the bug." +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:226 +msgid "Include as much information as possible about what went wrong and how to reproduce the issue." +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:230 src/gui/tracebackdialog.py:14 +msgid "Send Bug Report" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:255 +msgid "Submit Bug Report - Screenshot - Digsby" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:277 +msgid "Blank out non Digsby windows" +msgstr "" + +#: src/gui/bugreporter/bugreportergui.py:284 +msgid "Is it OK to send this screenshot to digsby.com?" +msgstr "" + +#: src/gui/capabilitiesbar.py:24 +msgid "Info" +msgstr "" + +#: src/gui/capabilitiesbar.py:24 +msgid "View buddy information" +msgstr "" + +#: src/gui/capabilitiesbar.py:25 +msgid "Instant message this buddy" +msgstr "" + +#: src/gui/capabilitiesbar.py:25 src/gui/imdialogs.py:43 +msgid "IM" +msgstr "" + +#: src/gui/capabilitiesbar.py:26 +msgid "Video" +msgstr "" + +#: src/gui/capabilitiesbar.py:26 +msgid "Start an audio/video chat" +msgstr "" + +#: src/gui/capabilitiesbar.py:27 +msgid "Files" +msgstr "" + +#: src/gui/capabilitiesbar.py:27 +msgid "Send files to this buddy" +msgstr "" + +#: src/gui/capabilitiesbar.py:29 +msgid "Send email" +msgstr "" + +#: src/gui/capabilitiesbar.py:29 src/gui/vcard/vcardgui.py:138 +msgid "Email" +msgstr "" + +#: src/gui/capabilitiesbar.py:30 +msgid "SMS" +msgstr "" + +#: src/gui/capabilitiesbar.py:30 +msgid "Send SMS messages" +msgstr "" + +#: src/gui/capabilitiesbar.py:107 +msgid "Group Chat" +msgstr "" + +#: src/gui/capabilitiesbar.py:110 +msgid "View Past Chats" +msgstr "" + +#: src/gui/capabilitiesbar.py:114 +msgid "Alert Me When..." +msgstr "" + +#: src/gui/capabilitiesbar.py:115 +msgid "Block" +msgstr "" + +#: src/gui/capabilitiesbar.py:138 +msgid "To:" +msgstr "" + +#: src/gui/capabilitiesbar.py:141 +msgid "From:" +msgstr "" + +#: src/gui/capabilitiesbar.py:300 +msgid "Unblock {name}" +msgstr "" + +#: src/gui/capabilitiesbar.py:302 +msgid "Block {name}" +msgstr "" + +#: src/gui/capabilitiesbar.py:354 src/gui/imwin/imwin_native.py:379 +msgid "Transfer History" +msgstr "" + +#: src/gui/capabilitiesbar.py:377 +msgid "Icons Only" +msgstr "" + +#: src/gui/capabilitiesbar.py:378 +msgid "Text Only" +msgstr "" + +#: src/gui/capabilitiesbar.py:379 +msgid "Icons Next to Text" +msgstr "" + +#: src/gui/capabilitiesbar.py:380 +msgid "Icons Above Text" +msgstr "" + +#: src/gui/capabilitiesbar.py:389 +msgid "Hide Actions Bar" +msgstr "" + +#: src/gui/chatgui.py:90 +msgid "Join Chat Room" +msgstr "" + +#: src/gui/chatgui.py:98 src/hub.py:155 +msgid "Join Chat" +msgstr "" + +#: src/gui/chatgui.py:111 +msgid "Account:" +msgstr "" + +#: src/gui/chatgui.py:117 +msgid "Invite:" +msgstr "" + +#: src/gui/chatgui.py:155 +msgid "Server:" +msgstr "" + +#: src/gui/chatgui.py:160 +msgid "Room Name:" +msgstr "" + +#: src/gui/contactdialogs.py:37 +msgid "Would you like to send \"{files[0]}\" to {buddy_name:s}?" +msgid_plural "Would you like to send {num_files:d} files to {buddy_name:s}?" +msgstr[0] "" +msgstr[1] "" + +#: src/gui/contactdialogs.py:41 +msgid "Send Files" +msgstr "" + +#: src/gui/contactdialogs.py:78 +msgid "Contact &Name:" +msgstr "" + +#: src/gui/contactdialogs.py:81 +msgid "Accoun&t:" +msgstr "" + +#: src/gui/contactdialogs.py:87 +msgid "&Add" +msgstr "" + +#: src/gui/contactdialogs.py:158 +msgid "Would you like to merge these contacts?" +msgstr "" + +#: src/gui/contactdialogs.py:161 +msgid "They will appear as one item on your buddy list." +msgstr "" + +#: src/gui/contactdialogs.py:182 +msgid "Drag and drop to rearrange:" +msgstr "" + +#: src/gui/filetransfer/filetransferlist.py:234 +#: src/gui/filetransfer/filetransferlist.py:243 +msgid "File not found" +msgstr "" + +#: src/gui/filetransfer/filetransferlist.py:461 +#: src/gui/filetransfer/filetransferlist.py:599 src/gui/pref/pg_files.py:11 +#: src/gui/pref/prefstrings.py:19 +msgid "File Transfers" +msgstr "" + +#: src/gui/filetransfer/filetransferlist.py:474 +msgid "Open &Containing Folder" +msgstr "" + +#: src/gui/filetransfer/filetransferlist.py:542 +msgid "Clean Up" +msgstr "" + +#: src/gui/filetransfer/filetransferlist.py:642 +msgid "file" +msgid_plural "files" +msgstr[0] "" +msgstr[1] "" + +#: src/gui/helpdigsby.py:4 +msgid "Keep Digsby Free" +msgstr "" + +#: src/gui/helpdigsby.py:5 +msgid "Digsby will use your computer's free time using it to conduct both free and paid research." +msgstr "" + +#: src/gui/helpdigsby.py:14 src/gui/pref/pg_privacy.py:342 +#: src/gui/pref/pg_research.py:129 +msgid "Options" +msgstr "" + +#: src/gui/helpdigsby.py:15 src/gui/imagedialog.py:11 +msgid "OK" +msgstr "" + +#: src/gui/imagedialog.py:71 +msgid "Send Image" +msgstr "" + +#: src/gui/imagedialog.py:72 +msgid "&Send Image" +msgstr "" + +#: src/gui/imagedialog.py:73 +msgid "&Don't Send" +msgstr "" + +#: src/gui/imdialogs.py:19 +msgid "New IM" +msgstr "" + +#: src/gui/imdialogs.py:30 +msgid "To" +msgstr "" + +#: src/gui/imdialogs.py:36 +msgid "From" +msgstr "" + +#: src/gui/imwin/imhub.py:483 +msgid "Group Chat ({chat.chat_member_count:d})" +msgstr "" + +#: src/gui/imwin/imhub.py:484 +msgid "{alias:s}: {message:s}" +msgstr "" + +#: src/gui/imwin/imtabs.py:57 +msgid "Close IM Window" +msgstr "" + +#: src/gui/imwin/imtabs.py:58 +msgid "Warn me when I attempt to close multiple conversations" +msgstr "" + +#: src/gui/imwin/imtabs.py:59 +msgid "You are about to close {num_tabs} conversations. Are you sure you want to continue?" +msgstr "" + +#: src/gui/imwin/imtabs.py:60 +msgid "Close &tabs" +msgstr "" + +#: src/gui/imwin/imtabs.py:757 +msgid "IM Windows" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:209 +msgid "Please add an email address for this buddy by clicking the \"To:\" box." +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:211 +msgid "Compose email to {name}" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:221 +msgid "Message Sent" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:230 +msgid "Failed to Send Email" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:234 +msgid "Sending..." +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:270 +msgid "Please add an SMS number first." +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:271 src/gui/imwin/imwin_ctrl.py:274 +msgid "Send SMS Message" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:273 +msgid "You are not signed in to any accounts which can send SMS messages." +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:288 +msgid "The error message received was:" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:292 +msgid "There was an error in sending your SMS message." +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:293 +msgid "Send SMS Message Error" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:299 +msgid "Only the first {max_length:d} characters of your message can be sent over SMS:" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:300 +msgid "Do you want to send this message now?" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:303 +msgid "Send SMS - Character Limit" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:461 +msgid "Reconnected" +msgstr "" + +#: src/gui/imwin/imwin_ctrl.py:689 +msgid "You can only have one audio/video call at a time." +msgstr "" + +#: src/gui/imwin/imwin_email.py:49 +msgid "Edit in {client}..." +msgstr "" + +#: src/gui/imwin/imwin_email.py:95 +msgid "Subject:" +msgstr "" + +#: src/gui/imwin/imwin_email.py:116 +#: src/gui/uberwidgets/formattedinput2/iminput.py:67 +msgid "Send" +msgstr "" + +#: src/gui/imwin/imwin_gui.py:43 +msgid "Typing" +msgstr "" + +#: src/gui/imwin/imwin_gui.py:44 +msgid "Entered Text" +msgstr "" + +#: src/gui/imwin/imwin_gui.py:75 +msgid "Digsby Announcement" +msgstr "" + +#: src/gui/imwin/imwin_gui.py:265 +#, python-format +msgid "Would you like to send this image to %s?" +msgstr "" + +#: src/gui/imwin/imwin_native.py:417 src/gui/uberwidgets/formattedinput.py:758 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:137 +msgid "Choose a background color" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:318 +msgid "{username} disconnected" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:321 +msgid "now sending from: {username}" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:342 +msgid "Add..." +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:584 +msgid "Please enter a valid SMS number (ie: 555-555-5555 or 5555555555)" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:585 +msgid "Invalid SMS Number" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:593 +msgid "That SMS number is already in this buddy's list." +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:594 +msgid "Add SMS Number" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:634 +msgid "Accounts..." +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:673 +msgid "Please enter a valid email address (ie: john123@digsby.com)" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:674 +msgid "Invalid Email Address" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:677 +msgid "That email is already registered with this buddy." +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:678 +msgid "Add Email Address" +msgstr "" + +#: src/gui/imwin/imwin_tofrom.py:729 +msgid "Add Email Account" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:12 +msgid "Buddy Info" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:13 +msgid "Send IM" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:16 +msgid "Send Email" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:17 +msgid "Send SMS" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:40 src/gui/pastbrowser.py:414 +#: src/gui/toolbox/toolbox.py:1448 src/gui/uberwidgets/formattedinput.py:95 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:105 +#: src/plugins/twitter/twitter.py:1211 src/plugins/twitter/twitter_gui.py:1496 +msgid "Copy" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:41 src/gui/toolbox/toolbox.py:1451 +#: src/gui/uberwidgets/formattedinput.py:96 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:106 +msgid "Paste" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:44 src/gui/imwin/imwinmenu.py:123 +msgid "Show &Actions Bar" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:45 src/gui/imwin/imwinmenu.py:124 +msgid "Show &Formatting Bar" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:46 +msgid "Show Send Button" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:64 src/plugins/twitter/twitter_gui.py:50 +msgid "&Keep on Top" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:85 +msgid "Copy\tCtrl+C" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:94 +msgid "Copy &Link" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:96 +msgid "Paste\tCtrl+V" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:100 +msgid "Edit Source" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:103 +msgid "&Javascript Console" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:108 src/plugins/twitter/twitter_gui.py:1507 +msgid "&Increase Text Size\tCtrl+=" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:110 src/plugins/twitter/twitter_gui.py:1508 +msgid "&Decrease Text Size\tCtrl+-" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:113 src/plugins/twitter/twitter_gui.py:1510 +msgid "&Reset Text Size\tCtrl+0" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:116 +msgid "&Text Size" +msgstr "" + +#: src/gui/imwin/imwinmenu.py:122 +msgid "Show &Room List" +msgstr "" + +#: src/gui/imwin/roomlist.py:405 +msgid "Invite Buddy" +msgstr "" + +#: src/gui/imwin/roomlist.py:524 +msgid "Do you want to invite {name} to this chat?" +msgstr "" + +#: src/gui/imwin/roomlist.py:525 +msgid "Chat Invite" +msgstr "" + +#: src/gui/imwin/styles/adiummsgstyles.py:193 src/gui/skin/skintree.py:427 +#: src/msn/MSNBuddy.py:563 +msgid "(none)" +msgstr "" + +#: src/gui/infobox/emailpanels.py:596 +msgid "(No Subject)" +msgstr "" + +#: src/gui/infobox/emailpanels.py:604 +msgid "(No Preview)" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:137 +msgid "No Profile" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:235 +msgid "Subscription:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:238 src/gui/infobox/htmlgeneration.py:395 +#: src/plugins/facebook/res/status.py.xml:3 +msgid "Status:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:339 +#: src/gui/pref/pg_text_conversations.py:191 src/yahoo/yahoobuddy.py:154 +msgid "Location:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:344 +msgid "IP Address:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:351 +msgid "Time on Page:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:374 +msgid "Online:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:381 +msgid "Idle:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:387 +msgid "Away:" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:396 +msgid "{status} + Idle" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:416 src/plugins/msim/res/content.tenjin:77 +msgid "Hide Profile" +msgstr "" + +#: src/gui/infobox/htmlgeneration.py:416 src/plugins/msim/res/content.tenjin:79 +msgid "Show Profile" +msgstr "" + +#: src/gui/infobox/infobox.py:1062 +#, python-format +msgid "Unblock %s" +msgstr "" + +#: src/gui/infobox/infobox.py:1064 +#, python-format +msgid "Block %s" +msgstr "" + +#: src/gui/infobox/infobox.py:1365 +msgid "Error generating content" +msgstr "" + +#: src/gui/infobox/infobox.py:1564 +msgid "No additional information" +msgstr "" + +#: src/gui/infobox/infobox.py:1792 +msgid "InfoBox" +msgstr "" + +#: src/gui/input/inputmanager.py:391 +msgid "Key Debugger" +msgstr "" + +#: src/gui/native/win/jumplist.py:51 +msgid "Chat with {name}" +msgstr "" + +#: src/gui/native/win/jumplist.py:124 +msgid "Change IM Status to {status}" +msgstr "" + +#: src/gui/native/win/jumplist.py:139 +msgid "Set your status on multiple networks" +msgstr "" + +#: src/gui/native/win/jumplist.py:139 src/gui/social_status_dialog.py:830 +msgid "Set Global Status" +msgstr "" + +#: src/gui/native/win/jumplist.py:140 +msgid "Open the New IM window" +msgstr "" + +#: src/gui/native/win/jumplist.py:140 +msgid "New IM..." +msgstr "" + +#: src/gui/native/win/jumplist.py:141 +msgid "Open the Digsby Preferences window" +msgstr "" + +#: src/gui/native/win/jumplist.py:141 +msgid "Preferences..." +msgstr "" + +#: src/gui/native/win/jumplist.py:150 +msgid "Close all windows and exit" +msgstr "" + +#: src/gui/native/win/jumplist.py:150 +msgid "Exit Digsby" +msgstr "" + +#: src/gui/native/win/jumplist.py:164 +msgid "Opens the Preferences Window where you can enable logging" +msgstr "" + +#: src/gui/native/win/jumplist.py:164 +msgid "Enable Chat Logs" +msgstr "" + +#: src/gui/notifications/notificationlist.py:68 +msgid "Sound" +msgstr "" + +#: src/gui/notifications/notificationlist.py:69 +msgid "Popup" +msgstr "" + +#: src/gui/pastbrowser.py:89 +msgid "Group Chats" +msgstr "" + +#: src/gui/pastbrowser.py:301 +msgid "Find" +msgstr "" + +#: src/gui/pastbrowser.py:304 +msgid "Next" +msgstr "" + +#: src/gui/pastbrowser.py:305 +msgid "Prev" +msgstr "" + +#: src/gui/pastbrowser.py:386 src/gui/status.py:374 +msgid "Account" +msgstr "" + +#: src/gui/pastbrowser.py:392 src/gui/pastbrowser.py:470 +msgid "Buddy" +msgstr "" + +#: src/gui/pastbrowser.py:402 +msgid "Date" +msgstr "" + +#: src/gui/pastbrowser.py:449 +msgid "Conversation Log" +msgstr "" + +#: src/gui/pastbrowser.py:466 +msgid "Chats" +msgstr "" + +#: src/gui/pastbrowser.py:558 +msgid "{month}, {day}, {year}" +msgstr "" + +#: src/gui/pastbrowser.py:561 +msgid "There are no chat logs for {specific_day_string}." +msgstr "" + +#: src/gui/pastbrowser.py:563 +msgid "There are no chat logs for {specific_day_string} with {name}." +msgstr "" + +#: src/gui/pastbrowser.py:664 +msgid "There is no chat history for {name} on {service} with {acct}." +msgstr "" + +#: src/gui/pastbrowser.py:673 +msgid "Past Chat Browser" +msgstr "" + +#: src/gui/pref/iconeditor.py:34 +msgid "Scree&n" +msgstr "" + +#: src/gui/pref/iconeditor.py:35 +msgid "Capt&ure" +msgstr "" + +#: src/gui/pref/iconeditor.py:48 +msgid "Set Buddy Icon" +msgstr "" + +#: src/gui/pref/iconeditor.py:430 +msgid "Choose an icon" +msgstr "" + +#: src/gui/pref/iconeditor.py:439 +msgid "Not a valid image:" +msgstr "" + +#: src/gui/pref/iconeditor.py:442 +msgid "Invalid Image" +msgstr "" + +#: src/gui/pref/iconeditor.py:547 +msgid "Cli&pboard" +msgstr "" + +#: src/gui/pref/iconeditor.py:553 src/gui/protocols/jabbergui.py:289 +msgid "C&lear" +msgstr "" + +#: src/gui/pref/pg_accounts.py:617 +msgid "(auto login)" +msgstr "" + +#: src/gui/pref/pg_accounts.py:828 +msgid "My Accounts" +msgstr "" + +#: src/gui/pref/pg_accounts.py:832 +msgid "Account Options" +msgstr "" + +#: src/gui/pref/pg_accounts.py:863 +msgid "buddy list" +msgstr "" + +#: src/gui/pref/pg_accounts.py:864 +msgid "icon tray" +msgstr "" + +#: src/gui/pref/pg_accounts.py:865 +msgid "buddy list and icon tray" +msgstr "" + +#: src/gui/pref/pg_accounts.py:901 +msgid "Show email accounts in:" +msgstr "" + +#: src/gui/pref/pg_accounts.py:902 +msgid "Show social networks in:" +msgstr "" + +#: src/gui/pref/pg_advanced.py:14 +msgid "IM Window" +msgstr "" + +#: src/gui/pref/pg_advanced.py:60 src/gui/pref/pg_advanced.py:94 +msgid "&Use Proxy Server" +msgstr "" + +#: src/gui/pref/pg_advanced.py:68 +msgid "Host" +msgstr "" + +#: src/gui/pref/pg_advanced.py:69 +msgid "Port" +msgstr "" + +#: src/gui/pref/pg_advanced.py:94 +msgid "&Use Global Settings" +msgstr "" + +#: src/gui/pref/pg_advanced.py:130 +msgid "&Protocol:" +msgstr "" + +#: src/gui/pref/pg_appearance.py:39 +msgid "Application Skin" +msgstr "" + +#: src/gui/pref/pg_appearance.py:41 +msgid "Conversation Theme" +msgstr "" + +#: src/gui/pref/pg_appearance.py:44 +msgid "Conversation Preview" +msgstr "" + +#: src/gui/pref/pg_appearance.py:115 +msgid "Skin:" +msgstr "" + +#: src/gui/pref/pg_appearance.py:115 src/gui/pref/pg_appearance.py:203 +msgid "Variant:" +msgstr "" + +#: src/gui/pref/pg_appearance.py:187 +msgid "Show header" +msgstr "" + +#: src/gui/pref/pg_appearance.py:192 +msgid "Show message fonts" +msgstr "" + +#: src/gui/pref/pg_appearance.py:194 +msgid "Show message colors" +msgstr "" + +#: src/gui/pref/pg_appearance.py:201 +msgid "Theme:" +msgstr "" + +#: src/gui/pref/pg_appearance.py:270 +msgid "Fonts:" +msgstr "" + +#: src/gui/pref/pg_appearance.py:275 +msgid "Colors:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:27 +msgid "Sorting and Groups" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:46 +msgid "Autohide when not in &focus" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:51 +msgid "Automatically &dock when near edge of screen" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:57 src/gui/pref/pg_text_conversations.py:42 +msgid "&Keep on top of other applications" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:58 +msgid "Show in taskbar" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:64 src/gui/pref/pg_text_conversations.py:61 +msgid "Window Options" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:81 +msgid "Contact Layout" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:104 src/gui/pref/pg_contact_list.py:110 +msgid "Basic" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:104 src/gui/pref/pg_contact_list.py:110 +#: src/plugins/digsby_service_editor/service_editor.py:58 +msgid "Advanced" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:243 +msgid "Group by:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:244 +msgid "Sort by:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:245 +msgid "Then by:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:353 src/gui/pref/pg_sandbox.py:79 +msgid "Far Left" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:354 src/gui/pref/pg_sandbox.py:80 +msgid "Left" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:355 src/gui/pref/pg_sandbox.py:81 +msgid "Badge (Lower Left)" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:356 src/gui/pref/pg_sandbox.py:82 +msgid "Badge (Lower Right)" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:357 src/gui/pref/pg_sandbox.py:83 +msgid "Right" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:358 src/gui/pref/pg_sandbox.py:84 +msgid "Far Right" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:377 +msgid "Show service icon on:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:379 +msgid "Show status icon on:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:381 +msgid "Show buddy icon on the:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:390 +msgid "Buddy icon size:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:396 +msgid "Buddy padding:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:401 +msgid "Contact name:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:403 +msgid "&Show extra info:" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:406 +msgid "Idle Time" +msgstr "" + +#: src/gui/pref/pg_contact_list.py:407 +msgid "Idle Time + Status" +msgstr "" + +#: src/gui/pref/pg_files.py:13 +msgid "Save &files to:" +msgstr "" + +#: src/gui/pref/pg_files.py:15 +msgid "Create s&ubfolders for each IM account" +msgstr "" + +#: src/gui/pref/pg_files.py:16 +msgid "&Auto-accept all file transfers from contacts on my contact list" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:108 +msgid "&Launch Digsby when this computer starts" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:125 +msgid "&Automatically download software updates" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:126 +msgid "&If connection to IM service is lost, automatically attempt to reconnect" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:127 +msgid "Show trending news articles in social network feeds (powered by OneRiot)" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:131 +msgid "General Options" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:143 +msgid "Profile (AIM Only)" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:147 +msgid "Buddy Icon" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:163 +msgid "Language" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:216 +msgid "Change" +msgstr "" + +#: src/gui/pref/pg_general_profile.py:252 +msgid "&Promote Digsby in my AIM profile" +msgstr "" + +#: src/gui/pref/pg_helpdigsby.py:11 +msgid "&Allow Digsby to conduct research during idle itme" +msgstr "" + +#: src/gui/pref/pg_helpdigsby.py:15 +msgid "Help Digsby" +msgstr "" + +#: src/gui/pref/pg_notifications.py:23 +msgid "bottom right corner" +msgstr "" + +#: src/gui/pref/pg_notifications.py:24 +msgid "bottom left corner" +msgstr "" + +#: src/gui/pref/pg_notifications.py:25 +msgid "top right corner" +msgstr "" + +#: src/gui/pref/pg_notifications.py:26 +msgid "top left corner" +msgstr "" + +#: src/gui/pref/pg_notifications.py:38 +msgid "Enable &pop-up notifications in the {location_dropdown} on monitor {monitor_dropdown}" +msgstr "" + +#: src/gui/pref/pg_notifications.py:69 +msgid "Enable &sounds" +msgstr "" + +#: src/gui/pref/pg_notifications.py:70 src/gui/pref/prefstrings.py:22 +#: src/plugins/facebook/fbgui.py:49 +msgid "Notifications" +msgstr "" + +#: src/gui/pref/pg_notifications.py:77 src/plugins/myspace/msgui.py:49 +msgid "Events" +msgstr "" + +#: src/gui/pref/pg_notifications.py:79 +msgid "Restore Defaults" +msgstr "" + +#: src/gui/pref/pg_notifications.py:93 +msgid "" +"Are you sure you want to restore the default notification set?\n" +"\n" +"All of your notification settings will be lost." +msgstr "" + +#: src/gui/pref/pg_notifications.py:96 +msgid "Restore Default Notifications" +msgstr "" + +#: src/gui/pref/pg_privacy.py:17 +msgid "&Let others know that I am typing" +msgstr "" + +#: src/gui/pref/pg_privacy.py:18 +msgid "&Automatically sign me into websites (e.g., Yahoo! Mail)" +msgstr "" + +#: src/gui/pref/pg_privacy.py:21 +msgid "Global Privacy Options" +msgstr "" + +#: src/gui/pref/pg_privacy.py:144 +msgid "You must be signed in to modify privacy settings." +msgstr "" + +#: src/gui/pref/pg_privacy.py:145 +#, python-format +msgid "Sign in with \"%s\"" +msgstr "" + +#: src/gui/pref/pg_privacy.py:234 +msgid "{title} ({username})" +msgstr "" + +#: src/gui/pref/pg_privacy.py:295 src/gui/pref/pg_privacy.py:518 +msgid "Allow all users to contact me" +msgstr "" + +#: src/gui/pref/pg_privacy.py:296 src/gui/pref/pg_privacy.py:519 +#: src/gui/pref/pg_privacy.py:884 +msgid "Allow only users on my contact list" +msgstr "" + +#: src/gui/pref/pg_privacy.py:297 src/gui/pref/pg_privacy.py:675 +msgid "Allow only users on 'Allow List'" +msgstr "" + +#: src/gui/pref/pg_privacy.py:298 +msgid "Block all users" +msgstr "" + +#: src/gui/pref/pg_privacy.py:299 src/gui/pref/pg_privacy.py:676 +#: src/gui/pref/pg_privacy.py:885 +msgid "Block only users on 'Block List'" +msgstr "" + +#: src/gui/pref/pg_privacy.py:301 +msgid "Only my screen name" +msgstr "" + +#: src/gui/pref/pg_privacy.py:302 +msgid "Only that I have an account" +msgstr "" + +#: src/gui/pref/pg_privacy.py:303 +msgid "Nothing about me" +msgstr "" + +#: src/gui/pref/pg_privacy.py:330 src/gui/pref/pg_privacy.py:556 +#: src/gui/pref/pg_privacy.py:727 src/gui/pref/pg_privacy.py:900 +#: src/gui/pref/pg_privacy.py:948 +msgid "Permissions" +msgstr "" + +#: src/gui/pref/pg_privacy.py:337 +msgid "Allow users who know my email address to find:" +msgstr "" + +#: src/gui/pref/pg_privacy.py:411 src/gui/pref/pg_privacy.py:470 +#: src/gui/pref/pg_privacy.py:542 src/gui/pref/pg_privacy.py:643 +#: src/gui/pref/pg_privacy.py:719 src/gui/pref/pg_privacy.py:840 +msgid "Block List" +msgstr "" + +#: src/gui/pref/pg_privacy.py:494 src/gui/pref/pg_privacy.py:542 +msgid "Visible List" +msgstr "" + +#: src/gui/pref/pg_privacy.py:498 src/gui/pref/pg_privacy.py:542 +msgid "Invisible List" +msgstr "" + +#: src/gui/pref/pg_privacy.py:530 src/gui/pref/pg_privacy.py:934 +msgid "Allow only users on my buddy list to contact me" +msgstr "" + +#: src/gui/pref/pg_privacy.py:531 +msgid "Require authorization before users can add me to their contact list" +msgstr "" + +#: src/gui/pref/pg_privacy.py:532 +msgid "Block authorization requests with URLs in them" +msgstr "" + +#: src/gui/pref/pg_privacy.py:533 +msgid "Allow others to view my online status from the web" +msgstr "" + +#: src/gui/pref/pg_privacy.py:658 src/gui/pref/pg_privacy.py:717 +msgid "Allow List" +msgstr "" + +#: src/gui/pref/pg_privacy.py:674 src/gui/pref/pg_privacy.py:710 +msgid "Allow unknown users to contact me" +msgstr "" + +#: src/gui/pref/pg_privacy.py:862 +msgid "Could not block {item:s}" +msgstr "" + +#: src/gui/pref/pg_privacy.py:870 +msgid "{item:s} is on your buddylist. Do you want to remove {item:s} from your buddylist and block the user?" +msgstr "" + +#: src/gui/pref/pg_privacy.py:872 +msgid "Confirm Block and Remove" +msgstr "" + +#: src/gui/pref/pg_privacy.py:939 +msgid "Hide operating system from other users" +msgstr "" + +#: src/gui/pref/pg_research.py:12 +msgid "" +"Help Digsby stay free for all users. Allow Digsby to use part of your computer's idle processing power to contribute to commercial grid computing projects by enabling the Research Module.\n" +"

\n" +"This module turns on after your computer has been completely idle (no mouse or keyboard movement) for a period of time. It turns off the instant you move your mouse or press a key, so it has no effect on your PC's performance when you're using it. The research module also runs as a low priority, sandboxed Java process, so that other tasks your computer is doing will get done first, and so that it is completely secure.\n" +"

\n" +"For more details, see the Research Module FAQ.\n" +msgstr "" + +#: src/gui/pref/pg_research.py:98 src/gui/pref/prefstrings.py:25 +msgid "Research Module" +msgstr "" + +#: src/gui/pref/pg_research.py:105 +msgid "Allow Digsby to use CPU time to conduct research after %2(research.idle_time_min)d minutes of idle time" +msgstr "" + +#: src/gui/pref/pg_research.py:111 +msgid "Maximum CPU Usage:" +msgstr "" + +#: src/gui/pref/pg_research.py:116 src/gui/pref/pg_research.py:123 +msgid "{val}%" +msgstr "" + +#: src/gui/pref/pg_research.py:118 +msgid "Maximum Bandwidth Usage:" +msgstr "" + +#: src/gui/pref/pg_status.py:19 +msgid "Status Options" +msgstr "" + +#: src/gui/pref/pg_status.py:21 +msgid "Promote Digsby in my IM status messages" +msgstr "" + +#: src/gui/pref/pg_status.py:24 +msgid "Help Digsby by linking album when sharing \"Listening to...\" as status" +msgstr "" + +#: src/gui/pref/pg_status.py:26 +msgid "Let others know that I am idle after %2(messaging.idle_after)d minutes of inactivity" +msgstr "" + +#: src/gui/pref/pg_status.py:33 +msgid "Autorespond with status message" +msgstr "" + +#: src/gui/pref/pg_status.py:34 +msgid "Disable sounds" +msgstr "" + +#: src/gui/pref/pg_status.py:35 +msgid "Disable pop-up notifications" +msgstr "" + +#: src/gui/pref/pg_status.py:38 +msgid "When away..." +msgstr "" + +#: src/gui/pref/pg_status.py:45 +msgid "&Hide new conversation windows" +msgstr "" + +#: src/gui/pref/pg_status.py:46 +msgid "&Disable sounds" +msgstr "" + +#: src/gui/pref/pg_status.py:47 +msgid "Disable &pop-up notifications" +msgstr "" + +#: src/gui/pref/pg_status.py:50 +msgid "When running full screen applications..." +msgstr "" + +#: src/gui/pref/pg_status.py:60 +msgid "Status Messages" +msgstr "" + +#: src/gui/pref/pg_status.py:60 src/gui/status.py:607 +msgid "New Status Message" +msgstr "" + +#: src/gui/pref/pg_supportdigsby.py:12 +#: src/gui/supportdigsby/supportdialog.py:120 +msgid "Support Digsby" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:21 +msgid "automatically take focus" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:22 +msgid "start minimized in taskbar" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:23 +msgid "start hidden (tray icon blinks)" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:27 +msgid "bottom" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:28 +msgid "top" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:29 +msgid "left" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:30 +msgid "right" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:46 +msgid "Group multiple conversations into one tabbed window" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:52 +msgid "New conversation windows: " +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:54 +msgid "buddy icon" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:55 +msgid "service icon" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:56 +msgid "status icon" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:57 +msgid "Identify conversations with the contact's: " +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:64 +msgid "Conversation Options" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:66 +msgid "Don't show flash ads" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:70 +msgid "" +"Help keep Digsby free by showing an\n" +"advertisement in the IM window." +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:71 +msgid "Support Digsby development with an ad" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:73 +msgid "Location of ad in IM window: " +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:77 +msgid "Ad Options" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:86 +msgid "Text Formatting" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:106 +msgid "Your messages will look like this." +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:157 +msgid "Show last %2(conversation_window.num_lines)d lines in IM window" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:160 +msgid "&Display timestamp:" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:168 +msgid "Spell check:" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:187 +msgid "Log IM conversations to hard drive" +msgstr "" + +#: src/gui/pref/pg_text_conversations.py:219 +msgid "Show &emoticons:" +msgstr "" + +#: src/gui/pref/pg_widgets.py:41 +msgid "Widget Preview" +msgstr "" + +#: src/gui/pref/pg_widgets.py:49 +msgid "&Copy To Clipboard" +msgstr "" + +#: src/gui/pref/pg_widgets.py:88 +msgid "New Widget" +msgstr "" + +#: src/gui/pref/pg_widgets.py:88 src/gui/pref/prefstrings.py:23 +msgid "Widgets" +msgstr "" + +#: src/gui/pref/pg_widgets.py:90 +msgid "Embed Tag" +msgstr "" + +#: src/gui/pref/prefcontrols.py:584 +msgid "Custom ({prefval})" +msgstr "" + +#: src/gui/pref/prefcontrols.py:643 +msgid "Choose a folder" +msgstr "" + +#: src/gui/pref/prefcontrols.py:841 +msgid "{val}px" +msgstr "" + +#: src/gui/pref/prefsdialog.py:170 +msgid "Digsby Preferences" +msgstr "" + +#: src/gui/pref/prefsdialog.py:263 +msgid "&Done" +msgstr "" + +#: src/gui/pref/prefsdialog.py:289 +msgid "Search" +msgstr "" + +#: src/gui/pref/prefstrings.py:14 +msgid "Accounts" +msgstr "" + +#: src/gui/pref/prefstrings.py:15 +msgid "General & Profile" +msgstr "" + +#: src/gui/pref/prefstrings.py:16 +msgid "Skins" +msgstr "" + +#: src/gui/pref/prefstrings.py:18 +msgid "Conversations" +msgstr "" + +#: src/gui/pref/prefstrings.py:21 src/plugins/msim/myspacegui/privacy.py:88 +msgid "Privacy" +msgstr "" + +#: src/gui/pref/prefstrings.py:26 src/gui/proxydialog.py:222 +msgid "Connection Settings" +msgstr "" + +#: src/gui/pref/prefstrings.py:42 +msgid "Developer" +msgstr "" + +#: src/gui/protocols/__init__.py:7 +msgid "Enter a new password for {username}:" +msgstr "" + +#: src/gui/protocols/__init__.py:8 src/gui/protocols/jabbergui.py:139 +msgid "Change Password" +msgstr "" + +#: src/gui/protocols/__init__.py:22 +msgid "Are you sure you want to delete contact {name}?" +msgstr "" + +#: src/gui/protocols/__init__.py:23 +msgid "Delete Contact" +msgstr "" + +#: src/gui/protocols/__init__.py:36 +msgid "WARNING!" +msgstr "" + +#: src/gui/protocols/__init__.py:37 +msgid "All your contacts in this group will be deleted locally AND on the server." +msgstr "" + +#: src/gui/protocols/__init__.py:38 +msgid "Are you sure you want to remove {groupname}?" +msgstr "" + +#: src/gui/protocols/__init__.py:42 +msgid "Delete Group" +msgstr "" + +#: src/gui/protocols/__init__.py:47 +msgid "Please enter a group name:" +msgstr "" + +#: src/gui/protocols/__init__.py:47 +msgid "Add Group" +msgstr "" + +#: src/gui/protocols/__init__.py:60 +#, python-format +msgid "Are you sure you want to block %s?" +msgstr "" + +#: src/gui/protocols/__init__.py:61 +msgid "Block Buddy" +msgstr "" + +#: src/gui/protocols/jabbergui.py:18 +#, python-format +msgid "Enter a priority for %s:" +msgstr "" + +#: src/gui/protocols/jabbergui.py:19 +msgid "Set Jabber Priority" +msgstr "" + +#: src/gui/protocols/jabbergui.py:105 +msgid "Incorrect Password" +msgstr "" + +#: src/gui/protocols/jabbergui.py:111 +msgid "Failed to delete account from the server." +msgstr "" + +#: src/gui/protocols/jabbergui.py:112 +msgid "Delete Account - Failed" +msgstr "" + +#: src/gui/protocols/jabbergui.py:150 +msgid "Old Password: " +msgstr "" + +#: src/gui/protocols/jabbergui.py:152 +msgid "New Password: " +msgstr "" + +#: src/gui/protocols/jabbergui.py:154 +msgid "Confirm New Password: " +msgstr "" + +#: src/gui/protocols/jabbergui.py:217 +msgid "Incorrect Old Password" +msgstr "" + +#: src/gui/protocols/jabbergui.py:219 +msgid "Passwords do not match" +msgstr "" + +#: src/gui/protocols/jabbergui.py:219 +msgid "Paswords do not match" +msgstr "" + +#: src/gui/protocols/jabbergui.py:280 +msgid "&Enabled" +msgstr "" + +#: src/gui/protocols/jabbergui.py:283 +msgid "&Scroll Lock" +msgstr "" + +#: src/gui/protocols/jabbergui.py:290 +msgid "&Close" +msgstr "" + +#: src/gui/protocols/oscargui.py:10 +msgid "Enter a formatted screenname for {username}" +msgstr "" + +#: src/gui/protocols/oscargui.py:11 +msgid "The new screenname must be the same as the old one, except for changes in capitalization and spacing." +msgstr "" + +#: src/gui/protocols/oscargui.py:14 +msgid "Edit Formatted Screenname" +msgstr "" + +#: src/gui/protocols/oscargui.py:26 +msgid "Enter an email address:" +msgstr "" + +#: src/gui/protocols/oscargui.py:27 +msgid "Edit Account Email: {username}" +msgstr "" + +#: src/gui/proxydialog.py:40 +msgid "&No proxy" +msgstr "" + +#: src/gui/proxydialog.py:41 +msgid "Use &default system settings" +msgstr "" + +#: src/gui/proxydialog.py:42 +msgid "&Specify proxy settings" +msgstr "" + +#: src/gui/proxydialog.py:59 +msgid "&Host:" +msgstr "" + +#: src/gui/proxydialog.py:66 +msgid "P&ort:" +msgstr "" + +#: src/gui/proxydialog.py:96 +msgid "&Username:" +msgstr "" + +#: src/gui/proxydialog.py:117 +msgid "Proxy Server" +msgstr "" + +#: src/gui/proxydialog.py:118 +msgid "Protocol" +msgstr "" + +#: src/gui/proxydialog.py:121 +msgid "How to Connect" +msgstr "" + +#: src/gui/proxydialog.py:123 +msgid "Authentication" +msgstr "" + +#: src/gui/proxydialog.py:232 +msgid "&OK" +msgstr "" + +#: src/gui/searchgui.py:43 +msgid "Arrange Searches" +msgstr "" + +#: src/gui/searchgui.py:89 +msgid "Search {search_engine_name:s} for \"{search_string:s}\"" +msgstr "" + +#: src/gui/searchgui.py:132 +msgid "Web Search" +msgstr "" + +#: src/gui/social_status_dialog.py:103 +msgid "Set IM Status" +msgstr "" + +#: src/gui/social_status_dialog.py:380 +msgid "Choose Accounts:" +msgstr "" + +#: src/gui/social_status_dialog.py:438 +msgid "Insert Link" +msgstr "" + +#: src/gui/social_status_dialog.py:451 +msgid "Website URL:" +msgstr "" + +#: src/gui/social_status_dialog.py:461 +msgid "Insert &Link" +msgstr "" + +#: src/gui/social_status_dialog.py:507 +msgid "Share Image" +msgstr "" + +#: src/gui/social_status_dialog.py:582 +msgid "Uploading Image..." +msgstr "" + +#: src/gui/social_status_dialog.py:777 +msgid "Errors Encountered:" +msgstr "" + +#: src/gui/social_status_dialog.py:882 +#: src/plugins/msim/myspacegui/editstatus.py:6 +msgid "Your Status:" +msgstr "" + +#: src/gui/social_status_dialog.py:891 +msgid "&Update Status" +msgstr "" + +#: src/gui/social_status_dialog.py:1081 +#, python-format +msgid "Network error (%r)" +msgstr "" + +#: src/gui/social_status_dialog.py:1083 +#, python-format +msgid "Network error (%s)" +msgstr "" + +#: src/gui/social_status_dialog.py:1085 +msgid "Network error ({error!r} - {error!s})" +msgstr "" + +#: src/gui/social_status_dialog.py:1087 src/gui/social_status_dialog.py:1090 +#, python-format +msgid "%s" +msgstr "" + +#: src/gui/social_status_dialog.py:1092 +#, python-format +msgid "Unknown error (%s)" +msgstr "" + +#: src/gui/social_status_dialog.py:1094 +msgid "Unknown error" +msgstr "" + +#: src/gui/spellchecktextctrlmixin.py:550 +msgid "Add to Dictionary" +msgstr "" + +#. Translators: Separator when listing multiple accounts, ex: MSN/ICQ only +#: src/gui/status.py:118 +msgid "/" +msgstr "" + +#: src/gui/status.py:119 +msgid "{status} ({account_types} Only)" +msgstr "" + +#: src/gui/status.py:374 +msgid "Message" +msgstr "" + +#: src/gui/status.py:496 +msgid "&Title:" +msgstr "" + +#: src/gui/status.py:501 +msgid "&State:" +msgstr "" + +#: src/gui/status.py:509 +msgid "&Status message:" +msgstr "" + +#: src/gui/status.py:535 +msgid "&Use a different status for some accounts" +msgstr "" + +#: src/gui/status.py:539 +msgid "&Set" +msgstr "" + +#: src/gui/status.py:548 +msgid "Save for &later" +msgstr "" + +#: src/gui/status.py:608 +msgid "New Status for {name}" +msgstr "" + +#: src/gui/status.py:633 +msgid "Edit Status Message" +msgstr "" + +#: src/gui/status.py:636 +msgid "Edit Status for {account}" +msgstr "" + +#: src/gui/status.py:793 +msgid "Are you sure you want to delete status message \"{title}\"?" +msgstr "" + +#: src/gui/status.py:794 +msgid "Delete Status Message" +msgstr "" + +#: src/gui/statuscombo.py:87 src/gui/statuscombo.py:165 +#: src/gui/statuscombo.py:529 src/plugins/twitter/twitter_gui.py:615 +msgid "Global Status" +msgstr "" + +#: src/gui/statuscombo.py:105 src/gui/statuscombo.py:540 +msgid "Promote Digsby!" +msgstr "" + +#: src/gui/statuscombo.py:125 src/gui/statuscombo.py:160 +#: src/gui/statuscombo.py:526 +msgid "Custom..." +msgstr "" + +#: src/gui/statuscombo.py:639 +msgid "Press 'Ctrl+F' to Search List" +msgstr "" + +#: src/gui/supportdigsby/supportdialog.py:96 +#: src/gui/supportdigsby/supportoptions.py:71 +msgid "Are you sure?" +msgstr "" + +#: src/gui/supportdigsby/supportdialog.py:97 +msgid "Are you sure you want do this?" +msgstr "" + +#: src/gui/supportdigsby/supportdialog.py:111 +msgid "Thank you for supporting Digsby." +msgstr "" + +#: src/gui/supportdigsby/supportdialog.py:111 +msgid "Thank you!" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:22 +#: src/gui/supportdigsby/supportoptions.py:32 +msgid "Set" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:23 +msgid "Make Google Powered Digsby Search your homepage" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:33 +msgid "Make Google Powered Digsby Search your default search engine" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:42 +#: src/gui/supportdigsby/supportoptions.py:47 +msgid "Invite" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:43 +msgid "Invite your friends via Email" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:48 +msgid "Invite your friends via Facebook" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:52 +#: src/gui/supportdigsby/supportoptions.py:57 +msgid "Join Group" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:53 +msgid "Become a fan on Facebook" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:58 +msgid "Become a fan on LinkedIn" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:62 +msgid "Follow" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:63 +msgid "Follow us on Twitter" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:67 +msgid "Help Digsby conduct research" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:70 +msgid "Helping us conduct research keeps Digsby free and ad-free. Are you sure you want to disable this option?" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:83 +msgid "Disable" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:85 +msgid "Enable" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:94 +msgid "Subscribe" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:95 +msgid "Subscribe to our Blog" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:99 +msgid "Create" +msgstr "" + +#: src/gui/supportdigsby/supportoptions.py:100 +msgid "Create a Digsby widget for your blog or website" +msgstr "" + +#: src/gui/toast/toasthelp.py:25 +msgid "TIP: Popup Notifications" +msgstr "" + +#: src/gui/toast/toasthelp.py:27 +msgid "You can right click popups to close them right away instead of waiting for them to fade out." +msgstr "" + +#: src/gui/toolbox/toolbox.py:427 +msgid "Confirm" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1225 src/gui/toolbox/toolbox.py:1252 +msgid "Right To Left" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1336 +msgid "Use Long URL" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1346 +msgid "Shorten URL" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1437 +msgid "Undo" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1440 +msgid "Redo" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1445 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:104 +msgid "Cut" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1455 +#: src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py:108 +msgid "Select All" +msgstr "" + +#: src/gui/toolbox/toolbox.py:1461 +msgid "Select an image file" +msgstr "" + +#: src/gui/trayicons.py:172 +msgid "Set &Global Status..." +msgstr "" + +#: src/gui/trayicons.py:175 +msgid "Set IM &Status" +msgstr "" + +#: src/gui/trayicons.py:179 src/gui/trayicons.py:185 +msgid "Show &Buddy List" +msgstr "" + +#: src/gui/trayicons.py:185 +msgid "Hide &Buddy List" +msgstr "" + +#: src/gui/trayicons.py:190 +msgid "Show Menu Bar" +msgstr "" + +#: src/gui/trayicons.py:214 +msgid "Digsby" +msgstr "" + +#: src/gui/trayicons.py:225 +msgid "{msgs} message" +msgid_plural "{msgs} messages" +msgstr[0] "" +msgstr[1] "" + +#: src/gui/trayicons.py:230 +msgid "{alias} ({service}) - {message}" +msgstr "" + +#: src/gui/trayicons.py:298 +msgid "Show All" +msgstr "" + +#: src/gui/uberwidgets/formattedinput.py:423 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:103 +msgid "Hide Formatting Bar" +msgstr "" + +#: src/gui/uberwidgets/formattedinput.py:726 +#: src/gui/uberwidgets/formattedinput2/formattedinput.py:206 +msgid "Set Font..." +msgstr "" + +#: src/gui/uberwidgets/formattedinput.py:739 +#: src/gui/uberwidgets/formattedinput2/formattingbar.py:133 +msgid "Choose a foreground color" +msgstr "" + +#: src/gui/vcard/vcardgui.py:19 +msgid "Unit" +msgstr "" + +#: src/gui/vcard/vcardgui.py:48 +msgid "Last Name" +msgstr "" + +#: src/gui/vcard/vcardgui.py:49 +msgid "First Name" +msgstr "" + +#: src/gui/vcard/vcardgui.py:50 +msgid "Middle Name" +msgstr "" + +#: src/gui/vcard/vcardgui.py:51 +msgid "Prefix" +msgstr "" + +#: src/gui/vcard/vcardgui.py:52 +msgid "Suffix" +msgstr "" + +#: src/gui/vcard/vcardgui.py:108 +msgid "Number" +msgstr "" + +#: src/gui/vcard/vcardgui.py:173 +msgid "Street" +msgstr "" + +#: src/gui/vcard/vcardgui.py:176 +msgid "City" +msgstr "" + +#: src/gui/vcard/vcardgui.py:177 +msgid "State" +msgstr "" + +#: src/gui/vcard/vcardgui.py:178 +msgid "Postal Code" +msgstr "" + +#: src/gui/vcard/vcardgui.py:179 +msgid "Country" +msgstr "" + +#: src/gui/vcard/vcardgui.py:263 +msgid "vCard Editor for {username}" +msgstr "" + +#: src/gui/vcard/vcardgui.py:263 +msgid "vCard Viewer" +msgstr "" + +#: src/gui/vcard/vcardgui.py:274 +msgid "Retreive" +msgstr "" + +#: src/gui/vcard/vcardgui.py:334 +msgid "General" +msgstr "" + +#: src/gui/vcard/vcardgui.py:337 +msgid "Full Name" +msgstr "" + +#: src/gui/vcard/vcardgui.py:338 src/plugins/msim/res/content.tenjin:22 +msgid "Nickname" +msgstr "" + +#: src/gui/vcard/vcardgui.py:339 src/oscar/OscarBuddies.py:292 +msgid "Birthday" +msgstr "" + +#: src/gui/vcard/vcardgui.py:341 +msgid "Homepage" +msgstr "" + +#: src/gui/vcard/vcardgui.py:358 +msgid "Work" +msgstr "" + +#: src/gui/vcard/vcardgui.py:363 src/oscar/OscarBuddies.py:340 +msgid "Position" +msgstr "" + +#: src/gui/vcard/vcardgui.py:364 +msgid "Role" +msgstr "" + +#: src/gui/vcard/vcardgui.py:377 src/plugins/msim/res/content.tenjin:26 +msgid "Location" +msgstr "" + +#: src/gui/vcard/vcardgui.py:390 +msgid "About" +msgstr "" + +#: src/gui/video/webvideo.py:19 +msgid "{name} has invited you to an audio/video chat." +msgstr "" + +#: src/gui/video/webvideo.py:20 src/hub.py:122 +msgid "Would you like to join?" +msgstr "" + +#: src/gui/video/webvideo.py:24 +msgid "Audio/Video Chat Invitation" +msgstr "" + +#: src/gui/visuallisteditor.py:114 +msgid "(Unnamed Panel)" +msgstr "" + +#: src/gui/visuallisteditor.py:298 +msgid "Drag and drop to reorder" +msgstr "" + +#: src/gui/visuallisteditor.py:308 +msgid "Done" +msgstr "" + +#: src/gui/widgetlist.py:124 +msgid "Are you sure you want to delete widget \"{widgetname}\"?" +msgstr "" + +#: src/gui/widgetlist.py:125 +msgid "Delete Widget" +msgstr "" + +#: src/hub.py:107 +msgid "{protocolname} DirectIM" +msgstr "" + +#: src/hub.py:108 +msgid "{name} wants to directly connect with you. (Your IP address will be revealed.)" +msgstr "" + +#: src/hub.py:127 +msgid "{name} has invited you to a group chat." +msgstr "" + +#: src/hub.py:130 +msgid "You have been invited to a group chat." +msgstr "" + +#: src/hub.py:140 +msgid "Join" +msgstr "" + +#: src/hub.py:141 +msgid "Ignore" +msgstr "" + +#: src/hub.py:151 +msgid "{protocol_name:s} Invite" +msgstr "" + +#: src/hub.py:157 +msgid "Ignore Invite" +msgstr "" + +#: src/hub.py:176 +msgid "Allow {buddy} to add you ({you}) as a buddy on {protocol}?" +msgstr "" + +#: src/imaccount.py:515 src/imaccount.py:522 +msgid "Connect" +msgstr "" + +#: src/imaccount.py:523 +msgid "Disconnect" +msgstr "" + +#: src/jabber/JabberBuddy.py:176 src/oscar/OscarBuddies.py:280 +msgid "Full Name:" +msgstr "" + +#: src/jabber/JabberBuddy.py:187 +msgid "Birthday:" +msgstr "" + +#: src/jabber/JabberBuddy.py:187 +msgid "Phone:" +msgstr "" + +#: src/jabber/JabberBuddy.py:187 +#: src/plugins/digsby_service_editor/default_ui.py:143 +msgid "Email Address:" +msgstr "" + +#: src/jabber/JabberBuddy.py:196 +msgid "Website" +msgstr "" + +#: src/jabber/JabberBuddy.py:203 src/oscar/OscarBuddies.py:314 +msgid "Additional Information:" +msgstr "" + +#: src/jabber/JabberBuddy.py:210 +msgid "{street} {extra_adress} {locality} {region} {postal_code} {country}" +msgstr "" + +#: src/jabber/JabberBuddy.py:256 src/oscar/OscarBuddies.py:320 +msgid "Home Address:" +msgstr "" + +#: src/jabber/JabberChat.py:298 +msgid "You have been invited to {roomname}" +msgstr "" + +#: src/jabber/JabberConversation.py:133 +msgid "{name} has left the conversation." +msgstr "" + +#: src/jabber/JabberResource.py:15 src/oscar/OscarBuddies.py:45 +msgid "Free for Chat" +msgstr "" + +#: src/main.py:379 +msgid "Your digsby password has been changed. Please log back in with the new password." +msgstr "" + +#: src/main.py:381 +msgid "Password Changed" +msgstr "" + +#: src/main.py:562 +msgid "Please login first." +msgstr "" + +#: src/main.py:562 +msgid "Advanced Preferences" +msgstr "" + +#: src/main.py:816 +msgid "Global Shortcuts" +msgstr "" + +#: src/main.py:820 +msgid "Debug Global Shortcuts" +msgstr "" + +#: src/main.py:823 +msgid "Text Controls" +msgstr "" + +#: src/main.py:1345 +msgid "There was an error submitting your crash report." +msgstr "" + +#: src/main.py:1358 +msgid "Crash report submitted successfully." +msgstr "" + +#: src/main.py:1365 +msgid "Would you like to restart Digsby now?" +msgstr "" + +#: src/main.py:1367 +msgid "Crash Report" +msgstr "" + +#: src/msn/MSNBuddy.py:284 src/oscar/OscarBuddies.py:242 +msgid "Mobile" +msgstr "" + +#: src/msn/MSNBuddy.py:488 src/plugins/digsby_service_editor/default_ui.py:207 +msgid "Display Name:" +msgstr "" + +#: src/msn/MSNBuddy.py:559 +msgid "Administrators:" +msgstr "" + +#: src/msn/MSNBuddy.py:561 +msgid "Assistant Administrators:" +msgstr "" + +#: src/msn/MSNBuddy.py:563 +msgid "Members:" +msgstr "" + +#: src/msn/MSNBuddy.py:565 +msgid "Invited:" +msgstr "" + +#: src/msn/MSNBuddy.py:802 +msgid "{song:s} by {artist:s}" +msgstr "" + +#: src/msn/MSNConversation.py:287 +msgid "There was a network error creating a chat session." +msgstr "" + +#: src/msn/MSNConversation.py:466 +msgid "Your messages could not be sent because too many conversation sessions have been requested." +msgstr "" + +#. Translators: ex: Frank nudged you! +#: src/msn/MSNConversation.py:599 src/msn/p21/MSNP21Conversation.py:260 +msgid "{name} {action}" +msgstr "" + +#: src/msn/MSNConversation.py:603 src/msn/p21/MSNP21Conversation.py:264 +msgid "{name} winked at you!" +msgstr "" + +#: src/msn/MSNConversation.py:604 src/msn/p21/MSNP21Conversation.py:265 +msgid "{name} nudged you!" +msgstr "" + +#: src/msn/msngui.py:15 +msgid "Group Invite" +msgstr "" + +#: src/msn/msngui.py:20 src/plugins/digsby_updater/updater.py:487 +#: src/plugins/digsby_updater/updater.py:499 +msgid "Yes" +msgstr "" + +#: src/msn/msngui.py:21 src/plugins/digsby_updater/updater.py:488 +#: src/plugins/digsby_updater/updater.py:500 +msgid "No" +msgstr "" + +#: src/msn/msngui.py:25 +msgid "{inviter_email} says: \"{invite_message}\"" +msgstr "" + +#: src/msn/msngui.py:28 +msgid "" +"{inviter_email} has invited you to join the group {circle_name}.{invite_segment}\n" +"Would you like to join?" +msgstr "" + +#: src/msn/msngui.py:36 +msgid "Please enter a name for your group chat:" +msgstr "" + +#: src/oscar/OscarBuddies.py:265 src/oscar/OscarBuddies.py:356 +msgid "Profile URL:" +msgstr "" + +#: src/oscar/OscarBuddies.py:291 src/plugins/msim/res/content.tenjin:25 +msgid "Gender" +msgstr "" + +#: src/oscar/OscarBuddies.py:295 src/oscar/OscarBuddies.py:343 +msgid "{label}:" +msgstr "" + +#: src/oscar/OscarBuddies.py:301 +msgid "Website:" +msgstr "" + +#: src/oscar/OscarBuddies.py:333 +msgid "Work Address:" +msgstr "" + +#: src/oscar/OscarBuddies.py:340 +msgid "Company" +msgstr "" + +#: src/oscar/OscarBuddies.py:340 +msgid "Department" +msgstr "" + +#: src/oscar/OscarBuddies.py:348 +msgid "Work Website:" +msgstr "" + +#: src/oscar/snac/family_x07.py:165 +msgid "You should receive an email with confirmation instructions shortly." +msgstr "" + +#: src/oscar/snac/family_x07.py:166 +msgid "Your account is already confirmed." +msgstr "" + +#: src/oscar/snac/family_x07.py:167 +msgid "There was an unknown server error." +msgstr "" + +#: src/oscar/snac/family_x07.py:180 +msgid "Confirm Account: {username}" +msgstr "" + +#: src/oscar/snac/family_x15.py:636 +msgid "Female" +msgstr "" + +#: src/oscar/snac/family_x15.py:636 +msgid "Male" +msgstr "" + +#: src/plugins/component_gmail/gmail.py:123 +msgid "Mark as Read" +msgstr "" + +#: src/plugins/component_gmail/gmail.py:124 +msgid "Archive" +msgstr "" + +#: src/plugins/digsby_about/aboutdialog.py:48 +msgid "About Digsby" +msgstr "" + +#: src/plugins/digsby_about/res/content.tenjin:14 +msgid "Checking for updates..." +msgstr "" + +#: src/plugins/digsby_about/res/content.tenjin:20 +msgid "Digsby is up to date" +msgstr "" + +#: src/plugins/digsby_about/res/content.tenjin:26 +#: src/plugins/digsby_updater/updater.py:484 +#: src/plugins/digsby_updater/updater.py:496 +msgid "Update Available" +msgstr "" + +#: src/plugins/digsby_about/res/content.tenjin:31 +msgid "Copyright © 2010 dotSyntax, LLC." +msgstr "" + +#: src/plugins/digsby_about/res/content.tenjin:33 +msgid "All Rights Reserved." +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:62 +msgid "{account_name:s} - {service_name:s} Settings" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:167 +msgid "Forgot Password?" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:247 +msgid "&{server_type} Server:" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:248 +msgid "&This server requires SSL" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:266 +msgid "This server requires SSL" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:326 +msgid "IM Server:" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:348 +msgid "Always connect over HTTP" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:363 +msgid "Check for new mail every {minutes_input} minutes" +msgstr "" + +#: src/plugins/digsby_service_editor/default_ui.py:600 +msgid "Custom ({mailclient})" +msgstr "" + +#: src/plugins/digsby_status/status_tag_urls.py:56 +#: src/plugins/facebook/fbgui.py:10 src/plugins/linkedin/ligui.py:8 +#: src/plugins/myspace/msgui.py:10 +#: src/plugins/researchdriver/researchtoast.py:36 +#: src/plugins/twitter/twgui.py:14 +msgid "Learn More" +msgstr "" + +#: src/plugins/digsby_status/status_tag_urls.py:67 +msgid "Yes, Spread the Word!" +msgstr "" + +#: src/plugins/digsby_status/status_tag_urls.py:68 +#: src/plugins/facebook/fbgui.py:19 src/plugins/linkedin/ligui.py:17 +#: src/plugins/msim/myspacegui/prompts.py:11 src/plugins/myspace/msgui.py:19 +#: src/plugins/twitter/twgui.py:23 +msgid "No Thanks" +msgstr "" + +#: src/plugins/digsby_status/status_tag_urls.py:106 +msgid "Spread the Word!" +msgstr "" + +#: src/plugins/digsby_status/status_tag_urls.py:109 +msgid "We've made it easier to spread the word about Digsby by adding a link to your IM status. You can disable this option in Preferences > Status." +msgstr "" + +#: src/plugins/digsby_updater/UpdateProgress.py:55 +msgid "Digsby Update" +msgstr "" + +#: src/plugins/digsby_updater/UpdateProgress.py:63 +msgid "{state}: {completed} of {size} -- {time} remain" +msgstr "" + +#: src/plugins/digsby_updater/UpdateProgress.py:145 +msgid "Slower" +msgstr "" + +#: src/plugins/digsby_updater/UpdateProgress.py:146 +msgid "Faster!" +msgstr "" + +#: src/plugins/digsby_updater/machelpers.py:18 +msgid "Error while updating Digsby. Please restart and try again, or grab the latest version from digsby.com. Digsby will now shut down." +msgstr "" + +#: src/plugins/digsby_updater/machelpers.py:25 +msgid "Updated successfully. Digsby now needs to restart." +msgstr "" + +#: src/plugins/digsby_updater/machelpers.py:31 +msgid "Unable to authenticate. Please restart and try again." +msgstr "" + +#: src/plugins/digsby_updater/updater.py:202 +msgid "Check for {tag} updates" +msgstr "" + +#: src/plugins/digsby_updater/updater.py:204 +msgid "Check for Updates" +msgstr "" + +#: src/plugins/digsby_updater/updater.py:485 +#: src/plugins/digsby_updater/updater.py:497 +msgid "" +"A Digsby update is available to download.\n" +"Would you like to begin downloading it now?" +msgstr "" + +#: src/plugins/digsby_updater/updater.py:573 +msgid "Update Failed" +msgstr "" + +#: src/plugins/digsby_updater/updater.py:574 +msgid "Digsby was unable to complete the download. This update will be attempted again later." +msgstr "" + +#: src/plugins/digsby_updater/updater.py:576 +msgid "Manual Update" +msgstr "" + +#: src/plugins/digsby_updater/updater.py:606 +msgid "Update Ready" +msgstr "" + +#: src/plugins/digsby_updater/updater.py:607 +msgid "A new version of Digsby is ready to install. Restart Digsby to apply the update." +msgstr "" + +#: src/plugins/digsby_updater/updater.py:609 +msgid "Restart Now" +msgstr "" + +#: src/plugins/digsby_updater/updater.py:610 +msgid "Restart Later" +msgstr "" + +#: src/plugins/facebook/fbacct.py:310 +#: src/plugins/facebook/res/comment_link.py.xml:19 +#: src/plugins/facebook/res/comment_link.py.xml:20 +#: src/plugins/linkedin/LinkedInAccount.py:235 +#: src/plugins/linkedin/res/comment_link.tenjin:12 +#: src/plugins/linkedin/res/comment_link.tenjin:13 +#: src/plugins/myspace/MyspaceProtocol.py:417 +#: src/plugins/myspace/res/comment_link.tenjin:7 +#: src/plugins/myspace/res/comment_link.tenjin:9 +msgid "Comment" +msgstr "" + +#: src/plugins/facebook/fbacct.py:318 +#: src/plugins/facebook/res/like_link.py.xml:11 +#: src/plugins/linkedin/res/like_dislike_link.tenjin:20 +#: src/plugins/myspace/res/like_dislike_link.tenjin:17 +msgid "Like" +msgstr "" + +#: src/plugins/facebook/fbacct.py:318 +#: src/plugins/facebook/res/like_link.py.xml:16 +msgid "Unlike" +msgstr "" + +#: src/plugins/facebook/fbacct.py:329 +#: src/plugins/myspace/MyspaceProtocol.py:425 +msgid "comment" +msgstr "" + +#: src/plugins/facebook/fbacct.py:349 src/plugins/facebook/fbacct.py:353 +msgid "like" +msgstr "" + +#: src/plugins/facebook/fbacct.py:366 +msgid "Facebook Error" +msgstr "" + +#: src/plugins/facebook/fbacct.py:367 +#: src/plugins/myspace/MyspaceProtocol.py:433 +msgid "Error posting {thing}" +msgstr "" + +#: src/plugins/facebook/fbacct.py:368 +msgid "Digsby does not have permission to publish to your stream." +msgstr "" + +#: src/plugins/facebook/fbacct.py:369 +#: src/plugins/myspace/MyspaceProtocol.py:435 +msgid "Grant Permissions" +msgstr "" + +#: src/plugins/facebook/fbacct.py:389 +msgid "Facebook Alerts" +msgstr "" + +#: src/plugins/facebook/fbacct.py:396 +msgid "You have new messages." +msgstr "" + +#: src/plugins/facebook/fbacct.py:399 +msgid "You have new pokes." +msgstr "" + +#: src/plugins/facebook/fbacct.py:402 +msgid "You have new shares." +msgstr "" + +#: src/plugins/facebook/fbacct.py:405 +msgid "You have new friend requests." +msgstr "" + +#: src/plugins/facebook/fbacct.py:408 +msgid "You have new group invites." +msgstr "" + +#: src/plugins/facebook/fbacct.py:411 +msgid "You have new event invites." +msgstr "" + +#: src/plugins/facebook/fbacct.py:416 +msgid "You have new notifications." +msgstr "" + +#: src/plugins/facebook/fbgui.py:18 src/plugins/linkedin/ligui.py:16 +#: src/plugins/myspace/msgui.py:18 src/plugins/twitter/twgui.py:22 +msgid "Post Achievements" +msgstr "" + +#: src/plugins/facebook/fbgui.py:24 +msgid "Post achievements to my Wall" +msgstr "" + +#: src/plugins/facebook/fbgui.py:40 src/plugins/myspace/msgui.py:69 +msgid "Show Alerts:" +msgstr "" + +#: src/plugins/facebook/fbgui.py:43 +msgid "Messages" +msgstr "" + +#: src/plugins/facebook/fbgui.py:44 +msgid "Pokes" +msgstr "" + +#: src/plugins/facebook/fbgui.py:45 +msgid "Shares" +msgstr "" + +#: src/plugins/facebook/fbgui.py:46 src/plugins/myspace/MyspaceProtocol.py:34 +#: src/plugins/myspace/msgui.py:79 +msgid "Friend Requests" +msgstr "" + +#: src/plugins/facebook/fbgui.py:47 +msgid "Group Invitations" +msgstr "" + +#: src/plugins/facebook/fbgui.py:48 src/plugins/myspace/MyspaceProtocol.py:33 +msgid "Event Invitations" +msgstr "" + +#: src/plugins/facebook/res/alerts.py.xml:2 +#: src/plugins/myspace/res/indicators.tenjin:2 +#: src/plugins/twitter/res/content.tenjin:8 +msgid "Alerts" +msgstr "" + +#: src/plugins/facebook/res/alerts.py.xml:4 +msgid "No Alerts" +msgstr "" + +#: src/plugins/facebook/res/alerts.py.xml:6 +#, python-format +msgid "%d new message" +msgid_plural "%d new messages" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/alerts.py.xml:7 +#, python-format +msgid "%d new friend request" +msgid_plural "%d new friend requests" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/alerts.py.xml:8 +#, python-format +msgid "%d new poke" +msgid_plural "%d new pokes" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/alerts.py.xml:9 +#, python-format +msgid "%d new share" +msgid_plural "%d new shares" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/alerts.py.xml:10 +#, python-format +msgid "%d new group invite" +msgid_plural "%d new group invites" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/alerts.py.xml:11 +#, python-format +msgid "%d new event invite" +msgid_plural "%d new event invites" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/alerts.py.xml:12 +#, python-format +msgid "%d new notification" +msgid_plural "%d new notifications" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/birthdays.py.xml:9 +msgid "Upcoming Birthdays" +msgstr "" + +#: src/plugins/facebook/res/comment.py.xml:11 +#: src/plugins/facebook/res/comment.py.xml:13 +#: src/plugins/facebook/res/dislikes.py.xml:28 +#: src/plugins/facebook/res/dislikes.py.xml:30 +#: src/plugins/facebook/res/dislikes.py.xml:39 +#: src/plugins/facebook/res/dislikes.py.xml:41 +#: src/plugins/facebook/res/dislikes.py.xml:55 +#: src/plugins/facebook/res/dislikes.py.xml:57 +#: src/plugins/facebook/res/dislikes.py.xml:63 +#: src/plugins/facebook/res/dislikes.py.xml:65 +#: src/plugins/facebook/res/dislikes.py.xml:71 +#: src/plugins/facebook/res/dislikes.py.xml:73 +#: src/plugins/facebook/res/likes.py.xml:42 +#: src/plugins/facebook/res/likes.py.xml:46 +#: src/plugins/facebook/res/likes.py.xml:55 +#: src/plugins/facebook/res/likes.py.xml:62 +#: src/plugins/facebook/res/likes.py.xml:80 +#: src/plugins/facebook/res/likes.py.xml:85 +msgid "Facebook User" +msgstr "" + +#: src/plugins/facebook/res/comment_link.py.xml:2 +#: src/plugins/facebook/res/comment_post_link.py.xml:2 +#: src/plugins/facebook/res/comments.py.xml:2 +#: src/plugins/facebook/res/dislike_link.py.xml:2 +#: src/plugins/facebook/res/dislikes.py.xml:2 +msgid "Dislike! (via http://digsby.com/fb)" +msgstr "" + +#: src/plugins/facebook/res/comment_post_link.py.xml:15 +#, python-format +msgid "See all %d comments" +msgstr "" + +#: src/plugins/facebook/res/comment_post_link.py.xml:17 +msgid "See all comments" +msgstr "" + +#: src/plugins/facebook/res/content.tenjin:35 +msgid "Back to News Feed" +msgstr "" + +#: src/plugins/facebook/res/dislike_link.py.xml:18 +msgid "Dislike" +msgstr "" + +#: src/plugins/facebook/res/dislike_link.py.xml:23 +msgid "Undislike" +msgstr "" + +#: src/plugins/facebook/res/dislike_link.py.xml:32 +msgid "Dislikes" +msgstr "" + +#: src/plugins/facebook/res/like_link.py.xml:25 +#: src/plugins/linkedin/res/like_dislike_link.tenjin:29 +#: src/plugins/myspace/res/like_dislike_link.tenjin:26 +msgid "Likes" +msgstr "" + +#: src/plugins/facebook/res/likes.py.xml:14 +msgid "You liked this" +msgstr "" + +#: src/plugins/facebook/res/likes.py.xml:16 +msgid "You and {startlink_other}one other{endlink_other} liked this" +msgid_plural "You and {startlink_other}{num_others} others{endlink_other} liked this" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/likes.py.xml:40 +msgid "{startlink_name}{name}{endlink_name} liked this" +msgstr "" + +#: src/plugins/facebook/res/likes.py.xml:51 +msgid "{startlink_name}{name}{endlink_name} and {startlink_other}one other{endlink_other} liked this" +msgid_plural "{startlink_name}{name}{endlink_name} and {startlink_other}{num_others} others{endlink_other} liked this" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/likes.py.xml:76 +msgid "{startlink_other}one user{endlink_other} liked this" +msgid_plural "{startlink_other}{num_users} users{endlink_other} liked this" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/facebook/res/post.py.xml:63 +msgid "See Friendship" +msgstr "" + +#: src/plugins/facebook/res/status.py.xml:7 +#: src/plugins/linkedin/res/content.tenjin:3 +msgid "You have no status message" +msgstr "" + +#: src/plugins/facebook/res/status.py.xml:9 +#: src/plugins/myspace/res/status.tenjin:18 +#: src/plugins/twitter/res/status.tenjin:14 +#: src/plugins/twitter/res/status.tenjin:21 +msgid "update" +msgstr "" + +#: src/plugins/fbchat/xmppfb.py:116 +msgid "Send Message" +msgstr "" + +#: src/plugins/fbchat/xmppfb.py:117 +msgid "Send Poke" +msgstr "" + +#: src/plugins/fbchat/xmppfb.py:118 +msgid "View Photos" +msgstr "" + +#: src/plugins/fbchat/xmppfb.py:122 src/yahoo/yahoobuddy.py:133 +msgid "View Profile" +msgstr "" + +#: src/plugins/fbchat/xmppfb.py:123 +msgid "Write on Wall" +msgstr "" + +#: src/plugins/fbchat/xmppfb.py:132 src/yahoo/yahoobuddy.py:164 +#: src/yahoo/yahoobuddy.py:181 +msgid "Links:" +msgstr "" + +#: src/plugins/feed_trends/feed_trends.py:702 +msgid "Trending News" +msgstr "" + +#: src/plugins/feed_trends/feed_trends.py:973 +msgid "read more..." +msgstr "" + +#: src/plugins/feed_trends/feed_trends.py:1010 +msgid "Feed Trends Debug" +msgstr "" + +#: src/plugins/feed_trends/geo_trends.py:259 +msgid "Featured Content" +msgstr "" + +#: src/plugins/feed_trends/res/trend_details.tenjin:7 +msgid "get address" +msgstr "" + +#: src/plugins/feed_trends/res/trend_details.tenjin:12 +msgid "phone number" +msgstr "" + +#: src/plugins/feed_trends/res/trend_details.tenjin:25 +msgid "reviews" +msgstr "" + +#: src/plugins/linkedin/LinkedInAccount.py:66 +msgid "Checking now..." +msgstr "" + +#: src/plugins/linkedin/LinkedInAccount.py:82 +#: src/plugins/twitter/twitter.py:214 +msgid "Home" +msgstr "" + +#: src/plugins/linkedin/LinkedInAccount.py:83 +msgid "Inbox" +msgstr "" + +#: src/plugins/linkedin/LinkedInAccount.py:85 +#: src/plugins/twitter/twitter.py:215 +msgid "Profile" +msgstr "" + +#: src/plugins/linkedin/LinkedInAccount.py:176 +msgid "API request limit has been reached." +msgstr "" + +#: src/plugins/linkedin/LinkedInAccount.py:378 +#: src/plugins/myspace/MyspaceAccount.py:334 +msgid "Please set your computer clock to the correct time / timezone." +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:138 +msgid "Like! (via http://digsby.com)" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:139 +msgid "Dislike! (via http://digsby.com)" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:294 +#, python-format +msgid "answered the question \"%s\"" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:356 +#: src/plugins/linkedin/LinkedInObjects.py:435 +#, python-format +msgid "%s and " +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:360 +#, python-format +msgid "is now connected to %s" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:371 +#: src/plugins/linkedin/res/ncon.tenjin:1 +msgid "is now a connection" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:385 +msgid "joined LinkedIn" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:412 +msgid "posted a job: {position_title:s} at {company_name:s}" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:439 +#, python-format +msgid "is now a member of %s" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:459 +#: src/plugins/linkedin/res/picu.tenjin:1 +msgid "has a new profile photo" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:498 +#, python-format +msgid "has recommended %s" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:500 +#, python-format +msgid "was recommended by %s" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:512 +msgid "has an updated profile" +msgstr "" + +#: src/plugins/linkedin/LinkedInObjects.py:536 +#, python-format +msgid "asked a question: %s" +msgstr "" + +#: src/plugins/linkedin/ligui.py:28 +msgid "Post achievements to my LinkedIn Network" +msgstr "" + +#: src/plugins/linkedin/res/ccem.tenjin:1 +msgid "joined" +msgstr "" + +#: src/plugins/linkedin/res/conn.tenjin:1 +msgid "is now connected to" +msgstr "" + +#: src/plugins/linkedin/res/content.tenjin:11 +msgid "Network Updates" +msgstr "" + +#: src/plugins/linkedin/res/content.tenjin:14 +msgid "No news today." +msgstr "" + +#: src/plugins/linkedin/res/dislikes.tenjin:15 +#: src/plugins/linkedin/res/dislikes.tenjin:33 +#: src/plugins/myspace/res/dislikes.tenjin:15 +#: src/plugins/myspace/res/dislikes.tenjin:30 +#: src/plugins/myspace/res/likes.tenjin:15 +#: src/plugins/myspace/res/likes.tenjin:29 +msgid "and" +msgstr "" + +#: src/plugins/linkedin/res/dislikes.tenjin:16 +#: src/plugins/linkedin/res/dislikes.tenjin:34 +#: src/plugins/myspace/res/dislikes.tenjin:17 +#: src/plugins/myspace/res/dislikes.tenjin:31 +#: src/plugins/myspace/res/likes.tenjin:16 +#: src/plugins/myspace/res/likes.tenjin:30 +msgid "other" +msgid_plural "others" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/linkedin/res/dislikes.tenjin:18 +#: src/plugins/myspace/res/dislikes.tenjin:19 +#: src/plugins/myspace/res/dislikes.tenjin:33 +msgid "disliked this" +msgstr "" + +#: src/plugins/linkedin/res/jgrp.tenjin:1 +msgid "has joined" +msgstr "" + +#: src/plugins/linkedin/res/jobp.tenjin:1 +msgid "posted a job" +msgstr "" + +#: src/plugins/linkedin/res/jobp.tenjin:1 +msgid "at" +msgstr "" + +#: src/plugins/linkedin/res/like_dislike_link.tenjin:18 +#: src/plugins/myspace/res/like_dislike_link.tenjin:15 +msgid "Liked" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:35 +msgid "{name} zapped you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:36 +msgid "You zapped {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:37 +msgid "Zap" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:41 +msgid "{name} whacked you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:42 +msgid "You whacked {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:43 +msgid "Whack" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:47 +msgid "{name} torched you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:48 +msgid "You torched {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:49 +msgid "Torch" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:53 +msgid "{name} smooched you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:54 +msgid "You smooched {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:55 +msgid "Smooch" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:59 +msgid "{name} slapped you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:60 +msgid "You slapped {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:61 +msgid "Slap" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:65 +msgid "{name} goosed you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:66 +msgid "You goosed {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:67 +msgid "Goose" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:71 +msgid "{name} high-fived you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:72 +msgid "You high-fived {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:73 +msgid "High-Five" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:77 +msgid "{name} punk'd you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:78 +msgid "You punk'd {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:79 +msgid "Punk" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:83 +msgid "{name} raspberry'd you" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:84 +msgid "You raspberry'd {name}" +msgstr "" + +#: src/plugins/msim/MSIMConversation.py:85 +msgid "Raspberry" +msgstr "" + +#: src/plugins/msim/myspacegui/editstatus.py:6 +msgid "MySpace Status" +msgstr "" + +#: src/plugins/msim/myspacegui/privacy.py:20 +msgid "Everyone" +msgstr "" + +#: src/plugins/msim/myspacegui/privacy.py:21 +msgid "Only people on my contact list" +msgstr "" + +#: src/plugins/msim/myspacegui/privacy.py:22 +msgid "No one" +msgstr "" + +#: src/plugins/msim/myspacegui/privacy.py:63 +msgid "Only people on my contact list can see my status" +msgstr "" + +#: src/plugins/msim/myspacegui/privacy.py:67 +msgid "Only people on my contact list can send me messages" +msgstr "" + +#: src/plugins/msim/myspacegui/privacy.py:74 +msgid "When I'm offline, receive and store messages from:" +msgstr "" + +#: src/plugins/msim/myspacegui/privacy.py:80 +msgid "Offline Messages" +msgstr "" + +#: src/plugins/msim/myspacegui/prompts.py:8 +msgid "Would you like to automatically add your MySpace friends to your MySpace IM contact list?" +msgstr "" + +#: src/plugins/msim/myspacegui/prompts.py:9 +msgid "Add Top Friends" +msgstr "" + +#: src/plugins/msim/myspacegui/prompts.py:10 +msgid "Add All Friends" +msgstr "" + +#: src/plugins/msim/myspacegui/prompts.py:13 +msgid "Add Friends" +msgstr "" + +#: src/plugins/msim/res/content.tenjin:20 +msgid "IM Name" +msgstr "" + +#: src/plugins/msim/res/content.tenjin:21 +msgid "Display Name" +msgstr "" + +#: src/plugins/msim/res/content.tenjin:23 +msgid "Real Name" +msgstr "" + +#: src/plugins/msim/res/content.tenjin:24 +msgid "Age" +msgstr "" + +#: src/plugins/msim/res/content.tenjin:27 +msgid "IM Headline" +msgstr "" + +#: src/plugins/myspace/MyspaceAccount.py:557 +msgid "added" +msgstr "" + +#: src/plugins/myspace/MyspaceAccount.py:558 +#, python-format +msgid "%s account" +msgstr "" + +#: src/plugins/myspace/MyspaceAccount.py:559 +msgid "to" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:29 src/plugins/myspace/msgui.py:83 +msgid "Birthdays" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:30 src/plugins/myspace/msgui.py:73 +msgid "Blog Comments" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:31 src/plugins/myspace/msgui.py:74 +msgid "Blog Subscriptions" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:32 +msgid "Comments" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:35 src/plugins/myspace/msgui.py:81 +msgid "Group Notifications" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:36 src/plugins/myspace/msgui.py:78 +msgid "Photo Tag Approvals" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:37 src/plugins/myspace/msgui.py:75 +msgid "Picture Comments" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:38 src/plugins/myspace/msgui.py:82 +msgid "Recently Added Friends" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:39 src/plugins/myspace/msgui.py:80 +msgid "Video Comments" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:74 +msgid "Mail ({mailcount})" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:432 +msgid "MySpace Error" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:434 +msgid "Digsby does not have permission to perform this action." +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:566 +msgid "MySpace Alerts" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:567 +#, python-format +msgid "You have new %s" +msgstr "" + +#: src/plugins/myspace/MyspaceProtocol.py:684 +msgid "Activity stream item not found. It may have been removed by its creator." +msgstr "" + +#: src/plugins/myspace/msgui.py:24 +msgid "Post achievements to my Activity Stream" +msgstr "" + +#: src/plugins/myspace/msgui.py:38 +msgid "Show Newsfeed:" +msgstr "" + +#: src/plugins/myspace/msgui.py:42 +msgid "Status Updates" +msgstr "" + +#: src/plugins/myspace/msgui.py:43 +msgid "Friend Updates" +msgstr "" + +#: src/plugins/myspace/msgui.py:44 +msgid "Blog/Forum Posts" +msgstr "" + +#: src/plugins/myspace/msgui.py:45 +msgid "Group Updates" +msgstr "" + +#: src/plugins/myspace/msgui.py:46 +msgid "Photos" +msgstr "" + +#: src/plugins/myspace/msgui.py:47 +msgid "Music" +msgstr "" + +#: src/plugins/myspace/msgui.py:48 +msgid "Videos" +msgstr "" + +#: src/plugins/myspace/msgui.py:50 +msgid "Applications" +msgstr "" + +#: src/plugins/myspace/msgui.py:76 +msgid "Event Invites" +msgstr "" + +#: src/plugins/myspace/msgui.py:77 +msgid "Profile Comments" +msgstr "" + +#: src/plugins/myspace/objects.py:301 +msgid "Private user" +msgstr "" + +#: src/plugins/myspace/objects.py:355 +msgid "Like! (via http://lnk.ms/C5dls)" +msgstr "" + +#: src/plugins/myspace/objects.py:356 +msgid "Dislike! (via http://lnk.ms/C5dls)" +msgstr "" + +#: src/plugins/myspace/res/content.tenjin:15 +msgid "Activity Stream" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:6 +msgid "New Mail" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:12 +msgid "New Birthdays" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:19 +msgid "New Blog Comments" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:26 +msgid "New Blog Subscriptions" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:33 +msgid "New Comments" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:40 +msgid "New Event Invitations" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:47 +msgid "New Friend Requests" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:54 +msgid "New Group Notifications" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:61 +msgid "New Photo Tag Approvals" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:68 +msgid "New Picture Comments" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:75 +msgid "New Recently Added Friends" +msgstr "" + +#: src/plugins/myspace/res/indicators.tenjin:82 +msgid "New Video Comments" +msgstr "" + +#: src/plugins/myspace/res/likes.tenjin:18 +#: src/plugins/myspace/res/likes.tenjin:32 +msgid "liked this" +msgstr "" + +#: src/plugins/nowplaying/nowplaying.py:29 +msgid "Listening to music" +msgstr "" + +#: src/plugins/nowplaying/nowplaying.py:182 +msgid "Listening To..." +msgstr "" + +#: src/plugins/provider_aol/aol_sp.py:79 +msgid "Password should be 8 characters or less." +msgstr "" + +#: src/plugins/provider_jabber/jabber_gui.py:33 +msgid "Use TLS if Possible" +msgstr "" + +#: src/plugins/provider_jabber/jabber_gui.py:34 +msgid "Require TLS" +msgstr "" + +#: src/plugins/provider_jabber/jabber_gui.py:35 +msgid "Force SSL" +msgstr "" + +#: src/plugins/provider_jabber/jabber_gui.py:36 +msgid "No Encryption" +msgstr "" + +#: src/plugins/provider_jabber/jabber_gui.py:50 +msgid "Ignore SSL Warnings" +msgstr "" + +#: src/plugins/provider_jabber/jabber_gui.py:54 +msgid "Allow Plaintext Login" +msgstr "" + +#: src/plugins/provider_windows_live/wl_sp.py:17 +msgid "Password should be 16 characters or less." +msgstr "" + +#: src/plugins/provider_windows_live/wl_sp.py:22 +msgid "You can't have your password as your display name." +msgstr "" + +#: src/plugins/researchdriver/researchtoast.py:30 +msgid "Help Digsby Stay Free" +msgstr "" + +#: src/plugins/researchdriver/researchtoast.py:32 +msgid "You are helping Digsby stay free by allowing Digsby to use your PC's idle time." +msgstr "" + +#: src/plugins/twitter/res/alerts.tenjin:6 +msgid "Search: " +msgstr "" + +#: src/plugins/twitter/res/alerts.tenjin:8 +msgid "Group: " +msgstr "" + +#: src/plugins/twitter/res/alerts.tenjin:25 +msgid "No alerts" +msgstr "" + +#: src/plugins/twitter/res/content.tenjin:34 +msgid "Trending Topics" +msgstr "" + +#: src/plugins/twitter/res/status.tenjin:20 +msgid "What are you doing?" +msgstr "" + +#: src/plugins/twitter/res/tweet.tenjin:80 +msgid "Share" +msgstr "" + +#: src/plugins/twitter/twgui.py:31 +msgid "Follow Digsby on Twitter" +msgstr "" + +#: src/plugins/twitter/twgui.py:37 +msgid "Post achievements to my feed" +msgstr "" + +#: src/plugins/twitter/twitter.py:216 +msgid "Followers" +msgstr "" + +#: src/plugins/twitter/twitter.py:217 +msgid "Following" +msgstr "" + +#: src/plugins/twitter/twitter.py:606 src/plugins/twitter/twitter_gui.py:702 +msgid "Favorites" +msgstr "" + +#: src/plugins/twitter/twitter.py:607 src/plugins/twitter/twitter_gui.py:703 +msgid "History" +msgstr "" + +#: src/plugins/twitter/twitter.py:851 +msgid "Reply" +msgstr "" + +#: src/plugins/twitter/twitter.py:852 +msgid "Retweet" +msgstr "" + +#: src/plugins/twitter/twitter.py:853 +msgid "Direct" +msgstr "" + +#: src/plugins/twitter/twitter.py:1207 +msgid "Twitter Error" +msgstr "" + +#: src/plugins/twitter/twitter.py:1208 +msgid "Send Tweet Failed" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:9 +msgid "Twitter allows for 150 requests per hour. Make sure to leave room for manual updates and other actions." +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:14 +msgid "Friends:" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:15 +msgid "Mentions:" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:16 +msgid "Directs:" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:17 +msgid "Searches:" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:35 +msgid "{mins} minute" +msgid_plural "{mins} minutes" +msgstr[0] "" +msgstr[1] "" + +#: src/plugins/twitter/twitter_account_gui.py:38 +msgid "Never" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:94 +msgid "Update Frequency:" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:111 +msgid "Auto-throttle when Twitter lowers the rate limit." +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:114 +msgid "Server" +msgstr "" + +#: src/plugins/twitter/twitter_account_gui.py:185 +msgid "{total_updates} / hour" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:123 +msgid "&Search" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:127 +msgid "Twitter Search" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:187 +msgid "Search For:" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:195 +msgid "Title:" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:201 +msgid "Trending Topics:" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:211 +msgid "Search &Options" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:212 +msgid "Merge search results into Timeline view" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:214 +msgid "Popup notifications for new search results" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:355 +msgid "Twitter Group" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:408 +msgid "&Group Name" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:413 +msgid "Group &Members" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:426 +msgid "Group &Options" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:427 +msgid "&Filter this group's tweets out of the Timeline view" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:429 +msgid "Show &popup notifications for this group's tweets" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:606 +msgid "&Hide Toolbar" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:612 +msgid "Shorten &Links" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:613 +msgid "Image" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:614 +msgid "Shrink" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:710 +msgid "New Group..." +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:711 +msgid "New Search..." +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:712 +msgid "Edit and Rearrange..." +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1006 +msgid "Shortening..." +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1102 +msgid "Are you sure you want to upload the image in your clipboard?" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1103 +msgid "Image Upload" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1385 +msgid "&Rearrange" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1387 +msgid "Edit and &Rearrange" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1390 +msgid "&Mark As Read" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1393 +msgid "&Adds to Unread Count" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1511 +msgid "Text Size" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1601 +msgid "Shorten URLs\tCtrl+L" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1602 +msgid "Share Picture\tCtrl+P" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1603 +msgid "TweetShrink\tCtrl+S" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1605 +msgid "Set Global Status\tCtrl+G" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1607 +msgid "Auto Shorten Pasted URLs" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1608 +msgid "Auto Upload Pasted Images" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1609 +msgid "Auto Scroll When At Bottom" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1730 +msgid "Your tweet has spelling errors." +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1731 +msgid "Are you sure you'd like to send it?" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1735 +msgid "Tweet spelling errors" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1738 +msgid "Send Anyways" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:1996 +msgid "Twitter Groups and Searches" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:2020 +msgid "Refresh Now" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:2021 +msgid "Mark All As Read" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:2046 +msgid "Set Status" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:2064 +#, python-format +msgid "Twitter (%s)" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:2290 +msgid "&Invite Followers" +msgstr "" + +#: src/plugins/twitter/twitter_gui.py:2291 +msgid "&No Thanks" +msgstr "" + +#: src/plugins/twitter/twitter_notifications.py:76 +#, python-format +msgid "Twitter - %(feed_label)s (%(username)s)" +msgstr "" + +#: src/plugins/twitter/twitter_notifications.py:78 +#, python-format +msgid "Twitter - Group: %(feed_label)s (%(username)s)" +msgstr "" + +#: src/plugins/twitter/twitter_util.py:83 +msgid "about a minute ago" +msgstr "" + +#: src/plugins/twitter/twitter_util.py:85 +msgid "about {minutes} minutes ago" +msgstr "" + +#: src/plugins/twitter/twitter_util.py:87 +msgid "about an hour ago" +msgstr "" + +#: src/plugins/twitter/twitter_util.py:89 +msgid "about {hours} hours ago" +msgstr "" + +#: src/plugins/twitter/twitter_util.py:91 +msgid "about a day ago" +msgstr "" + +#: src/plugins/twitter/twitter_util.py:93 +msgid "about {days} days ago" +msgstr "" + +#: src/util/diagnostic.py:584 src/util/diagnostic.py:941 +msgid "Submit Bug Report" +msgstr "" + +#: src/util/diagnostic.py:587 +msgid "Bug report submitted successfully." +msgstr "" + +#: src/util/diagnostic.py:589 +msgid "Bug report submission failed." +msgstr "" + +#: src/util/diagnostic.py:916 +msgid "Please wait while we process the diagnostic information." +msgstr "" + +#: src/util/diagnostic.py:917 +msgid "Thanks for your patience!" +msgstr "" + +#: src/util/diagnostic.py:919 +msgid "Processing Diagnostic Info" +msgstr "" + +#: src/util/diagnostic.py:922 +msgid "There was a problem submitting your bug report." +msgstr "" + +#: src/util/diagnostic.py:923 +msgid "If the problem persists, please email bugs@digsby.com" +msgstr "" + +#: src/util/diagnostic.py:930 +msgid "Bug report sent successfully." +msgstr "" + +#: src/util/httplib2/httplib2.py:343 +#, python-format +msgid "Content purported to be compressed with %s but failed to decompress." +msgstr "" + +#: src/util/httplib2/httplib2.py:502 +msgid "The challenge doesn't contain a server nonce, or this one is empty." +msgstr "" + +#: src/util/httplib2/httplib2.py:946 +msgid "Redirected but the response is missing a Location: header." +msgstr "" + +#: src/util/httplib2/httplib2.py:973 +msgid "Redirected more times than rediection_limit allows." +msgstr "" + +#: src/util/perfmon.py:127 +msgid "" +"A log of the problem has been sent to digsby.com.\n" +"\n" +"Thanks for helping!" +msgstr "" + +#: src/util/perfmon.py:128 +msgid "Diagnostic Log" +msgstr "" + +#: src/util/perfmon.py:132 +msgid "There was an error when submitting the diagnostic log." +msgstr "" + +#: src/util/perfmon.py:146 +msgid "Digsby appears to be running slowly." +msgstr "" + +#: src/util/perfmon.py:147 +msgid "Do you want to capture diagnostic information and send it to digsby.com?" +msgstr "" + +#: src/util/perfmon.py:151 +msgid "Digsby CPU Usage" +msgstr "" + +#: src/util/primitives/strings.py:587 +msgid "aeiou" +msgstr "" + +#: src/util/primitives/strings.py:588 +msgid "an" +msgstr "" + +#: src/util/primitives/strings.py:590 +msgid "a" +msgstr "" + +#: src/util/primitives/strings.py:592 +#, python-format +msgid "%(article)s %(s)s" +msgstr "" + +#: src/yahoo/YahooProtocol.py:1163 +msgid "May I add you to my contact list?" +msgstr "" + +#: src/yahoo/YahooProtocol.py:1323 +msgid "There was an error modifying stealth settings for {name}." +msgstr "" + +#: src/yahoo/login.py:35 +msgid "Bad Password" +msgstr "" + +#: src/yahoo/login.py:36 +msgid "There is a security lock on your account. Log in to http://my.yahoo.com and try again." +msgstr "" + +#: src/yahoo/login.py:37 +msgid "Account Not Set Up" +msgstr "" + +#: src/yahoo/login.py:38 +msgid "Bad Username" +msgstr "" + +#: src/yahoo/login.py:39 +msgid "Rate Limit" +msgstr "" + +#: src/yahoo/yahoobuddy.py:130 +msgid "No Updates" +msgstr "" + +#: src/yahoo/yahoobuddy.py:134 src/yahoo/yahoobuddy.py:137 +#: src/yahoo/yahoobuddy.py:151 src/yahoo/yahoobuddy.py:173 +msgid "Yahoo! 360:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:145 src/yahoo/yahoobuddy.py:167 +#: src/yahoo/yahoobuddy.py:174 src/yahoo/yahoobuddy.py:195 +#: src/yahoo/yahoobuddy.py:203 +msgid "Directory URL:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:152 +msgid "Real Name:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:153 +msgid "Nickname:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:155 +msgid "Age:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:156 +msgid "Sex:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:157 +msgid "Marital Status:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:158 +msgid "Occupation:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:159 +msgid "Email:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:160 +msgid "Home Page:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:161 src/yahoo/yahoobuddy.py:181 +msgid "Hobbies:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:162 src/yahoo/yahoobuddy.py:181 +msgid "Latest News:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:163 src/yahoo/yahoobuddy.py:181 +msgid "Favorite Quote:" +msgstr "" + +#: src/yahoo/yahoobuddy.py:165 src/yahoo/yahoobuddy.py:195 +msgid "Member Since " +msgstr "" + +#: src/yahoo/yahoobuddy.py:166 src/yahoo/yahoobuddy.py:195 +msgid "Last Update: " +msgstr "" + +#. $Rev: 30062 $ +msgctxt "__metadata__" +msgid "__version__" +msgstr "" + diff --git a/digsby/lib/aspell/bin/aspell-15.dll b/digsby/lib/aspell/bin/aspell-15.dll new file mode 100644 index 0000000..03ff2a9 Binary files /dev/null and b/digsby/lib/aspell/bin/aspell-15.dll differ diff --git a/digsby/lib/aspell/bin/aspell.exe b/digsby/lib/aspell/bin/aspell.exe new file mode 100644 index 0000000..47e3dd6 Binary files /dev/null and b/digsby/lib/aspell/bin/aspell.exe differ diff --git a/digsby/lib/aspell/bin/pspell-15.dll b/digsby/lib/aspell/bin/pspell-15.dll new file mode 100644 index 0000000..a441386 Binary files /dev/null and b/digsby/lib/aspell/bin/pspell-15.dll differ diff --git a/digsby/lib/aspell/bin/word-list-compress.exe b/digsby/lib/aspell/bin/word-list-compress.exe new file mode 100644 index 0000000..353cc89 Binary files /dev/null and b/digsby/lib/aspell/bin/word-list-compress.exe differ diff --git a/digsby/lib/aspell/data/ASCII.dat b/digsby/lib/aspell/data/ASCII.dat new file mode 100644 index 0000000..7e80b3c --- /dev/null +++ b/digsby/lib/aspell/data/ASCII.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +ASCII +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +208 letter 240 208 208 208 240 208 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +222 letter 254 222 222 222 254 222 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +240 letter 240 208 208 208 240 240 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +254 letter 254 222 222 222 254 254 +255 letter 255 255 255 0 121 121 diff --git a/digsby/lib/aspell/data/cp1250.dat b/digsby/lib/aspell/data/cp1250.dat new file mode 100644 index 0000000..fe87d22 --- /dev/null +++ b/digsby/lib/aspell/data/cp1250.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1250 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +57345 other 129 129 129 0 129 129 +8218 other 130 130 130 0 130 130 +57347 other 131 131 131 0 131 131 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +57352 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +352 letter 154 138 138 83 115 83 +8249 other 139 139 139 0 139 139 +346 letter 156 140 140 83 115 83 +356 letter 157 141 141 84 116 84 +381 letter 158 142 142 90 122 90 +377 letter 159 143 143 90 122 90 +57360 other 144 144 144 0 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +57368 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +353 letter 154 138 138 83 115 115 +8250 other 155 155 155 0 155 155 +347 letter 156 140 140 83 115 115 +357 letter 157 141 141 84 116 116 +382 letter 158 142 142 90 122 122 +378 letter 159 143 143 90 122 122 +160 space 160 160 160 0 160 160 +711 other 161 161 161 0 161 161 +728 other 162 162 162 0 162 162 +321 letter 179 163 163 76 108 76 +164 other 164 164 164 0 164 164 +260 letter 185 165 165 0 97 65 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +350 letter 186 170 170 83 115 83 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +379 letter 191 175 175 90 122 90 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +731 other 178 178 178 0 178 178 +322 letter 179 163 163 76 108 108 +180 other 180 180 180 0 180 180 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +261 letter 185 165 165 0 97 97 +351 letter 186 170 170 83 115 115 +187 other 187 187 187 0 187 187 +317 letter 190 188 188 76 108 76 +733 other 189 189 189 0 189 189 +318 letter 190 188 188 76 108 108 +380 letter 191 175 175 90 122 122 +340 letter 224 192 192 82 114 82 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +258 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +313 letter 229 197 197 76 108 76 +262 letter 230 198 198 67 99 67 +199 letter 231 199 199 67 99 67 +268 letter 232 200 200 67 99 67 +201 letter 233 201 201 0 101 69 +280 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +282 letter 236 204 204 0 101 69 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +270 letter 239 207 207 68 100 68 +272 letter 240 208 208 68 100 68 +323 letter 241 209 209 78 110 78 +327 letter 242 210 210 78 110 78 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +336 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +344 letter 248 216 216 82 114 82 +366 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +368 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +354 letter 254 222 222 84 116 84 +223 letter 223 223 223 223 223 223 +341 letter 224 192 192 82 114 114 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +259 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +314 letter 229 197 197 76 108 108 +263 letter 230 198 198 67 99 99 +231 letter 231 199 199 67 99 99 +269 letter 232 200 200 67 99 99 +233 letter 233 201 201 0 101 101 +281 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +283 letter 236 204 204 0 101 101 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +271 letter 239 207 207 68 100 100 +273 letter 240 208 208 68 100 100 +324 letter 241 209 209 78 110 110 +328 letter 242 210 210 78 110 110 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +337 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +345 letter 248 216 216 82 114 114 +367 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +369 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +355 letter 254 222 222 84 116 116 +729 other 255 255 255 0 255 255 diff --git a/digsby/lib/aspell/data/cp1251.dat b/digsby/lib/aspell/data/cp1251.dat new file mode 100644 index 0000000..0bc4874 --- /dev/null +++ b/digsby/lib/aspell/data/cp1251.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1251 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +1026 letter 144 128 128 128 144 128 +1027 letter 131 129 129 129 131 129 +8218 other 130 130 130 0 130 130 +1107 letter 131 129 129 129 131 131 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +8364 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +1033 letter 154 138 138 138 154 138 +8249 other 139 139 139 0 139 139 +1034 letter 156 140 140 140 156 140 +1036 letter 157 141 141 141 157 141 +1035 letter 158 142 142 142 158 142 +1039 letter 159 143 143 143 159 143 +1106 letter 144 128 128 128 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +57368 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +1113 letter 154 138 138 138 154 154 +8250 other 155 155 155 0 155 155 +1114 letter 156 140 140 140 156 156 +1116 letter 157 141 141 141 157 157 +1115 letter 158 142 142 142 158 158 +1119 letter 159 143 143 143 159 159 +160 space 160 160 160 0 160 160 +1038 letter 162 161 161 161 162 161 +1118 letter 162 161 161 161 162 162 +1032 letter 188 163 163 163 188 163 +164 other 164 164 164 0 164 164 +1168 letter 180 165 165 195 227 195 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +1025 letter 184 168 168 0 229 197 +169 other 169 169 169 0 169 169 +1028 letter 186 170 170 170 186 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +1031 letter 191 175 175 175 191 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +1030 letter 179 178 178 178 179 178 +1110 letter 179 178 178 178 179 179 +1169 letter 180 165 165 195 227 227 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +1105 letter 184 168 168 0 229 229 +8470 other 185 185 185 0 185 185 +1108 letter 186 170 170 170 186 186 +187 other 187 187 187 0 187 187 +1112 letter 188 163 163 163 188 188 +1029 letter 190 189 189 189 190 189 +1109 letter 190 189 189 189 190 190 +1111 letter 191 175 175 175 191 191 +1040 letter 224 192 192 0 224 192 +1041 letter 225 193 193 193 225 193 +1042 letter 226 194 194 194 226 194 +1043 letter 227 195 195 195 227 195 +1044 letter 228 196 196 196 228 196 +1045 letter 229 197 197 0 229 197 +1046 letter 230 198 198 198 230 198 +1047 letter 231 199 199 199 231 199 +1048 letter 232 200 200 0 232 200 +1049 letter 233 201 201 201 233 201 +1050 letter 234 202 202 202 234 202 +1051 letter 235 203 203 203 235 203 +1052 letter 236 204 204 204 236 204 +1053 letter 237 205 205 205 237 205 +1054 letter 238 206 206 0 238 206 +1055 letter 239 207 207 207 239 207 +1056 letter 240 208 208 208 240 208 +1057 letter 241 209 209 209 241 209 +1058 letter 242 210 210 210 242 210 +1059 letter 243 211 211 0 243 211 +1060 letter 244 212 212 212 244 212 +1061 letter 245 213 213 213 245 213 +1062 letter 246 214 214 214 246 214 +1063 letter 247 215 215 215 247 215 +1064 letter 248 216 216 216 248 216 +1065 letter 249 217 217 217 249 217 +1066 letter 250 218 218 218 250 218 +1067 letter 251 219 219 0 251 219 +1068 letter 252 220 220 220 252 220 +1069 letter 253 221 221 0 253 221 +1070 letter 254 222 222 0 254 222 +1071 letter 255 223 223 0 255 223 +1072 letter 224 192 192 0 224 224 +1073 letter 225 193 193 193 225 225 +1074 letter 226 194 194 194 226 226 +1075 letter 227 195 195 195 227 227 +1076 letter 228 196 196 196 228 228 +1077 letter 229 197 197 0 229 229 +1078 letter 230 198 198 198 230 230 +1079 letter 231 199 199 199 231 231 +1080 letter 232 200 200 0 232 232 +1081 letter 233 201 201 201 233 233 +1082 letter 234 202 202 202 234 234 +1083 letter 235 203 203 203 235 235 +1084 letter 236 204 204 204 236 236 +1085 letter 237 205 205 205 237 237 +1086 letter 238 206 206 0 238 238 +1087 letter 239 207 207 207 239 239 +1088 letter 240 208 208 208 240 240 +1089 letter 241 209 209 209 241 241 +1090 letter 242 210 210 210 242 242 +1091 letter 243 211 211 0 243 243 +1092 letter 244 212 212 212 244 244 +1093 letter 245 213 213 213 245 245 +1094 letter 246 214 214 214 246 246 +1095 letter 247 215 215 215 247 247 +1096 letter 248 216 216 216 248 248 +1097 letter 249 217 217 217 249 249 +1098 letter 250 218 218 218 250 250 +1099 letter 251 219 219 0 251 251 +1100 letter 252 220 220 220 252 252 +1101 letter 253 221 221 0 253 253 +1102 letter 254 222 222 0 254 254 +1103 letter 255 223 223 0 255 255 diff --git a/digsby/lib/aspell/data/cp1252.dat b/digsby/lib/aspell/data/cp1252.dat new file mode 100644 index 0000000..2878022 --- /dev/null +++ b/digsby/lib/aspell/data/cp1252.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1252 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +1027 letter 131 129 129 129 131 129 +8218 other 130 130 130 0 130 130 +402 letter 131 131 131 70 102 102 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +710 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +352 letter 154 138 138 83 115 83 +8249 other 139 139 139 0 139 139 +338 letter 156 140 140 140 156 140 +1036 letter 157 141 141 141 157 141 +381 letter 158 142 142 90 122 90 +1039 letter 159 143 143 143 159 143 +1106 letter 144 128 128 128 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +732 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +353 letter 154 138 138 83 115 115 +8250 other 155 155 155 0 155 155 +339 letter 156 140 140 140 156 156 +1116 letter 157 141 141 141 157 157 +382 letter 158 142 142 90 122 122 +376 letter 255 159 159 0 121 89 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +208 letter 240 208 208 208 240 208 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +222 letter 254 222 222 222 254 222 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +240 letter 240 208 208 208 240 240 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +254 letter 254 222 222 222 254 254 +255 letter 255 159 159 0 121 121 diff --git a/digsby/lib/aspell/data/cp1253.dat b/digsby/lib/aspell/data/cp1253.dat new file mode 100644 index 0000000..10414c1 --- /dev/null +++ b/digsby/lib/aspell/data/cp1253.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1253 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +1027 letter 131 129 129 129 131 129 +8218 other 130 130 130 0 130 130 +402 letter 131 131 131 70 102 102 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +710 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +352 letter 154 138 138 83 115 83 +8249 other 139 139 139 0 139 139 +338 letter 156 140 140 140 156 140 +1036 letter 157 141 141 141 157 141 +381 letter 158 142 142 90 122 90 +1039 letter 159 143 143 143 159 143 +1106 letter 144 128 128 128 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +732 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +353 letter 154 138 138 83 115 115 +8250 other 155 155 155 0 155 155 +339 letter 156 140 140 140 156 156 +1116 letter 157 141 141 141 157 157 +382 letter 158 142 142 90 122 122 +376 letter 255 159 159 0 121 89 +160 space 160 160 160 0 160 160 +901 other 161 161 161 0 161 161 +902 letter 220 162 162 193 225 193 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +8213 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +900 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +904 letter 221 184 184 197 229 197 +905 letter 222 185 185 199 231 199 +906 letter 223 186 186 201 233 201 +187 other 187 187 187 0 187 187 +908 letter 252 188 188 207 239 207 +189 other 189 189 189 0 189 189 +910 letter 253 190 190 213 245 213 +911 letter 254 191 191 217 249 217 +912 letter 192 192 192 192 192 192 +913 letter 225 193 193 193 225 193 +914 letter 226 194 194 194 226 194 +915 letter 227 195 195 195 227 195 +916 letter 228 196 196 196 228 196 +917 letter 229 197 197 197 229 197 +918 letter 230 198 198 198 230 198 +919 letter 231 199 199 199 231 199 +920 letter 232 200 200 200 232 200 +921 letter 233 201 201 201 233 201 +922 letter 234 202 202 202 234 202 +923 letter 235 203 203 203 235 203 +924 letter 236 204 204 204 236 204 +925 letter 237 205 205 205 237 205 +926 letter 238 206 206 206 238 206 +927 letter 239 207 207 207 239 207 +928 letter 240 208 208 208 240 208 +929 letter 241 209 209 209 241 209 +210 letter 242 210 210 0 111 79 +931 letter 243 211 211 211 243 211 +932 letter 244 212 212 212 244 212 +933 letter 245 213 213 213 245 213 +934 letter 246 214 214 214 246 214 +935 letter 247 215 215 215 247 215 +936 letter 248 216 216 216 248 216 +937 letter 249 217 217 217 249 217 +938 letter 250 218 218 201 233 201 +939 letter 251 219 219 213 245 213 +940 letter 220 162 162 193 225 225 +941 letter 221 184 184 197 229 229 +942 letter 222 185 185 199 231 231 +943 letter 223 186 186 201 233 233 +944 letter 224 224 224 224 224 224 +945 letter 225 193 193 193 225 225 +946 letter 226 194 194 194 226 226 +947 letter 227 195 195 195 227 227 +948 letter 228 196 196 196 228 228 +949 letter 229 197 197 197 229 229 +950 letter 230 198 198 198 230 230 +951 letter 231 199 199 199 231 231 +952 letter 232 200 200 200 232 232 +953 letter 233 201 201 201 233 233 +954 letter 234 202 202 202 234 234 +955 letter 235 203 203 203 235 235 +956 letter 236 204 204 204 236 236 +957 letter 237 205 205 205 237 237 +958 letter 238 206 206 206 238 238 +959 letter 239 207 207 207 239 239 +960 letter 240 208 208 208 240 240 +961 letter 241 209 209 209 241 241 +962 letter 242 211 211 211 243 243 +963 letter 243 211 211 211 243 243 +964 letter 244 212 212 212 244 244 +965 letter 245 213 213 213 245 245 +966 letter 246 214 214 214 246 246 +967 letter 247 215 215 215 247 247 +968 letter 248 216 216 216 248 248 +969 letter 249 217 217 217 249 249 +970 letter 250 218 218 201 233 233 +971 letter 251 219 219 213 245 245 +972 letter 252 188 188 207 239 239 +973 letter 253 190 190 213 245 245 +974 letter 254 191 191 217 249 249 +255 letter 255 159 159 0 121 121 diff --git a/digsby/lib/aspell/data/cp1254.dat b/digsby/lib/aspell/data/cp1254.dat new file mode 100644 index 0000000..47ac1bd --- /dev/null +++ b/digsby/lib/aspell/data/cp1254.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1254 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +1027 letter 131 129 129 129 131 129 +8218 other 130 130 130 0 130 130 +402 letter 131 131 131 70 102 102 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +710 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +352 letter 154 138 138 83 115 83 +8249 other 139 139 139 0 139 139 +338 letter 156 140 140 140 156 140 +1036 letter 157 141 141 141 157 141 +381 letter 158 142 142 90 122 90 +1039 letter 159 143 143 143 159 143 +1106 letter 144 128 128 128 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +732 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +353 letter 154 138 138 83 115 115 +8250 other 155 155 155 0 155 155 +339 letter 156 140 140 140 156 156 +1116 letter 157 141 141 141 157 157 +382 letter 158 142 142 90 122 122 +376 letter 255 159 159 0 121 89 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +286 letter 240 208 208 71 103 71 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +304 letter 105 221 221 0 105 73 +350 letter 254 222 222 83 115 83 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +287 letter 240 208 208 71 103 103 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +305 letter 253 73 73 0 105 105 +351 letter 254 222 222 83 115 115 +255 letter 255 159 159 0 121 121 diff --git a/digsby/lib/aspell/data/cp1255.dat b/digsby/lib/aspell/data/cp1255.dat new file mode 100644 index 0000000..9c6f7ab --- /dev/null +++ b/digsby/lib/aspell/data/cp1255.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1255 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +1027 letter 131 129 129 129 131 129 +8218 other 130 130 130 0 130 130 +402 letter 131 131 131 70 102 102 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +710 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +352 letter 154 138 138 83 115 83 +8249 other 139 139 139 0 139 139 +338 letter 156 140 140 140 156 140 +1036 letter 157 141 141 141 157 141 +381 letter 158 142 142 90 122 90 +1039 letter 159 143 143 143 159 143 +1106 letter 144 128 128 128 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +732 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +353 letter 154 138 138 83 115 115 +8250 other 155 155 155 0 155 155 +339 letter 156 140 140 140 156 156 +1116 letter 157 141 141 141 157 157 +382 letter 158 142 142 90 122 122 +376 letter 255 159 159 0 121 89 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +8362 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +215 other 170 170 170 0 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +247 other 186 186 186 0 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +1456 other 192 192 192 0 192 192 +1457 other 193 193 193 0 193 193 +1458 other 194 194 194 0 194 194 +1459 other 195 195 195 0 195 195 +1460 other 196 196 196 0 196 196 +1461 other 197 197 197 0 197 197 +1462 other 198 198 198 0 198 198 +1463 other 199 199 199 0 199 199 +1464 other 200 200 200 0 200 200 +1465 other 201 201 201 0 201 201 +202 letter 234 202 202 0 101 69 +1467 other 203 203 203 0 203 203 +1468 other 204 204 204 0 204 204 +1469 other 205 205 205 0 205 205 +1470 other 206 206 206 0 206 206 +1471 other 207 207 207 0 207 207 +1472 other 208 208 208 0 208 208 +1473 other 209 209 209 0 209 209 +1474 other 210 210 210 0 210 210 +1475 other 211 211 211 0 211 211 +1520 letter 212 212 212 212 212 212 +1521 letter 213 213 213 213 213 213 +1522 letter 214 214 214 214 214 214 +1523 other 215 215 215 0 215 215 +1524 other 216 216 216 0 216 216 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +304 letter 105 221 221 0 105 73 +350 letter 254 222 222 83 115 83 +223 letter 223 223 223 223 223 223 +1488 letter 224 224 224 224 224 224 +1489 letter 225 225 225 225 225 225 +1490 letter 226 226 226 226 226 226 +1491 letter 227 227 227 227 227 227 +1492 letter 228 228 228 228 228 228 +1493 letter 229 229 229 229 229 229 +1494 letter 230 230 230 230 230 230 +1495 letter 231 231 231 231 231 231 +1496 letter 232 232 232 232 232 232 +1497 letter 233 233 233 233 233 233 +1498 letter 234 234 234 234 234 234 +1499 letter 235 235 235 235 235 235 +1500 letter 236 236 236 236 236 236 +1501 letter 237 237 237 237 237 237 +1502 letter 238 238 238 238 238 238 +1503 letter 239 239 239 239 239 239 +1504 letter 240 240 240 240 240 240 +1505 letter 241 241 241 241 241 241 +1506 letter 242 242 242 242 242 242 +1507 letter 243 243 243 243 243 243 +1508 letter 244 244 244 244 244 244 +1509 letter 245 245 245 245 245 245 +1510 letter 246 246 246 246 246 246 +1511 letter 247 247 247 247 247 247 +1512 letter 248 248 248 248 248 248 +1513 letter 249 249 249 249 249 249 +1514 letter 250 250 250 250 250 250 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +8206 other 253 253 253 0 253 253 +8207 other 254 254 254 0 254 254 +255 letter 255 159 159 0 121 121 diff --git a/digsby/lib/aspell/data/cp1256.dat b/digsby/lib/aspell/data/cp1256.dat new file mode 100644 index 0000000..1a2e410 --- /dev/null +++ b/digsby/lib/aspell/data/cp1256.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1256 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +1662 letter 129 129 129 129 129 129 +8218 other 130 130 130 0 130 130 +402 letter 131 131 131 70 102 102 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +710 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +1657 letter 138 138 138 138 138 138 +8249 other 139 139 139 0 139 139 +338 letter 156 140 140 140 156 140 +1670 letter 141 141 141 141 141 141 +1688 letter 142 142 142 142 142 142 +1672 letter 143 143 143 143 143 143 +1711 letter 144 144 144 144 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +1705 letter 152 152 152 152 152 152 +8482 other 153 153 153 0 153 153 +1681 letter 154 154 154 154 154 154 +8250 other 155 155 155 0 155 155 +339 letter 156 140 140 140 156 156 +8204 other 157 157 157 0 157 157 +8205 other 158 158 158 0 158 158 +1722 letter 159 159 159 159 159 159 +160 space 160 160 160 0 160 160 +1548 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +1726 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +1563 other 186 186 186 0 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +1567 other 191 191 191 0 191 191 +1729 letter 192 192 192 192 192 192 +1569 letter 193 193 193 193 193 193 +1570 letter 194 194 194 199 199 199 +1571 letter 195 195 195 199 199 199 +1572 letter 196 196 196 230 230 230 +1573 letter 197 197 197 199 199 199 +1574 letter 198 198 198 237 237 237 +1575 letter 199 199 199 199 199 199 +1576 letter 200 200 200 200 200 200 +1577 letter 201 201 201 201 201 201 +1578 letter 202 202 202 202 202 202 +1579 letter 203 203 203 203 203 203 +1580 letter 204 204 204 204 204 204 +1581 letter 205 205 205 205 205 205 +1582 letter 206 206 206 206 206 206 +1583 letter 207 207 207 207 207 207 +1584 letter 208 208 208 208 208 208 +1585 letter 209 209 209 209 209 209 +1586 letter 210 210 210 210 210 210 +1587 letter 211 211 211 211 211 211 +1588 letter 212 212 212 212 212 212 +1589 letter 213 213 213 213 213 213 +1590 letter 214 214 214 214 214 214 +215 other 215 215 215 0 215 215 +1591 letter 216 216 216 216 216 216 +1592 letter 217 217 217 217 217 217 +1593 letter 218 218 218 218 218 218 +1594 letter 219 219 219 219 219 219 +1600 other 220 220 220 0 220 220 +1601 letter 221 221 221 221 221 221 +1602 letter 222 222 222 222 222 222 +1603 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +1604 letter 225 225 225 225 225 225 +226 letter 226 194 194 0 97 97 +1605 letter 227 227 227 227 227 227 +1606 letter 228 228 228 228 228 228 +1607 letter 229 229 229 229 229 229 +1608 letter 230 230 230 230 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +1609 letter 236 236 236 236 236 236 +1610 letter 237 237 237 237 237 237 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +1611 other 240 240 240 0 240 240 +1612 other 241 241 241 0 241 241 +1613 other 242 242 242 0 242 242 +1614 other 243 243 243 0 243 243 +244 letter 244 212 212 0 111 111 +1615 other 245 245 245 0 245 245 +1616 other 246 246 246 0 246 246 +247 other 247 247 247 0 247 247 +1617 other 248 248 248 0 248 248 +249 letter 249 217 217 0 117 117 +1618 other 250 250 250 0 250 250 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +8206 other 253 253 253 0 253 253 +8207 other 254 254 254 0 254 254 +1746 letter 255 255 255 255 255 255 diff --git a/digsby/lib/aspell/data/cp1257.dat b/digsby/lib/aspell/data/cp1257.dat new file mode 100644 index 0000000..cb2048a --- /dev/null +++ b/digsby/lib/aspell/data/cp1257.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1257 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +1662 letter 129 129 129 129 129 129 +8218 other 130 130 130 0 130 130 +402 letter 131 131 131 70 102 102 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +710 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +1657 letter 138 138 138 138 138 138 +8249 other 139 139 139 0 139 139 +338 letter 156 140 140 140 156 140 +168 other 141 141 141 0 141 141 +711 other 142 142 142 0 142 142 +184 other 143 143 143 0 143 143 +1711 letter 144 144 144 144 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +1705 letter 152 152 152 152 152 152 +8482 other 153 153 153 0 153 153 +1681 letter 154 154 154 154 154 154 +8250 other 155 155 155 0 155 155 +339 letter 156 140 140 140 156 156 +175 other 157 157 157 0 157 157 +731 other 158 158 158 0 158 158 +1722 letter 159 159 159 159 159 159 +160 space 160 160 160 0 160 160 +1548 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +216 letter 184 168 168 0 111 79 +169 other 169 169 169 0 169 169 +342 letter 186 170 170 82 114 82 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +198 letter 191 175 175 0 191 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +248 letter 184 168 168 0 111 111 +185 other 185 185 185 0 185 185 +343 letter 186 170 170 82 114 114 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +230 letter 191 175 175 0 191 191 +260 letter 224 192 192 0 97 65 +302 letter 225 193 193 0 105 73 +256 letter 226 194 194 0 97 65 +262 letter 227 195 195 67 99 67 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +280 letter 230 198 198 0 101 69 +274 letter 231 199 199 0 101 69 +268 letter 232 200 200 67 99 67 +201 letter 233 201 201 0 101 69 +377 letter 234 202 202 90 122 90 +278 letter 235 203 203 0 101 69 +290 letter 236 204 204 71 103 71 +310 letter 237 205 205 75 107 75 +298 letter 238 206 206 0 105 73 +315 letter 239 207 207 76 108 76 +352 letter 240 208 208 83 115 83 +323 letter 241 209 209 78 110 78 +325 letter 242 210 210 78 110 78 +211 letter 243 211 211 0 111 79 +332 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +370 letter 248 216 216 0 117 85 +321 letter 249 217 217 76 108 76 +346 letter 250 218 218 83 115 83 +362 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +379 letter 253 221 221 90 122 90 +381 letter 254 222 222 90 122 90 +223 letter 223 223 223 223 223 223 +261 letter 224 192 192 0 97 97 +303 letter 225 193 193 0 105 105 +257 letter 226 194 194 0 97 97 +263 letter 227 195 195 67 99 99 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +281 letter 230 198 198 0 101 101 +275 letter 231 199 199 0 101 101 +269 letter 232 200 200 67 99 99 +233 letter 233 201 201 0 101 101 +378 letter 234 202 202 90 122 122 +279 letter 235 203 203 0 101 101 +291 letter 236 204 204 71 103 103 +311 letter 237 205 205 75 107 107 +299 letter 238 206 206 0 105 105 +316 letter 239 207 207 76 108 108 +353 letter 240 208 208 83 115 115 +324 letter 241 209 209 78 110 110 +326 letter 242 210 210 78 110 110 +243 letter 243 211 211 0 111 111 +333 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +371 letter 248 216 216 0 117 117 +322 letter 249 217 217 76 108 108 +347 letter 250 218 218 83 115 115 +363 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +380 letter 253 221 221 90 122 122 +382 letter 254 222 222 90 122 122 +729 other 255 255 255 0 255 255 diff --git a/digsby/lib/aspell/data/cp1258.dat b/digsby/lib/aspell/data/cp1258.dat new file mode 100644 index 0000000..82c8da4 --- /dev/null +++ b/digsby/lib/aspell/data/cp1258.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +cp1258 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +8364 other 128 128 128 0 128 128 +1662 letter 129 129 129 129 129 129 +8218 other 130 130 130 0 130 130 +402 letter 131 131 131 70 102 102 +8222 other 132 132 132 0 132 132 +8230 other 133 133 133 0 133 133 +8224 other 134 134 134 0 134 134 +8225 other 135 135 135 0 135 135 +710 other 136 136 136 0 136 136 +8240 other 137 137 137 0 137 137 +1657 letter 138 138 138 138 138 138 +8249 other 139 139 139 0 139 139 +338 letter 156 140 140 140 156 140 +168 other 168 168 168 0 168 168 +711 other 142 142 142 0 142 142 +184 other 184 184 184 0 184 184 +1711 letter 144 144 144 144 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +732 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +1681 letter 154 154 154 154 154 154 +8250 other 155 155 155 0 155 155 +339 letter 156 140 140 140 156 156 +175 other 175 175 175 0 175 175 +731 other 158 158 158 0 158 158 +376 letter 255 159 159 0 121 89 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +258 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +768 other 204 204 204 0 204 204 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +272 letter 240 208 208 68 100 68 +209 letter 241 209 209 78 110 78 +777 other 210 210 210 0 210 210 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +416 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +431 letter 253 221 221 0 117 85 +771 other 222 222 222 0 222 222 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +259 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +769 other 236 236 236 0 236 236 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +273 letter 240 208 208 68 100 100 +241 letter 241 209 209 78 110 110 +803 other 242 242 242 0 242 242 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +417 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +432 letter 253 221 221 0 117 117 +8363 other 254 254 254 0 254 254 +255 letter 255 159 159 0 121 121 diff --git a/digsby/lib/aspell/data/dvorak.kbd b/digsby/lib/aspell/data/dvorak.kbd new file mode 100644 index 0000000..c2707d0 --- /dev/null +++ b/digsby/lib/aspell/data/dvorak.kbd @@ -0,0 +1,25 @@ +# Dvorak keyboard data file + +py +yf +fg +gc +cr +rl +ao +oe +eu +ui +id +dh +ht +tn +ns +qj +jk +kx +xb +bm +mw +wv +vz diff --git a/digsby/lib/aspell/data/en.dat b/digsby/lib/aspell/data/en.dat new file mode 100644 index 0000000..cbbe1f6 --- /dev/null +++ b/digsby/lib/aspell/data/en.dat @@ -0,0 +1,5 @@ +# Generated with Aspell Dicts "proc" script version 0.50.1 +name en +charset iso8859-1 +special ' -*- +soundslike en diff --git a/digsby/lib/aspell/data/en_phonet.dat b/digsby/lib/aspell/data/en_phonet.dat new file mode 100644 index 0000000..9095c85 --- /dev/null +++ b/digsby/lib/aspell/data/en_phonet.dat @@ -0,0 +1,250 @@ +# phonetic_english.h - phonetic transformation rules for use with phonetic.c +# Copyright (C) 2000 Bjrn Jacke +# +# This rule set is based on Lawrence Phillips original metaphone +# algorithm with modifications made by Michael Kuhn in his +# C implantation, more modifications by Bjrn Jacke when +# converting the algorithm to a rule set and minor +# touch ups by Kevin Atkinson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 2.1 as published by the Free Software Foundation; +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Bjrn Jacke may be reached by email at bjoern.jacke@gmx.de +# +# Changelog: +# +# 2000-01-05 Bjrn Jacke +# - first version with translation rules derived from +# metaphone.cc distributed with aspell 0.28.3 +# - "TH" is now representated as "@" because "0" is a +# meta character +# - removed TH(!vowel) --> T; always use TH --> # instead +# - dropped "^AE" -> "E" (redundant) +# - "ing" is transformed to "N", not "NK" +# - "SCH(EO)" transforms to "SK" now +# - added R --> SILENT if (after a vowel) and no (vowel or +# "y" follows) like in "Marcy" or "abort" +# - H is SILENT in RH at beginning of words +# - H is SILENT if vowel leads and "Y" follows +# - some ".OUGH.." --> ...F exceptions added +# - "^V" transforms to "W" +# 2000-01-07 Kevin Atkinson +# Converted from header to data file. +# + +version 1.1 + +AH(AEIOUY)-^ *H +AR(AEIOUY)-^ *R +A(HR)^ * +A^ * +AH(AEIOUY)- H +AR(AEIOUY)- R +A(HR) _ +BB- _ +B B +CQ- _ +CIA X +CH X +C(EIY)- S +CK K +COUGH^ KF +CC< C +C K +DG(EIY) K +DD- _ +D T +< E +EH(AEIOUY)-^ *H +ER(AEIOUY)-^ *R +E(HR)^ * +ENOUGH^$ *NF +E^ * +EH(AEIOUY)- H +ER(AEIOUY)- R +E(HR) _ +FF- _ +F F +GN^ N +GN$ N +GNS$ NS +GNED$ N +GH(AEIOUY)- K +GH _ +GG9 K +G K +H H +IH(AEIOUY)-^ *H +IR(AEIOUY)-^ *R +I(HR)^ * +I^ * +ING6 N +IH(AEIOUY)- H +IR(AEIOUY)- R +I(HR) _ +J K +KN^ N +KK- _ +K K +LAUGH^ LF +LL- _ +L L +MB$ M +MM M +M M +NN- _ +N N +OH(AEIOUY)-^ *H +OR(AEIOUY)-^ *R +O(HR)^ * +O^ * +OH(AEIOUY)- H +OR(AEIOUY)- R +O(HR) _ +PH F +PN^ N +PP- _ +P P +Q K +RH^ R +ROUGH^ RF +RR- _ +R R +SCH(EOU)- SK +SC(IEY)- S +SH X +SI(AO)- X +SS- _ +S S +TI(AO)- X +TH @ +TCH-- _ +TOUGH^ TF +TT- _ +T T +UH(AEIOUY)-^ *H +UR(AEIOUY)-^ *R +U(HR)^ * +U^ * +UH(AEIOUY)- H +UR(AEIOUY)- R +U(HR) _ +V^ W +V F +WR^ R +WH^ W +W(AEIOU)- W +X^ S +X KS +Y(AEIOU)- Y +ZZ- _ +Z S + +#The rules in a different view: +# +# Exceptions: +# +# Beginning of word: "gn", "kn-", "pn-", "wr-" ----> drop first letter +# "Aebersold", "Gnagy", "Knuth", "Pniewski", "Wright" +# +# Beginning of word: "x" ----> change to "s" +# as in "Deng Xiaopeng" +# +# Beginning of word: "wh-" ----> change to "w" +# as in "Whalen" +# Beginning of word: leading vowels are transformed to "*" +# +# "[crt]ough" and "enough" are handled separately because of "F" sound +# +# +# A --> A at beginning +# _ otherwise +# +# B --> B unless at the end of word after "m", as in "dumb", "McComb" +# +# C --> X (sh) if "-cia-" or "-ch-" +# S if "-ci-", "-ce-", or "-cy-" +# SILENT if "-sci-", "-sce-", or "-scy-", or "-cq-" +# K otherwise, including in "-sch-" +# +# D --> K if in "-dge-", "-dgy-", or "-dgi-" +# T otherwise +# +# E --> A at beginnig +# _ SILENT otherwise +# +# F --> F +# +# G --> SILENT if in "-gh-" and not at end or before a vowel +# in "-gn" or "-gned" or "-gns" +# in "-dge-" etc., as in above rule +# K if before "i", or "e", or "y" if not double "gg" +# +# K otherwise (incl. "GG"!) +# +# H --> SILENT if after vowel and no vowel or "Y" follows +# or after "-ch-", "-sh-", "-ph-", "-th-", "-gh-" +# or after "rh-" at beginning +# H otherwise +# +# I --> A at beginning +# _ SILENT otherwise +# +# J --> K +# +# K --> SILENT if after "c" +# K otherwise +# +# L --> L +# +# M --> M +# +# N --> N +# +# O --> A at beginning +# _ SILENT otherwise +# +# P --> F if before "h" +# P otherwise +# +# Q --> K +# +# R --> SILENT if after vowel and no vowel or "Y" follows +# R otherwise +# +# S --> X (sh) if before "h" or in "-sio-" or "-sia-" +# SK if followed by "ch(eo)" (SCH(EO)) +# S otherwise +# +# T --> X (sh) if "-tia-" or "-tio-" +# 0 (th) if before "h" +# silent if in "-tch-" +# T otherwise +# +# U --> A at beginning +# _ SILENT otherwise +# +# V --> V if first letter of word +# F otherwise +# +# W --> SILENT if not followed by a vowel +# W if followed by a vowel +# +# X --> KS +# +# Y --> SILENT if not followed by a vowel +# Y if followed by a vowel +# +# Z --> S + diff --git a/digsby/lib/aspell/data/iso8859-1.dat b/digsby/lib/aspell/data/iso8859-1.dat new file mode 100644 index 0000000..abaea94 --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-1.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-1 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +208 letter 240 208 208 208 240 208 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +222 letter 254 222 222 222 254 222 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +240 letter 240 208 208 208 240 240 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +254 letter 254 222 222 222 254 254 +255 letter 255 255 255 0 121 121 diff --git a/digsby/lib/aspell/data/iso8859-10.dat b/digsby/lib/aspell/data/iso8859-10.dat new file mode 100644 index 0000000..04ac39e --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-10.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-10 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +260 letter 177 161 161 0 97 65 +274 letter 178 162 162 0 101 69 +290 letter 179 163 163 71 103 71 +298 letter 180 164 164 0 105 73 +296 letter 181 165 165 0 105 73 +310 letter 182 166 166 75 107 75 +167 other 167 167 167 0 167 167 +315 letter 184 168 168 76 108 76 +272 letter 185 169 169 68 100 68 +352 letter 186 170 170 83 115 83 +358 letter 187 171 171 84 116 84 +381 letter 188 172 172 90 122 90 +173 other 173 173 173 0 173 173 +362 letter 190 174 174 0 117 85 +330 letter 191 175 175 175 191 175 +176 other 176 176 176 0 176 176 +261 letter 177 161 161 0 97 97 +275 letter 178 162 162 0 101 101 +291 letter 179 163 163 71 103 103 +299 letter 180 164 164 0 105 105 +297 letter 181 165 165 0 105 105 +311 letter 182 166 166 75 107 107 +183 other 183 183 183 0 183 183 +316 letter 184 168 168 76 108 108 +273 letter 185 169 169 68 100 100 +353 letter 186 170 170 83 115 115 +359 letter 187 171 171 84 116 116 +382 letter 188 172 172 90 122 122 +8213 other 189 189 189 0 189 189 +363 letter 190 174 174 0 117 117 +331 letter 191 175 175 175 191 191 +256 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +302 letter 231 199 199 0 105 73 +268 letter 232 200 200 67 99 67 +201 letter 233 201 201 0 101 69 +280 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +278 letter 236 204 204 0 101 69 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +208 letter 240 208 208 208 240 208 +325 letter 241 209 209 78 110 78 +332 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +360 letter 247 215 215 0 117 85 +216 letter 248 216 216 0 111 79 +370 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +222 letter 254 222 222 222 254 222 +223 letter 223 223 223 223 223 223 +257 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +303 letter 231 199 199 0 105 105 +269 letter 232 200 200 67 99 99 +233 letter 233 201 201 0 101 101 +281 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +279 letter 236 204 204 0 101 101 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +240 letter 240 208 208 208 240 240 +326 letter 241 209 209 78 110 110 +333 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +361 letter 247 215 215 0 117 117 +248 letter 248 216 216 0 111 111 +371 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +254 letter 254 222 222 222 254 254 +312 letter 255 255 255 255 255 255 diff --git a/digsby/lib/aspell/data/iso8859-13.dat b/digsby/lib/aspell/data/iso8859-13.dat new file mode 100644 index 0000000..f9b13eb --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-13.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-13 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +8221 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +8222 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +216 letter 184 168 168 0 111 79 +169 other 169 169 169 0 169 169 +342 letter 186 170 170 82 114 82 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +198 letter 191 175 175 0 191 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +8220 other 180 180 180 0 180 180 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +248 letter 184 168 168 0 111 111 +185 other 185 185 185 0 185 185 +343 letter 186 170 170 82 114 114 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +230 letter 191 175 175 0 191 191 +260 letter 224 192 192 0 97 65 +302 letter 225 193 193 0 105 73 +256 letter 226 194 194 0 97 65 +262 letter 227 195 195 67 99 67 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +280 letter 230 198 198 0 101 69 +274 letter 231 199 199 0 101 69 +268 letter 232 200 200 67 99 67 +201 letter 233 201 201 0 101 69 +377 letter 234 202 202 90 122 90 +278 letter 235 203 203 0 101 69 +290 letter 236 204 204 71 103 71 +310 letter 237 205 205 75 107 75 +298 letter 238 206 206 0 105 73 +315 letter 239 207 207 76 108 76 +352 letter 240 208 208 83 115 83 +323 letter 241 209 209 78 110 78 +325 letter 242 210 210 78 110 78 +211 letter 243 211 211 0 111 79 +332 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +370 letter 248 216 216 0 117 85 +321 letter 249 217 217 76 108 76 +346 letter 250 218 218 83 115 83 +362 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +379 letter 253 221 221 90 122 90 +381 letter 254 222 222 90 122 90 +223 letter 223 223 223 223 223 223 +261 letter 224 192 192 0 97 97 +303 letter 225 193 193 0 105 105 +257 letter 226 194 194 0 97 97 +263 letter 227 195 195 67 99 99 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +281 letter 230 198 198 0 101 101 +275 letter 231 199 199 0 101 101 +269 letter 232 200 200 67 99 99 +233 letter 233 201 201 0 101 101 +378 letter 234 202 202 90 122 122 +279 letter 235 203 203 0 101 101 +291 letter 236 204 204 71 103 103 +311 letter 237 205 205 75 107 107 +299 letter 238 206 206 0 105 105 +316 letter 239 207 207 76 108 108 +353 letter 240 208 208 83 115 115 +324 letter 241 209 209 78 110 110 +326 letter 242 210 210 78 110 110 +243 letter 243 211 211 0 111 111 +333 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +371 letter 248 216 216 0 117 117 +322 letter 249 217 217 76 108 108 +347 letter 250 218 218 83 115 115 +363 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +380 letter 253 221 221 90 122 122 +382 letter 254 222 222 90 122 122 +8217 other 255 255 255 0 255 255 diff --git a/digsby/lib/aspell/data/iso8859-14.dat b/digsby/lib/aspell/data/iso8859-14.dat new file mode 100644 index 0000000..0d4d32c --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-14.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-14 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +7682 letter 162 161 161 66 98 66 +7683 letter 162 161 161 66 98 98 +163 other 163 163 163 0 163 163 +266 letter 165 164 164 67 99 67 +267 letter 165 164 164 67 99 99 +7690 letter 171 166 166 68 100 68 +167 other 167 167 167 0 167 167 +7808 letter 184 168 168 87 119 87 +169 other 169 169 169 0 169 169 +7810 letter 186 170 170 87 119 87 +7691 letter 171 166 166 68 100 100 +7922 letter 188 172 172 0 121 89 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +376 letter 255 175 175 0 121 89 +7710 letter 177 176 176 70 102 70 +7711 letter 177 176 176 70 102 102 +288 letter 179 178 178 71 103 71 +289 letter 179 178 178 71 103 103 +7744 letter 181 180 180 77 109 77 +7745 letter 181 180 180 77 109 109 +182 other 182 182 182 0 182 182 +7766 letter 185 183 183 80 112 80 +7809 letter 184 168 168 87 119 119 +7767 letter 185 183 183 80 112 112 +7811 letter 186 170 170 87 119 119 +7776 letter 191 187 187 83 115 83 +7923 letter 188 172 172 0 121 121 +7812 letter 190 189 189 87 119 87 +7813 letter 190 189 189 87 119 119 +7777 letter 191 187 187 83 115 115 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +372 letter 240 208 208 87 119 87 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +7786 letter 247 215 215 84 116 84 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +374 letter 254 222 222 0 121 89 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +373 letter 240 208 208 87 119 119 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +7787 letter 247 215 215 84 116 116 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +375 letter 254 222 222 0 121 121 +255 letter 255 175 175 0 121 121 diff --git a/digsby/lib/aspell/data/iso8859-15.dat b/digsby/lib/aspell/data/iso8859-15.dat new file mode 100644 index 0000000..6397191 --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-15.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-15 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +8364 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +352 letter 168 166 166 83 115 83 +167 other 167 167 167 0 167 167 +353 letter 168 166 166 83 115 115 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +381 letter 184 180 180 90 122 90 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +382 letter 184 180 180 90 122 122 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +187 other 187 187 187 0 187 187 +338 letter 189 188 188 188 189 188 +339 letter 189 188 188 188 189 189 +376 letter 255 190 190 0 121 89 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +208 letter 240 208 208 208 240 208 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +222 letter 254 222 222 222 254 222 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +240 letter 240 208 208 208 240 240 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +254 letter 254 222 222 222 254 254 +255 letter 255 190 190 0 121 121 diff --git a/digsby/lib/aspell/data/iso8859-2.dat b/digsby/lib/aspell/data/iso8859-2.dat new file mode 100644 index 0000000..1e0bb19 --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-2.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-2 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +260 letter 177 161 161 0 97 65 +728 other 162 162 162 0 162 162 +321 letter 179 163 163 76 108 76 +164 other 164 164 164 0 164 164 +317 letter 181 165 165 76 108 76 +346 letter 182 166 166 83 115 83 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +352 letter 185 169 169 83 115 83 +350 letter 186 170 170 83 115 83 +356 letter 187 171 171 84 116 84 +377 letter 188 172 172 90 122 90 +173 other 173 173 173 0 173 173 +381 letter 190 174 174 90 122 90 +379 letter 191 175 175 90 122 90 +176 other 176 176 176 0 176 176 +261 letter 177 161 161 0 97 97 +731 other 178 178 178 0 178 178 +322 letter 179 163 163 76 108 108 +180 other 180 180 180 0 180 180 +318 letter 181 165 165 76 108 108 +347 letter 182 166 166 83 115 115 +711 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +353 letter 185 169 169 83 115 115 +351 letter 186 170 170 83 115 115 +357 letter 187 171 171 84 116 116 +378 letter 188 172 172 90 122 122 +733 other 189 189 189 0 189 189 +382 letter 190 174 174 90 122 122 +380 letter 191 175 175 90 122 122 +340 letter 224 192 192 82 114 82 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +258 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +313 letter 229 197 197 76 108 76 +262 letter 230 198 198 67 99 67 +199 letter 231 199 199 67 99 67 +268 letter 232 200 200 67 99 67 +201 letter 233 201 201 0 101 69 +280 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +282 letter 236 204 204 0 101 69 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +270 letter 239 207 207 68 100 68 +272 letter 240 208 208 68 100 68 +323 letter 241 209 209 78 110 78 +327 letter 242 210 210 78 110 78 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +336 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +344 letter 248 216 216 82 114 82 +366 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +368 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +354 letter 254 222 222 84 116 84 +223 letter 223 223 223 223 223 223 +341 letter 224 192 192 82 114 114 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +259 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +314 letter 229 197 197 76 108 108 +263 letter 230 198 198 67 99 99 +231 letter 231 199 199 67 99 99 +269 letter 232 200 200 67 99 99 +233 letter 233 201 201 0 101 101 +281 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +283 letter 236 204 204 0 101 101 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +271 letter 239 207 207 68 100 100 +273 letter 240 208 208 68 100 100 +324 letter 241 209 209 78 110 110 +328 letter 242 210 210 78 110 110 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +337 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +345 letter 248 216 216 82 114 114 +367 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +369 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +355 letter 254 222 222 84 116 116 +729 other 255 255 255 0 255 255 diff --git a/digsby/lib/aspell/data/iso8859-3.dat b/digsby/lib/aspell/data/iso8859-3.dat new file mode 100644 index 0000000..d2304c9 --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-3.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-3 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +294 letter 177 161 161 72 104 72 +728 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +292 letter 182 166 166 72 104 72 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +304 letter 105 169 169 0 105 73 +350 letter 186 170 170 83 115 83 +286 letter 187 171 171 71 103 71 +308 letter 188 172 172 74 106 74 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +379 letter 191 175 175 90 122 90 +176 other 176 176 176 0 176 176 +295 letter 177 161 161 72 104 104 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 181 181 181 181 181 +293 letter 182 166 166 72 104 104 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +305 letter 185 73 73 0 105 105 +351 letter 186 170 170 83 115 115 +287 letter 187 171 171 71 103 103 +309 letter 188 172 172 74 106 106 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +380 letter 191 175 175 90 122 122 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +266 letter 229 197 197 67 99 67 +264 letter 230 198 198 67 99 67 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +208 letter 240 208 208 208 240 208 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +288 letter 245 213 213 71 103 71 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +284 letter 248 216 216 71 103 71 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +364 letter 253 221 221 0 117 85 +348 letter 254 222 222 83 115 83 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +267 letter 229 197 197 67 99 99 +265 letter 230 198 198 67 99 99 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +240 letter 240 208 208 208 240 240 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +289 letter 245 213 213 71 103 103 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +285 letter 248 216 216 71 103 103 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +365 letter 253 221 221 0 117 117 +349 letter 254 222 222 83 115 115 +729 other 255 255 255 0 255 255 diff --git a/digsby/lib/aspell/data/iso8859-4.dat b/digsby/lib/aspell/data/iso8859-4.dat new file mode 100644 index 0000000..e43e15c --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-4.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-4 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +260 letter 177 161 161 0 97 65 +312 letter 162 162 162 162 162 162 +342 letter 179 163 163 82 114 82 +164 other 164 164 164 0 164 164 +296 letter 181 165 165 0 105 73 +315 letter 182 166 166 76 108 76 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +352 letter 185 169 169 83 115 83 +274 letter 186 170 170 0 101 69 +290 letter 187 171 171 71 103 71 +358 letter 188 172 172 84 116 84 +173 other 173 173 173 0 173 173 +381 letter 190 174 174 90 122 90 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +261 letter 177 161 161 0 97 97 +731 other 178 178 178 0 178 178 +343 letter 179 163 163 82 114 114 +180 other 180 180 180 0 180 180 +297 letter 181 165 165 0 105 105 +316 letter 182 166 166 76 108 108 +711 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +353 letter 185 169 169 83 115 115 +275 letter 186 170 170 0 101 101 +291 letter 187 171 171 71 103 103 +359 letter 188 172 172 84 116 116 +330 letter 191 189 189 189 191 189 +382 letter 190 174 174 90 122 122 +331 letter 191 189 189 189 191 191 +256 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +302 letter 231 199 199 0 105 73 +268 letter 232 200 200 67 99 67 +201 letter 233 201 201 0 101 69 +280 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +278 letter 236 204 204 0 101 69 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +298 letter 239 207 207 0 105 73 +272 letter 240 208 208 68 100 68 +325 letter 241 209 209 78 110 78 +332 letter 242 210 210 0 111 79 +310 letter 243 211 211 75 107 75 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +370 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +360 letter 253 221 221 0 117 85 +362 letter 254 222 222 0 117 85 +223 letter 223 223 223 223 223 223 +257 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +303 letter 231 199 199 0 105 105 +269 letter 232 200 200 67 99 99 +233 letter 233 201 201 0 101 101 +281 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +279 letter 236 204 204 0 101 101 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +299 letter 239 207 207 0 105 105 +273 letter 240 208 208 68 100 100 +326 letter 241 209 209 78 110 110 +333 letter 242 210 210 0 111 111 +311 letter 243 211 211 75 107 107 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +371 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +361 letter 253 221 221 0 117 117 +363 letter 254 222 222 0 117 117 +729 other 255 255 255 0 255 255 diff --git a/digsby/lib/aspell/data/iso8859-5.dat b/digsby/lib/aspell/data/iso8859-5.dat new file mode 100644 index 0000000..f0cf176 --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-5.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-5 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +1025 letter 241 161 161 0 213 181 +1026 letter 242 162 162 162 242 162 +1027 letter 243 163 163 163 243 163 +1028 letter 244 164 164 164 244 164 +1029 letter 245 165 165 165 245 165 +1030 letter 246 166 166 166 246 166 +1031 letter 247 167 167 167 247 167 +1032 letter 248 168 168 168 248 168 +1033 letter 249 169 169 169 249 169 +1034 letter 250 170 170 170 250 170 +1035 letter 251 171 171 171 251 171 +1036 letter 252 172 172 172 252 172 +173 other 173 173 173 0 173 173 +1038 letter 254 174 174 174 254 174 +1039 letter 255 175 175 175 255 175 +1040 letter 208 176 176 0 208 176 +1041 letter 209 177 177 177 209 177 +1042 letter 210 178 178 178 210 178 +1043 letter 211 179 179 179 211 179 +1044 letter 212 180 180 180 212 180 +1045 letter 213 181 181 0 213 181 +1046 letter 214 182 182 182 214 182 +1047 letter 215 183 183 183 215 183 +1048 letter 216 184 184 0 216 184 +1049 letter 217 185 185 185 217 185 +1050 letter 218 186 186 186 218 186 +1051 letter 219 187 187 187 219 187 +1052 letter 220 188 188 188 220 188 +1053 letter 221 189 189 189 221 189 +1054 letter 222 190 190 0 222 190 +1055 letter 223 191 191 191 223 191 +1056 letter 224 192 192 192 224 192 +1057 letter 225 193 193 193 225 193 +1058 letter 226 194 194 194 226 194 +1059 letter 227 195 195 0 227 195 +1060 letter 228 196 196 196 228 196 +1061 letter 229 197 197 197 229 197 +1062 letter 230 198 198 198 230 198 +1063 letter 231 199 199 199 231 199 +1064 letter 232 200 200 200 232 200 +1065 letter 233 201 201 201 233 201 +1066 letter 234 202 202 202 234 202 +1067 letter 235 203 203 0 235 203 +1068 letter 236 204 204 204 236 204 +1069 letter 237 205 205 0 237 205 +1070 letter 238 206 206 0 238 206 +1071 letter 239 207 207 0 239 207 +1072 letter 208 176 176 0 208 208 +1073 letter 209 177 177 177 209 209 +1074 letter 210 178 178 178 210 210 +1075 letter 211 179 179 179 211 211 +1076 letter 212 180 180 180 212 212 +1077 letter 213 181 181 0 213 213 +1078 letter 214 182 182 182 214 214 +1079 letter 215 183 183 183 215 215 +1080 letter 216 184 184 0 216 216 +1081 letter 217 185 185 185 217 217 +1082 letter 218 186 186 186 218 218 +1083 letter 219 187 187 187 219 219 +1084 letter 220 188 188 188 220 220 +1085 letter 221 189 189 189 221 221 +1086 letter 222 190 190 0 222 222 +1087 letter 223 191 191 191 223 223 +1088 letter 224 192 192 192 224 224 +1089 letter 225 193 193 193 225 225 +1090 letter 226 194 194 194 226 226 +1091 letter 227 195 195 0 227 227 +1092 letter 228 196 196 196 228 228 +1093 letter 229 197 197 197 229 229 +1094 letter 230 198 198 198 230 230 +1095 letter 231 199 199 199 231 231 +1096 letter 232 200 200 200 232 232 +1097 letter 233 201 201 201 233 233 +1098 letter 234 202 202 202 234 234 +1099 letter 235 203 203 0 235 235 +1100 letter 236 204 204 204 236 236 +1101 letter 237 205 205 0 237 237 +1102 letter 238 206 206 0 238 238 +1103 letter 239 207 207 0 239 239 +8470 other 240 240 240 0 240 240 +1105 letter 241 161 161 0 213 213 +1106 letter 242 162 162 162 242 242 +1107 letter 243 163 163 163 243 243 +1108 letter 244 164 164 164 244 244 +1109 letter 245 165 165 165 245 245 +1110 letter 246 166 166 166 246 246 +1111 letter 247 167 167 167 247 247 +1112 letter 248 168 168 168 248 248 +1113 letter 249 169 169 169 249 249 +1114 letter 250 170 170 170 250 250 +1115 letter 251 171 171 171 251 251 +1116 letter 252 172 172 172 252 252 +167 other 253 253 253 0 253 253 +1118 letter 254 174 174 174 254 254 +1119 letter 255 175 175 175 255 255 diff --git a/digsby/lib/aspell/data/iso8859-6.dat b/digsby/lib/aspell/data/iso8859-6.dat new file mode 100644 index 0000000..503e35a --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-6.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-6 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +1548 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 181 181 181 181 181 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +1563 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +1567 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +1569 letter 193 193 193 193 193 193 +1570 letter 194 194 194 199 199 199 +1571 letter 195 195 195 199 199 199 +1572 letter 196 196 196 232 232 232 +1573 letter 197 197 197 199 199 199 +1574 letter 198 198 198 234 234 234 +1575 letter 199 199 199 199 199 199 +1576 letter 200 200 200 200 200 200 +1577 letter 201 201 201 201 201 201 +1578 letter 202 202 202 202 202 202 +1579 letter 203 203 203 203 203 203 +1580 letter 204 204 204 204 204 204 +1581 letter 205 205 205 205 205 205 +1582 letter 206 206 206 206 206 206 +1583 letter 207 207 207 207 207 207 +1584 letter 208 208 208 208 208 208 +1585 letter 209 209 209 209 209 209 +1586 letter 210 210 210 210 210 210 +1587 letter 211 211 211 211 211 211 +1588 letter 212 212 212 212 212 212 +1589 letter 213 213 213 213 213 213 +1590 letter 214 214 214 214 214 214 +1591 letter 215 215 215 215 215 215 +1592 letter 216 216 216 216 216 216 +1593 letter 217 217 217 217 217 217 +1594 letter 218 218 218 218 218 218 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +222 letter 254 222 222 222 254 222 +223 letter 223 223 223 223 223 223 +1600 other 224 224 224 0 224 224 +1601 letter 225 225 225 225 225 225 +1602 letter 226 226 226 226 226 226 +1603 letter 227 227 227 227 227 227 +1604 letter 228 228 228 228 228 228 +1605 letter 229 229 229 229 229 229 +1606 letter 230 230 230 230 230 230 +1607 letter 231 231 231 231 231 231 +1608 letter 232 232 232 232 232 232 +1609 letter 233 233 233 233 233 233 +1610 letter 234 234 234 234 234 234 +1611 other 235 235 235 0 235 235 +1612 other 236 236 236 0 236 236 +1613 other 237 237 237 0 237 237 +1614 other 238 238 238 0 238 238 +1615 other 239 239 239 0 239 239 +1616 other 240 240 240 0 240 240 +1617 other 241 241 241 0 241 241 +1618 other 242 242 242 0 242 242 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +254 letter 254 222 222 222 254 254 +255 letter 255 255 255 0 121 121 diff --git a/digsby/lib/aspell/data/iso8859-7.dat b/digsby/lib/aspell/data/iso8859-7.dat new file mode 100644 index 0000000..0a26c3c --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-7.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-7 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +701 other 161 161 161 0 161 161 +700 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +8213 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +900 other 180 180 180 0 180 180 +901 other 181 181 181 0 181 181 +902 letter 220 182 182 193 225 193 +183 other 183 183 183 0 183 183 +904 letter 221 184 184 197 229 197 +905 letter 222 185 185 199 231 199 +906 letter 223 186 186 201 233 201 +187 other 187 187 187 0 187 187 +908 letter 252 188 188 207 239 207 +189 other 189 189 189 0 189 189 +910 letter 253 190 190 213 245 213 +911 letter 254 191 191 217 249 217 +912 letter 192 192 192 192 192 192 +913 letter 225 193 193 193 225 193 +914 letter 226 194 194 194 226 194 +915 letter 227 195 195 195 227 195 +916 letter 228 196 196 196 228 196 +917 letter 229 197 197 197 229 197 +918 letter 230 198 198 198 230 198 +919 letter 231 199 199 199 231 199 +920 letter 232 200 200 200 232 200 +921 letter 233 201 201 201 233 201 +922 letter 234 202 202 202 234 202 +923 letter 235 203 203 203 235 203 +924 letter 236 204 204 204 236 204 +925 letter 237 205 205 205 237 205 +926 letter 238 206 206 206 238 206 +927 letter 239 207 207 207 239 207 +928 letter 240 208 208 208 240 208 +929 letter 241 209 209 209 241 209 +210 letter 242 210 210 0 111 79 +931 letter 243 211 211 211 243 211 +932 letter 244 212 212 212 244 212 +933 letter 245 213 213 213 245 213 +934 letter 246 214 214 214 246 214 +935 letter 247 215 215 215 247 215 +936 letter 248 216 216 216 248 216 +937 letter 249 217 217 217 249 217 +938 letter 250 218 218 201 233 201 +939 letter 251 219 219 213 245 213 +940 letter 220 182 182 193 225 225 +941 letter 221 184 184 197 229 229 +942 letter 222 185 185 199 231 231 +943 letter 223 186 186 201 233 233 +944 letter 224 224 224 224 224 224 +945 letter 225 193 193 193 225 225 +946 letter 226 194 194 194 226 226 +947 letter 227 195 195 195 227 227 +948 letter 228 196 196 196 228 228 +949 letter 229 197 197 197 229 229 +950 letter 230 198 198 198 230 230 +951 letter 231 199 199 199 231 231 +952 letter 232 200 200 200 232 232 +953 letter 233 201 201 201 233 233 +954 letter 234 202 202 202 234 234 +955 letter 235 203 203 203 235 235 +956 letter 236 204 204 204 236 236 +957 letter 237 205 205 205 237 237 +958 letter 238 206 206 206 238 238 +959 letter 239 207 207 207 239 239 +960 letter 240 208 208 208 240 240 +961 letter 241 209 209 209 241 241 +962 letter 242 211 211 211 243 243 +963 letter 243 211 211 211 243 243 +964 letter 244 212 212 212 244 244 +965 letter 245 213 213 213 245 245 +966 letter 246 214 214 214 246 246 +967 letter 247 215 215 215 247 247 +968 letter 248 216 216 216 248 248 +969 letter 249 217 217 217 249 249 +970 letter 250 218 218 201 233 233 +971 letter 251 219 219 213 245 245 +972 letter 252 188 188 207 239 239 +973 letter 253 190 190 213 245 245 +974 letter 254 191 191 217 249 249 +255 letter 255 255 255 0 121 121 diff --git a/digsby/lib/aspell/data/iso8859-8.dat b/digsby/lib/aspell/data/iso8859-8.dat new file mode 100644 index 0000000..4189b2e --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-8.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-8 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +215 other 170 170 170 0 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +8254 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +247 other 186 186 186 0 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +208 letter 240 208 208 208 240 208 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 170 170 170 0 170 170 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +221 letter 253 221 221 0 121 89 +222 letter 254 222 222 222 254 222 +8215 other 223 223 223 0 223 223 +1488 letter 224 224 224 224 224 224 +1489 letter 225 225 225 225 225 225 +1490 letter 226 226 226 226 226 226 +1491 letter 227 227 227 227 227 227 +1492 letter 228 228 228 228 228 228 +1493 letter 229 229 229 229 229 229 +1494 letter 230 230 230 230 230 230 +1495 letter 231 231 231 231 231 231 +1496 letter 232 232 232 232 232 232 +1497 letter 233 233 233 233 233 233 +1498 letter 234 234 234 234 234 234 +1499 letter 235 235 235 235 235 235 +1500 letter 236 236 236 236 236 236 +1501 letter 237 237 237 237 237 237 +1502 letter 238 238 238 238 238 238 +1503 letter 239 239 239 239 239 239 +1504 letter 240 240 240 240 240 240 +1505 letter 241 241 241 241 241 241 +1506 letter 242 242 242 242 242 242 +1507 letter 243 243 243 243 243 243 +1508 letter 244 244 244 244 244 244 +1509 letter 245 245 245 245 245 245 +1510 letter 246 246 246 246 246 246 +1511 letter 247 247 247 247 247 247 +1512 letter 248 248 248 248 248 248 +1513 letter 249 249 249 249 249 249 +1514 letter 250 250 250 250 250 250 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +253 letter 253 221 221 0 121 121 +254 letter 254 222 222 222 254 254 +255 letter 255 255 255 0 121 121 diff --git a/digsby/lib/aspell/data/iso8859-9.dat b/digsby/lib/aspell/data/iso8859-9.dat new file mode 100644 index 0000000..ba22d4e --- /dev/null +++ b/digsby/lib/aspell/data/iso8859-9.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +iso8859-9 +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +128 other 128 128 128 0 128 128 +129 other 129 129 129 0 129 129 +130 other 130 130 130 0 130 130 +131 other 131 131 131 0 131 131 +132 other 132 132 132 0 132 132 +133 other 133 133 133 0 133 133 +134 other 134 134 134 0 134 134 +135 other 135 135 135 0 135 135 +136 other 136 136 136 0 136 136 +137 other 137 137 137 0 137 137 +138 other 138 138 138 0 138 138 +139 other 139 139 139 0 139 139 +140 other 140 140 140 0 140 140 +141 other 141 141 141 0 141 141 +142 other 142 142 142 0 142 142 +143 other 143 143 143 0 143 143 +144 other 144 144 144 0 144 144 +145 other 145 145 145 0 145 145 +146 other 146 146 146 0 146 146 +147 other 147 147 147 0 147 147 +148 other 148 148 148 0 148 148 +149 other 149 149 149 0 149 149 +150 other 150 150 150 0 150 150 +151 other 151 151 151 0 151 151 +152 other 152 152 152 0 152 152 +153 other 153 153 153 0 153 153 +154 other 154 154 154 0 154 154 +155 other 155 155 155 0 155 155 +156 other 156 156 156 0 156 156 +157 other 157 157 157 0 157 157 +158 other 158 158 158 0 158 158 +159 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +161 other 161 161 161 0 161 161 +162 other 162 162 162 0 162 162 +163 other 163 163 163 0 163 163 +164 other 164 164 164 0 164 164 +165 other 165 165 165 0 165 165 +166 other 166 166 166 0 166 166 +167 other 167 167 167 0 167 167 +168 other 168 168 168 0 168 168 +169 other 169 169 169 0 169 169 +170 letter 170 170 170 170 170 170 +171 other 171 171 171 0 171 171 +172 other 172 172 172 0 172 172 +173 other 173 173 173 0 173 173 +174 other 174 174 174 0 174 174 +175 other 175 175 175 0 175 175 +176 other 176 176 176 0 176 176 +177 other 177 177 177 0 177 177 +178 other 178 178 178 0 178 178 +179 other 179 179 179 0 179 179 +180 other 180 180 180 0 180 180 +181 letter 181 204 204 204 236 236 +182 other 182 182 182 0 182 182 +183 other 183 183 183 0 183 183 +184 other 184 184 184 0 184 184 +185 other 185 185 185 0 185 185 +186 letter 186 186 186 186 186 186 +187 other 187 187 187 0 187 187 +188 other 188 188 188 0 188 188 +189 other 189 189 189 0 189 189 +190 other 190 190 190 0 190 190 +191 other 191 191 191 0 191 191 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +196 letter 228 196 196 0 97 65 +197 letter 229 197 197 0 97 65 +198 letter 230 198 198 0 230 198 +199 letter 231 199 199 67 99 67 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +203 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +206 letter 238 206 206 0 105 73 +207 letter 239 207 207 0 105 73 +286 letter 240 208 208 71 103 71 +209 letter 241 209 209 78 110 78 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +213 letter 245 213 213 0 111 79 +214 letter 246 214 214 0 111 79 +215 other 215 215 215 0 215 215 +216 letter 248 216 216 0 111 79 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +219 letter 251 219 219 0 117 85 +220 letter 252 220 220 0 117 85 +304 letter 105 221 221 0 105 73 +350 letter 254 222 222 83 115 83 +223 letter 223 223 223 223 223 223 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +228 letter 228 196 196 0 97 97 +229 letter 229 197 197 0 97 97 +230 letter 230 198 198 0 230 230 +231 letter 231 199 199 67 99 99 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +235 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +238 letter 238 206 206 0 105 105 +239 letter 239 207 207 0 105 105 +287 letter 240 208 208 71 103 103 +241 letter 241 209 209 78 110 110 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 213 213 0 111 111 +246 letter 246 214 214 0 111 111 +247 other 247 247 247 0 247 247 +248 letter 248 216 216 0 111 111 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +251 letter 251 219 219 0 117 117 +252 letter 252 220 220 0 117 117 +305 letter 253 73 73 0 105 105 +351 letter 254 222 222 83 115 115 +255 letter 255 255 255 0 121 121 diff --git a/digsby/lib/aspell/data/koi8-f.dat b/digsby/lib/aspell/data/koi8-f.dat new file mode 100644 index 0000000..ffdb6a0 --- /dev/null +++ b/digsby/lib/aspell/data/koi8-f.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +koi8-f +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +9472 other 128 128 128 0 128 128 +9474 other 129 129 129 0 129 129 +9484 other 130 130 130 0 130 130 +9488 other 131 131 131 0 131 131 +9492 other 132 132 132 0 132 132 +9496 other 133 133 133 0 133 133 +9500 other 134 134 134 0 134 134 +9508 other 135 135 135 0 135 135 +9516 other 136 136 136 0 136 136 +9524 other 137 137 137 0 137 137 +9532 other 138 138 138 0 138 138 +9600 other 139 139 139 0 139 139 +9604 other 140 140 140 0 140 140 +9608 other 141 141 141 0 141 141 +9612 other 142 142 142 0 142 142 +9616 other 143 143 143 0 143 143 +9617 other 144 144 144 0 144 144 +8216 other 145 145 145 0 145 145 +8217 other 146 146 146 0 146 146 +8220 other 147 147 147 0 147 147 +8221 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8211 other 150 150 150 0 150 150 +8212 other 151 151 151 0 151 151 +169 other 152 152 152 0 152 152 +8482 other 153 153 153 0 153 153 +160 space 160 160 160 0 160 160 +187 other 155 155 155 0 155 155 +174 other 156 156 156 0 156 156 +171 other 157 157 157 0 157 157 +183 other 158 158 158 0 158 158 +164 other 159 159 159 0 159 159 +160 space 160 160 160 0 160 160 +1106 letter 161 177 177 177 161 161 +1107 letter 162 178 178 178 162 162 +1105 letter 163 179 179 0 197 197 +1108 letter 164 180 180 180 164 164 +1109 letter 165 181 181 181 165 165 +1110 letter 166 182 182 182 166 166 +1111 letter 167 183 183 183 167 167 +1112 letter 168 184 184 184 168 168 +1113 letter 169 185 185 185 169 169 +1114 letter 170 186 186 186 170 170 +1115 letter 171 187 187 187 171 171 +1116 letter 172 188 188 188 172 172 +1169 letter 173 189 189 231 199 199 +1118 letter 174 190 190 190 174 174 +1119 letter 175 191 191 191 175 175 +8470 other 176 176 176 0 176 176 +1026 letter 161 177 177 177 161 177 +1027 letter 162 178 178 178 162 178 +1025 letter 163 179 179 0 197 229 +1028 letter 164 180 180 180 164 180 +1029 letter 165 181 181 181 165 181 +1030 letter 166 182 182 182 166 182 +1031 letter 167 183 183 183 167 183 +1032 letter 168 184 184 184 168 184 +1033 letter 169 185 185 185 169 185 +1034 letter 170 186 186 186 170 186 +1035 letter 171 187 187 187 171 187 +1036 letter 172 188 188 188 172 188 +1168 letter 173 189 189 231 199 231 +1038 letter 174 190 190 190 174 190 +1039 letter 175 191 191 191 175 191 +1102 letter 192 224 224 0 192 192 +1072 letter 193 225 225 0 193 193 +1073 letter 194 226 226 226 194 194 +1094 letter 195 227 227 227 195 195 +1076 letter 196 228 228 228 196 196 +1077 letter 197 229 229 0 197 197 +1092 letter 198 230 230 230 198 198 +1075 letter 199 231 231 231 199 199 +1093 letter 200 232 232 232 200 200 +1080 letter 201 233 233 0 201 201 +1081 letter 202 234 234 234 202 202 +1082 letter 203 235 235 235 203 203 +1083 letter 204 236 236 236 204 204 +1084 letter 205 237 237 237 205 205 +1085 letter 206 238 238 238 206 206 +1086 letter 207 239 239 0 207 207 +1087 letter 208 240 240 240 208 208 +1103 letter 209 241 241 0 209 209 +1088 letter 210 242 242 242 210 210 +1089 letter 211 243 243 243 211 211 +1090 letter 212 244 244 244 212 212 +1091 letter 213 245 245 0 213 213 +1078 letter 214 246 246 246 214 214 +1074 letter 215 247 247 247 215 215 +1100 letter 216 248 248 248 216 216 +1099 letter 217 249 249 0 217 217 +1079 letter 218 250 250 250 218 218 +1096 letter 219 251 251 251 219 219 +1101 letter 220 252 252 0 220 220 +1097 letter 221 253 253 253 221 221 +1095 letter 222 254 254 254 222 222 +1098 letter 223 255 255 255 223 223 +1070 letter 192 224 224 0 192 224 +1040 letter 193 225 225 0 193 225 +1041 letter 194 226 226 226 194 226 +1062 letter 195 227 227 227 195 227 +1044 letter 196 228 228 228 196 228 +1045 letter 197 229 229 0 197 229 +1060 letter 198 230 230 230 198 230 +1043 letter 199 231 231 231 199 231 +1061 letter 200 232 232 232 200 232 +1048 letter 201 233 233 0 201 233 +1049 letter 202 234 234 234 202 234 +1050 letter 203 235 235 235 203 235 +1051 letter 204 236 236 236 204 236 +1052 letter 205 237 237 237 205 237 +1053 letter 206 238 238 238 206 238 +1054 letter 207 239 239 0 207 239 +1055 letter 208 240 240 240 208 240 +1071 letter 209 241 241 0 209 241 +1056 letter 210 242 242 242 210 242 +1057 letter 211 243 243 243 211 243 +1058 letter 212 244 244 244 212 244 +1059 letter 213 245 245 0 213 245 +1046 letter 214 246 246 246 214 246 +1042 letter 215 247 247 247 215 247 +1068 letter 216 248 248 248 216 248 +1067 letter 217 249 249 0 217 249 +1047 letter 218 250 250 250 218 250 +1064 letter 219 251 251 251 219 251 +1069 letter 220 252 252 0 220 252 +1065 letter 221 253 253 253 221 253 +1063 letter 222 254 254 254 222 254 +1066 letter 223 255 255 255 223 255 diff --git a/digsby/lib/aspell/data/koi8-r.dat b/digsby/lib/aspell/data/koi8-r.dat new file mode 100644 index 0000000..dcc08a5 --- /dev/null +++ b/digsby/lib/aspell/data/koi8-r.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +koi8-r +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +9472 other 128 128 128 0 128 128 +9474 other 129 129 129 0 129 129 +9484 other 130 130 130 0 130 130 +9488 other 131 131 131 0 131 131 +9492 other 132 132 132 0 132 132 +9496 other 133 133 133 0 133 133 +9500 other 134 134 134 0 134 134 +9508 other 135 135 135 0 135 135 +9516 other 136 136 136 0 136 136 +9524 other 137 137 137 0 137 137 +9532 other 138 138 138 0 138 138 +9600 other 139 139 139 0 139 139 +9604 other 140 140 140 0 140 140 +9608 other 141 141 141 0 141 141 +9612 other 142 142 142 0 142 142 +9616 other 143 143 143 0 143 143 +9617 other 144 144 144 0 144 144 +9618 other 145 145 145 0 145 145 +9619 other 146 146 146 0 146 146 +8992 other 147 147 147 0 147 147 +9632 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8730 other 150 150 150 0 150 150 +8776 other 151 151 151 0 151 151 +8804 other 152 152 152 0 152 152 +8805 other 153 153 153 0 153 153 +160 space 154 154 154 0 154 154 +8993 other 155 155 155 0 155 155 +176 other 156 156 156 0 156 156 +178 other 157 157 157 0 157 157 +183 other 158 158 158 0 158 158 +247 other 159 159 159 0 159 159 +9552 other 160 160 160 0 160 160 +9553 other 161 161 161 0 161 161 +9554 other 162 162 162 0 162 162 +1105 letter 163 179 179 0 197 197 +9555 other 164 164 164 0 164 164 +9556 other 165 165 165 0 165 165 +9557 other 166 166 166 0 166 166 +9558 other 167 167 167 0 167 167 +9559 other 168 168 168 0 168 168 +9560 other 169 169 169 0 169 169 +9561 other 170 170 170 0 170 170 +9562 other 171 171 171 0 171 171 +9563 other 172 172 172 0 172 172 +9564 other 173 173 173 0 173 173 +9565 other 174 174 174 0 174 174 +9566 other 175 175 175 0 175 175 +9567 other 176 176 176 0 176 176 +9568 other 177 177 177 0 177 177 +9569 other 178 178 178 0 178 178 +1025 letter 163 179 179 0 197 229 +9570 other 180 180 180 0 180 180 +9571 other 181 181 181 0 181 181 +9572 other 182 182 182 0 182 182 +9573 other 183 183 183 0 183 183 +9574 other 184 184 184 0 184 184 +9575 other 185 185 185 0 185 185 +9576 other 186 186 186 0 186 186 +9577 other 187 187 187 0 187 187 +9578 other 188 188 188 0 188 188 +9579 other 189 189 189 0 189 189 +9580 other 190 190 190 0 190 190 +169 other 191 191 191 0 191 191 +1102 letter 192 224 224 0 192 192 +1072 letter 193 225 225 0 193 193 +1073 letter 194 226 226 226 194 194 +1094 letter 195 227 227 227 195 195 +1076 letter 196 228 228 228 196 196 +1077 letter 197 229 229 0 197 197 +1092 letter 198 230 230 230 198 198 +1075 letter 199 231 231 231 199 199 +1093 letter 200 232 232 232 200 200 +1080 letter 201 233 233 0 201 201 +1081 letter 202 234 234 234 202 202 +1082 letter 203 235 235 235 203 203 +1083 letter 204 236 236 236 204 204 +1084 letter 205 237 237 237 205 205 +1085 letter 206 238 238 238 206 206 +1086 letter 207 239 239 0 207 207 +1087 letter 208 240 240 240 208 208 +1103 letter 209 241 241 0 209 209 +1088 letter 210 242 242 242 210 210 +1089 letter 211 243 243 243 211 211 +1090 letter 212 244 244 244 212 212 +1091 letter 213 245 245 0 213 213 +1078 letter 214 246 246 246 214 214 +1074 letter 215 247 247 247 215 215 +1100 letter 216 248 248 248 216 216 +1099 letter 217 249 249 0 217 217 +1079 letter 218 250 250 250 218 218 +1096 letter 219 251 251 251 219 219 +1101 letter 220 252 252 0 220 220 +1097 letter 221 253 253 253 221 221 +1095 letter 222 254 254 254 222 222 +1098 letter 223 255 255 255 223 223 +1070 letter 192 224 224 0 192 224 +1040 letter 193 225 225 0 193 225 +1041 letter 194 226 226 226 194 226 +1062 letter 195 227 227 227 195 227 +1044 letter 196 228 228 228 196 228 +1045 letter 197 229 229 0 197 229 +1060 letter 198 230 230 230 198 230 +1043 letter 199 231 231 231 199 231 +1061 letter 200 232 232 232 200 232 +1048 letter 201 233 233 0 201 233 +1049 letter 202 234 234 234 202 234 +1050 letter 203 235 235 235 203 235 +1051 letter 204 236 236 236 204 236 +1052 letter 205 237 237 237 205 237 +1053 letter 206 238 238 238 206 238 +1054 letter 207 239 239 0 207 239 +1055 letter 208 240 240 240 208 240 +1071 letter 209 241 241 0 209 241 +1056 letter 210 242 242 242 210 242 +1057 letter 211 243 243 243 211 243 +1058 letter 212 244 244 244 212 244 +1059 letter 213 245 245 0 213 245 +1046 letter 214 246 246 246 214 246 +1042 letter 215 247 247 247 215 247 +1068 letter 216 248 248 248 216 248 +1067 letter 217 249 249 0 217 249 +1047 letter 218 250 250 250 218 250 +1064 letter 219 251 251 251 219 251 +1069 letter 220 252 252 0 220 252 +1065 letter 221 253 253 253 221 253 +1063 letter 222 254 254 254 222 254 +1066 letter 223 255 255 255 223 255 diff --git a/digsby/lib/aspell/data/koi8-u.dat b/digsby/lib/aspell/data/koi8-u.dat new file mode 100644 index 0000000..5db1d3d --- /dev/null +++ b/digsby/lib/aspell/data/koi8-u.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +koi8-u +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +2 other 2 2 2 0 2 2 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +5 other 5 5 5 0 5 5 +6 other 6 6 6 0 6 6 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +20 other 20 20 20 0 20 20 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +25 other 25 25 25 0 25 25 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +30 other 30 30 30 0 30 30 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +9472 other 128 128 128 0 128 128 +9474 other 129 129 129 0 129 129 +9484 other 130 130 130 0 130 130 +9488 other 131 131 131 0 131 131 +9492 other 132 132 132 0 132 132 +9496 other 133 133 133 0 133 133 +9500 other 134 134 134 0 134 134 +9508 other 135 135 135 0 135 135 +9516 other 136 136 136 0 136 136 +9524 other 137 137 137 0 137 137 +9532 other 138 138 138 0 138 138 +9600 other 139 139 139 0 139 139 +9604 other 140 140 140 0 140 140 +9608 other 141 141 141 0 141 141 +9612 other 142 142 142 0 142 142 +9616 other 143 143 143 0 143 143 +9617 other 144 144 144 0 144 144 +9618 other 145 145 145 0 145 145 +9619 other 146 146 146 0 146 146 +8992 other 147 147 147 0 147 147 +9632 other 148 148 148 0 148 148 +8226 other 149 149 149 0 149 149 +8730 other 150 150 150 0 150 150 +8776 other 151 151 151 0 151 151 +8804 other 152 152 152 0 152 152 +8805 other 153 153 153 0 153 153 +160 space 154 154 154 0 154 154 +8993 other 155 155 155 0 155 155 +176 other 156 156 156 0 156 156 +178 other 157 157 157 0 157 157 +183 other 158 158 158 0 158 158 +247 other 159 159 159 0 159 159 +9552 other 160 160 160 0 160 160 +9553 other 161 161 161 0 161 161 +9554 other 162 162 162 0 162 162 +1105 letter 163 179 179 0 197 197 +1108 letter 164 180 180 180 164 164 +9556 other 165 165 165 0 165 165 +1110 letter 166 182 182 182 166 166 +1111 letter 167 183 183 183 167 167 +9559 other 168 168 168 0 168 168 +9560 other 169 169 169 0 169 169 +9561 other 170 170 170 0 170 170 +9562 other 171 171 171 0 171 171 +9563 other 172 172 172 0 172 172 +1169 letter 173 189 189 231 199 199 +9565 other 174 174 174 0 174 174 +9566 other 175 175 175 0 175 175 +9567 other 176 176 176 0 176 176 +9568 other 177 177 177 0 177 177 +9569 other 178 178 178 0 178 178 +1025 letter 163 179 179 0 197 229 +1028 letter 164 180 180 180 164 180 +9571 other 181 181 181 0 181 181 +1030 letter 166 182 182 182 166 182 +1031 letter 167 183 183 183 167 183 +9574 other 184 184 184 0 184 184 +9575 other 185 185 185 0 185 185 +9576 other 186 186 186 0 186 186 +9577 other 187 187 187 0 187 187 +9578 other 188 188 188 0 188 188 +1168 letter 173 189 189 231 199 231 +9580 other 190 190 190 0 190 190 +169 other 191 191 191 0 191 191 +1102 letter 192 224 224 0 192 192 +1072 letter 193 225 225 0 193 193 +1073 letter 194 226 226 226 194 194 +1094 letter 195 227 227 227 195 195 +1076 letter 196 228 228 228 196 196 +1077 letter 197 229 229 0 197 197 +1092 letter 198 230 230 230 198 198 +1075 letter 199 231 231 231 199 199 +1093 letter 200 232 232 232 200 200 +1080 letter 201 233 233 0 201 201 +1081 letter 202 234 234 234 202 202 +1082 letter 203 235 235 235 203 203 +1083 letter 204 236 236 236 204 204 +1084 letter 205 237 237 237 205 205 +1085 letter 206 238 238 238 206 206 +1086 letter 207 239 239 0 207 207 +1087 letter 208 240 240 240 208 208 +1103 letter 209 241 241 0 209 209 +1088 letter 210 242 242 242 210 210 +1089 letter 211 243 243 243 211 211 +1090 letter 212 244 244 244 212 212 +1091 letter 213 245 245 0 213 213 +1078 letter 214 246 246 246 214 214 +1074 letter 215 247 247 247 215 215 +1100 letter 216 248 248 248 216 216 +1099 letter 217 249 249 0 217 217 +1079 letter 218 250 250 250 218 218 +1096 letter 219 251 251 251 219 219 +1101 letter 220 252 252 0 220 220 +1097 letter 221 253 253 253 221 221 +1095 letter 222 254 254 254 222 222 +1098 letter 223 255 255 255 223 223 +1070 letter 192 224 224 0 192 224 +1040 letter 193 225 225 0 193 225 +1041 letter 194 226 226 226 194 226 +1062 letter 195 227 227 227 195 227 +1044 letter 196 228 228 228 196 228 +1045 letter 197 229 229 0 197 229 +1060 letter 198 230 230 230 198 230 +1043 letter 199 231 231 231 199 231 +1061 letter 200 232 232 232 200 232 +1048 letter 201 233 233 0 201 233 +1049 letter 202 234 234 234 202 234 +1050 letter 203 235 235 235 203 235 +1051 letter 204 236 236 236 204 236 +1052 letter 205 237 237 237 205 237 +1053 letter 206 238 238 238 206 238 +1054 letter 207 239 239 0 207 239 +1055 letter 208 240 240 240 208 240 +1071 letter 209 241 241 0 209 241 +1056 letter 210 242 242 242 210 242 +1057 letter 211 243 243 243 211 243 +1058 letter 212 244 244 244 212 244 +1059 letter 213 245 245 0 213 245 +1046 letter 214 246 246 246 214 246 +1042 letter 215 247 247 247 215 247 +1068 letter 216 248 248 248 216 248 +1067 letter 217 249 249 0 217 249 +1047 letter 218 250 250 250 218 250 +1064 letter 219 251 251 251 219 251 +1069 letter 220 252 252 0 220 252 +1065 letter 221 253 253 253 221 253 +1063 letter 222 254 254 254 222 254 +1066 letter 223 255 255 255 223 255 diff --git a/digsby/lib/aspell/data/split.kbd b/digsby/lib/aspell/data/split.kbd new file mode 100644 index 0000000..3628872 --- /dev/null +++ b/digsby/lib/aspell/data/split.kbd @@ -0,0 +1,23 @@ +# Natural (split) keyboard data file + +qw +we +er +rt +yu +ui +io +op +as +sd +df +fg +hj +jk +kl +zx +xc +cv +bn +nm + diff --git a/digsby/lib/aspell/data/standard.kbd b/digsby/lib/aspell/data/standard.kbd new file mode 100644 index 0000000..fd57868 --- /dev/null +++ b/digsby/lib/aspell/data/standard.kbd @@ -0,0 +1,25 @@ +# Standard keyboard data file + +qw +we +er +rt +ty +yu +ui +io +op +as +sd +df +fg +gh +hj +jk +kl +zx +xc +cv +vb +bn +nm diff --git a/digsby/lib/aspell/data/viscii.dat b/digsby/lib/aspell/data/viscii.dat new file mode 100644 index 0000000..faeb53a --- /dev/null +++ b/digsby/lib/aspell/data/viscii.dat @@ -0,0 +1,258 @@ +# Aspell Character Data File. Do Not Edit! +viscii +0 other 0 0 0 0 0 0 +1 other 1 1 1 0 1 1 +7858 letter 198 2 2 0 97 65 +3 other 3 3 3 0 3 3 +4 other 4 4 4 0 4 4 +7860 letter 199 5 5 0 97 65 +7850 letter 231 6 6 0 97 65 +7 other 7 7 7 0 7 7 +8 other 8 8 8 0 8 8 +9 space 9 9 9 0 9 9 +10 space 10 10 10 0 10 10 +11 space 11 11 11 0 11 11 +12 space 12 12 12 0 12 12 +13 space 13 13 13 0 13 13 +14 other 14 14 14 0 14 14 +15 other 15 15 15 0 15 15 +16 other 16 16 16 0 16 16 +17 other 17 17 17 0 17 17 +18 other 18 18 18 0 18 18 +19 other 19 19 19 0 19 19 +7926 letter 214 20 20 0 121 89 +21 other 21 21 21 0 21 21 +22 other 22 22 22 0 22 22 +23 other 23 23 23 0 23 23 +24 other 24 24 24 0 24 24 +7928 letter 219 25 25 0 121 89 +26 other 26 26 26 0 26 26 +27 other 27 27 27 0 27 27 +28 other 28 28 28 0 28 28 +29 other 29 29 29 0 29 29 +7924 letter 220 30 30 0 121 89 +31 other 31 31 31 0 31 31 +32 space 32 32 32 0 32 32 +33 other 33 33 33 0 33 33 +34 other 34 34 34 0 34 34 +35 other 35 35 35 0 35 35 +36 other 36 36 36 0 36 36 +37 other 37 37 37 0 37 37 +38 other 38 38 38 0 38 38 +39 other 39 39 39 0 39 39 +40 other 40 40 40 0 40 40 +41 other 41 41 41 0 41 41 +42 other 42 42 42 0 42 42 +43 other 43 43 43 0 43 43 +44 other 44 44 44 0 44 44 +45 other 45 45 45 0 45 45 +46 other 46 46 46 0 46 46 +47 other 47 47 47 0 47 47 +48 other 48 48 48 0 48 48 +49 other 49 49 49 0 49 49 +50 other 50 50 50 0 50 50 +51 other 51 51 51 0 51 51 +52 other 52 52 52 0 52 52 +53 other 53 53 53 0 53 53 +54 other 54 54 54 0 54 54 +55 other 55 55 55 0 55 55 +56 other 56 56 56 0 56 56 +57 other 57 57 57 0 57 57 +58 other 58 58 58 0 58 58 +59 other 59 59 59 0 59 59 +60 other 60 60 60 0 60 60 +61 other 61 61 61 0 61 61 +62 other 62 62 62 0 62 62 +63 other 63 63 63 0 63 63 +64 other 64 64 64 0 64 64 +65 letter 97 65 65 0 97 65 +66 letter 98 66 66 66 98 66 +67 letter 99 67 67 67 99 67 +68 letter 100 68 68 68 100 68 +69 letter 101 69 69 0 101 69 +70 letter 102 70 70 70 102 70 +71 letter 103 71 71 71 103 71 +72 letter 104 72 72 72 104 72 +73 letter 105 73 73 0 105 73 +74 letter 106 74 74 74 106 74 +75 letter 107 75 75 75 107 75 +76 letter 108 76 76 76 108 76 +77 letter 109 77 77 77 109 77 +78 letter 110 78 78 78 110 78 +79 letter 111 79 79 0 111 79 +80 letter 112 80 80 80 112 80 +81 letter 113 81 81 81 113 81 +82 letter 114 82 82 82 114 82 +83 letter 115 83 83 83 115 83 +84 letter 116 84 84 84 116 84 +85 letter 117 85 85 0 117 85 +86 letter 118 86 86 86 118 86 +87 letter 119 87 87 87 119 87 +88 letter 120 88 88 88 120 88 +89 letter 121 89 89 0 121 89 +90 letter 122 90 90 90 122 90 +91 other 91 91 91 0 91 91 +92 other 92 92 92 0 92 92 +93 other 93 93 93 0 93 93 +94 other 94 94 94 0 94 94 +95 other 95 95 95 0 95 95 +96 other 96 96 96 0 96 96 +97 letter 97 65 65 0 97 97 +98 letter 98 66 66 66 98 98 +99 letter 99 67 67 67 99 99 +100 letter 100 68 68 68 100 100 +101 letter 101 69 69 0 101 101 +102 letter 102 70 70 70 102 102 +103 letter 103 71 71 71 103 103 +104 letter 104 72 72 72 104 104 +105 letter 105 73 73 0 105 105 +106 letter 106 74 74 74 106 106 +107 letter 107 75 75 75 107 107 +108 letter 108 76 76 76 108 108 +109 letter 109 77 77 77 109 109 +110 letter 110 78 78 78 110 110 +111 letter 111 79 79 0 111 111 +112 letter 112 80 80 80 112 112 +113 letter 113 81 81 81 113 113 +114 letter 114 82 82 82 114 114 +115 letter 115 83 83 83 115 115 +116 letter 116 84 84 84 116 116 +117 letter 117 85 85 0 117 117 +118 letter 118 86 86 86 118 118 +119 letter 119 87 87 87 119 119 +120 letter 120 88 88 88 120 120 +121 letter 121 89 89 0 121 121 +122 letter 122 90 90 90 122 122 +123 other 123 123 123 0 123 123 +124 other 124 124 124 0 124 124 +125 other 125 125 125 0 125 125 +126 other 126 126 126 0 126 126 +127 other 127 127 127 0 127 127 +7840 letter 213 128 128 0 97 65 +7854 letter 161 129 129 0 97 65 +7856 letter 162 130 130 0 97 65 +7862 letter 163 131 131 0 97 65 +7844 letter 164 132 132 0 97 65 +7846 letter 165 133 133 0 97 65 +7848 letter 134 166 166 0 97 65 +7852 letter 167 135 135 0 97 65 +7868 letter 168 136 136 0 101 69 +7864 letter 169 137 137 0 101 69 +7870 letter 170 138 138 0 101 69 +7872 letter 171 139 139 0 101 69 +7874 letter 172 140 140 0 101 69 +7876 letter 173 141 141 0 101 69 +7878 letter 174 142 142 0 101 69 +7888 letter 175 143 143 0 111 79 +7890 letter 176 144 144 0 111 79 +7892 letter 177 145 145 0 111 79 +7894 letter 178 146 146 0 111 79 +7896 letter 181 147 147 0 111 79 +7906 letter 254 148 148 0 111 79 +7898 letter 190 149 149 0 111 79 +7900 letter 182 150 150 0 111 79 +7902 letter 183 151 151 0 111 79 +7882 letter 184 152 152 0 105 73 +7886 letter 246 153 153 0 111 79 +7884 letter 247 154 154 0 111 79 +7880 letter 239 155 155 0 105 73 +7910 letter 252 156 156 0 117 85 +360 letter 251 157 157 0 117 85 +7908 letter 248 158 158 0 117 85 +7922 letter 207 159 159 0 121 89 +213 letter 245 160 160 0 111 79 +7855 letter 161 129 129 0 97 97 +7857 letter 162 130 130 0 97 97 +7863 letter 163 131 131 0 97 97 +7845 letter 164 132 132 0 97 97 +7847 letter 165 133 133 0 97 97 +7848 letter 166 166 166 0 97 65 +7853 letter 167 135 135 0 97 97 +7869 letter 168 136 136 0 101 101 +7865 letter 169 137 137 0 101 101 +7871 letter 170 138 138 0 101 101 +7873 letter 171 139 139 0 101 101 +7875 letter 172 140 140 0 101 101 +7877 letter 173 141 141 0 101 101 +7879 letter 174 142 142 0 101 101 +7889 letter 175 143 143 0 111 111 +7891 letter 176 144 144 0 111 111 +7893 letter 177 145 145 0 111 111 +7895 letter 178 146 146 0 111 111 +7904 letter 222 179 179 0 111 79 +416 letter 189 180 180 0 111 79 +7897 letter 181 147 147 0 111 111 +7901 letter 182 150 150 0 111 111 +7903 letter 183 151 151 0 111 111 +7883 letter 184 152 152 0 105 105 +7920 letter 241 185 185 0 117 85 +7912 letter 209 186 186 0 117 85 +7914 letter 215 187 187 0 117 85 +7916 letter 216 188 188 0 117 85 +417 letter 189 180 180 0 111 111 +7899 letter 190 149 149 0 111 111 +431 letter 223 191 191 0 117 85 +192 letter 224 192 192 0 97 65 +193 letter 225 193 193 0 97 65 +194 letter 226 194 194 0 97 65 +195 letter 227 195 195 0 97 65 +7842 letter 228 196 196 0 97 65 +258 letter 229 197 197 0 97 65 +7859 letter 198 2 2 0 97 97 +7861 letter 199 5 5 0 97 97 +200 letter 232 200 200 0 101 69 +201 letter 233 201 201 0 101 69 +202 letter 234 202 202 0 101 69 +7866 letter 235 203 203 0 101 69 +204 letter 236 204 204 0 105 73 +205 letter 237 205 205 0 105 73 +296 letter 238 206 206 0 105 73 +7923 letter 207 159 159 0 121 121 +272 letter 240 208 208 68 100 68 +7913 letter 209 186 186 0 117 117 +210 letter 242 210 210 0 111 79 +211 letter 243 211 211 0 111 79 +212 letter 244 212 212 0 111 79 +7841 letter 213 128 128 0 97 97 +7927 letter 214 20 20 0 121 121 +7915 letter 215 187 187 0 117 117 +7917 letter 216 188 188 0 117 117 +217 letter 249 217 217 0 117 85 +218 letter 250 218 218 0 117 85 +7929 letter 219 25 25 0 121 121 +7925 letter 220 30 30 0 121 121 +221 letter 253 221 221 0 121 89 +7905 letter 222 179 179 0 111 111 +432 letter 223 191 191 0 117 117 +224 letter 224 192 192 0 97 97 +225 letter 225 193 193 0 97 97 +226 letter 226 194 194 0 97 97 +227 letter 227 195 195 0 97 97 +7843 letter 228 196 196 0 97 97 +259 letter 229 197 197 0 97 97 +7919 letter 230 255 255 0 117 117 +7851 letter 231 6 6 0 97 97 +232 letter 232 200 200 0 101 101 +233 letter 233 201 201 0 101 101 +234 letter 234 202 202 0 101 101 +7867 letter 235 203 203 0 101 101 +236 letter 236 204 204 0 105 105 +237 letter 237 205 205 0 105 105 +297 letter 238 206 206 0 105 105 +7881 letter 239 155 155 0 105 105 +273 letter 240 208 208 68 100 100 +7921 letter 241 185 185 0 117 117 +242 letter 242 210 210 0 111 111 +243 letter 243 211 211 0 111 111 +244 letter 244 212 212 0 111 111 +245 letter 245 160 160 0 111 111 +7887 letter 246 153 153 0 111 111 +7885 letter 247 154 154 0 111 111 +7909 letter 248 158 158 0 117 117 +249 letter 249 217 217 0 117 117 +250 letter 250 218 218 0 117 117 +361 letter 251 157 157 0 117 117 +7911 letter 252 156 156 0 117 117 +253 letter 253 221 221 0 121 121 +7907 letter 254 148 148 0 111 111 +7918 letter 230 255 255 0 117 85 diff --git a/digsby/lib/aspell/dict/american-w_accents.alias b/digsby/lib/aspell/dict/american-w_accents.alias new file mode 100644 index 0000000..1ade3a2 --- /dev/null +++ b/digsby/lib/aspell/dict/american-w_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_US-w_accents.multi diff --git a/digsby/lib/aspell/dict/american-wo_accents.alias b/digsby/lib/aspell/dict/american-wo_accents.alias new file mode 100644 index 0000000..c0c1493 --- /dev/null +++ b/digsby/lib/aspell/dict/american-wo_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_US-wo_accents.multi diff --git a/digsby/lib/aspell/dict/american.alias b/digsby/lib/aspell/dict/american.alias new file mode 100644 index 0000000..cc47890 --- /dev/null +++ b/digsby/lib/aspell/dict/american.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_US.multi diff --git a/digsby/lib/aspell/dict/british-ise-w_accents.alias b/digsby/lib/aspell/dict/british-ise-w_accents.alias new file mode 100644 index 0000000..55af8f7 --- /dev/null +++ b/digsby/lib/aspell/dict/british-ise-w_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ise-w_accents.multi diff --git a/digsby/lib/aspell/dict/british-ise-wo_accents.alias b/digsby/lib/aspell/dict/british-ise-wo_accents.alias new file mode 100644 index 0000000..8f7ed35 --- /dev/null +++ b/digsby/lib/aspell/dict/british-ise-wo_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ise-wo_accents.multi diff --git a/digsby/lib/aspell/dict/british-ise.alias b/digsby/lib/aspell/dict/british-ise.alias new file mode 100644 index 0000000..4a0e50f --- /dev/null +++ b/digsby/lib/aspell/dict/british-ise.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ise.multi diff --git a/digsby/lib/aspell/dict/british-ize-w_accents.alias b/digsby/lib/aspell/dict/british-ize-w_accents.alias new file mode 100644 index 0000000..95c5b7c --- /dev/null +++ b/digsby/lib/aspell/dict/british-ize-w_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ize-w_accents.multi diff --git a/digsby/lib/aspell/dict/british-ize-wo_accents.alias b/digsby/lib/aspell/dict/british-ize-wo_accents.alias new file mode 100644 index 0000000..f383d9e --- /dev/null +++ b/digsby/lib/aspell/dict/british-ize-wo_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ize-wo_accents.multi diff --git a/digsby/lib/aspell/dict/british-ize.alias b/digsby/lib/aspell/dict/british-ize.alias new file mode 100644 index 0000000..87b2a58 --- /dev/null +++ b/digsby/lib/aspell/dict/british-ize.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ize.multi diff --git a/digsby/lib/aspell/dict/british-w_accents.alias b/digsby/lib/aspell/dict/british-w_accents.alias new file mode 100644 index 0000000..fa1572b --- /dev/null +++ b/digsby/lib/aspell/dict/british-w_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-w_accents.multi diff --git a/digsby/lib/aspell/dict/british-wo_accents.alias b/digsby/lib/aspell/dict/british-wo_accents.alias new file mode 100644 index 0000000..5310653 --- /dev/null +++ b/digsby/lib/aspell/dict/british-wo_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-wo_accents.multi diff --git a/digsby/lib/aspell/dict/british.alias b/digsby/lib/aspell/dict/british.alias new file mode 100644 index 0000000..f425424 --- /dev/null +++ b/digsby/lib/aspell/dict/british.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB.multi diff --git a/digsby/lib/aspell/dict/canadian-w_accents.alias b/digsby/lib/aspell/dict/canadian-w_accents.alias new file mode 100644 index 0000000..107ea69 --- /dev/null +++ b/digsby/lib/aspell/dict/canadian-w_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_CA-w_accents.multi diff --git a/digsby/lib/aspell/dict/canadian-wo_accents.alias b/digsby/lib/aspell/dict/canadian-wo_accents.alias new file mode 100644 index 0000000..1c6a28f --- /dev/null +++ b/digsby/lib/aspell/dict/canadian-wo_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_CA-wo_accents.multi diff --git a/digsby/lib/aspell/dict/canadian.alias b/digsby/lib/aspell/dict/canadian.alias new file mode 100644 index 0000000..bd1fd84 --- /dev/null +++ b/digsby/lib/aspell/dict/canadian.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_CA.multi diff --git a/digsby/lib/aspell/dict/digsby-en_US.rws b/digsby/lib/aspell/dict/digsby-en_US.rws new file mode 100644 index 0000000..4ddb876 Binary files /dev/null and b/digsby/lib/aspell/dict/digsby-en_US.rws differ diff --git a/digsby/lib/aspell/dict/en-common.rws b/digsby/lib/aspell/dict/en-common.rws new file mode 100644 index 0000000..be74295 Binary files /dev/null and b/digsby/lib/aspell/dict/en-common.rws differ diff --git a/digsby/lib/aspell/dict/en-variant_0.multi b/digsby/lib/aspell/dict/en-variant_0.multi new file mode 100644 index 0000000..ac1755e --- /dev/null +++ b/digsby/lib/aspell/dict/en-variant_0.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-variant_0.rws diff --git a/digsby/lib/aspell/dict/en-variant_0.rws b/digsby/lib/aspell/dict/en-variant_0.rws new file mode 100644 index 0000000..2890f00 Binary files /dev/null and b/digsby/lib/aspell/dict/en-variant_0.rws differ diff --git a/digsby/lib/aspell/dict/en-variant_1.multi b/digsby/lib/aspell/dict/en-variant_1.multi new file mode 100644 index 0000000..9df494e --- /dev/null +++ b/digsby/lib/aspell/dict/en-variant_1.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-variant_1.rws diff --git a/digsby/lib/aspell/dict/en-variant_1.rws b/digsby/lib/aspell/dict/en-variant_1.rws new file mode 100644 index 0000000..bae0f77 Binary files /dev/null and b/digsby/lib/aspell/dict/en-variant_1.rws differ diff --git a/digsby/lib/aspell/dict/en-variant_2.multi b/digsby/lib/aspell/dict/en-variant_2.multi new file mode 100644 index 0000000..4b655cf --- /dev/null +++ b/digsby/lib/aspell/dict/en-variant_2.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-variant_2.rws diff --git a/digsby/lib/aspell/dict/en-variant_2.rws b/digsby/lib/aspell/dict/en-variant_2.rws new file mode 100644 index 0000000..082ccab Binary files /dev/null and b/digsby/lib/aspell/dict/en-variant_2.rws differ diff --git a/digsby/lib/aspell/dict/en-w_accents.multi b/digsby/lib/aspell/dict/en-w_accents.multi new file mode 100644 index 0000000..4faac20 --- /dev/null +++ b/digsby/lib/aspell/dict/en-w_accents.multi @@ -0,0 +1,4 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_US-w_accents-only.rws +add en_GB-ise-w_accents-only.rws diff --git a/digsby/lib/aspell/dict/en-wo_accents.multi b/digsby/lib/aspell/dict/en-wo_accents.multi new file mode 100644 index 0000000..5c134bc --- /dev/null +++ b/digsby/lib/aspell/dict/en-wo_accents.multi @@ -0,0 +1,4 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_US-wo_accents-only.rws +add en_GB-ise-wo_accents-only.rws diff --git a/digsby/lib/aspell/dict/en.dat b/digsby/lib/aspell/dict/en.dat new file mode 100644 index 0000000..48d36ae --- /dev/null +++ b/digsby/lib/aspell/dict/en.dat @@ -0,0 +1,6 @@ +name en +charset iso8859-1 +special ' -*- +soundslike en +#affix en +#repl-table en_affix.dat diff --git a/digsby/lib/aspell/dict/en.multi b/digsby/lib/aspell/dict/en.multi new file mode 100644 index 0000000..f3047a4 --- /dev/null +++ b/digsby/lib/aspell/dict/en.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-wo_accents.multi diff --git a/digsby/lib/aspell/dict/en_CA-w_accents-only.rws b/digsby/lib/aspell/dict/en_CA-w_accents-only.rws new file mode 100644 index 0000000..83e0f0b Binary files /dev/null and b/digsby/lib/aspell/dict/en_CA-w_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_CA-w_accents.multi b/digsby/lib/aspell/dict/en_CA-w_accents.multi new file mode 100644 index 0000000..53d0b3e --- /dev/null +++ b/digsby/lib/aspell/dict/en_CA-w_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_CA-w_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_CA-wo_accents-only.rws b/digsby/lib/aspell/dict/en_CA-wo_accents-only.rws new file mode 100644 index 0000000..60ebd6c Binary files /dev/null and b/digsby/lib/aspell/dict/en_CA-wo_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_CA-wo_accents.multi b/digsby/lib/aspell/dict/en_CA-wo_accents.multi new file mode 100644 index 0000000..7373367 --- /dev/null +++ b/digsby/lib/aspell/dict/en_CA-wo_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_CA-wo_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_CA.multi b/digsby/lib/aspell/dict/en_CA.multi new file mode 100644 index 0000000..1c6a28f --- /dev/null +++ b/digsby/lib/aspell/dict/en_CA.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_CA-wo_accents.multi diff --git a/digsby/lib/aspell/dict/en_GB-ise-w_accents-only.rws b/digsby/lib/aspell/dict/en_GB-ise-w_accents-only.rws new file mode 100644 index 0000000..fb0173d Binary files /dev/null and b/digsby/lib/aspell/dict/en_GB-ise-w_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_GB-ise-w_accents.multi b/digsby/lib/aspell/dict/en_GB-ise-w_accents.multi new file mode 100644 index 0000000..9b93728 --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-ise-w_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_GB-ise-w_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_GB-ise-wo_accents-only.rws b/digsby/lib/aspell/dict/en_GB-ise-wo_accents-only.rws new file mode 100644 index 0000000..b8b986b Binary files /dev/null and b/digsby/lib/aspell/dict/en_GB-ise-wo_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_GB-ise-wo_accents.multi b/digsby/lib/aspell/dict/en_GB-ise-wo_accents.multi new file mode 100644 index 0000000..f20b498 --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-ise-wo_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_GB-ise-wo_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_GB-ise.multi b/digsby/lib/aspell/dict/en_GB-ise.multi new file mode 100644 index 0000000..8f7ed35 --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-ise.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ise-wo_accents.multi diff --git a/digsby/lib/aspell/dict/en_GB-ize-w_accents-only.rws b/digsby/lib/aspell/dict/en_GB-ize-w_accents-only.rws new file mode 100644 index 0000000..8998b73 Binary files /dev/null and b/digsby/lib/aspell/dict/en_GB-ize-w_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_GB-ize-w_accents.multi b/digsby/lib/aspell/dict/en_GB-ize-w_accents.multi new file mode 100644 index 0000000..c6e2d95 --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-ize-w_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_GB-ize-w_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_GB-ize-wo_accents-only.rws b/digsby/lib/aspell/dict/en_GB-ize-wo_accents-only.rws new file mode 100644 index 0000000..b7031bb Binary files /dev/null and b/digsby/lib/aspell/dict/en_GB-ize-wo_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_GB-ize-wo_accents.multi b/digsby/lib/aspell/dict/en_GB-ize-wo_accents.multi new file mode 100644 index 0000000..587810e --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-ize-wo_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_GB-ize-wo_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_GB-ize.multi b/digsby/lib/aspell/dict/en_GB-ize.multi new file mode 100644 index 0000000..f383d9e --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-ize.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ize-wo_accents.multi diff --git a/digsby/lib/aspell/dict/en_GB-w_accents.multi b/digsby/lib/aspell/dict/en_GB-w_accents.multi new file mode 100644 index 0000000..55af8f7 --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-w_accents.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ise-w_accents.multi diff --git a/digsby/lib/aspell/dict/en_GB-wo_accents.multi b/digsby/lib/aspell/dict/en_GB-wo_accents.multi new file mode 100644 index 0000000..8f7ed35 --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB-wo_accents.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ise-wo_accents.multi diff --git a/digsby/lib/aspell/dict/en_GB.multi b/digsby/lib/aspell/dict/en_GB.multi new file mode 100644 index 0000000..8f7ed35 --- /dev/null +++ b/digsby/lib/aspell/dict/en_GB.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_GB-ise-wo_accents.multi diff --git a/digsby/lib/aspell/dict/en_US-w_accents-only.rws b/digsby/lib/aspell/dict/en_US-w_accents-only.rws new file mode 100644 index 0000000..357093b Binary files /dev/null and b/digsby/lib/aspell/dict/en_US-w_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_US-w_accents.multi b/digsby/lib/aspell/dict/en_US-w_accents.multi new file mode 100644 index 0000000..4db79a1 --- /dev/null +++ b/digsby/lib/aspell/dict/en_US-w_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_US-w_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_US-wo_accents-only.rws b/digsby/lib/aspell/dict/en_US-wo_accents-only.rws new file mode 100644 index 0000000..4812802 Binary files /dev/null and b/digsby/lib/aspell/dict/en_US-wo_accents-only.rws differ diff --git a/digsby/lib/aspell/dict/en_US-wo_accents.multi b/digsby/lib/aspell/dict/en_US-wo_accents.multi new file mode 100644 index 0000000..94f8465 --- /dev/null +++ b/digsby/lib/aspell/dict/en_US-wo_accents.multi @@ -0,0 +1,3 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-common.rws +add en_US-wo_accents-only.rws diff --git a/digsby/lib/aspell/dict/en_US.multi b/digsby/lib/aspell/dict/en_US.multi new file mode 100644 index 0000000..c0c1493 --- /dev/null +++ b/digsby/lib/aspell/dict/en_US.multi @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en_US-wo_accents.multi diff --git a/digsby/lib/aspell/dict/en_phonet.dat b/digsby/lib/aspell/dict/en_phonet.dat new file mode 100644 index 0000000..9095c85 --- /dev/null +++ b/digsby/lib/aspell/dict/en_phonet.dat @@ -0,0 +1,250 @@ +# phonetic_english.h - phonetic transformation rules for use with phonetic.c +# Copyright (C) 2000 Bjrn Jacke +# +# This rule set is based on Lawrence Phillips original metaphone +# algorithm with modifications made by Michael Kuhn in his +# C implantation, more modifications by Bjrn Jacke when +# converting the algorithm to a rule set and minor +# touch ups by Kevin Atkinson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 2.1 as published by the Free Software Foundation; +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Bjrn Jacke may be reached by email at bjoern.jacke@gmx.de +# +# Changelog: +# +# 2000-01-05 Bjrn Jacke +# - first version with translation rules derived from +# metaphone.cc distributed with aspell 0.28.3 +# - "TH" is now representated as "@" because "0" is a +# meta character +# - removed TH(!vowel) --> T; always use TH --> # instead +# - dropped "^AE" -> "E" (redundant) +# - "ing" is transformed to "N", not "NK" +# - "SCH(EO)" transforms to "SK" now +# - added R --> SILENT if (after a vowel) and no (vowel or +# "y" follows) like in "Marcy" or "abort" +# - H is SILENT in RH at beginning of words +# - H is SILENT if vowel leads and "Y" follows +# - some ".OUGH.." --> ...F exceptions added +# - "^V" transforms to "W" +# 2000-01-07 Kevin Atkinson +# Converted from header to data file. +# + +version 1.1 + +AH(AEIOUY)-^ *H +AR(AEIOUY)-^ *R +A(HR)^ * +A^ * +AH(AEIOUY)- H +AR(AEIOUY)- R +A(HR) _ +BB- _ +B B +CQ- _ +CIA X +CH X +C(EIY)- S +CK K +COUGH^ KF +CC< C +C K +DG(EIY) K +DD- _ +D T +< E +EH(AEIOUY)-^ *H +ER(AEIOUY)-^ *R +E(HR)^ * +ENOUGH^$ *NF +E^ * +EH(AEIOUY)- H +ER(AEIOUY)- R +E(HR) _ +FF- _ +F F +GN^ N +GN$ N +GNS$ NS +GNED$ N +GH(AEIOUY)- K +GH _ +GG9 K +G K +H H +IH(AEIOUY)-^ *H +IR(AEIOUY)-^ *R +I(HR)^ * +I^ * +ING6 N +IH(AEIOUY)- H +IR(AEIOUY)- R +I(HR) _ +J K +KN^ N +KK- _ +K K +LAUGH^ LF +LL- _ +L L +MB$ M +MM M +M M +NN- _ +N N +OH(AEIOUY)-^ *H +OR(AEIOUY)-^ *R +O(HR)^ * +O^ * +OH(AEIOUY)- H +OR(AEIOUY)- R +O(HR) _ +PH F +PN^ N +PP- _ +P P +Q K +RH^ R +ROUGH^ RF +RR- _ +R R +SCH(EOU)- SK +SC(IEY)- S +SH X +SI(AO)- X +SS- _ +S S +TI(AO)- X +TH @ +TCH-- _ +TOUGH^ TF +TT- _ +T T +UH(AEIOUY)-^ *H +UR(AEIOUY)-^ *R +U(HR)^ * +U^ * +UH(AEIOUY)- H +UR(AEIOUY)- R +U(HR) _ +V^ W +V F +WR^ R +WH^ W +W(AEIOU)- W +X^ S +X KS +Y(AEIOU)- Y +ZZ- _ +Z S + +#The rules in a different view: +# +# Exceptions: +# +# Beginning of word: "gn", "kn-", "pn-", "wr-" ----> drop first letter +# "Aebersold", "Gnagy", "Knuth", "Pniewski", "Wright" +# +# Beginning of word: "x" ----> change to "s" +# as in "Deng Xiaopeng" +# +# Beginning of word: "wh-" ----> change to "w" +# as in "Whalen" +# Beginning of word: leading vowels are transformed to "*" +# +# "[crt]ough" and "enough" are handled separately because of "F" sound +# +# +# A --> A at beginning +# _ otherwise +# +# B --> B unless at the end of word after "m", as in "dumb", "McComb" +# +# C --> X (sh) if "-cia-" or "-ch-" +# S if "-ci-", "-ce-", or "-cy-" +# SILENT if "-sci-", "-sce-", or "-scy-", or "-cq-" +# K otherwise, including in "-sch-" +# +# D --> K if in "-dge-", "-dgy-", or "-dgi-" +# T otherwise +# +# E --> A at beginnig +# _ SILENT otherwise +# +# F --> F +# +# G --> SILENT if in "-gh-" and not at end or before a vowel +# in "-gn" or "-gned" or "-gns" +# in "-dge-" etc., as in above rule +# K if before "i", or "e", or "y" if not double "gg" +# +# K otherwise (incl. "GG"!) +# +# H --> SILENT if after vowel and no vowel or "Y" follows +# or after "-ch-", "-sh-", "-ph-", "-th-", "-gh-" +# or after "rh-" at beginning +# H otherwise +# +# I --> A at beginning +# _ SILENT otherwise +# +# J --> K +# +# K --> SILENT if after "c" +# K otherwise +# +# L --> L +# +# M --> M +# +# N --> N +# +# O --> A at beginning +# _ SILENT otherwise +# +# P --> F if before "h" +# P otherwise +# +# Q --> K +# +# R --> SILENT if after vowel and no vowel or "Y" follows +# R otherwise +# +# S --> X (sh) if before "h" or in "-sio-" or "-sia-" +# SK if followed by "ch(eo)" (SCH(EO)) +# S otherwise +# +# T --> X (sh) if "-tia-" or "-tio-" +# 0 (th) if before "h" +# silent if in "-tch-" +# T otherwise +# +# U --> A at beginning +# _ SILENT otherwise +# +# V --> V if first letter of word +# F otherwise +# +# W --> SILENT if not followed by a vowel +# W if followed by a vowel +# +# X --> KS +# +# Y --> SILENT if not followed by a vowel +# Y if followed by a vowel +# +# Z --> S + diff --git a/digsby/lib/aspell/dict/english-variant_0.alias b/digsby/lib/aspell/dict/english-variant_0.alias new file mode 100644 index 0000000..90f947c --- /dev/null +++ b/digsby/lib/aspell/dict/english-variant_0.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-variant_0.multi diff --git a/digsby/lib/aspell/dict/english-variant_1.alias b/digsby/lib/aspell/dict/english-variant_1.alias new file mode 100644 index 0000000..c89477d --- /dev/null +++ b/digsby/lib/aspell/dict/english-variant_1.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-variant_1.multi diff --git a/digsby/lib/aspell/dict/english-variant_2.alias b/digsby/lib/aspell/dict/english-variant_2.alias new file mode 100644 index 0000000..2dd12e5 --- /dev/null +++ b/digsby/lib/aspell/dict/english-variant_2.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-variant_2.multi diff --git a/digsby/lib/aspell/dict/english-w_accents.alias b/digsby/lib/aspell/dict/english-w_accents.alias new file mode 100644 index 0000000..4f88e18 --- /dev/null +++ b/digsby/lib/aspell/dict/english-w_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-w_accents.multi diff --git a/digsby/lib/aspell/dict/english-wo_accents.alias b/digsby/lib/aspell/dict/english-wo_accents.alias new file mode 100644 index 0000000..f3047a4 --- /dev/null +++ b/digsby/lib/aspell/dict/english-wo_accents.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en-wo_accents.multi diff --git a/digsby/lib/aspell/dict/english.alias b/digsby/lib/aspell/dict/english.alias new file mode 100644 index 0000000..d80c835 --- /dev/null +++ b/digsby/lib/aspell/dict/english.alias @@ -0,0 +1,2 @@ +# Generated with Aspell Dicts "proc" script version 0.60.2 +add en.multi diff --git a/digsby/lib/digsby.dummy b/digsby/lib/digsby.dummy new file mode 100644 index 0000000..41520b5 Binary files /dev/null and b/digsby/lib/digsby.dummy differ diff --git a/digsby/lib/pyxmpp/__init__.py b/digsby/lib/pyxmpp/__init__.py new file mode 100644 index 0000000..915895b --- /dev/null +++ b/digsby/lib/pyxmpp/__init__.py @@ -0,0 +1,72 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +""" +PyXMPP - Jabber/XMPP protocol implementation +============================================ + +Conventions +----------- + +PyXMPP is object-oriented, most of its fetures are implemented via classes, +defined in various pyxmpp modules. The API is very asynchronous -- often +requested objects are not returned immediately, but instead a callback is +called when the object is available or an event occurs. + +As python is not a strongly-typed language so the parameter and attribute types +shown in this documentation are not enforced, but those types are expected by +the package and others may simply not work or stop working in future releases +of PyXMPP. + +Module hierarchy +................ + +Base XMPP features (`RFC 3920 `__, `RFC +3921 `__) are implemented in direct +submodules of `pyxmpp` package. Most `JSF `__ defined +extensions are defined in `pyxmpp.jabber` package and modules for server +components are placed in `pyxmpp.jabberd`. + +For convenience most important names (classes for application use) may be +imported into `pyxmpp`, `pyxmpp.jabber` or `pyxmpp.jabberd` packages. To do +that `pyxmpp.all`, `pyxmpp.jabber.all` or `pyxmpp.jabberd.all` must be +imported. One doesn't have to remember any other module name then. + +Constructors +............ + +Most of PyXMPP object constructors are polymorphic. That means they accept +different types and number of arguments to create object from various input. +Usually the first argument may be an XML node to parse/wrap into the object +or parameters needed to create a new object from scratch. E.g. +`pyxmpp.stanza.Stanza` constructor accepts single `libxml2.xmlNode` argument +with XML stanza or set of keyword arguments (from_jid, to_jid, stanza_type, +etc.) to create such XML stanza. Most of the constructors will also accept +instance of their own class to create a copy of it. + +Common methods +.............. + +Most objects describing elements of the XMPP protocol or its extensions have +method as_xml() providing their XML representations. +""" + + +__revision__="$Id: __init__.py 714 2010-04-05 10:20:10Z jajcus $" +__docformat__="restructuredtext en" + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/all.py b/digsby/lib/pyxmpp/all.py new file mode 100644 index 0000000..cde74e9 --- /dev/null +++ b/digsby/lib/pyxmpp/all.py @@ -0,0 +1,49 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# pylint: disable-msg=W0611 + +"""Convenience module containing most important objects from pyxmpp package. + +Suggested usage:: +import pyxmpp.all + +(imports all important names into pyxmpp namespace)""" + +"""PyXMPP - Jabber/XMPP protocol implementation""" + +__revision__="$Id: __init__.py 477 2004-12-29 13:25:42Z jajcus $" +__docformat__="restructuredtext en" + +import pyxmpp + +from pyxmpp.stream import Stream +from pyxmpp.streambase import StreamError,FatalStreamError,StreamParseError +from pyxmpp.streamtls import StreamEncryptionRequired,tls_available,TLSSettings +from pyxmpp.clientstream import ClientStream,ClientStreamError +from pyxmpp.client import Client,ClientError +from pyxmpp.iq import Iq +from pyxmpp.presence import Presence +from pyxmpp.message import Message +from pyxmpp.jid import JID,JIDError +from pyxmpp.roster import Roster,RosterItem +from pyxmpp.exceptions import * + +for name in dir(): + if not name.startswith("_") and name != "pyxmpp": + setattr(pyxmpp,name,globals()[name]) + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/cache.py b/digsby/lib/pyxmpp/cache.py new file mode 100644 index 0000000..5785675 --- /dev/null +++ b/digsby/lib/pyxmpp/cache.py @@ -0,0 +1,816 @@ +# +# (C) Copyright 2005-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""Caching proxy for Jabber/XMPP objects. + +This package provides facilities to retrieve and transparently cache +cachable objects like Service Discovery responses or e.g. client version +informations.""" + +__revision__ = "$Id: cache.py 714 2010-04-05 10:20:10Z jajcus $" +__docformat__ = "restructuredtext en" + +import threading +from datetime import datetime, timedelta + +_state_values = { + 'new': 0, + 'fresh': 1, + 'old': 2, + 'stale': 3, + 'purged': 4 + }; + +# locking order (anti-deadlock): +# CacheSuite, Cache, CacheHandler, CacheItem + +class CacheItem(object): + """An item in a cache. + + :Ivariables: + - `value`: item value (cached object). + - `address`: item address. + - `state`: current state. + - `state_value`: numerical value of the current state (lower number means + fresher item). + - `timestamp`: time when the object was created. + - `freshness_time`: time when the object stops being fresh. + - `expire_time`: time when the object expires. + - `purge_time`: time when the object should be purged. When 0 then + item will never be automaticaly purged. + - `_lock`: lock for thread safety. + :Types: + - `value`: `instance` + - `address`: any hashable + - `state`: `str` + - `state_value`: `int` + - `timestamp`: `datetime` + - `freshness_time`: `datetime` + - `expire_time`: `datetime` + - `purge_time`: `datetime` + - `_lock`: `threading.RLock`""" + __slots__ = ['value', 'address', 'state', 'timestamp', 'freshness_time', + 'expire_time', 'purge_time', 'state_value', '_lock'] + def __init__(self, address, value, freshness_period, expiration_period, + purge_period, state = "new"): + """Initialize an CacheItem object. + + :Parameters: + - `address`: item address. + - `value`: item value (cached object). + - `freshness_period`: time interval after which the object stops being fresh. + - `expiration_period`: time interval after which the object expires. + - `purge_period`: time interval after which the object should be purged. When 0 then + item will never be automaticaly purged. + - `state`: initial state. + :Types: + - `address`: any hashable + - `value`: `instance` + - `freshness_period`: `timedelta` + - `expiration_period`: `timedelta` + - `purge_period`: `timedelta` + - `state`: `str`""" + if freshness_period>expiration_period: + raise ValueError, "freshness_period greater then expiration_period" + if expiration_period>purge_period: + raise ValueError, "expiration_period greater then purge_period" + self.address = address + self.value = value + now = datetime.utcnow() + self.timestamp = now + self.freshness_time = now+freshness_period + self.expire_time = now+expiration_period + if purge_period: + self.purge_time = now+purge_period + else: + self.purge_time = datetime.max + self.state = state + self.state_value = _state_values[state] + self._lock = threading.RLock() + + def update_state(self): + """Update current status of the item and compute time of the next + state change. + + :return: the new state. + :returntype: `datetime`""" + self._lock.acquire() + try: + now = datetime.utcnow() + if self.state == 'new': + self.state = 'fresh' + if self.state == 'fresh': + if now > self.freshness_time: + self.state = 'old' + if self.state == 'old': + if now > self.expire_time: + self.state = 'stale' + if self.state == 'stale': + if now > self.purge_time: + self.state = 'purged' + self.state_value = _state_values[self.state] + return self.state + finally: + self._lock.release() + + def __cmp__(self,other): + try: + return cmp( + (-self.state_value, self.timestamp, id(self)), + (-other.state_value, other.timestamp, id(other)) + ) + except AttributeError: + return cmp(id(self),id(other)) + +_hour = timedelta(hours = 1) + +class CacheFetcher: + """Base class for cache object fetchers -- classes responsible for + retrieving objects from network. + + An instance of a fetcher class is created for each object requested and + not found in the cache, then `fetch` method is called to initialize + the asynchronous retrieval process. Fetcher object's `got_it` method + should be called on a successfull retrieval and `error` otherwise. + `timeout` will be called when the request timeouts. + + :Ivariables: + - `cache`: cache object which created this fetcher. + - `address`: requested item address. + - `timeout_time`: timeout time. + - `active`: `True` as long as the fetcher is active and requestor + expects one of the handlers to be called. + :Types: + - `cache`: `Cache` + - `address`: any hashable + - `timeout_time`: `datetime` + - `active`: `bool` + """ + def __init__(self, cache, address, + item_freshness_period, item_expiration_period, item_purge_period, + object_handler, error_handler, timeout_handler, timeout_period, + backup_state = None): + """Initialize an `CacheFetcher` object. + + :Parameters: + - `cache`: cache object which created this fetcher. + - `address`: requested item address. + - `item_freshness_period`: freshness period for the requested item. + - `item_expiration_period`: expiration period for the requested item. + - `item_purge_period`: purge period for the requested item. + - `object_handler`: function to be called after the item is fetched. + - `error_handler`: function to be called on error. + - `timeout_handler`: function to be called on timeout + - `timeout_period`: timeout interval. + - `backup_state`: when not `None` and the fetch fails than an + object from cache of at least this state will be passed to the + `object_handler`. If such object is not available, then + `error_handler` is called. + :Types: + - `cache`: `Cache` + - `address`: any hashable + - `item_freshness_period`: `timedelta` + - `item_expiration_period`: `timedelta` + - `item_purge_period`: `timedelta` + - `object_handler`: callable(address, value, state) + - `error_handler`: callable(address, error_data) + - `timeout_handler`: callable(address) + - `timeout_period`: `timedelta` + - `backup_state`: `bool`""" + self.cache = cache + self.address = address + self._item_freshness_period = item_freshness_period + self._item_expiration_period = item_expiration_period + self._item_purge_period = item_purge_period + self._object_handler = object_handler + self._error_handler = error_handler + self._timeout_handler = timeout_handler + if timeout_period: + self.timeout_time = datetime.utcnow()+timeout_period + else: + self.timeout_time = datetime.max + self._backup_state = backup_state + self.active = True + + def _deactivate(self): + """Remove the fetcher from cache and mark it not active.""" + self.cache.remove_fetcher(self) + if self.active: + self._deactivated() + + def _deactivated(self): + """Mark the fetcher inactive after it is removed from the cache.""" + self.active = False + + def fetch(self): + """Start the retrieval process. + + This method must be implemented in any fetcher class.""" + raise RuntimeError, "Pure virtual method called" + + def got_it(self, value, state = "new"): + """Handle a successfull retrieval and call apriopriate handler. + + Should be called when retrieval succeeds. + + Do nothing when the fetcher is not active any more (after + one of handlers was already called). + + :Parameters: + - `value`: fetched object. + - `state`: initial state of the object. + :Types: + - `value`: any + - `state`: `str`""" + if not self.active: + return + item = CacheItem(self.address, value, self._item_freshness_period, + self._item_expiration_period, self._item_purge_period, state) + self._object_handler(item.address, item.value, item.state) + self.cache.add_item(item) + self._deactivate() + + def error(self, error_data): + """Handle a retrieval error and call apriopriate handler. + + Should be called when retrieval fails. + + Do nothing when the fetcher is not active any more (after + one of handlers was already called). + + :Parameters: + - `error_data`: additional information about the error (e.g. `StanzaError` instance). + :Types: + - `error_data`: fetcher dependant + """ + if not self.active: + return + if not self._try_backup_item(): + self._error_handler(self.address, error_data) + self.cache.invalidate_object(self.address) + self._deactivate() + + def timeout(self): + """Handle fetcher timeout and call apriopriate handler. + + Is called by the cache object and should _not_ be called by fetcher or + application. + + Do nothing when the fetcher is not active any more (after + one of handlers was already called).""" + if not self.active: + return + if not self._try_backup_item(): + if self._timeout_handler: + self._timeout_handler(self.address) + else: + self._error_handler(self.address, None) + self.cache.invalidate_object(self.address) + self._deactivate() + + def _try_backup_item(self): + """Check if a backup item is available in cache and call + the item handler if it is. + + :return: `True` if backup item was found. + :returntype: `bool`""" + if not self._backup_state: + return False + item = self.cache.get_item(self.address, self._backup_state) + if item: + self._object_handler(item.address, item.value, item.state) + return True + else: + False + +class Cache: + """Caching proxy for object retrieval and caching. + + Object factories ("fetchers") are registered in the `Cache` object and used + to e.g. retrieve requested objects from network. They are called only when + the requested object is not in the cache or is not fresh enough. + + A state (freshness level) name may be provided when requesting an object. + When the cached item state is "less fresh" then requested, then new object + will be retrieved. + + Following states are defined: + + - 'new': always a new object should be retrieved. + - 'fresh': a fresh object (not older than freshness time) + - 'old': object not fresh, but most probably still valid. + - 'stale': object known to be expired. + + :Ivariables: + - `default_freshness_period`: default freshness period (in seconds). + - `default_expiration_period`: default expiration period (in seconds). + - `default_purge_period`: default purge period (in seconds). When + 0 then items are never purged because of their age. + - `max_items`: maximum number of items to store. + - `_items`: dictionary of stored items. + - `_items_list`: list of stored items with the most suitable for + purging first. + - `_fetcher`: fetcher class for this cache. + - `_active_fetchers`: list of active fetchers sorted by the time of + its expiration time. + - `_lock`: lock for thread safety. + :Types: + - `default_freshness_period`: timedelta + - `default_expiration_period`: timedelta + - `default_purge_period`: timedelta + - `max_items`: `int` + - `_items`: `dict` of (`classobj`, addr) -> `CacheItem` + - `_items_list`: `list` of (`int`, `datetime`, `CacheItem`) + - `_fetcher`: `CacheFetcher` based class + - `_active_fetchers`: `list` of (`int`, `CacheFetcher`) + - `_lock`: `threading.RLock` + """ + def __init__(self, max_items, default_freshness_period = _hour, + default_expiration_period = 12*_hour, default_purge_period = 24*_hour): + """Initialize a `Cache` object. + + :Parameters: + - `default_freshness_period`: default freshness period (in seconds). + - `default_expiration_period`: default expiration period (in seconds). + - `default_purge_period`: default purge period (in seconds). When + 0 then items are never purged because of their age. + - `max_items`: maximum number of items to store. + :Types: + - `default_freshness_period`: number + - `default_expiration_period`: number + - `default_purge_period`: number + - `max_items`: number + """ + self.default_freshness_period = default_freshness_period + self.default_expiration_period = default_expiration_period + self.default_purge_period = default_purge_period + self.max_items = max_items + self._items = {} + self._items_list = [] + self._fetcher = None + self._active_fetchers = [] + self._purged = 0 + self._lock = threading.RLock() + + def request_object(self, address, state, object_handler, + error_handler = None, timeout_handler = None, + backup_state = None, timeout = timedelta(minutes=60), + freshness_period = None, expiration_period = None, + purge_period = None): + """Request an object with given address and state not worse than + `state`. The object will be taken from cache if available, and + created/fetched otherwise. The request is asynchronous -- this metod + doesn't return the object directly, but the `object_handler` is called + as soon as the object is available (this may be before `request_object` + returns and may happen in other thread). On error the `error_handler` + will be called, and on timeout -- the `timeout_handler`. + + :Parameters: + - `address`: address of the object requested. + - `state`: the worst acceptable object state. When 'new' then always + a new object will be created/fetched. 'stale' will select any + item available in cache. + - `object_handler`: function to be called when object is available. + It will be called with the following arguments: address, object + and its state. + - `error_handler`: function to be called on object retrieval error. + It will be called with two arguments: requested address and + additional error information (fetcher-specific, may be + StanzaError for XMPP objects). If not given, then the object + handler will be called with object set to `None` and state + "error". + - `timeout_handler`: function to be called on object retrieval + timeout. It will be called with only one argument: the requested + address. If not given, then the `error_handler` will be called + instead, with error details set to `None`. + - `backup_state`: when set and object in state `state` is not + available in the cache and object retrieval failed then object + with this state will also be looked-up in the cache and provided + if available. + - `timeout`: time interval after which retrieval of the object + should be given up. + - `freshness_period`: time interval after which the item created + should become 'old'. + - `expiration_period`: time interval after which the item created + should become 'stale'. + - `purge_period`: time interval after which the item created + shuld be removed from the cache. + :Types: + - `address`: any hashable + - `state`: "new", "fresh", "old" or "stale" + - `object_handler`: callable(address, value, state) + - `error_handler`: callable(address, error_data) + - `timeout_handler`: callable(address) + - `backup_state`: "new", "fresh", "old" or "stale" + - `timeout`: `timedelta` + - `freshness_period`: `timedelta` + - `expiration_period`: `timedelta` + - `purge_period`: `timedelta` + """ + self._lock.acquire() + try: + if state == 'stale': + state = 'purged' + item = self.get_item(address, state) + if item: + object_handler(item.address, item.value, item.state) + return + if not self._fetcher: + raise TypeError, "No cache fetcher defined" + if not error_handler: + def default_error_handler(address, _unused): + "Default error handler." + return object_handler(address, None, 'error') + error_handler = default_error_handler + if not timeout_handler: + def default_timeout_handler(address): + "Default timeout handler." + return error_handler(address, None) + timeout_handler = default_timeout_handler + if freshness_period is None: + freshness_period = self.default_freshness_period + if expiration_period is None: + expiration_period = self.default_expiration_period + if purge_period is None: + purge_period = self.default_purge_period + + fetcher = self._fetcher(self, address, freshness_period, + expiration_period, purge_period, object_handler, error_handler, + timeout_handler, timeout, backup_state) + fetcher.fetch() + self._active_fetchers.append((fetcher.timeout_time,fetcher)) + self._active_fetchers.sort() + finally: + self._lock.release() + + def invalidate_object(self, address, state = 'stale'): + """Force cache item state change (to 'worse' state only). + + :Parameters: + - `state`: the new state requested. + :Types: + - `state`: `str`""" + self._lock.acquire() + try: + item = self.get_item(address) + if item and item.state_value<_state_values[state]: + item.state=state + item.update_state() + self._items_list.sort() + finally: + self._lock.release() + + def add_item(self, item): + """Add an item to the cache. + + Item state is updated before adding it (it will not be 'new' any more). + + :Parameters: + - `item`: the item to add. + :Types: + - `item`: `CacheItem` + + :return: state of the item after addition. + :returntype: `str` + """ + self._lock.acquire() + try: + state = item.update_state() + if state != 'purged': + if len(self._items_list) >= self.max_items: + self.purge_items() + self._items[item.address] = item + self._items_list.append(item) + self._items_list.sort() + return item.state + finally: + self._lock.release() + + def get_item(self, address, state = 'fresh'): + """Get an item from the cache. + + :Parameters: + - `address`: its address. + - `state`: the worst state that is acceptable. + :Types: + - `address`: any hashable + - `state`: `str` + + :return: the item or `None` if it was not found. + :returntype: `CacheItem`""" + self._lock.acquire() + try: + item = self._items.get(address) + if not item: + return None + self.update_item(item) + if _state_values[state] >= item.state_value: + return item + return None + finally: + self._lock.release() + + def update_item(self, item): + """Update state of an item in the cache. + + Update item's state and remove the item from the cache + if its new state is 'purged' + + :Parameters: + - `item`: item to update. + :Types: + - `item`: `CacheItem` + + :return: new state of the item. + :returntype: `str`""" + + self._lock.acquire() + try: + state = item.update_state() + self._items_list.sort() + if item.state == 'purged': + self._purged += 1 + if self._purged > 0.25*self.max_items: + self.purge_items() + return state + finally: + self._lock.release() + + def num_items(self): + """Get the number of items in the cache. + + :return: number of items. + :returntype: `int`""" + return len(self._items_list) + + def purge_items(self): + """Remove purged and overlimit items from the cache. + + TODO: optimize somehow. + + Leave no more than 75% of `self.max_items` items in the cache.""" + self._lock.acquire() + try: + il=self._items_list + num_items = len(il) + need_remove = num_items - int(0.75 * self.max_items) + + for _unused in range(need_remove): + item=il.pop(0) + try: + del self._items[item.address] + except KeyError: + pass + + while il and il[0].update_state()=="purged": + item=il.pop(0) + try: + del self._items[item.address] + except KeyError: + pass + finally: + self._lock.release() + + def tick(self): + """Do the regular cache maintenance. + + Must be called from time to time for timeouts and cache old items + purging to work.""" + self._lock.acquire() + try: + now = datetime.utcnow() + for t,f in list(self._active_fetchers): + if t > now: + break + f.timeout() + self.purge_items() + finally: + self._lock.release() + + def remove_fetcher(self, fetcher): + """Remove a running fetcher from the list of active fetchers. + + :Parameters: + - `fetcher`: fetcher instance. + :Types: + - `fetcher`: `CacheFetcher`""" + self._lock.acquire() + try: + for t, f in list(self._active_fetchers): + if f is fetcher: + self._active_fetchers.remove((t, f)) + f._deactivated() + return + finally: + self._lock.release() + + def set_fetcher(self, fetcher_class): + """Set the fetcher class. + + :Parameters: + - `fetcher_class`: the fetcher class. + :Types: + - `fetcher_class`: `CacheFetcher` based class + """ + self._lock.acquire() + try: + self._fetcher = fetcher_class + finally: + self._lock.release() + +class CacheSuite: + """Caching proxy for object retrieval and caching. + + Object factories for other classes are registered in the + `Cache` object and used to e.g. retrieve requested objects from network. + They are called only when the requested object is not in the cache + or is not fresh enough. + + Objects are addressed using their class and a class dependant address. + Eg. `pyxmpp.jabber.disco.DiscoInfo` objects are addressed using + (`pyxmpp.jabber.disco.DiscoInfo`,(jid, node)) tuple. + + Additionaly a state (freshness level) name may be provided when requesting + an object. When the cached item state is "less fresh" then requested, then + new object will be retrieved. + + Following states are defined: + + - 'new': always a new object should be retrieved. + - 'fresh': a fresh object (not older than freshness time) + - 'old': object not fresh, but most probably still valid. + - 'stale': object known to be expired. + + :Ivariables: + - `default_freshness_period`: default freshness period (in seconds). + - `default_expiration_period`: default expiration period (in seconds). + - `default_purge_period`: default purge period (in seconds). When + 0 then items are never purged because of their age. + - `max_items`: maximum number of obejects of one class to store. + - `_caches`: dictionary of per-class caches. + - `_lock`: lock for thread safety. + :Types: + - `default_freshness_period`: timedelta + - `default_expiration_period`: timedelta + - `default_purge_period`: timedelta + - `max_items`: `int` + - `_caches`: `dict` of (`classobj`, addr) -> `Cache` + - `_lock`: `threading.RLock` + """ + def __init__(self, max_items, default_freshness_period = _hour, + default_expiration_period = 12*_hour, default_purge_period = 24*_hour): + """Initialize a `Cache` object. + + :Parameters: + - `default_freshness_period`: default freshness period (in seconds). + - `default_expiration_period`: default expiration period (in seconds). + - `default_purge_period`: default purge period (in seconds). When + 0 then items are never purged because of their age. + - `max_items`: maximum number of items to store. + :Types: + - `default_freshness_period`: number + - `default_expiration_period`: number + - `default_purge_period`: number + - `max_items`: number + """ + self.default_freshness_period = default_freshness_period + self.default_expiration_period = default_expiration_period + self.default_purge_period = default_purge_period + self.max_items = max_items + self._caches = {} + self._lock = threading.RLock() + + def request_object(self, object_class, address, state, object_handler, + error_handler = None, timeout_handler = None, + backup_state = None, timeout = None, + freshness_period = None, expiration_period = None, purge_period = None): + """Request an object of given class, with given address and state not + worse than `state`. The object will be taken from cache if available, + and created/fetched otherwise. The request is asynchronous -- this + metod doesn't return the object directly, but the `object_handler` is + called as soon as the object is available (this may be before + `request_object` returns and may happen in other thread). On error the + `error_handler` will be called, and on timeout -- the + `timeout_handler`. + + :Parameters: + - `object_class`: class (type) of the object requested. + - `address`: address of the object requested. + - `state`: the worst acceptable object state. When 'new' then always + a new object will be created/fetched. 'stale' will select any + item available in cache. + - `object_handler`: function to be called when object is available. + It will be called with the following arguments: address, object + and its state. + - `error_handler`: function to be called on object retrieval error. + It will be called with two arguments: requested address and + additional error information (fetcher-specific, may be + StanzaError for XMPP objects). If not given, then the object + handler will be called with object set to `None` and state + "error". + - `timeout_handler`: function to be called on object retrieval + timeout. It will be called with only one argument: the requested + address. If not given, then the `error_handler` will be called + instead, with error details set to `None`. + - `backup_state`: when set and object in state `state` is not + available in the cache and object retrieval failed then object + with this state will also be looked-up in the cache and provided + if available. + - `timeout`: time interval after which retrieval of the object + should be given up. + - `freshness_period`: time interval after which the item created + should become 'old'. + - `expiration_period`: time interval after which the item created + should become 'stale'. + - `purge_period`: time interval after which the item created + shuld be removed from the cache. + :Types: + - `object_class`: `classobj` + - `address`: any hashable + - `state`: "new", "fresh", "old" or "stale" + - `object_handler`: callable(address, value, state) + - `error_handler`: callable(address, error_data) + - `timeout_handler`: callable(address) + - `backup_state`: "new", "fresh", "old" or "stale" + - `timeout`: `timedelta` + - `freshness_period`: `timedelta` + - `expiration_period`: `timedelta` + - `purge_period`: `timedelta` + """ + + self._lock.acquire() + try: + if object_class not in self._caches: + raise TypeError, "No cache for %r" % (object_class,) + + self._caches[object_class].request_object(address, state, object_handler, + error_handler, timeout_handler, backup_state, timeout, + freshness_period, expiration_period, purge_period) + finally: + self._lock.release() + + def tick(self): + """Do the regular cache maintenance. + + Must be called from time to time for timeouts and cache old items + purging to work.""" + self._lock.acquire() + try: + for cache in self._caches.values(): + cache.tick() + finally: + self._lock.release() + + def register_fetcher(self, object_class, fetcher_class): + """Register a fetcher class for an object class. + + :Parameters: + - `object_class`: class to be retrieved by the fetcher. + - `fetcher_class`: the fetcher class. + :Types: + - `object_class`: `classobj` + - `fetcher_class`: `CacheFetcher` based class + """ + self._lock.acquire() + try: + cache = self._caches.get(object_class) + if not cache: + cache = Cache(self.max_items, self.default_freshness_period, + self.default_expiration_period, self.default_purge_period) + self._caches[object_class] = cache + cache.set_fetcher(fetcher_class) + finally: + self._lock.release() + + def unregister_fetcher(self, object_class): + """Unregister a fetcher class for an object class. + + :Parameters: + - `object_class`: class retrieved by the fetcher. + :Types: + - `object_class`: `classobj` + """ + self._lock.acquire() + try: + cache = self._caches.get(object_class) + if not cache: + return + cache.set_fetcher(None) + finally: + self._lock.release() + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/client.py b/digsby/lib/pyxmpp/client.py new file mode 100644 index 0000000..309a587 --- /dev/null +++ b/digsby/lib/pyxmpp/client.py @@ -0,0 +1,474 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""Basic XMPP-IM client implementation. + +Normative reference: + - `RFC 3921 `__ +""" + +__revision__="$Id: client.py 717 2010-04-09 09:36:45Z jajcus $" +__docformat__="restructuredtext en" + +import threading +import logging + +from pyxmpp.clientstream import ClientStream +from pyxmpp.iq import Iq +from pyxmpp.presence import Presence +from pyxmpp.roster import Roster +from pyxmpp.exceptions import ClientError, FatalClientError +from pyxmpp.interfaces import IPresenceHandlersProvider, IMessageHandlersProvider +from pyxmpp.interfaces import IIqHandlersProvider, IStanzaHandlersProvider + +class Client: + """Base class for an XMPP-IM client. + + This class does not provide any JSF extensions to the XMPP protocol, + including legacy authentication methods. + + :Ivariables: + - `jid`: configured JID of the client (current actual JID + is avialable as `self.stream.jid`). + - `password`: authentication password. + - `server`: server to use if non-standard and not discoverable + by SRV lookups. + - `port`: port number on the server to use if non-standard and not + discoverable by SRV lookups. + - `auth_methods`: methods allowed for stream authentication. SASL + mechanism names should be preceded with "sasl:" prefix. + - `keepalive`: keepalive interval for the stream or 0 when keepalive is + disabled. + - `stream`: current stream when the client is connected, + `None` otherwise. + - `roster`: user's roster or `None` if the roster is not yet retrieved. + - `session_established`: `True` when an IM session is established. + - `lock`: lock for synchronizing `Client` attributes access. + - `state_changed`: condition notified the the object state changes + (stream becomes connected, session established etc.). + - `interface_providers`: list of object providing interfaces that + could be used by the Client object. Initialized to [`self`] by + the constructor if not set earlier. Put objects providing + `IPresenceHandlersProvider`, `IMessageHandlersProvider`, + `IIqHandlersProvider` or `IStanzaHandlersProvider` into this list. + :Types: + - `jid`: `pyxmpp.JID` + - `password`: `unicode` + - `server`: `unicode` + - `port`: `int` + - `auth_methods`: `list` of `str` + - `keepalive`: `int` + - `stream`: `pyxmpp.ClientStream` + - `roster`: `pyxmpp.Roster` + - `session_established`: `bool` + - `lock`: `threading.RLock` + - `state_changed`: `threading.Condition` + - `interface_providers`: `list` + """ + def __init__(self,jid=None,password=None,server=None,port=5222, + auth_methods=("sasl:DIGEST-MD5",), + tls_settings=None,keepalive=0): + """Initialize a Client object. + + :Parameters: + - `jid`: user full JID for the connection. + - `password`: user password. + - `server`: server to use. If not given then address will be derived form the JID. + - `port`: port number to use. If not given then address will be derived form the JID. + - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms + in the list should be prefixed with "sasl:" string. + - `tls_settings`: settings for StartTLS -- `TLSSettings` instance. + - `keepalive`: keepalive output interval. 0 to disable. + :Types: + - `jid`: `pyxmpp.JID` + - `password`: `unicode` + - `server`: `unicode` + - `port`: `int` + - `auth_methods`: sequence of `str` + - `tls_settings`: `pyxmpp.TLSSettings` + - `keepalive`: `int` + """ + self.jid=jid + self.password=password + self.server=server + self.port=port + self.auth_methods=list(auth_methods) + self.tls_settings=tls_settings + self.keepalive=keepalive + self.stream=None + self.lock=threading.RLock() + self.state_changed=threading.Condition(self.lock) + self.session_established=False + self.roster=None + self.stream_class=ClientStream + if not hasattr(self, "interface_providers"): + self.interface_providers = [self] + self.__logger=logging.getLogger("pyxmpp.Client") + +# public methods + + def connect(self, register = False): + """Connect to the server and set up the stream. + + Set `self.stream` and notify `self.state_changed` when connection + succeeds.""" + if not self.jid: + raise ClientError, "Cannot connect: no or bad JID given" + self.lock.acquire() + try: + stream = self.stream + self.stream = None + if stream: + import common + common.netcall(stream.close) + + self.__logger.debug("Creating client stream: %r, auth_methods=%r" + % (self.stream_class, self.auth_methods)) + stream=self.stream_class(jid = self.jid, + password = self.password, + server = self.server, + port = self.port, + auth_methods = self.auth_methods, + tls_settings = self.tls_settings, + keepalive = self.keepalive, + owner = self) + stream.process_stream_error = self.stream_error + self.stream_created(stream) + stream.state_change = self.__stream_state_change + stream.connect() + self.stream = stream + self.state_changed.notify() + self.state_changed.release() + except: + self.stream = None + self.state_changed.release() + raise + + def get_stream(self): + """Get the connected stream object. + + :return: stream object or `None` if the client is not connected. + :returntype: `pyxmpp.ClientStream`""" + self.lock.acquire() + stream=self.stream + self.lock.release() + return stream + + def disconnect(self): + """Disconnect from the server.""" + stream=self.get_stream() + if stream: + stream.disconnect() + + def request_session(self): + """Request an IM session.""" + stream=self.get_stream() + if not stream.version: + need_session=False + elif not stream.features: + need_session=False + else: + ctxt = stream.doc_in.xpathNewContext() + ctxt.setContextNode(stream.features) + ctxt.xpathRegisterNs("sess","urn:ietf:params:xml:ns:xmpp-session") + # jabberd2 hack + ctxt.xpathRegisterNs("jsess","http://jabberd.jabberstudio.org/ns/session/1.0") + sess_n=None + try: + sess_n=ctxt.xpathEval("sess:session or jsess:session") + finally: + ctxt.xpathFreeContext() + if sess_n: + need_session=True + else: + need_session=False + + if not need_session: + self.state_changed.acquire() + self.session_established=1 + self.state_changed.notify() + self.state_changed.release() + self._session_started() + else: + iq=Iq(stanza_type="set") + iq.new_query("urn:ietf:params:xml:ns:xmpp-session","session") + stream.set_response_handlers(iq, + self.__session_result,self.__session_error,self.__session_timeout) + stream.send(iq) + + def request_roster(self): + """Request the user's roster.""" + stream=self.get_stream() + iq=Iq(stanza_type="get") + iq.new_query("jabber:iq:roster") + stream.set_response_handlers(iq, + self.__roster_result,self.__roster_error,self.__roster_timeout) + stream.set_iq_set_handler("query","jabber:iq:roster",self.__roster_push) + stream.send(iq) + + def get_socket(self): + """Get the socket object of the active connection. + + :return: socket used by the stream. + :returntype: `socket.socket`""" + return self.stream.socket + + def loop(self,timeout=1): + """Simple "main loop" for the client. + + By default just call the `pyxmpp.Stream.loop_iter` method of + `self.stream`, which handles stream input and `self.idle` for some + "housekeeping" work until the stream is closed. + + This usually will be replaced by something more sophisticated. E.g. + handling of other input sources.""" + while 1: + stream=self.get_stream() + if not stream: + break + act=stream.loop_iter(timeout) + if not act: + self.idle() + +# private methods + + def __session_timeout(self): + """Process session request time out. + + :raise FatalClientError:""" + raise FatalClientError("Timeout while tryin to establish a session") + + def __session_error(self,iq): + """Process session request failure. + + :Parameters: + - `iq`: IQ error stanza received as result of the session request. + :Types: + - `iq`: `pyxmpp.Iq` + + :raise FatalClientError:""" + err=iq.get_error() + msg=err.get_message() + raise FatalClientError("Failed to establish a session: "+msg) + + def __session_result(self, _unused): + """Process session request success. + + :Parameters: + - `_unused`: IQ result stanza received in reply to the session request. + :Types: + - `_unused`: `pyxmpp.Iq`""" + self.state_changed.acquire() + self.session_established=True + self.state_changed.notify() + self.state_changed.release() + self._session_started() + + def _session_started(self): + """Called when session is started. + + Activates objects from `self.interface_provides` by installing + their stanza handlers, etc.""" + for ob in self.interface_providers: + if IPresenceHandlersProvider.providedBy(ob): + for handler_data in ob.get_presence_handlers(): + self.stream.set_presence_handler(*handler_data) + if IMessageHandlersProvider.providedBy(ob): + for handler_data in ob.get_message_handlers(): + self.stream.set_message_handler(*handler_data) + if IIqHandlersProvider.providedBy(ob): + for handler_data in ob.get_iq_get_handlers(): + self.stream.set_iq_get_handler(*handler_data) + for handler_data in ob.get_iq_set_handlers(): + self.stream.set_iq_set_handler(*handler_data) + self.session_started() + + def __roster_timeout(self): + """Process roster request time out. + + :raise ClientError:""" + raise ClientError("Timeout while tryin to retrieve roster") + + def __roster_error(self,iq): + """Process roster request failure. + + :Parameters: + - `iq`: IQ error stanza received as result of the roster request. + :Types: + - `iq`: `pyxmpp.Iq` + + :raise ClientError:""" + err=iq.get_error() + msg=err.get_message() + raise ClientError("Roster retrieval failed: "+msg) + + def __roster_result(self,iq): + """Process roster request success. + + :Parameters: + - `iq`: IQ result stanza received in reply to the roster request. + :Types: + - `iq`: `pyxmpp.Iq`""" + q=iq.get_query() + if q: + self.state_changed.acquire() + self.roster=Roster(q) + self.state_changed.notify() + self.state_changed.release() + self.roster_updated() + else: + raise ClientError("Roster retrieval failed") + + def __roster_push(self,iq): + """Process a "roster push" (change notification) received. + + :Parameters: + - `iq`: IQ result stanza received. + :Types: + - `iq`: `pyxmpp.Iq`""" + fr=iq.get_from() + if fr and fr.bare() != self.jid.bare(): + resp=iq.make_error_response("forbidden") + self.stream.send(resp) + self.__logger.warning("Got roster update from wrong source") + return + if not self.roster: + raise ClientError("Roster update, but no roster") + q=iq.get_query() + items=self.roster.update(q) + for item in items: + self.roster_updated(item) + resp=iq.make_result_response() + self.stream.send(resp) + + def __stream_state_change(self,state,arg): + """Handle stream state changes. + + Call apopriate methods of self. + + :Parameters: + - `state`: the new state. + - `arg`: state change argument. + :Types: + - `state`: `str`""" + self.stream_state_changed(state,arg) + if state=="fully connected": + self.connected() + elif state=="authorized": + self.authorized() + elif state=="disconnected": + self.state_changed.acquire() + try: + if self.stream: + self.stream.close() + self.stream_closed(self.stream) + self.stream=None + self.state_changed.notify() + finally: + self.state_changed.release() + self.disconnected() + +# Method to override + def idle(self): + """Do some "housekeeping" work like cache expiration or timeout + handling. Should be called periodically from the application main + loop. May be overriden in derived classes.""" + stream=self.get_stream() + if stream: + stream.idle() + + def stream_created(self,stream): + """Handle stream creation event. May be overriden in derived classes. + This one does nothing. + + :Parameters: + - `stream`: the new stream. + :Types: + - `stream`: `pyxmpp.ClientStream`""" + pass + + def stream_closed(self,stream): + """Handle stream closure event. May be overriden in derived classes. + This one does nothing. + + :Parameters: + - `stream`: the new stream. + :Types: + - `stream`: `pyxmpp.ClientStream`""" + pass + + def session_started(self): + """Handle session started event. May be overriden in derived classes. + This one requests the user's roster and sends the initial presence.""" + self.request_roster() + p=Presence() + self.stream.send(p) + + def stream_error(self,err): + """Handle stream error received. May be overriden in derived classes. + This one passes an error messages to logging facilities. + + :Parameters: + - `err`: the error element received. + :Types: + - `err`: `pyxmpp.error.StreamErrorNode`""" + self.__logger.error("Stream error: condition: %s %r" + % (err.get_condition().name,err.serialize())) + + def roster_updated(self,item=None): + """Handle roster update event. May be overriden in derived classes. + This one does nothing. + + :Parameters: + - `item`: the roster item changed or `None` if whole roster was + received. + :Types: + - `item`: `pyxmpp.RosterItem`""" + pass + + def stream_state_changed(self,state,arg): + """Handle any stream state change. May be overriden in derived classes. + This one does nothing. + + :Parameters: + - `state`: the new state. + - `arg`: state change argument. + :Types: + - `state`: `str`""" + pass + + def connected(self): + """Handle "connected" event. May be overriden in derived classes. + This one does nothing.""" + pass + + def authenticated(self): + """Handle "authenticated" event. May be overriden in derived classes. + This one does nothing.""" + pass + + def authorized(self): + """Handle "authorized" event. May be overriden in derived classes. + This one requests an IM session.""" + self.request_session() + + def disconnected(self): + """Handle "disconnected" event. May be overriden in derived classes. + This one does nothing.""" + pass + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/clientstream.py b/digsby/lib/pyxmpp/clientstream.py new file mode 100644 index 0000000..00ca961 --- /dev/null +++ b/digsby/lib/pyxmpp/clientstream.py @@ -0,0 +1,377 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# pylint: disable-msg=W0221 + +"""Client stream handling. + +Normative reference: + - `RFC 3920 `__ +""" + +__revision__="$Id: clientstream.py 720 2010-04-20 10:31:35Z jajcus $" +__docformat__="restructuredtext en" + +import logging + +from pyxmpp.stream import Stream +from pyxmpp.streambase import BIND_NS +from pyxmpp.streamsasl import SASLNotAvailable,SASLMechanismNotAvailable +from pyxmpp.jid import JID +from pyxmpp.utils import to_utf8 +from pyxmpp.exceptions import StreamError,StreamAuthenticationError,FatalStreamError +from pyxmpp.exceptions import ClientStreamError, FatalClientStreamError + +class ClientStream(Stream): + """Handles XMPP-IM client connection stream. + + Both client and server side of the connection is supported. This class handles + client SASL authentication, authorisation and resource binding. + + This class is not ready for handling of legacy Jabber servers, as it doesn't + provide legacy authentication. + + :Ivariables: + - `my_jid`: requested local JID. Please notice that this may differ from + `me`, which is actual authorized JID after the resource binding. + - `server`: server to use. + - `port`: port number to use. + - `password`: user's password. + - `auth_methods`: allowed authentication methods. + :Types: + - `my_jid`: `pyxmpp.JID` + - `server`: `str` + - `port`: `int` + - `password`: `str` + - `auth_methods`: `list` of `str` + """ + def __init__(self, jid, password=None, server=None, port=None, + auth_methods = ("sasl:DIGEST-MD5",), + tls_settings = None, keepalive = 0, owner = None): + """Initialize the ClientStream object. + + :Parameters: + - `jid`: local JID. + - `password`: user's password. + - `server`: server to use. If not given then address will be derived form the JID. + - `port`: port number to use. If not given then address will be derived form the JID. + - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms + in the list should be prefixed with "sasl:" string. + - `tls_settings`: settings for StartTLS -- `TLSSettings` instance. + - `keepalive`: keepalive output interval. 0 to disable. + - `owner`: `Client`, `Component` or similar object "owning" this stream. + :Types: + - `jid`: `pyxmpp.JID` + - `password`: `unicode` + - `server`: `unicode` + - `port`: `int` + - `auth_methods`: sequence of `str` + - `tls_settings`: `pyxmpp.TLSSettings` + - `keepalive`: `int` + """ + sasl_mechanisms=[] + for m in auth_methods: + if not m.startswith("sasl:"): + continue + m=m[5:].upper() + sasl_mechanisms.append(m) + Stream.__init__(self, "jabber:client", + sasl_mechanisms = sasl_mechanisms, + tls_settings = tls_settings, + keepalive = keepalive, + owner = owner) + self.server=server + self.port=port + self.password=password + self.auth_methods=auth_methods + self.my_jid=jid + self.me = None + self._auth_methods_left = None + self.__logger=logging.getLogger("pyxmpp.ClientStream") + + def _reset(self): + """Reset `ClientStream` object state, making the object ready to handle + new connections.""" + Stream._reset(self) + self._auth_methods_left=[] + + def connect(self,server=None,port=None): + """Establish a client connection to a server. + + [client only] + + :Parameters: + - `server`: name or address of the server to use. Not recommended -- proper value + should be derived automatically from the JID. + - `port`: port number of the server to use. Not recommended -- + proper value should be derived automatically from the JID. + + :Types: + - `server`: `unicode` + - `port`: `int`""" + self.lock.acquire() + try: + self._connect(server,port) + finally: + self.lock.release() + + def _connect(self,server=None,port=None): + """Same as `ClientStream.connect` but assume `self.lock` is acquired.""" + if not self.my_jid.node or not self.my_jid.resource: + raise ClientStreamError,"Client JID must have username and resource" + if not server: + server=self.server + if not port: + port=self.port + if server: + self.__logger.debug("server: %r", (server,)) + service=None + else: + service="xmpp-client" + if port is None: + port=5222 + if server is None: + server=self.my_jid.domain + self.me=self.my_jid + Stream._connect(self,server,port,service,self.my_jid.domain) + + def accept(self,sock): + """Accept an incoming client connection. + + [server only] + + :Parameters: + - `sock`: a listening socket.""" + Stream.accept(self,sock,self.my_jid) + + def _post_connect(self): + """Initialize authentication when the connection is established + and we are the initiator.""" + if self.initiator: + self._auth_methods_left=list(self.auth_methods) + self._try_auth() + + def _try_auth(self): + """Try to authenticate using the first one of allowed authentication + methods left. + + [client only]""" + if not self.doc_out: + self.__logger.debug("try_auth: disconnecting already?") + return + if self.authenticated: + self.__logger.debug("try_auth: already authenticated") + return + self.__logger.debug("trying auth: %r", (self._auth_methods_left,)) + if not self._auth_methods_left: + raise StreamAuthenticationError,"No allowed authentication methods available" + method=self._auth_methods_left[0] + if method.startswith("sasl:"): + if self.version: + self._auth_methods_left.pop(0) + try: + mechanism = method[5:].upper() + # A bit hackish, but I'm not sure whether giving authzid won't mess something up + if mechanism != "EXTERNAL": + self._sasl_authenticate(self.my_jid.node, None, + mechanism=mechanism) + else: + self._sasl_authenticate(self.my_jid.node, self.my_jid.bare().as_utf8(), + mechanism=mechanism) + except (SASLMechanismNotAvailable,SASLNotAvailable): + self.__logger.debug("Skipping unavailable auth method: %s", (method,) ) + return self._try_auth() + else: + self._auth_methods_left.pop(0) + self.__logger.debug("Skipping auth method %s as legacy protocol is in use", + (method,) ) + return self._try_auth() + else: + self._auth_methods_left.pop(0) + self.__logger.debug("Skipping unknown auth method: %s", method) + return self._try_auth() + + def _get_stream_features(self): + """Include resource binding feature in the stream features list. + + [server only]""" + features=Stream._get_stream_features(self) + if self.peer_authenticated: + bind=features.newChild(None,"bind",None) + ns=bind.newNs(BIND_NS,None) + bind.setNs(ns) + self.set_iq_set_handler("bind",BIND_NS,self.do_bind) + return features + + def do_bind(self,stanza): + """Do the resource binding requested by a client connected. + + [server only] + + :Parameters: + - `stanza`: resource binding request stanza. + :Types: + - `stanza`: `pyxmpp.Iq`""" + fr=stanza.get_from() + if fr and fr!=self.peer: + r=stanza.make_error_response("forbidden") + self.send(r) + r.free() + return + resource_n=stanza.xpath_eval("bind:bind/bind:resource",{"bind":BIND_NS}) + if resource_n: + resource=resource_n[0].getContent() + else: + resource="auto" + if not resource: + r=stanza.make_error_response("bad-request") + else: + self.unset_iq_set_handler("bind",BIND_NS) + r=stanza.make_result_response() + self.peer.set_resource(resource) + q=r.new_query(BIND_NS,"bind") + q.newTextChild(None,"jid",to_utf8(self.peer.as_unicode())) + self.state_change("authorized",self.peer) + r.set_to(None) + self.send(r) + r.free() + + def get_password(self, username, realm=None, acceptable_formats=("plain",)): + """Get a user password for the SASL authentication. + + :Parameters: + - `username`: username used for authentication. + - `realm`: realm used for authentication. + - `acceptable_formats`: acceptable password encoding formats requested. + :Types: + - `username`: `unicode` + - `realm`: `unicode` + - `acceptable_formats`: `list` of `str` + + :return: The password and the format name ('plain'). + :returntype: (`unicode`,`str`)""" + _unused = realm + if self.initiator and self.my_jid.node==username and "plain" in acceptable_formats: + return self.password,"plain" + else: + return None,None + + def get_realms(self): + """Get realms available for client authentication. + + [server only] + + :return: list of realms. + :returntype: `list` of `unicode`""" + return [self.my_jid.domain] + + def choose_realm(self,realm_list): + """Choose authentication realm from the list provided by the server. + + [client only] + + Use domain of the own JID if no realm list was provided or the domain is on the list + or the first realm on the list otherwise. + + :Parameters: + - `realm_list`: realm list provided by the server. + :Types: + - `realm_list`: `list` of `unicode` + + :return: the realm chosen. + :returntype: `unicode`""" + if not realm_list: + return self.my_jid.domain + if self.my_jid.domain in realm_list: + return self.my_jid.domain + return realm_list[0] + + def check_authzid(self,authzid,extra_info=None): + """Check authorization id provided by the client. + + [server only] + + :Parameters: + - `authzid`: authorization id provided. + - `extra_info`: additional information about the user + from the authentication backend. This mapping will + usually contain at least 'username' item. + :Types: + - `authzid`: unicode + - `extra_info`: mapping + + :return: `True` if user is authorized to use that `authzid`. + :returntype: `bool`""" + if not extra_info: + extra_info={} + if not authzid: + return 1 + if not self.initiator: + jid=JID(authzid) + if not extra_info.has_key("username"): + ret=0 + elif jid.node!=extra_info["username"]: + ret=0 + elif jid.domain!=self.my_jid.domain: + ret=0 + elif not jid.resource: + ret=0 + else: + ret=1 + else: + ret=0 + return ret + + def get_serv_type(self): + """Get the server name for SASL authentication. + + :return: 'xmpp'.""" + return "xmpp" + + def get_serv_name(self): + """Get the service name for SASL authentication. + + :return: domain of the own JID.""" + return self.my_jid.domain + + def get_serv_host(self): + """Get the service host name for SASL authentication. + + :return: domain of the own JID.""" + # FIXME: that should be the hostname choosen from SRV records found. + return self.my_jid.domain + + def fix_out_stanza(self,stanza): + """Fix outgoing stanza. + + On a client clear the sender JID. On a server set the sender + address to the own JID if the address is not set yet.""" + if self.initiator: + stanza.set_from(None) + else: + if not stanza.get_from(): + stanza.set_from(self.my_jid) + + def fix_in_stanza(self,stanza): + """Fix an incoming stanza. + + Ona server replace the sender address with authorized client JID.""" + if self.initiator: + Stream.fix_in_stanza(self,stanza) + else: + stanza.set_from(self.peer) + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/error.py b/digsby/lib/pyxmpp/error.py new file mode 100644 index 0000000..6aaca34 --- /dev/null +++ b/digsby/lib/pyxmpp/error.py @@ -0,0 +1,538 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""XMPP error handling. + +Normative reference: + - `RFC 3920 `__ + - `JEP 86 `__ +""" + +__revision__="$Id: error.py 714 2010-04-05 10:20:10Z jajcus $" +__docformat__="restructuredtext en" + +import libxml2 + +from pyxmpp.utils import from_utf8, to_utf8 +from pyxmpp.xmlextra import common_doc, common_root, common_ns +from pyxmpp import xmlextra +from pyxmpp.exceptions import ProtocolError + +stream_errors={ + u"bad-format": + ("Received XML cannot be processed",), + u"bad-namespace-prefix": + ("Bad namespace prefix",), + u"conflict": + ("Closing stream because of conflicting stream being opened",), + u"connection-timeout": + ("Connection was idle too long",), + u"host-gone": + ("Hostname is no longer hosted on the server",), + u"host-unknown": + ("Hostname requested is not known to the server",), + u"improper-addressing": + ("Improper addressing",), + u"internal-server-error": + ("Internal server error",), + u"invalid-from": + ("Invalid sender address",), + u"invalid-id": + ("Invalid stream ID",), + u"invalid-namespace": + ("Invalid namespace",), + u"invalid-xml": + ("Invalid XML",), + u"not-authorized": + ("Not authorized",), + u"policy-violation": + ("Local policy violation",), + u"remote-connection-failed": + ("Remote connection failed",), + u"resource-constraint": + ("Remote connection failed",), + u"restricted-xml": + ("Restricted XML received",), + u"see-other-host": + ("Redirection required",), + u"system-shutdown": + ("The server is being shut down",), + u"undefined-condition": + ("Unknown error",), + u"unsupported-encoding": + ("Unsupported encoding",), + u"unsupported-stanza-type": + ("Unsupported stanza type",), + u"unsupported-version": + ("Unsupported protocol version",), + u"xml-not-well-formed": + ("XML sent by client is not well formed",), + } + +stanza_errors={ + u"bad-request": + ("Bad request", + "modify",400), + u"conflict": + ("Named session or resource already exists", + "cancel",409), + u"feature-not-implemented": + ("Feature requested is not implemented", + "cancel",501), + u"forbidden": + ("You are forbidden to perform requested action", + "auth",403), + u"gone": + ("Recipient or server can no longer be contacted at this address", + "modify",302), + u"internal-server-error": + ("Internal server error", + "wait",500), + u"item-not-found": + ("Item not found" + ,"cancel",404), + u"jid-malformed": + ("JID malformed", + "modify",400), + u"not-acceptable": + ("Requested action is not acceptable", + "modify",406), + u"not-allowed": + ("Requested action is not allowed", + "cancel",405), + u"not-authorized": + ("Not authorized", + "auth",401), + u"payment-required": + ("Payment required", + "auth",402), + u"recipient-unavailable": + ("Recipient is not available", + "wait",404), + u"redirect": + ("Redirection", + "modify",302), + u"registration-required": + ("Registration required", + "auth",407), + u"remote-server-not-found": + ("Remote server not found", + "cancel",404), + u"remote-server-timeout": + ("Remote server timeout", + "wait",504), + u"resource-constraint": + ("Resource constraint", + "wait",500), + u"service-unavailable": + ("Service is not available", + "cancel",503), + u"subscription-required": + ("Subscription is required", + "auth",407), + u"undefined-condition": + ("Unknown error", + "cancel",500), + u"unexpected-request": + ("Unexpected request", + "wait",400), + } + +legacy_codes={ + 302: "redirect", + 400: "bad-request", + 401: "not-authorized", + 402: "payment-required", + 403: "forbidden", + 404: "item-not-found", + 405: "not-allowed", + 406: "not-acceptable", + 407: "registration-required", + 408: "remote-server-timeout", + 409: "conflict", + 500: "internal-server-error", + 501: "feature-not-implemented", + 502: "service-unavailable", + 503: "service-unavailable", + 504: "remote-server-timeout", + 510: "service-unavailable", + } + +STANZA_ERROR_NS='urn:ietf:params:xml:ns:xmpp-stanzas' +STREAM_ERROR_NS='urn:ietf:params:xml:ns:xmpp-streams' +PYXMPP_ERROR_NS='http://pyxmpp.jajcus.net/xmlns/errors' +STREAM_NS="http://etherx.jabber.org/streams" + +class ErrorNode: + """Base class for both XMPP stream and stanza errors""" + def __init__(self,xmlnode_or_cond,ns=None,copy=True,parent=None): + """Initialize an ErrorNode object. + + :Parameters: + - `xmlnode_or_cond`: XML node to be wrapped into this object + or error condition name. + - `ns`: XML namespace URI of the error condition element (to be + used when the provided node has no namespace). + - `copy`: When `True` then the XML node will be copied, + otherwise it is only borrowed. + - `parent`: Parent node for the XML node to be copied or created. + :Types: + - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode` + - `ns`: `unicode` + - `copy`: `bool` + - `parent`: `libxml2.xmlNode`""" + if type(xmlnode_or_cond) is str: + xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8") + self.xmlnode=None + self.borrowed=0 + if isinstance(xmlnode_or_cond,libxml2.xmlNode): + self.__from_xml(xmlnode_or_cond,ns,copy,parent) + elif isinstance(xmlnode_or_cond,ErrorNode): + if not copy: + raise TypeError, "ErrorNodes may only be copied" + self.ns=from_utf8(xmlnode_or_cond.ns.getContent()) + self.xmlnode=xmlnode_or_cond.xmlnode.docCopyNode(common_doc,1) + if not parent: + parent=common_root + parent.addChild(self.xmlnode) + elif ns is None: + raise ValueError, "Condition namespace not given" + else: + if parent: + self.xmlnode=parent.newChild(common_ns,"error",None) + self.borrowed=1 + else: + self.xmlnode=common_root.newChild(common_ns,"error",None) + cond=self.xmlnode.newChild(None,to_utf8(xmlnode_or_cond),None) + ns=cond.newNs(ns,None) + cond.setNs(ns) + self.ns=from_utf8(ns.getContent()) + + def __from_xml(self,xmlnode,ns,copy,parent): + """Initialize an ErrorNode object from an XML node. + + :Parameters: + - `xmlnode`: XML node to be wrapped into this object. + - `ns`: XML namespace URI of the error condition element (to be + used when the provided node has no namespace). + - `copy`: When `True` then the XML node will be copied, + otherwise it is only borrowed. + - `parent`: Parent node for the XML node to be copied or created. + :Types: + - `xmlnode`: `libxml2.xmlNode` + - `ns`: `unicode` + - `copy`: `bool` + - `parent`: `libxml2.xmlNode`""" + if not ns: + ns=None + c=xmlnode.children + while c: + ns=c.ns().getContent() + if ns in (STREAM_ERROR_NS,STANZA_ERROR_NS): + break + ns=None + c=c.next + if ns==None: + raise ProtocolError, "Bad error namespace" + self.ns=from_utf8(ns) + if copy: + self.xmlnode=xmlnode.docCopyNode(common_doc,1) + if not parent: + parent=common_root + parent.addChild(self.xmlnode) + else: + self.xmlnode=xmlnode + self.borrowed=1 + if copy: + ns1=xmlnode.ns() + xmlextra.replace_ns(self.xmlnode, ns1, common_ns) + + def __del__(self): + if self.xmlnode: + self.free() + + def free(self): + """Free the associated XML node.""" + if not self.borrowed: + self.xmlnode.unlinkNode() + self.xmlnode.freeNode() + self.xmlnode=None + + def free_borrowed(self): + """Free the associated "borrowed" XML node.""" + self.xmlnode=None + + def is_legacy(self): + """Check if the error node is a legacy error element. + + :return: `True` if it is a legacy error. + :returntype: `bool`""" + return not self.xmlnode.hasProp("type") + + def xpath_eval(self,expr,namespaces=None): + """Evaluate XPath expression on the error element. + + The expression will be evaluated in context where the common namespace + (the one used for stanza elements, mapped to 'jabber:client', + 'jabber:server', etc.) is bound to prefix "ns" and other namespaces are + bound accordingly to the `namespaces` list. + + :Parameters: + - `expr`: the XPath expression. + - `namespaces`: prefix to namespace mapping. + :Types: + - `expr`: `unicode` + - `namespaces`: `dict` + + :return: the result of the expression evaluation. + """ + ctxt = common_doc.xpathNewContext() + ctxt.setContextNode(self.xmlnode) + ctxt.xpathRegisterNs("ns",to_utf8(self.ns)) + if namespaces: + for prefix,uri in namespaces.items(): + ctxt.xpathRegisterNs(prefix,uri) + ret=ctxt.xpathEval(expr) + ctxt.xpathFreeContext() + return ret + + def get_condition(self,ns=None): + """Get the condition element of the error. + + :Parameters: + - `ns`: namespace URI of the condition element if it is not + the XMPP namespace of the error element. + :Types: + - `ns`: `unicode` + + :return: the condition element or `None`. + :returntype: `libxml2.xmlNode`""" + if ns is None: + ns=self.ns + c=self.xpath_eval("ns:*") + if not c: + self.upgrade() + c=self.xpath_eval("ns:*") + if not c: + return None + if ns==self.ns and c[0].name=="text": + if len(c)==1: + return None + c=c[1:] + return c[0] + + def get_text(self): + """Get the description text from the error element. + + :return: the text provided with the error or `None`. + :returntype: `unicode`""" + c=self.xpath_eval("ns:*") + if not c: + self.upgrade() + t=self.xpath_eval("ns:text") + if not t: + return None + return from_utf8(t[0].getContent()) + + def add_custom_condition(self,ns,cond,content=None): + """Add custom condition element to the error. + + :Parameters: + - `ns`: namespace URI. + - `cond`: condition name. + - `content`: content of the element. + + :Types: + - `ns`: `unicode` + - `cond`: `unicode` + - `content`: `unicode` + + :return: the new condition element. + :returntype: `libxml2.xmlNode`""" + c=self.xmlnode.newTextChild(None,to_utf8(cond),content) + ns=c.newNs(to_utf8(ns),None) + c.setNs(ns) + return c + + def upgrade(self): + """Upgrade a legacy error element to the XMPP compliant one. + + Use the error code provided to select the condition and the + CDATA for the error text.""" + + if not self.xmlnode.hasProp("code"): + code=None + else: + try: + code=int(self.xmlnode.prop("code")) + except (ValueError,KeyError): + code=None + + if code and legacy_codes.has_key(code): + cond=legacy_codes[code] + else: + cond=None + + condition=self.xpath_eval("ns:*") + if condition: + return + elif cond is None: + condition=self.xmlnode.newChild(None,"undefined-condition",None) + ns=condition.newNs(to_utf8(self.ns),None) + condition.setNs(ns) + condition=self.xmlnode.newChild(None,"unknown-legacy-error",None) + ns=condition.newNs(PYXMPP_ERROR_NS,None) + condition.setNs(ns) + else: + condition=self.xmlnode.newChild(None,cond,None) + ns=condition.newNs(to_utf8(self.ns),None) + condition.setNs(ns) + txt=self.xmlnode.getContent() + if txt: + text=self.xmlnode.newTextChild(None,"text",txt) + ns=text.newNs(to_utf8(self.ns),None) + text.setNs(ns) + + def downgrade(self): + """Downgrade an XMPP error element to the legacy format. + + Add a numeric code attribute according to the condition name.""" + if self.xmlnode.hasProp("code"): + return + cond=self.get_condition() + if not cond: + return + cond=cond.name + if stanza_errors.has_key(cond) and stanza_errors[cond][2]: + self.xmlnode.setProp("code",to_utf8(stanza_errors[cond][2])) + + def serialize(self): + """Serialize the element node. + + :return: serialized element in UTF-8 encoding. + :returntype: `str`""" + return self.xmlnode.serialize(encoding="utf-8") + +class StreamErrorNode(ErrorNode): + """Stream error element.""" + def __init__(self,xmlnode_or_cond,copy=1,parent=None): + """Initialize a StreamErrorNode object. + + :Parameters: + - `xmlnode_or_cond`: XML node to be wrapped into this object + or the primary (defined by XMPP specification) error condition name. + - `copy`: When `True` then the XML node will be copied, + otherwise it is only borrowed. + - `parent`: Parent node for the XML node to be copied or created. + :Types: + - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode` + - `copy`: `bool` + - `parent`: `libxml2.xmlNode`""" + if type(xmlnode_or_cond) is str: + xmlnode_or_cond = xmlnode_or_cond.decode("utf-8") + if type(xmlnode_or_cond) is unicode: + if not stream_errors.has_key(xmlnode_or_cond): + raise ValueError, "Bad error condition" + ErrorNode.__init__(self,xmlnode_or_cond,STREAM_ERROR_NS,copy=copy,parent=parent) + + def get_message(self): + """Get the message for the error. + + :return: the error message. + :returntype: `unicode`""" + cond=self.get_condition() + if not cond: + self.upgrade() + cond=self.get_condition() + if not cond: + return None + cond=cond.name + if not stream_errors.has_key(cond): + return None + return stream_errors[cond][0] + +class StanzaErrorNode(ErrorNode): + """Stanza error element.""" + def __init__(self,xmlnode_or_cond,error_type=None,copy=1,parent=None): + """Initialize a StreamErrorNode object. + + :Parameters: + - `xmlnode_or_cond`: XML node to be wrapped into this object + or the primary (defined by XMPP specification) error condition name. + - `error_type`: type of the error, one of: 'cancel', 'continue', + 'modify', 'auth', 'wait'. + - `copy`: When `True` then the XML node will be copied, + otherwise it is only borrowed. + - `parent`: Parent node for the XML node to be copied or created. + :Types: + - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode` + - `error_type`: `unicode` + - `copy`: `bool` + - `parent`: `libxml2.xmlNode`""" + if type(xmlnode_or_cond) is str: + xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8") + if type(xmlnode_or_cond) is unicode: + if not stanza_errors.has_key(xmlnode_or_cond): + raise ValueError, "Bad error condition" + + ErrorNode.__init__(self,xmlnode_or_cond,STANZA_ERROR_NS,copy=copy,parent=parent) + + if type(xmlnode_or_cond) is unicode: + if error_type is None: + error_type=stanza_errors[xmlnode_or_cond][1] + self.xmlnode.setProp("type",to_utf8(error_type)) + + def get_type(self): + """Get the error type. + + :return: type of the error. + :returntype: `unicode`""" + if not self.xmlnode.hasProp("type"): + self.upgrade() + return from_utf8(self.xmlnode.prop("type")) + + def upgrade(self): + """Upgrade a legacy error element to the XMPP compliant one. + + Use the error code provided to select the condition and the + CDATA for the error text.""" + ErrorNode.upgrade(self) + if self.xmlnode.hasProp("type"): + return + + cond=self.get_condition().name + if stanza_errors.has_key(cond): + typ=stanza_errors[cond][1] + self.xmlnode.setProp("type",typ) + + def get_message(self): + """Get the message for the error. + + :return: the error message. + :returntype: `unicode`""" + cond=self.get_condition() + if not cond: + self.upgrade() + cond=self.get_condition() + if not cond: + return None + cond=cond.name + if not stanza_errors.has_key(cond): + return None + return stanza_errors[cond][0] + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/exceptions.py b/digsby/lib/pyxmpp/exceptions.py new file mode 100644 index 0000000..472c918 --- /dev/null +++ b/digsby/lib/pyxmpp/exceptions.py @@ -0,0 +1,178 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""PyXMPP exceptions. + +This module defines all exceptions raised by PyXMPP. +""" + +__revision__="$Id: error.py 647 2006-08-26 18:27:39Z jajcus $" +__docformat__="restructuredtext en" + +import logging + + +class Error(StandardError): + """Base class for all PyXMPP exceptions.""" + pass + +class JIDError(Error, ValueError): + "Exception raised when invalid JID is used" + pass + +class StreamError(Error): + """Base class for all stream errors.""" + pass + +class StreamEncryptionRequired(StreamError): + """Exception raised when stream encryption is requested, but not used.""" + pass + +class HostMismatch(StreamError): + """Exception raised when the connected host name is other then requested.""" + pass + +class FatalStreamError(StreamError): + """Base class for all fatal Stream exceptions. + + When `FatalStreamError` is raised the stream is no longer usable.""" + pass + +class StreamParseError(FatalStreamError): + """Raised when invalid XML is received in an XMPP stream.""" + pass + +class StreamAuthenticationError(FatalStreamError): + """Raised when stream authentication fails.""" + pass + +class TLSNegotiationFailed(FatalStreamError): + """Raised when stream TLS negotiation fails.""" + pass + +class TLSError(FatalStreamError): + """Raised on TLS error during stream processing.""" + pass + +class TLSNegotiatedButNotAvailableError(TLSError): + """Raised on TLS error during stream processing.""" + pass + +class SASLNotAvailable(StreamAuthenticationError): + """Raised when SASL authentication is requested, but not available.""" + pass + +class SASLMechanismNotAvailable(StreamAuthenticationError): + """Raised when none of SASL authentication mechanisms requested is + available.""" + pass + +class SASLAuthenticationFailed(StreamAuthenticationError): + """Raised when stream SASL authentication fails.""" + pass + +class StringprepError(Error): + """Exception raised when string preparation results in error.""" + pass + +class ClientError(Error): + """Raised on a client error.""" + pass + +class FatalClientError(ClientError): + """Raised on a fatal client error.""" + pass + +class ClientStreamError(StreamError): + """Raised on a client stream error.""" + pass + +class FatalClientStreamError(FatalStreamError): + """Raised on a fatal client stream error.""" + pass + +class LegacyAuthenticationError(ClientStreamError): + """Raised on a legacy authentication error.""" + pass + +class RegistrationError(ClientStreamError): + """Raised on a in-band registration error.""" + pass + +class ComponentStreamError(StreamError): + """Raised on a component error.""" + pass + +class FatalComponentStreamError(ComponentStreamError,FatalStreamError): + """Raised on a fatal component error.""" + pass + +######################## +# Protocol Errors + +class ProtocolError(Error): + """Raised when there is something wrong with a stanza processed. + + When not processed earlier by an application, the exception will be catched + by the stanza dispatcher to return XMPP error to the stanza sender, when + allowed. + + ProtocolErrors handled internally by PyXMPP will be logged via the logging + interface. Errors reported to the sender will be logged using + "pyxmpp.ProtocolError.reported" channel and the ignored errors using + "pyxmpp.ProtocolError.ignored" channel. Both with the "debug" level. + + :Ivariables: + - `xmpp_name` -- XMPP error name which should be reported. + - `message` -- the error message.""" + + logger_reported = logging.getLogger("pyxmpp.ProtocolError.reported") + logger_ignored = logging.getLogger("pyxmpp.ProtocolError.ignored") + + def __init__(self, xmpp_name, message): + self.args = (xmpp_name, message) + @property + def xmpp_name(self): + return self.args[0] + @property + def message(self): + return self.args[1] + def log_reported(self): + self.logger_reported.debug(u"Protocol error detected: %s", self.message) + def log_ignored(self): + self.logger_ignored.debug(u"Protocol error detected: %s", self.message) + def __unicode__(self): + return str(self.args[1]) + def __repr__(self): + return "" % (self.xmpp_name, self.message) + +class BadRequestProtocolError(ProtocolError): + """Raised when invalid stanza is processed and 'bad-request' error should be reported.""" + def __init__(self, message): + ProtocolError.__init__(self, "bad-request", message) + +class JIDMalformedProtocolError(ProtocolError, JIDError): + """Raised when invalid JID is encountered.""" + def __init__(self, message): + ProtocolError.__init__(self, "jid-malformed", message) + +class FeatureNotImplementedProtocolError(ProtocolError): + """Raised when stanza requests a feature which is not (yet) implemented.""" + def __init__(self, message): + ProtocolError.__init__(self, "feature-not-implemented", message) + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/expdict.py b/digsby/lib/pyxmpp/expdict.py new file mode 100644 index 0000000..02746d1 --- /dev/null +++ b/digsby/lib/pyxmpp/expdict.py @@ -0,0 +1,149 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""Dictionary with item expiration.""" + +__revision__="$Id: expdict.py 714 2010-04-05 10:20:10Z jajcus $" +__docformat__="restructuredtext en" + +import time +import threading + +__all__ = ['ExpiringDictionary'] + +sentinel = object() + +class ExpiringDictionary(dict): + """An extension to standard Python dictionary objects which implements item + expiration. + + Each item in ExpiringDictionary has its expiration time assigned, after + which the item is removed from the mapping. + + :Ivariables: + - `_timeouts`: a dictionary with timeout values and timeout callback for + stored objects. + - `_default_timeout`: the default timeout value (in seconds from now). + - `_lock`: access synchronization lock. + :Types: + - `_timeouts`: `dict` + - `_default_timeout`: `int` + - `_lock`: `threading.RLock`""" + + __slots__=['_timeouts','_default_timeout','_lock'] + + def __init__(self,default_timeout=300): + """Initialize an `ExpiringDictionary` object. + + :Parameters: + - `default_timeout`: default timeout value for stored objects. + :Types: + - `default_timeout`: `int`""" + dict.__init__(self) + self._timeouts={} + self._default_timeout=default_timeout + self._lock=threading.RLock() + + def __delitem__(self,key): + self._lock.acquire() + try: + del self._timeouts[key] + return dict.__delitem__(self,key) + finally: + self._lock.release() + + def __getitem__(self,key): + self._lock.acquire() + try: + self._expire_item(key) + return dict.__getitem__(self,key) + finally: + self._lock.release() + + def pop(self,key,default=sentinel): + self._lock.acquire() + try: + self._expire_item(key) + del self._timeouts[key] + if default is not sentinel: + return dict.pop(self,key,default) + else: + return dict.pop(self,key) + finally: + self._lock.release() + + def __setitem__(self,key,value): + return self.set_item(key,value) + + def set_item(self,key,value,timeout=None,timeout_callback=None): + """Set item of the dictionary. + + :Parameters: + - `key`: the key. + - `value`: the object to store. + - `timeout`: timeout value for the object (in seconds from now). + - `timeout_callback`: function to be called when the item expires. + The callback should accept none, one (the key) or two (the key + and the value) arguments. + :Types: + - `key`: any hashable value + - `value`: any python object + - `timeout`: `int` + - `timeout_callback`: callable""" + self._lock.acquire() + try: + if not timeout: + timeout=self._default_timeout + self._timeouts[key]=(time.time()+timeout,timeout_callback) + return dict.__setitem__(self,key,value) + finally: + self._lock.release() + + def expire(self): + """Do the expiration of dictionary items. + + Remove items that expired by now from the dictionary.""" + self._lock.acquire() + try: + for k in self._timeouts.keys(): + self._expire_item(k) + finally: + self._lock.release() + + def _expire_item(self,key): + """Do the expiration of a dictionary item. + + Remove the item if it has expired by now. + + :Parameters: + - `key`: key to the object. + :Types: + - `key`: any hashable value""" + (timeout,callback)=self._timeouts[key] + if timeout<=time.time(): + item = dict.pop(self, key) + del self._timeouts[key] + if callback: + try: + callback(key,item) + except TypeError: + try: + callback(key) + except TypeError: + callback() + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/interface.py b/digsby/lib/pyxmpp/interface.py new file mode 100644 index 0000000..8e262cd --- /dev/null +++ b/digsby/lib/pyxmpp/interface.py @@ -0,0 +1,36 @@ +# +# (C) Copyright 2006 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""Interface API. + +If zope.interface is available this module will be its equivalent, otherwise +minimum interface API (partially compatible with zope.interface) will be +defined here. + +When full ZopeInterfaces API is needed impoer zope.interface instead of this module.""" + +__revision__="$Id: utils.py 647 2006-08-26 18:27:39Z jajcus $" + +try: + from zope.interface import Interface, Attribute, providedBy, implementedBy, implements +except ImportError: + from pyxmpp.interface_micro_impl import Interface, Attribute, providedBy, implementedBy, implements + + +__all__ = ("Interface", "Attribute", "providedBy", "implementedBy", "implements") + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/interface_micro_impl.py b/digsby/lib/pyxmpp/interface_micro_impl.py new file mode 100644 index 0000000..fa3bcc5 --- /dev/null +++ b/digsby/lib/pyxmpp/interface_micro_impl.py @@ -0,0 +1,130 @@ +# +# (C) Copyright 2006 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""Interface API, minimal implementation. + +This is minimal Zope Interfaces API implementation, as required by PyXMPP, not add another dependencies. + +If zope.interface package is available it will be used instead of this one. Never import this module directly.""" + +__revision__="$Id: utils.py 647 2006-08-26 18:27:39Z jajcus $" +__docformat__="restructuredtext en" + +import sys +from types import FunctionType + +def classImplements(cls, *interfaces): + if not isinstance(cls, classobj): + raise TypeError, "%r is not a class" + for interface in interfaces: + if not isinstance(interface, InterfaceClass): + raise TypeError, "Only interfaces may be implemented" + cls.__implemented__ = tuple(interfaces) + +def implements(*interfaces): + for interface in interfaces: + if not isinstance(interface, InterfaceClass): + raise TypeError, "Only interfaces may be implemented" + + frame = sys._getframe(1) + locals = frame.f_locals + + if (locals is frame.f_globals) or ('__module__' not in locals): + raise TypeError, "implements() may only be used in a class definition" + + if "__implemented__" in locals: + raise TypeError, "implements() may be used only once" + + locals["__implemented__"] = tuple(interfaces) + +def _whole_tree(cls): + yield cls + for base in cls.__bases__: + for b in _whole_tree(base): + yield b + +def implementedBy(cls): + try: + for interface in cls.__implemented__: + for c in _whole_tree(interface): + yield c + except AttributeError: + pass + for base in cls.__bases__: + for interface in implementedBy(base): + yield interface + +def providedBy(ob): + try: + for interface in ob.__provides__: + yield interface + except AttributeError: + try: + for interface in implementedBy(ob.__class__): + yield interface + except AttributeError: + return + +class InterfaceClass(object): + def __init__(self, name, bases = (), attrs = None, __doc__ = None, __module__ = None): + if __module__ is None: + if (attrs is not None and ('__module__' in attrs) and isinstance(attrs['__module__'], str)): + __module__ = attrs['__module__'] + del attrs['__module__'] + else: + __module__ = sys._getframe(1).f_globals['__name__'] + if __doc__ is not None: + self.__doc__ = __doc__ + if attrs is not None and "__doc__" in attrs: + del attrs["__doc__"] + self.__module__ = __module__ + for base in bases: + if not isinstance(base, InterfaceClass): + raise TypeError, 'Interface bases must be Interfaces' + if attrs is not None: + for aname, attr in attrs.items(): + if not isinstance(attr, Attribute) and type(attr) is not FunctionType: + raise TypeError, 'Interface attributes must be Attributes o functions (%r found in %s)' % (attr, aname) + self.__bases__ = bases + self.__attrs = attrs + self.__name__ = name + self.__identifier__ = "%s.%s" % (self.__module__, self.__name__) + + def providedBy(self, ob): + """Is the interface implemented by an object""" + if self in providedBy(ob): + return True + return False + + def implementedBy(self, cls): + """Do instances of the given class implement the interface?""" + return self in implementedBy(cls) + + def __repr__(self): + name = self.__name__ + module = self.__module__ + if module and module != "__main__": + name = "%s.%s" % (module, name) + return "<%s %s>" % (self.__class__.__name__, name) + +class Attribute(object): + def __init__(self, doc): + self.__doc__ = doc + +Interface = InterfaceClass("Interface", __module__ = "pyxmpp.inteface_micro_impl") + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/interfaces.py b/digsby/lib/pyxmpp/interfaces.py new file mode 100644 index 0000000..3386e42 --- /dev/null +++ b/digsby/lib/pyxmpp/interfaces.py @@ -0,0 +1,61 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""Interfaces for flexible API extensions.""" + +__revision__ = "$Id: error.py 647 2006-08-26 18:27:39Z jajcus $" +__docformat__ = "restructuredtext en" + +from pyxmpp.interface import Interface, Attribute + +class IPyXMPPHelper(Interface): + """Base for all interfaces used as PyXMPP helpers.""" + +class IPresenceHandlersProvider(IPyXMPPHelper): + def get_presence_handlers(): + """Returns iterable over (presence_type, handler[, namespace[, priority]]) tuples. + + The tuples will be used as arguments for `Stream.set_presence_handler`.""" + +class IMessageHandlersProvider(IPyXMPPHelper): + def get_message_handlers(): + """Returns iterable over (message_type, handler[, namespace[, priority]]) tuples. + + The tuples will be used as arguments for `Stream.set_message_handler`.""" + +class IIqHandlersProvider(IPyXMPPHelper): + def get_iq_get_handlers(): + """Returns iterable over (element_name, namespace) tuples. + + The tuples will be used as arguments for `Stream.set_iq_get_handler`.""" + def get_iq_set_handlers(): + """Returns iterable over (element_name, namespace) tuples. + + The tuples will be used as arguments for `Stream.set_iq_set_handler`.""" + +class IStanzaHandlersProvider(IPresenceHandlersProvider, IMessageHandlersProvider, IIqHandlersProvider): + pass + +class IFeaturesProvider(IPyXMPPHelper): + def get_features(): + """Return iterable of namespaces (features) supported, for disco#info + query response.""" + + +__all__ = [ name for name in dir() if name.startswith("I") and name != "Interface" ] + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/iq.py b/digsby/lib/pyxmpp/iq.py new file mode 100644 index 0000000..1053496 --- /dev/null +++ b/digsby/lib/pyxmpp/iq.py @@ -0,0 +1,165 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""Iq XMPP stanza handling + +Normative reference: + - `RFC 3920 `__ +""" + +__revision__="$Id: iq.py 714 2010-04-05 10:20:10Z jajcus $" +__docformat__="restructuredtext en" + +import libxml2 + +from pyxmpp.xmlextra import get_node_ns_uri +from pyxmpp.stanza import Stanza, gen_id + +class Iq(Stanza): + """Wraper object for stanzas.""" + stanza_type="iq" + def __init__(self, xmlnode = None, from_jid = None, to_jid = None, stanza_type = None, + stanza_id = None, error = None, error_cond=None, stream = None): + """Initialize an `Iq` object. + + :Parameters: + - `xmlnode`: XML node to_jid be wrapped into the `Iq` object + or other Iq object to be copied. If not given then new + presence stanza is created using following parameters. + - `from_jid`: sender JID. + - `to_jid`: recipient JID. + - `stanza_type`: staza type: one of: "get", "set", "result" or "error". + - `stanza_id`: stanza id -- value of stanza's "id" attribute. If not + given, then unique for the session value is generated. + - `error_cond`: error condition name. Ignored if `stanza_type` is not "error". + :Types: + - `xmlnode`: `unicode` or `libxml2.xmlNode` or `Iq` + - `from_jid`: `JID` + - `to_jid`: `JID` + - `stanza_type`: `unicode` + - `stanza_id`: `unicode` + - `error_cond`: `unicode`""" + self.xmlnode=None + if isinstance(xmlnode,Iq): + pass + elif isinstance(xmlnode,Stanza): + raise TypeError,"Couldn't make Iq from other Stanza" + elif isinstance(xmlnode,libxml2.xmlNode): + pass + elif xmlnode is not None: + raise TypeError,"Couldn't make Iq from %r" % (type(xmlnode),) + elif not stanza_type: + raise ValueError, "type is required for Iq" + else: + if not stanza_id and stanza_type in ("get", "set"): + stanza_id=gen_id() + + if not xmlnode and stanza_type not in ("get","set","result","error"): + raise ValueError, "Invalid Iq type: %r" % (stanza_type,) + + if xmlnode is None: + xmlnode="iq" + + Stanza.__init__(self, xmlnode, from_jid = from_jid, to_jid = to_jid, + stanza_type = stanza_type, stanza_id = stanza_id, error = error, + error_cond = error_cond, stream = stream) + + def copy(self): + """Create a deep copy of the iq stanza. + + :returntype: `Iq`""" + return Iq(self) + + def make_error_response(self,cond): + """Create error response for the a "get" or "set" iq stanza. + + :Parameters: + - `cond`: error condition name, as defined in XMPP specification. + + :return: new `Iq` object with the same "id" as self, "from" and "to" + attributes swapped, type="error" and containing element + plus payload of `self`. + :returntype: `Iq`""" + + if self.get_type() in ("result", "error"): + raise ValueError, "Errors may not be generated for 'result' and 'error' iq" + + iq=Iq(stanza_type="error",from_jid=self.get_to(),to_jid=self.get_from(), + stanza_id=self.get_id(),error_cond=cond) + n=self.get_query() + if n: + n=n.copyNode(1) + iq.xmlnode.children.addPrevSibling(n) + return iq + + def make_result_response(self): + """Create result response for the a "get" or "set" iq stanza. + + :return: new `Iq` object with the same "id" as self, "from" and "to" + attributes replaced and type="result". + :returntype: `Iq`""" + + if self.get_type() not in ("set","get"): + raise ValueError, "Results may only be generated for 'set' or 'get' iq" + + iq=Iq(stanza_type="result", from_jid=self.get_to(), + to_jid=self.get_from(), stanza_id=self.get_id()) + + return iq + + def new_query(self,ns_uri,name="query"): + """Create new payload element for the stanza. + + :Parameters: + - `ns_uri`: namespace URI of the element. + - `name`: element name. + :Types: + - `ns_uri`: `str` + - `name`: `unicode` + + :return: the new payload node. + :returntype: `libxml2.xmlNode`""" + return self.set_new_content(ns_uri,name) + + def get_query(self): + """Get the payload element of the stanza. + + :return: the payload element or None if there is no payload. + :returntype: `libxml2.xmlNode`""" + c = self.xmlnode.children + while c: + try: + if c.ns(): + return c + except libxml2.treeError: + pass + c = c.next + return None + + def get_query_ns(self): + """Get a namespace of the stanza payload. + + :return: XML namespace URI of the payload or None if there is no + payload. + :returntype: `str`""" + q=self.get_query() + if q: + return get_node_ns_uri(q) + else: + return None + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/jabber/__init__.py b/digsby/lib/pyxmpp/jabber/__init__.py new file mode 100644 index 0000000..5023a40 --- /dev/null +++ b/digsby/lib/pyxmpp/jabber/__init__.py @@ -0,0 +1,23 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +"""JSF defined XMPP extension and legacy Jabber protocol elements""" + +__revision__="$Id: __init__.py 714 2010-04-05 10:20:10Z jajcus $" +__docformat__="restructuredtext en" + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/jabber/all.py b/digsby/lib/pyxmpp/jabber/all.py new file mode 100644 index 0000000..e896357 --- /dev/null +++ b/digsby/lib/pyxmpp/jabber/all.py @@ -0,0 +1,44 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# pylint: disable-msg=W0611 + +"""Convenience module containing most important objects from pyxmpp.jabber +package. + +Suggested usage:: +import pyxmpp.jabber.all + +(imports all important names into pyxmpp.jabber namespace)""" + +__revision__="$Id: __init__.py 477 2004-12-29 13:25:42Z jajcus $" +__docformat__="restructuredtext en" + +import pyxmpp +import pyxmpp.jabber + +from pyxmpp.jabber.clientstream import LegacyClientStream +from pyxmpp.jabber.client import JabberClient as Client +from pyxmpp.jabber.disco import DISCO_NS,DISCO_INFO_NS,DISCO_ITEMS_NS +from pyxmpp.jabber.disco import DiscoInfo,DiscoItems,DiscoItem,DiscoIdentity +from pyxmpp.jabber.vcard import VCARD_NS,VCard +from pyxmpp.jabber.register import Register + +for name in dir(): + if not name.startswith("__") and name!="pyxmpp": + setattr(pyxmpp.jabber,name,globals()[name]) + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/jabber/client.py b/digsby/lib/pyxmpp/jabber/client.py new file mode 100644 index 0000000..9df8027 --- /dev/null +++ b/digsby/lib/pyxmpp/jabber/client.py @@ -0,0 +1,287 @@ +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +"""Basic Jabber client functionality implementation. + +Extends `pyxmpp.client` interface with legacy authentication +and basic Service Discovery handling. + +Normative reference: + - `JEP 78 `__ + - `JEP 30 `__ +""" + +__revision__="$Id: client.py 714 2010-04-05 10:20:10Z jajcus $" +__docformat__="restructuredtext en" + +import logging + +from pyxmpp.jabber.clientstream import LegacyClientStream +from pyxmpp.jabber.disco import DISCO_ITEMS_NS,DISCO_INFO_NS +from pyxmpp.jabber.disco import DiscoInfo,DiscoItems,DiscoIdentity +from pyxmpp.jabber import disco +from pyxmpp.client import Client +from pyxmpp.stanza import Stanza +from pyxmpp.cache import CacheSuite +from pyxmpp.utils import from_utf8 +from pyxmpp.interfaces import IFeaturesProvider + +class JabberClient(Client): + """Base class for a Jabber client. + + :Ivariables: + - `disco_items`: default Disco#items reply for a query to an empty node. + - `disco_info`: default Disco#info reply for a query to an empty node -- + provides information about the client and its supported fetures. + - `disco_identity`: default identity of the default `disco_info`. + - `register`: when `True` than registration will be started instead of authentication. + :Types: + - `disco_items`: `DiscoItems` + - `disco_info`: `DiscoInfo` + - `register`: `bool` + """ + def __init__(self,jid=None, password=None, server=None, port=5222, + auth_methods=("sasl:DIGEST-MD5","digest"), + tls_settings=None, keepalive=0, + disco_name=u"pyxmpp based Jabber client", disco_category=u"client", + disco_type=u"pc"): + """Initialize a JabberClient object. + + :Parameters: + - `jid`: user full JID for the connection. + - `password`: user password. + - `server`: server to use. If not given then address will be derived form the JID. + - `port`: port number to use. If not given then address will be derived form the JID. + - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms + in the list should be prefixed with "sasl:" string. + - `tls_settings`: settings for StartTLS -- `TLSSettings` instance. + - `keepalive`: keepalive output interval. 0 to disable. + - `disco_name`: name of the client identity in the disco#info + replies. + - `disco_category`: category of the client identity in the disco#info + replies. The default of u'client' should be the right choice in + most cases. + - `disco_type`: type of the client identity in the disco#info + replies. Use `the types registered by Jabber Registrar `__ + :Types: + - `jid`: `pyxmpp.JID` + - `password`: `unicode` + - `server`: `unicode` + - `port`: `int` + - `auth_methods`: sequence of `str` + - `tls_settings`: `pyxmpp.TLSSettings` + - `keepalive`: `int` + - `disco_name`: `unicode` + - `disco_category`: `unicode` + - `disco_type`: `unicode` + """ + + Client.__init__(self,jid,password,server,port,auth_methods,tls_settings,keepalive) + self.stream_class = LegacyClientStream + self.disco_items=DiscoItems() + self.disco_info=DiscoInfo() + self.disco_identity=DiscoIdentity(self.disco_info, + disco_name, disco_category, disco_type) + self.register_feature(u"dnssrv") + self.register_feature(u"stringprep") + self.register_feature(u"urn:ietf:params:xml:ns:xmpp-sasl#c2s") + self.cache = CacheSuite(max_items = 1000) + self.__logger = logging.getLogger("pyxmpp.jabber.JabberClient") + +# public methods + + def connect(self, register = False): + """Connect to the server and set up the stream. + + Set `self.stream` and notify `self.state_changed` when connection + succeeds. Additionally, initialize Disco items and info of the client. + """ + Client.connect(self, register) + if register: + self.stream.registration_callback = self.process_registration_form + + def register_feature(self, feature_name): + """Register a feature to be announced by Service Discovery. + + :Parameters: + - `feature_name`: feature namespace or name. + :Types: + - `feature_name`: `unicode`""" + self.disco_info.add_feature(feature_name) + + def unregister_feature(self, feature_name): + """Unregister a feature to be announced by Service Discovery. + + :Parameters: + - `feature_name`: feature namespace or name. + :Types: + - `feature_name`: `unicode`""" + self.disco_info.remove_feature(feature_name) + + def submit_registration_form(self, form): + """Submit a registration form + + :Parameters: + - `form`: the form to submit + :Types: + - `form`: `pyxmpp.jabber.dataforms.Form`""" + self.stream.submit_registration_form(form) + +# private methods + def __disco_info(self,iq): + """Handle a disco#info request. + + `self.disco_get_info` method will be used to prepare the query response. + + :Parameters: + - `iq`: the IQ stanza received. + :Types: + - `iq`: `pyxmpp.iq.Iq`""" + q=iq.get_query() + if q.hasProp("node"): + node=from_utf8(q.prop("node")) + else: + node=None + info=self.disco_get_info(node,iq) + if isinstance(info,DiscoInfo): + resp=iq.make_result_response() + self.__logger.debug("Disco-info query: %s preparing response: %s with reply: %s" + % (iq.serialize(),resp.serialize(),info.xmlnode.serialize())) + resp.set_content(info.xmlnode.copyNode(1)) + elif isinstance(info,Stanza): + resp=info + else: + resp=iq.make_error_response("item-not-found") + self.__logger.debug("Disco-info response: %s" % (resp.serialize(),)) + self.stream.send(resp) + + def __disco_items(self,iq): + """Handle a disco#items request. + + `self.disco_get_items` method will be used to prepare the query response. + + :Parameters: + - `iq`: the IQ stanza received. + :Types: + - `iq`: `pyxmpp.iq.Iq`""" + q=iq.get_query() + if q.hasProp("node"): + node=from_utf8(q.prop("node")) + else: + node=None + items=self.disco_get_items(node,iq) + if isinstance(items,DiscoItems): + resp=iq.make_result_response() + self.__logger.debug("Disco-items query: %s preparing response: %s with reply: %s" + % (iq.serialize(),resp.serialize(),items.xmlnode.serialize())) + resp.set_content(items.xmlnode.copyNode(1)) + elif isinstance(items,Stanza): + resp=items + else: + resp=iq.make_error_response("item-not-found") + self.__logger.debug("Disco-items response: %s" % (resp.serialize(),)) + self.stream.send(resp) + + def _session_started(self): + """Called when session is started. + + Activates objects from `self.interface_provides` by installing + their disco features.""" + Client._session_started(self) + for ob in self.interface_providers: + if IFeaturesProvider.providedBy(ob): + for ns in ob.get_features(): + self.register_feature(ns) + +# methods to override + + def authorized(self): + """Handle "authorized" event. May be overriden in derived classes. + By default: request an IM session and setup Disco handlers.""" + Client.authorized(self) + self.stream.set_iq_get_handler("query",DISCO_ITEMS_NS,self.__disco_items) + self.stream.set_iq_get_handler("query",DISCO_INFO_NS,self.__disco_info) + disco.register_disco_cache_fetchers(self.cache,self.stream) + + def disco_get_info(self,node,iq): + """Return Disco#info data for a node. + + :Parameters: + - `node`: the node queried. + - `iq`: the request stanza received. + :Types: + - `node`: `unicode` + - `iq`: `pyxmpp.iq.Iq` + + :return: self.disco_info if `node` is empty or `None` otherwise. + :returntype: `DiscoInfo`""" + to=iq.get_to() + if to and to!=self.jid: + return iq.make_error_response("recipient-unavailable") + if not node and self.disco_info: + return self.disco_info + return None + + def disco_get_items(self,node,iq): + """Return Disco#items data for a node. + + :Parameters: + - `node`: the node queried. + - `iq`: the request stanza received. + :Types: + - `node`: `unicode` + - `iq`: `pyxmpp.iq.Iq` + + :return: self.disco_info if `node` is empty or `None` otherwise. + :returntype: `DiscoInfo`""" + to=iq.get_to() + if to and to!=self.jid: + return iq.make_error_response("recipient-unavailable") + if not node and self.disco_items: + return self.disco_items + return None + + def process_registration_form(self, stanza, form): + """Fill-in the registration form provided by the server. + + This default implementation fills-in "username" and "passwords" + fields only and instantly submits the form. + + :Parameters: + - `stanza`: the stanza received. + - `form`: the registration form. + :Types: + - `stanza`: `pyxmpp.iq.Iq` + - `form`: `pyxmpp.jabber.dataforms.Form` + """ + _unused = stanza + self.__logger.debug(u"default registration callback started. auto-filling-in the form...") + if not 'FORM_TYPE' in form or 'jabber:iq:register' not in form['FORM_TYPE'].values: + raise RuntimeError, "Unknown form type: %r %r" % (form, form['FORM_TYPE']) + for field in form: + if field.name == u"username": + self.__logger.debug(u"Setting username to %r" % (self.jid.node,)) + field.value = self.jid.node + elif field.name == u"password": + self.__logger.debug_s(u"Setting password to %r.decode('rot13')" % (self.password.encode('rot13'),)) + field.value = self.password + elif field.required: + self.__logger.debug(u"Unknown required field: %r" % (field.name,)) + raise RuntimeError, "Unsupported required registration form field %r" % (field.name,) + else: + self.__logger.debug(u"Unknown field: %r" % (field.name,)) + self.submit_registration_form(form) + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/jabber/clientstream.py b/digsby/lib/pyxmpp/jabber/clientstream.py new file mode 100644 index 0000000..339e71a --- /dev/null +++ b/digsby/lib/pyxmpp/jabber/clientstream.py @@ -0,0 +1,469 @@ +# +# (C) Copyright 2003-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +"""XMPP stream support with fallback to legacy non-SASL Jabber authentication. + +Normative reference: + - `JEP 78 `__ +""" + +__revision__="$Id: clientstream.py 703 2010-04-03 17:45:43Z jajcus $" +__docformat__="restructuredtext en" + +import hashlib +import logging + +from pyxmpp.iq import Iq +from pyxmpp.utils import to_utf8,from_utf8 +from pyxmpp.jid import JID +from pyxmpp.clientstream import ClientStream +from pyxmpp.jabber.register import Register + +from pyxmpp.exceptions import ClientStreamError, LegacyAuthenticationError, RegistrationError + +class LegacyClientStream(ClientStream): + """Handles Jabber (both XMPP and legacy protocol) client connection stream. + + Both client and server side of the connection is supported. This class handles + client SASL and legacy authentication, authorisation and XMPP resource binding. + """ + def __init__(self, jid, password = None, server = None, port = 5222, + auth_methods = ("sasl:DIGEST-MD5", "digest"), + tls_settings = None, keepalive = 0, owner = None): + """Initialize a LegacyClientStream object. + + :Parameters: + - `jid`: local JID. + - `password`: user's password. + - `server`: server to use. If not given then address will be derived form the JID. + - `port`: port number to use. If not given then address will be derived form the JID. + - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms + in the list should be prefixed with "sasl:" string. + - `tls_settings`: settings for StartTLS -- `TLSSettings` instance. + - `keepalive`: keepalive output interval. 0 to disable. + - `owner`: `Client`, `Component` or similar object "owning" this stream. + :Types: + - `jid`: `pyxmpp.JID` + - `password`: `unicode` + - `server`: `unicode` + - `port`: `int` + - `auth_methods`: sequence of `str` + - `tls_settings`: `pyxmpp.TLSSettings` + - `keepalive`: `int` + """ + (self.authenticated, self.available_auth_methods, self.auth_stanza, + self.peer_authenticated, self.auth_method_used, + self.registration_callback, self.registration_form, self.__register) = (None,) * 8 + ClientStream.__init__(self, jid, password, server, port, + auth_methods, tls_settings, keepalive, owner) + self.__logger=logging.getLogger("pyxmpp.jabber.LegacyClientStream") + + def _reset(self): + """Reset the `LegacyClientStream` object state, making the object ready + to handle new connections.""" + ClientStream._reset(self) + self.available_auth_methods = None + self.auth_stanza = None + self.registration_callback = None + + def _post_connect(self): + """Initialize authentication when the connection is established + and we are the initiator.""" + if not self.initiator: + if "plain" in self.auth_methods or "digest" in self.auth_methods: + self.set_iq_get_handler("query","jabber:iq:auth", + self.auth_in_stage1) + self.set_iq_set_handler("query","jabber:iq:auth", + self.auth_in_stage2) + elif self.registration_callback: + iq = Iq(stanza_type = "get") + iq.set_content(Register()) + self.set_response_handlers(iq, self.registration_form_received, self.registration_error) + self.send(iq) + return + ClientStream._post_connect(self) + + def _post_auth(self): + """Unregister legacy authentication handlers after successfull + authentication.""" + ClientStream._post_auth(self) + if not self.initiator: + self.unset_iq_get_handler("query","jabber:iq:auth") + self.unset_iq_set_handler("query","jabber:iq:auth") + + def _try_auth(self): + """Try to authenticate using the first one of allowed authentication + methods left. + + [client only]""" + if self.authenticated: + self.__logger.debug("try_auth: already authenticated") + return + self.__logger.debug("trying auth: %r" % (self._auth_methods_left,)) + if not self._auth_methods_left: + raise LegacyAuthenticationError,"No allowed authentication methods available" + method=self._auth_methods_left[0] + if method.startswith("sasl:"): + return ClientStream._try_auth(self) + elif method not in ("plain","digest"): + self._auth_methods_left.pop(0) + self.__logger.debug("Skipping unknown auth method: %s" % method) + return self._try_auth() + elif self.available_auth_methods is not None: + self._auth_methods_left.pop(0) + if method in self.available_auth_methods: + self.auth_method_used=method + if method=="digest": + self._digest_auth_stage2(self.auth_stanza) + else: + self._plain_auth_stage2(self.auth_stanza) + self.auth_stanza=None + return + else: + self.__logger.debug("Skipping unavailable auth method: %s" % method) + return self._try_auth() + else: + self._auth_stage1() + + def auth_in_stage1(self,stanza): + """Handle the first stage () of legacy ("plain" or + "digest") authentication. + + [server only]""" + self.lock.acquire() + try: + if "plain" not in self.auth_methods and "digest" not in self.auth_methods: + iq=stanza.make_error_response("not-allowed") + self.send(iq) + return + + iq=stanza.make_result_response() + q=iq.new_query("jabber:iq:auth") + q.newChild(None,"username",None) + q.newChild(None,"resource",None) + if "plain" in self.auth_methods: + q.newChild(None,"password",None) + if "digest" in self.auth_methods: + q.newChild(None,"digest",None) + self.send(iq) + iq.free() + finally: + self.lock.release() + + def auth_in_stage2(self,stanza): + """Handle the second stage () of legacy ("plain" or + "digest") authentication. + + [server only]""" + self.lock.acquire() + try: + if "plain" not in self.auth_methods and "digest" not in self.auth_methods: + iq=stanza.make_error_response("not-allowed") + self.send(iq) + return + + username=stanza.xpath_eval("a:query/a:username",{"a":"jabber:iq:auth"}) + if username: + username=from_utf8(username[0].getContent()) + resource=stanza.xpath_eval("a:query/a:resource",{"a":"jabber:iq:auth"}) + if resource: + resource=from_utf8(resource[0].getContent()) + if not username or not resource: + self.__logger.debug("No username or resource found in auth request") + iq=stanza.make_error_response("bad-request") + self.send(iq) + return + + if stanza.xpath_eval("a:query/a:password",{"a":"jabber:iq:auth"}): + if "plain" not in self.auth_methods: + iq=stanza.make_error_response("not-allowed") + self.send(iq) + return + else: + return self._plain_auth_in_stage2(username,resource,stanza) + if stanza.xpath_eval("a:query/a:digest",{"a":"jabber:iq:auth"}): + if "plain" not in self.auth_methods: + iq=stanza.make_error_response("not-allowed") + self.send(iq) + return + else: + return self._digest_auth_in_stage2(username,resource,stanza) + finally: + self.lock.release() + + def _auth_stage1(self): + """Do the first stage () of legacy ("plain" or + "digest") authentication. + + [client only]""" + iq=Iq(stanza_type="get") + q=iq.new_query("jabber:iq:auth") + q.newTextChild(None,"username",to_utf8(self.my_jid.node)) + q.newTextChild(None,"resource",to_utf8(self.my_jid.resource)) + self.send(iq) + self.set_response_handlers(iq,self.auth_stage2,self.auth_error, + self.auth_timeout,timeout=60) + iq.free() + + def auth_timeout(self): + """Handle legacy authentication timeout. + + [client only]""" + self.lock.acquire() + try: + self.__logger.debug("Timeout while waiting for jabber:iq:auth result") + if self._auth_methods_left: + self._auth_methods_left.pop(0) + finally: + self.lock.release() + + def auth_error(self,stanza): + """Handle legacy authentication error. + + [client only]""" + self.lock.acquire() + try: + err=stanza.get_error() + ae=err.xpath_eval("e:*",{"e":"jabber:iq:auth:error"}) + if ae: + ae=ae[0].name + else: + ae=err.get_condition().name + raise LegacyAuthenticationError,("Authentication error condition: %s" + % (ae,)) + finally: + self.lock.release() + + def auth_stage2(self,stanza): + """Handle the first stage authentication response (result of the ). + + [client only]""" + self.lock.acquire() + try: + self.__logger.debug("Procesing auth response...") + self.available_auth_methods=[] + if (stanza.xpath_eval("a:query/a:digest",{"a":"jabber:iq:auth"}) and self.stream_id): + self.available_auth_methods.append("digest") + if (stanza.xpath_eval("a:query/a:password",{"a":"jabber:iq:auth"})): + self.available_auth_methods.append("plain") + self.auth_stanza=stanza.copy() + self._try_auth() + finally: + self.lock.release() + + def _plain_auth_stage2(self, _unused): + """Do the second stage () of legacy "plain" + authentication. + + [client only]""" + iq=Iq(stanza_type="set") + q=iq.new_query("jabber:iq:auth") + q.newTextChild(None,"username",to_utf8(self.my_jid.node)) + q.newTextChild(None,"resource",to_utf8(self.my_jid.resource)) + q.newTextChild(None,"password",to_utf8(self.password)) + self.send(iq) + self.set_response_handlers(iq,self.auth_finish,self.auth_error) + iq.free() + + def _plain_auth_in_stage2(self, username, _unused, stanza): + """Handle the second stage () of legacy "plain" + authentication. + + [server only]""" + password=stanza.xpath_eval("a:query/a:password",{"a":"jabber:iq:auth"}) + if password: + password=from_utf8(password[0].getContent()) + if not password: + self.__logger.debug("No password found in plain auth request") + iq=stanza.make_error_response("bad-request") + self.send(iq) + return + + if self.check_password(username,password): + iq=stanza.make_result_response() + self.send(iq) + self.peer_authenticated=True + self.auth_method_used="plain" + self.state_change("authorized",self.peer) + self._post_auth() + else: + self.__logger.debug("Plain auth failed") + iq=stanza.make_error_response("bad-request") + e=iq.get_error() + e.add_custom_condition('jabber:iq:auth:error',"user-unauthorized") + self.send(iq) + + def _digest_auth_stage2(self, _unused): + """Do the second stage () of legacy "digest" + authentication. + + [client only]""" + iq=Iq(stanza_type="set") + q=iq.new_query("jabber:iq:auth") + q.newTextChild(None,"username",to_utf8(self.my_jid.node)) + q.newTextChild(None,"resource",to_utf8(self.my_jid.resource)) + + digest = hashlib.sha1(to_utf8(self.stream_id)+to_utf8(self.password)).hexdigest() + + q.newTextChild(None,"digest",digest) + self.send(iq) + self.set_response_handlers(iq,self.auth_finish,self.auth_error) + iq.free() + + def _digest_auth_in_stage2(self, username, _unused, stanza): + """Handle the second stage () of legacy "digest" + authentication. + + [server only]""" + digest=stanza.xpath_eval("a:query/a:digest",{"a":"jabber:iq:auth"}) + if digest: + digest=digest[0].getContent() + if not digest: + self.__logger.debug("No digest found in digest auth request") + iq=stanza.make_error_response("bad-request") + self.send(iq) + return + + password,pwformat=self.get_password(username) + if not password or pwformat!="plain": + iq=stanza.make_error_response("bad-request") + e=iq.get_error() + e.add_custom_condition('jabber:iq:auth:error',"user-unauthorized") + self.send(iq) + return + + mydigest = hashlib.sha1(to_utf8(self.stream_id)+to_utf8(password)).hexdigest() + + if mydigest==digest: + iq=stanza.make_result_response() + self.send(iq) + self.peer_authenticated=True + self.auth_method_used="digest" + self.state_change("authorized",self.peer) + self._post_auth() + else: + self.__logger.debug("Digest auth failed: %r != %r" % (digest,mydigest)) + iq=stanza.make_error_response("bad-request") + e=iq.get_error() + e.add_custom_condition('jabber:iq:auth:error',"user-unauthorized") + self.send(iq) + + def auth_finish(self, _unused): + """Handle success of the legacy authentication.""" + self.lock.acquire() + try: + self.__logger.debug("Authenticated") + self.authenticated=True + self.state_change("authorized",self.my_jid) + self._post_auth() + finally: + self.lock.release() + + def registration_error(self, stanza): + """Handle in-band registration error. + + [client only] + + :Parameters: + - `stanza`: the error stanza received or `None` on timeout. + :Types: + - `stanza`: `pyxmpp.stanza.Stanza`""" + self.lock.acquire() + try: + err=stanza.get_error() + ae=err.xpath_eval("e:*",{"e":"jabber:iq:auth:error"}) + if ae: + ae=ae[0].name + else: + ae=err.get_condition().name + raise RegistrationError,("Authentication error condition: %s" % (ae,)) + finally: + self.lock.release() + + def registration_form_received(self, stanza): + """Handle registration form received. + + [client only] + + Call self.registration_callback with the registration form received + as the argument. Use the value returned by the callback will be a + filled-in form. + + :Parameters: + - `stanza`: the stanza received. + :Types: + - `stanza`: `pyxmpp.iq.Iq`""" + self.lock.acquire() + try: + self.__register = Register(stanza.get_query()) + self.registration_callback(stanza, self.__register.get_form()) + finally: + self.lock.release() + + def submit_registration_form(self, form): + """Submit a registration form. + + [client only] + + :Parameters: + - `form`: the filled-in form. When form is `None` or its type is + "cancel" the registration is to be canceled. + + :Types: + - `form`: `pyxmpp.jabber.dataforms.Form`""" + self.lock.acquire() + try: + if form and form.type!="cancel": + self.registration_form = form + iq = Iq(stanza_type = "set") + iq.set_content(self.__register.submit_form(form)) + self.set_response_handlers(iq, self.registration_success, self.registration_error) + self.send(iq) + else: + self.__register = None + finally: + self.lock.release() + + def registration_success(self, stanza): + """Handle registration success. + + [client only] + + Clean up registration stuff, change state to "registered" and initialize + authentication. + + :Parameters: + - `stanza`: the stanza received. + :Types: + - `stanza`: `pyxmpp.iq.Iq`""" + _unused = stanza + self.lock.acquire() + try: + self.state_change("registered", self.registration_form) + if ('FORM_TYPE' in self.registration_form + and self.registration_form['FORM_TYPE'].value == 'jabber:iq:register'): + if 'username' in self.registration_form: + self.my_jid = JID(self.registration_form['username'].value, + self.my_jid.domain, self.my_jid.resource) + if 'password' in self.registration_form: + self.password = self.registration_form['password'].value + self.registration_callback = None + self._post_connect() + finally: + self.lock.release() + +# vi: sts=4 et sw=4 diff --git a/digsby/lib/pyxmpp/jabber/dataforms.py b/digsby/lib/pyxmpp/jabber/dataforms.py new file mode 100644 index 0000000..9e3b5b7 --- /dev/null +++ b/digsby/lib/pyxmpp/jabber/dataforms.py @@ -0,0 +1,715 @@ +# +# (C) Copyright 2005-2010 Jacek Konieczny +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License Version +# 2.1 as published by the Free Software Foundation. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +"""Jabber Data Forms support. + +Normative reference: + - `JEP 4 `__ +""" + +__revision__="$Id: disco.py 513 2005-01-09 16:34:00Z jajcus $" +__docformat__="restructuredtext en" + +import copy +import libxml2 +import warnings +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.utils import from_utf8, to_utf8 +from pyxmpp.xmlextra import xml_element_ns_iter +from pyxmpp.jid import JID +from pyxmpp.exceptions import BadRequestProtocolError + +DATAFORM_NS = "jabber:x:data" + +class Option(StanzaPayloadObject): + """One of optional data form field values. + + :Ivariables: + - `label`: option label. + - `value`: option value. + :Types: + - `label`: `unicode` + - `value`: `unicode` + """ + xml_element_name = "option" + xml_element_namespace = DATAFORM_NS + + def __init__(self, value = None, label = None, values = None): + """Initialize an `Option` object. + + :Parameters: + - `value`: option value (mandatory). + - `label`: option label (human-readable description). + - `values`: for backward compatibility only. + :Types: + - `label`: `unicode` + - `value`: `unicode` + """ + self.label = label + + if value: + self.value = value + elif values: + warnings.warn("Option constructor accepts only single value now.", DeprecationWarning, stacklevel=1) + self.value = values[0] + else: + raise TypeError, "value argument to pyxmpp.dataforms.Option is required" + + + @property + def values(self): + """Return list of option values (always single element). Obsolete. For + backwards compatibility only.""" + return [self.value] + + def complete_xml_element(self, xmlnode, doc): + """Complete the XML node with `self` content. + + :Parameters: + - `xmlnode`: XML node with the element being built. It has already + right name and namespace, but no attributes or content. + - `doc`: document to which the element belongs. + :Types: + - `xmlnode`: `libxml2.xmlNode` + - `doc`: `libxml2.xmlDoc`""" + _unused = doc + if self.label is not None: + xmlnode.setProp("label", self.label.encode("utf-8")) + xmlnode.newTextChild(xmlnode.ns(), "value", self.value.encode("utf-8")) + return xmlnode + + @classmethod + def _new_from_xml(cls, xmlnode): + """Create a new `Option` object from an XML element. + + :Parameters: + - `xmlnode`: the XML element. + :Types: + - `xmlnode`: `libxml2.xmlNode` + + :return: the object created. + :returntype: `Option` + """ + label = from_utf8(xmlnode.prop("label")) + child = xmlnode.children + value = None + for child in xml_element_ns_iter(xmlnode.children, DATAFORM_NS): + if child.name == "value": + value = from_utf8(child.getContent()) + break + if value is None: + raise BadRequestProtocolError, "No value in

$Me You don't seem to like any fantasy movies, ever see Dragon Heart?
+
Friend Naw, but Braveheart is kind of fantasy
+
$Me Not really, lol
+
Friend I think so
+
Friend He fights dragons, Dragons are a fantasy icon man! Lol
+
Friend Braveheart ftw!
+
$Me There's no dragons in Braveheart...
+
Friend Wait, did I get them confused?
+
Friend What's the one where he fights dragons?
+
$Me Dragon Hart
+
Friend Mel Gibson?
+
$Me I don't think Mel Gibson ever fought any dragons :P
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..7a84534 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + Candybars Adium Message Style + CFBundleIdentifier + com.adiumx.candybars.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + Candybars + CFBundlePackageType + AdIM + DefaultFontFamily + Lucida Grande + DefaultFontSize + 11 + DisplayNameForNoVariant + Blue + MessageViewVersion + 1 + ShowsUserIcons + + + diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..9943c62 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Footer.html @@ -0,0 +1,3 @@ + + + diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..178f73d --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,8 @@ +
+
%time%
+ %sender% +
+
+ %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..e12a437 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,8 @@ +
+
%time%
+ %sender% +
+
+ %message% +
+
diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..b00f807 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,6 @@ + +
+
%time%
+ %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..353281d --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,6 @@ + +
+
%time%
+ %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..353e6c6 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,8 @@ +
+
%time%
+ %sender% +
+
+ %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..3dd0347 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,8 @@ +
+
%time%
+ %sender% +
+
+ %message% +
+
diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..12bbb10 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,6 @@ + +
+
%time%
+ %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..12bbb10 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,6 @@ + +
+
%time%
+ %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..8ef33f6 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,5 @@ +
+
%time%
+ %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Green.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Green.css new file mode 100644 index 0000000..879cc6c --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Green.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #86C500; } + .outgoing_row { background-color: #0096FF; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #768B48; + background-color: #C3DD8C; + } + + .outgoing_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #86C500; + } + .message_outgoing a, .message_outgoing_context a + { + color: #0096FF; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5B800B; + background-color: #E3F0C6; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #7BA71B; } \ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Grey.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Grey.css new file mode 100644 index 0000000..ac1cd97 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Grey.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #6C6C6C; } + .outgoing_row { background-color: #0096FF; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + .outgoing_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #6C6C6C; + } + .message_outgoing a, .message_outgoing_context a + { + color: #0096FF; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Pink.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Pink.css new file mode 100644 index 0000000..23a0461 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Pink.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FF3DB8; } + .outgoing_row { background-color: #0096FF; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #F122A5; + background-color: #FFB2E3; + } + + .outgoing_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #FF3DB8; + } + .message_outgoing a, .message_outgoing_context a + { + color: #0096FF; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } \ No newline at end of file diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Red.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Red.css new file mode 100644 index 0000000..6d72f84 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Red.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FF3F1F; } + .outgoing_row { background-color: #0096FF; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #B52121; + background-color: #FF7676; + } + + .outgoing_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #E51600; + } + .message_outgoing a, .message_outgoing_context a + { + color: #0096FF; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Yellow.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Yellow.css new file mode 100644 index 0000000..05d9240 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Yellow.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FFBA00; } + .outgoing_row { background-color: #0096FF; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + .outgoing_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #FFBA00; + } + .message_outgoing a, .message_outgoing_context a + { + color: #0096FF; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Graphite vs Grey.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Graphite vs Grey.css new file mode 100644 index 0000000..11c2d61 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Graphite vs Grey.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #6C6C6C; } + .outgoing_row { background-color: #808EA1; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + .outgoing_row_context + { color: #6D7887; + background-color: #B3C1CC; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #6C6C6C; + } + .message_outgoing a, .message_outgoing_context a + { + color: #808EA1; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5D6F85; + background-color: #D6E1EF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #919191; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Graphite.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Graphite.css new file mode 100644 index 0000000..4cc10d3 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Graphite.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #808EA1; } + .outgoing_row { background-color: #808EA1; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #6D7887; + background-color: #B3C1CC; + } + + .outgoing_row_context + { color: #6D7887; + background-color: #B3C1CC; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #808EA1; + } + .message_outgoing a, .message_outgoing_context a + { + color: #808EA1; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5D6F85; + background-color: #D6E1EF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #8A9BB0; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green vs Blue.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green vs Blue.css new file mode 100644 index 0000000..6d61261 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green vs Blue.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #0096FF; } + .outgoing_row { background-color: #86C500; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + .outgoing_row_context + { color: #768B48; + background-color: #C3DD8C; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #0096FF; + } + .message_outgoing a, .message_outgoing_context a + { + color: #86C500; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5B800B; + background-color: #E3F0C6; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #7BA71B; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green vs Yellow.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green vs Yellow.css new file mode 100644 index 0000000..aaaf366 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green vs Yellow.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FFBA00; } + .outgoing_row { background-color: #86C500; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + .outgoing_row_context + { color: #768B48; + background-color: #C3DD8C; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #FFBA00; + } + .message_outgoing a, .message_outgoing_context a + { + color: #86C500; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5B800B; + background-color: #E3F0C6; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #7BA71B; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green.css new file mode 100644 index 0000000..e400ac6 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Green.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #86C500; } + .outgoing_row { background-color: #86C500; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #768B48; + background-color: #C3DD8C; + } + + .outgoing_row_context + { color: #768B48; + background-color: #C3DD8C; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #86C500; + } + .message_outgoing a, .message_outgoing_context a + { + color: #86C500; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5B800B; + background-color: #E3F0C6; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #7BA71B; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Blue.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Blue.css new file mode 100644 index 0000000..f5f084f --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Blue.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #0096FF; } + .outgoing_row { background-color: #6C6C6C; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + .outgoing_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #0096FF; + } + .message_outgoing a, .message_outgoing_context a + { + color: #6C6C6C; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Graphite.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Graphite.css new file mode 100644 index 0000000..e85abb3 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Graphite.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #808EA1; } + .outgoing_row { background-color: #6C6C6C; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #6D7887; + background-color: #B3C1CC; + } + + .outgoing_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #808EA1; + } + .message_outgoing a, .message_outgoing_context a + { + color: #6C6C6C; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5D6F85; + background-color: #D6E1EF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #919191; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Pink.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Pink.css new file mode 100644 index 0000000..dce7083 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Pink.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FF3DB8; } + .outgoing_row { background-color: #6C6C6C; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #F122A5; + background-color: #FFB2E3; + } + + .outgoing_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #FF3DB8; + } + .message_outgoing a, .message_outgoing_context a + { + color: #6C6C6C; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5A5A5A; + background-color: #E4E4E4; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #919191; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey.css new file mode 100644 index 0000000..cb242f8 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Grey.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #6C6C6C; } + .outgoing_row { background-color: #6C6C6C; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + .outgoing_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #6C6C6C; + } + .message_outgoing a, .message_outgoing_context a + { + color: #6C6C6C; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5A5A5A; + background-color: #E4E4E4; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #919191; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink vs Blue.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink vs Blue.css new file mode 100644 index 0000000..ff61aac --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink vs Blue.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #0096FF; } + .outgoing_row { background-color: #FF3DB8; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + .outgoing_row_context + { color: #F122A5; + background-color: #FFB2E3; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #0096FF; + } + .message_outgoing a, .message_outgoing_context a + { + color: #FF3DB8; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink vs Grey.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink vs Grey.css new file mode 100644 index 0000000..135b535 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink vs Grey.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #6C6C6C; } + .outgoing_row { background-color: #FF3DB8; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #5A5A5A; + background-color: #B9B9B9; + } + + .outgoing_row_context + { color: #F122A5; + background-color: #FFB2E3; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #6C6C6C; + } + .message_outgoing a, .message_outgoing_context a + { + color: #FF3DB8; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5A5A5A; + background-color: #E4E4E4; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #919191; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink.css new file mode 100644 index 0000000..d84b3ed --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Pink.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FF3DB8; } + .outgoing_row { background-color: #FF3DB8; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #F122A5; + background-color: #FFB2E3; + } + + .outgoing_row_context + { color: #F122A5; + background-color: #FFB2E3; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #FF3DB8; + } + .message_outgoing a, .message_outgoing_context a + { + color: #FF3DB8; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red vs Blue.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red vs Blue.css new file mode 100644 index 0000000..230907e --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red vs Blue.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #0096FF; } + .outgoing_row { background-color: #FF3F1F; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + .outgoing_row_context + { color: #B52121; + background-color: #FF7676; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #0096FF; + } + .message_outgoing a, .message_outgoing_context a + { + color: #E51600; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red vs Yellow.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red vs Yellow.css new file mode 100644 index 0000000..c7cea1b --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red vs Yellow.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FFBA00; } + .outgoing_row { background-color: #FF3F1F; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + .outgoing_row_context + { color: #B52121; + background-color: #FF7676; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #FFBA00; + } + .message_outgoing a, .message_outgoing_context a + { + color: #E51600; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red.css new file mode 100644 index 0000000..3a13279 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Red.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FF3F1F; } + .outgoing_row { background-color: #FF3F1F; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #B52121; + background-color: #FF7676; + } + + .outgoing_row_context + { color: #B52121; + background-color: #FF7676; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #E51600; + } + .message_outgoing a, .message_outgoing_context a + { + color: #E51600; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Blue.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Blue.css new file mode 100644 index 0000000..dfd4f4d --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Blue.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #0096FF; } + .outgoing_row { background-color: #FFBA00; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + .outgoing_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #0096FF; + } + .message_outgoing a, .message_outgoing_context a + { + color: #FFBA00; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Green.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Green.css new file mode 100644 index 0000000..b74c57c --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Green.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #86C500; } + .outgoing_row { background-color: #FFBA00; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #768B48; + background-color: #C3DD8C; + } + + .outgoing_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #86C500; + } + .message_outgoing a, .message_outgoing_context a + { + color: #FFBA00; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #5B800B; + background-color: #E3F0C6; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #7BA71B; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Red.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Red.css new file mode 100644 index 0000000..8fa35e4 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow vs Red.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FF3F1F; } + .outgoing_row { background-color: #FFBA00; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #B52121; + background-color: #FF7676; + } + + .outgoing_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #E51600; + } + .message_outgoing a, .message_outgoing_context a + { + color: #FFBA00; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow.css new file mode 100644 index 0000000..5d74cec --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/Variants/Yellow.css @@ -0,0 +1,56 @@ + + @import url("../base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #FFBA00; } + .outgoing_row { background-color: #FFBA00; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + .outgoing_row_context + { color: #CDAB50; + background-color: #FFE28C; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #FFBA00; + } + .message_outgoing a, .message_outgoing_context a + { + color: #FFBA00; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/base.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/base.css new file mode 100644 index 0000000..6af19e9 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/base.css @@ -0,0 +1,149 @@ + body + { + color: rgb(0, 0, 0); + background: #fff; + + margin: 0; + + } + + #Chat + { + margin: 0; + padding: 0; + } + + p + { + margin: 0; + } + + img + { + vertical-align: middle; + } + + .incoming_row, .outgoing_row, .incoming_row_context, .outgoing_row_context, .status_row + { + font-family: Lucida Grande; + font-size: 11px; + font-weight: bold; + } + + .incoming_row, .outgoing_row, .incoming_row_context, .outgoing_row_context + { + padding: 3px 10px; + + overflow: hidden; + + background-image: url("images/row_background_noshadow.png"); + background-position: repeat-x top; + } + + .status_row + { + text-align: center; + font-weight: normal; + + margin-bottom: 2px; + + padding: 3px 10px; + + overflow: hidden; + } + + .message_incoming, .message_outgoing, .message_incoming_context, .message_outgoing_context + { + background: url("images/row_shadow.png") repeat-x top left; + + padding: 4px 10px 10px 10px; + + overflow: auto; + } + + .message_incoming_context, .message_outgoing_context + { + color: rgb(0, 0, 0); + } + + .next_message + { + padding: 6px 0 0 0; + min-height: 1.1em; + } + + .timestamp + { + float: right; + font-weight: normal; + } + + .next_message .timestamp + { + padding: 0 0 0 6px; + color: rgb(128, 128, 128); + font-size: 60%; + } + + .header + { + background: url("images/row_header.png") repeat-x; + + position: fixed; + top: 0; + left: 0; + right: 0; + + z-index:2; + + height: 25px; + } + + .header_icon + { + position: absolute; + top: 3px; + left: 7px; + + width: 18px; + height: 18px; + + float: left; + + background: #eee; + } + + .header_text + { + padding: 4px 10px 0 30px; + + font-family: Lucida Grande; + font-size: 11px; + line-height:16px; + } + + .iconwindow + { + position:fixed; + top: 22px; + left:6px; + + z-index: 2; + background: #fff; + + padding: 4px; + } + + + + + + + + + + + + + + diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/images/row_background_noshadow.png b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/images/row_background_noshadow.png new file mode 100644 index 0000000..eca80cf Binary files /dev/null and b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/images/row_background_noshadow.png differ diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/images/row_shadow.png b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/images/row_shadow.png new file mode 100644 index 0000000..5acf4fc Binary files /dev/null and b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/images/row_shadow.png differ diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/javascripts/scroll.js b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/javascripts/scroll.js new file mode 100644 index 0000000..e6be621 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/javascripts/scroll.js @@ -0,0 +1,28 @@ +/* This file provides the smooth scrolling effect via Javascript. If you don't like it, just delete it! */ + +//Auto-scroll to bottom. Use nearBottom to determine if a scrollToBottom is desired. +function nearBottom() +{ + return ( window.pageYOffset >= ( document.body.offsetHeight - ( window.innerHeight * 1.2 ) ) ); +} + +var intervall_scroll; + +function scrollToBottom() +{ + //document.body.scrollTop = (document.body.scrollHeight-window.innerHeight); + //return; + if (intervall_scroll) clearInterval( intervall_scroll ); + intervall_scroll = setInterval( function() { + var target_scroll = document.body.offsetHeight - window.innerHeight; + var scrolldiff = target_scroll - window.pageYOffset; + if (window.pageYOffset != target_scroll) { + var saved_scroll = window.pageYOffset; + window.scrollTo(window.pageXOffset, window.pageYOffset + (scrolldiff / 5 + ( scrolldiff >= 0 ? (scrolldiff != 0 ) : -1 ))); + } else { + saved_scroll = -1; + clearInterval( intervall_scroll ); + } + } , 10 ); + return; +} diff --git a/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..bab4974 --- /dev/null +++ b/digsby/res/MessageStyles/Candybars.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,56 @@ + + @import url("base.css"); + + + /* Text shadows and text opacity of incoming/outgoing messages: */ + .incoming_row, .outgoing_row + { + color: rgb(255, 255, 255); + text-shadow: 0 1px 2px rgb( 0, 0, 0); + } + + + /* Background colors of incoming/outgoing messages: */ + + .incoming_row { background-color: #0096FF; } + .outgoing_row { background-color: #0096FF; } + + + /* Background colors of incoming/outgoing context messages: */ + + .incoming_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + .outgoing_row_context + { color: #388FCC; + background-color: #80CBFF; + } + + + /* Color of links in incoming/outgoing messages */ + .message_incoming a, .message_incoming_context a + { + color: #0096FF; + } + .message_outgoing a, .message_outgoing_context a + { + color: #0096FF; + } + + + /* Color of incoming/outgoing timestamps */ + .incoming_row .timestamp, .outgoing_row .timestamp + { + color: #fff; + } + + /* Status message colors: */ + .status_row + { color: #005897; + background-color: #C9E8FF; + } + + /* Color for status message timestamp */ + .status_row .timestamp { color: #58AFEE; } diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..9410b35 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + Fiat Adium Message Style + CFBundleIdentifier + com.adiumx.fiat.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + Fiat + CFBundlePackageType + AdIM + DefaultBackgroundColor + e0e0e0 + DefaultFontFamily + Lucida Grande + DefaultFontSize + 11 + DefaultVariant + Blue over Green + MessageViewVersion + 3 + + diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..0a7c4d9 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,30 @@ +
+
+
+
+
+
+ +
+ %sender% +
+
+
+
+
+ %time% +

+ %message% +

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..b740249 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,32 @@ +
+
+
+
+
+
+ +
+
+
+ %sender% +
+
+
+
+
+ %time% +

+ %message% +

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..2dc79b4 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,8 @@ +
+
+%time% +

+ %message% +

+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..2dc79b4 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,8 @@ +
+
+%time% +

+ %message% +

+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..21c7695 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..6f4ae4e --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,30 @@ +
+
+
+
+
+
+ +
+ %sender% +
+
+
+
+
+ %time% +

+ %message% +

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..b740249 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,32 @@ +
+
+
+
+
+
+ +
+
+
+ %sender% +
+
+
+
+
+ %time% +

+ %message% +

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..2dc79b4 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,8 @@ +
+
+%time% +

+ %message% +

+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..2dc79b4 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,8 @@ +
+
+%time% +

+ %message% +

+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..21c7695 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..f3a0b58 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,8 @@ +
+ %time% +

+ %message% +

+
+
+
diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Template.html b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Template.html new file mode 100644 index 0000000..b654656 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Template.html @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + +%@ +
+
+%@ + + diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Gray.css new file mode 100644 index 0000000..bbaa1ed --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Gray.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-gray.css); @import url(../styles/outgoing-blue.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Green.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Green.css new file mode 100644 index 0000000..02ef5de --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Green.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-green.css); @import url(../styles/outgoing-blue.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Orange.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Orange.css new file mode 100644 index 0000000..f41b9dc --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Orange.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-orange.css); @import url(../styles/outgoing-blue.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Red.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Red.css new file mode 100644 index 0000000..747d084 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue over Red.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-red.css); @import url(../styles/outgoing-blue.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue.css new file mode 100644 index 0000000..8778345 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Blue.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-blue.css); @import url(../styles/outgoing-blue.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Dark over Gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Dark over Gray.css new file mode 100644 index 0000000..7e16b4b --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Dark over Gray.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-gray.css); @import url(../styles/outgoing-dark.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Dark.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Dark.css new file mode 100644 index 0000000..0e71885 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Dark.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-dark.css); @import url(../styles/outgoing-dark.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Blue.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Blue.css new file mode 100644 index 0000000..ff5ce9c --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Blue.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-blue.css); @import url(../styles/outgoing-gray.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Dark.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Dark.css new file mode 100644 index 0000000..284cfa2 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Dark.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-dark.css); @import url(../styles/outgoing-gray.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Green.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Green.css new file mode 100644 index 0000000..fd6644c --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Green.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-green.css); @import url(../styles/outgoing-gray.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Orange.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Orange.css new file mode 100644 index 0000000..6fa3824 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Orange.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-orange.css); @import url(../styles/outgoing-gray.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Red.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Red.css new file mode 100644 index 0000000..9cd9c8b --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray over Red.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-red.css); @import url(../styles/outgoing-gray.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray.css new file mode 100644 index 0000000..fad6d79 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Gray.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-gray.css); @import url(../styles/outgoing-gray.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Blue.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Blue.css new file mode 100644 index 0000000..2bed9c6 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Blue.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-blue.css); @import url(../styles/outgoing-green.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Gray.css new file mode 100644 index 0000000..9f64089 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Gray.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-gray.css); @import url(../styles/outgoing-green.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Orange.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Orange.css new file mode 100644 index 0000000..0ab0af6 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Orange.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-orange.css); @import url(../styles/outgoing-green.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Red.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Red.css new file mode 100644 index 0000000..1f90864 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green over Red.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-red.css); @import url(../styles/outgoing-green.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green.css new file mode 100644 index 0000000..a8168f8 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Green.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-green.css); @import url(../styles/outgoing-green.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Blue.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Blue.css new file mode 100644 index 0000000..165610e --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Blue.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-blue.css); @import url(../styles/outgoing-orange.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Gray.css new file mode 100644 index 0000000..1d9789a --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Gray.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-gray.css); @import url(../styles/outgoing-orange.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Green.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Green.css new file mode 100644 index 0000000..2faf149 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Green.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-green.css); @import url(../styles/outgoing-orange.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Red.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Red.css new file mode 100644 index 0000000..6551441 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange over Red.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-red.css); @import url(../styles/outgoing-orange.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange.css new file mode 100644 index 0000000..08fb80d --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Orange.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-orange.css); @import url(../styles/outgoing-orange.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Blue.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Blue.css new file mode 100644 index 0000000..2d86b6b --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Blue.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-blue.css); @import url(../styles/outgoing-red.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Gray.css new file mode 100644 index 0000000..a46ed70 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Gray.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-gray.css); @import url(../styles/outgoing-red.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Green.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Green.css new file mode 100644 index 0000000..3e09eec --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Green.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-green.css); @import url(../styles/outgoing-red.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Orange.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Orange.css new file mode 100644 index 0000000..268b4af --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red over Orange.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-orange.css); @import url(../styles/outgoing-red.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red.css new file mode 100644 index 0000000..3ef4e83 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/Variants/Red.css @@ -0,0 +1 @@ +@import url(../styles/common.css); @import url(../styles/context-gray.css); @import url(../styles/incoming-red.css); @import url(../styles/outgoing-red.css); @import url(../styles/status-gray.css); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-center.png new file mode 100644 index 0000000..c229f5e Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-left.png new file mode 100644 index 0000000..463cdc1 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-right.png new file mode 100644 index 0000000..797b92d Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-bottom-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-center.png new file mode 100644 index 0000000..b74240a Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-left.png new file mode 100644 index 0000000..931caff Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-right.png new file mode 100644 index 0000000..c3918af Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-header-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-left.png new file mode 100644 index 0000000..1197f1f Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-right.png new file mode 100644 index 0000000..4cf0255 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/blue/m-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-center.png new file mode 100644 index 0000000..af9ce4a Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-left.png new file mode 100644 index 0000000..12a195b Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-right.png new file mode 100644 index 0000000..28b2eab Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-bottom-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-center.png new file mode 100644 index 0000000..5ee1e04 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-left.png new file mode 100644 index 0000000..804b82d Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-right.png new file mode 100644 index 0000000..1de7baf Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-header-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-left.png new file mode 100644 index 0000000..1adf812 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-right.png new file mode 100644 index 0000000..97160b7 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/dark/m-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-center.png new file mode 100644 index 0000000..af9ce4a Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-left.png new file mode 100644 index 0000000..12a195b Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-right.png new file mode 100644 index 0000000..28b2eab Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-bottom-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-center.png new file mode 100644 index 0000000..ad1b109 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-left.png new file mode 100644 index 0000000..9aa847e Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-right.png new file mode 100644 index 0000000..f88781f Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-header-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-icon-overlay.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-icon-overlay.png new file mode 100644 index 0000000..7e262d8 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-icon-overlay.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-left.png new file mode 100644 index 0000000..1adf812 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-right.png new file mode 100644 index 0000000..97160b7 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/gray/m-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-center.png new file mode 100644 index 0000000..dd1e8f4 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-left.png new file mode 100644 index 0000000..e183ebd Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-right.png new file mode 100644 index 0000000..5a77a9b Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-bottom-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-center.png new file mode 100644 index 0000000..e8037f8 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-left.png new file mode 100644 index 0000000..2bc5cf8 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-right.png new file mode 100644 index 0000000..5da3ac6 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-header-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-left.png new file mode 100644 index 0000000..b61e367 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-right.png new file mode 100644 index 0000000..23b8e3b Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/green/m-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-center.png new file mode 100644 index 0000000..17935f6 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-left.png new file mode 100644 index 0000000..bdefc65 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-right.png new file mode 100644 index 0000000..6216e63 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-bottom-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-center.png new file mode 100644 index 0000000..2b58699 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-left.png new file mode 100644 index 0000000..b476f17 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-right.png new file mode 100644 index 0000000..fc03740 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-header-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-left.png new file mode 100644 index 0000000..36d8bcb Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-right.png new file mode 100644 index 0000000..3c9e4ac Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/orange/m-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-center.png new file mode 100644 index 0000000..d765791 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-left.png new file mode 100644 index 0000000..eb9117b Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-right.png new file mode 100644 index 0000000..2541d1b Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-bottom-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-center.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-center.png new file mode 100644 index 0000000..48005a8 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-center.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-left.png new file mode 100644 index 0000000..5326747 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-right.png new file mode 100644 index 0000000..c2e7525 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-header-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-left.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-left.png new file mode 100644 index 0000000..16b40b0 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-left.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-right.png b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-right.png new file mode 100644 index 0000000..77f9d74 Binary files /dev/null and b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/images/red/m-right.png differ diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/common.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/common.css new file mode 100644 index 0000000..72bdf37 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/common.css @@ -0,0 +1,108 @@ +body { + background: #e8e8e8; + margin: 0; + padding: 3px; +} + +.header { + position: relative; + margin: 2px 8px 0; + height: 26px; +} + +.header .left { + position: absolute; + left: -8px; + width: 8px; + height: 26px; +} + +.header .right { + position: absolute; + right: -8px; + width: 8px; + height: 26px; +} + +.header .sender { + height: 16px; + font-family: helvetica; + font-size: 14px; + font-weight: bold; + padding: 5px 2px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.header img { + float: right; + padding: 5px 2px; +} + +.messages { + padding-left: 8px; +} + +.messages > div { + padding-right: 8px; +} + +.messages > div > div { + padding: 1px 1px 0; +} + +.time { + float: left; + margin: 0 5px 0 0; + padding: 0; + overflow: hidden; +} + +.message { + margin: 0; + padding: 0; + overflow: hidden; +} + +.messages .sep { + margin: 3px 0 1px; + height: 1px; +} + +.bottom { + position: relative; + margin: 0 8px; + height: 8px; +} + +.bottom .left { + position: absolute; + left: -8px; + width: 8px; + height: 8px; +} + +.bottom .right { + position: absolute; + right: -8px; + width: 8px; + height: 8px; +} + +.status { + margin: 0 8px 2px; + padding: 0; +} + +.context .header .overlay { + float: right; + width: 16px; + height: 16px; + margin: 5px -18px; +} + +.context + .incoming, .context + .outgoing { + margin-top: 4px; + padding-top: 2px; +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/context-gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/context-gray.css new file mode 100644 index 0000000..0e4e97b --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/context-gray.css @@ -0,0 +1,26 @@ +.context a { color: #606060; } .context a:hover { text-shadow: #c0c0c0 0 1px 1px; } +.context .header { + background: url(../images/gray/m-header-center.png) repeat-x; +} +.context .header .left { + background: url(../images/gray/m-header-left.png); +} +.context .header .right { + background: url(../images/gray/m-header-right.png); +} .context .header .sender { color: #606060; text-shadow: #a0a0a0 0 2px 2px; } .context .header .overlay { background-image: url(../images/gray/m-icon-overlay.png); } +.context .messages { + background: url(../images/gray/m-left.png) left repeat-y; +} +.context .messages > div { + background: url(../images/gray/m-right.png) right repeat-y; +} +.context .messages > div > div { background: #f8f8f8; } .context .messages .time { color: #404040; /*text-shadow: #c0c0c0 0 1px 1px;*/ } .context .messages .message { color: #404040; } .context .messages .sep { background: #d0d0d0; border-bottom: 1px solid #ebebeb; } +.context .bottom { + background: url(../images/gray/m-bottom-center.png) repeat-x; +} +.context .bottom .left { + background: url(../images/gray/m-bottom-left.png); +} +.context .bottom .right { + background: url(../images/gray/m-bottom-right.png); +} .context + .incoming, .context + .outgoing { border-top: 2px solid rgb(64,64,64); } diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-blue.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-blue.css new file mode 100644 index 0000000..957acce --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-blue.css @@ -0,0 +1,20 @@ +.incoming a { color: #234578; } .incoming a:hover { text-shadow: #a3c5f8 0 1px 1px; } +.incoming .header { + background: url(../images/blue/m-header-center.png) repeat-x; +} +.incoming .header .left { + background: url(../images/blue/m-header-left.png); +} +.incoming .header .right { + background: url(../images/blue/m-header-right.png); +} +.incoming .header .sender { color: #234578; text-shadow: #83a5d8 0 2px 2px; } +.incoming .messages { + background: url(../images/blue/m-left.png) left repeat-y; +} +.incoming .messages > div { + background: url(../images/blue/m-right.png) right repeat-y; +} .incoming .messages > div > div { background: #f5f9ff; } .incoming .messages .time { color: #234578; text-shadow: #a3c5f8 0 1px 1px; } .incoming .messages .sep { background: #a3c5f8; border-bottom: 1px solid #e8ecf2; } .incoming .bottom { + background: url(../images/blue/m-bottom-center.png) repeat-x; } .incoming .bottom .left { + background: url(../images/blue/m-bottom-left.png); } .incoming .bottom .right { + background: url(../images/blue/m-bottom-right.png); } diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-dark.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-dark.css new file mode 100644 index 0000000..5ca0606 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-dark.css @@ -0,0 +1,25 @@ +.incoming a { color: #404040; } .incoming a:hover { text-shadow: #a0a0a0 0 1px 1px; } +.incoming .header { + background: url(../images/dark/m-header-center.png) repeat-x; +} +.incoming .header .left { + background: url(../images/dark/m-header-left.png); +} +.incoming .header .right { + background: url(../images/dark/m-header-right.png); +} .incoming .header .sender { color: #404040; text-shadow: #808080 0 2px 2px; } +.incoming .messages { + background: url(../images/dark/m-left.png) left repeat-y; +} +.incoming .messages > div { + background: url(../images/dark/m-right.png) right repeat-y; +} .incoming .messages > div > div { background: #f8f8f8; } .incoming .messages .time { color: #404040; text-shadow: #a0a0a0 0 1px 1px; } .incoming .messages .sep { background: #a0a0a0; border-bottom: 1px solid #ebebeb; } +.incoming .bottom { + background: url(../images/dark/m-bottom-center.png) repeat-x; +} +.incoming .bottom .left { + background: url(../images/dark/m-bottom-left.png); +} +.incoming .bottom .right { + background: url(../images/dark/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-gray.css new file mode 100644 index 0000000..61c5f88 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-gray.css @@ -0,0 +1,26 @@ +.incoming a { color: #404040; } .incoming a:hover { text-shadow: #c0c0c0 0 1px 1px; } +.incoming .header { + background: url(../images/gray/m-header-center.png) repeat-x; +} +.incoming .header .left { + background: url(../images/gray/m-header-left.png); +} +.incoming .header .right { + background: url(../images/gray/m-header-right.png); +} .incoming .header .sender { color: #404040; text-shadow: #a0a0a0 0 2px 2px; } +.incoming .messages { + background: url(../images/gray/m-left.png) left repeat-y; +} +.incoming .messages > div { + background: url(../images/gray/m-right.png) right repeat-y; +} +.incoming .messages > div > div { background: #f8f8f8; } .incoming .messages .time { color: #404040; text-shadow: #c0c0c0 0 1px 1px; } .incoming .messages .sep { background: #c0c0c0; border-bottom: 1px solid #ebebeb; } +.incoming .bottom { + background: url(../images/gray/m-bottom-center.png) repeat-x; +} +.incoming .bottom .left { + background: url(../images/gray/m-bottom-left.png); +} +.incoming .bottom .right { + background: url(../images/gray/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-green.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-green.css new file mode 100644 index 0000000..177cf4d --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-green.css @@ -0,0 +1,25 @@ +.incoming a { color: #265904; } .incoming a:hover { text-shadow: #a6d984 0 1px 1px; } +.incoming .header { + background: url(../images/green/m-header-center.png) repeat-x; +} +.incoming .header .left { + background: url(../images/green/m-header-left.png); +} +.incoming .header .right { + background: url(../images/green/m-header-right.png); +} .incoming .header .sender { color: #265904; text-shadow: #86b964 0 2px 2px; } +.incoming .messages { + background: url(../images/green/m-left.png) left repeat-y; +} +.incoming .messages > div { + background: url(../images/green/m-right.png) right repeat-y; +} .incoming .messages > div > div { background: #f2ffe8; } .incoming .messages .time { color: #265904; text-shadow: #a6d984 0 1px 1px; } .incoming .messages .sep { background: #a6d984; border-bottom: 1px solid #e5f2dc; } +.incoming .bottom { + background: url(../images/green/m-bottom-center.png) repeat-x; +} +.incoming .bottom .left { + background: url(../images/green/m-bottom-left.png); +} +.incoming .bottom .right { + background: url(../images/green/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-orange.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-orange.css new file mode 100644 index 0000000..7372bb0 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-orange.css @@ -0,0 +1,26 @@ +.incoming a { color: #643a01; } .incoming a:hover { text-shadow: #ebb874 0 1px 1px; } +.incoming .header { + background: url(../images/orange/m-header-center.png) repeat-x; +} +.incoming .header .left { + background: url(../images/orange/m-header-left.png); +} +.incoming .header .right { + background: url(../images/orange/m-header-right.png); +} .incoming .header .sender { color: #643a01; text-shadow: #cb9854 0 2px 2px; } +.incoming .messages { + background: url(../images/orange/m-left.png) left repeat-y; +} +.incoming .messages > div { + background: url(../images/orange/m-right.png) right repeat-y; +} +.incoming .messages > div > div { background: #fff8ed; } .incoming .messages .time { color: #643a01; text-shadow: #ebb874 0 1px 1px; } .incoming .messages .sep { background: #ebb874; border-bottom: 1px solid #f2ebe0; } +.incoming .bottom { + background: url(../images/orange/m-bottom-center.png) repeat-x; +} +.incoming .bottom .left { + background: url(../images/orange/m-bottom-left.png); +} +.incoming .bottom .right { + background: url(../images/orange/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-red.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-red.css new file mode 100644 index 0000000..df188f8 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/incoming-red.css @@ -0,0 +1,26 @@ +.incoming a { color: #712d1c; } .incoming a:hover { text-shadow: #f1ad9c 0 1px 1px; } +.incoming .header { + background: url(../images/red/m-header-center.png) repeat-x; +} +.incoming .header .left { + background: url(../images/red/m-header-left.png); +} +.incoming .header .right { + background: url(../images/red/m-header-right.png); +} .incoming .header .sender { color: #712d1c; text-shadow: #d18d7c 0 2px 2px; } +.incoming .messages { + background: url(../images/red/m-left.png) left repeat-y; +} +.incoming .messages > div { + background: url(../images/red/m-right.png) right repeat-y; +} +.incoming .messages > div > div { background: #fff6f4; } .incoming .messages .time { color: #712d1c; text-shadow: #f1ad9c 0 1px 1px; } .incoming .messages .sep { background: #f1ad9c; border-bottom: 1px solid #f2e9e7; } +.incoming .bottom { + background: url(../images/red/m-bottom-center.png) repeat-x; +} +.incoming .bottom .left { + background: url(../images/red/m-bottom-left.png); +} +.incoming .bottom .right { + background: url(../images/red/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-blue.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-blue.css new file mode 100644 index 0000000..502f033 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-blue.css @@ -0,0 +1,45 @@ +.outgoing a { + color: #234578; +} +.outgoing a:hover { + text-shadow: #a3c5f8 0 1px 1px; +} +.outgoing .header { + background: url(../images/blue/m-header-center.png) repeat-x; +} +.outgoing .header .left { + background: url(../images/blue/m-header-left.png); +} +.outgoing .header .right { + background: url(../images/blue/m-header-right.png); +} +.outgoing .header .sender { + color: #234578; + text-shadow: #83a5d8 0 2px 2px; +} +.outgoing .messages { + background: url(../images/blue/m-left.png) left repeat-y; +} +.outgoing .messages > div { + background: url(../images/blue/m-right.png) right repeat-y; +} +.outgoing .messages > div > div { + background: #f5f9ff; +} +.outgoing .messages .time { + color: #234578; + text-shadow: #a3c5f8 0 1px 1px; +} +.outgoing .messages .sep { + background: #a3c5f8; + border-bottom: 1px solid #e8ecf2; +} +.outgoing .bottom { + background: url(../images/blue/m-bottom-center.png) repeat-x; +} +.outgoing .bottom .left { + background: url(../images/blue/m-bottom-left.png); +} +.outgoing .bottom .right { + background: url(../images/blue/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-dark.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-dark.css new file mode 100644 index 0000000..ce0d310 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-dark.css @@ -0,0 +1,45 @@ +.outgoing a { + color: #404040; +} +.outgoing a:hover { + text-shadow: #a0a0a0 0 1px 1px; +} +.outgoing .header { + background: url(../images/dark/m-header-center.png) repeat-x; +} +.outgoing .header .left { + background: url(../images/dark/m-header-left.png); +} +.outgoing .header .right { + background: url(../images/dark/m-header-right.png); +} +.outgoing .header .sender { + color: #404040; + text-shadow: #808080 0 2px 2px; +} +.outgoing .messages { + background: url(../images/dark/m-left.png) left repeat-y; +} +.outgoing .messages > div { + background: url(../images/dark/m-right.png) right repeat-y; +} +.outgoing .messages > div > div { + background: #f8f8f8; +} +.outgoing .messages .time { + color: #404040; + text-shadow: #a0a0a0 0 1px 1px; +} +.outgoing .messages .sep { + background: #a0a0a0; + border-bottom: 1px solid #ebebeb; +} +.outgoing .bottom { + background: url(../images/dark/m-bottom-center.png) repeat-x; +} +.outgoing .bottom .left { + background: url(../images/dark/m-bottom-left.png); +} +.outgoing .bottom .right { + background: url(../images/dark/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-gray.css new file mode 100644 index 0000000..ecc0a0b --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-gray.css @@ -0,0 +1,45 @@ +.outgoing a { + color: #404040; +} +.outgoing a:hover { + text-shadow: #c0c0c0 0 1px 1px; +} +.outgoing .header { + background: url(../images/gray/m-header-center.png) repeat-x; +} +.outgoing .header .left { + background: url(../images/gray/m-header-left.png); +} +.outgoing .header .right { + background: url(../images/gray/m-header-right.png); +} +.outgoing .header .sender { + color: #404040; + text-shadow: #a0a0a0 0 2px 2px; +} +.outgoing .messages { + background: url(../images/gray/m-left.png) left repeat-y; +} +.outgoing .messages > div { + background: url(../images/gray/m-right.png) right repeat-y; +} +.outgoing .messages > div > div { + background: #f8f8f8; +} +.outgoing .messages .time { + color: #404040; + text-shadow: #c0c0c0 0 1px 1px; +} +.outgoing .messages .sep { + background: #c0c0c0; + border-bottom: 1px solid #ebebeb; +} +.outgoing .bottom { + background: url(../images/gray/m-bottom-center.png) repeat-x; +} +.outgoing .bottom .left { + background: url(../images/gray/m-bottom-left.png); +} +.outgoing .bottom .right { + background: url(../images/gray/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-green.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-green.css new file mode 100644 index 0000000..a2c371f --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-green.css @@ -0,0 +1,45 @@ +.outgoing a { + color: #265904; +} +.outgoing a:hover { + text-shadow: #a6d984 0 1px 1px; +} +.outgoing .header { + background: url(../images/green/m-header-center.png) repeat-x; +} +.outgoing .header .left { + background: url(../images/green/m-header-left.png); +} +.outgoing .header .right { + background: url(../images/green/m-header-right.png); +} +.outgoing .header .sender { + color: #265904; + text-shadow: #86b964 0 2px 2px; +} +.outgoing .messages { + background: url(../images/green/m-left.png) left repeat-y; +} +.outgoing .messages > div { + background: url(../images/green/m-right.png) right repeat-y; +} +.outgoing .messages > div > div { + background: #f2ffe8; +} +.outgoing .messages .time { + color: #265904; + text-shadow: #a6d984 0 1px 1px; +} +.outgoing .messages .sep { + background: #a6d984; + border-bottom: 1px solid #e5f2dc; +} +.outgoing .bottom { + background: url(../images/green/m-bottom-center.png) repeat-x; +} +.outgoing .bottom .left { + background: url(../images/green/m-bottom-left.png); +} +.outgoing .bottom .right { + background: url(../images/green/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-orange.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-orange.css new file mode 100644 index 0000000..7e76427 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-orange.css @@ -0,0 +1,45 @@ +.outgoing a { + color: #643a01; +} +.outgoing a:hover { + text-shadow: #ebb874 0 1px 1px; +} +.outgoing .header { + background: url(../images/orange/m-header-center.png) repeat-x; +} +.outgoing .header .left { + background: url(../images/orange/m-header-left.png); +} +.outgoing .header .right { + background: url(../images/orange/m-header-right.png); +} +.outgoing .header .sender { + color: #643a01; + text-shadow: #cb9854 0 2px 2px; +} +.outgoing .messages { + background: url(../images/orange/m-left.png) left repeat-y; +} +.outgoing .messages > div { + background: url(../images/orange/m-right.png) right repeat-y; +} +.outgoing .messages > div > div { + background: #fff8ed; +} +.outgoing .messages .time { + color: #643a01; + text-shadow: #ebb874 0 1px 1px; +} +.outgoing .messages .sep { + background: #ebb874; + border-bottom: 1px solid #f2ebe0; +} +.outgoing .bottom { + background: url(../images/orange/m-bottom-center.png) repeat-x; +} +.outgoing .bottom .left { + background: url(../images/orange/m-bottom-left.png); +} +.outgoing .bottom .right { + background: url(../images/orange/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-red.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-red.css new file mode 100644 index 0000000..7edb9a9 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/outgoing-red.css @@ -0,0 +1,26 @@ +.outgoing a { color: #712d1c; } .outgoing a:hover { text-shadow: #f1ad9c 0 1px 1px; } +.outgoing .header { + background: url(../images/red/m-header-center.png) repeat-x; +} +.outgoing .header .left { + background: url(../images/red/m-header-left.png); +} +.outgoing .header .right { + background: url(../images/red/m-header-right.png); +} .outgoing .header .sender { color: #712d1c; text-shadow: #d18d7c 0 2px 2px; } +.outgoing .messages { + background: url(../images/red/m-left.png) left repeat-y; +} +.outgoing .messages > div { + background: url(../images/red/m-right.png) right repeat-y; +} +.outgoing .messages > div > div { background: #fff6f4; } .outgoing .messages .time { color: #712d1c; text-shadow: #f1ad9c 0 1px 1px; } .outgoing .messages .sep { background: #f1ad9c; border-bottom: 1px solid #f2e9e7; } +.outgoing .bottom { + background: url(../images/red/m-bottom-center.png) repeat-x; +} +.outgoing .bottom .left { + background: url(../images/red/m-bottom-left.png); +} +.outgoing .bottom .right { + background: url(../images/red/m-bottom-right.png); +} diff --git a/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/status-gray.css b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/status-gray.css new file mode 100644 index 0000000..a91fbb6 --- /dev/null +++ b/digsby/res/MessageStyles/Fiat.AdiumMessageStyle/Contents/Resources/styles/status-gray.css @@ -0,0 +1 @@ +.status { text-shadow: #c0c0c0 0 1px 1px; background-color: rgb(232,232,232) } .status a { color: #404040; } .status .time { color: #404040; } .status .message { } \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/.typeAttributes.dict b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/.typeAttributes.dict new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..56e7a82 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + GoneDark Message Style + CFBundleIdentifier + com.adiumx.gonedark.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + GoneDark + CFBundlePackageType + AdIM + DefaultFontFamily + Arial + DefaultFontSize + 11 + DisplayNameForNoVariant + Standard + MessageViewVersion + 1 + DefaultVariant + Steel + ShowsUserIcons + + AllowTextColors + + + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..4cdcdc7 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Header.html @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..7cf56b8 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,15 @@ + +
+
%service%
+
%sender%
+
+
+
%time%
+
%message%
+
+
+
+
+
+ + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..f8d5363 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,15 @@ + +
+
%service%
+
%sender%
+
+
+
%time%
+
%message%
+
+
+
+
+
+ + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..1f5a635 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,5 @@ +
+
%time%
+
%message%
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..1f5a635 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,5 @@ +
+
%time%
+
%message%
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..334fc21 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..c358cba --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,14 @@ + +
+
%service%
+
%sender%
+
+
+
%time%
+
%message%
+
+
+
+
+
+ \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..b04aa87 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,15 @@ + +
+
%service%
+
%sender%
+
+
+
%time%
+
%message%
+
+
+
+
+
+ + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..1f5a635 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,5 @@ +
+
%time%
+
%message%
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..1f5a635 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,5 @@ +
+
%time%
+
%message%
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..334fc21 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..cd0038b --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,2 @@ +
%message% (%time%)
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Cosmic.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Cosmic.css new file mode 100644 index 0000000..3b9733a --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Cosmic.css @@ -0,0 +1,50 @@ +@import url('../main.css'); + +body { + background: url(../images/backgroundCosmic.jpg) repeat-y center top #000 fixed; +} + +.message1 .userName, .message1 .date { + color: #ff57ab; +} + +.message2 .userName, .message2 .date { + color: #ccc; +} + +.content { + border-top: 3px solid #ff57ab; + border-bottom: 1px solid #ff57ab; +} + +.message2 .content { + border-top: 3px solid #ccc; + border-bottom: 1px solid #ccc; +} + + +#headerChatStarted h2 { + color: #ff57ab; +} + +#headerImage { + background: url(../images/backgroundUserImageCosmic.png) no-repeat; +} + +.message1 a { + color: #ff1387; +} + +.message1 a:hover { + color: #ff67d3; +} + +.message2 a { + color: #d5d5d5; +} + +.message2 a:hover { + color: #f0f0f0; +} + + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Crimson.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Crimson.css new file mode 100644 index 0000000..c1d82b3 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Crimson.css @@ -0,0 +1,46 @@ +@import url('../main.css'); + +body { + background: url(../images/backgroundCrimson.jpg) repeat-y center top #000 fixed; +} + +.message1 .userName, .message1 .date, #headerChatStarted h2 { + color: #e1000b; +} + +.message2 .userName, .message2 .date { + color: #b50000; +} + +.content { + border-top: 3px solid #e1000b; + border-bottom: 1px solid #e1000b; +} + +.message2 .content { + border-top: 3px solid #b50000; + border-bottom: 1px solid #b50000; +} + +#headerImage { + background: url(../images/backgroundUserImageCrimson.png) no-repeat; +} + +.message1 a { + color: #d3000a; +} + +.message1 a:hover { + color: #f4222c; +} + +.message2 a { + color: #d0000a; +} + +.message2 a:hover { + color: #f0151f; +} + + + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Delaware Punch.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Delaware Punch.css new file mode 100644 index 0000000..019d77e --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Delaware Punch.css @@ -0,0 +1,49 @@ +@import url('../main.css'); + +body { + background: url(../images/backgroundDelaware.jpg) repeat-y center top #000 fixed; +} + +.message1 .userName, .message1 .date { + color: #b24573; +} + +.message2 .userName, .message2 .date { + color: #d2c599; +} + +.content { + border-top: 3px solid #b24573; + border-bottom: 1px solid #b24573; +} + +.message2 .content { + border-top: 3px solid #d2c599; + border-bottom: 1px solid #d2c599; +} + + +#headerChatStarted h2 { + color: #b24573; +} + +#headerImage { + background: url(../images/backgroundUserImageDelaware.png) no-repeat; +} + +.message1 a { + color: #e24d8a; +} + +.message1 a:hover { + color: #f27dac; +} + +.message2 a { + color: #ffedb3; +} + +.message2 a:hover { + color: #fff0c0; +} + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Lava Flow.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Lava Flow.css new file mode 100644 index 0000000..0bd5e38 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Lava Flow.css @@ -0,0 +1,51 @@ +@import url('../main.css'); + +body { + background: url(../images/backgroundLava.jpg) repeat-y center top #000 fixed; +} + +.message1 .userName, .message1 .date { + color: #edb200; +} + +.message2 .userName, .message2 .date { + color: #da2c0b; +} + +.content { + border-top: 3px solid #edb200; + border-bottom: 1px solid #edb200; +} + +.message2 .content { + border-top: 3px solid #da2c0b; + border-bottom: 1px solid #da2c0b; +} + + +#headerChatStarted h2 { + color: #edb200; +} + +#headerImage { + background: url(../images/backgroundUserImageLava.png) no-repeat; +} + +.message1 a { + color: #ffc10f; +} + +.message1 a:hover { + color: #ffd665; +} + +.message2 a { + color: #ff3f1e; +} + +.message2 a:hover { + color: #ff5b31; +} + + + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Mutation.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Mutation.css new file mode 100644 index 0000000..211fad9 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Mutation.css @@ -0,0 +1,50 @@ +@import url('../main.css'); + +body { + background: url(../images/backgroundMutation.jpg) repeat-y center top #000 fixed; +} + +.message1 .userName, .message1 .date { + color: #b4df00; +} + +.message2 .userName, .message2 .date { + color: #e79d10; +} + +.content { + border-top: 3px solid #b4df00; + border-bottom: 1px solid #b4df00; +} + +.message2 .content { + border-top: 3px solid #e79d10; + border-bottom: 1px solid #e79d10; +} + + +#headerChatStarted h2 { + color: #b4df00; +} + +#headerImage { + background: url(../images/backgroundUserImageMutation.png) no-repeat; +} + +.message1 a { + color: #d9ff35; +} + +.message1 a:hover { + color: #eaff87; +} + +.message2 a { + color: #ffb022; +} + +.message2 a:hover { + color: #ffc863; +} + + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Steel.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Steel.css new file mode 100644 index 0000000..30f5081 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Steel.css @@ -0,0 +1,45 @@ +@import url('../main.css'); + +body { + background: url(../images/backgroundSteel.jpg) repeat-y center top #000 fixed; +} + +.message1 .userName, .message1 .date, #headerChatStarted h2 { + color: #568aa8; +} + +.message2 .userName, .message2 .date { + color: #aac0cd; +} + +.content { + border-top: 3px solid #568aa8; + border-bottom: 1px solid #568aa8; +} + +.message2 .content { + border-top: 3px solid #aac0cd; + border-bottom: 1px solid #aac0cd; +} + +#headerImage { + background: url(../images/backgroundUserImageSteel.png) no-repeat; +} + +.message1 a { + color: #4d85a9; +} + +.message1 a:hover { + color: #7cadcc; +} + +.message2 a { + color: #b7cad6; +} + +.message2 a:hover { + color: #c5d6e1; +} + + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Undersea.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Undersea.css new file mode 100644 index 0000000..7d4a3c6 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/Undersea.css @@ -0,0 +1,50 @@ +@import url('../main.css'); + +body { + background: url(../images/backgroundUndersea.jpg) repeat-y center top #000 fixed; +} + +.message1 .userName, .message1 .date { + color: #00a1ff; +} + +.message2 .userName, .message2 .date { + color: #7fadcd; +} + +.content { + border-top: 3px solid #00a1ff; + border-bottom: 1px solid #00a1ff; +} + +.message2 .content { + border-top: 3px solid #7fadcd; + border-bottom: 1px solid #7fadcd; +} + + +#headerChatStarted h2 { + color: #00a1ff; +} + +#headerImage { + background: url(../images/backgroundUserImageUndersea.png) no-repeat; +} + +.message1 a { + color: #2fb5ff; +} + +.message1 a:hover { + color: #66e7ff; +} + +.message2 a { + color: #a3d5f7; +} + +.message2 a:hover { + color: #cfecff; +} + + diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Cosmic.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Cosmic.css new file mode 100644 index 0000000..32894d3 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Cosmic.css @@ -0,0 +1,5 @@ +@import url('Cosmic.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Crimson.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Crimson.css new file mode 100644 index 0000000..00357df --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Crimson.css @@ -0,0 +1,5 @@ +@import url('Crimson.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Delaware Punch.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Delaware Punch.css new file mode 100644 index 0000000..0c6fc0b --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Delaware Punch.css @@ -0,0 +1,5 @@ +@import url('Delaware Punch.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Lava Flow.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Lava Flow.css new file mode 100644 index 0000000..477bdce --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Lava Flow.css @@ -0,0 +1,5 @@ +@import url('Lava Flow.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Mutation.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Mutation.css new file mode 100644 index 0000000..c418a39 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Mutation.css @@ -0,0 +1,5 @@ +@import url('Mutation.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Standard.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Standard.css new file mode 100644 index 0000000..69ed9d9 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Standard.css @@ -0,0 +1,5 @@ +@import url('../main.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Steel.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Steel.css new file mode 100644 index 0000000..c6ab7fb --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Steel.css @@ -0,0 +1,5 @@ +@import url('Steel.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Undersea.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Undersea.css new file mode 100644 index 0000000..4207ca8 --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/Variants/no gloss - Undersea.css @@ -0,0 +1,5 @@ +@import url('Undersea.css'); + +.gloss { + background-image: none; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/background.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/background.jpg new file mode 100644 index 0000000..1de567a Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/background.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundBarBlue.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundBarBlue.png new file mode 100644 index 0000000..02a6181 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundBarBlue.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundBarGreen.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundBarGreen.png new file mode 100644 index 0000000..60789d2 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundBarGreen.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundCosmic.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundCosmic.jpg new file mode 100644 index 0000000..f122687 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundCosmic.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundCrimson.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundCrimson.jpg new file mode 100644 index 0000000..9feccd0 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundCrimson.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundDelaware.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundDelaware.jpg new file mode 100644 index 0000000..ee238fa Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundDelaware.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundHeader.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundHeader.png new file mode 100644 index 0000000..574e8f1 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundHeader.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundLava.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundLava.jpg new file mode 100644 index 0000000..3157cd3 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundLava.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundMutation.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundMutation.jpg new file mode 100644 index 0000000..55d6969 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundMutation.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundSteel.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundSteel.jpg new file mode 100644 index 0000000..66e88be Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundSteel.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUndersea.jpg b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUndersea.jpg new file mode 100644 index 0000000..b5fb3d6 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUndersea.jpg differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImage.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImage.png new file mode 100644 index 0000000..0a49108 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImage.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageCosmic.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageCosmic.png new file mode 100644 index 0000000..8f4b782 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageCosmic.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageCrimson.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageCrimson.png new file mode 100644 index 0000000..604cef86 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageCrimson.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageDelaware.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageDelaware.png new file mode 100644 index 0000000..7dd7fe2 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageDelaware.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageLava.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageLava.png new file mode 100644 index 0000000..e5f2ea3 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageLava.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageMutation.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageMutation.png new file mode 100644 index 0000000..d9f32d8 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageMutation.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageSteel.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageSteel.png new file mode 100644 index 0000000..7a6946b Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageSteel.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageUndersea.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageUndersea.png new file mode 100644 index 0000000..2e8ebd5 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/backgroundUserImageUndersea.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/divider.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/divider.png new file mode 100644 index 0000000..056b328 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/divider.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/gloss.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/gloss.png new file mode 100644 index 0000000..84ea73e Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/gloss.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/messageShadow.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/messageShadow.png new file mode 100644 index 0000000..f3164a6 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/messageShadow.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/transGrey.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/transGrey.png new file mode 100644 index 0000000..8d98c45 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/images/transGrey.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/incoming_icon.png b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/incoming_icon.png new file mode 100644 index 0000000..991d7f1 Binary files /dev/null and b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/incoming_icon.png differ diff --git a/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..abdd9dc --- /dev/null +++ b/digsby/res/MessageStyles/GoneDark.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,166 @@ +body { + margin: 0; + padding: 0; + background: url(images/background.jpg) repeat-y center top #000 fixed; +} + +#header { + display: block; + height: 50px; + position: fixed; + top: 0; + width: 100%; + background: url(images/backgroundHeader.png) repeat-x top; + z-index: 100; +} + +#headerImage { + position: absolute; + background: url(images/backgroundUserImage.png) no-repeat; + width: 32px; + height: 32px; + margin-left: 20px; + margin-top: 4px; + padding: 3px; + left: 0px; + top: 0px; + +} + +#headerChatStarted { + position: absolute; + color: #ccc; + font: 10px "Trebuchet MS", TrebuchetMS; + margin-left: 10px; + margin-top: 6px; + left: 52px; + top: 0px; + right: 0px; + height: 50px; + overflow: hidden; +} + +#headerChatStarted h2 { + color: #86ce4c; + font-size: 19px; + font-weight: bold; + margin: 0; + padding: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +#headerSpace { + height: 50px; +} + +.userName { + font: bold 15px "Trebuchet MS", TrebuchetMS; + margin-left: 20px; + margin-right: 20px; + margin-bottom: -3px; + /*text-shadow: 1px 0px 0px #000*/ + text-shadow: 0px -1px 5px #000; +} + +.service { + float: right; + font-size: 9px; + margin-right: 20px; + line-height: 15px; + color: #fff; + opacity: .4; + font-family: "Trebuchet MS", TrebuchetMS; +} + +.message1 .userName, .message1 .date { + color: #86ce4c; + +} + +.content { + border-top: 3px solid #86ce4c; + border-bottom: 1px solid #86ce4c; + color: #fff; + font-size: 12px; + font-family: Tahoma, Arial, sans-serif; + background: url(images/transGrey.png); + word-wrap: break-word; +} + +.message1 a { + color: #b4fe2b; + text-decoration: underline; +} + +.message1 a:hover { + color: #f2fd0d; +} + +.gloss { + background: url(images/gloss.png) no-repeat center 4px; +} + +.contentText { + padding-top: 5px; + padding-bottom: 5px; + margin-right: 60px; + margin-left: 20px; + font-size: 14px; +} + +.divider { + background: url(images/divider.png) repeat-x center; + height: 2px; +} + +.shadow { + background: url(images/messageShadow.png) repeat-x top; + height: 9px; +} + +.date { + float: right; + font-size: 10px; + margin-right: 20px; + padding-top: 5px; + text-shadow: #000 2px 2px 0px; +} + +.message2 .userName, .message2 .date { + color: #42b6ff; +} + +.message2 a { + color: #42b6ff; + text-decoration: underline; +} + +.message2 a:hover { + color: #73ddfe; +} + +.message2 .content { + border-top: 3px solid #42b6ff; + border-bottom: 1px solid #42b6ff; +} + +.status { + color: #fff; + text-align: center; + margin-bottom: .3em; + opacity: .7; + font: bold 10px "Trebuchet MS", TrebuchetMS; +} + +#Chat { + overflow: hidden; + padding-top: 50px; + padding-bottom: 6px; + margin-bottom: 0px; +} + +.context { + opacity: .6; +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/.typeAttributes.dict b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/.typeAttributes.dict new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..0c20d9e --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + Metal Chat 2 Adium Message Style + CFBundleIdentifier + com.adiumx.metalchat2.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + MetalChat2 + CFBundlePackageType + AdIM + DefaultFontFamily + Lucida Grande + DefaultFontSize + 11 + DisplayNameForNoVariant + Blue & Green + MessageViewVersion + 1 + ShowsUserIcons + + + diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..d8e43e0 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Header.html @@ -0,0 +1,14 @@ + +
+
+
+
+
+ +
started at %timeOpened{%I:%M %p}%
+
+ %chatName% +
+
+ + diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..5c0be6e --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,38 @@ + +
+ +
+
+
+
+ + + +
+
+
+ %sender% + %time% +
+
+
+ + +
+
+
+
+

%message%

+
+ +
+
+ +
+
+
+
+
+
+ + diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..97cbd72 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,9 @@ +
+
+
+
+
%time%
+

%message%

+
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..334fc21 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..1383c4f --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,37 @@ + +
+ +
+
+
+
+ + + +
+
+
+ %sender% + %time% +
+
+
+ + +
+
+
+
+

%message%

+
+ +
+
+ +
+
+
+
+
+
+ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..97cbd72 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,9 @@ +
+
+
+
+
%time%
+

%message%

+
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..334fc21 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..7e9351b --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,10 @@ + +
+
+
+ %message% (%time%) +
+
+
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Green (No Header).css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Green (No Header).css new file mode 100644 index 0000000..82a52c8 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Green (No Header).css @@ -0,0 +1,9 @@ +@import url('../main.css'); + +.divApple { + display: none; +} + +.divHeader { + display: none; +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Orange (No Header).css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Orange (No Header).css new file mode 100644 index 0000000..10e9df1 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Orange (No Header).css @@ -0,0 +1,19 @@ +@import url('../main.css'); + +.divApple { + display: none; +} + +.divHeader { + display: none; +} + +.container .headerOutgoing .left { + background-image: url(../images/chatHeaderBarOrangeLeft.png); +} +.container .headerOutgoing .userName { + background-image: url(../images/chatHeaderBarOrangeTile.png); +} +.container .headerOutgoing .right { + background-image: url(../images/chatHeaderBarOrangeRight.png); +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Orange.css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Orange.css new file mode 100644 index 0000000..c30fa66 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Orange.css @@ -0,0 +1,11 @@ +@import url('../main.css'); + +.container .headerOutgoing .left { + background-image: url(../images/chatHeaderBarOrangeLeft.png); +} +.container .headerOutgoing .userName { + background-image: url(../images/chatHeaderBarOrangeTile.png); +} +.container .headerOutgoing .right { + background-image: url(../images/chatHeaderBarOrangeRight.png); +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Red (No Header).css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Red (No Header).css new file mode 100644 index 0000000..4db9c6d --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Red (No Header).css @@ -0,0 +1,19 @@ +@import url('../main.css'); + +.divApple { + display: none; +} + +.divHeader { + display: none; +} + +.container .headerOutgoing .left { + background-image: url(../images/chatHeaderBarRedLeft.png); +} +.container .headerOutgoing .userName { + background-image: url(../images/chatHeaderBarRedTile.png); +} +.container .headerOutgoing .right { + background-image: url(../images/chatHeaderBarRedRight.png); +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Red.css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Red.css new file mode 100644 index 0000000..e034f8b --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Blue & Red.css @@ -0,0 +1,11 @@ +@import url('../main.css'); + +.container .headerOutgoing .left { + background-image: url(../images/chatHeaderBarRedLeft.png); +} +.container .headerOutgoing .userName { + background-image: url(../images/chatHeaderBarRedTile.png); +} +.container .headerOutgoing .right { + background-image: url(../images/chatHeaderBarRedRight.png); +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Red & Orange (No Header).css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Red & Orange (No Header).css new file mode 100644 index 0000000..44cadae --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Red & Orange (No Header).css @@ -0,0 +1,29 @@ +@import url('../main.css'); + +.divApple { + display: none; +} + +.divHeader { + display: none; +} + +.container .headerIncoming .left { + background-image: url(../images/chatHeaderBarRedLeft.png); +} +.container .headerIncoming .userName { + background-image: url(../images/chatHeaderBarRedTile.png); +} +.container .headerIncoming .right { + background-image: url(../images/chatHeaderBarRedRight.png); +} + +.container .headerOutgoing .left { + background-image: url(../images/chatHeaderBarOrangeLeft.png); +} +.container .headerOutgoing .userName { + background-image: url(../images/chatHeaderBarOrangeTile.png); +} +.container .headerOutgoing .right { + background-image: url(../images/chatHeaderBarOrangeRight.png); +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Red & Orange.css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Red & Orange.css new file mode 100644 index 0000000..2479038 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/Variants/Red & Orange.css @@ -0,0 +1,21 @@ +@import url('../main.css'); + +.container .headerIncoming .left { + background-image: url(../images/chatHeaderBarRedLeft.png); +} +.container .headerIncoming .userName { + background-image: url(../images/chatHeaderBarRedTile.png); +} +.container .headerIncoming .right { + background-image: url(../images/chatHeaderBarRedRight.png); +} + +.container .headerOutgoing .left { + background-image: url(../images/chatHeaderBarOrangeLeft.png); +} +.container .headerOutgoing .userName { + background-image: url(../images/chatHeaderBarOrangeTile.png); +} +.container .headerOutgoing .right { + background-image: url(../images/chatHeaderBarOrangeRight.png); +} diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_1.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_1.png new file mode 100644 index 0000000..bee4119 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_1.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_2.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_2.png new file mode 100644 index 0000000..7841fa9 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_2.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_3.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_3.png new file mode 100644 index 0000000..7101d50 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_3.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_4.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_4.png new file mode 100644 index 0000000..2bab298 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_4.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_5.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_5.png new file mode 100644 index 0000000..74b1c84 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_5.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_6.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_6.png new file mode 100644 index 0000000..0016289 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_6.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_7.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_7.png new file mode 100644 index 0000000..2d279ed Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/NewMsgButton/NewMessage_7.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/backFade.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/backFade.png new file mode 100644 index 0000000..d2a0ebe Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/backFade.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/background_blue.jpg b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/background_blue.jpg new file mode 100644 index 0000000..8b6bb7b Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/background_blue.jpg differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackBottomLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackBottomLeft.png new file mode 100644 index 0000000..6f6e2db Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackBottomLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackLeftTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackLeftTile.png new file mode 100644 index 0000000..91298ea Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackLeftTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackLeftTopBack.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackLeftTopBack.png new file mode 100644 index 0000000..9b56716 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackLeftTopBack.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackRightGlare.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackRightGlare.png new file mode 100644 index 0000000..c51f6f6 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBlackRightGlare.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBottomLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBottomLeft.png new file mode 100644 index 0000000..6d62d6a Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBottomLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBottomTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBottomTile.png new file mode 100644 index 0000000..e756e7d Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatBottomTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatDivider.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatDivider.png new file mode 100644 index 0000000..4130675 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatDivider.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueLeft.png new file mode 100644 index 0000000..ef6f81a Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueRight.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueRight.png new file mode 100644 index 0000000..5f1dd15 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueRight.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueTile.png new file mode 100644 index 0000000..0e34332 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarBlueTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenLeft.png new file mode 100644 index 0000000..e053359 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenRight.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenRight.png new file mode 100644 index 0000000..2d5e8ce Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenRight.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenTile.png new file mode 100644 index 0000000..462fd6f Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarGreenTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeLeft.png new file mode 100644 index 0000000..7974595 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeRight.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeRight.png new file mode 100644 index 0000000..23850ac Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeRight.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeTile.png new file mode 100644 index 0000000..4315c9b Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarOrangeTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedLeft.png new file mode 100644 index 0000000..69a7c8a Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedRight.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedRight.png new file mode 100644 index 0000000..12c3bd9 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedRight.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedTile.png new file mode 100644 index 0000000..e02c8ae Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatHeaderBarRedTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatPictureBack.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatPictureBack.png new file mode 100644 index 0000000..5d2faf7 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatPictureBack.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatRightBottom.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatRightBottom.png new file mode 100644 index 0000000..f88101c Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatRightBottom.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatRightTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatRightTile.png new file mode 100644 index 0000000..f346378 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatRightTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatTableTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatTableTile.png new file mode 100644 index 0000000..ec7e1e9 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/chatTableTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarApple.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarApple.png new file mode 100644 index 0000000..6e9bd6e Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarApple.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarLeft.png new file mode 100644 index 0000000..fdfbf19 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarRight.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarRight.png new file mode 100644 index 0000000..60897f3 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarRight.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarTile.png new file mode 100644 index 0000000..7aa1619 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/headerbarTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/spacer.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/spacer.png new file mode 100644 index 0000000..7847564 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/spacer.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarLeft.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarLeft.png new file mode 100644 index 0000000..687c258 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarLeft.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarRight.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarRight.png new file mode 100644 index 0000000..049efe7 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarRight.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarTile.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarTile.png new file mode 100644 index 0000000..42e18c2 Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/statusbarTile.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/tempimage.png b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/tempimage.png new file mode 100644 index 0000000..82f029a Binary files /dev/null and b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/images/tempimage.png differ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..82ecefc --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,405 @@ +body { + background : #10224e url(images/background_blue.jpg) center top repeat-y fixed; + margin: 0px 0px 0px 0px; +} + +p { + overflow: auto; +} + +.fadeBack { + z-index: -1; + height: 83px; + background: url(images/backFade.png) repeat-x top; + top: 0px; + position: fixed; + width: 100%; +} + +.divApple { + z-index: 500; + position: fixed; + top: 0px; + right: 50%; + left: 50%; + height: 21px; + margin-top: 6px; +} + +.headerApple { + background: url(images/headerbarApple.png) no-repeat; + width: 17px; + height: 21px; + margin-left: auto; + margin-right: auto; +} + +.divHeader { + z-index: 100; + top: 0px; + position: fixed; + width: 100%; + height: 31px; + font-family: "Lucida Grande"; +} + +.headerTile { + position: relative; + margin-left: 9px; + margin-right: 9px; + height: 31px; + padding-top: 6px; + background: url(images/headerbarTile.png) top repeat-x; +} + +.headerLeft { + position: absolute; + left: 0; + background: url(images/headerbarLeft.png) left top; + width: 9px; + height: 31px; +} + +.headerRight { + position: absolute; + right: 0px; + width: 9px; + height: 31px; + background: url(images/headerbarRight.png) left top; +} + +#headerUsername { + color: #FFFFFF; + font-size: 13px; + font-weight: bold; +} + +#headerChatStarted { + position: absolute; + color: #c4c4c4; + font-size: 10px; + top: 10px; + right: 9px; + z-index: 101; +} + +/* +#chatArea { + z-index: 50; + margin-left: 30px; + margin-right: 30px; + position: relative; + margin-top: 37px; +} +*/ + +.container { + position: relative; + padding-bottom: 6px; + margin-left: 20px; + margin-right: 20px; +} + +.container .topLeft { + position: absolute; + left: 0; + top: 0; + width: 45px; + height: 41px; + background: url(images/chatBlackLeftTopBack.png) no-repeat left top; +} + +.container .topLeft .userImage { + background: url(images/chatPictureBack.png) no-repeat; + width: 35px; + height: 35px; + padding: 1px 0 0 1px; + margin: 5px 0 0 6px; +} + +.container .topRight { + position: absolute; + top: 0px; + right: 0px; + bottom: 12px; + width: 10px; + background: url(images/chatBlackRightGlare.png) no-repeat right top; + padding: 0px; +} + +.container borderBottom { + position: relative; + height: 12px; +} + +.container .bottomTile { + position: relative; + margin-left: 56px; + margin-right: 21px; + height: 12px; + background: url(images/chatBottomTile.png) repeat-x top; +} + +.container .bottomLeft { + float: left; + height: 12px; + width: 56px; + background: url(images/chatBlackBottomLeft.png) no-repeat; +} + +.container .bottomRight { + float: right; + width: 21px; + height: 12px; + background: url(images/chatRightBottom.png) no-repeat left top; +} + +.container .headerIncoming, +.container .headerOutgoing { + position: relative; + margin-left: 45px; + margin-right: 10px; +} + +.container .headerIncoming .left { + position: absolute; + left: 0px; + top: 0px; + background: url(images/chatHeaderBarBlueLeft.png) no-repeat left top; + width: 11px; + height: 17px; +} +.container .headerOutgoing .left { + position: absolute; + left: 0px; + top: 0px; + background: url(images/chatHeaderBarGreenLeft.png) no-repeat left top; + width: 11px; + height: 17px; +} + +.container .headerIncoming .userName { + position: relative; + margin: 0 11px; + height: 16px; + background: url(images/chatHeaderBarBlueTile.png) repeat-x right top; + color: #FFFFFF; + font-size: 9px; + padding-top: 4px; + font-family: "Arial"; + font-weight: bold; + text-shadow: 2px 2px 1px #000000; + overflow: hidden; + white-space: nowrap; +} +.container .headerIncoming .userName .sender{ + position: absolute; + left: 0px; + right: 60px; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.container .headerIncoming .userName .timestamp{ + float: right; +} +.container .headerOutgoing .userName { + position: relative; + margin: 0 11px; + height: 16px; + background: url(images/chatHeaderBarGreenTile.png) repeat-x right top; + color: #FFFFFF; + font-size: 9px; + padding-top: 4px; + font-family: "Arial"; + font-weight: bold; + text-shadow: 2px 2px 1px #000000; + overflow: hidden; + white-space: nowrap; +} +.container .headerOutgoing .userName .sender{ + position: absolute; + left: 0px; + right: 60px; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.container .headerOutgoing .userName .timestamp{ + float: right; +} +.container .headerIncoming .right { + position: absolute; + top: 0px; + right: 0px; + background: url(images/chatHeaderBarBlueRight.png) no-repeat left top; + width: 11px; + height: 17px; +} +.container .headerOutgoing .right { + position: absolute; + top: 0px; + right: 0px; + background: url(images/chatHeaderBarGreenRight.png) no-repeat left top; + width: 11px; + height: 17px; +} + +.container .messageBox { + position: relative; + color: #000000; + /*font: 12px/14px 'Lucida Grande', LucidaGrande, Lucida, Helvetica, Arial, sans-serif;*/ +} + +.container .messageBox .message { + position: relative; +} + +.container .messageBox .message p { + margin: -3px 10px 0 45px; + padding: 7px 10px 6px 10px; + background: url(images/chatTableTile.png) repeat-x top #c4c4c4; +} + +.container .messageBox .message .leftTile { + position: absolute; + left: 0px; + top: 24px; + bottom: 0px; + width: 45px; + background: url(images/chatBlackLeftTile.png) repeat-y left; +} +.container .messageBox .nextMessage .leftTile { + position: absolute; + left: 0px; + top: 0px; + bottom: 0px; + width: 45px; + background: url(images/chatBlackLeftTile.png) repeat-y left; +} + +.container .messageBox .message .rightTile { + position: absolute; + right: 0px; + top: 21px; + bottom: 0px; + width: 10px; + background: url(images/chatRightTile.png) repeat-y; +} +.container .messageBox .nextMessage .rightTile { + position: absolute; + right: 0px; + top: 0px; + bottom: 0px; + width: 10px; + background: url(images/chatRightTile.png) repeat-y; +} + +.container .messageBox .divider { + position: relative; + margin-left: 45px; + margin-right: 10px; + background: #c4c4c4 url(images/chatDivider.png) top repeat-x; + height: 2px; + padding: 0px; +} + +.container .messageBox .nextMessage { + position: relative; +} + +.container .messageBox .nextMessage .timestamp { + position: absolute; + top: 2px; + left: 0px; + width: 45px; + text-align: center; + color: #AAAAAA; + font: bold 9px 'Lucida Grande', LucidaGrande, Lucida, Helvetica, Arial, sans-serif; + padding-top: 8px; +} + +.container .messageBox .nextMessage p { + position: relative; + margin: 0 10px 0 45px; + padding: 7px 10px 6px 10px; + background: #c4c4c4; +} + +.statusBar { + position: relative; + text-align: center; + margin-left: 30px; + margin-right: 30px; +} + +.statusBar .left { + position: absolute; + left: 0px; + top: 0px; + background: url(images/statusbarLeft.png) no-repeat; + width: 9px; + height: 24px; +} + +.statusBar .center { + position: relative; + margin: 0 9px; + height: 24px; + background: url(images/statusbarTile.png) repeat-x; + font-family: "Lucida Grande"; + font-size: 10px; + font-weight: bold; + padding-top: 4px; + padding-right: 5px; + padding-left: 5px; + color: #c4c4c4; + text-shadow: 0px 0px 3px #000000; +} + +.statusBar .right { + position: absolute; + right: 0px; + top: 0px; + background: url(images/statusbarRight.png) no-repeat; + width: 9px; + height: 24px; +} + +.statusBar .timestamp { + color: #999; +} + +.statusBar .online { + color: #8DDA4F; +} +.statusBar .away { + color: #F4EC1B; +} +.statusBar .DND { + color: #EB295F; +} +.statusBar .offline { + color: #838383; +} + +/* +#newMsgButton { + position: fixed; + text-align: center; + width: 100%; + bottom: 0px; + height: 32px; + z-index: 100; +} +#newMsgButton .button { + margin-left: auto; + margin-right: auto; + width: 211px; + height: 32px; + background: url(images/NewMsgButton/NewMessage_1.png) no-repeat; + padding-top: 11px; +} +*/ diff --git a/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/scrolling.js b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/scrolling.js new file mode 100644 index 0000000..43f21f3 --- /dev/null +++ b/digsby/res/MessageStyles/Metal Chat 2.AdiumMessageStyle/Contents/Resources/scrolling.js @@ -0,0 +1,15 @@ + +var scrollToBottomIsNeeded = false; var newMsgCounter = 0; var newMsgButtonVisible = true; function scrollToBottom() { smoothScrollToBottom(); if (newMsgButtonVisible) { pulsateNewMsgButton(true); newMsgButtonVisible = false; slideObjectBottom("newMsgButton", 0, -32, 20, 5); } newMsgCounter = 0; } function checkIfScrollToBottomIsNeeded() { scrollToBottomIsNeeded = (document.body.scrollTop >= (document.body.offsetHeight - (window.innerHeight * 1.2))); + alert("checkIfScrollToBottomIsNeeded() called with result " + scrollToBottomIsNeeded); } function scrollToBottomIfNeeded() { if ( scrollToBottomIsNeeded || scrolling ) + scrollToBottom(); + else { + newMsgCounter++; + document.getElementById("newMsgCounter").innerHTML = + newMsgCounter + + " new message" + + (newMsgCounter > 1 ? "s" : ""); if (!newMsgButtonVisible && newMsgCounter != 0) { newMsgButtonVisible = true; slideObjectBottom("newMsgButton", -32, 0, 20, 1); + pulsateNewMsgButton(true); } + } } var scrollID; var scrolling = false; function smoothScrollToBottom() { if (scrolling) window.clearInterval(scrollID); scrolling = true; var y0 = window.scrollY; var y1 = document.body.offsetHeight; var steps = 15; var delay = 1; var incr = (y1 - y0) / steps; var y = y0; scrollID = window.setInterval(function () { y += incr; if ((y0 < y1 && y1 < y) || (y0 > y1 && y1 > y)) { scrolling = false; window.clearInterval(scrollID); } else window.scrollTo(0, y); }, delay); } + var pulsateID; var currentPulsateNum = 1; var pulsateIncr = 1; function pulsateNewMsgButton(activate) { var obj = document.getElementById("newMsgCounter"); obj.style.backgroundImage = 'url("images/NewMsgButton/NewMessage_1.png")'; window.clearInterval(pulsateID); if (activate) { currentPulsateNum = 1; pulsateID = window.setInterval(function () { currentPulsateNum += pulsateIncr; obj.style.backgroundImage = 'url("images/NewMsgButton/NewMessage_' + currentPulsateNum + '.png")'; if (currentPulsateNum >= 7) pulsateIncr = -1; else if (currentPulsateNum <= 1) pulsateIncr = 1; }, 100); } } + + var slideID; function slideObjectBottom(id, start, end, steps, delay) { if (slideID) window.clearInterval(slideID); var obj = document.getElementById(id); var incr = (end - start) / steps; var bottom = start; obj.style.bottom = bottom; if (start != end) slideID = window.setInterval(function () { bottom += incr; if ((start < end && end <= bottom) || (start > end && end >= bottom)) { window.clearInterval(slideID); } else obj.style.bottom = bottom; }, delay) } diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..f84efd0 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + MiniBubble Adium Message Style + CFBundleIdentifier + com.adiumx.minibubble.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + MiniBubble + CFBundlePackageType + AdIM + DefaultFontFamily + Lucida Grande + DefaultFontSize + 11 + DisplayNameForNoVariant + Left + MessageViewVersion + 1 + ShowsUserIcons + + + diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..b47e6a8 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,17 @@ +
+
+
+ + +
+
+
+
+ %time% +
+
+

%message%

+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..efd6002 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,17 @@ +
+
+
+ + +
+
+
+
+ %time% +
+
+

%message%

+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..79f5a97 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,5 @@ +
+ %time% +
+

%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..38cfd63 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,5 @@ +
+ %time% +
+

%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..ca986c9 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..2bdc6e8 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,17 @@ +
+
+
+ + +
+
+
+
+ %time% +
+
+

%message%

+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..88732cc --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,17 @@ +
+
+
+ + +
+
+
+
+ %time% +
+
+

%message%

+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..79f5a97 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,5 @@ +
+ %time% +
+

%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..38cfd63 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,5 @@ +
+ %time% +
+

%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..cace742 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..fc2979b --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,13 @@ +
+
+
+
+
+ %time% +
+
+

%message%

+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Left in, Right out.css b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Left in, Right out.css new file mode 100644 index 0000000..9bb7a89 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Left in, Right out.css @@ -0,0 +1,37 @@ +@import "../base.css"; + +.container, .container_context { + margin: 0px 36px 0px 48px; +} + +.buddyicon_div_incoming { + float:left; +} + +.buddyicon_div_outgoing { + float:right; +} + +.arrow_incoming { + left:32px; + + background: url("../images/arrow.png"); +} + +.arrow_outgoing { + right:33px; + + background: url("../images/arrow_right.png"); +} + +.arrow_context_incoming { + left:32px; + + background: url("../images/context_arrow.png"); +} + +.arrow_context_outgoing { + right:33px; + + background: url("../images/context_arrow_right.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Right in, Left out.css b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Right in, Left out.css new file mode 100644 index 0000000..daf7e2b --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Right in, Left out.css @@ -0,0 +1,37 @@ +@import "../base.css"; + +.container, .container_context { + margin: 0px 36px 0px 48px; +} + +.buddyicon_div_outgoing { + float:left; +} + +.buddyicon_div_incoming { + float:right; +} + +.arrow_outgoing { + left:32px; + + background: url("../images/arrow.png"); +} + +.arrow_incoming { + right:33px; + + background: url("../images/arrow_right.png"); +} + +.arrow_context_outgoing { + left:32px; + + background: url("../images/context_arrow.png"); +} + +.arrow_context_incoming { + right:33px; + + background: url("../images/context_arrow_right.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Right.css b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Right.css new file mode 100644 index 0000000..6188f81 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/Variants/Right.css @@ -0,0 +1,37 @@ +@import "../base.css"; + +.container, .container_context { + margin: 0px 36px 0px 12px; +} + +.buddyicon_div_incoming { + float:right; +} + +.buddyicon_div_outgoing { + float:right; +} + +.arrow_incoming { + right:33px; + + background: url("../images/arrow_right.png"); +} + +.arrow_outgoing { + right:33px; + + background: url("../images/arrow_right.png"); +} + +.arrow_context_incoming { + right:33px; + + background: url("../images/context_arrow_right.png"); +} + +.arrow_context_outgoing { + right:33px; + + background: url("../images/context_arrow_right.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/base.css b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/base.css new file mode 100644 index 0000000..4dce941 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/base.css @@ -0,0 +1,204 @@ +body { + background: #3A3A3A url("images/background.png") bottom left repeat fixed; + margin: -5px 8px 15px 8px; +} + +.message img { + vertical-align:middle; +} + +.message_context img { + vertical-align:middle; +} + +.post_container { + margin-top:14px; + position:relative; +} + +.container { + position: relative; + top:-1px; + background: url("images/bottom_right.png") bottom right no-repeat; + margin: 0px 0px 0px 48px; + padding: 0 0 1px 0; +} + +.container_context { + position: relative; + top:-1px; + background: url("images/context_bottom_right.png") bottom right no-repeat; + padding: 0 0 1px 0; +} + +.container_incoming, .container_outgoing { + position: relative; + top:-1px; + background: url("images/bottom_right.png") bottom right no-repeat; + padding: 0 0 1px 0; +} + +.sender { + top: -6px; + left: -10px; + right: 10px; + position: absolute; + background: url("images/top_left.png") top left no-repeat; + + padding: 6px 0 0 56px; + margin-bottom: 0; +} + +.sender_context { + top: -6px; + left: -10px; + right: 38px; + position: absolute; + background: url("images/context_top_left.png") top left no-repeat; + + padding: 6px 0 0 56px; + margin-bottom: 0; +} + +.time, .time_context { + position: absolute; + right: 3px; + top: -6px; + + text-align:right; + + font-size: 9px; + font-family: "Lucida Grande"; + + padding: 6px 10px 0 2px; + margin-bottom: 0; +} + +.time { + color: #888888; + background: url("images/top_right.png") top right no-repeat; +} + +.time_context { + color: #333333; + background: url("images/context_top_right.png") top right no-repeat; + left: -15px; + +} + +.time_next, .time_next_context { + float:right; + + font-size: 9px; + font-family: "Lucida Grande"; + + padding: 0 0 0 0; + margin-right: -1px; + margin-bottom: 0; +} + +.time_next { + color: #888888; +} + +.time_next_context { + color: #333333; +} + +.message { + position: relative; + background: url("images/bottom_left.png") bottom left repeat-y; + top: -1px; + left: -12px; + margin-right: 2px; + padding: 0px 0 1px 10px; + color: #333333; +} + +.message_context { + position: relative; + background: url("images/context_bottom_left.png") bottom left repeat-y; + top: 0px; + left: -12px; + margin-right: 2px; + margin-bottom: 1px; + padding: 0px 0 1px 10px; + color: #333333; +} + +p { + padding: 0; + padding-top:0px; + margin-bottom: 7px; + margin-right:40px; + overflow: auto; +} + +p.next { + padding: 0; + padding-top:1px; + margin-top:-3px; + margin-bottom: 7px; + overflow: auto; +} + +.buddyicon_div { + position:relative; + top:-6px; + left:0px; + + float:left; + + height:25px; + width:29px; +} + +.buddyicon_div_outgoing, .buddyicon_div_incoming { + position:relative; + top:-6px; + left:0px; + + height:25px; + width:29px; +} + +.buddyicon_img { + height:24px; + width:24px; + + position:relative; + top:0px; + left:2px; +} +.buddyicon_overlay_img { + height:25px; + width:29px; + + position:absolute; + z-index:1; +} + +.arrow_outgoing, .arrow_incoming { + position:absolute; + top:6px; + + height:6px; + width:7px; + + z-index:2; +} + +.arrow_context_outgoing, .arrow_context_incoming { + position:absolute; + top:6px; + + height:6px; + width:6px; + + z-index:2; +} + +#Chat { + overflow: hidden; + margin-bottom: -8px; +} diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/arrow.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/arrow.png new file mode 100644 index 0000000..869182f Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/arrow.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/arrow_right.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/arrow_right.png new file mode 100644 index 0000000..d0a926b Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/arrow_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/background.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/background.png new file mode 100644 index 0000000..bd4553c Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/background.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/bottom_left.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/bottom_left.png new file mode 100644 index 0000000..97634d4 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/bottom_left.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/bottom_right.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/bottom_right.png new file mode 100644 index 0000000..8b8d65a Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/bottom_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/buddy_background.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/buddy_background.png new file mode 100644 index 0000000..9404c0f Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/buddy_background.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_arrow.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_arrow.png new file mode 100644 index 0000000..aee4e0e Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_arrow.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_arrow_right.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_arrow_right.png new file mode 100644 index 0000000..09521f6 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_arrow_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_bottom_left.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_bottom_left.png new file mode 100644 index 0000000..7b19ccc Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_bottom_left.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_bottom_right.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_bottom_right.png new file mode 100644 index 0000000..03d0431 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_bottom_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_top_left.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_top_left.png new file mode 100644 index 0000000..0d1a55d Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_top_left.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_top_right.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_top_right.png new file mode 100644 index 0000000..cc48b06 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/context_top_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/icon_filler.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/icon_filler.png new file mode 100644 index 0000000..d5b2b06 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/icon_filler.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/icon_overlay.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/icon_overlay.png new file mode 100644 index 0000000..645c7c5 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/icon_overlay.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/top_left.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/top_left.png new file mode 100644 index 0000000..dd44437 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/top_left.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/top_right.png b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/top_right.png new file mode 100644 index 0000000..cce0926 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/images/top_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..f8fcc2f --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,37 @@ +@import "base.css"; + +.container, .container_context { + margin: 0px 0px 0px 48px; +} + +.buddyicon_div_incoming { + float:left; +} + +.buddyicon_div_outgoing { + float:left; +} + +.arrow_incoming { + left:32px; + + background: url("images/arrow.png"); +} + +.arrow_outgoing { + left:32px; + + background: url("images/arrow.png"); +} + +.arrow_context_incoming { + left:32px; + + background: url("images/context_arrow.png"); +} + +.arrow_context_outgoing { + left:32px; + + background: url("images/context_arrow.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..bc285d6 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + MiniBubble 2 Adium Message Style + CFBundleIdentifier + com.adiumx.minibubble2.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + MiniBubble 2 + CFBundlePackageType + AdIM + DefaultFontFamily + Lucida Grande + DefaultFontSize + 10 + DisplayNameForNoVariant + Left + MessageViewVersion + 2 + ShowsUserIcons + + + diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..a28f07e --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,25 @@ +
+ + + + + + + %sender% + + + %time% + + + + + + + + +

%message%

+
+
+
+
+
diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..24e8d23 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,25 @@ +
+ + + + + + + %sender% + + + %time% + + + + + + + + +

%message%

+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..b32072c --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,8 @@ + + + %time% + +

%message%

+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..ce046e0 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,8 @@ + + + %time% + +

%message%

+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..c6c84ad Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..5ed9703 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,25 @@ +
+ + + + + + + %sender% + + + %time% + + + + + + + + +

%message%

+
+
+
+
+
diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..da11f62 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,25 @@ +
+ + + + + + + %sender% + + + %time% + + + + + + + + +

%message%

+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..39b3c04 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,8 @@ + + + %time% + +

%message%

+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..c7b3a33 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,8 @@ + + + %time% + +

%message%

+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..c6c84ad Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..94b5a95 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,12 @@ +
+ + + + %time% + +

%message%

+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Left in, right out.css b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Left in, right out.css new file mode 100644 index 0000000..e4b3409 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Left in, right out.css @@ -0,0 +1,79 @@ +@import "../style.css"; + + +.post_incoming { + margin: 0 29px 0 36px; +} + +.post_outgoing { + margin: 0 36px 0 29px; +} + + +.toprow_incoming { + margin: -2px 3px 0 0; +} + +.toprow_outgoing { + margin: -2px 0 0 0; +} + + +.toprow_middle_incoming +{ + left: 10px; + right: 8px; +} + +.toprow_middle_outgoing +{ + left: 8px; + right: 10px; +} + + +span.buddy_icon_incoming +{ + float: left; +} + +span.buddy_icon_outgoing +{ + float: right; +} + +.bottom_right_incoming, .bottom_right_incoming_context { + left: 2px; + right: 11px; +} + +.bottom_right_outgoing, .bottom_right_outgoing_context { + left: 9px; + right: 2px; +} + + +.arrow_incoming, .arrow_incoming_context { + left:30px; +} + +.arrow_outgoing, .arrow_outgoing_context { + right:31px; +} + +.arrow_incoming { + background: url("../images/arrow.png"); +} + +.arrow_outgoing { + background: url("../images/arrow_right.png"); +} + + +.arrow_incoming_context { + background: url("../images/arrow_context.png"); +} + +.arrow_outgoing_context { + background: url("../images/arrow_right_context.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Right in, left out.css b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Right in, left out.css new file mode 100644 index 0000000..8790db1 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Right in, left out.css @@ -0,0 +1,80 @@ +@import "../style.css"; + + +.post_incoming { + margin: 0 36px 0 29px; +} + +.post_outgoing { + margin: 0 29px 0 36px; +} + + +.toprow_incoming { + margin: -2px 0 0 0; +} + +.toprow_outgoing { + margin: -2px 3px 0 0; +} + + +.toprow_middle_incoming +{ + left: 8px; + right: 10px; +} + +.toprow_middle_outgoing +{ + left: 10px; + right: 8px; +} + + +span.buddy_icon_incoming +{ + float: right; +} + +span.buddy_icon_outgoing +{ + float: left; +} + + +.bottom_right_incoming, .bottom_right_incoming_context { + left: 9px; + right: 2px; +} + +.bottom_right_outgoing, .bottom_right_outgoing_context { + left: 2px; + right: 11px; +} + + +.arrow_incoming, .arrow_incoming_context { + right:31px; +} + +.arrow_outgoing, .arrow_outgoing_context { + left:30px; +} + +.arrow_incoming { + background: url("../images/arrow_right.png"); +} + +.arrow_outgoing { + background: url("../images/arrow.png"); +} + + +.arrow_incoming_context { + background: url("../images/arrow_right_context.png"); +} + +.arrow_outgoing_context { + background: url("../images/arrow_context.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Right.css b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Right.css new file mode 100644 index 0000000..525069c --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/Variants/Right.css @@ -0,0 +1,39 @@ +@import "../style.css"; + + +.post_incoming, .post_outgoing { + margin: 0 36px 0 0; +} + +.toprow_incoming, .toprow_outgoing { + margin: -2px 3px 0 0; +} + +.toprow_middle_incoming, .toprow_middle_outgoing +{ + left: 8px; + right: 8px; +} + +span.buddy_icon_incoming, span.buddy_icon_outgoing +{ + float: right; +} + +.bottom_right_incoming, .bottom_right_incoming_context, .bottom_right_outgoing, .bottom_right_outgoing_context { + left: 9px; + right: 2px; +} + + +.arrow_incoming, .arrow_outgoing, .arrow_incoming_context, .arrow_outgoing_context { + right:31px; +} + +.arrow_incoming, .arrow_outgoing { + background: url("../images/arrow_right.png"); +} + +.arrow_incoming_context, .arrow_outgoing_context { + background: url("../images/arrow_right_context.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow.png new file mode 100644 index 0000000..24fc59c Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_context.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_context.png new file mode 100644 index 0000000..7ed1c2d Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_context.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_right.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_right.png new file mode 100644 index 0000000..96e4eed Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_right_context.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_right_context.png new file mode 100644 index 0000000..14281e4 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/arrow_right_context.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/background.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/background.png new file mode 100644 index 0000000..e281a74 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/background.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_left.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_left.png new file mode 100644 index 0000000..204abd0 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_left.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_left_context.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_left_context.png new file mode 100644 index 0000000..d084912 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_left_context.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_right.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_right.png new file mode 100644 index 0000000..56ed2cd Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_right_context.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_right_context.png new file mode 100644 index 0000000..4c09091 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/bottom_right_context.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/buddy_icon_background.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/buddy_icon_background.png new file mode 100644 index 0000000..069104e Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/buddy_icon_background.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_left.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_left.png new file mode 100644 index 0000000..dd44437 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_left.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_left_context.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_left_context.png new file mode 100644 index 0000000..55c6cb4 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_left_context.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_right.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_right.png new file mode 100644 index 0000000..cce0926 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_right_context.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_right_context.png new file mode 100644 index 0000000..2ff627f Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/top_right_context.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left.png new file mode 100644 index 0000000..29ff3c6 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_edge.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_edge.png new file mode 100644 index 0000000..54dd6fd Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_edge.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_middle.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_middle.png new file mode 100644 index 0000000..5dccbc5 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_middle.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_start.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_start.png new file mode 100644 index 0000000..2037bbe Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_left_start.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_middle.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_middle.png new file mode 100644 index 0000000..00a63d3 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_middle.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_right.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_right.png new file mode 100644 index 0000000..10dd23d Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_right.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_right_edge.png b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_right_edge.png new file mode 100644 index 0000000..3ba8cd8 Binary files /dev/null and b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/images/toprow_right_edge.png differ diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..0a5a2e4 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,39 @@ +@import "style.css"; + + +.post_incoming, .post_outgoing { + margin: 0 0 0 36px; +} + +.toprow_incoming, .toprow_outgoing { + margin: -2px 0 0 0; +} + +.toprow_middle_incoming, .toprow_middle_outgoing +{ + left: 10px; + right: 8px; +} + +span.buddy_icon_incoming, span.buddy_icon_outgoing +{ + float: left; +} + +.bottom_right_incoming, .bottom_right_incoming_context, .bottom_right_outgoing, .bottom_right_outgoing_context { + left: 2px; + right: 11px; +} + + +.arrow_incoming, .arrow_outgoing, .arrow_incoming_context, .arrow_outgoing_context { + left:30px; +} + +.arrow_incoming, .arrow_outgoing { + background: url("images/arrow.png"); +} + +.arrow_incoming_context, .arrow_outgoing_context { + background: url("images/arrow_context.png"); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/style.css b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/style.css new file mode 100644 index 0000000..542a376 --- /dev/null +++ b/digsby/res/MessageStyles/MiniBubble2.AdiumMessageStyle/Contents/Resources/style.css @@ -0,0 +1,274 @@ +body { + font-family: Lucida Grande; + font-size: 10px; + + background: #fff url("images/background.png") bottom left fixed; + + margin: 4px; + margin-left: 10px; +} + +span.block +{ + display: block; +} + +.post_container { + clear: both; + margin: 0 10px 0px -4px; + + position: relative; +} + +.post_incoming, .post_outgoing { + padding: 5px 5px 3px 5px; +} + +.bottom_right_status { + position: relative; + top: 4px; + left: 2px; + right: 11px; + + margin-bottom: 4px; +} + +.post_message +{ + color:#000; + background: #fff; + + margin: 4px 0 0 0; + padding: 3px; +} + +span.buddy_icon_incoming, span.buddy_icon_outgoing +{ + height: 32px; + width: 32px; + background: url("images/buddy_icon_background.png") top left; + + padding: 5px 6px 9px 5px; + margin: 0; + + position:relative; +} + +img.buddy_icon_incoming, img.buddy_icon_outgoing +{ + height: 32px; + width: 32px; + background: #F0F0F0; +} + +.toprow_incoming, .toprow_outgoing { + color: #fff; + + font-family: Lucida Grande; + font-size: 9px; + line-height: 11px; + font-weight:bold; + + height: 12px; + position: relative; + + white-space: nowrap; +} + +.toprow_middle_incoming, .toprow_middle_outgoing +{ + position: absolute; + top: 0px; + + height: 12px; + z-index: -1; + + float: left; + + background: url("images/toprow_middle.png") top left; + + +} + +.toprow_name_start +{ + float:left; + background: url("images/toprow_left_start.png") top right; + + height:12px; + width:8px; + + padding: 0; + margin: 0; +} +.toprow_name +{ + float: left; + + text-overflow: ellipsis; + + background: url("images/toprow_left_middle.png") top left; + + + max-width: 65%; + height: 12px; + z-index: 2; + + padding: 0; + margin: 0; + + overflow: hidden; + white-space: nowrap; +} + +.toprow_name_end +{ + float:left; + background: url("images/toprow_right.png") top right; + + height:12px; + width:2px; + + padding: 0 0 0 4px; +} + +.toprow_timestamp +{ + float: right; + background: url("images/toprow_right_edge.png") top right; + + font-size: 8px; + + height: 11px; + z-index: 2; + + padding: 1px 6px 0 0; +} + +.toprow_timestamp_end +{ + float: right; + background: url("images/toprow_left.png") top left; + + height: 12px; + width: 4px; + + padding: 0 2px 0 0; +} + + + + + +.bottom_right_incoming, .bottom_right_outgoing, .bottom_right_incoming_context, .bottom_right_outgoing_context { + position: relative; + top: 4px; + + margin-bottom: 4px; +} + +.bottom_right_incoming, .bottom_right_outgoing { + background: url("images/bottom_right.png") bottom right no-repeat; +} + +.bottom_right_incoming_context, .bottom_right_outgoing_context { + background: url("images/bottom_right_context.png") bottom right no-repeat; +} + +.bottom_left, .bottom_left_context { + position: relative; + top: 0px; + left: -12px; + + padding: 0 26px 10px 8px; + color: #333333; +} + +.bottom_left { + background: url("images/bottom_left.png") bottom left repeat-y; +} + +.bottom_left_context { + background: url("images/bottom_left_context.png") bottom left repeat-y; +} + +.top_left, .top_left_context { + position: absolute; + top: -4px; + left: -9px; + right: 10px; + height: 4px; + + padding: 0; +} + +.top_left { + background: url("images/top_left.png") top left no-repeat; +} + +.top_left_context { + background: url("images/top_left_context.png") top left no-repeat; +} + +.top_right, .top_right_context { + position: absolute; + right: 3px; + top: -4px; + + color: #888888; + + font-family: Lucida Grande; + font-size: 9px; + + padding: 4px 6px 0 4px; +} + +.top_right { + background: url("images/top_right.png") top right no-repeat; +} + +.top_right_context { + background: url("images/top_right_context.png") top right no-repeat; +} + +p +{ + padding: 0; + margin: 1px 10px 0 0; + overflow: auto; +} + +/*.arrow_incoming, .arrow_incoming_context { + left:30px; +} + +.arrow_incoming { + background: url("images/arrow.png"); +} + +.arrow_incoming_context { + background: url("images/arrow_context.png"); +} + +.arrow_outgoing { + left:30px; + + background: url("images/arrow.png"); +} */ + +.arrow_incoming, .arrow_outgoing, .arrow_incoming_context, .arrow_outgoing_context { + position: absolute; + top: 28px; + + height: 4px; + width: 5px; + + z-index: 2; + + background: #f00; +} + + +p img { + vertical-align: middle; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..bf33f81 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + Modern Bubbling Adium Message Style + CFBundleIdentifier + com.adiumx.modern.bubbling.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + Modern Bubbling + CFBundlePackageType + AdIM + DefaultBackgroundColor + FFFFFF + DefaultFontFamily + Helvetica + DefaultFontSize + 11 + DefaultVariant + Glass (Glass) + DisableCustomBackground + + MessageViewVersion + 3 + ShowsUserIcons + + + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..a45946d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Footer.html @@ -0,0 +1,5 @@ + + + + + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..8e51ae6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Header.html @@ -0,0 +1,20 @@ + + \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..3e03b74 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..cfc371b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..3e03b74 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..cfc371b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..36f3c06 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..4c34950 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..63b32b5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..4c34950 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..63b32b5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,22 @@ + +
+
+
%sender%
+
%service%
+
+
+
+
+
+
%time%
+
%message%
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..36f3c06 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..0748779 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,4 @@ +
+
%message%
+
%time%
+
diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Template.html b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Template.html new file mode 100644 index 0000000..e3868c1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Template.html @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + +%@ +
+
+%@ + + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Blue).css new file mode 100644 index 0000000..8a23015 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Cyan).css new file mode 100644 index 0000000..09d9721 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Glass).css new file mode 100644 index 0000000..928c939 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Graphite).css new file mode 100644 index 0000000..9506519 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Green).css new file mode 100644 index 0000000..33d858a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Grey).css new file mode 100644 index 0000000..ef71bd1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Lime).css new file mode 100644 index 0000000..440a5fb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Orange).css new file mode 100644 index 0000000..8249826 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Pink).css new file mode 100644 index 0000000..c202c36 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Purple).css new file mode 100644 index 0000000..8d645de --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Red).css new file mode 100644 index 0000000..49bfd72 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Yellow).css new file mode 100644 index 0000000..5866de5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua).css new file mode 100644 index 0000000..d9b46a7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-aqua.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Aqua).css new file mode 100644 index 0000000..25a55a8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Cyan).css new file mode 100644 index 0000000..b44d697 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Glass).css new file mode 100644 index 0000000..daea0a4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Graphite).css new file mode 100644 index 0000000..393933b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Green).css new file mode 100644 index 0000000..210bad9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Grey).css new file mode 100644 index 0000000..abbfe0a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Lime).css new file mode 100644 index 0000000..b89e7a8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Orange).css new file mode 100644 index 0000000..1075a3d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Pink).css new file mode 100644 index 0000000..7b15310 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Purple).css new file mode 100644 index 0000000..644b505 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Red).css new file mode 100644 index 0000000..9b36d6a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Yellow).css new file mode 100644 index 0000000..f1303f7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue).css new file mode 100644 index 0000000..a0a315f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-blue.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Aqua).css new file mode 100644 index 0000000..b24e1b1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Blue).css new file mode 100644 index 0000000..cd15781 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Glass).css new file mode 100644 index 0000000..50dd184 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Graphite).css new file mode 100644 index 0000000..c90e193 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Green).css new file mode 100644 index 0000000..a6221fa --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Grey).css new file mode 100644 index 0000000..7979332 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Lime).css new file mode 100644 index 0000000..c44e7b9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Orange).css new file mode 100644 index 0000000..3508833 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Pink).css new file mode 100644 index 0000000..2ce2a5e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Purple).css new file mode 100644 index 0000000..f6a5ea3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Red).css new file mode 100644 index 0000000..049533a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Yellow).css new file mode 100644 index 0000000..9a695b4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan).css new file mode 100644 index 0000000..5868205 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-cyan.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Aqua).css new file mode 100644 index 0000000..9710f38 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Blue).css new file mode 100644 index 0000000..d849107 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Cyan).css new file mode 100644 index 0000000..a69cc87 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Graphite).css new file mode 100644 index 0000000..2ff6b75 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Green).css new file mode 100644 index 0000000..4251eb6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Grey).css new file mode 100644 index 0000000..046fad1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Lime).css new file mode 100644 index 0000000..0093e24 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Orange).css new file mode 100644 index 0000000..8cbb1cb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Pink).css new file mode 100644 index 0000000..fff9461 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Purple).css new file mode 100644 index 0000000..f109d62 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Red).css new file mode 100644 index 0000000..7fe44cf --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Yellow).css new file mode 100644 index 0000000..8250127 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass).css new file mode 100644 index 0000000..8e0ed32 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-glass.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Aqua).css new file mode 100644 index 0000000..a51fa50 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Blue).css new file mode 100644 index 0000000..fe2eb28 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Cyan).css new file mode 100644 index 0000000..0fd13ec --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Glass).css new file mode 100644 index 0000000..e4d23ec --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Green).css new file mode 100644 index 0000000..eef9233 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Grey).css new file mode 100644 index 0000000..11c2fd6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Lime).css new file mode 100644 index 0000000..0ac6b93 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Orange).css new file mode 100644 index 0000000..0c0c9c1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Pink).css new file mode 100644 index 0000000..faa8ade --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Purple).css new file mode 100644 index 0000000..30c2a0d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Red).css new file mode 100644 index 0000000..93699c3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Yellow).css new file mode 100644 index 0000000..0b16621 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite).css new file mode 100644 index 0000000..c903947 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-graphite.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Aqua).css new file mode 100644 index 0000000..c87cccb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Blue).css new file mode 100644 index 0000000..78255e9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Cyan).css new file mode 100644 index 0000000..31d4128 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Glass).css new file mode 100644 index 0000000..6fedc03 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Graphite).css new file mode 100644 index 0000000..5fd9f8f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Grey).css new file mode 100644 index 0000000..a4211d7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Lime).css new file mode 100644 index 0000000..fa9043c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Orange).css new file mode 100644 index 0000000..386ad77 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Pink).css new file mode 100644 index 0000000..573cd50 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Purple).css new file mode 100644 index 0000000..7860c18 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Red).css new file mode 100644 index 0000000..4dc4258 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Yellow).css new file mode 100644 index 0000000..e595dad --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green).css new file mode 100644 index 0000000..70d9e8e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-green.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Aqua).css new file mode 100644 index 0000000..1229622 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Blue).css new file mode 100644 index 0000000..134dd02 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Cyan).css new file mode 100644 index 0000000..be81e28 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Glass).css new file mode 100644 index 0000000..252592f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Graphite).css new file mode 100644 index 0000000..44a7a0a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Green).css new file mode 100644 index 0000000..e7ca087 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Lime).css new file mode 100644 index 0000000..5938a4c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Orange).css new file mode 100644 index 0000000..92a0fc7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Pink).css new file mode 100644 index 0000000..603e35b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Purple).css new file mode 100644 index 0000000..bd2b5e4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Red).css new file mode 100644 index 0000000..18bd2b2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Yellow).css new file mode 100644 index 0000000..3e51516 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey).css new file mode 100644 index 0000000..cb772bf --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-grey.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Aqua).css new file mode 100644 index 0000000..480a886 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Blue).css new file mode 100644 index 0000000..d733290 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Cyan).css new file mode 100644 index 0000000..042264a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Glass).css new file mode 100644 index 0000000..7c9b4a0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Graphite).css new file mode 100644 index 0000000..d2025c9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Green).css new file mode 100644 index 0000000..9ff8090 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Grey).css new file mode 100644 index 0000000..ada3497 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Orange).css new file mode 100644 index 0000000..9eef735 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Pink).css new file mode 100644 index 0000000..dda8499 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Purple).css new file mode 100644 index 0000000..61e0ff9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Red).css new file mode 100644 index 0000000..4db88f0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Yellow).css new file mode 100644 index 0000000..07d243e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime).css new file mode 100644 index 0000000..59830ce --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-lime.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Aqua).css new file mode 100644 index 0000000..a320ce6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Blue).css new file mode 100644 index 0000000..6b3d61b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Cyan).css new file mode 100644 index 0000000..cdf1dd3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Glass).css new file mode 100644 index 0000000..2c5ea6c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Graphite).css new file mode 100644 index 0000000..b13ec1a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Green).css new file mode 100644 index 0000000..36f0388 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Grey).css new file mode 100644 index 0000000..627b378 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Lime).css new file mode 100644 index 0000000..79e024f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Pink).css new file mode 100644 index 0000000..e0c0595 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Purple).css new file mode 100644 index 0000000..349267b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Red).css new file mode 100644 index 0000000..179d5da --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Yellow).css new file mode 100644 index 0000000..c2da08e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange).css new file mode 100644 index 0000000..0173ea4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-orange.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Aqua).css new file mode 100644 index 0000000..d608236 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Blue).css new file mode 100644 index 0000000..b075852 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Cyan).css new file mode 100644 index 0000000..90e29ec --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Glass).css new file mode 100644 index 0000000..21b3f92 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Graphite).css new file mode 100644 index 0000000..8685325 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Green).css new file mode 100644 index 0000000..9228f1a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Grey).css new file mode 100644 index 0000000..80fc5f9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Lime).css new file mode 100644 index 0000000..15afe9e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Orange).css new file mode 100644 index 0000000..8243b28 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Purple).css new file mode 100644 index 0000000..a954119 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Red).css new file mode 100644 index 0000000..2841eb0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Yellow).css new file mode 100644 index 0000000..1ffa473 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink).css new file mode 100644 index 0000000..f08f910 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-pink.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Aqua).css new file mode 100644 index 0000000..a568683 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Blue).css new file mode 100644 index 0000000..c54e7a5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Cyan).css new file mode 100644 index 0000000..09bd03b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Glass).css new file mode 100644 index 0000000..600c560 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Graphite).css new file mode 100644 index 0000000..1d8385e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Green).css new file mode 100644 index 0000000..198002b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Grey).css new file mode 100644 index 0000000..67304b0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Lime).css new file mode 100644 index 0000000..e92848a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Orange).css new file mode 100644 index 0000000..fb07713 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Pink).css new file mode 100644 index 0000000..ebcc90f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Red).css new file mode 100644 index 0000000..3e7c7ce --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Yellow).css new file mode 100644 index 0000000..2b9136b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple).css new file mode 100644 index 0000000..379e779 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-purple.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Aqua).css new file mode 100644 index 0000000..9550b43 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Blue).css new file mode 100644 index 0000000..1c8969b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Cyan).css new file mode 100644 index 0000000..0cdd92b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Glass).css new file mode 100644 index 0000000..c44cedb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Graphite).css new file mode 100644 index 0000000..79a1078 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Green).css new file mode 100644 index 0000000..5f769d6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Grey).css new file mode 100644 index 0000000..b4be5e3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Lime).css new file mode 100644 index 0000000..441e1a0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Orange).css new file mode 100644 index 0000000..17c4ed4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Pink).css new file mode 100644 index 0000000..d34d340 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Purple).css new file mode 100644 index 0000000..1396259 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Yellow).css new file mode 100644 index 0000000..2917f24 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red).css new file mode 100644 index 0000000..e6b6115 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-red.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Aqua).css new file mode 100644 index 0000000..09ced9e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Blue).css new file mode 100644 index 0000000..86e3798 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Cyan).css new file mode 100644 index 0000000..cba2d78 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Glass).css new file mode 100644 index 0000000..70df837 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Graphite).css new file mode 100644 index 0000000..bc25aa3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Green).css new file mode 100644 index 0000000..240c8aa --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Grey).css new file mode 100644 index 0000000..589b70f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Lime).css new file mode 100644 index 0000000..6a2a521 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Orange).css new file mode 100644 index 0000000..1e3c581 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Pink).css new file mode 100644 index 0000000..545ef1e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Purple).css new file mode 100644 index 0000000..6cd39d2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Red).css new file mode 100644 index 0000000..e1351e2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow).css new file mode 100644 index 0000000..d70bf5a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Glass (Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); +@import url("../styles/alternative/_glass.css"); + +@import url("../styles/alternative/glass-out-yellow.css"); +@import url("../styles/alternative/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Modern.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Modern.css new file mode 100644 index 0000000..31b00bf --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative Modern.css @@ -0,0 +1,2 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative White Bubbling.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative White Bubbling.css new file mode 100644 index 0000000..63b9f73 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative White Bubbling.css @@ -0,0 +1,4 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); + +@import url("../styles/alternative/_white-bubbling.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative White.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative White.css new file mode 100644 index 0000000..bf80e57 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Alternative White.css @@ -0,0 +1,4 @@ +@import url("../main.css"); +@import url("../styles/alternative/_default.css"); + +@import url("../styles/alternative/_white.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Blue).css new file mode 100644 index 0000000..ec1e3ae --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Cyan).css new file mode 100644 index 0000000..40ed51b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Glass).css new file mode 100644 index 0000000..1af835a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Graphite).css new file mode 100644 index 0000000..adc12c9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Green).css new file mode 100644 index 0000000..4f1c7e8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Grey).css new file mode 100644 index 0000000..d9928be --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Lime).css new file mode 100644 index 0000000..b37c297 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Orange).css new file mode 100644 index 0000000..c9b0834 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Pink).css new file mode 100644 index 0000000..f2fa73e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Purple).css new file mode 100644 index 0000000..b7177d0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Red).css new file mode 100644 index 0000000..5faa82d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Yellow).css new file mode 100644 index 0000000..3c4fc0c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua).css new file mode 100644 index 0000000..4123f8b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-aqua.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Aqua).css new file mode 100644 index 0000000..0aa0789 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Cyan).css new file mode 100644 index 0000000..9e4954b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Glass).css new file mode 100644 index 0000000..01812fe --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Graphite).css new file mode 100644 index 0000000..9ce93c1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Green).css new file mode 100644 index 0000000..5867adb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Grey).css new file mode 100644 index 0000000..45c34bf --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Lime).css new file mode 100644 index 0000000..2362491 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Orange).css new file mode 100644 index 0000000..08de2c9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Pink).css new file mode 100644 index 0000000..ef23eb1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Purple).css new file mode 100644 index 0000000..0f1c7d8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Red).css new file mode 100644 index 0000000..f52ae74 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Yellow).css new file mode 100644 index 0000000..7057c12 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue).css new file mode 100644 index 0000000..5b95479 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-blue.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Aqua).css new file mode 100644 index 0000000..6855a55 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Blue).css new file mode 100644 index 0000000..db2a3ae --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Glass).css new file mode 100644 index 0000000..63447b5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Graphite).css new file mode 100644 index 0000000..ff935de --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Green).css new file mode 100644 index 0000000..5b3bad6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Grey).css new file mode 100644 index 0000000..f91f115 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Lime).css new file mode 100644 index 0000000..27100b0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Orange).css new file mode 100644 index 0000000..d5c6662 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Pink).css new file mode 100644 index 0000000..a0863db --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Purple).css new file mode 100644 index 0000000..4c1697a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Red).css new file mode 100644 index 0000000..c8a9c2e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Yellow).css new file mode 100644 index 0000000..e60961d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan).css new file mode 100644 index 0000000..3de20fb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-cyan.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Aqua).css new file mode 100644 index 0000000..1aefc54 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Blue).css new file mode 100644 index 0000000..4ba64b9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Cyan).css new file mode 100644 index 0000000..09defe7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Graphite).css new file mode 100644 index 0000000..efea79a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Green).css new file mode 100644 index 0000000..fa204d2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Grey).css new file mode 100644 index 0000000..ee5bc6b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Lime).css new file mode 100644 index 0000000..3d976d3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Orange).css new file mode 100644 index 0000000..acf462d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Pink).css new file mode 100644 index 0000000..25f8746 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Purple).css new file mode 100644 index 0000000..a1a1942 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Red).css new file mode 100644 index 0000000..2b23c31 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Yellow).css new file mode 100644 index 0000000..ba9699d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass).css new file mode 100644 index 0000000..4086c01 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-glass.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Aqua).css new file mode 100644 index 0000000..db5a9a3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Blue).css new file mode 100644 index 0000000..0430cf6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Cyan).css new file mode 100644 index 0000000..122104a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Glass).css new file mode 100644 index 0000000..29d368a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Green).css new file mode 100644 index 0000000..0e27235 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Grey).css new file mode 100644 index 0000000..e0160d2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Lime).css new file mode 100644 index 0000000..5ef9d8a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Orange).css new file mode 100644 index 0000000..043490a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Pink).css new file mode 100644 index 0000000..7d9dbcd --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Purple).css new file mode 100644 index 0000000..14e914e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Red).css new file mode 100644 index 0000000..3a9d7e8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Yellow).css new file mode 100644 index 0000000..f39b456 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite).css new file mode 100644 index 0000000..a2960eb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-graphite.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Aqua).css new file mode 100644 index 0000000..80667f1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Blue).css new file mode 100644 index 0000000..d7a8fae --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Cyan).css new file mode 100644 index 0000000..522aa72 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Glass).css new file mode 100644 index 0000000..f148ec2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Graphite).css new file mode 100644 index 0000000..2d87719 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Grey).css new file mode 100644 index 0000000..fe50376 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Lime).css new file mode 100644 index 0000000..b80b2c4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Orange).css new file mode 100644 index 0000000..b904648 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Pink).css new file mode 100644 index 0000000..c464aa1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Purple).css new file mode 100644 index 0000000..dd41d8a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Red).css new file mode 100644 index 0000000..b3589b2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Yellow).css new file mode 100644 index 0000000..1600286 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green).css new file mode 100644 index 0000000..05de383 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-green.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Aqua).css new file mode 100644 index 0000000..5150c46 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Blue).css new file mode 100644 index 0000000..891dbfe --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Cyan).css new file mode 100644 index 0000000..67d0f96 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Glass).css new file mode 100644 index 0000000..1053302 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Graphite).css new file mode 100644 index 0000000..3d54e0d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Green).css new file mode 100644 index 0000000..d8253eb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Lime).css new file mode 100644 index 0000000..233af44 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Orange).css new file mode 100644 index 0000000..2ad63cb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Pink).css new file mode 100644 index 0000000..f991abf --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Purple).css new file mode 100644 index 0000000..cddd4b4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Red).css new file mode 100644 index 0000000..d6f86d6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Yellow).css new file mode 100644 index 0000000..fe376ea --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey).css new file mode 100644 index 0000000..587eb8e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-grey.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Aqua).css new file mode 100644 index 0000000..2e9dd4b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Blue).css new file mode 100644 index 0000000..95fb198 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Cyan).css new file mode 100644 index 0000000..bb4b49c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Glass).css new file mode 100644 index 0000000..68f4263 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Graphite).css new file mode 100644 index 0000000..ac5d471 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Green).css new file mode 100644 index 0000000..e80b533 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Grey).css new file mode 100644 index 0000000..f6fcbba --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Orange).css new file mode 100644 index 0000000..b078605 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Pink).css new file mode 100644 index 0000000..4d28560 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Purple).css new file mode 100644 index 0000000..8e6d92d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Red).css new file mode 100644 index 0000000..b9979c8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Yellow).css new file mode 100644 index 0000000..fedab12 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime).css new file mode 100644 index 0000000..a6db580 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-lime.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Aqua).css new file mode 100644 index 0000000..f4ce380 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Blue).css new file mode 100644 index 0000000..a047ecf --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Cyan).css new file mode 100644 index 0000000..7f4cd5c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Glass).css new file mode 100644 index 0000000..e7a317a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Graphite).css new file mode 100644 index 0000000..eadc229 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Green).css new file mode 100644 index 0000000..0c5d179 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Grey).css new file mode 100644 index 0000000..abbcf46 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Lime).css new file mode 100644 index 0000000..c1821ac --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Pink).css new file mode 100644 index 0000000..74d1234 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Purple).css new file mode 100644 index 0000000..31a80d1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Red).css new file mode 100644 index 0000000..b8fed7f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Yellow).css new file mode 100644 index 0000000..345413d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange).css new file mode 100644 index 0000000..b1ec935 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-orange.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Aqua).css new file mode 100644 index 0000000..1337327 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Blue).css new file mode 100644 index 0000000..5371a7b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Cyan).css new file mode 100644 index 0000000..23ffd35 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Glass).css new file mode 100644 index 0000000..0316500 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Graphite).css new file mode 100644 index 0000000..99be849 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Green).css new file mode 100644 index 0000000..1f2827f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Grey).css new file mode 100644 index 0000000..8b31a93 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Lime).css new file mode 100644 index 0000000..9330346 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Orange).css new file mode 100644 index 0000000..d629a3a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Purple).css new file mode 100644 index 0000000..4fbb9a4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Red).css new file mode 100644 index 0000000..2caaa5b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Yellow).css new file mode 100644 index 0000000..c78ae2a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink).css new file mode 100644 index 0000000..1af2921 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-pink.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Aqua).css new file mode 100644 index 0000000..bc44cd2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Blue).css new file mode 100644 index 0000000..fe3ee9f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Cyan).css new file mode 100644 index 0000000..3a68d73 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Glass).css new file mode 100644 index 0000000..28b5109 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Graphite).css new file mode 100644 index 0000000..f8022ea --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Green).css new file mode 100644 index 0000000..5dc0bc7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Grey).css new file mode 100644 index 0000000..70b62ac --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Lime).css new file mode 100644 index 0000000..c8bd145 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Orange).css new file mode 100644 index 0000000..66b80aa --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Pink).css new file mode 100644 index 0000000..e228cd0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Red).css new file mode 100644 index 0000000..f0ce9f1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Yellow).css new file mode 100644 index 0000000..e2ac94c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple).css new file mode 100644 index 0000000..e310c0a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-purple.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Aqua).css new file mode 100644 index 0000000..c77a483 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Blue).css new file mode 100644 index 0000000..74eed57 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Cyan).css new file mode 100644 index 0000000..72ab570 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Glass).css new file mode 100644 index 0000000..ed23978 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Graphite).css new file mode 100644 index 0000000..f25d753 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Green).css new file mode 100644 index 0000000..0daf662 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Grey).css new file mode 100644 index 0000000..b458b49 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Lime).css new file mode 100644 index 0000000..6984c08 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Orange).css new file mode 100644 index 0000000..5a32d1b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Pink).css new file mode 100644 index 0000000..f0f74a5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Purple).css new file mode 100644 index 0000000..cfc8230 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Yellow).css new file mode 100644 index 0000000..6ea4ad5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red & Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red).css new file mode 100644 index 0000000..784b637 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-red.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Aqua).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Aqua).css new file mode 100644 index 0000000..e68952d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Aqua).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-aqua.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Blue).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Blue).css new file mode 100644 index 0000000..985d5f8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Blue).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-blue.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Cyan).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Cyan).css new file mode 100644 index 0000000..d0650a0 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Cyan).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-cyan.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Glass).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Glass).css new file mode 100644 index 0000000..e45fac7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Glass).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-glass.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Graphite).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Graphite).css new file mode 100644 index 0000000..9c9cc03 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Graphite).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-graphite.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Green).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Green).css new file mode 100644 index 0000000..37a1fb1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Green).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-green.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Grey).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Grey).css new file mode 100644 index 0000000..8290d55 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Grey).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-grey.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Lime).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Lime).css new file mode 100644 index 0000000..18be31b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Lime).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-lime.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Orange).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Orange).css new file mode 100644 index 0000000..18d8bec --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Orange).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-orange.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Pink).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Pink).css new file mode 100644 index 0000000..a438686 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Pink).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-pink.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Purple).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Purple).css new file mode 100644 index 0000000..9ce94c6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Purple).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-purple.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Red).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Red).css new file mode 100644 index 0000000..183c80b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow & Red).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-red.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow).css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow).css new file mode 100644 index 0000000..df47976 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Glass (Yellow).css @@ -0,0 +1,6 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); +@import url("../styles/normal/_glass.css"); + +@import url("../styles/normal/glass-out-yellow.css"); +@import url("../styles/normal/glass-in-yellow.css"); diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Modern.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Modern.css new file mode 100644 index 0000000..cd55c5d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/Modern.css @@ -0,0 +1,2 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/White Bubbling.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/White Bubbling.css new file mode 100644 index 0000000..b2eec09 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/White Bubbling.css @@ -0,0 +1,4 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); + +@import url("../styles/normal/_white-bubbling.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/White.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/White.css new file mode 100644 index 0000000..26c379a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/Variants/White.css @@ -0,0 +1,4 @@ +@import url("../main.css"); +@import url("../styles/normal/_default.css"); + +@import url("../styles/normal/_white.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/glass.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/glass.png new file mode 100644 index 0000000..8a8e823 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/glass.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/tiger.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/tiger.png new file mode 100644 index 0000000..d98c9c5 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/tiger.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-hide-hover.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-hide-hover.png new file mode 100644 index 0000000..babf615 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-hide-hover.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-hide.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-hide.png new file mode 100644 index 0000000..f8d7d1e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-hide.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-show-hover.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-show-hover.png new file mode 100644 index 0000000..3d96074 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-show-hover.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-show.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-show.png new file mode 100644 index 0000000..a4da87b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/_headers/toggle-show.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/bg-white.jpg b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/bg-white.jpg new file mode 100644 index 0000000..1b40874 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/bg-white.jpg differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/bg.jpg b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/bg.jpg new file mode 100644 index 0000000..4c23b45 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/bg.jpg differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottom.png new file mode 100644 index 0000000..bc8626f Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomleft-arrow.png new file mode 100644 index 0000000..8a7fb7a Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomleft.png new file mode 100644 index 0000000..192e48f Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomright-arrow.png new file mode 100644 index 0000000..3312a08 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomright.png new file mode 100644 index 0000000..151cecd Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/left.png new file mode 100644 index 0000000..3d7a724 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/middle.png new file mode 100644 index 0000000..a51d6e5 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/right.png new file mode 100644 index 0000000..f924ef2 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/top.png new file mode 100644 index 0000000..1badb46 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/topleft.png new file mode 100644 index 0000000..f90f28e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/topright.png new file mode 100644 index 0000000..7b3eb0c Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-aqua/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottom.png new file mode 100644 index 0000000..4311914 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomleft-arrow.png new file mode 100644 index 0000000..3bd0a69 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomleft.png new file mode 100644 index 0000000..9247caf Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomright-arrow.png new file mode 100644 index 0000000..e0e3e7e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomright.png new file mode 100644 index 0000000..8e7e6f7 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/left.png new file mode 100644 index 0000000..dccfd5f Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/middle.png new file mode 100644 index 0000000..a94fbf9 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/right.png new file mode 100644 index 0000000..97bc330 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/top.png new file mode 100644 index 0000000..df93672 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/topleft.png new file mode 100644 index 0000000..3697a13 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/topright.png new file mode 100644 index 0000000..b5fbe45 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-blue/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottom.png new file mode 100644 index 0000000..9f9b51c Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomleft-arrow.png new file mode 100644 index 0000000..009bbab Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomleft.png new file mode 100644 index 0000000..09df8a0 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomright-arrow.png new file mode 100644 index 0000000..13fa8c3 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomright.png new file mode 100644 index 0000000..f8f8937 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/left.png new file mode 100644 index 0000000..c0e1dce Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/middle.png new file mode 100644 index 0000000..ffb9266 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/right.png new file mode 100644 index 0000000..e98e5d4 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/top.png new file mode 100644 index 0000000..132ad36 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/topleft.png new file mode 100644 index 0000000..b2bfbfb Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/topright.png new file mode 100644 index 0000000..a09b52a Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-cyan/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottom.png new file mode 100644 index 0000000..f652617 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomleft-arrow.png new file mode 100644 index 0000000..bc2f206 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomleft.png new file mode 100644 index 0000000..582feb3 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomright-arrow.png new file mode 100644 index 0000000..7f8b7d0 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomright.png new file mode 100644 index 0000000..c2266d8 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/left.png new file mode 100644 index 0000000..d1925a8 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/middle.png new file mode 100644 index 0000000..982375d Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/right.png new file mode 100644 index 0000000..6b1d917 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/top.png new file mode 100644 index 0000000..002bcda Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/topleft.png new file mode 100644 index 0000000..2fee67b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/topright.png new file mode 100644 index 0000000..6e464e0 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-graphite/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottom.png new file mode 100644 index 0000000..73ff7a4 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomleft-arrow.png new file mode 100644 index 0000000..f6e4e7d Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomleft.png new file mode 100644 index 0000000..ee132ea Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomright-arrow.png new file mode 100644 index 0000000..7ccdaee Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomright.png new file mode 100644 index 0000000..759de2b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/left.png new file mode 100644 index 0000000..7b95f46 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/middle.png new file mode 100644 index 0000000..576ac7a Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/right.png new file mode 100644 index 0000000..a452557 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/top.png new file mode 100644 index 0000000..187f995 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/topleft.png new file mode 100644 index 0000000..e0a5203 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/topright.png new file mode 100644 index 0000000..7f1c113 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-green/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottom.png new file mode 100644 index 0000000..3f1fe88 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomleft-arrow.png new file mode 100644 index 0000000..10b13e3 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomleft.png new file mode 100644 index 0000000..f9ed305 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomright-arrow.png new file mode 100644 index 0000000..81438b2 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomright.png new file mode 100644 index 0000000..1b350b6 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/left.png new file mode 100644 index 0000000..fde13d5 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/middle.png new file mode 100644 index 0000000..46bfa23 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/right.png new file mode 100644 index 0000000..2502865 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/top.png new file mode 100644 index 0000000..a8b5b33 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/topleft.png new file mode 100644 index 0000000..d8c34c3 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/topright.png new file mode 100644 index 0000000..0ecc854 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-grey/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottom.png new file mode 100644 index 0000000..efe3f77 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomleft-arrow.png new file mode 100644 index 0000000..3facef4 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomleft.png new file mode 100644 index 0000000..609ec44 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomright-arrow.png new file mode 100644 index 0000000..d7b2cb3 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomright.png new file mode 100644 index 0000000..37a2eaf Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/left.png new file mode 100644 index 0000000..b804af7 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/middle.png new file mode 100644 index 0000000..1c1e6ca Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/right.png new file mode 100644 index 0000000..c9e63c0 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/top.png new file mode 100644 index 0000000..ffd4455 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/topleft.png new file mode 100644 index 0000000..dc0c2f5 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/topright.png new file mode 100644 index 0000000..4a5bf29 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-lime/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottom.png new file mode 100644 index 0000000..2d00551 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomleft-arrow.png new file mode 100644 index 0000000..57cdd09 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomleft.png new file mode 100644 index 0000000..9445749 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomright-arrow.png new file mode 100644 index 0000000..1c5c20f Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomright.png new file mode 100644 index 0000000..dbcee7d Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/left.png new file mode 100644 index 0000000..f574b39 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/middle.png new file mode 100644 index 0000000..4155218 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/right.png new file mode 100644 index 0000000..2ca625d Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/top.png new file mode 100644 index 0000000..1204f12 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/topleft.png new file mode 100644 index 0000000..2974b72 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/topright.png new file mode 100644 index 0000000..68d321b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-orange/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottom.png new file mode 100644 index 0000000..d67ed3a Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomleft-arrow.png new file mode 100644 index 0000000..af5ad00 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomleft.png new file mode 100644 index 0000000..5182631 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomright-arrow.png new file mode 100644 index 0000000..ad8d0c6 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomright.png new file mode 100644 index 0000000..16b118d Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/left.png new file mode 100644 index 0000000..f814ccf Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/middle.png new file mode 100644 index 0000000..0fb5764 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/right.png new file mode 100644 index 0000000..09cf606 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/top.png new file mode 100644 index 0000000..6bb326e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/topleft.png new file mode 100644 index 0000000..2c80e04 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/topright.png new file mode 100644 index 0000000..4716d48 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-pink/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottom.png new file mode 100644 index 0000000..d825773 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomleft-arrow.png new file mode 100644 index 0000000..b48f0d1 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomleft.png new file mode 100644 index 0000000..82bc51b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomright-arrow.png new file mode 100644 index 0000000..72f9602 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomright.png new file mode 100644 index 0000000..e0deeae Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/left.png new file mode 100644 index 0000000..b172f0e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/middle.png new file mode 100644 index 0000000..0a9b3f7 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/right.png new file mode 100644 index 0000000..95a6f86 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/top.png new file mode 100644 index 0000000..91f062f Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/topleft.png new file mode 100644 index 0000000..66e368e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/topright.png new file mode 100644 index 0000000..53c36de Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-purple/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottom.png new file mode 100644 index 0000000..f218e61 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomleft-arrow.png new file mode 100644 index 0000000..929d7e9 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomleft.png new file mode 100644 index 0000000..4dd8f19 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomright-arrow.png new file mode 100644 index 0000000..59b1bb8 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomright.png new file mode 100644 index 0000000..ad596b0 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/left.png new file mode 100644 index 0000000..3e4a713 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/middle.png new file mode 100644 index 0000000..1e9a28b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/right.png new file mode 100644 index 0000000..184c342 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/top.png new file mode 100644 index 0000000..cfbf0f1 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/topleft.png new file mode 100644 index 0000000..2e66fe5 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/topright.png new file mode 100644 index 0000000..8ddf907 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-red/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottom.png new file mode 100644 index 0000000..c48a80e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomleft-arrow.png new file mode 100644 index 0000000..5a3a8a1 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomleft.png new file mode 100644 index 0000000..8b9e071 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomright-arrow.png new file mode 100644 index 0000000..3a2644e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomright.png new file mode 100644 index 0000000..884b149 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/left.png new file mode 100644 index 0000000..ce4c936 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/middle.png new file mode 100644 index 0000000..db449d7 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/right.png new file mode 100644 index 0000000..f3b3ff9 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/top.png new file mode 100644 index 0000000..0816400 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/topleft.png new file mode 100644 index 0000000..3b811ef Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/topright.png new file mode 100644 index 0000000..5174677 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass-yellow/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottom.png new file mode 100644 index 0000000..f5c3f5c Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomleft-arrow.png new file mode 100644 index 0000000..ca95a0b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomleft.png new file mode 100644 index 0000000..d947bbd Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomright-arrow.png new file mode 100644 index 0000000..3c3981f Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomright.png new file mode 100644 index 0000000..cf2b18c Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/left.png new file mode 100644 index 0000000..3aeebf3 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/middle.png new file mode 100644 index 0000000..01c7552 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/right.png new file mode 100644 index 0000000..09391e6 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/top.png new file mode 100644 index 0000000..c7e1236 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/topleft.png new file mode 100644 index 0000000..22640af Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/topright.png new file mode 100644 index 0000000..0ef7cd0 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/glass/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottom.png new file mode 100644 index 0000000..720d13b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomleft-arrow.png new file mode 100644 index 0000000..430b7cf Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomleft.png new file mode 100644 index 0000000..ddf6094 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomright-arrow.png new file mode 100644 index 0000000..697e44f Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomright.png new file mode 100644 index 0000000..c38354b Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/left.png new file mode 100644 index 0000000..e598620 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/middle.png new file mode 100644 index 0000000..b7d69fd Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/right.png new file mode 100644 index 0000000..c9df6b0 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/top.png new file mode 100644 index 0000000..b6e2848 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/topleft.png new file mode 100644 index 0000000..fead5f7 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/topright.png new file mode 100644 index 0000000..ce6ffff Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/modern/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottom.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottom.png new file mode 100644 index 0000000..b146171 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottom.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomleft-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomleft-arrow.png new file mode 100644 index 0000000..985b4de Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomleft-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomleft.png new file mode 100644 index 0000000..46690e4 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomright-arrow.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomright-arrow.png new file mode 100644 index 0000000..c93d47c Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomright-arrow.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomright.png new file mode 100644 index 0000000..340f520 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/bottomright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/left.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/left.png new file mode 100644 index 0000000..11a22ff Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/left.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/middle.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/middle.png new file mode 100644 index 0000000..cad76eb Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/middle.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/right.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/right.png new file mode 100644 index 0000000..dce355a Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/right.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/top.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/top.png new file mode 100644 index 0000000..dd65db5 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/top.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/topleft.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/topleft.png new file mode 100644 index 0000000..715304e Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/topleft.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/topright.png b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/topright.png new file mode 100644 index 0000000..e9248b7 Binary files /dev/null and b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/images/white/topright.png differ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/javascripts/reflection.js b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/javascripts/reflection.js new file mode 100644 index 0000000..89d2866 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/javascripts/reflection.js @@ -0,0 +1,170 @@ +/** + * reflection.js v1.6 + * + * Contributors: Cow http://cow.neondragon.net + * Gfx http://www.jroller.com/page/gfx/ + * Sitharus http://www.sitharus.com + * Andreas Linde http://www.andreaslinde.de + * Tralala, coder @ http://www.vbulletin.org + * + * Freely distributable under MIT-style license. + */ + +/* From prototype.js */ +document.getElementsByClassName = function(className) { + var children = document.getElementsByTagName('*') || document.all; + var elements = new Array(); + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var classNames = child.className.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == className) { + elements.push(child); + break; + } + } + } + return elements; +} + +var Reflection = { + defaultHeight : 0.45, + defaultOpacity: 0.4, + + add: function(image, options) { + Reflection.remove(image); + + doptions = { "height" : Reflection.defaultHeight, "opacity" : Reflection.defaultOpacity } + if (options) { + for (var i in doptions) { + if (!options[i]) { + options[i] = doptions[i]; + } + } + } else { + options = doptions; + } + + try { + var d = document.createElement('div'); + var p = image; + + var classes = p.className.split(' '); + var newClasses = ''; + for (j=0;j= (document.body.offsetHeight - (window.innerHeight * 1.2))); +} + +var intervall_scroll; + +function scrollToBottom() +{ + window.scrollTo(window.pageXOffset, document.body.offsetHeight); + return; + + /* + if ( intervall_scroll ) clearInterval( intervall_scroll ); + intervall_scroll = setInterval( function() { + var target_scroll = (document.body.scrollHeight-window.innerHeight); + var scrolldiff = target_scroll - document.body.scrollTop; + if ( document.body.scrollTop != target_scroll ) { + var saved_scroll = document.body.scrollTop; + document.body.scrollTop += scrolldiff / 5 + ( scrolldiff >= 0 ? (scrolldiff != 0 ) : -1 ); + } else { + saved_scroll = -1; + clearInterval( intervall_scroll ); + } + } , 10 ); + return; + */ +} diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..34921ed --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,200 @@ + +/* + + Modern Bubbling v0.9.2 beta + an Adium Message Style + + Created by Jim Myhrberg (jim@zhuoqe.org). + http://zhuoqe.org/blog/category/modern-bubbling/ + +*/ + + +/* import default header style sheet */ +@import url("styles/_headers/_default.css"); + + +BODY { + background: #fff url("images/bg.jpg") bottom repeat-x; + color: #303030; + /*font-family: helvetica;*/ + /*font-size: 11px;*/ + margin-left: 10px; + margin-right: 10px; +} + +#Chat { + overflow: hidden; + padding-top: 5px; + padding-bottom: 12px; +} +.emoticon { + border-style: none; + position: relative; + margin: -3px 0px -5px 0px; +} + + +.history { + color: #b3b3b3; +} + +.status { + color: #c7c7c7; + font-family: helvetica, sans-serif; + font-size: 10px; + margin: 4px 0px 0px 0px; + text-align: center; + cursor: default; +} +.status .stime { + font-size: 9px; + /*visibility: hidden;*/ +} +/* +.status:hover .stime { + visibility: visible; +} +*/ + +/* @group message */ + +.message { + position: relative; + min-height: 42px; +} +.message .body { + padding: 9px 20px 22px 20px; + /*word-wrap: break-word;*/ + /*text-shadow: #ccc 0px 1px 1px;*/ + overflow: auto; +} +.message .time { + font-size: 9px; + position: absolute; + bottom: -2px; + text-align: center; + width: 100%; + color: #c7c7c7; + visibility: hidden; +} +.message:hover .time { + visibility: visible; +} + +.message .topleft { + background: url("images/modern/topleft.png") top left no-repeat; + width: 24px; + height: 14px; + position: absolute; + top: 0px; + left: 0px; + z-index: -1; +} +.message .top { + background: url("images/modern/top.png") top right repeat-x; + height: 14px; + position: absolute; + top: 0px; + right: 24px; + left: 24px; + z-index: -1; +} +.message .topright { + background: url("images/modern/topright.png") top right no-repeat; + width: 24px; + height: 16px; + position: absolute; + top: 0px; + right: 0px; + z-index: -1; +} +.message .left { + background: url("images/modern/left.png") top left repeat-y; + width: 24px; + position: absolute; + left: 0px; + top: 14px; + bottom: 29px; + z-index: -1; +} +.message .middle { + background: url("images/modern/middle.png") top left repeat; + position: absolute; + left: 24px; + right: 24px; + top: 14px; + bottom: 29px; + z-index: -1; +} +.message .right { + background: url("images/modern/right.png") top right repeat-y; + width: 24px; + position: absolute; + right: 0px; + top: 14px; + bottom: 29px; + z-index: -1; +} +.message .bottomleft { + background: url("images/modern/bottomleft.png") bottom left no-repeat; + width: 24px; + height: 29px; + position: absolute; + bottom: 0px; + left: 0px; + z-index: -1; +} +.message .bottom { + background: url("images/modern/bottom.png") bottom right repeat-x; + height: 29px; + position: absolute; + bottom: 0px; + right: 24px; + left: 24px; + z-index: -1; +} +.message .bottomright { + background: url("images/modern/bottomright.png") bottom right no-repeat; + width: 24px; + height: 29px; + position: absolute; + bottom: 0px; + right: 0px; + z-index: -1; +} + +.compact { + padding: 9px 20px 22px 20px; +} +.compact .cbody, .compact .cnbody { + position: relative; + word-wrap: break-word; +} +.compact .cnbody { + border-top: 1px dotted #ededed; + padding-top: 3px; + margin-top: 3px; +} +.compact .ctime { + font-size: 9px; + position: absolute; + width: 40px; + top: 3px; + color: #c7c7c7; + white-space: nowrap; + visibility: hidden; +} +.compact .cnbody .ctime { + top: 5px; +} +.compact .cbody:hover .ctime, .compact .cnbody:hover .ctime { + visibility: visible; +} + +/* @end */ + + + + + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/_headers/_default.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/_headers/_default.css new file mode 100644 index 0000000..0f976e9 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/_headers/_default.css @@ -0,0 +1,79 @@ +/* @group header */ + +#header { + color: #FFF; + cursor: default; + font-family: helvetica, sans-serif; + overflow: hidden; + position: fixed; + top: 0px; + left: 0px; + z-index: 5; + width: 100%; + height: 49px; + text-shadow: #1f4971 0px 1px 2px; +} +#header_bar { + background: url("../../images/_headers/tiger.png") repeat-x top center; + width: 100%; + height: 49px; +} + +#header_bar .left { + font-size: 21px; + /*letter-spacing: 1px;*/ + position: fixed; + top: 8px; + left: 11px; +} +#header_bar .right { + font-size: 16px; + letter-spacing: 0px; + position: fixed; + top: 11px; + right: 10px; +} + + +/* @group toggle */ + +#toggle_hide { + cursor: pointer; + background: url("../../images/_headers/toggle-hide.png") no-repeat top left; + position: fixed; + top: 3px; + left: 3px; + width: 14px; + height: 10px; + visibility: hidden; + z-index: 6; +} + +#header:hover #toggle_hide { + visibility: visible; +} + +#header #toggle_hide:hover { + background-image: url("../../images/_headers/toggle-hide-hover.png"); +} + +#toggle_show { + cursor: pointer; + background: url("../../images/_headers/toggle-show.png") no-repeat top left; + position: fixed; + top: 3px; + left: 3px; + width: 14px; + height: 10px; + z-index: 6; +} + +#header #toggle_show:hover { + background-image: url("../../images/_headers/toggle-show-hover.png"); +} + +/* @end */ + + +/* @end */ + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/_headers/glass.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/_headers/glass.css new file mode 100644 index 0000000..0d3f6bb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/_headers/glass.css @@ -0,0 +1,28 @@ +/* @group header */ + +#header { + height: 40px; + text-shadow: #505050 0px 1px 2px; +} + +#header_bar { + background-image: url("../../images/_headers/glass.png"); + height: 40px; +} + +#header_bar .left { + font-size: 16px; + letter-spacing: 0px; + top: 6px; + left: 10px; +} + +#header_bar .right { + font-size: 14px; + letter-spacing: 0px; + top: 7px; + right: 10px; +} + +/* @end */ + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_default.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_default.css new file mode 100644 index 0000000..8c5568d --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_default.css @@ -0,0 +1,125 @@ + + +/* @group outgoing */ + +.outgoing { + float: right; + margin: 8px 0px 0px 38px; + position: relative; + max-width: 100%; +} + +.outgoing .sender { + position: absolute; + right: 0px; + bottom: 11px; + width: 32px; + height: 32px; +} +.outgoing .icon { + +} +.outgoing .name { + font-size: 9px; + position: absolute; + right: 0px; + bottom: -14px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #6e6e6e; +} +.outgoing .sender:hover .name { + visibility: visible; +} +.outgoing .service { + font-size: 9px; + position: absolute; + right: 0px; + bottom: 34px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #c7c7c7; +} +.outgoing .sender:hover .service { + visibility: visible; +} +.outgoing .message { + margin-right: 38px; +} +.outgoing .message .bottomright { + background-image: url("../../images/modern/bottomright-arrow.png"); +} +.outgoing .compact .ctime { + left: -58px; + text-align: right; +} + +/* @end */ + + + +/* @group incoming */ + +.incoming { + float: left; + margin: 8px 38px 0px 0px; + position: relative; + max-width: 100%; +} + +.incoming .sender { + position: absolute; + left: 0px; + bottom: 11px; + width: 32px; + height: 32px; +} +.incoming .icon { + +} +.incoming .name { + font-size: 9px; + position: absolute; + left: 0px; + bottom: -14px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #6e6e6e; +} +.incoming .sender:hover .name { + visibility: visible; +} +.incoming .service { + font-size: 9px; + position: absolute; + left: 0px; + bottom: 34px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #c7c7c7; +} +.incoming .sender:hover .service { + visibility: visible; +} +.incoming .message { + margin-left: 38px; +} +.incoming .message .bottomleft { + background-image: url("../../images/modern/bottomleft-arrow.png"); +} +.incoming .compact .ctime { + right: -58px; + text-align: left; +} + +/* @end */ + + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_glass.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_glass.css new file mode 100644 index 0000000..7c23d47 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_glass.css @@ -0,0 +1,2 @@ +/* import the glass header */ +@import url("../_headers/glass.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_white-bubbling.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_white-bubbling.css new file mode 100644 index 0000000..2a0ded5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_white-bubbling.css @@ -0,0 +1,48 @@ + +BODY { + background: #fff url("../../images/bg-white.jpg") top center repeat-y; +} + + +.message .topleft { + background: url("../../images/white/topleft.png") top left no-repeat; +} +.message .top { + background: url("../../images/white/top.png") top right repeat-x; +} +.message .topright { + background: url("../../images/white/topright.png") top right no-repeat; +} +.message .left { + background: url("../../images/white/left.png") top left repeat-y; +} +.message .middle { + background: url("../../images/white/middle.png") top left repeat; +} +.message .right { + background: url("../../images/white/right.png") top right repeat-y; +} +.message .bottomleft { + background: url("../../images/white/bottomleft.png") top left no-repeat; + height: 17px; + bottom: 12px; +} +.message .bottom { + background: url("../../images/white/bottom.png") top right repeat-x; + height: 17px; + bottom: 12px; +} +.message .bottomright { + background: url("../../images/white/bottomright.png") top right no-repeat; + height: 17px; + bottom: 12px; +} + + +.incoming .message .bottomleft { + background-image: url("../../images/white/bottomleft-arrow.png"); +} + +.outgoing .message .bottomright { + background-image: url("../../images/white/bottomright-arrow.png"); +} diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_white.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_white.css new file mode 100644 index 0000000..be1859a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/_white.css @@ -0,0 +1,40 @@ + +.message .topleft { + background: url("../../images/white/topleft.png") top left no-repeat; +} +.message .top { + background: url("../../images/white/top.png") top right repeat-x; +} +.message .topright { + background: url("../../images/white/topright.png") top right no-repeat; +} +.message .left { + background: url("../../images/white/left.png") top left repeat-y; +} +.message .middle { + background: url("../../images/white/middle.png") top left repeat; +} +.message .right { + background: url("../../images/white/right.png") top right repeat-y; +} +.message .bottomleft { + background: url("../../images/white/bottomleft.png") bottom left no-repeat; +} +.message .bottom { + background: url("../../images/white/bottom.png") bottom right repeat-x; +} +.message .bottomright { + background: url("../../images/white/bottomright.png") bottom right no-repeat; +} + + +.incoming .message .bottomleft { + background-image: url("../../images/white/bottomleft-arrow.png"); +} + +.outgoing .message .bottomright { + background-image: url("../../images/white/bottomright-arrow.png"); +} + + + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-aqua.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-aqua.css new file mode 100644 index 0000000..2167698 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-aqua.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #586bb1; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-aqua/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #9ca0c0; +} +.incoming .message .topleft { + background: url("../../images/glass-aqua/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-aqua/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-aqua/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-aqua/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-aqua/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-aqua/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-aqua/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-aqua/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-aqua/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-blue.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-blue.css new file mode 100644 index 0000000..e04c435 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-blue.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #586bb1; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-blue/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #9ca0c0; +} +.incoming .message .topleft { + background: url("../../images/glass-blue/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-blue/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-blue/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-blue/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-blue/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-blue/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-blue/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-blue/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-blue/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-cyan.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-cyan.css new file mode 100644 index 0000000..8d724a7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-cyan.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #92c9c9; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #5beffc; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-cyan/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #a1e2e7; +} +.incoming .message .topleft { + background: url("../../images/glass-cyan/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-cyan/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-cyan/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-cyan/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-cyan/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-cyan/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-cyan/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-cyan/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-cyan/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-glass.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-glass.css new file mode 100644 index 0000000..ac6651b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-glass.css @@ -0,0 +1,44 @@ + +/* @group incoming */ + +.incoming .compact .cnbody { + border-top: 1px dotted #e5e5e5; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .topleft { + background: url("../../images/glass/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-graphite.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-graphite.css new file mode 100644 index 0000000..bfe9628 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-graphite.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #757b8e; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-graphite/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c9cbd6; +} +.incoming .message .topleft { + background: url("../../images/glass-graphite/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-graphite/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-graphite/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-graphite/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-graphite/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-graphite/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-graphite/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-graphite/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-graphite/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-green.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-green.css new file mode 100644 index 0000000..2f08839 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-green.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #5db158; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-green/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #89c582; +} +.incoming .message .topleft { + background: url("../../images/glass-green/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-green/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-green/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-green/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-green/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-green/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-green/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-green/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-green/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-grey.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-grey.css new file mode 100644 index 0000000..a40ebea --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-grey.css @@ -0,0 +1,44 @@ + +/* @group incoming */ + +.incoming .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-grey/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .topleft { + background: url("../../images/glass-grey/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-grey/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-grey/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-grey/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-grey/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-grey/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-grey/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-grey/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-grey/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-lime.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-lime.css new file mode 100644 index 0000000..1e749e4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-lime.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #68c662; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-lime/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #aed18d; +} +.incoming .message .topleft { + background: url("../../images/glass-lime/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-lime/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-lime/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-lime/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-lime/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-lime/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-lime/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-lime/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-lime/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-orange.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-orange.css new file mode 100644 index 0000000..811b9dc --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-orange.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #b19158; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #edc276; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-orange/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c6af7f; +} +.incoming .message .topleft { + background: url("../../images/glass-orange/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-orange/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-orange/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-orange/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-orange/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-orange/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-orange/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-orange/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-orange/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-pink.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-pink.css new file mode 100644 index 0000000..e03d8d2 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-pink.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #b1799d; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #efa3d4; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-pink/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #dcc3dc; +} +.incoming .message .topleft { + background: url("../../images/glass-pink/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-pink/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-pink/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-pink/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-pink/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-pink/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-pink/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-pink/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-pink/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-purple.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-purple.css new file mode 100644 index 0000000..d5afc99 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-purple.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #92569f; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #df83f2; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-purple/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c0a4c6; +} +.incoming .message .topleft { + background: url("../../images/glass-purple/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-purple/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-purple/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-purple/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-purple/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-purple/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-purple/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-purple/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-purple/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-red.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-red.css new file mode 100644 index 0000000..7dba44e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-red.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #9f5657; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #f28385; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-red/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c5a3a4; +} +.incoming .message .topleft { + background: url("../../images/glass-red/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-red/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-red/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-red/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-red/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-red/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-red/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-red/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-red/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-yellow.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-yellow.css new file mode 100644 index 0000000..ec21f87 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-in-yellow.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #b19158; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #ffef00; +} + +.incoming .message .bottomleft { + background-image: url("../../images/glass-yellow/bottomleft-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c6af7f; +} +.incoming .message .topleft { + background: url("../../images/glass-yellow/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-yellow/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-yellow/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-yellow/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-yellow/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-yellow/right.png") top right repeat-y; +} +/*.incoming .message .bottomleft { + background: url("../../images/glass-yellow/bottomleft.png") bottom left no-repeat; +}*/ +.incoming .message .bottom { + background: url("../../images/glass-yellow/bottom.png") bottom right repeat-x; +} +.incoming .message .bottomright { + background: url("../../images/glass-yellow/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-aqua.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-aqua.css new file mode 100644 index 0000000..cc08dd4 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-aqua.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #586bb1; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-aqua/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #9ca0c0; +} +.outgoing .message .topleft { + background: url("../../images/glass-aqua/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-aqua/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-aqua/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-aqua/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-aqua/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-aqua/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-aqua/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-aqua/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-aqua/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-blue.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-blue.css new file mode 100644 index 0000000..80a6e23 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-blue.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #586bb1; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-blue/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #9ca0c0; +} +.outgoing .message .topleft { + background: url("../../images/glass-blue/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-blue/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-blue/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-blue/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-blue/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-blue/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-blue/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-blue/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-blue/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-cyan.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-cyan.css new file mode 100644 index 0000000..344d105 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-cyan.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #92c9c9; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #5beffc; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-cyan/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #a1e2e7; +} +.outgoing .message .topleft { + background: url("../../images/glass-cyan/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-cyan/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-cyan/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-cyan/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-cyan/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-cyan/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-cyan/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-cyan/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-cyan/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-glass.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-glass.css new file mode 100644 index 0000000..67386b1 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-glass.css @@ -0,0 +1,44 @@ + +/* @group outgoing */ + +.outgoing .compact .cnbody { + border-top: 1px dotted #e5e5e5; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .topleft { + background: url("../../images/glass/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-graphite.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-graphite.css new file mode 100644 index 0000000..b7adc1e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-graphite.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #757b8e; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-graphite/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c9cbd6; +} +.outgoing .message .topleft { + background: url("../../images/glass-graphite/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-graphite/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-graphite/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-graphite/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-graphite/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-graphite/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-graphite/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-graphite/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-graphite/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-green.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-green.css new file mode 100644 index 0000000..74c28b7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-green.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #5db158; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-green/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #89c582; +} +.outgoing .message .topleft { + background: url("../../images/glass-green/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-green/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-green/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-green/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-green/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-green/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-green/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-green/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-green/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-grey.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-grey.css new file mode 100644 index 0000000..1f85865 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-grey.css @@ -0,0 +1,44 @@ + +/* @group outgoing */ + +.outgoing .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-grey/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .topleft { + background: url("../../images/glass-grey/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-grey/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-grey/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-grey/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-grey/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-grey/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-grey/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-grey/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-grey/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-lime.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-lime.css new file mode 100644 index 0000000..9ba166c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-lime.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #68c662; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-lime/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #aed18d; +} +.outgoing .message .topleft { + background: url("../../images/glass-lime/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-lime/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-lime/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-lime/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-lime/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-lime/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-lime/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-lime/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-lime/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-orange.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-orange.css new file mode 100644 index 0000000..2bec3a7 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-orange.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #b19158; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #edc276; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-orange/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c6af7f; +} +.outgoing .message .topleft { + background: url("../../images/glass-orange/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-orange/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-orange/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-orange/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-orange/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-orange/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-orange/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-orange/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-orange/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-pink.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-pink.css new file mode 100644 index 0000000..2b0ef94 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-pink.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #b1799d; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #efa3d4; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-pink/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #dcc3dc; +} +.outgoing .message .topleft { + background: url("../../images/glass-pink/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-pink/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-pink/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-pink/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-pink/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-pink/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-pink/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-pink/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-pink/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-purple.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-purple.css new file mode 100644 index 0000000..89de230 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-purple.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #92569f; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #df83f2; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-purple/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c0a4c6; +} +.outgoing .message .topleft { + background: url("../../images/glass-purple/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-purple/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-purple/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-purple/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-purple/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-purple/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-purple/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-purple/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-purple/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-red.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-red.css new file mode 100644 index 0000000..5d597e8 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-red.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #9f5657; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #f28385; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-red/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c5a3a4; +} +.outgoing .message .topleft { + background: url("../../images/glass-red/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-red/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-red/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-red/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-red/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-red/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-red/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-red/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-red/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-yellow.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-yellow.css new file mode 100644 index 0000000..f252fef --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/alternative/glass-out-yellow.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #b19158; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #ffef00; +} + +.outgoing .message .bottomright { + background-image: url("../../images/glass-yellow/bottomright-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c6af7f; +} +.outgoing .message .topleft { + background: url("../../images/glass-yellow/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-yellow/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-yellow/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-yellow/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-yellow/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-yellow/right.png") top right repeat-y; +} +.outgoing .message .bottomleft { + background: url("../../images/glass-yellow/bottomleft.png") bottom left no-repeat; +} +.outgoing .message .bottom { + background: url("../../images/glass-yellow/bottom.png") bottom right repeat-x; +} +/*.outgoing .message .bottomright { + background: url("../../images/glass-yellow/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_default.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_default.css new file mode 100644 index 0000000..2d7c89c --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_default.css @@ -0,0 +1,125 @@ + + +/* @group outgoing */ + +.outgoing { + float: left; + margin: 8px 38px 0px 0px; + position: relative; + max-width: 100%; +} + +.outgoing .sender { + position: absolute; + left: 0px; + bottom: 11px; + width: 32px; + height: 32px; +} +.outgoing .icon { + +} +.outgoing .name { + font-size: 9px; + position: absolute; + left: 0px; + bottom: -14px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #6e6e6e; +} +.outgoing .sender:hover .name { + visibility: visible; +} +.outgoing .service { + font-size: 9px; + position: absolute; + left: 0px; + bottom: 34px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #c7c7c7; +} +.outgoing .sender:hover .service { + visibility: visible; +} +.outgoing .message { + margin-left: 38px; +} +.outgoing .message .bottomleft { + background-image: url("../../images/modern/bottomleft-arrow.png"); +} +.outgoing .compact .ctime { + right: -58px; + text-align: left; +} + +/* @end */ + + + +/* @group incoming */ + +.incoming { + float: right; + margin: 8px 0px 0px 38px; + position: relative; + max-width: 100%; +} + +.incoming .sender { + position: absolute; + right: 0px; + bottom: 11px; + width: 32px; + height: 32px; +} +.incoming .icon { + +} +.incoming .name { + font-size: 9px; + position: absolute; + right: 0px; + bottom: -14px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #6e6e6e; +} +.incoming .sender:hover .name { + visibility: visible; +} +.incoming .service { + font-size: 9px; + position: absolute; + right: 0px; + bottom: 34px; + min-width: 32px; + text-align: center; + visibility: hidden; + white-space: nowrap; + color: #c7c7c7; +} +.incoming .sender:hover .service { + visibility: visible; +} +.incoming .message { + margin-right: 38px; +} +.incoming .message .bottomright { + background-image: url("../../images/modern/bottomright-arrow.png"); +} +.incoming .compact .ctime { + left: -58px; + text-align: right; +} + +/* @end */ + + diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_glass.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_glass.css new file mode 100644 index 0000000..7c23d47 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_glass.css @@ -0,0 +1,2 @@ +/* import the glass header */ +@import url("../_headers/glass.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_white-bubbling.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_white-bubbling.css new file mode 100644 index 0000000..6497253 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_white-bubbling.css @@ -0,0 +1,48 @@ + +BODY { + background: #fff url("../../images/bg-white.jpg") top center repeat-y; +} + + +.message .topleft { + background: url("../../images/white/topleft.png") top left no-repeat; +} +.message .top { + background: url("../../images/white/top.png") top right repeat-x; +} +.message .topright { + background: url("../../images/white/topright.png") top right no-repeat; +} +.message .left { + background: url("../../images/white/left.png") top left repeat-y; +} +.message .middle { + background: url("../../images/white/middle.png") top left repeat; +} +.message .right { + background: url("../../images/white/right.png") top right repeat-y; +} +.message .bottomleft { + background: url("../../images/white/bottomleft.png") top left no-repeat; + height: 17px; + bottom: 12px; +} +.message .bottom { + background: url("../../images/white/bottom.png") top right repeat-x; + height: 17px; + bottom: 12px; +} +.message .bottomright { + background: url("../../images/white/bottomright.png") top right no-repeat; + height: 17px; + bottom: 12px; +} + + +.incoming .message .bottomright { + background-image: url("../../images/white/bottomright-arrow.png"); +} + +.outgoing .message .bottomleft { + background-image: url("../../images/white/bottomleft-arrow.png"); +} diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_white.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_white.css new file mode 100644 index 0000000..b61e1fb --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/_white.css @@ -0,0 +1,37 @@ + +.message .topleft { + background: url("../../images/white/topleft.png") top left no-repeat; +} +.message .top { + background: url("../../images/white/top.png") top right repeat-x; +} +.message .topright { + background: url("../../images/white/topright.png") top right no-repeat; +} +.message .left { + background: url("../../images/white/left.png") top left repeat-y; +} +.message .middle { + background: url("../../images/white/middle.png") top left repeat; +} +.message .right { + background: url("../../images/white/right.png") top right repeat-y; +} +.message .bottomleft { + background: url("../../images/white/bottomleft.png") bottom left no-repeat; +} +.message .bottom { + background: url("../../images/white/bottom.png") bottom right repeat-x; +} +.message .bottomright { + background: url("../../images/white/bottomright.png") bottom right no-repeat; +} + + +.incoming .message .bottomright { + background-image: url("../../images/white/bottomright-arrow.png"); +} + +.outgoing .message .bottomleft { + background-image: url("../../images/white/bottomleft-arrow.png"); +} diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-aqua.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-aqua.css new file mode 100644 index 0000000..f3de622 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-aqua.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #586bb1; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-aqua/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #9ca0c0; +} +.incoming .message .topleft { + background: url("../../images/glass-aqua/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-aqua/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-aqua/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-aqua/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-aqua/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-aqua/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-aqua/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-aqua/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-aqua/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-blue.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-blue.css new file mode 100644 index 0000000..3036d36 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-blue.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #586bb1; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-blue/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #9ca0c0; +} +.incoming .message .topleft { + background: url("../../images/glass-blue/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-blue/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-blue/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-blue/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-blue/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-blue/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-blue/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-blue/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-blue/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-cyan.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-cyan.css new file mode 100644 index 0000000..a02b8d5 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-cyan.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #92c9c9; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #5beffc; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-cyan/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #a1e2e7; +} +.incoming .message .topleft { + background: url("../../images/glass-cyan/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-cyan/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-cyan/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-cyan/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-cyan/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-cyan/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-cyan/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-cyan/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-cyan/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-glass.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-glass.css new file mode 100644 index 0000000..53b3127 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-glass.css @@ -0,0 +1,44 @@ + +/* @group incoming */ + +.incoming .compact .cnbody { + border-top: 1px dotted #e5e5e5; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .topleft { + background: url("../../images/glass/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-graphite.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-graphite.css new file mode 100644 index 0000000..5c021be --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-graphite.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #757b8e; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-graphite/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c9cbd6; +} +.incoming .message .topleft { + background: url("../../images/glass-graphite/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-graphite/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-graphite/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-graphite/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-graphite/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-graphite/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-graphite/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-graphite/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-graphite/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-green.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-green.css new file mode 100644 index 0000000..2bcf34e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-green.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #5db158; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-green/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #89c582; +} +.incoming .message .topleft { + background: url("../../images/glass-green/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-green/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-green/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-green/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-green/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-green/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-green/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-green/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-green/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-grey.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-grey.css new file mode 100644 index 0000000..0c10865 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-grey.css @@ -0,0 +1,44 @@ + +/* @group incoming */ + +.incoming .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-grey/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .topleft { + background: url("../../images/glass-grey/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-grey/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-grey/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-grey/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-grey/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-grey/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-grey/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-grey/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-grey/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-lime.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-lime.css new file mode 100644 index 0000000..437b431 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-lime.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #68c662; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-lime/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #aed18d; +} +.incoming .message .topleft { + background: url("../../images/glass-lime/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-lime/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-lime/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-lime/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-lime/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-lime/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-lime/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-lime/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-lime/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-orange.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-orange.css new file mode 100644 index 0000000..0185f87 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-orange.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #b19158; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #edc276; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-orange/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c6af7f; +} +.incoming .message .topleft { + background: url("../../images/glass-orange/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-orange/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-orange/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-orange/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-orange/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-orange/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-orange/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-orange/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-orange/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-pink.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-pink.css new file mode 100644 index 0000000..f44ba6f --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-pink.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #b1799d; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #efa3d4; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-pink/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #dcc3dc; +} +.incoming .message .topleft { + background: url("../../images/glass-pink/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-pink/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-pink/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-pink/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-pink/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-pink/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-pink/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-pink/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-pink/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-purple.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-purple.css new file mode 100644 index 0000000..ec63fda --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-purple.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #92569f; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #df83f2; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-purple/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c0a4c6; +} +.incoming .message .topleft { + background: url("../../images/glass-purple/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-purple/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-purple/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-purple/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-purple/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-purple/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-purple/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-purple/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-purple/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-red.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-red.css new file mode 100644 index 0000000..2d1f76e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-red.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #9f5657; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #f28385; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-red/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c5a3a4; +} +.incoming .message .topleft { + background: url("../../images/glass-red/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-red/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-red/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-red/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-red/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-red/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-red/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-red/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-red/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-yellow.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-yellow.css new file mode 100644 index 0000000..1d19685 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-in-yellow.css @@ -0,0 +1,51 @@ + +/* @group incoming */ + +.incoming .history { + color: #b19158; +} + +.incoming .compact .cnbody { + border-top: 1px dotted #ffef00; +} + +.incoming .message .bottomright { + background-image: url("../../images/glass-yellow/bottomright-arrow.png"); +} + +/* @group message */ + +.incoming .message .time { + color: #c6af7f; +} +.incoming .message .topleft { + background: url("../../images/glass-yellow/topleft.png") top left no-repeat; +} +.incoming .message .top { + background: url("../../images/glass-yellow/top.png") top right repeat-x; +} +.incoming .message .topright { + background: url("../../images/glass-yellow/topright.png") top right no-repeat; +} +.incoming .message .left { + background: url("../../images/glass-yellow/left.png") top left repeat-y; +} +.incoming .message .middle { + background: url("../../images/glass-yellow/middle.png") top left repeat; +} +.incoming .message .right { + background: url("../../images/glass-yellow/right.png") top right repeat-y; +} +.incoming .message .bottomleft { + background: url("../../images/glass-yellow/bottomleft.png") bottom left no-repeat; +} +.incoming .message .bottom { + background: url("../../images/glass-yellow/bottom.png") bottom right repeat-x; +} +/*.incoming .message .bottomright { + background: url("../../images/glass-yellow/bottomright.png") bottom right no-repeat; +}*/ + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-aqua.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-aqua.css new file mode 100644 index 0000000..1a04d5a --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-aqua.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #586bb1; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-aqua/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #9ca0c0; +} +.outgoing .message .topleft { + background: url("../../images/glass-aqua/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-aqua/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-aqua/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-aqua/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-aqua/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-aqua/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-aqua/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-aqua/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-aqua/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-blue.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-blue.css new file mode 100644 index 0000000..4b5a735 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-blue.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #586bb1; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #8cb4ff; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-blue/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #9ca0c0; +} +.outgoing .message .topleft { + background: url("../../images/glass-blue/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-blue/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-blue/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-blue/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-blue/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-blue/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-blue/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-blue/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-blue/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-cyan.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-cyan.css new file mode 100644 index 0000000..33c7076 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-cyan.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #92c9c9; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #5beffc; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-cyan/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #a1e2e7; +} +.outgoing .message .topleft { + background: url("../../images/glass-cyan/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-cyan/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-cyan/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-cyan/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-cyan/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-cyan/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-cyan/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-cyan/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-cyan/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-glass.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-glass.css new file mode 100644 index 0000000..94d0c21 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-glass.css @@ -0,0 +1,44 @@ + +/* @group outgoing */ + +.outgoing .compact .cnbody { + border-top: 1px dotted #e5e5e5; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .topleft { + background: url("../../images/glass/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-graphite.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-graphite.css new file mode 100644 index 0000000..c29e73b --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-graphite.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #757b8e; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-graphite/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c9cbd6; +} +.outgoing .message .topleft { + background: url("../../images/glass-graphite/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-graphite/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-graphite/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-graphite/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-graphite/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-graphite/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-graphite/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-graphite/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-graphite/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-green.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-green.css new file mode 100644 index 0000000..5205d82 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-green.css @@ -0,0 +1,50 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #5db158; +} +.outgoing .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-green/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #89c582; +} +.outgoing .message .topleft { + background: url("../../images/glass-green/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-green/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-green/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-green/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-green/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-green/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-green/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-green/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-green/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-grey.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-grey.css new file mode 100644 index 0000000..8931245 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-grey.css @@ -0,0 +1,44 @@ + +/* @group outgoing */ + +.outgoing .compact .cnbody { + border-top: 1px dotted #d1d1d1; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-grey/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .topleft { + background: url("../../images/glass-grey/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-grey/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-grey/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-grey/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-grey/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-grey/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-grey/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-grey/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-grey/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-lime.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-lime.css new file mode 100644 index 0000000..afaa639 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-lime.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #68c662; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #77e770; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-lime/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #aed18d; +} +.outgoing .message .topleft { + background: url("../../images/glass-lime/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-lime/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-lime/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-lime/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-lime/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-lime/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-lime/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-lime/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-lime/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-orange.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-orange.css new file mode 100644 index 0000000..15c9eb6 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-orange.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #b19158; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #edc276; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-orange/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c6af7f; +} +.outgoing .message .topleft { + background: url("../../images/glass-orange/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-orange/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-orange/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-orange/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-orange/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-orange/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-orange/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-orange/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-orange/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-pink.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-pink.css new file mode 100644 index 0000000..8953c86 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-pink.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #b1799d; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #efa3d4; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-pink/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #dcc3dc; +} +.outgoing .message .topleft { + background: url("../../images/glass-pink/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-pink/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-pink/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-pink/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-pink/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-pink/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-pink/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-pink/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-pink/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-purple.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-purple.css new file mode 100644 index 0000000..233112e --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-purple.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #92569f; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #df83f2; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-purple/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c0a4c6; +} +.outgoing .message .topleft { + background: url("../../images/glass-purple/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-purple/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-purple/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-purple/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-purple/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-purple/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-purple/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-purple/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-purple/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-red.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-red.css new file mode 100644 index 0000000..d9ac1d3 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-red.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #9f5657; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #f28385; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-red/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c5a3a4; +} +.outgoing .message .topleft { + background: url("../../images/glass-red/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-red/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-red/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-red/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-red/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-red/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-red/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-red/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-red/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-yellow.css b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-yellow.css new file mode 100644 index 0000000..dc20607 --- /dev/null +++ b/digsby/res/MessageStyles/Modern Bubbling.AdiumMessageStyle/Contents/Resources/styles/normal/glass-out-yellow.css @@ -0,0 +1,51 @@ + +/* @group outgoing */ + +.outgoing .history { + color: #b19158; +} + +.outgoing .compact .cnbody { + border-top: 1px dotted #ffef00; +} + +.outgoing .message .bottomleft { + background-image: url("../../images/glass-yellow/bottomleft-arrow.png"); +} + +/* @group message */ + +.outgoing .message .time { + color: #c6af7f; +} +.outgoing .message .topleft { + background: url("../../images/glass-yellow/topleft.png") top left no-repeat; +} +.outgoing .message .top { + background: url("../../images/glass-yellow/top.png") top right repeat-x; +} +.outgoing .message .topright { + background: url("../../images/glass-yellow/topright.png") top right no-repeat; +} +.outgoing .message .left { + background: url("../../images/glass-yellow/left.png") top left repeat-y; +} +.outgoing .message .middle { + background: url("../../images/glass-yellow/middle.png") top left repeat; +} +.outgoing .message .right { + background: url("../../images/glass-yellow/right.png") top right repeat-y; +} +/*.outgoing .message .bottomleft { + background: url("../../images/glass-yellow/bottomleft.png") bottom left no-repeat; +}*/ +.outgoing .message .bottom { + background: url("../../images/glass-yellow/bottom.png") bottom right repeat-x; +} +.outgoing .message .bottomright { + background: url("../../images/glass-yellow/bottomright.png") bottom right no-repeat; +} + +/* @end */ + +/* @end */ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..d36f6a2 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + PurePlastics Adium Message Style + CFBundleIdentifier + com.adiumx.pureplastics.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + PurePlastics + CFBundlePackageType + AdIM + DefaultFontFamily + Lucida Grande + DefaultFontSize + 11 + DisplayNameForNoVariant + Red vs Blue + MessageViewVersion + 1 + ShowsUserIcons + + + diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..7db308b --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,28 @@ +
+
+
+ +
+
+
+
+
+
+
+
+ %sender% +
%time%
+
+
+
+

%message%

+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..12baddf --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,28 @@ +
+
+
+ +
+
+
+
+
+
+
+
+ %sender% +
%time%
+
+
+
+

%message%

+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..a033423 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,7 @@ + +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..a033423 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,7 @@ + +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..334fc21 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..fb52c43 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,28 @@ +
+
+
+ +
+
+
+
+
+
+
+
+ %sender% +
%time%
+
+
+
+

%message%

+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..12baddf --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,28 @@ +
+
+
+ +
+
+
+
+
+
+
+
+ %sender% +
%time%
+
+
+
+

%message%

+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..a033423 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,7 @@ + +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..a033423 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,7 @@ + +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..334fc21 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..3b95175 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,23 @@ +
+
status
+
+
+
+
+
+
+

+

%time%
+ %message% +

+
+
+
+
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Green.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Green.css new file mode 100644 index 0000000..0e313e7 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Green.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_green_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_blue_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_green.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_blue.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #1E9BAD; +} + +.message_blue a { + color: #75AC1E; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Red.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Red.css new file mode 100644 index 0000000..b3b1b44 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Blue vs Red.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_red_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_blue_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_red.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_blue.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #1E9BAD; +} + +.message_blue a { + color: #D34D00; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Graphite vs Grey.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Graphite vs Grey.css new file mode 100644 index 0000000..a94bde6 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Graphite vs Grey.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_grey_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_graphite_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_grey.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_graphite.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #738091; +} + +.message_blue a { + color: #555; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Graphite.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Graphite.css new file mode 100644 index 0000000..e372f56 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Graphite.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #C3CCD7 bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_graphite_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_graphite_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_graphite.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_graphite.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #738091; +} + +.message_blue a { + color: #738091; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Green vs Blue.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Green vs Blue.css new file mode 100644 index 0000000..5f4ca1a --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Green vs Blue.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_blue_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_green_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_blue.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_green.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #75AC1E; +} + +.message_blue a { + color: #1E9BAD; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Green vs Red.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Green vs Red.css new file mode 100644 index 0000000..c89b7b3 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Green vs Red.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_red_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_green_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_red.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_green.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #75AC1E; +} + +.message_blue a { + color: #D34D00; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Graphite.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Graphite.css new file mode 100644 index 0000000..828e2e0 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Grey vs Graphite.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_graphite_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_grey_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_graphite.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_grey.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #555; +} + +.message_blue a { + color: #738091; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Greyscale.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Greyscale.css new file mode 100644 index 0000000..c88d319 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Greyscale.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_grey.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_grey_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_grey_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_grey.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_grey.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #555555; +} + +.message_blue a { + color: #555555; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Red vs Green.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Red vs Green.css new file mode 100644 index 0000000..922f473 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/Variants/Red vs Green.css @@ -0,0 +1,44 @@ +@import "../base.css"; + +body { + background: #BFBFBF url("../images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("../images/header_green_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("../images/header_red_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("../images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("../images/header_green.png") top right no-repeat; +} + +.header_red_edge { + background: url("../images/header_red.png") top right no-repeat; +} + +.header_context_edge { + background: url("../images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #D34D00; +} + +.message_blue a { + color: #75AC1E; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/base.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/base.css new file mode 100644 index 0000000..0c63f49 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/base.css @@ -0,0 +1,259 @@ +body { + margin: 0; +} + +p { + margin: 0; + padding: 0; + + overflow: auto; +} + +.next { + margin-top: 3px; + padding-top: 3px; + border-top: 1px solid #EBEBEB; + +} + +.next_text { + margin-right: 2px; + overflow:auto; +} + +.next_timestamp { + float: right; + + color: rgb(0, 0, 0); + + font-family: "Lucida Grande"; + font-size: 9px; + font-weight: bold; + + margin: 0 2px 0 4px; +} + +.status { + color: #555; +} + +.container_post { + margin: 16px 2px 10px 6px; + + position: relative; + clear: both; +} + +.container_left { + margin: 0; + padding: 6px 0px 0px 8px; + float: left; + + background: url("images/shadow_buddyicon.png") top left no-repeat; + height: 48px; + width: 43px; + + z-index: 3; +} + +.status_left { + color: #999999; + + margin: 0; + padding: 7px 0px 0px 10px; + float: left; + + font-family: "Lucida Grande"; + font-size: 9px; + font-weight: bold; + + background: url("images/status.png") top left no-repeat; + height: 24px; + width: 38px; + + z-index: 3; + + position: relative; +} + +.buddyicon { + height: 32px; + width: 32px; + border: 0px; +} + +.iconchecker[background="Incoming/buddy_icon.png"], +.iconchecker[background="Outgoing/buddy_icon.png"] { + display: none; +} + +.container_right { + margin: 0px 5px 0px 38px; + + position: relative; + top: 2px; +} + +.container_right[background="Incoming/buddy_icon.png"], +.container_right[background="Outgoing/buddy_icon.png"] { + margin: 0px 5px 0px 0px; +} + +.place_holder{ + height: 17px +} + +.header_blue, .header_red, .header_context { + + font-family: "Lucida Grande"; + font-size: 9px; + font-weight: bold; + line-height: 17px; + + height: 17px; + + padding: 0px 10px 0px 8px; + + position: absolute; + left: 5px; + right: 11px; + + + overflow: hidden; + +} + + +.sender{ + + position: absolute; + left: 8px; + right: 70px; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + + +.timestamp , .timestamp_context{ + font-family: "Lucida Grande"; + font-size: 9px; + font-weight: bold; + + color: rgb(255, 255, 255); + + float: right; + +} + +.timestamp_context { + color: #999999; +} + +.container_message { + position: relative; +} + +.container_bottom { + position: relative; +} + +.message_red, .message_blue, .message_context { + background: #fff url("images/message_background.png") top left repeat-x; + + padding: 3px 10px 0px 8px; + + margin-left: 5px; + margin-right: 5px; +} + +.message_red img, .message_blue img, .message_context img { + vertical-align: middle; +} + +.message_red a, .message_blue a, .message_context a { + text-decoration: none; +} + +.message_red a:hover, .message_blue a:hover, .message_context a:hover { + text-decoration: underline; +} + +.header_blue_edge, .header_red_edge, .header_context_edge { + width: 11px; + height: 23px; + position: absolute; + right: 0px; + top: -2px; +} + + +.left { + background: url("images/shadow_left.png") top right repeat-y; + width: 5px; + height: 100%; + position: absolute; + left: 0px; +} + +.right { + background: url("images/shadow_right.png") top right repeat-y; + width: 5px; + position: absolute; + right: 0px; + top:21px; + bottom:0px; +} + +.top { + background: url("images/shadow_top.png") top right repeat-x; + height:3px; + position:absolute; + top:-3px; + left:4px; + right:4px; + +} + +.right_status { + background: url("images/shadow_right.png") top right repeat-y; + width: 5px; + position: absolute; + right: 0px; + top:0px; + bottom:0px; +} + +.bottom_left { + background: url("images/shadow_bottom_left.png") bottom right no-repeat; + + height: 12px; + width: 5px; + position: absolute; + left: 0px; +} + +.bottom_center { + background: url("images/shadow_bottom_center.png") bottom left repeat-x; + + height: 12px; + position: absolute; + right: 13px; + left: 5px; +} + +.bottom_right { + background: url("images/shadow_bottom_right.png") bottom right no-repeat; + + height: 12px; + width: 13px; + position: absolute; + right: 0px; +} + + +#Chat { + overflow: hidden; + margin-bottom: -8px; +} diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/background_grey.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/background_grey.png new file mode 100644 index 0000000..c426b94 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/background_grey.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/background_sand.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/background_sand.png new file mode 100644 index 0000000..0a40564 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/background_sand.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/buddyicon_context.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/buddyicon_context.png new file mode 100644 index 0000000..29b4119 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/buddyicon_context.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_blue.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_blue.png new file mode 100644 index 0000000..c6a0b9f Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_blue.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_blue_background.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_blue_background.png new file mode 100644 index 0000000..05fea2f Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_blue_background.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_context.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_context.png new file mode 100644 index 0000000..0cd4243 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_context.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_context_background.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_context_background.png new file mode 100644 index 0000000..f0b7e55 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_context_background.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_graphite.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_graphite.png new file mode 100644 index 0000000..5b3f349 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_graphite.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_graphite_background.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_graphite_background.png new file mode 100644 index 0000000..299906a Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_graphite_background.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_green.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_green.png new file mode 100644 index 0000000..da898ac Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_green.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_green_background.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_green_background.png new file mode 100644 index 0000000..5c0e225 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_green_background.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_grey.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_grey.png new file mode 100644 index 0000000..57965ae Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_grey.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_grey_background.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_grey_background.png new file mode 100644 index 0000000..d87d74d Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_grey_background.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_red.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_red.png new file mode 100644 index 0000000..544196b Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_red.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_red_background.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_red_background.png new file mode 100644 index 0000000..38a53dc Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/header_red_background.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/message_background.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/message_background.png new file mode 100644 index 0000000..3003640 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/message_background.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_center.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_center.png new file mode 100644 index 0000000..7d74641 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_center.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_left.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_left.png new file mode 100644 index 0000000..ba8230b Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_left.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_right.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_right.png new file mode 100644 index 0000000..aa20b4a Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_bottom_right.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_buddyicon.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_buddyicon.png new file mode 100644 index 0000000..aef5dc6 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_buddyicon.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_left.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_left.png new file mode 100644 index 0000000..c0fc116 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_left.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_right.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_right.png new file mode 100644 index 0000000..3359d3c Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_right.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_top.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_top.png new file mode 100644 index 0000000..1bfce24 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/shadow_top.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/status.png b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/status.png new file mode 100644 index 0000000..d889635 Binary files /dev/null and b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/images/status.png differ diff --git a/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..a0c6af1 --- /dev/null +++ b/digsby/res/MessageStyles/PurePlastics.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,44 @@ +@import "base.css"; + +body { + background: #D5CFBC url("images/background_sand.png") bottom left repeat fixed; +} + +.header_blue { + background: url("images/header_blue_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_red { + background: url("images/header_red_background.png") bottom left repeat-x; + color:#ffffff; +} + +.header_context { + background: url("images/header_context_background.png") bottom left repeat-x; + color:#999999; +} + +.header_blue_edge { + background: url("images/header_blue.png") top right no-repeat; +} + +.header_red_edge { + background: url("images/header_red.png") top right no-repeat; +} + +.header_context_edge { + background: url("images/header_context.png") top right no-repeat; +} + +.message_red a { + color: #D34D00; +} + +.message_blue a { + color: #1E9BAD; +} + +.message_context a { + color: #999999; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..4543fd1 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + Satin Adium Message Style + CFBundleIdentifier + com.adiumx.satin.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + Satin + CFBundlePackageType + AdIM + DefaultBackgroundColor + ffffff + DefaultFontFamily + Helvetica + DefaultFontSize + 10 + DisplayNameForNoVariant + GBO-Left Icon + MessageViewVersion + 1 + ShowsUserIcons:Blue-Green-No Icon + + ShowsUserIcons:Green-Blue-No Icon + + + diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..44e1dca --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,17 @@ +
+
%sender%
%service%
+
+
+
+
+
+
+
+
+

%time%%message%

+
+
+
+
+
+
diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..78816af --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,17 @@ +
+
%sender%
%service%
+
+
+
+
+
+
+
+
+

%time%%message%

+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..50984c8 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,3 @@ +
+

%time%%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..50984c8 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,3 @@ +
+

%time%%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..c345d9b Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon_alt.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon_alt.png new file mode 100644 index 0000000..d626111 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon_alt.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..fe82af4 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,17 @@ +
+
%sender%
%service%
+
+
+
+
+
+
+
+
+

%time%%message%

+
+
+
+
+
+
diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..1dccb8f --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,17 @@ +
+
%sender%
%service%
+
+
+
+
+
+
+
+
+

%time%%message%

+
+
+
+
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..50984c8 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,3 @@ +
+

%time%%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..50984c8 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,3 @@ +
+

%time%%message%

+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..c345d9b Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon_alt.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon_alt.png new file mode 100644 index 0000000..d626111 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon_alt.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..0c91f72 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,5 @@ +
+
%message%
+
%time%
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Left Alternate Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Left Alternate Icon.css new file mode 100644 index 0000000..15f5ffd --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Left Alternate Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_alternate_left.css"); +@import url("../styles/green_blue_orange_alternate_left.css"); diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-No Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-No Icon.css new file mode 100644 index 0000000..4b96f46 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-No Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_hidden.css"); +@import url("../styles/green_blue_orange_left.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Right Alternate Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Right Alternate Icon.css new file mode 100644 index 0000000..922fc6b --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Right Alternate Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_alternate_right.css"); +@import url("../styles/green_blue_orange_alternate_right.css"); diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Right Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Right Icon.css new file mode 100644 index 0000000..460a9fb --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBO-Right Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_right.css"); +@import url("../styles/green_blue_orange_right.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Left Alternate Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Left Alternate Icon.css new file mode 100644 index 0000000..3f8d681 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Left Alternate Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_alternate_left.css"); +@import url("../styles/green_blue_purple_alternate_left.css"); diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Left Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Left Icon.css new file mode 100644 index 0000000..73e95e6 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Left Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_left.css"); +@import url("../styles/green_blue_purple_left.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-No Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-No Icon.css new file mode 100644 index 0000000..a5da333 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-No Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_hidden.css"); +@import url("../styles/green_blue_purple_left.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Right Alternate Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Right Alternate Icon.css new file mode 100644 index 0000000..c53043a --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Right Alternate Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_alternate_right.css"); +@import url("../styles/green_blue_purple_alternate_right.css"); diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Right Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Right Icon.css new file mode 100644 index 0000000..08cd72b --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/GBP-Right Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_right.css"); +@import url("../styles/green_blue_purple_right.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Left Alternate Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Left Alternate Icon.css new file mode 100644 index 0000000..4f87db4 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Left Alternate Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_alternate_left.css"); +@import url("../styles/red_blue_purple_alternate_left.css"); diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Left Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Left Icon.css new file mode 100644 index 0000000..e587040 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Left Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_left.css"); +@import url("../styles/red_blue_purple_left.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-No Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-No Icon.css new file mode 100644 index 0000000..1cada27 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-No Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_hidden.css"); +@import url("../styles/red_blue_purple_left.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Right Alternate Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Right Alternate Icon.css new file mode 100644 index 0000000..acd71ea --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Right Alternate Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_alternate_right.css"); +@import url("../styles/red_blue_purple_alternate_right.css"); diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Right Icon.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Right Icon.css new file mode 100644 index 0000000..78f64b6 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/Variants/RBP-Right Icon.css @@ -0,0 +1,3 @@ +@import url("../styles/defaults.css"); +@import url("../styles/layout_right.css"); +@import url("../styles/red_blue_purple_right.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/arrow.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/arrow.png new file mode 100644 index 0000000..1a7fa39 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/arrow.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/background.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/background.png new file mode 100644 index 0000000..44cdb47 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/background.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/buddy_background.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/buddy_background.png new file mode 100644 index 0000000..3c5d326 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/buddy_background.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_bot_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_bot_left.png new file mode 100644 index 0000000..967a69b Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_bot_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_bot_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_bot_right.png new file mode 100644 index 0000000..fddc4e9 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_bot_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_bot_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_bot_left.png new file mode 100644 index 0000000..09bd715 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_bot_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_bot_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_bot_right.png new file mode 100644 index 0000000..6e108a9 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_bot_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_fill.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_fill.png new file mode 100644 index 0000000..e8f359d Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_fill.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_left.png new file mode 100644 index 0000000..36f58f9 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_right.png new file mode 100644 index 0000000..c2584e2 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_context_top_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_fill.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_fill.png new file mode 100644 index 0000000..f28124c Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_fill.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_left.png new file mode 100644 index 0000000..5cbb490 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_right.png new file mode 100644 index 0000000..3211e45 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/message/message_top_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/blue/sender_context_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/blue/sender_context_left.png new file mode 100644 index 0000000..5f9e9c0 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/blue/sender_context_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/blue/sender_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/blue/sender_left.png new file mode 100644 index 0000000..b671c16 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/blue/sender_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/green/sender_context_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/green/sender_context_left.png new file mode 100644 index 0000000..c1a551c Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/green/sender_context_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/green/sender_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/green/sender_left.png new file mode 100644 index 0000000..346cdbe Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/green/sender_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/red/sender_context_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/red/sender_context_left.png new file mode 100644 index 0000000..d0a1501 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/red/sender_context_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/red/sender_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/red/sender_left.png new file mode 100644 index 0000000..1f7baf9 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/red/sender_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/sender_context_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/sender_context_right.png new file mode 100644 index 0000000..7a3a75d Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/sender_context_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/sender_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/sender_right.png new file mode 100644 index 0000000..4c8972e Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/flipped/sender_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/sender_context_fill.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/sender_context_fill.png new file mode 100644 index 0000000..6d7ef60 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/sender_context_fill.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/sender_fill.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/sender_fill.png new file mode 100644 index 0000000..f73c046 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/sender_fill.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/blue/sender_context_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/blue/sender_context_right.png new file mode 100644 index 0000000..cec2171 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/blue/sender_context_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/blue/sender_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/blue/sender_right.png new file mode 100644 index 0000000..0de5eb1 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/blue/sender_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/green/sender_context_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/green/sender_context_right.png new file mode 100644 index 0000000..c5261b5 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/green/sender_context_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/green/sender_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/green/sender_right.png new file mode 100644 index 0000000..b730928 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/green/sender_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/red/sender_context_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/red/sender_context_right.png new file mode 100644 index 0000000..d79b178 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/red/sender_context_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/red/sender_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/red/sender_right.png new file mode 100644 index 0000000..424939d Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/red/sender_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/sender_context_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/sender_context_left.png new file mode 100644 index 0000000..ea1811f Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/sender_context_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/sender_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/sender_left.png new file mode 100644 index 0000000..b64b18c Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/sender/standard/sender_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_fill.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_fill.png new file mode 100644 index 0000000..4fae7b1 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_fill.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_left.png new file mode 100644 index 0000000..cdcd6b0 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_right.png new file mode 100644 index 0000000..f44b26a Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/gray/status_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_fill.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_fill.png new file mode 100644 index 0000000..1c7d55d Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_fill.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_left.png new file mode 100644 index 0000000..bd84f21 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_right.png new file mode 100644 index 0000000..169b3e8 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/orange/status_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_fill.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_fill.png new file mode 100644 index 0000000..c7e3389 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_fill.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_left.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_left.png new file mode 100644 index 0000000..db3c8a2 Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_left.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_right.png b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_right.png new file mode 100644 index 0000000..4d7990f Binary files /dev/null and b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/images/status/purple/status_right.png differ diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..8fc0346 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,3 @@ +@import url("styles/defaults.css"); +@import url("styles/layout_left.css"); +@import url("styles/green_blue_orange_left.css"); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/defaults.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/defaults.css new file mode 100644 index 0000000..ac7329c --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/defaults.css @@ -0,0 +1,67 @@ +body {margin: 10px 15px 10px 15px;} + + +p {margin: 0px;overflow: hidden;} + +.spacer {clear: both;} + +.nameheader { + font-family: "Myriad"; + font-size: 11px; + font-weight: 900; + font-style: normal; + opacity: 1.0; + /* #border: 1px solid #999999; */ +} +.name { + /* #border: 1px solid #999999; */ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.protocol { + /* b#order: 1px solid #999999; */ +} + +.message { + font-size: 10px; + color: #454545; + text-shadow: 0px 1px 0px rgb(255,255,255); +} +.messagetime { + padding-left: 11px; + font-family: "Helvetica"; + font-size: 9px; + color: #999999; + float: right; + padding-top: 2px; + vertical-align: baseline; +} + +.status { + font-family: "Myriad"; + font-size: 9px; + font-weight: bold; + color: rgb(255,255,255); + /* #border: 1px solid #999999; */ +} +.statusmessage { + text-align: left; + /* #border: 1px solid #999999; */ +} +.statustime { + text-align: right; + /* #border: 1px solid #999999; */ +} + +.nextmessageline { + border-bottom: 1px dotted #CCCCCC; + height: 1px; + margin-top: 2px; + margin-bottom: 2px; +} + +#chat { + overflow: hidden; + margin-bottom: 10px; +} diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_alternate_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_alternate_left.css new file mode 100644 index 0000000..3f3ee94 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_alternate_left.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.out.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } +.in.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.in.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/standard/green/sender_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/standard/green/sender_context_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/flipped/blue/sender_left.png") left top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/flipped/blue/sender_context_left.png") left top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/orange/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/orange/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/orange/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_alternate_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_alternate_right.css new file mode 100644 index 0000000..a793b9b --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_alternate_right.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.out.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } +.in.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.in.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/flipped/green/sender_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/flipped/green/sender_context_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/standard/blue/sender_right.png") right top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/standard/blue/sender_context_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/orange/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/orange/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/orange/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_left.css new file mode 100644 index 0000000..bb6fe6f --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_left.css @@ -0,0 +1,59 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.out.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } +.in.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.in.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/standard/green/sender_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/standard/green/sender_context_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/standard/blue/sender_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.in.context .protocol { + background: url("../images/sender/standard/blue/sender_context_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/orange/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/orange/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/orange/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_right.css new file mode 100644 index 0000000..9ccc28b --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_orange_right.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.out.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } +.in.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.in.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/flipped/green/sender_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/flipped/green/sender_context_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/flipped/blue/sender_left.png") left top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/flipped/blue/sender_context_left.png") left top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/orange/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/orange/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/orange/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_alternate_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_alternate_left.css new file mode 100644 index 0000000..f1c16f1 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_alternate_left.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.out.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } +.in.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.in.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/standard/green/sender_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/standard/green/sender_context_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/flipped/blue/sender_left.png") left top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/flipped/blue/sender_context_left.png") left top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_alternate_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_alternate_right.css new file mode 100644 index 0000000..45e69ae --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_alternate_right.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.out.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } +.in.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.in.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/flipped/green/sender_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/flipped/green/sender_context_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/standard/blue/sender_right.png") right top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/standard/blue/sender_context_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_left.css new file mode 100644 index 0000000..f5efac2 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_left.css @@ -0,0 +1,59 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.out.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } +.in.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.in.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/standard/green/sender_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/standard/green/sender_context_right.png") right top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/standard/blue/sender_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.in.context .protocol { + background: url("../images/sender/standard/blue/sender_context_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_right.css new file mode 100644 index 0000000..91fe969 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/green_blue_purple_right.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(84,155,38); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.out.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } +.in.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.in.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/flipped/green/sender_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.out.context .protocol { + background: url("../images/sender/flipped/green/sender_context_left.png") left top no-repeat; + color: rgb(84,155,38); + } +.in.content .protocol { + background: url("../images/sender/flipped/blue/sender_left.png") left top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/flipped/blue/sender_context_left.png") left top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_all.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_all.css new file mode 100644 index 0000000..6819d43 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_all.css @@ -0,0 +1,62 @@ +.in {margin-bottom: 0px;} +.out {margin-bottom: 0px;} + +.message { + margin-left: 0px; + padding: 0px 11px 12px 0px; +} +.messagetop { + position: relative; + height: 12px; + top: -8px; + padding: 0px 0px 0px 0px; + margin: 0px 12px 0px 12px; +} +.messagetopleft { + position: relative; + float: left; + height: 12px; + top: 0px; + left: -12px; + padding: 0px 12px 0px 0px; + margin: 0px -13px 0px 0px; +} +.messagetopright { + position: relative; + float: right; + height: 12px; + top: 0px; + right: -12px; + padding: 0px 0px 0px 12px; + margin: 0px 0px 0px -13px; +} +.messagetextcontainer { + position: relative; + margin: -8px 0px 0px 0px; + padding: 0px 0px 0px 12px; +} +.status { + position: relative; + height: 23px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 30px 0px 30px; +} +.statusmessage { + position: relative; + float: left; + height: 23px; + top: 0px; + left: -30px; + padding: 5px 30px 0px 12px; + margin: 0px 0px 0px 0px; +} +.statustime { + position: absolute; + float: right; + height: 23px; + top: 0px; + right: -30px; + padding: 5px 12px 0px 18px; + margin: 0px 0px 0px 0px; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_alternate_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_alternate_left.css new file mode 100644 index 0000000..6ba6412 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_alternate_left.css @@ -0,0 +1,97 @@ +@import url("layout_all.css"); + +.out .nameheader { + position: relative; + height: 25px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 77px 0px 25px; +} +.in .nameheader { + position: relative; + height: 25px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 24px 0px 78px; +} +.out .name { + position: absolute; + float: left; + height: 25px; + top: 0px; + left: -25px; + right: 0px; + padding: 4px 0px 0px 12px; + margin: 0px 0px 0px 0px; +} +.in .name { + position: absolute; + text-align: right; + float: right; + height: 25px; + top: 0px; + left: 0px; + right: -25px; + padding: 4px 12px 0px 0px; + margin: 0px 0px 0px 0px; +} +.out .protocol { + position: absolute; + height: 25px; + top: 0px; + right: -78px; + padding: 6px 12px 0px 66px; + margin: 0px 0px -2px 0px; +} +.in .protocol { + position: absolute; + height: 25px; + top: 0px; + left: -78px; + padding: 6px 66px 0px 12px; + margin: 0px 0px -2px 0px; +} +.messagecontainer { + padding: 0px 0px 0px 0px; +} +.out .messagecontainer { + margin: 4px 0px 0px 48px; +} +.in .messagecontainer { + margin: 4px 48px 0px 0px; +} +.buddyicon { + position: relative; + top: -6px; + z-index: 4; +} +.buddyicon img { + height: 32px; + width: 32px; + z-index: 3; +} +.junkIcon { + /* #background: url("../images/buddy_background.png") top left no-repeat; + #height: 40px; + #width: 40px; + #padding: 2px 0px 0px 4px; + #border: 1px solid #999999; */ +} +.junkIcon img { + /* #margin-top: 2px; + #margin-left: 4px; */ +} +.out .buddyicon { + float: left; + margin-left: 10px; + margin-right: 0px; + margin-bottom: 0px; + margin-top: 4px; +} +.in .buddyicon { + float: right; + margin-left: 0px; + margin-bottom: 0px; + margin-top: 4px; + margin-right: 10px; +} diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_alternate_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_alternate_right.css new file mode 100644 index 0000000..bbdc257 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_alternate_right.css @@ -0,0 +1,93 @@ +@import url("layout_all.css"); + +.in .nameheader { + position: relative; + height: 25px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 77px 0px 25px; +} +.out .nameheader { + position: relative; + height: 25px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 24px 0px 78px; +} +.in .name { + position: absolute; + float: left; + height: 25px; + top: 0px; + left: -25px; + right: 0px; + padding: 4px 0px 0px 12px; + margin: 0px 0px 0px 0px; +} +.out .name { + position: absolute; + text-align: right; + float: right; + height: 25px; + top: 0px; + left: 0px; + right: -25px; + padding: 4px 12px 0px 0px; + margin: 0px 0px 0px 0px; +} +.in .protocol { + position: absolute; + height: 25px; + top: 0px; + right: -78px; + padding: 6px 12px 0px 66px; + margin: 0px 0px -2px 0px; +} +.out .protocol { + position: absolute; + height: 25px; + top: 0px; + left: -78px; + padding: 6px 66px 0px 12px; + margin: 0px 0px -2px 0px; +} +.messagecontainer { + padding: 0px 0px 0px 0px; +} +.in .messagecontainer { + margin: 4px 0px 0px 48px; +} +.out .messagecontainer { + margin: 4px 48px 0px 0px; +} +.buddyicon { + position: relative; + top: -6px; + /* #background: url("../images/buddy_background.png") top left no-repeat; + #height: 40px; + #width: 40px; */ + z-index: 4; + /* #padding: 2px 0px 0px 4px; + #border: 1px solid #999999; */ +} +.buddyicon img { + height: 32px; + width: 32px; + /* #margin-top: 2px; + #margin-left: 4px; */ + z-index: 3; +} +.in .buddyicon { + float: left; + margin-left: 10px; + margin-right: 0px; + margin-bottom: 0px; + margin-top: 4px; +} +.out .buddyicon { + float: right; + margin-left: 0px; + margin-bottom: 0px; + margin-top: 4px; + margin-right: 10px; +} diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_hidden.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_hidden.css new file mode 100644 index 0000000..ecb9fec --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_hidden.css @@ -0,0 +1,32 @@ +@import url("layout_all.css"); + +.nameheader { + position: relative; + height: 25px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 77px 0px 25px; +} +.name { + position: absolute; + float: left; + height: 25px; + top: 0px; + left: -25px; + right: 0px; + padding: 4px 0px 0px 12px; + margin: 0px 0px 0px 0px; +} +.protocol { + position: absolute; + height: 25px; + top: 0px; + right: -78px; + padding: 6px 12px 0px 66px; + margin: 0px 0px -2px 0px; +} +.messagecontainer { + padding: 0px 0px 0px 0px; + margin: 4px 0px 0px 0px; +} +.buddyicon {display: none;} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_left.css new file mode 100644 index 0000000..a81ff82 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_left.css @@ -0,0 +1,57 @@ +@import url("layout_all.css"); + +.nameheader { + position: relative; + height: 25px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 77px 0px 25px; +} +.name { + position: absolute; + float: left; + height: 25px; + top: 0px; + left: -25px; + right: 0px; + padding: 4px 0px 0px 12px; + margin: 0px 0px 0px 0px; +} +.protocol { + position: absolute; + height: 25px; + top: 0px; + right: -78px; + padding: 6px 12px 0px 50px; + margin: 0px 0px -2px 0px; +} +.messagecontainer { + padding: 0px 0px 0px 0px; + margin: 4px 0px 0px 48px; +} +.buddyicon img { + height: 32px; + width: 32px; + z-index: 3; +} +.buddyicon { + position: relative; + top: -6px; + z-index: 4; + float: left; + margin-left: 10px; + margin-right: 0px; + margin-bottom: 0px; + margin-top: 4px; +} +.junkIcon { + /* #background: url("../images/buddy_background.png") top left no-repeat; + #height: 40px; + #width: 40px; + #padding: 2px 0px 0px 4px; + #border: 1px solid #999999; */ +} +.junkIcon img { + /* #margin-top: 2px; + #margin-left: 4px; */ +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_right.css new file mode 100644 index 0000000..9bfbe8b --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/layout_right.css @@ -0,0 +1,53 @@ +@import url("layout_all.css"); + +.nameheader { + position: relative; + height: 25px; + top: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 24px 0px 78px; +} +.name { + position: absolute; + float: right; + height: 25px; + top: 0px; + right: -25px; + left: 0px; + padding: 4px 12px 0px 0px; + margin: 0px 0px 0px 0px; +} +.protocol { + position: absolute; + height: 25px; + top: 0px; + left: -78px; + padding: 6px 66px 0px 12px; + margin: 0px 0px -2px 0px; +} +.messagecontainer { + padding: 0px 0px 0px 0px; + margin: 4px 48px 0px 0px; +} +.buddyicon { + position: relative; + top: -6px; + /* #background: url("../images/buddy_background.png") top left no-repeat; + #height: 40px; + #width: 40px; */ + z-index: 4; + float: right; + margin-left: 0px; + margin-bottom: 0px; + margin-top: 4px; + margin-right: 10px; + /* #padding: 2px 0px 0px 4px; + #border: 1px solid #999999; */ +} +.buddyicon img { + height: 32px; + width: 32px; + /* #margin-top: 2px; + #margin-left: 4px; */ + z-index: 3; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/message_common.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/message_common.css new file mode 100644 index 0000000..c03044d --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/message_common.css @@ -0,0 +1,34 @@ +body { + background: #FFFFFF url("../images/background.png") bottom fixed repeat-x; +} + +.content .message { + background: url("../images/message/message_bot_right.png") bottom right no-repeat; +} +.context .message { + background: url("../images/message/message_context_bot_right.png") bottom right no-repeat; +} +.content .messagetextcontainer { + background: url("../images/message/message_bot_left.png") bottom left no-repeat; +} +.context .messagetextcontainer { + background: url("../images/message/message_context_bot_left.png") bottom left no-repeat; +} +.content .messagetop { + background: url("../images/message/message_top_fill.png") top repeat-x; +} +.context .messagetop { + background: url("../images/message/message_context_top_fill.png") top repeat-x; +} +.content .messagetopleft { + background: url("../images/message/message_top_left.png") left top no-repeat; +} +.context .messagetopleft { + background: url("../images/message/message_context_top_left.png") left top no-repeat; +} +.content .messagetopright { + background: url("../images/message/message_top_right.png") right top no-repeat; +} +.context .messagetopright { + background: url("../images/message/message_context_top_right.png") right top no-repeat; +} diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_alternate_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_alternate_left.css new file mode 100644 index 0000000..87929d0 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_alternate_left.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(170,17,17); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(170,17,17); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.out.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } +.in.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.in.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/standard/red/sender_right.png") right top no-repeat; + color: rgb(170,17,17); + } +.out.context .protocol { + background: url("../images/sender/standard/red/sender_context_right.png") right top no-repeat; + color: rgb(170,17,17); + } +.in.content .protocol { + background: url("../images/sender/flipped/blue/sender_left.png") left top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/flipped/blue/sender_context_left.png") left top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_alternate_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_alternate_right.css new file mode 100644 index 0000000..f2669d6 --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_alternate_right.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(170,17,17); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(170,17,17); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.out.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } +.in.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.in.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/flipped/red/sender_left.png") left top no-repeat; + color: rgb(170,17,17); + } +.out.context .protocol { + background: url("../images/sender/flipped/red/sender_context_left.png") left top no-repeat; + color: rgb(170,17,17); + } +.in.content .protocol { + background: url("../images/sender/standard/blue/sender_right.png") right top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/standard/blue/sender_context_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_left.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_left.css new file mode 100644 index 0000000..9491fcc --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_left.css @@ -0,0 +1,59 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(170,17,17); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(170,17,17); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.out.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } +.in.content .name { + background: url("../images/sender/standard/sender_left.png") left top no-repeat; + } +.in.context .name { + background: url("../images/sender/standard/sender_context_left.png") left top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/standard/red/sender_right.png") right top no-repeat; + color: rgb(170,18,18); + } +.out.context .protocol { + background: url("../images/sender/standard/red/sender_context_right.png") right top no-repeat; + color: rgb(170,18,18); + } +.in.content .protocol { + background: url("../images/sender/standard/blue/sender_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.in.context .protocol { + background: url("../images/sender/standard/blue/sender_context_right.png") right top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_right.css b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_right.css new file mode 100644 index 0000000..3200dce --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/styles/red_blue_purple_right.css @@ -0,0 +1,58 @@ +@import url("message_common.css"); + +.out.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(178,18,18); + } +.out.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(178,18,18); + } +.in.content .nameheader { + background: url("../images/sender/sender_fill.png") top repeat-x; + color: rgb(39,55,152); + } +.in.context .nameheader { + background: url("../images/sender/sender_context_fill.png") top repeat-x; + color: rgb(39,55,152); + } + +.out.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.out.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } +.in.content .name { + background: url("../images/sender/flipped/sender_right.png") right top no-repeat; + } +.in.context .name { + background: url("../images/sender/flipped/sender_context_right.png") right top no-repeat; + } + +.out.content .protocol { + background: url("../images/sender/flipped/red/sender_left.png") left top no-repeat; + color: rgb(178,18,18); + } +.out.context .protocol { + background: url("../images/sender/flipped/red/sender_context_left.png") left top no-repeat; + color: rgb(178,18,18); + } +.in.content .protocol { + background: url("../images/sender/flipped/blue/sender_left.png") left top no-repeat; + color: rgb(39,55,152); + } +.in.context .protocol { + background: url("../images/sender/flipped/blue/sender_context_left.png") left top no-repeat; + color: rgb(39,55,152); + } + +.status { + background: url("../images/status/purple/status_fill.png") top repeat-x; + } +.statusmessage { + background: url("../images/status/purple/status_left.png") left top no-repeat; + } +.statustime { + background: url("../images/status/purple/status_right.png") right top no-repeat; + } diff --git a/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/test.html b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/test.html new file mode 100644 index 0000000..54d6f2b --- /dev/null +++ b/digsby/res/MessageStyles/Satin.AdiumMessageStyle/Contents/Resources/test.html @@ -0,0 +1,160 @@ + + + + + + + + + +
+
Evan has gone offline
+
11:26:20
+
+
+ +
+
Evan come back online
+
11:26:45
+
+
+ +
+
Jeff (Me)
Yahoo!
+
+
+
+
+
+
+

This is going to be a message from the past5:27:45

+
+

Instant Messaging is really fun!!!20:29

+
+

Right?20:50

+
+
+
+
+
+ +
+
Evan (Buddy)
AIM
+
+
+
+
+
+
+

Not if your life depended on it5:28:01

+
+
+
+
+
+ +
+
Evan has gone away: this is going to be a really long status message so I can make sure wrapping is handled correctly...
+
11:28:13
+
+
+ +
+
A really long alias if it will fit into this view
AIM
+
+
+
+
+
+
+

This is a new message11:29:20

+
+

Instant Messaging is supposed to be fun...29:41

+
+

Right?30:50

+
+
+
+
+
+ +
+
Evan has gone idle
+
11:31:05
+
+
+ +
+
Jeff (Me)
Yahoo!
+
+
+
+
+
+
+

If given the opportunity, a person would write a book about their life story. It may be the most boring thing in the world to read, but you're the one who put them on your buddy list ;).11:31:50

+
+
+
+
+
+ +
+
Evan (Buddy)
Yahoo!
+
+
+
+
+
+
+

If given the opportunity, a person would write a book about their life story. It may be the most boring thing in the world to read, but you're the one who put them on your buddy list ;).11:38:20

+
+
+
+
+
+ +
+
Jeff (Me)
Yahoo!
+
+
+
+
+
+
+

I have no idea what you're talking about!11:40:10

+
+
+
+
+
+ + \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..d79a596 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + Smooth Operator Adium Message Style + CFBundleIdentifier + com.adiumx.smooth.operator.style + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + Smooth Operator + CFBundlePackageType + AdIM + MessageViewVersion + 3 + DefaultFontFamily + Lucida Grande + DefaultFontSize + 10 + ShowsUserIcons + + DisableCustomBackground + + DefaultBackgroundColor + FFFFFF + DefaultVariant + Normal + + diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..1f88757 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,26 @@ +
+
+
+
+
+
+
+
+
+
+
+ + %sender% + +
+
+ %time% +
+
+ +
+
+

%message%

+
+
+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..3a0564f --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,26 @@ +
+
+
+
+
+
+
+
+
+
+
+ + %sender% + +
+
+ %time% +
+
+ +
+
+

%message%

+
+
+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..2e88594 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,2 @@ +
%time{%M:%S}%

%message%

+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html new file mode 100644 index 0000000..2e88594 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/NextContext.html @@ -0,0 +1,2 @@ +
%time{%M:%S}%

%message%

+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png new file mode 100644 index 0000000..3a1943f Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..12966c2 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,26 @@ +
+
+
+
+
+
+
+
+
+
+
+ + %sender% + +
+
+ %time% +
+
+ +
+
+

%message%

+
+
+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..a68f920 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,26 @@ +
+
+
+
+
+
+
+
+
+
+
+ + %sender% + +
+
+ %time% +
+
+ +
+
+

%message%

+
+
+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..2e88594 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,2 @@ +
%time{%M:%S}%

%message%

+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html new file mode 100644 index 0000000..2e88594 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/NextContext.html @@ -0,0 +1,2 @@ +
%time{%M:%S}%

%message%

+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png new file mode 100644 index 0000000..e5edc36 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..b0171dd --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,9 @@ +
+
+ %message% +
+
+ %time% +
+
+
diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Variants/Normal.css b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Variants/Normal.css new file mode 100644 index 0000000..3d07c79 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/Variants/Normal.css @@ -0,0 +1 @@ +@import url('../main.css'); \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/arrow.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/arrow.png new file mode 100644 index 0000000..a07459e Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/arrow.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/background.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/background.png new file mode 100644 index 0000000..1814ca5 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/background.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_center.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_center.png new file mode 100644 index 0000000..a81a937 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_center.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_left.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_left.png new file mode 100644 index 0000000..08086c3 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_left.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_right.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_right.png new file mode 100644 index 0000000..7cb58db Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/bottom_right.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/buddy_background.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/buddy_background.png new file mode 100644 index 0000000..bda9ed5 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/buddy_background.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_center.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_center.png new file mode 100644 index 0000000..dbbecaa Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_center.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_left.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_left.png new file mode 100644 index 0000000..d06af08 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_left.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_right.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_right.png new file mode 100644 index 0000000..f08f3ad Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_bottom_right.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_center.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_center.png new file mode 100644 index 0000000..3488eca Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_center.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_left.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_left.png new file mode 100644 index 0000000..4dbad95 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_left.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_right.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_right.png new file mode 100644 index 0000000..5e48faf Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_middle_right.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_center.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_center.png new file mode 100644 index 0000000..ef6c531 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_center.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_left.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_left.png new file mode 100644 index 0000000..d75e829 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_left.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_right.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_right.png new file mode 100644 index 0000000..0d82feb Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/context_top_right.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_center.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_center.png new file mode 100644 index 0000000..fe8cd41 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_center.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_left.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_left.png new file mode 100644 index 0000000..01c8561 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_left.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_right.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_right.png new file mode 100644 index 0000000..e654916 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/middle_right.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_background.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_background.png new file mode 100644 index 0000000..04c9ea4 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_background.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_bottom.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_bottom.png new file mode 100644 index 0000000..7132cae Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_bottom.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left.png new file mode 100644 index 0000000..e68a2dd Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left_bottom.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left_bottom.png new file mode 100644 index 0000000..3768b62 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left_bottom.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left_top.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left_top.png new file mode 100644 index 0000000..679157d Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_left_top.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right.png new file mode 100644 index 0000000..ea9af85 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right_bottom.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right_bottom.png new file mode 100644 index 0000000..5b3ff0a Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right_bottom.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right_top.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right_top.png new file mode 100644 index 0000000..e37bee5 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_right_top.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_top.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_top.png new file mode 100644 index 0000000..f61055b Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/status_top.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_center.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_center.png new file mode 100644 index 0000000..7e4d8de Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_center.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_left.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_left.png new file mode 100644 index 0000000..01a9db1 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_left.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_right.png b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_right.png new file mode 100644 index 0000000..cf2b7e3 Binary files /dev/null and b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/images/top_right.png differ diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..c1c1dd5 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,286 @@ +body { + background: white url(images/background.png) center top repeat-x fixed; + font-weight: normal; + margin: 0 0 10px 0; +} + +.incoming { color: #a6002c; } +.context .incoming { color:#A65369; } + +.outgoing { color: #001190; } +.context .outgoing { color: #47508F; } + +.container { + position: relative; +} + +.context { +} +.context .top_left{ + background: url("images/context_top_left.png") top left no-repeat; +} + +.context .top_center{ + background: url("images/context_top_center.png") top left repeat-x; +} + +.context .top_right{ + background: url("images/context_top_right.png") top right no-repeat; +} + +.context .middle_left{ + background: url("images/context_middle_left.png") top left repeat-y; +} + +.context .middle_right{ + background: url("images/context_middle_right.png") top right repeat-y; +} + +.context .bottom_left{ + background: url("images/context_bottom_left.png") bottom left no-repeat; + +} + +.context .bottom_center{ + background: url("images/context_bottom_center.png") bottom left repeat-x; + +} + +.context .bottom_right{ + background: url("images/context_bottom_right.png") bottom right no-repeat; +} + +.context .middle_center{ + background: url("images/context_middle_center.png") top left repeat; +} +.top_left{ + position: absolute; + left: 0px; + top: 0px; + width: 16px; + height: 16px; + + background: url("images/top_left.png") top left no-repeat; +} + +.top_center{ + position: absolute; + left: 16px; + top: 0px; + right: 16px; + height: 16px; + + background: url("images/top_center.png") top left repeat-x; +} + +.top_right{ + position: absolute; + right: 0px; + top: 0px; + width: 16px; + height: 16px; + + background: url("images/top_right.png") top right no-repeat; +} + +.middle_left{ + position: absolute; + left: 0px; + top: 16px; + width: 41px; + bottom: 35px; + + background: url("images/middle_left.png") top left repeat-y; +} + +.middle_right{ + position: absolute; + right: 0px; + top: 16px; + width: 41px; + bottom: 35px; + + background: url("images/middle_right.png") top right repeat-y; +} + +.bottom_left{ + position: absolute; + left: 0px; + bottom: 0px; + width: 41px; + height: 35px; + + background: url("images/bottom_left.png") bottom left no-repeat; + +} + +.bottom_center{ + position: absolute; + left: 41px; + bottom: 0px; + right: 41px; + height: 35px; + + background: url("images/bottom_center.png") top left repeat-x; + +} + +.bottom_right{ + position: absolute; + right: 0px; + bottom: 0px; + width: 41px; + height: 35px; + + background: url("images/bottom_right.png") bottom right no-repeat; +} + +.middle_center{ + position: absolute; + left: 41px; + top: 16px; + right: 41px; + bottom: 35px; + + background: url("images/middle_center.png") top left repeat; +} + +.senderstring{ + + position: absolute; + left: 56px; + right: 40px; + top: 13px; + + z-index: 2; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + font-size: 11px; + font-weight: bold; + text-transform: lowercase; + text-shadow: 1px 1px 3px #ccc; + + margin-bottom: 0; +} + +.sender { + top: -1px; + left: 0px; + right: 38px; + height: 16px; + position: absolute; + padding: 13px 5px 0 56px; + margin-bottom: 0; +} + +.context .sender { +} + +.time_initial { + position: absolute; + right: 0; + top: -1px; + font-size: 9px; + font-weight: normal; + text-transform: uppercase; + text-shadow: 1px 1px 3px #ccc; + color: #808080; + padding: 14px 16px 0 15px; + margin-bottom: 0; +} + +.context .time_initial { +} + +.time_consecutive { + float: left; + background: 29px url("images/arrow.png") no-repeat; + margin-left: -40px; + margin-top: 5px; + font-size: 9px; + text-shadow: 1px 1px 3px #ddd; + color: #bfbfbf; +} + + +.buddyicon { + position: absolute; + background: url("images/buddy_background.png"); + top: 11px; + left: 12px; + z-index: 4; + height: 40px; + width: 40px; +} + +.buddyicon img { + position: absolute; + top: 3px; + left: 3px; + z-index: 3; +} + +.message { + position: relative; + top: 15px; + left: 0px; + margin-right: 19px; + text-shadow: 1px 1px 3px #ccc; + padding: 6px 0 25px 56px; + color: #404040; +} + +.context .message { + color: #666666; +} + + p { + padding: 0; + margin-bottom: 6px; + overflow: auto; +} + +.status_container { + position: relative; + background: url("images/status_background.png"); + margin: 0px 30px -1px 32px; + color: #fff; + text-shadow: 1px 1px 3px #aaa; + font-size: 9px; + font-weight: bold; + text-transform: lowercase; + height: 29px; +} + +.status_message { + position: relative; + left: -30px; + background: url("images/status_left.png") top left no-repeat; + padding: 7px 8px 7px 15px; +} + +.status_time { + position: absolute; + top: 0px; + right: -30px; + padding: 8px 16px 8px 5px; + text-transform: uppercase; + font-size: 8px; + font-weight: normal; + background: url("images/status_right.png") top right no-repeat; +} + +IMG { + vertical-align:middle; + top:-3px; +} + +img.scaledToFitImage{ width:auto; } + +#Chat { + overflow: hidden; + margin-bottom: -8px; +} diff --git a/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/testlayout.css b/digsby/res/MessageStyles/Smooth Operator.AdiumMessageStyle/Contents/Resources/testlayout.css new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Info.plist b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Info.plist new file mode 100644 index 0000000..dcca909 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + SmoothSeparateLines Adium Message Style + CFBundleIdentifier + de.proculo.smoothseperatelines.style + CFBundleInfoDictionaryVersion + 1.1 + CFBundleName + Smooth Separate Lines + CFBundlePackageType + AdIM + DefaultFontFamily + Lucida Grande + DefaultFontSize + 11 + DisableCombineConsecutive + + DisableCustomBackground + + DisplayNameForNoVariant + Red vs. Blue - No Time + MessageViewVersion + + ShowsUserIcons + + + diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Footer.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Footer.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Header.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Header.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/Content.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 0000000..7af5e7b --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,6 @@ +
+
+ [%time%] %sender% [%time%]: %message% +
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/Context.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/Context.html new file mode 100644 index 0000000..58b34a3 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/Context.html @@ -0,0 +1,6 @@ +
+
+ [%time%] %sender% [%time%]: %message% +
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 0000000..675cd1c --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,4 @@ +
+ [%time%] %sender% [%time%]: %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 0000000..dbe9043 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,6 @@ +
+
+ [%time%] %sender% [%time%]: %message% +
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html new file mode 100644 index 0000000..b4d7bf6 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/Context.html @@ -0,0 +1,6 @@ +
+
+ [%time%] %sender% [%time%]: %message% +
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 0000000..675cd1c --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,4 @@ +
+ [%time%] %sender% [%time%]: %message% +
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Status.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Status.html new file mode 100644 index 0000000..b6314f3 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Status.html @@ -0,0 +1,7 @@ +
+
+ %message% + %time% +
+
+
\ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - (Time) Name.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - (Time) Name.css new file mode 100644 index 0000000..4c5ccb5 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - (Time) Name.css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: inline; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #CB7100; +} + +.outgoing .prefix +{ + color: #694900; +} + +.incoming.context .prefix { + color: #CB7100; +} + +.outgoing.context .prefix { + color: #694900; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - Name (Time).css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - Name (Time).css new file mode 100644 index 0000000..ac22a82 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - Name (Time).css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: inline; +} + +.incoming .prefix { + color: #CB7100; +} + +.outgoing .prefix +{ + color: #694900; +} + +.incoming.context .prefix { + color: #CB7100; +} + +.outgoing.context .prefix { + color: #694900; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - No Time.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - No Time.css new file mode 100644 index 0000000..5f717f9 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Brown vs. Orange - No Time.css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #CB7100; +} + +.outgoing .prefix +{ + color: #694900; +} + +.incoming.context .prefix { + color: #CB7100; +} + +.outgoing.context .prefix { + color: #694900; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - (Time) Name.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - (Time) Name.css new file mode 100644 index 0000000..dab7449 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - (Time) Name.css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: inline; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #21632C; +} + +.outgoing .prefix +{ + color: #293027; +} + +.incoming.context .prefix { + color: #21632C; +} + +.outgoing.context .prefix { + color: #293027; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - Name (Time).css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - Name (Time).css new file mode 100644 index 0000000..b955a76 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - Name (Time).css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: inline; +} + +.incoming .prefix { + color: #21632C; +} + +.outgoing .prefix +{ + color: #293027; +} + +.incoming.context .prefix { + color: #21632C; +} + +.outgoing.context .prefix { + color: #293027; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - No Time.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - No Time.css new file mode 100644 index 0000000..599c20a --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Green vs. Grey - No Time.css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #21632C; +} + +.outgoing .prefix +{ + color: #293027; +} + +.incoming.context .prefix { + color: #21632C; +} + +.outgoing.context .prefix { + color: #293027; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - (Time) Name.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - (Time) Name.css new file mode 100644 index 0000000..fc781d9 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - (Time) Name.css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: inline; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #FF66CC; +} + +.outgoing .prefix +{ + color: #996699; +} + +.incoming.context .prefix { + color: #FF66CC; +} + +.outgoing.context .prefix { + color: #996699; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - Name (Time).css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - Name (Time).css new file mode 100644 index 0000000..a709a03 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - Name (Time).css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: inline; +} + +.incoming .prefix { + color: #FF66CC; +} + +.outgoing .prefix +{ + color: #993399; +} + +.incoming.context .prefix { + color: #FF66CC; +} + +.outgoing.context .prefix { + color: #993399; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - No Time.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - No Time.css new file mode 100644 index 0000000..267a231 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Pink vs. Purple - No Time.css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #FF66CC; +} + +.outgoing .prefix +{ + color: #993399; +} + +.incoming.context .prefix { + color: #FF66CC; +} + +.outgoing.context .prefix { + color: #993399; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Red vs. Blue - (Time) Name.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Red vs. Blue - (Time) Name.css new file mode 100644 index 0000000..2c6dc0f --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Red vs. Blue - (Time) Name.css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: inline; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #2B5674; +} + +.outgoing .prefix +{ + color: #99171D; +} + +.incoming.context .prefix { + color: #2B5674; +} + +.outgoing.context .prefix { + color: #99171D; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Red vs. Blue - Name (Time).css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Red vs. Blue - Name (Time).css new file mode 100644 index 0000000..c11de94 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/Variants/Red vs. Blue - Name (Time).css @@ -0,0 +1,42 @@ +@import url(../styles/layout.css); +@import url(../styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: inline; +} + +.incoming .prefix { + color: #2B5674; +} + +.outgoing .prefix +{ + color: #99171D; +} + +.incoming.context .prefix { + color: #2B5674; +} + +.outgoing.context .prefix { + color: #99171D; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/main.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/main.css new file mode 100644 index 0000000..2bcba8f --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/main.css @@ -0,0 +1,42 @@ +@import url(styles/layout.css); +@import url(styles/base.css); + +.prefix .timestampbefore { + display: none; +} + +.prefix .sender { + display: inline; +} + +.prefix .timestampafter { + display: none; +} + +.incoming .prefix { + color: #2B5674; +} + +.outgoing .prefix +{ + color: #99171D; +} + +.incoming.context .prefix { + color: #2B5674; +} + +.outgoing.context .prefix { + color: #99171D; +} + +.context .messagebody, .context .nextmessagebody { + color: #454642; + background-color: #FFFFFF; +} + +.status_message { + text-align: center; + font: 9px "Lucida Grande, Arial, Lucida Sans Unicode"; + color: #454642; +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/sample.html b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/sample.html new file mode 100644 index 0000000..74f6f09 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/sample.html @@ -0,0 +1,147 @@ + + + + + + + + + +
+
+
(20:41:49) eevyleevyl@mac.com (20:41:49): %message%
+
%message%
+
+
+
(20:41:49) eevyleevyl@mac.com (20:41:49): huhuhuhu
+
huhuhu
+
whatever we may take
+
+
+
+ (20:41:49) eevyleevyl@mac.com (20:41:49): + hu
+
+
+
+ (20:41:49) eevyleevyl@mac.com (20:41:49): + huhuhuhu +
+
huhuhu
+
whatever we may take
+
+
+ eevyl went away + (16:19) +
+
+ Away Message: Busy / Ocupado + (16:19) +
+
+ eevyl came back + (16:19) +
+
+
+ (20:41:49) eevyleevyl@mac.com (20:41:49): + huhuhuhu atest eth us heteshlthe tletshiet iso ehtioioseh tiseoteuo thseo oseht eh tset hshet stli ethsil etile thilsethilwsethilsthlsitehs ei +
+
+
huhuhu
+
+
ujdfbhfffdfsabhudfsabhjdfahbfjlfdabhjlsfdbjfdbhjfdbjfadbjafdlsafjbdlafdjlbafdsbjafdsbjfadsbjfdasjbfdasbjafdsbj
+
+
+
+ + diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/styles/base.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/styles/base.css new file mode 100644 index 0000000..968945b --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/styles/base.css @@ -0,0 +1,19 @@ +body { + padding: 0px; + margin: 0px; +} + +.prefix +{ + font: medium 11px "Lucida Grande, Arial"; +} + +.prefix { + font-weight: bold; + font-size: 85%; + +} + +.context + *[class="incoming"], .context + *[class="outgoing"] { + border-top: 2px solid rgb(200, 200, 200); +} \ No newline at end of file diff --git a/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/styles/layout.css b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/styles/layout.css new file mode 100644 index 0000000..5a8d4c4 --- /dev/null +++ b/digsby/res/MessageStyles/Smooth Separate Lines.AdiumMessageStyle/Contents/Resources/styles/layout.css @@ -0,0 +1,53 @@ +body +{ + margin: 0px; +} + +#Chat { + overflow: hidden; +} + +.bottomdivider { + width: 100%; + position: fixed; + bottom: 0px; + margin: 0px; + padding: 0px; + left: 0px; +} + +.status_message { + padding: 0px 7px 0px 7px; +} + +.prefixcontainer{ + white-space: nowrap; + max-width: 100%; + position: relative; + left: -22px; +} + +.prefix { + white-space: normal; + position: relative; +} + +.timestampbefore, .timestampafter{ + white-space: nowrap; +} + +.messagebody { + padding: 1px 7px 1px 1px; + max-width: 100%; + margin-left: 23px; +} + +.nextmessagebody { + padding: 1px 7px 1px 1px; + margin-left: 23px; +} + +.message{ + margin-left: -23px; + margin-right: 23px; +} diff --git a/digsby/res/MessageStyles/Template.html b/digsby/res/MessageStyles/Template.html new file mode 100644 index 0000000..ce27349 --- /dev/null +++ b/digsby/res/MessageStyles/Template.html @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + +%@ +
+
+%@ + + diff --git a/digsby/res/actions.yaml b/digsby/res/actions.yaml new file mode 100644 index 0000000..dff7121 --- /dev/null +++ b/digsby/res/actions.yaml @@ -0,0 +1,357 @@ +# +# actions.yaml +# +# +# This file specifies the ordering and layout of actions as they appear +# in user interface elements. +# + +# the buddylist right click menu displays the following "common" actions for all buddies: + +Contact: + + - call: buddy_info + name: !_ '&Buddy Info' + + - call: chat + name: !_ "&Send IM" + + - call: send_file + name: !_ 'Send &File' + + - call: send_email + name: !_ 'Send &Email' + + - call: send_sms + name: !_ 'Send &SMS' + + - --------- + + - call: view_past_chats + name: !_ '&View Past Chats' + + - call: edit_alerts + name: !_ '&Alert Me When...' + + - --------- + + - call: rename_gui + name: !_ 'Re&name' + + - call: block + name: !_ 'Bl&ock' + gui: gui.protocols.block_buddy + + - call: unblock + name: !_ 'Unbl&ock' + + - call: remove + name: !_ "&Delete" + gui: gui.protocols.remove_contact + +YahooContact: + + - -------------------------- + + - call: view_past_chats + name: !_ '&View Past Chats' + + - call: block + name: !_ 'Bl&ock' + gui: gui.protocols.block_buddy + + - call: unblock + name: !_ 'Unbl&ock' + + - method: gui.buddylist.buddylistmenu.yahoo_buddy_menu + +OscarContact: + + - call: view_past_chats + name: !_ '&View Past Chats' + + - call: block + name: !_ 'Bl&ock' + gui: gui.protocols.block_buddy + + - call: unblock + name: !_ 'Unbl&ock' + +JabberContact: + + - -------------------------- + + - call: view_past_chats + name: !_ '&View Past Chats' + + - method: gui.buddylist.buddylistmenu.jabber_buddy_menu + + - call: subscribed + name: !_ 'Resend authorization' + + - call: subscribe + name: !_ "Re-request authorization" + + - call: unsubscribed + name: !_ "Remove authorization" + +MSNContact: + + - call: view_past_chats + name: !_ '&View Past Chats' + + - call: appear_offline + name: !_ "Appear offline" + + - call: appear_online + name: !_ "Appear online" + + - call: block + name: !_ 'Bl&ock' + gui: gui.protocols.block_buddy + + - call: unblock + name: !_ 'Unbl&ock' + +MetaContact: + + - call: buddy_info + name: !_ '&Buddy Info' + + - call: chat + name: !_ "&Send IM" + + - call: send_file + name: !_ 'Send &File' + + - call: send_email + name: !_ 'Send &Email' + + - call: send_sms + name: !_ 'Send &SMS' + + - --------- + + - call: edit_alerts + name: !_ '&Alert Me When...' + + - --------- + + - call: rename_gui + name: !_ 'Re&name && Rearrange...' + + - call: explode + name: !_ 'Split Merged Contact...' + + - ---------------- + + - method: gui.buddylist.buddylistmenu.metacontact_buddy_menu + +DGroup: + + - method: gui.addcontactdialog.groupaddcontact + + - call: rename_gui + name: !_ "&Rename Group" + + - call: delete + name: !_ '&Delete Group' + gui: gui.protocols.remove_group + + - call: add_group + name: !_ 'Add &Group' + +Protocol: + + - call: Connect + name: !_ '&Connect' + + - call: Disconnect + name: !_ '&Disconnect' + +OscarProtocol: + + - call: format_screenname + name: !_ '&Format Screen Name' + gui: gui.protocols.oscargui.format_screenname + + - call: set_account_email + name: !_ '&Update Email Address' + gui: gui.protocols.oscargui.set_account_email + + - call: request_account_confirm + name: !_ '&Confirm Account' + + - call: change_password + name: !_ 'Change &Password (URL)' + + - call: im_forwarding + name: !_ '&IM Forwarding (URL)' + + - call: aol_alerts + name: !_ '&AOL Alerts (URL)' + + - call: my_icq_page + name: !_ '&My Page on ICQ.com (URL)' + +JabberProtocol: + + - call: set_priority + name: !_ 'Set Priority' + gui: gui.protocols.jabbergui.set_priority + + - call: change_password + name: !_ 'Change &Password' + gui: gui.protocols.jabbergui.PasswordChangeDialog + + - call: xml_console + name: '&XML Console' + +MSNClient: + + - call: search_for_contact + name: !_ '&Search for a Contact' + + - call: address_book + name: !_ 'Address Book' + + - call: mobile_settings + name: !_ 'Mobile Settings' + + - call: my_profile + name: !_ 'My Account' + + - call: CreateCircle + name: !_ 'Create Group Chat' + +YahooProtocol: + + - call: my_account_info + name: !_ 'My Account Info (URL)' + + - call: my_web_profile + name: !_ 'My Web Profile (URL)' + +FacebookChat: + - call: _update + name: !_ 'Update presence' + +EmailAccount: + - call: OnClickInboxURL + name: !_ "Open &Inbox" + + - call: OnComposeEmail + name: !_ 'C&ompose...' + + - ------------------------ + + - call: rename_gui + name: !_ "&Rename" + + - ------------------------ + + - call: update_now + name: !_ '&Check Now' + + - call: tell_me_again + name: !_ '&Tell Me Again' + +AOLMail: + - call: open + name: !_ '&Open' + - call: markAsRead + name: !_ 'Mark as &Read' + - call: archive + name: !_ '&Archive' + - call: delete + name: !_ '&Delete' + - call: reportSpam + name: !_ 'Report &Spam' + +Gmail: + - call: open + name: !_ '&Open' + - call: markAsRead + name: !_ 'Mark as &Read' + - call: archive + name: !_ '&Archive' + - call: delete + name: !_ '&Delete' + - call: reportSpam + name: !_ 'Report &Spam' + +Hotmail: + - call: open + name: !_ '&Open' + - call: markAsRead + name: !_ 'Mark as &Read' + - call: delete + name: !_ '&Delete' + - call: reportSpam + name: !_ 'Report &Spam' + +IMAPMail: + - call: open + name: !_ '&Open' + - call: markAsRead + name: !_ 'Mark as &Read' + - call: delete + name: !_ '&Delete' + +PopMail: + - call: open + name: !_ '&Open' + - call: delete + name: !_ '&Delete' + +YahooMail: + - call: open + name: !_ '&Open' + - call: markAsRead + name: !_ 'Mark as &Read' + - call: delete + name: !_ '&Delete' + - call: reportSpam + name: !_ 'Report &Spam' + +Myspace: + - call: update_now + name: !_ '&Update Now' + + - call: OpenHome + name: !_ '&Home' + + - call: OpenProfile + name: !_ '&Profile' + + - call: OpenMail + name: !_ '&Inbox' + + - call: OpenFriends + name: !_ '&Friends' + + - call: OpenBlog + name: !_ '&Blog' + + - call: OpenBulletins + name: !_ 'B&ulletins' + - ------------------------ + - call: edit_status + name: !_ '&Set Status' + +SearchWebGroup: + - call: edit + name: !_ 'Edit...' + +SearchEntry: + - call: activate + name: ${obj.menu_search_string} + - ----------- + - call: delete + name: !_ 'Delete' + +IRCClient: +# - name: !_ Join Room +# call: join_chat_room +# gui: irc.ircgui.join_chat_room + diff --git a/digsby/res/ca-bundle.crt b/digsby/res/ca-bundle.crt new file mode 100644 index 0000000..41bda24 --- /dev/null +++ b/digsby/res/ca-bundle.crt @@ -0,0 +1,3509 @@ +## +## lib/ca-bundle.crt -- Bundle of CA Root Certificates +## +## Converted at: Sat Feb 6 17:17:09 2010 UTC +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## '/mozilla/security/nss/lib/ckfw/builtins/certdata.txt' +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## + +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1994-2000 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** +# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.53 $ $Date: 2009/05/21 19:50:28 $ + +Verisign/RSA Secure Server CA +============================= +-----BEGIN CERTIFICATE----- +MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx +IDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVow +XzELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQL +EyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUA +A4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1Xp +sSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJ +VCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw4jrg7HFG +mhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2 +qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== +-----END CERTIFICATE----- + +GTE CyberTrust Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIB+jCCAWMCAgGjMA0GCSqGSIb3DQEBBAUAMEUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xHDAaBgNVBAMTE0dURSBDeWJlclRydXN0IFJvb3QwHhcNOTYwMjIzMjMwMTAw +WhcNMDYwMjIzMjM1OTAwWjBFMQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9u +MRwwGgYDVQQDExNHVEUgQ3liZXJUcnVzdCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQC45k+625h8cXyvRLfTD0bZZOWTwUKOx7pJjTUteueLveUFMVnGsS8KDPufpz+iCWaEVh43KRuH +6X4MypqfpX/1FZSj1aJGgthoTNE3FQZor734sLPwKfWVWgkWYXcKIiXUT0Wqx73llt/51KiOQswk +wB6RJ0q1bQaAYznEol44AwIDAQABMA0GCSqGSIb3DQEBBAUAA4GBABKzdcZfHeFhVYAA1IFLezEP +I2PnPfMD+fQ2qLvZ46WXTeorKeDWanOB5sCJo9Px4KWlIjeaY8JIILTbcuPI9tl8vrGvU9oUtCG4 +1tWW4/5ODFlitppK+ULdjG+BqXH/9ApybW1EDp3zdHSo1TRJ6V6e6bR64eVaH4QwnNOfpSXY +-----END CERTIFICATE----- + +GTE CyberTrust Global Root +========================== +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG +A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz +MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL +Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 +IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u +sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql +HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID +AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW +M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF +NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +Thawte Personal Basic CA +======================== +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYDVQQKExFUaGF3dGUgQ29uc3Vs +dGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMY +VGhhd3RlIFBlcnNvbmFsIEJhc2ljIENBMSgwJgYJKoZIhvcNAQkBFhlwZXJzb25hbC1iYXNpY0B0 +aGF3dGUuY29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgcsxCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UEChMRVGhh +d3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24x +ITAfBgNVBAMTGFRoYXd0ZSBQZXJzb25hbCBCYXNpYyBDQTEoMCYGCSqGSIb3DQEJARYZcGVyc29u +YWwtYmFzaWNAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvLyTU23AUE+C +FeZIlDWmWr5vQvoPR+53dXLdjUmbllegeNTKP1GzaQuRdhciB5dqxFGTS+CN7zeVoQxN2jSQHReJ +l+A1OFdKwPQIcOk8RHtQfmGakOMj04gRRif1CwcOu93RfyAKiLlWCy4cgNrx454p7xS9CkT7G1sY +0b8jkyECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAt4plrsD16 +iddZopQBHyvdEktTwq1/qqcAXJFAVyVKOKqEcLnZgA+le1z7c8a914phXAPjLSeoF+CEhULcXpvG +t7Jtu3Sv5D/Lp7ew4F2+eIMllNLbgQ95B21P9DkVWlIBe94y1k049hJcBlDfBVu9FEuh3ym6O0GN +92NWod8isQ== +-----END CERTIFICATE----- + +Thawte Personal Premium CA +========================== +-----BEGIN CERTIFICATE----- +MIIDKTCCApKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBzzELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYDVQQKExFUaGF3dGUgQ29uc3Vs +dGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEjMCEGA1UEAxMa +VGhhd3RlIFBlcnNvbmFsIFByZW1pdW0gQ0ExKjAoBgkqhkiG9w0BCQEWG3BlcnNvbmFsLXByZW1p +dW1AdGhhd3RlLmNvbTAeFw05NjAxMDEwMDAwMDBaFw0yMDEyMzEyMzU5NTlaMIHPMQswCQYDVQQG +EwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xGjAYBgNVBAoT +EVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlz +aW9uMSMwIQYDVQQDExpUaGF3dGUgUGVyc29uYWwgUHJlbWl1bSBDQTEqMCgGCSqGSIb3DQEJARYb +cGVyc29uYWwtcHJlbWl1bUB0aGF3dGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJ +Ztn4B0TPuYwu8KHvE0VsBd/eJxZRNkERbGw77f4QfRKe5ZtCmv5gMcNmt3M6SK5O0DI3lIi1DbbZ +8/JE2dWIEt12TfIa/G8jHnrx2JhFTgcQ7xZC0EN1bUre4qrJMf8fAHB8Zs8QJQi6+u4A6UYDZicR +FTuqW/KY3TZCstqIdQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBAUAA4GB +AGk2ifc0KjNyL2071CKyuG+axTZmDhs8obF1Wub9NdP4qPIHb4Vnjt4rueIXsDqg8A6iAJrf8xQV +brvIhVqYgPn/vnQdPfP+MCXRNzRn+qVxeTBhKXLA4CxM+1bkOqhv5TJZUtt1KFBZDPgLGeSs2a+W +jS9Q2wfD6h+rM+D1KzGJ +-----END CERTIFICATE----- + +Thawte Personal Freemail CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDLTCCApagAwIBAgIBADANBgkqhkiG9w0BAQQFADCB0TELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYDVQQKExFUaGF3dGUgQ29uc3Vs +dGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMb +VGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1mcmVl +bWFpbEB0aGF3dGUuY29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgdExCzAJBgNV +BAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UE +ChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 +aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBGcmVlbWFpbCBDQTErMCkGCSqGSIb3DQEJ +ARYccGVyc29uYWwtZnJlZW1haWxAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEA1GnX1LCUZFtx6UfYDFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyeflXtL734Zhx2G6qPduc6W +ZBrCFG5ErHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56fAylS1V/Bhkpf56aJtVquzgk +CGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQF +AAOBgQDH7JJ+Tvj1lqVnYiqk8E0RYNBvjWBYYawmu1I1XAjPMPuoSpaKH2JCI4wXD/S6ZJwXrEcp +352YXtJsYHFcoqzceePnbgBHH7UNKOgCneSa/RP0ptl8sfjcXyMmCZGAc9AUG95DqYMl8uacLxXK +/qarigd1iwzdUYRr5PjRzneigQ== +-----END CERTIFICATE----- + +Thawte Server CA +================ +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE +AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j +b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV +BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u +c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG +A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 +ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl +/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 +1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J +GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ +GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +Thawte Premium Server CA +======================== +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE +AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl +ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU +VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 +aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ +cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 +aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh +Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ +qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm +SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf +8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t +UCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +Equifax Secure CA +================= +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE +ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT +B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR +fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW +8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG +A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE +CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG +A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS +spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB +Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 +zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB +BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 +70+sB3c4 +-----END CERTIFICATE----- + +ABAecom (sub., Am. Bankers Assn.) Root CA +========================================= +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIRANAeQJAAAEZSAAAAAQAAAAQwDQYJKoZIhvcNAQEFBQAwgYkxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJEQzETMBEGA1UEBxMKV2FzaGluZ3RvbjEXMBUGA1UEChMOQUJBLkVD +T00sIElOQy4xGTAXBgNVBAMTEEFCQS5FQ09NIFJvb3QgQ0ExJDAiBgkqhkiG9w0BCQEWFWFkbWlu +QGRpZ3NpZ3RydXN0LmNvbTAeFw05OTA3MTIxNzMzNTNaFw0wOTA3MDkxNzMzNTNaMIGJMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCREMxEzARBgNVBAcTCldhc2hpbmd0b24xFzAVBgNVBAoTDkFCQS5F +Q09NLCBJTkMuMRkwFwYDVQQDExBBQkEuRUNPTSBSb290IENBMSQwIgYJKoZIhvcNAQkBFhVhZG1p +bkBkaWdzaWd0cnVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx0xHgeVVD +BwhMywVCAOINg0Y95JO6tgbTDVm9PsHOQ2cBiiGo77zM0KLMsFWWU4RmBQDaREmA2FQKpSWGlO1j +Vv9wbKOhGdJ4vmgqRF4vz8wYXke8OrFGPR7wuSw0X4x8TAgpnUBV6zx9g9618PeKgw6hTLQ6pbNf +WiKX7BmbwQVo/ea3qZGULOR4SCQaJRk665WcOQqKz0Ky8BzVX/tr7WhWezkscjiw7pOp03t3POtx +A6k4ShZsiSrK2jMTecJVjO2cu/LLWxD4LmE1xilMKtAqY9FlWbT4zfn0AIS2V0KFnTKo+SpU+/94 +Qby9cSj0u5C8/5Y0BONFnqFGKECBAgMBAAGjFjAUMBIGA1UdEwEB/wQIMAYBAf8CAQgwDQYJKoZI +hvcNAQEFBQADggEBAARvJYbk5pYntNlCwNDJALF/VD6Hsm0kqS8Kfv2kRLD4VAe9G52dyntQJHsR +W0mjpr8SdNWJt7cvmGQlFLdh6X9ggGvTZOirvRrWUfrAtF13Gn9kCF55xgVM8XrdTX3O5kh7VNJh +koHWG9YA8A6eKHegTYjHInYZw8eeG6Z3ePhfm1bR8PIXrI6dWeYf/le22V7hXZ9F7GFoGUHhsiAm +/lowdiT/QHI8eZ98IkirRs3bs4Ysj78FQdPB4xTjQRcm0HyncUwZ6EoPclgxfexgeqMiKL0ZJGA/ +O4dzwGvky663qyVDslUte6sGDnVdNOVdc22esnVApVnJTzFxiNmIf1Q= +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 1 +======================================= +-----BEGIN CERTIFICATE----- +MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE +ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMTAeFw05ODEy +MTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs +IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQCgbIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJE +NySZj9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlVSn5JTe2i +o74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo +BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0 +dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw +IoAPMTk5ODEyMTAxODEwMjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQY +MBaAFGp5fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i+DAM +BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB +ACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lNQseSJqBcNJo4cvj9axY+IO6CizEq +kzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4 +RbyhkwS7hp86W0N6w4pl +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 3 +======================================= +-----BEGIN CERTIFICATE----- +MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE +ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMjAeFw05ODEy +MDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs +IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQC/k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGOD +VvsoLeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3oTQPMx7JS +xhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo +BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0 +dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw +IoAPMTk5ODEyMDkxOTE3MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQY +MBaAFB6CTShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5WzAM +BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB +AEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHRxdf0CiUPPXiBng+xZ8SQTGPdXqfi +up/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVLB3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1 +mPnHfxsb1gYgAlihw6ID +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 2 +======================================= +-----BEGIN CERTIFICATE----- +MIID2DCCAsACEQDQHkCLAAACfAAAAAIAAAABMA0GCSqGSIb3DQEBBQUAMIGpMQswCQYDVQQGEwJ1 +czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0 +YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgWDExFjAUBgNVBAMTDURTVCBS +b290Q0EgWDExITAfBgkqhkiG9w0BCQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTAeFw05ODEyMDExODE4 +NTVaFw0wODExMjgxODE4NTVaMIGpMQswCQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UE +BxMOU2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjER +MA8GA1UECxMIRFNUQ0EgWDExFjAUBgNVBAMTDURTVCBSb290Q0EgWDExITAfBgkqhkiG9w0BCQEW +EmNhQGRpZ3NpZ3RydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANLGJrbn +pT3BxGjVUG9TxW9JEwm4ryxIjRRqoxdfWvnTLnUv2Chi0ZMv/E3Uq4flCMeZ55I/db3rJbQVwZsZ +PdJEjdd0IG03Ao9pk1uKxBmd9LIO/BZsubEFkoPRhSxglD5FVaDZqwgh5mDoO3TymVBRaNADLbGA +vqPYUrBEzUNKcI5YhZXhTizWLUFv1oTnyJhEykfbLCSlaSbPa7gnYsP0yXqSI+0TZ4KuRS5F5X5y +P4WdlGIQ5jyRoa13AOAV7POEgHJ6jm5gl8ckWRA0g1vhpaRptlc1HHhZxtMvOnNn7pTKBBMFYgZw +I7P0fO5F2WQLW0mqpEPOJsREEmy43XkCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAojeyP2n714Z5 +VEkxlTMr89EJFEliYIalsBHiUMIdBlc+LegzZL6bqq1fG03UmZWii5rJYnK1aerZWKs17RWiQ9a2 +vAd5ZWRzfdd5ynvVWlHG4VMElo04z6MXrDlxawHDi1M8Y+nuecDkvpIyZHqzH5eUYr3qsiAVlfuX +8ngvYzZAOONGDx3drJXK50uQe7FLqdTF65raqtWjlBRGjS0f8zrWkzr2Pnn86Oawde3uPclwx12q +gUtGJRzHbBXjlU4PqjI3lAoXJJIThFjSY28r9+ZbYgsTF7ANUkz+/m9c4pFuHf2kYtdo+o56T9II +2pPc8JIRetDccpMMc5NihWjQ9A== +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 4 +======================================= +-----BEGIN CERTIFICATE----- +MIID2DCCAsACEQDQHkCLAAB3bQAAAAEAAAAEMA0GCSqGSIb3DQEBBQUAMIGpMQswCQYDVQQGEwJ1 +czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0 +YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgWDIxFjAUBgNVBAMTDURTVCBS +b290Q0EgWDIxITAfBgkqhkiG9w0BCQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTAeFw05ODExMzAyMjQ2 +MTZaFw0wODExMjcyMjQ2MTZaMIGpMQswCQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UE +BxMOU2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjER +MA8GA1UECxMIRFNUQ0EgWDIxFjAUBgNVBAMTDURTVCBSb290Q0EgWDIxITAfBgkqhkiG9w0BCQEW +EmNhQGRpZ3NpZ3RydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANx18IzA +dZaawGIfJvfE4Zrq4FZzW5nNAUSoCLbVp9oaBBg5kkp4o4HC9Xd6ULRw/5qrxsfKboNPQpj7Jgva +3G3WqZlVUmfpKAOS3OWwBZoPFflrWXJW8vo5/Kpo7g8fEIMv/J36F5bdguPmRX3AS4BEH+0s4IT9 +kVySVGkl5WJp3OXuAFK9MwutdQKFp2RQLcUZGTDAJtvJ0/0uma1ZtQtN1EGuhUhDWdy3qOKi3sOP +17ihYqZoUFLkzzGnlIXan0YyF1bl8utmPRL/Q9uY73fPy4GNNLHGUEom0eQ+QVCvbK4iNC7Va26D +unm4dmVI2gkpZGMiuftHdoWMhkTLCdsCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAtTYOXeFhKFoR +ZcA/gwN5Tb4opgsHAlKFzfiR0BBstWogWxyQ2TA8xkieil5k+aFxd+8EJx8H6+Qm93N0yUQYGmbT +4EOvkTvRyyzYdFQ6HE3K1GjNI3wdEJ5F6fYAbqbNGf9PLCmPV03Ed5K+4EwJ+11EhmYhqLkyolbV +6YyDfFk/xPEL553snr2cGA4+wjl5KLcDDQjLxufZATdQEOzMYRZA1K8xdHv8PzGn0EdzMzkbzE5q +10mDEQb+64JYMzJM8FasHpwvVpp7wUocpf1VNs78lk30sPDst2yC7S8xmUJMqbINuBVd8d+6ybVK +1GSYsyapMMj9puyrliGtf8J4tg== +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPTCCAaYCEQDNun9W8N/kvFT+IqyzcqpVMA0GCSqGSIb3DQEBAgUAMF8xCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05NjAxMjkwMDAwMDBaFw0yODA4MDEyMzU5NTla +MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3Mg +MSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEA5Rm/baNWYS2ZSHH2Z965jeu3noaACpEO+jglr0aIguVzqKCbJF0NH8xlbgyw +0FaEGIeaBpsQoXPftFg5a27B9hXVqKg/qhIGjTGsf7A01480Z4gJzRQR4k5FVmkfeAKA2txHkSm7 +NsljXMXg1y2He6G3MrB7MLoqLzGq7qNn2tsCAwEAATANBgkqhkiG9w0BAQIFAAOBgQBMP7iLxmjf +7kMzDl3ppssHhE16M/+SG/Q2rdiVIjZoEWx8QszznC7EBz8UsA9P/5CSdvnivErpj82ggAr3xSnx +giJduLHdgSOjeyUVRjB5FvjqBUuUfx3CHMjjt/QQQDwTw18fU+hI5Ia0e6E1sHslurjTjqs/OJ0A +NACY89FxlA== +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEC0b/EoXjaOR6+f/9YtFvgswDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAyIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAy +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQC2WoujDWojg4BrzzmH9CETMwZMJaLtVRKXxaeAufqDwSCg+i8VDXyhYGt+eSz6 +Bg86rvYbb7HS/y8oUl+DfUvEerf4Zh+AVPy3wo5ZShRXRtGak75BkQO7FYCTXOvnzAhsPz6zSvz/ +S2wj1VCCJkQZjiPDceoZJEcEnnW/yKYAHwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBAIobK/o5wXTX +XtgZZKJYSi034DNHD6zt96rbHuSLBlxgJ8pFUs4W7z8GZOeUaHxgMxURaa+dYo2jA1Rrpr7l7gUY +YAS/QoD90KioHgE796Ncr6Pc5iaAIzy4RHT3Cq5Ji2F4zCS/iIqnDupzGUH9TQPwiNHleI2lKk/2 +lw0Xd8rY +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA +TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah +WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf +Tqj/ZA1k +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgd +k4xWArzZbxpvUjZudVYKVdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIq +WpDBucSmFc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0Jh9ZrbWB85a7FkCMM +XErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2uluIncrKTdcu1OofdPvAbT6shkdHvC +lUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68DzFc6PLZ +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1h +cnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNp +Z24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1 +c3QgTmV0d29yazAeFw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1h +cnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNp +Z24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1 +c3QgTmV0d29yazCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjx +nNuX6Zr8wgQGE75fUsjMHiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRC +wiNPStjwDqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cCAwEA +ATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9jinb3/7aHmZuovCfTK +1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAXrXfMSTWqz9iP0b63GJZHc2pUIjRk +LbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnInjBJ7xUS0rg== +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO +FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 +lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT +1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD +Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEDKIjprS9esTR/h/xCA3JfgwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgNCBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgNCBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC68OTP+cSuhVS5B1f5j8V/aBH4 +xBewRNzjMHPVKmIquNDMHO0oW369atyzkSTKQWI8/AIBvxwWMZQFl3Zuoq29YRdsTjCG8FE3KlDH +qGKB3FtKqsGgtG7rL+VXxbErQHDbWk2hjh+9Ax/YA9SPTJlxvOKCzFjomDqG04Y48wApHwIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAIWMEsGnuVAVess+rLhDityq3RS6iYF+ATwjcSGIL4LcY/oCRaxF +WdcqWERbt5+BO5JoPeI3JPV7bI92NZYJqFmduc4jq3TWg/0ycyfYaT5DdPauxYma51N86Xv2S/PB +ZYPejYqcPIiNOVn8qj8ijaHBZlCBckztImRPT8qAkbYp +-----END CERTIFICATE----- + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +GlobalSign Root CA - R2 +======================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 +ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp +s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN +S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL +TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C +ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i +YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN +BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp +9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu +01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 +9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +ValiCert Class 1 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy +MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi +GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm +DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG +lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX +icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP +Orf1LXLI +-----END CERTIFICATE----- + +ValiCert Class 2 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC +CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf +ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ +SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV +UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 +W9ViH0Pd +-----END CERTIFICATE----- + +RSA Root Certificate 1 +====================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td +3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H +BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs +3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF +V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r +on+jjBXu +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAN2E1Lm0+afY8wR4nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/E +bRrsC+MO8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjVojYJ +rKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjbPG7PoBMAGrgnoeS+ +Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP26KbqxzcSXKMpHgLZ2x87tNcPVkeB +FQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vrn5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +q2aN17O6x5q25lXQBfGfMY1aqtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/N +y9Sn2WCVhDr4wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 +ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrspSCAaWihT37h +a88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4E1Z5T21Q6huwtVexN2ZYI/Pc +D98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29y +azE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ug +b25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1 +c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y +aXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArwoNwtUs22e5LeWUJ92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6 +tW8UvxDOJxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUYwZF7 +C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9okoqQHgiBVrKtaaNS +0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjNqWm6o+sdDZykIKbBoMXRRkwXbdKs +Zj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/ESrg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0 +JhU8wI1NQ0kdvekhktdmnLfexbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf +0xwLRtxyID+u7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU +sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RIsH/7NiXaldDx +JBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTPcjnhsUPgKM+351psE2tJs//j +GHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 +EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc +cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw +EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj +055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f +j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 +xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa +t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS +tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM +8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW +Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX +Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt +mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd +RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG +UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +Entrust.net Secure Server CA +============================ +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV +BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg +cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl +ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG +A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi +eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p +dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ +aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 +gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw +ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw +CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l +dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw +NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow +HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA +BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN +Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 +n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +Entrust.net Secure Personal CA +============================== +-----BEGIN CERTIFICATE----- +MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UEBhMCVVMxFDASBgNV +BAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50cnVzdC5uZXQvQ2xpZW50X0NBX0luZm8v +Q1BTIGluY29ycC4gYnkgcmVmLiBsaW1pdHMgbGlhYi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1 +c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBaMIHJMQswCQYDVQQGEwJV +UzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRf +Q0FfSW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMpIDE5 +OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDIOpleMRffrCdv +kHvkGf9FozTC28GoT/Bo6oT9n3V5z8GKUZSvx1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGp +OZ5V+Pux5zDeg7K6PvHViTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zmAqTm +T173iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSCARkwggEVMIHkoIHh +oIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNVBAsUP3d3 +dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBs +aWFiLjElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50 +cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCyg +KqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9DbGllbnQxLmNybDArBgNVHRAEJDAigA8x +OTk5MTAxMjE5MjQzMFqBDzIwMTkxMDEyMTkyNDMwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU +xPucKXuXzUyW/O5bs8qZdIuV6kwwHQYDVR0OBBYEFMT7nCl7l81MlvzuW7PKmXSLlepMMAwGA1Ud +EwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEEBQADgYEAP66K +8ddmAwWePvrqHEa7pFuPeJoSSJn59DXeDDYHAmsQOokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6h +URzzwy5E97BnRqqS5TvaHBkUODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/aEkP/TOYG +JqibGapEPHayXOw= +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0xOTEyMjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo3QwcjARBglghkgBhvhC +AQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGAvtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdER +gL7YibkIozH5oSQJFrlwMB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0B +AQUFAAOCAQEAWUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo +oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQh7A6tcOdBTcS +o8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18f3v/rxzP5tsHrV7bhZ3QKw0z +2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfNB/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjX +OP/swNlQ8C5LWK5Gb9Auw2DaclVyvUxFnmG6v4SBkgPR0ml8xQ== +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Equifax Secure Global eBusiness CA +================================== +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp +bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx +HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds +b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV +PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN +qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn +hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs +MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN +I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY +NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 1 +============================= +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB +LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE +ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz +IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ +1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a +IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk +MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW +Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF +AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 +lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ +KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 2 +============================= +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEXMBUGA1UE +ChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0y +MB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoT +DkVxdWlmYXggU2VjdXJlMSYwJAYDVQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn +2Z0GvxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/BPO3QSQ5 +BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0CAwEAAaOCAQkwggEFMHAG +A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUx +JjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoG +A1UdEAQTMBGBDzIwMTkwNjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9e +uSBIplBqy/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQFMAMB +Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAAyGgq3oThr1 +jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia +78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUm +V+GRMOrN +-----END CERTIFICATE----- + +Visa International Global Root 2 +================================ +-----BEGIN CERTIFICATE----- +MIIDgDCCAmigAwIBAgICAx4wDQYJKoZIhvcNAQEFBQAwYTELMAkGA1UEBhMCVVMxDTALBgNVBAoT +BFZJU0ExLzAtBgNVBAsTJlZpc2EgSW50ZXJuYXRpb25hbCBTZXJ2aWNlIEFzc29jaWF0aW9uMRIw +EAYDVQQDEwlHUCBSb290IDIwHhcNMDAwODE2MjI1MTAwWhcNMjAwODE1MjM1OTAwWjBhMQswCQYD +VQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZp +Y2UgQXNzb2NpYXRpb24xEjAQBgNVBAMTCUdQIFJvb3QgMjCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKkBcLWqxEDwq2omYXkZAPy/mzdZDK9vZBv42pWUJGkzEXDK41Z0ohdXZFwgBuHW +73G3O/erwWnQSaSxBNf0V2KJXLB1LRckaeNCYOTudNargFbYiCjh+20i/SN8RnNPflRzHqgsVVh1 +t0zzWkWlAhr62p3DRcMiXvOL8WAp0sdftAw6UYPvMPjU58fy+pmjIlC++QU3o63tmsPm7Igbthkn +GziLgE3sucfFicv8GjLtI/C1AVj59o/ghalMCXI5Etuz9c9OYmTaxhkVOmMd6RdVoUwiPDQyRvhl +V7or7zaMavrZ2UT0qt2E1w0cslSsMoW0ZA3eQbuxNMYBhjJk1Z8CAwEAAaNCMEAwHQYDVR0OBBYE +FJ59SzS/ca3CBfYDdYDOqU8axCRMMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0G +CSqGSIb3DQEBBQUAA4IBAQAhpXYUVfmtJ3CPPPTVbMjMCqujmAuKBiPFyWHbmQdpNSYx/scuhMKZ +YdQN6X0uEyt8joW2hcdLzzW2LEc9zikv2G+fiRxkk78IvXbQkIqUs38oW26sTTMs7WXcFsziza6k +PWKSBpUmv9+55CCmc2rBvveURNZNbyoLaxhNdBA2aGpawWqn3TYpjLgwi08hPwAuVDAHOrqK5MOe +yti12HvOdUVmB/RtLdh6yumJivIj2C/LbgA2T/vwLwHMD8AiZfSr4k5hLQOCfZEWtTDVFN5ex5D8 +ofyrEK9ca3CnB+8phuiyJccg/ybdd+95RBTEvd07xQObdyPsoOy7Wjm1zK0G +-----END CERTIFICATE----- + +beTRUSTed Root CA +================= +-----BEGIN CERTIFICATE----- +MIIFLDCCBBSgAwIBAgIEOU99hzANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJXVzESMBAGA1UE +ChMJYmVUUlVTVGVkMRswGQYDVQQDExJiZVRSVVNUZWQgUm9vdCBDQXMxGjAYBgNVBAMTEWJlVFJV +U1RlZCBSb290IENBMB4XDTAwMDYyMDE0MjEwNFoXDTEwMDYyMDEzMjEwNFowWjELMAkGA1UEBhMC +V1cxEjAQBgNVBAoTCWJlVFJVU1RlZDEbMBkGA1UEAxMSYmVUUlVTVGVkIFJvb3QgQ0FzMRowGAYD +VQQDExFiZVRSVVNUZWQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANS0 +c3oTCjhVAb6JVuGUntS+WutKNHUbYSnE4a0IYCF4SP+00PpeQY1hRIfo7clY+vyTmt9P6j41ffgz +eubx181vSUs9Ty1uDoM6GHh3o8/n9E1z2Jo7Gh2+lVPPIJfCzz4kUmwMjmVZxXH/YgmPqsWPzGCg +c0rXOD8Vcr+il7dw6K/ifhYGTPWqZCZyByWtNfwYsSbX2P8ZDoMbjNx4RWc0PfSvHI3kbWvtILNn +mrRhyxdviTX/507AMhLn7uzf/5cwdO2NR47rtMNE5qdMf1ZD6Li8tr76g5fmu/vEtpO+GRg+jIG5 +c4gW9JZDnGdzF5DYCW5jrEq2I8QBoa2k5MUCAwEAAaOCAfgwggH0MA8GA1UdEwEB/wQFMAMBAf8w +ggFZBgNVHSAEggFQMIIBTDCCAUgGCisGAQQBsT4BAAAwggE4MIIBAQYIKwYBBQUHAgIwgfQagfFS +ZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFu +Y2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBv +ZiB1c2UsIGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudCwgd2hpY2ggY2FuIGJl +IGZvdW5kIGF0IGJlVFJVU1RlZCdzIHdlYiBzaXRlLCBodHRwczovL3d3dy5iZVRSVVNUZWQuY29t +L3ZhdWx0L3Rlcm1zMDEGCCsGAQUFBwIBFiVodHRwczovL3d3dy5iZVRSVVNUZWQuY29tL3ZhdWx0 +L3Rlcm1zMDQGA1UdHwQtMCswKaAnoCWkIzAhMRIwEAYDVQQKEwliZVRSVVNUZWQxCzAJBgNVBAYT +AldXMB0GA1UdDgQWBBQquZtpLjub2M3eKjEENGvKBxirZzAfBgNVHSMEGDAWgBQquZtpLjub2M3e +KjEENGvKBxirZzAOBgNVHQ8BAf8EBAMCAf4wDQYJKoZIhvcNAQEFBQADggEBAHlh26Nebhax6nZR ++csVm8tpvuaBa58oH2U+3RGFktToQb9+M70j5/Egv6S0phkBxoyNNXxlpE8JpNbYIxUFE6dDea/b +ow6be3ga8wSGWsb2jCBHOElQBp1yZzrwmAOtlmdE/D8QDYZN5AA7KXvOOzuZhmElQITcE2K3+spZ +1gMe1lMBzW1MaFVA4e5rxyoAAEiCswoBw2AqDPeCNe5IhpbkdNQ96gFxugR1QKepfzk5mlWXKWWu +GVUlBXJH0+gY3Ljpr0NzARJ0o+FcXxVdJPP55PS2Z2cS52QiivalQaYctmBjRYoQtLpGEK5BV2Vs +PyMQPyEQWbfkQN0mDCP2qq4= +-----END CERTIFICATE----- + +AddTrust Low-Value Services Root +================================ +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU +cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw +CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO +ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 +54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr +oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 +Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui +GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w +HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT +RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw +HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt +ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph +iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr +mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj +ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +AddTrust External Root +====================== +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD +VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw +NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU +cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg +Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 ++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw +Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo +aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy +2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 +7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL +VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk +VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl +j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 +e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u +G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +AddTrust Public Services Root +============================= +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU +cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ +BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l +dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu +nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i +d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG +Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw +HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G +A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G +A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 +JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL ++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 +Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H +EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +AddTrust Qualified Certificates Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU +cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx +CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ +IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx +64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 +KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o +L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR +wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU +MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE +BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y +azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG +GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze +RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB +iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= +-----END CERTIFICATE----- + +Verisign Time Stamping Authority CA +=================================== +-----BEGIN CERTIFICATE----- +MIIDzTCCAzagAwIBAgIQU2GyYK7bcY6nlLMTM/QHCTANBgkqhkiG9w0BAQUFADCBwTELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTwwOgYDVQQLEzNDbGFzcyAzIFB1YmxpYyBQ +cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIxOjA4BgNVBAsTMShjKSAxOTk4IFZl +cmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAsTFlZlcmlTaWdu +IFRydXN0IE5ldHdvcmswHhcNMDAwOTI2MDAwMDAwWhcNMTAwOTI1MjM1OTU5WjCBpTEXMBUGA1UE +ChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNV +BAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTAwMSww +KgYDVQQDEyNWZXJpU2lnbiBUaW1lIFN0YW1waW5nIEF1dGhvcml0eSBDQTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEA0hmdZ8IAIVlizrQJIkRpivglWtvtDbc2fk7gu5Q+kCWHwmFHKdm9VLhj +zCx9abQzNvQ3B5rB3UBU/OB4naCTuQk9I1F/RMIUdNsKvsvJMDRAmD7Q1yUQgZS9B0+c1lQn3y6o +v8uQjI11S7zi6ESHzeZBCiVu6PQkAsVSD27smHUCAwEAAaOB3zCB3DAPBgNVHRMECDAGAQH/AgEA +MEUGA1UdIAQ+MDwwOgYMYIZIAYb4RQEHFwEDMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LnZl +cmlzaWduLmNvbS9ycGEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovL2NybC52ZXJpc2lnbi5jb20v +cGNhMy5jcmwwCwYDVR0PBAQDAgEGMEIGCCsGAQUFBwEBBDYwNDAyBggrBgEFBQcwAaYmFiRodHRw +Oi8vb2NzcC52ZXJpc2lnbi5jb20vb2NzcC9zdGF0dXMwDQYJKoZIhvcNAQEFBQADgYEAgnBold+2 +DcIBcBlK0lRWHqzyRUyHuPU163hLBanInTsZIS5wNEqi9YngFXVF5yg3ADQnKeg3S/LvRJdrF1Ea +w1adPBqK9kpGRjeM+sv1ZFo4aC4cw+9wzrhGBha/937ntag+RaypJXUie28/sJyU58dzq6wf7iWb +wBbtt8pb8BQ= +-----END CERTIFICATE----- + +Thawte Time Stamping CA +======================= +-----BEGIN CERTIFICATE----- +MIICoTCCAgqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsG +A1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg +Q0EwHhcNOTcwMTAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBizELMAkGA1UEBhMCWkExFTATBgNV +BAgTDFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEd +MBsGA1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBp +bmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANYrWHhhRYZT6jR7UZztsOYuGA7+4F+o +J9O0yeB8WU4WDnNUYMF/9p8u6TqFJBU820cEY8OexJQaWt9MevPZQx08EHp5JduQ/vBR5zDWQQD9 +nyjfeb6Uu522FOMjhdepQeBMpHmwKxqL8vg7ij5FrHGSALSQQZj7X+36ty6K+Ig3AgMBAAGjEzAR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAZ9viwuaHPUCDhjc1fR/OmsMMZiCo +uqoEiYbC9RAIDb/LogWK0E02PvTX72nGXuSwlG9KuefeW4i2e9vjJ+V2w/A1wcu1J5szedyQpgCe +d/r8zSeUQhac0xxo7L9c3eWpexAKMnRUEzGLhQOEkbdYATAUOK8oyvyxUBkZCayJSdM= +-----END CERTIFICATE----- + +Entrust.net Global Secure Server CA +=================================== +-----BEGIN CERTIFICATE----- +MIIElTCCA/6gAwIBAgIEOJsRPDANBgkqhkiG9w0BAQQFADCBujEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxPzA9BgNVBAsUNnd3dy5lbnRydXN0Lm5ldC9TU0xfQ1BTIGluY29ycC4gYnkgcmVmLiAobGlt +aXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDIwMDAgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UE +AxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0w +MDAyMDQxNzIwMDBaFw0yMDAyMDQxNzUwMDBaMIG6MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDE/MD0G +A1UECxQ2d3d3LmVudHJ1c3QubmV0L1NTTF9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlh +Yi4pMSUwIwYDVQQLExwoYykgMjAwMCBFbnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRy +dXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDHwV9OcfHO8GCGD9JYf9Mzly0XonUwtZZkJi9ow0SrqHXmAGc0V55l +xyKbc+bT3QgON1WqJUaBbL3+qPZ1V1eMkGxKwz6LS0MKyRFWmponIpnPVZ5h2QLifLZ8OAfc439P +mrkDQYC2dWcTC5/oVzbIXQA23mYU2m52H083jIITiQIDAQABo4IBpDCCAaAwEQYJYIZIAYb4QgEB +BAQDAgAHMIHjBgNVHR8EgdswgdgwgdWggdKggc+kgcwwgckxFDASBgNVBAoTC0VudHJ1c3QubmV0 +MT8wPQYDVQQLFDZ3d3cuZW50cnVzdC5uZXQvU1NMX0NQUyBpbmNvcnAuIGJ5IHJlZi4gKGxpbWl0 +cyBsaWFiLikxJTAjBgNVBAsTHChjKSAyMDAwIEVudHJ1c3QubmV0IExpbWl0ZWQxOjA4BgNVBAMT +MUVudHJ1c3QubmV0IFNlY3VyZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxDTALBgNV +BAMTBENSTDEwKwYDVR0QBCQwIoAPMjAwMDAyMDQxNzIwMDBagQ8yMDIwMDIwNDE3NTAwMFowCwYD +VR0PBAQDAgEGMB8GA1UdIwQYMBaAFMtswGvjuz7L/CKc/vuLkpyw8m4iMB0GA1UdDgQWBBTLbMBr +47s+y/winP77i5KcsPJuIjAMBgNVHRMEBTADAQH/MB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4w +AwIEkDANBgkqhkiG9w0BAQQFAAOBgQBi24GRzsiad0Iv7L0no1MPUBvqTpLwqa+poLpIYcvvyQbv +H9X07t9WLebKahlzqlO+krNQAraFJnJj2HVQYnUUt7NQGj/KEQALhUVpbbalrlHhStyCP2yMNLJ3 +a9kC9n8O6mUE8c1UyrrJzOCE98g+EZfTYAkYvAX/bIkz8OwVDw== +-----END CERTIFICATE----- + +Entrust.net Global Secure Personal CA +===================================== +-----BEGIN CERTIFICATE----- +MIIEgzCCA+ygAwIBAgIEOJ725DANBgkqhkiG9w0BAQQFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9HQ0NBX0NQUyBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAyMDAwIEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMDAyMDcx +NjE2NDBaFw0yMDAyMDcxNjQ2NDBaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0dDQ0FfQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDIwMDAgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCTdLS25MVL1qFof2LV7PdRV7NySpj10InJrWPNTTVRaoTUrcloeW+46xHbh65cJFET8VQl +hK8pK5/jgOLZy93GRUk0iJBeAZfv6lOm3fzB3ksqJeTpNfpVBQbliXrqpBFXO/x8PTbNZzVtpKkl +Wb1m9fkn5JVn1j+SgF7yNH0rhQIDAQABo4IBnjCCAZowEQYJYIZIAYb4QgEBBAQDAgAHMIHdBgNV +HR8EgdUwgdIwgc+ggcyggcmkgcYwgcMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUAwPgYDVQQLFDd3 +d3cuZW50cnVzdC5uZXQvR0NDQV9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUw +IwYDVQQLExwoYykgMjAwMCBFbnRydXN0Lm5ldCBMaW1pdGVkMTMwMQYDVQQDEypFbnRydXN0Lm5l +dCBDbGllbnQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw +IoAPMjAwMDAyMDcxNjE2NDBagQ8yMDIwMDIwNzE2NDY0MFowCwYDVR0PBAQDAgEGMB8GA1UdIwQY +MBaAFISLdP3FjcD/J20gN0V8/i3OutN9MB0GA1UdDgQWBBSEi3T9xY3A/ydtIDdFfP4tzrrTfTAM +BgNVHRMEBTADAQH/MB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQQF +AAOBgQBObzWAO9GK9Q6nIMstZVXQkvTnhLUGJoMShAusO7JE7r3PQNsgDrpuFOow4DtifH+La3xK +p9U1PL6oXOpLu5OOgGarDyn9TS2/GpsKkMWr2tGzhtQvJFJcem3G8v7lTRowjJDyutdKPkN+1MhQ +Gof4T4HHdguEOnKdzmVml64mXg== +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +AOL Time Warner Root Certification Authority 1 +============================================== +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMxHTAbBgNVBAoT +FEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNhIE9ubGluZSBJbmMuMTcwNQYD +VQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAy +MDUyOTA2MDAwMFoXDTM3MTEyMDE1MDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wg +VGltZSBXYXJuZXIgSW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMu +QU9MIFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnej8Mlo2k06AX3dLm/WpcZuS+U0pPlLYnKhHw/EEMbjIt8 +hFj4JHxIzyr9wBXZGH6EGhfT257XyuTZ16pYUYfw8ItITuLCxFlpMGK2MKKMCxGZYTVtfu/FsRkG +IBKOQuHfD5YQUqjPnF+VFNivO3ULMSAfRC+iYkGzuxgh28pxPIzstrkNn+9R7017EvILDOGsQI93 +f7DKeHEMXRZxcKLXwjqFzQ6axOAAsNUl6twr5JQtOJyJQVdkKGUZHLZEtMgxa44Be3ZZJX8VHIQI +fHNlIAqhBC4aMqiaILGcLCFZ5/vP7nAtCMpjPiybkxlqpMKX/7eGV4iFbJ4VFitNLLMCAwEAAaNj +MGEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUoTYwFsuGkABFgFOxj8jYPXy+XxIwHwYDVR0j +BBgwFoAUoTYwFsuGkABFgFOxj8jYPXy+XxIwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUA +A4IBAQCKIBilvrMvtKaEAEAwKfq0FHNMeUWn9nDg6H5kHgqVfGphwu9OH77/yZkfB2FK4V1Mza3u +0FIy2VkyvNp5ctZ7CegCgTXTCt8RHcl5oIBN/lrXVtbtDyqvpxh1MwzqwWEFT2qaifKNuZ8u77Bf +WgDrvq2g+EQFZ7zLBO+eZMXpyD8Fv8YvBxzDNnGGyjhmSs3WuEvGbKeXO/oTLW4jYYehY0KswsuX +n2Fozy1MBJ3XJU8KDk2QixhWqJNIV9xvrr2eZ1d3iVCzvhGbRWeDhhmH05i9CBoWH1iCC+GWaQVL +juyDUTEH1dSf/1l7qG6Fz9NLqUmwX7A5KGgOc90lmt4S +-----END CERTIFICATE----- + +AOL Time Warner Root Certification Authority 2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMxHTAbBgNVBAoT +FEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNhIE9ubGluZSBJbmMuMTcwNQYD +VQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAy +MDUyOTA2MDAwMFoXDTM3MDkyODIzNDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wg +VGltZSBXYXJuZXIgSW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMu +QU9MIFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ7ouZzU9AhqS2TcnZsdw8 +TQ2FTBVsRotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilbm2BPJoPRYxJWSXakFsKlnUWsi4SVqBax +7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOYxFSMFkpBd4aVdQxHAWZg/BXxD+r1FHjHDtdugRxev17n +OirYlxcwfACtCJ0zr7iZYYCLqJV+FNwSbKTQ2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2w +TPDaRrbqJS5Gr42whTg0ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fxI2rS +AG2X+Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETzkxmlJ85per5n0/xQ +pCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFhEVsVS6kkUfykXPcXnbDS+gfpj1bk +GoxoigTTfFrjnqKhynFbotSg5ymFXQNoKk/SBtc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuW +CpTehTacyH+BCQJJKg71ZDIMgtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1Ex +MVCgyhwn2RAurda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaAFE9pbQN+nZ8HGEO8txBO1b+pxCAoMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAO/Ouyuguh4X7ZVnnrREUpVe8WJ8kEle7 ++z802u6teio0cnAxa8cZmIDJgt43d15Ui47y6mdPyXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRFASbI +5Rq8NEQh3q0l/HYWdyGQgJhXnU7q7C+qPBR7V8F+GBRn7iTGvboVsNIYvbdVgaxTwOjdaRITQrcC +tQVBynlQboIOcXKTRuidDV29rs4prWPVVRaAMCf/drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ +68W/ClTluUI8JPu3B5wwn3la5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyBM5kYJRF3 +p+v9WAksmWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQmy8YJPamTQr5O8t1wswv +ziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xOAU++CrYD062KRffaJ00psUjf5BHklka9 +bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT9Y41xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4o +GKQWDzH9OmwjkyB24f0HhdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2 +uBOLZ8/5fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg= +-----END CERTIFICATE----- + +beTRUSTed Root CA-Baltimore Implementation +========================================== +-----BEGIN CERTIFICATE----- +MIIFajCCBFKgAwIBAgIEPLU9RjANBgkqhkiG9w0BAQUFADBmMRIwEAYDVQQKEwliZVRSVVNUZWQx +GzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEzMDEGA1UEAxMqYmVUUlVTVGVkIFJvb3QgQ0Et +QmFsdGltb3JlIEltcGxlbWVudGF0aW9uMB4XDTAyMDQxMTA3Mzg1MVoXDTIyMDQxMTA3Mzg1MVow +ZjESMBAGA1UEChMJYmVUUlVTVGVkMRswGQYDVQQLExJiZVRSVVNUZWQgUm9vdCBDQXMxMzAxBgNV +BAMTKmJlVFJVU1RlZCBSb290IENBLUJhbHRpbW9yZSBJbXBsZW1lbnRhdGlvbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALx+xDmcjOPWHIb/ymKt4H8wRXqOGrO4x/nRNv8i805qX4QQ ++2aBw5R5MdKR4XeOGCrDFN5R9U+jK7wYFuK13XneIviCfsuBH/0nLI/6l2Qijvj/YaOcGx6Sj8Co +Cd8JEey3fTGaGuqDIQY8n7pc/5TqarjDa1U0Tz0yH92BFODEPM2dMPgwqZfT7syj0B9fHBOB1Bir +lNFjw55/NZKeX0Tq7PQiXLfoPX2k+YmpkbIq2eszh+6l/ePazIjmiSZuxyuC0F6dWdsU7JGDBcNe +DsYq0ATdcT0gTlgn/FP7eHgZFLL8kFKJOGJgB7Sg7KxrUNb9uShr71ItOrL/8QFArDcCAwEAAaOC +Ah4wggIaMA8GA1UdEwEB/wQFMAMBAf8wggG1BgNVHSAEggGsMIIBqDCCAaQGDysGAQQBsT4AAAEJ +KIORMTCCAY8wggFIBggrBgEFBQcCAjCCAToaggE2UmVsaWFuY2Ugb24gb3IgdXNlIG9mIHRoaXMg +Q2VydGlmaWNhdGUgY3JlYXRlcyBhbiBhY2tub3dsZWRnbWVudCBhbmQgYWNjZXB0YW5jZSBvZiB0 +aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwg +dGhlIENlcnRpZmljYXRpb24gUHJhY3RpY2UgU3RhdGVtZW50IGFuZCB0aGUgUmVseWluZyBQYXJ0 +eSBBZ3JlZW1lbnQsIHdoaWNoIGNhbiBiZSBmb3VuZCBhdCB0aGUgYmVUUlVTVGVkIHdlYiBzaXRl +LCBodHRwOi8vd3d3LmJldHJ1c3RlZC5jb20vcHJvZHVjdHNfc2VydmljZXMvaW5kZXguaHRtbDBB +BggrBgEFBQcCARY1aHR0cDovL3d3dy5iZXRydXN0ZWQuY29tL3Byb2R1Y3RzX3NlcnZpY2VzL2lu +ZGV4Lmh0bWwwHQYDVR0OBBYEFEU9w6nR3D8kVpgccxiIav+DR+22MB8GA1UdIwQYMBaAFEU9w6nR +3D8kVpgccxiIav+DR+22MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEASZK8o+6s +vfoNyYt5hhwjdrCAWXf82n+0S9/DZEtqTg6t8n1ZdwWtColzsPq8y9yNAIiPpqCy6qxSJ7+hSHyX +EHu67RMdmgduyzFiEuhjA6p9beP4G3YheBufS0OM00mG9htc9i5gFdPp43t1P9ACg9AYgkHNZTfq +jjJ+vWuZXTARyNtIVBw74acT02pIk/c9jH8F6M7ziCpjBLjqflh8AXtb4cV97yHgjQ5dUX2xZ/2j +vTg2xvI4hocalmhgRvsoFEdV4aeADGvi6t9NfJBIoDa9CReJf8Py05yc493EG931t3GzUwWJBtDL +SoDByFOQtTwxiBdQn8nEDovYqAJjDQ== +-----END CERTIFICATE----- + +beTRUSTed Root CA - Entrust Implementation +========================================== +-----BEGIN CERTIFICATE----- +MIIGUTCCBTmgAwIBAgIEPLVPQDANBgkqhkiG9w0BAQUFADBmMRIwEAYDVQQKEwliZVRSVVNUZWQx +GzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEzMDEGA1UEAxMqYmVUUlVTVGVkIFJvb3QgQ0Eg +LSBFbnRydXN0IEltcGxlbWVudGF0aW9uMB4XDTAyMDQxMTA4MjQyN1oXDTIyMDQxMTA4NTQyN1ow +ZjESMBAGA1UEChMJYmVUUlVTVGVkMRswGQYDVQQLExJiZVRSVVNUZWQgUm9vdCBDQXMxMzAxBgNV +BAMTKmJlVFJVU1RlZCBSb290IENBIC0gRW50cnVzdCBJbXBsZW1lbnRhdGlvbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALr0RAOqEmq1Q+xVkrYwfTVXDNvzDSduTPdQqJtOK2/b9a0c +S12zqcH+e0TrW6MFDR/FNCswACnxeECypP869AGIF37m1CbTukzqMvtDd5eHI8XbQ6P1KqNRXuE7 +0mVpflUVm3rnafdE4Fe1FehmYA8NA/uCjqPoEXtsvsdjDheT389Lrm5zdeDzqrmkwAkbhepxKYhB +MvnwKg5sCfJ0a2ZsUhMfGLzUPvfYbiCeyv78IZTuEyhL11xeDGbu6bsPwTSxfwh28z0mcMmLJR1i +JAzqHHVOwBLkuhMdMCktVjMFu5dZfsZJT4nXLySotohAtWSSU1Yk5KKghbNekLQSM80CAwEAAaOC +AwUwggMBMIIBtwYDVR0gBIIBrjCCAaowggGmBg8rBgEEAbE+AAACCSiDkTEwggGRMIIBSQYIKwYB +BQUHAgIwggE7GoIBN1JlbGlhbmNlIG9uIG9yIHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNyZWF0 +ZXMgYW4gYWNrbm93bGVkZ21lbnQgYW5kIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs +ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIHRoZSBDZXJ0aWZpY2F0aW9u +IFByYWN0aWNlIFN0YXRlbWVudCBhbmQgdGhlIFJlbHlpbmcgUGFydHkgQWdyZWVtZW50LCB3aGlj +aCBjYW4gYmUgZm91bmQgYXQgdGhlIGJlVFJVU1RlZCB3ZWIgc2l0ZSwgaHR0cHM6Ly93d3cuYmV0 +cnVzdGVkLmNvbS9wcm9kdWN0c19zZXJ2aWNlcy9pbmRleC5odG1sMEIGCCsGAQUFBwIBFjZodHRw +czovL3d3dy5iZXRydXN0ZWQuY29tL3Byb2R1Y3RzX3NlcnZpY2VzL2luZGV4Lmh0bWwwEQYJYIZI +AYb4QgEBBAQDAgAHMIGJBgNVHR8EgYEwfzB9oHugeaR3MHUxEjAQBgNVBAoTCWJlVFJVU1RlZDEb +MBkGA1UECxMSYmVUUlVTVGVkIFJvb3QgQ0FzMTMwMQYDVQQDEypiZVRSVVNUZWQgUm9vdCBDQSAt +IEVudHJ1c3QgSW1wbGVtZW50YXRpb24xDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMjAwMjA0 +MTEwODI0MjdagQ8yMDIyMDQxMTA4NTQyN1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFH1w5a44 +iwY/qhwaj/nPJDCqhIQWMB0GA1UdDgQWBBR9cOWuOIsGP6ocGo/5zyQwqoSEFjAMBgNVHRMEBTAD +AQH/MB0GCSqGSIb2fQdBAAQQMA4bCFY2LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEAKrgX +zh8QlOu4mre5X+za95IkrNySO8cgjfKZ5V04ocI07cUTWVwFtStPYZuR+0H8/NU8TZh2BvWBfevd +kObRVlTa4y0MnxEylCIBevZsLHRnBMylj44ss0O1lKLQfelifwa+JwGDnjr9iu6YQ0pr17WXOzq/ +T220Y/ozADQuLW2WyXvKmWO6vvT2MKAtmJbpVkQFqUSjYRDrgqFnXbxdJ3Wqiig2KjiS2d2kXgCl +zMx8KSreKJCrt+G2/30lC0DYqjSjLd4H61/OCt3Kfjp9JsFiaDrmLzfzgYYhxKlkqu9FNtEaZnz4 +6TfW1mG+oq1I59/mdP7TbX3SJdysYlep9w== +-----END CERTIFICATE----- + +beTRUSTed Root CA - RSA Implementation +====================================== +-----BEGIN CERTIFICATE----- +MIIFaDCCBFCgAwIBAgIQO1nHe81bV569N1KsdrSqGjANBgkqhkiG9w0BAQUFADBiMRIwEAYDVQQK +EwliZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEvMC0GA1UEAxMmYmVUUlVT +VGVkIFJvb3QgQ0EgLSBSU0EgSW1wbGVtZW50YXRpb24wHhcNMDIwNDExMTExODEzWhcNMjIwNDEy +MTEwNzI1WjBiMRIwEAYDVQQKEwliZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENB +czEvMC0GA1UEAxMmYmVUUlVTVGVkIFJvb3QgQ0EgLSBSU0EgSW1wbGVtZW50YXRpb24wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkujQwCY5X0LkGLG9uJIAiv11DpvpPrILnHGhwhRuj +brWqeNluB0s/6d/16uhUoWGKDi9pdRi3DOUUjXFumLhV/AyV0Jtu4S2I1DpAa5LxmZZk3tv/ePTu +lh1HiXzUvrmIdyM6CeYEnm2qXtLIvZpOGd+J6lsOfsPktPDgaTuID0GQ+NRxQyTBjyZLO1bp/4xs +N+lFrYWMU8NghpBKlsmzVLC7F/AcRdnUGxlkVgoZ98zh/4avflherHqQH8koOUV7orbHnB/ahdQh +hlkwk75TMzf270HPM8ercmsl9fNTGwxMLvF1S++gh/f+ihXQbNXL+WhTuXAVE8L1LvtDNXUtAgMB +AAGjggIYMIICFDAMBgNVHRMEBTADAQH/MIIBtQYDVR0gBIIBrDCCAagwggGkBg8rBgEEAbE+AAAD +CSiDkTEwggGPMEEGCCsGAQUFBwIBFjVodHRwOi8vd3d3LmJldHJ1c3RlZC5jb20vcHJvZHVjdHNf +c2VydmljZXMvaW5kZXguaHRtbDCCAUgGCCsGAQUFBwICMIIBOhqCATZSZWxpYW5jZSBvbiBvciB1 +c2Ugb2YgdGhpcyBDZXJ0aWZpY2F0ZSBjcmVhdGVzIGFuIGFja25vd2xlZGdtZW50IGFuZCBhY2Nl +cHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5kIGNvbmRpdGlv +bnMgb2YgdXNlLCB0aGUgQ2VydGlmaWNhdGlvbiBQcmFjdGljZSBTdGF0ZW1lbnQgYW5kIHRoZSBS +ZWx5aW5nIFBhcnR5IEFncmVlbWVudCwgd2hpY2ggY2FuIGJlIGZvdW5kIGF0IHRoZSBiZVRSVVNU +ZWQgd2ViIHNpdGUsIGh0dHA6Ly93d3cuYmV0cnVzdGVkLmNvbS9wcm9kdWN0c19zZXJ2aWNlcy9p +bmRleC5odG1sMAsGA1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSp7BR++dlDzFMrFK3P9/BZiUHNGTAd +BgNVHQ4EFgQUqewUfvnZQ8xTKxStz/fwWYlBzRkwDQYJKoZIhvcNAQEFBQADggEBANuXsHXqDMTB +mMpWBcCorSZIry0g6IHHtt9DwSwddUvUQo3neqh03GZCWYez9Wlt2ames30cMcH1VOJZJEnl7r05 +pmuKmET7m9cqg5c0Lcd9NUwtNLg+DcTsiCevnpL9UGGCqGAHFFPMZRPB9kdEadIxyKbdLrML3kqN +Wz2rDcI1UqJWN8wyiyiFQpyRQHpwKzg21eFzGh/l+n5f3NacOzDq28BbJ1zTcwfBwvNMm2+fG8oe +qqg4MwlYsq78B+g23FW6L09A/nq9BqaBwZMifIYRCgZ3SK41ty8ymmFei74pnykkiFY5LKjSq5YD +WtRIn7lAhAuYaPsBQ9Yb4gmxlxw= +-----END CERTIFICATE----- + +RSA Security 2048 v3 +==================== +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK +ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy +MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb +BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 +Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb +WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH +KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP ++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E +FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY +v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj +0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj +VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 +nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA +pKnXwiJPZ9d37CAFYd4= +-----END CERTIFICATE----- + +RSA Security 1024 v3 +==================== +-----BEGIN CERTIFICATE----- +MIICXDCCAcWgAwIBAgIQCgEBAQAAAnwAAAALAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK +ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMTAyNCBWMzAeFw0wMTAy +MjIyMTAxNDlaFw0yNjAyMjIyMDAxNDlaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb +BgNVBAsTFFJTQSBTZWN1cml0eSAxMDI0IFYzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDV +3f5mCc8kPD6ugU5OisRpgFtZO9+5TUzKtS3DJy08rwBCbbwoppbPf9dYrIMKo1W1exeQFYRMiu4m +mdxY78c4pqqv0I5CyGLXq6yp+0p9v+r+Ek3d/yYtbzZUaMjShFbuklNhCbM/OZuoyZu9zp9+1Blq +FikYvtc6adwlWzMaUQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAf +BgNVHSMEGDAWgBTEwBykB5T9zU0B1FTapQxf3q4FWjAdBgNVHQ4EFgQUxMAcpAeU/c1NAdRU2qUM +X96uBVowDQYJKoZIhvcNAQEFBQADgYEAPy1q4yZDlX2Jl2X7deRyHUZXxGFraZ8SmyzVWujAovBD +leMf6XbN3Ou8k6BlCsdNT1+nr6JGFLkM88y9am63nd4lQtBU/55oc2PcJOsiv6hy8l4A4Q1OOkNu +mU4/iXgDmMrzVcydro7BqkWY+o8aoI2II/EVQQ2lRj6RP4vr93E= +-----END CERTIFICATE----- + +GeoTrust Global CA +================== +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw +MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo +BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet +8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc +T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU +vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk +DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q +zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 +d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 +mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p +XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm +Mw== +-----END CERTIFICATE----- + +GeoTrust Global CA 2 +==================== +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw +MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ +NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k +LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA +Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b +HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH +K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 +srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh +ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL +OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC +x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF +H4z1Ir+rzoPz4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +GeoTrust Universal CA +===================== +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 +MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu +Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t +JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e +RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs +7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d +8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V +qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga +Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB +Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu +KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 +ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 +XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 +qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL +oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK +xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF +KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 +DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK +xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU +p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI +P/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +GeoTrust Universal CA 2 +======================= +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 +MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg +SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 +DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 +j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q +JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a +QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 +WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP +20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn +ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC +SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG +8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 ++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ +4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ +mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq +A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg +Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP +pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d +FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp +gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm +X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +UTN-USER First-Network Applications +=================================== +-----BEGIN CERTIFICATE----- +MIIEZDCCA0ygAwIBAgIQRL4Mi1AAJLQR0zYwS8AzdzANBgkqhkiG9w0BAQUFADCBozELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzAp +BgNVBAMTIlVUTi1VU0VSRmlyc3QtTmV0d29yayBBcHBsaWNhdGlvbnMwHhcNOTkwNzA5MTg0ODM5 +WhcNMTkwNzA5MTg1NzQ5WjCBozELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5T +YWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VSRmlyc3QtTmV0d29yayBB +cHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCz+5Gh5DZVhawGNFug +mliy+LUPBXeDrjKxdpJo7CNKyXY/45y2N3kDuatpjQclthln5LAbGHNhSuh+zdMvZOOmfAz6F4Cj +DUeJT1FxL+78P/m4FoCHiZMlIJpDgmkkdihZNaEdwH+DBmQWICzTSaSFtMBhf1EI+GgVkYDLpdXu +Ozr0hAReYFmnjDRy7rh4xdE7EkpvfmUnuaRVxblvQ6TFHSyZwFKkeEwVs0CYCGtDxgGwenv1axwi +P8vv/6jQOkt2FZ7S0cYu49tXGzKiuG/ohqY/cKvlcJKrRB5AUPuco2LkbG6gyN7igEL66S/ozjIE +j3yNtxyjNTwV3Z7DrpelAgMBAAGjgZEwgY4wCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFPqGydvguul49Uuo1hXf8NPhahQ8ME8GA1UdHwRIMEYwRKBCoECGPmh0dHA6Ly9j +cmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LU5ldHdvcmtBcHBsaWNhdGlvbnMuY3JsMA0G +CSqGSIb3DQEBBQUAA4IBAQCk8yXM0dSRgyLQzDKrm5ZONJFUICU0YV8qAhXhi6r/fWRRzwr/vH3Y +IWp4yy9Rb/hCHTO967V7lMPDqaAt39EpHx3+jz+7qEUqf9FuVSTiuwL7MT++6LzsQCv4AdRWOOTK +RIK1YSAhZ2X28AvnNPilwpyjXEAfhZOVBt5P1CeptqX8Fs1zMT+4ZSfP1FMa8Kxun08FDAOBp4Qp +xFq9ZFdyrTvPNximmMatBrTcCKME1SmklpoSZ0qMYEWd8SOasACcaLWYUNPvji6SZbFIPiG+FTAq +DbUMo2s/rn9X9R+WfN9v3YIwLGUbQErNaLly7HF27FSOH4UMAWr6pjisH8SE +-----END CERTIFICATE----- + +America Online Root Certification Authority 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG +v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z +DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh +sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP +8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z +o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf +GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF +VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft +3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g +Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +America Online Root Certification Authority 2 +============================================= +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en +fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 +f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO +qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN +RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 +gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn +6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid +FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 +Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj +B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op +aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY +T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p ++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg +JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy +zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO +ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh +1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf +GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff +Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP +cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= +-----END CERTIFICATE----- + +Visa eCommerce Root +=================== +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG +EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug +QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 +WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm +VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL +F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b +RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 +TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI +/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs +GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc +CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW +YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz +zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu +YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +TC TrustCenter, Germany, Class 2 CA +=================================== +-----BEGIN CERTIFICATE----- +MIIDXDCCAsWgAwIBAgICA+owDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRFMRAwDgYDVQQI +EwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFUQyBUcnVzdENlbnRlciBmb3Ig +U2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJIMSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBD +bGFzcyAyIENBMSkwJwYJKoZIhvcNAQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05 +ODAzMDkxMTU5NTlaFw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFt +YnVyZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9yIFNlY3Vy +aXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3Mg +MiBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVAdHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBANo46O0yAClxgwENv4wB3NrGrTmkqYov1YtcaF9QxmL1Zr3KkSLs +qh1R1z2zUbKDTl3LSbDwTFXlay3HhQswHJJOgtTKAu33b77c4OMUuAVT8pr0VotanoWT0bSCVq5N +u6hLVxa8/vhYnvgpjbB7zXjJT6yLZwzxnPv8V5tXXE8NAgMBAAGjazBpMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3LnRydXN0Y2VudGVy +LmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0GCSqGSIb3DQEBBAUAA4GBAIRS+yjf +/x91AbwBvgRWl2p0QiQxg/lGsQaKic+WLDO/jLVfenKhhQbOhvgFjuj5Jcrag4wGrOs2bYWRNAQ2 +9ELw+HkuCkhcq8xRT3h2oNmsGb0q0WkEKJHKNhAngFdb0lz1wlurZIFjdFH0l7/NEij3TWZ/p/Ac +ASZ4smZHcFFk +-----END CERTIFICATE----- + +TC TrustCenter, Germany, Class 3 CA +=================================== +-----BEGIN CERTIFICATE----- +MIIDXDCCAsWgAwIBAgICA+swDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRFMRAwDgYDVQQI +EwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFUQyBUcnVzdENlbnRlciBmb3Ig +U2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJIMSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBD +bGFzcyAzIENBMSkwJwYJKoZIhvcNAQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05 +ODAzMDkxMTU5NTlaFw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFt +YnVyZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9yIFNlY3Vy +aXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3Mg +MyBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVAdHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBALa0wTUFLg2N7KBAahwOJ6ZQkmtQGwfeLud2zODa/ISoXoxjaitN +2U4CdhHBC/KNecoAtvGwDtf7pBc9r6tpepYnv68zoZoqWarEtTcI8hKlMbZD9TKWcSgoq40oht+7 +7uMMfTDWw1Krj10nnGvAo+cFa1dJRLNu6mTP0o56UHd3AgMBAAGjazBpMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3LnRydXN0Y2VudGVy +LmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0GCSqGSIb3DQEBBAUAA4GBABY9xs3B +u4VxhUafPiCPUSiZ7C1FIWMjWwS7TJC4iJIETb19AaM/9uzO8d7+feXhPrvGq14L3T2WxMup1Pkm +5gZOngylerpuw3yCGdHHsbHD2w2Om0B8NwvxXej9H5CIpQ5ON2QhqE6NtJ/x3kit1VYYUimLRzQS +CdS7kjXvD9s0 +-----END CERTIFICATE----- + +Certum Root CA +============== +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK +ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla +Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u +by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x +wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL +kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ +89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K +Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P +NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ +GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg +GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ +0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS +qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +Comodo Secure Services root +=========================== +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw +MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu +Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi +BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP +9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc +rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC +oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V +p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E +FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm +aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm +4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL +DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw +pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H +RR3B7Hzs/Sk= +-----END CERTIFICATE----- + +Comodo Trusted Services root +============================ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw +MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h +bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw +IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 +3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y +/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 +juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS +ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud +DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp +ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl +cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw +uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA +BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l +R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O +9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +IPS Chained CAs root +==================== +-----BEGIN CERTIFICATE----- +MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARwxCzAJBgNVBAYTAkVTMRIwEAYDVQQI +EwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1 +Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAg +Qi02MDkyOTQ1MjEzMDEGA1UECxMqSVBTIENBIENoYWluZWQgQ0FzIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MTMwMQYDVQQDEypJUFMgQ0EgQ2hhaW5lZCBDQXMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkxHjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczAeFw0wMTEyMjkwMDUzNThaFw0yNTEy +MjcwMDUzNThaMIIBHDELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNlbG9uYTESMBAGA1UEBxMJ +QmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQgcHVibGlzaGluZyBTZXJ2aWNlcyBzLmwu +MSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5JLkYuICBCLTYwOTI5NDUyMTMwMQYDVQQLEypJ +UFMgQ0EgQ2hhaW5lZCBDQXMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMzAxBgNVBAMTKklQUyBD +QSBDaGFpbmVkIENBcyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqGSIb3DQEJARYPaXBz +QG1haWwuaXBzLmVzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcVpJJspQgvJhPUOtopKdJ +C7/SMejHT8KGC/po/UNaivNgkjWZOLtNA1IhW/A3mTXhQSCBhYEFcYGdtJUZqV92NC5jNzVXjrQf +Qj8VXOF6wV8TGDIxya2+o8eDZh65nAQTy2nBBt4wBrszo7Uf8I9vzv+W6FS+ZoCua9tBhDaiPQID +AQABo4IEQzCCBD8wHQYDVR0OBBYEFKGtMbH5PuEXpsirNPxShwkeYlJBMIIBTgYDVR0jBIIBRTCC +AUGAFKGtMbH5PuEXpsirNPxShwkeYlJBoYIBJKSCASAwggEcMQswCQYDVQQGEwJFUzESMBAGA1UE +CBMJQmFyY2Vsb25hMRIwEAYDVQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBw +dWJsaXNoaW5nIFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0BtYWlsLmlwcy5lcyBDLkkuRi4g +IEItNjA5Mjk0NTIxMzAxBgNVBAsTKklQUyBDQSBDaGFpbmVkIENBcyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTEzMDEGA1UEAxMqSVBTIENBIENoYWluZWQgQ0FzIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAMBgNV +HQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUF +BwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGC +NwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1Ud +EgQTMBGBD2lwc0BtYWlsLmlwcy5lczBCBglghkgBhvhCAQ0ENRYzQ2hhaW5lZCBDQSBDZXJ0aWZp +Y2F0ZSBpc3N1ZWQgYnkgaHR0cDovL3d3dy5pcHMuZXMvMCkGCWCGSAGG+EIBAgQcFhpodHRwOi8v +d3d3Lmlwcy5lcy9pcHMyMDAyLzA3BglghkgBhvhCAQQEKhYoaHR0cDovL3d3dy5pcHMuZXMvaXBz +MjAwMi9pcHMyMDAyQ0FDLmNybDA8BglghkgBhvhCAQMELxYtaHR0cDovL3d3dy5pcHMuZXMvaXBz +MjAwMi9yZXZvY2F0aW9uQ0FDLmh0bWw/MDkGCWCGSAGG+EIBBwQsFipodHRwOi8vd3d3Lmlwcy5l +cy9pcHMyMDAyL3JlbmV3YWxDQUMuaHRtbD8wNwYJYIZIAYb4QgEIBCoWKGh0dHA6Ly93d3cuaXBz +LmVzL2lwczIwMDIvcG9saWN5Q0FDLmh0bWwwbQYDVR0fBGYwZDAuoCygKoYoaHR0cDovL3d3dy5p +cHMuZXMvaXBzMjAwMi9pcHMyMDAyQ0FDLmNybDAyoDCgLoYsaHR0cDovL3d3d2JhY2suaXBzLmVz +L2lwczIwMDIvaXBzMjAwMkNBQy5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUFBzABhhNodHRw +Oi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAERyMJ1WWKJBGyi3leGmGpVfp3hAK+/b +lkr8THFj2XOVvQLiogbHvpcqk4A0hgP63Ng9HgfNHnNDJGD1HWHc3JagvPsd4+cSACczAsDAK1M9 +2GsDgaPb1pOVIO/Tln4mkImcJpvNb2ar7QMiRDjMWb2f2/YHogF/JsRj9SVCXmK9 +-----END CERTIFICATE----- + +IPS CLASE1 root +=============== +-----BEGIN CERTIFICATE----- +MIIH6jCCB1OgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARIxCzAJBgNVBAYTAkVTMRIwEAYDVQQI +EwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1 +Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAg +Qi02MDkyOTQ1MjEuMCwGA1UECxMlSVBTIENBIENMQVNFMSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlSVBTIENBIENMQVNFMSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqG +SIb3DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAwNTkzOFoXDTI1MTIyNzAwNTkzOFow +ggESMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFyY2Vsb25hMRIwEAYDVQQHEwlCYXJjZWxvbmEx +LjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBwdWJsaXNoaW5nIFNlcnZpY2VzIHMubC4xKzApBgNVBAoU +Imlwc0BtYWlsLmlwcy5lcyBDLkkuRi4gIEItNjA5Mjk0NTIxLjAsBgNVBAsTJUlQUyBDQSBDTEFT +RTEgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLjAsBgNVBAMTJUlQUyBDQSBDTEFTRTEgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkxHjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEA4FEnpwvdr9G5Q1uCN0VWcu+atsIS7ywSzHb5BlmvXSHU0lq4 +oNTzav3KaY1mSPd05u42veiWkXWmcSjK5yISMmmwPh5r9FBSYmL9Yzt9fuzuOOpi9GyocY3h6YvJ +P8a1zZRCb92CRTzo3wno7wpVqVZHYUxJZHMQKD/Kvwn/xi8CAwEAAaOCBEowggRGMB0GA1UdDgQW +BBTrsxl588GlHKzcuh9morKbadB4CDCCAUQGA1UdIwSCATswggE3gBTrsxl588GlHKzcuh9morKb +adB4CKGCARqkggEWMIIBEjELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNlbG9uYTESMBAGA1UE +BxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQgcHVibGlzaGluZyBTZXJ2aWNlcyBz +LmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5JLkYuICBCLTYwOTI5NDUyMS4wLAYDVQQL +EyVJUFMgQ0EgQ0xBU0UxIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVJUFMgQ0Eg +Q0xBU0UxIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5p +cHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMB +BggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYB +BAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYDVR0R +BBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlwcy5lczBBBglghkgB +hvhCAQ0ENBYyQ0xBU0UxIENBIENlcnRpZmljYXRlIGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5l +cy8wKQYJYIZIAYb4QgECBBwWGmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMDoGCWCGSAGG+EIB +BAQtFitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTEuY3JsMD8GCWCGSAGG ++EIBAwQyFjBodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25DTEFTRTEuaHRtbD8w +PAYJYIZIAYb4QgEHBC8WLWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmVuZXdhbENMQVNFMS5o +dG1sPzA6BglghkgBhvhCAQgELRYraHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lDTEFT +RTEuaHRtbDBzBgNVHR8EbDBqMDGgL6AthitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIw +MDJDTEFTRTEuY3JsMDWgM6Axhi9odHRwOi8vd3d3YmFjay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAy +Q0xBU0UxLmNybDAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9vY3NwLmlwcy5l +cy8wDQYJKoZIhvcNAQEFBQADgYEAK9Dr/drIyllq2tPMMi7JVBuKYn4VLenZMdMu9Ccj/1urxUq2 +ckCuU3T0vAW0xtnIyXf7t/k0f3gA+Nak5FI/LEpjV4F1Wo7ojPsCwJTGKbqz3Bzosq/SLmJbGqmO +DszFV0VRFOlOHIilkfSj945RyKm+hjM+5i9Ibq9UkE6tsSU= +-----END CERTIFICATE----- + +IPS CLASE3 root +=============== +-----BEGIN CERTIFICATE----- +MIIH6jCCB1OgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARIxCzAJBgNVBAYTAkVTMRIwEAYDVQQI +EwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1 +Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAg +Qi02MDkyOTQ1MjEuMCwGA1UECxMlSVBTIENBIENMQVNFMyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlSVBTIENBIENMQVNFMyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqG +SIb3DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAxMDE0NFoXDTI1MTIyNzAxMDE0NFow +ggESMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFyY2Vsb25hMRIwEAYDVQQHEwlCYXJjZWxvbmEx +LjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBwdWJsaXNoaW5nIFNlcnZpY2VzIHMubC4xKzApBgNVBAoU +Imlwc0BtYWlsLmlwcy5lcyBDLkkuRi4gIEItNjA5Mjk0NTIxLjAsBgNVBAsTJUlQUyBDQSBDTEFT +RTMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLjAsBgNVBAMTJUlQUyBDQSBDTEFTRTMgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkxHjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAqxf+DrDGaBtT8FK+n/ra+osTBLsBjzLZH49NzjaY2uQARIwo +2BNEKqRrThckQpzTiKRBgtYj+4vJhuW5qYIF3PHeH+AMmVWY8jjsbJ0gA8DvqqPGZARRLXgNo9Ko +OtYkTOmWehisEyMiG3zoMRGzXwmqMHBxRiVrSXGAK5UBsh8CAwEAAaOCBEowggRGMB0GA1UdDgQW +BBS4k/8uy9wsjqLnev42USGjmFsMNDCCAUQGA1UdIwSCATswggE3gBS4k/8uy9wsjqLnev42USGj +mFsMNKGCARqkggEWMIIBEjELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNlbG9uYTESMBAGA1UE +BxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQgcHVibGlzaGluZyBTZXJ2aWNlcyBz +LmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5JLkYuICBCLTYwOTI5NDUyMS4wLAYDVQQL +EyVJUFMgQ0EgQ0xBU0UzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVJUFMgQ0Eg +Q0xBU0UzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5p +cHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMB +BggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYB +BAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYDVR0R +BBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlwcy5lczBBBglghkgB +hvhCAQ0ENBYyQ0xBU0UzIENBIENlcnRpZmljYXRlIGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5l +cy8wKQYJYIZIAYb4QgECBBwWGmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMDoGCWCGSAGG+EIB +BAQtFitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTMuY3JsMD8GCWCGSAGG ++EIBAwQyFjBodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25DTEFTRTMuaHRtbD8w +PAYJYIZIAYb4QgEHBC8WLWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmVuZXdhbENMQVNFMy5o +dG1sPzA6BglghkgBhvhCAQgELRYraHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lDTEFT +RTMuaHRtbDBzBgNVHR8EbDBqMDGgL6AthitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIw +MDJDTEFTRTMuY3JsMDWgM6Axhi9odHRwOi8vd3d3YmFjay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAy +Q0xBU0UzLmNybDAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9vY3NwLmlwcy5l +cy8wDQYJKoZIhvcNAQEFBQADgYEAF2VcmZVDAyevJuXr0LMXI/dDqsfwfewPxqmurpYPdikc4gYt +fibFPPqhwYHOU7BC0ZdXGhd+pFFhxu7pXu8Fuuu9D6eSb9ijBmgpjnn1/7/5p6/ksc7C0YBCJwUE +NPjDfxZ4IwwHJPJGR607VNCv1TGyr33I6unUVtkOE7LFRVA= +-----END CERTIFICATE----- + +IPS CLASEA1 root +================ +-----BEGIN CERTIFICATE----- +MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQI +EwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1 +Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAg +Qi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTEgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUExIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJ +KoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwHhcNMDExMjI5MDEwNTMyWhcNMjUxMjI3MDEwNTMy +WjCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9u +YTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENM +QVNFQTEgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUExIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwgZ8w +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALsw19zQVL01Tp/FTILq0VA8R5j8m2mdd81u4D/u6zJf +X5/S0HnllXNEITLgCtud186Nq1KLK3jgm1t99P1tCeWu4WwdByOgF9H5fahGRpEiqLJpxq339fWU +oTCUvQDMRH/uxJ7JweaPCjbB/SQ9AaD1e+J8eGZDi09Z8pvZ+kmzAgMBAAGjggRTMIIETzAdBgNV +HQ4EFgQUZyaW56G/2LUDnf473P7yiuYV3TAwggFGBgNVHSMEggE9MIIBOYAUZyaW56G/2LUDnf47 +3P7yiuYV3TChggEcpIIBGDCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ +BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2Vydmlj +ZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0G +A1UECxMmSVBTIENBIENMQVNFQTEgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQ +UyBDQSBDTEFTRUExIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNA +bWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsG +AQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIB +FQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcw +GgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlwcy5lczBC +BglghkgBhvhCAQ0ENRYzQ0xBU0VBMSBDQSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkgaHR0cDovL3d3 +dy5pcHMuZXMvMCkGCWCGSAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyLzA7Bglg +hkgBhvhCAQQELhYsaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyQ0xBU0VBMS5jcmww +QAYJYIZIAYb4QgEDBDMWMWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmV2b2NhdGlvbkNMQVNF +QTEuaHRtbD8wPQYJYIZIAYb4QgEHBDAWLmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmVuZXdh +bENMQVNFQTEuaHRtbD8wOwYJYIZIAYb4QgEIBC4WLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIv +cG9saWN5Q0xBU0VBMS5odG1sMHUGA1UdHwRuMGwwMqAwoC6GLGh0dHA6Ly93d3cuaXBzLmVzL2lw +czIwMDIvaXBzMjAwMkNMQVNFQTEuY3JsMDagNKAyhjBodHRwOi8vd3d3YmFjay5pcHMuZXMvaXBz +MjAwMi9pcHMyMDAyQ0xBU0VBMS5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUFBzABhhNodHRw +Oi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAH66iqyAAIQVCtWYUQxkxZwCWINmyq0e +B81+atqAB98DNEock8RLWCA1NnHtogo1EqWmZaeFaQoO42Hu6r4okzPV7Oi+xNtff6j5YzHIa5bi +KcJboOeXNp13XjFr/tOn2yrb25aLH2betgPAK7N41lUH5Y85UN4HI3LmvSAUS7SG +-----END CERTIFICATE----- + +IPS CLASEA3 root +================ +-----BEGIN CERTIFICATE----- +MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQI +EwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1 +Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAg +Qi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUEzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJ +KoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwHhcNMDExMjI5MDEwNzUwWhcNMjUxMjI3MDEwNzUw +WjCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9u +YTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENM +QVNFQTMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUEzIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwgZ8w +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAO6AAPYaZC6tasiDsYun7o/ZttvNG7uGBiJ2MwwSbUhW +YdLcgiViL5/SaTBlA0IjWLxH3GvWdV0XPOH/8lhneaDBgbHUVqLyjRGZ/fZ98cfEXgIqmuJKtROK +AP2Md4bm15T1IHUuDky/dMQ/gT6DtKM4Ninn6Cr1jIhBqoCm42zvAgMBAAGjggRTMIIETzAdBgNV +HQ4EFgQUHp9XUEe2YZM50yz82l09BXW3mQIwggFGBgNVHSMEggE9MIIBOYAUHp9XUEe2YZM50yz8 +2l09BXW3mQKhggEcpIIBGDCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ +BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2Vydmlj +ZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0G +A1UECxMmSVBTIENBIENMQVNFQTMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQ +UyBDQSBDTEFTRUEzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNA +bWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsG +AQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIB +FQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcw +GgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlwcy5lczBC +BglghkgBhvhCAQ0ENRYzQ0xBU0VBMyBDQSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkgaHR0cDovL3d3 +dy5pcHMuZXMvMCkGCWCGSAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyLzA7Bglg +hkgBhvhCAQQELhYsaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyQ0xBU0VBMy5jcmww +QAYJYIZIAYb4QgEDBDMWMWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmV2b2NhdGlvbkNMQVNF +QTMuaHRtbD8wPQYJYIZIAYb4QgEHBDAWLmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmVuZXdh +bENMQVNFQTMuaHRtbD8wOwYJYIZIAYb4QgEIBC4WLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIv +cG9saWN5Q0xBU0VBMy5odG1sMHUGA1UdHwRuMGwwMqAwoC6GLGh0dHA6Ly93d3cuaXBzLmVzL2lw +czIwMDIvaXBzMjAwMkNMQVNFQTMuY3JsMDagNKAyhjBodHRwOi8vd3d3YmFjay5pcHMuZXMvaXBz +MjAwMi9pcHMyMDAyQ0xBU0VBMy5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUFBzABhhNodHRw +Oi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAEo9IEca2on0eisxeewBwMwB9dbB/MjD +81ACUZBYKp/nNQlbMAqBACVHr9QPDp5gJqiVp4MI3y2s6Q73nMify5NF8bpqxmdRSmlPa/59Cy9S +KcJQrSRE7SOzSMtEQMEDlQwKeAYSAfWRMS1Jjbs/RU4s4OjNtckUFQzjB4ObJnXv +-----END CERTIFICATE----- + +IPS Servidores root +=================== +-----BEGIN CERTIFICATE----- +MIICtzCCAiACAQAwDQYJKoZIhvcNAQEEBQAwgaMxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCQVJD +RUxPTkExEjAQBgNVBAcTCUJBUkNFTE9OQTEZMBcGA1UEChMQSVBTIFNlZ3VyaWRhZCBDQTEYMBYG +A1UECxMPQ2VydGlmaWNhY2lvbmVzMRcwFQYDVQQDEw5JUFMgU0VSVklET1JFUzEeMBwGCSqGSIb3 +DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTk4MDEwMTIzMjEwN1oXDTA5MTIyOTIzMjEwN1owgaMx +CzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCQVJDRUxPTkExEjAQBgNVBAcTCUJBUkNFTE9OQTEZMBcG +A1UEChMQSVBTIFNlZ3VyaWRhZCBDQTEYMBYGA1UECxMPQ2VydGlmaWNhY2lvbmVzMRcwFQYDVQQD +Ew5JUFMgU0VSVklET1JFUzEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwuaXBzLmVzMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQCsT1J0nznqjtwlxLyYXZhkJAk8IbPMGbWOlI6H0fg3PqHILVik +gDVboXVsHUUMH2Fjal5vmwpMwci4YSM1gf/+rHhwLWjhOgeYlQJU3c0jt4BT18g3RXIGJBK6E2Eh +im51KODFDzT9NthFf+G4Nu+z4cYgjui0OLzhPvYR3oydAQIDAQABMA0GCSqGSIb3DQEBBAUAA4GB +ACzzw3lYJN7GO9HgQmm47mSzPWIBubOE3yN93ZjPEKn+ANgilgUTB1RXxafey9m4iEL2mdsUdx+2 +/iU94aI+A6mB0i1sR/WWRowiq8jMDQ6XXotBtDvECgZAHd1G9AHduoIuPD14cJ58GNCr+Lh3B0Zx +8coLY1xq+XKU1QFPoNtC +-----END CERTIFICATE----- + +IPS Timestamping root +===================== +-----BEGIN CERTIFICATE----- +MIIIODCCB6GgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCAR4xCzAJBgNVBAYTAkVTMRIwEAYDVQQI +EwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1 +Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAg +Qi02MDkyOTQ1MjE0MDIGA1UECxMrSVBTIENBIFRpbWVzdGFtcGluZyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTE0MDIGA1UEAxMrSVBTIENBIFRpbWVzdGFtcGluZyBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAxMTAxOFoXDTI1 +MTIyNzAxMTAxOFowggEeMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFyY2Vsb25hMRIwEAYDVQQH +EwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBwdWJsaXNoaW5nIFNlcnZpY2VzIHMu +bC4xKzApBgNVBAoUImlwc0BtYWlsLmlwcy5lcyBDLkkuRi4gIEItNjA5Mjk0NTIxNDAyBgNVBAsT +K0lQUyBDQSBUaW1lc3RhbXBpbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxNDAyBgNVBAMTK0lQ +UyBDQSBUaW1lc3RhbXBpbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHjAcBgkqhkiG9w0BCQEW +D2lwc0BtYWlsLmlwcy5lczCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvLjuVqWajOY2ycJi +oGaBjRrVetJznw6EZLqVtJCneK/K/lRhW86yIFcBrkSSQxA4Efdo/BdApWgnMjvEp+ZCccWZ73b/ +K5Uk9UmSGGjKALWkWi9uy9YbLA1UZ2t6KaFYq6JaANZbuxjC3/YeE1Z2m6Vo4pjOxgOKNNtMg0Gm +qaMCAwEAAaOCBIAwggR8MB0GA1UdDgQWBBSL0BBQCYHynQnVDmB4AyKiP8jKZjCCAVAGA1UdIwSC +AUcwggFDgBSL0BBQCYHynQnVDmB4AyKiP8jKZqGCASakggEiMIIBHjELMAkGA1UEBhMCRVMxEjAQ +BgNVBAgTCUJhcmNlbG9uYTESMBAGA1UEBxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJu +ZXQgcHVibGlzaGluZyBTZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5J +LkYuICBCLTYwOTI5NDUyMTQwMgYDVQQLEytJUFMgQ0EgVGltZXN0YW1waW5nIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MTQwMgYDVQQDEytJUFMgQ0EgVGltZXN0YW1waW5nIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB +/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMG +CCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYK +KwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVz +MBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlwcy5lczBHBglghkgBhvhCAQ0EOhY4VGltZXN0YW1waW5n +IENBIENlcnRpZmljYXRlIGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5lcy8wKQYJYIZIAYb4QgEC +BBwWGmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMEAGCWCGSAGG+EIBBAQzFjFodHRwOi8vd3d3 +Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJUaW1lc3RhbXBpbmcuY3JsMEUGCWCGSAGG+EIBAwQ4FjZo +dHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25UaW1lc3RhbXBpbmcuaHRtbD8wQgYJ +YIZIAYb4QgEHBDUWM2h0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmVuZXdhbFRpbWVzdGFtcGlu +Zy5odG1sPzBABglghkgBhvhCAQgEMxYxaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lU +aW1lc3RhbXBpbmcuaHRtbDB/BgNVHR8EeDB2MDegNaAzhjFodHRwOi8vd3d3Lmlwcy5lcy9pcHMy +MDAyL2lwczIwMDJUaW1lc3RhbXBpbmcuY3JsMDugOaA3hjVodHRwOi8vd3d3YmFjay5pcHMuZXMv +aXBzMjAwMi9pcHMyMDAyVGltZXN0YW1waW5nLmNybDAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUH +MAGGE2h0dHA6Ly9vY3NwLmlwcy5lcy8wDQYJKoZIhvcNAQEFBQADgYEAZbrBzAAalZHK6Ww6vzoe +FAh8+4Pua2JR0zORtWB5fgTYXXk36MNbsMRnLWhasl8OCvrNPzpFoeo2zyYepxEoxZSPhExTCMWT +s/zif/WN87GphV+I3pGW7hdbrqXqcGV4LCFkAZXOzkw+UPS2Wctjjba9GNSHSl/c7+lW8AoM6HU= +-----END CERTIFICATE----- + +QuoVadis Root CA +================ +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE +ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz +MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp +cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD +EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk +J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL +F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL +YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen +AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w +PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y +ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 +MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj +YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs +ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW +Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu +BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw +FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 +tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo +fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul +LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x +gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi +5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi +5nrQNiOKSnQ2+Q== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +Sonera Class 1 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAxMDQwNjEwNDkxM1oXDTIxMDQw +NjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H88 +7dF+2rDNbS82rDTG29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9 +EJUkoVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk3w0LBUXl +0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBLqdReLjVQCfOAl/QMF645 +2F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIINnvmLVz5MxxftLItyM19yejhW1ebZrgUa +HXVFsculJRwSVzb9IjcCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZT +iFIwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE9 +28Jj2VuXZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0HDjxV +yhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VOTzF2nBBhjrZTOqMR +vq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2UvkVrCqIexVmiUefkl98HVrhq4uz2P +qYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4wzMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9Z +IRlXvVWa +-----END CERTIFICATE----- + +Sonera Class 2 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw +NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 +/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT +dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG +f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P +tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH +nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT +XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt +0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI +cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph +Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx +EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH +llpwrN9M +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA +============================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE +ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w +HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh +bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt +vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P +jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca +C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth +vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 +22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV +HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v +dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN +BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR +EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw +MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y +nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +TDC Internet Root CA +==================== +-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE +ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx +NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu +ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j +xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL +znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc +5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 +otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI +AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM +VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM +MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC +AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe +UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G +CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m +gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ +2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb +O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU +Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l +-----END CERTIFICATE----- + +TDC OCES Root CA +================ +-----BEGIN CERTIFICATE----- +MIIFGTCCBAGgAwIBAgIEPki9xDANBgkqhkiG9w0BAQUFADAxMQswCQYDVQQGEwJESzEMMAoGA1UE +ChMDVERDMRQwEgYDVQQDEwtUREMgT0NFUyBDQTAeFw0wMzAyMTEwODM5MzBaFw0zNzAyMTEwOTA5 +MzBaMDExCzAJBgNVBAYTAkRLMQwwCgYDVQQKEwNUREMxFDASBgNVBAMTC1REQyBPQ0VTIENBMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArGL2YSCyz8DGhdfjeebM7fI5kqSXLmSjhFuH +nEz9pPPEXyG9VhDr2y5h7JNp46PMvZnDBfwGuMo2HP6QjklMxFaaL1a8z3sM8W9Hpg1DTeLpHTk0 +zY0s2RKY+ePhwUp8hjjEqcRhiNJerxomTdXkoCJHhNlktxmW/OwZ5LKXJk5KTMuPJItUGBxIYXvV +iGjaXbXqzRowwYCDdlCqT9HU3Tjw7xb04QxQBr/q+3pJoSgrHPb8FTKjdGqPqcNiKXEx5TukYBde +dObaE+3pHx8b0bJoc8YQNHVGEBDjkAB2QMuLt0MJIf+rTpPGWOmlgtt3xDqZsXKVSQTwtyv6e1mO +3QIDAQABo4ICNzCCAjMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgewGA1UdIASB +5DCB4TCB3gYIKoFQgSkBAQEwgdEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2VydGlmaWthdC5k +ay9yZXBvc2l0b3J5MIGdBggrBgEFBQcCAjCBkDAKFgNUREMwAwIBARqBgUNlcnRpZmlrYXRlciBm +cmEgZGVubmUgQ0EgdWRzdGVkZXMgdW5kZXIgT0lEIDEuMi4yMDguMTY5LjEuMS4xLiBDZXJ0aWZp +Y2F0ZXMgZnJvbSB0aGlzIENBIGFyZSBpc3N1ZWQgdW5kZXIgT0lEIDEuMi4yMDguMTY5LjEuMS4x +LjARBglghkgBhvhCAQEEBAMCAAcwgYEGA1UdHwR6MHgwSKBGoESkQjBAMQswCQYDVQQGEwJESzEM +MAoGA1UEChMDVERDMRQwEgYDVQQDEwtUREMgT0NFUyBDQTENMAsGA1UEAxMEQ1JMMTAsoCqgKIYm +aHR0cDovL2NybC5vY2VzLmNlcnRpZmlrYXQuZGsvb2Nlcy5jcmwwKwYDVR0QBCQwIoAPMjAwMzAy +MTEwODM5MzBagQ8yMDM3MDIxMTA5MDkzMFowHwYDVR0jBBgwFoAUYLWF7FZkfhIZJ2cdUBVLc647 ++RIwHQYDVR0OBBYEFGC1hexWZH4SGSdnHVAVS3OuO/kSMB0GCSqGSIb2fQdBAAQQMA4bCFY2LjA6 +NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEACromJkbTc6gJ82sLMJn9iuFXehHTuJTXCRBuo7E4 +A9G28kNBKWKnctj7fAXmMXAnVBhOinxO5dHKjHiIzxvTkIvmI/gLDjNDfZziChmPyQE+dF10yYsc +A+UYyAFMP8uXBV2YcaaYb7Z8vTd/vuGTJW1v8AqtFxjhA7wHKcitJuj4YfD9IQl+mo6paH1IYnK9 +AOoBmbgGglGBTvH1tJFUuSN6AJqfXY3gPGS5GhKSKseCRHI53OI8xthV9RVOyAUO28bQYqbsFbS1 +AoLbrIyigfCbmTH1ICCoiGEKB5+U/NDXG8wuF/MEJ3Zn61SD/aSQfgY9BKNDLdr8C2LqL19iUw== +-----END CERTIFICATE----- + +UTN DATACorp SGC Root CA +======================== +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ +BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa +MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w +HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy +dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys +raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo +wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA +9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv +33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud +DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 +BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD +LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 +DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 +I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx +EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP +DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +UTN USERFirst Email Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCBrjELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0 +BgNVBAMTLVVUTi1VU0VSRmlyc3QtQ2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05 +OTA3MDkxNzI4NTBaFw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQx +FzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsx +ITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UEAxMtVVROLVVTRVJGaXJz +dC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWlsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3BYHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIx +B8dOtINknS4p1aJkxIW9hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8 +om+rWV6lL8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLmSGHG +TPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM1tZUOt4KpLoDd7Nl +yP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws6wIDAQABo4G5MIG2MAsGA1UdDwQE +AwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNV +HR8EUTBPME2gS6BJhkdodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGll +bnRBdXRoZW50aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u7mFVbwQ+zzne +xRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0xtcgBEXkzYABurorbs6q15L+ +5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQrfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarV +NZ1yQAOJujEdxRBoUp7fooXFXAimeOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZ +w7JHpsIyYdfHb0gkUSeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ= +-----END CERTIFICATE----- + +UTN USERFirst Hardware Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd +BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx +OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 +eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz +ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI +wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd +tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 +i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf +Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw +gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF +UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF +BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW +XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 +lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn +iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 +nfhmqA== +-----END CERTIFICATE----- + +UTN USERFirst Object Root CA +============================ +-----BEGIN CERTIFICATE----- +MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAb +BgNVBAMTFFVUTi1VU0VSRmlyc3QtT2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAz +NlowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkx +HjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2Vy +dHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VR +loTN2+O5bj4x2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ +w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vu +lBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehbkkj7 +RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUCAwEAAaOBrzCBrDAL +BgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8 +ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmly +c3QtT2JqZWN0LmNybDApBgNVHSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQw +DQYJKoZIhvcNAQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw +NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXBmMiKVl0+7kNO +PmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU4U3GDZlDAQ0Slox4nb9QorFE +qmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK581OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCG +hU3IfdeLA/5u1fedFqySLKAj5ZyRUh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g= +-----END CERTIFICATE----- + +Camerfirma Chambers of Commerce Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx +NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp +cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn +MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC +AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU +xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH +NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW +DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV +d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud +EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v +cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P +AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh +bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD +VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi +fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD +L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN +UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n +ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 +erfutGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +Camerfirma Global Chambersign Root +================================== +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx +NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt +YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg +MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw +ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J +1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O +by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl +6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c +8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ +BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j +aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B +Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj +aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y +ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA +PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y +gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ +PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 +IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes +t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +NetLock Qualified (Class QA) Root +================================= +-----BEGIN CERTIFICATE----- +MIIG0TCCBbmgAwIBAgIBezANBgkqhkiG9w0BAQUFADCByTELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMUIwQAYDVQQDEzlOZXRMb2NrIE1pbm9zaXRldHQgS296amVn +eXpvaSAoQ2xhc3MgUUEpIFRhbnVzaXR2YW55a2lhZG8xHjAcBgkqhkiG9w0BCQEWD2luZm9AbmV0 +bG9jay5odTAeFw0wMzAzMzAwMTQ3MTFaFw0yMjEyMTUwMTQ3MTFaMIHJMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNhZ2kgS2Z0 +LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxQjBABgNVBAMTOU5ldExvY2sgTWlub3NpdGV0 +dCBLb3pqZWd5em9pIChDbGFzcyBRQSkgVGFudXNpdHZhbnlraWFkbzEeMBwGCSqGSIb3DQEJARYP +aW5mb0BuZXRsb2NrLmh1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1Ilstg91IRV +CacbvWy5FPSKAtt2/GoqeKvld/Bu4IwjZ9ulZJm53QE+b+8tmjwi8F3JV6BVQX/yQ15YglMxZc4e +8ia6AFQer7C8HORSjKAyr7c3sVNnaHRnUPYtLmTeriZ539+Zhqurf4XsoPuAzPS4DB6TRWO53Lhb +m+1bOdRfYrCnjnxmOCyqsQhjF2d9zL2z8cM/z1A57dEZgxXbhxInlrfa6uWdvLrqOU+L73Sa58XQ +0uqGURzk/mQIKAR5BevKxXEOC++r6uwSEaEYBTJp0QwsGj0lmT+1fMptsK6ZmfoIYOcZwvK9UdPM +0wKswREMgM6r3JSda6M5UzrWhQIDAMV9o4ICwDCCArwwEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV +HQ8BAf8EBAMCAQYwggJ1BglghkgBhvhCAQ0EggJmFoICYkZJR1lFTEVNISBFemVuIHRhbnVzaXR2 +YW55IGEgTmV0TG9jayBLZnQuIE1pbm9zaXRldHQgU3pvbGdhbHRhdGFzaSBTemFiYWx5emF0YWJh +biBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBBIG1pbm9zaXRldHQgZWxla3Ryb25p +a3VzIGFsYWlyYXMgam9naGF0YXMgZXJ2ZW55ZXN1bGVzZW5laywgdmFsYW1pbnQgZWxmb2dhZGFz +YW5hayBmZWx0ZXRlbGUgYSBNaW5vc2l0ZXR0IFN6b2xnYWx0YXRhc2kgU3phYmFseXphdGJhbiwg +YXogQWx0YWxhbm9zIFN6ZXJ6b2Rlc2kgRmVsdGV0ZWxla2JlbiBlbG9pcnQgZWxsZW5vcnplc2kg +ZWxqYXJhcyBtZWd0ZXRlbGUuIEEgZG9rdW1lbnR1bW9rIG1lZ3RhbGFsaGF0b2sgYSBodHRwczov +L3d3dy5uZXRsb2NrLmh1L2RvY3MvIGNpbWVuIHZhZ3kga2VyaGV0b2sgYXogaW5mb0BuZXRsb2Nr +Lm5ldCBlLW1haWwgY2ltZW4uIFdBUk5JTkchIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0 +aGlzIGNlcnRpZmljYXRlIGFyZSBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIFF1YWxpZmllZCBDUFMg +YXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3Lm5ldGxvY2suaHUvZG9jcy8gb3IgYnkgZS1tYWlsIGF0 +IGluZm9AbmV0bG9jay5uZXQwHQYDVR0OBBYEFAlqYhaSsFq7VQ7LdTI6MuWyIckoMA0GCSqGSIb3 +DQEBBQUAA4IBAQCRalCc23iBmz+LQuM7/KbD7kPgz/PigDVJRXYC4uMvBcXxKufAQTPGtpvQMznN +wNuhrWw3AkxYQTvyl5LGSKjN5Yo5iWH5Upfpvfb5lHTocQ68d4bDBsxafEp+NFAwLvt/MpqNPfMg +W/hqyobzMUwsWYACff44yTB1HLdV47yfuqhthCgFdbOLDcCRVCHnpgu0mfVRQdzNo0ci2ccBgcTc +R08m6h/t280NmPSjnLRzMkqWmf68f8glWPhY83ZmiVSkpj7EUFy6iRiCdUgh0k8T6GB+B3bbELVR +5qq5aKrN9p2QdRLqOBrKROi3macqaJVmlaut74nLYKkGEsaUR+ko +-----END CERTIFICATE----- + +NetLock Notary (Class A) Root +============================= +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI +EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j +ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX +DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH +EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD +VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz +cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM +D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ +z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC +/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 +tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 +4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG +A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC +Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv +bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn +LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 +ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz +IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh +IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu +b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh +bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg +Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp +bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 +ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP +ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB +CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr +KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM +8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +NetLock Business (Class B) Root +=============================== +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg +VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD +VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv +bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg +VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S +o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr +1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV +HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ +RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh +dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 +ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv +c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg +YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz +Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA +bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl +IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 +YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj +cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM +43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR +stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +NetLock Express (Class C) Root +============================== +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD +KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ +BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j +ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z +W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 +euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw +DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN +RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn +YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB +IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i +aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 +ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y +emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k +IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ +UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg +YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 +xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW +gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +StartCom Ltd. +============= +-----BEGIN CERTIFICATE----- +MIIFFjCCBH+gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsDELMAkGA1UEBhMCSUwxDzANBgNVBAgT +BklzcmFlbDEOMAwGA1UEBxMFRWlsYXQxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xGjAYBgNVBAsT +EUNBIEF1dGhvcml0eSBEZXAuMSkwJwYDVQQDEyBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTEhMB8GCSqGSIb3DQEJARYSYWRtaW5Ac3RhcnRjb20ub3JnMB4XDTA1MDMxNzE3Mzc0OFoX +DTM1MDMxMDE3Mzc0OFowgbAxCzAJBgNVBAYTAklMMQ8wDQYDVQQIEwZJc3JhZWwxDjAMBgNVBAcT +BUVpbGF0MRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMRowGAYDVQQLExFDQSBBdXRob3JpdHkgRGVw +LjEpMCcGA1UEAxMgRnJlZSBTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxITAfBgkqhkiG9w0B +CQEWEmFkbWluQHN0YXJ0Y29tLm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA7YRgACOe +yEpRKSfeOqE5tWmrCbIvNP1h3D3TsM+x18LEwrHkllbEvqoUDufMOlDIOmKdw6OsWXuO7lUaHEe+ +o5c5s7XvIywI6Nivcy+5yYPo7QAPyHWlLzRMGOh2iCNJitu27Wjaw7ViKUylS7eYtAkUEKD4/mJ2 +IhULpNYILzUCAwEAAaOCAjwwggI4MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgHmMB0GA1Ud +DgQWBBQcicOWzL3+MtUNjIExtpidjShkjTCB3QYDVR0jBIHVMIHSgBQcicOWzL3+MtUNjIExtpid +jShkjaGBtqSBszCBsDELMAkGA1UEBhMCSUwxDzANBgNVBAgTBklzcmFlbDEOMAwGA1UEBxMFRWls +YXQxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xGjAYBgNVBAsTEUNBIEF1dGhvcml0eSBEZXAuMSkw +JwYDVQQDEyBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYS +YWRtaW5Ac3RhcnRjb20ub3JnggEAMB0GA1UdEQQWMBSBEmFkbWluQHN0YXJ0Y29tLm9yZzAdBgNV +HRIEFjAUgRJhZG1pbkBzdGFydGNvbS5vcmcwEQYJYIZIAYb4QgEBBAQDAgAHMC8GCWCGSAGG+EIB +DQQiFiBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAyBglghkgBhvhCAQQEJRYjaHR0 +cDovL2NlcnQuc3RhcnRjb20ub3JnL2NhLWNybC5jcmwwKAYJYIZIAYb4QgECBBsWGWh0dHA6Ly9j +ZXJ0LnN0YXJ0Y29tLm9yZy8wOQYJYIZIAYb4QgEIBCwWKmh0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9y +Zy9pbmRleC5waHA/YXBwPTExMTANBgkqhkiG9w0BAQQFAAOBgQBscSXhnjSRIe/bbL0BCFaPiNhB +OlP1ct8nV0t2hPdopP7rPwl+KLhX6h/BquL/lp9JmeaylXOWxkjHXo0Hclb4g4+fd68p00UOpO6w +NnQt8M2YI3s3S9r+UZjEHjQ8iP2ZO1CnwYszx8JSFhKVU2Ui77qLzmLbcCOxgN8aIDjnfg== +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj +YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH +AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw +Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg +U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 +LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh +cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT +dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC +AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh +3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm +vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk +fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 +fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ +EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl +1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ +lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro +g14= +-----END CERTIFICATE----- + +Taiwan GRCA +=========== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG +EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X +DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv +dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN +w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 +BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O +1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO +htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov +J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 +Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t +B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB +O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 +lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV +HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 +09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj +Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 +Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU +D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz +DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk +Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk +7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ +CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy ++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS +-----END CERTIFICATE----- + +Firmaprofesional Root CA +======================== +-----BEGIN CERTIFICATE----- +MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMxIjAgBgNVBAcT +GUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1dG9yaWRhZCBkZSBDZXJ0aWZp +Y2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FA +ZmlybWFwcm9mZXNpb25hbC5jb20wHhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTEL +MAkGA1UEBhMCRVMxIjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMT +OUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2 +ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20wggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5uCp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5V +j1H5WuretXDE7aTt/6MNbg9kUDGvASdYrv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJH +lShbz++AbOCQl4oBPB3zhxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf +3H5idPayBQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcLiam8 +NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcbAgMBAAGjgZ8wgZww +KgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lvbmFsLmNvbTASBgNVHRMBAf8ECDAG +AQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQAD +ggEBAEdz/o0nVPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq +u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36mhoEyIwOdyPdf +wUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzflZKG+TQyTmAyX9odtsz/ny4Cm +7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBpQWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YG +VM+h4k0460tQtcsm9MracEpqoeJ5quGnM/b9Sh/22WA= +-----END CERTIFICATE----- + +Wells Fargo Root CA +=================== +-----BEGIN CERTIFICATE----- +MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMCVVMxFDASBgNV +BAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN +MDAxMDExMTY0MTI4WhcNMjEwMTE0MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dl +bGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEv +MC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n135zHCLielTWi5MbqNQ1mX +x3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHESxP9cMIlrCL1dQu3U+SlK93OvRw6esP3 +E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4OJgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5 +OEL8pahbSCOz6+MlsoCultQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4j +sNtlAHCEAQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMBAAGj +YTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcBCzAyMDAGCCsGAQUF +BwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRwb2xpY3kwDQYJKoZIhvcNAQEFBQAD +ggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrv +m+0fazbuSCUlFLZWohDo7qd/0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0R +OhPs7fpvcmR7nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx +x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ33ZwmVxwQ023 +tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s= +-----END CERTIFICATE----- + +Swisscom Root CA 1 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 +MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM +MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF +NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe +AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC +b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn +7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN +cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp +WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 +haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY +MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 +MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn +jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ +MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H +VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl +vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl +OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 +1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq +nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy +x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW +NY6E0F/6MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +Certplus Class 2 Primary CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE +BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN +OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy +dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR +5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ +Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO +YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e +e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME +CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ +YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t +L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD +P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R +TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ +7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW +//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +DST ACES CA X6 +============== +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT +MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha +MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE +CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI +DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa +pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow +GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy +MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu +Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy +dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU +CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 +5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t +Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs +vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 +oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 1 +============================================== +-----BEGIN CERTIFICATE----- +MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP +MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 +acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx +MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg +U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB +TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC +aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX +yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i +Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ +8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 +W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 +sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE +q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy +B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY +nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2 +============================================== +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN +MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr +dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G +A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls +acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe +LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI +x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g +QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr +5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB +AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt +Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 +Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ +hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P +9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 +UrbnBEI= +-----END CERTIFICATE----- + +SwissSign Platinum CA - G2 +========================== +-----BEGIN CERTIFICATE----- +MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWduIFBsYXRpbnVtIENBIC0gRzIw +HhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAwWjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMM +U3dpc3NTaWduIEFHMSMwIQYDVQQDExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu +669yIIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2HtnIuJpX+UF +eNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+6ixuEFGSzH7VozPY1kne +WCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5objM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIo +j5+saCB9bzuohTEJfwvH6GXp43gOCWcwizSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/6 +8++QHkwFix7qepF6w9fl+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34T +aNhxKFrYzt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaPpZjy +domyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtFKwH3HBqi7Ri6Cr2D ++m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuWae5ogObnmLo2t/5u7Su9IPhlGdpV +CX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMBAAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCv +zAeHFUdvOMW0ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW +IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUAA4ICAQAIhab1 +Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0uMoI3LQwnkAHFmtllXcBrqS3 +NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4 +U99REJNi54Av4tHgvI42Rncz7Lj7jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8 +KV2LwUvJ4ooTHbG/u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl +9x8DYSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1puEa+S1B +aYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXaicYwu+uPyyIIoK6q8QNs +OktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbGDI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSY +Mdp08YSTcU1f+2BY0fvEwW2JorsgH51xkcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAci +IfNAChs0B0QTwoRqjt8ZWr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g== +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ +cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN +b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 +nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge +RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt +tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI +hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K +Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN +NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa +Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG +1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +thawte Primary Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 +MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg +SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv +KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT +FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs +oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ +1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc +q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K +aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p +afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF +AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE +uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 +jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH +z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G5 +============================================================ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln +biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh +dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz +j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD +Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r +fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv +Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG +SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ +X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE +KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC +Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE +ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +DigiNotar Root CA +================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQDHbanJEMTiye/hXQWJM8TDANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQG +EwJOTDESMBAGA1UEChMJRGlnaU5vdGFyMRowGAYDVQQDExFEaWdpTm90YXIgUm9vdCBDQTEgMB4G +CSqGSIb3DQEJARYRaW5mb0BkaWdpbm90YXIubmwwHhcNMDcwNTE2MTcxOTM2WhcNMjUwMzMxMTgx +OTIxWjBfMQswCQYDVQQGEwJOTDESMBAGA1UEChMJRGlnaU5vdGFyMRowGAYDVQQDExFEaWdpTm90 +YXIgUm9vdCBDQTEgMB4GCSqGSIb3DQEJARYRaW5mb0BkaWdpbm90YXIubmwwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCssFjBAL3YIQgLK5r+blYwBZ8bd5AQQVzDDYcRd46B8cp86Yxq +7Th0Nbva3/m7wAk3tJZzgX0zGpg595NvlX89ubF1h7pRSOiLcD6VBMXYtsMW2YiwsYcdcNqGtA8U +i3rPENF0NqISe3eGSnnme98CEWilToauNFibJBN4ViIlHgGLS1Fx+4LMWZZpiFpoU8W5DQI3y0u8 +ZkqQfioLBQftFl9VkHXYRskbg+IIvvEjzJkd1ioPgyAVWCeCLvriIsJJsbkBgWqdbZ1Ad2h2TiEq +bYRAhU52mXyC8/O3AlnUJgEbjt+tUwbRrhjd4rI6y9eIOI6sWym5GdOY+RgDz0iChmYLG2kPyes4 +iHomGgVMktck1JbyrFIto0fVUvY//s6EBnCmqj6i8rZWNBhXouSBbefK8GrTx5FrAoNBfBXva5pk +XuPQPOWx63tdhvvL5ndJzaNl3Pe5nLjkC1+Tz8wwGjIczhxjlaX56uF0i57pK6kwe6AYHw4YC+Vb +qdPRbB4HZ4+RS6mKvNJmqpMBiLKR+jFc1abBUggJzQpjotMipuih2TkGl/VujQKQjBR7P4DNG5y6 +xFhyI6+2Vp/GekIzKQc/gsnmHwUNzUwoNovTyD4cxojvXu6JZOkd69qJfjKmadHdzIif0dDJZiHc +BmfFlHqabWJMfczgZICynkeOowIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQUiGi/4I41xDs4a2L3KDuEgcgM100wDQYJKoZIhvcNAQEFBQADggIBADsC +jcs8MOhuoK3yc7NfniUTBAXT9uOLuwt5zlPe5JbF0a9zvNXD0EBVfEB/zRtfCdXyfJ9oHbtdzno5 +wozWmHvFg1Wo1X1AyuAe94leY12hE8JdiraKfADzI8PthV9xdvBoY6pFITlIYXg23PFDk9Qlx/KA +ZeFTAnVR/Ho67zerhChXDNjU1JlWbOOi/lmEtDHoM/hklJRRl6s5xUvt2t2AC298KQ3EjopyDedT +FLJgQT2EkTFoPSdE2+Xe9PpjRchMPpj1P0G6Tss3DbpmmPHdy59c91Q2gmssvBNhl0L4eLvMyKKf +yvBovWsdst+Nbwed2o5nx0ceyrm/KkKRt2NTZvFCo+H0Wk1Ya7XkpDOtXHAd3ODy63MUkZoDweoA +ZbwH/M8SESIsrqC9OuCiKthZ6SnTGDWkrBFfGbW1G/8iSlzGeuQX7yCpp/Q/rYqnmgQlnQ7KN+ZQ +/YxCKQSa7LnPS3K94gg2ryMvYuXKAdNw23yCIywWMQzGNgeQerEfZ1jEO1hZibCMjFCz2IbLaKPE +CudpSyDOwR5WS5WpI2jYMNjD67BVUc3l/Su49bsRn1NU9jQZjHkJNsphFyUXC4KYcwx3dMPVDceo +EkzHp1RxRy4sGn3J4ys7SN4nhKdjNrN9j6BkOSQNPXuHr2ZcdBtLc7LljPCGmbjlxd+Ewbfr +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +WellsSecure Public Root Certificate Authority +============================================= +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM +F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw +NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN +MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl +bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD +VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 +iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 +i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 +bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB +K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB +AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu +cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm +lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB +i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww +GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI +K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 +bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj +qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es +E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ +tylv2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +MD5 Collisions Forged Rogue CA 25c3 +=================================== +-----BEGIN CERTIFICATE----- +MIIEMjCCA5ugAwIBAgIBQjANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp +bmVzcyBDQS0xMB4XDTA0MDczMTAwMDAwMVoXDTA0MDkwMjAwMDAwMVowPDE6MDgGA1UEAxMxTUQ1 +IENvbGxpc2lvbnMgSW5jLiAoaHR0cDovL3d3dy5waHJlZWRvbS5vcmcvbWQ1KTCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAuqZZySwo1iqw+O2fRqSkN+4OGWhZ0bMDmVHWFppeN2sV4A5L9YRk ++KPbQW811ZsVH9vEOFJwgZdej6C193458DKsHq1E0rP6SMPOkZvs9Jx84Vr1yDdrmoPe58oglzFC +cxWRaPSIr/koKMXpD3OwF0sTTJl10ETmfghsGvJPG0ECAwEAAaOCAiQwggIgMAsGA1UdDwQEAwIB +xjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSnBGAfq3JDCMV/CJBVVhzWzuY46zAfBgNVHSME +GDAWgBS+qKB0clBrRLfJI9j7qP+zV2tobDCCAb4GCWCGSAGG+EIBDQSCAa8WggGrMwAAACdeOeCJ +YQ9Oo8VFCza7AdFTqsMIj2/4Tz6Hh0QR3GDg35JV+bhzG1STxZ/QRsRgtjVizbmvHKhpGslbPJY3 +wO1n77v+wIucUC8pvYMino4I+qwTcKJYf2JiihH3ifbftmdZcxb7YxaKtJE4zi71tr5MpJRJ5GUR +CkIVycEw4mnVRX2lJru5YexiZPA54ee8aNhQUZ4dYNPRo6cK+AMgoXABF5E2TwJwMYaD3fcP2Acd +EbMTBKXc8K5QsSgOY2kqDIJvj0cz32yiBpLxT0W+2TA2oyuM1neuNWN/Tkyak0g22Z8CAwEAAaOB +vTCBujAOBgNVHQ8BAf8EBAMCBPAwHQYDVR0OBBYEFM2mg/qlYDf3ljcXKd5BePGHiVXnMDsGA1Ud +HwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmwuZ2VvdHJ1c3QuY29tL2NybHMvZ2xvYmFsY2ExLmNybDAf +BgNVHSMEGDAWgBS+qKB0clBrRLfJI9j7qP+zV2tobDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQQFAAOBgQCnIQKN0Q6igHcl/UNgFY/s75BH +1IRCFSYRHM3CPBApqbbfq1d1kdrlK7OQRRwwY1Y/itlQ+u1YbMBlrGZX3hzGdjv1AA6ORc5/TJDs +K8bNs7SPYtD+t8UmckTt9phbrsvRlfXaCL5oRrF1yOwdjx56lPGqU3iiRa5U6tGedMh2Zw== +-----END CERTIFICATE----- + +IGC/A +===== +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD +VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE +Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy +MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI +EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT +STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 +TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW +So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy +HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd +frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ +tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB +egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC +iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK +q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q +MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI +lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF +0mBWWg== +-----END CERTIFICATE----- + +Security Communication EV RootCA1 +================================= +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE +BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl +Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO +/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX +WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z +ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 +bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK +9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm +iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG +Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW +mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW +T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GA CA +=============================== +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE +BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG +A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH +bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD +VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw +IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 +IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 +Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg +Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD +d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ +/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R +LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm +MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 ++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY +okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= +-----END CERTIFICATE----- + +S-TRUST Authentication and Encryption Root CA 2005 PN +===================================================== +-----BEGIN CERTIFICATE----- +MIIEezCCA2OgAwIBAgIQNxkY5lNUfBq1uMtZWts1tzANBgkqhkiG9w0BAQUFADCBrjELMAkGA1UE +BhMCREUxIDAeBgNVBAgTF0JhZGVuLVd1ZXJ0dGVtYmVyZyAoQlcpMRIwEAYDVQQHEwlTdHV0dGdh +cnQxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fzc2VuIFZlcmxhZyBHbWJIMT4wPAYDVQQDEzVT +LVRSVVNUIEF1dGhlbnRpY2F0aW9uIGFuZCBFbmNyeXB0aW9uIFJvb3QgQ0EgMjAwNTpQTjAeFw0w +NTA2MjIwMDAwMDBaFw0zMDA2MjEyMzU5NTlaMIGuMQswCQYDVQQGEwJERTEgMB4GA1UECBMXQmFk +ZW4tV3VlcnR0ZW1iZXJnIChCVykxEjAQBgNVBAcTCVN0dXR0Z2FydDEpMCcGA1UEChMgRGV1dHNj +aGVyIFNwYXJrYXNzZW4gVmVybGFnIEdtYkgxPjA8BgNVBAMTNVMtVFJVU1QgQXV0aGVudGljYXRp +b24gYW5kIEVuY3J5cHRpb24gUm9vdCBDQSAyMDA1OlBOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA2bVKwdMz6tNGs9HiTNL1toPQb9UY6ZOvJ44TzbUlNlA0EmQpoVXhOmCTnijJ4/Ob +4QSwI7+Vio5bG0F/WsPoTUzVJBY+h0jUJ67m91MduwwA7z5hca2/OnpYH5Q9XIHV1W/fuJvS9eXL +g3KSwlOyggLrra1fFi2SU3bxibYs9cEv4KdKb6AwajLrmnQDaHgTncovmwsdvs91DSaXm8f1Xgqf +eN+zvOyauu9VjxuapgdjKRdZYgkqeQd3peDRF2npW932kKvimAoA0SVtnteFhy+S8dF2g08LOlk3 +KC8zpxdQ1iALCvQm+Z845y2kuJuJja2tyWp9iRe79n+Ag3rm7QIDAQABo4GSMIGPMBIGA1UdEwEB +/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTVFJv +bmxpbmUxLTIwNDgtNTAdBgNVHQ4EFgQUD8oeXHngovMpttKFswtKtWXsa1IwHwYDVR0jBBgwFoAU +D8oeXHngovMpttKFswtKtWXsa1IwDQYJKoZIhvcNAQEFBQADggEBAK8B8O0ZPCjoTVy7pWMciDMD +pwCHpB8gq9Yc4wYfl35UvbfRssnV2oDsF9eK9XvCAPbpEW+EoFolMeKJ+aQAPzFoLtU96G7m1R08 +P7K9n3frndOMusDXtk3sU5wPBG7qNWdX4wple5A64U8+wwCSersFiXOMy6ZNwPv2AtawB6MDwidA +nwzkhYItr5pCHdDHjfhA7p0GVxzZotiAFP7hYy0yh9WUUpY6RsZxlj33mA6ykaqP2vROJAA5Veit +F7nTNCtKqUDMFypVZUF0Qn71wK/Ik63yGFs9iQzbRzkk+OBM8h+wPQrKBU6JIRrjKpms/H+h8Q8b +Hz2eBIPdltkdOpQ= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE +BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL +EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 +MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz +dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT +GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG +d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N +oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc +QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ +PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb +MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG +IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD +VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 +LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A +dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA +4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg +AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA +egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 +Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO +PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv +c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h +cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw +IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT +WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV +MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp +Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal +HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT +nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE +aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK +yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB +S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +AC Ra+z Certic+mara S.A. +========================== +-----BEGIN CERTIFICATE----- +MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT +AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg +LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w +HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ +U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh +IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN +yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU +2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 +4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP +2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm +8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf +HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa +Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK +5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b +czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g +ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF +BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug +cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf +AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX +EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v +/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 +MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 +3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk +eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f +/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h +RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU +Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 2 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw +MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw +IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 +xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ +Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u +SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G +dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ +KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj +TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP +JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk +vQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 3 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw +MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W +yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo +6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ +uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk +2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE +O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 +yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 +IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal +092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc +5A== +-----END CERTIFICATE----- + +TC TrustCenter Universal CA I +============================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy +IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN +MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg +VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw +JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC +qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv +xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw +ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O +gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j +BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG +1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy +vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 +ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT +ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a +7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY +-----END CERTIFICATE----- + +Deutsche Telekom Root CA 2 +========================== +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT +RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG +A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 +MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G +A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS +b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 +bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI +KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY +AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK +Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV +jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV +HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr +E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy +zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 +rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G +dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +ComSign CA +========== +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0MRMwEQYDVQQD +EwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTMy +MThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMTCkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNp +Z24xCzAJBgNVBAYTAklMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49q +ROR+WCf4C9DklBKK8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTy +P2Q298CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb2CEJKHxN +GGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxCejVb7Us6eva1jsz/D3zk +YDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7KpiXd3DTKaCQeQzC6zJMw9kglcq/QytNuEM +rkvF7zuZ2SOzW120V+x0cAwqTwIDAQABo4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAy +oDCgLoYsaHR0cDovL2ZlZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0P +AQH/BAQDAgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRLAZs+ +VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWdfoPPbrxHbvUanlR2 +QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0McXS6hMTXcpuEfDhOZAYnKuGntewI +mbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb +/627HOkthIDYIb6FUtnUdLlphbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VG +zT2ouvDzuFYkRes3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U +AGegcQCCSA== +-----END CERTIFICATE----- + +ComSign Secured CA +================== +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE +AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w +NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD +QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs +49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH +7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB +kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 +9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw +AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t +U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA +j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC +AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a +BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp +FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP +51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz +OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== +-----END CERTIFICATE----- + +Cybertrust Global Root +====================== +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li +ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 +MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD +ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW +0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL +AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin +89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT +8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 +MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G +A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO +lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi +5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 +hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T +X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- diff --git a/digsby/res/corner1.gif b/digsby/res/corner1.gif new file mode 100644 index 0000000..a55d26a Binary files /dev/null and b/digsby/res/corner1.gif differ diff --git a/digsby/res/corner2.gif b/digsby/res/corner2.gif new file mode 100644 index 0000000..838ccec Binary files /dev/null and b/digsby/res/corner2.gif differ diff --git a/digsby/res/corner3.gif b/digsby/res/corner3.gif new file mode 100644 index 0000000..d876294 Binary files /dev/null and b/digsby/res/corner3.gif differ diff --git a/digsby/res/defaults.yaml b/digsby/res/defaults.yaml new file mode 100644 index 0000000..86bee2d --- /dev/null +++ b/digsby/res/defaults.yaml @@ -0,0 +1,593 @@ +profile: + promote: yes + name: 'I use digsby!' + + formatting: "arial 10" + +login: + invisible: no + status: online + status_message: "" + + reconnect: + attempt: yes + attempt_times: 5 + +messaging: + spellcheck: + enabled: yes + max_suggestions: 5 + engineoptions: + lang: en + encoding: utf-8 + keyboard: standard + kb_shortcut_fixes: no + default_style: "arial 10" + typed_delay: 3.5 + shift_to_send: no + show_send_button: no + show_formatting_bar: yes + show_actions_bar: yes + rtl_input: no + echo_immediately: yes + tabs: + enabled: yes + ctrl_new_window: yes + icon: buddy + warn_on_close: yes + left_down_activates: no + psychic: no + become_idle: yes + idle_after: 10 + auto_away: no + auto_away_after: 20 + auto_away_message: "I walked away from the computer." + typing_notifications: + show_in_title: yes + clear_after: 30 + when_away: + autorespond: no + disable_sound: no + disable_popup: no + show_hide_convo_checkbox: yes + hide_convos: no + popups: + close_dismisses_hidden: no + groupchat: + enabled: yes + +videochat: + report_capability: yes + +appearance: + skin: Windows 7 + variant: ~ + conversations: + theme: Smooth Operator + variant: ~ + icon: 'none' + emoticons: + enabled: yes + pack: "MSN Messenger" + show_header: yes + show_message_fonts: yes + show_message_colors: yes + +conversation_window: + open_email_for_offline: no + when_created: window + always_on_top: no + room_announcements: yes + resize_input: yes + input_base_height: 0 + show_history: yes + merge_metacontact_history: no + num_lines: 5 + new_action: stealfocus + + timestamp: yes + timestamp_format: '%#I:%M %p' + border: no + + actions_bar_icons: next + + roomlist: + width: 110 + + notify_flash: yes + flash_count: 3 + flash_time: 0 + cycle_unread_ims: yes + unread_cycle_pause: 1000 + paste: + images: yes + +imwin: + ads: yes + ads_glass: yes + ads_position: bottom + ads_disable_flash: no + +tabs: + enabled: no + hide_at_1: yes + rows: 2 + max_width: 100 + side_tabs: no + side_tabs_width: 100 + style: 2 + flip: no + tabbar_x: no + notify_count: 10 + notify_duration: 1000 + no_type_note: no + preview_alpha: 200 + fade_speed: normal + + +log: + ims: yes + chats: yes + events: yes + encrypt: no + + outputdir: "" + + +startup: + splash: yes + single_instance: yes + launch_on_login: no + default_im_check: no + +filetransfer: + create_subfolders: no + auto_accept_from_blist: no + use_port: no + port: 0 + virus_scan: no + virus_scan_file: "" + + save_to: no + save_to_dir: '' + + window: + show_on_starting: yes + +connectionlist: + max_height: 200 + online_linger: 2000 + +infobox: + pause_time: 250 + min_angle_of_entry: 30 + max_angle_of_entry: 60 + tracelines: no + show: yes + showprofile: yes + showcapabilities: yes + hide_delay: 500 + show_delay: 1000 + ratio_to_screen: 0.75 + width: 375 + iconsize: 64 + right_of_list: no + animate: yes + animation: + time: 200 + interval: 10 + resizing: no + method: 2 + slide_speed: 0.4 + birthday_format: '%m/%d/%y' + fonts: + normal: Times New Roman + fixed: Courier New + aim: + show_profile_link: no + +email: + preview_length: 200 + marquee: + enabled: no + delay_ms: 40 + signature: + enabled: yes + value: !python/unicode |- + + _____________________________________________________________ + Sent using Digsby - http://email.digsby.com + + +trayicons: + digsby: + show_status_orb: yes + show_available_orb: no + unread_messages: + flash_interval_ms: 1000 + show_count: yes + flash_only_count: no + email: + gray_on_empty: yes + show_count: yes + + +buddylist: + show_on_startup: yes + show_mobile: yes + show_offline: yes + group_offline: yes + hide_offline_groups: no + fakeroot_name: Contacts + + show_email_as: both + show_social_as: both + + emailpanel: + order: [] + + socialpanel: + order: [] + + show_status: yes + + order: ['status', 'blist', 'clist', 'elist', 'slist'] + + tray_icon: always + close_button_exits: no + use_skin: no + + hide_scrollbar: no + auto_resize: no + + autohide: no # Autohide when not in focus + always_on_top: no + show_menubar: yes + show_in_taskbar: yes + + hide_group_titles: no + show_recent_buddies: no + recent_buddies_num: 5 + + # seconds for a buddy to appear differently when signing on or off + coming_going_time: 4 + + sorting: yes + sortby: none none + + border_space: 35 + + dock: + slide_velocity: 1000 + + layout: + ez_layout: yes + name_font_face: Tahoma + name_font_size: 10 + show_extra: yes + extra_padding: 1 + extra_info: both + extra_font_face: Arial + extra_font_size: 7 + show_buddy_icon: yes + buddy_icon_pos: right + buddy_icon_size: 32 + show_status_icon: yes + status_icon_pos: left + status_icon_size: 16 + side_icon_size: 13 + show_service_icon: yes + service_icon_size: 16 + service_icon_pos: bright + indent: 6 + minimum_height: 16 + badge_ratio: 0.5 + badge_min_size: 6 + badge_max_size: 16 + padding: 2 + show_idle_after: 1 + grey_offline: yes + + expanded: [] + +prompts: + defaults: + contacts: + del_group: yes + del_contact: yes + block: yes + +privacy: + send_typing_notifications: yes + www_auto_signin: yes + unknown_buddy_popup: yes + +email_view: panel +social_view: panel +can_has_social_update: no + +notifications: + enable_sound: yes + enable_popup: yes + popups: + location: lowerleft + duration: 4000 + opacity: + hover: 100 + normal: 100 + max_height: 400 + max_lines: 2 + offset: [0, 0] + monitor: 0 + reverse_scroll: yes #yes: scroll down increases, no: scroll up increases + + quiet_time: 10 # how many seconds after a silence to squash popups + editor: + view: simple + +windows: + sticky: yes + +fullscreen: + hide_convos: no + disable_sounds: no + disable_popups: no + disable_alerts: yes + set_status: no + set_status_message: "" + +locale: en + +proxy: + use_proxy: no + protocol: SOCKS4 + host: '' + port: '' + username: '' + password: '' + + +# +# Protocols +# + +icq: + default_server: login.icq.com + default_port: 5190 + proxy: + use_global: yes + protocol: SOCKS4 + host: '' + port: '' + username: '' + password: '' + +oscar: + keepalive: 30 + buddyicon: + default: linc.gif + max_size: [40, 40] + capabilities: + - avatar + - chat_service + - ichatav_info + - direct_im + icq: + set_about: no + peer: + auto_accept: + sharing: no + direct_connect: no + file_transfer: no + always_proxy: no + proxy_server: ars.oscar.aol.com + + # local IP address and port to bind when listening for p2p conns + local_ip: '' + incoming_port: 0 + use_port: no + port: 0 + share_files: no + share_files_dir: "" + default_port: 5190 + default_server: login.oscar.aol.com + +aim: + default_server: login.oscar.aol.com + default_port: 5190 + proxy: + use_global: yes + protocol: SOCKS4 + host: '' + port: '' + username: '' + password: '' + +msn: + default_port: 1863 + default_server: messenger.hotmail.com + proxy: + use_global: yes + protocol: SOCKS4 + host: '' + port: '' + username: '' + password: '' + spaces: + photosize: 20 + direct: + timeout: 5 + login: + timeout: 60 + +jabber: + default_port: 5222 + default_server: '' + use_direct_ft: yes + use_proxy_ft: yes + use_faulty_localhost_ips: no + system_message: + show_gone: no + proxy: + use_global: yes + protocol: SOCKS4 + host: '' + port: '' + username: '' + password: '' + phone_is_mobile: no + +digsby: + updater: + auto_download: yes + install_prompt: yes + use_transfer_window: yes + help_menu: no + initial_delay: 300 # 5 minutes + update_interval: 21600 # 6 hours + default_port: 5222 + default_server: digsby.org + guest: + domains: ['guest.digsby.org'] + status: + promote_tag: + enabled: yes + upgrade_response: ~ + +yahoo: + default_port: 5050 + default_server: scs.msg.yahoo.com + filetransfer: + server: 'filetransfer.msg.yahoo.com:80' + relay: 'relay.msg.yahoo.com' + proxy: + use_global: yes + protocol: SOCKS4 + host: '' + port: '' + username: '' + password: '' + +gmail: + proxy: + use_global: yes + protocol: SOCKS4 + host: '' + port: '' + username: '' + password: '' + default_server: 'gmail.com' + default_port: 5222 + +irc: + show_colors: yes + default_port: 6667 + +myspace: + default_server: im.myspace.akadns.net + default_port: 1863 + +facebook: + newsfeed: + maxage: 172800 #two days in seconds + dateformat: "%B %d" + user_filter: ['-1'] + min_wall_interval: 172800 + combined_wall: + num_visible: 5 + webkitbrowser: yes + api: + https: no #significantly slower + +debug: + protocolstate: no + shell: + minimal: yes + font: Courier New + history: + size: 600 + lines: [] + + shell_history: [] + shell_history_size: 600 + loglevel: 0 + bug_reporter: + auto_open: true + message_area: + show_edit_source: no + show_jsconsole: no + traceback_dialog: yes + +menus: + shift_mode: no + submenu_delay: 200 + +metacontacts: + show_first_online_status: no + + +plugins: + nowplaying: + format: "%(artist)s - %(title)s" + backup_format: "%(filename)s" + show_link: no + +research: #plura processing +# enabled: yes #bool: old and broken, replaced with local pref. + revive_interval_seconds: 3600 #int: try to reboot the java process after an hour dead + cpu_percent: 75 #float: soft limit max, still hard limited to 25% on laptops, etc. + bandwidth_percent: 90 #float + idle_time_min: 5 #int: minutes of idle time to wait before turning on. +# idle_time_ms: 300000 #int: if present, gives more fine grained control over idle time (and overrides it) + always_on: no #bool: always on? still limited by battery setting + battery_override: no #bool: run when on battery? still limited by idle/always on + +imap: + max_fetch_bytes: 6144 + +twitter: + max_tab_length: 100 # number to show + max_fetch_size: 100 # number to ask the api for + min_cache_size: 100 # number to store + scan_urls: yes + + paste: + shorten_urls: true + upload_images: true + autoscroll: + when_at_bottom: true + popups: + mark_as_read: true + +fbchat: + min_update_interval: 35000 # milliseconds + max_update_interval: 60000 # milliseconds + signoff: no + use_persistent: yes + conn_lost_threshold: 3 + +logging: + comtypes: 20 + comtypes._objects: 20 + +search: + buddylist: + enabled: yes + show_hint: yes + external: + - {name: google, enabled: yes} + - {name: oneriot, enabled: yes} + - {name: amazon, enabled: yes} + - {name: newegg, enabled: yes} + - {name: itunes, enabled: yes} + - {name: twitter, enabled: yes} + - {name: facebook, enabled: yes} + - {name: linkedin, enabled: yes} + - {name: youtube, enabled: yes} + - {name: wikipedia, enabled: yes} + - {name: technorati, enabled: yes} + - {name: bing, enabled: no} + +social: + use_global_status: yes + feed_ads: yes diff --git a/digsby/res/dictionaries-5.yaml b/digsby/res/dictionaries-5.yaml new file mode 100644 index 0000000..fd4d86c --- /dev/null +++ b/digsby/res/dictionaries-5.yaml @@ -0,0 +1,195 @@ +--- +rw: + name_english: Kinyarwanda + location: rw/aspell-rw-0.50-0.tar.bz2 +pl: + name_english: Polish + location: pl/aspell5-pl-6.0_20061121-0.tar.bz2 + name_native: polski +de: + name_english: German + location: de/aspell-de-0.50-2.tar.bz2 +de_CH: + name_english: German (Swiss) + location: de/aspell-de-0.50-2.tar.bz2 +sw: + name_english: Swahili + location: sw/aspell-sw-0.50-0.tar.bz2 + name_native: Kiswahili +ms: + name_english: Malay + location: ms/aspell-ms-0.50-0.tar.bz2 +gl: + name_english: Galician + location: gl/aspell-gl-0.50-0.tar.bz2 + name_native: Galego +fr: + name_english: French + location: fr/aspell-fr-0.50-3.tar.bz2 + name_native: "Fran\xC3\xA7ais" +fr_CH: + name_english: French (Swiss) + location: fr/aspell-fr-0.50-3.tar.bz2 +tet: + name_english: Tetum + location: tet/aspell5-tet-0.1.1.tar.bz2 +pt_PT: + name_english: Portuguese + location: pt/aspell-pt-0.50-2.tar.bz2 +pt_BR: + name_english: Brazilian Portuguese + location: pt/aspell-pt-0.50-2.tar.bz2 +nn: + name_english: Norwegian Nynorsk + location: nn/aspell-nn-0.50.1-1.tar.bz2 +it: + name_english: Italian + location: it/aspell-it-0.53-0.tar.bz2 +es: + name_english: Spanish + location: es/aspell-es-0.50-2.tar.bz2 + name_native: "Espa\xC3\xB1ol" +bg: + name_english: Bulgarian + location: bg/aspell5-bg-4.0-0.tar.bz2 +wa: + name_english: Walloon + location: wa/aspell-wa-0.50-0.tar.bz2 + name_native: walon +mi: + name_english: Maori + location: mi/aspell-mi-0.50-0.tar.bz2 +gd: + name_english: Scottish Gaelic + location: gd/aspell5-gd-0.1.1-1.tar.bz2 + name_native: "G\xC3\xA0idhlig" +uk: + name_english: Ukrainian + location: uk/aspell-uk-0.51-0.tar.bz2 +ro: + name_english: Romanian + location: ro/aspell-ro-0.50-2.tar.bz2 +hr: + name_english: Croatian + location: hr/aspell-hr-0.51-0.tar.bz2 +sv: + name_english: Swedish + location: sv/aspell-sv-0.51-0.tar.bz2 +da: + name_english: Danish + location: da/aspell5-da-1.4.42-1.tar.bz2 + name_native: Dansk +mg: + name_english: Malagasy + location: mg/aspell5-mg-0.03-0.tar.bz2 +sk: + name_english: Slovak + location: sk/aspell-sk-0.52-0.tar.bz2 +is: + name_english: Icelandic + location: is/aspell-is-0.51.1-0.tar.bz2 +mt: + name_english: Maltese + location: mt/aspell-mt-0.50-0.tar.bz2 +ca: + name_english: Catalan + location: ca/aspell-ca-0.50-2.tar.bz2 +sc: + name_english: Sardinian + location: sc/aspell5-sc-1.0.tar.bz2 +id: + name_english: Indonesian + location: id/aspell5-id-1.2-0.tar.bz2 + name_native: Bahasa Indonesia +gv: + name_english: Manx Gaelic + location: gv/aspell-gv-0.50-0.tar.bz2 +br: + name_english: Breton + location: br/aspell-br-0.50-2.tar.bz2 +nl: + name_english: Dutch + location: nl/aspell-nl-0.50-2.tar.bz2 +ga: + name_english: Irish + location: ga/aspell5-ga-4.3-0.tar.bz2 + name_native: Gaeilge +eo: + name_english: Esperanto + location: eo/aspell-eo-0.50-2.tar.bz2 +mk: + name_english: Macedonian + location: mk/aspell-mk-0.50-0.tar.bz2 +tn: + name_english: Setswana + location: tn/aspell5-tn-1.0.1-0.tar.bz2 +hil: + name_english: Hiligaynon + location: hil/aspell5-hil-0.11-0.tar.bz2 +cs: + name_english: Czech + location: cs/aspell-cs-0.51-0.tar.bz2 +nb: + name_english: Norwegian Bokmal + location: nb/aspell-nb-0.50.1-0.tar.bz2 +af: + name_english: Afrikaans + location: af/aspell-af-0.50-0.tar.bz2 +cy: + name_english: Welsh + location: cy/aspell-cy-0.50-3.tar.bz2 +#tk: +# name_english: Turkmen +# location: tk/aspell5-tk-0.01-0.tar.bz2 +ia: + name_english: Interlingua + location: ia/aspell-ia-0.50-1.tar.bz2 +zu: + name_english: Zulu + location: zu/aspell-zu-0.50-0.tar.bz2 +tr: + name_english: Turkish + location: tr/aspell-tr-0.50-0.tar.bz2 +ny: + name_english: Chichewa + location: ny/aspell5-ny-0.01-0.tar.bz2 +ku: + name_english: Kurdi + location: ku/aspell5-ku-0.20-1.tar.bz2 + name_native: "Kurd\xC3\xAE" +en: + name_english: English + location: en/aspell5-en-6.0-0.tar.bz2 +en_CA: + name_english: English (Canadian) + location: en/aspell5-en-6.0-0.tar.bz2 +en_GB: + name_english: English (British) + location: en/aspell5-en-6.0-0.tar.bz2 +en_US: + name_english: English (American) + location: en/aspell5-en-6.0-0.tar.bz2 +be: + name_english: Belarusian + location: be/aspell5-be-0.01.tar.bz2 + #name_native: "\xD0\x91\xD0\xB5\xD0\xBB\xD0\xB0\xD1\x80\xD1\x83\xD1\x81\xD0\xBA\xD1\x96" +be_SU: + name_english: Belarusian (Soviet) + location: be/aspell5-be-0.01.tar.bz2 +tl: + name_english: Tagalog + location: tl/aspell5-tl-0.02-1.tar.bz2 + name_native: Tagalog +ru: + name_english: Russian + location: ru/aspell5-ru-0.99f7-0.tar.bz2 +sl: + name_english: Slovenian + location: sl/aspell-sl-0.50-0.tar.bz2 +el: + name_english: Greek + location: el/aspell-el-0.50-3.tar.bz2 +fo: + name_english: Faroese + location: fo/aspell5-fo-0.2.16-1.tar.bz2 + name_native: "F\xC3\xB8royskt" diff --git a/digsby/res/dictionaries-6.yaml b/digsby/res/dictionaries-6.yaml new file mode 100644 index 0000000..6e254be --- /dev/null +++ b/digsby/res/dictionaries-6.yaml @@ -0,0 +1,330 @@ +--- +de-alt: + name_english: German - Old Spelling + location: de-alt/aspell6-de-alt-2.1-1.tar.bz2 +lt: + name_english: Lithuanian + location: lt/aspell6-lt-1.2.1-0.tar.bz2 + name_native: "lietuvi\xC5\xB3" +af: + name_english: Afrikaans + location: af/aspell-af-0.50-0.tar.bz2 +cs: + name_english: Czech + location: cs/aspell6-cs-20040614-1.tar.bz2 + name_native: "\xC4\x8Ce\xC5\xA1tina" +hi: + name_english: Hindi + location: hi/aspell6-hi-0.02-0.tar.bz2 + name_native: "\xE0\xA4\xB9\xE0\xA4\xBF\xE0\xA4\x82\xE0\xA4\xA6\xE0\xA5\x80" +vi: + name_english: Vietnamese + location: vi/aspell6-vi-0.01.1-1.tar.bz2 + name_native: "Vi\xE1\xBB\x87t ng\xE1\xBB\xAF" +tk: + name_english: Turkmen + location: tk/aspell5-tk-0.01-0.tar.bz2 +hsb: + name_english: Upper Sorbian + location: hsb/aspell6-hsb-0.01-1.tar.bz2 + name_native: hornjoserbsce +ku: + name_english: Kurdi + location: ku/aspell5-ku-0.20-1.tar.bz2 + name_native: "Kurd\xC3\xAE" +ny: + name_english: Chichewa + location: ny/aspell5-ny-0.01-0.tar.bz2 +tn: + name_english: Setswana + location: tn/aspell5-tn-1.0.1-0.tar.bz2 +es: + name_english: Spanish + location: es/aspell-es-0.50-2.tar.bz2 + name_native: Espa +tet: + name_english: Tetum + location: tet/aspell5-tet-0.1.1.tar.bz2 +az: + name_english: Azerbaijani + location: az/aspell6-az-0.02-0.tar.bz2 + name_native: "Az\xC9\x99rbaycanca" +is: + name_english: Icelandic + location: is/aspell-is-0.51.1-0.tar.bz2 +hu: + name_english: Hungarian + location: hu/aspell6-hu-0.99.4.2-0.tar.bz2 +mt: + name_english: Maltese + location: mt/aspell-mt-0.50-0.tar.bz2 +mg: + name_english: Malagasy + location: mg/aspell5-mg-0.03-0.tar.bz2 +pt_PT: + name_english: Portuguese + location: pt_PT/aspell6-pt_PT-20070510-0.tar.bz2 + name_native: "portugu\xC3\xAAs" +ms: + name_english: Malay + location: ms/aspell-ms-0.50-0.tar.bz2 +bn: + name_english: Bengali + location: bn/aspell6-bn-0.01.1-1.tar.bz2 + name_native: "\xE0\xA6\xAC\xE0\xA6\xBE\xE0\xA6\x82\xE0\xA6\xB2\xE0\xA6\xBE" +yi: + name_english: Yiddish + location: yi/aspell6-yi-0.01.1-1.tar.bz2 + name_native: "\xD7\x99\xD7\x99\xD6\xB4\xD7\x93\xD7\x99\xD7\xA9" +bg: + name_english: Bulgarian + location: bg/aspell6-bg-4.1-0.tar.bz2 + name_native: "\xD0\x91\xD1\x8A\xD0\xBB\xD0\xB3\xD0\xB0\xD1\x80\xD1\x81\xD0\xBA\xD0\xB8" +fi: + name_english: Finnish + location: fi/aspell6-fi-0.7-0.tar.bz2 +fa: + name_english: Farsi (Persian) + location: fa/aspell6-fa-0.11-0.tar.bz2 + name_native: "\xD9\x81\xD8\xA7\xD8\xB1\xD8\xB3\xDB\x8C" +id: + name_english: Indonesian + location: id/aspell5-id-1.2-0.tar.bz2 + name_native: Bahasa Indonesia +sv: + name_english: Swedish + location: sv/aspell-sv-0.51-0.tar.bz2 +br: + name_english: Breton + location: br/aspell-br-0.50-2.tar.bz2 +he: + name_english: Hebrew + location: he/aspell6-he-1.0-0.tar.bz2 + name_native: "\xD7\xA2\xD7\x91\xD7\xA8\xD7\x99\xD7\xAA" +eo: + name_english: Esperanto + location: eo/aspell-eo-0.50-2.tar.bz2 +lv: + name_english: Latvian + location: lv/aspell6-lv-0.5.5-1.tar.bz2 +gl: + name_english: Galician + location: gl/aspell-gl-0.50-0.tar.bz2 + name_native: Galego +hr: + name_english: Croatian + location: hr/aspell-hr-0.51-0.tar.bz2 +ro: + name_english: Romanian + location: ro/aspell-ro-0.50-2.tar.bz2 +ia: + name_english: Interlingua + location: ia/aspell-ia-0.50-1.tar.bz2 +nb: + name_english: Norwegian Bokmal + location: nb/aspell-nb-0.50.1-0.tar.bz2 +ml: + name_english: Malayalam + location: ml/aspell6-ml-0.03-1.tar.bz2 + name_native: "\xE0\xB4\xAE\xE0\xB4\xB2\xE0\xB4\xAF\xE0\xB4\xBE\xE0\xB4\xB3\xE0\xB4\x82" +csb: + name_english: Kashubian + location: csb/aspell6-csb-0.02-0.tar.bz2 + name_native: "Kasz\xC3\xABbsczi" +pl: + name_english: Polish + location: pl/aspell6-pl-6.0_20061121-0.tar.bz2 + name_native: polski +ca: + name_english: Catalan + location: ca/aspell6-ca-20040130-1.tar.bz2 + name_native: "Catal\xC3\xA0" +en: + name_english: English + location: en/aspell6-en-7.1-0.tar.bz2 +en_US: + name_english: English (American) + location: en/aspell6-en-7.1-0.tar.bz2 +en_GB: + name_english: English (British) + location: en/aspell6-en-7.1-0.tar.bz2 +en_CA: + name_english: English (Canadian) + location: en/aspell6-en-7.1-0.tar.bz2 +el: + name_english: Greek + location: el/aspell-el-0.50-3.tar.bz2 +am: + name_english: Amharic + location: am/aspell6-am-0.03-1.tar.bz2 + name_native: "\xE1\x8A\xA0\xE1\x88\x9B\xE1\x88\xAD\xE1\x8A\x9B" +mn: + name_english: Mongolian + location: mn/aspell6-mn-0.01-0.tar.bz2 + name_native: "\xD0\x9C\xD0\xBE\xD0\xBD\xD0\xB3\xD0\xBE\xD0\xBB" +or: + name_english: Oriya + location: or/aspell6-or-0.03-1.tar.bz2 + name_native: "\xE0\xAC\x93\xE0\xAC\xA1\xE0\xAC\xBC\xE0\xAC\xBF\xE0\xAC\x86" +sc: + name_english: Sardinian + location: sc/aspell5-sc-1.0.tar.bz2 +cy: + name_english: Welsh + location: cy/aspell-cy-0.50-3.tar.bz2 +nn: + name_english: Norwegian Nynorsk + location: nn/aspell-nn-0.50.1-1.tar.bz2 +it: + name_english: Italian + location: it/aspell6-it-2.2_20050523-0.tar.bz2 + name_native: Italiano +gv: + name_english: Manx Gaelic + location: gv/aspell-gv-0.50-0.tar.bz2 +qu: + name_english: Quechua + location: qu/aspell6-qu-0.02-0.tar.bz2 + name_native: Runasimi (qheshwa) +fo: + name_english: Faroese + location: fo/aspell5-fo-0.2.16-1.tar.bz2 + name_native: "F\xC3\xB8royskt" +tr: + name_english: Turkish + location: tr/aspell-tr-0.50-0.tar.bz2 +da: + name_english: Danish + location: da/aspell5-da-1.4.42-1.tar.bz2 + name_native: Dansk +la: + name_english: Latin + location: la/aspell6-la-20020503-0.tar.bz2 +nl: + name_english: Dutch + location: nl/aspell-nl-0.50-2.tar.bz2 +ru: + name_english: Russian + location: ru/aspell6-ru-0.99f7-1.tar.bz2 + name_native: "\xD0\xA0\xD1\x83\xD1\x81\xD1\x81\xD0\xBA\xD0\xB8\xD0\xB9" +be: + name_english: Belarusian + location: be/aspell5-be-0.01.tar.bz2 + name_native: "\xD0\x91\xD0\xB5\xD0\xBB\xD0\xB0\xD1\x80\xD1\x83\xD1\x81\xD0\xBA\xD1\x96" +be_SU: + name_english: Belarusian (Soviet) + location: be/aspell5-be-0.01.tar.bz2 + name_native: "\xD0\x91\xD0\xB5\xD0\xBB\xD0\xB0\xD1\x80\xD1\x83\xD1\x81\xD0\xBA\xD1\x96" + +tl: + name_english: Tagalog + location: tl/aspell5-tl-0.02-1.tar.bz2 + name_native: Tagalog +sk: + name_english: Slovak + location: sk/aspell6-sk-2.00-0.tar.bz2 + name_native: "Slovensk\xC3\xBD" +gu: + name_english: Gujarati + location: gu/aspell6-gu-0.03-0.tar.bz2 + name_native: "\xE0\xAA\x97\xE0\xAB\x81\xE0\xAA\x9C\xE0\xAA\xB0\xE0\xAA\xBE\xE0\xAA\xA4\xE0\xAB\x80" +sl: + name_english: Slovenian + location: sl/aspell-sl-0.50-0.tar.bz2 +ga: + name_english: Irish + location: ga/aspell5-ga-4.3-0.tar.bz2 + name_native: Gaeilge +te: + name_english: Telugu + location: te/aspell6-te-0.01-2.tar.bz2 + name_native: "\xE0\xB0\xA4\xE0\xB1\x86\xE0\xB0\xB2\xE0\xB1\x81\xE0\xB0\x97\xE0\xB1\x81" +mr: + name_english: Marathi + location: mr/aspell6-mr-0.10-0.tar.bz2 + name_native: "\xE0\xA4\xAE\xE0\xA4\xB0\xE0\xA4\xBE\xE0\xA4\xA0\xE0\xA5\x80" +uk: + name_english: Ukrainian + location: uk/aspell6-uk-1.4.0-0.tar.bz2 + name_native: "\xD0\xA3\xD0\xBA\xD1\x80\xD0\xB0\xD1\x97\xD0\xBD\xD1\x81\xD1\x8C\xD0\xBA\xD0\xB0" +fr: + name_english: French + location: fr/aspell-fr-0.50-3.tar.bz2 + name_native: Fran +fr_CH: + name_english: Swiss (French) + location: fr/aspell-fr-0.50-3.tar.bz2 + name_native: Suisse +fy: + name_english: Frisian + location: fy/aspell6-fy-0.12-0.tar.bz2 + name_native: Frysk +mi: + name_english: Maori + location: mi/aspell-mi-0.50-0.tar.bz2 +zu: + name_english: Zulu + location: zu/aspell-zu-0.50-0.tar.bz2 +de: + name_english: German + location: de/aspell6-de-20030222-1.tar.bz2 + name_native: Deutsch +de_CH: + name_english: Swiss (German) + location: de/aspell6-de-20030222-1.tar.bz2 +sw: + name_english: Swahili + location: sw/aspell-sw-0.50-0.tar.bz2 + name_native: Kiswahili +pt_BR: + name_english: Brazilian Portuguese + location: pt_BR/aspell6-pt_BR-20080221-1.tar.bz2 + name_native: "Portugu\xC3\xAAs do Brasil" +gd: + name_english: Scottish Gaelic + location: gd/aspell5-gd-0.1.1-1.tar.bz2 + name_native: "G\xC3\xA0idhlig" +hy: + name_english: Armenian + location: hy/aspell6-hy-0.10.0-0.tar.bz2 + name_native: "\xD5\x80\xD5\xA1\xD5\xB5\xD5\xA5\xD6\x80\xD5\xA5\xD5\xB6" +rw: + name_english: Kinyarwanda + location: rw/aspell-rw-0.50-0.tar.bz2 +wa: + name_english: Walloon + location: wa/aspell-wa-0.50-0.tar.bz2 + name_native: walon +ar: + name_english: Arabic + location: ar/aspell6-ar-1.2-0.tar.bz2 + name_native: "\xD8\xB9\xD8\xB1\xD8\xA8\xD9\x8A" +uz: + name_english: Uzbek + location: uz/aspell6-uz-0.6-0.tar.bz2 + name_native: "\xD0\x8E\xD0\xB7\xD0\xB1\xD0\xB5\xD0\xBA\xD1\x87\xD0\xB0" +hil: + name_english: Hiligaynon + location: hil/aspell5-hil-0.11-0.tar.bz2 +ta: + name_english: Tamil + location: ta/aspell6-ta-20040424-1.tar.bz2 + name_native: "\xE0\xAE\xA4\xE0\xAE\xAE\xE0\xAE\xBF\xE0\xAE\xB4\xE0\xAF\x8D" +nds: + name_english: Low Saxon + location: nds/aspell6-nds-0.01-0.tar.bz2 + name_native: "Plattd\xC3\xBC\xC3\xBCtsch" +et: + name_english: Estonian + location: et/aspell6-et-0.1.21-1.tar.bz2 +pa: + name_english: Punjabi + location: pa/aspell6-pa-0.01-1.tar.bz2 + name_native: "\xE0\xA8\xAA\xE0\xA9\xB0\xE0\xA8\x9C\xE0\xA8\xBE\xE0\xA8\xAC\xE0\xA9\x80" +mk: + name_english: Macedonian + location: mk/aspell-mk-0.50-0.tar.bz2 +sr: + name_english: Serbian + location: sr/aspell6-sr-0.02.tar.bz2 + name_native: "\xD0\xA1\xD1\x80\xD0\xBF\xD1\x81\xD0\xBA\xD0\xB8" diff --git a/digsby/res/dictionaries-en-5.yaml b/digsby/res/dictionaries-en-5.yaml new file mode 100644 index 0000000..e400357 --- /dev/null +++ b/digsby/res/dictionaries-en-5.yaml @@ -0,0 +1,13 @@ +--- +en: + name_english: English + location: en/aspell5-en-6.0-0.tar.bz2 +en_CA: + name_english: English (Canadian) + location: en/aspell5-en-6.0-0.tar.bz2 +en_GB: + name_english: English (British) + location: en/aspell5-en-6.0-0.tar.bz2 +en_US: + name_english: English (American) + location: en/aspell5-en-6.0-0.tar.bz2 \ No newline at end of file diff --git a/digsby/res/digsby.icns b/digsby/res/digsby.icns new file mode 100644 index 0000000..790520d Binary files /dev/null and b/digsby/res/digsby.icns differ diff --git a/digsby/res/digsby.ico b/digsby/res/digsby.ico new file mode 100644 index 0000000..63b2b4f Binary files /dev/null and b/digsby/res/digsby.ico differ diff --git a/digsby/res/digsby_stpatricks.png b/digsby/res/digsby_stpatricks.png new file mode 100644 index 0000000..e74211f Binary files /dev/null and b/digsby/res/digsby_stpatricks.png differ diff --git a/digsby/res/digsbybig.bmp b/digsby/res/digsbybig.bmp new file mode 100644 index 0000000..6377849 Binary files /dev/null and b/digsby/res/digsbybig.bmp differ diff --git a/digsby/res/digsbybig.png b/digsby/res/digsbybig.png new file mode 100644 index 0000000..df53eeb Binary files /dev/null and b/digsby/res/digsbybig.png differ diff --git a/digsby/res/digsbyclaus.png b/digsby/res/digsbyclaus.png new file mode 100644 index 0000000..977b1c9 Binary files /dev/null and b/digsby/res/digsbyclaus.png differ diff --git a/digsby/res/digsbysmall.ico b/digsby/res/digsbysmall.ico new file mode 100644 index 0000000..27802f5 Binary files /dev/null and b/digsby/res/digsbysmall.ico differ diff --git a/digsby/res/emoticons/MSN Messenger/ASL.png b/digsby/res/emoticons/MSN Messenger/ASL.png new file mode 100644 index 0000000..71f7f1c Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/ASL.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Airplane.png b/digsby/res/emoticons/MSN Messenger/Airplane.png new file mode 100644 index 0000000..8ce9372 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Airplane.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Angel.png b/digsby/res/emoticons/MSN Messenger/Angel.png new file mode 100644 index 0000000..de83137 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Angel.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Angry.png b/digsby/res/emoticons/MSN Messenger/Angry.png new file mode 100644 index 0000000..a48de89 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Angry.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Auto.png b/digsby/res/emoticons/MSN Messenger/Auto.png new file mode 100644 index 0000000..0eba405 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Auto.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Baring Teeth.png b/digsby/res/emoticons/MSN Messenger/Baring Teeth.png new file mode 100644 index 0000000..ab863e0 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Baring Teeth.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Bat.png b/digsby/res/emoticons/MSN Messenger/Bat.png new file mode 100644 index 0000000..62b30f5 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Bat.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Be right back.png b/digsby/res/emoticons/MSN Messenger/Be right back.png new file mode 100644 index 0000000..1d2909e Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Be right back.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Beer mug.png b/digsby/res/emoticons/MSN Messenger/Beer mug.png new file mode 100644 index 0000000..4b9297a Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Beer mug.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Birthday cake.gif b/digsby/res/emoticons/MSN Messenger/Birthday cake.gif new file mode 100644 index 0000000..99282fa Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Birthday cake.gif differ diff --git a/digsby/res/emoticons/MSN Messenger/Bowl.png b/digsby/res/emoticons/MSN Messenger/Bowl.png new file mode 100644 index 0000000..af1cb7c Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Bowl.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Boy.png b/digsby/res/emoticons/MSN Messenger/Boy.png new file mode 100644 index 0000000..fa2fc91 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Boy.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Camera.png b/digsby/res/emoticons/MSN Messenger/Camera.png new file mode 100644 index 0000000..46f8632 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Camera.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Cat Face.png b/digsby/res/emoticons/MSN Messenger/Cat Face.png new file mode 100644 index 0000000..5cfae0b Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Cat Face.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Cigar.png b/digsby/res/emoticons/MSN Messenger/Cigar.png new file mode 100644 index 0000000..6c1cb94 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Cigar.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Clap.png b/digsby/res/emoticons/MSN Messenger/Clap.png new file mode 100644 index 0000000..602606d Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Clap.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Clock.png b/digsby/res/emoticons/MSN Messenger/Clock.png new file mode 100644 index 0000000..46b2da1 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Clock.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Coffee up.png b/digsby/res/emoticons/MSN Messenger/Coffee up.png new file mode 100644 index 0000000..a34755d Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Coffee up.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Computer.png b/digsby/res/emoticons/MSN Messenger/Computer.png new file mode 100644 index 0000000..7add9dc Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Computer.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Confused.png b/digsby/res/emoticons/MSN Messenger/Confused.png new file mode 100644 index 0000000..6f7ace3 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Confused.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Cross Fingers.png b/digsby/res/emoticons/MSN Messenger/Cross Fingers.png new file mode 100644 index 0000000..e225b39 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Cross Fingers.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Devil.png b/digsby/res/emoticons/MSN Messenger/Devil.png new file mode 100644 index 0000000..f413da0 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Devil.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Disappointed.png b/digsby/res/emoticons/MSN Messenger/Disappointed.png new file mode 100644 index 0000000..14aa121 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Disappointed.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Dog Face.png b/digsby/res/emoticons/MSN Messenger/Dog Face.png new file mode 100644 index 0000000..f8cb58e Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Dog Face.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Don't Tell Anyone.png b/digsby/res/emoticons/MSN Messenger/Don't Tell Anyone.png new file mode 100644 index 0000000..75f0c7a Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Don't Tell Anyone.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Email.png b/digsby/res/emoticons/MSN Messenger/Email.png new file mode 100644 index 0000000..dd6d3d6 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Email.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Emoticons.plist b/digsby/res/emoticons/MSN Messenger/Emoticons.plist new file mode 100644 index 0000000..ce6e827 --- /dev/null +++ b/digsby/res/emoticons/MSN Messenger/Emoticons.plist @@ -0,0 +1,761 @@ + + + + + AdiumSetVersion + 1 + Service Class + MSN + Emoticons + + ASL.png + + Equivalents + + (?) + + Name + ASL + + Airplane.png + + Equivalents + + (ap) + + Name + Airplane + + Angel.png + + Equivalents + + (A) + (a) + + Name + Angel + + Angry.png + + Equivalents + + :-@ + :@ + + Name + Angry + + Auto.png + + Equivalents + + (au) + + Name + Auto + + Baring Teeth.png + + Equivalents + + 8o| + + Name + Baring teeth + + Be right back.png + + Equivalents + + (brb) + + Name + Be Right Back + + Beer.png + + Equivalents + + (B) + (b) + + Name + Beer Mug + + Birthday cake.gif + + Equivalents + + (^) + + Name + Birthday cake + + Sheep.png + + Equivalents + + (bah) + + Name + Sheep + + Bowl.png + + Equivalents + + (||) + + Name + Bowl + + Boy.png + + Equivalents + + (Z) + (z) + + Name + Boy + + broken heart.png + + Equivalents + + (U) + (u) + + Name + Broken heart + + Camera.png + + Equivalents + + (P) + (p) + + Name + Camera + + Cat Face.png + + Equivalents + + (@) + + Name + Cat + + Cigar.png + + Equivalents + + (ci) + + Name + Cigar + + Clap.png + + Equivalents + + (h5) + + Name + Clap + + Clock.png + + Equivalents + + (O) + (o) + + Name + Clock + + Coffee up.png + + Equivalents + + (C) + (c) + + Name + Coffee up + + Computer.png + + Equivalents + + (co) + + Name + Computer + + Confused.png + + Equivalents + + :-S + :-s + :S + :s + :/ + :\ + :-/ + :-\ + + Name + Confused + + Cross Fingers.png + + Equivalents + + (yn) + + Name + Cross Fingers + + crying.gif + + Equivalents + + :'( + + Name + Crying + + Devil.png + + Equivalents + + (6) + + Name + Devil + + Disappointed.png + + Equivalents + + :-| + :| + + Name + Disappointed + + Dog Face.png + + Equivalents + + (&) + + Name + Dog + + Don't Tell Anyone.png + + Equivalents + + :-# + + Name + Don't tell anyone + + Email.png + + Equivalents + + (E) + (e) + + Name + E-mail + + embarrassed.png + + Equivalents + + :-$ + :$ + + Name + Embarrassed + + Eye-Rolling.gif + + Equivalents + + 8-) + + Name + Eye-rolling + + Filmstrip.png + + Equivalents + + (~) + + Name + Filmstrip + + Foot In Mouth.png + + Equivalents + + :-! + :! + + Name + Foot in mouth + + Gift.png + + Equivalents + + (G) + (g) + + Name + Gift + + Girl.png + + Equivalents + + (X) + (x) + + Name + Girl + + Hot.png + + Equivalents + + (H) + (h) + + Name + Hot + + I Don't Know.png + + Equivalents + + :^) + + Name + I don't know + + Island.png + + Equivalents + + (ip) + + Name + Island + + Left Hug.png + + Equivalents + + ({) + + Name + Left hug + + Light Bulb.png + + Equivalents + + (I) + (i) + + Name + Light bulb + + Lightning.gif + + Equivalents + + (li) + + Name + Lightning + + MSN.png + + Equivalents + + (M) + (m) + + Name + MSN Messenger icon + + Martini.png + + Equivalents + + (D) + (d) + + Name + Martini + + Mobile.png + + Equivalents + + (mp) + + Name + Mobile Phone + + Money.png + + Equivalents + + (mo) + + Name + Money + + Nerd.png + + Equivalents + + 8-| + + Name + Nerd + + Note.png + + Equivalents + + (8) + + Name + Note + + Grin.png + + Equivalents + + :-D + :-d + :D + :d + + Name + Grin + + Party.png + + Equivalents + + <:o) + + Name + Party + + Pizza.png + + Equivalents + + (pi) + + Name + Pizza + + Plate.png + + Equivalents + + (pl) + + Name + Plate + + Rainbow.png + + Equivalents + + (r) + (R) + + Name + Rainbow + + heart.png + + Equivalents + + (L) + (l) + + Name + Heart + + Red lips.png + + Equivalents + + (K) + (k) + + Name + Red lips + + Rose.png + + Equivalents + + (F) + (f) + + Name + Rose + + Right Hug.png + + Equivalents + + (}) + + Name + Right hug + + Sad.png + + Equivalents + + :-( + :( + + Name + Sad + + Sarcastic.png + + Equivalents + + ^o) + + Name + Sarcastic + + Secret telling.png + + Equivalents + + :-* + + Name + Secret telling + + Sick.png + + Equivalents + + +o( + + Name + Sick + + Sleeping Moon.png + + Equivalents + + (S) + + Name + Sleeping half-moon + + Smile.png + + Equivalents + + :-) + :) + + Name + Happy + + Snail.png + + Equivalents + + (sn) + + Name + Snail + + Soccer.png + + Equivalents + + (so) + + Name + Soccer ball + + Star.png + + Equivalents + + (*) + + Name + Star + + Stormy cloud.png + + Equivalents + + (st) + + Name + Stormy cloud + + Sun.png + + Equivalents + + (#) + + Name + Sun + + Surprised.png + + Equivalents + + :-O + :-o + :O + :o + + Name + Surprised + + Telephone.png + + Equivalents + + (T) + (t) + + Name + Telephone receiver + + Thinking.png + + Equivalents + + *-) + + Name + Thinking + + Thumbs down.png + + Equivalents + + (N) + (n) + + Name + Thumbs down + + Thumbs up.png + + Equivalents + + (Y) + (y) + + Name + Thumbs up + + Sticking Out Tongue.png + + Equivalents + + :-P + :-p + :P + :p + + Name + Raspberry + + Tortoise.png + + Equivalents + + (tu) + + Name + Turtle + + Umbrella.png + + Equivalents + + (um) + + Name + Umbrella + + Bat.png + + Equivalents + + :-[ + :[ + + Name + Vampire bat + + Wilted Rose.png + + Equivalents + + (W) + (w) + + Name + Wilted rose + + Wink.png + + Equivalents + + ;-) + ;) + + Name + Wink + + XBox.png + + Equivalents + + (xx) + + Name + XBox + + + + diff --git a/digsby/res/emoticons/MSN Messenger/Eye-Rolling.gif b/digsby/res/emoticons/MSN Messenger/Eye-Rolling.gif new file mode 100644 index 0000000..61b9ee0 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Eye-Rolling.gif differ diff --git a/digsby/res/emoticons/MSN Messenger/Filmstrip.png b/digsby/res/emoticons/MSN Messenger/Filmstrip.png new file mode 100644 index 0000000..6bf48a3 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Filmstrip.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Foot In Mouth.png b/digsby/res/emoticons/MSN Messenger/Foot In Mouth.png new file mode 100644 index 0000000..073298b Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Foot In Mouth.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Gift.png b/digsby/res/emoticons/MSN Messenger/Gift.png new file mode 100644 index 0000000..52c8ba3 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Gift.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Girl.png b/digsby/res/emoticons/MSN Messenger/Girl.png new file mode 100644 index 0000000..3afea94 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Girl.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Grin.png b/digsby/res/emoticons/MSN Messenger/Grin.png new file mode 100644 index 0000000..2ab3e16 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Grin.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Hot.png b/digsby/res/emoticons/MSN Messenger/Hot.png new file mode 100644 index 0000000..701eb9a Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Hot.png differ diff --git a/digsby/res/emoticons/MSN Messenger/I Don't Know.png b/digsby/res/emoticons/MSN Messenger/I Don't Know.png new file mode 100644 index 0000000..0ef921b Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/I Don't Know.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Island.png b/digsby/res/emoticons/MSN Messenger/Island.png new file mode 100644 index 0000000..2545f4a Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Island.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Kiss.png b/digsby/res/emoticons/MSN Messenger/Kiss.png new file mode 100644 index 0000000..302af14 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Kiss.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Left Hug.png b/digsby/res/emoticons/MSN Messenger/Left Hug.png new file mode 100644 index 0000000..7b48192 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Left Hug.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Light Bulb.png b/digsby/res/emoticons/MSN Messenger/Light Bulb.png new file mode 100644 index 0000000..c7db63b Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Light Bulb.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Lightning.gif b/digsby/res/emoticons/MSN Messenger/Lightning.gif new file mode 100644 index 0000000..4f69f0f Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Lightning.gif differ diff --git a/digsby/res/emoticons/MSN Messenger/MSN.png b/digsby/res/emoticons/MSN Messenger/MSN.png new file mode 100644 index 0000000..8e0cd56 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/MSN.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Martini.png b/digsby/res/emoticons/MSN Messenger/Martini.png new file mode 100644 index 0000000..24267cd Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Martini.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Mobile.png b/digsby/res/emoticons/MSN Messenger/Mobile.png new file mode 100644 index 0000000..9184db8 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Mobile.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Money.png b/digsby/res/emoticons/MSN Messenger/Money.png new file mode 100644 index 0000000..ea17f4b Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Money.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Nerd.png b/digsby/res/emoticons/MSN Messenger/Nerd.png new file mode 100644 index 0000000..c1668e8 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Nerd.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Note.png b/digsby/res/emoticons/MSN Messenger/Note.png new file mode 100644 index 0000000..842a379 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Note.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Party.png b/digsby/res/emoticons/MSN Messenger/Party.png new file mode 100644 index 0000000..32fc355 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Party.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Pizza.png b/digsby/res/emoticons/MSN Messenger/Pizza.png new file mode 100644 index 0000000..f75874b Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Pizza.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Plate.png b/digsby/res/emoticons/MSN Messenger/Plate.png new file mode 100644 index 0000000..60a2e0f Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Plate.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Rainbow.png b/digsby/res/emoticons/MSN Messenger/Rainbow.png new file mode 100644 index 0000000..c4ed3b9 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Rainbow.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Red lips.png b/digsby/res/emoticons/MSN Messenger/Red lips.png new file mode 100644 index 0000000..16f8080 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Red lips.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Right Hug.png b/digsby/res/emoticons/MSN Messenger/Right Hug.png new file mode 100644 index 0000000..82cd0cf Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Right Hug.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Rose.png b/digsby/res/emoticons/MSN Messenger/Rose.png new file mode 100644 index 0000000..957e212 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Rose.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Sad.png b/digsby/res/emoticons/MSN Messenger/Sad.png new file mode 100644 index 0000000..696ec43 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Sad.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Sarcastic.png b/digsby/res/emoticons/MSN Messenger/Sarcastic.png new file mode 100644 index 0000000..91c915e Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Sarcastic.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Secret telling.png b/digsby/res/emoticons/MSN Messenger/Secret telling.png new file mode 100644 index 0000000..b227bca Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Secret telling.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Sheep.png b/digsby/res/emoticons/MSN Messenger/Sheep.png new file mode 100644 index 0000000..4de8846 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Sheep.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Sick.png b/digsby/res/emoticons/MSN Messenger/Sick.png new file mode 100644 index 0000000..606931e Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Sick.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Sleeping Moon.png b/digsby/res/emoticons/MSN Messenger/Sleeping Moon.png new file mode 100644 index 0000000..049d0d0 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Sleeping Moon.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Smile.png b/digsby/res/emoticons/MSN Messenger/Smile.png new file mode 100644 index 0000000..fe4ee59 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Smile.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Snail.png b/digsby/res/emoticons/MSN Messenger/Snail.png new file mode 100644 index 0000000..a2cf135 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Snail.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Soccer.png b/digsby/res/emoticons/MSN Messenger/Soccer.png new file mode 100644 index 0000000..ee04aab Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Soccer.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Star.png b/digsby/res/emoticons/MSN Messenger/Star.png new file mode 100644 index 0000000..a31d28a Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Star.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Sticking Out Tongue.png b/digsby/res/emoticons/MSN Messenger/Sticking Out Tongue.png new file mode 100644 index 0000000..36580ac Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Sticking Out Tongue.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Stormy cloud.png b/digsby/res/emoticons/MSN Messenger/Stormy cloud.png new file mode 100644 index 0000000..12f9128 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Stormy cloud.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Sun.png b/digsby/res/emoticons/MSN Messenger/Sun.png new file mode 100644 index 0000000..c316484 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Sun.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Surprised.png b/digsby/res/emoticons/MSN Messenger/Surprised.png new file mode 100644 index 0000000..6b327a5 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Surprised.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Telephone.png b/digsby/res/emoticons/MSN Messenger/Telephone.png new file mode 100644 index 0000000..f4f77ec Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Telephone.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Thinking.png b/digsby/res/emoticons/MSN Messenger/Thinking.png new file mode 100644 index 0000000..1df1883 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Thinking.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Thumbs Down.png b/digsby/res/emoticons/MSN Messenger/Thumbs Down.png new file mode 100644 index 0000000..fdcb7dd Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Thumbs Down.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Thumbs Up.png b/digsby/res/emoticons/MSN Messenger/Thumbs Up.png new file mode 100644 index 0000000..efd1f40 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Thumbs Up.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Tortoise.png b/digsby/res/emoticons/MSN Messenger/Tortoise.png new file mode 100644 index 0000000..1bb922d Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Tortoise.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Umbrella.png b/digsby/res/emoticons/MSN Messenger/Umbrella.png new file mode 100644 index 0000000..da1f1e4 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Umbrella.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Wilted Rose.png b/digsby/res/emoticons/MSN Messenger/Wilted Rose.png new file mode 100644 index 0000000..e8a71d7 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Wilted Rose.png differ diff --git a/digsby/res/emoticons/MSN Messenger/Wink.png b/digsby/res/emoticons/MSN Messenger/Wink.png new file mode 100644 index 0000000..6b91d1e Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/Wink.png differ diff --git a/digsby/res/emoticons/MSN Messenger/XBox.png b/digsby/res/emoticons/MSN Messenger/XBox.png new file mode 100644 index 0000000..74fbb17 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/XBox.png differ diff --git a/digsby/res/emoticons/MSN Messenger/beer.png b/digsby/res/emoticons/MSN Messenger/beer.png new file mode 100644 index 0000000..59f980d Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/beer.png differ diff --git a/digsby/res/emoticons/MSN Messenger/broken heart.png b/digsby/res/emoticons/MSN Messenger/broken heart.png new file mode 100644 index 0000000..d6ddfbf Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/broken heart.png differ diff --git a/digsby/res/emoticons/MSN Messenger/crying.gif b/digsby/res/emoticons/MSN Messenger/crying.gif new file mode 100644 index 0000000..f7ca349 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/crying.gif differ diff --git a/digsby/res/emoticons/MSN Messenger/embarrassed.png b/digsby/res/emoticons/MSN Messenger/embarrassed.png new file mode 100644 index 0000000..ac6c5d2 Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/embarrassed.png differ diff --git a/digsby/res/emoticons/MSN Messenger/heart.png b/digsby/res/emoticons/MSN Messenger/heart.png new file mode 100644 index 0000000..11618cd Binary files /dev/null and b/digsby/res/emoticons/MSN Messenger/heart.png differ diff --git a/digsby/res/emoticons/Riceballs/Angel.png b/digsby/res/emoticons/Riceballs/Angel.png new file mode 100644 index 0000000..61dc1b1 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Angel.png differ diff --git a/digsby/res/emoticons/Riceballs/Angry Face.png b/digsby/res/emoticons/Riceballs/Angry Face.png new file mode 100644 index 0000000..c2e2994 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Angry Face.png differ diff --git a/digsby/res/emoticons/Riceballs/Blush.png b/digsby/res/emoticons/Riceballs/Blush.png new file mode 100644 index 0000000..00e1413 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Blush.png differ diff --git a/digsby/res/emoticons/Riceballs/Confused.png b/digsby/res/emoticons/Riceballs/Confused.png new file mode 100644 index 0000000..5dcbc58 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Confused.png differ diff --git a/digsby/res/emoticons/Riceballs/Crying.png b/digsby/res/emoticons/Riceballs/Crying.png new file mode 100644 index 0000000..63cdf40 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Crying.png differ diff --git a/digsby/res/emoticons/Riceballs/Emoticons.plist b/digsby/res/emoticons/Riceballs/Emoticons.plist new file mode 100644 index 0000000..d56c1b6 --- /dev/null +++ b/digsby/res/emoticons/Riceballs/Emoticons.plist @@ -0,0 +1,323 @@ + + + + + AdiumSetVersion + 1 + Emoticons + + Angry Face.png + + Equivalents + + >:o + (¬_¬") + + Name + Angry Face + + VeryAngry.png + + Equivalents + + >:-O + :@ + :-@ + (ò_ó) + + Name + Angry Scary + + + Angel.png + + Equivalents + + "(^_^)" + + Name + Wings + + + Blush.png + + Equivalents + + :-[ + :[ + (n///n) + + Name + Blush + + Confused.png + + Equivalents + + :S + :-S + :s + :-s + (.O.') + + Name + Confused + + Crying.png + + Equivalents + + :'( + (T_T) + + Name + Crying + + Foot In Mouth.png + + Equivalents + + :! + :| + :-! + :-| + + Name + Worried + + Frown.png + + Equivalents + + :-( + :( + (._.) + (u_u) + + Name + Frown + + Gasp.png + + Equivalents + + :o + =-o + + Name + Gasp + + LargeGasp.png + + Equivalents + + :O + =O + =-O + :-O + (OwO) + (O_O) + + Name + Gasp Big + + Grin.png + + Equivalents + + :-D + :D + (^O^) + + Name + Grin + + Halo.png + + Equivalents + + O:-) + O:) + o:-) + o:) + (*_*) + (A) + + Name + Halo + + Heart.png + + Equivalents + + (L) + <3 + (♥_♥) + + Name + Heart + + Hot.png + + Equivalents + + 8-) + 8) + (H) + + Name + Hot + + Kiss.png + + Equivalents + + :-* + :* + (K) + )^o^( + + Name + Kiss + + Lips are Sealed.png + + Equivalents + + :X + :-x + :-X + :x + + + Name + Lips are Sealed + + Money-mouth.png + + Equivalents + + :$ + $) + :-$ + $-) + ($_$) + + Name + Money-mouth + + Sarcastic.png + + Equivalents + + (sarc) + </sarcasm> + ^) + (o_O) + + Name + Sarcastic + + ohnoes.png + + Equivalents + + D: + + Name + Oh Noes! + + + Sick.png + + Equivalents + + :[[ + (sick) + (-__-) + + Name + Sick + + Smile.png + + Equivalents + + :-) + :) + (^_^) + ^_^ + + Name + Smile + + Sticking Out Tongue.png + + Equivalents + + :P + :-p + :-P + :p + + Name + Sticking Out Tongue + + Pirate.png + + Equivalents + + P-[ + + Name + Pirate + + Undecided.png + + Equivalents + + :\ + :/ + :-\ + :-/ + (=_=) + + Name + Undecided + + Thumbs Down.png + + Equivalents + + (N) + (n) + (-_-)p + + Name + Thumbs Down + + Thumbs Up.png + + Equivalents + + (Y) + (y) + (^_^)d + + Name + Thumbs Up + + Wink.png + + Equivalents + + ;-) + ;) + (^_-) + + Name + Wink + + + + diff --git a/digsby/res/emoticons/Riceballs/Foot In Mouth.png b/digsby/res/emoticons/Riceballs/Foot In Mouth.png new file mode 100644 index 0000000..2b75af5 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Foot In Mouth.png differ diff --git a/digsby/res/emoticons/Riceballs/Frown.png b/digsby/res/emoticons/Riceballs/Frown.png new file mode 100644 index 0000000..1e61038 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Frown.png differ diff --git a/digsby/res/emoticons/Riceballs/Gasp.png b/digsby/res/emoticons/Riceballs/Gasp.png new file mode 100644 index 0000000..9178848 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Gasp.png differ diff --git a/digsby/res/emoticons/Riceballs/Grin.png b/digsby/res/emoticons/Riceballs/Grin.png new file mode 100644 index 0000000..166e460 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Grin.png differ diff --git a/digsby/res/emoticons/Riceballs/Halo.png b/digsby/res/emoticons/Riceballs/Halo.png new file mode 100644 index 0000000..fb3171c Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Halo.png differ diff --git a/digsby/res/emoticons/Riceballs/Heart.png b/digsby/res/emoticons/Riceballs/Heart.png new file mode 100644 index 0000000..92a92f3 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Heart.png differ diff --git a/digsby/res/emoticons/Riceballs/Hot.png b/digsby/res/emoticons/Riceballs/Hot.png new file mode 100644 index 0000000..c77eebf Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Hot.png differ diff --git a/digsby/res/emoticons/Riceballs/Kiss.png b/digsby/res/emoticons/Riceballs/Kiss.png new file mode 100644 index 0000000..f4c8b09 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Kiss.png differ diff --git a/digsby/res/emoticons/Riceballs/LargeGasp.png b/digsby/res/emoticons/Riceballs/LargeGasp.png new file mode 100644 index 0000000..675722b Binary files /dev/null and b/digsby/res/emoticons/Riceballs/LargeGasp.png differ diff --git a/digsby/res/emoticons/Riceballs/License.txt b/digsby/res/emoticons/Riceballs/License.txt new file mode 100644 index 0000000..50d910a --- /dev/null +++ b/digsby/res/emoticons/Riceballs/License.txt @@ -0,0 +1,5 @@ +Riceballs Emoticons + +Copyright (c) 2007-2009 David Lanham (http://www.dlanham.com) + +Used with permission from copyright owner. \ No newline at end of file diff --git a/digsby/res/emoticons/Riceballs/Lips are Sealed.png b/digsby/res/emoticons/Riceballs/Lips are Sealed.png new file mode 100644 index 0000000..64f6ca1 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Lips are Sealed.png differ diff --git a/digsby/res/emoticons/Riceballs/Money-mouth.png b/digsby/res/emoticons/Riceballs/Money-mouth.png new file mode 100644 index 0000000..0fa0c7c Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Money-mouth.png differ diff --git a/digsby/res/emoticons/Riceballs/Pirate.png b/digsby/res/emoticons/Riceballs/Pirate.png new file mode 100644 index 0000000..6528737 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Pirate.png differ diff --git a/digsby/res/emoticons/Riceballs/Sarcastic.png b/digsby/res/emoticons/Riceballs/Sarcastic.png new file mode 100644 index 0000000..cd4d686 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Sarcastic.png differ diff --git a/digsby/res/emoticons/Riceballs/Sick.png b/digsby/res/emoticons/Riceballs/Sick.png new file mode 100644 index 0000000..2bc6266 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Sick.png differ diff --git a/digsby/res/emoticons/Riceballs/Smile.png b/digsby/res/emoticons/Riceballs/Smile.png new file mode 100644 index 0000000..3f1b5c3 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Smile.png differ diff --git a/digsby/res/emoticons/Riceballs/Sticking Out Tongue.png b/digsby/res/emoticons/Riceballs/Sticking Out Tongue.png new file mode 100644 index 0000000..3b69fda Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Sticking Out Tongue.png differ diff --git a/digsby/res/emoticons/Riceballs/Thumbs Down.png b/digsby/res/emoticons/Riceballs/Thumbs Down.png new file mode 100644 index 0000000..14af5ec Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Thumbs Down.png differ diff --git a/digsby/res/emoticons/Riceballs/Thumbs Up.png b/digsby/res/emoticons/Riceballs/Thumbs Up.png new file mode 100644 index 0000000..11abf45 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Thumbs Up.png differ diff --git a/digsby/res/emoticons/Riceballs/Undecided.png b/digsby/res/emoticons/Riceballs/Undecided.png new file mode 100644 index 0000000..83b4710 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Undecided.png differ diff --git a/digsby/res/emoticons/Riceballs/VeryAngry.png b/digsby/res/emoticons/Riceballs/VeryAngry.png new file mode 100644 index 0000000..0501758 Binary files /dev/null and b/digsby/res/emoticons/Riceballs/VeryAngry.png differ diff --git a/digsby/res/emoticons/Riceballs/Wink.png b/digsby/res/emoticons/Riceballs/Wink.png new file mode 100644 index 0000000..bd0301c Binary files /dev/null and b/digsby/res/emoticons/Riceballs/Wink.png differ diff --git a/digsby/res/emoticons/Riceballs/ohnoes.png b/digsby/res/emoticons/Riceballs/ohnoes.png new file mode 100644 index 0000000..394052a Binary files /dev/null and b/digsby/res/emoticons/Riceballs/ohnoes.png differ diff --git a/digsby/res/emoticons/Yahoo Messenger/1.gif b/digsby/res/emoticons/Yahoo Messenger/1.gif new file mode 100644 index 0000000..9ef4ca0 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/1.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/10.gif b/digsby/res/emoticons/Yahoo Messenger/10.gif new file mode 100644 index 0000000..294c677 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/10.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/100.gif b/digsby/res/emoticons/Yahoo Messenger/100.gif new file mode 100644 index 0000000..d64d17e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/100.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/101.gif b/digsby/res/emoticons/Yahoo Messenger/101.gif new file mode 100644 index 0000000..728ad77 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/101.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/102.gif b/digsby/res/emoticons/Yahoo Messenger/102.gif new file mode 100644 index 0000000..9c12abb Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/102.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/103.gif b/digsby/res/emoticons/Yahoo Messenger/103.gif new file mode 100644 index 0000000..24fa37f Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/103.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/104.gif b/digsby/res/emoticons/Yahoo Messenger/104.gif new file mode 100644 index 0000000..373d348 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/104.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/105.gif b/digsby/res/emoticons/Yahoo Messenger/105.gif new file mode 100644 index 0000000..b15cfad Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/105.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/106.gif b/digsby/res/emoticons/Yahoo Messenger/106.gif new file mode 100644 index 0000000..ac32ba3 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/106.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/107.gif b/digsby/res/emoticons/Yahoo Messenger/107.gif new file mode 100644 index 0000000..3f33463 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/107.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/108.gif b/digsby/res/emoticons/Yahoo Messenger/108.gif new file mode 100644 index 0000000..c4dd23e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/108.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/109.gif b/digsby/res/emoticons/Yahoo Messenger/109.gif new file mode 100644 index 0000000..76ba184 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/109.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/11.gif b/digsby/res/emoticons/Yahoo Messenger/11.gif new file mode 100644 index 0000000..e61889e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/11.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/110.gif b/digsby/res/emoticons/Yahoo Messenger/110.gif new file mode 100644 index 0000000..f6f66ae Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/110.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/111.gif b/digsby/res/emoticons/Yahoo Messenger/111.gif new file mode 100644 index 0000000..60283ec Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/111.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/112.gif b/digsby/res/emoticons/Yahoo Messenger/112.gif new file mode 100644 index 0000000..a91c178 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/112.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/113.gif b/digsby/res/emoticons/Yahoo Messenger/113.gif new file mode 100644 index 0000000..21c7ef5 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/113.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/114.gif b/digsby/res/emoticons/Yahoo Messenger/114.gif new file mode 100644 index 0000000..d29ce81 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/114.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/115.gif b/digsby/res/emoticons/Yahoo Messenger/115.gif new file mode 100644 index 0000000..fab7a9e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/115.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/12.gif b/digsby/res/emoticons/Yahoo Messenger/12.gif new file mode 100644 index 0000000..0a9ddcc Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/12.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/13.gif b/digsby/res/emoticons/Yahoo Messenger/13.gif new file mode 100644 index 0000000..0c79ba4 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/13.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/14.gif b/digsby/res/emoticons/Yahoo Messenger/14.gif new file mode 100644 index 0000000..1170edd Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/14.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/15.gif b/digsby/res/emoticons/Yahoo Messenger/15.gif new file mode 100644 index 0000000..f1ba3ba Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/15.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/16.gif b/digsby/res/emoticons/Yahoo Messenger/16.gif new file mode 100644 index 0000000..9d7b618 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/16.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/17.gif b/digsby/res/emoticons/Yahoo Messenger/17.gif new file mode 100644 index 0000000..492b276 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/17.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/18.gif b/digsby/res/emoticons/Yahoo Messenger/18.gif new file mode 100644 index 0000000..722af09 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/18.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/19.gif b/digsby/res/emoticons/Yahoo Messenger/19.gif new file mode 100644 index 0000000..cfd1574 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/19.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/2.gif b/digsby/res/emoticons/Yahoo Messenger/2.gif new file mode 100644 index 0000000..522493c Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/2.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/20.gif b/digsby/res/emoticons/Yahoo Messenger/20.gif new file mode 100644 index 0000000..6acc02c Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/20.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/21.gif b/digsby/res/emoticons/Yahoo Messenger/21.gif new file mode 100644 index 0000000..24af59b Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/21.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/22.gif b/digsby/res/emoticons/Yahoo Messenger/22.gif new file mode 100644 index 0000000..9b543e2 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/22.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/23.gif b/digsby/res/emoticons/Yahoo Messenger/23.gif new file mode 100644 index 0000000..7315e7f Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/23.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/24.gif b/digsby/res/emoticons/Yahoo Messenger/24.gif new file mode 100644 index 0000000..58c32c1 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/24.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/25.gif b/digsby/res/emoticons/Yahoo Messenger/25.gif new file mode 100644 index 0000000..85bf150 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/25.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/26.gif b/digsby/res/emoticons/Yahoo Messenger/26.gif new file mode 100644 index 0000000..1773d91 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/26.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/27.gif b/digsby/res/emoticons/Yahoo Messenger/27.gif new file mode 100644 index 0000000..371da0f Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/27.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/28.gif b/digsby/res/emoticons/Yahoo Messenger/28.gif new file mode 100644 index 0000000..0b09db8 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/28.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/29.gif b/digsby/res/emoticons/Yahoo Messenger/29.gif new file mode 100644 index 0000000..bfbccce Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/29.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/3.gif b/digsby/res/emoticons/Yahoo Messenger/3.gif new file mode 100644 index 0000000..3b441a1 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/3.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/30.gif b/digsby/res/emoticons/Yahoo Messenger/30.gif new file mode 100644 index 0000000..25fbf9a Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/30.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/31.gif b/digsby/res/emoticons/Yahoo Messenger/31.gif new file mode 100644 index 0000000..03a4ac2 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/31.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/32.gif b/digsby/res/emoticons/Yahoo Messenger/32.gif new file mode 100644 index 0000000..f8a2498 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/32.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/33.gif b/digsby/res/emoticons/Yahoo Messenger/33.gif new file mode 100644 index 0000000..5c0e96e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/33.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/34.gif b/digsby/res/emoticons/Yahoo Messenger/34.gif new file mode 100644 index 0000000..d642f2b Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/34.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/35.gif b/digsby/res/emoticons/Yahoo Messenger/35.gif new file mode 100644 index 0000000..9412be2 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/35.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/36.gif b/digsby/res/emoticons/Yahoo Messenger/36.gif new file mode 100644 index 0000000..7121507 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/36.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/37.gif b/digsby/res/emoticons/Yahoo Messenger/37.gif new file mode 100644 index 0000000..7d9a69f Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/37.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/38.gif b/digsby/res/emoticons/Yahoo Messenger/38.gif new file mode 100644 index 0000000..c6adaaf Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/38.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/39.gif b/digsby/res/emoticons/Yahoo Messenger/39.gif new file mode 100644 index 0000000..4916892 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/39.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/4.gif b/digsby/res/emoticons/Yahoo Messenger/4.gif new file mode 100644 index 0000000..fbf140b Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/4.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/40.gif b/digsby/res/emoticons/Yahoo Messenger/40.gif new file mode 100644 index 0000000..d72ffd1 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/40.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/41.gif b/digsby/res/emoticons/Yahoo Messenger/41.gif new file mode 100644 index 0000000..a5b183f Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/41.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/42.gif b/digsby/res/emoticons/Yahoo Messenger/42.gif new file mode 100644 index 0000000..3b3c951 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/42.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/43.gif b/digsby/res/emoticons/Yahoo Messenger/43.gif new file mode 100644 index 0000000..d1e6277 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/43.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/44.gif b/digsby/res/emoticons/Yahoo Messenger/44.gif new file mode 100644 index 0000000..e079429 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/44.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/45.gif b/digsby/res/emoticons/Yahoo Messenger/45.gif new file mode 100644 index 0000000..9d9f9c1 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/45.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/46.gif b/digsby/res/emoticons/Yahoo Messenger/46.gif new file mode 100644 index 0000000..b3117a3 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/46.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/47.gif b/digsby/res/emoticons/Yahoo Messenger/47.gif new file mode 100644 index 0000000..c6da2c5 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/47.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/48.gif b/digsby/res/emoticons/Yahoo Messenger/48.gif new file mode 100644 index 0000000..0aae1d9 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/48.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/49.gif b/digsby/res/emoticons/Yahoo Messenger/49.gif new file mode 100644 index 0000000..4719784 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/49.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/5.gif b/digsby/res/emoticons/Yahoo Messenger/5.gif new file mode 100644 index 0000000..5fcf989 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/5.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/50.gif b/digsby/res/emoticons/Yahoo Messenger/50.gif new file mode 100644 index 0000000..429e240 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/50.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/51.gif b/digsby/res/emoticons/Yahoo Messenger/51.gif new file mode 100644 index 0000000..6f3ebc0 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/51.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/52.gif b/digsby/res/emoticons/Yahoo Messenger/52.gif new file mode 100644 index 0000000..36bdf07 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/52.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/53.gif b/digsby/res/emoticons/Yahoo Messenger/53.gif new file mode 100644 index 0000000..e3c65e9 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/53.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/54.gif b/digsby/res/emoticons/Yahoo Messenger/54.gif new file mode 100644 index 0000000..f68e1c0 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/54.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/55.gif b/digsby/res/emoticons/Yahoo Messenger/55.gif new file mode 100644 index 0000000..9bf85d1 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/55.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/56.gif b/digsby/res/emoticons/Yahoo Messenger/56.gif new file mode 100644 index 0000000..9317976 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/56.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/57.gif b/digsby/res/emoticons/Yahoo Messenger/57.gif new file mode 100644 index 0000000..be34a9e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/57.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/58.gif b/digsby/res/emoticons/Yahoo Messenger/58.gif new file mode 100644 index 0000000..06c2193 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/58.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/59.gif b/digsby/res/emoticons/Yahoo Messenger/59.gif new file mode 100644 index 0000000..075a513 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/59.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/6.gif b/digsby/res/emoticons/Yahoo Messenger/6.gif new file mode 100644 index 0000000..fc2e8fa Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/6.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/60.gif b/digsby/res/emoticons/Yahoo Messenger/60.gif new file mode 100644 index 0000000..45b301c Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/60.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/61.gif b/digsby/res/emoticons/Yahoo Messenger/61.gif new file mode 100644 index 0000000..fe2cd86 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/61.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/62.gif b/digsby/res/emoticons/Yahoo Messenger/62.gif new file mode 100644 index 0000000..cea7673 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/62.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/63.gif b/digsby/res/emoticons/Yahoo Messenger/63.gif new file mode 100644 index 0000000..8b907aa Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/63.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/64.gif b/digsby/res/emoticons/Yahoo Messenger/64.gif new file mode 100644 index 0000000..9e5f3c0 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/64.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/65.gif b/digsby/res/emoticons/Yahoo Messenger/65.gif new file mode 100644 index 0000000..7c885b4 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/65.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/66.gif b/digsby/res/emoticons/Yahoo Messenger/66.gif new file mode 100644 index 0000000..50ba402 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/66.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/67.gif b/digsby/res/emoticons/Yahoo Messenger/67.gif new file mode 100644 index 0000000..938e9cb Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/67.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/68.gif b/digsby/res/emoticons/Yahoo Messenger/68.gif new file mode 100644 index 0000000..5c72797 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/68.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/69.gif b/digsby/res/emoticons/Yahoo Messenger/69.gif new file mode 100644 index 0000000..88f377c Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/69.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/7.gif b/digsby/res/emoticons/Yahoo Messenger/7.gif new file mode 100644 index 0000000..d16485e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/7.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/70.gif b/digsby/res/emoticons/Yahoo Messenger/70.gif new file mode 100644 index 0000000..a7bcc44 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/70.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/71.gif b/digsby/res/emoticons/Yahoo Messenger/71.gif new file mode 100644 index 0000000..51f0a11 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/71.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/72.gif b/digsby/res/emoticons/Yahoo Messenger/72.gif new file mode 100644 index 0000000..295e0ee Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/72.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/73.gif b/digsby/res/emoticons/Yahoo Messenger/73.gif new file mode 100644 index 0000000..9513235 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/73.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/74.gif b/digsby/res/emoticons/Yahoo Messenger/74.gif new file mode 100644 index 0000000..0eb19d9 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/74.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/75.gif b/digsby/res/emoticons/Yahoo Messenger/75.gif new file mode 100644 index 0000000..953ca96 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/75.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/76.gif b/digsby/res/emoticons/Yahoo Messenger/76.gif new file mode 100644 index 0000000..41f6fc4 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/76.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/77.gif b/digsby/res/emoticons/Yahoo Messenger/77.gif new file mode 100644 index 0000000..f47c30b Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/77.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/78.gif b/digsby/res/emoticons/Yahoo Messenger/78.gif new file mode 100644 index 0000000..cb1bcb9 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/78.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/79.gif b/digsby/res/emoticons/Yahoo Messenger/79.gif new file mode 100644 index 0000000..7f35b0e Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/79.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/8.gif b/digsby/res/emoticons/Yahoo Messenger/8.gif new file mode 100644 index 0000000..1d202d1 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/8.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/9.gif b/digsby/res/emoticons/Yahoo Messenger/9.gif new file mode 100644 index 0000000..ce8f162 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/9.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/Emoticons.plist b/digsby/res/emoticons/Yahoo Messenger/Emoticons.plist new file mode 100644 index 0000000..75a14e1 --- /dev/null +++ b/digsby/res/emoticons/Yahoo Messenger/Emoticons.plist @@ -0,0 +1,914 @@ + + + + + AdiumSetVersion + 1 + Emoticons + + 1.gif + + Equivalents + + :) + :-) + + Name + happy + + 10.gif + + Equivalents + + :P + :p + :-p + :-P + + Name + tongue + + 100.gif + + Equivalents + + :)] + + Name + on the phone + + 101.gif + + Equivalents + + :-c + + Name + call me + + 102.gif + + Equivalents + + ~X( + + Name + at wits' end + + 103.gif + + Equivalents + + :-h + + Name + wave + + 104.gif + + Equivalents + + :-t + + Name + time out + + 105.gif + + Equivalents + + 8-> + + Name + daydreaming + + 106.gif + + Equivalents + + :-?? + + Name + I don't know + + 107.gif + + Equivalents + + %-( + + Name + not listening + + 108.gif + + Equivalents + + :o3 + + Name + puppy dog eyes + + 11.gif + + Equivalents + + :-* + :* + + Name + kiss + + 12.gif + + Equivalents + + =(( + + Name + broken heart + + 13.gif + + Equivalents + + :-O + :O + + Name + surprise + + 14.gif + + Equivalents + + X( + + Name + angry + + 15.gif + + Equivalents + + :> + + Name + smug + + 16.gif + + Equivalents + + B-) + + Name + cool + + 17.gif + + Equivalents + + :-S + + Name + worried + + 18.gif + + Equivalents + + #:-S + #:-s + + Name + whew! + + 19.gif + + Equivalents + + >:) + >:-) + + Name + devil + + 2.gif + + Equivalents + + :( + :-( + + Name + sad + + 20.gif + + Equivalents + + :(( + :-(( + :'( + :'-( + + Name + crying + + 21.gif + + Equivalents + + :)) + :-)) + + Name + laughing + + 22.gif + + Equivalents + + :| + :-| + + Name + straight face + + 23.gif + + Equivalents + + /:) + /:-) + + Name + raised eyebrow + + 24.gif + + Equivalents + + =)) + + Name + rolling on the floor + + 25.gif + + Equivalents + + O:-) + O:) + + Name + angel + + 26.gif + + Equivalents + + :-B + + Name + nerd + + 27.gif + + Equivalents + + =; + + Name + talk to the hand + + 28.gif + + Equivalents + + I-) + + Name + sleepy + + 29.gif + + Equivalents + + 8-| + + Name + rolling eyes + + 3.gif + + Equivalents + + ;) + ;-) + + Name + winking + + 30.gif + + Equivalents + + L-) + + Name + loser + + 31.gif + + Equivalents + + :-& + + Name + sick + + 32.gif + + Equivalents + + :-$ + + Name + don't tell anyone + + 33.gif + + Equivalents + + [-( + + Name + not talking + + 34.gif + + Equivalents + + :O) + + Name + clown + + 35.gif + + Equivalents + + 8-} + + Name + silly + + 36.gif + + Equivalents + + <:-P + <:-P + + Name + party + + 37.gif + + Equivalents + + (:| + + Name + yawn + + 38.gif + + Equivalents + + =P~ + + Name + drooling + + 39.gif + + Equivalents + + :-? + + Name + thinking + + 4.gif + + Equivalents + + :D + :-D + + Name + big grin + + 40.gif + + Equivalents + + #-o + #-O + + Name + d'oh + + 41.gif + + Equivalents + + =D> + + Name + applause + + 42.gif + + Equivalents + + :-SS + :-ss + + Name + nailbiting + + 43.gif + + Equivalents + + @-) + + Name + hypnotized + + 44.gif + + Equivalents + + :^o + + Name + liar + + 45.gif + + Equivalents + + :-w + :-W + + Name + waiting + + 46.gif + + Equivalents + + :-< + + Name + sigh + + 47.gif + + Equivalents + + >:P + >:p + + Name + phbbbbt + + 48.gif + + Equivalents + + <):) + + Name + cowboy + + 49.gif + + Equivalents + + :@) + + Name + pig + + 5.gif + + Equivalents + + ;;) + + Name + batting eyelashes + + 50.gif + + Equivalents + + 3:-O + 3:-o + + Name + cow + + 51.gif + + Equivalents + + :(|) + + Name + monkey + + 52.gif + + Equivalents + + ~:> + + Name + chicken + + 53.gif + + Equivalents + + @};- + + Name + rose + + 54.gif + + Equivalents + + %%- + + Name + good luck + + 55.gif + + Equivalents + + **== + + Name + flag + + 56.gif + + Equivalents + + (~~) + + Name + pumpkin + + 57.gif + + Equivalents + + ~O) + + Name + coffee + + 58.gif + + Equivalents + + *-:) + + Name + idea + + 59.gif + + Equivalents + + 8-X + + Name + skull + + 6.gif + + Equivalents + + >:D< + + Name + big hug + + 60.gif + + Equivalents + + =:) + + Name + bug + + 61.gif + + Equivalents + + >-) + + Name + alien + + 62.gif + + Equivalents + + :-L + :L + + Name + frustrated + + 63.gif + + Equivalents + + [-O< + + Name + praying + + 64.gif + + Equivalents + + $-) + + Name + money eyes + + 65.gif + + Equivalents + + :-" + + Name + whistling + + 66.gif + + Equivalents + + b-( + + Name + feeling beat up + + 67.gif + + Equivalents + + :)>- + + Name + peace sign + + 68.gif + + Equivalents + + [-X + + Name + shame on you + + 69.gif + + Equivalents + + \:D/ + + Name + dancing + + 7.gif + + Equivalents + + :-/ + :/ + + Name + confused + + 70.gif + + Equivalents + + >:/ + + Name + bring it on + + 71.gif + + Equivalents + + ;)) + + Name + hee hee + + 72.gif + + Equivalents + + o-> + + Name + hiro + + 73.gif + + Equivalents + + o=> + + Name + billy + + 74.gif + + Equivalents + + o-+ + + Name + april + + 75.gif + + Equivalents + + (%) + + Name + yin yang + + 76.gif + + Equivalents + + :-@ + + Name + chatterbox + + 77.gif + + Equivalents + + ^:)^ + + Name + not worthy + + 78.gif + + Equivalents + + :-j + + Name + oh go on + + 79.gif + + Equivalents + + (*) + + Name + star + + 8.gif + + Equivalents + + :x + :X + + Name + love struck + + 9.gif + + Equivalents + + :"> + + Name + blushing + + 109.gif + + Equivalents + + X_X + x_x + + Name + I don't want to see + + 110.gif + + Equivalents + + :!! + + Name + hurry up! + + 111.gif + + Equivalents + + \m/ + + Name + rock on! + + 112.gif + + Equivalents + + :-q + + Name + thumbs down + + 113.gif + + Equivalents + + :-bd + + Name + thumbs up + + 114.gif + + Equivalents + + ^#(^ + + Name + it wasn't me + + pirate.gif + + Equivalents + + :ar! + + Name + pirate + + 115.gif + + Equivalents + + :bz + + Name + bee + + transformer.gif + + Equivalents + + [..] + + Name + transformer + + + Service Class + Yahoo + + diff --git a/digsby/res/emoticons/Yahoo Messenger/pirate.gif b/digsby/res/emoticons/Yahoo Messenger/pirate.gif new file mode 100644 index 0000000..7a897e6 Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/pirate.gif differ diff --git a/digsby/res/emoticons/Yahoo Messenger/transformer.gif b/digsby/res/emoticons/Yahoo Messenger/transformer.gif new file mode 100644 index 0000000..28109da Binary files /dev/null and b/digsby/res/emoticons/Yahoo Messenger/transformer.gif differ diff --git a/digsby/res/emoticons/default/Adore.png b/digsby/res/emoticons/default/Adore.png new file mode 100644 index 0000000..18e490d Binary files /dev/null and b/digsby/res/emoticons/default/Adore.png differ diff --git a/digsby/res/emoticons/default/Angry.png b/digsby/res/emoticons/default/Angry.png new file mode 100644 index 0000000..ba06526 Binary files /dev/null and b/digsby/res/emoticons/default/Angry.png differ diff --git a/digsby/res/emoticons/default/Badly.png b/digsby/res/emoticons/default/Badly.png new file mode 100644 index 0000000..e30a37f Binary files /dev/null and b/digsby/res/emoticons/default/Badly.png differ diff --git a/digsby/res/emoticons/default/Celebrate.png b/digsby/res/emoticons/default/Celebrate.png new file mode 100644 index 0000000..fde5f15 Binary files /dev/null and b/digsby/res/emoticons/default/Celebrate.png differ diff --git a/digsby/res/emoticons/default/Cool.png b/digsby/res/emoticons/default/Cool.png new file mode 100644 index 0000000..37b0659 Binary files /dev/null and b/digsby/res/emoticons/default/Cool.png differ diff --git a/digsby/res/emoticons/default/Cry.png b/digsby/res/emoticons/default/Cry.png new file mode 100644 index 0000000..a8c2ee2 Binary files /dev/null and b/digsby/res/emoticons/default/Cry.png differ diff --git a/digsby/res/emoticons/default/Dizzy.png b/digsby/res/emoticons/default/Dizzy.png new file mode 100644 index 0000000..b2d4e1f Binary files /dev/null and b/digsby/res/emoticons/default/Dizzy.png differ diff --git a/digsby/res/emoticons/default/Easy-money.png b/digsby/res/emoticons/default/Easy-money.png new file mode 100644 index 0000000..09ac28e Binary files /dev/null and b/digsby/res/emoticons/default/Easy-money.png differ diff --git a/digsby/res/emoticons/default/Frown.png b/digsby/res/emoticons/default/Frown.png new file mode 100644 index 0000000..d453b4b Binary files /dev/null and b/digsby/res/emoticons/default/Frown.png differ diff --git a/digsby/res/emoticons/default/Furious.png b/digsby/res/emoticons/default/Furious.png new file mode 100644 index 0000000..6c11679 Binary files /dev/null and b/digsby/res/emoticons/default/Furious.png differ diff --git a/digsby/res/emoticons/default/Hysterical.png b/digsby/res/emoticons/default/Hysterical.png new file mode 100644 index 0000000..4270d50 Binary files /dev/null and b/digsby/res/emoticons/default/Hysterical.png differ diff --git a/digsby/res/emoticons/default/Impish.png b/digsby/res/emoticons/default/Impish.png new file mode 100644 index 0000000..10fa0b8 Binary files /dev/null and b/digsby/res/emoticons/default/Impish.png differ diff --git a/digsby/res/emoticons/default/Kiss.png b/digsby/res/emoticons/default/Kiss.png new file mode 100644 index 0000000..e9c86a9 Binary files /dev/null and b/digsby/res/emoticons/default/Kiss.png differ diff --git a/digsby/res/emoticons/default/Kissed.png b/digsby/res/emoticons/default/Kissed.png new file mode 100644 index 0000000..bcf8e86 Binary files /dev/null and b/digsby/res/emoticons/default/Kissed.png differ diff --git a/digsby/res/emoticons/default/Laugh.png b/digsby/res/emoticons/default/Laugh.png new file mode 100644 index 0000000..16c97ed Binary files /dev/null and b/digsby/res/emoticons/default/Laugh.png differ diff --git a/digsby/res/emoticons/default/Music.png b/digsby/res/emoticons/default/Music.png new file mode 100644 index 0000000..e6d46ba Binary files /dev/null and b/digsby/res/emoticons/default/Music.png differ diff --git a/digsby/res/emoticons/default/Pudently.png b/digsby/res/emoticons/default/Pudently.png new file mode 100644 index 0000000..a7b6bdd Binary files /dev/null and b/digsby/res/emoticons/default/Pudently.png differ diff --git a/digsby/res/emoticons/default/Sad.png b/digsby/res/emoticons/default/Sad.png new file mode 100644 index 0000000..828c5dd Binary files /dev/null and b/digsby/res/emoticons/default/Sad.png differ diff --git a/digsby/res/emoticons/default/Shok.png b/digsby/res/emoticons/default/Shok.png new file mode 100644 index 0000000..f4aace9 Binary files /dev/null and b/digsby/res/emoticons/default/Shok.png differ diff --git a/digsby/res/emoticons/default/Sleep.png b/digsby/res/emoticons/default/Sleep.png new file mode 100644 index 0000000..7ef7692 Binary files /dev/null and b/digsby/res/emoticons/default/Sleep.png differ diff --git a/digsby/res/emoticons/default/Smile.png b/digsby/res/emoticons/default/Smile.png new file mode 100644 index 0000000..15fd558 Binary files /dev/null and b/digsby/res/emoticons/default/Smile.png differ diff --git a/digsby/res/emoticons/default/Stop.png b/digsby/res/emoticons/default/Stop.png new file mode 100644 index 0000000..745b1ac Binary files /dev/null and b/digsby/res/emoticons/default/Stop.png differ diff --git a/digsby/res/emoticons/default/Struggle.png b/digsby/res/emoticons/default/Struggle.png new file mode 100644 index 0000000..4a0ab8e Binary files /dev/null and b/digsby/res/emoticons/default/Struggle.png differ diff --git a/digsby/res/emoticons/default/Study.png b/digsby/res/emoticons/default/Study.png new file mode 100644 index 0000000..68780fd Binary files /dev/null and b/digsby/res/emoticons/default/Study.png differ diff --git a/digsby/res/emoticons/default/Surprise.png b/digsby/res/emoticons/default/Surprise.png new file mode 100644 index 0000000..459fa0c Binary files /dev/null and b/digsby/res/emoticons/default/Surprise.png differ diff --git a/digsby/res/emoticons/default/Sweat.png b/digsby/res/emoticons/default/Sweat.png new file mode 100644 index 0000000..0d3848a Binary files /dev/null and b/digsby/res/emoticons/default/Sweat.png differ diff --git a/digsby/res/emoticons/default/Sweet-angel.png b/digsby/res/emoticons/default/Sweet-angel.png new file mode 100644 index 0000000..675c11e Binary files /dev/null and b/digsby/res/emoticons/default/Sweet-angel.png differ diff --git a/digsby/res/emoticons/default/Wink.png b/digsby/res/emoticons/default/Wink.png new file mode 100644 index 0000000..9f9cbc2 Binary files /dev/null and b/digsby/res/emoticons/default/Wink.png differ diff --git a/digsby/res/emoticons/default/Woo.png b/digsby/res/emoticons/default/Woo.png new file mode 100644 index 0000000..708d6ff Binary files /dev/null and b/digsby/res/emoticons/default/Woo.png differ diff --git a/digsby/res/emoticons/default/Worn-out.png b/digsby/res/emoticons/default/Worn-out.png new file mode 100644 index 0000000..74d9bb3 Binary files /dev/null and b/digsby/res/emoticons/default/Worn-out.png differ diff --git a/digsby/res/emoticons/default/emoticons.txt b/digsby/res/emoticons/default/emoticons.txt new file mode 100644 index 0000000..7dae084 --- /dev/null +++ b/digsby/res/emoticons/default/emoticons.txt @@ -0,0 +1,26 @@ +Digsby +Smile.png :) =) :-) =-) +Wink.png ;) ;-) +Sad.png :( :-( =( =-( +Impish.png :-P :P :p =P :-p =-P +Laugh.png :D =D :-D =-D +Surprise.png :O :o :0 =O =o =0 =-O +Kiss.png :-* =* :* =-* +Kissed.png :*o =*o +Adore.png <3 +Angry.png >:| >=| >| >:( >=( >( >:-| >:-( +Badly.png :0~ :O~ =0~ =O~ +Celebrate.png <(:) <:) <(=) <=) +Cool.png 8-) 8) +Cry.png :'( ='( +Dizzy.png @_@ @.@ @o@ +Easy-money.png :-$ $) $-) +Furious.png >:o }:| }=| }:( }=( }:-| }=-| }:-( }=-( +Music.png (|^.^|) (|^_^|) (|^o^|) (|-.-|) (|-_-|) (|-o-|) +Pudently.png :#) =#) +Shok.png :/ :-/ :\ :-\ +Sleep.png -.- -_- +Stop.png :-X :X :x =X =x +Study.png (nerd) +Sweat.png (> <) +Sweet-angel.png O:-) O:) O:o O:| O:( O:-o O:-| O:-( diff --git a/digsby/res/emoticons/iChat Complete/Angry-Tongue.png b/digsby/res/emoticons/iChat Complete/Angry-Tongue.png new file mode 100644 index 0000000..ad958d9 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Angry-Tongue.png differ diff --git a/digsby/res/emoticons/iChat Complete/Angry.png b/digsby/res/emoticons/iChat Complete/Angry.png new file mode 100644 index 0000000..048e289 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Angry.png differ diff --git a/digsby/res/emoticons/iChat Complete/Blushing.png b/digsby/res/emoticons/iChat Complete/Blushing.png new file mode 100644 index 0000000..83efb9b Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Blushing.png differ diff --git a/digsby/res/emoticons/iChat Complete/Confused.png b/digsby/res/emoticons/iChat Complete/Confused.png new file mode 100644 index 0000000..9302da4 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Confused.png differ diff --git a/digsby/res/emoticons/iChat Complete/Cool.png b/digsby/res/emoticons/iChat Complete/Cool.png new file mode 100644 index 0000000..f3fdd2e Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Cool.png differ diff --git a/digsby/res/emoticons/iChat Complete/Crying.png b/digsby/res/emoticons/iChat Complete/Crying.png new file mode 100644 index 0000000..8a1a4d6 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Crying.png differ diff --git a/digsby/res/emoticons/iChat Complete/Embarrassed.png b/digsby/res/emoticons/iChat Complete/Embarrassed.png new file mode 100644 index 0000000..b298390 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Embarrassed.png differ diff --git a/digsby/res/emoticons/iChat Complete/Emoticons.plist b/digsby/res/emoticons/iChat Complete/Emoticons.plist new file mode 100644 index 0000000..7785597 --- /dev/null +++ b/digsby/res/emoticons/iChat Complete/Emoticons.plist @@ -0,0 +1,340 @@ + + + + + AdiumSetVersion + 1 + Emoticons + + Angry-Tongue.png + + Equivalents + + >:-P + >:P + >:-p + >:p + + Name + Angry Tongue + + Angry.png + + Equivalents + + >:-( + >:( + + Name + Angry + + Blushing.png + + Equivalents + + :-] + :] + :-` + + Name + Blushing + + Confused.png + + Equivalents + + :-? + :? + + Name + Confused + + Cool.png + + Equivalents + + B-) + B) + 8-) + 8) + + Name + Cool + + Crying.png + + Equivalents + + :'-( + :'( + + Name + Crying + + Embarrassed.png + + Equivalents + + :-[ + :[ + + Name + Embarrassed + + Evil.png + + Equivalents + + >:-D + >:D + + Name + Evil + + Frown.png + + Equivalents + + :-( + :( + + Name + Frown + + Gasp.png + + Equivalents + + :-o + :o + :-O + :O + =-o + =-O + + Name + Gasp + + Grimmace.png + + Equivalents + + X-( + x-( + X( + x( + + Name + Grimmace + + Grin.png + + Equivalents + + :-D + :D + + Name + Grin + + Grinning-Wink.png + + Equivalents + + ;-D + ;D + + Name + Grinning Wink + + Innocent.png + + Equivalents + + O:-) + O:) + o:-) + o:) + 0:-) + 0:) + + Name + Innocent + + Kiss.png + + Equivalents + + :-* + :* + + Name + Kiss + + Lips-Are-Sealed.png + + Equivalents + + :-X + :-x + + Name + Lips Are Sealed + + Mischievous.png + + Equivalents + + >:-) + >:) + + Name + Mischievous + + Money-Mouth.png + + Equivalents + + :-$ + :$ + + Name + Money Mouth + + Not-Amused.png + + Equivalents + + >:-| + >:| + >:-I + >:I + + Name + Not Amused + + Oops.png + + Equivalents + + :-! + :! + + Name + Oops + + Shouting.png + + Equivalents + + >:-O + >:O + >:-o + >:o + :-@ + :@ + >:-@ + >:@ + >:-V + >:V + :-V + :V + + Name + Shouting + + Sleeping.png + + Equivalents + + |-) + I-) + I-| + |-| + |-* + |-* + |-. + |-. + + Name + Sleeping + + Smile.png + + Equivalents + + :-) + :) + + Name + Smile + + Smirk.png + + Equivalents + + :-> + :> + + Name + Smirk + + Straight-Faced.png + + Equivalents + + :-| + :| + :-I + :I + + Name + Straight Faced + + Tongue.png + + Equivalents + + :-P + :P + :-p + :p + + Name + Tongue + + Undecided.png + + Equivalents + + :-\ + :\ + + Name + Undecided + + Wink-Tongue.png + + Equivalents + + ;-P + ;P + ;-p + ;p + + Name + Wink Tongue + + Wink.png + + Equivalents + + ;-) + ;) + + Name + Wink + + + + diff --git a/digsby/res/emoticons/iChat Complete/Evil.png b/digsby/res/emoticons/iChat Complete/Evil.png new file mode 100644 index 0000000..8de6e83 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Evil.png differ diff --git a/digsby/res/emoticons/iChat Complete/Frown.png b/digsby/res/emoticons/iChat Complete/Frown.png new file mode 100644 index 0000000..79234a7 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Frown.png differ diff --git a/digsby/res/emoticons/iChat Complete/Gasp.png b/digsby/res/emoticons/iChat Complete/Gasp.png new file mode 100644 index 0000000..ff76cd0 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Gasp.png differ diff --git a/digsby/res/emoticons/iChat Complete/Grimmace.png b/digsby/res/emoticons/iChat Complete/Grimmace.png new file mode 100644 index 0000000..1a677fe Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Grimmace.png differ diff --git a/digsby/res/emoticons/iChat Complete/Grin.png b/digsby/res/emoticons/iChat Complete/Grin.png new file mode 100644 index 0000000..1513a3c Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Grin.png differ diff --git a/digsby/res/emoticons/iChat Complete/Grinning-Wink.png b/digsby/res/emoticons/iChat Complete/Grinning-Wink.png new file mode 100644 index 0000000..546ed28 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Grinning-Wink.png differ diff --git a/digsby/res/emoticons/iChat Complete/Innocent.png b/digsby/res/emoticons/iChat Complete/Innocent.png new file mode 100644 index 0000000..ca94dd5 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Innocent.png differ diff --git a/digsby/res/emoticons/iChat Complete/Kiss.png b/digsby/res/emoticons/iChat Complete/Kiss.png new file mode 100644 index 0000000..9491d5d Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Kiss.png differ diff --git a/digsby/res/emoticons/iChat Complete/Lips-Are-Sealed.png b/digsby/res/emoticons/iChat Complete/Lips-Are-Sealed.png new file mode 100644 index 0000000..0993fc9 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Lips-Are-Sealed.png differ diff --git a/digsby/res/emoticons/iChat Complete/Mischievous.png b/digsby/res/emoticons/iChat Complete/Mischievous.png new file mode 100644 index 0000000..6db9a1c Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Mischievous.png differ diff --git a/digsby/res/emoticons/iChat Complete/Money-Mouth.png b/digsby/res/emoticons/iChat Complete/Money-Mouth.png new file mode 100644 index 0000000..274a175 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Money-Mouth.png differ diff --git a/digsby/res/emoticons/iChat Complete/Not-Amused.png b/digsby/res/emoticons/iChat Complete/Not-Amused.png new file mode 100644 index 0000000..d5041bc Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Not-Amused.png differ diff --git a/digsby/res/emoticons/iChat Complete/Oops.png b/digsby/res/emoticons/iChat Complete/Oops.png new file mode 100644 index 0000000..b38c020 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Oops.png differ diff --git a/digsby/res/emoticons/iChat Complete/Shouting.png b/digsby/res/emoticons/iChat Complete/Shouting.png new file mode 100644 index 0000000..adfa228 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Shouting.png differ diff --git a/digsby/res/emoticons/iChat Complete/Sleeping.png b/digsby/res/emoticons/iChat Complete/Sleeping.png new file mode 100644 index 0000000..50bed0d Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Sleeping.png differ diff --git a/digsby/res/emoticons/iChat Complete/Smile.png b/digsby/res/emoticons/iChat Complete/Smile.png new file mode 100644 index 0000000..696e9a0 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Smile.png differ diff --git a/digsby/res/emoticons/iChat Complete/Smirk.png b/digsby/res/emoticons/iChat Complete/Smirk.png new file mode 100644 index 0000000..60acc06 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Smirk.png differ diff --git a/digsby/res/emoticons/iChat Complete/Straight-Faced.png b/digsby/res/emoticons/iChat Complete/Straight-Faced.png new file mode 100644 index 0000000..252fbe3 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Straight-Faced.png differ diff --git a/digsby/res/emoticons/iChat Complete/Tongue.png b/digsby/res/emoticons/iChat Complete/Tongue.png new file mode 100644 index 0000000..caa396f Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Tongue.png differ diff --git a/digsby/res/emoticons/iChat Complete/Undecided.png b/digsby/res/emoticons/iChat Complete/Undecided.png new file mode 100644 index 0000000..6b3b155 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Undecided.png differ diff --git a/digsby/res/emoticons/iChat Complete/Wink-Tongue.png b/digsby/res/emoticons/iChat Complete/Wink-Tongue.png new file mode 100644 index 0000000..b63a14a Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Wink-Tongue.png differ diff --git a/digsby/res/emoticons/iChat Complete/Wink.png b/digsby/res/emoticons/iChat Complete/Wink.png new file mode 100644 index 0000000..c8cb8d2 Binary files /dev/null and b/digsby/res/emoticons/iChat Complete/Wink.png differ diff --git a/digsby/res/happynewdigsby.png b/digsby/res/happynewdigsby.png new file mode 100644 index 0000000..00e01dd Binary files /dev/null and b/digsby/res/happynewdigsby.png differ diff --git a/digsby/res/html/date.format.js b/digsby/res/html/date.format.js new file mode 100644 index 0000000..3992c50 --- /dev/null +++ b/digsby/res/html/date.format.js @@ -0,0 +1,126 @@ +/* + * Date Format 1.2.3 + * (c) 2007-2009 Steven Levithan + * MIT license + * + * Includes enhancements by Scott Trenda + * and Kris Kowal + * + * Accepts a date, a mask, or a date and a mask. + * Returns a formatted version of the given date. + * The date defaults to the current date/time. + * The mask defaults to dateFormat.masks.default. + */ + +var dateFormat = function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = dateFormat; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + date = date ? new Date(date) : new Date; + if (isNaN(date)) throw SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; +}(); + +// Some common format strings +dateFormat.masks = { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" +}; + +// Internationalization strings +dateFormat.i18n = { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] +}; + +// For convenience... +Date.prototype.format = function (mask, utc) { + return dateFormat(this, mask, utc); +}; + diff --git a/digsby/res/html/infobox/head.tenjin b/digsby/res/html/infobox/head.tenjin new file mode 100644 index 0000000..d5a66a7 --- /dev/null +++ b/digsby/res/html/infobox/head.tenjin @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/res/html/infobox/infobox.css b/digsby/res/html/infobox/infobox.css new file mode 100644 index 0000000..65a6427 --- /dev/null +++ b/digsby/res/html/infobox/infobox.css @@ -0,0 +1,39 @@ +.ul { + display: inline; +} + +.time-container { + white-space: nowrap; +} + +.time-container:before { + content: "("; +} + +.time-container:after { + content: ")"; +} + +.date-separator + .date-separator.today { + display: none; +} + +a:hover { + text-decoration: underline; +} + +body { + word-wrap: break-word; +} + +.trend_link { + border-left: solid 2px #d7d7d7; + margin-top: 2px; + padding-left: .5em; +} + +.trend_link_text { + display: inline; + max-width: 100%; +} + diff --git a/digsby/res/html/infobox/infobox.js b/digsby/res/html/infobox/infobox.js new file mode 100644 index 0000000..814d8f4 --- /dev/null +++ b/digsby/res/html/infobox/infobox.js @@ -0,0 +1,509 @@ + +function format_time(sec, how) { + var unix = parseInt(sec, 10); + var millis = sec * 1000; + millis = (millis); + + var now = new Date(); + var d = new Date(millis); + + if (how === "pretty") { + return prettyDate(d); + } else if (how === "smart") { + if (_isSameMonth(d, now) || _isLastMonth(d, now)) { + return d.format("shortTime"); + } else if (d.getFullYear() >= (now.getFullYear() - 1)) { // this year or last year + return d.format("mmmm d"); + } else { + return d.format("mmmm d"); + } + } else { + return d.format(how); + } +} + +function convert_timestamps_to_text(tree) { + if (!tree) { tree = $(); } + + var _do_convert = function(node) { + if (node === parseInt(node,10)) { + node = $(this); + } else { + node = $(node); + } + var tx_text = null; + try { + + ts_text = format_time(node.attr("timestamp"), node.attr("timestyle")); + } catch (e) { + console.log("error formatting text:" + e + "/" + node.attr("timestamp")); + } + + try { + node.find(".time-container").text(ts_text); + } catch (e) { + console.log("error attaching text:" + e); + } + } + + if (tree.attr("timestamp")) _do_convert(tree); + else tree.find("[timestamp]").each(_do_convert); +} + +function get_today() { + return dayOfDate(new Date()); +} + +function _isSameMonth(t1, t2) { + if (!t2) { + t2 = new Date(); + } + + return ((t1.getFullYear() == t2.getFullYear()) && (t1.getMonth() == t2.getMonth())); +} + +function _isLastMonth(t1, sinceWhen) { + if (!sinceWhen) { + sinceWhen = new Date(); + } + + t1 = dayOfDate(t1); + t2 = dayOfDate(sinceWhen); + + t2.setDate(0); + t2 = new Date(t2.valueOf() - 1); + // t2 is now the last millisecond of the previous month + + return _isSameMonth(t1, t2); +} + +function relevant_time_diff(t1, t2) { + // Return a timestamp with granularity appropriate for the difference between + // the two provided timestamps. + t1 = dayOfDate(t1); + if (!t2) t2 = new Date(); + t2 = dayOfDate(t2); + + var t1v = t1.valueOf(); + var t2v = t2.valueOf(); + + var diff_v = t2v - t1v; + + // was it in the same day? + if (diff_v === 0) { + return t1; + // yesterday? + } else if (diff_v <= (1000 * 60 * 60 * 24)) { + return t1; + } else if (_isSameMonth(t1, t2)) { + return t1; + // last month? + } else if (_isLastMonth(t1, t2)) { + // timeframe is the first of the month + return t1; + } else if (t1.getFullYear() >= (t2.getFullYear() - 1)) { + t1.setDate(1) + return t1; + } else { + // timeframe is first of january + t1.setMonth(0); + t1.setDate(1); // this must be the fabled "poor date handling" in javascript. starts at 1? + return t1; + } +} + +function _get_timestamp(el) { + return new Date(parseInt($(el).attr("timestamp"), 10)*1000); +} + +var separator_count = 0; +function _insert_separator(el, the_date, today_text, today, do_count, initial) { + do_count = do_count || (do_count === null) || (do_count === undefined); + + var date_sep = document.createElement("span"); + $(date_sep).addClass("date-separator"); + $(date_sep).addClass("title"); + + the_date = dayOfDate(the_date); + if (today === null || today === undefined) { + today = get_today(); + } + var text = null; + var is_today = the_date.valueOf() == today.valueOf(); + + if (is_today) { + $(date_sep).addClass("today"); + } + + if (today_text === null || today_text === undefined){ + today_text = TODAY_TEXT; + } + + if (do_count && (separator_count === 0) && today_text) { + text = today_text || TODAY_TEXT; + } else if ((!do_count) && initial && today_text) { + text = today_text || TODAY_TEXT; + } else if (is_today) { + text = "Today"; + } else if (_isSameMonth(the_date, today) || _isLastMonth(the_date, today)) { + if ((today.valueOf() - the_date.valueOf()) <= (1000 * 60 * 60 * 24)) { + text = "Yesterday"; + } else { + text = the_date.format("mmmm d"); + } + } else { + text = the_date.format("mmmm"); + } + + if (do_count){ + separator_count++; + } + if (!text) { return }; + $(date_sep).text(text); + + $(date_sep).insertBefore($(el)); +} + +function add_date_separators(selector, do_count, initial) { + if ($(".date-separator:not(.static)").length) return; // don't add date seperators more than once. + // Get the last time that's still today (i.e. tomorrow - 1) + var today = get_today(); + var first_event_of_time_period = null; + var current_time_period = null; + + var acts = $(selector); + for (var i = 0; i < acts.length; i++) { + var ts = _get_timestamp(acts[i]); + if (!ts) continue; + var time_period = relevant_time_diff(ts, today); + if ((!current_time_period) || + (time_period.valueOf() != current_time_period.valueOf())) { + first_event_of_time_period = acts[i]; + current_time_period = time_period; + _insert_separator(acts[i], + //time_period, + _get_timestamp(acts[i]), + undefined, undefined, do_count, initial); + } + } +} + +function clear_previous_date_separators(tree) { + if (!tree){ + tree = $(); + }; + separator_count = 0; + to_remove = tree.find(".date-separator:not(.static)"); + if (to_remove) to_remove.remove(); +} + +function add_time_containers(to_what) { + $(to_what).each(function() { + var myspan = document.createElement("span"); + $(myspan).addClass("time-container"); + $(myspan).addClass("minor"); + $(this).append(" "); + $(this).after(myspan); + }); +} + +function clear_previous_time_containers() { + $(".time-container").remove(); +} + +function add_link_class_to_a_tags(tree) { + if (!tree) { tree = $(); } + tree.find("a").each(function(){ + $(this).addClass("link"); + }); +} + +var DIGSBY_LIB_CLASS = 'digsbylib'; + +if (window.digsbyLoadedLibs === undefined){ + window.digsbyLoadedLibs = {}; +} + +function ensureLibrariesLoaded(libraries) { + var head = document.getElementsByTagName('head')[0]; + + jQuery.each(libraries, function (i, libsrc) { + if (!digsbyLoadedLibs[libsrc]) { + console.log('loading script:' + libsrc); + $(head).append(' + + +''' + +html_log_entry = ( +'
%(buddy)s ' +'%(message)s' +'
\n' +) + +class Logger(object): + 'Logs IMs and Chats to file.' + + def __init__(self, output_dir = None, + log_ims = True, + log_chats = True, + log_widgets = False): + + self.OutputType = 'html' + + self.LogChats = log_chats + self.LogIMs = log_ims + self.LogWidgets = log_widgets + + def calculate_log_sizes(self): + log_sizes = blist.getLogSizes(self.OutputDir) + return sorted(((name, size) for name, size in log_sizes.iteritems()), + key=itemgetter(1), reverse=True) + + def on_message(self, messageobj = None, convo = None, **opts): + ''' + Called for every incoming and outgoing message. + ''' + + if not self.should_log_message(messageobj): + return + + messageobj = modify_message(messageobj) + + output = self.generate_output(messageobj) + assert isinstance(output, str) + + written_size = self.write_output(output, messageobj) + + try: + buddy = messageobj.conversation.buddy + except AttributeError: + pass + else: + try: + buddy.increase_log_size(written_size) + except AttributeError: + import traceback + traceback.print_exc() + + def should_log_message(self, messageobj): + + if messageobj is None: + return False + + # if not logging this type of message, return + convo = messageobj.conversation + + if convo.ischat and not self.LogChats: + return False + + elif not convo.ischat and not self.LogIMs: + return False + + elif not self.LogWidgets and iswidget(convo.buddy): + return False + + elif messageobj.buddy is None: + # this is an "event" + return False + + elif not messageobj.buddy.protocol.should_log(messageobj): + # we don't really need to log exciting conversations with "AOL System Message" + return False + + return True + + + def history_for(self, account, buddy): + 'Returns an iterator which yields a succession of message objects back into the past.' + + log.debug('history_for(%r, %r)', account, buddy) + + files = self.logfiles_for(account, buddy) + log.debug('%d %s log files found', len(files), self.OutputType) + + if not files: + return iter([]) + + if fastFind is None: + # files come back sorted from fastFind + files.sort(reverse = True) + + return history_from_files(files, 'html') + + def history_for_safe(self, account, buddy): + try: + hist = self.history_for(account, buddy) + except Exception: + print_exc() + hist = iter([]) + + return hist + + def logsize(self, account, buddy): + "Returns the total size of all the files in the specified buddy's log folder." + + return sum(f.size for f in self.logfiles_for(account, buddy)) + + def logsize_for_nameservice(self, name, service): + glob_str = ''.join(('*/', name, '_', service, '/*.html')) + + outpath = self.OutputDir + types = SERVICE_MAP.get(service, [service]) + + total = 0 + for accttype in types: + logpath = outpath / accttype + total += sum(f.size for f in logpath.glob(glob_str)) + + return total + + def logfiles_for(self, account, buddy): + '''Returns a list of log files for a buddy on a given account.''' + + logdir = self.pathfor(account, buddy) + + if not logdir.isdir(): + return [] + + # Only match files that look like the logs we output. + global fastFind + if fastFind is not None: + pathjoin = os.path.join + + # use an optimized file search if possible + try: + wildcard = pathjoin(logdir, '*-*-*.html') + return [pathjoin(logdir, p) for p in fastFind(wildcard)] + except Exception: + print_exc() + fastFind = None + + return list(f for f in logdir.files('*.' + self.OutputType) + if filename_format_re.match(f.name)) + + def pathfor(self, account, buddy): + 'Returns the path to the directory where logs for the specified buddy is stored.' + + return self.OutputDir.joinpath(buddy_path(account, buddy)) + + # + # OutputDir property: where to write the logs + # + + def get_outputdir(self): + return get_default_logging_dir() + + OutputDir = property(get_outputdir, doc = 'where to write logs') + + def walk_group_chats(self): + ''' + yields all group chats + ''' + + for service in path(self.OutputDir).dirs(): + for account in service.dirs(): + group_chat_dir = account / GROUP_CHAT_DIRNAME + if group_chat_dir.isdir(): + for chat_file in group_chat_dir.files(): + filename = chat_file.namebase + try: + if filename.count('-') == 2: + time, roomname = datetime.strptime(filename, chat_time_format), None + else: + time_part, roomname = filename.split(' - ', 1) + time = datetime.strptime(time_part, chat_time_format) + if isinstance(roomname, str): + roomname = roomname.decode('filesys') + except ValueError: + pass + except Exception: + print_exc() + else: + yield dict(time=time, + service=service.name, + file=chat_file, + roomname=roomname) + + # + # + # + + def get_path_for_chat(self, chat): + pathdir = path(self.OutputDir) / chat_path(chat.protocol, chat) + + # log chats with the same name into the same file if it happened today + if chat.chat_room_name: + for f in pathdir.files('*.html'): + name = f.namebase + if 'T' in name: + day_part = name.split('T')[0] + try: + dt = datetime.strptime(day_part, chat_time_category) + except ValueError: + pass + else: + if fromutc(chat.start_time_utc).date() == dt.date(): + try: + time_part, roomname = name.split(' - ', 1) + except ValueError: + pass + else: + if roomname == chat.chat_room_name: + return f + + return pathdir / (convo_time_filename(chat) + '.' + self.OutputType) + + + + def write_output(self, output, messageobj): + ''' + Given output text and a message object, chooses a path for writing + the log message and appends the output to that file. + ''' + + convo = messageobj.conversation + proto = convo.protocol + + # Convert THIS timestamp to the local timezone so filenames are chosen based + # on the local date. + + if convo.ischat: + p = self.get_path_for_chat(convo) + else: + datefilename = fromutc(messageobj.timestamp).date().isoformat() # 2007-17-5 + pathelems = (buddy_path(proto, convo.buddy), datefilename) + p = path(path(self.OutputDir).joinpath(*pathelems) + '.' + self.OutputType) + + # assure path exists ( aim/digsby01/dotsyntax1337.html ) + if not p.parent.isdir(): + try: + p.parent.makedirs() + except WindowsError, e: # for race condition between exists check and makedirs + if e.winerror == 183: + pass + else: + raise + + written_size = 0 + if not p.isfile(): + # write a header if the file does not exist + header = globals()['generate_header_' + self.OutputType](messageobj, self.output_encoding) + assert isinstance(header, str) + written_size += len(header) + p.write_bytes(header) + + # write out to file + written_size += len(output) + p.write_bytes(output, append = p.isfile()) + return written_size + + def generate_output(self, messageobj): + 'Generates logging output for a message object.' + + return globals()['generate_output_' + self.OutputType](messageobj, self.output_encoding) + + output_encoding = 'utf-8' + +# +# format: HTML +# + +def generate_header_html(messageobj, encoding): + + c = messageobj.conversation + datefmt = messageobj.timestamp.date().isoformat() + + if c.ischat: + title = 'Chat in %s on %s' % (c.name, datefmt) + else: + title = 'IM Logs with %s on %s' % (c.buddy.name, datefmt) + + return (html_header % dict(title = title.encode('xml'))).encode(encoding, 'replace') + + + +def generate_output_html(m, encoding = 'utf-8'): + return (html_log_entry % dict(buddy = m.buddy.name if m.buddy is not None else '', + #time = m.timestamp.strftime(message_shorttime_fmt), + timestamp = m.timestamp.strftime(message_timestamp_fmt), + message = m.message, + type = m.type, + auto = getattr(m, 'auto', False), + )).encode(encoding, 'replace') + +class_buddy = {'class': 'buddy'} +class_message = {'class': 'message'} +class_msgcontent = {'class': 'msgcontent'} + +def parse_html_lxml(html): + 'parses a logfile with lxml' + + messages = [] + + doc = lxml.html.document_fromstring(html, parser = lxmlparser()) + for div in doc.xpath('//html/body/div'): + try: + message_type = div.attrib.get('class', '') + if not 'message' in message_type: + continue + + message_type = message_type.replace('message', '').strip() + if not message_type in ('incoming', 'outgoing'): + continue + + buddyname = div.find_class('buddy')[0].text + timestamp = div.attrib.get('timestamp') + if timestamp is not None: + timestamp = parse_timestamp(timestamp) + message = render_contents(div.find_class('msgcontent')[0]) + auto = boolify(div.attrib.get('auto', 'false')) + except Exception: + print_exc() + else: + messages.append(Message(buddy = S(name=buddyname), + timestamp = timestamp, + message = message, + type = message_type, + auto = auto, + has_autotext = auto, + )) + + return messages + +# a global lxml.html.HTMLParser object with encoding overridden to be utf-8 +_lxmlparser = None +def lxmlparser(): + global _lxmlparser + if _lxmlparser is None: + _lxmlparser = lxml.html.HTMLParser(encoding='utf-8') + return _lxmlparser + + +def parse_html_slow(html): + 'Uses Beautiful Soup to parse messages out of a log file.' + + html = html.decode('utf-8', 'ignore') + + soup = soupify(html, markupMassage = ((br_re,lambda m: '
'),)) + messages = [] + strptime = datetime.strptime + + for div in soup.findAll(message_divs): + try: + buddyname = div.findAll('span', class_buddy)[0].renderContents(None) + timestamp = parse_timestamp(div['timestamp']) + message = div.findAll('span', class_msgcontent)[0].renderContents(None) + type = div['class'].replace('message', '').strip() + auto = boolify(div.get('auto', 'false')) + except Exception: + print_exc() + else: + messages.append(Message(buddy = S(name = buddyname), + timestamp = timestamp, + message = message, + type = type, + auto = auto)) + + log_info('parse_html_slow with %d bytes returning %d messages', len(html), len(messages)) + return messages + +def message_divs(tag): + # Returns True for all Beautiful Soup tags which are
s with + # a class including "message" + return tag.name == 'div' and 'message' in dict(tag.attrs).get('class', '') + +show_logparse_tracebacks = True + +def parse_html(html): + 'HTML logs -> Message objects' + + # FIXME: On Mac, we are getting crashes when parse_html_fast is run. Disable it + # for now until we have time to come back and look into what is causing the crash. + if sys.platform == "darwin": + messages = parse_html_slow(html) + else: + try: + messages = parse_html_lxml(html) + if __debug__: + log.debug('parsed fast: got %d messages', len(messages)) + except Exception: + global show_logparse_tracebacks + if __debug__ or show_logparse_tracebacks: + print_exc() + show_logparse_tracebacks = False + + messages = parse_html_slow(html) + log_info('parsed slow: got %d messages', len(messages)) + + return messages + +def parse_timestamp(timestamp): + '"2008-02-23 22:34:50" -> datetime object' + try: + return datetime.strptime(timestamp, message_timestamp_fmt) + except Exception: + return datetime.strptime(timestamp, message_timestamp_fmt_OLD) + + +def history_from_files(files, logtype = 'html'): + parse = globals()['parse_' + logtype] + + for logfile in files: + # grab the last N bytes of the log file + try: + bytes = tail(logfile, LOGSIZE_PARSE_LIMIT) + except Exception: + print_exc() + else: + for msg in reversed(parse(bytes)): + # yield messages going back into the paaaaast. + yield msg + + if len(bytes) < logfile.size: + # if tail only returned part of a file, stop now so we don't skip + # messages + break + +chat_time_format = '%Y-%m-%dT%H.%M.%S' +chat_time_category = '%Y-%m-%d' + +def convo_time_filename(convo): + ''' + Given a Conversation object, returns a date string which can be used to + identify its logfile. + ''' + + # remove microseconds; we don't need THAT much precision. + time_part = fromutc(convo.start_time_utc).replace(microsecond=0).strftime(chat_time_format) + + room_name = convo.chat_room_name + if room_name: + return '%s - %s' % (time_part, room_name) + else: + return time_part + +import config +USE_LXML = config.platform == 'win' + +if USE_LXML: + from util.htmlutils import to_xhtml +else: + to_xhtml = lambda s:s + +def modify_message(msgobj): + msg = getattr(msgobj, 'message', None) + + if msg is not None: + # will fixup bad HTML like aim's stuff + msgobj.message = to_xhtml(msg) + + return msgobj + +import re +real_br = '
' +br_re = re.compile('', re.IGNORECASE) +brfix = lambda s: br_re.sub(real_br, s) + diff --git a/digsby/src/common/message.py b/digsby/src/common/message.py new file mode 100644 index 0000000..4963e21 --- /dev/null +++ b/digsby/src/common/message.py @@ -0,0 +1,44 @@ +from datetime import datetime +from util.primitives import Storage, curly + +class Message(Storage): + def __init__(self, buddy = None, message = None, conversation = None, type = 'incoming', timestamp = None, **other): + self.buddy = buddy + self.message = message + self.type = type + self.conversation = conversation + self.timestamp = timestamp if timestamp is not None else datetime.utcnow() + + self.update(other) + + def copy(self): + m = Message() + m.update(self) + return m + + def __hash__(self): + return hash(self._attrs) + + def __eq__(self, other): + return self is other or self._attrs == getattr(other, '_attrs', None) + + def __ne__(self, other): + return not self.__eq__(other) + + @property + def _attrs(self): + # round timestamps off to the same granularity used by the logger + from common import logger + datetime = logger.message_timestamp_id(self.timestamp) + return hash((self.message, self.type, datetime)) + +class StatusUpdateMessage(Message): + def __init__(self, **info): + buddy = info['buddy'] + message = curly(info['header'], source = info) + +# TODO: some of the skins don't have room for longer status lines... + Message.__init__(self, + buddy = buddy, + message = message, + type = 'status') diff --git a/digsby/src/common/notifications.py b/digsby/src/common/notifications.py new file mode 100644 index 0000000..aba3f98 --- /dev/null +++ b/digsby/src/common/notifications.py @@ -0,0 +1,531 @@ +''' +Notifications for program events. + +See the docstring for the "fire" method for a detailed description. +''' +from __future__ import with_statement +import wx, os.path +import config +from util.primitives import traceguard, dictadd +from util.introspect import memoize +import util.data_importer as importer +from path import path +from common.slotssavable import SlotsSavable +from os.path import exists as path_exists +from common import profile, pref +from itertools import chain +from logging import getLogger; log = getLogger('notifications') + +from Queue import Queue, Empty +from weakref import ref + +class cancellables(Queue): + def __init__(self): + Queue.__init__(self) + self.reaction_names = set() + + def put(self, item): + item = item() # weakref + if item is not None: + self.reaction_names.add(item.__class__.__name__) + return Queue.put(self, item) + + def had_reaction(self, reaction_name): + return reaction_name in self.reaction_names + + def cancel(self): + n = 0 + try: + while True: + item = self.get(block = False) + if isinstance(item, ref): + item = item() + + if item is not None: + try: + wx.CallAfter(item.cancel) + except wx.PyDeadObjectError: + pass + except Exception: + from traceback import print_exc + print_exc() + else: + n += 1 + except Empty: + pass + log.info('cancelled %d notification reactions', n) + +def add_reaction(notifications, notification_name, reaction_name): + reactions = notifications[None].setdefault(notification_name, []) + reaction = dict(reaction=reaction_name) + if reaction not in reactions: + reactions.append(reaction) + +def add_required_notifications(nots): + ''' + Adds notifications that cannot be disabled. + ''' + + if nots and config.platform == 'mac': + add_reaction(nots, 'message.received', 'BounceDockIcon') + +from copy import deepcopy +def get_user_notifications(): + user_notifications = deepcopy(dict(profile.notifications)) + add_required_notifications(user_notifications) + return user_notifications + +def fire(topic, **info): + ''' + Presents a simple interface for firing notification events. + + >> fire('error', title = 'Connection Error', msg = 'A connection could not be established') + + topic one or more of the topic keys listed in src/gui/notificationview.py, + i.e., "message.received.initial" + + info special keyword arguments giving information about the event. + Each notification topic expects different arguments, and some + "Reactions" (i.e., Popup windows) also look for options here. + + Extra arguments are passed to "Reactions" the user has set up to trigger + for specific topics. + + Topics use dotted notation to indicate a hierarchy. If "message.received.initial" + is fired and there is a sound effect set to trigger for "message.received", + it will play. Only one type of each "Reaction" is allowed to fire for each event, + so if a sound effect is set for "message" as well, it will not play. + ''' + assert '_' not in topic, 'underscores not allowed in topic names' + + # topic can be a string, or a list of strings. + if isinstance(topic, basestring): topic = [topic] + else: assert isinstance(topic, list) # todo: iterable + + # fire the longest topic strings first, since they will be deepest in the tree. + topics = sorted(set(chain(*(_topics_from_string(t) for t in topic))), reverse=True) + + log.debug('fired topics %r', topics) + + types = set() + cancels = cancellables() + + fired = False + + try: + notifications = get_user_notifications() + except AttributeError: + return log.warning('no notifications yet') + + # If this topic is in a set of topics to always fire, mark + # 'always_show' as True in the info dict. (See below) + always_show = info.get('always_show', []) + always_show = set(always_show) if always_show is not None else set() + for topic in topics: + always_show.update(always_fire.get(topic, [])) + + if 'buddy' in info: + # First, try buddy specific events. + + try: + idstr = info['buddy'].idstr() + buddy_events = notifications.get(idstr, []) + except: + buddy_events = [] + + log.debug('found %d buddy specific events', len(buddy_events)) + if buddy_events: + fired = True + firetopics(topics, buddy_events, types = types, cancels = cancels, **info) + + # Then fire "generic" events set for all buddies. + generic_events = notifications.get(None, []) + if generic_events: + fired = True + firetopics(topics, generic_events, + types = types, cancels = cancels, **info) + + if always_show: + # Optionally, "mandatory" reaction types can be specified. + # If we haven't fired them yet, do so. + import gui.notificationview as nview + + # map reaction names -> reaction classes + G = globals() + reactions = set(G[name] for name in always_show) + + # TODO: this block should be factored out and replaced by another call to firetopics + ninfo = nview.get_notification_info() + didFireType = set() + for topic in topics: + for reaction in reactions - types: + if reaction in didFireType: + continue + + args = dictadd(ninfo.get(topic, {}), info) + + def doit(cancels=cancels, reaction=reaction, args=args): + cancellable = reaction()(**args) + if hasattr(cancellable, 'cancel'): + cancels.put(ref(cancellable)) + + didFireType.add(reaction) + wx.CallAfter(doit) + fired = True + + # Call "on_done" if specified. + try: on_done = info['on_done'] + except KeyError: pass + else: + if not fired: + log.info('Calling on_done callback') + with traceguard: on_done() + + # return a delegate lowercased typenames of all event types that were triggered + return cancels + +def firetopics(topics, events, types, cancels, **info): + import gui.notificationview as nview + if types is None: + types = set() + + ninfo = nview.get_notification_info() + + for topic in topics: + for event in events.get(topic, []): + reaction, eventinfo = getReaction(event, topic) + if reaction in types or reaction is None: + continue + + template_info = ninfo.get(topic, {}) + + def doit(cancels = cancels, reaction = reaction, args = dictadd(template_info, info), eventinfo = eventinfo): + cancellable = reaction(**eventinfo)(**args) + if hasattr(cancellable, 'cancel'): + cancels.put(ref(cancellable)) + + + wx.CallAfter(doit) + types.add(reaction) + + return types + +def getReaction(mapping, topic): + mapping = dict(mapping) + reaction = mapping.pop('reaction') + + if isinstance(reaction, basestring): + reaction = globals().get(reaction, None) + + if not reaction in reactions_set: + return None, None + + #TODO: temporary...until we allow customizable soundsets. + if reaction is Sound: + from gui.notifications.sounds import active_soundset, SoundsetException + + try: + soundset = active_soundset() + except SoundsetException: + soundset = {} + try: + sound_filename = soundset[topic] + except KeyError: + log.warning("Sound specified for topic %r but no sound descriptor found in sounds.yaml. using default", topic) + sound_filename = soundset.get('default') + if sound_filename is None: + log.warning("\tNo default found in soundset. No sound will be played") + return None, None + + mapping.update(soundfile = sound_filename) + + return reaction, mapping + + +# +# these object are possible reactions to events occuring. +# +# calling them invokes the event. +# + +class Reaction(object): + + def preview(self): + 'This method is invoked via the GUI as a demonstration.' + pass + + @property + def allowed(self): + 'notifications.enable_%s' + cname = type(self).__name__.lower() + try: + away = profile.status.away + except AttributeError: + return True + else: + return bool(pref('notifications.enable_%s' % cname, True) and + not (away and pref('messaging.when_away.disable_%s' % cname, False))) + +class Sound(Reaction): + 'Play a sound' + + desc = 'Play sound %(filename)s' + + def __init__(self, soundfile): + self.soundfile = soundfile + + def __call__(self, **k): + if not self.allowed: + return + + import gui.native.helpers as helpers + if pref('fullscreen.disable_sounds', False) and helpers.FullscreenApp(): + return + + if path_exists(self.soundfile): + wx.Sound.PlaySound(self.soundfile) + + def preview(self): + self() + + def __repr__(self): + return '' % path(self.filename).basename() + +class Alert(Reaction): + "Display an alert" + + desc = 'Show alert "%(msg)s"' + + def __init__(self, msg): + self.msg = msg + + def __call__(self, **info): + if not self.allowed: + return + wx.MessageBox(self.msg) + +class Popup(Reaction): + 'Show a popup notification' + + @classmethod + def desc(cls, info): + return 'Show a popup notification' + + def __init__(self, sticky = False): + self.sticky = sticky + + def __call__(self, **info): + cpy = vars(self).copy() + cpy.update(info) + + if 'Popup' in (cpy.get('always_show', None) or []): + cpy['always_show'] = True + else: + cpy['always_show'] = False + + if not (self.allowed or cpy.get('always_show', False)): + return + + from gui.toast import popup + return popup(**cpy) + +class ShowContactList(Reaction): + 'Show the contact list' + DEFAULT_DURATION_SEC = 3 + + desc = 'Show the contact list for %(duration)s seconds' + + def __init__(self, duration): + self.duration = duration + + def __call__(self): + print 'do buddylist stuff for', self.duration, 'sec' + + +class LogMessage(Reaction): + 'Log a message to the console' + + def __init__(self, msg): + self.msg = msg + + def __call__(self, **info): + if self.msg.find('%') != -1 and info.get('buddy', None) is not None: + log.info(self.msg, info['buddy']) + else: + log.info(self.msg) + +class StartCommand(Reaction): + 'Start an external command' + + desc = 'Start external command "%(path)s"' + + def __init__(self, path): + self.path = path + + def __call__(self, **info): + os.startfile(self.path) + + def preview(self): self() + + +def get_notification_info(_cache = []): + try: + nots = _cache[0] + except IndexError: + pass + + from gui import skin + mod = importer.yaml_import('notificationview', loadpath = [skin.resourcedir()]) + + nots = _process_notifications_list(mod.__content__) + + _cache.append(nots) + + # allow plugins to add their own notification topics here. + import hooks + hooks.notify('digsby.notifications.get_topics', nots) + + return nots + +def _process_notifications_list(nots_list): + from util.primitives.mapping import odict_from_dictlist + nots = odict_from_dictlist(nots_list) + + ordered_underscores_to_dots(nots) + update_always_fire(nots) + return nots + +def add_notifications(nots_list): + to_update = get_notification_info() + mynots = _process_notifications_list(nots_list) + + for k in mynots: + to_update[k] = mynots[k] + + # Now, keep "error" at the end. + error = to_update.pop('error', None) + if error is not None: + to_update['error'] = error + +def add_default_notifications(not_defaults): + default_notifications[None].update(not_defaults) + +always_fire = {} + +def update_always_fire(nots): + always_fire.clear() + + for name, info in nots.iteritems(): + if 'always_show' in info: + reactions = info.get('always_show') + if isinstance(reactions, basestring): + reactions = [reactions] + + assert all(isinstance(o, basestring) for o in reactions) + always_fire[name] = reactions + +def ordered_underscores_to_dots(d): + 'like above, for odicts' + + ordered_keys = d._keys[:] + + for i, key in enumerate(list(ordered_keys)): + if key and '_' in key: + new_key = key.replace('_', '.') + d[new_key] = d.pop(key) + ordered_keys[i] = new_key + + d._keys = ordered_keys + + +from common.message import StatusUpdateMessage + +class IMWinStatusUpdate(Reaction): + def __init__(self, **info): + self.info = info + + def __call__(self, **info): + self.info.update(info) + + on_done = info.pop('on_done', lambda *a, **k: None) + + from gui.imwin.imhub import on_status + wx.CallAfter(on_status, StatusUpdateMessage(**self.info), on_done) + +# +# these reactions fill the dropdown in the notification editor +# for choosing a new event. +# +reactions = [Popup, Alert, Sound, ShowContactList, StartCommand, IMWinStatusUpdate] + +# Add mac specific Reactions +if 'wxMac' in wx.PlatformInfo: + + class BounceDockIcon(Reaction): + 'Bounce the Dock icon' + + def __call__(self, **info): + tlw = wx.GetApp().GetTopWindow() + if tlw: + # not really an error, but we can only choose info or error, + # and info doesn't give us the right behavior + tlw.RequestUserAttention(wx.USER_ATTENTION_ERROR) + + reactions.extend([BounceDockIcon]) + +reactions_set = set(reactions) + +# +# DO NOT ADD ENTRIES TO THIS LIST +# please see the note in _incoming_blob_notifications in digsbyprofile.py +# +default_notifications = { + None: {'contact.returnsfromidle': [], + 'email.new': [{'reaction': 'Popup'}], + 'error': [{'reaction': 'Popup'}], + + 'filetransfer.ends': [{'reaction': 'Popup'}], + 'filetransfer.error': [{'reaction': 'Popup'}], + 'filetransfer.request': [{'reaction': 'Popup'}], + 'message.received.background': [{'reaction': 'Popup'}], + 'message.received.initial': [{'reaction': 'Sound'}], + 'message.received.hidden': [{'reaction': 'Popup'}], + 'myspace.alert': [{'reaction': 'Popup'}], + 'myspace.newsfeed': [{'reaction': 'Popup'}], + + # omg do not want + 'twitter.newdirect': [{'reaction':'Popup'}], + 'twitter.newtweet': [{'reaction':'Popup'}], + } +} + + +for topic in ['contact.signon', 'contact.signoff','contact.available', 'contact.away', 'contact.returnsfromidle', 'contact.idle']: + seq = default_notifications[None].setdefault(topic, []) + seq += [{'reaction': 'IMWinStatusUpdate'}] + + +class Notification(SlotsSavable): + pass + + +TOPIC_SEP = '.' + + +@memoize +def _topics_from_string(topic): + _check_topic_string(topic) + + topiclist = topic.split(TOPIC_SEP) + topics = reversed([TOPIC_SEP.join(topiclist[:x]) + for x in xrange(1, len(topiclist)+1)]) + return list(topics) + +def _check_topic_string(topic): + if not isinstance(topic, basestring): + raise TypeError('topic must be a string') + if topic.find('..') != -1: + raise ValueError('consecutive periods not allowed in topic') + if topic.startswith('.') or topic.endswith('.'): + raise ValueError('topic cannot start or end with a topic') + diff --git a/digsby/src/common/oauth_util.py b/digsby/src/common/oauth_util.py new file mode 100644 index 0000000..283df26 --- /dev/null +++ b/digsby/src/common/oauth_util.py @@ -0,0 +1,1012 @@ +import time +import logging +import cgi +import simplejson as json +import lxml.objectify as objectify + +import oauth.oauth as oauth + +import util +import util.net as net +import util.httptools as httptools +import util.Events as Events +import util.callbacks as callbacks + +import common +import common.asynchttp as asynchttp +import common.asynchttp.server as httpserver + +log = logging.getLogger("common.oauth_util") + +class Unauthorized(Exception): + pass + +class UserCancelled(Exception): + pass + +class OAuthConsumerBase(oauth.OAuthConsumer): + KEY = None + SECRET = None + def __init__(self): + oauth.OAuthConsumer.__init__(self, self.KEY, self.SECRET) + +class OAuthRequest(asynchttp.HTTPRequest): + def __init__(self, client, url, data = None, headers = None, method = None, + oauth_url = False, callback_url = None, sign = True, parameters = None, **k): + + params = self.get_params(client, parameters, k.pop('use_default_params', True)) + sig_params = self.get_sig_params(params, data) + + oauth_request = self.create_oauth_request(callback_url, client, url, method, sig_params, data) + + if sign: + self.sign_request(oauth_request, client, data) + + final_method, final_url, final_headers, final_data = self.finalize_fields(oauth_url, data, headers, method, oauth_request) + + asynchttp.HTTPRequest.__init__(self, final_url, final_data, final_headers, method = final_method) + + def get_params(self, client, parameters, use_default_params): + if use_default_params: + params = client.default_params.copy() + else: + params = {} + + if parameters is not None: + params.update(parameters) + + if 'oauth_timestamp' not in params: + server_time_offset = getattr(client, '_server_time_offset', None) + if server_time_offset is not None: + #log.info('adding oauth_timestamp to request') + #params['oauth_timestamp'] = int(time.time() - server_time_offset) + pass + + return params + + def get_sig_params(self, params, data): + sig_params = params.copy() + if data is not None and hasattr(data, 'items'): + sig_params.update(data) + + return sig_params + + def create_oauth_request(self, callback_url, client, url, method, sig_params, data): + if callback_url is None: + oauth_request = oauth.OAuthRequest.from_consumer_and_token(client.consumer, + client.token, + http_url = url, + http_method = method, + parameters = sig_params, + ) + else: + _data = sig_params.copy() + if data: + _data.update(data) + oauth_request = oauth.OAuthRequest.from_token_and_callback(client.token, callback_url, method, url, _data) + + token = oauth_request.parameters.get('oauth_token', None) + if not token: + oauth_request.parameters.pop('oauth_token', None) + + return oauth_request + + def sign_request(self, oauth_request, client, data): + oauth_request.sign_request(client.signature_method, client.consumer, client.token) + + if data is not None: + for k in data: + oauth_request.parameters.pop(k, None) + + def finalize_fields(self, oauth_url, data, headers, method, oauth_request): + if oauth_url: #or method == 'GET': + assert not data, "Can't use data with oauth_url" + assert not headers, "Can't use headers with oauth_url" + assert method in ('GET', None), "Must use GET with oauth_url" + url = oauth_request.to_url() + final_headers = {} + method = None + else: + url = net.UrlQuery(oauth_request.http_url, **oauth_request.get_nonoauth_parameters()) + final_headers = oauth_request.to_header(getattr(self, 'realm', '')) + if headers is not None: + final_headers.update(headers) + + method = oauth_request.http_method + + return method, url, final_headers, data + +class OAuthAPICall(object): + def __init__(self, client, endpoint, **kw): + self.client = client + self.endpoint = endpoint + self.kw = kw + + @callbacks.callsback + def PUT(self, data, callback = None, **kw): + self._request('PUT', data, callback = callback, **kw) + + @callbacks.callsback + def POST(self, data, callback = None, **kw): + self._request('POST', data, callback = callback, **kw) + + @callbacks.callsback + def DELETE(self, callback = None, **kw): + self._request('DELETE', callback = callback, **kw) + + @callbacks.callsback + def GET(self, callback = None, **kw): + self._request('GET', callback = callback, **kw) + + def _normalize_url(self, client, **kw): + if kw.get('OpenSocial', False): + api_base = getattr(client, 'OS_API_BASE', client.API_BASE) + kw['use_default_params'] = False + else: + api_base = client.API_BASE + + url = net.httpjoin(api_base, self.endpoint, keepquery = True) + + format = kw.pop('format', client.DEFAULT_FORMAT) + if url.endswith('.json'): + format = 'json' + elif url.endswith('.xml'): + format = 'xml' + elif url.endswith('.atom'): + format = 'atom' + + kw['format'] = format + + return url, format + + @callbacks.callsback + def _request(self, method, data = None, **kw): + kw.update(self.kw) + client = self.client + callback = kw.pop('callback') + + url, format = self._normalize_url(client, **kw) + + log.debug("%s %s", method, url) + log.debug_s("\tdata = %r", data) + log.debug_s("\t kw = %r", kw) + + if kw.pop('vital', True): + error_handler = self.unauthorized_handler(url, method, data, callback, **kw) + else: + error_handler = callback.error + + client.request(url, + success = self.success_handler(url, method, data, callback, **kw), + error = error_handler, + method = method, + data = data, + **kw) + + def unauthorized_handler(self, url, method, data, callback, **kw): + client = self.client + def auth_error(e): + client.authenticate_error(e) + callback.error(e) + + def unauthorized(e): + log.info("unauthorized: %r", e) + + if not ((getattr(e, 'oauth_problem', None) == 'permission_denied') or + (getattr(e, 'oauth_problem', None) == 'signature_invalid') or + (getattr(e, 'code', 0) == 401 and util.try_this(lambda: e.headers['x-opensocial-error'], None) is not None) or + (getattr(e, 'code', 0) == 401 and util.try_this(lambda: "Invalid digital signature" in e.document.get('statusDescription', ''))) or + (isinstance(e, Unauthorized)) + ): + return callback.error(e) + + etxt = None + if hasattr(e, 'read'): + etxt = e.read() + if not etxt: + etxt = repr(e) + log.error('Error accessing resource %r: %r', self.endpoint, etxt) + + if not client.allow_authenticate(): + return + + client.authenticate_start() + + client.fetch_request_token( + error = auth_error, + success = lambda results: ( + client.request_token_fetched(results), + client.authorize_token( + error = auth_error, + success = lambda results: ( + client.token_authorized(results), + client.fetch_access_token( + error = auth_error, + success = lambda results: ( + client.access_token_fetched(results), + client.request(url, + method = method, data = data, + error = callback.error, + success = lambda resp: callback.success(resp.document), + **kw))))))) + + return unauthorized + + def success_handler(self, url, method, data, callback, **kw): + def success(resp): + log.info("success handler for %r : %r", self.endpoint, getattr(resp, 'code', None)) + if (resp.code // 100) == 2: + try: + callback.success(resp.document) + except Exception as e: + import traceback; traceback.print_exc() + callback.error(e) + else: + if resp.code == 401: + return self.unauthorized_handler(url, method, data, callback, **kw)(resp) + raise Exception(resp) + + return success + +def load_document(resp, expected): + data = resp.content + + #log.info_s('Loading document: %r', data) + if not data: + resp.document = None + return + + ctype = resp.headers.get('Content-Type', 'application/%s; charset=utf-8' % expected) + ctype, params = cgi.parse_header(ctype) + + cenc = resp.headers.get('Content-Encoding', 'identity') + + if cenc != 'identity': + try: + # this will probably only work with gzip... + data = data.decode(cenc) + except Exception, e: + log.error('Error decoding data with Content-Encoding=%r. Error = %r, data[:500] = %r', cenc, e, data[:500]) + + charset = params.get('charset', 'utf-8') + + if ctype != expected: + log.warning('was expecting content-type=%r, got content-type=%r', expected, ctype) + + if hasattr(data, 'read'): + data = data.read() + + if ctype in ('text/xml', 'application/xml'): + document = objectify.fromstring(data) + elif ctype == 'application/json': + document = json.loads(data, object_hook = util.Storage) + elif ctype == 'application/x-www-form-urlencoded': + document = net.WebFormData.parse(data) + elif ctype == 'application/None': + log.warning("Attempting json decode for content-type = %r for data = %r.", ctype, data) + document = json.loads(data, object_hook = util.Storage) + else: + log.warning('Unknown data type %r for data: %r', ctype, data) + document = data.decode(charset) + + return document + +class OAuthClientBase(oauth.OAuthClient, httptools.WebScraper, Events.EventMixin): + DEFAULT_FORMAT = None + API_BASE = None + + urls = {} + + events = Events.EventMixin.events | set(( + 'authenticate_start', + 'authenticate_done', + 'authenticate_error', + + 'requests_complete', + + 'token_expired', + + 'open_oauth_page', + )) + + APICallFactory = OAuthAPICall + ConsumerFactory = None + RequestFactory = None + + def __init__(self, username, token, consumer = None, signature_method = None, default_params = None, time_offset = None): + Events.EventMixin.__init__(self) + self.pending_auth = False + self.username = username + self._server_time_offset = time_offset + if default_params is None: + default_params = {} + + if signature_method is None: + signature_method = 'hmac-sha1' + if consumer is None: + consumer = self.ConsumerFactory() + + if signature_method == "hmac-sha1": + self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1() + else: + raise ValueError('Don\'t know what to do with this signature method: %r', signature_method) + + if token: + try: + token = oauth.OAuthToken.from_string(token) + except oauth.OAuthError: + import traceback; traceback.print_exc() + token = None + else: + token = None + + self.default_params = default_params + oauth.OAuthClient.__init__(self, consumer, token) + httptools.WebScraper.__init__(self) + + def get_time_offset(self): + return self._server_time_offset + + def done_waiting(self): + log.info('done waiting') + self.event('requests_complete') + + def allow_authenticate(self): + return not self.pending_auth + + @Events.event + def authenticate_start(self): + log.info("authenticate start") + self.pending_auth = True + self.token = oauth.OAuthToken('', '') + + @Events.event + def authenticate_error(self, error): + log.info("authenticate error") + self.pending_auth = False + return error + + @Events.event + def authenticate_done(self): + log.info("authenticate done") + self.pending_auth = False + + @callbacks.callsback + def fetch_request_token(self, callback = None): + self.request('request_token', + callback = callback) + + def request_token_fetched(self, results): + self.token = results.get('token') + + def preprocess_resp_default(self, name, resp, **req_options): + code = resp.code + + server_time = net.http_date_to_timestamp(resp.headers.get('Date', None)) + if server_time is not None: + time_diff = int(time.time() - server_time) + self._server_time_offset = time_diff + log.info("Got server time offset: %r", time_diff) + + if self._server_time_offset is None: + log.error("Server does not report Date header.") + + log.info('response for %r: code = %r, headers = %r', name, code, str(resp.headers)) + log.debug_s('\tcontent: %s', repr(resp.content)[:5000]) + document = load_document(resp, expected = req_options.get('format')) + + if code not in (200, 201, 204, 401): + # check for oauth error, parse it out and raise as an exception + raise Exception(document) + + resp.document = document + return resp + + def preprocess_resp_request_token(self, name, resp, **req_options): + return resp + + def handle_success_default(self, name, resp, **req_options): + pass + + def handle_success_request_token(self, name, resp, **req_options): + content = resp.content + content = content.replace(', ', '&') + return dict(token = oauth.OAuthToken.from_string(content)) + + @callbacks.callsback + def fetch_access_token(self, callback = None): + self.request('access_token', callback = callback) + + def access_token_fetched(self, results): + log.info("Access token acquired") + self.token = results.get('token') + self.authenticate_done() + + def handle_success_access_token(self, name, resp, **req_options): + content = resp.content + content = content.replace(', ', '&') + return dict(token = oauth.OAuthToken.from_string(content)) + + @callbacks.callsback + def authorize_token(self, callback = None): + callback_url_function = lambda cb_url: self.RequestFactory( + self.urls['authorization'], + callback_url = cb_url, + oauth_url = True, + sign = False, + #params = {'myspaceid.permissions' : '|'.join(self.required_permissions)} + ).get_full_url() + + self.event("open_oauth_page", callback_url_function, callback) + + def token_authorized(self, results): + self.token.key = results.get('key') + + def build_request_default(self, name, **req_options): + link = self.urls.get(name, name) + + if callable(link): + link = link() + + link = link.format(**req_options) + + req = self.RequestFactory(link, **req_options) + return req + + def call(self, endpoint, **kw): + return self.APICallFactory(self, endpoint, **kw) + +class OAuthProtocolBase(Events.EventMixin): + events = Events.EventMixin.events | set(( + 'on_connect', + + 'authenticate_pre', + 'authenticate_post', + 'authenticate_error', + + 'update_pre', + 'update_post', + 'update_error', + + 'need_cache', + 'open_oauth_page', + 'openurl', + + 'on_feedinvalidated', + 'need_permissions', + )) + + def __init__(self, username, token): + Events.EventMixin.__init__(self) + self._dirty = True + self._authenticating = False + + self.username = username + self.token = token + + self.api = None + + def connect(self): + self.init_api() + self.event('on_connect') + + def init_api(self): + sto = None + if self.api is not None: + sto = getattr(self.api, '_server_time_offset', None) + self.uninit_api() + + self._create_api() + self.api._server_time_offset = sto + self.api.bind_event('requests_complete', self.check_update_complete) + self.api.bind_event('authenticate_start', self.authenticate_pre) + self.api.bind_event('authenticate_done', self.authenticate_post) + self.api.bind_event('authenticate_error', self.authenticate_error) + self.api.bind_event('open_oauth_page', self.open_oauth_page) + + def uninit_api(self): + api, self.api = self.api, None + api.unbind('requests_complete', self.check_update_complete) + api.unbind('authenticate_start', self.authenticate_pre) + api.unbind('authenticate_done', self.authenticate_post) + api.unbind('authenticate_error', self.authenticate_error) + api.unbind('open_oauth_page', self.open_oauth_page) + + @Events.event + def open_oauth_page(self, callback_url_function, callback): + return callback_url_function, callback + + def get_oauth_token(self): + if self.token is None: + return None + return self.token.to_string() + + @Events.event + def authenticate_pre(self): + self._authenticating = True + + @Events.event + def authenticate_error(self, e = None): + self._authenticating = False + + @Events.event + def authenticate_post(self): + self._authenticating = False + self.token = self.api.token + log.info("Getting oauth token from API for %r: %r", self, self.token) + + def clear_oauth_token(self): + self.api.token = self.token = None + + def set_dirty(self): + self._dirty = True + + @Events.event + def update_post(self): + self.apply_pending() + if not self._dirty: + log.info('\tno changes.') + self.pending.clear() + + @Events.event + def update_error(self, e): + if getattr(e, 'code', None) == 404: + return Events.Veto + log.info('Error updating: %r', e) + return e + + def _feed_invalidated(self): + self._dirty = True + self.event('on_feedinvalidated') + +class OAuthAccountBase(Events.EventMixin): + + def __init__(self, **k): + Events.EventMixin.__init__(self) + self.oauth_token = k.get('oauth_token', None) + self._auth = None + self._has_updated = False + self._forcing_login = False + + + def get_options(self): + try: + get_opts = super(OAuthAccountBase, self).get_options + except AttributeError: + opts = {} + else: + opts = get_opts() + + opts['oauth_token'] = self.oauth_token + return opts + + def _get_auth_class(self, prefkey): + auth_class_name = common.pref(prefkey, default = None) + if auth_class_name == 'internal-openid': + AuthClass = InternalBrowserAuthenticatorOpenID + elif auth_class_name == 'internal': + AuthClass = InternalBrowserAuthenticator + elif auth_class_name == 'browser': + AuthClass = UserBrowserAuthenticator + elif auth_class_name == 'auto': + AuthClass = AutoAuthenticator + else: + AuthClass = self.AuthClass + + return AuthClass + + def bind_events(self): + log.info("bind_events: %r / %r", self, self.connection) + + self.connection.bind('on_connect', self._on_protocol_connect) + + self.connection.bind('authenticate_pre', self._authenticate_pre) + self.connection.bind('authenticate_post', self._authenticate_post) + self.connection.bind('authenticate_error', self._authenticate_error) + + self.connection.bind('update_pre', self._update_pre) + self.connection.bind('update_post', self._update_post) + self.connection.bind('update_error', self._update_error) + + self.connection.bind('need_cache', self._cache_data) + self.connection.bind('open_oauth_page', self._on_oauth_url) + + self.connection.bind('openurl', self.openurl) + + self.connection.bind('on_feedinvalidated', self.on_feed_invalidated) + self.connection.bind('need_permissions', self.initiate_login) + + return self.connection + + def unbind_events(self): + conn = self.connection + log.info("unbind_events: %r / %r", self, conn) + + if conn is None: + return + + conn.unbind('on_connect', self._on_protocol_connect) + + conn.unbind('authenticate_pre', self._authenticate_pre) + conn.unbind('authenticate_post', self._authenticate_post) + conn.unbind('authenticate_error', self._authenticate_error) + + conn.unbind('update_pre', self._update_pre) + conn.unbind('update_post', self._update_post) + conn.unbind('update_error', self._update_error) + + conn.unbind('need_cache', self._cache_data) + conn.unbind('open_oauth_page', self._on_oauth_url) + + conn.unbind('openurl', self.openurl) + + conn.unbind('on_feedinvalidated', self.on_feed_invalidated) + conn.unbind('need_permissions', self.initiate_login) + + return conn + + def clear_oauth_token(self): + if self.connection is not None: + self.connection.clear_oauth_token() + self.save_oauth_token() + + def save_oauth_token(self): + self._on_auth_done() + if self.connection is not None: + oauth_token = self.connection.get_oauth_token() + self.update_info(oauth_token = oauth_token) + + def _authenticate_pre(self): + if not self._forcing_login: + self.change_state(self.Statuses.AUTHENTICATING) + + def _authenticate_post(self): + self.save_oauth_token() + if not self._forcing_login: + self.change_state(self.Statuses.CONNECTING) + + def _authenticate_error(self, e): + log.info('authentication error: %r', e) + self._autherror = e + import oauth.oauth as oauth + if isinstance(e, (oauth.OAuthError, asynchttp.httptypes.HTTPResponse)): + if isinstance(e, oauth.OAuthError): + data = getattr(e, 'oauth_data', '') + details = net.WebFormData.parse(data) + else: + data = e.read() + if data: + details = dict(x.strip().split('=', 1) for x in data.split(',')) + else: + details = {} + + return self._handle_oauth_error(details) + + self.clear_oauth_token() + self.Disconnect(self.Reasons.BAD_PASSWORD) + + def _update_pre(self): + if self._has_updated or self._forcing_login: + st = self.Statuses.CHECKING + else: + st = self.Statuses.CONNECTING + + self.change_state(st) + + def _update_post(self): + # the first time that MyspaceProtocol fire on_feedinvalidated, its event handlers are not bound yet. + if self.state != self.Statuses.ONLINE: + self.on_feed_invalidated() + + self.change_state(self.Statuses.ONLINE) + self._has_updated = True + self._forcing_login = False + self._dirty = self.connection._dirty + + def _update_error(self, e): + log.debug("%r got update error: %r", self, e) + if hasattr(e, 'read'): + log.debug_s('\tbody: %r', e.read()) + + if self.state == self.Statuses.OFFLINE: + return + + if self._has_updated: + rsn = self.Reasons.CONN_LOST + else: + rsn = self.Reasons.CONN_FAIL + + self.Disconnect(rsn) + + def _on_oauth_url(self, callback_url_function, callback): + if self._auth is None: + self._auth = self.get_authenticator(callback_url_function) + self._auth.bind('on_done', self._on_auth_done) + else: + self._auth.url_hook = callback_url_function + + self._auth.authenticate(callback = callback) + + def _on_auth_done(self): + auth, self._auth = self._auth, None + if auth is not None: + auth.unbind('on_done', self._on_auth_done) + auth.done() + self._auth = None + + def openurl(self, url): + import wx + wx.CallAfter(wx.GetApp().OpenUrl, url) + + def initiate_login(self, *a, **k): + self._forcing_login = True + self._has_updated = False + self.clear_oauth_token() + self.update_now() + + def _update(self): + if not getattr(self, 'should_update', lambda: True)(): + return + + log.info("calling connection.update") + util.threaded(self.connection.update)() + +class OAuthCallbackHandler(object): + def __init__(self, client, callback): + self.oauth_client = client + self.callback = callback + + def init_handler(self, *a): + return self + + def handle(self, http_client, request): + http_client.push('HTTP/1.1 200 OK\r\n' + 'Connection: close\r\n' + 'Content-Type: text/html\r\n' + '\r\n' + '' + '' + 'You may now close this window' + '' + '' + ) + http_client.close_when_done() + httpserver.shutdown() + + callback, self.callback = self.callback, None + callback(request) + import sys + sys.stderr.flush() + + +def show_browser(url): + import webbrowser + webbrowser.open(url) + print('opened in browser: %s' % url) + +class OAuthenticatorBase(Events.EventMixin): + events = Events.EventMixin.events | set(( + 'on_done', + )) + def __init__(self, username, url_hook, path_to_serve, window_title, watch_url, frame_icon_skinkey): + self._watch_url = watch_url + self.serve_path = path_to_serve + self.window_title = window_title + self.username = username + self.url_hook = url_hook + self.frame_icon_skinkey = frame_icon_skinkey + Events.EventMixin.__init__(self) + + @callbacks.callsback + def authenticate(self, callback = None): + return NotImplemented + + def done(self): + try: + self.stop_serving() + except Exception: + pass + self.event('on_done') + + @callbacks.callsback + def serve(self, callback = None): + # Start 1-shot localhost HTTP server, bind to something like + # http://localhost/oauth/myspace/{username} + # when it gets hit, save the new token (in oauth_token variable in the url) + # (also check for oauth_problem) + # call callback.success or callback.error appropriately + def on_oauth_callback(request): + httpserver.stop_serving(path, host, port) + # parse oauth URL query string + data = net.UrlQuery.parse(request.get_full_url()) + query = data.get('query') + oauth_problem = query.get('oauth_problem') + if oauth_problem: + callback.error(Exception(oauth_problem)) + else: + callback.success(query.get('oauth_token')) + + path = self.serve_path + host, port = httpserver.serve(path, OAuthCallbackHandler(self, on_oauth_callback).init_handler, host = 'localhost', port = 0) + callback_url = 'http://localhost:{port}{path}'.format(path = path, port = port) + + self._path = path + self._host = host + self._port = port + + url = self.url_hook(callback_url) + + log.info('oauth url: %r', url) + return url + + def stop_serving(self): + path, host, port = getattr(self, '_path', None), getattr(self, '_host', None), getattr(self, '_port', None) + + if None not in (path, host, port): + self._path = self._host = self._port = None + httpserver.stop_serving(path, host, port) + +class UserBrowserAuthenticator(OAuthenticatorBase): + @callbacks.callsback + def authenticate(self, callback = None): + url = self.serve(callback = callback) + return show_browser(url) + +class AutoAuthenticator(OAuthenticatorBase): + @callbacks.callsback + def authenticate(self, callback = None): + url = self.serve(callback = callback) + # create browser, bind events + # nav to url, submit form with self.account credentials + self._callback = callback + import wx + def _gui_stuff(): + + frame = wx.Frame(None, size = (840, 660), title = self.window_title, name = self.window_title) + from gui.browser.webkit.webkitwindow import WebKitWindow as Browser + frame.CenterOnScreen() + b = Browser(frame) + + frame.Bind(wx.EVT_CLOSE, self.on_close) + b.LoadURL(url) + frame.Show() + + wx.CallAfter(_gui_stuff) + + def on_close(self, e): + self._do_callback('error', Exception("user closed auth window")) + e.Skip() + + def _do_callback(self, which, *a): + cb, self._callback = self._callback, None + if cb is not None: + f = getattr(cb, which, None) + if f is not None: + f(*a) + +class InternalBrowserAuthenticator(OAuthenticatorBase): + frame_shown = False + frame_size = (630, 520) + @callbacks.callsback + def _fallback_authenticate(self, callback = None): + return UserBrowserAuthenticator(self.username, self.url_hook).authenticate(callback = callback) + + @callbacks.callsback + def authenticate(self, callback = None): + log.info('login_gui start') + if self._watch_url is None: + url = self.serve(callback = callback) + args = (url,) + else: + url = self.url_hook(self._watch_url) + args = (url, callback) + import wx + wx.CallAfter(lambda: self._do_gui(*args)) + + def _do_gui(self, url, callback = None): + import wx, wx.webview + import gui.skin as skin + import gui.browser as B + + log.info('login_gui before frame') + if getattr(self, 'frame', None) is not None: + log.info("already have a web frame open!") + return + + self.frame = frame = wx.Frame(None, size = self.frame_size, title = self.window_title, name = self.window_title) + + icon = skin.get(self.frame_icon_skinkey) + if icon is not None: + frame.SetIcon(wx.IconFromBitmap(icon)) + log.info('login_gui before center') + frame.CentreOnScreen() + log.info('login_gui before frame') + + try: + b = B.Browser(frame, url = url, external_links=False) + except Exception: + # some people are getting + # COMError: (-2147024809, 'The parameter is incorrect.', (None, None, None, 0, None)) + # try a fallback with just the browser + from traceback import print_exc + print_exc() + + self.frame.Destroy() + self.frame = None + + # don't use this + ## Fallback to a real browser. + + if callback is not None: + callback.error("Unable to authenticate") + return + + self.success = False + log.info('login_gui after frame') + + def on_close(e): + e.Skip() + if self.frame_shown and not self.success: + # Broswer window closed before successful login. + if callback is not None: + callback.error(UserCancelled("User cancelled.")) + self.frame = None + + log.info('login_gui before bind') + frame.Bind(wx.EVT_CLOSE, on_close) + + b.OnDoc += lambda navurl: self.before_load(b, navurl, callback) + b.OnTitleChanged += lambda e: self.on_title_changed(b, e, callback) + log.info('login_gui end') + wx.CallLater(2000, lambda: setattr(self, 'frame_shown', self.frame.Show() if not getattr(self.frame, 'Shown', True) else False)) + + def on_title_changed(self, browser, event, callback): + pass + + def before_load(self, browser, navurl, callback): + import wx + log.info('before_load: %r', navurl) + + if self._watch_url and navurl.startswith(self._watch_url): + + parsed = net.UrlQuery.parse(navurl) + log.info('parsed url: %r', parsed) + query = parsed.get('query') + + oauth_problem = query.get('oauth_problem') + + if oauth_problem: + if callback is not None: + callback.error(Exception(oauth_problem)) + else: + self.success = True + if callback is not None: + log.info("success calling %r with %r", callback.success, query) + callback.success(query) + + wx.CallAfter(self.frame.Close) + self.frame = None + +class InternalBrowserAuthenticatorOpenID(InternalBrowserAuthenticator): + def before_load(self, navurl, callback): + import wx + log.info('before_load: %r', navurl) + + if navurl.startswith(self._watch_url): + parsed = net.UrlQuery.parse(navurl) + + log.info('parsed url: %r', parsed) + query = parsed.get('query') + + mode = query.get('openid.mode', None) + log.info('\topenid mode is: %r', mode) + + if mode == 'error': + callback.error(Exception(query.get('openid.error'))) + + else: + self.success = True + callback.success(dict(key = query.get('openid.oauth.request_token'))) + + wx.CallAfter(self.frame.Close) + self.frame = None diff --git a/digsby/src/common/protocolmeta.py b/digsby/src/common/protocolmeta.py new file mode 100644 index 0000000..d1ef315 --- /dev/null +++ b/digsby/src/common/protocolmeta.py @@ -0,0 +1,206 @@ +''' + +Information specific to creating accounts on the different protocols. + + +''' + +from util.primitives.mapping import ostorage as oS, Storage as S, dictadd, odict +from util.primitives.structures import enum, oset as set +from util.introspect import import_function +from threading import RLock + +jcrypt_opts = enum('Use TLS if Possible', + 'Require TLS', + 'Force SSL', + 'No Encryption') + +jabber_details = [ + {'type':'enum', + 'store':'encryption', + 'elements':jcrypt_opts}, + {'type':'bool', + 'store':'ignore_ssl_warnings', + 'label':'Ignore SSL Warnings'}, + {'type':'bool', + 'store':'allow_plaintext', + 'label':'Allow Plaintext Login'}, +] + + +jabber_defaults = { + 'encryption': jcrypt_opts['Use TLS if Possible'], + 'ignore_ssl_warnings': False, + 'allow_plaintext': True, + 'autologin': False, + 'server': ('', 5222), + 'block_unknowns': False, + 'hide_os':False, + 'dataproxy':'', +} + +jabber_statuses = [[N_('Available'),N_('Free For Chat')], + [N_('Away'),N_('Do Not Disturb'),N_('Extended Away')]] + +alt_gtalk_opts = [dict(server=('talk.google.com', 443), + do_tls= False, + require_tls= False, + verify_tls_peer= False, + do_ssl = True,), + dict(server=('talk.google.com',80), + do_tls= True, + require_tls= True, + verify_tls_peer= False, + do_ssl = False,), + dict(server=('talk.google.com', 5223), + do_tls= False, + require_tls= False, + verify_tls_peer= False, + do_ssl = True,), + ] + +gtalk_alt_conn_lock = RLock() + +email_defaults = dict( + updatefreq = 300, +) + +smtp_defaults = dict( + smtp_port = 25, + smtp_port_ssl = 465, + smtp_require_ssl = False, + smtp_server = '', + smtp_username = '', + smtp_password = u'', + email_address = '', +) + +pop_defaults = dict(popport = 110, + popport_ssl = 995, + require_ssl = False) + +imap_defaults = dict(imapport = 143, + imapport_ssl = 993, + default_ssl_port = 993, #TODO: replace uses of this with the more standardly named imapport_ssl + require_ssl = False) + +update_mixin_opts = set(['enabled', 'updatefreq', 'alias']) +email_accounts_opts = update_mixin_opts +smtp_acct_opts = email_accounts_opts | set('smtp_server smtp_port smtp_require_ssl smtp_username email_address'.split()) +imap_acct_opts = smtp_acct_opts | set('imapserver imapport require_ssl'.split()) +social_net_opts = update_mixin_opts - set(['updatefreq']) + +protocols = oS() + +protocols.digsby = S( + service_provider = 'dotsyntax', + name = 'Digsby', + name_truncated = 'digs', + path = 'digsby.protocol', + username_desc = 'Digsby ID', + newuser_url = 'http://www.digsby.com', + password_url = 'http://www.digsby.com', + form = 'jabber', + more_details = jabber_details, + defaults = dictadd(jabber_defaults, + {'priority': 5, + 'resource': 'Digsby'}), + statuses = [['Available'],['Away']], + type='im', + allow_contact_add = False, + ) + +def compatible(*protos): + for p1 in protos: + for p2 in protos: + compat = protocols[p1].setdefault('compatible', set()) + compat.add(p2) + +def protos_of_type(s): + return odict((k,v) for (k,v) in protocols.items() if v.get('component_type', v.get('type', None)) == s) + +def is_compatible(one, theother): + ''' + returns True iff one is compatible with theother. this is not necessarily commutative + ''' + return theother in protocols[one].compatible + +def proto_init(proto_name): + return import_function(protocols[proto_name].path) + +improtocols = odict() +emailprotocols = odict() +socialprotocols = odict() + +def proto_update_types(): + global protocols + global improtocols + global emailprotocols + global socialprotocols + improtocols.update( protos_of_type('im') ) + emailprotocols.update( protos_of_type('email') ) + socialprotocols.update( protos_of_type('social') ) + + improtocols.pop('digsby', None) #incase updates do something funny. + + popularity_key = lambda d: d.get('popularity') #default None + + improtocols.sort_values(key=popularity_key, reverse=True) + emailprotocols.sort_values(key=popularity_key, reverse=True) + socialprotocols.sort_values(key=popularity_key, reverse=True) + + global fix_shortname + fix_shortname = dict((v.name_truncated, k) for k,v in protocols.items()) + + # make sure protos can talk to themselves + for name, proto in protocols.items(): + proto['compatible'] = set(proto.setdefault('compatible', ())) + proto['compatible'].add(name) + + # given a service, which services can we talk to? + global SERVICE_MAP + SERVICE_MAP = dict((k, sorted(protocols[k].get('compatible', []), key=(lambda x: x!=k))) for k in protocols) + + # given a buddy service, which protocols can talk to it + global REVERSE_SERVICE_MAP + REVERSE_SERVICE_MAP = RSM = dict() + for k in SERVICE_MAP.keys(): + + for i in SERVICE_MAP[k]: + if i not in RSM: + RSM[i] = [] + if k not in RSM[i]: + RSM[i].append(k) + +proto_update_types() + +_name_exceptions = { + 'msn' :'MSN Messenger', + 'yahoo' :'Yahoo Messenger', + 'ymail' :'Yahoo Mail', + 'aolmail':'AOL Mail', + 'pop' :'POP', + 'imap' :'IMAP', + } +def nice_name_for_proto(proto): + ''' + Get the user-displayable name for a protocol. + This is currently only used by Facebook & MySpace 'achievement' modules for putting the name + of the services on the web (in newsfeeds, etc). + ''' + return _name_exceptions.get(proto, protocols.get(proto, {}).get('name', proto)) + +_icon_exceptions = { + 'imap' :'email', + 'pop' :'email', + 'aolmail':'aol', + 'msim' :'myspace', + + 'tw20' :'twitter', + 'fb20' :'facebook', + } + +_icon_location = "http://img.digsby.com/service/64/%s.png" +def web_icon_url(proto): + return _icon_location % _icon_exceptions.get(proto, proto) + diff --git a/digsby/src/common/proxysockets.py b/digsby/src/common/proxysockets.py new file mode 100644 index 0000000..ebd96ea --- /dev/null +++ b/digsby/src/common/proxysockets.py @@ -0,0 +1,451 @@ +import util +from AsyncSocket import AsyncSocket as asocket # our AsyncSocket +import socks # socks module +from socket import _socket as socket # original python socket module +from functools import wraps + +import sys + +import logging; log = logging.getLogger('proxysockets') + +class ProxyType: + SOCKS4 = socks.PROXY_TYPE_SOCKS4 # 1 + SOCKS5 = socks.PROXY_TYPE_SOCKS5 # 2 + HTTP = socks.PROXY_TYPE_HTTP # 3 + HTTPS = socks.PROXY_TYPE_HTTPS # 4 + +class ProxySocket(asocket): + def __init__(self, proxy, conn, post): + asocket.__init__(self, conn) + + self.callback = None + self.post_negotiate = post + self._negotiating = False + if proxy is None: + proxy = {} + self._proxyinfo = proxy.copy() + self.mid = (self._proxyinfo.get('addr', ''), self._proxyinfo.get('port',0)) + self.end = ('',0) + + @util.callbacks.callsback + def connect(self, end, callback = None): + self.callback = callback + self.end = end + self._pconnect() + + def _pconnect(self): + asocket.connect(self, self.mid) + + def handle_close(self): + log.info('ProxySocket.handle_close - calling callback.error') + self.close() + asocket.handle_close(self) + + def close(self): + if getattr(self, 'socket', None) is not None: + asocket.close(self) + + if self.callback is not None: + self.callback, cb = None, self.callback + cb.error() + + def handle_connect(self): + + if not self._negotiating: + log.info('ProxySocket connected. starting negotiation... ') + self._negotiating = True + self._pnegotiate() + + def _pnegotiate(self): + negs = { + ProxyType.HTTP :self._negotiatehttp, + ProxyType.HTTPS :self._negotiatehttps, + ProxyType.SOCKS4 :self._negotiatesocks4, + ProxyType.SOCKS5 :self._negotiatesocks5, + } + + neg = negs.get(self._proxyinfo.get('proxytype', None), self._negotiation_done) + + neg() + + def _negotiatehttp(self): + self._endhost_resolved = None + endhost = self.end[0] + endport = self.end[1] + + if not self._proxyinfo.get('rdns', True): + try: + endhost = self._endhost_resolved = socket.gethostbyname(self.end[0]) + except socket.gaierror: + pass + + authstr = self._httpauthstring() + http_connect = (('CONNECT %s:%d HTTP/1.1\r\n' + 'Host: %s\r\n' + '%s\r\n') % + (endhost, endport, + self.end[0], # use unresolved host here + authstr, + )) + + log.info('ProxySocket._negotiatehttp: sending proxy CONNECT%s. %r:%r', + (" and auth string" if authstr else ''), + self, (endhost, endport)) + + self.push(http_connect) + self.push_handler(self._httpfinish) + self.set_terminator('\r\n\r\n') + + def _httpauthstring(self): + username, password = self._proxyinfo.get('username', None), self._proxyinfo.get('password', None) + + if all((username, password)): + raw = "%s:%s" % (username, password) + auth = 'Basic %s' % ''.join(raw.encode('base-64').strip().split()) + return 'Proxy-Authorization: %s\r\n' % auth + else: + return '' + + def _httpfinish(self, data): + self.pop_handler() + statusline = data.splitlines()[0].split(" ",2) + if statusline[0] not in ("HTTP/1.0","HTTP/1.1"): + log.info("ProxySocket._httpfinish: Bad data from server, disconnecting: (%r)", data) + return self.close() + try: + statuscode = int(statusline[1]) + except ValueError: + log.info('ProxySocket._httpfinish: Got some bad data from the server, disconnecting: %r (%r)', statusline, data) + return self.close() + if statuscode != 200: + log.info('ProxySocket._httpfinish: got HTTPError code %r, disconnecting (%r)', statuscode, data) + return self.close() + + log.info('ProxySocket._httpfinish: success %r', self) + + self.__proxysockname = ("0.0.0.0",0) + self.__proxypeername = (self._endhost_resolved or self.end[0],self.end[1]) + + self._negotiation_done() + + def _negotiation_done(self): + log.info('proxy negotiation complete') + self._proxy_setup = True + self.del_channel() + + self.finish(self.socket, 'handle_connect') + if self.callback is not None: + self.callback.success() + + self.callback = None + self.socket = None + + def finish(self, sck, handler_name): + sck = self.post_negotiate(sck) + sck.connected = True + sck._proxy_setup = True + self.collect_incoming_data = sck.collect_incoming_data + getattr(sck, handler_name)() + + def _negotiatehttps(self): + raise NotImplementedError + + def _negotiatesocks4(self): + from struct import pack + destaddr = self.end[0] + destport = self.end[1] + rresolve = self._proxyinfo.get('rdns', True) + + def zstring(s): + return s + '\0' + + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # Named server. needs to be resolved at some point + if not rresolve: + try: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + except socket.error: + ipaddr = pack('!I', 1) + rresolve = True + + req = pack('!BBH', 4, 1, destport) + ipaddr + username = self._proxyinfo.get('username', '') + + req += zstring(username) + + if rresolve: + req += zstring(destaddr) + + log.info('ProxySocket._negotiatesocks4: sending request') + self.push(req) + self.push_handler(self._socks4finish) + self.set_terminator(8) + + if rresolve: + self.__proxypeername = (socket.inet_ntoa(ipaddr),destport) + else: + self.__proxypeername = (destaddr, destport) + + def _socks4finish(self, data): + from struct import unpack + self.pop_handler() + log.info('ProxySocket._negotiatesocks4: received response') + + try: + null, returncode, port, ip = unpack('!BBH4s', data) + except Exception, e: + log.info('ProxySocket._negotiatesocks4: bad data received from server. original exception is: %r', e) + return self.close() + + ip = socket.inet_ntoa(ip) + + if null != 0: + # Bad data + log.info('ProxySocket._negotiatesocks4: Bad data from server- expected null byte, got %r', null) + return self.close() + if returncode != 0x5A: + # Server returned an error + log.info('ProxySocket._negotiatesocks4: received error code %r', returncode) + return self.close() + + log.info('ProxySocket._negotiatesocks4: success') + self.__proxysockname = (ip,port) + self._negotiation_done() + + def _negotiatesocks5_gen(self): + from struct import pack, unpack + from util import Storage + + destaddr, destport = self.end + uname, password = self._proxyinfo.get('username', ''), self._proxyinfo.get('password', '') + + this = Storage() + this.errors = False + this.authtype = 0 + this.incoming_host_type = 0 + + def pstring(s): + return chr(len(s)) + s + + def single_use_handler(f): + @wraps(f) + def wrapper(data): + self.pop_handler() + return f(data) + return wrapper + + def if_errors_close(f): + def wrapper(*a, **k): + ok = not this.errors + if ok: + try: + return f(*a, **k) + except Exception, e: + import traceback; traceback.print_exc() + log.info('ProxySocket._negotiatesocks5: there was an error calling %r(*%r, **%r). the exception was: %r', + f, a, k, e) + this.errors = True + self.close() + return '',None + else: + log.info('ProxySocket._negotiatesocks5: Previous errors prevented %r(*%r, **%r) from happening', f,a,k) + return '',None + return wrapper + + sender = if_errors_close + def recver(f): + return if_errors_close(single_use_handler(f)) + + @sender + def _sendauthtype(): + if uname and password: + data = pack('!BBBB', 5,2,0,2) + else: + data = pack('!BBB', 5,1,0) + + return data, 2 + + @recver + def _recvauthtype(data): + status, authmethod = unpack('!BB', data) + if status != 5: + raise Exception("Bad data was received from the proxy server: %r", data) + + if authmethod in (0,2): + this.authtype = authmethod + elif authmethod == 0xFF: + this.authtype = None + raise Exception('All auth methods were rejected') + + @sender + def _sendauth(): + return chr(1) + pstring(uname) + pstring(password), 2 + + @recver + def _recvauth(data): + code, status = map(ord, data) + if code != 1: + raise Exception('Was expecting 1, got %r', code) + + if status != 0: + raise Exception('authentication failed. bad uname/pword?') + + @sender + def _sendproxysetup(): + request = pack('!BBB', 5, 1, 0) + rresolve = self._proxyinfo.get('rdns', True) + + this.resolved_ip = None + if rresolve: + try: + this.resolved_ip = socket.inet_aton(destaddr) + except socket.error: + try: + this.resolved_ip = socket.inet_aton(socket.gethostbyname(destaddr)) + except: + rresolve = True + + if rresolve or (this.resolved_ip is None): + request += chr(3) + pstring(destaddr) + else: + request += chr(1) + this.resolved_ip + + request += pack('!H', destport) + + return request, 4 + + @recver + def _recvproxysetup(data): + five, null, _unused, status = map(ord, data) + + if five != 5: + raise Exception('Was expecting 5, got: %r', five) + + if null != 0: + raise Exception('Connection failed, reason code was: %r', null) + + if status in (1, 3): + this.incoming_host_type = status + return + + raise Exception('Unknown error occurred.') + + @sender + def _sendpstringhost1(): + return '', 1 + @recver + def _recvpstringhost1(data): + this.hostlen = ord(data) + @sender + def _sendpstringhost2(): + return '', this.hostlen + @recver + def _recvpstringhost2(data): + this.boundhost = data + + @sender + def _sendiphost(): + return '', 4 + @recver + def _recvhost(data): + this.boundhost = socket.inet_ntoa(data) + + @sender + def _getport(): + return '',2 + @recver + def _recvport(data): + this.boundport, = unpack('!H', data) + + #----------- + + steps = ((_recvauthtype, _sendauthtype, None), + (_recvauth, _sendauth, lambda: this.authtype), + (_recvproxysetup, _sendproxysetup, None), + (_recvhost, _sendiphost, lambda: this.incoming_host_type == 1), + (_recvpstringhost1, _sendpstringhost1, lambda: this.incoming_host_type == 3), + (_recvpstringhost2, _sendpstringhost2, lambda: this.incoming_host_type == 3), + (_recvport, _getport, None), + ) + + for recvr, sendr, check in steps: + if check is None or check(): + recvr((yield sendr)) + + #----------- + self.__proxysockname = (this.boundhost, this.boundport) + if this.resolved_ip is not None: + self.__proxypeername = (socket.inet_ntoa(this.resolved_ip), destport) + else: + self.__proxypeername = (destaddr, destport) + + if not this.errors: + self._negotiation_done() + else: + self.close() + + def _negotiatesocks5(self): + gen = self._negotiatesocks5_gen() + + def handler(data): + log.info('ProxySocket._negotiatesocks5: in =%r', data) + try: + next = gen.send(data) + except StopIteration: + # Done! + return + data, term = next() + + if data and ord(data[0]) == 1: + logdata = '' + else: + logdata = data + log.info('ProxySocket._negotiatesocks5: out=%r, terminator=%r', logdata, term) + + self.push(data); self.push_handler(handler); self.set_terminator(term) + + # Go! + handler(None) + + def __repr__(self): + parentrepr = asocket.__repr__(self).strip('<>') + + return '<%s, fileno=%r>' % (parentrepr, self._fileno) + +def main(): + from tests.testapp import testapp + from AsyncoreThread import end + + import wx; a = testapp('../..') + a.toggle_crust() + + + class HeadSocket(asocket): + def __init__(self, *a, **k): + asocket.__init__(self, *a, **k) + self.set_terminator('\r\n\r\n') + self.push('HEAD / HTTP/1.0\r\n\r\n') + self.push_handler(lambda d: log.info(repr(d))) + + def GetProxyInfo(self): + return {} + + h = HeadSocket() + h.connect(('www.google.com', 80), success=lambda s=h: log.info('success! socket: %r',s)) + + a.MainLoop(); end() + return + +if __name__ == '__main__': + def GetProxyInfo(): + return dict( + proxytype=3, + username='digsby', + password='password', + addr='athena', + port=9999, + ) + + print main() diff --git a/digsby/src/common/scriptengine.py b/digsby/src/common/scriptengine.py new file mode 100644 index 0000000..650b731 --- /dev/null +++ b/digsby/src/common/scriptengine.py @@ -0,0 +1,124 @@ +from __future__ import with_statement +from types import GeneratorType +import collections +import os.path +import sys + +def runfile(filename): + filename = os.path.expanduser(filename) + + with open(filename) as f: + contents = f.read() + runscript(contents, filename) + +def runscript(script, filename): + + script_globals = {} + exec script in script_globals + + try: + main = script_globals['main'] + except KeyError: + raise AssertionError('script %r did not define a main() function' % filename) + + if not hasattr(main, '__call__'): + raise AssertionError('script %r main is not callable') + + exec 'main()' in script_globals + + +class Trampoline(object): + """Manage communications between coroutines""" + + # thanks PEP 342 + + running = False + + def __init__(self): + self.queue = collections.deque() + + def add(self, coroutine): + """Request that a coroutine be executed""" + self.schedule(coroutine) + + def run(self): + result = None + self.running = True + try: + while self.running and self.queue: + func = self.queue.popleft() + result = func() + return result + finally: + self.running = False + + def stop(self): + self.running = False + + def schedule(self, coroutine, stack=(), value=None, *exc): + def resume(): + try: + if exc: + val = coroutine.throw(value, *exc) + else: + val = coroutine.send(value) + except: + if stack: + # send the error back to the "caller" + self.schedule( + stack[0], stack[1], *sys.exc_info() + ) + else: + # Nothing left in this pseudothread to + # handle it, let it propagate to the + # run loop + raise + + print 'val is', val + + if isinstance(val, GeneratorType): + # Yielded to a specific coroutine, push the + # current one on the stack, and call the new + # one with no args + self.schedule(val, (coroutine, stack)) + + elif stack: + # Yielded a result, pop the stack and send the + # value to the caller + self.schedule(stack[0], stack[1], val) + + elif hasattr(val, 'schedule'): + print 'stopping', stack + val.schedule(self) + self.stop() + self.schedule(coroutine, stack) + + # else: this pseudothread has ended + + self.queue.append(resume) + +def main(): + t = Trampoline() + #t.add(api.guilogin(t, 'kevin', 'password')) + #t.run() + #return + + sys.path.append('src/tests/scripts') + import open_im_window + t.add(open_im_window.main()) + + import wx + class MyApp(wx.App): + def __init__(self, trampoline): + self.trampoline = trampoline + wx.App.__init__(self) + + def OnInit(self): + self.trampoline.run() + #wx.Frame(None).Show() + + a = MyApp(t) + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/common/search.py b/digsby/src/common/search.py new file mode 100644 index 0000000..a9917ef --- /dev/null +++ b/digsby/src/common/search.py @@ -0,0 +1,137 @@ +''' +search utils +''' + +def enabled_searches(): + return [e for e in searches if e.enabled] + +class Search(object): + 'base class for all available external searches' + + def __init__(self, name, gui_name, enabled = True): + self.name = name + self.gui_name = gui_name + self.enabled = enabled + + def dict(self, **kws): + d = dict(name=self.name, enabled=self.enabled) + d.update(kws) + return d + +class WebSearch(Search): + 'external searches whose action is to launch a browser URL' + + replstring = '$$query$$' + + def __init__(self, name, gui_name, url): + Search.__init__(self, name, gui_name) + self.url = url + + def search(self, query): + url = self.url.replace(self.replstring, query.encode('url')) + launch_browser(url) + + def __repr__(self): + return '' % (self.gui_name, '' if self.enabled else ' (disabled)') + +# global list of search objects +searches = [] + +def launch_browser(url): + import wx; wx.LaunchDefaultBrowser(url) + +_did_link_prefs = False +def link_prefs(prefs): + global _did_link_prefs + + if _did_link_prefs: + return + + _did_link_prefs = True + prefs.link('search.external', on_external_prefs, + obj=on_external_prefs, callnow=True) + +def on_external_prefs(external): + global searches + + by_name = dict((engine.name, engine) for engine in all_search_engines) + + new_searches = [] + + found_searches = set() + to_remove = set() + for ex in external: + name, enabled = ex.get('name'), ex.get('enabled', False) + found_searches.add(name) + + if name not in by_name: + to_remove.add(name) + continue + + engine = by_name[name] + engine.enabled = enabled + new_searches.append(engine) + + # Add all searches that were in defaults.yaml but not appearing in user prefs + # (for when we add a new search). Maintain original position (will re-order users' + # search list). + for name in by_name: + if name not in found_searches: + v = {'name' : name, + 'enabled': by_name[name].enabled} + + import common + order = common.profile.defaultprefs.get('search.external', []) + if v in order: + default_pos = order.index(v) + else: + default_pos = len(new_searches) + new_searches.insert(default_pos, by_name[name]) + external.append(v) + + # Remove preferences for unknown searches + external[:] = [x for x in external if x.get('name') not in to_remove] + + searches[:] = new_searches + +all_search_engines = [ + WebSearch('google', u'Google', + 'http://search.digsby.com/search.php?q=$$query$$&sa=Search&cx=partner-pub-0874089657416012:7ev7ao6zrit&cof=FORID%3A10&ie=UTF-8#1166'), + + WebSearch('amazon', u'Amazon', + 'http://www.amazon.com/gp/search?ie=UTF8&keywords=$$query$$&tag=digsby-20&index=blended&linkCode=ur2&camp=1789&creative=9325'), + + WebSearch('ebay', u'Ebay', + 'http://rover.ebay.com/rover/1/711-53200-19255-0/1?type=3&campid=5336256582&toolid=10001&referrer=www.digsby.com&customid=&ext=$$query$$&satitle=$$query$$'), + + WebSearch('newegg', u'NewEgg', + 'http://www.newegg.com/Product/ProductList.aspx?Submit=ENE&DEPA=0&Order=BESTMATCH&Description=$$query$$&x=0&y=0'), + + WebSearch('itunes', u'iTunes', + 'http://www.apple.com/search/ipoditunes/?q=$$query$$'), + + WebSearch('twitter', u'Twitter', + 'http://search.twitter.com/search?q=$$query$$'), + + WebSearch('facebook', u'Facebook', + 'http://www.facebook.com/s.php?init=q&q=$$query$$'), + + WebSearch('linkedin', u'LinkedIn', + 'http://www.linkedin.com/search?keywords=$$query$$&sortCriteria=Relevance&proposalType=Y&pplSearchOrigin=ADVS&newnessType=Y&searchLocationType=Y&viewCriteria=1&search='), + + WebSearch('youtube', u'YouTube', + 'http://www.youtube.com/results?search_type=&search_query=$$query$$'), + + WebSearch('wikipedia', u'Wikipedia', + 'http://en.wikipedia.org/wiki/Special:Search?search=$$query$$'), + + WebSearch('technorati', u'Technorati', + 'http://technorati.com/search/$$query$$'), + + WebSearch('oneriot', u'OneRiot', + 'http://www.oneriot.com/search?q=$$query$$&spid=eec0ecbd-1151-4b26-926d-82a155c73372&p=digsby&ssrc=blist'), + + WebSearch('bing', u'Bing', + 'http://www.bing.com/search?q=$$query$$'), +] + diff --git a/digsby/src/common/shaped.py b/digsby/src/common/shaped.py new file mode 100644 index 0000000..74a1479 --- /dev/null +++ b/digsby/src/common/shaped.py @@ -0,0 +1,138 @@ +from common.MultiImage import MultiImage +import yaml +import util + +import wx + + +#---------------------------------------------------------------------- + +class TestFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "Shaped Window", + style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER | + wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP ) + + self.hasShape = False + self.delta = (0,0) + + [self.Bind(e, m) for e,m in [ + (wx.EVT_LEFT_DCLICK, self.OnDoubleClick), + (wx.EVT_LEFT_DOWN, self.OnLeftDown), + (wx.EVT_LEFT_UP, self.OnLeftUp), + (wx.EVT_MOTION, self.OnMouseMove), + (wx.EVT_RIGHT_UP, self.OnExit), + (wx.EVT_PAINT, self.OnPaint), + ]] + + from skins import images as imgmngr + from skins import skins + + f = file("../../res/skins/halloween/skin.yaml") + images = util.to_storage(yaml.load(f)).Images + f.close() + + skins.res_path = "../../res/skins/halloween" + + mimg = MultiImage(images) + + from gui.uberwidgets.UberBar import UberBar as UberBar + from gui.uberwidgets.UberButton import UberButton as UberButton + + self.content = wx.Panel(self) + + innerSizer = wx.BoxSizer(wx.HORIZONTAL) + + + self.bsizer = wx.BoxSizer(wx.VERTICAL) + self.menu = UberBar(self.content) + [self.menu.add(UberButton(self.menu, -1, s)) + for s in 'Digsby Edit Help'.split()] + + self.bsizer.Add(self.menu, 0, wx.EXPAND, 0) + self.content.SetSizer(self.bsizer) + + self.hsizer = wx.BoxSizer(wx.HORIZONTAL) + self.hsizer.Add(self.content, 1, wx.EXPAND | wx.ALL, 140) + self.SetSizer(self.hsizer) + + w, h = 400, 400 + self.SetClientSize( (w, h) ) + dc = wx.ClientDC(self) + destbitmap = wx.EmptyBitmap(w, h) + + temp_dc = wx.MemoryDC(); + temp_dc.SelectObject(destbitmap); + mimg.draw(temp_dc, wx.Rect(0,0,w,h)) + temp_dc.SelectObject(wx.NullBitmap) + destbitmap.SetMask(wx.Mask(destbitmap, wx.BLACK)) + self.bmp=destbitmap + + if wx.Platform != "__WXMAC__": + # wxMac clips the tooltip to the window shape, YUCK!!! + self.SetToolTipString("Right-click to close the window\n" + "Double-click the image to set/unset the window shape") + + if wx.Platform == "__WXGTK__": + # wxGTK requires that the window be created before you can + # set its shape, so delay the call to SetWindowShape until + # this event. + self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape) + else: + # On wxMSW and wxMac the window has already been created, so go for it. + self.SetWindowShape() + + dc.DrawBitmap(destbitmap, 0,0,True) + + + + + def SetWindowShape(self, *evt): + # Use the bitmap's mask to determine the region + r = wx.RegionFromBitmap(self.bmp) + self.hasShape = self.SetShape(r) + + + def OnDoubleClick(self, evt): + if self.hasShape: + self.SetShape(wx.Region()) + self.hasShape = False + else: + self.SetWindowShape() + + + def OnPaint(self, evt): + dc = wx.PaintDC(self) + dc.DrawBitmap(self.bmp, 0,0, True) + + def OnExit(self, evt): + self.Close() + + + def OnLeftDown(self, evt): + self.CaptureMouse() + x, y = self.ClientToScreen(evt.GetPosition()) + originx, originy = self.GetPosition() + dx = x - originx + dy = y - originy + self.delta = ((dx, dy)) + + + def OnLeftUp(self, evt): + if self.HasCapture(): + self.ReleaseMouse() + + + def OnMouseMove(self, evt): + if evt.Dragging() and evt.LeftIsDown(): + x, y = self.ClientToScreen(evt.GetPosition()) + fp = (x - self.delta[0], y - self.delta[1]) + self.Move(fp) + +if __name__ == '__main__': + import sys,os + app = wx.PySimpleApp() + win = TestFrame(None) + win.Show(True) + app.MainLoop() + diff --git a/digsby/src/common/slotssavable.py b/digsby/src/common/slotssavable.py new file mode 100644 index 0000000..20e4335 --- /dev/null +++ b/digsby/src/common/slotssavable.py @@ -0,0 +1,57 @@ +from util.observe import Observable +from util.primitives.funcs import do + +class SlotsSavable(object): + ''' + Prereqs: + + 1) use slots + 2) only store persistent information in slots + 3) child objects stored in slots must also be SlotSavable (or pickleable) + ''' + + def __getstate__(self): + return dict((k, getattr(self, k)) for k in self.__slots__) + + def __setstate__(self, info): + do(setattr(self, key, info.get(key, None)) for key in self.__slots__) + + def __eq__(self, s): + try: + return all(getattr(self, attr) == getattr(s, attr) for attr in self.__slots__) + except Exception: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + val = 0 + for child in [getattr(self, slot) for slot in self.__slots__]: + if isinstance(child, list): + for c in child: + val ^= c.__hash__() + elif isinstance(child, dict): + for k,v in child.iteritems(): + val ^= v.__hash__() + else: + val ^= child.__hash__() + return val + + +class ObservableSlotsSavable(SlotsSavable, Observable): + ''' + Prereqs: + + 1) use slots + 2) only store persistent information in slots + 3) child objects stored in slots must also be SlotSavable (or pickleable) + ''' + def __init__(self): + Observable.__init__(self) + + def __setstate__(self, info): + if not hasattr(self, 'observers'): + Observable.__init__(self) + + return SlotsSavable.__setstate__(self, info) diff --git a/digsby/src/common/sms.py b/digsby/src/common/sms.py new file mode 100644 index 0000000..34855f1 --- /dev/null +++ b/digsby/src/common/sms.py @@ -0,0 +1,33 @@ +from string import digits +digits_set = frozenset(digits) + +SMS_MAX_LENGTH = 135 + +def normalize_sms(sms_number): + ''' + Returns a consistent form for an SMS number. + + Raises ValueError if the sms number is not valid. + ''' + + sms_number = str(sms_number).translate(None, ' ()-.+') + + if not all(s in digits_set for s in sms_number): + raise ValueError('invalid sms number: ' + repr(sms_number)) + + if len(sms_number) == 10: + sms_number = '1' + sms_number + + elif len(sms_number) != 11: + raise ValueError('invalid sms number: ' + repr(sms_number)) + + return sms_number + +def validate_sms(n): + try: + normalize_sms(n) + except ValueError: + return False + else: + return True + diff --git a/digsby/src/common/spelling/__init__.py b/digsby/src/common/spelling/__init__.py new file mode 100644 index 0000000..bc16572 --- /dev/null +++ b/digsby/src/common/spelling/__init__.py @@ -0,0 +1,7 @@ +import sys, os.path as path +sys.path.append(path.join(path.split(__file__)[0], 'aspell')) +del sys, path + + +from pyaspell import Aspell +from spellcheck import spellchecker, SpellChecker, SpellEngine diff --git a/digsby/src/common/spelling/dicts.py b/digsby/src/common/spelling/dicts.py new file mode 100644 index 0000000..21a1cd2 --- /dev/null +++ b/digsby/src/common/spelling/dicts.py @@ -0,0 +1,265 @@ +from __future__ import with_statement, division +from util import soupify, httpjoin, autoassign, httpok +from util.cacheable import urlcacheopen +from path import path +import subprocess +import os + +import tarfile, shlex + + +import logging +log = logging.getLogger('spellchecker') + +class AspellDictionary(object): + def __init__(self, id, name_english, name_native): + autoassign(self, locals()) + + def __repr__(self): + return '<%s id=%s name=%s(%r)>' % (type(self).__name__, self.id, self.name_english,self.name_native) + + @property + def name_description(self): + return self.name_english + +class RemoteAspellDictionary(AspellDictionary): + ROOT = 'http://ftp.gnu.org/gnu/aspell/dict/' + def __init__(self, id, n_e, n_n, dict_dir, dict_path): + AspellDictionary.__init__(self, id, n_e, n_n) + + self.directory = dict_dir + self.package_path = dict_path + self.digest_location = dict_path + '.sig' + + self.sig = None + self.needs_update = True + + def fetch_sig(self, root=None): + ''' + Fetches the .sig file for the remote dictionary file. Returns True if successful, else False. + If successful, sets _needs_update to be True if the file has changed (that is, if it was fetched + from the server and not the local cache). + ''' + if self.sig is None: + response, content = urlcacheopen(self.get_digest_location(root)) + if not response.ok: + return False + + self.sig = content + self.needs_update = not response.fromcache + + return True + + def clear_sig(self, root=None): + ''' + Clear the signature out of the cache. useful if you tried to update a dictionary + but the download failed. + ''' + + def get_package_path(self, root=None): + return httpjoin(root or self.ROOT, self.package_path) + + def get_digest_location(self, root=None): + return self.get_package_path(root) + '.sig' + + def get_directory(self, root=None): + return httpjoin(root or self.ROOT, self.directory) + + def __repr__(self): + return AspellDictionary.__repr__(self)[:-1] + (' url=%s>' % self.package_path) + +class LocalAspellDictionary(AspellDictionary): + def __init__(self, id, n_e, n_n, signame): + AspellDictionary(self, id, n_e, n_n) + self.signame = signame + +def AspellIndexToYaml(root=None, outfile=None): + import syck + + index = _GetAspellIndex(root) + mydict = {} + def RAD_to_dict(rad): + res = {} + if rad.name_native is not None: + res.update(name_native=rad.name_native.encode('utf-8')) + res.update(name_english=rad.name_english.encode('utf-8'), location=rad.package_path.encode('utf-8')) + return res + + for d in index: + mydict[d.id.encode('utf-8')] = RAD_to_dict(d) + + return syck.dump(mydict, outfile) + +def _GetAspellIndex(root=None): + RAD = RemoteAspellDictionary + response, content = urlcacheopen(httpjoin(root or RAD.ROOT,'0index.html'), decode=False) + + if not response.ok: + print 'Unhandled HTTP response code: %r' % response + return () + + soup = soupify(content) + results = {} + for row in soup.find('a', attrs=dict(name='0.50')).findAllNext('tr'): + contents = row.findAll('td') + if len(contents) == 4: + id, name_english, name_native, dictionary_path = contents + id = id.find(href=True) + + if id is None: + continue + + id = id['href'] + if id not in results: + dictionary_path = dictionary_path.find(href=True) + if dictionary_path is None: + continue + dictionary_path = dictionary_path['href'] + + name_english = name_english.renderContents(None).decode('xml') + name_native = name_native.renderContents(None).decode('xml').strip() or None + results[id] = RAD(id, name_english, name_native, id, dictionary_path) + + return results.values() + +def DownloadAllDictionaries(infodict, towhere, root=None): + + if root is None: + root = RemoteAspellDictionary.ROOT + + for id in infodict: + name = infodict[id]['name_english'] + bz2path = infodict[id]['location'] + + localpath = path(towhere) / bz2path + bz2path = httpjoin(root,bz2path) + + localpath = localpath.expand() + + print ('Downloading %s (%s) from %s to %s... ' % (name, id, bz2path, localpath)), + response, content = urlcacheopen(bz2path) + print response.reason + if response.ok: + if not localpath.parent.isdir(): + localpath.parent.makedirs() + + with open(localpath, 'wb') as f: + f.write(content) + +def ExtractInfoFiles(localroot): + + localroot = path(localroot) + for bz2path in localroot.walkfiles('*.tar.bz2'): + tar = None + infofile = None + try: + tar = tarfile.open(bz2path, 'r:bz2') + + for member in tar.getmembers(): + mempth = path(member.name) + if mempth.name == 'info': + break + else: + print 'Couldn\'t get "info" from %s' % bz2path + continue + infofile = tar.extractfile(member) + with open(bz2path.parent/'info', 'wb') as f: + f.write(infofile.read()) + finally: + if tar is not None: + tar.close() + if infofile is not None: + infofile.close() + +class AspellInfoShlexer(shlex.shlex): + def __init__(self, *a, **k): + if 'posix' not in k: + k['posix'] = True + shlex.shlex.__init__(self, *a, **k) + self.whitespace_split = True + + def get_token(self): + curlineno = self.lineno + token = shlex.shlex.get_token(self) + if self.lineno != curlineno: + self.pushback.append('\n') + return token + +def GetAliases(root, id): + aliases = [] + root = path(root) + with open(root/id/'info') as info: + for line in info: + line = line.rstrip() # chomp newline + if line.startswith('alias'): + _alias, rest = line.split(None, 1) + + rest = rest.split() + alias_id, alias_names = rest[0], rest[1:] + if alias_id != id: + aliases.append((alias_id, alias_names)) + + return aliases + +def GetAllAliases(root): + root = path(root) + aliases = {} + for lang in root.walkdirs(): + lang = lang.name + aliases = GetAliases(root, lang) + if aliases: + print lang, aliases + + return aliases + +def _getshlexer(fpath): + f = open(fpath, 'rb') + return AspellInfoShlexer(f) + +def MakeDigsbyDict(lang='en', local_dir=None): + """ + Create a custom wordlist of common IM slang + """ + digsby_words = set(('asap', 'asl', 'bbs', 'bff', 'brb', 'btw', 'cya', + 'digsby', 'fud', 'fwiw', 'gl', 'ic', 'ily', 'im', + 'imho', 'irl', 'jk', 'lmao', 'lol', 'np', 'oic', + 'omg', 'plz', 'rofl', 'roflmao', 'thx', 'ttyl', + 'ttys', 'u', 'wtf')) + + #local_dir + + dict_file = local_dir / ('digsby-%s.rws' % lang) + cmd = ' '.join(['.\\lib\\aspell\\bin\\aspell.exe', + '--local-data-dir=%s' % subprocess.list2cmdline([local_dir]), #@UndefinedVariable + '--lang=%s' % lang, + 'create', + 'master', + '"%s"' % dict_file]) + + + if os.name == 'nt': + import _subprocess + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = _subprocess.SW_HIDE + else: + startupinfo = None + + from subprocess import Popen, PIPE + proc = Popen(cmd.encode('filesys'), stdout=PIPE, stderr=PIPE, stdin=PIPE, startupinfo=startupinfo) + log.info('Creating Digsby dict in "%s"', lang) + log.info('Executing Command: %s', cmd) + + result = proc.communicate('\n'.join(digsby_words)) + + log.info('Subprocess returned: %s', result) + + return dict_file + +def _main(*a,**k): + MakeDigsbyDict(*a,**k) + + +if __name__ == '__main__': + import sys + _main(sys.argv[1:]) diff --git a/digsby/src/common/spelling/pyaspell.py b/digsby/src/common/spelling/pyaspell.py new file mode 100644 index 0000000..98cf458 --- /dev/null +++ b/digsby/src/common/spelling/pyaspell.py @@ -0,0 +1,435 @@ +# -*- coding: iso-8859-2 -*- +# Aspell interface using ctypes. +# $Date: 2007-04-07 14:27:33 $, $Revision: 1.3 $ +# +# This is straightforward translation of my +# aspell-python, C extension. +# +# License: BSD +# +# author: Wojciech Mu?a +# e-mail: wojciech_mula@poczta.onet.pl +# www : http://wmula.republika.pl/proj/aspell-python/ +# +# TODO: add method to get/change **current** speller's config + +try: + import ctypes + import ctypes.util +except ImportError: + raise ImportError("ctypes library is needed") + + +class AspellError(Exception): pass +class AspellConfigError(AspellError): pass +class AspellSpellerError(AspellError): pass + + +class Aspell(object): + """ + Aspell speller object. Allows to check spelling, get suggested + spelling list, manage user dictionarias, and other. + + Must be closed with 'close' method, or one may experience + problems, like segfaults. + """ + + VERSION = 5 # must be in (5,6) + + if VERSION == 5: + LIBNAME = 'aspell\\bin\\aspell-15' + elif VERSION == 6: + LIBNAME = 'aspell\\bin\\aspell' + def __init__(self, configkeys=None, libname=None, **kwds): + """ + Parameters: + * configkeys - list of configuration parameters; + each element is a pair key & value (both strings) + if None, then default configuration is used + * libname - explicity set aspell library name; + if None then default name is used + """ + if libname is None: + libname = ctypes.util.find_library(self.LIBNAME) + self.__lib = ctypes.CDLL(libname) + self.libname = libname + + # Initialize speller + + # 1. create configuration + config = self.__lib.new_aspell_config() + if config == None: + raise AspellError("Can't create aspell config object") + + # 2. parse configkeys arg. + if configkeys: + assert type(configkeys) in (tuple, list, dict), "Tuple, list, or dict expected" + if hasattr(configkeys, 'items'): + configkeys = configkeys.items() + + if len(configkeys) == 2 and \ + type(configkeys[0]) is str and \ + type(configkeys[1]) is str: + configkeys = [configkeys] + + configkeys = dict(configkeys) + else: + configkeys = {} + + configkeys.update(kwds) + configkeys = configkeys.items() + + for key, value in configkeys: + assert type(key) is str, "Key must be string: %r" % key + assert type(value) is str, "Value must be string: %r" % value + if not self.__lib.aspell_config_replace(config, key, value): + raise self._aspell_config_error(config) + + # 3. create speller + possible_error = self.__lib.new_aspell_speller(config) + self.__lib.delete_aspell_config(config) + + errno = self.__lib.aspell_error_number(possible_error) + #errmsg = self.__lib.aspell_error_message(errno) + if errno != 0: + errmsg = ctypes.string_at(self.__lib.aspell_error_message(possible_error)) + self.__lib.delete_aspell_can_have_error(possible_error) + raise AspellError(errno, errmsg) + + self.__speller = self.__lib.to_aspell_speller(possible_error) + + def check(self, word): + """ + Check if word is present in main, personal or session + dictionary. Boolean value is returned + """ + if type(word) is str: + return bool( + self.__lib.aspell_speller_check( + self.__speller, + word, + len(word) + )) + else: + raise TypeError("String expeced") + + + def suggest(self, word): + """ + Return list of spelling suggestions of given word. + Works even if word is correct. + """ + if type(word) is str: + return self._aspellwordlist( + self.__lib.aspell_speller_suggest( + self.__speller, + word, + len(word) + )) + else: + raise TypeError("String expeced") + + + def personal_dict(self, word=None): + """ + Aspell's personal dictionary is a user defined, persistent + list of word (saved in certain file). + + If 'word' is not given, then method returns list of words stored in + dict. If 'word' is given, then is added to personal dict. New words + are not saved automatically, method 'save_all' have to be call. + """ + if word is not None: + # add new word + assert type(word) is str, "String expeced" + self.__lib.aspell_speller_add_to_personal( + self.__speller, + word, + len(word) + ) + self._aspell_check_error() + else: + # return list of words from personal dictionary + return self._aspellwordlist( + self.__lib.aspell_speller_personal_word_list(self.__speller) + ) + + + def session_dict(self, word=None, clear=False): + """ + Aspell's session dictionary is a user defined, volatile + list of word, that is destroyed with aspell object. + + If 'word' is None, then list of words from session dictionary + is returned. If 'word' is present, then is added to dict. + If 'clear' is True, then session dictionary is cleared. + """ + if clear: + self.__lib.aspell_speller_clear_session(self.__speller) + self._aspell_check_error() + return + + + if word is not None: + # add new word + assert type(word) is str, "String expeced" + self.__lib.aspell_speller_add_to_session( + self.__speller, + word, + len(word) + ) + self._aspell_check_error() + else: + # return list of words from personal dictionary + return self._aspellwordlist( + self.__lib.aspell_speller_session_word_list(self.__speller) + ) + + + def add_replacement_pair(self, misspelled, correct): + """ + Add replacement pair, i.e. pair of misspelled and correct + word. It affects on order of words appear on list returned + by 'suggest' method. + """ + assert type(misspelled) is str, "String is required" + assert type(correct) is str, "String is required" + + self.__lib.aspell_speller_store_replacement( + self.__speller, + misspelled, + len(misspelled), + correct, + len(correct) + ) + self._aspell_check_error() + + + def save_all(self): + """ + Saves all words added to personal or session dictionary to + the apell's defined file. + """ + self.__lib.aspell_speller_save_all_word_lists(self.__speller) + self._aspell_check_error() + + + def configkeys(self): + """ + Returns list of all available config keys that can be passed + to contructor. + + List contains a 3-tuples: + 1. key name + 2. default value of type: + * bool + * int + * string + * list of string + 3. short description + if None, then this key is undocumented is should not + be used, unless one know what really do + """ + + config = self.__lib.aspell_speller_config(self.__speller) + if config is None: + raise AspellConfigError("Can't get speller's config") + + keys_enum = self.__lib.aspell_config_possible_elements(config, 1) + if keys_enum is None: + raise AspellError("Can't get list of config keys") + + class KeyInfo(ctypes.Structure): + _fields_ = [ + ("name", ctypes.c_char_p), + ("type", ctypes.c_int), + ("default", ctypes.c_char_p), + ("desc", ctypes.c_char_p), + ("flags", ctypes.c_int), + ("other_data", ctypes.c_int), + ] + + key_next = self.__lib.aspell_key_info_enumeration_next + key_next.restype = ctypes.POINTER(KeyInfo) + + list = [] + while True: + key_info = key_next(keys_enum) + if not key_info: + break + else: + key_info = key_info.contents + + if key_info.type == 0: + # string + list.append(( + key_info.name, + key_info.default, + key_info.desc, + )) + + elif key_info.type == 1: + # integer + list.append(( + key_info.name, + int(key_info.default), + key_info.desc, + )) + elif key_info.type == 2: + # boolean + if key_info.default.lower() == 'true': + list.append(( + key_info.name, + True, + key_info.desc, + )) + else: + list.append(( + key_info.name, + False, + key_info.desc, + )) + elif key_info.type == 3: + # list + list.append(( + key_info.name, + key_info.default.split(), + key_info.desc, + )) + + self.__lib.delete_aspell_key_info_enumeration(keys_enum) + return list + + def reset_cache(self): + return self._reload_lib() #self.__lib.aspell_reset_cache() + + def close(self): + """ + Close aspell speller object. + """ + self.__lib.delete_aspell_speller(self.__speller) + + def _reload_lib(self): + from _ctypes import LoadLibrary as _LoadLibrary, FreeLibrary as _FreeLibrary + if self.__lib._handle != 0: + _FreeLibrary(self.__lib._handle) + del self.__lib + self.__lib = ctypes.CDLL(self.libname) + + # XXX: internal function, do not call directly + def _aspellwordlist(self, wordlist_id): + """ + XXX: internal function + + Converts aspell list into python list. + """ + elements = self.__lib.aspell_word_list_elements(wordlist_id) + list = [] + while True: + wordptr = self.__lib.aspell_string_enumeration_next(elements) + if not wordptr: + break + else: + word = ctypes.c_char_p(wordptr) + list.append(word.value) + + self.__lib.delete_aspell_string_enumeration(elements) + return list + + + def _aspell_config_error(self, config): + """ + XXX: internal function + + Raise excpetion if operation of speller config + caused an error. Additionaly destroy config object. + """ + # make exception object & copy error msg + exc = AspellConfigError( + ctypes.c_char_p( + self.__lib.aspell_config_error_message(config) + ).value + ) + + # then destroy config objcet + self.__lib.delete_aspell_config(config) + + # and then raise exception + raise exc + + + def _aspell_check_error(self): + """ + XXX: internal function + + Raise exception if previous speller operation + caused an error. + """ + if self.__lib.aspell_speller_error(self.__speller) != 0: + msg = self.__lib.aspell_speller_error_message(self.__speller) + raise AspellSpellerError(ctypes.string_at(msg)) +#class + +_test_basic = '''\ +>>> a = Aspell(('lang', 'en')) +>>> a.check("when") +True +>>> set(a.suggest("wehn")) == set(['when', 'wen', 'wean', 'ween', 'Wehr', 'whens', 'hen']) +True +>>> a.add_replacement_pair("wehn", "ween") +>>> set(a.suggest("wehn")) == set(['ween', 'when', 'wen', 'wean', 'Wehr', 'whens', 'hen']) +True +>>> a.session_dict() == [] +True +>>> a.check("pyaspell") +False +>>> a.session_dict("pyaspell") +>>> a.session_dict() == ['pyaspell'] +True +>>> a.check("pyaspell") +True +>>> a.session_dict(clear=True) +>>> a.session_dict() +[] +>>> a.close()''' +_test_locales = '''\ +>>> # Locales +>>> a1 = Aspell(('lang', 'en-us')) +>>> a2 = Aspell(('lang', 'en-gb')) +>>> (a1.check('localize'), a2.check('localize')) == (True, False) +True +>>> (a1.check('localize'), a2.check('localize')) == (True, False) +True +>>> (a1.check('localise'), a2.check('localise')) == (False, True) +True +>>> (a1.suggest('localise')[0], a2.suggest('localize')[0]) == ('localize', 'localise') +True +>>> a1.close() +>>> a2.close()''' +_test_case = '''\ +>>> # Case sensitivity +>>> a1 = Aspell(('ignore-case','')) +>>> a2 = Aspell(('dont-ignore-case', '')) +>>> (a1.check('steve'), a2.check('steve')) == (True, False) +True +>>> (a1.check('Steve'), a2.check('Steve')) == (True, True) +True +>>> a1.close() +>>> a2.close()''' + +__test__ = dict( + basic=_test_basic, + locales = _test_locales, + case = _test_case, + ) + +def _test(): + import doctest + doctest.testmod(verbose=True) + + +if __name__ == '__main__': + #_test() + Aspell(('lang', 'fake')) + +# vim: ts=4 sw=4 diff --git a/digsby/src/common/spelling/spellcheck.py b/digsby/src/common/spelling/spellcheck.py new file mode 100644 index 0000000..6e0bfbf --- /dev/null +++ b/digsby/src/common/spelling/spellcheck.py @@ -0,0 +1,717 @@ +from __future__ import with_statement +from pyaspell import Aspell +from util.net import isurl +from common.spelling.dicts import MakeDigsbyDict +from common.filetransfer import ManualFileTransfer +from common import profile, pref + +from common.notifications import fire + +objget = object.__getattribute__ + +import wx, os +import syck +import tarfile +import subprocess, _subprocess +from path import path + +from util import callsback, threaded, autoassign, program_dir +import stdpaths +from common import setpref +from traceback import print_exc + +import logging +log = logging.getLogger('spellchecker') + +#L_O_G = log + +ASPELLBINDIR = (program_dir() /'lib' / Aspell.LIBNAME).parent + +from subprocess import Popen, PIPE, CalledProcessError + +ASPELL_CMD = './lib/aspell/bin/aspell %s -a --ignore-case' +ASPELL_OPT = '--%s="%s"' +ASPELL_DFT = ASPELL_CMD % '--lang=en --encoding=utf-8 --keyboard=standard --sugMode=normal' + +def FormAspellCommand(parameters): + + options = ' '.join([ASPELL_OPT %(key,parameters[key].replace('\\','/')) for key in parameters]) + cmd = ASPELL_CMD % options + + return cmd + + +class NullSpellEngine(object): + """ + Fake SpellEngine for when there is none + """ + lang = None + def check(self, word): + return True + def suggest(self, word): + return [] + def kill(self): + pass + def add(self): + pass + +class SpellEngine(object): + """ + Wraps a asepell process so the SpellCheck class can treat it as an object + """ + def __init__(self, parameters = None): + + self.vLog = pref('messaging.spellcheck.verbose_log', default=False) + + if parameters: + self.lang = parameters['lang'] + self.cmd = FormAspellCommand(parameters) + else: + self.lang = 'en' + self.cmd = ASPELL_DFT + + self.start() + + + + def __nonzero__(self): + """ + If the process is till running the value of a SpellEnging is True + Otherwise it is False + """ + return self.aspell.poll() is None + + + + def start(self): + """ + Start aspell process + """ + + log.info('Starting aspell process with %s', self.cmd) + + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = _subprocess.SW_HIDE + + self.aspell = Popen(self.cmd.encode('filesys'), stdin=PIPE, stdout=PIPE, stderr=PIPE, startupinfo=startupinfo) + + exitCode = self.aspell.poll() + if exitCode != None: + log.error('Aspell failed to start with exit code %s with comand: \n%s', exitCode, self.cmd) + + startupstring = self.aspell.stdout.readline() + if startupstring == '': + log.error('Aspell failed to start and is exiting') + raise CalledProcessError(0,'ZOMG') + + def kill(self): + """ + Kill the aspell process + """ + + log.info('Killing Aspell') + self.aspell.stdin.close() + + def add(self, word): + """ + Add a word to the aspell dictionary and save it to disk + """ + + self.resuscitate() + + #L_O_G.info('Adding "%s" to dictionary', word) + + # in aspell &word saves word in the personal dictionary, and sending # saves the changes to disk + try: + self.aspell.stdin.write('*%s\n#\n' % word) + except: + log.error('Failed communicating to aspell process, poll result: %s;', self.aspell.poll()) + + def resuscitate(self): + """ + Starts a new aspell process if the process is dead + """ + + aspellIsDead = self.aspell.poll() is not None + if aspellIsDead: + log.info('Resuscitating aspell') + self.start() + + def suggest(self, word): + """ + Return a list of words that are possible corrections to word if word is misspelled + """ + + self.resuscitate() + + #L_O_G.info('Looking up suggestions for "%s"', word) + + try: + self.aspell.stdin.write(('$$cs sug-mode,normal\n')) #switch mode to normal + self.aspell.stdin.write(('^%s\n' % word)) #prepend ^ to the word to make sure it's checked and not treated as a command + self.aspell.stdin.write(('$$cs sug-mode,ultra\n')) #switch mode back to ultra + except IOError: + log.error('Failed communicating to aspell process, poll result: %s;', self.aspell.poll()) + return [] + + output = self.aspell.stdout.readline() + + + #return an empty list if the word is correct(*) or no suggestions(#) + if not output or output[0] in '*#': + while output != '\r\n' and output != '': + output = self.aspell.stdout.readline() + return [] + + # expected format of aspell responce: + # & [original] [count] [offset]: [miss], [miss], ... + cutindex = output.find(':') + 2 + output = output[cutindex:].strip() + + suggestions = output.split(', ') + + #L_O_G.info('Suggested %s', suggestions) + + #flush stdout + while output != '\r\n' and output != '': + #L_O_G.info('Output %s', output) + output = self.aspell.stdout.readline() + + return suggestions + + def check(self, word): + """ + Return True if the word is correctly spelled False otherwise + """ + + self.resuscitate() + + + #L_O_G.info('Checking %s...;', word) + + try: + self.aspell.stdin.write(('^%s\n' % word)) + except IOError: + log.error('Failed communicating to aspell process, poll result: %s;', self.aspell.poll()) + return True + + output = self.aspell.stdout.readline() + if not output: + log.error('Aspell is likely dead, empty string read from stdout, poll result: %s;', self.aspell.poll()) + return True + + #Correct spelling is signified by a '*' + correct = output[0] == '*' + + #L_O_G.info('Checked. %s is %s;', word,'OK' if correct else 'INCORRECT') + + #flush stdout + while output != '\r\n': + output = self.aspell.stdout.readline() + + return correct + +def LocalAspellDataDir(more = ''): + """ + Build a return data directory + """ + return (stdpaths.userlocaldata / ('aspell%s'%Aspell.VERSION) / 'dict' / more) #@UndefinedVariable + +class SpellChecker(object): + + def __init__(self, lang_override = None): + + #L_O_G.info('init spellchecker') + + self.spellengine = None + self.lang = None + self._need_to_download = None + self.currentDownloads = set() + self.expectedNext = None + + # load YAML file describing the dictionaries + + filename = program_dir() / 'res' / ('dictionaries-%s.yaml' % Aspell.VERSION) + try: + with open(filename) as f: + self.dict_info = syck.load(f) + if not isinstance(self.dict_info, dict): + raise ValueError('invalid YAML in %s' % filename) + except Exception: + print_exc() + self.dict_info = {} + + # load an engine using swap engine, if no engine is failed use the NullSpellEngine + if not self.SwapEngine(lang_override): + self.spellengine = NullSpellEngine() + + profile.prefs.add_observer(self.on_prefs_change, #@UndefinedVariable + 'messaging.spellcheck.enabled', + 'messaging.spellcheck.engineoptions.lang', + 'messaging.spellcheck.engineoptions.encoding', + 'messaging.spellcheck.engineoptions.keyboard', + 'messaging.spellcheck.engineoptions.sug-mode') #@UndefinedVariable + + def CreateEngine(self, lang_override=None): + ''' + Create an Aspell engine from the values in prefs. Optional lang_override allows for creating an engine in a different + language. + + http://aspell.net/man-html/The-Options.html + + TODO: take lots of kwargs and use them to override the options going into the Aspell engine + + Returns the new Aspell object if it was created. + Returns None if the requested language was not found. + + Raises all unknown errors. + ''' + + if (not self._pref('enabled')) or \ + pref('messaging.spellcheck.engineoptions.lang') not in self.dict_info: + return NullSpellEngine() + + #Time to build the args + + #first set of args comes from the prefs + spellprefs = 'lang encoding keyboard'.split() + parameters = dict((str(key), str(pref('messaging.spellcheck.engineoptions.' + key))) for key in spellprefs) + + #so check is fast + parameters['sug-mode'] = 'ultra' + + if lang_override is not None: + parameters['lang'] = lang_override + + lang = parameters['lang'] + + #set the directories + local_dir = LocalAspellDataDir() + parameters['local-data-dir'] = local_dir.encode('filesys') + parameters['add-word-list-path'] = local_dir.encode('filesys') + + home_dir = local_dir / profile.username + if not home_dir.isdir(): + home_dir.makedirs() + parameters['home-dir'] = home_dir.encode('filesys') + + if not lang.startswith('en'): + parameters['dict-dir'] = local_dir.encode('filesys') + + #If the digsby dict for this language doesn't exist, make it, mostly just for english the first time you run it + #other languages should lready have it at this point + digsby_dict_location = local_dir / ('digsby-%s.rws' % lang) + if not digsby_dict_location.isfile(): + try: + MakeDigsbyDict(lang, local_dir) + except CalledProcessError, e: + log.error("failed to create Digsby Dictionary in '%s' at '%s', probable cause: dict not yet downloaded, exception was '%s'", lang, local_dir, e) + return None + + parameters['add-extra-dicts'] = digsby_dict_location.encode('filesys') + + #encode for filesystem + for k,v in parameters.items(): + if isinstance(v, unicode): + parameters[k] = v.encode('filesys') + + try: + speller = SpellEngine(parameters) + except CalledProcessError: + log.error('SpellEngine failed to load, returning None') + speller = None + + return speller + + def __nonzero__(self): + """ + True if aspell is running, false otherwise + """ + return self.aspell.poll() == None + + def on_prefs_change(self, *a, **k): + ''' + This is the function that watches the related prefs. currently we just create a new engine and toss the old one. + ''' + log.info('Spelling prefs changed, switching engines') + self.SwapEngine() + + def SwapEngine(self, lang_override=None, shouldDownloadOnFail = True): + ''' + Toss the old spellengine and create a new one using CreateEngine(). + If creation fails, the last language used is substituted and the '_need_to_download' attribute + is set to the requested language. + + Takes an optional lang_override to create another spell checker. This is passed directly to CreateEngine + Returns True if a new engine was created and False if the old one was retained. + ''' + + #L_O_G.info('SwapEngine') + try: + newengine = self.CreateEngine(lang_override) + except Exception: + log.error('Something just went horribly wrong in CreateEngine...') + print_exc() + return False + + if not newengine and shouldDownloadOnFail: # fail, but download + self._need_to_download = lang_override or self._pref('engineoptions.lang') + wx.CallAfter(self.DownloadDict) + return False + elif newengine: #success + if self.spellengine is not None: + self.spellengine.kill() + self.spellengine = newengine + self.lang = self.spellengine.lang + log.info('Speller switched to %r', self.lang) + + return True + else: #Epic Fail + log.error("Language not loaded but already attempted retrieving it") + return False + + def _pref(self, name, default=sentinel, type=sentinel): + ''' + Convenience method to get a pref prefixed with 'messaging.spellcheck' + ''' + return pref('messaging.spellcheck.'+name, default=default, type=type) + + def _get_encoding(self): + return self._pref('engineoptions.encoding', type=str, default='utf-8') + + def _encode(self, s): + ''' + Encode a string using the encoding determined by the user's spellcheck prefs + ''' + if isinstance(s, unicode): + return s.encode(self._get_encoding()) + else: + return s + def _decode(self, s): + ''' + Decode a string using the encoding determined by the user's spellcheck prefs + ''' + if isinstance(s, str): + return s.decode(self._get_encoding()) + else: + return s + + def Check(self,text): + """ + Returns True if the word is correctly spelled, false otherwise + """ + + if self.spellengine is None or not text: + return True + + puretext = self._encode(text) + + return puretext.isdigit() or isurl(puretext) or self.spellengine is None or self.spellengine.check(puretext) + + def Suggest(self,word,count=None): + """ + Return a list of suggested replacement words if the word is spelled incorrectly + Returns an empty list if the word is correctly spelled + """ + if self.spellengine is None: + return [] + + + if not word: + return [] + + if not count: + count = self._pref("max_suggestions") + + suggestions = self.spellengine.suggest(self._encode(word)) + + if len(suggestions) > count: + suggestions = suggestions[:count] + + return [self._decode(s) for s in suggestions] + + def Add(self, word): + """ + Add a word to the dictionary + """ + if self.spellengine is None: + return + + self.spellengine.add(self._encode(word)) + + def DownloadDict(self): + """ + Get everything set for, then call, DownloadAndInstall + """ + + # decide if we actualy need to get the language + self._need_to_download, need = None, self._need_to_download + if not need or need == self.lang: + log.error('not downloading dictionary') + return + + #set what langugae is expected next + self.expectedNext = need + if need in self.currentDownloads: + log.info('Already downloading dictionary, returning') + return + + #Get the full name of the language + langInfo = self.dict_info[need] + langName = langInfo['name_english']#'name_native' if 'name_native' in langInfo else + + #ask the user about downloading + log.info('Download %s?', need) + userResponse = wx.MessageBox(_('You need to download the {langname} dictionary to use it. Would you like to download this dictionary now?').format(langname=langName), + _('Download Dictionary?'), + wx.YES_NO) + + #if the user answered no, inform them of how to download and return + if userResponse == wx.NO: + + lastlang = self.spellengine.lang + if lastlang: + setpref('messaging.spellcheck.engineoptions.lang', lastlang) + + dictcancel_hdr = _('Dictionary not downloaded.') + dictcancel_msg = _('To download it later, select it in the Conversation Preferences.') + + wx.MessageBox(u'%s\n\n%s' % (dictcancel_hdr, dictcancel_msg), + _('Download Dictionary Canceled'), + wx.OK) + return + + + #build URL + remote_repo = pref('messaging.spellcheck.aspell_mirror', type=str, default='http://dict.digsby.com/') + remote_path = remote_repo + langInfo['location'] + + + + def on_install_success(): + log.info('%r has been installed.', need) + + #Swap out the language if the new language is still selected + if self.expectedNext == need: + #Attempt the swap and fire a notification on success + if self.SwapEngine(shouldDownloadOnFail=False): + fire('dictionary.install', + title=_('Dictionary Set'), + msg=_('Spellcheck language has been set to {langname}.').format(langname=langName), + popupid='dict_install_%s' % self.lang) + #If successfull download and install, but fails to load, fire a error notification + else: + fire('dictionary.install', + title=_('Spellcheck error'), + msg=_('Failed setting up dictionary. Try reselecting desired language in the preferences.'), + popupid='dict_install_%s' % self.lang) + + #if no longer the set language announce the install was complete + else: + fire('dictionary.install', + title=_('Dictionary Installed'), + msg=_('You can set this language in the conversation preferences.'), + popupid='dict_install_%s' % self.lang) + + #Remove the language from current downloads + self.currentDownloads.discard(need) + + #if there's an error, log it + def on_install_error(): + log.error('There was an error installing %s', need) + + self.currentDownloads.discard(need) + + def on_install_cancel(): + log.info('Dictionary download cancelled by user.') + + self.currentDownloads.discard(need) + + lastlang = self.spellengine.lang + if lastlang: + setpref('messaging.spellcheck.engineoptions.lang', lastlang) + + + #add to the current downloads set to pervent duplicate downloads + self.currentDownloads.add(need) + + #Start download + log.info('Downloading %r from %r', need, remote_path) + DownloadAndInstall(need, langName, remote_path, + cancel = on_install_cancel, + success = on_install_success, + error = on_install_error) + +class DictionaryInstaller(object): + SUFFIXES = '.alias .multi .cwl .rws .dat'.split() + + def __init__(self, id, bz2path): + autoassign(self, locals()) + self.cwl_files = [] + + @callsback + def Install(self, callback=None): + log.info('Installing Dictionary...') + + #fire a notification + fire('dictionary.install', title=_('Installing Dictionary'), msg=_('Dictionary will be activated after install completes.'), + popupid='dict_install_%s' % self.id) + + #go Extract, then Finalize on success + self.Extract(error = callback.error, + success = lambda:self.Finalize(callback=callback)) + + + log.info('Finished Installing Dictionary') + + @threaded + def Extract(self): + """ + Extract the usefull files from the tar.bz2 to the local dict directory + """ + + log.info('Extracting Dictionary...') + + log.info('Opening tar %s', self.bz2path) + tar = tarfile.open(fileobj=open(self.bz2path, 'rb'), mode='r:bz2') + log.info('Tar opened') + fobj = None + outfile = None + try: + #Extract any .alias, .multi, .cwl, .rws, and .dat files from the temp file + log.info('Retrieving file information from tar') + for fileinfo in tar: + if not fileinfo.isfile(): + continue + + fname = path(fileinfo.name.decode('filesys')) + + if fname.ext and fname.ext in self.SUFFIXES: + log.info('Extracting %s', fname) + + ex_path = path(LocalAspellDataDir()) / fname.name + + if fname.ext == '.cwl': + self.cwl_files.append(ex_path) + + if not ex_path.parent.isdir(): + ex_path.parent.makedirs() + + fobj = tar.extractfile(fileinfo) + with open(ex_path, 'wb') as outfile: + while outfile.tell() < fileinfo.size: + outfile.write(fobj.read(16*1024)) + + log.info('Extracted %s', fname) + + else: + log.info('Ignoring %s', fname) + + except Exception: + log.error('Failed extracting files') + + finally: + #close all files + for f in (tar, fobj, outfile): + if f is not None: + f.close() + + + log.info('Finished Extracting Dictionary...') + + return True + + @threaded + def Finalize(self): + """ + Decompress the CWLs to make RWSs + """ + def quote_encode(s): + return '"%s"' % s.encode('filesys') + + aspell_opts = ["--lang=%s" % self.id, + "--local-data-dir=%s" % quote_encode(LocalAspellDataDir().strip('\\')), + "create", "master"] + decomp_opts = ['d'] + + decomp_exe = ASPELLBINDIR/'word-list-compress.exe' + aspell_exe = ASPELLBINDIR/'aspell.exe' + + startupinfo = subprocess.STARTUPINFO() #@UndefinedVariable + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW #@UndefinedVariable + startupinfo.wShowWindow = subprocess.SW_HIDE #@UndefinedVariable + + log.info('Decompressing wordlists') + for cwl in self.cwl_files: + rws = path(LocalAspellDataDir()) / cwl.namebase + '.rws' + command = ['cmd', '/c', quote_encode(decomp_exe)] + decomp_opts + \ + ['<', quote_encode(cwl), '|', quote_encode(aspell_exe)] + aspell_opts + [quote_encode(rws)] + + command = ' '.join(command) + # this will raise an exception if the command fails, and callsback will call our error callback + log.info('Decompressing %s', cwl) + log.info("Executing: %r", command) + subprocess.check_call(command, shell=True, startupinfo=startupinfo) + os.remove(cwl) + + os.remove(self.bz2path) + + #Make the digsby dict + local_dir = LocalAspellDataDir() + digsby_dict_location = local_dir / ('digsby-%s.rws' % id) + if not digsby_dict_location.isfile(): + try: + MakeDigsbyDict(self.id, local_dir) + except Exception: + log.error("failed to create Digsby Dictionary in '%s', probable cause: dict not yet downloaded", id) + return None + + #dictionary installed notification + fire('dictionary.install', title=_('Dictionary Installed'), msg=_('Setting spellcheck language...'), + popupid='dict_install_%s' % self.id) + return True + + +@callsback +def DownloadAndInstall(langID, langName, remotePath, cancel, callback=None): + """ + Does what it says, via a DictionaryDownloader and DictionaryInstaller Objects, using alot of callbacks + """ + + log.info('Downloading dictionary...') + + ManualFileTransfer( _('{langname} Dictionary').format(langname=langName), remotePath, + lambda downloaded_path: DictionaryInstaller(langID, downloaded_path).Install(callback=callback), + cancel, + callback.error).manual_download() + +''' +cd aspell +bin\word-list-compress.exe d < dict\es.cwl | bin\aspell.exe --lang=es create master es.rws +''' + +class SpellCheckerMock(object): + """ + Stuff to make it so there can only be one + """ + def __getattr__(self, key, default = sentinel): + try: + spellchecker = object.__getattribute__(self, '_spellchecker') + except AttributeError: + #L_O_G.info('No old spellchecker found... Creating') + try: + spellchecker = SpellChecker() + except: + spellchecker = SpellChecker(lang_override='en') + + + #L_O_G.info('Setting _spellchecker') + object.__setattr__(self, '_spellchecker', spellchecker) + + if default is sentinel: + return getattr(spellchecker, key) + else: + return getattr(spellchecker, key, default) + +spellchecker = SpellCheckerMock() diff --git a/digsby/src/common/statusmessage.py b/digsby/src/common/statusmessage.py new file mode 100644 index 0000000..35cf742 --- /dev/null +++ b/digsby/src/common/statusmessage.py @@ -0,0 +1,251 @@ +from __future__ import with_statement +from util import autoassign +from common.slotssavable import ObservableSlotsSavable +from logging import getLogger; log = getLogger('statusmessage') + +MAX_TITLE_CHARS = 60 + +def acct_reduce(account): + 'Return a string representation for an account.' + + return account.protocol + '_' + account.name + +def proto_reduce(proto): + return proto.name + '_' + proto.username + +def simple(status_string, protocol): + ''' + Given a status string like "Free For Chat", returns the "simplified" + version, which is just 'available' or 'away'. + ''' + + from common.protocolmeta import protocols + statuses = protocols[protocol.name].statuses + if status_string in [s.lower() for s in statuses[0]]: + return 'available' + else: + return 'away' + +# Used to show the status (not the message) in the GUI. +nice_statuses = dict( + online = _('Available'), + available = _('Available'), + away = _('Away'), + invisible = _('Invisible'), + offline = _('Offline'), +) + +class StatusMessage(ObservableSlotsSavable): + 'A saved status message with a title, a state, and possible exceptions.' + + __slots__ = 'title status message exceptions editable format edit_toggle'.split() + + online_statuses = ('online', 'available', 'free for chat') + + @classmethod + def is_available_state(cls, state): + if not isinstance(state, basestring): + raise TypeError('is_available_state takes a string') + + return state.lower() in (s.lower() for s in cls.online_statuses) + + @classmethod + def icon_for(cls, status): + 'Returns an icon for a status or status string.' + + from gui import skin + return skin.get('statusicons.' + cls.icon_status(status)) + + @classmethod + def icon_status(cls, status): + if isinstance(status, StatusMessage) or hasattr(status, 'status'): + status = status.status + + status = status.lower() + + if cls.is_available_state(status): + s = 'available' + elif status == 'invisible': + s = 'invisible' + elif status == 'offline': + s = 'offline' + elif status == 'idle': + s = 'idle' + else: + s = 'away' + + return s + + @property + def icon(self): + return self.icon_for(self.status) + + def __init__(self, title, status, message, exceptions = None, editable = True, format = None, edit_toggle = True, **k): + ObservableSlotsSavable.__init__(self) + + if exceptions is None: + exceptions = {} + + autoassign(self, locals()) + + self.check_types() + self.cap_title_len() + + def check_types(self): + for attr in ('title', 'status', 'message'): + if not isinstance(getattr(self, attr, None), basestring): + setattr(self, attr, u'') + from util.primitives import Storage + if self.format is not None: + assert isinstance(self.format, dict) + if not isinstance(self.format, Storage): + self.format = Storage(self.format) + if not self.exceptions: #StatusMessageException's have a tuple + return + for ex,exobj in self.exceptions.items(): + if not isinstance(exobj, StatusMessage): + assert isinstance(exobj, dict) + self.exceptions[ex] = StatusMessageException(**exobj) + + @property + def hint(self): + return _(self.status) + + edit_toggle = True + + def __setstate__(self, *a, **k): + ObservableSlotsSavable.__setstate__(self, *a, **k) + self.check_types() + self.cap_title_len() + + def __getstate__(self, network=False, *a, **k): + ret = ObservableSlotsSavable.__getstate__(self, *a, **k) + if ret['format'] is not None: + ret['format'] = dict(ret['format']) + exceptions = ret['exceptions'] + if network: + ret.pop('edit_toggle', None) + if not exceptions: #could be a tuple + return ret + exceptions = dict(exceptions) #do not want to change our own state + for ex, exobj in exceptions.items(): + exceptions[ex] = exobj.__getstate__(network=network) + ret['exceptions'] = exceptions + return ret + + def cap_title_len(self): + if len(self.title) > MAX_TITLE_CHARS: + self.title = _('{truncatedtitle}...').format(truncatedtitle = self.title[:MAX_TITLE_CHARS]) + + def copy(self, title = None, status = None, message = None, hint = None, editable=True, edit_toggle = True): + if isinstance(self.exceptions, dict): + exceptions = {} + for key, ex in self.exceptions.iteritems(): + exceptions[key] = ex.copy(editable=None) + else: + assert self.exceptions == () + exceptions = self.exceptions + return self.__class__(title = self.title if title is None else title, + status = self.status if status is None else status, + message = self.message if message is None else message, + exceptions = exceptions, + editable = (self.editable if editable is None else editable), + edit_toggle = (self.edit_toggle if edit_toggle is None else edit_toggle), + format = self.format) + + @property + def use_exceptions(self): + ''' + Property is true if this status message has one or more exceptions + defined. + ''' + return bool(self.exceptions) + + @property + def invisible(self): + return self.status.lower() == StatusMessage.Invisible.status.lower() + + @property + def offline(self): + return self.status.lower() == StatusMessage.Offline.status.lower() + + @property + def away(self): + return not any((self.available, self.invisible, self.offline)) + + @property + def idle(self): + return self.status == StatusMessage.Idle.status + + @property + def available(self): + return type(self).is_available_state(self.status) + + def for_account(self, acct): + 'Returns the status message (or exception) for a given Account or Protocol.' + + from common.Protocol import Protocol + from digsbyprofile import Account, DigsbyProfile + + key = None + if isinstance(acct, Protocol): + key = proto_reduce(acct) + elif isinstance(acct, Account): + key = acct_reduce(acct) + elif isinstance(acct, DigsbyProfile): + pass + else: + log.error('Got unknown object for status message. object was %r (%r)', acct, type(acct)) + + return self.exceptions.get(key, self) + + def ToggleStatus(self): + self.status = 'Available' if self.away else 'Away' + + # TODO: this won't work after i18n + if self.message in ('Available', 'Away'): + self.message = self.status + + @property + def nice_status(self): + try: + return nice_statuses[self.status.lower()] + except KeyError: + # TODO: On the Phone, etc? + return nice_statuses['away'] + + def __repr__(self): + attrs = ', '.join('%s=%r' % (slot, getattr(self, slot, '')) for slot in self.__slots__) + return '<%s %s>' % (type(self).__name__, attrs) + +# Singleton status messages. +StatusMessage.Available = StatusMessage(_('Available'), 'Available', u'') +StatusMessage.Away = StatusMessage(_('Away'), 'Away', u'') +StatusMessage.Idle = StatusMessage(_('Idle'), 'Idle', u'') +StatusMessage.Invisible = StatusMessage(_('Invisible'), 'Invisible', u'', editable = False) +StatusMessage.Offline = StatusMessage(_('Offline'), 'Offline', u'', editable = False) + +StatusMessage.SpecialStatuses = [ + StatusMessage.Available, + StatusMessage.Away, + StatusMessage.Idle, + StatusMessage.Invisible, + StatusMessage.Offline, +] + +class StatusMessageException(StatusMessage): + def __init__(self, status, message, format = None, + title=False, exceptions=False, editable=False, edit_toggle = False): #these four aren't used. + StatusMessage.__init__(self, None, status, message, exceptions =(), format = format) + +if __name__ == '__main__': + + st = StatusMessage('my title', 'my status', 'some message', + StatusMessageException('msn away', 'my msn is away')) + st2 = StatusMessage('my title', 'my status', 'some message', + StatusMessageException('msn away', 'my msn is away')) + + print st.__hash__() + print st2.__hash__() + + print st == st2 diff --git a/digsby/src/common/timeoutsocket.py b/digsby/src/common/timeoutsocket.py new file mode 100644 index 0000000..c69956a --- /dev/null +++ b/digsby/src/common/timeoutsocket.py @@ -0,0 +1,267 @@ +''' +Connects to a series of addresses, trying each one in sequence. +''' +from util.primitives import lock + +import common +from logging import getLogger +from util import Timer +log = getLogger('common.timeoutsocket'); info = log.info; + +class TimeoutSocket(common.socket): + + def tryconnect(self, ips, on_connect, on_fail, timeout=2.0): + ''' + Setup for a new set of ips and start the connect routine + + @param ips: + @param on_connect: + @param on_fail: + @param timeout: + ''' + self.cancel_timeout() + self.timetowait = timeout + self.on_connect = on_connect + self.on_fail = on_fail + self._ips = iptuples(ips) + self.attempts = 0 + self._accepting = False + self.try_connect() + + def try_connect(self): + 'Do the connection routine.' + + addr = self._ips[self.attempts] + log.warning('tryconnect: %r', (addr,)) + self.attempts += 1 + self.timeout = Timer(self.timetowait, lambda s=self.socket: self.handle_timeout(s)) + self.make_socket() + if self.timeout is not None: + self.timeout.start() + def succ(*a, **k): + log.info("WIN") + def fail(*a, **k): + log.info("FAIL") + self.connect(addr, success=succ, error=fail) + + def tryaccept(self, addr, on_connect, on_fail, timeout = 1.5): + self._accepting = True + info('tryaccept Y=%r, N=%r', on_connect, on_fail) + self.on_connect = on_connect + self.on_fail = on_fail + + info('listening for a connection at %r', (addr,)) + self.make_socket() + common.socket.bind( self, addr ) + self.listen(1) + + if timeout: + info('timeout in %r secs', timeout) + self.timeout = Timer(timeout, lambda s=self.socket: self.handle_timeout(s)) + self.timeout.start() + + def handle_timeout(self, socket): + info('TIMEOUT %r', socket) + if socket is self.socket: + self.do_disconnect() + elif socket is not None: + socket.close() + + def handle_expt(self): + info('handle_expt in %r', self) + self.do_disconnect() + + def handle_error(self, e=None): + info('handle_error in %r', self) + import traceback + traceback.print_exc() + self.do_disconnect() + + def do_disconnect(self): + ''' + toss away the current connection + will try the next address immediately + ''' + + log.warning('do_disconnect') + self.cancel_timeout() + self.close() + + if not self._accepting and self.attempts < len(self._ips): + self.try_connect() + else: + self.on_fail() + + def handle_connect(self): + info('connected!') + self.cancel_timeout() + self.on_connect(self) + + def handle_accept(self): + self.cancel_timeout() + conn, address = self.accept() + info('%r connection accepted (%r), canceling timeout and calling %r', self, address, self.on_connect) + self.on_connect(conn) + + def cancel_timeout(self): + # Cancel any timeout. + if hasattr(self, 'timeout') and self.timeout is not None: + info('cancelling timeout') + self.timeout.cancel() + else: + log.warning('there was no timeout to cancel') + self.timeout = None + + def __repr__(self): + if hasattr(self,'ips') and len(self.ips): + return '' % self.ips[0] + else: + pn = None + try: pn = self.socket.getpeername() + finally: return "<%s connected to %r>" % (self.__class__.__name__,pn) + +class TimeoutSocketOne(common.socket): + ''' + single socket timeout socket + ''' + + @lock + def try_connect(self, address, succ, fail, time_to_wait, provide_init): + provide_init(self) + self.real_success = succ + self.fail = fail + self.dead = False + self.data = None + #make new socket + self.make_socket() + self.timeoutvalid = True + self.timeout = Timer(time_to_wait, self.handle_timeout) + self.timeout.start() + + print '*'*40 + from util import funcinfo + print funcinfo(self.connect) + print '*'*40 + + self.connect(address, error=self.do_fail) + #do connect with callback + #success indicates that the socket started, but guarantees nothing + #error indicates that there was a problem, should try to close + do fail + + def succ(self): + info('succ') + self.real_success() + + @lock + def do_fail(self, *a, **k): + info('do_fail') + if self.timeout is not None: + self.timeout.cancel() + self.timeout = None + self.timeoutvalid = False + self.close() + print a, k + self.fail(*a,**k) + + + @lock + def handle_connect(self): + info('CONNECT') + if self.timeout is not None: + self.timeout.cancel() + self.timeout = None + self.timeoutvalid = False + self.succ() + #cancel timeout + #success + + @lock + def handle_timeout(self): + info('TIMEOUT') + #mark as dead + if self.timeoutvalid: + if self.timeout is not None: + self.timeout.cancel() + self.timeout = None + self.timeoutvalid = False + self.close() + self.dead = True + self.fail() + + @lock + def collect_incoming_data(self, data): + self.data += data + + @lock + def __error(self): + olddead = self.dead + self.dead = True + if self.timeout is not None: + self.timeout.cancel() + self.timeout = None + self.timeoutvalid = False + #cancel timeout + self.close() + if not olddead: + self.fail() + + def handle_error(self, e=None): + info('ERROR: %r', e) + import traceback;traceback.print_exc() + self.__error() + + def handle_expt(self): + info('EXPT') + self.__error() + + def handle_close(self): + info('CLOSE') + self.__error() + +class TimeoutSocketMulti(object): + + def tryconnect(self, ips, on_connect, on_fail, timeout=2.0, + cls=TimeoutSocketOne, provide_init=lambda self: None): + ''' + Setup for a new set of ips and start the connect routine + + @param ips: + @param on_connect: + @param on_fail: + @param timeout: + ''' + self.provide_init = provide_init + self.cls = cls + self.timetowait = timeout + self.on_connect = on_connect + self.on_fail = on_fail + self._ips = iptuples(ips) + self.attempts = 0 + self.try_connect() + + def try_connect(self): + self.socket = self.cls(False) + address = self._ips[self.attempts] + log.warning('tryconnect: %r', address) + self.attempts += 1 + self.socket.try_connect(address, self.win, self.lose, self.timetowait, + self.provide_init) + + def win(self): + self.on_connect(self.socket) + + def lose(self, *a, **k): + if self.attempts < len(self._ips): + self.try_connect() + else: + self.on_fail(*a, **k) + +def iptuples(ips): + if not hasattr(ips, '__len__'): + raise TypeError('ips must be (host, port) or [(host,port), (host,port)]') + if isinstance(ips[0], basestring): + ips = tuple([ips]) + + # ips is now a sequence of (host, port) tuples + assert all(isinstance(a, basestring) and isinstance(p, int) for a, p in ips) + return ips diff --git a/digsby/src/common/urlhandler.py b/digsby/src/common/urlhandler.py new file mode 100644 index 0000000..ef9c75c --- /dev/null +++ b/digsby/src/common/urlhandler.py @@ -0,0 +1,74 @@ +''' +register callbacks for URLs digsby handles +''' + +import re +import traceback + +matchers = [] + +URL_OPEN_IN_BROWSER = object() + +class URLHandlerResult(object): + ''' + Returned by "handle" + ''' + + __slots__ = ('cancel_navigation', 'url') + + def __init__(self, url, cancel_navigation=False): + assert isinstance(url, basestring) + self.url = url + self.cancel_navigation = cancel_navigation + + def cancel(self, cancel=True): + assert isinstance(cancel, bool) + self.cancel_navigation = cancel + +def handle(url): + result = URLHandlerResult(url) + + # for now, only dispatch digsby:// urls + if not url.startswith('digsby://'): + return result + + url = url[len('digsby://'):] + + # try all compiled regexes against the rest of the URL + for compiled_matcher, handler in matchers: + match = compiled_matcher.match(url) + if match is not None: + try: + handle_result = lazy_call(handler, *match.groups()) + except Exception: + traceback.print_exc() + else: + if handle_result is not URL_OPEN_IN_BROWSER: + result.cancel() + + return result + +def register(match_re, handler): + ''' + Register a url handler. + ''' + matchers.append((re.compile(match_re), handler)) + +def unregister(url, handler): + global matchers + new_matchers = [] + for compiled_matcher, url_handler in matchers: + if url_handler is not handler or compiled_matcher.pattern != url: + new_matchers.append((compiled_matcher, url_handler)) + + matchers = new_matchers + +def lazy_call(handler, *args): + if not hasattr(handler, '__call__'): + assert isinstance(handler, basestring) + from util import import_function + handler = import_function(handler) + assert hasattr(handler, '__call__') + + return handler(*args) + diff --git a/digsby/src/contacts/BuddyListElement.py b/digsby/src/contacts/BuddyListElement.py new file mode 100644 index 0000000..f33b338 --- /dev/null +++ b/digsby/src/contacts/BuddyListElement.py @@ -0,0 +1,22 @@ +from util.primitives.funcs import isiterable +from common.actions import ActionMeta + +class BuddyListElement(object): + + __metaclass__ = ActionMeta + + @property + def num_online(self): + from Contact import Contact + if isiterable(self) and not isinstance(self, Contact): + return sum(elt.num_online for elt in self) + else: + return int(self.online) + + def find(self, obj): + assert isinstance(self, list) + return list.find(self, obj) + + def chat(self): + import gui.imwin, wx + wx.CallAfter(lambda: gui.imwin.begin_conversation(self)) diff --git a/digsby/src/contacts/Contact.py b/digsby/src/contacts/Contact.py new file mode 100644 index 0000000..f6292a2 --- /dev/null +++ b/digsby/src/contacts/Contact.py @@ -0,0 +1,197 @@ +import traceback + +from util import callsback +import common.actions +action = common.actions.action +ObservableActionMeta = common.actions.ObservableActionMeta +from common import profile +from logging import getLogger; log = getLogger('Contact') + +objget = object.__getattribute__ + +CONTACT_ATTRS = set(['id', 'buddy', 'remove', 'watched', '__repr__', + 'rename_gui', 'rename', 'edit_alerts', 'alias', 'get_group', 'move_to_group' + '__getattr__', '__hash__', '__cmp__', 'sort', '_notify_dirty', + ]) + +class Contact(object): + ''' + Contact. Represents an entry on a protocol buddy list. + + Several contacts may point to the same Buddy object. + Ex: AIM when a buddy is in 2+ groups - same buddy in both + places but each has its own SSI. + ''' + + watched = 'online '.split() + __metaclass__ = ObservableActionMeta + + def __init__(self, buddy, id): + self.buddy, self.id = buddy, id + self._metacontact = None + + def remove(self): + self.protocol.remove_buddy(self.id) + + def _compatible_accounts(self): + from common.protocolmeta import is_compatible + result = [] + for account in profile.connected_accounts: + if is_compatible(account.protocol, self.buddy.service): + result.append(account) + + return result + + def _all_buddies(self, check_if_has=False): + result = [] + for account in self._compatible_accounts(): + connection = account.connection + if connection: + if not check_if_has or connection.has_buddy(self.buddy.name): + # don't let a protocol create the buddy + buddy = connection.get_buddy(self.buddy.name) + if buddy is not None: + result.append(buddy) + + return result + + + def _is_blocked(self): + buddies = [buddy.blocked for buddy in self._all_buddies(check_if_has=True)] + return bool(buddies and all(buddies)) + + blocked = property(_is_blocked) + + def _block_pred(self, block=True, **k): + return True if bool(block) ^ self._is_blocked() else None + def _unblock_pred(self, *a, **k): + return True if self._is_blocked() else None + + @action(_block_pred) + def block(self, block=True, **k): + for buddy in self._all_buddies(): + if bool(block) ^ bool(buddy.blocked): + buddy.block(block, **k) + + @action(_unblock_pred) + def unblock(self, *a,**k): + self.block(False,*a,**k) + + def get_notify_dirty(self): + return self.buddy._notify_dirty + + def set_notify_dirty(self, value): + self.buddy._notify_dirty = value + + _notify_dirty = property(get_notify_dirty, set_notify_dirty) + + @action() + def rename_gui(self): + from gui.toolbox import GetTextFromUser + + localalias = self.alias + if localalias is None: + localalias = '' + + s = GetTextFromUser(_('Enter an alias for %s:') % self.name, + caption = _('Rename %s') % self.name, + default_value = localalias ) + if s is not None: + if s == '' or s.strip(): + # dialog returns None if "Cancel" button is pressed -- that means do nothing + + # rename expects None to mean "no alias" and anything else to mean an alias--so + # do the bool check to turn '' into None here. + self.rename(s if s else None) + return s + + def rename(self, new_alias): + log.info('setting alias for %r to %r', self, new_alias) + profile.set_contact_info(self, 'alias', new_alias) + self.buddy.notify('alias') + + @action() + def edit_alerts(self): + import gui.pref.prefsdialog as prefsdialog + prefsdialog.show('notifications') + + @property + def alias(self): + a = profile.get_contact_info(self, 'alias') + if a: return a + + for attr in ('local_alias', 'remote_alias', 'nice_name'): + try: + a = getattr(self, attr, None) + except Exception: + traceback.print_exc() + continue + + if a: return a + + return self.name + + def get_group(self): + g = self.protocol.group_for(self) + assert isinstance(g, (basestring, type(None))), 'Is %s' % type(g) + return g + + @callsback + def move_to_group(self, groupname, index = 0, callback = None): + if not isinstance(groupname, basestring): + raise TypeError, 'groupname must be a string: %r' % groupname + + self.protocol.move_buddy_creating_group(self, groupname, self.get_group(), + index, callback = callback) + + def __getattr__(self, attr): + if attr in CONTACT_ATTRS: + return objget(self, attr) + else: + return getattr(objget(self, 'buddy'), attr) + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, self.buddy) + + def __hash__(self): + # First part of this hash should match Buddy.idstr() + b = self.buddy + id = self.id + if isinstance(id, bytes): + id = id.decode('fuzzy utf-8') + return hash(u'/'.join((b.protocol.name, b.protocol.username, b.name, unicode(id)))) + + def __cmp__(self, other): + if self is other: + return 0 + else: + return cmp((self.buddy, self.id), (getattr(other, 'buddy', None), getattr(other, 'id', None))) + +class ContactCapabilities: + 'Buddy capabilities. Exposed as common.caps' + + INFO = 'INFO' + + IM = 'IM' + 'Instant messaging.' + + FILES = 'FILES' + 'Sending and receiving files.' + + PICTURES = 'PICTURES' + 'Sharing pictures over a direct connection.' + + SMS = 'SMS' + 'Sending messages directly to a cell phone.' + + BLOCKABLE = 'BLOCKABLE' + 'Blocking buddies.' + + EMAIL = 'EMAIL' + 'Sending email.' + + BOT = 'BOT' + 'User is a bot, and will join the Machines when Skynet turns on the human race. Be vigilant.' + + VIDEO = 'VIDEO' + 'Video chat.' diff --git a/digsby/src/contacts/Group.py b/digsby/src/contacts/Group.py new file mode 100644 index 0000000..8817f29 --- /dev/null +++ b/digsby/src/contacts/Group.py @@ -0,0 +1,435 @@ +from __future__ import with_statement +from common.actions import action +from util.observe import ObservableList +from threading import RLock +import collections +from BuddyListElement import BuddyListElement +from itertools import izip + +import traceback + +from traceback import print_exc + +from util.callbacks import callsback +from util.primitives.error_handling import traceguard +from util.primitives.funcs import do +from common import netcall + +no_offline_group = lambda self, *a, **k: True if self.id != Group.OFFLINE_ID else None + +def SpecialGroup_TEST(self, *a, **k): + from gui.buddylist.buddylistrules import SpecialGroup_TEST + return SpecialGroup_TEST(self) + +from logging import getLogger +log = getLogger('groups') + +class Group(BuddyListElement, ObservableList): + 'A Group. Represents a group on the protocol/network level.' + + OFFLINE_ID = '__digsbyoffline__' + FAKEROOT_ID = '__fakerootgroup__' + + def __init__(self, name, protocol, id, *children): + BuddyListElement.__init__(self) + ObservableList.__init__(self, *children) + self.name = name + self.protocol = protocol + self.id = id + self.watching = set() + + def __str__(self): + return u"%s (%d/%d)" % (self.name, self.num_online, len(self)) + + def __repr__(self): + try: + return '' % (self.name, u',\n'.join(repr(item) for item in self)) + except: + return '' + + @action(no_offline_group, + needs = (unicode, 'buddyname')) + def add_buddy(self, buddy_name, service=None): + self.protocol.add_buddy(buddy_name, self.id, service=service) + + def __eq__(self, other): + if self is other: + return True + elif hasattr(other, 'name') and self.name == other.name and (self is self.id or self.id == other.id): + return ObservableList.__eq__(self, other) + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def groupkey(self): + return self.name.lower() + + def __hash__(self): + 'Something about this could be bad...' + + return hash(self.name) + +class moving_truck(object): + @callsback + def __init__(self, size, callback=None): + self.lock = RLock() + self.size = size + self.counter = 0 + self.successes = 0 + self.failures = 0 + self.callback = callback + + def success(self, *a, **k): + with self.lock: + self.counter += 1 + self.successes += 1 + self.check_done() + + def check_done(self): + if self.counter == self.size: + self.callback.success() + elif self.counter > self.size: + traceback.print_stack() + raise AssertionError("too many results") + + def error(self, *a, **k): + with self.lock: + self.counter += 1 + self.failures += 1 + self.check_done() + + +class DGroup(BuddyListElement, ObservableList): + ''' + Groups in multiple protocols with the same name. + + Instead of "protocol" and "id" it has "protocols" and "ids." + ''' + + inherited_actions = [Group] + + def __init__(self, name, protocols = [], ids = [], *children): + + if not isinstance(name, basestring): + raise TypeError('name must be a string, it was %s' %type(name)) + if len(protocols) != len(ids): + raise AssertionError('protocols and ids must have same length: ' + '%d %d' % (len(protocols), len(ids))) + + BuddyListElement.__init__(self) + ObservableList.__init__(self, *children) + + # assert that the incoming name string is a unicode object, or can + # be converted to one trivially + self.name = unicode(name) + + self.protocols = protocols + self.ids = ids + self.id_lookup = dict(izip(protocols, ids)) # stores {protocol: id} + + # This will store the number of buddies moved out of this group during + # the filter process so __str__ can still return an accurate offline + # count. + self._offline_moved = 0 + + # Whether or not to show the offline count, i.e. the 6 in Buddies (5/6) + self._show_offline_count = True + + @action(lambda self, *a, **k: (not SpecialGroup_TEST(self)) and no_offline_group(self, *a, **k) or None) + def delete(self, force = False): + from common import profile + log.info('Deleting DGroup %r. force=%r', self.name, force) + profile.blist.metacontacts.remove_group(self.name) + + if not force: + to_move = profile.blist.metacontacts.contacts_for_group(self.name) + to_move = filter(lambda c: c[0].get_group().lower() != c[1], to_move) + if to_move: + mover = moving_truck(len(to_move), success = lambda: self.delete(True)) + + for contact, groupname in to_move: + log.info('Moving %r to %r', contact, groupname) + contact.move_to_group(groupname, success = mover.success, + error = mover.error, timeout=mover.error) + return + + errors = [] + for proto in self.protocols: + try: + log.info('Removing %r from %r', self.id_lookup[proto], proto) + proto.remove_group(self.id_lookup[proto]) + except Exception, e: + errors.append(e) + traceback.print_exc() + + if errors: + raise Exception(errors) + + + @action(no_offline_group) + def add_contact(self, name, account): + if not isinstance(self.name, unicode): + raise TypeError('DGroup.name must always be unicode') + + if not isinstance(name, basestring): + raise TypeError('name must be a string: %s' % type(name)) + + from common import profile + import hub + x = (name, account.connection.service) + if x in profile.blist.metacontacts.buddies_to_metas: + id = list(profile.blist.metacontacts.buddies_to_metas[x])[0].id + m = profile.blist.metacontacts[id] + alias = m.alias + group = list(m.groups)[0][-1] + message = _('That buddy is already part of metacontact ' + '"{alias}" in group "{group}."').format(alias=alias, group=group) + hub.get_instance().user_message(message, _('Add Contact')) + + + if account.connection: + proto = account.connection + + def do_add(groupid): + proto.add_contact(name, groupid) + self.protocols.append(proto) + self.ids.append(groupid) + self.id_lookup[proto] = groupid + + if proto not in self.protocols: + # the group doesn't exist yet + netcall(lambda: proto.add_group(self.name, success = do_add)) + else: + # the group already exists. + netcall(lambda: do_add(self.id_lookup[proto])) + + @callsback + def remove_contact(self, buddy, callback = None): + if buddy.protocol in self.protocols: + netcall(lambda: buddy.protocol.remove_buddy(buddy, callback = callback)) + + def renamable(self): + return True + + @action(lambda self, callback=None: type(self).renamable(self)) + @callsback + def rename_gui(self, callback = None): + + from gui.toolbox import GetTextFromUser + new_name = GetTextFromUser(_('Enter a new name for {name}:'.format(name=self.name)), + caption = _('Rename Group'), + default_value = self.name) + if not new_name: + return callback.success() + + else: + return self._rename(new_name, callback) + + def _rename(self, new_name, callback): + old_name = self.name + + from common import profile + profile.blist.rename_group(old_name, new_name) + + for protocol in self.protocols: + if protocol is not None: + with traceguard: + protocol.rename_group(self.id_lookup[protocol], new_name) + + self._fix_expanded_state(new_name) + + return new_name + + def _fix_expanded_state(self, new_name): + # TODO: resolve this horrible layering violation + + # change the expanded state in the buddylist if necessary + from gui.treelist import expanded_id + import wx; blist = wx.FindWindowByName('Buddy List').buddyListPanel.blist + collapsed = blist.model.collapsed + + eid = expanded_id(self) + + if eid in collapsed: + # rename self and insert it's expanded ID back into the list + collapsed.discard(eid) + self.name = new_name + collapsed.add(expanded_id(self)) + blist.model.update_list() + + @action() + def add_group(self): + from gui.protocols import add_group + add_group() + + @property + def protocol(self): + #assert len(self.protocols) == 1, self.protocols + return self.protocols[0] if self.protocols else None + + @property + def id(self): + return self.ids[0] if len(self.ids) else None + + def __repr__(self): + try: + return u'<%s %r %s>' % (type(self).__name__, getattr(self, 'name', ''), list.__repr__(self)) + except: + try: + traceback.print_exc_once() + except: + return '' + + try: + return '<%s %r ???>' % (type(self).__name__, self.name,) + except: + return '' + + def remove_duplicates(self, hash = lambda contact: contact.info_key): + newlist = [] + unique = set() + + for c in self: + h = hash(c) + if h not in unique: + unique.add(hash(c)) + newlist.append(c) + + self[:] = newlist + + def __str__(self): + try: + # The new sorter sets this manually. + return self._new_display_string + except AttributeError: + pass + + if not getattr(self, '_show_offline_count', True): + from contacts import buddylistsort + if buddylistsort.grouping() and self.name == 'Offline': + return '%s (%d)' % (self.name, len(self)) + else: + return '%s (%d)' % (self.name, self.num_online) + else: + return '%s (%d/%d)' % (self.name, self.num_online, + len(self) + getattr(self, '_offline_moved', 0)) + + def groupkey(self): + return self.name.lower() + + @property + def display_string(self): + return self.__str__() + + def __hash__(self): + return hash(self.name)# + str(self.ids)) + +GroupTypes = (Group, DGroup) + +def group_hash(g): + name = g.name.lower() + + if hasattr(g, 'ids'): + if g.ids == [Group.OFFLINE_ID]: + name += g.id + else: + if g.id == Group.OFFLINE_ID: + name += g.id + +# #TODO: ask chris why oscar's root group is named '' sometimes... + + return 'root' if name == '' else name + +def remove_duplicate_contacts(group): + from contacts.Contact import Contact + + if not isinstance(group, DGroup): return + + unique = set() + i = 0 + for con in group[:]: + if isinstance(con, Contact): + chash = con.info_key + + if chash in unique: + group.pop(i) + else: + unique.add(chash) + i += 1 + else: + i += 1 + +def merge_groups(root, grouphash=group_hash, depth=0): + ''' + if hash(group1) == hash(group2) the contents of both are put + into a new group. + ''' + from contacts.metacontacts import MetaContact + assert callable(grouphash) + + group_contents = collections.defaultdict(list) + is_group = {True: [], False: []} + do(is_group[isinstance(x, GroupTypes)].append(x) for x in root) + ordered_names = [] + for g in is_group[True]: + group_contents[grouphash(g)].append(g) + if grouphash(g) not in ordered_names: + ordered_names.append(grouphash(g)) + + del root[:] + + newlist = [] + for _, groups in ((name, group_contents[name]) for name in ordered_names): + # Create a DGroup which preserves information from the original + # protocol groups. + def plural(objects, attr): + ret = [] + for g in objects: + if hasattr(g, attr + 's'): + ret.extend(getattr(g, attr + 's')) + else: + ret.append(getattr(g, attr)) + return ret + + protos = plural(groups, 'protocol') + ids = plural(groups, 'id') + + newgroup = DGroup(groups[0].name, # Choose a name + protos, ids, # All the protocols and ids + sum(groups, [])) # and all the contents. + + merged = merge_groups(newgroup, depth = depth+1) + newlist.append(merged) + + root.extend(newlist) + + # remove "duplicate" contacts--that is, contacts with the same on the same + # protocol that appear under different accounts. If functionality in the + # right click menu is required, we'll have to make some kind of "DContact" + # with multiple protocols. + unique = set() + for con in is_group[False]: + chash = con.info_key + if chash not in unique: + unique.add(chash) + root.append(con) + + return root + +def remove_group(profile, group_name, force = False): + group_name = group_name.lower() + + # Remove all metacontacts in this group. + profile.blist.metacontacts.remove_group(group_name) + + def network(accts): + for acct in accts: + try: + acct.connection.remove_group(group_name) + except Exception, e: + print_exc() + + netcall(lambda: network(profile.connected_accounts)) + diff --git a/digsby/src/contacts/__init__.py b/digsby/src/contacts/__init__.py new file mode 100644 index 0000000..3ec61f0 --- /dev/null +++ b/digsby/src/contacts/__init__.py @@ -0,0 +1,7 @@ +from Contact import Contact +from Group import * +from BuddyListElement import * +from metacontacts import MetaContact + +renamable = (Contact, MetaContact, DGroup) + diff --git a/digsby/src/contacts/buddyinfo.py b/digsby/src/contacts/buddyinfo.py new file mode 100644 index 0000000..d577f7f --- /dev/null +++ b/digsby/src/contacts/buddyinfo.py @@ -0,0 +1,66 @@ +from common import profile +from logging import getLogger; log = getLogger('buddyinfo') + + +class BuddyInfo(object): + __slots__ = ['protocol_name', 'protocol_username', 'buddy_name'] + + def __init__(self, buddy): + protocol = buddy.protocol + self.protocol_name = protocol.name + self.protocol_username = protocol.username + self.buddy_name = buddy.name + + def __eq__(self, obj): + s = object() + for slot in self.__slots__: + if getattr(self, slot) != getattr(obj, slot, s): + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash('_'.join(getattr(self, slot) for slot in self.__slots__)) + + def buddy(self): + protocol = profile.account_manager.get_im_account(self.protocol_username, self.protocol_name) + if protocol is None or not protocol.connected: + return None + + return protocol.connection.get_buddy(self.buddy_name) + + def isbuddy(self, buddy): + return (buddy.name == self.buddy_name and + buddy.protocol.username == self.protocol_username and + buddy.protocol.name == self.protocol_name) + + def __repr__(self): + return '' % (self.buddy_name, self.protocol_name, self.protocol_username) + + +class binfoproperty(object): + ''' + server-side buddy information + ''' + + def __init__(self, name, default = sentinel): + if not isinstance(name, basestring): + raise TypeError + + self.name = name + self.default = default + + def __get__(self, obj, objtype): + res = profile.blist.get_contact_info(obj, self.name) + if res is None and self.default is not sentinel: + res = self.default() + log.info('%s: %s not found, placing default %s', obj.name, self.name, res) + profile.blist.set_contact_info(obj, self.name, res) + + return res + + def __set__(self, obj, value): + return profile.blist.set_contact_info(obj, self.name, value) diff --git a/digsby/src/contacts/buddylistfilters.py b/digsby/src/contacts/buddylistfilters.py new file mode 100644 index 0000000..e8c1a8e --- /dev/null +++ b/digsby/src/contacts/buddylistfilters.py @@ -0,0 +1,55 @@ +''' +Buddylist manipulators that rearrange and filter the buddylist before it makes +it to the screen. +''' + +from contacts.Group import DGroup, Group +from contacts.buddylistsort import SpecialGroup +from common import pref + +class OfflineGroup(SpecialGroup): + 'Used as a place to collect all offline buddies.' + + _renderer = 'DGroup' + + def __init__(self): + DGroup.__init__(self, _('Offline'), [None], [Group.OFFLINE_ID], []) + + def __str__(self): + return u'%s (%d)' % (self.name, len(self)) + + def groupkey(self): + return SpecialGroup.__name__ + '_' + type(self).__name__.lower() + +class FakeRootGroup(SpecialGroup): + '''Used as a place to collect buddies who aren't in other groups''' + _renderer = 'DGroup' + + PREF = 'buddylist.fakeroot_name' + PREFDEFAULT = _('Contacts') + + def __init__(self): + DGroup.__init__(self, pref(self.PREF, default=self.PREFDEFAULT), [None], [Group.FAKEROOT_ID], []) + + def _rename(self, new_name, callback): + self.name = new_name + return SpecialGroup._rename(self, new_name, callback) + + def _get_name(self): + return pref(self.PREF, default=self.PREFDEFAULT) + + def _set_name(self, new_val): + if pref(self.PREF, default=self.PREFDEFAULT) != new_val: + # calling setpref triggers a notify; only do it if it's different + from common import setpref + setpref('buddylist.fakeroot_name', new_val) + + def groupkey(self): + return SpecialGroup.__name__ + '_' + type(self).__name__.lower() + + name = property(_get_name, _set_name) + + def renamable(self): + return True + + diff --git a/digsby/src/contacts/buddylistsort.py b/digsby/src/contacts/buddylistsort.py new file mode 100644 index 0000000..7913fd1 --- /dev/null +++ b/digsby/src/contacts/buddylistsort.py @@ -0,0 +1,29 @@ +from contacts.Group import DGroup +from common import pref +from logging import getLogger; log = getLogger('blistsort'); info = log.info + +def grouping(): + s = pref('buddylist.sortby', 'none none').startswith + + return s('*status') or s('*service') + +class SpecialGroup(DGroup): + _renderer = 'DGroup' + + def groupkey(self): + return self.__class__.__name__ + '_' + DGroup.groupkey(self) + + def renamable(self): + return None + + +STATUS_ORDER = ['available', + 'away', + 'idle', + 'mobile', + 'invisible', + 'offline', + 'unknown'] + +STATUS_ORDER_INDEXES = dict((STATUS_ORDER[i], i) for i in xrange(len(STATUS_ORDER))) + diff --git a/digsby/src/contacts/buddyliststore.py b/digsby/src/contacts/buddyliststore.py new file mode 100644 index 0000000..3aa7d22 --- /dev/null +++ b/digsby/src/contacts/buddyliststore.py @@ -0,0 +1,1424 @@ +''' +buddyliststore.py + + Stores buddylist information. + +Blobs: + This object's data BLOB (accessible on the console as "profile.buddylist") + is sent/received in save_data and update_data. + +Sorting: + See contacts/buddylistsort.py and ext/src/BuddyList. + +Ordering: + The "rearrange" and "rearrange_group" methods order the buddylist manually. + The relevant attribute is the "self.order" dictionary, which has the + following structure: + + self.order = \ + { 'contacts': # Stores contact ordering + { groupname1_lowercase: + [ buddy1_idstring + buddy2_idstring + buddy3_idstring ] + groupname2_lowercase: ... + } + 'groups': # Stores group ordering + [ group1_lower + group2_lower + group3_lower ] + } + + idstrings come from the Contact.idstr() which generally does the right + thing unless they need to be overridden in the Protocol's more specific + contact object. + + Rearranging methods take an object to move, an object to move _near_, and + a string specifying "above" or "below." This makes it easy to maintain + "relative" order through an interface that may be filtering any number + of elements before they're shown visually. + +Contact Info: + "Contact Info" refers to server-side extraneous information stored about a + buddy, like email, or an SMS number. Local things are maintained in a + dictionary in self.info. + +Metacontacts: + See contacts/metacontacts.py and the MetaContactManager class. + +''' +from __future__ import with_statement +from common.protocolmeta import protocols +from collections import defaultdict +from logging import getLogger; +log = getLogger('bliststore') +#tofromlog = getLogger('tofrom') + +from contacts.Group import Group, DGroup, GroupTypes, remove_duplicate_contacts +from contacts.metacontacts import MetaContactManager, MetaContact +from contacts.buddylistfilters import FakeRootGroup +from operator import itemgetter +from traceback import print_exc +from util import Storage, RepeatTimer +from util.primitives.error_handling import try_this, traceguard +from util.primitives.funcs import Delegate, find, isiterable +from util.primitives.structures import oset +from util.cacheable import DiskCache +from util.threads.bgthread import on_thread +from contacts.buddylistsort import SpecialGroup +S = Storage +from copy import deepcopy +from util.observe import Observable +from common import profile, netcall, pref +import blist +import hooks +import metrics +import sort_model + +from contacts.identity import Personality + +import sys +import wx +import re +import warnings + +offline_nonempty_group_re = re.compile('.*\(0/[^0](.*\))$') + + +DisplayGroup = (DGroup, ) + +# how often, in seconds, to sync buddylist blob changes with the server. +UPDATE_FREQ_SECS = 60 * 5 + +# the to/from list updates on a different timer that fires less frequently, +# since sending IMs can change it +TOFROM_UPDATE_FREQ_SECS = 30 + +SEARCH_CONTACTS_GROUPNAME = _('Contacts') + +from contacts.dispatch import ContactDispatcher, default_tofrom + +class BuddyListStore(Observable): + ''' + Stores Metacontact and ordering information for the buddylist. + + conn_accts must be an observable list of connected accounts + ''' + + DROP_END = object() + DROP_BEGINNING = object() + + def get_status(self, name, service): + p = Personality(name, service) + return getattr(self, 'personalities', {}).get(p, 'available') + + def __init__(self, conn_accts): + Observable.__init__(self) + + self.dirty = False + self.sorting_paused = True + + #self._listen_for_pref_load() + + self.rebuild_timer = wx.PyTimer(self.rebuild_later) + self.rebuild_timer.StartRepeating(500) + + # Holds the final view that goes into the TreeList. + self.view = DGroup('__root__', [], []) + self.info = {} + + # Rootgroups are the "protocol" rootgroups with the original, + # unmodified versions of the buddy list structure for each account + self.rootgroups = [] + + self.base_attrs = frozenset([None, 'alias', 'status', 'status_message', 'icon', 'icon_hash', 'entering', 'leaving', 'idle', 'away','mobile']) + self.attrs = set(self.base_attrs) + + # conn_accts must be an observable list of connected accounts + conn_accts.add_observer(self.on_connections_changed) + + self.metacontacts = MetaContactManager(self) + self._init_order() + self.contact_info_changed = Delegate() + + self.dispatch = ContactDispatcher() + self.caches = dict(tofrom = DiskCache('im_history.dat', validator = validate_tofrom)) + self.load_local_data() + + # save local to/from data on exit + hooks.register('digsby.app.exit', self.save_local_data) + + self._search_by = u'' + self._search_results = (0, 0) # number of contacts found in last two searches + + #todo: make then_bys based on a pref + self.sort_models = sort_model.build_models( + then_bys = 1, + obj = self) + self.sbw = sort_model.SortByWatcher(self.sort_models) + + def get_tofrom_copy(self): + return self.dispatch.get_tofrom_copy() + + def _listen_for_pref_load(self): + # resort checks profile.prefs_loaded to make sure we actually have prefs + # before constructing the sorter. (there are events that can happen that + # trigger a resort before prefs are loaded.) make sure to rebuild once + # prefs are actually in. + def on_prefs(_prefs): + if getattr(self, '_prefs_did_load', False): + return + + self._prefs_did_load = True + self.rebuild() + + hooks.register('blobs.update.prefs', on_prefs) + + def __repr__(self): + return '' % len(self.metacontacts) + + def contact_for_id(self, idstr): + '''Returns a contact object for an idstr + + digsby/kevin@digsby.org/aaron@digsby.org => Contact + ''' + + _protocol_name, username, name = idstr.split('/') + + for acct in profile.account_manager.accounts: + if acct.name == username and acct.connection: + return acct.connection.buddies[name] + + def buddy_changed(self, buddy, attr): + if attr is None or attr in self.attrs: + self.rebuild() + + def contact_for_idstr(self, idstr): + ''' + Returns a buddy object for a unique buddy id string. (See + Buddy.idstr) + ''' + contact = self.metacontacts.forid(idstr) + if contact is None: + service, acct_username, buddy_name = idstr.split('/') + + for acct in profile.connected_accounts: + if acct.connection.username == acct_username and acct.protocol == service: + contact = acct.connection.get_buddy(buddy_name) + if contact is not None: + break + + return contact + + def online_popular_buddies(self): + if not hasattr(blist, 'getLogSizes'): + warnings.warn('this platform doesnt have blist.getLogSizes implemented yet') + return [] + + try: + logsizes = self._logsizes + except AttributeError: + logsizes = self._logsizes = sorted(blist.getLogSizes(profile.logger.OutputDir).iteritems(), + key=itemgetter(1), reverse=True) + + # combine log sizes for metacontacts + logsizemap = defaultdict(int) + for c, l in logsizes: + contacts = self.contacts_for_nameservice([c]) + if contacts and contacts[0] is not None: + logsizemap[contacts[0]] += l + + return [c for c, l in sorted(logsizemap.iteritems(), key=itemgetter(1), reverse=True)] + + def contacts_for_nameservice(self, nameservice_seq): + metacontacts = self.metacontacts + conn_accts = profile.connected_accounts + contacts = [] + + for nameserv in nameservice_seq: + contact = metacontacts.forid(nameserv) + if contact is None: + i = nameserv.rfind('_') + if i != -1: + name, service = nameserv[:i], nameserv[i+1:] + + for acct in conn_accts: + try: + compat = im_service_compatible(acct.protocol, service) + except KeyError: + pass + else: + if compat: + proto = acct.connection + if proto is not None: + if proto.has_buddy_on_list(S(name=name, service=service)): + contact = proto.get_buddy(name) + if contact is not None: + break + + contacts.append(contact) + + return contacts + + def track_personalities(self, rootgroups): + # collect IM contacts + personalities = {} + + for root in rootgroups: + for group in root: + proto = root.protocol + if isinstance(group, GroupTypes): + for contact in group: + ident = Personality(contact.name, contact.service) + + try: + personalities[ident].add(proto) + except KeyError: + personalities[ident] = set([proto]) + else: + contact = group + ident = Personality(contact.name, contact.service) + try: + personalities[ident].add(proto) + except KeyError: + personalities[ident] = set([proto]) + + return personalities + + def on_buddylist(self, buddy): + personality = Personality(buddy.name, buddy.service) + + try: + protos = self.personalities[personality] + except KeyError: + return False + else: + return len(protos) > 0 + + Groupers = dict(status = 'ByStatus', + service = 'ByService') + + Sorters = dict(none = 'UserOrdering', + name = 'Alias', + log = 'LogSize', + service = 'Service', + status = 'Status', + online = 'Online') + + BuddySortAttrs = dict(name = 'alias', + log = 'log_size', + service = 'service') + + def _setup_blist_sorter(self): + if hasattr(self, 'new_sorter'): + return + + blist.set_group_type((DGroup, Group, MockGroup)) + self.new_sorter = blist.BuddyListSorter() + self.update_order() + + # link prefs + cb = self._on_blist_sort_pref + link = profile.prefs.link + for prefname in ('buddylist.fakeroot_name', + 'buddylist.show_offline', + 'buddylist.group_offline', + 'buddylist.show_mobile', + 'buddylist.hide_offline_groups', + 'buddylist.sortby'): + link(prefname, cb) + + cb() + assert on_thread('sorter').now + self._reconfig_sorter(False) + + def search(self, s, cb = None): + if not hasattr(self, 'new_sorter'): + return + assert isinstance(s, basestring) + self._search_by = s + self.reconfig_sorter(rebuild=False) + self.rebuild_now() + + if cb is not None: + on_thread('sorter').call(lambda: wx.CallAfter(cb, self._search_results)) + + @property + def search_string(self): + return getattr(self, '_search_by', '') + + def _on_blist_sort_pref(self, val=None): + self.reconfig_sorter() + + @on_thread('sorter') + def reconfig_sorter(self, rebuild = True): + self._reconfig_sorter(rebuild = rebuild) + + def _reconfig_sorter(self, rebuild = True): + if not hasattr(self, '_rebuild_sorter_count'): + self._rebuild_sorter_count = 0 + log.debug('rebuilding sorter %d', self._rebuild_sorter_count) + self._rebuild_sorter_count += 1 + + sorts = pref('buddylist.sortby') + assert isinstance(sorts, basestring) + sorts = sorts.split() + + search = getattr(self, '_search_by', '') + + s = self.new_sorter + s.clearSorters() + + show_offline = pref('buddylist.show_offline') + group_offline = pref('buddylist.group_offline') + show_mobile = pref('buddylist.show_mobile') + hide_offline_groups = pref('buddylist.hide_offline_groups') + + if search: + # search by + show_offline = True + s.addSorter(blist.ByGroup(False, 2)) + s.addSorter(blist.BySearch(search, SEARCH_CONTACTS_GROUPNAME)) + else: + if not sorts[0].startswith('*'): + s.addSorter(blist.ByFakeRoot(pref('buddylist.fakeroot_name', default=_('Contacts')))) + s.addSorter(blist.ByGroup(not sorts[0].startswith('*'), 2)) + + + #until status grouper can do it, always add a mobile filter. + #mobile needs to happen before the status grouper (otherwise you may see a mobile group for now) + if not search and not show_mobile: + s.addSorter(blist.ByMobile(show_mobile)) + + # Add any necessary groupers + added_status_grouper = False + if not search and sorts[0].startswith('*'): + show_groups = True + sorter = sorts[0][1:] + grouper = self.Groupers.get(sorter) + if grouper is not None: + if sorter == 'status': + # pass showOffline flag to ByStatus grouper + args = (show_offline, ) + added_status_grouper = True + else: + args = () + + grouper_obj = getattr(blist, grouper)(show_groups, *args) + + if sorter == 'service': + # Set group names on the ByService grouper + for service, protocolinfo in protocols.iteritems(): + grouper_obj.setGroupName(service, protocolinfo['name']) + + s.addSorter(grouper_obj) + + # Comparators + sorters = [blist.CustomOrder] + + if search: + # move offline buddies to the bottom when searching, and sort alphabetically + sorts = ['online', 'name'] + else: + # If we're grouping offline buddies, or filtering them out, + # and we didn't add a ByStatus grouper, then we need to add a simpler + # ByOnline grouper that accomplish the same things. + # And, btw, we're "always" grouping offline buddies, we need the counts. + if not added_status_grouper: + s.addSorter(blist.ByOnline(group_offline, show_offline)) + + # Always sort by user order. + if sorts[-1] != 'none': + sorts.append('none') + + self.attrs = set(self.base_attrs) + cmpnames = [] + for sort in sorts: + if sort.startswith('*'): + #continue + sort = sort[1:] + cmpname = self.Sorters[sort] + cmpnames.append(cmpname) + + # make sure we rebuild when buddy attributes change that are important + # to the sorter + sort_attr = self.BuddySortAttrs.get(sort, None) + if sort_attr is not None: + self.attrs.add(sort_attr) + + sorters.append(getattr(blist, cmpname)) + + log.debug('comparators are: %s', ', '.join(cmpnames)) + + self.comparators = sorters + s.setComparators(sorters) + if pref('buddylist.hide_offline_dependant', False, bool): + s.setPruneEmpty(not pref('buddylist.show_offline') and pref('buddylist.hide_offline_groups')) + else: + s.setPruneEmpty(pref('buddylist.hide_offline_groups')) + + if rebuild: + self.rebuild_threaded() + + def rebuild(self, *a): + 'Triggers a full buddylist update.' + + if on_thread('sorter').now: + # sorter thread should never trigger a rebuild + return + + self.dirty = True + + def rebuild_later(self, *a): + if not self.dirty: return + self.dirty = False + self.rebuild_threaded() + + def rebuild_now(self): + self.dirty = False + self.rebuild_threaded() + + @on_thread('sorter') + def rebuild_threaded(self): + if getattr(self, 'disable_gui_updates', False): + return + + view = self.resort() + wx.CallAfter(self.setnotify, 'view', view) + + def by_log_size(self): + 'Returns a list of MetaContacts and Contacts by log size.' + + sorter = blist.BuddyListSorter() + sorter.addSorter(blist.ByGroup(False)) + sorter.addSorter(blist.ByOnline(True, False)) + sorter.setComparators([blist.LogSize]) + return remove_contacts_in_metacontacts(self.use_sorter(sorter)) + + def safe_metacontacts(self, rootgroups, use_cached=False): + default = lambda: DGroup('Root', [], [], []) + if use_cached: + try: + return self.collected_metacontacts + except AttributeError: + pass + + try: + self.collected_metacontacts = metacontacts = self.metacontacts.collect(*rootgroups) + except Exception: + # If there was an exception collecting metacontacts, + # just proceed. + print_exc() + metacontacts = DGroup('Root', [], [], []) + + return metacontacts + + def use_sorter(self, sorter): + rootgroups = [display_copy(g) for g in self.rootgroups if isinstance(g, GroupTypes)] + newroots = rootgroups[:] + [self.safe_metacontacts(rootgroups, use_cached=True)] + for i, root in enumerate(newroots): + root.name = "Root" + str(i) + root._root = True + + root = DGroup('none', [], [], newroots) + sorter.set_root(root) + view = get_view_from_sorter(sorter) + for g in view: + remove_duplicate_contacts(g) + return view + + def resort(self, mock = False): + assert on_thread('sorter').now + + rootgroups = [display_copy(g) for g in self.rootgroups if isinstance(g, GroupTypes)] + self.personalities = self.track_personalities(rootgroups) + + metacontacts = self.safe_metacontacts(rootgroups) + + # Always collect metacontacts, but exit early here if sorting is paused. + if self.sorting_paused:# or not profile.prefs_loaded: + return + + metrics.event('Buddylist Sort') + + self._setup_blist_sorter() + + # invalidate all sorter knowledge of contacts. + # results in more CPU usage, but until we put metacontact combining into the sorter + # this might be necessary. + self.new_sorter.removeAllContacts() + + newroots = rootgroups[:] + [metacontacts] + for i, root in enumerate(newroots): + root.name = "Root" + str(i) + root._root = True + root = DGroup('none', [], [], newroots) + if mock: self.mock_root = make_mocklist(root) + self.new_sorter.set_root(root) + + view = get_view_from_sorter(self.new_sorter) + + if getattr(self, '_search_by', ''): + if len(view) > 0: + contacts_group = view[0] + + # don't allow renaming, etc of the search "Contacts" group + contacts_group._disallow_actions = True + num_contacts = len(contacts_group) + else: + num_contacts = -1 + + self._search_results = self._search_results[1], num_contacts + else: + if pref('buddylist.hide_offline_dependant', False, bool): + hide_offline_groups = not pref('buddylist.show_offline') and pref('buddylist.hide_offline_groups') + else: + hide_offline_groups = pref('buddylist.hide_offline_groups') + if hide_offline_groups: + view[:] = filter((lambda g: not offline_nonempty_group_re.match(g.display_string)), view) + + for g in view: remove_duplicate_contacts(g) + + self.add_search_entries(view) + + hooks.notify('buddylist.sorted', view) + + return view + + def add_search_entries(self, view): + search = getattr(self, '_search_by', '') + if not search: + return + + if not getattr(self, '_did_search_link', False): + self._did_search_link = True + profile.prefs.link('search.external', self.on_search_pref) + + from gui.searchgui import add_search_entries + add_search_entries(view, search) + + def on_search_pref(self, val): + self.rebuild_now() + + @on_thread('sorter') + def set_fake_root(self, root): + self.new_sorter.set_root(root) + + try: + old_view = self._old_view + except AttributeError: + pass + else: + self.new_sorter._done_gather(old_view) + + self._old_view = _rootgroup = self.new_sorter._gather() + view = _newsortgroups_to_dgroups(_rootgroup) + wx.CallAfter(self.setnotify, 'view', view) + + def imaccts_for_buddy(self, buddy, contacts, force_has_on_list=False): + ''' + For a buddy, returns a tuple of + + - the best account to IM that buddy from, given history (may be None) + - a list of connected accounts which can message that buddy (may be empty) + ''' + + return self.dispatch.imaccts_for_buddy(buddy, contacts, force_has_on_list) + + # + # buddylist ordering + # + + def rearrange(self, clist_obj, area, to_group, drop_to): + 'Rearranges manual ordering.' + + with traceguard: log.info('moving %s %s %s', clist_obj, area, drop_to) + + self._update_order_from_sorter() + + # get the list of ordered keys for a group + grp_key = to_group.groupkey() + + # HACK: effectively, groups that share a name with the fake root group do not exist. + # side effect: if you rename the fake root group w/o being logged into another account that + # has a group which shares it's name, the ordering for that group is lost. + if grp_key.lower() == pref('buddylist.fakeroot_name', default=_('Contacts')).lower(): + grp_key = FAKE_ROOT_GROUP_KEY + # end HACK. + + order = self.order['contacts'][grp_key] + + # rearrange a Contact, using it's idstr + obj = clist_obj.idstr() + + # index of the thing you're moving, otherwise end + i = try_this(lambda: order.index(obj), len(order)) + + # index of where to insert + if drop_to is self.DROP_BEGINNING: + j = 0 + elif drop_to is self.DROP_END: + j = len(order) + else: + j = try_this(lambda: order.index(drop_to.idstr()) + + (1 if area == 'below' else 0), 0) + + #if destination is farther than current position, + #we will leave a hole, account for it. + if j > i: j -= 1 + + with traceguard: + log.info('rearranging buddies in %s (groupkey=%s): %d -> %d', + to_group.name, grp_key, i, j) + + if i != len(order): + order.pop(i) + + order.insert(j, obj) + self._info_changed() + self.update_order() + self.rebuild_now() + + def rearrange_group(self, group, area, togroup): + ''' + Move a group above or below another group. + + area should be "above" or "below" + ''' + + if not isinstance(group, DisplayGroup) or not isinstance(togroup, DisplayGroup): + raise TypeError('group and togroup must be DisplayGroups: %r %r' % (group, togroup)) + + order = self.order['groups'] + + try: i = order.index(groupkey(group)) + except ValueError: found = False + else: found = True + + + #index of where to insert + j = try_this(lambda: order.index(groupkey(togroup)) + (1 if area == 'below' else 0), 0) + if found and j > i: + j -= 1 + + #log.info('moving group %r from %r to %r', group, i, j) + + if found: + popped = order.pop(i) + else: + popped = groupkey(group) + + order.insert(j, popped) + + self._info_changed() + self.update_order() + self.rebuild_now() + + def merge_server_list(self, protocol): + 'Merges server-side changes with local data.' + # from #513 + #x If a buddy is no longer in on the list, remove them from the merged buddy list + #x If a buddy was added, add them to the merged buddy list object + #x If a buddy is in another group, move them in the merged buddy list object + # If that buddy was in a metacontact, split them off as if the user did it (keeps events, sms numbers, email addresses of the metacontact) + #x If a buddy is in a different position, move them as best as we can. + + root = getattr(protocol, 'root_group', None) + if not root: + log.error('merge_server_list: %s has no root_group', protocol) + return False + + idstr_prefix = protocol.name + '/' + protocol.username + + # Contact orderings + corder = self.order['contacts'] + + # a set of all contact id strings + all_contacts = set(c.idstr() for group in root if isinstance(group, DisplayGroup) for c in group) + + # lookup dict of { contact id string : metacontact entry } + def cdict_to_idstr(s): return '/'.join([s['protocol'], s['username'], s['name']]) + + metas = dict((cdict_to_idstr(contact_dict), (id, meta)) + for id, meta in self.metacontacts.iteritems() + for contact_dict in meta['contacts']) + + for group in root: + if not isinstance(group, DisplayGroup): continue + order = corder[group.groupkey()] + server_order = [c.idstr() for c in group] + + # Add any new buddies from the server. + before = None + network_cids = set() + for cid in server_order: + network_cids.add(cid) + + try: + before = order.index(cid) + except ValueError: + # this contact is new. insert it into the ordering at the + # correct location + order.insert(find(order, before) + 1, cid) + before = cid + + if protocol.contact_order: + # attempt to order based on the server list as much as possible. + order_lists(order, order_for_account(protocol, server_order)) + + # if buddy is no longer in the buddy list, remove them + for cid in list(order): + if cid.startswith(idstr_prefix) and cid not in network_cids: + log.info(cid + ' has moved out of group %s', group.name) + if cid in metas: + # remove the contact from its metacontact, if it's in one. + self.metacontacts.remove(metas[cid][0], cid) + + order.remove(cid) + + return True + + # + # to/from history list + # + + def add_tofrom(self, history_type, to, from_): + 'Add a new entry in the to/from history list.' + + self.dispatch.add_tofrom(history_type, to, from_) + self._info_changed('tofrom_timer', TOFROM_UPDATE_FREQ_SECS) + + def get_from(self, tobuddy, connected=True): + 'Return the preferred account to message a buddy from, based on message history.' + + return self.dispatch.get_from(tobuddy, connected=connected) + + def get_tofrom_email(self, buddy): + ''' + Given an email address, returns the last email account to have "Composed" + an email to that address. + ''' + + return self.dispatch.get_tofrom_email(buddy) + + def get_tofrom_sms(self, buddy): + return self.dispatch.get_tofrom_sms(buddy) + + def rename_group(self, old_name, new_name): + ''' + Renames a group in the buddylist order. + ''' + self._update_order_from_sorter() + + old_name = old_name.lower() + new_name = new_name.lower() + grps = self.order['groups'] + + self.metacontacts.rename_group(old_name, new_name) + try: + i = grps.index(old_name) + except ValueError: + pass + else: + try: + j = grps.index(new_name) + except ValueError: + grps[i] = new_name + else: + grps[i] = new_name + grps.pop(j) + self._info_changed() + + + + # + # contact info methods + # + + def set_contact_info(self, contact, attr, val): + 'Set server-side information for a contact.' + + _contact = contact + + if not isinstance(attr, basestring): raise TypeError('attr must be a string') + + if isinstance(contact, int): + key = self.metacontacts.idstr(contact) + else: + key = getattr(contact, 'info_key', contact) + + if key is None: + with traceguard: + assert False, 'set_contact_info is trying to store data for a None contact: called with contact=%r, attr=%r, val=%r' % (_contact, attr, val) + return + + log.info("info[%r][%r] = %r", key, attr, val) + + # None removes the key. + if val is None: + self.info[key].pop(attr, None) + else: + self.info[key][attr] = val + + self.contact_info_changed(key, attr, val) + self._info_changed() + + def get_contact_info(self, contact, attr): + 'Get server-side information for a contact.' + + if isinstance(contact, int): + contact = self.metacontacts.idstr(contact) + + key = getattr(contact, 'info_key', contact) + + if key in self.info: + info = self.info[key] + if attr in info: + return info[attr] + + def remove_contact_info(self, contact): + 'Remove server-side information for a contact.' + + self._update_order_from_sorter() + + try: + info_key = getattr(contact, 'info_key', contact) + del self.info[info_key] + + found_one = False + for l in self.order['contacts'].itervalues(): + if info_key in l: + l.remove(info_key) + found_one = True + + if found_one: + self.update_order() + except KeyError: + log.info("remove_contact_info: there was no contact info for %s", contact) + + def on_connections_changed(self, *a): + 'Invoked when the DigsbyProfile connected_accounts attribute changes size.' + + accts = [a for a in profile.account_manager.accounts + [profile] if a.is_connected] + + log.info('connected accounts changed, getting root groups from %r', accts) + + rebuild = self.rebuild + + # unobserve all old rootgroups + for grp in self.rootgroups: + grp.remove_observer(rebuild) + + self.rootgroups = filter(lambda g: g is not None, + [getattr(a.connection, 'root_group', None) for a in accts]) + + # observe all new rootgroups + for grp in self.rootgroups: + grp.add_observer(rebuild) + + self.rebuild() + + # + # server syncing + # + + def _info_changed(self, timer_name = 'network_timer', update_frequency = UPDATE_FREQ_SECS): + ''' + Called when information in the buddylist store object changes. + + It resets a timer, and every so many seconds after no changes have occurred, + the changes are synced with the server. + ''' + + t = getattr(self, timer_name, None) + if t is None: + t = RepeatTimer(update_frequency, lambda: self._on_timer(timer_name)) + setattr(self, timer_name, t) + t.start() + else: + t.reset() + + def _on_timer(self, timer_name): + t = getattr(self, timer_name, None) + if t is not None: + t.stop() + + if timer_name == 'network_timer': + log.info('timer fired. saving buddylist blob...') + netcall(lambda: profile.save('buddylist')) + elif timer_name == 'tofrom_timer': + log.info('local timer fired. saving tofrom to disk') + wx.CallAfter(self.save_local_data) + else: + assert False, 'invalid timer name' + + def reset_tofrom(self): + '''clears tofrom data''' + + self.dispatch.set_tofrom(default_tofrom()) + self._info_changed('tofrom_timer', TOFROM_UPDATE_FREQ_SECS) + + def load_local_data(self): + self.dispatch.set_tofrom(self.caches['tofrom'].safe_load(default_tofrom)) + log.info('TOFROM: loaded to/from data') + + def save_local_data(self): + log.info('TOFROM: saving tofrom data') + + with self.dispatch.lock_all_data(): + self.caches['tofrom'].save(self.dispatch.tofrom) + + def update_data(self, data): + """ + Updates this store's current state with incoming data from the network. + + data should be a mapping containing 'metacontacts', 'order', and 'info' + structures (see comment at top of file) + """ + rebuild = False + + # This method needs to substitute some defaultdicts for the normal + # dictionaries that come back from the server. + + # Metacontact information + + #if data['metacontacts'] + mc_dict = data.get('metacontacts', {}) + if not isinstance(mc_dict, dict): + log.critical('invalid metacontacts dictionary') + mc_dict = {} + + # Contact information like SMS numbers and email addresses. + self.info = defaultdict(dict) + + si = self.info + if 'info' in data: + for (k, v) in data['info'].iteritems(): + if isinstance(k, str): + cmpk = k.decode('utf8') + else: + cmpk = k + + if not isinstance(cmpk, unicode): + continue + + if cmpk.startswith('Meta') or any((cmpk.endswith('_' + prot) + for prot in protocols.iterkeys())): + if any(v.values()): + si[k] = v + + for c, v in si.iteritems(): + for attr in ('email', 'sms'): + if attr in v: + self.contact_info_changed(c, attr, v[attr]) + + self.metacontacts = MetaContactManager(self, mc_dict) + if hasattr(self, 'new_sorter'): + on_thread('sorter').call(self.new_sorter.removeAllContacts) + rebuild = True + + # Manual ordering of groups + try: + self.order = deepcopy(data['order']) + self.order['groups'] = list(oset(self.order['groups'])) + contacts = self._filtered_contacts() + self.order['contacts'] = defaultdict(list) + self.order['contacts'].update(contacts) + except Exception: + log.critical('error receiving order') + self._init_order() + + # note: loading tofrom data from the network is deprecated. this data + # now goes out to disk. see save/load_local_data + if 'tofrom' in data and isinstance(data['tofrom'], dict) and \ + 'im' in data['tofrom'] and 'email' in data['tofrom']: + self.dispatch.set_tofrom(deepcopy(data['tofrom'])) + + if rebuild: + self.rebuild() + + self.update_order() + + @property + def user_ordering(self): + ''' + Returns True when the buddies should be partially ordered by their + server-side ordering. (i.e., when the user has "None" selected) + ''' + return all(cmp in (blist.CustomOrder, blist.UserOrdering) for cmp in getattr(self, 'comparators', ())) + + def update_order(self): + 'Sends order to the sorter.' + + if hasattr(self, 'new_sorter'): + order = deepcopy(self.order['contacts']) + order['__groups__'] = self.order['groups'] + on_thread('sorter').call(self.new_sorter.set_contact_order, order) + + def _update_order_from_sorter(self): + 'Retrieves order from the sorter.' + + # TODO: the sorter is not threadsafe. its internal contact order map needs a lock. + + if hasattr(self, 'new_sorter'): + order = self.new_sorter.get_contact_order() + self.order['groups'] = order.pop('__groups__', []) + contacts = defaultdict(list) + contacts.update(order) + self.order['contacts'] = contacts + + def _filtered_contacts(self): + return dict((groupname, filter(lambda c: not c.endswith('guest.digsby.org'), list(oset(ordering)))) + for groupname, ordering in self.order['contacts'].iteritems()) + + + def _init_order(self): + groups = [] + + # Initialize a "group by status" order + from contacts.buddylistsort import STATUS_ORDER + for status in STATUS_ORDER: + groups.append(SpecialGroup(status).groupkey()) + + self.order = dict(groups = groups, + contacts = defaultdict(list), + info = {}) + + def save_data(self): + "Returns the data to saved to the Digsby server." + + self._update_order_from_sorter() + + return dict(metacontacts = self.metacontacts.save_data(), + order = dict(contacts = self._filtered_contacts(), + groups = self.order['groups']), + + # leaves out default dict emptiness + info = dict(((k, v) for k, v in self.info.iteritems() + if v and any(v.values()) and k is not None))) + + def set_sort_paused(self, paused): + ''' + All sorting will be paused if you pass True. + Passing False triggers a rebuild. + ''' + if self.sorting_paused == paused: + return + + self.sorting_paused = paused + log.debug('setting sort paused: %r', paused) + if not paused: + # Call rebuild_threaded here, so the sort happens instantly, instead + # of maybe after .5 secs. + self.rebuild_now() + +from common.protocolmeta import SERVICE_MAP + +def display_copy(group): + 'Turns Groups into DGroups.' + + elems = [] + + for elem in group: + if isinstance(elem, Group): + elems.append(display_copy(elem)) + else: + elems.append(elem) + + return DGroup(group.name, [group.protocol], [group.id], elems) + +# Return the unique "hash" for a group. +groupkey = lambda a: a.groupkey() + +def _flatten(L, forbidden = ()): + # some loop variables + i, size = 0, len(L) + + while i < size: + if isiterable(L[i]) and not isinstance(L[i], forbidden): + L[i:i+1] = L[i] + size = len(L) + else: + i += 1 + + return L + +def order_lists(seq, ref): + def idx(seq, ref, elem): + try: + return ref.index(elem) + except ValueError: + try: + return len(ref) + seq.index(elem) + except ValueError: + return len(ref) + + seq.sort(key = lambda elem: idx(seq, ref, elem)) + +def order_for_account(protocol, ordering): + from util import repr_exception + + proto, un = protocol.name, protocol.username + filtered = [] + + for cid in ordering: + if '/' not in cid and cid.startswith(MetaContact.idstr_prefix): + pass + else: + with repr_exception(cid): + protocol_name, username = cid.split('/')[:2] + + if proto == protocol_name and un == username: + filtered.append(cid) + + return filtered + +tofrom_entry_length = dict( + im = 4, + email = 3, + sms = 3, +) + +def validate_tofrom(tofrom): + ''' + Validate the data in a "tofrom" dict. + + {'im': [('buddy', 'service', 'buddy', 'service'), ...], + 'email': + 'sms'} + ''' + if not isinstance(tofrom, dict): + raise TypeError('tofrom should be a dict, instead is a %r: %r' % (tofrom.__class__, tofrom)) + + for key in ('im', 'email', 'sms'): + for history_entry in tofrom[key]: + if not isinstance(history_entry, list): + raise TypeError + + entries = [] + if len(history_entry) != tofrom_entry_length[key]: + raise TypeError('invalid length') + elif not all(isinstance(s, basestring) for s in history_entry): + raise TypeError('each history entry should be a sequence of strings') + + return tofrom + +def im_service_compatible(to_service, from_service): + ''' + Returns True if a buddy on to_service can be IMed from a connection to from_service. + ''' + + return to_service in protocols[from_service].compatible + +def get_account_name(a): + # TODO: profile.name is different than any other account.name in that it doesn't include name@digsby.org + if a is profile(): + return a.name + '@digsby.org' + else: + return a.name + +def rearrange_meta_by_first_online(metacontact): + ''' + Given a metacontact, return a list of Contacts with the Metacontact's + first_online buddy first. + ''' + + first = getattr(metacontact, 'first_online', None) + + if first is not None: + metacontact = list(metacontact) + try: + metacontact.remove(first) + except ValueError: + pass + metacontact.insert(0, first) + + return metacontact + +def best_im_connection(metacontact, connected_accounts, tofrom): + ''' + Returns a (buddy, connection) pair best suited for IMing with any of the buddies in metacontact, + a sequence of buddy objects. + + "Best" here is defined as the most recent buddy you IMed, based on the to/from history. + + If a best contact cannot be found that way, then the first compatible connection (defined by the + im_service_compatible function) with the first possible buddy in the metacontact sequence is + returned. + ''' + + # Search for tobuddy in the listing of IM history + for bname, bservice, fromname, fromservice in tofrom: + for bud in metacontact: + if bud.online and bud.name == bname and bud.service == bservice: + # if listing found see if the associated account is online + for acct in connected_accounts: + if get_account_name(acct) == fromname and acct.protocol == fromservice: + return bud, acct.connection + + # Make sure the first online buddy is first in the sequence. + metacontact = rearrange_meta_by_first_online(metacontact) + + # If no to/from history could find an exact match for this buddy, see if + # any currently connected accounts are compatible. + for acct in connected_accounts: + for bud in metacontact: + if im_service_compatible(bud.service, acct.protocol): + return bud, acct.connection + + return metacontact[0], None + +FAKE_ROOT_GROUP_KEY = blist.fakeRootGroupKey() + +class SortGroup(DGroup): + def __init__(self, name, protocols = [], ids = [], groupkey=None,*children): + DGroup.__init__(self, name, protocols, ids, *children) + self._groupkey = groupkey + + def groupkey(self): + if self._groupkey is None: + return DGroup.groupkey(self) + return self._groupkey + + def renamable(self): + from gui.buddylist.buddylistrules import SpecialGroup_TEST + if not SpecialGroup_TEST(self): + return True + #else: return None #(not visible) + +def _newsortgroups_to_dgroups(g): + ''' + While we transition to C++ sorting, we still need to make DGroups + to keep the rest of Digsby happy. + ''' + protocol_ids = g._protocol_ids + + if protocol_ids is not None: + protos, ids = g._protocol_ids + else: + protos, ids = [], [] + + children = [] + for elem in g: + if isinstance(elem, blist.Group): + elem = _newsortgroups_to_dgroups(elem) + children.append(elem) + + groupkey = g.groupkey() + if groupkey == FAKE_ROOT_GROUP_KEY: + group = SpecialGroup(g.name, [None] + protos, ['__fakerootgroup__'] + ids, children) + group.__class__ = FakeRootGroup + assert group.groupkey() == groupkey + else: + group = SortGroup(g.name, protos, ids, groupkey, children) + group._new_display_string = g.display_string + return group + +buddy_attrs = ('name', 'alias', 'log_size', 'status', 'service', 'mobile') + +### + +class MockBuddy(object): + def __init__(self, **attrs): + self.__dict__.update(attrs) + self.attrs = attrs.keys() + + def __repr__(self): + attrs_str = ', '.join('%s=%r' % (k, getattr(self, k)) + for k in self.attrs) + + return 'MockBuddy(%s)' % attrs_str + + _notify_dirty = property(lambda self: True, lambda self, val: None) + +def make_mockbuddy(b): + buddy = MockBuddy(**dict((a, getattr(b, a)) for a in buddy_attrs)) + buddy.protocol = make_mockprotocol(b.protocol) + buddy.attrs.append('protocol') + + return buddy + +class MockGroup(list): + def __init__(self, name, protocol, id, children=None): + self.name = name + self.protocol = protocol + self.id = id + if children: + self[:] = children + + def __repr__(self): + return 'MockGroup(%r, %r, %r, %s)' % \ + (self.name, self.protocol, self.id, list.__repr__(self)) + +def make_mockgroup(g): + return MockGroup(g.name, make_mockprotocol(g.protocol), str(g.id)) + +class MockProtocol(object): + def __init__(self, username, service): + self.username = username + self.service = service + + def __repr__(self): + return 'MockProtocol(%r, %r)' % (self.username, self.service) + +def make_mockprotocol(p): + if p is None: + return p + + return MockProtocol(p.username, p.service) + +def make_mocklist(root): + if isinstance(root, GroupTypes): + res = make_mockgroup(root) + res.extend([make_mocklist(c) for c in root]) + else: + res = make_mockbuddy(root) + + return res + +def mocklist(): + from common import profile + + profile.blist.resort(mock=True) + return profile.blist.mock_root + +def dump_elem_tree(e, indent = 0, maxwidth=sys.maxint): + s = '' + + from contacts.Group import Group, DGroup + import blist + + GroupTypes = (Group, DGroup, blist.Group) + space = ' |-' * indent + s += space + if isinstance(e, GroupTypes): + s += e.display_string[:maxwidth] + '\n' + s += ''.join(dump_elem_tree(a, indent + 1, maxwidth) for a in e) + else: + s += repr(e)[:maxwidth] + '\n' + + return s + +def get_view_from_sorter(sorter): + _rootgroup = sorter._gather() + try: + view = _newsortgroups_to_dgroups(_rootgroup) + + finally: + sorter._done_gather(_rootgroup) + + # tracebacks trying to repr(_rootgroup) will crash + del _rootgroup + + return view + +def remove_contacts_in_metacontacts(view): + '''Removes any contacts in "view" that are also in MetaContacts in the view.''' + + metacontacts = set() + for contact in view: + if isinstance(contact, MetaContact): + metacontacts.update(c for c in contact) + return [c for c in view if c not in metacontacts] + + diff --git a/digsby/src/contacts/contactsdnd.py b/digsby/src/contacts/contactsdnd.py new file mode 100644 index 0000000..e354b44 --- /dev/null +++ b/digsby/src/contacts/contactsdnd.py @@ -0,0 +1,42 @@ +from traceback import print_exc +import wx + +BLIST_ITEM_DATAOBJECT_FMT = 'BuddyListItem' + +_df = None +def dataformat(): + global _df + if _df is None: + _df = wx.CustomDataFormat(BLIST_ITEM_DATAOBJECT_FMT) + return _df + + +def dataobject(): + return wx.CustomDataObject(dataformat()) + +def add_to_dataobject(data, blist_item): + ''' + given a buddylist item, potentially adds a wx.CustomDataObject to the given + wx.DataObjectComposite + ''' + + if hasattr(blist_item, 'idstr'): + try: + strdesc = blist_item.idstr() + if isinstance(strdesc, unicode): strdesc = strdesc.encode('utf8') + except Exception: + print_exc() + else: + obj = dataobject() + obj.SetData(strdesc) + data.Add(obj) + + # TextDataObject for buddy name + try: + name = unicode(getattr(blist_item, 'alias', blist_item.name)).encode('utf-8') + tdata = wx.TextDataObject(name) + except Exception: + print_exc() + else: + data.Add(tdata) + diff --git a/digsby/src/contacts/dispatch.py b/digsby/src/contacts/dispatch.py new file mode 100644 index 0000000..aade8ba --- /dev/null +++ b/digsby/src/contacts/dispatch.py @@ -0,0 +1,255 @@ +import contextlib +import common +from common.protocolmeta import protocols +from contacts.metacontacts import MetaContact +from copy import deepcopy +from util import Storage, traceguard +from util.primitives.funcs import make_first +from threading import RLock +S = Storage +from common import profile + +from logging import getLogger; +log = getLogger('dispatch') +#tofromlog = getLogger('tofrom') + +from common.protocolmeta import REVERSE_SERVICE_MAP, SERVICE_MAP + +class ContactDispatcher(object): + _locknames = ('im', 'email', 'sms') + + def __init__(self, profile=None): + self.locks = dict((t, RLock()) for t in self._locknames) + self.tofrom = default_tofrom() + self.profile = common.profile if profile is None else profile + + def set_tofrom(self, tofrom): + with self.lock_all_data(): + self.tofrom = tofrom + + @contextlib.contextmanager + def lock_all_data(self): + locks = [self.locks[name] for name in self._locknames] + with contextlib.nested(*locks): + yield + + def get_tofrom_copy(self): + with self.lock_all_data(): + return deepcopy(self.tofrom) + + def imaccts_for_buddy(self, buddy, contacts, force_has_on_list=False): + # Find the best matching From account. + contact, fromacct = self.get_from(buddy, only_on_list=force_has_on_list) + + if contact is None or fromacct is None: + log.critical('get_from returned (%r, %r) for %r', contact, fromacct, buddy) + contact, fromacct = self.get_from(contacts, only_on_list=force_has_on_list) + + if contact is None: + contact = buddy + + srv = contact.service + + # Pick account services to show. + services = [k for (k,v) in SERVICE_MAP.items() if srv in v] + + protos = [a.connection for a in self.profile.connected_accounts + if a.protocol in services] + + if fromacct is None: + bproto = buddy.protocol + for a in protos: + if a.username == bproto.name and a.name == bproto.protocol: + fromacct = a + log.info('Found exact match for %r: %r', buddy, a) + break + if fromacct is None: + if force_has_on_list: + for proto in protos: + if proto.has_buddy_on_list(buddy): + fromacct = proto + break + elif protos: + fromacct = protos[0] + + return fromacct, contact, protos + + # + # to/from history list + # + + def add_tofrom(self, history_type, to, from_): + 'Add a new entry in the to/from history list.' + + if history_type not in ('im', 'email', 'sms'): + raise ValueError + + tofrom = self.tofrom[history_type] + + entries = dict(# IM: buddy name, buddy service, from username, from service name + im = lambda: (to.name, to.service, from_.username, from_.name), + + # EMAIL: email address, from email username, from protcol + email = lambda: (to, from_.name, from_.protocol), + + # SMS: tosmsnumber, from username, from protocol + sms = lambda: (to, from_.username, from_.name)) + + entry = entries[history_type]() + + log.info('making %r first in to/from list', (entry,)) + + with self.locks[history_type]: + make_first(tofrom, entry) + + def get_from(self, tobuddy, connected=True, only_on_list=False): + 'Return the preferred account to message a buddy from, based on message history.' + + #if tobuddy is a metacontact use first online + if isinstance(tobuddy, MetaContact): + metacontact = tobuddy + tobuddy = metacontact.first_online + + if tobuddy is None: + tobuddy = metacontact[0] + + #if tobuddy is a list, use the first buddy + elif isinstance(tobuddy, list): + tobuddy = tobuddy[0] + + #All connected accounts + connected = getattr(self.profile.account_manager, 'connected_accounts' if connected else 'accounts') + + #All services that are compatible with the current tobuddy + compatible = REVERSE_SERVICE_MAP[tobuddy.service] + + #All accounts that are online and compatible with the current buddy + conncomp = [account for account in connected if account.protocol in compatible] + + #if no online and compatible accounts are found return None + if not conncomp: + return tobuddy, None + + #Finding and return combination of tobuddy and one of the applicable accounts in history + with self.locks['im']: + for (bname, bservice, fromname, fromservice) in self.tofrom['im']: + if tobuddy.name == bname and tobuddy.service == bservice: + for account in conncomp: + if account.connection.protocol == fromservice and account.connection.username == fromname: + return tobuddy, account.connection + + if only_on_list: + return None, None + + # if the buddy is online, choose an account that sees the buddy as online + with traceguard: + for account in conncomp: + conn = account.connection + if conn.get_protocol_buddy(tobuddy).online: + return tobuddy, conn + + # First, find an account with the buddy on its list + with traceguard: + for account in conncomp: + if account.connection.has_buddy_on_list(tobuddy): + return tobuddy, account.connection + + #Find first online and compatible account that has the same service as buddy + for account in conncomp: + if account.protocol == tobuddy.service: + return tobuddy, account.connection + + #Return first compatible account as a last resort + return tobuddy, conncomp[0].connection + + def get_tofrom_email(self, buddy): + ''' + Given an email address, returns the last email account to have "Composed" + an email to that address. + ''' + emailaccts = self.profile.emailaccounts + emails = self.get_contact_info(buddy, 'email') or [] + + def findacct(username, proto): + for acct in emailaccts: + if acct.name == username and acct.protocol == proto: + return acct + + with self.locks['email']: + for (to, fromuser, fromproto) in self.tofrom['email']: + for email in emails: + if to == email: + acct = findacct(fromuser, fromproto) + if acct is not None: + return email, acct + + return None, None + + def get_tofrom_sms(self, buddy): + + # return all capable accounts + with self.locks['sms']: + for (to, from_username, protoname) in self.tofrom['sms']: + for acct in self.profile.connected_accounts: + # find enabled email accounts + if acct.name == protoname and acct.username == from_username: + # found an (SMS, account) pair + return to, acct + + return None, None + + +tofrom_entry_length = dict( + im = 4, + email = 3, + sms = 3, +) + +def validate_tofrom(tofrom): + ''' + Validate the data in a "tofrom" dict. + + {'im': [('buddy', 'service', 'buddy', 'service'), ...], + 'email': + 'sms'} + ''' + if not isinstance(tofrom, dict): + raise TypeError('tofrom should be a dict, instead is a %r: %r' % (tofrom.__class__, tofrom)) + + for key in ('im', 'email', 'sms'): + for history_entry in tofrom[key]: + if not isinstance(history_entry, list): + raise TypeError + + if len(history_entry) != tofrom_entry_length[key]: + raise TypeError('invalid length') + elif not all(isinstance(s, basestring) for s in history_entry): + raise TypeError('each history entry should be a sequence of strings') + + return tofrom + +def default_tofrom(): + ''' + The default to/from dataset. + + The to/from table stores buddies that you last messaged, and from which + account. + ''' + + log.info('TOFROM: default_tofrom') + return {'im':[], 'email':[], 'sms':[]} + +def im_service_compatible(to_service, from_service): + ''' + Returns True if a buddy on to_service can be IMed from a connection to from_service. + ''' + + return to_service in protocols[from_service].compatible + +def get_account_name(a): + # TODO: profile.name is different than any other account.name in that it doesn't include name@digsby.org + if a is profile(): + return a.name + '@digsby.org' + else: + return a.name + diff --git a/digsby/src/contacts/identity.py b/digsby/src/contacts/identity.py new file mode 100644 index 0000000..f68e1e6 --- /dev/null +++ b/digsby/src/contacts/identity.py @@ -0,0 +1,70 @@ +from util import removedupes + +class Identity(object): + __slots__ = ['id', 'alias', 'groups', 'buddies'] + + def __init__(self, id, alias=None, groups = None, buddies = None): + if groups is None: groups = set() + if buddies is None: buddies = [] + + self.id = id + self.alias = alias + self.groups = groups + + # None is not a valid group name...for now. + none_tuple = (None,) + if any(g == none_tuple for g in groups): + raise ValueError('groups had a None') + + self.buddies = removedupes(buddies) + + def serialize(self): + buds = [buddy.serialize() for buddy in self.buddies] + buds2 = [] + for bud in buds: + if bud not in buds2: + buds2.append(bud) + return dict(id = self.id, alias = self.alias, + groups = self.groups, buddies = buds2) + + @classmethod + def unserialize(cls, d): + buds = [Personality.unserialize(buddy) for buddy in d['buddies']] + d['buddies'] = buds + return cls(**d) + + def __repr__(self): + return "" % (self.alias, self.groups, self.buddies) + +class Personality(object): + __slots__ = ['name', 'service'] + + def __init__(self, name, service): + self.name = name.lower() + self.service = service + + def serialize(self): + return dict(name = self.name.lower(), service = self.service) + + @property + def tag(self): + return (self.name.lower(), self.service) + + def __hash__(self): + return hash(self.tag) + + def __cmp__(self, other): + if other is self: + return 0 + if not isinstance(other, type(self)): + return -1 + return cmp(self.tag, other.tag) + + @classmethod + def unserialize(cls, d): + return cls(**d) + + def __repr__(self): + return "" % (self.name.lower(), self.service) + + diff --git a/digsby/src/contacts/metacontacts.py b/digsby/src/contacts/metacontacts.py new file mode 100644 index 0000000..2408cec --- /dev/null +++ b/digsby/src/contacts/metacontacts.py @@ -0,0 +1,1014 @@ +''' +Logic and storage for metacontacts. +''' + +from contacts.BuddyListElement import BuddyListElement +from contacts.Group import DGroup +from common.actions import action +from common import profile,pref +from util import callsback, removedupes, Storage as S +from util.threads.bgthread import on_thread +from util.callbacks import do_cb +from pprint import pformat +from types import NoneType +from contacts.buddyinfo import binfoproperty +from contacts.Contact import ContactCapabilities as caps +import util.observe +from collections import defaultdict +from util.primitives import try_this +from common.Buddy import get_cached_icon, icon_path_for +from itertools import chain +from os import path +from path import path as path2 +from traceback import print_exc +from util.primitives.structures import oset + +''' +{0: {'alias': None, + 'contacts': [{'name': 'brokenhalo282', + 'protocol': 'aim', + 'username': 'digsby03'}, + {'name': 'digsby01', + 'protocol': 'aim', + 'username': 'digsby03'}], + 'grouppath': ['dotsyntax'], + 'id': 0}, +''' + +from operator import attrgetter +readonly = lambda name: property(attrgetter(name)) +from util.observe import Observable + +def get_fakeroot_name(): + return pref('buddylist.fakeroot_name', default=_('Contacts')).lower() + +def first(gen): + try: + return gen.next() + except StopIteration: + return None + +class OfflineBuddy(Observable): + def __init__(self, name, service): + Observable.__init__(self) + self.name = self.nice_name = name + self._service = service + + self._notify_dirty = True + + self.protocol = p = S(name = self.service, + self_buddy = S(name = name), + connected = False) + + status = 'offline' + status_orb = 'unknown' + sightly_status = _('Unknown') + pretty_profile = u'' + + email_hint = \ + property(lambda self: None) + + sms = property(lambda self: True) + + @property + def history(self): + return profile.logger.history_for_safe(self.protocol, self) + + @property + def cache_path(self): + # cached attributes go in TEMPDIR + the following path + return path.join(self.protocol.name, self.name) + '.dat' + + @property + def log_size(self): + return profile.log_sizes[(self.name, self.service)] + + alias = readonly('name') + remote_alias = alias + + def idstr(self): + return u'/'.join(('', self.name, self.service)) + + @property + def info_key(self): + return self.name + '_' + self.service + + @property + def status_message(self): + return '' + + online = \ + mobile = \ + idle = \ + away = \ + blocked = \ + online_time = \ + property(lambda self: False) + + def get_buddy_icon(self): + pass + + def block(self, *a, **k): + return False + + def unblock(self, *a, **k): + return False + + service = readonly('_service') + caps = frozenset((caps.IM, caps.EMAIL)) + + def imwin_mode(self, mode): + from gui.imwin import begin_conversation + begin_conversation(self, mode = mode) + + @property + def serviceicon(self): + from gui import skin + return skin.get('serviceicons.%s' % self.service) + + @property + def buddy_icon(self): + from gui.buddylist.renderers import get_buddy_icon + return get_buddy_icon(self, 32, False) + + icon_bitmap = None #this needs to be settable + @property + def icon(self): + if self.icon_bitmap is None: + # check the disk for a cached icon + bitmap, hash = get_cached_icon(self) + # if nothing got loaded, flag that we tried and failed with -1 + self.icon_bitmap = bitmap if bitmap is not None else -1 + + if self.icon_bitmap is not None and self.icon_bitmap is not -1: + if isinstance(self.icon_bitmap, str) and self.icon_bitmap == "empty": + return None + else: + return self.icon_bitmap + else: + return None + + @property + def icon_path(self): + return path2(icon_path_for(self)) + + +def offline_contact(cinfo): + protocol = S(name = cinfo['protocol'], + service = cinfo['protocol'], + username = cinfo['username'], + self_buddy = S(name = cinfo['username']), + connected = False) + + return OfflineBuddy(cinfo['name'], protocol) + +# +# TODO: use objects as contact info descriptions, not dumb dictionaries. +# +def cinfotuple(c): + return (c['name'], c['protocol'], c['username']) + +def cinfodict(t): + return {'name': t[0], 'protocol': t[1], 'username': t[2]} + +def cdiff(cinfos, sub): + cinfos = set(cinfotuple(cinfo) for cinfo in cinfos) + sub = set(cinfotuple(s) for s in sub) + + return [cinfodict(q) for q in cinfos - sub] + + +from logging import getLogger; log = getLogger('metacontacts'); info = log.info + +class MetaContactManager(dict): + 'Stores MetaContacts.' + + def clear(self): + dict.clear(self) + self.metacontact_objs.clear() + self.rebuild_buddy_mapping() + self.blist._info_changed() + self.blist.rebuild() + + def __init__(self, blist, mc_data = {}): + 'Creates a metacontact manager initiailzed with mc_data.' + from .identity import Identity + self.blist = blist + if mc_data and not mc_data.values()[0].has_key('groups'): + self.old_data = dict(mc_data) + def cleanup(): + for key in mc_data.iterkeys(): + idstr = '%s #%d' % (MetaContact.idstr_prefix, key) + self.blist.remove_contact_info(idstr) + self.cleanup = cleanup + mc_data = {} + self.metacontact_objs = {} + for id, meta in mc_data.iteritems(): + try: + self[id] = Identity.unserialize(meta) + self.metacontact_objs[id] = MetaContact(id, self) + except Exception: + log.critical('ignoring metacontact %r because of an exception while unserializing:', id) + print_exc() + + self._built_with = mc_data + self.rebuild_buddy_mapping() + + def rebuild_buddy_mapping(self): + #map buddy description to set of metas + b2m = self.buddies_to_metas = defaultdict(set) + self.groupnames = mc_gnames = set() + for id_, m1 in self.iteritems(): + mc_gnames.update(m1.groups) + m = self.metacontact_objs[id_] + for b in m1.buddies: + b2m[(b.name.lower(), b.service)].add(m) + + def forid(self, id): + if isinstance(id, basestring) and id.startswith('Metacontact'): + # if "id" is an idstr for a MetaContact, int it + id = int(id[13:]) + try: + return self.metacontact_objs[id] + except KeyError: + return None + + def forbuddy(self, buddy, match_protocol = False, return_best = False): + 'Returns one, or a a sequence of MetaContact objects for a buddy.' + if not hasattr(buddy, 'name') or not hasattr(buddy, 'service'): + raise TypeError('metacontact.forbuddy takes a buddy, you gave a %s' % (buddy,)) + x = (buddy.name.lower(), buddy.service) + return set(self.buddies_to_metas[x]) if x in self.buddies_to_metas else set() + + def contacts_for_group(self, groupname): + ''' + If you delete a group that looks empty, it may not really be--a + metacontact may have moved a contact out of the group. + + The code that deletes the group will ask this method for contacts + in the group--this method will include those "hidden" contacts. + ''' + if groupname is not None: + groupname = groupname.lower() + result = [] + for meta in self.metacontact_objs.values(): + group = list(meta.mcinfo.groups)[0][-1] + for contact in list(meta) + list(meta.hidden): + if isinstance(contact, OfflineBuddy): + continue + + cgroupname = contact.get_group() + if cgroupname is not None: + cgroupname = cgroupname.lower() + + if groupname == cgroupname: + result.append((contact, group)) + return result + + def rename_group(self, oldname, newname): + log.info('renaming all metacontacts in %r -> %r', + oldname, newname) + marked = False + for meta in self.itervalues(): + for grouppath in list(meta.groups): + if grouppath[-1].lower() == oldname: + meta.groups.discard(grouppath) + newpath = list(grouppath[:-1]) + newpath.append(newname) + meta.groups.add(tuple(newpath)) + log.info(' - %r', meta.alias) + marked = True + if marked: + self.rebuild_buddy_mapping() + self.blist._info_changed() + self.blist.rebuild() + + + def idstr(self, number): + return u'%s #%d' % (MetaContact.idstr_prefix, number) + + def save_data(self): + # assert all in self is primitive? + mc_data = {} + for id, meta in self.iteritems(): + mc_data[id] = meta.serialize() + return mc_data + + def create(self, mc_alias, contacts, update = True): + 'Creates a new metacontact given a list of contacts.' + print mc_alias, contacts, update + from contacts import Contact + + if not isinstance(mc_alias, basestring): raise TypeError('alias must be a string') + if not all(isinstance(c, Contact) for c in contacts): + raise TypeError('not all contacts were Contact objects: %r' % contacts) + + id = self.new_id() + + self[id] = self.build_metacontact_info(id, mc_alias, contacts) + self.metacontact_objs[id] = MetaContact(id, self) + self.rebuild_buddy_mapping() + + self.blist._info_changed() + + info('created metacontact %r', mc_alias) + info('\t\t%r', self[id]) + + if update: self.blist.rebuild() + return id + + def edit(self, metacontact, contacts = sentinel, alias = sentinel, grouppath = None): + if not isinstance(metacontact, MetaContact): raise TypeError + assert contacts is not sentinel or alias is not sentinel + + # Set new alias? + if alias is not sentinel: + metacontact.alias = alias + else: + alias = metacontact.mcinfo.alias + + # Set new contacts? + if contacts is not sentinel: + info('new contact order for %r: %r', alias, contacts) + metacontact[:] = contacts + else: + contacts = metacontact[:] + + id = metacontact.id + self[id] = self.build_metacontact_info(id, alias, contacts, grouppath = grouppath) + info('edited metacontact %r', alias) + info(pformat(self[id])) + self.rebuild_buddy_mapping() + self.blist._info_changed() + self.blist.rebuild() + + def rearrange(self, mc, src, area, dest): + if src is dest: return + contacts = mc.mcinfo.contacts + src, dest = contact_dict(src), contact_dict(dest) + contacts.remove(src) + contacts.insert(contacts.index(dest) + (1 if 'below' == area else 0), src) + info('rearranged') + info(pformat(contacts)) + + self.blist._info_changed() + self.blist.rebuild() + + def remove_group(self, groupname): + groupname = groupname.lower() + log.info('removing all metacontacts in %r', + groupname) + marked = False + for meta in self.values(): + for grouppath in list(meta.groups): + if grouppath[-1].lower() == groupname: + meta.groups.discard(grouppath) + marked = True + if len(meta.groups) == 0: + self.explode(self.metacontact_objs[meta.id], update=True, cleanup=False) + if marked: + self.rebuild_buddy_mapping() + self.blist._info_changed() + self.blist.rebuild() + + def remove(self, mc, contact, explode = True, cleanup = True): + if isinstance(mc, int): + mcname = self.idstr(mc) + if mc not in self: + return + contacts = self[mc].buddies + else: + mcname = mc.name + contacts = mc.mcinfo.buddies + + log.info('removing %r from metacontact %r', contact, mcname) + + + cd = contact_dict(contact) + + for d in list(contacts): + if cd['name'] == d.name and cd['service'] == d.service: + contacts.remove(d) + + # Contact inherits certain properties from the MetaContact. + get, set = self.blist.get_contact_info, self.blist.set_contact_info + sms, email = get(mcname, 'sms'), get(mcname, 'email') + + contact_obj = self.blist.contact_for_id(contact) if isinstance(contact, basestring) else contact + def tolist(s): + if isinstance(s, basestring): return [s] + + # "inherit" email addresses and sms numbers from the metacontact. + if sms: + new_sms = removedupes((tolist(get(mcname, 'sms')) or []) + sms) + set(contact_obj, 'sms', new_sms) + if email: + new_email = removedupes((tolist(get(mcname, 'email')) or []) + email) + set(contact_obj, 'email', new_email) + + info('removed %r from %r', contact, mc) + if hasattr(self.blist, 'new_sorter'): + on_thread('sorter').call(self.blist.new_sorter.removeContact, mcname) + self.rebuild_buddy_mapping() + self.blist._info_changed() + + if explode and len(contacts) == 1: + self.remove(mc, contacts[0], True) + self.remove_mc_entry(mc) + elif explode and len(contacts) == 0: + pass + else: + if cleanup: + self.blist.rebuild() + + def explode(self, metacontact, update = True, cleanup=True): + for contact in list(metacontact): + self.remove(metacontact, contact, False, cleanup=cleanup) + + self.blist._info_changed() + self.remove_mc_entry(metacontact, update) + + def remove_mc_entry(self, metacontact, update = True): + id = getattr(metacontact, 'id', metacontact) + self.pop(id) + log.info('removed metacontact entry %d', id) + + # remove any server-side information (SMS, email) about this metacontact + self.blist.remove_contact_info(self.idstr(id)) + self.metacontact_objs.pop(id, None) + self.rebuild_buddy_mapping() + if update: self.blist.rebuild() + + def collect(self, *roots): + ''' + For contacts which are in metacontacts, remove them from the original + protocol groups and add them to a new group. + + Returns that new group full of DGroups holding MetaContacts. + ''' + + # Remove meta contacts + mc_root = DGroup('Root', protocols = [], ids = []) + + b2m = self.buddies_to_metas# = = defaultdict(set) #map buddy description to set of metas + + groupnames = oset() + + # For each protocol root group + cs = defaultdict(list) + mc_gnames = self.groupnames + metacontact_objs = self.metacontact_objs + + def maybe_remove_contact(contact, group): + if (contact.name.lower(), contact.service) in b2m: + for meta in b2m[(contact.name.lower(), contact.service)]: + cs[meta.id].append(contact) + + group.remove(contact) + return True + + return False + + from contacts.Group import GroupTypes + + for root in roots: + # Find the corresponding group + for group in list(root): + gflag = False + + if group is root: + continue + + if isinstance(group, GroupTypes): + for elem in list(group): + gflag |= maybe_remove_contact(elem, group) + + if gflag and (group.name not in groupnames): + groupnames.add(group.name) + else: + # contact + elem = group + if maybe_remove_contact(elem, root): + groupnames.add(get_fakeroot_name()) + + assert not set(cs.keys()) - set(self.keys()) + + for id in self.iterkeys(): + elems = cs[id] + order = [b.tag for b in self[id].buddies] + elems = list(sorted(elems, key = lambda elem: order.index((elem.name.lower(), elem.service)))) + + out = [] + hidden = [] + for tag in order: + online = False + while elems and (elems[0].name.lower(), elems[0].service) == tag: + b = elems.pop(0) + if not online: + out.append(b) + online = True + else: + hidden.append(b) + if not online: + old = [o for o in metacontact_objs[id] if + (isinstance(o, OfflineBuddy) and (o.name.lower(), o.service) == tag)] + if old: + out.append(old[0]) + else: + out.append(OfflineBuddy(*tag)) + + metacontact_objs[id].set_new(out, hidden) + + groups = {} + for m in metacontact_objs.itervalues(): + if any(not isinstance(b, OfflineBuddy) for b in m): + for gname in self[m.id].groups: + try: + g = groups[gname[0]] + except KeyError: + groups[gname[0]] = g = DGroup(gname[0]) + g.append(m) + + glen = len(groups) + nextroot = DGroup('Root') + for gname in groupnames: + if gname in groups: + nextroot.append(groups.pop(gname)) + + for gname in set(g[0] for g in mc_gnames) - set(groupnames): + if gname in groups: + nextroot.append(groups.pop(gname)) + + mc_root.extend(nextroot) +# assert len(nextroot) == glen + return mc_root + + def build_metacontact_info(self, id, mc_alias, contacts, grouppath = None): + assert isinstance(id, int) and isinstance(mc_alias, (basestring, NoneType)) + from .identity import Identity, Personality + keys = [(c.name, c.service) for c in contacts] + keys2 = [] + for k in keys: + if k not in keys2: + keys2.append(k) + buddies = [Personality(*k) for k in keys2] + mc = Identity(id, mc_alias, buddies=buddies) + + # Metacontact gets all emails/sms from buddy + sms, email = [], [] + get, set_ = self.blist.get_contact_info, self.blist.set_contact_info + for contact in contacts: + email += get(contact, 'email') or [] + sms += get(contact, 'sms') or [] + set_(contact, 'email', None) + set_(contact, 'sms', None) + + set_(id, 'email', removedupes(email)) + set_(id, 'sms', removedupes(sms)) + + if grouppath is None: + grouppaths = [None if x is None else x.lower() for x in [contact.get_group() for contact in contacts]] + mc.groups = set([(grouppaths[0],)]) + elif isinstance(grouppath, set): + mc.groups = set(grouppath) + else: + mc.groups = set([tuple(grouppath)]) + + # Contacts in the fake root group will return None for get_group. + # Put the metacontact in that fake root group. + if mc.groups == set([(None,)]): + mc.groups = set([(get_fakeroot_name(),)]) + + return mc + + + def new_id(self): + 'Returns the lowest integer that is not a key in this dictionary.' + + c = 0 + for k in sorted(self.keys()): + if c != k: break + else: c += 1 + + return c + + +#import contacts +class MetaContact(BuddyListElement, util.observe.ObservableList): + 'Multiple contacts.' + + + idstr_prefix = 'Metacontact' + + "allows metacontact to record if it should be considered dirty based on just being created, " + "or other conditions under which the meta contact requires the sorter to grab info from it again" + meta_dirty = True + + @property + def history(self): + '''Merges messages from all of this metacontact's buddies' histories.''' + + from util.merge import lazy_sorted_merge + from datetime import datetime + now = datetime.now() + #doesn't have to be now, just need a common point to subtract from + #to make the "largest" timestamp have the "smallest" timedelta for "min" heap + return lazy_sorted_merge(*(b.history for b in self if not isinstance(b, OfflineBuddy)), + **{'key':lambda x:now-x['timestamp']}) + + def __init__(self, id, manager, contacts = None): + if not isinstance(id, int): + raise TypeError('id must be an int') + + self.id = id + self.manager = manager + self.hidden = [] + + util.observe.ObservableList.__init__(self, contacts or []) + + for contact in self: + contact._metacontact = self + + def set_new(self, contacts, hidden_contacts): + if self[:] != contacts or self.hidden != hidden_contacts: + self[:] = contacts + self.hidden[:] = hidden_contacts + self.meta_dirty = True + + # when the contents of a MetaContact changes, the sorter needs to invalidate + # it's knowledge of it + sorter = getattr(self.manager.blist, 'new_sorter', None) + if sorter is not None: + def invalidate_sorter_contact(): + sorter.removeContact(self.name) + + if on_thread('sorter').now: + # if called from resort->collect, this is already true, + invalidate_sorter_contact() + else: # but sometimes we call it on the console for testing + on_thread('sorter').call(invalidate_sorter_contact) + + def get_notify_dirty(self): + return any(getattr(c, '_notify_dirty', True) for c in self[:] + self.hidden[:]) or self.meta_dirty + + def set_notify_dirty(self, value): + for c in self[:] + self.hidden[:]: + c._notify_dirty = value + if not value: + self.meta_dirty = False + + _notify_dirty = property(get_notify_dirty, set_notify_dirty) + + @property + def service(self): + f = self.first_online + return f.service if f is not None else 'digsby' + + @property + def sortservice(self): + f = self.first_online + return f.sortservice + + @property + def serviceicon(self): + from gui import skin + f = self.first_online + return f.serviceicon if f is not None else lambda: skin.get('serviceicons.digsby') + + @property + def caps(self): + return set(chain(*(b.caps for b in self))) + + @property + def send_file(self): + return self.file_buddy.send_file + + @property + def file_buddy(self): + for c in self: + if c.online and hasattr(c, 'send_file') and caps.FILES in c.caps: + return c + raise AttributeError, "no online buddy with send_file found" + + @action() + def edit_alerts(self): + import gui.pref.prefsdialog as prefsdialog + prefsdialog.show('notifications') + + def idstr(self): + if isinstance(self.name, str): + return self.name.decode('fuzzy utf8') + else: + return self.name + + @action(lambda self: not getattr(self, 'iswidget', False)) + def rename_gui(self): + + from gui.contactdialogs import MetaContactDialog + from wx import FindWindowByName + + diag = MetaContactDialog(FindWindowByName('Buddy List'), list(self),self, self.alias) + try: + diag.Prompt() + finally: + diag.Destroy() + + # + # Alias + # + def set_alias(self, alias): + self.mcinfo.alias = alias if alias else None + self.meta_dirty = True + self.manager.blist._info_changed() + self.manager.blist.rebuild() + + def get_alias(self): + # First choice: the metacontact alias, if any. + alias = try_this(lambda: self.mcinfo.alias, None) + + # Otherwise, the "first online's" alias. + try: + return alias if alias is not None else self.first_online.alias + except AttributeError: + return None + + alias = property(get_alias, set_alias) + + @property + def status_orb(self): + + if pref('metacontacts.show_first_online_status',False): + return self.first_online.status_orb + + statuses = self.by_status() + if not statuses: + return 'unknown' + return statuses[0].status_orb + + + @property + def mcinfo(self): + return self.manager[self.id] + + @property + def num_online(self): + return int(any(c.online for c in self)) + + @property + def name(self): + return self.manager.idstr(self.id) + + @property + def idle(self): + sorted = self.by_status() + if not sorted: + return None + + # If available or mobile, do not show the idle time. + if sorted[0].status_orb in ('available', 'mobile'): + return None + + # Show the lowest idle time we can find. + idle_times = [] + for c in self: + idle = c.idle + if isinstance(idle, (int, long)): + idle_times.append(idle) + + if idle_times: + return max(idle_times) + + # Otherwise return the most available buddy's idle attribute. + return sorted[0].idle + + + def __repr__(self): + try: + return '' % (self.alias, list.__repr__(self)) + except KeyError: + return '' % (self.name, list.__repr__(self)) + + def __hash__(self): + return hash((self.name, self.id)) +# return hash(''.join(unicode(s) for s in [self.name, self.mcinfo['grouppath']])) +# + def __eq__(self, other): + return type(other) == type(self) and self.id == other.id + + def __ne__(self, other): + return not self.__eq__(other) + + @property + def online(self): + return any(contact.online for contact in self) + + @action() + def explode(self, ask = True): + if ask: + import wx + res = wx.MessageBox(_('Are you sure you want to split up this merged contact?'), + _('Split Contacts'), + style = wx.YES_NO) + if res != wx.YES: + return + + self.set_notify_dirty(True) + self.manager.explode(self) + + @property + def icon(self): + for c in self: + icon = c.icon + if icon and icon.getextrema() != (0, 0): + return icon + + @property + def log_size(self): + return sum(c.log_size for c in self) + + def by_status(self): + from contacts.buddylistsort import STATUS_ORDER_INDEXES + + # don't include OfflineBuddy objects + non_offline = (c for c in self if not isinstance(c, OfflineBuddy)) + + def key(contact): + return STATUS_ORDER_INDEXES[contact.status_orb] + + return sorted(non_offline, key = key) or sorted(self, key = key) + + @property + def status(self): + try: + return self.by_status()[0].status + except IndexError: + return 'offline' + + @property + def protocol(self): + try: + return self.by_status()[0].protocol + except IndexError: + return profile.connection + + def rename(self, new_alias): + self.set_alias(new_alias) + + @property + def away(self): + # Are any contacts "Available" ? If so, we're not away. + if any(c.status == 'online' for c in self): + return False + + return any(c.away for c in self) + + @property + def pending_auth(self): + return False + + @property + def mobile(self): + f = self.first_online + return f.mobile if f is not None else False + + @property + def blocked(self): + return all(c.blocked for c in self) + + @property + def status_message(self): + msg = '' + for c in self: + msg = c.status_message + if msg: return msg + + @property + def stripped_msg(self): + try: + for c in self: + if c.status_message: + stripped_msg = getattr(c, 'stripped_msg', None) + if stripped_msg is not None: + return stripped_msg + else: + return '' + except AttributeError: + return '' + + @property + def first_online(self): + ''' + Returns the first contact found to be online, or the first contact in self. + + If self has no contact, returns None. + ''' + return first(c for c in self if c.online) or self.first_real or first(iter(self)) + + @property + def first_real(self): + return first(c for c in self if not isinstance(c, OfflineBuddy)) + + @property + def email_hint(self): + f = self.first_online + return f.email_hint if f is not None else '' + + @property + def info_key(self): + return self.name + + # these attributes are stored on the network. + email = binfoproperty('email', default = lambda: []) + sms = binfoproperty('sms', default = lambda: []) + + def imwin_mode(self, mode): + from gui.imwin import begin_conversation + begin_conversation(self, mode = mode) + + def chat(self): self.imwin_mode('im') + def buddy_info(self): self.imwin_mode('info') + + @action(lambda self: 'EMAIL' in self.caps) #@NoEffect + def send_email(self): self.imwin_mode('email') + + @action(lambda self: 'SMS' in self.caps or None) #@NoEffect + def send_sms(self): self.imwin_mode('sms') + + + def _block_predicate(self, setblocked=True, callback=None): + return True if setblocked ^ self.blocked else None + + def _unblock_predicate(self, callback=None): + return True if self.blocked else None + + @callsback + def block(self, setblocked = True, callback = None): + cbs = [] + for contact in self: + @callsback + def doblock(contact = contact, callback = None): + contact.block(setblocked, callback = callback) + + if contact.blocked != setblocked: + cbs += [doblock] + + do_cb(cbs, callback=callback) + + @callsback + def unblock(self, callback = None): + self.block(False, callback=callback) + + @callsback + def move_to_group(self, groupname, order = None, callback = None): + if not isinstance(groupname, basestring): + raise TypeError('groupname must be a string') + + if len(self.mcinfo.groups) != 1: + raise AssertionError('I thought metacontacts would only be in one group!') + + self.mcinfo.groups.clear() + self.mcinfo.groups.add((groupname,)) + self.manager.rebuild_buddy_mapping() + self.manager.blist._info_changed() + self.manager.blist.rebuild() + return callback.success() + +groupkey = lambda a: a.name.lower() + +def walk_group(root, path): + from contacts.Contact import Contact + + # Is this the successful case? If so, return the group + if not path: return root + + # Search. (this is O(n) now. should these be lookups?) + next, path = path[0], path[1:] + for elem in root: + if not isinstance(elem, Contact) and groupkey(elem) == next.lower(): + return walk_group(elem, path) + + return None + +def inflate_groups(grouppath): + 'Returns a DGroup tree given a sequence of names.' + + return [] if not grouppath else \ + DGroup(grouppath[0], protocols = None, ids = None, + *inflate_groups(grouppath[1:])) + + + +def contact_dict(contact): + 'Return a short dictionary for a contact.' + + return dict(name = contact.name, + service = contact.service) + +def underMeta(contact): + 'Returns True if the specified contact is in a metacontact.' + + c = contact_dict(contact) + + return any(c in meta['contacts'] + for _, meta in profile.blist.metacontacts.iteritems()) + diff --git a/digsby/src/contacts/sort_model.py b/digsby/src/contacts/sort_model.py new file mode 100644 index 0000000..393cb3a --- /dev/null +++ b/digsby/src/contacts/sort_model.py @@ -0,0 +1,194 @@ +''' +Contact List tab for the preferences dialog. +''' + +from common import pref, profile +from peak.events import trellis + +class ChoiceModel(trellis.Component): + values = trellis.attr([]) + selection = trellis.attr(0) + enabled = trellis.attr(True) + +class CheckModel(trellis.Component): + checked = trellis.attr(False) + enabled = trellis.attr(True) + +class SortOptionWatcher(trellis.Component): + model = trellis.attr(None) + + @trellis.perform + def print_selection(self): + if self.model is not None: + print 'selected', self.model.selection + +class OptionLink(trellis.Component): + parent = trellis.attr() + this = trellis.attr() + child = trellis.attr() + dependant = trellis.attr(True) + name = trellis.attr() + + @trellis.maintain + def selected_val(self): + try: + return self.this.values[self.this.selection] + except Exception: + pass + + @trellis.make + def orig_values(self): + return self.this.values[:] + + @trellis.maintain + def available_options(self): + newvals = [val for val in self.orig_values + if val not in self.parent_vals or val[0] == 'none'] + return newvals + + @trellis.maintain + def parent_vals(self): + c = self + parent_vals = [] + while c.parent is not None: + if c.parent.selected_val is not None: + parent_vals.append(c.parent.selected_val) + c = c.parent + return parent_vals + + @trellis.maintain + def enabled(self): + if not self.dependant: + return True + if not self.parent: + return True + if not self.parent.enabled: + return False + if self.parent.selected_val is None or self.parent.selected_val[0] in ['none', 'name']: + return False + return True + + @trellis.maintain + def keepenabled(self): + self.this.enabled = self.enabled + + @trellis.maintain + def sync_values(self): + newvals = self.available_options + if not self.enabled: + self.this.selection = -1 + self.this.values = [] + return + if self.this.selection > 0 and self.this.values and newvals: + oldval = self.this.values[self.this.selection] + if oldval in newvals: + self.this.selection = newvals.index(oldval) + else: + self.this.selection = 0 + elif not newvals: + self.this.selection = -1 + else: + self.this.selection = 0 + self.this.values = newvals + +def set_sort_values(*a): + sortby = pref('buddylist.sortby') + sorts = sortby.split() + if not sortby.startswith('*'): + sorts.insert(0, 'none') + else: + sorts[0] = sorts[0][1:] + if len(a) > len(sorts): + sorts.extend(['none'] * (len(a) - len(sorts))) + for model, val in zip(a, sorts): + try: + model.selection = [v[0] for v in model.values].index(val) + except ValueError: + #in my case, 'log status' isn't allowed now. + break + +def get_sort_value(group_by, *sort_bys): + out = [] + if group_by.selection >= 0: + gb_val = group_by.values[group_by.selection][0] + if gb_val != 'none': + out.append('*' + gb_val) + for sort_by in sort_bys: + if sort_by.selection >= 0: + sb_val = sort_by.values[sort_by.selection][0] + if sb_val == 'none': + break + out.append(sb_val) + else: + break + if len(out) < 2: + out.extend(['none']*(2-len(out))) + return ' '.join(out) + +class SortByWatcher(trellis.Component): + def __init__(self, sorts): + trellis.Component.__init__(self) + self.sorts = sorts + + @trellis.perform + def output_pref(self): + val = get_sort_value(*self.sorts) + from gui.pref.prefcontrols import mark_pref + mark_pref('buddylist.sortby', val) + +GROUP_BY_CHOICES = [ + ('none', _('None (custom)')), + ('status', _('Status')), + ('service', _('Service')), + ] +SORT_BY_CHOICES = [ + ('none', _('None')), + ('log', _('Log Size')), + ('name', _('Name')), + ('status', _('Status')), + ('service', _('Service')), + ] + +def build_models(then_bys=1, obj=None): + #build models + models = [ChoiceModel(values = GROUP_BY_CHOICES[:])] + models.extend([ChoiceModel(values = SORT_BY_CHOICES[:]) + for _i in range(then_bys+1)]) + + models[0].link = OptionLink( + this = models[0], + child = models[1], + name = 'group_by') + models[1].link = OptionLink(parent = models[0].link, + this = models[1], + child = models[2], + dependant = False, + name = 'sort_by') + for i in range(then_bys): + this_idx = i + 2 + this = models[this_idx] + if this_idx + 1 < len(models): + chld = models[this_idx + 1] + else: + chld = None + this.link = OptionLink(parent = models[this_idx - 1].link, + this = this, + child = chld, + name = 'then_by%d' % (i+1)) + + #this needs to be inside a "maintain" rule hooked from prefs + # should set the value of a maintain rule from prefs, + # a rule based on itself, so it will push down on a change, then + # return it's value which can be pushed to prefs in a perform rule, at + # which point, nothing should change, so we can stop there. + #read pref, set selections: + set_sort_values(*models) + + def on_sortby_changed(src, attr, old, new): + import wx + wx.CallAfter(set_sort_values, *models) + + profile.prefs.add_observer(on_sortby_changed, 'buddylist.sortby', + obj = obj) + return models + diff --git a/digsby/src/contacts/tofrom.py b/digsby/src/contacts/tofrom.py new file mode 100644 index 0000000..fe46ed0 --- /dev/null +++ b/digsby/src/contacts/tofrom.py @@ -0,0 +1,72 @@ +from common.protocolmeta import protocols +from itertools import islice + +def first(iterable): + try: + return islice(iterable, 1).next() + except StopIteration: + return None + +def im_service_compatible(to_service, from_service): + ''' + Returns True if a buddy on to_service can be IMed from a connection to from_service. + ''' + return to_service in protocols[from_service].compatible + +def choose_to(metacontact): + return metacontact.first_online + +def choose_from(contact, accounts, tofrom): + ''' + Given a contact, returns the best connected account to IM it from. + + Checks the to/from history. + + Returns an account object, or None. + ''' + + # First, check the to/from history. + acct = lookup_tofrom_account(contact, accounts, tofrom) + if acct is not None: + return acct + + # If no to/from history exists for this contact, but one of + # the connected accounts is its owner, return that account. + for account in accounts: + if account.connection is contact.protocol: + return account + + # No connected accounts actually owned the Contact. Now just + # find a *compatible* connected account. + return first(compatible_im_accounts(contact, accounts)) + +def lookup_tofrom_account(contact, connected_accounts, tofrom): + ''' + Searches the to/from IM list for an entry matching contact, where + the from account matching the entry must be in connected_accounts. + + Returns a matching from account, or None. + ''' + name, service = contact.name, contact.service + + # Loop through each entry in the tofrom list + for bname, bservice, fromname, fromservice in tofrom: + # If the buddy matches, + if name == bname and service == bservice: + for acct in connected_accounts: + # and the from account matches, return it. + if acct.name == fromname and acct.service == fromservice: + return acct + +def compatible_im_accounts(contact, accounts): + ''' + Given a Contact and a sequence of Accounts, yields the accounts + which can send IMs to the Contact. + ''' + for account in accounts: + to_service = contact.service + from_service = account.protocol + if im_service_compatible(to_service, from_service): + yield account + + diff --git a/digsby/src/crashgui.py b/digsby/src/crashgui.py new file mode 100644 index 0000000..4788377 --- /dev/null +++ b/digsby/src/crashgui.py @@ -0,0 +1,58 @@ +import wx + +# TODO: remove this hack +if not hasattr(wx, 'WindowClass'): + wx.WindowClass = getattr(wx, '_Window', wx.Window) +if __name__ == '__main__': + import gettext; gettext.install('Digsby') +import wx.lib.sized_controls as sc + + +CRASH_TITLE = _('Digsby Crash Report') +CRASH_MSG_HDR = _('Digsby appears to have crashed.') +CRASH_MSG_SUB = _('If you can, please describe what you were doing before it crashed.') +CRASH_MSG = u'%s\n\n%s' % (CRASH_MSG_HDR, CRASH_MSG_SUB) +CRASH_SEND_BUTTON = _("&Send Crash Report") + +class CrashDialog(sc.SizedDialog): + '''Shown after crashes.''' + + def __init__(self, parent = None): + sc.SizedDialog.__init__(self, parent, -1, CRASH_TITLE, + style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + + self.panel = CrashPanel(self) + s = self.Sizer = wx.BoxSizer(wx.VERTICAL) + s.Add(self.panel, 1, wx.EXPAND | wx.ALL, 8) + + self.SetButtonSizer(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL)) + self.FindWindowById(wx.ID_OK, self).SetLabel(CRASH_SEND_BUTTON) + + self.Fit() + self.SetMinSize((self.Size.width, 150)) + + @property + def Description(self): + return self.panel.input.Value + +class CrashPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + self.construct() + self.layout() + + def construct(self): + self.msg = wx.StaticText(self, -1, CRASH_MSG) + self.input = wx.TextCtrl(self, -1, size = (350, 200), style = wx.TE_MULTILINE) + + wx.CallAfter(self.input.SetFocus) + + def layout(self): + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.AddMany([(self.msg, 0, wx.EXPAND), + ((10, 10)), + (self.input, 1, wx.EXPAND)]) + +if __name__ == '__main__': + a=wx.PySimpleApp() + CrashDialog().ShowModal() diff --git a/digsby/src/csvlogging.py b/digsby/src/csvlogging.py new file mode 100644 index 0000000..dfbcd32 --- /dev/null +++ b/digsby/src/csvlogging.py @@ -0,0 +1,89 @@ +import logging, gzip +from time import strftime +from traceback import format_exception + +COMMA = '#comma#' +LINE = '#line#' + +def csv_escape(s): + return s.replace(',', COMMA).replace('\n', LINE) + +CSV_HEADERS = ("Time, Log Level, Level Name, Log Name, Message," + "Pathname, Filename, Module, Function Name, " + "Line No., Timestamp, Thread Count, Thread Name, " + "Thread No., Process No.\n") + +class CSVFormatter(logging.Formatter): + _fmt = "%(asctime)s,%(levelno)s,%(levelname)s,%(name)s,%(message)s,%(pathname)s,%(filename)s,%(module)s," \ + "%(funcName)s,%(lineno)d,%(created)f,%(threadCount)d,%(threadName)s,%(thread)d,%(process)d" + + def __init__(self, fmt=None, datefmt=None): + logging.Formatter.__init__(self, fmt, datefmt) + self._fmt = CSVFormatter._fmt + self._asctime = self._fmt.find('%(asctime)') >= 0 + + def formatTime(self, record, datefmt=None): + ct = self.converter(record.created) + if datefmt: + s = strftime(datefmt, ct) + else: + t = strftime("%Y-%m-%d %H:%M:%S", ct) + s = "%s.%03d" % (t, record.msecs) + return s + + def formatException(self, ei): + s = '\n'.join(format_exception(*ei)) + if s.endswith('\n'): + s = s[:-1] + + return s + + def format(self, record): + record.message = ''.join(('"', record.getMessage().replace('"', '""'), '"')) + if self._asctime: + record.asctime = self.formatTime(record, self.datefmt) + + s = self._fmt % record.__dict__ + + if record.exc_info: + # Cache the traceback text to avoid converting it multiple times + # (it's constant anyway) + if not record.exc_text: + record.exc_text = csv_escape(self.formatException(record.exc_info)) + + if record.exc_text: + if not s.endswith(","): + s = s + "," + s = ''.join((s, record.exc_text)) + return s + + +class gzipFileHandler(logging.StreamHandler): + def __init__(self, t, filename=None): + if not filename: + filename = 'digsby-' + t + '.log.csv.gz' + f = open('logs/digsby-' + t + '.log.csv.gz', 'wb') + self.gzfileobj = gzip.GzipFile(filename, fileobj=f) + self.gzfileobj.write("Time, Log Level, Level Name, Log Name, Message," + "Pathname, Filename, Module, Function Name, " + "Line No., Timestamp, Thread No., " + "Thread Name, Process No.\n") + logging.StreamHandler.__init__(self, self.gzfileobj) + + def close(self): + logging.StreamHandler.close(self) + self.gzfileobj.close() + +class CloseFileHandler(logging.StreamHandler): + def __init__(self, openfile, level = None, formatter = None): + self.fileobj = openfile + logging.StreamHandler.__init__(self, self.fileobj) + + if level is not None: + self.setLevel(level) + if formatter is not None: + self.setFormatter(formatter) + + def close(self): + logging.StreamHandler.close(self) + self.fileobj.close() diff --git a/digsby/src/datastore/__init__.py b/digsby/src/datastore/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/datastore/common/__init__.py b/digsby/src/datastore/common/__init__.py new file mode 100644 index 0000000..5d852d6 --- /dev/null +++ b/digsby/src/datastore/common/__init__.py @@ -0,0 +1,290 @@ +import time +import syck + + +class Datastore(object): + sentinel = object() + + def __init__(self, **kwds): + super(Datastore, self).__init__() + + def get(self, key, default=sentinel): + raise NotImplementedError + + def __getitem__(self, key): + return self.get(key) + + def __iter__(self): + return iter(self.keys()) + + def keys(self): + raise NotImplementedError + + def items(self): + raise NotImplementedError + + def set(self, key, val=sentinel): + raise NotImplementedError + + def __setitem__(self, key, value): + return self.set(key, value) + + def clear(self, key): + return self.set(key, self.sentinel) + + def create_substore(self, key, data): + props = vars(self).copy() + props['data'] = data + return type(self)(**props) + + +class DictDatastore(Datastore): + def __init__(self, data = None, **kwds): + self.data = data + super(DictDatastore, self).__init__(data=data, **kwds) + + def get(self, key, default=Datastore.sentinel): + try: + super(DictDatastore, self).get(key, default) + except NotImplementedError: + pass + + value = self.data.get(key, default) + if value is self.sentinel: + raise KeyError(key) + elif isinstance(value, dict): + return self.create_substore(key, value) + else: + return value + + def set(self, key, val=Datastore.sentinel): + try: + super(DictDatastore, self).set(key, val) + except NotImplementedError: + pass + if val is self.sentinel: + self.data.pop(key) + else: + self.data[key] = val + + def keys(self): + return self.data.keys() + + def items(self): + return self.data.items() + + +class FileDatastore(Datastore): + def __init__(self, filepath, **kwds): + self.filepath = filepath + self._needsWrite = False + self._lastRead = -1 + super(FileDatastore, self).__init__(filepath=filepath, **kwds) + + def set(self, key, val=Datastore.sentinel): + mydefault = object() + if val == self.get(key, mydefault): + # Not changing the value, no need to set + return + + if self._lastRead < self.get_mtime(): + raise Exception("Synchronization error: file was changed on disk and in memory") + + super(FileDatastore, self).set(key, val) + self._needsWrite = True + self.write() + + def get(self, key, default=Datastore.sentinel): + mtime = self.get_mtime() + if self._needsWrite and self._lastRead < mtime: + raise Exception("Synchronization error: file was changed on disk and in memory") + elif self._needsWrite: + self.write() + elif self._lastRead < mtime: + self.read() + + return super(FileDatastore, self).get(key, default) + + def write(self): + if not self.filepath: + return + if not self._needsWrite: + return + + with open(self.filepath, 'wb') as f: + self.serialize(f) + self._lastRead = self.get_mtime() + self._needsWrite = False + + def read(self): + if not self.filepath: + return + + if not self.filepath.isfile(): + self.data = {} + self._needsWrite = True + self.write() + + with open(self.filepath, 'rb') as f: + self.deserialize(f) + self._lastRead = self.get_mtime() + + def get_mtime(self): + try: + return getattr(self.filepath, 'mtime', None) + except OSError: + return 0 + + def serialize(self, file): + raise NotImplementedError + + def deserialize(self, file): + raise NotImplementedError + + def items(self): + if self._lastRead < self.get_mtime(): + self.read() + + return super(FileDatastore, self).items() + + def keys(self): + if self._lastRead < self.get_mtime(): + self.read() + + return super(FileDatastore, self).keys() + + +class AuthenticatedDatastore(Datastore): + def __init__(self, credentials, **kwds): + self.credentials = credentials + super(AuthenticatedDatastore, self).__init__(credentials=credentials, **kwds) + + +class EncryptingDatastore(Datastore): + def __init__(self, encrypted_properties, **kwds): + self.encrypted_properties = encrypted_properties + super(EncryptingDatastore, self).__init__(encrypted_properties=encrypted_properties, **kwds) + + def get(self, key, default=Datastore.sentinel): + value = super(EncryptingDatastore, self).get(key, default) + if key in self.encrypted_properties and value is not default: + try: + value = self._decrypt(value) + except UnicodeDecodeError: + raise ValueError('Invalid Key') + elif value is not Datastore.sentinel: + value = value + else: + raise KeyError(key) + + return value + + def set(self, key, val=Datastore.sentinel): + if key in self.encrypted_properties and val is not self.sentinel: + val = self._encrypt(val) + + return super(EncryptingDatastore, self).set(key, val) + + def _encrypt(self, val): + raise NotImplementedError + + def _decrypt(self, val): + raise NotImplementedError + + +class DeepDatastore(Datastore): + def __init__(self, sep='.', prefix=None, **kwds): + self.sep = sep + self.prefix = prefix + super(DeepDatastore, self).__init__(sep=sep, prefix=prefix, **kwds) + + def get_keyparts(self, key): + if hasattr(key, 'split'): + keyparts = key.split('.') + else: + keyparts = key + + try: + iter(keyparts) + except: + keyparts = (keyparts,) + + return tuple(keyparts) + + def get(self, key, default=Datastore.sentinel): + keyparts = list(self.get_keyparts(key)) + + firstkey = keyparts.pop(0) + val = super(DeepDatastore, self).get(firstkey, default) + if val is default: + if default is Datastore.sentinel: + raise KeyError + return default + + if keyparts: + val.prefix = (firstkey,) + val = val.get(tuple(keyparts), default) + + return val + + def items(self): + for k1 in super(DeepDatastore, self).keys(): + k1 = self.get_keyparts(k1) + + self.prefix = k1 + v1 = self.get(k1) + if isinstance(v1, Datastore): + for k2, v2 in v1.items(): + if not isinstance(k2, tuple): + k2 = (k2,) + yield (k1 + k2, v2) + else: + yield (k1, v1) + + def keys(self): + for k, _v in self.items(): + yield k + + def set(self, key, val=Datastore.sentinel): + keyparts = list(self.get_keyparts(key)) + firstkey = keyparts.pop(0) + + mydefault = object() + if keyparts: + substore = self.get(firstkey, mydefault) + if substore is mydefault: + substore = self.create_substore(firstkey, {}) + + substore.set(tuple(keyparts), val) + super(DeepDatastore, self).set(firstkey, substore.data) + else: + super(DeepDatastore, self).set(firstkey, val) + + def create_substore(self, key, value): + props = vars(self).copy() + props['data'] = value + props['prefix'] = key + return type(self)(**props) + + +class NestedDictDatastore(DeepDatastore, DictDatastore): + pass + + +class YAMLDatastore(FileDatastore, NestedDictDatastore): + def serialize(self, f): + syck.dump(self.data, f) + + def deserialize(self, f): + self.data = syck.load(f) + + def create_substore(self, key, value): + substore = super(YAMLDatastore, self).create_substore(key, value) + substore.data = value + #substore.read = self.read + #substore.write = self.write + substore.serialize = self.serialize + substore.deserialize = self.deserialize + + return substore + diff --git a/digsby/src/datastore/v0/__init__.py b/digsby/src/datastore/v0/__init__.py new file mode 100644 index 0000000..6200600 --- /dev/null +++ b/digsby/src/datastore/v0/__init__.py @@ -0,0 +1,84 @@ +import sys +import os +import logging +import stdpaths +import sysident + +log = logging.getLogger('datastore.v0') + +from ..common import Datastore, FileDatastore, EncryptingDatastore, YAMLDatastore + +## TODO: make this ;) +#class LocalPrefs(FileDatastore): +# """Data access for local prefs stored in an INI file""" +# def __init__(self, **kwds): +# super(LocalPrefs, self).__init__(**kwds) + + +class LoginInfo(EncryptingDatastore, YAMLDatastore): + """Login info stored in YAML""" + def __init__(self, encrypted_properties=(('password',), 'password',), **kwds): + filepath = kwds.pop('filepath', None) + + if filepath is None: + if sys.REVISION == 'dev': + filename = 'logininfodev.yaml' + else: + filename = 'logininfo.yaml' + + filepath = stdpaths.userlocaldata / filename + if not filepath.parent.isdir(): + filepath.parent.makedirs() + + super(LoginInfo, self).__init__( + filepath=filepath, + encrypted_properties=encrypted_properties, + **kwds + ) + +# # TODO: if setting username, re-encrypt password +# def set(self, key, value): +# pass + + def _encrypt(self, val): + encrypted = self.simple_crypt(val.encode('utf8'), keymat=self.get('username', '').encode('utf8')) + return encrypted + + def _decrypt(self, val): + decrypted = self.simple_crypt(val, keymat=self.get('username', '').encode('utf8')) + return decrypted.decode('utf8') + + def simple_crypt(self, s, k=None, keymat=''): + from M2Crypto.RC4 import RC4 + if k is None: + k = self._get_key(keymat) + return RC4(k).update(s) + + def _get_key(self, keymat=''): + keys = getattr(self, '_keys', None) + if keys is None: + keys = self._keys = {} + + key = keys.get(keymat, None) + if key is not None: + return key + + self._keys[keymat] = sysident.sysident(append=keymat) + + return self._keys[keymat] + + def load_from_disk(self): + users = self.get('users', None) + if users is None: + self.set('users', {}) + users = self.get('users') + return self.get('last', ''), users + + def save_to_disk(self, last, info): + self.set('last', last) + for k, v in info.items(): + if type(v) is object: + raise Exception(list(info.items())) + self.set(('users',) + k, v) + + return True diff --git a/digsby/src/devmode.py b/digsby/src/devmode.py new file mode 100644 index 0000000..3a1f099 --- /dev/null +++ b/digsby/src/devmode.py @@ -0,0 +1 @@ +awesome = True \ No newline at end of file diff --git a/digsby/src/digsby/DigsbyBaseProtocol.py b/digsby/src/digsby/DigsbyBaseProtocol.py new file mode 100644 index 0000000..b69c2be --- /dev/null +++ b/digsby/src/digsby/DigsbyBaseProtocol.py @@ -0,0 +1,602 @@ +''' +Jabber Protocol. +''' + +from common import pref, netcall +from digsby.loadbalance import DigsbyLoadBalanceManager +from hashlib import sha256 +from jabber.JabberProtocol import JabberProtocol +from jabber.threadstream import ThreadStream +from logging import getLogger +from peak.util.imports import lazyModule +from pyxmpp.all import Iq, Presence +from pyxmpp.jabber.client import JabberClient +from pyxmpp.jid import JID +from traceback import print_exc +from util.callbacks import callsback, CallLater +from util.introspect import callany, funcinfo +from util.primitives.funcs import Delegate, do +from util.primitives.mapping import odict +from util.primitives.structures import enum +from util.threads.threadpool2 import threaded +import blobs +import common +import digsby +import hooks +import jabber +import random +import string +import sys +from common.Protocol import StateMixin + +callbacks = lazyModule('util.callbacks') +mapping = lazyModule('util.primitives.mapping') +funcs = lazyModule('util.primitives.funcs') +error_handling = lazyModule('util.primitives.error_handling') +hook_util = lazyModule('util.hook_util') +util_threads = lazyModule('util.threads') + +log = getLogger('jabber.protocol') +methods_dict = odict(dict(digsby_login="sasl:DIGSBY-SHA256-RSA-CERT-AES")) + +log = getLogger('digsbyImportProtocol') + +LOAD_BALANCE_ADDRS = [ + ('login1.digsby.org', 80), + ('login2.digsby.org', 80) + ] + +JABBER_SRVS_FALLBACK = [ + 'api1.digsby.org', + 'api2.digsby.org', + ] + +class DigsbyProtocol(JabberClient, StateMixin): + + states = enum('Disconnected', + 'Authenticating', + 'Connected') + + #layering violations from threadstream.py + do_ssl = False + connect_killed = False + + def __init__(self, username, password, server, resource="Digsby"): + + host, port = server + + alphanum = string.letters + string.digits + resource = resource + "." + "".join(random.choice(alphanum) for _x in xrange(6)) + jid = JID(username, "digsby.org", resource) + + if isinstance(username, unicode): + username = username.encode('utf8') + if isinstance(password, unicode): + password = sha256(password.encode('utf-8')).digest() + + jkwargs = dict(jid = jid, + password = password, + keepalive = 45) + + if host: + jkwargs.update(server = host) + if port: + jkwargs.update(port = port) + + jkwargs.update(auth_methods = ("sasl:DIGSBY-SHA256-RSA-CERT-AES",)) + JabberClient.__init__(self, **jkwargs) + StateMixin.__init__(self) + self.stream_class = ThreadStream + + @callsback + def Connect(self, callback=None): + + self.change_state(self.Statuses.CONNECTING) + + self.connect_callback = callback + + def conn_attmpt_failed(): + log.debug('conn_attmpt_failed') + #nowhere else to go, + report conn fail + self.set_offline(self.Reasons.CONN_FAIL) + callback.error() + self.connect_attempt_failed = conn_attmpt_failed + try: + self.connect() + except Exception as _e: + print_exc() + self.connect_attempt_failed() + else: + self.connect_attempt_succeeded() + + # Go find the load balancer; if you cannot, call on_fail + #self.get_login_servers(l.username) + + def connect_attempt_succeeded(self): + log.debug('connect_attempt_succeeded') + with self.lock: + self.idle_loop = Delegate() + self.idle_loop += self.idle + self.idle_looper = jabber.IdleLoopTimer(1, self.idle_loop) + + def connect_attempt_failed(self): + raise AssertionError('connection attempt cannot fail before it is attempted') + + def connected(self): + with self.lock: + if self.state == self.Statuses.CONNECTING: + self.change_state(self.Statuses.AUTHENTICATING) + + def disconnected(self, want_try_again = False): + log.debug('disconnected 1') + with self.lock: + log.debug('disconnected 2') + if not want_try_again and not self.want_try_again: + log.debug('disconnected 3') +# assert False + self.change_state(self.Statuses.OFFLINE) + JabberClient.disconnected(self) + + def _get_stream(self): + return getattr(self, '_stream', None) + + def _set_stream(self, stream): + new = stream + if new is None: + old = self.stream + if old is not None: + netcall(old.close) + for attr in ('process_stream_error', 'state_change', 'owner'): + setattr(old, attr, Null) + self._stream = new + + stream = property(_get_stream, _set_stream) + +# def auth_failed(self, reason=''): +# if reason: +# self._auth_error_msg = reason +# self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) +# self.Disconnect() + + def auth_failed(self, reason=''): + if reason in ('bad-auth', 'not-authorized'): + self._auth_error_msg = reason + self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) + elif reason: + self.error_txt = reason + self.fatal_error() + + + @callsback + def _reconnect(self, invisible = False, + do_conn_fail = True, callback=None): + log.info('jabber _reconnect!') + getattr(getattr(self, 'idle_looper', None), 'stop', lambda: None)() + + #grab next set of connection opts + opts = self._get_next_connection_options() + if opts is None: + return self.fatal_error() + + self.change_state(self.Statuses.CONNECTING) + + self.server, self.port = opts.pop('server') + + #collect $200 + threaded(lambda: JabberProtocol.Connect(self, invisible = invisible, + do_conn_fail = do_conn_fail, callback=callback))() + + def fatal_error(self): + if getattr(self.stream, '_had_host_mismatch', False) and not self.offline_reason: + log.error('there was a host mismatch, interpreting it as auth error...') + self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) # technically it's a bad username, but auth error regardless + + reason = self.offline_reason or self.Reasons.CONN_LOST + log.info('Out of connection options. Changing to %r', reason) + self.set_offline(reason) + return False + + def connect(self): + """Connect to the server and set up the stream. + + Set `self.stream` and notify `self.state_changed` when connection + succeeds. Additionally, initialize Disco items and info of the client. + """ + JabberClient.connect(self, register=False) + + def stop_idle_looper(self): + idle_looper, self.idle_looper = getattr(self, 'idle_looper', None), None + if idle_looper is not None: + idle_looper.stop() + + del self.idle_looper + + def stop_timer_loops(self): + self.stop_idle_looper() + + def Disconnect(self): + netcall(self._Disconnect) + + def _Disconnect(self): + log.debug('logging out %r', self.want_try_again) + with self.lock: + pres = Presence(stanza_type="unavailable", + status='Logged Out') + try: + self.stream.send(pres) + except AttributeError: + pass + self.connect_killed = True + self.disconnect() + try: + self.stop_timer_loops() + except AttributeError: + print_exc() + + if getattr(self, 'idle_loop', None) is not None: + del self.idle_loop[:] + + if self.interface_providers is not None: + del self.interface_providers[:] + self.interface_providers = None + + if self.stream is not None: + self.stream = None + + log.debug('1logged out %r', self.want_try_again) + self.offline_reason = None +# self.disconnected() + log.debug('1logged out %r', self.want_try_again) + + common.protocol.Disconnect(self) + + def session_started(self): + ''' + Called when the IM session is successfully started (after all the + neccessery negotiations, authentication and authorizasion). + ''' + with self.lock: + self.idle_looper.start() + log.info('session started') + + s = self.stream + + newstate = self.Statuses.AUTHORIZED + #when this is true, don't try to start a new connection on conn_lost + self.have_connected = True + + # set up handlers for supported queries + s.set_iq_get_handler("query", "jabber:iq:version", self.get_version) + + # set up handlers for stanzas +# do(s.set_presence_handler(name, func) for name, func in [ +# (None, self.buddies.update_presence), +# ('unavailable', self.buddies.update_presence), +# ('available', self.buddies.update_presence), +# ('subscribe', self.subscription_requested), +# ('subscribed', self.presence_control), +# ('unsubscribe', self.presence_control), +# ('unsubscribed', self.presence_control), +# ]) + + self.session_started_notify(s) + + self.connect_callback.success() + self.change_state(newstate) + log.info('session started done') + + def session_started_notify(self, s): + ''' + whatever needs to be done after most setup and before the callback/state change happens. + usually that will be plugins doing their own setup. + allows notify to be overridden in subclass + ''' + hooks.notify('digsby.jabber.session_started', self, s) + + def authorized(self): + log.info('authorized1') + JabberClient.authorized(self) # required! + log.info('authorized2') + + def get_version(self,iq): + """Handler for jabber:iq:version queries. + + jabber:iq:version queries are not supported directly by PyXMPP, so the + XML node is accessed directly through the libxml2 API. This should be + used very carefully!""" + iq = iq.make_result_response() + q = iq.new_query("jabber:iq:version") + q.newTextChild( q.ns(), "name", "Digsby Import Client" ) +# q.newTextChild( q.ns(), "version", ('%s %s' % (sys.REVISION, sys.TAG)).strip()) # strip because sometimes TAG is '' + if not self.hide_os: + import platform + platform_string = platform.platform() + # for some reason, on my XP box, platform.platform() contains both + # the platform AND release in platform.platform(). On Ubuntu, OS X, + # and I believe older versions of Windows, this does not happen, + # so we need to add the release in all other cases. + if platform_string.find("XP") == -1: + platform_string += " " + platform.release() + + q.newTextChild( q.ns(), "os", platform_string ) + self.send(iq) + return True + + # presense start + + def presence_push(self): + pres = Presence() + self.send_presence(pres) + + def presence_control(self,*_a, **_k): + ''' + Handle subscription control stanzas -- acknowledge + them. + ''' + return True + + # presense end + + @callsback + def send_cb(self, query, timeout_duration=None, callback=None): + ''' + Given a callback object with callable attributes success, error, and timeout, + sends out a query with response handlers set. + ''' + + def free_stanza(st): + stream = self.get_stream() + with stream.lock: + if stream.doc_out is not None: + st.free() + else: + if st._error: + st._error.xmlnode = None + st.xmlnode = None + + def my_super_callback_success(stanza): + stanza.handler_frees = True + if not isinstance(callback.success, list): + try: + log.info("what is this?, callback was %r", funcinfo(callback.success)) + except Exception: + log.error('bad callback.success %r', callback.success) + return free_stanza(stanza) + if len(callback.success) == 0: + return free_stanza(stanza) + + try: + f = callback.success[0].cb + _call_free = CallLater(lambda:free_stanza(stanza)) + def my_hyper_callback_success(st): + try: f(st) + except Exception: + log.error('error processing %r', f) + print_exc() + finally: _call_free() + callback.success[0].cb = my_hyper_callback_success + callany(callback.success, stanza) + except Exception: + print_exc() + log.error('failed to set up success stanza.free for %r, %r', callback, stanza) + + def my_super_callback_error(stanza): + stanza.handler_frees = True + if not isinstance(callback.error, list): + try: + log.info("what is this?, callback was %r", funcinfo(callback.error)) + except Exception: + log.error('bad callback.error %r', callback.error) + return free_stanza(stanza) + if len(callback.error) == 0: + return free_stanza(stanza) + + try: + f = callback.error[0].cb + _call_free = CallLater(lambda:free_stanza(stanza)) + def my_hyper_callback_error(st): + try: f(st) + except Exception: + log.error('error processing %r', f) + print_exc() + finally: _call_free() + callback.error[0].cb = my_hyper_callback_error + callany(callback.error, stanza) + except Exception: + print_exc() + log.error('failed to set up error stanza.free for %r, %r', callback, stanza) + + def my_super_callback_timeout(*_a): + ''' + consume the arguments, they are from ExpiringDictionary and cause problems + with the number of arguments taken by timeout function, + which in this case is expected to be 0, since we can store the context in it's closure + and hardly anything even uses timeouts. + ''' + return callback.timeout() + + s = self.get_stream() + if s is not None: + try: + if timeout_duration is not None: + self.stream.set_response_handlers(query, my_super_callback_success, + my_super_callback_error, + my_super_callback_timeout, + timeout = timeout_duration) + else: + self.stream.set_response_handlers(query, my_super_callback_success, + my_super_callback_error, + my_super_callback_timeout) + except Exception as _e: + print_exc() + log.critical("couln't set stream handlers") + return callany(callback.error) + try: + self.stream.send(query) + except Exception as _e: + print_exc() + log.critical("couln't send query") + try: + return callany(callback.error) + except Exception: + log.critical("couln't call callany(callback.error) %r", callback) + + def send_presence(self, pres): + assert isinstance(pres, Presence) + self.send(pres) + + def send_iq(self, iq): + assert isinstance(iq, Iq) + self.send(iq) + + def send(self, stanza): + s = self.get_stream() + if s is not None: + try: + self.stream.send(stanza) + except Exception as _e: + print_exc() + log.critical("couln't send stanza") + + def idle(self): + try: + JabberClient.idle(self) + if not self.stream.socket or self.stream.eof: + raise AssertionError, "if the stream is dead or gone, we can't really send a keep-alive" + except Exception: + self.stop_timer_loops() + else: + self.cache.tick() + + def stream_error(self, err): + if err.get_condition().name == "pwchanged": + self.change_reason(self.Reasons.BAD_PASSWORD) + self.profile.signoff(kicked=True) + elif err.get_condition().name == "conflict": + self.change_reason(self.Reasons.OTHER_USER) + else: + self.change_reason(self.Reasons.CONN_LOST) + JabberClient.stream_error(self, err) + + class Statuses(jabber.protocol.Statuses): + SYNC_PREFS = _('Synchronizing Preferences...') + #LOAD_SKIN = _('Loading Skin...') + AUTHORIZED = _('Synchronizing...') + + @callsback + def get_blob_raw(self, elem_name, tstamp='0', callback=None): + try: + blob = blobs.name_to_obj[elem_name](tstamp) + blob._data = None + iq = blob.make_get(self) + except Exception: + blob = blobs.name_to_obj[elem_name]('0') + blob._data = None + iq = blob.make_get(self) + self.send_cb(iq, callback = callback) + + @callsback + def get_accounts(self, callback=None): + iq = digsby.accounts.Accounts().make_get(self) + self.send_cb(iq, callback = callback) + + +class DigsbyLoadbalanceStuff(object): + + def __init__(self): + self.callback = None + self.profile = None + self.initial = None + + def finish_init(self, sock): + self.hosts = sock + _lh = len(self.hosts) + self.alt_connect_opts = alt_ops = [] + self.on_alt_no = 0 + + add = lambda **k: alt_ops.append(k) + if getattr(getattr(sys, 'opts', None), 'start_offline', False) \ + and not pref('debug.reenable_online', type=bool, default=False): + self.offline_reason = self.Reasons.CONN_FAIL + return self.connect_opts['on_fail']() + +# add lots of places to connect + for host in self.hosts: + add(server = (host, 443), + do_tls = True, + require_tls = False, + verify_tls_peer = False, + do_ssl = False) + + for host in self.hosts: + add(server = (host, 5222), + do_tls = True, + require_tls = False, + verify_tls_peer = False, + do_ssl = False) + + for host in self.hosts: + add(server = (host, 5223), + do_tls = False, + require_tls = False, + verify_tls_peer = False, + do_ssl = True) + + getLogger('digsbylogin').info('got potential servers: %r', self.hosts) + self.success() + + def success(self): + callback, self.callback = self.callback, None + if callback is not None: + callback.success(self.alt_connect_opts) + + @callsback + def get_login_servers(self, username='', callback = None): + if self.callback is not None: + raise Exception + + self.callback = callback + srvs = list(LOAD_BALANCE_ADDRS) + load_server = getattr(getattr(sys, 'opts', None), 'loadbalancer', None) + if load_server is not None: + ls = load_server.split(':') + if len(ls) != 2: + ls = (load_server, 80) + else: + ls = (ls[0], int(ls[1])) + srvs = [ls] + random.shuffle(srvs) + #idle + DigsbyLoadBalanceManager(profile = self.profile, username=username, servers=srvs, success=self.lb_success, error=self.lb_error, + load_server=load_server, initial=self.initial).process_one() + + def lb_success(self, lb_mgr, balance_info): + loadbalanced_servers = balance_info.addresses + strategy = balance_info.reconnect_strategy + if strategy: + self.offline_reason = self.Reasons.CONN_FAIL + self.profile.balance_info = balance_info + self.connect_opts['on_fail']() + else: + self.lb_finish(lb_mgr, loadbalanced_servers) + + def lb_error(self, lb_mgr): + self.lb_finish(lb_mgr) + + def lb_finish(self, lb_mgr, loadbalanced_servers=None): + #do fallback + if not loadbalanced_servers: + hosts = list(JABBER_SRVS_FALLBACK) + random.shuffle(hosts) + log.error("could not get server information from HTTP load interface. falling back to %r", hosts) + ret = hosts + else: + ret = loadbalanced_servers + #inject commandline + opts_server = getattr(getattr(sys, 'opts', None), 'server', None) + #commandline lb answer preempts commandline server + opts_server = [opts_server] if (opts_server and not lb_mgr.load_server) else [] + self.finish_init(opts_server + ret) + +if __name__ == '__main__': + pass diff --git a/digsby/src/digsby/DigsbyProtocol.py b/digsby/src/digsby/DigsbyProtocol.py new file mode 100644 index 0000000..29fdd6b --- /dev/null +++ b/digsby/src/digsby/DigsbyProtocol.py @@ -0,0 +1,673 @@ +''' +A Jabber connection to the Digsby server. +''' +from __future__ import with_statement +from peak.util.imports import lazyModule +import datetime +from digsby.loadbalance import DigsbyLoadBalanceAPI, DigsbyLoadBalanceManager +callbacks = lazyModule('util.callbacks') +mapping = lazyModule('util.primitives.mapping') +funcs = lazyModule('util.primitives.funcs') +error_handling = lazyModule('util.primitives.error_handling') +hook_util = lazyModule('util.hook_util') +util_threads = lazyModule('util.threads') +import common +import sys, traceback +from common import pref, netcall +from hashlib import sha256 +import jabber +from jabber import JID +from pyxmpp.presence import Presence +from pyxmpp.roster import RosterItem +from operator import itemgetter +import digsby +from .digsbybuddy import DigsbyBuddy +import random +import string +import blobs + +import simplejson + +from logging import getLogger +log = getLogger('digsbylogin') + +def_cb = lambda *a, **k:None + +LOAD_BALANCE_ADDRS = [ + ('login1.digsby.org', 80), + ('login2.digsby.org', 80) + ] + +JABBER_SRVS_FALLBACK = [ + 'api1.digsby.org', + 'api2.digsby.org', + ] + +from jabber.JabberConversation import get_message_body, Conversation,\ + get_message_timestamp + +class DigsbyProtocol(jabber.protocol): + buddy_class = DigsbyBuddy + + bots = jabber.protocol.bots | set((JID('digsby.org'),)) + + class Statuses(jabber.protocol.Statuses): + SYNC_PREFS = _('Synchronizing Preferences...') + #LOAD_SKIN = _('Loading Skin...') + AUTHORIZED = _('Synchronizing...') + + name = 'digsby' + + status_state_map = { + 'available': 'normal', + 'away': 'dnd', + 'free for chat': 'chat', + 'do not disturb': 'dnd', + 'extended away': 'xa' + } + + def get_login_servers(self, username=''): + srvs = list(LOAD_BALANCE_ADDRS) + load_server = getattr(getattr(sys, 'opts', None), 'loadbalancer', None) + if load_server is not None: + ls = load_server.split(':') + if len(ls) != 2: + ls = (load_server, 80) + else: + ls = (ls[0], int(ls[1])) + srvs = [ls] + random.shuffle(srvs) + #idle + DigsbyLoadBalanceManager(profile = self.profile, username=username, servers=srvs, success=self.lb_success, error=self.lb_error, + load_server=load_server, initial=self.initial).process_one() + + def lb_success(self, lb_mgr, balance_info): + loadbalanced_servers = balance_info.addresses + strategy = balance_info.reconnect_strategy + if strategy: + self.offline_reason = self.Reasons.CONN_FAIL + self.profile.balance_info = balance_info + self.connect_opts['on_fail']() + else: + self.lb_finish(lb_mgr, loadbalanced_servers) + + def lb_error(self, lb_mgr): + self.lb_finish(lb_mgr) + + def lb_finish(self, lb_mgr, loadbalanced_servers=None): + #do fallback + if not loadbalanced_servers: + hosts = list(JABBER_SRVS_FALLBACK) + random.shuffle(hosts) + log.error("could not get server information from HTTP load interface. falling back to %r", hosts) + ret = hosts + else: + ret = loadbalanced_servers + #inject commandline + opts_server = getattr(getattr(sys, 'opts', None), 'server', None) + #commandline lb answer preempts commandline server + opts_server = [opts_server] if (opts_server and not lb_mgr.load_server) else [] + self.finish_init(opts_server + ret) + + thread_id = 0 + def __init__(self, username, password, profile, user, server, login_as='online', + resource="Digsby", priority=5, + **opts): + self.login_opts = l = mapping.Storage() + self.initial = opts.pop('initial', None) + #'#("digsby.org",5555) + l.cid = "foo" + l.jid = JID(username + "@digsby.org") + l.username = username + + l.password = sha256(password.encode('utf-8')).digest() + + self.profile = profile + + #initialize the OneShotHook + hook_util.OneShotHook(self.profile, 'digsby.accounts.released.async').check_registered() + + l.user = user + l.server = server + l.login_as = login_as + alphanum = string.letters + string.digits + l.resource = resource + "." + "".join(random.choice(alphanum) for x in xrange(6)) + l.priority = priority + l.opts = opts + + common.StateMixin.__init__(self) + + jabber.protocol.__init__(self, l.jid.as_utf8(), "fakepw", l.user, ('fakehost', 1337), + login_as = l.login_as, + resource = l.resource, + priority = l.priority, + **l.opts) + self.blobhashes = {} + + self.video_chats = {} + self.video_chat_buddies = {} + + self._idle = None + + def message(self, message): + if self.video_widget_intercept(message): + # Messages from special resource IDs on guest.digsby.org are routed to + # video chat IM windows. + return + else: + return jabber.protocol.message(self, message) + + def add_video_chat(self, resource, video_chat): + log.info('associating video widget resource %r with %r', resource, video_chat) + self.video_chats[resource] = video_chat + self.video_chat_buddies[video_chat.buddy_info] = video_chat + + def remove_video_chat(self, resource): + log.info('removing video resource %r', resource) + + try: + video_chat = self.video_chats.pop(resource) + del self.video_chat_buddies[video_chat.buddy_info] + except KeyError: + log.warning('tried to remove video chat token %s, but not found', resource) + + def send_message_intercept(self, buddy, message): + if not self.video_chat_buddies: return + + from contacts.buddyinfo import BuddyInfo + binfo = BuddyInfo(buddy) + + if binfo in self.video_chat_buddies: + log.debug('intercepted outgoing videochat message') + video_chat = self.video_chat_buddies[binfo] + video_chat.send_im(message) + + def video_widget_intercept(self, message): + 'Reroutes messages from video widgets.' + + video_buddy = self._video_buddy_for_message(message) + if video_buddy is None: + return False + elif video_buddy is True: + return True + + body = get_message_body(message) + log.info('intercepted video widget message') + log.info_s('message is %r', body) + + if body: + buddy = video_buddy.buddy() + if buddy is None: + log.critical('got video widget message, but linked buddy is gone') + log.critical('buddy info was %r', video_buddy) + return True + + convo = buddy.protocol.convo_for(buddy) + if convo.received_message(buddy, body): + Conversation.incoming_message(convo) + + return True # TODO: typing notifications? + + def _video_buddy_for_message(self, message): + 'Returns a video buddy info for a message object.' + + _from = message.get_from() + + # Video widgets are always on the guest server. + if _from.domain != 'guest.digsby.org': + return log.info('not a video widget: domain is %r', _from.domain) + + # Video widgets' resources always start with "video." + resource = _from.resource + if not resource.startswith('video.'): + return log.info("resource does not start with video: %s", resource) + else: + # strip off the video. part + resource = resource[6:] + + # Do we have a matching resource ID for a video widget? + video_chat = self.video_chats.get(resource, None) + if video_chat is None: + log.info('ignoring video widget message--no video chat open client side (resource=%s)' % resource) + return True + + # set the JID in the video chat object so it can forward messages to the widget as well + video_chat.widget_jid = _from + + video_buddy = video_chat.buddy_info + if video_buddy is None: + log.info('ignoring message from unknown video widget: JID=%s, message=%r', _from, get_message_body(message)) + return None + + return video_buddy + + def remove_widget_buddies(self, widget): + ''' + Removes all JIDs in the group specified by the given widget. + ''' + group = widget.title + + try: + buddies = self.buddies.values() + for buddy in buddies: + try: + if getattr(buddy, 'iswidget', False) and group in buddy.groups: + buddy.remove() + except Exception: + pass + except Exception: + pass + + def finish_init(self, sock): + self.hosts = sock + lh = len(self.hosts) + self.alt_connect_opts = alt_ops = [] + self.on_alt_no = 0 + + add = lambda **k: alt_ops.append(k) + if getattr(getattr(sys, 'opts', None), 'start_offline', False) \ + and not pref('debug.reenable_online', type=bool, default=False): + self.offline_reason = self.Reasons.CONN_FAIL + return self.connect_opts['on_fail']() + +# add lots of places to connect + for host in self.hosts: + add(server = (host, 443), + do_tls = True, + require_tls = False, + verify_tls_peer = False, + do_ssl = False) + + for host in self.hosts: + add(server = (host, 5222), + do_tls = True, + require_tls = False, + verify_tls_peer = False, + do_ssl = False) + + for host in self.hosts: + add(server = (host, 5223), + do_tls = False, + require_tls = False, + verify_tls_peer = False, + do_ssl = True) + + l = self.login_opts + getLogger('digsbylogin').info('got potential servers: %r', self.hosts) + self.password = l.password + self.finish_Connect() + + def filter_group(self, group): + # Filter groups with all video widgets. + + # May be the root group--which might have Contacts and Groups. + return group and all(getattr(contact, 'is_video_widget', False) for contact in group) + + def filter_contact(self, contact): + return getattr(contact, 'is_video_widget', False) + + def Connect(self, register = False, on_success = None, on_fail = None, invisible = False): + self.change_state(self.Statuses.CONNECTING) + + def on_fail(err=None, fail_cb=on_fail): + log.info('on_fail in Connect') + self.offline_reason = self.Reasons.CONN_FAIL + self.setnotifyif('state', self.Statuses.OFFLINE) + if fail_cb is not None: + fail_cb() + + self.connect_opts = c = mapping.Storage(register = register, + on_success = on_success, + on_fail = on_fail, + invisible = invisible) + l = self.login_opts + + # Go find the load balancer; if you cannot, call on_fail + self.get_login_servers(l.username) + + def finish_Connect(self): + c = self.connect_opts + on_fail = c.on_fail + + invisible = c.invisible + #don't start with the defaults, start with the list created in finish_init + return jabber.protocol._reconnect(self, invisible=invisible, do_conn_fail=False, error=on_fail) + + def session_started(self): + #register/init blobs + s = self.stream + funcs.do(s.set_iq_set_handler("query", ns, self.profile.blob_manager.blob_set) + for ns in blobs.ns_to_name.keys()) + s.set_iq_set_handler("query", digsby.accounts.DIGSBY_ACCOUNTS_NS, self.profile.account_manager.accounts_set) + s.set_iq_set_handler("query", digsby.widgets.DIGSBY_WIDGETS_NS, self.incoming_widgets) + + s.set_message_handler('normal', self.conditional_messages_handler, + namespace = 'http://www.digsby.org/conditions/v1.0', + priority = 98) + jabber.protocol.session_started(self) + #simulates conditions in ticket #3186 + #if this is uncommented and the "have_connected" property below is removed + #the connection will fail. + +# assert not getattr(self.profile, 'kill', False) +# if not getattr(self, 'test_conn_killed', False): #see ticket #3186 +# self.test_conn_killed = 1 +# assert False + + def profile_has_connected(self): + return getattr(self.profile, '_have_connected', False) + + @property + def want_try_again(self): + ''' + True if we've never started a session and we have other places left to try. + ''' + #auth error == failed to connect for digsbyprotocol + # auth error means the server is having trouble talking to it's database, or you + # logged in from two places at once (race condition w/ login server) + if not self.have_connected and bool((self.on_alt_no + 1) <= len(self.alt_connect_opts)): + return True + if self.offline_reason == self.Reasons.BAD_PASSWORD: + return self.profile_has_connected() + return False + + def auth_failed(self, reason=''): + if reason in ('bad-auth', 'not-authorized'): + self._auth_error_msg = reason + self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) + self._failed_hosts.add(self.stream.server) + elif reason: + self.error_txt = reason + self.fatal_error() + + def fatal_error(self): + if getattr(self.stream, '_had_host_mismatch', False) and not self.offline_reason: + log.error('there was a host mismatch, interpreting it as auth error...') + self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) # technically it's a bad username, but auth error regardless + + return jabber.protocol.fatal_error(self) + + @property + def service(self): + return 'digsby' + + @callbacks.callsback + def set_blob(self, elem_name, data, force = False, callback = None): + assert False + blob = blobs.name_to_obj[elem_name](data=data) + + if self.blobhashes.get(elem_name, sentinel) == self.calc_blob_hash(blob) and not force: + log.info('set_blob %s: no change', elem_name) + return callback.success() + + # send blob out to network. + iq = blob.make_push(self) + + log.info('%s: changed, sending stanza %r', elem_name, iq) + self.send_cb(iq, success = lambda s: self.set_blob_success(s, blob._data, callback = callback), + error = callback.error, + timeout = callback.timeout) + + @callbacks.callsback + def set_blob_success(self, stanza, data, callback = None): + assert False + ns = stanza.get_query_ns() + name = blobs.ns_to_name[ns] + + blob = blobs.ns_to_obj[ns](stanza.get_query()) + blob._data = data + + self.blob_cache(blob) + callback.success() + + + def incoming_blob(self, blob): + useful_data = blob.data + name = blobs.ns_to_name[blob.xml_element_namespace] + self.profile.update_blob(name, useful_data) + + @callbacks.callsback + def get_blob_raw(self, elem_name, tstamp='0', callback=None): + try: + blob = blobs.name_to_obj[elem_name](tstamp) + blob._data = None + iq = blob.make_get(self) + except Exception: + blob = blobs.name_to_obj[elem_name]('0') + blob._data = None + iq = blob.make_get(self) + self.send_cb(iq, callback = callback) + + @callbacks.callsback + def set_account(self, account, action='add', order=None, callback=None): + account = digsby.accounts.Account(account.id if action == 'delete' else account, action=action) + self.set_accounts([account], order, callback=callback) + + @callbacks.callsback + def set_accounts(self, accounts=[], order=None, callback=None): + if not order: + order = self.profile.account_manager.order + daccts = digsby.accounts.Accounts(accounts, order) + log.debug('setting accounts: %r with order: %r', [(a.action, a.id, a.protocol, a.username) for a in daccts], order) + iq = daccts.make_push(self) + self.send_cb(iq, callback=callback) + + @callbacks.callsback + def get_accounts(self, callback=None): + iq = digsby.accounts.Accounts().make_get(self) + self.send_cb(iq, callback = callback) + + def get_widgets(self): + iq = digsby.widgets.make_get(self) + self.send_cb(iq, success=self.incoming_widgets) + + def incoming_widgets(self, stanza): + widgets = digsby.widgets.Widgets(stanza.get_query()) + self.profile.incoming_widgets(widgets) + + def set_profile(self, *a, **k): + pass + + def subscription_requested(self, stanza): + 'A contact has requested to subscribe to your presence.' + + assert stanza.get_type() == 'subscribe' + + to_jid=stanza.get_from() + if to_jid.domain in pref('digsby.guest.domains', ['guest.digsby.org']): + from_jid = stanza.get_to() + groups = jabber.jabber_util.xpath_eval(stanza.xmlnode, 'd:group', {'d':"digsby:setgroup"}) + if groups: + group = groups[0].getContent() + item = RosterItem(node_or_jid = to_jid, + subscription = 'none', + name = None, + groups = (group,), + ask = None) + q = item.make_roster_push() + self.send(q) + pr2=Presence(stanza_type='subscribe', from_jid=from_jid, + to_jid=to_jid) + self.send(pr2) + self.send(stanza.make_accept_response()) + return True + else: + return jabber.protocol.subscription_requested(self, stanza) + + def stream_error(self, err): + if err.get_condition().name == "pwchanged": + self.change_reason(self.Reasons.BAD_PASSWORD) + self.profile.signoff(kicked=True) + else: + jabber.protocol.stream_error(self, err) + + def set_buddy_icon(self, icon_data): + pass + + def get_buddy_icon(self, screenname): + pass + + photo_hash = property(lambda *a: None, lambda *a: None) + + def conditional_messages_handler(self, stanza): + # bind profile.account_manager.all_accounts and sys.REVISION to the conditional_messages function + from common import profile + import sys + + ret = conditional_messages(stanza, + revision = sys.REVISION) + + if ret in (None, True): #None = not a conditional message, True = filtered for cause + return ret + log.critical('got a conditional message. accttypes = %r', ret) + + if ret == []: #no account types, no reason to filter it here. + return None + accttypes = ret + + message = stanza + from_jid = message.get_from() + log.critical('from_jid = %r', from_jid) + if from_jid != 'digsby.org': + return None + + buddy = self.get_buddy(from_jid) + + body = get_message_body(message) + log.critical('body = %r', body) + if not body: #no message + return + + timestamp = get_message_timestamp(message) + if not timestamp: + timestamp = datetime.datetime.utcnow() + + content_type = 'text/html' + #delay call based on accounts having shown up, then: + + messageobj = common.message.Message(buddy = buddy, + message = body, + timestamp = timestamp, + content_type = content_type) + + import hooks + def do_acct_math(*a, **k): + has_accts = set(a.protocol for a in self.profile.all_accounts) + if (has_accts & set(accttypes)): #user has accounts we're targeting + log.critical('sending message obj to hook') + hooks.notify('digsby.server.announcement', messageobj) + else: + log.critical('filtering conditional message for no accounts in common') + + log.critical('one shot next') + if not hook_util.OneShotHook(self.profile, 'digsby.accounts.released.async')(do_acct_math, if_not_fired=True): + log.critical('no one shot, calling now') + do_acct_math() + + return True + + def presence_push(self, status=None): + assert status is None + status = self.status + import hooks + if status: + status = hooks.reduce('digsby.status.tagging.strip_tag', status, impl='text') + return super(DigsbyProtocol, self).presence_push(status) + + def set_idle(self, yes): + self._idle = bool(yes) + self.presence_push() + + def set_message(self, message, status, format=None, default_status='dnd'): + jabber.protocol.set_message(self, message, status, format, default_status) + + def __set_show(self, state): + self._show = state + + def __get_show(self): + if self._idle: + return 'away' + else: + return self._show + + show = property(__get_show, __set_show) + +def conditional_messages(stanza, **opts): + ''' + Returns True (meaning stanza was handled) if special conditions are met. + + opts must have + all_accounts - usually profile.account_manager.all_accounts + revision - sys.REVISION + ''' + + d = dict(x = 'http://www.digsby.org/conditions/v1.0') + conditions = stanza.xpath_eval('x:x/x:condition', d) + if not conditions: + return + + num_conds = len(conditions) + num_handled = 0 + + accttypes = error_handling.try_this(lambda: + [str(n.getContent().strip()) for n in stanza.xpath_eval("x:x/x:condition[@type='has-account-type']", d)], + []) + + num_handled += len(accttypes) + + try: + below_conds = stanza.xpath_eval("x:x/x:condition[@type='revision-below-eq']", d) + if below_conds: + rev_below = int(below_conds[0].getContent().strip()) + else: + rev_below = None + except Exception: + rev_below = None + + try: + above_conds = stanza.xpath_eval("x:x/x:condition[@type='revision-above-eq']", d) + if above_conds: + rev_above = int(above_conds[0].getContent().strip()) + else: + rev_above = None + except Exception: + rev_above = None + + try: + not_understood_conds = stanza.xpath_eval("x:x/x:condition[@type='if-not-understood']", d) + if not_understood_conds: + not_understood = not_understood_conds[0].getContent().strip() + else: + not_understood = None + except Exception: + not_understood = None + + if not_understood is not None: + num_handled += 1 + + if rev_above is not None: + num_handled += 1 + + if rev_below is not None: + num_handled += 1 + + sysrev = error_handling.try_this(lambda: int(opts['revision']), None) + + if rev_below is not None and rev_above is not None and sysrev is not None: + if rev_below <= rev_above: + if sysrev > rev_below and sysrev < rev_above: + log.critical('filtering conditional message for revision not in range') + return True + else: + if sysrev > rev_below or sysrev < rev_above: + log.critical('filtering conditional message for revision in range') + return True + + if num_handled != num_conds and not_understood == "don't-show": + log.critical('filtering conditional message for not understood') + return True + + return accttypes + +if __name__ == '__main__': + pass diff --git a/digsby/src/digsby/__init__.py b/digsby/src/digsby/__init__.py new file mode 100644 index 0000000..5bd2696 --- /dev/null +++ b/digsby/src/digsby/__init__.py @@ -0,0 +1,22 @@ +from digsby.abstract_blob import * +from digsby.DigsbyProtocol import DigsbyProtocol as protocol +from digsby.digsbybuddy import DigsbyBuddy as dbuddy +from digsby.widgets.widget import iswidget +import loginutil +import blobs +import accounts +import widgets +import web +from loginutil import DigsbyLoginError + +def make_get(self, digsby_protocol): + iq = Iq(stanza_type="get") + iq.set_to(digsby_protocol.jid.domain) + self.as_xml(parent=iq.get_node()) + return iq + +import digsbysasl +import pyxmpp.sasl + +pyxmpp.sasl.safe_mechanisms_dict["DIGSBY-SHA256-RSA-CERT-AES"] = (digsbysasl.DigsbyAESClientAuthenticator,None) +pyxmpp.sasl.all_mechanisms_dict["DIGSBY-SHA256-RSA-CERT-AES"] = (digsbysasl.DigsbyAESClientAuthenticator,None) diff --git a/digsby/src/digsby/abstract_blob.py b/digsby/src/digsby/abstract_blob.py new file mode 100644 index 0000000..66b9ad4 --- /dev/null +++ b/digsby/src/digsby/abstract_blob.py @@ -0,0 +1,107 @@ +import binascii +from pyxmpp.xmlextra import get_node_ns_uri +from pyxmpp.utils import to_utf8 +import libxml2 #@UnresolvedImport +from pyxmpp.iq import Iq +from pyxmpp.xmlextra import common_doc +import base64 +from pyxmpp.objects import StanzaPayloadObject + +MAX_BLOB_SIZE = int(65536 * 3/5) # b/c of base 64 +MAX_VARBINARY = 255 + + +class AbstractBlob(StanzaPayloadObject): + xml_element_name = 'query' + + def __init__(self, xmlnode_or_time=None, data=None, rawdata=sentinel): + self._data = None + self.tstamp = None + self.update_needed = False + if isinstance(xmlnode_or_time,libxml2.xmlNode): + self.__from_xml(xmlnode_or_time) + elif rawdata is not sentinel: + self.tstamp = xmlnode_or_time + self._data = rawdata + else: + self.tstamp = xmlnode_or_time + self.data = data + + def __from_xml(self, node): + self.tstamp = None + AbstractBlob.set_data(self, None) + if node.type!="element": + raise ValueError,"XML node is not a %s (not en element)" % self.xml_element_namespace + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s" % self.xml_element_namespace + + n=node.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=self.xml_element_namespace: + n=n.next + continue + if n.name=="data": + AbstractBlob.set_data(self, base64.decodestring(n.getContent())) + elif n.name=="time": + self.tstamp=n.getContent() + elif n.name=="update-needed": + self.update_needed = True + n=n.next + + def complete_xml_element(self, xmlnode, _unused): + xmlnode.newTextChild(None, "time", to_utf8(self.tstamp)) if self.tstamp is not None else None + + bytes = self._data + if bytes is not None: + xmlnode.newTextChild(None, "data", binascii.b2a_base64(bytes)) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + + def set_data(self, data): + datalen = len(data) if data is not None else 0 + if datalen > MAX_BLOB_SIZE: + raise ValueError("Blob Size %d out of range 0 - %d." % + (datalen, MAX_BLOB_SIZE)) + self._data = data + + def get_data(self): + return self._data + + def del_data(self): + self._data = None + + data = property(get_data, set_data, del_data) + + def make_push(self, digsby_protocol): + 'Creates a set stanza.' + + iq=Iq(stanza_type="set") + iq.set_to(digsby_protocol.jid.domain) + self.as_xml(parent=iq.get_node()) + return iq + + def make_get(self, digsby_protocol): + iq = Iq(stanza_type="get") + iq.set_to(digsby_protocol.jid.domain) + self.as_xml(parent=iq.get_node()) + return iq + +class AbstractVarBinary(AbstractBlob): + def set_data(self, data): + datalen = len(data) if data is not None else 0 + if datalen > MAX_VARBINARY: + raise ValueError("VarBinary Size %d out of range 0 - %d." % + (datalen, MAX_VARBINARY)) + self._data = data + + data = property(AbstractBlob.get_data, set_data, AbstractBlob.del_data) diff --git a/digsby/src/digsby/accounts/__init__.py b/digsby/src/digsby/accounts/__init__.py new file mode 100644 index 0000000..becc1d1 --- /dev/null +++ b/digsby/src/digsby/accounts/__init__.py @@ -0,0 +1,23 @@ +from pyxmpp.iq import Iq +DIGSBY_ACCOUNTS_NS = 'digsby:accounts' +ADD = 'add' +UPDATE = 'update' +DELETE = 'delete' +ACC_ACTS = [ADD, UPDATE, DELETE] +import account +from account import Account +from accounts import Accounts + + +def make_push(acct, digsby_protocol): + iq=Iq(stanza_type="set") + iq.set_to(digsby_protocol.jid.domain) + q = iq.new_query(DIGSBY_ACCOUNTS_NS) + acct.as_xml(parent=q) + return iq + +def make_get(digsby_protocol): + iq = Iq(stanza_type="get") + iq.set_to(digsby_protocol.jid.domain) + q = iq.new_query(DIGSBY_ACCOUNTS_NS) + return iq \ No newline at end of file diff --git a/digsby/src/digsby/accounts/account.py b/digsby/src/digsby/accounts/account.py new file mode 100644 index 0000000..f2a8c37 --- /dev/null +++ b/digsby/src/digsby/accounts/account.py @@ -0,0 +1,129 @@ +from pyxmpp.utils import from_utf8 +from pyxmpp.iq import Iq +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.utils import to_utf8 +from pyxmpp.xmlextra import common_doc +from pyxmpp.xmlextra import get_node_ns_uri +import base64 +import cPickle +import pickletools +import binascii +import digsby.accounts +import digsbyprofile +import common +from digsby.accounts import DIGSBY_ACCOUNTS_NS +import libxml2 + + +def fix_truncated(short): + from common.protocolmeta import fix_shortname + return fix_shortname.get(short, short) + +class Account(StanzaPayloadObject, common.HashedAccount): + xml_element_name = 'account' + xml_element_namespace = DIGSBY_ACCOUNTS_NS + + def __init__(self, xmlnode_or_acct_or_id=None, protocol=None, username=None, + password=None, data=None, action=None): + + # from an incoming XML node + if isinstance(xmlnode_or_acct_or_id, libxml2.xmlNode): + self.__from_xml(xmlnode_or_acct_or_id) + + # from an account object + elif isinstance(xmlnode_or_acct_or_id, common.AccountBase): + acct = xmlnode_or_acct_or_id + self.id = acct.id + self.protocol = acct.protocol_info().get('name_truncated', acct.protocol) + self.username = acct.name + self.password = acct.password + try: + self.data = cPickle.dumps(acct.get_options()) + except: + print 'acct.get_options()', repr(acct.get_options()) + raise + else: + if hasattr(pickletools, 'optimize'): + self.data = pickletools.optimize(self.data) + self.action = action + + # id + else: + self.id = xmlnode_or_acct_or_id + self.protocol = protocol + self.username = username + self.password = password + self.data = data + self.action = action + + if not isinstance(self.id, int) or not self.id >= 0: + raise ValueError("positive int id is required! (got %r)" % self.id) + + + def __repr__(self): + return '' % (self.protocol, self.username) + + def __from_xml(self, node): + '''A libxml2 node to a digsby.account''' + + if node.type!="element": + raise ValueError,"XML node is not an account element (not en element)" + ns = get_node_ns_uri(node) + if ns and ns != DIGSBY_ACCOUNTS_NS or node.name != "account": + raise ValueError,"XML node is not an account element" + id = node.prop("id") + self.id = int(from_utf8(id)) if id else None + + username = node.prop("username") + self.username = from_utf8(username) if username else None + + protocol = node.prop("protocol") + self.protocol = from_utf8(protocol) if protocol else None + + self.protocol = fix_truncated(self.protocol) + + password = node.prop("password") + self.password = base64.b64decode(password) if password else None + + action = node.prop("action") + self.action = from_utf8(action) if action else None + + self.data = None + n=node.children + while n: + if n.type!="element": + n = n.next + continue + ns = get_node_ns_uri(n) + if ns and ns!=DIGSBY_ACCOUNTS_NS: + n=n.next + continue + if n.name=="data": + self.data=base64.decodestring(n.getContent()) + n = n.next + + def complete_xml_element(self, xmlnode, _unused): + 'to xml' + + if isinstance(self.id, int) and self.id >=0: + xmlnode.setProp("id", to_utf8(str(self.id))) + else: + raise ValueError, "self.id must be int" + xmlnode.setProp("protocol", to_utf8(self.protocol)) if self.protocol else None + xmlnode.setProp("username", to_utf8(self.username)) if self.username else None + xmlnode.setProp("password", base64.b64encode(self.password)) if self.password else None + xmlnode.setProp("action", to_utf8(self.action)) if self.action else None + xmlnode.newTextChild(None, "data", binascii.b2a_base64(self.data)) if self.data is not None else None + + def make_push(self, digsby_protocol): + return digsby.accounts.Accounts([self]).make_push(digsby_protocol) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + + def get_options(self): + return cPickle.loads(self.data) diff --git a/digsby/src/digsby/accounts/accounts.py b/digsby/src/digsby/accounts/accounts.py new file mode 100644 index 0000000..a89c9cd --- /dev/null +++ b/digsby/src/digsby/accounts/accounts.py @@ -0,0 +1,80 @@ +from digsby.accounts.account import Account +from pyxmpp.iq import Iq +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri +from common.hashacct import HashedAccounts +import base64 +import binascii +from digsby.accounts import DIGSBY_ACCOUNTS_NS +import libxml2 + +from jabber.jabber_util import xpath_eval + + +class Accounts(StanzaPayloadObject, list, HashedAccounts): + xml_element_name = 'query' + xml_element_namespace = DIGSBY_ACCOUNTS_NS + + def __init__(self, xmlelem_or_accounts=[], order=sentinel): + if isinstance(xmlelem_or_accounts, libxml2.xmlNode): + self.__from_xml(xmlelem_or_accounts) + else: + self.order = order + self.extend(xmlelem_or_accounts) + + def __from_xml(self, node): + if node.type!="element": + raise ValueError,"XML node is not an Accounts element (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=DIGSBY_ACCOUNTS_NS or node.name!="query": + raise ValueError,"XML node is not an Accounts element" + accts = xpath_eval(node, 'a:account',{'a':DIGSBY_ACCOUNTS_NS}) + orders = xpath_eval(node, 'a:order',{'a':DIGSBY_ACCOUNTS_NS}) + self.order = [ord(c) for c in + base64.decodestring(orders[0].getContent())] if orders else [] + self.extend(Account(acct) for acct in accts) + + def complete_xml_element(self, xmlnode, doc): + if self.order is not sentinel: + xmlnode.newTextChild(None, "order", + binascii.b2a_base64(''.join(chr(c) + for c in self.order))) + + [item.as_xml(xmlnode, doc) for item in self] + + def make_push(self, digsby_protocol): + iq=Iq(stanza_type="set") + iq.set_to(digsby_protocol.jid.domain) + self.as_xml(parent=iq.get_node()) + return iq + + def make_get(self, digsby_protocol): + iq = Iq(stanza_type="get") + iq.set_to(digsby_protocol.jid.domain) + self.as_xml(parent=iq.get_node()) + return iq + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + + @staticmethod + def from_local_store(local_info): + ''' + Create an Accounts object filled with Account objects from a dictionary + of primitives (usually from the local accounts store). + ''' + accts = Accounts(order=local_info['order']) + + for a in local_info['accts']: + # TODO: from_net always calls cPickle.loads on it's .data -- we have a case + # where that shouldn't happen + import cPickle + data = cPickle.dumps(a['data']) + + accts.append(Account(a['id'], a['protocol'], a['username'], a['password'], data)) + + return accts diff --git a/digsby/src/digsby/accounts/util.py b/digsby/src/digsby/accounts/util.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/digsby/blobs.py b/digsby/src/digsby/blobs.py new file mode 100644 index 0000000..727d0fb --- /dev/null +++ b/digsby/src/digsby/blobs.py @@ -0,0 +1,290 @@ +from __future__ import with_statement +from digsby.abstract_blob import AbstractBlob +import cPickle +from pyxmpp.utils import to_utf8 +from copy import deepcopy +from util import Storage +from logging import getLogger; log = getLogger('blobs'); info = log.info +from util.observe import ObservableDict, ObservableList +from struct import pack, unpack +import struct +from os.path import join as pathjoin +from zlib import compress, decompress +import util, syck +import prefs + +import common.notifications as comnot + +DIGSBY_PREFS_NS = 'digsby:prefs' +DIGSBY_DEFAULT_PREFS_NS = 'digsby:defaultprefs' +DIGSBY_ICON_NS = 'digsby:icon' +DIGSBY_EVENTS_NS = 'digsby:events' +DIGSBY_STATUS_NS = 'digsby:status' +DIGSBY_BUDDYLIST_NS = 'digsby:blist' +DIGSBY_PROFILE_NS = 'digsby:profile' + +def to_primitive(thing): + if isinstance(thing, ObservableDict): + return dict(thing) + elif isinstance(thing, ObservableList): + return list(thing) + return thing + + +def maybe_copy(thing): + if isinstance(thing, (dict, list)): + return deepcopy(thing) + return thing + +class NetData(AbstractBlob): + + timeout = 300 + + @classmethod + def pack(cls, data): + return cPickle.dumps(data) + + @classmethod + def unpack(cls, data): + return cPickle.loads(data) + + def set_data(self, data): + try: + return AbstractBlob.set_data(self, self.pack(data)) + except: + import sys + print >> sys.stderr, 'Error pickling %r: %s' % (self, repr(data)[:200]) + print >> sys.stderr, ' %s' % type(data) + raise + + def get_data(self): + try: + retval = self.unpack(AbstractBlob.get_data(self)) + return retval + except Exception, e: + import traceback + traceback.print_exc() + + log.error('Could not unpickle %s', type(self).__name__) + self.set_data(to_primitive(self.fallback())) + return self.get_data() + + def del_data(self): + self._data = None + + data = property(get_data, set_data, del_data) + + def fallback(self): + return list() + +class SerializableNetData(NetData): + __VERSION = 3 + + @classmethod + def pack(cls, data): + from util.json import pydumps + d = pydumps(data).encode('z') + v = cls.__VERSION + log.info("packing version %d of %s", v, cls.__name__) + return pack("!I", v) + d + + @classmethod + def unpack(cls, data): + if len(data) < 4: + # profile strings less than 4 bytes long should be treated as + # strings + version = 0 + else: + (version,) = unpack("!I", data[:4]) + + if version not in (1,2,3): + #assume it was really old and either v0 or had no version + #(used for converting from NetData to SerializableNetData) + log.info("unpacking version %d of %s", version, cls.__name__) + data = pack("I", 1) + data + version = 1 + if version == 1: + log.info("unpacking version %d of %s", version, cls.__name__) + data = data[4:] + try: + data = data.decode('z') + except Exception: pass + if version == 2: + log.info("unpacking version %d of %s", version, cls.__name__) + data = data[4:] + data = data.decode('z') + if version != 3: + d = NetData.unpack(data) + if isinstance(d, (SerializedDict, SerializedSet)): + return unserialize(d) + elif isinstance(d, cls.basetype): + return d + else: + return cls.upgrade(d) + if version == 3: + log.info("unpacking version %d of %s", version, cls.__name__) + from util.json import pyloads + return pyloads(data[4:].decode('z')) + + @classmethod + def upgrade(cls, d): + return cls.basetype(d) + +class ODictNetData(NetData): + fallback = lambda self: ObservableDict() + basetype = dict + +class Prefs(ODictNetData, SerializableNetData): + xml_element_namespace = DIGSBY_PREFS_NS + +class DefaultPrefs(NetData): + xml_element_namespace = DIGSBY_DEFAULT_PREFS_NS + diskdata = None + + def complete_xml_element(self, xmlnode, _unused): + xmlnode.newTextChild(None, "time", to_utf8(self.tstamp)) if self.tstamp is not None else None + + def set_data(self, data): + pass + + def get_data(self): + if self.diskdata is None: + defaults = prefs.defaultprefs() + self.diskdata = defaults + return self.diskdata + + + def del_data(self): + pass + + data = property(get_data, set_data, del_data) + +class BuddyList(ODictNetData, SerializableNetData): + xml_element_namespace = DIGSBY_BUDDYLIST_NS + +class Statuses(SerializableNetData): + xml_element_namespace = DIGSBY_STATUS_NS + + fallback = lambda self: ObservableList() + basetype = list + +class Profile(SerializableNetData): + xml_element_namespace = DIGSBY_PROFILE_NS + basetype = dict + fallback = lambda self: dict(plaintext='') + + @classmethod + def upgrade(cls, d): + # allow upgrading from when profile blobs were just (pickled) strings + # the code to handle this case is in _incoming_blob_profile + if isinstance(d, basestring): + return d + + return cls.basetype(d) + +class Icon(NetData): + xml_element_namespace = DIGSBY_ICON_NS + + fallback = lambda self: '' + + @classmethod + def pack(cls, data): + return data + + @classmethod + def unpack(cls, data): + return data + + +class Notifications(ODictNetData, SerializableNetData): + xml_element_namespace = DIGSBY_EVENTS_NS + + fallback = lambda self: deepcopy(comnot.default_notifications) + +# +# +# + +#to be used for (developer) hacking only +def load_cache_from_data_disk(name, data): + from StringIO import StringIO + f = StringIO(data) + + try: + plen = struct.unpack('!H', f.read(2))[0] + tstamp = f.read(plen) + data = f.read() + except IOError: + tstamp = '0001-01-01 00:00:00' + data = None + + #print 'tstamp, data', tstamp, data + return name_to_obj[name](tstamp, rawdata=data) + +#to be used for (developer) hacking only +def load_cache_from_data_db(name, data): + tstamp = '0001-01-01 00:00:00' + #print 'tstamp, data', tstamp, data + return name_to_obj[name](tstamp, rawdata=data) + +class SerializedDict(list): + def __init__(self, dict_): + self[:] = sorted(dict_.iteritems()) + +class SerializedSet(list): + def __init__(self, set_): + self[:] = sorted(set_) + +PrimitiveTypes = frozenset((int, bool, float, long, str, unicode, type(None), type)) + +def serialize(thing): + t = type(thing) + + if t in PrimitiveTypes: + return thing + elif t is tuple: + return tuple(serialize(foo) for foo in thing) + elif t is list: + return list(serialize(foo) for foo in thing) + elif issubclass(t, dict): + bar = SerializedDict(thing) + bar[:] = [serialize(foo) for foo in bar] + return bar + elif t is set: + bar = SerializedSet(thing) + bar[:] = [serialize(foo) for foo in bar] + return bar + else: + assert False, (t, thing) + +def unserialize(thing): + t = type(thing) + + if t is SerializedDict: + return dict(unserialize(foo) for foo in thing) + elif t is SerializedSet: + return set(unserialize(foo) for foo in thing) + elif t is tuple: + return tuple(unserialize(foo) for foo in thing) + elif t is list: + return list(unserialize(foo) for foo in thing) + elif t in PrimitiveTypes: + return thing + else: + assert False, t + +ns_to_obj = {DIGSBY_PREFS_NS : Prefs, +# DIGSBY_DEFAULT_PREFS_NS : DefaultPrefs, + DIGSBY_ICON_NS : Icon, + DIGSBY_EVENTS_NS : Notifications, + DIGSBY_STATUS_NS : Statuses, + DIGSBY_BUDDYLIST_NS : BuddyList, + DIGSBY_PROFILE_NS : Profile} + +name_to_ns = dict((value.__name__.lower(), key) + for (key, value) in ns_to_obj.iteritems()) + +name_to_obj = dict((name, ns_to_obj[name_to_ns[name]]) for name in name_to_ns) + +from util import dictreverse +ns_to_name =dictreverse(name_to_ns) diff --git a/digsby/src/digsby/digsbybuddy.py b/digsby/src/digsby/digsbybuddy.py new file mode 100644 index 0000000..e00ebdb --- /dev/null +++ b/digsby/src/digsby/digsbybuddy.py @@ -0,0 +1,142 @@ +from jabber import jbuddy, jabber_util +from util import Timer, callsback +from common import pref, profile +from common.actions import action +from .objects import ip, pagetime +from pyxmpp.presence import Presence +from jabber.JabberResource import JabberResource +jbuddy_caps = jbuddy.caps + +import logging; log = logging.getLogger('digsby.buddy') + +def no_widgets(self, *a, **k): + return not self.iswidget + +class DigsbyResource(JabberResource): + away_is_idle = True + +class DigsbyBuddy(jbuddy): + + away_is_idle = True + resource_class = DigsbyResource + + def __init__(self, jabber_, jid, rosteritem = None): + jbuddy.__init__(self, jabber_, jid, rosteritem) + + from digsby.widgets.widget import iswidget + self.iswidget = iswidget(self) + self.ip = None + self.online_time = None + + # digsby buddies don't get watched + p = profile() + if p: + p.account_manager.buddywatcher.unregister(self) + + @property + def supports_group_chat(self): + return self.protocol.supports_group_chat and not self.iswidget + + @property + def is_video_widget(self): + node = self.jid.node + return node and node.startswith('video.') + + @action() + @callsback + def remove(self, callback=None): + ret = jbuddy.remove(self, callback = callback) + if self.iswidget: + def goaway(): + try: + pres = Presence(stanza_type="unavailable", status='Logged Out', to_jid=self.jid) + self.protocol.stream.send(pres) + except (AttributeError, Exception): + pass + Timer(3, goaway).start() + return ret + + @property + def caps(self): + cs = [] + + from common import caps + if self.id in self.protocol.bots: + cs.append(caps.BOT) + + if self.iswidget: + # widgets can only IM + cs.extend([caps.INFO, caps.IM]) + else: + cs.extend(jbuddy.get_caps(self)) + + return cs + + @property + def service(self): + return 'digsby' + + @property + def serviceicon(self): + from gui import skin + return skin.get('serviceicons.digsby') if not self.iswidget else skin.get('serviceicons.widget') + + def update_presence(self, presence, buddy=None): + groups = jabber_util.xpath_eval(presence.xmlnode, 'd:group',{'d':"digsby:setgroup"}) + if groups: + try: + self.widget_to_group(groups[0].getContent()) + except Exception: pass + + ips = jabber_util.xpath_eval(presence.xmlnode, 'i:ip',{'i':ip.IP_NS}) + if ips: + try: + self.ip = ip.Ip(ips[0]).ip + except Exception: pass + + pagetimes = jabber_util.xpath_eval(presence.xmlnode, 'p:pagetime',{'p':pagetime.PAGETIME_NS}) + if pagetimes: + try: + self.online_time = pagetime.PageTime(pagetimes[0]).pagetime/1000 + except Exception: pass + + jbuddy.update_presence(self, presence, buddy=buddy) + + @action(needs = ((unicode, "Group name"),)) + @callsback + def widget_to_group(self, groupname, callback = None): + log.info('%s add_to_group %s', self, groupname) + pending = self.pending_adds + + # Prevent excessive add requests. + if groupname in pending: + log.info('ignoring request.') + else: + pending.add(groupname) + + item = self.protocol.roster.get_item_by_jid(self.id).clone() + + if groupname not in item.groups: + item.groups[:] = [groupname] + query = item.make_roster_push() + + def onsuccess(_s): + pending.discard(groupname) + callback.success() + + def onerror(_s = None): + pending.discard(groupname) + log.warning("error adding %r to %s", self.id, groupname) + callback.error() + + self.protocol.send_cb(query, success = onsuccess, error = onerror, timeout = onerror) + + icon_disabled = property(lambda *a: True, lambda *a: None) + + def cache_icon(self, *a, **k): + if not self.icon_disabled: + return super(DigsbyBuddy, self).cache_icon(*a, **k) + + @property + def _disk_cacheable(self): + return not self.iswidget diff --git a/digsby/src/digsby/digsbylocal.py b/digsby/src/digsby/digsbylocal.py new file mode 100644 index 0000000..af36af4 --- /dev/null +++ b/digsby/src/digsby/digsbylocal.py @@ -0,0 +1,160 @@ +''' +Saves and loads accounts to and from disk. +''' + +from __future__ import with_statement + +import os.path +from hashlib import sha1 +import base64 +import util.cryptography as crypto +import util.primitives.files as files +import util.primitives.strings as strings +import util.cacheable as cache + +from logging import getLogger +from BlobManager import BlobManager +from digsby.blobs import load_cache_from_data_disk +import os.path +log = getLogger('digsbylocal') + +from util.json import pydumps, pyloads + +#don't zip w/o considering pad character for encryption. +#default is \0, and that's not going to the be the last character of JSON. +serialize = pydumps +unserialize = pyloads + +class InvalidUsername(Exception): + pass +class InvalidPassword(Exception): + pass + +def cipher_functions(username, password): + from sysident import sysident + + mach_key = sysident() + digest = sha1(username + password).digest() + assert len(mach_key) == len(digest) + key = strings.string_xor(digest, mach_key) + return crypto.cipher_functions(key[-16:], mode = crypto.Mode.CBC) + +def encrypt(username, password, data): + _encrypter, _decrypter = cipher_functions(username, password) + return _encrypter(data) + +def decrypt(username, password, data): + _encrypter, _decrypter = cipher_functions(username, password) + return _decrypter(data) + +def local_file(username): + 'Where to store account and local order data.' + cache_path = cache.get_cache_root(user = True) + return cache_path / 'local.accounts' + +def server_file(username): + 'Where to store the server side account hash and server order' + cache_path = cache.get_cache_root(user = True) + return cache_path / 'server.accounts' + +def dict_for_acct(acct): + acct_dictionary = dict((attr, getattr(acct, attr)) + for attr in ('id', 'protocol', 'username', 'password')) + acct_dictionary['data'] = acct.get_options() + password = acct_dictionary['password'] + assert (isinstance(password, str) or password is None) + if password is not None: + password = base64.b64encode(password) + acct_dictionary['password'] = password + return acct_dictionary + +def serialize_local(accts, order): + assert validate_order(order) + + return serialize(dict(order=order, + accts=[dict_for_acct(acct) for acct in accts])) + +def serialize_server(accounts_hash, server_order): + assert validate_order(server_order) + + return serialize(dict(accounts_hash = accounts_hash, + server_order = server_order)) + +def _load_data(username, password, filename): + if not os.path.isfile(filename): + # if there is not accounts file, then we haven't ever saved this + # username + raise InvalidUsername + + with open(filename, 'rb') as f: + data = f.read() + + data = decrypt(username, password, data) + + try: + return unserialize(data) + except Exception, e: + # if the data could not be unserialized, then it was decrypted with + # the wrong password + raise InvalidPassword(e) + +def load_local(username, password): + local_info = _load_data(username, password, local_file(username)) + + assert 'order' in local_info + assert 'accts' in local_info + + for a in local_info['accts']: + password = a['password'] + assert (isinstance(password, str) or password is None) + if password is not None: + password = password.decode('base64') + a['password'] = password + log.debug_s('loaded local: %r', local_info) + return local_info + +def load_server(username, password): + sever_info = _load_data(username, password, server_file(username)) + log.debug_s('loaded server: %r', sever_info) + return sever_info + +def save_local_info(username, password, accounts, local_order): + assert isinstance(username, basestring) + assert isinstance(password, basestring) + log.debug_s('saving local: %r', serialize_local(accounts, local_order)) + data = encrypt(username, password, serialize_local(accounts, local_order)) + + with files.atomic_write(local_file(username), 'wb') as f: + f.write(data) + +def save_server_info(username, password, server_account_hash, server_order): + assert isinstance(username, basestring) + assert isinstance(password, basestring) + server_data = serialize_server(server_account_hash, server_order) + log.debug_s('saving server: %r', server_data) + data = encrypt(username, password, server_data) + + with files.atomic_write(server_file(username), 'wb') as f: + f.write(data) + +def validate_order(o): + 'Validate that an account order object is of the correct type.' + + return isinstance(o, list) and all(isinstance(e, int) for e in o) + + +def load_local_blob(username, blobname): + cache_path = cache.get_cache_root(user = True, username=username) / BlobManager.CACHE_FOLDER + try: + pth = cache_path / blobname + if not os.path.isfile(pth): + return None + data = pth.bytes() + blob = load_cache_from_data_disk(blobname, data) + return blob.data + except Exception: + return None + + + + diff --git a/digsby/src/digsby/digsbyrsa.py b/digsby/src/digsby/digsbyrsa.py new file mode 100644 index 0000000..8a87f28 --- /dev/null +++ b/digsby/src/digsby/digsbyrsa.py @@ -0,0 +1,167 @@ +import M2Crypto + +RSA_size = len +def DIGSBY_RSA_blocksize(rsa, padding): + ''' + int DIGSBY_RSA_blocksize(RSA *rsa, int padding){ + switch (padding){ + case RSA_PKCS1_PADDING: + case RSA_SSLV23_PADDING: + return RSA_size(rsa) - 11; + case RSA_PKCS1_OAEP_PADDING: + return RSA_size(rsa) - 42; //docs say 41, but 41 doesn't work. + default: + return RSA_size(rsa); + } + } + ''' + return RSA_size(rsa) - {M2Crypto.RSA.pkcs1_padding:11, M2Crypto.RSA.pkcs1_padding:42}.get(padding, 0) + +def DIGSBY_RSA_numblocks(flen, blocksize): + ''' + int DIGSBY_RSA_numblocks(int flen, int blocksize) { + div_t numblocks; + if (!(flen && blocksize)){ + return -1; + } + numblocks = div(flen, blocksize); + return numblocks.rem ? (numblocks.quot + 1) : numblocks.quot; + } + ''' + if flen <=0 or blocksize <= 0: + raise ValueError("need both positive flen and blocksize") + quot, rem = divmod(flen, blocksize) + return quot + 1 if rem else quot + +def DIGSBY_RSA_size(flen, rsa, padding): + ''' + int DIGSBY_RSA_size(int flen, RSA *rsa, int padding) { + int blocksize, numblocks; + blocksize = DIGSBY_RSA_blocksize(rsa, padding); + numblocks = DIGSBY_RSA_numblocks(flen, blocksize); + if (numblocks <= 0) { + return -1; + } + return numblocks * RSA_size(rsa); + } + ''' + blocksize = DIGSBY_RSA_blocksize(rsa, padding) + numblocks = DIGSBY_RSA_numblocks(flen, blocksize) + if (numblocks <= 0): + raise ValueError('bad number of blocks') + return numblocks * RSA_size(rsa) + +def DIGSBY_RSA_size_inverse(flen, rsa, padding): + ''' + int DIGSBY_RSA_size_inverse(int flen, RSA *rsa, int padding) { + int blocksize, numblocks; + blocksize = DIGSBY_RSA_blocksize(rsa, padding); + numblocks = flen / RSA_size(rsa); + + if (numblocks <= 0) { + return -1; + } + return numblocks * blocksize; + } + ''' + blocksize = DIGSBY_RSA_blocksize(rsa, padding) + numblocks = flen / RSA_size(rsa) + if (numblocks <= 0): + raise ValueError('bad number of blocks') + return numblocks * blocksize; + +def DIGSBY_RSA_public_encrypt(from_, rsa, padding): + ''' + int DIGSBY_RSA_public_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) { + int m, c; + int status; + int blocksize, numblocks; + int to_encrypt, left; + blocksize = DIGSBY_RSA_blocksize(rsa, padding); + numblocks = DIGSBY_RSA_numblocks(flen, blocksize); + if (numblocks <= 0) { + return -1; + } + m = 0; + c = 0; + while (m < flen) { + left = flen - m; + to_encrypt = blocksize > left ? left : blocksize; + status = RSA_public_encrypt(to_encrypt, from + m, to + c, rsa, padding); + if (status <= 0){ + return -1; + } + c += status; + m += blocksize; + } + return c; + } + ''' + blocksize = DIGSBY_RSA_blocksize(rsa, padding) + numblocks = DIGSBY_RSA_numblocks(len(from_), blocksize) + if (numblocks <= 0): + raise ValueError('bad number of blocks') + from StringIO import StringIO + to_ = StringIO() + from_ = StringIO(from_) + while (from_.tell() < from_.len): + to_.write(rsa.public_encrypt(from_.read(blocksize), padding)) + return to_.getvalue() + +def DIGSBY_RSA_private_encrypt(from_, rsa, padding): + blocksize = DIGSBY_RSA_blocksize(rsa, padding) + numblocks = DIGSBY_RSA_numblocks(len(from_), blocksize) + if (numblocks <= 0): + raise ValueError('bad number of blocks') + from StringIO import StringIO + to_ = StringIO() + from_ = StringIO(from_) + while (from_.tell() < from_.len): + to_.write(rsa.private_encrypt(from_.read(blocksize), padding)) + return to_.getvalue() + +def DIGSBY_RSA_private_decrypt(from_, rsa, padding): + blocksize = RSA_size(rsa) + numblocks = DIGSBY_RSA_numblocks(len(from_), blocksize) + if (numblocks <= 0): + raise ValueError('bad number of blocks') + from StringIO import StringIO + to_ = StringIO() + from_ = StringIO(from_) + while (from_.tell() < from_.len): + to_.write(rsa.private_decrypt(from_.read(blocksize), padding)) + return to_.getvalue() + +def DIGSBY_RSA_public_decrypt(from_, rsa, padding): + blocksize = RSA_size(rsa) + numblocks = DIGSBY_RSA_numblocks(len(from_), blocksize) + if (numblocks <= 0): + raise ValueError('bad number of blocks') + from StringIO import StringIO + to_ = StringIO() + from_ = StringIO(from_) + while (from_.tell() < from_.len): + to_.write(rsa.public_decrypt(from_.read(blocksize), padding)) + return to_.getvalue() + +__all__ = [ + 'DIGSBY_RSA_public_encrypt', + 'DIGSBY_RSA_public_decrypt', + 'DIGSBY_RSA_private_encrypt', + 'DIGSBY_RSA_private_decrypt' + ] + +def make_x509(): + import M2Crypto + t = M2Crypto.ASN1.ASN1_UTCTIME() + t.set_time(0) + x = M2Crypto.X509.X509() + rsa = M2Crypto.RSA.gen_key(512, 0x10001) + pk = M2Crypto.EVP.PKey() + pk.assign_rsa(rsa) + del rsa + x.set_pubkey(pk) + x.set_not_after(t) + x.set_not_before(t) + x.sign(pk, 'sha1') + return x diff --git a/digsby/src/digsby/digsbysasl.py b/digsby/src/digsby/digsbysasl.py new file mode 100644 index 0000000..383a3b5 --- /dev/null +++ b/digsby/src/digsby/digsbysasl.py @@ -0,0 +1,205 @@ +import traceback +import logging + +from pyxmpp.utils import to_utf8 +from pyxmpp.sasl.core import ClientAuthenticator +from pyxmpp.sasl.core import Success,Failure,Challenge,Response + +import struct, hashlib + +log = logging.getLogger('digsby.sasl') + +import M2Crypto, util.cryptography, util.cacheable +import digsbyrsa + +# OPENSOURCE: does this need to go? seems like it should be a +# public key sort of thing but we should double check. +ROOT_CERT_RAW = '''-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIJAIm6tjo3F7DQMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxEjAQBgNVBAcTCVJvY2hlc3RlcjEX +MBUGA1UEChMOZG90U3ludGF4LCBMTEMxDzANBgNVBAsTBkRpZ3NieTETMBEGA1UE +AxMKZGlnc2J5Lm9yZzEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZGlnc2J5LmNvbTAe +Fw0xMDEyMTUyMDUwNTZaFw0xMTEyMTUyMDUwNTZaMIGUMQswCQYDVQQGEwJVUzER +MA8GA1UECBMITmV3IFlvcmsxEjAQBgNVBAcTCVJvY2hlc3RlcjEXMBUGA1UEChMO +ZG90U3ludGF4LCBMTEMxDzANBgNVBAsTBkRpZ3NieTETMBEGA1UEAxMKZGlnc2J5 +Lm9yZzEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZGlnc2J5LmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALvRZBUWh9IhxNfajLc6gXA8OVArXp2XvxCf +hHR/CznE4bqNSs8kGlkXZzGZlhRXDpMM4ytlktYN6Bu+RwR1BzjQSt/GpnuxCL5X ++l9yukATrfBs/i7iHwmWREkvJXzgi3ToyZ/NsmjmA1CtzYn44D8Sc/lsui4EeGyb +slno/ZYpCIniaHPnA1+A8u6Fbq/DgZkpLY8ZA/lgKRwtQMa216eBEhROjJVLjdN3 +GDenu/tGBIdKQLAJ1bLCswSamtmmTAIT4nqd5GH/p1PlZKpObn38+PV1Cth1ZA3R +SnIxFSRUO5s2OxWTR694hEufrLV3ccK5NSW1tuMYeDaWUfo3MIECAwEAAaOB/DCB ++TAdBgNVHQ4EFgQUq3dfcfI0E/07C6iZiyp1lwmdA24wgckGA1UdIwSBwTCBvoAU +q3dfcfI0E/07C6iZiyp1lwmdA26hgZqkgZcwgZQxCzAJBgNVBAYTAlVTMREwDwYD +VQQIEwhOZXcgWW9yazESMBAGA1UEBxMJUm9jaGVzdGVyMRcwFQYDVQQKEw5kb3RT +eW50YXgsIExMQzEPMA0GA1UECxMGRGlnc2J5MRMwEQYDVQQDEwpkaWdzYnkub3Jn +MR8wHQYJKoZIhvcNAQkBFhBhZG1pbkBkaWdzYnkuY29tggkAibq2OjcXsNAwDAYD +VR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAJCsHm8osylNqfmNMTEL6Nczr +hD95jl1D3a3hKlKHYPkZ5/pmGHV4C/ZYteSm9yWtWNQp/ZGTS+XG4I9NFQ6s6Cr1 +LLOoK52BVzal5LemAPyzXyIKuG2fwTMdBiL9fIoYDLWvjzp5SGHHc4K0mofetgxZ +TdqQr7qWXY62zdkKSgwo9HPqrhtUzyfvDBJPjzeRbGguV3jCvodgV5D7aK18K1gz +C9lIMQaWRzS80+a1dUtibwG4fTKSRaIrOmdhvI+YdTj4aNKcmq985CXD068hG09P +ArAEDrQEul9GOIqcx6RmtDSx1r+f1Iv+ef5boBu/04TZCCClDF7AYUwIErJCoQ== +-----END CERTIFICATE----- +''' + +ROOT_CERT = M2Crypto.X509.load_cert_string(ROOT_CERT_RAW) + +def _save_client_key_pem(key, uname, password): + passfunc = lambda _dec_enc: hashlib.sha256(password + "digsby" + uname.encode('utf-8')).digest() + key = key.as_pem(callback = passfunc) + if key is not None: + dec_key = key.decode('utf8') + else: + dec_key = key + + return util.cacheable.save_cache_object('client_priv_key.key', dec_key, json=True, user=True) + +def _get_client_key_pem(uname, password): + passfunc = lambda _dec_enc: hashlib.sha256(password + "digsby" + uname.encode('utf-8')).digest() + key = util.cacheable.load_cache_object('client_priv_key.key', json=True, user=True) + + if key is not None: + enc_key = key.encode('utf8') + try: + ret_key = M2Crypto.RSA.load_key_string(enc_key, callback=passfunc) + if not ret_key.check_key(): + ret_key = None + except Exception: + ret_key = None + else: + ret_key = key + + return ret_key + +def pstrI(s): + return struct.pack('!I', len(s)) + s + +def pstrIlist(l): + return [pstrI(s) for s in l] + +def pstrAES(key, s, iv=None): + p = pstrI(s) + return util.cryptography.encrypt(key, p, iv=iv, padchar = chr(256 + ~ord(p[-1])), mode = util.cryptography.Mode.CBC) + +def unpackpstrlist(s): + ret = [] + while s: + l, s = struct.unpack('!I', s[:4])[0], s[4:] + ret.append(s[:l]) + s = s[l:] + return ret + +def unpackpstr(s): + l = struct.unpack('!I', s[:4])[0] + return s[4:4+l], s[4+l:] + +class DigsbyAESClientAuthenticator(ClientAuthenticator): + + def __init__(self,password_manager): + ClientAuthenticator.__init__(self,password_manager) + self.username=None + self.password=None + self.key = None + self.step = 0 + self.authzid=None + self.__logger=logging.getLogger("digsby.sasl.AES") + + def start(self,username,authzid): + self.username=username + if authzid: + self.authzid=authzid + else: + self.authzid="" + self.step = 0 + return self.challenge("") + + def challenge(self, challenge): + if self.password is None: + self.password,pformat=self.password_manager.get_password(self.username) + if not self.password or pformat!="plain": + self.__logger.debug("Couldn't retrieve plain password") + return Failure("password-unavailable") + if self.step == 0: + self.step = 1 + return Response(''.join(pstrIlist([ + to_utf8(self.authzid), + to_utf8(self.username)]))) + + elif self.step == 1: + self.step = 2 + srv_rsa_key = None + self.__logger.critical("loading server certificate") + try: + srv_cert = M2Crypto.X509.load_cert_string(challenge) + if srv_cert is not None: + self.__logger.critical("retrieving server pubkey") + srv_key = srv_cert.get_pubkey() + if srv_key is not None: + self.__logger.critical("retrieving server RSA pubkey") + srv_rsa_key = srv_key.get_rsa() + except Exception: + traceback.print_exc() + + if srv_rsa_key is None: + return Failure("bad-server-cert") + + if not srv_cert.verify(ROOT_CERT.get_pubkey()): + return Failure("bad-server-cert") + + self.srv_rsa_key = srv_rsa_key + self.__logger.critical("generating Nonce") + from M2Crypto import m2 + nonce = m2.rand_bytes(16) + self.__logger.critical("encrypting Nonce") + enonce = digsbyrsa.DIGSBY_RSA_public_encrypt(nonce, srv_rsa_key, M2Crypto.RSA.pkcs1_oaep_padding) + + self.__logger.critical("loading key") + try: + self.key = _get_client_key_pem(self.username, self.password) + except Exception: + self.key = None + + if self.key is None: + self.__logger.critical("generating new key") + self.key = M2Crypto.RSA.gen_key(2048, 0x10001) + self.__logger.critical("saving new key") + if not self.key.check_key(): + raise ValueError("failed to generate key") + try: + _save_client_key_pem(self.key, self.username, self.password) + except Exception: + traceback.print_exc() + self.__logger.critical("creating buffer") + buff = M2Crypto.BIO.MemoryBuffer() + self.__logger.critical("serializing client public key to buffer") + self.key.save_pub_key_bio(buff) + + self.__logger.critical_s("Nonce: %r", nonce) + genkey = buff.getvalue() + self.__logger.critical_s("Key: %r", genkey) + eKey = pstrAES(nonce, genkey) + + self.__logger.critical("returning response") + + return Response(''.join(pstrIlist([ + enonce, + eKey + + ]))) + elif self.step == 2: + self.step = 3 + + package_nonce_C_userpub, package_C_AES_C_serverpriv = unpackpstrlist(challenge) + package_nonce = digsbyrsa.DIGSBY_RSA_private_decrypt(package_nonce_C_userpub, self.key, M2Crypto.RSA.pkcs1_oaep_padding) + package_C_serverpriv = util.cryptography.decrypt(package_nonce, package_C_AES_C_serverpriv, padchar=None, mode = util.cryptography.Mode.CBC) + + nonce = digsbyrsa.DIGSBY_RSA_public_decrypt(package_C_serverpriv, self.srv_rsa_key, M2Crypto.RSA.pkcs1_padding) + + return Response(pstrAES(nonce, self.password)) + else: + return Failure("extra-challenge") + + def finish(self,data): + _unused = data + return Success(self.username,None,self.authzid) diff --git a/digsby/src/digsby/loadbalance.py b/digsby/src/digsby/loadbalance.py new file mode 100644 index 0000000..b77b852 --- /dev/null +++ b/digsby/src/digsby/loadbalance.py @@ -0,0 +1,173 @@ +import simplejson +import sys +import util.net as net +import urllib2 +from util.primitives import funcs +from operator import itemgetter +import random +import util.callbacks as callbacks +import util.threads as threads +import common.asynchttp + +from logging import getLogger +log = getLogger('loadbalance') + +class DigsbyLoadBalanceAPI(object): + __version__ = (1, 0) + def __init__(self, profile, username, host="login1.digsby.org", port=80, mode='async', initial=None, **_k): + self.profile = profile + self.username = username + self.host = host + self.port = port + self.mode = mode + self.httpmaster = common.asynchttp.HttpMaster() + self.initial = initial + + def copy(self): + ret = type(self)(**self.__dict__) + return ret + + @property + def console(self): + new = self.copy() + new.mode = "console" + return new + + @property + def stringversion(self): + return '.'.join(['%d']*len(self.__version__)) % self.__version__ + + @callbacks.callsback + def get(self, callback=None, **k): + version = self.stringversion + + from gui.native.helpers import GetUserIdleTime + from AccountManager import SECONDS_FOR_IDLE + idle = GetUserIdleTime() > (1000 * SECONDS_FOR_IDLE) + + local_load_exc = getattr(self.profile, 'local_load_exc', None) + log.debug('loaded: %s initial: %s', local_load_exc, self.initial) + have_acct_data = not bool(local_load_exc) + button_clicked = bool(self.initial) + log.debug('have_data: %s button_clicked: %s', have_acct_data, button_clicked) + if button_clicked and not have_acct_data: + state = 'initial_nocache' + elif button_clicked: + state = 'initial' + elif idle: + state = 'reconnect_idle' + else: + state = 'reconnect' + + url = net.UrlQueryObject('http://%s:%s/load/all/json' % (self.host, self.port), + revision = getattr(sys, 'REVISION', 'unknown'), + tag = getattr(sys, 'TAG', 'unknown'), + username = self.username, + version = version, + state = state, + v = version, + **k) + log.debug('calling loadbalance URL: %s', url) + if self.mode == 'async': + return self.call_async(url, callback=callback) + elif self.mode == 'console': + return self.get_urllib(url) + elif self.mode == 'threaded': + return threads.threaded(self.get_urllib)(url, callback=callback) + else: + return callbacks.callback_adapter(self.get_urllib)(url, callback=callback) + + def get_urllib(self, url): + res = urllib2.urlopen(url) + return self.clean(res.read()) + + @callbacks.callsback + def call_async(self, url, callback=None): + return self.httpmaster.open(url, + success=(lambda *a, **k: + callbacks.callback_adapter(self.parse_response, do_return=False)(callback=callback, *a, **k)), + error = callback.error, + timeout = callback.timeout) + + def parse_response(self, _req, resp): + if hasattr(resp, 'read'): + response = resp.read() + #if not asynchttp: + if hasattr(resp, 'close'): + resp.close() + else: + raise TypeError('failed to parse: %r', resp) + return self.clean(response) + + def clean(self, val): + log.debug("Got loadbalance result data: %r", val) + info = simplejson.loads(val) + if not isinstance(info, dict): + return self.clean_0_0(val) + return getattr(self, 'clean_%s' % info['version'].replace('.', '_'))(info) + + def clean_0_0(self, val): + info = simplejson.loads(val) + return DigsbyLoadBalanceInfo(nodes = info) + + def clean_1_0(self, info): + info['version'] = map(int, info['version'].split('.')) + return DigsbyLoadBalanceInfo(**info) + + +class DigsbyLoadBalanceInfo(object): + def __init__(self, nodes = None, reconnect_strategy = None, version = (0, 0), **k): + self.nodes = nodes + self.state = k.get('state', None) + self.reconnect_strategy = reconnect_strategy + self.version = version + + @property + def addresses(self): + if not self.nodes: + return None + else: + grouped = dict(funcs.groupby(self.nodes, itemgetter('load'))) + sorts = sorted(grouped.items()) + addresses = [] + for _load, hosts in sorts: + addys = [] + for host in hosts: + addys.extend(host.get('addresses', [])) + random.shuffle(addys) + addresses.extend(addys) + addresses = [a.encode('idna') for a in addresses] + return addresses or None + + def __repr__(self): + return "<%(name)s version:%(version)s state:'%(state)s' reconnect_strategy:%(reconnect_strategy)r nodes:%(nodes)r>" % dict(name = type(self).__name__, **self.__dict__) + + +class DigsbyLoadBalanceManager(object): + def __init__(self, profile, username, servers, success, error, timeout=None, load_server=None, initial=None): + self.servers = servers + self.pos = 0 + self.success = success + self.error = error + self.timeout = timeout + self.username = username + self.load_server = load_server + self.profile = profile + self.initial = initial + + def process_one(self): + if self.pos >= len(self.servers): + return self.error(self) + h,p = self.servers[self.pos] + api = DigsbyLoadBalanceAPI(profile = self.profile, username=self.username, host=h, port=p, mode = 'async', initial = self.initial) + api.get(success = self.response, error = self.api_error) + + def response(self, val): + self.success(self, val) + + def api_error(self, *_a, **_k): + self.pos += 1 + self.process_one() + +if __name__ == '__main__': + print DigsbyLoadBalanceAPI('foo', host='192.168.99.71', mode='console').get() diff --git a/digsby/src/digsby/loginutil.py b/digsby/src/digsby/loginutil.py new file mode 100644 index 0000000..6d7c1cc --- /dev/null +++ b/digsby/src/digsby/loginutil.py @@ -0,0 +1,217 @@ +from socket import SocketType as socket +from hashlib import sha1, md5 +from struct import pack, unpack +from common.timeoutsocket import TimeoutSocketOne +from functools import partial +from common.timeoutsocket import TimeoutSocketMulti +from util import Timer +from util import lock +from traceback import print_exc + +from logging import getLogger +log = getLogger('loginutil') + +''' +Works with java server +maybe. +''' +POLL_SLEEP_TIME = 3.5 # Server polling sleep interval is 3 seconds + +class DigsbyLoginError(Exception): + def __init__(self, reason): + self.reason = reason + Exception.__init__(self, + { + 'auth' : 'Incorrect username or password.', + 'server' : 'Server is unable to authenticate you at this time.', + 'client' : 'Could not contact remote server. Check your internet connection.', + 'connlost' : 'The connection to the server was unexpectedly terminated.', + }.get(reason, 'Unknown error: %r' % reason) + ) + +def send_pstring(sck, str): + sck.sendall(pack('!I', len(str))) + sck.sendall(str) + +def recv_pstring(sck): + i = unpack('!I',sck.recv(4))[0] + v = sck.recv(i) + return v + +def login(srv, cid, un, password): + s = socket() + s.connect(srv) + send_pstring(s, cid) + send_pstring(s, un) + send_pstring(s, password) + try: + code = recv_pstring(s) + if code == 'success': + cookie = recv_pstring(s) + servers = recv_pstring(s) + servers = servers.split(' ') + return cookie, servers + elif code == 'error': + reason = recv_pstring(s) + raise DigsbyLoginError(reason) + else: + raise DigsbyLoginError('client') + except DigsbyLoginError, e: + raise e + except Exception, e: + print_exc() + raise DigsbyLoginError('client') + +def custom_crypt(c): + return str(int(c)+1) + +def hash_cookie(cookie, password): + return sha1(custom_crypt(cookie)+password).hexdigest().lower() + +def connect(host, jid, password): + raise NotImplementedError + +def digsby_login(srv, cid, un, password): + password = md5(password).hexdigest() + cookie, host = login(srv, cid, un, password) + password = hash_cookie(cookie, password) + connect(host, un, password) + +def make_pstring(s): + assert isinstance(s, str) + l = len(s) + format_str = '!I%ds' % l + return pack(format_str, l, s) + +class DigsbyConnect(TimeoutSocketOne): + _SERVERTIMEOUT = 8 + + def stale_connection(self): + + if getattr(self, '_triumphant', False): + log.info('stale_connection was called but i already won! yayayay') + else: + log.info('%r had a stale connection. Calling do_fail (%r) with a connlost error', self, self.do_fail) + self.do_fail(DigsbyLoginError('connlost')) + + def succ(self): + generator = self.do_login() + + self._timeouttimer = Timer(self._SERVERTIMEOUT, self.stale_connection) + self._timeouttimer.start() + self.run_sequence( generator ) + + @lock + def handle_error(self, e=None): + if hasattr(self, '_timeouttimer'): + self._timeouttimer.stop() + TimeoutSocketOne.handle_error(self) + + @lock + def handle_expt(self): + if hasattr(self, '_timeouttimer'): + self._timeouttimer.stop() + TimeoutSocketOne.handle_expt(self) + + @lock + def handle_close(self): + if hasattr(self, '_timeouttimer'): + self._timeouttimer.stop() + TimeoutSocketOne.handle_close(self) + + def do_login(self): + login_str = make_pstring(self.cid) + make_pstring(self.un) + make_pstring(self.password) + codelen = yield (4, login_str) + codelen = unpack('!I', codelen)[0] + if codelen <= 0: + raise DigsbyLoginError('client') + code = yield (codelen, '') + + try: + if code == 'success': + cookielen = unpack('!I', (yield (4, '')))[0] + cookie = yield (cookielen, '') + log.debug('Got cookie: %r', cookie) + serverslen = unpack('!I', (yield (4, '')))[0] + servers = yield (serverslen, '') + log.debug('Got servers: %r', servers) + servers = servers.split(' ') + self.cookie = cookie + self.servers = servers + self._triumphant = True + return + elif code == 'error': + log.debug('Got error!') + reasonlen = unpack('!I', (yield (4, '')))[0] + reason = yield (reasonlen, '') + log.debug('Got error reason: %r', reason) + raise DigsbyLoginError(reason) + else: + log.debug('Unknown error occurred! blaming the client!') + raise DigsbyLoginError('client') + except DigsbyLoginError, e: + if e.reason == 'server': + log.debug('Got "upgrading digsby" error code. Sleeping.') + import time; time.sleep(POLL_SLEEP_TIME) + raise e + except Exception, e: + print_exc() + raise DigsbyLoginError('client') + finally: + self._timeouttimer.stop() + + def run_sequence(self, generator): + try: + to_read, out_bytes = generator.send(self.data) + except StopIteration: + #win + self.close() + self._timeouttimer.stop() #just for good measure. + return TimeoutSocketOne.succ(self) + except DigsbyLoginError, e: + self._timeouttimer.stop() + self.do_fail(e) + return + except Exception, e: + self._timeouttimer.stop() + self.do_fail(e) + return + + bytes = str(out_bytes) + if out_bytes: + log.info('Sending %r', bytes) + self.push(bytes) + self.data = '' + self.found_terminator = partial(self.run_sequence, generator) + if isinstance(to_read, int): + self.set_terminator(to_read) + else: + self.set_terminator(to_read._size()) + +def connected(sock): + pass + +def youfail(): + pass + + +class DigsbyLoginMulti(TimeoutSocketMulti): + def lose(self, e=None): + if e and getattr(e,'reason',None) == 'auth' or self.attempts >= len(self._ips): + self.on_fail(e) + else: + self.try_connect() + +def new_digsby_login(addrtuples, cid, un, password, win, lose): + def provide(self): + self.cid = cid + self.un = un + self.password = password + t = DigsbyLoginMulti() + t.tryconnect(addrtuples, + win, lose, timeout=20, cls=DigsbyConnect, provide_init=provide) + +if __name__ == "__main__": + password = md5('password2').hexdigest() + new_digsby_login([('129.21.160.40', 5555), ('129.21.160.41', 5555)], "foo", 'chris', password, + connected, youfail) diff --git a/digsby/src/digsby/objects/__init__.py b/digsby/src/digsby/objects/__init__.py new file mode 100644 index 0000000..f314e96 --- /dev/null +++ b/digsby/src/digsby/objects/__init__.py @@ -0,0 +1,2 @@ +import ip +import pagetime diff --git a/digsby/src/digsby/objects/ip.py b/digsby/src/digsby/objects/ip.py new file mode 100644 index 0000000..584c9f8 --- /dev/null +++ b/digsby/src/digsby/objects/ip.py @@ -0,0 +1,34 @@ +import libxml2 +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri +from pyxmpp.utils import to_utf8, from_utf8 + +IP_NS = 'digsby:ip' + +class Ip(StanzaPayloadObject): + xml_element_name = 'ip' + xml_element_namespace = IP_NS + + def __init__(self, xmlnode_or_ip): + if isinstance(xmlnode_or_ip,libxml2.xmlNode): + self.from_xml(xmlnode_or_ip) + else: + self.ip = xmlnode_or_ip + + def from_xml(self,node): + if node.type!="element": + raise ValueError,"XML node is not a ip (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s descriptor" % self.xml_element_name + self.ip = from_utf8(node.getContent()) + + def complete_xml_element(self, xmlnode, _unused): + xmlnode.addContent(to_utf8(self.ip)) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r \ No newline at end of file diff --git a/digsby/src/digsby/objects/pagetime.py b/digsby/src/digsby/objects/pagetime.py new file mode 100644 index 0000000..fe2ea45 --- /dev/null +++ b/digsby/src/digsby/objects/pagetime.py @@ -0,0 +1,35 @@ +import libxml2 +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri +from pyxmpp.utils import to_utf8, from_utf8 +from datetime import datetime + +PAGETIME_NS = 'digsby:pagetime' + +class PageTime(StanzaPayloadObject): + xml_element_name = 'pagetime' + xml_element_namespace = PAGETIME_NS + + def __init__(self, xmlnode_or_pagetime): + if isinstance(xmlnode_or_pagetime,libxml2.xmlNode): + self.from_xml(xmlnode_or_pagetime) + else: + self.pagetime = xmlnode_or_pagetime + + def from_xml(self,node): + if node.type!="element": + raise ValueError,"XML node is not a pagetime (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s descriptor" % self.xml_element_name + self.pagetime = int(from_utf8(node.getContent())) + + def complete_xml_element(self, xmlnode, _unused): + xmlnode.addContent(to_utf8(self.pagetime)) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r \ No newline at end of file diff --git a/digsby/src/digsby/videochat.py b/digsby/src/digsby/videochat.py new file mode 100644 index 0000000..f8e493c --- /dev/null +++ b/digsby/src/digsby/videochat.py @@ -0,0 +1,332 @@ +''' + +Video Chat + +''' +from __future__ import with_statement + +import string +import re +from operator import attrgetter + +from digsby.web import DigsbyHttp, DigsbyHttpError +import simplejson as json +from contacts.buddyinfo import BuddyInfo +from common import profile, netcall +from util import threaded, traceguard +from util.primitives.funcs import Delegate + +from logging import getLogger; log = getLogger('videochat') + +# Sent to buddy when requesting a video conference. +INVITE_URL = 'http://v.digsby.com/?id=%(video_token)s&t=%(widget_id)s&v=%(tb_version)s' + +TOKBOX_VERSION = '2' + +VALID_TOKEN_CHARACTERS = string.ascii_letters + string.digits + +VIDEO_CHAT_TITLE = _('Audio/Video Call with {name}') + +def gui_call_later(cb, *a, **k): + import wx + wx.CallAfter(cb, *a, **k) + +class VideoChat(object): + ''' + Represents a link between a video widget and a buddy. + + - Uses VideoChatHttp to register video widget tokens with the server. + - Creates and manages a video window + - Maintains a link to a buddy, working with DigsbyProtocol to route + messages to and from the correct IM window + ''' + + def __init__(self, buddy): + self.buddy_info = BuddyInfo(buddy) + self.http = VideoChatHttp(profile.username, profile.password) + + self._widget_jid = None + self._stopped = False + self.on_stop = Delegate() + + self.handle_buddy_state(buddy) + + # Video chat aliases + my_alias = buddy.protocol.self_buddy.name + friend_alias = buddy.name + + # fetch a video token on the threadpool + create = threaded(self.http.create_video) + create(my_alias, friend_alias, success = self.on_token, error = self.error_token) + + def handle_buddy_state(self, buddy): + # when the protocol you're chatting from goes offline, close the video window + proto = buddy.protocol + + def on_protocol_state(proto, attr, old, new): + if not proto.connected: + self.stop() + + proto.add_observer(on_protocol_state, 'state', obj = self) + self.on_stop += lambda: proto.remove_observer(on_protocol_state, 'state') + + + def __repr__(self): + return '' % self.buddy_info + + def on_token(self): + 'Called back when VideoChatHttp successfully obtains tokens.' + + token = self.http.video_token + log.info('received video token: %s', token) + profile.connection.add_video_chat(token, self) + gui_call_later(self.on_url, self.http.invite_url(), self.http.widget_id) + + def on_url(self, invite_url, widget_id): + 'Called back when VideoChatHttp successfully creates a new video widget token.' + + # Send an invite message. + buddy = self.buddy_info.buddy() + if buddy is not None: + message = _('Join me in an audio/video call: %s') % invite_url + + def send_and_echo_invite(): + convo = buddy.protocol.convo_for(buddy) + convo.send_plaintext_message(message) + convo.system_message(_('You have invited {name} to an audio/video chat.').format(name=buddy.name)) + + netcall(send_and_echo_invite) + + # Show the video chat window. + title = VIDEO_CHAT_TITLE.format(name=self.buddy_info.buddy_name) + + from gui.video.webvideo import VideoChatWindow + frame = self.video_frame = VideoChatWindow(title, widget_id, on_close = self.stop) + gui_call_later(frame.Show) + + def error_token(self): + 'Called when there is an error retreiving tokens from the server.' + + log.warning('error receiving token') + self.system_message(_('Audio/Video chat is currently unavailable.')) + + def send_im(self, message): + 'Sends an unechoed IM to the video widget.' + + convo = self.widget_convo + if convo is not None: + netcall(lambda: convo.send_plaintext_message(message)) + + @property + def widget_convo(self): + 'Returns a conversation object with the video widget, if one can be found, or None.' + + if self.widget_jid is None: + self.widget_jid = self.find_widget_jid() + + if self.widget_jid is None: + return log.warning('no widget jid, cannot forward message to widget') + + conn = profile.connection + if not conn: + return log.warning('no Digsby connection, cannot forward message to widget') + + return conn.convo_for(self.widget_jid) + + def set_widget_jid(self, jid): + if jid != self._widget_jid: + self._widget_jid = jid + + # if buddy signs off, stop + if profile.connection: + profile.connection.get_buddy(jid).add_observer(self.buddy_status_change, 'status') + + widget_jid = property(attrgetter('_widget_jid'), set_widget_jid) + + def find_widget_jid(self): + 'Checks for a video widget JID on the Digsby connection.' + + conn = profile.connection + if conn is None: + return log.warning('cannot find widget jid: no digsby connection') + + # Search for a buddy on the Digsby connection with a matching resource + # to the one the server told us about. + # + # TODO: for loops nested this deep are never a good idea. can have + # the server tell us about this resource more specifically? + resource = 'video.' + self.http.video_token + + for buddict in conn.buddy_dictionaries(): + for name, buddy in buddict.iteritems(): + if buddy.jid.domain == u'guest.digsby.org': + for jid, res in buddy.resources.iteritems(): + if jid.resource == resource: + return jid + + def buddy_status_change(self, buddy, attr, old, new): + 'Invoked when the widget buddy changes status.' + + # we're looking for the buddy to go offline... + if buddy.online: return + + log.info('buddy %r went offline...stopping', buddy) + + buddy.remove_observer(self.buddy_status_change, 'status') + + # ...if they do, show a message in the IM window + if not self._stopped: + self.system_message(_('Audio/Video call ended by other party.')) + + self.stop() + + def system_message(self, message, **opts): + 'Echoes a system message to the IM window.' + + with traceguard: + im_buddy = self.buddy_info.buddy() + if im_buddy is not None: + convo = im_buddy.protocol.convo_for(im_buddy) + convo.system_message(message, **opts) + + def stop(self): + 'End all communication with the video widget.' + + self.stop = lambda *a: None # don't stop more than once + self._stopped = True + log.info('stopping video chat %r', self) + + # destroy the video window + if self.video_frame: gui_call_later(self.video_frame.Destroy) + + # appear offline to the widget + convo = self.widget_convo + if convo is not None: + netcall(self.widget_convo.buddy.appear_offline_to) + + # remove IM window link + token = self.http.video_token + if token is not None: + conn = profile.connection + if conn is not None: + conn.remove_video_chat(token) + + # tell the server to kill the video info + threaded(self.http.close_video)() + + self.on_stop() + +class VideoChatException(Exception): + pass + +class VideoChatHttp(DigsbyHttp): + def __init__(self, username, password): + ''' + Creates a VideoChat object for setting up web based video chats. + + username your Digsby ID + password your unencrypted digsby password + ''' + DigsbyHttp.__init__(self, username, password) + + def create_video(self, nick, guest, video_token = None, widget_id = None): + ''' + Creates an AV page. + + nick unicode display nickname for your "from" account + guest unicode guest nickname for the "to" account + ''' + + vtokens = self.GET(obj = 'widget', + act = 'video', + tbv = TOKBOX_VERSION, + nick = nick.encode('utf-8'), + guest = guest.encode('utf-8')) + + # check to make sure the returned token is alphanumeric + # TODO: make self.GET return status, resp pair so we can check HTTP + # status codes + assert isinstance(vtokens, str) + + tokens = json.loads(vtokens) + + def invalid(): + raise DigsbyHttpError('invalid video token returned: %r' % tokens) + + def validate(t): + return isinstance(t, basestring) and set(t).issubset(VALID_TOKEN_CHARACTERS) + + if not isinstance(tokens, dict): + invalid() + + self.video_token = tokens.get('token') + self.widget_id = tokens.get('widget') + + if not validate(self.video_token) or not validate(self.widget_id): + invalid() + + def invite_url(self): + if self.video_token is None: + raise VideoChatException('no AV page has been created') + + return INVITE_URL % dict(video_token = self.video_token, + widget_id = self.widget_id, + tb_version = TOKBOX_VERSION) + + def close_video(self): + 'Closes a video chat.' + + log.info('%r.close_video()', self) + self.GET(obj = 'widget', act = 'killvideo') + +video_invite_re = re.compile('http://v\.digsby\.com/\?id=(\w+)&(?:amp;)?t=(\w+)&(?:amp;)?v=2') + +def _on_message(messageobj, type, **opts): + ''' + intercept all messages, catching tokbox invites from other Digsby users, + and turning them into the special video chat window + ''' + + if type != 'incoming': + return + + msg = getattr(messageobj, 'message', None) + if not isinstance(msg, basestring): + return + + match = video_invite_re.search(msg) + if match is None: + return + + id, wid = match.groups() + + # replace the URL + buddy_name = messageobj.buddy.name + new_url = 'Join Now' % (buddy_name, wid) + first_part = msg[:match.start()] + last_part = msg[match.end():] + + if messageobj.content_type == 'text/plain': + print 'encoding xml and changinge content type' + first_part = first_part.encode('xml') + last_part = last_part.encode('xml') + messageobj.content_type = 'text/html' + + messageobj.message = ''.join( + (first_part, new_url, last_part)) + +def show_video_chat_window(buddy_name, widget_id): + from gui.video.webvideo import VideoChatWindow + gui_call_later(lambda: VideoChatWindow(VIDEO_CHAT_TITLE.format(name=buddy_name), widget_id).Show()) + +def register_message_hook(): + import hooks + hooks.register('digsby.im.msg.pre', _on_message) + + # setup a handler for digsby://avcall links + from common import urlhandler + urlhandler.register('avcall/([\w@\.]+)/(\w+)', show_video_chat_window) + +def unregister_message_hook(): + raise NotImplementedError('Hook does not have unregister yet') + diff --git a/digsby/src/digsby/web.py b/digsby/src/digsby/web.py new file mode 100644 index 0000000..2c59687 --- /dev/null +++ b/digsby/src/digsby/web.py @@ -0,0 +1,80 @@ +''' + +Functions for accessing the Digsby server's web account management functionality. + +''' +from util import threaded, UrlQuery +from hashlib import sha256 + +import traceback + +import urllib2 + +from logging import getLogger; log = getLogger('digsby.web') + +ACCOUNT_URL = 'https://accounts.digsby.com/login.php' + +class DigsbyHttpError(Exception): + pass + +class DigsbyHttp(object): + ''' + for talking to login.php + ''' + + def __init__(self, username, password, url = ACCOUNT_URL): + self.username = username + self.password = sha256(password).hexdigest() + self.url = url + + def __repr__(self): + return '<%s to %s>' % (self.__class__.__name__, self.url) + + def _urlopen(self, **params): + resp = digsby_webget_no_thread(self.url, user = self.username, key = self.password, **params) + + # TODO: use real HTTP status codes... + if resp == 'ERR': + raise DigsbyHttpError('server indicated error: %r' % resp) + + return resp + + GET = _urlopen + + # TODO: post? + +def digsby_acct_http(username, password, **params): + 'Account management with the digsby server.' + + return digsby_webget_no_thread(ACCOUNT_URL, + user = username, + key = sha256(password).hexdigest(), + **params) + +@threaded +def digsby_webget(url, **params): + return digsby_webget_no_thread(url, **params) + +def digsby_webget_no_thread(url, **params): + log.info('GETting url %s', url) + url = UrlQuery(url, **params) + log.info_s('full query is %s', url) + + req = urllib2.Request(str(url)) + req.add_header("Cache-Control", 'no-cache') + req.add_header("User-Agent", 'Digsby') + + response = None + try: + response = urllib2.urlopen(req) + res = response.read() + except Exception, e: + log.error('Error opening %r: %r', url, e) + traceback.print_exc() + return None + finally: + if response is not None: + response.close() + + log.info('response: %r', res) + return res diff --git a/digsby/src/digsby/widgets/__init__.py b/digsby/src/digsby/widgets/__init__.py new file mode 100644 index 0000000..219e3c1 --- /dev/null +++ b/digsby/src/digsby/widgets/__init__.py @@ -0,0 +1,19 @@ +from pyxmpp.iq import Iq +DIGSBY_WIDGETS_NS = 'digsby:widgets' +import widget +from widget import Widget +from widgets import Widgets + +def create(): + 'Opens the "create widget" page in a web browser.' + from digsby.web.weblogin import autologin + from common import profile + + autologin(profile.username, profile.password, + 'http://widget.digsby.com') + +def make_get(digsby_protocol): + iq = Iq(stanza_type="get") + iq.set_to(digsby_protocol.jid.domain) + q = iq.new_query(DIGSBY_WIDGETS_NS) + return iq \ No newline at end of file diff --git a/digsby/src/digsby/widgets/widget.py b/digsby/src/digsby/widgets/widget.py new file mode 100644 index 0000000..243a3c4 --- /dev/null +++ b/digsby/src/digsby/widgets/widget.py @@ -0,0 +1,169 @@ +from peak.util.imports import lazyModule +from pyxmpp.utils import from_utf8 +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import get_node_ns_uri +from digsby.widgets import DIGSBY_WIDGETS_NS +from hashlib import sha256 +from util import callsback +from digsby.web import digsby_webget +from logging import getLogger; log = getLogger('digsby.widget') +skin = lazyModule('gui.skin') + +from util.xml_tag import tag +from urllib2 import urlopen +from urllib import urlencode + + +from common import profile +from util.net import UrlQuery +import wx + + + +def iswidget(buddy): + 'Returns True if the given buddy is from a webpage widget.' + + return hasattr(buddy, 'jid') and buddy.jid.domain == 'guest.digsby.org' + +def get_local_swf(): + return skin.resourcedir() / 'widget' / 'digsby_widget.swf' + + +class Widget(StanzaPayloadObject): + + action_url = 'https://accounts.digsby.com/login.php?' + + modify_url = 'http://widget.digsby.com/?' + + + xml_element_name = 'widget' + xml_element_namespace = DIGSBY_WIDGETS_NS + + def __init__(self, xmlnode_or_acct_or_id): + self.__from_xml(xmlnode_or_acct_or_id) + + def __repr__(self): + return '' % self.__dict__ + + def __from_xml(self,node): + if node.type != "element": + raise ValueError,"XML node is not an %s element (not en element)" % self.xml_element_name + + ns = get_node_ns_uri(node) + + if ns and ns != DIGSBY_WIDGETS_NS or node.name != self.xml_element_name: + raise ValueError,"XML node is not an %s element" % self.xml_element_name + + for prop in ('id', 'title', 'on', 'width', 'height', 'type', 'typeuid'): + val = node.prop(prop) + if val is not None: + setattr(self, prop, from_utf8(val)) + else: + setattr(self, prop, val) + + for prop in ['width', 'height']: + if getattr(self, prop): + setattr(self, prop, int(getattr(self, prop))) + + self.on = bool(int(self.on)) + + def set_enabled(self, enabled): + if not enabled: + # when disabling a widget, all of that widget's buddies should go + # offline + conn = profile.connection + if conn is not None: + conn.remove_widget_buddies(self) + + self._action('toggleon' if enabled else 'toggleoff') + + def edit(self): + def success(res): + print + print res + print + + file, key = res.split(':') + wx.LaunchDefaultBrowser(UrlQuery(self.modify_url, id=file, tkn=key)) + + self._action('modify', success = success) + #autologin(profile.username, profile.password, 'http://widget.digsby.com/?id=' + self.id) + + def delete(self): + self._action('del') + + @callsback + def _action(self, action, callback = None): + url = self.action_url + params = dict(obj='widget', + user = profile.username, + key = sha256(profile.password).hexdigest(), + act = action, + doto = self.id) + + def error(result = ''): + log.warning('server indicated an error %r', result) + callback.error() + + def success(result): + if result.lower() == 'err': error(result) + else: callback.success(result) + + digsby_webget(url, success = success, error = error, **params) + + + @property + def embed_tag(self): + ''' + Returns the text of the embed tag which will embed this widget in a + webpage. + ''' + + if self.type == 'fb': + return '' + + return ('' + % (self.id, self.width, self.height)) + + + @property + def flash_url(self): + return 'http://w.digsby.com/dw.swf?c=%s&STATE=creator' % self.id + + def embed_creator(self, w, h): + ' for showing a disabled widget preview' + + widget = ('' + % (self.flash_url, self.width, self.height)) + + return ('' + '
%s
' % self.get_config(w,h)) + + + + def get_config(self,w,h): + 'returns an embed tag with the parsed values of a given widget config file' + + url = 'http://config.digsby.com/%s' % (self.id) + data = urlopen(url).read() + xml = tag(data) + + sc = xml.style.text.status['color'] + bc = xml.style.background['color'] + tc = xml.style.title['color'] + fc = xml.style.field['color'] + xc = xml.style.text['color'] + tt = xml.o['title'] + nt = xml.o['nick'] + + d = dict(title=tt, nick=nt, statustext=sc, bgcolor=bc, titletext=tc, field=fc, text=xc) + flashvars = urlencode(d) + + #local_widget = skin.resourcedir() / 'widget' / 'digsby_widget.swf' + widget_url = 'http://w.digsby.com/dw.swf' + + return ('' % (widget_url, '?STATE=creator&' + flashvars, w, h)) diff --git a/digsby/src/digsby/widgets/widgets.py b/digsby/src/digsby/widgets/widgets.py new file mode 100644 index 0000000..0064f7f --- /dev/null +++ b/digsby/src/digsby/widgets/widgets.py @@ -0,0 +1,21 @@ +from digsby.widgets.widget import Widget +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import get_node_ns_uri +from digsby.widgets import DIGSBY_WIDGETS_NS +from jabber.jabber_util import xpath_eval + +class Widgets(StanzaPayloadObject, list): + xml_element_name = 'query' + xml_element_namespace = DIGSBY_WIDGETS_NS + + def __init__(self, xmlelem): + self.__from_xml(xmlelem) + + def __from_xml(self, node): + if node.type!="element": + raise ValueError,"XML node is not an Widgets element (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=DIGSBY_WIDGETS_NS or node.name!="query": + raise ValueError,"XML node is not an Widgets element" + widgets = xpath_eval(node, 'w:widget',{'w':DIGSBY_WIDGETS_NS}) + self.extend(Widget(widget) for widget in widgets) diff --git a/digsby/src/digsby_chatlogs/__init__.py b/digsby/src/digsby_chatlogs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/digsby_chatlogs/interfaces.py b/digsby/src/digsby_chatlogs/interfaces.py new file mode 100644 index 0000000..98bd7d8 --- /dev/null +++ b/digsby/src/digsby_chatlogs/interfaces.py @@ -0,0 +1,18 @@ +from peak.util.addons import AddOn +import protocols + +class IAliasProvider(protocols.Interface): + def get_alias(name, service, protocol=None): + pass + + def set_alias(name, service, protocol, alias): + pass + +class StubAliasProvider(AddOn): + protocols.advise(instancesProvide=(IAliasProvider,), asAdapterForTypes=(object,)) + + def get_alias(self, name, service, protocol=None): + return None #"Foo" + + def set_alias(self, name, service, protocol, alias): + pass diff --git a/digsby/src/digsby_chatlogs/profilealiases.py b/digsby/src/digsby_chatlogs/profilealiases.py new file mode 100644 index 0000000..b713b7c --- /dev/null +++ b/digsby/src/digsby_chatlogs/profilealiases.py @@ -0,0 +1,213 @@ +from digsby_chatlogs.interfaces import IAliasProvider +from peak.util.addons import AddOn +import util.primitives.mapping as mapping +import util.cacheable as cacheable +from util.command_queue import cmdqueue, SerialCommandQueue +from util.primitives.error_handling import traceguard +from util.cacheable import DiskCache +import traceback +import sqlite3 +import warnings +import protocols + +import logging +log = logging.getLogger('profilealiases') + +WHERE_DOUBLE = 'WHERE name=? AND service=?' +WHERE_TRIPLE = WHERE_DOUBLE + ' AND protocol=?' + +class ProfileAliasProviderBase(AddOn): + def __init__(self, subject): + self.profile = subject + super(ProfileAliasProviderBase, self).__init__(subject) + + def get_alias(self, name, service, protocol=None): + mcs = self.profile.blist.metacontacts.forbuddy(mapping.Storage(name = name.lower(), service=service.lower())) + ret = None + if len(mcs) == 1: #how to choose if there is more than one metacontact? sorted()[0]? + ret = list(mcs)[0].alias + if not ret: + ret = self.profile.get_contact_info(name.lower() + '_' + service.lower(), 'alias') + ret = ret or self.find_alias(name, service, protocol) + return ret + +class MemoryProfileAliasProvider(ProfileAliasProviderBase): + protocols.advise(instancesProvide=(IAliasProvider,)) + + @classmethod + def key_transform(cls, name, service, protocol): + return (name.lower(), service.lower(), protocol) + + def __init__(self, subject, d=None): + self.store = dict() if d is None else d + super(MemoryProfileAliasProvider, self).__init__(subject) + + def set_alias(self, name, service, protocol, alias): + self.store[self.key_transform(name, service, protocol)] = alias + + def find_alias(self, name, service, protocol): + return self.store.get(self.key_transform(name, service, protocol)) + +def alias_cache_validator(obj): + ret = {} + if isinstance(obj, dict): + try: + for ((name, service, protocol), alias) in obj.iteritems(): + if name != alias: + ret[(name, service, protocol)] = alias + except Exception: + traceback.print_exc() + return ret + +class YamlProfileAliasProvider(MemoryProfileAliasProvider): + def __init__(self, subject): + self.cache = DiskCache('alias_cache_v1.yaml', format='yaml', compression = 'gzip', validator=alias_cache_validator) + self.store = self.cache.safe_load(dict) + super(YamlProfileAliasProvider, self).__init__(subject, self.store) + + def set_alias(self, name, service, protocol, alias): + if alias is None or alias == name: + return + if super(YamlProfileAliasProvider, self).find_alias(name, service, protocol) != alias: + super(YamlProfileAliasProvider, self).set_alias(name, service, protocol, alias) + self.cache.save(self.store) + +class DBProfileAliasProvider(MemoryProfileAliasProvider): + + def __init__(self, subject): + self.initing = False + self.cmdq = SerialCommandQueue([self.connect_db, self.integrity_check], [self.shutdown_db]) + super(DBProfileAliasProvider, self).__init__(subject) + self.corrupted = False + self.load_db() + + @cmdqueue() + def load_db(self): + if self.corrupted or not self.integrity_check(): + log.critical('database was corrupted, short-circuiting load') + return + try: + self.db.execute('CREATE TABLE IF NOT EXISTS profile_aliases (name text, service text, protocol text, alias text, PRIMARY KEY(name, service, protocol))').close() + except Exception: + traceback.print_exc() + else: + with traceguard: + self.db.execute('DELETE FROM profile_aliases where name == alias').close() + with traceguard: + self.db.execute('VACUUM').close() + with traceguard: + self.db.commit() + r = None + try: + r = self.db.execute('SELECT name, service, protocol, alias FROM profile_aliases') + for (name, service, protocol, alias) in r: + self.store[self.key_transform(name, service, protocol)] = alias + except Exception: + traceback.print_exc() + finally: + if r is not None: + r.close() + + def integrity_check(self, tries=0): + if tries > 5: + log.critical('database cannot be fixed, flagging as corrupted') + self.corrupted = True + return False + try: + r = self.db.execute('PRAGMA integrity_check;') + r = list(r) + if r != [(u'ok',)]: + self.on_corrupt_db() + return self.integrity_check(tries = tries+1) + except Exception: + self.on_corrupt_db() + return self.integrity_check(tries = tries+1) + else: + return True + + def db_file(self): + return cacheable.get_cache_root(user=True) / 'alias_cache_v1.db' + + def on_corrupt_db(self): + self.shutdown_db() + with traceguard: + self.db_file().remove() + try: + self.connect_db() + except Exception: + log.critical('database could not be connected, flagging as corrupted') + self.corrupted = True + + def connect_db(self): + if self.corrupted: + return + self.db = sqlite3.connect(self.db_file()) + + def shutdown_db(self): + with traceguard: + self.db.commit() + with traceguard: + self.db.close() + try: + del self.db + except AttributeError: + pass + + def need_set(self, name, service, protocol, alias): + if self.corrupted: + return False + #validate other values? + if alias is not None and not isinstance(alias, unicode): + if protocol not in ('aim', 'icq', 'msim', 'fbchat', 'msn'): #this may not be desirable, but oscar usually has bytes here. + warnings.warn('%r, %r, %r has non-unicode alias: %r' % (name, service, protocol, alias)) + try: + alias = alias.decode('fuzzy utf-8') + except Exception: + traceback.print_exc() + return False + name, service, protocol = self.key_transform(name, service, protocol) + if super(DBProfileAliasProvider, self).find_alias(name, service, protocol) != alias: + return True + + def set_alias(self, name, service, protocol, alias): + if self.need_set(name, service, protocol, alias): + self._set_alias(name, service, protocol, alias) + + @cmdqueue() + def _set_alias(self, name, service, protocol, alias): + if self.need_set(name, service, protocol, alias): + if not alias or name == alias: + with traceguard: + self.db.execute('DELETE FROM profile_aliases ' + WHERE_TRIPLE, (name, service, protocol)).close() + else: + with traceguard: + self.db.execute('INSERT OR REPLACE INTO profile_aliases VALUES (?,?,?,?)', (name, service, protocol, alias)).close() + super(DBProfileAliasProvider, self).set_alias(name, service, protocol, alias) + + def find_alias(self, name, service, protocol): + name, service, protocol = self.key_transform(name, service, protocol) + try: + return self.store[(name, service, protocol)] + except KeyError: + db = None + with traceguard: + db = sqlite3.connect(self.db_file()) + for query, args in [ + ('SELECT alias FROM profile_aliases ' + WHERE_TRIPLE, (name, service, protocol)), + ('SELECT alias FROM profile_aliases ' + WHERE_DOUBLE, (name, service)) + ]: + r = db.execute(query, args) + try: + for row in r: + alias = row[0] + if alias: + super(DBProfileAliasProvider, self).set_alias(name, service, protocol, alias) + return alias + finally: + r.close() + if db is not None: + db.close() + alias = None #we didn't find one. cache that knowledge. + super(DBProfileAliasProvider, self).set_alias(name, service, protocol, alias) + +ProfileAliasProvider = DBProfileAliasProvider diff --git a/digsby/src/digsbycrust.py b/digsby/src/digsbycrust.py new file mode 100644 index 0000000..717d0f3 --- /dev/null +++ b/digsby/src/digsbycrust.py @@ -0,0 +1,22 @@ +from util.primitives import Storage +import wx +from gui.shell import PyCrustFrame + +class FakeApp(wx.PySimpleApp): + + def OnInit(self, *a, **k): + PyCrustFrame(standalone=True).Show() + return True + +def main(): + FakeApp().MainLoop() + +if __name__ == '__main__': + import gettext + gettext.install("digsby") + import main as main_mod + main_mod.setup_log_system() + main_mod.init_threadpool() + from common.commandline import * + import digsbyprofile; digsbyprofile.profile = Storage(username = 'TEST') + main() \ No newline at end of file diff --git a/digsby/src/digsbyprofile.py b/digsby/src/digsbyprofile.py new file mode 100644 index 0000000..65f651e --- /dev/null +++ b/digsby/src/digsbyprofile.py @@ -0,0 +1,1455 @@ +''' +digsbyprofile.py + +Global account information and preferences. +''' + +from __future__ import with_statement +import wx +import sys +import logging +import traceback +from digsby.DigsbyProtocol import DigsbyProtocol +from util.threads.timeout_thread import ResetTimer +from common import netcall +from common.Buddy import LogSizeDict +from util.primitives.funcs import Delegate, PausableDelegate, CallCounter +from common.statusmessage import StatusMessage +from prefs.prefsdata import localprefs +from digsby.loginutil import DigsbyLoginError +from hashlib import sha1 +from functools import partial +from util.observe import ObservableDict, ObservableList, Observable, ObservableProperty +from collections import defaultdict +from common import AccountBase, StateMixin +from util import dictadd, Storage, traceguard, try_this, dictdiff, threaded +from util.primitives.funcs import get +from util.callbacks import callsback, do_cb, wxcall +from cStringIO import StringIO +from path import path +from PIL import Image +from util.cacheable import save_cache_object, load_cache_object +from traceback import print_exc, print_stack +from config import platformName +from peak.util.plugins import Hook +from imaccount import Account, ChatProtocol +from digsby import digsbylocal +import util.cryptography +import hooks +import prefs + +STATUS_UPDATE_FREQ_SECS = 60 +PREF_UPDATE_FREQ_SECS = 60 +IDLE_UNIT = 60 # also in seconds + +HIBERNATE = "HIBERNATE" +UNHIBERNATE = "UNHIBERNATE" + +DISCONNECT_TIMEOUT = 6 +NOWPLAYING_STATUS_PREF = 'plugins.nowplaying.initial_status' + +from logging import getLogger +log = getLogger('digsbyprofile') +info = log.info +warning = log.warning + +import logextensions +console_handler_class = logextensions.ColorStreamHandler + +from common.protocolmeta import proto_init, protocols + +DIGSBY_SERVER = ("digsby.org", 5555) +PROMOTE_STRING_HTML = '

I use digsby!' +PROMOTE_STRING_RTF = '\\par\\par I use {\\hl {\\hlloc http://www.digsby.com/?utm_source=aim&utm_medium=aim&utm_campaign=aimprofilelink } {\\hlfr digsby} }' + +profile = None +login_tries = 0 + + +def get_login_tries(): + global login_tries + return login_tries + +initialized = Delegate() + +def signin(identity): + global profile + + if profile is not None: + try_this(lambda: profile.connection.observers.clear(), None) + profile = None + + if profile is None: + profile = DigsbyProfile(identity) + +def is_im_account(x): + 'Returns True if x is an IM account.' + meta = protocols.get(x.protocol, {}) + type = meta.get('type', None) + return type == 'im' or (type == 'service_component' and meta.get('component_type', None) == 'im') + +regular_accounts = is_im_account # compatibility alias + + +def email_accounts(x): + meta = protocols.get(x.protocol, {}) + type = meta.get('type', None) + return type == 'email' or (type == 'service_component' and meta.get('component_type', None) == 'email') + + +def social_accounts(x): + meta = protocols.get(x.protocol, {}) + type = meta.get('type', None) + return type == 'social' or (type == 'service_component' and meta.get('component_type', None) == 'social') + + +class DigsbyProfile(Observable, ChatProtocol): + 'A collection of accounts and preferences.' + + MAX_ICON_SIZE = 96 + MAX_ICON_BYTES = 64 * 1024 + + protocol = 'digsby' + + @property + def display_name(self): + from common import pref + return try_this(lambda: getattr(self, pref('profile.display_attr')), self.username) + + def __init__(self, identity): + Observable.__init__(self) + ChatProtocol.__init__(self) + + self.identity = identity + + from AccountManager import AccountManager + + self.PreDisconnectHooks = Delegate() + self.PostDisconnectHooks = Delegate() + + if not getattr(getattr(sys, 'opts', None), 'limit_log', True): + DelayedStreamLimiter = lambda s: s + else: + from fileutil import DelayedStreamLimiter + + self.consolehandlers = defaultdict(lambda: console_handler_class(DelayedStreamLimiter(sys.stdout))) + + self._status = None + + self.error_count = 0 + self.offline_reason = StateMixin.Reasons.NONE + self.account_manager = AccountManager(profile = self) + self.last_hiber_req = None + self.hibernated = False + self.linked_observers = False + self.xfers = ObservableList() + self.prefs = ObservableDict() + self.defaultprefs = ObservableDict(prefs.defaultprefs()) + self.quiet = False + self.prefs_loaded = False + + # set the common.pref lookup to point to our prefs + import common + common.set_active_prefs(self.prefs, self.defaultprefs) + + self.prefs.add_observer(self._prefs_changed) + + self.has_authorized = False + + self.statuses = ObservableList() + self.statuses.add_observer(self._statuses_changed) + + self._xfercount = 0 + self.xfers.add_observer(self._on_file_transfer) + + self.widgets = ObservableList() + + self._encrypter, self._decrypter = util.cryptography.cipher_functions(sha1(self.password.encode('utf8')).digest()[:16]) + + self.log_sizes = LogSizeDict() + + global profile + if profile not in (self, None): + warnmsg = 'Another DigsbyProfile has been created but the old one is still around!' + if __debug__: + raise ValueError(warnmsg) + else: + log.critical(warnmsg) + + profile = self # hack! BuddyListStore needs profile.username + + from contacts.buddyliststore import BuddyListStore + self.blist = BuddyListStore(self.account_manager.connected_accounts) + + self.set_contact_info = self.blist.set_contact_info + self.get_contact_info = self.blist.get_contact_info + + from BlobManager import BlobManager + self.blob_manager = BlobManager(self) + + self.account_manager.add_observer(self.check_loading, 'got_accounts') + self.account_manager.add_observer(self.on_accounts_loaded, 'accounts_loaded') + + self.blob_manager.add_observer(self.check_loading, 'loading') + self.loaded = False + + self.OnReturnFromIdle = Delegate() + self.on_message = PausableDelegate() + + self.OnStatusChange = Delegate() + + self.setup_hub() + + self.idle_timer = None + self.idle = False + + self.plugins_setup = False + self.connection = None + self.setup_plugins() + + self.do_local_load() + + @property + def password(self): + return self.identity.password + @property + def username(self): + return self.identity.name + + def setup_plugins(self, *a, **k): + assert not self.plugins_setup + if not self.plugins_setup: + + self.plugins_setup = True + wx.CallAfter(self._setup_plugins) + + def _setup_plugins(self): + for hook in Hook('digsby.profile.addons'): + try: + getattr(hook(self), 'setup', lambda *a, **k: None)() + except Exception: + traceback.print_exc() + + import plugin_manager.plugin_hub as plugin_hub + plugin_hub.act('digsby.plugin.load.async') + + def stop_timers(self): + if self.idle_timer: + self.idle_timer.stop() + + def _get_status(self): + if self._status is None: + self._status = self.load_saved_status() + + return self._status + + def _set_status(self, val): + + # digsby go_idle + + self._status = val + if val is not None: + if val.status != 'Idle': + self.save_status() + + status = property(_get_status, _set_status) + + def setup_hub(self): + import hub + h = hub.get_instance() + if h.filter_message not in self.on_message: + self.on_message += h.filter_message + + # register IM windows for incoming messages + from gui.imwin import on_message + self.on_message += lambda *a, **k: wx.CallAfter(on_message, *a, **k) + + self.on_message.pause() + + def set_profile_blob(self, new_profile): + self.profile = new_profile + + fstr = self.profile + + for acct in profile.account_manager.connected_accounts: + self.set_formatted_profile(acct.connection, fstr) + + self.save() + + def on_chat_invite(self, protocol, buddy, message, room_name, on_yes=None, on_no=None): + @wx.CallAfter + def after(): + import hub + hub.get_instance().on_invite(protocol=protocol, + buddy=buddy, + message=message, + room_name=room_name, + on_yes=on_yes, + on_no=on_no) + + def on_entered_chat(self, convo): + return self.on_message(convo=convo, raisenow=True) + + def set_formatted_profile(self, protocol, fstr=None): + if fstr is None: + fstr = self.profile + + # $$plugin setprofile + import plugin_manager.plugin_hub as plugin_hub + if not plugin_hub.act('digsby.im.setprofile.pre', protocol, fstr): + return + + plugin_hub.act('digsby.im.setprofile.async', protocol, fstr) + + add_promo_string = self.prefs.get('profile.promote', True) + if fstr.bestFormat == "rtf": + if add_promo_string: + fstr = fstr + PROMOTE_STRING_RTF + format = None + else: + #legacy profile support + if add_promo_string: + fstr = fstr.format_as("plaintext").encode('xml') + PROMOTE_STRING_HTML + from gui.uberwidgets.formattedinput import get_default_format + format = get_default_format('profile.formatting') + + netcall(lambda: protocol.set_profile(fstr, format)) + + def set_profile(self, *a, **k): + pass + + def _on_file_transfer(self, src, attr, old, new): + if all((not getattr(x, 'autoshow', True)) for x in new if x not in (old or []) and + (x.state not in (x.states.CompleteStates | x.states.FailStates))): + self._xfercount = len(new) + return + new, old = len(self.xfers), self._xfercount + + if self.prefs.get('filetransfer.window.show_on_starting', True) and new > old: + from gui.filetransfer import FileTransferDialog + wx.CallAfter(FileTransferDialog.Display) + + self._xfercount = new + + def __repr__(self): + return AccountBase._repr(self) + + def _reconnect(self, initial=False): + if getattr(self, 'connection', None) is not None: + self.connection.observers.clear() + self.connection.Disconnect() + del self.connection + + self.disconnecting = False + + extra = {} + resource = getattr(getattr(sys, 'opts', None), 'resource', None) + if resource is not None: + extra['resource'] = resource + elif getattr(sys, 'DEV', False): + extra['resource'] = 'dev' + import hub + conn = self.connection = DigsbyProtocol(self.username, self.password, + self, hub.get_instance(), + DIGSBY_SERVER, # srvs, + do_tls = False, + sasl_md5 = False, + digsby_login=True, + initial=initial, + **extra + ) + conn.account = self + + conn.add_observer(self.connection_state_changed, 'state') + conn.add_observer(self.offline_changed, 'offline_reason') + + conn.Connect(on_success = getattr(getattr(self, 'callback', None), 'success', None), + on_fail = self.connect_error) + + def do_local_load(self): + self.local_load_exc = None + self.blob_manager.load_from_identity(identity = self.identity) + self.account_manager.load_from_identity(identity = self.identity) + + def connect_error(self): + if self.has_authorized: + return self.callback.error() + else: + return self.local_login() + + def local_login(self): + ''' + After a failed network login, attempt to "log in" with self.username and + self.password to the local accounts store. + ''' + try: + exc, self.local_load_exc = self.local_load_exc, None + if exc is not None: + raise exc + except digsbylocal.InvalidPassword: + self.callback.error(DigsbyLoginError('auth')) + except Exception: + self.callback.error() + else: + self.blob_manager.local_load() + + # Setup connection and call load_cb + self.connection_state_changed(None, 'state', None, DigsbyProtocol.Statuses.AUTHORIZED) + + self.connection_state_changed(None, 'state', None, DigsbyProtocol.Statuses.ONLINE) + self.offline_reason = DigsbyProtocol.Reasons.CONN_LOST + self.offline_changed(None, 'offline_reason', None, + DigsbyProtocol.Reasons.CONN_LOST) + self.connection_state_changed(None, 'state', None, DigsbyProtocol.Statuses.OFFLINE) + self.account_manager.do_load_local_notification() + + def offline_changed(self, src, attr, old, new): + self.notify(attr, old, new) + + def connection_state_changed(self, src, attr, old, new): + assert False + log.info('connection_state_changed %r -> %r', old, new) + + assert type(src) in (DigsbyProtocol, type(None)) + + if attr == 'state' and new == getattr(DigsbyProtocol.Statuses, 'AUTHORIZED', Sentinel()): + self.error_count = 0 + self.watch_account(self) + self.has_authorized = True + log.info('Calling load with cb of %r', self.callback) + + self.load(self.callback) + conn = self.connection + if conn is not None: + conn._set_status_object(profile.status) + + elif attr == 'state' and new == DigsbyProtocol.Statuses.OFFLINE: + self.setnotifyif('offline_reason', getattr(src, 'offline_reason', None)) + if not self.has_authorized and getattr(src, 'offline_reason', None) == DigsbyProtocol.Reasons.BAD_PASSWORD: + self.unwatch_account(self) + if self in self.account_manager.connected_accounts: + self.account_manager.connected_accounts.remove(self) + self.account_manager.unwatch_account(self) + self.connection = None + + dccb = getattr(self, '_disconnect_cb', None) + if dccb is not None: + self._disconnect_cb = None + dccb.success() + + elif attr == 'state' and new == DigsbyProtocol.Statuses.ONLINE: + self.reconnected_callbacks(self.connection) + + self.notify('state', old, new) + + @property + def state(self): + return try_this(lambda: self.connection.state, StateMixin.Statuses.OFFLINE) + + def when_active(self, callback): + if not hasattr(callback, '__call__'): + raise TypeError('argument "callback" must be callable') + + if self.idle: + if callback not in self.OnReturnFromIdle: + self.OnReturnFromIdle += callback + log.info('added a callback to the idle queue: %r', callback) + else: + log.info('callback already in idle queue') + else: + log.info('not idle, calling now') + return callback() + + @wxcall + def signoff(self, kicked=False): + 'Return to the splash screen.' + + # $$plugin unload + import plugin_manager.plugin_hub as plugin_hub + plugin_hub.act('digsby.plugin.unload.async') + + if platformName == 'win': + return wx.GetApp().Restart() + + # todo: confirm if there are (active) file transfers + + # hide all top level windows + top = wx.GetTopLevelWindows + for win in top(): + win.Hide() + + del self.on_message[:] + del self.OnReturnFromIdle[:] + self.stop_timers() + + def dodisconnect(success = True): + + if not success: + log.info('there was an error saving all blobs.') + + # disconnect all accounts + with traceguard: + self.disconnect() + + # destroy all top level windows + f = getattr(wx.GetApp(), 'buddy_frame', None) + if f: + f.on_destroy() + + for win in top(): + with traceguard: + if not win.IsDestroyed(): + win.Destroy() + + # clear input shortcuts + from gui.input.inputmanager import input_manager + input_manager.reset() + + import gc + import observe + gc.collect() + + numCleared = observe.clear_all() + log.info('cleared %d observers dicts', numCleared) + + # show the splash, preventing autologin + wx.GetApp().ShowSplash(autologin_override = False, kicked=kicked) + + log.info('saving all blobs before signoff...') + self.save(success = dodisconnect, + error = lambda: dodisconnect(False)) + + from gui import toast + toast.cancel_all() + + def _statuses_changed(self, src, attr, old, new): + if not hasattr(self, 'status_timer'): + self.status_timer = t = ResetTimer(STATUS_UPDATE_FREQ_SECS, self._on_status_timer) + t.start() + else: + self.status_timer.reset() + + def _on_status_timer(self): + self.status_timer.stop() + netcall(lambda: self.save('statuses')) + + def _prefs_changed(self, src, attr, old, new): + if not hasattr(self, 'pref_timer'): + self.pref_timer = t = ResetTimer(PREF_UPDATE_FREQ_SECS, self._on_pref_timer) + t.start() + else: + self.pref_timer.reset() + + def _on_pref_timer(self): + self.pref_timer.stop() + netcall(lambda: self.save('prefs')) + + def SetStatusMessage(self, message, editable = True, edit_toggle = True, **k): + new_status = StatusMessage(title = None, + status = self.status.status, + message = message, + editable = editable, + edit_toggle = edit_toggle) + + import hooks + hooks.notify('digsby.statistics.ui.select_status') + self.set_status(new_status) + + def maybe_return_from_offline(self): + '''Called by IM accounts when they are connecting to clear an "Offline" status.''' + + if hasattr(self, 'were_connected'): + log.info("protocol has 'were_connected', deleting and setting Available") + del self.were_connected + + status = getattr(self, 'were_connected_status', StatusMessage.Available) + self.set_status(status) + + def set_status(self, status): + ''' + Takes a StatusMessage object and sets the status in all connected (and + which will connect in the future) accounts. + ''' + if status == self.status: + return log.warning('set_status got an identical status.') + + # $$plugin status change + from plugin_manager import plugin_hub + plugin_hub.act('digsby.im.mystatuschange.pre', status) + + if status == '': + return + + plugin_hub.act('digsby.im.mystatuschange.async', status) + + for hook in Hook('digsby.im.statusmessages.set.pre'): # can't use query or notify (want the chained effect) + status = hook(status) + + log.warning('set_status got %r', status) + + accts = [a for a in self.account_manager.connected_accounts if a is not self] + + def allaccts(func): + for a in accts: + with traceguard: + func(a) + + Offline = StatusMessage.Offline + + # disconnecting + if status == Offline: + log.info('disconnecting all connected accounts') + + # store a list of the accounts which were connected prior + # to disconnecting. + self.were_connected = accts[:] + self.were_connected_status = self.status + allaccts(lambda a: a.disconnect()) + #reconnecting + elif self.status == Offline and hasattr(self, 'were_connected'): + accts = self.were_connected + del self.were_connected + for acct in accts: + with traceguard: + if acct in self.account_manager.accounts: + acct.connect(invisible=(status.for_account(acct).invisible)) + else: + log.warning('not reconnecting %s', acct) + else: + for acct in self.account_manager.connected_accounts[:]: + with traceguard: + prev_invis = self.status.for_account(acct).invisible + this_invis = status.for_account(acct).invisible + #going to/returning from invisible + if (prev_invis or this_invis) and this_invis != prev_invis: + acct.connection.set_invisible(this_invis) + #just setting a status + if not this_invis: + acct.connection._set_status_object(status) + + self.setnotifyif('status', status.copy(editable=None, edit_toggle=None)) + self.save_status() + + hooks.notify('digsby.im.statusmessages.set.post', self.status) + + def add_account(self, **attrdict): + # $$plugin + self.account_manager.add(Account(**attrdict), 'im') + import plugin_manager.plugin_hub as plugin_hub + plugin_hub.act('digsby.im.addaccount.async', attrdict['protocol'], attrdict['name']) + + def add_email_account(self, **info): + protocol = info.get('protocol') + name = info.get('name') + + self.account_manager.add(proto_init(protocol)(**info), 'em') + + # $$plugin + import plugin_manager.plugin_hub as plugin_hub + plugin_hub.act('digsby.email.addaccount.async', protocol, name) + + def add_social_account(self, **info): + protocol = info.pop('protocol') + name = info.get('name') + + acct = proto_init(protocol)(**info) + self.account_manager.add(acct, 'so') + + # $$plugin + import plugin_manager.plugin_hub as plugin_hub + plugin_hub.act('digsby.social.addaccount.async', protocol, name) + return acct + + def register_account(self, on_success, on_fail, **attrdict): + newacct = Account(**attrdict) + newacct.connect(register = True, on_success=on_success, on_fail=on_fail) + + def update_account(self, account, force=False): + + self.account_manager.update_account(account, force=force) + + # $$plugin + import plugin_manager.plugin_hub as plugin_hub + plugin_hub.act('digsby.updateaccount.async', account) + + def add_status_message(self, status_obj = None, **info): + if status_obj is None: + assert info + self.statuses.append(StatusMessage(**info)) + else: + assert info == {} + self.statuses.append(status_obj) + + def remove_account(self, account): + self.account_manager.remove(account) + + # $$plugin + import plugin_manager.plugin_hub as plugin_hub + plugin_hub.act('digsby.removeaccount.async', account) + + remove_email_account = \ + remove_social_account = \ + remove_account + + def remove_status_message(self, status_message): + self.statuses.remove(status_message) + + def get_widgets(self): + self.connection.get_widgets() + + def incoming_widgets(self, widgets): + self.widgets[:] = widgets + hooks.notify('digsby.widgets.result', widgets) + + def blob_failed(self, name): + try: + self.connection.Disconnect() + self.offline_changed(None, 'offline_reason', None, + DigsbyProtocol.Reasons.CONN_FAIL) + except Exception: + pass + + def update_blob(self, name, useful_data): + if name == 'prefs': + + log.critical('prefs updated from the network') + + with self.prefs.flagged('network'): + if 'defaultprefs' not in self.blob_manager.waiting_blobs: + new_prefs = dictadd(self.defaultprefs, useful_data) + self.prefs.update(new_prefs) + else: + self.prefs.update(useful_data) + new_prefs = useful_data + if hasattr(self, 'defaultprefs'): + for key in set(self.prefs.keys()) - (set(new_prefs.keys()) | set(self.defaultprefs.keys())): + self.prefs.pop(key, None) + + self.prefs_loaded = True + hooks.notify('blobs.update.prefs', self.prefs) + + elif name == 'defaultprefs': + if 'prefs' not in self.blob_manager.waiting_blobs: + new_prefs = dictadd(useful_data, self.prefs) + self.prefs.update(new_prefs) + if hasattr(self, 'defaultprefs'): + for key in set(self.defaultprefs.keys()) - set(useful_data.keys()): + self.prefs.pop(key, None) + self.defaultprefs.update(useful_data) + + elif name == 'buddylist': + self.blist.update_data(useful_data) + + elif callable(getattr(self, '_incoming_blob_' + name, None)): + getattr(self, '_incoming_blob_' + name)(useful_data) + + else: + log.critical('replacing profile attribute %s', name) + if name == 'statuses': + assert False + setattr(self, name, observable_type(useful_data)) + + def _incoming_blob_profile(self, profile_str_or_fmtstr): + from util.primitives.fmtstr import fmtstr + + # self.profile used to be a string, but now it is a fmtstr, and goes out + # over the wire as a JSON dict. + # + # assume that if we cannot parse the incoming profile blob as JSON, then + # it must be an old-style string profile. + if isinstance(profile_str_or_fmtstr, dict): + fstr = fmtstr.fromDict(profile_str_or_fmtstr) + else: + from gui.uberwidgets.formattedinput import get_default_format + fstr = fmtstr.singleformat(profile_str_or_fmtstr, format=get_default_format('profile.formatting')) + + self.profile = fstr + + def _incoming_blob_statuses(self, newdata): + data = [(StatusMessage(**d) if isinstance(d, dict) + else d) for d in newdata] + self.statuses[:] = data + + def _incoming_blob_notifications(self, newdata): + def fix_underscore(d): + for key in d.keys()[:]: + if key and '_' in key: + d[key.replace('_', '.')] = d.pop(key) + + if not hasattr(self, 'notifications'): + self.notifications = ObservableDict() + else: + fix_underscore(self.notifications) + + fix_underscore(newdata) + + self.notifications.update(newdata) + import common.notifications + + # for any notification keys that exist in YAML, but not in the users + # blob, add them with the values in the YAML 'default' key + ni = common.notifications.get_notification_info() + base = self.notifications[None] + for k in ni: + if k in base: + continue + + try: + defaults = ni[k].get('default', {}) + base[k] = [dict(reaction=v) for v in defaults.get('reaction', ())] + except Exception: + traceback.print_exc() + continue + + import hooks + hooks.notify('digsby.notifications.changed') + + def load(self, cb): + 'Loads network data from the server.' + self.loaded = False + + def callback(_cb=cb): + self.loaded = True + + log.info('Calling callback that was given to load: %r', _cb) + _cb(lambda *a, **k: None) + + self.link_observers() + + with traceguard: + conn = self.connection + if conn is not None: + conn.change_state(self.connection.Statuses.SYNC_PREFS) + + def on_accounts_loaded(): + # show the myspace account wizard if all you have are the automagic accounts + def after(): + if len(self.accounts) == 0 and \ + len(self.socialaccounts) == 0 and \ + len(self.emailaccounts) == 0 and \ + len(self.widgets) == 0: + import gui.accountwizard + gui.accountwizard.show() + wx.CallLater(1000, after) + + on_accounts_loaded_cc = CallCounter(2, on_accounts_loaded) + + def call_cc(*a, **k): + on_accounts_loaded_cc() + import util.hook_util + if not util.hook_util.OneShotHook(self, 'digsby.accounts.released.async')(call_cc, if_not_fired=True): + call_cc() + if not util.hook_util.OneShotHook(self, 'digsby.widgets.result')(call_cc, if_not_fired=True): + call_cc() + + self.get_widgets() + + self.load_cb = callback + + log.info('Forcing check_loading call') + self.check_loading() + + def link_observers(self): + if self.linked_observers: + return + + link = self.prefs.link + for pref in ('become_idle', 'idle_after',): + link('messaging.%s' % pref, getattr(self, '%s_changed' % pref)) + + self.setup_logger() + self.prefs.add_observer(self.link_logging) + + for key in self.prefs: + self.link_logging(self.prefs, key) + + self.linked_observers = True + + def check_loading(self, src=None, attr=None, old=None, new=None): + if self.account_manager.got_accounts and not self.blob_manager.loading: +# if self.connection is not None: +# log.warning('connection is not None') +# self.connection.change_state(self.connection.Statuses.ONLINE) + initialized() + if not self.loaded: + self._have_connected = True +# cb, self.load_cb = self.load_cb, (lambda *a, **k: None) + self.loaded = True + self.link_observers() +# log.info('Calling load_cb: %r', cb) +# cb() + + def on_accounts_loaded(self, src, attr, old, new): + if new: + log.info('unpausing the message queue') + wx.CallAfter(self.on_message.unpause) + + def link_logging(self, src, key, *a, **k): + n = 'logging' + if not isinstance(key, basestring) or not key.startswith(n): + return + logname = key[len(n):] or None + if logname is not None: + logname = logname.strip('.') + newlevel = try_this(lambda: int(get(src, key)), 0) + logging.log(100, 'Setting %s to level %d', logname or 'root', newlevel) + + import main + if not hasattr(main, 'full_loggers'): + return # logging system not setup + + # don't bother modifying console handlers if we never setup any + if not getattr(main, 'logging_to_stdout', False): + return + + if not logname: + logger = logging.getLogger('') + s_handlers = [h for h in logger.handlers if (h.__class__ is console_handler_class) and h not in main.full_loggers] + s_handlers[0].setLevel(newlevel) + else: + rootlogger = logging.getLogger('') + root_handlers = [h for h in rootlogger.handlers if (h.__class__ is not console_handler_class) or h in main.full_loggers] + handler = self.consolehandlers[newlevel] + handler.setLevel(newlevel) + + from main import ConsoleFormatter + formatter = ConsoleFormatter() + + handler.setFormatter(formatter) + root_handlers.append(handler) + new_logger = logging.getLogger(logname) + new_logger.propagate = False + new_logger.handlers[:] = root_handlers + + def setup_logger(self): + 'Sets up an IM and event logging object.' + from common.logger import Logger + logger = self.logger = Logger() + + # logger receives all messages, incoming and outgoing. + def later(*a, **k): + wx.CallLater(1000, threaded(self.logger.on_message), *a, **k) + self.on_message += lambda *a, **k: wx.CallAfter(later, *a, **k) + + set = lambda attr: lambda val: setattr(self.logger, attr, val) + link = lambda attr, cb: self.prefs.link(attr, cb, obj = logger) + link('log.ims', set('LogIMs')) + link('log.ims', set('LogChats')) + + @callsback + def save(self, saveblobs = None, force = False, callback = None): + ''' + Save one, or more, or all, data blobs. + + if saveblobs is: + None: saves all of them + a string: it must be one of the blob names + a sequence: all blobs in the sequence will be saved + ''' + + if saveblobs is None: # None means all blobs + saveblobs = self.blob_manager.blob_names + elif isinstance(saveblobs, basestring): # put a string into a list + saveblobs = [saveblobs] + + # check for correct blobnames + diff = set(saveblobs) - set(self.blob_manager.blob_names) + if len(diff) > 0: + raise ValueError('illegal blob names: %s' % ', '.join(diff)) + + saveblobs = set(saveblobs) + waiting = set(self.blob_manager.waiting_blobs) + output = saveblobs - waiting + + if len(output) < len(saveblobs): + log.info("blobs failed to save, not yet loaded: %r", + waiting & saveblobs) + + if self.blob_manager.loading: + info('blobs still loading, disallowing save') + callback.success() + return + else: + saveblobs = list(output) + + info('saving blobs %s', ', '.join(saveblobs)) + + cbs = [] + for name in saveblobs: + if name == 'buddylist': + cbs.append(partial(self.blob_manager.set_blob, name, + data = self.blist.save_data(), force = force)) + elif name == 'prefs': + cbs.append(partial(self.save_out_prefs, force = force)) + elif name == 'defaultprefs': + pass + elif name == 'statuses': + data = [s.__getstate__(network=True) for s in self.statuses] + for s in data: + s['format'] = dict(s['format']) if s['format'] is not None else None + cbs.append(partial(self.blob_manager.set_blob, name, + data = data, + force = force)) + elif name == 'profile': + data = self.profile.asDict() + cbs.append(partial(self.blob_manager.set_blob, name, data=data, force=force)) + else: + cbs.append(partial(self.blob_manager.set_blob, name, + data = to_primitive(getattr(self, name)), + force = force)) + + do_cb(cbs, callback = callback) + + def backup_blobs(self, dir): + pth = path(dir) + from util.json import pydumps + from time import time + for name in ['profile', 'buddylist', 'notifications', 'prefs', 'statuses', 'icon']: + if name == 'buddylist': + data = self.blist.save_data() + elif name == 'prefs': + data = to_primitive(dictdiff(profile.defaultprefs, self.prefs)) + elif name == 'defaultprefs': + pass + elif name == 'statuses': + data = [s.__getstate__() for s in self.statuses] + for s in data: + s['format'] = dict(s['format']) + else: + data = to_primitive(getattr(self, name)) + f = pth / name + '_' + str(int(time())) + '.blob' + with f.open('wb') as out: + if name == 'icon': + out.write(data) + else: + out.write(pydumps(data).encode('z')) + + @property + def localprefs(self): + return localprefs() + + @callsback + def save_blob(self, name, data, callback = None): + assert name not in ('buddylist', 'prefs', 'defaultprefs', 'statuses') + + log.critical('replacing attribute %s in profile', name) + setattr(self, name, data) + self.blob_manager.set_blob(name, data = to_primitive(getattr(self, name)), + callback = callback) + + @callsback + def save_out_prefs(self, force = False, callback = None): + 'Pack the data and send it to the server.' + data = dictdiff(profile.defaultprefs, self.prefs) + self.blob_manager.set_blob('prefs', data = to_primitive(data), force = force, + callback = callback) + + @callsback + def disconnect(self, callback = None): + if getattr(self, 'disconnecting', False): + return + + self.disconnecting = True + self.PreDisconnectHooks() + + complete_disconnect = lambda: self._finish_disconnect(callback=callback) + self.account_manager.disconnect_all( + success = lambda : + self.disconnect_profile(success = complete_disconnect, + error = complete_disconnect)) + + self._force_dc_timer = util.Timer(DISCONNECT_TIMEOUT, complete_disconnect) + self._force_dc_timer.start() + + self.stop_timers() + + @callsback + def disconnect_profile(self, callback = None): + log.info('Disconnect digsbyprofile') + self._disconnect_cb = callback + if getattr(self, 'connection', None) is not None: + self.connection.Disconnect() + + def _finish_disconnect(self, callback): + try: + log.info('finishing profile disconnect') + if getattr(self, '_force_dc_timer', None) is not None: + self._force_dc_timer.stop() + self._force_dc_timer = None + + self.PostDisconnectHooks() + + finally: + callback.success() + + def hibernate(self): + #called from windows (should be on wx thread) + self.last_hiber_req = HIBERNATE + self.check_hibernate_state() + + def unhibernate(self, delay = 15): + #called from windows (should be on wx thread) + self.last_hiber_req = UNHIBERNATE + delay = max(int(delay), 0) + if delay: + wx.CallLater(delay * 1000, self.check_hibernate_state) + else: + self.check_hibernate_state() + + def check_hibernate_state(self): + if self.last_hiber_req == HIBERNATE: + if self.hibernated: + return + else: + self.hibernated = True + self._do_hibernate() + return + elif self.last_hiber_req == UNHIBERNATE: + if not self.hibernated: + return + else: + self.hibernated = False + self._do_unhibernate() + return + + def _do_hibernate(self): + log.warning("HIBERNATING") + self.hibernated_im = hibernated_im = [] + self.hibernated_email = hibernated_email = [] + self.hibernated_social = hibernated_social = [] + for a in self.account_manager.connected_accounts[:]: + if a is not self: + with traceguard: + a.disconnect() + hibernated_im.append(a) + + for a in self.account_manager.emailaccounts: + with traceguard: + if a.enabled: + a.set_enabled(False) + a.disconnect() + hibernated_email.append(a) + + for a in self.account_manager.socialaccounts: + with traceguard: + if a.enabled: + a.set_enabled(False) + a.Disconnect() + hibernated_social.append(a) + + if getattr(self, 'connection', None) is not None: + self.connection.Disconnect() + log.warning("HIBERNATED") + + def _do_unhibernate(self): + log.warning("UN-HIBERNATING") + hibernated_im = self.hibernated_im + hibernated_email = self.hibernated_email + hibernated_social = self.hibernated_social + for a in hibernated_im: + with traceguard: + a._reconnect() + + for a in hibernated_email: + with traceguard: + a.set_enabled(True) + + for a in hibernated_social: + with traceguard: + a.set_enabled(True) + + self._reconnect() + log.warning("UN-HIBERNATED") + + @property + def allow_status_changes(self): + 'Used by the main status combo do decide whether or not to show itself.' + + if hasattr(self, 'were_connected'): + # This means that "Disconnected" was selected in the Status dialog + # were_connected is a list of account objects to reconnect if the + # status is changed again. + return True + + connecting = [a for a in self.account_manager.accounts if getattr(a, 'connection', None) is not None and + a.connection.state != a.connection.Statuses.OFFLINE] + + if connecting: + return True + + return False + + def plain_pw(self, password): + "Returns pw decrypted with the profile's password as the key." + + return self._decrypter(password if password is not None else '').decode('utf-8') + + def crypt_pw(self, password): + "Returns pw encrypted with the profile's password as the key." + if password and not isinstance(password, unicode): + print_stack() + return self._encrypter((password if password is not None else '').encode('utf-8')) + + @property + def is_connected(self): + return bool(getattr(self, 'connection', None) and (self.connection.state == self.connection.states['Connected'] or + self.connection.is_connected)) + + # + # buddy icon + # + + def get_icon_bitmap(self): + 'Returns the current buddy icon.' + + if self.icon is None: + log.info('get_icon_bitmap: self.icon is None, returning None') + return None + elif self.icon == '\x01': + # a single 1 byte in the database means "use the default" + # and is set in newly created accounts. + img = wx.Image(path('res') / 'digsbybig.png') + if not img.Ok(): + log.warning('get_icon_bitmap: could not load digsbybig.png, returning None') + return None + return wx.BitmapFromImage(img).Resized(self.MAX_ICON_SIZE) + else: + try: + return Image.open(StringIO(self.icon)).WXB + except Exception: + log.warning('could not create wxImageFromStream with profile.icon data') + return None + + def get_icon_bytes(self): + if self.icon is None: + return None + elif self.icon == '\x01': + return (path('res') / 'digsbybig.png').bytes() + else: + return self.icon + + @property + def name(self): + return self.username + + def protocol_info(self): + return protocols['digsby'] + + @property + def metacontacts(self): + return self.blist.metacontacts + + @property + def buddylist(self): + 'Returns the buddylist GUI window.' + + return wx.FindWindowByName('Buddy List').Children[0].blist + + def __getattr__(self, attr): + try: + return Observable.__getattribute__(self, attr) + + except AttributeError, e: + try: + return getattr(self.account_manager, attr) + except AttributeError: + raise e + + def get_is_connected(self): + conn = getattr(self, 'connection', None) + return bool(conn and (conn.state in (conn.Statuses.ONLINE, conn.Statuses.AUTHORIZED) + or conn.is_connected)) + + @property + def serviceicon(self): + from gui import skin + return skin.get('serviceicons.%s' % self.protocol) + + is_connected = ObservableProperty(get_is_connected, observe='connection') + connected = property(lambda self: self.is_connected) + + def get_error_txt(self): + return getattr(self, '_error_txt', False) or getattr(getattr(self, 'connection', None), + 'error_txt', False) + + def set_error_txt(self, value): + self._error_txt = value + + def del_error_txt(self): + self._error_txt = False + try: + del self.connection.error_txt + except Exception: + pass + + error_txt = property(get_error_txt, set_error_txt, del_error_txt) + + def load_saved_status(self): + try: + saved_status = load_cache_object('laststatus', user=True) + except Exception: + saved_status = None + print_exc() + + return saved_status if saved_status is not None else StatusMessage.Available + + def save_status(self): + status = self._status + if not any((status.offline, status.idle)): + with traceguard: + save_cache_object('laststatus', status, user=True) + + def become_idle_changed(self, new): + from common import pref + minutes = pref('messaging.idle_after', default=10, type=float) + if self.idle_timer is None: + self.idle_timer = util.RepeatTimer(minutes * IDLE_UNIT, self.set_idle) + if new: + self.idle_timer.start() + else: + self.idle_timer.stop() # so that reset can work below. + else: + if new: + self.idle_timer.reset() + else: + self.idle_timer.stop() + + def idle_after_changed(self, minutes): + from common import pref + minutes = try_this(lambda: float(minutes), + try_this(lambda: float(pref('messaging.idle_after', 10)), 10)) + if pref('messaging.become_idle', True): + self.idle_timer.reset(minutes * IDLE_UNIT) + + def reset_timers(self): + if self.idle_timer: + self.idle_timer.reset() + + def set_idle(self, *a, **k): + self.idle_timer.stop() + # $$plugin load + import plugin_manager.plugin_hub as plugin_hub + if not plugin_hub.act('digsby.goidle.pre'): + return + plugin_hub.act('digsby.goidle.async') + + from common import pref + if not pref('messaging.become_idle'): + log.debug('idle is not enabled') + return + + if self.status.invisible or self.status.offline: + log.debug('currently invisible, not setting idle') + return + + idle_msg = self.status.copy(status=StatusMessage.Idle.status) + self.last_status = self.status + + self.setnotify('status', idle_msg) + + for acct in self.account_manager.connected_accounts: + log.info('Setting idle on %r to %r', acct, self.idle_timer._interval) + try: + acct.connection.set_idle(self.idle_timer._interval) + except Exception: + log.error('Failed to set idle on this account: %r', acct) + print_exc() + + log.info('setting idle = True') + self.idle = True + + def return_from_idle(self): + if self.idle: + + # $$plugin load + import plugin_manager.plugin_hub as plugin_hub + if not plugin_hub.act('digsby.unidle.pre'): + return + + plugin_hub.act('digsby.unidle.async') + + self.idle = False + for acct in profile.account_manager.connected_accounts: + try: + acct.connection.set_idle(0) + except Exception: + log.error('Failed to return from idle on this account: %r', acct) + print_exc() + + log.info('return from idle') + + from common import pref + util.call_later(pref('profile.idle_return_delay', default=0, type=int), self.OnReturnFromIdle.call_and_clear) + + if self.last_status is not None: + self.set_status(self.last_status) + self.last_status = None + + @property + def allow_contact_add(self): + return self.prefs.get('digsby.allow_add', False) + + def get_account_for_protocol(self, proto): + return self.account_manager.get_account_for_protocol(proto) + + def find_account(self, username = None, protocol = None): + return self.account_manager.find_account(username, protocol) + + +def observable_type(thing): + if type(thing) is list: + return ObservableList(thing) + elif type(thing) in (dict, Storage): + return ObservableDict(thing) + return thing + + +def to_primitive(thing): + if isinstance(thing, ObservableDict): + return dict(thing) + elif isinstance(thing, ObservableList): + return list(thing) + return thing + +import digsby.videochat +digsby.videochat.register_message_hook() + + +def on_prefs_loaded(prefs): + ''' + for upgrading prefs + ''' + + old_focus_pref = 'conversation_window.steal_focus' + new_focus_pref = 'conversation_window.new_action' + + steal_focus = prefs.pop(old_focus_pref, None) + if steal_focus is not None: + log.info('upgraded conversation_window.steal_focus pref') + prefs[new_focus_pref] = 'stealfocus' if steal_focus else 'minimize' + +hooks.register('blobs.update.prefs', on_prefs_loaded) + + +def load_local_accounts(username, password): + ''' + Loads local accounts for a username and password. + + Returns tuple of (digsby.accounts.Accounts, + server_hash, + server_order) + ''' + local_info = digsbylocal.load_local(username, password) + server_info = digsbylocal.load_server(username, password) + + from digsby.accounts import Accounts + return Accounts.from_local_store(local_info), server_info['accounts_hash'], server_info['server_order'] + + +def set_simple_status(status): + profile.set_status(profile.status.copy(status=status.title())) + +from digsby_chatlogs.interfaces import IAliasProvider +from digsby_chatlogs.profilealiases import ProfileAliasProvider +import protocols as ptcls +ptcls.declareAdapterForType(IAliasProvider, ProfileAliasProvider, DigsbyProfile) diff --git a/digsby/src/digsbysite.py b/digsby/src/digsbysite.py new file mode 100644 index 0000000..06ad3fe --- /dev/null +++ b/digsby/src/digsbysite.py @@ -0,0 +1,348 @@ +import sys, os +import bootstrap + +if os.name == 'nt': + # fix comtypes issues on windows 7 + # see http://sourceforge.net/tracker/index.php?func=detail&aid=2855280&group_id=115265&atid=692940 + import ctypes + ctypes.OleDLL._func_restype_ = ctypes.c_void_p + +USE_PEAK = False +if USE_PEAK: + import peak + peak.install() + +try: + import srcrev; srcrev.REVISION +except Exception: + sys.REVISION = 'dev' +else: + sys.REVISION = srcrev.REVISION + +sys.TAG = '' + +try: + import devmode +except Exception: + sys.DEV = False +else: + sys.DEV = devmode.awesome + +sys.BRAND = None + +# Adds "Digsby/lib" to the system PATH for this process. +# This allows DLL lookups to find modules there. +sepjoin = os.pathsep.join +pathjoin = os.path.join +lib_dir = pathjoin(os.path.dirname(sys.executable), 'lib') + +os.environ['PATH'] = sepjoin([os.environ['PATH'], lib_dir]) + +if USE_PEAK: + @peak.whenImported('events') + def __monkeypatch_wx__CallAfter(mod): + print 'lololmonkeypatch' + import wx + wx.CallAfter = mod.CallAfterCombining + +bootstrap.install_sentinel() + +sys.modules['psyco'] = None + +restricted_names = frozenset(['password', 'secret', 'pass', 'passwd']) + +OMITTED = '' +VALUE_LENGTH_LIMIT = 360 + +def formatexception(excinfo=None, lastframes=8): + """Pretty print exception, including local variable information. + See Python Cookbook, recipe 14.4. + @param excinfo: tuple of information returned from sys.exc_info when + the exception occurred. If you don't supply this then + information about the current exception being handled + is used + @param lastframes: local variables are shown for these number of + frames + @return: A pretty printed string + """ + import StringIO + import traceback + if excinfo is None: + excinfo=sys.exc_info() + + s=StringIO.StringIO() + tb=excinfo[2] + stack=[] + + if tb is not None: + while True: + if not tb.tb_next: + break + tb=tb.tb_next + f=tb.tb_frame + while f: + stack.append(f) + f=f.f_back + + stack.reverse() + if len(stack)>lastframes: + stack=stack[-lastframes:] + print >>s, "\nVariables by last %d frames, innermost last" % (lastframes,) + + restricted_values = [] + + for frame in stack: + print >>s, "" + print >>s, ' File "%s", line %d, in %s' % (frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name) + + for key,value in frame.f_locals.items(): + # filter out modules + if type(value)==type(sys): + continue + if key == '__builtins__': + continue + + for badthing in restricted_names: + if badthing in key and value: + restricted_values.append(value) + value = OMITTED + + print >>s,"%15s = " % (key,), + try: + if isinstance(value, type({})) and value: + valstring = [] + for _kk, _vv in sorted(value.items()): + if _kk == '__builtins__': continue + if any((x in _kk) for x in restricted_names) and _vv: + valstring.append('%s=%r' % (_kk, OMITTED)) + restricted_values.append(_vv) + else: + valstring.append('%s=%r' % (_kk, _vv)) + + valstring = ' '.join(valstring)[:VALUE_LENGTH_LIMIT] + print >>s, valstring, + else: + print >>s,repr(value)[:VALUE_LENGTH_LIMIT] + except: + print >>s,"(Exception occurred printing value)" + traceback.print_exception(*excinfo, **{'file': s}) + + retval = s.getvalue() + if isinstance(retval, unicode): + retval_str = retval.encode('utf-8', 'replace') + else: + retval_str = retval + for value in restricted_values: + if not value or value == OMITTED: + continue + + try: + value_type = type(value) + if issubclass(value_type, basestring): + if issubclass(value_type, unicode): + value_str = value.encode('utf-8', 'replace') + elif issubclass(value_type, str): + value_str = value + + retval_str = retval_str.replace(value_str, OMITTED) + + retval_str = retval_str.replace(repr(value)[:VALUE_LENGTH_LIMIT], OMITTED) + except UnicodeError: + continue + return retval + +import traceback + +traceback._old_print_exc = traceback.print_exc +traceback._old_format_exc = traceback.format_exc + +COLORIZE_EXCEPTIONS = False +SHOW_TRACEBACK_DIALOG = sys.DEV + +def get_exc_dialog(): + diag = getattr(sys, 'exc_dialog', None) + if diag is not None: + import wx + if wx.IsDestroyed(diag): + diag = None + if diag is None: + import gui.tracebackdialog + diag = sys.exc_dialog = gui.tracebackdialog.ErrorDialog() + + return diag + +def tb_pref_enabled(): + try: + import common + return common.pref('debug.traceback_dialog', default=True) + except: + return False + +def print_exc(limit=None, file=None): + traceback.format_exc = traceback._old_format_exc() + try: + try: + if file is None: + file = sys.stderr + + formattedexc = formatexception() + + if SHOW_TRACEBACK_DIALOG and tb_pref_enabled(): + def show_dialog(): + try: + diag = get_exc_dialog() + diag.AppendText(formattedexc + "\n\n") + if not diag.IsShown(): diag.CenterOnScreen() + diag.Show() + except Exception: + #break infinite loop, just in case + print sys.stderr, 'error showing exception dialog: %r' % e + + import wx + if wx.App.IsMainLoopRunning(): + wx.CallAfter(show_dialog) + + if COLORIZE_EXCEPTIONS and file is sys.stderr: + from gui.native.win import console + with console.color('bold red'): + file.write(formattedexc) + else: + file.write(formattedexc) + except: + # Old exception is lost. + traceback._old_print_exc() + finally: + traceback.format_exc = formatexception + +traceback.print_exc = print_exc + +def format_exc(): + traceback.format_exc = traceback._old_print_exc() + try: + try: + return formatexception() + except: + # Old exception is lost. + return traceback._old_format_exc() + finally: + traceback.format_exc = formatexception + +traceback.format_exc = formatexception + +print_exc_once_cache = set() +import inspect + +def print_exc_once(): + 'Like print_exc, but only displays an exception once.' + + import traceback + + try: + frame = inspect.currentframe() + filename = frame.f_back.f_code.co_filename + line_number = frame.f_lineno + key = (filename, line_number) + except Exception: + traceback.print_exc() + else: + if key not in print_exc_once_cache: + traceback.print_exc() + print_exc_once_cache.add(key) + +traceback.print_exc_once = print_exc_once + +# in DEV or with --track-windowids, keep track of where we're calling wx.NewId from +TRACK_WINDOW_ID_ALLOCATIONS = sys.DEV or \ + getattr(getattr(sys, 'opts', None), 'track_windowids', False) or \ + getattr(sys, 'TAG', None) == 'alpha' + +if TRACK_WINDOW_ID_ALLOCATIONS: + # replace wx.NewId + + import wx + + count_map = {} + _original_NewId = wx.NewId + def debug_NewId(): + global count_map + try: + loc = tuple(traceback.extract_stack()[-3:-1]) # 2 outer frames of wx.NewId caller + new_count = count_map.get(loc, 0) + 1 + count_map[loc] = new_count + + # don't let the map get too big + if len(count_map) > 100: + count_map = dict(sorted(count_map.iteritems(), key=lambda item: item[1], reverse=True)[:50]) + + except Exception: + print_exc() + return _original_NewId() + + wx.NewId = debug_NewId + + def get_window_id_allocs(): + return count_map + +def eh(*args): + try: + print >>sys.stderr,formatexception(args) + except: + print >>sys.stderr,args +if True: + sys.excepthook=eh + + + +class NullType(object): + ''' + >> # Null() is a no-op, like lambda *a, **k: None + >> bool(Null) == False + True + >>> Null.Foo.Bar.Meep is Null + True + ''' + # thanks Python cookbook + def __new__(cls, *args, **kwargs): + if '_inst' not in vars(cls): + cls._inst = object.__new__(cls, *args, **kwargs) + return cls._inst + + def __init__(self, *args, **kwargs): pass + def __call__(self, *args, **kwargs): return self + def __repr__(self): return '' + def __nonzero__(self): return False + def __getattr__(self, name): return self + def __setattr__(self, name, value): return self + def __delattr__(self, name): return self + +import __builtin__ +__builtin__.Null = NullType() +del NullType + +# +# patch ctypes.util.find_library to look in sys.path, as well as +# the usual os.environ['PATH'] +# +find_library = None +if os.name == "nt": + def find_library(name): + # See MSDN for the REAL search order. + for directory in (sys.path + os.environ['PATH'].split(os.pathsep)): + fname = os.path.join(directory, name) + if os.path.exists(fname): + return fname + if fname.lower().endswith(".dll"): + continue + fname = fname + ".dll" + if os.path.exists(fname): + return fname + return None + +if find_library is not None: + import ctypes.util + ctypes.util.find_library = find_library +del find_library + +import gettext +gettext.install('Digsby', './locale', unicode=True) + diff --git a/digsby/src/digsbysplash.py b/digsby/src/digsbysplash.py new file mode 100644 index 0000000..530a380 --- /dev/null +++ b/digsby/src/digsbysplash.py @@ -0,0 +1,690 @@ +''' +Digsby splash screen. + +Logs in to the Digsby server. +''' + +from __future__ import with_statement + +import wx +import cgui +from cgui import IsMainThread +#from cgui import LoginWindow +from LoginWindow import LoginWindow +from traceback import print_exc + +import hooks +DEFAULT_SPLASH_POS = (300, 300) + +from cPickle import dump, load +import os.path +import sys +import path +import syck +import re + +import datastore.v0 as datastore + +import logging +log = logging.getLogger('loginwindow') + +try: + _ +except: + _ = lambda s: s + +SIGN_IN = _("&Sign In") +RESDIR = 'res' +USE_NEW_STORAGE = True or sys.DEV + +import datetime + +if datetime.date.today().timetuple()[1:3] == (3, 17): + digsby_logo_filename = 'digsby_stpatricks.png' +else: + digsby_logo_filename = 'digsbybig.png' + +connection_errors = dict( + auth = _('Authentication Error'), + connlost = _('Connection Lost'), + server = _('We are upgrading Digsby. Please try connecting again in a few minutes.'), + client = _('Could not contact remote server. Check your network configuration.'), +) + + +class DataProblems(object): + BAD_USERNAME = _('Invalid Digsby Username') + + +def GetUserTempDir(): + import stdpaths + + if getattr(sys, 'is_portable', False): + base_temp = stdpaths.temp / 'digsby' + else: + base_temp = stdpaths.userlocaldata + + pth = path.path(base_temp) / 'temp' + if not pth.isdir(): + os.makedirs(pth) + return pth + + +def res(*a): + return os.path.join(RESDIR, *a) + + +def digsby_icon_filename(): + return res('digsby.ico') + +_icon_bundle = None + + +def icon_bundle(): + global _icon_bundle + if _icon_bundle is None: + _icon_bundle = wx.IconBundleFromFile(digsby_icon_filename(), wx.BITMAP_TYPE_ANY) + return _icon_bundle + +def identity(username): + return (i for i in hooks.first('digsby.identity.all') + if i.name == username).next() + +def identities(): + return [i for i in hooks.first('digsby.identity.all')] + +def last_identity(): + return hooks.first('digsby.identity.last') + +class LoginController(object): + ''' + Shows and interacts with the login window (see LoginWindow.cpp) + ''' + + have_shown_updated = False + + def __init__(self, on_success=lambda *a, **k: None, autologin_override = None): + # Signing off respawns Digsby with "--noautologin" so that we don't + # quickly log back in. + self.allow_autologin = sys.opts.autologin + self.cancelling = False + self.on_success = on_success + + self._datastore = datastore.LoginInfo() + self._profile = sentinel + + profiles, profile = identities(), None + if profiles: + profile = last_identity() or profiles[0] + username = profile.name + position = profile.get('pos', DEFAULT_SPLASH_POS) + else: + username = '' + position = DEFAULT_SPLASH_POS + + bitmaps = cgui.LoginWindowBitmaps() + bitmaps.logo = wx.Bitmap(res('digsbybig.png')) + bitmaps.help = wx.Bitmap(res('skins/default/help.png')) + bitmaps.settings = wx.Bitmap(res('AppDefaults', 'gear.png')) + bitmaps.language = wx.Bitmap(res('skins/default/serviceicons/widget_trans.png')) + + revision_string = ' '.join(str(x) for x in (getattr(sys, 'REVISION', ''), + getattr(sys, 'TAG', ''), + getattr(sys, 'BRAND', ''))) + + show_languages = False + self.window = LoginWindow(None, position, bitmaps, str(revision_string), show_languages, profiles) + self.bind_events() + + try: + self._set_frame_icon() + except Exception: + print_exc() + + cgui.FitInMonitors(self.window) + + status_message = '' + + if not status_message: + if not self.have_shown_updated and sys.opts.updated: + status_message = _('Update Successful') + LoginController.have_shown_updated = True + + self.set_status(status_message) + + if profile: + self.apply_info(profile) + + # don't allow autologin if the password is empty + if not profile or not profile.get('password', ''): + autologin_override = False + + self._init_state(autologin_override) + + @wx.CallAfter + def doselection(): + un_textbox = self.window.FindWindowById(LoginWindow.USERNAME) + un_textbox.SetFocus() + + self.setup_languages() + + def setup_languages(self): + choice = self.window.LanguageChoice + if choice is None: + return + + languages = [p.name for p in wx.GetApp().plugins if p.__class__.__name__ == 'LangPluginLoader'] + + with choice.Frozen(): + choice.Clear() + for lang in languages: + choice.Append(lang) + if languages: + choice.SetSelection(0) + + def _set_frame_icon(self): + wx.Log.SetActiveTarget(wx.LogStderr()) + wx.Log.EnableLogging(False) + self.window.SetIcons(icon_bundle()) + wx.Log.EnableLogging(True) + + def ShowWindow(self): + self.window.Show() + self.window.Raise() + + def DestroyWindow(self): + import digsbyprofile + try: + if digsbyprofile.profile and getattr(digsbyprofile.profile, 'connection', None) is not None: + digsbyprofile.profile.connection.remove_observer(self.OnStatusChange, 'state') + self.window.Hide() + finally: + self.window.Destroy() + + def bind_events(self): + bind = self.window.Bind + bind(wx.EVT_CHECKBOX, self.OnCheck) + #bind(wx.EVT_TEXT, self.OnText) + bind(wx.EVT_CHOICE, self.OnChoice) + bind(wx.EVT_BUTTON, self.OnButton, id=wx.ID_OK) + bind(wx.EVT_BUTTON, self.OnHelpButton, id=LoginWindow.HELPBUTTON) + bind(wx.EVT_BUTTON, self.OnCreateProfile, id=LoginWindow.CREATEPROFILE) + bind(wx.EVT_HYPERLINK, self.OnPWLink, id=LoginWindow.FORGOTPASSWORD) + bind(wx.EVT_HYPERLINK, show_conn_settings, id=LoginWindow.CONNSETTINGS) + bind(wx.EVT_CLOSE, self.OnClose) + bind(wx.EVT_CHOICE, self.OnLanguage, id=LoginWindow.LANGUAGE) + + def OnLanguage(self, e): + import main + main.set_language(['', 'en_LT'][e.Int]) + self.window.UpdateUIStrings() + + def _init_state(self, autologin_override): + # Call some events to get the initial state set properly + self.OnCheck(None) + self.OnText(None) + + # Click the login button if we're auto logging in + if self._should_autologin(autologin_override): + + # make sure the window has had a chance to paint itself before autologin + def paint(): + self.window.Refresh() + self.window.Update() + wx.CallAfter(paint) + + wx.CallAfter(self.signin) + + def _should_autologin(self, autologin_override): + # autologin_override is None (ignore), True or False + + if not self.allow_autologin: + return False + + if autologin_override is not None and not autologin_override: + return False + + if not self.window.GetAutoLogin() or (autologin_override is not None and not autologin_override): + return False + + return True + + def disconnect_prof(self): + import digsbyprofile as d + if d.profile: + from AsyncoreThread import call_later + call_later(d.profile.disconnect) + if d.profile is self._profile: + self.unwatch_profile(d.profile) + + def OnClose(self, evt): + sys.util_allowed = True + self.window.Hide() + self.cleanup() + wx.CallAfter(self.exit) + + def cleanup(self): + try: + self.disconnect_prof() + self.save_info() + except Exception: + print_exc() + + def exit(self): + try: + self.DestroyWindow() + except: + print_exc() + + wx.GetApp().DigsbyCleanupAndQuit() + + def watch_profile(self, p): + if self._profile is not sentinel: + self.unwatch_profile(self._profile) + self._profile = p + + def unwatch_profile(self, p=None): + if p is None: + p = self._profile + + if p is self._profile: + self._profile = sentinel + + def OnButton(self, evt): + self.signin() + + def signin(self): + # Set the label early if we're about to import the rest of Digsby, + # because it may take awhile with a cold cache. + my_id = identity(self._get_window_username()) + my_id.password = self.window.GetPassword() + hooks.first('digsby.identity.activate', my_id, raise_hook_exceptions = True) + + if not 'digsbyprofile' in sys.modules: + sys.util_allowed = True + self.set_status(_('Loading...')) + self.window.EnableControls(False, SIGN_IN, False) + self.window.Update() + from M2Crypto import m2 #yeah, we're actually Loading... + m2.rand_bytes(16) #just in case this chunk of dll isn't in memory, preload + import digsbyprofile + if (digsbyprofile.profile and digsbyprofile.profile.is_connected): + self.window.EnableControls(True, SIGN_IN, True) + self.disconnect_prof() + self.cancelling = True + else: + self.login() + + def login(self): + good_data, reason = validate_data(self.get_info().values()[0]) + if not good_data: + self.set_status(_(reason)) + self.window.EnableControls(True, SIGN_IN, True) + self.window.Update() + return + + self.set_status(_('Connecting...')) + self.window.EnableControls(False, SIGN_IN) + self.window.Update() + + #self.save_info() + + #info = self.allinfo[self._get_window_username()] + info = self.get_info()[self._get_window_username()] + + def myfunc(): + import digsby + + try: + self.cancelling = False + self.on_success(info) + import digsbyprofile + self.watch_profile(digsbyprofile.profile) + except digsby.DigsbyLoginError: + # this will NEVER happen. need to switch to using callbacks?! + self.set_status(_('Login error!')) + self.window.EnableControls(True, SIGN_IN, True) + return + else: + import digsbyprofile + if digsbyprofile.profile and getattr(digsbyprofile.profile, 'connection', None) is not None: + conn, cb = digsbyprofile.profile.connection, self.OnStatusChange + conn.add_observer(cb, 'state') + + wx.CallAfter(myfunc) + + def OnChoice(self, evt): + evt.Skip() + + last_choice = getattr(self, '_last_choice', 0) + + i = evt.Int + length = len(evt.EventObject.Items) + + print 'LAST WUT', evt.EventObject.GetCurrentSelection() + + if i == length - 3: + # the ----- line. do nothing + self.window.FindWindowById(LoginWindow.USERNAME).SetSelection(last_choice) + elif i == length - 2: + # Add Profile + evt.Skip(False) + if not self.OnCreateProfile(): + self.window.FindWindowById(LoginWindow.USERNAME).SetSelection(last_choice) + + elif i == length - 1: + username = self.window.FindWindowById(LoginWindow.USERNAME).GetItems()[last_choice] + identity_obj = identity(username) + if identity_obj is None: + return + + identity_obj.password = self.window.GetPassword() + + if not (self.window.GetPassword() or identity_obj.is_valid): + evt.EventObject.SetSelection(last_choice) + wx.MessageBox( + _('Please enter the profile\'s password before removing it.'), + _('Remove Profile')) + return + + if wx.OK == wx.MessageBox( + _('Are you sure you want to delete profile "%s"?' % username), + _('Remove Profile'), wx.OK | wx.CANCEL): + + import digsby.digsbylocal as digsbylocal + try: + hooks.first('digsby.identity.delete', username, self.window.GetPassword(), + raise_hook_exceptions=True) + except digsbylocal.InvalidPassword: + wx.MessageBox(_('Please enter the correct password to delete "%s".' % username), + _('Remove Profile')) + self.window.FindWindowById(LoginWindow.USERNAME).SetSelection(last_choice) + else: + self.window.set_profiles(identities()) + self._last_choice = 0 + self.window.FindWindowById(LoginWindow.PASSWORD).SetValue('') + self.window.panel.Layout() + self.window.Layout() + + else: + self.window.FindWindowById(LoginWindow.USERNAME).SetSelection(last_choice) + + else: + self._last_choice = i + print 'LAST CHOICE', i + self.window.FindWindowById(LoginWindow.PASSWORD).SetValue('') + + def OnText(self, evt): + self._ontextcount = getattr(self, '_ontextcount', 0) + 1 + if getattr(self, "_in_on_text", False): + return evt.Skip() + else: + self._in_on_text = True + + try: + if evt is not None: + evt.Skip() + + window = self.window + + if not hasattr(self, 'allinfo'): + return + + if evt and evt.Id == LoginWindow.USERNAME: + if self.allinfo.get(self._get_window_username(), None) is not None: + self.apply_info(self.allinfo[self._get_window_username()], set_username = False) + else: + window.SetPassword('') + + enabled = bool(self._get_window_username() and window.GetPassword()) + + window.FindWindowById(wx.ID_OK).Enable(enabled) + finally: + self._in_on_text = False + + def OnCheck(self, evt): + if not self.window.GetSaveInfo(): + self.window.SetAutoLogin(False) + + if evt and evt.GetId() == LoginWindow.SAVEPASSWORD and evt.GetInt() and getattr(sys, 'is_portable', False): + if not self.do_portable_security_warning(): + self.window.SetAutoLogin(False) + self.window.FindWindowById(LoginWindow.SAVEPASSWORD).Value = False + self.save_info() + return + + if evt and evt.GetId() in (LoginWindow.SAVEPASSWORD, LoginWindow.AUTOLOGIN): + self.save_info() + + def do_portable_security_warning(self): + security_warning_hdr = _("Your password will be saved on your portable device.") + security_warning_q = _("Anyone with access to this device will be able to log into your Digsby account. " + "Are you sure you want to save your password?") + + security_msg = u'%s\n\n%s' % (security_warning_hdr, security_warning_q) + + dlg = wx.MessageDialog(self.window, security_msg, _("Security Information"), wx.ICON_EXCLAMATION | wx.YES_NO) + + response = dlg.ShowModal() + + return response == wx.ID_YES + + def OnPWLink(self, evt): + dlg = wx.MessageDialog( + None, + _("For security, your password is not saved anywhere. " + "If you forget it, there is no way to decrypt the " + "account information for that profile. You'll need " + "to remove the profile, create a new one, and add " + "your accounts back."), + _('Forgotten password'), + wx.OK) + + dlg.ShowModal() + + def OnHelpButton(self, evt = None): + wx.LaunchDefaultBrowser('http://wiki.digsby.com') + + def OnCreateProfile(self, evt=None): + from gui.profiledialog import ProfileDialog + + # show a profile dialog + dialog = ProfileDialog(self.window) + self.profile_dialog = dialog + dialog.CenterOnParent() + + res = dialog.ShowModal() + + if res != wx.ID_OK: return + + # create the identity + username, password = dialog.GetUsername(), dialog.GetPassword() + assert username and password + + if dialog.is_new_profile: + hooks.first('digsby.identity.create', username, password) + else: + hooks.first('digsby.identity.get', username, password) + + self.window.set_profiles(identities(), username, password) + self._last_choice = self.window.FindWindowById(LoginWindow.USERNAME).GetSelection() + self.window.panel.Layout() + self.window.Layout() + + return True + + def OnStatusChange(self, src, attr, old, new): + assert attr == 'state' + wx.CallAfter(self.OnStatusChanged, src, attr, old, new) + + def OnStatusChanged(self, src, attr, old, new): + + if wx.IsDestroyed(self.window): + log.warning("Warning: splash screen is Destroyed but still getting notified.") + return + + if self.cancelling and new != src.Statuses.OFFLINE: + src.disconnect() + + if new in (src.Statuses.CONNECTING, src.Statuses.AUTHENTICATING, src.Statuses.SYNC_PREFS, src.Statuses.AUTHORIZED): + self.window.EnableControls(False, SIGN_IN, False) + + def f(): + if self.cancelling or new == src.Statuses.OFFLINE: + + if self.cancelling: + self.set_status('') + else: + self.set_status(_(src.offline_reason)) + self.window.EnableControls(True, SIGN_IN, True) + else: + self.window.EnableControls(False, SIGN_IN) + self.set_status(_(new)) + + wx.CallAfter(f) + + def set_status(self, label, window_title = None, do_conn_error=False): + ''' + Changes the main label and the window title. + + If not window title is given, it is set to be the same as the label. + ''' + assert IsMainThread() + + conn_fail_message = _('Failed to Connect') + + self.window.SetStatus(label, window_title or '') + + if label == _('Authentication Error') and not getattr(self, 'auth_error_fired', False): + self.auth_error_fired = True + line1 = _('Please make sure you have entered your Digsby username and password correctly.') + line2 = _('If you need an account or forgot your password, use the links on the login screen.') + wx.MessageBox(u'%s\n%s' % (line1, line2), _('Authentication Error')) + if do_conn_error: + #TODO: fix protocol states. + if label == conn_fail_message and not getattr(self, 'connect_error_fired', False): + self.connect_error_fired = True + wx.MessageBox(_('Please check your Internet connection and make sure a firewall isn\'t blocking Digsby.\n' + 'If you connect to the Internet through a proxy server,\n' + 'click the "Connection Settings" link to set up the proxy.\n' + 'If you are still unable to connect, email bugs@digsby.com for technical support.'), + _('Failed to Connect')) + + def _get_window_username(self): + return self.window.GetUsername().strip() + + def get_info(self): + assert IsMainThread() + find = self.window.FindWindowById + + window = self.window + username = self._get_window_username() + + info_dict = dict( + username=username, + password=window.GetPassword(), + save=window.GetSaveInfo(), + autologin=window.GetAutoLogin() + ) + + return {username: info_dict} + + def apply_info(self, info, set_username=True): + w = self.window + if set_username: + w.SetUsername(info.name) + w.SetPassword(info.password or '') + w.SetSaveInfo(info.get('save', False)) + w.SetAutoLogin(info.get('autologin', False)) + + def set_username(self, username): + self.window.SetUsername(username) + + def set_password(self, password): + self.window.SetPassword(password) + + def save_info(self): + username = self._get_window_username() + if not username or username == u'------------------------------': + return + + userinfo = self.get_info() + profile = identity(username) + + for k in ('save', 'autologin'): + profile.set(k, userinfo[username][k]) + + if userinfo[username].get('save'): + profile.set('saved_password', userinfo[username]['password']) + else: + profile.set('saved_password', '') + + def proto_error(self, exc): + import digsby + + msgbox = None + + if exc is None: + message = _('Failed to Connect') + else: + try: + raise exc + except digsby.DigsbyLoginError: + if exc.reason == 'auth': + message = _('Authentication Error') + elif exc.reason == 'connlost': + message = _('Connection Lost') + else: + message = _('Failed to Connect') + + if exc.reason == 'server': + msgbox = _("We are upgrading Digsby. Please try connecting again in a few minutes.") + elif exc.reason == 'client': + msgbox = _('Could not contact remote server. Check your network configuration.') + + except Exception: + print_exc() + message = _("Failed to Connect") + + self.set_status(message, do_conn_error=True) + self.window.EnableControls(True, SIGN_IN, True) + self.disconnect_prof() + + if msgbox is not None: + wx.MessageBox(msgbox, message) + +def show_conn_settings(evt): + sys.util_allowed = True + from gui.proxydialog import ProxyDialog + import util + + parent = None + for tlw in wx.GetTopLevelWindows(): + if isinstance(tlw, LoginWindow): + parent = tlw + + p = ProxyDialog(parent) + p.CenterOnParent() + + def later(): + try: + p.ShowModal() + except Exception: + print_exc() + else: + hooks.notify('proxy.info_changed', util.GetProxyInfo()) + finally: + p.Destroy() + + wx.CallAfter(later) + + +def validate_data(info): + # copypasta'd from util/net.py since we can't import util yet + email_regex_string = r'^(?:([a-zA-Z0-9_][a-zA-Z0-9_\-\.]*)(\+[a-zA-Z0-9_\-\.]+)?@((?:[a-zA-Z0-9\-_]+\.?)*[a-zA-Z]{1,4}))$' + + if not re.match(email_regex_string, info['username'] + '@digsby.org'): + log.error('Bad username: %r', info['username']) + return False, DataProblems.BAD_USERNAME + + return True, None diff --git a/digsby/src/events.py b/digsby/src/events.py new file mode 100644 index 0000000..fb5b66f --- /dev/null +++ b/digsby/src/events.py @@ -0,0 +1,76 @@ +''' +events.py + +Provides a way for method calls to happen through wxEvent notifications. +''' +from __future__ import with_statement +import wx, pprint, traceback, sys +from threading import currentThread +from weakref import ref + +MAINTHREAD_NAME = 'MainThread' + +class ThreadsafeGUIProxy(object): + ''' + Uses wxCallAfter to proxy all method calls to a GUI target if called from + any thread but the main one. + ''' + + def __init__(self, target): + self.target = target + + def __getattr__(self, attr): + ''' + Intercept all undefined method calls, and proxy them via a thread-safe + InvokeEvent to the GUI. + ''' + + method = getattr(self.target, attr) + if currentThread().getName() == MAINTHREAD_NAME: + return lambda *a, **k: method(*a, **k) + else: + return lambda *a, **k: wx.CallAfter(method, *a, **k) + +from traceback import print_exc + +def callevent(e): + e.ref = None + try: + for callAfterCallback, a, k in e._callables: + try: callAfterCallback(*a, **k) + except Exception, e: # no "with traceguard:" for performance + print_exc() + + except AttributeError: + e.callable(*e.args, **e.kw) + +from wx import GetApp, NewEventType, PyEvent + + +def CallAfterCombining(callable, *args, **kw): + 'a call after tuned to be a bit faster' + + assert(hasattr(callable, '__call__')) + + app = GetApp() + + try: + r = app._last + except AttributeError: + pass + else: + e = r() + if e is not None and e.ref is r: + return e._callables.append((callable, args, kw)) + + try: + evt = PyEvent(0, app._CallAfterId) + except AttributeError: + id = app._CallAfterId = NewEventType() + app.Connect(-1, -1, id, callevent) + evt = PyEvent(0, id) + + evt._callables = [(callable, args, kw)] + evt.ref = app._last = ref(evt) + + app.AddPendingEvent(evt) diff --git a/digsby/src/fileutil.py b/digsby/src/fileutil.py new file mode 100644 index 0000000..9448367 --- /dev/null +++ b/digsby/src/fileutil.py @@ -0,0 +1,433 @@ +from __future__ import with_statement + +import os, codecs +pathjoin = os.path.join +from threading import RLock +from time import time +from path import path +from logging import getLogger; log = getLogger('fileutil') + +import Queue as Q +import traceback + +if os.name == 'nt': + import ctypes + GetDiskFreeSpaceEx = ctypes.windll.kernel32.GetDiskFreeSpaceExW + bytes_free = ctypes.c_ulonglong() + def free_disk_space(): + if not GetDiskFreeSpaceEx(None, ctypes.byref(bytes_free), None, None): + try: + raise ctypes.WinError() + except Exception: + traceback.print_exc() + return 0 + + return bytes_free.value +else: + def free_disk_space(): + log.warning('free_disk_space not implemented for this platform') + return 0 + + +class cd(object): + ''' + chdirs to path, always restoring the cwd + + >>> with cd('mydir'): + >>> do_stuff() + ''' + def __init__(self, *path): + self.path = path + + def __enter__(self): + self.original_cwd = os.getcwd() + new_cwd = pathjoin(*self.path) + os.chdir(new_cwd) + + def __exit__(self, exc_type, exc_val, exc_tb): + os.chdir(self.original_cwd) + +def tail(filename, maxbytes, encoding = None): + 'Return the last "maxbytes" bytes of filename or fileobject.' + + if maxbytes <= 0: + raise ValueError('maxbytes must be more than 0') + + seekargs = (-maxbytes, os.SEEK_END) + + if hasattr(filename, 'read'): + f = filename + will_close = False + else: + filesize = os.stat(filename).st_size + if encoding is not None: + f = codecs.open(filename, 'rb', encoding) + + else: + f = open(filename, 'rb') + + # change seekargs because codecs.open doesn't support seek from the end + if maxbytes > filesize: + seek = 0 + else: + seek = filesize - maxbytes + + seekargs = (seek,) + + will_close = True + + f.seek(*seekargs) + + s = f.read() + + if will_close: + f.close() + + return s + +def streamcopy(fobjin, fobjouts, limit = None, chunk = 4096): + # TODO: Support multiple input sources? i.e. read from in[0] until it is exhausted, + # then read from in[1], etc. + ''' + Copy data from 'fobjin' (must have read method) to 'fobjouts' (which may + be an object with a write method or a list of such objets). Up to 'limit' + bytes are copied in chunks of size 'chunk'. + + Returns the number of bytes written. + + Does not open or close streams and does not handle exceptions. + These are the responsibility of the caller. + ''' + if hasattr(fobjouts, 'write'): + fobjouts = [fobjouts] + + def writer(data): + for fobjout in fobjouts: + fobjout.write(data) + + return functioncopy(fobjin.read, writer, limit, chunk) + +def functioncopy(reader, writer, limit=None, chunk=4096): + ''' + functioncopy(reader, writer, limit=None, chunk=4096) + callable: reader(sz) -> data + this function will be called with a number of bytes to be read. it should return that much data (as a string). + An empty return value signifies no more data. + callable: writer(data) -> None + this function will be called with the data. its return value is ignored. + int: limit (optional) + this is the maximum amount to be transferred. if left as the default (None), copy will continue until + reader returns no more data. + int: chunk (optional) + this is the size to be read for each iteration through the loop. Default is 4096 + ''' + if not callable(reader) or not callable(writer): + raise TypeError("Both 'reader' and 'writer' must be callable. Got (%r, %r) instead.", reader, writer) + written = 0 + + if limit is not None: + sz_to_read = min(limit, chunk) + else: + limit = -1 + sz_to_read = chunk + + bytes = reader(sz_to_read) + while bytes: + writer(bytes) + + limit -= len(bytes) + written += len(bytes) + + if limit > 0: + sz_to_read = min(limit, chunk) + elif limit == 0: + break + else: + sz_to_read = chunk + + bytes = reader(sz_to_read) + return written + +CHUNKSIZE = 32 * 1024 +def trim_file(fname, cap, newsize): + fobjin = fobjout = None + + fname = path(fname) + if fname.size > cap: + try: + fobjin = open(fname, 'rb') + fobjout = open(fname+'.new', 'wb') + + fobjin.seek(-newsize, os.SEEK_END) + streamcopy(fobjin, fobjout, CHUNKSIZE) + finally: + for f in (fobjin, fobjout): + if f is not None: + f.close() + + os.remove(fname) + os.rename(fobjout.name, fname) + + +class PausableStream(object): + ''' + A stream that can be paused. if s.pause() is called, no data will be written + to the underlying stream until s.unpause() is called. Calling unpause will also + write out all data that was written while it was paused. + ''' + def __init__(self, stream): + self._lock = RLock() + self.paused = False + self.stream = stream + self._queue = Q.Queue() + def pause(self): + self.paused = True + + def unpause(self): + if self._lock.acquire(): + try: + while True: + try: + self.stream.write(self._queue.get_nowait()) + except Q.Empty: + break + finally: + self._lock.release() + + self.paused = False + + def write(self, data): + if self.paused: + self._queue.put(data) + else: + if self._lock.acquire(0): + try: + self.stream.write(data) + finally: + self._lock.release() + else: + self._queue.put(data) + + return len(data) + + def flush(self): + if not self.paused: + self.unpause() # make sure to dump the Q to the stream. + return self.stream.flush() + def close(self): + return self.stream.close() + def tell(self): + return self.stream.tell() + +class SwappableStream(PausableStream): + ''' + Call start_swap, do any further cleanup of old s.stream or prep for the newstream + and then call finish_swap with the new stream. + ''' + def start_swap(self): + self.pause() + self.stream.flush() + self.stream.close() + + def finish_swap(self, newstream): + self.stream = newstream + self.unpause() + +class LimitedFileSize(SwappableStream): + def __init__(self, fname, filesize_limit, resize, initmode='wb'): + ''' + Construct with a filename, a size_limit for the file, and the size to resize it to when size_limit is reached. + Data is truncated from the beginning of the file. + ''' + fobj = open(fname, initmode) + if resize > filesize_limit: + raise ValueError('resize must be smaller than filesize_limit. (resize=%r, filesize_limit=%r)', resize, filesize_limit) + SwappableStream.__init__(self, fobj) + self._szlimit = filesize_limit + self._fname = fname + self._resize = resize + self._known_size = None + + def write(self, data): + SwappableStream.write(self, data) + + if self._known_size is None: + self._known_size = os.path.getsize(self._fname) + else: + self._known_size += len(data) + + if self._known_size > self._szlimit: + self.start_swap() + try: + trim_file(self._fname, self._szlimit, self._resize) + finally: + self.finish_swap(open(self._fname, 'ab')) + +from ratelimited import RateLimiter +class StreamLimiter(RateLimiter): + ''' + Will not write more than 'limit' bytes per second (using a sliding window of 'window' seconds). + If data is being written too fast, only a "writing too fast" message is printed. + ''' + def __init__(self, stream, limit=4096, window=5): + self.stream = stream + RateLimiter.__init__(self, self.stream.write, limit, window) + def write(self, data): + self.handle_data(data) + def flush(self): + return self.stream.flush() + def close(self): + return self.stream.close() + def tell(self): + return self.stream.tell() + + def too_fast(self, data): + s = self.stream + s.write('Writing too fast: %r\n' % self.bps) + s.flush() + +class DelayedStreamLimiter(StreamLimiter): + ''' + same as stream limiter but continues writing for a length of time after going over the limit. + ''' + DELAY = .25 # seconds + def __init__(self, *a, **k): + StreamLimiter.__init__(self, *a, **k) + self._process_stop_time = 0 + def handle_data(self, data): + should_write = None + if not StreamLimiter.handle_data(self, data): + now = time() + if self._process_stop_time == 0: + if (now - self._process_stop_time) < self.DELAY: + # Write it anyway! + should_write = True + else: + # have been 'writing too fast' for more than DELAY seconds + # store the time for later calls of this method + self._process_stop_time = now + should_write = False + else: + # We've already stored the time in a previous iteration of this + # method, see if it's been longer than DELAY since then. + if (now - self._process_stop_time) < self.DELAY: + # it hasn't been that long yet + should_write = True + else: + # it has been too long + should_write = False + else: + # The data was already written for us + should_write = False + + # clear the stored time + self._process_stop_time = 0 + + if should_write: + self.f_process(data) + + if should_write: + # data was written by this method + return True + else: + if self._process_stop_time == 0: + # Data was written in superclass + return True + else: + # Data was not written + return False + + +class DisablingStream(object): + ''' + An output stream that disables itself if there is an error in writing or flushing. + After that, it can only be re-enabled with a call to s.enable() + + While disabled, all data written is lost. + ''' + def __init__(self, target): + self.target = target + + self.write = self.write_enabled + self.flush = self.flush_enabled + + def write_enabled(self, s): + try: self.target.write(s) + except: self.disable() + + def flush_enabled(self): + try: self.target.flush() + except: self.disable() + + def disable(self): + self.set_enabled(False) + def enable(self): + self.set_enabled(True) + + def disabled(self, data=None): + ''' + sink hole for data. + ''' + + def set_enabled(self, val): + if val: + self.flush = self.flush_enabled + self.write = self.write_enabled + else: + self.flush = self.write = self.disabled + + +if __name__ == '__main__': + from primitives.bits import getrandbytes + data = getrandbytes(100) + half_len = len(data)/2 + + from StringIO import StringIO + in_ = StringIO(data) + out = None + + def reset(i): + i.seek(0) + return StringIO() + + def check(i,o,l,w): + ''' + in, out, limit, written + ''' + return i.getvalue()[:l] == o.getvalue() and w == l + + # TODO: Tests for multiple 'out' streams. + __test_stream_copy = '''\ +>>> out = reset(in_); written = streamcopy(in_, out); check(in_, out, len(data), written) +True +>>> out = reset(in_); written = streamcopy(in_, out, chunk = len(data)); check(in_, out, len(data), written) +True +>>> out = reset(in_); written = streamcopy(in_, out, limit = half_len); check(in_, out, half_len, written) +True +>>> out = reset(in_); written = streamcopy(in_, out, limit = half_len, chunk = half_len+1); check(in_, out, half_len, written) +True +>>> out = reset(in_); written = streamcopy(in_, out, limit = half_len, chunk = half_len-1); check(in_, out, half_len, written) +True +''' + # TODO: tests for actual files! + __test_tail = '''\ +>>> in_.seek(0); tail(in_, 5) == in_.getvalue()[-5:] +True +>>> in_.seek(0); tail(in_, 1000) == in_.getvalue() +True +''' + + __test__ = dict( + streamcopy = __test_stream_copy, + tail = __test_tail, + ) + + import doctest + doctest.testmod(verbose=True) + + import sys + f = DelayedStreamLimiter(sys.stdout, limit = 8, window=1) + import time as time_mod + for i in range(20): + f.write(str(i) + '\n') + time_mod.sleep(.04 * i) diff --git a/digsby/src/gui/MultiImage.py b/digsby/src/gui/MultiImage.py new file mode 100644 index 0000000..e6e2f52 --- /dev/null +++ b/digsby/src/gui/MultiImage.py @@ -0,0 +1,355 @@ +from util import print_timing +from util import to_storage, Storage +import wx +from gui.toolbox import TransparentBitmap +from logging import getLogger +log = getLogger('MultiImage'); info = log.info; error = log.error + +class MultiImage(object): + 'Creates displayable bitmaps from a skinning language description.' + + def __init__(self, images): + self.images = images + self.tags = {} + self.drawrects = {} + + # record anchors + for image in self.images: + if hasattr(image, 'anchors'): + for anchor in image.anchors: + if 'tag' in anchor and anchor['tag'] is not None: + self.tags[anchor['tag']] = image + + self.cached_result = Storage(bitmap=None, size=(0,0)) + + def __repr__(self): + from pprint import pformat + return '' + + def __contains__(self, tagname): + "Returns True if the specified tag name is in this multi image's tags." + + return tagname in self.tags + + def tag_rect(self, tagname): + "Returns a wx.Rect for the requested tag." + + if not tagname in self.tags: + raise ValueError('tag %s not in this MultiImage' % tagname) + + return self.drawrects[self.tags[tagname]] + + def Draw(self, dc, rect): + position, size = rect[:2], rect[2:] + self.w, self.h = size + + if not self.cached_result.bitmap or self.cached_result.size != size: + info('new size, making bitmap') + + self.recalc() + self.cached_result.size = size + self.cached_result.bitmap = self.make_bitmap(*size) + + dc.DrawBitmapPoint(self.cached_result.bitmap, position) + + + def make_bitmap(self, width, height): + ''' + Creates a wx.Bitmap with the specified size, drawing this MultiImage's + total contents onto the bitmap. + + @param mask_color an optional parameter for the bitmap's mask + ''' + size = (width, height) + bitmap = TransparentBitmap(size) + + + if not hasattr(self, 'temp_dc'): + self.temp_dc = wx.MemoryDC(); + + self.temp_dc.SelectObject(bitmap); + self.drawtodc(self.temp_dc, wx.Rect(0,0,*size)) + self.temp_dc.SelectObject(wx.NullBitmap) + + self.region = wx.RegionFromBitmapColour(bitmap, (0,0,0,0)) +# gui.toolbox.toscreen(self.region.ConvertToBitmap(),500,500) + + + return bitmap + + def recalc(self): + self.drawrects.clear() + + # compute the local size/position of each image + for i in self.images: + if i not in self.drawrects: + self.compute_rect(i, wx.Rect(0,0,self.w,self.h)) + + def drawtodc(self, dc, rect): + ''' + + @param dc: dc to draw on + @param rect: rectangle to draw this combined image into + ''' + + #[self.compute_rect(i, rect) + # for i in self.images if i not in self.drawrects] + + for image in self.images: + drect = wx.Rect(*self.drawrects[image]) + drect.Offset((rect.x, rect.y)) + image.Draw(dc, drect) + + def compute_rect(self, image, dcrect): + # count anchors to + myanchors = image.get_anchors_to() + numanchors = len(myanchors) + + #if stretch/tile, assert 0 < numanchorsto < 3 + if image.style != 'static': + if not (numanchors == 1 or numanchors == 2): + raise AssertionError('%r is not static but has anchors %r' % (image, image.anchors)) + + # if 1, use dc + image offset/align as other "to" + # compute anchor position(self, DCsize, (0,0)) + # map 0,0 of self to that position + if numanchors == 1: + anchorfrom = to_storage(myanchors[0]) + tag = anchorfrom.to + imageto = self.tags[tag] + + anchorto = to_storage([anchor for anchor in imageto.anchors if + anchor['tag'] == tag][0]) + if imageto not in self.drawrects: + self.compute_rect(imageto, dcrect) + + rectto1 = self.drawrects[imageto] + rectto2 = (0,0,dcrect.width,dcrect.height) + + positionto1 = compute_anchor_position(anchorto, rectto1[2:], rectto1[:2]) + positionto2 = compute_anchor_position(get_SI2_anchor(image), rectto2[2:], rectto2[:2]) + + #relative position (local anchors) + positionfrom1 = compute_anchor_position(anchorfrom, [image.width, image.height], + [0,0]) + positionfrom2 = (0,0) + diffxto = abs(positionto1[0] - positionto2[0]) + diffyto = abs(positionto1[1] - positionto2[1]) + diffxlocal = abs(positionfrom1[0] - positionfrom2[0]) + diffylocal = abs(positionfrom1[1] - positionfrom2[1]) + + increasex = diffxto - diffxlocal + increasey = diffyto - diffylocal + + newsizew = image.width + increasex + newsizeh = image.height + increasey + + newlocalanchorposition = positionto2 + positiontodrawat = (positionfrom2[0] + newlocalanchorposition[0], + positionfrom2[1] + newlocalanchorposition[1]) +# print 'positiontodrawat1', positiontodrawat + self.drawrects[image] = (positiontodrawat[0], positiontodrawat[1], + newsizew, newsizeh) +# print "self.drawrects[image]", self.drawrects[image] + return + #if 2, whatever + elif numanchors == 2: + #local anchors + anchorfrom1, anchorfrom2 = myanchors + tag1, tag2 = anchorfrom1['to'], anchorfrom2['to'] + imageto1, imageto2 = self.tags[tag1], self.tags[tag2] + #remote anchors + anchorto1 = [anchor for anchor in imageto1.get_anchors_to() if + anchor['tag'] == tag1] + anchorto2 = [anchor for anchor in imageto2.get_anchors_to() if + anchor['tag'] == tag2] + + if imageto1 not in self.drawrects: + self.compute_rect(imageto1, dcrect) + if imageto2 not in self.drawrects: + self.compute_rect(imageto2, dcrect) + rectto1 = self.drawrects[imageto1] + rectto2 = self.drawrects[imageto2] + #absolute position (remote anchors) + positionto1 = compute_anchor_position(anchorto1, rectto1[2:], rectto1[:2]) + positionto2 = compute_anchor_position(anchorto2, rectto2[2:], rectto2[:2]) + + #relative position (local anchors) + positionfrom1 = compute_anchor_position(anchorfrom1, [image.imgw, image.imgh], + [0,0]) + positionfrom2 = compute_anchor_position(anchorfrom2, [image.imgw, image.imgh], + [0,0]) + #CAS: check relative positioning here + + diffxto = abs(positionto1[0] - positionto2[0]) + diffyto = abs(positionto1[1] - positionto2[1]) + diffxlocal = abs(positionfrom1[0] - positionfrom2[0]) + diffylocal = abs(positionfrom1[1] - positionfrom2[1]) + + increasex = diffxto - diffxlocal + increasey = diffyto - diffylocal + + newsizew = image.imgw + increasex + newsizeh = image.imgh + increasey + #compute new position of one local anchor on new size + newlocalanchorposition = compute_anchor_position(anchorfrom1, newsizew,newsizeh,[0,0]) + #subtract from position of remote anchor to find the absolute position of this image + + positiontodrawat = (positionto1[0] - newlocalanchorposition[0], + positionto1[1] - newlocalanchorposition[1]) +# print 'positiontodrawat2', positiontodrawat + self.drawrects[image] = (positiontodrawat[0], positiontodrawat[1], + newsizew, newsizeh) + return + else: + raise AssertionError("invalid skin, wrong number" + " (%d) of anchors for " + "image of type %s!" % + (numanchors, + image.image_dictionary['style'])) + #else assert -1 < numanchors < 2 + else: + assert(numanchors == 0 or numanchors == 1) + #if 0, use dc + image offset/align as "to" + #compute anchor position(self, DCsize, (0,0)) + #draw at that position + #print numanchors, image + if numanchors == 0: +# print "image", image +# print dir(image) +# print "anchor", get_SI2_anchor(image) + + positiontodrawat = compute_anchor_position(get_SI2_anchor(image), (dcrect.width, dcrect.height), + [0,0]) +# print 'positiontodrawat3', positiontodrawat + #rect = our offset and size + #print "adding", image.image_dictionary, "to drawrects" + self.drawrects[image] = (positiontodrawat[0], positiontodrawat[1], + image.width, image.height) +# print "self.drawrects[image]", self.drawrects[image] + return + #draw this image there + #if 1, whatever + elif numanchors == 1: + anchorfrom = image.get_anchors_to()[0] + tag = anchorfrom['to'] + imageto = self.tags[tag] + anchorto = [anchor for anchor in imageto.get_anchors_to() if + anchor['tag'] == tag] + if imageto not in self.drawrects: + self.compute_rect(imageto, dcrect) + rectto = self.drawrects[imageto] + #absolute position + positionto = compute_anchor_position(anchorto, rectto[2:], rectto[:2]) + #relative position + positionfrom = compute_anchor_position(anchorfrom, [image.width, image.height], + [0,0]) + positiontodrawat = (positionto[0] - positionfrom[0], + positionto[1] - positionfrom[1]) +# print 'positiontodrawat4', positiontodrawat + self.drawrects[image] = (positiontodrawat[0], positiontodrawat[1], + image.width, image.height) + return + else: + raise AssertionError("invalid skin, wrong number" + " (%d) of anchors for " + "image of type %s!" % + (numanchors, + image.image_dictionary['style'])) + #compute anchor position on self, do magic to figure out + #where that goes on the dc + #if they have no anchors to anything: + #assume anchor is to make-believe image (dc) + #else: + #compute anchors to, resize/reposition as necessary + +def compute_anchor_position(anchor, size, offset): + ''' + calculates the position of an anchor on an image + + @param anchor: the (single) anchor dictionary + @param size: the size of the image on which the anchor is placed + @param offset: the offset of the image on which the anchor is placed + + @return: tuple(x,y) + ''' + if 'halign' in anchor: + halign = anchor.halign + else: + halign = 'left' + if 'valign' in anchor: + valign = anchor.valign + else: + valign = 'top' + if 'offset' in anchor: + off = anchor.offset + else: + off = [0,0] + + #CAS: integer rounding + if isinstance(off[0], int): + myoffsetx = off[0] + else: + myoffsetx = int(str(off[0])[:-1]) * size[0] / 100 + + if isinstance(off[1], int): + myoffsety = off[1] + else: + myoffsety = int(str(off[1])[:-1]) * size[1] / 100 + + off = [myoffsetx, myoffsety] + + tup = (offset[0], offset[1], size[0], size[1]) + if halign == 'left': + x = 0 + elif halign == 'right': + x = tup[2] + else: + x = tup[2]/2 + x = x + tup[0] + off[0] + if valign == 'top': + y = 0 + elif valign == 'bottom': + y = tup[3] + else: + y = tup[3]/2 + y = y + tup[1] + off[1] + return (x,y) + +def get_SI2_anchor(image): + retval = Storage() + if hasattr(image, 'offset'): + retval['offset'] = image.offset + if hasattr(image, 'valign'): + retval['valign'] = image.valign + if hasattr(image, 'halign'): + retval['halign'] = image.halign + return retval + +def main(images): + temp_dc = wx.MemoryDC(); + temp_dc.SelectObject(destbitmap); + mimg = MultiImage(images) + drawrect = wx.Rect(10,10,220,440) + mimg.draw(temp_dc, drawrect) + + temp_dc.SelectObject(wx.NullBitmap) + + + +if __name__ == '__main__': + import util, syck + from skins import images as imgmngr + from skins import skins + app = wx.PySimpleApp() + +# image['source'] = 'skins/default/checkerboard9.png' + skins.res_path = "../../res/" + destbitmap = imgmngr.get('skins/default/blue-flower.jpg') + f = file("../../res/skins/skinExample") + + images = to_storage(syck.load(f)).Images + f.close() + util.profile(main, images) + destbitmap.SaveFile('C:/workspace/Digsby/res/skins/default/output.png', + wx.BITMAP_TYPE_PNG) diff --git a/digsby/src/gui/SI4Demo.cpp b/digsby/src/gui/SI4Demo.cpp new file mode 100644 index 0000000..881e827 --- /dev/null +++ b/digsby/src/gui/SI4Demo.cpp @@ -0,0 +1,114 @@ +#include ".\si4demo.h" +#include ".\SplitImage4.h" +#include "wx/sysopt.h" + +IMPLEMENT_APP(SI4Demo) + +bool SI4Demo::OnInit(){ + wxInitAllImageHandlers(); + Frame* f = new Frame(_T("Test"),wxPoint(50,50),wxSize(800,800)); + f->Show(TRUE); + SetTopWindow(f); + return TRUE; +} + +ImageData MakeImageData(){ + + ImageData idata; + + idata.x1=30; + idata.x2=-30; + idata.y1=30; + idata.y2=-30; + idata.source= wxT("digsbybig.png"); + + Region* reg[]={&idata.center,&idata.top,&idata.right,&idata.bottom,&idata.left}; + + reg[0]->offset=wxPoint(0,0); + reg[0]->hstyle=1; + reg[0]->vstyle=1; + reg[0]->align = wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL;//wxALIGN_LEFT | wxALIGN_TOP;//wxALIGN_RIGHT | wxALIGN_BOTTOM;// + reg[0]->extends.down=false; + reg[0]->extends.left=false; + reg[0]->extends.right=false; + reg[0]->extends.up=false; + + for(int i=1;i<5;++i){ + reg[i]->offset=wxPoint(0,0); + reg[i]->hstyle=1; + reg[i]->vstyle=1; + reg[i]->align = wxALIGN_TOP|wxALIGN_LEFT; + bool* ext[]={®[i]->extends.down,®[i]->extends.left,®[i]->extends.right,®[i]->extends.up}; + for(int n=0;n<4;++n){ + *ext[n]=false; + } + } + + return idata; + +} + +Frame::Frame(const wxString& title, const wxPoint& pos, const wxSize& size) +:wxFrame((wxFrame*)NULL,-1,title,pos,size){ + + //InitConsole(this,console); + wxMenu* menuFile = new wxMenu; + + menuFile->Append(ID_About,_T("&About...")); + menuFile->AppendSeparator(); + menuFile->Append(ID_Quit,_T("E&xit")); + + wxMenuBar* menuBar = new wxMenuBar; + menuBar->Append(menuFile, _T("&File")); + + SetMenuBar(menuBar); + + CreateStatusBar(); + SetStatusText(_T("Welcome to my Nightmare")); + + Connect(ID_About,wxEVT_COMMAND_MENU_SELECTED,wxCommandEventHandler(Frame::OnQuit)); + Connect(ID_About,wxEVT_COMMAND_MENU_SELECTED,wxCommandEventHandler(Frame::OnAbout)); + Connect(wxEVT_PAINT,wxPaintEventHandler(Frame::OnPaint)); + Connect(wxEVT_ERASE_BACKGROUND,wxEraseEventHandler(Frame::OnBG)); + Connect(wxEVT_SIZE,wxSizeEventHandler(Frame::OnSize)); + + ImageData idata=MakeImageData(); + + si4= new SplitImage4(idata); + //idata.source=_("menubgofulyness.png"); + //si4_2 = new SplitImage4(idata); + + //digsby =new wxBitmap(_("digsbybig.png"),wxBITMAP_TYPE_ANY); + +} +Frame::~Frame(){ + delete si4; +} + +void Frame::OnQuit(wxCommandEvent& WXUNUSED(event)){ + Close(TRUE); +} + +void Frame::OnAbout(wxCommandEvent& WXUNUSED(event)){ + wxMessageBox(_T("This is a wxWidgets Hello world sample"),_T("About Hello World"),wxOK | wxICON_INFORMATION, this); +} + +void Frame::OnSize(wxSizeEvent& event){ + event.Skip(); + Refresh(); +} + +void Frame::OnPaint(wxPaintEvent& event){ + wxBufferedPaintDC dc(this); + wxRect rect=wxRect(GetSize()); + dc.SetBrush(*wxRED_BRUSH); + dc.DrawRectangle(rect); + //si4->Draw(&dc,rect.Deflate(100,100)); + //dc.DrawBitmap(si4_2->GetBitmap(rect.GetSize()),100,100); + + si4->Draw(&dc,rect.Deflate(100,100)); + //dc.DrawBitmap(si4_2->GetBitmap(rect.Deflate(200,200).GetSize()),GetSize().x/3,GetSize().y/3); + +} + +void Frame::OnBG(wxEraseEvent &event){} diff --git a/digsby/src/gui/SI4Demo.h b/digsby/src/gui/SI4Demo.h new file mode 100644 index 0000000..9ac7e75 --- /dev/null +++ b/digsby/src/gui/SI4Demo.h @@ -0,0 +1,34 @@ +#ifndef __DEMO__ +#define __DEMO__ + +#include "wx/wx.h" +#include "wx/event.h" +#include "wx/dcbuffer.h" +#include "wx/textctrl.h" +#include ".\splitimage4.h" + +class SI4Demo: public wxApp{ + virtual bool OnInit(); +}; + +class Frame: public wxFrame{ +public: + + SplitImage4 *si4; + //SplitImage4 *si4_2; + + Frame(const wxString& title, const wxPoint& pos, const wxSize& size); + ~Frame(); + void OnQuit(wxCommandEvent& event); + void OnAbout(wxCommandEvent& event); + void OnPaint(wxPaintEvent& event); + void OnBG(wxEraseEvent &event); + void OnSize(wxSizeEvent& event); +}; + +enum{ + ID_About = 1, + ID_Quit, +}; + +#endif //__DEMO__ \ No newline at end of file diff --git a/digsby/src/gui/__init__.py b/digsby/src/gui/__init__.py new file mode 100644 index 0000000..ffe4e0d --- /dev/null +++ b/digsby/src/gui/__init__.py @@ -0,0 +1,19 @@ +import wx, os + +# add all platform-specific wx extensions to the wx API here so +# they're always available. +from gui.native.extensions import * + +# now add all platform-indepdenent extensions as well. +from gui.wxextensions import * + +def dist(here, there): + 'Returns the euclidean distance from here to there.' + return sum(map(lambda a, b: (a-b) ** 2, here, there)) ** .5 + +wx.Point.DistanceTo = dist + +def yes(msg, title = ''): + return wx.MessageBox(msg, title, wx.YES_NO) == wx.YES + + diff --git a/digsby/src/gui/accountdialog.py b/digsby/src/gui/accountdialog.py new file mode 100644 index 0000000..bfd6892 --- /dev/null +++ b/digsby/src/gui/accountdialog.py @@ -0,0 +1,1173 @@ +''' + +Account editing dialog. + +''' +from __future__ import with_statement + +import wx +from wx import EXPAND, ALL, ALIGN_CENTER_VERTICAL, ALIGN_RIGHT, LEFT, BOTTOM, TOP + +from util import Storage, try_this, import_function, traceguard +from util.primitives.funcs import get, Delegate, do +from gui.toolbox import build_button_sizer +from common.protocolmeta import protocols + +from gui.controls import CheckBox, RadioPanel +from gui.clique import Clique +from wx import StaticText, TextCtrl, BoxSizer, RadioButton +from logging import getLogger; log = getLogger('accountdialog') +from gui.validators import LengthLimit, NumericLimit + +from config import platformName +from gettext import ngettext +from common import profile + +# imported for monkeypatches +import wx.lib.sized_controls + +DEFAULT_JABBER_PRIORITY = 5 +txtSize = (130, -1) + +centerright = ALIGN_CENTER_VERTICAL | ALIGN_RIGHT + +#TODO: validators + +PortValidator = lambda: NumericLimit(65535) + +MAIL_CLIENT_SYSDEFAULT = _('System Default') +MAIL_CLIENT_OTHER = _('Other Mail Client...') +MAIL_CLIENT_URL = _('Launch URL...') + +def edit_account(parent, account): + log.info('editing %r', account) + + parent = parent.Top + + diag = AccountPrefsDialog(parent, account = account) + if platformName == 'mac': + diag.Center() + + try: + if diag.ShowModal() != wx.ID_SAVE: + return # if cancelled + else: + account.update_info(**diag.info()) + finally: + diag.Destroy() + + +GTALK_FILTERS = ('@gmail.com', '@googlemail.com') + +def filter_acct_id(proto, name): + if proto == 'gtalk' or proto == 'gmail': + name = name.lower() + for domain in GTALK_FILTERS: + if name.endswith(domain): + name = name[:-len(domain)] + break + if proto == 'yahoo': + if '@yahoo.' in name: + name = name.lower().split('@yahoo.')[0] + if proto in ('oscar', 'aim', 'icq'): + name = name.lower().replace(' ', '') + return proto, name + +def strip_acct_id(proto, name): + if proto == 'gtalk' or proto == 'gmail': + for domain in GTALK_FILTERS: + if name.lower().endswith(domain): + name = name[:-len(domain)] + break + if proto == 'yahoo': + if '@yahoo.' in name: + name = name.split('@yahoo.')[0] + return proto, name + +def acctid(proto, name): + proto, name = filter_acct_id(proto, name) + return proto + '%%~~%%' + name + + +class AccountPrefsDialog(wx.Dialog): + 'Small dialog window for editing and creating accounts.' + + # Use the following two methods to create and edit accounts. + + @classmethod + def create_new(cls, parent, protocol_name): + ''' + Make a dialog box that can create a new account. + ''' + return cls(parent, protocol_name = protocol_name) + + @classmethod + def edit_account(cls, parent, account): + ''' + Make a dialog box that can edit an existing Account. + ''' + return cls(parent, account = account) + + # + + def __init__(self, parent, account = None, protocol_name = None): + "Please do not call directly. See classmethods create_new and edit_account." + + # Editing an existing account + if account is not None: + self.new = False + assert protocol_name is None + protocolinfo = account.protocol_info() + self.protocol_name = account.protocol + title = '%s - %s Settings' % (account.name, protocolinfo.name) + + # Creating a new account + if account is None: + self.new = True + protocolinfo = protocols[protocol_name] + self.protocol_name = protocol_name + title = '%s Account' % protocolinfo.name + + # What to call the username (screenname, username, Jabber ID, etc.) + self.screenname_name = protocolinfo.username_desc + + wx.Dialog.__init__(self, parent, title=title, size=(400,300)) + self.account = account if account is not None else emptystringer(getattr(protocolinfo, 'defaults', None)) + self.new = account is None + self.protocolinfo = protocolinfo + + # Set the account type icon + from gui import skin + self.SetFrameIcon(skin.get('serviceicons.%s' % self.protocol_name)) + + self.formtype = getattr(protocolinfo, 'form', 'default') + self.info_callbacks = Delegate() + + if self.new: + self._allaccts = [acctid(a.protocol, a.name) for a in profile.account_manager] + + self.construct(account is None) + self.layout() + + # enable or disable the save button as necessary. + self.check_warnings() + self.Fit() + + # focus the first enabled text control. + for c in self.Children: + if isinstance(c, TextCtrl) and c.IsEnabled() and c.IsEditable(): + if c is get(self, 'password', None): + c.SetSelection(-1, -1) # only makes sense to select all on a password field :) + + wx.CallAfter(c.SetFocus) + break + + def info(self): + 'Returns a Storage containing the attributes edited by this dialog.' + + info = Storage(name = self.name.Value, + protocol = self.protocol_name) + + info.protocol, info.name = strip_acct_id(info.protocol, info.name) + + if hasattr(self, 'password'): + info.password_len = len(self.password.Value) + try: + info.password = profile.crypt_pw(self.password.Value) + except UnicodeEncodeError: + # the database has corrupted the password. + log.warning('corrupted password') + info.password = '' + self.password.Value = '' + import hub + hub.get_instance().on_error('This account\'s password has been corrupted somehow. Please report it immediately.') + + if hasattr(self, 'host'): + info.server = (self.host.Value, int(self.port.Value) if self.port.Value else '') + + if hasattr(self, 'remote_alias'): + info.remote_alias = self.remote_alias.Value + + if hasattr(self, 'autologin'): + info.autologin = bool(self.autologin.Value) + + if hasattr(self, 'resource'): + info.update(resource = self.resource.Value, + priority = try_this(lambda: int(self.priority.Value), DEFAULT_JABBER_PRIORITY)) +# , +# confserver = self.confserver.Value + if hasattr(self, 'dataproxy'): + info.update(dataproxy = self.dataproxy.Value) + + for d in getattr(self.protocolinfo, 'more_details', []): + attr = d['store'] + ctrl = getattr(self, attr) + info[attr] = ctrl.Value + + getattr(self, 'info_' + self.formtype, lambda *a: {})(info) + + for info_cb in self.info_callbacks: + info_cb(info) + + defaults = self.protocolinfo.get('defaults', {}) + for k in defaults: + if k not in info: + info[k] = getattr(self.account, k, defaults.get(k)) + + return info + + def info_email(self, info): + info.update(Storage(updatefreq = int(self.updatefreq.Value)*60)) + + if hasattr(self, 'mailclient'): + assert isinstance(self.mailclient, basestring) + info.update(dict(mailclient = self.mailclient, + custom_inbox_url = self.custom_inbox_url, + custom_compose_url = self.custom_compose_url)) + + if hasattr(self, 'emailserver'): + # email server information + servertype = self.protocolinfo.needs_server.lower() + info.update({servertype + 'server': self.emailserver.Value, + servertype + 'port' : int(self.emailport.Value) \ + if self.emailport.Value else '', + 'require_ssl': self.require_ssl.Value}) + + if hasattr(self, 'smtp_server'): + info.update(email_address = self.email_address.Value, + smtp_server = self.smtp_server.Value, + smtp_port = int(self.smtp_port.Value) if self.smtp_port.Value else '', + smtp_require_ssl = self.smtp_require_ssl.Value) + + if self.smtp_same.Value: + info.update(smtp_username = self.name.Value, + smtp_password = self.password.Value) + else: + info.update(smtp_username = self.smtp_username.Value, + smtp_password = self.smtp_password.Value) + + def info_social(self, info): + for d in ((self.new and getattr(self.protocolinfo, 'new_details', []) or []) + + getattr(self.protocolinfo, 'basic_details', [])): + type_ = d['type'] + + if type_ in ['bool']: + attr = d['store'] + ctrl = getattr(self, attr) + info[attr] = ctrl.Value + elif type_ == 'meta': + key = d['store'] + val = d['value'] + info[key] = val + elif type_ == 'label': + pass + else: + raise AssertionError("This mechanism needs to be completed!") + + filters = {} + for key in self.checks: + filters[key] = [] + for chk in self.checks[key]: + t, i = chk.Name.split('/') + + assert len(filters[key]) == int(i), (key, len(filters), int(i)) + filters[t].append(chk.Value) + + info['filters'] = filters + + def on_expand(self, e): + isshown = self.expanded + + self.details.Show(not isshown) + self.FitInScreen() + self.expanded = not isshown + + wx.CallAfter(self.Refresh) + + def construct(self, is_new): + self.construct_common(is_new) + getattr(self, 'construct_' + self.formtype, getattr(self, 'construct_default'))() + + # after all textboxes have been constructed, bind to their KeyEvents so + # that we can disable the Save button when necessary + + # Make sure textboxes have values. + txts = [self.name] + + for textbox in self.get_required_textboxes(all = True): + textbox.Bind(wx.EVT_TEXT, lambda e: self.check_warnings()) + + if self.protocolinfo.get('needs_smtp', False): + self.Bind(wx.EVT_RADIOBUTTON, lambda e: (e.Skip(), self.check_warnings())) + + # A small arrow for expanding the dialog to show advanced options. + from gui.chevron import Chevron + self.expand = Chevron(self, 'Advanced') + self.expand.Bind(wx.EVT_CHECKBOX, self.on_expand) + self.expanded = False + + self.AffirmativeId = wx.ID_SAVE + self.save = wx.Button(self, wx.ID_SAVE, _('&Save')) + self.save.Bind(wx.EVT_BUTTON, self.on_save) + self.save.SetDefault() + if is_new or try_this(lambda: self.password.Value, None) == '': self.save.Enable(False) + + self.cancel = wx.Button(self, wx.ID_CANCEL, _('&Cancel')) + self.cancel.Bind(wx.EVT_BUTTON, self.on_cancel) + + def check_warnings(self): + warnings = list(getattr(self.protocolinfo, 'warnings', ())) + warnings.append(dict(checker = self.check_account_unique, critical = True, + text = _('That account already exists.'))) + warnings.append(dict(checker = self.filled_in, critical = True)) + + warn_texts = [] + + enable_save = True + info = self.info() + + if self.protocolinfo.get('needs_password', True): + info['plain_password'] = self.password.Value + + for warning in warnings: + checker = warning.get('checker', None) + check_passed = True + if checker is not None: + check_passed = checker(info) + + if not check_passed: + text = warning.get('text', None) + if text is not None: + warn_texts.append(text) + + if warning.get('critical', False): + enable_save = False + + self.set_warnings(warn_texts) + self.save.Enable(enable_save) + + def construct_default(self): + acct = self.account + + # Auto login checkbox: shown by default, turn off with show_autologin = False + if getattr(self.protocolinfo, 'show_autologin', True): + self.autologin = wx.CheckBox(self, -1, _('&Auto login')) + self.autologin.SetToolTipString(_('If checked, this account will automatically sign in when Digsby starts.')) + self.autologin.Value = bool(getattr(self.account, 'autologin', False)) + + # Register new account checkbox: off by default. shows only on when this + # is a new account dialog, and when needs_register = True + if self.new and getattr(self.protocolinfo, 'needs_register', False): + self.register = wx.CheckBox(self, -1, _('&Register New Account')) + self.register.Bind(wx.EVT_CHECKBOX, self.on_register) + + if getattr(self.protocolinfo, 'needs_remotealias', False): + self.remote_alias = TextCtrl(self, -1, value = getattr(acct, 'remote_alias', ''), validator = LengthLimit(120)) + + if getattr(self.protocolinfo, 'needs_resourcepriority', False): + # Length limit is according to rfc + self.resource = TextCtrl(self, value = getattr(acct, 'resource') or 'Digsby', validator = LengthLimit(1023)) + + priority = getattr(acct, 'priority', DEFAULT_JABBER_PRIORITY) + if priority == '': + priority = DEFAULT_JABBER_PRIORITY + self.priority = TextCtrl(self, value = str(priority), validator = NumericLimit(-127,128)) + self.priority.MinSize = wx.Size(1, -1) + + if getattr(self.protocolinfo, 'needs_dataproxy', False): + self.dataproxy = TextCtrl(self, value = getattr(acct, 'dataproxy', ''), validator = LengthLimit(1024)) + + if getattr(self.protocolinfo, 'hostport', True): + server = getattr(self.account, 'server') + if server: host, port = server + else: host, port = '', '' + + self.host = TextCtrl(self, size = (110, -1), value=host, validator = LengthLimit(1023)) + + self.port = TextCtrl(self, value = str(port), validator = PortValidator()) + self.port.MinSize = wx.Size(1, -1) + + def on_register(self, event): + checked = self.register.IsChecked() + + self.save.Label = _(u"&Register") if checked else _(u"&Save") + + def add_warning(self, text): + lbl = self.label_warnings.Label + if lbl: + newlbl = lbl + u'\n' + text + else: + newlbl = text + self.set_warning(newlbl) + + def set_warnings(self, texts): + self.set_warning('\n'.join(texts)) + + def set_warning(self, text): + self.label_warnings.Label = text + + # FIXME: this throws the sizing on Mac all out of whack. Perhaps some native API gets + # messed up when called on a hidden control? + if not platformName == "mac": + if not text: + self.label_warnings.Show(False) + else: + self.label_warnings.Show(True) + + self.Layout() + self.Fit() + self.Refresh() + + def clear_warning(self): + self.set_warning(u'') + + def construct_common(self, is_new): + self.label_warnings = StaticText(self, -1, '', style = wx.ALIGN_CENTER) + self.label_warnings.SetForegroundColour(wx.Colour(224, 0, 0)) + self.clear_warning() + + needs_password = self.protocolinfo.get('needs_password', True) + self.label_screenname = StaticText(self, -1, self.screenname_name + ':', style = ALIGN_RIGHT) + + if needs_password: + self.label_password = StaticText(self, -1, 'Password:', style = ALIGN_RIGHT) + + + if self.account.name == '' and hasattr(self.protocolinfo, 'newuser_url'): + sn = self.url_screenname = wx.HyperlinkCtrl(self, -1, 'New User?', + getattr(self.protocolinfo, 'newuser_url')) + sn.HoverColour = sn.VisitedColour = sn.NormalColour + + if needs_password and hasattr(self.protocolinfo, 'password_url'): + password = self.url_password = wx.HyperlinkCtrl(self, -1, 'Forgot Password?', + getattr(self.protocolinfo, 'password_url')) + + password.HoverColour = password.VisitedColour = password.NormalColour + + if self.protocolinfo.get('needs_smtp', False): + self.email_address = TextCtrl(self, -1, value = getattr(self.account, 'email_address', ''), size = txtSize, validator=LengthLimit(1024)) + + self.name = TextCtrl(self, -1, value=self.account.name, size=txtSize, validator=LengthLimit(1024)) + + # disable editing of username if this account is not new + if not self.new: + self.name.SetEditable(False) + self.name.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_SCROLLBAR)) + # self.name.Enable(self.new) + + if needs_password: + password = self.account._decryptedpw() + + f = lambda p: TextCtrl(self, -1, value = p, + size = txtSize, style = wx.TE_PASSWORD, + validator = LengthLimit(128)) + try: + self.password = f(password) + except UnicodeDecodeError: + self.password = f('') + + + def on_cancel(self, e): + self.EndModal(self.EscapeId) + + def on_save(self, e): + # Do some simple protocol dependent validation. + + c = get(self.account, 'connection', None) + + if c is not None: + for updatee in get(c, 'needs_update', []): + try: + attr, fname = updatee + f = getattr(c, fname) + except: + attr = updatee + f = lambda _v: setattr(c, attr, _v) + + f(getattr(self, attr).Value) + + for attr, validator, message in getattr(self.protocolinfo, 'validators', []): + if not validator(getattr(self, attr).Value): + return wx.MessageBox(message, 'Account Information Error') + + if hasattr(self, 'register') and self.register.IsChecked(): + self.save.Enabled = False + info = self.info() + log.info_s('adding account: %r', info) + profile.register_account( + on_success = lambda: wx.CallAfter(self.on_success_register), + on_fail = lambda error: wx.CallAfter(self.on_fail_register, error), + **info) + else: + self.EndModal(wx.ID_SAVE) + + def on_success_register(self): + self.EndModal(wx.ID_SAVE) + + def on_fail_register(self, error): + textcode, text, kind, codenum = error + wx.MessageBox("Error %(codenum)d: %(text)s" % locals(), textcode) + self.EndModal(wx.ID_CANCEL) + + def EndModal(self, return_code): + if self.formtype == 'social' and hasattr(self, '_origfilters') and return_code != self.AffirmativeId and self.account: + if self.account.filters != self._origfilters: + self.account.filters = self._origfilters + self.account.notify('alerts') + + wx.Dialog.EndModal(self, return_code) + + def get_required_textboxes(self, all = False): + tb = [self.name] + pinfo = self.protocolinfo + + if pinfo.get('needs_password', True): + tb += [self.password] + + if pinfo.get('needs_smtp', False): + tb += [self.email_address, + self.emailserver, self.emailport, + self.smtp_port, self.smtp_server] + + # when the "same" radio is not checked, the extra SMTP user/pass boxes + # are required as well. + if all or not self.smtp_same.Value: + tb += [self.smtp_username, self.smtp_password] + + if pinfo.get('needs_remotealias', False) and all: + tb += [self.remote_alias] + + return tb + + def check_account_unique(self, i): + if self.new: + return not (acctid(self.protocol_name, i.name) in self._allaccts) or self.protocol_name in ('imap', 'pop') + else: + return True + + def filled_in(self, _i): + return all(tb.Value != '' for tb in self.get_required_textboxes()) + + def SwapDefaultPorts(self, event, srv, ssl, portfield): + stdport = unicode(self.protocolinfo.defaults[srv + 'port']) + sslport = unicode(self.protocolinfo.defaults[srv + 'port_ssl']) + + rightport, wrongport = (sslport, stdport) if ssl else (stdport, sslport) + + if portfield.Value == wrongport: + portfield.Value = rightport + + event.Skip() + + def construct_email(self): + + srv = self.protocolinfo.get('needs_server', None) + if srv is not None: + srv = srv.lower() + self.emailserver = TextCtrl(self, -1, size = txtSize, + value = unicode(getattr(self.account, srv + 'server')), validator=LengthLimit(1024)) + self.emailport = TextCtrl(self, -1, size = (60, -1), + value = unicode(getattr(self.account, srv + 'port')), + validator = PortValidator()) + reqssl = self.require_ssl = CheckBox(self, '&This server requires SSL', + value = bool(self.account.require_ssl)) + + reqssl.Bind(wx.EVT_CHECKBOX, lambda e: self.SwapDefaultPorts(e, srv, reqssl.Value, self.emailport)) + + smtp = self.protocolinfo.get('needs_smtp', False) + if smtp: self.construct_smtp() + + updatetext = _('Check for new mail every {n} minutes').split(' {n} ') + + self.updatetext1 = StaticText(self, -1, updatetext[0]) + self.updatetext2 = StaticText(self, -1, updatetext[1]) + + # email update frequency + self.updatefreq = TextCtrl(self, -1, size=(30, -1), validator = NumericLimit(1, 999)) + + def update_changed(e): + e.Skip(True) + import gettext + newval = gettext.ngettext(u'minute', u'minutes', int(self.updatefreq.Value or 0)) + + if newval != self.updatetext2.Label: + self.updatetext2.Label = newval + + self.updatefreq.Bind(wx.EVT_TEXT, update_changed) + + minutes = str(self.account.updatefreq/60) + + self.updatefreq.Value = minutes + + if self.protocolinfo.get('needs_webclient', True): + self.mailclient = self.account.mailclient or 'sysdefault' + self.custom_inbox_url = self.account.custom_inbox_url + self.custom_compose_url = self.account.custom_compose_url + + self.mailclienttext = StaticText(self, -1, _('Mail Client:')) + self.mailclient_choice = wx.Choice(self) + self.update_mailclient() + self.mailclient_choice.Bind(wx.EVT_CHOICE, self.on_mailclient_choice) + + def construct_smtp(self): + self.smtp_server = TextCtrl(self, -1, size = txtSize, + value = unicode(getattr(self.account, 'smtp_server', '')), + validator = LengthLimit(1024), + ) + self.smtp_port = TextCtrl(self, -1, size = (60, -1), + value = unicode(getattr(self.account, 'smtp_port', '')), + validator = PortValidator()) + reqssl = self.smtp_require_ssl = CheckBox(self, '&This server requires SSL', + value = bool(getattr(self.account, 'smtp_require_ssl', False))) + + reqssl.Bind(wx.EVT_CHECKBOX, lambda e: self.SwapDefaultPorts(e, 'smtp_', reqssl.Value, self.smtp_port)) + + servertype = self.protocolinfo.get('needs_server') + self.smtp_same = RadioButton(self, -1, _('SMTP username/password are the same as {servertype}').format(servertype=servertype), style = wx.RB_GROUP) + self.smtp_different = RadioButton(self, -1, _('Log on using:')) + + u = self.smtp_username = TextCtrl(self, -1, size = (110, -1), validator=LengthLimit(1024)) + p = self.smtp_password = TextCtrl(self, -1, size = (110, -1), style = wx.TE_PASSWORD, validator=LengthLimit(1024)) + + smtpuser, smtppass = getattr(self.account, 'smtp_username', ''), getattr(self.account, 'smtp_password', '') + if (not smtpuser and not smtppass) or smtpuser == self.name.Value and smtppass == self.password.Value: + self.smtp_same.SetValue(True) + u.Enable(False) + p.Enable(False) + else: + self.smtp_different.SetValue(True) + u.Enable(True) + p.Enable(True) + + u.Value = smtpuser + p.Value = smtppass + + def on_radio(e = None, val = False): + # when a radio is clicked + enabled = val if e is None else e.EventObject is not self.smtp_same + u.Enable(enabled) + p.Enable(enabled) + + self.Bind(wx.EVT_RADIOBUTTON, on_radio) + + def construct_social(self): + types = ('alerts','feed', 'indicators') + self.checks = {} + + if self.account.filters: + from copy import deepcopy as copy + self._origfilters = copy(self.account.filters) + + + def set_filter(e): + _t,_i = e.EventObject.Name.split('/') + keylist = get(self.account,'%s_keys' % _t) + _k = get(keylist, int(_i)) + + self.account.filters[_t][_k] = e.IsChecked() + + if _t == 'alerts': + self.account.notify(_t) + + + for typ in types: + if getattr(self.protocolinfo, 'needs_%s' % typ, False): + self.checks[typ] = [] + + for i, nicename in enumerate(self.protocolinfo[typ]): + key = get(get(self.account,'%s_keys' % typ), i, None) + + if key is not None: + val = self.account.filters[typ][key] + else: + val = True + + chk = wx.CheckBox(self, label=nicename, name='%s/%s' % (typ,i)) + chk.Value = val + + if self.account: + chk.Bind(wx.EVT_CHECKBOX, set_filter) + + self.checks[typ].append(chk) + + def layout_social(self, sizer, row): + sizer, row = self.layout_top(sizer, row) + + self.layout_bottom(sizer,row) + + def layout_top(self, sizer, row): + add = sizer.Add + + for d in ((self.new and getattr(self.protocolinfo, 'new_details', []) or []) + + getattr(self.protocolinfo, 'basic_details', [])): + type_ = d['type'] + + if type_ == 'bool': + attr = d['store'] + desc = d['label'] + default = d['default'] + ctrl = wx.CheckBox(self, -1, desc) + + setattr(self, attr, ctrl) + + s = wx.BoxSizer(wx.HORIZONTAL) + ctrl.SetValue(getattr(self.account, attr, default) if self.account else default) + s.Add(ctrl, 0, wx.EXPAND) + + # allow checkboxes to have [?] links next to them + help_url = d.get('help', None) + if help_url is not None: + from gui.toolbox import HelpLink + s.Add(HelpLink(self, help_url), 0, wx.EXPAND) + + add(s, (row,1), (1,3), flag = ALL, border = ctrl.GetDefaultBorder()) + + row += 1 + elif type_ == 'label': + desc = d['label'] + ctrl = wx.StaticText(self, -1, desc) + add(ctrl, (row,1), (1,3), flag = ALL, border = ctrl.GetDefaultBorder()) + row += 1 + elif type_ == 'meta': + pass + else: + raise AssertionError("This mechanism needs to be completed!") + return sizer, row + + + def build_details_social(self,sizer,row): + types = ('alerts','feed', 'indicators') + hsz = BoxSizer(wx.HORIZONTAL) + + + d = self.details + + add = lambda c,s,*a: (d.add(c),s.Add(c,*a)) + + for i, key in enumerate(types): + checks = self.checks.get(key,()) + if not checks: + continue + + sz = BoxSizer(wx.VERTICAL) + tx = StaticText(self, -1, _('{show_topic}:').format(show_topic = self.protocolinfo['show_%s_label' % key]), style = wx.ALIGN_LEFT) + from gui.textutil import CopyFont + tx.Font = CopyFont(tx.Font, weight = wx.BOLD) + + add(tx, sz, 0, wx.BOTTOM, tx.GetDefaultBorder()) + for chk in checks: + add(chk, sz, 0, ALL, chk.GetDefaultBorder()) + + hsz.Add(sz,0) +# if i != len(types)-1: hsz.AddSpacer(15) + + self.Sizer.Add(hsz,0,wx.BOTTOM | wx.LEFT,10) + self.build_details_default(sizer, row) + + def update_mailclient(self, mc = None): + if mc is None: + mc = self.account.mailclient or '' + + ch = self.mailclient_choice + with ch.Frozen(): + ch.Clear() + + choices = [MAIL_CLIENT_SYSDEFAULT] + + file_entry = 0 + if mc.startswith('file:'): + import os.path + if not os.path.exists(mc[5:]): + mc == 'sysdefault' + else: + choices += [_('Custom ({mailclient_name})').format(mailclient_name=mc[5:])] + file_entry = len(choices) - 1 + + choices += [MAIL_CLIENT_OTHER, + MAIL_CLIENT_URL] + + do(ch.Append(s) for s in choices) + + if mc == 'sysdefault': + selection = 0 + elif mc == '__urls__': + selection = ch.Count - 1 + else: + selection = file_entry + + ch.SetSelection(selection) + ch.Layout() + + def on_mailclient_choice(self, e): + # TODO: don't use StringSelection, that's dumb. + val = self.mailclient_choice.StringSelection + + if val.startswith(MAIL_CLIENT_SYSDEFAULT): + self.mailclient = 'sysdefault' + elif val == MAIL_CLIENT_OTHER: + import os, sys + defaultDir = os.environ.get('ProgramFiles', '') + + wildcard = '*.exe' if sys.platform == 'win32' else '*.*' + filediag = wx.FileDialog(self, _('Please choose a mail client'), + defaultDir = defaultDir, + wildcard = wildcard, + style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if filediag.ShowModal() == wx.ID_OK: + self.mailclient = 'file:' + filediag.Path + elif val == MAIL_CLIENT_URL: + diag = LaunchURLDialog(self, self.custom_inbox_url, self.custom_compose_url) + try: + if wx.ID_OK == diag.ShowModal(): + self.mailclient = '__urls__' + self.custom_inbox_url = diag.InboxURL + self.custom_compose_url = diag.ComposeURL + finally: + diag.Destroy() + else: + self.mailclient = val + + self.update_mailclient(getattr(self, 'mailclient', 'sysdefault')) + + def build_details_email(self,sizer,row): + v = BoxSizer(wx.VERTICAL) + d = self.details + + add = lambda c,s,*a,**k: (d.add(c),s.Add(c,*a,**k)) + + # text, update frequency textbox, text + h = BoxSizer(wx.HORIZONTAL) + add(self.updatetext1, h, 0, wx.ALIGN_CENTER_VERTICAL | ALL, self.updatetext1.GetDefaultBorder()) + add(self.updatefreq, h, 0, ALL, self.updatefreq.GetDefaultBorder()) + add(self.updatetext2, h, 0, wx.ALIGN_CENTER_VERTICAL | ALL, self.updatetext2.GetDefaultBorder()) + v.Add(h, 0, EXPAND) + + # text, mail client choice + if hasattr(self, 'mailclient_choice'): + h2 = BoxSizer(wx.HORIZONTAL) + add(self.mailclienttext, h2, 0, ALIGN_CENTER_VERTICAL | ALL, self.mailclienttext.GetDefaultBorder()) + add(self.mailclient_choice, h2, 1, ALL, self.mailclient_choice.GetDefaultBorder()) +# h2.Add((30,0)) + v.Add(h2, 0, EXPAND | ALL, 3) + add(wx.StaticLine(self),v,0,EXPAND|wx.TOP|wx.BOTTOM,3) + + if hasattr(self, 'smtp_same'): + add(self.smtp_same, v, 0, EXPAND | ALL, self.GetDefaultBorder()) + add(self.smtp_different, v, 0, EXPAND | ALL, self.GetDefaultBorder()) +# v.AddSpacer(3) + + v2 = wx.GridBagSizer(8, 8); v2.SetEmptyCellSize((0,0)) + add(StaticText(self, -1, _('Username:')), v2, (0, 0), flag = ALIGN_CENTER_VERTICAL | ALIGN_RIGHT | ALL, border = self.GetDefaultBorder()) + add(self.smtp_username, v2, (0, 1), flag = ALL, border = self.smtp_username.GetDefaultBorder()) + add(StaticText(self, -1, _('Password:')), v2, (1, 0), flag = ALIGN_CENTER_VERTICAL | ALIGN_RIGHT | ALL, border = self.GetDefaultBorder()) + add(self.smtp_password, v2, (1, 1), flag = ALL, border = self.smtp_password.GetDefaultBorder()) + + v.Add(v2, 0, EXPAND | wx.LEFT, 20) +# v.AddSpacer(3) + + + self.Sizer.Add(v,0,wx.ALIGN_CENTER_HORIZONTAL|wx.BOTTOM,10) + + def layout(self): + self.Sizer = s = BoxSizer(wx.VERTICAL) + + if hasattr(self, 'label_warnings'): + warn_sz = BoxSizer(wx.HORIZONTAL) + warn_sz.Add(self.label_warnings, 1, flag = wx.EXPAND | wx.ALIGN_CENTER) + s.Add(warn_sz, flag = wx.EXPAND | wx.TOP, border = self.GetDefaultBorder()) + + # Top Sizer: username, password, autologin[, register new] + fx = wx.GridBagSizer(0,0) + s.Add(fx, 1, EXPAND|ALL, self.GetDialogBorder()) + + # screenname: label, textfield, hyperlink + row = 0 + + fx.SetEmptyCellSize((0,0)) + + if self.protocolinfo.get('needs_smtp', False): + # email address + label = StaticText(self, -1, _('Email Address')) + fx.Add(label, (row, 0), flag = centerright | ALL, border = label.GetDefaultBorder()) + fx.Add(self.email_address, (row, 1), flag = ALL, border = self.email_address.GetDefaultBorder()) + row += 1 + + # username + fx.Add(self.label_screenname, (row,0), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | ALL, border = self.label_screenname.GetDefaultBorder()) + fx.Add(self.name, (row,1), flag = ALL, border = self.name.GetDefaultBorder()) + if hasattr(self, 'url_screenname'): + fx.Add(self.url_screenname, (row,2), (1,2), flag=ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | ALL, border = self.url_screenname.GetDefaultBorder()) + + row += 1 + + # password: label, textfield, hyperlink + if self.protocolinfo.get('needs_password', True): + fx.Add(self.label_password, (row,0), flag=ALIGN_CENTER_VERTICAL | ALIGN_RIGHT | ALL, border = self.label_password.GetDefaultBorder()) + fx.Add(self.password, (row,1), flag = ALL, border = self.password.GetDefaultBorder()) + if hasattr(self, 'url_password'): + fx.Add(self.url_password, (row,2), (1,2), flag=ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | ALL, border = self.url_password.GetDefaultBorder()) + + fx.AddGrowableCol(1,1) + row += 1 + + + getattr(self, 'layout_' + self.formtype, getattr(self, 'layout_default'))(fx, row) + row += 1 + + def layout_default(self, sizer, row): + add = sizer.Add + + if hasattr(self, 'remote_alias'): + add(StaticText(self, -1, _('&Display Name:')), (row,0), flag = ALIGN_RIGHT|ALIGN_CENTER_VERTICAL|ALL, border=self.GetDefaultBorder()) + add(self.remote_alias, (row,1),flag = EXPAND | ALL, border = self.remote_alias.GetDefaultBorder()) + row += 1 + + # autologin and register new account + if hasattr(self, 'autologin') or hasattr(self, 'register'): + checks = BoxSizer(wx.HORIZONTAL) + + if hasattr(self, 'autologin'): + checks.Add(self.autologin, 0, EXPAND | ALL, self.autologin.GetDefaultBorder()) + + if hasattr(self, 'register'): + #checks.AddSpacer(10) + checks.Add(self.register, 0, EXPAND | ALL, self.register.GetDefaultBorder()) + + sizer.Add(checks, (row,1), (1,3)) + + row += 1 + + self.layout_bottom(sizer,row) + + def layout_bottom(self, sizer, row): + s = self.Sizer + + sizer.Add(self.expand, (row, 0), (1,3)) + row+=1 + + self.details = Clique() + + account_gui = getattr(self.protocolinfo, 'account_gui', None) + if account_gui is not None: + # Protocolmeta can specify a lazy import path to separate + # GUI components. + with traceguard: + self.add_account_gui(account_gui) + else: + getattr(self, 'build_details_' + self.formtype, + getattr(self, 'build_details_default'))(sizer,row) + + self.expand.Show(bool(self.details)) + + self.details.Show(False) + s.Add(build_button_sizer(self.save, self.cancel, border=self.save.GetDefaultBorder()), 0, EXPAND | ALL, self.GetDefaultBorder()) + + def add_account_gui(self, account_gui): + ''' + Adds account specific GUI to the "extended" section. + + account_gui must be a dotted string import path to a function + which returns a GUI component, and will be called with two + arguments: this dialog, and the account we're editing/creating. + ''' + + log.info('loading account GUI from %r', account_gui) + self.details_panel = import_function(account_gui)(self, self.account) + self.details.add(self.details_panel) + self.info_callbacks += lambda info: info.update(self.details_panel.info()) + + self.Sizer.Add(self.details_panel, 0, EXPAND | ALL, self.GetDefaultBorder()) + + def layout_email(self, gridbag, row): + add = gridbag.Add + + # email Server, Port + servertype = getattr(self.protocolinfo, 'needs_server', None) + if servertype is not None: + add(wx.StaticLine(self),(row,0),(1,4),flag = EXPAND) + + row +=1 + + add(StaticText(self, -1, '&%s Server:' % _(servertype), + style = ALIGN_RIGHT), (row, 0), flag = centerright | ALL, border = self.GetDefaultBorder()) + add(self.emailserver, (row, 1), flag = ALL, border = self.emailserver.GetDefaultBorder()) + add(StaticText(self, -1, 'P&ort:', style = ALIGN_RIGHT), (row, 2), flag = centerright | ALL, border = self.GetDefaultBorder()) + add(self.emailport, (row, 3), flag = ALL, border = self.emailport.GetDefaultBorder()) + row += 1 + + # This server requires SSL + add(self.require_ssl, (row, 1), flag = ALL, border = self.require_ssl.GetDefaultBorder()) + row += 1 + + if getattr(self.protocolinfo, 'needs_smtp', False): + add(StaticText(self, -1, _('SMTP Server:'), style = ALIGN_RIGHT), (row, 0), flag = centerright | ALL, border = self.GetDefaultBorder()) + add(self.smtp_server, (row, 1), flag = ALL, border = self.smtp_server.GetDefaultBorder()) + add(StaticText(self, -1, _('Port:'), style = ALIGN_RIGHT), (row, 2), flag = centerright | ALL, border = self.GetDefaultBorder()) + add(self.smtp_port, (row, 3), flag = ALL, border = self.smtp_port.GetDefaultBorder()) + row += 1 + + add(self.smtp_require_ssl, (row, 1), flag = ALL, border = self.smtp_require_ssl.GetDefaultBorder()) + row += 1 + + self.layout_default(gridbag, row) + + def build_details_default(self,sizer,row): + + Txt = lambda s: StaticText(self, -1, _(s)) + + details = self.details + + add = lambda i, *a, **k: (sizer.Add(i, *a, **k),details.add(i)) + + # position span + if hasattr(self, 'host'): + add(Txt(_('Host:')), (row, 0), flag = ALIGN_RIGHT|ALIGN_CENTER_VERTICAL|ALL, border = self.GetDefaultBorder()) + add( self.host, (row, 1), flag = EXPAND|ALL, border = self.host.GetDefaultBorder()) + add(Txt(_('Port:')), (row, 2), flag = ALIGN_RIGHT|ALIGN_CENTER_VERTICAL|ALL, border = self.GetDefaultBorder()) + add( self.port, (row, 3), flag = EXPAND|ALL, border = self.port.GetDefaultBorder()) + row += 1 + + if hasattr(self, 'resource'): + add(Txt(_('Resource:')), (row, 0), flag = ALIGN_RIGHT|ALIGN_CENTER_VERTICAL|ALL, border = self.GetDefaultBorder()) + add( self.resource, (row, 1), flag = EXPAND|ALL, border = self.resource.GetDefaultBorder()) + add(Txt(_('Priority:')), (row, 2), flag = ALIGN_RIGHT|ALIGN_CENTER_VERTICAL|ALL, border = self.GetDefaultBorder()) + add( self.priority, (row, 3), flag = EXPAND|ALL, border = self.priority.GetDefaultBorder()) + row += 1 + + if hasattr(self, 'dataproxy'): + add(Txt(_('Data Proxy:')), (row, 0), flag = ALIGN_RIGHT|ALIGN_CENTER_VERTICAL|ALL, border = self.GetDefaultBorder()) + add( self.dataproxy, (row, 1), (1, 3), flag = EXPAND|ALL, border = self.dataproxy.GetDefaultBorder()) + row += 1 + + sub2 = BoxSizer(wx.HORIZONTAL) + col1 = BoxSizer(wx.VERTICAL) + col2 = BoxSizer(wx.VERTICAL) + +# add = lambda c,r,f,p,s: (details.add(c),s.Add(c,r,f,p)) + + for d in getattr(self.protocolinfo, 'more_details', []): + type_ = d['type'] + name = d['store'] + if type_ == 'bool': + ctrl = wx.CheckBox(self, -1, d['label']) + setattr(self, name, ctrl) + + ctrl.SetValue(bool(getattr(self.account, name))) + details.add(ctrl) + col2.Add(ctrl, 0, ALL, ctrl.GetDefaultBorder()) + elif type_ == 'enum': + ctrl = RadioPanel(self, d['elements'], details) + setattr(self, name, ctrl) + + ctrl.SetValue(getattr(self.account, name)) + col1.Add(ctrl, 0, ALL, self.GetDefaultBorder()) + + sub2.Add(col1,0,wx.RIGHT) + sub2.Add(col2,0,wx.LEFT) + + self.Sizer.Add(sub2, 0, wx.ALIGN_CENTER_HORIZONTAL) + +def choicefor(parent, choices, callback, current_value = None): + ''' + Returns a wxChoice for a set of choice tuples that calls "callback" when + it changes. + ''' + + prefnames, displaynames = zip(*choices) + prefnames = list(prefnames) + + def on_choice(e): + callback(prefnames[e.GetInt()]) + + c = wx.Choice(parent, choices = displaynames) + c.Selection = prefnames.index(current_value) if current_value in prefnames else 0 + c.Bind(wx.EVT_CHOICE, on_choice) + c.SetPref = lambda c: setattr(c, 'Selection', prefnames.index(c) if c in prefnames else 0) + + return c + + + +class emptystringer(object): + def __init__(self, defaults = None): + self.defaults = {} if defaults is None else defaults + + def __getattr__(self, a): + return self.defaults.get(a, '') + + def _decryptedpw(self): + return '' + + def __nonzero__(self): + return False + +# centered, right and left +cr = lambda c: (c, 0, ALIGN_CENTER_VERTICAL | ALIGN_RIGHT) +cl = lambda c: (c, 0, ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + + +from gui.toolbox import OKCancelDialog + +class LaunchURLDialog(OKCancelDialog): + ''' + email accounts let you specify custom URLs for inbox and compose actions. + this dialog lets you enter those URLs. + ''' + + MINSIZE = (350, 1) + + inbox_tooltip = _('Enter the URL that will be launched when you click "Inbox" for this email account.') + compose_tooltip = _('Enter the URL that will be launched when you click "Compose" for this email account.') + + def __init__(self, parent, inbox_url = None, compose_url = None): + OKCancelDialog.__init__(self, parent, title=_('Launch URL')) + + self.construct(inbox_url, compose_url) + self.layout() + + @property + def InboxURL(self): return self.inbox_text.Value + + @property + def ComposeURL(self): return self.compose_text.Value + + def construct(self, inbox_url = None, compose_url = None): + # construct GUI + self.inbox_label = StaticText(self, -1, _('Enter a URL for the Inbox')) + self.inbox_text = TextCtrl(self, -1, inbox_url or '') + + self.compose_label = StaticText(self, -1, _('Enter a URL for the Compose window')) + self.compose_text = TextCtrl(self, -1, compose_url or '') + + # add tooltips + self.inbox_label.SetToolTipString(self.inbox_tooltip) + self.inbox_text.SetToolTipString(self.inbox_tooltip) + self.compose_label.SetToolTipString(self.compose_tooltip) + self.compose_text.SetToolTipString(self.compose_tooltip) + + # connect event handlers for disabling OK when there is missing + # content. + self.inbox_text.Bind(wx.EVT_TEXT, self.on_text) + self.compose_text.Bind(wx.EVT_TEXT, self.on_text) + self.on_text() + + def on_text(self, e = None): + if e is not None: + e.Skip() + + self.OKButton.Enable(bool(self.inbox_text.Value and self.compose_text.Value)) + + def layout(self): + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.AddMany([ + (self.inbox_label, 0, EXPAND | BOTTOM | TOP, 5), + (self.inbox_text, 0, EXPAND | LEFT, 7), + (self.compose_label, 0, EXPAND | BOTTOM | TOP, 5), + (self.compose_text, 0, EXPAND | LEFT, 7), + self.MINSIZE, + ]) + + self.set_component(sizer) + + self.Fit() + diff --git a/digsby/src/gui/accountslist.py b/digsby/src/gui/accountslist.py new file mode 100644 index 0000000..a05c9cc --- /dev/null +++ b/digsby/src/gui/accountslist.py @@ -0,0 +1,474 @@ +''' +Accounts GUI + +For displaying accounts (IM, email, and social) in the Preference window. +''' + +from __future__ import with_statement +import wx +from contextlib import contextmanager +from gui.uberwidgets.umenu import UMenu +from .anylists import AnyRow, AnyList +from .accountdialog import AccountPrefsDialog +from . import skin +from common import profile, StateMixin + +import digsbyprofile +import common + +from logging import getLogger; log = getLogger('accountslist'); info = log.info +from .toolbox.refreshtimer import refreshtimer + +class AccountRow(AnyRow): + + def __init__(self, *a, **k): +# self.ChildPaints = Delegate() # for cleartext + AnyRow.__init__(self, *a, **k) + +# def _paint(self, e): +# unused_dc = AnyRow._paint(self, e) +# return unused_dc +# self.ChildPaints(dc) + + def ConstructMore(self): + + # Extra component--the edit hyperlink + edit = self.edit = wx.HyperlinkCtrl(self, -1, _('Edit'), '#') + edit.Hide() + edit.Bind(wx.EVT_HYPERLINK, lambda e: self.on_edit()) + + remove = self.remove = wx.HyperlinkCtrl(self, -1, _('Remove'), '#') + remove.Hide() + remove.Bind(wx.EVT_HYPERLINK, lambda e: self.on_delete()) + + edit.HoverColour = edit.VisitedColour = edit.ForegroundColour + remove.HoverColour = remove.VisitedColour = remove.ForegroundColour + + def LayoutMore(self, sizer): + sizer.AddStretchSpacer() + sizer.Add(self.edit, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) + sizer.Add(self.remove, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) + +class AccountList(AnyList): + 'Base class for IM, Email, and Social account lists.' + + def __init__(self, parent, accounts, row_control, edit_buttons, velocity=None): + AnyList.__init__(self, parent, accounts, + row_control = row_control, + edit_buttons = edit_buttons, + velocity=velocity, + ) + + self.show_selected = False + + Bind = self.Bind + Bind(wx.EVT_LISTBOX_DCLICK, self.on_doubleclick) + Bind(wx.EVT_LIST_ITEM_FOCUSED,self.OnHoveredChanged) + + @contextmanager + def create_account_dialog(self, protocol_name): + with AccountPrefsDialog.create_new(self.Top, protocol_name = protocol_name) as diag: + diag.CenterOnParent() + yield diag + + def on_doubleclick(self, e): + try: + row = self.rows[e.Int] + except IndexError: + return None + else: + row.on_edit() + + def on_edit(self, account): + profile.account_manager.edit(account) + + def OnHoveredChanged(self,e): + row = self.GetRow(e.Int) + + if row: + if row.IsHovered(): + row.edit.Show() + row.remove.Show() + row.Layout() + row.Refresh() + else: + row.edit.Hide() + row.remove.Hide() + row.Layout() + + def on_data_changed(self, *args): + oldrowcount = len(self.rows) + AnyList.on_data_changed(self, *args) + newrowcount = len(self.rows) + + # when adding a new account, scroll to the bottom + if oldrowcount < newrowcount: + wx.CallAfter(self.Scroll, 0, self.VirtualSize.height) + +link_states = ('Hover', 'Normal', 'Visited') + +class IMAccountRow(AccountRow): + 'One row in the accounts list.' + + icon_cache = {} + + def __init__(self, *a, **k): + refreshtimer().Register(self) + + AccountRow.__init__(self, *a, **k) + + @property + def popup(self): + if not hasattr(self,'_menu') or not self._menu: + self._menu = menu = UMenu(self) + else: + menu = self._menu + menu.RemoveAllItems() + + menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) + menu.AddItem(_('&Remove'), callback = lambda: self.on_delete()) + + menu.AddSep() + if self.data.connection: + common.actions.menu(self, self.data.connection, menu) + common.actions.menu(self, self.data, menu) + else: + menu.AddItem(_('&Connect'), callback = lambda: self.data.connect()) + + return menu + + def PopulateControls(self, account): + # Name label + self.text = self.acct_text(account) + + # Checkbox + # if we're not connected or disconnected, we must be somewhere in the middle, + # so don't alter the checkbox + conn = account.connection + + if conn is None and account.offline_reason != StateMixin.Reasons.WILL_RECONNECT: + self.checkbox.Value = False + elif conn is not None and conn.state == conn.Statuses.ONLINE: + self.checkbox.Value = True + else: + self.checkbox.Set3StateValue(wx.CHK_UNDETERMINED) + + def acct_text(self, account = None): + if account is None: account = self.data + + text = account.name + + note = profile.account_manager.state_desc(account) + if note: text = text + (' (%s)' % note) + + return text + + @property + def image(self): + return skin.get('serviceicons.%s' % self.data.protocol).PIL.Resized(24).WXB + + @property + def online_bitmap(self): + ''' + Returns a red or green light depending on if this account is connected. + ''' + + conn = self.data.connection + state = 'Online' if conn and conn.is_connected else 'Offline' + return skin.get(state + '.icon') + + + def on_close(self, *a,**k): + refreshtimer().UnRegister(self) + + AccountRow.on_close(self, *a, **k) + + def Refresh(self, *a, **k): + self.text = self.acct_text() + AccountRow.Refresh(self, *a, **k) + + + +class IMAccountsList(AccountList): + 'The accounts list.' + + def __init__(self, parent, accounts, edit_buttons = None): + AccountList.__init__(self, parent, accounts, + row_control = IMAccountRow, edit_buttons = edit_buttons, + velocity = 150, + ) + + # Clicking an account's checkbox toggles its connected state + Bind = self.Bind + Bind(wx.EVT_CHECKBOX, self.on_account_checked) + # And double clicking an element pops up the details dialog +# Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_selected) +# Bind(wx.EVT_LIST_ITEM_DESELECTED, self.on_deselected) + Bind(wx.EVT_KILL_FOCUS, self.on_kill_focus) + + + def on_kill_focus(self, e): + self.Layout() + self.Refresh() + + def on_account_checked(self, e): + 'A checkbox next to one of the accounts has been clicked.' + + row = e.EventObject.Parent + account = row.data + + if row.IsChecked(): + call = account.connect + row.checkbox.Set3StateValue(wx.CHK_UNDETERMINED) + else: + call = lambda: profile.account_manager.cancel_reconnect(account) + row.checkbox.SetValue(False) + + wx.CallAfter(call) + + def OnNew(self, e = None): + 'Called when the plus button above this list is clicked.' + + self.addmenu.PopupMenu() + + def OnDelete(self, acct): + 'Called when the minus button above this list is clicked.' + +# acct = self.GetDataObject(self.Selection) + if not acct: return + + # Display a confirmation dialog. + message = _('Are you sure you want to delete account "{name}"?').format(name=acct.name) + caption = _('Delete Account') + style = wx.ICON_QUESTION | wx.YES_NO + parent = self + + import jabber + if acct.protocol_class() == jabber.protocol: + from gui.protocols.jabbergui import JabberDeleteConfirmBox + msgbox = JabberDeleteConfirmBox(acct, message, parent, title=caption) + else: + msgbox = wx.MessageDialog(parent, message, caption, style) + + try: + if msgbox.ShowModal() == wx.ID_YES: + profile.remove_account(acct) + finally: + msgbox.Destroy() + + def add_account(self, protocol_name): +# protocolinfo = digsbyprofile.protocols[protocol_name] + with self.create_account_dialog(protocol_name) as diag: + unusedres = diag.ShowModal() + + if diag.ReturnCode == wx.ID_SAVE: + # this results in a network operation that may fail... + profile.add_account(**diag.info()) + + # but for now, just show the account + self.on_data_changed() + + @property + def addmenu(self): + try: + return self.add_popup + except AttributeError: + self.add_popup = menu = UMenu(self) + + protocols = common.protocolmeta.protocols + improtocols = common.protocolmeta.improtocols + + for p in improtocols.keys(): + menu.AddItem(protocols[p].name, callback = lambda p=p: self.add_account(p), + bitmap = skin.get("serviceicons." + p, None)) + return menu + +# ---------------- +class SocialRow(AccountRow): + checkbox_border = 3 + row_height = 20 + image_offset = (6,0) + + def PopulateControls(self, account): + self.text = account.display_name + self.checkbox.Value = bool(account.enabled) + + @property + def image(self): + img = skin.get("serviceicons." + self.data.protocol, None) + return img.Resized(16) if img else None + + @property + def popup(self): + if not hasattr(self,'_menu') or not self._menu: + self._menu = menu = UMenu(self) + else: + menu = self._menu + menu.RemoveAllItems() + + menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) + menu.AddItem(_('&Remove'), callback = lambda: self.on_delete()) + + if self.data.enabled: + menu.AddSep() + common.actions.menu(self, self.data, menu) + + return menu + +class SocialList(AccountList): + def __init__(self, parent, accounts, edit_buttons = None): + AccountList.__init__(self, parent, accounts, + row_control = SocialRow, edit_buttons = edit_buttons, + velocity = 100, + ) + + self.Bind(wx.EVT_CHECKBOX, self.on_account_checked) + + def on_account_checked(self, e): + account = e.EventObject.Parent.data + checked = e.EventObject.Value + account.setnotify('enabled', checked) + wx.CallAfter(account.update_info) + + def OnDelete(self, acct): + 'Called when the minus button above this list is clicked.' + + if acct is None: + return + + # Display a confirmation dialog. + message = _('Are you sure you want to delete social network account "{name}"?').format(name=acct.name) + caption = _('Delete Social Network Account') + style = wx.ICON_QUESTION | wx.YES_NO + parent = self + + if wx.YES == wx.MessageBox(message, caption, style, parent): + log.info('removing account %r' % acct) + profile.remove_social_account(acct) + + def OnNew(self, e = None): + 'Called when the plus button above this list is clicked.' + self.addmenu.PopupMenu() + + def add_account(self, protocol_name): + with self.create_account_dialog(protocol_name) as diag: + unusedres = diag.ShowModal() + if diag.ReturnCode == wx.ID_SAVE: + acct = profile.add_social_account(**diag.info()) + on_create = getattr(acct, 'onCreate', None) #CamelCase for GUI code + if on_create is not None: + on_create() + + @property + def addmenu(self): + try: + return self.add_popup + except AttributeError: + self.add_popup = menu = UMenu(self) + + protocols = common.protocolmeta.protocols + socialprotocols = common.protocolmeta.socialprotocols + + for p in socialprotocols.keys(): + menu.AddItem(protocols[p].name, callback = lambda p=p: self.add_account(p), + bitmap = skin.get('serviceicons.' + p)) + + return menu + +# ---------------- + +class EmailRow(AccountRow): + + checkbox_border = 3 + row_height = 20 + image_offset = (6, 0) + + def PopulateControls(self, account): + self.text = account.display_name + self.checkbox.Value = bool(account.enabled) + + @property + def image(self): + icon = getattr(self.data, 'icon', None) + if icon is not None: + icon = icon.Resized(16) + + return icon + + @property + def popup(self): + if not hasattr(self,'_menu') or not self._menu: + self._menu = menu = UMenu(self) + else: + menu = self._menu + menu.RemoveAllItems() + + menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) + menu.AddItem(_('&Remove'), callback = lambda: self.on_delete()) + + from common.emailaccount import EmailAccount + if self.data.enabled: + menu.AddSep() + common.actions.menu(self, self.data, menu, cls = EmailAccount) + + return menu + +class EmailList(AccountList): + 'Email accounts list.' + + def __init__(self, parent, accounts, edit_buttons = None): + AccountList.__init__(self, parent, accounts, + row_control = EmailRow, edit_buttons = edit_buttons, + velocity = 100, + ) + + self.Bind(wx.EVT_CHECKBOX, self.on_account_checked) + + def on_account_checked(self, e): + eo = e.EventObject + account, checked = eo.Parent.data, eo.Value + account.setnotifyif('enabled', checked) + wx.CallAfter(account.update_info) + + def OnDelete(self, acct): + 'Called when the minus button above this list is clicked.' + +# acct = self.GetDataObject(self.Selection) + if acct is None: return + + # Display a confirmation dialog. + message = _('Are you sure you want to delete email account "{account_name}"?').format(account_name=acct.name) + caption = _('Delete Email Account') + style = wx.ICON_QUESTION | wx.YES_NO + parent = self + + if wx.MessageBox(message, caption, style, parent) == wx.YES: + profile.remove_email_account(acct) + + + def OnNew(self, e = None): + 'Called when the plus button above this list is clicked.' + self.addmenu.PopupMenu() + + def add_account(self, protocol_name): + with self.create_account_dialog(protocol_name) as diag: + unusedres = diag.ShowModal() + if diag.ReturnCode == wx.ID_SAVE: + info = diag.info() + common.profile.add_email_account(**info) + import hooks + hooks.notify('digsby.email.new_account', parent = self.Top, **info) + + @property + def addmenu(self): + try: + return self.add_popup + except AttributeError: + self.add_popup = menu = UMenu(self) + protocols = common.protocolmeta.protocols + emailprotocols = common.protocolmeta.emailprotocols + + for p in emailprotocols.keys(): + menu.AddItem(protocols[p].name, callback = lambda p=p: self.add_account(p), + bitmap = skin.get('serviceicons.' + p, None)) + return menu diff --git a/digsby/src/gui/accountwizard.py b/digsby/src/gui/accountwizard.py new file mode 100644 index 0000000..228d999 --- /dev/null +++ b/digsby/src/gui/accountwizard.py @@ -0,0 +1,134 @@ +''' +Account wizard. +''' + +import wx +from gui.pref import pg_accounts +from gui import skin +from gui.native.win.winutil import is_vista +import traceback + +import util.primitives.funcs as utilfuncs + +def show(): + if not AccountWizard.RaiseExisting(): + w = AccountWizard() + w.CenterOnScreen() + w.Show() + +def bind_paint(ctrl, paint): + def on_paint(e): + dc = wx.AutoBufferedPaintDC(ctrl) + return paint(dc) + + ctrl.Bind(wx.EVT_PAINT, on_paint) + +class AccountWizard(wx.Frame): + MIN_SIZE = (567, 569) + + def __init__(self, parent=None): + wx.Frame.__init__(self, parent, -1, + title=_('Digsby Setup Wizard')) + + self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon')) + self.Bind(wx.EVT_CLOSE, self.on_close) + big_panel = wx.Panel(self) + + # header + header = wx.Panel(big_panel) + header.SetBackgroundColour(wx.Colour(244, 249, 251)) + hdr1 = wx.StaticText(header, -1, _('Welcome to Digsby!')) + set_font(hdr1, 18, True) + + elems = \ + [(False, 'All your '), + (True, 'IM'), + (False, ', '), + (True, 'Email'), + (False, ' and '), + (True, 'Social Network'), + (False, ' accounts under one roof.')] + + txts = [] + for emphasis, text in elems: + txt = wx.StaticText(header, -1, text) + set_font(txt, 12, bold=emphasis, underline=False) + txts.append(txt) + + txt_sizer = wx.BoxSizer(wx.HORIZONTAL) + txt_sizer.AddMany(txts) + + icon = skin.get('AppDefaults.TaskBarIcon').PIL.Resized(48).WXB + bind_paint(header, lambda dc: dc.DrawBitmap(icon, 5, 3, True)) + icon_pad = icon.Width + 6 + + header.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + sz.AddMany([(hdr1, 0, wx.EXPAND | wx.LEFT, 6 + icon_pad), + (3, 3), + (txt_sizer, 0, wx.EXPAND | wx.LEFT, 6 + icon_pad)]) + + # accounts panel + panel = wx.Panel(big_panel) + panel.BackgroundColour = wx.WHITE + + panel.Sizer = sizer = wx.BoxSizer(wx.VERTICAL) + self.exithooks = utilfuncs.Delegate() + pg_accounts.panel(panel, sizer, None, self.exithooks) + + # paint the background + line + def paint(e): + dc = wx.AutoBufferedPaintDC(big_panel) + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + r = big_panel.ClientRect + dc.DrawRectangleRect(r) + dc.Brush = wx.Brush(header.BackgroundColour) + + y = header.Size.height + 19 + dc.DrawRectangle(0, 0, r.width, y) + dc.Brush = wx.Brush(wx.BLACK) + dc.DrawRectangle(0, y, r.width, 1) + + big_panel.BackgroundStyle = wx.BG_STYLE_CUSTOM + big_panel.Bind(wx.EVT_PAINT, paint) + + # Done button +# button_sizer = wx.BoxSizer(wx.HORIZONTAL) +# button_sizer.AddStretchSpacer(1) +# done = wx.Button(panel, -1, _('&Done')) +# done.Bind(wx.EVT_BUTTON, lambda e: self.Close()) +# button_sizer.Add(done, 0, wx.EXPAND) +# sizer.Add(button_sizer, 0, wx.EXPAND | wx.TOP, 10) + + big_panel.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + sz.Add(header, 0, wx.EXPAND | wx.ALL, 8) + sz.Add((5, 5)) + sz.Add(panel, 1, wx.EXPAND | wx.ALL, 12) + + self.SetMinSize(self.MIN_SIZE) + self.SetSize(self.MIN_SIZE) + + def on_close(self, e): + def show_hint(): + icons = wx.GetApp().buddy_frame.buddyListPanel.tray_icons + if icons: + icon = icons[0][1] + icon.ShowBalloon(_('Quick Access to Newsfeeds'), + _('\nYou can access social network and email newsfeeds' + ' by clicking their icons in the tray.\n' + '\nDouble click to update your status (social networks)' + ' or launch your inbox (email accounts).\n' + ' \n'), 0, wx.ICON_INFORMATION) + wx.CallLater(300, show_hint) + if getattr(self, 'exithooks', None) is not None: + self.exithooks() + e.Skip(True) + + +def set_font(ctrl, size, bold=False, underline=False): + f = ctrl.Font + if is_vista(): f.SetFaceName('Segoe UI') + f.PointSize = size + if bold: f.Weight = wx.FONTWEIGHT_BOLD + if underline: f.SetUnderlined(True) + ctrl.Font = f diff --git a/digsby/src/gui/addcontactdialog.py b/digsby/src/gui/addcontactdialog.py new file mode 100644 index 0000000..52d9f82 --- /dev/null +++ b/digsby/src/gui/addcontactdialog.py @@ -0,0 +1,487 @@ +from common import pref +import wx +import wx.lib.sized_controls as sc + +from cgui import SimplePanel +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.simplemenu import SimpleMenuItem + +from gui.textutil import default_font +from gui.validators import LengthLimit +from util.primitives.error_handling import traceguard +from util.primitives.funcs import do +from util.primitives.structures import oset + +from common import profile +import common.protocolmeta as protocolmeta +from contacts.buddylistfilters import OfflineGroup + +from wx import EXPAND, LEFT, VERTICAL, HORIZONTAL, ALIGN_LEFT, \ + ALIGN_CENTER_VERTICAL, FULL_REPAINT_ON_RESIZE + + +bgcolors = [ + wx.Color(238, 238, 238), + wx.Color(255, 255, 255), +] + +def namebyproto(p): + return protocolmeta.protocols[p].username_desc+':' + +def ShowAddContactDialog(g): + AddContactDialog.MakeOrShow(g) + +def groupaddcontact(menu, group): + if not isinstance(group, OfflineGroup): + menu.AddItem(_('Add Contact'), callback=lambda : ShowAddContactDialog(group.name)) + +class CheckablePanel(SimplePanel): + def __init__(self,parent,name,associate,bitmap=None,checkcallback=None): + SimplePanel.__init__(self,parent,FULL_REPAINT_ON_RESIZE) + + s = self.Sizer = wx.BoxSizer(HORIZONTAL) + + self.checkcallback = checkcallback + checkbox = self.checkbox = wx.CheckBox(self,-1) + checkbox.Bind(wx.EVT_CHECKBOX,self.OnCheck) + + self.MinSize = wx.Size(-1, 22) + + s.Add(checkbox,0,LEFT|ALIGN_CENTER_VERTICAL,3) + + self.name = name + + self.associate = associate + + self.bitmap = bitmap + + + self.checkbox.Value=False + self.checkcallback(self.associate, self.checkbox.Value) + + Bind = self.Bind + Bind(wx.EVT_PAINT,self.OnPaint) + Bind(wx.EVT_LEFT_UP, lambda e: self.Check(not self.checkbox.Value)) + + + def Check(self, value = True): + self.checkbox.Value= value + self.OnCheck() + + + def OnCheck(self,event = None): + + if self.checkcallback: + self.checkcallback(self.associate,self.checkbox.Value) + + def OnPaint(self,event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + + n = self.Parent.Index(self) + dc.Brush = wx.Brush(bgcolors[n % len(bgcolors)]) + dc.Pen = wx.TRANSPARENT_PEN #@UndefinedVariable + + dc.DrawRectangleRect(rect) + + x = self.checkbox.Rect.Right+3 + w = self.Rect.Width - x - 3 + textrect = wx.Rect(x,0,w,rect.Height) + + dc.Font = default_font() + dc.DrawLabel(self.name,textrect,ALIGN_LEFT|ALIGN_CENTER_VERTICAL) + + bm = self.bitmap + + dc.DrawBitmap(bm,rect.width-(bm.Size.width+3),3) + +class CheckableList(wx.ScrolledWindow): + def __init__(self,parent,thelist = None,setchangedcallback = None): + wx.ScrolledWindow.__init__(self,parent) + + self.Sizer = wx.BoxSizer(VERTICAL) + + self.BackgroundColour = wx.WHITE #@UndefinedVariable + + self.theset = set() + self.setchangedcallback = setchangedcallback + + + self.SetScrollRate(0,1) + + if thelist != None: + self.SetList(thelist) + + self.Bind(wx.EVT_PAINT,self.OnPaint) + + def OnPaint(self,event): + + event.Skip() + + srect= wx.Rect(*self.Rect) + srect.Inflate(1,1) + pcdc = wx.ClientDC(self.Parent) + pcdc.Brush = wx.TRANSPARENT_BRUSH #@UndefinedVariable + + pcdc.Pen = wx.Pen(wx.Colour(213,213,213)) + + pcdc.DrawRectangleRect(srect) + + + def SetList(self,thelist): + + if not hasattr(self,'thelist'): + self.thelist=[] + + if thelist == self.thelist: + return + + oldset = set(self.thelist) + newset = set(thelist) + + removeset = oldset - newset + addset = newset - oldset + + self.thelist = thelist +# self.Sizer.Clear(True) + + def AddOrRemove(acct,add): + + theset = self.theset + + if add: + theset.add(acct) + elif acct in theset: + theset.remove(acct) + + print theset + + self.setchangedcallback(theset) + + for sizeritem in self.Sizer.Children: + + if sizeritem.Window: + chkitem = sizeritem.Window + + if chkitem.associate in removeset: + self.Sizer.Detach(chkitem) + chkitem.Destroy() + + self.theset -= removeset + + + + + for item in addset: + self.Sizer.Add(CheckablePanel(self,item.username,item,item.serviceicon.Resized(16),AddOrRemove),0,EXPAND) + + self.SetVirtualSize(self.Sizer.MinSize) + self.Layout() + self.Refresh() + + self.setchangedcallback(self.theset) + + def Index(self,item): + try: + return self.Children.index(item) + except: + return None + + def SelectAccount(self, account): + for child in self.Children: + if isinstance(child, CheckablePanel): + if child.associate == account: + child.Check() + else: + print "NOT THE SAME!" + print "Account:", type(account), account + print "Associate:", type(child.associate), child.associate + +no_connections_label = _('No Connections') +class AddContactDialog(sc.SizedDialog): + def __init__(self, parent=None, group='', onservice='', newname = '', onaccount = None): + sc.SizedDialog.__init__(self, parent, -1, _('Add Contact'), size = (314, 250), + style = wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.RESIZE_BORDER) + #S = self.Sizer = wx.BoxSizer(VERTICAL) + + p = self.panel = self.GetContentsPane() #wx.Panel(self) + + p.SetSizerType("form") + + self.SetButtonSizer(self.CreateButtonSizer(wx.OK | wx.CANCEL)) + + okbutton = self.okbutton = self.FindWindowById(wx.ID_OK, self) #wx.Button(p, wx.ID_OK, _('Add')) + okbutton.SetLabel(_('Add')) + okbutton.SetDefault() + okbutton.Enabled = False + cancelbutton = self.FindWindowById(wx.ID_CANCEL, self) #wx.Button(p,wx.ID_CANCEL, _('Cancel')) + +#================================================================= + + protoitems = self.MakeProtocolItems() + groupitems = self.MakeGroupItems() + + Text = lambda s: wx.StaticText(p, -1, s) + +#========================================================================================================= + R_CENTER= dict(halign='right', valign='center') + Text(_('Contact Type:')).SetSizerProps(**R_CENTER) + protocombo = self.protocombo = UberCombo(p, no_connections_label, False, None, skinkey='AppDefaults.PrefCombo') + protocombo.SetSizerProps(expand=True) + + namelabel = self.namelabel = Text(_('Screen Name:')) + namelabel.SetSizerProps(**R_CENTER) + + name = self.namefield = wx.TextCtrl(p, validator=LengthLimit(255), value=newname) + name.SetFocus() + name.Bind(wx.EVT_TEXT,self.OnNameChange) + name.SetSizerProps(expand=True) + + Text(_('Alias:')).SetSizerProps(**R_CENTER) + alias = self.aliasfield = wx.TextCtrl(p, validator=LengthLimit(255)) + alias.SetSizerProps(expand=True) + + Text(_('In Group:')).SetSizerProps(**R_CENTER) + groupcombo = self.groupcombo = UberCombo(p, group, True, None, skinkey='AppDefaults.PrefCombo') + groupcombo.display.txtfld.Bind(wx.EVT_TEXT,self.OnNameChange) + groupcombo.SetSizerProps(expand=True) + + Text(_('On Accounts:')).SetSizerProps(halign='right', valign='top') + checklist = self.accountchecks = CheckableList(p, setchangedcallback = self.OnCheckedSetChange) + checklist.MinSize = (-1, 66) + checklist.SetSizerProps(expand=True, proportion=1) + +#========================================================================================================== + defserv = 0 + if onservice: + for item in protoitems: + if item.id == onservice: + defserv = protoitems.index(item) + break + + protocombo.SetItems(protoitems, defserv) + groupcombo.SetItems(groupitems, 0 if group == '' else None) + +#=========================================================================================================== + + def onproto(*a): + self.UpdateAccounts() + name.SetFocus() + + protocombo.SetCallbacks(value = onproto) + self.UpdateAccounts() + + if onaccount is not None: + checklist.SelectAccount(onaccount) + + def NoEmptyGroup(*a): + if groupcombo.Value == '': + groupcombo.SetSelection(0) + if groupcombo.Value == '': + groupcombo.Value = pref('buddylist.fakeroot_name', default='Contacts') + groupcombo.SetCallbacks(value = NoEmptyGroup) + + self.Fit() + self.Size = (314, self.Size.y) + self.MinSize = self.Size + + okbutton.Bind(wx.EVT_BUTTON, self.OnOk) + cancelbutton.Bind(wx.EVT_BUTTON, lambda e: self.Close()) + + Bind = self.Bind + Bind(wx.EVT_CLOSE,self.OnClose) + + profile.account_manager.connected_accounts.add_observer(self.WhenOnlineAcctsChange) + profile.blist.add_observer(self.WhenGroupsChange, 'view') + + import hooks; hooks.notify('digsby.statistics.ui.dialogs.add_contact.created') + + def UpdateAccounts(self,*a): + v = self.protocombo.Value + + accts = set() + + if not isinstance(v,basestring): + proto = v.id.lower() + self.namelabel.SetLabel(namebyproto(proto)) + + + accounts = profile.account_manager.connected_accounts + + for account in accounts: + accpro = protocolmeta.SERVICE_MAP[account.protocol] + if proto in accpro: + accts.add(account) + + accts = list(accts) + self.accountchecks.SetList(accts) + + if len(accts) == 1: + self.accountchecks.SelectAccount(accts[0]) + + wx.CallAfter(self.panel.Layout) + + + def OnNameChange(self,event): + self.okbutton.Enabled = bool(self.accountchecks.theset and self.namefield.Value and (self.groupcombo.Value or self.groupcombo.display.txtfld.Value)) + event.Skip() + + def OnCheckedSetChange(self,theset): + self.okbutton.Enabled = bool(theset and self.namefield.Value and (self.groupcombo.Value or self.groupcombo.display.txtfld.Value)) + + def WhenOnlineAcctsChange(self,*a): + protocombo = self.protocombo + + protocombo.SetItems(self.MakeProtocolItems()) + + if not protocombo.Value in protocombo: + if len(protocombo): + protocombo.SetSelection(0) + else: + protocombo.Value = no_connections_label + else: + self.UpdateAccounts() + + def WhenGroupsChange(self,*a): + + self.groupcombo.SetItems(self.MakeGroupItems()) + + + + def OnOk(self,event): + + self.Show(False) + + import hooks; hooks.notify('digsby.statistics.contact_added') + proto = self.protocombo.Value.id + name = self.namefield.Value + group = self.groupcombo.Value if isinstance(self.groupcombo.Value,basestring) else self.groupcombo.Value.GetContentAsString() + alias = self.aliasfield.Value + accounts = self.accountchecks.theset + + + import hub + from common import profile + meta = False + x = None + for account in accounts: + with traceguard: + account.connection.add_new_buddy(name, group, proto, alias) + x = (name, account.connection.service) + if x in profile.blist.metacontacts.buddies_to_metas: + meta = True + + if meta: + id = list(profile.blist.metacontacts.buddies_to_metas[x])[0].id + m = profile.blist.metacontacts[id] + alias = m.alias + group = list(m.groups)[0][-1] + hub.get_instance().user_message( + "That buddy is already part of metacontact \"" + alias + + "\" in group \"" + group + "\"") + + self.Close() + + def OnClose(self,event): + profile.account_manager.connected_accounts.remove_observer(self.WhenOnlineAcctsChange) + profile.blist.remove_observer(self.WhenGroupsChange, 'view') + + self.Show(False) + + type(self).current_instance = None + + self.Destroy() + + def MakeProtocolItems(self): + set = oset + if not hasattr(self,'protoitems'): + self.protoitems = set() + + protoitems = self.protoitems + + oldprotocols = set(p.id for p in protoitems) + +#============================================================================== + + accounts = profile.account_manager.connected_accounts + protocols = set() + protocols2 = set() + do(protocols.add(account.protocol) for account in accounts) + + for proto in protocols: + try: + protocols2.update(protocolmeta.SERVICE_MAP[proto]) + except: + pass + + protocols = protocols2 + +#============================================================================== + + for account in accounts: + if not account.allow_contact_add: + protocols.discard(account.protocol) + + removeprotos = oldprotocols - protocols + + addprotos = protocols - oldprotocols + +#============================================================================== + + for item in set(protoitems): + if item.id in removeprotos: + protoitems.remove(item) + + for protocol in addprotos: + skinval = skin.get('serviceicons.%s'%protocol, None) + if skinval is not None: + skinval = skinval.Resized(16) + protoval = protocolmeta.protocols.get(protocol, None) + protoval = protoval.name if protoval is not None else protocol + protoitems.add(SimpleMenuItem([skinval, protoval],id = protocol)) + + return list(protoitems) + + def MakeGroupItems(self): + accounts = profile.account_manager.connected_accounts + groups = set() + groups.add(pref('buddylist.fakeroot_name', default='Contacts')) + do(groups.update(account.connection.get_groups()) + for account in accounts if getattr(account, 'connection', None) is not None) + + return [SimpleMenuItem(group) for group in list(sorted(groups,key = lambda g: g.lower()))] + + + @classmethod + def MakeOrShow(cls,group='', service = '', name = '', account = None): + + if not hasattr(cls,'current_instance'): + cls.current_instance = None + + if cls.current_instance: + cls.current_instance.Show(True) + cls.current_instance.Raise() + else: + cls.current_instance = AddContactDialog(None, group, service, name, account) + cls.current_instance.Show(True) + + import hooks; hooks.notify('digsby.statistics.ui.dialogs.add_contact.shown') + +#========================================================================== + +from gui import skin + +class FakeAccount(object): + def __init__(self,username,serviceicon): + self.username = username + self.serviceicon = serviceicon + + +if __name__ == '__main__': + from tests.testapp import testapp + app = testapp('../../') + + f=AddContactDialog() + f.Show(True) + + app.MainLoop() diff --git a/digsby/src/gui/alphaborder/__init__.py b/digsby/src/gui/alphaborder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/alphaborder/alphaborder.py b/digsby/src/gui/alphaborder/alphaborder.py new file mode 100644 index 0000000..b9df643 --- /dev/null +++ b/digsby/src/gui/alphaborder/alphaborder.py @@ -0,0 +1,59 @@ +import wx +from wx import Point, Size, TOP, BOTTOM, LEFT, RIGHT, Frame, RectPS, Rect +from gui.windowfx import resize_smoothly, move_smoothly, setalpha + +from cgui import AlphaBorder, BorderedFrame, fadein, fadeout +from gui.toolbox import Monitor +from wx import FindWindowAtPointer +from operator import attrgetter +from gui import skin +from util.primitives.funcs import Delegate +from util.primitives.mapping import Storage as S + +def main2(): + from tests.testapp import testapp + a = testapp('../../..') + + frames = [wx.Frame(None, -1, 'test %d' % c) for c in xrange(5)] + + stack = PopupStack(1, BOTTOM | LEFT) + stack.Add(frames[0]) + + +def main(): + from tests.testapp import testapp + a = testapp('../../..') + from gui.skin.skinparse import makeImage + + b = makeImage('popupshadow.png 12 12 25 25') + #slices = (12, 12, 25, 25) + + f = Popup(b) + + def onbutton(e): + #f.Fit() + f2 = Popup(b) + f2.Size = (300, 100) + f2.DesiredSize = Size(300, 100) + stack.Add(f2) + + f.button.Bind(wx.EVT_BUTTON, onbutton) + + stack = PopupStack(1, BOTTOM | LEFT) + f.DesiredSize = Size(300, 100) + stack.Add(f) + + ctrl = wx.Frame(None) + #slider = wx.Slider(ctrl, value = 255, minValue = 0, maxValue = 255) + #def onslide(e): + # setalpha(f, slider.Value) + # f.border.Refresh() + + #f.SetTransparent(slider.Value) + #slider.Bind(wx.EVT_SLIDER, onslide) + ctrl.Show() + + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/alphaborder/alphawin.py b/digsby/src/gui/alphaborder/alphawin.py new file mode 100644 index 0000000..ed68147 --- /dev/null +++ b/digsby/src/gui/alphaborder/alphawin.py @@ -0,0 +1,84 @@ +import wx + +from ctypes import windll, Structure, c_byte, c_long, c_int, byref, WinError, GetLastError + +user32, kernel32 = windll.user32, windll.kernel32 + +GetDC = user32.GetDC +UpdateLayeredWindow = user32.UpdateLayeredWindow + +AC_SRC_OVER = 0 +AC_SRC_ALPHA = 1 +ULW_ALPHA = 2 +WS_EX_LAYERED = 0x00080000 + + +class CPoint(Structure): + _fields_ = (('x', c_long), + ('y', c_long)) +class BLENDFUNCTION(Structure): + ''' + The BLENDFUNCTION structure controls blending by specifying the blending + functions for source and destination bitmaps. + ''' + + # see http://msdn2.microsoft.com/en-us/library/ms532306.aspx + + _fields_ = (('BlendOp', c_byte), + ('BlendFlags', c_byte), + ('SourceConstantAlpha', c_byte), + ('AlphaFormat', c_byte)) + +def setLayered(win, layered): + hwnd = win.Handle + style = user32.GetWindowLongA(hwnd, 0xffffffecL) + oldlayered = bool(WS_EX_LAYERED & style) + + if layered == oldlayered: return + + if layered: style |= WS_EX_LAYERED + else: style &= ~WS_EX_LAYERED + + user32.SetWindowLongA(hwnd, 0xffffffecL, style) + +def makeBlendFunction(alpha): + if not isinstance(alpha, int) or alpha < 0 or alpha > 255: + raise TypeError('alpha must be an integer from 0 to 255') + + f = BLENDFUNCTION() + f.BlendOp = AC_SRC_OVER + f.BlendFlags = 0 + f.SourceConstantAlpha = alpha + f.AlphaFormat = AC_SRC_ALPHA + + return f + +def ApplyAlpha(win, bitmap, sourceAlpha = 255): + setLayered(win, True) + + r = win.Rect + pos = CPoint(); pos.x, pos.y = r[:2] + size = CPoint(); size.x, size.y = r[2:] + + memdc = wx.MemoryDC(bitmap) + + imgpos = CPoint(); imgpos.x = imgpos.y = 0 + + colorkey = c_int(0) + blendPixelFunction = makeBlendFunction(sourceAlpha) + + res = UpdateLayeredWindow(win.Handle, + GetDC(None), + byref(pos), + byref(size), + memdc.GetHDC(), + byref(imgpos), + colorkey, + byref(blendPixelFunction), + ULW_ALPHA) + + if not res: + raise WinError(GetLastError()) + + + memdc.SelectObject(wx.NullBitmap) \ No newline at end of file diff --git a/digsby/src/gui/animation.py b/digsby/src/gui/animation.py new file mode 100644 index 0000000..79a9191 --- /dev/null +++ b/digsby/src/gui/animation.py @@ -0,0 +1,112 @@ +''' +simple animation class +''' + +from time import time +from wx import PyTimer as timer + +class Listenable(object): + __slots__ = ('listeners',) + + def __init__(self): + self.listeners = [] + + def add_listener(self, listener): + self.listeners.append(listener) + + def remove_listener(self, listener): + self.listeners.remove(listener) + + def _notify_listeners(self): + for listener in self.listeners: + listener() + +class Animation(Listenable): + __slots__ = ('frames', 'frame_delays', 'timer', 'index', 'started') + + def __init__(self, frames = None, frame_delays = None): + Listenable.__init__(self) + + if frames is None: + frames = [] + if frame_delays is None: + frame_delays = [] + + self.frames = frames + self.frame_delays = frame_delays + self.index = 0 + + self.timer = timer(self._on_timer) + + def set_frames(self, frames, delays): + assert all(isinstance(d, int) for d in delays) + + if self.frames: + frame, delay = self.current_frame, self.current_frame_delay + else: + frame, delay = None, None + + old_frame_len = len(self.frames) + + assert frames, repr(frames) + assert delays, repr(delays) + + self.frames = frames[:] + self.frame_delays = delays[:] + self.update_frame() + + # notify if our current frame is different + if frame != self.current_frame: + self._notify_listeners() + + # start if we only had one frame before, or if + # the current delay is different. + if (old_frame_len == 1 and len(frames) > 1) or \ + delay != self.current_frame_delay: + + if frame is None or old_frame_len == 1: + self.start() + else: + diff = self.current_frame_delay - delay + self.start(delay - abs(diff)) + + # stop if only one frame + if len(self.frames) == 1: + self.stop() + + def start(self, delay = None): + if delay is None: + delay = self.current_frame_delay + + self.started = time() + self.timer.Start(delay, True) + + def stop(self): + self.timer.Stop() + + def increment_frame(self): + self.index += 1 + self.update_frame() + + def update_frame(self): + if self.index > len(self.frames)-1: + self.index = 0 + + @property + def current_frame_delay(self): + return self.frame_delays[self.index] + + @property + def current_frame(self): + return self.frames[self.index] + + def set_frame(self, i, frame): + self.frames[i] = frame + if self.index == i: + self._notify_listeners() + + def _on_timer(self): + self.increment_frame() + self._notify_listeners() + self.start() + diff --git a/digsby/src/gui/anylists.py b/digsby/src/gui/anylists.py new file mode 100644 index 0000000..849b869 --- /dev/null +++ b/digsby/src/gui/anylists.py @@ -0,0 +1,946 @@ +''' + +Cross-platform lists with arbitrary controls backed by data models of any type. + +''' + +from __future__ import with_statement +from util.primitives.structures import oset + +import wx +from wx.lib.scrolledpanel import ScrolledPanel +from wx import FindWindowAtPointer, RectS, wxEVT_MOTION, wxEVT_MOUSE_CAPTURE_LOST + +from gui.skin.skinobjects import SkinColor +from gui.textutil import default_font +from util.primitives.error_handling import traceguard +from util.primitives.misc import clamp +from gui.uberwidgets.umenu import UMenu + +import cPickle +from gui.toolbox.scrolling import WheelScrollMixin + +editable_controls = (wx.CheckBox, wx.HyperlinkCtrl, wx.StaticBitmap) +syscol = wx.SystemSettings_GetColour + +from logging import getLogger; log = getLogger('anylists'); info = log.info; warning = log.warning +from util import default_timer + +bgcolors = [ + wx.Color(238, 238, 238), + wx.Color(255, 255, 255), +] + +selbgcolor = wx.Color(180, 180, 180) +hovbgcolor = wx.Color(220, 220, 220) + +class AnyRow(wx.Panel): + 'One row in an AnyList.' + + checkbox_border = 10 + row_height = 40 + + def __init__(self, parent, data, use_checkbox = True, linkobservers = True): + wx.Panel.__init__(self, parent, style = wx.FULL_REPAINT_ON_RESIZE) +# self.text_controls = [] + oldChildren = set(self.Children) + self.data = data + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.selectedbg = SkinColor(syscol(wx.SYS_COLOUR_HIGHLIGHT)) + self.bg = SkinColor(wx.WHITE) + + self.padding = wx.Point(5, 5) + + # Construct and layout GUI elements + self.construct(use_checkbox) + self.layout() + + # Observe the data object for changes + if linkobservers: + try: + data.add_observer(self.on_data_changed) + except AttributeError: + pass + self.on_data_changed(data) + + # Bind events + Bind = self.Bind + Bind(wx.EVT_PAINT, self._paint) + Bind(wx.EVT_ERASE_BACKGROUND, lambda e: e.Skip(False)) + Bind(wx.EVT_MOTION, self.on_motion) + Bind(wx.EVT_KILL_FOCUS, self.Parent.on_lost_focus) + Bind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) + Bind(wx.EVT_RIGHT_UP, self.on_right_up) + Bind(wx.EVT_RIGHT_DOWN, self.on_right_down) + Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.on_mouse_lost) + + + # Bind special mouse events that fall through to parent + controls = [self] + list(set(self.Children) - oldChildren) + for win in controls: + winBind = win.Bind + winBind(wx.EVT_LEFT_DOWN , self.on_left_down) + winBind(wx.EVT_RIGHT_DOWN , self.on_right_down) + winBind(wx.EVT_RIGHT_UP , self.on_right_up) + winBind(wx.EVT_LEFT_DCLICK, self.on_doubleclick) + winBind(wx.EVT_KILL_FOCUS , self.Parent.on_lost_focus) + + self.CalcColors(False) + self.UpdateSkin() + + self.SetDropTarget(AnyListDropTarget(self)) + + def UpdateSkin(self): + # overridden in subclasses + pass + + def on_data_changed(self, src, *a): + 'Invoked when the data object this row represents changes.' + + if not self: + objget = lambda x:object.__getattribute__(self, x) + log.error('%r object has been deleted.', objget('__class__'), )#objget('data')) + + if wx.IsDestroyed(self): + return self._cleanup() + + with self.Frozen(): + self.PopulateControls(self.data) + self.Refresh() + + @property + def image(self): + return wx.EmptyBitmap(32, 32) + + def PopulateControls(self, data): + if isinstance(data, bytes): + data, _data = data.decode('utf8'), data + + self.text = data + + if hasattr(self, 'checkbox'): + self.checkbox.Value = bool(data) + + def on_doubleclick(self, e): + 'Cause the parent list to emit a LISTBOX_DOUBLECLICK event' + self.Parent.emit_doubleclick_event(self.Index) + + def on_close(self, e = None): + for c in self.Children + [self]: + c.ReleaseAllCapture() + + # NOTE: wx maintains the capture stack as a static variable. + # As a result, we can end up with the parent and child controls + # setting focus to each other as the stack is popped, and so + # even after removing the capture from the parent and all children, + # we'll still get "capture == this" errors on deletion. To stop that, + # this code completely unwinds and pops the stack. + count = 0 + #CAS: short circuit in case the broken behavior of wx mouse capture + # changes + while count < 100 and self.GetCapture(): + count += 1 + self.ReleaseMouse() + + self._cleanup() + + def _cleanup(self): + if hasattr(self.data, 'remove_observer'): + self.data.remove_observer(self.on_data_changed) + + def on_mouse_lost(self,event): + while self.HasCapture(): +# print "Parent releasing mouse" + self.ReleaseMouse() + + i = self.Parent.GetIndex(self) + if i == self.Parent.Hovered: + self.Parent.Hovered = -1 + + self.Refresh() + + def MouseCaptureSystem(self,event,mouseinactions,mouseoutactions): + winatpoint = FindWindowAtPointer() + isover = winatpoint == self or winatpoint in self.Children + + if not RectS(self.Size).Contains(event.Position) or not isover: + self.ReleaseAllCapture() + mouseoutactions() + + else: + if not self.HasCapture(): +# print "parent capturing mouse" + self.CaptureMouse() + + mouseinactions() + + def childwithmouse(p = event.Position): + for child in self.Children: + if child.Shown and child.Rect.Contains(p): + return child + + passto = childwithmouse() + + if passto and not passto.HasCapture(): + + def passback(e, passto = passto): + if not RectS(passto.Size).Contains(e.Position) or FindWindowAtPointer()!=passto: + while passto.HasCapture(): + +# print "child releasing capture" + passto.ReleaseMouse() + passto.Parent.AddPendingEvent(e) + discon() + e.Skip() + + def passlost(e,passto=passto): +# print "child disconecting" + discon() + #e.Skip() + + passto.Connect(passto.Id,passto.Id, wxEVT_MOTION,passback) + passto.Connect(passto.Id,passto.Id, wxEVT_MOUSE_CAPTURE_LOST,passlost) + + def discon(passto=passto): + passto.Disconnect(passto.Id,passto.Id, wxEVT_MOTION) + passto.Disconnect(passto.Id,passto.Id, wxEVT_MOUSE_CAPTURE_LOST) +# print "disconected child capture" + +# print "parent giving mouse to child" + passto.CaptureMouse() + + def on_motion(self, event): + + # Is the motion a drag event? + if event.LeftIsDown() and event.Dragging() and self.Parent.dragging: + ds = AnyRowDropSource(self) + + data = wx.CustomDataObject(self.Parent.data_format) + data.SetData(cPickle.dumps(self.Index)) + ds.SetData(data) + ds.DoDragDrop(wx.Drag_AllowMove) + self.Parent.drag = -1 + self.Parent.dragging = False + self.Parent.Refresh() + else: + if self.Parent.ClickTogglesCheckbox: + self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + + self.Parent.dragging = False + + def MouseInActions(): + i = self.Parent.GetIndex(self) + if i != self.Parent.Hovered: + self.Parent.Hovered = i + + self.Refresh() + + def MouseOutActions(): + i = self.Parent.GetIndex(self) + if i == self.Parent.Hovered: + self.Parent.Hovered = -1 + + self.Refresh() + + self.MouseCaptureSystem(event,MouseInActions,MouseOutActions) + + event.Skip(True) + + def on_left_down(self, e = None, right_click=False): + # For things like checkboxes and hyperlinks, allow the click to fall + # through so they work normally. + if e and isinstance(e.EventObject, (editable_controls)): + if not e.ButtonDClick(): + e.Skip(True) + return + + p = self.Parent + + if p.SelectionEnabled: + p.SetSelections([self.Index]) + self.CalcColors() + + if p.DraggableItems: + p.dragging = True + + # a single click in a row with a checkbox simulates a click on the checkbox + elif not right_click and p.ClickTogglesCheckbox and hasattr(self, 'checkbox'): + e = wx.CommandEvent(wx.EVT_COMMAND_CHECKBOX_CLICKED, self.checkbox.Id) + e.SetInt(not self.checkbox.Value) + e.EventObject = self.checkbox + self.checkbox.Command(e) + + p.SetFocus() + + def CalcColors(self, selected = None): + if selected is None: + selected = self.IsSelected() + + # The colors, including the background color, are dependent on if + # this item is selected. + if selected and self.Parent.show_selected: + self.BackgroundColour = selbgcolor + elif self.IsHovered(): + self.BackgroundColour = hovbgcolor + else: + self.BackgroundColour = bgcolors[self.Index % len(bgcolors)] + + self.bg = SkinColor(self.BackgroundColour) + + def on_right_down(self, e): + self.on_left_down(right_click=True) + + def on_right_up(self, e): + popup = self.popup + if popup is not None: + self.Parent._menurow = self + popup.PopupMenu() + + @property + def popup(self): + try: + return self._popupmenu + except AttributeError: + self._popupmenu = menu = UMenu(self) + + menu.AddItem(_('&Edit'), callback = self.on_edit) + menu.AddItem(_('&Remove'), callback = self.on_delete) + return menu + + def on_edit(self): + return self.Parent.on_edit(self.data) + + def on_delete(self): + return self.Parent.OnDelete(self.data) + + def _paint(self, e): + dc = wx.AutoBufferedPaintDC(self) + pad = self.padding + sz = self.ClientSize + + selected = self.IsSelected() + self.CalcColors(selected) + + # Draw a background rectangle + bg = self.bg#getattr(self, 'selectedbg' if selected else 'bg') + bg.Draw(dc, wx.RectS(sz), self.Index) + + + x = 0 + if hasattr(self, 'checkbox'): + x += self.checkbox_border * 2 + self.checkbox.Size.width + + x += pad.x + + image = self.image + if image: + dc.DrawBitmap(image, x, sz.height/2 - self.image.Height/2, True) + x += image.Width + pad.x + + self.draw_text(dc, x, sz) + + # Paint additional things from subclasses + self.PaintMore(dc) + + # Paint the drag indication (if needed) + self.draw_drag_indication(dc, self.Parent.drag) + + return dc + + def draw_text(self, dc, x, sz): + ''' + Draws the main text label for this row. + + Subclasses may override this for custom behavior. + ''' + + dc.Font = self.Font + dc.TextForeground = wx.BLACK#syscol(wx.SYS_COLOUR_HIGHLIGHTTEXT if self.IsSelected() else wx.SYS_COLOUR_WINDOWTEXT) + + labelrect = (x, 0, sz.width - x, sz.height) + dc.DrawLabel(self.get_text(), labelrect, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) + + def get_text(self): + return self.text + + def draw_drag_indication(self, dc, dragi): + idx = self.Index + + if dragi == idx: + y = 0 + elif dragi == idx + 1: + y = self.Size.height - 1 + else: # No drag indication + return + + # Drag indication + dc.Pen = wx.Pen(wx.Colour(140,140,140))#BLACK_PEN + dc.DrawLine(0, y, self.Size.width, y) +# for i in (2, 3): +# dc.DrawLine(i, y, 0, y-i) +# dc.DrawLine(i, y, 0, y+i) + + + def construct(self, use_checkbox): + self.text = '' + + if use_checkbox: + + self.checkbox = wx.CheckBox(self, -1, style=wx.CHK_3STATE) +# def bg(e): +# dc = wx.ClientDC(self.checkbox) +# dc.Pen = wx.TRANSPARENT_PEN +# dc.Brush = wx.Brush(self.BackgroundColour) +# dc.DrawRectangle(*self.checkbox.Rect) + + self.checkbox.Bind(wx.EVT_CHECKBOX, self.on_checkbox) + + self.ConstructMore() + + def IsChecked(self): + return self.checkbox.Get3StateValue() == wx.CHK_CHECKED + + def on_checkbox(self, e): + '''Intercepts checkbox events coming from children of this row, adds + this row's index under the wxCommandEvents "Int" property, and then + happily sends the events back on their way.''' + + e.SetInt(self.Index) + e.Skip(True) + + def layout(self): + self.Sizer = sz = wx.BoxSizer(wx.HORIZONTAL) + pad = self.padding + width = 0 + + if hasattr(self, 'checkbox'): + sz.Add(self.checkbox, 0, wx.EXPAND | wx.ALL, self.checkbox_border) + width += self.checkbox.Size.width + self.checkbox_border*2 + + height = max(getattr(self.image, 'Height', 0), self.Font.Height) + pad.y * 2 + height = max(getattr(self, 'min_row_height', 0), height) + width += getattr(self.image, 'Width', 0) + sz.Add((width, height), 0, 0) + + self.LayoutMore(sz) + + + # Overridden in subclasses + def LayoutMore(self, sizer): pass + def ConstructMore(self): pass + def PaintMore(self, dc): pass + + def GetIndex(self): + return self.Parent.GetIndex(self) + + Index = property(GetIndex) + + def IsSelected(self): + return self.Parent.IsSelected(self.Index) + + def IsHovered(self): + return self.Parent.Hovered == self.Index + +SCROLL_RATE = 20 + +class AnyList(WheelScrollMixin, ScrolledPanel): + sizer_args = (0, wx.EXPAND) + default_row_control = AnyRow + default_velocity = 175 + SelectionEnabled = True + ClickTogglesCheckbox = False + + if 'wxMac' in wx.PlatformInfo: scroll_sizes = () + else: scroll_sizes = (5, 1) + + def __init__(self, parent, data, + row_control = None, + multiselect = False, + edit_buttons = None, + draggable_items = True, + style = 0, + velocity=None): + super(AnyList, self).__init__(parent, -1, style = wx.FULL_REPAINT_ON_RESIZE|style) + + self.data_format = wx.CustomDataFormat('AnyList-%s'%id(self)) + + self.SetSizer(self.create_sizer()) + self.Font = default_font() + + if edit_buttons is not None: + edit = dict((child.Id, child) for child in edit_buttons) + + if not hasattr(self, 'OnNew') or not hasattr(self, 'OnDelete'): + raise AssertionError('to use the edit_buttons parameter you must implement OnNew and OnDelete (in class %s)' % self.__class__.__name__) + + edit[wx.ID_NEW].Bind(wx.EVT_BUTTON, self.OnNew ) + edit[wx.ID_DELETE].Bind(wx.EVT_BUTTON, self.OnDelete) + + self.delete_button = edit[wx.ID_DELETE] + self.delete_button.Enable(False) + else: + self.delete_button = None + + self.velocity = velocity or self.default_velocity + + # List set up + self.show_selected = True + self.rows = [] + self.DraggableItems = draggable_items + self._selection = set() + self._hovered = -1 + + if row_control is None: self.row_control = self.default_row_control + else: self.row_control = row_control + + # Check to make sure data is observable + self.data = data + if hasattr(data, 'add_observer') and callable(data.add_observer): + # Watch the observable list for changes. + data.add_observer(self.on_data_changed) + + self.on_data_changed() + + self.multiselect = multiselect + + self.SetAutoLayout(True) + self.SetupScrolling(False, True, *self.scroll_sizes) + + Bind = self.Bind + Bind(wx.EVT_LEFT_DOWN, self.on_left_down) + self.BindWheel(self) + Bind(wx.EVT_KILL_FOCUS, self.on_lost_focus) + Bind(wx.EVT_KEY_DOWN, self.on_key) + Bind(wx.EVT_CHILD_FOCUS, Null) # to nullify bad effects of ScrolledPanel + Bind(wx.EVT_SCROLLWIN_LINEDOWN, lambda e: self.Scroll(0, self.ViewStart[1] + SCROLL_RATE)) + Bind(wx.EVT_SCROLLWIN_LINEUP, lambda e: self.Scroll(0, self.ViewStart[1] - SCROLL_RATE)) + + self.drag = -1 + self.dragging = False + + def on_left_down(self, e): + self.SetSelections([]) + + def on_key(self, e): + if e.KeyCode == wx.WXK_UP: + if self.Selection > 0: + self.SetSelections([self.Selection-1]) + elif e.KeyCode == wx.WXK_DOWN: + self.SetSelections([self.Selection+1]) + else: + e.Skip() + + def on_lost_focus(self, e): + self.Refresh() + + def on_close(self): + try: + self.data.remove_observer(self.on_data_changed) + except AttributeError: + pass + + for row in self.rows: + row.on_close() + + def drag_item(self, fromindex, toindex): + # Not a valid drag? Get outta here. + if toindex is None: return + + info('drag_item %d -> %d (%d rows)', fromindex, toindex, len(self.rows)) + # If the source is lower than the destination, change the destination + # index because of the offset created by the "gap" + if fromindex < toindex: toindex -= 1 + + # Actually rearrange the data + data = self.data + + with self.Frozen(): + with traceguard: + + insert = lambda: data.insert(toindex, data.pop(fromindex)) + + if hasattr(self.data, 'freeze'): + self.data.freeze() + insert() + self.data.thaw() + else: + insert() + + self.SetSelections([toindex]) + + def ScrollLines(self, lines): + dist = 0 + a = b = None + try: + if len(self) > 1: + a = self[1].Position.y + b = self[0].Position.y + else: + dist = 0 + except Exception: + dist = 0 + else: + if a is not None and b is not None: + dist = a - b + if dist: + dist = lines * dist - getattr(self, 'borrowed_dist', 0) + self.borrowed_dist = int(round(dist)) - dist + self.Scroll(0, self.ViewStart.y + int(round(dist))) + else: + super(AnyList, self).ScrollLines(lines) + + def indicate_drag(self, x, y): + # Accounting for scrolling, find out how many pixels down we are + unuseddx, dy = self.GetScrollPixelsPerUnit() + y += dy * self.ViewStart[1] # account for scrolling + + def finddrag(y): + 'Returns the index between children the dragging cursor is over.' + + for i, row in enumerate(self.rows): + h = row.Size.height + if y < h / 2: return i + elif y < h: return i + 1 + y -= row.Size.height + + return -1 + + newdrag = finddrag(y) + + if newdrag != self.drag: + self.drag = newdrag + self.Refresh() + + def on_data_changed(self, *args): + 'Invoked when the observable data list changes.' + if wx.IsDestroyed(self): + return + + with self.Frozen(): + sz = self.Sizer + rows = self.rows + + oldrowcount = len(rows) + scrolly = self.GetScrollPos(wx.VERTICAL) + + dataset = oset(self.data) + prevset = oset(row.data for row in rows) + + oldset = prevset - dataset + newset = dataset - prevset + + for i_, row in list(enumerate(rows)): + sz.Detach(row) + if row.data in oldset: + if i_ == self.Hovered: + self._hovered = -1 + rows.remove(row) + row.ReleaseAllCapture() + row.on_close() + row.Destroy() + + # Create a new row control for each data element, and add it to + # sizer. + for elem in newset: + control = self.row_control(self, elem) + rows.append(control) + + idxs = {} + for i, d in enumerate(self.data): idxs[d] = i + rows.sort(key = lambda r: idxs.get(r.data)) + + for row in rows: + sz.Add(row, *self.sizer_args) + + self.Layout() + + # Restore the old scroll position. + newrowcount = len(rows) + if oldrowcount != 0 and oldrowcount != newrowcount: + self.SetupScrolling(False, True, *self.scroll_sizes) + + # on MSW, scrolling immediately doesn't work. + wx.CallAfter(self.Scroll, 0, scrolly) + + def RemoveItem(self, child): + if isinstance(child, int): + del self.data[child] + else: + self.data.remove(child) + + if not hasattr(self.data, 'observers'): + self.on_data_changed() + + def GetIndex(self, child): + 'Get the index of the specified child.' + + try: + return self.rows.index(child) + except ValueError: + return -1 + + def GetDataObject(self, index): + 'Returns the data model object at the specified index.' + + if not isinstance(index, int): + raise TypeError('index must be an integer') + + try: + return self.rows[index].data + except IndexError: + return None + + def GetRow(self, index): + try: + return self.rows[index] + except IndexError: + return None + + def IsSelected(self, n): + 'Returns True if the item at the nth position is selected.' + + return n in self._selection + + def GetSelection(self): + 'Returns the index of the currently selected element, or -1 if none.' + if not getattr(self, '_selection', False): + return wx.NOT_FOUND + + if len(self._selection) != 1: + raise AssertionError('GetSelection used on a multiselection list?') + + return list(self._selection)[0] + + def GetSelections(self): + '''Returns a list containing the integer indices of the selected + elements in the list.''' + + return list(self._selection) + + def SetSelections(self, sel): + 'Set the selection state of this list. sel must be iterable' + + if not self.SelectionEnabled: + return + + myclamp = lambda x: clamp(x, 0, len(self.data)-1) + + torefresh = self._selection.copy() + self._selection = set(myclamp(x) for x in sel) + + if not len(self.rows): self._selection = set() + for i in self._selection: assert i >= 0 and i < len(self.rows) + + + for i in self._selection.difference(torefresh): + self.emit_selected_event(i) + for i in torefresh.difference(self._selection): + self.emit_deselected_event(i) + + if self.delete_button: + self.delete_button.Enable(bool(self._selection)) + + if not self.rows: return + + for i in set(myclamp(x) for x in torefresh.union(self._selection)): + try: + if i != myclamp(i): continue + self.rows[i].Refresh() + except IndexError, ie: + import sys + print >> sys.stderr, 'index:', i + raise ie + + Selection = property(GetSelection) + Selections = property(GetSelections, SetSelections) + + def SetHovered(self,i): + n = self._hovered + oldhov = self[n] if n != -1 else None + try: + newhov = self[i] if i != -1 else None + except IndexError, ie: + import sys + print >> sys.stderr, 'index:', i + raise ie + + self._hovered = i + + if oldhov: + oldhov.Refresh() + self.emit_hovered_event(n) + + if newhov: + newhov.Refresh() + self.emit_hovered_event(i) + + + + def GetHovered(self): + return self._hovered + + Hovered = property(GetHovered,SetHovered) + + def create_sizer(self): + return wx.BoxSizer(wx.VERTICAL) + + def emit_event(self,n,type): + e = wx.CommandEvent(type) + e.SetEventObject(self) + e.SetInt(n) + self.AddPendingEvent(e) + + def emit_hovered_event(self,n): + self.emit_event(n,wx.wxEVT_COMMAND_LIST_ITEM_FOCUSED) + + def emit_selected_event(self, n): + self.emit_event(n,wx.wxEVT_COMMAND_LIST_ITEM_SELECTED) + + def emit_deselected_event(self, n): + self.emit_event(n,wx.wxEVT_COMMAND_LIST_ITEM_DESELECTED) + + def emit_doubleclick_event(self, n): + self.emit_event(n,wx.wxEVT_COMMAND_LISTBOX_DOUBLECLICKED) + + def __contains__(self, x): + return self.data.__contains__(x) + + def __len__(self): + return len(self.rows) + + def __getitem__(self, n): + return self.rows[n] + +# drag and drop + +class AnyRowDropSource(wx.DropSource): + def __init__(self, window): + wx.DropSource.__init__(self, window) + self.window = window + +class AnyListDropTarget(wx.PyDropTarget): + #VELOCITY = 100 + def __init__(self, row): + wx.PyDropTarget.__init__(self) + + self.lasttick = None + + self.row = row + self.parent_list = row.GetParent() + + self.dragged = wx.CustomDataObject(self.parent_list.data_format) + self.SetDataObject(self.dragged) + + self.id = id(self.parent_list) + + def OnEnter(self, x, y, d): + return d + + def OnLeave(self): + self.lasttick = None + + def OnDrop(self, x, y): + return True + + def OnDragOver(self, x, y, d): + if not self.parent_list.dragging: + return wx.DragCancel + + plist = self.parent_list + + y += self.row.Position.y # account for y position of the row + + # draw the drag indicator line + self.parent_list.indicate_drag(x, y) + + listrect = wx.RectS(plist.Size) + + topdif = y - listrect.y + botdif = listrect.bottom - y + ply = plist.ViewStart[1] + + if topdif < 7 or botdif < 7: + if self.lasttick is None: + self.lasttick = default_timer() + + now = default_timer() + + # clamp to 0: negative time deltas--from floating point roundoff errors? + diff = max(0, now - self.lasttick) + toscroll = int(diff * self.velocity) + + if toscroll >= 1: + self.lasttick = now + + if topdif < 5: + plist.Scroll(0, ply - toscroll) + elif botdif < 5: + plist.Scroll(0, ply + toscroll) + + return wx.DragMove + + @property + def velocity(self): + return self.parent_list.velocity + + def OnData(self, x, y, d): + "Called when OnDrop returns True. Get data and do something with it." + + if not self.GetData(): # Copies data from drag source to self.dragging + return wx.DragNone + + list = self.parent_list + toindex = list.drag + + with traceguard: + fromindex = cPickle.loads(self.dragged.Data) + + if toindex != -1: + list.drag_item(fromindex, toindex) + return wx.DragMove + + return wx.DragError + + + + +def main(): + from util.observe import ObservableList, Observable + + app = wx.PySimpleApp() + + f = wx.Frame(None, -1, 'AnyList Test') + f.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + + class Foo(Observable): + def __init__(self, name): + Observable.__init__(self) + self.name = name + + def __call__(self): + wx.Bell() + + def __repr__(self): return '' % self.name + + def on_doubleclick(e): + print e + + foos = [Foo(n) for n in 'one two three four five six seven eight nine'.split()] + data = ObservableList(foos) + + splist = AnyList(f, data) + splist.Bind(wx.EVT_LISTBOX_DCLICK, on_doubleclick) + + sz.Add(splist, 1, wx.EXPAND | wx.SOUTH, 15 if 'wxMac' in wx.PlatformInfo else 0) + splist.Bind(wx.EVT_CHECKBOX, lambda e: e.EventObject.Parent.data()) + + f.Show() + app.MainLoop() + + + +if __name__ == '__main__': + main() + #testColors() diff --git a/digsby/src/gui/app/__init__.py b/digsby/src/gui/app/__init__.py new file mode 100644 index 0000000..751eef3 --- /dev/null +++ b/digsby/src/gui/app/__init__.py @@ -0,0 +1,58 @@ +""" +The purpose of this module is to store code that affects DigsbyApp. Eventually, +it'd be nice to move some of main.py into here as well. +""" + +import sip +import sys +import traceback +import types +import wx + +def checkMainThread(func): + def new(*args, **kwargs): + if not wx.IsMainThread(): + import util.introspect + # print the stack before we assert to be more helpful :) + print >> sys.stderr, util.introspect.print_stack_trace() + assert wx.IsMainThread() + return func(*args, **kwargs) + return new + +excludes = ["Event", "Timer", "CallLater", "sip", "ExitMainLoop"] + +def addThreadChecksToClassRecursive(obj): + for symbol in dir(obj): + objname = getattr(obj, '__name__', None) + if objname is None: + continue + + if hasattr(obj, "__module__"): + objname = obj.__module__ + "." + objname + + if symbol.startswith("_") or objname.startswith("_"): + continue + + exclude = False + for exc in excludes: + if symbol.find(exc) != -1 or objname.find(exc) != -1: + exclude = True + + if exclude: + continue + + try: + sym = getattr(obj, symbol) + except: + continue + + if type(sym) == types.MethodType: + #print objname, symbol + assert objname.find("PyEvent") == -1 and symbol.find("PyTimer") == -1 + # im_self existing means classmethod, we hit problems if we do this on classmethods + + if not sym.im_self: + exec "%s.%s = checkMainThread(%s.%s)" % (objname, symbol, objname, symbol) + elif type(sym) == types.ClassType or type(sym) == types.TypeType or type(sym) == sip.wrappertype: + addThreadChecksToClassRecursive(sym) + diff --git a/digsby/src/gui/app/eventStack.py b/digsby/src/gui/app/eventStack.py new file mode 100644 index 0000000..a99753e --- /dev/null +++ b/digsby/src/gui/app/eventStack.py @@ -0,0 +1,127 @@ +#---------------------------------------------------------------------- +# Name: wxblox/events.py +# Purpose: These mixins implement a push and pop menu/UI update event +# handler system at the wx.App level. This is useful for resolving +# cases where multiple views may want to respond to an event +# (say, wx.ID_COPY) and where you also want a "default" handler +# for the event (and UI update status) when there is no active +# view which wishes to handle the event. +# +# Author: Kevin Ollivier +# +# Created: -Mar- +# Copyright: (c) Kevin Ollivier +# Licence: wxWindows license +#---------------------------------------------------------------------- + +import wx + +class AppEventManager: + ui_events = [ + wx.ID_NEW, wx.ID_OPEN, wx.ID_CLOSE_ALL, wx.ID_CLOSE, + wx.ID_REVERT, wx.ID_SAVE, wx.ID_SAVEAS, wx.ID_UNDO, + wx.ID_REDO, wx.ID_PRINT, wx.ID_PRINT_SETUP, wx.ID_PREVIEW, + wx.ID_EXIT + ] + + def __init__(self): + pass + + def RegisterEvents(self): + app = wx.GetApp() + #app.AddHandlerForID(wx.ID_EXIT, self.OnExit) + #app.AddHandlerForID(wx.ID_ABOUT, self.OnAbout) + + for eventID in self.ui_events: + app.AddHandlerForID(eventID, self.ProcessEvent) + app.AddUIHandlerForID(eventID, self.ProcessUpdateUIEvent) + +class AppEventHandlerMixin: + """ + The purpose of the AppEventHandlerMixin is to provide a centralized + location to manage menu and toolbar events. In an IDE which may have + any number of file editors and services open that may want to respond + to certain menu and toolbar events (e.g. copy, paste, select all), + we need this to efficiently make sure that the right handler is handling + the event. + + To work with this system, views must call + Add(UI)HandlerForID(ID, handlerFunc) + in their EVT_SET_FOCUS handler, and call Remove(UI)HandlerForID(ID) in their + EVT_KILL_FOCUS handler. + """ + + def __init__(self): + self.handlers = {} + self.uihandlers = {} + + # When a view changes the handler, move the old one here. + # Then "pop" the handler when the view loses the focus + self.pushed_handlers = {} + self.pushed_uihandlers = {} + + def AddHandlerForIDs(self, eventID_list, handlerFunc): + for eventID in eventID_list: + self.AddHandlerForID(eventID, handlerFunc) + + def AddHandlerForID(self, eventID, handlerFunc): + self.Bind(wx.EVT_MENU, self.HandleEvent, id=eventID) + + if eventID in self.handlers: + self.pushed_handlers[eventID] = self.handlers[eventID] + + self.handlers[eventID] = handlerFunc + + def AddUIHandlerForID(self, eventID, handlerFunc): + self.Bind(wx.EVT_UPDATE_UI, self.HandleUpdateUIEvent, id=eventID) + + if eventID in self.uihandlers: + self.pushed_uihandlers[eventID] = self.uihandlers[eventID] + + self.uihandlers[eventID] = handlerFunc + + def RemoveHandlerForIDs(self, eventID_list): + for eventID in eventID_list: + self.RemoveHandlerForID(eventID) + + def RemoveHandlerForID(self, eventID): + self.Unbind(wx.EVT_MENU, eventID) + self.handlers[eventID] = None + + if eventID in self.pushed_handlers: + self.handlers[eventID] = self.pushed_handlers[eventID] + + def RemoveUIHandlerForID(self, eventID): + self.Unbind(wx.EVT_UPDATE_UI, eventID) + self.uihandlers[eventID] = None + + if eventID in self.pushed_uihandlers: + self.uihandlers[eventID] = self.pushed_uihandlers[eventID] + + def HandleEvent(self, event): + e_id = event.GetId() + if e_id in self.handlers: + handler = self.handlers[e_id] + try: + if handler: + return handler(event) + except wx.PyDeadObjectError: + self.RemoveHandlerForID(e_id) + else: + event.Skip() + + return False + + def HandleUpdateUIEvent(self, event): + e_id = event.GetId() + if e_id in self.uihandlers: + handler = self.uihandlers[e_id] + try: + if handler: + return handler(event) + except wx.PyDeadObjectError: + self.RemoveUIHandlerForID(e_id) + else: + event.Skip() + + return False diff --git a/digsby/src/gui/app/mainMenuEvents.py b/digsby/src/gui/app/mainMenuEvents.py new file mode 100644 index 0000000..e3f3a49 --- /dev/null +++ b/digsby/src/gui/app/mainMenuEvents.py @@ -0,0 +1,412 @@ +import wx +import sys +import actionIDs + +from peak.util.imports import lazyModule + +common = lazyModule('common') +from eventStack import AppEventHandlerMixin +config = lazyModule('config') +from weakref import WeakValueDictionary + +def stub(): + pass + +def tracecall(fn): + """ + This decorator allows us to register that an event handler has fired + during automated tests. + """ + def trace(*args): + if getattr(wx.GetApp(), "testing", False): + wx.GetApp().event_fired = fn + print "function %r fired" % fn + return stub + else: + return fn(*args) + + return trace + + +# TODO: FInd a better place to put these data structures. +sorts = { + actionIDs.SortByNone: 'none', + actionIDs.SortByStatus: 'status', + actionIDs.SortByName: 'name', + actionIDs.SortByLogSize: 'log', + actionIDs.SortByService: 'service' + } + +# For windows, since we have to catch native MENU_OPEN events :( +hwndMap = WeakValueDictionary() + +# another cache for avoiding lookups when possible +menuTitles = {} + +buddylist_panel_names = dict(status = _('Status Panel'), + blist = _('Buddy List'), + elist = _('Email Accounts'), + slist = _('Social Networks'), + clist = _('Connections')) + +class MainMenuEventHandler(AppEventHandlerMixin): + def __init__(self): + AppEventHandlerMixin.__init__(self) + + def register_handlers(self): + self.AddHandlerForID(actionIDs.NewStatusMessage, self.new_status_message) + self.AddHandlerForID(actionIDs.EditStatusMessage, self.edit_status_message) + self.AddHandlerForID(actionIDs.MyAccounts, self.accounts) + self.AddHandlerForID(actionIDs.NewIM, self.new_im) + self.AddHandlerForID(actionIDs.AddContact, self.add_contact) + self.AddHandlerForID(actionIDs.AddGroup, self.add_group) + self.AddHandlerForID(actionIDs.RenameSelection, self.rename_selection) + self.AddHandlerForID(actionIDs.DeleteSelection, self.delete_selection) + self.AddHandlerForID(actionIDs.SignOff, self.sign_off) + + self.AddHandlerForID(actionIDs.AlwaysOnTop, self.always_on_top) + self.AddHandlerForID(actionIDs.Skins, self.skins) + self.AddHandlerForID(actionIDs.MenuBar, self.show_menubar) + self.AddHandlerForID(actionIDs.StatusPanel, self.show_status_panel) + self.AddHandlerForID(actionIDs.ArrangePanels, self.arrange_panels) + self.AddHandlerForID(actionIDs.ShowMobileContacts, self.show_mobile_contacts) + self.AddHandlerForID(actionIDs.ShowOfflineContacts, self.show_offline_contacts) + self.AddHandlerForID(actionIDs.GroupOfflineContacts, self.group_offline_contacts) + self.AddHandlerForID(actionIDs.HideOfflineGroups, self.hide_offline_groups) + + self.AddHandlerForID(actionIDs.SortByNone, self.sort_by) + self.AddHandlerForID(actionIDs.SortByStatus, self.sort_by) + self.AddHandlerForID(actionIDs.SortByName, self.sort_by) + self.AddHandlerForID(actionIDs.SortByLogSize, self.sort_by) + self.AddHandlerForID(actionIDs.SortByService, self.sort_by) + self.AddHandlerForID(actionIDs.SortByAdvanced, self.sort_by) + + self.AddHandlerForID(actionIDs.Preferences, self.show_preferences) + self.AddHandlerForID(actionIDs.FileTransferHistory, self.show_file_transfer_history) + self.AddHandlerForID(actionIDs.ChatHistory, self.show_chat_history) + + self.AddHandlerForID(actionIDs.Documentation, self.show_documentation) + self.AddHandlerForID(actionIDs.SupportForums, self.show_support_forums) + self.AddHandlerForID(actionIDs.SubmitBugReport, self.submit_bug_report) + self.AddHandlerForID(actionIDs.SuggestAFeature, self.suggest_a_feature) + self.AddHandlerForID(actionIDs.ShowDebugConsole, self.show_debug_console) + # See note in run_tests method. + # self.AddHandlerForID(actionIDs.RunTests, self.run_tests) + self.AddHandlerForID(actionIDs.InviteYourFriends, self.invite_your_friends) + + if 'wxMSW' in wx.PlatformInfo: + parentFrame = self.get_buddy_list_frame() + parentFrame.Bind(wx.EVT_MENU_OPEN, self.update_menu) + else: + self.Bind(wx.EVT_MENU_OPEN, self.update_menu) + + def fire_event_for_action(self, action, target=None): + return fire_action_event(action, target) + + # TODO: I wonder if we want to move buddylist-specific events into the buddylist + # itself? + def get_buddy_list_frame(self): + buddylist = None + app = wx.GetApp() + if app: + buddylist = app.buddy_frame + + return buddylist + + def get_buddy_list_panel(self): + buddylist = self.get_buddy_list_frame() + + if buddylist: + return buddylist.buddyListPanel + else: + return None + + def update_menu(self, event): + menu = event.Menu + + # ooohkay... so wx doesn't allow you to get a menu's title from the menu + # object, but you can get it by getting them menubar then passing in the + # menu object's position in the menubar... + if config.platformName == "mac": + menubar = self.get_buddy_list_frame().MenuBar + else: + menubar = self.get_buddy_list_panel().menubar + + global menuTitles + if menubar: + title = None + if menu in menuTitles: + title = menuTitles[menu] + else: + for i in xrange(menubar.MenuCount): + iter_menu = menubar.GetMenu(i) + if iter_menu == menu: + title = menuTitles[menu] = menubar.GetMenuLabel(i) + else: + for item in iter_menu.MenuItems: + if item.IsSubMenu() and menu == item.SubMenu: + title = menuTitles[menu] = item.GetItemLabel() + + if title == _("&Digsby"): + self.update_digsby_menu(menu) + elif title == _("&Sort By"): + self.update_sort_menu(menu) + elif title == _('&View'): + self.update_view_menu(menu) + + def update_digsby_menu(self, menu): + buddylist = self.get_buddy_list_panel() + if buddylist: + b = buddylist.blist.SelectedItem + + from contacts.buddylistfilters import OfflineGroup + + + allow_add = any(x.allow_contact_add for x in common.profile.account_manager.connected_accounts) + menu.Enable(actionIDs.NewIM, allow_add) #TODO: Needs to decide if it should be enabled + menu.Enable(actionIDs.AddContact, allow_add) + menu.Enable(actionIDs.AddGroup, allow_add) + + renameitem = menu.FindItemById(actionIDs.RenameSelection) + deleteitem = menu.FindItemById(actionIDs.DeleteSelection) + + if b is None or getattr(b, 'iswidget', False) or isinstance(b, OfflineGroup): + + renameitem.SetItemLabel(_('&Rename Selection')) + deleteitem.SetItemLabel(_('&Delete Selection')) + renameitem.Enable(False) + deleteitem.Enable(False) + else: + renameitem.SetItemLabel(_('&Rename {name}').format(name=getattr(b, 'alias', b.name))) + deleteitem.SetItemLabel(_('&Delete {name}').format(name=getattr(b, 'alias', b.name))) + renameitem.Enable(True) + deleteitem.Enable(True) + + def update_sort_menu(self, menu): + checkedOne = False + sortby = common.pref('buddylist.sortby', 'none none') + global sorts + + for item in menu.MenuItems: + try: + if item.Id in sorts: + sorttypes = sorts[item.Id] + ' none' + else: + sorttypes = object() + except IndexError: + sorttypes = object() + + if item.Kind == wx.ITEM_CHECK: + + val = sorttypes == sortby + item.Check(val) + if val: + checkedOne = True + + if not checkedOne: + list(menu.MenuItems)[-1].Check(True) + + def update_view_menu(self, menu): + sortstatus = common.pref('buddylist.sortby').startswith('*status') + + showoff = common.pref('buddylist.show_offline') + showmob = common.pref('buddylist.show_mobile') + hidegroups = common.pref('buddylist.hide_offline_groups') + groupoff = common.pref('buddylist.group_offline') + + showstat = common.pref('buddylist.show_status') + showmenu = common.pref('buddylist.show_menubar') + + ontop = menu.FindItemById(actionIDs.AlwaysOnTop) + showmobile = menu.FindItemById(actionIDs.ShowMobileContacts) + showoffline = menu.FindItemById(actionIDs.ShowOfflineContacts) + groupoffline = menu.FindItemById(actionIDs.GroupOfflineContacts) + hideoffline = menu.FindItemById(actionIDs.HideOfflineGroups) + + statuspanel = menu.FindItemById(actionIDs.StatusPanel) + menubar = menu.FindItemById(actionIDs.MenuBar) + + showmobile.Check(showmob) + showoffline.Check(showoff) + statuspanel.Check(showstat) + # Mac doesn't have this option since menubar is always at the top of the screen + if menubar: + menubar.Check(showmenu) + + buddy_frame = self.get_buddy_list_frame() + if buddy_frame: + ontop.Check(buddy_frame.HasFlag(wx.STAY_ON_TOP)) + + groupoffline.Enable(not sortstatus and showoff) + groupoffline.Check(groupoff and (not sortstatus and showoff)) + + hideoffline.Enable(not sortstatus) + hideoffline.Check((sortstatus and not showoff) + or (not sortstatus and hidegroups)) + + @tracecall + def new_status_message(self, event=None): + from gui.status import new_custom_status + new_custom_status(None, save_checkbox = True) + + @tracecall + def edit_status_message(self, event=None): + import gui.pref.prefsdialog as prefsdialog + prefsdialog.show('status') + + @tracecall + def accounts(self, event=None): + prefsdialog.show('accounts') + + @tracecall + def update_bool_pref(self, pref): + common.profile.prefs[pref] = not common.profile.prefs[pref] + + def update_check_pref(self, item, value): + item.Check(value) + + @tracecall + def new_im(self, event=None): + from gui.imdialogs import ShowNewIMDialog + ShowNewIMDialog() + + @tracecall + def add_contact(self, event=None): + from gui.addcontactdialog import AddContactDialog + AddContactDialog.MakeOrShow() + + @tracecall + def add_group(self, event=None): + import gui.protocols + gui.protocols.add_group() + + @tracecall + def rename_selection(self, event=None): + buddylist = self.get_buddy_list_panel() + if buddylist: + buddylist.blist.rename_selected() + + @tracecall + def delete_selection(self, event=None): + buddylist = self.get_buddy_list_panel() + if buddylist: + buddylist.blist.delete_blist_item(buddylist.blist.SelectedItem) + + @tracecall + def sign_off(self, event=None): + common.profile.signoff() + + @tracecall + def always_on_top(self, event=None): + self.update_bool_pref('buddylist.always_on_top') + + @tracecall + def skins(self, event=None): + prefsdialog.show('appearance') + + @tracecall + def show_menubar(self, event=None): + pref = 'buddylist.show_menubar' + self.update_bool_pref(pref) + if not common.profile.prefs[pref]: + wx.MessageBox(_('You can bring back the menubar by right clicking ' + 'on the digsby icon in the task tray.'), + _('Hide Menu Bar')) + + @tracecall + def show_status_panel(self, event=None): + self.update_bool_pref('buddylist.show_status') + + @tracecall + def arrange_panels(self, event=None): + from gui.visuallisteditor import VisualListEditor + if VisualListEditor.RaiseExisting(): + return + + buddylist = self.get_buddy_list_panel() + editor = VisualListEditor(buddylist, common.profile.prefs['buddylist.order'], + buddylist_panel_names, + lambda l: common.setpref('buddylist.order', l), + _('Arrange Panels')) + editor.Show() + + @tracecall + def show_mobile_contacts(self, event=None): + self.update_bool_pref('buddylist.show_mobile') + + @tracecall + def show_offline_contacts(self, event=None): + self.update_bool_pref('buddylist.show_offline') + + @tracecall + def group_offline_contacts(self, event=None): + self.update_bool_pref('buddylist.group_offline') + + @tracecall + def hide_offline_groups(self, event=None): + self.update_bool_pref('buddylist.hide_offline_groups') + + @tracecall + def sort_by(self, event): + global sorts + if event.GetId() in sorts: + common.setpref('buddylist.sortby', sorts[event.GetId()] + ' none') + else: + prefsdialog.show('contact_list') + + @tracecall + def show_preferences(self, event=None): + prefsdialog.show('accounts') + + @tracecall + def show_file_transfer_history(self, event=None): + from gui.filetransfer import FileTransferDialog + FileTransferDialog.Display() + + @tracecall + def show_chat_history(self, event=None): + from gui.pastbrowser import PastBrowser + PastBrowser.MakeOrShow() + + @tracecall + def show_documentation(self, event=None): + wx.LaunchDefaultBrowser('http://wiki.digsby.com') + + @tracecall + def show_support_forums(self, event=None): + wx.LaunchDefaultBrowser('http://forum.digsby.com') + + @tracecall + def submit_bug_report(self, event=None): + from util.diagnostic import do_diagnostic + do_diagnostic() + + @tracecall + def suggest_a_feature(self, event=None): + from gui.native.helpers import createEmail + createEmail('mailto:features@digsby.com') + + @tracecall + def show_debug_console(self, event=None): + wx.GetApp().toggle_crust() + + @tracecall + def invite_your_friends(self, event=None): + wx.LaunchDefaultBrowser('http://www.digsby.com/invite') + + # NOTE: Temporarily disabled until we can get path / the AutoUpdater properly + # handling non-ascii filename characters on Mac. + #@tracecall + #def run_tests(self, event=None): + # import tests.unittests as test + # testDialog = test.UnitTestDialog(None, -1, size=(400,400), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + # testDialog.Show() + +def fire_action_event(action, target=None): + if not target: + target = wx.GetApp() + + if target: + event = wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, action) + target.ProcessEvent(event) + diff --git a/digsby/src/gui/app/menubar.py b/digsby/src/gui/app/menubar.py new file mode 100644 index 0000000..9563fac --- /dev/null +++ b/digsby/src/gui/app/menubar.py @@ -0,0 +1,82 @@ +import wx +import sys +import actionIDs +from gui.model.menus import * +from config import platformName + +def digsbyWxMenuBar(): + menus = [] + + # We will want to move this somewhere else, but using it right now for testing. + digsby = Menu(_('&Digsby')) + + status = Menu() + status.addItem(_('&New Status Message...'), id=actionIDs.NewStatusMessage) + status.addItem(_('&Edit Status Messages...'), id=actionIDs.EditStatusMessage) + + digsby.addItem(_('My Status'), subMenu=status) + digsby.addItem(_('My &Accounts...'), id=actionIDs.MyAccounts) + digsby.addSep() + + digsby.addItem(_('&New IM...'), id=actionIDs.NewIM, defaultAccel='Ctrl+N') + digsby.addItem(_('Add &Contact...'), id=actionIDs.AddContact, defaultAccel='Ctrl+A') + digsby.addItem(_('Add &Group...'), id=actionIDs.AddGroup, type="checkbox") + digsby.addItem(_('&Rename Selection'), id=actionIDs.RenameSelection) + digsby.addItem(_('&Delete Selection...'), id=actionIDs.DeleteSelection) + digsby.addSep() + digsby.addItem(_('Sign &Off Digsby'), id=actionIDs.SignOff) + # wx.App handles this for proper shutdown. + digsby.addItem(_('E&xit Digsby'), id=actionIDs.Exit) + menus.append(digsby) + + view = Menu(_('&View')) + view.addItem(_('&Always On Top'), id=actionIDs.AlwaysOnTop, type="checkbox") + view.addItem(_('Skins...'), id=actionIDs.Skins, defaultAccel='Ctrl+S') + view.addSep() + if not platformName == "mac": + view.addItem(_('&Menu Bar'), id=actionIDs.MenuBar, type="checkbox") + view.addItem(_('Status Panel'), id=actionIDs.StatusPanel, type="checkbox") + view.addItem(_('Arrange &Panels...'), id=actionIDs.ArrangePanels) + view.addSep() + view.addItem(_('Show &Mobile Contacts'), id=actionIDs.ShowMobileContacts, defaultAccel='Ctrl+M', type="checkbox") + view.addItem(_('Show &Offline Contacts'), id=actionIDs.ShowOfflineContacts, defaultAccel='Ctrl+O', type="checkbox") + view.addItem(_('&Group Offline Contacts'), id=actionIDs.GroupOfflineContacts, defaultAccel='Ctrl+G', type="checkbox") + view.addItem(_('&Hide Offline Groups'), id=actionIDs.HideOfflineGroups, type="checkbox") + + # sort by + sortby = Menu() + # TODO: Create one master list of the possible sort orders and iterate through them here. + sortby.addItem(_('&None'), id=actionIDs.SortByNone, type="checkbox") + sortby.addItem(_('&Status'), id=actionIDs.SortByStatus, type="checkbox") + sortby.addItem(_('N&ame'), id=actionIDs.SortByName, type="checkbox") + sortby.addItem(_('&Log Size'), id=actionIDs.SortByLogSize, type="checkbox") + sortby.addItem(_('Ser&vice'), id=actionIDs.SortByService, type="checkbox") + sortby.addItem(_('Advan&ced...'), id=actionIDs.SortByAdvanced, type="checkbox") + + view.addItem(_('&Sort By'), subMenu=sortby) + menus.append(view) + + tools = Menu(_('&Tools')) + tools.addItem(_('&Preferences...'), defaultAccel='Ctrl+P', id = actionIDs.Preferences) + tools.addItem(_('&File Transfer History'), id=actionIDs.FileTransferHistory, defaultAccel='Ctrl+J') + tools.addItem(_('&Chat History'), id=actionIDs.ChatHistory) + menus.append(tools) + + help = Menu(_('&Help')) + help.addItem(_('&Documentation'), id=actionIDs.Documentation) + help.addItem(_('Support &Forums'), id=actionIDs.SupportForums) + help.addSep() + help.addItem(_('&Submit Bug Report'), id=actionIDs.SubmitBugReport) + help.addItem(_('Su&ggest a Feature'), id=actionIDs.SuggestAFeature) + help.addSep() + + if getattr(sys, 'DEV', False): # or pref('debug.console', False): + help.addItem('Show Debug Console', id=actionIDs.ShowDebugConsole) + help.addItem('Run Tests', id=actionIDs.RunTests) + help.addSep() + + help.addItem(_('&Invite Your Friends'), id=actionIDs.InviteYourFriends) + help.addItem(_('&About Digsby'), id=actionIDs.About) + menus.append(help) + + return menus diff --git a/digsby/src/gui/authorizationdialog.py b/digsby/src/gui/authorizationdialog.py new file mode 100644 index 0000000..4b870b9 --- /dev/null +++ b/digsby/src/gui/authorizationdialog.py @@ -0,0 +1,91 @@ +from __future__ import with_statement +import wx +from gui.addcontactdialog import AddContactDialog +from gui import skin +from util import traceguard + +LEFTLESS = wx.ALL & ~wx.LEFT + +try: + _ +except NameError: + def _(s): + return s + +authdialog_style = ( wx.DEFAULT_FRAME_STYLE + #& ~(wx.RESIZE_BORDER) + | wx.STAY_ON_TOP) + +class AuthorizationDialog(wx.Frame): + def __init__(self, protocol, buddy, message, username_added, callback = None): + wx.Frame.__init__(self, None, -1, _('Authorize Contact'), style = authdialog_style) + with traceguard: + self.SetFrameIcon(skin.get('appdefaults.taskbaricon')) + p = wx.Panel(self) + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(p,1,wx.EXPAND) + + self.protocol = protocol + self.buddy = buddy + self.bname = getattr(buddy, 'name', buddy) + self.username_added = username_added + self.callback = callback + + bitmap = wx.ArtProvider.GetBitmap(wx.ART_QUESTION) + bitmap.SetMaskColour(wx.BLACK) + + sbitmap = wx.StaticBitmap(p, -1, bitmap) + + text = wx.StaticText(p, -1, message) + + addbutton = wx.Button(p, wx.YES, _('Authorize and Add')) + authbutton = wx.Button(p, wx.OK, _('Authorize')) + denybutton = wx.Button(p, wx.NO, _('Deny')) + + selfSizer = p.Sizer = wx.BoxSizer(wx.VERTICAL ) + topSizer = wx.BoxSizer(wx.HORIZONTAL) + buttonSizer = wx.BoxSizer(wx.HORIZONTAL) + + topSizer.Add(sbitmap, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT|wx.RIGHT, 8) + topSizer.Add(text, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) + + buttonSizer.AddStretchSpacer() + add_flags = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|LEFTLESS + buttonSizer.AddMany([ (addbutton, 0, add_flags, 8), + (authbutton, 0, add_flags, 8), + (denybutton, 0, add_flags, 8), ]) + selfSizer.Add(topSizer, 1, wx.ALIGN_CENTER_HORIZONTAL) + selfSizer.Add(buttonSizer, 0, wx.EXPAND) + + self.Fit() + + self.Size += (40, 30) + + self.Bind(wx.EVT_BUTTON, self.OnButton) + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnButton(self, event): + if event.Id == wx.YES: + AddContactDialog.MakeOrShow(service = getattr(self.buddy, 'service', self.protocol.service), name = unicode(self.bname)) + self.callback(self.buddy, True, self.username_added) + elif event.Id == wx.OK: + self.callback(self.buddy, True, self.username_added) + elif event.Id == wx.NO: + self.callback(self.buddy, False, self.username_added) + + self.Close() + + def OnClose(self, event): + self.Hide() + self.Destroy() + + + + +if __name__ == '__main__': + from tests.testapp import testapp + app = testapp() + + ad = AuthorizationDialog(None, None, 'Allow SomeBuddy to add you as a buddy as YourNick on Protocol?', 'protoname') + ad.Show() + app.MainLoop() diff --git a/digsby/src/gui/autocomplete.py b/digsby/src/gui/autocomplete.py new file mode 100644 index 0000000..81e911f --- /dev/null +++ b/digsby/src/gui/autocomplete.py @@ -0,0 +1,201 @@ +import wx +import cgui + +from gui.prototypes.menus.simplemenu import EVT_SMI_CLICKED +from gui.toolbox.monitor import Monitor +from gui.prototypes.menus.simplemenu import SimpleMenuItemClickedEvent + +menu_keys = frozenset((wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, + wx.WXK_PAGEDOWN, wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_HOME, + wx.WXK_END, wx.WXK_TAB)) + +class AutoCompleteDropDown(cgui.SkinVList): + max_height = 500 + frame_border_size = 1 + def __init__(self, parent): + self.frame = AutoCompleteFrame(parent) + self.frame.SetSize((300, self.max_height+self.frame_border_size*2)) + cgui.SkinVList.__init__(self, self.frame) + self.frame.Sizer = wx.BoxSizer(wx.VERTICAL) + self.frame.Sizer.Add(self, 1, wx.EXPAND | wx.ALL, self.frame_border_size) + self.frame.SetBackgroundColour(wx.Color(0x77, 0x77, 0x77)) + self.frame.Layout() + self.SetDrawCallback(self.OnPaint) + self.Bind(wx.EVT_MOTION, self.__OnMouseMotion) + self.Bind(wx.EVT_LEFT_UP, self.__OnLeftUp) + self.Bind(wx.EVT_KEY_DOWN, self.__OnKeyDown) + + def __OnKeyDown(self, e): + keycode = e.KeyCode + + if keycode == wx.WXK_UP: + sel = self.GetSelection() - 1 + if sel >= 0: self.SetSelection(sel) + + elif keycode == wx.WXK_DOWN: + sel = self.GetSelection() + 1 + if sel < self.GetItemCount(): self.SetSelection(sel) + + elif keycode == wx.WXK_PAGEUP: + self.PageUp() + self.SetSelection(self.GetFirstVisibleLine()) + + elif keycode == wx.WXK_PAGEDOWN: + self.PageDown() + self.SetSelection(self.GetFirstVisibleLine()) + + elif keycode == wx.WXK_END: + self.SetSelection(self.GetItemCount()-1) + + elif keycode == wx.WXK_HOME: + self.SetSelection(0) + + elif keycode in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_TAB): + self.EmitClick(self.Selection) + + else: + e.Skip() + + def __OnLeftUp(self, e): + e.Skip() + self.EmitClick(self.Selection) + + def EmitClick(self, i): + if i != -1: + se = SimpleMenuItemClickedEvent(0) + se.SetInt(i) + self.AddPendingEvent(se) + + def __OnMouseMotion(self, e): + e.Skip() + self.SetSelection(self.HitTest(e.Position)) + + item_height = 26 + + def CalcHeight(self): + return self.item_height * len(self.items) + self.frame_border_size*2 + + def SetItems(self, items): + selected_item = None + if hasattr(self, 'items'): + try: + selected_item = self.items[self.Selection] + except ValueError: + pass + + #print 'selected_item', selected_item + self.items = items + self.SetHeights([self.item_height]*len(items)) + height = min(self.max_height, self.CalcHeight()) + self.frame.SetSize((self.frame.Size.width, height)) + + for i, item in enumerate(items): + if selected_item is not None and item == selected_item: + break + else: + i = 0 + + self.SetSelection(i) + + + def PopUp(self, rect): + self.rect = rect + monarea = Monitor.GetFromRect(rect).ClientArea + + below = wx.Point(rect.BottomLeft) + above = wx.Point(rect.TopLeft - wx.Point(0, self.frame.Size.height)) + + if getattr(self, 'valign', 'bottom') == 'top': + prefer, second = above, below + else: + prefer, second = below, above + + if monarea.ContainsRect(wx.RectPS(prefer, self.frame.Size)): + chose = prefer + else: + chose = second + + self.frame.SetPosition(chose) + + if not hasattr(self, 'valign'): + self.valign = 'bottom' if chose == below else 'top' + + return self.frame.ShowNoActivate(True) + + def Dismiss(self): + return self.frame.Hide() + +class AutoCompleteFrame(wx.Frame): + style = wx.FRAME_SHAPED | wx.NO_BORDER | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR + def __init__(self, parent): + wx.Frame.__init__(self, parent, style=self.style) + + + +def autocomplete(textctrl, items, controller): + def update_popup(new=False): + @wx.CallAfter + def after(): + items = controller.complete(textctrl.Value, textctrl.InsertionPoint) + if items is not None: + a.SetItems(items) + if new or a.Selection == -1: + a.SetSelection(0) + a.PopUp(get_popup_rect()) + else: + a.Dismiss() + + def onkey(e): + e.Skip() + keycode = e.KeyCode + if a.frame.IsShown(): + if keycode == wx.WXK_ESCAPE: + a.Dismiss() + e.Skip(False) + elif keycode in menu_keys: + a.ProcessEvent(e) + return + else: + if not a.should_ignore_key(e): + update_popup() + else: + if keycode == wx.WXK_BACK: + update_popup() + + textctrl.Bind(wx.EVT_KEY_DOWN, onkey) + + try: + a = textctrl._autocomplete + except AttributeError: + # TODO: don't import twitter here. duh + from twitter.twitter_gui import TwitterAutoCompleteDropDown + a = textctrl._autocomplete = TwitterAutoCompleteDropDown(textctrl) + def onmenu(e): + selection = a.items[e.GetInt()].user['screen_name'] + result = controller.finish(textctrl.Value, textctrl.InsertionPoint, selection) + a.SetSelection(0) + if result is not None: + val, cursor = result + textctrl.SetValue(val) + textctrl.SetInsertionPoint(cursor) + update_popup() + textctrl.Bind(EVT_SMI_CLICKED, onmenu) + def onkillfocus(e): + e.Skip() + + # dismiss on focus lost, unless we're clicking the popup itself + if wx.FindWindowAtPointer() is not a: + a.Dismiss() + + textctrl.Bind(wx.EVT_KILL_FOCUS, onkillfocus) + + if not a.frame.IsShown(): + update_popup(True) + + def get_popup_rect(): + coords = textctrl.IndexToCoords(textctrl.InsertionPoint) + rect = wx.RectPS(textctrl.ClientToScreen(coords), wx.Size(0, textctrl.CharHeight)) + return rect + + return a + diff --git a/digsby/src/gui/browser/__init__.py b/digsby/src/gui/browser/__init__.py new file mode 100644 index 0000000..961d327 --- /dev/null +++ b/digsby/src/gui/browser/__init__.py @@ -0,0 +1,41 @@ +import wx + +# +# import browser classes lazily +# + +if 'wxMSW' in wx.PlatformInfo: + USE_WEBKIT_AS_BROWSER = True + + if USE_WEBKIT_AS_BROWSER: + def Browser(*a, **k): + from gui.browser.webkit.webkitwindow import WebKitWindow + return WebKitWindow(*a, **k) + else: + def Browser(*a, **k): + from gui.browser.iewindow import IEWindow + return IEWindow(*a, **k) + +elif 'wxMac' in wx.PlatformInfo: + def Browser(*a, **k): + from gui.browser.mac_webkit import WebKitWindow + return WebKitWindow(*a, **k) + +else: + raise NotImplementedError('no Browser interface implemented for this platform') + +class BrowserFrame(wx.Frame): + def __init__(self, parent, title = '', size = wx.DefaultSize, pos = wx.DefaultPosition, url = '', style = wx.DEFAULT_FRAME_STYLE, name = '', external_links=True): + wx.Frame.__init__(self, parent, title = title, size = size, pos = pos, style = style, name = name) + + self.browser = Browser(self, url = url, external_links=external_links) + self.OnDoc = self.browser.OnDoc + +def reload_plugins(): + import wx.webview + if 'wxMSW' in wx.PlatformInfo and USE_WEBKIT_AS_BROWSER: + wx.webview.WebView.ReloadPlugins() + return True + + return False + diff --git a/digsby/src/gui/browser/iewindow.py b/digsby/src/gui/browser/iewindow.py new file mode 100644 index 0000000..8d8beff --- /dev/null +++ b/digsby/src/gui/browser/iewindow.py @@ -0,0 +1,113 @@ +''' + +embedded controllable ie + +''' + +from __future__ import with_statement +import wx +from random import randint +import wx.lib.iewin as iewin +from logging import getLogger; log = getLogger('iewindow') +from util.primitives.funcs import Delegate +from time import time +import stdpaths +import metrics + +class IEWindow(iewin.IEHtmlWindow): + def __init__(self, parent, initialContents = '', url = None): + metrics.event('IE Window Created') + + iewin.IEHtmlWindow.__init__(self, parent, style = wx.NO_BORDER) + + self.OnNav = Delegate() # Called for NavigateComplete2 events + self.OnBeforeNav = Delegate() # Called for Navigate2 events + self.OnDoc = Delegate() # Called for DocumentComplete events + + # allow security popups to appear + self._set_Silent(False) + + if url is not None: + self.seturl = url + assert isinstance(url, basestring) + self.LoadUrl(url) + else: + s = initialContents or '' + if s: + self.SetPage(s) + + def LoadUrl(self, url): + if isinstance(url, unicode): + import warnings + warnings.warn('LoadUrl called with a unicode: %r' % url) + url = str(url) + + if not isinstance(url, str): + raise TypeError('must pass a string to LoadUrl') + + return iewin.IEHtmlWindow.LoadUrl(self, url) + + def OnURL(self, url, callback): + if not callable(callback): + raise TypeError('callback must be callable') + + self.urltriggers[url] += [callback] + + @property + def FileURL(self): + try: + return 'file:///' + self.file.name.replace('\\', '/') + except AttributeError: + return self.seturl + + #SetPage = iewin.IEHtmlWindow.LoadString + + def SetPage(self, content): + # TODO: fix comtypes full IE support to not be SLOOOOOOOOOW + # + # At the moment, using LoadString requires comtypes to load the MSHTML + # interface to IE, which is something like a 9mb generated Python file, + # and memory usage spikes 40mb. + # + # To avoid this we write to a temporary file and use LoadUrl instead. + + tempname = 'digsby-%s-%s.html' % (time(), randint(1,9999)) # why doesn't TemporaryNamedFile work? + + p = stdpaths.temp / tempname + p.write_bytes(content) + return self.LoadUrl(p.url()) + + # COM overloads (see DWebBrowserEvents2 interface at http://msdn.microsoft.com/en-us/library/aa768283.aspx) + + def BeforeNavigate2(self, this, pDisp, URL, *a): + self.OnBeforeNav(URL[0]) + + def NavigateComplete2(self, this, pDisp, URL, *a): + self.OnNav(URL[0]) + + def DocumentComplete(self, this, pDisp, URL, *a): + self.OnDoc(URL[0]) + + +if __name__ == '__main__': + a = wx.PySimpleApp() + _ = lambda s: s + + fbSize = (646, 436) + url = 'http://www.google.com/' + + from util import trace + trace(IEWindow) + + f = wx.Frame(None, size = fbSize, title = 'ie test') + ie = IEWindow(f, url = url) + + def ondoc(e): + print type(e) + print e + print e.URL + + ie.Bind(iewin.EVT_DocumentComplete, ondoc) + + f.Show() + a.MainLoop() diff --git a/digsby/src/gui/browser/jsconsole.py b/digsby/src/gui/browser/jsconsole.py new file mode 100644 index 0000000..87fccdd --- /dev/null +++ b/digsby/src/gui/browser/jsconsole.py @@ -0,0 +1,82 @@ +from __future__ import with_statement +from util import traceguard +from weakref import ref +import wx + +ID_CLEAR = wx.NewId() + +try: _ +except: _ = lambda s:s + +console = None + +class JSConsoleFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, title = _('Javascript Console'), name = 'JavaScript Console') + + with traceguard: + from gui.toolbox import persist_window_pos, snap_pref + persist_window_pos(self, + defaultPos = wx.Point(50, 50), + defaultSize = wx.Size(400, 300), + nostack = True) + snap_pref(self) + + from gui import skin + self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon').Inverted) + + # don't allow menu events, etc, to go up to the IM window which + # is our parent. + self.SetExtraStyle(wx.WS_EX_BLOCK_EVENTS) + + self.construct() + self.Bind(wx.EVT_TOOL, self.on_clear, id = ID_CLEAR) + self.Bind(wx.EVT_CLOSE, self.close) + + def on_message(self, message, line_number, source_id): + self.console.AppendText('(line %d) %s (source: %s)\n' % (line_number, message, source_id)) + + def on_clear(self, e): + self.console.Clear() + + def construct(self): + self.console = wx.TextCtrl(self, -1, '', style = wx.TE_MULTILINE | wx.TE_READONLY) + + from gui import skin + toolbar = self.CreateToolBar(wx.NO_BORDER | wx.TB_HORIZONTAL | wx.TB_FLAT) + toolbar.AddTool(ID_CLEAR, _('Clear'), skin.get('AppDefaults.RemoveIcon'), wx.NullBitmap, + wx.ITEM_NORMAL, _('Clear')) + toolbar.Realize() + + def close(self, e): + global console + console = None + e.Skip() + + +def on_message(message, line_number, source_id): + global console + if console is None: return + console.on_message(message, line_number, source_id) + +def show_console(): + global console + + if console is not None: + if console.Visible: + wc = ref(console) + console.Close() + else: + console.Show() + console.Raise() + else: + console = JSConsoleFrame(None) + wx.CallAfter(console.Show) + +def main(): + a = wx.PySimpleApp() + JSConsoleFrame(None).Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/gui/browser/mac_webkit.py b/digsby/src/gui/browser/mac_webkit.py new file mode 100644 index 0000000..2afaf8b --- /dev/null +++ b/digsby/src/gui/browser/mac_webkit.py @@ -0,0 +1,85 @@ +''' + +embedded controllable ie + +''' + +from __future__ import with_statement +import wx +import wx.webkit as webkit +from logging import getLogger; log = getLogger('iewindow') +from util.primitives.funcs import Delegate + +class WebKitWindow(webkit.WebKitCtrl): + def __init__(self, parent, initialContents = '', url = None): + webkit.WebKitCtrl.__init__(self, parent, style = wx.NO_BORDER) + + self.OnNav = Delegate() # Called for NavigateComplete2 events + self.OnDoc = Delegate() # Called for DocumentComplete events + + # allow security popups to appear + #self._set_Silent(False) + + if url is not None: + self.seturl = url + assert isinstance(url, basestring) + self.LoadUrl(url) + else: + s = initialContents or '' + if s: + self.SetPage(s) + + self.Bind(webkit.EVT_WEBKIT_BEFORE_LOAD, self.BeforeLoad) + self.Bind(webkit.EVT_WEBKIT_STATE_CHANGED, self.StateChanged) + + def LoadUrl(self, url): + if isinstance(url, unicode): + import warnings + warnings.warn('LoadUrl called with a unicode: %r' % url) + url = str(url) + + if not isinstance(url, str): + raise TypeError('must pass a string to LoadUrl') + + return self.LoadURL(url) + + def OnURL(self, url, callback): + if not callable(callback): + raise TypeError('callback must be callable') + + self.urltriggers[url] += [callback] + + @property + def FileURL(self): + try: + return 'file:///' + self.file.name.replace('\\', '/') + except AttributeError: + return self.seturl + + #SetPage = iewin.IEHtmlWindow.LoadString + + def SetPage(self, content): + return self.SetPageSource(content) + + def BeforeLoad(self, event): + self.OnNav(event.GetURL()) + + def StateChanged(self, event): + if event.GetState() == webkit.WEBKIT_STATE_STOP: + self.OnDoc(event.GetURL()) + +if __name__ == '__main__': + a = wx.PySimpleApp() + _ = lambda s: s + + fbSize = (646, 436) + url = 'http://www.google.com/' + + from util import trace + trace(WebKitWindow) + + f = wx.Frame(None, size = fbSize, title = 'ie test') + wk = WebKitWindow(f, url = url) + + f.Show() + a.MainLoop() diff --git a/digsby/src/gui/browser/webkit/__init__.py b/digsby/src/gui/browser/webkit/__init__.py new file mode 100644 index 0000000..92252ff --- /dev/null +++ b/digsby/src/gui/browser/webkit/__init__.py @@ -0,0 +1,76 @@ +from .webkitwindow import WebKitWindow + +import logging +import wx.webview + +webview_to_logging_levels = { + wx.webview.TipMessageLevel: logging.DEBUG, + wx.webview.LogMessageLevel: logging.INFO, + wx.webview.WarningMessageLevel: logging.WARNING, + wx.webview.ErrorMessageLevel: logging.ERROR +} + +def setup_webview_logging(webview, log, logfilter=None, logbasedir=None): + log = logging.getLogger(log) if isinstance(log, basestring) else log + + if logbasedir is not None: + assert logfilter is None + + def logfilter(i): + filename = i['fn'] + if filename.startswith('file:///'): + filename = filename[len('file:///'):] + relpath = logbasedir.relpathto(filename) + if len(relpath) < len(filename): + i['fn'] = unicode(relpath) + return True + + if logfilter is None: + logfilter = lambda info: True + + def on_js_console_message(e): + info = dict( + level = webview_to_logging_levels.get(e.Level, logging.INFO), + fn = e.GetSourceID(), + lno = e.LineNumber, + msg = e.Message) + + if logfilter(info): + msg = '%(fn)s:%(lno)s | %(msg)s' % info + record = log.makeRecord(log.name, info['level'], info['fn'], info['lno'], msg, (), None, "(unknown function)", None) + log.handle(record) + + webview.Bind(wx.webview.EVT_WEBVIEW_CONSOLE_MESSAGE, on_js_console_message) + +class WebKitDisplay(wx.webview.WebView): + def __init__(self, parent): + wx.webview.WebView.__init__(self, parent) + self.Bind(wx.EVT_CONTEXT_MENU, lambda e: e.Skip(False)) + self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.on_before_load) + + def on_before_load(self, e): + e.Cancel() + wx.LaunchDefaultBrowser(e.URL) + +_origin_whitelist = {} +def get_origin_whitelist(): + return dict(_origin_whitelist) + + +def update_origin_whitelist(originURL, destProtocol, destDomain, allowSubdomains): + 'Makes an exception for XSS to destProtocol://destDomain from originURL.' + # keep a map of exceptions we've already added, since webkit's API + # for SecurityOrigin exceptions doesn't allow us to check if we already + # have + key = (originURL, destProtocol, destDomain, allowSubdomains) + + already_added = key in _origin_whitelist + if already_added: + return + + _origin_whitelist[key] = True + assert len(_origin_whitelist) < 100 # ensure we're not leaking--this map should stay small + + wx.webview.WebView.AddOriginAccessWhitelistEntry( + originURL, destProtocol, destDomain, allowSubdomains) + diff --git a/digsby/src/gui/browser/webkit/imageloader.py b/digsby/src/gui/browser/webkit/imageloader.py new file mode 100644 index 0000000..ae98382 --- /dev/null +++ b/digsby/src/gui/browser/webkit/imageloader.py @@ -0,0 +1,216 @@ +''' +maintains a hidden webview for loading images + +TODO: this could be replaced by exposing a webkit resource loader API to python. +''' + +from util.primitives.funcs import Delegate +from util.primitives.refs import better_ref +from gui.browser.webkit.webkitwindow import WebKitWindow +from simplejson import dumps as jsenc +from rpc.jsonrpc import JSPythonBridge +import wx + +_imageloader = None # global instance + +class LazyWebKitImage(object): + ''' + an object, given a url and a default image, which has a "lazy_load" function + which will return a cached image, or start downloading the image, and return it + at a later call. + ''' + + def __init__(self, url, default_image): + self.url = url + self.default_image = default_image + + def lazy_load(self, refresh_cb=None): + img = None + if self.url: + img = load_image(self.url, refresh_cb) + if img is None: + img = self.default_image + return img + +def load_image(url, refresh_cb=None): + global _imageloader + if _imageloader is None: + _imageloader = WebKitImageLoader() + + img = _imageloader.get_no_download(url) + if img is not None: + return img + + if refresh_cb is not None: + refresh_cb = better_ref(refresh_cb) + + def unhook(): + _imageloader.on_load -= on_load + _imageloader.on_error -= on_error + + def on_load(img, src): + if src == url: + unhook() + refresh_cb.maybe_call() + + def on_error(src): + if src == url: + unhook() + + _imageloader.on_load += on_load + _imageloader.on_error += on_error + + return _imageloader.get(url, check_cache=False) + +class WebKitImageLoader(object): + def __init__(self): + self.webview = None + self.on_load = Delegate() + self.on_error = Delegate() + + def _webview_on_call(self, obj): + method = obj['method'] + src = obj['params'][0]['src'] + + if method == 'onLoad': + img = self.webview.GetCachedBitmap(src) + self.on_load(img, src) + elif method == 'onError': + self.on_error(src) + else: + from pprint import pformat + raise AssertionError('unexpected JS call: ' + pformat(obj)) + + def GetWebView(self): + if self.webview is not None: + return self.webview + + self.frame = wx.Frame(None) + w = self.webview = WebKitWindow(self.frame) + w.js_to_stderr = True + + self.bridge = bridge = JSPythonBridge(self.webview) + bridge.on_call += self._webview_on_call + + import gui.skin + jslib = lambda name: jsenc((gui.skin.resourcedir() / 'html' / name).url()) + + html = ''' + + + + + + + + +''' % dict(pythonbridgelib=jslib('pythonbridge.js'), + utils=jslib('utils.js')) + + self.bridge.SetPageSource(html, 'file://imageloader') + return self.webview + + def get_no_download(self, url): + # first, see if the image is already in the cache. if so, just return it. + webview = self.GetWebView() + img = webview.GetCachedBitmap(url) + if img.Ok(): + return img + + def get(self, url, check_cache=True): + if check_cache: + img = self.get_no_download(url) + if img is not None: + return img + + # otherwise, find an existing image tag loading this url, or create one. + html = ''' +(function() { + +var img = document.getElementById(%(hashed_url)s); +if (!img) { + img = document.createElement('img'); + document.body.appendChild(img); + window.allImages.push(img); + img.setAttribute('id', %(hashed_url)s); + img.setAttribute('onLoad', 'onLoad(this);'); + img.setAttribute('onError', 'onError(this);'); + img.src = %(url)s; +} else { + if (img.load_state === 'loaded') + onLoad(img); + else if (img.load_state === 'error') + onError(img); +} + +img.lastAccessTime = new Date().getTime(); +if (window.allImages.length > window.MAX_IMAGES) + evictOld(); + +})(); + ''' % dict(url = jsenc(url), hashed_url=jsenc(urlhash(url))) + + self.GetWebView().RunScript(html) + + +import hashlib +def urlhash(url, algo=hashlib.md5): + return algo(url.encode('utf8')).hexdigest() + +def main(): + from tests.testapp import testapp + + app = testapp() + il = WebKitImageLoader() + + def test_onLoad(img, url): + print 'onLoad', img, url + + def test_onError(url): + print 'onError', url + + il.on_load += test_onLoad + il.on_error += test_onError + il.get('http://img.digsby.com/logos/digsby_196x196.png') + il.frame.Show() + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/browser/webkit/webkiteditor.py b/digsby/src/gui/browser/webkit/webkiteditor.py new file mode 100644 index 0000000..1e154e3 --- /dev/null +++ b/digsby/src/gui/browser/webkit/webkiteditor.py @@ -0,0 +1,189 @@ +''' + +WebKit editing + +''' + +import wx, wx.webview +from functools import partial + +class WebKitEditor(wx.webview.WebView): + ''' + Exposes editor functionality of WebKit through the execCommand Javascript API. + ''' + + def __init__(self, parent): + wx.webview.WebView.__init__(self, parent) + + def __getattr__(self, attr): + ''' + wkEditor.BackColor(True, '#ff0000') # Python becomes... + -> + document.execCommand('BackColor', true, '#ff0000'); // Javascript + ''' + try: + return object.__getattribute__(self, attr) + except AttributeError: + # any commands from the list below get forwarded to JavaScript + if attr in jseditor_commands: + return partial(self._execCommand, attr) + else: + raise + + def _execCommand(self, *a): + args = ', '.join(jsrepr(arg) for arg in a) + js = 'document.execCommand(%s);' % args + + print js + + return self.RunScript(js) + + +def jsrepr(o): + 'Python object -> Javascript equivalent' + + if isinstance(o, bool): + return str(o).lower() # True -> true + elif isinstance(o, unicode): + return repr(o)[1:] # u'string' -> 'string' + else: + return repr(o) + +# +# these commands are from the map in \WebKit\webcore\editing\JSEditor.cpp +# +jseditor_commands = ( + 'BackColor', + 'Bold', + 'Copy', + 'CreateLink', + 'Cut', + 'Delete', + 'FindString', + 'FontName', + 'FontSize', + 'FontSizeDelta', + 'ForeColor', + 'FormatBlock', + 'ForwardDelete', + 'HiliteColor', + 'Indent', + 'InsertHorizontalRule', + 'InsertHTML', + 'InsertImage', + 'InsertLineBreak', + 'InsertOrderedList', + 'InsertParagraph', + 'InsertNewlineInQuotedContent', + 'InsertText', + 'InsertUnorderedList', + 'Italic', + 'JustifyCenter', + 'JustifyFull', + 'JustifyLeft', + 'JustifyNone', + 'JustifyRight', + 'Outdent', + 'Paste', + 'PasteAndMatchStyle', + 'Print', + 'Redo', + 'RemoveFormat', + 'SelectAll', + 'Strikethrough', + 'Subscript', + 'Superscript', + 'Transpose', + 'Underline', + 'Undo', + 'Unlink', + 'Unselect' +) + + +if __name__ == '__main__': + + empty_editable_doc = '''\ + + + + + + +''' + + def Button(parent, text, callback, **kwargs): + button = wx.Button(parent, -1, text, **kwargs) + button.Bind(wx.EVT_BUTTON, lambda *e: callback()) + return button + + + + # construct gui + app = wx.PySimpleApp() + + + + + f = wx.Frame(None, title = 'WebKit Editor Test') + + wdc = wx.WindowDC(f) + gc = wx.GraphicsContext.Create(wdc) + + gc.SetFont(f.GetFont()) + wdc.SetFont(f.GetFont()) + print gc.GetTextExtent(' ') + print wdc.GetTextExtent(' ') + + + f.Sizer = fs = wx.BoxSizer(wx.VERTICAL) + + editor = WebKitEditor(f) + editor.SetPageSource(empty_editable_doc) + editor.MakeEditable(True) + + button = lambda label, callback: Button(f, label, callback, style = wx.BU_EXACTFIT) + + def get_set_color(title, okfunc): + c = wx.GetColourFromUser(f, caption = title) + if c.IsOk(): okfunc(c.GetAsString(wx.C2S_HTML_SYNTAX)) + + editbuttons = [ + ('B', editor.Bold), + ('I', editor.Italic), + ('U', editor.Underline), + ('A+', lambda: editor.fontsize(16)), + ('A-', lambda: editor.fontsize(11)), + ('bg', lambda: get_set_color('Background Color', lambda c: editor.BackColor(True, c))), + ('fg', lambda: get_set_color('Foreground Color', lambda c: editor.ForeColor(True, c))), + ] + + # layout gui + bsizer = wx.BoxSizer(wx.HORIZONTAL) + for label, callback in editbuttons: + bsizer.Add(button(label, callback)) + + fs.Add(bsizer, 0, wx.EXPAND) + fs.Add(editor, 1, wx.EXPAND) + + panel = wx.Panel(f) + panel.Sizer = wx.BoxSizer(wx.HORIZONTAL) + panel.Sizer.AddSpacer((50, 50)) + def paint(e): + dc = wx.PaintDC(panel) + dc.SetFont(f.GetFont()) + dc.DrawText('ggggggg', 0, 0) + + panel.Bind(wx.EVT_PAINT, paint) + + fs.Add(panel, 0, wx.EXPAND) + + # run + wx.CallAfter(f.Show) + app.MainLoop() diff --git a/digsby/src/gui/browser/webkit/webkiteditsource.py b/digsby/src/gui/browser/webkit/webkiteditsource.py new file mode 100644 index 0000000..addf151 --- /dev/null +++ b/digsby/src/gui/browser/webkit/webkiteditsource.py @@ -0,0 +1,97 @@ +from __future__ import with_statement + +def EditSource(self): + 'Brings up a simple editor with the HTML source of this window, available for editing.' + + import wx + from util import soupify + from wx.stc import StyledTextCtrl, STC_LEX_HTML + + font = wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_NORMAL, False, "Consolas") + + f = wx.Frame(wx.GetTopLevelParent(self), -1, 'View Source', name = 'View Source', size = (640, 480)) + s = wx.BoxSizer(wx.VERTICAL) + t = StyledTextCtrl(f, -1, wx.DefaultPosition, wx.DefaultSize, wx.NO_BORDER) + #t.SetLexer(STC_LEX_HTML) + + orightml = self.HTML + + # TODO: BeautifulSoup is more destructive than is useful here. + html = soupify(orightml).prettify() + t.SetText(html) + #t.SetFont(font) + wx.CallAfter(t.SetSelection, 0, 0) + + buttons = wx.Panel(f) + save = wx.Button(buttons, -1, '&Save') + save.Bind(wx.EVT_BUTTON, lambda e: self.SetHTML(t.GetText())) + + save_as_file = wx.Button(buttons, -1, 'Save &As File...') + + def onsaveasfile(e): + diag = wx.FileDialog(self, "Save HTML", "contents.html", style=wx.SAVE) + + if diag.ShowModal() == wx.ID_OK: + with open(diag.GetPath(), 'wb') as f: + f.write(orightml.encode('utf-8')) + + save_as_file.Bind(wx.EVT_BUTTON, onsaveasfile) + + copybutton = wx.Button(buttons, -1, _('&Copy')) + + def openinbrowser(e): + from subprocess import Popen + import os.path, tempfile + + fdesc, fname = tempfile.mkstemp() + with os.fdopen(fdesc, 'w') as f: + f.write(t.GetText().encode('utf-8')) + + if "wxMSW" in wx.PlatformInfo: + from common import pref + from path import path + + browser_exe = pref('debug.message_area.debug_browser', + r'c:\Program Files\Safari\Safari.exe', type=basestring) + browser_exe = path(browser_exe).expand() + + if browser_exe.isfile(): + Popen([browser_exe, fname]) + else: + wx.MessageBox('Error launching browser:\n\n' + '"%s"\n\n' + 'Please set the "debug.message_area.debug_browser" pref to\n' + 'the path to your web browser.' % browser_exe, + 'Open in Browser') + else: + import webbrowser + webbrowser.open_new("file://" + fname) + + + openbutton = wx.Button(buttons, -1, _('&Open in Browser')) + openbutton.Bind(wx.EVT_BUTTON, openinbrowser) + openbutton.SetToolTipString(_('Launches browser in pref "debug.message_area.debug_browser"')) + + def docopy(e): + clip = wx.TheClipboard + if clip.Open(): + clip.SetData( wx.TextDataObject(t.Value) ) + clip.Close() + + copybutton.Bind(wx.EVT_BUTTON, docopy) + + buttons.Sizer = wx.BoxSizer(wx.HORIZONTAL) + buttons.Sizer.AddMany([save, copybutton, openbutton, save_as_file]) + + s.Add(t, 1, wx.EXPAND) + s.Add(buttons, 0, wx.EXPAND) + f.SetSizer(s) + + # remember position and cascade when necessary + from gui.toolbox import persist_window_pos + persist_window_pos(f) + f.EnsureNotStacked() + + f.Show() + diff --git a/digsby/src/gui/browser/webkit/webkitwindow.py b/digsby/src/gui/browser/webkit/webkitwindow.py new file mode 100644 index 0000000..58aa2e7 --- /dev/null +++ b/digsby/src/gui/browser/webkit/webkitwindow.py @@ -0,0 +1,225 @@ +''' + +Web browser control implemented on top of wx.webview.WebView, which is +based on WebKit. + +''' +from __future__ import with_statement + +import wx +from collections import defaultdict +from logging import getLogger; log = getLogger('webkitwindow') +from util import traceguard +from util.primitives.funcs import Delegate + +try: + import webview + WebView = webview.WebView +except ImportError: + from traceback import print_exc + print_exc() + + class WebKitWindow(wx.Panel): + pass +else: + MinimumTextSizeMultiplier = 0.5 + MaximumTextSizeMultiplier = 3.0 + TextSizeMultiplierRatio = 1.2 + + class WebKitWindow(WebView): + def __init__(self, parent, initialContents = '', contentPath = 'file:///c:/', url = None, simple_events = False, external_links=True, **opts): + super(WebKitWindow, self).__init__(parent, size=wx.Size(200,200), **opts) + + self._jsqueue_enabled = True + self.jsqueue = [] + self.js_to_stderr = False + + self.ExternalLinks = external_links + + self.OnNav = Delegate() # Called for NavigateComplete2 events + self.OnDoc = Delegate() # Called for DocumentComplete events + self.OnTitleChanged = Delegate() + + Bind = self.Bind + Bind(wx.EVT_CONTEXT_MENU, self.__OnContextMenu) + Bind(webview.EVT_WEBVIEW_LOAD, self.OnStateChanged) + Bind(webview.EVT_WEBVIEW_BEFORE_LOAD, self.OnBeforeLoad) + Bind(webview.EVT_WEBVIEW_RECEIVED_TITLE, self.OnTitleChanged) + from gui.browser.webkit import setup_webview_logging + setup_webview_logging(self, 'webview') + + self.urltriggers = defaultdict(list) + + if initialContents and url is not None: + raise ValueError("please specify initialContents or url, but not both") + + # some APIs call LoadUrl + self.LoadUrl = self.LoadURL + + if url is not None: + self.LoadURL(url) + else: + self.SetPageSource(initialContents, 'file:///') + + self.BlockWebKitMenu = True + + + def set_jsqueue_enabled(self, enabled): + self._jsqueue_enabled = enabled + + def set_window_open_redirects_to_browser(self, url_callback=None): + '''Redirects window.open calls in this webview to the users's default browser.''' + + add_window_open_redirect(self, url_callback) + + def __OnContextMenu(self, e): + # disable webkit's default context menus + if not self.BlockWebKitMenu: + e.Skip() + + def SetPageSource(self, source, baseUrl): + if self._jsqueue_enabled: + del self.jsqueue[:] + self._js_paused = True + WebView.SetPageSource(self, source, baseUrl) + + def SetPage(self, source, baseUrl=None): + if baseUrl is None: + baseUrl = 'file:///' + return self.SetPageSource(source, baseUrl) + + def RunScript(self, s, cb = None, immediate=False): + # ensure that the page loader isn't loading the current page--we have + # to wait until after its done to execute javascript + assert wx.IsMainThread() + if self._jsqueue_enabled and self._js_paused: + if immediate: + pass #log.debug('ignoring immediate javascript call: %r', s) + else: + #log.debug('delaying execution of JS') + self.jsqueue.append((s, cb)) + else: + val = self._runscript(s) + if cb is None: + return val + else: + cb(val) + + # __call__ = webview.WebView.RunScript + + def AppendToPage(self, content): + escaped = content.replace('\n', '\\\n').replace('"', '\\"') + self.RunScript('appendMessage("%s");' % escaped) + + def ScrollToBottom(self): + self.RunScript('window.scroll(0, 10000000);') + + def OnStateChanged(self, e): + e.Skip() + state = e.GetState() + + if state == webview.WEBVIEW_LOAD_NEGOTIATING: + #log.debug('WEBVIEW_LOAD_NEGOTIATING') + #log.debug('e.URL %r', e.URL) + # pause javascript when loading a page + self._pause_javascript() + elif state == webview.WEBVIEW_LOAD_DOC_COMPLETED: + #log.debug('WEBVIEW_LOAD_DOC_COMPLETED, calling _execute_delayed_javascript') + #log.debug('e.URL %r', e.URL) + # when the page is done loading, execute delayed javascript + self._execute_delayed_javascript() + self.OnDoc(e.URL) + + if state == webview.WEBVIEW_LOAD_DL_COMPLETED: + self.OnNav(e.URL) + + def _pause_javascript(self): + self._js_paused = True + + def _execute_delayed_javascript(self): + self._js_paused = False + + #if self.jsqueue: + #log.debug('done loading, executing %d JS calls', len(self.jsqueue)) + + for n, (script, cb) in enumerate(self.jsqueue): + val = self._runscript(script) + if val != 'undefined': + log.debug('result %d: %r' % (n, val)) + if cb is not None: + cb(val) + + del self.jsqueue[:] + + def _runscript(self, s): + return WebView.RunScript(self, s) + + def OnBeforeLoad(self, e): + type = e.GetNavigationType() + e.Skip() + + if e.IsCancelled(): + return + + url = e.GetURL() + if type == webview.WEBVIEW_NAV_LINK_CLICKED: + + callback = self.urltriggers.get(url, None) + if callback is not None: + with traceguard: callback() + e.Cancel() + + if self.ExternalLinks and not url.startswith('javascript'): + wx.LaunchDefaultBrowser(url) + e.Cancel() + + + def OnURL(self, url, callback): + if not hasattr(callback, '__call__'): + raise TypeError('OnURL takes a callable') + self.urltriggers[url].append(callback) + + def SetHTML(self, contents, baseUrl = 'file:///'): + self.SetPageSource(contents, baseUrl) + + HTML = property(webview.WebView.GetPageSource, SetHTML) + Title = property(webview.WebView.GetPageTitle, webview.WebView.SetPageTitle) + + def EditSource(self): + from gui.browser.webkit.webkiteditsource import EditSource + return EditSource(self) + +def add_window_open_redirect(self, url_callback=None, blank_opens_browser=False): + # window.open calls in JavaScript fire EVT_WEBVIEW_NEW_WINDOW without an + # event.URL argument. this is an invisible webview frame to work around + # that fact for the times we want window.open to open links in a real + # browser. + + if url_callback is None: + url_callback = wx.LaunchDefaultBrowser + + class WindowOpenRedirector(wx.webview.WebView): + def __init__(self): + self.frame = wx.Frame(None) + wx.webview.WebView.__init__(self, self.frame) + self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.__onbeforeload) + def __onbeforeload(self, e): + print '*** caught window.open(%r)' % e.URL + url_callback(e.URL) + e.Cancel() + wx.CallAfter(self.Destroy) + + def _on_new_window(e): + print '_on_new_window', e, e.URL + if not e.URL: + e.WebView = WindowOpenRedirector() + e.Skip(False) + else: + if blank_opens_browser: + url_callback(e.URL) + e.Skip(False) + else: + e.Skip() + + self.Bind(wx.webview.EVT_WEBVIEW_NEW_WINDOW, _on_new_window) + diff --git a/digsby/src/gui/buddylist/__init__.py b/digsby/src/gui/buddylist/__init__.py new file mode 100644 index 0000000..a952191 --- /dev/null +++ b/digsby/src/gui/buddylist/__init__.py @@ -0,0 +1,3 @@ +from buddylist import * +from buddylistframe import * +from renderers import * \ No newline at end of file diff --git a/digsby/src/gui/buddylist/accountlist.py b/digsby/src/gui/buddylist/accountlist.py new file mode 100644 index 0000000..4e9d4a9 --- /dev/null +++ b/digsby/src/gui/buddylist/accountlist.py @@ -0,0 +1,343 @@ +''' + +Buddylist panel for displaying email and social network accounts. + +''' +from __future__ import with_statement +import wx +from common.emailaccount import EmailAccount +import common.actions as actions + +from gui.textutil import default_font +from common import profile, pref, setpref +from gui.skin import get as skin +from gui.skin.skinobjects import SkinColor +from util.primitives.funcs import Delegate +from gui.uberwidgets.uberwidget import UberWidget +from gui.uberwidgets.umenu import UMenu + +from logging import getLogger; log = getLogger('accountlist') + +from gui.buddylist.accounttray import should_grey + + +from gui.toolbox.refreshtimer import refreshtimer + +class AccountList(wx.VListBox,UberWidget): + 'Shows a list of active accounts with counts of new items' + + def __init__(self, + parent, + accts, + infobox, + skinkey, + prefkey = None, + onDoubleClick = None, # a callable taking an acct + labelCallback = None, # a callable: acct -> unicode + ): + wx.VListBox.__init__(self, parent) + self.SetSkinKey(skinkey) + + self.prefkey = prefkey + self.unlocked = pref(self.prefkey + '.unlocked', True) + + self._obs_link = None + self.infobox = infobox + infobox.Befriend(self) + + self.accts = accts + + self.willreconaccts= set() + + self.itemheight = 0 + self.UpdateSkin() + + Bind = self.Bind + Bind(wx.EVT_MOTION, self.OnMouseMotion) + Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseOut) + Bind(wx.EVT_RIGHT_UP, self.OnRightUp) + Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) + Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDblClick) + Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseLost) + + self.BuildList() + + self.menu = UMenu(self) + + self.OnDoubleClick = Delegate([onDoubleClick] if onDoubleClick is not None else []) + self.labelCallback = labelCallback + + self.BindObservers() + + def OnClose(self, e = None): + """ + Unbinds observer when this widget closes + """ + log.info('OnClose: %r', self) + self.UnbindObservers() + + def WhenOrderPrefChanged(self, sortorder): + if self.order != sortorder: + self.order = sortorder + active = dict([(acct.id, acct) for acct in self.active]) + self.active = [active[id] for id in sortorder if id in active] + self.Refresh() + + def WhenUnlockedPrefChanged(self, unlcoked): + self.unlocked = pref(self.prefkey + '.unlocked', True) + + def BindObservers(self): + """ + Sets up observers on a list of accounts + """ + self._obs_link = self.accts.add_list_observer(self.BuildList, self.WhenStateChanged, 'state', 'enabled', 'count','offline_reason', 'alerts', 'alias') + profile.prefs.link(self.prefkey + '.order', self.WhenOrderPrefChanged, False) + profile.prefs.link(self.prefkey + '.unlocked', self.WhenUnlockedPrefChanged, False) + + def UnbindObservers(self): + 'Removes observers on a list of accounts' + + if self._obs_link is not None: + self._obs_link.disconnect() + self._obs_link = None + + def CalledAfterRefreshLine(self,acct): + try: + self.RefreshLine(self.active.index(acct)) + except ValueError: + self.Refresh() + + + def WhenStateChanged(self, acct, attr, old, new): + 'This handles all changes on an account level.' + + #update new item count + if attr in ('count', 'alerts', 'alias'): + wx.CallAfter(self.CalledAfterRefreshLine,acct) + #rebuild list when account is disabled or enabled + elif attr == 'enabled': + wx.CallAfter(self.BuildList) + #Offline reason has changed, set up message + elif attr == 'state' or attr == 'offline_reason': + if acct.offline_reason == acct.Reasons.WILL_RECONNECT: + self.willreconaccts.add(acct) + else: + self.willreconaccts.discard(acct) + + if len(self.willreconaccts): + refreshtimer().Register(self) + else: + refreshtimer().UnRegister(self) + + self.Refresh() + + + + + def OnMouseOut(self, event = None): + """ + Make sure selection gets updated on mouse out + """ + i = self.Selection + if i != -1: + self.RefreshLine(i) + + self.Selection = -1 + + def OnMouseWheel(self, e): + # foward mouse wheel events to the infobox. + if self.infobox.IsShown(): + self.infobox.on_mousewheel(e) + + def OnMouseMotion(self,event): + """ + Selection gets update and infobox gets requested on mouse over item + """ + + mp = event.Position + hit = self.HitTest(mp) + dragging = event.Dragging() + selection = self.Selection + active = self.active + + if self.unlocked and event.LeftIsDown() and dragging and self.HasCapture() and -1 not in (selection, hit) and hit != selection: + item = active[selection] + active.pop(selection) + active.insert(hit, item) + + sortorder = self.order + sortorder.remove(item.id) + i = sortorder.index(active[hit-1].id) + 1 if hit > 0 else 0 + sortorder.insert(i, item.id) + setpref(self.prefkey + '.order', sortorder) + + self.Refresh() + + self.Selection = hit + self.TryShowInfobox(hit) + + def OnLeftUp(self, event): + + while self.HasCapture(): + self.ReleaseMouse() + + if not self.ClientRect.Contains(event.Position): + self.OnMouseOut(event) + + def OnMouseLost(self, event): + if not self.ClientRect.Contains(self.ScreenToClient(wx.GetMousePosition())): + self.OnMouseOut() + + def OnLeftDown(self,event): + self.infobox.quickshow=True + self.TryShowInfobox(self.Selection) + + + if not self.HasCapture(): + self.CaptureMouse() + + def TryShowInfobox(self,i): + if pref('infobox.show', True) and i >= 0: + p = self.Parent + pl = p.ClientToScreen((0, self.Position.y + self.OnMeasureItem(0) * i)) + pr = pl + (p.Size.width, 0) + + self.infobox.Display(pl, pr, self.active[i]) + + def OnLeftDblClick(self,event): + """ + performs the action associated with the list on DOuble Click + """ + self.OnDoubleClick(self.active[self.Selection]) + self.infobox.Hide() + + def ToggleOrderLock(self): + self.unlocked = not self.unlocked + setpref(self.prefkey + '.unlocked', self.unlocked) + + def OnRightUp(self, event): + """ + Generate and open menu on right click + """ + if self.Selection >= 0: + # populate the popup menu with actions + self.menu.RemoveAllItems() + + acct = self.active[self.Selection] + if hasattr(getattr(acct, 'menu_actions', None), '__call__'): + acct.menu_actions(self.menu) + elif isinstance(acct, EmailAccount): + #HAX: an ugly hack until Email-specific actions are removed from EmailAccount. + actions.menu(wx.FindWindowByName('Buddy List'), acct, menu = self.menu, search_bases = False, cls = EmailAccount) + else: + actions.menu(wx.FindWindowByName('Buddy List'), acct, menu = self.menu) + + self.menu.AddSep() + +# if self.prefkey is not None: +# unlockitem = self.menu.AddCheckItem(_('Allow Rearrange'), callback = self.ToggleOrderLock) +# unlockitem.Check(self.unlocked) + + self.menu.PopupMenu() + + def BuildList(self,*__): + """ + When the account list changes rebuild the list of items to display. + Then it recalculates size needs. + """ + + try: self.__i += 1 + except: self.__i = 1 + + accts = self.accts + + sortorder = pref(self.prefkey + '.order') + self.order = sortorder[:] + + if not len(sortorder): + sortorder = [acct.id for acct in accts] + elif len(sortorder) != len(accts) or set(acct.id for acct in accts) != set(sortorder): + for acct in accts: + if acct.id not in sortorder: + i = accts.index(acct) + i = sortorder.index(self.accts[i-1].id) + 1 if i > 0 else 0 + sortorder.insert(i, acct.id) + + + sortset = set(sortorder) + if len(sortorder) != len(sortset): + cleansortorder = [] + for i in sortorder: + if i in sortset: + cleansortorder.append(i) + sortset.remove(i) + sortorder = cleansortorder + + + if self.order != sortorder: + setpref(self.prefkey + '.order', sortorder) + + + active = dict([(acct.id, acct) for acct in accts if acct.enabled]) + self.active = [active[id] for id in sortorder if id in active] + + with self.Frozen(): + self.ItemCount = len(self.active) + self.Size = self.MinSize = wx.Size(-1, self.ItemCount * self.itemheight) + self.Top.Layout() + self.Top.Refresh() + + self.Refresh() + + def UpdateSkin(self): + """ + The usual + """ + skinget = lambda s, default: skin('%s.%s' % (self.skinkey, s), default) + + self.padding = skinget('padding',lambda: wx.Point(3,3)) + self.Font = skinget('font', default_font) + self.iconsize = skinget('iconsize',16) + self.itemheight = max(self.iconsize,self.Font.LineHeight)+self.padding.y*2 + + default_color = SkinColor(wx.Color(225,255,225)) + self.bg = skinget('backgrounds.normal', default_color) + self.selbg = skinget('backgrounds.hover', default_color) + + self.fontcolor = skinget('fontcolors.normal',wx.BLACK) + self.selfontcolor = skinget('fontcolors.hover',wx.BLACK) + + self.MinSize = wx.Size(-1, self.itemheight * self.ItemCount) + + def OnMeasureItem(self, n): + "Returns the predetermined item height" + + return self.itemheight + + def OnDrawBackground(self, dc, rect, n): + getattr(self, 'selbg' if self.Selection == n else 'bg').Draw(dc, rect, n) + + def OnDrawItem(self, dc, rect, n): + 'Draw the foreground content of the item.' + + dc.Font = self.Font + dc.TextForeground = self.selfontcolor if n == self.Selection else self.fontcolor + + acct = self.active[n] + iconsize=self.iconsize + + if (hasattr(acct, 'count') and acct.count > 0) or not should_grey(acct): + icon = acct.icon.Resized(iconsize) + else: + icon = acct.icon.Greyed.Resized(iconsize) + + pad = self.padding.x + + dc.DrawBitmap(icon, rect.x + pad, rect.y + self.itemheight / 2 - icon.Height/2, True) + + xoff = iconsize + 2 * pad + textRect = wx.Rect(rect.x + xoff, rect.y + self.itemheight / 2 - dc.Font.LineHeight/2, rect.width - xoff, dc.Font.LineHeight) + dc.DrawTruncatedText(self.labelCallback(acct), textRect) + diff --git a/digsby/src/gui/buddylist/accounttray.py b/digsby/src/gui/buddylist/accounttray.py new file mode 100644 index 0000000..39bb521 --- /dev/null +++ b/digsby/src/gui/buddylist/accounttray.py @@ -0,0 +1,267 @@ +''' +tray icons +''' +import config + +import wx +from wx import Point +from gui.taskbar import DigsbyTaskBarIcon +import common.actions as actions +from common import pref +from gui.toolbox import draw_tiny_text, Monitor, GetDoubleClickTime + +from util import try_this +import social +from traceback import print_exc +from operator import itemgetter +import common +import protocols +from gettext import ngettext + + +class ITrayIconProvider(protocols.Interface): + 'This is an intermediate interface, which should be superseded in the future' + def tray_icon_class(): + ''' + returns a class that can be constructed with (acct, infobox) on Win + and .initWithAccount(acct, infobox) on mac + ''' + pass + +class AccountTrayIconProvider(object): + protocols.advise(instancesProvide=[ITrayIconProvider], asAdapterForTypes=[common.AccountBase]) + def __init__(self, subject): + self.subject = subject + + def tray_icon_class(self): + from common.emailaccount import EmailAccount + from myspace.MyspaceAccount import MyspaceAccount as MySpace + acct = self.subject + if isinstance(acct, EmailAccount): + return EmailTrayIcon + elif isinstance(acct, MySpace): + return MyspaceTrayIcon + elif hasattr(acct, 'tray_icon_class'): + return acct.tray_icon_class() #should this be on the Icon class? can we set infobox at a later time and have the constructor be ok? + elif isinstance(acct, social.network): + return SocialAccountTrayIcon + else: + assert False, type(acct) + +def should_grey(acct): + "If this returns True, the account's tray icon will be greyed out when its count is zero." + + return not isinstance(acct, social.network) + +baseAccountTrayClass = DigsbyTaskBarIcon +if config.platform == 'mac': + from gui.native.mac import macmenuicon + baseAccountTrayClass = macmenuicon.MenuBarIconDelegate + +class AccountTrayIcon(baseAccountTrayClass): + @classmethod + def create(cls, acct, infobox): + ### + ### TODO: this is less awful, but still awful + ### + + trayClass = ITrayIconProvider(acct).tray_icon_class() + + if config.platform == 'mac': + object = trayClass.alloc().init() + object.initWithAccount(acct, infobox) + return object + else: + return trayClass(acct, infobox) + + def __init__(self, acct, infobox = None): + + # This method doesn't run for PyObjC icons (see initWithAccount instead), so it's okay + # to put wx and Observable code in here. + + self.acct = acct + self.infobox = infobox + from gui.uberwidgets.umenu import UMenu + self._menu = UMenu(wx.FindWindowByName('Buddy List'), onshow = self.update_menu) + + # generate unique tray icon IDs for each account that are persistent + # across program runs (specifically, for Windows' tray icon hiding options) + trayid = hash('accounttrayicon_' + acct.protocol + '_' + acct.name) + + super(AccountTrayIcon, self).__init__(acct.icon, menu = self._menu, id = trayid) + + self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_click) + self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.on_double_click) + + self.register_observers(acct, self.on_account_updated) + self.on_account_updated() + + def update_menu(self, event=None): + self._menu.RemoveAllItems() + actions.menu(wx.FindWindowByName('Buddy List'), self.acct, cls = type(self.acct), menu = self._menu) + + def on_click(self, e = None): + try: dclick_timer = self.dclick_timer + except AttributeError: + dclick_timer = self.dclick_timer = wx.PyTimer(lambda: None) + + if dclick_timer.IsRunning(): + self.on_double_click() + dclick_timer.Stop() + else: + dclick_timer.StartOneShot(GetDoubleClickTime()) + self.show_infobox() + + e.Skip() + + + def on_double_click(self, e = None): + if self.infobox.IsShown(): + self.infobox.Hide() + + url = self.acct.DefaultAction() + + if url is not None: + wx.LaunchDefaultBrowser(url) + + def show_infobox(self): + if not self.infobox: return + + info = self.infobox + + if info.IsShown() and getattr(info, 'account', None) is self.acct: + info.Hide() + else: + pt = self.get_infobox_tray_position() + info.ShowFromTray(pt, self.acct) +# info.Show() + + # tell the infobox to gain focus, so the mousewheel works + wx.CallAfter(info.do_focus) + + def get_infobox_tray_position(self): + #TODO: find taskbar position from the correct HWND. this code assumes the mouse + # is on the same display as the tray, and that the tray is on the bottom of the + # "client area" rectangle returned by wxDisplay + try: + import cgui + r = cgui.GetTrayRect() + pt = Point(r.Right - r.Width / 2, r.Bottom - r.Height / 2) + + display = Monitor.GetFromPoint(pt, find_near = True) + rect = display.GetClientArea() + distances = [] + + for p in ('TopLeft', 'TopRight', 'BottomLeft', 'BottomRight'): + corner = getattr(rect, p) + distances.append((corner, corner.DistanceTo(pt), p)) + + distances.sort(key = itemgetter(1)) + corner, distance, name = distances[0] + return corner + + except Exception: + print_exc() + return Monitor.GetFromPointer().ClientArea.BottomRight + + def Destroy(self): + self._destroyed = True + + if not config.platform == 'mac': + self.unregister_observers(self.acct, self.on_account_updated) + + return super(AccountTrayIcon, self).Destroy() + + @property + def count_string(self): + acct = self.acct + if acct.offline_reason != acct.Reasons.NONE: + count = 'X' + else: + count = getattr(acct, 'count', 0) + + return count + + def on_account_updated(self, obj=None, attr=None, old=None, new=None): + obj_or_event = obj + if not self or getattr(self, '_destroyed', False): + return + + acct = self.acct + count = self.count_string + + if acct.enabled: + # todo: remove this lame way figure out icon size + icon = acct.icon.PIL.Resized(self._IconSize) + + if self.should_show_count() and count: + # place text in the corner + icon = draw_tiny_text(icon, str(count)).WX + + if pref('trayicons.email.gray_on_empty', True) and count in (0, 'X') and should_grey(acct): + icon = icon.WXB.Greyed + + self.SetIcon(icon, self.Tooltip) + + def should_show_count(self): + return pref('trayicons.email.show_count', True) + + @property + def Tooltip(self): + return '' + +class UpdateMixinAccountTrayIcon(AccountTrayIcon): + def register_observers(self, acct, callback): + acct.add_observer(callback, 'count', 'state') + + def unregister_observers(self, acct, callback): + acct.remove_observer(callback, 'count', 'state') + +class EmailTrayIcon(UpdateMixinAccountTrayIcon): + def update_menu(self, event=None): + from common.emailaccount import EmailAccount + + self._menu.RemoveAllItems() + actions.menu(wx.FindWindowByName('Buddy List'), self.acct, cls = EmailAccount, search_bases = False, + menu = self._menu) + + + @property + def Tooltip(self): + c = self.count_string + if c == 'X': + return _(u'{account.offline_reason} ({account.email_address})').format(account=self.acct) + else: + try: + count = int(c) + except ValueError: + count = 0 + + return ngettext(u'{count} unread message ({account.email_address})', + u'{count} unread messages ({account.email_address})', count).format(count=c, account=self.acct) + + +# +#TODO: maybe have the accounts "publish" which attributes to observe? this is dumb. +# +class SocialAccountTrayIcon(UpdateMixinAccountTrayIcon): + @property + def Tooltip(self): + c = self.count_string + if c == 'X': + return _(u'{account.offline_reason} ({account.name})').format(account=self.acct) + else: + return try_this(lambda: ngettext(u'{count} new alert ({account.name})', + u'{count} new alerts ({account.name})').format(count=c, account=self.acct), '') + +class MyspaceTrayIcon(SocialAccountTrayIcon): + def register_observers(self, acct, callback): + SocialAccountTrayIcon.register_observers(self, acct, callback) + acct.add_observer(callback, 'alerts') + + def unregister_observers(self, acct, callback): + SocialAccountTrayIcon.unregister_observers(self, acct, callback) + acct.remove_observer(callback, 'alerts') + + + diff --git a/digsby/src/gui/buddylist/buddylist.py b/digsby/src/gui/buddylist/buddylist.py new file mode 100644 index 0000000..3278b20 --- /dev/null +++ b/digsby/src/gui/buddylist/buddylist.py @@ -0,0 +1,1015 @@ +''' +Buddy-list specific functionality. +''' +from __future__ import with_statement +from __future__ import division +from gui.toolbox.scrolling import WheelScrollMixin + +DEFAULT_FIND_TIMEOUTMS = 1500 +IDLE_UPDATE_SECS = 30 + +EXPANSION_STATE_KEY = 'collapsedgroups' +EXPANSION_SAVE_TIME_SECS = 4 + +NEED_HELP_LINK = 'http://wiki.digsby.com/doku.php?id=gettingstarted#adding_your_accounts' + +import sys +import config +import wx +from wx import Rect +import actionIDs +from wx import StockCursor, CURSOR_HAND, CURSOR_ARROW + +from gui.contactdialogs import MetaContactDialog +from gui.treelist import TreeList, TreeListModel +from gui.uberwidgets.umenu import UMenu +from gui.textutil import default_font +from contacts.buddylistsort import SpecialGroup + +from gui import skin +from gui.skin.skinobjects import SkinColor +from contacts import DGroup, Contact, MetaContact +from traceback import print_exc + +GroupTypes = (DGroup, ) + +from common import profile, prefprop, pref +from util import callsback, Storage as S, traceguard, delayed_call, default_timer, try_this + +from collections import defaultdict + +from logging import getLogger +log = getLogger('buddylist'); info = log.info + +# keys that cause the IM window to open +activate_keys = set([wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]) + +class BuddyListBase(TreeList): + 'Base buddy list class.' + + def __init__(self, parent, model, keyhandler = None): + TreeList.__init__(self, parent, model, keyhandler = keyhandler) + self.last_pair = (-1, -1) + self.hilite = None # Where the mouse is dragging. + + self.SetDrawCallback(self.cskin_drawcallback) + self.SetPaintCallback(self.PostPaint) + + def cskin_drawcallback(self, dc, rect, n): + self.OnDrawBackground(dc, Rect(*rect), n) + self.OnDrawItem(dc, Rect(*rect), n) + self.OnDrawSeparator(dc, Rect(*rect), n) + + def OnDrawSeparator( self, dc, rect, n ): + 'Draws visual feedback when dragging.' + + bar, box = self.dragimgs.bar, self.dragimgs.box + + hilite = getattr(self, 'hilite') + if hilite: + area, i, drop_to = hilite + s = bar.Size + +# print "area: %s, n: %d, i: %d, (drop_to): %r" % (area, n, i, drop_to) + + if area == 'below_group' and self.model.is_expanded(i): + if n == i + len(drop_to): + bar.Draw(dc, Rect(rect.X, rect.Bottom - s.height / 2 + 1, rect.Width, s.height)) + return + elif area == 'below_group': + area = 'below' + + if n == i: + if area == 'box': + box.Draw(dc, rect, n) + elif area == 'above': + bar.Draw(dc, Rect(rect.X, rect.Y - s.height / 2, rect.Width, s.height)) + elif area == 'below': + bar.Draw(dc, Rect(rect.X, rect.Bottom - s.height / 2 + 1, rect.Width, s.height)) + elif n == i + 1: + if area == 'below': + bar.Draw(dc, Rect(rect.X, rect.Y - s.height / 2, rect.Width, s.height)) + elif n == i - 1: + if area == 'above': + bar.Draw(dc, Rect(rect.X, rect.Bottom - s.height / 2 + 1, rect.Width, s.height)) + +class BuddyList(WheelScrollMixin, BuddyListBase): + 'Main buddy list control.' + + # The amount of space given to "borders" between buddy list elements during + # drag and drop. + def __init__(self, parent, infobox, collapsed = None, keyhandler = None): + # load expanded groups + try: collapsed = eval(profile.localprefs[EXPANSION_STATE_KEY]) + except: collapsed = [u'OfflineGroup_Offline'] # TODO: actually run expanded_id on the offline group to obtain this string + + self.dragResult = wx.DragMove + self.infobox_scrollers = set() + + # an empty group forms the root of the hierarchy + model = TreeListModel( collapsed = collapsed ) + + # store idle indices for a periodical refresh + self.idle_indices = [] + self.idle_timer = wx.PyTimer(self.refresh_idle_buddies) + + # save expansion after EXPANSION_SAVE_TIME seconds + def save_expansion(): profile.localprefs[EXPANSION_STATE_KEY] = repr(list(model.collapsed)) + profile.PreDisconnectHooks.append(save_expansion) + model.expansion_state_changed += delayed_call(save_expansion, EXPANSION_SAVE_TIME_SECS) + model.expansion_state_changed += lambda: self.renderers_cache.clear() + + from jabber.JabberContact import JabberContact + model.donotexpand += [MetaContact, JabberContact] + + super(BuddyList, self).__init__(parent, model) + + Bind = self.Bind + Bind(wx.EVT_MOTION, self.motion) + Bind(wx.EVT_ENTER_WINDOW, self.enter_window) + Bind(wx.EVT_LEAVE_WINDOW, self.leave_window) + Bind(wx.EVT_MIDDLE_UP, self.on_middle_up) + self.BindWheel(self) + Bind(wx.EVT_RIGHT_DOWN, self.on_right_down) + Bind(wx.EVT_CONTEXT_MENU, self.on_context_menu) + Bind(wx.EVT_LEFT_DOWN, self.on_left_down) + Bind(wx.EVT_KEY_DOWN, self.__onkey) + + if keyhandler is not None: + Bind(wx.EVT_CHAR, keyhandler) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.update_renderers() + + self.dragging_obj = None + self.shownbuddies = defaultdict(list) + + with traceguard: + self.SetDropTarget(BuddyListDropTarget(self)) + + # On Mac OS X, we don't get mouse moved events when the + # window is inactive or not focused, so poll the mouse state + # whenever we enter the window + self.mouse_tracker = None + if config.platform == 'mac': + self.mouse_tracker = wx.PyTimer(self.motion) + + blist = profile.blist + + # Observe changes to the buddylist structure. + blist.add_observer(self.on_blist_update) + + self.infobox = infobox + infobox.Befriend(self) + + top_bind = self.Top.Bind + top_bind(wx.EVT_SHOW, self.on_frame_show_or_iconized) + top_bind(wx.EVT_ICONIZE, self.on_frame_show_or_iconized) + + def on_frame_show_or_iconized(self, e): + # Since this control is the only observer of the sorter, we can + # add a hack that disables sorting when the buddylist isn't shown. + # + # When you bring the buddylist back, a sort will happen. + e.Skip() + frame = self.Top + + def later(): + visible = frame.IsShown() and not frame.IsIconized() + profile.blist.set_sort_paused(not visible) + + # allow adjacent iconize/show to catch up + wx.CallLater(50, later) + + @property + def context_menu(self): + try: + return self._context_menu + except AttributeError: + self._context_menu = UMenu(self, _('BuddyList Popup')) + return self._context_menu + + def on_blist_update(self, blist, *a): + if not self: return + self.set_root(blist.view) + + showing_idle = prefprop('buddylist.layout.extra_info') + + def PostPaint(self, dc): + self.paint_add_link(dc) + + # reset the idle timer when painting + if self.showing_idle in ('both', 'idle'): + self.idle_timer.Start(IDLE_UPDATE_SECS * 1000) + + profile.blist.set_sort_paused(False) + + if not getattr(self, '_did_startup_time', False): + self._did_startup_time = True + import sys + from time import clock + sys._startup_time = clock() + + if getattr(getattr(sys, 'opts', None), 'measure') == 'startup': + print 'startup time', sys._startup_time + import os + os._exit(0) + + def refresh_idle_buddies(self): + for i in self.idle_indices: + self.RefreshLine(i) + + def paint_add_link(self, dc): + if len(self.model) == 0: + amgr = profile.account_manager + + if amgr.accounts_loaded and len(amgr.accounts) == 0: + try: + f = skin.get('BuddiesPanel.Fonts.AddAccountsLink', default_font) + f.SetUnderlined(True) + + dc.SetTextForeground(skin.get('BuddiesPanel.FontColors.AddAccountsLink', wx.BLUE)) + except Exception: + print_exc() + f = self.Font + + dc.SetFont(f) + + # Center an "Add Accounts" link in the client area. + s = _('Add Accounts') + + w, h = dc.GetTextExtent(s) + + # position the link 1/4 down the list + r = self.ClientRect + r.Height = r.Height / 2 + x, y = r.HCenterW(w), r.VCenterH(h) + + dc.DrawText(s, x, y) + self._linkrect = wx.Rect(x, y, w, h) + + # Draw a "Need Help?" link as well. + help_string = _('Need Help?') + w, new_height = dc.GetTextExtent(help_string) + x, y = r.HCenterW(w), y + h * 1.5 + dc.DrawText(help_string, x, y) + self._helplinkrect = wx.Rect(x, y, w, h) + return + + try: + del self._linkrect + del self._helplinkrect + except AttributeError: pass + + def set_root(self, root): + return TreeList.set_root(self, root) + + def rename_selected(self): + obj = self.SelectedItem + if obj is not None: + # check the action precondition + if obj.rename_gui.action_allowed(obj): + obj.rename_gui() + + def __onkey(self, e): + c = e.KeyCode + + if c == wx.WXK_SPACE: + self.RotateContact(not e.ShiftDown()) + elif c == wx.WXK_F2: + self.rename_selected() + elif c == wx.WXK_DELETE: + self.delete_blist_item(self.SelectedItem) + elif c in activate_keys: + self.on_doubleclick() + elif c == wx.WXK_HOME: + self.SetSelection(0) if len(self.model) else None + elif c == wx.WXK_END: + self.SetSelection(len(self.model)-1) if len(self.model) else None + else: + e.Skip(True) + + def UpdateSkin(self): + self.dragimgs = S(bar = skin.get('BuddiesPanel.Dragimages.Bar'), #TODO: stretch? + box = skin.get('BuddiesPanel.Dragimages.Box')) + + for renderer in self.renderers.itervalues(): + renderer.UpdateSkin() + + bg = skin.get('BuddiesPanel.Backgrounds.List', lambda: SkinColor(wx.WHITE)) + self.SetBackground(bg) + + self.RefreshAll() + + def update_renderers(self): + import gui.buddylist.renderers as r + + self.UpdateSkin() + + contact_renderer = r.ContactCellRenderer(self) + group_renderer = r.GroupCellRenderer(self) + search_renderer = r.SearchCellRenderer(self) + + self.renderers.update(dict(Group = group_renderer, + DGroup = group_renderer, + SortGroup = group_renderer, + JabberContact = contact_renderer, + Contact = contact_renderer, + JabberResource = contact_renderer, + YahooContact = contact_renderer, + MetaContact = r.MetaContactCellRenderer(self), + SearchEntry = search_renderer, + SearchOptionsEntry = r.SearchCellOptionsRenderer(self), + SearchWebGroup = group_renderer)) + + def dragon_allowed(self, i): + 'Is dragging on to the ith buddy allowed.' + + dragging = self.dragging_obj + target = self.model[i] + + + if isinstance(target, SpecialGroup): + return False + + if isinstance(dragging, GroupTypes): + return False + + from digsby import iswidget + if iswidget(target): + return False + + + return True + + def on_middle_up(self, e): + self.RotateContact(not e.ShiftDown()) + + def RotateContact(self,forward = True): + ib = self.infobox + if forward: + ib.SelectNext() + else: + ib.SelectLast() + + + def _on_mousewheel(self, e): + if e.RightIsDown(): + self._menu_ok = False + ib = self.infobox + rot = e.WheelRotation + if rot < 0: + ib.SelectNext() + elif rot > 0: + ib.SelectLast() + else: + # forward the mousewheel event to the infobox + win = wx.FindWindowAtPointer() + if isinstance(win, tuple(self.infobox_scrollers)): + self.infobox.on_mousewheel(e) + else: + super(BuddyList, self)._on_mousewheel(e) + + def on_right_down(self, e): + i = self.HitTest(e.Position) + self.SetSelection(i) + e.Skip() + + def on_context_menu(self, e): + # ensure the selection is underneath the mouse cursor + self.context_menu_event_selection(e) + + menu = self.context_menu + menu.RemoveAllItems() + + i = self.GetSelection() + + if i != -1: + obj = self.model[i] + if hasattr(obj, '_disallow_actions'): + return + + # The mouse is over a group or buddy + import common.actions as actions + actions.menu(self, self.model[i], self.context_menu) + else: + # The mouse is over an empty space in the buddylist + # + # Only show "Add Group" if you are connected to an IM account (other than Digsby) + # OR if you have the "digsby.allow_add" preference set. + if not any(x.allow_contact_add for x in profile.account_manager.connected_accounts): + return + + from gui.protocols import add_group + menu.AddItem(_('&Add Group'), callback = add_group) + + menu.PopupMenu() + + def context_menu_event_selection(self, e): + # EVT_CONTEXT_MENU has .Position == (-1, -1) when caused by the keyboard + # if that's not the case, and the mouse is over an item that isn't + # selected, select it. + if e and e.Position != (-1, -1): + # watch out! EVT_RIGHT_DOWN's event.Position is in client coordinates + # but EVT_CONTEXT_MENU gets screen coordinates (at least on windows) + self.SetSelection(self.HitTest(self.ScreenToClient(e.Position))) + + def open_convo_with_selected( self ): + 'Opens a conversation window with the buddy currently selected.' + + i = self.GetSelection() + if i != -1: + obj = self.model[i] + if self.can_chat(obj): + chat_with(obj) + self.infobox.DoubleclickHide() + return True + elif hasattr(obj, 'activate'): + with traceguard: + obj.activate() + return True + + def enter_window(self, e): + if self.mouse_tracker: + self.mouse_tracker.Start(10) + + def leave_window(self, e): + if self.mouse_tracker: + self.mouse_tracker.Stop() + e.Skip() + self.infobox.InvalidateDoubleclickHide() + + def can_chat(self, obj): + return isinstance(obj, (Contact, MetaContact)) + + def on_doubleclick(self, e = None): + if not self.open_convo_with_selected(): + TreeList.on_doubleclick( self, e ) + + activate_selected_item = on_doubleclick + + def expand_all(self): + with self.save_selection(): + model = self.model + isexp = model.is_expanded + + for i, obj in enumerate(model): + if isinstance(obj, GroupTypes) and not isexp(i): + model.expand(obj) + return self.expand_all() + + def collapse_all(self): + with self.save_selection(): + model = self.model + isexp = model.is_expanded + + for i, obj in enumerate(model): + if isinstance(obj, GroupTypes) and isexp(i): + model.collapse(obj) + return self.collapse_all() + + def on_left_down(self, e): + self.drag_point = e.Position + + try: + linkrect = self._linkrect + helprect = self._helplinkrect + except AttributeError: + pass + else: + if linkrect.Contains(e.Position): + import gui.pref.prefsdialog + gui.pref.prefsdialog.show('accounts') + elif helprect.Contains(e.Position): + wx.LaunchDefaultBrowser(NEED_HELP_LINK) + + i = self.HitTest((e.GetX(), e.GetY())) + + if i == -1: + self.SetSelection(-1) + return e.Skip(True) + + if self.model.expandable(self[i]) and e.GetX() < 15: + # If we're clicking a Group expander triangle, toggle the group + # expansion but do not select. + self.toggle_expand(self[i]) + else: + self.SetSelection(i) + e.Skip(True) + + self.infobox.quickshow=True + self.infobox_hittest(e.Position) + + def motion( self, e=None ): + 'Invoked on mouse motion over the buddy list.' + pos = None + if e: + e.Skip(True) + pos = e.Position + else: + pos = self.Parent.ScreenToClient(wx.GetMousePosition()) + + try: + linkrect = self._linkrect + helprect = self._helplinkrect + except AttributeError: + pass + else: + # If the cursor is over a custom drawn link show a hand + if any(r.Contains(pos) for r in (linkrect, helprect)): + self.SetCursor(StockCursor(CURSOR_HAND)) + else: + self.SetCursor(StockCursor(CURSOR_ARROW)) + + + # Drag "distance" must be at least ten pixels + if e and hasattr(self, 'drag_point') and e.LeftIsDown() and e.Dragging() \ + and _dist2(e.GetPosition(), self.drag_point) > 100: + + i = self.GetSelection() + if i != -1 and i < len( self.model ): + data = self.make_drag_data( self.model[i] ) + + ds = BuddyListDropSource( self ) + ds.SetData( data ) + unused_result = ds.DoDragDrop( wx.Drag_AllowMove ) + self.hilite = None + self.Refresh() + self.dragging = False + elif e and not e.LeftIsDown() and hasattr(self, 'drag_point'): + del self.drag_point + + else: + self.infobox_hittest(pos) + + show_infobox = prefprop('infobox.show') + + def infobox_hittest(self,pos): + i = self.HitTest(pos) + + if i != -1: + obj = self[i] + if not isinstance(obj, GroupTypes): + if self.show_infobox and isinstance(obj, (Contact, MetaContact)): + p = self.Parent + pl = p.ClientToScreen(wx.Point(0, self.Position.y + self.GetItemY(i))) + pr = pl + wx.Point(p.Size.width, 0) + return self.infobox.Display(pl, pr, obj) + + # hide infobox when over search entries, or empty space + self._hide_infobox() + + def _hide_infobox(self): + if self.infobox.Shown: + self.infobox.DelayedHide() + else: + self.infobox.Hide() + + + def make_drag_data( self, blist_item ): + data = wx.DataObjectComposite() + + import contacts.contactsdnd as contactsdnd + contactsdnd.add_to_dataobject(data, blist_item) + + self.dragging_obj = blist_item + return data + + def on_drop_buddylistitem( self, clist_obj ): + if not getattr(self, 'hilite', None): + return + + # hilite is a tuple of (area, index) + area, _i, drop_to = self.hilite + + if area == 'below_group': + area = 'below' + + if area == 'disallow': return +# if self.model[i] is clist_obj: return # early exit for dropping to same + + from gui.searchgui import SearchEntry + + if isinstance(clist_obj, Contact): + return self.on_drop_contact(clist_obj, area, drop_to) + elif isinstance(clist_obj, MetaContact): + return self.on_drop_metacontact(clist_obj, area, drop_to) + elif isinstance(clist_obj, GroupTypes): + return self.on_drop_dgroup(clist_obj, area, drop_to) + elif isinstance(clist_obj, SearchEntry): + return self.on_drop_search(clist_obj, area, drop_to) + self.hilite = None + + def on_drop_dgroup(self, group, area, togroup): + assert isinstance(togroup, GroupTypes),"dragging above or below something that isn't a group" + assert area in ('above', 'below') + profile.blist.rearrange_group(group, area, togroup) + + def on_drop_metacontact(self, clist_obj, area, drop_to): + if isinstance(drop_to, GroupTypes): + return self.on_drop_metacontact_dgroup(clist_obj, area, drop_to) + elif isinstance(drop_to, MetaContact): + return self.on_drop_metacontact_metacontact(clist_obj, area, drop_to) + elif isinstance(drop_to, Contact): + return self.on_drop_metacontact_contact(clist_obj, area, drop_to) + + def on_drop_metacontact_dgroup(self, clist_obj, area, drop_to): + assert area in ('box', 'below') + assert isinstance(clist_obj, MetaContact) + blist = profile.blist + if area == 'below': + position = blist.DROP_BEGINNING + else: + position = blist.DROP_END + blist.rearrange(clist_obj, area, drop_to, position) + if not in_same_group(clist_obj, drop_to): + clist_obj.move_to_group(drop_to.name) + + def do_relative_metacontact(self, clist_obj, area, drop_to): + drop_group = self.model.parent_of(drop_to) + profile.blist.rearrange(clist_obj, area, drop_group, drop_to) + if not in_same_group(clist_obj, drop_group): + clist_obj.move_to_group(drop_group.name) + + def on_drop_search(self, entry, area, drop_to): + 'rearrange search web items' + from common.search import searches + from common import setpref + entry = entry.searchengine.dict() + drop_to = drop_to.searchengine.dict() + engines = [s.dict() for s in searches[:]] + + i = engines.index(entry) + j = engines.index(drop_to) + (1 if area == 'below' else 0) + + if j > i: j -= 1 + + if i != len(engines): + engines.pop(i) + engines.insert(j, entry) + + setpref('search.external', engines) + + def on_drop_metacontact_metacontact(self, clist_obj, area, drop_to): + assert area in ('above', 'box', 'below') + if area == 'box': + contacts = list(drop_to) + list(clist_obj) + diag = MetaContactDialog(self, contacts, metacontact = drop_to, title = _('Merge Contacts'), + order = None) + try: + diag.Prompt(ondone = lambda *a, **k: clist_obj.explode(ask = False)) + finally: + diag.Destroy() + else: + self.do_relative_metacontact(clist_obj, area, drop_to) + + def on_drop_metacontact_contact(self, clist_obj, area, drop_to): + assert area in ('above', 'box', 'below') + if area == 'box': + contacts = [drop_to] + list(clist_obj) + diag = MetaContactDialog(self, contacts, metacontact = clist_obj, title = _('Merge Contacts'), + order = None) + drop_group = self.model.parent_of(drop_to) + def morelater(*a, **k): + profile.blist.rearrange(clist_obj, 'above', drop_group, drop_to) + if not in_same_group(clist_obj, drop_group): + clist_obj.move_to_group(drop_group.name) + try: + diag.Prompt(ondone = morelater) + finally: + diag.Destroy() + else: + self.do_relative_metacontact(clist_obj, area, drop_to) + + def delete_blist_item(self, item): + + if isinstance(item, Contact): + from gui.protocols import remove_contact + remove_contact(item, item.remove) + elif isinstance(item, MetaContact): + item.explode() + elif isinstance(item, GroupTypes): + from gui.protocols import remove_group + remove_group(item, item.delete) + + def on_drop_contact(self, clist_obj, area, drop_to): + if isinstance(drop_to, GroupTypes): + return self.on_drop_contact_dgroup(clist_obj, area, drop_to) + elif isinstance(drop_to, MetaContact): + return self.on_drop_contact_metacontact(clist_obj, area, drop_to) + elif isinstance(drop_to, Contact): + return self.on_drop_contact_contact(clist_obj, area, drop_to) + + def on_drop_contact_dgroup(self, clist_obj, area, drop_to): + assert area in ('box', 'below') + assert isinstance(clist_obj, Contact) + blist = profile.blist + if area == 'below': + position = blist.DROP_BEGINNING + else: + position = blist.DROP_END + success = lambda *a: blist.rearrange(clist_obj, area, drop_to, position) + if clist_obj not in drop_to: + @callsback + def do_move(result = None, callback = None): + clist_obj.move_to_group(drop_to.name, callback = callback) + do_move(success=success) + else: + success() + + def do_relative_contact(self, clist_obj, area, drop_to): + drop_group = self.model.parent_of(drop_to) + blist = profile.blist + success = lambda *a: blist.rearrange(clist_obj, area, drop_group, drop_to) + if clist_obj not in drop_group: + @callsback + def do_move(result = None, callback = None): + clist_obj.move_to_group(drop_group.name, callback = callback) + do_move(success=success) + else: + success() + + def on_drop_contact_metacontact(self, clist_obj, area, drop_to): + assert area in ('above', 'box', 'below') + if area == 'box': + diag = MetaContactDialog.add_contact(self, drop_to, clist_obj, -1) + diag.Prompt(ondone = lambda *a: None) + diag.Destroy() + else: + self.do_relative_contact(clist_obj, area, drop_to) + + def on_drop_contact_contact(self, clist_obj, area, drop_to): + assert area in ('above', 'box', 'below') + if area == 'box': + order = ('__meta__', 'above', self.model.parent_of(drop_to), drop_to) + diag = MetaContactDialog(self, [drop_to, clist_obj], order = order ) + diag.Prompt(ondone = lambda *a: None) + diag.Destroy() + else: + self.do_relative_contact(clist_obj, area, drop_to) + + def get_feedback(self, clientPt): + # Percent will be the percentage of vertical space the cursor has + # passed over the item it's on. + i, percent = self.hit_test_ex( clientPt ) + dragging = self.dragging_obj + drop_to = self.model[i] + if i == -1: + # We must be dragging off into the "void" below the + # buddylist: put it below the last item. + i = len(self.model)-1 + percent = 1 + parent_percent = ('foo', 1) + else: + parent_percent = self.hit_test_parent( clientPt ) + + from .buddylistrules import target, feedback + new_to, position = target(self.model, dragging, drop_to, i, percent, parent_percent) + feedback_result = feedback(self.model, dragging, new_to, position) + return new_to, feedback_result + + def GiveFeedback(self, effect, dragsource): + 'Logic for drawing drag and drop indication marks.' + + mousepos = wx.GetMousePosition() + clientPt = self.ScreenToClient( mousepos ) + new_to, feedback_result = self.get_feedback(clientPt) + +# ITEM_BOX = 'box' +# GROUP_BOX = 'group_box' +# ABOVE = 'above' +# BELOW = 'below' +# BELOW_GROUP = 'below_group' +# DISALLOW = 'disallow' + + if feedback_result == 'group_box': + feedback_result = 'box' +# if feedback_result == 'below_group': +# feedback_result = 'below' + +# if feedback_result not in ('above', 'below', 'box'): +# feedback_result = None + + old_hilite = self.hilite + if feedback_result is not None: + self.hilite = (feedback_result, self.model.index_of(new_to), new_to) + else: + self.hilite = None + + # Is there a better way to do this? Keep track of previous mouse + # positions, perhaps? + +# if self.hilite is not None: +# area, i, drop_to = self.hilite +# if area == 'disallow': +# dragsource.SetCursor(effect, wx.StockCursor(wx.CURSOR_NO_ENTRY)) +# else: +# dragsource.SetCursor(effect, wx.StockCursor(wx.CURSOR_COPY_ARROW)) + + # only refresh lines that need it + if self.hilite != old_hilite: +# print self.hilite, old_hilite +# print [old_hilite[1] if old_hilite else -1, self.hilite[1] if self.hilite else -1] + hilites = filter(lambda a: a!=-1, [old_hilite[1] if old_hilite else -1, self.hilite[1] if self.hilite else -1]) + i, j = min(hilites), max(hilites) + self.RefreshLines(max(0, i-1), min(self.GetItemCount()-1, j+1)) + if old_hilite and old_hilite[0] == 'below_group': + if self.model.is_expanded(old_hilite[1]): + self.RefreshLine(old_hilite[1] + len(old_hilite[2])) + if self.hilite and self.hilite[0] == 'below_group': + if self.model.is_expanded(self.hilite[1]): + self.RefreshLine(self.hilite[1] + len(self.hilite[2])) + + self.dragResult = wx.DragNone if not self.hilite or self.hilite[0] == 'disallow' else wx.DragMove + + return False + + def search(self, search_string): + self.fallback_selection = 1 + profile.blist.search(search_string, self._on_search_cb) + + def _on_search_cb(self, results): + # results is num contacts in (prevSearch, thisSearch) + # if we went from finding no contacts to finding some contacts, select the first one. + prev, this = results + if prev == -1 and this > 0: + wx.CallAfter(self.SetSelection, 1) + + def clear_search(self): + profile.blist.search('') + +class BuddyListDropTarget(wx.PyDropTarget): + + VELOCITY = 70 + + def __init__(self, list): + wx.PyDropTarget.__init__(self) + + self.list = list + self.lasttick = None + self.CreateNewDataObject() + + def OnEnter(self, x, y, d): + return self.list.dragResult + + def OnLeave(self): + self.lasttick = None + + def OnDrop(self, x, y): + lasttick = None + + return True + + def OnDragOver(self, x, y, d): + blist = self.list + + listrect = wx.RectS(blist.Size) + mp = wx.Point(x,y) + + topdif = mp.y - listrect.y + botdif = listrect.bottom - mp.y + + if topdif < 7 or botdif < 7: + if self.lasttick is None: + self.lasttick = default_timer() + + now = default_timer() + + toscroll = int((now - self.lasttick) * self.VELOCITY) + + if toscroll >= 1: + self.lasttick = now + + if topdif < 5: + blist.ScrollLines(-toscroll) + elif botdif < 5: + blist.ScrollLines(toscroll) + else: + self.lasttick = None + + return blist.dragResult + + def CreateNewDataObject(self): + self.dragged = wx.DataObjectComposite() + + # This drop target will receive certain types of draggable objects. + import contacts.contactsdnd as cdnd + drag_types = dict( + file = wx.FileDataObject(), + buddy = cdnd.dataobject(), + text = wx.TextDataObject(), + bitmap = wx.PyBitmapDataObject() ) + + # For easy access, like self.dragged.file + for dt, dobj in drag_types.iteritems(): + setattr(self.dragged, dt, dobj) + + # Add to the wx.DataObjectComposite item, and set as our data object. + for v in drag_types.itervalues(): self.dragged.Add(v) + self.SetDataObject(self.dragged) + + def OnData(self, x, y, drag_result): + "Called when OnDrop returns True. Get data and do something with it." + + with traceguard: + self.GetData() # Copies data from drag source to self.dragging + dragged = self.dragged + + dropped = S(files = dragged.file.GetFilenames(), + bitmap = dragged.bitmap.GetBitmap(), + text = dragged.text.GetText()) + + if dropped.files: + i, unused_percent = self.list.hit_test_ex(wx.Point(x,y)) + if i != -1: + obj = self.list.model[i] + + # open a "send files" confirmation + from common import caps + obj = getattr(obj, 'file_buddy', obj) #metacontact + + @wx.CallAfter # so that we don't block the Drag and Drop + def later(): + if hasattr(obj, 'send_file') and caps.FILES in obj.caps: + window = self.list.Top + window.Raise() + + from gui.contactdialogs import send_files + send_files(window, obj, dropped.files) + else: + msg = _("This buddy can't accept file transfers") + wx.MessageBox(msg, _("Send File")) + + if dropped.bitmap: + print dropped.bitmap + print dir(dropped.bitmap) + return drag_result + if dropped.text: + #print 'Dropped Text (%s)' % dropped.text + pass + + if getattr(self.list, 'dragging_obj', None) is not None: + self.list.on_drop_buddylistitem( self.list.dragging_obj ) + + del self.dragged + self.CreateNewDataObject() + + return drag_result + + +def _dist2(one, two): + 'Distance squared between two points.' + + return (one.x - two.x) ** 2 + (one.y - two.y) ** 2 + +def in_same_group(clist_obj, drop_group): + groups = clist_obj.manager[clist_obj.id].groups + return (drop_group.name.lower(),) in (tuple([i[0].lower()] + list(i[1:])) for i in groups) + +ismeta = lambda obj: isinstance(obj, MetaContact) + +def find_group(model, obj): + if isinstance(obj, GroupTypes): + drop_group = obj + else: + parent = model.parent_of(obj) + if isinstance(parent, MetaContact): + drop_group = model.parent_of(parent) + else: + drop_group = parent + + return drop_group + + +class BuddyListDropSource(wx.DropSource): + def __init__( self, list ): + wx.DropSource.__init__(self, list) + self.SetCursor(wx.DragNone, wx.StockCursor(wx.CURSOR_NO_ENTRY)) + self.SetCursor(wx.DragMove, wx.StockCursor(wx.CURSOR_COPY_ARROW)) + self.list = list + + def GiveFeedback(self, effect): + return self.list.GiveFeedback(effect, self) + +def fill_menu(menu, actions): + for action_id, label in actions: + menu.AddItem(label, id=action_id) + +def chat_with(obj): + import gui.imwin + + mode = 'im' + if pref('conversation_window.open_email_for_offline', False): + if not obj.online: + mode = 'email' + + wx.CallAfter(gui.imwin.begin_conversation, obj, mode = mode) + +GroupActions = [ + (actionIDs.AddContact, _('&Add Contact')), + (actionIDs.RenameSelection, _('&Rename Group')), + (actionIDs.DeleteSelection, _('&Delete Group')), + (actionIDs.AddGroup, _('Add &Group')), +] + diff --git a/digsby/src/gui/buddylist/buddylistframe.py b/digsby/src/gui/buddylist/buddylistframe.py new file mode 100644 index 0000000..8216afe --- /dev/null +++ b/digsby/src/gui/buddylist/buddylistframe.py @@ -0,0 +1,595 @@ +''' + +Main buddylist frame GUI + +''' + +from __future__ import with_statement +import config + +from traceback import print_exc +from wx import EXPAND +import wx +import logging; log = logging.getLogger('blist_fr'); warning = log.warning +from util.primitives.error_handling import traceguard, try_this +from util.primitives.funcs import do +from util.primitives.mapping import Storage + +import gui + +from gui.uberwidgets.panelframe import PanelFrame +from gui.buddylist.accountlist import AccountList +from gui.uberwidgets.connectionlist import ConnectionsPanel +from gui.buddylist.accounttray import AccountTrayIcon +from gui.native import memory_event +from common import profile, bind +from gui.toolbox import AddInOrder, calllimit + + +from hub import Hub +hub = Hub.getInstance() +from gui.toolbox import saveWindowPos +from gui.toolbox import Monitor +from gui.statuscombo import StatusCombo +from common import pref +from cgui import SimplePanel + +# keys which are ignored for starting searches. +_function_keys = [getattr(wx, 'WXK_F' + str(i)) for i in xrange(1, 13)] + +platform_disallowed_keys = [] +if config.platform == 'win': + platform_disallowed_keys.extend([wx.WXK_WINDOWS_LEFT, wx.WXK_WINDOWS_RIGHT]) + +disallowed_search_keys = frozenset([wx.WXK_ESCAPE, wx.WXK_MENU, wx.WXK_TAB, + wx.WXK_BACK] + platform_disallowed_keys + _function_keys) + +import gui.app.menubar as menubar + +from config import platformName, newMenubar + +class BuddyListFrame(wx.Frame): + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + # Set the frame icon + with traceguard: + from gui import skin + self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon')) + + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + # Do snapping if set in the preferences + from gui.toolbox import snap_pref, loadWindowPos + snap_pref(self) + defaultRect = get_default_blist_rect() + loadWindowPos(self, defaultPos = defaultRect.Position, defaultSize = defaultRect.Size) + + # frame doesn't hide when mouse is over the infobox + from gui.native.docking import Docker #@UnresolvedImport + docker = self.docker = Docker(self) + + panel = self.buddyListPanel = BuddyListPanel(self) + + docker.ShouldShowInTaskbar = lambda: pref('buddylist.show_in_taskbar', default=True, type=bool) + docker.ShouldAlwaysStayOnTop = self.SetAlwaysOnTop + docker.LinkedWindows += [panel.infobox] + docker.OnHide += panel.infobox.Hide + + self.userSizeForMaximize = None + self.inMaximize = False + + Bind = self.Bind + Bind(wx.EVT_SET_FOCUS, lambda e: panel.buddylist.SetFocus()) + Bind(wx.EVT_CLOSE, self.on_close) + + def on_show(e): + # hiding the buddylist triggers paging out RAM + e.Skip() + if not e.GetShow(): memory_event() + + Bind(wx.EVT_SHOW, on_show) + + if config.platform == 'win': + Bind(wx.EVT_ICONIZE, self.on_iconize) + + if config.platform == 'mac': + Bind(wx.EVT_MAXIMIZE, self.on_maximize) + Bind(wx.EVT_SIZE, self.on_size) + + @bind('Buddylist.ToggleAutoHide') + def ToggleAutoHideWhenDocked(self): + profile.localprefs['buddylist.dock.autohide'] = not profile.localprefs['buddylist.dock.autohide'] + + + def SetAlwaysOnTop(self, val = None): + """ + Adds or removes the STAY_ON_TOP style from the window + + Returns True if the old style had STAY_ON_TOP in it, False otherwise + """ + + stayOnTop = val if val is not None else pref('buddylist.always_on_top', False) + + style = self.GetWindowStyle() + if stayOnTop: self.SetWindowStyle( wx.STAY_ON_TOP | style) + else: self.SetWindowStyle(~wx.STAY_ON_TOP & style) + + def on_size(self, e): + e.Skip() + # when the user sizes the window themselves, on Mac we don't want the next + # 'maximize' call to restore the previous custom size the user set. This + # ensures the zoom button will save the size the user sets the window to + # and use that for the next maximize call. + if not self.inMaximize: + self.userSizeForMaximize = None + + def on_maximize(self, e): + self.inMaximize = True + monitor = Monitor.GetFromWindow(self) + assert monitor + + if self.userSizeForMaximize: + self.SetSize(self.userSizeForMaximize) + self.userSizeForMaximize = None + return + else: + self.userSizeForMaximize = self.Size + + display_size = monitor.GetClientArea() + max_size_needed = self.buddyListPanel.GetMaxRequiredSize() + max_size_needed.y += 22 # add the title bar height to the total size + + final_size = max_size_needed + if max_size_needed.x > display_size.width: + final_size.x = display_size.width + + if max_size_needed.y > display_size.height: + final_size.y = display_size.height + + + # if our y position + height go below the bottom of the display, + # move the y position up to help fit it + pos = self.GetPosition() + if pos.y + final_size.y > display_size.height - pos.y: + extra_y = abs(display_size.height - pos.y - final_size.y) + pos.y -= extra_y + + self.SetPosition(pos) + + print "display_size = %r" % display_size + print "final_size = %r, max_size_needed = %r" % (final_size, max_size_needed) + self.SetSize(final_size) + self.inMaximize = False + + def on_iconize(self, e): + # Catch a minimize event so that if we're not showing in the taskbar, + # we can hide the frame. (Otherwise it collapses to a mini + # window above the taskbar) + if e.Iconized(): + memory_event() + + if not self.OnTaskbar: + self.maybe_undock() + self.Hide() + + + def on_destroy(self): + log.info('BuddylistFrame.OnDestroy()') + self.buddyListPanel.on_destroy() + + def on_close(self, e=None, exiting = False): + ''' + Window has been asked to close by the user. + + (Alt-F4 or the X button on Windows.) + ''' + log.info('BuddylistFrame.on_close') + + autohidden = False + if not exiting: + with traceguard: + autohidden = self.maybe_undock() + + if not autohidden: + saveWindowPos(self) + + # FIXME: It's probably better for us to set wx.App.SetExitOnFrameDelete based on the + # setting of this pref. However, for that to work, we need to make sure wx.App.SetTopWindow + # refers to this window once the splash screen is gone. + if not exiting and pref('buddylist.close_button_exits', False): + wx.GetApp().DigsbyCleanupAndQuit() + else: + self.Show(False) + + memory_event() + + @property + def Docked(self): + return self.docker.Enabled and self.docker.docked + + @property + def AutoHidden(self): + return self.Docked and self.docker.autohidden + + def ComeBackFromAutoHide(self): + if self.AutoHidden: + self.docker.ComeBack() + + def maybe_undock(self): + docker = self.docker + if docker.Enabled and docker.docked: + autohidden = docker.autohidden + if docker.AutoHide and not autohidden: + docker.GoAway() + autohidden = True + elif not docker.AutoHide: + docker.wasDocked = True + docker.Undock(setFrameStyle=False) + + return autohidden + + def toggle_show_hide(self): + 'Shows or hides the buddy list frame.' + + self.show(not self.Visible) + + def show(self, show=True): + # TODO: just have the docker catch show events... + docker = getattr(self, 'docker', None) + if show and docker is not None and self.IsShown(): + if getattr(docker, 'Enabled', False) and docker.docked: + if docker.autohidden: + docker.ComeBack() + + self.Raise() + return + + self.Show(show) + + if show: + if self.IsIconized(): + self.Iconize(False) + self.EnsureInScreen() + self.Raise() + +class BuddyListPanel(SimplePanel): + 'Holds the buddy list.' + + def __init__( self, parent = None ): + SimplePanel.__init__(self, parent) + self.Sizer = wx.BoxSizer(wx.VERTICAL) + + link = profile.prefs.link #@UndefinedVariable + + # setup and layout GUI + self.tray_icons = [] + self.gui_construct() + rebuild = self.rebuild_panels + rebuild() + + # Watch always on top changes + def ontop_changed(val): + docker = wx.GetTopLevelParent(self).docker + if docker.docked and docker.AutoHide: + return + + p = wx.GetTopLevelParent(self) + if val: p.WindowStyle = p.WindowStyle | wx.STAY_ON_TOP + else: p.WindowStyle = p.WindowStyle & ~wx.STAY_ON_TOP + + self.unlinkers = [link(*a) for a in [ + ('buddylist.always_on_top', ontop_changed, True, self), + ('buddylist.order', lambda v: self.gui_layout(), False, self), + ('buddylist.show_status', rebuild, False), + ('buddylist.show_email_as', rebuild, False), + ('buddylist.show_social_as', rebuild, False), + ('buddylist.show_menubar', lambda v: self.gui_layout(), False, self), + ('social.display_attr', rebuild, False), + ('email.display_attr', rebuild, False), + ('buddylist.show_in_taskbar', lambda val: wx.CallAfter(lambda: setattr(self.Top, 'OnTaskbar', val)), True, self) + ]] + + # link docking preferences + link = profile.localprefs.link + docker = wx.GetTopLevelParent(self).docker + + self.unlinkers += [link(*a) for a in [ + ('buddylist.dock.autohide', lambda v: docker.SetAutoHide(bool(v)), True, docker), + ('buddylist.dock.enabled', lambda v: docker.SetEnabled(bool(v)), True, docker), + ('buddylist.dock.revealms', lambda v: setattr(docker, 'RevealDurationMs', try_this(lambda: int(v), 300)), True, docker), + ]] + + self.unlinkers.append(profile.prefs.link('buddylist.dock.slide_velocity', lambda v: wx.CallAfter(docker.SetVelocity,int(v)), obj = docker)) #@UndefinedVariable + self.unlinkers.append(Storage(unlink = profile.emailaccounts.add_list_observer (rebuild, rebuild, 'enabled').disconnect)) + self.unlinkers.append(Storage(unlink = profile.socialaccounts.add_list_observer(rebuild, rebuild, 'enabled').disconnect)) + + # don't ever let this control take focus + self.Bind(wx.EVT_SET_FOCUS, lambda e:self.blist.SetFocus()) + + + def UpdateSkin(self): + wx.CallAfter(self.Layout) + + @bind('buddylist.infobox.selectnext') + def SelectNext(self): + self.blist.RotateContact(forward = True) + + @bind('buddylist.infobox.selectprev') + def SelectPrev(self): + self.blist.RotateContact(forward = False) + + def GetMaxRequiredSize(self): + """ + This method doesn't quite work as it needs to for Mac. buddylist.GetVirtualSize() does + not give us the right size because we want the size it needs to display without + scrolling, but the VirtualSize grows as the actual control size grows. i.e. once the + control size is larger than the virtual size, the virtual size simply returns the control + size. Also, for some reason, the virtual size reported on Mac is about 20 pixels less than + what is needed to actually display the contents. + """ + size = self.buddylist.GetVirtualSize() + size.y += self.statuscombo.GetSize().y + size.y += self.elist.GetSize().y + size.y += self.clist.GetVirtualSize().y + + return size + + def on_blist_thumbtrack(self, event): + event.Skip() + if pref('search.buddylist.show_hint', True) and not self.statuscombo.searchHintShown: + self.statuscombo.ShowSearchHint() + + def on_blist_thumbrelease(self, event): + event.Skip() + if self.statuscombo.searchHintShown: + self.statuscombo.HideSearchHint() + + def gui_construct(self): + from gui.buddylist.buddylist import BuddyList + from gui.infobox.infobox import InfoBox + + # the main buddy list + self.infobox = InfoBox(self) + self.buddylist = BuddyList(self, self.infobox, keyhandler = self.on_buddylist_char) + self.buddylist.infobox_scrollers.update([InfoBox, AccountList]) + self.buddylist.Bind(wx.EVT_KEY_DOWN, self.on_buddylist_key) + self.buddylist.Bind(wx.EVT_SCROLLWIN_THUMBTRACK, self.on_blist_thumbtrack) + self.buddylist.Bind(wx.EVT_SCROLLWIN_THUMBRELEASE, self.on_blist_thumbrelease) + self.statuscombo = StatusCombo(self, self.buddylist, profile.statuses) + self.statuscombo.OnActivateSearch += self.on_activate_search + self.statuscombo.OnDeactivateSearch += self.on_deactivate_search + self.status = PanelFrame(self, self.statuscombo, 'statuspanel') + + self.blist = PanelFrame(self, self.buddylist, 'buddiespanel') + + def labelcallback(a, acctlist): + if getattr(a, 'alias', None) is not None: + out = a.alias + else: + atypecount = len([acct.protocol_info().name for acct in acctlist if acct.enabled and acct.protocol_info().name == a.protocol_info().name]) + out = a.protocol_info().name if atypecount == 1 else ('%s' % a.display_name) + if a.state != a.Statuses.CHECKING and profile.account_manager.state_desc(a): + return out + (' (%s)' % profile.account_manager.state_desc(a)) + else: + if hasattr(a, 'count_text_callback'): + return a.count_text_callback(out) + elif hasattr(a, 'count') and a.count is not None: + return out + (' (%s)' % a.count) + else: + return out + + # panel for all active email accounts + self.elist = PanelFrame(self, + AccountList(self, profile.emailaccounts, self.infobox, + skinkey = 'emailpanel', + prefkey = 'buddylist.emailpanel', + onDoubleClick = lambda a: a.OnClickInboxURL(), + labelCallback = lambda a: labelcallback(a, profile.emailaccounts)), + 'emailpanel' + ) + + def foo(a): + if a.state != a.Statuses.CHECKING: + bar = profile.account_manager.state_desc(a) + else: + bar = a.count + return '%s (%s)' % (a.display_name, bar) + + # panel for all active social network accounts + + def DoubleClickAction(a): + url = a.DefaultAction() + self.infobox.DoubleclickHide() + if url is not None: + wx.LaunchDefaultBrowser(url) + + def leave_slist(e): + # since slist may call infobox.DoubleClickHide, it needs to invalidate + # it as well. + e.Skip() + self.infobox.InvalidateDoubleclickHide() + + self.slist = PanelFrame(self, + AccountList(self, profile.socialaccounts, self.infobox, + skinkey = 'socialpanel', + prefkey = 'buddylist.socialpanel', + onDoubleClick = DoubleClickAction, + labelCallback = lambda a: labelcallback(a, profile.socialaccounts)), + 'socialpanel' + ) + + self.slist.panel.Bind(wx.EVT_LEAVE_WINDOW, leave_slist) + + self.clist = ConnectionsPanel(self) + + + self.infobox.Befriend(self.blist) + self.infobox.Befriend(self.elist) + self.infobox.Befriend(self.slist) + + # construct the main menu + if not newMenubar: + from gui.buddylist import buddylistmenu + self.menubar = buddylistmenu.create_main_menu(self) + if self.menubar.native: + self.Top.SetMenuBar(self.menubar) + else: + asumenu = False + parent = self.Parent + if platformName != "mac": + asumenu = True + parent = self + self.menubar = menus.set_menubar(parent, menubar.digsbyWxMenuBar(),umenu=asumenu) #@UndefinedVariable + + @bind('BuddyList.Accounts.ToggleShow') + def ToggleConnPanel(self): + self.clist.ToggleState() + + def gui_layout(self, layoutNow = True): + assert wx.IsMainThread() + + elems = ['status', 'blist','clist', 'slist', 'elist'] + + searching = self.Searching + panel_order = pref('buddylist.order', elems) + email_view = pref('buddylist.show_email_as', 'panel') in ('panel', 'both') and len(self.elist.active) + social_view = pref('buddylist.show_social_as', 'panel') in ('panel', 'both') and len(self.slist.active) + status_view = searching or pref('buddylist.show_status', True) + + viewable = Storage() + + with self.Frozen(): + self.Sizer.Clear() # remove all children, but don't delete. + + show_menu = pref('buddylist.show_menubar', True) + if not config.platform == 'mac': + if show_menu: + self.Sizer.Add(self.menubar.SizableWindow, 0, EXPAND) + + self.menubar.Show(show_menu) + + if searching or (hasattr(self, 'status') and status_view): + viewable.status = (self.status, 0, EXPAND) + + viewable.blist = (self.blist, 1, EXPAND) + viewable.clist = (self.clist, 0, EXPAND) + + if email_view: + viewable.elist = (self.elist, 0, EXPAND) + + if social_view: + viewable.slist = (self.slist, 0, EXPAND) + + AddInOrder(self.Sizer, *panel_order, **viewable) + + self.status.Show(status_view) + self.elist.Show(email_view) + self.slist.Show(social_view) + + if layoutNow: + self.Layout() + + def rebuild_panels(self, *a): + wx.CallAfter(self._rebuild_panels) + + @calllimit(1) + def _rebuild_panels(self): + trayaccts = [] + if pref('buddylist.show_email_as', 'panel') in ('systray', 'both'): + trayaccts += [a for a in profile.emailaccounts] + if pref('buddylist.show_social_as', 'panel') in ('systray', 'both'): + trayaccts += [a for a in profile.socialaccounts] + + with self.Frozen(): + if trayaccts: + shown = [a for (a, icon) in self.tray_icons] + enabled = [a for a in trayaccts if a.enabled] + icons = dict(self.tray_icons) + e, s = set(enabled), set(shown) + + # remove tray icons no longer needed + do(icons.pop(acct).Destroy() for acct in s - e) + + # add new ones, indexed by their positions in the accounts list + for acct in sorted(e - s, key = lambda a: enabled.index(a), reverse = True): + try: + icons[acct] = AccountTrayIcon.create(acct, self.infobox) + except Exception: + print_exc() + + self.tray_icons = icons.items() + else: + # destroy all tray icons + do(icon.Destroy() for acct, icon in self.tray_icons) + self.tray_icons = {} + + wx.CallAfter(self.gui_layout) + + def on_destroy(self): + + log.info('BuddylistPanel.on_destroy') + + for linker in self.unlinkers: + linker.unlink() + + self.elist.OnClose() + self.slist.OnClose() + + log.info('destroying account tray icons') + for acct, icon in self.tray_icons: + try: + log.info("destroying account tray icon %r...", icon) + icon.Destroy() + log.info('ok') + except Exception: + print_exc() + + # + # searchbox functionality + # + + def start_search(self, e = None): + self.statuscombo.search(e) + if not self.status.IsShown(): + self.gui_layout() + + def on_buddylist_char(self, e): + if e.KeyCode not in disallowed_search_keys: + with self.Frozen(): + # the key event will be emulated on the search box + self.start_search(e) + + if e is not None: + e.Skip() + + def on_buddylist_key(self, e): + if e.Modifiers == wx.MOD_CMD and e.KeyCode == ord('F'): + self.start_search() + else: + e.Skip() + + @property + def Searching(self): + 'Returns True if the searchbox has focus.' + status = getattr(self, 'statuscombo', None) + return status is not None and status.searching + + def on_activate_search(self): + self.infobox.Hide() + + def on_deactivate_search(self): + if not pref('buddylist.show_status', True): + self.gui_layout() + +def get_default_blist_rect(): + 'Initial buddylist position.' + + r = wx.Display(0).GetClientArea() + DEF_WIDTH = 220 + r.x = r.Right - DEF_WIDTH + r.width = DEF_WIDTH + return r + +gui.input.add_class_context(_('Buddy List'), 'BuddyList', cls = BuddyListPanel) #@UndefinedVariable diff --git a/digsby/src/gui/buddylist/buddylistmenu.py b/digsby/src/gui/buddylist/buddylistmenu.py new file mode 100644 index 0000000..30b3e2b --- /dev/null +++ b/digsby/src/gui/buddylist/buddylistmenu.py @@ -0,0 +1,499 @@ +''' + + +Main window's main menu + + +''' +import wx, sys + +from gui.uberwidgets.umenu import UMenuBar as MenuBar, UMenu as Menu + +from common import actions, profile, pref, setpref +from common.statusmessage import StatusMessage +from logging import getLogger; log = getLogger('blistmenu') +from peak.util.plugins import Hook + +import config + +import traceback +import gui.supportdigsby as support +from gui.filetransfer import FileTransferDialog +from gui.addcontactdialog import AddContactDialog +from gui.imdialogs import ShowNewIMDialog +from gui.visuallisteditor import VisualListEditor +from util.diagnostic import do_diagnostic +from gui.native.helpers import createEmail +from peak.events import trellis + +import contacts +from contacts.sort_model import GROUP_BY_CHOICES, SORT_BY_CHOICES + +def prefsdialog_show(name): + import gui.pref.prefsdialog as prefsdialog + return prefsdialog.show(name) + +def update_statuses(menu): + # New Status + # Edit Statuses + # ---- (separator) + # << STATUSES >> + + while len(menu) > 3: # ignore first three items. + menu.RemoveItem(menu[3]) + + from gui.statuscombo import status_menu + status_menu(menu, add_global = True, add_promote = True) + +def new_status_message(): + from gui.status import new_custom_status + new_custom_status(None, save_checkbox = True) + +def allow_rename(b): + from contacts.buddylistfilters import OfflineGroup + if b is None or getattr(b, 'iswidget', False) or isinstance(b, OfflineGroup): + return False + + if not isinstance(b, contacts.renamable): + return False + + if hasattr(b, '_disallow_actions'): + return False + + return True + +class reset_checks_SortOptionWatcher(trellis.Component): + model = trellis.attr(None) + names = trellis.attr(None) + view = trellis.attr(None) + @trellis.perform + def sync(self): + model_keys = [v[0] for v in self.model.values] + selection = self.model.selection + for i, item in enumerate(self.view): + if i < len(self.names): + val_key = self.names[i][0] + item.Check(val_key == model_keys[selection]) + item.Enable(val_key in model_keys) +# else: +# item.Check(False) +# item.Enable(True) + +class reset_check_AdvWatcher(trellis.Component): + model = trellis.attr(None) + view = trellis.attr(None) + @trellis.perform + def sync(self): + selection = self.model.selection + self.view.Check(selection > 0) + + +# TODO: Remove this code once the kinks have been worked out in the +# new menubar implementation. +def create_main_menu(parent): + 'Returns the main menu object.' + + menu = MenuBar(parent, skinkey = 'menubar') + digsby = Menu(parent) + view = Menu(parent) + tools = Menu(parent) + help = Menu(parent) + + def makeadd(m): + return (lambda title, callback = None, id = -1: m.AddItem(title, callback = callback, id = id)) + + # + # My Status + # + mystatus = Menu(parent, onshow = update_statuses); add = makeadd(mystatus) + add(_('&New Status Message...'), new_status_message) + add(_('&Edit Status Messages...'), lambda: prefsdialog_show('status')) + mystatus.AddSep() + + from gui.protocols import add_group + + def add_chat(): + import gui.chatgui + gui.chatgui.join_chat() + + # + # Digsby + # + digsby.AddSubMenu(mystatus, _('My Status')); add = makeadd(digsby) + add(_('My &Accounts...'), lambda: prefsdialog_show('accounts')) + digsby.AddSep() + sendimitem = add(_('&New IM...\tCtrl+N'), ShowNewIMDialog) + if pref('messaging.groupchat.enabled', False): + groupchatitem = add(_('New Group C&hat...\tCtrl+Shift+N'), add_chat) + else: + groupchatitem = None + digsby.AddSep() + addcontactitem = add(_('Add &Contact...\tCtrl+A'),lambda : AddContactDialog.MakeOrShow()) + addgroupitem = add(_('Add &Group...\tCtrl+Shift+A'), lambda: add_group()) + renameitem = add(_('&Rename Selection'), lambda: parent.blist.rename_selected()) + deleteitem = add(_('&Delete Selection...'), lambda: parent.blist.delete_blist_item(parent.blist.SelectedItem)) + digsby.AddSep() + add(_('Sign &Off Digsby (%s)') % profile.name, lambda: profile.signoff()) + # wx.App handles this for proper shutdown. + add(_('E&xit Digsby'), id = wx.ID_EXIT) + + def on_digsby_show(_m): + b = parent.blist.SelectedItem + + allow_add = any(x.allow_contact_add for x in profile.account_manager.connected_accounts) + + for item in (sendimitem, groupchatitem, addcontactitem, addgroupitem): + if item is not None: + item.Enable(allow_add) + + if not allow_rename(b): + renameitem.SetItemLabel(_('&Rename Selection')) + deleteitem.SetItemLabel(_('&Delete Selection')) + renameitem.Enable(False) + deleteitem.Enable(False) + else: + renameitem.SetItemLabel(_('&Rename {name}').format(name=getattr(b, 'alias', b.name))) + deleteitem.SetItemLabel(_('&Delete {name}').format(name=getattr(b, 'alias', b.name))) + renameitem.Enable(True) + deleteitem.Enable(True) + + # + # View + # + + add = makeadd(view) + view.AddPrefCheck('buddylist.always_on_top', _('&Always On Top')) + add(_('Skins...\tCtrl+S'), lambda: prefsdialog_show('appearance')) + view.AddSep() + view.AddPrefCheck('buddylist.show_menubar', _('&Menu Bar')) + + def on_menubar(val): + if not val: + wx.MessageBox(_('You can bring back the menubar by right clicking ' + 'on the digsby icon in the task tray.'), + _('Hide Menu Bar')) + + profile.prefs.link('buddylist.show_menubar', lambda val: wx.CallAfter(on_menubar, val), False, menu) + + view.AddPrefCheck('buddylist.show_status', _('Status Panel')) + add(_('Arrange &Panels...'), callback = lambda *_a: edit_buddylist_order(parent)) + view.AddSep() + view.AddPrefCheck('buddylist.show_mobile', _('Show &Mobile Contacts\tCtrl+M')) + view.AddPrefCheck('buddylist.show_offline', _('Show &Offline Contacts\tCtrl+O')) + groupoffline = view.AddPrefCheck('buddylist.group_offline', _('&Group Offline Contacts\tCtrl+G')) + + hideoffline = view.AddPrefCheck('buddylist.hide_offline_groups', _('&Hide Offline Groups')) + + groupby = Menu(parent); add = makeadd(groupby) + groupby.sorttypes = [] + + # sort by + sortby = Menu(parent); add = makeadd(sortby) + sortby.sorttypes = [] + + sort_models = profile.blist.sort_models + group_by = sort_models[0] + sort_by = sort_models[1] + then_by = sort_models[2] + + def addsort(model, view, sortstr, title): + def set(model = model, view = view, sortstr = sortstr): + model.selection = [v[0] for v in model.values].index(sortstr) + view[model.selection].Check(True) + + mi = view.AddCheckItem(title, set) + view.sorttypes.append( sortstr ) + return mi + + sort_names = dict((('none', _('&None')), + ('status', _('&Status')), + ('name', _('N&ame')), + ('log', _('&Log Size')), + ('service', _('Ser&vice')))) + + def addsorts(model, view, names): + for name in names: + addsort(model = model, view = view, + sortstr = name, title = sort_names[name]) + + addsorts(model = group_by, view = groupby, + names = [k for k,_v in GROUP_BY_CHOICES]) + groupby.AddSep() + groupby.AddItem(_('Advan&ced...'), callback = lambda: prefsdialog_show('contact_list')) + + addsorts(model = sort_by, view = sortby, + names = [k for k,_v in SORT_BY_CHOICES]) + sortby.AddSep() + def sortby_adv_click(): + sortby[-1].Check(then_by.selection > 0) + prefsdialog_show('contact_list') + sortby.AddCheckItem(_('Advan&ced...'), callback = sortby_adv_click) + sortby_adv = sortby[-1] + + groupby.reset_watcher = reset_checks_SortOptionWatcher(model = group_by, + view = groupby, + names = GROUP_BY_CHOICES) + sortby.reset_watcher = reset_checks_SortOptionWatcher(model = sort_by, + view = sortby, + names = SORT_BY_CHOICES) + sortby.adv_watcher = reset_check_AdvWatcher(model = then_by, view = sortby_adv) + + view.AddSep() + view.AddSubMenu(groupby, _('&Group By')) + view.AddSubMenu(sortby, _('&Sort By')) + + # + # Tools + # + + add = makeadd(tools) + add(_('&Preferences...\tCtrl+P'), id = wx.ID_PREFERENCES, callback = lambda: prefsdialog_show('accounts')) + add(_('Buddy List &Search\tCtrl+F'), parent.start_search) + add(_('&File Transfer History\tCtrl+J'), FileTransferDialog.Display) + + def pb_show(): + from gui.pastbrowser import PastBrowser + PastBrowser.MakeOrShow() + + add(_('&Chat History\tCtrl+H'), pb_show) + + + # + # Help + # + add = makeadd(help) + add(_('&Documentation'), lambda: wx.LaunchDefaultBrowser('http://wiki.digsby.com')) + add(_('Support &Forums'), lambda: wx.LaunchDefaultBrowser('http://forum.digsby.com')) + help.AddSep() + add(_('&Submit Bug Report'), do_diagnostic) + add(_('Su&ggest a Feature'), send_features_email) + help.AddSep() + + if getattr(sys, 'DEV', False) or pref('debug.console', False): + add(_('Show Debug Console'), wx.GetApp().toggle_crust) + help.AddSep() + + add(_('Su&pport Digsby'), lambda: support.SupportFrame.MakeOrShow(parent)) + + for hook in Hook("digsby.help.actions"): + help_menu_items = hook() + for item in help_menu_items: + add(*item) + + def on_view_show(_m): + sortstatus = pref('buddylist.sortby').startswith('*status') + + showoffline = pref('buddylist.show_offline') + hidegroups = pref('buddylist.hide_offline_groups') + groupoff = pref('buddylist.group_offline') + + groupoffline.Enable(not sortstatus and showoffline) + groupoffline.Check(groupoff and (not sortstatus and showoffline)) + + hideoffline.Enable(not sortstatus) + hideoffline.Check((sortstatus and not showoffline) + or (not sortstatus and hidegroups)) + + digsbyMenuName = _('&Digsby') + if config.platformName == "mac": + digsbyMenuName = _("&File") + + menu.Append(digsby, digsbyMenuName, onshow = on_digsby_show) + menu.Append(view, _('&View'), onshow = on_view_show) + menu.Append(tools, _('&Tools'), onshow = lambda m: on_accounts_entries(parent, m)) + menu.Append(help, _('&Help')) + return menu + +buddylist_panel_names = dict(status = _('Status Panel'), + blist = _('Buddy List'), + elist = _('Email Accounts'), + slist = _('Social Networks'), + clist = _('Connections')) + +def edit_buddylist_order(parent, *_a): + # show an existing one if already on screen + + if VisualListEditor.RaiseExisting(): + return + + editor = VisualListEditor(parent, profile.prefs['buddylist.order'], + buddylist_panel_names, + lambda l: setpref('buddylist.order', l), + _('Arrange Panels')) + editor.Show() + +def im_account_menu(parent, menu, account): + 'Builds a menu for an IM account.' + + menu.RemoveAllItems() + + # Sign On / Sign Off + if account.connected: menu.AddItem(_('&Sign Off'), callback = account.disconnect) + else: menu.AddItem(_('&Sign On'), callback = account.connect) + + # Edit Account + menu.AddItem(_('&Edit Account...'), callback = lambda: profile.account_manager.edit(account)) + + # if connected, append more action menu items + if account.connected: + menu.AddSep() + + # grab actions for the connection (ignoring Connect and Disconnect methods, since + # we already have "Sign On/Off" above) + actions.menu(parent, account.connection, menu, + filter = lambda func: func.__name__ not in ('Disconnect', 'Connect')) + + actions.menu(parent, account, menu) + + +def status_setter(status): + def _do_set_status(s = status): + import hooks; hooks.notify('digsby.statistics.ui.select_status') + return profile.set_status(s) + return _do_set_status + +def add_status_menu_item(menu, statusmsg): + return menu.Append(statusmsg.title, + bitmap = StatusMessage.icon_for(statusmsg), + callback = status_setter(statusmsg)) + + +def on_accounts_entries(parent, menu): + accounts = profile.account_manager.accounts + + if hasattr(menu, '_oldaccts'): + if getattr(menu, '_oldaccts', []) == [(id(acct), acct.connected) for acct in accounts]: + return # early exit if the accounts list hasn't changed + + if not hasattr(menu, '_account_items'): + menu._account_items = [] + + item_ids = menu._account_items + + # first: remove old + for itemid in item_ids: + menu.Remove(itemid) + + item_ids[:] = [] + parent.Unbind(wx.EVT_MENU_OPEN) + + if accounts and not menu[-1].IsSeparator(): + menu.AddSep() + + for acct in accounts: + accticon = acct.serviceicon + bitmap = accticon.Greyed if not acct.connected else accticon + mi = menu.AppendLazyMenu(acct.name, (lambda m, acct=acct: im_account_menu(parent, m, acct)), + bitmap = bitmap) + + # add a (potentially greyed) service icon + item_ids += [mi.Id] + + menu._oldaccts = [(id(acct), acct.connected) for acct in accounts] + + if menu[-1].IsSeparator(): menu.RemoveItem(menu[-1]) + + +def yahoo_buddy_menu(menu, contact): + ''' + Appends a Yahoo-specific "Stealth Settings" submenu to "menu". + + account - YahooProtocol object + menu - a UMenu + selection - the selected Contact object or None + ''' + menu_title = _('&Stealth Settings') + + stealth_menu = Menu(menu.Window) + enable = stealth_menu.Enable + a = stealth_menu.AddCheckItem + + name = getattr(contact, 'name', _('Selection')) + + # append an item for each stealth option + items = [a(_('Appear Online to {name}').format(name=name), + callback = lambda: contact.set_stealth_session(False)), + a(_('Appear Offline to {name}').format(name=name), + callback = lambda: contact.set_stealth_session(True)), + a(_('Appear Permanently Offline to {name}').format(name=name), + callback = lambda: contact.set_stealth_perm(True))] + + # append a separator and a link to the webpage explaining stealth mode + stealth_menu.AddSep() + a(_('Learn More (URL)'), + callback = lambda: wx.LaunchDefaultBrowser('http://messenger.yahoo.com/stealth.php')) + + online, offline, perm = items + + # items are disabled unless a yahoo buddy is selected + if contact is None or contact.service != 'yahoo': + for item in items: + enable(item.Id, False) + item.Check(False) + else: + invisible = profile.status.invisible + enable(online.Id, True) + enable(offline.Id, invisible) + enable(perm.Id, True) + + if contact.stealth_perm: + perm.Check(True) + elif invisible and contact.stealth_session: + offline.Check(True) + elif not invisible or not contact.stealth_session: + online.Check(True) + + menu.AddSubMenu(stealth_menu, menu_title) + +def jabber_buddy_menu(menu, contact): + + # offline contacts have no resources + #don't need a menu if there's only one, and it is None + if not contact.online or getattr(contact, 'iswidget', False) or \ + (len(list(contact))==1 and list(contact)[0].jid.resource is None): + return + + resource_menu = Menu(menu.Window) + a = resource_menu.AddItem + + for resource in contact: + # append a menu item for each resource + a(resource.name, callback = lambda r=resource: r.chat()) + + menu.AddSubMenu(resource_menu, _('Chat with Resource')) + +def metacontact_buddy_menu(menu, mc): + lenmc = len(mc) + + for contact in mc: + # add a service specific menu for each contact in the metacontact + contact_menu = Menu(menu.Window) + + # search_bases = False means we'll get the actions specific to the buddy's service + actions.menu(menu.Window, contact, contact_menu, search_bases = False) + + if contact_menu[0].IsSeparator(): + contact_menu.RemoveItem(contact_menu[0]) + + if lenmc > 2: + contact_menu.AddSep() + contact_menu.AddItem(_('&Remove from Merged Contact'), + callback = lambda contact=contact: + mc.manager.remove(mc, contact)) + + # use the contact's service icon for the submenu (greyed out if offline) + icon = contact.serviceicon + if not contact.online: + icon = icon.Greyed + + menu.AddSubMenu(contact_menu, contact.name, bitmap = icon) + +def send_features_email(): + try: + createEmail('mailto:features@digsby.com') + except Exception: + traceback.print_exc() + + from common.emailaccount import mailclient_launch_error + mailclient_launch_error() + diff --git a/digsby/src/gui/buddylist/buddylistrules.py b/digsby/src/gui/buddylist/buddylistrules.py new file mode 100644 index 0000000..90e4c75 --- /dev/null +++ b/digsby/src/gui/buddylist/buddylistrules.py @@ -0,0 +1,256 @@ +''' +rules for drag and drop feedback when moving items around on the buddylist +''' + +from common import pref +from contacts.buddylistsort import SpecialGroup +from common import buddy +from contacts import Contact +from contacts.metacontacts import MetaContact +from contacts.Group import Group, DGroup +from common import profile +from contacts.buddylistfilters import OfflineGroup, FakeRootGroup + +# TODO: don't import everything here. instead, have an interface that +# buddylist elements conform to. +from gui.searchgui import SearchEntry + +SERVICES_WITHOUT_GROUPS = [] + +def user_ordering(): + ''' + Returns True if user ordering is affecting the current sort algorithm. + ''' + + return profile.blist.user_ordering + +def can_drop_buddy_group(group): + if SpecialGroup_TEST(group) and not FakeRootGroup_TEST(group): + return False + + return user_ordering() + +def above_below_middle(percent): + border_space = pref('buddylist.border_space', 35) / 100.0 + + if percent < border_space: + return 'above' + elif percent > 1 - border_space: + return 'below' + else: + return 'middle' + +def above_below(percent): + if percent <= .50: + return 'above' + else: + return 'below' + +def buddy_buddy(model, frm, to, i, position, parent_position, percent): + if position in ('above', 'below'): + parent = model.parent_of(to) + frmparent = model.parent_of(frm) + if can_drop_buddy_group(parent) and not OfflineGroup_TEST(frmparent): #does this check sorting or not? I think probably. must also check what kind of group, duh. +# to = parent + return to, position +# if position is 'above': +# return buddy_group(model, frm, to, i, position='foo') +# else: +# return buddy_group(model, frm, to, i, position='foo') + else: + return buddy_buddy(model, frm, to, i, position='middle', parent_position=parent_position, percent=percent) + elif position == 'middle': + return to, 'middle' #yup, try to drop it on this buddy, though still decide if that's possible + +def group_buddy(model, frm, to, i, position, parent_position, percent): + to = model.parent_of(to) + return group_group(model, frm, to, model.index_of(to), position, parent_position, percent) + +def group_group(model, frm, to, i, position, parent_position, percent): + #find position of cursor, group bounding box, recalculate percent. + #if user ordering, then ok, except for offline group. + if OfflineGroup_TEST(to): + if i != 0: + return item(model, frm, model[i-1], i-1, position='below', parent_position='below', percent=percent) + return to, parent_position + +def buddy_group(model, frm, to, i, position, parent_position, percent): +# if not can_drop_buddy_group(to) and position in ('middle', 'below'): +# return to, position + if OfflineGroup_TEST(model.parent_of(frm)): + return to, 'middle' + + if position == 'above': + if i == 0: #hit the top of the list + return to, 'middle' #target is group, position -1 + else: + #if item above is a collapsed group, drop on this group + if not model.is_expanded(i-1): + return to, 'middle' + else: + return item(model, frm, model[i-1], i-1, position='below', parent_position=parent_position, percent=percent) + elif position == 'below': + #if item below is a collapsed group, drop on this group + if not model.is_expanded(i): + return to, 'middle' + else: + return to, 'below' + elif position == 'middle': + return to, 'middle' + else: + raise AssertionError, "buddy_group needs a valid" \ + " position (above/below/middle), got %r" % position + + +BUDDY = (MetaContact, Contact, buddy) +GROUP = (DGroup, ) + +OFFLINEGROUP = (OfflineGroup,) + +def OfflineGroup_TEST(obj): + if isinstance(obj, OFFLINEGROUP): + return True + elif hasattr(obj, 'groupkey'): + retval = obj.groupkey() == 'SpecialGroup_offlinegroup' + return retval + else: + return False + +def SpecialGroup_TEST(obj): + if isinstance(obj, SpecialGroup): + return True + elif hasattr(obj, 'groupkey'): + retval = obj.groupkey().startswith('SpecialGroup_') + return retval + else: + return False + +def FakeRootGroup_TEST(obj): + return isinstance(obj, FakeRootGroup) + +TYPES = (BUDDY, GROUP, SearchEntry) + +def target_search(model, frm, to, i, position, parent_position, percent): + return to, above_below(percent) + +origin = {(BUDDY, BUDDY) : buddy_buddy, + (BUDDY, GROUP) : buddy_group, + (GROUP, BUDDY) : group_buddy, + (GROUP, GROUP) : group_group, + (SearchEntry, SearchEntry) : target_search, + } + +def to_type(obj): + for typ in TYPES: + if isinstance(obj, typ): + return typ + return sentinel + +def item(model, frm, to, i, position=None, parent_position=None, percent=None): + typ_to = to_type(to) + typ_frm = to_type(frm) + if (typ_to, typ_frm) not in origin: + return sentinel + else: + return origin[(typ_frm, typ_to)](model, frm, to, i, position, parent_position, percent) + +def target(model, frm, to, i, percent, parent_percent): + position = above_below_middle(percent) + parent_position = above_below(parent_percent[1]) + result = item(model, frm, to, i, position, parent_position, percent) + if result is sentinel: + return to, DISALLOW + return result + +ITEM_BOX = 'box' +GROUP_BOX = 'group_box' +ABOVE = 'above' +BELOW = 'below' +BELOW_GROUP = 'below_group' +DISALLOW = 'disallow' + +FEEDBACK_TYPES = set([ITEM_BOX, + GROUP_BOX, + ABOVE, + BELOW, + BELOW_GROUP, + DISALLOW]) + + +def feed_buddy_buddy(model, frm, to, position): + assert position in ('above', 'middle', 'below') + if getattr(to, 'iswidget', False): + return DISALLOW + if OfflineGroup_TEST(model.parent_of(to)) and not OfflineGroup_TEST(model.parent_of(frm)): + return DISALLOW + if getattr(frm, 'service', None) in SERVICES_WITHOUT_GROUPS and not isinstance(frm, MetaContact): + return ITEM_BOX + if position == 'middle': + return ITEM_BOX + return position + +def feed_buddy_group(model, frm, to, position): + assert position in ('middle', 'below') + if getattr(frm, 'service', None) in SERVICES_WITHOUT_GROUPS and not isinstance(frm, MetaContact): + return DISALLOW + if SpecialGroup_TEST(to): + if to.id == Group.FAKEROOT_ID: + return GROUP_BOX + else: + return DISALLOW + if OfflineGroup_TEST(model.parent_of(frm)): + return DISALLOW + if position == 'middle': + return GROUP_BOX + else: + # based on user ordering + if user_ordering(): + return BELOW + else: + return GROUP_BOX + +def feed_group_buddy(model, frm, to, position): + assert False + pass + +def feed_group_group(model, frm, to, position): + assert position in (ABOVE, BELOW) + if OfflineGroup_TEST(to): + return DISALLOW + if position == BELOW: + return BELOW_GROUP + return position + + +def feed_search(model, frm, to, position): + if position == 'middle': + return DISALLOW + else: + return position + +feedback_types = {(BUDDY, BUDDY) : feed_buddy_buddy, + (BUDDY, GROUP) : feed_buddy_group, + (GROUP, BUDDY) : feed_group_buddy, + (GROUP, GROUP) : feed_group_group, + (SearchEntry, SearchEntry) : feed_search, + } + +def feedback(model, frm, to, position): + typ_to = to_type(to) + typ_frm = to_type(frm) + if to is frm or not allow_drag(frm): + return DISALLOW + if (typ_to, typ_frm) not in feedback_types: + return DISALLOW + result = feedback_types[(typ_frm, typ_to)](model, frm, to, position) + assert result in FEEDBACK_TYPES + return result + + +def allow_drag(frm): + if getattr(frm, 'iswidget', False): + return False + if OfflineGroup_TEST(frm): + return False + return True + diff --git a/digsby/src/gui/buddylist/renderers.py b/digsby/src/gui/buddylist/renderers.py new file mode 100644 index 0000000..062e8b5 --- /dev/null +++ b/digsby/src/gui/buddylist/renderers.py @@ -0,0 +1,832 @@ +''' + +BuddyList renderers for drawing contact rows + +''' +import util.primitives.strings as strings +from common import profile +from common.Buddy import get_status_orb + +DEFAULT_NOICON_OPACITY = .40 + + +import wx +from wx import ALIGN_LEFT, ALIGN_CENTER_VERTICAL, Font, Rect, ALIGN_BOTTOM, FONTFAMILY_DEFAULT, \ + FONTSTYLE_ITALIC, FONTSTYLE_NORMAL, FONTWEIGHT_BOLD, FONTWEIGHT_NORMAL, SystemSettings_GetColour, Point, \ + ALIGN_RIGHT, ALIGN_CENTER +lmiddle = ALIGN_LEFT | ALIGN_CENTER_VERTICAL +LBOTTOM = ALIGN_LEFT | ALIGN_BOTTOM +RBOTTOM = ALIGN_RIGHT | ALIGN_BOTTOM +from gui.textutil import default_font +from util.introspect import memoize +from util.lrucache import lru_cache +from util.primitives.error_handling import try_this +from util.primitives.funcs import isiterable, do +from util.primitives.mapping import Storage as S +from time import time +from logging import getLogger; log = getLogger('renderers'); info = log.info +from common import pref, prefprop +import hooks + + +from gui import skin +from gui.skin.skinobjects import SkinColor, Margins +syscol = lambda s: SkinColor(SystemSettings_GetColour(s)) + +from PIL import Image + +from traceback import print_exc +import sys + +replace_newlines = lru_cache(100)(strings.replace_newlines) + +def contact_online(contact): + 'Whether to display a buddy as online or not in the buddylist.' + + return contact.online or contact.status == 'mobile' + +def get_contact_status(contact): + if not contact_online(contact): + return '' + + msg = hooks.reduce('digsby.status.tagging.strip_tag', contact.stripped_msg, contact.status, impl='text') + if msg is not None: + return replace_newlines(msg) + + return '' + +_cached_noicon = None + +from gui.toolbox.imagefx import pil_setalpha + +def _load_noicon(): + try: + return skin.get('BuddiesPanel.BuddyIcons.NoIcon').PIL + except: + # A fallback. + return Image.open(skin.resourcedir() / 'AppDefaults' / 'contact.png') + +try: + _registered_noicon_hook +except NameError: + _registered_noicon_hook = False + +def _on_skin_change(skin, variant): + global _cached_noicon + _cached_noicon = None + +def get_no_icon(with_transparency = False): + 'Return the icon used for buddies with no icons.' + + global _cached_noicon + global _registered_noicon_hook + + if not _registered_noicon_hook: + _registered_noicon_hook = True + import hooks + hooks.register('skin.set.pre', _on_skin_change) + + try: + return _cached_noicon[int(with_transparency)] + except TypeError: + pass + + img = _load_noicon() + imgnoalpha = img.copy() + + try: + # Transparency is specifed in a skin value like "66%"... + alpha = skin.get('BuddiesPanel.BuddyIcons.NoIconAlpha', '75%').strip().rstrip('%') + alpha_opacity = float(alpha) / 100.0 + except: + alpha_opacity = DEFAULT_NOICON_OPACITY + + pil_setalpha(img, alpha_opacity) # Lighten the alpha channel somewhat + + + _cached_noicon = (imgnoalpha, img) + + return _cached_noicon[int(with_transparency)] + +def print_bicon_exc(buddykey): + print_exc() + +if not getattr(sys, 'DEV', False) or True: + # on release builds, never print a traceback for a buddy's icon more than + # once. + print_bicon_exc = memoize(print_bicon_exc) + +def _geticon(buddy): + 'Returns a valid buddy icon, or None.' + + try: + icon = buddy.icon + except Exception: + print_bicon_exc(buddy.info_key) + return None + + # don't return empty GIFs lacking any color + try: + if icon is not None: + try: + extrema = icon._extrema + except AttributeError: + extrema = icon._extrema = icon.getextrema() + + # extrema may be (0, 0) or ((0, 0), (0, 0), ...) for each channel + if extrema != (0, 0) and not all(e == (0, 0) for e in extrema): + return icon + except Exception, e: + try: + print_bicon_exc(buddy.info_key) + except Exception: + pass # if we fail printing the exception, so what. + + return None + +def get_buddy_icon_path(buddy): + icon = _geticon(buddy) + + if icon is None: + return skin.get('BuddiesPanel.BuddyIcons.NoIcon').path + else: + return buddy.icon_path + +def get_buddy_icon_url(buddy): + path = get_buddy_icon_path(buddy) + + # webkit is caching the icon; add the modification time to URL to + # prevent stale icons from being shown + return path.url() + '?modified=%d' % int(path.mtime) + +def get_buddy_icon(buddy, size=None, round_size=1, grey_offline=True, with_transparency=False, meta_lookup=False): + ''' + Returns a buddy icon or a special "no icon" image for the given buddy. + + size should be one integer, the size to resize to + round_corners indicates whether to cut the corners off the icon + ''' + icon = None + + if meta_lookup: + metas = profile.blist.metacontacts.forbuddy(buddy) + if metas: + metas = set(metas) + icon = _geticon(metas.pop()) + while icon is None and metas: + icon = _geticon(metas.pop()) + + if icon is None: + icon = _geticon(buddy) + + isno = icon is None + icon = get_no_icon(with_transparency) if isno else icon + icon = icon.Resized(size) if size else icon + + if isno or not round_size: + i = icon.Greyed.WXB if grey_offline and not contact_online(buddy) else icon.WXB + else: + i = icon.Rounded(round_size).WXB + i = i.Greyed if grey_offline and not contact_online(buddy) else i + + return i + +def get_idle_string(contact): + ''' + Returns a single string, possibly empty for a contact's idle time. + ''' + idle = getattr(contact, 'idle', None) + + if idle in (True, False, None): + return '' + + elif isinstance(idle, (int, long)): + diff = int(time() - idle) + + if diff != 0: + return _get_idle_string_from_seconds(diff) + else: + return '' + else: + return '' + #raise TypeError, str(type(idle)) + + + +@lru_cache(100) +def _get_idle_string_from_seconds(secs): + 'Formats seconds into a readable idle time, like (32) or (4:55)' + + mins, secs = divmod(secs, 60) + hours, mins = divmod(mins, 60) + days, hours = divmod(hours, 24) + + timeStr = '' + if days: + return '(%dd)' % int(days) + if hours > 0: + timeStr += '%d' % int( hours ) + ":" + timeStr += '%02d' % int( mins ) + else: + mins = int(mins) + if mins < 10: + timeStr += '%dm' % mins + else: + timeStr += '%02dm' % mins + + return '(%s)' % timeStr if timeStr else '' + +def get_prefs(): + try: + from common import profile + return profile.prefs + except ImportError: + from util.observe import ObservableDict + return ObservableDict() + + +def safefont(name, size, bold = False): + weight = FONTWEIGHT_BOLD if bold else FONTWEIGHT_NORMAL + + try: + return Font(size, FONTFAMILY_DEFAULT, FONTSTYLE_NORMAL, weight, False, name) + except: + print_exc() + + font = default_font() + font.SetPointSize(size) + font.SetWeight(weight) + return font + + + + + +#from gui.ctextutil import RectPos +#wx.Rect.Pos = RectPos + +class Renderer(object): + 'Common elements between Groups and Contacts.' + + def __init__(self, parent): + self.parent = parent + self.prefs = get_prefs() + self.skin = S(fontcolors = S()) + + def UpdateSkin(self): + # Initialize skin values. + s, g = self.skin, skin.get + + s.bg = g('BuddiesPanel.Backgrounds.Buddy', lambda: syscol(wx.SYS_COLOUR_LISTBOX)) + s.selectedbg = g('BuddiesPanel.Backgrounds.BuddySelected', lambda: syscol(wx.SYS_COLOUR_HIGHLIGHT)) + s.hoverbg = g('BuddiesPanel.Backgrounds.BuddyHover', lambda: syscol(wx.SYS_COLOUR_LISTBOX)) + + def getpref(self, prefname, default = None): + return pref('buddylist.layout.%s' % prefname, default) + + def attrlink(self, attr): + return self.prefs.link('buddylist.layout.%s' % attr, + lambda val: (self.calcsizes(), + self.parent.list_changed()), False, obj = self) + + def draw_background( self, obj, dc, rect, n, selected, hover ): + s = self.skin + if selected and s.selectedbg: s.selectedbg.Draw(dc, rect, n) + elif hover and s.hoverbg: s.hoverbg.Draw(dc, rect, n) + elif s.bg: s.bg.Draw(dc, rect, n) + + +class GroupCellRenderer(Renderer): + def __init__( self, parent ): + Renderer.__init__(self, parent) + + layout_attrs = ''' + name_font_face + name_font_size + padding + '''.strip().split() + + do(self.attrlink(attr) for attr in layout_attrs) + + self.UpdateSkin() + + def UpdateSkin(self): + Renderer.UpdateSkin(self) + s = self.skin + + s.margins = skin.get('BuddiesPanel.GroupMargins') + s.padding = skin.get('BuddiesPanel.GroupPadding', lambda: Point(4,4)) + + # Expanded/Collapsed icons next to group names + g = lambda k, default = sentinel: skin.get('BuddiesPanel.GroupIcons.' + k, default) + s.expanded = g('Expanded', lambda: None) + s.expandedhover = g('ExpandedHover', lambda: s.expanded) + s.expandedselected = g('ExpandedSelected', lambda: s.expanded) + + s.collapsed = g('Collapsed', lambda: None) + s.collapsedhover = g('CollapsedHover', s.collapsed) + s.collapsedselected = g('CollapsedSelected', s.collapsed) + + # Group backgrounds (default to Buddy backgrounds if not specified) + g = lambda k, default: skin.get('BuddiesPanel.Backgrounds.' + k, default) + s.bg = g('Group', lambda: g('Buddy')) + s.hoverbg = g('GroupHover', lambda: g('BuddyHover')) + s.selectedbg = g('GroupSelected', lambda: g('BuddySelected')) + + # Group font colors (default to Buddy font colors if not specified) + f = s.fontcolors + g = lambda k, default: skin.get('BuddiesPanel.FontColors.' + k, default) + f.group = g('Group', lambda: g('Buddy', lambda: syscol(wx.SYS_COLOUR_WINDOWTEXT))) + f.grouphover = g('GroupHover', lambda: g('BuddyHover', lambda: syscol(wx.SYS_COLOUR_WINDOWTEXT))) + f.groupselected = g('GroupSelected', lambda: g('BuddySelected', lambda: syscol(wx.SYS_COLOUR_HIGHLIGHTTEXT))) + + self.calcsizes() + + def item_height( self, obj ): + return int(self.group_height) + + def calcsizes(self): + p = self.getpref + margins = self.skin.margins + padding = self.skin.padding + + # Main Font: contact's name + self.mainfont = safefont(p('name_font_face', None), try_this(lambda: int(p('name_font_size')), 10)) + self.mainfont_height = self.mainfont.LineHeight + + # group_height is reported via OnMeasureItem to VListBox + self.group_height = int(self.mainfont_height) + margins.top + margins.bottom + (padding.y * 2) + + self.depth_indent = p('indent', 5) + + font_face = prefprop('buddylist.layout.name_font_face', None) + font_size = prefprop('buddylist.layout.name_font_size', None) + group_indent = prefprop('buddylist.layout.indent', 0) + + def Draw( self, dc, rect, selected, obj, depth, expanded, index, hover ): + s = self.skin + + # apply margins + rect = rect.AddMargins(wx.Rect(*s.margins)).AddMargins(wx.Rect(0, s.padding.y, 0, s.padding.y)) + + # Group font is drawn with the same as the buddies. + fontface = self.font_face + font = safefont(fontface, try_this(lambda: int(self.font_size), 10), bold = True) + dc.SetFont( font ) + + # indent for depth + rect = rect.Subtract(left = self.group_indent * depth) + + # Expander triangles. + if isiterable( obj ): + triangle = self.get_expander(selected, expanded, hover) + + if triangle is not None: + dc.DrawBitmap(triangle, rect.x, rect.VCenter(triangle), True) + rect = rect.Subtract(left = triangle.Width + s.padding.x) + + # decide on a foreground text color + if selected: fg = s.fontcolors.groupselected + elif hover: fg = s.fontcolors.grouphover + else: fg = s.fontcolors.group + dc.SetTextForeground( fg ) + + # the actual text label + dc.DrawTruncatedText(obj.display_string, rect, alignment = lmiddle) + + def get_expander(self, selected, expanded, hover): + iconname = 'expanded' if expanded else 'collapsed' + if selected: iconname += 'selected' + elif hover: iconname += 'hover' + return getattr(self.skin, iconname, None) + +class ContactCellRenderer(Renderer): + def __init__(self, parent): + Renderer.__init__(self, parent) + + self._lastcalc = None + + # changing any of these prefs triggers a RefreshAll + self.layout_attrs = ''' + name_font_face + name_font_size + show_extra + extra_info + extra_font_face + extra_font_size + extra_padding + show_buddy_icon + buddy_icon_pos + badge_max_size + badge_min_size + show_status_icon + status_icon_pos + status_icon_size + show_service_icon + service_icon_pos + badge_ratio + buddy_icon_size + service_icon_size + side_icon_size + padding + indent + grey_offline + blocked + '''.strip().split() + + do(self.attrlink(attr) for attr in self.layout_attrs) + + + self.UpdateSkin() + + icons = ['service_icon', 'status_icon', 'buddy_icon'] + + def UpdateSkin(self): + Renderer.UpdateSkin(self) + + self.drawseqs = {} + self._lastcalc = [] + + s, g = self.skin, skin.get + + self.statusicons = g('statusicons') + + s.margins = g('BuddiesPanel.BuddyMargins') + s.icon_frame = g('BuddiesPanel.BuddyIcons.Frame', None) + s.icon_frame_size = Margins(g('BuddiesPanel.BuddyIcons.FrameSize', (0, 0, 0, 0))) + + s.round_corners = try_this(lambda: int(g('BuddiesPanel.BuddyIcons.Rounded', 1)), 1) + + f, g = s.fontcolors, lambda k, default: skin.get('BuddiesPanel.FontColors.' + k, default) + f.buddy = g('Buddy', lambda: syscol(wx.SYS_COLOUR_WINDOWTEXT)) + f.buddyoffline = g('BuddyOffline', lambda: syscol(wx.SYS_COLOUR_GRAYTEXT)) + f.buddyselected = g('BuddySelected', lambda: syscol(wx.SYS_COLOUR_HIGHLIGHTTEXT)) + f.buddyhover = g('BuddyHover', lambda: f.buddy) + + f.status = g('Status', lambda: f.buddy) + f.statushover = g('StatusHover', lambda: f.buddyhover) + f.statusselected = g('StatusSelected', lambda: f.buddyselected) + + f.idletime = g('IdleTime', lambda: syscol(wx.SYS_COLOUR_GRAYTEXT)) + f.idletimehover = g('IdleTimeHover', lambda: syscol(wx.SYS_COLOUR_GRAYTEXT)) + f.idletimeselected = g('IdleTimeSelected', lambda: syscol(wx.SYS_COLOUR_HIGHLIGHTTEXT)) + + # icons to be drawn + self.calcsizes() + + def calcsizes(self): + p, s = self.getpref, self.skin + padding = p('padding', 4) + + do(setattr(self, k.replace('.', '_'), p(k)) for k in self.layout_attrs) + + # Main Font: contact's name + sz = int(p('name_font_size', 10)) + self.mainfont = safefont(p('name_font_face', None), sz) + self.mainfont.Style = FONTSTYLE_NORMAL + self.mainfont_height = mainfont_height = self.mainfont.LineHeight + + # Extra font: idle time, status message + self.extrafont = safefont(p('extra_font_face', None), int(p('extra_font_size', 10))) + self.extrafont_height = extrafont_height = self.extrafont.LineHeight + + # depth indent + self.depth_indent = p('indent', 5) + + # decide on a maximum height + icon_size = p('buddy_icon_size', 0) + if s.icon_frame_size: + # add overlay size if necessary + icon_size += s.icon_frame_size.top + s.icon_frame_size.bottom + + + show_icon = p('show_buddy_icon', False) + + # item_height method will use this + extraheight = extrafont_height if (p('show_extra', True) \ + and p('extra_info', 'status') in ('status','both'))\ + else 0 + margins = self.skin.margins + self.cell_height = padding * 2 + \ + max(icon_size if show_icon else 0, mainfont_height + extraheight) + \ + margins.top + margins.bottom + + if self.cell_height < mainfont_height * 1.2: + self.cell_height = mainfont_height * 1.2 + + self.drawseqs.clear() + self._serviceiconcache = {} + self._lastcalc = None + return self.cell_height + + def calcdraw(self, w, h, Rect = Rect): + if self._lastcalc == (w, h): + return self._lastseq + + s = self.skin + rect = Rect(0, 0, w, h).AddMargins(wx.Rect(*s.margins)) + icons = sorted(((icon, getattr(self, icon + '_pos')) for icon in self.icons), + key = lambda o: {'f': -1, 'b': 1}.get(o[1][0], 0)) + + seq = [] + last = Rect() + badge_size = min(self.badge_max_size, max(self.badge_min_size, int(self.buddy_icon_size * self.badge_ratio))) + frame_size = s.icon_frame_size + padding = self.padding + hpadding = 4 + + for icon, pos in icons: + if getattr(self, 'show_' + icon): + pos = pos.lower() + size = getattr(self, icon + '_size') + left = pos.endswith('left') + iconpos = Point(-size * int(not left), 0) + + if icon == 'buddy_icon': + # special case for buddy icon, which can optionally have a frame around it. + iconw = size + frame_size.left + frame_size.right + frameRect = Rect(0, 0, iconw, size + frame_size.top + frame_size.bottom) + frameRect.x, frameRect.y = rect.Pos(wx.Point(-frameRect.width * int(not left), 0))[0], rect.VCenterH(frameRect.height) + + last = Rect(frameRect.x + frame_size.left, frameRect.y + frame_size.top, size, size) + seq += [(getattr(self, 'get_buddy_icon'), last, 0)] + + seq += [(getattr(self, 'get_frame_icon'), frameRect, 0)] + rect = rect.Subtract(**{'left' if left else 'right': iconw + hpadding}) + bitmap = getattr(self, 'get_' + icon) + else: + if not pos.startswith('b'): + # non badge + r = Rect(rect.Pos(iconpos)[0], rect.VCenterH(size), size, size) + rect = rect.Subtract(**{'left' if left else 'right': size + hpadding}) + last = r + alignment = ALIGN_CENTER + bitmap = getattr(self, 'get_' + icon) + else: + # badge + bp = badge_size + alignment = LBOTTOM if left else RBOTTOM + badgepos = last.Pos(wx.Point(0 if left else -bp, -bp)) + r = Rect(badgepos[0], badgepos[1], badge_size, badge_size) + + bitmap = lambda obj, icon=icon: getattr(self, 'get_' + icon)(obj).ResizedSmaller(badge_size) + + seq.append((bitmap, r, alignment)) + + + self.inforect = rect + self._lastcalc = (w, h) + self._lastseq = seq + return seq + + def Draw(self, dc, rect, selected, obj, depth, expanded, index, hover, Rect = Rect): + DrawBitmap = dc.DrawBitmap + DrawTruncatedText = dc.DrawTruncatedText + idle_string = get_idle_string(obj) + extrafont = self.extrafont + extra_info = self.extra_info if self.show_extra else None + msg = get_contact_status(obj) + padding, extra_padding = self.padding, self.extra_padding + contact_name = obj.alias + mainfont_height = self.mainfont_height + + + # draw all icons + for method, r, align in self.calcdraw(rect.width, rect.height): + try: + b = method(obj) + except: + print_exc() + else: + if b: b.Draw(dc, Rect(rect.x + r.x, rect.y + r.y, r.width, r.height), align) + + rect = rect.AddMargins(wx.Rect(*self.skin.margins)) + rect.x, rect.width = self.inforect.x, self.inforect.width + + # draw the status message (if necessary) + if msg and extra_info in ('status', 'both'): + th = self.mainfont.LineHeight + extra_padding + self.extrafont_height + rect = Rect(rect.x, rect.VCenterH(th), rect.width, rect.height) + namerect = Rect(rect.x, rect.y + 1, rect.Width, self.mainfont.LineHeight) + inforect = Rect(rect.x, rect.y + self.mainfont.LineHeight + extra_padding, rect.Width, self.extrafont_height) + + DrawTruncatedText(self.get_contact_info(obj, dc, selected, expanded, hover), inforect, alignment = lmiddle) + else: + namerect = rect + + # draw idle time + hpadding = 4 + if idle_string and extra_info in ('idle', 'both'): + # do some measurements to see if + # a) idle time needs to be left aligned against the buddy name, or + # b) right aligned and cutting off the buddy name (i.e., buddy na...IDLE) + namew, nameh, namedescent, __ = dc.GetFullTextExtent(contact_name, self.mainfont) + w, h, desc, __ = dc.GetFullTextExtent(idle_string, extrafont) + + iy = 3 + diff = namew + w + hpadding - namerect.width + + if diff > 0: + x, y = namerect.Pos((-w, 0))[0], namerect.Y + r = Rect(x, y, w, namerect.Height) + namerect = namerect.Subtract(right = w + hpadding) + else: + r = Rect(namerect.X + namew + hpadding, namerect.Y, w, namerect.Height) + + self.set_idle_time_dc(obj, dc, selected, expanded, hover) + dc.DrawLabel(idle_string, r, ALIGN_LEFT | ALIGN_CENTER_VERTICAL) + + # draw buddy name + self.set_contact_name_dc(obj, dc, selected, expanded, hover) + DrawTruncatedText(contact_name, namerect, alignment = lmiddle) + + def get_buddy_icon(self, contact, *a): + return get_buddy_icon(contact, self.buddy_icon_size, self.skin.round_corners, self.grey_offline, with_transparency = True) + + def get_frame_icon(self, contact, *a): + return self.skin.icon_frame + + def get_service_icon(self, contact, *a): + try: + icon = contact.serviceicon + except AttributeError: + icon = skin.get('serviceicons.' + contact.service) + + if max(icon.Width, icon.Height) > self.service_icon_size: + icon = icon.Resized(self.service_icon_size) + + if self.grey_offline and not contact_online(contact): + icon = icon.Greyed + + return icon + + + def get_status_icon(self, contact, *a): + "Returns an icon for a contact's status." + if contact.blocked: + orb = 'blocked' + else: + try: + orb = contact.status_orb + except AttributeError: + orb = get_status_orb(contact) + icon = skin.get('statusicons.'+orb) + if max(icon.Width, icon.Height) > self.status_icon_size: + return icon.Resized(self.status_icon_size) + else: + return icon + + + def set_contact_name_dc(self, contact, dc, selected, expanded, hover): + 'Returns a name for the given contact.' + + # select foreground colors + fontcolors = self.skin.fontcolors + online = contact_online(contact) + + if selected: fg = fontcolors.buddyselected + elif hover: fg = fontcolors.buddyhover + elif not online: fg = fontcolors.buddyoffline + else: fg = fontcolors.buddy + + dc.TextForeground = fg + + # select font + mainfont = self.mainfont + mainfont.SetStyle(FONTSTYLE_NORMAL if online else FONTSTYLE_ITALIC) + + # bold the buddyname when just after they come online + mainfont.SetWeight(FONTWEIGHT_BOLD if getattr(contact, 'entering', False) else FONTWEIGHT_NORMAL) + + dc.SetFont( mainfont ) + + def set_idle_time_dc(self, contact, dc, selected, expanded, hover): + # select foreground colors + fontcolors = self.skin.fontcolors + + if selected: fg = fontcolors.idletimeselected + elif hover: fg = fontcolors.idletimehover + else: fg = fontcolors.idletime + + dc.TextForeground = fg + + # select font + dc.Font = self.extrafont + + def get_contact_info(self, contact, dc, selected, expanded, hover): + 'Line #2 of the contact row (the status message)' + + # select foreground colors + fontcolors = self.skin.fontcolors + + if selected: fg = fontcolors.statusselected + elif hover: fg = fontcolors.statushover + else: fg = fontcolors.status + + dc.TextForeground = fg + + dc.SetFont( self.extrafont ) + return get_contact_status(contact) + + def item_height( self, obj ): + return int(self.cell_height) + +class MetaContactCellRenderer(ContactCellRenderer): + def __init__(self, parent): + ContactCellRenderer.__init__(self, parent) + +class SearchCellBase(Renderer): + icon_height = 16 + + def __init__(self, parent): + Renderer.__init__(self, parent) + self.UpdateSkin() + + for attr in ('padding', 'name_font_face', 'name_font_size'): + self.attrlink(attr) + + def calcsizes(self): + s = self.skin + p = self.getpref + self.padding = p('padding') + self.mainfont = safefont(p('name_font_face', None), try_this(lambda: int(p('name_font_size')), 10)) + self.mainfont_height = self.mainfont.LineHeight + self.cell_height = (max(self.icon_height, self.mainfont_height) + + s.margins.top + s.margins.bottom + + self.padding * 2) + + def item_height(self, obj): + return self.cell_height + +class SearchCellRenderer(SearchCellBase): + ''' + Draws buddylist entries for searching the web + ''' + + icon_horizontal_padding = 8 + + def UpdateSkin(self): + Renderer.UpdateSkin(self) + s = self.skin + self.skin.margins = skin.get('BuddiesPanel.BuddyMargins') + + f, g = s.fontcolors, lambda k, default: skin.get('BuddiesPanel.FontColors.' + k, default) + f.buddy = g('Buddy', lambda: syscol(wx.SYS_COLOUR_WINDOWTEXT)) + f.buddyoffline = g('BuddyOffline', lambda: syscol(wx.SYS_COLOUR_GRAYTEXT)) + f.buddyselected = g('BuddySelected', lambda: syscol(wx.SYS_COLOUR_HIGHLIGHTTEXT)) + f.buddyhover = g('BuddyHover', lambda: f.buddy) + f.details = g('IdleTime', lambda: syscol(wx.SYS_COLOUR_GRAYTEXT)) + + self.calcsizes() + + def Draw(self, dc, rect, selected, obj, depth, expanded, index, hover): + s = self.skin + icon = skin.get('appdefaults.search.icons.' + obj.searchengine.name, None) + + rect = rect.AddMargins(wx.Rect(*s.margins)) + + if icon is not None: + dc.DrawBitmap(icon, rect.x, rect.VCenter(icon), True) + rect = rect.Subtract(left = icon.Width + self.icon_horizontal_padding) + + dc.SetFont(self.mainfont) + + if selected: fg = s.fontcolors.buddyselected + elif hover: fg = s.fontcolors.buddyhover + else: fg = s.fontcolors.buddy + dc.TextForeground = fg + + # draw search engine name + text = obj.searchengine.gui_name + ': ' + w, h, desc, __ = dc.GetFullTextExtent(text) + dc.DrawLabel(text, rect, ALIGN_LEFT | ALIGN_CENTER_VERTICAL) + rect = rect.Subtract(left = w) + + # draw search string + dc.TextForeground = s.fontcolors.details + text = obj.searchstring + dc.DrawTruncatedText(text, rect, ALIGN_LEFT | ALIGN_CENTER_VERTICAL) + +class SearchCellOptionsRenderer(SearchCellBase): + def UpdateSkin(self): + Renderer.UpdateSkin(self) + s = self.skin + self.skin.margins = skin.get('BuddiesPanel.BuddyMargins') + + f, g = s.fontcolors, lambda k, default: skin.get('BuddiesPanel.FontColors.' + k, default) + f.details = g('IdleTime', lambda: syscol(wx.SYS_COLOUR_GRAYTEXT)) + + self.calcsizes() + + def Draw(self, dc, rect, selected, obj, depth, expanded, index, hover): + s = self.skin + rect = rect.AddMargins(wx.Rect(*s.margins)) + dc.SetFont(self.mainfont) + + # WARNING: assuming icon height = width + rect = rect.Subtract(left = self.icon_height + 8) + + # draw options string + dc.TextForeground = s.fontcolors.details + dc.DrawTruncatedText(_('Options...'), rect, ALIGN_LEFT | ALIGN_CENTER_VERTICAL) + diff --git a/digsby/src/gui/bugreporter/__init__.py b/digsby/src/gui/bugreporter/__init__.py new file mode 100644 index 0000000..140e163 --- /dev/null +++ b/digsby/src/gui/bugreporter/__init__.py @@ -0,0 +1,8 @@ +import wx + +if 'wxMSW' in wx.PlatformInfo: + from .bugreporterguiold import show_dialog +else: + from .bugreportergui import show_dialog + +from .crashgui import CrashDialog diff --git a/digsby/src/gui/bugreporter/bugreportergui.py b/digsby/src/gui/bugreporter/bugreportergui.py new file mode 100644 index 0000000..3a95c4d --- /dev/null +++ b/digsby/src/gui/bugreporter/bugreportergui.py @@ -0,0 +1,378 @@ +from __future__ import with_statement +from wx import TextCtrl, StaticText, ALIGN_CENTER, RadioButton, BoxSizer, HORIZONTAL, VERTICAL, StaticLine, \ + EXPAND, Button, ALL, TOP, BOTTOM, LEFT, RIGHT, Point +import wx +from util import Storage as S, callsback, traceguard +from traceback import print_exc +from gui.toolbox.imagefx import pil_to_wxb_nocache +from cgui import getScreenBitmap, GetTrayRect +from gui import skin +from gui.toolbox import Monitor +from PIL import Image, ImageDraw +from PIL.Image import BICUBIC, ANTIALIAS +from cStringIO import StringIO +from logging import getLogger; log = getLogger('bugreporter') + +from gui.imagedialog import ImageDialog +import wx.lib.sized_controls as sc + +SCREENSHOT_TIMER_SECS = 4 +num_screenshots = 0 + + +class BugReportPanel(sc.SizedPanel): + def __init__(self, parent, callback = None): + sc.SizedPanel.__init__(self, parent) + self.screenshot = None + self.construct() + # self.layout() + self.callback = callback + + def construct(self): + def Text(*a, **k): + txt = StaticText(self, -1, *a, **k) + txt.Wrap(520) + return txt + + def BoldText(*a, **k): + t = Text(*a, **k); t.SetBold() + return t + + self.header = BoldText(_('Use this tool to submit a diagnostic log right after you experience a bug'), + style = ALIGN_CENTER) + + self.subheader = Text(_("This diagnostic log file does not contain personal data such as the content of sent/received IMs, the content of emails, and the content of social network newsfeeds except where it directly pertains to an error."), + style = ALIGN_CENTER) + + self.subheader.SetSizerProps(expand=True) + + self.line = StaticLine(self) + + self.input_desc = BoldText(_('Please describe the bug in as much detail as possible. Include information such as what you were doing when the bug occurred and exactly what goes wrong.')) + + self.input_desc.SetSizerProps(expand=True) + + self.input = TextCtrl(self, -1, size = (400, 200), style = wx.TE_MULTILINE) + self.input.SetSizerProps(expand=True, proportion=1) + + radioPanel = self.radioPanel = sc.SizedPanel(self, -1) + radioPanel.SetSizerType("horizontal") + + self.reproduce_text = wx.StaticText(radioPanel, -1, _('Can you consistently reproduce this bug?')) + + self.radios = S(yes = RadioButton(radioPanel, -1, _('&Yes'), style = wx.RB_GROUP), + no = RadioButton(radioPanel, -1, _('&No')), + unknown = RadioButton(radioPanel, -1, _("&Don't Know"))) + self.radios.unknown.SetValue(True) + + self.screenshot_text = BoldText(_('If this is a visual bug, please attach a screenshot to this report.')) + self.screenshot_link = wx.HyperlinkCtrl(self, -1, _('Take Screenshot'), '#') + self.screenshot_link.Bind(wx.EVT_HYPERLINK, self.OnScreenshot) + + self.screenshot_timer = ScreenshotTimer(SCREENSHOT_TIMER_SECS, + lambda t: self.OnScreenshotLinkTimer(_('Taking Screenshot in {secs}').format(secs=t)), + self.OnScreenshotTimer) + + self.Bind(wx.EVT_SIZE, self.OnSize) + + + def OnSize(self, event): + self.subheader.SetLabel(_("This diagnostic log file does not contain personal data such as the content of sent/received IMs," + " the content of emails, and the content of social network newsfeeds except where it directly pertains to an error.")) + self.subheader.Wrap(event.Size.width) + self.input_desc.SetLabel(_('Please describe the bug in as much detail as possible. Include information such as what you were doing when the bug occurred and exactly what goes wrong.')) + self.input_desc.Wrap(event.Size.width) + + event.Skip() + + def OnScreenshotLinkTimer(self, l): + if wx.IsDestroyed(self): + self.screenshot_timer.Stop() + return + + self.SetScreenshotLabel(l) + + def SetScreenshotLabel(self, l): + self.screenshot_link.SetLabel(l) + self.Layout() + self.screenshot_link.Refresh() + + def OnScreenshot(self, e): + if self.screenshot_timer.IsRunning() or self.screenshot is not None: + self.screenshot = None + self.screenshot_timer.Stop() + self.screenshot_link.Label = _('Take Screenshot') + return + else: + self.screenshot_timer.Start() + + def OnScreenshotTimer(self): + oldpos = self.Parent.Position + + try: + top = self.Top + top.Move((-top.Size.width - 50, -top.Size.height - 50)) + + wx.BeginBusyCursor() + wx.WakeUpIdle() + wx.MilliSleep(500) + + global num_screenshots + num_screenshots +=1 + log.info('taking screenshot %d', num_screenshots) + + screen_full, screen = app_windows_image() + diag = ScreenshotDialog(self, screen_full, screen, pos = oldpos + Point(40, 40)) + except: + print_exc() + screen = None + finally: + wx.EndBusyCursor() + self.Top.Move(oldpos) + + if screen is None: + return + + try: + if diag.ShowModal() == wx.ID_OK: + self.screenshot = diag.SelectedScreenshot + self.screenshot_link.Label = _("Remove Screenshot") + else: + self.screenshot_link.Label = _('Take Screenshot') + finally: + diag.Destroy() + + self.Layout() + self.Refresh() + + @property + def reproduce(self): + for name, radio in self.radios.iteritems(): + if radio.Value: return name + + def info(self): + shot = self.screenshot + if shot is not None: + f = StringIO() + shot.save(f, 'PNG') + shot = f.getvalue() + + return S(description = self.input.Value, + reproducible = self.reproduce, + screenshot = shot ) + + def layout(self): + reproduce_sizer = HSizer() + reproduce_sizer.AddMany([(self.reproduce_text, 0), + (self.radios.yes, 1, LEFT, 20), + (self.radios.no, 1, LEFT, 20), + (self.radios.unknown, 1, LEFT, 20)]) + + visual_sizer = HSizer() + visual_sizer.AddMany([(self.screenshot_text, 0, RIGHT, 15), + (self.screenshot_link, 0)]) + + button_sizer = HSizer() + button_sizer.AddStretchSpacer(1) + button_sizer.AddMany([(self.submit, 0, EXPAND | RIGHT, 10), + (self.cancel, 0)]) + + self.Sizer = v = VSizer() + v.AddMany([ + (self.header, 0, EXPAND | BOTTOM, 6), + (self.subheader, 0, EXPAND | BOTTOM, 6), + (self.line, 0, EXPAND | BOTTOM, 6), + (self.input_desc, 0, EXPAND | BOTTOM, 6), + (self.input, 1, EXPAND | BOTTOM, 6), + (reproduce_sizer, 0, EXPAND | BOTTOM, 6), + (visual_sizer, 0, EXPAND | BOTTOM, 10), + (button_sizer, 0, EXPAND)]) + +@callsback +def show_dialog(callback = None): + diag = BugReportDialog(None, callback = callback) + diag.CenterOnScreen() + diag.Show() + +class BugReportDialog(sc.SizedDialog): + def __init__(self, parent, callback = None): + sc.SizedDialog.__init__(self, parent, -1, _('Submit Bug Report - Digsby'), + size=(590, 600), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + self.SetFrameIcon(skin.get('appdefaults.taskbaricon')) + + self.panel = BugReportPanel(self.GetContentsPane(), callback) + self.panel.SetSizerProps(expand=True, proportion=1) + self.callback = callback + + self.SetButtonSizer(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL)) + + # rename OK Button + button = self.FindWindowById(wx.ID_OK, self) + if button: + button.SetLabel(_("Submit")) + + if callback is not None: + self.Bind(wx.EVT_BUTTON, self.OnButton) + + self.Fit() + self.MinSize = self.Size + + def OnButton(self, e): + e.Skip(False) + submit = e.Id == wx.ID_OK and self.callback is not None + + if submit and not self.panel.input.Value: + bug_desc_header = _("Please enter a description of the bug.") + bug_desc_info = _("Include as much information as possible about " + "what went wrong and how to reproduce the issue.") + + wx.MessageBox(u'%s\n\n%s' % (bug_desc_header, bug_desc_info), + _('Send Bug Report')) + return + + try: + if submit: + self.callback(self.panel.info()) + finally: + self.Destroy() + +def resize_screenshot(img): + w, h = img.size + squaresize = 650 + + if w > h: + new_height = int(h/float(w) * squaresize) + img = img.resize((squaresize, new_height), BICUBIC if squaresize > w else ANTIALIAS) + else: + new_width = int(w/float(h) * squaresize) + img = img.resize((new_width, squaresize), BICUBIC if squaresize > h else ANTIALIAS) + + return img + + +class ScreenshotDialog(sc.SizedDialog): + def __init__(self, parent, screenshot_full, screenshot, pos): + sc.SizedDialog.__init__(self, parent, -1, _('Submit Bug Report - Screenshot - Digsby')) + self.SetFrameIcon(skin.get('appdefaults.taskbaricon')) + + # store both screenshots -- the one with blanked out areas, and the + # full one + self.big_screenshot_digsby = screenshot + self.screenshot_digsby = pil_to_wxb_nocache(resize_screenshot(screenshot)) + + self.big_screenshot_full = screenshot_full + self.screenshot_full = pil_to_wxb_nocache(resize_screenshot(screenshot_full)) + + self.screenshot = self.screenshot_digsby + self.hide_non_digsby = True + + p = self.panel = self.GetContentsPane() + + screenshot_panel = sc.SizedPanel(self.panel, -1) + screenshot_panel.SetMinSize((self.screenshot_full.Width, self.screenshot_full.Height + 20)) + screenshot_panel.Bind(wx.EVT_PAINT, self.paint) + + hpanel = sc.SizedPanel(self.panel, -1) + hpanel.SetSizerType("horizontal") + checkbox = wx.CheckBox(hpanel, -1, _('Blank out non Digsby windows')) + checkbox.SetValue(True) + checkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckFullScreenShot) + checkbox.SetSizerProps(expand=True) + stretchpanel = sc.SizedPanel(hpanel, -1) + stretchpanel.SetSizerProps(expand=True, proportion=1) + + StaticText(hpanel, -1, _('Is it OK to send this screenshot to digsby.com?')) + + self.SetButtonSizer(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL)) + + self.Fit() + + self.SetPosition(pos) + + @property + def SelectedScreenshot(self): + if self.hide_non_digsby: + return self.big_screenshot_digsby + else: + return self.big_screenshot_full + + def OnCheckFullScreenShot(self, e): + self.hide_non_digsby = e.IsChecked() + self.Refresh() + + def paint(self, e): + dc = wx.PaintDC(e.EventObject) + screenshot = self.screenshot_digsby if self.hide_non_digsby else self.screenshot_full + dc.DrawBitmap(screenshot, 10, 10, True) + +class ScreenshotTimer(wx.Timer): + def __init__(self, secs, on_tick, on_done): + wx.Timer.__init__(self) + self.secs = secs + self.tick = on_tick + self.done = on_done + + def Start(self): + self.count = self.secs + wx.Timer.Start(self, 1000, False) + self.tick(self.count) + + def Notify(self): + self.count -= 1 + self.tick(self.count) + if self.count == 0: + with traceguard: + self.done() + self.Stop() + + +HSizer = lambda: BoxSizer(HORIZONTAL) +VSizer = lambda: BoxSizer(VERTICAL) + +import gui.wxextensions + +def app_windows_image(): + ''' + Returns a bitmap showing all the top level windows in this application. + + Other areas of the screen are blanked out. + ''' + + # get the union of all screen rectangles (this will be a big rectangle + # covering the area of all monitors) + rect = reduce(wx.Rect.Union, (m.Geometry for m in Monitor.All())) + if "wxMSW" in wx.PlatformInfo: + screen = getScreenBitmap(rect).PIL + else: + screen = wx.ScreenDC().GetAsBitmap().PIL + + mask = Image.new('RGBA', rect.Size, (255, 255, 255, 255)) + drawrect = lambda r: ImageDraw.Draw(mask).rectangle([r.x, r.y, r.Right, r.Bottom ], fill = (0, 0, 0, 0)) + + # draw rectangles into a mask for each top level window (and the system tray) + for r in [GetTrayRect()] + [w.Rect for w in wx.GetTopLevelWindows() if w.IsShown()]: + r.Offset((-rect.Position[0], -rect.Position[1])) # some monitors may be in negative coordinate space... + drawrect(r) + + # paste the mask into the screenshot--whiteing out areas not in our windows + screen_full = screen.copy() + screen.paste(mask, (0, 0), mask) + + return screen_full, screen + +if __name__ == '__main__': + import gettext; gettext.install('Digsby') + + from tests.testapp import testapp + a = testapp('../../..') + + f = wx.Frame(None, pos = (50, 50)) + f.Show() + f = wx.Frame(None, pos = (10050, 80)) + f.Show() + f = wx.Frame(None, pos = (400, 300)) + f.Show() + + + print show_dialog() + diff --git a/digsby/src/gui/bugreporter/bugreporterguiold.py b/digsby/src/gui/bugreporter/bugreporterguiold.py new file mode 100644 index 0000000..b139f6a --- /dev/null +++ b/digsby/src/gui/bugreporter/bugreporterguiold.py @@ -0,0 +1,374 @@ +from __future__ import with_statement +from wx import TextCtrl, StaticText, ALIGN_CENTER, RadioButton, BoxSizer, HORIZONTAL, VERTICAL, StaticLine, \ + EXPAND, Button, ALL, BOTTOM, LEFT, RIGHT, Point +import wx +from util import Storage as S, callsback, traceguard +from traceback import print_exc +from gui.toolbox.imagefx import pil_to_wxb_nocache +from cgui import getScreenBitmap, GetTrayRect +from gui import skin +from gui.toolbox import Monitor +from PIL import Image, ImageDraw +from PIL.Image import BICUBIC, ANTIALIAS +from cStringIO import StringIO +from logging import getLogger; log = getLogger('bugreporter') + +SCREENSHOT_TIMER_SECS = 4 +num_screenshots = 0 + +TAKE_SCREENSHOT_LABEL = _('Take Screenshot') + +class BugReportPanel(wx.Panel): + def __init__(self, parent, callback = None): + wx.Panel.__init__(self, parent) + self.screenshot = None + self.construct() + self.layout() + self.callback = callback + + if callback is not None: + self.Bind(wx.EVT_BUTTON, self.OnButton) + + self.Top.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnClose(self, e): + self.Top.Destroy() + + def OnButton(self, e): + e.Skip() + b = e.EventObject + submit = b is self.submit and self.callback is not None + + if submit and not self.input.Value: + e.Skip(False) + bug_desc_header = _("Please enter a description of the bug.") + bug_desc_info = _("Include as much information as possible about " + "what went wrong and how to reproduce the issue.") + + wx.MessageBox(u'%s\n\n%s' % (bug_desc_header, bug_desc_info), + _('Send Bug Report')) + return + + def after(): + try: + if submit: + self.callback(self.info()) + finally: + self.Top.Destroy() + + wx.CallAfter(after) + + def construct(self): + def Text(*a, **k): + txt = StaticText(self, -1, *a, **k) + txt.Wrap(520) + return txt + + def BoldText(*a, **k): + t = Text(*a, **k); t.SetBold() + return t + + self.header = BoldText(_('Use this tool to submit a diagnostic log right after you experience a bug'), + style = ALIGN_CENTER) + + self.subheader = Text(_("This diagnostic log file does not contain personal data such as the content of sent/received IMs, the content of emails, and the content of social network newsfeeds except where it directly pertains to an error."), + style = ALIGN_CENTER) + + self.line = StaticLine(self) + + self.input_desc = BoldText(_('Please describe the bug in as much detail as possible. Include information such as what you were doing when the bug occurred and exactly what goes wrong.')) + + self.input = TextCtrl(self, -1, size = (400, 200), style = wx.TE_MULTILINE) + + self.reproduce_text = Text(_('Can you consistently reproduce this bug?')) + + self.radios = S(yes = RadioButton(self, -1, _('&Yes'), style = wx.RB_GROUP), + no = RadioButton(self, -1, _('&No')), + unknown = RadioButton(self, -1, _("&Don't Know"))) + self.radios.unknown.SetValue(True) + + self.screenshot_text = BoldText(_('If this is a visual bug, please attach a screenshot to this report.')) + self.screenshot_link = wx.HyperlinkCtrl(self, -1, TAKE_SCREENSHOT_LABEL, '#') + self.screenshot_link.Bind(wx.EVT_HYPERLINK, self.OnScreenshot) + + self.screenshot_timer = ScreenshotTimer(SCREENSHOT_TIMER_SECS, + lambda t: self.OnScreenshotLinkTimer(_('Taking Screenshot in {secs}').format(secs=t)), + self.OnScreenshotTimer) + + self.submit = Button(self, wx.ID_OK, _('&Submit')) + self.submit.SetDefault() + self.cancel = Button(self, wx.ID_CANCEL, _('&Cancel')) + + def OnScreenshotLinkTimer(self, l): + if wx.IsDestroyed(self): + self.screenshot_timer.Stop() + return + + self.SetScreenshotLabel(l) + + def SetScreenshotLabel(self, l): + self.screenshot_link.SetLabel(l) + self.Layout() + self.screenshot_link.Refresh() + + def OnScreenshot(self, e): + if self.screenshot_timer.IsRunning() or self.screenshot is not None: + self.screenshot = None + self.screenshot_timer.Stop() + self.screenshot_link.Label = TAKE_SCREENSHOT_LABEL + return + else: + self.screenshot_timer.Start() + + def OnScreenshotTimer(self): + oldpos = self.Parent.Position + + try: + top = self.Top + top.Move((-top.Size.width - 50, -top.Size.height - 50)) + + wx.BeginBusyCursor() + wx.WakeUpIdle() + wx.MilliSleep(500) + + global num_screenshots + num_screenshots +=1 + log.info('taking screenshot %d', num_screenshots) + + screen_full, screen = app_windows_image() + diag = ScreenshotDialog(self, screen_full, screen, pos = oldpos + Point(40, 40)) + except: + print_exc() + screen = None + finally: + wx.EndBusyCursor() + self.Top.Move(oldpos) + + if screen is None: + return + + try: + self.screenshot_link.Label = TAKE_SCREENSHOT_LABEL + if diag.ShowModal() == wx.ID_OK: + self.screenshot = diag.SelectedScreenshot + self.screenshot_link.Label = _("Remove Screenshot") + else: + self.screenshot_link.Label = TAKE_SCREENSHOT_LABEL + finally: + diag.Destroy() + + self.Layout() + self.Refresh() + + @property + def reproduce(self): + for name, radio in self.radios.iteritems(): + if radio.Value: return name + + def info(self): + shot = self.screenshot + if shot is not None: + f = StringIO() + shot.save(f, 'PNG') + shot = f.getvalue() + + return S(description = self.input.Value, + reproducible = self.reproduce, + screenshot = shot ) + + def layout(self): + reproduce_sizer = HSizer() + reproduce_sizer.AddMany([(self.reproduce_text, 0), + (self.radios.yes, 1, LEFT, 20), + (self.radios.no, 1, LEFT, 20), + (self.radios.unknown, 1, LEFT, 20)]) + + visual_sizer = HSizer() + visual_sizer.AddMany([(self.screenshot_text, 0, RIGHT, 15), + (self.screenshot_link, 0)]) + + button_sizer = HSizer() + button_sizer.AddStretchSpacer(1) + button_sizer.AddMany([(self.submit, 0, EXPAND | RIGHT, 10), + (self.cancel, 0)]) + + self.Sizer = v = VSizer() + v.AddMany([ + (self.header, 0, EXPAND | BOTTOM, 6), + (self.subheader, 0, EXPAND | BOTTOM, 6), + (self.line, 0, EXPAND | BOTTOM, 6), + (self.input_desc, 0, EXPAND | BOTTOM, 6), + (self.input, 1, EXPAND | BOTTOM, 6), + (reproduce_sizer, 0, EXPAND | BOTTOM, 6), + (visual_sizer, 0, EXPAND | BOTTOM, 10), + (button_sizer, 0, EXPAND)]) + +@callsback +def show_dialog(callback = None): + if not BugReportDialog.RaiseExisting(): + diag = BugReportDialog(None, callback = callback) + diag.CenterOnScreen() + diag.Show() + +class BugReportDialog(wx.Dialog): + def __init__(self, parent, callback = None): + wx.Dialog.__init__(self, parent, -1, _('Submit Bug Report - Digsby')) + self.SetFrameIcon(skin.get('appdefaults.taskbaricon')) + + self.panel = BugReportPanel(self, callback) + self.info = self.panel.info + + s = self.Sizer = VSizer() + s.Add(self.panel, 1, EXPAND | ALL, 8) + self.SetMaxSize((500, -1)) + self.Fit() + +def resize_screenshot(img): + w, h = img.size + squaresize = 650 + + if w > h: + new_height = int(h/float(w) * squaresize) + img = img.resize((squaresize, new_height), BICUBIC if squaresize > w else ANTIALIAS) + else: + new_width = int(w/float(h) * squaresize) + img = img.resize((new_width, squaresize), BICUBIC if squaresize > h else ANTIALIAS) + + return img + +class ScreenshotDialog(wx.Dialog): + def __init__(self, parent, screenshot_full, screenshot, pos): + wx.Dialog.__init__(self, parent, -1, _('Submit Bug Report - Screenshot - Digsby')) + self.SetFrameIcon(skin.get('appdefaults.taskbaricon')) + + # store both screenshots -- the one with blanked out areas, and the + # full one + self.big_screenshot_digsby = screenshot + self.screenshot_digsby = pil_to_wxb_nocache(resize_screenshot(screenshot)) + + self.big_screenshot_full = screenshot_full + self.screenshot_full = pil_to_wxb_nocache(resize_screenshot(screenshot_full)) + + self.screenshot = self.screenshot_digsby + self.hide_non_digsby = True + + p = self.panel = wx.Panel(self) + s = self.panel.Sizer = VSizer() + + s.Add((self.screenshot_full.Width, self.screenshot_full.Height + 20)) + p.Bind(wx.EVT_PAINT, self.paint) + + hz = wx.BoxSizer(wx.HORIZONTAL) + checkbox = wx.CheckBox(p, -1, _('Blank out non Digsby windows')) + checkbox.SetValue(True) + checkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckFullScreenShot) + hz.Add(checkbox, 0, EXPAND) + hz.AddStretchSpacer(1) + hz.Add(StaticText(p, -1, _('Is it OK to send this screenshot to digsby.com?')), 0, EXPAND) + s.Add(hz, 0, wx.BOTTOM | EXPAND, 7) + + send = Button(p, wx.ID_OK, _('OK')) + send.SetDefault() + + cancel = Button(p, wx.ID_CANCEL, _('Cancel')) + + h = HSizer() + h.AddStretchSpacer(1) + h.AddMany([(send, 0, EXPAND | RIGHT, 10), + (cancel, 0)]) + s.Add(h, 0, EXPAND) + + s = self.Sizer = VSizer() + s.Add(self.panel, 1, EXPAND | ALL, 8) + + self.Fit() + self.SetPosition(pos) + + @property + def SelectedScreenshot(self): + if self.hide_non_digsby: + return self.big_screenshot_digsby + else: + return self.big_screenshot_full + + def OnCheckFullScreenShot(self, e): + self.hide_non_digsby = e.IsChecked() + self.Refresh() + + def paint(self, e): + dc = wx.PaintDC(e.EventObject) + screenshot = self.screenshot_digsby if self.hide_non_digsby else self.screenshot_full + dc.DrawBitmap(screenshot, 10, 10, True) + +class ScreenshotTimer(wx.Timer): + def __init__(self, secs, on_tick, on_done): + wx.Timer.__init__(self) + self.secs = secs + self.tick = on_tick + self.done = on_done + + def Start(self): + self.count = self.secs + wx.Timer.Start(self, 1000, False) + self.tick(self.count) + + def Notify(self): + self.count -= 1 + self.tick(self.count) + if self.count == 0: + self.Stop() + with traceguard: + self.done() + + + +HSizer = lambda: BoxSizer(HORIZONTAL) +VSizer = lambda: BoxSizer(VERTICAL) + +import gui.wxextensions + +def app_windows_image(): + ''' + Returns a bitmap showing all the top level windows in this application. + + Other areas of the screen are blanked out. + ''' + + # get the union of all screen rectangles (this will be a big rectangle + # covering the area of all monitors) + rect = reduce(wx.Rect.Union, (m.Geometry for m in Monitor.All())) + if "wxMSW" in wx.PlatformInfo: + screen = getScreenBitmap(rect).PIL + else: + screen = wx.ScreenDC().GetAsBitmap().PIL + + mask = Image.new('RGBA', rect.Size, (255, 255, 255, 255)) + drawrect = lambda r: ImageDraw.Draw(mask).rectangle([r.x, r.y, r.Right, r.Bottom ], fill = (0, 0, 0, 0)) + + # draw rectangles into a mask for each top level window (and the system tray) + for r in [GetTrayRect()] + [w.Rect for w in wx.GetTopLevelWindows() if w.IsShown()]: + r.Offset(-rect.Position) # some monitors may be in negative coordinate space... + drawrect(r) + + # paste the mask into the screenshot--whiteing out areas not in our windows + screen_full = screen.copy() + screen.paste(mask, (0, 0), mask) + + return screen_full, screen + +if __name__ == '__main__': + import gettext; gettext.install('Digsby') + + from tests.testapp import testapp + a = testapp('../../..') + + f = wx.Frame(None, pos = (50, 50)) + f.Show() + f = wx.Frame(None, pos = (10050, 80)) + f.Show() + f = wx.Frame(None, pos = (400, 300)) + f.Show() + + + print show_dialog() + diff --git a/digsby/src/gui/capabilitiesbar.py b/digsby/src/gui/capabilitiesbar.py new file mode 100644 index 0000000..a526c31 --- /dev/null +++ b/digsby/src/gui/capabilitiesbar.py @@ -0,0 +1,396 @@ +from __future__ import with_statement + +import wx +from logging import getLogger; log = getLogger('capsbar') + +from common import caps, pref, setpref, profile +from config import nativeIMWindow +from util.primitives.funcs import Delegate + +from gui import skin +from gui.textutil import default_font +from gui.filetransfer import FileTransferDialog +from gui.uberwidgets.UberBar import UberBar +from gui.uberwidgets.simplemenu import SimpleMenu,SimpleMenuItem +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.umenu import UMenu +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.cleartext import ClearText +from cgui import SimplePanel + +action_icons_key = 'conversation_window.actions_bar_icons' + +# identifier GUI label # tooltip +buttons = [('info', _('Info'), _('View buddy information')), + ('im', _('IM'), _('Instant message this buddy')), + ('video', _('Video'), _('Start an audio/video chat')), + ('files', _('Files'), _('Send files to this buddy')), + #('pictures', _('Pictures')), + ('email', _('Email'), _('Send email')), + ('sms', _('SMS'), _('Send SMS messages')), + ] + +class CapabilitiesBar(SimplePanel): + ''' + A specialized UberBar used used in the infobox and the IM window + has a subbar with to/from combos. + ''' + + def __init__(self, parent, buddy_callback, + showCapabilities = True, + infoboxmode = False): + SimplePanel.__init__(self, parent) + + self.buddy_callback = buddy_callback + + self.Bind(wx.EVT_PAINT, lambda e: wx.PaintDC(self)) + + self.infoboxmode = infoboxmode + self._lastcaps = None + + self.UpdateSkin() + self.Sizer = wx.BoxSizer(wx.VERTICAL) + + # create delegates for callbacks + for action in ('OnSendFiles', 'OnSendFolder', 'OnViewPastChats', 'OnAlert', 'OnBlock', 'OnAddContact'): + setattr(self, action, Delegate()) + + # Create the uberbar for the capabilities. + self.cbar = bar = UberBar(self, skinkey = self.capabilitiesskin, overflowmode = True) + # FIXME: we should simply not allow the capabilities bar to be created for native mode + if not showCapabilities or nativeIMWindow: + self.cbar.Hide() + + if not infoboxmode: + self.cbar.Bind(wx.EVT_CONTEXT_MENU, lambda e: self.ActionsBarMenu.PopupMenu(event=e)) + + # Create all the buttons for the capabilities bar. + + iconsize = skin.get('ActionsBar.IconSize') + icons = skin.get('ActionsBar.Icons') + + for attr, title, tooltip in buttons: + icon = getattr(icons, attr).Resized(iconsize) + if attr == 'files': + # "files" has a dropdown menu + button = UberButton(bar, -1, title, icon = icon, + type = 'menu', menu = self.FileMenu) + + # Change the label and action of the files button when it's overflowed into + # the menu on the right. + button.overflow_label = _('Send File') + button.overflow_callback = self.OnSendFiles + else: + # hack until I fix this :[ -kevin + if attr == 'video' and infoboxmode: continue + + button = UberButton(bar, -1, title, icon = icon) + button.overflow_label = title + + button.SetToolTipString(tooltip) + + setattr(self, 'b' + attr, button) + bar.Add(button, calcSize = False) + + bar.OnUBSize() + + #TODO Add button logics + +# if not self.infoboxmode: +# self.badd = UberButton(bar,-1,'',icon = getattr(icons, 'add').Resized(iconsize)) +# bar.AddStatic(self.badd) +# self.badd.Bind(wx.EVT_BUTTON,lambda e: self.OnAddContact()) + + # Create multichat icon for the roomlist + if pref('messaging.groupchat.enabled', False) and not self.infoboxmode: + self.bmultichat = UberButton(bar, -1, icon = skin.get('actionsbar.icons.roomlist').Resized(16), type='toggle') + self.bmultichat.SetToolTipString(_('Group Chat')) + bar.AddStatic(self.bmultichat) + + self.ihistory = SimpleMenuItem(_('View Past Chats'), method = self.OnViewPastChats) + def show_prefs_notifications(a): + import gui.pref.prefsdialog as prefsdialog + prefsdialog.show('notifications') + self.ialert = SimpleMenuItem(_("Alert Me When..."), method = show_prefs_notifications) + self.iblock = SimpleMenuItem(_("Block"), method = self.OnBlock) + + if not self.infoboxmode: + self.iadd = SimpleMenuItem(_("Add Contact"), method = self.OnAddContact) + bar.AddMenuItem(self.iadd) + bar.AddMenuItem(self.ihistory) + bar.AddMenuItem(self.ialert) + + if not self.infoboxmode: + bar.AddMenuItem(SimpleMenuItem(id = -1)) + bar.AddMenuItem(self.iblock) + + self.Sizer.Add(bar, 0, wx.EXPAND) + + # create the To/From bar + self.tfbar = tfbar = UberBar(self, skinkey = self.tofromskin) + self.tfbar.Hide() + + tofrom_font = skin.get('tofrombar.font', default_font) + tofrom_color = skin.get('tofrombar.fontcolor', wx.BLACK) + + talign = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT + + self.to_label = ClearText(tfbar, _('To:'), alignment = talign) + self.to_label.Font = tofrom_font + self.to_label.FontColor = tofrom_color + self.from_label = ClearText(tfbar, _('From:'), alignment = talign) + self.from_label.Font = tofrom_font + self.from_label.FontColor = tofrom_color + + self.cto = UberCombo(tfbar, skinkey = self.tofromcomboskin, typeable = False, size = (100, 20), minmenuwidth = 200) + self.cfrom = UberCombo(tfbar, skinkey = self.tofromcomboskin, typeable = False, size = (100, 20), minmenuwidth = 200) + + tfbar.Add(self.to_label, calcSize = False) + tfbar.Add(self.cto, True, calcSize = False) + tfbar.Add(self.from_label, calcSize = False) + tfbar.Add(self.cfrom, True) + + self.Sizer.Add(tfbar, 0, wx.EXPAND) + + profile.prefs.link(action_icons_key, self.UpdateIcons) + + self.cbar.overflowmenu.BeforeDisplay += self.ApplyCaps + + ToCombo = property(lambda self: self.cto) + FromCombo = property(lambda self: self.cfrom) + + + def UpdateSkin(self): + 'Tells the subbars what skins they should load.' + + sget = skin.get + self.capabilitiesskin = sget('actionsbar.toolbarskin', None) + self.tofromskin = sget('tofrombar.toolbarskin', None) + + self.menuskin = sget('%s.menuskin'%self.capabilitiesskin,None) + + self.tofromcomboskin = sget("tofrombar.comboboxskin",None) + + self.iconsize = sget('ActionsBar.IconSize') + self.icons = sget('ActionsBar.Icons') + + if hasattr(self, 'to_label'): + tofrom_font = sget('tofrombar.font', lambda: default_font()) + tofrom_color = sget('tofrombar.fontcolor', lambda: wx.BLACK) + + self.to_label.Font = self.from_label.Font = tofrom_font + self.to_label.FontColor = self.from_label.FontColor = tofrom_color + + if hasattr(self, 'cbar'): self.cbar.SetSkinKey(self.capabilitiesskin) + if hasattr(self, 'tfbar'): self.tfbar.SetSkinKey(self.tofromskin) + if hasattr(self, 'cto'): self.cto.SetSkinKey(self.tofromcomboskin) + if hasattr(self, 'cfrom'): self.cfrom.SetSkinKey(self.tofromcomboskin) + + self.UpdateIcons() + + def UpdateIcons(self, *a): + 'Updates icon sizes for buttons.' + + icons_pref = pref(action_icons_key) + textonly = icons_pref == 'text' + + icons = self.icons + size = self.iconsize + #TODO: Add Button logics +# allbuttons = list(buttons) +# allbuttons.append(('add','')) + + with self.Frozen(): + for attr, title, tooltip in buttons: + icon = None if textonly else getattr(icons, attr).Resized(size) + button = getattr(self, 'b' + attr, None) + if button is not None: + button.SetIcon(icon) + button.SetAlignment(wx.VERTICAL if icons_pref == 'above' else wx.HORIZONTAL) + button.SetLabel('' if icons_pref == 'icons' else title) + + self.Parent.Layout() + self.Refresh() + + def ShowToFrom(self, show = True): + 'Show or hide the to/from bar.' + + return self.tfbar.Show(show) + + @property + def RoomListButtonShown(self): + bmultichat = getattr(self, 'bmultichat', None) + return bmultichat is not None and bmultichat.IsShown() + + @property + def ToFromShown(self): + 'Returns True if the To/From bar is shown.' + + return self.tfbar.IsShown() + + def ShowCapabilities(self, show = True): + 'Show/Hide the capabilities bar.' + + return self.cbar.Show(show) + #self.Layout() + + def CapabilitiesIsShown(self): + 'Returns True if the capabilities bar is shown.' + + return self.cbar.IsShown() + + def GetButton(self, button): + 'Returns one of the butons by name.' + + return getattr(self, 'b' + button, None) + + def ApplyCaps(self, contact = None, convo = None): + 'Those shows and hides options depending on the capabilities the Contact reports.' + + if contact is None and convo is None: + convo = self.buddy_callback() + from common.Conversation import Conversation + if not isinstance(convo, Conversation): + contact = convo + convo = None + + c = None + if convo is not None: + if convo.ischat: + c = set([caps.IM]) + elif contact is None: + contact = convo.buddy + + if c is None: + c = set(contact.caps) + + if contact is not None: + c.add(('blocked', contact.blocked)) + + # early exit if capabilities are the same. + if c == self._lastcaps: return + + buttons_caps = [ + ('binfo', contact is not None and not any((contact.sms and contact.mobile, self.infoboxmode))), + ('bim', caps.IM in c), + ('bfiles', caps.FILES in c), + ('bemail', caps.EMAIL in c), + ('bsms', caps.SMS in c), + ('bvideo', caps.VIDEO in c) + ] + + for name, val in buttons_caps: + ctrl = getattr(self, name, None) + if ctrl is not None: + ctrl.Show(ctrl, val, False) + + cbar = self.cbar + menu = cbar.overflowmenu + count = menu.spine.items.count + iblock = self.iblock + + if caps.BLOCKABLE in c and not count(iblock): + cbar.AddMenuItem(SimpleMenuItem(id = -1)) + cbar.AddMenuItem(iblock) + elif not caps.BLOCKABLE in c and count(iblock): + cbar.overflowmenu.RemoveItem(iblock) + + if contact is not None: + if contact.blocked: + content = _('Unblock {name}') + else: + content = _('Block {name}') + + iblock.content = [content.format(name=contact.name)] + + self.set_groupchat_visibility(contact, convo) + + i = len(menu.spine.items) - 1 + if menu.GetItem(i).id == -1: + menu.RemoveItem(i) + + # don't show the dropdown on the right for widgets. + self.cbar.overflowbutton.Show(not getattr(contact, 'iswidget', False)) + + self._lastcaps = c + + cbar.GenWidthRestriction(True) + + self.update_add_contact_shown(convo) + + self.Parent.Layout() + + def update_add_contact_shown(self, convo): + if not hasattr(self, 'iadd'): + return + + ischat = convo is not None and convo.ischat + overflow = self.cbar.overflowmenu + + if ischat: + if overflow.GetIndex(self.iadd) != -1: + overflow.RemoveItem(self.iadd) + else: + if overflow.GetIndex(self.iadd) == -1: + overflow.InsertItem(overflow.GetIndex(self.ihistory), self.iadd) + + def set_groupchat_visibility(self, contact, convo): + if not hasattr(self, 'bmultichat'): + return + + proto = getattr(contact, 'protocol', getattr(convo, 'protocol', None)) + groupchat = False + if proto is not None: + groupchat = getattr(proto, 'supports_group_chat', False) and getattr(contact, 'supports_group_chat', False) + self.bmultichat.Show(groupchat) + + @property + def FileMenu(self): + + self._filemenu = SimpleMenu(self,self.menuskin) + self.send_file_item = SimpleMenuItem(_('Send File'),lambda *a: self.OnSendFiles()) +# if b and b.online: + self._filemenu.AppendItem(self.send_file_item) + self._filemenu.AppendItem(SimpleMenuItem(_('Transfer History'),lambda *a: FileTransferDialog.Display())) + + return self._filemenu + +# try: +# return self._filemenu +# except AttributeError: +# self._filemenu = self.build_file_menu() +# return self._filemenu + + @property + def ActionsBarMenu(self): + try: + return self._actionsbarmenu + except AttributeError: + self._actionsbarmenu = self.build_actionsbar_menu() + return self._actionsbarmenu + + + def build_actionsbar_menu(self): + m = UMenu(self, onshow = self.update_actionsbar_menu) + + c = self._actionsbar_checks = {} + for name, label in [('icons', _('Icons Only')), + ('text', _('Text Only')), + ('next', _('Icons Next to Text')), + ('above', _('Icons Above Text'))]: + + def cb(name=name): + with self.Frozen(): + setpref(action_icons_key, name) + + c[name] = m.AddCheckItem(label, callback = cb) + + m.AddSep() + m.AddItem(_('Hide Actions Bar'), callback = lambda: setpref('messaging.show_actions_bar', False)) + + return m + + def update_actionsbar_menu(self, menu): + p = pref(action_icons_key) + for name, item in self._actionsbar_checks.iteritems(): + item.Check(p == name) diff --git a/digsby/src/gui/chatgui.py b/digsby/src/gui/chatgui.py new file mode 100644 index 0000000..4787a5e --- /dev/null +++ b/digsby/src/gui/chatgui.py @@ -0,0 +1,237 @@ +import wx +import wx.lib.sized_controls as sc + +from gui import skin +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.simplemenu import SimpleMenuItem +from gui.anylists import AnyList + +from common import profile, pref +from util.Events import EventMixin + +from logging import getLogger; log = getLogger('chatgui') + +class ConnectedAccountsCombo(UberCombo, EventMixin): + no_connections_label = _('No Connections') + events = set(('account_changed',)) + + def __init__(self, parent, skinkey='AppDefaults.PrefCombo'): + EventMixin.__init__(self) + UberCombo.__init__(self, parent, self.no_connections_label, False, + valuecallback=self._on_account_changed, skinkey=skinkey) + + profile.account_manager.connected_accounts.add_observer(self._on_connaccts_changed) + self._on_connaccts_changed() + + def _on_account_changed(self, *a): + self.event('account_changed', self.Value.id) + + def ChooseProtocol(self, proto): + for item in self: + if item.id.connection is proto: + break + else: + return + + self.Value = item + + def _get_accounts(self): + accts = profile.account_manager.connected_accounts + + # filter out Protocols without supports_group_chat + accts = [a for a in accts + if getattr(getattr(a, 'connection', None), 'supports_group_chat', False)] + + p = profile() + if pref('digsby.allow_add', False): + if p not in accts: + accts.insert(0, p) + else: + if p in accts: + accts.remove(p) + + return accts + + def _on_connaccts_changed(self, *a): + self.SetItems(self._make_proto_items()) + if not self.Value in self: + if len(self): self.SetSelection(0) + else: self.Value = ConnectedAccountsCombo.no_connections_label + + def _make_proto_items(self): + items = [] + for acct in self._get_accounts(): + icon = skin.get('serviceicons.%s' % acct.protocol, None) + if icon is not None: icon = icon.Resized(16) + items.append(SimpleMenuItem([icon, acct.name], id=acct)) + + return items + +from gui.toolbox import NonModalDialogMixin +from gui.pref.prefcontrols import PrivacyListRow +from gui.imwin import roomlist + +class InviteRow(PrivacyListRow): + def get_text(self): + return roomlist.contact_display_str(self.data) + +class InviteList(AnyList): + SelectionEnabled = False + def remove_row(self, data): + self.data.remove(data) + +class JoinChatDialog(sc.SizedDialog, NonModalDialogMixin): + + @property + def Account(self): + return self.account.Value.id + + def __init__(self, parent=None, protocol=None): + sc.SizedDialog.__init__(self, parent, title=_('Join Chat Room'), + style=wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.RESIZE_BORDER) + + p = self.GetContentsPane() + p.SetSizerType('form') + self.SetButtonSizer(self.CreateButtonSizer(wx.OK | wx.CANCEL)) + + ok = self.FindWindowById(wx.ID_OK, self) + ok.SetLabel(_('Join Chat')) + ok.SetDefault() + + R_CENTER = dict(halign='right', valign='center') + R_TOP = dict(halign='right', valign='top') + + Text = lambda label: wx.StaticText(p, wx.ID_ANY, label) + + def TextItem(label, align=R_CENTER): + t = Text(label) + t.SetSizerProps(**align) + return t + + TextItem(_('Account:')) + self.account = account = ConnectedAccountsCombo(p) + account.bind_event('account_changed', self._on_account_changed) + if protocol is not None: account.ChooseProtocol(protocol) + account.SetSizerProps(expand=True) + + TextItem(_('Invite:'), dict( + halign='right', valign='top', + border=(('top',), 6), + )) + + combo_panel = wx.Panel(p) + + from gui.imwin.roomlist import ContactCombo, RoomListModel + + self.model = RoomListModel([]) + self.active_account = None + self.combo = ContactCombo(combo_panel, + contacts = {}, + inviteCallback = self._on_invite_buddy, + accountCallback = lambda: self.Account, + model = self.model, + use_confirm_dialog=False) + + self.invited_buddies = self.model.pending_contacts + + border = wx.Panel(combo_panel) + border.SetBackgroundColour(wx.Color(153, 153, 153)) + self.list = InviteList(border, data=self.invited_buddies, + row_control=InviteRow, style=8, + draggable_items=False) + self.list.SetBackgroundColour(wx.WHITE) + self.list.SetMinSize((-1, 100)) + + border.Sizer = wx.BoxSizer(wx.HORIZONTAL) + border.Sizer.Add(self.list, 1, wx.EXPAND | wx.ALL, 1) + + vsizer = wx.BoxSizer(wx.VERTICAL) + vsizer.AddMany([(self.combo, 0, wx.EXPAND), + (1, 6), + (border, 1, wx.EXPAND)]) + combo_panel.SetSizer(vsizer) + combo_panel.SetSizerProps(expand=True) + + self.server_label = TextItem(_('Server:')) + self.server = wx.TextCtrl(p) + self.server.SetSizerProps(expand=True) + self.server.Bind(wx.EVT_TEXT, self._on_server_text) + + self.room_label = TextItem(_('Room Name:')) + room = self.room = wx.TextCtrl(p) + room.SetSizerProps(expand=True) + + self.SetMinSize((350, 250)) + self.FitInScreen() + + + can_specify_roomname = True + + def _on_server_text(self, e=None): + if e: e.Skip() + + # HACK: cannot specify a roomname on gtalk's default server. + self.can_specify_roomname = self.protocol_name != 'gtalk' or self.server.Value.strip() != u'groupchat.google.com' + + self.room.Enable(self.can_specify_roomname) + + def _on_invite_buddy(self, b): + if self.model.add_pending_contact(b): + return True + + def _on_account_changed(self, account): + print '_on_account_changed', account + print ' protocol', account.protocol + + self.protocol_name = account.protocol + self._on_server_text() + + connection = getattr(account, 'connection', None) + visible = getattr(connection, 'can_specify_chatserver', False) + + for ctrl in (self.server, self.server_label, self.room, self.room_label): + ctrl.Show(visible) + + self.server.Value = getattr(connection, 'default_chat_server', lambda: '')() + + self.combo.contacts = connection.buddies if connection is not None else {} + + # clear pending contacts + self.model.pending_contacts[:] = [] + self.model.fire_contacts_changed() + + self.FitInScreen() + +def join_chat(protocol=None, cb=None): + if JoinChatDialog.RaiseExisting(): + return + + diag = JoinChatDialog(protocol=protocol) + + def cb(ok): + if not ok: return + + acct = diag.Account + if acct is not None: + if acct.connection is not None: + room_name = None + if diag.room.IsShown() and diag.room.IsEnabled(): + room_name = diag.room.Value.strip() or None + + server = None + if diag.server.IsShown(): + server = diag.server.Value.strip() or None + + acct.connection.make_chat_and_invite( + diag.model.pending_contacts, + room_name=room_name, + server=server, + notify=True) + + diag.ShowWithCallback(cb) + +def main(): + JoinChatDialog().Show() + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/chevron.py b/digsby/src/gui/chevron.py new file mode 100644 index 0000000..1136b8a --- /dev/null +++ b/digsby/src/gui/chevron.py @@ -0,0 +1,156 @@ +import wx + +from gui.textutil import GetTextWidth +from gui import skin +from gui.toolbox import prnt +from cgui import SimplePanel + +from config import platformName + +class Chevron(SimplePanel): + + def __init__(self,parent,label='',callapsedicon=None,expandedicon=None): + SimplePanel.__init__(self,parent) + + self.callapsedicon = callapsedicon or skin.get('AppDefaults.icons.chevroncolapsed') + self.expandedicon = expandedicon or skin.get('AppDefaults.icons.chevronexpanded') + self.Label = label + self._expanded = False + + self.CalcSize() + + self.isdown = False + + Bind = self.Bind + Bind(wx.EVT_PAINT,self.OnPaint) + Bind(wx.EVT_LEFT_DOWN,self.OnLeftDown) + Bind(wx.EVT_LEFT_UP,self.OnLeftUp) + Bind(wx.EVT_LEFT_DCLICK, self.OnDClick) + Bind(wx.EVT_ENTER_WINDOW, self.OnMouseIn) + Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseOut) + + def OnMouseIn(self, e): + self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + + def OnMouseOut(self, e): + self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + + def CalcSize(self): + iwidth = max([self.callapsedicon.Width,self.expandedicon.Width]) + iheight = max([self.callapsedicon.Height,self.expandedicon.Height]) + + self.iconspace = wx.Size(iwidth,iheight) + + fwidth = GetTextWidth(self.Label,self.Font) + fheight = self.Font.Height + + self.MinSize = wx.Size(iwidth + fwidth + 12, + max([iheight, fheight]) + 6) + + + def GetExpanded(self): + return self._expanded + + def SetExpanded(self,expand): + self._expanded = expand + + e = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED) + e.EventObject = self + e.SetInt(expand) + self.AddPendingEvent(e) + + Expanded = property(GetExpanded,SetExpanded) + + def OnPaint(self,event): + + dc = wx.PaintDC(self) + rect = wx.RectS(self.Size) + + if platformName != 'mac': + dc.Brush = wx.Brush(self.BackgroundColour) + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(rect) + + dc.Font = self.Font + + iwidth, iheight = self.iconspace + icon = self.expandedicon if self.Expanded else self.callapsedicon + textrect = wx.Rect(iwidth + 9,0, rect.width - (iwidth+9) - 3,rect.height) + dc.DrawBitmap(icon,(iwidth//2-icon.Width//2)+3,(iheight//2-icon.Height//2)+3,True) + dc.DrawLabel(self.Label, textrect, wx.ALIGN_LEFT|wx.ALIGN_TOP) + + def OnDClick(self, event): + self.OnLeftDown(event) + self.OnLeftUp(event) + + def OnLeftDown(self,event): + self.isdown = True + + def OnLeftUp(self,event): + if self.isdown: + self.Expanded = not self.Expanded + self.isdown = False + + self.Refresh() + +class ChevronPanel(SimplePanel): + def __init__(self, parent, label = '', collapsedicon = None, expandedicon = None): + SimplePanel.__init__(self, parent) + + self.construct(label, collapsedicon, expandedicon) + self.layout() + self.bind_events() + self.ToggleContents() + + self.Label = "chevron panel" + + def construct(self, label, collapsedicon, expandedicon): + self.chevron = Chevron(self, label, collapsedicon, expandedicon) + self.contents = wx.Panel(self, -1) + self.contents.Label = 'chevron contents panel' + self.contents.Sizer = wx.BoxSizer(wx.VERTICAL) + + def layout(self): + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.chevron, 0, wx.EXPAND | wx.ALL) + self.Sizer.Add(self.contents, 1, wx.EXPAND | wx.ALL) + + def bind_events(self): + self.chevron.Bind(wx.EVT_COMMAND_CHECKBOX_CLICKED, self.ToggleContents) + + def ToggleContents(self, evt = None): + if evt is not None: + obj = evt.GetEventObject() + else: + obj = self.chevron + + self.contents.Show(obj.Expanded) + self.Top.Fit() + +class F(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None) + + S = self.Sizer = wx.BoxSizer(wx.VERTICAL) + p = self.panel = wx.Panel(self) + S.Add(p,1,wx.EXPAND) + + + s = p.Sizer = wx.BoxSizer(wx.VERTICAL) + + chev = self.chev = Chevron(p,'Expand') + chev.Bind(wx.EVT_CHECKBOX,lambda e: prnt('clixxd',e.IsChecked())) + + s.Add(chev,0,wx.ALL,10) + +if __name__ == '__main__': + from tests.testapp import testapp + + hit = wx.FindWindowAtPointer + + a = testapp('../../') + + f=F() + f.Show(True) + + a.MainLoop() diff --git a/digsby/src/gui/clipboard.py b/digsby/src/gui/clipboard.py new file mode 100644 index 0000000..e7325f7 --- /dev/null +++ b/digsby/src/gui/clipboard.py @@ -0,0 +1,70 @@ +''' +clipboard utilities +''' + +import wx +import path + +def CopyToClipboard(s): + ''' + Copies string s to the clipboard. + ''' + if not s: + return + if not isinstance(s, basestring): + raise TypeError + + clip = wx.TheClipboard + + if clip.Open(): + try: + clip.SetData(wx.TextDataObject(s)) + return True + finally: + clip.Close() + + return False + +copy = CopyToClipboard + +_clipboard_types = {wx.DF_BITMAP: (wx.BitmapDataObject, 'Bitmap'), + wx.DF_TEXT: (wx.TextDataObject, 'Text'), + wx.DF_FILENAME: (wx.FileDataObject, 'Filenames'), + } + +def clipboard_get(df_type): + ''' + Get contents of clipboard. df_type must be one of: + + wx.DF_TEXT + wx.DF_BITMAP + + Returns None if the format was not in the clipboard. + ''' + df = wx.DataFormat(df_type) + + clip = wx.TheClipboard + if clip.Open(): + try: + if clip.IsSupported(df): + obj_type, obj_attr = _clipboard_types[df_type] + obj = obj_type() + clip.GetData(obj) + return getattr(obj, obj_attr) + finally: + clip.Close() + +def get_bitmap(): + return clipboard_get(wx.DF_BITMAP) + +def get_text(): + return clipboard_get(wx.DF_TEXT) + +def get_files(): + res = clipboard_get(wx.DF_FILENAME) + if res is None: + return res + + else: + return map(path.path, res) + diff --git a/digsby/src/gui/clique.py b/digsby/src/gui/clique.py new file mode 100644 index 0000000..6d8b044 --- /dev/null +++ b/digsby/src/gui/clique.py @@ -0,0 +1,22 @@ +from util import FunctionList + +class Clique(set): + def __setattr__(self, attr, val): + for o in self: setattr(o, attr, val) + + def __getattr__(self, attr): + try: + return set.__getattr__(self, attr) + except AttributeError: + try: + return self.__dict__[attr] + except KeyError: + + default = lambda *a, **k: None + + res = FunctionList(getattr(x, attr, default) for x in self) + + return res + + def __repr__(self): + return '<%s: %r>' % (type(self).__name__, set.__repr__(self)) diff --git a/digsby/src/gui/contactdialogs.py b/digsby/src/gui/contactdialogs.py new file mode 100644 index 0000000..acec75c --- /dev/null +++ b/digsby/src/gui/contactdialogs.py @@ -0,0 +1,323 @@ +''' +GUI for editing contacts, metacontacts. +''' + +from __future__ import with_statement +import wx +from wx import VERTICAL, HORIZONTAL, ALIGN_CENTER_HORIZONTAL, EXPAND, ALL, LEFT, RIGHT, TOP, BOTTOM,ALIGN_CENTER_VERTICAL,ALIGN_LEFT +TOPLESS = ALL & ~TOP + +from gui.visuallisteditor import VisualListEditorList + +from gui.toolbox import build_button_sizer, wx_prop +from gui.textutil import CopyFont +from gui.validators import LengthLimit +from util import Storage as S +from logging import getLogger; log =getLogger('contactdialogs'); info = log.info +from common import profile +from gettext import ngettext + +REQUIRE_ALIAS = True + +def get_profile(): + from common import profile + return profile + +def contact_string(c): + return '%s (%s)' % (c.name, c.protocol.name) + +def account_string(a): + return '%s (%s)' % (a.name, a.protocol) + +def send_files(parent, buddy, files): + ''' + parent wxWindow parent + buddy buddy to call send_files on + files a list of file paths + ''' + msg = ngettext(u'Would you like to send "{files[0]}" to {buddy_name:s}?', + u'Would you like to send {num_files:d} files to {buddy_name:s}?', + len(files)).format(files=files, num_files=len(files), buddy_name=buddy.name) + + if wx.YES == wx.MessageBox(msg, _('Send Files'), style = wx.YES_NO, parent = parent): + for filename in files: + buddy.send_file(filename) + +class ContactPanel(wx.Panel): + 'GUI for adding a contact.' + + def __init__(self, parent, to_group = None): + wx.Panel.__init__(self, parent) + + self.construct() + self.layout() + + # Listen for changes to the connected accounts list. + from common import profile + profile.account_manager.connected_accounts.add_observer(self.on_conns_changed) + self.on_conns_changed(profile.account_manager.connected_accounts, to_group) + + def on_close(self): + profile.account_manager.connected_accounts.remove_observer(self.on_conns_changed) + + def on_conns_changed(self, connected_accounts, *a): + 'Updates the accounts choice.' + + choice = self.acct_choice + sel = choice.GetStringSelection() + + with choice.Frozen(): + choice.Clear() + for acct in connected_accounts: + proto_str = account_string(acct) + choice.Append(proto_str) + + choice.SetStringSelection(sel) if sel else choice.SetSelection(0) + + def construct(self): + + self.name_st = wx.StaticText(self, -1, _('Contact &Name:')) + self.name_txt = wx.TextCtrl(self, -1, validator=LengthLimit(255)) + + self.acct_st = wx.StaticText(self, -1, _('Accoun&t:')) + self.acct_choice = wx.Choice(self, -1) + + self.get_acct = lambda: get_profile().account_manager.connected_accounts[self.acct_choice.Selection] + + # Add and Cancel buttons + self.save = wx.Button(self, wx.ID_SAVE, _('&Add')) + self.save.SetDefault() + self.save.Bind(wx.EVT_BUTTON, self.Parent.on_save) + + self.cancel = wx.Button(self, wx.ID_CANCEL, _('&Cancel')) + + name = wx_prop('name_txt') + + def get_info(self): + return S(name = self.name, + account = self.get_acct()) + + def layout(self): + self.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + + sz.Add(self.name_st, 0, wx.EXPAND | wx.ALL, 5) + sz.Add(self.name_txt, 0, wx.EXPAND | wx.ALL, 5) + + sz.Add((0,5)) + + sz.Add(self.acct_st, 0, wx.EXPAND | wx.ALL, 5) + sz.Add(self.acct_choice, 0, wx.EXPAND | wx.ALL, 5) + + # Add/Cancel + sz.Add(build_button_sizer(save=self.save, cancel=self.cancel), + 0, wx.EXPAND | wx.SOUTH | wx.EAST | wx.WEST, 4) + + +class MetaListEditor(VisualListEditorList): + def __init__(self, parent, list2sort, listcallback = None): + VisualListEditorList.__init__(self, parent, list2sort, listcallback = listcallback) + + def OnDrawItem(self,dc,rect,n): + + dc.Font=self.Font + + buddy = self.thelist[n] + + icon = buddy.buddy_icon.Resized(16) + serv = buddy.serviceicon.Resized(16) + + x = rect.x + 3 + y = rect.y + 3 + + textrect = wx.Rect(x + 16 + 3,rect.y,rect.Width - x - 38,rect.Height) + + dc.DrawLabel(buddy.name,textrect,ALIGN_CENTER_VERTICAL|ALIGN_LEFT) + + dc.DrawBitmap(icon,x,y,True) + + + + dc.DrawBitmap(serv,rect.x + rect.Width - 16 - 3,y,True) + +class MetaContactPanel(wx.Panel): + 'GUI for creating or appending to a metacontact.' + + def __init__(self, parent, contacts = [], metacontact = None, order = None): + wx.Panel.__init__(self, parent) + + self.contacts = contacts + self.metacontact = metacontact + self.order = order + + self.construct() + self.layout() + + def construct(self): + + Text = lambda s: wx.StaticText(self,-1,_(s)) + + self.line1 = Text(_('Would you like to merge these contacts?')) + self.line1.Font = CopyFont(self.line1.Font,weight = wx.BOLD) + + self.line2 = Text(_('They will appear as one item on your buddy list.')) + self.sep = wx.StaticLine(self,-1) + + self.alias_label = wx.StaticText(self, -1, _('Alias:')) + self.alias_label.Font = CopyFont(self.alias_label.Font,weight = wx.BOLD) + + # Decide on an alias: don't use the alias property from the metacontact + # because that falls through to the best available one. + alias = self.find_alias_suggestion() + self.alias_text = wx.TextCtrl(self, -1, alias if alias else '', validator=LengthLimit(255)) + + s = self.save = wx.Button(self, wx.ID_SAVE, _('&Save')) # save + s.SetDefault() + s.Bind(wx.EVT_BUTTON, self.Parent.on_save) + + if REQUIRE_ALIAS: + self.alias_text.Bind(wx.EVT_TEXT, lambda e: self.save.Enable(self.alias_text.Value!='')) + self.save.Enable(self.alias_text.Value != '') + + self.cancel = wx.Button(self, wx.ID_CANCEL, _('&Cancel')) # cancel + + self.line4 = Text(_('Drag and drop to rearrange:')) + self.line4.Font = CopyFont(self.line4.Font,weight = wx.BOLD) + self.contacts_list = MetaListEditor(self,self.contacts,self.update_contacts_list) + + + def find_alias_suggestion(self): + 'Returns a suggestion for the alias for this metacontact.' + + if self.metacontact: + # Is this already a metacontact? If so, it already has an alias: + # use that. + return self.metacontact.alias + + # Otherwise use the first available alias. + for contact in self.contacts: + a = profile.get_contact_info(contact, 'alias') + if a: return a + + # No suggestion. + return '' + + + def update_contacts_list(self,contacts): + self.contacts = contacts + + def layout(self): + self.Sizer = sz = wx.BoxSizer(VERTICAL) + + h1 = wx.BoxSizer(VERTICAL) + h1.Add(self.line1,0,ALIGN_CENTER_HORIZONTAL) + h1.Add(self.line2,0,ALIGN_CENTER_HORIZONTAL|TOP,3) + h1.Add(self.sep,0,EXPAND|TOP,6) + h1.Add(self.alias_label,0,TOP,6) + h1.Add(self.alias_text,0,EXPAND|TOP,3) + + h1.Add(self.line4,0,TOP,6) + h1.Add(self.contacts_list,1, EXPAND|TOP,3) + + sz.Add(h1, 1, EXPAND|ALL,6) + # Save/Cancel + sz.Add(build_button_sizer(save=self.save, cancel=self.cancel), + 0, EXPAND | TOPLESS, 4) + + def commit(self): + 'Commits changes.' + + blist = get_profile().blist + mcs = blist.metacontacts + + if self.metacontact: + self.metacontact.rename(self.alias) + mcs.edit(self.metacontact, self.contacts, grouppath = self.metacontact.mcinfo.groups) + return self.metacontact + else: + meta = mcs.create(self.alias, self.contacts, update = False) + meta = mcs.metacontact_objs[meta] + if self.order is not None: + order = [(c if c != '__meta__' else meta) for c in self.order] + blist.rearrange(*order) + return meta + + + + + + alias = wx_prop('alias_text') + +class ContactDialog(wx.Dialog): + + def __init__(self, parent, to_group = None): + wx.Dialog.__init__(self, parent, title = _('Add Contact')) + self.contact_panel = ContactPanel(self, to_group) + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + s.Add(self.contact_panel, 1, wx.EXPAND) + self.Fit() + + self.contact_panel.name_txt.SetFocus() + self.Centre() + + def Prompt(self, callback): + try: + if wx.ID_SAVE == self.ShowModal(): + callback(**self.contact_panel.get_info()) + finally: + self.contact_panel.on_close() + self.Destroy() + + def on_save(self, e): + self.SetReturnCode(wx.ID_SAVE) + if self.IsModal(): + self.EndModal(wx.ID_SAVE) + +class MetaContactDialog(wx.Dialog): + + minsize = (290, 290) + + def __init__(self, parent, contacts, metacontact = None, title=None, order = None): + wx.Dialog.__init__(self, parent, title=title or _('Merge Contacts'), pos=(400,200), + style = wx.DEFAULT_DIALOG_STYLE) + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + self.mc_panel = panel = MetaContactPanel(self, contacts, metacontact, order = order) + s.Add(panel, 1, wx.EXPAND) +# self.SetMinSize(self.minsize) +# self.Layout() + self.Fit() + + + @classmethod + def add_contact(cls, parent, metacontact, contact, position = -1): + 'Creates and returns a dialog for adding a contact to a MetaContact.' + + from contacts.metacontacts import MetaContact + if not isinstance(metacontact, MetaContact): + raise TypeError('parent (wxWindow), metacontact (MetaContact), ' + 'contact (Contact), [position (-1 < p < ' + 'len(metacontact))') + + # Append the contact. (position -1 means at the end) + contacts = list(metacontact) + if position < 0: contacts.append(contact) + else: contacts.insert(position, contact) + + info('contact order for dialog: %r', contacts) + + title = _('Merge Contacts') + return cls(parent, contacts, metacontact, title = title) + + def on_save(self, e): + self.SetReturnCode(wx.ID_SAVE) + if self.IsModal(): + self.EndModal(wx.ID_SAVE) + + def get_info(self): + return self.mc_panel.get_info() + + def Prompt(self, ondone = None): + if wx.ID_SAVE == self.ShowModal(): + result = self.mc_panel.commit() + if ondone: ondone(result) + + diff --git a/digsby/src/gui/controls.py b/digsby/src/gui/controls.py new file mode 100644 index 0000000..294167e --- /dev/null +++ b/digsby/src/gui/controls.py @@ -0,0 +1,101 @@ +'Easy gui.' + +import wx + +def Button(parent, text, callback, **kwargs): + button = wx.Button(parent, -1, _(text), **kwargs) + button.Bind(wx.EVT_BUTTON, lambda *e: callback()) + return button + +def Text(parent, text, *args, **kwargs): + return wx.StaticText(parent, -1, text, *args, **kwargs) + +def CheckBox(parent, text, value = sentinel, **kwargs): + checkbox = wx.CheckBox(parent, -1, _(text), **kwargs) + if value is not sentinel: + checkbox.Value = value + return checkbox + +def TextInput(parent, value = sentinel, *args, **kwargs): + textctrl = wx.TextCtrl(parent, -1, *args, **kwargs) + if value is not sentinel: + textctrl.Value = value + return textctrl + + +def BoxSizer(type, *elems, **opts): + s = wx.BoxSizer({'H': wx.HORIZONTAL, + 'V': wx.VERTICAL}[type]) + + border = opts.get('border', 6) + return add_all(elems, s, border) + +def add_all(elems, s, border): + for elem in elems: + if elem == 'stretch': s.AddStretchSpacer() + else: + s.Add(elem, 0, wx.ALL | wx.EXPAND, border) + return s + +def HSizer(*elems, **opts): + return BoxSizer('H', *elems, **opts) + +def VSizer(*elems, **opts): + return BoxSizer('V', *elems, **opts) + +def FGridSizer(rows, cols, *elems, **opts): + s = wx.FlexGridSizer(rows, cols, vgap=opts.get('vgap', 0), hgap=opts.get('hgap', 0)) + border = opts.get('border', 6) + add_all(elems, s, border) + return s + +class CustomControls(object): + def __init__(self, parent): + self.parent = parent + + class _TextInput(wx.TextCtrl): + def __init__(self, controls, parent, value = sentinel, get_=None, set_=None, *args, **kwargs): + self.controls = controls + self.get_ = get_ + self.set_ = set_ + wx.TextCtrl.__init__(self, parent, -1, *args, **kwargs) + + def GetValue(self): + return self.get_(wx.TextCtrl.GetValue(self)) if self.get_ is not None else wx.TextCtrl.GetValue(self) + def SetValue(self,x): + return wx.TextCtrl.SetValue(self, self.set_(x)) if self.set_ is not None else wx.TextCtrl.SetValue(self, x) + Value = property(GetValue, SetValue) + + def TextInput(self, *a, **k): + return self._TextInput(self, parent = self.parent, *a, **k) + + def LabeledTextInput(self, label, *a, **k): + label_ = Text(self.parent, label) + text = self.TextInput(*a, **k) + return label_, text + + def intBox(self, *a, **k): + return self.TextInput(get_=int, set_=str) + +class RadioPanel(wx.BoxSizer): + 'Radio button group without a surrounding box.' + + def __init__(self, parent, choices, clique = None): + wx.BoxSizer.__init__(self, wx.VERTICAL) + self.Buttons = [] + + for i, choice in enumerate(choices): + b = wx.RadioButton(parent, -1, choice) + b.Bind(wx.EVT_RADIOBUTTON, + lambda e, i=i: setattr(self, 'Value', i)) + self.Buttons.append(b) + if clique is not None: clique.add(b) + self.Add(b, 0, wx.ALL, b.GetDefaultBorder()) + + def GetValue(self): + return self.Value + + def SetValue(self, val): + val = int(val) if val != '' else 0 + self.Buttons[val].SetValue(True) + self.Value = val diff --git a/digsby/src/gui/ctextutil.py b/digsby/src/gui/ctextutil.py new file mode 100644 index 0000000..0e87c73 --- /dev/null +++ b/digsby/src/gui/ctextutil.py @@ -0,0 +1,74 @@ +# This file was created automatically by SWIG 1.3.29. +# Don't modify this file, modify the SWIG interface instead. + +import _ctextutil +import new +new_instancemethod = new.instancemethod +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'PySwigObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static) or hasattr(self,name): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError,name + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +import types +try: + _object = types.ObjectType + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 +del types + + +def _swig_setattr_nondynamic_method(set): + def set_attr(self,name,value): + if (name == "thisown"): return self.this.own(value) + if hasattr(self,name) or (name == "this"): + set(self,name,value) + else: + raise AttributeError("You cannot add attributes to %s" % self) + return set_attr + + +import wx._core +import wx._gdi +import wx +__docfilter__ = wx._core.__DocFilter(globals()) + +def Subtract(*args, **kwargs): + """Subtract(Rect r, int left=0, int right=0, int up=0, int down=0)""" + return _ctextutil.Subtract(*args, **kwargs) + +def RectPos(*args, **kwargs): + """RectPos(Rect rect, Point point) -> PyObject""" + return _ctextutil.RectPos(*args, **kwargs) + +def truncateText(*args, **kwargs): + """ + truncateText(String text, int size, Font font=None, DC dc=None, + String thepostfix=wxT("...")) -> String + """ + return _ctextutil.truncateText(*args, **kwargs) + + diff --git a/digsby/src/gui/ctextutil_setup.py b/digsby/src/gui/ctextutil_setup.py new file mode 100644 index 0000000..43c8c4f --- /dev/null +++ b/digsby/src/gui/ctextutil_setup.py @@ -0,0 +1,33 @@ +import distutils +from distutils.core import setup +from distutils.core import Extension + +WXPYTHON_BIN_DIR = 'C:\\src\\wxPython-2.8.4.0' + +files = ''' +ctextutil.cpp +ctextutil.i +'''.split() + +ctextutilExt = Extension('_ctextutil', files, + swig_opts = ['-c++', '-I%s\\include\\wx\\wxPython\\i_files' % WXPYTHON_BIN_DIR], + include_dirs = ['C:\\src\\wxPython-2.8.1.1\\lib\\vc_dll\\mswuh', + "%s/include" % WXPYTHON_BIN_DIR, + '%s/include/wx/msw' % WXPYTHON_BIN_DIR, + 'C:\\program files\\Microsoft Visual C++ Toolkit 2003\\include', + 'C:\\program files\\Microsoft Platform SDK for Windows Server 2003 R2\\Include', + ], + library_dirs = ['C:\\program files\\Microsoft Platform SDK for Windows Server 2003 R2\\Lib', + 'C:\\program files\\Microsoft Visual Studio .NET 2003\\Vc7\\lib', + 'C:\\src\\wxPython-2.8.1.1\\lib\\vc_dll', + ], + libraries = 'wxmsw28uh_core wxmsw28uh_adv wxbase28uh'.split(), + define_macros = [('WXUSINGDLL','1'), + ('_UNICODE',1)] +) + +if __name__ == '__main__': + + setup(name='ctextutil', version='1.0', + ext_modules=[ ctextutilExt ] + ) \ No newline at end of file diff --git a/digsby/src/gui/dialogs.py b/digsby/src/gui/dialogs.py new file mode 100644 index 0000000..ca92c23 --- /dev/null +++ b/digsby/src/gui/dialogs.py @@ -0,0 +1,18 @@ +from util.callbacks import callsback +import wx + +@callsback +def NonModalMessageDialog(*a, **k): + ''' + workaround to get an effectively non-modal Message Dialog + callback.success called with the return code from ShowModal + ''' + callback=k['callback'] + def doNonModal(parent=None, *a): + f = wx.Frame(parent) + d = wx.MessageDialog(f, *a) + ret = d.ShowModal() + d.Destroy() + f.Destroy() + callback.success(ret) + wx.CallAfter(doNonModal, *a) diff --git a/digsby/src/gui/draw.py b/digsby/src/gui/draw.py new file mode 100644 index 0000000..8616f78 --- /dev/null +++ b/digsby/src/gui/draw.py @@ -0,0 +1,33 @@ +import wx + +def border(dc, rect, top=True, bottom=True, left=True, right=True, + rounded=False, size=None): + """ + Draws a border for the specified rectangle. The current brush and pen are + used. + + Defaults are to draw the whole rectangle, but if you want to leave sides + out, specify them as False, like + + >>> draw.border(dc, rect, left=False, right=False) + + If the rounded keyword argument is set to True, then a full rectangle will + be drawn with rounded edges. + """ + dc.SetPen(wx.BLACK_PEN) + + if top and bottom and left and right: + dc.SetBrush(wx.TRANSPARENT_BRUSH) + if rounded: + dc.DrawRoundedRectangle(rect.x,rect.y,rect.width,rect.height, rounded) + else: + dc.DrawRectangle(rect.x,rect.y,rect.width, rect.height) + else: + pen_width = dc.GetPen().GetWidth() + bottompos = rect.y + rect.height - pen_width + rightpos = rect.x + rect.width - pen_width + + if top: dc.DrawLine(rect.x, rect.y, rightpos, rect.y) + if bottom: dc.DrawLine(rect.x, bottompos, rightpos, bottompos) + if left: dc.DrawLine(rect.x, rect.y, rect.x, bottompos) + if right: dc.DrawLine(rightpos, rect.y, rightpos, bottompos) \ No newline at end of file diff --git a/digsby/src/gui/filetransfer/__init__.py b/digsby/src/gui/filetransfer/__init__.py new file mode 100644 index 0000000..5ca5609 --- /dev/null +++ b/digsby/src/gui/filetransfer/__init__.py @@ -0,0 +1 @@ +from gui.filetransfer.filetransferlist import FileTransferDialog \ No newline at end of file diff --git a/digsby/src/gui/filetransfer/filetransferlist.py b/digsby/src/gui/filetransfer/filetransferlist.py new file mode 100644 index 0000000..fcac667 --- /dev/null +++ b/digsby/src/gui/filetransfer/filetransferlist.py @@ -0,0 +1,648 @@ +''' + +GUI for managing file transfers + +''' +from __future__ import with_statement +from __future__ import division + +import wx +from wx import Point, FONTWEIGHT_NORMAL, FONTWEIGHT_BOLD, RectPS, Size, \ + HORIZONTAL, VERTICAL, EXPAND, RIGHT, ALIGN_RIGHT, BOTTOM, TOP, BoxSizer, Colour + +import os +from traceback import print_exc + +from gui import skin +from gui.skin.skinobjects import SkinColor +from gui.anylists import AnyList, AnyRow +from gui.toolbox import persist_window_pos, check_destroyed, AutoDC, calllimit +from gui.uberwidgets.clearlink import ClearLink +from gui.textutil import default_font +from gui.uberwidgets.UberProgressBar import UberProgressBar +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.umenu import UMenu +from util.primitives.error_handling import traceguard +from util.primitives.funcs import Delegate +from util.primitives.mapping import Storage as S +from path import path +from common import bind +from common.filetransfer import FileTransfer +from gettext import ngettext +from logging import getLogger; log = getLogger('filetransferlist') + + + +xs = FileTransfer.states + +FILEXFER_ICON = 'AppDefaults.NotificationIcons.FileTransfer' + +class FTLinkControl(ClearLink): + def __init__(self, parent, text, on_click=None, should_be_active=None, align='right'): + + self._on_click = Delegate() + + if on_click: + self._on_click += on_click + + ClearLink.__init__(self, parent, -1, text, on_click, style = wx.NO_BORDER | getattr(wx, 'HL_ALIGN_%s' % align.upper())) + linkfont = skin.get('filetransfers.fonts.link', default_font) + + self.SetFont(linkfont) + + self._is_active = Delegate(collect_values=True) + if should_be_active: + self._is_active += should_be_active + +# self.Bind(wx.EVT_HYPERLINK, on_click) + + def on_click(self, e=None): + return self._on_click(e) + + def is_active(self): + return all(self._is_active()) + + +class FileTransferRow(AnyRow): + ''' + One row in the file transfer manager. + ''' + + update_interval = 1000 + + def __init__(self, parent, data): + self.xfer = data + + self.text = '' + + self.links = {} + + self.UpdateSkin(first = True) + AnyRow.__init__(self, parent, data, use_checkbox = False, linkobservers = False) + + self.SetMinSize((20, self.MinSize.height)) + + self._bc_timer = wx.PyTimer(lambda: wx.CallAfter(self.on_completed_changed, self.xfer, None, None, None)) + self._bc_timer.Start(self.update_interval) + + data.add_gui_observer(self.on_completed_changed, 'completed') + data.add_gui_observer(self.on_update_gui, 'state') + + if getattr(self.xfer, 'autoremove', False): + if self.xfer.state in self.xfer.autoremove_states: + self.on_remove() + + def on_close(self): + self.data.remove_gui_observer(self.on_completed_changed) + self.data.remove_gui_observer(self.on_update_gui) + AnyRow.on_close(self) + + def UpdateSkin(self, first = False): + s = skin.get('filetransfers') + self.normalbg = s.get('backgrounds.normal',[SkinColor(wx.Color(238, 238, 238)),SkinColor(wx.Color(255, 255, 255))]) + self.selectedbg = s.get('backgrounds.selected',SkinColor(wx.Color(180, 180, 180))) + self.hoveredbg = s.get('backgrounds.hovered',SkinColor(wx.Color(220, 220, 220))) + self.padding = s.get('padding', lambda: Point(5, 5)) + self.margins = s.get('margins', lambda: skin.ZeroMargins) + + if not first: + with traceguard: self.details.SetFont(skin.get('filetransfers.fonts.other', default_font)) + + linkfont = skin.get('filetransfers.fonts.link', default_font) + for link in self.links.values(): + link.SetFont(linkfont) + + self.layout() + self.Refresh(False) + + @calllimit(.3) + def on_update_gui(self, *a): + if not wx.IsDestroyed(self): + self.PopulateControls(self.xfer) + if getattr(self.xfer, 'autoremove', False): + if self.xfer.state in self.xfer.autoremove_states: + self.on_remove() + + @calllimit(.2) + def on_completed_changed(self, xfer, attr, old, new): + if wx.IsDestroyed(self): + return + + sz = xfer.size + if sz < 1: + self.pbar.Pulse() # indicate indeterminate progress + else: + self.pbar.SetValue(xfer.completed / float(xfer.size) * 100.0) + + try: + self.on_update_gui() + except Exception: + self._bc_timer.Stop() + print_exc() + else: + if xfer.state in xfer.states.TransferringStates: + self._bc_timer.Start(self.update_interval) + else: + self._bc_timer.Stop() + + def construct(self, use_checkbox): + self.details = wx.StaticText(self, -1, '') + with traceguard: self.details.SetFont(skin.get('filetransfers.fonts.other', default_font)) + self.ConstructMore() + + def ConstructMore(self): + self.pbar = UberProgressBar(self, range = 100, skinkey = 'ProgressBar') + self.pbar.SetMinSize((50, 13)) + + self.right_links = [] + for name, txt, cb, act in self.xfer.get_right_links(): + + fn = lambda e = None, nm=name, _cb = cb: (_cb(), getattr(self, 'on_%s'%nm, self._on_default_link)(e)) + ln = FTLinkControl(self, txt, fn, act) + self.right_links.append(ln) + + assert name not in self.links + self.links[name] = ln + + self.bottom_links = [] + + for name, txt, cb, act in self.xfer.get_bottom_links(): + + fn = lambda e = None, nm=name, _cb = cb: (_cb(), getattr(self, 'on_%s'%nm, self._on_default_link)(e)) + ln = FTLinkControl(self, txt, fn, act, align='left') + self.bottom_links.append(ln) + + assert name not in self.links + self.links[name] = ln + + def _on_default_link(self, e = None): + if e is not None: + log.info("No callback function found for link %r", e.GetEventObject().Label) + else: + log.info("Default link callback called.") + + def layout(self): + self.Sizer = None + + # + # overrides AnyRow.layout + # + sz = BoxSizer(HORIZONTAL) + p = self.padding + links = self.links + rlinks = self.right_links + blinks = self.bottom_links + + if self.image: + sz.AddSpacer((p.x + self.image.Width + p.x, self.row_height)) + + v = BoxSizer(VERTICAL) + + topH = BoxSizer(HORIZONTAL) + topH.AddSpacer((1, self.Parent.fonts.filename.LineHeight), 0, EXPAND) + topH.AddStretchSpacer(1) + if rlinks: + topH.Add(rlinks[0], 0, EXPAND | RIGHT | ALIGN_RIGHT, p.x) + + v.Add(topH, 0, EXPAND | TOP | BOTTOM, p.y) + v.Add(self.pbar, 0, EXPAND | RIGHT, p.x) + + bottomH = BoxSizer(HORIZONTAL); Add = bottomH.Add + Add(self.details, 0, EXPAND) + if blinks: + for link in blinks: + Add(link, 0, EXPAND | RIGHT, p.x) + + bottomH.AddStretchSpacer(1) + if rlinks: + for link in rlinks[1:]: + Add(link, 0, EXPAND | RIGHT, p.x) + + + v.Add(bottomH, 0, EXPAND | TOP | BOTTOM, p.y) + sz.Add(v, 1) + + # apply margins + self.Sizer = self.margins.Sizer(sz) + + + def on_open(self, e = None): + try: + os.startfile(self.xfer.filepath) + except WindowsError, e: + strerror = '%s:\n%s' % (e.strerror, self.xfer.filepath.abspath()) + wx.MessageBox(strerror, _('File not found'), style=wx.OK | wx.ICON_ERROR) + + + def on_open_folder(self, e = None): + xfer = self.xfer + try: + xfer.filepath.openfolder() + except WindowsError, e: + strerror = '%s:\n%s' % (e.strerror, xfer.filepath.parent.abspath()) + wx.MessageBox(strerror, _('File not found'), style=wx.OK | wx.ICON_ERROR) + + def on_cancel(self, e = None): + self._bc_timer.Stop() + wx.CallAfter(self.PopulateControls, self.xfer) + + def on_remove(self, e = None): + s, sts = self.xfer.state, self.xfer.states + if s in (sts.CONNECTING, sts.TRANSFERRING): + return log.info('not removing transfer, state is CONNECTING or TRANSFERRING') + + xfers = self.Parent.data + + log.info('removing transfer %r', xfers) + xfers.remove(self.xfer) + log.info('transfers: %r', xfers) + + self._bc_timer.Stop() + wx.CallAfter(self.Parent.Layout) + + def on_save(self, e=None, path=None): + # get default save directory, try to save there - if it fails then fall back to on_saveas + pass + + def on_saveas(self, e=None): + # get directory from user and then pass it to on_save + pass + + def on_reject(self, e=None): + # reject the file + pass + + def CalcColors(self, selected = None): + selected = self.IsSelected() if selected is None else selected + + self.bg = self.selectedbg if selected else self.hoveredbg if self.IsHovered() else self.normalbg[self.Index % len(self.normalbg)] if isinstance(self.normalbg,list) else self.normalbg + + fontcolors = self.Parent.fontcolors + for ctrl in self.links.values(): + color = getattr(fontcolors, 'link' + ('selected' if selected else '')) + if isinstance(color, Colour): + ctrl.SetForegroundColour(color) + + def PaintMore(self, dc): + + p = self.padding + xfer = self.xfer + fonts, fontcolors = self.Parent.fonts, self.Parent.fontcolors + iconsize = 16 + + selected = self.IsSelected() + def col(name): + return getattr(self.Parent.fontcolors, name + ('selected' if selected else '')) + + states = xfer.states + + if xfer.state in (states.TRANSFERRING, states.WAITING_FOR_YOU, states.WAITING_FOR_BUDDY) and getattr(xfer, 'use_serviceicon', True): + icon = xfer.buddy.serviceicon.Resized(iconsize) + + if self.pbar.Shown: + r = self.pbar.Rect + x, y = r.Right - icon.Width, r.Top - icon.Height - p.y + else: + r = self.ClientRect.AddMargins(self.margins) + x, y = r.Right - icon.Width - p.y, r.Top + p.y + + dc.DrawBitmap(icon, x, y, True) + + if self.text: + fontcolors = self.Parent.fontcolors + cr = self.ClientRect + pp = cr.TopLeft + Point(p.x * 2 + self.Parent.filetype_icon_size, p.y) + + first_shown_rlink = ([x for x in self.right_links if x.IsShown()] or [None])[0] + if first_shown_rlink is not None: + _move_width = first_shown_rlink.Size.width + else: + _move_width = iconsize + + mainw = cr.width - pp.x - p.x*2 - _move_width + + f = self.Parent.fonts.filename + + drect = RectPS(pp, Size(mainw, f.Height)) + f.Weight = FONTWEIGHT_BOLD + dc.Font = f + + w, h = dc.GetTextExtent(self.text) + dc.TextForeground = col('filename') + dc.DrawTruncatedText(self.text, drect) + + if drect.width: + dc.Font = fonts.filename + f = dc.Font + f.Weight = FONTWEIGHT_NORMAL + dc.Font = f + + drect.Subtract(left = w) + dc.TextForeground = col('filename') + dc.DrawTruncatedText(self.buddytext_Label, drect) + + if self.details_Label: + dc.Font = self.details.Font + + details = self.details + for link in self.right_links[::-1]: + if link.IsShown(): + r = link.Position + Point(-p.x, link.Size.height) + break + else: + r = self.Rect.BottomRight - p + + + drect = RectPS(details.Position, wx.Size(r.x - details.Position.x, details.Size.height)) + dc.TextForeground = col('other') + dc.DrawTruncatedText(self.details_Label, drect) + + def draw_text(self, dc, x, sz): + pass + + @property + def BuddyText(self): + dir, name = self.xfer.direction, self.xfer.buddy.name + + if dir == 'incoming': + return ' from %s' % name + else: + return ' to %s' % name + + if hasattr(wx.Icon, 'ForFileType'): + @property + def image(self): + if hasattr(self.xfer, 'icon'): + return self.xfer.icon.Resized(self.Parent.filetype_icon_size) + + # use wxIcon.ForFileType to look up the system icon for a filetype + fname = self.xfer.filepath if self.xfer.filepath is not None else self.xfer.name + + icon = None + try: + ext = path(fname).ext + icons = self.Parent.filetype_icons + + if ext not in icons: + icons[ext] = None # only check for the filetype icon once + with traceguard: + icon = wx.Icon.ForFileType(ext) + if icon is not None and icon.IsOk(): + icons[ext] = icon.WXB + + icon = icons.get(ext) + except Exception: + print_exc() + + if not icon: + icon = skin.get(FILEXFER_ICON).Resized(self.Parent.filetype_icon_size) + + return icon + else: + @property + def image(self): + if hasattr(self.xfer, 'icon'): + return self.xfer.icon.Resized(self.Parent.filetype_icon_size) + + return skin.get(FILEXFER_ICON).Resized(self.Parent.filetype_icon_size) + + def PopulateControls(self, xfer, *a): + if self.text != xfer.name: + self.text = xfer.name + + s, states = xfer.state, xfer.states + + for link in self.links.values(): + link.Show(link.is_active()) + + self.buddytext_Label = self.BuddyText if xfer.should_show_buddy_name else '' + + self.pbar.Show(xfer.state in states.TransferringStates) + # XXX: Raise an exception here and it might not show! + if not getattr(xfer, 'size', None): + val = 1 + else: + val = xfer.completed / float(xfer.size) * 100.0 + + self.percent_done = val + self.pbar.SetValue(val) + + self.Layout() + self.Parent.Layout() + self.Parent.Refresh() + + self.Parent.update_title() + + @property + def details_Label(self): + return self.xfer.details_string + + @property + def popup(self): + return self.Parent.xfer_popup + +class FileTransferList(AnyList): + def __init__(self, parent, data): + + self.UpdateSkin(first = True) + self.filetype_icons = {} + + AnyList.__init__(self, parent, data, row_control = FileTransferRow, + draggable_items = False) + + self.Bind(wx.EVT_PAINT, self.on_paint) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + @calllimit(.5) + def update_title(self): + percent = percent_complete(self.data) + info = ('%s - ' % percent) if percent is not None else '' + if not wx.IsDestroyed(self.Top): + self.Top.SetTitle(info + _('File Transfers')) + + @property + def xfer_popup(self): + list = self + try: + return list._popupmenu + except AttributeError: + pass + + list._popupmenu = menu = UMenu(self, onshow = self.on_rightclick_show) + + menu.open = menu.AddItem(_('&Open'), callback = lambda: list._menurow.on_open()) + menu.openfolder = menu.AddItem(_('Open &Containing Folder'), callback = lambda: list._menurow.on_open_folder()) + menu.AddSep() + menu.remove = menu.AddItem(_('&Remove'), callback = lambda: list._menurow.on_remove()) + return menu + + def on_rightclick_show(self, *a): + ''' + Invoked just before the menu is shown. + + Enables/disables items as appropriate for the transfer. + ''' + row = self._menurow + xfer = row.data + menu = self.xfer_popup + + menu.open.Enable(xfer.allow_open()) + menu.openfolder.Enable(xfer.allow_open_folder()) + menu.remove.Enable(xfer.allow_remove()) + + def on_paint(self, e): + dc = AutoDC(self) + rect = self.ClientRect + self.bg.Draw(dc, rect) + + def UpdateSkin(self, first = False): + skin_get = skin.get + + def g(k, default = sentinel): + elem = skin_get('FileTransfers.Fonts.%s' % k, default) + if elem is None: return default() + else: return elem + + fonts = self.fonts = S() + fonts.filename = g('filename', lambda: default_font()) + fonts.other = g('other', lambda: fonts.filename) + fonts.link = g('link', lambda: fonts.filename) + + g = lambda k, default = sentinel: (skin_get('FileTransfers.FontColors.%s' % k, default) or default()) + + fontcolors = self.fontcolors = S() + fontcolors.filename = g('filename', lambda: wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) + fontcolors.other = g('other', lambda: fontcolors.filename) + fontcolors.link = g('link', lambda: wx.BLUE) + + fontcolors.filenameselected = g('filenameselected', lambda: wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)) + fontcolors.otherselected = g('otherselected', lambda: fontcolors.filenameselected) + fontcolors.linkselected = g('linkselected', lambda: fontcolors.filenameselected) + + self.filetype_icon_size = 32 + + self.bg = skin_get('filetransfers.backgrounds.list', lambda: SkinColor(wx.WHITE)) + + if not first: wx.CallAfter(self.Layout) + +class FileTransferPanel(wx.Panel): + def __init__(self, parent, xferlist): + wx.Panel.__init__(self, parent) + self.xferlist = xferlist + + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + ftlist = FileTransferList(self, xferlist) + s.Add(ftlist, 1, wx.EXPAND | wx.ALL) + + from gui.uberwidgets.UberBar import UberBar + bar = self.bar = UberBar(self, skinkey = skin.get('FileTransfers.CleanupTaskBar'), alignment = wx.ALIGN_RIGHT) + s.Add(self.bar, 0, wx.EXPAND) + + bar.AddStretchSpacer(1) + self.cleanup = cleanup = UberButton(bar, -1, _('Clean Up')) + cleanup.Bind(wx.EVT_BUTTON, self.on_cleanup) + + # button disabled when there are no transfers + cleanup.Enable(bool(xferlist)) + xferlist.add_gui_observer(self.on_xfer) + self.Bind(wx.EVT_WINDOW_DESTROY, lambda e, w=self: self.xferlist.remove_observer(self.on_xfer) if e.EventObject is w else None) + + bar.Add(cleanup) + + def on_xfer(self, xfers, *a): + if check_destroyed(self): return + log.info('ENABLE/DISABLE cleanup button: %r' % xfers) + self.cleanup.Enable(bool(self.xferlist)) + + def on_cleanup(self, e = None): + xferlist = self.xferlist + xferlist[:] = [x for x in xferlist if not x.is_done()] + + def UpdateSkin(self): + self.bar.SetSkinKey(skin.get('FileTransfers.CleanUpTaskBar')) + wx.CallAfter(self.Layout) + self.Refresh() + + + + +class FileTransferDialog(wx.Frame): + + @staticmethod + @bind('Global.DownloadManager.ToggleShow') + def ToggleShow(xfers=None): + for win in wx.GetTopLevelWindows(): + if isinstance(win, FileTransferDialog): + return win.Close() + + FileTransferDialog._OpenNewWindow(xfers) + + @staticmethod + def Display(xfers=None): + for win in wx.GetTopLevelWindows(): + if isinstance(win, FileTransferDialog): + win.Show() + win.Raise() + return + + FileTransferDialog._OpenNewWindow(xfers) + + @staticmethod + def _OpenNewWindow(xfers): + if xfers is None: + from common import profile + xfers = profile.xfers + + FileTransferDialog(None, xfers).ShowNoActivate(True) + + def __init__(self, parent, xferlist): + wx.Frame.__init__(self, parent, title = _('File Transfers'), name = 'File Transfers') + self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon')) + + with traceguard: + from gui.toolbox import snap_pref + snap_pref(self) + + s = self.Sizer = wx.BoxSizer(wx.VERTICAL) + ftp = FileTransferPanel(self, xferlist) + s.Add(ftp, 1, wx.EXPAND) + + persist_window_pos(self, defaultPos = wx.Point(200, 200), defaultSize = wx.Size(450, 300)) + + self.Bind(wx.EVT_CLOSE, self.__OnClose) + + def __OnClose(self, e): + self.Hide() + e.Skip() + + def UpdateSkin(self): + wx.CallAfter(self.Layout) + + +def percent_complete(xfers): + ''' + given a list of file transfer objects, returns a string like + "42% of 6 files" or None if no transfers are active + ''' + count = totalsize = totalcompleted = 0 + + for xfer in xfers: + size = getattr(xfer, 'size', -1) + completed = getattr(xfer, 'completed', None) + + if xfer.is_active() and size >= 0 and completed is not None: + count += 1 + totalsize += size + totalcompleted += completed + + if count: + # average + if totalsize: + percent = (totalcompleted / totalsize) * 100 + files = ngettext('file', 'files', count) + percent_str = '%.0f' % percent + else: + return 'Unknown Size' + + if percent_str != '0': + return '%s%% of %d %s' % (percent_str, count, files) diff --git a/digsby/src/gui/helpdigsby.py b/digsby/src/gui/helpdigsby.py new file mode 100644 index 0000000..ae0b905 --- /dev/null +++ b/digsby/src/gui/helpdigsby.py @@ -0,0 +1,29 @@ +__all__ = ['show_research_popup', + 'show_research_popup_once'] + +HEADER = _('Keep Digsby Free') +MINOR = _("Digsby will use your computer's free time using it to conduct both free and paid research.") + +def _on_options(): + from gui.pref import prefsdialog + prefsdialog.show('helpdigsby') + +def show_research_popup(): + from gui.toast import popup + + buttons = [(_('Options'), _on_options), + (_('OK'), lambda: None)] + + popup(header=HEADER, + minor=MINOR, + sticky=True, + buttons = buttons) + +def show_research_popup_once(): + from common import pref, setpref + + if not pref('research.showed_notification', default=False, type=bool): + show_research_popup() + + setpref('research.showed_notification', True) + diff --git a/digsby/src/gui/imagedialog.py b/digsby/src/gui/imagedialog.py new file mode 100644 index 0000000..c7d0dfa --- /dev/null +++ b/digsby/src/gui/imagedialog.py @@ -0,0 +1,81 @@ +from PIL.Image import BICUBIC, ANTIALIAS +import wx +from gui.toolbox.imagefx import wxb_to_pil_nocache, pil_to_wxb_nocache + +class ImageDialog(wx.Dialog): + SuccessID = wx.ID_OK + + def __init__(self, parent, + screenshot, pos, title, + message, + oklabel = _('OK'), + cancellabel = _('Cancel')): + + wx.Dialog.__init__(self, parent, -1, title) + + self.screenshot = resize_image_for_dialog(screenshot) + + p = self.panel = wx.Panel(self) + s = self.panel.Sizer = VSizer() + + s.Add((self.screenshot.Width, self.screenshot.Height + 20)) + p.Bind(wx.EVT_PAINT, self.paint) + s.Add(wx.StaticText(p, -1, message), 0, wx.EXPAND | wx.BOTTOM, 8) + + send = wx.Button(p, self.SuccessID, oklabel) + send.SetDefault() + + cancel = wx.Button(p, wx.ID_CANCEL, cancellabel) + + h = HSizer() + h.AddStretchSpacer(1) + h.AddMany([(send, 0, wx.EXPAND | wx.RIGHT, 10), (cancel, 0)]) + s.Add(h, 0, wx.EXPAND) + + s = self.Sizer = VSizer() + s.Add(self.panel, 1, wx.EXPAND | wx.ALL, 8) + + self.Fit() + self.SetPosition(pos) + + def paint(self, e): + dc = wx.PaintDC(e.EventObject) + r = self.panel.ClientRect + dc.DrawBitmap(self.screenshot, r.HCenter(self.screenshot), 10, True) + +def resize_image_for_dialog(screenshot, maxsize=650): + if isinstance(screenshot, wx.Bitmap): + screenshot = wxb_to_pil_nocache(screenshot) + + w, h = screenshot.size + img = screenshot + + if w > maxsize or h > maxsize: + if w > h: + new_height = int(h/float(w) * maxsize) + img = img.resize((maxsize, new_height), BICUBIC if maxsize > w else ANTIALIAS) + else: + new_width = int(w/float(h) * maxsize) + img = img.resize((new_width, maxsize), BICUBIC if maxsize > h else ANTIALIAS) + + return pil_to_wxb_nocache(img) + +HSizer = lambda: wx.BoxSizer(wx.HORIZONTAL) +VSizer = lambda: wx.BoxSizer(wx.VERTICAL) + +def show_image_dialog(parent, message, bitmap, title = None): + from gui.imagedialog import ImageDialog + + diag = ImageDialog(parent, bitmap, wx.DefaultPosition, + message = message, + title = title or _('Send Image'), + oklabel = _('&Send Image'), + cancellabel = _('&Don\'t Send')) + + diag.CenterOnParent() + try: + return diag.ShowModal() == ImageDialog.SuccessID + finally: + if not wx.IsDestroyed(diag): + diag.Destroy() + diff --git a/digsby/src/gui/imdialogs.py b/digsby/src/gui/imdialogs.py new file mode 100644 index 0000000..f9c6e2a --- /dev/null +++ b/digsby/src/gui/imdialogs.py @@ -0,0 +1,147 @@ +import wx +import wx.lib.sized_controls as sc + +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.simplemenu import SimpleMenuItem +from gui.imwin import begin_conversation + +from gui.toolbox import persist_window_pos +from gui.validators import LengthLimit + +from common import profile +from util.primitives.structures import oset + +def ShowNewIMDialog(): + NewIMDialog.MakeOrShow() + +class NewIMDialog(sc.SizedDialog): + def __init__(self): + sc.SizedDialog.__init__(self, None, -1, _('New IM'), + style = wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT) + + self.Name = 'New IM Dialog' + + p = self.GetContentsPane() + p.SetSizerType("form") + + R_CENTER= dict(halign='right', valign='center') + + Text = lambda t: wx.StaticText(p, -1, t) + Text(_('To')).SetSizerProps(**R_CENTER) + + toctrl = self.toctrl = wx.TextCtrl(p,style = wx.TE_PROCESS_ENTER, validator=LengthLimit(255)) + toctrl.Bind(wx.EVT_KEY_DOWN,self.OnKeyDown) + toctrl.SetSizerProps(expand=True) + + Text(_('From')).SetSizerProps(**R_CENTER) + fromcombo = self.fromcombo = UberCombo(p, None, skinkey='AppDefaults.PrefCombo') + fromcombo.SetItems(self.UpdateAccountItems(),0) + fromcombo.SetSizerProps(expand=True) + + self.SetButtonSizer(self.CreateButtonSizer(wx.OK | wx.CANCEL)) + sendbutton = self.sendbutton = self.FindWindowById(wx.ID_OK, self) + sendbutton.SetLabel(_('IM')) + sendbutton.Enable(False) + sendbutton.Bind(wx.EVT_BUTTON,self.OnAccept) + + profile.account_manager.connected_accounts.add_observer(self.WhenOnlineAcctsChange) + + Bind = self.Bind + Bind(wx.EVT_CLOSE,self.OnClose) + Bind(wx.EVT_KEY_DOWN,self.OnKeyDown) + + persist_window_pos(self, defaultPos = 'center', position_only = True) + + self.Fit() + self.SetSize((300, self.BestSize.height)) + + + def WhenOnlineAcctsChange(self,*a): + fromcombo = self.fromcombo + + fromcombo.SetItems(self.UpdateAccountItems()) + + if not fromcombo.Value in fromcombo: + if len(fromcombo): + fromcombo.SetSelection(0) + else: + fromcombo.Value = _('No Connections') + + self.UpdateButtonState() + + def UpdateAccountItems(self): + + if not hasattr(self,'acctitems'): + self.acctitems = oset() + + acctitems = self.acctitems + + accounts = oset(profile.account_manager.connected_accounts) + accts = set(item.id for item in acctitems) + + newaccts = accounts - accts + oldaccts = accts - accounts + + for item in set(acctitems): + if item.id in oldaccts: + acctitems.remove(item) + + for account in newaccts: + if account.allow_contact_add: + acctitems.add(SimpleMenuItem([account.serviceicon.Resized(16),account.username],id = account)) + + + return list(acctitems) + + def OnKeyDown(self, event): + ctrl = event.ControlDown() + key = event.GetKeyCode() + + if ctrl and key==wx.WXK_DOWN: + self.fromcombo.SelectNextItem(True) + elif ctrl and key==wx.WXK_UP: + self.fromcombo.SelectPrevItem(True) + elif key==wx.WXK_ESCAPE: + self.Close() + elif key==wx.WXK_RETURN and (bool(self.toctrl.Value) and len(self.fromcombo)): + self.OnAccept() + else: + event.Skip() + + self.UpdateButtonState() + + def UpdateButtonState(self): + wx.CallAfter(lambda: self.sendbutton.Enable(bool(self.toctrl.Value) and len(self.fromcombo))) + + def OnAccept(self, event = None): + self.Hide() + + proto = self.fromcombo.Value.id.connection + begin_conversation(proto.get_buddy(self.toctrl.Value), forceproto = True) + + self.Close() + + + def OnClose(self,event): + profile.account_manager.connected_accounts.remove_observer(self.WhenOnlineAcctsChange) + + self.Show(False) + + type(self).new_im_instance = None + + self.Destroy() + + + @classmethod + def MakeOrShow(cls): + + if not hasattr(cls,'new_im_instance'): + cls.new_im_instance = None + + if cls.new_im_instance: + cls.new_im_instance.Show(True) + else: + cls.new_im_instance = NewIMDialog() + cls.new_im_instance.Show(True) + + cls.new_im_instance.ReallyRaise() diff --git a/digsby/src/gui/imwin/__init__.py b/digsby/src/gui/imwin/__init__.py new file mode 100644 index 0000000..d349565 --- /dev/null +++ b/digsby/src/gui/imwin/__init__.py @@ -0,0 +1,7 @@ +''' + +im window/tab controls + +''' + +from gui.imwin.imhub import on_message, begin_conversation \ No newline at end of file diff --git a/digsby/src/gui/imwin/actionlink.py b/digsby/src/gui/imwin/actionlink.py new file mode 100644 index 0000000..da83394 --- /dev/null +++ b/digsby/src/gui/imwin/actionlink.py @@ -0,0 +1,64 @@ +import wx +from gui.imwin.styles import get_theme, BasicMessageStyle +from gui.imwin.messagearea import MessageArea +from util import Storage as S +from datetime import datetime +from logging import getLogger; log = getLogger('actionlink') + +class ActionLink(object): + def __init__(self, html, **callbacks): + self.html = html + self.callbacks = callbacks + + +if __name__ == '__main__': + message_style = 'GoneDark' + + + def msgobj(msg): + return S(buddy = b, + conversation = c, + message = msg, + timestamp = datetime.now()) + + from tests.testapp import testapp + a = testapp('../../..') + + theme = get_theme(message_style) + f = wx.Frame(None, size = (600,400)) + p = wx.Panel(f) + + b = wx.Button(p, -1, 'foo') + b2 = wx.Button(p, -1, 'bar') + b3 = wx.Button(p, -1, 'html') + + s = p.Sizer = wx.BoxSizer(wx.VERTICAL) + msg = MessageArea(p, theme = theme) + + msg.OnURL('21321321:accept', lambda: log.info("SUCCESS")) + + b.Bind(wx.EVT_BUTTON, + lambda e: (msg.format_message('incoming', msgobj('foo foo foo foo fooo fo foo?')))) + + b2.Bind(wx.EVT_BUTTON, + lambda e: (msg.format_message('incoming', msgobj('barbar bar!'), next = True))) + + b3.Bind(wx.EVT_BUTTON, lambda e: log.info(msg.HTML)) + + s.Add(msg, 1, wx.EXPAND) + + h = wx.BoxSizer(wx.HORIZONTAL) + s.Add(h, 0, wx.EXPAND) + h.AddMany([b, b2, b3]) + + from tests.mock.mockbuddy import MockBuddy + from tests.mock.mockconversation import MockConversation + + c = MockConversation() + b = MockBuddy('Digsby Dragon') + + + #msg.format_message('incoming', msgobj('test message')) + + f.Show() + a.MainLoop() diff --git a/digsby/src/gui/imwin/emoticons.py b/digsby/src/gui/imwin/emoticons.py new file mode 100644 index 0000000..642a443 --- /dev/null +++ b/digsby/src/gui/imwin/emoticons.py @@ -0,0 +1,380 @@ +''' +Loads emoticons +''' + +from __future__ import with_statement +from traceback import print_exc +from util import Storage as S, odict +from util.xml_tag import tag +from util.plistutil import plisttype_to_pytype +from collections import defaultdict +from PIL import Image +from os.path import getmtime +import re +from logging import getLogger; log = getLogger('emoticons') + + +DEFAULT_PACK = u'MSN Messenger' +def _default_emoticon_pack(): + import common + return common.pref('appearance.conversations.emoticons.pack', type = unicode, default = DEFAULT_PACK) + +class EmoticonPack(object): + emoticon_loaders = [] + + def __repr__(self): + return '<%s %r from %r>' % (type(self).__name__, self.name, self.path) + + def __init__(self, path): + self.path = path + self.name = None + self.emoticons = None + self.bitmaps = None + + @classmethod + def register_loader(cls, loader): + cls.emoticon_loaders.append(loader) + + @classmethod + def is_emoticon_dir(cls, path): + loader = cls.get_loader(path) + return loader is not None + + @classmethod + def get_loader(cls, path): + for loader in cls.emoticon_loaders: + if loader.is_emoticon_dir(path): + return loader + + return None + + def load(self): + raise NotImplementedError + + def post_load(self): + # build a regex matching every emoticon + # sort items by length so longer smileys are matched first + for key in list(self.emoticons.keys()): + self.emoticons[key.encode('xml')] = self.emoticons[key] + emotitems = sorted(self.emoticons.items(), key = lambda e: len(e[0]), reverse = True) + + smiley_regex = '|'.join('(?:%s)' % re.escape(smiley) for (smiley, __emoticon) in emotitems) + + # whitespace before, after, or both + + # Facebook's smiley matcher is this: '(?:^|\\s|\'|"|\\.)(' + regexArr.join('|') + ')(?:\\s|\'|"|\\.|,|!|\\?|$)' + # I'd imagine the var regexArr is exactly what we have in smiley_regex. seems like a simple translation to me... + patterns = [r'(?:^|\s)(%s)(?:\s|$)'] + #r'(?:^|\s+)(%s)', + #r'(%s)(?:\s+|$)'] + + compiled_patterns = [re.compile(r % smiley_regex, re.MULTILINE) for r in patterns] + + self.patterns = compiled_patterns + +@EmoticonPack.register_loader +class DigsbyEmoticonPack(EmoticonPack): + @classmethod + def is_emoticon_dir(cls, pth): + emoticons_txt = pth / 'emoticons.txt' + return emoticons_txt.isfile() + + def load(self): + emoticons_txt = self.path / 'emoticons.txt' + if not emoticons_txt.isfile(): + raise Exception("%r not found", emoticons_txt) + + title = self.path.name + emoticons = {} + bitmaps = odict() + + with file(emoticons_txt) as f: + # the first nonblank line is the title + for line in f: + line = line.strip() + if line: + title = line + break + + for line in f: + line = line.strip() + if not line: + continue + + content = line.split() + image_filename, smileys = content[0], content[1:] + + imgpath = self.path / image_filename + if imgpath.isfile(): + for smiley in smileys: + emoticons[smiley.encode('xml')] = S(path = imgpath) + if not imgpath in bitmaps: + bitmaps[imgpath] = [smiley] + else: + bitmaps[imgpath].append(smiley) + + self.name = title + self.emoticons = emoticons + self.bitmaps = bitmaps + + self.post_load() + +@EmoticonPack.register_loader +class PidginEmoticonPack(EmoticonPack): + @classmethod + def is_emoticon_dir(cls, pth): + return (pth / 'theme').isfile() + + def load(self): + theme = self.path / 'theme' + if not theme.isfile(): + return None, None + + smileys = defaultdict(list) + emoticons = {} + bitmaps = odict() + title = self.path.name + + with file(theme) as f: + for line in f: + if line.count('\t') > 0: + seq = filter(None, line.strip().split('\t')) + if len(seq) >= 2: + img, smiley = seq[:2] + imgpath = self.path / img + if imgpath.isfile(): + emoticons[smiley.encode('xml')] = S(path = imgpath) + + if not imgpath in bitmaps: + bitmaps[imgpath] = [smiley] + else: + bitmaps[imgpath].append(smiley) + elif line.count('='): + key, val = line.split('=', 1) + if key.lower() == 'name': + title = val + + self.name = title + self.emoticons = emoticons + self.bitmaps = bitmaps + + self.post_load() + +@EmoticonPack.register_loader +class AdiumPlistEmoticonPack(EmoticonPack): + @classmethod + def is_emoticon_dir(cls, pth): + return (pth / 'Emoticons.plist').isfile() + + def load(self): + emoticons_txt = self.path / 'Emoticons.plist' + if not emoticons_txt.isfile(): + return None, None + + title = self.path.name + with file(emoticons_txt) as f: + plist = tag(f.read()) + toplevel = plist._children[0] + converted = plisttype_to_pytype(toplevel) + emoticons = odict() + bitmaps = odict() + + for img_name, info in sorted(converted['Emoticons'].items()): + smileys = info['Equivalents'] + imgpath = self.path / img_name + if imgpath.isfile(): + for smiley in smileys: + if not smiley: + continue # for badly formed plists with + + emoticons[smiley] = S(path = imgpath) + if not imgpath in bitmaps: + bitmaps[imgpath] = [smiley] + else: + bitmaps[imgpath].append(smiley) + + self.name = title + self.emoticons = emoticons + self.bitmaps = bitmaps + + self.post_load() + +@EmoticonPack.register_loader +class AdiumFilesysEmoticonPack(EmoticonPack): + @classmethod + def is_emoticon_dir(cls, pth): + return any(x.isdir() for x in pth.glob('*.emoticon')) + + def load(self): + + dirs = [x for x in self.path.glob('*.emoticon') if x.isdir()] + emoticons = odict() + bitmaps = odict() + title = self.path.name + for dir in dirs: + imgpath = dir / 'Emoticon.gif' + if not imgpath.isfile(): + continue + + smileys_path = (dir / 'TextEquivalents.txt') + if not smileys_path.isfile(): + continue + + smileys = smileys_path.lines() + + for smiley in smileys: + smiley = smiley.strip() + if not smiley: + continue + + emoticons[smiley] = S(path = imgpath) + + if not imgpath in bitmaps: + bitmaps[imgpath] = [smiley] + else: + bitmaps[imgpath].append(smiley) + + self.name = title + self.emoticons = emoticons + self.bitmaps = bitmaps + + self.post_load() + +def get_emoticon_dirs(): + import stdpaths + + dirs = [] + emotidir = 'emoticons' + for dir in (stdpaths.userdata, stdpaths.config, resdir()): + pth = dir / emotidir + try: + if not pth.isdir(): + pth.makedirs() + + except Exception: + print_exc() + else: + dirs.append(pth) + + return dirs + +def load_emoticons(emoticon_pack = None): + if emoticon_pack is None: + emoticon_pack = _default_emoticon_pack() + + log.info('load_emoticons: %s', emoticon_pack) + + emoticons = None + for emoticon_dir in get_emoticon_dirs(): + emoticon_packdir = emoticon_dir / emoticon_pack + loader = EmoticonPack.get_loader(emoticon_packdir) + if loader is not None: + pack = loader(emoticon_packdir) + pack.load() + return pack + + log.info('emoticon pack %r could not be found', emoticon_pack) + return None + +_emoticons = sentinel +_emoticons_pack = sentinel + +def first_non_none(seq): + for i, elem in enumerate(seq): + if elem is not None: + return i + + raise AssertionError + +def repl(emoticons): + def _repl(m, e=emoticons): + (x, y), (i, j) = m.span(), m.span(1) + s = m.string + emot = e[s[i:j]] + size = imgsize(emot.path) + + emottext = s[i:j].encode('xml') + replacement = '%s' % \ + (emot.path.url(), size[0], size[1], emottext, emottext) + + return ''.join([s[x:i], replacement, s[j:y]]) + + return _repl + +def load_pack(emoticon_pack): + if emoticon_pack is None: + emoticon_pack = _default_emoticon_pack() + + if not isinstance(emoticon_pack, basestring): + raise TypeError('emoticon_pack must be a string') + + global _emoticons, _emoticons_pack + if _emoticons is sentinel or emoticon_pack != _emoticons_pack: + _emoticons = load_emoticons(emoticon_pack) + _emoticons_pack = emoticon_pack + + return _emoticons + +def apply_emoticons(s, emoticon_pack = None): + load_pack(emoticon_pack) + + if _emoticons is None: + return s + + for p in _emoticons.patterns: + s = p.sub(repl(_emoticons.emoticons), s) + s = p.sub(repl(_emoticons.emoticons), s) + + return s + +def get_emoticon_bitmaps(emoticon_pack = None): + if emoticon_pack is None: + emoticon_pack = _default_emoticon_pack() + load_pack(emoticon_pack) + return list(_emoticons.bitmaps.items()) + +def findsets(): + '''Returns path objects to all emoticon sets.''' + + sets = [] + for d in get_emoticon_dirs(): + for subdir in d.dirs(): + if EmoticonPack.is_emoticon_dir(subdir): + pack = EmoticonPack.get_loader(subdir)(subdir) + pack.load() + sets.append(pack) + log.info('discovered emoticon directory: %r', subdir) + + log.info('all found emoticon sets: %r', sets) + return sets + +def imgsize(p, _cache = {}): + key = (p, getmtime(p)) + + try: + return _cache[key] + except KeyError: + size = Image.open(p).size + return _cache.setdefault(key, size) + +if __name__ == '__main__': + from path import path + def resdir(): return path('../../../res') +else: + def resdir(): + from gui import skin + return skin.resourcedir() + +if __name__ == '__main__': + print load_emoticons_pidgin(path('../../../res/emoticons/sa')) + #global _emoticons + #from pprint import pprint + #print apply_emoticons('>(') + #pprint(_emoticons) + + #s='one two three' + #from util import soupify + + #soup = soupify(s) + #print soup.find(text = None) + diff --git a/digsby/src/gui/imwin/imhub.py b/digsby/src/gui/imwin/imhub.py new file mode 100644 index 0000000..bb027c1 --- /dev/null +++ b/digsby/src/gui/imwin/imhub.py @@ -0,0 +1,722 @@ +''' + +Logic for routing incoming messages to the correct IM window, and to the +notification system. + +SMS messages are a special case, see window_for_sms + + on_message + / \ +fire_notifications window_for -> (resulting ImWin).message + / \ + window_for_sms create_imwin +''' +from __future__ import with_statement + +import hooks +import wx +from wx import GetTopLevelWindows, GetTopLevelParent, WXK_CONTROL, GetKeyState, CallLater +FindFocus = wx.Window.FindFocus +import threading + +import gui.native.helpers as helpers +from util import strip_html2, odict, traceguard +from util.primitives.funcs import Delegate +from util.primitives.fmtstr import fmtstr +from util.lrucache import lru_cache +from common.notifications import fire +from common.sms import normalize_sms as smsize +from common.sms import validate_sms +from common import profile, pref +from gui.imwin.imwin_gui import ImWinPanel +from traceback import print_exc +from logging import getLogger; log = getLogger('imhub') +LOG = log.debug +from gui.uberwidgets.formattedinput import get_default_format + +strip_html2 = lru_cache(80)(strip_html2) + +def is_system_message(messageobj): + return messageobj is not None and messageobj.buddy is None and \ + not getattr(messageobj, 'system_message_raises', False) + +def is_announcement(message): + buddy = getattr(message, 'buddy', None) + return buddy is not None and buddy.service == 'digsby' and buddy.name == 'digsby.org' + +def on_status(status, on_done=None): + for imwin in ImWinPanel.all(): + if imwin.Buddy == status.buddy: + wx.CallAfter(imwin.show_status, status, on_done) + break + +def show_announce_window(message): + from gui.imwin.imwin_gui import AnnounceWindow + announce = AnnounceWindow(wx.FindWindowByName('Buddy List')) + announce.message(message) + wx.CallAfter(announce.Show) + +def show_announce_window_After(message): + wx.CallAfter(show_announce_window, message) + +hooks.register('digsby.server.announcement', show_announce_window_After) + +def pre_message_hook(message): + if is_announcement(message): + # only show announcement windows to users with accounts (so new users + # aren't shown an update changelog on first login) + if profile.account_manager.all_accounts: + show_announce_window(message) + + # If the user is new, ignore the message. + return False + +im_show = Delegate() + +def frame_show(frame, show, no_activate=False): + assert isinstance(show, bool) + + # wxMSW has a bug where frames created with wxMINIMIZE and then shown with ::Show() + # do not have frame.m_iconize = true, and therefore don't send a wxIconizeEvent + # for the first restore. showing the window with Iconize keeps m_iconized = true + # and avoids the problem. + if show and getattr(frame, '_starting_minimized', False): + frame.Iconize(True) + + # there's a wx bug where Iconize(True) to show a window as minimized does not set IsShown() + # to be true. so call Show(True) manually here. + frame.Show(True) + + frame._starting_minimized = False + else: + if no_activate: + frame.ShowNoActivate(show) + else: + frame.Show(show) + + +def on_message(messageobj = None, convo = None, raisenow = False, meta = None, mode = 'im', firenots=True): + ''' + Called with IM messages or conversations. + + messageobj has "buddy" and "message" and "conversation" (at least) + ''' + thread_check() + from gui.imwin.imtabs import ImFrame + + convo = messageobj.conversation if messageobj is not None else convo + sms = messageobj.get('sms', False) if messageobj is not None else False + sms = sms or getattr(getattr(convo, 'buddy', None), 'sms', False) + + if pre_message_hook(messageobj) is False: + return + + system_message = is_system_message(messageobj) + + if not raisenow: + should_hide, isnew = hide_message(messageobj, meta, sms, system_message) + if should_hide: + if firenots: + fire_notifications(messageobj, None, isnew, mode, hidden=True) + return + + win, isnew = window_for(convo, meta = meta, sms = sms, + system_message = system_message, + focus = True if raisenow else None, + mode = mode) + if win is None: + return + + # inform notification system + if firenots: + fire_notifications(messageobj, win, isnew, mode) + + flashnow = True + frame = GetTopLevelParent(win) + focusedImFrame = isinstance(focused_top(), ImFrame) + +# print 'frame ', frame +# print 'frame.IsShown()', frame.IsShown() +# print 'focused_top ' , focused_top() +# print 'raisenow ', raisenow +# print 'focusedImFrame ', focusedImFrame + global im_show + + # Find out if the window's tab is currently being dragged + if isnew: + if raisenow and not focusedImFrame: + im_show += lambda: log.info('calling frame.Show on frame at %r', frame.Rect) + im_show += lambda: wx.CallAfter(lambda: frame_show(frame, True)) + else: + if not frame.IsShown(): + im_show += lambda: log.info('calling frame.ShowNoActivate on frame at %r', frame.Rect) + im_show += lambda: wx.CallAfter(lambda: frame_show(frame, True, no_activate=True)) + + if not focusedImFrame and (raisenow or (isnew and 'stealfocus' == new_action())): + im_show += lambda: log.info('raising existing IM frame at %r', frame.Rect) + im_show += lambda: raise_imwin(frame, win) + else: + if flashnow and messageobj is not None and not win.IsActive(): + bud = messageobj.buddy + if bud is not None and bud is not messageobj.conversation.self_buddy: + im_show += lambda: wx.CallAfter(win.Notify) + + if not (pref('fullscreen.hide_convos', True) and helpers.FullscreenApp()): #@UndefinedVariable + im_show.call_and_clear() + else: + log.info('im_hub.on_message ignoring for now because of fullscreen (delegate is %d messages long)', len(im_show)) + helpers.FullscreenAppLog() + + # hand the message object off to the IM window + win.message(messageobj, convo, mode, meta) + + return win + +def focused_top(): + focused = FindFocus() + return focused.Top if focused is not None else None + +def raise_imwin(frame, imwin): + 'Obtrusively raise an ImWin/ImFrame pair.' + + log.info('raise_imwin: %r %r', frame, imwin) + + if frame.IsIconized(): + frame.Iconize(False) + + #HAX: For some reason Iconize(False) doesn't throw an IconizeEvent, fixed + event = wx.IconizeEvent(frame.Id, False) + frame.AddPendingEvent(event) + + frame.ReallyRaise() + + tab = imwin.Tab + if tab is not None: tab.SetActive(True) + +def open(idstr): + '''Opens an IM window (or raises an existing one) for a buddy id string.''' + + contact = profile.blist.contact_for_idstr(idstr) + if contact is not None: + return begin_conversation(contact) + +def begin_conversation(contact, mode = 'im', forceproto = False): + ''' + Invoked for actions like double clicking a buddy on the buddylist, + i.e. starting a conversation with someone without messaging them + yet. + ''' + thread_check() + + from contacts.metacontacts import MetaContact, OfflineBuddy + if isinstance(contact, OfflineBuddy): + log.info('cannot open an IM window for OfflineBuddy %r', contact) + return + + # are we asking for a metacontact? + meta = contact if isinstance(contact, MetaContact) else None + + # decide on a to/from + if forceproto: + proto = contact.protocol + else: + contact, proto = profile.blist.get_from(contact) + + if proto is None: + log.info('cannot open an IM window for %r, no compatible protocols?', contact) + return + + convo = proto.convo_for(contact) + + # open the window + if contact.sms and not profile.blist.on_buddylist(contact): + mode = 'sms' + + # pop any hidden_messages from this contact + pop_any_hidden(contact) + + return on_message(convo = convo, raisenow = True, meta = meta, mode = mode) + +def window_for(convo, meta = None, sms = False, system_message = False, focus = None, mode = 'im'): + win, meta = find_window_for(convo, meta, sms, system_message) + + if win is not None: + return win, False + + if sms and not profile.blist.on_buddylist(convo.buddy): + # is this SMS number associated with a buddy? then open a window for + # that buddy, not for the number + convo = window_for_sms(convo) + + if system_message: + return None, None + + win = create_imwin(convo, meta, sms, focus, mode) + return win, True + +def find_window_for(convo, meta = None, sms = False, system_message = False): + ''' + Finds the best IM tab for the given conversation. + + Returns two objects, an ImWin and a boolean value indicating if the window + is "new" or not. + + If "system_message" is True, then a window will not be created on demand. + ''' + + thread_check() + + # the "meta" argument signifies to look only for the specified metacontact. + if meta is None: + metas = profile.metacontacts.forbuddy(convo.buddy) if not convo.ischat else [] + if metas: + meta = list(metas)[0] + else: + metas = [meta] + + return search_for_buddy(metas, convo, sms), meta + +def search_for_buddy(metas, convo, sms): + for win in ImWinPanel.all(): + with traceguard: + c = win.Conversation + + # already talking to this buddy? + if c is convo: + LOG('direct conversation object match: win: %r, convo: %r', win, convo) + return win + + # is this an SMS message? + if validate_sms(convo.buddy.name): + for num in win.SMS: + if validate_sms(num): + if smsize(num) == smsize(convo.buddy.name): + return win + + # chat messages will go only to windows with matching conversation objects. + if convo.ischat != win.convo.ischat: + continue + + # is there a window already open talking to another contact in this + # contact's metacontact? + winbud = win.Buddy + if winbud is None: + continue + + for meta in metas: + for contact in meta: + if winbud == contact: + LOG('matched %r with %r', winbud, contact) + return win + + # a looser match--one that might not match "From:" but only "To:" + for contact in meta: + if winbud.name == contact.name and winbud.protocol.name == contact.protocol.name: + LOG('loosely matched %r with %r', winbud, contact) + return win + + if winbud.info_key == convo.buddy.info_key: + return win + +def window_for_sms(convo): + ''' + For a conversation with an SMS number, looks up contact infos for a buddy + that matches and returns a conversation with that buddy. + ''' + log.info('window_for_sms: %r', convo) + thread_check() + + buddy_sms = smsize(convo.buddy.name) + + keys = [] + + # 'aim_dotsyntax1': {'alias': '', + # 'sms': [u'4567891000', u'17248406085']}, + for infokey, infodict in profile.blist.info.iteritems(): + try: + sms_numbers = infodict['sms'] + except KeyError: + pass + else: + for s in list(sms_numbers): + try: + sms = smsize(s) + except ValueError: + log.critical("invalid SMS number in infodict[%r]['sms']: %r", infokey, s) + sms_numbers.remove(s) + else: + if buddy_sms == sms: + keys += [infokey] + if not keys: + log.info('no matching sms numbers found') + return convo + + conn = convo.protocol + + for key in keys: + if key.startswith('Metacontact #'): + continue #TODO: metacontact-sms association + + buddyname, proto = info_key_tuple(key) + + #TODO: use something SERVICE_MAP in buddyliststore.py to make sure + # digsby/jabber and aim/icq work correctly. + if conn.protocol == proto and conn.has_buddy(buddyname): + return conn.convo_for(conn.get_buddy(buddyname)) + + return convo + +def new_action(): + return pref('conversation_window.new_action') + +def create_imwin(convo, meta, sms, focus = None, mode = 'im'): + ''' + Logic for where to place a new IM tab. + + Spawns a a new ImWin object, placing it as a new tab in _some_ window + somewhere, and returns the ImWin. + ''' + thread_check() + + from gui.imwin.imtabs import ImFrame + f = None + + hooks.notify('imwin.created') + + focus = new_action() == 'stealfocus' if focus is None else focus + + # if tabs are enabled, search for the oldest living ImFrame and place + # the new message there--unless CTRL is being held down. + ctrlDown = pref('messaging.tabs.ctrl_new_window', True) and GetKeyState(WXK_CONTROL) + + if not ctrlDown and pref('messaging.tabs.enabled', True): + for win in GetTopLevelWindows(): + if isinstance(win, ImFrame): + f = win + if f.IsActive(): + focus = False + break + + # if the focused control is an IM win's input box, don't steal focus. + if isinstance(focused_top(), ImFrame): + focus = False + + if getattr(wx.Window.FindFocus(), 'click_raises_imwin', False) and wx.LeftDown(): + focus = True + + # if we haven't found an ImFrame to put the tab in, create a new one + if f is None: + if pref('messaging.tabs.enabled', True): + id = '' + else: + id = meta.idstr() if meta is not None else convo.buddy.idstr() + f = ImFrame(startMinimized = not focus, posId = id) + + w = ImWinPanel(f) + + if convo is not None: + w.set_conversation(convo, meta) + + if focus: + global im_show + im_show += lambda: raise_imwin(f, w) + im_show += lambda: w.FocusTextCtrl() + + tab = f.AddTab(w, focus = focus) + # NOTE: the native IM window doesn't use Page objects so we need to check + # for it before adding to it. + # FIXME: tab.OnActive seems to always be called by tab.SetActive, and tab.SetActive + # also calls FocusTextCtrl on its text control, so is this needed still? + if hasattr(tab, "OnActive"): + tab.OnActive += w.FocusTextCtrl + + hooks.notify('digsby.statistics.imwin.imwin_created') + + return w + + + +def fire_notifications(msg, win, isnew, mode, hidden=False): + ''' + Relays message information to the notifications system, for things like + popups and sound effects. + + msg a message object storage + win the ImWin about to show this message + isnew a bool indicating if the ImWin is "new" + ''' + + if msg is None or msg.buddy is None: return [] + + convo = msg.conversation + bud = msg.buddy + + def stop_notify(win=win): + if win: + try: + win.Unnotify() + except wx.PyDeadObjectError: + pass + else: + if not win.Top.AnyNotified and pref('conversation_window.notify_flash'): + win.Top.StopFlashing() + if hidden and pref('messaging.popups.close_dismisses_hidden', False): + _remove_hidden_message(bud, msg) + + if msg.get('content_type', 'text/html') in ('text/html', 'text/xhtml'): + try: + popup_message = strip_html2(msg.message).decode('xml') + except Exception: + print_exc() + popup_message = msg.message + else: + popup_message = msg.message + + # decide on options to pass to the popup + fire_opts = dict(buddy = bud, + onuserclose = stop_notify) + + ischat = convo is not None and convo.ischat + if ischat: + from gui import skin + fire_opts.update(header = _('Group Chat ({chat.chat_member_count:d})').format(chat=convo), + msg = _('{alias:s}: {message:s}').format(alias=bud.alias, message=popup_message), + icon = skin.get('ActionsBar.Icons.RoomList', None), + popupid = 'chat!!!%r!!!%r' % (convo.protocol.name, convo.chat_room_name)) + else: + fire_opts.update(msg = popup_message, + icon = bud.buddy_icon if bud is not None else convo.icon, + popupid = msg.buddy.idstr()) + + + # Clicking on the popup should open that buddy's message window. + # - if there is text entered in the popup and not the IM window, + # copy it there + if bud is not None: + def click_popup(text): + if pop_any_hidden(bud): + return + + if convo.ischat: + on_message(convo = convo, raisenow = True) + else: + begin_conversation(bud) + + if win: + try: + val = win.input_area.Value + except wx.PyDeadObjectError: + pass + else: + if not val: + win.input_area.Value = text + wx.CallAfter(win.TextCtrl.SetInsertionPointEnd) + + fire_opts.update(onclick = click_popup) + + notification = _get_notification_types(bud, convo, win, hidden, isnew) + + def send_from_popup(text, options, convo = convo, win = win, opts = fire_opts.copy()): + if not text: return + + CallLater(200, stop_notify) + + # if the window's still around, use its formatting + convo.send_message(fmtstr.singleformat(text, format=_get_format(win))) + + if not wx.GetKeyState(wx.WXK_CONTROL): + # returning a string appends it to the popup's content. + return '> ' + text + + # returning None results in the popup closing + + fire_opts['input'] = send_from_popup + + return fire(notification, **fire_opts) + +def _get_notification_types(bud, convo, win, hidden, isnew): + # decide which type of message event to fire + if bud is convo.self_buddy: + notification = 'message.sent' + elif hidden: + # hidden messages get message.received.hidden, and also .initial if they are new + notification = ['message.received.hidden'] + if isnew: notification.append('message.received.initial') + else: + # New messages have their own event type. + if isnew: + notification = 'message.received.initial' + + # If the IM window isn't the active tab, or the "IM" button isn't the active one, + # then fire a "background" message event. + elif not win or not win.IsActive() or not wx.GetApp().IsActive() or win.Mode not in ('im', 'sms'): + notification = 'message.received.background' + + # Otherwise just use message.received. + else: + notification = 'message.received' + + return notification + +def _get_format(win): + ''' + returns the formatting dictionary for sending a message given an IM + window that may or may not already be destroyed + ''' + + format = None + + if win: + try: + format = win.input_area.Format + except wx.PyDeadObjectError: + pass + except Exception: + print_exc() + + if format is None: + format = get_default_format() + + return format + +def show_info(buddy): + begin_conversation(buddy, mode = 'info') + +def thread_check(): + if threading.currentThread().getName() != 'MainThread': + raise Exception('imhub methods must be called on the main GUI thread') + + +def info_key_tuple(info_key): + i = info_key.find('_') + if i == -1: + assert False, repr(info_key) + return info_key[:i], info_key[i+1:] + +# +# hidden messages +# + +hidden_windows = odict() # { contact: [message1, message2, ..] } + +def hidden_count(): + 'Returns the number of hidden conversation windows.' + + return len(hidden_windows) + +def hidden_convo_contacts(): + 'Returns a list of all contacts with hidden conversations.' + + return hidden_windows.keys() + +def hide_message(messageobj, meta, sms, system_message): + 'Hides a message.' + + if messageobj is None: + return False, False + + convo = messageobj.conversation + + # When messageobj buddy is self_buddy, we're sending a message. If there + # is no existing IM window for the conversation, don't create one. + if convo.self_buddy == messageobj.buddy: + win, meta = find_window_for(convo, meta, sms, system_message) + if not get_any_hidden(convo.buddy, pop=False): + return win is None, False + + if new_action() != 'hide': + return False, False + + win, meta = find_window_for(convo, meta, sms, system_message) + + if win is not None: + return False, False + + ident = (meta or convo.buddy).info_key + + if ident in hidden_windows: + hidden_windows[ident].append(messageobj) + isnew = False + else: + hidden_windows[ident] = [messageobj] + isnew = True + + _notify_hidden() + return True, isnew + +def pop_all_hidden(): + 'Display all hidden conversations.' + + for contact in list(hidden_windows.keys()): + pop_any_hidden(contact) + +def pop_any_hidden(contact, notify=True): + # the quiet_log_messages is checked by MessageArea when replaying + # log messages to see if any messages should be ignored + from gui.imwin.messagearea import quiet_log_messages + + all_messages = get_any_hidden(contact) + + if not all_messages: + return + + with quiet_log_messages(all_messages): + for messageobj in all_messages: + with traceguard: + on_message(messageobj, raisenow=True, firenots=False) + + if notify: + _notify_hidden() + +def get_any_hidden(contact, pop=True): + keys = hidden_windows.keys() + if not keys: + return [] + + # hidden message may be stored under a metacontact info_key, so look it up here. + contacts = set() + if not isinstance(contact, basestring): + contacts.update(m.info_key for m in + profile.metacontacts.forbuddy(contact)) + + contact = getattr(contact, 'info_key', contact) + contacts.add(contact) + + all_messages = [] + for message_list in hidden_windows.values(): + all_messages.extend(message_list) + + messages = [] + for c in keys: + if c in contacts: + if pop: + msgs = hidden_windows.pop(c, []) + else: + msgs = hidden_windows.get(c, []) + + messages.extend(msgs) + + return messages + +def _remove_hidden_message(contact, message): + contact = getattr(contact, 'info_key', contact) + + try: + messages = hidden_windows[contact] + except KeyError: + return False + else: + try: + messages.remove(message) + except ValueError: + return False + else: + if len(messages) == 0: + hidden_windows.pop(contact) + _notify_hidden() + + return True + +def _notify_hidden(): + hooks.notify('digsby.im.message_hidden', hidden_windows) + diff --git a/digsby/src/gui/imwin/imtabs.py b/digsby/src/gui/imwin/imtabs.py new file mode 100644 index 0000000..e2bf6be --- /dev/null +++ b/digsby/src/gui/imwin/imtabs.py @@ -0,0 +1,759 @@ +''' +imtabs.py + +Main frame for the tabbed IM window implementation. In order to support the ability to switch between +native and skinned IM windows based on appearance.skin, we needed a design that would allow the ImFrame's +inheritence hierarchy to remain the same while having different implementations for its guts. + +So now, ImFrame contains common logic shared by both native and skinned implementations. When loaded, +based on the appearance.skin setting, it will load the platform-specific frame events and notebook +by loading an event handler object (so we can handle events on the frame without binding directly +to ImFrame) and a notebook panel object needed for the selected version. + +So, when adding code, here is where things go: + +- Common code goes to ImFrame + +- Code related to frame events and updating frame elements: + - Skinned: SkinnedIMFrameEventHandler + - Native: NativeIMFrameEventHandler +- Code that is specific to the IM window notebook implementation: + - Skinned: SkinnedNotebookPanel + - Native: NativeNotebookPanel + +Anything else, that may be shared by new notebook-based UIs, should of course go into UberBook or +DigsbyFlatNotebook. + +''' +from __future__ import with_statement +from wx import PyDeadObjectError +try: _ +except: import gettext; gettext.install('Digsby') + +import wx +import hooks +from wx.lib import pubsub + +import config + +from gui import skin +from gui.uberwidgets.uberbook.tabmanager import TabWindowManager +from gui.uberwidgets.uberbook.UberBook import NoteBook +from gui.toolbox import draw_tiny_text +from PIL import Image +from gui.windowfx import fadein +from common import setpref, pref, profile, bind, prefprop +from util import traceguard, default_timer +from util.primitives.structures import oset +from util.primitives.funcs import Delegate + +from gui.uberwidgets.UberEvents import EVT_TAB_NOTIFIED +from gui.toolbox import saveWindowPos, preLoadWindowPos, snap_pref +from gui.native import memory_event + +IMWIN_STAT_IDLE_TIME = 5*60*1000 #five minutes == no longer engaged even if we still have focus + + +CLOSETABS_TITLE = _('Close IM Window') +CHECKBOX_TEXT = _('Warn me when I attempt to close multiple conversations') +CLOSETABS_MSG = _('You are about to close {num_tabs} conversations. Are you sure you want to continue?') +CLOSE_BUTTON_TEXT = _('Close &tabs') + +WARN_PREF = 'messaging.tabs.warn_on_close' +IMWIN_ALWAYS_ON_TOP_PREF = 'conversation_window.always_on_top' + +def explodeAllWindows(): + # FIXME: if this import is at the module level, we get a recusive import loop w/ImFrame + from gui.imwin.imtabs import ImFrame + newtabs = 0 + for win in (w for w in wx.GetTopLevelWindows() if isinstance(w, ImFrame)): + # create a new IM window for all tabs but the first + for page in win.notebook.Pages()[1:]: + newtabs += 1 + + win.notebook.Remove(page) + page.tab.Close() + + pos = wx.Point(30, 30) + (20*(newtabs), 20*(newtabs)) + newwin = win.notebook.winman.NewWindow(pos) + newwin.notebook.Insert(page, False) + + +class CloseTabsDialog(wx.Dialog): + ''' + A confirmation dialog for closing an IM window with more than one tab. + ''' + + @property + def WarnMe(self): + return self.panel.warn_cb.Value + + def __init__(self, parent, num_tabs, warn_value = True): + wx.Dialog.__init__(self, parent, title = CLOSETABS_TITLE) + + self.panel = CloseTabsPanel(self, num_tabs, warn_value) + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.panel, 1, wx.EXPAND) + + self.Fit() + + +class CloseTabsPanel(wx.Panel): + def __init__(self, parent, num_tabs, warnme_value = True): + wx.Panel.__init__(self, parent) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + + self.warn_cb = warn_cb = wx.CheckBox(self, -1, CHECKBOX_TEXT) + warn_cb.SetValue(warnme_value) + + msgsizer = wx.BoxSizer(wx.VERTICAL) + self.close_msg = wx.StaticText(self, -1, CLOSETABS_MSG.format(num_tabs=num_tabs)) + msgsizer.Add(self.close_msg, 0, wx.EXPAND | wx.SOUTH, 8) + msgsizer.Add(warn_cb, 0, wx.EXPAND) + + h = wx.BoxSizer(wx.HORIZONTAL) + self.bitmap = wx.ArtProvider.GetBitmap(wx.ART_QUESTION) + self.bitmap.SetMaskColour(wx.BLACK) + h.Add((self.bitmap.Width, self.bitmap.Height), 0, wx.EXPAND | wx.ALL, 9) + h.Add(msgsizer, 0, wx.EXPAND | wx.ALL, 12) + + close_button = wx.Button(self, wx.ID_OK, CLOSE_BUTTON_TEXT) + close_button.SetDefault() + close_button.SetFocus() + + cancel_button = wx.Button(self, wx.ID_CANCEL, _('&Cancel')) + + buttonsizer = wx.BoxSizer(wx.HORIZONTAL) + buttonsizer.AddStretchSpacer(1) + buttonsizer.Add(close_button, 0, wx.RIGHT, 6) + buttonsizer.Add(cancel_button) + buttonsizer.AddStretchSpacer(1) + + s = self.Sizer = wx.BoxSizer(wx.VERTICAL) + s.Add(h, 0, wx.EXPAND | wx.ALL) + s.Add(buttonsizer, 0, wx.EXPAND | wx.BOTTOM, 12) + + def OnPaint(self, e): + dc = wx.PaintDC(self) + pos = self.close_msg.Position + dc.DrawBitmap(self.bitmap, pos.x - self.bitmap.Width - 10, pos.y, True) + +from gui.native.toplevel import FlashOnce, Flash + +class TitleBarTimer(wx.Timer): + ''' + Manages titles of tab and window titles for conversations. + + Shows flashing strings like (New IM) and (5 New IMs). + ''' + + shouldFlash = prefprop('conversation_window.notify_flash') + cyclePause = prefprop('conversation_window.unread_cycle_pause') + + + def __init__(self, win, tabs): + + wx.Timer.__init__(self) + + self.win = win + self.tabs = tabs + self.index = 0 + + + + def Start(self): + self.title = self.win.Title + + wx.Timer.Start(self, self.cyclePause) + + def Notify(self): + win = self.win + tabs = self.tabs + + if wx.IsDestroyed(win): + self.Stop() + return + + # (>")>[New IMs]<("<) + if not win.IsActive() and len(tabs): + tabNum = len(tabs) + if self.index >= tabNum: + self.index = 0 + tab = tabs[self.index] + if not wx.IsDestroyed(tab): + win.SetTitle('*' + tab.label1) + self.index += 1 + else: + tabs.remove(tab) + + if self.shouldFlash: + FlashOnce(win) # hack until we figure out how to set the title without clearing the notify state + else: + self.Stop() + + + def Stop(self): + wx.Timer.Stop(self) + if not wx.IsDestroyed(self.win): + self.win.SetTitle(self.title) + self.index = 0 + +class SkinnedNotebookPanel(wx.Panel): + def __init__(self, *args, **kwargs): + preview = kwargs.pop('preview', None) + wx.Panel.__init__(self, *args, **kwargs) + self.notebook = NoteBook(self, skinkey = 'Tabs', preview=preview) + + sz = self.Sizer = wx.BoxSizer(wx.VERTICAL) + sz.Add(self.notebook, 1, wx.EXPAND) + +class SkinnedIMFrameEventHandler(wx.EvtHandler): + ''' + A generic frame that can hold tabs. Sets up the TabManager and WinManager for uberbook + + TODO: move this logic/functionality to uberbook.py where it belongs + ''' + + def __init__(self, frame): + wx.EvtHandler.__init__(self) + + self.frame = frame + self.notebook = self.frame.notebookPanel.notebook + + self.mergetimer = None + self.notifiedtabs = oset() + self.titletimer = TitleBarTimer(self.frame, self.notifiedtabs) + + self.BindEventsToFrame() + + def BindEventsToFrame(self): + Bind = self.frame.Bind + Bind(wx.EVT_CLOSE, self.OnClose) + Bind(wx.EVT_MOVE, self.OnMove) + Bind(wx.EVT_SIZE, self.OnSize) + Bind(wx.EVT_ACTIVATE, self.OnActivate) + Bind(EVT_TAB_NOTIFIED, self.OnTabNotify) + + publisher = pubsub.Publisher() + publisher.subscribe(self.OnPageTitleUpdated, 'tab.title.updated') + publisher.subscribe(self.OnPageIconUpdated, 'tab.icon.updated') + + def OnClose(self, event): + if self.frame.CloseAndSaveState(event): + self.frame.Destroy() + else: + event.Veto() + + def OnActivate(self, e): + # when window activates, focus the IM input area + e.Skip() + if e.GetActive(): + tab = self.notebook.ActiveTab + if tab is not None: + # ensure the active tab becomes unnotified + tab.SetNotify(False) + + # focus the input area + tab.page.Content.FocusTextCtrl() + + if self.titletimer.IsRunning(): + self.titletimer.Stop() + + def OnPageIconUpdated(self, message): + """ + Update the notebook when a convo's icon changes. + """ + + page, icon = message.data + if not self.frame or wx.IsDestroyed(self.frame): + return + + assert getattr(self.frame.notebook, "_name", "") != "[unknown]" + for mypage in self.frame.notebook.Pages(): + # TODO: on windows, this comparison will never be True because + # we are comparing Pages and ImWinPanels. + if mypage == page and self.frame.notebook.ActiveTab == mypage.tab: + self.frame.SetFrameIcon(icon) + + def OnPageTitleUpdated(self, message): + """ + Update the frame and notebook when a convo's name and/or typing status changes + """ + if not self.frame or wx.IsDestroyed(self.frame): + return + + imwin, title, window_title = message.data + + imwin.SetName(title) + + assert getattr(self.frame.notebook, "_name", "") != "[unknown]" + + page = None + for mypage in self.frame.notebook.Pages(): + if mypage.Content is imwin: + page = mypage + + if page is None or page.tab is not self.frame.notebook.ActiveTab: + return + + if window_title is not None: + frame_title = window_title + else: + frame_title = title + + if self.titletimer.IsRunning(): + self.titletimer.title = frame_title + else: + self.frame.SetTitle(frame_title) + + flashTime = prefprop('conversation_window.flash_time') + flashCount = prefprop('conversation_window.flash_count') + + def OnTabNotify(self, event): + tab = event.tab + + if tab.notified: + self.notifiedtabs.add(tab) + notify_unread_message_hook() + elif tab in self.notifiedtabs: + self.notifiedtabs.remove(tab) + notify_unread_message_hook() + return + else: + return + + if not self.frame.cycleTitle: + if self.frame.shouldFlash: + Flash(self.frame, timeout = self.flashTime, count = self.flashCount) + return + + if len(self.notifiedtabs) and not self.frame.IsActive(): + if not self.titletimer.IsRunning(): + self.titletimer.Start() + elif self.titletimer.IsRunning(): + self.titletimer.Stop() + + def OnMove(self, event): + event.Skip(True) + + # check if we need to move to a window underneath + if pref('messaging.tabs.enabled', True): + + mt = self.mergetimer + if mt is None: + self.mergetimer = wx.CallLater(10, self.notebook.StartWindowDrag) + else: + mt.Start(10) + + event.Skip(True) + + def OnSize(self, event): + mt = self.mergetimer + if mt is not None: + # cancel the move check if we're resizing the top left corner + mt.Stop() + + event.Skip(True) + +highlight_color = wx.Color(0xff, 0xd6, 0x48) +_highlight_bitmap = None + +def get_highlight_bitmap(): + global _highlight_bitmap + if _highlight_bitmap is None: + import PIL.Image + _highlight_bitmap = PIL.Image.new('RGBA', (5, 5), (0xe2, 0xd6, 0x8b, 255)).WXB + return _highlight_bitmap + +if config.platform == 'win': + from gui.uberwidgets.uberbook.UberBook import UberBookTabController + from gui.buddylist.renderers import get_buddy_icon + + def icon_for_tab(tab, width, height): + window = tab.Window + if window.ischat: + return skin.get('actionsbar.icons.roomlist') + + buddy = window.Buddy + notified = window.Tab.notified + icon = get_buddy_icon(buddy, size=height, round_size=0, grey_offline=True, meta_lookup=True) + return icon.WXB + + class ImTabController(UberBookTabController): + def GetSmallIcon(self, tab): + bitmap = im_badge(tab.Window) + window = tab.Window + + if not window.ischat and bitmap is None: + bitmap = skin.get('statusicons.' + tab.Window.Buddy.status_orb, None).Resized((16,16)) + elif window.ischat: + bitmap = window.chat_icon + + return wx.IconFromBitmap(bitmap.WXB) if bitmap is not None else wx.IconFromBitmap(wx.NullBitmap) + + def GetIconicHBITMAP(self, tab, width, height): + import cgui + icon = icon_for_tab(tab, width, height) + notified = tab.Window.Tab.notified + + highlight = get_highlight_bitmap() if notified else wx.NullBitmap + + if icon is not None: + return cgui.getBuddyPreview((width, height), icon, highlight) + + + def GetLivePreview(self, tab, rect): + # TODO: we get a black overlay unless we draw a transparent bitmap + # onto a DC onto the bitmap. WTF? + overlay = skin.get('AppDefaults.TaskBarIcon') + bitmap = wx.EmptyBitmap(rect.width, rect.height, False) + dc = wx.MemoryDC(bitmap) + overlay.Resized(1).Draw(dc, wx.Rect(0,0,1,1)) + dc.SelectObject(wx.NullBitmap) + + return bitmap + + + #return self.GetIconicBitmap(tab, rect.width, rect.height) + #return skin.get('serviceicons.digsby').Resized(rect.Size) + +class ImFrame(wx.Frame): + ''' + The frame around conversation tabs. + ''' + + WindowName = u'IM Window' + + def __init__(self, pos = None, size = None, startMinimized = False, posId = ''): + if pref('imwin.ads', type = bool, default = False): + defaultSize = wx.Size(490, 470) + else: + defaultSize = wx.Size(470, 390) + + wininfo, placement = preLoadWindowPos(ImFrame.WindowName, uniqueId = posId, defaultPos = wx.Point(200, 200), defaultSize = defaultSize) + wininfo['style'] |= wx.DEFAULT_FRAME_STYLE + + setPos = pos is not None + setSize = size is not None + + if setPos or setSize: + wininfo['style'] &= ~wx.MAXIMIZE + + if startMinimized: + wininfo['style'] |= wx.ICONIZE + self._starting_minimized = True # see comment in imhub.py's frame_show function + + wininfo['style'] |= wx.FULL_REPAINT_ON_RESIZE + + wx.Frame.__init__(self, parent = None, name = ImFrame.WindowName, **wininfo) + + self.on_engaged_start = Delegate() + self.on_engaged_end = Delegate() + self.on_sent_message = Delegate() + + # FIXME: Currently the IM window appearance is set by a load-time switch, as I want to first test + # to ensure altering appearance.skin for Mac doesn't have other side-effects. + if config.nativeIMWindow: + import gui.imwin.imwin_native + self.notebookPanel = gui.imwin.imwin_native.NativeNotebookPanel(self, -1) + self.eventHandler = gui.imwin.imwin_native.NativeIMFrameEventHandler(self) + else: + preview = None + if config.platform == 'win': + preview = ImTabController + self.notebookPanel = SkinnedNotebookPanel(self, -1, preview=preview) + self.eventHandler = SkinnedIMFrameEventHandler(self) + + from gui.imwin.imwin_ads import construct_ad_panel + ad_panel = construct_ad_panel(self, self.notebookPanel, self.notebookPanel.notebook.did_add) + self.notebook.winman = TabWindowManager(lambda pos, size=None: ImFrame(pos = pos, size=size)) + + if placement is not None and not (setPos or setSize): + with traceguard: + from gui.toolbox import SetWindowPlacement + SetWindowPlacement(self, placement) + + if setPos: + self.Position = pos + if setSize: + self.Size = size + + if not config.nativeIMWindow: + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.posId = posId + + self.EnsureInScreen() + if pos is not None: + wx.CallAfter(self.EnsureNotStacked) + + # obey always on top and snapping prefs + profile.prefs.link(IMWIN_ALWAYS_ON_TOP_PREF, self.on_always_on_top, callnow=not startMinimized) + snap_pref(self) + + self.iconizecallbacks = set() + + Bind = self.Bind + Bind(wx.EVT_ICONIZE, self.OnIconize) + + memory_event() + + def gainfocus(e): + if e.Active: + self._startengage() + else: + self._endengage() + + e.Skip() + + Bind(wx.EVT_ACTIVATE, gainfocus) + + self.register_hooks() + + def register_hooks(self): + if getattr(ImFrame, 'did_register_hooks', False): + return + + ImFrame.did_register_hooks = True + ImFrame.engage_is_idle = False + + def goidle(): + ImFrame.engage_is_idle = True + for win in all_imframes(): win._endengage() + return True + + def unidle(): + ImFrame.engage_is_idle = False + for win in all_imframes(): + if win.IsActive(): + win._startengage() + return True + + def idle_ms(ms_idle_time): + if not ImFrame.engage_is_idle and ms_idle_time > IMWIN_STAT_IDLE_TIME: + goidle() + elif ImFrame.engage_is_idle and ms_idle_time < IMWIN_STAT_IDLE_TIME: + unidle() + return 15*1000 #request check in 15 second intervals + + hooks.register('digsby.app.idle', idle_ms) + + # win7 taskbar hook + import cgui + if config.platform == 'win' and cgui.isWin7OrHigher(): + from gui.native.win.taskbar import set_overlay_icon, get_tab_notebook + def icon_updated(imwin): + get_tab_notebook(imwin.Top).InvalidateThumbnails(imwin) + + def later(): + if wx.IsDestroyed(imwin): return + set_overlay_icon(im_badge(), tlw=imwin.Top) + wx.CallLater(300, later) + + hooks.register('digsby.overlay_icon_updated', icon_updated) + + def _startengage(self): + self._endengage() + #CAS: crossplatform? + self.start_time = default_timer() + self.on_engaged_start() + + def _endengage(self): + if hasattr(self, 'start_time'): + diff = max(int(default_timer() - self.start_time), 0) + del self.start_time + hooks.notify('digsby.statistics.imwin.imwin_engage', diff) + self.on_engaged_end() + + cycleTitle = prefprop('conversation_window.cycle_unread_ims') + shouldFlash = prefprop('conversation_window.notify_flash') + + + def OnIconize(self, event=None): + self._didoniconize = True + if event is not None: + event.Skip() + + if event is not None and not event.Iconized() and not self.OnTop and pref(IMWIN_ALWAYS_ON_TOP_PREF, False): + self.OnTop = True + + for callback in set(self.iconizecallbacks): + try: + callback() + except PyDeadObjectError: + self.iconizecallbacks.remove(callback) + + @property + def notebook(self): + if hasattr(self, "notebookPanel") and self.notebookPanel: + return self.notebookPanel.notebook + + return None + + @property + def AnyNotified(self): + return any(iwin.Tab.notified for iwin in self) + + # FIXME: These iter methods aren't compatible with the native IM Window, and IMHO if we use + # them somewhere we should replace them with a ImFrame.Tabs accessor rather than making the + # frame itself an iterator for its tabs. + def __iter__(self): + if config.nativeIMWindow: + assert True # just assert so we know where we hit this from + return iter(p.Content for p in self.notebook.Pages()) + + def __getitem__(self, n): + if config.nativeIMWindow: + assert True # just assert so we know where we hit this from + return self.notebook.Pages()[n].Content + + def GetTabCount(self): + return self.notebook.GetTabCount() + + def AddTab(self, ctrl, focus = None): + return self.notebook.Add(ctrl, focus = focus) + + @bind('ImFrame.Tabs.CloseIfNotLast') + def CloseTabIfNotLast(self): + if self.notebook.GetTabCount() > 1: + tab = self.notebook.ActiveTab + self.notebook.CloseTab(tab) + + @bind('ImFrame.Tabs.CloseActive') + def CloseActiveTab(self): + tab = self.notebook.ActiveTab + self.notebook.CloseTab(tab) + + if self.notebook.GetTabCount() < 1: + self.Close() + + @bind('ImFrame.Tabs.NextTab') + def NextTab(self): + self.notebook.NextTab() + + @bind('ImFrame.Tabs.PrevTab') + def PrevTab(self): + self.notebook.PrevTab() + + @bind('ImFrame.ChatWindow.IncreaseTextSize') + def IncreaseTextSize(self): + self.ActiveMessageArea.IncreaseTextSize() + + @bind('ImFrame.ChatWindow.DecreaseTextSize') + def DescreaseTextSize(self): + self.ActiveMessageArea.DecreaseTextSize() + + @bind('ImFrame.ChatWindow.ResetTextSize') + def ResetTextSize(self): + self.ActiveMessageArea.ResetTextSize() + + @property + def ActiveMessageArea(self): + # TODO: no. + return self.notebook.ActiveTab.page.Content.message_area + + def CloseAndSaveState(self, e): + # Confirm multiple tab close + tabcount = self.GetTabCount() + + if e.CanVeto() and tabcount > 1 and pref(WARN_PREF, True): + with CloseTabsDialog(self, tabcount, True) as diag: + diag.CenterOnParent() + res = diag.ShowModal() + + if not diag.WarnMe: + setpref(WARN_PREF, False) + + if res == wx.ID_CANCEL: + return False + + self.Hide() + saveWindowPos(self, uniqueId = self.posId) # Save our window position + + # Call each IMWin's on_close method. + if not config.nativeIMWindow: + for page in self.notebook.Pages(): + page.Children[0].on_close() + + memory_event() + return True + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, id(self)) + + def on_always_on_top(self, val): + 'Invoked when "conversation_window.always_on_top" preference changes.' + + self.OnTop = val + + def UpdateSkin(self): + wx.CallAfter(self.Layout) + + +def newIMWindow(pos, size): + win = ImFrame(pos, size) + win.Show(False) + fadein(win,'normal') + return win + +def all_imframes(): + return [win for win in wx.GetTopLevelWindows() + if not wx.IsDestroyed(win) and isinstance(win, ImFrame)] + + +def notify_unread_message_hook(): + hooks.notify('digsby.im.unread_messages_changed') + +def all_unread_convos(): + imwins = [] + + for imframe in all_imframes(): + imwins.extend(tab.page.panel for tab in imframe.eventHandler.notifiedtabs) + + return imwins + +def im_badge(specific_page=None): + '''typing status and unread count badge for Win7 taskbar''' + + imwins = [] + + typing_statuses = dict((s, i) for i, s in enumerate([None, 'typed', 'typing'])) + max_typing = None + unread_count = 0 + needs_bubbles = False + + if specific_page is None: + for imframe in all_imframes(): + notified_wins = set(tab.page.panel for tab in imframe.eventHandler.notifiedtabs) + + for page in imframe.notebook.Pages(): + imwin = page.panel + if typing_statuses[imwin.typing] > typing_statuses[max_typing]: + max_typing = imwin.typing + + if imwin in notified_wins: + unread_count += 1 + else: + max_typing = specific_page.typing + needs_bubbles = specific_page.Notified + + bubble_icon = None + if max_typing is not None: + bubble_icon = skin.get('statusicons.' + max_typing, None) + if bubble_icon is None and (unread_count or (specific_page is not None and needs_bubbles)): + bubble_icon = skin.get('AppDefaults.UnreadMessageIcon', None) + if bubble_icon is not None: + bubble_icon = bubble_icon.PIL.ResizeCanvas(16, 16) + if unread_count: + if bubble_icon is None: + bubble_icon = Image.new('RGBA', (16, 16)) + bubble_icon = draw_tiny_text(bubble_icon, str(unread_count)) + if specific_page is None and bubble_icon is not None: + bubble_icon = bubble_icon.WXB + + return bubble_icon + +from gui.input import add_class_context; add_class_context(_('IM Windows'), 'ImFrame', cls = ImFrame) + + diff --git a/digsby/src/gui/imwin/imwin_ads.py b/digsby/src/gui/imwin/imwin_ads.py new file mode 100644 index 0000000..5059df7 --- /dev/null +++ b/digsby/src/gui/imwin/imwin_ads.py @@ -0,0 +1,703 @@ +''' +im window ad +''' + +import sys +import traceback +import wx +import cgui +import simplejson +import random +from time import time +from gui.browser.webkit import WebKitWindow +from common import pref, profile +from util.primitives.funcs import Delegate + +from logging import getLogger; log = getLogger('imwin_ads') +from util.net import UrlQuery + +# number of minutes which must elapse with no IM windows open for us to clear cookies on the next IM window open +AD_COOKIE_CLEAR_MINS = 60 + +IMFRAME_MINSIZE = (320, 220) +IMFRAME_WITH_AD_MINSIZE_H = (490, 350) +IMFRAME_WITH_AD_MINSIZE_V = (320, 397) +SHOULD_ROTATE = True +PREF_FLASH_DISABLED = 'imwin.ads_disable_flash' + +CAMPAIGNS = { + # version controlled at + # svn.tagged.com/web/cool/www/digsbytag.html + 'tagged':'http://www.tagged.com/digsbytag.html', + # SVN/dotSyntax/s3/trunk/serve.digsby.com/rubicon.html + 'rubicon':'http://serve.digsby.com/rubicon.html', + # not under VCS + 'rubicon_vertical':'http://serve.digsby.com/rubicon2.html' +} + +def get_ad_campagin(): + if _adposition() in ('left', 'right'): + return 'rubicon_vertical' + else: + return 'tagged' + +PREF_AD_POSITION = 'imwin.ads_position' +def _adposition(): + return pref(PREF_AD_POSITION, default='bottom') + +PREF_ADS_ENABLED = 'imwin.ads' +def _adsenabled(): + return pref(PREF_ADS_ENABLED, default=False) + + +AD_PANEL_MINSIZE_H = (466, 58) +AD_PANEL_MINSIZE_V = (-1, -1) # (102, 384) + +AD_PANEL_MAXSIZE_H = (728, 90) +AD_PANEL_MAXSIZE_V = (160, 600) + +allowed_trigger_modes = ('focus', 'sendim') +allowed_time_modes = ('real', 'engagement') + +ad_scenarios = [ + (120, 'real', 'focus'), + #(180, 'real', 'focus'), + #(240, 'real', 'focus'), + #(60, 'engagement', 'focus'), + #(90, 'engagement', 'focus'), + #(120, 'engagement', 'focus'), + #(180, 'engagement', 'focus'), + #(120, 'real', 'sendim'), + #(180, 'real', 'sendim'), + #(240, 'real', 'sendim'), + #(60, 'engagement', 'sendim'), + #(90, 'engagement', 'sendim'), + #(120, 'engagement', 'sendim'), + #(180, 'engagement', 'sendim'), +] + +assert all(isinstance(s[0], int) and s[0] > 0 for s in ad_scenarios) +assert all(s[1] in allowed_time_modes for s in ad_scenarios) +assert all(s[2] in allowed_trigger_modes for s in ad_scenarios) + +def choose_ad_scenario(): + s = ad_scenarios[:] + random.shuffle(s) + return s[0] + +class AdRotater(object): + + def __repr__(self): + from gui.imwin.imtabs import ImFrame + return '' % (self.timer_secs, self.time_mode, self.trigger_mode, self.has_focus, ImFrame.engage_is_idle, self._engagement()) + + def __init__(self, timer_secs, time_mode, trigger_mode, current_time_func=time): + assert isinstance(timer_secs, int) + assert trigger_mode in allowed_trigger_modes + assert time_mode in allowed_time_modes + + self.has_focus = False + + self.timer_secs = timer_secs + self.time_mode = time_mode + self.trigger_mode = trigger_mode + + self.scenario_identifier = '%s_%s_%s' % (timer_secs, time_mode, trigger_mode) + + assert hasattr(current_time_func, '__call__') + self._get_time = current_time_func + + self._reset_time(start=False) # the last UNIX time we showed an ad ( = now). + self.on_reload = Delegate() + + if self.trigger_mode == 'focus': + self.wx_timer = wx.PyTimer(self._on_wxtimer) + self.wx_timer.StartRepeating(1000) + + def _on_wxtimer(self): + if not self.has_focus: + return + + if (self.time_mode == 'engagement' and not self.timer.paused) or \ + self.time_mode == 'real': + self.trigger_event('focus') + + def trigger_event(self, event): + if self.should_rotate(event): + self.on_reload() + + def should_rotate(self, trigger_event): + '''Returns True if enough time has passed to show a new ad. Resets + internal timers if that is the case.''' + + if not SHOULD_ROTATE: # for debugging + return False + + assert trigger_event in allowed_trigger_modes, '%r not in %r' % (trigger_event, allowed_trigger_modes) + + from gui.imwin.imtabs import ImFrame + if ImFrame.engage_is_idle: + return False + + if self.trigger_mode != trigger_event: + return False + + if not self._enough_time_elapsed(): + return False + + return True + + def pause(self): + if self.time_mode == 'engagement': + return self.timer.pause() + + def unpause(self): + if self.time_mode == 'engagement': + return self.timer.unpause() + + def _reset_time(self, start=True): + self.timer = Timer(get_time_func=self._get_time) + if start: + self.timer.start() + + def _engagement(self): + return self.timer.get_ticks() + + def _enough_time_elapsed(self): + return self._engagement() >= self.timer_secs + +_ad_scenario = None +def ad_scenario(): + global _ad_scenario + if _ad_scenario is None: + _ad_scenario = choose_ad_scenario() + return _ad_scenario + +class AdPanel(WebKitWindow): + def __init__(self, parent, rotater): + self.rotater = rotater + self.refresh_campaign() + + WebKitWindow.__init__(self, parent, simple_events=True) + + self._update_flash_enabled() + profile.prefs.add_gui_observer(self._update_flash_enabled, PREF_FLASH_DISABLED) + + self.set_jsqueue_enabled(False) + self.set_window_open_redirects_to_browser(self._url_callback) + self.SetMinimumFontSize(10) + + # indicates that we've actually arrived at the AD url + self._navigated_to_base_url = False + + self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.OnBeforeLoad) + self.Bind(wx.webview.EVT_WEBVIEW_LOAD, self.on_loading) + + self.update_minsize() + + from gui.browser.webkit import setup_webview_logging + jslog = getLogger('imwin_ads_js') + setup_webview_logging(self, jslog) + + self._did_notify_click = False + self.SetFineGrainedResourceEvents(True) + + def update_minsize(self): + if _adposition() in ('left', 'right'): + self.SetMinSize(AD_PANEL_MINSIZE_V) + self.SetMaxSize(AD_PANEL_MAXSIZE_V) + else: + self.SetMinSize(AD_PANEL_MINSIZE_H) + self.SetMaxSize(AD_PANEL_MAXSIZE_H) + + def refresh_campaign(self): + campaign = get_ad_campagin() + self.ad_url_base = CAMPAIGNS[campaign] + + old_ad_url = getattr(self, 'ad_url', None) + self.ad_url = UrlQuery(self.ad_url_base, + utm_source='digsby_client', + utm_medium='im_window', + utm_content=self.rotater.scenario_identifier, + utm_campaign=campaign, + ) + + if old_ad_url is not None and old_ad_url != self.ad_url: + self._reload_ad() + + def _update_flash_enabled(self, *a): + if not wx.IsDestroyed(self): + flash_enabled = not pref(PREF_FLASH_DISABLED, default=False) + self.WebSettings.SetPluginsEnabled(flash_enabled) + + @property + def URL(self): + return self.RunScript('window.location.href') + + def on_loading(self, e): + try: + url = self.URL + except wx.PyDeadObjectError: + return e.Skip() + + if not url or e.State != wx.webview.WEBVIEW_LOAD_TRANSFERRING: + return e.Skip() + + at_base = url.startswith(self.ad_url_base) + + if at_base: + self._navigated_to_base_url = True + + hijacked_url = getattr(self, '_did_hijack_url', None) + + # if we have never successfully navigated to serve.digsby.com, assume that a proxy + # is blocking access to it + if not at_base and not self._navigated_to_base_url: + log.info('loading blank window b/c caught possible proxy block to url %r', self.ad_url) + self.LoadURL('about:blank') + + elif not at_base and self._navigated_to_base_url and \ + (hijacked_url is None or not urls_have_same_domain(url, hijacked_url)): + self._did_hijack_url = url + log.warning('!!!! escaped from serve.digsby.com: %r', url) + self._url_callback(url) + self.RefreshAd() + + def _url_callback(self, url): + wx.LaunchDefaultBrowser(url) + self.notify_click(url) + + def _is_double_url_call(self, url): + last = getattr(self, '_last_url_launched', None) + new = (url, time()) + if last is not None and url == last[0]: + if abs(new[1] - last[1]) < 200: + return True + self._last_url_launched = new + + def OnBeforeLoad(self, e): + url = e.URL + skip = True + if e.NavigationType == wx.webview.WEBVIEW_NAV_LINK_CLICKED: + e.Cancel() # don't navigate in webkit + self._url_callback(url) + skip = False + elif e.NavigationType == wx.webview.WEBVIEW_NAV_OTHER: + # just catch page requests if we're not in "ad debug mode" + if not _debug_ads(): + self._log_ad_url(url) + elif e.NavigationType == wx.webview.WEBVIEW_NAV_REQUEST: + url = e.URL + # all resource requests can be cancelled when + # SetFineGrainedResourceEvents(True) was called + if self._should_blacklist_ad_url(url): + log.info('ad URL BLACKLISTED, cancelling request %s', url) + e.Cancel() + skip = False + elif _debug_ads(): + self._log_ad_url(url) + + e.Skip(skip) + + def _log_ad_url(self, url): + last = getattr(self, '_last_logged_url', None) + if last is not None and last == url: + del self._last_logged_url + else: + log.info('ad URL %s', url) + self._last_logged_url = url + + blacklisted_urls = set(( + 'http://qnet.hit.gemius.pl/pp_gemius.js', + )) + + def _should_blacklist_ad_url(self, url): + if url in self.blacklisted_urls: + return True + + return False + + def _reload_ad(self): + self._did_notify_click = False + self.rotater._reset_time() + + log.info('Loading ad URL: %r', self.ad_url) + self._navigated_to_base_url = False + self.LoadURL(self.ad_url) + + def notify_click(self, url): + if self._did_notify_click: + return + + self._did_notify_click = True + log.info('notifying ad click: %r', url) + self._track_analytics_event('click') + + def _track_analytics_event(self, action): + assert wx.IsMainThread() + script = '_gaq.push(%s);' % \ + simplejson.dumps(['_trackEvent', self.rotater.scenario_identifier, action]) + print script + result = self.RunScript(script) + print 'RESULT', result + + +GLASS_TRANSPARENT_COLOR = (0, 0, 0) # the color that Vista will turn into glass + +glass = lambda: cgui.isGlassEnabled() and pref('imwin.ads_glass', default=True) + +def construct_ad_panel(self, mainPanel, new_tab_cb, bind_pref=True): + if bind_pref: + def on_ad_pref(src, attr, old, new): + ap = getattr(self, 'ad_panel', None) + if ap is None and _adsenabled(): + with self.Frozen(): + construct_ad_panel(self, mainPanel, new_tab_cb, bind_pref=False) + self.Layout() + else: + self.build_ad_sizer() + + profile.prefs.add_gui_observer(on_ad_pref, PREF_ADS_ENABLED, obj=self) + + if not _adsenabled(): + return + + should_clear_cookies = setup_cookie_timer(self) + + adpos = _adposition() + + borderSize = 1 + extra_top = 8 + + rotater = AdRotater(*ad_scenario()) + self.ad_panel = ad_panel = AdPanel(self, rotater) + if should_clear_cookies: + self.ad_panel.ClearCookies() + self._ad_rotater = rotater + + self.on_engaged_start += rotater.unpause + self.on_engaged_end += rotater.pause + + def on_message(mode, imwin_ctrl): + if mode == 'im': + rotater.trigger_event('sendim') + + self.on_sent_message += on_message + + self._did_show_ad = False + + def check_focus(): + if wx.IsDestroyed(self): + return + + is_foreground = self.Top.IsForegroundWindow() + + rotater.has_focus = is_foreground and not self.IsIconized() + if rotater.has_focus: + if not self._did_show_ad: + self._did_show_ad = True + rotater.on_reload() + else: + rotater.trigger_event('focus') + + wx.CallLater(50, check_focus) + + # listen for when the frame is activated + def on_iconize(e): + e.Skip() + wx.CallAfter(check_focus) + + def on_activate(e): + e.Skip() + wx.CallAfter(check_focus) + if not glass(): + self.Refresh() # the ad background color changes with activation + + self.Bind(wx.EVT_ACTIVATE, on_activate) + self.Bind(wx.EVT_ICONIZE, on_iconize) + + ### + + horiz_padding = 8 + extra_horizontal = 1 + + def build_ad_sizer(adpos=None, layout_now=True): + if wx.IsDestroyed(self): + return + enabled = _adsenabled() + if adpos is None: + adpos = _adposition() + + with self.Frozen(): + self.SetSizer(None) + + if not enabled: + minsize = IMFRAME_MINSIZE + elif adpos in ('left', 'right'): + minsize = IMFRAME_WITH_AD_MINSIZE_V + else: + minsize = IMFRAME_WITH_AD_MINSIZE_H + self.SetMinSize(minsize) + + if enabled: + ad_panel.Show() + + + if not enabled: + ad_panel.Hide() + sz = wx.BoxSizer(wx.VERTICAL) + sz.Add(mainPanel, 1, wx.EXPAND) + elif adpos == 'top': + sz = wx.BoxSizer(wx.VERTICAL) + sz.Add((borderSize, borderSize))# + (0 if glass() else 7))) + sz.Add(ad_panel, 0, wx.ALIGN_CENTER_HORIZONTAL) + sz.Add((borderSize, borderSize+extra_top)) + sz.Add(mainPanel, 1, wx.EXPAND) + elif adpos == 'left': + sz = wx.BoxSizer(wx.HORIZONTAL) + sz.Add((extra_horizontal,1)) + sz.Add(ad_panel, 0, wx.ALIGN_CENTER_VERTICAL) + sz.Add((horiz_padding, 1)) + sz.Add(mainPanel, 1, wx.EXPAND) + elif adpos == 'right': + sz = wx.BoxSizer(wx.HORIZONTAL) + sz.Add(mainPanel, 1, wx.EXPAND) + sz.Add((horiz_padding, borderSize)) + sz.Add(ad_panel, 0, wx.ALIGN_CENTER_VERTICAL) + sz.Add((extra_horizontal, 1)) + else: # == 'bottom': + sz = wx.BoxSizer(wx.VERTICAL) + sz.Add(mainPanel, 1, wx.EXPAND) + sz.Add((borderSize, borderSize+extra_top)) + sz.Add(ad_panel, 0, wx.ALIGN_CENTER_HORIZONTAL) + sz.Add((borderSize, borderSize))# + (0 if glass() else 7))) + + self.SetSizer(sz) + if layout_now: + self.Layout() + + build_ad_sizer(adpos, layout_now=False) + self.build_ad_sizer = build_ad_sizer + + def on_pref_change(src, attr, old, new): + if wx.IsDestroyed(self): + return + build_ad_sizer(_adposition()) + ad_panel.update_minsize() + on_resize() + ad_panel.refresh_campaign() + + + profile.prefs.add_gui_observer(on_pref_change, PREF_AD_POSITION, obj=self) + + if 'wxMSW' in wx.PlatformInfo: + from gui.native.win.winutil import get_frame_color + else: + get_frame_color = lambda active: wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + + def paint(e): + r = mainPanel.Rect + x, y = r.BottomLeft + x += (r.Width - x) / 2 - ad_panel.Size.width / 2 + + dc = wx.AutoBufferedPaintDC(self) + + dc.Pen = wx.TRANSPARENT_PEN + if glass(): + dc.Brush = wx.Brush(wx.Color(*GLASS_TRANSPARENT_COLOR)) + else: + dc.Brush = wx.Brush(get_frame_color(self.IsActive())) + + dc.DrawRectangleRect(self.ClientRect) + + if not _adsenabled(): + if glass(): + cgui.glassExtendInto(self, 0, 0, 0, 0) + return + + if borderSize: + r = ad_panel.Rect + r.y -= borderSize + r.height += 1 + borderSize + r.x -= borderSize + r.width += 1 + borderSize + dc.DrawBitmap(_get_border_image(r.Size), r.x, r.y, True) + + if glass(): + adpos = _adposition() + glass_width = horiz_padding + ad_panel.Size.width + extra_horizontal + glass_height = borderSize*2+extra_top + (extra_top if not glass() else 0) + ad_panel.Size.height + if adpos == 'top': + cgui.glassExtendInto(self, 0, 0, glass_height, 0) + elif adpos == 'left': + cgui.glassExtendInto(self, glass_width, 0, 0, 0) + elif adpos == 'right': + cgui.glassExtendInto(self, 0, glass_width, 0, 0) + else: # 'bottom' + cgui.glassExtendInto(self, 0, 0, 0, glass_height) + + + self.Bind(wx.EVT_PAINT, paint) + + def _ad_panel_shown(): + ad_panel = getattr(self, 'ad_panel', None) + if ad_panel is not None: + return ad_panel.IsShown() + + def on_resize(e=None): + if hasattr(e, 'Skip'): + e.Skip() + + if wx.IsDestroyed(self) or not _ad_panel_shown(): + return + + if _adposition() in ('left', 'right'): + maxw, maxh = AD_PANEL_MAXSIZE_V + cheight = self.ClientSize.height + ad_h = min(maxh, cheight) + if ad_h < maxh: + ad_h -= 2 + zoom = float(ad_h) / maxh + ad_w = zoom*maxw + else: + maxw, maxh = AD_PANEL_MAXSIZE_H + cwidth = self.ClientSize.width + ad_w = min(maxw, cwidth) + if ad_w < maxw: + ad_w -= 2 # leave room for border on sides when smaller + zoom = float(ad_w) / maxw + ad_h = zoom*maxh + + ad_panel.SetMinSize((ad_w, ad_h)) + ad_panel.SetPageZoom(zoom) + ad_panel.Parent.Layout() + ad_panel.Parent.Refresh() + + self.Bind(wx.EVT_SIZE, on_resize) + + def RefreshAd(): + if _ad_panel_shown(): + ad_panel._reload_ad() + wx.CallLater(50, on_resize) + + rotater.on_reload += RefreshAd + ad_panel.OnDoc += on_resize + ad_panel.RefreshAd = RefreshAd + + # new tab callback + + def on_new_tab(*a): + if not getattr(self, '_first_tab_opened', False): + self._first_tab_opened = True + return + + ad_panel.RefreshAd() + new_tab_cb += on_new_tab + + # allow dragging the frame by grabbing the area around the ad. + self.PushEventHandler(cgui.DragMixin(self)) + + +# gdi DrawRect on glass doesn't fill the alpha channel, so use an image +_cached_border_image = (None, None) +_border_color = (93, 108, 122, 255) +def _get_border_image(sz): + global _cached_border_image + w, h = sz + sz = (w, h) + if _cached_border_image[0] == sz: + return _cached_border_image[1] + import PIL + img = PIL.Image.new('RGBA', (w, h), _border_color).WXB + _cached_border_image = (sz, img) + return img + +class Timer(object): + def __init__(self, get_time_func=time): + self._get_time = get_time_func + + self.start_ticks = 0 + self.paused_ticks = 0 + self.paused = False + self.started = False + + def start(self): + self.started = True + self.paused = False + self.start_ticks = self._get_time() + + def stop(self): + self.started = False + self.paused = False + + def pause(self): + if not self.started or self.paused: + return + + self.paused = True + self.paused_ticks = self._get_time() - self.start_ticks + + def unpause(self): + if not self.started or not self.paused: + return + + self.paused = False + self.start_ticks = self._get_time() - self.paused_ticks + self.paused_ticks = 0 + + def get_ticks(self): + if not self.started: + return 0 + + if self.paused: + return self.paused_ticks + else: + return self._get_time() - self.start_ticks + +def _debug_ads(): + opts = getattr(sys, 'opts', None) + return getattr(opts, 'debugads', False) + +def urls_have_same_domain(a, b): + try: + return UrlQuery.parse(a).netloc == UrlQuery.parse(b).netloc + except Exception: + traceback.print_exc_once() + return a == b + +def find_instances(clz): + return [w for w in wx.GetTopLevelWindows() + if isinstance(w, clz)] + +last_imframe_close = None + +def secs_since_last_close(): + if last_imframe_close is not None: + return time() - last_imframe_close + +def should_clear_cookies(): + secs = secs_since_last_close() + return secs is not None and secs > 60 * AD_COOKIE_CLEAR_MINS + +def setup_cookie_timer(imframe): + ''' + keeps track of the time when the last IM window is closed. if a new IM window is opened + some time after that, and enough time has elapsed, then we clear cookies. + + this is so that we have a new session for the ad networks and don't serve + ads on the 'long session time' end of the spectrum too often. + ''' + imclz = imframe.__class__ + + def num_imframes(): + return len(find_instances(imclz)) + + def on_close(e): + global last_imframe_close + e.Skip() + if num_imframes() == 1: + last_imframe_close = time() + + imframe.Bind(wx.EVT_CLOSE, on_close) + + return num_imframes() == 1 and should_clear_cookies() diff --git a/digsby/src/gui/imwin/imwin_ctrl.py b/digsby/src/gui/imwin/imwin_ctrl.py new file mode 100644 index 0000000..be80500 --- /dev/null +++ b/digsby/src/gui/imwin/imwin_ctrl.py @@ -0,0 +1,695 @@ +''' + +IM window logic and interaction + +''' +from __future__ import with_statement + + + +import sys +import wx +from wx import EVT_BUTTON, MessageBox, PyTimer + +from operator import attrgetter +from traceback import print_exc +from logging import getLogger; log = getLogger('imwinctrl'); info = log.info; info_s = getattr(log, 'info_s', log.info) + +from util import traceguard +from common import profile, prefprop, pref +from common.message import Message + +from gui.infobox.htmlgeneration import GetInfo +from imwin_tofrom import IMControl, EmailControl, SMSControl +from gui.toolbox import check_destroyed, calllimit +from common.sms import SMS_MAX_LENGTH + +class ImWinCtrl(object): + ''' + IM window logic. + + - incoming messages and new conversations go to the "message" function + - the on_mode_change[d] functions are called when changing modes + ''' + + def __init__(self): + # "controllers" in charge of the contents of the To/From combos + self.IMControl = IMControl(self, self.capsbar, self.To, self.From) + self.IMControl.OnSelection += self.FocusTextCtrl + self.IMControl.OnSwitchContact += lambda c: self.set_conversation(c.protocol.convo_for(c)) + + self.EmailControl = EmailControl(self.To, self.From) + self.EmailControl.OnLoseFocus += lambda: self._emailpanel.subject_input.SetFocus() + + self.SMSControl = SMSControl(self.To, self.From) + self.SMSControl.OnLoseFocus += self.FocusTextCtrl + + self.controllers = {'im': self.IMControl, + 'email': self.EmailControl, + 'sms': self.SMSControl} + + self.mode = None + self.convo = None + + self.typing = None # buddy's typing state + self.typing_status = None # your typing state + self.typing_timer = None # timer for keeping track of your typing state + self.clear_typing_timer = wx.PyTimer(self.on_clear_typing_timer) + + self.GetButton('im').Bind(EVT_BUTTON, lambda e: self.set_mode('im', toggle_tofrom = True)) + + for mode in ('email', 'sms', 'info'): + self.GetButton(mode).Bind(EVT_BUTTON, lambda e, mode=mode: self.set_mode(mode)) + + self.GetButton('video').Bind(EVT_BUTTON, lambda e: self.on_video()) + + multichat = self.GetButton('multichat') + if multichat is not None: + multichat.Bind(wx.EVT_TOGGLEBUTTON, lambda e: self.toggle_roomlist()) + + send_typing = prefprop('privacy.send_typing_notifications', True) + typed_delay = prefprop('messaging.typed_delay', 5) + + def message(self, messageobj, convo = None, mode = 'im', meta = None): + "Called by imhub.py with incoming messages." + + info('%r', self) + info_s(' messageobj: %r', messageobj) + info(' convo: %r', convo) + info(' mode: %r', mode) + info(' meta: %r', meta) + + assert wx.IsMainThread() + + if messageobj is None: + # starting a new conversation--no message + self.set_conversation(convo) + self.set_mode(mode) + self.IMControl.SetConvo(convo) + if convo.ischat: + self.show_roomlist(True) + + elif (messageobj.get('sms', False) or getattr(convo.buddy, 'sms', None)) and not profile.blist.on_buddylist(convo.buddy): + # an incoming SMS message + if self.convo is None: + self.set_conversation(convo) + + if self.mode != 'sms': + self.set_mode('sms') + + # just show it + self.show_message(messageobj) + else: + convo = messageobj.conversation + + if self.mode is None: + self.set_mode(mode) + self.show_message(messageobj) + self.set_conversation(convo) +# self.IMControl.SetConvo(convo) + + def set_mode(self, mode, toggle_tofrom = False): + with self.Frozen(): + oldmode = getattr(self, 'mode', None) + self.on_mode_change(oldmode, mode, toggle_tofrom) + self.show_controls(mode) + self.on_mode_changed(mode) + + Mode = property(attrgetter('mode'), set_mode) + + def on_mode_change(self, oldmode, mode, toggle_tofrom = False): + 'Invoked before the GUI is shown for a new mode.' + + # To/From showing and hiding + if oldmode != mode and mode in ('email', 'sms'): + self.ShowToFrom(True) + elif mode == 'info': + self.ShowToFrom(False) + + # Enabling/disabling formatting buttons + if mode == 'sms' and oldmode != 'sms': + wx.CallAfter(lambda: self.input_area.tc.SetMaxLength(SMS_MAX_LENGTH)) + elif oldmode == 'sms' and mode != 'sms': + wx.CallAfter(lambda: self.input_area.tc.SetMaxLength(0)) + + # If going to IM, Email, or SMS--show that mode's to/from combo + for m in ('im', 'email', 'sms'): + if mode == m and oldmode != m: + wx.CallAfter(self.controllers[mode].Apply) + break + + # If going to IM mode... + if mode == 'im': + # toggle if clicking IM button + # or + # ask IM control if there is more than one choice for the To/From choices. + if self.convo.ischat: + self.ShowToFrom(False) + elif oldmode != 'im': + self.ShowToFrom(self.IMControl.HasChoices) + elif toggle_tofrom: + self.ShowToFrom(not self.capsbar.ToFromShown) + + + #show = self.IMControl.HasChoices if oldmode != 'im' else not self.capsbar.ToFromShown + #self.ShowToFrom(show) + + if oldmode is not None: + self.GetButton(oldmode).Active(False) + + self.GetButton(mode).Active(True) + + def on_mode_changed(self, mode): + 'Invoked after the GUI is shown for a new mode.' + + if mode in ('im', 'sms'): + self.on_message_area_shown() + if self.IsActive(): + # for some reason, if we don't use wx.CallAfter on Mac, the call happens to early. + if sys.platform.startswith('darwin'): + wx.CallAfter(self.FocusTextCtrl) + else: + self.FocusTextCtrl() + + elif mode == 'info': + self.set_profile_html(self.Buddy) + self.profile_html.SetFocus() # so mousewheel works immediately + + elif mode == 'email': + self._emailpanel.subject_input.SetFocus() + + def on_send_message(self): + if getattr(self, 'on_send_message_' + self.mode, lambda a: None)(): + self.Top.on_sent_message(self.mode, self) + + def on_send_message_im(self): + 'Invoked when enter is pressed in the message input box during IM mode.' + + val = self.input_area.GetFormattedValue() + + # Early exit if there is no message to send. + if not val.format_as('plaintext'): + return + + self.history.commit(val.format_as('plaintext')) + + # If the user has selected different to/from accounts, change + # our conversation object. + if self.set_conversation_from_combos(): + self.convo.send_message(val) + self.ClearAndFocus() + return True + + def on_send_email(self, *a): + 'Invoked when the "Send" button is pressed. (For Emails)' + + to, frm = self.EmailControl.ToEmail, self.EmailControl.FromAccount + + if to is None: + return wx.MessageBox(_('Please add an email address for this buddy by ' + 'clicking the "To:" box.'), + _('Compose email to {name}').format(name=self.Buddy.name)) + + epanel = self._emailpanel + + def success(*a): + log.info('Email send success') + # store history + profile.blist.add_tofrom('email', to, frm) + + epanel.Clear() + epanel.SetStatusMessage(_('Message Sent')) + epanel.send_button.Enable(True) + epanel.openin.Enable(True) + + import hooks + hooks.notify('digsby.statistics.email.sent_from_imwindow') + + def error(*a): + log.info('Email send error') + epanel.SetStatusMessage(_('Failed to Send Email')) + epanel.send_button.Enable(True) + epanel.openin.Enable(True) + + epanel.SetStatusMessage(_('Sending...')) + epanel.send_button.Enable(False) + epanel.openin.Enable(False) + + subject = self._get_email_subject() + body = self._get_email_body() + + frm.OnClickSend(to = to, + subject = subject, + body = body, + success = success, + error = error) + + def _get_email_subject(self): + return self._emailpanel.subject_input.Value + + def _get_email_body(self): + + body = self._emailpanel.email_input_area.Value + if pref('email.signature.enabled', type = bool, default = False): + footer = u'\r\n' + pref('email.signature.value', type = unicode, + default = u'\r\n_______________________________________________________________' + '\r\nSent using Digsby - http://email.digsby.com') + else: + footer = '' + + return body + footer + + def on_send_message_sms(self): + 'Invoked when enter is pressed in the message input box during SMS mode.' + + # Early exit if there is no message to send. + if not self.input_area.Value: return + + to, frm = self.SMSControl.ToSMS, self.SMSControl.FromAccount + if to is None: + MessageBox(_('Please add an SMS number first.'), + _('Send SMS Message')) + elif frm is None: + MessageBox(_('You are not signed in to any accounts which can send SMS messages.'), + _('Send SMS Message')) + else: + + message = self.input_area.Value + + def on_success(): + self.show_message(Message(buddy = frm.self_buddy, + message = message[:SMS_MAX_LENGTH], + conversation = self.convo, + type = 'outgoing')) + self.ClearAndFocus() + + def on_error(errstr=None): + if errstr is not None: + more = '\n' + _('The error message received was:') + '\n\t%s' % errstr + else: + more = '' + + MessageBox(_('There was an error in sending your SMS message.') + more, + _('Send SMS Message Error'), + style = wx.ICON_ERROR) + + # Check the length--even though we limit the number of characters in SMS mode, the input box + # may already have had too many characters. + if len(message) > SMS_MAX_LENGTH: + sms_line1 = _('Only the first {max_length:d} characters of your message can be sent over SMS:').format(max_length=SMS_MAX_LENGTH) + sms_line2 = _('Do you want to send this message now?') + + if wx.NO == wx.MessageBox(u'%s\n\n"%s"\n\n%s' % (sms_line1, message, sms_line2), + _('Send SMS - Character Limit'), style = wx.YES_NO): + return + + import hooks + hooks.notify('digsby.statistics.sms.sent') + frm.send_sms(to, message[:SMS_MAX_LENGTH], success = on_success, error = on_error) + + def on_edit_email(self): + ''' + Uses the email account's mail client to edit the currently entered email. + + Invoked when the "Edit In..." button is clicked in the email panel. + ''' + + to, frm = self.EmailControl.ToEmail, self.EmailControl.FromAccount + + if to is not None and frm is not None: + frm.OnComposeEmail(to = to, + subject = self._emailpanel.subject_input.Value, + body = self._emailpanel.email_input_area.Value) + + def set_conversation_from_combos(self): + ''' + If our current conversation doesn't match the to and from accounts + chosen by the combos, obtains a new conversation. + + Returns False if there are no accounts to send the IM with. + ''' + if self.ischat: + return self.convo.protocol.connected + + to, frm = self.IMControl.Buddy, self.IMControl.Account + + # If IMControl's Account object is None, all accounts which can message + # the buddy we're talking to have signed off. We can't send the message. + if frm is None: return False + + convo = self.convo + if convo.protocol is not frm or convo.buddy is not to: + log.info('asking protocol %r for a new convo for buddy %r with service %r', frm, to, to.service) + convo = frm.convo_for(to) + log.info('got conversation %r with buddy/service %r %r:', convo, convo.buddy, convo.buddy.service) + self.set_conversation(convo) + + return True + + def on_close(self): + if getattr(self, '_closed', False): + log.warning('FIXME: imwin_ctrl.on_close was called more than once!!!') + return + + self._closed = True + + del self.capsbar.buddy_callback + del self.capsbar + + import hooks + hooks.notify('digsby.overlay_icon_updated', self) + + from plugin_manager import plugin_hub + + plugin_hub.act('digsby.im.conversation.close.async', self.convo) + + self.unlink_observers() + if self.convo is not None: + self.unwatch_conversation(self.convo) + try: + self.convo.explicit_exit() + except Exception: + print_exc() + + @property + def Conversation(self): + return self.convo + + @property + def Buddy(self): + return self.IMControl.Buddy + + @property + def SMS(self): + return self.SMSControl.get_contact_sms() + + def set_conversation(self, convo, meta = None): + if convo is self.convo: + return + + # watch/unwatch + shouldShowToFrom = False + if self.convo is not None: + self.unwatch_conversation(self.convo) + self.convo.exit() + if not self.convo.ischat: + shouldShowToFrom = True + + self.convo = convo + self.watch_conversation(convo) + + if self.is_roomlist_constructed(): + self.roomlist.SetConversation(convo) + + contact = meta if meta is not None else convo.buddy + + self.capsbar.ApplyCaps(convo=convo) + self.IMControl.SetConvo(convo, meta) + if shouldShowToFrom and not self.convo.ischat: + self.ShowToFrom(shouldShowToFrom) + + self.EmailControl.SetContact(contact) + self.SMSControl.SetContact(contact) + self.update_icon() + self.update_title() + + self.choose_message_formatting() + self.convo.play_queued_messages() + + if convo.ischat: + @wx.CallAfter + def after(): + self.show_roomlist(True) + + def _update_caps_and_title(self, *a): + @wx.CallAfter + def after(): + self.capsbar.ApplyCaps(convo=self.convo) + self.update_title() + + if self.convo.ischat and not getattr(self, 'roomlist_has_been_shown', False): + log.info("showing roomlist...") + self.toggle_roomlist() + + def watch_conversation(self, convo): + + from plugin_manager import plugin_hub + + plugin_hub.act('digsby.im.conversation.open.async', convo) + + convo.typing_status.add_observer(self.typing_status_changed) + convo.add_observer(self._update_caps_and_title, 'ischat') + + buddy = convo.buddy + + buddy.add_observer(self.buddy_status_changed, 'status') + buddy.add_observer(self.buddy_info_changed) + + if convo.ischat: + convo.room_list.add_observer(self.chat_buddies_changed) + convo.conversation_reconnected.add_unique(self.on_conversation_reconnected) + + convo.protocol.add_observer(self._on_convo_proto_state_change, 'state') + + #profile.account_manager.buddywatcher.watch_status(buddy, self.on_status_change) + + def on_conversation_reconnected(self, convo): + @wx.CallAfter + def gui(): + log.warning('on_conversation_reconnected: %r', convo) + self.set_conversation(convo) + convo.system_message(_('Reconnected')) + + def chat_buddies_changed(self, *a): + wx.CallAfter(self.update_title) + + def unwatch_conversation(self, convo = None): + if convo is None: convo = self.convo + if convo is not None: + buddy = convo.buddy + + convo.remove_observer(self._update_caps_and_title, 'ischat') + convo.typing_status.remove_observer(self.typing_status_changed) + if buddy is not None: + buddy.remove_observer(self.buddy_status_changed, 'status') + buddy.remove_observer(self.buddy_info_changed) + + convo.room_list.remove_observer(self.chat_buddies_changed) + convo.conversation_reconnected.remove_maybe(self.on_conversation_reconnected) + + convo.protocol.remove_observer(self._on_convo_proto_state_change, 'state') + #profile.account_manager.buddywatcher.unwatch_status(buddy, self.on_status_change) + + def _on_convo_proto_state_change(self, proto, attr, old, new): + @wx.CallAfter + def after(): + if self.convo.ischat and new == proto.Statuses.OFFLINE: + # chats include roomlist count, and need to be updated on disconnect. + self.update_title() + + def show_status(self, update, ondone=None): + cb = lambda u=update: self.show_message(u, ondone) + + try: + timer = self._statustimer + except AttributeError: + timer = self._statustimer = wx.PyTimer(cb) + else: + timer.SetCallback(cb) + + if not self._statustimer.IsRunning(): + self._statustimer.Start(250, True) + + @calllimit(1) + def buddy_info_changed(self, *a): + ''' + This method is called anytime the buddy's information changes. + + If we're in "info" mode, the HTML profile box is updated. + ''' + if self.mode == 'info': + self.set_profile_html(self.Buddy) + + def buddy_status_changed(self, *a): + wx.CallAfter(self._buddy_status_changed) + + def _buddy_status_changed(self): + if check_destroyed(self): + return + + if self.convo.ischat: + return + + # if the buddy's online status changes, we may need to add/remove the + # Files button + self.capsbar.ApplyCaps(self.Buddy) + + # if we're showing the buddy's status orb in the tab/window title, + # update those icons now. + if self.icontype == 'status': + self.update_icon() + + def typing_status_changed(self, *a): + "Called when the conversation's typing status changes." + + typing = self.convo.typing_status.get(self.convo.buddy, None) + + # this pref indicates how long after not receiving typing notifications + # we wait until clearing them, in seconds. 0 means never clear. + typing_clear_time_secs = pref('messaging.typing_notifications.clear_after', default=30, type=int) + if typing_clear_time_secs > 0: + if typing is not None: + self.clear_typing_timer.StartOneShot(1000 * typing_clear_time_secs) + else: + self.clear_typing_timer.Stop() + + self.on_typing(typing) + + def on_typing(self, typing): + self.typing = typing + self.update_title() + self.update_icon() + + def on_clear_typing_timer(self): + ''' + Called after a set period of time with no typing updates. + ''' + + if not wx.IsDestroyed(self): + self.on_typing(None) + + def choose_message_formatting(self): + ''' + Gives a chance for both the conversation and the protocol to expose + an attribute "message_formatting", which if set to 'plaintext' + will cause us only to extract (and send as IMs) text from the input + box, not HTML. + ''' + plain = False + conv = self.convo + + try: plain = conv.message_formatting == 'plaintext' + except AttributeError: + try: plain = conv.protocol.message_formatting == 'plaintext' + except AttributeError: pass + + self.plainttext = plain + + def show_message(self, messageobj, ondone=None): + "Shows a message object in the message area." + + c = messageobj.conversation + b = messageobj.buddy + t = messageobj.type + + # used to remember incoming<->outgoing for whether to "glue" + # consecutive messages together visually + buddyid = (b.idstr(), messageobj.type) if b is not None else None + + if buddyid is None: + next = False + else: + next = getattr(self, 'last_buddy', None) == buddyid + self.last_buddy = buddyid + + self.message_area.format_message(t, messageobj, next = next) + + if ondone is not None: + #ondone() + pass + + def set_profile_html(self, buddy): + "Sets the HTML info window's contents to buddy's profile." + + profilewindow = self.profile_html + + # don't generate HTML for the same buddy twice. + try: + html = GetInfo(self.Buddy, + showprofile = True, + showhide = False, + overflow_hidden = False) + except Exception: + print_exc() + html = buddy.name + + # freeze/thaw since HTML window is flickery when updating contents + with self.Frozen(): + profilewindow.SetHTML(html) + + def on_text_changed(self, e): + 'Called when the main text input box changes.' + + e.Skip() + + # change conversations if we need to + oldConvo = self.convo + if not self.send_typing or self.Mode != 'im' or not self.set_conversation_from_combos(): + return + + # end typing notifications for the old conversation + if oldConvo is not None and oldConvo is not self.convo: + with traceguard: + oldConvo.send_typing_status(None) + + txt = self.input_area.Value + + if len(txt) == 0: + if self.typing_status != None: + self.typing_status = None + self.convo.send_typing_status(None) + + if self.typing_timer: + self.typing_timer.Stop() + self.typing_timer = None + + else: + if self.typing_status != 'typing': + self.typing_status = 'typing' + self.convo.send_typing_status(self.typing_status) + + self.cancel_timer() + self.typing_timer = PyTimer(self.send_typed) + self.typing_timer.Start(self.typed_delay * 1000, True) + + def send_typed(self, *e): + if self.typing_status != 'typed': + self.typing_status = 'typed' + self.convo.send_typing_status(self.typing_status) + + def cancel_timer(self): + if self.typing_timer: + self.typing_timer.Stop() + self.typing_timer = None + + def on_message_area_shown(self): + 'Sets up the MessageStyle for the IM area.' + + if hasattr(self, 'message_area') and not self.message_area.inited: + if hasattr(self, 'convo'): + self.init_message_area(self.convo.name, self.convo.buddy, show_history = not self.convo.ischat) + else: + self.init_message_area('', None) + + self.input_area.tc.Bind(wx.EVT_TEXT, self.on_text_changed) + + from gui.imwin.imwindnd import ImWinDropTarget + self.SetDropTarget( ImWinDropTarget(self) ) + + def on_video(self): + + import hooks + hooks.notify('digsby.video_chat.requested') + + buddy = self.Buddy + + from gui.video.webvideo import VideoChatWindow + + if VideoChatWindow.RaiseExisting(): + self.convo.system_message(_('You can only have one audio/video call at a time.')) + log.info('video window already up') + else: + log.info('requesting video chat') + + from digsby.videochat import VideoChat + VideoChat(buddy) diff --git a/digsby/src/gui/imwin/imwin_email.py b/digsby/src/gui/imwin/imwin_email.py new file mode 100644 index 0000000..f966c19 --- /dev/null +++ b/digsby/src/gui/imwin/imwin_email.py @@ -0,0 +1,146 @@ +''' + +GUI for the IM window's email tab. + +''' + +import wx + +from util.primitives.funcs import Delegate + +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.UberBar import UberBar +from gui.uberwidgets.skintextctrl import SkinTextCtrl +from gui.uberwidgets.cleartext import ClearText +from gui.textutil import default_font +from gui.validators import LengthLimit +from gui import skin +from cgui import SimplePanel + +class ImWinEmailPanel(SimplePanel): + def __init__(self, parent): + SimplePanel.__init__(self, parent) + + self.OnEditEmail = Delegate() + self.OnSendEmail = Delegate() + + + self.gui_constructed = False + + self.UpdateSkin() + + self.construct_gui() + + def SetEmailClient(self, email_account): + ''' + Changes the "Edit In..." button to show the name of an email client. + + If None, the button becomes disabled. + ''' + + + client = email_account.client_name if email_account is not None else None + + if client is not None: + self.send_button.Enable(True) + self.openin.Enable(True) + + if client: + label = _('Edit in {client}...').format(client=client) + else: + label = _('Edit...') + + self.openin.SetLabel(label) + else: + self.send_button.Enable(False) + self.openin.Enable(False) + self.openin.SetLabel(_('Edit...')) + + def UpdateSkin(self): + + g = skin.get + self.buttonbarskin = g('SendBar.ToolBarSkin', None) + self.subjectskin = g('EmailSubjectBar.ToolBarSkin',None) + + self.subjectfont = g('EmailSubjectBar.Fonts.SubjectLabel',lambda: default_font()) + self.subjectfc = g('EmailSubjectBar.FontColors.SubjectLabel', wx.BLACK) + + self.buttonbarfont = g('SendBar.Font', default_font) + self.buttonnarfc = g('SendBar.FontColor', wx.BLACK) + + + if self.gui_constructed: + + self.subject_bar.SetSkinKey(self.subjectskin) + self.email_buttons.SetSkinKey(self.buttonbarskin) + + ept = self.email_progress_text + ept.SetFont(self.buttonbarfont) + ept.SetFontColor(self.buttonnarfc) + + sl = self.subject_label + sl.SetFont(self.subjectfont) + sl.SetFontColor(self.subjectfc) + + + def construct_gui(self): + self.Sizer = wx.BoxSizer(wx.VERTICAL) + + s = self.subject_bar = UberBar(self, skinkey = self.subjectskin) + + self.subject_input = SkinTextCtrl(s, skinkey = ('EmailSubjectBar', 'SubjectField'), + skinkey_bg = 'EmailSubjectBar.FieldBackgroundColor', + validator=LengthLimit(1024), + ) + self.subject_label = ClearText(s, _('Subject:'), alignment = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + self.subject_label.Font = self.subjectfont + self.subject_label.FontColor = self.subjectfc + + s.Add(self.subject_label) + s.Add(self.subject_input,1) + + # construct email buttons panel + email_buttons = self.email_buttons = UberBar(self, skinkey=self.buttonbarskin) + + ept = self.email_progress_text = ClearText(email_buttons, '', alignment = wx.ALIGN_CENTER_VERTICAL) + ept.SetFont(self.buttonbarfont) + ept.SetFontColor(self.buttonnarfc) + + # email body text input + self.email_input_area = wx.TextCtrl(self, style = wx.TE_MULTILINE, + validator=LengthLimit(20480), + ) + + # "open in" and "send" + self.openin = UberButton(email_buttons, -1, _('Edit...'), onclick = self.OnEditEmail) + self.send_button = UberButton(email_buttons, -1, _('Send'), onclick = self.OnSendClicked) + + # layout email buttons + email_buttons.Add(ept) + email_buttons.Add(wx.Size(1,1), 1)#StretchSpacer(1) + email_buttons.Add(self.openin) + email_buttons.Add(self.send_button) + + # Make sure Tab from the subject input goes to the body input. + self.email_input_area.MoveAfterInTabOrder(self.subject_bar) + + s = self.Sizer + s.AddMany([(self.subject_bar, 0, wx.EXPAND), + (self.email_input_area, 1, wx.EXPAND), + (self.email_buttons, 0, wx.EXPAND)]) + + self.gui_constructed = True + + def OnSendClicked(self, e): + self.OnSendEmail(e) + + def Clear(self): + self.subject_input.Clear() + self.email_input_area.Clear() + + def SetStatusMessage(self, msg): + self.email_progress_text.SetLabel(msg) + + def EnableSendButton(self, enabled): + self.send_button.Enable(enabled) + diff --git a/digsby/src/gui/imwin/imwin_gui.py b/digsby/src/gui/imwin/imwin_gui.py new file mode 100644 index 0000000..17596a5 --- /dev/null +++ b/digsby/src/gui/imwin/imwin_gui.py @@ -0,0 +1,671 @@ +''' + +IM window GUI construction + +''' +from __future__ import with_statement + +try: _ +except: import gettext; gettext.install('Digsby') + +import wx, sys +import metrics, hooks +import traceback + +from wx import EXPAND, EVT_BUTTON, VERTICAL, BoxSizer, \ + GetTopLevelParent, EVT_CONTEXT_MENU +from wx.lib import pubsub + +from logging import getLogger; log = getLogger('imwingui') + +from common import profile, pref, prefprop +from util import InstanceTracker + +from gui.skin import get as skinget +from gui.imwin.imwin_ctrl import ImWinCtrl +from gui.imwin.imwin_email import ImWinEmailPanel +from gui.imwin.imwin_roomlist import RoomListMixin +from gui.imwin.messagearea import MessageArea +from gui.imwin import imwinmenu +from gui.imwin.styles import get_theme_safe +from gui.capabilitiesbar import CapabilitiesBar +from gui.uberwidgets.formattedinput2.iminput import IMInput +from gui.validators import LengthLimit +from gui import clipboard +from gui.toolbox.toolbox import Frozen + +from gui.addcontactdialog import AddContactDialog + + +# these strings are placed next to the buddy name +# in the window title when they're typing +typing_status_strings = { + 'typing': _('Typing'), + 'typed': _('Entered Text'), +} + +from gui.browser.webkit import WebKitWindow +class ImHtmlWindow(WebKitWindow): + ''' + Implements [Can]Copy so the right click menu can "just work" with this + control. + ''' + + def __init__(self, *a, **k): + WebKitWindow.__init__(self, *a, **k) + self.Bind(wx.EVT_KEY_DOWN, self.__OnKey) + + def __OnKey(self, e): + if e.KeyCode == ord('C') and e.Modifiers == wx.MOD_CMD: + self.Copy() + else: + e.Skip() + + def SelectionToText(self): + return self.RunScript('window.getSelection()') + + def CanCopy(self): + return bool(self.SelectionToText()) + + def Copy(self): + return clipboard.copy(self.SelectionToText()) + +class AnnounceWindow(wx.Frame): + def __init__(self, parent = None): + wx.Frame.__init__(self, parent, -1, _('Digsby Announcement'), size = (400, 330)) + self.SetFrameIcon(skinget('AppDefaults.TaskbarIcon')) + + self.message_area = MessageArea(self, header_enabled = False, prevent_align_to_bottom=True) + self.inited = False + self.CenterOnScreen() + + def message(self, messageobj): + if not self.inited: + self.inited = True + theme, variant = pref('appearance.conversations.theme'), pref('appearance.conversations.variant') + buddy = messageobj.buddy + + # initialize the message area, not showing history + self.message_area.init_content(get_theme_safe(theme, variant), + buddy.name, buddy, show_history = False) + #prevent_align_to_bottom=True) # disable until all skins look correct with this option + + self.message_area.format_message(messageobj.type, messageobj) + + +class LayoutChange(Frozen): + def __exit__(self, exc_type, exc_val, exc_tb): + try: + self.win.Layout() + self.win.Layout() #CAS: why are there two? see r9728 + finally: + return super(LayoutChange, self).__exit__(exc_type, exc_val, exc_tb) + +class ImWinPanel(wx.Panel, RoomListMixin, ImWinCtrl, InstanceTracker): + ''' + The main message window GUI. + + Acts mostly like a tabbed interface for the various "modes:" info, im, email, sms + + Tabs are lazily constructed when needed--see the "construct_XXX" methods. + ''' + + def __init__(self, parent, pos = wx.DefaultPosition): + wx.Panel.__init__(self, parent, pos = (-300, -300)) + InstanceTracker.track(self) + + self.BackgroundStyle = wx.BG_STYLE_CUSTOM + self.Sizer = BoxSizer(VERTICAL) + self.showncontrol = None + + self.link_observers() + self.construct_gui() + self.setup_delegation() + + ImWinCtrl.__init__(self) + imwinmenu.add_menus(self) + + metrics.event('IM Window Opened') + + self.UpdateSkin() + + self.IMControl.OnSelection += self.on_im_to_changed + + self.Bind(wx.EVT_SIZE, self.OnSize) + + def on_im_to_changed(self): + self.capsbar.ApplyCaps() + + # hide roomlist if switching to a protocol without groupchat + print 'on_im_to_changed' + print 'RoomListButtonShown', self.capsbar.RoomListButtonShown + + if not self.capsbar.RoomListButtonShown: + self.show_roomlist(False) + + def OnSize(self, event): + event.Skip() + wx.CallAfter(self.Layout) + + def _on_view_past_chats(self, *a): + if self.convo.ischat: + from gui.pastbrowser import PastBrowser + PastBrowser.MakeOrShowAndSelectConvo(self.convo) + else: + self.IMControl.Buddy.view_past_chats(self.IMControl.Account) + + def construct_gui(self): + c = self.capsbar = CapabilitiesBar(self, buddy_callback = lambda: self.convo, showCapabilities = self.show_actions_bar) + c.OnSendFiles += lambda: self.Buddy.send_file() + c.OnViewPastChats += self._on_view_past_chats + c.OnAddContact += lambda *a: AddContactDialog.MakeOrShow(service=self.Buddy.service, name=self.Buddy.name, account = profile.account_manager.get_account_for_protocol(self.IMControl.Account)) + c.OnBlock += lambda *a: self.Buddy.block(not self.Buddy.blocked) + self.Sizer.Add(c, 0, EXPAND) + + + def construct_infopanel(self): + self.profile_html = html = ImHtmlWindow(self) + html.Bind(EVT_CONTEXT_MENU, lambda e: self.GetMenu().PopupMenu(event = e)) + html.Bind(wx.EVT_RIGHT_UP, lambda e: self.GetMenu().PopupMenu(event = e)) + html.Hide() + return html + + def construct_messagepanel(self): + from gui.uberwidgets.skinsplitter import SkinSplitter + self.input_splitter = spl = SkinSplitter(self, MSGSPLIT_FLAGS) + spl.SetMinimumPaneSize(10) + spl.SetSashGravity(1) + wx.CallAfter(spl.SetSashPosition, 400) + + msgarea = self.message_area = MessageArea(spl) + msgarea.SetMouseWheelZooms(True) + msgarea.MinSize = wx.Size(msgarea.MinSize.width, 100) + + self.IMControl.msgarea = msgarea + + maBind = msgarea.Bind + maBind(wx.EVT_CONTEXT_MENU, lambda e: self.GetMenu().PopupMenu(event = e)) + # Apparently on Windows a window without focus still gets key events, + # but this doesn't happen on Mac, meaning the user can't select text unless we get focus. + if not sys.platform.startswith('darwin'): + maBind(wx.EVT_SET_FOCUS, lambda e: self.FocusTextCtrl()) + + self.input_area = IMInput(spl, + showFormattingBar = self.show_formatting_bar, + multiFormat = True, +# rtl = self.rtl_input, + skin="FormattingBar", + entercallback = lambda txtfld: self.on_send_message(), + validator=LengthLimit(10240)) + wx.CallAfter(self.input_area.tc.AutoSetRTL) + self.input_area.BindSplitter(spl, 'conversation_window.input_base_height') + + text_control = self.input_area.tc + if not sys.platform.startswith('darwin'): + text_control.Bind(wx.EVT_CONTEXT_MENU, lambda e: self.GetTextCtrlMenu().PopupMenu(event = e)) + text_control.Bind(wx.EVT_TEXT_PASTE, self._on_text_ctrl_paste) + + def OnToFromShow(event): + event.Skip() + text_control.ForceExpandEvent() + + self.capsbar.tfbar.Bind(wx.EVT_SHOW, OnToFromShow) + + #HAX: There has to be a better way... Besides only works for the first window + tab = self.Tab + if tab and hasattr(tab, "OnActive"): + #TODO: expandhax? + #tab.OnActive += lambda: self.input_area.expandEvent() + #self.SupaHax() + tab.firsttop = self.Top + + self.setup_keys(self.input_area.tc) + + self.do_input_split(msgarea) + + return spl + + def do_input_split(self, ctrl): + if self.input_splitter.IsSplit(): + self.input_splitter.Unsplit(self.input_splitter.GetWindow1()) + self.input_splitter.SplitHorizontally(ctrl, self.input_area) + + def _on_text_ctrl_paste(self, e): + e.Skip() + + if not pref('conversation_window.paste.images', default=True): + return + + # if we have a convo object... + convo = self.convo + if convo is None: + return + + # and if the protocol supports sending files... + from common import caps + if caps.FILES not in convo.protocol.caps: + return + + + text = clipboard.get_text() + # Some forms of text (like excel data) can be sent as images. we'd rather send it as text though. + if text is not None: + return + + # and there's a bitmap in the clipboard... + bitmap = clipboard.get_bitmap() + if bitmap is None: + return + + # show a "send image" dialog + buddy = convo.buddy + self_name = convo.protocol.self_buddy.name + + from gui.imagedialog import show_image_dialog + message = _(u'Would you like to send this image to %s?') % buddy.name + if show_image_dialog(self, message, bitmap): + import time, stdpaths + filename = '%s.clipboard.%s.png' % (self_name, time.time()) + filename = stdpaths.temp / filename + bitmap.SaveFile(filename, wx.BITMAP_TYPE_PNG) + buddy.send_file(filename) + +# def SupaHax(self): +# top = self.Top +# def ExpandHax(): +# if self.IsShownOnScreen(): +# self.input_area.expandEvent() +# +# def OnTopChanged(): +# top.iconizecallbacks.remove(ExpandHax) +# wx.CallAfter(self.SupaHax) +# +# top.iconizecallbacks.add(ExpandHax) +# +# +# self.OnTopChanged = OnTopChanged + + def setup_keys(self, textctrl): + msgarea = self.message_area + + def key(e): + # Catch some keys in the input box that should/can be redirected + # to the IM area + c = e.KeyCode + + if c == wx.WXK_PAGEUP: + msgarea.ScrollPages(-1) + elif c == wx.WXK_PAGEDOWN: + msgarea.ScrollPages(1) + elif c == ord('C') and e.GetModifiers() == wx.MOD_CMD: + # catch Ctrl+C in the formatted input. If there is selected + # text in the IM area, then that will be copied. otherwise, + # the event is skipped and the text control will copy its + # text + if not self.Copy(): e.Skip() + else: + e.Skip() + + msgarea.BindWheel(textctrl) + msgarea.BindWheel(msgarea) + msgarea.BindScrollWin(textctrl) + msgarea.BindScrollWin(msgarea) + textctrl.Bind(wx.EVT_KEY_DOWN, key) + + # TextHistory provides Ctrl+Up and Ctrl+Down history support + from gui.toolbox.texthistory import TextHistory + self.history = TextHistory(textctrl) + + def Copy(self): + # If the input area has selection, use that... + if self.input_area.tc.CanCopy(): + self.input_area.tc.Copy() + + # ...otherwise if the conversation area has selection, use that. + elif self.message_area.CanCopy(): + self.message_area.Copy() + + else: + return False + + return True + + def construct_emailpanel(self): + emailpanel = ImWinEmailPanel(self) + + emailpanel.send_button.Bind(EVT_BUTTON, lambda e: self.on_send_email()) + emailpanel.openin.Bind(EVT_BUTTON, lambda e: self.on_edit_email()) + + # Email panel children need the main IMWin right click menu + for child in emailpanel.Children: + child.Bind(EVT_CONTEXT_MENU, lambda e: self.GetMenu().PopupMenu(event = e)) + emailpanel.subject_input.Bind(EVT_CONTEXT_MENU, lambda e: self.GetMenu().PopupMenu(event = e)) + + self.EmailControl.OnEmailAccountChanged += emailpanel.SetEmailClient + + return emailpanel + + def show_controls(self, mode): + if self.showncontrol == mode: + return + + with self.Frozen(): + # hide old controls, show new ones + ctrlname = modectrls[mode] + + if self.showncontrol is not None: + self.showncontrol.Hide() + + ctrl = self.get_panel(ctrlname) + ctrl.Show() + self.Layout() + + self.mode = mode + self.showncontrol = ctrl + + def get_panel(self, ctrlname): + try: + return getattr(self, '_' + ctrlname) + except AttributeError: + if not hasattr(self, '_' + ctrlname): + # look up function construct_XXX where XXX is ctrlname + # and call it to lazily construct the panel + c = getattr(self, 'construct_' + ctrlname)() + + # set it as _XXX in self + setattr(self, '_' + ctrlname, c) + + self.Sizer.Add(c, 1, EXPAND) + return c + + + def init_message_area(self, chatName, buddy, show_history=None): + # apply the MessageStyle if we haven't already. + self.message_area.init_content(self.theme, chatName, buddy, show_history=show_history) + + @property + def theme(self): + theme, variant = pref('appearance.conversations.theme'), pref('appearance.conversations.variant') + key = '%s__%s' % (theme, variant) + + cls = ImWinPanel + + + try: + if cls.__msgthemename == key: + return cls.__messagetheme + except AttributeError: + pass + + t = cls.__messagetheme = get_theme_safe(theme, variant) + cls.__msgthemename = key + return t + + def UpdateSkin(self): + self.typing_icons = { + 'typing': skinget('statusicons.typing'), + 'typed': skinget('statusicons.typed'), + } + wx.CallAfter(wx.CallAfter, self.Layout) + + @property + def Tab(self): + # until reparenting is not necessary for UberBook, tab might + # be None + return getattr(self.Parent, 'tab', None) + + @property + def Notified(self): + '''Returns True if this IM window's tab is in a "notified" state.''' + + return getattr(self.Tab, 'notified', False) + + def IsActive(self): + ''' + Returns True if this window is both the active top level window and + the active tab. + ''' + + # FIXME: TLW.IsActive seems not to be returning the right value... + if sys.platform.startswith('darwin'): + return self == self.Parent.ActiveTab + else: + tab = self.Tab + return tab is not None and tab.GetActive() and GetTopLevelParent(self).IsActive() + + def Notify(self): + 'Cause this window/tab to "blink" and grab user attention.' + + # cause the tab to go into the "notify" state + tab = self.Tab + + if tab is not None and (not tab.active or not self.Top.IsActive()): + tab.SetNotify(True) + + def Unnotify(self): + tab = self.Tab + if tab is not None: tab.SetNotify(False) + + def ClearAndFocus(self): + 'Clears and focuses the input box, and hides the to/from bar.' + + with self.Frozen(): + self.capsbar.ShowToFrom(False) + self.input_area.Clear() + self.FocusTextCtrl() + self.Layout() + + def link_observers(self): + 'Ties GUI preferences to methods that implement them.' + + link = profile.prefs.link + self.show_formatting_bar = pref('messaging.show_formatting_bar', False) + self.show_actions_bar = pref('messaging.show_actions_bar', True) + self.show_send_button = pref('messaging.show_send_button', False) +# self.rtl_input = pref('messaging.rtl_input', False) + + self.links = [ + link('messaging.show_formatting_bar', self.pref_show_formatting_bar, False), + link('messaging.show_actions_bar', self.pref_show_actions_bar, False), + link('messaging.show_send_button', self.pref_show_send_button, False), +# link('messaging.rtl_input', self.pref_rtl_input, False), + link('messaging.tabs.icon', self.pref_tab_icon, False) + ] + + def unlink_observers(self): + 'Cleans up observable links.' + + for link in self.links: + link.unlink() + del self.links[:] + + def pref_show_formatting_bar(self, val): + 'Invoked when the "show formatting bar" pref changes.' + + self.show_formatting_bar = val + + if hasattr(self, 'input_area'): + # may not have been constructed yet + self.input_area.ShowFormattingBar(val) + + def pref_show_send_button(self, val): + + self.show_send_button = val + + if hasattr(self, 'input_area'): + self.input_area.ShowSendButton(val) + + +# def pref_rtl_input(self, val): +# 'Invoked when the "show actions bar" pref changes.' +# self.rtl_input = val +# +# with self.Frozen(): +# 'Toggles the layout direction of a control between right-to-left and left-to-right.' +# self.input_area.tc.LayoutDirection = wx.Layout_RightToLeft if val else wx.Layout_LeftToRight +# self.input_area.tc.Refresh() + + def pref_show_actions_bar(self, val): + 'Invoked when the "show actions bar" pref changes.' + + with self.LayoutChange(): + self.capsbar.ShowCapabilities(val) + + def ShowToFrom(self, show): + if show != self.capsbar.ToFromShown: + with self.LayoutChange(): + self.capsbar.ShowToFrom(show) + + def LayoutChange(self): + return LayoutChange(self) + + def pref_tab_icon(self, val): + wx.CallAfter(self.update_icon) + + icontype = prefprop('messaging.tabs.icon', 'buddy') + + @property + def TypingBadge(self): + return self.typing_icons.get(self.typing, None) + + @property + def ischat(self): + return self.convo.ischat + + @property + def chat_icon(self): + try: + bitmap = self.convo.protocol.serviceicon.ResizedSmaller((16,16)) + except Exception: + traceback.print_exc_once() + from gui import skin + bitmap = skin.get('actionsbar.icons.roomlist').Resized((16,16)) + + return bitmap + + def update_icon(self): + # if the buddy is typing, use a typing badge. + typing_icon = icon = self.TypingBadge + + # otherwise choose based on the pref + if icon is None: + if not self.ischat: + icon = icons.get(self.icontype, 'buddy')(self.Buddy) + else: + icon = self.chat_icon + + pubsub.Publisher().sendMessage(('tab', 'icon', 'updated'), (self, icon)) + + if self.Page is not None: + self.Page.SetIcon(icon) + + hooks.notify('digsby.overlay_icon_updated', self) + + title_typing_notifications = prefprop('messaging.typing_notifications.show_in_title', type=bool, default=True) + + def _get_title(self): + if self.ischat: + self._last_title = self._get_title_chat() + return self._last_title + + bud = self.Buddy + if bud is None: return getattr(self, '_last_title', ('', '')) + name = bud.alias + + if sys.DEV and not isinstance(name, unicode): + msg = 'Please only unicode aliases for buddies! (got %r from %r)', name, self.Buddy + + if pref('errors.nonunicode_buddy_names', type=bool, default=False): + raise TypeError(msg) + else: + log.warning(*msg) + + if self.typing is not None and self.title_typing_notifications: + window_title = '%s (%s)' % (name, typing_status_strings.get(self.typing, self.typing)) + else: + window_title = None + + self._last_title = name, window_title + return self._last_title + + def _get_title_chat(self): + s = u'Group Chat (%d)' % self.convo.chat_member_count + return (s, )*2 + + def update_title(self): + ''' + Updates the title of this IM window, which shows in it's tab, and + possibly in the window containing it. + ''' + + assert wx.IsMainThread() + + if wx.IsDestroyed(self): + return + + name, window_title = self._get_title() + + pubsub.Publisher().sendMessage(('tab', 'title', 'updated'), (self, name, window_title)) + + if self.Page is not None: + self.Page.SetTitle(name, window_title) + + @property + def Page(self): + p = self.Parent + from gui.uberwidgets.uberbook.page import Page + if isinstance(p, Page): + return p + + @property + def TextCtrl(self): + return self.input_area.tc + + def FocusTextCtrl(self): + 'Gives focus to the input box, if this window has one currently and it is shown.' + + if not self.Top.IsForegroundWindow(): + return + + try: tc = self.input_area.tc + except AttributeError: + pass # no input area yet + else: + if tc.Shown: tc.SetFocus() + + # + # delegation to child controls + # + + def setup_delegation(self): + self.To = self.capsbar.cto + self.From = self.capsbar.cfrom + + for delegate_name, attrs in self.delegation.iteritems(): + delegate = getattr(self, delegate_name) + for attr in attrs: + setattr(self, attr, getattr(delegate, attr)) + + + + delegation = {'capsbar': ('GetButton',)} + + ToFromShown = property(lambda self: self.capsbar.ToFromShown) + +def budicon(bud): + from gui.buddylist.renderers import get_buddy_icon + return get_buddy_icon(bud, round_size = False, meta_lookup=True) + + +icons = dict(buddy = budicon, + status = lambda bud: skinget('statusicons.' + bud.status_orb), + service = lambda bud: bud.serviceicon) + +# maps "modes" to the GUI panels they use +modectrls = {'info': 'infopanel', + 'im': 'messagepanel', + 'email': 'emailpanel', + 'sms': 'messagepanel'} + +MSGSPLIT_FLAGS = wx.SP_NOBORDER | wx.SP_LIVE_UPDATE + diff --git a/digsby/src/gui/imwin/imwin_native.py b/digsby/src/gui/imwin/imwin_native.py new file mode 100644 index 0000000..dac46d2 --- /dev/null +++ b/digsby/src/gui/imwin/imwin_native.py @@ -0,0 +1,581 @@ +""" +imwin_native.py + +This file contains the code needed for the 'native', that is, not skinned, version of the +IM window. + +Blame Rests With: Kevin Ollivier +""" + +import sys + +import wx +import wx.lib.flatnotebook as fnb +import wx.lib.newevent + +from wx.lib import pubsub + +import actionIDs + +import gui +import gui.imwin.imtabs +import gui.imwin.imwin_gui as imwin_gui + +from gui import capabilitiesbar +from gui import skin +from gui.addcontactdialog import AddContactDialog +from gui.filetransfer import FileTransferDialog +from gui.uberwidgets.uberbook.OverlayImage import OverlayImage +from gui.windowfx import fadein + +buttonIDs = { + 'info': actionIDs.InfoMode, + 'im': actionIDs.IMMode, + 'email': actionIDs.EmailMode, + 'video': actionIDs.CallMode, + } + +FlatNotebookDragStarted, EVT_FNB_DRAG_STARTED = wx.lib.newevent.NewCommandEvent() + +class NativeDragTimer(wx.Timer): + """ + A timer handling the movement of the tab preview, Dropmarker hiding, + and showing of hidden tabbars when dragged over a window. + """ + def __init__(self, source): + wx.Timer.__init__(self) + self.source = source + + def Notify(self): + """ + On the trigger time does D&D operations + """ + + if not wx.LeftDown(): + self.Stop() + self.source.OnDragFinish() + else: + self.source.OnDragging() + +class FrameDragManager(wx.EvtHandler): + """ + Used to manage the drag overlay preview image and alert a callback to a drag finished event. + Designed to be reusable across various frame classes. + """ + def __init__(self, frame): + wx.EvtHandler.__init__(self) + self.frame = frame + self.dragtimer = NativeDragTimer(self) + self.draggedidx = -1 + self.dragimage = None + self.dragwindow = None + self.callback = None + # since we take a snapshot of the whole window, + # we need to know where the mouse is in that window + self.mousePosOffset = None + + def StartDrag(self, callback = None, draggedidx = -1, dragwindow = None): + """ + Called when a drag event starts, generates the preview image and sets the drag + finished callback function. + """ + self.mousePos = wx.GetMousePosition() + self.draggedidx = draggedidx + self.dragtimer.Start(1) + screendc = wx.ScreenDC() + + self.mousePosOffset = self.mousePos - self.frame.GetScreenRect().GetPosition() + + if dragwindow: + self.dragwindow = dragwindow + self.dragwindow.Move(wx.GetMousePosition() - self.mousePosOffset) + else: + dragimage = screendc.GetAsBitmap().GetSubBitmap(self.frame.GetScreenRect()) + self.dragimage = OverlayImage(self.frame, dragimage, size=(dragimage.GetWidth(), dragimage.GetHeight())) + + if self.dragimage: + self.dragimage.SetTransparent(172) + self.dragimage.Move(wx.GetMousePosition() - self.mousePosOffset) + + self.dragimage.Show() + + self.callback = callback + + def OnDragging(self): + """ + Update the dragging image as the mouse moves + """ + pos = wx.GetMousePosition() - self.mousePosOffset + if self.dragimage: + self.dragimage.Move(pos) + + if self.dragwindow: + self.dragwindow.Move(pos) + + def OnDragFinish(self): + """ + Remove the drag image and call the drag finished callback to process the drag. + """ + if self.dragimage: + self.dragimage.Hide() + self.dragimage.Destroy() + self.dragimage = None + + self.callback(draggedidx=self.draggedidx) + +class NativeIMFrameEventHandler(wx.EvtHandler): + """ + Controller class for the native version of ImFrame. Using controllers helps us to + separate (and sometimes share) logic between native and skinned versions. + """ + def __init__(self, frame): + wx.EvtHandler.__init__(self) + self.frame = frame + self.dragManager = FrameDragManager(self.frame) + self.frameID = self.frame.GetId() + self.potentialNewWin = None + + self.BuildToolBar() + self.BindEventsToFrame() + + def BindEventsToFrame(self): + """ + Any events the native IM frame should always handle (i.e. even when inactive) should be connected here. + """ + self.frame.Bind(wx.EVT_ACTIVATE, self.OnActivate) + self.frame.Bind(wx.EVT_CLOSE, self.OnClose) + self.frame.notebook.Bind(EVT_FNB_DRAG_STARTED, self.OnFNBTabDragStart) + + publisher = pubsub.Publisher() + publisher.subscribe(self.OnPageTitleUpdated, 'tab.title.updated') + publisher.subscribe(self.OnPageIconUpdated, 'tab.icon.updated') + + def OnActivate(self, event): + """ + Only sign up this frame to handle toolbar/menu events when it's the active frame. + """ + event.Skip() + if event.Active: + self.ConnectToolBarEvents() + else: + self.DisconnectToolBarEvents() + + def OnClose(self, event): + """ + Disconnect any notifications we have in place. + """ + + if self.frame.CloseAndSaveState(event): + publisher = pubsub.Publisher() + publisher.unsubscribe(self.OnPageTitleUpdated, 'tab.title.updated') + publisher.unsubscribe(self.OnPageIconUpdated, 'tab.icon.updated') + + self.DisconnectToolBarEvents() + + self.frame.Destroy() + del self + else: + event.Veto() + + # ---- Drag Handling ---- + def OnFNBTabDragStart(self, event): + """ + Called when a drag is initiated on a FlatNotebook tab. + """ + + if self.frame.notebook.GetTabCount() > 1: + newpos = wx.GetMousePosition() + newpos[1] = newpos[1] - self.frame.Size.height + newwin = gui.imwin.imtabs.ImFrame(pos = newpos, size = self.frame.Size) + newwin.SetTransparent(123) + + page = self.frame.notebook.GetPage(event.draggedidx) + newwin.AddTab(page, False) + newwin.ShowNoActivate(True) + self.potentialNewWin = newwin + else: + self.frame.SetTransparent(123) + newwin = self.potentialNewWin = self.frame + + self.dragManager.StartDrag(self.OnDragFinish, event.draggedidx, dragwindow = newwin) + + def OnDragFinish(self, draggedidx): + """ + Called when a drag completes. Determine whether to create a new IM window, add a tab, etc. + and handle destruction of the current frame if all notebook frames are gone. + """ + + assert draggedidx != -1 + + # FIXME: Really the drop targets themselves should respond to the event IMHO. + # Not sure what the proper way of notifying the app that a target was dropped on "desktop space" + # is though. + pos = wx.GetMousePosition() + + for imframe in gui.imwin.imtabs.all_imframes(): + notebook = imframe.notebook + localcoords = notebook._pages.ScreenToClient(pos) + notebook._pages._isdragging = True + where = notebook._pages.HitTest(localcoords)[0] + notebook._pages._isdragging = False + if not imframe == self.potentialNewWin and where == fnb.FNB_TAB: + # this is ugly, but having external DND handling requires us to fiddle with the class internals + page = self.frame.notebook.GetPage(draggedidx) + imframe.AddTab(page, True) + self.potentialNewWin.Destroy() + break + + else: + fadein(self.potentialNewWin, 'normal') + self.potentialNewWin.Raise() + + self.frame.notebook.RemovePage(draggedidx) + + # ---- Toolbar handling ---- + + def BuildToolBar(self): + """ + Create the toolbar for the native IM window frame. + """ + toolbar = self.frame.CreateToolBar() + + s = lambda name, d = None: skin.get('AppDefaults.FormattingBar.%s' % name, d) + icons = s('icons').get + actionIcons = skin.get('ActionsBar.Icons') + + iconsize = (24, 24) + eicon = icons('emote').Resized(iconsize) + + toolbar.AddLabelTool(actionIDs.BuddyOptions, "Buddy", wx.EmptyBitmap(32, 32)) + toolbar.AddLabelTool(actionIDs.Files, "Files", getattr(actionIcons, 'files').Resized(iconsize)) + toolbar.AddSeparator() + + app = wx.GetApp() + for attr, title, tooltip in capabilitiesbar.buttons: + if attr in ["files", "sms", "video"]: + continue + baricon = getattr(actionIcons, attr).Resized(iconsize) + buttonID = buttonIDs[attr] + toolbar.AddLabelTool(buttonID, title, baricon) + app.AddHandlerForID(buttonID, self.OnModeClicked) + + toolbar.AddSeparator() + toolbar.AddLabelTool(actionIDs.SetFont, "Font", getattr(actionIcons, "font").Resized(iconsize)) + toolbar.AddLabelTool(actionIDs.SetBackgroundColor, "Bg Color", getattr(actionIcons, "background").Resized(iconsize)) + toolbar.AddLabelTool(actionIDs.ChooseEmoticon, "Emoticon", eicon) + + toolbar.AddSeparator() + + toolbar.AddLabelTool(actionIDs.AddContact, "Add Contact", getattr(actionIcons, "addcontact").Resized(iconsize)) + toolbar.AddLabelTool(actionIDs.ViewPastChats, "View Past Chats", getattr(actionIcons, "viewpastchats").Resized(iconsize)) + toolbar.AddLabelTool(actionIDs.AlertMeWhen, "Alert Me When", getattr(actionIcons, "alertmewhen").Resized(iconsize)) + + toolbar.Realize() + + def ConnectToolBarEvents(self, event=None): + """ + Used to dynamically connect event handlers to the frame." + """ + app = wx.GetApp() + app.AddHandlerForID(actionIDs.SetFont, self.OnFontClicked) + app.AddHandlerForID(actionIDs.SetBackgroundColor, self.OnBCToolClicked) + app.AddHandlerForID(actionIDs.ChooseEmoticon, self.OnEmoteToolClicked) + app.AddHandlerForID(actionIDs.AddContact, self.OnAddContact) + app.AddHandlerForID(actionIDs.ViewPastChats, self.OnViewPastChats) + app.AddHandlerForID(actionIDs.AlertMeWhen, self.OnAlert) + app.AddHandlerForID(actionIDs.Files, self.OnFilesClicked) + app.AddHandlerForIDs(buttonIDs.values(), self.OnModeClicked) + + app.AddHandlerForID(actionIDs.FileTransferHistory, self.OnFileTransferHistory) + app.AddHandlerForID(actionIDs.SendFile, self.OnSendFile) + + def DisconnectToolBarEvents(self, event=None): + """ + Used to remove event handlers from the frame. + """ + app = wx.GetApp() + app.RemoveHandlerForID(actionIDs.SetFont) + app.RemoveHandlerForID(actionIDs.SetBackgroundColor) + app.RemoveHandlerForID(actionIDs.ChooseEmoticon) + app.RemoveHandlerForID(actionIDs.AddContact) + app.RemoveHandlerForID(actionIDs.ViewPastChats) + app.RemoveHandlerForID(actionIDs.AlertMeWhen) + app.RemoveHandlerForID(actionIDs.Files) + app.RemoveHandlerForIDs(buttonIDs.values()) + + def OnAddContact(self, event): + """ + Show the add contact dialog. + """ + if self.frame.notebook.ActiveTab: + buddy = self.frame.notebook.ActiveTab.Buddy + AddContactDialog.MakeOrShow(service = buddy.service, name = buddy.name) + + def OnViewPastChats(self, event): + """ + Load up the active buddy's past chats. + """ + + # FIXME: Why is this stuff gotten through IMControl? + if self.frame.notebook.ActiveTab: + im_control = self.frame.notebook.ActiveTab.IMControl + im_control.Buddy.view_past_chats(im_control.Account) + + def OnPageIconUpdated(self, message): + """ + Update the notebook when a convo's icon changes. + """ + + page = message.data[0] + icon = message.data[1] + + # FIXME: This should probably be part of the notebook + if self.frame: + assert getattr(self.frame.notebook, "_name", "") != "[unknown]" + #sys.stderr.write("icon = %r\n" % icon) + for pagenum in xrange(self.frame.notebook.GetPageCount()): + if self.frame.notebook.GetPage(pagenum) == page: + self.frame.notebook.UpdatePageImage(pagenum, icon) + self.frame.ToolBar.SetToolNormalBitmap(actionIDs.BuddyOptions, icon.Resized((32, 32))) + + if self.frame.notebook.ActiveTab == page: + self.frame.SetFrameIcon(icon) + + def OnPageTitleUpdated(self, message): + """ + Update the frame and notebook when a convo's name and/or typing status changes + """ + page = message.data[0] + title = message.data[1] + window_title = message.data[2] + + if self.frame: + assert getattr(self.frame.notebook, "_name", "") != "[unknown]" + pageInNotebook = False + for pagenum in xrange(self.frame.notebook.GetPageCount()): + if self.frame.notebook.GetPage(pagenum) == page: + self.frame.notebook.SetPageText(pagenum, title) + pageInNotebook = True + + if pageInNotebook: + if window_title: + self.frame.SetTitle(self.frame.WindowName + ' - ' + window_title) + else: + self.frame.SetTitle(self.frame.WindowName + ' - ' + title) + + def OnAlert(self, event): + """ + Show alert preferences + """ + gui.pref.prefsdialog.show('notifications') + + def OnFilesClicked(self, event): + """ + Show popup menu for Files button + """ + popup = wx.Menu() + + popup.Append(actionIDs.SendFile, _('Send File')) + popup.Append(actionIDs.FileTransferHistory, _('Transfer History')) + + self.frame.PopupMenu(popup, self.frame.ScreenToClient(wx.GetMousePosition())) + + def OnSendFile(self, event): + """ + Load up the active buddy's past chats. + """ + + # FIXME: Why is this stuff gotten through IMControl? + if self.frame.notebook.ActiveTab: + im_control = self.frame.notebook.ActiveTab.IMControl + im_control.Buddy.send_file() + + def OnFileTransferHistory(self, event): + FileTransferDialog.Display() + + def OnModeClicked(self, event): + """ + Toggle between IM/Info/Email/Video panes + """ + eventid = event.GetId() + for name in buttonIDs: + if buttonIDs[name] == eventid: + self.frame.notebook.ActiveTab.set_mode(name, toggle_tofrom = True) + break + + def OnFontClicked(self, event): + """ + Show the font dialog + """ + self.frame.notebook.ActiveTab.input_area.ShowModalFontDialog() + + def OnBCToolClicked(self, event): + """ + Set the background color for messages + """ + tc = self.frame.notebook.ActiveTab.input_area.tc + newcolor = wx.GetColourFromUser(self.frame, tc.BackgroundColour, _('Choose a background color')) + + if newcolor.IsOk(): + #input_area.tc.BackgroundColour = newcolor + attrs = tc.GetDefaultStyle() + attrs.SetBackgroundColour(newcolor) + tc.SetDefaultStyle(attrs) + + tc.Refresh() + self.frame.notebook.ActiveTab.FocusTextCtrl() + + def OnEmoteToolClicked(self, event): + """ + Show emoticon picker + """ + wx.MessageBox("Not working yet. (need to decide the best way to share code between this and the formattinb bar) Just type smileys by hand, it won't hurt. ;)") + #input_area = self.frame.notebook.ActiveTab.input_area + #point = wx.GetMousePosition() + #input_area.DisplayEmotibox(wx.Rect(point.x, point.y, 16, 16)) + +class DigsbyFlatNotebook(fnb.FlatNotebook): + """ + Version of FlatNotebook customized for Digsby to match UberBook impl. and so that we can use our own DND logic. + """ + + def __init__(self, *args, **kwargs): + fnb.FlatNotebook.__init__(self, *args, **kwargs) + + self.imageList = wx.ImageList(16, 16) + self.SetImageList(self.imageList) + + # FNB overrides needed for DND + self.isDragging = False + self._pages.Bind(wx.EVT_MOTION, self.OnNotebookMouseMove) + + def OnNotebookMouseMove(self, event): + """ + Determine whether a drag has started or finished and fire an event if so. + """ + where, tabidx = self._pages.HitTest(event.GetPosition()) + if event.Dragging() and where == fnb.FNB_TAB: + if not self.isDragging: + assert tabidx != -1 + event = FlatNotebookDragStarted(self.GetId(), draggedidx=tabidx) + self.ProcessEvent(event) + self.isDragging = True + + else: + if self.isDragging: + self.isDragging = False + + event.Skip() + + def Pages(self): + """ + Page iterator needed for compatibility with UberBook impl. + """ + pagelist = [] + for page in xrange(self.GetPageCount()): + pagelist.append(self.GetPage(page)) + + return pagelist + + def Add(self, ctrl, focus = None): + """ + Needed for compatibility w/ UberBook impl., adds the tab and fires off title and icon updated events. + """ + ctrl.show_actions_bar = False + page = self.AddPage(ctrl, "Chat Name", select = focus) + ctrl.update_title() + ctrl.update_icon() + return page + + def Insert(self, ctrl): + """ + Needed for compatibility w/UberBook, here it is the same as Add. + """ + self.Add(ctrl, False) + + def IndexForPage(self, ctrl): + """ + Retrieve the index for a particular control, or -1 if not found. + """ + for index in xrange(self.GetPageCount()): + if ctrl == self.GetPage(index): + return index + + assert False + return -1 + + def Remove(self, ctrl): + """ + Remove the control from the notebook. + """ + index = self.IndexForPage(ctrl) + assert index >= 0 + self.RemovePage(index) + + def GetTabCount(self): + """ + Needed for compatibility w/ UberBook impl, calls GetPageCount. + """ + return self.GetPageCount() + + @property + def ActiveTab(self): + """ + Property that returns the selected NoteBook page, or None if there isn't one. + """ + sel = self.GetSelection() + if sel != -1: + return self.GetPage(sel) + elif self.GetPageCount() > 0: + return self.GetPage(0) + + return None + + def UpdatePageImage(self, pagenum, icon): + """ + Update the tab's image - we need to also update it in the imageList. + """ + imageList = self.GetImageList() + iconnum = pagenum + if pagenum >= imageList.GetImageCount(): + imageList.Add(icon.Resized((16, 16))) + iconnum = imageList.GetImageCount() - 1 + else: + imageList.Replace(pagenum, icon.Resized((16, 16))) + + self.SetPageImage(pagenum, iconnum) + +class NativeNotebookPanel(wx.Panel): + """ + Top level panel for ImFrame that contains all controls the native version needs within it. + """ + def __init__(self, *args, **kwargs): + wx.Panel.__init__(self, *args, **kwargs) + + style = fnb.FNB_X_ON_TAB | fnb.FNB_ALLOW_FOREIGN_DND | fnb.FNB_NODRAG | fnb.FNB_BOTTOM | fnb.FNB_FANCY_TABS | fnb.FNB_NO_X_BUTTON + self.notebook = DigsbyFlatNotebook(self, wx.ID_ANY, style=style) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.notebook, 1, wx.EXPAND) + + self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnNotebookPageChanged) + self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CLOSED, self.OnNotebookPageClosed) + + self.Pages = self.notebook.Pages + self.ActiveTab = self.notebook.ActiveTab + + def OnNotebookPageClosed(self, event): + if self.notebook.GetPageCount() == 0: + tlw = wx.GetTopLevelParent(self.notebook) + tlw.Close() + return + + def OnNotebookPageChanged(self, event): + """ + Fire notifications so that the frame can handle changes to the active convo. + """ + page = self.notebook.GetPage(event.GetSelection()) + + icon = imwin_gui.icons.get(page.icontype, 'buddy')(page.Buddy) + pubsub.Publisher().sendMessage(('tab', 'icon', 'updated'), (page, icon)) + pubsub.Publisher().sendMessage(('tab', 'title', 'updated'), (page, page.Buddy.alias, None)) diff --git a/digsby/src/gui/imwin/imwin_roomlist.py b/digsby/src/gui/imwin/imwin_roomlist.py new file mode 100644 index 0000000..bf7f589 --- /dev/null +++ b/digsby/src/gui/imwin/imwin_roomlist.py @@ -0,0 +1,134 @@ +''' +Provides a RoomList control to the IM window, and the splitter that holds the +roomlist and the message area. +''' + +import wx +from util import callsback, Storage as S +from gui.toolbox import local_settings +from common import profile + +ROOMLIST_MINSIZE = (110, 10) + +class RoomListMixin(object): + def toggle_roomlist(self): + self.show_roomlist(not self.is_roomlist_shown()) + + self.roomlist_has_been_shown = getattr(self, 'roomlist_has_been_shown', False) or self.is_roomlist_shown() + + def is_roomlist_constructed(self): + return hasattr(self, 'roomlist') + + def is_roomlist_shown(self): + return self.is_roomlist_constructed() and self.roomlist.IsShown() + + def show_roomlist(self, show): + '''Shows or hides the roomlist.''' + + if show == self.is_roomlist_shown(): + return False + + with self.Frozen(): + multichat = self.GetButton('multichat') + if multichat is not None and multichat.IsActive() != show: + multichat.Active(show) + + # Hide the roomlist. + if not show: + self.roomlist_splitter.Unsplit(self.roomlist) + self.FocusTextCtrl() + return + + # Show the roomlist. + if not hasattr(self, 'roomlist'): + self.construct_roomlist() + self.roomlist.SetConversation(self.convo) + # if we try to set the sash pos higher than the roomlist minsize, + # then it is "frozen" for the first + # + self.roomlist.SetMinSize(ROOMLIST_MINSIZE) + + self.roomlist_splitter.SplitVertically(self.message_area, self.roomlist) + self._restore_roomlist_splitter() + + self.roomlist_has_been_shown = getattr(self, 'roomlist_has_been_shown', False) or show + + @callsback + def _on_invite_buddy(self, buddy, callback=None): + original_convo = self.convo + protocol = original_convo.protocol + + def doinvite(convo, buddy, callback=None): + protocol.invite_to_chat(buddy, convo, callback=callback) + + if self.convo.ischat: + self.roomlist.model.add_pending_contact(buddy) + return doinvite(original_convo, buddy, callback) + + def success(convo): + self.set_conversation(convo) + + buddies_to_invite = original_convo.other_buddies + if buddy not in buddies_to_invite: + self.roomlist.model.add_pending_contact(buddy) + buddies_to_invite.append(buddy) + + protocol.make_chat_and_invite(buddies_to_invite, convo=original_convo, success=success) + return True + + def construct_roomlist(self): + ''' + roomlist is constructed lazily + ''' + + from gui.uberwidgets.skinsplitter import SkinSplitter + from gui.imwin.imwin_gui import MSGSPLIT_FLAGS + + sash_pos = self.input_splitter.GetSashPosition() + + # construct the splitter + self.roomlist_splitter = SkinSplitter(self.input_splitter, MSGSPLIT_FLAGS) + self.roomlist_splitter.SetSashGravity(1) + self.roomlist_splitter.Bind(wx.EVT_LEFT_UP, self._save_roomlist_splitter) + + def _on_top_maximize(e): + e.Skip() + + # HACK: maximizing the IM window sometimes makes the roomlist splitter resize really big. + # restore the size 100ms after a maximize to fix this problem. + wx.CallLater(100, self._restore_roomlist_splitter) + + self.Top.Bind(wx.EVT_MAXIMIZE, _on_top_maximize) + + self.message_area.Reparent(self.roomlist_splitter) + + # construct the roomlist + from gui.imwin.roomlist import RoomListPanel + self.roomlist = RoomListPanel(self.roomlist_splitter, + inviteCallback = self._on_invite_buddy, + accountCallback = lambda: S(connection=self.convo.protocol)) + + self.do_input_split(self.roomlist_splitter) + self.input_splitter.SetSashPosition(sash_pos) + + def GetSectionName(self): + from common import profile + username = getattr(profile, 'username', None) + + return ' '.join(["Roomlist", username]) + + def _save_roomlist_splitter(self, e=None): + if e is not None: + e.Skip() + + profile.localprefs["roomlist_width"] = str(self.roomlist_splitter.Parent.Size.width - self.roomlist_splitter.GetSashPosition() + 3) + + + def _restore_roomlist_splitter(self): + if "roomlist_width" in profile.localprefs: + sash_pos = self.roomlist_splitter.Parent.Size.width - int(profile.localprefs["roomlist_width"]) - 3 + else: + sash_pos = self.roomlist_splitter.Parent.Size.width - ROOMLIST_MINSIZE[0] - 3 + + self.roomlist_splitter.SetSashPosition(sash_pos) + diff --git a/digsby/src/gui/imwin/imwin_tofrom.py b/digsby/src/gui/imwin/imwin_tofrom.py new file mode 100644 index 0000000..3eceb5d --- /dev/null +++ b/digsby/src/gui/imwin/imwin_tofrom.py @@ -0,0 +1,789 @@ +''' + +Controllers for the to/from combos in the IM window. + +''' +from __future__ import with_statement +import wx, sys +from wx import EVT_WINDOW_DESTROY, MessageBox +from operator import attrgetter + +from logging import getLogger; log = getLogger('imwin_tofrom'); info = log.info +LOG = log.debug + +from common import profile +from common.sms import normalize_sms +from util import repeat_guard, is_email, traceguard +from util.primitives.funcs import Delegate +from util.callbacks import wxcall + +from gui.toolbox import calllimit + +from gui.skin import get as skinget +from gui.uberwidgets.simplemenu import SimpleMenuItem, SimpleMenu + +from common.protocolmeta import emailprotocols, protocols + +def can_send_sms(service): + return service not in ('digsby', 'jabber', 'gtalk', 'fbchat') + +class IMControl(object): + 'Manages who you are IMing with, and from which account.' + + def __init__(self, imwin, capsbar, tocombo, fromcombo, onselection = None): + + self.imwin = imwin + self.capsbar = capsbar + self.tocombo = tocombo + self.fromcombo = fromcombo + + self.contact = None + self.blist = profile.blist + + self.register_observers() + + self.OnSelection = Delegate() + self.OnSwitchContact = Delegate() + + + self.msgarea = None + self.ischat = False + + +# def ToShowAdd(): +# toshow = (not self.Buddy.protocol.has_buddy_on_list(self.Buddy)) if self.Buddy and self.Account else False +# +# self.capsbar.badd.Show(toshow) +# wx.CallAfter(self.capsbar.cbar.OnReSize) + + +# self.OnSelection += ToShowAdd + + def register_observers(self): + accts = profile.connected_accounts + on_connaccts_changed = lambda *a, **k: wx.CallAfter(self.on_connaccts_changed, *a, **k) + + accts.add_observer(on_connaccts_changed, obj = self) + + def unregister_observers(): + accts.remove_observer(on_connaccts_changed) + if hasattr(self, 'contacts'): + for contact in self.contacts: + contact.remove_observer(self.buddy_status_changed, 'status') + + #TODO: ToCombo destruction is not the most reliable indication that we need + # to unregister this observer. + self.tocombo.Bind(EVT_WINDOW_DESTROY, lambda e: (e.Skip(), unregister_observers())) + + Buddy = property(attrgetter('contact')) + Account = property(attrgetter('account')) + + def Apply(self): + if self.ischat: return + + self.tocombo._control = self + + self.ApplyTo() + self.ApplyFrom() + + @property + def HasChoices(self): + 'Returns True if there is more than one possible To/From pair.' + + return len(self.to_menu_items) > 1 or len(self.from_menu_items) > 1 + + def ApplyTo(self): + tocombo = self.tocombo + if wx.IsDestroyed(tocombo) or self.ischat: + return + + tocombo.SetCallbacks(selection = self.OnToBuddySelected, value = None) + tocombo.SetItems(self.to_menu_items) + tocombo.SetValue(self.to_menu_item) + + + def ApplyFrom(self): + fromcombo = self.fromcombo + if wx.IsDestroyed(fromcombo): + return + + fromcombo.SetItems(self.from_menu_items) + fromcombo.SetValue(self.from_menu_item) + fromcombo.SetCallbacks(selection = self.OnFromBuddySelected, value = None) + + def OnToBuddySelected(self, item): + assert item in self.to_menu_items + assert len(self.contacts) == len(self.to_menu_items) + + contact = self.contacts[self.to_menu_items.index(item)] + + if self.set_target(contact): + info('selected %s', contact) + self.Apply() + + self.OnSelection() + + def OnFromBuddySelected(self, item): + assert item in self.from_menu_items + assert len(self.accounts) == len(self.from_menu_items) + + i = self.from_menu_items.index(item) + if self.set_account(self.accounts[i]): + self.from_menu_item = self.from_menu_items[i] + self.ApplyFrom() + + self.OnSelection() + + def contacts_changed(self, src, attr, old, new): + wx.CallAfter(self.contacts_changed_gui, src, attr, old, new) + + def contacts_changed_gui(self, src, attr, old, new): + self.generate_to_items() + if getattr(self.tocombo, '_control', None) is self: + self.Apply() + + def SetPersonality(self, personality): + pass + + def SetConvo(self, convo, contacts = None): + LOG('IMControl.SetConvo') + LOG(' convo: %r', convo) + LOG(' contacts: %r', contacts) + + self.ischat = convo.ischat + if self.ischat: + self.account = convo.protocol + return + + old_contacts = set(getattr(self, 'contacts', [])) + + if contacts is not None: + self.contacts = contacts + try: self.contacts.add_observer(self.contacts_changed) + except AttributeError: + pass + + self.generate_to_items() + + if not hasattr(self, 'contacts'): + self.contacts = [convo.buddy] + self.generate_to_items() + + new_contacts = set(self.contacts) + changed = self.buddy_status_changed + + for new in new_contacts - old_contacts: + new.add_observer(changed, 'status') + for old in old_contacts - new_contacts: + old.remove_observer(changed, 'status') + + if self.set_target(convo.buddy, from_account = convo.protocol): + self.Apply() + + + def buddy_status_changed(self, buddy, attr=None, old=None, new=None): + wx.CallAfter(self.buddy_status_changed_gui, buddy, attr, old, new) + + @calllimit(.5) + def buddy_status_changed_gui(self, buddy, attr=None, old=None, new=None): + + log.debug('buddy_status_changed %r %s (%s -> %s)', buddy, attr, old, new) + + try: + i = self.contacts.index(buddy) + except ValueError: + wx.CallLater(1500, self.proto_change) + else: + self.to_menu_items[i] = buddy_menu_item(self.contacts[i]) + if self.contact is buddy: + self.to_menu_item = self.to_menu_items[i] + + if getattr(self.tocombo, '_control', None) is self: + if wx.IsDestroyed(self.tocombo): + print >> sys.stderr, "WARNING: buddy_status_changed_gui being called but combo is destroyed" + else: + self.ApplyTo() + + def proto_change(self, *a): + self.generate_to_items() + log.debug('proto_change, new items: %r', self.to_menu_items) + + n, s = self.contact.name, self.contact.service + for i, c in enumerate(self.contacts): + if c.name == n and c.service == c: + self.contact = c + self.to_menu_item = self.to_menu_items[i] + + if getattr(self.tocombo, '_control', None) is self: + self.ApplyTo() + + def generate_to_items(self): + self.to_menu_items = [buddy_menu_item(b) for b in self.contacts] + + def set_to_item(self, contact): + #settoitem:print '+'*80 + #settoitem:print 'set_to_item' + #settoitem:print ' contact:', contact + + + item = None + for i, c in enumerate(self.contacts): + #settoitem:print ' ', i, c + #settoitem:print ' infokeys:', c.info_key, contact.info_key + if c.info_key == contact.info_key: + item = self.to_menu_items[i] + break + else: + with traceguard: + log.critical('could not match info_key %r against %r', + contact.info_key, [c.info_key for c in self.contacts]) + + if len(self.to_menu_items) > 0: + item = self.to_menu_items[i] + + self.to_menu_item = item + + #settoitem:print 'selected to item:', item + #settoitem:print '+'*80 + + def set_target(self, contact, from_account = None, disconnected=False): + info('set_target (%r): %r', self, contact) + + self.set_to_item(contact) + + contacts = self.contacts + + suggested_account, contact, self.accounts = self.blist.imaccts_for_buddy(contact, contacts, + force_has_on_list=disconnected) + + self.contact = contact + LOG('%r', contact) + LOG(' conn: %r', profile.connected_accounts) + LOG(' sugg: %r', suggested_account) + LOG(' capable: ' + ('%r, ' * len(self.accounts)), *self.accounts) + + self.account = from_account if from_account in self.accounts else suggested_account + + if type(self.contacts) is list and self.account is not None and self.account is not self.contact.protocol: + log.warning('account did not match contact protocol, changing') + self.contact = self.account.get_buddy(self.contact.name) + self.contacts = [self.contact] + self.contact.add_observer(self.contacts_changed) + wx.CallLater(2000, self.buddy_status_changed, self.contact, 'status', None, None) + self.OnSwitchContact(self.contact) + + log.warning('got %r with status %s', self.contact, self.contact.status) + + #fromlogics:print 'Final selections:', contact, self.account, '\n\n' + + self.set_to_item(self.contact) + self._update_menu_items() + return True + + def _update_menu_items(self): + self.from_menu_items = [account_menu_item(a) for a in self.accounts] + + if self.from_menu_items: + try: + idx = self.accounts.index(self.account) + except ValueError: + item = '' + else: + item = self.from_menu_items[idx] + else: + item = '' + + self.from_menu_item = item + + def set_account(self, account): + if self.account == account: + return False + + self.account = account + return True + + def on_connaccts_changed(self, *a): + + contact = self.contact + account = self.account + + if not self.imwin.convo.ischat and getattr(self.tocombo, '_control', None) is self: + if self.set_target(contact, account, disconnected=True): + self.Apply() + + wx.CallLater(1000, self.buddy_status_changed, self.contact) + + if self.account != account and self.msgarea is not None: + if account is not None: + self.imwin.convo.system_message(_('{username} disconnected').format(username=account.username)) + + if self.account is not None and self.contact is not None: + self.imwin.convo.system_message(_('now sending from: {username}').format(username=self.account.username)) + + + def __repr__(self): + return '' + + +class ComboListEditor(object): + ''' + Given an UberCombo and a list of strings, allows the combo to "edit" + the list. + ''' + + def __init__(self, combo, OnLoseFocus): + self.combo = combo + + # the "remove" menu contains every item + menu = combo.menu + self.remove_menu = SimpleMenu(menu, combo.menuskin, callback = self.OnRemove) + + self.remove_item = SimpleMenuItem(_('Remove'), menu = self.remove_menu) + self.add_item = SimpleMenuItem(_('Add...'), method = self.OnAdd) + + self.OnLoseFocus = OnLoseFocus + + def SetContentCallback(self, cb): + if not hasattr(cb, '__call__'): raise TypeError + self.content_cb = cb + + def EditList(self, seq, selected_item = None, + add_cb = None, + remove_cb = None, + default = ''): + + self.default = default + + if add_cb is not None: self.add_cb = add_cb + if remove_cb is not None: self.remove_cb = remove_cb + + assert isinstance(seq, list), repr(list) + self.seq = list(seq) + self.menu_items = [SimpleMenuItem(self.content_cb(i)) for i in seq] + self.remove_menu.SetItems(list(self.menu_items)) + + if selected_item is not None: + self.selection = seq.index(selected_item) + else: + self.selection = -1 if len(seq) == 0 else 0 + + def Apply(self): + items = list(self.menu_items) # Normal Items + + if len(self.menu_items): + items.append(SimpleMenuItem(id = -1)), # ---- a separator + + items.append(self.add_item) # Add + + if len(self.menu_items): + items.append(self.remove_item) # Remove (if there are items to remove) + + combo = self.combo + combo.SetCallbacks(selection = self.OnComboSelection, + value = self.OnValue) + combo.menu.SetItems(items) + + newval = self.menu_items[self.selection] if len(self.menu_items) and self.selection != -1 else self.default + combo.ChangeValue(newval) + + def OnComboSelection(self, item): + LOG('OnSelection: %r', item) + + self.selection = self.menu_items.index(item) + self.combo.ChangeValue(item) + self.OnLoseFocus() + + def GetValue(self): + + return self.menu_items[self.selection].GetContentAsString() if self.selection != -1 else (None if not self.default else self.default) + + def OnAdd(self, item): + 'Invoked when the "Add" menu item is clicked.' + + self.combo.EditValue('') + + def OnValue(self, val): + wx.CallAfter(self.OnValueLater, val) + + def OnValueLater(self, val): + + # This method is called when the editable part of the combo loses focus. + # - if the combo is on screen, the user hit enter or otherwise caused this to happen. + # in this case, perform validation and display an error dialog if the validation fails + # - otherwise, maybe another app stole focus--some external event cause this to happen + # popping up a modal dialog would be bad, so just return the value to what it was before + + if val == '' or not self.combo.IsShown() or not self.combo.Top.IsActive(): + + LOG("Val: %s\nCombo is ShownOnScreen: %s\nTop level parent is Active: %s", + val, self.combo.IsShownOnScreen(), not self.combo.Top.IsActive()) + + if val == '': LOG('empty string, selection is %s', self.selection) + else: LOG('combo not on screen, falling back to %s', self.selection) + + # Restore the combo's value. + if self.combo.Top.IsActive(): + self.OnLoseFocus() + return self.combo.ChangeValue(self.menu_items[self.selection] if self.selection != -1 else self.default) + + val = self.add_cb(val) + + if val is None: + self.combo.EditValue() + else: + LOG('add_cb returned True') + + self.seq.append(val) + self.menu_items.append(SimpleMenuItem(self.content_cb(val))) + self.remove_menu.AppendItem(SimpleMenuItem(self.content_cb(val))) + self.selection = len(self.menu_items)-1 + self.Apply() + + + def OnRemove(self, item): + 'Invoked when one of the "remove" menu items is clicked.' + i = self.remove_menu.GetItemIndex(item) + assert len(self.menu_items) <= len(self.remove_menu) + + if self.remove_cb(self.seq[i]): + LOG('removing item %d (selection is %s)', i, self.selection) + self.seq.pop(i) + self.remove_menu.RemoveItem(i) + self.menu_items.pop(i) + + if i == self.selection: + l = len(self.menu_items) + if l: + i = i % len(self.menu_items) + newval = self.menu_items[i] + else: + i = -1 + newval = self.default + self.combo.ChangeValue(newval) + elif i < self.selection: + i -= 1 + + self.selection = i + LOG('now selection is %s', self.selection) + self.Apply() + +class BInfoControl(object): + def __init__(self, tocombo, fromcombo, info_attr, content_cb): + self.tocombo, self.fromcombo = tocombo, fromcombo + self._editing = False + + assert isinstance(info_attr, str) + self.info_attr = info_attr + + self.OnLoseFocus = Delegate() + e = self.to_editor = ComboListEditor(tocombo, self.OnLoseFocus) + e.SetContentCallback(content_cb) + + self.blist = profile.blist + +class SMSControl(BInfoControl): + def __init__(self, tocombo, fromcombo): + BInfoControl.__init__(self, tocombo, fromcombo, 'sms', sms_menu_content) + self.acct = None + + caccts = profile.connected_accounts + + cb = repeat_guard(lambda *a, **k: wx.CallAfter(self.conn_accts_changed, *a, **k)) + caccts.add_observer(cb, obj = self) + self.conn_accts_changed(caccts) + self.tocombo.Bind(EVT_WINDOW_DESTROY, lambda e: (e.Skip(), caccts.remove_observer(cb))) + + def conn_accts_changed(self, *a): + wx.CallAfter(self.update_senders) + + def update_senders(self): + LOG('SMSControl: connected accounts changed') + senders = self.sms_senders() + LOG("%r", senders) + self.from_menu_items = [account_menu_item(a) for a in senders] + + if self.acct not in senders: + self.acct = senders[0] if senders else None + + if getattr(self.tocombo, '_control', None) is self: + self.Apply() + + @property + def ToSMS(self): + 'Returns the currently selected SMS number. (can be None)' + + if self.contact_sms: + return self.contact_sms + else: + return self.to_editor.GetValue() + + @property + def FromAccount(self): + 'Returns the currently selected account to send SMS messages from. (can be None)' + + return self.acct + + def SetContact(self, contact): + self.info_key = contact.info_key + self.contact_sms = None #contact.name if contact.sms else None + self.update_items() + + def get_contact_sms(self): + sms = self.blist.get_contact_info(self.info_key, self.info_attr) + + if not isinstance(sms, list): + return [] + + return [s for s in sms if isinstance(s, basestring)] + + def sms_senders(self): + return [a.connection for a in profile.connected_accounts + if a.connection is not None and can_send_sms(a.connection.service)] + + def update_items(self): + # get list of SMS numbers for our contact + sms = self.get_contact_sms() + + if self.contact_sms: + self.sms_item = SimpleMenuItem([self.contact_sms]) + else: + + # obtain a "best choice" SMS number + best_sms, best_acct = self.blist.get_tofrom_sms(self.info_key) + + self.to_editor.EditList(sms, best_sms, + add_cb = self.OnAddSMS, + remove_cb = self.OnRemoveSMS) + + def Apply(self): + self.tocombo._control = self + + # Set To + if self.contact_sms: + self.tocombo.SetCallbacks(None, None) + self.tocombo.SetItems([]) + self.tocombo.ChangeValue(self.contact_sms) + else: + wx.CallAfter(self.to_editor.Apply) + + # Set From + senders = self.sms_senders() + i = senders.index(self.acct) if self.acct in senders else -1 + self.fromcombo.SetItems(self.from_menu_items) + self.fromcombo.ChangeValue(self.from_menu_items[i] if i != -1 else '') + self.fromcombo.SetCallbacks(None, None) + + def OnAddSMS(self, sms): + blist = self.blist + smslist = blist.get_contact_info(self.info_key, 'sms') + smslist = list(smslist) if smslist is not None else [] + + try: + sms = normalize_sms(sms) + except ValueError: + MessageBox(_('Please enter a valid SMS number (ie: 555-555-5555 or 5555555555)'), + _('Invalid SMS Number'), style = wx.ICON_ERROR) + else: + if sms not in smslist: + smslist.append(sms) + blist.set_contact_info(self.info_key, 'sms', smslist) + self.OnLoseFocus() + return sms + else: + MessageBox(_("That SMS number is already in this buddy's list."), + _('Add SMS Number')) + + + def OnRemoveSMS(self, sms): + blist = self.blist + smslist = blist.get_contact_info(self.info_key, 'sms') + smslist = list(smslist) if smslist is not None else [] + + if sms in smslist: + smslist.remove(sms) + blist.set_contact_info(self.info_key, 'sms', smslist) + self.OnLoseFocus() + return True + +class EmailControl(BInfoControl): + def __init__(self, tocombo, fromcombo): + BInfoControl.__init__(self, tocombo, fromcombo, 'email', email_menu_content) + + self._obs_link = None + self.setup_from_accts() + self.OnEmailAccountChanged = Delegate() + + @property + def ToEmail(self): + return self.to_editor.GetValue() + + @property + def FromAccount(self): + return self.acct + + def setup_from_accts(self): + # add an "Add" menu containing each email account type + self.addList = addList = SimpleMenu(self.fromcombo.menu, 'simplemenu') + for emailtype in emailprotocols.keys(): + addList.Append([skinget('serviceicons.%s' % emailtype).Resized(16), + protocols[emailtype].name], + method = lambda item, emailtype=emailtype: add_email_account(emailtype)) + + # link to creating an email account + from gui.pref import prefsdialog + self.accounts_item = SimpleMenuItem(_('Accounts...'), method = lambda *a: prefsdialog.show('accounts')) + + # watch the email list for changes + pass + + self._obs_link = profile.emailaccounts.add_list_observer(self.on_email_accounts_changed, self.on_email_accounts_changed, 'enabled') + + self.tocombo.Bind(EVT_WINDOW_DESTROY, lambda e: (e.Skip(), self.unregister_observers())) + + def unregister_observers(self, *a): + if self._obs_link is not None: + self._obs_link.disconnect() + self._obs_link = None + profile.emailaccounts.remove_list_observer(self.on_email_accounts_changed, self.on_email_accounts_changed, 'enabled') + + @wxcall + def on_email_accounts_changed(self, src, attr, old, new): + if getattr(self.tocombo, '_control', None) is self: + self.ApplyFrom() + + def SetContact(self, contact): + self.info_key = contact.info_key + hint = getattr(contact, 'email_hint', '') + email_list = self.blist.get_contact_info(self.info_key, 'email') + + if email_list is None: email_list = [] + + self.to_editor.EditList(email_list, + add_cb = self.OnAddEmail, + remove_cb = self.OnRemoveEmail, + default = hint) + + + def OnAddEmail(self, email): + blist = self.blist + emails = blist.get_contact_info(self.info_key, 'email') + emails = list(emails) if emails is not None else [] + + if not is_email(email): + MessageBox(_('Please enter a valid email address (ie: john123@digsby.com)'), + _('Invalid Email Address'), style = wx.ICON_ERROR) + + elif email in emails: + MessageBox(_('That email is already registered with this buddy.'), + _('Add Email Address')) + + else: + emails.append(email) + blist.set_contact_info(self.info_key, 'email', emails) + self.OnLoseFocus() + return email + + + def OnRemoveEmail(self, email): + blist = self.blist + emails = blist.get_contact_info(self.info_key, 'email') + emails = list(emails) if emails is not None else [] + + if email in emails: + emails.remove(email) + blist.set_contact_info(self.info_key, 'email', emails) + self.OnLoseFocus() + return True + + def Apply(self): + self.tocombo._control = self + + LOG('Control.Apply: %s', self.info_attr) + self.to_editor.Apply() + self.ApplyFrom() + + def ApplyFrom(self): + frm = self.fromcombo + accts = [a for a in profile.emailaccounts if a.enabled] + + #TODO: UberCombo doesn't store items :( + + def sel(item, acct): + frm.ChangeValue(item) + self.OnEmailAccountChanged(acct) + self.acct = acct + + # Create menu items for each enabled email account. + fitems = self.from_menu_items = [] + for a in accts: + item = SimpleMenuItem(emailacct_menu_content(a), method = lambda item, a=a: sel(item, a)) + item.acct = a + fitems.append(item) + + # Add extra account managment items. + items = list(fitems) + + if len(fitems): + items.append(SimpleMenuItem(id = -1)) # separator + + items.append(SimpleMenuItem(_('Add Email Account'), menu = self.addList)) + items.append(self.accounts_item) + + self.fromcombo.SetCallbacks(None, None) + self.fromcombo.SetItems(items) + + + if accts: + try: i = accts.index(getattr(self, 'acct', None)) + except ValueError: i = 0 + + sel(fitems[i], accts[i]) + else: + self.fromcombo.ChangeValue('') + self.OnEmailAccountChanged(None) + + + + +tofromIconSize = lambda: int(skinget('capabilities.tofrom.iconsize', 16)) + + +def buddy_menucontent(buddy): + return [skinget("statusicons.%s" % buddy.status_orb), + buddy.serviceicon.Resized(tofromIconSize()), buddy.nice_name] + +def buddy_menu_item(buddy): + 'Return a menu item object for a "To" buddy.' + + return SimpleMenuItem(buddy_menucontent(buddy)) + +def account_menucontent(acct): + 'Returns the menu content for a "from" account.' + + return [acct.serviceicon.Resized(tofromIconSize()), + acct.username] + +def account_menu_item(acct): + 'Return a menu item object for a "From" account.' + + return SimpleMenuItem(account_menucontent(acct)) + +def email_menu_content(email): + return [email] + +def sms_menu_content(sms): + return [sms] + +def emailacct_menu_content(emailacct): + return [emailacct.icon.Resized(16), emailacct.display_name] + +def add_email_account(protocol_name): + from gui.accountdialog import AccountPrefsDialog + import wx + diag = AccountPrefsDialog.create_new(wx.GetTopLevelWindows()[0], protocol_name) + res = diag.ShowModal() + + if diag.ReturnCode == wx.ID_SAVE: + profile.add_email_account(**diag.info()) + + diag.Destroy() diff --git a/digsby/src/gui/imwin/imwindnd.py b/digsby/src/gui/imwin/imwindnd.py new file mode 100644 index 0000000..95102de --- /dev/null +++ b/digsby/src/gui/imwin/imwindnd.py @@ -0,0 +1,58 @@ +import wx +import util +from gui import contactdialogs + +class ImWinDropTarget( wx.PyDropTarget ): + def __init__(self, parent): + wx.PyDropTarget.__init__( self ) + self.dragged = wx.DataObjectComposite() + self.imwin = parent + + # This drop target will receive certain types of draggable objects. + import contacts.contactsdnd as contactsdnd + drag_types = dict(file = wx.FileDataObject(), + text = wx.TextDataObject(), + bitmap = wx.PyBitmapDataObject(), + blist_item = contactsdnd.dataobject()) + + for dt, dobjs in drag_types.iteritems(): + setattr(self.dragged, dt, dobjs) + + # Add to the wx.DataObjectComposite item, and set as our data object. + for v in drag_types.itervalues(): + self.dragged.Add(v) + + self.SetDataObject( self.dragged ) + + def OnEnter(self, x, y, d): return d + def OnDrop(self, x, y): return True + def OnDragOver(self, x, y, d): return wx.DragMove + + def OnData(self, x, y, d): + if not self.GetData(): + return + + dragged = self.dragged + dropped = util.Storage(files = dragged.file.GetFilenames(), + bitmap = dragged.bitmap.GetBitmap(), + text = dragged.text.GetText(), + blist_item = dragged.blist_item.GetData()) + + import contacts.contactsdnd as contactsdnd + + #print 'format count', dragged.GetFormatCount() + #print 'is supported', dragged.IsSupported(contactsdnd.dataformat()) + #print 'BLIST_ITEM', repr(dropped.blist_item) + + if dropped.files: + self.imwin.Top.Raise() + contactdialogs.send_files(self.imwin, self.imwin.convo.buddy, dropped.files) + return True + if dropped.bitmap: + return True + if dropped.text: + self.imwin.input_area.SetValue(dropped.text) + self.imwin.input_area.tc.SetSelection(-1, -1) + return True + + return False diff --git a/digsby/src/gui/imwin/imwinmenu.py b/digsby/src/gui/imwin/imwinmenu.py new file mode 100644 index 0000000..09afe3b --- /dev/null +++ b/digsby/src/gui/imwin/imwinmenu.py @@ -0,0 +1,174 @@ +''' +Menus in the IM window. +''' +import traceback + +import wx +from common import pref +import gui.toolbox +from gui import clipboard + +# Create all the menu items for each button in the capabilities bar. +buttons = [('info', _('Buddy Info')), + ('im', _('Send IM')), + ('files', _('Send File')), + #('pictures', _('Send Pictures')), + ('email', _('Send Email')), + ('sms', _('Send SMS'))] + +#class ImWinMenuMixin(object): + +def add_menus(ctrl): + from new import instancemethod + + t = type(ctrl) + t.GetTextCtrlMenu = GetTextCtrlMenu + t.TextCtrlMenu = property(GetTextCtrlMenu) + t.GetMenu = instancemethod(GetMenu, None, t) + t._onpopupshown = instancemethod(_onpopupshown, None, t) + +def GetTextCtrlMenu(self): + from gui.uberwidgets.umenu import UMenu + + m, tc = UMenu(self), self.input_area.tc + + # spelling suggestions and options + from gui.spellchecktextctrlmixin import add_spelling_suggestions + if add_spelling_suggestions(tc, m): + m.AddSep() + + m.AddItem(_('Copy'), id = wx.ID_COPY, callback = tc.Copy) # todo: disable if "not tc.CanCopy()" when shown. + m.AddItem(_('Paste'), id = wx.ID_PASTE, callback = tc.Paste) + m.AddSep() + + m.AddPrefCheck('messaging.show_actions_bar', _('Show &Actions Bar')) + m.AddPrefCheck('messaging.show_formatting_bar', _('Show &Formatting Bar')) + m.AddPrefCheck('messaging.show_send_button', _('Show Send Button')) + m.AddSep() + gui.toolbox.add_rtl_checkbox(tc, m) + + return m + +def GetMenu(self): + try: + self._menu.Destroy() + except AttributeError: + pass + + from gui.uberwidgets.umenu import UMenu + m = UMenu(self, onshow = self._onpopupshown) + + self._menu = m + self._topid = id(self.Top) + + self.keep_on_top = m.AddCheckItem(_('&Keep on Top'), callback = self.Top.ToggleOnTop) + self.view_past_chats_item = m.AddItem(_('&View Past Chats'), callback = self._on_view_past_chats) + + m.AddSep() + + add, addcheck, setmode = m.AddItem, m.AddCheckItem, self.set_mode + + c = self.capsbuttons = {} + buddy = self.Buddy + for bname, caption in buttons: + if bname == 'files': + c[bname] = add(caption, callback = lambda: self.Buddy.send_file()) + else: + if buddy is None or (bname == 'sms' and 'SMS' not in buddy.caps): + continue + + c[bname] = addcheck(caption, callback = lambda b = bname: setmode(b, toggle_tofrom = b == 'im')) + + m.AddSep() + + self.edit_items = {} + self.edit_items['Copy'] = add(_('Copy\tCtrl+C'), id = wx.ID_COPY) + + # add a "Copy Link" item if the mouse is hovering over a link + message_area = getattr(self, 'message_area', None) + if message_area is not None: # may not be created yet. + ctrl = wx.FindWindowAtPointer() + if ctrl is message_area: + info = ctrl.HitTest(ctrl.ScreenToClient(wx.GetMousePosition())) + if info.Link: + add(_('Copy &Link'), callback = lambda: clipboard.copy(info.Link)) + + self.paste_item = self.edit_items['Paste'] = add(_('Paste\tCtrl+V'), id = wx.ID_PASTE) + self.paste_index = len(self._menu) - 1 + + if pref('debug.message_area.show_edit_source', False): + add(_('Edit Source'), callback = lambda: self.message_area.EditSource()) + if pref('debug.message_area.show_jsconsole', False): + from gui.browser import jsconsole + add(_('&Javascript Console'), callback = lambda: jsconsole.show_console()) + + # text size menu + if message_area is not None and message_area.IsShownOnScreen(): + textsize_menu = UMenu(self) + self.textbigger = textsize_menu.AddItem(_('&Increase Text Size\tCtrl+='), + callback = message_area.IncreaseTextSize) + self.textsmaller = textsize_menu.AddItem(_('&Decrease Text Size\tCtrl+-'), + callback = message_area.DecreaseTextSize) + textsize_menu.AddSep() + textsize_menu.AddItem(_('&Reset Text Size\tCtrl+0'), callback = message_area.ResetTextSize) + + + m.AddSubMenu(textsize_menu, _('&Text Size')) + + m.AddSep() + + # these checkboxes affect a global preference that immediately takes effect in + # all open ImWins + self.roomlist_item = m.AddCheckItem(_('Show &Room List'), callback = self.toggle_roomlist) + self.actions_item = m.AddPrefCheck('messaging.show_actions_bar', _('Show &Actions Bar')) + self.formatting_item = m.AddPrefCheck('messaging.show_formatting_bar', _('Show &Formatting Bar')) + + return self._menu + +def _onpopupshown(self, menu): + 'Invoked just before the conversation area menu is shown.' + + # Should we show the paste item? + ctrl = wx.FindWindowAtPointer() + + # Retarget "edit" menu items + for name, item in self.edit_items.iteritems(): + #TODO: why does removing "Paste" cause a hard crash? + method = getattr(ctrl, name, None) + + capable_method = getattr(ctrl, 'Can%s' % name, None) + try: + should_enable = False + if capable_method is not None: + should_enable = capable_method() + except Exception: + traceback.print_exc() + item.Enable(method is not None and (ctrl is not getattr(self, 'message_area', None) or name != 'Paste')) + else: + item.Enable(should_enable) + + if method is not None: + item.SetCallback(method) + + # "always on top" checkbox: checked if on top + self.keep_on_top.Check(self.Top.OnTop) + + # menu items for capabilities (Info, IM, SMS, ...) + # "active" if ImWin is in that mode + Button = self.capsbar.GetButton + buddy = self.Buddy + + from common import caps + + for bname, menuitem in self.capsbuttons.iteritems(): + if menuitem.IsCheckable(): + menuitem.Check(self.mode == bname) + + if bname == 'files': + menuitem.Enable(buddy is not None and buddy.online and caps.FILES in buddy.caps) + elif bname == 'sms': + menuitem.Enable(buddy is not None and caps.SMS in buddy.caps) + elif bname == 'email': + menuitem.Enable(buddy is not None and caps.EMAIL in buddy.caps) + + self.roomlist_item.Check(self.is_roomlist_shown()) diff --git a/digsby/src/gui/imwin/messagearea.py b/digsby/src/gui/imwin/messagearea.py new file mode 100644 index 0000000..f599ffd --- /dev/null +++ b/digsby/src/gui/imwin/messagearea.py @@ -0,0 +1,289 @@ +''' + +MessageArea + + Given Message objects and a MessageStyle, this class knows how to fill the IM + window's browser area with content. + + It also applies IM window specific text transformations to messages like + linkify and emoticons, and shows "date statuses" between messages that + happened in different days. + +''' +from __future__ import with_statement + +from datetime import datetime + +from gui.imwin.styles.stripformatting import strip +from gui.browser.webkit import WebKitWindow +from gui.toolbox.scrolling import WheelScrollCtrlZoomMixin, WheelShiftScrollFastMixin, ScrollWinMixin,\ + FrozenLoopScrollMixin + +from common import pref, prefprop +from util import Storage as S, takemany, preserve_whitespace, traceguard +from util.primitives.misc import toutc, fromutc + +from logging import getLogger; log = getLogger('msgarea') + +from common.Conversation import AUTORESP + +class quiet_log_messages(object): + ''' + used by hidden messages system to hide certain messages when replaying logs. + see imhub.py + ''' + messages = set() + + def __init__(self, messages): + self._messages = messages + + def __enter__(self): + quiet_log_messages.messages = set(self._messages) + + def __exit__(self, exc_type, exc_val, exc_tb): + quiet_log_messages.messages = set() + +def history_enabled(): + 'Returns True if the IM window should show history when appearing.' + + return pref('conversation_window.show_history', True) and pref('log.ims', True) + +def should_show_time(tstamp1, tstamp2): + ''' + Given two datetime objects, returns True if a "date status" should be + shown between them. + ''' + # show on date boundaries, but + # convert to local before comparing the dates + return fromutc(tstamp1).date() != fromutc(tstamp2).date() + + +try: + import webview +except: + import wx.webview as webview + +class MessageArea(FrozenLoopScrollMixin, ScrollWinMixin, WheelShiftScrollFastMixin, WheelScrollCtrlZoomMixin, WebKitWindow): + def __init__(self, parent, header_enabled = True, prevent_align_to_bottom=False): + super(MessageArea, self).__init__(parent) + + self.inited = False + self.header_enabled = header_enabled + self.prevent_align_to_bottom = prevent_align_to_bottom + + self.Bind(webview.EVT_WEBVIEW_BEFORE_LOAD, self._before_load) + + date_context_format = '%A, %B %d, %Y' + + show_fonts = prefprop('appearance.conversations.show_message_fonts', True) + show_colors = prefprop('appearance.conversations.show_message_colors', True) + show_emoticons = prefprop('appearance.conversations.emoticons.enabled', True) + htmlize_links = prefprop('appearance.conversations.htmlize_links', True) + + def _before_load(self, e): + e.Skip() + if e.NavigationType == webview.WEBVIEW_NAV_LINK_CLICKED: + url = e.URL + if url.startswith('digsby://'): + from common import urlhandler + handle_result = urlhandler.handle(url) + if handle_result.cancel_navigation: + e.Cancel() + else: + if url != handle_result.url: + e.SetURL(url) + + def init_content(self, theme, chatName = None, buddy = None, show_history = None, prevent_align_to_bottom=False): + # grab initial theme contents + self.theme = theme + + # the message area keeps a timestamp, updated with each message, to + # know when to insert a "date status" line + now = datetime.now() + self.tstamp = toutc(datetime(now.year, now.month, now.day)) + + show_header = self.header_enabled and pref('appearance.conversations.show_header', False) + initialContents = theme.initialContents(chatName, buddy, show_header, prevent_align_to_bottom=prevent_align_to_bottom) + + self.SetPageSource(initialContents, theme.baseUrl) + + if show_history is None: + show_history = True + + if show_history and history_enabled() and buddy is not None: + with traceguard: + self.show_history(buddy) + + self.inited = True + + def show_history(self, buddy): + num_lines = max(0, pref('conversation_window.num_lines', 5, int)) + + if num_lines > 0: + logsource = buddy + if pref('conversation_window.merge_metacontact_history', False): + from common import profile + metacontact = profile.metacontacts.forbuddy(buddy) + if metacontact: logsource = list(metacontact).pop() + + msgobjs = reversed(list(takemany(num_lines, logsource.history))) + self.replay_messages(msgobjs, buddy) + + def replay_messages(self, msgobjs, buddy, context = True): + 'Displays a sequence of message objects.' + + next = False + oldname = None + olddirection = None + + num_messages = 0 + skipped_messages = 0 + + for msg in msgobjs: + num_messages += 1 + + if msg in quiet_log_messages.messages: + skipped_messages += 1 + if __debug__: log.info('skipping message: %r', msg) + continue + + # the theme needs to know which messages to glue together as "adjacent"-- + # so we check the buddy name and the direction attribute for changes. + name = msg.buddy.name + direction = msg.type + next = oldname == name and direction == olddirection + + msg.buddy = buddy_lookup(buddy, name) + msg.content_type = 'text/html' + + # context means "history" here + self.format_message(direction, msg, next, context = context) + + oldname = name + olddirection = direction + + log.info('replay_messages: %d total messages (%d skipped) for buddy %r', num_messages, skipped_messages, buddy) + + def show_header(self, show): + 'Show or hide the message header.' + + return self.RunScript(self.theme.show_header_script(show)) + + def date_status(self, dt): + # "Status" messages are reused as a form of date context for displaying + # old messages in the history, and for when an IM window is open for + # more than a day. + + # displayed timestamps need to be converted from UTC->local + format_dt = fromutc(dt) + + return S(message = format_dt.strftime(self.date_context_format), + timestamp = dt, + buddy = None, + type = None) + + def format_message(self, messagetype, messageobj, next = False, context = False): + ''' + messagetype status, incoming, or outgoing + messageobj a storage with buddy, message, timestamp, conversation + next True if this message is from the same sender as the last one + context True if this message is being displayed as message history + ''' + if messagetype != 'status': + + # don't show date for the first message in the window + msgtime = messageobj.timestamp + + if msgtime is None: + msgtime = datetime.utcnow() + + # do the timestamps differ enough to show a date context line? + if should_show_time(self.tstamp, msgtime): + self.theme.set_always_show_timestamp(True) + try: + self.format_message('status', self.date_status(msgtime), next = False, context = context) + finally: + self.theme.set_always_show_timestamp(False) + + # This can't be a "next" message, since we just showed a status line. + next = False + + self.tstamp = msgtime + + content_type = getattr(messageobj, 'content_type', 'text/plain') + + if content_type == 'text/plain': + messageobj.message = messageobj.message.encode('xml') + messageobj.message = preserve_whitespace(messageobj.message) + messageobj.content_type = 'text/html' + + # apply text transformations, including emoticons and stripping + # colors and formatting (if enabled) + show_colors = self.theme.allow_text_colors and self.show_colors + show_emoticons = pref('appearance.conversations.emoticons.pack') if self.show_emoticons else None + + transforms = dict(emoticons = show_emoticons, + links = self.htmlize_links, + spaces = True) + + if not getattr(messageobj, 'linkify', True): + transforms['links'] = False + + stripped, strip_values = strip(messageobj.message, + formatting = not self.show_fonts, + colors = not show_colors, + plaintext_transforms = transforms) + + messageobj = messageobj.copy() + messageobj.message = stripped + + if getattr(messageobj, 'has_autotext', False): + extra = {} + else: + extra = dict(has_autotext = True, autotext = AUTORESP) + + # If there is one background color, pass it to the conversation theme + if self.show_colors: + extra.update(handle_colors(strip_values)) + + # get the JS function and complete message (including theme elements) + func, msg = self.theme.format_message(messagetype, messageobj, next, context, **extra) + + if func: + script = "%s('%s');" % (func, js_escape(msg)) + self.RunScript(script) + +def handle_colors(vals): + 'vals : defaultdict(list) of {keys: ["attrs",...]}' + + bodycolor = None + + if 'bgcolor' in vals: + bodycolor = vals['bgcolor'][0] + elif 'back' in vals: + bodycolor = vals['back'][0] + + if bodycolor: + return {'textbackgroundcolor': bodycolor} + + return {} + +def js_escape(msg): + # TODO: find a real escape function + return (msg.replace('\\', '\\\\') + .replace('\n', '\\\n') + .replace('\r', '\\\r') + .replace("'", "'")) + +def buddy_lookup(buddy, name): + protocol = getattr(buddy, 'protocol', None) + + # protocol might be offline, or missing a buddies dictionary. + if protocol is not None and hasattr(protocol, 'buddies') and protocol.buddies: + try: + buddy = buddy.protocol.get_buddy(name) + except Exception: + import traceback + traceback.print_exc_once() + + return buddy diff --git a/digsby/src/gui/imwin/roomlist.py b/digsby/src/gui/imwin/roomlist.py new file mode 100644 index 0000000..1b4f939 --- /dev/null +++ b/digsby/src/gui/imwin/roomlist.py @@ -0,0 +1,619 @@ +''' + +IM window roomlist/multichat management + +''' + +if __name__ == '__main__': + import gettext; gettext.install('Digsby') + +import wx +from time import time +from gui.textutil import default_font +from util.primitives.mapping import Storage +from util.primitives.error_handling import try_this +from gui.buddylist.renderers import get_buddy_icon +from util.observe import ObservableList +from gui.skin.skinobjects import SkinColor +from gui import skin +from gui.toolbox import update_tooltip +import gui.imwin + +from util.Events import EventMixin + +from logging import getLogger; log = getLogger('roomlist') + +S = Storage + +class TextControl(wx.TextCtrl): + def __init__(self, parent, value=None, empty_text=None): + wx.TextCtrl.__init__(self, parent) + + self.EmptyText = empty_text + self.Value = value or '' + + self.defaultcolor = self.GetForegroundColour() + self.emptycolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT) + + self.BBind(KILL_FOCUS=self.OnLoseFocus, + SET_FOCUS=self.OnSetFocus) + + def OnLoseFocus(self, e): + e.Skip() + if self.EmptyText and self.Value == '': + self.SetForegroundColour(self.emptycolor) + self.Value = self.EmptyText + + def OnSetFocus(self, e): + e.Skip() + if self.EmptyText and self.ForegroundColour == self.emptycolor: + self.SetForegroundColour(self.defaultcolor) + self.Value = '' + +class SkinVListBox(wx.VListBox): + def __init__(self, *a, **k): + wx.VListBox.__init__(self, *a, **k) + syscol = wx.SystemSettings.GetColour + + self.colors = Storage(textfg=Storage(selected=syscol(wx.SYS_COLOUR_HIGHLIGHTTEXT), + normal=syscol(wx.SYS_COLOUR_WINDOWTEXT))) + + self.bg = Storage(selected=SkinColor(syscol(wx.SYS_COLOUR_HIGHLIGHT)), + normal=SkinColor(syscol(wx.SYS_COLOUR_LISTBOX))) + + self.fonts = Storage(text=default_font()) + + self.BBind(LEFT_DOWN=self._OnLeftDown) + + def GetSelections(self): + item, cookie = self.GetFirstSelected() + + yield item + while item != -1: + item, cookie = self.GetNextSelected(cookie) + yield item + + def IsSelected(self, n): + if self.HasMultipleSelection(): + return n in self.GetSelections() + else: + return self.Selection == n + + def OnDrawItem(self, dc, rect, n): + selected = self.IsSelected(n) + + textfg = getattr(self.colors.textfg, 'selected' if selected else 'normal') + + dc.SetTextForeground(textfg) + dc.SetFont(self.fonts.text) + + self._draw(dc, rect, n, selected) + + def OnDrawBackground(self, dc, rect, n): + selected = self.IsSelected(n) + + bg = getattr(self.bg, 'selected' if selected else 'normal') + bg.Draw(dc, rect, n) + + try: + self._drawbg(dc, rect, n, selected) + except AttributeError: + pass + + def OnMeasureItem(self, n): + try: + measure = self._measure + except AttributeError: + return 20 + else: + return measure(n) + + def _OnLeftDown(self, e): + 'Makes clicking a blank area of the list deselect.' + + e.Skip() + i = self.HitTest((e.GetX(), e.GetY())) + if i == -1: + self.SetSelection(-1) + + +class RoomListModel(EventMixin): + events = set(( + 'contacts_changed', + )) + + def __init__(self, contacts): + EventMixin.__init__(self) + self.contacts = None + self.offline = False + self.set_contacts(contacts) + + def _init_contacts(self): + self.contacts_view = [] + self.contact_enter_times = {} + self.contact_leave_times = {} + self.pending_contacts = ObservableList() + + def set_contacts(self, contacts): + if hasattr(self.contacts, 'remove_observer'): + self.contacts.remove_observer(self._on_contacts_changed) + + self.contacts = contacts + self._init_contacts() + for contact in contacts: + self.contact_enter_times[contact] = 0 + + if hasattr(self.contacts, 'add_observer'): + self.contacts.add_observer(self._on_contacts_changed) + + self._on_contacts_changed() + + def _on_contacts_changed(self, *a, **k): + self._update_pending() + self._update_view() + self.fire_contacts_changed() + + def fire_contacts_changed(self): + self.event('contacts_changed') + + TIME_LEAVING = 4 + + def _update_view(self): + self.leaving_contacts = view = [] + now = time() + for gone_contact, t in list(self.contact_leave_times.items()): + if now - t > self.TIME_LEAVING: + self.contact_leave_times.pop(gone_contact, None) + else: #leaving + view.append(gone_contact) + + old_length = len(self.contacts_view) + self.contacts_view = sorted(list(self.contacts) + view) + return len(self.contacts_view) != old_length + + def _update_pending(self): + for pending_contact in list(self.pending_contacts): + for contact in self.contacts: + if pending_contact.equals_chat_buddy(contact): + self.pending_contacts.remove(pending_contact) + + contacts = set(self.contacts) + now = time() + for contact in list(self.contact_enter_times.keys()): + if contact not in contacts: + self.contact_enter_times.pop(contact, None) + self.contact_leave_times.setdefault(contact, now) + + for contact in contacts: + if contact not in self.contact_enter_times: + self.contact_enter_times.setdefault(contact, now) + + def contact_is_new(self, contact): + time_joined = self.contact_enter_times.get(contact, None) + if time_joined is None: + return False + TIME_NEW = 2 + now = time() + return now - time_joined < TIME_NEW + + def contact_is_leaving(self, contact): + time_left = self.contact_leave_times.get(contact, None) + return time_left is not None and time() - time_left < self.TIME_LEAVING + + def add_pending_contact(self, contact): + if contact not in self.pending_contacts: + self.pending_contacts.append(contact) + self.event('contacts_changed') + return True + + def remove_pending_contact(self, contact): + try: + self.pending_contacts.remove(contact) + except ValueError: + return False + else: + self.event('contacts_changed') + return True + + @property + def length_including_pending(self): + return self.length + len(self.pending_contacts) + + @property + def length(self): + return len(self.contacts_view) + + def get_contact(self, n): return self.contacts_view[n] + + def get_pending_contact(self, n): return self.pending_contacts[n] + +class ContactListCtrl(SkinVListBox): + 'Shows contacts and their status icons in a list.' + + click_raises_imwin = True + + def __init__(self, parent, model): + SkinVListBox.__init__(self, parent, style=wx.LB_MULTIPLE) + self.model = model + self.show_pending = True + self.model.bind_event('contacts_changed', lambda * a, **k: wx.CallAfter(self.update_count)) + self.Bind(wx.EVT_MOTION, self._on_motion) + self.Bind(wx.EVT_LEFT_DOWN, self._on_left_down) + self.Bind(wx.EVT_LEFT_DCLICK, self._on_left_dclick) + + self.contact_timer = wx.PyTimer(self._on_contact_timer) + self.UpdateSkin() + + def _on_contact_timer(self): + if wx.IsDestroyed(self): + return + if self.model._update_view(): + self.update_count() + self.Refresh() + + def UpdateSkin(self): + getattr(SkinVListBox, 'UpdateSkin', lambda self: None)(self) + + # "X" remove icon for pending contacts + self.x_icon = skin.get('appdefaults.removeicon').ResizedSmaller(16).WXB + self.pad_x = 2 + + def _on_left_down(self, e): + i = self.HitTest(e.Position) + if i != -1: + info = self._contact(i) + if info.pending: + if e.Position.x > self.Size.width - self.x_icon.Width - self.pad_x * 2: + self.model.remove_pending_contact(info.contact) + e.Skip() + + def _on_left_dclick(self, e): + i = self.HitTest(e.Position) + if i == -1: + return e.Skip() + + chat_buddy = self.model.get_contact(i) + if hasattr(chat_buddy, 'private_message_buddy'): + chat_buddy = chat_buddy.private_message_buddy() + + gui.imwin.begin_conversation(chat_buddy) + + def _on_motion(self, e): + e.Skip() + i = self.HitTest(e.Position) + if i == -1: + tip = None + else: + tip = self._contact_str(i) + + update_tooltip(self, tip) + + def SetContacts(self, contacts): + self.model.set_contacts(contacts) + + def update_count(self): + if wx.IsDestroyed(self): + print 'error: update_count still being called' + else: + if self.model.offline: + count = 0 + elif self.show_pending: + count = self.model.length_including_pending + else: + count = self.model.length + + self.SetItemCount(count) + self.RefreshAll() + + iconsize = 16 + padding = 3 + + def _contact(self, n): + try: + contact = self.model.get_contact(n) + icon = get_buddy_icon(contact, self.iconsize, False) + pending = False + except IndexError: + contact = self.model.get_pending_contact(n - self.model.length) + #TODO: This is not in skin anymore + icon = None#skin.get('miscicons.smallspinner') + pending = True + + leaving = self.model.contact_is_leaving(contact) + + return S(contact=contact, icon=icon, pending=pending, leaving=leaving) + + def _contact_str(self, n): + return contact_display_str(self._contact(n).contact) + + def _draw(self, dc, rect, n, selected): + _contact = self._contact(n) + contact, icon = _contact.contact, _contact.icon + contact_str = self._contact_str(n) + + # draw icon + rect.Subtract(left=self.padding) + if icon: + dc.DrawBitmap(icon, rect.X, rect.Y + (rect.Height / 2 - icon.Size.height / 2)) + + # draw buddy name text + rect.Subtract(left=self.iconsize + self.padding) + + bold = not _contact.pending and self.model.contact_is_new(contact) + if (bold or _contact.leaving) and not self.contact_timer.IsRunning(): + self.contact_timer.StartOneShot(1000) + + f = dc.Font + if not _contact.pending: + color = wx.BLACK if not _contact.leaving else wx.Colour(128, 128, 128) + f.SetStyle(wx.FONTSTYLE_NORMAL) + else: + color = wx.Colour(128, 128, 128) + f.SetStyle(wx.FONTSTYLE_ITALIC) + rect.width -= self.x_icon.Width + self.pad_x * 2 + + f.SetWeight(wx.FONTWEIGHT_BOLD if bold else wx.FONTWEIGHT_NORMAL) + dc.Font = f + + dc.SetTextForeground(color) + dc.DrawTruncatedText(contact_str, rect, wx.ALIGN_CENTER_VERTICAL) + + if _contact.pending: + dc.DrawBitmap(self.x_icon, rect.Right + self.pad_x, rect.Top + (rect.Height / 2 - self.x_icon.Height / 2)) + + def _measure(self, n): + return 22 #TODO: fontsize + + def OnGetItemImage(self, item): + return item + +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.simplemenu import SimpleMenuItem + +def item_for_contact(contact): + alias, name = contact.alias, contact.name + + icon = skin.get('statusicons.' + contact.status_orb) + + if alias == name: + smi = SimpleMenuItem([icon, name]) + else: + smi = SimpleMenuItem([icon, "%s (%s)" % (alias, name)]) + + smi.buddy = contact + return smi + + +class ContactCombo(UberCombo): + def __init__(self, parent, + skinkey=None, + contacts=None, + inviteCallback=None, + accountCallback=None, + model=None, + use_confirm_dialog=True): + + if skinkey is None: + skinkey = skin.get('RoomListComboSkin') + + UberCombo.__init__(self, parent, typeable=True, + skinkey=skinkey, + editmethod=self.EditField, + selectioncallback=self.on_selection, + maxmenuheight=10, + minmenuwidth=230, + empty_text=_('Invite Buddy')) + + self.TextField.Bind(wx.EVT_KEY_DOWN, self.OnKey) + + self.contacts = contacts if contacts is not None else {} + self.inviteCallback = inviteCallback + self.accountCallback = accountCallback + self.model = model + self.use_confirm_dialog = use_confirm_dialog + + #from util import trace + #from gui.uberwidgets.simplemenu import SimpleMenu + #trace(SimpleMenu) + + def EditField(): + if not self.display.txtfld.IsShown(): + self.display.TypeField() + + self.menu.BeforeDisplay += lambda: self.update_menu(self.TextField.Value) + self.menu.BeforeDisplay += EditField + + def Active(self, active): + pass + + def buddy_sort(self, c): + # show online buddies first, alphabetically + return (-int(c.online), c.alias) + + def OnKey(self, e): + k = e.GetKeyCode() + m = self.menu + + if k == wx.WXK_DOWN: + m.Selection = (m.Selection + 1) % m.Count + elif k == wx.WXK_UP: + if m.Selection == -1: + m.Selection = m.Count - 1 + else: + m.Selection = (m.Selection - 1) % m.Count + elif k == wx.WXK_RETURN: + if m.IsShown() and m.Selection >= 0: + item = m.GetItem(m.Selection) + m.Hide() + m.Selection = -1 + self.on_selection(item) + else: + self.on_selection(self.TextField.Value) + #m.Hide() + self.GrandParent.SetFocus() + #self.ShowMenu() + + elif k == wx.WXK_ESCAPE: + self.TextField.Value = '' + m.Hide() + else: + e.Skip() + + def update_menu(self, val=None): + m = self.menu + m.RemoveAll() + + for item in self.get_dropdown_items(val): + m.AppendItem(item) + + if m.Count: + m.spine.CalcSize() + m.Selection = -1 + m.Refresh() + else: + m.Selection = -1 + m.Hide() + + def get_dropdown_items(self, prefix=None): + return [item_for_contact(c) for c in self.get_dropdown_contacts(prefix)] + + def get_dropdown_contacts(self, prefix=None): + val = prefix.lower() if prefix is not None else '' + + #print '-'*80 + #from pprint import pprint + #pprint(self.contacts) + + filtered_contacts = [] + + contacts_inviting = set(self.model.contacts) + is_self = lambda b: try_this(lambda: b is b.protocol.self_buddy, False) + + for contact in sorted(self.contacts.itervalues(), key=self.buddy_sort): + if contact not in contacts_inviting and not is_self(contact) and getattr(contact, 'supports_group_chat', False): + # search "name" and "alias" fields + n, a = contact.name.lower(), contact.alias.lower() + if n.startswith(val) or a.startswith(val): + filtered_contacts.append(contact) + + return filtered_contacts + + @property + def ActiveConnection(self): + acct = self.accountCallback() + if acct is not None: + return getattr(acct, 'connection', None) + + def on_selection(self, item): + if self.model.offline: + return + + if isinstance(item, basestring): + buddy = None + if item.strip(): + connection = self.ActiveConnection + if connection is not None: + buddy = connection.get_buddy(item) + else: + buddy = item.buddy + + if buddy is None: + return + + if not self.use_confirm_dialog or \ + wx.YES == wx.MessageBox(_('Do you want to invite {name} to this chat?').format(name=contact_display_str(buddy)), + _('Chat Invite'), + wx.YES | wx.NO): + + cb = self.inviteCallback + if cb is None: + cb = lambda * a, **k: log.warning('inviteCallback(%r, %r)', a, k) + + if cb(buddy): + self.SetValue('') + + def EditField(self): + if self.display.txtfld.IsShown(): + wx.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + self.OpenMenu() + + +class RoomListPanel(wx.Panel): + def __init__(self, parent, buddies=None, inviteCallback=None, accountCallback=None): + wx.Panel.__init__(self, parent) + + self._obs_link = None + self.roomlist = None + self.model = RoomListModel([]) + self.list = ContactListCtrl(self, model=self.model) + self.combo = ContactCombo(self, + contacts=buddies, + inviteCallback=inviteCallback, + accountCallback=accountCallback, + model=self.model) + + s = self.Sizer = wx.BoxSizer(wx.VERTICAL) + s.Add(self.combo, 0, wx.EXPAND | wx.ALL) + s.Add(self.list, 1, wx.EXPAND | wx.ALL) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + + # when the following properties of contacts change, they are updated in + # the list + contact_attrs = ('status', 'alias') + + def UpdateSkin(self): + self.combo.SetSkinKey(skin.get('RoomListComboSkin')) + + def SetConversation(self, convo): + self.model.offline = False + self._watch_protocol(convo) + + self.SetRoomList(convo.room_list) + self.combo.contacts = convo.protocol.buddies + convo.pending_contacts_callbacks.add(self.on_pending_contacts) + self.list.show_pending = getattr(convo, 'contact_identities_known', True) + + proto = None + + def _watch_protocol(self, convo): + if self.proto is not None: + self.proto.remove_observer(self._on_state_change, 'state') + self.proto = convo.protocol + self.proto.add_observer(self._on_state_change, 'state') + + def _on_state_change(self, protocol, attr, old, new): + offline = self.model.offline + self.model.offline = new == protocol.Statuses.OFFLINE + if offline != self.model.offline: + self.list.update_count() + + def on_pending_contacts(self, contacts): + added = False + for c in contacts: + if c not in self.model.pending_contacts: + self.model.pending_contacts.append(c) + added = True + + if added: + self.model.event('contacts_changed') + + def SetRoomList(self, obslist): + self.roomlist = obslist + self.list.SetContacts(obslist) + + RoomList = property(lambda self: self.roomlist, + SetRoomList, + lambda self: self.SetRoomList(None), + "Sets or gets this panel's roomlist.") + + def on_list_changed(self, src, attr, old, new): + pass + + def on_contact_changed(self, contact, attr, old, new): + pass + +def contact_display_str(contact): + alias, name = contact.alias, contact.name + return alias if alias == name else u'%s (%s)' % (alias, name) diff --git a/digsby/src/gui/imwin/styles/Template.html b/digsby/src/gui/imwin/styles/Template.html new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/imwin/styles/__init__.py b/digsby/src/gui/imwin/styles/__init__.py new file mode 100644 index 0000000..9199b50 --- /dev/null +++ b/digsby/src/gui/imwin/styles/__init__.py @@ -0,0 +1,11 @@ +''' + +Displaying themed conversations + +''' + +from gui.imwin.styles.msgstyles import * +from gui.imwin.styles.basicmsgstyle import BasicMessageStyle +from gui.imwin.styles.adiummsgstyles import AdiumMessageStyle + +register_message_style_type('AdiumMessageStyle', AdiumMessageStyle) \ No newline at end of file diff --git a/digsby/src/gui/imwin/styles/adiummsgstyles.py b/digsby/src/gui/imwin/styles/adiummsgstyles.py new file mode 100644 index 0000000..c898d6f --- /dev/null +++ b/digsby/src/gui/imwin/styles/adiummsgstyles.py @@ -0,0 +1,430 @@ +''' + +Adium MessageStyles + +http://trac.adiumx.com/wiki/CreatingMessageStyles + +''' + +CACHE_CSS = True + +import config +from datetime import datetime +from traceback import print_exc +import re, sys, logging + +from gui import skin +from gui.imwin.styles import MessageStyle, MessageStyleException + +from util import strftime_u +from util.primitives.misc import fromutc +from util.xml_tag import plist +from path import path +from common import prefprop +from common.protocolmeta import protocols + +from gui.buddylist.renderers import get_buddy_icon_url + + +from util import strip_html2 +from gui.textutil import isRTL + +log = logging.getLogger('adiummsgstyles') + + +from util import memoizedprop as memoizedprop_cache + +if not CACHE_CSS: + memoizedprop = lambda p: p + print >>sys.stderr, 'WARNING: not caching CSS' +else: + memoizedprop = memoizedprop_cache + +# font alternatives, since PCs usually don't have some mac fonts + +font_alternatives = {} + +if config.platform == 'win': + font_alternatives.update({ + 'Lucida Grande': ['Arial', 'Lucida Sans Unicode', 'Lucida Sans', 'Lucida'], + 'Monaco': ['Consolas', 'Lucida Console'], + 'Geneva': ['Arial'], + 'Helvetica': ['Arial'], + }) + + +# Not all of the strftime formatting options used by Adium skins have equivalents +# in Python's strftime. Replace them here. +strftime_replacements = [ + ('%1I', '%I'), + ('%e', '%d') +] + +# If key.html is missing, value.html will be tried also. +htmlfile_fallbacks = { + 'Context': 'Content', + 'NextContext': 'NextContent', + 'Incoming': 'Outgoing', + 'Outgoing': 'Incoming', +} + +# pattern for finding adium message style %variables% +msgpattern = re.compile('(%([^{%\s]+)(\{([^}]+)\})?%)') + +class AdiumMessageStyleBase(MessageStyle): + def __init__(self, themepath): + MessageStyle.__init__(self) + + self.path = path(themepath) + self._cached_contents = {} + + if not (self.path / 'Contents').isdir(): + raise MessageStyleException('Adium MessageStyle has no "Contents" ' + 'directory (%s)' % themepath) + + @property + def baseUrl(self): + return 'file:///%s/' % self.path.drive + + def _getcontent(self, *paths): + p = paths[0] + for elem in paths[1:]: + if elem is not None: + p = p / elem + + return self.cached_contents(p) + + @memoizedprop + def resources(self): + return self.path / 'Contents' / 'Resources' + + @property + def plist(self): + try: + return self._plist + except AttributeError: + plistpath = self.path / 'Contents' / 'Info.plist' + self._plist = {} + if plistpath.isfile(): + try: + self._plist = plist(plistpath.text()) + except Exception: + print_exc() + + return self._plist + + @memoizedprop + def default_css_styles(self): + p = self.plist + + style = 'body {\n' + + try: + fontfamily = p['DefaultFontFamily'] + alternatives = font_alternatives.get(fontfamily, []) + fontfamilies = u', '.join([fontfamily] + alternatives) + + style += ' font-family: %s;\n' % fontfamilies + except KeyError: + pass + + try: + style += ' font-size: %spt;\n' % p['DefaultFontSize'] + except KeyError: + pass + + style += '}\n' + + return style + + @memoizedprop + def base(self): + return self.resources.url() + '/' + + @property + def css_file(self): + return path('Variants') / self.variant + '.css' if self.variant else 'main.css' + + if CACHE_CSS: + def cached_contents(self, path): + # todo: make a selfmemoize + try: return self._cached_contents[path] + except KeyError: return self._cached_contents.setdefault(path, path.text()) + else: + def cached_contents(self, path): + return path.text() + + def get_variant(self): + try: + variants = self.resources / 'Variants' + return self._variant + except AttributeError: + if variants.isdir(): + variants = variants.files('*.css') + self._variant = self.plist.get('DefaultVariant', variants[0].namebase) + if not self._variant: + self._variant = None + else: + # no variant specified. + self._variant = None + + return self._variant + + def set_variant(self, variant): + self._variant = variant + + variant = property(get_variant, set_variant) + + @property + def variants(self): + v = self.resources / 'Variants' + variants = [p.namebase for p in v.files('*.css')] if v.isdir() else [] + + try: + novariant_name = self.plist['DisplayNameForNoVariant'] + except KeyError: + return variants + else: + return [None] + variants + + + @memoizedprop + def novariant_name(self): + return self.plist.get('DisplayNameForNoVariant', _('(none)')) + + @memoizedprop + def theme_name(self): + return self.path.namebase + + @memoizedprop + def shows_background_colors(self): + return '%textbackgroundcolor%' in self._getcontent(self.resources, 'Incoming', 'Content.html') + +class AdiumMessageStyle(AdiumMessageStyleBase): + + def __init__(self, themepath, **options): + 'Represents the information, styles, and HTML in a AdiumMessageStyle directory.' + + AdiumMessageStyleBase.__init__(self, themepath) + + def initialContents(self, chatName, buddy, header = False, prevent_align_to_bottom=False): + ''' + Return the initial HTML contents of this message style. Usually + res/MessageStyles/Template.html, but can be overridden by an individual + style by a file with the same name in its resource directory. + ''' + + self.options = dict(chatName = chatName) + template = self.resources / 'Template.html' + + if template.isfile(): + s = template.text('utf-8') + else: + baseTemplate = skin.resourcedir() / 'MessageStyles' / 'Template.html' + s = baseTemplate.text('utf-8') + + # the default Adium Template.html file includes several string + # special substitution character sequences + s = s.replace('%', '%%').replace('%%@', '%s') + + args = (self.base.replace('\\', '/'), + self.default_css_styles, + self.css_file.replace('\\', '/'), + self.applySubstitutions(self.get_header(prevent_align_to_bottom=prevent_align_to_bottom), buddy = buddy), + self.applySubstitutions(self.footer, buddy = buddy) + \ + self.init_header_script(header)) + + try: + s = s % args + except Exception: + try: + s = s % (args[:1] + args[2:]) + except Exception: + print >> sys.stderr, s + print_exc() + + return s + + def show_header_script(self, show): + 'Returns Javascript for showing or hiding the header.' + + display = '' if show else 'none' + return "document.getElementById('header').style.display = '%s'" % display + + def init_header_script(self, show_header): + 'Returns CSS for the " + return headerText + + @property + def footer(self): + footerPath = self.resources / 'Footer.html' + return footerPath.text() if footerPath.isfile() else '' + + @property + def allow_text_colors(self): + return self.plist.get('AllowTextColors', True) + + def format_message(self, messagetype, messageobj, next = False, context = False, **extra): + ''' + Applies a section of this theme's formatting template to the message + object given. + + messagetype 'incoming' or 'outgoing' or 'status' or...more to come maybe! + + messageobj a Digsby messageobj + + next a boolean indicating if this is a "next" message (i.e., + one to be appended to the previous block + + context a boolean indicating if this message is "context"--the + history showed when you open an IM window + ''' + if messagetype in ('incoming', 'outgoing'): + direction = messagetype.title() + content = '%s%s' % ('Next' if next else '', + 'Context' if context else 'Content') + else: + direction = None + content = messagetype.title() + + try: + content = self._getcontent(self.resources, direction, '%s.html' % content) + except IOError: + #TODO: discovery these fallbacks once. + if content in htmlfile_fallbacks: + content = htmlfile_fallbacks[content] + try: + content = self._getcontent(self.resources, direction, '%s.html' % content) + except IOError: + return None, None + else: + return None, None + + self.apply_auto(messageobj, **extra) + + # apply magic variables + msg = self.applySubstitutions(content, messageobj, getattr(messageobj, 'buddy', None), **extra) + + return 'append%sMessage' % ('Next' if next else ''), msg + + def apply_auto(self, mobj, autotext='{message}', **extras): + if getattr(mobj, 'auto', False): + mobj.message = autotext.format(message = mobj.message) + + def applySubstitutions(self, s, msgobj = None, buddy = None, **extra): + ''' + Replace MessageStyle variables like %incomingIconPath% or + %time{%H:%M} + ''' + + if not isinstance(s, basestring): raise TypeError + + def _repl(match): + wholeMatch, attr, __, args = match.groups() + return self.getMagicAttr(attr, args, wholeMatch, m = msgobj, b = buddy, **extra) + + if isinstance(s, str): + s = s.decode('fuzzy utf-8') + + try: + s = msgpattern.sub(_repl, s) + except UnicodeDecodeError, e: + log.critical('error substituting message style variables') + log.critical(' s: %r', s) + log.critical(' buddy: %r', buddy) + if buddy is not None: + log.critical(' buddy alias: %r\n', getattr(buddy, 'alias', '')) + log.critical(' extra: %r', extra) + log.critical('%r', e) + + return s + + + def getMagicAttr(self, attr, args = None, originalString = '', m = None, b = None, **extra): + try: + attrs = self.magicattrs + except AttributeError: + self.setup_string_substitution() + attrs = self.magicattrs + + try: + try: + return attrs[attr](m, b) if args is None else attrs[attr](m, b, args) + except KeyError: + return extra[attr] + except Exception: + #print_exc() #uncomment me to find out why there are %strayvariables% in your HTML + return '%' + attr + '%' + + show_tstamp = prefprop('conversation_window.timestamp', True) + tstamp_fmt = prefprop('conversation_window.timestamp_format', '%#I:%M') + + def setup_string_substitution(self): + # TODO: this should probably be done once, at the class level...not in a function + + icon = get_buddy_icon_url + + def strf(t, fmt = None): + if not self.show_tstamp and not self.should_always_show_timestamp: + return '     ' + + fmt = self.tstamp_fmt if fmt is None else fmt + + for s, repl in strftime_replacements: + fmt = fmt.replace(s, repl) + + return strftime_u(fromutc(t), fmt) + + def time(m, b, fmt = None): + # %a %B %e %H:%M:%S %Y + return strf(m.timestamp, fmt) + + def timeOpened(m, b, fmt = None): + # TODO: this is wrong. have convos track their opened datetime + return strf(datetime.utcnow(), fmt) + + def xml_or_None(s): + if s is None: + return None + return s.encode('xml') + + self.magicattrs = dict( + # + # magic name = lambda: message, buddy: "template substitution" + # + chatName = lambda m, b: xml_or_None(self.options.get('chatName')), + incomingIconPath = lambda m, b: icon(b), + userIconPath = lambda m, b: icon(b), + senderScreenName = lambda m, b: xml_or_None(b.name), # service screenname + senderDisplayName = lambda m, b: xml_or_None(b.remote_alias or b.alias), + sender = lambda m, b: xml_or_None(getattr(b, 'alias', b.name)), + time = time, + timeOpened = timeOpened, + message = lambda m, b: m.message, + service = lambda m, b: protocols[b.service].name if b is not None else '', + messageDirection = lambda m, b: 'rtl' if isRTL(strip_html2(m.message)[0]) else 'ltr', + ) + +class DigsbyMessageStyle(AdiumMessageStyle): + def setup_string_substitution(self): + AdiumMessageStyle.setup_string_substitution(self) + + self.magicattrs.update( + auto = lambda m,b: m.auto, + ) + diff --git a/digsby/src/gui/imwin/styles/basicmsgstyle.py b/digsby/src/gui/imwin/styles/basicmsgstyle.py new file mode 100644 index 0000000..e066ec9 --- /dev/null +++ b/digsby/src/gui/imwin/styles/basicmsgstyle.py @@ -0,0 +1,85 @@ +from gui.imwin.styles import MessageStyle + +class BasicMessageStyle(MessageStyle): + ''' + A backup message style to use if no styles can be found on disk. + ''' + + template = \ +''' + + + + + + +
+
+ + + +''' + + theme_name = 'basic' + variant = None + baseUrl = 'file:///' + allow_text_colors = True + + def initialContents(self, chatName, buddy, header = False, prevent_align_to_bottom=False): + return self.template + + def show_header_script(self, show): + return '' + + def format_message(self, messagetype, messageobj, next, context, **extra): + if messageobj.buddy is not None: + return 'appendMessage', u'
%s: %s
' % (messageobj.buddy.name, messageobj.message) + else: + return 'appendMessage', u'
%s
' % messageobj.message diff --git a/digsby/src/gui/imwin/styles/msgstyles.py b/digsby/src/gui/imwin/styles/msgstyles.py new file mode 100644 index 0000000..ebaf165 --- /dev/null +++ b/digsby/src/gui/imwin/styles/msgstyles.py @@ -0,0 +1,107 @@ +from __future__ import with_statement +import re +from gui import skin +from common import pref +from path import path +from util import traceguard, memoize +from traceback import print_exc +from logging import getLogger; log = getLogger('msgstyles') + +class MessageStyle(object): + @property + def should_always_show_timestamp(self): + return getattr(self, '_ignore_show_tstamp_flag', False) + + def set_always_show_timestamp(self, val): + self._ignore_show_tstamp_flag = val + + @property + def header(self): + try: + return ''.join(header.text() for header in get_user_files("Header.html")) + except Exception: + print_exc() + return '' + +class MessageStyleException(Exception): + pass + + +CONVO_THEME_DIR = 'MessageStyles' + + +msgStyleTypes = {} + +def register_message_style_type(name, constructor): + global msgStyleTypes + + msgStyleTypes[name] = constructor + +def get_user_files(filename): + import stdpaths + dirs = [] + for user_themes_dir in (stdpaths.userdata / CONVO_THEME_DIR, stdpaths.config / CONVO_THEME_DIR): + fname = user_themes_dir / filename + if fname.isfile(): + dirs.append(fname) + + return dirs + +def get_user_themes_dirs(): + import stdpaths + dirs = [] + for user_themes_dir in (stdpaths.userdata / CONVO_THEME_DIR, stdpaths.config / CONVO_THEME_DIR): + with traceguard: + if not user_themes_dir.isdir(): + user_themes_dir.makedirs() + dirs.append(user_themes_dir) + + return dirs + +@memoize +def get_themes(): + p = skin.resourcedir() / CONVO_THEME_DIR + themes = [] + userdirs = get_user_themes_dirs() + + userthemes = [] + for dir in userdirs: + userdir = dir.abspath() + try: + userthemes.extend(pth.abspath() for pth in (userdir.dirs() if userdir.isdir() else [])) + except Exception, e: + print_exc() + + systemthemes = p.dirs() + subdirs = userthemes + systemthemes + + global msgStyleTypes + + for subdir in subdirs: + ext = subdir.ext[1:] + + if ext in msgStyleTypes: + with traceguard: + themes.append(msgStyleTypes[ext](subdir.abspath())) + + return themes + +def get_theme(name, variant = None): + for theme in get_themes(): + if theme.theme_name == name: + theme.variant = variant + return theme + + raise MessageStyleException('theme "%s" not found' % name) + +def get_theme_safe(name, variant = None): + try: + return get_theme(name, variant) + except Exception: + print_exc() + + from basicmsgstyle import BasicMessageStyle + return BasicMessageStyle() + + + diff --git a/digsby/src/gui/imwin/styles/stripformatting.py b/digsby/src/gui/imwin/styles/stripformatting.py new file mode 100644 index 0000000..8651333 --- /dev/null +++ b/digsby/src/gui/imwin/styles/stripformatting.py @@ -0,0 +1,168 @@ +''' + +Strips formatting and colors from strings. + +''' +from __future__ import with_statement + +from gui.imwin.emoticons import apply_emoticons +from util import traceguard, linkify, soupify + +from traceback import print_exc +from collections import defaultdict +from re import compile + +from logging import getLogger; log = getLogger('stripformatting') +LOG = log.debug + +def prewrap(m): + return '' + m + '' + +plaintext_transform_functions = [ + ('emoticons', apply_emoticons), + ('links', linkify), + ('spaces', prewrap), +] + +def strip(html, formatting = True, colors = True, plaintext_transforms = None): + ''' + Strips formatting and/or colors from a string. + + Returns (stripped_string, {stripped_values}]) + ''' + + #LOG('before strip: %r', html) + + if plaintext_transforms is None: + plaintext_transforms = {} + + # A dictionary of lists of things this function has stripped out. + removed = defaultdict(list) + + try: + soup = soupify(html, convertEntities = 'html') + #LOG('strip: %r', html) + + if formatting: + strip_formatting(soup, removed) + # LOG('after stripping formatting: %r', soup.renderContents(None)) + + remove_attrs(soup, ['color', 'bgcolor'], removed, doremove = colors) + + if colors: + remove_styles(soup, ['background', 'color'], removed) + remove_attrs(soup, ['back'], removed) + else: + convert_back(soup, removed) + + #LOG('after colors: %r', soup.renderContents(None)) + + remove_tags(soup, 'html') + remove_tags(soup, 'body') + + #LOG('after removing color: %r', soup.renderContents(None)) + + apply_plaintext_transforms(soup, plaintext_transforms) + + final = soup.renderContents(None) + #LOG('after transformations: %r', final) + + return final, removed + + except Exception: + # If any exceptions occur, just return the original string. + print_exc() + + return html, removed + +def strip_formatting(soup, removed): + 'Removes font formatting attributes.' + + remove_attrs(soup, ['face', 'size'], removed) + remove_tags(soup, 'small', 'big') #'b', 'i', 'strong', 'em', <- decision was made to not remove bold, etc. + remove_styles(soup, ['font-family', 'font-size'], removed) + remove_styles(soup, ['font'], removed) #<- will not do partial remove + +def apply_plaintext_transforms(soup, plaintext_transforms): + "Applies selected functions to each text node in 'soup.'" + + for textElem in soup(text=True): + s = textElem + + # Functions are keyed by name in "plaintext_transform_functions" + # above. + for name, func in plaintext_transform_functions: + res = plaintext_transforms.get(name, False) + if res: + with traceguard: # Guard each call for failures. + if res not in (False, True, None): + s = func(s, res) + else: + s = func(s) + + with traceguard: + textElem.replaceWith(s) + +def attr_match(tag, attrnames): + tagattrs = set(attrName for attrName, attrValue in tag.attrs) + return any((a in tagattrs) for a in attrnames) + +def remove_attrs(soup, attrs, removed, doremove = True): + for tag in soup.findAll(lambda t: attr_match(t, attrs)): + for attrName, attrValue in list(tag.attrs): + #LOG('%s in %s?', attrName, attrs) + if attrName in attrs: + removed[attrName].append(attrValue) + if doremove: + del tag[attrName] + + +def remove_tags(soup, *tags): + for tag in soup.findAll(name = tags): + # TODO: don't use soupify to reparse HTML a lot + tag.replaceWith(soupify(tag.renderContents(None))) + +def remove_style(s, style): + search = compile(r'\s*' + style + ' *:([^;]*)').search + removed = [] + + match = search(s) + while match: + # grab the value of (^;]*) in the above regex + removed.append(match.groups(1)[0].strip()) + + # cut out the matched expression. + i, j = match.span() + s = s[:i] + s[j+1:] + + match = search(s) + + return s, removed + +def remove_styles(soup, styles, removed): + all_removed = [] + + for tag in soup.findAll(style = True): + for style in styles: + stripped_style, removed_style = remove_style(tag['style'], style) + removed[stripped_style].append(removed_style) + tag['style'] = stripped_style + + return all_removed + + +def convert_back(soup, removed): + 'AIM ' + + # Convert all + for tag in soup.findAll(name = 'font', back = True): + removed['back'].append(tag['back']) + + styles = ['background-color: %s' % tag['back']] + + if 'color' in dict(tag.attrs): + removed['color'].append(tag['color']) + styles.append('color: %s' % tag['color']) + + tag.replaceWith(soupify(('' % '; '.join(styles)) + tag.renderContents(None) + '')) + diff --git a/digsby/src/gui/infobox/__init__.py b/digsby/src/gui/infobox/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/infobox/adapters.py b/digsby/src/gui/infobox/adapters.py new file mode 100644 index 0000000..9352c78 --- /dev/null +++ b/digsby/src/gui/infobox/adapters.py @@ -0,0 +1,50 @@ +from peak.util.addons import AddOn +import interfaces as gui_interfaces +from util.net import linkify +import protocols + +class IBCacheAddon(AddOn, dict): + pass + +class CachingIBP(protocols.Adapter): + protocols.advise(instancesProvide=[gui_interfaces.ICachingInfoboxHTMLProvider]) + + def __init__(self, subject): + try: + gui_interfaces.ICacheableInfoboxHTMLProvider(subject) + except protocols.AdaptationFailure: + raise + return super(CachingIBP, self).__init__(subject) + + def get_html(self, *a, **k): + c = IBCacheAddon(self.subject) + ibp = gui_interfaces.ICacheableInfoboxHTMLProvider(self.subject) + if ibp._dirty or 'cachedata' not in c: + c['cachedata'] = ibp.get_html(*a, **k) + return c['cachedata'] + +class AccountCachingIBP(protocols.Adapter): + protocols.advise(instancesProvide=[gui_interfaces.ICachingInfoboxHTMLProvider]) + + def get_html(self, htmlfonts, make_format, *a, **k): + acct = self.subject + cachekey = '__cache__' + format_key = acct + if getattr(acct, 'header_tabs', False): + currtab = acct._current_tab + cachekey = currtab + format_key = (acct, cachekey) + dirty = acct._dirty[currtab] + elif getattr(acct, '_dirty', False): + dirty = True + else: + dirty = False + _cache = IBCacheAddon(acct) + if dirty or cachekey not in _cache: + _cache[cachekey] = linkify(make_format(htmlfonts, format_key)) + if getattr(acct, 'header_tabs', False): + acct._dirty[currtab] = False + else: + acct._dirty = False + _cache[cachekey] = ''.join(y.strip() for y in _cache[cachekey].split('\n')) + return _cache[cachekey] diff --git a/digsby/src/gui/infobox/emailpanels.py b/digsby/src/gui/infobox/emailpanels.py new file mode 100644 index 0000000..9b0c4af --- /dev/null +++ b/digsby/src/gui/infobox/emailpanels.py @@ -0,0 +1,627 @@ +#TODO: This should be replaced with Tenjin templates similar to how Social Networks are handled +''' +List of emails shown in the infobox. +''' + +from __future__ import with_statement + +import sys, wx +import traceback +from wx import Rect, RectS, BLACK +from datetime import datetime + +from gui import skin +from gui.textutil import default_font, CopyFont +from gui.skin.skinobjects import SkinColor +from gui.toolbox import add_image_text +from gui.uberwidgets.pseudosizer import PseudoSizer +from gui.uberwidgets.clearlink import ClearLink +from cgui import SimplePanel +from common import actions, pref +from gui.textutil import GetFontHeight + +link_style = wx.NO_BORDER | wx.HL_ALIGN_LEFT | wx.TRANSPARENT_WINDOW + +class Header(SimplePanel): + def __init__(self, parent): + """ + This is the header for the infobox used when displaying Email or + Social networks. Shows the account name and a number of related + links. + """ + + SimplePanel.__init__(self, parent) + self.account=None + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.linkage = PseudoSizer() + self.extralinkage = None + self.UpdateSkin() + + def SetAccount(self, account): + """ + Set an account to the header + Handles unloading the previous acount and setting up the links + and observer callbacks for the new account + """ + + if account is self.account: + return + + if self.account is not None: + try: + self.account.unobserve_count(self.Refreshxor) + except NotImplementedError: + pass + try: + self.account.unobserve_state(self.Refreshxor) + except NotImplementedError: + pass + + self.account = account + self.icon = account.icon.Resized(16) + + # Construct action links + self.linkage.Clear(True) + for linkfunc in account.header_funcs: + link = ClearLink(self, -1, linkfunc[0], lambda l=linkfunc: self.do_link(l), + style = link_style, pos = (-400, -400)) + link.NormalColour = link.HoverColour = link.VisitedColour = self.linkfc + link.Font = self.linkfont + self.linkage.Add(link) + + self.linkage.Layout() + + if self.extralinkage: + self.extralinkage.Show(False) + self.extralinkage.Destroy() + self.extralinkage = None + + if getattr(account, 'extra_header_func', None) is not None: + self.extralinkage = elink = ClearLink(self, -1, account.extra_header_func[0], lambda l=account.extra_header_func: self.do_link(l), + style = link_style, pos = (-400, -400)) + + elink.NormalColour = elink.HoverColour = elink.VisitedColour = self.linkfc + elink.Font = self.elinkfont + + self.ExtraLinkLayout() + + + + + try: + self._unbound_cbs.clear() + except AttributeError: + pass + + try: + account.observe_count(self.Refreshxor) + except NotImplementedError: + pass + try: + account.observe_state(self.Refreshxor) + except NotImplementedError: + pass + + + self.Refresh(False) + + def OnSize(self, event): + event.Skip() + self.ExtraLinkLayout() + + def ExtraLinkLayout(self): + elink = self.extralinkage + if elink is not None: + elink.Size = elink.BestSize + elinkposx = self.Size.width - self.padding[0] - elink.BestSize.width + elinkposy = self.headerfont.Height+2*self.padding[1] + elink.SetPosition((elinkposx, elinkposy)) + + def do_link(self, link): + if len(link) > 2: + if link[2]: #False == don't hide + wx.GetTopLevelParent(self).Hide() + else: + wx.GetTopLevelParent(self).Hide() + + if callable(link[1]): + link[1]() + else: + wx.LaunchDefaultBrowser(link[1]) + + + def Refreshxor(self, *a): + """ + Just a wrapper for refresh, used instead of a lambda so there's a + function to unbind later + """ + + self.Refresh(False) + + def UpdateSkin(self): + self.padding = wx.Point(4, 4) + + self.headerfont = skin.get('infobox.fonts.header',lambda: default_font()) + self.linkfont = CopyFont(skin.get('infobox.fonts.link',lambda: default_font()), underline=True) + self.elinkfont = CopyFont(self.linkfont, weight=wx.FONTWEIGHT_BOLD) + self.headerfc=skin.get('infobox.fontcolors.navbarheader', lambda: wx.BLACK) + self.linkfc=skin.get('infobox.fontcolors.navbarlink', lambda: wx.BLUE) + + linkposx = self.padding[0]*2 + 16 + linkposy = self.headerfont.Height+2*self.padding[1] + self.linkage.SetPosition((linkposx,linkposy)) + + + for link in self.linkage: + link.NormalColour=link.HoverColour=link.VisitedColour=self.linkfc + link.Font=self.linkfont + self.linkage.Layout() + + + elink = self.extralinkage + if elink: + elink.NormalColour = elink.HoverColour = elink.VisitedColour = self.linkfc + elink.Font = self.elinkfont + + elink.Size = elink.BestSize + elinkposx = self.Size.width - self.padding[0] - elink.BestSize.width + elink.SetPosition((elinkposx, linkposy)) + + + self.bg = skin.get('infobox.backgrounds.header', lambda: SkinColor(wx.Color(225, 255, 225))) +# self.sep = skin.get('infobox.longseparatorimage', None) + self.Size = self.MinSize = wx.Size(-1, self.headerfont.Height + self.linkfont.Height + self.padding.y * 4)# + self.sep.Size.height + + def OnPaint(self, event): + dc = wx.AutoBufferedPaintDC(self) + rect = RectS(self.Size) + padx, pady = self.padding + + self.bg.Draw(dc, rect) + + dc.Font = self.headerfont + font_height = self.headerfont.Height + + dc.TextForeground = self.headerfc + dc.DrawBitmap(self.icon, padx, padx + (font_height // 2) - self.icon.Height // 2, True) + + printable = rect.width - padx + curserx = rect.x + 2 * padx + self.icon.Width + cursery = rect.y + pady + + dc.DrawLabel(self.account.display_name, wx.Rect(curserx, cursery, printable-curserx, font_height)) + + curserx += dc.GetTextExtent(self.account.display_name+' ')[0] + + # TODO: remove this hack! + if getattr(self.account, 'service', None) != 'twitter': + dc.DrawLabel(('' if not hasattr(self.account, 'count') else '(' + str(self.account.count) + ')'), + Rect(curserx, cursery, printable-curserx, dc.Font.Height)) + +class EmailList(wx.VListBox): + """ + This is a list of the unread emails currently in the inbox. + Refreshes on email count change + Has links for common email actions + Double click will open the email + """ + def __init__(self, parent): + wx.VListBox.__init__(self, parent) + + import common.favicons + common.favicons.on_icon += self.OnFaviconReceived + + self.emails = [] + self.account = None + self.itemheight = 0 + + self.preview_offset = 0 + self.preview_timer = wx.PyTimer(self.on_preview_timer) + self.marquee_timer = wx.PyTimer(self.on_marquee) + + self.SetItemCount(len(self.emails)) + + self.linkage = PseudoSizer() + self.linkage.Show(False) + self.errorlink=None + + self.UpdateSkin() + + #links as (object name, String to be displayed, + # callback in self.account fitting this call callback(email)) + links = [('olink', 'Open', 'OnClickEmail'), + ('rlink', 'Mark as Read', 'markAsRead'), + ('alink', 'Archive', 'archive'), + ('dlink', 'Delete', 'delete'), + ('slink', 'Report Spam', 'reportSpam')] + + # Use the above list to generate ClearLink objects + for attr, text, method in links: + link = ClearLink(self, -1, text, method, style = wx.NO_BORDER | wx.HL_ALIGN_CENTRE) + link.NormalColour=link.HoverColour=link.VisitedColour=self.hoverlinkfc + link.Font=self.linkfont + setattr(self, attr, link) + + self.linkage.Add(link) + + self.BBind(HYPERLINK = self.OnLink, + MOTION = self.OnMouseMotion, + LEFT_DCLICK = self.OnDblClick, + LEAVE_WINDOW = self.OnMouseOut, + MOUSEWHEEL = self.OnMouseWheel, + PAINT = self.OnPaint, + SHOW = self.OnShow) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + def SetSelection(self, i): + try: + self.preview_offset = 0 + self.marquee_timer.Stop() + + if pref('email.marquee.enabled', False): + if i == -1: + self.preview_timer.Stop() + else: + self.preview_timer.Start(1000, True) + except Exception: + import traceback + traceback.print_exc() + + return wx.VListBox.SetSelection(self, i) + + def on_preview_timer(self): + self.marquee_timer.Start(pref('email.marquee.delay_ms', 50), False) + + def on_marquee(self): + sel = self.Selection + if sel == -1: + self.marquee_timer.Stop() + else: + self.preview_offset += 1 + self.RefreshRect(self.preview_rect) + + Selection = property(wx.VListBox.GetSelection, SetSelection) + + def OnFaviconReceived(self, domain): + 'Called when the favicon for "domain" is available.' + + from common.favicons import get_icon_domain + + for i, email in enumerate(self.emails): + if not email.domain: + continue + if domain == get_icon_domain(email.domain): + self.RefreshLine(i) + + def OnMouseWheel(self, event): + self.ScrollLines(-event.WheelRotation // abs(event.WheelRotation)) + + def OnDblClick(self,event): + 'Open an email on double click of the item' + + self.account.OnClickEmail(self.account.emails[self.Selection]) + + def OnMouseOut(self, event): + 'Unselects the email and hide links on mouse out of list.' + + if not self.linkage.Rect.Contains(event.Position) and self.Selection != -1: + self.RefreshLine(self.Selection) + self.Selection = -1 + self.linkage.Show(False) + + def OnMouseMotion(self, event): + 'Select the email and show links when mouse passes over an item.' + + i = self.HitTest(event.Position) + if self.Selection != i: + oldSelection = self.Selection + self.Selection=i + + if self.Selection==-1: + self.linkage.Show(False) + else: + self.linkage.Show(True) + + if oldSelection >=0: + self.RefreshLine(oldSelection) + + if self.Selection >= 0: + self.RefreshLine(self.Selection) + + def remove_observers(self): + if self.account is not None: + self.account.emails.remove_observer(self.ListChanged) + + try: + self._unbound_cbs.clear() + except AttributeError: + pass + + def OnShow(self, event): + """ + Make sure there is no selection or links shown when the box is shown + and removes oservers,links, and selection when hidden + """ + event.Skip() + + if wx.IsDestroyed(self): + print >> sys.stderr, 'WARNING: emailpanels.py OnShow called, but is destroyed' + return + + if not self.Shown: + self.remove_observers() + self.Selection=-1 + self.linkage.Show(False) + self.Refresh(False) + + def SetAccount(self, account): + 'Build list and links for the account.' + + self.ScrollToLine(0) + self.Selection=-1 + self.linkage.Show(False) + + self.remove_observers() + self.account = account + self.account.emails.add_observer(self.ListChanged) + + self.ListChanged() + + # Get the actions this account supports + acts = actions.forclass(account) + + # list of function names for each action + fnames = [act['call'] if not isinstance(act, basestring) else None for act in acts] + + # if each name exists show the corrosponding link + self.olink.Show('open' in fnames) + self.rlink.Show('markAsRead' in fnames) + self.alink.Show('archive' in fnames) + self.dlink.Show('delete' in fnames) + self.slink.Show('reportSpam' in fnames) + + self.linkage.Layout() + + # recalc item height, may be 3 or 4 lines long depending on account + self.itemheight = self.padding.y * 4 + self.titlefont.Height + self.majorfont.Height + self.linkfont.Height + (self.sep.Size.height if self.sep else 0) + if account.can_has_preview: + self.itemheight+=self.minorfont.Height + self.padding.y + + self.RefreshAll() + + self.Show(True) + + with self.Frozen(): + self.GrandParent.DoSizeMagic() + + def ListChanged(self, *args, **kwargs): + """ + Updates the list of emails when the list is changed and attemps to + keep the same email under the mouse + """ + i = self.GetFirstVisibleLine() + + try: + topemail = self.emails[i] + except IndexError: + topemail = None + + self.emails = self.account.emails + + with self.Frozen(): + self.SetItemCount(len(self.emails)) + self.GrandParent.DoSizeMagic() + + if topemail is not None: + try: + i = self.emails.index(topemail) + except ValueError: + pass + + self.ScrollLines(i) + self.Parent.Refresh(False) + + + def UpdateSkin(self): + s = skin.get + + self.padding = s('infobox.padding', lambda: wx.Point(4, 4)) + self.headerfont = s('infobox.fonts.header', default_font) + self.titlefont = s('infobox.fonts.title', default_font) + self.majorfont = s('infobox.fonts.major', default_font) + self.minorfont = s('infobox.fonts.minor', default_font) + self.linkfont = CopyFont(s('infobox.fonts.link', default_font), underline=True) + + fc = s('infobox.fontcolors'); g = fc.get + self.titlefc = g('title', BLACK) + self.majorfc = g('major', wx.BLACK) + self.minorfc = g('minor', wx.Color(128, 128, 128)) + self.linkfc = g('link', wx.BLUE) + self.hovertitlefc = g('emailhovertitle', self.titlefc) + self.hovermajorfc = g('emailhovermajor', self.majorfc) + self.hoverminorfc = g('emailhoverminor', self.minorfc) + self.hoverlinkfc = g('emailhoverlink', self.linkfc) + + for link in self.linkage: + link.NormalColour = link.HoverColour = link.VisitedColour = self.hoverlinkfc + link.Font = self.linkfont + + self.linkage.Layout() + + if self.errorlink: + self.errorlink.Font = self.Parent.Font + self.errorlink.VisitedColour = self.errorlink.HoverColour = self.errorlink.NormalColour = s('infobox.linkcolor', lambda: wx.BLUE) + + self.bg = s('infobox.backgrounds.email', lambda: SkinColor(wx.WHITE)) + self.selbg = s('infobox.backgrounds.emailhover', lambda: SkinColor(wx.Color(225, 255, 225))) + self.sep = s('infobox.shortseparatorimage', None) + + + def GetFullHeight(self): + 'Returns the summed height of all items.' + + return (self.ItemCount or 1) * self.OnMeasureItem(0) + + def SkimIt(self, it): + """ + Given the availible height returns the height it wants in order + for the number of items shown to exactly fit + """ + + if self.ItemCount: + h = self.OnMeasureItem(0) + r = (it // h) * h # Yay for int division and it's always floor attitude + + return r + return self.OnMeasureItem(0) + + def OnMeasureItem(self, n): + """ + Since all items have a predetermined height based determined by + a combination of skin and and weither or not the account type has + a preview, this just returns that predetermined number + """ + return self.itemheight + + def OnDrawBackground(self, dc, rect, n): + 'Draws the background for each item.' + + if self.Selection == n: + self.selbg.Draw(dc, rect, n) + else: + self.bg.Draw(dc, rect, n) + + def OnPaint(self,event): + + if self.ItemCount: + event.Skip() + return + + dc = wx.AutoBufferedPaintDC(self) + rect = RectS(self.Size) + + self.OnDrawBackground(dc, rect, 0) + + dc.Font = self.titlefont + dc.DrawLabel('No Previews' if self.account.count else 'No New Email', rect, wx.ALIGN_CENTER) +# +# errlink = self.errorlink +# if errlink: +# errlink.Position = (self.Size.width - (errlink.Size.width + self.padding[0]), +# self.Size.height - (errlink.Size.height + self.padding[1])) + + def OnDrawItem(self, dc, rect, n): + """ + Draws the content for the item, and positions the links for + selection + """ + issel = n == self.Selection + + email = self.emails[n] + pad = self.padding + sendtime = getattr(email, 'sendtime', None) + if hasattr(sendtime, 'strftime'): + strf = sendtime.strftime + + try: + # Generate a timestamp for the email -- may raise an exception + # if the date is < 1900 + iscurdate = strf('%b %d %y') == datetime.now().strftime('%b %d %y') + + date = strf('%b %d') + time = strf('%I:%M %p') + if time[0] == '0': + time = time[1:] + + if iscurdate and getattr(self.account, 'timestamp_is_time', lambda t: True)(sendtime): + timestamp = time + else: + timestamp = date + except Exception: + traceback.print_exc_once() + timestamp = None + else: + timestamp = sendtime + + if timestamp is None: + timestamp = '' + + curserx = rect.x + pad.x + cursery = rect.y + pad.y + + # Draw the favicon. + from common.favicons import favicon + d = email.domain + icon = favicon(d) if d is not None else None + + iconsize = 16 + if icon is None: + icon = skin.get('emailpanel.miscicons.defaulticon', None) + + if icon is not None: + icon = icon.Resized(iconsize) + dc.DrawBitmap(icon, curserx, cursery, True) + + curserx += iconsize + pad.x + + #draw the timestamp + dc.Font = self.minorfont + dc.TextForeground = self.hoverminorfc if issel else self.minorfc + tste = dc.GetFullTextExtent(timestamp) + tsrect = wx.Rect(rect.x+rect.width-tste[0] - pad.x, cursery, tste[0], tste[1] + tste[2] + tste[3]) + dc.DrawTruncatedText(timestamp, tsrect) + + # draw attachment icon + if email.attachments: + + atticon = skin.get('emailpanel.miscicons.attachmenticon', None) + if atticon is not None: + count = len(email.attachments) + if count > 1: + atticon = add_image_text(atticon, str(count)) + dc.DrawBitmap(atticon, tsrect.X - atticon.Width - pad.x, tsrect.Y, True) + + # draw the sender name or email + dc.Font = self.titlefont + dc.TextForeground = self.hovertitlefc if issel else self.titlefc + dc.DrawTruncatedText(u'%s' % (email.fromname or email.fromemail), + Rect(curserx, cursery, rect.Width-(curserx + pad.x) - tsrect.width, dc.Font.Height)) + + cursery+=dc.Font.Height + self.padding.y + + # Draw the subject + dc.Font = self.majorfont + dc.TextForeground = self.hovermajorfc if issel else self.majorfc + dc.DrawTruncatedText(email.subject or _('(No Subject)'), Rect(curserx, cursery, rect.Width-(curserx+pad.x), dc.Font.Height)) + + # Draw the preview line if applicable + if self.account.can_has_preview: + cursery += dc.Font.Height + pad.y + dc.Font = self.minorfont + dc.TextForeground = self.hoverminorfc if issel else self.minorfc + self.preview_rect = wx.Rect(curserx, cursery, rect.Width-(curserx + pad.x), GetFontHeight(dc.Font, line_height=True)) + if dc.DrawTruncatedTextInfo(email.content[self.preview_offset:] or _('(No Preview)'), self.preview_rect): + self.marquee_timer.Stop() + + # draw separator if there is one and not last email + if self.sep and n < len(self.emails)-1: + self.sep.Draw(dc, wx.RectPS(rect.Position + (2, rect.height-self.sep.Size.height), (rect.width-4, self.sep.Size.height))) + + # place links if selection + if issel: + cursery += dc.Font.Height + pad.y + self.linkage.SetPosition((curserx, cursery)) + + def OnLink(self, event): + # Hide infobox + if event.URL =='OnClickEmail': + wx.GetTopLevelParent(self).Hide() + + # remove selection + getattr(self.account, event.URL)(self.account.emails[self.Selection]) + self.Selection = -1 + self.linkage.Show(False) + + diff --git a/digsby/src/gui/infobox/errorpanel.py b/digsby/src/gui/infobox/errorpanel.py new file mode 100644 index 0000000..40f865c --- /dev/null +++ b/digsby/src/gui/infobox/errorpanel.py @@ -0,0 +1,200 @@ +import wx +from gui.uberwidgets.uberwidget import UberWidget +from gui.textutil import CopyFont,default_font,GetTextWidth +from gui.skin.skinobjects import SkinColor +from gui import skin + +from gui.toolbox.refreshtimer import refreshtimer + +#TODO: This should be replaced with Tenjin templates similar to how Social Networks are handled +class ErrorPanel(wx.Panel,UberWidget): + """ + Panel used to display errors in the infobox + """ + def __init__(self,parent): + wx.Panel.__init__(self,parent,-1) + + self.link = '' + self.linkhovered = False #True if mouse is in the link rect + self.linkdown = False #True if the mouse button was pushed while in link rect + + self.message = None + + self.UpdateSkin() + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e:None) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_ENTER_WINDOW,self.OnMouseIn) + self.Bind(wx.EVT_LEAVE_WINDOW,self.OnMouseOut) + self.Bind(wx.EVT_MOTION,self.OnMouseMotion) + self.Bind(wx.EVT_LEFT_DOWN,self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP,self.OnLeftUp) + self.Bind(wx.EVT_LEFT_DCLICK,lambda e: (self.OnLeftDown(e),self.OnLeftUp(e))) + + + self.Show(False) + + def OnSize(self,event): + """ + This changes the link position when the size of the error panel + changes. + """ + if self.link: + linksize=self.linkrect.Size + if self.link: + self.linkrect = wx.Rect(self.Size.width-linksize.width-self.padding.x,self.Size.height-linksize.height-self.padding.y,*linksize) + self.Refresh(False) + + + def UpdateSkin(self): + """ + The Usual + """ + key = 'infobox' + + if skin.get(key,False): + s = lambda k,d: skin.get('%s.%s'%(key,k),d) + else: + s = lambda k,d: d + + self.padding = s('padding', lambda: wx.Point(2, 2)) + self.labelf = s('fonts.title',lambda: default_font()) + self.labelfc = s('fontcolors.title', wx.BLACK) + self.linkf = CopyFont(s('fonts.link',lambda: default_font()), underline=True) + self.linkfc = s('fontcolors.link', wx.BLUE) + self.bg = s('backgrounds.email', lambda: SkinColor(wx.WHITE)) + + def OnPaint(self,event): + """ + Custom paint, nothing special + """ + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + + dc.Font = self.labelf + dc.TextForeground = self.labelfc + + self.bg.Draw(dc,rect) + + rect2 = wx.Rect(0,0,self.Size.width,self.Size.height-(self.linkrect.Size.height if self.link else 0)) + + + message = self.message + dc.DrawLabel(message() if callable(message) else message,rect2,wx.ALIGN_CENTER) + + if self.link: + dc.Font = self.linkf + dc.TextForeground = self.linkfc + dc.DrawLabel(self.link,self.linkrect) + + + def Error(self, message = None, link = None, callback = None): + """ + This shows the panel with the message provided. + Has a link in the lower right if a link string and callback are provided. + If message is None, the panel is hidden. + If the message is a callable, then the panel is added to the refresh + timer and message is called every 1 second, expecting a string to be + returned. + """ + + self.message = message + + #destroy old link if there is one + self.link='' + + if message: + # if link and callback are provided, make a ClearLink object + if link and callback: + self.linkcb = lambda *a: (wx.GetTopLevelParent(self).Hide(), callback(*a)) + self.link = link + linksize = wx.Size(GetTextWidth(link,self.linkf),self.linkf.Height) + self.linkrect = wx.Rect(self.Size.width-linksize.width-self.padding.x,self.Size.height-linksize.height-self.padding.y,*linksize) + + self.Show(True) + + self.MinSize = wx.Size(-1, 5 * max(self.labelf.Height, self.linkf.Height)) + + self.GrandParent.DoSizeMagic() + self.Refresh() + else: + self.Show(False) + + #if message is a callable, register with refresh timer + if callable(message): + refreshtimer().Register(self) + else: + refreshtimer().UnRegister(self) + + + def SkimIt(self,height): + """ + This is just for compatibility with EmailList and ProfileBox + """ + return height + + def OnLeftDown(self,event): + """ + Detects left mouse button down events for emulation of links and + buttons + """ + + if self.linkhovered: + self.linkdown = True + + def OnLeftUp(self,event): + """ + Detects left mouse button up events for emulation of links and + buttons + """ + + #If link is down + if self.linkdown: + #And if the pointer is over the link + if self.linkhovered: + #call the callback for the link + self.linkcb() + self.linkdown = False + + self.OnMouseMotion(event) + + self.Refresh() + + def OnMouseMotion(self,event): + """ + Insures mouse in and mouse out capture events occure, also handels + hover for the button and link + """ + + #If the left mouse button is not down, make sure the panel gets/losses + # capture accordingly + if not event.LeftIsDown(): + mouseisin = wx.FindWindowAtPointer() is self + + if mouseisin and not self.HasCapture(): + self.OnMouseIn(event) + + elif not mouseisin and self.HasCapture(): + self.OnMouseOut(event) + + #handle link mouseover + if self.link: + mouseinl = self.linkhovered = self.linkrect.Contains(event.Position) + self.SetCursor(wx.StockCursor(wx.CURSOR_HAND if mouseinl else wx.CURSOR_DEFAULT)) + + def OnMouseIn(self,event): + """ + Captures the mouse when the cursor enters the panel + """ + if not self.HasCapture() and not event.LeftIsDown(): + self.CaptureMouse() + + def OnMouseOut(self,event): + """ + Releases the mouse when the cursor leaves the panle + """ + if not event.LeftIsDown(): + while self.HasCapture(): + self.ReleaseMouse() + diff --git a/digsby/src/gui/infobox/htmlgeneration.py b/digsby/src/gui/infobox/htmlgeneration.py new file mode 100644 index 0000000..910d6b8 --- /dev/null +++ b/digsby/src/gui/infobox/htmlgeneration.py @@ -0,0 +1,426 @@ +#TODO: This should be replaced with a newer system similar to how social networks are handle with Tenjin templates +''' +These functions are used to build the HTML used in the infobox for IM buddies +''' + +from gui.buddylist.renderers import get_buddy_icon_url +import wx +from time import time +from xml.sax.saxutils import escape +from gui.textutil import default_font +from util import linkify +from common import pref + +LINK_CSS = '' +'''\ +a:link + { + text-decoration:none; + } +a:hover + { + text-decoration:underline; + } +''' + +import logging +log = logging.getLogger('htmlgeneration') + +from gui.skin import get as skin_get +import gui.skin as skin + +def separatorshort(): + return get_hr('shortseparatorimage') + +def separatorlong(): + return get_hr('longseparatorimage') + +def separatorsocial(): + return get_hr('shortseparatorimage', padding = False) + +def get_hr(key = None, padding = True): + + if key is not None: + separator = skin_get('infobox.%s' % key, None) + else: + separator = None + + if separator is not None: + html = '
' % ((4 if padding else 0), separator.path.url(), separator.Size.height) + else: + html = '
' + + return html + + + +JABBER_SERVICES = [u'jabber', u'gtalk', u'digsby'] + +from common.protocolmeta import protocols + +def ColorHTML(color): + if color[0:2].lower() == '0x': + return ''.join('#', color[2:8]) + return color + +def GenBitmapHTML(key, width, height): + + imagedata = {'source': key, + 'width': width, + 'height': height} + + return u'' % imagedata + +def GenBuddyIconHTML(contact): + + iconinfo = {'iconurl': get_buddy_icon_url(contact), + 'iconsize': pref('infobox.iconsize', 64)} + + return (u'') % iconinfo + +def GenStatusIconHTML(contact): + + sicon = skin_get("statusicons." + contact.status_orb) + margins = skin_get('infobox.margins') + + iconinfo = {'iconurl': sicon.path.url(), + 'iwidth': sicon.Width, + 'iheight': sicon.Height, + 'iposx': margins.right + (16-sicon.Width)//2, + 'iposy': margins.top} + + return u'' % iconinfo + +def FontTagify(string, fonttype, nowrap = False): + font = skin_get('infobox.fonts.%s' % fonttype, default_font) + color = skin_get('infobox.fontcolors.%s' % fonttype, wx.BLACK).GetAsString(wx.C2S_HTML_SYNTAX) + + if isinstance(string, str): + try: + string = string.decode('fuzzy') # Last ditch attempt + except UnicodeDecodeError, e: + log.info('Couldn\'t put %r into a font tag- decode failed with error %r', string, e) + return '' + + tag = u''.join(['' % + {'facename': font.FaceName, + 'size': font.PointSize, + 'color': color, + 'nowrap': 'white-space: nowrap;' if nowrap else ''}, + '' if font.Weight == wx.BOLD else '', + '' if font.Style == wx.ITALIC else '', + '' if font.Underlined else '', + string, + '' if font.Underlined else '', + '' if font.Style == wx.ITALIC else '', + '' if font.Weight == wx.BOLD else '', + '']) + return tag + + +def TitleHTML(title): + return FontTagify((title+'  ').replace('\n','
'),'title') + +def BodyHTML(text): + return FontTagify(escape(text).replace('\n','
'),'minor') + +def DetailHTML(text): + return FontTagify(text.replace('\n','
'),'minor') + +def List2HTML(listostuff): + string='' + for stuff in listostuff: + string=''.join([string,(BodyHTML(stuff) if isinstance(stuff, basestring) else + LinkHTML(*stuff) if isinstance(stuff, tuple) else + List2HTML(stuff) if isinstance(stuff, list) else + ImageHTML(**stuff) if isinstance(stuff, dict) else '')]) + + return string + +def NoProfile(): + return FontTagify(_('No Profile'), 'minor') + +def ListProfile2HTML(listostuff): + profile=[] + for stuff in listostuff: + profile.append(stuff if isinstance(stuff,basestring) else + PrettyProfile2HTML(stuff) if isinstance(stuff,dict) else + NoProfile() if stuff is None else + '') + + return ''.join(profile) + +def PrettyProfile2HTML(profile): + if not profile: return NoProfile() + string='' + + for key in profile.keys(): + value = profile[key] + if value: + if isinstance(value,int): string=''.join([string,exbr(value)]) + else: string=''.join([string, TitleHTML(key), + (BodyHTML(value) if isinstance(value,basestring) else + LinkHTML(*value) if isinstance(value,tuple) else + List2HTML(value) if isinstance(value,list) else + ImageHTML(**value) if isinstance(value,dict) else ''), + '
']) + + return string + +def exbr(height): + return '
' % height + +def ProfileHTML(profile): + + if isinstance(profile, dict): + profile = PrettyProfile2HTML(profile) + elif isinstance(profile, list): + profile = ListProfile2HTML(profile) + else: + profile = u''.join([u'
', NoProfile(), '
']) + + return u''.join([u'', + u'
', + separatorlong(), + profile]) + +def ImageHTML(**attrs): + if not attrs: + return '' + + if 'src' in attrs: + key = 'url' + cls = 'BitmapFromWeb' + data = attrs['src'] + elif 'data' in attrs: + from base64 import b64encode + log.error('Getting a bitmap from data is deprecated; data was: \n%s', b64encode(attrs['data'])) +# key = 'data' +# cls = 'BitmapFromData' +# data = b64encode(attrs['data']) + else: + assert False + + w = attrs.get('width', -1) + h = attrs.get('height', -1) + href = attrs['href'] + + return ('') % locals() + +def LinkHTML(url,text, nowrap = False): + return u''.join([u'', + FontTagify((text.replace('\n','
') if isinstance(text,basestring) else ImageHTML(text) if isinstance(text,dict) else ''), + 'link', + nowrap = nowrap), + + u'
']) + +def GenTimedString(timeinsecs): + secs = int(time()) - timeinsecs + + mins, secs = divmod(secs, 60) + hours, mins = divmod(mins, 60) + days, hours = divmod(hours, 24) + + if not (days > 0 or hours > 0 or mins > 0): + return u'<1m' + + timeStr = u'' + if days > 0: timeStr += str(int(days)) + u'd ' + if hours > 0: timeStr += str(int(hours)) + u'h ' + if mins > 0 and days == 0: timeStr += str(int(mins)) + u'm' + + return timeStr + +def JabberStatusMagic(contact): + string=u''.join([TitleHTML(_('Subscription:')),BodyHTML(contact.protocol.roster.get_item_by_jid(contact.id).subscription.capitalize())]) + for r in contact.resources.values(): + string=u''.join([string,separatorshort(),TitleHTML(_('Resource:')),BodyHTML(u''.join([r.jid.resource or '',u' (',str(r.priority),u')']))]) + string=u''.join([string,u'
',TitleHTML(_(u'Status:')),BodyHTML(r.sightly_status)]) + import hooks + if r.status_message and hooks.reduce('digsby.status.tagging.strip_tag', r.status_message, impl='text'): + string=u''.join([string,u'
',BodyHTML(hooks.reduce('digsby.status.tagging.strip_tag', r.status_message, impl='text'))]) + return string + +def GetLocationFromIP(contact): + import urllib2 + import lxml + from util.primitives.mapping import odict + from common import pref + info = odict() + + if pref('digsby.guest.geoiplookup', default = False, type = bool): + if getattr(contact, 'ip', False): + url = 'http://www.iplocationtools.com/ip_query.php?output=xml&ip=%s' % contact.ip + try: + resp = urllib2.urlopen(url).read() + except Exception: + import traceback + traceback.print_exc() + return info + + try: + doc = lxml.etree.fromstring(resp) + except Exception: + import traceback + traceback.print_exc() + return info + + divs = [ + ('City', 'City'), + ('RegionName', 'State'), + ('CountryCode', 'Country'), + ] + for tag, key in divs: + val = doc.find(tag) + if tag: + info[key] = val.text + + return info + +def GetInfo(contact, showprofile=False, showhide=True, overflow_hidden=True):#showicon=True): + + css = '''\ +table{ + table-layout: fixed; +} +body{ + word-wrap: break-word; + %s +} +div{ + overflow: hidden; +} +''' % ('overflow: hidden' if overflow_hidden else '') + LINK_CSS + skin.get_css() + + no_icon_path = skin.get('BuddiesPanel.BuddyIcons.NoIcon').path.url() + + constanttop = u'''\ + + + + + + +
+''' % dict(css=css, no_icon_path=no_icon_path) + + constantmiddle = u'' + constantbottom = u'
' + + s = contact.serviceicon + + if contact.service == 'digsby' and getattr(contact, 'iswidget', False): + s = 'widget' + + ico = skin_get('serviceicons.' + s) if isinstance(s, basestring) else s + servico=u''.join([u'
', + GenBitmapHTML(ico.path.url(), 16, 16), + u'
']) + + alias = contact.alias + name=u''.join([u'
', + FontTagify(escape(alias),'header'), + u'
']) + + if s=='widget': + location = GetLocationFromIP(contact) #odict + moreinfo = u'' + + if location: + moreinfo = u''.join(['
', + TitleHTML(_(u'Location:')), + BodyHTML(', '.join(location.values())), + '
']) + + ip = u''.join(['
', + TitleHTML(_(u'IP Address:')), + '' % contact.ip, + BodyHTML(contact.ip), + '', + '
']) + + time_ = u''.join(['
', + TitleHTML(_(u'Time on Page:')), + DetailHTML(GenTimedString(contact.online_time)), + '
']) + + + html = u''.join([constanttop, servico, name, time_, ip, moreinfo, constantbottom]) + + return html + + nicename = contact.nice_name + + if nicename != alias: + username = u''.join(['
', + TitleHTML(protocols[contact.service].username_desc + u':'), + BodyHTML(nicename), + '
']) + else: + username = '' + + profile = ProfileHTML(contact.pretty_profile) if showprofile else u'' + + times = '' + if contact.service in ('icq', 'aim') and contact.online_time: + times = u''.join([TitleHTML(_(u'Online:')), + DetailHTML(GenTimedString(contact.online_time)) + ]) + + idle_since = contact.idle + if contact.service in ('icq', 'aim', 'yahoo') and idle_since and idle_since is not True: + + times += (u''.join([TitleHTML(('  ' if times else '') + _(u'Idle:')), + DetailHTML(GenTimedString(idle_since)), + ])) + + away_since = getattr(contact, 'away_updated', None) + if getattr(contact, 'away', False) and away_since: + times += (u''.join([TitleHTML(_(('  ' if times else '') + _(u'Away:'))), + DetailHTML(GenTimedString(away_since)) + ])) + if times: + times = '
%s
' % times + + if contact.status_orb == 'unknown' or contact.service not in JABBER_SERVICES: + status = u''.join(['
', + TitleHTML(_(u'Status:')), + BodyHTML((_('{status} + Idle').format(status = contact.sightly_status) if contact.status == u'away' and contact.idle else contact.sightly_status)), + '
']) + else: + status = JabberStatusMagic(contact) + + statusmsg = getattr(contact, '_infobox_status_message', contact.status_message) + import hooks + if statusmsg is not None: + statusmsg = hooks.reduce('digsby.status.tagging.strip_tag', statusmsg, impl='text') + if not statusmsg or contact.service in JABBER_SERVICES: + statusmsg = '' + else: + if contact.service not in ('aim', 'icq'): + statusmsg = BodyHTML(statusmsg) + + statusmsg = u''.join((separatorshort(), statusmsg)) + + icon = ''.join([constantmiddle, + GenStatusIconHTML(contact), + GenBuddyIconHTML(contact), + LinkHTML(u'profile', (_(u'Hide Profile') if showprofile else _(u'Show Profile')) if showhide else '', nowrap = True)]) + + html = u''.join([constanttop, servico, name, username, + times, status, statusmsg, icon, profile, constantbottom]) + + return linkify(html) diff --git a/digsby/src/gui/infobox/infobox.py b/digsby/src/gui/infobox/infobox.py new file mode 100644 index 0000000..fd47fd6 --- /dev/null +++ b/digsby/src/gui/infobox/infobox.py @@ -0,0 +1,1824 @@ +''' +Mouseover popup for showing contact information on the buddylist. +''' +from __future__ import with_statement + +from util.vec import vector +from util import linkify +from rpc.jsonrpc import JSPythonBridge +import logging + +import config +import sys +import hooks +from wx import RectPS, RectS, Point, CallLater, \ + GetMouseState, FindWindowAtPointer, GetMousePosition, Size, PaintDC, AutoBufferedPaintDC, \ + BLACK, GREEN, WHITE, BLUE, RED_PEN #@UnresolvedImport + +import warnings +from gui.browser.webkit import WebKitWindow +from gui.toolbox.scrolling import WheelScrollMixin, WheelShiftScrollFastMixin, WheelCtrlScrollFastMixin,\ + FrozenLoopScrollMixin +import wx.webview +from gui.capabilitiesbar import CapabilitiesBar +from contacts.Contact import Contact +from contacts.metacontacts import MetaContact +from common.emailaccount import EmailAccount +from social.network import SocialNetwork +import protocols +from .adapters import AccountCachingIBP, CachingIBP +from .interfaces import ICacheableInfoboxHTMLProvider, IInfoboxHTMLProvider, ICachingInfoboxHTMLProvider + +def adapt_cache(obj): + try: + return CachingIBP(obj) + except protocols.AdaptationFailure: + return AccountCachingIBP(obj) + +protocols.declareAdapterForType(ICachingInfoboxHTMLProvider, adapt_cache, SocialNetwork) + +from gui import skin +from gui.skin.skinobjects import SkinColor, Margins +from gui.infobox.emailpanels import EmailList, Header +from common import pref, setpref, profile +from gui.buddylist import BuddyList +from gui.textutil import default_font +from util import Storage, Point2HTMLSize +from gui.infobox.errorpanel import ErrorPanel +from gui.windowfx import ApplySmokeAndMirrors #@UnresolvedImport +from common import prefprop +from gui.toolbox import Monitor, GetDoubleClickTime +from gui import imwin +from config import platform +import cgui +from common import protocolmeta +from time import time + +from gui.windowfx import move_smoothly, resize_smoothly +from gui.infobox.htmlgeneration import GetInfo, LINK_CSS + +from logging import getLogger; log = getLogger('infobox') + +import traceback + +DEFAULT_INFOBOX_WIDTH = 350 +TRAY_TIMER_MS = 500 + +from threading import currentThread + +class InfoboxWebkitWindow(FrozenLoopScrollMixin, + WheelShiftScrollFastMixin, WheelCtrlScrollFastMixin, + WheelScrollMixin, WebKitWindow): + pass + +class ExpandoPanel(wx.Panel): + """ + These are the panels in the infobox that represent contacts of a + metacontact + """ + def __init__(self, parent, infobox): + wx.Panel.__init__(self, parent, pos = (-300, -300)) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.SetMinSize(wx.Size(20, 20)) + + self.infobox = infobox + + Bind = self.Bind + Bind(wx.EVT_PAINT, self.OnPaint) + Bind(wx.EVT_LEFT_UP, self.OnLUp) + Bind(wx.EVT_LEFT_DCLICK, self.OnDClick) + Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter) + Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) + + def OnMouseEnter(self, event): + """ + Makes sure the mouse is captured if the cursor is over the panel + """ + if not self.HasCapture(): self.CaptureMouse() + self.Refresh(False) + + def OnMouseLeave(self, event): + "Ensure mouse is released when it has left the panel" + + self.ReleaseAllCapture() + self.Refresh(False) + + def OnPaint(self, event): + dc = AutoBufferedPaintDC(self) + + infobox = self.infobox + contact = infobox.metacontact[infobox.expandopanels.index(self)] + rect = RectS(self.Size) + iconsize = 16 + padx = infobox.margins.left #+ infobox.margins.right + + i = 2 if infobox.account == contact else self.HasCapture() + + infobox.contactbg[i].Draw(dc, rect) + + dc.SetFont(infobox.headerfont) + dc.SetTextForeground(infobox.contactfontcolor[i]) + + name = contact.alias + servico = contact.serviceicon.Resized(iconsize) + statico = skin.get("statusicons." + contact.status_orb).ResizedSmaller(iconsize) + + dc.DrawBitmap(servico, 2+padx, 2, True) + dc.DrawText(name, 21+padx, 2) #18 + (16 - statico.Size.width)//2 - + dc.DrawBitmap(statico, rect.width - (padx + (16-statico.Width)//2) - statico.Width, rect.height//2- statico.Height//2) + + + def OnDClick(self, event): + infobox = self.infobox + contact = infobox.metacontact[infobox.expandopanels.index(self)] + + infobox.Hide() + imwin.begin_conversation(contact) + + def OnLUp(self, event): + "Set contact in infobox if clicked" + + infobox = self.infobox + + contact = infobox.metacontact[infobox.expandopanels.index(self)] + + if self.HasCapture() and not contact is infobox.account: + infobox.SelectContact(contact) + infobox.cpanel.Refresh(False) + +class InfoBoxShowingTimer(wx.Timer): + """ + Timer for delayed showing of the infobox + """ + def __init__(self, infobox): + self.infobox = infobox + wx.Timer.__init__(self) + + def Start(self, contact): + self.contact = contact + wx.Timer.Start(self, pref('infobox.show_delay', 1000), True) + + def Notify(self): + i = self.infobox + if i is None or wx.IsDestroyed(i): + print >>sys.stderr, 'Infobox is dead but still getting notified!' + return + if i.FriendlyTouch(): + i.ShowOnScreen() + i.InfoSync() + i.quickshow = True + +class InfoBoxHidingTimer(wx.Timer): + """ + Hides the infobox when the timer is up, disables other timers + """ + def __init__(self, infobox): + self.infobox = infobox + wx.Timer.__init__(self) + + def Notify(self): + i = self.infobox + if i is None or wx.IsDestroyed(i): + return + + i.mouseouttimer.Stop() + i.showingtimer.Stop() + i.quickshow = False + i.Hide() + +class InfoBoxTrayTimer(wx.Timer): + "When summonned by the tray icon" + + def __init__(self, infobox): + wx.Timer.__init__(self) + self.infobox = infobox + + def Notify(self): + try: mp = GetMousePosition() + except Exception: return + + i = self.infobox + if i is None or wx.IsDestroyed(i): + return + + infobox_rect = i.Rect.Inflate(30, 30) + + if not infobox_rect.Contains(mp) and not cgui.GetTrayRect().Contains(mp): + i.Hide() + self.Stop() + + + +class InfoBoxMouseOutTimer(wx.Timer): + 'This is used to detect when the mouse leaves the infobox.' + + def __init__(self, infobox): + self.infobox=infobox + wx.Timer.__init__(self) + self.hider = InfoBoxHidingTimer(infobox) + self.noneweredown = False + + def Start(self): + wx.Timer.Start(self, 200) + self.delayhide = False + + def Notify(self): + try: mp = GetMousePosition() + except: return + + ms = GetMouseState() + button_down = ms.LeftDown() or ms.RightDown() or ms.MiddleDown() + + infobox = self.infobox + infobox_rect = infobox.Rect + + inside = infobox_rect.Contains(mp) + ftouch = infobox.FriendlyTouch(mp) + + # Decide to delayhide or not, yes if mouse is inside the infobox + # no if mouse is in a friendly + if inside: self.delayhide = True + elif ftouch: self.delayhide = False + + # If mouse is not over any object that should keep the infobox open start hider + hider = self.hider + if not (inside or ftouch or infobox.capbar.cbar.overflowmenu.IsShown() or infobox.capbar.cto.menu.IsShown() or infobox.capbar.cfrom.menu.IsShown()) and self.noneweredown: + # If a mouse button is down, close immediately + if button_down: + hider.Start(1, True) + + # otherwise with the delay specified in prefs + if not hider.IsRunning(): + wap = wx.FindWindowAtPoint(mp) + if wap is not None: + inancestor = infobox.Parent.Top is wap.Top and wap.Top is not wap + else: + inancestor = False + + hider.Start(pref('infobox.hide_delay', 1000) if self.delayhide or inancestor else 1, True) + + # otherwise stop the hiding process if started + else: + self.noneweredown = False + if hider.IsRunning(): + hider.Stop() + + self.noneweredown = not button_down + + +class InfoBox(wx.Frame): + """ + A Box of Information + ... + Well, that didn't help much did it + + This is a window that displays information on whatever you're currently + moused over on the buddy list. It shows details on Contacts, MetaContacts + Emails, and Social Networks. It is also used for accounts on the + system tray when clicked. + """ + + animation_time = prefprop('infobox.animation.time', 200) + animation_interval = prefprop('infobox.animation.interval', 10) + animation_method = prefprop('infobox.animation.method', 1) + animate = prefprop('infobox.animate', False) + right_of_list = prefprop('infobox.right_of_list', True) + max_angle_of_entry = prefprop('infobox.max_angle_of_entry', 60) + min_angle_of_entry = prefprop('infobox.min_angle_of_entry', 30) + pause_time = prefprop('infobox.pause_time', 250) + width = prefprop('infobox.width', DEFAULT_INFOBOX_WIDTH) + + def __dtor__(self): + for attr in ('mouseouttimer', 'showingtimer', 'hidingtimer', 'traytimer'): + obj = getattr(self, attr, None) + if obj is not None: + obj.infobox = None + obj.Stop() + + def __init__(self, parent): + style = wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP | wx.NO_BORDER + + # On Mac, FRAME_TOOL_WINDOW windows are not visible when the app is inactive. + if config.platform == 'win': + style |= wx.FRAME_TOOL_WINDOW + + wx.Frame.__init__(self, parent, -1, '', style = style ) + + self.friends = set() + + #When displayed should the infobox fade in or be shown instantly + self.quickshow=False + + #this is where the metacontacts stored if there is one + self.metacontact=None + #this is the Contact, EmailAccount, or SocialNetwork selected + self.account = None + self._doubleclickhide = False + + #list of panels for MetaContact's Contacts + self.expandopanels=[] + + #Various timers for the infobox's showing and hiding delays + self.mouseouttimer = InfoBoxMouseOutTimer(self) + self.showingtimer = InfoBoxShowingTimer(self) + self.hidingtimer = None + self.traytimer = InfoBoxTrayTimer(self) + + self.timers = [self.mouseouttimer, self.showingtimer, self.hidingtimer] + + self.animationtimer = None + + self.htmlcacher = construct_html_cacher() + + #position left and position right options + self.pl = wx.Point(0, 0) + self.pr = wx.Point(0, 0) + + #Force to corner for systemtray usage + self.fromTray=False + self.shouldShow = False + + self.Show(False) + + panel = self.panel = wx.Panel(self, pos = (-400, -400)) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.UpdateSkin() + + self.content=wx.BoxSizer(wx.VERTICAL) + + #Margin stuff, replaceable with a margins object? + sizer = panel.Sizer = wx.GridBagSizer() + sizer.SetEmptyCellSize(wx.Size(0, 0)) + sizer.Add(self.content, (1, 1), flag = wx.EXPAND) + sizer.Add(wx.Size(self.framesize.left, self.framesize.top), (0, 0)) + sizer.Add(wx.Size(self.framesize.right, self.framesize.bottom), (2, 2)) + sizer.AddGrowableCol(1, 1) + sizer.AddGrowableRow(1, 1) + + # hook actions up to capabilities bar buttons + caps = self.capbar = CapabilitiesBar(self.panel, lambda: self.account, True, True) + + # each of these buttons opens an IM window with the contact in the specified mode. + for b in ('info', 'im', 'email', 'sms'): + caps.GetButton(b).Bind(wx.EVT_BUTTON, + lambda e, b=b: (self.Hide(), wx.CallAfter(self.account.imwin_mode, b))) + + def show_prefs_notifications(): + import gui.pref.prefsdialog + gui.pref.prefsdialog.show('notifications') + + caps.OnSendFiles += lambda *a: (self.Hide(), self.account.send_file()) + caps.OnSendFolder += lambda *a: (self.Hide(), self.account.send_folder()) + caps.OnViewPastChats += lambda *a: (self.Hide(), self.account.view_past_chats()) + caps.OnAlert += lambda *a: (self.Hide(), show_prefs_notifications()) + +#------------------------------------------------------- +#HAX: Do this right + #CallAfter(caps.bmultichat.Show,False) + wx.CallAfter(caps.ShowToFrom, False) + wx.CallAfter(caps.ShowCapabilities, pref('infobox.showcapabilities', True)) + +#----------------------------------------------------------- + + self.content.Add(self.capbar, 0, wx.EXPAND) + + #add the email header + self.eheader = Header(panel) + self.content.Add(self.eheader, 0, wx.EXPAND) + self.eheader.Show(False) + + #add the content panel + self.cpanel = wx.Panel(panel) + #self.cpanel.SetBackgroundColour(wx.WHITE) + self.cpanel.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.cpanel.Sizer = wx.BoxSizer(wx.VERTICAL) + self.content.Add(self.cpanel, 1, wx.EXPAND) + + #add the email list + self.elist = EmailList(panel) + self.content.Add(self.elist, 1, wx.EXPAND) + self.elist.Show(False) + + #add the error panel + self.errorpanel = ErrorPanel(panel) + self.content.Add(self.errorpanel, 1, wx.EXPAND) + self.errorpanel.Show(False) + + panel.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + panel.Bind(wx.EVT_PAINT, self.OnPaint) + panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + panel.SetSize(wx.Size(DEFAULT_INFOBOX_WIDTH, DEFAULT_INFOBOX_WIDTH)) + + #add profile box + self.profilebox = self.construct_webview() + self.account_webviews = {None: self.profilebox} + self.account_jsbridges = {None: JSPythonBridge(self.profilebox)} + + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_WINDOW_DESTROY, self._on_destroy) + self.Bind(wx.EVT_CLOSE, self.__onclose) + + wx.CallAfter(self.DoSizeMagic) + + def OnKeyDown(self, e): + # hack Ctrl+C to copy + if e.KeyCode == ord('C') and e.GetModifiers() == wx.MOD_CMD: + self.profilebox.Copy() + else: + e.Skip() + + def construct_webview(self): + webview = InfoboxWebkitWindow(self.cpanel, style=wx.WANTS_CHARS) # WANTS_CHARS means webkit will receive arrow keys, enter, etc + webview.hosted = False + + # HACK: see HACK note in main.on_proxy_info_changed + from main import set_did_create_webview + set_did_create_webview() + + self.setup_webview(webview) + self.cpanel.Sizer.Add(webview, 1, wx.EXPAND) + return webview + + def setup_webview(self, webview): + b = webview.Bind + b(wx.EVT_KEY_DOWN, self.OnKeyDown) + b(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.OnBeforeLoad) + b(wx.webview.EVT_WEBVIEW_LOAD, self.OnHTMLLoad) + b(wx.EVT_MOUSEWHEEL, self.on_mousewheel) + + from gui.browser.webkit import setup_webview_logging + jslog = getLogger('infobox_js') + import gui.skin + setup_webview_logging(webview, jslog, logbasedir=gui.skin.resourcedir()) + + def __onclose(self, e): + # alt+f4 should just hide the infobox, not destroy it + self.Hide() + + def _on_destroy(self, e): + e.Skip() + if e.EventObject is self: + for t in self.timers: + t.Stop() + + def on_mousewheel(self, e): +# e.Skip() + + # hack to forward mouse events to the email list + if self.elist.IsShown(): + return self.elist.OnMouseWheel(e) + elif self.profilebox.IsShown(): + return self.profilebox._on_mousewheel(e) + + def do_focus(self): + for ctrl in (self.profilebox, self.elist, self.cpanel): + if ctrl.IsShown(): + self.ReallyRaise() + ctrl.SetFocusFromKbd() + break + + def UpdateSkin(self): + s = skin.get +# self.border = 3 + self.framesize = s('infobox.framesize', lambda: Margins([0, 0, 0, 0])) + self.padding = s('infobox.padding', lambda: Point(2, 2)) + self.margins = wx.Rect(*s('infobox.margins', lambda: [4, 4, 4, 4])) + + self.headerfont = s('infobox.fonts.header', default_font) + self.headerfc = s('infobox.fontcolors.header', BLACK) + self.headerhoverfc = s('infobox.fontcolors.contacthover', BLACK) + self.headerselfc = s('infobox.fontcolors.contactselected', BLACK) + + titlefont = skin.get('infobox.fonts.title', default_font) + majorfont = skin.get('infobox.fonts.major', default_font) + minorfont = skin.get('infobox.fonts.minor', default_font) + linkfont = skin.get('infobox.fonts.link', default_font) + + + h = self.htmlfonts = Storage() + +#------------------------------------------------------------------------------- +# Code for TagFont function +#---------------------------------------- + h.header = self.headerfont + h.title = titlefont + h.major = majorfont + h.minor = minorfont + h.link = linkfont + + h.headerfont = self.headerfont.FaceName + h.titlefont = titlefont.FaceName + h.majorfont = majorfont.FaceName + h.minorfont = minorfont.FaceName + h.linkfont = linkfont.FaceName + + h.headersize = Point2HTMLSize(self.headerfont.PointSize) + h.titlesize = Point2HTMLSize(titlefont.PointSize) + h.majorsize = Point2HTMLSize(majorfont.PointSize) + h.minorsize = Point2HTMLSize(minorfont.PointSize) + h.linksize = Point2HTMLSize(linkfont.PointSize) + + h.headerfc = self.headerfc.GetAsString(wx.C2S_HTML_SYNTAX) + h.titlefc = s('infobox.fontcolors.title', BLACK).GetAsString(wx.C2S_HTML_SYNTAX) + h.majorfc = s('infobox.fontcolors.major', BLACK).GetAsString(wx.C2S_HTML_SYNTAX) + h.minorfc = s('infobox.fontcolors.minor', lambda: wx.Color(128, 128, 128)).GetAsString(wx.C2S_HTML_SYNTAX) + h.linkfc = s('infobox.fontcolors.link', BLUE).GetAsString(wx.C2S_HTML_SYNTAX) +#------------------------------------------------------------------------------- + + self.bg = s('infobox.frame', lambda: SkinColor(BLACK)) + + self.barskin = s('capabilitiesbar', None) + + self.contactbg = [s('infobox.backgrounds.contact', lambda: SkinColor(wx.Colour(128, 128, 128))), + s('infobox.backgrounds.contacthover', lambda: SkinColor(GREEN)), + s('infobox.backgrounds.contactselected', lambda: SkinColor(WHITE))] + + self.contactfontcolor=[s('infobox.fontcolors.contact', BLACK), + s('infobox.fontcolors.contacthover', BLACK), + s('infobox.fontcolors.contactselected', BLACK)] + s = self.panel.Sizer + if s: + s.Detach(1) + s.Detach(1) + s.Add(wx.Size(self.framesize.left, self.framesize.top), (0, 0)) + s.Add(wx.Size(self.framesize.right, self.framesize.bottom), (2, 2)) + if hasattr(self, 'capbar') and hasattr(self, 'cpanel'): + sizer = self.content + sizer.Detach(self.capbar) + sizer.Detach(self.cpanel) + sizer.Add(self.capbar, 0, wx.EXPAND) + sizer.Add(self.cpanel, 1, wx.EXPAND) + + self.htmlcacher.clear() + + def OnPaint(self, event): + self.bg.Draw(PaintDC(self.panel), RectS(self.panel.Size)) + + def OnHTMLLoad(self, e): + e.Skip() + if e.GetState() == wx.webview.WEBVIEW_LOAD_ONLOAD_HANDLED: + if not self.should_use_maxheight: + wx.CallAfter(self.DoSizeMagic) + + def DoSizeMagic(self): + "Determines the size for the infobox, based on what's shown." + + self.last_do_size_magic = time() + width = self.width + + # How much space is filled by panels if shown + filled = sum(panel.Size.height for panel in self.expandopanels) + + # How much space is filled by the capabilities bar if shown + if self.capbar.Shown: + filled += self.capbar.Size.height + + # How much space is filled by the header if shown + if self.eheader.Shown: + filled += self.eheader.Size.height + + # calculate the max height in pixels based off of the percent max height of the screen from prefs + # TODO: this isn't correct when the infobox isn't shown from the tray (#3568) + maxheight = Monitor.GetFromWindow(wx.FindWindowByName('Buddy List')).ClientArea.height * pref('infobox.ratio_to_screen', 0.75) #@UndefinedVariable + use_maxheight = self.should_use_maxheight + + # Get the desired height of the dynamically sized object displayed + if self.elist.Shown: + content = self.elist + desired = content.GetFullHeight() + elif self.cpanel.Shown: + content = self.profilebox + + if use_maxheight: + desired = maxheight + #this really should be json.dumps, but it's only an int. + js = "try {setInfoboxDesiredHeight(%d);} catch (err){}" % desired + content.RunScript(js) + else: + js = "document.getElementById('content').clientHeight;" + desired = content.RunScript(js) + if desired in (None, ''): + log.error("No HTML element with ID 'content' in infobox") + return + desired = int(desired) + self.margins.top + self.margins.bottom #+ 2*self.padding.y + + js = 'document.body.style.overflow = "auto"' + content.RunScript(js) + + + else: + content = self.errorpanel + desired = content.MinSize.height + + # determine the height the infobox is actually going to be + if self.should_use_maxheight: + # some accounts just always take 75% + allotted = maxheight + else: + allotted = min(desired + filled, maxheight) + + # sets the content size to be less that the alloted size but not enough to show partial items + contentsize = content.SkimIt(allotted-filled) if isinstance(content, EmailList) and allotted == maxheight else allotted - filled + content.SetMinSize(wx.Size(width, contentsize)) + + sz = Size(width + self.framesize.left + self.framesize.right, + contentsize + filled + self.framesize.top + self.framesize.bottom) + + # animate or just set the infobox to the new size + if getattr(self, '_resizingto', None) != sz and pref('infobox.animation.resizing', False): + resize_smoothly(self, sz) + self._resizingto = sz + else: + if not self.fromTray: + self.Size = sz + + self.panel.SetSize(sz) + wx.CallAfter(self.panel.Layout) + wx.CallAfter(self.cpanel.Layout) + + if hasattr(self, 'bg') and isinstance(self.bg, cgui.SplitImage4): + ApplySmokeAndMirrors(self, self.bg.GetBitmap(sz)) + else: + ApplySmokeAndMirrors(self) + + if self.fromTray: + self.RepositionTray(sz) + + def DelayedHide(self): + """ + This starts a cancelable timer that will close the infobox in + 1 second if not interupted + """ + if not self.hidingtimer: + self.hidingtimer = CallLater(1000, self.DoDelayedHide) + + def DoDelayedHide(self): + """ + Second half of a delayed hide, called by the timer when it's up. + Hides the infobox if the mouse isn't on the rect + """ + self.hidingtimer = None + + if not self.Rect.Contains(GetMousePosition()): + self.Hide() + + def Hide(self): + """ + Hides the infobox immediately, stopping the hiding and showing timers + if running + """ + + if self.hidingtimer: + self.hidingtimer.Stop() + self.hidingtimer = None + + if self.showingtimer.IsRunning(): + self.showingtimer.Stop() + + if self.traytimer.IsRunning(): + self.traytimer.Stop() + + self.Show(False) + self.infobox_app_hiding() + + def DoubleclickHide(self): + 'A special temporary hide for one buddy.' + + self._doubleclickhide = True + self.Hide() + + def InvalidateDoubleclickHide(self): + 'Invalidate any special hide requested by "ActivatedHide"' + + self._doubleclickhide = False + + def DrawCrap(self, c1, c2, mp): + """ + Code used for debugging, left in because it's kinda cool + draws lines from the mouse pointer to the coreners of the infobox. + Happens when the pref infobox.tracelines is true + """ + + from math import tan, radians + + DX=abs(c1.x-mp.x) + LA = self.min_angle_of_entry + HA = self.max_angle_of_entry + +# print 'DX',DX + +# print 'min and max angles',LA,HA + + minDY=int(abs(tan(radians(LA))*DX)) + maxDY=int(abs(tan(radians(HA))*DX)) + +# print 'min and max dy',minDY,maxDY + +# print 'delta y c1 and c2',abs(c1.y-mp.y),abs(c2.y-mp.y) + + DY1=min(max(abs(c1.y-mp.y), minDY), maxDY) + DY2=min(max(abs(c2.y-mp.y), minDY), maxDY) + +# print 'delta ys',DY1,DY2 + + AP1 = Point(c1.x, mp.y-DY1) + AP2 = Point(c1.x, mp.y+DY2) + + sdc=wx.ScreenDC() + sdc.SetBrush(wx.Brush(wx.Color(0, 255, 0, 125))) + sdc.SetPen(wx.Pen(wx.Color(0, 0, 255, 255))) + + sdc.DrawPolygon((AP1, AP2, mp)) + + sdc.Pen=RED_PEN + sdc.DrawLinePoint(mp, c1) +# sdc.Pen=wx.GREEN_PEN + sdc.DrawLinePoint(mp, c2) +# sdc.Pen=wx.BLACK_DASHED_PEN +# sdc.DrawLinePoint(mp,(c1.x,(c2.y-c1.y)//2+c1.y)) +# sdc.Pen=wx.Pen(wx.ColorRGB((0,0,255))) +# sdc.DrawLinePoint(lp,mp) + + def StateChanged(self, *a): + wx.CallAfter(self._StateChanged, *a) + + def _StateChanged(self, *a): + """ + This is the callback for when the state changes in the current + account for EmailAccounts and Social Networks + """ + + did_infosync = False + account = self.account + + # determine if the account is online and what type of account it is + online = account.state in (account.Statuses.ONLINE, account.Statuses.CHECKING) + + # remove an account specific webview if it has one, and the account is offline + if not online and account is not None: + self.remove_webview(account) + + issocnet = isinstance(account, SocialNetwork) + isemail = isinstance(account, EmailAccount) + + #capbar is only shown on contact based accounts + self.capbar.Show(False) + + #the content panel is shown by online SocialNetworks + self.cpanel.Show(issocnet and online) + #Header is shown, regardless of state + self.eheader.SetAccount(account) + self.eheader.Show(True) + + #if the account is an online EmailAccount, then the list is shown + if isemail and online: + self.elist.SetAccount(account) + else: + self.elist.Show(False) + + if issocnet and online and account.dirty: + #____log.info('InfoSync due to state changed in %s, active account is %s', obj, account) + self.InfoSync() + did_infosync = True + + # If the account is not online, it tries to get a reason message + # if there is not one, just is Offline, or None if online + active = account.state == account.Statuses.OFFLINE and account.offline_reason == account.Reasons.WILL_RECONNECT + if active: + message = lambda: profile.account_manager.state_desc(account) + else: + message = profile.account_manager.state_desc(account) or 'Offline' if not online else None + + error_link = account.error_link() + if error_link is None: + # If failed to get a link, just set the panel to the message + # Hides itself if the message is None + self.errorpanel.Error(message) + else: + link, cb = error_link + self.errorpanel.Error(message, link, cb) + + return did_infosync + + @property + def BuddyListItem(self): + return self.metacontact if self.metacontact else self.account + + def SelectNext(self): + mc = self.metacontact + if mc: + i = mc.index(self.account) + 1 + l = len(mc) + if i >= l: + i -= l + self.SelectContact(mc[i]) + + + for panel in self.expandopanels: + panel.Refresh() + + def SelectLast(self): + mc = self.metacontact + if mc: + i = mc.index(self.account) - 1 + self.SelectContact(mc[i]) + + + for panel in self.expandopanels: + panel.Refresh() + + def MetaContactObserver(self, obj, attr, old, new): + wx.CallAfter(self._MetaContactObserver, obj, attr, old, new) + + def _MetaContactObserver(self, obj, attr, old, new): + if wx.IsDestroyed(self): + warnings.warn('Infobox is dead but is still getting notified from MetaContactOberver') + return + + if not self.account in self.metacontact or not self.Shown: + try: + self.account = self.metacontact.first_online + except AttributeError: + + err = ''.join(['The obj: ' , str(obj), + '\nThe attr: ' , attr, + '\nold -> new: ' , str(old), '->', str(new), + '\n\nThe Metacontact: ', str(self.metacontact)]) + log.error(err) + + self.Repanelmater() + self.Reposition() + + + def ContactObserver(self, obj, attr, old, new): + wx.CallAfter(self._ContactObserver, obj, attr, old, new) + + def _ContactObserver(self, obj, attr, old, new): + #____log.info('InfoSyncing on contact, ContactObserver called with (%s, %s, %s, %s)', old, attr, obj, new) + self.InfoSync() + + for panel in self.expandopanels: + panel.Refresh() + + def Display(self, pl, pr, caller, force_change=False): + ''' + Show the infobox + + pl: point left + pr: point right + caller: contact, metacontact, or account + ''' + + if self.fromTray: + self.Hide() + self.fromTray = False + + #stop the hiding timer if going + self.hidingtimer, ht = None, getattr(self, 'hidingtimer', None) + if ht is not None: + ht.Stop() + + if self._doubleclickhide and caller is self.BuddyListItem: + # _doubleclickhide is true after a doubleclick--don't show for that buddy + return + + # if the position options are new or the caller isn't the same + if not (pl == self.pl and pr == self.pr and (caller is self.metacontact or caller is self.account)): + + self.pausedover, po_timer = None, getattr(self, 'pausedover', None) + if po_timer is not None: + po_timer.Stop() + + mp = GetMousePosition() + rect = self.Rect + onleft = rect.x < mp.x + + #If infobox is shown and not set to be forced + if self.Shown and not (force_change): + + #figure out the delta of mouse movement + lp = getattr(self, 'LastMousePoint', mp) + dp = mp - lp + + lmd = getattr(self, 'LastMouseDirection', [False, False]) + d = self.LastMouseDirection = ((dp.x > 0 if dp.x else lmd[0]), + (dp.y > 0 if dp.y else lmd[1])) + c1 = rect.Position + c2 = c1 + wx.Point(0, rect.height) + if onleft: + c1.x += rect.width + c2.x += rect.width + + #See DrawCrap + if pref('infobox.tracelines', False): self.DrawCrap(c1, c2, mp) + + #if there is a delta on the x axis + if d[0] ^ onleft: + #this block of code determines the angle of mouse movment to + # determine if the user is moving the mouse to the infobox or away + c = c2 if d[1] else c1 + am = (vector(*lp) - vector(*mp)).angle + ac = max(min((vector(*lp) - vector(*c)).angle, self.max_angle_of_entry), self.min_angle_of_entry) + if am < ac and mp.y > c1.y and mp.y < c2.y: + self.LastMousePoint = mp + ((1, 0) if onleft else (-1, 0)) + self.pausedover = CallLater(self.pause_time, lambda *__: (self.Display(pl, pr, caller, force_change=True) if self.FriendlyTouch(mp) else None)) + return + + #Saves mouse position for reference next time + self.LastMousePoint = mp + wx.Point(*((1, 0) if onleft else (-1, 0))) + + #sets the left and right positions + self.pl = pl + self.pr = pr + + self.InfoConnect(caller) + + + self.Reposition() + + elif not self.Shown: + self.StartShow() + + if self.panel.IsFrozen(): + self.panel.Thaw() + + def ShowFromTray(self, pt, account): + self.fromTray = True + if not self.Shown: + self.shouldShow = True + + + self.hidingtimer, ht = None, getattr(self, 'hidingtimer', None) + if ht is not None: + ht.Stop() + + self.pr = self.pl = pt + self.InfoConnect(account) + + def RepositionTray(self, size): + pos = self.pr - Point(*size) + Point(1, 1) + r = wx.RectPS(wx.Point(*pos), wx.Size(*size)) + screenrect = Monitor.GetFromRect(r).ClientArea + + # on windows, when the task bar is autohidden, it isn't automatically + # subtracted from ClientArea for us--so do it manually + if platform == 'win': + get_autohide = getattr(cgui, 'GetTaskBarAutoHide', None) + if get_autohide is not None and get_autohide(): + region = wx.Region(screenrect) + if region.SubtractRect(cgui.GetTaskbarRect()): + screenrect = region.GetBox() + + pos = screenrect.Clamp(r, wx.ALL).Position + + if self.animationtimer: + self.animationtimer.stop() + self.animationtimer = None + + self.SetRect(wx.RectPS(pos, size)) + self.traytimer.Start(TRAY_TIMER_MS) + self._moving_to = pos + + if not self.Shown and self.shouldShow: + self.shouldShow = False + self.ShowOnScreen() + + def InfoConnect(self, account): + #if the last account was an EmailAccount or SocialNetworks + # clear the errorpanel and disconnect the observers + + last_dosize = getattr(self, 'last_do_size_magic', 0) + + if isinstance(self.account, SocialNetwork): + self.errorpanel.Error() + self.account.unobserve_count(self.InfoSync) + self.account.remove_observer(self.StateChanged, 'state') + + elif isinstance(self.account, EmailAccount): + self.errorpanel.Error() + self.account.remove_observer(self.StateChanged, 'state') + elif isinstance(self.account, Contact): + if self.metacontact: + # remove any previous list observer + mco, self.metacontact_observer = getattr(self, 'metacontact_observer', None), None + if mco is not None: + mco.disconnect() + else: + log.warning("unobserving a MetaContact, but didn't have a metacontact_observer") + else: + self.account.remove_observer(self.ContactObserver, 'status', 'status_message', 'idle', 'icon') + + try: + self._unbound_cbs.clear() + except AttributeError: + pass + + + did_infosync = False + showing_feed = False + + #if the account type is a contact or metacontact + if isinstance(account, (Contact, MetaContact)): + self.eheader.Show(False) + self.elist.Show(False) + self.capbar.Show(True) + self.cpanel.Show(True) + + #set appropriate values for metacontact and selected contact + if isinstance(account, (MetaContact)): + metacontact = account + contact = account.first_online + else: + metacontact = [] + contact = account + + + #if infobox is not shown or is a different metacontact or contact than last time + if not self.IsShown() or metacontact != self.metacontact or (contact is not self.account and ((contact is not metacontact.first_online) if metacontact else True)): + #set the account to the contact and set the metacontact + self.metacontact = metacontact + self.account = contact + + if metacontact: + self.metacontact_observer = metacontact.add_list_observer(self.MetaContactObserver,self.ContactObserver, 'status', 'status_message', 'idle', 'icon') #TODO: resource for jabber + else: + contact.add_observer(self.ContactObserver, 'status', 'status_message', 'idle', 'icon') + + # if there is an account selcted + if self.account is not None: + # set up block/unblock command in the dropdown + caps = self.capbar + if isinstance(self.account, MetaContact): + buddies = self.account + name = buddies.alias + else: + buddies = [self.account] + name = self.account.name + + if any(b.blocked for b in buddies): + content = _('Unblock %s') + else: + content = _('Block %s') + + caps.iblock.content = [content % name] + + # if the account is a EmailAccount set the account and state observer + elif isinstance(account, EmailAccount): + showing_feed = True + self.metacontact = [] + self.account = account + + self.account.add_observer(self.StateChanged, 'state') + self._StateChanged() + + # if the account is a SocialNetwork set the account and state observer + elif isinstance(account, SocialNetwork): + showing_feed = True + self.metacontact = [] + self.account = account + self.account.observe_count(self.InfoSync) + self.account.add_observer(self.StateChanged, 'state') + did_infosync = self._StateChanged() + + if showing_feed: self.maybe_notify_stats() + + self.Repanelmater(not did_infosync) + + if last_dosize == getattr(self, 'last_do_size_magic', -1): + # if our calls to InfoSync did not result in a DoSizeMagic, then do it now + self.DoSizeMagic() + + def notify_stats(self): + self.maybe_notify_twitter() + hooks.notify('digsby.statistics.infobox.shown', self.account) + self.showed_with_contact = False + + def maybe_notify_twitter(self): + ''' + use a timer to only notify the twitter infobox stat AFTER it has been open + for more than the double click time, so that we don't count double clicks + opening the main feed window. + ''' + if getattr(self.account, 'protocol', None) != 'twitter': + return + + def later(): + if self.IsShown(): + hooks.notify('digsby.statistics.twitter.infobox.shown') + + try: + timer = self._dclick_timer + except AttributeError: + timer = self._dclick_timer = wx.PyTimer(later) + else: + timer.StartOneShot(GetDoubleClickTime()) + + def maybe_notify_stats(self): + # If the infobox was originally Shown with a Contact, but is now + # switching to a social network or email account., + if self.IsShown() and getattr(self, 'showed_with_contact', False): + self.notify_stats() + + def ShowOnScreen(self): + ''' + Shows the infobox. + + (Includes some special code to keep the infobox on top of other windows.) + ''' + + if self.ShowNoActivate(True): + if not isinstance(self.account, Contact): + self.notify_stats() + else: + self.showed_with_contact = True + + if config.platform == 'win': + show_on_top(self) # make sure the infobox is always on top + + def OnSize(self, event): + 'Runs Repostion and refreshes if the infobox gets resized.' + + event.Skip() + + if self.pl and self.pr and not self.fromTray: + self.Reposition() + + self.Refresh() + + def Reposition(self): + """ + Moves the infobox, and decides how to do it + """ + pl = self.pl + pr = self.pr + size = self.Size + + + #decide which is first pl or pr, and set p1 and p2 aproprietly + # if the info box fits at p1, more it there, otherwise p2 + + primaryPoint = pr if self.right_of_list else (pl[0] - self.Size.width, pl[1]) + primaryRect = RectPS(wx.Point(*primaryPoint), wx.Size(*size)) + secondaryPoint = (pl[0] - size.width, pl[1]) if self.right_of_list else pr + + screenrect = Monitor.GetFromWindow(self.Parent.Top).ClientArea #@UndefinedVariable + offscreen = (primaryRect.right > screenrect.right) if self.right_of_list else (primaryRect.left < screenrect.left) + + pos = secondaryPoint if offscreen else primaryPoint + + direction = wx.TOP|wx.BOTTOM + + r = wx.RectPS(wx.Point(*pos), wx.Size(*size)) + screenrect = Monitor.GetFromRect(r).ClientArea #@UndefinedVariable + pos = screenrect.Clamp(r, direction).Position + #decide to animate or move to new position + if self.animate and self.Shown and pos.x == self.Position.x: + if self.animation_method == 2: + if getattr(self, '_moving_to', None) != pos: + self.animationtimer = move_smoothly(self, pos, time = self.animation_time, + interval = self.animation_interval) + else: + self.animationtimer = self.SlideTo(pos) + else: + if self.animationtimer: + self.animationtimer.stop() + self.animationtimer = None + self.SetPosition(pos) + self._moving_to = pos + + if not self.Shown: + self.StartShow() + + def StartShow(self): + ''' + Figures out how to show the infobox and sets up the mouseout timer if needed + ''' + + if self.quickshow: + self.ShowOnScreen() + elif not self.showingtimer.IsRunning() or self.account is not self.showingtimer.contact: + self.showingtimer.Start(self.account) + + if not self.mouseouttimer.IsRunning(): + self.mouseouttimer.Start() + + + def on_synctimer(self): + t = self.synctimer + t.Stop() + if t.needs_sync: + t.needs_sync = False + self.InfoSync() + + @property + def should_use_maxheight(self): + # check that self.account.protocol is a string--this means that + # self.account is an account and not a buddy. + return self.account is not None and \ + isinstance(self.account.protocol, basestring) and \ + account_info(self.account, 'infobox', 'maxheight', default=False) + + + def get_webview_key(self, account): + if account_info(account, 'infobox', 'hosted', default=False): + key = 'hosted' + elif account_info(account, 'infobox', 'own_webview', default=False): + key = account + else: + key = None + return key + + def set_active_webview(self, account): + ''' + If account.infobox_own_webview is True, then a new WebView object is + constructed for that account and stored in self.account_webviews. After + that, this method will reuse that webview for the account. + + That webview becomes the active webview, is Shown, and set to + self.profilebox. + ''' + key = self.get_webview_key(account) + + account_key = get_account_key(account) + self.active_account_key = account_key + + try: + webview = self.account_webviews[key] + except KeyError: + webview = self.account_webviews[key] = self.construct_webview() + self.account_jsbridges[key] = bridge = JSPythonBridge(webview) + if key == 'hosted': + webview.hosted = True + from .infoboxapp import init_host + init_host(bridge, protocol=account.protocol) + + if key == 'hosted': + def infobox_json_handler(*a, **k): + # add special D.rpc method "infobox_hide" for hiding the infobox window. + # TODO: dynamic dispatch and delegate this + + if len(a) and hasattr(a[0], 'get'): + method = a[0].get('method', None) + else: + method = None + + if method == 'infobox_hide': + return self.DoubleclickHide() + elif method is not None: + if hooks.any('infobox.jsonrpc.' + method, account_key, a[0]): + return + + return account.json(*a, **k) + + #this only needs to be done once, not sure where to check right now. + self.account_jsbridges[key].register_specifier(account_key, infobox_json_handler) + + if self.profilebox is not webview: + self.infobox_app_hiding() + self.profilebox.Hide() + self.profilebox = webview + self.profilebox.Show() + + return key + + def infobox_app_hiding(self): + if getattr(self.profilebox, 'hosted', False): + self.profilebox.RunScript('if (callOnHide) callOnHide();', immediate=True) + else: + # when swapping between webviews, or when the infobox hides, if the webview is an "old style" + # account, just scroll to the top + self.profilebox.RunScript('window.scrollTo(0, 0);', immediate=True) + + def remove_webview(self, account): + 'Destroys an account specific webview.' + webview = self.account_webviews.pop(account, None) + self.account_jsbridges.pop(account, None) + if webview is not None: + webview.Destroy() + + def get_content(self, account): + return self.htmlcacher.GetGeneratedProfile(account, self.htmlfonts) + + def InfoSync(self, *a): + """Applies capabilities and sets the profilebox to a stripped version of + what FillInProfile returns, then DoSizeMagic.""" + + if not wx.IsMainThread(): + raise AssertionError('InfoSync must be called on the main thread') + + current_key = getattr(self, 'active_account_key', '') + account_key = get_account_key(self.account) + + if self.IsShown() and \ + account_key is not None and \ + account_key == current_key and \ + not hasattr(self.account, '_current_tab'): # skipping InfoSync is not compatible with accounts with tabs + + # do not immediately infosync--we don't want to replace the currently shown content + log.info('Skipping InfoSync because content is open') + self.synctimer.Stop() + return + + # Maintain a timer to throttle down this method if it is called a lot + if not hasattr(self, 'synctimer'): + self.synctimer = wx.PyTimer(self.on_synctimer) + self.synctimer.needs_sync = False + + # If the timer is already running, we have run too recently-- + # mark it to trigger an InfoSync later. + if self.synctimer.IsRunning(): + self.synctimer.needs_sync = True + return + + cpanel = self.cpanel + + with self.Frozen(): + if cpanel.IsShown(): + if self.capbar.Shown: + self.capbar.ApplyCaps(self.account) + + skipped_sethtml = self.set_content(self.account) + if skipped_sethtml or self.should_use_maxheight: + self.DoSizeMagic() + else: + self.DoSizeMagic() + + self.synctimer.StartOneShot(300) + + def set_content(self, account): + key = self.set_active_webview(account) + pb = self.profilebox + + if key == 'hosted': + from .infoboxapp import set_hosted_content + set_hosted_content(pb, account) + return False + + try: + pfile = self.get_content(self.account) + except Exception: + traceback.print_exc() + pfile = u'
{message}
' + pfile = pfile.format(self=self, message = _(u'Error generating content').encode('xml')) + + if getattr(pb, '_page', None) != pfile: + pb._page = pfile + pb.GetPage = lambda: pb._page + + # If we set content while the user is scrolling, the window locks up. Avoid this + # by manually releasing the capture the scrollbar gets + while pb.HasCapture(): pb.ReleaseMouse() + + pb.SetHTML(pfile) + skipped_sethtml = False + else: + skipped_sethtml = True + + return skipped_sethtml + + def Repanelmater(self, infosync = True): + """ + This generates and remove the contact panels for a metacontact as + needed. Then it calls InfoSync if infosync is True. + """ + panelsneeded = len(self.metacontact) + exp = self.expandopanels + sz = self.cpanel.Sizer + + if panelsneeded>0: + while len(exp) < panelsneeded: + panel = ExpandoPanel(self.cpanel, self) + exp.insert(0, panel) + sz.Insert(0, panel, 0, wx.EXPAND) + while len(exp) > panelsneeded: + panel = exp[0] + panel.Show(False) + exp.remove(panel) + + sz.Detach(panel) + panel.Destroy() + else: + for panel in exp[:]: + panel.Show(False) + exp.remove(panel) + sz.Detach(panel) + panel.Destroy() + + self.cpanel.Refresh() + + if infosync: + self.InfoSync() + + def SelectContact(self, contact): + """ + When a new contact is selected, account is set to that contact and + InfoSync is called to get the new profile. + """ + self.account = contact + self.InfoSync() + + def OnBeforeLoad(self, event): + type = event.GetNavigationType() + if type == wx.webview.WEBVIEW_NAV_LINK_CLICKED: + event.Cancel() + self.OnLinkClicked(event) + + def OnLinkClicked(self, event): + """ + This is for when a link is clicked in the profile box + """ + #get the href infor of the link object + url=event.GetURL() + + if url.startswith('file:///'): + url = url[8:] + +# should_not_hide = False + #if it's just a profile anchor, the toggle infobox.showprofile and InfoSync + if url =="profile": + setpref('infobox.showprofile', not pref('infobox.showprofile')) + self.InfoSync() + return + #if starts with ^_^ the rest of the string represents a function in + # account with / used to separate arguments + elif url.decode('url')[:3]=="^_^": + url = url.decode('url') + url = url[3:].split('/') + fname, args = url.pop(0), url +# try: +# should_not_hide = + getattr(self.account, fname)(*args) +# except Exception, e: +# print_exc() + # otherwise assume it's a link and open in default browser + else: + wx.CallAfter(wx.LaunchDefaultBrowser, url) + + #Hide the infobox, depending on pref +# if pref('infobox.hide_on_click', True): +# if not should_not_hide: self.Hide() + + + + + + def Befriend(self, friend): + """ + Register a GUI element as a friend of the infobox + The infobox does not close if the mouse is over a friend + """ + + self.friends.add(friend) + + def Defriend(self, friend): + """ + Unregister as a friend + """ + + self.friends.discard(friend) + + def FriendlyTouch(self, mp = None): + """ + Checks friends to see if the mouse is over them + True if the mouse is over one and the inobox should not close + False if the infobox should close + """ + windowatpointer = FindWindowAtPointer() + + for friend in self.friends: + if friend and windowatpointer is friend: + if isinstance(friend, BuddyList) and \ + not friend.ClientRect.Contains(friend.Parent.ScreenToClient(mp or GetMousePosition())) and \ + GetMouseState().LeftDown(): + return False + else: + return True + return False + +def construct_html_cacher(): + #Load up the yaml used to build infoboxes for SocialNetworks + htmlcacher = HtmlCacher() + htmlcacher.load_format('res/infobox.yaml') + + # Let accounts generate and cache infobox "ahead of time" via a hook + def on_cache_data(account, html): + htmlcacher._cache[account] = html + + import hooks + hooks.register('infobox.cache', on_cache_data) + + return htmlcacher + +class HtmlCacher(object): + def __init__(self): + self._cache = {} + def clear(self): + self._cache.clear() + def GetGeneratedProfile(self, acct, htmlfonts): + """ + Decides where to get the profile from, depending on account type and + protocol, and returns the profile + """ + ibp = None + try: + ibp = ICachingInfoboxHTMLProvider(acct) + except protocols.AdaptationFailure: + try: + ibp = IInfoboxHTMLProvider(acct) + except protocols.AdaptationFailure: + pass + if ibp is not None: + return ibp.get_html(htmlfonts, self.make_format) + + #Otherwise, generate + if isinstance(acct, EmailAccount): + print >> sys.stderr, 'WARNING: FillInProfile called with an email account' + else: + try: + import time + a = time.clock() + info = GetInfo(acct, pref('infobox.showprofile', False)) + b = time.clock() + acct.last_generate = b -a + return info + except Exception: + traceback.print_exc() + return '' + + def make_format(self, htmlfonts, cachekey, obj=None, data=None): + """ + If acct is digsby, facebook, or myspace call memo_format with the + acct, otherwise will fail assertion + """ + + #TODO: This strikes me as silly... + if isinstance(cachekey, tuple): + acct = cachekey[0] + else: + acct = cachekey + if acct.service not in ('digsby', 'facebook', 'twitter'): + warnings.warn('Don\'t know how to make an infobox for %r' % acct) + return u'
%s
' % _('No additional information') + return self.memo_format(htmlfonts, cachekey) + + def memo_format(self, htmlfonts, cachekey): + """Calls format with the format, acct, and htmlfonts.""" + +# if not hasattr(self, 'memo_formats'): +# from collections import defaultdict +# self.memo_formats = defaultdict(dict) +# if acct.service == 'facebook' and False: +# if acct not in self.memo_formats or self.memo_formats[acct]['time'] != acct.last_update: +# self.memo_formats[acct]['time'] = acct.last_update +# self.memo_formats[acct]['val'] = format(self.format[acct.service], acct) +# return self.memo_formats[acct]['val'] +# else: + if isinstance(cachekey, tuple): + acct = cachekey[0] + return format(self.format[acct.service][cachekey[1]], acct, htmlfonts) + else: + acct = cachekey + return format(self.format[acct.service], acct, htmlfonts) + + def load_format(self, fname): + """ + Load the YAML for the socialnetworks + """ + import syck + + self._format_fname = fname + try: + with open(fname) as f: + self.format = dict(syck.load(f)) + except: + self.format = {} + traceback.print_exc() + + def reload_format(self): + self.load_format(self._format_fname) + self.clear() + +from copy import deepcopy as copy +from util.primitives.strings import curly +from util.primitives.funcs import get + +#TODO: move this back to text util +def TagFont(string, fonttype, fonts): + font = fonts[fonttype] + color = fonts['%sfc'%fonttype] + tag = u''.join(['' % + {'facename': font.FaceName, + 'size': font.PointSize, + 'color': color}, + '' if font.Weight == wx.BOLD else '', + '' if font.Style == wx.ITALIC else '', + '' if font.Underlined else '', + string if isinstance(string, unicode) else string.decode('utf-8'), + '' if font.Underlined else '', + '' if font.Style == wx.ITALIC else '', + '' if font.Weight == wx.BOLD else '', + '']) + return tag + +def skinimagetag(key): + ''' + Returns an tag for a skin key. + ''' + + try: + img = skin.get(key) + url = img.path.url() + width, height = img.Width, img.Height + except Exception: + traceback.print_exc() + return '' + + return '' % (width, height, url) + +def format(fmt, obj, htmlfonts, data=None): + #TODO: Comment this, I'll let Mike do that - Aaron + + if data is None: + data = [] + return_str = True + else: + return_str = False + + mysentinel = Sentinel() + + sget = lambda o, k: get(o, k, mysentinel) + + mydata = [] + mydata_append = mydata.append + + mydata_append("""\ + + + + + ') + + order = get(fmt, 'order', fmt.keys()) + + from htmlgeneration import separatorsocial + + curlylocals = copy(fmt) + curlylocals.update( + fonts = htmlfonts, + TagFont = TagFont, + separator = lambda *a,**k: separatorsocial(), + skinimagetag = skinimagetag) + + for key in order: + fmtval = sget(fmt, key) + objval = sget(obj, key) + + if not objval and get(fmtval, 'hide_empty', False): + continue + + if mysentinel in (fmtval, objval): + continue + + elif isinstance(objval, list): + assert isinstance(fmtval, dict) + + + hdr = curly(get(fmtval, 'header', ''), source = curlylocals) + sep = curly(get(fmtval, 'separator', ''), source = curlylocals) + ftr = curly(get(fmtval, 'footer', ''), source = curlylocals) + + mydata.append(hdr) + + if not objval: + curlylocals['obj'] = obj + + val = get(fmtval, 'none', '') + + mydata_append(curly(val, source=curlylocals)) + else: + for thing in objval: + type_d = get(fmtval, type(thing).__name__, {}) + if not type_d: continue + + mydata_append(sep) + + mydata_append(make_icon_str(get(type_d, 'icon', ''))) + curlylocals['obj'] = thing + mesgstr = get(type_d, 'message', '') + + mydata_append(curly(mesgstr, source=curlylocals)) + + mydata_append(sep) + + mydata_append(ftr) + + elif isinstance(fmtval, dict): + format(fmtval, objval, htmlfonts, mydata) + else: + assert isinstance(fmtval, basestring), fmtval + if not objval: + continue + + curlylocals['obj'] = obj + mydata_append(curly(fmtval, source=curlylocals)) + + if (len(mydata) - 1) == 0: + curlylocals['obj'] = obj + mydata_append(curly(get(fmt, 'none', ''), source=curlylocals)) + + mydata_append("") + + mydata = make_header(fmt, curlylocals) + mydata + make_footer(fmt, curlylocals) + + data.extend(mydata) + + if return_str: + return ''.join(data) + else: + return data + + +def make_icon_str(s): + #TODO: Comment this, I'll let Mike do that - Aaron + if not s: + return '' + assert s.startswith('skin:') + + s = s[5:] + sent = object() + assert skin.get(s, lambda: sent) is not sent, s + return ('' + '' % s) + +def make_header(fmt, obj): + """ + Do curly for the header information + """ + return [curly(get(fmt, k, ''), source = obj) for k in ('header', 'separator')] + +def make_footer(fmt, obj): + """ + Do curly for the footer information + """ + return [curly(get(fmt, k, ''), source = obj) for k in ('separator', 'footer')] + +if 'wxMSW' in wx.PlatformInfo: + # import some windows functions and constants to keep the infobox floating + # above other windows (see the ShowOnScreen method below) + from gui.native.win.winconstants import HWND_TOPMOST, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_SHOWWINDOW + WINDOWPOS_FLAGS = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW + + from ctypes import windll + SetWindowPos = windll.user32.SetWindowPos + + def show_on_top(win): + SetWindowPos(win.Handle, HWND_TOPMOST, 0, 0, 0, 0, WINDOWPOS_FLAGS) + +import gui.input +gui.input.add_class_context(_('InfoBox'), 'InfoBox', cls = InfoBox) + +def account_info(obj, *path, **k): + default = k.pop('default', None) + protocol = getattr(obj, 'protocol', None) + ret = default + + if isinstance(protocol, basestring) and \ + protocol in protocolmeta.protocols: + + ret = protocolmeta.protocols[protocol] + while path: + path0 = path[0] + path=path[1:] + if path: + ret = ret.get(path0, {}) + else: + ret = ret.get(path0, default) + + return ret + +def get_account_key(account): + """Returns the string key used by infobox to hash accounts when + swapping them.""" + + try: + if isinstance(account.protocol, basestring): + return u'%s_%s' % (account.protocol, account.username) + else: + return None #explicit is good. + except AttributeError: + return None + diff --git a/digsby/src/gui/infobox/infoboxapp.py b/digsby/src/gui/infobox/infoboxapp.py new file mode 100644 index 0000000..ec34473 --- /dev/null +++ b/digsby/src/gui/infobox/infoboxapp.py @@ -0,0 +1,113 @@ +import util.primitives.functional as functional +import peak.util.addons +import hooks +import simplejson +from util import Storage + +COMBINE_INFOBOX_CSS = False + +class JScript(functional.AttrChain): + def __init__(self): + self.script = [] + + def __call__(self, method, *a, **k): + assert not k + callstr = ''.join((method, '(', ', '.join(['%s'] * len(a)), ');')) + self.script.append(callstr % tuple(simplejson.dumps(arg) for arg in a)) + + def js(self): + return '\n'.join(self.script) + +mock_app_context = lambda **k: Storage(resource= lambda *a: Storage(url=lambda *a:''), **k) + + +def init_host(bridge, baseUrl = 'file:///infoboxapp', protocol=None): + def get_plugin_head_content(): + return hooks.each('infobox.content.head', protocol) + plugins = Storage(head=get_plugin_head_content) + + from .providers import InfoboxProviderBase + class InfoboxAppProvider(InfoboxProviderBase): + def get_context(self): + context = InfoboxProviderBase.get_context(self) + context['plugins'] = plugins + return context + def get_app_context(self, context): + # TODO: fix head.tenjin to include all the permutations of + # resource loading in a more intelligent way + return mock_app_context(plugins=Storage( + head=get_plugin_head_content)) + + ip = InfoboxAppProvider() + app_content = ip.get_html(file='infoboxapp.tenjin') + bridge.SetPageSource(app_content, baseUrl) + +def set_hosted_content(webview, account, infobox_js=None): + script = JScript() + key = u'%s_%s' % (account.protocol, account.username) + + from .interfaces import IInfoboxHTMLProvider + ip = IInfoboxHTMLProvider(account) + ip.load_files(ip.get_context()) + dir = ip.app.get_res_dir('base') + + dirty = account._dirty + if dirty: + new_content = '' + account.loaded_count = getattr(account, 'loaded_count', 0) + 1 + if not getattr(account, '_did_load_head', False): + account._did_load_head = True + new_content += ip.get_html(file='head.tenjin', dir=dir) + + new_content += ip.get_html(file='content.tenjin', dir=dir) + + script.updateContent(key, new_content) + account._dirty = False + + jsscript = 'callOnHide();\n' + + script.swapToContent(key) + + jsscript += script.js() + + jsscript = SkinMemoizedCssScript(webview).get_css(jsscript) + + # switch our tag to app's infobox.css file + css_url = simplejson.dumps((dir / 'infobox.css').url()) + + jsscript = ('document.getElementById("appCSS").setAttribute("href", %s);\n' % css_url) + jsscript + + # replace $ if we are initializing + if dirty: + + jsscript += 'clearOnHide();\n' + + if infobox_js is None: + ijs_file = dir / 'infobox.js' + if ijs_file.isfile(): infobox_js = ijs_file.bytes() + + infobox_js = infobox_js or '' + + infobox_js += '\n'.join(hooks.each('infobox.content.infoboxjs', account)) + + jsscript += ('(function() {if (window.DCallbacks !== undefined) {var D = new DCallbacks(%s);}; %s})();\n' % (simplejson.dumps(key), infobox_js)) + + jsscript = 'callWithInfoboxOnLoad(function() {' + jsscript +'});' + jsscript += '\ncallSwapIn(currentContentName);' + + account.last_script = jsscript + webview.RunScript(jsscript) + +class SkinMemoizedCssScript(peak.util.addons.AddOn): + def get_css(self, script): + from common import pref + skin_tuple = getattr(self, 'skin_tuple', None) + new_skin_tuple = pref('appearance.skin'), pref('appearance.variant') + if skin_tuple != new_skin_tuple: + self.skin_tuple = new_skin_tuple + import gui + print 'new css' + gen_css = gui.skin.get_css() + script = ('''$('#gen_css').html(%s);''' % simplejson.dumps(gen_css)) + script + return script + diff --git a/digsby/src/gui/infobox/interfaces.py b/digsby/src/gui/infobox/interfaces.py new file mode 100644 index 0000000..13bd0d9 --- /dev/null +++ b/digsby/src/gui/infobox/interfaces.py @@ -0,0 +1,14 @@ +import protocols + +class IInfoboxHTMLProvider(protocols.Interface): + + def get_html(htmlfonts): + pass + +class ICacheableInfoboxHTMLProvider(IInfoboxHTMLProvider): + _dirty = property() + +class ICachingInfoboxHTMLProvider(IInfoboxHTMLProvider): + + def get_html(htmlfonts, make_format): + pass diff --git a/digsby/src/gui/infobox/providers.py b/digsby/src/gui/infobox/providers.py new file mode 100644 index 0000000..54b290c --- /dev/null +++ b/digsby/src/gui/infobox/providers.py @@ -0,0 +1,223 @@ +import protocols +import gui.skin +import path +import hooks +import time +import stdpaths +import gui.infobox.interfaces as gui_interfaces +from traceback import print_exc +from util.introspect import memoize +from gettext import ngettext +import logging +log = logging.getLogger('infobox.providers') + +class FileContext(object): + resdirs = {} + def __init__(self, basepath, name): + self._context_dirs = {} + self.name = name + self.base = path.path(basepath).abspath() + + def resource(self, resname, context = 'base'): + for path in hooks.each('infobox.content.resource', resname): + if path is not None: + return path + + dir = self.get_res_dir(context) + file = dir / resname + return file.abspath() + + def resourceurl(self, resname, context = 'base'): + return self.resource(resname, context).url() + + def get_res_dir(self, context): + '''context can be 'base', 'local', or 'user''' + return self.get_dir(context) + + def get_dir(self, context): + '''context can be 'base', 'local', or 'user''' + if context == 'base': + pth = self.base + + elif context == 'local': + pth = stdpaths.config + + elif context == 'user': + pth = stdpaths.userdata + + else: + pth = self._context_dirs[context] + + return pth / self.name + + def add_file_context(self, context, dir): + path_dir = path.path(dir) + self._context_dirs[context] = path_dir + +class AppFileContext(FileContext): + def get_res_dir(self, context): + return FileContext.get_res_dir(self, context) / self.resdirs.get(context, 'res') + +template_engine = 'tenjin' + + +if template_engine == 'mako': + class MakoTemplate(object): + def __init__(self, dir, fname): + self.dir = dir + self.fname = fname + + def generate(self, **context): + self.context = context + return self + + def get_template(self, fname): + import mako.lookup as L + import mako.template as T + dirs = [str(self.dir)] + lookup = L.TemplateLookup(directories = dirs) + return lookup.get_template(fname or self.fname) + + def render(self, **kwds): + return self.get_template(self.fname).render_unicode(**self.context) + +elif template_engine == 'tenjin': + import tenjin + class SafeIncludeEngine(tenjin.Engine): + def include(self, template_name, *a, **k): + import sys + if isinstance(template_name, bytes): + try: + template_name = template_name.decode('utf8') + except UnicodeDecodeError: + template_name = template_name.decode('filesys') + + template_name = template_name.encode('filesys') + + frame = sys._getframe(1) + locals = frame.f_locals + globals = frame.f_globals + assert '_context' in locals + _context = locals['_context'] + _buf = locals.get('_buf', None) + + try: + return tenjin.Engine.include(self, template_name, *a, **k) + except IOError, e: + log.error("error performing tenjin include: template_name = %r, a = %r, k = %r, e = %r", template_name, a, k, e) + return '' + + class TenjinTemplate(object): + e = None + def __init__(self, dir, fname): + self.dir = dir + self.fname = fname + + def generate(self, **context): + self.context = context + return self + + def render(self, **kwds): + import tenjin + import tenjin.helpers + if not self.e: + self.e = SafeIncludeEngine(preprocess = True, cache = None) + self.context.update(vars(tenjin.helpers)) + p = self.dir/self.fname + try: + return self.e.render(p.encode('filesys'), self.context) #str(path object) == encode filesys, not str+path() (ascii, sometimes) + except IOError: + print 'Error loading: %r' % unicode(p) + return '' + + def get_tenjin_template(dir, file): + return TenjinTemplate(dir, file) + +class InfoboxProviderBase(object): + javascript_libs = [ + 'jquery', + 'jquery.hotkeys', + 'jquery.fullsize', + 'jquery.lightbox', + 'jquery.jgrow-singleline', + 'json', + 'dateformat', + 'utils', + ] + + protocols.advise(instancesProvide=[gui_interfaces.ICacheableInfoboxHTMLProvider]) + def get_html(self, *a, **k): + try: + context = self.get_context() + context.update(k.get('context', {})) + self.load_files(context) + a = time.clock() + context.setdefault('ngettext', ngettext) + stream = self.get_template(file=k.get('file'), + loader=k.get('loader'), + dir=k.get('dir'), + ).generate(**context) + ret = stream.render(method='xhtml', strip_whitespace=context.get('strip_whitespace', True)) + if hasattr(self, 'acct'): + b = time.clock() + self.acct.last_gen_time = b - a +# setattr(self.acct, 'gens', getattr(self.acct, 'gens', []) + [self.acct.last_gen_time]) + return ret + except Exception: + print_exc() + raise + + def load_files(self, context): + self.platform = context.get('platform') + self.lib = context.get('lib') + self.app = context.get('app') + + if template_engine == 'mako': + def get_template(self, file=None, loader=None, dir=None): + return MakoTemplate(dir or self.get_dir(), file or 'infobox.mako') + + elif template_engine == 'tenjin': + def get_template(self, file = None, loader = None, dir = None): + dir = dir or self.get_dir() + file = file or 'infobox.tenjin' + return get_tenjin_template(dir, file) + + elif template_engine == 'genshi': + def get_template(self, file=None, loader=None, dir=None): + dir = dir or self.get_dir() + loader = loader or self.get_loader(dir) + return loader.load(file or 'infobox.xml') + + def get_dir(self, ): + if self.platform is None: + dir = path.path('.').abspath() + else: + dir = self.platform.get_res_dir('base') + return dir + + def get_loader(self, dir=None): + if dir is None: + dir = self.get_dir() + from genshi.template import TemplateLoader + loader = TemplateLoader(dir) + + return loader + + def get_context(self): + import util + platform = FileContext(gui.skin.resourcedir() / 'html', 'infobox') # digsby/infobox stuff + lib = FileContext(gui.skin.resourcedir(), 'html') + app = self.get_app_context(AppFileContext) # stuff for the component using the infobox + + ctx = dict(app = app, + lib = lib, + platform = platform, + + gui = gui, + util = util, + javascript_libs = self.javascript_libs) + + hooks.notify('infobox.content.context', ctx, getattr(self, 'acct', None)) + + return ctx + diff --git a/digsby/src/gui/input/__init__.py b/digsby/src/gui/input/__init__.py new file mode 100644 index 0000000..e64fcb3 --- /dev/null +++ b/digsby/src/gui/input/__init__.py @@ -0,0 +1,5 @@ +from .inputmanager import InputManager, keycodes, ClassContext, GlobalContext, input_manager + +add_class_context = input_manager.AddClassContext +add_global_context = input_manager.AddGlobalContext +add_action_callback = input_manager.AddActionCallback \ No newline at end of file diff --git a/digsby/src/gui/input/inputmanager.py b/digsby/src/gui/input/inputmanager.py new file mode 100644 index 0000000..cb797bc --- /dev/null +++ b/digsby/src/gui/input/inputmanager.py @@ -0,0 +1,448 @@ +''' +Links actions and keyboard shortcuts (or possibly other input methods). +''' +from __future__ import with_statement + +from traceback import print_exc +from collections import defaultdict + +from util.introspect import import_function, memoize +from util.primitives.funcs import Delegate +from util.merge import merge_keys +from prefs import flatten + +import wx +from wx import WXK_F1, WXK_F24, wxEVT_KEY_DOWN + +from logging import getLogger; log = getLogger('input'); DEBUG = log.debug + +class InputManager(object): + ''' + Binds keyboard shortcuts to actions. + + Watches the wxApp object for key events, and then uses the window returned + by wxKeyEvent::GetEventObject to determine contexts for the keyboard + action. + ''' + + def __init__(self): + self.reset() + + def reset(self): + self.handlers = defaultdict(Delegate) # {actionname: callbacks} + self.actions = defaultdict(dict) # {wx event type: {context: actionset}} + self.contexts = [] # [sorted list of contexts to try] + self.context_lookups = [] # [list of (contextstr, context)] + self.actionnames = {} # {actionname: actionset} + self.context_cache = {} # {actionname: context} + self.bound = False # if bound to an EvtHandler, the EvtHandler + + def AddGlobalContext(self, name, contextstr): + 'Add a context that works across the entire application.' + + self.context_lookups.append((contextstr.lower(), GlobalContext())) + self.context_cache.clear() + self.resolve_actions() + + def AddClassContext(self, name, contextstr, cls): + 'Add a context that works inside controls of a certain class.' + + self.context_lookups.append((contextstr.lower(), ClassContext(name, cls))) + self.context_cache.clear() + self.resolve_actions() + + def AddKeyboardShortcut(self, actionname, accels): + ''' + Adds a new keyboard shortcut. + + actionname a dotted.hierachical.string describing the action + accels a string describing the keys in the shortcut, like + "shift+alt+k." for more info see the keycodes function + below. + + this string maybe also me several keyboard shortcuts + separated by a comma + ''' + + keys = KeyActionSet((keycodes(accel), actionname) for accel in accels.split(',')) + self.actionnames[actionname.lower()] = keys + self.resolve_actions() + + def LoadKeys(self, filepath): + 'Loads a set of keyboard shortcuts from a YAML file.' + + addkey = self.AddKeyboardShortcut + import syck + + with open(filepath) as f: + for actionname, accels in flatten(merge_keys(syck.load(f))): + addkey(actionname, accels) + + def resolve_actions(self): + 'Links inputs (like keyboard commands) to their actions.' + + if not self.bound: return + + contexts = set() + find_context = self.find_context + + for actionname, actionset in self.actionnames.iteritems(): + context = find_context(actionname) + if context is not None: + try: + keyactions = self.actions[wxEVT_KEY_DOWN][context] + except KeyError: + keyactions = self.actions[wxEVT_KEY_DOWN][context] = KeyActionSet() + + keyactions.update(actionset) + contexts.add(context) + + self.contexts = sorted(contexts, reverse = True) + + def AddActionCallback(self, actionname, callback): + 'Associates a callable with an action.' + + actionname = actionname.lower() + + if isinstance(callback, basestring): + callback = LazyStringImport(callback) + + if not actionname in self.handlers: + self.handlers[actionname] = Delegate([callback]) + else: + self.handlers[actionname] += callback + + self.resolve_actions() + + def BindWxEvents(self, evt_handler): + if not self.bound is evt_handler: + evt_handler.Bind(wx.EVT_KEY_DOWN, self.handle_event) + self.bound = evt_handler + + self.resolve_actions() + + def find_context(self, actionname): + if actionname in self.context_cache: + return self.context_cache[actionname] + + actionname = actionname.lower() + startswith = actionname.startswith + + found = [(cstr, c) for cstr, c in self.context_lookups if startswith(cstr)] + + # Find the longest match. + found.sort(key = lambda s: len(s[0]), reverse = True) + + context = found[0][1] if found else None + return self.context_cache.setdefault(actionname, context) + + def handle_event(self, e): + contexts = self.contexts + + # walk control tree up to the top level window + for win in child_and_parents(e.EventObject): + # see if any of the contexts return True for the window + for context in contexts: + if context(win): + #DEBUG('context %r responded', context) + if self.invoke_actions(context, e, win) is False: + return + e.Skip() + + def invoke_actions(self, context, e, win): + try: + actionset = self.actions[e.EventType][context] + except KeyError: + #DEBUG('no actionset found') + return + + try: + actionname = actionset(e, win) + if actionname is False: + return + elif actionname is not None: + return self.event(actionname, win) + except Exception: + print_exc() + + + + def event(self, actionname, *a, **k): + try: + DEBUG('firing action %r', actionname) + action_delegate = self.handlers[actionname] + DEBUG(' callbacks: %r', action_delegate) + except KeyError: + pass + else: + try: + if action_delegate: + action_delegate(*a, **k) + return False + except Exception: + print_exc() + +class Context(object): + ''' + Keyboard shortcuts are each associated with different "context" objects + that describe when and where they work. + ''' + __slots__ = ['name'] + + priority = 0 + + def __init__(self, name): + self.name = name + + def __call__(self): + raise NotImplementedError('Context subclasses must implement __call__') + + def __cmp__(self, other): + return cmp(self.priority, other.priority) + + def __hash__(self): + return hash(id(self)) + +class WindowNameContext(Context): + ''' + Groups keyboard shortcuts that work when a top level window with a given + name is active. + + Note that a window's "name" is not the same as it's "title," which is + visible to the user. See wxTopLevelWindow's constructor. + ''' + __slots__ = ['window_name'] + + priority = 100 + + def __init__(self, name, window_name): + Context.__init__(self, name) + self.window_name = window_name + + def __call__(self, window): + return window.Name == self.window_name + + def __repr__(self): + return '' % self.window_name + +class ClassContext(Context): + ''' + Groups keyboard shortcuts that work with a certain class. + + (Uses an isinstance check) + ''' + __slots__ = ['cls'] + + priority = 90 + + def __init__(self, name, cls): + Context.__init__(self, name) + self.cls = cls + + def __call__(self, window): + return isinstance(window, self.cls) + + def __repr__(self): + return '' % self.cls + +class GlobalContext(Context): + ''' + Groups keyboard shortcuts that work any time. + ''' + + priority = 10 + + def __init__(self, name = 'Global Shortcuts'): + Context.__init__(self, name) + + def __call__(self, window, tlw = wx.TopLevelWindow): + return isinstance(window, tlw) + + def __repr__(self): + return '' % self.name + +class KeyActionSet(dict): + def __call__(self, e, win): + keycode = e.KeyCode + + if WXK_F1 <= keycode <= WXK_F24: + pass + elif keycode < 256: + # we used to check e.UnicodeKey, but that behaves differently + # on Mac and MSW, and moreover, keycodes for key shortcuts + # should always correspond to actual keys on the keyboard, + # meaning we don't need any Unicode conversions, which are used + # for keycodes greater than 128. + keycode = ord(chr(keycode).upper()) + + key = (e.Modifiers, keycode) + return self.get(key, None) + + +def child_and_parents(win): + 'Yields a window and all of its parents.' + + yield win + + win = getattr(win, 'Parent', None) + while win is not None: + yield win + win = win.Parent + + +def _accelerr(s): + raise ValueError('illegal accelerator: like "cmd+k" or "k" (you gave "%s")' % s) + +# easier to remember replacements for some keys with strangely named wx enums +replacements = { + 'backspace': 'back', + 'capslock' : 'capital', + + # This is evil, but EVT_KEY_DOWN fires different keycodes than EVT_CHAR. + # We might end up needed to react to both events, depending on which key + # we're looking for. + '=' : 43, +} + +@memoize +def keycodes(s, accel = True): + ''' + Turns an accelerator shortcut string into a wx.ACCEL and wx.WXK constants. + + cmd+k --> (wx.ACCEL_CMD, ord('k')) + ctrl+alt+return --> (wx.ACCEL_CTRL | wx.ACCEL_ALT, wx.WXK_RETURN) + + If accel is True, wxACCEL_XXX values will be returned; otherwise wxMOD_XXX + ''' + if isinstance(s, basestring): + s = s.strip() + seq = s.split('+') + for i, _elem in enumerate(seq[:]): + if _elem == '': + seq[i] = '+' + + if len(seq) == 1: + modifiers, key = ['normal'], s + else: + modifiers, key = seq[:-1], seq[-1] + else: + if not isinstance(s, int): + _accelerr(s) + + modifiers, key = ['normal'], s + + modifier = 0 + + PREFIX = 'ACCEL_' if accel else 'MOD_' + + for mod in modifiers: + modifier |= getattr(wx, PREFIX + mod.upper()) + + if isinstance(key, basestring): + if len(key) == 1 and key not in replacements: + key = ord(key.upper()) + else: + key = replacements.get(key.lower(), key) + if isinstance(key, basestring): + try: + key = getattr(wx, 'WXK_' + replacements.get(key.lower(), key).upper()) + except AttributeError: + _accelerr(s) + + assert isinstance(modifier, int) + assert isinstance(key, int) + return modifier, key + + +class LazyStringImport(str): + def __call__(self, *a, **k): + try: + return self.cb() + except TypeError, e: + # TODO: type annotation? interface? something? + if str(e).endswith('takes exactly 1 argument (0 given)'): + return self.cb(*a, **k) + else: + raise + + @property + def cb(self): + try: + return self._cb + except AttributeError: + self._cb = import_function(self) + return self._cb + +# global instance of InputManager +input_manager = InputManager() + + +if __debug__: + if 'wxMac' in wx.PlatformInfo: + AutoDC = wx.PaintDC + else: + AutoDC = wx.AutoBufferedPaintDC + + class KeyDebugger(wx.Frame): + + key_event_attrs = 'Modifiers', 'KeyCode', 'UnicodeKey' + + def __init__(self): + wx.Frame.__init__(self, None, title = _('Key Debugger')) + + self.Bind(wx.EVT_KEY_DOWN, self.on_key) + self.Bind(wx.EVT_KEY_UP, self.on_key) + self.Bind(wx.EVT_PAINT, self.on_paint) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.text = wx.TextCtrl(self, -1) + self.text.Bind(wx.EVT_TEXT, self.on_text) + self.label = wx.StaticText(self, -1) + self.label.SetBackgroundColour(wx.WHITE) + + h = wx.BoxSizer(wx.HORIZONTAL) + h.Add(self.text, 1, wx.EXPAND) + h.Add(self.label, 1, wx.EXPAND) + + s = self.Sizer = wx.BoxSizer(wx.VERTICAL) + s.AddStretchSpacer(1) + s.Add(h, 0, wx.EXPAND) + + def on_text(self, e): + try: + s = str(keycodes(self.text.Value)) + except Exception: + from traceback import print_exc + print_exc() + s = '' + + self.label.SetLabel(s) + + e.Skip() + + def on_paint(self, e): + dc = AutoDC(self) + dc.SetFont(self.Font) + dc.SetBrush(wx.WHITE_BRUSH) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangleRect(self.ClientRect) + + x = 0 + y = 0 + for a in self.key_event_attrs: + txt = '%s: %s' % (a, getattr(self, a, '')) + dc.DrawText(txt, x, y) + y += 15 + + if hasattr(self, 'KeyName'): + dc.DrawText(self.KeyName, x, y) + + def on_key(self, e): + for a in self.key_event_attrs: + setattr(self, a, getattr(e, a)) + + from gui.toolbox.keynames import keynames + self.KeyName = keynames.get(e.KeyCode, '') + + self.Refresh() + diff --git a/digsby/src/gui/invite_friends.py b/digsby/src/gui/invite_friends.py new file mode 100644 index 0000000..fdcd9ec --- /dev/null +++ b/digsby/src/gui/invite_friends.py @@ -0,0 +1,253 @@ +from .anylists import AnyRow +from .pref import prefcontrols +from .uberwidgets.PrefPanel import PrefPanel +from common import profile as p #@UnresolvedImport +from gui.anylists import AnyList +from util.observe import ObservableList +from util.callbacks import callsback +import wx + +class StaticEmailRow(AnyRow): + checkbox_border = 3 + row_height = 20 + image_offset = (6, 0) + + def __init__(self, *a, **k): + AnyRow.__init__(self, *a, **k) + + def PopulateControls(self, account): + self.checkbox.Value = True + self.text = self.data[1] + + @property + def image(self): + return self.data[0] + + def on_right_up(self, *a, **k): + pass + + +def f_f(a, b): + def factory(*args): + return a(b(*args)) + factory.__repr__ = \ + lambda: '''''' % locals() + return factory + +pen_f = f_f(wx.Pen, wx.Color) +brush_f = f_f(wx.Brush, wx.Color) + +def pen_f_f(*a): + return lambda: pen_f(*a) + +def brush_f_f(*a): + return lambda: f_f(wx.Brush, wx.Color)(*a) + +webmail_types = frozenset(('gmail', 'ymail', 'aolmail', 'hotmail')) + +TIME_UNTIL_SPAM = 60 * 60 * 24 * 7 + +def do_user_initiated_check(): + make_invite_diag(background = False, success=dialog_closed_callback) + +def do_background_check(): + from common import pref, setpref + from time import time + invite_times = pref('usertrack.invite.dialog.results', type=dict, default={}) + if invite_times: + return + if not any(e.protocol in webmail_types for e in p.emailaccounts[:]): + return + first_online = pref('usertrack.firstknownonline', type=int, default=-1) + if not first_online > 0: + setpref('usertrack.firstknownonline', int(time())) + return + elif int(time()) - first_online < 60 * 60 * 24 * 7: + return + else: + make_invite_diag(background = True, success=dialog_closed_callback) + +def dialog_closed_callback(result): + from util import Storage as S + from time import time + from common import pref, setpref + result = S(result) + invite_times = pref('usertrack.invite.dialog.results', type=dict, default={}) + + num_email_accts = len(p.emailaccounts) + num_webmail_accts = len([e.protocol in webmail_types for e in p.emailaccounts[:]]) + + track_data = dict( + accts=len(result.accts), + web_accts = num_webmail_accts, + email_accts = num_email_accts, + background=result.background, + send=result.send + ) + + num_not_triggered = sum(t.get('background', False) for t in invite_times.values()) + if not result.background or num_not_triggered < 5: + invite_times[int(time())] = track_data + setpref('usertrack.invite.dialog.results', invite_times) + + if result.send: + from pprint import pprint + print 'would send:' + pprint(result.accts) + +@callsback +def make_invite_diag(background = False, callback = None): + f = wx.Dialog(None) + f.Sizer = wx.BoxSizer(wx.VERTICAL) + + p1 = InvitePanel(f) + + def handle_invite_done(e): + #handle + if e.EventType in wx.EVT_BUTTON: + print 'send was hit' + name, accts = p1.info() + if not name: + return wx.MessageBox('Please enter your name so that ...\n will know from whom the invites were sent.', 'Please enter your name.') + callback.success(dict(name=name, accts=accts, background=background, send=True)) + elif e.EventType in wx.EVT_CLOSE: + name, accts = p1.info() + callback.success(dict(name=name, accts=accts, background=background, send=False)) + else: + assert False + f.Show(False) + f.Destroy() + + f.Bind(wx.EVT_CLOSE, handle_invite_done) + f.Bind(wx.EVT_BUTTON, handle_invite_done) + + f.Sizer.Add(p1, 1, wx.EXPAND) + p1.Layout() + f.Layout() + f.Fit() + f.SetMinSize(f.GetSize()) + f.Show() + return f + +class InvitePanel(wx.Panel): + + def __init__(self, *a, **k): + if k.get('name') is not None: + k['name'] = 'Invite Panel' + + wx.Panel.__init__(self, *a, **k) + + accounts = [e for e in p.emailaccounts[:] if e.protocol in ('gmail', 'ymail', 'aolmail', 'hotmail')] + + data = [] + for acct in accounts: + if acct.icon is not None: + ico = acct.icon.Resized(16) + else: + ico = None + data.append( + ( + ico, + acct.display_name, + acct.protocol, + acct.name, + acct._decryptedpw() + ) + ) + self.data = data + self.Construct() + self.Fonts() + self.Layout() + + def Construct(self): + parent = self + + self.line1 = wx.StaticText(parent, label="We hope you've enjoyed using Digsby.", style=wx.TE_CENTER) + self.line2 = wx.StaticText(parent, label="Please show your support and invite your friends.", style=wx.TE_CENTER) + + self.separator = wx.StaticLine(parent) + + self.name_label = wx.StaticText(parent, label='Full Name: ') + self.name_text = wx.TextCtrl(parent) + + self.acct_list = AnyList( + parent, + ObservableList(self.data), + row_control = StaticEmailRow, + multiselect = False, + edit_buttons = None, + draggable_items = False, + style = 0, + velocity = None + ) + self.acct_list.SetMinSize(wx.Size(-1, (16+10) * 4)) + + self.acct_panel = PrefPanel(parent, self.acct_list, 'Account') + self.acct_panel._bg_brush = lambda: wx.Brush (wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)) + self.acct_panel._fg_pen = lambda: wx.Pen (wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW)) + + self.send_button = wx.Button(parent, wx.ID_OK, label='Send Invite!') + self.send_button.MoveAfterInTabOrder(self.name_text) + + def info(self): + # iter(AnyList) is bad news + acct_list = self.acct_list + acct_list = [acct_list[i] for i in xrange(len(acct_list))] + return self.name_text.Value, [acct.data[2:] for acct in acct_list if acct.checkbox.Get3StateValue() == wx.CHK_CHECKED] + + def Fonts(self): + #=============================================================================================================== + # top 2 lines + #=============================================================================================================== + fnt1 = self.line1.Font + + fnt1.SetPointSize(fnt1.GetPointSize() + 4) + fnt1.SetWeight(wx.FONTWEIGHT_BOLD) + + self.line1.Font = fnt1 + self.line2.Font = fnt1 + + #=============================================================================================================== + #=============================================================================================================== + fnt2 = self.name_label.Font + fnt2.SetPointSize(fnt2.GetPointSize() + 2) + self.name_label.Font = fnt2 + + def Layout(self): + self.Sizer = s1 = prefcontrols.VSizer() + + #=============================================================================================================== + # top 2 lines + #=============================================================================================================== + s1.Add(self.line1, 0, wx.EXPAND | wx.ALL, 5) + s1.Add(self.line2, 0, wx.EXPAND | wx.ALL & ~wx.TOP, 5) + s1.Add(self.separator,0, wx.EXPAND | wx.LEFT | wx.RIGHT, 6) + + #=============================================================================================================== + # Full Name: --------------- + #=============================================================================================================== + s2 = prefcontrols.HSizer() + + s2.AddSpacer(6) + s2.Add(self.name_label, 0, wx.EXPAND) + s2.Add(self.name_text, 3, wx.EXPAND) + s2.AddSpacer(6) + + s1.Add(s2, 0, wx.EXPAND | wx.ALL, 9) + + #=============================================================================================================== + # panel full of checkboxes + #=============================================================================================================== + s1.Add(self.acct_panel, 1, wx.EXPAND | wx.ALL & ~wx.TOP, 15) + + #=============================================================================================================== + # Send Invites! (click) + #=============================================================================================================== + s3 = prefcontrols.HSizer() + + s3.AddStretchSpacer (20) + s3.Add(self.send_button, 60, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL & ~wx.TOP, 3) + s3.AddStretchSpacer (20) + + s1.Add(s3, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND | wx.BOTTOM, 6) + diff --git a/digsby/src/gui/lattice/__init__.py b/digsby/src/gui/lattice/__init__.py new file mode 100644 index 0000000..ac4ce8f --- /dev/null +++ b/digsby/src/gui/lattice/__init__.py @@ -0,0 +1,40 @@ +from peak.events import trellis +import protocols +import wx +from util.lego.blocks import IBindableValue + +class GUIChangeListener(trellis.Component): + def __init__(self, model): + self.model = model + + def bind(self, func): + assert func + self.func = func + self.model.Bind(self.evt_type, self.on_change) + + def unbind(self): + self.model.Unbind(self.evt_type, handler=self.on_change) + del self.func + + def on_change(self, e): + self.func() + +class ChoiceListener(GUIChangeListener): + protocols.advise(asAdapterForTypes=[wx.Choice], + instancesProvide=[IBindableValue]) + + evt_type = wx.EVT_CHOICE + + @property + def value(self): + return self.model.GetSelection() + +class CheckListener(GUIChangeListener): + protocols.advise(asAdapterForTypes=[wx.CheckBox], + instancesProvide=[IBindableValue]) + + evt_type = wx.EVT_CHECKBOX + + @property + def value(self): + return self.model.Value diff --git a/digsby/src/gui/linebox.py b/digsby/src/gui/linebox.py new file mode 100644 index 0000000..a3f5d3b --- /dev/null +++ b/digsby/src/gui/linebox.py @@ -0,0 +1,163 @@ +''' +Surrounds a panel with lines and a label. + +Use like a wxPanel, specify label in the constructor. + +>>> f = wx.Frame(None) +>>> box = LineBox(f, label = 'My Controls') +>>> button = wx.Button(box, -1, 'Push') +''' + +import wx, gui + + + +class LineBox(wx.Panel): + border = 30 + + def __init__(self, parent, id = -1, label = '', **kws): + wx.Panel.__init__(self, parent, id, **kws) + self.label_pos = wx.Point(25, 5) + self.padding = wx.Size(5, 5) + + self.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + if isinstance(label, basestring): + self.set_label(wx.StaticText(self, -1, label)) + + self.BackgroundColour = wx.WHITE + + self.BBind(PAINT = self.on_paint, + SIZE = self.on_size,) + + self.color1, self.color2 = wx.BLACK, wx.WHITE + + self.on_size() + + def on_size(self, e = None): + sz = self.Size + + y = self.label.Size.height + self.label_pos.y + self.padding.height + self.lines = [ ((self.label_pos.x - self.padding.width, 0, 2, sz.height), wx.SOUTH), + ((0, y, sz.width, 2), wx.EAST) ] + if e: e.Skip() + + def set_label(self, label): + if hasattr(self, 'label') and self.label: + self.label.Destroy() + + self.label = label + sz = label.MinSize + + self.Sizer.AddSpacer(self.label_pos.y) + s = wx.BoxSizer(wx.HORIZONTAL) + s.AddSpacer(self.label_pos.x) + s.Add(label) + self.Sizer.Add(s) + + def on_paint(self, e): + dc = wx.PaintDC(self) + for rect, direction in self.lines: + dc.GradientFillLinear(rect, self.color1, self.color2, direction) + e.Skip(True) + + def Add(self, control): + self.Sizer.Add(control, 1, wx.EXPAND | wx.WEST , self.border) + + +class LineBoxOld(wx.Panel): + def __init__(self, parent, id = -1, label = '', **kws): + wx.Panel.__init__(self, parent, id, **kws) + self.label = label + + self.BBind(PAINT = self.on_paint, + SIZE = self.on_size, + ERASE_BACKGROUND = lambda e: None) + + self.lines = [] + self.color1 = wx.BLACK + self.color2 = wx.WHITE + self.center = wx.Point(20, 20) + self.margin = 10 + self.BackgroundColour = wx.WHITE + + def on_size(self, e = None): + extra = 10 + linewidth = 2 + + # Resize the lines + self.lines = [ (wx.Rect(self.center.x - extra, self.center.y, + self.Size[0], linewidth), wx.EAST), + (wx.Rect(self.center.x, self.center.y - extra, + linewidth, self.Size[1]), wx.SOUTH)] + + children = self.GetChildren() + assert len(children) == 1 + panel = children[0] + r = self.Rect + xoff, yoff = self.center.x + self.margin, self.center.y + self.margin + panel.Rect = wx.Rect(r.x + xoff, r.y + yoff, + r.Width - xoff, r.Height - yoff) + + + size = wx.Size(*panel.Sizer.MinSize) + wx.Size(*self.center) + (10,10) + self.SetMinSize( size ) + self.Refresh() + + def on_paint(self, e): + dc = wx.AutoBufferedPaintDC(self) + + # if 'wxMac' not in wx.PlatformInfo: + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangle(*self.Rect) + + font = wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) + font.Weight = wx.FONTWEIGHT_BOLD + dc.Font = font + dc.DrawText(self.label, self.center.x + 6, self.center.y - 15) + + for line in self.lines: + rect, direction = line + dc.GradientFillLinear(rect, self.color1, self.color2, direction) + +def buildbox(parent, box_dict): + label = box_dict.keys()[0] + + linebox = wx.StaticBox(parent, -1, label) + sz = wx.StaticBoxSizer(linebox, wx.VERTICAL) + + for pref in box_dict[label]: + if isinstance(pref, (str, unicode)): + # Checkbox + check = wx.CheckBox(parent, label = pref) + sz.Add(check, flag = wx.ALL, border = 3) + elif isinstance(pref, dict): + k = pref.keys()[0] + if k == 'custom': + v = pref.values()[0] + control = globals()['prefs_' + v.replace(' ', '_')](parent) + sz.Add(control, flag = wx.ALL, border = 3) + return sz + + + +if __name__ == '__main__': + app = wx.PySimpleApp() + f = wx.Frame(None, -1, 'Linebox Test') + + sizer = wx.BoxSizer(wx.VERTICAL) + box = LineBox(f, label = 'Test Preference Group') + + grid = wx.GridSizer(2,2) + grid.Add(wx.Button(box, -1, 'one')) + grid.Add(wx.Button(box, -1, 'two')) + grid.Add(wx.Button(box, -1, 'three')) + grid.Add(wx.Button(box, -1, 'four')) + box.Add(grid) + + + sizer.Add(box, 1, wx.EXPAND) + + f.SetSizer(sizer) + f.Show(True) + app.MainLoop() \ No newline at end of file diff --git a/digsby/src/gui/model/__init__.py b/digsby/src/gui/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/model/menus.py b/digsby/src/gui/model/menus.py new file mode 100644 index 0000000..1a0ff5e --- /dev/null +++ b/digsby/src/gui/model/menus.py @@ -0,0 +1,178 @@ +""" +The purpose of this class is to have a Menu class focused only on the menu itself, +leaving the event handling and menu display to the controller and view, respectively. + +On Mac, since the main menubar is always displayed, having the main menu tied tightly to +a particular view is problematic, as once that view loses focus, some of the menu items +may not do the correct thing, for example. +""" + +import wx +import sys +import gui.uberwidgets.umenu + +item_types = ["normal", "checkbox", "radio", "separator"] + +def wxTypeForItemType(type): + if not type or type == "normal": + return wx.ITEM_NORMAL + if type == "checkbox": + return wx.ITEM_CHECK + if type == "radio": + return wx.ITEM_RADIO + if type == "separator": + return wx.ITEM_SEPARATOR + + assert(type in item_types) + +def set_menubar(parent, menus, umenu = False): + if not umenu: + menubar = wx.MenuBar() + else: + menubar = gui.uberwidgets.umenu.UMenuBar(parent) + + for menu in menus: + label = menu.label + + if umenu: + menu = menu.asUMenu(parent) + else: + menu = menu.asWxMenu() + + menubar.Append(menu, label) + + if not umenu: + parent.SetMenuBar(menubar) + + return menubar + +class Menu(object): + def __init__(self, label="", items=None): + self.label = label + if items: + self.items = items + else: + self.items = [] + + def __repr__(self): + return '' % (self.label, '\n'.join(' ' + repr(item) for item in self)) + + def __iter__(self): + return iter(self.items) + + def __len__(self): + return len(self.items) + + def addItem(self, label, description="", id=None, type="normal", + defaultAccel=None, customAccel=None, subMenu=None, bitmap=None): + + item = MenuItem(label, description, id, type, defaultAccel, customAccel, subMenu, bitmap) + self.items.append(item) + return item + + def addSep(self): + item = MenuItem(label="", type="separator") + self.items.append(item) + return item + + def asWxMenu(self): + menu = wx.Menu() + for item in self.items: + menu.AppendItem(item.asWxMenuItem(menu)) + + return menu + + def asUMenu(self, parent, windowless = False): + menu = gui.uberwidgets.umenu.UMenu(parent, windowless = windowless) + for item in self.items: + item.asUMenuItem(menu) + + assert len(menu) == len(self.items) + return menu + +class MenuItem(object): + def __init__(self, label, description = "", id = None, type = "normal", + defaultAccel = None, customAccel = None, subMenu = None, bitmap = None): + self.label = label + self.description = description + self.defaultAccel = defaultAccel + self.customAccel = customAccel + self.type = type + self.subMenu = subMenu + self.bitmap = bitmap + + + if type == "separator": + assert id is None, 'separators cannot have ids, you gave %r' % id + id = wx.ID_SEPARATOR + elif id is None: + id = wx.NewId() + + self.id = id + + def __repr__(self): + if self.type == 'separator': + return '' + else: + return "" % \ + (self.id, self.label, self.description, self.defaultAccel, self.customAccel, self.type) + + def asWxMenuItem(self, menu=None): + label = self.label + if self.customAccel: + label += "\t" + self.customAccel + elif self.defaultAccel: + label += "\t" + self.defaultAccel + + subMenu = None + if self.subMenu is not None: + subMenu = self.subMenu.asWxMenu() + + item = wx.MenuItem(menu, self.id, label, self.description, + wxTypeForItemType(self.type), subMenu) + + if self.bitmap: + item.SetBitmap(self.bitmap) + + return item + + def asUMenuItem(self, umenu): + if self.subMenu is not None: + return umenu.AddSubMenu(self.subMenu.asUMenu(umenu.Window), self.label, self.bitmap) + else: + if self.type == "checkbox": + item = umenu.AddCheckItem(self.label) + item.Id = self.id + return item + + elif self.type == "radio": + item = umenu.AddRadioItem(self.label) + item.Id = self.id + return item + else: + return umenu.Append(self.id, self.label, self.bitmap) + +if __name__ == "__main__": + def _(text): + return text + + from tests.testapp import testapp + app = testapp('../../..') + frame = wx.Frame(None, -1) + frame.Sizer = sizer = wx.BoxSizer(wx.VERTICAL) + + + use_umenu = False + menus = digsbyWxMenuBar(use_umenu, frame) + menubar = set_menubar(frame, menus, use_umenu) + + app.SetTopWindow(frame) + + if use_umenu: + sizer.Add(menubar.SizableWindow, 0, wx.EXPAND) + + frame.Show() + + + + app.MainLoop() diff --git a/digsby/src/gui/native/__init__.py b/digsby/src/gui/native/__init__.py new file mode 100644 index 0000000..3533f41 --- /dev/null +++ b/digsby/src/gui/native/__init__.py @@ -0,0 +1,43 @@ +import wx, sys +from logging import getLogger; log = getLogger('Not Implemented') + +def notImplemented(): + + # prep the error message + caller_info = sys._getframe(1).f_code + notImplMessage = "Unimplemented function. Function %s at line %d of file %s" \ + % (caller_info.co_name, caller_info.co_firstlineno, caller_info.co_filename) + + if getattr(sys, "notImplementedIsError", False): + raise NotImplementedError(notImplMessage) + else: + log.error(notImplMessage) + +def getPlatformDir(): + if "wxMSW" in wx.PlatformInfo: + return "win" + elif "wxMac" in wx.PlatformInfo: + return "mac" + elif "wxGTK" in wx.PlatformInfo: + return "gtk" + else: + notImplemented() + +def extendStdPaths(): + exec("import %s.%spaths" % (getPlatformDir(), getPlatformDir())) + +if 'wxMSW' in wx.PlatformInfo: + def lower_memory_footprint(): #lazy call for lazyModule('process') + import win.process + win.process.page_out_ram() + from .memfootprint import memory_event +else: + def lower_memory_footprint(): + pass + + def memory_event(): + pass + +# uhm this looks bad. do not want +# we need a way to make stuff in the 'win' module be imported here if platform is windows, etc. +exec("from %s import *" % getPlatformDir()) \ No newline at end of file diff --git a/digsby/src/gui/native/docking.py b/digsby/src/gui/native/docking.py new file mode 100644 index 0000000..8042253 --- /dev/null +++ b/digsby/src/gui/native/docking.py @@ -0,0 +1,3 @@ +import gui.native + +exec("from gui.native.%s.%sdocking import *" % (gui.native.getPlatformDir(), gui.native.getPlatformDir())) diff --git a/digsby/src/gui/native/effects.py b/digsby/src/gui/native/effects.py new file mode 100644 index 0000000..f0f64fe --- /dev/null +++ b/digsby/src/gui/native/effects.py @@ -0,0 +1,6 @@ +import new +import gui.native + +exec("from gui.native.%s.%seffects import *" % (gui.native.getPlatformDir(), gui.native.getPlatformDir())) + +wx.WindowClass.Cut = new.instancemethod(ApplySmokeAndMirrors, None, wx.WindowClass) diff --git a/digsby/src/gui/native/extensions.py b/digsby/src/gui/native/extensions.py new file mode 100644 index 0000000..ec1ac54 --- /dev/null +++ b/digsby/src/gui/native/extensions.py @@ -0,0 +1,3 @@ +import gui.native + +exec("from gui.native.%s.%sextensions import *" % (gui.native.getPlatformDir(), gui.native.getPlatformDir())) diff --git a/digsby/src/gui/native/flash.py b/digsby/src/gui/native/flash.py new file mode 100644 index 0000000..a918d49 --- /dev/null +++ b/digsby/src/gui/native/flash.py @@ -0,0 +1,27 @@ +from config import platformName +import wx +from traceback import print_exc +from logging import getLogger +logger = getLogger('flash') + +class StubFlashWindow(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + flash_url = 'http://www.adobe.com/go/EN_US-H-GET-FLASH' + + self.link = wx.HyperlinkCtrl(self, -1, 'Adobe Flash is required', url = flash_url) + + def LoadMovie(self, layer, url): + log.warning('Flash is not initialized, cannot LoadMovie(%r)', url) + +# +# TODO: Disabled until the cause of b14552 can be determined... +# +#if platformName == 'win': +# try: +# from wx.lib.flashwin import FlashWindow +# except Exception: +# print_exc() +# FlashWindow = StubFlashWindow +#else: +# raise NotImplementedError() \ No newline at end of file diff --git a/digsby/src/gui/native/fonts.py b/digsby/src/gui/native/fonts.py new file mode 100644 index 0000000..0f6a072 --- /dev/null +++ b/digsby/src/gui/native/fonts.py @@ -0,0 +1,7 @@ +from gui.native import getPlatformDir +platdir = getPlatformDir() + +exec("from gui.native.%s.%sfonts import *" % (platdir, platdir)) + +#__import__('gui.native.%s.%sfonts' % (platdir, platdir), +# globals(), locals(), fromlist = '*') diff --git a/digsby/src/gui/native/gtk/__init__.py b/digsby/src/gui/native/gtk/__init__.py new file mode 100644 index 0000000..77c5f01 --- /dev/null +++ b/digsby/src/gui/native/gtk/__init__.py @@ -0,0 +1 @@ +import process \ No newline at end of file diff --git a/digsby/src/gui/native/gtk/gtkdocking.py b/digsby/src/gui/native/gtk/gtkdocking.py new file mode 100644 index 0000000..038a491 --- /dev/null +++ b/digsby/src/gui/native/gtk/gtkdocking.py @@ -0,0 +1,61 @@ +from util.primitives.funcs import Delegate +from gui.native import notImplemented + +class Docker(object): + def __init__(self, win, autohide = False, enabled = True): + self.docked = False + self.LinkedWindows = [] + self.ShouldShowInTaskbar = lambda: True + self.OnHide = Delegate() + notImplemented() + + def SetAutoHide(self, *a, **k): + notImplemented() + pass + + def SetEnabled(self, *a, **k): + notImplemented() + pass + + def SetRectSimple(self, rect): + notImplemented() + pass + + def GoAway(self): + notImplemented() + pass + + def ComeBack(self): + notImplemented() + pass + + def AnimateWindowTo(self, r): + notImplemented() + pass + + def UpdateTaskbar(self): + notImplemented() + pass + + def Dock(self, side = None, setStyle = True): + notImplemented() + pass + + def Undock(self, setStyle = True): + notImplemented() + pass + + def GetDockRect(self, rect = None): + notImplemented() + pass + + @property + def Enabled(self): + notImplemented() + return False + + def SetVelocity(self, vel): + notImplemented() + return False + + velocity = property(lambda self: notImplemented(), SetVelocity) diff --git a/digsby/src/gui/native/gtk/gtkeffects.py b/digsby/src/gui/native/gtk/gtkeffects.py new file mode 100644 index 0000000..d6cf14a --- /dev/null +++ b/digsby/src/gui/native/gtk/gtkeffects.py @@ -0,0 +1,25 @@ +import gui.native + +def _setalpha_wxGTK(window, alpha): + window._transparency = alpha + window.SetTransparent(alpha) +def _getalpha_wxGTK(window): + return getattr(window, '_transparency', 255) +def _donealpha_wxGTK(window): + return _setalpha_wxGTK(window, 255) + +setalpha = _setalpha_wxGTK +getalpha = _getalpha_wxGTK +drawnative = lambda *a,**kw: None +controls = {} +states = {} + +def DrawSubMenuArrow(dc, arect): + gui.native.notImplemented() + +import wx +import gui.native +import ctypes + +def ApplySmokeAndMirrors(win, shape = None): + pass#gui.native.notImplemented() diff --git a/digsby/src/gui/native/gtk/gtkextensions.py b/digsby/src/gui/native/gtk/gtkextensions.py new file mode 100644 index 0000000..3fb17c1 --- /dev/null +++ b/digsby/src/gui/native/gtk/gtkextensions.py @@ -0,0 +1,12 @@ +''' +This file will contain all platform-specific extensions or method replacements +to functions in the Python stdlib or the WX API. +''' + +import wx +import gui.native + +# wxTLW.Show does not activate the window like it does +# on some other platforms--no special code is needed. +wx.TopLevelWindow.ShowNoActivate = wx.TopLevelWindow.Show + diff --git a/digsby/src/gui/native/gtk/gtkhelpers.py b/digsby/src/gui/native/gtk/gtkhelpers.py new file mode 100644 index 0000000..ffb121f --- /dev/null +++ b/digsby/src/gui/native/gtk/gtkhelpers.py @@ -0,0 +1,28 @@ +import wx +import gui.native +import time +import os + +def FullscreenApp(): + gui.native.notImplemented() + return False + +def FullscreenAppLog(): + gui.native.notImplemented() + +def SetOnTaskbar(f, val): + # TODO: Do we need to implement this for the Window menu? + gui.native.notImplemented() + +def GetOnTaskbar(f): + return True + +def GetUserIdleTime(): + return 0 + +def createEmail(mailto): + return gui.native.notImplemented() + +wx.Window.ShowNoFocus = wx.Window.Show +wx.Window.ReallyRaise = wx.Window.Raise +wx.TopLevelWindow.Visible = wx.TopLevelWindow.Shown diff --git a/digsby/src/gui/native/gtk/gtkpaths.py b/digsby/src/gui/native/gtk/gtkpaths.py new file mode 100644 index 0000000..5c902ee --- /dev/null +++ b/digsby/src/gui/native/gtk/gtkpaths.py @@ -0,0 +1,40 @@ +''' + +Patches wx.StandardPaths to include extra Windows specific methods. + +''' + +import wx +import os +import gui.native + +_paths = [ + ('GetUserStartupDir', gui.native.notImplemented()), + ('GetStartupDir', gui.native.notImplemented()), + # TODO: Get properly localized version! + ('GetUserDesktopDir', os.path.join(os.environ["HOME"], "Desktop")), + ('GetDesktopDir', os.path.join(os.environ["HOME"], "Desktop")) +] + +for method_name, path in _paths: + setattr(wx.StandardPaths, method_name, lambda p: path) +''' + +Patches wx.StandardPaths to include extra Windows specific methods. + +''' + +import wx +import os +import gui.native + +_paths = [ + ('GetUserStartupDir', gui.native.notImplemented()), + ('GetStartupDir', gui.native.notImplemented()), + # TODO: Get properly localized version! + ('GetUserDesktopDir', os.path.join(os.environ["HOME"], "Desktop")), + ('GetDesktopDir', os.path.join(os.environ["HOME"], "Desktop")) +] + +for method_name, path in _paths: + setattr(wx.StandardPaths, method_name, lambda p: path) diff --git a/digsby/src/gui/native/gtk/gtksysinfo.py b/digsby/src/gui/native/gtk/gtksysinfo.py new file mode 100644 index 0000000..dc98727 --- /dev/null +++ b/digsby/src/gui/native/gtk/gtksysinfo.py @@ -0,0 +1,9 @@ +import gui.native.shared.posix_sysinfo as sysinfo + +class SystemInformation: + + def _ram(self): + return sysinfo.system_ram_info() + + def _disk_c(self): + return sysinfo.volume_info(root="/") diff --git a/digsby/src/gui/native/gtk/process.py b/digsby/src/gui/native/gtk/process.py new file mode 100644 index 0000000..2edb9c3 --- /dev/null +++ b/digsby/src/gui/native/gtk/process.py @@ -0,0 +1,2 @@ +def count_gdi_objects(): + return -1 \ No newline at end of file diff --git a/digsby/src/gui/native/gtk/toplevel.py b/digsby/src/gui/native/gtk/toplevel.py new file mode 100644 index 0000000..98b5241 --- /dev/null +++ b/digsby/src/gui/native/gtk/toplevel.py @@ -0,0 +1,10 @@ +import gui.native + +def Flash(win, titlebar = True, taskbar = True, timeout = 0, count = 3, until = 'foreground'): + gui.native.notImplemented() + +def FlashOnce(win): + gui.native.notImplemented() + +def StopFlashing(win): + gui.native.notImplemented() \ No newline at end of file diff --git a/digsby/src/gui/native/helpers.py b/digsby/src/gui/native/helpers.py new file mode 100644 index 0000000..785cbf1 --- /dev/null +++ b/digsby/src/gui/native/helpers.py @@ -0,0 +1,3 @@ +import gui.native + +exec("from gui.native.%s.%shelpers import *" % (gui.native.getPlatformDir(), gui.native.getPlatformDir())) diff --git a/digsby/src/gui/native/mac/__init__.py b/digsby/src/gui/native/mac/__init__.py new file mode 100644 index 0000000..805c562 --- /dev/null +++ b/digsby/src/gui/native/mac/__init__.py @@ -0,0 +1 @@ +import machelpers diff --git a/digsby/src/gui/native/mac/macdocking.py b/digsby/src/gui/native/mac/macdocking.py new file mode 100644 index 0000000..b202915 --- /dev/null +++ b/digsby/src/gui/native/mac/macdocking.py @@ -0,0 +1,246 @@ +import sys + +from util.primitives.funcs import Delegate +from gui.native import notImplemented +from gui.toolbox import Monitor +from logging import getLogger; log = getLogger('dock') + +import wx +from wx.lib.pubsub import Publisher + +AUTOHIDE_TIMER_MS = 300 +ANIMATE_DELTA = 6 + +DOCK_NONE = 0 +DOCK_RIGHT = 1 +DOCK_LEFT = 2 + +DOCK_MARGIN = 30 +DRAG_THRESHOLD = 50 + +sign = lambda v: (1 if v > 0 else (-1 if v < 0 else 0)) + +class Docker(object): + def __init__(self, win, autohide = False, enabled = True): + self.Animate = True + self.autohidden = False + self.AutoHide = False + self.docked = False + self.docking = False + self.LinkedWindows = [] + self.manualMove = False + self.motiontrigger = False + self.pixelsdragged = 0 + self.ShouldShowInTaskbar = lambda: True + self.timer = wx.PyTimer(self.OnTimer) + self.spookyGhostWindow = None + + self.win = win + self.win.Bind(wx.EVT_ACTIVATE, self.OnActivateWin) + self.win.Bind(wx.EVT_MOVE, self.OnMoving) + + self.lastrect = None + + self.SetAutoHide(autohide) + + self.OnDock = Delegate() + self.OnHide = Delegate() + + publisher = Publisher() + publisher.subscribe(self.OnActivateApp, 'app.activestate.changed') + + def OnActivateApp(self, message): + if not self.AutoHide: + return + + is_active = message.data + + if is_active: + self.win.Show() + + def OnActivateWin(self, event): + if not event.Active and self.AutoHide and self.docked: + self.GoAway() + + def OnMouseEnterWindow(self, event): + self.spookyGhostWindow.Destroy() + self.bringBack() + + def bringBack(self): + if self.autohidden: + self.ComeBack() + + def SetAutoHide(self, val): + self.AutoHide = val + + def OnMoving(self, e): + ''' + Bound to wx.EVT_MOVE. Detects when it is time to dock and undock. + ''' + + pos, margin = self.win.Rect, DOCK_MARGIN + rect = monitor_rect() + + if not self.manualMove: + e.Skip(True) + else: + return + + if not self.docked and not self.docking: + if pos.Right > rect.Right - margin: # and pos.x < rect.Right: + if not self.AutoHide or not onscreen(rect.Right+1, rect.Y): + self.Dock(DOCK_RIGHT) + elif pos.x < rect.X + margin and pos.Right > rect.X: + if not self.AutoHide or not onscreen(rect.X - 1, rect.Y): + self.Dock(DOCK_LEFT) + elif self.docked: + if self.side == DOCK_LEFT and pos.Left > rect.Left + margin: + self.Undock() + elif self.side == DOCK_RIGHT and pos.Right < rect.Right - margin: + self.Undock() + + def SetEnabled(self, *a, **k): + notImplemented() + pass + + def SetRectSimple(self, rect): + notImplemented() + pass + + def OnTimer(self, e=None): + 'Called by self.timer for reliable mouse off detection.' + if not (self.Enabled and self.AutoHide and self.docked): + return + + w, p = self.win, wx.GetMousePosition() + if w and not self.motiontrigger and not w.IsActive(): + if not any(r.Contains(p) for r in (w.ScreenRect for w in [w] + self.LinkedWindows)): + self.GoAway() + + def GoAway(self): + 'Causes the window to autohide.' + if not self.AutoHide: + return + + log.debug('GoAway') + w = self.win + r = monitor_rect(w) + margin = 3 + screen_y = self.win.Position.y + + # move it a little ways off screen so that shadows, etc. don't show at the very edge. + if self.side == DOCK_LEFT: + x, y = (-w.Rect.width - 10, screen_y) + elif self.side == DOCK_RIGHT: + x, y = (r.Right + 10, screen_y) + else: + assert False + + self.motiontrigger = True + self.autohidden = True + + self.timer.Stop() + self.OnHide() + self.AnimateWindowTo(wx.Rect(x, y, *w.Size)) + ghost_x = x - 30 + if self.side == DOCK_LEFT: + ghost_x = 1 + self.spookyGhostWindow = wx.Frame(None, -1, "Spooky Ghost Window", (ghost_x, y), (40, w.Size.height), style=0) + panel = wx.Panel(self.spookyGhostWindow, -1) + panel.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow) + self.spookyGhostWindow.SetTransparent(0) + self.spookyGhostWindow.Show() + + def ComeBack(self): + rect = self.win.Rect + mon_rect = monitor_rect() + + if self.side == DOCK_RIGHT: + rect.x = mon_rect.Right - rect.width + else: + rect.x += mon_rect.Left + rect.width + + self.AnimateWindowTo(rect) + self.autohidden = False + + def AnimateWindowTo(self, r): + 'Slides the window to position x, y.' + + x, y = r[:2] + + w = self.win + rect = monitor_rect() + if w.Position != (x, y): + self.manualMove = True + if self.Animate: + end = w.Position.x + x + steps = (x - w.Position.x) / ANIMATE_DELTA + s = 1 + if x < w.Position.x: + end = w.Position.x - x + steps = (w.Position.x - x) / ANIMATE_DELTA + s = -1 + + delta = s * ANIMATE_DELTA + for step in xrange(abs(steps)): + w.Move((w.Position.x + delta, y)) + + else: + w.SetRect(r) + self.manualMove = False + + def UpdateTaskbar(self): + pass + + def GetDockRect(self, side): + screen_rect = monitor_rect(self.win) + rect = self.win.Rect + if side == DOCK_RIGHT: + rect.x = screen_rect.width - rect.width + else: + rect.x = 0 + + print >> sys.stderr, "old = %r, new = %r" % (self.win.Rect, rect) + return rect + + def Dock(self, side = None, setStyle = True): + print >> sys.stderr, "Docking window..." + log.debug("In Dock...") + self.docking = True + self.side = side + + rect = self.GetDockRect(side) + self.AnimateWindowTo(rect) + if self.AutoHide: + self.timer.Start(AUTOHIDE_TIMER_MS) + + self.OnDock(True) + self.docking = False + self.docked = True + return True + + def Undock(self, setFrameStyle = True): + self.side = DOCK_NONE + self.docked = self.docking = False + self.OnDock(False) + + @property + def Enabled(self): + #notImplemented() + return True + + def SetVelocity(self, vel): + notImplemented() + return False + + velocity = property(lambda self: notImplemented(), SetVelocity) + +def monitor_rect(win = None): + 'returns a wxRect for the client area of the monitor the mouse pointer is over' + + return (Monitor.GetFromPoint(wx.GetMousePosition()) if win is None else Monitor.GetFromWindow(win)).ClientArea + +def onscreen(x, y): + 'Returns True if the specified (x, y) point is on any screen.' + + return Monitor.GetFromPoint((x, y), find_near = False) is not None diff --git a/digsby/src/gui/native/mac/maceffects.py b/digsby/src/gui/native/mac/maceffects.py new file mode 100644 index 0000000..71aa268 --- /dev/null +++ b/digsby/src/gui/native/mac/maceffects.py @@ -0,0 +1,23 @@ +import wx +import gui.native +import ctypes + +def ApplySmokeAndMirrors(win, shape = None): + pass + +def _setalpha_wxMac(window, alpha): + tlw = wx.GetTopLevelParent(window) + tlw.SetTransparent(alpha) + +def _getalpha_wxMac(window): + tlw = wx.GetTopLevelParent(window) + return tlw.GetTransparent() + +setalpha = _setalpha_wxMac +getalpha = _getalpha_wxMac + +def DrawSubMenuArrow(dc, arect): + gui.native.notImplemented() + +controls = {} +states = {} diff --git a/digsby/src/gui/native/mac/macextensions.py b/digsby/src/gui/native/mac/macextensions.py new file mode 100644 index 0000000..f83901e --- /dev/null +++ b/digsby/src/gui/native/mac/macextensions.py @@ -0,0 +1,20 @@ +''' +This file will contain all platform-specific extensions or method replacements +to functions in the Python stdlib or the WX API. +''' + +import wx +import gui.native + +# on mac, the %#I sequence to the system's strftime doesn't work--but +# a lowercase 'l' does almost the same thing + +import time +_strftime = time.strftime +def patched_strftime(fmt, t = None): + return _strftime(fmt.replace('%#I', '%l'), t) + +time.strftime = patched_strftime +del patched_strftime + +wx._Window.ReallyRaise = lambda win: win.Raise() diff --git a/digsby/src/gui/native/mac/macfonts.py b/digsby/src/gui/native/mac/macfonts.py new file mode 100644 index 0000000..4366c8b --- /dev/null +++ b/digsby/src/gui/native/mac/macfonts.py @@ -0,0 +1,30 @@ +''' +some Mac code from the internet + +// Path to resource files in Mac bundle +wxString m_resourceDir; +// container of locally activated font +ATSFontContainerRef m_fontContainer; +FSSpec spec; + +wxMacFilename2FSSpec(m_resourceDir + _T("Bank Gothic Light.ttf"), +&spec); +OSStatus status = ATSFontActivateFromFileSpecification(&spec, +kATSFontContextLocal, kATSFontFormatUnspecified, NULL, +kATSOptionFlagsDefault, &m_fontContainer); +wxASSERT_MSG(status == noErr, _T("font activation failed")); + +and then anywhere in the app this works fine: +wxFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL | +wxFONTFLAG_ANTIALIASED, wxFONTWEIGHT_NORMAL, false, _T("Bank Gothic +Light")); +''' +import gui.native + +def loadfont(fontpath, private = True, enumerable = False): + gui.native.notImplemented() + return False + +def unloadfont(fontpath): + gui.native.notImplemented() + return False \ No newline at end of file diff --git a/digsby/src/gui/native/mac/machelpers.py b/digsby/src/gui/native/mac/machelpers.py new file mode 100644 index 0000000..d462b32 --- /dev/null +++ b/digsby/src/gui/native/mac/machelpers.py @@ -0,0 +1,47 @@ +import wx +import gui.native +import time +import os + +import Carbon.CarbonEvt + +def FullscreenApp(): + gui.native.notImplemented() + return False + +def FullscreenAppLog(): + gui.native.notImplemented() + +def SetOnTaskbar(f, val): + # TODO: Do we need to implement this for the Window menu? + gui.native.notImplemented() + +def GetOnTaskbar(f): + return True + +def GetUserIdleTime(): + # Carbon functions are in seconds, not milliseconds, so multiply after getting the proper time + lastEventTime = Carbon.CarbonEvt.GetCurrentEventTime() - Carbon.CarbonEvt.GetLastUserEventTime() + return lastEventTime * 1000 + +def createEmail(mailto): + return os.system("open " + mailto) + +wx.Window.ShowNoFocus = wx.Window.Show +wx.Window.ReallyRaise = wx.Window.Raise +# wx.TopLevelWindow.Visible = wx.TopLevelWindow.Shown + +from AppKit import * +from Foundation import * + +class nspool(object): + def __init__(self): + self.pool = None + + def __enter__(self): + self.pool = NSAutoreleasePool.alloc().init() + + def __exit__(self, exc_type, exc_val, exc_tb): + # the pool calls release in its destructor, if we call it ourselves + # we'll get asserts about doing a double-free. + self.pool = None diff --git a/digsby/src/gui/native/mac/maciconeditor.py b/digsby/src/gui/native/mac/maciconeditor.py new file mode 100644 index 0000000..a068299 --- /dev/null +++ b/digsby/src/gui/native/mac/maciconeditor.py @@ -0,0 +1,54 @@ +import os + +import wx +import objc +from AppKit import * +from Foundation import * +from Quartz import * + +from .machelpers import * + +NSApplicationLoad() + +class IconEditor(object): + def __init__(self, parent, icon=''): + self.image_changed = False + with nspool(): + self.picTaker = IKPictureTaker.alloc().init() + try: + if not icon == '': + data = NSData.dataWithBytes_length_(icon, len(icon)) + image = NSImage.alloc().initWithData_(data) + + if image: + self.picTaker.setInputImage_(image) + except: + raise + + @classmethod + def RaiseExisting(cls): + return False + + def Prompt(self, callback): + result = self.picTaker.runModal() + if result == NSOKButton: + self.image_changed = True + self.output = self.picTaker.outputImage() + + callback() + + @property + def Bytes(self): + rep = None + # we need a NSBitmapImageRep to convert to png. This code assumes + # outputImage always returns an NSImage that contains an NSBitmapImageRep + # representation. + for arep in self.output.representations(): + if isinstance(arep, NSBitmapImageRep): + rep = arep + assert rep + return str(rep.representationUsingType_properties_(NSPNGFileType, None).bytes()) + + @property + def ImageChanged(self): + return self.image_changed diff --git a/digsby/src/gui/native/mac/macmenuicon.py b/digsby/src/gui/native/mac/macmenuicon.py new file mode 100644 index 0000000..44f2c54 --- /dev/null +++ b/digsby/src/gui/native/mac/macmenuicon.py @@ -0,0 +1,108 @@ +import os + +import wx +import objc +from AppKit import * +from Foundation import * +import tempfile +import util.observe as observe + +from gui.uberwidgets.umenu import UMenu +from .machelpers import * + +NSApplicationLoad() + +class Notifier(observe.Observable): + def __init__(self, parent): + self.parent = parent + + def on_account_updated(self, *a): + with nspool(): + wx.CallAfter(self.parent.on_account_updated, a) + +class MenuBarIconDelegate(NSObject): + def initWithAccount(self, acct, infobox): + with nspool(): + self.acct = acct + self.infobox = infobox + from gui.uberwidgets.umenu import UMenu + self._menu = UMenu(wx.FindWindowByName('Buddy List'), onshow = self.update_menu) + statusbar = NSStatusBar.systemStatusBar() + self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength) + + self.statusitem.setTarget_(self) + self.statusitem.setAction_("itemClicked:") + + # set up our observer + self.observer = Notifier(self) + acct.add_observer(self.observer.on_account_updated, 'count', 'state', obj = self.observer) + self.on_account_updated() + + def Destroy(self): + with nspool(): + self.acct.remove_observer(self.observer.on_account_updated) + NSStatusBar.systemStatusBar().removeStatusItem_(self.statusitem) + del self.statusitem + return True + + def SetIcon(self, icon, tooltip = None): + with nspool(): + handle, filename = tempfile.mkstemp() + pngfilename = filename + ".png" + os.close(handle) + os.rename(filename, pngfilename) + icon.WXB.SaveFile(pngfilename, wx.BITMAP_TYPE_PNG) + + nsicon = NSImage.alloc().initWithContentsOfFile_(pngfilename) + + self.statusitem.setImage_(nsicon) + if tooltip: + self.statusitem.setToolTip_(tooltip) + + @property + def _IconSize(self): + return 16 + + def umenuToNSMenu(self, umenu): + with nspool(): + menu = NSMenu.alloc().initWithTitle_("submenu") + for item in umenu: + fullname = item.GetItemLabel() + accel = "" + if fullname.find("\\t") != -1: + fullname, accel = fullname.split("\\t") + if item.IsSeparator(): + menu.addItem_(NSMenuItem.separatorItem()) + else: + nsitem = menu.addItemWithTitle_action_keyEquivalent_(item.GetItemLabelText(), "menuItemClicked:", accel) + if item.GetSubMenu(): + submenu = self.umenuToNSMenu(item.GetSubMenu()) + menu.setSubMenu_forItem_(submenu, nsitem) + + nsitem.setTarget_(self) + return menu + + def itemClicked_(self, sender): + with nspool(): + self.update_menu() + if hasattr(self, "subMenu"): + del self.subMenu + + self.subMenu = self.umenuToNSMenu(self._menu) + if self.subMenu: + self.statusitem.popUpStatusItemMenu_(self.subMenu) + + def menuItemClicked_(self, sender): + with nspool(): + self.fireHandlerForMenu(sender, self._menu) + + def fireHandlerForMenu(self, sender, menu): + for item in menu: + if item.GetItemLabelText() == sender.title(): + try: + callback = self._menu.cbs[item.GetId()] + callback() + except KeyError: + pass + elif item.GetSubMenu(): + self.fireHandlerForMenu(sender, item.GetSubMenu()) diff --git a/digsby/src/gui/native/mac/macpaths.py b/digsby/src/gui/native/mac/macpaths.py new file mode 100644 index 0000000..5c902ee --- /dev/null +++ b/digsby/src/gui/native/mac/macpaths.py @@ -0,0 +1,40 @@ +''' + +Patches wx.StandardPaths to include extra Windows specific methods. + +''' + +import wx +import os +import gui.native + +_paths = [ + ('GetUserStartupDir', gui.native.notImplemented()), + ('GetStartupDir', gui.native.notImplemented()), + # TODO: Get properly localized version! + ('GetUserDesktopDir', os.path.join(os.environ["HOME"], "Desktop")), + ('GetDesktopDir', os.path.join(os.environ["HOME"], "Desktop")) +] + +for method_name, path in _paths: + setattr(wx.StandardPaths, method_name, lambda p: path) +''' + +Patches wx.StandardPaths to include extra Windows specific methods. + +''' + +import wx +import os +import gui.native + +_paths = [ + ('GetUserStartupDir', gui.native.notImplemented()), + ('GetStartupDir', gui.native.notImplemented()), + # TODO: Get properly localized version! + ('GetUserDesktopDir', os.path.join(os.environ["HOME"], "Desktop")), + ('GetDesktopDir', os.path.join(os.environ["HOME"], "Desktop")) +] + +for method_name, path in _paths: + setattr(wx.StandardPaths, method_name, lambda p: path) diff --git a/digsby/src/gui/native/mac/macsysinfo.py b/digsby/src/gui/native/mac/macsysinfo.py new file mode 100644 index 0000000..06b1911 --- /dev/null +++ b/digsby/src/gui/native/mac/macsysinfo.py @@ -0,0 +1,12 @@ +import gui.native.shared.posix_sysinfo as sysinfo + +class SystemInformation: + + def _ram(self): + return sysinfo.system_ram_info() + + def _disk_c(self): + return sysinfo.volume_info(root="/") + + def _digsby_ram(self): + return sysinfo.digsby_ram_info() diff --git a/digsby/src/gui/native/mac/toplevel.py b/digsby/src/gui/native/mac/toplevel.py new file mode 100644 index 0000000..8b312d5 --- /dev/null +++ b/digsby/src/gui/native/mac/toplevel.py @@ -0,0 +1,10 @@ +import gui.native + +def Flash(win, titlebar = True, taskbar = True, timeout = 0, count = 3, until = 'foreground'): + gui.native.notImplemented() + +def FlashOnce(win): + gui.native.notImplemented() + +def StopFlashing(win): + gui.native.notImplemented() diff --git a/digsby/src/gui/native/memfootprint.py b/digsby/src/gui/native/memfootprint.py new file mode 100644 index 0000000..cd02382 --- /dev/null +++ b/digsby/src/gui/native/memfootprint.py @@ -0,0 +1,51 @@ +''' +Timers that trigger memory to page out while Digsby is idle. +''' + +import wx + +__all__ = ['memory_event'] + +enabled = True + +def memory_event(): + ''' + Indicates to the system that it should soon try to lower its memory + footprint. Threadsafe. + ''' + + wx.CallAfter(start_timer) + +def set_enabled(val): + global enabled + enabled = bool(val) + +WAIT_MS = 3000 # how long to wait after a trigger before paging out RAM +LONGTERM_MS = 1000 * 60 * 15 # 15 minutes + +from gui.native import lower_memory_footprint + + +def lower(): + if enabled: + lower_memory_footprint() + +def start_timer(): + t = memtimer() + if not t.IsRunning(): + t.StartOneShot(WAIT_MS) + +def memtimer(): + app = wx.GetApp() + + try: + return app._memory_timer + except AttributeError: + # this timer is used to delay paging out RAM after a user action + timer = app._memory_timer = wx.PyTimer(lower) + + # a longer term timer that runs repeating, trigger a page out independently + longterm_timer = app._longterm_memory_timer = wx.PyTimer(lower) + longterm_timer.StartRepeating(LONGTERM_MS) + + return timer diff --git a/digsby/src/gui/native/shared/__init__.py b/digsby/src/gui/native/shared/__init__.py new file mode 100644 index 0000000..f414567 --- /dev/null +++ b/digsby/src/gui/native/shared/__init__.py @@ -0,0 +1,5 @@ +''' +The purpose of this directory is to contain files used by multiple +platforms, but not by all. (In some cases GTK and Mac impls. can share code, +for example.) +''' diff --git a/digsby/src/gui/native/shared/posix_sysinfo.py b/digsby/src/gui/native/shared/posix_sysinfo.py new file mode 100644 index 0000000..b4a0fd1 --- /dev/null +++ b/digsby/src/gui/native/shared/posix_sysinfo.py @@ -0,0 +1,44 @@ +import commands +import os +import statvfs +import sys + +def free_bytes(path): + stats = os.statvfs(path) + return stats[statvfs.F_FRSIZE] * stats[statvfs.F_BFREE] + +def total_bytes(path): + stats = os.statvfs(path) + return stats[statvfs.F_FRSIZE] * stats[statvfs.F_BLOCKS] + +def avail_bytes(path): + stats = os.statvfs(path) + return stats[statvfs.F_FRSIZE] * stats[statvfs.F_BAVAIL] + +def system_ram_info(): + d = {} + sample_arg = "-n 1" + if sys.platform.startswith("darwin"): + sample_arg = "-l 1" + output = commands.getoutput("top " + sample_arg).split("\n") + for line in output: + if line.startswith("PhysMem:"): + fields = line[8:].split(",") + for field in fields: + field = field.strip() + amount, type = field.split(" ") + type = type.replace(".", "") + d[type] = amount + break + return d + +def digsby_ram_info(): + output = commands.getoutput("ps %s -o psz" % os.getpid()) + return int(output[1]) + +def volume_info(root="/"): + d = dict(drive=root, + freeuser = free_bytes(root), + total = total_bytes(root), + free = avail_bytes(root)) + return d \ No newline at end of file diff --git a/digsby/src/gui/native/sysinfo.py b/digsby/src/gui/native/sysinfo.py new file mode 100644 index 0000000..c2e39bc --- /dev/null +++ b/digsby/src/gui/native/sysinfo.py @@ -0,0 +1,3 @@ +import gui.native + +exec("from gui.native.%s.%ssysinfo import *" % (gui.native.getPlatformDir(), gui.native.getPlatformDir())) diff --git a/digsby/src/gui/native/toplevel.py b/digsby/src/gui/native/toplevel.py new file mode 100644 index 0000000..8f2e386 --- /dev/null +++ b/digsby/src/gui/native/toplevel.py @@ -0,0 +1,3 @@ +import gui.native + +exec("from gui.native.%s.toplevel import *" % gui.native.getPlatformDir()) diff --git a/digsby/src/gui/native/win/__init__.py b/digsby/src/gui/native/win/__init__.py new file mode 100644 index 0000000..c33e915 --- /dev/null +++ b/digsby/src/gui/native/win/__init__.py @@ -0,0 +1,15 @@ +import sys +if not sys.platform.startswith('win'): + # Importing from gui.native.win on other platforms should never happen. + assert 0 + +from peak.util.imports import lazyModule +dockconstants = lazyModule('gui.native.win.dockconstants') +winhelpers = lazyModule('gui.native.win.winhelpers') +process = lazyModule('gui.native.win.process') +toplevel = lazyModule('gui.native.win.toplevel') +winconstants = lazyModule('gui.native.win.winconstants') +winpaths = lazyModule('gui.native.win.winpaths') +winutil = lazyModule('gui.native.win.winutil') +appbar = lazyModule('gui.native.win.appbar') + diff --git a/digsby/src/gui/native/win/appbar.py b/digsby/src/gui/native/win/appbar.py new file mode 100644 index 0000000..744c060 --- /dev/null +++ b/digsby/src/gui/native/win/appbar.py @@ -0,0 +1,47 @@ +from gui.native.win.winutil import WinStruct, win_id +from gui.native.win.winconstants import ABM_NEW +from ctypes.wintypes import UINT, HWND, DWORD, c_uint, RECT, HANDLE, LPARAM +from ctypes import windll +import winhelpers + +SHAppBarMessage = windll.shell32.SHAppBarMessage + +def notify_fullscreen(win, on_fullscreen, off_fullscreen): + ''' + Registers for fullscreen notification. "on_fullscreen" is called when an + application goes into fullscreen mode, and "off_fullscreen" is called when + it comes back from fullscreen mode. + ''' + + win.BindWin32(EVT_WIN32_FULLSCREEN, _fullscreen_callback) + abd = APPBARDATA(hWnd = win.Handle, uCallbackMessage = EVT_WIN32_FULLSCREEN) + SHAppBarMessage(ABM_NEW, abd.ptr) + +def _fullscreen_callback(hWnd, msg, wParam, lParam): + print 'in _fullscreen_callback' + print locals() + +EVT_WIN32_FULLSCREEN = win_id() + +class APPBARDATA(WinStruct): + 'Used with the Win32 shell.dll SHAppBarMessage function.' + + _fields_ = [('cbSize', DWORD), + ('hWnd', HANDLE), + ('uCallbackMessage', c_uint), + ('uEdge', c_uint), + ('rc', RECT), + ('lParam', LPARAM)] + +def main(): + import wx + a = wx.PySimpleApp() + f = wx.Frame(None) + + notify_fullscreen(f, lambda: None, lambda: None) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/gui/native/win/console.py b/digsby/src/gui/native/win/console.py new file mode 100644 index 0000000..ae3a4c7 --- /dev/null +++ b/digsby/src/gui/native/win/console.py @@ -0,0 +1,84 @@ +import contextlib +import ctypes + +from ctypes import byref +from ctypes.wintypes import SHORT, WORD + +# See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winprog/winprog/windows_api_reference.asp +# for information on Windows APIs. +STD_INPUT_HANDLE = -10 +STD_OUTPUT_HANDLE= -11 +STD_ERROR_HANDLE = -12 + +FOREGROUND_BLUE = 0x01 # text color contains blue. +FOREGROUND_GREEN= 0x02 # text color contains green. +FOREGROUND_RED = 0x04 # text color contains red. +FOREGROUND_INTENSITY = 0x08 # text color is intensified. +BACKGROUND_BLUE = 0x10 # background color contains blue. +BACKGROUND_GREEN= 0x20 # background color contains green. +BACKGROUND_RED = 0x40 # background color contains red. +BACKGROUND_INTENSITY = 0x80 # background color is intensified. + + +kernel32 = ctypes.windll.kernel32 + +SetConsoleTextAttribute = kernel32.SetConsoleTextAttribute +GetConsoleScreenBufferInfo = kernel32.GetConsoleScreenBufferInfo +std_out_handle = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) + +class COORD(ctypes.Structure): + _fields_ = [('X', SHORT), + ('Y', SHORT)] + +class SMALL_RECT(ctypes.Structure): + _fields_ = [('Left', SHORT), + ('Top', SHORT), + ('Right', SHORT), + ('Bottom', SHORT)] + +class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): + _fields_ = [('dwSize', COORD), + ('dwCursorPosition', COORD), + ('wAttributes', WORD), + ('srWindow', SMALL_RECT), + ('dwMaximumWindowSize', COORD)] + +screen_info = CONSOLE_SCREEN_BUFFER_INFO() + +def set_color(color, handle=std_out_handle): + """(color) -> BOOL + + Example: set_color(FOREGROUND_GREEN | FOREGROUND_INTENSITY) + """ + return SetConsoleTextAttribute(handle, color) + +colormap = dict(grey = 0x08, + white = 0x07, + red = FOREGROUND_RED, + green = FOREGROUND_GREEN, + blue = FOREGROUND_BLUE, + yellow = FOREGROUND_RED | FOREGROUND_GREEN, + bold = FOREGROUND_INTENSITY) + +@contextlib.contextmanager +def color(color): + color_int = 0 + for c in color.split(): + color_int |= colormap.get(c) + + GetConsoleScreenBufferInfo(std_out_handle, byref(screen_info)) + set_color(color_int) + try: + yield + finally: + set_color(screen_info.wAttributes) + +def main(): + with color('bold red'): + print 'red' + with color('grey'): print 'grey' + with color('white'): print 'white' + print 'normal' + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/native/win/dockconstants.py b/digsby/src/gui/native/win/dockconstants.py new file mode 100644 index 0000000..c7bb601 --- /dev/null +++ b/digsby/src/gui/native/win/dockconstants.py @@ -0,0 +1,124 @@ +ABM_NEW = 0x00000000 +ABM_REMOVE = 0x00000001 +ABM_QUERYPOS = 0x00000002 +ABM_SETPOS = 0x00000003 +ABM_GETSTATE = 0x00000004 +ABM_GETTASKBARPOS = 0x00000005 +ABM_ACTIVATE = 0x00000006 +ABM_GETAUTOHIDEBAR = 0x00000007 +ABM_SETAUTOHIDEBAR = 0x00000008 +ABM_WINDOWPOSCHANGED = 0x0000009 +ABN_STATECHANGE = 0x0000000 +ABN_POSCHANGED = 0x0000001 +ABN_FULLSCREENAPP = 0x0000002 +ABN_WINDOWARRANGE = 0x0000003 +ABS_AUTOHIDE = 0x0000001 +ABS_ALWAYSONTOP = 0x0000002 + +AW_HOR_POSITIVE = 0x00000001 +AW_HOR_NEGATIVE = 0x00000002 +AW_VER_POSITIVE = 0x00000004 +AW_VER_NEGATIVE = 0x00000008 +AW_CENTER = 0x00000010 +AW_HIDE = 0x00010000 +AW_ACTIVATE = 0x00020000 +AW_SLIDE = 0x00040000 +AW_BLEND = 0x00080000 +WS_EX_LAYERED = 0x00080000 + +SW_HIDE = 0 +SW_SHOWNORMAL = 1 +SW_NORMAL = 1 +SW_SHOWMINIMIZED = 2 +SW_SHOWMAXIMIZED = 3 +SW_MAXIMIZE = 3 +SW_SHOWNOACTIVATE = 4 +SW_SHOW = 5 +SW_MINIMIZE = 6 +SW_SHOWMINNOACTIVE = 7 +SW_SHOWNA = 8 +SW_RESTORE = 9 +SW_SHOWDEFAULT = 10 +SW_MAX = 10 + +ABE_LEFT = 0 +ABE_TOP = 1 +ABE_RIGHT = 2 +ABE_BOTTOM = 3 + +sideStrings = {0:'left', 1:'top', 2:'right', 3:'bottom'} + +HWND_TOP = 0 +HWND_BOTTOM = 1 +HWND_TOPMOST = -1 +HWND_NOTOPMOST = -2 +SWP_NOSIZE = 0x0001 +SWP_NOMOVE = 0x0002 +SWP_NOZORDER = 0x0004 +SWP_NOREDRAW = 0x0008 +SWP_NOACTIVATE = 0x0010 +SWP_FRAMECHANGED = 0x0020 +SWP_SHOWWINDOW = 0x0040 +SWP_HIDEWINDOW = 0x0080 +SWP_NOCOPYBITS = 0x0100 +SWP_NOOWNERZORDER = 0x0200 +SWP_NOSENDCHANGING = 0x0400 +SWP_DRAWFRAME = 0x0020 +SWP_NOREPOSITION = 0x0200 +SWP_DEFERERASE = 0x2000 +SWP_ASYNCWINDOWPOS = 0x4000 + +WS_EX_APPWINDOW = 0x40000 +WS_EX_TOOLWINDOW = 0x80 + +# Windows message constants +GWL_WNDPROC = -4 +GWL_EXSTYLE = -20 +WM_SIZING = 0x214 +WM_SIZE = 0x0005 +WM_MOVING = 0x216 +WM_ENTERSIZEMOVE = 0x231 +WM_EXITSIZEMOVE = 0x232 +WM_NCHITTEST = 0x084 +WM_WINDOWPOSCHANGING = 0x046 +WM_USER = 0x0400 + +WM_SYSCOMMAND = 0x0112 +SC_SIZE = 0xF000 +SC_MOVE = 0xF010 +SC_MINIMIZE = 0xF020 +SC_MAXIMIZE = 0xF030 +SC_ZOOM = 61488 + +# hittest contants +HTNOWHERE = 0 +HTCLIENT = 1 +HTCAPTION = 2 +HTSYSMENU = 3 +HTGROWBOX = 4 +HTSIZE = HTGROWBOX +HTMENU = 5 +HTHSCROLL = 6 +HTVSCROLL = 7 +HTMINBUTTON = 8 +HTMAXBUTTON = 9 +HTLEFT = 10 +HTRIGHT = 11 +HTTOP = 12 +HTTOPLEFT = 13 +HTTOPRIGHT = 14 +HTBOTTOM = 15 +HTBOTTOMLEFT = 16 +HTBOTTOMRIGHT = 17 +HTBORDER = 18 +HTREDUCE = HTMINBUTTON +HTZOOM = HTMAXBUTTON + +WMSZ_LEFT = 1 +WMSZ_RIGHT = 2 +WMSZ_TOP = 3 +WMSZ_TOPLEFT = 4 +WMSZ_TOPRIGHT = 5 +WMSZ_BOTTOM = 6 +WMSZ_BOTTOMLEFT = 7 +WMSZ_BOTTOMRIGHT = 8 \ No newline at end of file diff --git a/digsby/src/gui/native/win/internetexplorer.py b/digsby/src/gui/native/win/internetexplorer.py new file mode 100644 index 0000000..b7932fd --- /dev/null +++ b/digsby/src/gui/native/win/internetexplorer.py @@ -0,0 +1,31 @@ +''' +a comtypes driven IE +''' + +class InternetExplorer(object): + def __init__(self): + from comtypes.client import CreateObject, GetEvents + + self.ie = CreateObject("InternetExplorer.Application") + self.ie.Toolbar = False + self.ie.StatusBar = False + + def Show(self, shown = True): + self.ie.Visible = shown + + def LoadUrl(self, url): + self.ie.Navigate(url) + + @property + def Cookie(self): + return self.ie.Document.parentWindow.execScript('document.cookie') + +def main(): + ie = InternetExplorer() + ie.LoadUrl('http://www.facebook.com') + ie.Show() + + print ie.Cookie + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/native/win/jumplist.py b/digsby/src/gui/native/win/jumplist.py new file mode 100644 index 0000000..57ba452 --- /dev/null +++ b/digsby/src/gui/native/win/jumplist.py @@ -0,0 +1,180 @@ +''' +builds the Jumplist -- the menu that appears when you right click the digsby Windows 7 Taskbar icon + +the C++ implementation side is in ext/src/win/WinJumpList.cpp +''' +import cgui +import sys +import wx +import common +import traceback +from common import profile +from digsbyipcaction.funcs import funccall + +from main import APP_ID + +TOP_BUDDIES_ENABLED = False +SHOW_STATUS_ITEMS = True + +# +# hooks +# +def buddylist_sorted(view): + _update_jumplist() + +def status_set(status): + if SHOW_STATUS_ITEMS: + _set_jumplist_now() + +# +# + +if getattr(sys, 'DEV', False): + script = u'Digsby.py ' +else: + script = u'' + +def action(name, desc, label, icon=None): + return (script + '--action=' + name, desc, label, icon) + +def get_status(name, service): + return profile.blist.get_status(name, service) + +def buddy_item(buddy): + # disabling icons for now--when users pin buddies, their status icons become + # out of date. + status_icon = None + + ipc_call = funccall('chat', idstr=buddy.idstr()) + + name = unicode(buddy.alias) + desc = _('Chat with {name}').format(name=name) + + return action(ipc_call, desc, name, status_icon) + +def logging_enabled(): + if not getattr(logging_enabled, 'linked', False): + logging_enabled.linked = True + def on_log_ims_pref(val): + _update_jumplist() + + common.profile.prefs.link('log.ims', on_log_ims_pref, callnow=False, obj=logging_enabled) + return common.pref('log.ims') + +MAX_BUDDIES_IN_LOGSIZE_LIST = 7 + +def _set_jumplist_now(): + 'sets the jumplist immediately' + + popular_buddies = [] + + if TOP_BUDDIES_ENABLED: + try: + popular_buddies = profile.blist.online_popular_buddies() + except Exception: + traceback.print_exc() + + set_app_jumplist(popular_buddies) + +def _update_jumplist(): + ''' + updates the jumplist on a timer + ''' + if not cgui.isWin7OrHigher(): + return + + def on_timer(): + _set_jumplist_now() + + @wx.CallAfter + def after(): + try: + t = buddylist_sorted.timer + except AttributeError: + t = buddylist_sorted.timer = wx.PyTimer(on_timer) + on_timer() + else: + if not t.IsRunning(): + t.StartOneShot(6000) + +def _status_icons_for_status(status): + from gui import skin + status_icons = dict.fromkeys(('available', 'away', 'invisible'), None) + checked = skin.get('appdefaults.icons.taskbarcheck', None) + if checked is not None: + checked = checked.PIL.Resized(16).WXB + + if status.invisible: + status_icons['invisible'] = checked + elif status.away: + status_icons['away'] = checked + elif status.available: + status_icons['available'] = checked + + return status_icons + +def jumplist_status_items(current_status): + ''' + returns a series of status jumplist items. the current status will have + a check icon. + ''' + status_icons = _status_icons_for_status(current_status) + + def status_action(name, label): + tooltip = _('Change IM Status to {status}').format(status=label) + ipc = funccall('status', status=name) + return action(ipc, tooltip, label, status_icons[name]) + + return [ + status_action('available', _('Available')), + status_action('away', _('Away')), + status_action('invisible', _('Invisible')), + ] + +def set_app_jumplist(log_sizes=None): + @wx.CallAfter + def after(): + from gui import skin + TASKS = [ + action('globalstatus', _('Set your status on multiple networks'), _('Set Global Status'), skin.get('icons.globalstatus', None)), + action('newim', _('Open the New IM window'), _('New IM...'), skin.get('AppDefaults.UnreadMessageIcon', None)), + action('prefsdialog', _('Open the Digsby Preferences window'), _('Preferences...')), + ] + + if SHOW_STATUS_ITEMS: + TASKS.append(None) # separator + TASKS.extend(jumplist_status_items(profile.status)) + + # Exit Digsby + TASKS.append(None) + TASKS.append(action('exit', _('Close all windows and exit'), _('Exit Digsby'), skin.get('AppDefaults.JumpListCloseIcon', None))) + + jumplist = [ + (u'', TASKS), + ] + + if TOP_BUDDIES_ENABLED: + logging = logging_enabled() + + if logging and log_sizes is not None: + buddies = [buddy_item(buddy) + for buddy in log_sizes[:MAX_BUDDIES_IN_LOGSIZE_LIST] + if not buddy.__class__.__name__ == 'OfflineBuddy'] + else: + buddies = [action(funccall('prefsdialog', tabname='text_conversations'), _('Opens the Preferences Window where you can enable logging'), _('Enable Chat Logs'))] + + jumplist.append((u'Top Buddies (by log size)', buddies)) + + success = cgui.SetUpJumpList(APP_ID, jumplist) + + # clear jumplist on shutdown + register_shutdown_hook() + +_did_register_shutdown_hook = False +def register_shutdown_hook(): + global _did_register_shutdown_hook + if _did_register_shutdown_hook: return + _did_register_shutdown_hook = True + + wx.GetApp().PreShutdown.append(lambda: cgui.SetUpJumpList(APP_ID, [])) + diff --git a/digsby/src/gui/native/win/process.py b/digsby/src/gui/native/win/process.py new file mode 100644 index 0000000..6359991 --- /dev/null +++ b/digsby/src/gui/native/win/process.py @@ -0,0 +1,189 @@ +from util.ffi import cimport +from ctypes.wintypes import DWORD, HMODULE, MAX_PATH +from ctypes import byref, WinError, sizeof, create_unicode_buffer, Structure, c_ulong +import os + +SIZE_T = c_ulong + +cimport(Psapi = ['GetModuleBaseNameW', 'EnumProcesses', 'EnumProcessModules', 'GetModuleBaseNameW', 'GetProcessMemoryInfo', 'EmptyWorkingSet'], + kernel32 = ['OpenProcess', 'SetProcessWorkingSetSize', 'CloseHandle', 'GetCurrentProcess', 'GetCurrentThread', 'SetThreadPriority'], + user32 = ['GetGuiResources']) + +N = 500 +dword_array = DWORD * N +aProcesses = dword_array() +cBytes = DWORD() + +# constants for OpenProcess +PROCESS_QUERY_INFORMATION = 0x400 +PROCESS_VM_READ = 0x10 +PROCESS_SET_QUOTA = 0x0100 + +OPEN_PROCESS_FLAGS = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ + +szProcessName = create_unicode_buffer(MAX_PATH) +hMod = HMODULE() +cBytesNeeded = DWORD() + +class PROCESS_MEMORY_COUNTERS(Structure): + _fields_ = [ + ("cb", DWORD), + ("PageFaultCount", DWORD), + ("PeakWorkingSetSize", SIZE_T), + ("WorkingSetSize", SIZE_T), + ("QuotaPeakPagedPoolUsage", SIZE_T), + ("QuotaPagedPoolUsage", SIZE_T), + ("QuotaPeakNonPagedPoolUsage", SIZE_T), + ("QuotaNonPagedPoolUsage", SIZE_T), + ("PagefileUsage", SIZE_T), + ("PeakPagefileUsage", SIZE_T) + ] + +class PROCESS_MEMORY_COUNTERS_EX(Structure): + _fields_ = [ + ("cb", DWORD), + ("PageFaultCount", DWORD), + ("PeakWorkingSetSize", SIZE_T), + ("WorkingSetSize", SIZE_T), + ("QuotaPeakPagedPoolUsage", SIZE_T), + ("QuotaPagedPoolUsage", SIZE_T), + ("QuotaPeakNonPagedPoolUsage", SIZE_T), + ("QuotaNonPagedPoolUsage", SIZE_T), + ("PagefileUsage", SIZE_T), + ("PeakPagefileUsage", SIZE_T), + ("PrivateUsage", SIZE_T) + ] + +def page_out_ram(): + ''' + Warning: Evil RAM hack! + + TODO: call repeatedly when taskman.exe is present >:D + ''' + + print 'paging out ram' + + hProcess = OpenProcess(PROCESS_SET_QUOTA, False, os.getpid()) + + if not hProcess: + raise WinError() + + if not SetProcessWorkingSetSize(hProcess, -1, -1): + raise WinError() + + CloseHandle(hProcess) + +def process_list(): + 'Returns a list of running processes.' + + if not EnumProcesses(byref(aProcesses), N, byref(cBytes)): + raise WinError + + processes = [] + for processID in aProcesses: + if processID == 0: continue + + hProcess = OpenProcess(OPEN_PROCESS_FLAGS, False, processID) + if not hProcess: continue + try: + + if EnumProcessModules(hProcess, byref(hMod), sizeof(hMod), byref(cBytesNeeded)): + if GetModuleBaseNameW(hProcess, hMod, szProcessName, sizeof(szProcessName) / 2): + processes.append(szProcessName.value) + finally: + CloseHandle(hProcess) + + return processes + +_no_pmc_ex = False +pmc = PROCESS_MEMORY_COUNTERS_EX() + +def memory_info(processID = None, p=False): + if processID is None: + processID = os.getpid() + + hProcess = OpenProcess(OPEN_PROCESS_FLAGS, False, processID) + + if not hProcess: + raise WinError() + + global _no_pmc_ex + global pmc + + if _no_pmc_ex or not GetProcessMemoryInfo(hProcess, byref(pmc), sizeof(PROCESS_MEMORY_COUNTERS_EX)): + # windows pre-xp service pack 2 doesn't have PROCESS_MEMORY_COUNTERS_EX + _no_pmc_ex = True + ret = GetProcessMemoryInfo(hProcess, byref(pmc), sizeof(PROCESS_MEMORY_COUNTERS_EX)) + else: + ret = True + + if ret: + if p: + print "\tPageFaultCount: %10d" % pmc.PageFaultCount + print "\tPeakWorkingSetSize: %10d" % pmc.PeakWorkingSetSize + print "\tWorkingSetSize: %10d" % pmc.WorkingSetSize + print "\tQuotaPeakPagedPoolUsage: %10d" % pmc.QuotaPeakPagedPoolUsage + print "\tQuotaPagedPoolUsage: %10d" % pmc.QuotaPagedPoolUsage + print "\tQuotaPeakNonPagedPoolUsage: %10d" % pmc.QuotaPeakNonPagedPoolUsage + print "\tQuotaNonPagedPoolUsage: %10d" % pmc.QuotaNonPagedPoolUsage + print "\tPagefileUsage: %10d" % pmc.PagefileUsage + print "\tPeakPagefileUsage: %10d" % pmc.PeakPagefileUsage + print "\tPrivateUsage: %10d" % pmc.PrivateUsage + else: + raise WinError() + + CloseHandle(hProcess); + return pmc + +def str_meminfo(pmc): + return '\r\n'.join(filter(None, ['{', + "\t'PageFaultCount': %10d," % pmc.PageFaultCount, + "\t'PeakWorkingSetSize': %10d," % pmc.PeakWorkingSetSize, + "\t'WorkingSetSize': %10d," % pmc.WorkingSetSize, + "\t'QuotaPeakPagedPoolUsage': %10d," % pmc.QuotaPeakPagedPoolUsage, + "\t'QuotaPagedPoolUsage': %10d," % pmc.QuotaPagedPoolUsage, + "\t'QuotaPeakNonPagedPoolUsage': %10d," % pmc.QuotaPeakNonPagedPoolUsage, + "\t'QuotaNonPagedPoolUsage': %10d," % pmc.QuotaNonPagedPoolUsage, + "\t'PagefileUsage': %10d," % pmc.PagefileUsage, + "\t'PeakPagefileUsage': %10d," % pmc.PeakPagefileUsage, + (("\t'PrivateUsage': %10d," % pmc.PrivateUsage) + if isinstance(pmc, PROCESS_MEMORY_COUNTERS_EX) else ''), + '}'])) + + +GR_GDIOBJECTS = 0 +GR_USEROBJECTS = 1 +current_process_handle = GetCurrentProcess() + +def count_gdi_objects(): + return GetGuiResources(current_process_handle, GR_GDIOBJECTS) + +def count_user_objects(): + return GetGuiResources(current_process_handle, GR_USEROBJECTS) + +THREAD_MODE_BACKGROUND_BEGIN = 0x00010000 +THREAD_MODE_BACKGROUND_END = 0x00020000 + +THREAD_PRIORITY_ABOVE_NORMAL = 1 +THREAD_PRIORITY_BELOW_NORMAL = -1 +THREAD_PRIORITY_HIGHEST = 2 +THREAD_PRIORITY_IDLE = -15 +THREAD_PRIORITY_LOWEST = -2 +THREAD_PRIORITY_NORMAL = 0 +THREAD_PRIORITY_TIME_CRITICAL = 15 + +def set_bgthread(background = True, thread_handle = None): + if thread_handle is None: + thread_handle = GetCurrentThread() + + priority = THREAD_PRIORITY_IDLE if background else THREAD_PRIORITY_NORMAL + + print thread_handle, priority + if not SetThreadPriority(thread_handle, priority): + raise WinError() + +if __name__ == '__main__': + from time import clock + now = clock() + print process_list() + print clock() - now diff --git a/digsby/src/gui/native/win/taskbar.py b/digsby/src/gui/native/win/taskbar.py new file mode 100644 index 0000000..e682b3f --- /dev/null +++ b/digsby/src/gui/native/win/taskbar.py @@ -0,0 +1,41 @@ +''' +simple interface to windows 7 taskbar apis +''' + +import wx +import cgui + +tab_notebook_attr = '_tab_notebook' + +def get_tab_notebook(tlw): + try: + return getattr(tlw, tab_notebook_attr) + except AttributeError: + nb = cgui.TabNotebook(tlw) + setattr(tlw, tab_notebook_attr, nb) + return nb + +def app_taskbar_notebook(): + nb = None + others = [] + for win in wx.GetTopLevelWindows(): + if win.IsShown() and win.OnTaskbar: + nb = get_tab_notebook(win) + break + + return nb + +def set_overlay_icon(icon, tlw=None): + if icon is None: + icon = wx.NullBitmap + + if tlw is None: + nb = app_taskbar_notebook() + else: + nb = get_tab_notebook(tlw) + + if nb is not None: + return nb.SetOverlayIcon(icon) + else: + return False + diff --git a/digsby/src/gui/native/win/toplevel.py b/digsby/src/gui/native/win/toplevel.py new file mode 100644 index 0000000..dabfc45 --- /dev/null +++ b/digsby/src/gui/native/win/toplevel.py @@ -0,0 +1,105 @@ +import wx, sys, ctypes +from util import ffi +from ctypes import sizeof, byref +from ctypes.wintypes import UINT, HWND, DWORD +from gui.native.win.winutil import WinStruct + +ffi.cimport(user32 = ['FlashWindowEx', 'FlashWindow']) + +FLASHW_STOP = 0 +FLASHW_CAPTION = 0x00000001 +FLASHW_TRAY = 0x00000002 +FLASHW_ALL = FLASHW_CAPTION | FLASHW_TRAY +FLASHW_TIMER = 0x00000004 # Flash continuously, until the FLASHW_STOP flag is set. +FLASHW_TIMERNOFG = 0x0000000C # Flash continuously until the window comes to the foreground. + + +class FLASHWINFO(WinStruct): + _fields_ = [('cbSize', UINT), + ('hwnd', HWND), + ('dwFlags', DWORD), + ('uCount', UINT), + ('dwTimeout', DWORD)] + +# failed import? +if not FlashWindowEx: #@UndefinedVariable + wx.TopLevelWindow.StopFlashing = lambda win: None +else: + FLASHWINFOsize = sum(sizeof(zz) for zz in (UINT, HWND, DWORD, UINT, DWORD)) + assert FLASHWINFOsize == len(FLASHWINFO()) + + tlw = wx.TopLevelWindow + def StopFlashing(win): + f = FLASHWINFO(cbSize = FLASHWINFOsize, + hwnd = win.Handle, + dwFlags = FLASHW_STOP) + + FlashWindowEx(f.ptr) #@UndefinedVariable + def doint(): + if not wx.IsDestroyed(win): tlw.SetTitle(win, tlw.GetTitle(win)) + wx.CallLater(1000, doint) + wx.CallLater(3000, doint) + doint() + + wx.TopLevelWindow.StopFlashing = StopFlashing + +def Flash(win, titlebar = True, taskbar = True, timeout = 0, count = 1, until = 'foreground'): + ''' + Requests the user's attention. + + until can be + 'foreground' stop flashing when the window comes to the foreground + 'stop' don't stop flashing until FlashWindowEx is called with FLASHW_STOP + ''' + + flags = 0#FLASHW_TIMER if until == 'stop' else FLASHW_TIMERNOFG + flags |= FLASHW_CAPTION if titlebar else 0 + flags |= FLASHW_TRAY if taskbar else 0 + + flashinfo = FLASHWINFO(hwnd = win.Handle, + dwFlags = flags, + uCount = count, + dwTimeout = timeout) + + + flashinfo.cbSize = sizeof(FLASHWINFO) + #print flashinfo + + #print + FlashWindowEx(flashinfo.ptr) #@UndefinedVariable + +def FlashOnce(win): + return FlashWindow(win.Handle, 1) #@UndefinedVariable + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None, title = 'test') + f.Title = 'test %s' % f.Handle + f.Show() + + f2 = wx.Frame(None, title = 'control') + r = f.Rect + f2.Rect = wx.Rect(r.Right, r.Top, r.Width, r.Height) + + def b(t, c): + button = wx.Button(f2, -1, t) + button.Bind(wx.EVT_BUTTON, lambda e: c()) + return button + + #flash = b('FlashWindow', lambda: Flash2(f)) + flashex = b('FlashWindowEx', lambda: Flash(f)) + request = b('RequestUserAttention', f.RequestUserAttention) + settitle = b('Set Title', lambda: (f.SetTitle(f.GetTitle() + ' Yay'), Flash(f))) + stop = b('Stop Flashing', lambda: StopFlashing(f)) + + s = f2.Sizer = wx.BoxSizer(wx.VERTICAL) + s.AddMany([flashex, request, settitle, stop]) + s.Layout() + + f2.Show() + + + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/native/win/winconstants.py b/digsby/src/gui/native/win/winconstants.py new file mode 100644 index 0000000..e29e3b4 --- /dev/null +++ b/digsby/src/gui/native/win/winconstants.py @@ -0,0 +1,122 @@ +ABM_NEW = 0x00000000 +ABM_REMOVE = 0x00000001 +ABM_QUERYPOS = 0x00000002 +ABM_SETPOS = 0x00000003 +ABM_GETSTATE = 0x00000004 +ABM_GETTASKBARPOS = 0x00000005 +ABM_ACTIVATE = 0x00000006 +ABM_GETAUTOHIDEBAR = 0x00000007 +ABM_SETAUTOHIDEBAR = 0x00000008 +ABM_WINDOWPOSCHANGED = 0x0000009 +ABN_STATECHANGE = 0x0000000 +ABN_POSCHANGED = 0x0000001 +ABN_FULLSCREENAPP = 0x0000002 +ABN_WINDOWARRANGE = 0x0000003 +ABS_AUTOHIDE = 0x0000001 +ABS_ALWAYSONTOP = 0x0000002 + +AW_HOR_POSITIVE = 0x00000001 +AW_HOR_NEGATIVE = 0x00000002 +AW_VER_POSITIVE = 0x00000004 +AW_VER_NEGATIVE = 0x00000008 +AW_CENTER = 0x00000010 +AW_HIDE = 0x00010000 +AW_ACTIVATE = 0x00020000 +AW_SLIDE = 0x00040000 +AW_BLEND = 0x00080000 +WS_EX_LAYERED = 0x00080000 + +SW_HIDE = 0 +SW_SHOWNORMAL = 1 +SW_NORMAL = 1 +SW_SHOWMINIMIZED = 2 +SW_SHOWMAXIMIZED = 3 +SW_MAXIMIZE = 3 +SW_SHOWNOACTIVATE = 4 +SW_SHOW = 5 +SW_MINIMIZE = 6 +SW_SHOWMINNOACTIVE = 7 +SW_SHOWNA = 8 +SW_RESTORE = 9 +SW_SHOWDEFAULT = 10 +SW_MAX = 10 + +ABE_LEFT = 0 +ABE_TOP = 1 +ABE_RIGHT = 2 +ABE_BOTTOM = 3 + +sideStrings = {0:'left', 1:'top', 2:'right', 3:'bottom'} + +HWND_TOP = 0 +HWND_BOTTOM = 1 +HWND_TOPMOST = -1 +HWND_NOTOPMOST = -2 +SWP_NOSIZE = 0x0001 +SWP_NOMOVE = 0x0002 +SWP_NOZORDER = 0x0004 +SWP_NOREDRAW = 0x0008 +SWP_NOACTIVATE = 0x0010 +SWP_FRAMECHANGED = 0x0020 +SWP_SHOWWINDOW = 0x0040 +SWP_HIDEWINDOW = 0x0080 +SWP_NOCOPYBITS = 0x0100 +SWP_NOOWNERZORDER = 0x0200 +SWP_NOSENDCHANGING = 0x0400 +SWP_DRAWFRAME = 0x0020 +SWP_NOREPOSITION = 0x0200 +SWP_DEFERERASE = 0x2000 +SWP_ASYNCWINDOWPOS = 0x4000 + +WS_EX_APPWINDOW = 0x40000 +WS_EX_TOOLWINDOW = 0x80 + +# Windows message constants +GWL_WNDPROC = -4 +GWL_STYLE = -16 +GWL_EXSTYLE = -20 +WM_SIZING = 0x214 +WM_MOVING = 0x216 +WM_ENTERSIZEMOVE = 0x231 +WM_EXITSIZEMOVE = 0x232 +WM_NCHITTEST = 0x084 +WM_WINDOWPOSCHANGING = 0x046 +WM_USER = 0x0400 + +WM_SYSCOMMAND = 0x0112 +SC_MAXIMIZE = 0xF030 +SC_MINIMIZE = 0xF020 +SC_ZOOM = 61488 + +# hittest contants +HTNOWHERE = 0 +HTCLIENT = 1 +HTCAPTION = 2 +HTSYSMENU = 3 +HTGROWBOX = 4 +HTSIZE = HTGROWBOX +HTMENU = 5 +HTHSCROLL = 6 +HTVSCROLL = 7 +HTMINBUTTON = 8 +HTMAXBUTTON = 9 +HTLEFT = 10 +HTRIGHT = 11 +HTTOP = 12 +HTTOPLEFT = 13 +HTTOPRIGHT = 14 +HTBOTTOM = 15 +HTBOTTOMLEFT = 16 +HTBOTTOMRIGHT = 17 +HTBORDER = 18 +HTREDUCE = HTMINBUTTON +HTZOOM = HTMAXBUTTON + +WMSZ_LEFT = 1 +WMSZ_RIGHT = 2 +WMSZ_TOP = 3 +WMSZ_TOPLEFT = 4 +WMSZ_TOPRIGHT = 5 +WMSZ_BOTTOM = 6 +WMSZ_BOTTOMLEFT = 7 +WMSZ_BOTTOMRIGHT = 8 diff --git a/digsby/src/gui/native/win/windocking.py b/digsby/src/gui/native/win/windocking.py new file mode 100644 index 0000000..c69dd1f --- /dev/null +++ b/digsby/src/gui/native/win/windocking.py @@ -0,0 +1,906 @@ +''' + +Window docking and auto hiding. + +>> w = wx.Frame(None) +>> docker = Docker(w) + +Important properties: + +>> docker.Enabled # docking on or off +>> docker.AutoHide # auto hiding on or off +>> docker.DockMargin # number of pixels away from edge to begin docking +>> docker.Animate # whether to animate when autohiding + +''' + +from gui.native.win.dockconstants import WM_EXITSIZEMOVE, WM_WINDOWPOSCHANGING, WM_SIZING, WM_NCHITTEST, \ + WM_SYSCOMMAND, WM_USER, ABE_LEFT, ABE_RIGHT, ABM_REMOVE, SC_MAXIMIZE, \ + SC_MINIMIZE, HTBOTTOMLEFT, HTLEFT, HTTOPLEFT, HTBOTTOMRIGHT, HTRIGHT, \ + HTTOPRIGHT, SC_SIZE, ABM_SETPOS, WMSZ_LEFT, WMSZ_RIGHT, ABM_SETAUTOHIDEBAR, \ + sideStrings, ABM_NEW, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, ABM_GETSTATE, \ + ABS_ALWAYSONTOP, HWND_BOTTOM, HWND_TOPMOST, ABE_TOP, ABE_BOTTOM, ABM_QUERYPOS, \ + ABN_STATECHANGE, ABN_FULLSCREENAPP, ABN_POSCHANGED, ABN_WINDOWARRANGE, \ + ABS_AUTOHIDE + +import ctypes, wx +from wx import PyTimer +from ctypes import c_uint, Structure, byref, c_int, POINTER, cast +from logging import getLogger; log = getLogger('dock') +from gui.toolbox import Monitor +from util.primitives.funcs import Delegate +from util import default_timer + +from gui.native.win.winextensions import wxRectToRECT + +from ctypes.wintypes import DWORD, HANDLE, LPARAM, RECT +user32 = ctypes.windll.user32 +SetWindowPos = user32.SetWindowPos +MoveWindow = user32.MoveWindow +shell32_SHAppBarMessage = ctypes.windll.shell32.SHAppBarMessage +CallWindowProc = ctypes.windll.user32.CallWindowProcW +DefWindowProc = ctypes.windll.user32.DefWindowProcW + + +def SHAppBarMessage(msg, abd): + 'Sends a message to the AppBar API for handling docking areas.' + return shell32_SHAppBarMessage(msg, byref(abd)) + +ANIMATE_SECS = .1 +AUTOHIDE_TIMER_MS = 130 +ANIMATE_DELTA = 6 + +# WM_SYS_COMMAND's wParam throws this number on maximize, but it's not SC_MAXIMIZE +UNKNOWN_MAX_FLAG = 61490 + +sign = lambda v: (1 if v > 0 else (-1 if v < 0 else 0)) + + +class Docker(object): + 'Causes a window to dock and optionally autohide.' + + def __init__(self, win, autohide = False, enabled = True): + self.win = win + self._side = None#ABE_LEFT + self.DockMargin = 30 + self.ShouldShowInTaskbar = lambda: True #must be set by window + self.ShouldAlwaysStayOnTop = None + self.RevealDurationMs = 300 + self.Animate = True + self.autohidden = self.docking = self.docked = False + self._enabled = enabled + self._autohide = autohide + + self.bypassSizeEvents = False + self.bypassMoveEvents = False + self.reReserveTimer = None + + Bind = win.Bind + BindWin32 = win.BindWin32 + + Bind(wx.EVT_MOVING, self.OnMoving) + Bind(wx.EVT_CLOSE, self.OnClose) + Bind(wx.EVT_ACTIVATE, self.OnActivate) + Bind(wx.EVT_DISPLAY_CHANGED, self.OnDisplayChanged) + Bind(wx.EVT_SHOW, self.OnShow) + + + BindWin32(WM_WINDOWPOSCHANGING, self.OnWindowPosChanging) + + BindWin32(WM_EXITSIZEMOVE, self.OnExitSizeMove) + BindWin32(WM_SIZING, self.OnSizing) + + BindWin32(WM_NCHITTEST, self.OnNCHitTest) + BindWin32(WM_SYSCOMMAND, self.OnSysCommand) + + self.appbar_cb_id = WM_USER + 100 + BindWin32(self.appbar_cb_id, self.AppBarCallback) + + self.firstShow = True + self.wasDocked = False + self.oldSize = None + self.motiontrigger = False + self.timer = PyTimer(self.OnTimer) + + self.OnDock = Delegate() + self.OnHide = Delegate() + self.LinkedWindows = [] + + def __repr__(self): + return '' % self.win + + def GetSide(self): + if self._side == None: + pos, margin = self.win.Rect, self.DockMargin + screenRect = monitor_rect(self.win) + + if pos.x < screenRect.X + margin and pos.Right > screenRect.X: + self._side = ABE_LEFT + if pos.Right > screenRect.Right - margin and pos.x < screenRect.Right: + self._side = ABE_RIGHT + + return self._side + + def SetSide(self, side): + self._side = side + + side = property(GetSide, SetSide) + + + def OnDisplayChanged(self, event): + + log.debug('OnDisplayChanged') + + if self.docked and not self.AutoHide: + self.Undock(setFrameStyle=False) + self.win.FitInScreen(Monitor.GetFromDeviceId(self.monId)) #@UndefinedVariable + self.Dock(setFrameStyle=False) + + self.bypassMoveEvents = True + self.win.SetRect(self.dockedRect) + self.bypassMoveEvents = False + + self.docking = True + self.docked = False + + self.OnExitSizeMove() + + elif self.docked and self.AutoHide: + + onRight = self.side == ABE_RIGHT + + mon = Monitor.GetFromDeviceId(self.monId) #@UndefinedVariable + + monRect = mon.ClientArea + + xPos = monRect.Right+1 if onRight else monRect.Left-1 + while onscreen(xPos, monRect.Y): + mon = Monitor.GetFromPoint((xPos, monRect.Y)) #@UndefinedVariable + monRect = mon.ClientArea + xPos = monRect.Right+1 if onRight else monRect.Left-1 + +# self.win.FitInScreen(mon) + if onRight: + self.dockedRect = wx.Rect(monRect.Right-self.win.Size.width + 1, monRect.Top, self.win.Size.width, monRect.Bottom - monRect.Top) + else: + self.dockedRect = wx.Rect(monRect.left-1,monRect.Top, self.win.Size.width, monRect.Bottom - monRect.Top) + + self.bypassMoveEvents = True + self.win.SetRect(self.dockedRect) + self.bypassMoveEvents = False + + self.GoAway(monRect) + + event.Skip() + + def OnShow(self,event): + event.Skip() + if not self.win.Shown: + return + + onRight = self.side == ABE_RIGHT + mon = Monitor.GetFromWindow(self.win) #@UndefinedVariable + monRect = mon.ClientArea + + atEdge = not onscreen(monRect.Right+1 if onRight else monRect.Left-1, monRect.Y) + shouldDock = self.wasDocked or (self.firstShow and (atEdge or not self.AutoHide)) + + if self.Enabled and shouldDock and self.side is not None and not self.docked: + + self.Dock(setFrameStyle = self.firstShow) + + self.firstShow = False + + self.bypassMoveEvents = True + self.win.SetRect(self.dockedRect) + self.bypassMoveEvents = False + + self.docking = True + self.docked = False + + self.OnExitSizeMove() + + elif self.firstShow: + self.firstShow = False + + self.wasDocked = False + + + def SetEnabled(self, val): + """ + Enable or disable docking, called by buddy list when the pref changes. + """ + + if not isinstance(val, bool): + raise TypeError('Enabled must be a boolean') + + self._enabled = val + + #turn docking off docking + if not val and self.docked: + self.Undock() + p = self.dockedRect[:2] + self.docking = self.docked = self.autohidden = False + wx.CallLater(50, self.SetRectSimple, wx.Rect(p[0],p[1], *wx.Size(*self.oldSize))) + + self.UpdateTaskbar() + + #turn docking on + elif val and not self.docked and self.win.IsShownOnScreen(): + self.docking = False + + #emulate a motion event to initiate docking if the buddylist is in position + if self.OnMoving(): + + #emulate a finished moving event to finish docking procedure + self.OnExitSizeMove() + + #if autohide preference is set, start timer to autohide the buddylist + if self.AutoHide: + self.timer.Start(AUTOHIDE_TIMER_MS) + self.motiontrigger = False + + self.win.SetRect(self.dockedRect) + + elif self.wasDocked: + SetToolStyle(self.win,val) + self.bypassMoveEvents = True + if val: + self.win.Rect = self.dockedRect + else: + self.win.Rect = wx.RectPS(self.dockedRect.Position, self.oldSize) + self.bypassMoveEvents = False + + self.UpdateTaskbar() + + + if not val: + self.oldSize = None + + Enabled = property(lambda self: self._enabled, SetEnabled, None, + 'Set this boolean property to enable or disable docking') + + def SetAutoHide(self, val): + """ + Called by buddylist when autohide pref change + """ + + log.debug('SetAutoHide') + #if docking is enabled and the buddylist is currently docked, + #undock and redock with the new settings + if self.Enabled and self.docked: + self.Undock(setFrameStyle = False) + self._autohide = val + self.Dock(setFrameStyle = False) + + # this swapping of these two bools happens a lot, possibly should be something else + self.docking = True + self.docked = False + + self.OnExitSizeMove() + + if not val: + self.ComeBack() + + self.UpdateTaskbar() + else: + self._autohide = val + + + AutoHide = property(lambda self: self._autohide, SetAutoHide, None, + 'Set this boolean property to enable or disable autohiding.') + + def SetRectSimple(self, rect): + 'Sets the window rectangle without invoking special events.' + + self.bypassMoveEvents = True + self.win.SetRect(rect) + self.bypassMoveEvents = False + + def OnTimer(self, e=None): + 'Called by self.timer for reliable mouse off detection.' + if not (self.Enabled and self.AutoHide and self.docked): + return + + w = self.win + mp = wx.GetMousePosition() + if w and not self.motiontrigger and not w.IsActive(): + if not any(r.Contains(mp) for r in (w.ScreenRect for w in [w] + self.LinkedWindows)): + self.GoAway() + + def GoAway(self, rect = None): + 'Causes the window to autohide.' + + log.debug('GoAway') + w = self.win + r = rect if rect else monitor_rect(w) + margin = 1 + + if self.side == ABE_LEFT: + x, y = (r.X - w.Rect.width + margin, r.Y) + elif self.side == ABE_RIGHT: + x, y = (r.Right - margin, r.Y) + else: + assert False + + self.motiontrigger = True + self.autohidden = True + + self.timer.Stop() + self.OnHide() + self.AnimateWindowTo(wx.Rect(x, y, *w.Size)) + self.SetWinAlwaysOnTop(True) + + def ComeBack(self): + """ + Causes the window to come back from its autohide position. + + Sets both self.autohidden and self.motiontrigger to False + """ + + log.debug('ComeBack') + self.motiontrigger = False + self.AnimateWindowTo(self.dockedRect) + self.autohidden = False + self.timer.Start(AUTOHIDE_TIMER_MS) + + def SetVelocity(self, v): + self._velocity = v + + velocity = property(lambda self: self._velocity, SetVelocity) + + animateToTarget = None + lasttick = None + def AnimateWindowTo(self, r=None): + 'Slides the window to position x, y.' + + now = default_timer() + if r is not None: + self.animateToTarget = r + self.lasttick = now + + targetx, y = self.animateToTarget[:2] + win = self.win + winx = win.Position.x + + direction = sign(targetx - win.Position.x) + delta = int((now - self.lasttick)*self.velocity) * direction + + + self.bypassMoveEvents = True + if winx != targetx and self.Animate: + if delta: + newx = winx + delta + + if (targetx >= winx) != (targetx >= newx): + newx = targetx + + win.Move((newx, y)) + self.lasttick = now + wx.CallLater(15, self.AnimateWindowTo) + elif winx != targetx: + win.SetRect(r) + self.bypassMoveEvents = False + + def OnClose(self, e): + 'If the windows closing and docked, unregisters from the docking system.' + + log.info('OnClose: %r', e.EventObject) + + if self.Enabled and self.docked: + if self.AutoHide: + return self.GoAway() + else: + SHAppBarMessage(ABM_REMOVE, self.newAppBarData) + + e.Skip(True) + + def OnActivate(self, e): + """ + Unhide the Window when it gets focus + """ + + if not self.Enabled or not self.AutoHide or not self.docked or not self.autohidden: + return + + if e.GetActive(): + if not onscreen(*self.win.Position): + self.ComeBack() + self.win.Raise() + else: + self.timer.Start(AUTOHIDE_TIMER_MS) + e.Skip() + + def OnSysCommand(self, hWnd, msg, wParam, lParam): + ''' + Catches special WM_SYSCOMMAND messages to prevent minimizing and + maximizing when docked. + ''' + if self.Enabled and self.docked: + if msg == WM_SYSCOMMAND and wParam in (UNKNOWN_MAX_FLAG, SC_MINIMIZE, SC_MAXIMIZE): + return False + + def autohidden_mouseover(self): + if self.Enabled and self.autohidden and self.motiontrigger and wx.FindWindowAtPointer() is self.win: + + self.ComeBack() + + def OnNCHitTest(self, hWnd, msg, wParam, lParam): + 'Win32 message event for "hit test" mouse events on a window.' + + if self.Enabled and self.motiontrigger: + # The mouse has hit the one pixel window--bring the window back. + + + ms = wx.GetMouseState() + if ms.LeftDown(): + self.bypassSizeEvents = True + elif self.bypassSizeEvents: + self.bypassSizeEvents = False + + try: + t = self.autohidden_timer + except AttributeError: + t = self.autohidden_timer = wx.PyTimer(self.autohidden_mouseover) + + if not t.IsRunning(): + t.StartOneShot(self.RevealDurationMs) + + elif self.Enabled and self.docked: + # Don't allow the window's edge to be dragged if that edge is the + # one at the edge of the screen. + hit = DefWindowProc(hWnd, msg, wParam, lParam) + if self.side == ABE_LEFT and hit in (HTLEFT, HTTOPLEFT, HTBOTTOMLEFT) or \ + self.side == ABE_RIGHT and hit in (HTRIGHT, HTTOPRIGHT, HTBOTTOMRIGHT): + return False + + def OnExitSizeMove(self, hWnd=0, msg=0, wParam=0, lParam=0): + """ + When the window is _done_ moving, this method is called. + + Appears to cover the last steps of the (un)docking procedures + """ + log.debug('SC_SIZE: %s', SC_SIZE) + log.debug('OnExitSizeMove %s %s %s', msg, wParam, lParam) + + + if not self.Enabled: + return + + if self.docked and self.AutoHide: + if self.bypassSizeEvents: + ms = wx.GetMouseState() + if not ms.LeftDown(): + self.bypassSizeEvents = False + + return False + + #if in docking mode and not docked, dock + if self.docking and not self.docked: + self.docking = False + self.docked = True + self.AppBarQuerySetPos(set = True) + + if self.docked and not self.AutoHide: + abd = self.GetNewAppBarData() + abd.rc = wxRectToRECT(self.dockedRect) + SHAppBarMessage(ABM_SETPOS, abd) + + #clear old size if not docked + if not self.docked: + self.oldSize = None + + + self.UpdateTaskbar() + + def UpdateTaskbar(self): + """ + Toggle Taskbar entry for the window + + If the window is on the taskbar and docked, remove it + Else if it should show in taskbar, show it + """ + + log.debug('UpdateTaskbar') + + ontaskbar = self.win.OnTaskbar + if ontaskbar and self.docked: + log.debug('Hide Task') + self.win.OnTaskbar = False + else: + should = self.ShouldShowInTaskbar() + if should and not ontaskbar and not self.docked: + log.debug('Show Task') + self.win.OnTaskbar = True + + def OnSizing(self, hWnd, msg, wParam, lParam): + """ + Intercepts moving events at the Win32 level + + For special case sizing rules while docked + """ + + #log.debug('OnSizing') + + # WM_SIZING callback + if not (self.Enabled and self.docked): + #log.debug('Not docked') + return + + try: + if self.__sizing: return + except AttributeError: pass + + + + side = self.side + if not self.bypassSizeEvents and ((side == ABE_LEFT and wParam == WMSZ_RIGHT) or (side == ABE_RIGHT and wParam == WMSZ_LEFT)): + # lParam is a pointer to a RECT + log.debug('Docked sizing') + r = wx.Rect.FromRECT(RECT.from_address(lParam)) #@UndefinedVariable + + # readjust the docking rectangle + self.__sizing = True + + + # this will cause the window to resize + self.dockedRect = r + + # also save the width in our "oldSize" which is used when the window is finally undocked + self.oldSize.width = r.width + + self.__sizing = False + + else: + log.debug('Resize restricted') + d = self.dockedRect + + # lParam holds the address of the RECT which will be the new + # window size. setting it to our docked rectangle is like + # "cancelling" the resize + r = RECT.from_address(lParam) #@UndefinedVariable + + # set these explicitly so they go into memory at lParam. + r.x = d.x + r.y = d.y + r.right = d.x + d.width + r.bottom = d.y + d.height + + return False + + def OnWindowPosChanging(self, hWnd, msg, wParam, lParam): + ''' + Intercepts moving events at the Win32 level so that the window's new + position can be overridden. + ''' + + if not self.Enabled: + return + + elif not self.bypassMoveEvents: + mon = monitor_rect(self.win) + margin = self.DockMargin + pos = cast(lParam, POINTER(c_int)) + x, y, w, h = pos[2:6] + + #return if already docked or docking on that side + if self.docked or self.docking: + + awayFromLeft = self.side == ABE_LEFT and x > mon.X + margin or x + w < mon.X + awayFromRight = self.side == ABE_RIGHT and x+w < mon.Right - margin or x > mon.Right + notResized = y+h != self.dockedRect.Bottom+1 + + if (awayFromLeft or awayFromRight) and notResized: + return + + for i, j in enumerate(xrange(2, 6)): + pos[j] = self.dockedRect[i] + + elif self.oldSize: + pos[4] = self.oldSize.width + pos[5] = self.oldSize.height + + + def OnMoving(self, e=None): + 'Bound to wx.EVT_MOVE. Detects when it is time to dock and undock.' + + if e: e.Skip(True) + if not self.Enabled: + return + + #log.debug('OnMoving') + pos, margin = self.win.Rect, self.DockMargin + rect = monitor_rect(self.win) + + if not self.docked and not self.docking: + if pos.Right > rect.Right - margin and pos.x < rect.Right: + + isOnScreen = isRectOnScreen(wx.Rect(rect.Right+1, rect.Top, 1, rect.Height)) + + if not self.AutoHide or not isOnScreen: + return self.Dock(ABE_RIGHT) + + elif pos.x < rect.X + margin and pos.Right > rect.X: + + isOnScreen = isRectOnScreen(wx.Rect(rect.X-1, rect.Top, 1, rect.Height)) + + if not self.AutoHide or not isOnScreen: + return self.Dock(ABE_LEFT) + + else: + if self.side == ABE_LEFT and pos.x > rect.X + margin or pos.Right < rect.X: + self.Undock() + elif self.side == ABE_RIGHT and pos.Right < rect.Right - margin or pos.x > rect.Right: + self.Undock() + + def Dock(self, side = None, setFrameStyle = True): + """ + Dock to the side of the screen storing un-docked information for later use + If self.AutoHide is True tries docking with autohide first, + attempting to dock regularly if that fails or if self.autohide is False + + Sets the following: + self.side + self.oldSize + self.autohidden - Only in autohide mode + + Calls self.OnDock in either docking style + + returns True if either docking style succeeds, None otherwise + """ + + log.debug('Dock') + + self.monId = Monitor.GetFromWindow(self.win).DeviceId #@UndefinedVariable + + + if side is not None: + self.side = side + + if self.AutoHide: + if SHAppBarMessage(ABM_SETAUTOHIDEBAR, self.GetNewAppBarData(True)): + log.info('registered autohide %s', sideStrings[self.side]) + if setFrameStyle: SetToolStyle(self.win, True) + self.SetWinAlwaysOnTop(True) + if self.oldSize is None: + self.oldSize = self.win.Size + self.SetAutoHidePos() + self.timer.Start(AUTOHIDE_TIMER_MS) + self.autohidden = False + self.OnDock(True) + return True + else: + log.warning('obtaining autohide bar failed') + + # fall back to normal docking + if SHAppBarMessage(ABM_NEW, self.newAppBarData): + self.oldSize = self.oldSize or self.win.Size + if setFrameStyle: SetToolStyle(self.win, True) +# self.SetWinAlwaysOnTop(True) + self.AppBarQuerySetPos(set = False) + self.OnDock(True) + return True + + def Undock(self, setFrameStyle = True): + """ + Undock the window + """ + + log.debug('Undock') + + #TODO: Should we do this line? It never got hit before and it seemed to work fine + + if setFrameStyle: SetToolStyle(self.win, False) + + #TODO: should do both or just one SHAppBarMessage? + if self.AutoHide: + SHAppBarMessage(ABM_SETAUTOHIDEBAR, self.newAppBarData) + SHAppBarMessage(ABM_REMOVE, self.newAppBarData) + self.docking = self.docked = False + self.OnDock(False) + + self.autohidden = False + self.SetWinAlwaysOnTop() + + #=========================================================================== + # The following functions are invoked by the system when the AppBar needs + # to be notified. + #=========================================================================== + winPosArgs = (0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE) + + def OnAppBarStateChange(self, wParam, lParam): + log.info('OnAppBarStateChange') + + # Check to see if the taskbar's always-on-top state has + # changed and, if it has, change the appbar's state + # accordingly. + state = SHAppBarMessage(ABM_GETSTATE, self.newAppBarData) + SetWindowPos(self.Handle,HWND_TOPMOST if (ABS_ALWAYSONTOP & state) else HWND_BOTTOM, *self.winPosArgs) + + def OnAppBarFullscreenApp(self, wParam, lParam): + log.info('OnAppBarFullscreenApp') + + state = SHAppBarMessage(ABM_GETSTATE, self.newAppBarData) + + if lParam: + SetWindowPos(self.Handle, HWND_TOPMOST if (ABS_ALWAYSONTOP & state) else HWND_BOTTOM, *self.winPosArgs) + + elif state & ABS_ALWAYSONTOP: + SetWindowPos(self.Handle, HWND_TOPMOST, *self.winPosArgs) + + def OnAppBarPosChanged(self, wParam, lParam): +# log.warning('not implemented: OnAppBarPosChanged') +# return + + log.info('OnAppBarPosChanged') + + rc, mon = RECT(), monitor_rect(self.win) + rc.top = mon.Top + rc.left = mon.Left + rc.right = mon.Right + rc.bottom = mon.Bototm + + winrect = self.win.Rect + iHeight = winrect.Bottom - winrect.Top + iWidth = winrect.Right - winrect.Left + + if self.side == ABE_TOP: + rc.bottom = rc.top + iHeight + elif self.side == ABE_BOTTOM: + rc.top = rc.bottom - iHeight + elif self.side == ABE_LEFT: + rc.right = rc.left + iWidth + elif self.side == ABE_RIGHT: + rc.left = rc.right - iWidth; + + #self.AppBarQuerySetPos(rc = rc) + + def GetDockRect(self, rect = None): + """ + Calculates the new RECT for the window, + returns a appBarData, + also returns winWidth, the current width of the window + """ + abd = self.newAppBarData + mon = monitor_rect(self.win) + + if self.side in (ABE_LEFT, ABE_RIGHT): + r = self.win.Rect if rect is None else rect + winWidth = r.Width + abd.rc.left = mon.Left if self.side == ABE_LEFT else mon.Right - r.Width+1 + abd.rc.right = mon.Left + r.Width if self.side == ABE_LEFT else mon.Right+1 + + abd.rc.top = mon.Top + abd.rc.bottom = mon.Bottom+1 + return abd, winWidth + + def SetAutoHidePos(self): + abd, unused_winWidth = self.GetDockRect() + rc = abd.rc + self.dockedRect = wx.Rect(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top) + self.docking = True + + def AppBarQuerySetPos(self, set = True): + """ + Query or set the size and position of the docked BuddyList + Sets self.dockedRect + self.Docking becomes True + """ + side = self.side + appBarData, winWidth = self.GetDockRect() + + if not set: + SHAppBarMessage(ABM_QUERYPOS, appBarData) + + if side == ABE_LEFT: + appBarData.rc.right = appBarData.rc.left + winWidth + elif side == ABE_RIGHT: + appBarData.rc.left = appBarData.rc.right - winWidth + + if set: + SHAppBarMessage(ABM_SETPOS, appBarData) + + rc = appBarData.rc + self.dockedRect = wx.Rect(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top) + self.docking = True + + def OnAppBarWindowArrange(self, wParam, lParam): + log.info('OnAppBarWindowArrange') + + appBarCallbackMap = {ABN_STATECHANGE: OnAppBarStateChange, + ABN_FULLSCREENAPP: OnAppBarFullscreenApp, + ABN_POSCHANGED: OnAppBarPosChanged, + ABN_WINDOWARRANGE: OnAppBarWindowArrange} + + def AppBarCallback(self, *params): + log.info('AppBarCallback %r', params) + wParam, lParam = params[2:4] + + self.appBarCallbackMap[wParam](wParam, lParam) + + def GetNewAppBarData(self, lParam = False): + """ + Create and return a new AppBarData Struct (see MSDN's "APPBARDATA structure") + filling in the following with the current information: + hWnd - Window Handle + uEdge - side of screen + rc - Window Rectangle + lParam - uses lParam Arg + """ + + abd = APPBARDATA() + abd.hWnd = self.Handle + abd.uEdge = self.side + abd.rc = self.win.RECT + abd.lParam = lParam + # abd.uCallbackMessage = self.appbar_cb_id + return abd + + newAppBarData = property(GetNewAppBarData) + + @property + def Handle(self): + return self.win.Handle + + def SetWinAlwaysOnTop(self, val = None): + if self.ShouldAlwaysStayOnTop: + self.ShouldAlwaysStayOnTop(val) + + +def SetToolStyle(win, val): + ''' + Sets a window to be "tool style," that is, no maximize/minimize buttons and + with a smaller square border. + ''' + if val: win.SetWindowStyle(wx.FRAME_TOOL_WINDOW | win.GetWindowStyle()) + else: win.SetWindowStyle(~wx.FRAME_TOOL_WINDOW & win.GetWindowStyle()) + + +def monitor_rect(win): + 'returns a wxRect for the client area of the monitor the mouse pointer is over' + + return Monitor.GetFromWindow(win).ClientArea #@UndefinedVariable + +def onscreen(x, y): + 'Returns True if the specified (x, y) point is on any screen.' + + return Monitor.GetFromPoint((x, y), find_near = False) is not None #@UndefinedVariable + +def isRectOnScreen(rect): + + return Monitor.GetFromRect(rect, find_near = False) is not None #@UndefinedVariable + +class APPBARDATA(Structure): + 'Used with the Win32 shell.dll SHAppBarMessage function.' + + _fields_ = [ + ("cbSize", DWORD), + ("hWnd", HANDLE), + ("uCallbackMessage", c_uint), + ("uEdge", c_uint), + ("rc", RECT), + ("lParam", LPARAM), + ] + +taskbar_abd = APPBARDATA() +from ctypes import sizeof +taskbar_abd.cbSize = sizeof(APPBARDATA) + +def taskbar_info(): + ''' + Returns a mapping with information about the state of the taskbar, with + the following keys: + + always_on_top + autohide + ''' + uState = SHAppBarMessage(ABM_GETSTATE, taskbar_abd) + return dict(always_on_top = uState & ABS_ALWAYSONTOP, + autohide = uState & ABS_AUTOHIDE) + +try: + import psyco #@UnresolvedImport +except Exception, e: + pass +else: + psyco.bind(Docker) diff --git a/digsby/src/gui/native/win/wineffects.py b/digsby/src/gui/native/win/wineffects.py new file mode 100644 index 0000000..2957539 --- /dev/null +++ b/digsby/src/gui/native/win/wineffects.py @@ -0,0 +1,244 @@ +''' +Windows platform specific effects. +''' + +import sys +import wx +import ctypes +from ctypes.wintypes import RECT, byref +from wx import Rect +from platform import platform +from util import Storage + +user32 = ctypes.windll.user32 +gdi32 = ctypes.windll.gdi32 +controls = Storage( + captionclose = (1, 0x0000), + captionmin = (1, 0x0001), + captionmax = (1, 0x0002), + captionrestore = (1, 0x0003), + captionhelp = (1, 0x0004), + + menuarrow = (2, 0x0000), + menucheck = (2, 0x0001), + menubullet = (2, 0x0002), + menuarrowright = (2, 0x0004), + scrollup = (3, 0x0000), + scrolldown = (3, 0x0001), + scrollleft = (3, 0x0002), + scrollright = (3, 0x0003), + scrollcombobox = (3, 0x0005), + scrollsizegrip = (3, 0x0008), + scrollsizegripright = (3, 0x0010), + + buttoncheck = (1, 0x0000), + buttonradioimage = (4, 0x0001), + buttonradiomask = (4, 0x0002), + buttonradio = (4, 0x0004), + button3state = (4, 0x0008), + buttonpush = (4, 0x0010), +) + +states = Storage( + inactive =0x0100, + pushed =0x0200, + checked =0x0400, + + transparent =0x0800, + hot =0x1000, + + adjustrect =0x2000, + flat =0x4000, + mono =0x8000, +) + +def rect2wx(winRECT): + 'Converts a winapi RECT to a wxRect.' + + return Rect(winRECT.left, + winRECT.top, + winRECT.right - winRECT.left, + winRECT.bottom - winRECT.top) + + +_smokeFrame=None + +try: + from cgui import ApplySmokeAndMirrors +except ImportError: + print >> sys.stderr, 'WARNING: using slow ApplySmokeAndMirrors' + def ApplySmokeAndMirrors(win, shape = None, ox = 0, oy = 0): + ''' + Sets the shape of a window. + + shape (integer) a windows handle to a Windows Region + (wx.Region) a wx.Region specifying the shape + (wx.Bitmap or wx.Image) an image to use alpha or mask information for a shape + ''' + global _smokeFrame + if _smokeFrame is None: + _smokeFrame = wx.Frame(wx.FindWindowByName('Buddy List'), -1, '', style = wx.FRAME_SHAPED) + + def on_destroy(e): + # On shutdown, the smoke frame might be destroyed--make sure that ApplySmokeAndMirros + # is replaced with a stub. + e.Skip() + if e.EventObject is _smokeFrame: + globals()['ApplySmokeAndMirrors'] = lambda win, shape = None, ox = 0, oy = 0: None + + _smokeFrame.Bind(wx.EVT_WINDOW_DESTROY, on_destroy) + + if isinstance(shape, (int, type(None))): + return user32.SetWindowRgn(win.Handle, shape, True) + + rgn = gdi32.CreateRectRgn(0, 0, *win.Size) + + if shape: + if isinstance(shape, wx.Region): + region = shape + else: + if not shape.GetMask(): + image = wx.ImageFromBitmap(shape) + image.ConvertAlphaToMask(200) + bitmap = wx.BitmapFromImage(image) + else: + bitmap = shape + + region = wx.RegionFromBitmap(bitmap) + + _smokeFrame.SetShape(region) + user32.GetWindowRgn(_smokeFrame.Handle, rgn) + gdi32.OffsetRgn(rgn, -1 + ox, -1 + oy) + user32.SetWindowRgn(win.Handle, rgn, True) + gdi32.DeleteObject(rgn) + +def GetRgn(win): + rgn = gdi32.CreateRectRgn(0, 0, *win.Size) + type = user32.GetWindowRgn(win.Handle, rgn) + + if not type: + rgn = gdi32.CreateRectRgn(0, 0, win.Rect.Right,win.Rect.Bottom) + + return rgn + +def SmokeAndMirrorsBomb(win,windows): + rgn = gdi32.CreateRectRgn(0,0,0,0) + + shown = [window for window in windows if window.Shown] + rgns = [GetRgn(window) for window in shown] + + for i in xrange(len(shown)): + gdi32.OffsetRgn(rgns[i],*shown[i].Position) + gdi32.CombineRgn(rgn,rgn,rgns[i],2)# 2 is C constaing RGN_OR + gdi32.DeleteObject(rgns [i]) + +# gdi32.OffsetRgn(rgn,-1,-1) + user32.SetWindowRgn(win.Handle, rgn, True) + gdi32.DeleteObject(rgn) + +SB_HORZ = 0 +SB_VERT = 1 +SB_BOTH = 3 +WS_EX_LAYERED = 0x00080000 + +_user32 = ctypes.windll.user32 + +GetWindowLongA = _user32.GetWindowLongA +SetWindowLongA = _user32.SetWindowLongA +try: + GetLayeredWindowAttributes = _user32.GetLayeredWindowAttributes + SetLayeredWindowAttributes = _user32.SetLayeredWindowAttributes +except AttributeError: + # Windows 2000 is teh suck + pass +from ctypes import c_byte + +LWA_COLORKEY = 1 + +def SetColorKey(window, rgb_color_tuple): + assert len(rgb_color_tuple) == 3 + color = ctypes.c_uint((0xff000000 & 0) | + (0x00ff0000 & rgb_color_tuple[0]) | + (0x0000ff00 & rgb_color_tuple[1]) | + (0x000000ff & rgb_color_tuple[2])) + + hwnd = window.Handle + + # make WS_EX_LAYERED if necessary. + style = GetWindowLongA(hwnd, 0xffffffecL) + layered_style = style | WS_EX_LAYERED + if layered_style != style: + SetWindowLongA(hwnd, 0xffffffecL, layered_style) + + SetLayeredWindowAttributes(hwnd, byref(color), 0, LWA_COLORKEY) + +def ShowScrollbar(window, show): + hwnd = window.GetHandle() + scrollbar = SB_VERT + _user32.ShowScrollBar(hwnd, scrollbar, show) + +def _setalpha_wxMSW_ctypes(window, alpha): + '''Use SetLayeredWindowAttributes in user32.dll to adjust a + window's transparency.''' + + hwnd = window.GetHandle() + oldStyle = style = GetWindowLongA(hwnd, 0xffffffecL) + + if alpha == 255: + style &= ~WS_EX_LAYERED + else: + style |= WS_EX_LAYERED + + if oldStyle != style: + SetWindowLongA(hwnd, 0xffffffecL, style) + + SetLayeredWindowAttributes(hwnd, 0, alpha, 2) + window._alpha = alpha + +def _donealpha_wxMSW_ctypes(window): + if not window: return + hwnd = window.GetHandle() + oldStyle = style = GetWindowLongA(hwnd, 0xffffffecL) + style &= ~WS_EX_LAYERED + if getattr(window, '_alpha', 255) == 0: + window.Hide() + if oldStyle != style: + SetWindowLongA(hwnd, 0xffffffecL, style) + window._alpha = 255 + +def _getalpha_wxMSW_ctypes(window): + "Returns a window's transparency." + + return getattr(window, '_alpha', 255) + hwnd = window.GetHandle() + alpha = c_byte() + try: + GetLayeredWindowAttributes(hwnd, 0, alpha, 2) + return alpha.value + except AttributeError: + return getattr(window, '_alpha', 255) + +def _drawnativecontrol_wxMSW(handle, rect, control, state=0): + 'Use DrawFrameControl in user32.dll to draw a native control.' + + _user32.DrawFrameControl(handle, rect, control[0], control[1] | state) + +setalpha = _setalpha_wxMSW_ctypes +getalpha = _getalpha_wxMSW_ctypes +donealpha = _donealpha_wxMSW_ctypes + +def DrawSubMenuArrow(dc, rect): + from gui.native.win.winextensions import wxRectToRECT + rect = wxRectToRECT(rect) + + _drawnativecontrol_wxMSW(dc.GetHDC(), byref(rect), controls.menuarrow, 0) + +if platform().startswith('Windows-2000'): + setalpha = lambda *a: None + getalpha = lambda *a: 255 + + fadein = lambda win, *a, **k: win.Show(True) + def fadeout(win, speed = 'normal', on_done = None, from_ = None): + win.Show(False) + if on_done is not None: + on_done() diff --git a/digsby/src/gui/native/win/winextensions.py b/digsby/src/gui/native/win/winextensions.py new file mode 100644 index 0000000..31fc6c8 --- /dev/null +++ b/digsby/src/gui/native/win/winextensions.py @@ -0,0 +1,70 @@ +''' +This file will contain all platform-specific extensions or method replacements to wx API, +such as overriding wx.LaunchDefaultBrowser on Windows or adding wx.Window.Cut method. +''' + +from peak.util.imports import lazyModule, whenImported +wx = lazyModule('wx') +os = lazyModule('os') +wintypes = lazyModule('ctypes.wintypes') + +from threading import Thread +from logging import getLogger; log = getLogger('winextensions') +import traceback + +def browse(url): + 'Opens "url" in the default web browser.' + + def go(): + try: + os.startfile(url) + except WindowsError: + if hasattr(traceback, 'print_exc_once'): + traceback.print_exc_once() + else: + traceback.print_exc() + log.error('could not open browser for url: %r', url) + _fire_browser_error_popup() + + # reenable once we have advanced prefs + t = Thread(target=go) + t.setDaemon(True) + t.start() + +wx.LaunchDefaultBrowser = browse + +def _fire_browser_error_popup(): + from common import fire + fire('error', title='Error Launching Default Browser', + msg="No default web browser set in Windows. Please check your web browser's preferences.", + details='') + +def wxRectToRECT(rect): + r = wintypes.RECT() + r.left = rect.X + r.top = rect.Y + r.right = rect.X + rect.Width + r.bottom = rect.Y + rect.Height + return r + +def wxRectFromRECT(rect): + return wx.Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top) + +def GetRECT(win): + return wxRectToRECT(win.Rect) + +def GetLRECT(win): + return wxRectToRECT(wx.RectS(win.GetSize())) + +def _monkeypatch(*a, **k): + # Hack until patching methods works better in new bindings. + WindowClass = wx._Window if wx.WXPY else wx.Window + + WindowClass.GetRECT = GetRECT + WindowClass.RECT = property(GetRECT) + WindowClass.GetLRECT = GetLRECT + WindowClass.LRECT = property(GetLRECT) + wx.Rect.FromRECT = staticmethod(wxRectFromRECT) + wx.Rect.RECT = property(wxRectToRECT) + +whenImported('wx', _monkeypatch) diff --git a/digsby/src/gui/native/win/winfonts.py b/digsby/src/gui/native/win/winfonts.py new file mode 100644 index 0000000..692ac4a --- /dev/null +++ b/digsby/src/gui/native/win/winfonts.py @@ -0,0 +1,119 @@ +from util.ffi import cimport, Struct +from ctypes.wintypes import DWORD, WCHAR +from ctypes import windll, byref, create_unicode_buffer, create_string_buffer +from ctypes import c_ushort, sizeof + +from gui.native.win.winutil import WinStruct +from gui.textutil import default_font +from cgui import PyGetFontUnicodeRanges + + +# constants used in AddFontResourceEx function +FR_PRIVATE = 0x10 +FR_NOT_ENUM = 0x20 + +def loadfont(fontpath, private = True, enumerable = False): + ''' + Makes fonts located in file "fontpath" available to the font system. + + private if True, other processes cannot see this font, and this font + will be unloaded when the process dies + + enumerable if True, this font will appear when enumerating fonts + + see http://msdn2.microsoft.com/en-us/library/ms533937.aspx + ''' + + if isinstance(fontpath, str): + pathbuf = create_string_buffer(fontpath) + AddFontResourceEx = windll.gdi32.AddFontResourceExA + elif isinstance(fontpath, unicode): + pathbuf = create_unicode_buffer(fontpath) + AddFontResourceEx = windll.gdi32.AddFontResourceExW + else: + raise TypeError('fontpath must be a str or unicode') + + flags = (FR_PRIVATE if private else 0) | (FR_NOT_ENUM if not enumerable else 0) + + numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0) + + return bool(numFontsAdded) + +def unloadfont(fontpath, private = True, enumerable = False): + ''' + Unloads the fonts in the specified file. + + see http://msdn2.microsoft.com/en-us/library/ms533925.aspx + ''' + + if isinstance(fontpath, str): + pathbuf = create_string_buffer(fontpath) + RemoveFontResourceEx = windll.gdi32.RemoveFontResourceExA + elif isinstance(fontpath, unicode): + pathbuf = create_unicode_buffer(fontpath) + RemoveFontResourceEx = windll.gdi32.RemoveFontResourceExW + else: + raise TypeError('fontpath must be a str or unicode') + + + flags = (FR_PRIVATE if private else 0) | (FR_NOT_ENUM if not enumerable else 0) + return bool(RemoveFontResourceEx(byref(pathbuf), flags, 0)) + + + +_fontranges = {} + +# TODO: bloom filters? + +def MemoizedFontRanges(font): + key = hash(font.NativeFontInfoDesc) + + if key in _fontranges: + return _fontranges[key] + else: + return _fontranges.setdefault(key, PyGetFontUnicodeRanges(font)) + +def font_has_char(font, char): + ranges = MemoizedFontRanges(font) + char = ord(unicode(char)) + + for start, len in ranges: + end = start + len + if start <= char < end: + return True + + return False + + +def main(): + import wx + dc = wx.MemoryDC() + dc.SetFont(default_font()) + + size = GetFontUnicodeRanges(dc.GetHDC(), 0) + if not size: raise Exception(GFURerror) + + numRanges = (size - sizeof(DWORD) * 4) / sizeof(WCRANGE) + + class GLYPHSET(WinStruct): + _fields_ = [('cbThis', DWORD), + ('flAccel', DWORD), + ('cGlyphsSupported', DWORD), + ('cRanges', DWORD), + ('ranges', WCRANGE * numRanges), + ] + + g = GLYPHSET(cbThis = size, ranges = [WCRANGE() for x in xrange(numRanges)]) + + + if not GetFontUnicodeRanges(dc, glyphset.ptr): + raise Exception(GFURerror) + + print g + +GFURerror = 'GetFontUnicodeRanges failed, see http://msdn2.microsoft.com/en-us/library/ms533944(VS.85).aspx' + +if __name__ == '__main__': + import wx + a = wx.PySimpleApp() + main() diff --git a/digsby/src/gui/native/win/winfullscreen.py b/digsby/src/gui/native/win/winfullscreen.py new file mode 100644 index 0000000..8448ed9 --- /dev/null +++ b/digsby/src/gui/native/win/winfullscreen.py @@ -0,0 +1,42 @@ +# http://msdn2.microsoft.com/en-us/library/bb762533(VS.85).aspx + +from ctypes import c_int, byref, WinError, windll + +try: + SHQueryUserNotificationState = windll.shell32.SHQueryUserNotificationState +except AttributeError: + enabled = False +else: + ''' + typedef enum tagQUERY_USER_NOTIFICATION_STATE { + QUNS_NOT_PRESENT = 1, + QUNS_BUSY = 2, + QUNS_RUNNING_D3D_FULL_SCREEN = 3, + QUNS_PRESENTATION_MODE = 4, + QUNS_ACCEPTS_NOTIFICATIONS = 5 + QUNS_QUIET_TIME = 6 //win7 and later, new windows user. + } QUERY_USER_NOTIFICATION_STATE; + ''' + + enabled = True + + last_state = False + + def get_accepts_notifications(): + ''' + Returns True if the program should display notifications. + + VISTA ONLY. + + May raise WinError. + ''' + state = c_int() + if SHQueryUserNotificationState(byref(state)): + raise WinError() + val = state.value + global last_state + last_state = val + return val not in (1,2,3,4) + +if __name__ == '__main__': + print get_accepts_notifications() diff --git a/digsby/src/gui/native/win/winhelpers.py b/digsby/src/gui/native/win/winhelpers.py new file mode 100644 index 0000000..25340c8 --- /dev/null +++ b/digsby/src/gui/native/win/winhelpers.py @@ -0,0 +1,235 @@ +from __future__ import with_statement +import ctypes, sys, os +from ctypes.wintypes import RECT +from ctypes import byref, c_int, c_long, WinError +from logging import getLogger; log = getLogger('winhelpers') +import traceback + +user32 = ctypes.windll.user32 +kernel32 = ctypes.windll.kernel32 + +SetWindowLong = user32.SetWindowLongW +CallWindowProc = user32.CallWindowProcW +DefWindowProc = user32.DefWindowProcW +WndProcType = ctypes.PYFUNCTYPE(c_int, c_long, c_int, c_int, c_int) +GWL_WNDPROC = -4 + +# some events. +WM_SIZING = 0x214 +WM_MOVING = 0x216 +WM_ENTERSIZEMOVE = 0x231 +WM_EXITSIZEMOVE = 0x232 +WM_NCHITTEST = 0x084 +WM_WINDOWPOSCHANGING = 0x046 + +GW_HWNDNEXT = 2 + +import wx + +import win32events + + +wx.Window.ShowNoFocus = lambda win: win.Show(show = True, activate = False) + +if getattr(wx, 'WXPY', False): + import new + meth = new.instancemethod + wx._Window.BindWin32 = meth(win32events.bindwin32, None, wx._Window) + wx._Window.UnbindWin32 = meth(win32events.unbindwin32, None, wx._Window) +else: + wx.Window.BindWin32 = win32events.bindwin32 + wx.Window.UnbindWin32 = win32events.unbindwin32 + + +try: + AttachThreadInput = user32.AttachThreadInput + GetWindowThreadProcessId = user32.GetWindowThreadProcessId + GetCurrentThreadId = kernel32.GetCurrentThreadId + GetForegroundWindow = user32.GetForegroundWindow + GetDesktopWindow = user32.GetDesktopWindow + GetShellWindow = user32.GetShellWindow + SetForegroundWindow = user32.SetForegroundWindow +except: + print >> sys.stderr, 'No ReallyRaise' + wx.WindowClass.ReallyRaise = lambda win: win.Raise() +else: + def ReallyRaise(win): + ''' + Raises a window even when the active window doesn't belong to the + current process. + ''' + + AttachThreadInput(GetWindowThreadProcessId(GetForegroundWindow(), 0), GetCurrentThreadId(), True) + SetForegroundWindow(win.Handle) + AttachThreadInput(GetWindowThreadProcessId(GetForegroundWindow(), 0), GetCurrentThreadId(), False) + + getattr(wx, '_Window', wx.Window).ReallyRaise = ReallyRaise + + def IsForegroundWindow(win): + return win.GetHandle() == GetForegroundWindow() + + getattr(wx, '_Window', wx.Window).IsForegroundWindow = IsForegroundWindow + +GetLastInputInfo = user32.GetLastInputInfo +GetTickCount = kernel32.GetTickCount +GetLastError = kernel32.GetLastError + + +try: + from cgui import GetUserIdleTime +except ImportError: + print >> sys.stderr, 'WARNING: using slow GetUserIdleTime' + + class LastInputInfo(ctypes.Structure): + _fields_ = [('cbSize', ctypes.wintypes.UINT), + ('dwTime', ctypes.wintypes.DWORD)] + + + # a global LastInputInfo object used by the GetUserIdleTime + # function below + input_info = LastInputInfo() + input_info.cbSize = ctypes.sizeof(input_info) + input_info_ref = ctypes.byref(input_info) + + def GetUserIdleTime(): + '''Returns the time since last user input, in milliseconds.''' + + if GetLastInputInfo(input_info_ref): + return GetTickCount() - input_info.dwTime + else: + raise WinError() + +GetTopWindow = user32.GetTopWindow +GetWindow = user32.GetWindow +GetWindowRect = user32.GetWindowRect +IsWindowVisible = user32.IsWindowVisible +GetWindowLong = user32.GetWindowLongA +GWL_EXSTYLE = -20 +WS_EX_TOPMOST = 0x8 + +def win32ontop(hwnd): + return bool(GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) + + + +# +# the folowing logic checked for if the Taskbar was autohidden +# and didn't flash if so... +# but we decided that it should be configurable +# +#_TLW_RequestUserAttention = wx.TopLevelWindow.RequestUserAttention +# +#def RequestUserAttention(win): +# # don't flash the taskbar entry red if the taskbar is set to autohide. +# # flashing causes the taskbar to come back. +# try: +# from gui.docking.dock import taskbar_info +# autohide = taskbar_info()['autohide'] +# except Exception: +# print_exc() +# autohide = False +# +# if not autohide: +# return _TLW_RequestUserAttention(win) +# +#wx.TopLevelWindow.RequestUserAttention = RequestUserAttention +from wx import FRAME_NO_TASKBAR + +from gui.native.win.winconstants import SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SWP_FRAMECHANGED, WS_EX_APPWINDOW, GWL_EXSTYLE +window_pos_flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED + +from ctypes import windll; user32 = windll.user32 + +GA_ROOT = 2 + +def ctypes_import(src, **k): + G = globals() + for name, funcs in k.iteritems(): + dll = getattr(src, name) + G.update((func, getattr(dll, func)) for func in funcs) + + +SetWindowLongA = user32.SetWindowLongA +GetWindowLongA = user32.GetWindowLongA +SetWindowPos = user32.SetWindowPos +GetParent = user32.GetParent +SetParent = user32.SetParent +FindWindowA = user32.FindWindowA +ShowWindow = user32.ShowWindow +GetAncestor = user32.GetAncestor + +SetLastError = kernel32.SetLastError +GetLastError = kernel32.GetLastError + +def SetOnTaskbar(f, val): + hwnd = f.Handle + + needs_show = f.IsShown() + needs_focus = getattr(wx.Window.FindFocus(), 'Top', None) is f + + with f.Frozen(): + if needs_show: f.Hide() + + SetLastError(0) + + if val: + #print '\nsetting TASKBAR ICON' + f.WindowStyle = f.WindowStyle & ~FRAME_NO_TASKBAR + res = SetWindowLongA(hwnd, GWL_EXSTYLE, (GetWindowLongA(hwnd, GWL_EXSTYLE) | WS_EX_APPWINDOW)) + else: + #print '\nsetting NO TASKBAR' + f.WindowStyle = f.WindowStyle | FRAME_NO_TASKBAR + res = SetWindowLongA(hwnd, GWL_EXSTYLE, (GetWindowLongA(hwnd, GWL_EXSTYLE) & ~WS_EX_APPWINDOW)) + + if needs_show: + if not needs_focus: + # try showing without activating + try: return f.Show(True, False) + except Exception: pass + + f.Show(True) + +def GetOnTaskbar(f): + return not FRAME_NO_TASKBAR & f.WindowStyle + +wx.TopLevelWindow.OnTaskbar = property(GetOnTaskbar, SetOnTaskbar) + +import cgui + +wx.TopLevelWindow.Visible = property(cgui.WindowVisible) + +FullscreenApp = cgui.FullscreenApp + +def FullscreenAppLog(): + try: + from gui.native.win import winfullscreen + except ImportError: + traceback.print_exc_once() + else: + if winfullscreen.enabled: + try: + return log.debug('fullscreen (notification state) is %s ', winfullscreen.last_state) + except Exception: + traceback.print_exc() + winfullscreen.enabled = False + log.info('fullscreen was computed') + +def createEmail(mailto): + return os.startfile(mailto) + +def GetProcessDefaultLayout(): + from ctypes import windll, byref + from ctypes.wintypes import DWORD + + layout = DWORD() + windll.user32.GetProcessDefaultLayout(byref(layout)) + return layout.value + +WS_EX_LAYOUTRTL = 0x400000 + +def mirror(win): + """Flips a window's RTL setting.""" + + hwnd = win.Handle + new_style = GetWindowLong(hwnd, GWL_EXSTYLE) ^ WS_EX_LAYOUTRTL + return SetWindowLong(hwnd, GWL_EXSTYLE, new_style) diff --git a/digsby/src/gui/native/win/winpaths.py b/digsby/src/gui/native/win/winpaths.py new file mode 100644 index 0000000..2734306 --- /dev/null +++ b/digsby/src/gui/native/win/winpaths.py @@ -0,0 +1,9 @@ +''' + +Patches wx.StandardPaths to include extra Windows specific methods. + +''' + +import wx + + diff --git a/digsby/src/gui/native/win/winsysinfo.py b/digsby/src/gui/native/win/winsysinfo.py new file mode 100644 index 0000000..4afc759 --- /dev/null +++ b/digsby/src/gui/native/win/winsysinfo.py @@ -0,0 +1,97 @@ +import os +import ctypes +from ctypes import c_ulong, c_ulonglong, c_void_p, byref, POINTER, windll +from ctypes.wintypes import DWORD, WORD + +kernel32 = windll.kernel32 +DWORD_PTR = POINTER(DWORD) + +class SYSTEM_INFO(ctypes.Structure): + _fields_ =[ + ('dwOemId', DWORD), + ('dwPageSize', DWORD), + ('lpMinimumApplicationAddress', c_void_p), + ('lpMaximumApplicationAddress', c_void_p), + ('dwActiveProcessorMask', DWORD_PTR), + ('dwNumberOfProcessors', DWORD), + ('dwProcessorType', DWORD), + ('dwAllocationGranularity', DWORD), + ('wProcessorLevel', WORD), + ('wProcessorRevision', WORD), + ] + +class MEMORYSTATUS(ctypes.Structure): + _fields_ = [ + ('dwLength', c_ulong), + ('dwMemoryLoad', c_ulong), + ('dwTotalPhys', c_ulong), + ('dwAvailPhys', c_ulong), + ('dwTotalPageFile', c_ulong), + ('dwAvailPageFile', c_ulong), + ('dwTotalVirtual', c_ulong), + ('dwAvailVirtual', c_ulong) + ] + +DWORDLONG = c_ulonglong + +class MEMORYSTATUSEX(ctypes.Structure): + _fields_ = [ + ('dwLength', DWORD), + ('dwMemoryLoad', DWORD), + ('ullTotalPhys', DWORDLONG), + ('ullAvailPhys', DWORDLONG), + ('ullTotalPageFile', DWORDLONG), + ('ullAvailPageFile', DWORDLONG), + ('ullTotalVirtual', DWORDLONG), + ('ullAvailVirtual', DWORDLONG), + ('ullAvailExtendedVirtual', DWORDLONG) + ] + +try: + GetSystemInfo = kernel32.GetSystemInfo +except ImportError: + pass +else: + def get_num_processors(): + info = SYSTEM_INFO() + GetSystemInfo(byref(info)) + return info.dwNumberOfProcessors + +class SystemInformation(object): + + def _ram(self): + kernel32 = ctypes.windll.kernel32 + + memoryStatus = MEMORYSTATUS() + memoryStatus.dwLength = ctypes.sizeof(MEMORYSTATUS) + kernel32.GlobalMemoryStatus(ctypes.byref(memoryStatus)) + + ret = dict((k, getattr(memoryStatus, k)) + for k, _type in MEMORYSTATUS._fields_) + ret.update(self._ramex()) + return ret + + def _ramex(self): + kernel32 = ctypes.windll.kernel32 + + memoryStatus = MEMORYSTATUSEX() + memoryStatus.dwLength = ctypes.sizeof(MEMORYSTATUSEX) + kernel32.GlobalMemoryStatusEx(ctypes.byref(memoryStatus)) + + return dict((k, getattr(memoryStatus, k)) + for k, _type in MEMORYSTATUSEX._fields_) + + def _disk_c(self): + drive = unicode(os.getenv("SystemDrive")) + freeuser = ctypes.c_int64() + total = ctypes.c_int64() + free = ctypes.c_int64() + ctypes.windll.kernel32.GetDiskFreeSpaceExW(drive, + ctypes.byref(freeuser), + ctypes.byref(total), + ctypes.byref(free)) + d = dict(drive=drive, + freeuser = freeuser.value, + total = total.value, + free = free.value) + return d diff --git a/digsby/src/gui/native/win/winutil.py b/digsby/src/gui/native/win/winutil.py new file mode 100644 index 0000000..36019b0 --- /dev/null +++ b/digsby/src/gui/native/win/winutil.py @@ -0,0 +1,151 @@ +import wx +import ctypes.wintypes +from gui.native.win.winconstants import WM_USER +from ctypes.wintypes import UINT, HWND, DWORD +from util.ffi import Struct +from util import memoize + +def _win_id_gen(): + i = 100 + while True: + i += 1 + yield WM_USER + i + +_win_id_gen = _win_id_gen() + +def win_id(): + return _win_id_gen.next() + + +class WinStruct(Struct): + 'A struct that calculates its own cbSize for you.' + + cbSize = property(lambda self: len(self), lambda self, val: None) + +class FLASHWINFO(WinStruct): + _fields_ = [('cbSize', UINT), + ('hwnd', HWND), + ('dwFlags', DWORD), + ('uCount', UINT), + ('dwTimeout', DWORD)] + +@memoize +def is_vista(): + 'Returns True if the system is running Windows Vista or higher.' + + import wx + return 'wxMSW' in wx.PlatformInfo and hasattr(ctypes.windll, 'dwmapi') + +def disable_callback_filter(): + ''' + Exceptions in WM_PAINT (and other messages...?) on 64-bit versions of Windows + are silently ignored. It's a known issues and there's a hotfix here: + + http://support.microsoft.com/kb/976038 + + If that hotfix is installed, kernel32.dll exports two extra functions that + fix the behavior at runtime. This function will use those functions to make + exceptions go to the debugger and return True. + + If the hotfix isn't installed, this function does nothing and returns + False. + ''' + + from ctypes import byref + k = ctypes.windll.kernel32 + + PROCESS_CALLBACK_FILTER_ENABLED = 0x1 + + dwflags = DWORD() + + try: + GetProcessUserModeExceptionPolicy = k.GetProcessUserModeExceptionPolicy + SetProcessUserModeExceptionPolicy = k.SetProcessUserModeExceptionPolicy + except AttributeError: + pass # hotfix not installed + else: + if GetProcessUserModeExceptionPolicy(byref(dwflags)): + return SetProcessUserModeExceptionPolicy(dwflags.value & ~PROCESS_CALLBACK_FILTER_ENABLED) + + return False + + +get_glass_color = None + +if 'wxMSW' in wx.PlatformInfo: + from ctypes import c_uint32 + + class DWMCOLORIZATIONPARAMS(ctypes.Structure): + _fields_ = [ + ('ColorizationColor', c_uint32), + ('ColorizationAfterglow', c_uint32), + ('ColorizationColorBalance', c_uint32), + ('ColorizationAfterglowBalance', c_uint32), + ('ColorizationBlurBalance', c_uint32), + ('ColorizationGlassReflectionIntensity', c_uint32), + ('ColorizationOpaqueBlend', c_uint32), + ] + + p = DWMCOLORIZATIONPARAMS() + + try: + DwmGetColorizationParameters = ctypes.windll.dwmapi[127] # ordinal 127 is the unexported function + except Exception: + pass + else: + disable_gcp = False + def _get_glass_color(active=False): + global disable_gcp + + VISTA_GLASS_COLOR = wx.Color(189, 211, 239) + + if disable_gcp: + return VISTA_GLASS_COLOR + + try: + DwmGetColorizationParameters(ctypes.byref(p)) + except ValueError: + # function is undocumented and has a different signature on Vista...just return bluish + # if so + # TODO: this value is also accessible in the registry--maybe that's a safer way to do this? + disable_gcp = True + return VISTA_GLASS_COLOR + else: + extra_alpha_percent = p.ColorizationColorBalance/64.0 if active else 0 + return alpha_blend_on_white(p.ColorizationColor, extra_alpha_percent) + + get_glass_color = _get_glass_color + + def alpha_blend_on_white(c, extra_alpha_percent=0): + '''where c is a ARGB packed int, returns a wxColor with 255 alpha of the + resulting color if you had blitted c onto pure white.''' + + r, g, b = (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF + a = ((c >> 24) & 0xff)/255.0 + extra_alpha_percent + white = (1-a)*255 + + return wx.Color(white + a*r, white + a*g, white + a*b) + +try: + IsThemeActive = ctypes.windll.uxtheme.IsThemeActive +except Exception: + IsThemeActive = lambda: False + +def rgbdword_to_color(c): + return wx.Color((c & 0xFF), + (c & 0xFF00) >> 8, + (c & 0xFF0000) >> 16) + +def _get_active_caption_color(active): + COLOR_ACTIVECAPTION = 2 + return rgbdword_to_color(ctypes.windll.user32.GetSysColor(COLOR_ACTIVECAPTION)) + +def get_frame_color(active): + if IsThemeActive() and get_glass_color is not None: + return get_glass_color(active) + else: + if IsThemeActive(): + return _get_active_caption_color(active) + else: + return wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + diff --git a/digsby/src/gui/notifications/__init__.py b/digsby/src/gui/notifications/__init__.py new file mode 100644 index 0000000..ae76ae2 --- /dev/null +++ b/digsby/src/gui/notifications/__init__.py @@ -0,0 +1,23 @@ +from __future__ import with_statement + +from gui import skin +from util import odict, odict_from_dictlist +import syck + +def underscores_to_dots(d): + ''' + YAML syntax does really allow for + + contact.away: stuff + + so we have + + contact_away: stuff + + instead. Turns a dictionary from the second to the first. + ''' + + for key in d.keys()[:]: + if key and '_' in key: + d[key.replace('_','.')] = d.pop(key) + diff --git a/digsby/src/gui/notifications/notificationlist.py b/digsby/src/gui/notifications/notificationlist.py new file mode 100644 index 0000000..8415fc1 --- /dev/null +++ b/digsby/src/gui/notifications/notificationlist.py @@ -0,0 +1,361 @@ +''' + +a simplified notification list editor + +''' + +from __future__ import with_statement + +import wx +from wx import Rect, Point, ALIGN_CENTER_VERTICAL, Brush, \ + TRANSPARENT_PEN, WHITE_BRUSH, CONTROL_CHECKED +from operator import attrgetter +GetSysColor = wx.SystemSettings.GetColour + +from gui import skin +from gui.skin.skinobjects import Margins +from gui.vlist.skinvlist import SkinVListBox +from gui.toolbox import AutoDC +from gui.anylists import bgcolors +from cgui import SimplePanel + +from config import platformName +from logging import getLogger; log = getLogger('notificationlist') + +_hdralign = wx.ALIGN_BOTTOM | wx.ALIGN_CENTER_HORIZONTAL + +# values for positioning the header labels +W = 45 +H = 20 if platformName != 'mac' else 15 + +class NotifyPanel(SimplePanel): + def __init__(self, parent): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + + s = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(s) + + self.list = NotifyView(self) + + s.AddSpacer((1, H)) + s.Add(self.list, 1, wx.EXPAND| wx.ALL & ~wx.TOP,1) + + self.Bind(wx.EVT_PAINT, self.__paint) + + f = self.Font + f.SetWeight(wx.FONTWEIGHT_BOLD) + if platformName == 'mac': + f.SetPointSize(11) + self.Font = f + + self.Bind(wx.EVT_MOUSEWHEEL, lambda e: self.SetFocus()) + + def __paint(self, e): + dc = AutoDC(self) + dc.Font = self.Font + rect = self.list.ClientRect + + if platformName != 'mac': + dc.Pen = TRANSPARENT_PEN + dc.Brush = WHITE_BRUSH + dc.DrawRectangle(0, 0, self.ClientRect.width, H) + + r1 = Rect( rect.Right - W * 2, rect.y, W, H ) + r2 = Rect( rect.Right - W, rect.y, W, H ) + r3 = Rect(*self.list.Rect) + r3.Inflate(1,1) + + dc.DrawLabel(_('Sound'), r1, _hdralign) + dc.DrawLabel(_('Popup'), r2, _hdralign) + + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(wx.Pen(wx.Colour(213,213,213))) + dc.DrawRectangleRect(r3) + + NotifyView = property(attrgetter('list')) + +class NotifyView(SkinVListBox): + + def __init__(self, parent): + SkinVListBox.__init__(self, parent) + self.InitDefaults() + self.BindEvents() + + self._hovered = -1 + + def GetHovered(self): + return self._hovered + + def SetHovered(self,i): + n = self._hovered + + if i != n: + self._hovered = i + + if n != -1: + self.RefreshLine(n) + if i != -1: + self.RefreshLine(i) + + Hovered = property(GetHovered,SetHovered) + + def InitDefaults(self): + self.IconSize = 32 + self.margins = Margins((3, 3)) + self.padding = Point(5, 5) + self.CheckBoxSize = 16 + + self.SetNotificationInfo({}) + self.SetUserNotifications({}) + + self.UpdateSkin() + + def BindEvents(self): + Bind = self.Bind + Bind(wx.EVT_LEFT_DOWN, self.__leftdown) + Bind(wx.EVT_MOTION, self.OnMotion) + + + def OnMotion(self,event): + rect = wx.RectS(self.ClientSize) + wap = wx.FindWindowAtPointer() + mp = event.Position + + if not rect.Contains(mp) or wap != self: + while self.HasCapture(): + self.ReleaseMouse() + + self.Hovered = -1 + return + + elif not self.HasCapture(): + self.CaptureMouse() + + self.Hovered = self.HitTest(mp) + + def OnDrawItem(self, dc, rect, n): + + # setup + + ninfo = self.NotificationInfo + rect = rect.AddMargins(self.margins) + + key = ninfo._keys[n] + topic, info = key, ninfo[key] + + title = info['description'] + icon = None + icon_topic = topic + while icon is None: + icon = self.icons.get(icon_topic, None) + + if '.' in icon_topic: + icon_topic = icon_topic.split('.')[0] + else: + break + + if isinstance(icon, basestring): + icon = skin.get(icon) + iconsize = self.IconSize +# selected = self.IsSelected(n) + + # draw + + dc.Font = self.Font + dc.SetTextForeground(wx.BLACK) + + if icon is not None: + dc.DrawBitmap(icon.Resized(iconsize), self.padding.x, rect.y) + + rect.Subtract(left = iconsize + self.padding.x * 2) + + dc.DrawLabel(title, rect, alignment = ALIGN_CENTER_VERTICAL) + + self.DrawChecks(dc, n, rect) + + def DrawChecks(self, dc, idx, rect): + r = wx.RendererNative.Get() + + for i, cbrect in enumerate(self.CheckBoxRects(rect)): + flags = CONTROL_CHECKED if self.Checked(idx, i) else 0 + r.DrawCheckBox(self, dc, cbrect, flags) + + def CheckBoxRects(self, rect): + cbsize = self.CheckBoxSize + + num_checkboxes = 2 + rects = [] + + y = rect.VCenterH(cbsize) + + for i in reversed(range(num_checkboxes)): + r1 = Rect(rect.Right - W * (i+1), y, W, rect.height) + r1.x, r1.y = r1.HCenterW(cbsize) + 4, r1.y + r1.width = r1.height = cbsize + rects.append(r1) + + return rects + + + def Checked(self, idx, checkbox): + reactions = self.UserNotifications.get(None, {}).get(self.TopicForIndex(idx), {}) + r = self.checkBoxReactions[checkbox] + + return any(r == d.get('reaction', None) for d in reactions) + + def CheckBoxHitTest(self, pos): + rect = Rect(0, 0, self.ClientSize.width, self.OnMeasureItem(0)) + rect = rect.AddMargins(self.margins) + + for i, r in enumerate(self.CheckBoxRects(rect)): + if r.Contains(pos): + return i + + return -1 # not found + + def TopicForIndex(self, idx): + return self.NotificationInfo._keys[idx] + + def OnDrawBackground(self, dc, rect, n): + s = self.IsSelected(n) + h = self.Hovered + + dc.Brush = Brush(GetSysColor(wx.SYS_COLOUR_HIGHLIGHT) if s else wx.Color(220, 220, 220) if h==n else bgcolors[n%len(bgcolors)]) + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangle(*rect) + + def OnMeasureItem(self, n): + return self.margins.top + self.margins.bottom + self.IconSize + + def UpdateSkin(self): + ICON_SKINPATH = 'AppDefaults.notificationicons' + ni = self.NotificationInfo + appdefaults = skin.get(ICON_SKINPATH) + + all_icons = {} + for key in appdefaults.keys(): + all_icons[key] = skin.get(ICON_SKINPATH + '.' + key) + + for k in ni: + iconpath = ni[k].get('notification_icon', None) + if iconpath is not None: + all_icons[k] = iconpath + + self.icons = all_icons + # + + def __leftdown(self, e): + p = e.Position + i = self.HitTest(p) + + if i != -1: + cb_i = self.CheckBoxHitTest(self.ToItemCoords(i, p)) + if cb_i != -1: + return self.CheckBoxClicked(i, cb_i) + + #effectively disables selection +# e.Skip() + + checkBoxReactions = [ + 'Sound', + 'Popup', + ] + + def CheckBoxClicked(self, item, checkbox): + uinfo = self.UserNotifications + topic = self.TopicForIndex(item) + reaction = self.checkBoxReactions[checkbox] + + if not None in uinfo: uinfo[None] = {} + reactions = uinfo[None].setdefault(topic, []) + + # + # remove + # + foundOne = False + for rdict in list(reactions): + if rdict['reaction'] == reaction: + foundOne = True + log.info('removing %r', rdict) + reactions.remove(rdict) + # + # add + # + if not foundOne: + # adding one + newEntry = self.ReactionEntry(topic, reaction) + log.info('adding %r', newEntry) + reactions.append(newEntry) + + import hooks + hooks.notify('digsby.notifications.changed') + + self.RefreshLine(item) + + def ReactionEntry(self, topic, reaction): + return {'reaction': reaction} + + def ToItemCoords(self, item, pos): + return Point(pos.x, pos.y % self.OnMeasureItem(0)) + + # NotificationInfo: the information in notificationview.yaml describing + # possible notification topics + + def SetNotificationInfo(self, notification_info): + ninfo = type(notification_info)() + + # Filter out items that have "gui: no" + for key, value in notification_info.iteritems(): + if value.get('gui', True): + ninfo[key] = value + + self._ninfo = ninfo + self.SetItemCount(len(self._ninfo)) + + def GetNotificationInfo(self): + return self._ninfo + + NotificationInfo = property(GetNotificationInfo, SetNotificationInfo) + + # UserNotifications: the notifications blob stored in digsbyprofile + + def SetUserNotifications(self, usernots): + self._usernots = usernots + self.RefreshAll() + + def GetUserNotifications(self): + return self._usernots + + UserNotifications = property(GetUserNotifications, SetUserNotifications) + + + +def main(): + from common.notifications import get_notification_info + from common.notifications import Popup + + userInfo = {None: {'contact.available': [{'reaction': Popup}], + 'contact.away': [{'reaction': Popup}], + 'email.new': [{'reaction': Popup}], + 'error': [{'reaction': Popup}], + 'facebook.alert': [{'reaction': Popup}], + 'filetransfer.request': [{'reaction': Popup}], + 'message.received.background': [{'reaction': Popup}], + 'myspace.alert': [{'reaction': Popup}]}} + + from tests.testapp import testapp + app = testapp('../../..') + + f = wx.Frame(None, -1, 'notifications gui test') + p = NotifyPanel(f) + n = p.NotifyView + + n.NotificationInfo = get_notification_info() + n.UserNotifications = userInfo + + f.Show() + + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/notifications/sounds.py b/digsby/src/gui/notifications/sounds.py new file mode 100644 index 0000000..0ace4dd --- /dev/null +++ b/digsby/src/gui/notifications/sounds.py @@ -0,0 +1,76 @@ +from __future__ import with_statement +from gui import skin +import syck + +class SoundsetException(Exception): pass + +class Soundset(dict): + pass + +DESC_FILENAME = 'sounds.yaml' + +from gui.notifications import underscores_to_dots + +def fix_paths(d, soundset_dir): + for k, v in d.iteritems(): + d[k] = (soundset_dir / v).abspath() + +_soundset = None + +def active_soundset(): + global _soundset + + if _soundset is not None: + return _soundset + + _soundset = load_soundset('default') + return _soundset + +def load_soundset(name): + set_name = set_dir = None + + for set_name, set_dir in list_soundsets(): + if set_name == name: + found = True + break + else: + found = False + + if set_dir and found: + soundset_yaml = set_dir / DESC_FILENAME + else: + soundset_yaml = None + + if soundset_yaml is None or not soundset_yaml.isfile(): + raise SoundsetException('soundset %r is missing %r' % (name, DESC_FILENAME)) + + # load from YAML file in res dir + with file(soundset_yaml, 'r') as f: + soundset = syck.load(f) + + if soundset is None: + raise SoundsetException('soundset %r is empty' % name) + + # fix contact_signoff -> contact.signoff + underscores_to_dots(soundset) + + # turn relative paths in YAML to actual paths + fix_paths(soundset, set_dir) + + return Soundset(soundset) + +def list_soundsets(): + import stdpaths + paths = [ + stdpaths.userdata / 'sounds', + stdpaths.config / 'sounds', + skin.resourcedir() / 'sounds', + ] + + soundsets = [] + for pth in paths: + for dir in pth.dirs(): + if (dir / DESC_FILENAME).isfile(): + soundsets.append((dir.name, dir)) + + return soundsets diff --git a/digsby/src/gui/notificationview.py b/digsby/src/gui/notificationview.py new file mode 100644 index 0000000..74c3f1b --- /dev/null +++ b/digsby/src/gui/notificationview.py @@ -0,0 +1,566 @@ +''' +GUI for editing notification events. + +NotificationView is the main TreeList control which lists notifications. +''' + +from __future__ import with_statement + +import wx +from copy import deepcopy +from util import dictadd +from gui.treelist import TreeList, TreeListModel +from gui.toolbox import build_button_sizer +from util import autoassign, pythonize, dictsub, InstanceTracker +from gui import skin +from common import profile +from common.notifications import reactions +from gettext import ngettext + +from logging import getLogger; log = getLogger('notificationview'); info = log.info + +from common.notifications import get_notification_info + +from wx import Rect +#notification_info = get_notification_info() + +def get_notifications(testdict = {}): + "Returns user notifications in the form {'dotted.name': Reaction}" + + try: + from common import profile + return profile.notifications + except ImportError: + return testdict + + +class EventPanel(wx.Panel): + 'Popup for editing a single event.' + + def __init__(self, parent, event_desc, event = None): + wx.Panel.__init__(self, parent) + if not isinstance(event_desc, basestring): + raise TypeError + + # "who" is a string describing who's doing the eventings + aContact = _('a Contact') + if event is None: + replace_str = aContact + else: + if 'contact' in event: + replace_str = event['contact'].split('/')[-1] + else: + replace_str = aContact + + self.event_desc = _('When ') + event_desc.replace(_('Contact'), replace_str) + + self.action_ctrls = {} + + self.construct(event) + self.layout() + + # If we're editing an existing event, populate + if event: self.populate(event) + + def info(self): + details = dict() + for name, (ctrl, ex) in self.action_ctrls.iteritems(): + details[name] = getattr(ctrl, ex) + + details['reaction'] = reactions[self.action_choice.GetSelection()] + return details + + def populate(self, event): + with self.Frozen(): + for name, (c, ex) in self.action_ctrls.iteritems(): + setattr(c, ex, event[name]) + + def construct(self, event = None): + 'Constructs common event GUI elements.' + + # Top header: the description of the event. + self.event_header = Text(self, self.event_desc) + font = self.event_header.Font + font.SetWeight(wx.FONTWEIGHT_BOLD) + self.event_header.Font = font + + self.action_text = Text(self, _('&Action:')) + self.action_choice = self.build_action_choice(event) + self.action_choice.Bind(wx.EVT_CHOICE, self.on_action_choice) + + # Add and Cancel buttons + self.ok = wx.Button(self, wx.ID_OK, _('&OK')) + self.ok.SetDefault() + self.cancel = wx.Button(self, wx.ID_CANCEL, _('&Cancel')) + + def on_action_choice(self, e = None): + ''' + Invoked when the "action" choice is changed. Controls under the choice + need to be updated.' + ''' + + i = self.action_choice.GetSelection() + if i == -1: return + + with self.Frozen(): + f = self.flex + + [c.Window.Destroy() for c in f.Children + if c.Window not in (self.action_text, self.action_choice)] + + self.action_ctrls.clear() + + f.Clear() # does not Destroy child windows + f.Add(self.action_text, 0, wx.EXPAND | wx.ALL, 10) + f.Add(self.action_choice, 0, wx.EXPAND | wx.ALL, 10) + + # Call build_controls_XXX where XXX is the pythonized name of + # the Reaction class (see notifications.py for possible + # Reactions) + name = pythonize(reactions[i].__name__) + getattr(self, 'build_controls_%s' % name, + lambda s: None)(self.flex) + + self.Layout() + + def layout(self): + 'Initial sizer layout.' + + self.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + + # The flex grid sizer is where controls are subbed in and out depending + # on the action. + self.flex = f = wx.FlexGridSizer(-1, 2, 6, 6) + + # define some shortcuts for adding controls to the flex grid for + # consistent spacing + + AddFlexControl = lambda c: self.flex.Add(c, 0, wx.EXPAND | wx.ALL, 5) + + def AddHeader(header): + AddFlexControl(wx.StaticText(self, -1, _(header))) + + def AddControl(name, ctrl, data_extractor = 'Value'): + self.action_ctrls[name] = (ctrl, data_extractor) + AddFlexControl(ctrl) + + self.flex.AddHeader = AddHeader + self.flex.AddControl = AddControl + + self.on_action_choice() + + sz.Add(self.event_header, 0, wx.EXPAND | wx.ALL, 10) + sz.Add(f, 1, wx.EXPAND) + + # OK/Cancel + sz.Add(build_button_sizer(save=self.ok, cancel=self.cancel), + 0, wx.EXPAND | wx.SOUTH | wx.EAST | wx.WEST, 4) + + def build_action_choice(self, event = None): + 'Returns the action choice control.' + + choice_ctrl = wx.Choice(self, -1, choices = [r.__doc__ for r in reactions]) + + i = 0 if event is None else reactions.index(event['reaction']) + choice_ctrl.SetSelection(i) + return choice_ctrl + + ## + ## these methods define which controls appear for each action type. + ## if the method is missing, no controls are used. + ## + + def build_controls_alert(self, sz): + sz.AddHeader('&Alert Text:') + txt = wx.TextCtrl(self, -1, '', style = wx.TE_MULTILINE) + txt.Size = (300, 300) + sz.AddControl('msg', txt) + + def build_controls_sound(self, sz): + sz.AddHeader('&Sound:') + sz.AddControl('filename', wx.FilePickerCtrl(self, -1), 'Path') + + def build_controls_showcontactlist(self, sz): + sz.AddHeader('&Seconds:') + sz.AddControl('duration', wx.TextCtrl(self, -1, '')) + + def build_controls_startcommand(self, sz): + sz.AddHeader('&Command:') + txt = wx.TextCtrl(self, -1, '') + sz.AddControl('path', txt) + +class EventDialog(wx.Dialog): + 'Shows the event edit panel.' + + def __init__(self, parent, event_desc, event = None): + + if event is not None: + title = _('Edit Event for "{desc}"').format(desc=event_desc) + else: + title = _('Add Event for "{desc}"').format(desc=event_desc) + + wx.Dialog.__init__(self, parent, title = title) + self.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + + self.eventpanel = EventPanel(self, event_desc, event = event) + self.info = self.eventpanel.info + + sz.Add(self.eventpanel, 1, wx.EXPAND) + self.Bind(wx.EVT_BUTTON, self.on_button) + + def on_button(self, e): + self.EndModal(e.EventObject.Id) + + +class NotificationEntry(object): + 'A row in the Notifications VListBox.' + + def OnDrawBackground(self, dc, rect, n, selected): + from gui.anylists import bgcolors + + # alternate colors + bgcol = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) if selected else bgcolors[n % len(bgcolors)] + dc.Brush = wx.Brush(bgcol) + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangle(*rect) + +class EventEntry(NotificationEntry): + def __init__(self, event): + if not isinstance(event, dict) and 'reaction' in event: + raise TypeError('event should be a dict, got a %s' % type(event)) + + self.event = event + self.name = str(id(self.event)) + + def __repr__(self): + event = self.event + reac = event['reaction'] + + # If this event dict has a contact associated with it, add + # "for CONTACT" on the end of the description string. + if 'contact' in event: + contact_str = _(' for %s') % event['contact'].split('/')[-1] + else: + contact_str = '' + + try: + desc = reac.desc + except AttributeError: + desc = reac + #raise AssertionError('Reaction objects must have a desc attribute') + + + if callable(desc): + return desc(self.event) + contact_str + else: + try: + return (desc % self.event) + contact_str + except: + log.warning('illegal format string or mismatched') + log.warning('event: %r', self.event) + log.warning(desc) + return self.event['reaction'].__doc__ + contact_str + + def __hash__(self): + return id(self) + + + + +class ActionEntry(NotificationEntry, list): + def __init__(self, topic, events = [], description = '', icon_skinpath = None): + autoassign(self, locals()) + self.name = topic + if self.icon_skinpath is None: + self.icon_skinpath = 'AppDefaults.Notificationicons.%s' % self.topic.split('.', 1)[0] + + list.__init__(self, [EventEntry(event) for event in events]) + + def __repr__(self): + return self.description + + def __hash__(self): + return hash(unicode(self.topic) + ''.join(unicode(c.__hash__()) for c in self)) + + def expandable(self): + return bool(len(self)) + + @property + def icon(self): + icon = skin.get(self.icon_skinpath, None) + if icon is not None: + if len(self) > 0: + return icon.Resized(32) + else: + return icon.Resized(20) + + @property + def ItemHeight(self): + if len(self) > 0: + return 40 + else: + return 25 + + + + +class NotificationView(TreeList, InstanceTracker): + 'Displays notifications for all contacts or for an individual contact in a tree list.' + + def __init__(self, parent, notifications, for_contact = None, plusminus_buttons = None): + TreeList.__init__(self, parent, TreeListModel(), style = wx.NO_BORDER) + InstanceTracker.track(self) + + if for_contact is not None and not hasattr(for_contact, 'idstr'): + raise TypeError('for_contact must be a contact, a metacontact, or None') + + # if for_contact is None we'll show both global ztnotifications and + # notifications for each contact + info('showing notifications for %s', 'everybody' if for_contact is None else for_contact.idstr()) + self.for_contact = for_contact + self.notifications = notifications + + if plusminus_buttons is not None: + plus, minus = plusminus_buttons + plus.Bind(wx.EVT_BUTTON, self.on_add) + minus.Bind(wx.EVT_BUTTON, self.on_remove) + #TODO: disable add/deleted when no not. is selected + + self.update_view() + + self.BBind(LEFT_DCLICK = self.on_add_or_edit, + LEFT_DOWN = self.on_left_down, + LISTBOX = self.on_selection) + + self.SetDrawCallback(self.cskin_drawcallback) + + def cskin_drawcallback(self, dc, rect, n): + self.OnDrawBackground(dc, Rect(*rect), n) + self.OnDrawItem(dc, Rect(*rect), n) + #self.OnDrawSeparator(dc, Rect(*rect), n) + + + def on_selection(self, e): + 'When an event is selected, "preview" it.' + + sel = self.GetSelection() + if sel != -1: + obj = self[sel] + if isinstance(obj, EventEntry): + obj.event['reaction'](**dictsub(obj.event, {'reaction': None})).preview() + + def on_left_down(self, e): + if e.Position.x < 20: + i, percent = self.hit_test_ex(e.Position) + if i != -1: self.toggle_expand(self.model[i]) + e.Skip(False) + else: + e.Skip(True) + + @classmethod + def update_all(cls): + cls.CallAll(NotificationView.update_view) + + def update_view(self): + + #from common.notifications import set_active + #set_active(get_notifications()) + + notifications, contact = self.notifications, self.for_contact + if contact is not None: + contact = contact.idstr() + contactobj = contact + + showing_contacts = True + already_added, root = [], [] + + gnots = {} if None not in notifications else notifications[None] + + for topic, notif in get_notification_info().iteritems(): + events, desc = [], notif['description'] + + if contact is None: + if topic in gnots: + p = deepcopy(gnots[topic]) + events.extend(p) + + # Grab events for contacts (maybe) + if showing_contacts: + for subcontact in (set(notifications.keys()) - set([None])): + if subcontact in self.notifications: + if topic in self.notifications[subcontact]: + c_events = deepcopy(notifications[subcontact][topic]) + if c_events: + events.extend([(dictadd({'contact': subcontact}, e)) + for e in c_events]) + + if events: + already_added += [topic] + root.append(ActionEntry(topic, events, + description = desc, + icon_skinpath = notif.get('notification_icon', None))) + else: + if contact in notifications: + if topic in notifications[contact]: + events = deepcopy(notifications[contact][topic]) + if events: + already_added += [topic] + root.append(ActionEntry(topic, events, + description = desc, + icon_skinpath = notif.get('notification_icon', None))) + + + + # Add event categories with no events set + for topic, notif in get_notification_info().iteritems(): + if not topic in already_added: + root.append(ActionEntry(topic, + description = notif['description'], + icon_skinpath = notif.get('notification_icon', None))) + self.set_root(root) + + def on_add_or_edit(self, e = None): + i = self.GetSelection() + if i == -1: return + obj = self[i] + if isinstance(obj, ActionEntry): + return self.on_add(e) + else: + parent = self.model.parent_of(obj) + description = get_notification_info()[parent.topic]['description'] + diag = EventDialog(self, description, event = obj.event) + res = diag.ShowModal() + if res == wx.ID_OK: + contact = None if self.for_contact is None else self.for_contact.idstr() + if 'contact' in obj.event: + contact = obj.event['contact'] + nots = self.notifications[contact] + notif = nots[parent.topic] + notif.insert(notif.index(obj.event), deepcopy(diag.info())) + notif.remove(obj.event) + + self.CallAll(NotificationView.update_view) + + diag.Destroy() + + def on_add(self, e): + + i = self.GetSelection() + if i != -1: + if isinstance(self[i], EventEntry): + i = self.model.index_of(self.GetParent(self[i])) + + topic = self[i].topic + assert isinstance(topic, basestring), 'topic is a %s' % type(topic) + + # Display the event edit dialog. + diag = EventDialog(self, get_notification_info()[topic]['description']) + res = diag.ShowModal() + if res == wx.ID_OK: + contact = self.for_contact.idstr() if self.for_contact else None + + if contact not in self.notifications: + self.notifications[contact] = dict() + + if topic not in self.notifications[contact]: + self.notifications[contact][topic] = list() + + self.notifications[contact][topic].append(deepcopy(diag.info())) + + diag.Destroy() + + self.CallAll(NotificationView.update_view) + + def on_remove(self, e): + i = self.GetSelection() + if i == -1: return + + obj = self[i] + + if isinstance(obj, ActionEntry): + return + + contact = obj.event.get('contact', # buddy specific + None if self.for_contact is None # global + else self.for_contact.idstr()) # buddy specific window + + if 'contact' in obj.event: + contact = obj.event['contact'] + + if isinstance(obj, EventEntry): + parent = self.GetParent(obj) + parent.remove(obj) + + topic = self.notifications[contact][parent.topic] + topic.remove(dictsub(obj.event, {'contact':None})) + if len(topic) == 0: + del self.notifications[contact][parent.topic] + self.CallAll(NotificationView.update_view) + + elif isinstance(obj, NotificationEntry): + n = len(obj.events) + msg = ngettext(_('Are you sure you want to remove %d event?') % n, + _('Are you sure you want to remove %d events?') % n, + n) + if wx.YES == wx.MessageBox(msg, _('Remove Events: {notification_desc}').format(notification_desc=obj.description), + style = wx.YES_NO): + del self.notifications[contact][obj.topic] + self.update_view() + +class ContactAlertDialog(wx.Dialog): + def __init__(self, parent, contact): + + from contacts import MetaContact + name = contact.alias if isinstance(contact, MetaContact) else contact.name + + wx.Dialog.__init__(self, parent, title = _('Editing Alerts for {name}').format(name=name)) + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + + self.contact = contact + + add_button = wx.Button(self, -1, '+') + add_button.Size = (15, 15) + remove_button = wx.Button(self, -1, '-') + remove_button.Size = (15, 15) + h = wx.BoxSizer(wx.HORIZONTAL) + h.AddStretchSpacer(1) + h.Add(add_button) + h.Add(remove_button) + s.Add(h, 0, wx.EXPAND | wx.ALL, 3) + + self.notview = NotificationView(self, deepcopy(dict(get_notifications())), contact, + (add_button, remove_button)) + + s.Add(self.notview, 1, wx.EXPAND) + + # Save and cancel buttons + save = wx.Button(self, wx.ID_SAVE, _('&Save')) + cancel = wx.Button(self, wx.ID_CANCEL, _('&Cancel')) + button_sizer = build_button_sizer(save, cancel) + + save.Bind(wx.EVT_BUTTON, lambda e: self.EndModal(wx.ID_SAVE)) + + s.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 7) + + + def Prompt(self, callback): + if wx.ID_SAVE == self.ShowModal(): + callback(notifications = self.notview.notifications) + self.notview.update_all() + +def edit_contact_alerts(parent, contact): + diag = ContactAlertDialog(parent, contact) + + def callback(notifications): + idstr = contact.idstr() + profile.notifications[idstr] = notifications[idstr] + + diag.Prompt(callback) + + + + +Text = lambda self, txt: wx.StaticText(self, -1, txt) + + + diff --git a/digsby/src/gui/pastbrowser.py b/digsby/src/gui/pastbrowser.py new file mode 100644 index 0000000..5654b57 --- /dev/null +++ b/digsby/src/gui/pastbrowser.py @@ -0,0 +1,746 @@ +''' +View past chats +''' +from __future__ import with_statement +from gui.uberwidgets.umenu import UMenu +import wx +from gui import skin +from gui.uberwidgets.PrefPanel import PrefPanel +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.simplemenu import SimpleMenuItem +from gui.uberwidgets.UberButton import UberButton +from gui.toolbox import persist_window_pos, snap_pref, update_tooltip +from gui.browser.webkit import WebKitWindow +from cgui import SimplePanel + +from util.primitives.mapping import Storage +from digsby_chatlogs.interfaces import IAliasProvider +from common import profile +#from protocols import AdaptationFailure #need to fix package names to get to this +from common import logger + +import logging; log = logging.getLogger('pastbrowser') + +from wx import ALL, TOP, BOTTOM, LEFT, \ + EXPAND, \ + ALIGN_CENTER_VERTICAL, ALIGN_LEFT, ALIGN_RIGHT, \ + VERTICAL, HORIZONTAL, \ + Color, BoxSizer, Brush, Rect +from gui.toolbox.scrolling import WheelScrollCtrlZoomMixin, \ + WheelShiftScrollFastMixin,\ + ScrollWinMixin, FrozenLoopScrollMixin + +TOPLESS = ALL & ~TOP + +BUTTON_SKIN = 'AppDefaults.PastButton' + +from wx import DateTimeFromDMY + +from wx.calendar import CAL_SUNDAY_FIRST, CAL_SEQUENTIAL_MONTH_SELECTION, CAL_SHOW_HOLIDAYS, \ + EVT_CALENDAR_MONTH, EVT_CALENDAR_YEAR, EVT_CALENDAR_SEL_CHANGED, \ + CalendarCtrl, CalendarDateAttr + +SUBHTML = """ +$('.buddy').each(function(){ + var bname = %s; + var balias = %s; + if ($(this).html() == bname) { + $(this).html(balias); + $(this).attr('title', bname); + } +});""" + +wx.DateTime.__hash__ = lambda dt: hash(dt.GetTicks()) + +def logpath_for_date(buddy, date): + year = '%d' % int(date.GetYear()) + month = '%02d' % (date.GetMonth() + 1) + day = '%02d' % int(date.GetDay()) + + return buddy.dir / ''.join([year, '-', month, '-', day, '.html']) + + +def _group_chat_icon(): + return skin.get('actionsbar.icons.roomlist') + +def MakeAccountItems(): + logdir = logger.get_default_logging_dir() + + protodirs = logdir.dirs() + + accts = {} + for protodir in protodirs: + accts[protodir.name] = [dir.name for dir in protodir.dirs()] + + items = [] + for proto in sorted(accts): + try: + protoicon = skin.get('serviceicons.'+proto,None).ResizedSmaller(16) + except Exception: + continue + + for acct in accts[proto]: + items.append(SimpleMenuItem([protoicon,acct], id = {'logdir':logdir, + 'proto':proto, + 'acct': acct})) + + # group chat at bottom + icon = _group_chat_icon().Resized(16) + items.append(SimpleMenuItem([icon, _('Group Chats')], id = { + 'proto': 'group', + })) + + return items + +def GetGroupChats(): + return list(profile.logger.walk_group_chats()) + +def GetBuddies(id): + acctdir = id['logdir'] / id['proto'] / id['acct'] + buddies = [] + aliases = IAliasProvider(profile()) + for bdir in acctdir.dirs(): + try: + name, service = bdir.name.rsplit('_', 1) + serviceicon = skin.get('serviceicons.'+service,None).ResizedSmaller(16) + except Exception: + continue + buddies.append(Storage(icon = serviceicon, + name = name, + alias = aliases.get_alias(name.lower(), service, acctdir.parent.name) or name, + protocol = acctdir.parent.name, + service = service, + dir = bdir)) + + buddies.sort(key=lambda s: s.alias.lower()) + + return buddies + +def GetDates(logdir): + dates = [] + append = dates.append + + for logfile in logdir.files(): + datestring = logfile.name + + try: + y, m, d = datestring[:datestring.rfind('.')].split('-') + y, m, d = int(y), int(m)-1, int(d) + except Exception: + pass # ignore non-logfiles... + else: + append(DateTimeFromDMY(d, m, y)) + + return sorted(dates) + + + +bgcolors = [ + wx.Color(239, 239, 239), + wx.Color(255, 255, 255), +] + +hovbgcolor = wx.Color(220, 220, 220) + +def SelectAccountByDir(acctdir,combo): + for item in combo: + id = item.id + if acctdir.lower() == (id['logdir'] / id['proto'] / id['acct']).lower(): + combo.SetSelection(combo.GetIndex(item)) + return True + + return False + +class GroupChatRenderer(object): + def get_icon(self, chat): + return (skin.get('serviceicons.' + chat['service'], None) or _group_chat_icon()).ResizedSmaller(16) + + def get_label(self, chat): + time = chat['time'].strftime('%I:%M %p') + roomname = chat.get('roomname', None) + if roomname: + return '%s - %s' % (time, roomname) + else: + return time + + get_tooltip = get_label + +class BuddyRenderer(object): + def get_icon(self, buddy): + return buddy.icon + + def get_label(self, buddy): + return buddy.alias + + def get_tooltip(self, buddy): + return buddy.name + + +class ListOBuddies(wx.VListBox): + def __init__(self,parent): + wx.VListBox.__init__(self,parent,-1) + + self.buddies = [] + + self.Hovered = -1 + + self.SetItemCount(0) + + self.itemheight = max(self.Font.Height, 16)+10 + + self.date = None + + Bind = self.Bind + Bind(wx.EVT_MOTION, self.OnMotion) + + def OnMotion(self,event): + rect = self.ClientRect + wap = wx.FindWindowAtPointer() + mp = event.Position + hit = self.HitTest(mp) + + if not rect.Contains(mp) or not wap == self: + while self.HasCapture(): + self.ReleaseMouse() + + self.Hovered = -1 + self.SetToolTip(None) + self.Refresh() + return + + elif not self.HasCapture(): + self.CaptureMouse() + + self.Hovered = hit + + # buddy screenname tooltips + tooltip = self.renderer.get_tooltip(self.buddies[hit]) if hit != -1 else None + update_tooltip(self, tooltip) + + self.Refresh() + + + def OnDrawBackground(self,dc,rect,n): + if self.Selection == n: + dc.Brush = Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)) + + #wx.VListBox.OnDrawBackground(self,dc,rect,n) + #return + else: + dc.Brush = Brush(hovbgcolor if self.Hovered == n else bgcolors[n % len(bgcolors)]) + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(rect) + + def OnDrawItem(self, dc, rect, n): + + dc.Font=self.Font + dc.TextForeground = wx.WHITE if self.Selection==n else wx.BLACK + + + x = rect.x + 3 + y = rect.y + rect.height//2 - 8 + + item = self.buddies[n] + dc.DrawBitmap(self.renderer.get_icon(item),x,y,True) + + textrect = Rect(x + 16 + 3, rect.y, rect.Width - x - 38, rect.Height) + + dc.DrawLabel(self.renderer.get_label(item), textrect, ALIGN_CENTER_VERTICAL|ALIGN_LEFT) + + def OnMeasureItem(self,n): + return self.itemheight + + def SetList(self, buddies, renderer): + self.buddies = buddies + self.renderer = renderer + + self.SetItemCount(len(self.buddies)) + + if self.ItemCount: + self.SetSelection(0) + + wx.CallAfter(self.Refresh) + + @property + def SelectedBuddy(self): + return self.buddies[self.Selection] + + SelectedItem = SelectedBuddy + + def SelectConversation(self, convo): + for i, b in enumerate(self.buddies): + if b['file'] == profile.logger.get_path_for_chat(convo): + return self.SetSelection(i) + + def SelectBuddy(self, service, name): + + found = -1 + for i, buddy in enumerate(self.buddies): + if buddy.name == name and buddy.service == service: + found = i + break + + self.Selection = found + + if found != -1: + self.ProcessEvent(wx.CommandEvent(wx.wxEVT_COMMAND_LISTBOX_SELECTED)) + self.Refresh() + return True + else: + self.Refresh() + return False + +class FindBar(wx.Panel): + def __init__(self, parent, viewer): + wx.Panel.__init__(self, parent) + + + self.viewer = viewer + + # construct + find_label = wx.StaticText(self, -1, _('Find')) + self.TextControl = find_input = wx.TextCtrl(self, -1, size = (180, -1), style = wx.TE_PROCESS_ENTER) + + nextbutton = self.nextbutton = UberButton(self, label = _('Next'), skin = 'AppDefaults.PastButton') + prevbutton = self.prevbutton = UberButton(self, label = _('Prev'), skin = 'AppDefaults.PastButton') + + # layout + sz = self.Sizer = wx.BoxSizer(wx.HORIZONTAL) + sz.AddMany([(find_label, 0, TOP | BOTTOM | LEFT | ALIGN_CENTER_VERTICAL | ALIGN_RIGHT, 6), + (find_input, 0, TOP | BOTTOM | LEFT | ALIGN_CENTER_VERTICAL , 6), + (nextbutton, 0, TOP | BOTTOM | LEFT | ALIGN_CENTER_VERTICAL | EXPAND , 6), + (prevbutton, 0, TOP | BOTTOM | LEFT | ALIGN_CENTER_VERTICAL | EXPAND , 6)]) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + Bind = self.Bind + Bind(wx.EVT_PAINT, self.OnPaint) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + fiBind = find_input.Bind + fiBind(wx.EVT_TEXT, self.OnFindText) + fiBind(wx.EVT_KEY_DOWN, self.OnFindKey) + + nextbutton.Bind(wx.EVT_BUTTON, self.OnFindText) + prevbutton.Bind(wx.EVT_BUTTON, lambda e: self.OnFindText(e,False)) + + self.EnableFindButtons('') + + def OnFindKey(self, e): + c = e.KeyCode + if c in (wx.WXK_RETURN, wx.WXK_F3) or (c == ord('G') and e.GetModifiers() in (wx.MOD_CMD,wx.MOD_CMD+wx.MOD_SHIFT)): + self.OnFindText(forward = not e.ShiftDown()) + self.TextControl.SetFocus() + else: + e.Skip() + + + def OnFindText(self, e = None, forward = True): + value = self.TextControl.Value + self.EnableFindButtons(value) + self.viewer.FindString(self.TextControl.Value, + forward, + False, + True, + True) + + def EnableFindButtons(self, textctrl_value): + buttons_enabled = bool(textctrl_value) + + self.nextbutton.Enable(buttons_enabled) + self.prevbutton.Enable(buttons_enabled) + + + def OnPaint(self,event): + rect = wx.RectS(self.ClientSize) + + dc = wx.AutoBufferedPaintDC(self) + + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.Pen(wx.Color(213,213,213)) + + dc.DrawRectangleRect(rect) + + +class PastBrowserWebkitWindow(FrozenLoopScrollMixin, + ScrollWinMixin, + WheelShiftScrollFastMixin, + WheelScrollCtrlZoomMixin, + WebKitWindow): + pass + +class PastBrowserPanel(SimplePanel): + ''' + Holds the various sections of the past chat browser UI + ''' + + def __init__(self,parent): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + + self.BackgroundColour = wx.WHITE + + self.Sizer = wx.BoxSizer(HORIZONTAL) + sz = wx.BoxSizer(HORIZONTAL) + + self.Sizer.Add(sz,1,EXPAND|ALL,3) + + leftcol = wx.BoxSizer(VERTICAL) + + acctcombo = self.acctcombo = UberCombo(self, value='',skinkey='AppDefaults.PrefCombo') + acctcont = PrefPanel(self,acctcombo,_('Account')) + + + leftcol.Add(acctcont,0,EXPAND|ALL,3) + + buddylist = self.buddylist = ListOBuddies(self) + self.buddies_panel = buddycont = PrefPanel(self, buddylist, _('Buddy')) + leftcol.Add(buddycont, 1, EXPAND | TOPLESS, 3) + + style = wx.NO_BORDER | CAL_SUNDAY_FIRST | CAL_SEQUENTIAL_MONTH_SELECTION | CAL_SHOW_HOLIDAYS + cal = self.cal = CalendarCtrl(self, -1, wx.DateTime.Now(), wx.DefaultPosition, wx.DefaultSize, style) + + cal.SetForegroundColour(wx.Color(160, 160, 160)) + cal.SetHolidayColours(wx.BLACK, wx.WHITE) + cal.SetHeaderColours(Color(160, 160, 160), Color(239, 239, 239)) + + calcont = PrefPanel(self,cal,_('Date')) + leftcol.Add(calcont, 0, EXPAND | TOPLESS, 3) + + sz.Add(leftcol, 0, EXPAND) + + viewpanel = wx.Panel(self) + + viewer = self.viewer = PastBrowserWebkitWindow(viewpanel) +# viewer.SetMouseWheelZooms(True) + finder = self.finder = FindBar(viewpanel,viewer) + + menu = UMenu(viewer) + menu.AddItem(_('Copy'), id = wx.ID_COPY, callback = lambda *a: viewer.Copy()) + + viewer.BindWheel(self) + viewer.BindScrollWin(self) + viewer.Bind(wx.EVT_CONTEXT_MENU, + lambda e: (menu.GetItemById(wx.ID_COPY).Enable(viewer.CanCopy()), + menu.PopupMenu(event = e))) + + viewer.Bind(wx.EVT_KEY_DOWN,self.OnKeyDown) + finder.TextControl.Bind(wx.EVT_KEY_DOWN,self.OnKeyDown) + + nav = BoxSizer(wx.HORIZONTAL) + + prev = self.prev = UberButton(viewpanel, label = '<-', skin = BUTTON_SKIN) + next = self.next = UberButton(viewpanel, label = '->', skin = BUTTON_SKIN) + + datelabel = wx.StaticText(viewpanel, -1, style = wx.ALIGN_CENTER| wx.ST_NO_AUTORESIZE) + datelabel.SetMinSize((140, -1)) + + + prev.Bind(wx.EVT_BUTTON, lambda e: self.Flip(-1)) + next.Bind(wx.EVT_BUTTON, lambda e: self.Flip( 1)) + + nav.AddStretchSpacer(1) + nav.AddMany([(prev, 0, wx.EXPAND | wx.ALIGN_CENTER), + (datelabel, 0, wx.EXPAND | wx.ALIGN_CENTER), + (next, 0, wx.EXPAND | wx.ALIGN_CENTER)]) + nav.AddStretchSpacer(1) + + + viewpanel.Sizer = wx.BoxSizer(wx.VERTICAL) + viewpanel.Sizer.AddMany([ (nav, 0, EXPAND), + (viewer, 1, EXPAND), + (finder, 0, EXPAND) ]) + + sz.Add(PrefPanel(self, viewpanel, _('Conversation Log')), 1, EXPAND | ALL, 3) + + Bind = self.Bind + Bind(wx.EVT_PAINT, self.OnPaint) + + def OnAcct(*a): + ''' + Handle selection of a new account from the Account drop down + ''' + if self.GroupChatsSelected(): + from collections import defaultdict + self.groupchats = defaultdict(list) + for g in GetGroupChats(): + d = g['time'] + key = DateTimeFromDMY(d.day, d.month-1, d.year) + self.groupchats[key].append(g) + + #dates = sorted((g['date'], g) for g in + self.dates = sorted(self.groupchats.keys()) + UpdateCal() + self.buddies_panel.SetTitle(_('Chats')) + else: + buddylist.SetList(GetBuddies(acctcombo.Value.id), BuddyRenderer()) + OnBuddy() + self.buddies_panel.SetTitle(_('Buddy')) + + def OnBuddy(*a): + ''' + Handels selection of a buddy from the buddy pannel + ''' + + if not self.GroupChatsSelected(): + self.dates = GetDates(buddylist.SelectedBuddy.dir) + UpdateCal() + else: + ViewLogForFile(buddylist.SelectedItem['file'], do_aliases=False) + + def UpdateCal(): + ''' + Switches the date to the last date conversed with the selected budy + ''' + self.next.Enable(True) + self.prev.Enable(True) + if self.dates: + self.cal.Date = self.dates[-1] + + self.cal.Enable(True) + OnCalChange() + + def OnCalChange(*a): + ''' + Update the Calendar UI to a new date + ''' + caldate = cal.Date + + currentyear = caldate.GetYear() + currentmonth = caldate.GetMonth() + relevantdates = frozenset(date.GetDay() for date in self.dates + if date.GetYear() == currentyear and + date.GetMonth() == currentmonth and date.GetDay()) + SetHoliday, SetAttr = cal.SetHoliday, cal.SetAttr + + for i in xrange(1, 32): + if i in relevantdates: + SetHoliday(i) + else: + SetAttr(i, CalendarDateAttr(Color(160,160,160))) + + + OnDayChange() + + self.OnCalChange = OnCalChange + + def ViewLogForDay(date): + ''' + Load the log file for the specified date for the currently selected buddy + ''' + logpath = logpath_for_date(buddylist.SelectedBuddy, date) + ViewLogForFile(logpath) + + def ViewLogForFile(logpath, do_aliases=True): + ''' + Update the log viewer with the file specified + ''' + with viewer.Frozen(): + viewer.SetPageSource(logpath.text('utf-8', 'replace'), logpath.url()) + viewer.RunScript('window.scroll(0, 0);') + + if do_aliases: + substitue_aliases() + + import hooks + hooks.notify('digsby.statistics.logviewer.log_viewed') + + def substitue_aliases(): + ''' + Swap out buddy names with their allies + ''' + import gui + with open(gui.skin.resourcedir() / 'html' / 'jquery-1.3.2.js', 'rb') as f: + viewer.RunScript(f.read()) + buddy = buddylist.SelectedBuddy + aliases = IAliasProvider(profile()) + import simplejson as json + names = set(json.loads(viewer.RunScript("var foo = []; $('.buddy').each(function(){foo.push($(this).html())}); JSON.stringify(foo);"))) + for name in names: + alias = aliases.get_alias(name, buddy.service, buddy.protocol) or name + viewer.RunScript(SUBHTML % (json.dumps(name), json.dumps(alias))) + + def OnDayChange(*a): + ''' + Show the log for the day selected in the clander + ''' + date = cal.Date + self.date = date + + datelabel.SetLabel(date.FormatDate()) + + if cal.GetAttr(date.GetDay()).IsHoliday(): + if self.GroupChatsSelected(): + chats = sorted(self.groupchats[date], key=lambda g: g['time'], reverse=True) + buddylist.SetList(chats, GroupChatRenderer()) + if chats: + ViewLogForFile(chats[0]['file'], do_aliases=False) + else: + ViewLogForDay(date) + else: + year = str(date.GetYear()) + month = date.GetMonth() + month = wx.DateTime.GetMonthName(int(month)) + day = str(date.GetDay()) + + specific_day_string = _('{month}, {day}, {year}').format(month=month, day=day, year=year) + + if self.GroupChatsSelected(): + msg = _("There are no chat logs for {specific_day_string}.").format(specific_day_string=specific_day_string) + else: + msg = _("There are no chat logs for {specific_day_string} with {name}.").format(specific_day_string=specific_day_string, name=buddylist.SelectedBuddy.name) + + viewer.SetPageSource(msg, 'file:///C:/') + + viewer.SetFocus() + + + wx.CallAfter(cal.Refresh) + self.OnDayChange = OnDayChange + + acctcombo.SetCallbacks(value = OnAcct) + buddylist.Bind(wx.EVT_LISTBOX, OnBuddy) + + cBind = cal.Bind + cBind(EVT_CALENDAR_YEAR, OnCalChange) + cBind(EVT_CALENDAR_MONTH, OnCalChange) + cBind(EVT_CALENDAR_SEL_CHANGED, OnDayChange) + + acctcombo.SetItems(MakeAccountItems(), 0) + + def GroupChatsSelected(self): + obj = self.acctcombo.Value.id + return obj['proto'] == 'group' + + def OnKeyDown(self,event): + + KeyCode = event.KeyCode + + if KeyCode == wx.WXK_PAGEDOWN: + self.viewer.ScrollPages(1) + elif KeyCode == wx.WXK_PAGEUP: + self.viewer.ScrollPages(-1) + elif KeyCode == ord('C') and event.Modifiers == wx.MOD_CONTROL: + # do not remove until webkit key handling is fixed + self.viewer.Copy() + else: + event.Skip() + + + def Flip(self, delta): + dates = self.dates + date = self.date + + try: + foundIndex = dates.index(date) + delta + except ValueError: + alldates = [DateTimeFromDMY(1, 1, 1900)] + dates + [DateTimeFromDMY(20, 10, 3000)] + foundIndex = len(dates) - 1 + for i in xrange(1, len(alldates)): + if date.IsStrictlyBetween(alldates[i-1], alldates[i]): + foundIndex = i-2 if delta < 0 else i + break + + foundIndex = max(0, min(len(dates) - 1, foundIndex)) + + self.cal.SetDate(dates[foundIndex]) + self.OnCalChange() + + def OnPaint(self,event): + rect = wx.RectS(self.Size) + dc = wx.PaintDC(self) + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + + dc.DrawRectangleRect(rect) + + def OpenBuddyHistory(self, acctdir, acct, name, service): + + if not SelectAccountByDir(acctdir, self.acctcombo): + self.next.Enable(False) + self.prev.Enable(False) + self.cal.Enable(False) + return self.NoHistory(name, service, acct) + + + + def AfterOpenBuddyHistory(): + if not self.buddylist.SelectBuddy(service, name): + self.next.Enable(False) + self.prev.Enable(False) + self.cal.Enable(False) + return self.NoHistory(name, service, acct) + + self.next.Enable(True) + self.prev.Enable(True) + self.cal.Enable(True) + self.Flip(1) + + wx.CallAfter(AfterOpenBuddyHistory) + + def OpenConversation(self, convo): + if not convo.ischat: + wx.CallAfter(convo.Buddy.view_past_chats, convo.protocol.account) + + self.acctcombo.SetSelection(self.acctcombo.GetCount()-1) + + @wx.CallAfter + def after(): + self.buddylist.SelectConversation(convo) + + def NoHistory(self, name, service, acct): + self.viewer.SetPageSource(_("There is no chat history for {name} on {service} with {acct}.").format( + name=name, + service=service, + acct=acct),'file:///C:/') + self.viewer.SetFocus() + + +class PastBrowser(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, _('Past Chat Browser'), name = 'Past Chat Browser') + self.SetFrameIcon(skin.get('appdefaults.taskbaricon')) + + sz = self.Sizer = wx.BoxSizer(VERTICAL) + self.pastbrowser = PastBrowserPanel(self) + sz.Add(self.pastbrowser, 1, EXPAND) + + persist_window_pos(self, defaultPos = wx.Point(150, 150), defaultSize = wx.Size(700, 500)) + snap_pref(self) + + @classmethod + def MakeOrShow(cls): + win = cls.RaiseExisting() + + if win is None: + win = cls() + wx.CallAfter(win.Show) + + return win + + @classmethod + def MakeOrShowAndSelect(cls, buddydir): + acctdir = buddydir.parent + acct = acctdir.name + name,service = buddydir.name.rsplit('_',1) + + win = cls.MakeOrShow() + win.pastbrowser.OpenBuddyHistory(acctdir, acct, name, service) + + @classmethod + def MakeOrShowAndSelectConvo(cls, convo): + win = cls.MakeOrShow() + win.pastbrowser.OpenConversation(convo) + +if __name__=='__main__': + + from tests.testapp import testapp + a = testapp(username = 'aaron') + + from path import path + + dir = path('C:\\Users\\Aaron\\Documents\\Digsby Logs\\aaron\\digsby\\aaron@digsby.org\\mike@digsby.org_digsby') + PastBrowser.MakeOrShowAndSelect(dir) + +# f = PastBrowser() +# f.Show() + a.MainLoop() diff --git a/digsby/src/gui/pref/__init__.py b/digsby/src/gui/pref/__init__.py new file mode 100644 index 0000000..0c611a5 --- /dev/null +++ b/digsby/src/gui/pref/__init__.py @@ -0,0 +1 @@ +'Preferences GUI' \ No newline at end of file diff --git a/digsby/src/gui/pref/gensearchable.py b/digsby/src/gui/pref/gensearchable.py new file mode 100644 index 0000000..9357f66 --- /dev/null +++ b/digsby/src/gui/pref/gensearchable.py @@ -0,0 +1,89 @@ +''' + +generates something like: + + module = [ + "list", + "of internationalizble", + ] + + module2 = [ + "strings", + ] + +requires: + + "igettext" UNIX tool + +use like + + $ python gensearchable.py > prefsearchable.py + +''' + +import sys +from glob import glob +from util.primitives.misc import backtick + +from contextlib import contextmanager + +@contextmanager +def stdout_as_stderr(): + from io import BytesIO + old_stdout, sys.stdout = sys.stdout, BytesIO() + try: + yield + finally: + stdout, sys.stdout = sys.stdout, old_stdout + sys.stderr.write(stdout.getvalue()) + +def find_xgettext(): + # finding xgettext may write to stdout...redirect to stderr so this tool's + # stdout remains clean + with stdout_as_stderr(): + try: + from i18n.generate import check_for_i18n_tools + except ImportError: + import sys + import os.path + thisdir = os.path.dirname(os.path.abspath(__file__)) + path = os.path.normpath(os.path.join(thisdir, '../../..')) + + sys.path.append(path) + from i18n.generate import check_for_i18n_tools + + check_for_i18n_tools() + + +def main(): + find_xgettext() + print HEADER + + module_names = [] + for f in glob('pg_*.py'): + module_name = f[3:-3] # get pg_MODULENAME.py + module_names.append(module_name) + + print '%s = [' % module_name + + for line in backtick('xgettext -D. -LPython --no-wrap --no-location -o- ' + f).split('\n'): + if line.startswith('msgid '): + line = line.strip() + + # accumulate all lines like: + # msgid "some \"quoted\" string" + s = line[7:-1] + if s: print ' "' + s.replace('&', '') + '",' + + print ']' + print + +HEADER = '''\ +# +# DO NOT EDIT +# +# this file is generated by running gensearchable.py in this directory +# +''' + +if __name__ == '__main__': main() diff --git a/digsby/src/gui/pref/iconeditor.py b/digsby/src/gui/pref/iconeditor.py new file mode 100644 index 0000000..0528872 --- /dev/null +++ b/digsby/src/gui/pref/iconeditor.py @@ -0,0 +1,614 @@ +''' +Chooses, sizes, and moves a buddy icon. +''' +from __future__ import with_statement +from __future__ import division +if __name__ == '__main__': + __builtins__._ = lambda s: s +from traceback import print_exc +from util import debug_property +import config + +#TODO: +# -resizing big images vs small images +# an alternate way to get all the WX compatible extensions: +# [a.GetExtension() for a in wx.Image.GetHandlers()] + +IMAGE_WILDCARD = ('Image files (*.bmp;*.gif;*.jpeg;*.jpg;*.png)|*.bmp;*.gif;*.jpeg;*.jpg;*.png|' + 'All files (*.*)|*.*') + +from wx import BufferedPaintDC, ImageFromString, Size, Point, \ + GetMousePosition, Bitmap, ImageFromBitmap, BitmapFromImage + +from gui.windowfx import ApplySmokeAndMirrors +import wx +from PIL import Image +from cStringIO import StringIO +from util.primitives.error_handling import traceguard +from util.primitives.funcs import do +from path import path +from logging import getLogger; log = getLogger('iconeditor'); info = log.info + +MAX_BICON_SIZE = (128, 128) + +SCREEN_BUTTON_LABEL_SCREEN = _('Scree&n') +SCREEN_BUTTON_LABEL_CAPTURE = _('Capt&ure') + +class IconEditor(wx.Dialog): + ''' + An icon editor dialog. + + >>> i = IconEditor(frame) + >>> if i.ShowModal() == wx.ID_OK: print 'Got a bitmap!', i.Bitmap + ''' + + def_size = (295, 415) + + def __init__(self, parent, icon = None): + wx.Dialog.__init__(self, parent, -1, _('Set Buddy Icon'), size = self.def_size) + + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + + self.ReturnCode = wx.ID_CANCEL + + self.iconedit = IconEditPanel(self, icon) + s.Add(self.iconedit, 1, wx.EXPAND) + self.Fit() + + @property + def Bytes(self): + return self.iconedit.Bytes + + @property + def ImageChanged(self): + return self.iconedit.dragger._image_changed + + def Prompt(self, okCallback): + self.okCallback = okCallback + self.Show(True) + + def onclose(e): + self.Show(False) + if self.ReturnCode in (wx.ID_SAVE, wx.ID_OK): + self.okCallback() + e.Skip(True) + wx.CallAfter(self.Destroy) + + + + self.Bind(wx.EVT_CLOSE, onclose) + + + +class IconDragger(wx.Panel): + ''' + The panel showing the image and a square represnting the selection for + the buddy icon. + ''' + + normal = 48 # The size of a "normal" icon. (Won't be resized.) + maxpick = 128 # The biggest size you can pick for an icon. + + def __init__(self, parent, slider): + wx.Panel.__init__(self, parent, style = wx.SUNKEN_BORDER) + Bind = self.Bind + Bind(wx.EVT_PAINT, self.on_paint) + Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down) + Bind(wx.EVT_RIGHT_DOWN, self.on_right_down) + Bind(wx.EVT_LEFT_UP, self.on_mouse_up) + Bind(wx.EVT_MOTION, self.on_motion) + Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.on_mouse_capture_lost) + Bind(wx.EVT_KEY_DOWN, self.on_key) + + self.bitmap = wx.TransparentBitmap(32, 32) + self.adjustment = (0,0) + self.dragging = False + self.picksize = wx.Size(self.normal, self.normal) + + # The size slider + self.slider = slider + slider.Bind(wx.EVT_SLIDER, lambda e: self.on_slider(e, high_quality = False)) + slider.Bind(wx.EVT_SCROLL_CHANGED, lambda e: wx.CallAfter(lambda e=e: self.on_slider(e, high_quality = True))) + + self._image_changed = False + self._image_bytes = None + + sz = (260, 260) + self.SetMinSize(sz) + self.SetMaxSize(sz) + self.SetSize(sz) + + from gui.toolbox.dnd import SimpleDropTarget + self.SetDropTarget(SimpleDropTarget(self)) + self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + + def OnDropFiles(self, filelist): + for filepath in (path(f) for f in filelist): + if filepath.isfile(): + with traceguard: + img = Image.open(filepath) + img.load() + return self.set_image(img.WX) + + log.warning('no images in %r', filelist) + + def OnDropBitmap(self, bitmap): + self.set_image(bitmap) + + def on_slider(self, e = None, high_quality = True): + n = self.resize_func(self.slider.Value, high_quality = high_quality) + self.picksize = Size(n, n) + self.Refresh() + + if e is not None: + self._image_changed = True + self._image_bytes = None + + def _resized_high(self, width, height): + return self.image.PIL.Resized((width, height)) + + def _resized_low(self, width, height): + return self.image.Scale(width, height) + + def resize_image(self, dim, high_quality = True): + "Resizes the image so that it's largest dimension is dim." + + # Preserve aspect ratio + w, h = self.image.Width, self.image.Height + s = self._resized_high if high_quality else self._resized_low + + new_image = s(w/h*dim, dim) if w < h else s(dim, h/w*dim) + + self.bitmap = new_image.WXB + + def on_paint(self, e = None): + dc = BufferedPaintDC(self) + + # Draw empty background + b = wx.WHITE_BRUSH#Brush(Color(40, 40, 40)) @UndefinedVariable + #b.SetStipple(self.stipple) + dc.Brush = b + + dc.Pen = wx.TRANSPARENT_PEN #@UndefinedVariable + dc.DrawRectangle(0,0,*self.Size) + + # Draw the bitmap + self.paint_bitmap(dc) + + # Draw the picker square + dc.Brush = wx.TRANSPARENT_BRUSH #@UndefinedVariable + dc.Pen = wx.RED_PEN #@UndefinedVariable + + x,y = self.middle + x -= self.picksize[0] / 2 + y -= self.picksize[1] / 2 + dc.SetLogicalFunction(wx.INVERT) + dc.DrawRectangle(x,y,self.picksize[0],self.picksize[1]) + + def paint_bitmap(self, dc, pickerPos = None): + ''' + If dc is None, returns a PIL image + ''' + + bw, bh = self.bitmap.Width, self.bitmap.Height + + # get the 0,0 point of the image if centered + imagecenter = Point(self.Rect.Width / 2, self.Rect.Height / 2) - Point(bw/2, bh/2) + + # adjust away from center + xy = imagecenter + (self.adjustment[0] * bw, self.adjustment[1] * bh) + + # adjust the offset by how far into the picture the selection is +# if pickerPos: +# xy[0] += pickerPos[0]; xy[1] += pickerPos[1] + + + if dc is not None: +# dc.SetBrush(wx.TRANSPARENT_BRUSH) #@UndefinedVariable +# dc.SetPen(wx.RED_PEN) #@UndefinedVariable + dc.DrawBitmap(self.bitmap, *xy) +# dc.DrawRectangleRect(cropRect) + else: + + + pickWidth = int(self.picksize[0]) + pickHeight = int(self.picksize[1]) + pickRect = wx.RectPS(pickerPos or ((self.Rect.width // 2 - self.picksize[0] // 2), (self.Rect.height // 2 - self.picksize[1] // 2)) , (pickWidth, pickHeight)) + pickRect.Position -= xy + imageRect = wx.RectPS((0, 0), (bw, bh)) + cropRect = imageRect.Intersect(pickRect) + + crop_box = (cropRect.left, cropRect.top, cropRect.right, cropRect.bottom) + + croppedImage = self.bitmap.PIL.crop(crop_box) +# croppedImage.Show("croppedImage") + + + offsetX = max(-(pickRect.x - cropRect.x), 0) + offsetY = max(-(pickRect.y - cropRect.y), 0) + if(offsetX or offsetY): + paddedImage = Image.new('RGBA', (pickRect.width, pickRect.height), (0,0,0,0)) + paddedImage.paste(croppedImage, (offsetX, offsetY)) + return paddedImage + + return croppedImage + + @property + def middle(self): + w, h = self.Size + return Point(w/2, h/2) + + @debug_property + def Bytes(self): + # Size of the icon picker + s = self.picksize + + # Size of the icon dragger area + r = self.Rect + + # position of the iconpicker in relation to the dragger area + pickerPos = (r.Width // 2 - s[0] // 2), (r.Height // 2 - s[1] // 2) + + # crop the image + img = self.paint_bitmap(None, pickerPos) + + # Save as PNG + imgFile = StringIO() + img.save(imgFile, 'PNG', optimize = True) + + return imgFile.getvalue() + + def on_mouse_capture_lost(self,event): + self.CancelDrag() + + def on_mouse_down(self, e): + e.Skip() + if e.LeftDown(): + self.startdrag = self.dragging = GetMousePosition() + self.CaptureMouse() + + def on_right_down(self,event): + if self.dragging: + self.CancelDrag() + else: + event.Skip() + + + def on_key(self,event): + isesc = event.KeyCode == wx.WXK_ESCAPE + if self.dragging and isesc: + self.CancelDrag() + elif not isesc: + event.Skip() + + def CancelDrag(self): + startdrag = self.startdrag + curdrag = self.dragging + self.on_mouse_up() + self.dragging = curdrag + self.on_motion(forcednewpos = startdrag) + self.dragging = False + + def on_motion(self, e=None, forcednewpos=None): + if self.dragging is not False: + newpos = forcednewpos or GetMousePosition() + delta = newpos - Point(*self.dragging) + diff = (float(delta.x) / self.bitmap.Width, + float(delta.y) / self.bitmap.Height) + self.adjustment = (self.adjustment[0] + diff[0], + self.adjustment[1] + diff[1]) + + self.dragging = newpos + self.Refresh() + self._image_changed = True + self._image_bytes = None + + def on_mouse_up(self, e=None): + if not e or e.LeftUp(): + self.startdrag = None + self.dragging = False + while self.HasCapture(): + self.ReleaseMouse() + + def set_image(self, image, first = False): + self.Parent.set_screen_button_mode() + + if isinstance(image, str): + log.info('set_image received bytes') + self._image_bytes = image + image = ImageFromString(image) + + if not first: + self._image_changed = True + + self._set_imageorbitmap(image) + + self.dim = max(self.image.Width, self.image.Height) + if self.dim > max(MAX_BICON_SIZE): + self._image_changed = True + + self.adjustment = (0,0) + + # first: is it the normal size? if so, disable the slider + if self.image.Width == self.normal and self.image.Height == self.normal: + self.slider.Enable(False) + self.picksize = Size(self.normal, self.normal) + else: + self.slider.Enable(True) + + if self.dim < self.normal: f = self.f1 # case 1: small + elif self.dim < self.maxpick: f = self.f2 # case 2: smaller than max + else: f = self.f3 # case 3: bigger than max + self.resize_func = f + + self.on_slider() + + self.Refresh() + + # The following functions are used to determine the image's size when from + # the slider value. The slider behavior is different for images of + # different sizes. + + def f1(self, val, high_quality = True): + 'The image is smaller than normal.' + + return self.normal - int((self.normal - self.dim) * (val / 100.0)) + + def f2(self, val, high_quality = True): + 'The image is bigger than the normal, but smaller than the max pick size.' + + return self.dim - (val/100.0)*(self.dim - self.normal) + + def f3(self, val, high_quality = True): + 'The image is really big.' + + mid = 50 # This could probably be chosen based on how "really big" + pickval = val - mid + if pickval < 0: pickval = 0 + + if val < mid: + dim = (self.dim - self.maxpick) * (val/mid) + self.maxpick + self.resize_image(dim, high_quality = high_quality) + else: + self.resize_image(max(self.image.Width, self.image.Height), high_quality = high_quality) + + # Picker size + return self.maxpick - pickval / mid * (self.maxpick - self.normal) + + def _set_imageorbitmap(self, image): + if isinstance(image, Bitmap): + self.image = ImageFromBitmap(image) + if not self.image.HasAlpha(): + self.image.InitAlpha() + + self.bitmap = image + + elif isinstance(image, wx.Image): + self.image = image + if not image.HasAlpha(): + image.InitAlpha() + self.bitmap = BitmapFromImage(self.image) + else: + raise TypeError + +class IconEditPanel(wx.Panel): + ''' + The panel holding buttons for file, webcam, clipboard, etc. + ''' + + def __init__(self, parent, bitmap = None, dialog_buttons = True): + wx.Panel.__init__(self, parent) + wx.InitAllImageHandlers() + self.construct_and_layout(dialog_buttons) + + # bind on_XXX to button XXX + for b in self.buttonnames: + self.buttonfor(b).Bind(wx.EVT_BUTTON, getattr(self, 'on_' + b)) + + if bitmap: + if not hasattr(bitmap, 'IsOk') or bitmap.IsOk(): + with traceguard: + self.set_image(bitmap, first = True) + + @property + def Bytes(self): + if self.dragger._image_bytes and not self.dragger._image_changed: + log.info('returning original image bytes') + return self.dragger._image_bytes + else: + log.info('returning new bytes from resized image') + return self.dragger.Bytes + + + # + # Button callbacks + # + + def on_file(self, e): + filediag = wx.FileDialog(wx.GetTopLevelParent(self), + _('Choose an icon'), wildcard = IMAGE_WILDCARD) + if filediag.ShowModal() == wx.ID_OK: + imgpath = path(filediag.GetPath()) + if imgpath.isfile(): + + try: + # make sure its a valid image. + Image.open(imgpath).load() + except: + msg = _('Not a valid image:') + + wx.MessageBox(u'{msg}\n\n"{imgpath}"'.format(msg=msg, imgpath=imgpath), + _('Invalid Image'), style = wx.ICON_ERROR, + parent = self) + else: + self.set_image(imgpath.bytes()) + + def on_webcam(self, e): + pass + + capture_related = ['save'] + + def set_image(self, img, *a, **k): + return self.dragger.set_image(img, *a, **k) + + def on_screen(self, e): + if self._screen_button_mode == 'capture': + return self.on_capture() + + self._set_screen_button_mode('capture') + self.buttonfor('screen').SetBold() + self.slider.Enable(False) + for b in self.capture_related: self.buttonfor(b).Enable(False) + cuthole(self.dragger) + + def on_capture(self): + self.slider.Enable(True) + dragger = self.dragger + self.Freeze() + + try: + if config.platform == 'win': + try: + from cgui import getScreenBitmap + except ImportError: + print_exc() + return + + bitmap = getScreenBitmap(wx.RectPS(dragger.ScreenRect.Position, dragger.ClientSize)) + else: + screendc = wx.ScreenDC() + bitmap = screendc.GetAsBitmap() + + if bitmap is not None: + self.set_image(wx.ImageFromBitmap(bitmap)) + + s = self.slider + s.Value = (s.Max - s.Min) / 2 + finally: + wx.CallAfter(self.ThawLater) + + def ThawLater(self): + try: + self.dragger.on_slider() + finally: + self.Thaw() + + def _set_screen_button_mode(self, mode): + assert mode in ('capture', 'screen') + self._screen_button_mode = mode + + if mode == 'capture': + label = SCREEN_BUTTON_LABEL_CAPTURE + else: + label = SCREEN_BUTTON_LABEL_SCREEN + + self.buttonfor('screen').Label = label + + def set_screen_button_mode(self): + b = self.buttonfor('screen') + if self._screen_button_mode == 'capture': + self._set_screen_button_mode('screen') + f = b.Font; f.SetWeight(wx.FONTWEIGHT_NORMAL); b.Font = f + do(self.buttonfor(b).Enable(True) for b in self.capture_related) + ApplySmokeAndMirrors(wx.GetTopLevelParent(self)) + + + def on_clipboard(self, e): + cb = wx.TheClipboard + if cb.IsSupported(wx.DataFormat(wx.DF_BITMAP)): + data = wx.BitmapDataObject() + cb.GetData(data) + self.set_image(data.Bitmap) + else: + wx.MessageBox('No image data found in clipboard.', + 'Icon From Clipboard') + + def on_save(self, e): + self.EndWithReturnCode(wx.ID_OK) + + def EndWithReturnCode(self, code): + win = wx.GetTopLevelParent(self) + + while self.dragger.HasCapture(): + self.dragger.ReleaseMouse() + + win.ReturnCode = code + win.Close() + + def on_clear(self, e): + self.set_image(wx.TransparentBitmap(100, 100)) + + def on_cancel(self, e): + self.EndWithReturnCode(wx.ID_CANCEL) + + def button_names(self, dialog_buttons): + names = [('file', _('&File')), + ('clipboard', _('Cli&pboard')), + ('screen', SCREEN_BUTTON_LABEL_SCREEN)] + + if dialog_buttons: + names += [ + ('save', _('&Save')), + ('clear', _('C&lear')), + ('cancel', _('&Cancel')) + ] + + return names + + def construct_and_layout(self, dialog_buttons): + # File, Webcam, Clipboard buttons + top = wx.StaticBoxSizer(wx.StaticBox(self, -1, 'Source'), wx.HORIZONTAL) + middle = wx.StaticBoxSizer(wx.StaticBox(self, -1, 'Preview'), wx.VERTICAL) + + names = self.button_names(dialog_buttons) + self.buttonnames = [name for name, guiname in names] + self.buttons = [wx.Button(self, -1, guiname) for name, guiname in names] + self._set_screen_button_mode('screen') + + top.AddMany([(b, 1, wx.EXPAND | wx.ALL, 3) for b in self.buttons[:3]]) + + self.slider = wx.Slider(self) + self.slider.Disable() + self.dragger = IconDragger(self, self.slider) + + # The interactive window image preview + middle.Add(self.dragger, 1, wx.EXPAND | wx.ALL) + middle.Add(self.slider, 0, wx.EXPAND | wx.ALL) + + + self.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + sz.AddMany([ (top, 0, wx.EXPAND | wx.ALL, 3), + (middle, 1, wx.EXPAND | wx.ALL, 3) ]) + + if dialog_buttons: + # Save, Clear, and Cancel buttons + bottom = wx.BoxSizer(wx.HORIZONTAL) + bottom.AddMany([(b, 1, wx.EXPAND | wx.ALL, 3) for b in self.buttons[3:]]) + sz.AddMany([(bottom, 0, wx.EXPAND | wx.ALL, 3)]) + + def buttonfor(self, s): + return self.buttons[self.buttonnames.index(s)] + +def cuthole(ctrl): + top = wx.GetTopLevelParent(ctrl) + x = ctrl.ScreenRect.X - top.ScreenRect.X + y = ctrl.ScreenRect.Y - top.ScreenRect.Y + w, h = ctrl.ClientSize + + winsz = top.Size + region = wx.Region(0, 0, winsz[0], y) + region.UnionRect((0, 0, x, winsz[1])) + region.UnionRect((x+w, y, winsz[0] - w - x, h)) + region.UnionRect((0, y + h, winsz[0], winsz[1] - h - y)) + + ApplySmokeAndMirrors(top, region) + +if __name__ == '__main__': # Tests the editor + _ = lambda s: s + from gui.toolbox import setuplogging; setuplogging() + app = wx.PySimpleApp() + IconEditor(None).ShowModal() + + + diff --git a/digsby/src/gui/pref/noobcontactlayoutpanel.py b/digsby/src/gui/pref/noobcontactlayoutpanel.py new file mode 100644 index 0000000..706d7cd --- /dev/null +++ b/digsby/src/gui/pref/noobcontactlayoutpanel.py @@ -0,0 +1,200 @@ +import wx +from cgui import SimplePanel +from gui.pref.prefcontrols import mark_pref,get_pref + +class NoobContactLayoutPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self,parent) + self.links = parent.links + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.grid = wx.FlexGridSizer(2,3) + self.grid.AddGrowableRow(1,1) + self.Sizer.Add(self.grid,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL) + + from gui import skin + g=skin.get + + icons = g('AppDefaults.ez.icons') + + items = self.items =[ + LayoutItem(self, icons.one, dict(show_extra = True, + extra_info = 'both', + + show_buddy_icon = True, + buddy_icon_pos = 'left', + buddy_icon_size = 32, + + show_status_icon = True, + status_icon_pos = 'bleft', + + show_service_icon = True, + service_icon_pos = 'bright' + )), + + LayoutItem(self, icons.two, dict(show_extra = True, + extra_info = 'both', + + show_buddy_icon = True, + buddy_icon_pos = 'right', + buddy_icon_size = 32, + + show_status_icon = True, + status_icon_pos = 'bleft', + + show_service_icon = True, + service_icon_pos = 'bright' + )), + + LayoutItem(self, icons.three, dict(show_extra = True, + extra_info = 'both', + + show_buddy_icon = True, + buddy_icon_pos = 'right', + buddy_icon_size = 32, + + show_status_icon = True, + status_icon_pos = 'left', + + show_service_icon = True, + service_icon_pos = 'bright' + )), + + LayoutItem(self, icons.four, dict(show_extra = True, + extra_info = 'idle', + + show_buddy_icon = False, + + show_status_icon = True, + status_icon_pos = 'right', + + show_service_icon = True, + service_icon_pos = 'left' + )), + + LayoutItem(self, icons.five, dict(show_extra = True, + extra_info = 'idle', + + show_buddy_icon = False, + + show_status_icon = True, + status_icon_pos = 'left', + + show_service_icon = True, + service_icon_pos = 'right' + )), + + LayoutItem(self, icons.six, dict(show_extra = True, + extra_info = 'idle', + + show_buddy_icon = False, + + show_status_icon = True, + status_icon_pos = 'left', + + show_service_icon = False, + )) + ] + + self.grid.AddMany([(item, 0) for item in items]) + self.selection = None + + for item in items: + for key in item.prefdict: + if item.prefdict[key] != get_pref('buddylist.layout.%s'%key): + break + else: + self.SetSelection(item) + break +# +# lastselection = get_pref('buddylist.layout.ez_layout_selection',-1) +# if lastselection != -1: +# self.SetSelection(lastselection) + + def SetSelection(self,item): + +# if isinstance(item,int): +# newselection = self.items[item] +# else: + newselection = item + + print newselection + + oldselection = self.selection + if oldselection: + oldselection.selected = False + oldselection.Refresh() + + self.selection = newselection + + newselection.selected = True + + self.Refresh() + +# i = self.items.index(newselection) +# +# mark_pref('buddylist.layout.ez_layout_selection',i) + + from peak.events import trellis + @trellis.modifier + def update(): + links = self.links + for key in newselection.prefdict: + if key in links: + links[key].value = newselection.prefdict[key] + else: + value = newselection.prefdict[key] + mark_pref('buddylist.layout.%s'%key,value) + update() + +class LayoutItem(SimplePanel): + def __init__(self,parent, bitmap, prefdict): + SimplePanel.__init__(self,parent,wx.FULL_REPAINT_ON_RESIZE) + + self.prefdict=prefdict + self.bitmap = bitmap + self.MinSize = self.bitmap.Size + (16,16) + + self.selected = False + + self.Bind(wx.EVT_PAINT,self.OnPaint) + self.Bind(wx.EVT_MOTION,lambda e: self.Refresh()) + self.Bind(wx.EVT_LEAVE_WINDOW,self.OnMouseLeave) + self.Bind(wx.EVT_LEFT_DOWN,self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP,self.OnLeftUp) + + def OnPaint(self,event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(rect) + + if rect.Contains(self.ScreenToClient(wx.GetMousePosition())): + dc.Brush = wx.Brush(wx.Color(238,239,255)) + dc.Pen = wx.Pen(wx.Color(128,128,255)) + dc.DrawRoundedRectangleRect(rect,4) + if wx.GetMouseState().LeftDown(): + rect2 = rect.Deflate(5,5) + dc.Pen = wx.TRANSPARENT_PEN + dc.Brush = wx.Brush(wx.Color(200,200,255)) + dc.DrawRectangleRect(rect2) + + if self.selected: + rect2 = rect.Deflate(4,4) + dc.Pen = wx.TRANSPARENT_PEN + dc.Brush = wx.Brush(wx.Color(128,128,255)) + dc.DrawRectangleRect(rect2) + + dc.DrawBitmap(self.bitmap,8,8,True) + + def OnLeftUp(self,event): + self.Parent.SetSelection(self) + + def OnLeftDown(self,event): + + self.Refresh() + + def OnMouseLeave(self,event): + + self.Refresh() diff --git a/digsby/src/gui/pref/pg_accounts.py b/digsby/src/gui/pref/pg_accounts.py new file mode 100644 index 0000000..1c6dff0 --- /dev/null +++ b/digsby/src/gui/pref/pg_accounts.py @@ -0,0 +1,913 @@ +import logging +from traceback import print_exc +from util.introspect import memoize +log = logging.getLogger('pg_accounts2') +import wx +from gui.uberwidgets.PrefPanel import PrefPanel +from gui.pref.prefcontrols import HSizer, VSizer, mark_pref, SText +from wx import EXPAND +import protocols +from gui.textutil import default_font +from gui.accountslist import AccountRow +from gui.accountslist import AccountList +from util.primitives.funcs import find +from operator import attrgetter + +import hooks +from contacts.sort_model import ChoiceModel +from pg_contact_list import yield_choices, release_syncs +from common import pref, profile +from peak.util.addons import AddOn +import common +from peak.events import trellis +import util.observe as observe +from util.lego.lattice.blocks import IValueListener +from util.lego.lattice.frame import ObservableAttrBindable + +class IServiceProviderGUIMetaData(protocols.Interface): + provider_id = unicode() + icon = property() #wx.Bitmap (skin.get....) + name = unicode() + popularity = int() #needed for comparison + service_components = list() #IServiceComponentGUIData + +class IServiceProviderInstanceGUIMetaData(protocols.Interface): + account_name = unicode() + #service_components = list() + +class IServiceComponentGUIMetaData(protocols.Interface): + icon = property() #wx.Bitmap (skin.get....) + service_name = unicode() #'Yahoo Messenger' + type = str() #twitter is a 'social', aim is an 'im' + component_id = str() # 'msn' + +from plugin_manager.plugin_registry import ServiceProviderPlugin, \ + ServiceComponentPlugin + +import services.service_provider as sp + +class ServiceProviderPluginGUIMetaData(protocols.Adapter): + protocols.advise(asAdapterForTypes = [ServiceProviderPlugin], + instancesProvide = [IServiceProviderGUIMetaData]) + @property + def provider_id(self): + return self.subject.provider_id + @property + def icon(self): + from gui import skin + return skin.get('serviceprovidericons.%s' % self.subject.provider_id) + @property + def provider_name(self): + return self.subject.name + @property + def popularity(self): + return getattr(getattr(getattr(self.subject, 'info', None), 'provider_info', None), 'popularity', 0) + @property + def service_components(self): + return sp.get_meta_components_for_provider(self.subject.provider_id) + +class ServiceComponentPluginGUIMetaData(protocols.Adapter): + protocols.advise(asAdapterForTypes = [ServiceComponentPlugin], + instancesProvide = [IServiceComponentGUIMetaData]) + @property + def icon(self): + from gui import skin + return skin.get('serviceicons.%s' % self.subject.shortname) + @property + def service_name(self): + return self.subject.info.get('service_name') or self.subject.info.get('name') + @property + def component_id(self): + return self.subject.shortname + @property + def type(self): + return self.subject.component_type + +class StringServiceComponentGUIData(object): + protocols.advise(asAdapterForTypes=[basestring], + instancesProvide=[IServiceComponentGUIMetaData]) + def __init__(self, subject): + self.subject = subject + @property + def service_name(self): + from common import protocolmeta + p = protocolmeta.protocols.get(self.subject) + if p: + name = p.get('service_name') or p.get('name') + else: + name = self.subject.capitalize() + return name + return p.name if p else self.subject.capitalize() + @property + def icon(self): + from gui import skin + return skin.get('serviceicons.%s' % self.subject, skin.get('serviceprovidericons.%s' % self.subject, None)) + @property + def type(self): + from common import protocolmeta + p = protocolmeta.protocols.get(self.subject) + return p.get('component_type', p.get('type')) if p else 'unknown' + @property + def component_id(self): + return self.subject + +def get_meta_for_provider(provider_instance): + return IServiceProviderGUIMetaData(sp.get_meta_service_provider(provider_instance.provider_id)) + +protocols.declareAdapterForType(IServiceProviderGUIMetaData, get_meta_for_provider, sp.ServiceProvider) + +class IPaintable(protocols.Interface): + def on_paint(e): + pass + +class wxPaintingMixin(object): + def __init__(self, *a, **k): + try: + IPaintable(self) + except protocols.AdaptationFailure: + raise + super(wxPaintingMixin, self).__init__(*a, **k) + self.Bind(wx.EVT_PAINT, self.on_paint) + +class IDrawable(protocols.Interface): + def draw(dc): + pass + +class IDrawingContext(protocols.Interface): + def DrawText(text, x, y): pass + def DrawBitmap(bmp, x, y): pass + def DrawRoundedRectangleRect(rect, radius): pass + +def wxPaintEventDrawingContextAdapter(event): + if event.EventType == wx.EVT_PAINT: + return wx.AutoBufferedPaintDC(event.GetEventObject()) + +protocols.declareImplementation(wx.DC, instancesProvide=[IDrawingContext]) +protocols.declareAdapterForType(IDrawingContext, wxPaintEventDrawingContextAdapter, wx.Event) + +class IRectagleFactory(protocols.Interface): + def get_rect(x,y,w,h): + pass + +class wxRectangleFactory(protocols.Adapter): + protocols.advise(instancesProvide=[IRectagleFactory], + asAdapterForTypes=[wx.DC]) + @staticmethod + def get_rect(x,y,w,h): + return wx.Rect(x,y,w,h) + +class ServiceProviderGUIMetaIconUser(object): + icon_cache = {} + def get_icon(self, meta, size): + meta = IServiceProviderGUIMetaData(meta) + try: + ret = self.icon_cache[(meta.provider_id, size)] + except KeyError: + ret = self.icon_cache[(meta.provider_id, size)] = meta.icon.PIL.Resized(size).WXB + return ret + +class ServiceComponentGUIMetaIconUser(object): + icon_cache = {} + def get_icon(self, meta, size): + meta = IServiceComponentGUIMetaData(meta) + try: + ret = self.icon_cache[(meta.component_id, size)] + except KeyError: + ret = self.icon_cache[(meta.component_id, size)] = meta.icon.PIL.Resized(size).WXB + return ret + +class ServiceMetaProviderPanel(wxPaintingMixin, wx.Panel, ServiceProviderGUIMetaIconUser): + protocols.advise(instancesProvide=[IPaintable, IDrawable]) + def __init__(self, service_providers, height, spacing, *a, **k): + self.service_providers = [s[0] for s in + sorted(((sp,IServiceProviderGUIMetaData(sp)) + for sp in service_providers), + key = (lambda o: o[1].popularity), + reverse = True)] + self.height = height + self.spacing = spacing + super(ServiceMetaProviderPanel, self).__init__(*a, **k) + #wx object operations must wait until after the wx constructor + self.MinSize = (len(service_providers) - 1)*(height + spacing) + height, (height + spacing) + self.w = None + self.Bind(wx.EVT_MOTION, self.on_mouseover, self) + self.Bind(wx.EVT_LEFT_UP, self.on_mouseclick, self) + self.Bind(wx.EVT_ENTER_WINDOW, self.on_mousein, self) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouseout, self) + self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.on_capture_lost, self) + self.sx = self.sy = None + + def draw(self, obj): + dc = IDrawingContext(obj) + dc.Clear() + size = self.height + offset = self.height+self.spacing + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangle(0,0,self.Size.width, self.Size.height) + for i, s in enumerate(self.service_providers): + dc.DrawBitmap(self.get_icon(s, size), i*offset, 0) + on_paint = draw + + def service_provider_from_evt(self, e): + if e.GetEventObject() is not self: + return + if not self.HasCapture(): + return + lx = e.X + r = self.ClientRect + if not wx.Rect(r.y,r.x,r.width + 1, r.height + 1).Contains(e.Position): + return + offset = self.height+self.spacing + pos = lx / float(offset) + posi = int(pos) + posr = pos - posi + if posr * offset <= (offset - self.spacing): + if posi < len(self.service_providers): + return self.service_providers[posi] + + def on_mouseover(self, e): + e.Skip() + if e.GetEventObject() is not self: + return + if not self.HasCapture(): + return + lx = e.X + r = self.ClientRect + if not wx.Rect(r.y,r.x,r.width + 1, r.height + 1).Contains(e.Position): + return self.on_mouseout(e) + offset = self.height+self.spacing + pos = lx / float(offset) + posi = int(pos) + posr = pos - posi + if posr * offset <= (offset - self.spacing): + sx = posi * offset + self.height/2 + sy = self.height + if posi >= len(self.service_providers): + return self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + if self.w is None: + self.w = ServiceProviderBubble(self.service_providers[0], 0, parent=self) + if self.w.Shown and sx == self.sx and sy == self.sy: + return + self.sx = sx + self.sy = sy + with self.w.Frozen(): + self.w.service_provider = IServiceProviderGUIMetaData(self.service_providers[posi]) + self.w.show_point_to(self.ClientToScreen((sx,sy))) + else: + self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + + def on_mouseclick(self, e): + e.Skip() + sp = self.service_provider_from_evt(e) + if sp is None: + return + + diag = hooks.first('digsby.services.create', parent = self.Top, sp_info = sp, impl="digsby_service_editor") + diag.CenterOnParent() + return_code = diag.ShowModal() + + if return_code != wx.ID_SAVE: + log.info("Account creation cancelled. Return code = %r", return_code) + return + + info = diag.extract() + sp = hooks.first('digsby.service_provider', + impl = diag.sp_info.provider_id, + **info) + + log.info("Created %r", sp) + components = [] + types_ = sp.get_component_types() + if 'im' in types_: + sp.autologin = True + for type_ in types_: + comp = sp.get_component(type_) + components.append((comp, type_[:2])) + log.info("\thas component %r: %r", type_, comp) + import services.service_provider as sp + with sp.ServiceProviderContainer(profile()).rebuilding() as container: + profile.account_manager.add_multi(components) + for comp, type_ in components: + try: + if hasattr(comp, 'enable'): + comp.enable() + else: + comp.enabled = True + except Exception: + print_exc() + try: + on_create = getattr(comp, 'onCreate', None) #CamelCase for GUI code + if on_create is not None: + on_create() + except Exception: + print_exc() + if type_ == 'em': + hooks.notify('digsby.email.new_account', parent = self.Top, protocol = comp.protocol, **info) + container.rebuild() + + def on_mousein(self, e): + if e.GetEventObject() is not self: + return + self.CaptureMouse() + + def on_mouseout(self, e): + if self.w is not None: + self.w.Hide() + while self.HasCapture(): + self.ReleaseMouse() + self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + + def on_capture_lost(self, e): + pass + +class BubbleWindow(wx.Frame): + def __init__(self, internal_size, parent = None, *a, **k): + style = wx.FRAME_SHAPED | wx.BORDER_NONE | wx.FRAME_NO_TASKBAR | \ + ((parent and wx.FRAME_FLOAT_ON_PARENT) or wx.STAY_ON_TOP) + ret = super(BubbleWindow, self).__init__(parent, style=style, *a, **k) + self.Bind(wx.EVT_PAINT, self.on_paint) + self.internal_size = internal_size + self.point = (10,0) + return ret + + def draw_content(self, dc): + pass + + @property + def poly(self): + xborder = 0 + yborder = 0 + startx, starty = (0,10) + px,py = self.point + endx, endy = (2*xborder + self.internal_size[0] + startx), (2*yborder + self.internal_size[1] + starty) + return ((px,py),(px+1,py), (px+11,starty), (endx,starty),(endx,endy),(startx,endy),(startx,starty),(px,starty),(px,py)) + + def show_point_to(self, point): + x,y = point + xborder = 0 + yborder = 0 + startx, starty = (0,10) + px,py = self.point + endx, endy = (2*xborder + self.internal_size[0] + startx), (2*yborder + self.internal_size[1] + starty) + with self.Frozen(): + self.Position = (x - px, y - py) + self.Size = (endx+1, endy+1) + self.SetShape(get_polyregion(self.poly, endx, endy)) + self.ShowNoActivate(True) + + def on_paint(self, e): + dc = wx.AutoBufferedPaintDC(self) +# gc = wx.GraphicsContext.Create(dc) + o = wx.Color(254,214,76) # skin! + y = wx.Color(255,251,184) + dc.SetPen(wx.Pen(o)) + dc.SetBrush(wx.Brush(y)) + dc.DrawPolygon(self.poly) + self.draw_content(dc) + +class ServiceProviderBubble(BubbleWindow, ServiceComponentGUIMetaIconUser): + def __init__(self, service_provider, font_adjust = 0, *a, **k): + self.service_provider = IServiceProviderGUIMetaData(service_provider) + super(ServiceProviderBubble, self).__init__((0,0), *a, **k) + self.Font = default_font() + self.font_adjust = font_adjust + self._service_provider = None + self.recalcsize() + + def recalcsize(self): + if self.service_provider is self._service_provider: + return + ftitle = self.Font + ftitle.PointSize = int((ftitle.PointSize + self.font_adjust) * 4/3.) + fbase = self.Font + fbase.PointSize += self.font_adjust + h = 0 + h += ftitle.LineHeight + 2 + h += ftitle.Descent * 2 + lh = round(fbase.LineHeight / 4. + .25) * 4 + 2 + #^ .25 = .5 - 1/4. (ceil, except when exactly divisible by 4) + h += lh*len(self.service_provider.service_components) - 2 + dc = wx.ClientDC(self) + dc.Font = ftitle + w = dc.GetTextExtent(self.service_provider.provider_name)[0] + self.offset = dc.GetTextExtent(" ")[0] + dc.Font = fbase + w2 = max([dc.GetTextExtent(" " + IServiceComponentGUIMetaData(s).service_name)[0] + for s in self.service_provider.service_components] + [0]) + w2 += lh + w2 += self.offset + w = max(w, w2) + w += ftitle.Descent * 4 + h += 2 + self.internal_size = (int(w), int(h)) + self._service_provider = self.service_provider + + def draw_content(self, dc): + ftitle = self.Font + ftitle.PointSize = int((ftitle.PointSize + self.font_adjust) * 4/3.) + fbase = self.Font + fbase.PointSize += self.font_adjust + dc.Font = ftitle + x,y = ftitle.Descent * 2, 10 + ftitle.Descent * 1 + dc.DrawText(self.service_provider.provider_name, x, y) + y += ftitle.LineHeight + 2 + lh = int(round(fbase.LineHeight / 4. + .25)) * 4 + 2 + dc.Font = fbase + x += self.offset + for s in sorted((IServiceComponentGUIMetaData(sc) + for sc in self.service_provider.service_components), + key = attrgetter('type'), + cmp = component_sort): + dc.DrawBitmap(self.get_icon(s, lh -2), x, y) + dc.DrawText(" " + s.service_name, x+lh, y) + y += lh + + def show_point_to(self, point): + self.recalcsize() + return super(ServiceProviderBubble, self).show_point_to(point) + +@memoize +def get_polyregion(points, w, h, border=1): + i = wx.EmptyImage(w + border, h + border) + b = i.WXB + m = wx.MemoryDC(b) + m.Clear() + m.SetBrush(wx.Brush(wx.Color(0,0,0))) + m.SetPen(wx.Pen(wx.Color(0,0,0))) + #ceil(border/2)? + m.DrawRectangle(0,0,w + border, h + border) + m.SetBrush(wx.Brush(wx.Color(255,255,255))) + m.SetPen(wx.Pen(wx.Color(255,255,255))) + m.DrawPolygon(points) + m.SelectObject(wx.NullBitmap) + del m + b.SetMaskColour(wx.Color(0,0,0)) + return wx.RegionFromBitmap(b) + +class IServiceProviderInstance(protocols.Interface): + icon = property() + +def grayify(i, grayshade): + return int(round((i + grayshade) / 2.)) +def grayify3(tuple_, grayshade): + return tuple(grayify(i, grayshade) for i in tuple_) + +class CheckModel(trellis.Component): + value = trellis.attr(False) + enabled = trellis.attr(True) + +class ListenerSync(trellis.Component): + listener = trellis.attr() + model = trellis.attr() + +class ModelListenerSync(ListenerSync): + @trellis.maintain + def value(self): + self.model.value = self.listener.value + +class ListenerModelSync(ListenerSync): + @trellis.maintain + def keep_value(self): + self.listener.value = self.model.value + +class ViewListenerModelSync(ListenerModelSync): + view = trellis.attr() + +class CheckModelSync(ViewListenerModelSync): + @trellis.perform + def update(self): + try: +# print 'syncing', self.model.enabled, self.model.value + with self.view.Frozen(): + self.view.Enabled = self.model.enabled + self.view.Value = self.model.value + except Exception: + if not wx.IsDestroyed(self.view): + raise + +class CheckSync(object): + def __init__(self, model, view): + #order matters, make the view sync to the model before we allow + #the other direction + listener = IValueListener(view) + self.model_to_view = CheckModelSync(model = model, view = view, listener = listener) + self.view_to_model = ModelListenerSync(model = model, listener = listener) + +class AccountModelSync(ViewListenerModelSync): + #syncs the model.value to the account + @trellis.perform + def update(self): + has_enabled = hasattr(self.view, 'enabled') + if has_enabled and self.view.enabled != self.model.value: + val = self.model.value + #assume accounts with enable()/disable() know what to do + #as far as saving that data. + if val and hasattr(self.view, 'enable'): + self.view.enable() + elif (not val) and hasattr(self.view, 'disable'): + self.view.disable() + else: + self.view.setnotify('enabled', val) + #email/social get saved to the server; + #update_info without any data works to just save them. + wx.CallAfter(self.view.update_info) + +class AccountEnabledSync(object): + def __init__(self, model, view): + #order matters, model syncs to the account first + listener = IValueListener(ObservableAttrBindable(view, 'enabled')) + self.acct_to_model = ModelListenerSync(model = model, listener = listener) + self.model_to_acct = AccountModelSync (model = model, view = view, listener = listener) + +class EnabledAccountSync(object): + def __init__(self, model, view): + #order matters, acct syncs to the model first + listener = IValueListener(ObservableAttrBindable(view, 'enabled')) + self.model_to_acct = AccountModelSync (model = model, view = view, listener = listener) + self.acct_to_model = ModelListenerSync(model = model, listener = listener) + +class AccountEnabledCheckSync(object): + def __init__(self, account, check): + #double MVC + model = CheckModel() + self.acct_model = AccountEnabledSync(model = model, view = account) + self.view_model = CheckSync( model = model, view = check) + +class NoAccountEnabledCheckSync(trellis.Component): + created_account = trellis.attr(False) + def __init__(self, provider, component, check): + super(NoAccountEnabledCheckSync, self).__init__() + self.model = CheckModel() + self.provider = provider + self.component = component + self.view_model = CheckSync(model = self.model, view = check) + + @trellis.maintain + def update(self): + if not self.model.value: + return + + if self.created_account: + return + self.created_account = True + + # create account object, put it in the account manager + acct = self.provider.get_component(self.component.type) + profile.account_manager.add(acct, self.component.type[:2], ignore_on_rebuild=True) + + self.acct_to_model = EnabledAccountSync(model = self.model, view = acct) + +class ServiceProviderRow(AccountRow, ServiceProviderGUIMetaIconUser): + row_height = 50 + def __init__(self, parent, data, *a, **k): + self.data = data + self._base_color_tuple = (128,128,128) + self._name_color_tuple = (0,0,0) + ret = super(ServiceProviderRow, self).__init__(parent=parent, data=data, *a, **k) +# self.SetToolTipString(IServiceProviderGUIMetaData(data).provider_name +\ +# ': ' + IServiceProviderInstanceGUIMetaData(data).account_name) + self.Font = default_font() + return ret + + @property + def image(self): + try: + ret = self.get_icon(self.data, 40) + except AttributeError: + ret = super(ServiceProviderRow, self).image + return ret + + def draw_text(self, dc, x, sz): + pass + + def _paint(self, e): + dc = super(ServiceProviderRow, self)._paint(e) + f = self.Font + f.PointSize += 3 + dc.Font = f + name = IServiceProviderInstanceGUIMetaData(self.data).account_name + x,y = self._place_text.Position + y += f.Descent + + for (color, string) in (hooks.first('digsby.services.colorize_name', impls = (self.data.provider_id, 'digsby_service_editor'), name = name) or ()): + color_obj = getattr(self, '_active_%s_color' % color, None) + dc.SetTextForeground(wx.Color(*color_obj)) + dc.DrawText(string, x, y) + x += dc.GetTextExtent(string)[0] + + def ConstructMore(self): + self.checks = [] + self.hiddens = [] + import gui.lattice + for s in sorted((IServiceComponentGUIMetaData(sc) + for sc in IServiceProviderGUIMetaData(self.data).service_components), + key = attrgetter('type'), + cmp = component_sort): + c = wx.CheckBox(self, -1, s.service_name) + for a in self.data.accounts.values(): + if a.protocol == s.component_id: + c.controller = AccountEnabledCheckSync(account = a, + check = c) + break + else: + c.controller = NoAccountEnabledCheckSync(provider = self.data, component = s, check = c) + if s.type == 'im': + text = wx.StaticText(self, label = _('(auto login)')) + f = c.Font + text.Font = f + text.ForegroundColour = wx.Color(*self._base_color_tuple) + text.Hide() + self.hiddens.append(text) + c = (c, text) + self.checks.append(c) + + # Extra component--the edit hyperlink + edit = self.edit = wx.HyperlinkCtrl(self, -1, _('Edit'), '#') + edit.Hide() + edit.Bind(wx.EVT_HYPERLINK, lambda e: self.on_edit()) + + remove = self.remove = wx.HyperlinkCtrl(self, -1, _('Remove'), '#') + remove.Hide() + remove.Bind(wx.EVT_HYPERLINK, lambda e: self.on_delete()) + + self.hiddens.append(edit) + self.hiddens.append(remove) + + edit.HoverColour = edit.VisitedColour = edit.ForegroundColour + remove.HoverColour = remove.VisitedColour = remove.ForegroundColour + self._reset_text_name() + + def on_edit(self): + super(ServiceProviderRow, self).on_edit() + self.on_data_changed(self) + + def on_delete(self): + super(ServiceProviderRow, self).on_delete() + #super.on_data_changed to skip the GUI refresh + super(ServiceProviderRow, self).on_data_changed(self) + + def _reset_text_name(self): + dc = wx.ClientDC(self) + f = self.Font + f.PointSize += 3 + dc.Font = f + txt = IServiceProviderInstanceGUIMetaData(self.data).account_name + w,h = dc.GetTextExtent(txt) + self.text_name = w, h + + def CalcColors(self, *a, **k): + if self.IsHovered(): + self._active_base_color = self._base_color_tuple + self._active_name_color = self._name_color_tuple + else: + self._active_base_color = grayify3(self._base_color_tuple, 128) + self._active_name_color = grayify3(self._name_color_tuple, 128) + return super(ServiceProviderRow, self).CalcColors(*a, **k) + + def LayoutMore(self, sizer): + #checkboxes + check_sizer = VSizer() + check_sizer.AddStretchSpacer(3) + for check in self.checks: + if isinstance(check, tuple): + check, text = check + h = HSizer() + h.Add(check) + h.Add(text) + check_sizer.Add(h) + else: + check_sizer.Add(check) + check_sizer.AddStretchSpacer() + check_sizer.AddStretchSpacer(2) + #add checkboxes + sizer.Add(check_sizer, 1, wx.LEFT | wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, self.padding.x*2) + #right side + right_sizer = VSizer() + self._place_text = right_sizer.Add(self.text_name, 3, wx.ALIGN_RIGHT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, self.padding.y) + #right bottom + link_sizer = HSizer() + link_sizer.Add(self.edit, 0, wx.LEFT, self.padding.x) + link_sizer.Add(self.remove, 0, wx.LEFT, self.padding.x) + right_sizer.AddStretchSpacer() + #add right bottom to right + right_sizer.Add(link_sizer, 3, wx.ALIGN_RIGHT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, self.padding.y) + #add right side + sizer.Add(right_sizer, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL, 0) + + def on_data_changed(self, src=None, *a): + super(ServiceProviderRow, self).on_data_changed(src, *a) + self._reset_text_name() + self.layout() + self.Layout() + self.Refresh() + +#low to high priority order, (-1 = not found = lowest) +sorted_component_types = ['social', 'email', 'im'] +def component_sort(a, b): + return find(sorted_component_types, b) - find(sorted_component_types, a) + +class ServiceProviderGUIMetaDataAdapter(protocols.Adapter): + protocols.advise(asAdapterForTypes = [sp.ServiceProvider], + instancesProvide = [IServiceProviderInstanceGUIMetaData]) + @property + def account_name(self): + name = None + try: + name = getattr(self.subject, 'display_name', None) + except Exception: + print_exc() + if name is None: + name = self.subject.name + return name +# @property +# def service_components(self): +# return [ServiceComponentGUIMetaData(s.component_type, s.shortname) for s in +# sp.get_meta_components_for_provider(self.subject.provider_id)] + +class ProviderList(AccountList): + def __init__(self, *a, **k): + super(ProviderList, self).__init__(*a, **k) + self.Bind(wx.EVT_LIST_ITEM_FOCUSED,self.OnHoveredChanged) + + def OnHoveredChanged(self,e): + row = self.GetRow(e.Int) + if row: + with row.Frozen(): + hiddens = getattr(row, 'hiddens', []) + if row.IsHovered(): + for obj in hiddens: + obj.Show() + row.Layout() + row.Refresh() + else: + for obj in hiddens: + obj.Hide() + row.Layout() + row.Refresh() + + def on_edit(self, rowdata): + diag = hooks.first('digsby.services.edit', parent = self.Top, sp = rowdata, impl="digsby_service_editor") + return_code = diag.ShowModal() + if return_code != wx.ID_SAVE: + return + + info = diag.RetrieveData() + + log.info("Modifying %r", rowdata) + rowdata.update_info(info) + rowdata.update_components(info) + + def OnDelete(self, data): + + # Display a confirmation dialog. + msgbox = hooks.first("digsby.services.delete.build_dialog", impls = (data.provider_id, 'default'), parent = self.Top, SP = data) + + try: + if msgbox.ShowModal() == wx.ID_YES: + for type_ in data.get_component_types(): + comp = data.get_component(type_, create = False) + if comp is None: + continue + profile.account_manager.remove(comp) + finally: + msgbox.Destroy() + + def get_wheel_lines(self, rotation, e): + val = rotation / float(e.GetWheelDelta()) * e.LinesPerAction + return val / 2.5 # 2.5-line panels + + def rotation_for_lines(self, lines, e): + return float(e.GetWheelDelta()) / e.LinesPerAction * lines * 2.5 + +def panel(panel, sizer, addgroup, exithooks): + + service_providers = ServiceMetaProviderPanel(sp.get_meta_service_providers(), + 40, 3, panel) + + two = PrefPanel(panel, service_providers, _('Add Accounts')) + + import services + + container = services.service_provider.ServiceProviderContainer(profile()) + provider_accts = observe.ObservableList(container.get_ordered() or []) + + def on_change(*a, **k): + if not provider_accts.isflagged('being_set'): + order = [] + for provider in provider_accts: + for type_ in ('im', 'email', 'social'): #keep order! - no frozenset! + if type_ in provider.accounts: + order.append(provider.accounts[type_].id) + container.set_order(order) + + def order_set(*a, **k): + @wx.CallAfter + def do_set(): + new = container.get_ordered() + if new[:] != provider_accts[:]: + with provider_accts.flagged('being_set'): + provider_accts[:] = new + + container.on_order_changed += order_set + + def unregister(*a, **k): + if order_set in container.on_order_changed: + container.on_order_changed -= order_set + + exithooks += unregister + + #hook this into on_data_changed + + accts = ProviderList(panel, provider_accts, + row_control = lambda *a, **k: ServiceProviderRow(use_checkbox = False, *a, **k), + edit_buttons = None) + provider_accts.add_observer(on_change, obj = accts) + + three = PrefPanel(panel, accts, _('My Accounts')) + sizer.Add(two, 0, EXPAND) + sizer.Add(three, 1, EXPAND | wx.TOP, 3) + four = PrefPanel(panel, + choices_sizer(panel, exithooks), _('Account Options')) + sizer.Add(four, 0, EXPAND | wx.TOP, 3) + return panel + +def set_loc_values(model, prefname): + val = pref(prefname) + try: + model.selection = [v[0] for v in model.values].index(val) + except ValueError: + pass + +class PrefChoiceWatcher(trellis.Component): + def __init__(self, model, prefname): + trellis.Component.__init__(self) + self.model = model + self.prefname = prefname + + @trellis.perform + def output_pref(self): + mark_pref(self.prefname, self.model.values[self.model.selection][0]) + +class AccountLocationModels(AddOn): + did_setup = False + def __init__(self, subject): + self.profile = subject + super(AccountLocationModels, self).__init__(subject) + + def setup(self): + if self.did_setup: + return + self.did_setup = True + account_options = [('panel', _('buddy list')), + ('systray', _('icon tray')), + ('both', _('buddy list and icon tray'))] + email_pref = 'buddylist.show_email_as' + social_pref = 'buddylist.show_social_as' + self.email = email = ChoiceModel(values = account_options[:]) + self.social = social = ChoiceModel(values = account_options[:]) + set_loc_values(email, email_pref) + set_loc_values(social, social_pref) + self.profile.prefs.add_observer(self.on_email_changed, email_pref) + self.profile.prefs.add_observer(self.on_social_changed, social_pref) + self.email_watcher = PrefChoiceWatcher(self.email, email_pref) + self.social_watcher = PrefChoiceWatcher(self.social, social_pref) + + @property + def location_models(self): + return [self.email, self.social] + + def on_email_changed(self, src, attr, old, new): + import wx + wx.CallAfter(set_loc_values, self.email, 'buddylist.show_email_as') + def on_social_changed(self, src, attr, old, new): + import wx + wx.CallAfter(set_loc_values, self.social, 'buddylist.show_social_as') + +def choices_sizer(panel, exithooks): + p = wx.Panel(panel) + sz = p.Sizer = VSizer() + + model_addon = AccountLocationModels(common.profile()) + model_addon.setup() + + location_models = model_addon.location_models + + import gui.lattice #@UnusedImport: side effect registering interfaces + choices = list(yield_choices(location_models, p)) + exithooks.append(lambda:release_syncs(choices)) + + labels = [_('Show email accounts in:'), + _('Show social networks in:'),] + + to_add = [[(SText(p, label), 0, wx.ALIGN_CENTER_VERTICAL), + (choice, 1, 0)] + for label,choice in zip(labels, choices)] + + s = wx.FlexGridSizer(len(location_models), 2, 7,7) + s.AddMany(sum(to_add,[])) + s.AddGrowableCol(1,1) + sz.Add(s, 0, EXPAND) + + return p diff --git a/digsby/src/gui/pref/pg_advanced.py b/digsby/src/gui/pref/pg_advanced.py new file mode 100644 index 0000000..49cbf14 --- /dev/null +++ b/digsby/src/gui/pref/pg_advanced.py @@ -0,0 +1,145 @@ +''' +Advanced tab in the preferences dialog. +''' +from __future__ import with_statement +import wx +from gui.pref.prefcontrols import * +from gui.uberwidgets.PrefPanel import PrefPanel,PrefCollection + +from util.primitives.funcs import do +from util.primitives.mapping import lookup_table + +def panel(panel, sizer, addgroup, exithooks): + + addgroup(_('IM Window'), lambda p,*a: Check('conversation_window.merge_metacontact_history', 'Merge Metacontact History')(p)) + +# status_check = Check('fullscreen.set_status', _('&Set status to:'))(panel) +# status_input = wx.TextCtrl(panel, -1) +# +# status_sz = wx.BoxSizer(wx.HORIZONTAL) +# status_sz.AddMany([ +# (status_check, 0, wx.EXPAND), +# (status_input, 1, wx.EXPAND) +# ]) +# +# g.Add(status_sz, 0, wx.EXPAND | wx.ALL, 4) +# +# +# addgroup(_('Proxy Settings'), +# build_proxy_panel(panel) +# ) + + return panel + +# ---- + +def get_protocols(): + # (Protocol nicename, Preference short name) + + return [('Global Settings', 'proxy'), + ('AOL Instant Messenger', 'aim'), + ('MSN Messenger', 'msn'), + ('Yahoo! Messenger', 'yahoo'), + ('ICQ', 'icq'), + ('Jabber', 'jabber'), + ('Google Talk', 'gtalk')] + +proxy_protocols = ['SOCKS4', 'SOCKS5', 'HTTP', 'HTTPS'] + +def build_proxy_panel(parent): + s = VSizer() + + protos = get_protocols() + choices = [a[0] for a in protos] + + # Choice for "Global Settings" and then all the protocols + proto_choice = wx.Choice(parent, -1, choices = choices) + proto_choice.SetSelection(0) + + # Checkbox which is either "Use Proxy Server" or "Use Global Settings" + checkbox = wx.CheckBox(parent, -1, _('&Use Proxy Server')) + + # Proxy protocols (HTTP, HTTPS, ...) + proxy_proto_choice = wx.Choice(parent, -1, choices = proxy_protocols) + proxy_proto_choice.SetSelection(0) + + grid = wx.FlexGridSizer(0, 2, 5, 5) + proxy_info = [ + ('host', _('Host')), + ('port', _('Port')), + ('username', _('Username')), + ('password', _('Password')) + ] + + # Shortcuts for preference names. + prefbase = lambda i: 'proxy.' if i == 0 else (protos[i][1] + '.proxy.') + getcheckpref = lambda i: prefbase(i) + ('use_proxy' if i == 0 else 'use_global') + gettextpref = lambda i, name: prefbase(i) + name + getprotopref = lambda i: prefbase(i) + 'protocol' + + fields = [] + for i, (name, trans) in enumerate(proxy_info): + field = wx.TextCtrl(parent, -1, '', size=(300, -1), + style = wx.TE_PASSWORD if name == 'password' else wx.TE_LEFT) + fields.append( (name, field) ) + + proxy_info_fields = lookup_table(fields) + + def protocol_changed(e = None): + '''Invoked when the choice box with "Global Settings" and all the other + protocols changes.''' + + i = proto_choice.Selection + + checkstr = _('&Use Proxy Server') if i == 0 else _('&Use Global Settings') + checkbox.Label = checkstr + + enabled = bool(get_pref(getcheckpref(i))) + checkbox.SetValue(enabled) + + proxy_proto_choice.SetStringSelection(get_pref(getprotopref(i))) + for i, (name, trans) in enumerate(proxy_info): + proxy_info_fields[name].Value = get_pref(gettextpref(i, name)) + + print i, enabled + do(c.Window.Enable(enabled if i == 0 else not enabled) for c in grid.Children) + + def proxy_proto_changed(e): + i = proto_choice.Selection + mark_pref(getprotopref(i), proxy_proto_choice.StringSelection) + + def checkbox_changed(e): + i = proto_choice.Selection + val = checkbox.Value + + do(c.Window.Enable(val if i == 0 else not val) for c in grid.Children) + mark_pref(getcheckpref(i), val) + + def textfield_changed(e): + tfield = e.EventObject + name = proxy_info_fields[tfield] + + mark_pref(gettextpref(proto_choice.Selection, name), tfield.Value) + + # Connect event functions + proto_choice.BBind(CHOICE = protocol_changed) + proxy_proto_choice.BBind(CHOICE = proxy_proto_changed) + checkbox.BBind(CHECKBOX = checkbox_changed) + + # The grid of fields for protocol specific proxy options + grid.Add(wx.StaticText(parent, -1, _('&Protocol:')), 0, wx.ALIGN_CENTER_VERTICAL) + grid.Add(proxy_proto_choice) + + for name, label in proxy_info: + grid.Add(wx.StaticText(parent, -1, label + ':'), 0, wx.ALIGN_CENTER_VERTICAL) + textctrl = proxy_info_fields[name] + textctrl.BBind(KILL_FOCUS = textfield_changed) + grid.Add(textctrl) + + protocol_changed() + + s.Add( proto_choice, 0, wx.NORTH, 4 ) + s.Add( checkbox, 0, wx.NORTH, 8 ) + s.AddSpacer( 10 ) + s.Add( grid, 0, wx.WEST, 22 ) + return s diff --git a/digsby/src/gui/pref/pg_appearance.py b/digsby/src/gui/pref/pg_appearance.py new file mode 100644 index 0000000..c701573 --- /dev/null +++ b/digsby/src/gui/pref/pg_appearance.py @@ -0,0 +1,284 @@ +''' +"Skins" tab for the main preferences window. + +- application skin +- conversation preview +- conversation settings +- text formatting +''' +from __future__ import with_statement + +import wx +import config +from wx import VERTICAL, EXPAND, HORIZONTAL, CallLater, ALIGN_CENTER_VERTICAL, BOTTOM, \ + BoxSizer + +from gui.pref.prefcontrols import * +from gui.uberwidgets.PrefPanel import PrefPanel + +from common import pref +import common.logger +from util import try_this + +from string import Template + +from logging import getLogger; log = getLogger('prefs.appearance') + +from tests.mock.mockbuddy import MockBuddy +from gui.imwin.messagearea import MessageArea +from gui.imwin.styles import get_theme_safe +from common import profile +from gui import skin + +CONVO_THEME_PREF = 'appearance.conversations.theme' +EXAMPLE_CONVO_FILENAME = 'Example Conversation.html' + +def panel(panel, sizer, addgroup, exithooks): + # disable skins on mac + if config.platform != 'mac': + addgroup(_('Application Skin'), lambda p,*a: skin_sizer(p)) + + addgroup(_('Conversation Theme'), lambda p,*a: conversation_sizer(p, panel)) + + convo_preview = PrefPanel(panel, lambda parent, _prefix: conversation_preview(parent, exithooks, panel), + _('Conversation Preview')) + sizer.Add(convo_preview, 1, EXPAND) + + return panel + +def build_example_message_area(parent, theme): + buddy = MockBuddy('Friend') + + msgarea = MessageArea(parent) + msgarea.SetMinSize((-1, 150)) + msgarea.init_content(theme, buddy.alias, buddy, show_history = False) + + # load an example conversation from the resource directory. + bytes = (skin.resourcedir() / EXAMPLE_CONVO_FILENAME).bytes() + bytes = Template(bytes).safe_substitute({'Me': profile.username}) + msgs = parent._examplehistory = list(common.logger.parse_html(bytes)) + + log.info(' %d messages', len(msgs)) + msgarea.replay_messages(msgs, buddy, context = False) + + return msgarea + +def conversation_preview(parent, exithooks, pref_panel): + convo_panel = wx.Panel(parent) + convo_panel.Sizer = sz = BoxSizer(VERTICAL) + + def setcontent(*a): + convo_panel.Freeze() + sz.Clear(True) + + theme = pref(CONVO_THEME_PREF) + themeName = pref('appearance.conversations.theme') + theme = get_theme_safe(themeName, pref('appearance.conversations.variant')) + log.info('showing message style %r', themeName) + + pref_panel.msgarea = msgarea = build_example_message_area(convo_panel, theme) + sz.Add(msgarea, 1, EXPAND) + sz.Layout() + + parent._thawtimer = CallLater(150, lambda: convo_panel.Thaw()) + return msgarea + + timer = wx.PyTimer(setcontent) + + def doset(*a): + if timer.IsRunning(): return + else: timer.Start(300, True) + + link = profile.prefs.link + p = 'appearance.conversations.' + + # Changes to the following prefs cause the conversation preview window to update. + links = [link(name, doset, False, obj = convo_panel) for name in [ + # conversation appearance prefs + p + 'theme', + p + 'variant', + p + 'show_message_fonts', + p + 'show_message_colors', + p + 'show_header', + + # timestamp format prefs + 'conversation_window.timestamp', + 'conversation_window.timestamp_format']] + + # Unlink these prefs when the prefs dialog closes. + exithooks += lambda: [link.unlink() for link in links] + wx.CallLater(200, setcontent) + return convo_panel + +def skin_sizer(p): + from gui import skin + skin_caption, variant_caption = _('Skin:'), _('Variant:') + + skins = skin.list_skins() + skin_choices = [(s, s.alias) for s in skins] + + sz = wx.FlexGridSizer(rows = 2, cols = 2, vgap = 5, hgap = 5) + variant_sizer = BoxSizer(VERTICAL) + skin_choice = Choice('appearance.skin', skin_choices, caption = '', + callback = lambda *a: wx.CallAfter(setskin, *a), + do_mark_pref = False)(p) + + def setskin(prefname = None, val = None): + with p.Frozen(): + variant_sizer.Clear(True) + + if val is None: + i = try_this(lambda: [s.name for s in skins].index(pref('appearance.skin')), 0) + j = try_this(lambda: [v.path.namebase for v in skins[i].variants].index(pref('appearance.variant'))+1, 0) + val = skins[i] + + variants = list(val.variants) + + if prefname is not None: + mark_pref('appearance.skin', val.name) + mark_pref('appearance.variant', ('%s' % variants[0].path) if variants else None) + wx.CallAfter(skin.reload) + + vchoices = [(None, val.novariant_alias)] + if variants: + vchoices += [(v.path.namebase, v.alias) for v in variants] + + choice = Choice('appearance.variant', vchoices, '', + callback = lambda *a: wx.CallAfter(skin.reload))(p) + + if prefname is None: + skin_choice.SetSelection(i) + choice.SetSelection(j) + + variant_sizer.Add(choice, 1, wx.EXPAND) + choice.Enable(bool(variants)) + + sz.Layout() + + setskin() + sz.AddMany([ + (wx.StaticText(p,-1, skin_caption), 0, ALIGN_CENTER_VERTICAL), + (skin_choice, 1, EXPAND), + (wx.StaticText(p,-1, variant_caption), 0, ALIGN_CENTER_VERTICAL), + (variant_sizer, 1, EXPAND), + ]) + sz.AddGrowableCol(1,1) + return sz + +def conversation_sizer(p, panel): + from gui.imwin.styles import get_themes + + themes = get_themes() + + theme_choices = [(s.theme_name, s.theme_name) for s in themes] +# icon_choices = [('none','None')] +# variant_choices = [('red_green','Red - Green')] +# emoticon_choices = [(s.lower(), '%s Emoticons' % s) for s in +# 'Digsby Yahoo AIM Adium'.split()] + + sz = BoxSizer(wx.HORIZONTAL) + combosizer = wx.FlexGridSizer(2,2, vgap = 5, hgap = 5) + vsizer = BoxSizer(VERTICAL) + + checksizer = BoxSizer(wx.VERTICAL) + + PFX = 'appearance.conversations.' + + header_check = Check(PFX + 'show_header', _('Show header'), + callback = lambda val: panel.msgarea.show_header(val))(p) + + + checksizer.Add(header_check, 0, BOTTOM,5) + checksizer.Add(Check(PFX + 'show_message_fonts', _('Show message fonts'))(p), 0, BOTTOM,5) + + colors_checkbox = Check(PFX + 'show_message_colors', _('Show message colors'))(p) + checksizer.Add(colors_checkbox, 0, BOTTOM,5) + def combo(*a, **k): + choice = Choice(*a, **k)(p) + combosizer.Add(choice, 1, EXPAND) + return choice + + combosizer.Add(wx.StaticText(p,-1, _('Theme:')),0,ALIGN_CENTER_VERTICAL) + combo(PFX + 'theme', theme_choices, '', callback = lambda pref, value: wx.CallAfter(revar, value)) + combosizer.Add(wx.StaticText(p,-1, _('Variant:')),0,ALIGN_CENTER_VERTICAL) + combosizer.AddGrowableCol(1,1) + + def revar(name = None, first = False): + from common import pref + if name is None: + name = pref(PFX + 'theme') + + themes = get_themes() + found = None + for theme in themes: + if theme.theme_name == name: + found = theme + + if found is None: + found = themes[0] if themes else None + + if found is not None: + vars = found.variants + if vars: vchoices = [((v, v) if v is not None else (None, found.novariant_name)) for v in vars] + else: vchoices = [(None, found.novariant_name)] + else: + vchoices = [] + vars = [] + + p.Freeze() + vsizer.Clear(True) + + if not first: + mark_pref(PFX + 'variant', found.variant or '') + choice = Choice(PFX + 'variant', vchoices, '')(p) + + vsizer.Add(choice, 1, EXPAND) + if not vars: + #choice.SetSelection(vars.index(found.variant)) + choice.Enable(False) + + if found is not None: + allow_colors = found.allow_text_colors if found is not None else True + allow_header = bool(found.header) + else: + allow_colors = True + allow_header = True + + # "Show Message Colors" checkbox is disabled and unchecked if the theme does not + # support colors. + colors_checkbox.Enable(allow_colors) + colors_checkbox.SetValue(allow_colors and pref(PFX + 'show_message_colors')) + + header_check.Enable(allow_header) + header_check.SetValue(allow_header and pref(PFX + 'show_header')) + + sz.Layout() + p.Thaw() + + revar(first = True) + + sz.Add(combosizer,1,wx.EXPAND) + sz.Add(checksizer,0,wx.LEFT,10) + combosizer.Add(vsizer,1,wx.EXPAND) + return sz + +def text_formatting_sizer(p): + sz = wx.FlexGridSizer(2,4, 3, 3) + sz.FlexibleDirection = HORIZONTAL + return sz + + sz.Add(SText(p, _('Fonts:'))) + sz.Add(Choice(['Theme Fonts'])(p)) + sz.Add(Choice(['12'])(p)) + sz.AddStretchSpacer(0) + + sz.AddMany([(SText(p,_('Colors:'))), + (Choice(['Theme Colors'])(p)), + (Choice(['A'])(p)), + (Choice(['A'])(p)), + ]) + + + + return sz + diff --git a/digsby/src/gui/pref/pg_contact_list.py b/digsby/src/gui/pref/pg_contact_list.py new file mode 100644 index 0000000..c3066c2 --- /dev/null +++ b/digsby/src/gui/pref/pg_contact_list.py @@ -0,0 +1,431 @@ +''' +Contact List tab for the preferences dialog. +''' + +from __future__ import with_statement + +import wx +import config +from gui.pref.prefcontrols import HSizer, VSizer, Check, mark_pref, get_pref, \ + SText, PrefGroup, Choice, Slider, FontFaceAndSize, EnabledGroup, OffsetGroup +from gui.uberwidgets.PrefPanel import PrefPanel, PrefCollection + +from util.fsm import StateManager, StateMachine +from util.primitives.funcs import do +from util.primitives.mapping import dictreverse +from common import profile, pref + +from wx import LEFT, EXPAND, TOP, RIGHT, BOTTOM, EVT_CHOICE, EVT_CHECKBOX +from peak.events import trellis +from util.lego.lattice.blocks import IValueListener +from pg_sandbox import LayoutModel +from gui.pref.pg_sandbox import PrefLink, Selection, TranslatableChoices +from contacts.sort_model import ChoiceModel, CheckModel + +def panel(panel, sizer, addgroup, exithooks): + two = PrefPanel(panel, + sorting_sizer(panel, exithooks),_('Sorting and Groups')) + + autohide_panel = wx.Panel(panel) + aps = autohide_panel.Sizer = HSizer() + + if config.platform == 'win': + # Setting autohide has to modify taskbar settings, so focus jumps to + # the buddylist. This meant you wouldn't immediately see the effect + # of checking the box if the blist was already docked, though. + # + # Now 50ms after checking it, the checkbox gets focus again, and the + # buddylist will then slide away. + def on_autohide_check(v): + if v: wx.CallLater(50, autohide.SetFocus) + else: + def on_autohide_check(v): + pass + + autohide = Check('local.buddylist.dock.autohide', + _('Autohide when not in &focus'), + callback = on_autohide_check)(autohide_panel) + + aps.Add(autohide, 0, LEFT, 18) + + dock = Check('local.buddylist.dock.enabled', _('Automatically &dock when near edge of screen'), + callback = lambda v: autohide.Enable(v))(panel) + autohide.Enable(dock.IsChecked()) + + three = PrefPanel( panel, + PrefCollection( + Check('buddylist.always_on_top', _('&Keep on top of other applications')), + Check('buddylist.show_in_taskbar', _('Show in taskbar')), + dock, + autohide_panel, + layout = VSizer(), + itemoptions = (0, BOTTOM, 6) + ), + _('Window Options') + ) + + + # The autohide checkbox is slightly indented, and disabled when the "dock" + # checkbox is unhecked. + + h = HSizer() + h.Add(two, 1, EXPAND | RIGHT, 3) + h.Add(three, 0, EXPAND | LEFT, 3) + + v = VSizer() + v.Add(h, 0, EXPAND) + + from functools import partial + four = PrefPanel(panel, + partial(contact_layout_panel, exithooks=exithooks), + _('Contact Layout'), + prefix = 'buddylist.layout') + + def AdvancedToggleCB(button): + val = not pref('buddylist.layout.ez_layout',False) + mark_pref('buddylist.layout.ez_layout', val) + ezmode = val + + ez = four.content.ez_panel + adv = four.content.adv_panel + + four.content.Sizer.Clear() + + four.content.Show(False) + if ezmode: + adv.Destroy() + adv = None + easy_layout_panel(four.content, 'buddylist.layout') + else: + ez.Destroy() + ez = None + advanced_layout_panel(four.content, 'buddylist.layout', exithooks=exithooks) + + button.SetLabel(_('Advanced') if ezmode else _('Basic')) + + four.content.Layout() + four.content.Show(True) + panel.Layout() + + four.SetButton(_('Advanced') if four.content.ez_panel else _('Basic'), lambda button: wx.CallAfter(AdvancedToggleCB, button)) + + + v.Add(four, 0, EXPAND | TOP, 5) + + sizer.Add(v, 1, EXPAND) + + return panel + +def setChoiceString(choice, s): + if not s in choice.GetStrings(): + choice.Append(s) + + choice.SetStringSelection(s) + +wx.Choice.SetThisString = setChoiceString + +class ChoiceModelSync(trellis.Component): + view = trellis.attr() + model = trellis.attr() + listener = trellis.attr() + + @trellis.perform + def update(self): + try: +# print 'syncing', self.model.enabled, self.model.selection, self.values[:] + with self.view.Frozen(): + self.view.Enabled = self.model.enabled + self.view.Items = [val[1] for val in self.model.values] + self.view.SetSelection(self.model.selection) + #hack a size(?) event, roughly equivalent to the C function + # implementation of UpdateVisibleHeight() + #forces the list to not have a scrollbar (at least when going + # from 4 to 5 items) + self.view.Size = self.view.Size + except Exception: + if not wx.IsDestroyed(self.view): + raise + + @trellis.maintain + def selection(self): + self.listener.value = self.model.selection + +class CheckModelSync(trellis.Component): + view = trellis.attr() + model = trellis.attr() + listener = trellis.attr() + + @trellis.perform + def update(self): + try: + with self.view.Frozen(): + self.view.Enabled = self.model.enabled + self.view.Value = self.model.checked + except Exception: + if not wx.IsDestroyed(self.view): + raise + + @trellis.maintain + def state(self): + self.listener.value = self.model.checked + +class ModelChoiceSync(trellis.Component): + listener = trellis.attr() + model = trellis.attr() + + @trellis.maintain + def selection(self): + self.model.selection = self.listener.value + +class ModelCheckSync(trellis.Component): + listener = trellis.attr() + model = trellis.attr() + + @trellis.maintain + def state(self): + self.model.checked = self.listener.value + +class ChoiceSync(object): + def __init__(self, model, view): + self.model = model + self.view = view + #order matters, make the view sync to the model before we allow + #the other direction + self.listener = IValueListener(self.view) + self.model_to_view = ChoiceModelSync(model = model, view = view, listener = self.listener) + self.view_to_model = ModelChoiceSync(model = model, listener = self.listener) + +class CheckSync(object): + def __init__(self, model, view): + self.model = model + self.view = view + #order matters, make the view sync to the model before we allow + #the other direction + self.listener = IValueListener(self.view) + self.model_to_view = CheckModelSync(model = model, view = view, listener = self.listener) + self.view_to_model = ModelCheckSync(model = model, listener = self.listener) + +class SyncingChoice(wx.Choice): + def __init__(self, model, *a, **k): + super(SyncingChoice, self).__init__(*a, **k) + self.sync = ChoiceSync(view = self, model = model) + + def release(self): + del self.sync + +class SyncingCheck(wx.CheckBox): + def __init__(self, model, *a, **k): + super(SyncingCheck, self).__init__(*a, **k) + self.sync = CheckSync(view = self, model = model) + + def release(self): + del self.sync + +def yield_choices(models, *a, **k): + for model in models: + yield SyncingChoice(model, *a, **k) + +def release_syncs(choices): + for choice in choices: + choice.release() + +def sorting_sizer(panel, exithooks): + p = wx.Panel(panel) + sz = p.Sizer = VSizer() + + #get models + sort_models = profile.blist.sort_models + + import gui.lattice #@UnusedImport: side effect registering interfaces + choices = list(yield_choices(sort_models, p)) + exithooks.append(lambda:release_syncs(choices)) + + labels = [_('Group by:'), + _('Sort by:')] + \ + [_('Then by:')]*(len(sort_models)-2) + + to_add = [[(SText(p, label), 0, wx.ALIGN_CENTER_VERTICAL), + (choice, 1, EXPAND)] + for label,choice in zip(labels, choices)] + + s = wx.FlexGridSizer(len(sort_models), 2, 7,7) + s.AddMany(sum(to_add,[])) + s.AddGrowableCol(1,1) + sz.Add(s, 0, EXPAND) + + return p + +def contact_layout_panel(panel,pre,exithooks): + container_panel = wx.Panel(panel) + container_panel.Sizer = VSizer() + container_panel.ez_panel = None + container_panel.adv_panel = None + + prefix = 'buddylist.layout.' + + service = get_pref(prefix+'service_icon_pos') + se_enabled = get_pref(prefix+'show_service_icon') + status = get_pref(prefix+'status_icon_pos') + st_enabled = get_pref(prefix+'show_status_icon') + icon = get_pref(prefix+'buddy_icon_pos') + icon_enabled = get_pref(prefix+'show_buddy_icon') + + #heal some conflicting states + if not icon_enabled: + if status.startswith('b'): status = icon + if service.startswith('b'): service = icon + #service will be shown farther left if they're the same, so fix status first + #technically, these next two would be ok as long as they're both enabled, + #but let's get them to a consistent state ASAP + if status.startswith('f') and service != status[1:]: status = status[1:] + if service.startswith('f') and status != service[1:]: service = service[1:] + #other states may be conflicting, but do not cause problems, only those where + #the BUMP/VACUUM rules come into play need to be fixed + + container_panel.layout_model = LayoutModel( + service = service, + se_enabled = se_enabled, + status = status, + st_enabled = st_enabled, + icon = icon, + icon_enabled = icon_enabled, + ) + Cells = trellis.Cells + #hook up helper models + cells = Cells(container_panel.layout_model) + + #Trellis will keep the rest alive + container_panel.links = dict( + service_icon_pos = PrefLink(prefname=prefix+'service_icon_pos', value = cells['service']), + show_service_icon = PrefLink(prefname=prefix+'show_service_icon', value = cells['se_enabled']), + status_icon_pos = PrefLink(prefname=prefix+'status_icon_pos', value = cells['status']), + show_status_icon = PrefLink(prefname=prefix+'show_status_icon', value = cells['st_enabled']), + buddy_icon_pos = PrefLink(prefname=prefix+'buddy_icon_pos', value = cells['icon']), + show_buddy_icon = PrefLink(prefname=prefix+'show_buddy_icon', value = cells['icon_enabled']), + ) + + if pref('buddylist.layout.ez_layout', True): + easy_layout_panel(container_panel,pre) + else: + advanced_layout_panel(container_panel,pre,exithooks) + return container_panel + +def easy_layout_panel(container_panel,pre): + cp_sizer = container_panel.Sizer + + from gui.pref.noobcontactlayoutpanel import NoobContactLayoutPanel + ez_panel = container_panel.ez_panel = NoobContactLayoutPanel(container_panel) + + cp_sizer.Add(ez_panel, 0, EXPAND) + + +def advanced_layout_panel(container_panel, pre, exithooks): + cp_sizer = container_panel.Sizer + adv_panel = container_panel.adv_panel = wx.Panel(container_panel) + + adv_panel.Sizer = VSizer() + + v = VSizer() +#======================================================================================== +#HAX: This should be done differently +#======================================================================================== + cb = wx.CheckBox(adv_panel, -1, '') + adv_panel.Sizer.Add(v, 0, wx.ALIGN_CENTER_HORIZONTAL | RIGHT, cb.Size.width * 1.5) + cb.Destroy() +#======================================================================================== + old_spacing, PrefGroup.SPACING = PrefGroup.SPACING, 0 +#=============================================================================================================== + s = adv_panel.Sizer + prefix = 'buddylist.layout.' + Cells = trellis.Cells + cells = Cells(container_panel.layout_model) + + se_selection = Selection(value = cells['service'], + choices = cells['service_choices'], + enabled = cells['se_enabled']) + st_selection = Selection(value = cells['status'], + choices = cells['status_choices'], + enabled = cells['st_enabled']) + bi_selection = Selection(value = cells['icon'], + choices = ['left','right'], + enabled = cells['icon_enabled']) + #adapt for GUI models + translated = dict(fleft = _('Far Left'), + left = _('Left'), + bleft = _('Badge (Lower Left)'), + bright = _('Badge (Lower Right)'), + right = _('Right'), + fright = _('Far Right')) + se_choices = TranslatableChoices(keys=cells['service_choices'], translations = translated) + st_choices = TranslatableChoices(keys=cells['status_choices'], translations = translated) + bi_choices = TranslatableChoices(keys=['left','right'], translations = translated) + + #share a bunch of cells + service_model = ChoiceModel(values = Cells(se_choices)['values'], + selection = Cells(se_selection)['selection'], + enabled = cells['se_enabled']) + service_enabled = CheckModel( checked = Cells(service_model)['enabled']) + status_model = ChoiceModel(values = Cells(st_choices)['values'], + selection = Cells(st_selection)['selection'], + enabled = cells['st_enabled']) + status_enabled = CheckModel( checked = status_model.__cells__['enabled']) + icon_model = ChoiceModel(values = Cells(bi_choices)['values'], + selection = Cells(bi_selection)['selection'], + enabled = cells['icon_enabled']) + icon_enabled = CheckModel( checked = Cells(icon_model)['enabled']) + + service_check = SyncingCheck(service_enabled, adv_panel, label = _('Show service icon on:')) + service_choice = SyncingChoice(service_model, adv_panel) + status_check = SyncingCheck(status_enabled, adv_panel, label = _('Show status icon on:')) + status_choice = SyncingChoice(status_model, adv_panel) + icon_check = SyncingCheck(icon_enabled, adv_panel, label = _('Show buddy icon on the:')) + icon_choice = SyncingChoice(icon_model, adv_panel) + + need_del = [locals()[cat + '_' + typ] for cat in ['service', 'status', 'icon'] + for typ in ['check', 'choice']] + exithooks.append(lambda:release_syncs(need_del)) + + # Slider returns sizer, control + bicon_slide, s = Slider('buddy_icon_size', + _('Buddy icon size:'), + start = 12, stop = 65, step = 4, + value = get_pref('buddylist.layout.buddy_icon_size'), + fireonslide = True)(adv_panel,pre) + + buddypadding_slider, s2 = Slider('padding', + _('Buddy padding:'), + start = 0, stop = 12, step = 1, + value = get_pref('buddylist.layout.padding'), + fireonslide = True)(adv_panel, pre) + + v.Add(FontFaceAndSize('name_font', _('Contact name:'))(adv_panel, pre), 0, EXPAND) + extra_info = EnabledGroup(('', pre), + Check('buddylist.layout.show_extra', _('&Show extra info:')), + Choice('buddylist.layout.extra_info', + (('status', _('Status')), + ('idle', _('Idle Time')), + ('both', _('Idle Time + Status')))), + + FontFaceAndSize('extra_font'))(adv_panel) + bsizer = VSizer() + sssizer = VSizer() + + bsizer.AddMany([(extra_info, 0, EXPAND), + (OffsetGroup(('', pre), icon_check, icon_choice, bicon_slide )(adv_panel), 0, EXPAND)]) + + sssizer.AddMany([ + (OffsetGroup(('', pre), status_check, status_choice)(adv_panel), 0, EXPAND), + (OffsetGroup(('', pre), service_check, service_choice)(adv_panel), 0, EXPAND | TOP, 7), + (buddypadding_slider, 0, EXPAND | TOP, 22), + ]) + + h = HSizer() + h.AddMany([(bsizer, 1), + (sssizer, 0, LEFT, 48)]) + v.Add(h, 1, EXPAND) + +# cb(None) + + PrefGroup.SPACING = old_spacing + + cp_sizer.Add(adv_panel, 0, EXPAND) diff --git a/digsby/src/gui/pref/pg_dev.py b/digsby/src/gui/pref/pg_dev.py new file mode 100644 index 0000000..67cecac --- /dev/null +++ b/digsby/src/gui/pref/pg_dev.py @@ -0,0 +1,106 @@ +import wx +from gui.pref.prefcontrols import VSizer, Check, TOP, BOTTOM, EXPAND, ALL, pname,\ + Text, Label, HSizer +from gui.uberwidgets.PrefPanel import PrefPanel, PrefCollection +from gui.validators import NumericLimit +from common import setpref, pref + +def panel(panel, sizer, addgroup, exithooks): + top = HSizer() + top_right = VSizer() + + debug = PrefPanel(panel, + PrefCollection(Check('advanced_prefs', _('Advanced Prefs')), + Check('console', _('Enable Debug Console')), + Check('reenable_online', _('Allow Reconnect if --start-offline')), + layout = VSizer(), + itemoptions = (0, BOTTOM | TOP, 3)), + _('Debug'), + prefix = 'debug', + ) + + digsby = PrefPanel(panel, + PrefCollection(Check('allow_add', _('Allow Adding\n Digsby Buddies')), + layout = VSizer(), + itemoptions = (0, BOTTOM | TOP, 3)), + _('Digsby Protocol'), + prefix = 'digsby', + ) + + top.Add(debug, 1, EXPAND | ALL, 3) + top_right.Add(digsby, 1, EXPAND | ALL, 3) + top.Add(top_right, 1, EXPAND | ALL, 0) + + email_value_text = Text(panel, 'email.signature.value', + style = wx.TE_MULTILINE | wx.TE_AUTO_SCROLL )#| wx.TE_PROCESS_ENTER) + email_value_text.Enable(pref('email.signature.enabled', type = bool)) + email_value_text.SetMinSize((-1, 60)) + + email = PrefPanel(panel, + PrefCollection(Check('email.signature.enabled', _('Append signature'), + callback = email_value_text.Enable), + (email_value_text, 1, wx.LEFT | wx.EXPAND, 18), + layout = VSizer()), + _('Email'), + ) + + bottom = VSizer() + + bottom.Add(email, -1, EXPAND | ALL, 3) + #TODO: defaults for text fields. + try: + pref('research.percent') + except KeyError: + setpref('research.percent', 75) + try: + pref('research.revive_interval_seconds') + except KeyError: + setpref('research.revive_interval_seconds', 60*60) + + plura = PrefPanel(panel, + PrefCollection( + PrefCollection( + Check('local.research.enabled', _('Enabled'), default = True), + Check('research.debug_output', _("Print debug output to console. (don't use pipes)"), default = False), + Check('research.always_on', _('Always On'), default = False), + Check('research.battery_override', _('Run when on battery'), default = False), + layout = VSizer(), + itemoptions = (0, ALL, 3)), + PrefCollection( + Label('Percent:'), + lambda parent, prefix: Text(parent, pname(prefix, 'research.percent'), + validator=NumericLimit(2, 100), _type=int), + Label('Revive in x seconds:'), + lambda parent, prefix: Text(parent, pname(prefix, 'research.revive_interval_seconds'), _type=int), + layout = VSizer(), + itemoptions = (0, ALL, 3)), + layout = HSizer(), + itemoptions = (0, BOTTOM | TOP, 3)), + _('Plura'), + prefix = '', + ) + + social = PrefPanel(panel, + PrefCollection(Check('social.use_global_status', _('Use Global Status Dialog (may require restart)'), default = False), + Check('twitter.scan_urls', _('Scan tweets for URLs (for popup click action)'), default = False), + layout = VSizer(), + itemoptions = (0, BOTTOM | TOP, 3) + ), + _('Social accounts'), + prefix = '', + ) + + bottom.Add(top, 0, EXPAND | ALL, 0) + bottom.Add(plura, 0, EXPAND | ALL, 3) + bottom.Add(social, 0, EXPAND | ALL, 3) + sizer.Add(bottom, 0, EXPAND | BOTTOM) + + return panel + + + + + + + + diff --git a/digsby/src/gui/pref/pg_files.py b/digsby/src/gui/pref/pg_files.py new file mode 100644 index 0000000..1cbf583 --- /dev/null +++ b/digsby/src/gui/pref/pg_files.py @@ -0,0 +1,36 @@ +import wx +import stdpaths +from gui.pref.prefcontrols import * + +def default_save_dir(): + import os.path + return os.path.join(stdpaths.documents, 'Digsby Downloads') + + +def panel(panel, sizer, newgroup, exithooks): + newgroup((_('File Transfers'), 'filetransfer'), + + Browse('local.save_to', _('Save &files to:'), 'dir'), +# CheckBrowse('virus_scan', _('&Virus Scan:'), 'file'), + Check('create_subfolders', _('Create s&ubfolders for each IM account')), + Check('auto_accept_from_blist', _('&Auto-accept all file transfers from contacts on my contact list')), +# Check('use_port', _('Use &Port: %6(port)d')) + + ) + +# newgroup((_('AIM File Sharing'), 'oscar.peer'), +# +# CheckBrowse('share_files', _('Share &Folder:'), 'dir'), +# Check('auto_accept.sharing', _('Auto-accept all file sharing &requests from contacts on my contact list')), +# Check('use_port', _('Use Por&t: %6(port)d')) +# +# ) +# +# newgroup((_('AIM Direct Connect'), ''), +# +# Check('oscar.peer.auto_accept.direct_connect', +# _('Auto-accept all Direct Connect invitations from contacts on my contact list')), +# +# ) + + return panel diff --git a/digsby/src/gui/pref/pg_general_profile.py b/digsby/src/gui/pref/pg_general_profile.py new file mode 100644 index 0000000..1775e31 --- /dev/null +++ b/digsby/src/gui/pref/pg_general_profile.py @@ -0,0 +1,297 @@ +from __future__ import with_statement + +from common import profile +from config import platformName + +from gui.uberwidgets.formattedinput2.formatprefsmixin import PrefInput +import wx +import os +import sys +import stdpaths +from gui.pref.prefcontrols import * +import platform + +if platformName == 'mac' and int(platform.mac_ver()[0][3]) >= 5: + from gui.native.mac.maciconeditor import IconEditor +else: + from gui.pref.iconeditor import IconEditor + +def save_profile(new_profile): + profile.set_profile_blob(new_profile) + +def get_promote(): + return profile.prefs['profile.promote'] + + +def get_shortcut(pth): + import comtypes.client as client + shell = client.CreateObject('WScript.Shell') + scut = client.dynamic.Dispatch(shell.CreateShortcut(pth)) + + return scut + +def on_startup_change(new): + + if platformName == "mac": + import loginitems + appdir = os.path.abspath(os.path.join(sys.argv[0], "..", "..", "..")) + + if new and not loginitems.hasitem(appdir): + loginitems.additem(appdir) + elif not new and loginitems.hasitem(appdir): + loginitems.removeitem("Digsby") + + return + + from path import path + + shortcuts = startup_shortcuts() + if new and not shortcuts: + import sys, locale + exe = sys.executable.decode('locale') + + pth = stdpaths.userstartup /'digsby.lnk' + scut = get_shortcut(pth) + + scut.TargetPath = exe.encode('filesys') + scut.WorkingDirectory = unicode(path(exe).parent).encode('filesys') + scut.Save() + + elif shortcuts and not new: + # delete shortcuts + for scut in shortcuts: + try: + scut.remove() + except Exception, e: + log.info('Error removing shortcut %r: %r', scut, e) + continue + +def startup_shortcuts(): + if "wxMSW" in wx.PlatformInfo: + shortcuts = digsby_shortcuts(stdpaths.userstartup) + shortcuts.extend(digsby_shortcuts(stdpaths.startup)) + + return shortcuts + else: + return False + +def digsby_shortcuts(dir): + + from path import path + import sys, locale + + res = [] + + # FIXME: This shortcuts approach is, I think, unique to Windows. I think both + # Mac and Linux deal with registering apps in system prefs or the equivalent config files. + if not "wxMSW" in wx.PlatformInfo: + return res + + dir = path(dir) + exe = path(sys.executable.decode('locale')).basename().encode('filesys') + + if not dir.isdir(): + print 'warning, no startup dir', dir + return [] + + for lnk in dir.walk('*.lnk'): + scut = get_shortcut(lnk) + if exe == path(scut.TargetPath).basename(): + res.append(lnk) + + return res + + +from gui.uberwidgets.PrefPanel import PrefPanel,PrefCollection +def panel(panel, sizer, addgroup, exithooks): + + startup = wx.CheckBox(panel, label=_('&Launch Digsby when this computer starts')) + try: + startupval = bool(startup_shortcuts()) + except Exception: + startupval = False + startup.Disable() + + startup.Value = startupval + startup.Bind(wx.EVT_CHECKBOX, lambda e: on_startup_change(startup.Value)) + + # FIXME: Implement this for OS X (and GTK/Linux of course!) + + grp1 = PrefPanel( panel, + PrefCollection( + startup, + #Check('startup.launch_on_login', _('&Launch Digsby when this computer starts')), + #Check('startup.default_im_check', _('Check to see if &Digsby is the default IM client on this computer')), + Check('digsby.updater.auto_download', _("&Automatically download software updates")), + Check('login.reconnect.attempt', _('&If connection to IM service is lost, automatically attempt to reconnect')), + Check('social.feed_ads', _('Show trending news articles in social network feeds (powered by OneRiot)')), + layout = VSizer(), + itemoptions = (0,wx.BOTTOM|wx.TOP,3) + ), + _('General Options') + ) + + + p, input, chk = build_profile_panel(panel) + + panel.get_profile = lambda: input.GetFormattedValue() + panel.get_promote = lambda: chk.Value + + exithooks += lambda: save_profile(panel.get_profile()) + #exithooks += lambda: input.SaveStyle(input.formatpref) + + profile_grp = PrefPanel(panel, p, _('Profile (AIM Only)')) + + ico = build_buddy_icon_panel(panel, 'bicon') + + buddy_icon_grp = PrefPanel(panel, ico,_('Buddy Icon')) + + # Profile and Buddy Icon sizer + innersz = wx.BoxSizer(wx.HORIZONTAL) + innersz.Add(profile_grp, 1, wx.EXPAND) + innersz.Add(buddy_icon_grp, 0, wx.LEFT, 6) + + lang_choices = [ + ('en', 'English'), + ] + +# langpanel = VSizer() + langchoice = Choice('locale', lang_choices)(panel) +# langchoice.SetMinSize((200,-1)) +# langpanel.Add(langchoice) + + lang_grp = PrefPanel(panel, langchoice,_('Language')) + + sizer.AddMany([(grp1, 0, wx.EXPAND | wx.ALL, 3), + (innersz, 0, wx.EXPAND | wx.ALL, 3), + (lang_grp, 0, wx.EXPAND | wx.ALL, 3), + ])#(40, 40) + sizer.AddStretchSpacer() + + wx.CallAfter(input.SetFormattedValue, profile.profile) + + panel.Layout() + + return panel + + +class BitmapPanel(wx.Panel): + def __init__(self, *a, **k): + wx.Panel.__init__(self, *a, **k) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self._icon = None + + icon = property(lambda self: self._icon, # get + lambda self, i: (setattr(self, '_icon', i), # set + self.Refresh())) + + def OnPaint(self, e): + e.Skip() + icon, dc = self.icon, wx.PaintDC(self) + if icon is not None: + # draw a centered buddy icon + w, h = self.Size + assert isinstance(icon, wx.Bitmap), str(type(icon)) + + icopos = (w / 2 - icon.Width / 2, h / 2 - icon.Height / 2 ) + + dc.DrawBitmap(icon, *icopos) + + dc.Brush = wx.TRANSPARENT_BRUSH + dc.Pen = wx.Pen(wx.Color(213,213,213)) + dc.DrawRectangleRect(wx.RectPS(icopos,(icon.Width,icon.Height))) + +def build_buddy_icon_panel(parent, prefix): + p = wx.Panel(parent) + + icon_panel = BitmapPanel(p, size = (128, 128)) + + picon = profile.get_icon_bitmap() + + if picon is not None: + icon_panel.icon = picon.ResizedSmaller(128) + else: + icon_panel.icon = None + + button = wx.Button(p, -1, _('Change')) + if platformName == 'mac': + button.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + icon_button_flags = 0, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.BOTTOM, 4 + else: + icon_button_flags = 0, wx.EXPAND | wx.TOP, 5 + + def changeicon(e): + if IconEditor.RaiseExisting(): + return + + ie = IconEditor(p, profile.get_icon_bytes() or '') + + def onOK(): + if ie.ImageChanged: + log.info('iconeditor.ImageChanged = True') + import hub; h = hub.get_instance() + bytes = ie.Bytes + h.set_buddy_icon_file(bytes) + icon_panel.icon = profile.get_icon_bitmap() + + ie.Prompt(onOK) + + button.Bind(wx.EVT_BUTTON, changeicon) + + sz = p.Sizer = wx.BoxSizer(wx.VERTICAL) + sz.Add(icon_panel) + sz.Add(button, *icon_button_flags) + return p + + +def build_profile_panel(parent): + p = wx.Panel(parent) + + input = PrefInput(p, autosize = False, multiFormat = True, showFormattingBar = (platformName != 'mac'), skin = 'AppDefaults.FormattingBar', formatpref = 'profile.formatting') + + chk = Check('profile.promote', _('&Promote Digsby in my AIM profile'))(p) + chk.Value = get_promote() + + if platformName == 'mac': + # just show one font button on mac + checkbox = chk + chk = wx.BoxSizer(wx.HORIZONTAL) + chk.Add(checkbox) + chk.AddStretchSpacer(1) + chk.Add(input.CreateFontButton(p)) + chk.AddSpacer(4) + chk_sizer_flags = 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5 + else: + chk_sizer_flags = 0, wx.TOP, 3 + + s = p.Sizer = VSizer() + s.Add(input, 1, wx.EXPAND|wx.ALL, 1) + s.Add(chk, *chk_sizer_flags) + + + def OnPaintWithOutline(event): + dc = wx.AutoBufferedPaintDC(p) + + rect = wx.RectS(p.Size) + irect = wx.Rect(input.Rect) + irect.Inflate(1, 1) + + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + + dc.DrawRectangleRect(rect) + + dc.Pen = wx.Pen(wx.Color(213, 213, 213)) + dc.DrawRectangleRect(irect) + + p.Bind(wx.EVT_PAINT, OnPaintWithOutline) + + + return p, input, chk + + +def build_name_panel(parent): + s = wx.BoxSizer(wx.VERTICAL) + txt = Text(parent, 'profile.name') + s.Add(txt) + return s diff --git a/digsby/src/gui/pref/pg_helpdigsby.py b/digsby/src/gui/pref/pg_helpdigsby.py new file mode 100644 index 0000000..052085a --- /dev/null +++ b/digsby/src/gui/pref/pg_helpdigsby.py @@ -0,0 +1,18 @@ +''' +help digsby page +''' +import wx + +from gui.pref.prefcontrols import Check, VSizer +from gui.uberwidgets.PrefPanel import PrefPanel, PrefCollection + +def panel(panel, sizer, addgroup, exithooks): + collection = PrefCollection( + Check(None, _('&Allow Digsby to conduct research during idle itme')), + layout = VSizer(), + itemoptions = (0, wx.BOTTOM, 6)) + + help_group = PrefPanel(panel, collection, _('Help Digsby')) + sizer.Add(help_group) + return panel + diff --git a/digsby/src/gui/pref/pg_notifications.py b/digsby/src/gui/pref/pg_notifications.py new file mode 100644 index 0000000..c072711 --- /dev/null +++ b/digsby/src/gui/pref/pg_notifications.py @@ -0,0 +1,121 @@ +''' + +the "Notifications" panel in Preferences + +''' + +USE_TREE_VIEW = False + +import copy + +import wx +from gui.pref.prefcontrols import * +from gui.controls import Button + +from gui.uberwidgets.PrefPanel import PrefPanel,PrefCollection +from gui.notifications.notificationlist import NotifyPanel +from gui.toolbox import Monitor + +from common import pref +from config import platformName + +popupchoices = [ + ('lowerright', _('bottom right corner')), + ('lowerleft', _('bottom left corner')), + ('upperright', _('top right corner')), + ('upperleft', _('top left corner')), +] + +def panel(panel, sizer, newgroup, exithooks): + display_choices = [(n, str(n+1)) for n in xrange(Monitor.GetCount())] + + dddict = {'{location_dropdown}' : ('notifications.popups.location', popupchoices), + '{monitor_dropdown}' : ('notifications.popups.monitor', display_choices)} + + popup_panel = wx.Panel(panel) + popup_sizer = popup_panel.Sizer = HSizer() + + popupposstr = _('Enable &pop-up notifications in the {location_dropdown} on monitor {monitor_dropdown}') + + pattern = re.compile('(.*?)(\{\w*\})(.*?)(\{\w*\})(.*)') + + m = pattern.search(popupposstr) + startstr = m.group(1) + dd1 = m.group(2) + middlestr = m.group(3) + dd2 = m.group(4) + endstr = m.group(5) + + popup_sizer.Add(CheckChoice('notifications.enable_popup', dddict[dd1][0], + startstr, dddict[dd1][1])(popup_panel), + 0, wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) + + choice2 = Choice(dddict[dd2][0], dddict[dd2][1], caption = middlestr)(popup_panel) + choice2.Enable(get_pref('notifications.enable_popup')) + profile.prefs.add_observer(lambda *a: choice2.Enable(get_pref('notifications.enable_popup')), + 'notifications.enable_popup', + obj = panel) + popup_sizer.Add(choice2, 0, wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) + + if endstr: + popup_sizer.Add(Label(endstr)(popup_panel), 0, 3) + + + + top = PrefPanel( + panel, + PrefCollection( + popup_panel, + Check('enable_sound', _('Enable &sounds'))), + _('Notifications'), + prefix='notifications' + ) + + notifications_view = build_notification_area(panel) + notifications_view.SetFocus() + + bottom = PrefPanel(panel, notifications_view,_('Events')) + + restore_default_button = Button(panel, _('Restore Defaults'), + lambda: restore_default_notifications(notifications_view)) + if platformName == 'mac': + restore_default_button.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + + sizer.AddMany([ + (top, 0, wx.EXPAND | wx.ALL, 3), + (bottom, 1, wx.EXPAND | wx.ALL, 3), + (restore_default_button, 0, wx.BOTTOM | wx.LEFT, 4 if platformName == 'mac' else 0), + ]) + + return panel + +def restore_default_notifications(ctrl): + if wx.YES == wx.MessageBox(_('Are you sure you want to restore the default ' + 'notification set?\n\nAll of your notification ' + 'settings will be lost.'), + _('Restore Default Notifications'), + wx.ICON_EXCLAMATION | wx.YES_NO): + + import common.notifications as commnot + nots = profile.notifications + nots.clear() + nots.update(copy.deepcopy(commnot.default_notifications)) + ctrl.Refresh() + +def build_notification_area(parent): + notification_viewer = pref('notifications.editor.view', type=str, default='simple') + + if notification_viewer == 'tree': + # use the treelist view to edit notifications + from gui.notificationview import NotificationView + return NotificationView(parent, profile.notifications) + else: + # use a simple viewer with checkboxes for Popup and Sound + from common.notifications import get_notification_info + notifypanel = NotifyPanel(parent) + n = notifypanel.NotifyView + n.SetNotificationInfo(get_notification_info()) + n.SetUserNotifications(profile.notifications) + n.UpdateSkin() + return notifypanel + diff --git a/digsby/src/gui/pref/pg_plugins.py b/digsby/src/gui/pref/pg_plugins.py new file mode 100644 index 0000000..8b07cfa --- /dev/null +++ b/digsby/src/gui/pref/pg_plugins.py @@ -0,0 +1,11 @@ +import wx +from gui.pref.prefcontrols import * + +def panel(panel, sizer, newgroup, exithooks): + newgroup('Plugins', + PluginList(panel) + ) + return panel + +class PluginList(wx.VListBox): + pass \ No newline at end of file diff --git a/digsby/src/gui/pref/pg_privacy.py b/digsby/src/gui/pref/pg_privacy.py new file mode 100644 index 0000000..2eeee95 --- /dev/null +++ b/digsby/src/gui/pref/pg_privacy.py @@ -0,0 +1,1040 @@ +import wx +from gui.pref.prefcontrols import * + +from gui.uberwidgets.PrefPanel import PrefCollection, PrefPanel + +from util.primitives.error_handling import try_this +from util.primitives.funcs import get +from common import netcall, silence_notifications + +from logging import getLogger; log = getLogger('pg_privacy') + +wxMac = 'wxMac' in wx.PlatformInfo + +def panel(panel, sizer, newgroup, exithooks): + + gprivops = PrefPanel(panel, + PrefCollection(Check('send_typing_notifications', _('&Let others know that I am typing')), + Check('www_auto_signin', _('&Automatically sign me into websites (e.g., Yahoo! Mail)')), + layout = VSizer(), + itemoptions = (0,BOTTOM,6)), + _('Global Privacy Options'), + prefix = 'privacy') + + + privacy_panel = PrefPanel(panel) + PageFactory(privacy_panel, exithooks) + sizer.Add(gprivops, 0, EXPAND|BOTTOM,6) + sizer.Add(privacy_panel, 1, EXPAND) + + return panel + +def account_menuitem(a): + return [a.statusicon.ResizedSmaller(16), a.serviceicon.Resized(16), a.name] + +def account_stringitem(a): + return '%s (%s)' % (a.name, a.protocol_info().name + (', connected' if a.connected else '')) + +from config import platformName +def PageFactory(privacy_panel, exithooks): + _panels = {} + + def update_panels(): + for account in profile.account_manager.accounts: + if account not in _panels: + try: + panel = _panels[account] = GetAccountPanelType(account)(account, exithooks, privacy_panel) + except KeyError,e: + print 'Panel not defined for protocol %s,%r' % (account.protocol,e) + return + + if hasattr(panel, 'on_close'): + exithooks.append(panel.on_close) + + def accts_changed(*a, **k): + wx.CallAfter(_gui_accts_changed) + + def _gui_accts_changed(): + update_panels() + + accounts = profile.account_manager.accounts + if platformName != 'mac': + items = [(_panels[a], account_menuitem(a)) for a in accounts] + privacy_panel.SetContents(items) + privacy_panel.combo.menu.Refresh() + else: + # TODO: wxMac -- images in wxComboBox? + items = [(_panels[a], account_stringitem(a)) for a in accounts] + privacy_panel.SetContents(items) + + _gui_accts_changed() + obs_link = profile.account_manager.accounts.add_observer(accts_changed, obj = privacy_panel) + + def acct_changed(account, *a, **k): + wx.CallAfter(_gui_acct_changed, account) + + def _gui_acct_changed(account): + i = profile.account_manager.accounts.index(account) + item = privacy_panel.MenuItems[i] + item.content = account_menuitem(account) + + if wxMac: + privacy_panel.combo.SetString(i, account_stringitem(account)) + else: + privacy_panel.combo.menu.Refresh() + privacy_panel.combo.Refresh() + + for account in profile.account_manager.accounts: + account.add_observer(acct_changed, 'state', obj = privacy_panel) + + def remove_observers(): + accts = profile.account_manager.accounts + accts.remove_observer(accts_changed) + + for account in accts: + account.remove_observer(acct_changed,'state') + + exithooks.append(remove_observers) + + accs = profile.account_manager.accounts + occs = profile.account_manager.connected_im_accounts + + if len(occs): + privacy_panel.combo.SetSelection(accs.index(occs[0])) + privacy_panel.ChangeShownContent() + +def label_and_button(parent, text_label, button_lbl, button_cb): + sizer = HSizer() + label = wx.StaticText(parent, -1, text_label) + button = wx.Button(parent, -1, label=button_lbl, style=wx.BU_EXACTFIT) + button.Bind(wx.EVT_BUTTON, button_cb) + + sizer.Add(label, 0, wx.ALIGN_CENTER_VERTICAL) + sizer.AddSpacer(24) + sizer.AddStretchSpacer() + sizer.Add(button, 0, wx.ALIGN_RIGHT) + + return sizer + +class AcctPrivacyPanel(wx.Panel): + from common import profile + def __init__(self, acct, on_exit, *a, **k): + self.inited = False + + wx.Panel.__init__(self, *a, **k) + self.acct = acct + self.on_exit = on_exit + + self.on_exit += self.on_close + + self.acct.add_observer(self.acct_changed, 'state') + self.acct_will_be_saved = False + + self.editors = {} + + def acct_changed(self, *a, **k): + #TODO: plz unregister observers. + if self: + self.show_options(self.acct.is_connected) + + def build_panel(self): + sz = VSizer() + + ### Offline components + msg = SText(self, _("You must be signed in to modify privacy settings.")) + btn = wx.Button(self, -1, _('Sign in with "%s"') % self.acct.name, style=wx.BU_EXACTFIT) + if platformName == 'mac': + btn.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + btn.Bind(wx.EVT_BUTTON, lambda e: (btn.Enable(False), self.acct.connect())) + self._btn = btn + + sz.Add(msg, 0, wx.EXPAND) + sz.Add(btn, 0, wx.ALL, 4) + sz.AddStretchSpacer() + + sz.Enable(False) + sz.ShowItems(False) + + self.offline_components = sz + + def show_options(self, online): + old = self.Sizer + new = self.online_components if online else self.offline_components + + self.Freeze() + + if not new: return + self.SetSizer(new, False) + + if old is not None and old is not new: + old.Enable(False) + old.ShowItems(False) + + self.Sizer.Enable(True) + self.Sizer.ShowItems(True) + + self._btn.Enable(not (self.acct.connection and self.acct.connection.state)) + + self.Thaw() + +# self.Fit() + self.Layout() + + + def mark_for_save(self): + if not self.acct_will_be_saved: + self.acct_will_be_saved = True + self.on_exit += self.acct.update + + def on_close(self): + for cls, ed in self.editors.items(): + self.kill_editor(ed) + self.editors.pop(cls) + + self.acct.remove_observer(self.acct_changed, 'state') + + def editor_closed(self,cls,ed,e): + self.kill_editor(ed) + self.editors.pop(cls, None) + + def kill_editor(self, ed): + ed.on_close() + ed.Hide() + ed.Destroy() + + def show_editor(self, cls): + + if cls not in self.editors: + ed = self.editors[cls] = cls(self.acct.connection, self) + ed.Bind(wx.EVT_CLOSE, lambda e: self.editor_closed(cls, ed, e)) + + ed = self.editors[cls] + + if not ed.IsShown(): + ed.Show() + + ed.SetFocus() + +class AcctListEditor(AutoCompleteListEditor): + def __init__(self, protocol, *a, **k): + self.protocol = protocol + AutoCompleteListEditor.__init__(self, *a, **k) + if 'wxMac' in wx.PlatformInfo: + self.SetSize((400, 300)) + self.CenterOnParent() + self.Title = self.make_title() + + from gui import skin + ico = wx.IconFromBitmap(skin.get('serviceicons.%s' % self.protocol.service)) + self.SetIcon(ico) + + self.list.OnDelete = self.remove_item + + def make_title(self): + return _('{title} ({username})').format(title = self.title, username = self.protocol.username) + + def __contains__(self, thing): + if thing in self.protocol.buddies: + buddy = self.protocol.buddies[thing] + else: + buddy = None + + parent = AutoCompleteListEditor.__contains__ + + if parent(self, thing): + return True + elif buddy is not None and parent(self, buddy): + return True + + return False + +def CheckGridList(parent, check_list, choices, acct, checkcallback, buttoncallback): + perm_pct = [] + + for i,(_string, args) in enumerate(choices): + style = 0 if i else wx.RB_GROUP + rb = wx.RadioButton(parent, -1, label=_string, style=style) + + rb.Bind(wx.EVT_RADIOBUTTON, lambda e,a=args: checkcallback(a)) + + check_list.append(rb) + perm_pct.append((rb, (i, 0), (1, 1), ALIGN_CENTER_VERTICAL)) + + if 'list' in args: + try: + cls = globals()['%s%sListEditor' % (acct.protocol.upper(), args[-1])] + except KeyError: + cls = globals()['%s%sListEditor' % (acct.protocol.capitalize(), args[-1])] + + btn = wx.Button(parent, -1, _('Edit'), style=wx.BU_EXACTFIT) + if platformName == 'mac': + btn.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + btn.Bind(wx.EVT_BUTTON, lambda e, _cls=cls: buttoncallback(_cls)) + + perm_pct.append((btn,(i,1),(1,1),ALIGN_CENTER_VERTICAL)) + + return perm_pct + +def CheckVList(parent, check_list, choices, checkcallback): + perm_pct = [] + + for i,(_string, args) in enumerate(choices): + style = 0 if i else wx.RB_GROUP + rb = wx.RadioButton(parent, -1, label=_string, style=style) + + rb.Bind(wx.EVT_RADIOBUTTON, lambda e,a=args: checkcallback(a)) + rb._cb_args = args + + check_list.append(rb) + perm_pct.append(rb) + + return perm_pct + +class AIMPrivacyPanel(AcctPrivacyPanel): + perm_choices = [ + (_('Allow all users to contact me'), (True, 'all')), + (_('Allow only users on my contact list'), (True, 'contacts')), + (_("Allow only users on 'Allow List'"), (True, 'list', 'Allow')), + (_('Block all users'), (False, 'all')), + (_("Block only users on 'Block List'"), (False, 'list', 'Block'))] + + opts_choices = [(_('Only my screen name'), (True, )), + (_('Only that I have an account'), (False, )), + (_('Nothing about me'), (None, ))] + + def __init__(self, *a, **k): + AcctPrivacyPanel.__init__(self, *a, **k) + + self.build_panel() + self._got_search = False + self.acct_changed() + + + def build_panel(self): + AcctPrivacyPanel.build_panel(self) + ### Online components + + acct = self.acct + + self.perm_rbs = perm_rbs = [] + + + def rb_callback(a): + acct.connection.set_privacy(*a) + silence_notifications(acct.connection) + + + perms = PrefPanel(self, + PrefCollection(layout = wx.GridBagSizer(hgap = 6), + *CheckGridList(self, perm_rbs,self.perm_choices,acct, rb_callback,self.show_editor)), + _('Permissions')) + + + self.opts_rbs = opts_rbs = [] + + opts = PrefPanel(self, + PrefCollection( + wx.StaticText(self,-1,_('Allow users who know my email address to find:')), + layout = VSizer(), + itemoptions=(0,BOTTOM,6), + *CheckVList(self, opts_rbs, self.opts_choices, lambda a: acct.connection.set_search_response(*a))), + + _('Options')) + + sz = VSizer() + sz.Add(perms,0,EXPAND) + sz.Add(opts,0,EXPAND) + sz.ShowItems(False) + + self.online_components = sz + + def set_radio(self, val): + ''' + value coming in: + disclosure: True = full + False= limited + None = none + ''' + log.info('Got value for radio button: %r', val) + for rb in self.opts_rbs: + if rb._cb_args == (val,): + rb.SetValue(True) + + self._got_search = True + + def acct_changed(self,*a, **k): + #TODO: plz unregister me + if not self: return + + try: + val = ord(self.acct.connection.ssimanager.get_privacy_ssi().tlvs[0xca]) + except: + try: + val = self.acct.connection.ssimanager.get_privacy_ssi().tlvs[0xca].value + except: + val = -1 + + # magic avert your eyes + val = {-1: 0, 0: 0, 1: 0, 2: 3, 3: 2, 4: 4, 5: 1,}[val] + self.perm_rbs[val].SetValue(True) + + conn = self.acct.connection + + if conn and conn.connected and not self._got_search: + + if conn._search_disclosure is not sentinel: + self.set_radio(conn._search_disclosure) + + conn.get_search_response(success=lambda a: wx.CallAfter(self.set_radio,a)) + + elif not conn or not conn.connected: + self._got_search = False + + + AcctPrivacyPanel.acct_changed(self, *a, **k) + +_lowerstrip = lambda s: s.lower().replace(' ','') +class AIMListEditor(AcctListEditor): + + def validate(self, _str): + return bool(_str) + + def get_matches(self, _str): + _str = _lowerstrip(_str) + return filter(lambda s: _lowerstrip(s).startswith(_str), + (x.name for x in self.protocol.buddies.values() if x not in self)) + + def get_autovalues(self): + return sorted((x.name for x in self.protocol.buddies.values() if x.name not in self), key=lambda s: _lowerstrip(s)) + +class AIMBlockListEditor(AIMListEditor): + title = _('Block List') + + def get_values(self): + return list(self.protocol.block_list) + +# return sorted(set(self.protocol.buddies[s.name] +# for s in self.protocol.ssimanager.find(type=3)), key=lambda x: _lowerstrip(x.name)) + + def add_item(self, _str): + _str = _str.encode('utf-8') + b = self.protocol.buddies[_str] + + if b.name in self: + return + + b.block(True) + + AIMListEditor.add_item(self,b.name) + + def remove_item(self, _str): + b = self.protocol.buddies[_str] + + if b.name not in self: + return + + b.block(False) + + + AIMListEditor.remove_item(self, b.name) + +class AIMAllowListEditor(AIMListEditor): + title = 'Allow List' + def get_values(self): + return sorted(set(self.protocol.buddies[s.name].name + for s in self.protocol.ssimanager.find(type=2)), key=lambda x:_lowerstrip(x)) + + def add_item(self, _str): + _str = _str.encode('utf-8') + b = self.protocol.buddies[_str] + + if b.name in self: + return + + b.permit(True) + + AIMListEditor.add_item(self,b.name) + + def remove_item(self, _str): + b = self.protocol.buddies[_str] + + if b.name not in self: + return + + b.permit(False) + + AIMListEditor.remove_item(self,b.name) + +class ICQBlockListEditor(AIMListEditor): +#class ICQInvisibleListEditor(AIMListEditor): + title = _('Block List') +# title = 'Invisible List' + + def get_values(self): + return list(self.protocol.ignore_list) + + def add_item(self, _str): + buddy = self.protocol.buddies[_str.encode('utf-8')] + + if _str in self or buddy in self: + return + + self.protocol.block(buddy, success=lambda *a, **k:AIMListEditor.add_item(self,_str)) + + def remove_item(self, _str): + + buddy = self.protocol.buddies[_str.encode('utf-8')] + + if _str not in self and buddy not in self: + return + + self.protocol.block(buddy, False, success=lambda *a, **k:AIMListEditor.remove_item(self, _str)) + +class ICQVisibleListEditor(AIMAllowListEditor): + title = _('Visible List') + +class ICQInvisibleListEditor(AIMBlockListEditor): +#class ICQBlockListEditor(AIMBlockListEditor): + title = _('Invisible List') +# title = 'Block List' + + def add_item(self, _str): + buddy = self.protocol.buddies[_str.encode('utf-8')] + + if _str in self or buddy in self: + return + + self.protocol.ignore(buddy, success=lambda *a, **k:AIMListEditor.add_item(self,_str)) + + def remove_item(self, _str): + b = self.protocol.buddies[_str.encode('utf8')] + + if _str not in self and b not in self: + return + + self.protocol.unignore(b, success=lambda *a, **k:AIMListEditor.remove_item(self, _str)) + +class ICQPrivacyPanel(AcctPrivacyPanel): + perm_choices = [(_('Allow all users to contact me'), 0, (False,)), + (_('Allow only users on my contact list'), 1, (True,)), + ] + + def __init__(self, *a, **k): + AcctPrivacyPanel.__init__(self, *a, **k) + self.build_panel() + self.acct_changed() + + def build_panel(self): + AcctPrivacyPanel.build_panel(self) + + block_unknowns = wx.CheckBox(self, -1, label=_('Allow only users on my buddy list to contact me')) + req_auth = wx.CheckBox(self, -1, label=_('Require authorization before users can add me to their contact list')) + block_urls = wx.CheckBox(self, -1, label=_('Block authorization requests with URLs in them')) + web_status = wx.CheckBox(self, -1, label=_('Allow others to view my online status from the web')) + + req_auth.Bind(wx.EVT_CHECKBOX, self.auth_changed) + block_urls.Bind(wx.EVT_CHECKBOX, self.block_urls_changed) + web_status.Bind(wx.EVT_CHECKBOX, self.webaware_changed) + block_unknowns.Bind(wx.EVT_CHECKBOX, self.block_changed) + + lists = [] + + for labelclass in ((_('Block List'), ICQBlockListEditor), (_('Visible List'), ICQVisibleListEditor), (_('Invisible List'), ICQInvisibleListEditor)): + lists.append(Label(labelclass[0])) + lists.append(Button(_('Edit'),lambda b, cls=labelclass[1]: self.show_editor(cls))) + + perms = PrefPanel(self, + PrefCollection(block_unknowns, + req_auth, + block_urls, + web_status, + PrefCollection(layout = wx.GridSizer(cols = 2, hgap = 6), + itemoptions = (0,BOTTOM|ALIGN_CENTER_VERTICAL,3), + *lists), + layout = VSizer(), + itemoptions = (0,BOTTOM|ALIGN_CENTER_VERTICAL,6)), + _('Permissions')) + + + + self._web_status = web_status + self._req_auth = req_auth + self._block_urls = block_urls + self._block_unknowns = block_unknowns + + + sz = VSizer() + sz.Add(perms, 0, wx.EXPAND|wx.ALL) + sz.Enable(False) + sz.ShowItems(False) + + self.online_components = sz + self.get_check_values() + + def webaware_changed(self, e): + v = self._web_status.Value + self.acct.webaware = v + + conn = self.acct.connection + if conn is not None: + conn.set_webaware(v) + silence_notifications(conn) + self.mark_for_save() + + def auth_changed(self, e): + v = self._req_auth.Value + self.acct.auth_required = v + + conn = self.acct.connection + + if conn is not None: + conn.set_auth_required(v) + silence_notifications(conn) + + self.mark_for_save() + + def acct_changed(self, *a, **k): + if not self: return + self.get_check_values() + AcctPrivacyPanel.acct_changed(self, *a, **k) + + def block_urls_changed(self, e): + self.acct.block_url_requests = bool(self._block_urls.Value) + if self.acct.connection is not None: + self.acct.connection.block_url_requests = self.acct.block_url_requests + self.mark_for_save() + + def block_changed(self,e ): + a = self.acct + a.block_unknowns = bool(self._block_unknowns.Value) + + if a.connection is not None: + a.connection.block_unknowns = a.block_unknowns + silence_notifications(a.connection) + self.mark_for_save() + + def get_check_values(self): + #self._web_status.Value = try_this(lambda: self.acct.connection.webaware, False) + self._web_status.Value = self.acct.webaware + self._req_auth.Value = self.acct.auth_required + self._block_unknowns.Value = self.acct.block_unknowns + self._block_urls.Value = self.acct.block_url_requests + +# if c is not None: +# c.get_icq_privacy(success=lambda x,y:(req_auth.SetValue(x), +# web_status.SetValue(y))) + + +class MSNListEditor(AcctListEditor): + + def get_matches(self, _str): + return [b[0] for b in filter(lambda s: s[0].startswith(_str) or s[1].startswith(_str), + ((x.name, x.friendly_name or '') + for x in self.protocol.buddies.values()))] + + def get_autovalues(self): + return sorted(x.name for x in self.protocol.buddies.values() if x not in self) + + def validate(self, _str): + import util + return util.is_email(_str) + +class MSNBlockListEditor(MSNListEditor): + title = _('Block List') + def get_values(self): + return sorted(x.name for x in self.protocol.block_list) + + def add_item(self, _str): + netcall(lambda: self.protocol.add_to_block + (_str, success=lambda *a: wx.CallAfter + (lambda: MSNListEditor.add_item(self,_str)))) + + def remove_item(self, _str): + netcall(lambda: self.protocol.rem_from_block + (_str, success=lambda *a: (self.protocol.add_to_allow(_str), wx.CallAfter + (lambda: MSNListEditor.remove_item(self,_str))))) + +class MSNAllowListEditor(MSNListEditor): + title = _('Allow List') + def get_values(self): + return sorted(x.name for x in self.protocol.allow_list) + + def add_item(self, _str): + netcall(lambda: self.protocol.add_to_allow + (_str, success=lambda *a, **k: wx.CallAfter + (lambda: MSNListEditor.add_item(self,_str)))) + + def remove_item(self, _str): + netcall(lambda: self.protocol.rem_from_allow + (_str, success=lambda *a, **k: wx.CallAfter + (lambda: MSNListEditor.remove_item(self,_str)))) + +class MSNPrivacyPanel(AcctPrivacyPanel): + perm_choices = [ + (_('Allow unknown users to contact me'), 0, ()), + (_("Allow only users on 'Allow List'"), 1, ()), + (_("Block only users on 'Block List'"), 2, ()), + ] + + def __init__(self, *a, **k): + AcctPrivacyPanel.__init__(self, *a, **k) + +# conn = get(self.acct, 'connection', None) +# if conn is not None: +# conn.self_buddy.add_observer(self.privacy_changed,'allow_unknown_contacts') +# self.observing_privacy = True +# else: +# self.observing_privacy = False + + self.observing_privacy = False + + self.build_panel() + #self.bind_evts() + + self.acct_changed() + + +# def bind_evts(self): +# self.options.Bind(wx.EVT_RADIOBOX, self.opts_changed) +# +# def opts_changed(self, e): +# print e.GetString() + + def build_panel(self): + AcctPrivacyPanel.build_panel(self) + ### Online components + +# p_box = wx.StaticBox(self,-1, 'Permissions') +# perm_sz = MakeEnabledSizer(wx.StaticBoxSizer)(p_box,wx.VERTICAL) + + check = wx.CheckBox(self, 1, label=_("Allow unknown users to contact me")) + check.Bind(wx.EVT_CHECKBOX, self.chk_changed) + + self.check = check + + perms = PrefPanel(self, + PrefCollection(check, + PrefCollection(Label(_('Allow List')), + Button(_('Edit'),lambda *a : self.show_editor(MSNAllowListEditor)), + Label(_('Block List')), + Button(_('Edit'),lambda *a : self.show_editor(MSNBlockListEditor)), + + layout = wx.GridSizer(cols=2, hgap=6), + itemoptions = (0, ALIGN_CENTER_VERTICAL|BOTTOM, 6)), + + layout = VSizer(), + itemoptions = (0,BOTTOM,6)), + _('Permissions')) + + + sz = VSizer() + sz.Add(perms, 0, wx.EXPAND | wx.ALL, 3) + sz.ShowItems(False) + + self.online_components = sz + self.mobile_changed() + + #perm_sz.Add(unknown) + + ### Options removed per ticket 1534 + +# o_box = wx.StaticBox(self, -1, 'Options') +# opts_sz = MakeEnabledSizer(wx.StaticBoxSizer)(o_box, wx.VERTICAL) +# add_alert = wx.CheckBox(self, -1, 'Alert me when other people add me to their contact list') +# +# from msn import protocol as msnp +# mob_link_cfg = wx.HyperlinkCtrl(self, -1, 'Click here to configure mobile settings', +# msnp.mobile_edit_url) +# mob_link_set = wx.HyperlinkCtrl(self, -1, 'Mobile settings', +# msnp.mobile_enable_url) +# +# mob_allow_chk = wx.CheckBox(self, -1, 'Allow my contacts to message my mobile device (') +# mob_allow_par = wx.StaticText(self, -1, ')') +# +# self.mob_sz = mob_sz = {} +# mob_sz[False] = offsz = HSizer() +# mob_sz[True] = onsz = HSizer() +# +# #offsz.AddSpacer(20) +# offsz.Add(mob_link_cfg) +# onsz.AddMany([mob_allow_chk, mob_link_set, mob_allow_par]) +# +# opts_sz.AddMany([add_alert, offsz, onsz]) +# +# add_alert.Bind(wx.EVT_CHECKBOX, self.add_checked) +# mob_allow_chk.Bind(wx.EVT_CHECKBOX, self.mob_checked) + + + def add_checked(self, e): + val = e.EventObject.Value + self.acct.connection.send_gtc('A' if val else 'N') + + def mob_checked(self, e): + val = e.EventObject.Value + self.acct.connection.send_prp('MOB', 'Y' if val else 'N') + + def chk_changed(self, e): + c = self.acct.connection + c.set_blist_privacy(allow_unknowns=bool(self.check.Value)) + silence_notifications(c) + + def privacy_changed(self, src, attr, old, new): + self.check.Value = new + + def acct_changed(self, *a, **k): + if wx.IsDestroyed(self): + return log.warning('pg_privacy is getting notified but is destroyed. FIX ME') + + try: val = int(self.acct.connection.allow_unknown_contacts) + except: val = 0 + + self.check.SetValue(val) + AcctPrivacyPanel.acct_changed(self, *a, **k) + + conn = get(self.acct, 'connection',None) + if self.observing_privacy and not conn: + self.observing_privacy = False + + elif not self.observing_privacy and conn: + conn.add_observer(self.privacy_changed,'allow_unknown_contacts') + self.observing_privacy = True + else: + assert bool(self.observing_privacy) == bool(conn), (conn, self.observing_privacy) + + def mobile_changed(self, *args): + + # Returning from this method early as it is no longer used, + # but might come back in the future + # see ticket #1534 + return + + + if not self: + return log.critical('mobile_changed is an unregistered observer!') + + val = try_this(lambda: self.acct.connection.self_buddy.enable_mobile, False) + + assert type(val) is bool, (val, type(val)) + + self.Freeze() + + self.online_components.Hide(self.mob_sz[not val], recursive=True) + + if self.acct.is_connected: f = self.online_components.Show + else: f = self.online_components.Hide + f(self.mob_sz[val],recursive=True) + +# self.mob_sz[not val].Show(False, recursive=True) +# self.mob_sz[val].Show(True, recursive=True) + + self.Thaw() + self.Fit() + self.Layout() + + def on_close(self): + if self.observing_privacy: + self.acct.connection.remove_observer(self.privacy_changed,'allow_unknown_contacts') + self.observing_privacy = False + +class YahooBlockListEditor(AcctListEditor): + title = _('Block List') + + def get_matches(self, _str): + return filter(lambda s: s.startswith(_str),(x.name for x in self.protocol.buddies.values())) + + def get_autovalues(self): + return sorted(x.name for x in self.protocol.buddies.values() if not x.blocked) + + def validate(self, _str): + return True + + def get_values(self): + return sorted(x.name for x in self.protocol.buddies.values() if x.blocked) + + def add_item(self, _str): + + + def on_success(): + print 'blocked buddy, adding to list' + wx.CallAfter(AcctListEditor.add_item, self,_str) + + def on_error(): + wx.MessageBox(_('Could not block {item:s}').format(item=_str)) + + def do_block(): + print 'removed buddy, now blocking' + self.protocol.block_buddy(_str, success = on_success, error = on_error) + + if _str in self.protocol.buddies: + if wx.YES == wx.MessageBox\ + (_('{item:s} is on your buddylist. Do you want to remove {item:s}' + ' from your buddylist and block the user?').format(item=_str), + _('Confirm Block and Remove'), wx.YES_NO): + + self.protocol.remove_buddy(_str, success = do_block, error = do_block) + else: + do_block() + + def remove_item(self, _str): + self.protocol.unblock_buddy(_str, success = \ + lambda *a, **k: wx.CallAfter(AcctListEditor.remove_item, self,_str)) + +class YahooPrivacyPanel(AcctPrivacyPanel): + perm_choices = [ + (_('Allow only users on my contact list'), (True, 'contacts')), + (_("Block only users on 'Block List'"), (False, 'list', 'Block'))] + + def __init__(self, *a, **k): + AcctPrivacyPanel.__init__(self, *a, **k) + self.build_panel() + self.acct_changed() + + def build_panel(self): + AcctPrivacyPanel.build_panel(self) + + self.rbs = rbs = [] + + perms = PrefPanel(self, + PrefCollection(layout = wx.GridBagSizer(hgap = 6), + *CheckGridList(self, rbs,self.perm_choices,self.acct, self.rb_changed,self.show_editor)), + _('Permissions')) + + sz = VSizer() + sz.Add(perms,0,EXPAND) + sz.ShowItems(False) + + self.online_components = sz + + def acct_changed(self, *a, **k): + if not self: return + self.rbs[not self.acct.block_unknowns].Value = True + AcctPrivacyPanel.acct_changed(self, *a, **k) + + def rb_changed(self, args): + (block_unknowns, who) = args[:2] + self.mark_for_save() + silence_notifications(self.acct.connection) + self.acct.connection.block_unknowns = self.acct.block_unknowns = block_unknowns + +# def webtoggle(self, e): +# print 'web pref toggled' + +class JabberPrivacyPanel(AcctPrivacyPanel): + def __init__(self, *a, **k): + AcctPrivacyPanel.__init__(self, *a, **k) + self.build_panel() + self.inited = True + self.acct_changed() + + def build_panel(self): + AcctPrivacyPanel.build_panel(self) + + sz = VSizer() + + allow_chk = wx.CheckBox(self, -1, _('Allow only users on my buddy list to contact me')) + allow_chk.Bind(wx.EVT_CHECKBOX, self.allow_changed) + self.allow_chk = allow_chk + + + hide_os_chk = wx.CheckBox(self, -1, _('Hide operating system from other users')) + hide_os_chk.Bind(wx.EVT_CHECKBOX, self.cb_changed) + self.hide_os_chk = hide_os_chk + + perms = PrefPanel(self, + PrefCollection(allow_chk, + hide_os_chk, + layout = VSizer(), + itemoptions = (0,BOTTOM,6)), + _('Permissions')) + + sz.Add(perms, 0, wx.EXPAND| wx.ALL) + + sz.ShowItems(False) + + self.online_components = sz + + def allow_changed(self, e): + self.mark_for_save() + self.acct.connection.block_unknowns = self.acct.block_unknowns = bool(self.allow_chk.Value) + + def cb_changed(self, e): + self.mark_for_save() + self.acct.connection.hide_os = self.acct.hide_os = bool(self.hide_os_chk.Value) + + def acct_changed(self, *a, **k): + if not self or not self.inited: return + self.hide_os_chk.Value = self.acct.hide_os + self.allow_chk.Value = self.acct.block_unknowns + + AcctPrivacyPanel.acct_changed(self, *a, **k) + +class EmptyPrivacyPanel(AcctPrivacyPanel): + def __init__(self, *a, **k): + AcctPrivacyPanel.__init__(self, *a, **k) + self.build_panel() + self.inited = True + self.acct_changed() + + def build_panel(self): + AcctPrivacyPanel.build_panel(self) + + sz = VSizer() + + ### Online components + msg = SText(self, "Nothing to see here. If you do see this, this is either a special dev-only account, or something is broken.") + + sz.Add(msg, 0, wx.EXPAND) + sz.AddStretchSpacer() + + sz.Enable(False) + sz.ShowItems(False) + + self.online_components = sz + +class FBPrivacyPanel(AcctPrivacyPanel): + def __init__(self, *a, **k): + AcctPrivacyPanel.__init__(self, *a, **k) + self.build_panel() + self.inited = True + self.acct_changed() + + def build_panel(self): + sz = VSizer() + + ### Online components + msg = wx.HyperlinkCtrl(self, -1, "http://www.facebook.com/privacy/", + "http://www.facebook.com/privacy/") + + sz.Add(msg, 0, wx.EXPAND) + sz.AddStretchSpacer() + + sz.Enable(True) + sz.ShowItems(True) + + self.online_components = self.offline_components = sz + self.SetSizer(sz) + + def show_options(self, online): + pass +# self.Freeze() +# self.Thaw() +# self.Layout() + +class GTalkPrivacyPanel(JabberPrivacyPanel): + pass + +acct_to_panel = dict(aim = AIMPrivacyPanel, + icq = ICQPrivacyPanel, + msn = MSNPrivacyPanel, + yahoo = YahooPrivacyPanel, + jabber= JabberPrivacyPanel, + gtalk = GTalkPrivacyPanel, + fbchat= FBPrivacyPanel) + +def GetAccountPanelType(account): + log.info('getting privacy panel for %r', account) + proto_class = account.protocol_class() + if hasattr(proto_class, 'get_privacy_panel_class'): + return proto_class.get_privacy_panel_class() + else: + return acct_to_panel.get(account.protocol, EmptyPrivacyPanel) diff --git a/digsby/src/gui/pref/pg_proxy.py b/digsby/src/gui/pref/pg_proxy.py new file mode 100644 index 0000000..598774b --- /dev/null +++ b/digsby/src/gui/pref/pg_proxy.py @@ -0,0 +1,11 @@ +import wx +import gui.proxydialog as proxydialog + +from logging import getLogger; log = getLogger('pg_proxy') + +def panel(p, sizer, addgroup, exithooks): + pp = proxydialog.ProxyPanel(p) + sizer.Add(pp, 0, wx.EXPAND | wx.ALL) + p.on_close = pp.OnOK + + return p diff --git a/digsby/src/gui/pref/pg_research.py b/digsby/src/gui/pref/pg_research.py new file mode 100644 index 0000000..1f32449 --- /dev/null +++ b/digsby/src/gui/pref/pg_research.py @@ -0,0 +1,136 @@ +import wx + +import config +import gui.pref.prefcontrols as PC +from gui.uberwidgets.PrefPanel import PrefPanel, PrefCollection + +from common import setpref, pref +from util import traceguard +from gui.browser.webkit import WebKitDisplay + +WALL_OF_TEXT = _( +u'''\ +Help Digsby stay free for all users. Allow Digsby to use part of your computer's idle processing power to contribute to commercial grid computing projects by enabling the Research Module. +

+This module turns on after your computer has been completely idle (no mouse or keyboard movement) for a period of time. It turns off the instant you move your mouse or press a key, so it has no effect on your PC's performance when you're using it. The research module also runs as a low priority, sandboxed Java process, so that other tasks your computer is doing will get done first, and so that it is completely secure. +

+For more details, see the Research Module FAQ. +''' +) + +# TODO move all this into the WebKitDisplay class +if config.platform == 'win': + import gui.native.win.winutil as winutil + + if winutil.is_vista(): + font = 'Segoe UI, Tahoma, MS Sans Serif; font-size: 9pt;' + else: + font = 'Tahoma, MS Sans Serif; font-size: 11px;' +else: + font = 'Arial 12px' # TODO: other platforms. + + +css = u'''\ +body { +margin: 0; +overflow: hidden; +cursor: default; +background-color: white; +font-family: %s; +-webkit-text-size-adjust: none; +-webkit-user-select: none; +}''' % font + +description_html = u''' +

{body}'''.format( + css=css, + body=WALL_OF_TEXT) + +def build_description_webview(parent, prefix): + webview = WebKitDisplay(parent) + + # TODO: this entire figure-out-the-size-of-the-webview is also repeated in + # infobox.py. We need to add a method to wxWebView that says: layout your + # content at this width, and return your height. + + webview._loaded = False + def on_load(e): + e.Skip() + if e.GetState() == wx.webview.WEBVIEW_LOAD_ONLOAD_HANDLED: + webview._loaded = True + + def on_timer(): + if not webview._loaded: + return + + webview._timer.Stop() + with traceguard: + height = webview.RunScript('document.getElementById("container").clientHeight') + height = int(height) + webview.SetMinSize((-1, height)) + webview.GrandParent.Layout() + parent.Thaw() + + webview.SetMinSize((-1, 160)) + webview._timer = wx.PyTimer(on_timer) + webview._timer.StartRepeating(50) + webview.Bind(wx.webview.EVT_WEBVIEW_LOAD, on_load) + + webview.SetPageSource(description_html) + parent.Freeze() + return webview + + +def panel(panel, sizer, addgroup, exithooks): + + try: + import researchdriver.driver + except ImportError: + default_cpu_num = 75 + default_bandwidth_num = 90 + else: + default_cpu_num = int(researchdriver.driver.get_cpu_percent()) + default_bandwidth_num = int(researchdriver.driver.get_bandwidth_percent()) + + description = PrefPanel( + panel, + build_description_webview, + _('Research Module'), + prefix = '', + ) + options = PrefPanel(panel, + PrefCollection( + PrefCollection( + PC.Check('local.research.enabled', + _('Allow Digsby to use CPU time to conduct research after %2(research.idle_time_min)d minutes of idle time')), + layout = PC.VSizer(), + itemoptions = (0, wx.ALL, 3), + ), + PrefCollection( + lambda parent, prefix: PC.Slider(PC.pname(prefix, 'local.research.cpu_percent'), + _('Maximum CPU Usage:'), + start = 1, stop = 101, step = 1, + value = int(PC.get_pref('local.research.cpu_percent', default=default_cpu_num)), + default = int(PC.get_pref('local.research.cpu_percent', default=default_cpu_num)), + fireonslide = True, + unit = _('{val}%'))(parent)[0], # Slider returns the sizer and the control, we just want the sizer + lambda parent, prefix: PC.Slider(PC.pname(prefix, 'local.research.bandwidth_percent'), + _('Maximum Bandwidth Usage:'), + start = 1, stop = 101, step = 1, + value = int(PC.get_pref('local.research.bandwidth_percent', default=default_bandwidth_num)), + default = int(PC.get_pref('local.research.bandwidth_percent', default=default_bandwidth_num)), + fireonslide = True, + unit = _('{val}%'))(parent)[0], # Slider returns the sizer and the control, we just want the sizer + layout = PC.HSizer(), + itemoptions = (0, wx.ALL, 3), + ), + layout = PC.VSizer(), + itemoptions = (0, wx.BOTTOM | wx.TOP, 3)), + _('Options'), + prefix = '', + ) + + sizer.Add(description, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 3) + sizer.Add(options, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 3) + + return panel diff --git a/digsby/src/gui/pref/pg_sandbox.py b/digsby/src/gui/pref/pg_sandbox.py new file mode 100644 index 0000000..7ed7c33 --- /dev/null +++ b/digsby/src/gui/pref/pg_sandbox.py @@ -0,0 +1,290 @@ +from gui.pref.prefcontrols import VSizer, get_pref +from wx import EXPAND +import wx +from contacts.sort_model import ChoiceModel, CheckModel +from peak.events import trellis + +class TranslatableChoices(trellis.Component): + keys = trellis.attr() + translations = trellis.attr() + + @trellis.maintain + def values(self): + return [(k,self.translations[k]) for k in self.keys] + +class PrefLink(trellis.Component): + prefname = trellis.attr() + value = trellis.attr() + + @trellis.make + def init_value(self): + def on_value_changed(src, attr, old, new): + import wx + @wx.CallAfter + def update(): + self.value = new + from common import profile + profile.prefs.add_observer(on_value_changed, self.prefname, + obj = self) + return self.value + + def go(self): + return self.go or self.value != self.init_value + go = trellis.maintain(go, initially=False) + + @trellis.perform + def output_pref(self): + if self.go: + from gui.pref.prefcontrols import mark_pref + if get_pref(self.prefname) != self.value: + mark_pref(self.prefname, self.value) + +def panel(panel, sizer, addgroup, exithooks): + from gui.pref.pg_contact_list import SyncingCheck, SyncingChoice + p = wx.Panel(panel) + sz = p.Sizer = VSizer() + + s = wx.FlexGridSizer(6, 1, 7,7) + prefix = 'buddylist.layout.' + Cells = trellis.Cells + + layout_model = LayoutModel( + service = get_pref(prefix+'service_icon_pos'), + se_enabled = get_pref(prefix+'show_service_icon'), + status = get_pref(prefix+'status_icon_pos'), + st_enabled = get_pref(prefix+'show_status_icon'), + icon = get_pref(prefix+'buddy_icon_pos'), + icon_enabled = get_pref(prefix+'show_buddy_icon'), + ) + #hook up helper models + cells = Cells(layout_model) + + service_icon_pos = PrefLink(prefname=prefix+'service_icon_pos', value = cells['service']) + show_service_icon = PrefLink(prefname=prefix+'show_service_icon', value = cells['se_enabled']) + status_icon_pos = PrefLink(prefname=prefix+'status_icon_pos', value = cells['status']) + show_status_icon = PrefLink(prefname=prefix+'show_status_icon', value = cells['st_enabled']) + buddy_icon_pos = PrefLink(prefname=prefix+'buddy_icon_pos', value = cells['icon']) + show_buddy_icon = PrefLink(prefname=prefix+'show_buddy_icon', value = cells['icon_enabled']) + + se_selection = Selection(value = cells['service'], + choices = cells['service_choices'], + enabled = cells['se_enabled']) + st_selection = Selection(value = cells['status'], + choices = cells['status_choices'], + enabled = cells['st_enabled']) + bi_selection = Selection(value = cells['icon'], + choices = ['left','right'], + enabled = cells['icon_enabled']) + #adapt for GUI models + translated = dict(fleft = _('Far Left'), + left = _('Left'), + bleft = _('Badge (Lower Left)'), + bright = _('Badge (Lower Right)'), + right = _('Right'), + fright = _('Far Right')) + se_choices = TranslatableChoices(keys=cells['service_choices'], translations = translated) + st_choices = TranslatableChoices(keys=cells['status_choices'], translations = translated) + bi_choices = TranslatableChoices(keys=['left','right'], translations = translated) + + #share a bunch of cells + service_model = ChoiceModel(values = Cells(se_choices)['values'], + selection = Cells(se_selection)['selection'], + enabled = cells['se_enabled']) + service_enabled = CheckModel( checked = Cells(service_model)['enabled']) + status_model = ChoiceModel(values = Cells(st_choices)['values'], + selection = Cells(st_selection)['selection'], + enabled = cells['st_enabled']) + status_enabled = CheckModel( checked = status_model.__cells__['enabled']) + icon_model = ChoiceModel(values = Cells(bi_choices)['values'], + selection = Cells(bi_selection)['selection'], + enabled = cells['icon_enabled']) + icon_enabled = CheckModel( checked = Cells(icon_model)['enabled']) + + #Trellis will keep the rest alive + s.links = [layout_model, service_icon_pos, show_service_icon, status_icon_pos, show_status_icon, buddy_icon_pos, show_buddy_icon] + + service_check = SyncingCheck(service_enabled, p) + service_choice = SyncingChoice(service_model, p) + status_check = SyncingCheck(status_enabled, p) + status_choice = SyncingChoice(status_model, p) + icon_check = SyncingCheck(icon_enabled, p) + icon_choice = SyncingChoice(icon_model, p) + + s.Add(service_check) + s.Add(service_choice) + s.Add(status_check) + s.Add(status_choice) + s.Add(icon_check) + s.Add(icon_choice) + + s.AddGrowableCol(1,1) + sz.Add(s, 0, EXPAND) + + sizer.Add(p) + + return panel + +BUMP = {'left':'fleft', + 'fleft':'left', + 'right':'fright', + 'fright':'right', + 'bleft':'bright', + 'bright':'bleft'} + +VACUUM = {'fleft':'left', + 'fright':'right'} + +class LayoutModel(trellis.Component): + icon_enabled = trellis.attr(True) + se_enabled = trellis.attr(True) + st_enabled = trellis.attr(True) + icon = trellis.attr('left') + + @trellis.maintain + def monitor_icon(self): + ie = self.icon_enabled + st = self.status + se = self.service + i = self.icon + if not ie: + if i == 'left': pos = ['fleft', 'left', 'bleft', 'bright'] + else: pos = ['bleft', 'bright', 'right', 'fright'] + + from_ = [None, None, None, None] + if self.st_enabled and st in pos: from_[pos.index(st)] = 'status' + if self.se_enabled and se in pos: from_[pos.index(se)] = 'service' + to = filter(None, from_) + out = [None, None] + if i == 'left': + if to: out[-len(to):] = to + if out[0]: setattr(self, out[0], 'fleft') + if out[1]: setattr(self, out[1], 'left') + else: + if to: out[:len(to)] = to + if out[0]: setattr(self, out[0], 'right') + if out[1]: setattr(self, out[1], 'fright') + + @trellis.maintain + def service(self): + se = self.service + st = self.status + if se == st and se in BUMP: + return BUMP[se] + if se in VACUUM and st != VACUUM[se]: + return VACUUM[se] + return se + + @trellis.maintain + def status(self): + st = self.status + se = self.service + if st == se and st in BUMP: + return BUMP[st] + if st in VACUUM and se != VACUUM[st]: + return VACUUM[st] + return st + + @trellis.maintain + def status_choices(self): + return self._choices(other=self.service, other_enabled=self.se_enabled) + + @trellis.maintain + def service_choices(self): + return self._choices(other=self.status, other_enabled=self.st_enabled) + + def _choices(self, other, other_enabled): + out = [] + if other_enabled and other in ('left', 'fleft'): + out.append('fleft') + out.append('left') + if self.icon_enabled: + out.extend(['bleft', 'bright']) + out.append('right') + if other_enabled and other in ('right', 'fright'): + out.append('fright') + return out + + @trellis.maintain + def monitor_enabled(self): + if not self.st_enabled and self.se_enabled: + se = self.service + if se in VACUUM: + self.service = VACUUM[se] + if VACUUM[se] in BUMP: + self.status = BUMP[VACUUM[se]] + if not self.se_enabled and self.st_enabled: + st = self.status + if st in VACUUM: + self.status = VACUUM[st] + if VACUUM[st] in BUMP: + self.service = BUMP[VACUUM[st]] + +class Selection(trellis.Component): + value = trellis.attr() + choices = trellis.attr() + selection = trellis.attr(None) + enabled = trellis.attr() + + @trellis.maintain + def last_set_selection(self): + if self.selection is None: +# print '--initialize', self.current_selection + self.selection = self.current_selection + return self.current_selection + if self.current_selection == self.selection: +# print '--match ext', self.selection + return self.current_selection + if self.current_selection == self.last_set_selection: +# print '--match int' + if self.selection >= 0: +# print '--set int',self.selection + self.current_selection = self.selection + self.set_value(self.selection) + return self.selection + if self.selection == self.last_set_selection: +# print '--set ext', self.current_selection + self.selection = self.current_selection + return self.current_selection +# print '--returning', self.current_selection + return self.current_selection + + @trellis.maintain + def current_selection(self): + if not self.enabled: + return -1 + if self.value in self.choices: + return self.choices.index(self.value) + return -1 + + def set_value(self, pos): + self.value = self.choices[pos] + +if __name__ == '__main__': + class watch(trellis.Component): + w= trellis.attr() + @trellis.perform + def foo(self): + print 'service:', self.w.service + print 'status:', self.w.status + print 'service_choices:', self.w.service_choices + print 'status_choices:', self.w.status_choices + f = LayoutModel(service='fleft', status='fleft', se_enabled=True) + cells = trellis.Cells(f) + se = Selection(value = cells['service'], choices=cells['service_choices']) + st = Selection(value = cells['status'], choices=cells['status_choices']) + w = watch(w=f) +# f.icon_enabled = True +# +# f.service = 'bleft' +# f.status = 'right' +# f.icon = 'right' +# +# print '#'*80 +# print se.selection +# se.selection = 0 +# f.service = 'right' +# print se.selection +# st.selection=3 +# print se.selection + + diff --git a/digsby/src/gui/pref/pg_status.py b/digsby/src/gui/pref/pg_status.py new file mode 100644 index 0000000..f9d21bb --- /dev/null +++ b/digsby/src/gui/pref/pg_status.py @@ -0,0 +1,63 @@ +''' +Status tab in the preferences dialog. +''' + +import wx +from gui.pref.prefcontrols import * +from common import profile +from gui.status import StatusList + +from gui.uberwidgets.PrefPanel import PrefPanel, PrefCollection + +import config + +def panel(p, sizer, addgroup, exithooks): + + #idle_panel = wx.Panel(p) + + # Idle message checkbox, minutes box, and status box + addgroup(_('Status Options'), + Check('digsby.status.promote_tag.enabled', + _('Promote Digsby in my IM status messages'), default = True, + help = 'http://wiki.digsby.com/doku.php?id=faq#q34'), +# Check('plugins.nowplaying.show_link', +# _('Help Digsby by linking album when sharing "Listening to..." as status')), + Check('messaging.become_idle', + _('Let others know that I am idle after ' + '%2(messaging.idle_after)d minutes of inactivity')), + ) + + bottom = HSizer() + + when_away = PrefPanel(p, + PrefCollection(Check('autorespond', _('Autorespond with status message')), + Check('disable_sound', _('Disable sounds')), + Check('disable_popup', _('Disable pop-up notifications')), + layout = VSizer(), + itemoptions = (0, BOTTOM | TOP,3)), + _('When away...'), + prefix = 'messaging.when_away', + ) + bottom.Add(when_away, 1, EXPAND | ALL, 3) + + if config.platformName != 'mac': + fullscreen = PrefPanel(p, + PrefCollection(Check('hide_convos', _('&Hide new conversation windows')), + Check('disable_sounds', _('&Disable sounds')), + Check('disable_popups', _('Disable &pop-up notifications')), + layout = VSizer(), + itemoptions = (0, BOTTOM | TOP, 3)), + _('When running full screen applications...'), + prefix = 'fullscreen', + # Check('disable_alerts', _('Disable &alerts')), + ) + bottom.Add(fullscreen, 1, EXPAND | ALL, 3) + + sizer.Add(bottom, 0, EXPAND | BOTTOM) + + + statuses = StatusList(p, profile.statuses) + msgs = PrefPanel(p, statuses, _('Status Messages'), buttonlabel=_('New Status Message'), buttoncb=lambda b: statuses.add_status_message()) + + sizer.Add(msgs, 1, wx.EXPAND) + return p diff --git a/digsby/src/gui/pref/pg_supportdigsby.py b/digsby/src/gui/pref/pg_supportdigsby.py new file mode 100644 index 0000000..bffbf24 --- /dev/null +++ b/digsby/src/gui/pref/pg_supportdigsby.py @@ -0,0 +1,16 @@ +import wx +import gui.pref.prefcontrols as PC +from gui.uberwidgets.PrefPanel import PrefPanel, PrefCollection + +import gui.supportdigsby as Support + +import common + +def panel(p, sizer, addgroup, exithooks): + components = [o() for o in Support.supportoptions.get_enabled()] + _supportpanel = Support.supportdialog.SupportPanel(components, p) + outerpanel = PrefPanel(p, _supportpanel, _('Support Digsby')) + exithooks += _supportpanel.OnClose + + sizer.Add(outerpanel, 1, wx.EXPAND) + return p \ No newline at end of file diff --git a/digsby/src/gui/pref/pg_text_conversations.py b/digsby/src/gui/pref/pg_text_conversations.py new file mode 100644 index 0000000..dca1cac --- /dev/null +++ b/digsby/src/gui/pref/pg_text_conversations.py @@ -0,0 +1,221 @@ +import wx +from wx import EXPAND, BOTTOM, LEFT +from common import profile +from gui.uberwidgets.formattedinput2.formatprefsmixin import PrefInput +from gui.uberwidgets.formattedinput2.fromattedinputevents import EVT_TEXT_FORMAT_CHANGED +import operator +from gui.pref.prefcontrols import Check, Choice, CheckChoice, get_pref, HSizer, Label, Browse,\ + LocationButton +from gui.uberwidgets.PrefPanel import PrefCollection, PrefPanel +from config import platformName +from common import pref, delpref + +timestamp_options = (('%#I:%M', '5:43'), + ('%#I:%M %p', '5:43 PM'), + ('%#I:%M:%S', '5:43:20'), + ('%#I:%M:%S %p', '5:43:20 PM'), + ('%H:%M', '17:43'), + ('%H:%M:%S', '17:43:20')) + +new_im_choices = [ + ('stealfocus', _('automatically take focus')), + ('minimize', _('start minimized in taskbar')), + ('hide', _('start hidden (tray icon blinks)')), +] + +ad_position_options = ( + ('bottom', _('bottom')), + ('top', _('top')), + ('left', _('left')), + ('right', _('right')), +) + +def checkbox_enabled_when_pref(cb, prefname): + cb.Enabled = get_pref(prefname) + profile.prefs.link(prefname, lambda val: wx.CallAfter(cb.Enable, val), obj = cb) + +def panel(panel, sizer, newgroup, exithooks): + warncheck = Check('messaging.tabs.warn_on_close', 'Warn me when I attempt to close multiple conversations')(panel) + checkbox_enabled_when_pref(warncheck, 'messaging.tabs.enabled') + + window_options = [ + Check('conversation_window.always_on_top', _('&Keep on top of other applications')) + ] + + window_options.extend([ + Check('messaging.tabs.enabled', _("Group multiple conversations into one tabbed window")), + warncheck]) + + if platformName != 'mac': + window_options.append((Choice('conversation_window.new_action', + new_im_choices, + caption = _('New conversation windows: ')), 0, wx.EXPAND | wx.BOTTOM, 3)) + window_options.append( + Choice('messaging.tabs.icon', (('buddy', _("buddy icon")), + ('service', _("service icon")), + ('status', _("status icon"))), + _("Identify conversations with the contact's: "))) + + winops = PrefPanel(panel, + PrefCollection(*window_options), + _('Window Options')) + + + conops = PrefPanel(panel, get_conversation_entries(panel, exithooks), _('Conversation Options')) + + disable_flash = Check('imwin.ads_disable_flash', _("Don't show flash ads"))(panel) + checkbox_enabled_when_pref(disable_flash, 'imwin.ads') + + ad_options = PrefPanel(panel, + PrefCollection(Label(_('Help keep Digsby free by showing an\nadvertisement in the IM window.')), + Check('imwin.ads', _('Support Digsby development with an ad')), + disable_flash, + Choice('imwin.ads_position', ad_position_options, _('Location of ad in IM window: ')), + layout=wx.BoxSizer(wx.VERTICAL), + itemoptions = (0, wx.EXPAND | wx.BOTTOM, 8), + ), + _('Ad Options')) + + hsizer = HSizer() + hsizer.AddMany([ + (conops, 1, wx.EXPAND | wx.ALL, 3), + (ad_options, 0, wx.EXPAND | wx.ALL, 3) + ]) + + + textform = PrefPanel(panel, build_format_preview(panel, exithooks), _('Text Formatting')) + + panel._conops = conops + + sizer.AddMany([(winops, 0, EXPAND | BOTTOM, 6), + (hsizer, 0, EXPAND | BOTTOM, 6), + (textform, 1, EXPAND | BOTTOM, 6)]) + + return panel + +def AspellMenuEntries(spellchecker): + dicts = spellchecker.dict_info + langs = ((key, dicts[key]['name_native']) if 'name_native' in dicts[key] else (key, dicts[key]['name_english']) for key in dicts) + return sorted(langs, key = lambda x: x[1].upper()) + +def build_format_preview(parent, exithooks): + p = wx.Panel(parent) + s = p.Sizer = wx.BoxSizer(wx.VERTICAL) + + input = PrefInput(p, + value = _('Your messages will look like this.'), + autosize = False, + multiFormat = False, + showFormattingBar = (platformName != 'mac'), + skin = 'AppDefaults.FormattingBar', + formatpref = 'messaging.default_style') + + + def OnFormatChanged(event): + input.SaveStyle('messaging.default_style') + + input.Bind(EVT_TEXT_FORMAT_CHANGED, OnFormatChanged) + + input.SetMinSize((300, 77)) # why doesn't IMInput size correctly? + + s.Add(input, 1, wx.EXPAND|wx.ALL, 1) + + # include a "font" button on mac. + if platformName == 'mac': + h = wx.BoxSizer(wx.HORIZONTAL) + h.AddStretchSpacer(1) + h.Add(input.CreateFontButton(p), 0, EXPAND | wx.ALL, 5) + s.Add(h, 0, wx.EXPAND) + + + + def OnPaintWithOutline(event): + dc = wx.AutoBufferedPaintDC(p) + + rect = wx.RectS(p.Size) + irect = wx.Rect(input.Rect) + irect.Inflate(1, 1) + + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + + dc.DrawRectangleRect(rect) + + dc.Pen = wx.Pen(wx.Color(213, 213, 213)) + dc.DrawRectangleRect(irect) + + p.Bind(wx.EVT_PAINT, OnPaintWithOutline) + + return p + + +def get_conversation_entries(panel, exithooks): + p = wx.Panel(panel) + from common.spelling import spellchecker + + history = Check('conversation_window.show_history', + _('Show last %2(conversation_window.num_lines)d lines in IM window'))(p) + + conversation_entries = [ + Check('conversation_window.timestamp', _('&Display timestamp:'))(p), + Choice('conversation_window.timestamp_format', timestamp_options, allow_custom=True)(p), + ] + + if 'wxMac' not in wx.PlatformInfo: + # don't include Aspell options in wxMac, since spell checking will + # be provided by the system text controls + conversation_entries.extend([ + Check('messaging.spellcheck.enabled', _('Spell check:'))(p), + Choice('messaging.spellcheck.engineoptions.lang', AspellMenuEntries(spellchecker))(p) + ]) + + conversation_entries.extend(emoticon_choice(p)) + + s = wx.FlexGridSizer(len(conversation_entries), 2, 3, 6) + s.AddGrowableCol(1, 1) + + for i, entry in enumerate(conversation_entries): + assert not isinstance(entry, tuple) + s.Add(entry, i%2, wx.EXPAND) + + v = wx.BoxSizer(wx.VERTICAL) + v.Add(s, 1, wx.EXPAND) + + + v.AddMany([ + (1, 7), + (Check('log.ims', _('Log IM conversations to hard drive'), callback = history.Enable)(p), 0, wx.EXPAND), + (1, 4), + (history, 0, LEFT, 18), + (1, 4), + (LocationButton('local.chatlogdir', _('Location:'))(p), 0, wx.EXPAND | LEFT, 18), + ]) + + history.Enable(pref('log.ims', type=bool, default=True)) + + if 'wxMac' not in wx.PlatformInfo: + exithooks += spellchecker.DownloadDict + + p.Sizer = v + + return p + +def emoticon_choice(panel): + ''' + Builds a dropdown for selecting emoticon packs. + + If your current pack cannot be found, it is reset to default. + ''' + + from gui.imwin import emoticons + emoticonchoices = [(pack.path.name, pack.name) for pack in emoticons.findsets() if pack.name] + + current_pack = pref('appearance.conversations.emoticons.pack', None) + + if (current_pack) not in map(operator.itemgetter(0), emoticonchoices): + delpref('appearance.conversations.emoticons.pack') + + return [ + Check('appearance.conversations.emoticons.enabled', _('Show &emoticons:'))(panel), + Choice('appearance.conversations.emoticons.pack', emoticonchoices)(panel) + ] diff --git a/digsby/src/gui/pref/pg_widgets.py b/digsby/src/gui/pref/pg_widgets.py new file mode 100644 index 0000000..5a23860 --- /dev/null +++ b/digsby/src/gui/pref/pg_widgets.py @@ -0,0 +1,103 @@ +''' +Widgets tab in the preferences dialog. +''' +import wx +from gui.pref.prefcontrols import VSizer, HSizer +from gui.uberwidgets.PrefPanel import PrefPanel + +from common import profile +from gui.widgetlist import WidgetList +from gui.browser import Browser +from wx import RIGHT, LEFT, EXPAND, TOP, BOTTOM +from config import platformName + +preview_widget_url = ('http://w.digsby.com/dw.swf?' + 'STATE=creator&field=ffffff&statustext=777777&' + 'nick=my.nickname&bgcolor=eaeaea&text=444444&' + 'title=Digsby+Widget&titletext=7a7a7a') + +preview_placeholder=''' + + + + +
+ +
+ +''' % preview_widget_url + +from logging import getLogger; log = getLogger('pg_widgets') + +def panel(p, sizer, addgroup, exithooks): + widgetlist = WidgetList(p, profile.widgets) + + browser = Browser(p) + browser.SetMinSize((235, -1)) + + preview = PrefPanel(p, browser, _('Widget Preview')) + preview.SetMinSize((235, -1)) + + embedpanel = wx.Panel(p) + es = embedpanel.Sizer = VSizer() + embedtext = wx.TextCtrl(embedpanel, -1, '', style = wx.TE_MULTILINE | wx.TE_READONLY) + embedtext.SetMinSize((-1, 60)) + + copybutton = wx.Button(embedpanel, -1, _('&Copy To Clipboard'), + style = wx.BU_EXACTFIT if platformName == 'mac' else 0) + + if platformName == 'mac': + copybutton.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + button_flags = 0, wx.ALIGN_RIGHT | RIGHT | TOP | BOTTOM, 3 + else: + button_flags = 0, EXPAND | TOP, 3 + + es.Add(embedtext, 1, EXPAND) + es.Add(copybutton, *button_flags) + + def docopy(e): + clip = wx.TheClipboard + if clip.Open(): + clip.SetData( wx.TextDataObject(embedtext.Value) ) + clip.Close() + + copybutton.Bind(wx.EVT_BUTTON, docopy) + copybutton.Enable(False) + + + def show_widget(widget_embed_text): + browser.SetPage(widget_embed_text) + + def on_widget_selection(e): + i = e.Int + log.info('widget %d selected', i) + if i != -1: + widget = widgetlist.GetDataObject(i) + embedtext.Value = widget.embed_tag + copybutton.Enable(bool(embedtext.Value)) + + wx.CallLater(150, show_widget, widget.embed_creator(*browser.Size)) + else: + copybutton.Enable(False) + + widgetlist.Bind(wx.EVT_LIST_ITEM_SELECTED, on_widget_selection) + + widgets = PrefPanel(p, widgetlist, _('Widgets'), _('New Widget'), lambda b: widgetlist.OnNew()) + + embed = PrefPanel(p, embedpanel, _('Embed Tag')) + + top = HSizer() + top.Add(widgets, 1, EXPAND) + top.Add(preview, 0, EXPAND | LEFT,6) + + sizer.Add(top, 1, EXPAND) + sizer.Add(embed, 0, EXPAND | TOP,6) + + p.on_close = widgetlist.on_close + wx.CallLater(10, lambda: browser.SetPage(preview_placeholder % tuple(browser.Size))) + return p + + diff --git a/digsby/src/gui/pref/prefcontrols.py b/digsby/src/gui/pref/prefcontrols.py new file mode 100644 index 0000000..a14e891 --- /dev/null +++ b/digsby/src/gui/pref/prefcontrols.py @@ -0,0 +1,1141 @@ +''' +Utility functions for building GUI controls bound to the preference dictionary. +''' + +from __future__ import with_statement + +from wx import Choice, EXPAND, LEFT, EVT_CHOICE, BOTTOM, ALIGN_CENTER_VERTICAL, ALL, \ + EVT_LEFT_DOWN, EVT_LEFT_UP, HORIZONTAL,VERTICAL, Rect, RectS +from wx import StaticBox, StaticBoxSizer, StaticText, TOP, Window + +import wx, re + +from util.primitives.error_handling import traceguard +from util.primitives.funcs import autoassign, isiterable, do +from util.primitives.misc import clamp +import logging; log = logging.getLogger("prefs") +from itertools import izip +from gui.toolbox import wx_prop +from gui.textutil import CopyFont +from gui.anylists import AnyList +from gui.validators import LengthLimit + +from common import profile +from config import platformName +#log.setLevel(logging.DEBUG) +info = log.info; warning = log.warning +__metaclass__ = type + +# matcher for formatting strings like %2(name.subname)d +_fmtmatcher = re.compile(r'%(\d*)\(([A-Za-z_]?[A-Za-z_0-9\.]+[A-Za-z_0-9])\)([sdf])') + +typechar_to_type = dict(f=float, d=int) + +from contextlib import contextmanager + +class NullEvtHandler(wx.EvtHandler): + def AddPendingEvent(self, evt): + print 'SSSHHH secret', evt + + +@contextmanager +def secret(ctrl): + ctrl.PushEventHandler(NullEvtHandler()) + try: + yield + finally: + ctrl.PopEventHandler() + +wx.Control.secret = secret + +# These functions are the only interface to the outside preferences + +def pref_mapping(key): + if not isinstance(key, str): + raise TypeError('prefname must be a string') + + if key.startswith('local.'): + key = key[len('local.'):] + mapping = profile.localprefs + else: + mapping = profile.prefs + + return key, mapping + + +def mark_pref(prefname, value): + 'Change a preference!' + + if not isinstance(prefname, str): + raise TypeError('prefname must be a string') + + prefname, mapping = pref_mapping(prefname) + + # Set and log the change + mapping[prefname] = value + log.info('%s ---> %s', prefname, mapping[prefname]) + +def get_pref(prefname, default=sentinel): + 'Get a preference!' + + prefname, mapping = pref_mapping(prefname) + + try: + v = mapping[prefname] + except KeyError, e: + if default is sentinel: + try: + return profile.defaultprefs[prefname] + except KeyError: + raise e + else: v = default + + return v + +def get_prefs(): + return profile.prefs + +def get_localprefs(): + return profile.localprefs + +def wx_evt_type(component): + + if isinstance(component, wx.Sizer): + component = component.Children[0].Window + + if type(component) is not type: + component = type(component) + + types = { + wx.CheckBox : wx.EVT_CHECKBOX, + } + return types[component] + +# +# GUI elements +# + +class PrefsPanel(wx.Panel): + 'Simple backing wxPanel for pref controls.' + + def __init__(self, parent): + wx.Panel.__init__(self, parent, size=(0,0), pos=(-50,50)) + + if 'wxMac' not in wx.PlatformInfo: + self.BackgroundColour = wx.WHITE + +class PrefGroup(object): + + SPACING = 4 + + def __init__(self, title, *elems): + if isinstance(title, tuple): + title, prefix = title + else: + prefix = '' + autoassign(self, locals()) + + if 'wxMSW' in wx.PlatformInfo: + # this implementation uses a wx.StaticBox + def build(self, parent): + box = StaticBox(parent, -1, self.title) + bsizer = StaticBoxSizer(box, VERTICAL) + self.add_to_sizer(parent, bsizer) + return bsizer + else: + # this implementation does not + def build(self, parent): + sz = VSizer() + text = StaticText(parent, -1, self.title) + text.SetBold() + sz.Add(text) + + self.add_to_sizer(parent, sz) + return sz + + def __call__(self, parent): + return self.build(parent) + + def add_to_sizer(self, parent, sz): + for elem in self.elems: + if callable(elem): + if hasattr(elem, 'func_code') and elem.func_code.co_argcount == 2: + elem = elem(parent, self.prefix) + else: + elem = elem(parent) + sz.Add(elem, 0 if len(self.elems) > 1 else 1, EXPAND | ALL, self.SPACING) + + +class EnabledGroup(PrefGroup): + SPACING = 3 + def __init__(self, title, required, *elems): + PrefGroup.__init__(self, title, *elems) + try: + self.req_cmp, self.req_cb = required + except TypeError: + self.req_cmp = required + self.req_cb = lambda v, *a: v + + def build(self, parent): + sz = VSizer() + if self.title: + text = StaticText(parent, -1, self.title) + text.SetBold() + sz.Add(text) + + if callable(self.req_cmp): + if self.req_cmp.func_code.co_argcount == 2: + self.req_cmp = self.req_cmp(parent, self.prefix) + else: + self.req_cmp = self.req_cmp(parent) + + sz.Add(self.req_cmp, 0, EXPAND | TOP, 7) + spc_sz = HSizer() + sz.Add(spc_sz,0, EXPAND) + spc_sz.AddSpacer(int(self.req_cmp.Size.height*1.5)) + cmp_sz = VSizer() + spc_sz.Add(cmp_sz,1) + + self.add_to_sizer(parent, cmp_sz) + return sz + + def add_to_sizer(self, parent, sz): + + components = [] # collection of wxObjects + for elem in self.elems: + if callable(elem): + if elem.func_code.co_argcount == 2: + elem = elem(parent, self.prefix) + else: + elem = elem(parent) + + sz.Add(elem, 0 if len(self.elems) > 1 else 1, EXPAND | TOP, border = self.SPACING) + components.append(elem) + + if components: + def callback(e): + for c in components: + c.Enabled = self.req_cb(self.req_cmp.GetValue(), c) + if e: e.Skip() + + self.req_cmp.Bind(wx_evt_type(self.req_cmp), callback) + callback(None) + + return sz + +class OffsetGroup(PrefGroup): + SPACING = 3 + def __init__(self, title, header, *elems): + PrefGroup.__init__(self, title, *elems) + self.header = header + + def build(self, parent): + sz = VSizer() + if self.title: + text = StaticText(parent, -1, self.title) + text.SetBold() + sz.Add(text) + + sz.Add(self.header, 0, EXPAND | TOP, 7) + spc_sz = HSizer() + sz.Add(spc_sz,0, EXPAND) + spc_sz.AddSpacer(int(self.header.Size.height*1.5)) + cmp_sz = VSizer() + spc_sz.Add(cmp_sz,1) + + self.add_to_sizer(parent, cmp_sz) + return sz + + def add_to_sizer(self, parent, sz): + components = [] # collection of wxObjects + for elem in self.elems: + sz.Add(elem, 0 if len(self.elems) > 1 else 1, EXPAND | TOP, border = self.SPACING) + components.append(elem) + + return sz + +def MakeEnabledSizer(baseclass): + class EnabledSizer(baseclass): + def __init__(self, *a, **k): + try: + baseclass.__init__(self, *a, **k) + except: + import sys; print >> sys.stderr, baseclass + raise + self._enabled = True + + def SetEnabled(self, val=True): + res = self._enabled != val + for child in self.Children: + if child.Window: + child.Window.Enabled = val + + self._enabled = val + return res + + def SetSelection(self, i): + do(c.Window.SetSelection(i) for c in self.Children if hasattr(getattr(c, 'Window', None), 'SetSelection')) + + def GetEnabled(self): + return self._enabled + + Enable = SetEnabled + + Enabled = property(GetEnabled, SetEnabled) + + return EnabledSizer + +def HSizer(cls = wx.BoxSizer, *a, **k): + 'Returns a horizontal box sizer.' + return MakeEnabledSizer(cls)(HORIZONTAL, *a, **k) + +def VSizer(cls = wx.BoxSizer, *a, **k): + 'Returns a vertical box sizer.' + return MakeEnabledSizer(cls)(VERTICAL, *a, **k) + +def PlusMinus(): + def buildplusminus(panel): + h = HSizer() + h.AddStretchSpacer(1) + plus = wx.Button(panel, wx.ID_NEW, '+', size=(30,15)) + minus = wx.Button(panel, wx.ID_DELETE, '-', size=(30,15)) + h.GetButtons = lambda p=plus, m=minus: (p, m) + h.Add(plus, 0, wx.ALL | wx.ALIGN_RIGHT) + h.Add(minus, 0, wx.ALL | wx.ALIGN_RIGHT) + return h + return buildplusminus + + +class Text(wx.TextCtrl): + 'Text field. Updates its preference upon losing focus.' + def __init__(self, parent, prefname, name='', size=wx.DefaultSize, validator=None, _type=None, style = 0): + wx.TextCtrl.__init__(self, parent, size=size, validator=(validator or LengthLimit(2048)), style = style) + if not isinstance(prefname, basestring): + prefix, prefname = prefname + else: + prefix = '' + + self.validator = validator + + self.type = _type + self.prefix = prefix + self.prefname = prefname + self._style = style + + if self.validator is not None: + self.Bind(wx.EVT_CHAR, self.validator.OnChar) + self.Bind(wx.EVT_TEXT, self.validator.OnText) + + if self.pname.startswith('local.'): + self.prefname = self.pname[len('local.'):] + self.prefix = '' + self.mapping = profile.localprefs + else: + self.mapping = profile.prefs + + self.mapping.link(self.pname, self.pref_changed) + + self.secret = 0 + self.Bind(wx.EVT_KILL_FOCUS, self.changed) + + def changed(self, e): + self.mark_pref(self.Value) + + def pref_changed(self, val): + self.SetValue(unicode(val)) + + @property + def pname(self): + 'Glues a prefix on a name, if it exists.' + if self.prefname.startswith('local.'): + return self.prefname + return self.prefix + '.' + self.prefname if self.prefix else self.prefname + def mark_pref(self, val): + ''' + Set the value in the preferences dictionary. + ''' + if self.pname not in self.mapping: + raise KeyError("Trying to set a preference that doesn't exist: %s" % self.pname) + + if self.type is not None: + try: + val = self.type(val) + except Exception: + log.error('"pref[%r] = %r" : value does not match %r, not setting it', self.pname, val, self.type) + return + + if self.secret: + self.mapping.secret_set(self.pname, val) + else: + self.mapping[self.pname] = val + log.debug('%s ---> %s', self.pname, val) + + def get_pref(self): + return self.mapping[self.pname] + + @contextmanager + def secret(self): + self.PushEventHandler(NullEvtHandler()) + self.secret += 1 + try: + yield self + finally: + self.secret -= 1 + self.PopEventHandler() + +def Button(label,callback=None): + def makeButton(parent,prefix = ''): + button = wx.Button(parent,-1,label, style=wx.BU_EXACTFIT) + + if callback: + button.Bind(wx.EVT_BUTTON,lambda e: callback(button)) + + return button + + return makeButton + +def Label(text): + def makeStext(parent,prefix=''): + return SText(parent,text) + + return makeStext + + +WindowClass = getattr(wx, '_Window', wx.Window) + +def SText(parent, text): + if not isinstance(parent, WindowClass): + raise TypeError('first arg to SText must be a wx.Window') + return StaticText(parent, -1, text) + +def Check(prefname, caption, callback = None, default = False, help = None): + ''' + A checkbox for a single preference. + + Optionally, a %2(some.pref)d type string in the caption will be turned + into an additional textbox-linked-preference. + ''' + if callback is None: + callback = lambda *a, **k: None + + if caption.find('%') != -1: + def my_check(parent, prefix = ''): + p = wx.Panel(parent) + s = caption + textctrl = None + checkPref = pname(prefix, prefname) if prefname is not None else None + + checkbox = boundcheckbox(p, s[:s.find('%')], checkPref, + callback=(lambda v: (textctrl.Enable(v), callback(v))), + default = default) + elems = [ checkbox ] + match = _fmtmatcher.search(s) + + if match: + n, name, type = match.groups() + textctrl = Text(p, pname(prefix, name), name, + size = (int(n) * 8 +10,-1), + validator = validators[type](), + _type=typechar_to_type.get(type, lambda x:x)) + textctrl.SetMaxLength(long(n)) + textctrl.Enable(checkbox.GetValue()) + elems += [textctrl] + elems += [wx.StaticText(p, -1, s[match.span()[1]:])] + + hz = p.Sizer = HSizer() + hz.check = checkbox + hz.AddMany([(elem, 0, ALIGN_CENTER_VERTICAL) for elem in elems]) + + return p + else: + def my_check(parent, prefix='', default = default): + return boundcheckbox(parent, caption, pname(prefix, prefname), callback, default = default, help = help) + + return my_check + +def CheckChoice(checkprefname, choiceprefname, text, choices, allow_custom = False, max_width=None): + 'Checkbox with dropdown after.' + + def build(parent, prefix = ''): + choice = Choice(choiceprefname, choices, allow_custom = allow_custom, max_width=max_width)(parent, prefix) + choice.Enable(bool(get_pref(pname(prefix,checkprefname)))) + + check = Check(checkprefname, text, + lambda v: choice.Enable(v))(parent, prefix) + + sz = HSizer() + sz.Add(check, 0, EXPAND | ALIGN_CENTER_VERTICAL) + sz.Add(choice, 0, EXPAND | ALIGN_CENTER_VERTICAL) + + sz.Bind = check.Bind + sz.GetValue = check.GetValue + sz.SetValue = check.SetValue + + return sz + return build + + +class _AutoCombo(wx.ComboBox): + def __init__(self, *a, **k): + k['style'] = k.get('style', 0) | wx.CB_DROPDOWN #| wx.CB_READONLY + + if k.get('validator', None) is None: + k['validator'] = LengthLimit(1024) + + wx.ComboBox.__init__(self, *a, **k) + + self._items = self.Items + self._modifying = False + self.last_selection = self.Value + + def bind_evts(self): + self.Bind(wx.EVT_TEXT, self.keypressed) + + def keypressed(self, e): + e.Skip() + if self._modifying: return + + bad_selection = False + self._modifying = True + oldval = self.Value + newitems = filter(lambda x: x.lower().startswith(e.String.lower()), self.Items) + if not newitems: + newitems = self.Items + bad_selection = True + newval = self.last_selection + newval = e.String + else: + newval = newitems[0] + self.Value = newval + if bad_selection: + wx.Bell() + self.Value = self.last_selection + self.SetMark(0, len(newval)) + self.SetInsertionPoint(len(newval)) + else: + self.SetMark(len(oldval), len(newval)) + self.last_selection = newval + self._modifying = False + + def on_close(self): + pass + +def ComboOrChoice(mytype): + class mytype(mytype): + def DeleteString(self, s): + idx = self.FindString(s) + if idx != wx.NOT_FOUND: + self.Delete(idx) + def __contains__(self, s): + return self.FindString(s) != wx.NOT_FOUND + + def Choice(prefname, choices, caption='', callback=None, allow_custom = False, do_mark_pref = True, max_width=None): + ''' + Choice dropdown. + + choices is a list of tuples, like: + [('prefkey', '&Nice Pref Name'), ...] + + do_mark_pref if True, automatically sets the preference when changed + ''' + + + try: + prefnames, displaynames = izip(*choices) + prefnames, displaynames = list(prefnames), list(displaynames) + except ValueError: + prefnames, displaynames = choices, list(choices) + prefnames = list(prefnames) + + def build_choice(parent, prefix='', prefnames=prefnames, displaynames=displaynames): + def on_choice(e): + pref, value = pname(prefix, prefname), prefnames[e.GetInt()] + if do_mark_pref: mark_pref(pref, value) + if callback: + with traceguard: + callback(pref, value) + + _p = pname(prefix, prefname) if prefname is not None else None + + if not prefnames: + prefnames = [get_pref(_p)] + displaynames = list(prefnames) + + try: + c = mytype(parent, choices = displaynames if displaynames != [None] else ['']) + except Exception: + import sys + print >> sys.stderr, repr(displaynames) + raise + + if max_width is not None: + sz = (max_width, c.Size.height) + c.SetSize(sz) + c.SetMinSize(sz) + #hit().Size = hit().BestSize = hit().MaxSize = hit().MinSize = (120, 23) + + try: + if _p is None: raise ValueError() + c.Selection = prefnames.index(get_pref(_p)) + except KeyError: + c.Selection = 0 + except ValueError, unused_e: + if allow_custom: + disp = _('Custom ({prefval})').format(prefval=get_pref(_p)) + c.Append(disp) + prefnames += [get_pref(_p)] + displaynames += [disp] + c.Selection = c.GetCount() - 1 + else: + c.Selection = 0 + +# def pref_changed(val): +# +# try: +# newSelection = prefnames.index(val) +# except ValueError, unused_e: +# return +# +# if c.Selection != newSelection: +# c.Selection = newSelection +# evt = wx.CommandEvent(wx.wxEVT_COMMAND_CHOICE_SELECTED, c.Id) +# evt.Int = newSelection +# c.AddPendingEvent(evt) +# +# +# if _p: +# profile.prefs.link(_p, pref_changed, obj = c) + c.Bind(wx.EVT_CHOICE, on_choice) + + def PrefValue(c=c): + i = c.GetSelection() + if i == -1: i = 0 + return prefnames[i] + + c.GetPrefValue = PrefValue + + if caption != '': + sz = HSizer() + sz.Add(SText(parent, caption), 0, 3) + sz.Add(c, 0, wx.EXPAND) + return sz + else: + return c + return build_choice + return Choice + +Choice = ComboOrChoice(wx.Choice) +Combo = ComboOrChoice(wx.ComboBox) +AutoCombo = ComboOrChoice(_AutoCombo) + +def LocationButton(prefname, caption, default_pref_val=sentinel): + def _build(p, prefix = ''): + + + currentpath = get_pref(pname(prefix, prefname), default_pref_val) + + button = wx.Button(p, -1, currentpath, style = wx.BU_LEFT) #wx.DirPickerCtrl(p, -1) + + def OnButton(event): + + currentpath = get_pref(pname(prefix, prefname), default_pref_val) + + newpath = wx.DirSelector(_('Choose a folder'), currentpath) + + if newpath: + + + from path import path + newpath = path(newpath) + + if not newpath.isabs(): + newpath = newpath.abspath() + + button.SetLabel(newpath) + + mark_pref(pname(prefix, prefname), newpath) + + + button.Bind(wx.EVT_BUTTON, OnButton) + + if caption: + sz = HSizer() + sz.Add(SText(p, caption), 0, wx.EXPAND | wx.ALL, 3) + sz.Add(button, 1, wx.EXPAND | wx.ALL) + ctrl = sz + else: + ctrl = button + + return ctrl + + return _build + +def Browse(prefname, caption, type, default_pref_val=sentinel): + def _build(p, prefix): + def new_val(self, ): + + from path import path + p = path(picker.GetPath()) + + if not p: + _pref = pname(prefix, prefname + '_' + type) + _pref, d = pref_mapping(_pref) + d.pop(_pref, None) + return + + if not p.isabs(): + p = p.abspath() + #picker.SetPath(p) + + mark_pref(pname(prefix, prefname + '_' + type), p) + + if type == 'file': + picker = wx.FilePickerCtrl(p, -1, style = wx.FLP_DEFAULT_STYLE | wx.FLP_USE_TEXTCTRL) + picker.Bind(wx.EVT_FILEPICKER_CHANGED, new_val) + else: + picker = wx.DirPickerCtrl(p, -1, style = wx.DIRP_USE_TEXTCTRL) + picker.Bind(wx.EVT_DIRPICKER_CHANGED, new_val) + + picker.SetPath(get_pref(pname(prefix, prefname + '_' + type), default_pref_val)) + + if caption: + sz = HSizer() + sz.Add(SText(p, caption), 0, wx.EXPAND | wx.ALL, 3) + sz.Add(picker, 1, wx.EXPAND | wx.ALL) + ctrl = sz + else: + ctrl = picker + + return ctrl + return _build + +def CheckBrowse(prefname, caption, type): + 'Checkbox with an additional file browser field and button.' + + def _build(p, prefix): + + picker = Browse(prefname, '', type)(p,prefix) + + s = HSizer() + + checkbox = boundcheckbox(p, caption, pname(prefix, prefname), + callback=lambda v: picker.Enable(v)) + + s.Add(checkbox, 0, wx.EXPAND | wx.ALL) + + picker.Enable(checkbox.IsChecked()) + s.Add(picker, 1, wx.EXPAND | wx.LEFT, 5) + return s + return _build + +# +#def Radio(label, style=0, prefname=None, prefvalue=None, callback=None): +# def makeradio(parent, prefix = ''): +# radio = wx.RadioButton(parent, -1, label=label, style=style) +# +# def OnSelect(e): +# if radio.Value: +# if prefname is not None: +# mark_pref(pname(prefix,prefname),prefvalue) +# +# if callback is not None: +# callback(e) +# +# +# radio.Bind(wx.EVT_RADIOBUTTON, OnSelect) +# +# return radio + + +def RadioBox(prefname, choices): + def makeradio(parent, prefix = ''): + return NewRadioBox(parent, prefname, choices, prefix) + return makeradio + +class NewRadioBox(wx.Panel): + def __init__(self,parent,prefname, choices, prefix = ''): + wx.Panel.__init__(self, parent) + + self.prefvalues, self.displaynames = zip(*choices) + self.prefix = prefix + self.prefname = prefname + self.prefvalues = list(self.prefvalues) + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.radios = [] + + for name in self.displaynames: + radio = wx.RadioButton(self,-1,name) + self.radios.append(radio) + self.Sizer.Add(radio,0,wx.TOP|wx.BOTTOM,3) + + try: + sel = self.prefvalues.index(get_pref(prefname)) + except: + sel = 0 + + self.radios[sel].SetValue(True) + + self.Bind(wx.EVT_RADIOBUTTON,self.OnRadio) + + def OnRadio(self,event): + mark_pref(self.prefname, self.prefvalues[self.radios.index(event.EventObject)]) + + + +class StickySlider(wx.Slider): + def __init__(self, parent, start=0, stop=None, step=1, value=0): + if isiterable(start): + values = start + else: + if stop is None: + start, stop = 0, start + + if start > stop and step > 0: + step *= -1 + + values = range(start, stop, step) + + max_ = max(values) + min_ = min(values) + reverse = step < 0 + + self.start = start + self.step = step + + style = wx.SL_HORIZONTAL #| wx.SL_AUTOTICKS | wx.SL_LABELS + style |= wx.SL_INVERSE * reverse + + wx.Slider.__init__(self, value=value, minValue=min_, maxValue=max_, parent=parent, style = style) + self.SetTickFreq(step, start) + + def bind_evts(self): + self.Bind(wx.EVT_SCROLL_CHANGED, self.stick) + self.Bind(wx.EVT_SCROLL_THUMBTRACK, self.stick) + self.Bind(wx.EVT_KEY_DOWN, self.keypress) + + def stick(self, e): + e.Skip() + + if not self.Enabled: return + + v = self.Value + v -= self.start + div, mod = divmod(v, self.step) + move_up = bool((mod * 2) // self.step) + + new_val = (div + move_up) * self.step + self.Value = new_val + self.start + + + def keypress(self, e): + if not self.Enabled: return + if e.KeyCode in (wx.WXK_UP, wx.WXK_RIGHT): + self.Value += self.step + self.Value = min(self.Value, self.Max) + + elif e.KeyCode in (wx.WXK_DOWN, wx.WXK_LEFT): + self.Value -= self.step + self.Value = max(self.Value, self.Min) + +def Slider(prefname, caption='', start=0, stop=None, step=1, value=0, callback=None, + fireonslide = False, unit=_('{val}px'), default=sentinel): + + def build_slider(parent, prefix=''): + + c = StickySlider(parent, start, stop, step, value) + + def ctrl_string(c): + return unit.format(val=unicode(c.Value)) + + lbl = SText(parent, ctrl_string(c)) + + def on_select(e): + mark_pref(pname(prefix, prefname), c.Value) + lbl.Label = ctrl_string(c) + if callback: callback(prefname, c.Value) + + def on_slide(e): + lbl.Label = ctrl_string(c) + + try: + _p = pname(prefix, prefname) + c.Value = int(get_pref(_p, default=default)) + except ValueError: + c.Value = value + + c.Bind(wx.EVT_SCROLL_CHANGED, on_select) + c.Bind(wx.EVT_SCROLL_THUMBTRACK, on_slide if not fireonslide else on_select) + c.bind_evts() + if caption != '': + sz1 = VSizer() + sz1.Add(SText(parent, caption), 0, wx.EXPAND | wx.ALL, 3) + + sz2 = HSizer() + sz2.Add(c, 1, wx.EXPAND | wx.ALL, 3) + sz2.Add(lbl, 0, wx.EXPAND | wx.ALL, 3) + sz1.Add(sz2, 1, wx.EXPAND | wx.ALL, 3) + return sz1, c + else: + return c, c + return build_slider + +# +# +# + +def boundcheckbox(parent, cap, prefname, callback = None, default = False, help=None): + checkbox = wx.CheckBox(parent, -1, _(cap)) + + def check(val): + if prefname is not None: + +# if get_pref(prefname) != val: + mark_pref(prefname, val) + + if callback: callback(val) + + checkbox.SetValueEvent = check + + if prefname is not None: + checkbox.SetValue(bool(get_pref(prefname, default))) +# profile.prefs.link(prefname,lambda val: checkbox.SetValue(val)) + + checkbox.Bind(wx.EVT_CHECKBOX, lambda e: check(e.IsChecked())) + + if help is not None: + from gui.toolbox import HelpLink + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.AddMany([ (checkbox, 0, wx.EXPAND), + (HelpLink(parent, help), 0, wx.EXPAND) ]) + sizer.AddStretchSpacer(1) + + return sizer + + return checkbox + +from gui.validators import common_validators as validators + +font_sizes = map(str, range(6,13)+range(14,29)+[36,48,72]) + + +def FontFaceAndSize(prefname='', caption='', callback=None): + import gui + + fontlist = gui.textutil.GetFonts() + sizelist = font_sizes + + def build(parent, prefix=''): + fontchoice = wx.Choice(parent, choices=fontlist) + sizechoice = wx.Choice(parent, choices=sizelist) + + font_pref = pname(prefix, prefname+'_face') + size_pref = pname(prefix, prefname+'_size') + + def onfont(e): + pref, value = font_pref, fontchoice.GetStringSelection() + mark_pref(pref, value) + if callback: callback(pref, value) + + def onsize(e): + pref, value = size_pref, sizechoice.StringSelection + mark_pref(pref, value) + if callback: callback(pref, value) + + try: + fontchoice.SetStringSelection(get_pref(font_pref)) + except ValueError, unused_e: + fontchoice.Selection = 0 + + try: + sizechoice.SetStringSelection(unicode(get_pref(size_pref))) + except ValueError, unused_e: + sizechoice.Selection = 0 + + fontchoice.Bind(EVT_CHOICE, onfont) + sizechoice.Bind(EVT_CHOICE, onsize) + + + sz_ = HSizer() + sz_.Add(fontchoice, 1, EXPAND) + sz_.Add(sizechoice, 0, EXPAND | LEFT, 3) + + if caption != '': + sz = VSizer() + sz.Add(SText(parent, caption), 0, EXPAND | BOTTOM, 3) + sz.Add(sz_, 1, EXPAND | LEFT, 18) + return sz + else: + return sz_ + return build + +################################################################################################### +from gui.anylists import AnyRow +class PrivacyListRow(AnyRow): + row_height = 20 + def __init__(self, parent, data): + AnyRow.__init__(self, parent, data, False) + self.mouse_flag = False + self.Bind(EVT_LEFT_DOWN, self.onleftdown) + self.Bind(EVT_LEFT_UP, self.leftup) + + @property + def popup(self): + return None + + @property + def image(self): + return None + +# def LayoutMore(self, sizer): +# sizer.Add(self.rem_button, 0, ALL | ALIGN_CENTER_VERTICAL, 10) + + def ConstructMore(self): + from gui import skin +# self.rem_button = wx.StaticBitmap(self, -1, skin.get('AppDefaults.removeicon')) + self.rem_ico = skin.get('AppDefaults.removeicon') + + + + def onleftdown(self, e): + rem_ico_size = self.rem_ico.Size + + rect = Rect(self.Size.width - rem_ico_size.width - 4, self.Size.height//2 - rem_ico_size.height//2,*rem_ico_size) + + if rect.Contains(e.Position): + self.mouse_flag = True + else: + e.Skip() + + def leftup(self, e): + + rem_ico_size = self.rem_ico.Size + + rect = Rect(self.Size.width - rem_ico_size.width - 4, self.Size.height//2 - rem_ico_size.height//2,*rem_ico_size) + + if rect.Contains(e.Position): + if self.mouse_flag: + remove_row = getattr(self.Parent, 'remove_row', None) + if remove_row is not None: + remove_row(self.data) + else: + p = self.Parent.Parent.Parent + wx.CallAfter(p.remove_item, self.data) + else: + e.Skip() + self.mouse_flag = False + + def PaintMore(self, dc): + rect= RectS(self.Size) + + rem_ico = self.rem_ico + + dc.DrawBitmap(rem_ico,rect.Width - rem_ico.Width - 4, rect.Height//2 - rem_ico.Height//2,True) + + +class AutoCompleteListEditor(wx.Frame): + def __init__(self, *a, **k): + wx.Frame.__init__(self, *a, **k) + self._panel = p = wx.Panel(self, -1) + self._text = _AutoCombo(p, -1, style=wx.TE_PROCESS_ENTER) + + listvals = self.get_values() + + self.list = AnyList(p, data = listvals, row_control = PrivacyListRow, style = 8, + draggable_items = False) + self.list.BackgroundColour = wx.WHITE + + self.populate_combo() + + self.add_btn = wx.Button(p, -1, _('Add'), style = wx.BU_EXACTFIT) + self.add_btn.MinSize = wx.Size(-1, 0) + if platformName != 'mac': + self.add_btn.Font = CopyFont(self.add_btn.Font, weight = wx.BOLD) + else: + self.add_btn.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + + tb_sz = HSizer() + tb_sz.Add(self._text, 1, ALIGN_CENTER_VERTICAL) + tb_sz.Add(self.add_btn, 0, EXPAND | ALL | ALIGN_CENTER_VERTICAL) + + p.Sizer = sz = VSizer() + sz.Add(tb_sz, 0, EXPAND | ALL, border=3) + sz.Add(self.list, 1, EXPAND | ALL, border=3) + + self.add_btn.Bind(wx.EVT_BUTTON, self._add_clicked) + self._text.Bind(wx.EVT_TEXT, self.set_btn_enable) + self._text.Bind(wx.EVT_TEXT_ENTER, self._add_clicked) + + self.set_btn_enable() + self.Layout() + + text = wx_prop('_text') + + def set_btn_enable(self, e=None): + self.add_btn.Enable(self.validate(self.text)) + + def populate_combo(self): + self._text.Clear() + self._text.AppendItems(self.get_autovalues()) + + def _add_clicked(self, e): + if not self.validate(self.text): + log.error('%s does not validate. Not adding item.', self.text) + return + + if self.text: + self.add_item(self.text) + self.text = '' + + def _rem_clicked(self, e): + sel = self.list.Selection + if sel == wx.NOT_FOUND: return + obj = self.list.data[sel] + #_str = self.list.GetString(sel) + self.remove_item(obj) + first = clamp(sel, 0, len(self.list.data)-1) + print first, len(self.list.data)-1 + if first >= 0: + self.list.Selections = [first] + + def _get_list(self): + return self.list.data + + def _set_list(self, val): + self.list.data[:] = val + self.list.on_data_changed() + + mylist = property(_get_list, _set_list) + + def get_values(self): + raise NotImplementedError + + def add_item(self, _str): + self.list.data.append(_str) + self.populate_combo() + self.set_btn_enable() + self.list.on_data_changed() + + def remove_item(self, obj): + assert obj in self.list.data + self.list.RemoveItem(obj) + self.populate_combo() + + def get_matches(self, _str): + raise NotImplementedError + + def get_autovalues(self): + raise NotImplementedError + + def __contains__(self, x): + return x in self.list + + def on_close(self): + self._text.on_close() + self.list.on_close() + +def pname(prefix, name): + 'Glues a prefix on a name, if it exists.' + if name and name.startswith('local.'): + return name + return prefix + '.' + name if prefix else name diff --git a/digsby/src/gui/pref/prefsdialog.py b/digsby/src/gui/pref/prefsdialog.py new file mode 100644 index 0000000..6762b71 --- /dev/null +++ b/digsby/src/gui/pref/prefsdialog.py @@ -0,0 +1,419 @@ +''' +Main preferences dialog. +''' +from __future__ import with_statement + +import wx +import metrics + +from gui.textutil import default_font +from gui.toolbox import build_button_sizer, snap_pref, persist_window_pos, AutoDC, calllimit +from gui.validators import LengthLimit +from gui.pref.prefstrings import all as allprefstrings, tabnames + +from util import import_function, traceguard +from util.primitives.funcs import Delegate +from common import profile + +from logging import getLogger; log = getLogger('prefsdialog') + +EXPAND_ALL = wx.EXPAND | wx.ALL + +wxMac = 'wxMac' in wx.PlatformInfo + +def show(tabname='accounts'): + ''' + Displays the Preferences dialog with the specified tab active. + + For tab names, see "prefstrings.py" + ''' + + if not isinstance(tabname, str): + raise TypeError('prefsdialog.show takes a tab name') + + import hooks; hooks.notify('digsby.statistics.prefs.prefs_opened') + + tabindex = [c[0] for c in tabnames].index(tabname) + return show_prefs_window(None, tabindex) + +def show_prefs_window(parent, tab = 0): + win = PrefsDialog.RaiseExisting() + if win is not None: + win.show_tab(tab) + return win + + # Show the dialog (NOT modal) + pdiag = PrefsDialog(None, initial_tab = tab) + persist_window_pos(pdiag, position_only = True) + wx.CallAfter(pdiag.Show) + wx.CallAfter(pdiag.ReallyRaise) + return pdiag + +#TODO: +# - focus indication on tabs + +class PrefsSearch(wx.SearchCtrl): + 'A search textfield.' + + def __init__(self, parent): + wx.SearchCtrl.__init__(self, parent, -1, style = wx.TE_PROCESS_ENTER, validator=LengthLimit(128)) + self.ShowSearchButton(True) + +class PrefsTabs(wx.VListBox): + 'Custom drawn preference tabs.' + + item_height = 27 + + def __init__(self, parent): + wx.VListBox.__init__(self, parent, size=(130,-1)) + self.ItemCount = len(tabnames) + + self.spotlight_indices = set() + + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) + self.Bind(wx.EVT_KEY_DOWN, self.OnKey) + + + self.UpdateSkin() + + self.tab_indices = dict((modname, i) + for i, (modname, nicename) in enumerate(tabnames)) + self.hoveridx = -1 + + def OnKey(self, e): + e.Skip() + if e.KeyCode == wx.WXK_ESCAPE: + self.Top.Close() + + def OnLeave(self, e): + self.Hover = -1 + e.Skip(True) + + def OnMotion(self, e): + self.Hover = self.HitTest(e.Position) + e.Skip(True) + + + def get_hover(self): + return self.hoveridx + + def set_hover(self, i): + old = self.hoveridx + self.hoveridx = i + + if i != old: + if old != -1: self.RefreshLine(old) + if i != -1: self.RefreshLine(i) + + Hover = property(get_hover, set_hover) + + def UpdateSkin(self): + from gui import skin + self.bgs = skin.get('AppDefaults.preftabs.backgrounds') + + self.BackgroundColour = wx.Colour(*self.bgs.normal) + + def tabname(self, index): + return tabnames[index][1] + + def spotlight(self, tab_names): + old = self.spotlight_indices + self.spotlight_indices = set(self.tab_indices[name] for name in tab_names) + + + self.RefreshAll() + + def OnMeasureItem(self, n): + return self.item_height + + def OnDrawItem(self, dc, rect, n): + selected = self.GetSelection() == n + + + font = default_font() + if selected: + font.SetWeight( wx.FONTWEIGHT_BOLD ) + + fh = font.Height + dc.Font = font + dc.TextForeground = wx.BLACK + + pt = wx.Point(*rect[:2]) + (5, self.item_height / 2 - fh / 2) + dc.DrawText(self.tabname(n), *pt) + + def OnDrawBackground(self, dc, rect, n): + selected = self.Selection == n + hover = self.Hover == n + dc.Pen = wx.TRANSPARENT_PEN + + if n in self.spotlight_indices: + self.bgs.search.Draw(dc, rect, n) + elif selected: + self.bgs.selected.Draw(dc, rect, n) + elif hover: + self.bgs.hover.Draw(dc, rect, n) + else: + self.bgs.normal.Draw(dc, rect, n) + + +prefs_dialog_style = wx.DEFAULT_FRAME_STYLE & ~(wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX) + +class PrefsDialog(wx.Frame): + 'The main Preferences window.' + + border = 5 + default_selected_tab = 1 + default_size = (700, 525) + + def __init__(self, parent, initial_tab = default_selected_tab): + wx.Frame.__init__(self, parent, title = _('Digsby Preferences'), + size = self.default_size, + style = prefs_dialog_style, + name = 'Preferences Window') + + self.loaded_panels = {} + self.SetMinSize(self.default_size) + + metrics.event('Prefs Dialog Opened') + + self.create_gui() + self.bind_events() + self.layout_gui() + self.exithooks = Delegate() + + with traceguard: + from gui import skin + self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon')) + + if not wxMac: + self.BackgroundColour = wx.WHITE + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + # Fake a first selection + self.tabs.SetSelection(initial_tab) + self.on_tab_selected(initial_tab) + + self.tabnames = names = [module_name for module_name, nice_name in tabnames] + + self.Bind(wx.EVT_CLOSE, self.on_close) + self._loaded = 0 + + # Obey the windows.sticky prreference + snap_pref(self) + + profile.prefs.add_observer(self.incoming_network_prefs) + + from gui.uberwidgets.keycatcher import KeyCatcher + k = self._keycatcher = KeyCatcher(self) + k.OnDown('ctrl+w', self.Close) + k.OnDown('escape', self.Close) + + def OnPaint(self, e): + dc = AutoDC(self) + + crect = self.ClientRect + + if not wxMac: + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(crect) + + self.tabs.bgs.normal.Draw(dc, wx.Rect(0, 0, self.tabs.ClientRect.width, crect.height)) + + def incoming_network_prefs(self, src, *a): + wx.CallAfter(self._gui_incoming_network_prefs, src) + + @calllimit(2) + def _gui_incoming_network_prefs(self, src): + if src.isflagged('network'): + wx.CallAfter(self.reload) + + def on_close(self, e): + self.Hide() + e.Skip() # will Destroy the dialog + + from common import profile + profile.prefs.remove_observer(self.incoming_network_prefs) + + self.exithooks() + + for panel in self.loaded_panels.itervalues(): + if hasattr(panel, 'on_close'): + with traceguard: + panel.on_close() + + profile.save('prefs') + profile.save('notifications') + + from gui.native import memory_event + memory_event() + + def create_gui(self): + self.tabs = PrefsTabs(self) # The Tab list on the left + #self.content = wx.Panel(self) # Panel + self.search = PrefsSearch(self) # Search text box + self.search.Bind(wx.EVT_TEXT, self.search_text) + self.search.Bind(wx.EVT_TEXT_ENTER, self.search_enter) + + if False: #not wxMac: + self.save_button = wx.Button(self, wx.ID_SAVE, _('&Done')) + self.save_button.SetDefault() + self.save_button.Bind(wx.EVT_BUTTON, lambda e: self.Close()) + + def search_enter(self, e): + if len(self.tabs.spotlight_indices) == 1: + i = self.tabs.spotlight_indices.copy().pop() + self.tabs.SetSelection(i) + self.on_tab_selected(i) + + #@calllimit(.45) + def search_text(self, e): + wx.CallAfter(self._search) + + def get_prefstrings(self): + try: + return self._prefstrings + except AttributeError: + self._prefstrings = dict( + (name, ' '.join(s.lower().replace('&', '') for s in strings)) + for name, strings in allprefstrings.iteritems()) + + return self._prefstrings + + def _search(self): + val = self.search.Value + if val == _('Search'): return + val = val.lower() + if val == '': return self.tabs.spotlight([]) + + tab_highlights = set() + + for module_name, stringset in self.get_prefstrings().iteritems(): + if val in stringset: + tab_highlights.add(module_name) + + self.tabs.spotlight(tab_highlights) + + + def bind_events(self): + self.tabs.Bind(wx.EVT_LISTBOX, self.on_tab_selected) + + def layout_gui(self): + self.content_sizer = wx.BoxSizer(wx.VERTICAL) + + hz = wx.BoxSizer(wx.HORIZONTAL) + hz.Add(self.build_tab_sizer(), 0, EXPAND_ALL) + hz.Add(self.content_sizer, 1, EXPAND_ALL) + + v = wx.BoxSizer(wx.VERTICAL) + v.Add(hz, 1, EXPAND_ALL) + + if getattr(self, 'save_button', False): + v.Add(build_button_sizer(self.save_button, border = self.border), + 0, wx.EXPAND | wx.SOUTH | wx.EAST, 4) + self.Sizer = v + + def build_tab_sizer(self): + sz = wx.BoxSizer(wx.VERTICAL) + sz.Add(self.search, 0, EXPAND_ALL, self.border) + if wxMac: + sz.AddSpacer(6) + sz.Add(self.tabs, 1, EXPAND_ALL) + return sz + + def on_tab_selected(self, e): + 'A preference tab has been selected.' + + index = e if isinstance(e, int) else e.Int + if index != -1: + # paint the new tab selection before loading the panel + self.tabs.Update() + wx.CallAfter(self.show_panel, self.panel_for_tab(index)) + + def show_tab(self, n): + log.info('show_tab %d', n) + self.tabs.SetSelection(n) + self.show_panel(self.panel_for_tab(n)) + + def reload(self): + log.info('reloading Prefs dialog') + with self.Frozen(): + assert not wx.IsDestroyed(self.content_sizer) + child = self.content_sizer.Children[0] + child.Show(False) + self.content_sizer.Detach(0) + assert len(self.content_sizer.Children) == 0 + + for panel in self.loaded_panels.itervalues(): + try: panel.on_close() + except AttributeError: pass + panel.Destroy() + + self.loaded_panels.clear() + del self.exithooks[:] + + self.show_tab(self.tabs.GetSelection()) + + def show_panel(self, panel): + if not isinstance(panel, wx.WindowClass): + raise TypeError('show_panel takes a Window, you gave %r' % panel) + + with self.FrozenQuick(): + s = self.content_sizer + if len(s.GetChildren()) > 0: + s.Show(0, False) + s.Detach(0) + s.Add(panel, 1, EXPAND_ALL, 10) + assert len(s.Children) == 1 + self.SetMaxSize(self.default_size) + self.Layout() + self.Fit() + self.SetMaxSize((-1,-1)) + + on_show = getattr(panel, 'on_show', None) + if hasattr(on_show, '__call__'): + on_show() + + def later(): + panel.Show() + panel.Parent.Layout() + + wx.CallAfter(later) + + def panel_for_tab(self, i): + 'Returns the preference panel for the ith tab.' + + module_name = tabnames[i][0] + + if not module_name in self.loaded_panels: + log.info('loading panel "%s"', module_name) + func = import_function('gui.pref.pg_%s.panel' % module_name) + panel = self._construct_sub_panel(func) + self.loaded_panels[module_name] = panel + + return self.loaded_panels[module_name] + + def _construct_sub_panel(self, func): + # preference panel setup that is common to all tab panels. + from gui.pref.prefcontrols import PrefsPanel + p = PrefsPanel(self) + p.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + szAdd = sz.Add + + from gui.uberwidgets.PrefPanel import PrefPanel,PrefCollection + def addgroup(titleortuple, *workers,**options): # given as a shortcut to each pref page + if isinstance(titleortuple,tuple): + title,prefix = titleortuple + else: + title = titleortuple + prefix = '' + group = PrefCollection(*workers,**options) + panel = PrefPanel(p, group, title, prefix = prefix) + szAdd(panel, 0, EXPAND_ALL, 3) + return panel + + return func(p, sz, addgroup, self.exithooks) diff --git a/digsby/src/gui/pref/prefsearchable.py b/digsby/src/gui/pref/prefsearchable.py new file mode 100644 index 0000000..e3c697e --- /dev/null +++ b/digsby/src/gui/pref/prefsearchable.py @@ -0,0 +1,230 @@ +# +# DO NOT EDIT +# +# this file is generated by running gensearchable.py in this directory +# + +accounts = [ + "(auto login)", + "Edit", + "Remove", + "Add Accounts", + "My Accounts", + "Account Options", + "buddy list", + "icon tray", + "buddy list and icon tray", + "Show email accounts in:", + "Show social networks in:", +] + +advanced = [ + "IM Window", + "Merge Metacontact History", + "Use Proxy Server", + "Use Global Settings", + "Protocol:", + ":", +] + +appearance = [ + "Application Skin", + "Conversation Theme", + "Conversation Preview", + "Skin:", + "Variant:", + "Show header", + "Show message fonts", + "Show message colors", + "Theme:", + "Fonts:", + "Colors:", +] + +contact_list = [ + "Sorting and Groups", + "Autohide when not in focus", + "Automatically dock when near edge of screen", + "Keep on top of other applications", + "Show in taskbar", + "Window Options", + "Contact Layout", + "Advanced", + "Basic", + "Group by:", + "Sort by:", + "Then by:", + "Far Left", + "Left", + "Right", + "Far Right", + "Badge (Lower Left)", + "Badge (Lower Right)", + "Show buddy icon on the:", + "Buddy icon size:", + "Buddy padding:", + "Show status icon on:", + "Show service icon on:", + "Contact name:", + "Show extra info:", + "Status", + "Idle Time", + "Idle Time + Status", +] + +dev = [ + "Advanced Prefs", + "Enable Debug Console", + "Allow Reconnect if --start-offline", + "Debug", + "Digsby Protocol", + "Append signature", + "Email", + "Enabled", + "Print debug output to console. (don't use pipes)", + "Always On", + "Run when on battery", + "Plura", + "Use Global Status Dialog (may require restart)", + "Scan tweets for URLs (for popup click action)", + "Social accounts", +] + +files = [ + "File Transfers", + "Save files to:", + "Create subfolders for each IM account", + "Auto-accept all file transfers from contacts on my contact list", +] + +general_profile = [ + "Launch Digsby when this computer starts", + "Automatically download software updates", + "If connection to IM service is lost, automatically attempt to reconnect", + "Show trending news articles in social network feeds (powered by OneRiot)", + "General Options", + "Profile (AIM Only)", + "Buddy Icon", + "Language", + "Change", + "Promote Digsby in my AIM profile", +] + +helpdigsby = [ + "Allow Digsby to conduct research during idle itme", + "Help Digsby", +] + +notifications = [ + "bottom right corner", + "bottom left corner", + "top right corner", + "top left corner", + "Enable pop-up notifications in the ", + " on monitor ", + "Enable sounds", + "Notifications", + "Events", + "Restore Defaults", + "Restore Default Notifications", +] + +plugins = [ +] + +privacy = [ + "Let others know that I am typing", + "Automatically sign me into websites (e.g., Yahoo! Mail)", + "Global Privacy Options", + "You must be signed in to modify privacy settings.", + "Sign in with \"%s\"", + "Edit", + "Allow all users to contact me", + "Allow only users on my contact list", + "Allow only users on 'Allow List'", + "Block all users", + "Block only users on 'Block List'", + "Only my screen name", + "Only that I have an account", + "Nothing about me", + "Permissions", + "Options", + "Allow only users on my buddy list to contact me", + "Require authorization before users can add me to their contact list", + "Block authorization requests with URLs in them", + "Allow others to view my online status from the web", + "Allow unknown users to contact me", + "%s is on your buddylist. Do you want to remove %s from your buddylist and block the user?", + "Confirm Block and Remove", + "Nothing to see here. If you do see this, this is either a special dev-only account, or something is broken.", +] + +proxy = [ +] + +research = [ + "Research Module", + "Allow Digsby to use CPU time to conduct research after %2(research.idle_time_min)d minutes of idle time", + "Maximum CPU Usage:", + "%", + "Maximum Bandwidth Usage:", + "Options", +] + +status = [ + "Status Options", + "Promote Digsby in my IM status messages", + "Let others know that I am idle after %2(messaging.idle_after)d minutes of inactivity", + "Autorespond with status message", + "Disable sounds", + "Disable pop-up notifications", + "When away...", + "Hide new conversation windows", + "Disable sounds", + "Disable popup notifications", + "When running full screen applications...", + "Status Messages", +] + +supportdigsby = [ + "Support Digsby", +] + +text_conversations = [ + "automatically take focus", + "start minimized in taskbar", + "start hidden (tray icon blinks)", + "bottom", + "top", + "left", + "right", + "Keep on top of other applications", + "Group multiple conversations into one tabbed window", + "New conversation windows: ", + "buddy icon", + "service icon", + "status icon", + "Identify conversations with the contact's: ", + "Window Options", + "Conversation Options", + "Don't show flash ads", + "Support Digsby development with an ad", + "Location of ad in IM window: ", + "Ad Options", + "Text Formatting", + "Your messages will look like this.", + "Show last %2(conversation_window.num_lines)d lines in IM window", + "Display timestamp:", + "Spell check:", + "Log IM conversations to hard drive", + "Show emoticons:", +] + +widgets = [ + "Widget Preview", + "Copy To Clipboard", + "Widgets", + "New Widget", + "Embed Tag", +] + diff --git a/digsby/src/gui/pref/prefstrings.py b/digsby/src/gui/pref/prefstrings.py new file mode 100644 index 0000000..d257fac --- /dev/null +++ b/digsby/src/gui/pref/prefstrings.py @@ -0,0 +1,54 @@ +''' + +contains all visible strings in the preferences dialog for making searching fast + +''' +import sys +from config import platformName + +# maps pg_XXX.py (module names) to display names for each preference tab + +tabnames = [ + + # do not change the first column without searching the code for "prefsdialog.show('XXX')" + ('accounts', _('Accounts')), + ('general_profile', _('General & Profile')), + ('appearance', _('Skins')), + ('contact_list', _('Buddy List')), + ('text_conversations', _('Conversations')), + ('files', _('File Transfers')), + ('status', _('Status')), + ('privacy', _('Privacy')), + ('notifications', _('Notifications')), + ('widgets', _('Widgets')), +# ('supportdigsby', _('Support Digsby')), + ('research', _('Research Module')), + ('proxy', _("Connection Settings")), +# ('plugins', _('Plugins')), +# ('helpdigsby', _('Help Digsby')), +# ('advanced', _('Advanced')), +] + +if platformName != 'mac': + tabnames.extend([ + ]) + +if getattr(sys, 'DEV', False): + from path import path + location = path(__file__) + pg_dev_loc = location.parent / 'pg_dev.py' + if pg_dev_loc.isfile(): + tabnames.extend([ + ('dev', _('Developer')) + ]) +# tabnames.insert(4, ('sandbox', _('Sandbox')),) + +# for searching, keep a map of {module_name: set(translated strings)} +import gui.pref.prefsearchable as prefsearchable + +all = dict() +for module, nice_name in tabnames: + strs = getattr(prefsearchable, module, getattr(sys, 'DEV', False) and []) + if strs is not None: + all[module] = set(_(s) for s in strs) + diff --git a/digsby/src/gui/profiledialog.py b/digsby/src/gui/profiledialog.py new file mode 100644 index 0000000..beeb7e7 --- /dev/null +++ b/digsby/src/gui/profiledialog.py @@ -0,0 +1,407 @@ +import hooks +import wx +import digsby.blobs as blobs +import digsby.accounts as accounts +from digsby.DigsbyProtocol import DigsbyProtocol +from wx import WHITE, HORIZONTAL, VERTICAL, ALIGN_RIGHT, ALIGN_CENTER_VERTICAL, EXPAND, ALIGN_LEFT, ALL +from cgui import SimplePanel +from gui.uberwidgets.PrefPanel import PrefPanel +from gui.validators import NumericLimit +from config import platformName +import util.proxy_settings +import util.callbacks as callbacks + +import logging +log = logging.getLogger('gui.profiledialog') + +ID_NEWPROFILE = wx.NewId() +ID_IMPORTPROFILE = wx.NewId() + +class ProfilePanel(SimplePanel): + WRAP_WIDTH = 356 + + def __init__(self, parent, validator = None): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + self.validator = validator + + if platformName != 'mac': + self.BackgroundColour = WHITE + + sz = self.Sizer = wx.BoxSizer(VERTICAL) + + profile_type = wx.Panel(self) + rs = profile_type.Sizer = wx.BoxSizer(VERTICAL) + + RADIO = wx.RadioButton + overrads = self.overrads = dict(NEWPROFILE = RADIO(profile_type, ID_NEWPROFILE, _("&New Profile"), style = wx.RB_GROUP), + IMPORTPROFILE = RADIO(profile_type, ID_IMPORTPROFILE, _("&Import an existing profile from the Digsby servers"))) + + rs.Add(overrads["NEWPROFILE"], 0, ALL, 2) + rs.Add(overrads["IMPORTPROFILE"], 0, ALL, 2) + +#------------------------------------------------------------------------------- + login_info = wx.Panel(self) + + ps = wx.FlexGridSizer(2, 2) + + TEXT = lambda s: wx.StaticText(login_info, -1, s) + INPUT = lambda d, style = 0, v = wx.DefaultValidator: wx.TextCtrl(login_info, -1, d, style = style, validator = v) + + digsby_username = self.digsby_username = INPUT('') + digsby_password = self.digsby_password = INPUT('', style = wx.TE_PASSWORD) + + ps.Add(TEXT(_("&Digsby Username:")), 0, ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + ps.Add(digsby_username, 0, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + + ps.Add(TEXT(_("Digsby &Password:")), 0, ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + ps.Add(digsby_password, 0, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + + ps.AddGrowableCol(1, 1) + + login_info.Sizer = wx.BoxSizer(wx.VERTICAL) + login_info.Sizer.Add(ps, 1, wx.EXPAND) + self.login_status_text = TEXT('') + self.login_status_text.Hide() + login_info.Sizer.Add(self.login_status_text, 0, wx.EXPAND | wx.ALL, 7) + +#------------------------------------------------------------------------------- + + profile_info = wx.Panel(self) + aus = profile_info.Sizer = wx.FlexGridSizer(2, 2) + + aus = wx.FlexGridSizer(2, 2) + + TEXT = lambda s: wx.StaticText(profile_info, -1, s) + INPUT = lambda d, style = 0, id=-1: wx.TextCtrl(profile_info, id, d, style = style) + + profile_name = self.profile_name = INPUT('') + profile_password = self.profile_password = INPUT('', wx.TE_PASSWORD) + reenter_password = self.profile_password_2 = INPUT('', wx.TE_PASSWORD) + + aus.Add(TEXT(_("&Profile Name:")), 0, ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + aus.Add(profile_name, 0, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + + aus.Add(TEXT(_("&Profile Password:")), 0, ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + + s = wx.BoxSizer(wx.HORIZONTAL) + s.Add(profile_password, 1, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND) + s.Add(TEXT(_('(optional)')), 0, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | wx.LEFT | wx.TOP, 5) + aus.Add(s, 1, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + + aus.Add(TEXT(_("&Re-Enter Password:")), 0, ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + + s = wx.BoxSizer(wx.HORIZONTAL) + s.Add(reenter_password, 1, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND) + s.Add(TEXT(_('(optional)')), 0, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | wx.LEFT | wx.TOP, 5) + aus.Add(s, 1, ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + + aus.AddGrowableCol(1, 1) + + password_note = TEXT(_('NOTE: For security, your password is not saved anywhere. If you forget it, there is no way to decrypt the account information for that profile. You\'ll need to remove the profile, create a new one, and add your accounts back.')) + password_note.SetForegroundColour(wx.RED) + password_note.Wrap(356) + + profile_info.Sizer = wx.BoxSizer(VERTICAL) + profile_info.Sizer.Add(aus, 0, EXPAND | ALL) + profile_info.Sizer.Add(password_note, 0, EXPAND | ALL, 7) + +#------------------------------------------------------------------------------- + + self.profile_info_panel = PrefPanel(self, profile_info, _("Profile Info")) + self.login_info_panel = PrefPanel(self, login_info, _("Login Info")) + self.login_info_panel.Show(False) + + sz.Add(PrefPanel(self, profile_type, _("Profile Type")), 0, EXPAND | ALL, 2) + sz.Add(self.profile_info_panel, 0, EXPAND | ALL, 2) + sz.Add(self.login_info_panel, 0, EXPAND | ALL, 2) + + Bind = self.Bind + Bind(wx.EVT_RADIOBUTTON, self.OnRadio) + Bind(wx.EVT_TEXT, self.validate_form) + + if platformName != 'mac': + Bind(wx.EVT_PAINT, self.OnPaint) + + self.validate_form() + + def SetImportStatusText(self, text, red=False): + '''Sets the text below the "import account" section.''' + + self.login_status_text.SetLabel(text) + self.login_status_text.Wrap(356) + self.login_status_text.Show(bool(text)) + self.login_status_text.SetForegroundColour(wx.RED if red else wx.BLACK) + self.Layout() + + def validate_form(self, evt = None): + valid = True + + if self.is_new_profile: + if not self.profile_name.Value: + valid = False + if not self.profile_password.Value: + valid = False + else: + if not self.digsby_username.Value: + valid = False + if not self.digsby_password.Value: + valid = False + + if self.validator: + self.validator(valid) + + def passwords_match(self): + if self.is_new_profile: + return self.profile_password.Value and self.profile_password.Value == self.profile_password_2.Value + else: + return True + + def OnRadio(self, event): + new = self.is_new_profile + self.profile_info_panel.Show(new) + self.login_info_panel.Show(not new) + self.Layout() + + def OnPaint(self, event): + dc = wx.PaintDC(self) + rect = wx.RectS(self.ClientSize) + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(rect) + + @property + def is_new_profile(self): + return self.overrads['NEWPROFILE'].Value + + +class ProfileDialog(wx.Dialog): + def __init__(self, parent = None): + wx.Dialog.__init__(self, parent, title = _('Add Profile')) + + if not platformName == 'mac': + self.SetBackgroundColour(wx.WHITE) + + bsz = wx.BoxSizer(wx.HORIZONTAL) + self.ok_button = okb = wx.Button(self, wx.ID_OK, _("&OK")) + okb.SetDefault() + self.cancel_button = canb = wx.Button(self, wx.ID_CANCEL, _("&Cancel")) + bsz.Add(okb, 0, ALL, 4) + bsz.Add(canb, 0, ALL, 4) + + self.Sizer = wx.BoxSizer(VERTICAL) + self.pp = ProfilePanel(self, self.on_validate) + self.Sizer.Add(self.pp, 1, EXPAND | ALL, 5) + + self.Sizer.Add(bsz, 0, ALIGN_RIGHT) + + okb.Bind(wx.EVT_BUTTON, self.OnOK) + canb.Bind(wx.EVT_BUTTON, lambda e: self.Close()) + + self.Fit() + self.Size = wx.Size(400, self.Size.height) + + self.Layout() + self.Bind(wx.EVT_CLOSE, self.OnClose) + + self.profile_importer = None + + def on_validate(self, valid): + self.ok_button.Enabled = valid + + def GetUsername(self): + if self.pp.is_new_profile: + return self.pp.profile_name.Value + else: + return self.pp.digsby_username.Value + + def SetImportStatusText(self, text, red=False): + return self.pp.SetImportStatusText(text, red) + + def GetPassword(self): + if self.pp.is_new_profile: + return self.pp.profile_password.Value + else: + return self.pp.digsby_password.Value + + def OnClose(self, event): + self.Show(False) + + def OnOK(self, event): + if hooks.first('digsby.identity.exists', self.GetUsername()): + wx.MessageBox(_('A profile named "%s" already exists. Please choose a different name or remove the existing profile.') % self.GetUsername(), + _('Profile exists')) + return + + if not self.pp.passwords_match(): + wx.MessageBox(_('Please ensure your passwords match.'), + _('Passwords do not match')) + else: + if self.pp.is_new_profile: + self.CloseWithOK() + else: + self.SetImportStatusText(_("Importing...")) + self.ok_button.Enabled = False + self.profile_importer = import_profile(self.GetUsername(), self.GetPassword(), + success = self.import_profile_success, + error = self.import_profile_error, + progress = self.update_status_text) + + def update_status_text(self, pi): + self.SetImportStatusText(pi.connection.state) + + @property + def is_new_profile(self): + return self.pp.is_new_profile + + def CloseWithOK(self): + self.SetReturnCode(wx.ID_OK) + self.Close() + + def import_profile_success(self): + log.info("Got profile!") + wx.CallAfter(self.CloseWithOK) + + def import_profile_error(self, err_text = None): + if err_text is not None: + self.SetImportStatusText(err_text, True) + + if self.profile_importer is None: + return + + log.info("Error getting profile") + pi, self.profile_importer = self.profile_importer, None + + if hooks.first('digsby.identity.exists', pi.identity.name): + hooks.first('digsby.identity.delete', pi.identity.name, pi.identity.password) + + # Form validation will re-enable OK button + self.pp.validate_form() + + if not err_text: + self.SetImportStatusText(_("An error occurred."), red = True) + + +def import_profile(username, password, callback): + profile_importer = DigsbyProfileImporter(username, password) + profile_importer.import_profile(callback = callback) + return profile_importer +import_profile = callbacks.callsback(import_profile, ('success', 'error', 'progress')) + + +class DigsbyProfileImporter(object): + def __init__(self, username, password): + self.callback = None + self.identity = hooks.first('digsby.identity.create', username, password) + self.connection = DigsbyProtocol( + username, password, + profile = Null, + user = None, + server = ("digsby.org", 5555), + login_as='invisible', + do_tls = False, + sasl_md5 = False, + digsby_login = True + ) + + def import_profile(self, callback = None): + if self.callback is not None: + raise Exception("Import already in progress") + + self.callback = callback + self.connection.add_observer(self.on_conn_state_changed, 'state', 'offline_reason') + self.connection.Connect(invisible = True, on_fail = self._on_conn_error) + import_profile = callbacks.callsback(import_profile, ('success', 'error', 'progress')) + + def _on_conn_error(self, *a): + self.error() + + def on_conn_state_changed(self, source, attr, old, new): + if source is not self.connection: + return + + if attr == 'offline_reason' and new == self.connection.Reasons.BAD_PASSWORD: + return self.error(self.connection.offline_reason) + + if attr != 'state': + return + + self.progress() + + if old == self.connection.Statuses.AUTHORIZED or new != self.connection.Statuses.AUTHORIZED: + return + + log.info("Importer connection status: %r -> %r", old, new) + + blob_names = blobs.name_to_ns.keys() + + self.waiting_for = set(blob_names) + + for blob_name in blob_names: + log.info("Requesting blob: %r", blob_name) + self.connection.get_blob_raw(blob_name, + success = self._blob_fetch_success, + error = self._blob_fetch_error) + + self.waiting_for.add('accounts') + self.connection.get_accounts(success = self._accounts_fetch_success, + error = self._accounts_fetch_error) + + def got_item(self, name, data): + log.info("Got blob: %r, saving.", name) + self.identity.set(name, data) + self.waiting_for.discard(name) + log.info("\tNow waiting for: %r", self.waiting_for) + + if not len(self.waiting_for): + self.success() + + def _blob_fetch_success(self, stanza): + ns = stanza.get_query_ns() + blob = blobs.ns_to_obj[ns](stanza.get_query()) + name = blobs.ns_to_name[ns] + + self.got_item(name, blob.data) + + def _blob_fetch_error(self, *_a): + self.error() + + def _accounts_fetch_success(self, stanza): + fetched_accounts = accounts.Accounts(stanza.get_query()) + self.got_item('accounts', fetched_accounts) + + def _accounts_fetch_error(self, *_a): + self.error() + + def disconnect(self): + conn, self.connection = self.connection, None + if conn is not None: + conn.remove_observer(self.on_conn_state_changed, 'state') + conn.Disconnect() + + def progress(self): + callback = getattr(self, 'callback', None) + if callback is not None: + getattr(callback, 'progress', lambda x: None)(self) + + def error(self, *a): + self._cleanup('error', *a) + + def success(self): + self._cleanup('success') + + def _cleanup(self, status, *a): + util.threaded(self.disconnect)() + callback, self.callback = self.callback, None + + if callback is not None: + getattr(callback, status, lambda: None)(*a) + + +if __name__ == "__main__": + from tests.testapp import testapp + app = testapp() + + f = ProfileDialog() + f.Show(True) + + app.MainLoop() diff --git a/digsby/src/gui/protocols/__init__.py b/digsby/src/gui/protocols/__init__.py new file mode 100644 index 0000000..c921ff5 --- /dev/null +++ b/digsby/src/gui/protocols/__init__.py @@ -0,0 +1,64 @@ +from util import traceguard +from gui.toolbox import GetTextFromUser +from common import profile, pref +import wx + +def change_password(protocol, cb): + val = GetTextFromUser(_('Enter a new password for {username}:'.format(username=protocol.username)), + _('Change Password'), + default_value = protocol.password, + password = True) + if val: cb(val) + +def remove_contact(contact, do_remove): + + action_allowed = getattr(do_remove, 'action_allowed', lambda c: True) + if not action_allowed(contact): + return + + yes_default = pref('prompts.defaults.contacts.del_contact', type=bool, default=True) + more_flags = wx.NO_DEFAULT * (not yes_default) + + if wx.YES == wx.MessageBox(_('Are you sure you want to delete contact {name}?').format(name=contact.name), + _('Delete Contact'), style = wx.YES_NO | more_flags): + do_remove() + +def remove_group(group, do_remove): + + try: + s = u'group "%s"' % group.name + except: + s = u'this group' + + yes_default = pref('prompts.defaults.contacts.del_group', type=bool, default=True) + more_flags = wx.NO_DEFAULT * (not yes_default) + + line1 = _('WARNING!') + line2 = _('All your contacts in this group will be deleted locally AND on the server.') + line3 = _('Are you sure you want to remove {groupname}?').format(groupname=s) + + msg = u'\n\n'.join((line1, line2, line3)) + + if wx.YES == wx.MessageBox(msg, _('Delete Group'), + style = wx.YES_NO | wx.ICON_ERROR | more_flags): + do_remove() + +def add_group(): + group = GetTextFromUser(_('Please enter a group name:'),_('Add Group')) + if group is None or not group.strip(): + return + + protos = [acct.connection for acct in profile.account_manager.connected_accounts] + for proto in protos: + with traceguard: + proto.add_group(group) + +def block_buddy(buddy, do_block): + yes_default = pref('prompts.defaults.contacts.block', type=bool, default=True) + more_flags = wx.NO_DEFAULT * (not yes_default) + + if wx.YES == wx.MessageBox(_('Are you sure you want to block %s?') % buddy.name, + _('Block Buddy'), + style = wx.YES_NO | more_flags): + do_block() + diff --git a/digsby/src/gui/protocols/jabbergui.py b/digsby/src/gui/protocols/jabbergui.py new file mode 100644 index 0000000..e576dc4 --- /dev/null +++ b/digsby/src/gui/protocols/jabbergui.py @@ -0,0 +1,440 @@ +''' + +Controls and dialogs for Jabber. + +''' + +import wx +from wx import ALL, TOP, BOTTOM, ALIGN_CENTER, EXPAND, VERTICAL, HORIZONTAL, BoxSizer + +from gui.toolbox import GetTextFromUser, wx_prop, build_button_sizer, persist_window_pos, snap_pref +from gui.validators import LengthLimit +from common import profile +from util.primitives.funcs import Delegate +from util.primitives.mapping import Storage as S +from logging import getLogger; log = getLogger('jabbergui') + +def set_priority(jabber, set): + val = GetTextFromUser(_('Enter a priority for %s:' % jabber.username), + caption = _('Set Jabber Priority'), + default_value = str(jabber.priority)) + + if val is not None: + try: + set(int(val)) + except ValueError: + pass + + +class JabberDeleteConfirmBox(wx.Dialog): + def __init__(self, acct, msg, *a, **k): + self.acct = acct + wx.Dialog.__init__(self, *a, **k) + + self.init_components(msg) + self.bind_events() + self.layout_components() + + # TODO: we need + self.del_check.Enable(self.acct.connection is not None) + + self.Layout() + self.Sizer.Fit(self) + + + def init_components(self, msg): + self.help_bmp = wx.StaticBitmap(self) + self.help_bmp.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_HELP, wx.ART_OTHER, (32, 32))) + self.msg_label = wx.StaticText(self, label=msg) + self.del_check = wx.CheckBox(self, label="Also delete from Jabber server") + + self.pw_text = wx.TextCtrl(self, style=wx.TE_PASSWORD, validator=LengthLimit(1024)) + self.pw_label = wx.StaticText(self, label="Password: ") + self.yes_btn = wx.Button(self, wx.ID_YES) + self.yes_btn.SetDefault() + self.no_btn = wx.Button(self, wx.ID_NO) + self.del_label = wx.StaticText(self, label='Deleting...') + + self.del_label.Show(False) + self.del_label.Enable(False) + + self.info_sizer = BoxSizer(HORIZONTAL) + self.btn_sizer = BoxSizer(HORIZONTAL) + self.pw_sizer = BoxSizer(HORIZONTAL) + self.delpw_sizer = BoxSizer(VERTICAL) + self.in_sizer = BoxSizer(HORIZONTAL) + + self.main_sizer = BoxSizer(VERTICAL) + self.Sizer = BoxSizer(HORIZONTAL) + + def bind_events(self): + self.yes_btn.Bind(wx.EVT_BUTTON, self.yes_clicked) + self.no_btn.Bind(wx.EVT_BUTTON, self.no_clicked) + self.del_check.Bind(wx.EVT_CHECKBOX, self.on_check) + + self.on_check(None) + + def layout_components(self): + self.pw_sizer.Add(int(self.del_check.Size.height*1.5), 0, wx.ALL, 3) + self.pw_sizer.Add(self.pw_label, 0, ALL, 3) + self.pw_sizer.Add(self.pw_text, 0, ALL, 3) + + self.btn_sizer.AddMany([(self.yes_btn, 0, ALL, 3), + (self.no_btn, 0, ALL, 3), + (self.del_label, 0, ALL, 3)]) + + self.in_sizer.AddSpacer(self.help_bmp.Size.width) + + self.delpw_sizer.Add(self.del_check, 0, ALL) + self.delpw_sizer.Add(self.pw_sizer, 0, ALL, 3) + self.in_sizer.Add(self.delpw_sizer, 0, ALL, 3) + + self.info_sizer.Add(self.help_bmp, 0, TOP | BOTTOM | ALIGN_CENTER, 3) + self.info_sizer.Add(self.msg_label, 0, ALL | ALIGN_CENTER, 3) + + self.main_sizer.AddMany([(self.info_sizer, 0, ALL, 3), + (self.in_sizer, 0, ALL, 3), + (self.btn_sizer, 0, ALIGN_CENTER | ALL, 3)]) + + self.Sizer = self.main_sizer + + def yes_clicked(self, e): + success = lambda: self.EndModal(wx.ID_YES) + if self.delete: + if self.acct.password != profile.crypt_pw(self.password): + wx.MessageBox(_("Incorrect Password"), _("Incorrect Password")) + return + self.show_buttons(False) + self.acct.delete_from_server(self.password, + on_success=success, + on_fail=lambda: (self.show_buttons(), + wx.MessageBox(message=_("Failed to delete account from the server."), + caption=_("Delete Account - Failed"), style=wx.OK))) + else: + success() + + def show_buttons(self, val=True): + self.yes_btn.Enabled = val + self.no_btn.Enabled = val + self.yes_btn.Show(val) + self.no_btn.Show(val) + self.del_label.Show(not val) + + self.Layout() + + def no_clicked(self, e): + self.EndModal(wx.NO) + + def on_check(self, e): + self.pw_text.Enabled = self.delete + + delete = wx_prop('del_check') + password = wx_prop('pw_text') + + + +class PasswordChangeDialog(wx.Dialog): + def __init__(self, parent, acct, *a, **k): + self.acct = acct + wx.Dialog.__init__(self, parent, title = _('Change Password'), *a, **k) + + self.init_components() + self.bind_events() + self.layout_components() + + self.Layout() + self.Sizer.Fit(self) + + + def init_components(self): + self.old_pw_label = wx.StaticText(self, label = _("Old Password: ")) + self.old_pw_text = wx.TextCtrl(self, style=wx.TE_PASSWORD, validator=LengthLimit(1024)) + self.new1_pw_label = wx.StaticText(self, label = _("New Password: ")) + self.new1_pw_text = wx.TextCtrl(self, style=wx.TE_PASSWORD, validator=LengthLimit(1024)) + self.new2_pw_label = wx.StaticText(self, label = _("Confirm New Password: ")) + self.new2_pw_text = wx.TextCtrl(self, style=wx.TE_PASSWORD, validator=LengthLimit(1024)) + + self.ok_btn = wx.Button(self, wx.ID_OK) + self.ok_btn.Enable(False) + self.ok_btn.SetDefault() + self.cancel_btn = wx.Button(self, wx.ID_CANCEL) + + self.old_sizer = BoxSizer(HORIZONTAL) + self.new1_sizer = BoxSizer(HORIZONTAL) + self.new2_sizer = BoxSizer(HORIZONTAL) + + self.btn_sizer = build_button_sizer(self.ok_btn, self.cancel_btn, 2) + + self.main_sizer = BoxSizer(VERTICAL) + + self.Sizer = BoxSizer(HORIZONTAL) + + def bind_events(self): + self.old_pw_text.Bind(wx.EVT_TEXT, self.check_fields) + self.new1_pw_text.Bind(wx.EVT_TEXT, self.check_fields) + self.new2_pw_text.Bind(wx.EVT_TEXT, self.check_fields) + self.cancel_btn.Bind(wx.EVT_BUTTON, self.cancel_clicked) + self.ok_btn.Bind(wx.EVT_BUTTON, self.ok_clicked) + + def check_fields(self, e): + old, new1, new2 = self.old_password, self.new1_password, self.new2_password + + self.ok_btn.Enable(all([old, new1, new2]) and new1 == new2) + + def layout_components(self): + border = (0, ALL, 2) + + self.old_sizer.Add(self.old_pw_label, *border) + self.old_sizer.AddStretchSpacer() + self.old_sizer.Add(self.old_pw_text, *border) + + self.new1_sizer.Add(self.new1_pw_label, *border) + self.new1_sizer.AddStretchSpacer() + self.new1_sizer.Add(self.new1_pw_text, *border) + + self.new2_sizer.Add(self.new2_pw_label, *border) + self.new2_sizer.AddStretchSpacer() + self.new2_sizer.Add(self.new2_pw_text, *border) + + self.main_sizer.AddMany([(self.old_sizer, 0, ALL | EXPAND, 2), + (self.new1_sizer, 0, ALL | EXPAND, 2), + (self.new2_sizer, 0, ALL | EXPAND, 2), + (self.btn_sizer, 0, ALIGN_CENTER | ALL, 2)]) + + self.Sizer = self.main_sizer + + old_password = wx_prop('old_pw_text') + new1_password = wx_prop('new1_pw_text') + new2_password = wx_prop('new2_pw_text') + + def cancel_clicked(self, e): + self.EndModal(wx.ID_CANCEL) + + def ok_clicked(self, e): + self.ok_btn.Disable() + + if self.acct.password != self.old_password: + wx.MessageBox(_("Incorrect Old Password"), _("Incorrect Old Password")) + elif self.new1_password != self.new2_password: + wx.MessageBox(_("Passwords do not match"), _("Paswords do not match")) + else: + + def success(st = None): + print 'success!', st + print 'setting password in', self.acct + from common import profile + self.acct.password = profile.crypt_pw(self.new1_password) + + for acct in profile.account_manager.accounts: + if acct.name == self.acct.username and acct.protocol == self.acct.protocol: + acct.update_info(password = profile.crypt_pw(self.new1_password)) + break + + self.EndModal(wx.ID_OK) + + def error(self, e = None): + print 'error', e + self.EndModal(wx.ID_OK) + + self.acct.change_password(self.new1_password, success = success, error = error) + + def Prompt(self, callback): + #self.callback = callback + self.ShowModal() + +def serialize_xmlnode(node): + 'Returns a unicode string for a libxml2 node.' + + return node.serialize(encoding = 'utf-8', format = True).decode('utf-8') + +class XMLConsole(wx.Panel): + ''' + A simple console for displaying incoming and outgoing Jabber XML. + ''' + + separator_text = '\n\n' # goes between stanzas + + def __init__(self, parent, jabber): + wx.Panel.__init__(self, parent) + + self.enabled = True + self.scroll_lock = False + + self.intercept = JabberXMLIntercept(jabber) + self.intercept.on_incoming_node += lambda node: self.on_node(node, 'incoming') + self.intercept.on_outgoing_node += lambda node: self.on_node(node, 'outgoing') + + self.construct() + self.bind_events() + self.layout() + + @property + def Connection(self): + return self.intercept.jabber + + def construct(self): + 'Construct controls.' + + self.console = wx.TextCtrl(self, -1, style = wx.TE_RICH2 | wx.TE_MULTILINE) + + self.enabled_cb = wx.CheckBox(self, -1, _('&Enabled')) + self.enabled_cb.SetValue(self.enabled) + + self.scroll_lock_cb = wx.CheckBox(self, -1, _('&Scroll Lock')) + self.scroll_lock_cb.SetValue(self.scroll_lock) + + # until I figure out how to make this work, disable it. + self.scroll_lock_cb.Enable(False) + + self.buttons = S(clear_b = wx.Button(self, -1, _('C&lear')), + close = wx.Button(self, -1, _('&Close'))) + + self.font_colors = dict( + incoming = wx.BLUE, + outgoing = wx.RED, + ) + + def bind_events(self): + 'Bind events to callbacks.' + + self.enabled_cb.Bind(wx.EVT_CHECKBOX, lambda e: setattr(self, 'enabled', e.IsChecked())) + self.scroll_lock_cb.Bind(wx.EVT_CHECKBOX, lambda e: setattr(self, 'scroll_lock', e.IsChecked())) + self.buttons.clear_b.Bind(wx.EVT_BUTTON, lambda e: self.console.Clear()) + self.buttons.close.Bind(wx.EVT_BUTTON, lambda e: self.Top.Destroy()) + + def layout(self): + 'Layout controls.' + + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + + s.Add(self.console, 1, wx.EXPAND | wx.ALL, 7) + + # buttons + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + button_sizer.Add(self.enabled_cb, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 7) + button_sizer.AddStretchSpacer(1) + button_sizer.AddMany([(self.scroll_lock_cb, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 7), + (self.buttons.clear_b, 0, wx.RIGHT, 7), + (self.buttons.close, 0, wx.RIGHT, 7)]) + + s.Add(button_sizer, 0, wx.EXPAND | wx.BOTTOM, 7) + + def on_check(self, e): + 'Invoked when the "Enabled" checkbox is clicked.' + + self.enabled = e.Checked() + + def on_close(self, e): + 'Invoked when the XML console frame is closing.' + + self.intercept.close() + self.Top.Destroy() + + def on_node(self, node, node_type): + ''' + Invoked on the network thread with a libxml2 node and "incoming" or + "outgoing" for node_type. + ''' + + if self.enabled: + # no free-ing of the node necessary; it still belongs to the jabber + # stream + wx.CallAfter(self.on_node_gui, serialize_xmlnode(node), node_type) + + def on_node_gui(self, node_text, node_type): + ''' + Invoked on the GUI thread with a serialized XML node and "incoming" or + "outgoing" for node_type. + ''' + console = self.console + + style = console.GetDefaultStyle() + style.SetTextColour(self.font_colors[node_type]) + + start = end = console.GetLastPosition() + + console.SetStyle(start, end, style) + + if self.scroll_lock: + vpos = console.GetScrollPos(wx.VERTICAL) + + console.AppendText(self.separator_text + node_text) + + if self.scroll_lock: + console.SetScrollPos(wx.VERTICAL, vpos, True) + +class XMLConsoleFrame(wx.Frame): + 'Frame holding the XMLConsole.' + + def __init__(self, connection): + wx.Frame.__init__(self, None, -1, 'XML Console for %s' % connection.username, name = 'XML Console') + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.panel = XMLConsole(self, connection) + self.Bind(wx.EVT_CLOSE, self.panel.on_close) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.panel, 1, wx.EXPAND | wx.ALL) + + persist_window_pos(self, nostack = True) + snap_pref(self) + +def show_xml_console(connection): + ''' + Displays an XML console for connection. + + If an XML console for the connection is already showing, brings it to the + front. + ''' + + for win in wx.GetTopLevelWindows(): + if isinstance(win, XMLConsoleFrame) and win.panel.Connection is connection: + log.info('raising existing xml console: %r', win) + win.Raise() + return win + + frame = XMLConsoleFrame(connection) + log.info('created new xml console: %r', frame) + frame.Show() + return frame + +class JabberXMLIntercept(object): + 'Hooks callbacks up to a Jabber stream for incoming and outgoing stanzas.' + + def __init__(self, jabber): + self.jabber = jabber + + self.on_incoming_node = Delegate() + self.on_outgoing_node = Delegate() + + jabber.add_observer(self.state_changed, 'state') + self.state_changed(jabber) + + def close(self): + self.jabber.remove_observer(self.state_changed, 'state') + + stream = self.jabber.stream + if stream is not None: + self.disconnect(stream) + + del self.on_incoming_node[:] + del self.on_outgoing_node[:] + + def state_changed(self, jabber, *a): + if jabber.state == jabber.Statuses.ONLINE: + stream = jabber.stream + if stream is None: + log.warning('jabber is online but has no stream') + else: + self.connect(stream) + else: + self.disconnect(stream) + + def connect(self, stream): + stream.on_incoming_node.add_unique(self.on_incoming_node) + stream.on_outgoing_node.add_unique(self.on_outgoing_node) + + def disconnect(self, stream): + stream.on_incoming_node.remove_maybe(self.on_incoming_node) + stream.on_outgoing_node.remove_maybe(self.on_outgoing_node) diff --git a/digsby/src/gui/protocols/oscargui.py b/digsby/src/gui/protocols/oscargui.py new file mode 100644 index 0000000..13929d6 --- /dev/null +++ b/digsby/src/gui/protocols/oscargui.py @@ -0,0 +1,52 @@ +from gui.toolbox import GetTextFromUser +import wx +from logging import getLogger; log = getLogger('oscargui') + +def format_screenname(oscar, set): + + oldval = str(oscar.self_buddy.nice_name) + + while True: + line1 = _('Enter a formatted screenname for {username}').format(username=oscar.username) + line2 = _('The new screenname must be the same as the old one, except for changes in capitalization and spacing.') + + val = GetTextFromUser(u'%s\n\n%s' % (line1, line2), + caption = _('Edit Formatted Screenname'), + default_value = oldval) + + if val is None: return + + elif val.lower().replace(' ', '') == oldval.lower().replace(' ', ''): + print 'setting new formatted name', val + return set(str(val)) + + +def set_account_email(oscar, set): + def show(email = None): + val = GetTextFromUser(_('Enter an email address:'), + _('Edit Account Email: {username}').format(username=oscar.self_buddy.name), + default_value = email or '') + + if val is None or not val.strip(): + return + + log.info('setting new account email %r', val) + return set(val) + + timer = wx.PyTimer(show) + + def success(email): + if timer.IsRunning(): + timer.Stop() + show(email) + else: + log.info('server response was too late: %r', email) + + def error(): + log.info('error retreiving account email for %r', oscar) + if timer.IsRunning(): + timer.Stop() + show() + + timer.StartOneShot(2000) + oscar.get_account_email(success = success, error = error) diff --git a/digsby/src/gui/prototypes/__init__.py b/digsby/src/gui/prototypes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/prototypes/fontdropdown.py b/digsby/src/gui/prototypes/fontdropdown.py new file mode 100644 index 0000000..b069b4e --- /dev/null +++ b/digsby/src/gui/prototypes/fontdropdown.py @@ -0,0 +1,281 @@ +from gui.toolbox.monitor import Monitor +import wx +from gui.skin.skinobjects import SkinColor, Margins +from gui.textutil import default_font, GetFonts, GetTextExtent +from gui.prototypes.menus.simplemenu import SimpleMenuedControl, BasicMenuData, EVT_SMI_CLICKED, EVT_SM_CLOSED +from gui.prototypes.newskinmodule import NewSkinModule + +import config + +if config.platformName == "win": + from cgui import FitFontToRect + +from gui import skin + +from util import default_timer + + + +def MakeDropIcon(color = None): + + if color == None: + color = wx.BLACK #@UndefinedVariable + fillcolor = wx.WHITE if not color.IsSameAs(wx.WHITE) else wx.BLACK #@UndefinedVariable + + ico = wx.EmptyBitmap(7, 4, -1) + + mdc = wx.MemoryDC() + mdc.SelectObject(ico) + + + mdc.Pen = wx.TRANSPARENT_PEN #@UndefinedVariable + mdc.Brush = wx.Brush(fillcolor) + mdc.DrawRectangle(0, 0, 7, 4) + + mdc.Pen = wx.Pen(color, 1, wx.SOLID) + mdc.Brush = wx.TRANSPARENT_BRUSH #@UndefinedVariable + + + s = 0 + e = 0 + for x in xrange(7): + mdc.DrawLine(x,s,x,e) + e += (1 if x<3 else -1) + + mdc.SelectObject(wx.NullBitmap) #@UndefinedVariable + + #ico.SetMaskColour(fillcolor) + + return ico + +fontnames = [] + +def FontFromFacename(facename): + return wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, facename) + +FontDropDownSkinDefaults = { + 'padding' : lambda: wx.Point(2,2), + 'margins' : lambda: Margins([0,0,0,0]), + 'framesize' : lambda: Margins([0,0,0,0]), + 'frame' : lambda: SkinColor(wx.BLACK), #@UndefinedVariable + 'backgrounds.normal': lambda: SkinColor(wx.WHITE), #@UndefinedVariable + 'backgrounds.hover' : lambda: SkinColor(wx.BLACK), #@UndefinedVariable + 'backgrounds.down' : lambda: SkinColor(wx.BLACK), #@UndefinedVariable + 'backgrounds.active': lambda: SkinColor(wx.BLACK), #@UndefinedVariable + 'fontcolors.normal' : lambda: wx.BLACK, #@UndefinedVariable + 'fontcolors.hover' : lambda: wx.WHITE, #@UndefinedVariable + 'fontcolors.down' : lambda: wx.WHITE, #@UndefinedVariable + 'fontcolors.active' : lambda: wx.WHITE, #@UndefinedVariable +# 'fontcolors.hint' : lambda: wx.Color(128,128,128), + 'font' : lambda: default_font(), + 'menuskin' : lambda: None, + 'menuicon' : lambda: skin.get('appdefaults.dropdownicon') +} + +menuobjects = {} + + +class SharedFontDropDownData(BasicMenuData): + ''' + Extension on the BasicMenuData provider to be shared among all font menus saving memory + ''' + def __init__(self): + BasicMenuData.__init__(self) + + global fontnames + global menuobjects + + + if not fontnames: + fontnames = GetFonts() + + self._items = fontnames + self._clientData = menuobjects + + self._lastItemChange = default_timer() + self._lastSelChange = default_timer() + +class FontDropDown(SharedFontDropDownData, SimpleMenuedControl, NewSkinModule): + ''' + Custom drop down selector that shows all font names in that font + ''' + #wxChoice(parent, id,pos, size, name = "choice") + def __init__(self, parent, id = -1, pos = wx.DefaultPosition, size = wx.DefaultSize, skinkey = None): + + +# s = default_timer() + #self.itemSize = 22 + self.isDown = False + self.isActive = False + + self.SetSkinKey(skinkey, FontDropDownSkinDefaults) + + + SharedFontDropDownData.__init__(self) + + SimpleMenuedControl.__init__(self, parent, id, pos, size, 10, 212, 212, skinkey = "simplemenu") + + self.SetMinSize(wx.Size(-1, 12 + 2*self.skinFDD['padding'].y)) + self.itemSize = 12 + 2*self._customMenu.skinCML['padding'].y + + self.MakeFont(0) + self.SetSelection(0) + + + Bind = self.Bind + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + Bind(wx.EVT_PAINT, self.OnPaint) + Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + Bind( EVT_SMI_CLICKED, self.OnItemClicked) + Bind( EVT_SM_CLOSED, self.OnMenuClose) + Bind(wx.EVT_MOTION, self.OnMouseMotion) + Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter) + Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) + Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseCaptureLost) + +# e = default_timer() +# print "FontDD created: ", e - s + + + def MakeFont(self, n): + + fontname = self.GetString(n) + font = FontFromFacename(fontname) #FitFontToRect(FontFromFacename(fontname), wx.RectS(wx.Size(212, self.itemSize - 2*self._customMenu.skinCML['padding'].y)), fontname, False) + self.SetClientData(n, font) + + def OnMouseCaptureLost(self, event): + pass + + def SetSelection(self, n): + + if not isinstance(self.GetClientData(n), wx.Font): + self.MakeFont(n) + + BasicMenuData.SetSelection(self, n) + + self.Refresh() + + def GetSkinProxy(self): + return self.skinFDD if hasattr(self, 'skinFDD') else None + + def DoUpdateSkin(self, skin): + self.skinFDD = skin + + def OnItemClicked(self, event): + self._customMenu.Dismiss() + + sel = event.GetInt() + self.SetSelection(sel) + cEvent = wx.CommandEvent(wx.EVT_COMMAND_CHOICE_SELECTED, self.GetId()) + cEvent.SetInt(sel) + cEvent.SetClientData(self.GetClientData(sel)) + cEvent.SetString(self.GetString(sel)) + self.AddPendingEvent(cEvent) + + def OnMenuClose(self, event): + self.isActive = False + self.Refresh() + + def OnMouseMotion(self, event): + self.Refresh() + + def CMLMeasureItem(self, n, skin): + return self.itemSize + + def CMLCalcSize(self, skin): + width = self.maxWidth + height = (self.maxHeight * self.itemSize) + skin['framesize'].x + + return wx.Size(width, height) + + def OnLeftDown(self, event): + self.OpenMenu() + + + def OpenMenu(self): + rect = self.GetScreenRect() + + self._customMenu.CalcSize() + menuSize = self._customMenu.GetMinSize() + + pos = wx.Point(rect.GetLeft(), rect.GetBottom() + 1) + + monRect = Monitor.GetFromPoint(pos, True).Geometry + if pos.y + menuSize.height > monRect.bottom: + pos = wx.Point(rect.GetLeft(), rect.GetTop() - menuSize.height - 1) + + self.PopUp(pos) + + self.isActive = True + self.Refresh() + + + def OnMouseEnter(self, event): + self.Refresh() + if not self.HasCapture(): + self.CaptureMouse() + event.Skip() + + def OnMouseLeave(self, event): + self.Refresh() + while self.HasCapture(): + self.ReleaseMouse() + event.Skip() + + def OnPaint(self, event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + + skin = self.skinFDD + icon = skin['menuicon'] + + isActive = self.isActive + hasHover = self.ScreenRect.Contains(wx.GetMousePosition()) + + skin['backgrounds.active' if isActive else 'backgrounds.hover' if hasHover else 'backgrounds.normal'].Draw(dc, rect) + + iw = icon.GetWidth() + ih = icon.GetHeight() + + ix = rect.right - skin['padding'].x - skin['margins'].right - iw + iy = rect.top + (rect.height/2 - ih/2) + + dc.DrawBitmap(icon, ix, iy, True) + text = self.GetStringSelection() + + if text is None: + return + + dc.Font = self.GetClientData(self.GetSelection()) + dc.TextForeground = skin['fontcolors.active' if isActive else 'fontcolors.hover' if hasHover else 'fontcolors.normal'] + + + + tx = skin['margins'].left + skin['padding'].x + ty = skin['margins'].top + skin['padding'].y + tw = ix - skin['padding'].x*2 + th = rect.height - (skin['margins'].y + skin['padding'].y*2) + + trect = wx.Rect(tx, ty, tw, th) + + dc.DrawTruncatedText(text, trect, alignment = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) + + + def CMLDrawItem(self, dc, rect, n, skin): + + + item = self.GetString(n) + + if not isinstance(self.GetClientData(n), wx.Font): + self.MakeFont(n) + + dc.Font = self.GetClientData(n) + dc.TextForeground = skin["fontcolors.selection"] if self.GetHover() == n else skin["fontcolors.normal"] + + drawrect = wx.Rect(rect.x + skin["padding"].x, rect.y, rect.width - skin["padding"].x*3, rect.height) + + dc.DrawTruncatedText(item, drawrect, alignment = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) +# dc.SetBrush(wx.TRANSPARENT_BRUSH) +# dc.SetPen(wx.RED_PEN) +# dc.DrawRectangleRect(drawrect) diff --git a/digsby/src/gui/prototypes/menus/__init__.py b/digsby/src/gui/prototypes/menus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/prototypes/menus/cml.py b/digsby/src/gui/prototypes/menus/cml.py new file mode 100644 index 0000000..3a270be --- /dev/null +++ b/digsby/src/gui/prototypes/menus/cml.py @@ -0,0 +1,340 @@ +''' +This is the basic shared logic for the UI of any custom skinned menus + +Note: This was written to be easily ported down to C++ code, doing so should offer speed improvements +''' + +from util import default_timer +from gui.skin.skinobjects import Margins, SkinColor +from gui.textutil import default_font +import wx + +from gui.prototypes.newskinmodule import NewSkinModule + +from gui.vlist.skinvlist import SkinVListBox + +#from traceback import print_exc +#=============================================================================== +#TODO: platform specific code, check for other platforms as well + +wxMSW = 'wxMSW' in wx.PlatformInfo + +if wxMSW: + from ctypes import windll + ReleaseCapture_win32 = windll.user32.ReleaseCapture + +def ClearMouseCapture(): + + if wxMSW: + ReleaseCapture_win32() + + +#=============================================================================== +wxEVT_ENTER_WINDOW = 10032 +wxEVT_LEAVE_WINDOW = 10033 + +MenuSkinDefaults = { + 'framesize': lambda: Margins([0,0,0,0]), + 'frame': lambda: SkinColor(wx.BLACK), #@UndefinedVariable + + 'padding': lambda: wx.Point(2,2), + 'backgrounds.menu': lambda: SkinColor(wx.WHITE), #@UndefinedVariable + 'backgrounds.item': lambda: None, + 'backgrounds.selection': lambda: SkinColor(wx.BLACK), #@UndefinedVariable + 'font': lambda: default_font(), + 'fontcolors.normal': lambda: wx.BLACK, #@UndefinedVariable + 'fontcolors.selection': lambda: wx.WHITE, #@UndefinedVariable + 'separatorimage': lambda: None +} + + + +class CustomMenuFrame(wx.PopupTransientWindow, NewSkinModule): + def __init__(self, parent): + wx.PopupTransientWindow.__init__(self, parent) + self.SetPosition((-50000,-50000)) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.content = None + + def SetContent(self, content): + self.content = content + + def OnPaint(self, event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectPS(wx.Point(0,0), self.GetSize()) + + if self.content: + skin = self.content.skinCML + skin["frame"].Draw(dc, rect) + else: + dc.SetBrush(wx.BLACK_BRUSH) #@UndefinedVariable + dc.SetPen(wx.TRANSPARENT_PEN) #@UndefinedVariable + dc.DrawRectangleRect(rect) + + +def CreateCML(parent, customcalls, data, id = -1, style = 0, skinkey = None): + frame = CustomMenuFrame(parent) + cmlb = CustomMenuListBox(frame, customcalls, data, id, style, skinkey) + frame.SetContent(cmlb) + return cmlb + +class CustomMenuListBox(SkinVListBox, NewSkinModule): + ''' + This is the base UI for any new menu, expects + + @param frame The frame that will b the menus parent + @param customcalls An object implementing CustomMenuInterface + @param data An object expected to follow the same interface as wxControlWithItems + ''' + + def __init__(self, frame, customcalls, data, id = -1, style = 0, skinkey = None): + + SkinVListBox.__init__(self, frame, id, style = style) + + self._frame = frame + self._customcalls = customcalls + self._data = data + + self.SetSkinKey(skinkey, MenuSkinDefaults) + + Bind = self.Bind + Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseCaptureLost) + Bind(wx.EVT_MOUSE_EVENTS, self.OnAllMouseEvents) + Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) + Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + + self.mouseEventHandlers = {wx.wxEVT_MOTION : self._MouseMotion, + wxEVT_ENTER_WINDOW : self._MouseEnter, + wxEVT_LEAVE_WINDOW : self._MouseLeave, + wx.wxEVT_LEFT_DOWN : self._LeftDown, + wx.wxEVT_LEFT_UP : self._LeftUp, + wx.wxEVT_RIGHT_DOWN: self._RightDown, + wx.wxEVT_RIGHT_UP : self._RightUp} + + self._lastCalced = 0 + + def GetSkinProxy(self): + return self.skinCML if hasattr(self, 'skinCML') else None + + def DoUpdateSkin(self, skin): + + self.skinCML = skin + + frame = self._frame + + framesizer = frame.GetSizer() + + if framesizer and not wx.IsDestroyed(framesizer): + frame.Sizer.Clear() + + frame.SetSizer(skin["framesize"].Sizer(self)) + + self._lastSkinChange = default_timer() + + def Display(self, pos): + + self.CalcSize() + + self._frame.SetPosition(pos) + self._customcalls.CMLDisplay(pos, self.GetSize()) + + ClearMouseCapture() + + if not self.HasCapture(): + self.CaptureMouse() + + wx.CallAfter(self._frame.Show) + + def Dismiss(self): + + while self.HasCapture(): + self.ReleaseMouse() + + self._customcalls.CMLDismiss() + + def CalcSize(self): + + if self._lastCalced > max(self._data._lastItemChange, self._lastSkinChange): + return + + self._lastCalced = default_timer() + + self.SetItemCount(self._data.GetCount()) + + size = self._customcalls.CMLCalcSize(self.skinCML) + +# if size.height == -1: +# size.height = 0 +# for n in xrange(self._data.GetCount()): +# size.height += self.OnMeasureItem(n) +# +# if size.width == -1: +# size.width = self.CalcMenuWidth(self.skinCML) + + size.width -= (self.skinCML["framesize"].left + self.skinCML["framesize"].right) + size.height -= (self.skinCML["framesize"].top + self.skinCML["framesize"].bottom) + + self.SetMinSize(size) + + self._frame.Fit() + self._frame.Sizer.Layout() + + def OnMeasureItem(self, n): + return self._customcalls.CMLMeasureItem(n, self.skinCML) + + def CalcMenuWidth(self): + return self._customcalls.CMLCalcMenuWidth(self.skinCML) + + def PaintMoreBackground(self, dc, rect): + self._customcalls.CMLDrawBackground(dc, rect, self.skinCML) + + def OnDrawBackground(self, dc, rect, n): + self._customcalls.CMLDrawItemBG(dc, rect, n, self.skinCML) + + def OnDrawItem(self, dc, rect, n): + self._customcalls.CMLDrawItem(dc, rect, n, self.skinCML) + + def OnDrawSeparator(self, dc, rect, n): + self._customcalls.CMLDrawSeparator(dc, rect, n, self.skinCML) + + def OnMouseCaptureLost(self, event): + self._customcalls.CMLMouseCaptureLost(event) + + def OnAllMouseEvents(self, event): + + self._customcalls.CMLAllMouseEvents(event) + + try: eventHandler = self.mouseEventHandlers[event.EventType] + except KeyError: + print "Unhandled event type: ", event.EventType + + event.Skip() + + else: eventHandler(event) + + + def _MouseMotion(self, event): + mp = event.GetPosition() + i = self.HitTest(mp) if self.ClientRect.Contains(mp) else -1 + s = self.GetSelection() + + if i != s: + self.SetSelection(i) + + self._customcalls.CMLMouseMotion(i) + + def _MouseEnter(self, event): + + self._MouseMotion(event) + + self._customcalls.CMLMouseEnter(event) + + def _MouseLeave(self, event): + + self._MouseMotion(event) + self._customcalls.CMLMouseLeave(event) + + def _LeftDown(self, event): + + if not self.Rect.Contains(event.Position): + self.Dismiss() + + self._customcalls.CMLLeftDown(event) + + def _LeftUp(self, event): + self._customcalls.CMLLeftUp(event) + + def _RightDown(self, event): + + if not self.Rect.Contains(event.Position): + self.Dismiss() + + self._customcalls.CMLRightDown(event) + + def _RightUp(self, event): + self._customcalls.CMLRightUp(event) + + def OnMouseWheel(self, event): + self._customcalls.CMLMouseWheel(event) + + def OnKeyDown(self, e): + keycode = e.KeyCode + i = self.GetSelection() + + if keycode == wx.WXK_DOWN: + self.SetSelection((i + 1) % self._data.GetCount()) + elif keycode == wx.WXK_UP: + self.SetSelection((i - 1) % self._data.GetCount()) + elif keycode in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): + from gui.prototypes.menus.simplemenu import SimpleMenuItemClickedEvent + se = SimpleMenuItemClickedEvent(0) + se.SetInt(self.Selection) + self._frame.AddPendingEvent(se) + self.Dismiss() + else: + e.Skip() + +class CustomMenuInterface(object): + ''' + Interface expected to be implemented by custom menus + ''' + + def GetHover(self): + return -1 + + def CMLDisplay(self, pos, size): + pass + + def CMLDismiss(self): + pass + + def CMLCalcSize(self, skin): + return wx.Size(-1, -1) + + def CMLCalcMenuWidth(self, skin): + return -1 + + def CMLMeasureItem(self, n, skin): + return -1 + + def CMLDrawBackground(self, dc, rect, skin): + pass + + def CMLDrawItemBG(self, dc, rect, n, skin): + pass + + def CMLDrawItem(self, dc, rect, n, skin): + pass + + def CMLDrawSeparator(self, dc, rect, n, skin): + pass + + def CMLMouseCaptureLost(self, event): + self.Dismiss() + + def CMLAllMouseEvents(self, event): + pass + + def CMLMouseOverItem(self, event): + pass + + def CMLMouseEnter(self, event): + pass + + def CMLMouseLeave(self, event): + pass + + def CMLLeftDown(self, event): + pass + + def CMLLeftUp(self, event): + pass + + def CMLRightDown(self, event): + pass + + def CMLRightUp(self, event): + pass + + def CMLMouseWheel(self, event): + pass diff --git a/digsby/src/gui/prototypes/menus/gme.py b/digsby/src/gui/prototypes/menus/gme.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/prototypes/menus/simplemenu.py b/digsby/src/gui/prototypes/menus/simplemenu.py new file mode 100644 index 0000000..63fc0bc --- /dev/null +++ b/digsby/src/gui/prototypes/menus/simplemenu.py @@ -0,0 +1,367 @@ +from gui.textutil import GetTextWidth +from gui.prototypes.menus.cml import CustomMenuInterface, CreateCML +from util import default_timer + +import wx.lib.newevent +NewEvent = wx.lib.newevent.NewEvent +NewCommandEvent = wx.lib.newevent.NewCommandEvent +SimpleMenuItemClickedEvent, EVT_SMI_CLICKED = NewCommandEvent() +SimpleMenuOpenEvent, EVT_SM_OPENED = NewEvent() +SimpleMenuCloseEvent, EVT_SM_CLOSED = NewEvent() + +class RecapTimer(wx.Timer): + "This timer tells the curent lowest level menu to recapture the mouse, along with it's parent tree " + def __init__(self): + wx.Timer.__init__(self) + + def Start(self, target): + wx.Timer.Start(self, 10) + self.target=target + + def Notify(self): + target = self.target + mp = target.Parent.ScreenToClient(wx.GetMousePosition()) + + if (not target.Rect.Contains(mp) or target.ClientRect.Contains(mp)) and not wx.GetMouseState().LeftDown(): + self.Stop(target) + + def Stop(self,target): + wx.Timer.Stop(self) + target.CaptureMouse() + +recaptimer = RecapTimer() + + +class SimpleMenuBase(CustomMenuInterface): + ''' + A simple implementation of the CustomMenuInterface + Expects it's subclass to also inherit from a data provider modeled after wxControlWithItems + ''' + + def __init__(self, parent, maxHeight = -1, minWidth = -1, maxWidth=-1, skinkey = 'simplemenu'): + + self._customMenu = CreateCML(parent, self, self, skinkey=skinkey) + + self.minWidth = minWidth + self.maxWidth = maxWidth + self.maxHeight = maxHeight + + def PopUp(self, pos): + self._customMenu.Display(pos) + + def Dismiss(self): + self._customMenu.Dismiss() + + def CMLHover(self): + return self._customMenu.GetSelection() + + + def CMLCalcSize(self, skin): + height = 0 + + for n in xrange(self.GetCount()): + height += self.CMLMeasureItem(n, skin) + + height += skin["framesize"].top + skin["framesize"].bottom + + if self.maxHeight != -1: + height = min(height, self.maxHeight) + + width = self.CMLCalcMenuWidth(skin) + + return wx.Size(width, height) + + def CMLCalcMenuWidth(self, skin): + + maxWidth = self.maxWidth + minWidth = self.minWidth + + if maxWidth == minWidth and maxWidth != -1: + return maxWidth + + width = max(GetTextWidth(item, skin['font']) for item in self.GetStrings()) + + width += skin['padding'].x*2 + skin["framesize"].left + skin["framesize"].right + + if maxWidth != -1: + width = min(width, maxWidth) + + if minWidth != -1: + width = max(width, minWidth) + + return width + + + def CMLMeasureItem(self, n, skin): + return skin['font'].GetHeight() + (2 * skin["padding"].y) + + + def CMLDrawBackground(self, dc, rect, skin): + skin['backgrounds.menu'].Draw(dc, rect) + + def GetHover(self): + return self._customMenu.GetSelection() + + def CMLDrawItemBG(self, dc, rect, n, skin): + selbg = skin['backgrounds.selection'] + itembg = skin['backgrounds.item'] + + if self.GetHover() == n and selbg: + selbg.Draw(dc, rect) + elif itembg: + itembg.Draw(dc, rect) + + def CMLDrawItem(self, dc, rect, n, skin): + item = self.GetString(n) + + dc.Font = skin["font"] + dc.TextForeground = skin["fontcolors.selection"] if self.GetHover() == n else skin["fontcolors.normal"] + + drawrect = wx.Rect(rect.x + skin["padding"].x, rect.y, rect.width - skin["padding"].x*2, rect.height) + + dc.DrawTruncatedText(item, drawrect, alignment = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) + + def CMLMouseMotion(self, event): + cM = self._customMenu + mp = cM.Parent.ScreenToClient(wx.GetMousePosition()) + + while cM.HasCapture() and cM.Rect.Contains(mp) and not cM.ClientRect.Contains(mp): + cM.ReleaseMouse() + global recapTimer + recaptimer.Start(cM) + + def CMLDisplay(self, pos, size): + #TODO: Open event? + self._customMenu._frame.Show(True) + + def CMLDismiss(self): + #TODO: Close Event? + self._customMenu._frame.Show(False) + + def CMLLeftUp(self, event): + cM = self._customMenu + mp = cM.ScreenToClient(wx.GetMousePosition()) + i = cM.HitTest(mp) + + if i != -1 and cM.ScreenRect.Contains(wx.GetMousePosition()): + pass + #TODO: Event + +class SimpleMenuedControl(wx.PyControl, SimpleMenuBase): + ''' + Basic implementation of a control with a custom menu attached to it + Can be used for controls such as comboboxs + ''' + + def __init__(self, parent, id = -1, pos = wx.DefaultPosition, size = wx.DefaultSize, maxMenuHeight = -1, minMenuWidth = -1, maxMenuWidth=-1, skinkey = 'simplemenu'): + wx.PyControl.__init__(self, parent, id, pos, size, style = wx.BORDER_NONE) + SimpleMenuBase.__init__(self, parent, maxMenuHeight, minMenuWidth, maxMenuWidth, skinkey) + + def SetMenuSkinKey(self, skinkey): + self._customMenu.SetSkinKey(skinkey) + + def CMLDisplay(self, pos, size): + se = SimpleMenuOpenEvent() + self.AddPendingEvent(se) + self._customMenu._frame.Show(True) + + def CMLDismiss(self): + se = SimpleMenuCloseEvent() + self.AddPendingEvent(se) + self._customMenu._frame.Show(False) + + def CMLMouseCaptureLost(self, event): + se = SimpleMenuCloseEvent() + self.AddPendingEvent(se) + + + def CMLLeftUp(self, event): + cM = self._customMenu + mp = cM.ScreenToClient(wx.GetMousePosition()) + i = cM.HitTest(mp) + + if i != -1 and cM.ScreenRect.Contains(wx.GetMousePosition()): + se = SimpleMenuItemClickedEvent(0) + se.SetInt(i) + self.AddPendingEvent(se) + + +''' +A basic implementation of a data provider +''' +class BasicMenuData(object): + def __init__(self): + self._items = [] + self._clientData = {} + self._lastItemChange = default_timer() + self._lastSelChange = default_timer() + self._selection = -1 + + def Append(self, string, clientData = None): + self._items.append(string) + self._clientData[len(self._items) -1] = clientData + self._changed(True, False) + return self._items.index(string) + + def SetItems(self, strings): + self._items = list(strings) + self._clientData[len(self._items) - 1] = None + self._changed(True, True) + + def Clear(self): + self._items = [] + self._clientData = {} + self._selection = -1 + self._changed(True, True) + + def _changed(self, item, selection): + if item: + self._lastItemChange = default_timer() + + if selection: + self._lastSelChange = default_timer() + + if self._customMenu.IsShown(): + self._customMenu.CalcSize() + + def Delete(self, n): + self._items.remove(self._items[n]) + if n in self._clientData: + self._clientData.pop(n) + self._changed(True, False) + + def FindString(self, string, caseSensitive = False): + + if caseSensitive: + for i in xrange(len(self._items)): + if self._items[i] == string: + return i + + else: + for i in xrange(len(self._items)): + if self._items[i].lower() == string.lower(): + return i + return -1 + + def GetClientData(self, n): + return self._clientData.get(n, None) + + def GetClientObject(self, n): + return self._clientData.get(n, None) + + def GetCount(self): + return len(self._items) + + def GetSelection(self): + return self._selection + + def GetString(self, n): + return self._items[n] + + def GetStrings(self): + return self._items + + def GetStringSelection(self): + if self._selection == -1: + return None + + return self._items[self._selection] + + def Insert(self, string, n, clientData = None): + self._items.insert(n, string) + self._clientData[n] = clientData + + def IsEmpty(self): + return len(self._items) == 0 + + def Number(self): + return len(self._items) + + def Select(self, n): + self._selection = n + self._changed(False, True) + + SetSelection = Select + + def SetClientData(self, n, data): + self._clientData[n] = data + + def SetClientObject(self, n, data): + self._clientData[n] = data + + def SetString(self, n, string): + self._items[n] = string + + def SetStringSelection(self, string): + try: + self._selection = self._items.index(string) + except ValueError: + self._selection = -1 + + self._changed(False, True) + +class BasicMenu(SimpleMenuBase, BasicMenuData): + ''' + A basic popup menu example + ''' + + def __init__(self, parent, maxMenuHeight = -1, minMenuWidth = -1, maxMenuWidth=-1, skinkey = 'menu'): + BasicMenuData.__init__(self) + SimpleMenuBase.__init__(self, parent, maxMenuHeight, minMenuWidth, maxMenuWidth, skinkey) + + def Reposition(self, rect): + self._customMenu.CalcSize() + menuSize = self._customMenu.GetMinSize() + pos = wx.Point(rect.GetLeft(), rect.GetBottom() + 1) + + from gui.toolbox import Monitor + monRect = Monitor.GetFromPoint(pos, True).Geometry + if pos.y + menuSize.height > monRect.bottom: + pos = wx.Point(rect.GetLeft(), rect.GetTop() - menuSize.height - 1) + + return pos + + def PopUp(self, rect): + pos = self.Reposition(rect) + return SimpleMenuBase.PopUp(self, pos) + + def CMLDisplay(self, pos, size): + #TODO: Open event? + self._customMenu._frame.Show(True) + + def CMLDismiss(self): + #TODO: Close Event? + self._customMenu._frame.Show(False) + + def CMLLeftUp(self, event): + cM = self._customMenu + mp = cM.ScreenToClient(wx.GetMousePosition()) + i = cM.HitTest(mp) + + if i != -1 and cM.ScreenRect.Contains(wx.GetMousePosition()): + pass + + #TODO: Event + se = SimpleMenuItemClickedEvent(0) + se.SetInt(i) + cM._frame.AddPendingEvent(se) + + cM.Dismiss() + + def IsShown(self): + return self._customMenu._frame.IsShown() + + def SetItems(self, items): + # Maintain string selection. + selected_string = None + i = self._customMenu.Selection + if i != -1 and i < len(self._items): + selected_string = self._items[i] + + super(BasicMenu, self).SetItems(items) + + if selected_string is not None: + i = self.FindString(selected_string, True) + if i != -1: + self._customMenu.SetSelection(i) + diff --git a/digsby/src/gui/prototypes/newskinmodule.py b/digsby/src/gui/prototypes/newskinmodule.py new file mode 100644 index 0000000..d9d4a1c --- /dev/null +++ b/digsby/src/gui/prototypes/newskinmodule.py @@ -0,0 +1,87 @@ +__metaclass__ = type +from gui import skin as skintree + +class SkinProxy(object): + """ + Simplifies skin lookup by providing a common interface and a easier, common + fallback system + """ + + #local copy of reference to skin elements, saves lookup time and prevents regeneration of fallbacks + + + def __init__(self, skinkey, skindefaults): + """ + skinkey - were to look for the requested keys in the skintree + skindefaults - dictionary of defaults if key is not found in the skintree + """ + + self.skinkey = skinkey + self.skindefaults = skindefaults if skindefaults is not None else {} + self.localitems = {} + + def __getitem__(self, k): + + #first check the local cache + if k in self.localitems: + return self.localitems[k] + + #if not in the local cache look it up in the skintree + item = skintree.get(".".join([self.skinkey, k]), None) if self.skinkey is not None else None + + #if there is still nothing found, try generating item from the default dict or return None + if item is None and k in self.skindefaults: + item = self.skindefaults[k]() + + #store the reference to the item in localitems for faster lookup next time + self.localitems[k] = item + + return item + + def __setitem__(self, k, i): + """ + Set a item in the skin locally. + """ + self.localitems[k] = i + + +class NewSkinModule(object): + """ + All new GUI that accesses the skin should inherit from this + """ + + def SetSkinKey(self, skinkey, skindefaults = None): + """ + Defines where in the skintree to look for skin items + """ + + self.UpdateSkin(skinkey, skindefaults) + + def SetSkinDefaults(self, skindefaults): + """ + Set the defaults dict for when keys are not defined in the skin + """ + + self.UpdateSkin(defaults = skindefaults) + + def UpdateSkin(self, skinkey=None, skindefaults = None): + """ + Called by the skin system whenever the skin is changed + """ + + skinproxy = self.GetSkinProxy() + + skin = SkinProxy(skinkey if skinkey is not None else skinproxy.skinkey if skinproxy is not None else None, + skindefaults if skindefaults is not None else skinproxy.skindefaults if skinproxy is not None else None) + self.DoUpdateSkin(skin) + + def DoUpdateSkin(self, skin): + """ + Called whenever skin is changed, should be overridden by the subclass to + handle class specific calls that need to happen on skin change + """ + NotImplementedError('DoUpdateSkin() is not implemented in %s' % self.__class__.__name__) + + def GetSkinProxy(self): + NotImplementedError('GetSkinProxy() is not implemented in %s' % self.__class__.__name__) + diff --git a/digsby/src/gui/proxydialog.py b/digsby/src/gui/proxydialog.py new file mode 100644 index 0000000..8816e28 --- /dev/null +++ b/digsby/src/gui/proxydialog.py @@ -0,0 +1,266 @@ +import wx +from wx import WHITE, HORIZONTAL, VERTICAL, ALIGN_RIGHT, ALIGN_CENTER_VERTICAL, EXPAND, ALIGN_LEFT, ALL +from cgui import SimplePanel +from gui.uberwidgets.PrefPanel import PrefPanel + +from gui.validators import NumericLimit + +import util.proxy_settings + +ID_NONPROX = wx.NewId() +ID_SYSPROX = wx.NewId() +ID_SETPROX = wx.NewId() + +ID_HTTP = wx.NewId() +ID_HTTPS = wx.NewId() +ID_SOCKS4 = wx.NewId() +ID_SOCKS5 = wx.NewId() + +from config import platformName + +class ProxyPanel(SimplePanel): + + def __init__(self, parent): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + + if platformName != 'mac': + self.BackgroundColour = WHITE + + sz = self.Sizer = wx.BoxSizer(VERTICAL) + + top = wx.BoxSizer(HORIZONTAL) + + + radpanel = wx.Panel(self) + rs = radpanel.Sizer = wx.BoxSizer(VERTICAL) + + + RADIO = wx.RadioButton + + overrads = self.overrads = dict(NONPROX = RADIO(radpanel, ID_NONPROX, _("&No proxy"), style = wx.RB_GROUP, name = 'override'), + SYSPROX = RADIO(radpanel, ID_SYSPROX, _("Use &default system settings"), name = 'override'), + SETPROX = RADIO(radpanel, ID_SETPROX, _("&Specify proxy settings"), name = 'override')) + + + rs.Add(overrads["NONPROX"], 0, ALL, 2) + rs.Add(overrads["SYSPROX"], 0, ALL, 2) + rs.Add(overrads["SETPROX"], 0, ALL, 2) + +#------------------------------------------------------------------------------- + proxyp = wx.Panel(self) + ps = proxyp.Sizer = wx.FlexGridSizer(2, 2) + + TEXT = lambda s: wx.StaticText(proxyp, -1, s) + INPUT = lambda d, v = wx.DefaultValidator: wx.TextCtrl(proxyp, -1, d, validator = v) + + hosti = self.hosti = INPUT('') + porti = self.porti = INPUT('', NumericLimit(65535)) + + ps.Add(TEXT(_("&Host:")), + 0, + ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + ps.Add(hosti, + 0, + ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + + ps.Add(TEXT(_("P&ort:")), + 0, + ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + ps.Add(porti, + 0, + ALIGN_LEFT | ALIGN_CENTER_VERTICAL | ALL, 2) + ps.AddGrowableCol(1, 1) +#------------------------------------------------------------------------------- + protop = wx.Panel(self) + prs = protop.Sizer = wx.BoxSizer(VERTICAL) + + protorads = self.protorads = dict(HTTP = RADIO(protop, ID_HTTP, "&HTTP", style = wx.RB_GROUP, name = 'proxytype'), + #HTTPS = RADIO(protop, ID_HTTPS, "HTTPS", name = 'proxytype'), + SOCKS4 = RADIO(protop, ID_SOCKS4, "SOCKS &4", name = 'proxytype'), + SOCKS5 = RADIO(protop, ID_SOCKS5, "SOCKS &5", name = 'proxytype') + ) + + prs.Add(protorads["HTTP"], 0, ALL, 2) + #prs.Add(protorads["HTTPS"], 0, ALL, 2) + prs.Add(protorads["SOCKS4"], 0, ALL, 2) + prs.Add(protorads["SOCKS5"], 0, ALL, 2) + + +#------------------------------------------------------------------------------- + authp = wx.Panel(self) + aus = authp.Sizer = wx.FlexGridSizer(2, 2) + + TEXT = lambda s: wx.StaticText(authp, -1, s) + INPUT = lambda d, style = 0: wx.TextCtrl(authp, -1, d, style = style) + + aus.Add(TEXT(_("&Username:")), + 0, + ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + + useri = self.usernamei = INPUT('') + aus.Add(useri, + 0, + ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + + aus.Add(TEXT(_("&Password:")), + 0, + ALIGN_RIGHT | ALIGN_CENTER_VERTICAL | ALL, 2) + + passi = self.passwordi = INPUT('', wx.TE_PASSWORD) + aus.Add(passi, + 0, + ALIGN_LEFT | ALIGN_CENTER_VERTICAL | EXPAND | ALL, 2) + aus.AddGrowableCol(1, 1) +#------------------------------------------------------------------------------- + + + top.Add(PrefPanel(self, proxyp, _("Proxy Server")), 1, EXPAND | ALL, 2) + top.Add(PrefPanel(self, protop, _("Protocol")), 0, EXPAND | ALL, 2) + + + sz.Add(PrefPanel(self, radpanel, _("How to Connect")), 0, EXPAND | ALL, 2) + sz.Add(top, 1, EXPAND) + sz.Add(PrefPanel(self, authp, _("Authentication")), 1, EXPAND | ALL, 2) + + pd = self.proxy_dict + + override = pd.get('override', "SYSPROX") + + try: + override = int(override) + except: + pass + else: + override = ['SYSPROX', 'SETPROX'][override] + + self.override = override + self.overrads[self.override].Value = True + + self.addr = pd.get('addr', '') + self.port = pd.get('port', '') + + self.proxytype = pd.get('proxytype', 'HTTP') + self.protorads[self.proxytype].Value = True + + self.username = pd.get('username', '') + self.password = pd.get('password', '') + + self.Enablement() + + Bind = self.Bind + Bind(wx.EVT_RADIOBUTTON, self.OnRadio) + + if platformName != 'mac': + Bind(wx.EVT_PAINT, self.OnPaint) + + @property + def proxy_dict(self): + return util.proxy_settings.get_proxy_dict() + + def OnOK(self, event = None): + pd = self.proxy_dict + + + keys = ['override', + 'addr', + 'port', + 'proxytype', + 'username', + 'password'] + + for key in keys: + pd[key] = str(getattr(self, key)) + + #print pd + pd.save() + + addr = property(lambda self: self.hosti.Value, lambda self, address: self.hosti.SetValue(address)) + port = property(lambda self: self.porti.Value, lambda self, address: self.porti.SetValue(address)) + + username = property(lambda self: self.usernamei.Value, lambda self, address: self.usernamei.SetValue(address)) + password = property(lambda self: self.passwordi.Value, lambda self, address: self.passwordi.SetValue(address)) + + def OnRadio(self, event): + rad = event.GetEventObject() + + if rad.GetName() == "proxytype": + setattr(self, "proxytype", rad.GetLabelText().replace(' ' , '')) + elif rad.GetName() == 'override': + for key in self.overrads: + if self.overrads[key].Value: + setattr(self, "override", key) + + self.Enablement() + + def Enablement(self): + + switch = self.overrads["SETPROX"].Value + + self.hosti.Enable(switch) + self.porti.Enable(switch) + + for rad in self.protorads: + self.protorads[rad].Enable(switch) + + hasproxy = not self.overrads["NONPROX"].Value + self.usernamei.Enable(hasproxy) + self.passwordi.Enable(hasproxy) + + def OnPaint(self, event): + dc = wx.PaintDC(self) + + rect = wx.RectS(self.ClientSize) + + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + + dc.DrawRectangleRect(rect) + + +class ProxyDialog(wx.Dialog): + def __init__(self, parent = None): + wx.Dialog.__init__(self, parent, title = _("Connection Settings")) + + if not platformName == 'mac': + self.SetBackgroundColour(wx.WHITE) + + self.Sizer = wx.BoxSizer(VERTICAL) + self.pp = ProxyPanel(self) + self.Sizer.Add(self.pp, 1, EXPAND | ALL, 5) + + bsz = wx.BoxSizer(wx.HORIZONTAL) + okb = wx.Button(self, wx.ID_OK, _("&OK")) + okb.SetDefault() + canb = wx.Button(self, wx.ID_CANCEL, _("&Cancel")) + bsz.Add(okb, 0, ALL, 4) + bsz.Add(canb, 0, ALL, 4) + + self.Sizer.Add(bsz, 0, ALIGN_RIGHT) + + okb.Bind(wx.EVT_BUTTON, self.OnOK) + canb.Bind(wx.EVT_BUTTON, lambda e: self.Close()) + + self.Fit() + self.Size = wx.Size(400, self.Size.height) + + self.Layout() + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnClose(self, event): + self.Show(False) + self.pp = None + + def OnOK(self, event): + res = self.pp.OnOK(event) + self.SetReturnCode(wx.ID_OK) + self.Close() + return res + +if __name__ == "__main__": + from tests.testapp import testapp + app = testapp() + + f = ProxyDialog() + f.Show(True) + + app.MainLoop() diff --git a/digsby/src/gui/searchgui.py b/digsby/src/gui/searchgui.py new file mode 100644 index 0000000..4f9bc75 --- /dev/null +++ b/digsby/src/gui/searchgui.py @@ -0,0 +1,163 @@ +''' +search gui +''' + +import wx + +import common.search as search +from common import setpref, profile, action + +from gui.visuallisteditor import VisualListEditor, VisualListEditorList + +def set_searches(engine_list, checked): + engine_dicts = [e.dict(enabled=checked[n]) + for n, e in enumerate(engine_list)] + setpref('search.external', engine_dicts) + +class SearchEditor(VisualListEditorList): + 'small drag and drop editor for enabling/rearranging searches' + + text_alignment = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL + + def get_icon(self, searchname): + from gui import skin + return skin.get('appdefaults.search.icons.' + searchname, None) + + def OnDrawItem(self, dc, rect, n): + VisualListEditorList.OnDrawItem(self, dc, rect, n) + icon = self.get_icon(self.GetItem(n).name) + if icon is None: + return + + # Draw search favicons on the right side of the list + dc.DrawBitmap(icon, rect.Right - icon.Width - 32, rect.VCenter(icon), True) + +class SearchEditDialog(VisualListEditor): + text_alignment = wx.ALIGN_LEFT + + def __init__(self, parent): + VisualListEditor.__init__(self, parent, + list2sort = search.searches, + prettynames = lambda search: search.gui_name, + listcallback = set_searches, + title = _('Arrange Searches'), + listclass = SearchEditor, + ischecked = lambda search: search.enabled) + +def edit(parent = None): + search.link_prefs(profile.prefs) + SearchEditDialog(parent).Show() + +# +# buddylist classes +# +from common.actions import ActionMeta + +class SearchEntryBase(object): + __metaclass__ = ActionMeta + +class SearchEntry(SearchEntryBase): + 'Corresponds to one web search entry on the buddylist.' + + def __init__(self, searchengine, searchstring): + self.searchengine = searchengine + self.searchstring = searchstring + + # this attribute is to make TreeList happy + self.name = '%s: %s' % (searchengine.name, searchstring) + + # eq and hash do not include searchstring in their calculation, so that + # selecting Google, for example, and then changing the query string, will + # result in Google still being selected. + + def __eq__(self, other): + return self is other or (self.searchengine == other.searchengine) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(''.join((self.__class__.__name__, self.searchengine.__repr__()))) + + def activate(self): + self.searchengine.search(self.searchstring) + + @property + def menu_search_string(self): + 'used in the context menu for the "search" item' + + return _(u'Search {search_engine_name:s} for "{search_string:s}"').format(search_engine_name=self.searchengine.gui_name, + search_string=self.searchstring) + + def delete(self): + from common.search import searches + s = searches[:] + + for e in s: + if self.searchengine == e: + e.enabled = False + + setpref('search.external', [e.dict() for e in s]) + +class SearchOptionsEntry(SearchEntryBase): + '''options... link at the end of searches''' + + inherited_actions = [SearchEntry] + + def __init__(self): + # for treelist + self.name = '__searchoptionsentry__' + + def __eq__(self, other): + return self is other or isinstance(other, self.__class__) + + def __hash__(self): + return hash(self.name) + + def activate(self): + edit() + + @property + def menu_search_string(self): + return _('Options...') + + @action(lambda self: False) + def delete(self): + pass + +class SearchWebGroup(list): + 'the special "Search Web" group on the buddylist' + + __metaclass__ = ActionMeta + searchweb_name = _('Web Search') + + def edit(self): + edit(wx.FindWindowByName('Buddy List')) + + def __init__(self): + self.name = self.searchweb_name + + def __hash__(self): + return hash(self.name) + + @property + def display_string(self): + return self.searchweb_name + +def add_search_entries(view, search): + ''' + adds search items to a buddylist tree + ''' + + from common.search import enabled_searches + + search_group = SearchWebGroup() + + # add an entry for each enabled search + search_group.extend((SearchEntry(s, search) for s in enabled_searches())) + + # add an Options... link at the end. + search_group.append(SearchOptionsEntry()) + + view.append(search_group) + diff --git a/digsby/src/gui/shell.py b/digsby/src/gui/shell.py new file mode 100644 index 0000000..c8b0a72 --- /dev/null +++ b/digsby/src/gui/shell.py @@ -0,0 +1,117 @@ +from __future__ import with_statement +import os +import sys +import wx +import wx.py as py + +from util import traceguard +from gui.toolbox import loadWindowPos, saveWindowPos +import gui.native.helpers + +__all__ = ['PyCrustFrame'] + + +if hasattr(sys, 'gettotalrefcount'): + def get_total_refcount(): + return '-- totalrefs: %d' % sys.gettotalrefcount() +else: + def get_total_refcount(): + return '' + +class PyCrustFrame(wx.Frame): + def __init__(self, parent = None, id_ = -1, title = 'Digsby Shell', standalone = False, + name = 'Digsby Shell'): + wx.Frame.__init__(self, parent, id_) + + self.SetTitle(self.ShellTitle) + + self.Bind(wx.EVT_SHOW, self.on_show) + self.title_timer = wx.PyTimer(lambda: self.SetTitle(self.ShellTitle)) + self.title_timer_interval = 500 + + try: + import digsbyprofile + prefs = digsbyprofile.profile.prefs + except ImportError: + prefs = {} + except AttributeError: + prefs = {} + + py.editwindow.FACES['mono'] = prefs.get('debug.shell.font', 'Courier New') + + if prefs.get('debug.shell.minimal', True): + crust = shell = py.shell.Shell(self) + else: + crust = py.crust.Crust(self, intro="Digsby Shell") + shell = crust.shell + + wx.GetApp().shell_locals = shell.interp.locals + + import common.commandline + shell.interp.locals.update(common.commandline.__dict__) + + self.input = shell + + # Load history from file + if 'debug.shell.history.size' in prefs: + maxsize = prefs['debug.shell.history.size'] + h = prefs['debug.shell.history.lines'] + h = h[:maxsize] + prefs['debug.shell.history.lines'] = shell.history = h + self.crust = crust + self.Bind(wx.EVT_CLOSE, self.on_close) + loadWindowPos(self) + + self.standalone = standalone + + with traceguard: + from gui import skin + self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon').Inverted) + + def FocusInput(self): + self.input.SetFocus() + + def on_close(self, e = None): + if self.IsShown(): + saveWindowPos(self) + self.Show(False) + + if self.standalone: + sys.exit() + + def on_show(self, shown): + shown.Skip() + + if shown: self.title_timer.Start(self.title_timer_interval, False) + else: self.title_timer.Stop() + + if 'wxMSW' in wx.PlatformInfo: + @property + def ShellTitle(self): + m = memory_info() + ramusage = '%s' % nicebytecount(m.WorkingSetSize) + if m.PrivateUsage: + ramusage += '/%s' % nicebytecount(m.PrivateUsage) + + return ('Digsby Shell -- %s -- (gdi: %s, user: %s) -- pid: %s %s' % + (ramusage, + count_gdi_objects(), + count_user_objects(), + os.getpid(), + get_total_refcount())) + else: + ShellTitle = 'Digsby Shell' + + def toggle_shown(self): + if self.Visible: + self.on_close() + else: + self.Show() + if self.IsIconized(): + self.Iconize(False) + self.Raise() + +if 'wxMSW' in wx.PlatformInfo: + from gui.native.win.process import memory_info, count_gdi_objects, count_user_objects + from util import nicebytecount + import gc diff --git a/digsby/src/gui/si4demo.py b/digsby/src/gui/si4demo.py new file mode 100644 index 0000000..2577ce6 --- /dev/null +++ b/digsby/src/gui/si4demo.py @@ -0,0 +1,117 @@ +import wx +from splitimage4 import SplitImage4, ImageData, Extend +from pprint import pformat +from gui import skin as skincore + +def make_extends(s): + return Extend(up = 'up' in s, + down = 'down' in s, + left = 'left' in s, + right = 'right' in s) + +def SplitImage3(imagedict): + ''' + imagedata: + source: #where the file can be located + x1: #left hand cut on the x-axis + x2: #right hand cut on the x-axis + y1: #top cut on the y-axis + y2: #bottom cut on the y-axis + style: #sets what the rescaled parts of the image do; tile or stretch + #defaults to stretch + fillcolor: color to fill in the background with + + #the following lines are used to give styles to each region and + #the ability to extend. They all have vry simular function besides + #the directions they can be extended so only center will be fully + #described and the rest will onl be comented on supported extend directions + + center: + style: #is this region going to stretch or tile + extend: #what direction the region will be extended into + #center can extend; left, right, up, down down + fillcolor: color to fill in the background with + left: #extend up and down + top: #extend left and right + right: #extend up and down + bottom: #extend left and right + ''' + + i = ImageData() + for a in ('x1', 'x2', 'y1', 'y2'): + setattr(i, max(1, a), imagedict[a]) + + i.source = skincore.gSkin().load_image(imagedict['source'], path = True) + + style = imagedict.get('style', 1) + fillcolor = imagedict.get('fillcolor', wx.WHITE) + + for r in ('center', 'left', 'top', 'right', 'bottom'): + id_region = getattr(i, r) + region = imagedict.get(r, {}) + + if 'extend' in region: + id_region.extends = make_extends(region['extend']) + + id_region.style = region.get('style', style) + id_region.fillcolor = region.get('fillcolor', fillcolor) + + try: + return SplitImage4(i) + except: + import sys + print >> sys.stderr, pformat(imagedict) + raise + + + +def MakeImageData(): + idata = ImageData(); + + idata.x1 = 30; + idata.x2 = -30; + idata.y1 = 30; + idata.y2 = -30; + + idata.source= "c:\\src\\Digsby\\res\\digsbybig.png" + + reg = ('center', 'top', 'right', 'bottom', 'left') + + for region in reg: + r = getattr(idata, region) + r.fillcolor = wx.BLACK + r.style = 1 + + return idata; + +if __name__ == '__main__': + from tests.testapp import testapp + from gui import skin + + a = testapp('../..') + + f = wx.Frame(None, -1, 'Test', pos = (50, 50), size = (450, 340)) + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + + + si4 = skin.get('popup.backgrounds.normal') + #si4 = skin.get('tabs.dropmarker.image') + + + + def paint(e): + dc = wx.AutoBufferedPaintDC(f) + r = wx.RectS(f.Size) + dc.DrawRectangleRect(r) + + si4.Draw(dc, r.Deflate(100, 100)) + + + f.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + f.Bind(wx.EVT_SIZE, lambda e: f.Refresh()) + f.Bind(wx.EVT_PAINT, paint) + + + f.Show() + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/gui/skin/__init__.py b/digsby/src/gui/skin/__init__.py new file mode 100644 index 0000000..83dddb4 --- /dev/null +++ b/digsby/src/gui/skin/__init__.py @@ -0,0 +1,340 @@ +''' + +Skin management + +''' +from __future__ import with_statement +from wx import Image, BITMAP_TYPE_ANY, BitmapFromImage, GetApp, ImageFromString + +from path import path +from logging import getLogger; log = getLogger('skin') +from types import FunctionType +from util.data_importer import zipopen +from util.primitives import Storage +import time + +class SkinException(Exception): + 'Thrown when the structure of a skin file is invalid.' + +def skinpath(name, exc = True): + respaths = GetApp().skin.paths + return skinfile(name, respaths, exc) + +def skinfile(name, paths, exc = True): + #fast loop + for p in reversed(paths): + imgpath = p / name + if imgpath.isfile(): + return imgpath + #slow loop + for p in reversed(paths): + imgpath = p / name + try: + foo = zipopen(imgpath).read() + except Exception: + pass + else: + if foo is not None: + return imgpath + + if exc: + raise SkinException('could not find skinfile %s (paths: %r)' % (name, paths)) + else: + return None + +def basepath(name, exc = True): + path = GetApp().skin.path + return skinfile(name, paths=[path], exc=exc) + +#from gui.skin import skintree + +def skininit(pth, skinname = 'default'): + + if not hasattr(pth, 'abspath'): + pth = path(pth) + + set_resource_paths([pth.abspath()]) + + global _css_fonts + _css_fonts = None + set_active(skinname) + +def reload(): + 'Reloads the active skin.' + + t = time.clock() + from common import pref + global _css_fonts + _css_fonts = None + set_active(pref('appearance.skin'), pref('appearance.variant'), True) + log.info('skin reloaded in %ss', (time.clock() - t)) + +def set_resource_paths(resource_paths): + ''' + Tell the skin system where the resource path is. + + This path should contain a "skins" directory. + ''' + + from gui.skin import skintree + skintree.resource_paths = [path(p).abspath() for p in resource_paths] + +def get_resource_paths(): + ''' + Returns the resource path. + ''' + + from gui.skin import skintree + return skintree.resource_paths + +from skintree import set_active, list_skins, skindesc, get as skintree_get + +sentinel = object() + +def resourcedir(): + return get_resource_paths()[0] + +class LazyImage(object): + def __init__(self, pth, return_bitmap): + self.pth = pth + self.return_bitmap = return_bitmap + + def _lazy_skin_load(self): + return _loadimage(self.pth, return_bitmap = self.return_bitmap) + + def Ok(self): + return True + +class SkinStorage(Storage): + ''' + lazy loads skin images + ''' + + def __getitem__(self, key, gi = dict.__getitem__): + return self._lazy_load(key, gi(self, key)) + + def get(self, key, default=sentinel): + if default is sentinel: + val = Storage.get(self, key) + else: + val = Storage.get(self, key, default) + + return self._lazy_load(key, val) + + def _lazy_load(self, key, val): + if hasattr(val, '_lazy_skin_load'): + img = val._lazy_skin_load() + self[key] = img + return img + else: + return val + + def __getattr__(self, key, ga = dict.__getattribute__, gi = __getitem__): + try: + return ga(self, key) + except AttributeError: + try: + return gi(self, key) + except KeyError: + msg = repr(key) + if len(self) <= 20: + keys = sorted(self.keys()) + msg += '\n (%d existing keys: ' % len(keys) + str(keys) + ')' + raise AttributeError, msg + + +def get(dotted_path, default = sentinel): + + if dotted_path.startswith('skin:'): + dotted_path = dotted_path[5:] + + v = skintree_get(dotted_path, default = default) + if v is sentinel: + raise SkinException('not found: "%s"' % dotted_path) + elif v is default: + return v() if isinstance(v, FunctionType) else v + else: + return v + + + +def load_bitmap(name, return_bitmap = True): + return LazyImage(skinpath(name), return_bitmap) + #return _loadimage(skinpath(name), return_bitmap = return_bitmap) + +def load_image(name): + return load_bitmap(name, False) + + +def _loadimage(path, return_bitmap = False): + try: + if path.isfile(): + img = Image(path, BITMAP_TYPE_ANY) + else: + f = None + try: + f = zipopen(path) + if f is None: + raise IOError('Image ' + path + ' does not exist') + img = ImageFromString(f.read()) + finally: + if f is not None: + f.close() + + if not img.HasAlpha(): + img.InitAlpha() + + val = img if not return_bitmap else BitmapFromImage(img) + val.path = path + return val + + except Exception, err: + raise AssertionError(err) + + +import urllib2 + +try: + urllib2.urlopen('') # ensure an opener is present +except Exception, e: + pass + +class SkinHandler(urllib2.BaseHandler): + def skin_open(self, req): + from util import Storage + val = get(req.get_host()) + return Storage(read=lambda:val) + +urllib2._opener.add_handler(SkinHandler()) + +from gui.skin.skinobjects import Margins +ZeroMargins = Margins() + +from gui.skin.skinparse import \ + makeBrush as brush, \ + makeFont as font + +font_multiply_factor = 1.0 + + +def build_font_css(): + import wx + from gui.textutil import default_font + from util import Point2HTMLSize + + h = Storage() + +#------------------------------------------------------------------------------- +# Code for TagFont function +#---------------------------------------- + h.header = get('infobox.fonts.header', default_font) + h.title = get('infobox.fonts.title', default_font) + h.major = get('infobox.fonts.major', default_font) + h.minor = get('infobox.fonts.minor', default_font) + h.link = get('infobox.fonts.link', default_font) + + h.headerfc = get('infobox.fontcolors.header', wx.BLACK).GetAsString(wx.C2S_HTML_SYNTAX) + h.titlefc = get('infobox.fontcolors.title', wx.BLACK).GetAsString(wx.C2S_HTML_SYNTAX) + h.majorfc = get('infobox.fontcolors.major', wx.BLACK).GetAsString(wx.C2S_HTML_SYNTAX) + h.minorfc = get('infobox.fontcolors.minor', lambda: wx.Color(128, 128, 128)).GetAsString(wx.C2S_HTML_SYNTAX) + h.linkfc = get('infobox.fontcolors.link', wx.BLUE).GetAsString(wx.C2S_HTML_SYNTAX) + + import io + sio = io.StringIO() + for name in ('major', 'minor', 'header', 'title', 'link'): + writeline = lambda s: sio.write(s+u'\n') + if name == 'link': + sio.write(u'a, ') + + writeline('.%s {' % name) + writeline('\tcolor: %s;' % getattr(h, '%sfc' % name)) + writeline('\tfont-family: "%s";' % h[name].FaceName) + writeline('\tfont-size: %spt;' % h[name].PointSize) + if h[name].Style == wx.ITALIC: + writeline('\tfont-style: italic;') + else: + writeline('\tfont-style: normal;') + + if h[name].Weight == wx.BOLD: + writeline('\tfont-weight: bold;') + else: + writeline('\tfont-weight: normal;') + + if h[name].Underlined: + writeline('\ttext-decoration: underline;') + else: + writeline('\ttext-decoration: none;') + + writeline('}') + + return sio.getvalue() + +_css_fonts = None +def get_css_fonts(): + ''' + return some generated CSS related to fonts + ''' + global _css_fonts + if _css_fonts is None: + _css_fonts = build_font_css() + return _css_fonts + +def get_css_images(): + ''' + return some generated CSS with stuff related to images (?) + ''' + import path + + sep1 = get('infobox.shortseparatorimage') + sep2 = get('infobox.longseparatorimage') + return (''' +hr { + border-style: none; + height: %spx; + background: url("%s"); +}''' % (sep1.Size.height, path.path(sep1.Path).url())) +\ +(''' +hr[type="2"] { + border-style: none; + height: %spx; + background: url("%s"); +} +''' % (sep2.Size.height, path.path(sep2.Path).url())) + +def get_css_layout(): + ''' + return some generated CSS with stuff related to padding, margins, borders, etc. + ''' + pad = get('infobox.margins') + try: + l, t, r, b = pad + except TypeError: + try: + (l, t,), (r, b) = pad, pad + except TypeError: + t = l = b = r = pad + + return ''' +body { + margin: %spx %spx %spx %spx; +} +''' % (t, r, b, l) + +def get_social_css(): + import wx + minor_color = get('infobox.fontcolors.minor', lambda: wx.Color(128, 128, 128)).GetAsString(wx.C2S_HTML_SYNTAX) + postrow_hover_color = get('infobox.backgrounds.socialhovercolor', lambda: wx.Color(128, 128, 128)).GetAsString(wx.C2S_HTML_SYNTAX) + return ''' +.minor_border {{ + border-color: {minor_color}; +}} +.social_background_hover:hover {{ + background-color: {postrow_hover_color}; +}} +.social_background_hover_on {{ + background-color: {postrow_hover_color}; +}} +'''.format(**locals()) + +def get_css(): + return '\n'.join((get_css_fonts(), get_css_images(), get_css_layout(), get_social_css())) diff --git a/digsby/src/gui/skin/gcbug.py b/digsby/src/gui/skin/gcbug.py new file mode 100644 index 0000000..4b083fa --- /dev/null +++ b/digsby/src/gui/skin/gcbug.py @@ -0,0 +1,60 @@ +import wx + + + +if __name__ == '__main__': + + a = wx.PySimpleApp() + f = wx.Frame(None) + fix = False + + def drawrect(dc, x, y, round = False): + dc.SetBrush(wx.WHITE_BRUSH) + dc.SetPen(wx.TRANSPARENT_PEN) + + w, h = 30, 30 + + if round: + dc.DrawRoundedRectangle(x, y, w, h, 5) + else: + dc.DrawRectangle(x, y, w, h) + + + def paint(e): + dc = wx.AutoBufferedPaintDC(f) + gc = wx.GraphicsContext.Create(dc) + if fix: gc.Translate(-.5,-.5) + + dc.Brush = wx.BLACK_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(wx.RectS(f.ClientSize)) + + drawrect(dc, 10, 35) + drawrect(gc, 40, 35) + drawrect(dc, 70, 35, True) + drawrect(gc, 100, 35, True) + + + + + def button(e): + global fix + fix = not fix + f.Refresh() + + + f.Bind(wx.EVT_PAINT, paint) + f.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + + f.Sizer = wx.BoxSizer(wx.VERTICAL) + b = wx.Button(f, -1, 'toggle fix') + f.Sizer.Add(b) + b.Bind(wx.EVT_BUTTON, button) + + + + + + + f.Show() + a.MainLoop() diff --git a/digsby/src/gui/skin/skinnative_setup.py b/digsby/src/gui/skin/skinnative_setup.py new file mode 100644 index 0000000..af3a77b --- /dev/null +++ b/digsby/src/gui/skin/skinnative_setup.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +import sys, string + +sys.setup_is_main = __name__ == "__main__" +from wx.build.config import * + +if not os.path.exists(PKGDIR): + os.mkdir(PKGDIR) + +EXTRA_PATH = getExtraPath(addOpts=True) if INSTALL_MULTIVERSION else None + +WXPY_SRC = 'C:/src/wxPython-2.8.4.0' + +if True: + location = './' + + swig_files = [ + 'skinnative.i', + #'skinbrush.i', + #'skinvlist.i', + #'skinsplitter.i', + ] + + foosources = [ + 'skinbrush.cpp', + 'skinvlist.cpp', + #'skinsplitter.cpp', + ] + + swig_sources = run_swig(swig_files, + location, + GENDIR, + PKGDIR, + True or USE_SWIG, + True or swig_force, + ['-I%s\\include\\wx\\wxPython\\i_files' % WXPY_SRC] + swig_args, + swig_deps) + + includes.append('.') + + if os.name == "nt": + includes.append(WXPY_SRC + "/include") + if UNICODE: + includes.append(WXPY_SRC + "/lib/vc_dll/mswuh") + defines.append(('_UNICODE',1)) + else: + includes.append(WXPY_SRC + "/lib/vc_dll/mswh") + + libdirs.append(WXPY_SRC + "/lib/vc_dll") + + if debug: + defines = defines + [('DEBUG', 1), ('_DEBUG',1), ('TRACING',1)] + + + + ext = Extension('_skinnative', + foosources + swig_sources, + include_dirs = includes, + define_macros = defines, + + library_dirs = libdirs, + libraries = libs, + + extra_compile_args = cflags, + extra_link_args = lflags, + ) + wxpExtensions.append(ext) + + +if __name__ == "__main__": + if not PREP_ONLY: + setup(name = PKGDIR, + version = VERSION, + description = DESCRIPTION, + long_description = LONG_DESCRIPTION, + author = AUTHOR, + author_email = AUTHOR_EMAIL, + url = URL, + license = LICENSE, + packages = ['wx', 'wxPython'], + + extra_path = EXTRA_PATH, + ext_package = PKGDIR, + ext_modules = wxpExtensions, + + options = { 'build' : { 'build_base' : BUILD_BASE }}, + ) + + +#---------------------------------------------------------------------- +#---------------------------------------------------------------------- diff --git a/digsby/src/gui/skin/skinobjects.py b/digsby/src/gui/skin/skinobjects.py new file mode 100644 index 0000000..dafcb8d --- /dev/null +++ b/digsby/src/gui/skin/skinobjects.py @@ -0,0 +1,387 @@ +''' + +Skin objects for drawing into rectangular regions + +''' +from __future__ import with_statement +from __future__ import division +import wx +from util.primitives.funcs import autoassign +create_gc = wx.GraphicsContext.Create +from gui.toolbox import TransparentBitmap +from operator import itemgetter + +from cgui import Bitmap_Draw +from config import platformName + +import new +wx.Bitmap.Draw = new.instancemethod(Bitmap_Draw, None, wx.Bitmap) + +# This seems to have problems with SWIG, so let's just enable it on Win for now. +USE_CGUI_SKIN_OBJECTS = False + +if platformName == "win": + USE_CGUI_SKIN_OBJECTS = True + +class MarginSizer(wx.GridBagSizer): + 'A sizer that adds margin space.' + + def __init__(self, margins, content): + wx.GridBagSizer.__init__(self) + self.SetEmptyCellSize(wx.Size(0, 0)) + self.Add(content, (1,1), flag = wx.EXPAND) + + self.AddGrowableCol(1,1) + self.AddGrowableRow(1,1) + + self.SetMargins(margins) + + def SetMargins(self, margins): + if len(self.GetChildren()) > 2: + self.Detach(1) # remove the two padding + self.Detach(1) + + self.Add(wx.Size(margins.left, margins.top), (0, 0)) + self.Add(wx.Size(margins.right, margins.bottom), (2, 2)) + +class Margins(list): + def __init__(self, a = None): + if a is None: + list.__init__(self, [0, 0, 0, 0]) + else: + if isinstance(a, basestring): + list.__init__(self, (int(c) for c in a.split())) + elif isinstance(a, int): + list.__init__(self, [a] * 4) + else: + if len(a) == 2: + a = [a[0], a[1], a[0], a[1]] # x y -> x y x y + list.__init__(self, (int(c) for c in a)) + + assert len(self) == 4 + + def Sizer(self, content): + return MarginSizer(self, content) + + left = property(itemgetter(0)) + top = property(itemgetter(1)) + right = property(itemgetter(2)) + bottom = property(itemgetter(3)) + + x = property(lambda self: self[0] + self[2]) + y = property(lambda self: self[1] + self[3]) + + TopLeft = property(lambda self: wx.Point(self[0], self[1])) + BottomRight = property(lambda self: wx.Point(self[2], self[3])) + + def __neg__(self): + return Margins([-a for a in self]) + + + +class SkinBase(object): + def Show(self): + from gui.toolbox import ShowImage + ShowImage(self) + +class SkinRegion(SkinBase): + def __init__(self, border = None, rounded = False, highlight = False, shadow = False): + ''' + Any rectangular area. + + border a wxPen object used to draw the edges + rounded if True, the rectangle is drawn with rounded corners + highlight if True, the upper left corner is highlighted + shadow if True, the bottom right corner is shadowed + ''' + if not isinstance(border, (type(None), wx.Pen)): + raise TypeError('border must be a Pen or None') + + autoassign(self, locals()) + self.pen = wx.TRANSPARENT_PEN if border is None else border + + def GetBitmap(self, size, n = 0): + tb = TransparentBitmap(size) + dc = wx.MemoryDC() + dc.SelectObject(tb) + self.draw(dc, wx.RectS(size), n) + dc.SelectObject(wx.NullBitmap) + return tb + + def Stroke(self, dc, gc, rect, n = 0): + pen = self.pen + penw = pen.Width // 2 + rect = wx.Rect(*rect) + rect.Deflate(penw, penw) + + pen.SetCap(wx.CAP_PROJECTING) + gc.SetPen(pen) + dc.SetPen(pen) + dc.Brush = wx.TRANSPARENT_BRUSH + gc.Brush = wx.TRANSPARENT_BRUSH + + if self.rounded: + if self.border: + gc.DrawRoundedRectangle(*(tuple(rect) + (self.rounded * .97, ))) + + rect.Inflate(penw, penw) + self.stroke_highlights_rounded(gc, rect) + + else: + if self.border: + # because dc.DrawRectangle() results in a rounded rectangle. do + # it manually :( + offset = int(pen.Width % 2 == 0) + dl, x, y = dc.DrawLine, wx.Point(offset, 0), wx.Point(0, offset) + + for a, b in [(rect.TopLeft, rect.BottomLeft + y), + (rect.BottomLeft + y, rect.BottomRight + y + x), + (rect.BottomRight + y + x, rect.TopRight + x), + (rect.TopRight + x, rect.TopLeft)]: + dl(*(tuple(a) + tuple(b))) + + rect.Inflate(penw, penw) + self.stroke_highlights(gc, rect) + + def stroke_highlights_rounded(self, gc, rect): + pass + + def stroke_highlights(self, gc, rect): + hw = max(2, self.pen.Width) + seq = [] + + if self.highlight: + c1, c2 = wx.Color(255, 255, 255, 100), wx.Color(255, 255, 255, 150) + + seq.extend([( + (rect.x, rect.y), + [(rect.x, rect.Bottom), (rect.x + hw, rect.Bottom - hw), + (rect.x + hw, rect.y + hw), (rect.x, rect.y)], + gc.CreateLinearGradientBrush(rect.x, rect.y, rect.x + hw, rect.y, c1, c2) + ), ((rect.x, rect.y), + [(rect.Right + 1, rect.y), (rect.Right - hw, rect.y + hw), + (rect.x + hw, rect.y + hw), (rect.x, rect.y)], + gc.CreateLinearGradientBrush(rect.x, rect.y, rect.x, rect.y + hw, c1, c2) + )]) + + + if self.shadow: + sc1, sc2 = wx.Color(0, 0, 0, 50), wx.Color(0, 0, 0, 100) + + seq.extend([( + (rect.Right + 1, rect.Bottom + 1), + [(rect.x, rect.Bottom + 1), (rect.x + hw, rect.Bottom - hw + 1), + (rect.Right - hw + 1, rect.Bottom - hw + 1), (rect.Right + 1, rect.Bottom + 1)], + gc.CreateLinearGradientBrush(rect.x, rect.Bottom - hw + 1, rect.x, rect.Bottom + 1, sc1, sc2)), + + ((rect.Right + 1, rect.Bottom + 1), + [(rect.Right + 1, rect.y), (rect.Right - hw + 1, rect.y + hw), + (rect.Right - hw + 1, rect.Bottom - hw + 1), (rect.Right + 1, rect.Bottom + 1)], + gc.CreateLinearGradientBrush(rect.Right - hw + 1, rect.Bottom, rect.Right + 1, rect.Bottom, sc1, sc2))]) + + + if seq: + for origin, pts, brush in seq: + p = gc.CreatePath() + p.MoveToPoint(*origin) + for pt in pts: + p.AddLineToPoint(*pt) + + gc.SetBrush(brush) + gc.FillPath(p) + + + + def draw(self, *a, **k): + self.Draw(*a, **k) + + +class SkinStack(list, SkinBase): + 'Stacking skin regions.' + + def __init__(self, seq): + list.__init__(self, seq) + + if not all(callable(getattr(elem, 'Draw', None)) for elem in seq): + raise TypeError('SkinStack must be constructed with .Draw-able elements (you gave %r)' % seq) + + def Draw(self, dc, rect, n = 0): + for brush in reversed(self): + brush.Draw(dc, rect, n) + + def GetBitmap(self, size, n = 0): + tb = TransparentBitmap(size) + dc = wx.MemoryDC() + dc.SelectObject(tb) + + for region in reversed(self): + region.draw(dc, wx.RectS(size), n) + + dc.SelectObject(wx.NullBitmap) + return tb + + @property + def ytile(self): return all(c.ytile for c in self) + +class SkinList(list, SkinBase): + 'Alternating skin regions in a list of items.' + + def __init__(self, seq): + list.__init__(self, seq) + + if not all(callable(getattr(elem, 'Draw', None)) for elem in seq): + raise TypeError('SkinStack must be constructed with .Draw-able elements (you gave %r)' % seq) + + def Draw(self, dc, rect, n = 0): + self[n % len(self)].Draw(dc, rect, n) + + @property + def ytile(self): return all(c.ytile for c in self) + +class SkinGradient(SkinRegion): + + __slots__ = ['direction', 'ytile', 'colors', '_oldrect'] + + def __init__(self, direction, colors, **opts): + SkinRegion.__init__(self, **opts) + self.direction = direction + + self.ytile = direction == 'horizontal' and not self.border + + self.colors = colors + self._oldrect = None + + if self.rounded is not False or len(colors) > 2: + self.Fill = lambda gc, x, y, w, h: wx.GraphicsContext.DrawRoundedRectangle(gc, x, y, w, h, self.rounded) + else: + self.Fill = wx.GraphicsContext.DrawRectangle + + def __repr__(self): + return '<%s %s %r>' % (self.__class__.__name__, self.direction, self.colors) + + def _rects(self, therect): + # cache most recently used rectangles + if therect == self._oldrect: + return self._oldrects + + x, y = float(therect.x), float(therect.y) + w = float(therect.width) + h = float(therect.height) + vert = self.direction == 'vertical' + + p1 = float(therect.Top if vert else therect.Left) + + lc = len(self.colors) - 1 + dx = (h if vert else w) / float(lc) + + rects = [] + + for i in xrange(0, lc): + c1, c2 = self.colors[i:i+2] + + delta = 1 if i not in (lc, 0) else 0 + + if vert: + r = (x, p1, w, dx + delta) + gradrect = (x, p1 - delta, x, p1 + dx + delta*2, c1, c2) + else: + r = (p1, y, dx + delta, h) + gradrect = (p1 - delta, y, p1 + dx + delta*2, y, c1, c2) + + rects.append( (gradrect, r) ) + p1 = p1 + dx + + # todo: why is caching breaking VLists? + #self._oldrect = therect + #self._oldrects = rects + return rects + + def Draw(self, dc, therect, n = 0): + gc = create_gc(dc) + gc.SetPen(wx.TRANSPARENT_PEN) + createb = gc.CreateLinearGradientBrush + + gc.Clip(*therect) + + for gradrect, fillrect in self._rects(therect): + gc.SetBrush(createb(*gradrect)) + self.Fill(gc, *fillrect) + + self.Stroke(dc, gc, therect) + + + +class SkinColor(wx.Color, SkinRegion): + 'A solid color.' + + simple = False + + def __init__(self, color, **opts): + # self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + if not any(val for key, val in opts.iteritems()): + c = tuple(color) + if len(c) < 4 or c[3] == 255: + self.simple = True + + SkinRegion.__init__(self, **opts) + wx.Color.__init__(self) + + self.Set(*color) + self.ytile = not self.border + + def Fill(self, dc, rect): + if self.rounded and not self.simple: + dc.DrawRoundedRectangle(*(tuple(rect) + (self.rounded, ))) + else: + dc.DrawRectangle(*rect) + + def __repr__(self): + return '<%s (%s, %s, %s, %s)>' % ((self.__class__.__name__,) + tuple(self)) + + def Draw(self, dc, rect, n = 0): + brush = wx.Brush(self) + + if self.simple: + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(brush) + dc.DrawRectangle(*rect) + else: + gc = create_gc(dc) + gc.SetBrush(brush) + gc.SetPen(wx.TRANSPARENT_PEN) + + gc.Clip(*rect) + + # GraphicsContext off by half a pixel? + self.Fill(gc, rect) + self.Stroke(dc, gc, rect, n) + + +if USE_CGUI_SKIN_OBJECTS: + from cgui import SkinColor as CGUISkinColor + from cgui import SkinGradient as CGUISkinGradient + import gui.toolbox + + # Patch .Show method onto skin object classes. + for clz in (CGUISkinColor, CGUISkinGradient): + clz.Show = new.instancemethod(gui.toolbox.ShowImage, None, clz) + + from cgui import SkinRegion # for isinstance checks + + def SkinColor(color, **opts): + color = CGUISkinColor(color) + set_skin_options(color, opts) + return color + + def SkinGradient(direction, colors, **opts): + direction = wx.HORIZONTAL if direction == 'horizontal' else wx.VERTICAL + gradient = CGUISkinGradient(direction, colors) + set_skin_options(gradient, opts) + return gradient + + def set_skin_options(obj, opts): + obj.SetOutline(opts.get('border') or wx.TRANSPARENT_PEN, + opts.get('rounded', 0), + opts.get('highlight', False), + opts.get('shadow', False)) + diff --git a/digsby/src/gui/skin/skinparse.py b/digsby/src/gui/skin/skinparse.py new file mode 100644 index 0000000..6ba8828 --- /dev/null +++ b/digsby/src/gui/skin/skinparse.py @@ -0,0 +1,478 @@ +''' + +Parses skin syntax. + +''' +from __future__ import division, with_statement +import wx +from itertools import izip + +from util.primitives.funcs import isint +from util.primitives.mapping import Storage as S +from util.primitives.misc import clamp +from path import path + +from gui.toolbox import colorfor +from gui.textutil import default_font +from gui.skin.skinobjects import SkinGradient, SkinColor, Margins, SkinStack +from gui.skin import SkinException, skinpath + +import cgui +if not hasattr(cgui, 'ImageDataFromSource'): + cgui.ImageDataFromSource = cgui.ImageData + cgui.SIRegion = cgui.Region + +from cgui import SplitImage4, ImageData, ImageDataFromSource, Extend, SIRegion + +from logging import getLogger; log = getLogger('skinparse') + +sentinel = object() + +# valid words in a font line + +fontstyles = ('slant', 'italic') # -> wx.FONTSTYLE_%s +fontweights = ('light', 'bold') # -> wx.FONTWEIGHT_%s +fontflags = ('underline',) + fontstyles + fontweights + +DEFAULT_ROUNDED_RADIUS = 6 + +# If true, split image offsets are all treated as positive offsets from an edge +SPLITIMAGE_POSITIVE_OFFSETS = True + + +# style: +# 0 - canvas resize, image does not +# 1 - stretch +# 2 - tile +regionTypes = S(static = 0, + stretch = 1, + tile = 2) + + +SIREGIONS = ('left', 'right', 'center', 'top', 'bottom') + +SIEXTENDS = ('left', 'up', 'down', 'right') + +regionAlignments = dict( + h_center = wx.ALIGN_CENTER_HORIZONTAL, + v_center = wx.ALIGN_CENTER_VERTICAL, + h_left = wx.ALIGN_LEFT, + h_right = wx.ALIGN_RIGHT, + v_top = wx.ALIGN_TOP, + v_bottom = wx.ALIGN_BOTTOM) + +def makePadding(s): + if isinstance(s, int): + return s + elif isinstance(s, basestring): + return wx.Point(*(int(c) for c in s.split())) + else: + return wx.Point(*(s * 2 if len(s) == 1 else s)) + +def makeExtend(e): + left, up, down, right = [(a in e) for a in SIEXTENDS] + return Extend(up, down, left, right) + +def makeRegion(rdict): + ''' + left: + hstyle: tile + vstyle: static + offset: 5 -5 + align: left + extends: [left up down right] + ''' + + h = rdict.get('hstyle', regionTypes.stretch) + v = rdict.get('vstyle', regionTypes.stretch) + + try: offset = rdict['offset'] + except KeyError: offset = wx.Point() + else: offset = makePadding(offset) + + try: e = rdict.get('extend') + except: e = Extend() + else: e = makeExtend(e) + + align = rdict.get('align', wx.ALIGN_CENTER) + + return SIRegion(e, h, v, align, offset) + +def solid_si4(name): + 'Return a no-cut SplitImage4 that stretches.' + + return si4(S(source = name)) + +def si4(imgdict): + try: + imgdict.source = unicode(skinpath(imgdict.source)) + except: + pass + + if not path(imgdict.source).isfile(): + raise SkinException('cannot find file %s' % imgdict.source ) + + idata = ImageDataFromSource(imgdict.source) + + for attr in ('x1', 'x2', 'y1', 'y2'): + setattr(idata, attr, imgdict.get(attr, 0)) + + if SPLITIMAGE_POSITIVE_OFFSETS: + # splitimage4 expects negative numbers for x2/y2 + idata.x2 = -idata.x2 #+ (1 if idata.x2 else 0) + idata.y2 = -idata.y2 #+ (1 if idata.x2 else 0) + + for attr in SIREGIONS: + if attr in imgdict: + region = makeRegion(imgdict[attr]) + else: + region = SIRegion(Extend(), 1, 1, wx.ALIGN_CENTER, wx.Point()) + + setattr(idata, attr, region) + + s = SplitImage4(idata) + s.ytile = False + s.idata = idata + s.copy = lambda *a: si4copy(s, *a) + s.path = path(s.GetPath()) + return s + +def si4copy(split, x1, y1, x2, y2): + idata = ImageData(split.idata) + + idata.x1 = x1 + idata.y1 = y1 + idata.x2 = x2 + idata.y2 = y2 + + if SPLITIMAGE_POSITIVE_OFFSETS: + idata.x2 = -idata.x2 + idata.y2 = -idata.y2 + + s = SplitImage4(idata) + s.ytile = False + s.idata = idata + s.copy = lambda *a: si4copy(s, *a) + s.path = split.path + + return s + +imageExts = ('jpg', 'jpeg', 'png', 'bmp', 'gif') + +def makeImage(imagedesc): + 'Parses an image description, returning a SolidImage or SplitImage4.' + + imagedesc = imagedesc.strip() + if not imagedesc: return None + + # use the image extension as the "split" point between the filename + # and the options, so that spaces in image filenames are possible + # without quotes. + i = max(imagedesc.find('.' + ext) for ext in imageExts) + if i == -1: raise SkinException('images end in %r' % (imageExts,)) + + i = imagedesc.find(' ', i) + if i == -1: + # just "image.png" -- return a SolidImage. + return solid_si4(imagedesc) + + filename, options = imagedesc[:i], imagedesc[i+1:] + + imgdict = S(source = filename) + options = options.split() + + if options: + # one-liner: image with cuts + + if isint(options[0]): + # numbers...must be a splitimage + n = len(options) + for i, opt in enumerate(options): + if not isint(opt): + n = i + break + + splits, options = options[:n], options[n:] + if not splits: solid_si4(imgdict['source']) + + # parsing rules for splits are same as framesizes + imgdict.update(izip(('x1', 'y1', 'x2', 'y2'), Margins(splits))) + + hstyle = vstyle = regionTypes.stretch + align = None + posSpecified = False + offset = [] + + for option in options: + if option.startswith('h_'): + if option in ('h_right', 'h_center', 'h_left'): + hstyle = regionTypes.static + if align is None: + align = regionAlignments[option] + else: + align |= regionAlignments[option] + else: + hstyle = regionTypes[option[2:]] + elif option.startswith('v_'): + if option in ('v_top', 'v_center', 'v_bottom'): + vstyle = regionTypes.static + if align is None: + align = regionAlignments[option] + else: + align |= regionAlignments[option] + else: + vstyle = regionTypes[option[2:]] + + elif option == 'tile': + hstyle = vstyle = regionTypes.tile + + elif isint(option): + offset += [int(option)] + + else: + log.warning('unknown skin option "%s"') + + if len(offset) == 0: # no offsets given: use (0, 0) + offset = [0, 0] + elif len(offset) == 1: # one offset: means it is used for both X and Y + offset = offset * 2 + else: # more than two: use the last two numbers found + offset = offset[-2:] + + if align is None: + align = wx.ALIGN_CENTER + + for a in SIREGIONS: + imgdict[a] = S(extend = [], hstyle = hstyle, vstyle = vstyle, align = align, offset = wx.Point(*offset)) + return si4(imgdict) + else: + return solid_si4(imgdict['source']) + +image_exts = ('png', 'jpg', 'jpeg', 'bmp', 'ico', 'gif') + +def makeBrush(brushdesc): + ''' + Makes a rectangular skin brush given strings like + + red + red white blue + vertical green white + red rounded + red rounded 10 + blue shadow + 0xffffee 0x123456 + ''' + + if isinstance(brushdesc, list): + return SkinStack(makeBrush(e) for e in brushdesc) + elif isinstance(brushdesc, int): + return SkinColor(colorfor(brushdesc)) + elif brushdesc is None: + return None + + elems = brushdesc.split() + + try: + b = elems.index('border') + except ValueError: + border = None + else: + border = makeBorder(' '.join(elems[b:])) + elems = elems[:b] + + first = elems[0] + if any(first.endswith(e) for e in image_exts): + return makeImage(brushdesc) + + shadow = highlight = rounded = False + colors = [] + direction = 'vertical' + + for i, elem in enumerate(elems): + elem = elem.lower() + + if elem in ('h','v', 'horizontal', 'vertical'): + direction = {'h': 'horizontal', + 'v': 'vertical'}.get(elem, elem) + elif elem == 'rounded': + if len(elems) > i + 1 and isint(elems[i+1]): + rounded = float(elems[i+1]) + else: + rounded = DEFAULT_ROUNDED_RADIUS + elif elem == 'highlight': highlight = True + elif elem == 'shadow': shadow = True + elif elem.endswith('%') and isint(elem[:-1]) and colors: + # replace the last wxColor in colors with the same color and + # a new alpha value, so strings like "blue red 40%" produce + # [wx.Colour(0, 0, 255, 255), wx.Colour(255, 0, 0, 102)] + # + # (I know there is a wxColour.Set method but it didn't work for me) + alpha_value = clamp(float(elem[:-1])/100.00 * 255.0, 0, 255) + rgba = tuple(colors[-1])[:3] + (alpha_value,) + colors[-1] = wx.Colour(*rgba) + else: + try: colors.append(colorfor(elem)) + except ValueError: pass + + kwargs = dict(rounded = rounded, shadow = shadow, + highlight = highlight, border = border) + + if len(colors) == 0: + raise SkinException('no colors specified in "%s"' % brushdesc) + elif len(colors) == 1: + # one color -> SkinColor + return SkinColor(colors[0], **kwargs) + else: + # multiple colors -> SkinGradient + return SkinGradient(direction, colors, **kwargs) + +def makeBorder(borderdesc): + + vals = borderdesc.split() + + if not vals: return wx.TRANSPARENT_PEN + + if vals[0].lower() == 'border': + vals = vals[1:] + + d = S() + for elem in vals: + elem = elem.lower() + if elem.endswith('px') and isint(elem[:-2]): + d.size = int(elem[:-2]) + elif isint(elem): + d.size = int(elem) + else: + penstyle = penstyle_aliases.get(elem, elem) + if penstyle in penstyles: + d.style = penstyle + else: + try: d.color = colorfor(elem) + except ValueError: pass + + return _pen_from_dict(d) + +def _pen_from_dict(d): + ''' + Give {color: 'blue', width:5, style:'dot'}, makes the appropriate + wxPen object. + ''' + + color = colorfor(d.get('color', 'black')) + + width = int(d.get('size', 1)) + + style = d.get('style', 'solid') + + if not style: style = 'solid' + + if not style in penstyles: + raise SkinException('invalid pen style: "%s"' % style) + + import wx + style = getattr(wx, style.upper()) + + if not isinstance(color, wx.Colour): + raise TypeError('not a color: %r' % color) + + return wx.Pen(color, width, style) + +penstyles = set( + ('solid', + 'transparent', + 'dot', + 'long_dash', + 'short_dash', + 'dot_dash', + 'stipple', + 'user_dash', + 'bdiagonal_hatch', + 'crossdiag_hatch', + 'fdiagonal_hatch', + 'cross_hatch', + 'horizontal_hatch', + 'vertical_hatch')) + +penstyle_aliases = dict(dashed = 'long_dash') + +def makeFont(fontdesc, defaultFace = None, defaultSize = None): + ''' + Returns a wxFont for the following skin syntax: + + Arial 12 bold Underline + + or + + Comic Sans MS 14 italic strikethrough + ''' + from gui.skin import font_multiply_factor + system_font = default_font() + + if not fontdesc: + return system_font + elif isinstance(fontdesc, int): + system_font.SetPointSize(int(fontdesc)) + return system_font + + + # arguments to wx.Font constructor + opts = dict(faceName = defaultFace if defaultFace is not None else system_font.FaceName, + pointSize = defaultSize if defaultSize is not None else system_font.PointSize, + style = wx.FONTSTYLE_NORMAL, + weight = wx.FONTWEIGHT_NORMAL, + underline = False, + family = wx.FONTFAMILY_DEFAULT) + + fontdesc = fontdesc.strip() + elems = fontdesc.split() + + # find the last number -- that will be the size. everything before becomes + # the face name, and everything after is treated as flags. + lastsize = -1 + for i, elem in enumerate(elems[::-1]): + if isint(elem): + lastsize = len(elems) - i - 1 # since the indices are reversed + break + + if lastsize == -1: + flagi = -1 + for i, elem in enumerate(elems): + if elem in fontweights or elem in fontstyles or elem in ('underline', 'underlined'): + flagi = i + break + + size = defaultSize if defaultSize is not None else system_font.PointSize + + if flagi != -1: + splitpoint = fontdesc.rfind(elems[flagi]) + facename = fontdesc[:splitpoint].strip() + flags = fontdesc[splitpoint:].strip() + else: + facename = fontdesc.strip() + flags = '' + else: + splitpoint = fontdesc.rfind(elems[lastsize]) + facename = fontdesc[:splitpoint].strip() + size = int(elems[lastsize]) + flags = fontdesc[splitpoint + len(elems[lastsize]):] + + if facename: + opts['faceName'] = facename + + opts['pointSize'] = int(size) + + # parse remaining flags + for elem in flags.split(): + elem = elem.lower() + + if elem in fontweights: + opts['weight'] = getattr(wx, 'FONTWEIGHT_' + elem.upper()) + elif elem in fontstyles: + opts['style'] = getattr(wx, 'FONTSTYLE_' + elem.upper()) + elif elem in ('underline', 'underlined'): + opts['underline'] = True + + o = opts + return wx.Font(o['pointSize'], o['family'], o['style'], o['weight'], + o['underline'], o['faceName']) + diff --git a/digsby/src/gui/skin/skintransform.py b/digsby/src/gui/skin/skintransform.py new file mode 100644 index 0000000..092b532 --- /dev/null +++ b/digsby/src/gui/skin/skintransform.py @@ -0,0 +1,223 @@ +''' + +Applies transformations to the skin tree before it goes to the application. + +''' + +from __future__ import division +from gui.toolbox import colorfor +import wx +from util.primitives import strlist, LowerStorage, autoassign +import traceback, sys +from gui.textutil import CopyFont,default_font +from logging import getLogger; log = getLogger('skintransform') + +from gui.skin import SkinException +from gui.skin.skinparse import makeBrush, makeImage, makeFont, makePadding +from gui.skin.skinobjects import Margins + +# the character to use for string substitution +SUBSIGIL = '$' + +def which_rule(k): + k = k.lower() + for rule in erules: + if k.endswith(rule): + return rule + +def subs_common(s, common): + ''' + >>> subs_common('$one $two $three', {'one': 'a', 'two': 'b', 'three': 'c'}) + 'a b c' + ''' + words = s.split() + + for i, word in enumerate(list(words)): + if word.startswith(SUBSIGIL): + val = common.get(word[1:].lower(), None) + if val is None: + continue + words[i] = val + + return ' '.join(unicode(w) for w in words) + + +def transform(skintree, do_string_subs = True): + + if do_string_subs: + common = skintree.setdefault('common', {}) + else: + common = {} + + # + # replace all $variables with their values from the "common" root key + # + # todo: replace only the word...problem is numbers + # + def apply_string_substitution(elem): + if isinstance(elem, dict): + for k, v in elem.iteritems(): + + if isinstance(v, basestring): + elem[k] = subs_common(v, common) + else: + elem[k] = apply_string_substitution(v) + elif isinstance(elem, list): + newlist = [] + for e in elem: + if isinstance(e, basestring): + e = subs_common(e, common) + + newlist.append(e) + elem[:] = newlist + return elem + + # + # apply all skin transformation rules defined in functions in this file + # + def recursive_transform(elem): + if isinstance(elem, dict): + for k, v in elem.iteritems(): + if k in rules: + elem[k] = rules[k](v) + elif (not k.lower() in erule_excludes) and k.lower().endswith(tuple(erules.keys())): + elem[k] = erules[which_rule(k)](v) + else: + recursive_transform(v) + return elem + + if do_string_subs: + return recursive_transform(apply_string_substitution(skintree)) + else: + return recursive_transform(skintree) + + + +# r_item means the key has to EQUAL "item" +# er_item means the kye has to END WITH "item" + +def r_backgrounds(v): + if v is None: + return v + + for key, value in v.iteritems(): + v[key] = makeBrush(value) + + return v + +def r_background(v): + return makeBrush(v) + +def r_frame(v): + return makeBrush(v) + +def r_size(s): + return makePadding(s) + +def r_framesize(s): + return Margins(s) + +def er_margins(s): + return Margins(s) + +def er_padding(s): + return makePadding(s) + +def er_colors(v): + for colorname, color in v.iteritems(): + v[colorname] = colorfor(color) + return v + +def er_color(v): + return colorfor(v) + +def er_icons(v): + from gui import skin + for iconname, iconpath in v.iteritems(): + v[iconname] = skin.load_bitmap(iconpath) + + return v + +def er_icon(v): + from gui import skin + if v.startswith('skin:'): + return skin.get(v[5:], v) + + bitmap = skin.load_bitmap(v) + if not bitmap.Ok(): + raise SkinException('invalid bitmap %s' % v) + else: + return bitmap + +def er_images(v): + for key, value in v.iteritems(): + v[key] = makeImage(value) + return v + +def er_image(v): + return makeBrush(v) + + +def r_directionicons(v): + from gui import skin as skincore; img = skincore.load_image + up = img(v.up) if 'up' in v else None + down = img(v.down) if 'down' in v else None + left = img(v.left) if 'left' in v else None + right = img(v.right) if 'right' in v else None + + count = len(filter(None, [up, down, left, right])) + + if count==2 and not (left and right or up and down): + if not left: left = right.Mirror(True) + if not right: right = left.Mirror(True) + if not up: up = down.Mirror(False) + if not down: down = up.Mirror(False) + elif count!=4: + while not (up and down and left and right): + if not up: up = left.Rotate90() + if not right: right = up.Rotate90() + if not down: down = right.Rotate90() + if not left: left = down.Rotate90() + + + return LowerStorage({'up': wx.BitmapFromImage(up), + 'down': wx.BitmapFromImage(down), + 'left': wx.BitmapFromImage(left), + 'right': wx.BitmapFromImage(right)}) + +def r_color(v): + return wx.Brush(colorfor(v)) + + +def er_fonts(v): + for key, value in v.iteritems(): + v[key] = r_font(value) + + return v + +def r_fontcolor(v): + return colorfor(v) + +def r_font(v): + return makeFont(v) + +# Make a "rules" dictionary with all the functions in this module that begin +# with r_ (the r_ is chopped off for the keys in the dictionary, the values +# are the callable functions themselves) + +erule_excludes=[ + 'emoticon', + 'emoticons', + 'buddyicons', +] + +rules = {} +for func in locals().values(): + if callable(func) and func.__name__.startswith('r_'): + rules[func.__name__[2:]] = func + +erules={} + +for func in locals().values(): + if callable(func) and func.__name__.startswith('er_'): + erules[func.__name__[3:]] = func diff --git a/digsby/src/gui/skin/skintree.py b/digsby/src/gui/skin/skintree.py new file mode 100644 index 0000000..5ee25fb --- /dev/null +++ b/digsby/src/gui/skin/skintree.py @@ -0,0 +1,555 @@ +''' + +Manages a tree of skin objects used for drawing GUI elements + +''' + +from __future__ import with_statement + +# options for skin loading + +SKIN_FILENAME = 'skin.yaml' # base skin files must be named this +VARIANT_DIR = 'Variants' # variant skin files must appear in a subdirectory named this +SKIN_NAME_KEY = 'name' # skins can name themselves with this key +VARIANT_NAME_KEY = 'variant' # variants can name themselves with this key + +# skin elements at these keys will use the "alternating list" method +# instead of stacking skin elements + +alternating_keys = ['buddiespanel.backgrounds.buddy', + 'filetransfers.backgrounds.normal'] + +# factor to multiply font sizes by -- strings are "createdon_runningon" +# and are specified by FONT_MULTIPLY_KEY +FONT_MULTIPLY_KEY = 'FontPlatform' +FONT_MULTIPLY = {'windows_mac': 4.0/3.0, + 'mac_windows': 3.0/4.0} + + + +__metaclass__ = type +from operator import isMappingType +from util.primitives import Storage, traceguard, dictrecurse +S = Storage +from path import path +from copy import deepcopy +#import config +#import hooks +import syck +import sys, os, wx +from wx import GetTopLevelWindows, GetApp, MessageBox + +from traceback import print_exc +from gui.toolbox import colorfor +from types import FunctionType +from util.merge import merge, merge_keys, tolower +from util.primitives import syck_error_message +from util.introspect import funcinfo + +from gui.skin.skintransform import transform +from gui.skin import SkinException +from peak.util.plugins import Hook + +from logging import getLogger; log = getLogger('skin'); info = log.info + +import stdpaths + + +activeTree = None + +# the only state +resource_paths = [path('res').abspath(), + path('res/skins/default').abspath()] + +try: + userdata = stdpaths.userdata +except AttributeError: + log.warning('not including stdpaths.userdata in skin lookup paths') +else: + resource_paths.append(userdata / 'skins') + del userdata + +def get(dottedpath, default = sentinel): + global activeTree + + try: + return lookup(activeTree, dottedpath.lower().split('.')) + except (KeyError, TypeError): + if default is sentinel: + raise SkinException('not found: ' + dottedpath) + else: + return default + except: + print_exc() + print >> sys.stderr, 'exception for "%s"' % dottedpath + +def refresh_wx_tree(): + ''' + Calls "UpdateSkin" on every GUI control that has it, descending from top + level windows down through their children. + ''' + + # UMenus aren't necessarily wx Children accessible + # through the widget tree--skip them on the tree descent + from gui.uberwidgets.umenu import UMenu + skip = (UMenu, ) + + for window in GetTopLevelWindows(): + with window.FrozenQuick(): + _updateskin(window, skip) + + # now update menus + UMenu.CallAll(UMenu.UpdateSkin) + + +def _updateskin(window, skip): + ''' + Calls window.UpdateSkin() unless isinstance(window, skip), and then + does the same thing recursively on window's children. + ''' + + try: + # don't incur the hasattr() cost for every GUI element. + update = window.UpdateSkin + except AttributeError: + pass + else: + if not isinstance(window, skip): + try: update() + except: print_exc() + + for c in window.Children: + _updateskin(c, skip) + +def alternating(tree): + 'Makes some skin elements "alternating" lists.' + + from gui.skin.skinobjects import SkinList + + for k in alternating_keys: + spath = k.split('.') + try: + val = lookup(tree, spath) + except AttributeError: + pass + else: + if isinstance(val, list): + lookup(tree, spath[:-1])[spath[-1]] = SkinList(val) + +def fontplatform(finaltree): + from gui import skin + from config import platformName + + platform_aliases = {'win': 'windows', + 'mac': 'mac'} + + fontplatform = finaltree.get(FONT_MULTIPLY_KEY.lower(), None) + + fp = '%s_%s' % (fontplatform, platform_aliases.get(platformName)) + + factor = FONT_MULTIPLY.get(fp, 1) + log.info('new font multiply factor: %s -> %s', fp, factor) + skin.font_multiply_factor = factor + +pretransforms = [fontplatform] +posttransforms = [alternating] + +_loaded_fonts = set() + +def skin_font_files(paths): + fonts = [] + for p in paths: + fontsdir = p.parent / 'fonts' + if fontsdir.isdir(): + for ext in ('ttf', 'fon'): + fonts.extend(fontsdir.files('*.' + ext)) + + return [f.abspath().normpath() for f in fonts] + +def load_skinfonts(paths): + import config + if not config.platform == 'win': + return + + from gui.native.win.winfonts import loadfont, unloadfont + + fonts = set(skin_font_files(paths)) + + global _loaded_fonts + + if _loaded_fonts == fonts: + return + + # unload old + for fontfile in _loaded_fonts - fonts: + with traceguard: + res = unloadfont(fontfile, enumerable=True) + log.debug('unloaded font %r: %r', fontfile, res) + + # load new + for fontfile in fonts - _loaded_fonts: + with traceguard: + res = loadfont(fontfile, enumerable=True) + log.debug('loading font %r: %r', fontfile, res) + + _loaded_fonts = fonts + + import wx.webview + if hasattr(wx.webview.WebView, 'InvalidateFontCache'): + log.debug('invalidating webkit font cache') + wx.webview.WebView.InvalidateFontCache() + +def set_active(skin, variant = None, update_gui = False, callback = None): + 'Changes the active skin.' + log.info('set_active(%r, %r)', skin, variant) + app = GetApp() + global activeTree + + # Search all resource_paths for skin + skinname = skin + for rp in resource_paths: + skin = path(rp) / 'skins' / skinname + skinpath = skin / SKIN_FILENAME + if skinpath.isfile(): + break + import hooks + hooks.notify('skin.set.pre', skin, variant) + + # Find the variant file, if specified. + if variant is not None: + variant = skin / VARIANT_DIR / variant + '.yaml' + + default_path = resource_paths[0] / 'skins' / 'default' / SKIN_FILENAME + + paths = [default_path] + insert_position = 1 + + if os.path.basename(skin) == 'native': + paths.append(resource_paths[0] / path('skins') / 'silverblue' / SKIN_FILENAME) + + if not skinpath.isfile(): + log.critical('cannot find %r (%r.isfile() == False, defaulting to silverblue', skin, skinpath) + skin = resource_paths[0] / path('skins') / 'silverblue' + skinpath = skin / SKIN_FILENAME + variant = None + + if default_path.abspath() != skinpath.abspath(): + paths.append(skinpath) + if variant is not None: + if variant.isfile(): + paths.append(variant) + else: + log.warning('cannot find variant %r for skin %r', variant, skin) + + if not update_gui and hasattr(app, 'skin') and \ + app.skin.paths == paths: + log.info('skin did not change, returning') + + with traceguard: + load_skinfonts(paths) + + # load YAML from disk + log.info('loading YAML from %d path(s):\n %r', len(paths), '\n '.join(paths)) + + trees = get_skintrees(skin, paths, insert_position) + + # Ignore AppDefaults in all skins except default + for tree in trees[1:]: + tree.pop('appdefaults', None) + + # copy the default tree in case there is an exception later + default_tree = deepcopy(trees[0]) + + # merge keys from different trees into one (case insensitive) + combined_tree = merge(*trees, **dict(keytransform = lambda k: getattr(k, 'lower', lambda k: k)())) + + # create a skin storage object (transformation below depends on this being + # in the app) + if not hasattr(app, 'skin'): + app.skin = S() + + app.skin.update(path = skinpath.parent, paths = get_image_load_paths(paths)) + + for pretransform in pretransforms: + pretransform(combined_tree) + + # do skin transformation, creating images, brushes, etc. + try: + finaltree = transform(combined_tree) + except Exception, e: + MessageBox('There was an error processing skin "%s":\n\n%s' % (skin, str(e)), + 'Skin Error') + + # on an exception try loading the default tree + print_exc() + finaltree = transform(default_tree) + + for posttransform in posttransforms: + posttransform(finaltree) + + # actually place the new skintree + activeTree = finaltree + app.skin.update(tree = finaltree) + + def done(): + # refresh the GUI if requested + if update_gui: + refresh_wx_tree() + + if callback is not None: + with traceguard: + callback() + + # HACK: collect garbage after a skin change + import gc + gc.collect() + + wx.CallAfter(done) + return app.skin + +def list_skins(): + 'Returns paths to all available skin.yaml files.' + + resource_paths = sys.modules['gui.skin.skintree'].resource_paths + skins = [] + + for res in resource_paths: + skinrootdir = res / 'skins' + + if skinrootdir.isdir(): + for skindir in skinrootdir.dirs(): + if skindir.name.startswith('.'): continue + + rootfile = skindir / SKIN_FILENAME + + if rootfile.isfile() and skindir.name != 'default': + skins.append(skindesc(rootfile)) + + return skins + +def get_skintrees(skin, paths, insert_position): + ''' + Returns the separate mappings to be merged into one final skin later. + + paths is a list of skin YAML files. + ''' + trees = [] + errors = [] + successful = False + for f in paths: + try: + trees.append(load_skinfile(f)) + except Exception, e: + errors.append(e) + del e + else: + successful = True + + if not successful: + MessageBox('There was an error loading skin "%s":\n\n%s' % (skin, '\n'.join(str(e) for e in errors)), + 'Skin Error') + + # trees can also be supplied via Hook + from gui.skin import SkinStorage + to_skinstorage = dictrecurse(SkinStorage) + + for hook in Hook('digsby.skin.load.trees'): + hook_trees = hook() + + if hook_trees is not None: + # transform all mappings provided by plugins into SkinStorage + # objects + # TODO: why is dictrecurse not a deepcopy? + trees[insert_position:insert_position] = (to_skinstorage(deepcopy(t)) for t in hook_trees) + + return trees + + +def get_image_load_paths(paths): + 'Returns a list of paths from which skin images can be loaded.' + + image_load_paths = [p.parent for p in paths] + + for pathhook in Hook('digsby.skin.load.skinpaths'): + hook_paths = pathhook() + if hook_paths is not None: + image_load_paths.extend(hook_paths) + + return image_load_paths + +def quick_name_lookup(p, **names): + ''' + Given a path object to a skin YAML file, and "name keys", returns the values of those keys. + + The names must be on the highest level of key-value pairs. + + Returns a mapping of {names: values} + ''' + + #TODO: do we even need to parse the file here? + + names = dict((name.lower(), key) for key, name in names.iteritems()) + vals = {} + + with p.open('r') as f: + s = syck.load(f) + for k, name in getattr(s, 'iteritems', lambda: s)(): + try: + k = k.lower() + except AttributeError: + pass + else: + if k in names and isinstance(name, basestring): + vals[k] = name + if len(vals) == len(names): + break + return vals + +def skindesc(rootfile): + 'Returns a Storage for each skin found.' + + rootfile = path(rootfile) + aliases = quick_name_lookup(rootfile, skin_alias = SKIN_NAME_KEY, novariant_alias = VARIANT_NAME_KEY) + variants = [] + + # search the skin directory + vardir = rootfile.parent / VARIANT_DIR + if vardir.isdir(): + for variant in vardir.files('*.yaml'): # res/skins/myskin/variant3.yaml + if variant != rootfile: + valias = quick_name_lookup(variant, variant_alias = VARIANT_NAME_KEY).get('variant_alias', variant.namebase) + assert isinstance(valias, basestring) + variants.append(S(path = variant, + alias = valias)) + + return S(name = rootfile.parent.name, + alias = aliases.get(SKIN_NAME_KEY, rootfile.parent.name), + novariant_alias = aliases.get(VARIANT_NAME_KEY, _('(none)')), + base = rootfile, + variants = variants) + + +def lookup(root, pathseq): + 'Search a dictionary using a dotted path.' + + elem = root + for p in pathseq: + elem = elem[p] + + return elem + + +def load_skinfile(filepath): + if not filepath.isfile(): + raise ValueError('file %s does not exist' % filepath) + + return globals()['load_%s' % filepath.ext[1:]](filepath) + +def load_yaml(str_or_path): + from gui.skin import SkinStorage + + bytes, fpath = getcontent(str_or_path) + if isinstance(bytes, unicode): + bytes = bytes.encode('utf-8') + + if not bytes: + raise SkinException('no bytes in ' + str_or_path) + + content = load_yaml_content(bytes) + + try: + root = syck.load(content) + except syck.error, e: + raise SkinException(syck_error_message(e, fpath)) + + return merge_keys(root, maptype=SkinStorage) + +def load_yaml_content(bytes, included_paths = None): + if included_paths is None: + included_paths = [] + + # process includes + lines = [] + for line in bytes.split('\n'): + if line.lower().startswith('include:'): + include_path = line[line.index(':') + 1:].strip() + line = load_yaml_include(include_path, included_paths) + + lines.append(line) + + return '\n'.join(lines) + +def load_yaml_include(incpath, included_paths): + ''' + Returns bytes for an include. + + incpath include path to another YAML file + + included_paths a sequence of all already included (absolute) paths that + will be added to by this function + ''' + + incpath = path(incpath).abspath() + + if not incpath in included_paths: + bytes = load_yaml_content(incpath.bytes(), included_paths) + included_paths += [incpath] + return bytes + else: + # including a file twice is a no-op + return '' + +from util.data_importer import zipopen +from contextlib import closing +def getcontent(str_or_path): + try: + if str_or_path.isfile(): + return str_or_path.bytes(), str_or_path + with closing(zipopen(str_or_path)) as f: + return f.read(), str_or_path + except AttributeError: + return str_or_path, '' + + +if __name__ == '__main__': + s = '''common: +- &mycolor red + +menu: + color: *mycolor + +common: +- &mycolor blue''' + + print load_yaml(s) + + +if __name__ == '_AFDASF': + + import wx + a = wx.PySimpleApp() + + sys.modules['gui.skin.skintree'].resource_paths = [path('../../../res')] + skins = list_skins() + + if not skins: sys.exit() + + set_active(skins[1]) + + with file('test.yaml', 'w') as f: + f.write('''\ +button: + color: red''') + + s = ''' +include: test.yaml +include: test.yaml + +Button: + font: Arial + +button: + Font: Comic Sans MS +''' + + #assert load_yaml(s) == {'button': {'font': 'Comic Sans MS'}} diff --git a/digsby/src/gui/skindow.py b/digsby/src/gui/skindow.py new file mode 100644 index 0000000..a1afc37 --- /dev/null +++ b/digsby/src/gui/skindow.py @@ -0,0 +1,155 @@ +''' +skindow.py + +Skinned windows. +''' + +import wx, util +from gui import skin, MultiImage +from gui.toolbox import rect_with_negatives + +class Skindow( wx.Frame ): + + framestyles = {True: wx.FRAME_SHAPED | wx.SIMPLE_BORDER, + False: wx.DEFAULT_FRAME_STYLE} + + def __init__(self, parent, name, content_panel = None, use_skin = True, **kws ): + if parent is not None and not isinstance(parent, wx.Window): + raise TypeError("first argument to Skindow __init__ is a wxWindow parent") + + wx.Frame.__init__( self, parent, style = self.framestyles[use_skin], **kws ) + + self.margins = skin.get('%s.content' % name) + self.content_pane = wx.Panel(self) + self.sizer = wx.BoxSizer() + self.content_pane.SetSizer(self.sizer) + + if not isinstance(name, (str, unicode)): + raise TypeError('second argument name must be a string') + self.name = name + + self.skin_events = [ + ( wx.EVT_SIZE, self.on_size ), + ( wx.EVT_PAINT, self.on_paint ), + ( wx.EVT_LEFT_UP, self.OnLeftUp ), + ( wx.EVT_LEFT_DOWN, self.on_left_down ), + ( wx.EVT_MOTION, self.on_motion ), + ( wx.EVT_ERASE_BACKGROUND, lambda e: None ), # reduce flickering + ] + + self.Bind(wx.EVT_CLOSE, lambda e: self.Destroy()) + + self.set_skinned(use_skin) + + def set_skinned(self, skinned = True): + if skinned: + self.multi = skin.get('%s.images' % self.name) + + [self.Bind( e, m ) for e, m in self.skin_events] + self.cdc = wx.ClientDC( self ) + self.sizing = False + self.on_size() + self.SetWindowShape() + else: + [self.Unbind(e) for e,m in self.skin_events] + self.margins = (0,0,-1,-1) + + if self.GetWindowStyleFlag() == self.framestyles[True]: + self.SetShape(wx.Region().Union(self.content_pane.GetRect())) + + self.SetWindowStyleFlag( self.framestyles[skinned] ) + self.Refresh() + + def get_skinned(self): + return self.GetWindowStyleFlag() == self.framestyles[True] + + skinned = property(get_skinned, set_skinned) + + def set_content(self, content): +# if self.content: self.sizer.Remove(self.content) +# self.sizer.Add(content) +# self.content = content + self.Refresh() + + def on_size( self, e=None ): + m = rect_with_negatives(self.margins, self.GetSize()) + self.content_pane.SetPosition(m[:2]) + self.content_pane.SetSize(m[2:]) + #self.Update() + #self.Refresh() + if e: e.Skip( True ) + + def on_paint( self, e=None ): + dc = wx.AutoBufferedPaintDC( self ) + self.multi.Draw(dc, wx.Rect(0,0,*self.GetClientSizeTuple())) + self.SetShape(self.multi.region) + + def SetWindowShape( self, *evt ): + # Use the bitmap's mask to determine the region + new = (w, h) = self.GetClientSizeTuple() + + if hasattr(self.multi, 'region'): + print '%r has a region: %r' %(self.multi, self.multi.region) + self.hasShape = self.SetShape( self.multi.region ) + + def on_left_down( self, evt ): + self.CaptureMouse() + if 'dragger' in self.multi and wx.Rect( *self.multi.tag_rect( 'dragger' ) ).Contains( evt.GetPosition() ): + self.orig = self.ClientToScreen( evt.GetPosition() ) + self.origsize = self.GetSize() + self.click_state = 'resizing' + else: + self.click_state = 'moving' + x, y = self.ClientToScreen( evt.GetPosition() ) + originx, originy = self.GetPosition() + self.delta = ( ( x - originx, y - originy ) ) + + + def OnLeftUp( self, evt ): + self.on_size() + if self.HasCapture(): + self.ReleaseMouse() + + def on_motion( self, evt ): + + if 'dragger' in self.multi.tags: + if wx.Rect( *self.multi.tag_rect( 'dragger' ) ).Contains( evt.GetPosition() ): + self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENWSE)) + else: + self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + + if evt.Dragging() and evt.LeftIsDown(): + x, y = self.ClientToScreen( evt.GetPosition() ) + if self.click_state == 'resizing': + self.method_sizing() + if not self.sizing: + self.sizing = True + dx, dy = x - self.orig[0], y - self.orig[1] + self.SetSize( ( self.origsize[0] + dx, self.origsize[1] + dy ) ) + self.sizing = False + elif self.click_state == 'moving': + fp = (x - self.delta[0], y - self.delta[1]) + self.Move( fp ) + + def method_sizing(self): + pass + +if __name__ == '__main__': + import util + import windowfx + + + app = wx.PySimpleApp() + from gui import skininit + skininit('../../res','halloween') + + + frame = Skindow( None,'IMWin', title='Skindow Test' ) + + from uberwidgets.UberBook import SamplePanel + frame.set_content(SamplePanel(frame.content, 'orange')) + + + windowfx.fadein( frame ) + app.SetTopWindow( frame ) + util.profile(app.MainLoop) diff --git a/digsby/src/gui/snap.py b/digsby/src/gui/snap.py new file mode 100644 index 0000000..1ea76bd --- /dev/null +++ b/digsby/src/gui/snap.py @@ -0,0 +1,63 @@ +''' +Window snapping. + +>> f = wx.Frame(None, -1, 'My Frame') +>> f.Snap = True + +TODO: +- Mac/Linux implementations +''' + +import wx + +if 'wxMSW' in wx.PlatformInfo: + from cgui import WindowSnapper as Snapper +else: + + # + # Other platforms: not implemented. + # + + class Snapper(object): + def __init__(self, frame, *a, **k): pass + def SetDocked(self, val): pass + def SetEnabled(self, val): pass + def IsEnabled(self): return False + +def SetSnap(win, snapping, filter_func = None): + if not isinstance(snapping, bool): + raise TypeError('SetSnap first argument must be a bool, got type %r' % type(snapping)) + + @wx.CallAfter + def later(): + try: + snapper = win._snapper + except AttributeError: + snapper = win._snapper = Snapper(win, 12, snapping) + + # have the Docker object inform us when we're docked, so that the + # two objects don't interfere with another. + docker = getattr(win, 'docker', None) + if docker is not None: + docker.OnDock += win._snapper.SetDocked + + else: + win._snapper.SetEnabled(snapping) + +def GetSnap(win): + try: + snapper = win._snapper + except AttributeError: + return False + else: + return snapper.IsEnabled() + +# +# Add a "Snap" property to TopLevelWindow. +# + +import new +wx.TopLevelWindow.GetSnap = new.instancemethod(GetSnap, None, wx.TopLevelWindow) +wx.TopLevelWindow.SetSnap = new.instancemethod(SetSnap, None, wx.TopLevelWindow) +wx.TopLevelWindow.Snap = property(GetSnap, SetSnap) + diff --git a/digsby/src/gui/social_status_dialog.py b/digsby/src/gui/social_status_dialog.py new file mode 100644 index 0000000..0cca09f --- /dev/null +++ b/digsby/src/gui/social_status_dialog.py @@ -0,0 +1,1410 @@ +from common import pref +from gui.textutil import default_font +if __name__ == '__main__': + import Digsby + __builtins__._ = lambda s:s + +from gui.uberwidgets.umenu import UMenu + +import logging + +import weakref +import os.path +import wx +import util +import util.net as net +import util.callbacks as callbacks +import common +import gui.anylists +import gui.accountslist +from cgui import InputBox +import gui.spellchecktextctrlmixin as SCTCM +import gui.toolbox + +_BORDER = 3 + +log = logging.getLogger('gui.socialstatus') + +_msg_color_thresholds = ( + (129, (0x00, 0x00, 0x00)), + (140, (0xD8, 0x80, 0x00)), + (141, (0xFF, 0x00, 0x00)), + ) + +def acct_name_to_key(proto, name): + return '%r // %r' % (proto, name) + +class ParentHasControllerMixin(object): + def get_controller(self): + p = self + c = getattr(self, 'controller', None) + + while p is not None and c is None: + p = p.Parent + c = getattr(p, 'controller', None) + + return c + +PHCM = ParentHasControllerMixin + +class PanelWithController(wx.Panel, PHCM): + pass + +def color_for_message(message, thresholds): + mlen = len(message) + + low = -1 + for (high, color) in thresholds: + if low < mlen <= high: + return color + low = high + + return color + +ID_REDO = wx.NewId() +class SpellcheckedTextCtrl(InputBox, SCTCM.SpellCheckTextCtrlMixin): + def __init__(self, parent, + id = wx.ID_ANY, + value = '', + pos = wx.DefaultPosition, + size = wx.DefaultSize, + style = 0, + validator = wx.DefaultValidator): + + InputBox.__init__(self, parent, id, value, pos, size, style, validator) + SCTCM.SpellCheckTextCtrlMixin.__init__(self) + + self.SetStyle(0, self.LastPosition, wx.TextAttr(wx.BLACK, wx.WHITE, default_font())) #@UndefinedVariable + + self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) + + gui.toolbox.add_shortened_url_tooltips(self) + + import hooks + hooks.notify('digsby.status_textctrl.created', self) + + def CanPaste(self): + # with wx.TE_RICH2 on windows, CanPaste() only returns True (and therefore sends EVT_TEXT_PASTE) + # when there's text in the clipboard, but we want to be able to paste images. + return True + + def OnContextMenu(self, event): + menu = UMenu(self) + gui.toolbox.maybe_add_shorten_link(self, menu) + self.AddSuggestionsToMenu(menu) + gui.toolbox.std_textctrl_menu(self, menu) + menu.PopupMenu() + + +class SetIMStatusEntry(object): + protocol = 'im-status' + #protocol = 'fake' + username = name = 'im-status' + display_name = _("Set IM Status") + icon_key = 'icons.SetIMStatus' + enabled = True + + def __init__(self, profile, editable = True, edit_toggle = True): + self.profile = profile + self.editable = editable + self.edit_toggle = edit_toggle + + @property + def serviceicon(self): + return gui.skin.get(self.icon_key) + + @callbacks.callsback + def SetStatusMessage(self, message, callback = None, **k): + log.debug('Calling SetStatusMessage: message = %r, editable = %r, edit_toggle = %r', message, self.editable, self.edit_toggle) + self.profile.SetStatusMessage(message, editable = self.editable, edit_toggle = self.edit_toggle) + callback.success() + +''' + + text_alignment = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL + + def get_icon(self, searchname): + from gui import skin + return skin.get('appdefaults.search.icons.' + searchname, None) + + def OnDrawItem(self, dc, rect, n): + VisualListEditorList.OnDrawItem(self, dc, rect, n) + icon = self.get_icon(self.GetItem(n).name) + if icon is None: + return + + # Draw search favicons on the right side of the list + dc.DrawBitmap(icon, rect.Right - icon.Width - 32, rect.VCenter(icon), True) + +''' + +def make_counter_ctrl(parent): + return wx.StaticText(parent, -1, str(0)) + +class TextFieldWithCounter(wx.Panel, PHCM): + def __init__(self, parent, label = None, size = (300, 55), initial_text = '', select_text = False, text_id = None, + counter_ctrl=None): + wx.Panel.__init__(self, parent) + self._text_id = text_id + self._select_text = select_text + self._label_text = label or '' + self._requested_size = size + if counter_ctrl is False: + self.ownscount = False + self.use_counter = False + self.charcount = None + else: + self.ownscount = counter_ctrl is None + self.use_counter = True + self.charcount = counter_ctrl + + self.construct(initial_text) + self.bind_events() + self.layout() + + if select_text: + self.SelectText() + + def bind_events(self): + self.input.Bind(wx.EVT_KEY_DOWN, self.on_key) + + def on_key(self, e): + if e.KeyCode == wx.WXK_RETURN and e.GetModifiers() not in (wx.MOD_CONTROL, wx.MOD_SHIFT): + self.Parent.ProcessEvent(e) + else: + e.Skip() + + def get_text_ctrl_class(self): + wxMSW = 'wxMSW' in wx.PlatformInfo + if wxMSW: + return SpellcheckedTextCtrl + else: + return wx.TextCtrl + + @property + def TextCtrl(self): + return self.input + + def construct(self, initial_text=''): + if self._label_text: + self.text_label = wx.StaticText(self, -1, self._label_text) + self.text_label.SetBold() + + if self.use_counter and self.ownscount: + self.charcount = make_counter_ctrl(self) + + input = self.input = self.get_text_ctrl_class()(self, self._text_id or -1, initial_text, + style = wx.TE_MULTILINE + | wx.TE_AUTO_SCROLL + | wx.TE_PROCESS_ENTER) + input.SetSizeHints(self._requested_size) + input.Bind(wx.EVT_TEXT, self.updated_charcount) + self.updated_charcount() + + if not self._select_text: + @wx.CallAfter + def unselect(): + input.InsertionPoint = input.LastPosition + + def updated_charcount(self, e=None): + newlen = len(self.GetTextValue()) + + if not self.use_counter: + return + + self.charcount.SetLabel(str(newlen)) + + color_tup = color_for_message(self.input.Value, _msg_color_thresholds) + color = wx.Color(*color_tup) + self.charcount.SetForegroundColour(color) + + if e is not None: + e.Skip() + self.Layout() + + def on_button(self, e): + if self.url_input.IsShown(): + val = self.url_input.Value + if not val: + return self.end_url_input() + if '://' not in val: + val = 'http://' + val + with self.Frozen(): + self.url_input.Enable(False) + self.ok.Enable(False) + self.tiny_url(val, success=lambda url: + wx.CallAfter(self.tiny_url_success, url), error=lambda *a, **k: + wx.CallAfter(self.tiny_url_error, *a, **k)) + else: + self.on_close(self.info) + + def on_cancel(self, e): + if e.Id == wx.ID_CANCEL: + if e.EventObject is self.cancel and self.url_input.IsShown(): + self.end_url_input() + else: + self.on_close(None) + else: + e.Skip() + + @property + def info(self): + # replace vertical tabs that may have been inserted by shift+enter + msg_txt = self.input.Value.replace("\x0b", "\n") + + return dict(message=msg_txt) + + def layout(self): + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + + if self._label_text or self.ownscount: + h = wx.BoxSizer(wx.HORIZONTAL) + if hasattr(self, 'text_label'): + h.Add(self.text_label, 0) + h.AddStretchSpacer() + h.AddStretchSpacer() + + if self.use_counter and self.ownscount: + h.Add(self.charcount, 0, wx.RIGHT, border = _BORDER) + + s.Add(h, 0, wx.EXPAND) + + s.Add(self.input, 1, wx.EXPAND | wx.TOP, _BORDER) + + s.Layout() + + def append_text(self, text): + self.input.SetInsertionPointEnd() + self.insert_text(text) + + def insert_text(self, text): + + textctrl = self.input + ip = textctrl.InsertionPoint + + if ip != 0 and textctrl.Value[ip-1] and textctrl.Value[ip-1] != ' ': + textctrl.WriteText(' ') + + textctrl.WriteText(text + ' ') + self.focus_text() + + def focus_text(self): + self.input.SetFocus() + + def GetTextValue(self): + return self.input.Value + + def SetTextValue(self, txt): + self.input.Value = txt + + def SelectText(self): + self.input.SelectAll() + +class SocialStatusRow(gui.accountslist.SocialRow, PHCM): + def __init__(self, parent, *a, **k): + gui.accountslist.SocialRow.__init__(self, parent, *a, **k) + self.Bind(wx.EVT_CHECKBOX, self.on_account_checked) + + @property + def image(self): + img = getattr(self.data, 'serviceicon', None) or gui.skin.get("serviceicons." + self.data.protocol, None) + return img.Resized(16) if img else None + + def get_account_enabled(self): + return self.get_controller().get_account_enabled(self.account) + + def on_account_checked(self, e): + checked = e.EventObject.Value + self.get_controller().set_account_enabled(self.account, checked) + + e.Skip() + + def PopulateControls(self, account): + self.account = account + self.text = self.account.display_name + self.RefreshRowValue() + + def RefreshRowValue(self): + self.checkbox.Value = self.get_account_enabled() + +class SocialListEditor(gui.anylists.AnyList, PHCM): + SelectionEnabled = False + ClickTogglesCheckbox = True + + def __init__(self, parent, data): + gui.anylists.AnyList.__init__(self, parent, data, row_control = SocialStatusRow, draggable_items = False) + + self.Bind(wx.EVT_PAINT, self._on_paint) + + def GetSelectedAccounts(self): + return [row.data for row in self.rows if row.checkbox.Value] + + def RefreshValues(self): + for row in self.rows: + row.RefreshRowValue() + + wx.CallAfter(self.ScrollToFirstSelectedRow) + + def ScrollToFirstSelectedRow(self): + for row in self.rows: + if row.checkbox.Value: + break + else: + row = None + + if row is not None: + self.ScrollChildIntoView(row) + + def on_data_changed(self, *a): + self.data = self.get_controller().get_accounts() + gui.anylists.AnyList.on_data_changed(self, *a) + vheight = self.GetVirtualSize()[1] + self.SetSizeHints(wx.Size(self.Size[0], max(32, vheight))) + self.Top.FitInScreen() + self.UpdateWindowUI() + + def _on_paint(self,event): + event.Skip() + _x = wx.PaintDC(self) + + srect = wx.Rect(*self.Rect) + srect.Inflate(1,1) + pcdc = wx.ClientDC(self.Parent) + pcdc.Brush = wx.TRANSPARENT_BRUSH + + pcdc.Pen = wx.Pen(wx.Colour(213,213,213)) + + pcdc.DrawRectangleRect(srect) + +class AccountListPanel(wx.Panel, PHCM): + TOP_LABEL = _("Choose Accounts:") + TOP_LABEL_ID = wx.NewId() + + #VLE.VisualListEditorList + + def __init__(self, parent, *a, **k): + wx.Panel.__init__(self, parent) + self.construct() + self.layout() + self.bind_events() + + def bind_events(self): + self.account_list.Bind(wx.EVT_CHECKBOX, self.on_checkbox) + + self.Top.Bind(wx.EVT_CLOSE, self.on_top_closed) + + def layout(self): + pass + + def get_accounts(self): + return self.get_controller().get_accounts() + + def construct(self): + self.account_list = account_list = SocialListEditor(self, self.get_accounts()) + self.account_list.SetMinSize((200, 48)) # TODO: go away numbers + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + if not use_minimal_layout(): + top_label = wx.StaticText(self, self.TOP_LABEL_ID, self.TOP_LABEL, style = wx.BOLD) + top_label.SetBold() + self.Sizer.Add(top_label, 0, wx.EXPAND | wx.ALL, _BORDER) + self.Sizer.Add(account_list, 1, wx.EXPAND | wx.ALL, _BORDER) + self.Layout() + + def on_checkbox(self, e): + e.Skip() + + def on_list_changed(self, enabled_items): + log.info('list was changed: %r', enabled_items) + + for account in self.get_accounts(): + self.set_account_enabled(account, account in enabled_items) + + def on_top_closed(self, e): + print 'calling "on_close" on %r' % self.account_list + self.account_list.on_close() + + def GetSelectedAccounts(self): + return self.account_list.GetSelectedAccounts() + + def RefreshAccountsList(self): + self.account_list.on_data_changed() + #self.account_list.RefreshValues() + + def ScrollAccountList(self): + self.account_list.ScrollToFirstSelectedRow() + +class InsertLinkPanel(wx.Panel, PHCM): + label = _('Insert Link') + icon_key = 'icons.insertlink' + URL_TEXT_ID = wx.NewId() + + def __init__(self, parent, *a, **k): + wx.Panel.__init__(self, parent, *a, **k) + self.construct() + + def on_enter(self, e = None): + self.ProcessEvent(wx.CommandEvent(wx.EVT_BUTTON, wx.ID_OK)) + + def construct(self): + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.stext = wx.StaticText(self, wx.NewId(), _('Website URL:')) + self.stext.SetBold() + + self.url_text = wx.TextCtrl(self, wx.ID_OK, style = wx.TE_PROCESS_ENTER) + + self.Sizer.Add(self.stext, 0, wx.EXPAND | wx.ALL, _BORDER) + self.Sizer.Add(self.url_text, 0, wx.EXPAND | wx.ALL, _BORDER) + if not use_minimal_layout(): + self.Sizer.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, _BORDER) + + self.ok_button = wx.Button(self, wx.ID_OK, _('Insert &Link')) + self.ok_button.SetDefault() + + self.cancel_button = wx.Button(self, wx.ID_CANCEL) + + btnsizer = wx.BoxSizer(wx.HORIZONTAL) + btnsizer.AddStretchSpacer() + btnsizer.Add(self.ok_button, 0, wx.ALIGN_RIGHT | wx.ALL, _BORDER) + btnsizer.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.ALL, _BORDER) + + self.Sizer.Add(btnsizer, 0, wx.EXPAND | wx.ALL) + self.FitInScreen() + self.Layout() + + @callbacks.callsback + def GetInfo(self, callback = None): + self.url_text.Enable(False) + full_url = self.url_text.Value + + def _success(url): + callback.success({'link':url}) + + def _error(e): + try: + raise e + except callbacks.CallbackStream.Cancel as c: + callback.success({}) + except Exception as e: + wx.CallAfter(self.url_text.Enable, True) + callback.error(e) + + if not full_url: + return _error(callbacks.CallbackStream.Cancel()) + + util.threaded(net.get_short_url)(full_url, + success = _success, + error = _error) + + def clear(self): + self.url_text.Enable(True) + self.url_text.Value = u'' + + def set_focus(self): + self.url_text.SetFocus() + +class InsertImagePanel(wx.Panel, PHCM): + label = _('Share Image') + icon_key = 'icons.insertimage' + FILE_PICKER_ID = wx.NewId() + def __init__(self, parent, *a, **k): + wx.Panel.__init__(self, parent, *a, **k) + self.current_transfer = None + self.construct() + self.bind_events() + self.clear() + + def set_focus(self): + pass + + def BeforeShow(self): + filename = gui.toolbox.pick_image_file(self.Top) + + if filename is not None: + wx.CallAfter(self.do_upload, filename) + return True + else: + return False + + def do_upload(self, filename): + self.enable_retry(False) + self._filename = filename + self.progress_bar.SetRange(os.path.getsize(filename)) + self.progress_bar.SetValue(0) + + file = open(filename, 'rb') + cb = callbacks.CallbackStream(file, self.file_progress, self.file_finished) + self.current_transfer = cb +# import urllib2 +# util.threaded(urllib2.urlopen)("http://www.google.com", (('file', cb),), +# success = self.on_upload_success, +# error = self.on_upload_error) + + import imagehost.imgur as imgur + api = imgur.ImgurApi() + api.upload(cb, + success = self.on_upload_success, + error = self.on_upload_error) + + def file_progress(self, howmuch): + if not wx.IsMainThread(): + return wx.CallAfter(self.file_progress, howmuch) + + self.progress_bar.SetValue(howmuch) + + def file_finished(self): + if not wx.IsMainThread(): + return wx.CallAfter(self.file_finished) + + self.current_transfer = None + + def _emit_ok(self): + e = wx.CommandEvent(wx.EVT_COMMAND_BUTTON_CLICKED, self.Id) + e.SetEventObject(self) + e.SetId(wx.ID_OK) + self.GetEventHandler().ProcessEvent(e) + + def bind_events(self): + self.Bind(wx.EVT_BUTTON, self.on_button) + + def on_button(self, e): + if e.Id == wx.ID_CANCEL: + if self.current_transfer is not None: + self.current_transfer.cancel() + elif e.Id == wx.ID_REDO: + self.retry_clicked(e) + + if e.Id != wx.ID_REDO: + e.Skip() + + def construct(self): + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.sttext = wx.StaticText(self, label = _("Uploading Image...")) + self.progress_bar = wx.Gauge(self, style = wx.GA_SMOOTH) + self.Sizer.Add(self.sttext, 0, wx.EXPAND | wx.ALL, _BORDER) + self.Sizer.Add(self.progress_bar, 0, wx.EXPAND | wx.ALL, _BORDER) + if not use_minimal_layout(): + self.Sizer.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, _BORDER) + +# self.ok_button = wx.Button(self, wx.ID_OK, _('Insert &Image')) +# self.ok_button.SetDefault() + + self.retry_button = wx.Button(self, wx.ID_REDO, label = _("Retry")) + self.cancel_button = wx.Button(self, wx.ID_CANCEL) + + btnsizer = wx.BoxSizer(wx.HORIZONTAL) + btnsizer.AddStretchSpacer() + btnsizer.Add(self.retry_button, 0, wx.ALIGN_RIGHT | wx.ALL, _BORDER) + btnsizer.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.ALL, _BORDER) + + + self.Sizer.Add(btnsizer, 0, wx.EXPAND | wx.ALL) + self.FitInScreen() + self.Layout() + + @callbacks.callsback + def GetInfo(self, callback = None): + self._info_requested = True + self._callback = callback + if self._got_response or self._got_error: + return self.emit_result(callback = callback) + + def emit_result(self, callback = None, response = None, exception = None): + if self._got_response: + cb, cb_args = self._emit_success(callback, response, exception) + elif self._got_error: + cb, cb_args = self._emit_error(callback, response, exception) + + cb(*cb_args) + + def _emit_success(self, callback, response, exception): + cb = (self._callback or callback).success + try: + cb_args = self._prepare_success(self._response or response) + except Exception, e: + return self._emit_error(callback, response, e) + + return cb, cb_args + + def _emit_error(self, callback, response, exception): + cb = (self._callback or callback).error + cb_args = self._prepare_error(self._exception or exception, self._response or response) + wx.CallAfter(self.enable_retry) + + return cb, cb_args + + def _prepare_success(self, resp): + return ({'link' : resp['url']},) + + def _prepare_error(self, e, response): + if response is not None: + status = response.get('status', {}) + if status.get('result') == 'ERROR': + return status.get('message', str(e)), + + return str(e), + + def on_upload_success(self, resp): + self._got_response = True + self._response = resp + + if self._info_requested: + return self.emit_result(response = resp) + self._emit_ok() + + def on_upload_error(self, e): + self._got_error = True + self._exception = e + if self._info_requested: + return self.emit_result(exception = e) + self._emit_ok() + + def enable_retry(self, enable = True): + self.retry_button.Enable(enable) + + def retry_clicked(self, e): + self.do_upload(self._filename) + + def on_trim_error(self, e, err_cb): + err_cb(e) + + def clear(self): + self._got_response = self._info_requested = self._got_error = False + self._response = self._callback = self._exception = None + +class TransparentStaticBitmap(wx.StaticBitmap): + def __init__(self, *a): + wx.StaticBitmap.__init__(self, *a) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Bind(wx.EVT_PAINT, self._paint) + + def _paint(self, e): + dc = wx.AutoBufferedPaintDC(self) + sz = self.ClientSize + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.Brush(self.BackgroundColour)) + dc.DrawRectangleRect(wx.RectS(sz)) + + bmp = self.GetBitmap() + if bmp.HasAlpha(): + bmp.UseAlpha() + + dc.DrawBitmap(bmp, 0, 0, True) + + return dc + +class StatusToolbar(wx.Panel, PHCM): + def __init__(self, parent, **options): + self._options = options + wx.Panel.__init__(self, parent) + self._tool_ids = {} + + self.construct() + + def construct(self): + self.Sizer = wx.BoxSizer(wx.HORIZONTAL) + + if self._options.get('counter', False): + self.charcount = make_counter_ctrl(self) + self.Sizer.Add(self.charcount, 0, wx.EXPAND | wx.ALL, _BORDER) + + self.Sizer.AddStretchSpacer() + + # create hyperlinks without context menus + hl_style = wx.HL_DEFAULT_STYLE & ~wx.HL_CONTEXTMENU + + for toolname, tool in self._options.get('tools', {}).items(): + id = self._tool_ids[toolname] = wx.NewId() + lnk = wx.HyperlinkCtrl(self, id, tool.label, url = toolname, + style = hl_style) + #lnk.Bind(wx.EVT_HYPERLINK, self.on_link_click) + lnk.SetVisitedColour(lnk.GetNormalColour()) + + icon = TransparentStaticBitmap(self, -1, gui.skin.get(tool.icon_key)) + self.Sizer.Add(icon, 0, wx.ALIGN_RIGHT | wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border = _BORDER * 3) + self.Sizer.AddSpacer(3) + self.Sizer.Add(lnk, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL) + + self.Layout() + +class FauxNotebook(wx.Panel, PHCM): + def __init__(self, parent): + self.current_shown = None + wx.Panel.__init__(self, parent) + self.panels = {} + self.panel_list = [] + self.Sizer = wx.BoxSizer(wx.VERTICAL) + + def AddPanel(self, name, panel): + self.panels[name] = panel + self.panel_list.append(panel) + self.Sizer.Add(panel, 1, wx.EXPAND | wx.ALL, )# _BORDER) + + if len(self.panel_list) == 1: + self.ShowPanel(name) + else: + panel.Hide() + + def ShowPanel(self, name, do_precheck = True): + old_panel = self.GetPanel() + new_panel = self.panels[name] + + self.current_shown = name + + if (not do_precheck) or getattr(new_panel, 'BeforeShow', lambda: True)(): + if old_panel is not None: + getattr(old_panel, 'BeforeHide', Null)() + old_panel.Show(False) + new_panel.Show(True) + else: + e = wx.CommandEvent(wx.EVT_COMMAND_BUTTON_CLICKED, self.Id) + e.SetEventObject(new_panel) + e.SetId(wx.ID_CANCEL) + self.GetEventHandler().ProcessEvent(e) + + new_panel.set_focus() + self.Top.FitInScreen() + + def GetPanel(self, name = None): + if name is None: + name = self.current_shown + + return self.panels.get(name) + +class ErrorPanel(wx.Panel): + TITLE_TEXT = _("Errors Encountered:") + def __init__(self, *a, **k): + wx.Panel.__init__(self, *a, **k) + + self.construct() + self.layout() + + def construct(self): + self.title_text = wx.StaticText(self, -1) + self.title_text.SetLabel(self.TITLE_TEXT) + self.title_text.SetBold() + self.title_text.SetForegroundColour(wx.Color(255,0,0)) + + self.column1_text = wx.StaticText(self, -1) + self.column1_text.SetBold() + self.column1_text.SetForegroundColour(wx.Color(255,0,0)) + + self.column2_text = wx.StaticText(self, -1) + self.column2_text.SetForegroundColour(wx.Color(255,0,0)) + + def layout(self): + self.Sizer = wx.FlexGridSizer(2, 2) + self.Sizer.Add(self.title_text, 0, wx.ALL, _BORDER) + self.Sizer.AddStretchSpacer() + self.Sizer.Add(self.column1_text, 0, wx.ALL, _BORDER) + self.Sizer.Add(self.column2_text, 1, wx.ALL, _BORDER) + + def set_errors(self, texts): + col1_buffer = [] + col2_buffer = [] + for col1, col2 in texts: + col1_buffer.append(col1) + col2_buffer.append(col2) + + self.column1_text.SetLabel('\n'.join(col1_buffer)) + self.column2_text.SetLabel('\n'.join(col2_buffer)) + +def use_minimal_layout(): + return common.pref('global_status_dialog.minimal', False) + +class GlobalStatusDialog(wx.Dialog): + TOOLS = util.odict([ + ('link', InsertLinkPanel), + ('image', InsertImagePanel), + ], + ) + + @classmethod + def add_tool(cls, toolname, toolinfo): + cls.TOOLS[toolname] = toolinfo + + def __init__(self, controller, + parent = None, + title = _('Set Global Status'), + frame_icon_key = 'icons.globalstatus', + on_close = None, + **options): + + self.controller = controller + self._options = options + + style = (wx.DEFAULT_FRAME_STYLE) & ~(wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX)# | wx.RESIZE_BORDER) + wx.Dialog.__init__(self, parent, title = title, style = style) + + self.tools = util.odict() + + self.SetFrameIcon(gui.skin.get(frame_icon_key)) + self.construct() + self.layout() + self.bind_events() + gui.toolbox.persist_window_pos(self, close_method = on_close, position_only = True) + + + def SetText(self, txt): + self.text_panel.SetTextValue(txt) + + def SelectText(self): + self.text_panel.SelectText() + + def RefreshAccountsList(self): + self.accountslist_panel.RefreshAccountsList() + + def ScrollAccountsList(self): + self.accountslist_panel.ScrollAccountList() + + def get_controller(self): + return self.controller + + def construct(self): + + self.content = FauxNotebook(self) + + self.error_panel = ErrorPanel(self) + self.error_panel.Show(False) + self._text_id = wx.NewId() + + self.accounts_panel = PanelWithController(self.content) + + for toolname in self.TOOLS: + self.tools[toolname] = self.TOOLS[toolname](self.content) + + minimal_layout = use_minimal_layout() + + toolbar_panel = StatusToolbar(self.accounts_panel, tools = self.tools, counter=minimal_layout) + + text_field_label = None if minimal_layout else (self._options.get('text_panel_label') or _('Your Status:')) + self.text_panel = TextFieldWithCounter(self, text_id = self._text_id, + label = text_field_label, + initial_text = self._options.get('initial_text') or u'', + select_text = self._options.get('select_text') or False, + counter_ctrl = toolbar_panel.charcount if minimal_layout else None) + + # create these things next so they follow the text control in tab order. + buttons_panel = PanelWithController(self.accounts_panel) + self.ok_button = ok_button = wx.Button(buttons_panel, id = wx.ID_OK, label = _('&Update Status')) + cancel_button = wx.Button(buttons_panel, id = wx.ID_CANCEL) + + self.accounts_panel.set_focus = self.text_panel.focus_text + self.accountslist_panel = AccountListPanel(self.accounts_panel) + ok_button.SetDefault() + + sz = self.accounts_panel.Sizer = wx.BoxSizer(wx.VERTICAL) + sz.Add(toolbar_panel, 0, wx.EXPAND | (wx.ALL & ~wx.TOP), _BORDER) + if not minimal_layout: + sz.Add(wx.StaticLine(self.accounts_panel), 0, wx.EXPAND | wx.ALL, _BORDER) + sz.Add(self.accountslist_panel, 1, wx.EXPAND | wx.ALL, _BORDER) + if not minimal_layout: + sz.Add(wx.StaticLine(self.accounts_panel), 0, wx.EXPAND | wx.ALL, _BORDER) + + bsz = buttons_panel.Sizer = wx.BoxSizer(wx.HORIZONTAL) + bsz.AddStretchSpacer() + bsz.Add(ok_button, 0, wx.ALIGN_RIGHT | (wx.ALL & ~wx.BOTTOM), border = _BORDER) + bsz.Add(cancel_button, 0, wx.ALIGN_RIGHT | wx.ALL, border = _BORDER) + sz.Add(buttons_panel, 0, wx.EXPAND | wx.ALL, border = _BORDER) + + self.content.AddPanel('default', self.accounts_panel) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + hsizer = wx.BoxSizer(wx.HORIZONTAL) + hsizer.Add(self.text_panel, 1, wx.EXPAND | (wx.ALL & ~wx.BOTTOM), _BORDER) + self.Sizer.Add(hsizer, 0, wx.EXPAND | wx.ALL, _BORDER) + self.Sizer.Add(self.error_panel, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, _BORDER) + self.Sizer.Add(self.content, 1, wx.EXPAND | wx.ALL, _BORDER) + + for toolname in self.TOOLS: + p = self.tools[toolname] +# p.Sizer = wx.BoxSizer(wx.HORIZONTAL)+ +# sttext = wx.StaticText(p, wx.NewId(), p.label) +# p.Sizer.Add(sttext) + self.content.AddPanel(toolname, p) + + + def layout(self): + minsize = wx.Size(320, 200) + self.SetMinSize(minsize) + #self.content.SetMinSize(wx.Size(205, 270)) + maxheight = gui.toolbox.Monitor.GetFromWindow(self).ClientArea.height * common.pref('social.status.ratio_to_screen', type = float, default = 0.75) + maxsize = wx.Size(500, maxheight) + self.SetMaxSize(maxsize) + self.content.SetMaxSize(maxsize) + + self.text_panel.focus_text() + + def bind_events(self): + self.Bind(wx.EVT_BUTTON, self.on_button) + self.Bind(wx.EVT_CHECKBOX, self.check_enable_ok_button) + self.Bind(wx.EVT_HYPERLINK, self.on_link) + + self.Bind(wx.EVT_TEXT, self.check_enable_ok_button) + self.Bind(wx.EVT_TEXT_ENTER, self.on_button) + self.Bind(wx.EVT_TEXT_PASTE, self.on_text_paste) + + self.Bind(wx.EVT_UPDATE_UI, self.check_enable_ok_button) + + def check_enable_ok_button(self, e = None): + # use this line if you want to disable when character count is 0 + + self.ok_button.Enable(self.should_enable_ok()) + if e is not None: + e.Skip() + + def should_enable_ok(self): + return bool(self.GetSelectedAccounts()) +# return bool(self.GetMessage() and self.GetSelectedAccounts()) + + def on_text_paste(self, e): + import gui.clipboard as clipboard + + if e.EventObject is not self.text_panel.input: + e.Skip() + return + + text = clipboard.get_text() + if text is not None and common.pref('social.status.shorten_pasted_urls', default = False, type = bool) and util.isurl(text): + if not net.is_short_url(text): + self.text_panel.insert_text(net.get_short_url(text)) + return + + if text.endswith('\r\n'): + text = text[:-2] + + self.text_panel.insert_text(text) + + bitmap = clipboard.get_bitmap() + if bitmap is not None: +# if 'wxMSW' in wx.PlatformInfo and not bitmap.HasAlpha(): +# bitmap.UseAlpha() + + import stdpaths, time + filename = stdpaths.temp / 'digsby.trim.clipboard.%s.png' % time.time() + bitmap.SaveFile(filename, wx.BITMAP_TYPE_PNG) + + self.ChangePanel('image', False) + self.content.GetPanel().do_upload(filename) + return + + def check_image_data(self, e): + print 'image data:',e + #self.content.Bind(wx.EVT_SIZE, self.OnContentSized) + +# def OnContentSized(self, e): +# self.Fit() +# self.Layout() +# e.Skip() + + def CloseWithReturnCode(self, code): + self.SetReturnCode(code) + self.Close() + + def on_button(self, e): + panel_name = self.content.current_shown + if panel_name == 'default': + if e.Id == self._text_id: + if self.should_enable_ok(): + id = wx.ID_OK + else: + id = wx.ID_CANCEL + else: + id = e.Id + + if not wx.GetKeyState(wx.WXK_SHIFT) and id in (wx.ID_CANCEL, wx.ID_OK): + e.Skip() + self.CloseWithReturnCode(id) + else: + self.PanelDone(panel_name, e.Id) + + def PanelDone(self, name, return_code): + p = self.content.GetPanel() + if return_code == wx.ID_OK: + p.GetInfo(success = lambda info: wx.CallAfter(self.apply_info, name, info), + error = lambda *a: wx.CallAfter(self.info_error, p.label, *a)) + else: + self.SwitchToDefaultView() + + def SwitchToDefaultView(self): + self.ShowError(None) + self.content.GetPanel().clear() + self.content.ShowPanel('default') + self.content.GetPanel().set_focus() + self.text_panel.Enable(True) + + def on_link(self, e): + self.ChangePanel(e.URL) + + def ChangePanel(self, name, do_precheck = True): + self.ShowError(None) + self.text_panel.Enable(False) + self.content.ShowPanel(name, do_precheck = do_precheck) + + def info_error(self, name, e = None): + print 'ohno, an error' + #self.SwitchToDefaultView() + self.ShowError([(name, e)]) + + def ShowError(self, errors = None): + if errors is None: + errors = () + show = False + else: + show = True + + error_texts = [self.nice_message_for_error(*e) for e in errors] + + self.error_panel.set_errors(error_texts) + self.error_panel.Show(show) + self.Top.Layout() + self.Top.FitInScreen() + + def nice_message_for_error(self, acct, e): + import socket + if isinstance(acct, basestring): + acct_str = acct + elif acct.protocol == 'im-status': + acct_str = u"IM-Status: " + else: + acct_str = u"{acct.name!s} ({acct.protocol!s}): ".format(acct = acct) + # TODO: work out more error messages + log.info('Creating error message for account %r. error is %r', acct, e) + + import urllib2, socket + + try: + raise e + except socket.error: + err_str = _(u"Network error (%r)" % e) + except urllib2.URLError: + err_str = _(u"Network error (%s)" % e) + except IOError: + err_str = _(u"Network error ({error!r} - {error!s})").format(error = str(e).replace('\r', '\\r').replace('\n','\\n')) + except net.UrlShortenerException: + err_str = _(u"%s") % e.message + except Exception: + if getattr(e, 'message', None) and isinstance(e.message, basestring): + err_str = _(u"%s") % e.message + elif str(e): + err_str = _(u"Unknown error (%s)") % str(e) + else: + err_str = _(u"Unknown error") + + log.info('\terr_str = %r', err_str) + return (acct_str, err_str) + + def apply_info(self, name, info): + if name in ('link', 'image'): + link = info.get('link') + if link: + self.text_panel.insert_text(link) + + self.SwitchToDefaultView() + + def GetSelectedAccounts(self): + return self.accountslist_panel.GetSelectedAccounts() + + def GetMessage(self): + msg = self.text_panel.GetTextValue() + msg = msg.replace(u'\x0b', u'\n') + return msg.rstrip() + +class GlobalStatusController(object): + _inst = None + @classmethod + def GetInstance(cls, profile, enabled_accounts = None, initial_text = u'', **options): + if cls._inst is not None: + inst = cls._inst() + else: + inst = None + + if inst is None: + inst = cls(profile, enabled_accounts, initial_text, **options) + cls._inst = weakref.ref(inst) + else: + inst.Populate(profile, enabled_accounts, initial_text, options) + return inst + + def __init__(self, profile, enabled_accounts = None, initial_text = u'', **options): + self._dialog = None + self.Populate(profile, enabled_accounts, initial_text, options) + + self.observe_accounts() + + def observe_accounts(self): + self.profile.account_manager.socialaccounts.add_list_observer(self.accounts_changed, self.accounts_changed) + + def unobserve_accounts(self): + self.profile.account_manager.socialaccounts.remove_list_observer(self.accounts_changed, self.accounts_changed) + + def accounts_changed(self, *a): + if not wx.IsMainThread(): + wx.CallAfter(self.accounts_changed) + return + + if self._dialog is not None: + self.GetDialog().RefreshAccountsList() + + + def Populate(self, profile, enabled_accounts, initial_text, options): + self.profile = profile + self._options = options + + if enabled_accounts == 'ALL': + enabled_accounts = [self.get_accounts()[0]] + self.profile.socialaccounts[:] + + self.enabled_accounts = enabled_accounts + self.initial_text = initial_text + + def SetFocus(self): + if self._dialog is None: + return + + self._dialog.SetFocus() + + def ShowDialog(self, center = True): + dlg = self.GetDialog() + #dlg.CenterOnParent() + dlg.SetText(self.initial_text) + if self._options.get('select_text', False): + dlg.SelectText() + dlg.RefreshAccountsList() + dlg.Show() + dlg.ReallyRaise() + + def GetDialog(self): + if self._dialog is None: + self._dialog = self.CreateDialog() + + return self._dialog + + def CreateDialog(self): + dlg = GlobalStatusDialog(self, initial_text = self.initial_text, select_text = self._options.get('select_text', False), on_close = self._on_dialog_close) + return dlg + + def ShowError(self, etxt): + self.GetDialog().ShowError(etxt) + + def _on_dialog_close(self, e): + self.unobserve_accounts() + + dlg = self._dialog + if dlg is None: + return + + return_code = dlg.GetReturnCode() + if return_code == wx.ID_OK: + accounts = dlg.GetSelectedAccounts() + message = dlg.GetMessage() + self.apply_status_message(message, accounts) + submit_callback = self._options.pop('submit_callback', None) + if submit_callback is not None: + submit_callback(message, accounts) + + dlg.Destroy() + print 'dialog closed, return_code = %r' % return_code + self._dialog = None + + def apply_status_message(self, message, accounts): + if not accounts: + return + + collector = SetStatusResults(self.profile, accounts, message, self._options) + for account in accounts: + options = self._options.get('account_options', {}) + proto_options = options.get(account.protocol, {}).copy() + acct_options = options.get((account.protocol, account.name), {}).copy() + proto_options.update(acct_options) + try: +# import random +# if random.getrandbits(1): +# f = collector.error_handler(account) +# util.threaded(lambda e, func=f: func(e))(Exception("%r broke!" % account)) +# else: + if not hasattr(account, 'SetStatusMessage'): + continue + util.threaded(lambda a = account: a.SetStatusMessage + (message, + success = collector.success_handler(a), + error = collector.error_handler(a), + **proto_options))() + except Exception, e: + log.error("error setting status on account %r: %r", account, e) + + def get_accounts(self): + if getattr(self, '_im_status', None) is None: + self._im_status = SetIMStatusEntry(self.profile, + editable = self._options.get('editable', True), + edit_toggle = self._options.get('edit_toggle', True)) + + self.clear_unknown_accounts(self.profile.socialaccounts) + + return ([self._im_status] + + [a for a in self.profile.socialaccounts + if a.enabled and (a.ONLINE or a.CHECKING)]) + + def clear_unknown_accounts(self, accounts): + active_keys = [acct_name_to_key('im-status', 'im-status')] + [acct_name_to_key(a.protocol, a.name) for a in accounts] + + acct_status_settings = self.profile.prefs.get('social.status.enabled_accts', {}) + for key in acct_status_settings.keys(): + if key not in active_keys: + acct_status_settings.pop(key, None) + + def get_account_enabled(self, account): + if self.enabled_accounts is not None: + return account in self.enabled_accounts + + proto, name = account.protocol, account.name + + acct_status_settings = self.profile.prefs.get('social.status.enabled_accts', {}) + acctinfo = acct_status_settings.setdefault(acct_name_to_key(proto, name), {}) + + return acctinfo.setdefault('enabled', True) + + def set_account_enabled(self, account, enabled): + if self.enabled_accounts is not None: + if enabled and account not in self.enabled_accounts: + self.enabled_accounts.append(account) + + elif not enabled and account in self.enabled_accounts: + self.enabled_accounts.remove(account) + + return + proto, name = account.protocol, account.name + + acct_status_settings = self.profile.prefs.setdefault('social.status.enabled_accts', {}) + acctinfo = acct_status_settings.setdefault(acct_name_to_key(proto, name), {}) + + acctinfo['enabled'] = enabled + +class SetStatusResults(object): + def __init__(self, profile, accounts, message, options): + self.profile = profile + self.options = options + self.accounts = accounts + self.results = dict.fromkeys(accounts, None) + self.message = message + self._got_result = False + + def success_handler(self, acct): + def on_success(*a): + self.results[acct] = 'success' + self.check_results() + + return on_success + + def error_handler(self, acct): + def on_error(e): + import traceback; traceback.print_exc() + self.results[acct] = e + self.check_results() + return on_error + + def check_results(self): + if any(x is None for x in self.results.itervalues()) or self._got_result: + print 'setstatus still waiting for some' + return + + self._got_result = True + + # got response from every account + for acct in self.results.keys(): + if self.results[acct] == 'success': + self.results.pop(acct) + + if not self.results: + # successfully set status + log.info('Global status set successfully') + return + + # Now all we have left are accounts with errors. + log.info('Error(s) while setting global status: %r', self.results) + wx.CallAfter(self.show_dialog_again) + + def error_messages(self): + return [(account, self.results[account]) for account in self.results] + + def show_dialog_again(self): + gsc = GlobalStatusController.GetInstance(self.profile, + [a for a in self.accounts if a in self.results], + self.message, **self.options) + + gsc.ShowError(self.error_messages()) + gsc.ShowDialog() + +def main(): + from tests.testapp import testapp + import stdpaths + + a = testapp(plugins = False) + stdpaths.init() + + p = FakeProfile() + c = GlobalStatusController(p, + p.socialaccounts, + initial_text = 'initial text', + ) + c.ShowDialog(center = True) + d = c._dialog + #d.Bind(wx.EVT_DESTROY, lambda e: (wx.GetApp().ExitMainLoop())) + + tp = util.threadpool.ThreadPool(5) + + a.MainLoop() + + tp.joinAll() + print p.prefs + +def main1(): + def progress(howmuch): + print howmuch, 'was read' + def finished(): + print 'finished' + file = open('c:\\a.txt', 'rb') + cb = callbacks.CallbackStream(file, progress, finished) + +if __name__ == '__main__': + class FakeSocialAccount(object): + enabled = True + ONLINE = True + @property + def display_name(self): + return self.name + + @property + def serviceicon(self): + return gui.skin.get('serviceicons.%s' % self.protocol) + + def __init__(self, name, protocol): + self.username = self.name = name + self.protocol = protocol + + @callbacks.callsback + def SetStatusMessage(self, message, callback = None, **kw): + callback.success() + def __repr__(self): + return "FakeSocialAccount(%r, %r)" % (self.name, self.protocol) + + class FakeProfile(object): + prefs = {} + socialaccounts = [ + FakeSocialAccount('mike', 'twitter'), + FakeSocialAccount('mike2', 'facebook'), + FakeSocialAccount('mike3', 'facebook'), + FakeSocialAccount('mike', 'myspace') + ] + socialaccounts += [FakeSocialAccount('mike', 'myspace') for _ in range(30)] + + @callbacks.callsback + def SetStatusMessage(self, message, callback = None, **kw): + print 'omg setting status message! %r' % message + callback.success() + + account_manager = Null + + main() + diff --git a/digsby/src/gui/spellchecktextctrlmixin.py b/digsby/src/gui/spellchecktextctrlmixin.py new file mode 100644 index 0000000..524da93 --- /dev/null +++ b/digsby/src/gui/spellchecktextctrlmixin.py @@ -0,0 +1,579 @@ +import string +import wx +import re +from common import profile, pref, prefprop +from gui.textutil import FindAny, rFindAny + +from cgui import InputBox + +from common.spelling import spellchecker + + +from util.net import isurl + +punc = ''.join((string.punctuation.replace("'", ''),' ','\t','\n')) # all punctuation but single quote, which is used in correctly spelled words. i.e. don't +word_splitting_chars = re.escape(punc) +tokenizer_re = re.compile(r'\s+|[%s]' % word_splitting_chars) + + +import logging + +log = logging.getLogger('spellcheckmixin') + +class SpellCheckTextCtrlMixin(object): + + def __init__(self): + + #these hold a set of information for the last time the field has been + #spellchecked to check against to prevent redundant checks + self.lastvalue = self.Value + self.lastcharcount = len(self.Value) + self.lastinsertion = self.InsertionPoint + self.lastcurrentword = None + self.needscheck = False + self.lastkeycode = None + self.lastchar = None + + self.regex_ignores = [] + + self.spellcheckon = frozenset(punc + ' ') + self.spellerrors = dict() + + Bind = self.Bind + Bind(wx.EVT_KEY_DOWN , self.OnKey) + Bind(wx.EVT_TEXT , self.OnText) + Bind(wx.EVT_LEFT_DOWN , self.OnLeftDown) + + + WM_PAINT = 0x000F #Native Windows Paint Message, thanks MSDN or a source file, I don't remember + BindWin32 = self.BindWin32 + BindWin32(WM_PAINT, self.WMPaint) + + wx.CallAfter(self.SpellCheckAll) + + link = profile.prefs.link + link('messaging.spellcheck.enabled', self.UpdateEnablement) + link('messaging.spellcheck.engineoptions.lang', self.UpdateEnablement) + link('messaging.spellcheck.engineoptions.keyboard', self.UpdateEnablement) + self.UpdateEnablement() + + def HasSpellingErrors(self): + return bool(self.spellerrors) + + def AddRegexIgnore(self, regex): + self.regex_ignores.append(regex) + + def RemoveRegexIgnore(self, regex): + try: + self.regex_ignores.remove(regex) + except ValueError: + return False + else: + return True + + def UpdateEnablement(self, *a): + """ + Updates the display and spell errors when the spellcheck options are changed + """ + + #FIXME: Need a way to un-observe after and object is destroyed + if not wx.IsDestroyed(self): + spenabled = self.spenabled = pref('messaging.spellcheck.enabled') + + if spenabled: + self.SpellCheckAll() + else: + self.spellerrors.clear() + self.Refresh() + + else: + log.error('UpdateEnablement observer called on a SpellCheckTextCtrlMixin that has been destroyed') + + + + def WMPaint(self, hWnd, msg, wParam, lParam): + wx.CallAfter(self.PostPaint) + + + spsize = 2 #The thickness of the line + spstyle = wx.DOT #The type of line used for spellcheck + def PostPaint(self): + + """ + After the text is done painting, draw the spellcheck markup over it + """ + + try: + # text control must implement GetReqHeight + bottom = self.GetReqHeight() + except AttributeError: + return + + dc = wx.ClientDC(self) + + dc.Brush = wx.TRANSPARENT_BRUSH #@UndefinedVariable + dc.Pen = wx.Pen(wx.RED, self.spsize, self.spstyle) #@UndefinedVariable + + lastline = self.GetNumberOfLines() - 1 + + + Point = wx.Point + PosXY = lambda i: self.PositionToXY(i)[1:] + XYPos = self.XYToPosition + IndexToCoords = self.IndexToCoords + value = self.Value + + for word in self.spellerrors: + wordlen = len(word) + for i in self.spellerrors[word]: + + i1 = i + start = IndexToCoords(i) + end = IndexToCoords(i + wordlen) + + if PosXY(i)[1] != lastline: + ymod = IndexToCoords(XYPos(0, PosXY(i)[1]+1))[1] - start.y + else: + ymod = (bottom - start.y) - 8 + IndexToCoords(0).y + + points = [] + while start.y != end.y: + r1 = PosXY(i1)[1] + i2 = XYPos(0, r1 + 1) + e1 = i2 - 1 + + lineend = self.IndexToCoords(e1) + Point(dc.GetTextExtent(value[e1])[0], 0) + points.append((start, lineend)) + + i1 = i2 + start = IndexToCoords(i1) + + points.append((start,end)) + + for s, e in points: + dc.DrawLine(s.x, s.y + ymod, e.x, e.y + ymod) + + def AddSpellError(self,word,index, refresh = True): + + """ + Adds a misspelled word, or another index of that word to the dictionary of misspelled + words for lookup later + """ + + if not word in self.spellerrors: + self.spellerrors[word] = [] + + if not index in self.spellerrors[word]: + self.spellerrors[word].append(index) + + if refresh: self.Refresh() + + + def UpdateSpellErrorIndexes(self): + """ + Offsets all the spellerror indexes by the number of characters that + have been typed since the last check if the index is higher then the + last insertion index + """ + + lastinsert = self.lastinsertion + lastcount = self.lastcharcount + + deltachar = len(self.Value) - lastcount + spellerrors = self.spellerrors + + for word in spellerrors: + for n in list(spellerrors[word]): + if n > lastinsert: + i = spellerrors[word].index(n) + spellerrors[word][i] = n + deltachar + + self.Refresh() + + def ClearSpellErrors(self): + """ + Clears all the spell errors + Note that this does not update the display + """ + + self.spellerrors.clear() + + def RemoveExpiredSpellError(self, start, end, value=None): + """ + This does various checks to try and dismiss marked errors that are no longer valid + """ + + if value is None: + value = self.Value + + #URLs are not misspelled so lets get them out of the string + #urllessWords + purewords = [word for word in value.split() if not self.ignore_word(word)] + #purewords = ' '.join(urllessWords) + + #validate all the words in the spellerrors dictionary + for word in set(self.spellerrors): + wordlen = len(word) + + #if the word is't in the splash zone, don't bother checking it + if not any(start<=i<=end for i in self.spellerrors[word]): + continue + + #Remove the mark if the word is in the dictionary or no longer in the textctrl + if word not in purewords or self.Check(word): + self.spellerrors.pop(word) + + continue + + #if the word is still marked wrong but the number of instances have changed validate individual indexes + elif len(self.spellerrors[word]) != purewords.count(word): + for i in list(self.spellerrors[word]): + if value[i:i + wordlen] != word: + self.spellerrors[word].remove(i) + + self.Refresh() + + kb_shortcut_fixes = prefprop('messaging.spellcheck.kb_shortcut_fixes', default=False) + + def OnKey(self, e): + """ + Key press event handling + records last key code and the unicode character for that key + """ + + e.Skip() + + if not self.spenabled: + return + + if self.kb_shortcut_fixes and \ + e.GetModifiers() == wx.MOD_CMD and e.KeyCode == wx.WXK_SPACE: + e.Skip(False) + return self.ReplaceWordWithBestSuggestion() + + self.lastchar = e.UnicodeKey + self.lastvalue = self.Value + self.lastcharcount = len(self.lastvalue) + + wx.CallAfter(self.PostOnKey) + + def PostOnKey(self): + """ + Activates spellcheck on last change if a + navigation or other non-editing key is hit + """ + val = self.Value + charcount = len(val) + if self.needscheck and charcount and val == self.lastvalue: + self.SpellCheckLastChange() + + self.lastinsertion = self.InsertionPoint + self.lastcurrentword = self.GetCurrentWord() + + + def OnText(self,event): + """ + Text insertion event, flags the textfield as dirty if the text actually changed + """ + + event.Skip() + + if not self.spenabled or self.Value == self.lastvalue: + self.Refresh() + return + + self.needscheck = True + + self.UpdateSpellErrorIndexes() + + charcount = len(self.Value) + + if abs(charcount - self.lastcharcount) == 1: + keypressed = unichr(self.lastchar) if self.lastchar else None + currentword = self.GetCurrentWord() + + if ((keypressed in self.spellcheckon) or + self.lastcurrentword in self.spellerrors and currentword != self.lastcurrentword): + self.SpellCheckLastChange() + else: + self.SpellCheckAll() + + + def UpdateLasts(self): + """ + Updates variables used to determine if the string should be checked again + """ + pass + + def Check(self, word): + '''Returns True if a word should be considered spelled correctly.''' + + return self.ignore_word(word) or spellchecker.Check(word) + + def ignore_word(self, word): + if word and isurl(word): + return True + + for regex in self.regex_ignores: + if regex.match(word): + return True + + return False + + def SpellCheckAll(self): + """ + Clears all the errors and re-spellchecks the entire text field + """ + + self.ClearSpellErrors() + + val = self.Value + span = [word for word in val.split() if not self.ignore_word(word)] + words = tokenizer_re.split(' '.join(span)) + + index = 0 + + for word in words: + word = word.strip(string.punctuation) + correct = self.Check(word) + + index = val.find(word, index) + + if not correct: self.AddSpellError(word, index, False) + + index += len(word) + + + self.needscheck = False + + + self.Refresh() + + + def OnLeftDown(self,event): + """ + Event handler to trigger spellcheck on click + """ + event.Skip() + + if not self.spenabled: + return + + charcount = len(self.Value) + + if charcount and self.needscheck: + self.SpellCheckLastChange() + + self.lastinsertion = self.InsertionPoint + self.lastcurrentword = self.GetCurrentWord() + + + def GetCurrentWord(self): + """ + Get word at cursor position + """ + return self.GetWordAtPosition(self.InsertionPoint) + + def GetWordRangeAtPosition(self, ip): + """ + For suggestion menu, only returns the word range using punctuation + as a word breaking character + """ + s = self.Value + + # for misspellings like "audio/vedio" we will underline + # only "vedio." make sure that if you right click it, you + # get only "vedio" -- the word the cursor is under. + split_on = word_splitting_chars + + end = FindAny(s, split_on, ip) + if end == -1: end = len(s) + + start = max(rFindAny(s, split_on, 0, end), 0) + return start, end + + def GetWordAtPosition(self, ip): + """ + For suggestion menu, only returns the word range using punctuation + as a word breaking character + """ + start, end = self.GetWordRangeAtPosition(ip) + word = self.Value[start:end].split() + if word: + return word[0].strip(string.punctuation) + + def AddWordToDictionary(self, word): + """ + Add a word, duh + """ + spellchecker.Add(word) + + self.SpellCheckAll() + + def HitTestSuggestions(self, pos): + """ + Returns the word under the mouse + """ + result, col, row = self.HitTest(pos) + if result == wx.TE_HT_UNKNOWN: + return -1, [] + + i = self.XYToPosition(col, row) + return i, self.GetSuggestionsForPosition(i) + + def GetSuggestionsForPosition(self, i): + if i == -1: return [] + + word = self.GetWordAtPosition(i) + + if word and word in self.spellerrors: + return spellchecker.Suggest(word) + else: + return [] + + def AdjustSpellerrorIndex(self, index, diff): + """ + This offsets all spellerror index that are highr than index by diff + """ + spellerrors = self.spellerrors + + for word in spellerrors: + for n in list(spellerrors[word]): + if n > index: + i = spellerrors[word].index(n) + spellerrors[word][i] = n + diff + + + def ReplaceWord(self, position, new_word): + """ + Replaces the word at the position with the new word, and spell checks he area + """ + oldip = self.InsertionPoint + + old_value = self.Value + i, j = self.GetWordRangeAtPosition(position) + + + old_word = old_value[i:j] + + # Since GetWordRangeAtPosition returns punctuation and whitespace... + l = len(old_word) + old_word = old_word.lstrip(nonword_characters) + i += l - len(old_word) + + l = len(old_word) + old_word = old_word.rstrip(nonword_characters) + j -= l - len(old_word) + + self.Replace(i, j, new_word) + + + diff = (len(new_word) - len(old_word)) + self.AdjustSpellerrorIndex(position, diff) + self.InsertionPoint = oldip + diff + +# self.UpdateSpellErrorIndexes() + + self.SpellCheckLastChange(position) + self.ProcessEvent(wx.CommandEvent(wx.EVT_TEXT[0], self.Id)) + + def ReplaceWordWithBestSuggestion(self, pos=None): + '''Replaces the word at pos with the spellchecker's best suggestion. Has no + effect if there are no suggestions. + + If pos is None, the cursor position is used.''' + + pos = pos if pos is not None else self.InsertionPoint + suggestions = self.GetSuggestionsForPosition(pos) + if suggestions: + self.ReplaceWord(pos, suggestions[0]) + + # {'word': [1, 56, 34]} + + def SpellCheckLastChange(self, ip = None): + """ + Figure out a splash area for the last edit and spellcheck everything in it + """ + + # find the start and the end of the possibly relevant area + val = self.Value + end = val.find(' ', ip or self.lastinsertion) + if end != -1: end = val.find(' ', end + 1) + if end == -1: end = len(val) + start = val.rfind(' ', 0, end) + start = val.rfind(' ', 0, start) + start = max(start, 0) + start = val.rfind(' ', 0, start) + start = max(start, 0) + + self.RemoveExpiredSpellError(start, end, value=val) + + # filter out irrelevant words + span = [word for word in val[start:end].split() if not self.ignore_word(word)] + words = tokenizer_re.split(' '.join(span)) + + # check all the words + index = start + for word in words: + if not word: continue + + word = word.strip(string.punctuation) + correct = self.Check(word) + + index = val.find(word,index,end) + if not correct: self.AddSpellError(word, index) + index += len(word) + + self.needscheck = False + + def AddSuggestionsToMenu(self, menu): + return add_spelling_suggestions(self, menu) + +nonword_characters = string.whitespace + string.punctuation + +def add_spelling_suggestions(tc, menu): + ''' + Adds spelling suggestions to a UMenu. + ''' + + # Grab suggestions from the spell checker. + position, suggestions = tc.HitTestSuggestions(tc.ScreenToClient(wx.GetMousePosition())) + + # Add a menu item with each suggestion. + for sug in suggestions: + if sug != '': + menu.AddItem(sug, callback = lambda sug=sug: tc.ReplaceWord(position, sug)) + + word = tc.GetWordAtPosition(position) + + if word and word in tc.spellerrors: + menu.AddItem(_('Add to Dictionary'), + callback = lambda: tc.AddWordToDictionary(word) if word is not None else None) + + if suggestions: + menu.AddSep() + + return suggestions + + +class SpellCheckedTextCtrl(InputBox, SpellCheckTextCtrlMixin): + def __init__(self, parent, + id = wx.ID_ANY, + value = '', + pos = wx.DefaultPosition, + size = wx.DefaultSize, + style = 0, + validator = wx.DefaultValidator): + + InputBox.__init__(self, parent, id, value, pos, size, style, validator) + SpellCheckTextCtrlMixin.__init__(self) + self.Bind(wx.EVT_CONTEXT_MENU, self.__OnContextMenu) + + def __OnContextMenu(self, e): + from gui.uberwidgets.umenu import UMenu + from gui.toolbox import std_textctrl_menu + + with UMenu.Reuse(self) as menu: + self.AddSuggestionsToMenu(menu) + std_textctrl_menu(self, menu) + diff --git a/digsby/src/gui/splitimage4.py b/digsby/src/gui/splitimage4.py new file mode 100644 index 0000000..439f261 --- /dev/null +++ b/digsby/src/gui/splitimage4.py @@ -0,0 +1,210 @@ +# This file was created automatically by SWIG 1.3.29. +# Don't modify this file, modify the SWIG interface instead. + +import _splitimage4 +import new +new_instancemethod = new.instancemethod +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'PySwigObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static) or hasattr(self,name): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError,name + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +import types +try: + _object = types.ObjectType + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 +del types + + +def _swig_setattr_nondynamic_method(set): + def set_attr(self,name,value): + if (name == "thisown"): return self.this.own(value) + if hasattr(self,name) or (name == "this"): + set(self,name,value) + else: + raise AttributeError("You cannot add attributes to %s" % self) + return set_attr + + +import wx._core +import wx._windows +import wx._misc +wx = wx._core +__docfilter__ = wx.__DocFilter(globals()) + +def getObjectRef(*args, **kwargs): + """getObjectRef(Object obj) -> long""" + return _splitimage4.getObjectRef(*args, **kwargs) +class Extend(object): + """Proxy of C++ Extend class""" + thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc='The membership flag') + __repr__ = _swig_repr + def __init__(self, *args, **kwargs): + """__init__(self, bool up=False, bool down=False, bool left=False, bool right=False) -> Extend""" + this = _splitimage4.new_Extend(*args, **kwargs) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _splitimage4.delete_Extend + __del__ = lambda self : None; + up = property(_splitimage4.Extend_up_get, _splitimage4.Extend_up_set) + down = property(_splitimage4.Extend_down_get, _splitimage4.Extend_down_set) + left = property(_splitimage4.Extend_left_get, _splitimage4.Extend_left_set) + right = property(_splitimage4.Extend_right_get, _splitimage4.Extend_right_set) +_splitimage4.Extend_swigregister(Extend) + +class Region(object): + """Proxy of C++ Region class""" + thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc='The membership flag') + __repr__ = _swig_repr + def __init__(self, *args): + """ + __init__(self, Extend extends, int hstyle, int vstyle, int align, + Point offset) -> Region + __init__(self) -> Region + """ + this = _splitimage4.new_Region(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _splitimage4.delete_Region + __del__ = lambda self : None; + extends = property(_splitimage4.Region_extends_get, _splitimage4.Region_extends_set) + hstyle = property(_splitimage4.Region_hstyle_get, _splitimage4.Region_hstyle_set) + vstyle = property(_splitimage4.Region_vstyle_get, _splitimage4.Region_vstyle_set) + align = property(_splitimage4.Region_align_get, _splitimage4.Region_align_set) + offset = property(_splitimage4.Region_offset_get, _splitimage4.Region_offset_set) +_splitimage4.Region_swigregister(Region) + +class ImageData(object): + """Proxy of C++ ImageData class""" + thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc='The membership flag') + __repr__ = _swig_repr + def __init__(self, *args): + """ + __init__(self, String source) -> ImageData + __init__(self) -> ImageData + """ + this = _splitimage4.new_ImageData(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _splitimage4.delete_ImageData + __del__ = lambda self : None; + source = property(_splitimage4.ImageData_source_get, _splitimage4.ImageData_source_set) + x1 = property(_splitimage4.ImageData_x1_get, _splitimage4.ImageData_x1_set) + y1 = property(_splitimage4.ImageData_y1_get, _splitimage4.ImageData_y1_set) + x2 = property(_splitimage4.ImageData_x2_get, _splitimage4.ImageData_x2_set) + y2 = property(_splitimage4.ImageData_y2_get, _splitimage4.ImageData_y2_set) + left = property(_splitimage4.ImageData_left_get, _splitimage4.ImageData_left_set) + right = property(_splitimage4.ImageData_right_get, _splitimage4.ImageData_right_set) + top = property(_splitimage4.ImageData_top_get, _splitimage4.ImageData_top_set) + bottom = property(_splitimage4.ImageData_bottom_get, _splitimage4.ImageData_bottom_set) + center = property(_splitimage4.ImageData_center_get, _splitimage4.ImageData_center_set) +_splitimage4.ImageData_swigregister(ImageData) + +class Slice(object): + """Proxy of C++ Slice class""" + thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc='The membership flag') + __repr__ = _swig_repr + def __init__(self, *args, **kwargs): + """__init__(self) -> Slice""" + this = _splitimage4.new_Slice(*args, **kwargs) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _splitimage4.delete_Slice + __del__ = lambda self : None; + image = property(_splitimage4.Slice_image_get, _splitimage4.Slice_image_set) + hstyle = property(_splitimage4.Slice_hstyle_get, _splitimage4.Slice_hstyle_set) + vstyle = property(_splitimage4.Slice_vstyle_get, _splitimage4.Slice_vstyle_set) + pos = property(_splitimage4.Slice_pos_get, _splitimage4.Slice_pos_set) + offset = property(_splitimage4.Slice_offset_get, _splitimage4.Slice_offset_set) + align = property(_splitimage4.Slice_align_get, _splitimage4.Slice_align_set) +_splitimage4.Slice_swigregister(Slice) + +class ImageCluster(object): + """Proxy of C++ ImageCluster class""" + thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc='The membership flag') + __repr__ = _swig_repr + def __init__(self, *args, **kwargs): + """__init__(self) -> ImageCluster""" + this = _splitimage4.new_ImageCluster(*args, **kwargs) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _splitimage4.delete_ImageCluster + __del__ = lambda self : None; + center = property(_splitimage4.ImageCluster_center_get, _splitimage4.ImageCluster_center_set) + left = property(_splitimage4.ImageCluster_left_get, _splitimage4.ImageCluster_left_set) + right = property(_splitimage4.ImageCluster_right_get, _splitimage4.ImageCluster_right_set) + top = property(_splitimage4.ImageCluster_top_get, _splitimage4.ImageCluster_top_set) + bottom = property(_splitimage4.ImageCluster_bottom_get, _splitimage4.ImageCluster_bottom_set) + c1 = property(_splitimage4.ImageCluster_c1_get, _splitimage4.ImageCluster_c1_set) + c2 = property(_splitimage4.ImageCluster_c2_get, _splitimage4.ImageCluster_c2_set) + c3 = property(_splitimage4.ImageCluster_c3_get, _splitimage4.ImageCluster_c3_set) + c4 = property(_splitimage4.ImageCluster_c4_get, _splitimage4.ImageCluster_c4_set) +_splitimage4.ImageCluster_swigregister(ImageCluster) + +class SplitImage4(object): + """Proxy of C++ SplitImage4 class""" + thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc='The membership flag') + __repr__ = _swig_repr + splitimage = property(_splitimage4.SplitImage4_splitimage_get, _splitimage4.SplitImage4_splitimage_set) + Size = property(_splitimage4.SplitImage4_Size_get, _splitimage4.SplitImage4_Size_set) + MinSize = property(_splitimage4.SplitImage4_MinSize_get, _splitimage4.SplitImage4_MinSize_set) + ratio = property(_splitimage4.SplitImage4_ratio_get, _splitimage4.SplitImage4_ratio_set) + def __init__(self, *args, **kwargs): + """__init__(self, ImageData idata) -> SplitImage4""" + this = _splitimage4.new_SplitImage4(*args, **kwargs) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _splitimage4.delete_SplitImage4 + __del__ = lambda self : None; + def SetImage(*args, **kwargs): + """SetImage(self, ImageData idata)""" + return _splitimage4.SplitImage4_SetImage(*args, **kwargs) + + def Draw(*args, **kwargs): + """Draw(self, DC dc, Rect rect, int n=0)""" + return _splitimage4.SplitImage4_Draw(*args, **kwargs) + + def GetBitmap(*args, **kwargs): + """GetBitmap(self, Size size) -> Bitmap""" + return _splitimage4.SplitImage4_GetBitmap(*args, **kwargs) + + def PreRender(*args, **kwargs): + """ + PreRender(self, DC dc, Slice slice, int posx, int posy, int width, + int height) + """ + return _splitimage4.SplitImage4_PreRender(*args, **kwargs) + + def Render(*args, **kwargs): + """Render(self, DC dc, int w, int h, int x=0, int y=0, bool center=True)""" + return _splitimage4.SplitImage4_Render(*args, **kwargs) + +_splitimage4.SplitImage4_swigregister(SplitImage4) + + + diff --git a/digsby/src/gui/splitimage4_setup.py b/digsby/src/gui/splitimage4_setup.py new file mode 100644 index 0000000..34f24dd --- /dev/null +++ b/digsby/src/gui/splitimage4_setup.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +import sys, string + +sys.setup_is_main = __name__ == "__main__" #HAX: an icky hack! +from wx.build.config import * + +if not os.path.exists(PKGDIR): + os.mkdir(PKGDIR) + +EXTRA_PATH = getExtraPath(addOpts = True) if INSTALL_MULTIVERSION else None + +WXPY_SRC = 'C:/src/wxPython-2.8.4.2' + +if True: + location = './' + swig_files = ['splitimage4.i', ] + + swig_sources = run_swig(swig_files, + location, + GENDIR, + PKGDIR, + True or USE_SWIG, + True or swig_force, + ['-I%s\\include\\wx\\wxPython\\i_files' % WXPY_SRC] + swig_args, + swig_deps) + + includes.append('.') + + if os.name == "nt": + includes.append(WXPY_SRC + "/include") + if UNICODE: + includes.append(WXPY_SRC + "/lib/vc_dll/mswuh") + defines.append(('_UNICODE',1)) + else: + includes.append(WXPY_SRC + "/lib/vc_dll/mswh") + + libdirs.append(WXPY_SRC + "/lib/vc_dll") + + if debug: + defines = defines + [('DEBUG', 1), ('_DEBUG',1), ('TRACING',1)] + + foosources = ['SplitImage4.cpp'] + + ext = Extension('_splitimage4', foosources + swig_sources, + include_dirs = includes, + define_macros = defines, + + library_dirs = libdirs, + libraries = libs, + + extra_compile_args = cflags, + extra_link_args = lflags, + ) + wxpExtensions.append(ext) + + +if __name__ == "__main__": + if not PREP_ONLY: + setup(name = PKGDIR, + version = VERSION, + description = DESCRIPTION, + long_description = LONG_DESCRIPTION, + author = AUTHOR, + author_email = AUTHOR_EMAIL, + url = URL, + license = LICENSE, + packages = ['wx', 'wxPython'], + + extra_path = EXTRA_PATH, + ext_package = PKGDIR, + ext_modules = wxpExtensions, + + options = { 'build' : { 'build_base' : BUILD_BASE }}, + ) + + +#---------------------------------------------------------------------- +#---------------------------------------------------------------------- diff --git a/digsby/src/gui/status.py b/digsby/src/gui/status.py new file mode 100644 index 0000000..df96db4 --- /dev/null +++ b/digsby/src/gui/status.py @@ -0,0 +1,826 @@ +''' +status message editing GUI +''' + +from __future__ import with_statement +from gui.uberwidgets.formattedinput2.formatprefsmixin import StyleFromPref + +import wx, sys +from wx import BoxSizer, EXPAND, ALL, LEFT, RIGHT, BOTTOM, TOP + +from gui.toolbox import build_button_sizer +from gui import skin + +from common.statusmessage import StatusMessage, StatusMessageException, acct_reduce +from common import profile, actions + +from gui.uberwidgets.formattedinput2.formattedinput import FormattedInput +from gui.anylists import AnyRow, AnyList +from gui.uberwidgets.umenu import UMenu +from gui.validators import LengthLimit +from cgui import SimplePanel + +from util import Storage, removedupes, replace_newlines +from collections import defaultdict +from gui.textutil import default_font + +from logging import getLogger; log = getLogger('gui.status'); info = log.info + +def new_custom_status(window, save_checkbox = False, init_status=None): + ''' + Displays the status editor for creating a new custom status. + ''' + + if window is None: + window = wx.FindWindowByName('Buddy List') + + def onsave(diag): + status = diag.StatusMessageFromInfo() + if diag.SaveForLater: + # only save if the checkbox is checked + profile.add_status_message(status) + + import hooks; hooks.notify('digsby.statistics.ui.select_status') + profile.set_status(status) + + # Popup the Status dialog (with a save checkbox!) + StatusDialog.new(window, save_checkbox = save_checkbox, save_callback = onsave, init_status=init_status) + +# +# "digsby" logic for interfacing with the rest of the program +# + +def get_account_list(): + 'Return the accounts list.' + + return profile.account_manager.accounts + +DEFAULT_STATUS_CHOICES = [ + ('Available', _('Available')), + ('Away', _('Away')) +] + +def get_state_choices(curstatus = None, account = None): + 'Return state choices for all accounts, or just for one.' + + # Status choices come from connected accounts, unless there are none. + _profile = profile() + conn = list(_profile.account_manager.connected_accounts) + accts = conn + if accts != [_profile] and _profile in accts: + accts.remove(_profile) + + # When no accounts are connected, just use sensible defaults. + if not accts: + return DEFAULT_STATUS_CHOICES + + # Sort status messages by "category" in the order they appear in the status + # message lists in protocolmeta.py + statuses = [] + for acct in (accts if account in (None, False) else [account]): + if hasattr(acct, 'protocol_info'): + proto_statuses = acct.protocol_info().get('statuses', []) + invis = [StatusMessage.Invisible.title] + if acct.protocol_info().get('has_invisible', False) and invis not in proto_statuses: + proto_statuses.append(invis) + else: + proto_statuses = [] + + for cat_i, cat in enumerate(proto_statuses): + for status_id, status in enumerate(cat): + for st in cat: + statuses += [(cat_i, status_id, st)] + statuses.sort() + + statuses = removedupes([(s[0], s[2]) for s in statuses]) + status_strings = [(s[1], _(s[1])) for s in statuses] + + # If the status string itself in our accumulated list of statuses, that means + # it belongs to another protocol. Search for any protocols which have the + # status, and append an extra status message to the list. + + if curstatus is not None and curstatus not in [c[0] for c in status_strings]: + # Accumulate Status -> [Acct1, Acct2, ...] + from common.protocolmeta import protocols + status_acct_map = defaultdict(list) + for k, protocol in protocols.iteritems(): + for cat in protocol.get('statuses', []): + for st in cat: + status_acct_map[st].append(protocol.name) + + if protocol.get('has_invisible', False): + status_acct_map[StatusMessage.Invisible.title].append(protocol.name) + + # add a string like (MSN/ICQ only) to the status + accounts = sorted(status_acct_map.get(curstatus, [])) + + #Translators: Separator when listing multiple accounts, ex: MSN/ICQ only + account_types = _('/').join(accounts) + status_strings.append((curstatus, _('{status} ({account_types} Only)').format(status=curstatus, account_types=account_types))) + + return status_strings or DEFAULT_STATUS_CHOICES + + +# +# GUI +# + +status_message_size = (365, 160) + + +class StatusExceptionRow(AnyRow): + checkbox_border = 3 + image_offset = (22, 5) + image_size = (16, 16) + + def __init__(self, parent, data): + self.account = data + self.status_msg = parent.exceptions.get(acct_reduce(self.account), None) + + AnyRow.__init__(self, parent = parent, data = data, use_checkbox = True) + self.text = self.account.name + + def PopulateControls(self, data): + self.account = self.data = data + + self.text = self.account.name + self.checkbox.Value = self.status_msg is not None + + @property + def image(self): + img = skin.get('serviceicons.%s' % self.account.protocol) + if self.status_msg is None: + img = img.Greyed + + return img.Resized(self.image_size) + + def on_edit(self, e): + self.Parent.CreateOrEdit(self.data) + + def draw_text(self, dc, x, sz): + ''' + Draws the main text label for this row. + ''' + + status = self.status_msg.status if self.status_msg is not None else None + message = self.status_msg.message if self.status_msg is not None else None + + dc.Font = self.Font + + DrawExceptionLabels(dc, x, wx.RectS(sz), self.get_text(), _(status), message) + +def DrawExceptionLabels(dc, x, rect, accountname, status = None, message = None, isHeader = False, exceptionListWidth = None): + + height = rect.height + width = rect.width + paddingx = 5 + alignment = wx.ALIGN_CENTER if isHeader else wx.ALIGN_LEFT + + statuswidth = 60 + sx = (exceptionListWidth if exceptionListWidth is not None else width)/20*9 + accountwidth = sx - paddingx - x + mx = sx + statuswidth + + dc.SetPen(wx.Pen(wx.Color(200, 200, 200))) + dc.DrawLine(sx, 0, sx, height) + dc.DrawLine(mx, 0, mx, height) + dc.DrawLine(0, height-1, width, height-1) + + dc.TextForeground = wx.BLACK #syscol(wx.SYS_COLOUR_HIGHLIGHTTEXT if self.IsSelected() else wx.SYS_COLOUR_WINDOWTEXT) + + labelheight = height - dc.Font.Descent/2 + + accountrect = wx.Rect(x, 0, accountwidth, labelheight) + dc.DrawTruncatedText(accountname, accountrect, alignment | wx.ALIGN_CENTER_VERTICAL) + + if status is not None and status != "None": + x = sx + paddingx + + statusrect = wx.Rect(x, 0, statuswidth - 2*paddingx, labelheight) + dc.DrawTruncatedText(status, statusrect, wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL) + + if message is not None: + x = mx + paddingx + + messagerect = wx.Rect(x, 0, width - x - paddingx, labelheight) + dc.DrawTruncatedText(replace_newlines(message), messagerect, alignment | wx.ALIGN_CENTER_VERTICAL) + + +class StatusExceptionList(AnyList): + size = (360,150) + def __init__(self, parent, statussource, exceptions, edit_buttons = None): + + self.statussource = statussource + + self.accounts = get_account_list() + self.exceptions = exceptions + AnyList.__init__(self, parent, data = self.accounts, + row_control = StatusExceptionRow, edit_buttons = edit_buttons, draggable_items = False) + + self.show_selected = False + Bind = self.Bind + Bind(wx.EVT_LISTBOX_DCLICK, self.on_doubleclick) + Bind(wx.EVT_LIST_ITEM_FOCUSED, self.on_hover_changed) + Bind(wx.EVT_CHECKBOX, self.on_check) + + self.SetMinSize(self.size) + + def on_check(self, e): + row = self.GetRow(e.Int) + if row.checkbox.Value: + self.CreateOrEdit(row.data, row.checkbox) + else: + row.status_msg = None + self.OnDelete(row.data) + + + print self.exceptions + + def on_hover_changed(self, e): + row = self.GetRow(e.Int) + + if not row: + return + + row.Layout() + row.Refresh() + + def on_doubleclick(self, e): + self.CreateOrEdit(self.GetDataObject(e.Int)) + + def OnDelete(self, data): + self.exceptions.pop(acct_reduce(data), None) + self.update_data(self.exceptions) + self.Refresh() + + def CreateOrEdit(self, row_data, checkbox = None): + 'Create or edit an exception for the account.' + + account = row_data + accountstr = acct_reduce(account) + + # If SAVE is clicked, update this control's exception list. + + # Is there already an exception here? + if accountstr in self.exceptions: + diag = StatusDialog.edit(self, self.exceptions[accountstr], is_exception = account, + modal = True) + else: + # No, create one. + diag = StatusDialog.new(self, is_exception = account, + modal = True) + + with diag: + returncode = diag.ReturnCode + if returncode == wx.ID_SAVE: + exception = StatusMessageException(**diag.info()) + self.exceptions[accountstr] = exception + elif checkbox != None and returncode == wx.ID_CANCEL: + checkbox.Value = False + self.OnDelete(row_data) + + + + self.update_data(self.exceptions) + + self.Refresh() + + return returncode + + on_edit = CreateOrEdit + + def update_data(self, exceptions): + 'Given {account: exception}, update the table.' + + for i, account in enumerate(self.accounts): + row = self.GetRow(i) + if row is None: + continue + + exception_msg = exceptions.get(acct_reduce(row.account), None) + if exception_msg is not None: + row.status_msg = exception_msg + + + row.PopulateControls(account) + + self.Layout() + + @property + def Exceptions(self): + 'Returns exceptions which are checked.' + + excs = [] + for i, acct in enumerate(self.accounts): + acctstr = acct_reduce(acct) + if self.exceptions.get(acctstr, None) is not None: + excs.append((acctstr, self.exceptions[acctstr])) + return dict(excs) + +class OutlinePanel(SimplePanel): + def __init__(self, parent): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.contentSizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.contentSizer, 1, wx.EXPAND|wx.ALL, 1) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def AddControl(self, control, scale = 0, flags=wx.EXPAND): + self.contentSizer.Add(control, scale, flags) + + def OnPaint(self, event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + dc.SetBrush(wx.WHITE_BRUSH) + dc.SetPen(wx.Pen(wx.Color(200, 200, 200))) + dc.DrawRectangleRect(rect) + +class StatusExceptionPanel(OutlinePanel): + def __init__(self, parent): + OutlinePanel.__init__(self, parent) + + def SetExceptionList(self, excpetionlist): + self.header = StatusExceptionHeader(self, excpetionlist) + self.contentSizer.Add(self.header, 0, wx.EXPAND) + self.contentSizer.Add(excpetionlist, 0, wx.EXPAND) + + + +class StatusExceptionHeader(SimplePanel): + def __init__(self, parent, excpetionlist): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + + self.Font = default_font() + + self.exceptionlist = excpetionlist + self.MinSize = (-1, self.Font.Height + 10 + self.Font.Descent) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + + def OnPaint(self, event): + + + dc = wx.AutoBufferedPaintDC(self) + dc.Font = self.Font + + rect = wx.RectS(self.Size) + + dc.SetBrush(wx.WHITE_BRUSH) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangleRect(rect) + + DrawExceptionLabels(dc, 5, rect, _("Account"), _("Status"), _("Message"), True, self.exceptionlist.ClientSize.width) + +class StatusChoice(wx.Choice): + def __init__(self, parent, choices): + assert isinstance(choices, list) and (isinstance(choices[0], tuple) if len(choices) > 0 else True) + self.choices = dict(choices) + self.choices_indicies = dict((i, choice[0]) for i, choice in enumerate(choices)) + wx.Choice.__init__(self, parent, id=-1, choices = [_(c[1]) for c in choices]) + self.Bind(wx.EVT_CHOICE, self.on_choice) + + def SetStatus(self, choice): + try: + self.SetStringSelection(self.choices[choice]) + except KeyError: + choice = self.GetStringSelection() + + self.db_val = choice + + def on_choice(self, e): + # i -> choice + self.db_val = self.choices_indicies[self.GetSelection()] + +class StatusPanel(wx.Panel): + ''' + Fields for entering a status message, which include: + + - a title + - a message + - optional exceptions for each account + ''' + + def __init__(self, parent, status_message = None, is_exception = False, save_checkbox = False): + wx.Panel.__init__(self, parent) + + self.save_checkbox = save_checkbox # True if this dialog needs a "Save" checkbox + self.is_exception = is_exception # Is this status dialog editing an exception? + self.status_message = status_message # the current status object + self.title_edited = False # has the title text control been edited yet? + + self.construct(is_exception, status_message) + if status_message is not None: + self.populate_controls(status_message) + self.layout(is_exception) + + self.Fit() + + def info(self): + # Allows all text fields in this dialog to be optional. + # + # If "title" isn't specified but message is, then title becomes + # the first line of message. + # + # If message is blank and title is not, message becomes title. + # + # If both are blank, both become the status. + # + status = self.status.db_val + + if not self.is_exception: + title = self.title.Value + if not title or title.isspace(): + title = None + + message = self.message.Value + if not message or message.isspace(): message = None + + if not self.is_exception: + if title is None: + title = message.split('\n')[0] if message is not None else status + else: + title = None + + if message is None: + message = title if title is not None else _(status) + + s = Storage(message = message, + status = status, + format = self.message.Format) + + if not self.is_exception: + # while editing exceptions, there isn't a title. + s.title = title + s.exceptions = self.exceptions.Exceptions if self.diffcheck.IsChecked() else {} + + from pprint import pformat + log.info(pformat(dict(s))) + + return s + + def show_exceptions(self, show = True): + 'Shows or hides the exceptions list at the bottom of the dialog.' + + with self.Frozen(): + if self.exceptionspanel.Show(show): + self.Top.FitInScreen() + + def on_message_text(self, e): + e.Skip() + if not self.title_edited: + # the ChangeValue function does not emit EVT_TEXT + + # skip leading whitespace + msg = self.message.Value.lstrip() + + # find all text up to the first newline + newline = msg.find('\n') + if newline == -1: newline = len(msg) + + self.title.ChangeValue( msg[:newline] ) + + def populate_controls(self, status_message = None): + for key in ('title', 'message'): + if hasattr(self, key): + getattr(self, key).Value = getattr(status_message, key) + + # Online, Away, Out to Luncg, etc... + self.status.SetStatus(status_message.status) + + + def construct(self, is_exception, status_message = None): + + if not is_exception: + self.title_label = wx.StaticText(self, -1, _('&Title:')) + self.title = t = wx.TextCtrl(self, -1, '', size=(340,-1), validator=LengthLimit(255)) + t.Bind(wx.EVT_TEXT, lambda e: setattr(self, 'title_edited', + bool(self.title.Value))) + + self.status_label = wx.StaticText(self, -1, _('&State:')) + + curstatus = self.status_message.status if self.status_message is not None else None + choices = get_state_choices(curstatus, is_exception) + + self.status = StatusChoice(self, choices) + self.status.SetStatus('Away') + + self.message_label = wx.StaticText(self, -1, _('&Status message:')) + + self.message_panel = OutlinePanel(self) + + self.message = FormattedInput(self.message_panel, multiFormat = False, + autosize = False, + format = getattr(status_message, 'format', None) or StyleFromPref('messaging.default_style'), + skin = 'AppDefaults.FormattingBar', + validator= LengthLimit(10240), + ) + self.message_panel.AddControl(self.message, 1, wx.EXPAND) + + self.message.SetMinSize(status_message_size) + + if not is_exception: + self.message.tc.Bind(wx.EVT_TEXT, self.on_message_text) + + msg = self.status_message + + self.exceptionspanel = StatusExceptionPanel(self) + self.exceptions = StatusExceptionList(self.exceptionspanel, self, status_message.exceptions if status_message else {}) + self.exceptionspanel.SetExceptionList(self.exceptions) + + hasexcs = msg is not None and msg.use_exceptions + self.exceptionspanel.Show(hasexcs) + + chk = self.diffcheck = wx.CheckBox(self, -1, _('&Use a different status for some accounts')) + chk.Value = hasexcs + chk.Bind(wx.EVT_CHECKBOX, lambda e: self.show_exceptions(not self.exceptionspanel.IsShown())) + + s = self.save = wx.Button(self, wx.ID_SAVE, _('&Set') if self.save_checkbox else _('&Save')) + s.SetDefault() + s.Bind(wx.EVT_BUTTON, self.on_save) + + c = self.cancel = wx.Button(self, wx.ID_CANCEL, _('&Cancel')) + c.Bind(wx.EVT_BUTTON, self.on_cancel) + + # Save checkbox + if self.save_checkbox: + self.save_check = wx.CheckBox(self, -1, _('Save for &later')) + + def layout(self, is_exception): + self.Sizer = sz = BoxSizer(wx.VERTICAL) + + # The upper two fields: title text field and state combo box + inner = wx.FlexGridSizer(2, 2, 6, 6) + + if not is_exception: + inner.AddMany([ (self.title_label, 0, wx.ALIGN_CENTER_VERTICAL), + (self.title) ]) + inner.AddMany([ (self.status_label, 0, wx.ALIGN_CENTER_VERTICAL), + (self.status) ]) + inner.AddGrowableCol(1, 1) + sz.Add(inner, 0, wx.EXPAND | wx.ALL, 8) + + # The label for the message, and the big text area itself + sz.Add(self.message_label, 0, EXPAND | ALL, 8) + h = wx.BoxSizer(wx.HORIZONTAL) + h.Add((1,status_message_size[1])) + h.Add(self.message_panel, 1, EXPAND) + sz.Add(h, 1, EXPAND | RIGHT | LEFT, 15) + #sz.Add(self.message) + + sz.AddSpacer(3) + + # Save For Later + if self.save_checkbox: sz.Add(self.save_check, 0, EXPAND | TOP | LEFT, 8) + + if not is_exception: + # The checkbox to expand the dialog + sz.Add(self.diffcheck, 0, EXPAND | ALL, 8) + + # Status exceptions + sz.Add(self.exceptionspanel, 0, EXPAND | RIGHT | LEFT | BOTTOM, 8) + + # Save/Cancel + sz.Add(build_button_sizer(save=self.save, cancel=self.cancel), 0, EXPAND | BOTTOM | RIGHT | LEFT, 4) + + def on_cancel(self,e): + self.Parent.Cancel() + + def on_save(self, e): + self.Parent.Save() + + @property + def SaveForLater(self): + return hasattr(self, 'save_check') and self.save_check.IsChecked() + +class StatusDialog(wx.Dialog): + + minsize = (290, 290) + + @classmethod + def new(cls, parent, is_exception = False, save_checkbox = False, + save_callback = None, modal = False, init_status=None): + if not modal and cls.raise_existing(): + return + + if not is_exception: title = _('New Status Message') + else: title = _('New Status for {name}').format(name=getattr(is_exception, 'name', is_exception)) + + if parent: + parent = parent.Top + + diag = StatusDialog(parent, status_message = init_status, title = title, is_exception = is_exception, + save_checkbox = save_checkbox) + + if is_exception and parent: + diag.CenterOnParent() + + if not modal: + diag.Prompt(save_callback) + else: + diag.ShowModal() + return diag + + @classmethod + def edit(cls, parent, status_message, is_exception = False, + save_callback = None, modal = False): + + if not modal and cls.raise_existing(): + return + + if not is_exception: + title = _('Edit Status Message') + else: + account_str = u'{0} ({1})'.format(is_exception.username, is_exception.protocol) + title = _('Edit Status for {account}').format(account=account_str) + + diag = StatusDialog(parent, status_message, title = title, is_exception = is_exception) + if not modal: + diag.Prompt(save_callback) + else: + diag.ShowModal() + return diag + + @classmethod + def raise_existing(cls): + for win in reversed(wx.GetTopLevelWindows()): + if isinstance(win, StatusDialog): + win.Show() + win.Raise() + return True + + + def StatusMessageFromInfo(self): + "Builds a StatusMessage object from this dialog's fields." + return StatusMessage(**self.info()) + + def __init__(self, parent, status_message = None, pos=(400,200), + title='Status Message', is_exception = False, save_checkbox = False): + wx.Dialog.__init__(self, parent, title=title, pos=(400,200), + style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + self.Sizer = s = wx.BoxSizer(wx.VERTICAL) + self.statuspanel = panel = StatusPanel(self, status_message, is_exception, save_checkbox) + panel.message.SetFocus() + s.Add(panel, 1, wx.EXPAND) + + self.Fit() + self.SetMinSize(self.Size) + + self.Bind(wx.EVT_CLOSE,self.OnClose) + + def Save(self): + self.SetReturnCode(wx.ID_SAVE) + if hasattr(self, 'save_callback'): + self.save_callback(self) + self.Close() + + def Cancel(self): + self.SetReturnCode(wx.ID_CANCEL) + self.Close() + + + def OnClose(self,event): + if self.IsModal(): + self.EndModal(self.ReturnCode) + else: + self.Destroy() + + def Prompt(self, save_callback): + if not hasattr(save_callback, '__call__'): + raise TypeError('Prompt takes a callable which will be called when the dialog goes away.') + + self.save_callback = save_callback + self.Show() + + def info(self): + return self.statuspanel.info() + + @property + def SaveForLater(self): + return self.statuspanel.SaveForLater + +# ---------------- + + + +class StatusRow(AnyRow): + + checkbox_border = 3 + image_offset = (6, 5) + + def __init__(self, parent, status_message): + AnyRow.__init__(self, parent, status_message, use_checkbox = False) + + def PopulateControls(self, status): + self.text = status.title + + @property + def image(self): + statusmsg = self.data + return skin.get('statusicons.%s' % ('away' if statusmsg.away else 'available')) + + @property + def popup(self): + if hasattr(self, '_menu') and self._menu: self._menu.Destroy() + menu = UMenu(self) + + menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) + menu.AddItem(_('&Remove'), callback = lambda: self.on_delete()) + + menu.AddSep() + actions.menu(self, self.data, menu) + + self._menu = menu + return menu + + def ConstructMore(self): + + # Extra component--the edit hyperlink + edit = self.edit = wx.HyperlinkCtrl(self, -1, _('Edit'), '#') + edit.Hide() + edit.Bind(wx.EVT_HYPERLINK, lambda e: self.on_edit()) + + remove = self.remove = wx.HyperlinkCtrl(self, -1, _('Delete'), '#') + remove.Hide() + remove.Bind(wx.EVT_HYPERLINK, lambda e: self.on_delete()) + + edit.HoverColour = edit.VisitedColour = edit.ForegroundColour + remove.HoverColour = remove.VisitedColour = remove.ForegroundColour + + def LayoutMore(self, sizer): + sizer.AddStretchSpacer() + sizer.Add(self.edit, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) + sizer.Add(self.remove, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) + + +class StatusList(AnyList): + 'Status messages list.' + + def __init__(self, parent, status_messages, edit_buttons = None): + AnyList.__init__(self, parent, status_messages, + row_control = StatusRow, edit_buttons = edit_buttons) + + self.show_selected = False + + Bind = self.Bind + Bind(wx.EVT_LISTBOX_DCLICK, self.on_doubleclick) + Bind(wx.EVT_LIST_ITEM_FOCUSED,self.OnHoveredChanged) + + def OnHoveredChanged(self,e): + row = self.GetRow(e.Int) + + if row: + if row.IsHovered(): + row.edit.Show() + row.remove.Show() + row.Layout() + row.Refresh() + else: + row.edit.Hide() + row.remove.Hide() + row.Layout() + + def on_doubleclick(self, e): + self.on_edit(self.GetDataObject(e.Int)) + + def OnDelete(self, msg): + 'Called when the minus button above this list is clicked.' + + if not msg: return + + # Display a confirmation dialog. + message = _('Are you sure you want to delete status message "{title}"?').format(title=msg.title) + caption = _('Delete Status Message') + style = wx.ICON_QUESTION | wx.YES_NO + parent = self + + # shift bypasses confirm dialog on dev. + dev_and_shift = getattr(sys, 'DEV', False) and wx.GetKeyState(wx.WXK_SHIFT) + + if dev_and_shift or wx.MessageBox(message, caption, style, parent) == wx.YES: + profile.remove_status_message(msg) + self.Refresh() + + def on_edit(self, status_message): + def onsave(diag): + status_message.__setstate__(diag.info()) + status_message.notify() + self.Refresh() + + print status_message + StatusDialog.edit(self, status_message, save_callback = onsave) + + def OnNew(self, e = None): + 'Called when the plus button above this list is clicked.' + + self.add_status_message() + + def add_status_message(self): + StatusDialog.new(self, save_callback = lambda diag: profile.add_status_message(**diag.info())) + +if __name__ == '__main__': + from tests.testapp import testapp + from gui import skin + app = testapp('../..') + StatusDialog(None).ShowModal() diff --git a/digsby/src/gui/statuscombo.py b/digsby/src/gui/statuscombo.py new file mode 100644 index 0000000..cc48adc --- /dev/null +++ b/digsby/src/gui/statuscombo.py @@ -0,0 +1,747 @@ +''' + +A status combo box for quickly choosing statuses. + +Also doubles as a searchbox for the buddylist. + +''' + +from __future__ import with_statement + +import wx +from gui import skin +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.simplemenu import SimpleMenuItem, SimpleMenu +from common.statusmessage import StatusMessage +from logging import getLogger; log = getLogger('statuscombo'); info = log.info +from common import profile, search, pref, setpref +from gui.toolbox import calllimit +from gui.toolbox.keynames import non_alphanumeric +from util.primitives.funcs import Delegate + +from gui.status import new_custom_status + +import gui.model.menus as menus +import actionIDs +import hooks + +from peak.util.plugins import Hook + +# this is a dict of status -> ID references that we re-create each time a popup menu +# is displayed. +status_dict = {} + +def PROMOTE_STATUS_STRING(): + import branding + url = branding.get('digsby.promote.url', 'digsby_promote', 'http://im.digsby.com') + return u'I use Digsby to manage IM + Email + Social Networks - ' + url + +def set_profile_status(msg): + ''' + The combo calls this method by default when setting a new status. + + This can be changed in the constructor. + ''' + import hooks; hooks.notify('digsby.statistics.ui.select_status') + return profile.set_status(msg) + +def get_profile_status(): + return profile.status + +BUTTON_HOLD_TIME = 1000 + +def group_profile_statuses(): + msgs = sorted((c for c in profile.statuses), key = lambda msg: msg.away) + + j = -1 + for j, msg in enumerate(msgs): + if msg.away: break + else: + j = -1 + + if j == -1: + j = len(msgs) + + avail_msgs = msgs[:j] + avail_msgs.insert(0, StatusMessage.Available.copy()) + + away_msgs = msgs[j:] + away_msgs.insert(0, StatusMessage.Away.copy()) + return avail_msgs, away_msgs, filter(None, [s() for s in Hook('digsby.im.statusmessages')]) + +def get_profile_statuses(): + + # Find where to insert the special "Away" status item + avail, away, plugins = group_profile_statuses() + + return avail + away + plugins + +def umenuitem(menu, status): + menu.AddItem(status.title, bitmap = status.icon, + callback = lambda status=status: profile.set_status(status)) + +def global_status_enabled(): + return pref('social.use_global_status', type = bool, default = False) + +def add_global_to_menu(menu, text = _('Global Status'), use_icon = True, icon_key = 'icons.globalstatus', init_text = u''): + if not global_status_enabled(): + return + + _add_thingy_to_menu(menu, text, use_icon, icon_key, init_text) + +def _add_thingy_to_menu(menu, text, use_icon, icon_key, init_text): + if use_icon: + bmp = skin.get(icon_key) + else: + bmp = None + menu.AddItem(text, bitmap = bmp, + callback = lambda: wx.GetApp().SetStatusPrompt('ALL', + initial_text = init_text, + editable = False, + edit_toggle = False, + select_text = bool(init_text))) + +def add_promote_to_menu(menu, text = _('Promote Digsby!'), use_icon = True, icon_key = 'statusicons.promote', + init_text = None): + if init_text is None: + init_text = PROMOTE_STATUS_STRING() + if not global_status_enabled(): + return + return _add_thingy_to_menu(menu, text, use_icon, icon_key, init_text) + +def status_menu(menu, add_custom = False, add_global = False, add_promote = False): + 'Adds statuses to menu.' + + avail, away, plugins = group_profile_statuses() + + for status in avail: + umenuitem(menu, status) + + for status in away: + umenuitem(menu, status) + + if add_custom: + menu.AddItem(_('Custom...'), + callback = lambda: edit_custom_status(None)) + + menu.AddSep() + + if add_global: + add_global_to_menu(menu) + + for status in plugins: + umenuitem(menu, status) + + if add_promote: + add_promote_to_menu(menu) + + umenuitem(menu, StatusMessage.Invisible.copy(message = profile.status.message)) + + if profile.allow_status_changes: + # cannot go offline while only connected account is to the Digsby servers + umenuitem(menu, StatusMessage.Offline) + + +def create_status_menu(add_custom=False, add_global = False): + ''' + status_menu function adapted to work with gui.model.menus instead of UMenu + ''' + status_menu = menus.Menu() + global status_dict + + status_dict = {} + + for status in get_profile_statuses(): + status_dict[status] = wx.NewId() + item = status_menu.addItem(status.title, id=status_dict[status], bitmap=status.icon) + + if add_custom: + status_menu.addItem(_('Custom...'), id=actionIDs.SetStatusCustom) + + status_menu.addSep() + + if add_global and global_status_enabled(): + status_menu.addItem(_('Global Status'), bitmap = skin.get('icons.globalstatus'), id = actionIDs.SetStatusGlobal) + + invisible = StatusMessage.Invisible.copy(message = profile.status.message) + status_dict[invisible] = wx.NewId() + status_menu.addItem(invisible.title, id=status_dict[invisible], bitmap=invisible.icon) + + if profile.allow_status_changes: + # cannot go offline while only connected account is to the Digsby servers + offline = StatusMessage.Offline + status_dict[offline] = wx.NewId() + status_menu.addItem(StatusMessage.Offline.title, id=status_dict[offline], bitmap=StatusMessage.Offline.icon) + + return status_menu + +def edit_custom_status(window_parent): + ''' + Show GUI to edit a custom status. + ''' + + s = profile.status + + if s.editable: + new_custom_status(window_parent, init_status = s.copy(), save_checkbox = True) + else: + # Don't copy the messages from non-editable statuses like Now Playing + new_custom_status(window_parent, save_checkbox = True) + +def edit_global_status(): + wx.CallAfter(wx.GetApp().SetStatusPrompt) + + +class StatusCombo(UberCombo): + + # number of milliseconds to wait after clicking the status button before the + # status is set (if the user hasn't entered any text) + set_delay = 3000 + + def __init__(self, parent, buddylist, statuses, + get_status_method = get_profile_status, + set_status_method = set_profile_status): + ''' + StatusCombo constructor. + + parent - a wx.Window parent window + statuses - an observable list of StatusMessage objects + ''' + + self.buddylist = buddylist + self.buddylist.Bind(wx.EVT_KEY_DOWN, self.on_buddylist_key) + self.searching = False + self.searchHintShown = False + + if not getattr(StatusCombo, 'searchThresholdRegistered', False) and pref('search.buddylist.show_hint', True): + def SearchThresholdReached(*a, **k): + if pref('search.buddylist.show_hint', True): + setpref('search.buddylist.show_hint', False) + Hook('digsby.achievements.threshold', 'buddylist.search').register(SearchThresholdReached) + StatusCombo.searchThresholdRegistered = True + + self.offline_item = None + self.get_profile_status = get_status_method + self.set_profile_status = set_status_method + + status = self.get_profile_status() + + UberCombo.__init__(self, parent, skinkey = 'combobox', + typeable = True, + valuecallback = self.on_text_lose_focus, + empty_text=getattr(status, 'hint', status.title.title()), + maxmenuheight = 15) + + self.buttoncallback = self.on_status_button + self.cbutton = UberButton(self, -1, skin=self.cbuttonskin) + self.cbutton.Bind(wx.EVT_BUTTON, self._on_left_button) + self.content.Insert(0,self.cbutton, 0, wx.EXPAND) + + self.cbutton.BBind(RIGHT_UP = self.on_status_button_right_click, + LEFT_DOWN = self.on_status_button_left_click, + LEFT_UP = self.on_status_button_left_up) + + self.display.Bind(wx.EVT_LEFT_DOWN, lambda e: (e.Skip(), setattr(self, 'oldValue', self.Value))) + + + # the on_allow_status_changes method is called when the list of connected + # im accounts changes size. if all accounts are offline this control + # becomes disabled.. + + #profile.account_manager.connected_accounts.add_observer(self.on_allow_status_changes) + profile.account_manager.connected_accounts.add_observer(self.on_offline_allowed, obj = self) + + # Listen on status messages (changes, additions, deletes). + _obs_link = statuses.add_list_observer(self.on_status_messages_changed, + self.on_status_messages_changed) + self.Bind(wx.EVT_WINDOW_DESTROY, + lambda e: (log.info('status combo removing observers'), e.Skip(), _obs_link.disconnect())) + + self.on_status_messages_changed(statuses) + + # when the profile's status changes, update to reflect it + profile.add_observer(self.on_profile_status_changed, 'status') + + # Display the current status. + self.show_status(self.get_profile_status()) + + # Timer for committing status messages after a delay. + self.timer = wx.PyTimer(self.SetFocus) + self.Bind(wx.EVT_TEXT, self.on_typing) + + self.button_timer = wx.PyTimer(self.on_status_button_right_click) + + textbind = self.TextField.Bind + textbind(wx.EVT_SET_FOCUS, lambda e: setattr(self, 'skipenter', False)) + textbind(wx.EVT_KEY_DOWN, self._on_key_down) + textbind(wx.EVT_TEXT_ENTER, self._on_enter) + + self.DropDownButton.Bind(wx.EVT_LEFT_DOWN, self._dbutton_left) + + self.OnActivateSearch = Delegate() + self.OnDeactivateSearch = Delegate() + + def UpdateSkin(self): + key = 'statuspanel' + + if not skin.get(key, False) or skin.get(key+ '.mode','') == 'native': + s = lambda k,d: None + else: + s = lambda k, default: skin.get('%s.%s' % (key, k), default) + + comboskinkey = s('comboboxskin',None) + self.cbuttonskin = cbskinkey = s('statusbuttonskin',None) + + + self.SetSkinKey(comboskinkey) + UberCombo.UpdateSkin(self) + + if hasattr(self, 'cbutton'): + self.cbutton.SetSkinKey(cbskinkey, True) + self.SetButtonIcon(StatusMessage.icon_for(self.status_state)) + + + if hasattr(self,'menu') and self.menu: + self.on_status_messages_changed() + + + def SetButtonIcon(self, icon): + """set the icon for the cycle button""" + self.cbutton.SetIcon(icon) + self._button_icon = icon + self.Layout() + + def SetCallbacks(self, selection = sentinel, value = sentinel, button = sentinel): + 'Sets callbacks for this combobox.' + + UberCombo.SetCallbacks(self, selection, value) + if button is not sentinel: self.buttoncallback = button + + def on_allow_status_changes(self, *a, **k): + if self.Show(profile.allow_status_changes): + self.Parent.gui_layout() + + def setandshow(self, statusmsg): + 'Immediately sets the status message and shows it.' + + log.info('setandshow %r', statusmsg) + self.oldValue = None + self.show_status(statusmsg) + self.set_profile_status( statusmsg ) + + def show_status(self, status, force=False): + 'Displays the specified status message.' + + if not force and status is getattr(self, '_shown_status', None): + return + + # make the text area not editable for statuses like "Invisble" and + # "Offline", which have the "editable" attribute set to False + self.Editable = status.editable + + self.display.empty_text = getattr(status, 'hint', '') + self.ChangeValue(status.message) # change text + self.SetButtonIcon(StatusMessage.icon_for(status)) # change icon + self.status_state = status.status # store the state + self._shown_status = status + + # + # events + # + + def on_typing(self, e): + 'Invoked when the user is typing in the textfield.' + + if self.searching: + search.link_prefs(profile.prefs) + e.Skip() + self.buddylist.search(e.EventObject.Value) + else: + self.cancel_timer() + + def on_status_button(self, button): + ''' + Invoked when the user clicks the state button to the left of the + dropdown. + ''' + # toggle the control's status state + isavail = StatusMessage.is_available_state(self.status_state) + + # do we need to change the shown text? + needs_change = self._shown_status in StatusMessage.SpecialStatuses or not self._shown_status.editable + + self.oldValue = None + + self.change_state(state = 'Away' if isavail else 'Available',) + #change_text = needs_change) + + def change_state(self, state, change_text = False): + if not isinstance(state, basestring): + raise TypeError('change_state takes a string got a %s' % type(state)) + self.status_state = state + + if change_text: + self.ChangeValue(self.status_state, state.title()) + else: + self.Default = state.title() + + edit_toggle = getattr(profile.status, 'edit_toggle', True) + if getattr(profile.status, 'edit_toggle', True): + # update the icon + self.SetButtonIcon(StatusMessage.icon_for(self.status_state)) + + self.cancel_timer() + self.timer.StartOneShot(self.set_delay) + + # select all text in the textfield + disp = self.display + disp.TypeField() + wx.CallAfter(disp.txtfld.SetSelection, -1, -1) + else: + self.setandshow(profile.status.copy(status = self.status_state, editable = None, edit_toggle = None)) + + def on_status_button_left_click(self, e = None): + if self.searching: + return self.TextField.SetFocus() + self.skipenter = True + self.button_timer.Start(BUTTON_HOLD_TIME, True) + if e: e.Skip(True) + + def on_status_button_left_up(self, e = None): + if not self.searching: + self.button_timer.Stop() + if e: e.Skip(True) + + def on_status_button_right_click(self, e = None): + if not self.searching: + self.show_extended_status_menu() + + def show_extended_status_menu(self): + from gui.status import get_state_choices + + m = SimpleMenu(self, skinkey = skin.get('%s.MenuSkin'%self.skinkey)) + + for status in get_state_choices(): + statusname, statuslabel = status + + def onclick(item, state=statusname): + self.change_state(state)#, change_text = self.status_state == self.GetValue()) + + m.AppendItem(SimpleMenuItem([StatusMessage.icon_for(statusname), statuslabel], + method = onclick)) + + if m.GetCount() > 0: + m.Display(self.cbutton) + + def on_text_lose_focus(self, new_msg): + if self.searching: + return self.on_search_timer() + + # Cancel the status button timer if it's running. + self.cancel_timer() + + if getattr(self, 'skipenter', False): + wx.CallAfter(lambda: setattr(self, 'skipenter', False)) + else: + # don't set status if we lost focus because the user is clicking + # on the state button + if wx.GetMouseState().LeftDown() and wx.FindWindowAtPoint(wx.GetMousePosition()) is self.cbutton: + return + + profile_status = self.get_profile_status() + if new_msg == '': + self.display.empty_text = profile_status.hint + if new_msg != profile_status.message or self.status_state != profile_status.status: + # entering a new text value clears all exceptions + newmsg = StatusMessage(new_msg, self.status_state, new_msg) + self.set_profile_status(newmsg) + + def on_profile_status_changed(self, *a): + "Invoked when the profile's status changes." + + self.show_status(profile.status) + + @calllimit(1) + def on_offline_allowed(self, *a): + if not self: return + show_offline = profile.allow_status_changes + + if not show_offline and self.offline_item: + log.info('removing the offline item') + self.RemoveItem(self.offline_item) + self.offline_item = None + elif show_offline and not self.offline_item: + log.info('adding the offline item') + self.offline_item = self.additem([skin.get('statusicons.offline'), _('Offline')], self.on_offline) + + + def additem(self, *a, **k): + i = SimpleMenuItem(*a, **k) + self.AppendItem(i) + return i + + @calllimit(1) + def on_status_messages_changed(self, *a): + ''' + Invoked when a status message changes, or the user status list changes. + + Rebuilds the status menu items. + ''' + log.info('on_status_messages_changed, updating menu') + + self.RemoveAllItems() + additem = self.additem + + def add_status_item(pname, name): + additem([skin.get('statusicons.%s' % pname), name], + method = getattr(self, 'on_' + pname)) + + # Available + add_status_item('available', _('Available')) + + # user statuses + self.sortedstatuses = msgs = sorted([c for c in profile.statuses], + key = lambda msg: msg.away) + + # Find where to insert the special "Away" status item + j = -1 + found = False + for j, msg in enumerate(msgs): + if msg.away: + found = True + break + + for i, msg in enumerate(msgs): + if found and i == j: + add_status_item('away', _('Away')) + online_image = skin.get('statusicons.away' if msg.away else 'statusicons.available') + additem([online_image, msg.title], method = lambda mi, msg=msg: self.setandshow(msg), id = i) + + if not found or j == -1: + add_status_item('away', _('Away')) + + + # Custom... + additem(_('Custom...'), method = self.on_custom) + self.AppendSeparator() + if global_status_enabled(): + additem([skin.get('icons.globalstatus'), _('Global Status')], + method = self.on_global) + + log.info('updating status menu with %d extra statuses', len(Hook('digsby.im.statusmessages'))) + for msg in Hook('digsby.im.statusmessages'): + message = msg() + if message is None: + continue + additem([message.icon, message.title], method = lambda mi, msg=msg: self.setandshow(msg())) + + if global_status_enabled(): + additem([skin.get('statusicons.promote'), _('Promote Digsby!')], + method = self.on_promote) + + # Invisible + additem([skin.get('statusicons.invisible'), _('Invisible')], self.on_invisible) + + # Offline + self.offline_item = None + self.on_offline_allowed() + + + # + # special entries in the status menu. + # + + def on_offline(self, combo_item): + self.setandshow(StatusMessage.Offline) + + def on_available(self, comboitem): + self.show_status(StatusMessage.Available) + self.display.TypeField() + + def on_away(self, comboitem): + self.show_status(StatusMessage.Away) + self.display.TypeField() + + def on_custom(self, combo_item): + edit_custom_status(self) + + def on_global(self, combo_item): + wx.CallAfter(wx.GetApp().SetStatusPrompt) + + def on_promote(self, combo_item): + wx.CallAfter(wx.GetApp().SetStatusPrompt, 'ALL', PROMOTE_STATUS_STRING(), editable = False, edit_toggle = False) + + def on_nowplaying(self, combo_item): + self.setandshow(StatusMessage.NowPlaying) + + def on_invisible(self, combo_item): + sta = self.get_profile_status() + cpy = StatusMessage.Invisible.copy(message=sta.message) + self.setandshow(cpy) + + def cancel_timer(self): + if self.timer.IsRunning(): + self.timer.Stop() + + # + # search functionality + # + + def _on_left_button(self, e): + if not self.searching: + return self.buttoncallback(self.cbutton) + + def _on_enter(self, e): + if self.searching: + self.buddylist.activate_selected_item() + self.stop_searching() + else: + e.Skip() + + def _on_key_down(self, e): + if self.searching: + if e.KeyCode == wx.WXK_ESCAPE: + self.buddylist.SetFocus() + self.stop_searching() + elif e.KeyCode in txtcontrol_keys: + e.Skip() + else: + self.buddylist.on_key_down(e) + else: + e.Skip() + + def _interpret_char_event(self, e): + key = None + backspace = False + + if e is not None: + mod = e.Modifiers & ~wx.MOD_SHIFT + if e.KeyCode == wx.WXK_BACK: + backspace = True + elif mod or e.KeyCode <= ord(' ') or e.KeyCode in non_alphanumeric: + return key, backspace + else: + key = unichr(e.UnicodeKey) + + return key, backspace + + + def ShowSearchHint(self): + self.searchHintShown = True + + def size_like(img, i): + img = img.ResizedSmaller(max(i.Width, i.Height)).PIL + return img.ResizeCanvas(i.Width, i.Height).WXB + + self.cbutton.SetIcon(size_like(skin.get('StatusPanel.SearchIcon'), self._button_icon)) + self.DropDownButton.SetIcon(skin.get('StatusPanel.CancelSearchIcon')) + self.display.DisplayLabel = _("Press 'Ctrl+F' to Search List") + + def HideSearchHint(self): + self.SetButtonIcon(self._button_icon) + self.DropDownButton.SetIcon(self.dropdownicon) + self.searchHintShown = False + self.display.DisplayLabel = None + + def search(self, e=None): + if not pref('search.buddylist.enabled', True): + if e is not None: e.Skip() + return + + key, backspace = self._interpret_char_event(e) + + def size_like(img, i): + img = img.ResizedSmaller(max(i.Width, i.Height)).PIL + return img.ResizeCanvas(i.Width, i.Height).WXB + + icon = skin.get('StatusPanel.SearchIcon') + self.ForceTextFieldBackground = True + self.cbutton.SetIcon(size_like(icon, self._button_icon)) + self.DropDownButton.SetIcon(skin.get('StatusPanel.CancelSearchIcon')) + self.searching = True + if not hasattr(self, 'search_timer'): + self.search_timer = wx.PyTimer(self.on_search_timer) + self.search_timer.Start(500) + + self.display.TypeField() + + # emulate a keypress if one started the search + self.TextField.ChangeValue(profile.blist.search_string) + + if key is not None: + self.TextField.AppendText(key) + if backspace: + # emulate a backspace + size = self.TextField.LastPosition + self.TextField.Remove(size-1, size) + + self.OnActivateSearch() + + def on_search_timer(self): + active = wx.GetActiveWindow() + focused = wx.Window.FindFocus() + + if active is None or not self.searching: + self.stop_searching() + + if not hasattr(self, '_allowed_windows'): + # active windows search will stick around for + from gui.infobox.infobox import InfoBox + from gui.buddylist.buddylistframe import BuddyListFrame + from gui.searchgui import SearchEditDialog + + self._allowed_windows = frozenset([InfoBox, BuddyListFrame, SearchEditDialog]) + self._empty_textfield_cancels = frozenset([BuddyListFrame]) + + clz = active.__class__ + + if clz not in self._allowed_windows: + self.stop_searching() + + # if search loses focus to the buddylist and there is no text in the + # search field, just cancel the search + elif clz in self._empty_textfield_cancels and \ + focused is not self.TextField and \ + not self.TextField.Value: + self.stop_searching() + + def stop_searching(self): + if not self.searching: + return + + log.info('stopping search') + self.ForceTextFieldBackground = False + self.SetButtonIcon(self._button_icon) + self.DropDownButton.SetIcon(self.dropdownicon) + self.search_timer.Stop() + self.searching = False + focused_window = wx.Window.FindFocus() + if focused_window is self.TextField: + self.buddylist.SetFocus() + self.show_status(get_profile_status(), force=True) + self.buddylist.clear_search() + self.OnDeactivateSearch() + + hooks.notify('digsby.statistics.buddylist.search') + + def _dbutton_left(self, e): + if self.searching: + return self.stop_searching() + else: + e.Skip() + + def on_buddylist_key(self, e): + if self.searching and e.KeyCode == wx.WXK_ESCAPE: + self.stop_searching() + else: + e.Skip() + +# keys which, when focus is in the statuscombo's text control, are not +# forwarded to the buddylist when searching. +txtcontrol_keys = frozenset([ + wx.WXK_LEFT, + wx.WXK_RIGHT, + wx.WXK_NUMPAD_LEFT, + wx.WXK_NUMPAD_RIGHT +]) diff --git a/digsby/src/gui/supportdigsby/__init__.py b/digsby/src/gui/supportdigsby/__init__.py new file mode 100644 index 0000000..f92856e --- /dev/null +++ b/digsby/src/gui/supportdigsby/__init__.py @@ -0,0 +1 @@ +from supportdialog import SupportFrame \ No newline at end of file diff --git a/digsby/src/gui/supportdigsby/supportdialog.py b/digsby/src/gui/supportdigsby/supportdialog.py new file mode 100644 index 0000000..6764343 --- /dev/null +++ b/digsby/src/gui/supportdigsby/supportdialog.py @@ -0,0 +1,148 @@ +import traceback + +import wx +import gui.toolbox +import gui.wxextensions +import gui.pref.prefcontrols as pc +import supportoptions + +#import wx.lib.sized_controls as sc +#parentclass = sc.SizedPanel +_parentclass = wx.Panel + +def _SetColors(thing, fg, bg): + thing.BackgroundColour = bg + thing.ForegroundColour = fg + +class SupportPanel(_parentclass): + SPACING = 6 + def __init__(self, options, *a, **k): + self.separators = [] + self.options = options + _parentclass.__init__(self, *a, **k) + self.build() + self.do_layout() + + def OnClose(self): + pass + + def do_layout(self): + self.Layout() + self.Fit() + self.SetMinSize(self.Size) + self.SetMaxSize(self.Size) + + self.Parent.Layout() + self.Parent.Fit() + + self.Refresh() + + def build(self): + sz = pc.VSizer() + + #_SetColors(self, wx.WHITE, wx.BLACK) + + for option in self.options[:-1]: + sz.Add(self.build_option(option), border = self.SPACING, flag = wx.EXPAND | wx.ALL) + sz.Add(self.make_separator(), flag = wx.CENTER | wx.EXPAND) + + sz.Add(self.build_option(self.options[-1]), border = self.SPACING, flag = wx.EXPAND | wx.ALL) + + main_sz = pc.HSizer() + main_sz.Add(sz, 1, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = self.SPACING) + self.Sizer = main_sz + + def make_separator(self): + sz = pc.HSizer() + sz.AddSpacer((5, 0)) + line = wx.StaticLine(self, size = wx.Size(0, 2)) + #_SetColors(line, wx.WHITE, wx.BLACK) + sz.Add(line, proportion = 1, flag = wx.EXPAND | wx.CENTER) + sz.AddSpacer((5, 0)) + return sz + + def build_option(self, o): + sz = pc.HSizer() + + txt = wx.StaticText(self, label = o.description) + btn = wx.Button(self, label = o.action_text) + +# txt.SetBold(True) + #_SetColors(txt, wx.WHITE, wx.BLACK) + + sz.Add(txt, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = self.SPACING) + + help_link = getattr(o, 'help_link', None) + if help_link is not None: + from gui.toolbox import HelpLink + sz.Add(HelpLink(self, help_link)) + + sz.AddSpacer(10) + sz.AddStretchSpacer() + sz.Add(btn, flag = wx.ALIGN_RIGHT) + + btn.Bind(wx.EVT_BUTTON, self.make_click_handler(o, txt, btn)) + + return sz + + def make_click_handler(self, o, txt, btn): + def click_handler(e = None): + on_action = getattr(o, 'on_action', None) + action_url = getattr(o, 'action_url', None) + + perform_action = True + + if getattr(o, 'confirm', False) and getattr(o, 'should_confirm', lambda: True)(): + confirm_title = getattr(o, 'confirm_title', _('Are you sure?')) + confirm_msg = getattr(o, 'confirm_text', _('Are you sure you want do this?')) + default = getattr(o, 'confirm_default', True) + + if not gui.toolbox.yes_no_prompt(confirm_title, confirm_msg, default): + perform_action = False + + if perform_action: + if on_action is not None: + on_action() + + if action_url is not None: + wx.LaunchDefaultBrowser(action_url) + + if getattr(o, 'say_thanks', False): + wx.MessageBox(_("Thank you for supporting Digsby."), _("Thank you!")) + + txt.Label = o.description + btn.Label = o.action_text + + return click_handler + +class SupportFrame(wx.Frame): + def __init__(self, parent = None, **k): + title = k.pop('title', _('Support Digsby')) + style = k.pop('style', wx.DEFAULT_FRAME_STYLE & ~wx.RESIZE_BORDER & ~wx.MAXIMIZE_BOX) + components = k.pop('components', None) + if components is None: + components = [o() for o in supportoptions.get_enabled()] + wx.Frame.__init__(self, parent, title = title, style = style, **k) + self.AutoLayout = True + self._supportpanel = SupportPanel(components, self) + self.Fit() + + try: + import gui.skin as skin + self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon')) + except Exception: + traceback.print_exc() + + self.CenterOnScreen() + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnClose(self, e = None): + self._supportpanel.OnClose() + del self._supportpanel + self.Destroy() + +if __name__ == '__main__': + a = wx.PySimpleApp() + f = SupportFrame.MakeOrShow() + a.MainLoop() diff --git a/digsby/src/gui/supportdigsby/supportoptions.py b/digsby/src/gui/supportdigsby/supportoptions.py new file mode 100644 index 0000000..0653045 --- /dev/null +++ b/digsby/src/gui/supportdigsby/supportoptions.py @@ -0,0 +1,144 @@ +try: + _ +except NameError: + _ = lambda s: s + +class SupportMechanism(object): + action_text = None # "button label" + description = None # 'Short description' + tooltip = None # 'perhaps a longer description of how this helps digsby' + action_url = None # url to open in browser + say_thanks = False # do alert dialog that says "thanks" ? + + confirm = False # ask if they're sure + confirm_text = None # how to ask + confirm_title = None # title for message box + confirm_default = True # default for confirmation + + def on_action(self): + pass + +class SearchHomepage(SupportMechanism): + action_text = _('Set') + description = _('Make Google Powered Digsby Search your homepage') + say_thanks = True + + def on_action(self): + # set digsby homepage or set the file marker and trigger the home-page-setter thingy + import hooks + hooks.notify("digsby.browsersettings.set_homepage") + +class SearchEngine(SupportMechanism): + action_text = _('Set') + description = _('Make Google Powered Digsby Search your default search engine') + say_thanks = True + + def on_action(self): + # set digsby search engine or set the file marker and trigger the search-engine-setter thingy + import hooks + hooks.notify("digsby.browsersettings.set_search") + +class EmailInviter(SupportMechanism): + action_text = _('Invite') + description = _('Invite your friends via Email') + action_url = 'http://www.digsby.com/invite/' + +class FacebookInviter(SupportMechanism): + action_text = _('Invite') + description = _('Invite your friends via Facebook') + action_url = 'http://apps.facebook.com/digsbyim/invite.php' + +class FacebookFan(SupportMechanism): + action_text = _('Join Group') + description = _('Become a fan on Facebook') + action_url = 'http://www.facebook.com/apps/application.php?id=5895217474' + +class LinkedInFan(SupportMechanism): + action_text = _('Join Group') + description = _('Become a fan on LinkedIn') + action_url = 'http://www.linkedin.com/groups?gid=911917' + +class TwitterFollow(SupportMechanism): + action_text = _('Follow') + description = _('Follow us on Twitter') + action_url = 'http://www.twitter.com/digsby' + +class Research(SupportMechanism): + description = _('Help Digsby conduct research') + + confirm = False + confirm_text = _("Helping us conduct research keeps Digsby free and ad-free. Are you sure you want to disable this option?") + confirm_title = _("Are you sure?") + confirm_default = False + + help_link = 'http://wiki.digsby.com/doku.php?id=cpuusage' + + def should_confirm(self): + import common + return common.profile.localprefs['research.enabled'] + + def _get_action_text(self): + import common + if common.profile.localprefs['research.enabled']: + return _('Disable') + else: + return _('Enable') + + action_text = property(_get_action_text) + + def on_action(self): + import common + common.profile.localprefs['research.enabled'] = not common.profile.localprefs['research.enabled'] + +class BlogSubscribe(SupportMechanism): + action_text = _('Subscribe') + description = _('Subscribe to our Blog') + action_url = 'http://blog.digsby.com/feed' + +class CreateWidget(SupportMechanism): + action_text = _('Create') + description = _('Create a Digsby widget for your blog or website') + action_url = 'http://widget.digsby.com/' + +class IncrementCounter(SupportMechanism): + def _get_action_text(self): + return 'Set to %r' % (getattr(self, 'counter', 0) + 1) + + def _get_description(self): + return 'Increment this number: %r' % getattr(self, 'counter', 0) + + action_text = property(_get_action_text) + description = property(_get_description) + + def on_action(self): + setattr(self, 'counter', getattr(self, 'counter', 0) + 1) + +class TextElongonator(SupportMechanism): + action_text = 'More!' + description = 'Add dashes to this text label ' + + def on_action(self): + self.description += '-' + +enabled = [ + #TextElongonator, + #IncrementCounter, + SearchHomepage, + EmailInviter, + FacebookInviter, + FacebookFan, + LinkedInFan, + TwitterFollow, + #Research, + BlogSubscribe, + CreateWidget, + ] + +def get_enabled(): + import common + myenabled = enabled[:] + if common.pref('support.show_research_option', type = bool, default = False): + if Research not in myenabled: + myenabled.insert(-2, Research) + + return myenabled diff --git a/digsby/src/gui/taskbar.py b/digsby/src/gui/taskbar.py new file mode 100644 index 0000000..c327a83 --- /dev/null +++ b/digsby/src/gui/taskbar.py @@ -0,0 +1,151 @@ +import wx +from gui.toolbox import to_icon +from logging import getLogger; log = getLogger('taskbar') +import config +from gui.animation import Animation + +DEFAULT_TASKBAR_ID = 99 + +class TaskBarIconBase(wx.TaskBarIcon): + def __init__(self, id = DEFAULT_TASKBAR_ID): + try: + # our windows build has a wxTaskBarIcon with an extra ID argument + # so that Windows can track settings for them across program runs + wx.TaskBarIcon.__init__(self, id) + except TypeError: + wx.TaskBarIcon.__init__(self) + + # FIXME: We need to use CreatePopupMenu on all platforms rather than handling RIGHT_UP, + # but we can't do that until we have a mechanism for defining callbacks for it elsewhere. + if config.platform != 'mac': + self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnRightUp) + + @property + def _IconSize(self): + return native_taskbar_icon_size() + + def Destroy(self): + if getattr(self, '_icon_destroyed', False): + return log.critical('destroyed %r already. not destroying it again', self) + + log.info('Destroying %r', self) + self._icon_destroyed = True + wx.TaskBarIcon.Destroy(self) + + def OnRightUp(self, e): + menu = self.CreatePopupMenu() + if menu is not None: + menu.PopupMenu() + + def CreatePopupMenu(self): + return self.Menu + + def SetMenu(self, menu): + self._menu = menu + + # fixes weird taskbar behavior with UMenu + menu.Windowless = True + + def GetMenu(self): + return getattr(self, '_menu', None) + + Menu = property(GetMenu, SetMenu) + + def Refresh(self): + 'Resets the icon...used to prevent Windows from hiding tray icons.' + if not wx.IsDestroyed(self): + self.SetIcon(self.GetIcon(), self._tooltip) + + +class AnimatedTaskBarIcon(TaskBarIconBase): + def __init__(self, id = DEFAULT_TASKBAR_ID): + TaskBarIconBase.__init__(self, id) + + self.icons = [None] + self.delays = [1000] + + self.animation = Animation() + self.animation.add_listener(self._update_tray) + + self._tooltip = '' + + def SetTooltip(self, tooltip): + if tooltip != self._tooltip: + self._tooltip = tooltip + self._update_tray() + + def UpdateAnimation(self, tooltip = None): + if tooltip is not None: + assert isinstance(tooltip, basestring) + self._tooltip = tooltip + + self.animation.set_frames(self.icons, self.delays) + + def _update_tray(self): + if not wx.IsDestroyed(self): + self._icon = self.animation.current_frame + self.SetIcon(self._icon, self._tooltip) + + def GetIcon(self): + return self._icon + +class DigsbyTaskBarIcon(TaskBarIconBase): + def __init__(self, icon, menu = None, tooltip = None, id = DEFAULT_TASKBAR_ID): + TaskBarIconBase.__init__(self, id) + + if isinstance(icon, wx.WindowClass): + raise TypeError + + self._tooltip = '' + + if icon is not None: + self.SetIcon(icon, tooltip) + + if menu is not None: + self.Menu = menu + + def SetIcon(self, icon_or_bitmap, tooltip = None): + ''' + Sets an icon and tooltip for this tray item. + + If tooltip is None, the icon's tooltip will not be changed. + ''' + + size = self._IconSize + + # + # use PIL to reduce aliasing (on MSW the tray icon size is often a lot smaller than + # our source images) + # + # TODO: On mac, what is the best solution for the Dock? + # + self._icon = to_icon(icon_or_bitmap.PIL.ResizedSmaller(size).ResizeCanvas(size, size)) + + if tooltip is None: + tooltip = self._tooltip + else: + self._tooltip = tooltip + + wx.TaskBarIcon.SetIcon(self, self._icon, tooltip) + + def GetIcon(self): + return self._icon + + Icon = property(GetIcon, SetIcon) + + +if config.platform == 'mac': + def native_taskbar_icon_size(): + return 128 +elif config.platform == 'gtk': + #CAS: SMALLICON gave -1 on my system, ICON returns 32 + #32 looks like crap, 20 looked about right for my 24 pixel panel. + #there's got to be a better way, considering there might not even be a tray, + #and the height of these is shrunk to the height of the panel I have it on. + def native_taskbar_icon_size(): + return 20 +else: + GetMetric = wx.SystemSettings.GetMetric + def native_taskbar_icon_size(): + return GetMetric(wx.SYS_SMALLICON_X) + diff --git a/digsby/src/gui/textutil.py b/digsby/src/gui/textutil.py new file mode 100644 index 0000000..01d52c2 --- /dev/null +++ b/digsby/src/gui/textutil.py @@ -0,0 +1,403 @@ +import wx, string +from util.introspect import memoize +from util.primitives import Point2HTMLSize +from util.xml_tag import tag +from util.lrucache import LRU + +def FindAny(s, chrs, start = None, end = None): + chrs = set(chrs) + + for i in xrange(start or 0, end or len(s)): + if s[i] in chrs: + return i + + return -1 + +def rFindAny(s,chrs,start=None,end=None): + chrs = set(chrs) + + for i in xrange(end-1 or len(s)-1, start or 0, -1): + if s[i] in chrs: + return i + + return -1 + + +def ContainsNumbers(s): + return FindAny(s, string.digits) != -1 + + +rtlCharRanges = [(0x202B, 0x202B), #http://www.fileformat.info/info/unicode/char/202b/index.htm + (0x0590, 0x05FF), + (0x0600, 0x06FF), + (0x0750, 0x077F), + (0xFB1D, 0xFB40), + (0xFB50, 0xFDFF), + (0xFE70, 0xFEFF)] + +def isRTL(char): + charcode = ord(char) + for charRange in rtlCharRanges: + if charcode >= charRange[0] and charcode <= charRange[1]: + return True + return False + +# +# A side effect of passing both font and dc parameters to the following +# functions makes that font active in the DC. +# + + +VISTA_SHELL_FONT = u'Segoe UI' +VISTA_SHELL_FONT_SIZE = 9 + +_return_default_font = None + +def _find_default(): + global _return_default_font + faces = GetFonts() + + # Until wxWidgets uses the correct font on Vista, fake it. + try: + import ctypes + ctypes.windll.dwmapi + vista = True + except: + vista = False + + + if vista and VISTA_SHELL_FONT in faces: + _return_default_font = lambda: \ + wx.Font(VISTA_SHELL_FONT_SIZE, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_NORMAL, False, VISTA_SHELL_FONT) + else: + _return_default_font = lambda: wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + +def default_font(): + 'Returns the default system GUI font.' + + global _return_default_font + if _return_default_font is None: + _find_default() + + return _return_default_font() + + +def shorten_all_links(textctrl, ondone=None, timeoutms=5000): + '''Disables the text control, shortens all of its links, then enables it.''' + + import sys + from util.net import LinkAccumulator, get_short_url, is_short_url + from util import threaded + + links = LinkAccumulator(textctrl.Value) + + # since we're using string.replace, make a set + all_links = set(links.links) + + # don't shorten already shortened URLs + all_links = [l for l in all_links if not is_short_url(l)] + + if not all_links: return + + links.count = len(all_links) + + links.finished = False + def finish(): + @wx.CallAfter + def after(): + if not links.finished: + links.finished = True + + textctrl.Enable() + if ondone is not None: + ondone() + + # re-enable the text control after a timeout. + if timeoutms is not None: + wx.CallLater(timeoutms, finish) + + textctrl.Disable() + for link in all_links: + def shorten(link=link): # close over link + def one_link_shortened(): + links.count -= 1 + if links.count == 0: + finish() + + def success(short_url): + @wx.CallAfter + def after(): + if links.finished: return + # TODO: use adjusting spans. + textctrl.SetValue(textctrl.Value.replace(link, short_url)) + one_link_shortened() + + def error(): + print >> sys.stderr, 'error shortening link: %r' % link + one_link_shortened() + + threaded(get_short_url)(link, success=success, error=error) + + shorten(link) + +class VisualCharacterLimit(object): + '''A "visual validator" that highlights any text in a wxTextCtrl that is + over a specified limit.''' + + def __init__(self, textctrl, limit, length=None): + self.ctrl = textctrl + self.limit = limit + self.length = length if length is not None else len + + # TODO: text controls' default styles are not always black-on-white + self.default_style = wx.TextAttr(wx.BLACK, wx.WHITE) + self.limit_style = wx.TextAttr(wx.WHITE, wx.RED) + + self.needs_style_update = False + self.in_set_style = False + + self.ctrl.Bind(wx.EVT_TEXT, self._on_text) + self._on_text() + + def SetLimit(self, limit, refresh=True): + self.limit = limit + if refresh: self._on_text() + + def _on_text(self, e=None): + if e is not None: e.Skip() + + if self.in_set_style: + return + + value = self.ctrl.Value + limit = self.limit(value) if callable(self.limit) else self.limit + + if self.needs_style_update or self.length(value) > limit: + self.needs_style_update = True + + self.in_set_style = True + self.ctrl.SetStyle(0, limit, self.default_style) + self.ctrl.SetStyle(limit, self.ctrl.LastPosition, self.limit_style) + self.in_set_style = False + else: + self.needs_style_update = False + +# plz see http://style.cleverchimp.com/font_size_intervals/altintervals.html +aimsizes= { 8: 1, + 10: 2, + 12: 3, + 14: 4, + 18: 5, + 24: 6, + 36: 7 } + +# + +def attrToHTML(textattr): + font = textattr.Font + + attrs = {} + if textattr.HasTextColour() and textattr.GetTextColour() != wx.BLACK: + attrs['color'] = textattr.GetTextColour().GetAsString(wx.C2S_HTML_SYNTAX) + + attrs.update(size = str(aimsizes.get(font.PointSize // 15, 4)), + face = unicode(font.FaceName)) + + start = tag('font', **attrs)._to_xml(self_closing = False, pretty = False)[:-7] + end = '' + + if font.Weight == wx.FONTWEIGHT_BOLD: + start += ''; end = '' + end + if font.Style == wx.FONTSTYLE_ITALIC: + start += ''; end = '' + end + if font.Underlined: + start += ''; end = '' + end + + return start, end + +def TagFont(string, fonttype, fonts): + font = fonts[fonttype] + color = fonts['%sfc'%fonttype] + tag = u''.join(['', + '' if font.Weight == wx.BOLD else '', + '' if font.Style == wx.ITALIC else '', + '' if font.Underlined else '', + string if isinstance(string, unicode) else string.decode('utf-8'), + '' if font.Underlined else '', + '' if font.Style == wx.ITALIC else '', + '' if font.Weight == wx.BOLD else '', + '']) + return tag + +def tagfontxml(string, fonttype, fonts): + from lxml.builder import E + font = fonts[fonttype] + color = fonts['%sfc'%fonttype] + out = string + if isinstance(out, str): + out = out.decode('utf-8') + conditions = ((font.Underlined, 'u'), + (font.Style == wx.ITALIC, 'i'), + (font.Weight == wx.BOLD, 'b')) + for condition, tag in conditions: + if condition: + out = getattr(E, tag)(out) + out = E.span(out, + style = 'font-family: %(facename)s; font-size: %(size)ipt; color: %(color)s;' % + {'facename': font.FaceName, + 'size': font.PointSize, + 'color': color}) + return out + + + +font_init_args = ('pointSize', 'family', 'style', 'weight', 'underline', 'faceName', 'encoding') + +def CopyFont(font, **kwargs): + """ + pointSize + family + style + weight + underline + faceName + encoding + """ + + f=fontkwargs = dict( + pointSize = font.PointSize, + family = font.Family, + style = font.Style, + weight = font.Weight, + underline = font.Underlined, + faceName = font.FaceName, + encoding = font.Encoding + ) + fontkwargs.update(kwargs) + + if 'underlined' in fontkwargs: + fontkwargs['underline'] = fontkwargs.pop('underlined') + + # FIXME: Remove workaround once everything moves to SIP or wx.Font.init_args + # is available everywhere. + init_args = font_init_args + if hasattr(wx.Font, "init_args"): + init_args = wx.Font.init_args + + return wx.Font(*(f.get(a) for a in init_args)) + +if 'wxMac' in wx.PlatformInfo: + get_measuring_context = lambda: wx.ClientDC(wx.GetTopLevelWindows()[0]) +else: + get_measuring_context = wx.MemoryDC + + +_sizecache = LRU(100) +def GetMultilineTextSize(text, font = None, dc = None): + assert font or dc + + dc = dc or get_measuring_context() + + if font: dc.SetFont(font) + else: font = dc.Font + + nativeinfo = font.NativeFontInfoDesc + text + try: + ext = _sizecache[nativeinfo] + except KeyError: + _sizecache[nativeinfo] = ext = dc.GetMultiLineTextExtent(text)[:2] + + return wx.Size(*ext) + +_widthcache = LRU(100) +def GetTextWidth(line, font = None, dc = None): + assert font or dc + + dc = dc or get_measuring_context() + + if font: dc.SetFont(font) + else: font = dc.Font + + nativeinfo = font.NativeFontInfoDesc + line + try: + width = _widthcache[nativeinfo] + except KeyError: + _widthcache[nativeinfo] = width = dc.GetFullTextExtent(line)[0] + + return width + + +# fonts in a cache dictionary --> they need a __hash__ method +wx.Font.__hash__ = lambda f: hash(f.NativeFontInfoDesc) + +_heightcache = LRU(100) +def GetFontHeight(font = None, dc = None, line_height = False, descent = False): + 'Calculates the height of a font in pixels.' + + assert font or dc + + dc = dc or get_measuring_context() + + if font: dc.SetFont(font) + else: font = dc.Font + + nativeinfo = font.NativeFontInfoDesc + try: + extents = _heightcache[nativeinfo] + except KeyError: + _heightcache[nativeinfo] = extents = dc.GetFullTextExtent(string.ascii_letters) + + if line_height: + return extents[1] + if descent: + return extents[2] + else: + return extents[1] - extents[2] + extents[3] + +wx.Font.GetHeight = GetFontHeight +wx.Font.Height = property(lambda f: GetFontHeight(f)) +wx.Font.LineHeight = property(lambda f: GetFontHeight(f, line_height = True)) +wx.Font.Descent = property(lambda f: GetFontHeight(f, descent = True)) + +def GetTextExtent(text, font = None, dc = None): + 'Returns the width and hight of string text in supplied font.' + + dc = dc or get_measuring_context() + + if font: dc.SetFont(font) + return dc.GetTextExtent(text) + + +def DeAmp(text): + return text.replace('&', '', 1) + +from cgui import truncateText as TruncateText + +def dcDTTfunc(self, text, rect, alignment = wx.ALIGN_LEFT | wx.ALIGN_TOP, indexAccel = -1): + self.DrawLabel(TruncateText(text, rect.width, None, self), rect, alignment, indexAccel) + +wx.DC.DrawTruncatedText = dcDTTfunc + +def dcDTTfuncInfo(self, text, rect, alignment = wx.ALIGN_LEFT | wx.ALIGN_TOP, indexAccel = -1): + 'Returns true if text was cut off.' + txt = TruncateText(text, rect.width, None, self) + self.DrawLabel(txt, rect, alignment, indexAccel) + return txt == text + +wx.DC.DrawTruncatedTextInfo = dcDTTfuncInfo + +#from cgui import DrawTruncated +#wx.DC.DrawTruncatedText = DrawTruncated + + +@memoize +def GetFonts(): + return sorted(set(f.lstrip('@') for f in wx.FontEnumerator().GetFacenames())) + +# wxFont's constructor takes the following arguments +fontattrs = ('PointSize', 'Family', 'Style', 'Weight', 'Underlined', 'FaceName') + +from cgui import Wrap diff --git a/digsby/src/gui/toast/__init__.py b/digsby/src/gui/toast/__init__.py new file mode 100644 index 0000000..3a21474 --- /dev/null +++ b/digsby/src/gui/toast/__init__.py @@ -0,0 +1,2 @@ +from .toaststack import PopupStack +from .toast import Popup, popup, cancel_all, cancel_id \ No newline at end of file diff --git a/digsby/src/gui/toast/toast.py b/digsby/src/gui/toast/toast.py new file mode 100644 index 0000000..21f1f76 --- /dev/null +++ b/digsby/src/gui/toast/toast.py @@ -0,0 +1,1531 @@ +''' +Popup notification windows. + +The Popup class looks for special optional keyword arguments to "fire" (see +common/notifications.py) which are passed to its constructor and control its +behavior: + + input Must be a callable. If given, an input box at the bottom of the + popup will be shown for entering text. On an enter keypress, + the "input" callable will be called with two arguments: + + 1) the input, a string + 2) a dictionary of all keyword arguments given to Popup's + constructor + + pages Must be a string. If given, the Popup expects another argument + keyed by the value of this option. This other option must then + be a sequence of objects which will be shown "paginated" and the + user can flip through them with buttons shown at the bottom of + the popup. + + onclick Must be a callable.* If given, the mouse cursor will become a + "hand" when over any area of the Popup that isn't a button or a + text control. If this area is clicked, onclick will be called with + the current "Item". + + *Can also be a string URL. If so, clicking the popup will launch the + default browser with the given URL. + + onclose Callable, called when the Popup disappears. + + position If given must be one of the following strings: + + 'lowerright', 'upperright', 'upperleft', 'lowerleft' + + The Popup will show in the specified corner, regardless of the + user's preferences. + +''' +from __future__ import with_statement + +import wx, sys, traceback +import hooks +from wx import Size, StockCursor, CURSOR_HAND, \ + CURSOR_DEFAULT, CallLater, Point, \ + BG_STYLE_CUSTOM, Bitmap +from wx import VERTICAL, HORIZONTAL, EXPAND, TOP, LEFT, RIGHT, BOTTOM, ALIGN_CENTER_VERTICAL +from wx import BoxSizer as wxBoxSizer, FindWindowAtPointer + +from operator import attrgetter + +from util.primitives.error_handling import try_this, traceguard +from util.primitives.funcs import Delegate +from util.primitives.mapping import Storage as S +from util.primitives.strings import curly + +from gui import skin +from gui.textutil import default_font, TruncateText, VisualCharacterLimit +from gui.skin.skinobjects import Margins +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.cleartext import ClearText as statictext +from gui.uberwidgets import UberWidget +from gui.toast import PopupStack +from cgui import BorderedFrame, fadeout + +from gui.validators import LengthLimit + +from common import pref, prefprop + +from logging import getLogger; log = getLogger('popup'); info = log.info + +log_debug = log.debug + +position_map = dict(upperright = TOP | RIGHT, + upperleft = TOP | LEFT, + lowerleft = BOTTOM | LEFT, + lowerright = BOTTOM | RIGHT) + +# how long it takes a popup to fade if the mouse is over it. +LONG_FADE_TIME_MS = 60 * 1000 + +def get_popups(): + 'Returns a sequence of all visible popups.' + + popups = [] + append = popups.append + + for monitor in Popup.Stacks.values(): + for stack in monitor.values(): + for popup in stack: + append(popup) + + return popups + +def cancel_id(id): + for popup in get_popups(): + if getattr(popup, 'popupid', sentinel) == id: + with traceguard: + popup.cancel() + + log.info('Cancelled all popups with id of %r', id) + +def cancel_all(): + for popup in get_popups(): + popup.cancel() + +def popup(**options): + 'Displays a Popup window using keyword args as options.' + + if sys.DEV: + log.debug_s('Got popup request with options: %s', repr(options)[:80]) + + initial_setup() + + if not options.get('always_show', False): + import gui.native.helpers as helpers + if not pref('notifications.enable_popup', True) or \ + (pref('fullscreen.disable_popups', default = True, type = bool) and + helpers.FullscreenApp()): + return + + transform_popup_options(options) + + # If options has a popupid and there is a showing popup with the same + # id, then don't create a new one--call update_contents on it with + # the new options instead. + popupid = options.get('popupid', None) + if popupid is not None: + for p in get_popups(): + if p and p.popupid == popupid and not p.InputMode: + p.update_contents(options) + return p + + hooks.notify('popup.pre', options) + p = Popup(None, options = options) + + if sys.DEV: + log.debug_s('Displaying a popup that had options: %r', repr(options)[:80]) + + p.Display() + return p + +# Disable on windows 2000 (where popups cause the GUI to freeze) +from config import platformName +if platformName == 'win': + import platform + if 'Windows-2000' in platform.platform() or platform.release() == '2000': + + # replace popup() function with a no-op + globals().update(popup = lambda **options: None) + PopupBase = BorderedFrame +else: + PopupBase = wx.Frame + PopupBase.GetAlpha = lambda s: 255 + +class PopupItem(object): + 'The data for one page of a popup.' + + _blacklist = ['header','major','minor','icon','buttons','input', + 'onclick','page_noun','position','sticky','time', + 'popupid','merge_onclick','contents', '_options'] + + _copyattrs = ('header', 'major', 'minor', 'icon', 'buttons', 'input', 'onclick', 'target') + + def __repr__(self): + return '' % ' - '.join(repr(i) for i in (self.header, self.major, self.minor)) + + def __init__(self, options): + self._options = options + + get = options.get + + for attr in self._copyattrs: + setattr(self, attr, get(attr, None)) + + if 'pages' in options: + self.page_noun = get('pages')[:-1] + self._blacklist.append(self.page_noun) + + item = options[self.page_noun] + setattr(self, self.page_noun, item) + + # icon may come from item + if hasattr(item, 'icon'): + setattr(self, 'icon', item.icon) + else: + self.page_noun = None + + self._apply_options() + + def get_icon(self, refresh_cb=None): + if hasattr(self.icon, 'lazy_load'): + try: + return self.icon.lazy_load(refresh_cb) + except Exception: + traceback.print_exc() + return None + + return self.icon + + def get_icon_badge(self): + return getattr(self, 'icon_badge', None) + + def _apply_options(self, options=None, all=False): + + if options is not None: + self._options = options + + for k, v in self._options.iteritems(): + if all or k not in self._blacklist: + setattr(self, k, v) + + @property + def _max_lines(self): + return getattr(self, 'max_lines', self.pref_max_lines) + + pref_max_lines = prefprop('notifications.popups.max_lines', 2) + + @property + def contents(self): + if self.page_noun is None: + return None + else: + return self._options.get(self.page_noun) + +class PopupItemList(list): + ''' + Holds the series of PopupItems shown by a popup with pages. + ''' + + paged_originally = False + def __init__(self, options): + if options is None: + return + + if 'pages' in options and options['pages'] in options: + self.paged_originally = True + items = options[options['pages']] + + for item in items: + new = options.copy() + new[new['pages'][:-1]] = item + + self.append(PopupItem(new)) + else: + self.append(PopupItem(options)) + + def get_icon_and_preload_adjacent(self, n, refresh_cb=None): + ''' + Retreives the icon for page n. Also makes a request for icons at pages + n-1 and n+1, so that if they are lazy, they will hopefully be preloaded + by the time the user flips to them. + ''' + + icon = self[n].get_icon(refresh_cb) + + # preload next and previous icons when paging through items + before = (n - 1) % len(self) + after = (n + 1) % len(self) + for m in set([n, before, after]) - set([n]): + self[m].get_icon(None) + + return icon + + +class PopupHoverMixin(object): + + def __init__(self): + self._hover = None + self._hover_check_enabled = True + self._in_check_mouse = False + self.hover_timer = wx.PyTimer(self.CheckMouse) + + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_ENTER_WINDOW, self.CheckMouse) + self.Bind(wx.EVT_LEAVE_WINDOW, self.CheckMouse) + + def OnMotion(self, e): + e.Skip() + self.Hover = True + + def CheckMouse(self, e = None): + # strange thread stacks are showing that this method can recurse... + # guard against that. + if self._in_check_mouse or wx.IsDestroyed(self): + return + + self._in_check_mouse = True + try: + if self._hover_check_enabled: + win = FindWindowAtPointer() + self.Hover = win is not None and (win.Top is self or getattr(win.Top, 'Parent', None) is self) + finally: + self._in_check_mouse = False + + def SetHover(self, val): + if val == self._hover: return + if wx.IsDestroyed(self): return + if not self._hover_check_enabled: return + + if val: + self.hover_timer.Start(200, False) + self.OnEnter() + else: + self.hover_timer.Stop() + self.OnLeave() + + self._hover = val + + def DisableHoverTracking(self): + self._hover_check_enabled = False + self.hover_timer.Stop() + + Hover = property(attrgetter('_hover'), SetHover) + +class Popup(PopupBase, UberWidget, PopupHoverMixin): + 'Implements a popup notification window.' + + duration = prefprop('notifications.popups.duration', 4000) + location = prefprop('notifications.popups.location', 'lowerright') + monitor = prefprop('notifications.popups.monitor', 1) + + added_hotkey = False + last_input = None + shared_hover = False # used to keep all paged popups in view when mousing over one of them + popup_icon_badge_size = 16 + + def __init__(self, parent, options = None, skinkey = 'popup'): + self.skinkey = skinkey + self.UpdateSkin(first = True) + if platformName == 'win': + BorderedFrame.__init__(self, parent, self.Backgrounds.Normal, self.Backgrounds.Border, + self.margins, wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR | wx.FRAME_TOOL_WINDOW) + self.ControlParent = self + else: + wx.Frame.__init__(self, parent, style=wx.FRAME_SHAPED | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR) + self.ControlParent = wx.Panel(self, -1) + self.alphaBorder = None + + self.ControlParent.ChildPaints = Delegate() + self.OnClose = Delegate() + + # TODO: See if we need BG_STYLE_TRANSPARENT to work on GTK too + if platformName == 'mac': + self.SetBackgroundStyle(wx.BG_STYLE_TRANSPARENT) + else: + self.SetBackgroundStyle(BG_STYLE_CUSTOM) + + # Setup skinning options + self._options = options + get = self._options.get + + self.position = get('position', None) + self.sticky = get('sticky', False) + self.time = get('time', self.duration) + self.popupid = get('popupid', None) + self.merge_onclick = get('merge_onclick', True) + + self.current_page = 0 + self.page_items = PopupItemList(options) + self.num_pages = len(self.page_items) + + self.has_focus = False + + self.construct() # build common elements + self.paginate() # build page controls + self.layout() # layout controls in sizers + self.size() # size the overall window + + if self.Item.onclick: + # use a hand cursor when the popup is clickable + self.ControlParent.SetCursor(StockCursor(CURSOR_HAND)) + + self.bind_events() # register event handlers + + self.fade_timer = wx.PyTimer(self.DoFade) + self.long_fade_timer = wx.PyTimer(self.DoLongFade) + + PopupHoverMixin.__init__(self) + self.CheckMouse() + + if not Popup.added_hotkey: + # temporarily removed from release until we have keyboard shortcuts GUI + if getattr(sys, 'DEV', False) and hasattr(wx.App, 'AddGlobalHotkey'): + wx.GetApp().AddGlobalHotkey(((wx.MOD_CMD|wx.MOD_ALT), ord('q')), Popup.focus_next_popup_input) + wx.GetApp().AddGlobalHotkey((wx.MOD_CMD| wx.MOD_ALT, wx.WXK_LEFT), lambda e: Popup.recent_popup_flip_page(-1)) + wx.GetApp().AddGlobalHotkey((wx.MOD_CMD| wx.MOD_ALT, wx.WXK_RIGHT), lambda e: Popup.recent_popup_flip_page(1)) + Popup.added_hotkey = True + + if not hasattr(Popup, 'shared_hover_timer'): + Popup.shared_hover_timer = wx.PyTimer(Popup.on_shared_hover_timer) + + def OnEnter(self, e=None): + self._did_enter = True + self.fade_timer.Stop() + + if getattr(self, 'fade', None) is not None: + self.fade.Stop(False) + self.fade = None + + self.SetTransparent(self.opacity_hover) + Popup.shared_hover = True + Popup.shared_hover_timer.Start(500, False) + + def OnLeave(self): + self.SetTransparent(self.opacity_normal) + self.fade_timer.StartOneShot(self.time) + + if not getattr(self, '_did_enter', False): return + + self.stop_shared_hover() + + @classmethod + def stop_shared_hover(cls): + # Set shared hover flag to False, and start the fade timers of all + # popups who stay visible because of it. + notify = [p for p in get_popups() if p.SharedHover] + cls.shared_hover = False + for p in notify: + p.fade_timer.Start() + + @classmethod + def on_shared_hover_timer(cls): + if not isinstance(getattr(wx.FindWindowAtPointer(), 'Top', None), cls): + cls.shared_hover_timer.Stop() + cls.stop_shared_hover() + + @property + def opacity_normal(self): + return opacity_to_byte(pref('notifications.popups.opacity.normal', default=100, type=int)) + + @property + def opacity_hover(self): + return opacity_to_byte(pref('notifications.popups.opacity.hover', default=100, type=int)) + + def DoFade(self, *e): + ''' + Called by a timer when it might be time for this popup to fade away. + ''' + + if wx.IsDestroyed(self) or not self.ShouldFade or self.Hover: + return + + self._FadeNow() + + def _FadeNow(self): + if not wx.IsDestroyed(self) and getattr(self, 'fade', None) is None: + self.fade = fadeout(self, self.GetAlpha(), 0, 8, self.HideAndDestroy) + + def DoLongFade(self): + ''' + Called by a timer when it might be time for this popup to fade away, + even if the mouse is over it. + ''' + if not pref('notifications.popups.enable_long_fade', default = True, type = bool): + return + + if self.has_focus or self.sticky: + # has_focus is True when this popup has an input field that has + # focus right now--we never want to fade away in that situation, + # so just restart the timer. + self.start_long_fade_timer() + else: + self._FadeNow() + + def start_long_fade_timer(self): + ''' + Starts the timer that fades away the popup even when the mouse is over + it. + ''' + self.long_fade_timer.Start(LONG_FADE_TIME_MS, True) + + def reset_long_fade_timers(self): + ''' + Resets all "long fade" timers for popups that have SharedHover == True, + always including this one. + ''' + for p in get_popups(): + if p.SharedHover: + p.start_long_fade_timer() + + self.start_long_fade_timer() + + def Display(self): + from common import profile + + if profile: + self.Stack.Add(self) + else: + log.info('Not showing popup %r because profile is not available.', self) + + def cancel(self): + if not wx.IsDestroyed(self): + wx.CallAfter(self.HideAndDestroy) + + def UpdateSkin(self, first = False): + root = s = self.skin = skin.get(self.skinkey) + + fonts, colors = s.fonts, s.fontcolors + if hasattr(self, 'header_txt'): + h, m, i = self.header_txt, getattr(self, 'major_txt', None), self.minor_txt + + h.Font = fonts.title + h.ForegroundColour = colors.title + if m is not None: + m.Font = fonts.major + m.ForegroundColour = colors.major + i.Font = fonts.minor + i.ForegroundColour = colors.minor + + if hasattr(self, 'page_txt'): + self.page_txt.SetFont(fonts.minor) + self.page_txt.SetFontColor(colors.minor) + + buttonskin = self.skin.buttonskin + + self.padding = s.get('padding', lambda: Point()) + self.margins = s.get('margins', lambda: Margins()) + + self.Backgrounds = S() + self.Backgrounds.Border = root['background'] + + marginskey = 'background_%sx%sx%sx%s' % tuple(self.margins) + + try: + normal = root[marginskey] + except KeyError: + log.info('copying %r' % root['background']) + normal = root[marginskey] = root['background'].copy(*self.margins) + + self.Backgrounds.Normal = normal + + if not first: + for child in [c for c in self.ControlParent.Children + if isinstance(c, UberButton)]: + child.SetSkinKey(buttonskin) + + self.layout() + + + def bind_events(self): + self.BBind(TIMER = self.DoFade) + self.ControlParent.BBind(PAINT = self.OnPaint, + ERASE_BACKGROUND = lambda e: None, + ENTER_WINDOW = self.OnEnter, + LEFT_DOWN = self.OnClick, + MIDDLE_UP = self.OnRightClick, + RIGHT_UP = self.OnRightClick, + MOTION = self.OnMotion, + MOUSEWHEEL = self.OnMouseWheel, + KEY_DOWN = self.OnKeyDown, + ) + + if self is not self.ControlParent: + self.Bind(wx.EVT_PAINT, self.OnBGPaint) + + if self.alphaBorder: + # forward mouse wheel events from the border to the popup + self.alphaBorder.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) + + for stext in (c for c in self.ControlParent.Children if isinstance(c, statictext)): + stext.BBind(LEFT_DOWN = self.OnClick, + MIDDLE_UP = self.OnRightClick, + RIGHT_UP = self.OnRightClick) + + def SetRect(self, rect): + if self is self.ControlParent: + BorderedFrame.SetRect(self, rect) + else: + self.bmp_rect = (rect.width, rect.height) + bmp = self.Backgrounds.Normal.GetBitmap(self.bmp_rect) + self.bmp_region = wx.RegionFromBitmap(bmp) + self.SetShape(self.bmp_region) + self.Move((rect.x, rect.y)) + + def OnBGPaint(self, e): + dc = wx.AutoBufferedPaintDC(e.EventObject) + self.Backgrounds.Normal.Draw(dc, (0, 0, self.Rect.width, self.Rect.height)) + + def OnKeyDown(self, e): + if e.KeyCode == wx.WXK_ESCAPE and e.Modifiers == 0: + self.HideAndDestroy() + else: + e.Skip() + + def OnMouseWheel(self, e = None): + e.Skip() + + if self.InputShown: # do not flip pages during input mode + return + + # TODO: Figure out this stupid API and make it nicer. + import math + wheel_rotation = e.WheelRotation + pages = int(math.copysign(1, wheel_rotation)) + reverse = pref('notifications.popups.reverse_scroll', type=bool, default=True) + self.flip_page((reverse and -1 or 1)*pages) # scrolling down progresses through pages, increasing the number + + def OnRightClick(self, e = None): + self.MarkAsRead() + + # Fade out quickly, then call HideAndDestroy + self._destroy_fade = fadeout(self, self.GetAlpha(), 0, 75, lambda: self.HideAndDestroy(userClose = True)) + + def HideAndDestroy(self, e = None, userClose = False): + '''Destroys the popup.''' + + if wx.IsDestroyed(self): + return + + self.DisableHoverTracking() + self.Hide() + + if userClose: + userclose = self._options.get('onuserclose', None) + if userclose is not None: + with traceguard: + userclose() + + close = self._options.get('onclose', None) + if close is not None: + with traceguard: + close() + + self.OnClose(userClose = userClose) + self.fade_timer.Stop() + self.long_fade_timer.Stop() + + # HACK: there are wx.CallAfter callbacks that occur after HideAndDestroy + # that need access to page_items, so do this 1 second later. + def delete_attrs(): + try: + del self._options + except AttributeError: + pass + try: + del self.page_items + except AttributeError: + pass + wx.CallLater(1000, delete_attrs) + + wx.CallAfter(self.Destroy) + + def OnMotion(self, e): + e.Skip() + + self.reset_long_fade_timers() + + cursor = StockCursor(CURSOR_HAND if self.Item.onclick else CURSOR_DEFAULT) + self.ControlParent.SetCursor(cursor) + + def RefreshIcon(self): + if not wx.IsDestroyed(self): + self.ControlParent.RefreshRect(self.IconRect) + + @property + def IconRect(self): + return (0, 0, self.skin.iconsize, self.skin.iconsize) + + def OnPaint(self, e): + 'Paints the popup.' + + dc = wx.AutoBufferedPaintDC(e.EventObject) + + if platformName == 'win': + BorderedFrame.PaintBackground(self, dc) + + icon = self.icon + if icon: + # If the PopupItem has an icon, draw i.t + iconsize = self.skin.iconsize + if (icon.Width, icon.Height) != iconsize: + icon = icon.PIL.Resized(iconsize).WXB + dc.DrawBitmap(icon, 0, 0, True) + + # If the PopupItem has an icon_badge, draw it in the lower right hand corner. + badge = self.icon_badge + if badge: + badge = badge.ResizedSmaller(self.skin.iconbadgesize) + bsize = badge.Size + + dc.DrawBitmap(badge, iconsize - bsize.width, iconsize - bsize.height, True) + + self.draw_popup_badge(dc) + self.ControlParent.ChildPaints(dc) + + @property + def PopupBadge(self): + return self.Item._options.get('badge', None) + + def draw_popup_badge(self, dc): + popup_badge = self.PopupBadge + if popup_badge is not None: + popup_badge = popup_badge.PIL.Resized(self.popup_icon_badge_size).WXB + r = self.ControlParent.ClientRect + x, y = r.Right - self.popup_icon_badge_size, r.Top + import config + if config.platform == 'win': + alpha = chr(140) + dc.DrawBitmap(popup_badge, x, y, True, alpha) + else: + dc.DrawBitmap(popup_badge, x, y, True) + + def OnClick(self, e): + # Hackishly avoid event mixups when clicking buttons. + for child in [c for c in self.ControlParent.Children if isinstance(c, UberButton)]: + if child.Rect.Contains(e.Position): + return e.Skip() + + # shift click causes sticky. TODO: document and make visual indications for this. + if wx.GetKeyState(wx.WXK_SHIFT): + self.sticky = True + return + + self.MarkAsRead() + + # an "onclick" key in the options dictionary should be a callback or a string URL + onclick = getattr(self.Item, 'onclick', None) + if onclick is None: + return e.Skip() + + # Don't hide the popup if it has multiple pages + should_hide = len(self.page_items) < 2 and not self.Item.buttons + + if should_hide: + self.Hide() + + if not self.InputMode and hasattr(self, 'input'): + # If we have an input box, but we're not in the temporary input mode, + # it's value becomes the argument to the onclick handler. + contents = self.input.Value + else: + # Otherwise it's just the active item. + contents = self.Item.contents + + # This call later is the only way I found to ensure the popup is + # hidden before a potentially long running callback (like spawning + # Firefox) is finished. + CallLater(30, handle_popup_onclick, onclick, contents) + + if should_hide: + self.HideAndDestroy() + + @property + def Item(self): + return self.page_items[self.current_page] + + def MarkAsRead(self): + item = self.Item + mark_as_read = getattr(item, 'mark_as_read', None) + if mark_as_read is not None: + with traceguard: + mark_as_read(item) + + @property + def icon(self): + icon = self.page_items.get_icon_and_preload_adjacent(self.current_page, self.RefreshIcon) + + if not isinstance(icon, Bitmap): + return skin.get('BuddiesPanel.BuddyIcons.NoIcon').Resized(self.skin.iconsize) + else: + return icon + + @property + def icon_badge(self): + badge = self.page_items[self.current_page].get_icon_badge() + if isinstance(badge, Bitmap): + return badge + + @property + def ShouldFade(self): + return (not self.has_focus and + not self.sticky and + not self.SharedHover and + not self.InputMode) + + @property + def SharedHover(self): + return len(self.page_items) > 1 and Popup.shared_hover + + def size(self, keep_pos = False): + self.DesiredSize = Size(self.skin.width, + max(self.BestSize.height, self.skin.iconsize)) + + if keep_pos: self.Stack.DoPositions(self) # keeps baseline at the same position + else: self.Stack.DoPositions() + + Stacks = {} + + @classmethod + def focus_next_popup_input(cls, *a): + 'sets focus into the next popup input area' + currentFocus = wx.Window.FindFocus() + + inputs = filter(None, (getattr(p, 'input', None) for p in get_popups())) + if not inputs: return + + try: + i = inputs.index(currentFocus) + except ValueError: + i = -1 + + newFocus = inputs[(i + 1) % len(inputs)] + newFocus.ReallyRaise() + newFocus.SetFocus() + + @classmethod + def recent_popup_flip_page(cls, delta): + popups = get_popups() + if popups: + return popups[0].flip_page(delta) + + @property + def Stack(self): + monitor, position = self.monitor, self.ScreenPosition + + try: + mon = Popup.Stacks[monitor] + except KeyError: + mon = Popup.Stacks[monitor] = {} + + try: + stack = mon[position] + except KeyError: + + if self.ControlParent is self: + frameSize = self.margins + else: + frameSize = Margins() + stack = mon[position] = PopupStack(monitor, position_map[position], + border = (frameSize.left, frameSize.top)) + + return stack + + # + # page methods + # + + def paginate(self): + self.construct_page_controls() + self._update_page_controls() + + def _construct_button(self, label, callback=None, disables_buttons=False, enabled=True): + def cb(*a, **k): + val = callback(*a, **k) + if disables_buttons: + self.Item._buttons_disabled = True + self.construct_buttons([], layout=True) + return val + + button = UberButton(self.ControlParent, label=label, skin=self.skin.buttonskin, onclick=cb, + pos=(-20000, -20000)) + button.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + if not enabled: + button.Enable(False) + return button + + def construct_page_controls(self): + buttonskin = self.skin.buttonskin + self.next_button = self._construct_button('>', lambda: self.flip_page(1)) + self.prev_button = self._construct_button('<', lambda: self.flip_page(-1)) + + self.page_txt = statictext(self.ControlParent,'')#TODO: Not yet supported in ClearText: style = wx.ALIGN_CENTER_VERTICAL, + + self.page_txt.Font = self.skin.fonts.minor or default_font() + self.page_txt.FontColor = self.skin.fontcolors.minor or wx.BLACK + self.textctrls.append(self.page_txt) + + def flip_page(self, delta): + self.reset_long_fade_timers() + + if self.num_pages == 0 and self.current_page != 0: + self.current_page = 0 + self._update_page_controls() + return + + self.MarkAsRead() + old = self.current_page + self.current_page += delta + self.current_page %= self.num_pages + + #self.current_page = min(max(0, self.current_page), self.num_pages-1) + if self.current_page != old: + self._update_page_controls() + self.MarkAsRead() + + if callable(self.Item.buttons): + self.construct_buttons(self.Item.buttons, layout=True) + + self.size(keep_pos = True) + + + def _update_page_controls(self): + with self.FrozenQuick(): + self.update_for_page(self.current_page) + self.update_page_buttons() + + def update_page_buttons(self): + ctrls = (self.next_button, self.prev_button, self.page_txt) + + def update_spacers(show_buttons): + if hasattr(self, 'button_spacer'): + if show_buttons: + proportion = 0 if self.page_items.paged_originally else 1 + else: + proportion = 0 + + self.button_spacer.SetProportion(proportion) + if hasattr(self, 'prev_next_spacer'): + self.prev_next_spacer.SetProportion(0 if show_buttons else 1) + + if self.num_pages == 1: + # No pages? Don't show page buttons. + for c in ctrls: c.Hide() + update_spacers(True) + else: + pg = self.current_page + self.page_txt.Label = '%d/%d' % (pg+1, self.num_pages) + + for c in ctrls: c.Show() + update_spacers(False) + + def update_for_page(self, n): + 'Updates GUI elements for the nth page.' + + item = self.page_items[n] + + + self.Header = curly(unicode(item.header or ''), source = item._options) + #log.debug_s('Header %r curly\'d into %r (options were: %r)', item.header, self.Header, item._options) + self.Major = curly(unicode(item.major or ''), source = item._options) + #log.debug_s('Major %r curly\'d into %r (options were: %r)', item.major, self.Major, item._options) + self.Minor = curly(unicode(item.minor or ''), source = item._options) + #log.debug_s('Minor %r curly\'d into %r (options were: %r)', item.minor, self.Minor, item._options) + + def update_contents(self, options): + method = options.get('update', 'append') + self.sticky |= options.get('sticky', False) + + with self.FrozenQuick(): + getattr(self, 'update_contents_%s' % method, self.update_contents_unknown)(options) + + self.size(True) + + self.OnEnter() + self.MaybeStartTimer() + + def MaybeStartTimer(self): + if not self.Hover: self.fade_timer.StartOneShot(self.time) + + def update_contents_unknown(self, options): + log.error('Got unknown update method (%s) in options %r for popup %r', + options.get('update', ''), options, self) + + def update_contents_replace(self, options): + self.Item._apply_options(options, all=True) + self._update_page_controls() + + def update_contents_paged(self, options): + self.page_items.extend(PopupItemList(options)) + self.num_pages = len(self.page_items) + self._update_page_controls() + self.prev_button.Parent.Layout() + + def update_contents_append(self, options): + major = options.get('major', None) + minor = options.get('minor', None) + + # the "merge_onclick" option, if False, will cause any onclick callbacks + # to no longer be in effect for this popup. + merge_onclick = options.get('merge_onclick', True) and self.merge_onclick + if not merge_onclick: + self.Item.onclick = None + + log_debug('Updating contents of popup (appending)') + log.debug_s('old header=%r, major=%r, minor=%r', self.Header, self.Major, self.Minor) + + with self.FrozenQuick(): + if major: + + height = self.DesiredSize.height # this calls StaticText.Wrap + + if self.Minor.strip(): + self.Minor += '\n'+'\n'.join(filter(None, [self.Major,curly(major, source=options)])) + self.Major = '' + else: + self.Major += '\n' + curly(major, source=options) + + self._adjust_label(self.major_txt, height) + + if minor: + height = self.DesiredSize.height # this calls StaticText.Wrap + if self.Major.strip(): + self.Major += '\n'+'\n'.join(filter(None, [self.Minor,curly(minor, source=options)])) + self.Minor = '' + else: + self.Minor += '\n' + curly(minor, source=options) + + self._adjust_label(self.minor_txt, height) + + + log.debug_s('new header=%r, major=%r, minor=%r', self.Header, self.Major, self.Minor) + + def _adjust_label(self, ctrl, startheight): + lines = ctrl.Label.split('\n') + olen = len(lines) + lines = lines[-self.Item._max_lines:] + + if olen != len(lines): + ctrl.Label = '\n'.join(lines) + + + def construct(self): + self.textctrls = [] + cp, fonts, colors = self.ControlParent, self.skin.fonts, self.skin.fontcolors + + self.header_txt = statictext(cp, '') + self.header_txt.Font = fonts.title or default_font() + self.header_txt.FontColor = colors.title or wx.BLACK + + self.major_txt = statictext(cp, '') + self.major_txt.Font = fonts.major or default_font() + self.major_txt.FontColor = colors.major or wx.BLACK + + self.textctrls.append(self.major_txt) + + self.minor_txt = statictext(cp,'') + self.minor_txt.Font = fonts.minor or default_font() + self.minor_txt.FontColor = colors.minor or wx.BLACK + + self.textctrls.extend([self.header_txt, self.minor_txt]) + + self.construct_buttons(self.Item.buttons) + self.construct_input(self.Item.input) + + def construct_input(self, callback, + hide_on_enter=True, + initial_value=u'', + char_limit=None, + spellcheck=False, + spellcheck_regexes=None): + + if not callback: + return + + style = wx.TE_PROCESS_ENTER | wx.BORDER_SIMPLE + if char_limit is not None: + style |= wx.TE_RICH2 + + if spellcheck: + from gui.spellchecktextctrlmixin import SpellCheckedTextCtrl + input_clz = SpellCheckedTextCtrl + else: + input_clz = wx.TextCtrl + + input = self.input = input_clz(self.ControlParent, -1, initial_value, style = style, pos=(-20000, -20000), + validator=LengthLimit(20480)) + + if spellcheck and spellcheck_regexes is not None: + for r in spellcheck_regexes: + input.AddRegexIgnore(r) + + input.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM)) + if hasattr(input, 'SetDefaultColors'): + input.SetDefaultColors(wx.BLACK, wx.WHITE) + + def losefocus(e): + self.has_focus = False + self.fade_timer.StartRepeating(self.time) + + def onEnter(e): + value, options = self.input.Value, self.Item._options + + # if we've got a character limit and there are too many characters + # enter does nothing + if char_limit is not None and len(value) > char_limit: + return + + result = None + with traceguard: result = callback(value, options) + + log.info('input callback is %r', result) + + if result is not None: + input.ChangeValue('') + self.update_contents(dict(update = 'append', + minor = result)) + + self.has_focus = False + self.fade_timer.StartRepeating(self.time) + elif hide_on_enter: + self.HideAndDestroy() + + def onText(e): + e.Skip() + if e.String: + self.has_focus = True + self.OnEnter() + + # TODO: use this to keep text visible when deleting text + # ctypes.windll.user32.SendMessageW(hwnd, EM_SETSCROLLPOS, 0, byref(pt)) + + def onKeyDown(e): + c = e.KeyCode + if c == wx.WXK_ESCAPE: + if self.InputMode: + self.leave_input_mode() + else: + self.HideAndDestroy(userClose = True) + elif c in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): + onEnter(e) + else: + e.Skip() + + b = input.Bind + b(wx.EVT_TEXT, onText) + b(wx.EVT_TEXT_ENTER, onEnter) + b(wx.EVT_KEY_DOWN, onKeyDown) + b(wx.EVT_SET_FOCUS, lambda e: (e.Skip(), setattr(self, 'has_focus', True))) + b(wx.EVT_KILL_FOCUS, losefocus) + + # if there's a character limit, highlight text that is over the + # limit with a red background + if char_limit is not None: + VisualCharacterLimit(input, char_limit) + + def _get_button_list(self, item, buttons): + ''' + returns a sequence of (label, callback, enabled) describing buttons for the given popup item. + + "enabled" is optional and defaults to True. + ''' + + # buttons can be callable, or an ordered dict + if getattr(item, '_buttons_disabled', False): + buttons = [] + elif callable(buttons): + try: + buttons = buttons(item) + except Exception: + traceback.print_exc() + buttons = [] + + if hasattr(buttons, 'items'): + buttons = buttons.items() + + return buttons + + def construct_buttons(self, buttons, layout=False): + # If this is a popup with buttons, create them here. + + buttons = self._get_button_list(self.Item, buttons) + + # destroy old buttons, if we have them and they are different. + if hasattr(self, 'buttons'): + if self.buttons_data == buttons: + return # just return if buttons are the same + + self._destroy_buttons() + + self.buttons = [] + self.buttons_data = buttons + if not layout and not buttons: return + + #for label, callback in buttons: + for tup in buttons: + try: + label, callback, enabled = tup + except ValueError: + label, callback = tup + enabled = True + + # if the provided callback has a callable attribute "input_cb," + # then construct a text box and pass the resulting input to the + # callback later + input_cb = getattr(callback, 'input_cb', None) + disables_buttons = False + if hasattr(input_cb, '__call__'): + # TODO: abstract this mess into a reusable class, or maybe a set + # of hooks? + def cb(cb = input_cb, callback=callback): + self.MarkAsRead() + get_value = getattr(callback, 'get_value', None) + if hasattr(get_value, '__call__'): + initial_value = get_value(self.Item._options) + else: + initial_value = u'' + if hasattr(callback, 'spellcheck_regexes'): + regexes = callback.spellcheck_regexes() + else: + regexes = None + self.input_mode(cb, + getattr(callback, 'close_button', False), + initial_value, + getattr(callback, 'char_limit', None), + spellcheck = getattr(callback, 'spellcheck', False), + spellcheck_regexes = regexes) + else: + if not callable(callback): + label, callback = callback, lambda _label=label: getattr(self.Item.target, _label)() + + disables_buttons = getattr(callback, 'disables_buttons', False) + def cb(cb = callback, disables_buttons=disables_buttons): + self.MarkAsRead() + if getattr(cb, 'takes_popup_control', False): + wx.CallAfter(cb, self.Item, self.Control) + elif getattr(cb, 'takes_popup_item', False): + wx.CallAfter(cb, self.Item) + else: + wx.CallAfter(cb) + if not disables_buttons and not getattr(cb, 'takes_popup_control', False): + self.HideAndDestroy() + + self.buttons.append(self._construct_button(label, cb, disables_buttons, enabled)) + + if layout: + self.LayoutButtons(self.buttons) + + @property + def Control(self): + try: + return self._controller + except AttributeError: + self._controller = S(update_buttons=lambda: wx.CallAfter(self.refresh_buttons)) + return self._controller + + def refresh_buttons(self): + self.construct_buttons(self.Item.buttons, layout=True) + self.ControlParent.Layout() + + def _destroy_buttons(self): + for button in self.buttons: + self.button_sizer.Detach(button) + button.Destroy() + + def layout(self): + s = self.skin + padding, iconsize = s.padding, s.iconsize + + # FIXME: This annoyance is brought to you by the people who make a TLW with a single child + # expand that child to fill the TLW's space. We need to do this to allow for the margins. + if self.ControlParent is not self: + self.Sizer = wx.BoxSizer(wx.HORIZONTAL) + self.Sizer.Add(self.ControlParent, 1, wx.EXPAND | wx.ALL, 36) + + self.ControlParent.Sizer = marginsizer = wxBoxSizer(HORIZONTAL) + + self.main_sizer = sz = wxBoxSizer(VERTICAL) + + s = wxBoxSizer(HORIZONTAL) + s.AddSpacer(iconsize) + + v = wxBoxSizer(VERTICAL) + + header_sizer = wx.BoxSizer(wx.HORIZONTAL) + header_sizer.Add(self.header_txt, 1, EXPAND) + header_sizer.AddSpacer((36, 1)) # for popup badge + + v.Add(header_sizer, 0, EXPAND | BOTTOM, padding.y) + + if hasattr(self, 'major_txt'): + v.Add(self.major_txt, 0, EXPAND | BOTTOM, padding.y) + v.Add(self.minor_txt, 0, EXPAND | BOTTOM, padding.y) + s.Add(v, 1, wx.EXPAND) + + sz.Add(s, 1, EXPAND | LEFT, padding.x) + + self.button_sizer = wx.BoxSizer(HORIZONTAL) + + #if self.Item.buttons: + # Build a horizontal sizer contaning each "action" button + buttons = self.buttons + button_sizer = self.button_sizer + self.button_left_space = button_sizer.Add((self.skin.iconsize-padding.x, 1)) + self.button_spacer = button_sizer.AddStretchSpacer(1) + self.button_index = 2 + self.under_buttons_space = sz.Add((0, padding.y)) + self.LayoutButtons(buttons, layoutNow=False) + + sz.Add(self.button_sizer, 0, EXPAND | LEFT, padding.x) + + self._page_buttons_position = len(sz.GetChildren()) + self.layout_prev_next() + self.update_page_buttons() + + if hasattr(self, 'input'): + sz.AddSpacer(padding.y) + sz.Add(self.input, 0, EXPAND) + + marginsizer.Add(sz, 1, EXPAND) + + # FIXME: Way too much space is being allocated under Mac, this squeezes multi-page + # controls but it's better than the alternative until we find a fix. + if self.ControlParent is not self: + self.Fit() + + def LayoutButtons(self, buttons, layoutNow=True): + pad_x = self.skin.padding.x + + index = self.button_index + + for b in buttons: + self.button_sizer.Insert(index, b, 0, EXPAND | LEFT, pad_x) + index += 1 + + self.under_buttons_space.Show(len(buttons) > 0) + + if layoutNow: + self.button_sizer.Layout() + + def layout_prev_next(self): + 'Adds next and previous buttons, and "5/6" StaticText to a sizer.' + + xpadding = self.skin.padding.x + + h = self.button_sizer + self.prev_next_spacer = h.AddStretchSpacer(1) + # < + h.Add(self.prev_button, 0, ALIGN_CENTER_VERTICAL | EXPAND | RIGHT, xpadding) + + # 5/6 + vv = wxBoxSizer(VERTICAL) + vv.AddStretchSpacer(1) + vv.Add(self.page_txt, 0, EXPAND) + vv.AddStretchSpacer(1) + + h.Add(vv, 0, ALIGN_CENTER_VERTICAL | EXPAND) + # > + h.Add(self.next_button, 0, ALIGN_CENTER_VERTICAL | EXPAND | LEFT, xpadding) + return h + + def ShowButtons(self, show): + self.main_sizer.Show(self.button_sizer, show, True) + + @property + def InputShown(self): + '''returns True if this popup is showing an input text control.''' + + input = getattr(self, 'input', None) + return input is not None and input.IsShown() + + @property + def InputMode(self): + '''returns True if the popup is in a temporary "input mode" state where + the text ctrl will go away if the user clicks the X button''' + + return getattr(self, '_input_mode', False) + + def input_mode(self, cb, close_button, initial_value=u'', char_limit=None, spellcheck=False, spellcheck_regexes=None): + with self.Frozen(): + self.ShowButtons(False) + + def on_close_input(*a): + self._input_mode = False + del self.leave_input_mode + self.has_focus = False + with self.Frozen(): + self.main_sizer.Detach(self.input_sizer) + self.input_sizer.Detach(self.input) + if close_button: + self.input_sizer.Detach(self.close_input) + wx.CallAfter(self.close_input.Destroy) + del self.close_input + wx.CallAfter(self.input.Destroy) + del self.input + self.ShowButtons(True) + self.update_page_buttons() + self.Layout() + + self.leave_input_mode = on_close_input + + def callback(text, options): + on_close_input() + if getattr(cb, 'takes_popup_control', False): + wx.CallAfter(cb, self.Item, self.Control, text, options) + elif getattr(cb, 'takes_popup_item', False): + wx.CallAfter(cb, self.Item, text, options) + else: + wx.CallAfter(cb, text, options) + + # if initial_value is a tuple, assume (initial_value_string, cursor_position) + if not isinstance(initial_value, basestring): + initial_value, cursor_pos = initial_value + else: + cursor_pos = None + + assert isinstance(initial_value, basestring) + assert cursor_pos is None or isinstance(cursor_pos, int) + + self._input_mode = True + self.construct_input(callback, hide_on_enter=False, initial_value=initial_value, + char_limit=char_limit, spellcheck=spellcheck, + spellcheck_regexes=spellcheck_regexes) + self.input.SetInsertionPoint(self.input.LastPosition if cursor_pos is None else cursor_pos) + self.input.SetFocus() + self.input_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.input_sizer.Add(self.input, 1, wx.EXPAND) + if close_button: + self.close_input = self._construct_button('X', on_close_input) + self.input_sizer.Add(self.close_input, 0, wx.EXPAND) + + self.main_sizer.Add(self.input_sizer, 0, wx.EXPAND) + self.Layout() + + def __repr__(self): + return '' % try_this(lambda: self.Header, id(self)) + + # + # wx properties + # + + def SetHeader(self, header): + header = TruncateText(header, self.HeaderWrapWidth, self.header_txt.Font) + self.header_txt.Label = header + self.header_txt.Show(bool(header)) + + def GetHeader(self): + return self.header_txt.Label + + def SetMajor(self, msg): + t = self.major_txt + t.Label = msg + t.Wrap(self.WrapWidth, self.Item._max_lines) + t.Show(bool(msg)) + + def GetMajor(self): + return self.major_txt.Label + + def SetMinor(self, msg): + t = self.minor_txt + t.Label = msg + t.Wrap(self.WrapWidth, self.Item._max_lines) + t.Show(bool(msg)) + + def GetMinor(self): + return self.minor_txt.Label + + def GetPaged(self): + return bool(self.page_items) + + @property + def WrapWidth(self): + return self.skin.width - self.skin.iconsize - self.padding.x + + @property + def HeaderWrapWidth(self): + 'Width to truncate header text at.' + + badge_padding = (self.popup_icon_badge_size - 2) if self.PopupBadge is not None else 0 + return self.WrapWidth - badge_padding + + @property + def ScreenPosition(self): + return self.position or self.location + + Header = property(GetHeader, SetHeader) + Major = property(GetMajor, SetMajor) + Minor = property(GetMinor, SetMinor) + Paged = property(GetPaged) + +def transform_popup_options(info): + '''Do some preprocessing on incoming Popup args.''' + + from gui import skin + icon = info.get('icon') + + if icon is None: + # Look up buddy icons if there's 'buddy' but no 'icon' + buddy = info.get('buddy') + if buddy is not None: + from gui.buddylist.renderers import get_buddy_icon + + # Also round their corners if the skin specifies it. + round_size = try_this(lambda: int(skin.get('Popups.IconRounded', 2)), 2) + info['icon'] = get_buddy_icon(buddy, size = skin.get('Popups.Iconsize', 32), round_size = round_size) + + elif isinstance(icon, basestring) and icon.startswith('skin:'): + # If icon is specified as skin:dotted.path.to.skin.resource do the + # lookup here + with traceguard: + info['icon'] = skin.get(icon[5:], None) + + if pref('notifications.popups.scan_urls', type = bool, default = False) and info.get('onclick', None) is None: + from util import net + url = None + + text = info.get('minor', None) or info.get('major', None) or info.get('header', None) + links = net.find_links(text) + if links and len(links) == 1: + url = links[0] + + if url is not None: + info['onclick'] = url + +def opacity_to_byte(val): + '''Map a number from 0-100 to 0-255, returning 255 if it's not a number.''' + + try: + int(val) + except ValueError: + return 255 + else: + return int(min(100, max(0, val)) / 100.0 * 255) + +launch_browser = wx.LaunchDefaultBrowser + +def handle_popup_onclick(onclick, contents): + # onclick may be a string + if isinstance(onclick, basestring) and onclick: + launch_browser(onclick) + else: + # or a callable + res = onclick(contents) + + # if the callable returns a string, interpret it as a URL + if isinstance(res, basestring) and res: + launch_browser(res) + +_did_initial_setup = False + +def initial_setup(): + global _did_initial_setup + if _did_initial_setup: + return + + _did_initial_setup = True + + # until problems with changing popup skins on the fly are resolved, cancel + # all popups before a skin change. + hooks.register('skin.set.pre', lambda skin, variant: cancel_all()) + diff --git a/digsby/src/gui/toast/toasthelp.py b/digsby/src/gui/toast/toasthelp.py new file mode 100644 index 0000000..1b84b35 --- /dev/null +++ b/digsby/src/gui/toast/toasthelp.py @@ -0,0 +1,36 @@ +''' +Shows a popup explaining popup features. +''' + +from common import pref, setpref +from gui.toast.toast import popup +from gui import skin +import wx + +SHOWN_HELP_PREF = 'notifications.popups.help_shown' + +_didshow = False + +def on_popup(options): + global _didshow + if _didshow: + return + if pref(SHOWN_HELP_PREF, default=False, type=bool): + _didshow = True + return + + _didshow = True # A backup in case setpref fails, so you only get one per session. + setpref(SHOWN_HELP_PREF, True) + + popup(header = _('TIP: Popup Notifications'), + major = None, + minor = _("You can right click popups to close them right away instead of waiting for them to fade out."), + sticky = True, + icon = skin.get('appdefaults.notificationicons.error'), + always_show=True) + +def enable_help(): + import hooks + hooks.register('popup.pre', on_popup) + + diff --git a/digsby/src/gui/toast/toaststack.py b/digsby/src/gui/toast/toaststack.py new file mode 100644 index 0000000..f7e6cb7 --- /dev/null +++ b/digsby/src/gui/toast/toaststack.py @@ -0,0 +1,158 @@ +''' +Popup positioning and animation. +''' + + +import wx +from wx import Point, RectPS, TOP, LEFT, BOTTOM, GetMousePosition + +from gui.toolbox import Monitor, alignment_to_string +from gui.windowfx import move_smoothly +from cgui import fadein +from common import prefprop + +from logging import getLogger; log = getLogger('popupstack') + +def screenArea(monitor_n): + display_n = min(Monitor.GetCount() - 1, monitor_n) + return Monitor.All()[display_n].GetClientArea() + +AVOID_MOUSE = False +AVOID_MOUSE_PIXELS = 30 + +valid_corners = frozenset(( + wx.TOP | wx.LEFT, + wx.TOP | wx.RIGHT, + wx.BOTTOM | wx.LEFT, + wx.BOTTOM | wx.RIGHT +)) + +class PopupStack(list): + 'A popup stack is in charge of positioning popups for one corner of a display.' + + def __init__(self, monitor, position, + padding = None, + border = None): + + self.monitor = monitor + + self.corner = position + assert position in valid_corners, position + + if padding is None: + padding = (0, 10) + if border is None: + border = (10, 10) + + self.padding = Point(*padding) + self.border = Point(*border) + + self.NextRect = self.Down if TOP & self.corner else self.Up + self.OppositeRect = self.Up if TOP & self.corner else self.Down + + def __repr__(self): + return '' % (alignment_to_string(self.corner), self.monitor, len(self)) + + @property + def ScreenRect(self): + return screenArea(self.monitor) + + offset = prefprop('notifications.popups.offset', (0,0)) + + def Up(self, prevRect, newSize, user_action): + border, padding = self.border, self.padding + + if prevRect is None: + if LEFT & self.corner: + pt = self.ScreenRect.BottomLeft + (border.x + padding.x + self.offset[0], - border.y - self.offset[1]) + else: + pt = self.ScreenRect.BottomRight - (newSize.width + border.x + padding.x + self.offset[0], border.y + self.offset[1]) + else: + pt = prevRect.TopLeft - Point(0, border.y + padding.y) + + r = RectPS(pt - Point(0, newSize.height), newSize) + if AVOID_MOUSE and not user_action and r.Contains(GetMousePosition()): + r.y -= r.Bottom - GetMousePosition().y + AVOID_MOUSE_PIXELS + + return r + + def Down(self, prevRect, newSize, user_action): + border, padding = self.border, self.padding + + if prevRect is None: + if LEFT & self.corner: + pt = self.ScreenRect.TopLeft + (border.x + padding.x + self.offset[0], border.y + self.offset[1]) + else: + pt = self.ScreenRect.TopRight - (newSize.width + border.x + padding.x + self.offset[0], -border.y - self.offset[1]) + else: + pt = prevRect.BottomLeft + Point(0, border.y + padding.y) + + r = RectPS(pt, newSize) + + if AVOID_MOUSE and not user_action and r.Contains(GetMousePosition()): + r.y += GetMousePosition().y - r.y + AVOID_MOUSE_PIXELS + + return r + + def InitialPos(self, size): + return self.NextRect(None, size).Position + + def Add(self, popup): + assert popup not in self + self.append(popup) + popup.OnClose += lambda userClose: self.Remove(popup, user_action = userClose) + self.DoPositions(popup) + popup._infader = fadein(popup, 0, popup.opacity_normal, 30) + + def Remove(self, popup, user_action = False): + try: + self.remove(popup) + except ValueError: + pass + self.DoPositions(user_action = user_action) + + def DoPositions(self, paging = None, user_action = False): + prevRect = None + + for popup in self[:]: + try: + oldrect = popup.Rect + except wx.PyDeadObjectError: + self.remove(popup) + log.critical('dead Popup object in %r' % self) + continue + + quick = False + desired = popup.DesiredSize + + if popup.Hover or popup.has_focus: + rect = RectPS(popup.Position, desired) + + # if the popup is in one of the bottom corners and it's + # expanding, keep the bottom of its rectangle in the same + # place since that's where the interactable GUI is + if paging is popup and BOTTOM & self.corner: + rect.y -= rect.height - oldrect.height + else: + rect = self.NextRect(prevRect, desired, user_action = user_action) + + popup._moved = True + + self.SetPopupRect(popup, rect, quick = paging is popup) + prevRect = rect + + slidetime = prefprop('notifications.popups.slide_ms', 140) + + def SetPopupRect(self, popup, rect, quick = False): + t = int(self.slidetime) + if t == 0: quick = True + + oldrect = popup.Rect + + if quick: + popup.SetRect(rect) + else: + if oldrect.Size != rect.Size: + popup.SetRect(wx.RectPS(oldrect.Position, rect.Size)) + + move_smoothly(popup, rect.Position, time = t) diff --git a/digsby/src/gui/toolbox/__init__.py b/digsby/src/gui/toolbox/__init__.py new file mode 100644 index 0000000..a8bfc19 --- /dev/null +++ b/digsby/src/gui/toolbox/__init__.py @@ -0,0 +1,4 @@ +from .toolbox import * +from dnd import SimpleDropTarget +#from imgfx import drawreflected +import fontfx diff --git a/digsby/src/gui/toolbox/dnd.py b/digsby/src/gui/toolbox/dnd.py new file mode 100644 index 0000000..ea112e6 --- /dev/null +++ b/digsby/src/gui/toolbox/dnd.py @@ -0,0 +1,91 @@ +import wx +from logging import getLogger; log = getLogger('dnd') + +drag_types = {wx.DF_FILENAME: ('files', wx.FileDataObject, wx.FileDataObject.GetFilenames), + wx.DF_TEXT: ('text', wx.TextDataObject, wx.TextDataObject.GetText), + wx.DF_BITMAP: ('bitmap', wx.PyBitmapDataObject, wx.PyBitmapDataObject.GetBitmap) +} + + +class SimpleDropTarget( wx.PyDropTarget ): + accepts_default = (wx.DF_FILENAME, + wx.DF_TEXT, + wx.DF_BITMAP) + + def __init__( self, calltarget = None, accepts = accepts_default, **callbacks ): + wx.PyDropTarget.__init__( self ) + + if not all(callable(c) for c in callbacks.values()): + raise TypeError, 'keyword arguments to DropTarget must be callable' + + self.dragged = wx.DataObjectComposite() + + for dragtype in accepts: + datatype, datainit, datamethod = drag_types[dragtype] + + obj = datainit() + self.dragged.Add(obj) + setattr(self.dragged, datatype, obj) + + self.SetDataObject( self.dragged ) + + self.accepts = accepts + self.calltarget = calltarget + self.callbacks = callbacks + self.successful_drag_result = wx.DragCopy + + def callif(self, dragtype, data): + call = getattr(self.calltarget, 'OnDrop' + dragtype.title(), None) + if callable(call): + if dragtype == 'files': + wx.CallLater(300, call, data) + else: + call(data) + elif dragtype in self.callbacks: + if dragtype == 'files': + wx.CallLater(300, self.callbacks[dragtype], data) + else: + self.callbacks[dragtype](data) + else: + log.info('ignored (%s): %s', dragtype, repr(data)[:50]) + + def OnDrop( self, x, y ): + return True + + def OnData( self, x, y, d ): + "Called when OnDrop returns True. Get data and do something with it." + + self.GetData() + + format = self.dragged.GetReceivedFormat().GetType() + + for format in self.accepts: + datatype, datainit, datamethod = drag_types[format] + data = datamethod(getattr(self.dragged, datatype)) + + if data: + self.callif(datatype, data) + + return True + + def OnEnter( self, x, y, d ): + return d + + + def OnDragOver( self, x, y, d ): + return wx.DragMove + + +if __name__ == '__main__': + a = wx.PySimpleApp() + f = wx.Frame(None) + + def foo(data): + print data + + p = wx.Panel(f) + p.SetDropTarget(DropTarget(text = foo, files = foo, bitmap = foo)) + + f.Show(True) + a.MainLoop() + diff --git a/digsby/src/gui/toolbox/filetypeicons.py b/digsby/src/gui/toolbox/filetypeicons.py new file mode 100644 index 0000000..8601953 --- /dev/null +++ b/digsby/src/gui/toolbox/filetypeicons.py @@ -0,0 +1,53 @@ +from ctypes import * + +MAX_PATH = 260 +HICON = c_int + +class SHFILEINFO(Structure): + _fields_ = [("hIcon", HICON), + ("iIcon", c_int), + ("dwAttributes", c_uint), + ("szDisplayName", c_char * MAX_PATH), + ("szTypeName", c_char * 80)] + +FILE_ATTRIBUTE_NORMAL = 0x80 + +SHGFI_ICON = 0x000000100 +SHGFI_DISPLAYNAME = 0x000000200 +SHGFI_TYPENAME = 0x000000400 +SHGFI_ATTRIBUTES = 0x000000800 +SHGFI_ICONLOCATION = 0x000001000 +SHGFI_EXETYPE = 0x000002000 +SHGFI_SYSICONINDEX = 0x000004000 +SHGFI_LINKOVERLAY = 0x000008000 +SHGFI_SELECTED = 0x000010000 +SHGFI_ATTR_SPECIFIED = 0x000020000 +SHGFI_LARGEICON = 0x000000000 +SHGFI_SMALLICON = 0x000000001 +SHGFI_OPENICON = 0x000000002 +SHGFI_SHELLICONSIZE = 0x000000004 +SHGFI_PIDL = 0x000000008 +SHGFI_USEFILEATTRIBUTES = 0x000000010 + + +shfileinfo = SHFILEINFO() + +import sys + +#flags = SHGFI_DISPLAYNAME | SHGFI_TYPENAME | SHGFI_ATTRIBUTES +flags = SHGFI_ICON | SHGFI_USEFILEATTRIBUTES + +fileAttributes = FILE_ATTRIBUTE_NORMAL + +#print\ +windll.shell32.SHGetFileInfo('foo.txt', + fileAttributes, + byref(shfileinfo), + sizeof(shfileinfo), + flags) + +#print\ +shfileinfo.hIcon +#print hex(shfileinfo.dwAttributes) +#print repr(shfileinfo.szDisplayName) +#print repr(shfileinfo.szTypeName) \ No newline at end of file diff --git a/digsby/src/gui/toolbox/fontfx.py b/digsby/src/gui/toolbox/fontfx.py new file mode 100644 index 0000000..164c0b0 --- /dev/null +++ b/digsby/src/gui/toolbox/fontfx.py @@ -0,0 +1,37 @@ +import wx + + +__all__ = ['default_colors'] + +make_default_colors = lambda: [wx.BLACK, wx.Colour(190, 190, 190)] + +class foo(list): + def __getitem__(self, item): + globals()['default_colors'] = make_default_colors() + return globals()['default_colors'][item] + + def __getattr__(self, attr): + globals()['default_colors'] = make_default_colors() + return getattr(globals()['default_colors'], attr) + +default_colors = foo() + +def DrawTextFX(dc, text, x, y, colors = default_colors, spacing = 1): + l = len(colors) + p = wx.Point(x, y) + (spacing * l, spacing * l) + offset = (spacing, spacing) + + for color in reversed(colors): + dc.SetTextForeground(color) + dc.DrawText(text, *p) + p = p - offset + +wx.DC.DrawTextFX = DrawTextFX + +if __name__ == '__main__': + a = default_colors + ap = wx.App() + bar = a[0] + b = default_colors + assert a is not b + assert bar == wx.BLACK diff --git a/digsby/src/gui/toolbox/fonts.py b/digsby/src/gui/toolbox/fonts.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/toolbox/imagefx.py b/digsby/src/gui/toolbox/imagefx.py new file mode 100644 index 0000000..d5b9f8e --- /dev/null +++ b/digsby/src/gui/toolbox/imagefx.py @@ -0,0 +1,563 @@ +''' + +Efficient image effects and transformations. + +''' + +DISABLE_ALL_CACHING = False # slow! +NUM_IMAGES_TO_CACHE = 80 + +import wx +from wx import ImageFromDataWithAlpha, BitmapFromImage, BitmapFromIcon, \ + ImageFromBitmap, IMAGE_QUALITY_HIGH, Point + +from PIL import Image, ImageFilter, ImageMath, ImageOps, ImageEnhance +from PIL.Image import BICUBIC, ANTIALIAS +from functools import wraps +from uuid import uuid1 + +_imagecache = None + +def _get_imagecache(): + global _imagecache + if _imagecache is None: + from util.lrucache import LRU + _imagecache = LRU(NUM_IMAGES_TO_CACHE) + + return _imagecache + +def cachesize(): + return sum(img.DecodedSize for img in _get_imagecache.values()) + +CORNERS = ( + ("tl", None), + ("tr", Image.ROTATE_270), + ("bl", Image.FLIP_TOP_BOTTOM), + ("br", Image.ROTATE_180), +) + +# +# the idea here is that each function gets an LRU cache keyed by +# images and arguments, with result images as values, so that +# redrawing the same image transformations over and over results +# in no work with a small enough memory tradeoff. +# +def cacheing(func): + name = func.__name__ + + @wraps(func) + def wrapper(img, *a): + try: + cachekey = img.cachekey + except AttributeError: + try: + cachekey = (img.path,) + except AttributeError: + img.path = str(uuid1()) + cachekey = (img.path,) + + key = cachekey + (name + repr(a) ,) + + imagecache = _get_imagecache() + try: + return imagecache[key] + except KeyError: + img = func(img, *a) + img.cachekey = key + imagecache[key] = img + return img + + wrapper.nocache = func + + return wrapper + +if DISABLE_ALL_CACHING: + import sys + print >> sys.stderr, 'WARNING: image caching is disabled' + + objmemoize = refmemoize = cacheing = lambda f: f +else: + refmemoize = objmemoize = cacheing + +# +#--- conversions between image types +# + +@cacheing +def wxbitmap_to_wximage(wxbitmap): + return wxbitmap.ConvertToImage() + +@refmemoize +def wxbitmap_to_pil(wxbitmap): +# print 'wxbitmap_to_pil', wxbitmap + return wximage_to_pil(wxbitmap_to_wximage(wxbitmap)) + +@cacheing +def wxicon_to_pil(wxicon): + return wxbitmap_to_pil(BitmapFromIcon(wxicon)) + +@cacheing +def wxicon_to_bitmap(wxicon): + return BitmapFromIcon(wxicon) + + +@refmemoize +def wximage_to_pil(wximage): +# print 'wximage_to_pil', wximage + + size = (wximage.Width, wximage.Height) + img = Image.new('RGB', size) + img.fromstring(wximage.Data) + r, g, b = img.split() + + if wximage.HasAlpha() or wximage.HasMask(): + a = Image.new('L', wximage.GetSize()) + + # images that come from formats like GIF don't have an alpha channel + # but DO have a mask color set + if wximage.HasMask(): + if not wximage.HasAlpha(): + # uses that mask color to generate an alpha channel. + wximage.InitAlpha() + + if wximage.HasAlpha(): + a.fromstring(wximage.AlphaData) + + img = Image.merge('RGBA', (r, g, b, a)) + else: + img = Image.merge('RGB', (r, g, b)) + + return img + +def wximage_to_wxbitmap(wximage, depth = 32): + return BitmapFromImage(wximage) + +@objmemoize +def pil_to_wxbitmap(pilimg): +# print 'pil_to_wxbitmap', pilimg + return wximage_to_wxbitmap(pil_to_wximage(pilimg), 32) + +@objmemoize +def pil_to_wximage(pilimg): +# print 'pil_to_wximage', pilimg + + #TODO: there has to be a more efficient way to get the data from + # PIL then 'tostring' + + pilimage = pilimg if pilimg.mode == 'RGBA' else pilimg.convert('RGBA') + w, h = pilimg.size + + rgb = pilimage.tostring('raw', 'RGB') + alpha = pilimage.tostring('raw', 'A') + return ImageFromDataWithAlpha(w, h, rgb, alpha) + +@cacheing +def wxbitmap_inverted(wxbitmap): + return ImageOps.invert(pilimg_convert(wxbitmap.PIL, 'RGB')).WXB + +def has_transparency(i): + return any(i.histogram()[768:1024]) + +def pil_to_white_gif(i): + bg = Image.new('RGBA', i.size, (255, 255, 255, 255)) + bg.paste(i, i) + return pil_to_gif(bg) + +def pil_to_gif(i): + import cStringIO + s = cStringIO.StringIO() + + r, g, b, a = i.split() + rgb = Image.merge('RGB', (r,g,b)) + rgb = rgb.convert('P', palette=Image.ADAPTIVE, colors=255, dither=Image.NONE) + pal = rgb.getpalette() + + if len(pal) < 256*3: + # If there's room in the palette for a transparent color, create one. + r1, g1, b1 = pal[::3], pal[1::3], pal[2::3] + rv = (set(xrange(256)) - set(r1)).pop() + gv = (set(xrange(256)) - set(g1)).pop() + bv = (set(xrange(256)) - set(b1)).pop() + pal[-3:] = [rv, gv, bv] + rgb.putpalette(pal, rawmode='RGB') + a2 = a.point(lambda p: 0 if p >= 128 else 255) + rgb.paste(255, (0,0), a2) + rgb.save(s, 'GIF', transparency=255, interlace=False) + else: + # Otherwise, just save the GIF. + rgb.putpalette(pal, rawmode='RGB') + rgb.save(s, 'GIF', interlace=False) + + return s.getvalue() + +# +#--- resizing images +# + +@refmemoize +def wxbitmap_resizedwh(wxbitmap, w, h): +# print 'wxbitmap_resizedwh', wxbitmap, w, h + return pil_to_wxbitmap(pilimg_resizedwh(wximage_to_pil(wxbitmap_to_wximage(wxbitmap)), w, h)) + +@refmemoize +def wxbitmap_resized(wxbitmap, size): + ''' + Returns a resized version of a bitmap. + + "size" can either be an integer or a sequence of two integers. + ''' +# print 'wxbitmap_resized', wxbitmap, size + try: + # sequence: width and height were given + w, h = size + except TypeError: + # one integer: place the image in a square of width and height "size" + return wxbitmap_in_square(wxbitmap, size) + else: + return wxbitmap_resizedwh(wxbitmap, w, h) + +def wxbitmap_resized_smaller(wxbitmap, size): + if max(wxbitmap.Width, wxbitmap.Height) > size: + return wxbitmap.Resized(size) + else: + return wxbitmap + +def pil_resized_smaller(pil, size): + return pil.Resized(size) if max(pil.size) > size else pil + +@objmemoize +def pilimg_resized(pilimg, size): +# print 'pilimg_resized', pilimg, size + + try: + w, h = size + except TypeError: + return pilimg_in_square(pilimg, size) + else: + return pilimg_resizedwh(pilimg, w, h) + + +@objmemoize +def pilimg_convert(pilimg, mode): + return pilimg.convert(mode) + +@objmemoize +def pilimg_resizedwh(pilimg, w, h): +# print 'pilimg_resizedwh', pilimg, w, h + + w = pilimg.size[0] if w == -1 else int(w) + h = pilimg.size[1] if h == -1 else int(h) + size = (w, h) + + if pilimg.mode == 'P': # resizing paletted images doesn't antialias + pilimg = pilimg_convert(pilimg, 'RGBA') + + return pilimg.resize(size, Image.ANTIALIAS if min(size) < min(pilimg.size) else Image.BICUBIC) + +@objmemoize +def pilimg_in_square(img, squaresize): + ''' + Resizes a PIL Image to a square, maintaining aspect ratio. + + squaresize must be an integer + ''' +# print 'pilimg_in_square', img, squaresize + + w, h = img.size + + if img.mode == 'P': # resizing paletted images doesn't antialias + img = pilimg_convert(img, 'RGBA') + + # make a completely transparent square + new = Image.new('RGBA', (squaresize, squaresize), (0, 0, 0, 0)) + + if w > h: + new_height = int(h/float(w) * squaresize) + img = img.resize((squaresize, new_height), BICUBIC if squaresize > w else ANTIALIAS) + new.paste(img, (0, (squaresize - new_height)/2)) + else: + new_width = int(w/float(h) * squaresize) + img = img.resize((new_width, squaresize), BICUBIC if squaresize > h else ANTIALIAS) + new.paste(img, ((squaresize - new_width)/2, 0)) + + return new + +@objmemoize +def pil_resize_canvas(img, w, h, alignment = wx.ALIGN_CENTER): +# print 'pil_resize_canvas', img, w, h, alignment + if alignment != wx.ALIGN_CENTER: + raise NotImplementedError + + ow, oh = img.size + new = Image.new('RGBA', (w, h), (0, 0, 0, 0)) + + new.paste(img, (w / 2 - ow / 2, w / 2 - oh / 2)) + + return new + +def pil_setalpha(img, alpha): + 'Multiplies "alpha" by the images existing alpha channel. In place.' + +# print 'pil_setalpha', img, alpha + + assert hasattr(img, 'size'), 'Image must be a PIL Image' + assert 0 <= alpha <= 1, 'alpha must be 0 <= alpha <= 1' + + channels = img.split() + if len(channels) == 4: + r, g, b, a = channels + elif len(channels) == 3: + a = Image.new('L', img.size, 'white') + else: + raise AssertionError('Cannot set alpha, image does not have 3 or 4 bands.') + + img.putalpha(ImageEnhance.Brightness(a).enhance(alpha)) + + + +@refmemoize +def wxbitmap_in_square(bmp, squaresize, scaleup = True): +# print 'wxbitmap_insquare', bmp, squaresize, scaleup + + w, h = bmp.Width, bmp.Height + + if w > squaresize or h > squaresize or scaleup: + img = ImageFromBitmap(bmp) + if w > h: + new_height = int(h/float(w) * squaresize) + img = img.Scale(squaresize, new_height, IMAGE_QUALITY_HIGH) + offset = Point(0, (squaresize - new_height) / 2) + else: + new_width = int(w/float(h) * squaresize) + img = img.Scale(new_width, squaresize, IMAGE_QUALITY_HIGH) + offset = Point((squaresize - new_width) / 2, 0) + + return BitmapFromImage(img) + + return bmp + +# +#--- image effects +# + +@refmemoize +def wxbitmap_greyed(bitmap): + 'Returns a greyscale version of the bitmap.' + + return wximage_to_wxbitmap(wxbitmap_to_wximage(bitmap).ConvertToGreyscale()) + +@objmemoize +def pilimg_greyed(pil): + pil = pil.copy() + + # save the alpha channel if there is one. + alpha = 'A' in pil.getbands() + if alpha: r, g, b, a = pil.split() + + # convert to greyscale + pil = ImageOps.grayscale(pil) + + # reapply alpha channel (if necessary) + if alpha: pil.putalpha(a) + + return pil + + + +def drop_shadow(image, offset=(5, 5), background=(0, 0, 0, 0), shadow=0x444444, + border=8, iterations=3): + """ + Add a gaussian blur drop shadow to an image. + + image - The image to overlay on top of the shadow. + offset - Offset of the shadow from the image as an (x,y) tuple. Can be + positive or negative. + background - Background colour behind the image. + shadow - Shadow colour (darkness). + border - Width of the border around the image. This must be wide + enough to account for the blurring of the shadow. + iterations - Number of times to apply the filter. More iterations + produce a more blurred shadow, but increase processing time. + """ + image = image.PIL + + # Create the backdrop image -- a box in the background colour with a + # shadow on it. + totalWidth = image.size[0] + abs(offset[0]) + 2*border + totalHeight = image.size[1] + abs(offset[1]) + 2*border + back = Image.new(image.mode, (totalWidth, totalHeight), background) + + # Place the shadow, taking into account the offset from the image + shadowLeft = border + max(offset[0], 0) + shadowTop = border + max(offset[1], 0) + back.paste(shadow, [shadowLeft, shadowTop, shadowLeft + image.size[0], + shadowTop + image.size[1]]) + + # Apply the filter to blur the edges of the shadow. Since a small kernel + # is used, the filter must be applied repeatedly to get a decent blur. + n = 0 + while n < iterations: + back = back.filter(ImageFilter.BLUR) + n += 1 + + # Paste the input image onto the shadow backdrop + imageLeft = border - min(offset[0], 0) + imageTop = border - min(offset[1], 0) + back.paste(image, (imageLeft, imageTop)) + + return back + +def pil_combine(imgs, direction = wx.HORIZONTAL, align = wx.ALIGN_CENTER): + 'Combines images horizontally or vertically.' + + imgs = [img.PIL for img in imgs] + length_idx = [wx.HORIZONTAL, wx.VERTICAL].index(direction) + breadth_idx = 0 if length_idx else 1 + + length = breadth = 0 + + for img in imgs: + size = img.size + length += size[length_idx] + breadth = max(breadth, size[breadth_idx]) + + newsize = (length, breadth) if direction == wx.HORIZONTAL else (breadth, length) + newimg = Image.new('RGBA', newsize, (0, 0, 0, 0)) + + pos = (0, 0) + for img in imgs: + newimg.paste(img, pos) + if direction == wx.HORIZONTAL: + pos = (pos[0] + img.size[0], pos[1]) + else: + pos = (pos[0], pos[1] + img.size[1]) + + return newimg + +def rounded_corners(corner_size = 1, rounded_imgs = {}): + if not isinstance(corner_size, int): + import util + corner_size = util.try_this(lambda: int(bool(corner_size)), 1) + else: + corner_size = max(0, min(corner_size, 3)) + + try: return rounded_imgs[corner_size] + except KeyError: pass + + imgs = [] + + from gui import skin + rounded_img = Image.open(skin.resourcedir() / ('corner' + str(corner_size) + '.gif')).convert('L') + + for name, rotation in CORNERS: + mask = rounded_img.transpose(rotation) if rotation is not None else rounded_img + imgs.append(mask) + + return rounded_imgs.setdefault(corner_size, imgs) + +def rounded_mask(size, cornersize = 1): + ''' + Returns a grayscale image with the specified size, with alpha values + dropping off at the corners. + ''' + + img = Image.new('L', size, 255) + w, h = size + p, r = img.paste, rounded_corners(cornersize) + i = r[0]; p(i, (0, 0, i.size[0], i.size[1])) # top left + i = r[1]; p(i, (w-i.size[0], 0, w, i.size[1])) # top right + i = r[2]; p(i, (0, h-i.size[1], i.size[0], h)) # bottom left + i = r[3]; p(i, (w-i.size[0], h-i.size[1], w, h)) # bottom right + + return img + +@objmemoize +def pilimg_rounded(image, cornersize = 1): + ''' + Return a copy of the image (wx.Image, wx.Bitmap, or PIL.Image) with the + corners rounded. + + cornersize can be 1, 2, or 3, specifying the size of the corners to + chop off + ''' + + # at each pixel, take the minimum of the rounded mask and the alpha + # channel of the original image + newimage = image.copy() + newimage.putalpha(ImageMath.eval('convert(min(a,b), "L")', + a = newimage.split()[-1], + b = rounded_mask(newimage.size, cornersize))) + return newimage + + +@refmemoize +def wxbitmap_rounded(wxbitmap, cornersize = 1): +# print 'wxbitmap_rounded', wxbitmap, cornersize + + if cornersize == 0: + return wxbitmap + else: + return pil_to_wxbitmap(pilimg_rounded(wximage_to_pil(wxbitmap_to_wximage(wxbitmap)), cornersize)) + +def wxbitmap_decodedsize(b): + return b.Width * b.Height * (3 if not b.HasAlpha() else 4) + +def wximage_decodedsize(i): + return i.Width * i.Height * 4 + +def pil_decodedsize(p): + width, height = p.size + bpp = {'RGBA': 4, 'RGB': 3, 'L': 1}.get(p.mode, 0) + return width * height * bpp + + +# +#--- patching methods into classes +# + +Image.Image.Resized = pilimg_resized +Image.Image.ResizedSmaller = pil_resized_smaller +Image.Image.Rounded = pilimg_rounded +Image.Image.WXB = property(lambda pil: pil_to_wxbitmap(pil)) +Image.Image.WX = property(lambda pil: pil_to_wximage(pil)) +Image.Image.Greyed = property(pilimg_greyed) +Image.Image.ResizeCanvas = pil_resize_canvas +Image.Image.PIL = property(lambda pil: pil) +Image.Image.DecodedSize = property(lambda self: pil_decodedsize(self)) + +wx.Bitmap.Resized = wxbitmap_resized +wx.Bitmap.ResizedSmaller = wxbitmap_resized_smaller +wx.Bitmap.Greyed = property(wxbitmap_greyed) +wx.Bitmap.Rounded = wxbitmap_rounded +wx.Bitmap.WXB = property(lambda self: self) +wx.Bitmap.WX = property(lambda wxb: wxbitmap_to_wximage(wxb)) +wx.Bitmap.PIL = property(lambda self: wxbitmap_to_pil(self)) +wx.Bitmap.Inverted = property(wxbitmap_inverted) +wx.Bitmap.DecodedSize = property(lambda self: wxbitmap_decodedsize(self)) + +wx.Image.WX = property(lambda self: self) +wx.Image.WXB = property(lambda self: wximage_to_wxbitmap(self, 32)) +wx.Image.PIL = property(lambda self: wximage_to_pil(self)) +wx.Image.DecodedSize = property(lambda self: wximage_decodedsize(self)) + +wx.Icon.PIL = property(lambda self: wxicon_to_pil(self)) +wx.Icon.WXB = property(lambda self: wxicon_to_bitmap(self)) + + +# non-caching image conversion functions +def pil_to_wxb_nocache(pil): + return wx.BitmapFromImage(pil_to_wximage.nocache(pil)) + +def wxb_to_pil_nocache(wxb): + return wximage_to_pil.nocache(wxb.ConvertToImage()) + +if __name__ == '__main__': + from tests.testapp import testapp + from gui.skin import get + a = testapp('../../..') + + img = Image.new('RGBA', (40, 50), 'red')#get('appdefaults.taskbaricon') + img.Resized(100).Show() + + + a.MainLoop() diff --git a/digsby/src/gui/toolbox/keynames.py b/digsby/src/gui/toolbox/keynames.py new file mode 100644 index 0000000..f9ec992 --- /dev/null +++ b/digsby/src/gui/toolbox/keynames.py @@ -0,0 +1,179 @@ +import os +import wx + +# ordered for modifier entries in menus +modifiernames = [ + (wx.ACCEL_CTRL, 'Ctrl'), + (wx.ACCEL_ALT, 'Alt'), + (wx.ACCEL_SHIFT, 'Shift'), +# (wx.ACCEL_CMD, 'Ctrl'), +] + +keynames = { + wx.WXK_MENU: 'Menu', + wx.WXK_NUMPAD_TAB: 'Numpad Tab', + wx.WXK_NUMPAD_SEPARATOR: 'Numpad Separator', + wx.WXK_SNAPSHOT: 'Snapshot', + wx.WXK_DOWN: 'Down', + wx.WXK_CLEAR: 'Clear', + wx.WXK_ALT: 'Alt', + wx.WXK_COMMAND: 'Command', + wx.WXK_SPECIAL2: 'Special2', + wx.WXK_INSERT: 'Insert', + wx.WXK_NUMPAD_DECIMAL: 'Numpad Decimal', + wx.WXK_SHIFT: 'Shift', + wx.WXK_NEXT: 'Next', + wx.WXK_SCROLL: 'Scroll', + wx.WXK_CAPITAL: 'Capital', + wx.WXK_NUMPAD_HOME: 'Numpad Home', + wx.WXK_SPECIAL8: 'Special8', + wx.WXK_SPECIAL9: 'Special9', + wx.WXK_SPECIAL1: 'Special1', + wx.WXK_SPECIAL3: 'Special3', + wx.WXK_SPECIAL4: 'Special4', + wx.WXK_SPECIAL5: 'Special5', + wx.WXK_SPECIAL6: 'Special6', + wx.WXK_SPECIAL7: 'Special7', + wx.WXK_SPACE: 'Space', + wx.WXK_SELECT: 'Select', + wx.WXK_PAGEDOWN: 'Pagedown', + wx.WXK_F10: 'F10', + wx.WXK_END: 'End', + wx.WXK_F15: 'F15', + wx.WXK_UP: 'Up', + wx.WXK_SEPARATOR: 'Separator', + wx.WXK_NUMPAD_ENTER: 'Numpad Enter', + wx.WXK_NUMPAD_UP: 'Numpad Up', + wx.WXK_PAGEUP: 'Pageup', + wx.WXK_F3: 'F3', + wx.WXK_F2: 'F2', + wx.WXK_F1: 'F1', + wx.WXK_F6: 'F6', + wx.WXK_F5: 'F5', + wx.WXK_F4: 'F4', + wx.WXK_F9: 'F9', + wx.WXK_F8: 'F8', + wx.WXK_NUMPAD_DIVIDE: 'Numpad Divide', + wx.WXK_NUMPAD9: 'Numpad9', + wx.WXK_NUMPAD8: 'Numpad8', + wx.WXK_NUMPAD3: 'Numpad3', + wx.WXK_NUMPAD2: 'Numpad2', + wx.WXK_NUMPAD1: 'Numpad1', + wx.WXK_NUMPAD0: 'Numpad0', + wx.WXK_NUMPAD7: 'Numpad7', + wx.WXK_NUMPAD6: 'Numpad6', + wx.WXK_NUMPAD4: 'Numpad4', + wx.WXK_WINDOWS_MENU: 'Windows Menu', + wx.WXK_NUMPAD_SPACE: 'Numpad Space', + wx.WXK_NUMPAD_EQUAL: 'Numpad Equal', + wx.WXK_NUMPAD_RIGHT: 'Numpad Right', + wx.WXK_WINDOWS_LEFT: 'Windows Left', + wx.WXK_RETURN: 'Return', + wx.WXK_CONTROL: 'Control', + wx.WXK_SPECIAL18: 'Special18', + wx.WXK_HOME: 'Home', + wx.WXK_SPECIAL12: 'Special12', + wx.WXK_SPECIAL13: 'Special13', + wx.WXK_SPECIAL10: 'Special10', + wx.WXK_SPECIAL11: 'Special11', + wx.WXK_SPECIAL16: 'Special16', + wx.WXK_SPECIAL17: 'Special17', + wx.WXK_SPECIAL14: 'Special14', + wx.WXK_SPECIAL15: 'Special15', + wx.WXK_HELP: 'Help', + wx.WXK_SPECIAL19: 'Special19', + wx.WXK_NUMPAD_END: 'Numpad End', + wx.WXK_NUMPAD_PAGEUP: 'Numpad Pageup', + wx.WXK_NUMPAD_MULTIPLY: 'Numpad Multiply', + wx.WXK_DELETE: 'Delete', + wx.WXK_SPECIAL20: 'Special20', + wx.WXK_PAUSE: 'Pause', + wx.WXK_CANCEL: 'Cancel', + wx.WXK_ADD: 'Add', + wx.WXK_NUMPAD_F1: 'Numpad F1', + wx.WXK_NUMPAD_DELETE: 'Numpad Delete', + wx.WXK_MBUTTON: 'Mbutton', + wx.WXK_TAB: 'Tab', + wx.WXK_NUMPAD_DOWN: 'Numpad Down', + wx.WXK_ESCAPE: 'Escape', + wx.WXK_NUMLOCK: 'Numlock', + wx.WXK_LEFT: 'Left', + wx.WXK_PRIOR: 'Prior', + wx.WXK_NUMPAD_INSERT: 'Numpad Insert', + wx.WXK_EXECUTE: 'Execute', + wx.WXK_START: 'Start', + wx.WXK_F19: 'F19', + wx.WXK_F18: 'F18', + wx.WXK_F13: 'F13', + wx.WXK_F12: 'F12', + wx.WXK_F11: 'F11', + wx.WXK_F17: 'F17', + wx.WXK_F16: 'F16', + wx.WXK_F14: 'F14', + wx.WXK_RIGHT: 'Right', + wx.WXK_NUMPAD5: 'Numpad5', + wx.WXK_BACK: 'Back', + wx.WXK_RBUTTON: 'Rbutton', + wx.WXK_SUBTRACT: 'Subtract', + wx.WXK_NUMPAD_F4: 'Numpad F4', + wx.WXK_NUMPAD_F2: 'Numpad F2', + wx.WXK_NUMPAD_F3: 'Numpad F3', + wx.WXK_NUMPAD_ADD: 'Numpad Add', + wx.WXK_MULTIPLY: 'Multiply', + wx.WXK_F7: 'F7', + wx.WXK_NUMPAD_PAGEDOWN: 'Numpad Pagedown', + wx.WXK_DECIMAL: 'Decimal', + wx.WXK_NUMPAD_BEGIN: 'Numpad Begin', + wx.WXK_NUMPAD_SUBTRACT: 'Numpad Subtract', + wx.WXK_F22: 'F22', + wx.WXK_F23: 'F23', + wx.WXK_F20: 'F20', + wx.WXK_F21: 'F21', + wx.WXK_F24: 'F24', + wx.WXK_NUMPAD_LEFT: 'Numpad Left', + wx.WXK_LBUTTON: 'Lbutton', + wx.WXK_WINDOWS_RIGHT: 'Windows Right', + wx.WXK_DIVIDE: 'Divide', + wx.WXK_PRINT: 'Print', +} + +# keys which go to EVT_CHAR but aren't printable... +non_alphanumeric = set([ + wx.WXK_DELETE, + wx.WXK_PAGEDOWN, + wx.WXK_PAGEUP, + wx.WXK_TAB, + wx.WXK_ESCAPE, + wx.WXK_NUMPAD_SPACE, + wx.WXK_NUMPAD_TAB, + wx.WXK_NUMPAD_ENTER, + wx.WXK_NUMPAD_F1, + wx.WXK_NUMPAD_F2, + wx.WXK_NUMPAD_F3, + wx.WXK_NUMPAD_F4, + wx.WXK_NUMPAD_HOME, + wx.WXK_NUMPAD_LEFT, + wx.WXK_NUMPAD_UP, + wx.WXK_NUMPAD_RIGHT, + wx.WXK_NUMPAD_DOWN, + wx.WXK_NUMPAD_PAGEUP, + wx.WXK_NUMPAD_PAGEDOWN, + wx.WXK_NUMPAD_END, + wx.WXK_NUMPAD_BEGIN, + wx.WXK_NUMPAD_INSERT, + wx.WXK_NUMPAD_DELETE, + wx.WXK_NUMPAD_EQUAL, + wx.WXK_NUMPAD_MULTIPLY, + wx.WXK_NUMPAD_ADD, + wx.WXK_NUMPAD_SEPARATOR, + wx.WXK_NUMPAD_SUBTRACT, + wx.WXK_NUMPAD_DECIMAL, + wx.WXK_NUMPAD_DIVIDE, +]) + +if os.name == 'nt': + non_alphanumeric.update([wx.WXK_WINDOWS_LEFT, wx.WXK_WINDOWS_RIGHT, wx.WXK_WINDOWS_MENU]) + +# include all the function keys +non_alphanumeric.update((getattr(wx, 'WXK_F' + str(x)) for x in xrange(1, 13))) + diff --git a/digsby/src/gui/toolbox/monitor/__init__.py b/digsby/src/gui/toolbox/monitor/__init__.py new file mode 100644 index 0000000..63bf0f6 --- /dev/null +++ b/digsby/src/gui/toolbox/monitor/__init__.py @@ -0,0 +1,41 @@ +import warnings +import wx + +if 'wxMSW' in wx.PlatformInfo: + from monitorwin import Monitor as MonitorWin +else: + class MonitorDisplay(wx.Display): + def __init__(self, displaynum): + wx.Display.__init__(self, displaynum) + + @staticmethod + def GetFromWindow(window): + displaynum = wx.Display.GetFromWindow(window) + if displaynum != wx.NOT_FOUND: + return wx.Display(displaynum) + + return None + + @staticmethod + def GetFromPoint(point, find_near = False): + displaynum = wx.Display.GetFromPoint(point) + if displaynum != wx.NOT_FOUND: + return wx.Display(displaynum) + if find_near: + warnings.warn('Monitor.GetFromPoint with find_near = True is not implemented (and is returning display 0)') + return wx.Display(0) + return None + + @staticmethod + def GetFromRect(rect): + return Monitor.GetFromPoint(rect.GetPosition()) + + @staticmethod + def All(): + monitors = [] + for displaynum in range(0, wx.Display.GetCount()): + monitors.append(wx.Display(displaynum)) + + return monitors + +Monitor = MonitorWin if 'wxMSW' in wx.PlatformInfo else MonitorDisplay diff --git a/digsby/src/gui/toolbox/monitor/monitorwin.py b/digsby/src/gui/toolbox/monitor/monitorwin.py new file mode 100644 index 0000000..48c3c1e --- /dev/null +++ b/digsby/src/gui/toolbox/monitor/monitorwin.py @@ -0,0 +1,162 @@ +import ctypes +from ctypes import byref, WinError, wstring_at +from ctypes.wintypes import POINT, RECT, DWORD, WCHAR +from gui.native.win.winextensions import wxRectFromRECT, wxRectToRECT +import wx +from wx import Rect, GetMousePosition +import cgui + +# Windows user32 functions +user32 = ctypes.windll.user32 +MonitorFromPoint = user32.MonitorFromPoint +MonitorFromWindow = user32.MonitorFromWindow +MonitorFromRect = user32.MonitorFromRect +GetMonitorInfo = user32.GetMonitorInfoW + +# constants +MONITOR_DEFAULTTONULL = 0 +MONITOR_DEFAULTTOPRIMARY = 1 +MONITOR_DEFAULTTONEAREST = 2 + +CCHDEVICENAME = 32 + +class MONITORINFOEX(ctypes.Structure): + _fields_ = [('cbSize', DWORD), + ('rcMonitor', RECT), + ('rcWork', RECT), + ('dwFlags', DWORD), + ('szDevice', WCHAR * CCHDEVICENAME)] + +MONITORINFOEX_size = ctypes.sizeof(MONITORINFOEX) + +# seq -> POINT +def makePOINT(point): + pt = POINT() + pt.x, pt.y = point + return pt + +def Monitor_FromPoint(point, find_near = False): + hmonitor = MonitorFromPoint(makePOINT(point), MONITOR_DEFAULTTONEAREST if find_near else MONITOR_DEFAULTTONULL) + if hmonitor: + return Monitor(hmonitor) + +def Monitor_FromWindow(window): + return Monitor_FromHandle(window.Handle) + +def Monitor_FromHandle(handle): + return Monitor(MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST)) + +def Monitor_FromRect(rect, find_near = True): + hmonitor = MonitorFromRect(byref(wxRectToRECT(rect)), MONITOR_DEFAULTTONEAREST if find_near else MONITOR_DEFAULTTONULL) + if hmonitor: + return Monitor(hmonitor) + +def Monitor_FromDeviceId(deviceId, orPrimary = True): + mons = Monitor.All() + for mon in mons: + if mon.DeviceId == deviceId: + return mon + + if orPrimary: + for mon in mons: + if mon.Primary: + return mon + + return None + +class Monitor(object): + def __init__(self, hmonitor): + self.hmonitor = hmonitor + + def GetClientArea(self): + try: return self._clientarea + except AttributeError: + self._getinfo() + return self._clientarea + + ClientArea = property(GetClientArea) + + def GetGeometry(self): + try: return self._geometry + except AttributeError: + self._getinfo() + return self._geometry + + Geometry = property(GetGeometry) + + def GetDeviceId(self): + try: return self._deviceid + except AttributeError: + self._getinfo() + return self._deviceid + + DeviceId = property(GetDeviceId) + + GetFromPoint = staticmethod(Monitor_FromPoint) + GetFromPointer = staticmethod(lambda: Monitor_FromPoint(GetMousePosition(), find_near = True)) + GetFromWindow = staticmethod(Monitor_FromWindow) + GetFromRect = staticmethod(Monitor_FromRect) + GetFromHandle = staticmethod(Monitor_FromHandle) + GetFromDeviceId = staticmethod(Monitor_FromDeviceId) + + @staticmethod + def GetCount(): + return len(get_hmonitors()) + + @staticmethod + def All(): + return [Monitor(hmonitor) for hmonitor in get_hmonitors()] + + if hasattr(cgui, 'GetMonitorInfo'): + def _getinfo(self): + work, mon, self._deviceid = cgui.GetMonitorInfo(self.hmonitor) + + self._clientarea = RECTtuple_to_rect(work) + self._geometry = RECTtuple_to_rect(mon) + else: + def _getinfo(self): + info = MONITORINFOEX() + info.cbSize = MONITORINFOEX_size + if not GetMonitorInfo(self.hmonitor, byref(info)): + raise WinError() + + self._clientarea = wxRectFromRECT(info.rcWork) + self._geometry = wxRectFromRECT(info.rcMonitor) + self._deviceid = wstring_at(info.szDevice) + + def IsPrimary(self): + pos = self.Geometry.Position + + # Primary monitor is always at (0, 0) + if pos.x == pos.y == 0: + return True + return False + + Primary = property(IsPrimary) + +try: + from cgui import GetHMONITORs as get_hmonitors #@UnusedImport +except ImportError: + EnumDisplayMonitors = user32.EnumDisplayMonitors + _hmonitors = [] + + def MultimonEnumProc(hMonitor, hdcMonitor, lprcMonitor, dwData): + _hmonitors.append(hMonitor) + return True + + def get_hmonitors(): + global _hmonitors + del _hmonitors[:] + EnumDisplayMonitors(None, None, MultimonEnumProc, None) + return _hmonitors + +def RECTtuple_to_rect(r): + return Rect(r[0], r[1], r[2]-r[0], r[3]-r[1]) + +wx.Window.Monitor = Monitor_FromWindow + +def main(): + print [m.ClientArea for m in Monitor.All()] + +if __name__ == '__main__': + main() diff --git a/digsby/src/gui/toolbox/refreshtimer.py b/digsby/src/gui/toolbox/refreshtimer.py new file mode 100644 index 0000000..953a568 --- /dev/null +++ b/digsby/src/gui/toolbox/refreshtimer.py @@ -0,0 +1,64 @@ +import wx +import sys + +class RefreshTimer(wx.Timer): + """ + This calls refresh on all registered objects every second + """ + + def __init__(self): + wx.Timer.__init__(self) + self.registered = set() + self.inited = True + + def Register(self,item): + """ + Adds the item to the item set, and starts the timer if not yet started. + It's fine to add an item more than once, becasue all items are stored + as a set items added more tan once will still refresh only once. + """ + + self.registered.add(item) + + if not self.IsRunning(): +# print '--------------------Starting RefreshTimer--------------------' + self.Start(1000) + + def UnRegister(self,item): + """ + Removes and item fro mthe set, since only one refernce is held + to the item this will stop the item from being refreshed no matter + how many times it was registered. It has also been made safe to + unregister and item that isn't currently registered + """ + try: + self.registered.remove(item) + except Exception: + pass + + if not len(self.registered) and self.IsRunning(): +# print '--------------------Stoping RefreshTimer--------------------' + self.Stop() + + def Notify(self): + """ + Refreshes every item in the set, if there is an error with calling + refresh it removes that item from the set + """ +# print '--------------------Notify RefreshTimer--------------------' + for item in set(self.registered): + try: + item.Refresh() + except Exception: + sys.stderr.write(''.join(['Error refreshing ',str(item),', removing from list'])) + self.UnRegister(item) + + +_refresh_timer_instance = None + +def refreshtimer(): + global _refresh_timer_instance + if _refresh_timer_instance is None: + _refresh_timer_instance = RefreshTimer() + + return _refresh_timer_instance diff --git a/digsby/src/gui/toolbox/scrolling.py b/digsby/src/gui/toolbox/scrolling.py new file mode 100644 index 0000000..3893bbe --- /dev/null +++ b/digsby/src/gui/toolbox/scrolling.py @@ -0,0 +1,124 @@ +import wx + +class ScrollWinBase(object): + + def BindScrollWin(self, obj): + return + obj.Bind(wx.EVT_SCROLLWIN, self._on_scrollwin) + + def _on_scrollwin(self, e): + pass + +class ScrollWinMixin(ScrollWinBase): + + def _on_scrollwin(self, e): + scrollType = e.GetEventType() + horiz = e.GetOrientation() == wx.HORIZONTAL + if horiz: + return e.Skip() + + if (scrollType == wx.EVT_SCROLLWIN_THUMBTRACK or scrollType == wx.EVT_SCROLLWIN_THUMBRELEASE): + return e.Skip() + elif (scrollType == wx.EVT_SCROLLWIN_LINEDOWN): + self.ScrollLines(1) + elif (scrollType == wx.EVT_SCROLLWIN_LINEUP): + self.ScrollLines(-1) + elif (scrollType == wx.EVT_SCROLLWIN_PAGEUP): + self.ScrollPages(1) + elif (scrollType == wx.EVT_SCROLLWIN_PAGEDOWN): + self.ScrollPages(-1) + else: + return e.Skip(); + +class WheelBase(object): + + def get_wheel_lines(self, rotation, e): + return int(round(rotation / float(e.GetWheelDelta()) * e.LinesPerAction)) + + def rotation_for_lines(self, lines, e): + return int(float(e.GetWheelDelta()) / e.LinesPerAction * lines) + + def BindWheel(self, obj): + obj.Bind(wx.EVT_MOUSEWHEEL, self._on_mousewheel) + + def _on_mousewheel(self, e): + pass + +class WheelScrollMixin(WheelBase): + def __init__(self, *a, **k): + super(WheelScrollMixin, self).__init__(*a, **k) + self.wheel_rotation_scroll = 0 + + def _on_mousewheel(self, e): + wheel_rotation_scroll = e.WheelRotation + self.wheel_rotation_scroll + lines = self.get_wheel_lines(wheel_rotation_scroll, e) + if lines: + self._do_scroll(e, -lines) + self.wheel_rotation_scroll = self.rotation_for_lines(-lines, e) + wheel_rotation_scroll + else: + self.wheel_rotation_scroll = wheel_rotation_scroll + + def _do_scroll(self, e, lines): + self.ScrollLines(lines) + +class FrozenLoopScrollMixin(object): + def ScrollLines(self, lines): + #HAX: webkit doesn't understand multiple lines at a time. + abslines = int(abs(lines)) + with self.Frozen(): + for _i in xrange(abslines): + super(FrozenLoopScrollMixin, self).ScrollLines(int(lines/abslines)) + +class WheelZoomMixin(WheelBase): + def __init__(self, *a, **k): + super(WheelZoomMixin, self).__init__(*a, **k) + self.wheel_rotation_zoom = 0 + + def _on_mousewheel(self, e): + wheel_rotation_zoom = e.WheelRotation + self.wheel_rotation_zoom + lines = self.get_wheel_lines(wheel_rotation_zoom, e) + if lines > 0: + self.IncreaseTextSize() + elif lines < 0: + self.DecreaseTextSize() + else: + self.wheel_rotation_zoom = wheel_rotation_zoom + +class WheelCtrlZoomMixin(WheelZoomMixin): + def _on_mousewheel(self, e): + if e.CmdDown(): + WheelZoomMixin._on_mousewheel(self, e) + else: + super(WheelZoomMixin, self)._on_mousewheel(e) + +class WheelScrollCtrlZoomMixin(WheelCtrlZoomMixin, WheelScrollMixin): + pass + +class WheelScrollFastMixin(WheelScrollMixin): + def _do_scroll(self, e, lines): + if self._fast_scroll_test(e): + self.ScrollPages(lines) + else: + return super(WheelScrollFastMixin, self)._do_scroll(e, lines) + +class WheelShiftScrollFastMixin(WheelScrollFastMixin): + def _fast_scroll_test(self, e): + if e.ShiftDown(): + return True + else: + try: + return super(WheelShiftScrollFastMixin, self)._fast_scroll_test(e) + except AttributeError: + return False + +class WheelCtrlScrollFastMixin(WheelScrollFastMixin): + def _fast_scroll_test(self, e): + if e.CmdDown(): + return True + else: + try: + return super(WheelCtrlScrollFastMixin, self)._fast_scroll_test(e) + except AttributeError: + return False + + diff --git a/digsby/src/gui/toolbox/texthistory.py b/digsby/src/gui/toolbox/texthistory.py new file mode 100644 index 0000000..d20de24 --- /dev/null +++ b/digsby/src/gui/toolbox/texthistory.py @@ -0,0 +1,105 @@ +''' + +history support for text ctrls + +ctrl+up -> older +ctrl+down -> newer + +TODO: use util.fmtstr and textctrl.GetFormattedValue to preserve formatting + +''' +import wx +from util import strip_html2 +from util.primitives.structures import roset +from util.lrucache import lru_cache + +strip_html2 = lru_cache(80)(strip_html2) + + +class TextHistory(object): + def __init__(self, ctrl = None, backlog = None): + + self.history = roset() + self.clip = '' + self.index = 0 + self.backlog = backlog + + if ctrl is not None: + self.bind_textctrl(ctrl) + + def commit(self, val): + if not val: return + + # [x, y, z, ''] -> [x, y, z, A, ''] + + # insert the new message + self.history.add(val) + if self.clip == val: + self.clip = '' + + def prev(self): + val = self.get() + clip = self.clip + + if self.index == 0 and val: + self.history.add(val) + self.index -= 1 + + if not self.index and clip and clip != val: + self.set(clip) + elif self.history and abs(self.index) < len(self.history): + self.index -= 1 + self.set(self.history[self.index]) + elif self.backlog is not None: + try: + messageobj = self.backlog.next() + message = getattr(messageobj, 'message', None) + if message is not None: + message = strip_html2(message) + except StopIteration: + self.backlog = None + else: + if message is not None: + self.history.insert(0, message) + self.prev() + + + def next(self): + val = self.get() + + if self.index: + self.index += 1 + if self.index: + self.set(self.history[self.index]) + elif self.clip: + self.set(self.clip) + else: + self.set('') + elif val: + self.clip = val + self.set('') + + def ParadoxCheck(self): + present = self.get() + past = self.history[self.index] + if present != past: + self.index = 0 + + def bind_textctrl(self, ctrl): + ctrl.Bind(wx.EVT_KEY_DOWN, self.keydown) + self.set = ctrl.SetValue + self.get = ctrl.GetValue + + + def keydown(self, e, WXK_DOWN = wx.WXK_DOWN, WXK_UP = wx.WXK_UP): + c = e.KeyCode + + + if e.GetModifiers() == wx.MOD_CMD: + if c == WXK_DOWN: return self.next() + elif c == WXK_UP: return self.prev() + elif self.index: self.ParadoxCheck() + elif self.index: + self.ParadoxCheck() + + e.Skip() diff --git a/digsby/src/gui/toolbox/toolbox.py b/digsby/src/gui/toolbox/toolbox.py new file mode 100644 index 0000000..44aa68f --- /dev/null +++ b/digsby/src/gui/toolbox/toolbox.py @@ -0,0 +1,1607 @@ +''' +GUI utilities. +''' +from __future__ import with_statement +from __future__ import division +import config +import functools +import wx, struct +from wx import GetTopLevelWindows, Point, Size +from logging import getLogger; log = getLogger('gui.util') +from traceback import print_exc +from PIL import Image, ImageDraw, ImageFont +from ConfigParser import ConfigParser +import sys +from collections import defaultdict +from time import clock as time_clock +from gui.toolbox.monitor import Monitor +import cgui, new +import simplejson as json +import os.path + +# adds methods to Bitmap, Image, etc... +import imagefx #@UnusedImport + +wxMSW = 'wxMSW' in wx.PlatformInfo + +# colors in skin YAML can start with this string +color_prefix = '0x' + +def __repr__(self): + '''wxBitmap repr showing its .path, if it has one.''' + + try: + path = getattr(self, 'path', '') + return '' % (id(self), (' '+os.path.normpath(path)) if path else '') + except Exception: + return '' % id(self) + +wx.Bitmap.__repr__ = __repr__ +del __repr__ + +# convenience method for removing all of a wxMenu's items +wx.Menu.RemoveAllItems = lambda self: [self.RemoveItem(item) for item in self.GetMenuItems()] + +wx.Window.Tip = property(lambda self: self.GetToolTip().GetTip(), + lambda self, tip: self.SetToolTip(wx.ToolTip(tip))) + +def check_destroyed(ctrl): + if wx.IsDestroyed(ctrl): + code = sys._getframe(1).f_code + print >> sys.stderr, 'WARNING: destroyed object being used (%s in %s:%d)' % \ + (code.co_name, code.co_filename, code.co_firstlineno) + return True + return False + +################################################################################ +if wxMSW: + import ctypes + from ctypes import byref, WinError + from ctypes.wintypes import UINT, POINT, RECT + user32 = ctypes.windll.user32 + + class WINDOWPLACEMENT(ctypes.Structure): + _fields_ = [('length', UINT), + ('flags', UINT), + ('showCmd', UINT), + ('ptMinPosition', POINT), + ('ptMaxPosition', POINT), + ('rcNormalPosition', RECT)] + + + def GetWindowPlacement(win): + hwnd = win.GetHandle() + p = WINDOWPLACEMENT() + p.length = ctypes.sizeof(WINDOWPLACEMENT) + if not user32.GetWindowPlacement(hwnd, byref(p)): + raise WinError() + + return windowplacement_todict(p) + + def SetWindowPlacement(win, d): + d2 = GetWindowPlacement(win) + d2['rcNormalPosition'] = d['rcNormalPosition'] + d2['showCmd'] |= 0x0020 # SWP_FRAMECHANGED + d = d2 + + p = windowplacement_fromdict(d) + if not user32.SetWindowPlacement(win.Handle, byref(p)): + raise WinError() + + def windowplacement_todict(p): + return dict(flags = p.flags, + showCmd = p.showCmd, + ptMinPosition = (p.ptMinPosition.x, p.ptMinPosition.y), + ptMaxPosition = (p.ptMaxPosition.y, p.ptMaxPosition.y), + rcNormalPosition = (p.rcNormalPosition.left, p.rcNormalPosition.top, p.rcNormalPosition.right, p.rcNormalPosition.bottom)) + + def windowplacement_fromdict(d): + p = WINDOWPLACEMENT() + p.length = ctypes.sizeof(WINDOWPLACEMENT) + + p.showCmd = int(d['showCmd']) + + p.ptMinPosition = POINT() + p.ptMinPosition.x = int(d['ptMinPosition'][0]) + p.ptMinPosition.y = int(d['ptMinPosition'][1]) + + p.ptMaxPosition = POINT() + p.ptMaxPosition.x = int(d['ptMaxPosition'][0]) + p.ptMaxPosition.y = int(d['ptMaxPosition'][1]) + + p.rcNormalPosition = RECT() + p.rcNormalPosition.left, p.rcNormalPosition.top, p.rcNormalPosition.right, p.rcNormalPosition.bottom = d['rcNormalPosition'] + + return p + +################################################################################ + + +def DeleteRange(textctrl, b, e): + if b != e: + with textctrl.Frozen(): + textctrl.Remove(b, e) + textctrl.InsertionPoint = b + +def DeleteWord(textctrl): + ''' + Deletes the last word (or part of word) at the cursor. + + Commonly bound to Ctrl+Backspace. + + TODO: ignores punctuation--like + + this.is.a.dotted.word[CTRL+BACKSPACE] + + will delete the whole line. is that what we want? + ''' + + i = textctrl.InsertionPoint + s = textctrl.Value + + if not s or i < 1: return + + e = i + while s[i-1] == ' ' and i != 0: + i -= 1 + + b = s.rfind(' ', 0, i) + 1 if i != 0 else 0 + + if b == -1: + b = 0 + + DeleteRange(textctrl, b, e) + +def DeleteRestOfLine(textctrl): + ''' + Deletes from the cursor until the end of the line. + + Emulates Control+K from many Linux and Mac editors. + ''' + + i = textctrl.InsertionPoint + + # find the next newline + j = textctrl.Value[i:].find('\n') + j = textctrl.LastPosition if j == -1 else j + i + + # if the cursor is on the last character of the line before a newline, + # just delete the newline + if i == j and j + 1 <= textctrl.LastPosition: + j += 1 + + DeleteRange(textctrl, i, j) + + + +if 'wxMac' in wx.PlatformInfo: + AutoDC = wx.PaintDC +else: + AutoDC = wx.AutoBufferedPaintDC + + +# def to_icon(bitmap): +# return wx.IconFromBitmap(bitmap.WXB) +def to_icon(bitmap, size = None): + if isinstance(bitmap, wx.Image): + bitmap = wx.BitmapFromImage(bitmap) + + bitmap = bitmap.WXB + + return wx.IconFromBitmap(bitmap.Resized(size) if size is not None else bitmap) + +wx.Rect.Pos = new.instancemethod(cgui.RectPosPoint, None, wx.Rect) + + +from cgui import Subtract as cgui_Subtract + +def Rect_Subtract(r, left = 0, right = 0, up = 0, down = 0): + r.x, r.y, r.width, r.height = cgui_Subtract(r, left, right, up, down) + return r + +def Rect_SubtractCopy(r, left = 0, right = 0, up = 0, down = 0): + return cgui_Subtract(r, left, right, up, down) + +wx.Rect.Subtract = new.instancemethod(Rect_Subtract, None, wx.Rect) +wx.Rect.SubtractCopy = new.instancemethod(Rect_SubtractCopy, None, wx.Rect) +del Rect_Subtract + + +wx.Rect.AddMargins = new.instancemethod(cgui.RectAddMargins, None, wx.Rect) + +# methods for getting/setting "always on top" state for top level windows +# + +def GetOnTop(toplevelwin): + 'Returns True if this window is always on top, False otherwise.' + s = toplevelwin.WindowStyleFlag + + return bool(s & wx.STAY_ON_TOP) + +def SetOnTop(toplevelwin, ontop): + '''Sets this window's "always on top" state.''' + + if ontop: + flag = toplevelwin.WindowStyleFlag | wx.STAY_ON_TOP + else: + flag = toplevelwin.WindowStyleFlag & ~wx.STAY_ON_TOP + + toplevelwin.WindowStyleFlag = flag + +def ToggleOnTop(toplevelwin): + toplevelwin.OnTop = not toplevelwin.OnTop + +wx.TopLevelWindow.OnTop = property(GetOnTop, SetOnTop) +wx.TopLevelWindow.ToggleOnTop = ToggleOnTop + + +class FocusTimer(wx.Timer): + def __init__(self, draw = False): + wx.Timer.__init__(self) + self.draw = draw + + def Notify(self): + c = self.last_focused = wx.Window.FindFocus() + r = c.ScreenRect if c is not None else wx.Rect() + print 'wx.Window.FindFocus() ->', c, 'at', r + + if self.draw: + dc = wx.ScreenDC() + p = wx.RED_PEN + p.SetWidth(4) + dc.SetPen(p) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.DrawRectangleRect(r) + + +def trackfocus(update_ms = 2000, draw = False): + t = FocusTimer(draw = draw) + t.Start(update_ms) + return t + +# +# patch hcenter and vcenter methods into wxRect for centering images/rectangles +# + +def VCenter(rect, img): + return rect.y + rect.height / 2 - img.Height / 2 + +def HCenter(rect, img): + return rect.x + rect.width / 2 - img.Width / 2 + +def VCenterH(rect, h): + return rect.y + rect.height / 2 - h / 2 + +def HCenterW(rect, w): + return rect.x + rect.width / 2 - w / 2 + +def CenterPoint(rect, pt): + w, h = pt + return rect.x + rect.HCenterW(w), rect.y + rect.VCenterH(h) + +wx.Rect.VCenter = VCenter +wx.Rect.HCenter = HCenter +wx.Rect.VCenterH = VCenterH +wx.Rect.HCenterW = HCenterW +wx.Rect.CenterPoint = CenterPoint + +Image.Image.Width = property(lambda image: image.size[0]) +Image.Image.Height = property(lambda image: image.size[1]) + +class progress_dialog(object): + 'Threadsafe progress dialog.' + + def __init__(self, message, title): + self.stopped = False + + # callback to the GUI thread for dialog creation + wx.CallAfter(self.create_dialog, message, title) + + def create_dialog(self, message, title): + # dialog will not have a close button or system menu + self.dialog = d = wx.Dialog(None, -1, title, style = wx.CAPTION) + + d.Sizer = s = wx.BoxSizer(wx.VERTICAL) + self.gauge = wx.Gauge(d, -1, style = wx.GA_HORIZONTAL) + + s.Add(wx.StaticText(d, -1, message), 0, wx.EXPAND | wx.ALL, 10) + s.Add(self.gauge, 0, wx.EXPAND | wx.ALL, 10) + + self.timer = wx.PyTimer(self.on_timer) + self.timer.StartRepeating(300) + + self.gauge.Pulse() + s.Layout() + d.Fit() + d.CenterOnScreen() + d.Show() + + def on_timer(self): + if self.stopped: + self.dialog.Destroy() + self.timer.Stop() + del self.dialog + del self.timer + else: + self.gauge.Pulse() + + def stop(self): + self.stopped = True + +def yes_no_prompt(title, text, default = True): + flags = wx.NO_DEFAULT * (not default) + result = wx.MessageBox(text, title, style = wx.YES_NO | flags) + if result == wx.YES: + return True + elif result == wx.NO: + return False + else: + return None + +def ShowImage(b, title = ''): + ''' + Displays the given wxImage, wxBitmap, PIL image, or skin region on screen + in a frame. + ''' + title = title + ' ' + repr(b) + + b = getattr(b, 'WXB', b) + + f = wx.Frame(None, title = title, style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE) + if isinstance(b, wx.Bitmap): + f.SetClientRect((0, 0, b.Width, b.Height)) + def paint_bitmap(e): + dc = wx.AutoBufferedPaintDC(f) + dc.Brush, dc.Pen = wx.CYAN_BRUSH, wx.TRANSPARENT_PEN + dc.DrawRectangleRect(f.ClientRect) + dc.DrawBitmap(b, 0, 0, True) + f.Bind(wx.EVT_PAINT, paint_bitmap) + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + elif isinstance(b, wx.Colour): + f.SetBackgroundColour(b) + f.SetClientRect((0, 0, 200, 200)) + else: + f.SetClientRect((0, 0, 200, 200)) + def paint_skinregion(e): + dc = wx.AutoBufferedPaintDC(f) + dc.Brush, dc.Pen = wx.WHITE_BRUSH, wx.TRANSPARENT_PEN + dc.DrawRectangleRect(f.ClientRect) + b.Draw(dc, f.ClientRect) + f.Bind(wx.EVT_PAINT, paint_skinregion) + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + f.CenterOnScreen() + f.Show() + +# allow ".Show()" on any image or color object to display it on screen +Image.Image.Show = wx.Image.Show = wx.Bitmap.Show = wx.Icon.Show = wx.Colour.Show = ShowImage + +def wx_prop(attrname, field='Value', set_cleanup=lambda x:x, get_cleanup=lambda x:x): + def set(self, val): + setattr(getattr(self, attrname), field, set_cleanup(val)) + def get(self): + return get_cleanup(getattr(getattr(self, attrname), field)) + return property(get, set) + + +def TextEntryDialog(message, caption = '', default_value = '', password = False, limit=1024): + style = wx.OK | wx.CANCEL | wx.CENTRE + if password: + style |= wx.TE_PASSWORD + + TED = wx.TextEntryDialog(None, message, caption, default_value, style = style) + return TED + +def GetTextFromUser_FixInput(val, limit): + if limit is not None: + if len(val) > limit: + print >>sys.stderr, "Data is %d bytes long, cutting to %d bytes" % (len(val), limit) + val = val[:limit] + + return val + +def GetTextFromUser(message, caption = '', default_value = '', password = False, limit=1024): + try: + TED = TextEntryDialog(message, caption, default_value, password, limit) + id = TED.ShowModal() + val = TED.Value + finally: + TED.Destroy() + + val = GetTextFromUser_FixInput(val, limit) + return val if id == wx.ID_OK else None + +def make_okcancel(name, cls): + class okcancel_class(cls): + 'Wraps any component in a OK/Cancel dialog.' + + dialog_style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX + + def __init__(self, parent, id=-1, + title=None, + ok_caption = '', + cancel_caption = '', + style = dialog_style): + + if title is None: + title = _("Confirm") + + cls.__init__(self, parent, id, title=title, style=style) + + self.OKButton = ok = wx.Button(self, wx.ID_OK, ok_caption) + cancel = wx.Button(self, wx.ID_CANCEL, cancel_caption) + + if config.platform == 'win': + button_order = [ok, cancel] + else: + button_order = [cancel, ok] + + self._button_sizer = hbox = wx.BoxSizer(wx.HORIZONTAL) + + if hasattr(self, 'ExtraButtons'): + ctrl = self.ExtraButtons() + if ctrl is not None: + hbox.Add(ctrl, 0, wx.EXPAND | wx.ALL, 5) + + hbox.AddStretchSpacer(1) + + for button in button_order: + hbox.Add(button, 0, wx.ALL, 5) + + vbox = wx.BoxSizer(wx.VERTICAL) + vbox.Add(hbox, 0, wx.ALL | wx.EXPAND, 7) + self.vbox = vbox + + self.SetSizer(vbox) + + self.Layout() + ok.SetDefault() + + def set_component(self, c, border=7, line=False): + self.vbox.Insert(0, c, 1, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border) + if line: + self.vbox.Insert(1, wx.StaticLine(self), 0, wx.EXPAND | wx.LEFT | wx.RIGHT, border) + + self.Layout() + + @property + def ButtonSizer(self): + return self._button_sizer + + okcancel_class.__name__ = name + return okcancel_class + +OKCancelDialog = make_okcancel('OKCancelDialog', wx.Dialog) +OKCancelFrame = make_okcancel('OKCancelFrame', wx.Frame) + +class Link(wx.HyperlinkCtrl): + def __init__(self, parent, label, url): + wx.HyperlinkCtrl.__init__(self, parent, -1, label, url) + self.HoverColour = self.VisitedColour = self.NormalColour + + +class NonModalDialogMixin(object): + def ShowWithCallback(self, cb=None): + self.cb = cb + self.Bind(wx.EVT_BUTTON, self.on_button) + self.Show() + self.Raise() + + def on_button(self, e): + ok = e.Id == wx.ID_OK + self.Hide() + cb, self.cb = self.cb, None + if cb is not None: + import util + with util.traceguard: + cb(ok) + + self.Destroy() + +class SimpleMessageDialog(OKCancelDialog, NonModalDialogMixin): + def __init__(self, parent, title, message, + icon, ok_caption='', cancel_caption='', + style=None, + link=None, + wrap=None): + + if style is None: + style = self.dialog_style + + if link is not None: + def ExtraButtons(): + self._panel = wx.Panel(self) + return Link(self, link[0], link[1]) + self.ExtraButtons = ExtraButtons + + OKCancelDialog.__init__(self, parent, title=title, + ok_caption=ok_caption, cancel_caption=cancel_caption, + style=style) + + self.icon = icon + if icon is not None: + self.SetFrameIcon(self.icon) + + p = self._panel + p.Bind(wx.EVT_PAINT, self.OnPanelPaint) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + p.SetBackgroundColour(wx.WHITE) + + static_text = wx.StaticText(p, -1, message) + + sizer.AddSpacer((60, 20)) + sizer.Add(static_text, 1, wx.EXPAND) + + main_sizer.Add(sizer, 1, wx.EXPAND | wx.ALL, 10) + main_sizer.Add((5,5)) + p.SetSizer(main_sizer) + + self.set_component(p, border=0) + + if wrap is not None: + static_text.Wrap(wrap) + + self.Fit() + + + def OnPanelPaint(self, e): + dc = wx.PaintDC(self._panel) + + icon = self.icon + if icon is not None: + dc.DrawBitmap(icon, 20, 14, True) + + def ExtraButtons(self): + self._panel = wx.Panel(self) + +class UpgradeDialog(SimpleMessageDialog): + dialog_style = SimpleMessageDialog.dialog_style & ~wx.CLOSE_BOX + def __init__(self, *a, **k): + super(UpgradeDialog, self).__init__(*a, **k) + self.SetEscapeId(wx.ID_NONE) + + @classmethod + def show_dialog(cls, parent, title, message, success=None): + wx.CallAfter(cls.do_show_dialog, parent, title, message, success) + + @classmethod + def do_show_dialog(cls, parent, title, message, success=None): + dialog = cls(parent, title=title, message=message) + dialog.ShowWithCallback(success) + +try: + from cgui import FindTopLevelWindow +except ImportError: + print >> sys.stderr, "WARNING: using slow FindTopLevelWindow" + def FindTopLevelWindow(window): + return window if window.TopLevel else FindTopLevelWindow(window.Parent) + +# wx.Window.GetNormalRect : return the non maximized dimensions of a window +try: + from cgui import GetNormalRect +except ImportError: + def GetNormalRect(win): + return win.Rect + + +wx.WindowClass.NormalRect = property(GetNormalRect) +wx.WindowClass.GetNormalRect = new.instancemethod(GetNormalRect, None, wx.WindowClass) + +wx.WindowClass.Top = property(FindTopLevelWindow) + +def edit_list(parent=None, obj_list=None, title="Editing List"): + + if not isinstance(obj_list, list): + obj_list = [] + + diag = OKCancelDialog(wx.GetTopLevelParent(parent), title=title) + t = type(obj_list[0]) if len(obj_list) else None + textctrl = wx.TextCtrl(diag, value = ','.join([str(i) for i in obj_list])) + diag.set_component(textctrl) + textctrl.MinSize = (300, -1) + diag.Fit() + textctrl.SetFocus() + textctrl.SetInsertionPointEnd() + + result = diag.ShowModal() == wx.ID_OK + + if t is None: + t = int if all([s.isdigit() for s in textctrl.Value.split(',')]) else str + + return result, [t(s.strip()) for s in textctrl.Value.split(',')] if len(textctrl.Value) else [] + +try: + import wx.gizmos as gizmos +except ImportError: + def edit_string_list(parent=None, obj_list=['one', 'two', 'three'], title="Editing List"): + log.critical('no wx.gizmos') + return edit_list(parent, obj_list, title) +else: + def edit_string_list(parent=None, obj_list=['one', 'two', 'three'], title="Editing List"): + diag = OKCancelDialog(wx.GetTopLevelParent(parent), title=title) + t = type(obj_list[0]) + elb = gizmos.EditableListBox(diag, -1, title) + elb.SetStrings([unicode(elem) for elem in obj_list]) + diag.set_component(elb) + return diag.ShowModal() == wx.ID_OK, [t(s) for s in elb.GetStrings()] + +from wx import Color, ColourDatabase, NamedColor +from binascii import unhexlify +from types import NoneType + +def get_wxColor(c): + if isinstance(c, (NoneType, Color)): + return c + + elif isinstance(c, basestring): + if c[0:2].lower() == color_prefix.lower(): + # a hex string like "0xabcdef" + return Color(*(struct.unpack("BBB", unhexlify(c[2:8])) + (255,))) + + elif ColourDatabase().Find(c).Ok(): + # a color name + return NamedColor(c) + + else: + try: c = int(c) + except ValueError: pass + + if isinstance(c, int): + # an integer + return Color((c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff, (c >> 24) or 255) + + raise ValueError('error: %r is not a valid color' % c) + +colorfor = get_wxColor + +LOCAL_SETTINGS_FILE = 'digsbylocal.ini' + +class MyConfigParser(ConfigParser): + def save(self): + import util + with util.traceguard: + parent = local_settings_path().parent + if not parent.isdir(): + parent.makedirs() + + lsp = local_settings_path() + try: + with open(lsp, 'w') as f: + self.write(f) + except Exception, e: + log.error('Error saving file %r. Error was: %r', lsp, e) + + def iteritems(self, section): + return ((k, self.value_transform(v)) for k, v in ConfigParser.items(self, section)) + + def items(self, section): + return list(self.iteritems()) + + def _interpolate(self, section, option, rawval, vars): + try: + value = ConfigParser._interpolate(self, section, option, rawval, vars) + except TypeError: + value = rawval + return value + + def value_transform(self, v): + import util + return {'none': None, + 'true': True, + 'false': False}.get(util.try_this(lambda: v.lower(), None), v) + +def local_settings_path(): + import stdpaths + return stdpaths.userlocaldata / LOCAL_SETTINGS_FILE + +_global_ini_parser = None + +def local_settings(): + global _global_ini_parser + + if _global_ini_parser is None: + _global_ini_parser = MyConfigParser() + lsp = local_settings_path() + try: + _global_ini_parser.read(lsp) + except Exception, e: + log.error('There was an error loading file %r. The error was %r.', lsp, e) + + return _global_ini_parser + +def getDisplayHashString(): + ''' + Returns a unique string for the current monitor/resolution configuration. + + Used below in save/loadWindowPos. + + The rationale is that using things like Remote Desktop can result in the + window remembering a location that won't work on a differently sized + display. This way you only position the window once on each display + configuration and be done with it. + ''' + + return '{%s}' % ', '.join('<(%s, %s): %sx%s>' % tuple(m.Geometry) for m in Monitor.All()) + +def saveWindowPos(win, uniqueId=""): + ''' + Saves a window's position to the config file. + ''' + + cfg = local_settings() + section = windowId(win.Name, uniqueId) + if not cfg.has_section(section): + cfg.add_section(section) + + if wxMSW: + placement = GetWindowPlacement(win) + + # on win7, if a window is Aero Snapped, GetWindowPlacement will return + # it's "unsnapped" size. we want to save the size of the window as it + # is now, though--so grab the size from win.Rect and use that. + if cgui.isWin7OrHigher() and not win.IsMaximized() and not win.IsIconized(): + placement_set_size(placement, win.Rect.Size) + + cfg.set(section, 'placement', json.dumps(placement)) + else: + rect = GetNormalRect(win) + sz, p = rect.GetSize(), rect.GetPosition() + + for k, v in [("x", p.x), + ("y", p.y), + ("w", sz.width), + ("h", sz.height), + ('maximized', win.IsMaximized())]: + cfg.set(section, k, str(v)) + + cfg.save() + +defSizes = { + 'Buddy List': (280, 600), + 'IM Window': (420, 330), +} + +def windowId(windowName, uniqueId): + from common import profile + + username = getattr(profile, 'username', None) + if not username: + username = getattr(wx.FindWindowByName('Digsby Login Window'), 'username', '_') + return ' '.join([windowName, uniqueId, username, getDisplayHashString()]) + +def placement_set_size(placement, size): + np = placement['rcNormalPosition'] + right = np[0] + size.width + bottom = np[1] + size.height + placement['rcNormalPosition'] = [np[0], np[1], right, bottom] + +def preLoadWindowPos(windowName, uniqueId="", position_only = False, defaultPos = None, defaultSize = None): + # save based on classname, and any unique identifier that is specified + section = windowId(windowName, uniqueId) + + if defaultPos is not None: + doCenter = defaultPos == 'center' + hasDefPos = not doCenter + else: + hasDefPos = False + doCenter = False + + + size = defaultSize if defaultSize is not None else wx.DefaultSize#(450, 400) + pos = defaultPos if hasDefPos else wx.DefaultPosition + style = 0 + + try: + cfg = local_settings() + hassection = cfg.has_section(section) + except Exception: + print_exc() + hassection = False + + placement = None + if wxMSW: + if hassection: + import util + with util.traceguard: + placement = json.loads(cfg.get(section, 'placement')) + + if position_only: + placement_set_size(placement, size) + + if hassection and not position_only: + try: + size = Size(cfg.getint(section, "w"), cfg.getint(section, "h")) + except Exception: + pass + #TODO: this isn't expected to work anymore with IM windows, needs to + #be removed once everything else is moved to use SetPalcement + #print_exc() + + if doCenter: + mon = Monitor.GetFromRect(wx.RectPS(wx.Point(0, 0), size)) #@UndefinedVariable + pos = wx.Point(*mon.ClientArea.CenterPoint(size)) + + if hassection: + try: + pos = Point(cfg.getint(section, "x"), cfg.getint(section, "y")) + except Exception: + pass + #TODO: this isn't expected to work anymore with IM windows, needs to + #be removed once everything else is moved to use SetPalcement + #print_exc() + + import util + max = util.try_this(lambda: cfg.getboolean(section, "maximized"), False) if hassection else False + + if max: + style |= wx.MAXIMIZE + + return dict(style = style, size = size, pos = pos), placement + +def loadWindowPos(win, uniqueId="", position_only = False, defaultPos = None, defaultSize = None): + ''' + Loads a window's position from the default config file. + ''' + + wininfo, placement = preLoadWindowPos(win.Name, uniqueId, position_only, defaultPos, defaultSize or win.Size) + + if placement is not None: + SetWindowPlacement(win, placement) + else: + if not position_only: + win.SetRect(wx.RectPS(wininfo['pos'], wininfo['size'])) + else: + win.Position = wininfo['pos'] +# if wininfo['style'] & wx.MAXIMIZE: +# win.Maximize() + + win.EnsureInScreen() + +def persist_window_pos(frame, close_method=None, unique_id="", position_only = False, defaultPos = None, defaultSize = None, nostack = False): + ''' + To make a frame remember where it was, call this function on it in its + constructor. + ''' + def _persist_close(e): + saveWindowPos(frame, unique_id) + close_method(e) if close_method is not None else e.Skip(True) + + frame.Bind(wx.EVT_CLOSE, _persist_close) + loadWindowPos(frame, unique_id, position_only, defaultPos = defaultPos, defaultSize = defaultSize) + if nostack: + frame.EnsureNotStacked() + +def TransparentBitmap(size): + w, h = max(size[0], 1), max(size[1], 1) + return wx.TransparentBitmap(w, h) + +def toscreen(bmap, x, y): + wx.ScreenDC().DrawBitmap(bmap, x, y) + +def bbind(window, **evts): + ''' + Shortcut for binding wxEvents. + + Instead of: + + self.Bind(wx.EVT_PAINT, self.on_paint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.on_paint_background) + self.Bind(wx.EVT_SET_FOCUS, self.on_focus) + + Use this: + + self.BBind(PAINT = self.on_paint, + ERASE_BACKGROUND = self.on_paint_background, + SET_FOCUS, self.on_focus) + ''' + + bind = window.Bind + for k, v in evts.iteritems(): + bind(getattr(wx, 'EVT_' + k), v) + +wx.WindowClass.BBind = bbind + +def EnsureInScreen(win, mon=None, client_area=True): + mon = Monitor.GetFromWindow(win) + if mon: + rect = mon.ClientArea if client_area else mon.Geometry + win.SetRect(rect.Clamp(win.Rect)) + else: + win.CentreOnScreen() + +wx.WindowClass.EnsureInScreen = EnsureInScreen + +def FitInScreen(win, mon=None): + ''' + Like wx.Window.Fit(), except also ensures the window is within the client + rectangle of its current Display. + ''' + win.Fit() + EnsureInScreen(win, mon) + +wx.WindowClass.FitInScreen = FitInScreen + +def build_button_sizer(save, cancel = None, border=5): + 'Builds a standard platform specific button sizer.' + # Only because wxStdDialogButtonSizer.Realize crashed the Mac + sz = wx.BoxSizer(wx.HORIZONTAL) + sz.AddStretchSpacer(1) + + addbutton = lambda b: sz.Add(b, 0, (wx.ALL & ~wx.TOP) | wx.ALIGN_RIGHT, border) + + mac = 'wxMac' in wx.PlatformInfo + import util.primitives.funcs as funcs + if save and cancel: + funcs.do(addbutton(b) for b in ([cancel, save] if mac else [save, cancel])) + else: + addbutton(save) + return sz + +_tinyoffsets = ((-1, 0), (1, 0), (1, -1), (1, 1), (-1, -1), (-1, 1)) + +def draw_tiny_text(image, text, outline = 'black', fill = 'white'): + image = getattr(image, 'PIL', image).copy() + + # Load the pixel font. + font = load_tiny_font() + if font is None: + return image + + drawtext = ImageDraw.Draw(image).text + size = font.getsize(text) + + x, y = image.size[0] - size[0], image.size[1] - size[1] + + if outline: + # shift the color one pixel in several directions to create an outline + for a, b in _tinyoffsets: + drawtext((x+a, y+b), text, fill = outline, font = font) + + drawtext((x, y), text, fill = fill, font = font) + + return image + +_tinyfont = None + +def load_tiny_font(): + global _tinyfont + if _tinyfont == -1: + # There was an error loading the pixel font before. + return None + elif _tinyfont is None: + try: + import locale + from gui import skin + _tinyfont = ImageFont.truetype((skin.resourcedir() / 'slkscr.ttf').encode(locale.getpreferredencoding()), 9) + except Exception: + print_exc() + _tinyfont = -1 + return None + + return _tinyfont + +#@lru_cache(10) +def add_image_text(wxbitmap, text): + return draw_tiny_text(wxbitmap, unicode(text)).WXB + +def rect_with_negatives(rect, boundary): + ''' + Allows rectangles specified in negative coordinates within some boundary. + + Parameters: + rect: a sequence of four numbers specifying a rectangle + boundary: a sequence of two numbers, a width and height representing a BoundaryError + + Returns a sequence of four numbers, a new rectangle which takes any negative + numbers from the original rectangle and adds them to the boundary rectangle. + ''' + + if not len(rect) == 4 or not len(boundary) == 2: raise TypeError('parameters are (x,y,w,h) and (w,h)') + ret = list(rect) + for i in xrange(len(ret)): + if ret[i] < 0: + ret[i] += boundary[i%2] + + return ret + +class Frozen(object): + ''' + "with" statement context manager for freezing wx.Window GUI elements + ''' + + def __init__(self, win): + self.win = win + + def __enter__(self): + self.win.Freeze() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.win.Thaw() + del self.win + +wx.WindowClass.Frozen = lambda win: Frozen(win) +wx.WindowClass.FrozenQuick = lambda win: Frozen(win) + +from wx import IconFromBitmap +GetMetric = wx.SystemSettings.GetMetric + +_win7bigicon = None + +def SetFrameIcon(frame, bitmap): + "Given any Bitmap/Image/PILImage, sets this frame's icon." + + small_w, small_h = GetMetric(wx.SYS_SMALLICON_X), GetMetric(wx.SYS_SMALLICON_Y) + big_w, big_h = GetMetric(wx.SYS_ICON_X), GetMetric(wx.SYS_ICON_Y) + + if small_w == -1: + small_w = 16 + if small_h == -1: + small_h = 16 + + if big_w == -1: + big_w = 32 + if big_h == -1: + big_h = 32 + + if isinstance(bitmap, wx.IconBundle): + bundle = bitmap + elif isinstance(bitmap, list): + bundle = wx.IconBundle() + for b in bitmap: + if isinstance(b, wx.Icon): + bundle.AddIcon(b) + else: + bundle.AddIcon(IconFromBitmap(b.PIL.ResizedSmaller(big_w).ResizeCanvas(big_w, big_h).WXB)) + else: + small_bitmap = bitmap.PIL.ResizedSmaller(small_w).ResizeCanvas(small_w, small_h).WXB + + if cgui.isWin7OrHigher(): + # On Windows 7, always use the Digsby icon for the 32x32 version. + # this is so that our application icon in the taskbar always shows as the Digsby logo. + global _win7bigicon + if _win7bigicon is None: + from gui import skin + _win7bigicon = skin.get('AppDefaults.TaskBarIcon').PIL.ResizedSmaller(big_w).ResizeCanvas(big_w, big_h).WXB + large_bitmap = _win7bigicon + else: + large_bitmap = bitmap.PIL.ResizedSmaller(big_w).ResizeCanvas(big_w, big_h).WXB + + bundle = wx.IconBundle() + bundle.AddIcon(IconFromBitmap(large_bitmap)) + bundle.AddIcon(IconFromBitmap(small_bitmap)) + + frame.SetIcons(bundle) + +wx.TopLevelWindow.SetFrameIcon = SetFrameIcon + + +def snap_pref(win): + 'Makes a window obey the windows.sticky preference. (The window snaps to edges.)' + + from common import profile + + #needs to import snap to patch SetSnap into TopLevelWindow + import gui.snap + + if profile.prefs: + linked = profile.prefs.link('windows.sticky', win.SetSnap) + + def on_destroy(e): + e.Skip() + if e.EventObject is win: + linked.unlink() + + win.Bind(wx.EVT_WINDOW_DESTROY, on_destroy) + else: + raise Exception('profile.prefs is empty -- cannot observe') + +def setuplogging(logfilename = 'digsby-testapp.log', level=None): + import logging + + if level is None: + level = logging.DEBUG + + # Setup log so it's visible + logging.basicConfig(level=level, + filename=logfilename, + filemode='w') + + import logextensions + console = logextensions.ColorStreamHandler() + + from main import ConsoleFormatter + console.setFormatter(ConsoleFormatter()) + + logging.getLogger().addHandler(console) + +def OverflowShow(self, switch=True, genWidth = True): + self.shouldshow=switch + wx.Window.Show(self, switch) + if genWidth: self.Parent.GenWidthRestriction(True) + + +def EnsureNotStacked(f, clz = None, offset = (20, 20)): + ''' + Positions a top level window so that it is not directly stacked on + top of another for which isinstance(window, clz) + ''' + + if clz is None: + clz = f.__class__ + + found = True + top = GetTopLevelWindows() + pos = f.Position + + while found: + found = False + + for frame in top: + if frame is not f and isinstance(frame, clz) and frame.IsShown() and frame.Position == pos: + pos = pos + offset + found = True + + f.Position = pos + +wx.TopLevelWindow.EnsureNotStacked = EnsureNotStacked + + + +def AddInOrder(sizer, *order, **windows): + if sizer and windows: + for key in list(order): + if key in windows: + try: + sizer.Add(*windows[key]) + except Exception: + print >> sys.stderr, 'sizer', sizer + print >> sys.stderr, 'order', order + print >> sys.stderr, 'windows', windows + print >> sys.stderr, 'key', key + raise + +def GetStartupDir(): + import stdpaths + return stdpaths.userstartup + +def ToScreen(rect, ctrl): + r = wx.Rect(*rect) + r.x, r.y = ctrl.ClientToScreen(r.TopLeft) + return r + +wx.Rect.ToScreen = ToScreen + +from wx import TOP, BOTTOM, LEFT, RIGHT + +def alignment_to_string(a): + if a & TOP: s = 'upper' + elif a & BOTTOM: s = 'lower' + else: s = 'middle' + + if a & LEFT: s += 'left' + elif a & RIGHT: s += 'right' + else: s += 'center' + + return s + +def prnt(*a): + """ + Strings the arguments and pieces them together, separated with spaces + The string is printed inbetween lines of 80 '=' + """ + print + print '=' * 80 + print ' '.join(str(i) for i in a) + print '=' * 80 + +if wxMSW: + # use custom rich edit alignment flags -- wxLayoutDirection doesn't work + # with rich text controls + PFA_LEFT = 1 + PFA_RIGHT = 2 + PFA_CENTER = 3 + PFA_JUSTIFY = 4 + + + def set_rich_layoutdirection(ctrl, align): + align = {wx.Layout_RightToLeft: PFA_RIGHT, + wx.Layout_LeftToRight: PFA_LEFT}[align] + + if cgui.SetRichEditParagraphAlignment(ctrl, align): + ctrl.Refresh() + + def add_rtl_checkbox(ctrl, menu): + ''' + Adds a checkbox when the menu is over the main input area for toggling + a right to left reading order. + + ctrl is the control currently under the mouse + menu is the menu we're updating + ''' + # If we're over the main input area, add a checkbox for toggling RTL state. + item = menu.AddCheckItem(_('Right To Left'), callback = lambda: toggle_layout_direction(ctrl)) + + # The item is checked if RTL mode is on. + item.Check(cgui.GetRichEditParagraphAlignment(ctrl) == PFA_RIGHT) + + def toggle_layout_direction(tc): + 'Toggles the layout direction of a control between right-to-left and left-to-right.' + +# alignment = PFA_RIGHT if cgui.GetRichEditParagraphAlignment(tc) == PFA_LEFT else PFA_LEFT +# +# if not cgui.SetRichEditParagraphAlignment(tc, alignment): +# log.warning('SetRichEditParagraphAlignment returned False') +# else: +# tc.Refresh() + + tc.SetRTL(not tc.GetRTL()) + +else: + def add_rtl_checkbox(ctrl, menu): + ''' + Adds a checkbox when the menu is over the main input area for toggling + a right to left reading order. + + ctrl is the control currently under the mouse + menu is the menu we're updating + ''' + # If we're over the main input area, add a checkbox for toggling RTL state. + item = menu.AddCheckItem(_('Right To Left'), callback = lambda: toggle_layout_direction(ctrl)) + + # The item is checked if RTL mode is on. + item.Check(ctrl.GetRTL()) #LayoutDirection == wx.Layout_RightToLeft) + + def toggle_layout_direction(tc): + 'Toggles the layout direction of a control between right-to-left and left-to-right.' + + if tc: + tc.LayoutDirection = wx.Layout_RightToLeft if tc.LayoutDirection == wx.Layout_LeftToRight else wx.Layout_LeftToRight + tc.Refresh() + +def textctrl_hittest(txt, pos=None): + if pos is None: + pos = wx.GetMousePosition() + + hit, col, row = txt.HitTest(txt.ScreenToClient(pos)) + return txt.XYToPosition(col, row) + +class Unshortener(object): + def __init__(self, cb=None): + self.urls = {} + self.cb = cb + + def get_long_url(self, url): + try: + return self.urls[url] + except KeyError: + self.urls[url] = None + + def cb(longurl): + self.urls[url] = longurl + if self.cb: + self.cb() + + import util.net + util.net.unshorten_url(url, cb) + +def add_shortened_url_tooltips(txt): + ''' + binds a mouse motion handler that detects when the mouse hovers over shortened urls, + and shows the long version + ''' + def update_url_tooltip(e=None): + if e is not None: + e.Skip() + + val = txt.Value + import util.net + i = textctrl_hittest(txt) + tooltip = None + for link, span in util.net.LinkAccumulator(val): + if i < span[0] or i >= span[1]: + continue + if util.net.is_short_url(link): + try: + shortener = txt._url_unshortener + except AttributeError: + shortener = txt._url_unshortener = Unshortener(lambda: wx.CallAfter(update_url_tooltip)) + + tooltip = shortener.get_long_url(link) + break + + update_tooltip(txt, tooltip) + + txt.Bind(wx.EVT_MOTION, update_url_tooltip) + +def maybe_add_shorten_link(txt, menu): + import util.net + + val = txt.Value + i = textctrl_hittest(txt) # TODO: what if the menu was spawned via the keyboard? + + for link, span in util.net.LinkAccumulator(val): + if i < span[0] or i >= span[1]: + continue + + def repl(s): + i, j = span + txt.Value = ''.join((val[:i], s, val[j:])) + + if util.net.is_short_url(link): + longurl = util.net.long_url_from_cache(link) + if longurl is not None: + menu.AddItem(_('Use Long URL'), callback=lambda: repl(longurl)) + menu.AddSep() + continue + + @util.threaded + def bgthread(): + url = util.net.get_short_url(link) + if url and val == txt.Value: + wx.CallAfter(lambda: repl(url)) + + menu.AddItem(_('Shorten URL'), callback = bgthread) + menu.AddSep() + break + +def show_sizers(win, stream = None): + if isinstance(win, wx.WindowClass): + sizer = win.Sizer + if sizer is None: + raise ValueError('%r has no sizer' % win) + elif isinstance(win, wx.Sizer): + sizer = win + else: + raise TypeError('must pass a window or sizer, you gave %r' % win) + + if stream is None: + stream = sys.stdout + + _print_sizer(sizer, stream) + +def _shownstr(o): + return '(hidden)' if not o.IsShown() else '' + +def _print_sizer(sizer, stream, indent = '', sizer_shown_str = ''): + assert isinstance(sizer, wx.Sizer) + stream.write(''.join([indent, repr(sizer), ' ', sizer_shown_str, '\n'])) + indent = ' ' + indent + for child in sizer.Children: + assert isinstance(child, wx.SizerItem) + if child.Sizer is not None: + _print_sizer(child.Sizer, stream, ' ' + indent, sizer_shown_str = _shownstr(child)) + else: + stream.write(''.join([indent, + repr(child.Window if child.Window is not None else child.Spacer), + ' ', + _shownstr(child), + '\n'])) + +_delays = defaultdict(lambda: (0, None)) + +def calllimit(secs=.5): + ''' + Assures a function will only be called only once every "secs" seconds. + + If a new call comes in while a "delay" is occurring, the function is + guaranteed to be called after the delay is over. + ''' + def inner(func): # argument to the decorator: a function + @functools.wraps(func) + def wrapper(*args, **kwargs): # arguments to the original function + + now = time_clock() + key = (func, getattr(func, 'im_self', None)) + lastcalled, caller = _delays[key] + diff = now - lastcalled + + if diff > secs: + # CALL NOW + if isinstance(caller, wx.CallLater): caller.Stop() + _delays[key] = (now, None) + return func(*args, **kwargs) + else: + # CALL LATER + if caller == 'pending': + # the wx.CallAfter hasn't completed yet. + pass + elif not caller: + callin_ms = (lastcalled + secs - now) * 1000 + def later(): + def muchlater(): + _delays[key] = (time_clock(), None) + func(*args, **kwargs) + + _delays[key] = (lastcalled, + wx.CallLater(max(1, callin_ms), muchlater)) + _delays[key] = (lastcalled, 'pending') + wx.CallAfter(later) + + return func + return wrapper + return inner + +# TODO: move me to gui.native +GetDoubleClickTime = lambda: 600 +if wxMSW: + try: + from ctypes import windll + GetDoubleClickTime = windll.user32.GetDoubleClickTime + except Exception: + print_exc() + +def std_textctrl_menu(txt, menu): + menu.AddItem(_("Undo"), callback = txt.Undo, id=wx.ID_UNDO) + menu.Enable(wx.ID_UNDO, txt.CanUndo()) + + menu.AddItem(_("Redo"), callback = txt.Redo, id=wx.ID_REDO) + menu.Enable(wx.ID_REDO, txt.CanRedo()) + + menu.AppendSeparator() + + menu.AddItem(_("Cut"), callback = txt.Cut, id=wx.ID_CUT) + menu.Enable(wx.ID_CUT, txt.CanCut()) + + menu.AddItem(_("Copy"), callback = txt.Copy, id=wx.ID_COPY) + menu.Enable(wx.ID_COPY, txt.CanCopy()) + + menu.AddItem(_("Paste"), callback = txt.Paste, id=wx.ID_PASTE) + menu.Enable(wx.ID_PASTE, txt.CanPaste()) + + menu.AppendSeparator() + menu.AddItem(_("Select All"), callback = lambda: txt.SetSelection(0 , txt.GetLastPosition()), id=wx.ID_SELECTALL) + +IMAGE_WILDCARD = ('Image files (*.gif;*.jpeg;*.jpg;*.png)|*.gif;*.jpeg;*.jpg;*.png|' + 'All files (*.*)|*.*') + +def pick_image_file(parent): + diag = wx.FileDialog(parent, _('Select an image file'), + wildcard = IMAGE_WILDCARD) + filename = None + + try: + status = diag.ShowModal() + if status == wx.ID_OK: + filename = diag.Path + finally: + diag.Destroy() + + return filename + +def paint_outline(dc, control, color=None, border=1): + if color is None: + color = wx.Color(213, 213, 213) + + dc.Brush = wx.TRANSPARENT_BRUSH + dc.Pen = wx.Pen(color) + r = control.Rect + r.Inflate(border, border) + dc.DrawRectangleRect(r) + +def maybe_callable(val): + return val if not callable(val) else val() + +def insert_text(textctrl, text): + ip = textctrl.InsertionPoint + if ip != 0 and textctrl.Value[ip-1] and textctrl.Value[ip-1] != ' ': + textctrl.WriteText(' ') + + textctrl.WriteText(text + ' ') + +def insert_shortened_url(textctrl, url, ondone=None, timeoutms=5000): + import util.net + + textctrl.Freeze() + textctrl.Enable(False) + + class C(object): pass + c = C() + + c._finished = False + def finish(shorturl=None): + if c._finished: return + c._finished = True + insert_text(textctrl, shorturl or url) + textctrl.Thaw() + textctrl.Enable() + textctrl.SetFocus() + if ondone is not None: + ondone(shorturl) + + def get(): + short_url = None + with util.traceguard: + short_url = util.net.get_short_url(url) + if short_url is not None and len(short_url) >= len(url): + short_url = url + wx.CallAfter(lambda: finish(short_url)) + + util.threaded(get)() + if timeoutms is not None: + wx.CallLater(timeoutms, finish) + + def cancel(): finish(None) + return cancel + +def bind_special_paste(textctrl, shorten_urls=True, onbitmap=None, onfilename=None, + onshorten=None, onshorten_done=None): + import gui.clipboard as clipboard + + def on_text_paste(e): + if e.EventObject is not textctrl: return e.Skip() + + if onfilename is not None: + files = clipboard.get_files() or [] + for file in files: + if file.isfile(): + if onfilename(file) is False: + e.Skip() + return + + if maybe_callable(shorten_urls): + text = clipboard.get_text() + if text is not None: + import util.net + if util.isurl(text) and not util.net.is_short_url(text): + cancellable = insert_shortened_url(textctrl, text, ondone=onshorten_done) + if onshorten is not None: + onshorten(cancellable) + else: + e.Skip() + return + + if onbitmap is not None: + bitmap = clipboard.get_bitmap() + if bitmap is not None: + import stdpaths, time + filename = stdpaths.temp / 'digsby.clipboard.%s.png' % time.time() + bitmap.SaveFile(filename, wx.BITMAP_TYPE_PNG) + if onbitmap(filename, bitmap) is False: + e.Skip() + return + + e.Skip() + + textctrl.Bind(wx.EVT_TEXT_PASTE, on_text_paste) + +def HelpLink(parent, url): + sz = wx.BoxSizer(wx.HORIZONTAL) + txt_left = wx.StaticText(parent, label = u' [') + link = wx.HyperlinkCtrl(parent, -1, label = u' ? ', url = url) + txt_right = wx.StaticText(parent, label = u']') + + sz.Add(txt_left, flag = wx.ALIGN_CENTER_VERTICAL) + sz.Add(link, flag = wx.ALIGN_CENTER_VERTICAL) + sz.Add(txt_right, flag = wx.ALIGN_CENTER_VERTICAL) + + return sz + +def update_tooltip(ctrl, tip): + '''only updates a control's tooltip if it's different.''' + + if tip is None: + if ctrl.ToolTip is not None: + ctrl.SetToolTip(None) + else: + if ctrl.ToolTip is None: + ctrl.SetToolTipString(tip) + elif ctrl.ToolTip.Tip != tip: + ctrl.ToolTip.SetTip(tip) + +class tempdc(object): + def __init__(self, width, height, transparent=True): + self.width = width + self.height = height + self.transparent = transparent + + def __enter__(self): + self.bitmap = wx.TransparentBitmap(self.width, self.height) + self.dc = wx.MemoryDC(self.bitmap) + return self.dc, self.bitmap + + def __exit__(self, exc, val, tb): + self.dc.SelectObject(wx.NullBitmap) + diff --git a/digsby/src/gui/tracebackdialog.py b/digsby/src/gui/tracebackdialog.py new file mode 100644 index 0000000..4b56e1c --- /dev/null +++ b/digsby/src/gui/tracebackdialog.py @@ -0,0 +1,28 @@ +import wx +import wx.lib.sized_controls as sc + +ID_BUG_REPORT = wx.NewId() + +class ErrorDialog(sc.SizedFrame): + def __init__(self): + sc.SizedFrame.__init__(self, None, -1, "Traceback Viewer", style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + pane = self.GetContentsPane() + + self.exc_ctrl = wx.TextCtrl(pane, -1, "", size=(300, 300), style=wx.TE_MULTILINE) + self.exc_ctrl.SetSizerProps(expand=True, proportion=1) + + self.bug_report = wx.Button(pane, ID_BUG_REPORT, _('Send Bug Report')) + self.bug_report.SetSizerProps(expand=False, proportion=0) + self.Bind(wx.EVT_BUTTON, self.on_bug_report, id=ID_BUG_REPORT) + + self.Fit() + self.MinSize = self.Size + + def on_bug_report(self, e): + from util.diagnostic import do_diagnostic + do_diagnostic() + + def AppendText(self, txt): + return self.exc_ctrl.AppendText(txt) + + diff --git a/digsby/src/gui/trayicons.py b/digsby/src/gui/trayicons.py new file mode 100644 index 0000000..635ff0f --- /dev/null +++ b/digsby/src/gui/trayicons.py @@ -0,0 +1,301 @@ +''' +the main digsby tray icon +''' + +import config +import sys +import wx +import hooks +import actionIDs +from common import profile, pref +from config import platformName +from logging import getLogger; log = getLogger('trayicons') +from gettext import ngettext +from gui import statuscombo +from gui.toolbox import draw_tiny_text, to_icon +from gui.taskbar import AnimatedTaskBarIcon +from gui.uberwidgets.umenu import UMenu +from gui.windowfx import vista + +TRAY_SHOW_STATUS_PREF = 'trayicons.digsby.show_status_orb' + +MAIN_ICON = 0 +MESSAGE_ICON = 1 + +TASKBAR_REFRESH_MS = 5 * 60 * 1000 + +UNREADPREF = 'trayicons.digsby.unread_messages.' + +class BuddyListEventHandler(object): + def __init__(self): + bind = wx.GetApp().Bind + + bind(wx.EVT_MENU, self.OnShowHideBuddylist, id=actionIDs.ShowBuddyList) + bind(wx.EVT_MENU, self.OnMenubarPref, id=actionIDs.ShowMenuBar) + bind(wx.EVT_MENU, self.OnStatusCustom, id=actionIDs.SetStatusCustom) + + def SetupStatusHandlers(self): + for id in statuscombo.status_dict.values(): + wx.GetApp().Bind(wx.EVT_MENU, self.OnStatus, id=id) + + def OnMenubarPref(self, event): + # called when the user checks the item + prefs = profile.prefs + thepref = 'buddylist.show_menubar' + prefs[thepref] = not prefs[thepref] + + def OnStatus(self, event): + status_dict = statuscombo.status_dict + for status in status_dict: + if status_dict[status] == event.GetId(): + import hooks; hooks.notify('digsby.statistics.ui.select_status') + profile.set_status(status) + + def OnStatusCustom(self, event): + statuscombo.edit_custom_status(None) + +class BuddyListTaskBarIcon(AnimatedTaskBarIcon, BuddyListEventHandler): + ''' + the main digsby tray icon + ''' + + def __init__(self, *args, **kwargs): + AnimatedTaskBarIcon.__init__(self, id=hash('digsby_tray_icon')) + BuddyListEventHandler.__init__(self) + + hooks.register('digsby.im.mystatuschange.async', lambda status: wx.CallAfter(self.on_status_change, status)) + hooks.register('digsby.im.message_hidden', self.on_hidden_message) + + # regenerate icon when the pref changes + profile.prefs.link(TRAY_SHOW_STATUS_PREF, self.on_show_status_pref, callnow=True) + + self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnShowHideBuddylist) + self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.OnLeftClick) + + if config.platform == 'win': + # refresh the tray icon image every couple of minutes to keep it + # out of Window's inactive tray icon list + self.refresh_timer = wx.PyTimer(self.Refresh) + self.refresh_timer.Start(TASKBAR_REFRESH_MS, False) + + def on_hidden_message(self, hidden_conversations): + assert wx.IsMainThread() + self.set_hidden_messages(len(hidden_conversations)) + + def OnLeftClick(self, e): + e.Skip() + + # left click shows one hidden message, if there are any + import gui.imwin.imhub as imhub + if imhub.hidden_count(): + imhub.pop_all_hidden() + self.set_hidden_messages(imhub.hidden_count()) + + def OnShowHideBuddylist(self, e): + buddy_frame = wx.GetApp().buddy_frame + + if not buddy_frame.Docked: + return wx.GetApp().buddy_frame.toggle_show_hide() + + if buddy_frame.AutoHidden: + buddy_frame.docker.ComeBack() + buddy_frame.Raise() + else: + pass # do nothing if just docked + + def set_hidden_messages(self, n): + ''' + n can be + + -1: show bubble with no count, and no flashing + 0: show just the digsby head + >0: flash between digsby head and bubble with count, which is n + ''' + + status = profile.status + digsby_head = self.status_tray_icon(status) + + if pref(UNREADPREF + 'flash_only_count', False): + from gui import skin + message_icon = skin.get('AppDefaults.TaskbarIcon') + else: + message_icon = None + + if n == -1: + icons = [generate_hidden_message_icon(None)] + elif n == 0: + icons = [digsby_head] + else: + count = n if pref(UNREADPREF + 'show_count', True) else None + unread_icon = generate_hidden_message_icon(count, message_icon) + icons = [digsby_head, unread_icon] + + intervalms = pref(UNREADPREF + 'flash_interval_ms', default=1000) + + self.icons = [to_icon(i) for i in icons] + self.delays = [intervalms] * len(self.icons) + self.UpdateAnimation(status_tooltip(status)) + + def on_show_status_pref(self, val): + self.on_status_change(profile.status) + + def on_status_change(self, status): + assert wx.IsMainThread() + + icon = self.status_tray_icon(status) + tooltip = status_tooltip(status) + + self.icons[MAIN_ICON] = (icon) + self.UpdateAnimation(tooltip) + + def status_tray_icon(self, status): + if not self.show_status_orb: + status = None + elif status.available and not pref('trayicons.digsby.show_available_orb', default=False): + # by default, don't show an available orb. + status = None + + return to_icon(generate_tray_icon(status, self._IconSize)) + + @property + def show_status_orb(self): + return pref(TRAY_SHOW_STATUS_PREF) + + def CreatePopupMenu(self): + buddy_frame = wx.FindWindowByName('Buddy List') + + # TODO: fix skinned menus so that they don't need to be told they are in the system tray! + umenu = UMenu(buddy_frame, onshow = None, windowless = True) + + add_hidden_convo_menu_items(umenu) + + statuscombo.add_global_to_menu(umenu, _('Set &Global Status...')) + statmenu = UMenu(buddy_frame, onshow = None)#statuscombo.create_status_menu(add_custom = True) + statuscombo.status_menu(statmenu, add_custom=True, add_promote = True) + umenu.AddSubMenu(statmenu, _('Set IM &Status')) + + umenu.AddSep() + + show_hide = umenu.AddItem(_('Show &Buddy List'), id=actionIDs.ShowBuddyList) + if buddy_frame: + docker = getattr(buddy_frame, 'docker', None) + + if docker: + disable_hide = docker.Enabled and docker.docked and docker.AutoHide + show_hide.label = _('Hide &Buddy List') if buddy_frame.Shown else _('Show &Buddy List') + show_hide.Enable(not disable_hide) + + # The menubar is the "app" menubar on Mac and thus cannot be hidden. + if platformName != 'mac': + umenu.AddPrefCheck('buddylist.show_menubar', _('Show Menu Bar')) + + umenu.AddItem(_('&Preferences...'), id=wx.ID_PREFERENCES) + + # Mac gets this menu item standard on the Dock, don't duplicate it. + if platformName != 'mac': + umenu.AddSep() + umenu.AddItem(_('E&xit Digsby'), id = wx.ID_EXIT) + + # since status items are created dynamically, we must bind to them on the fly. + self.SetupStatusHandlers() + + return umenu + +def status_tooltip(status_obj): + 'Returns the tooltip used in the main Digsby tray icon.' + + dev = ' DEV' if getattr(sys, 'DEV', False) else u' ' + + if status_obj is None: + status = u'' + else: + status = u'- %s' % status_obj.nice_status + + tooltip = _('Digsby') + tooltip += dev + status + + # add tooltip lines for each hidden message + from gui.imwin import imhub + if imhub.hidden_windows: + elems = [] + for contact_infokey, messages in imhub.hidden_windows.items(): + contact = messages[0].buddy + + if config.platform != 'win' or vista: + message_str = ngettext('{msgs} message', '{msgs} messages', len(messages)).format(msgs=len(messages)) + else: + # xp cuts off longer tray icon tooltips + message_str = '%d' % len(messages) + + elems.append(_('{alias} ({service}) - {message}').format( + alias=contact.alias, service=contact.service, message=message_str)) + + tooltip += '\n\n' + '\n'.join(elems) + + return tooltip + + +def generate_tray_icon(status, size): + ''' + generates the Digsby tray icon. if status is not None, include a status + orb badge. + ''' + + from gui import skin + icon = skin.get('AppDefaults.TaskbarIcon') + if status is None: + return icon.PIL.ResizedSmaller(size) + + status_size = 8 + status_string = status.icon_status(status) if not profile.quiet else 'quiet' + status_icon = skin.get('AppDefaults.TrayStatus.Icons.' + status_string) + status_icon = status_icon.PIL.ResizedSmaller(status_size) + + from PIL import Image + resized_icon = icon.PIL.ResizedSmaller(size).ResizeCanvas(size, size) + + i = Image.new('RGBA', (size, size), (0, 0, 0, 0)) + i.paste(resized_icon, (0, 0)) + i.paste(status_icon, (size - status_size, size - status_size), status_icon) + return i + +from gui.taskbar import native_taskbar_icon_size + +def generate_hidden_message_icon(nummessages, icon = None): + from gui import skin + + if icon is None: + icon = skin.get('AppDefaults.UnreadMessageIcon') + + size = native_taskbar_icon_size() + icon = icon.PIL.ResizedSmaller(size).ResizeCanvas(size, size) + + if nummessages is not None: + icon = draw_tiny_text(icon, str(nummessages)) + + return icon + +def stop_flashing(): + wx.GetApp().tbicon.set_hidden_messages(-1) + +def add_hidden_convo_menu_items(menu): + 'populates a menu with items for hidden conversations' + + import gui.imwin.imhub as imhub + if not imhub.hidden_windows: + return + + for contact_infokey, messages in imhub.hidden_windows.items(): + contact = messages[0].buddy + if contact is None: + log.warning("Not adding hidden menu item, message object had None for .buddy: %r", messages[0]) + else: + callback = lambda c=contact_infokey: imhub.pop_any_hidden(c) + menu.AddItem(contact.alias, + callback=callback, + bitmap=contact.serviceicon) + + menu.AddItem(_('Show All'), callback=imhub.pop_all_hidden) + #menu.AddItem(_('Dismiss All'), callback=stop_flashing) + menu.AddSep() + diff --git a/digsby/src/gui/treelist.py b/digsby/src/gui/treelist.py new file mode 100644 index 0000000..00bccab --- /dev/null +++ b/digsby/src/gui/treelist.py @@ -0,0 +1,603 @@ +""" + +treelist.py + +makes + +- a + - tree + - of + - items + +into + +- a +- list +- of +- items + +""" +from __future__ import with_statement +from __future__ import division + + +app = None + +import wx +from wx import WXK_LEFT, WXK_RIGHT, WXK_DOWN, WXK_UP, Rect +from util.primitives.funcs import Delegate +from logging import getLogger; log = getLogger('treelist'); info = log.info +from gui.textutil import GetFontHeight,default_font +from contextlib import contextmanager + +# Determines equality of parents in list (group identity) +expanded_id = lambda obj: u'_'.join([type(obj).__name__, obj.name]) + +# Determines equality of objects in list (for maintaining things like selection) +idfunc = lambda obj: obj.__hash__() + +def hasChildren( obj ): + ''' + Returns True if + 1) obj has __iter__ OR + 2) obj has __len__ and __getitem__ + ''' + try: iter(obj) + except: return False + else: return True + +class ListMixin(object): + ''' + Mixin this class to let a list member variable be exposed as list methods + in the class object. + ''' + def __init__(self, listAttrName): + self._listAttrName = listAttrName + + # act as an iterable list + def __len__( self ): return len( self.__dict__[self._listAttrName] ) + def __iter__( self ): return self.__dict__[self._listAttrName].__iter__() + def __getitem__( self, n ): return self.__dict__[self._listAttrName][n] + +class TreeListModel(ListMixin): + ''' + Given a list of lists, provides a flattened view of that list for use in a + hierarchial list. + + Parents are included, so a list object appears before it's children. + + Example: + + >>> model = TreeListModel(["my group", ["sub", "children"], [["elem1", "elem2"], "thing"]]) + >>> print self.model[2] + + 'sub' + + ''' + + def __init__( self, root = None, collapsed = None): + self.root = root or [] + self.collapsed = set(collapsed) if collapsed is not None else set() + self.flattened_list = [] + self.listeners = [] + self.depths = {} + self.filters = [] + self.donotexpand = [] + self._expandable_cache = {} + + self.update_list() + self.expansion_state_changed = Delegate() + + # access this object (specifically, the flattened_list member) as a list + ListMixin.__init__(self, 'flattened_list') + + + def _expandable(self, eltype): + try: + return self._expandable_cache[eltype] + except KeyError: + for i in self.donotexpand: + if issubclass(eltype, i): + return self._expandable_cache.setdefault(eltype, False) + + return self._expandable_cache.setdefault(eltype, True) + + def expandable(self, el): + return self._expandable(el.__class__) + + def flatten( self, root, collapsed, depths, depth=0, filters=[], expanded_id = expanded_id ): + ''' + Flatten a list of objects. Parent objects are "included." + + Parameters: + + expanded - a hash of {repr(obj): boolean} pairs representing whether or not + parent items should be traversed. + depths - is a hash to store {obj: integer} depth values for + depth - depth to begin at (defaults to zero) + ''' + lst = [root] + + if hasChildren( root ): + for el in root: + depths[idfunc(el)] = (depth, root) + if expanded_id(el) not in collapsed and self.expandable(el): + lst.extend( self.flatten( el, collapsed, depths, depth+1, filters, expanded_id = expanded_id ) ) + else: + lst.append( el ) + + return lst + + def __repr__(self): + return '' % self.flattened_list + + def expand( self, obj ): + i = expanded_id(obj) + self.collapsed.discard(i) + self.expansion_state_changed() + self.update_list() + + def collapse( self, obj ): + self.collapsed.add(expanded_id(obj)) + self.expansion_state_changed() + self.update_list() + + def toggle_expand( self, obj ): + if obj.__class__ not in self.donotexpand: + if expanded_id(obj) not in self.collapsed: + self.collapse( obj ) + elif hasChildren( obj ): + self.expand( obj ) + + def is_expanded( self, n ): + 'Returns True if the nth element is currently expanded.' + + return expanded_id(self.flattened_list[n]) not in self.collapsed + + def parent_of(self, child): + return self.depths[idfunc(child)][1] + + def index_of(self, child): + 'Returns the index of the specified child.' + + try: + return self.indices[idfunc(child)] if idfunc(child) in self.indices else -1 + except: + return -1 + + def set_root(self, root): + self.depths = {} + self.root = root + self.update_list() + + def update_list(self): + assert wx.IsMainThread() + + self.flattened_list = self.flatten(self.root, self.collapsed, self.depths, filters = self.filters)[1:] + + # cache indices + self.indices = dict((idfunc(item), c) for c, item in enumerate(self.flattened_list)) + + for l in self.listeners: + l.list_changed() + + def remove_child(self, child): + assert child in self.flattened_list + parent = self.depths[idfunc(child)][1] + assert child in parent + parent.remove(child) + assert child not in parent + self.update_list() + + +from cgui import SkinVList as TreeListBase + +class TreeList(TreeListBase): + ''' + Hierarchial list control. + + Usage: + + frame = wx.Frame(None, -1, 'Test') + model = TreeListModel( [ ["lists", "of"], [ ["things", "go"], "here" ] ] ) + list = TreeList( frame, model ) + ''' + + def __init__(self, parent, model, id=-1, style = wx.NO_BORDER | wx.FULL_REPAINT_ON_RESIZE | wx.HSCROLL | wx.VSCROLL, + enable_hover = True, keyhandler = None): + self.renderers = {} + self.renderers_cache = {} + + self.context_menu_handlers = {} + + TreeListBase.__init__(self, parent, id, style) + + self.model = model + model.listeners.append( self ) + + measure = self.OnMeasureItem + + self.SetHeights([measure(n) for n in xrange(len(self.model))]) + + Bind = self.Bind + Bind(wx.EVT_LISTBOX_DCLICK, self.on_doubleclick) + Bind(wx.EVT_RIGHT_DOWN, self.on_right_down) + if keyhandler is not None: + Bind(wx.EVT_KEY_DOWN, keyhandler) + Bind(wx.EVT_KEY_DOWN, self.on_key_down) + + self.hoveridx = -1 + + if enable_hover: + Bind(wx.EVT_MOTION, self.on_motion) + Bind(wx.EVT_LEAVE_WINDOW, self.on_leave_window) + + # the amount to indent each group level by + self.indent = 10 + + # + # Hover Property + # + + def on_leave_window(self, e): + self.Hover = -1 + e.Skip(True) + + def on_motion(self, e): + self.Hover = self.HitTest(e.Position) + e.Skip(True) + + def toggle_expand(self, obj): + ''' + Expand or collapse a treelist item. + + If the selection is a child of a collapsing item, the selection + is moved to the collapsing item. + ''' + + i, model = self.GetSelection(), self.model + selected = self.model[i] + do_select = False + + if i != -1: + try: + parent = self.GetParent(model[i]) + except KeyError: + pass + else: + p = model.index_of(parent) + if parent is obj and model.is_expanded(p): + self.SetSelection(p) + else: + do_select = True + + self.model.toggle_expand(obj) + if do_select: + self.SetSelection(model.index_of(selected), keepVisible = False) + + def GetItemRect(self, item, include_children = True): + ''' + Return a rectangle (in client coordinates) for the given item. + + If include_children is True (default) children items will be included + in the rectangle calculation. + ''' + + if not include_children: + return TreeListBase.GetItemRect(self, self.model.index_of(item)) + + model = self.model + modellen = len(self.model) + measure = self.OnMeasureItem + + i = model.index_of(item) + rect = Rect(0, self.GetItemY(i), self.ClientRect.width, measure(i)) + + if include_children: + i += 1 + while i < modellen and self.GetParent(model[i]) is item: + rect.height += measure(i) + i += 1 + + return rect + + # + # Hover Property + # + + def get_hover(self): + return self.hoveridx + + def set_hover(self, i): + old = self.hoveridx + self.hoveridx = i + + if i != old: + if old != -1: + self.RefreshLine(old) + + if i != -1: + self.RefreshLine(i) + + Hover = property(get_hover, set_hover) + + def GetSelectedItem(self): + i = self.GetSelection() + if i != -1: + try: + return self.model[i] + except IndexError: + pass + + SelectedItem = property(GetSelectedItem) + + def __getitem__(self, i): + return self.model[i] + + def GetParent(self, obj): + return self.model.parent_of(obj) + + @contextmanager + def save_selection(self): + i, elem, model = self.GetSelection(), None, self.model + if i != - 1: + try: + elem = model[i] + except IndexError: + elem = None + + try: + yield + finally: + if elem is None: + sel = -1 + else: + sel = model.index_of(elem) + + if sel == -1: + if hasattr(self, 'fallback_selection'): + sel = self.fallback_selection + del self.fallback_selection + + TreeList.SetSelection(self, sel, False) + + def set_root(self, root): + with self.save_selection(): + self.renderers_cache = {} + self.model.set_root(root) + + def on_key_down(self, e): + i, model = self.GetSelection(), self.model + + keycode, modifiers = e.KeyCode, e.Modifiers + + try: + obj = self.model[i] + except IndexError: + obj = None + + # Right and left keys affect group expansion. + if keycode == WXK_LEFT: + if modifiers == wx.MOD_SHIFT: + self.collapse_all() + elif obj is not None and modifiers == wx.MOD_NONE: + if model.expandable(obj) and model.is_expanded( i ): + # left arrow on expanded item collapses it + self.toggle_expand( obj ) + else: + # left arrow on child item moves selection to its parent + self.select_parent(obj) + + elif keycode == WXK_RIGHT: + if modifiers == wx.MOD_SHIFT: + self.expand_all() + elif obj is not None and modifiers == wx.MOD_NONE: + if model.expandable(obj): + if not model.is_expanded( i ): + self.toggle_expand( obj ) + elif i+1 < self.GetItemCount() and self.GetParent(model[i+1]) is obj: + self.SetSelection(self.GetSelection() + 1) + + elif keycode == WXK_UP: + sel = self.GetSelection() - 1 + if sel >= 0: self.SetSelection(sel) + + elif keycode == WXK_DOWN: + sel = self.GetSelection() + 1 + if sel < self.GetItemCount(): self.SetSelection(sel) + + elif keycode == wx.WXK_PAGEUP: + self.PageUp() + self.SetSelection(self.GetFirstVisibleLine()) + + elif keycode == wx.WXK_PAGEDOWN: + self.PageDown() + self.SetSelection(self.GetFirstVisibleLine()) + + else: + e.Skip(True) + + def select_parent(self, obj): + 'Given a list item, selects its parent.' + + parent = self.GetParent(obj) + if parent is not None: + i = self.model.index_of(parent) + if i != -1: + self.SetSelection(i) + + def renderer_for(self, obj): + try: k = obj._renderer + except AttributeError: + try: k = obj.__class__.__name__ + except AttributeError: + k = None + + return self.renderers.get(k, None) + + def renderer_for_index(self, n): + try: + renderer = self.renderers_cache[n] + except KeyError: + renderer = self.renderers_cache[n] = self.renderer_for(self.model[n]) + + return renderer + + + + hit_test_ex = lambda self, pt, h = TreeListBase.HitTestEx: h(self, *pt) + + # some fake triangles used in the default drawing behavior for a TreeList + collapsedTri, expandedTri = [(0, 0), (7, 3), (0, 7)], [(0, 0), (7, 0), (3, 3)] + + def hit_test_parent(self, mouse_pos): + 'Returns the parent index and percent.' + + model = self.model + i, unused_percent = self.hit_test_ex(mouse_pos) + + # mouse is not over anything + if i == -1: + return -1, None + + parent = model.parent_of(model[i]) + j = model.index_of(parent) + + if j != -1: + rect = self.GetItemRect(parent) + i = j + else: + rect = self.GetItemRect(model[i]) + + + percent = (mouse_pos.y - rect.y) / rect.height + return i, percent + + def default_draw(self, dc, rect, n): + ''' + If a subclass has not provided a "better" way to draw this item, use + a default method of drawing here--which is just to str(the object) and + draw an exapander arrow if the object is iterable. + ''' + # decide on a text color + if self.IsSelected( n ): fg = wx.SYS_COLOUR_HIGHLIGHTTEXT + else: fg = wx.SYS_COLOUR_WINDOWTEXT + dc.SetTextForeground( wx.SystemSettings_GetColour( fg ) ) + + # use GUI font + font = default_font() + dc.SetFont( font ) + + # choose an expanded or collapsed triangle + if self.model.is_expanded( n ): tri = self.expandedTri + else: tri = self.collapsedTri + + # triangles will be black + dc.SetPen( wx.BLACK_PEN ) + dc.SetBrush( wx.BLACK_BRUSH ) + + obj = self.model[n] + xoffset = self.indent * self.model.depths[idfunc(obj)][0] + + yy = rect.y + (rect.height / 2) - (3) + if hasattr(obj, 'expandable'): + if obj.expandable(): + dc.DrawPolygon( [( x+rect.x + xoffset, y+yy ) for ( x, y ) in tri] ) + else: + if hasChildren( obj ): + dc.DrawPolygon( [( x+rect.x + xoffset, y+yy ) for ( x, y ) in tri] ) + + icon = getattr(obj, 'icon', None) + x = rect.x + 20 + xoffset + if icon: + dc.DrawBitmap(icon, rect.x+20, rect.y + (rect.Height / 2 - icon.GetHeight() / 2 )) + x += icon.GetWidth() + 10 + + dc.DrawText( unicode(obj), x, rect.y + (rect.Height / 2 - GetFontHeight(font, dc) / 2) ) + + def OnMeasureItem( self, n ): + 'Returns the size of the nth item.' + + renderer = self.renderer_for_index(n) + + if renderer: + return renderer.item_height(self.model[n]) + else: + return getattr(self.model[n], 'ItemHeight', 20) + + def OnDrawBackground(self, dc, rect, n, selected = None): + + obj = self.model[n] + selected = self.IsSelected(n) if selected is None else selected + renderer = self.renderer_for_index(n) + + try: + drawbg = renderer.draw_background + except AttributeError: + try: + return obj.OnDrawBackground(dc, rect, n, selected) + except AttributeError: + return TreeListBase.OnDrawBackground(self, dc, rect, n) + else: + drawbg(obj, dc, rect, n, selected, self.Hover == n) + + + def OnDrawItem( self, dc, rect, n): + 'Called for each item.' + + model = self.model + obj = model[n] + selected = self.IsSelected(n) + + # How many levels deep is this element? + try: + depthVal = model.depths[idfunc(obj)][0] + except KeyError: + log.warning('KeyError in TreeList.OnDrawItem: %r', obj) + return 1 + + draw_args = dict( + dc = dc, + rect = rect, + depth = depthVal, + obj = obj, + index = n, + expanded = model.is_expanded(n), + selected = selected, #self.IsSelected(n), + hover = self.Hover == n, + ) + + renderer = self.renderer_for_index(n) + + if renderer: + renderer.Draw(**draw_args) + else: + self.default_draw(dc, rect, n) + + def list_changed( self ): + measure = self.OnMeasureItem + self.SetHeights([measure(n) for n in xrange(len(self.model))]) + + def SetSelection(self, i, keepVisible=True): + TreeListBase.SetSelection(self, i, keepVisible) + + # + # mouse events + # + + def on_doubleclick( self, e ): + 'Double click on a tree: expand the tree item.' + + if e: e.Skip(True) + i = self.GetSelection() + if i != -1: + self.toggle_expand( self.model[i] ) + + def on_right_down(self, e): + """ + This is to provide slightly more native behavior--a right click down + means select an item. This does not prevent a popup. + """ + i = self.HitTest((e.GetX(), e.GetY())) + self.SetSelection(i) + e.Skip(True) diff --git a/digsby/src/gui/uberwidgets/PrefPanel.py b/digsby/src/gui/uberwidgets/PrefPanel.py new file mode 100644 index 0000000..2d83fa8 --- /dev/null +++ b/digsby/src/gui/uberwidgets/PrefPanel.py @@ -0,0 +1,278 @@ +import wx + +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.simplemenu import SimpleMenuItem +from gui.skin.skinparse import makeFont +from cgui import SimplePanel +from gui.anylists import AnyList +from gui.toolbox import AutoDC + +from wx import BoxSizer, BOTTOM, TOP, LEFT, RIGHT, EXPAND, VERTICAL, HORIZONTAL + +wxMac = 'wxMac' in wx.PlatformInfo + +def PrefCollection(*workers,**options): + ''' + Build all the components passed in as workers if they ned building and put them in a panel + ''' + + if 'layout' in options: + layout = options['layout'] + else: + layout = wx.GridSizer(rows=0,cols=1) + + if 'itemoptions' in options: + itemoptions = options['itemoptions'] + else: + itemoptions = (0, EXPAND) + + def Factory(parent, prefix = ''): + + panel = wx.Panel(parent, style = wx.FULL_REPAINT_ON_RESIZE) + panel.Sizer = layout + for workerortuple in workers: + + if isinstance(workerortuple, tuple): + worker = workerortuple[0] + addoptions = workerortuple[1:] + else: + worker = workerortuple + addoptions = None + + if callable(worker): + window = worker(panel, prefix) + elif isinstance(worker, wx.WindowClass): + window = worker + window.Reparent(panel) + + panel.Sizer.Add(window, *(addoptions or itemoptions)) + + return panel + return Factory + + +pref_sizer_style = EXPAND | LEFT | RIGHT | BOTTOM +combo_sizer_flags = LEFT | RIGHT, 7 + +if wxMac: + header_sizer_flags = wx.ALIGN_BOTTOM, + space_over_header = 3 + space_under_header = 8 +else: + header_sizer_flags = LEFT | wx.ALIGN_CENTER_VERTICAL, 7 + space_over_header = 0 + space_under_header = 5 + +class PrefPanel(SimplePanel): + def __init__(self, parent, content=None, title='', buttonlabel='', buttoncb=None, titlemaker=None, prefix=''): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + + sizer = self.Sizer = BoxSizer(VERTICAL) + self.headersizer = BoxSizer(HORIZONTAL) + self.bodysizer = BoxSizer(VERTICAL) + sizer.Add(self.headersizer, 0, EXPAND | TOP, space_over_header) + sizer.Add(self.bodysizer, 1, EXPAND | TOP, space_under_header) + + self.title = None + self.combo = None + self.button = None + self.content = None + self.contents = {} + self.titlemaker = titlemaker + if wxMac: + self.menuitems = {} + + if title and isinstance(title, basestring): + self.title = wx.StaticText(self, -1, ' ' + title + ' ', style = wx.ALIGN_CENTER_VERTICAL) + + #need grey backgound behind label on mac to hide the line + if wxMac: + self.title.BackgroundColour=wx.Color(232,232,232) + self.title.Font = self.HeaderFont + self.headersizer.Add(self.title, 0, *header_sizer_flags) + + if callable(content): + content = self.content = content(self, prefix) + self.bodysizer.Add(self.content, 1, pref_sizer_style, 7) + elif isinstance(content, wx.WindowClass): + content.Reparent(self) + self.content = content + self.bodysizer.Add(self.content, 1, pref_sizer_style, 7) + elif isinstance(content, list): + self.SetContents(content) + + if buttoncb: + self.SetButton(buttonlabel, buttoncb) + + Bind = self.Bind + Bind(wx.EVT_PAINT, self.OnPaint) + + #darker border if mac so it is visible for now + if not wxMac: + self.pen = wx.Pen(wx.Colour(213,213,213)) + else: + self.pen = wx.Pen(wx.Colour(155,155,155)) + + def SetTitle(self, title): + self.title.SetLabel(title) + + @property + def HeaderFont(self): + try: + return self._headerfont + except AttributeError: + if not wxMac: + PrefPanel._headerfont = makeFont('arial 8 bold') + else: + PrefPanel._headerfont = makeFont('9 bold') + return self._headerfont + + _fg_brush = \ + _bg_brush = \ + _fg_pen = \ + _bg_pen = lambda self: None + + def get_bg_brush(self): + return self._bg_brush() or wx.WHITE_BRUSH + def get_fg_brush(self): + return self._fg_brush() or wx.TRANSPARENT_BRUSH + def get_bg_pen(self): + return self._bg_pen() or wx.TRANSPARENT_PEN + def get_fg_pen(self): + return self._fg_pen() or self.pen + + bg_brush = property(get_bg_brush) + fg_brush = property(get_fg_brush) + bg_pen = property(get_bg_pen) + fg_pen = property(get_fg_pen) + + def OnPaint(self,event): + size = self.Size + dc = AutoDC(self) + + if not wxMac: + # Non mac: white background, rounded rectangle around controls + rect = wx.RectS(size) + dc.Brush = self.bg_brush #background + dc.Pen = self.bg_pen #background border + dc.DrawRectangleRect(rect) + ypos = self.headersizer.Size.height // 2 + space_over_header + gc = wx.GraphicsContext.Create(dc) + gc.SetBrush(self.fg_brush) #foreground + gc.SetPen(self.fg_pen) #foreground + gc.DrawRoundedRectangle(0, ypos, size.width-1, size.height-ypos-1, 5) + else: + # Mac: normal grey background, horizontal line above controls + ypos = self.headersizer.Size.height // 2 + space_over_header + 2 + dc.Pen = self.fg_pen + button_width = 0 if self.button is None else (self.button.Size.width) + dc.DrawLine(10, ypos, self.headersizer.Size.width - 10 - button_width, ypos) + + content = self.content + if isinstance(content, AnyList): # TODO: don't special case + crect = wx.Rect(*content.Rect) + crect = crect.Inflate(1, 1) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(self.pen) + dc.DrawRectangleRect(crect) + + def ChangeShownContent(self, *a): + if self.content: + self.content.Show(False) + + if wxMac: + menu_item = self.menuitems[self.combo.GetStringSelection()] + else: + menu_item = self.combo.Value + + self.content = self.contents[menu_item] + self.content.Show(True) + self.Layout() + + def SetButton(self,label,callback): + if self.button: + self.headersizer.Detach(self.button) + self.button.Destroy() + + # native button on mac instead of the vista clone button + if not wxMac: + self.button = UberButton(self, -1, label, skin = 'AppDefaults.PrefButton') + else: + self.button = wx.Button(self, -1, label) + self.button.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + + self.button.Bind(wx.EVT_BUTTON, lambda e: callback(self.button)) + + self.headersizer.AddStretchSpacer(1) + self.headersizer.Add(self.button, 0, wx.ALIGN_CENTER_VERTICAL | RIGHT, 7) + + @property + def MenuItems(self): + combo = self.combo + if wxMac: + return [combo.GetClientData(i) for i in xrange(combo.Count)] + else: + return combo.menu.spine.items + + def SetContents(self, content, destroyold = False): + if destroyold: + if not self.contents: + self.content.Destroy() + for content in self.contents.values(): + content.Destroy() + + # the currently showing pane in a multiple pane pref panel + if self.content: + self.content.Show(False) + self.content = None + + self.bodysizer.Clear() + contents = self.contents = {} + + titlemaker = self.titlemaker + + if self.combo is None: + if not wxMac: + self.combo = UberCombo(self, value='', skinkey='AppDefaults.PrefCombo', valuecallback = self.ChangeShownContent) + else: + # use a native ComboBox on mac + self.combo = wx.ComboBox(self, style = wx.CB_DROPDOWN | wx.CB_READONLY) + self.combo.Bind(wx.EVT_COMBOBOX, self.ChangeShownContent) + newcombo = True + else: + self.combo.RemoveAllItems() + newcombo = False + + for object in content: + if isinstance(object, tuple): + window, label = object + elif isinstance(object, wx.WindowClass): + window = object + label = titlemaker(window) if titlemaker else object.Label + + window.Show(False) + window.Reparent(self) + assert window.Parent is self + self.bodysizer.Add(window, 1, pref_sizer_style, 7) + + menuitem = SimpleMenuItem(label) + contents[menuitem] = window + + if wxMac: + itemname = menuitem.GetContentAsString() + self.combo.Append(itemname) + self.menuitems[itemname] = menuitem + else: + self.combo.AppendItem(menuitem) + + if self.combo: + if wxMac: + self.combo.SetSelection(0) + self.ChangeShownContent() + else: + self.combo.Value = self.combo[0] + + if self.combo is not None and newcombo: + self.headersizer.Add(self.combo, 1, *combo_sizer_flags) + diff --git a/digsby/src/gui/uberwidgets/SizerBar.py b/digsby/src/gui/uberwidgets/SizerBar.py new file mode 100644 index 0000000..1b30aeb --- /dev/null +++ b/digsby/src/gui/uberwidgets/SizerBar.py @@ -0,0 +1,168 @@ +import wx +from gui import skin +from gui.uberwidgets import UberWidget +from util.primitives.funcs import do + +class SizerBar(wx.Panel, UberWidget): + """ + A nifty liddle bar that can be droped between two objects in a boxsizer + and resize them. + at least one of them must not be must be manualy sized in the relevent direction + """ + def __init__(self, parent, item, sizer = None): + """ + item - the object on the left or top + """ + wx.Panel.__init__(self, parent, style=0) + + events=[(wx.EVT_PAINT, self.OnPaint), + (wx.EVT_ERASE_BACKGROUND, lambda e:None), + (wx.EVT_LEFT_DOWN, self.OnLDown), + (wx.EVT_LEFT_UP,self.OnLUp), + (wx.EVT_MOTION,self.OnDrag), + (wx.EVT_SIZE,self.OnSize), + (wx.EVT_IDLE,self.OnIdle), + (wx.EVT_LEAVE_WINDOW,lambda e: self.Refresh()), + (wx.EVT_ENTER_WINDOW,lambda e: self.Refresh())] + + do(self.Bind(event, method) for (event,method) in events) + + self.dpoint = 0 + self.delta = None + self.item = item + self.itemminsize = wx.Size(max(item.MinSize.width, 0), max(item.MinSize.height, 0)) + + def NewSetMinSize(size): + self.itemminsize=wx.Size(self.itemminsize.width if size[0]==-1 else size[0],self.itemminsize.height if size[1]==-1 else size[1]) + if self.item.OldMinSize itemlimit else \ + newsize + + self.sizer.Layout() + + #self.Parent.Refresh() + self.delta = None diff --git a/digsby/src/gui/uberwidgets/UberBar.py b/digsby/src/gui/uberwidgets/UberBar.py new file mode 100644 index 0000000..dd3cac1 --- /dev/null +++ b/digsby/src/gui/uberwidgets/UberBar.py @@ -0,0 +1,446 @@ +from gui.prototypes.fontdropdown import FontDropDown +import wx +from wx import Size, TOP, BOTTOM, EXPAND, ALIGN_LEFT, RIGHT, LEFT, HORIZONTAL, \ + FULL_REPAINT_ON_RESIZE, BufferedPaintDC, RectS, Point +from gui import skin +from UberButton import UberButton +from simplemenu import SimpleMenu,SimpleMenuItem +from gui.uberwidgets.umenu import UMenu +from gui.toolbox import OverflowShow +from gui.uberwidgets import UberWidget +from gui.skin.skinobjects import Margins,SkinColor +from cgui import SimplePanel +from util.primitives.funcs import Delegate +wxWindow_Show = wx.Window.Show + +class UberBar(SimplePanel, UberWidget): + """ + Has at least two uses as a ButtonBar + automatically resizes to biggest button, then resizes all other buttons to + fit height + + will have support for other items later but not yet + skinning will be passed + all items in the bar should think of it as there parent + + """ + def __init__(self, parent, id = wx.ID_ANY, skinkey = None, overflowmode = False, + name = 'UberBar', alignment = None): + + SimplePanel.__init__(self, parent, FULL_REPAINT_ON_RESIZE) + UberWidget.__init__(self,'toolbar') + + self.ChildPaints = Delegate() + + # Initing variables + self.alignment = alignment if alignment and not overflowmode else ALIGN_LEFT + self.overflowmode = overflowmode + self.navimode = False + self.active = None + self.children = [] + self.staticchildren = [] + self.overflowed = [] + self.focus = None + self.lastheight = 0 + + + Bind = self.Bind + Bind(wx.EVT_PAINT, self.OnBGPaint) + Bind(wx.EVT_SIZE, self.OnReSize) + + self.keyletters={} + + self.tlmargins = Size() + self.brmargins = Size() + + #Start setting up an alternaitve Native Menubar for native mode + + self.SetSkinKey(skinkey,True) + + self.content = wx.BoxSizer(HORIZONTAL) + sizer = self.Sizer = wx.GridBagSizer() + sizer.SetEmptyCellSize(wx.Size(0, 0)) + + contentFlag = TOP | BOTTOM | (self.alignment | EXPAND if self.alignment == ALIGN_LEFT else self.alignment) + + sizer.Add(self.content,(1,1),flag = contentFlag, border = self.padding.y) + sizer.Add(Size(self.margins.left,self.margins.top),(0,0)) + sizer.Add(Size(self.margins.right,self.margins.bottom),(2,2)) + sizer.AddGrowableCol(1, 1) + sizer.AddGrowableRow(1, 1) +# + + #Set up the menu for the overflowed items if overflow mode + if overflowmode: + self.overflowmenu = SimpleMenu(self,self.menuskin) + self.overflowbutton = UberButton(self, skin=self.buttonskin,type='menu',menu=self.overflowmenu) + self.content.Add( (self.padding.x, 1), 1, EXPAND ) + self.content.Add(self.overflowbutton,0, RIGHT | EXPAND, self.padding.x) + self.staticchildren.append(self.overflowbutton) + else: + spacersizer = self.spacersizer = wx.BoxSizer(wx.HORIZONTAL) + spacersizer.Add((self.padding.x, 1),0,EXPAND) + self.content.Add(spacersizer, 0, EXPAND ) + + self.GenWidthRestriction() + + def UpdateSkin(self): + """ + Update local skin references + and updates skins for buttons + """ + + key = self.skinkey + self.native = not key + if self.native: + if self.uxthemeable: + self.OpenNativeTheme() + self.bg = None + else: + self.bg = SkinColor(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)) + + self.padding = Point(2,2) + self.margins = Margins([0,0,0,0]) + + self.buttonskin = None + self.menuskin = None + + else: + + self.CloseNativeTheme() + + self.padding=skin.get(key+'.padding', Point(2,2)) + self.margins = skin.get(key+'.margins', Margins([0,0,0,0])) + + self.bg=skin.get(key+'.background',lambda: SkinColor(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))) + self.buttonskin=skin.get(key+'.buttonskin',None) + self.menuskin=skin.get(key+'.menuskin',None) + + s = self.Sizer + if s: + s.Detach(1) + s.Detach(1) + s.Add(Size(self.margins.left, self.margins.top), (0, 0)) + s.Add(Size(self.margins.right, self.margins.bottom), (2, 2)) + s.Children[0].SetBorder(self.padding.y) + for child in self.content.Children: + child.SetBorder(self.padding.x) + + for object in self.children + self.staticchildren: + if isinstance(object, UberButton): + object.SetSkinKey(self.buttonskin, True) + if object.menu: + object.menu.SetSkinKey(self.buttonskin) + + elif isinstance(object, FontDropDown): + object.SetSkinKey(self.buttonskin) + object.SetMenuSkinKey(self.menuskin) + + if hasattr(self,'overflowbutton'): + self.overflowbutton.SetSkinKey(self.buttonskin) + self.overflowmenu.SetSkinKey(self.menuskin) + + if not self.overflowmode and hasattr(self,'content'): + spacersizer = self.spacersizer + spacersizer.Detach(0) + spacersizer.Add((self.padding.x, 1), 0, EXPAND) + + wx.CallAfter(self.Layout) + + def GentAltCals(self): + """ + This Goes through and generates the keyboard shortcuts for the + menus in menubar mode + """ + self.keyletters = {} + for child in self.children: + amp_idx = child.label.find('&') + if isinstance(child, UberButton) and amp_idx != -1: + self.keyletters[child.label[amp_idx + 1].upper()] = child + + def Add(self, object, expand = False, calcSize = True): + 'Add object ot end of bar.' + + return self.Insert(len(self.children), object, expand, calcSize = calcSize) + + def AddMany(self, objects, expand = False): + for object in objects: + self.Add(object, expand) + + def AddStatic(self,object,expand=False): + 'Add object to end of bar on right of menu in overflow mode.' + + if self.overflowmode: + if isinstance(object, UMenu): + object = UberButton(self, -1, object.GetTitle(), self.buttonskin, + type='menu', menu=object) + elif isinstance(object, UberButton): + object.SetSkinKey(self.buttonskin,True) + else: + raise TypeError('Only buttons can be added to UberBar in overflow mode.') + + self.content.Insert(self.content.GetChildCount()-1, object, expand, RIGHT | EXPAND, self.padding.x) + self.staticchildren.append(object) + self.GenWidthRestriction() + else: + raise AssertionError('Static items are only availible in OverFlowMode') + + def AddMenuItem(self,item): + """ + Append a SimpleMenuItem to the overflow menu + """ + if self.overflowmode: + self.overflowmenu.AppendItem(item) + + def AddSpacer(self): + """ + Inserts a spacer to separate the items at the end of the current set + """ + if not self.overflowmode: + self.content.Add( (4*self.padding.x, 1), 1, EXPAND ) + self.GenWidthRestriction() + + def AddStretchSpacer(self, prop = 1): + self.content.AddStretchSpacer(prop) + self.GenWidthRestriction() + + def InsertSpacer(self,pos): + """ + Inserts a spacer to separate the items at the pos + """ + if not self.overflowmode: + self.content.Insert(pos,(4*self.padding.x, 1), 1, EXPAND) + self.GenWidthRestriction() + + def Remove(self, object, calcSize=True): + """ + untested + removes specified object + """ + if object.menuitem: + self.overflowmenu.RemoveItem(object.menuitem) + + try: + self.overflowed.remove(object) + except ValueError: + pass + + self.content.Detach(object) + self.children.remove(object) + + if calcSize: + self.OnUBSize() + + + def InsertNew(self, pos, thingy, id = -1, label = '',**args): + """ + Inserts a new object so you don't have to create the object beforehand + returns the id of new object + """ + + object = thingy(self, id = id, label = label, **args) + + self.Insert(pos, object) + + return id + + def Insert(self, pos, object, expand = False, calcSize = True): + 'Add object to certain position on the bar.' + + # Wrap UMenus in an UberButton + if isinstance(object, UMenu): + object = UberButton(self, -1, object.GetTitle(), self.buttonskin, type='menu', + menu = object) + + # Prepare overflow values if this is in overflow mode + elif self.overflowmode: + if not isinstance(object,UberButton): + raise TypeError('Can only add buttons or menus to an UberBar in overflow mode silly!') + + object.menuitem = None + object.shouldshow = True + object.Show = OverflowShow + + # updates skins of uberbuttons + if isinstance(object, UberButton): + object.SetSkinKey(self.buttonskin, True) + if object.menu: + object.menu.SetSkinKey(self.buttonskin) + elif isinstance(object, FontDropDown): + object.SetSkinKey(self.buttonskin) + object.SetMenuSkinKey(self.menuskin) + + # adds the item to content sizer and child list then updates values + self.content.Insert(pos, object, expand, LEFT | EXPAND, self.padding.x) + self.children.insert(pos, object) + + if calcSize: + self.OnUBSize() + + def OnBGPaint(self,event): + 'Handles wx.EVT_ERASE_BACKGROUND.' + + dc = wx.AutoBufferedPaintDC(self)#wx.BufferedDC(wx.ClientDC(self)) + + if self.bg: + rect = RectS(self.Size) + self.bg.Draw(dc,rect) + else: + self.DrawNativeLike(dc, 0, 0, wx.RectS(self.Size)) + + self.ChildPaints(dc) + +# if self.native: +# return event.Skip() + + + def OnUBSize(self,event=None): + "Does sizing when button changes it's size." + + self.GenWidthRestriction() + + + def SetAlignment(self,alignment): + """ + Can change the alignment of the buttons in the bar, left or center... + and maybe right? + """ + if self.alignment != alignment: + self.alignment = alignment + self.Sizer.Detach(self.content) + self.Sizer.Add(self.content, 1 if alignment == ALIGN_LEFT else 0, self.alignment) + self.Sizer.Layout() + + def OnReSize(self, event = None): + 'Updates width restriction information on Resize of bar.' + if event is not None: + event.Skip() + + self.GenWidthRestriction() + + + if self.Size.height != self.lastheight: + self.lastheight = self.Size.height + wx.CallAfter(self.Parent.Layout) + + def GenWidthRestriction(self, flushoverflow = False): + """ + When OverFlowMode is off this calculates the minimum size of the bar + given the minimum size of the items + + In OverFlowMode it moves items in and out of the dropdown menu + depending on bar size + """ + if not self.overflowmode: + return + + + children = [child for child in self.children if child.shouldshow] #all visible children + staticchildren = self.staticchildren #all static + overflowed = self.overflowed #all the children already overflowed + omenu = self.overflowmenu #the menu, including overflowed items + + if flushoverflow: + for thing in overflowed[:]: + + #Remove from menu + if thing.menuitem: + omenu.RemoveItem(thing.menuitem) + thing.menuitem = None + + #show the button + wxWindow_Show(thing, thing.shouldshow) + + #remove from overflow list and set overflowed flag to false + overflowed.remove(thing) + thing.overflowed = False + + #remove sizer if it's left as the first item + while omenu.spine.items[0].id == -1: + omenu.RemoveItem(0) + + self.Layout() + + if children: + + #g the rightmost, non-overflowed child + i = len(children) - len(overflowed) - 1 + laterchild = children[i] + + #find how much space there is for the children to show between the start of the bar and the dropdown button + cutoffpoint = self.Size.width - (sum(staticchild.Size.width for staticchild in staticchildren) + (len(staticchildren)+1)*self.padding.x)#staticchildren[0].Position.x - self.padding.x + + #while not all the children are overflowed and the rightmost child is over the cutoffpoint + while len(children) > len(overflowed) and (laterchild.Rect.Right >= cutoffpoint): + + #if it's the first overflowed item and there are other items in the menu + # add a separator + if not len(overflowed) and omenu.spine.items and not omenu.spine.items[0].id==-1: + omenu.Insert(0, id = -1) + + #if this item is not overflowed yet, put it in the overflowed list + overflowed.insert(0,laterchild) + + #add the now hidden item to the menu + + # let the button optionally have a different "overflowed" label and/or action than its usual ones + menu_title = getattr(laterchild, 'overflow_label', laterchild.label) + menu_cb = getattr(laterchild, 'overflow_callback', laterchild.SendButtonEvent) + + # add the icon, if it has one + if laterchild.icon is not None: + menu_title = [laterchild.icon, menu_title] + + laterchild.menuitem = SimpleMenuItem(menu_title, id=laterchild.Id, method = lambda i, mcb = menu_cb: mcb()) + omenu.InsertItem(0, laterchild.menuitem) + + #hide the item + wxWindow_Show(laterchild, False) + + #move to check next child left + i = len(children) - len(overflowed) - 1 + laterchild = children[i] + + #while there's enough room to fit the next overflowed item in the bar + while overflowed and (cutoffpoint-(0 if len(overflowed)==len(children) else laterchild.Rect.Right)>overflowed[0].Size.width+self.padding.x): + furtherchild = overflowed[0] + + if furtherchild.menuitem: + + #remove the menu item + omenu.RemoveItem(furtherchild.menuitem) + + furtherchild.menuitem = None + + #show the button + wxWindow_Show(furtherchild, True) + + #remove from overflow list + overflowed.remove(furtherchild) + + #choose next laterchild + i = len(children) - len(overflowed)-1 + laterchild = children[i] + + #remove sizer if it's left as the first item + while omenu.spine.items[0].id == -1: + omenu.RemoveItem(0) + + self.Layout() + self.Top.Layout() + + def MarkFocus(self, item): + "Set the menu's focus to said item." + + index=self.children.index(item) + if self.focus is not None and self.focus!=index: + self.children[self.focus].ReleaseHover() + self.focus=index + + def UpdateItemLabel(self, id, label): + '''Updates the button or menu item for the given id with a new label.''' + + for item in (self.FindWindowById(id, self), self.overflowmenu.FindItemById(id)): + if item is not None: + item.SetLabel(label) + diff --git a/digsby/src/gui/uberwidgets/UberButton.py b/digsby/src/gui/uberwidgets/UberButton.py new file mode 100644 index 0000000..ccf4b85 --- /dev/null +++ b/digsby/src/gui/uberwidgets/UberButton.py @@ -0,0 +1,831 @@ +import wx +from wx import Size, VERTICAL, HORIZONTAL, AutoBufferedPaintDC, Color, Rect, RectS +LEFT_VCENTER = wx.ALIGN_LEFT| wx.ALIGN_CENTRE_VERTICAL + + +from gui.textutil import GetTextExtent,DeAmp,TruncateText +import UberEvents +from gui import skin +from gui.windowfx import ApplySmokeAndMirrors #@UnresolvedImport +from util.primitives.funcs import do +from gui.skin.skinobjects import SkinGradient,Margins +from common import prefprop +from gui.textutil import default_font +from gui.uberwidgets import UberWidget + +NATIVE_BACKGROUNDS = [4, + 1, + 3, + 2, + 3, + 3, + 2 + ] + +from gui.uberwidgets.keycatcher import KeyCatcher + +class UberButton(UberWidget, wx.PyControl): + ''' + Skinnable buttons. + + Also acts as a wrapper for wx.Bitmap button to draw native looking buttons + with icons and labels can be also used to make menu buttons and checkbox + like toggle buttons. + ''' + + def __init__(self, parent, id = -1, label='', skin=None, icon=None, + pos=wx.DefaultPosition, size=None, style=HORIZONTAL, + type=None, menu=None, menubarmode=False, onclick = None): + """ + Usage: + UberButton(parent,id,label,skin,icon,pos,size,style,type,menu) + -skin - instead of detecting skins presence lke most UberGUI + this takes the skin as an argument from the parent + this allows different skins to be set to different + buttons at the same time + if not assigned will look OS native + + -icon - The icon to show up on the button + Note: the button resizes to fit the icon, not vice versa + + -pos - position of the button + + -size - size of the button, this is actualy ignored and will + likely be adjusted to affect restraint size later + + -style - wx.HORIZONTAL - Icon centered over label centered on button + wx.VERTICAL - Icon spaced from left with label to the right + + -type - None - normal button + combo - no behavior changes, only changes the drawing code + on native butons + toggle - button toggles True and False, needs visual + notification in native mode + menu - does not return an event, adds a dropdown icon + to the rightside of the button, when toggled on + displays the menu associated to it + -menu - the menu to drop down when the button is clicked if + type is menu + """ + + wx.PyControl.__init__(self, parent, id=id, pos=pos, style = wx.NO_BORDER | wx.FULL_REPAINT_ON_RESIZE) + + if type=="combo": + UberWidget.__init__(self, 'COMBOBOX' ) + else: + UberWidget.__init__(self, "button" ) + + + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.label=label + if label: + self.RegAccel(label) + + self.notified = False + self.active = False + self.isdown = False + self.hovered = False + + self.state = 1 + self.menu = menu if type == 'menu' else None + if menu is not None and hasattr(menu,'OnDismiss'): + menu.OnDismiss += lambda: self.AddPendingEvent(wx.MenuEvent(wx.wxEVT_MENU_CLOSE, menu.Id)) + + if icon: + self.icon = icon + self.MakeDicon() + else: + self.icon = None + + self.style = style + self.type = type + self.menubarmode = menubarmode + self.ssize = wx.Size(0,0) + self.staticwidth = None + + if size: + self.autoscale = False + else: + self.autoscale=True + + self.native = None + self.Sizer = wx.BoxSizer(HORIZONTAL) + + self.SetSkinKey(skin,True) + + Bind = self.Bind + Bind(wx.EVT_MOVE, lambda e: self.Refresh()), + Bind(wx.EVT_PAINT, self.OnPaint), + Bind(wx.EVT_SET_FOCUS,lambda e: self.Refresh()), + Bind(wx.EVT_KEY_DOWN,self.OnKeyDown), + Bind(wx.EVT_KEY_UP,self.OnKeyUp), + Bind(wx.EVT_KILL_FOCUS,self.OnKillFocus), + Bind(wx.EVT_LEFT_DOWN,self.OnLeftDown), + Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown), + Bind(wx.EVT_LEFT_UP,self.OnLeftUp), + Bind(wx.EVT_ENTER_WINDOW,self.OnMouseIn), + Bind(wx.EVT_LEAVE_WINDOW,self.OnMouseOut), + Bind(wx.EVT_MOTION,self.OnMotion), + Bind(wx.EVT_MENU_CLOSE, lambda e: (self.Active(False),self.OnMouseOut())) + Bind(wx.EVT_SHOW, self.OnShow) + + if onclick is not None: + if not hasattr(onclick, '__call__'): + raise TypeError('onclick must be callable') + self.Bind(wx.EVT_BUTTON, lambda e: (e.Skip(), onclick())) + + def __repr__(self): + try: + return '' % self.label + except: + return '' % id(self) + + def SetAlignment(self,alignment): + self.style=alignment + self.Calcumalate() + + def GetAlignment(self): + return self.style + + Alignment = property(GetAlignment,SetAlignment) + + +# def SetSize(self,size = None): +# +# self.autoscale = not size +# +# wx.Window.SetSize(self,size or self.ssize) +# +# Size = property(wx.Window.GetSize,SetSize) + + + def SetStaticWidth(self,width=None): + """This forces the button to not auto resize it's width.""" + + self.staticwidth = width + self.Calcumalate() + + def OnMotion(self,event): + """Ensures mouse release.""" + + if self.native: + event.Skip() + return + + r = RectS(self.Size) + if self.HasCapture() and not event.LeftIsDown() and \ + not r.Contains(event.Position): + self.ReleaseAllCapture() + + @property + def KeyCatcher(self): + try: + return self.Top._keycatcher + except AttributeError: + k = self.Top._keycatcher = KeyCatcher(self.Top) + return k + + def UpdateSkin(self): + 'Simply gets a skin and sets it all up.' + + key = self.skinkey + native= not key + + + if native and self.uxthemeable: + self.rendernative = True + self.destroy_native() + self.OpenNativeTheme() + + skinget = skin.get + + self.menuicon = skinget('appdefaults.dropdownicon') + self.padding = (5,5) + self.margins = Margins([0,0,0,0]) + self.Font = default_font() + + self.fontcolors = [wx.BLACK] * 7 + + self.backgrounds = NATIVE_BACKGROUNDS + self.Cut() + + elif native: + if not self.native: + self.native = wx.Button(self,style=wx.BU_EXACTFIT) + self.native.SetLabel(self.label) + self.Sizer.Add(self.native, 1, wx.EXPAND) + self.Layout() + self.Cut() + + else: + self.rendernative = False + self.destroy_native() + skinget = skin.get + skinroot = skin.get(key) + #s = lambda k, default = sentinel: skinget('%s.%s' % (key, k), default) + + s = skinroot.get + self.menuicon = s('menuicon', skinget('appdefaults.dropdownicon')) + self.padding = s('padding', (5,5)) + self.margins = s('margins', Margins([0,0,0,0])) + self.Font = s('font', default_font()) + + + fc = skinroot.get('fontcolors', {}) + s = fc.get + self.fontcolors = [s('disabled', Color(125,125,125)), + s('normal', wx.BLACK), + s('active', wx.BLACK), + s('hover', wx.BLACK), + s('activehover', wx.BLACK), + s('down', wx.BLACK), + s('notify', wx.WHITE)] + + bgs = skinroot.get('backgrounds', {}) + def s(key, default): + try: return bgs[key] + except: return default() + + disabled = s('disabled', lambda: SkinGradient('vertical', [Color(125,125,125), Color(237,237,237)])) + normal = s('normal', lambda: SkinGradient('vertical', [Color(200,255,200), Color(85,255,85)])) + active = s('active', lambda: SkinGradient('vertical', [Color(200,255,238), Color(85,255,238)])) + hover = s('hover', lambda: normal) + activehover = s('activehover', lambda: active) + down = s('down', lambda: SkinGradient('vertical', [Color(0,125,0), Color(00,204,00)])) + notify = s('notify', lambda: SkinGradient('vertical', [Color(255,255,200), Color(255,255,85)])) + + self.backgrounds = [disabled, + normal, + active, + hover, + activehover, + down, + notify] + + self.Calcumalate() + self.Refresh() + + def destroy_native(self): + '''Destroys the native peer button, if there is one.''' + + if self.native: + self.native.Show(False) + self.Sizer.Detach(self.native) + self.native.Destroy() + self.native = None + + def Enable(self,switch=True): + """ + Enables or disables the button + Enables by default + (maybe change to toggle?) + """ + if not self.native: + wx.PyControl.Enable(self, switch) + if switch: + events=[ + (wx.EVT_LEFT_DOWN, self.OnLeftDown), + (wx.EVT_LEFT_DCLICK, self.OnLeftDown), + (wx.EVT_LEFT_UP, self.OnLeftUp), + (wx.EVT_ENTER_WINDOW, self.OnMouseIn), + (wx.EVT_LEAVE_WINDOW, self.OnMouseOut) + ] + do(self.Bind(event, method) for (event,method) in events) + + else: + events=[wx.EVT_LEFT_DOWN, + wx.EVT_LEFT_DCLICK, + wx.EVT_LEFT_UP, + wx.EVT_ENTER_WINDOW, + wx.EVT_LEAVE_WINDOW] + do(self.Unbind(event) for event in events) + + self.state=(1 if switch else 0) + + if self.ScreenRect.Contains(wx.GetMousePosition()): + self.GetHover() + else: + self.ReleaseHover() + + else: + self.native.Enable(switch) + + self.Refresh() + + def SetMenuBarMode(self,switch=None): + """ + This turns on menubarmode + + This effects + - the showing of the dropmenu icon + - Ampersan denoted underlining + - Informing a menubar about focus events + - and some size and mouse capture logic differences logic + """ + if switch: + self.menubarmode=switch + else: + self.menubarmode= not self.menubarmode + + def MakeDicon(self): + 'Greys out an icon.' + + self.dicon = self.icon.Greyed.WXB + + def SetIcon(self, icon = None): + """ + Changes icon for the button + if no arguments speified or None, removes the icon from the button + """ + if icon: + self.icon = icon + self.MakeDicon() + else: + self.icon = self.dicon = None + + self.Calcumalate() + self.Refresh() + + def SetNotify(self, switch): + self.notified = switch + self.Refresh() + + def RegAccel(self,label = ""): +# oldLabel = self.label +# i = oldLabel.find('&') +# if i != -1 and i < len(oldLabel) - 1 and oldLabel[i+1] != '&': +# self.KeyCatcher.RemoveDown('alt+%s' % oldLabel[i+1], self.FakeClick) + + i = label.find('&') + if i != -1 and i < len(label) - 1 and label[i+1] != '&': +# print self, 'adding alt+%s' % label[i+1] + self.KeyCatcher.OnDown('alt+%s' % label[i+1], self.FakeClick) + + def SetLabel(self,label =""): + """ + Changes icon for the button + if no arguments speified or an empty string, removes the label from the button + """ + + self.RegAccel(label) + + self.label=label + if self.native: + self.native.SetLabel(label) + self.Calcumalate() + self.Refresh() + + + def GetLabel(self): + return self.label + + Label = property(GetLabel,SetLabel) + + def FakeClick(self, event = None): + if self.type == 'toggle': + # If this button is a toggle button, we need to switch + # states and throw a different type of command event. + self.Active() + self.SendButtonEvent(wx.wxEVT_COMMAND_TOGGLEBUTTON_CLICKED) + if self.type=='menu' and not self.active: + self.CallMenu() + elif not self.type == 'menu': + self.SendButtonEvent() + + def Calcumalate(self): + """ + Calculates the positioning of all content of the button and + sets the size of the button based off of these calculations + """ + if self.native: + self.Size = self.MinSize = self.native.BestSize + #self.Fit() + self.Parent.Layout() + ApplySmokeAndMirrors(self) + return + + label = DeAmp(self.label) + + if self.icon: + iconwidth, iconheight = self.icon.Width, self.icon.Height + else: + iconwidth, iconheight = (0, 0) + if label != '': + labelwidth, labelheight = GetTextExtent(label, self.Font)[0], self.Font.Height + else: + labelwidth, labelheight = (0, 0) + + self.labelsize = Size(labelwidth, labelheight) + + margins=self.margins + + sx, sy = self.padding + swidth = 0 + sheight = 0 + + #icon & label caclulations + if self.icon and label != '': + if self.style == HORIZONTAL: + swidth += iconwidth + labelwidth + 3*sx + if iconheight>labelheight: + sheight+=iconheight+2*sy + iy=sy + else: + sheight+=labelheight+2*sy + iy=sy+labelheight//2-iconheight//2 + + self.iconcurser = wx.Point(sx,iy) + self.labelcurser = wx.Point(self.iconcurser.x+iconwidth+sx,self.iconcurser.y+iconheight/2-labelheight/2) + + if self.menu and not self.menubarmode and self.menuicon: + swidth+=self.menuicon.Width+sx + + self.ssize = Size(margins.left+swidth+margins.right,margins.top+sheight+margins.bottom) + + elif self.style == VERTICAL: + labelwin = labelwidth > iconwidth + if labelwin: + swidth += labelwidth + 2 * sx + else: + swidth += iconwidth + 2 * sx + sheight += iconheight + labelheight + 3 * sy + + lx=sx + if labelwin: + self.iconcurser = wx.Point(lx + labelwidth/2-iconwidth/2,sy) + self.labelcurser = wx.Point(lx,self.iconcurser.y+iconheight+sy) + else: + self.iconcurser = wx.Point(lx,sy) + self.labelcurser = wx.Point(lx + iconwidth/2-labelwidth/2,self.iconcurser.y+iconheight+sy) + + + if self.menu and not self.menubarmode: + swidth += self.menuicon.Width+sx + + self.ssize = Size(margins.left + swidth + margins.right, + margins.top + sheight + margins.bottom) + + # Just icon caclulations + elif self.icon: + swidth += iconwidth + 2 * sx + sheight += iconheight + 2 * sy + + self.iconcurser = wx.Point(sx, sy) + + if self.menu and not self.menubarmode: + swidth += self.menuicon.Width + sx + + self.ssize = Size(margins.left + swidth + margins.right, + margins.top + sheight + margins.bottom) + + # Just label caclulations + elif label != '': + swidth += labelwidth + 2 * sx + sheight += labelheight + 2 * sy + + self.labelcurser = wx.Point(sx, sy) + + if self.menu and not self.menubarmode: + swidth += self.menuicon.Width + sx + + + self.ssize = Size(margins.left + swidth + margins.right, margins.top + sheight + margins.bottom) + + elif self.menu is not None and not self.menubarmode: + swidth += self.menuicon.Width + sheight += self.menuicon.Height + + self.ssize = Size(swidth + 2 * sx + margins.x, sheight + 2 * sy+margins.y) + else: + self.ssize = Size(16, 16) + + if self.staticwidth: + self.ssize.width = self.staticwidth + + self.MinSize = self.ssize + self.InvalidateBestSize() + + self.Parent.Layout() + + def DoGetBestSize(self): + self.CacheBestSize(self.ssize) + return self.ssize + + side_tabs = prefprop('tabs.side_tabs', False) + + def OnPaint(self,event): + "Draws the button's background." + + # excape if native + if self.native: + event.Skip() + return + + rect = RectS(self.Size) + + + parent, grandparent = self.Parent, self.GrandParent + if parent.__class__.__name__ == "Tab" and self.side_tabs: + #Clipping the button for when partualy hidden + cliptangle = RectS(self.Size) + sy = self.Rect.y + sw, sh = self.Size + py = parent.Position.y + pph = grandparent.Size.height - 16 + + if parent.Position.y + parent.Size.height > grandparent.Size.height-16 and \ + sy + sh + py > pph: + cliptangle = Rect(0,0,sw,pph-(sy+py)) + dc = wx.PaintDC(self) + dc.SetClippingRect(cliptangle) + else: + dc = AutoBufferedPaintDC(self) + + # actual drawing of background + currentstate=5 if self.isdown else \ + 6 if self.state and self.notified else \ + self.state+(self.hovered*2) if self.state else \ + 0 + + if self.rendernative: + + nrect = rect if self.type=='combo' else rect.Inflate(1,1) + #wx.RendererNative.Get().DrawPushButton(self,dc,nrect,self.backgrounds[currentstate]) + part = 1 + state = self.backgrounds[currentstate] + self.DrawNativeLike(dc,part,state,nrect) + else: + background = self.backgrounds[currentstate] + from cgui import SplitImage4 + if isinstance(background,SplitImage4): + ApplySmokeAndMirrors(self, background.GetBitmap(self.Size)) + else: + ApplySmokeAndMirrors(self) + background.Draw(dc, rect) + + #TODO: Backgroundless icon buttons + + if not (self.type == 'combo' and self.rendernative): + self.DrawContent(dc) + + def OnShow(self, e): + 'Invoked on EVT_SHOW' + + # Don't leave menus stranded if the button is being hidden. + if not e.GetShow() and self.menu and not wx.IsDestroyed(self.menu) and self.menu.IsShown(): + self.menu.Hide() + + def DrawContent(self,dc): + 'Draws the contents of the button to the DC provided.' + offset = ((self.Size.width / 2 - self.ssize[0] / 2) if (not self.menu or self.label!='' and self.icon and self.style==VERTICAL or self.label=='') and not (self.label!='' and self.icon and self.style==HORIZONTAL) else 0,self.Size.height / 2 - self.ssize[1] / 2) + ml, mt, mr, mb = self.margins + + #Draw Icons + if self.icon: + dc.DrawBitmap(self.icon if self.IsEnabled() else self.dicon, + self.iconcurser[0] + offset[0] + ml, self.iconcurser[1] + offset[1] + mt, True) + #Draw Labels + if self.label != '': + + dc.Font=self.Font + + #Set the font color + currentstate = 6 if self.state and self.notified else \ + 5 if self.isdown else \ + self.state + (self.hovered * 2) if self.state else \ + 0 + + dc.TextForeground = self.fontcolors[currentstate] + + dc.Font=self.Font + labelcurser = self.labelcurser + + loffset=(max(offset[0],0),max(offset[1],0)) + w=min(self.labelsize.x,self.Rect.Width - labelcurser.x - loffset[0] - self.padding[0] - mr - ((self.menuicon.Width +self.padding[0]) if self.menu and not self.menubarmode else 0))+2 + lrect = Rect(labelcurser.x + loffset[0] + ml,labelcurser.y + loffset[1] + mt,w,self.labelsize[1]) + +# dc.Pen=wx.Pen(wx.Color(0,255,0)) +# dc.Brush=wx.TRANSPARENT_BRUSH +# dc.DrawRectangleRect(lrect) + + dc.DrawLabel(TruncateText(self.label.replace('&', ''), w, None, dc), + lrect, LEFT_VCENTER, + indexAccel = self.label.find('&')) + + #Lastly draw the dropmenu icon if there is a menu + if self.type == 'menu' and not self.menubarmode: + dc.DrawBitmapPoint( + self.menuicon, + wx.Point(self.Size.width-self.menuicon.Width - self.padding[0] - mr, + (self.Size.height-(mt+mb))/2+mt-self.menuicon.Height/2), + True + ) + + + def Active(self, switch=None): + """ + Used for menu and toggle buttons. + + Toggles active state or sets it to the provided state. + """ + + if self.state: + old_active = self.active + self.active = not self.active if switch is None else switch + self.state = 2 if self.active else 1 + + if old_active != self.active: + self.Refresh(False) + + def IsActive(self): + return self.active + + def OnKillFocus(self,event): + """ + Part of an attempted tab-traversal fix, might work, might not. + """ + + if self.native: + event.Skip() + self.Refresh() + return + + if self.isdown: + self.OnMouseOut(event) + self.Refresh() + + def OnKeyDown(self,event): + """ + Used to allow spacebar to emulate mousedown + """ + + if self.native: + event.Skip() + self.Refresh() + return + + if event.KeyCode==wx.WXK_SPACE: + if not self.isdown and not wx.LeftDown(): self.OnLeftDown(event) + else: + event.Skip() + self.Refresh() + + def OnKeyUp(self,event): + """ + Allow spacebar to emulate mouse up + """ + + if self.native: + event.Skip() + self.Refresh() + return + + if event.KeyCode==wx.WXK_SPACE: + if self.isdown and not wx.LeftDown(): self.OnLeftUp(event) + else: + event.Skip() + + def OnLeftDown(self,event=None): + 'Left mouse down handling.' + + if self.native: + self.isdown=True + event.Skip() + self.Refresh() + return + + if self.isdown: + self.OnMouseOut(event) + return + + self.isdown=True + + if not self.HasCapture(): + self.CaptureMouse() + + self.SetFocus() + + if self.type == 'menu': + if not self.active: + self.CallMenu() + elif self.menu: + self.Active(False) + self.menu.Dismiss() + + self.Refresh() + + def SendButtonEvent(self, type = None): + """ + fakes a button event + """ + eventType = type or wx.wxEVT_COMMAND_BUTTON_CLICKED + evt = wx.CommandEvent(eventType, self.Id) + evt.EventObject = self + self.AddPendingEvent(evt) + + def OnLeftUp(self,event): + 'Left mouse up handling.' + + if self.native: + self.isdown=False + event.Skip() + self.Refresh() + return + + self.ReleaseAllCapture() + + if self.isdown: + self.isdown=False + + #If mouse is released over the button + if (event.m_x>=0 and event.m_x<=self.Size.x and event.m_y>=0 and event.m_y<=self.Size.y) or hasattr(event,'KeyCode') and event.KeyCode==wx.WXK_SPACE: + if self.type == 'toggle': + # If this button is a toggle button, we need to switch + # states and throw a different type of command event. + self.Active() + self.SendButtonEvent(wx.wxEVT_COMMAND_TOGGLEBUTTON_CLICKED) + elif not self.type == 'menu': + self.SendButtonEvent() + + self.Refresh() + + def GetHover(self): + """ + Mouse over detection for hover state and menubar mouse over open + """ + + self.hovered = True + self.Refresh() + + event = UberEvents.UBOver(source=self) + self.Parent.AddPendingEvent(event) + + def OnMouseIn(self,event): + "Mouse over handling." + + if not event.LeftIsDown() or self.menubarmode: + self.GetHover() + + if self.native: + event.Skip() + return + + elif self.HasCapture(): + # Mouse left is still down, has come back to our rectangle: depress + self.isdown = True + self.Refresh() + + def OnMouseOut(self,event = None): + 'Mouse out handling.' + + self.isdown = False + + if self.native: + event.Skip() + return + + if self.menubarmode: + self.ReleaseAllCapture() + +# if not self.menubarmode: + self.ReleaseHover() + + self.Refresh() + + def ReleaseHover(self): + 'Un-hover the button.' + + self.hovered=False + self.Refresh() + + event=UberEvents.UBOut(source=self) + self.Parent.AddPendingEvent(event) + + def CallMenu(self): + 'Click (down then up) handling.' + + self.Active() + if self.active: + event = wx.MenuEvent(wx.wxEVT_MENU_OPEN, -1) + event.SetEventObject(self.menu) + self.Top.ProcessEvent(event) + self.ReleaseAllCapture() + self.menu.Display(self) + + + def GetValue(self): + return self.active + + def SetValue(self,v): + self.active = v + self.Refresh() + + Value = property(GetValue, SetValue) + + def AcceptsFocusFromKeyboard(self): + return not self.menubarmode + + def AcceptsFocus(self): + return not self.menubarmode diff --git a/digsby/src/gui/uberwidgets/UberColorPicker.py b/digsby/src/gui/uberwidgets/UberColorPicker.py new file mode 100644 index 0000000..9ef6ae3 --- /dev/null +++ b/digsby/src/gui/uberwidgets/UberColorPicker.py @@ -0,0 +1,128 @@ +import wx +from gui.skin import skininit +from gui import skin +#from PIL import Image +#import StringIO +#from StringIO import StringIO +#import ImageEnhance + +class UberColorPicker(wx.PopupTransientWindow): + def __init__(self,parent): + wx.PopupTransientWindow.__init__(self,parent) + + events=[ + (wx.EVT_PAINT,self.OnPaint) + ] + [self.Bind(event,method) for (event,method) in events] + + self.skin = skin.get('colorpicker') + + colors=[ + (0,0,0),(0,0,128),(0,0,255),(0,128,0), + (0,128,128),(0,255,0),(0,255,255),(128,0,0), + (128,0,128),(128,128,0),(128,128,128),(255,0,0), + (255,0,255),(255,255,0),(255,255,255),(192,192,192) + ] + + padding=5 + size=28 + cx=padding + cy=padding + + self.swashes=[] + count=0 + for color in colors: + self.swashes.append(ColorBlock(self,color,(cx,cy),(size,size))) + cx+=size+padding + count+=1 + if count==4: + cx=padding + cy+=size+padding + count=0 + + self.SetSize((cy,cy)) + + def OnPaint(self,event): + dc=wx.PaintDC(self) + rect=wx.RectS(self.GetSize()) + + dc.SetPen(wx.BLACK_PEN) + dc.SetBrush(wx.WHITE_BRUSH) + + dc.DrawRectangleRect(rect) + +class ColorBlock(wx.Window): + def __init__(self,parent,color,pos,size): + wx.Window.__init__(self,parent,pos=pos,size=size) + + events=[ + (wx.EVT_PAINT,self.OnPaint) + ] + [self.Bind(event,method) for (event,method) in events] + + self.color=color + + def OnPaint(self,event): + pdc=wx.PaintDC(self) + mdc=wx.MemoryDC() + bitmap=wx.EmptyBitmap(*self.GetSize()) + mdc.SelectObject(bitmap) + + rect=wx.RectS(self.GetSize()) + + mdc.SetBrush(wx.WHITE_BRUSH) + mdc.SetPen(wx.TRANSPARENT_PEN) + + mdc.DrawRectangleRect(rect) + + mdc.SetBrush(wx.Brush(wx.Color(*self.color))) + + mdc.SetPen(wx.Pen( + wx.Color(self.color[0]/2,self.color[1]/2,self.color[2]/2), + 1, + wx.SOLID + )) + + mdc.DrawRoundedRectangleRect(rect,5) + mdc.SelectObject(wx.NullBitmap) +# image=Image.new('RGB',(bitmap.GetWidth(),bitmap.GetHeight())) +# image.fromstring(bitmap.ConvertToImage().GetData()) +# +# enhancer = ImageEnhance.Sharpness(image) +# image=enhancer.enhance(0) +# +# wximage=wx.EmptyImage(*image.size) +# wximage.SetData(image.convert('RGB').tostring()) + + + #wx.BitmapFromBuffer(Image.string) + pdc.DrawBitmap(bitmap,0,0)#wximage.ConvertToBitmap(),0,0) + + +class F(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None) + + events=[ + (wx.EVT_BUTTON,self.OnButton) + ] + [self.Bind(event,method) for (event,method) in events] + + self.b1=wx.Button(self,label='color') + self.cp=UberColorPicker(self) + + def OnButton(self,event): + self.cp.SetPosition(wx.GetMousePosition()) + self.cp.Popup() + +class A(wx.App): + def OnInit(self): + f=F() + f.Show(True) + return True + +if __name__=='__main__': + skininit('../../res') + assert gSkin() is not None + a=A(0) + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/gui/uberwidgets/UberCombo.py b/digsby/src/gui/uberwidgets/UberCombo.py new file mode 100644 index 0000000..d772de9 --- /dev/null +++ b/digsby/src/gui/uberwidgets/UberCombo.py @@ -0,0 +1,776 @@ +from __future__ import with_statement +import wx +import threading +from wx import RectS, Point, Bitmap, Rect, GetMousePosition, AutoBufferedPaintDC + +from util import try_this +from gui import skin +from gui.uberwidgets.UberEvents import EVT_UB_OVER,EVT_UB_OUT +from UberButton import UberButton +from gui.textutil import default_font +from simplemenu import SimpleMenu, SimpleMenuItem +from gui.skin.skinobjects import SkinColor,Margins,MarginSizer +from gui.uberwidgets import UberWidget +from gui.validators import LengthLimit + + +from logging import getLogger; log = getLogger('ubercombo') + +class UberCombo(wx.Window, UberWidget): + 'Skinnable Combobox' + + ForceTextFieldBackground = False + + def __init__(self, parent, value = None, typeable=False, + selectioncallback = None, + valuecallback = None, + skinkey = None, size = wx.DefaultSize, + pos = wx.DefaultPosition, maxmenuheight = None, minmenuwidth = None, + editmethod = None, + empty_text = None, # text to show in the display when it's not being edited + empty_text_hide = True, + ): + """ + value - String in the Box + typeable - if true can type in the text field + selectioncallback - method called when item is selected + Valuecallback - this is called when the text value in the combo changes + skinkey - the key string for the skin YAML + size - size + pos - position + maxmenuheight - max size of the menu + minmenuwidth - the width of the menu + editmethod - trigered when the value is being edited + """ + + wx.Window.__init__(self,parent,-1,pos,size) + + #Set Callbacks + self.selectioncallback = selectioncallback + self.valuecallback = valuecallback + self._menuClick = False + + if size: self.Size = wx.Size(*size) + + + self.content = wx.BoxSizer(wx.HORIZONTAL) + + + self.SetSkinKey(skinkey,True) + + self.selection = None + + # Create the dropdown button + self.dbutton = UberButton(self, -1, skin = self.ddbuttonskin,icon = self.dropdownicon,type = 'combo') + self.dbutton.Bind(wx.EVT_LEFT_DOWN, self.OnDropdownLeftDown) + self.dbutton.Bind(wx.EVT_LEFT_DCLICK, self.OnDropdownLeftDown) + + self.minmenuwidth = minmenuwidth + + # Create the menu for the combo + self.menu = SimpleMenu(self, maxheight = maxmenuheight, + width = minmenuwidth if minmenuwidth != None else self.Size.width, + skinkey = self.menuskin, + callback = lambda *a, **k: (setattr(self, '_menuClick', True), + self.OnSelection(*a, **k))) + + # Create the display for the combobox + disp = self.display = ComboDisplay(self, typeable = typeable, + #size = (displaywidth,self.Size.height), + empty_text = empty_text, + empty_text_hide = empty_text_hide) + disp.editmethod = editmethod + + self.content.Add(disp, 1, wx.EXPAND) + self.content.Add(self.dbutton,0,wx.EXPAND) + self.Layout() + + Bind=self.Bind + Bind(wx.EVT_PAINT, self.OnPaint) + Bind(wx.EVT_SIZE, self.OnSize) + Bind(wx.EVT_MENU_CLOSE, disp.OnMouseMove) + Bind(EVT_UB_OVER, disp.OnMouseIntoButton) + Bind(EVT_UB_OUT, disp.OnMouseOutOfButton) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + + if value: self.ChangeValue(value) + + self.Sizer = MarginSizer(self.framesize, self.content) + + def OnDropdownLeftDown(self, e): + self.OpenMenu() + + DropDownButton = property(lambda self: self.dbutton) + + def Enable(self, val): +# self.cbutton.Enable(val) + self.dbutton.Enable(val) + self.display.Enable(val) + + + def SetCallbacks(self, selection = sentinel, value = sentinel): + 'Sets callbacks for this combobox.' + + if selection is not sentinel: self.selectioncallback = selection + if value is not sentinel: self.valuecallback = value + + def GetCount(self): + 'Returns the number of choices in this combobox.' + + return self.menu.Count + + def GetIndex(self,item): + return self.menu.GetItemIndex(item) + + Count = property(GetCount) + + def __contains__(self, item): + return item in self.menu.spine.items + + def __getitem__(self, n): + if not isinstance(n, int): + raise TypeError + + return self.menu.spine.items[n] + + def __len__(self): + return len(self.menu.spine.items) + + def Add(self, itemname, method = None): + """Append a new item to the combo with an optional callback""" + self.AppendItem( SimpleMenuItem(itemname, method = method) ) + + def UpdateSkin(self): + """ + This updates the skin elements from the skin provided + can be used to update skins if the window changes, change the skin, + or revert to native mode if called with None or no arguments + """ + key = self.skinkey + + native = self.native = not key + + if native: + self.padding = Point(2,2) + self.margins = Margins([0,0,0,0]) + self.framesize = Margins([0,0,0,0]) + + + bgc = wx.SystemSettings_GetColour(wx.SYS_COLOUR_LISTBOX) + + self.framebg = SkinColor(wx.BLACK) + self.normalbg = SkinColor(bgc, border=wx.Pen(wx.BLACK, 1)) + self.activebg = SkinColor(bgc, border=wx.Pen(wx.BLACK, 1)) + self.hoverbg = SkinColor(bgc, border=wx.Pen(wx.BLACK, 1)) + + fc = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) + + self.normalfc = fc + self.activefc = fc + self.hoverfc = fc + + self.Font = default_font() + + self.menuskin = None + self.ddbuttonskin = None + self.dropdownicon = None + + else: + + s = lambda k, default: skin.get('%s.%s' % (key, k), default) + + self.padding = s('padding', Point(2,2)) + self.margins = s('margins', Margins([0,0,0,0])) + self.framesize = s('framesize', Margins([0,0,0,0])) + + self.framebg = s('frame', lambda: SkinColor(wx.BLACK)) + self.normalbg = s('backgrounds.normal', lambda: SkinColor(wx.WHITE)) + self.activebg = s('backgrounds.active', lambda: SkinColor(wx.WHITE)) + self.hoverbg = s('backgrounds.hover', lambda: SkinColor(wx.WHITE)) + + self.normalfc = s('fontcolors.normal', wx.BLACK) + self.activefc = s('fontcolors.active', wx.BLACK) + self.hoverfc = s('fontcolors.hover', wx.BLACK) + self.hintfc = s('fontcolors.hint', lambda: wx.Colour(128,128,128)) + + self.Font = s('font', lambda: default_font) + + self.menuskin = s('menuskin', '') + self.ddbuttonskin = s('dropdownbuttonskin','') + self.dropdownicon = s('dropdownbuttonicon','') + + + if getattr(self, 'dbutton', None): + self.dbutton.SetSkinKey(self.ddbuttonskin) + self.dbutton.SetIcon(self.dropdownicon) + if getattr(self, 'menu', None): + self.menu.SetSkinKey(self.menuskin) + + if self.Sizer: + self.Sizer.SetMargins(self.framesize) + + + def OnPaint(self, event): + 'Standard painting stuff.' + + dc = AutoBufferedPaintDC(self) + self.framebg.Draw(dc, RectS(self.Size)) + + def OnSize(self,event = None): + 'Resize the subcomponenets to fit when the combo is resized' + + self.Layout() + self._update_menu_width() + + def OpenMenu(self): + 'Displays menu with correct width and combo as caller' + + self._update_menu_width() + self.menu.Display(self,False) + + def _update_menu_width(self): + widths = [self.Size.width] + if self.minmenuwidth is not None: + widths.append(self.minmenuwidth) + + self.menu.SetWidth(max(widths)) + + def InsertItem(self,index,item): + """ + Inserts an item the menu at the index + """ + self.menu.InsertItem(index,item) + + def AppendItem(self, item): + """ + Adds item to the end of the menu + """ + self.menu.AppendItem(item) + + def Insert(self, index, *a, **kws): + """ + Inserts a new item into the menu + """ + self.menu.Insert(index, *a, **kws) + + def Append(self, *a, **kws): + """ + Appends a new to the end of the menu + """ + self.menu.Append(*a, **kws) + + def AppendSeparator(self): + """ + Adds a separator to the end of the menu + """ + self.menu.AppendItem(SimpleMenuItem(id=-1)) + + def RemoveItem(self,item): + """ + Removes the givin item from the menu + """ + self.menu.RemoveItem(item) + + def RemoveAllItems(self): + 'Remove all the items in this combo box.' + + self.menu.SetItems([]) + + def SetSelection(self,n): + self.SetValue(self.menu.spine.items[n]) + + def SelectNextItem(self, cycle = False): + i = self.GetItemIndex(self.Value) + 1 + + l = len(self) + + if i >= l: + if cycle: + i -= l + else: + return + + self.SetSelection(i) + + + + def SelectPrevItem(self, cycle = False): + i = self.GetItemIndex(self.Value) - 1 + + if not cycle and i<0: + return + + self.SetSelection(i) + + def SetItems(self, menuitems,selectnow=None): + self.menu.SetItems(menuitems) + + if selectnow != None and len(self): + self.SetValue(self.menu.spine.items[selectnow]) + + def SetEditable(self, editable): + self.display.Editable = editable + + def GetEditable(self): + self.display.Editable + + Editable = property(GetEditable, SetEditable) + + + def SetValue(self,value, default=None): + """ + Sets the value of the textfield to the value + in typable mode expects a string + otherwhys expects a ComboMenuItem + """ + + assert threading.currentThread().getName() == 'MainThread' + + self.display.SetValue(value,default) + + def EditValue(self, newvalue = None): + """ + Show typing field and select all for replacement. + with newvalue as the value of the field + """ + self.display.TypeField(newvalue) + + def ChangeValue(self, value, default=None): + 'Changes the value of the textfield without firing an event.' + self.display.ChangeValue(value, default) + + def GetValue(self): + 'Grabs the value of the display.' + + return self.display.GetValue() + + Value = property(GetValue, SetValue) + + def GetItemIndex(self, item): + """ + Returns the index of the item + """ + return self.menu.spine.items.index(item) + + def GetItems(self): + """ + Returns a list of the SimpleMenuItems in the menu + """ + return self.menu.spine.items + + def GetSelection(self): + 'Returns the curently selected item.' + + return self.selection + + Selection = property(GetSelection) + + def GetSelectionIndex(self): + 'Returns index of selected items.' + + return self.menu.spine.items.index(self.selection) + + def OnSelection(self,item): + """ + Item selected 'event' + calls the Selection Callback if availible + then throws a EVT_COMBOBOX to parent + """ + if self.selection is item: + return + else: + self.selection = item + + if isinstance(item,int): + item = self.menu.spine.items[item] + + self.display.SetFocus() + self.display.SelectAll() + + if self.selectioncallback: + self.selectioncallback(item) + else: + self.display.SetValue(item) + + event = wx.CommandEvent(10020, self.GetId()) + event.SetInt(try_this(lambda: self.menu.spine.items.index(item), -1)) + self.Parent.AddPendingEvent(event) + + def GetItemBy(self,attr,value): + """ + Find an item by one of its attribute's values + Returns first item or None if no matchin items were found + attr - string naming the atribute + value - the value you want the atribute of the item to be + """ + items = self.menu.spine.items + for item in items: + if value == getattr(item, attr, None): + return item + + def GetStringsAndItems(self): + """ + Returns a list of string,item tuples + a tuplefor each string in each item + """ + alist = [] + items = self.menu.spine.items + for item in items: + for thing in item.content: + if isinstance(thing, basestring): + alist.append((thing, item)) + + return alist + + + @property + def TextField(self): + return self.display.txtfld + + @property + def defaultvalue(self): + return self.display.defaultvalue + +ComboDisplayStyle = wx.FULL_REPAINT_ON_RESIZE | wx.NO_BORDER + +class ComboDisplay(wx.Window): + 'A sort of un-editable text field with picture support.' + + textfield_style = wx.NO_BORDER | wx.TE_PROCESS_ENTER + + def __init__(self,parent, id=-1, typeable=False, value='', + pos = wx.DefaultPosition, size = wx.DefaultSize, empty_text = None, empty_text_hide=True, + validator=None, ): + """ + ComboDisplay constructor. + + value - what is initialy displayed + """ + wx.Window.__init__(self, parent, id, pos, size, style = ComboDisplayStyle) + + Bind = self.Bind + Bind(wx.EVT_PAINT, self.OnPaint) + Bind(wx.EVT_LEFT_DOWN, self.OnLDown) + Bind(wx.EVT_TEXT, self.OnType) + Bind(wx.EVT_TEXT_ENTER, self.OnLoseFocus) + Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter) + Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) + Bind(wx.EVT_MOTION, self.OnMouseMove) + Bind(wx.EVT_SIZE, self.OnSize) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.value = self.lastvalue = value + self.defaultvalue = '' + self.empty_text = empty_text + self.empty_text_hide = empty_text_hide + self.hover=False + + if validator is None: + validator = LengthLimit(20480) + + txt = self.txtfld = wx.TextCtrl(self, -1, style=self.textfield_style, validator=validator) + txt.Show(False) + txt.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus) + txt.Bind(wx.EVT_SET_FOCUS, self.OnType) + + self._enabled = True + self.Editable = typeable + + self.UpdateSkin() + + def UpdateSkin(self): + """ + Updates the local skin references to the current values of the parent + """ + + txt, p = self.txtfld, self.Parent + txt.Font = self.Font = p.Font + txt.BackgroundColour = wx.Colour(*p.activebg) + txt.ForegroundColour = wx.Colour(*p.activefc) + + padding = self.Parent.padding + margins = self.Parent.margins + + txtheight = txt.Font.GetHeight() + txt.Font.Descent + + txt.MinSize = wx.Size(-1,txtheight) + txt.Size = wx.Size(self.Size.width - (2 * padding.x + margins.x), txtheight) + + self.MinSize = wx.Size(-1, txtheight + margins.y + 2 * padding.y) + + self.Parent.Layout() + self.Refresh(False) + + def OnSize(self,event=None): + if event: + event.Skip() + + txt = self.txtfld + padding = self.Parent.padding + margins = self.Parent.margins + + txt.Size = wx.Size(self.Size.width - (2 * padding.x + margins.x), txt.MinSize.height) + + + def OnMouseIntoButton(self,event): + if not self.typeable: + self.hover = True + self.Refresh(False) + + def OnMouseOutOfButton(self,event): + if not self.typeable: + self.hover=False + self.Refresh(False) + + def OnMouseEnter(self,event): + if not self.typeable: + wx.CallAfter(self.Parent.dbutton.GetHover) + if not self.HasCapture() and not self.Editable: + self.CaptureMouse() + + if self.Editable: + self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM)) + + self.hover = True + self.Refresh(False) + + def OnMouseMove(self,event): + rect = RectS(self.Size) + pos = self.ScreenToClient(GetMousePosition()) + + if not rect.Contains(pos): + self.OnMouseLeave(event) + elif not self.hover: + self.OnMouseEnter(event) + + def OnMouseLeave(self,event): + if not self.typeable: + wx.CallAfter(self.Parent.dbutton.ReleaseHover) + + while self.HasCapture(): + self.ReleaseMouse() + + self.hover=False + self.Refresh(False) + self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + + def Enable(self, val): + self._enabled = val + self.Editable = self.Editable # cause a cursor update + + def SetEditable(self, val): + self.typeable = val + self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM if val and self._enabled else wx.CURSOR_DEFAULT)) + + def GetEditable(self): + return self.typeable + + Editable = property(GetEditable, SetEditable) + + def SetValue(self,value, default = None): + 'Set a new display value. Calling the valuecallback' + + assert threading.currentThread().getName() == 'MainThread' + + if default != None: + self.defaultvalue = default + + self.value = value + + valuestring = '' + if isinstance(value,basestring): + valuestring = value + else: + for thing in value.content: + if isinstance(thing, basestring): + valuestring = thing + break + + if wx.IsDestroyed(self.txtfld): + return + + self.txtfld.SetValue(valuestring) + + valuecallback = self.Parent.valuecallback + if valuecallback: wx.CallAfter(valuecallback, valuestring) + + self.Refresh(False) + + def ChangeValue(self, value, default = None): + 'Sets the value without calling any callbacks.' + + + if default != None: + self.defaultvalue = default + + self.value = value + self.txtfld.ChangeValue(value if isinstance(value, basestring) else value.GetContentAsString()) + self.Refresh(False) + + def SetDisplayLabel(self, text): + self._displaylabel = text + + def GetDisplayLabel(self): + text = getattr(self, '_displaylabel', None) + return text if text is not None else self.value + + DisplayLabel = property(GetDisplayLabel, SetDisplayLabel) + + def SelectAll(self): + 'Select everthing in the text field' + + self.txtfld.SetInsertionPoint(0) + self.txtfld.SetSelection(-1,-1) + + def GetValue(self): + 'Returns a value' + + return self.value + + def OnPaint(self,event): + 'EVT_PAINT handling' + dc = AutoBufferedPaintDC(self) + rect = RectS(self.Size) + + # draw the background + background = self.Parent.hoverbg if self.hover else self.Parent.normalbg + background.Draw(dc, rect) + + margins = self.Parent.margins + padding = self.Parent.padding + + # Font setup + font = None + sp = self.Parent.menu.spine + sel = sp.Selection + if sel > len(sp.items): sel = sp.Selection = -1 + + if sel != -1 and sel < len(sp.items) and sel >= 0 and sp.items[sel].font: + font = sp.items[sel].font + #font.PointSize=self.Font.PointSize + else: + font = self.Font + + dc.Font = font + color = self.Parent.hoverfc if self.hover else self.Parent.normalfc + dc.TextForeground = color + fontHeight = font.Height + + # initial cursor setup + + if self.Parent.ForceTextFieldBackground: + self.Parent.activebg.Draw(dc,rect) + + label = self.DisplayLabel + + # Draw text if value is just a string + if isinstance(label, basestring): + cursor = Point(rect.x+margins.left+padding.x, margins.top+padding.y) + if not label and self.empty_text: + text = self.empty_text + dc.SetTextForeground(self.Parent.hintfc) + else: + text = label + + text_rect = Rect(cursor.x, cursor.y, rect.width - cursor.x - margins.right - padding.x, fontHeight) + text_rect, text = self.music_note_hack(dc, text_rect, text, fontHeight) + dc.DrawTruncatedText(text.split('\n', 1)[0], text_rect, alignment = wx.ALIGN_LEFT| wx.ALIGN_TOP) + + # or draw each part of the value + else: + cursor = Point(rect.x+margins.left+padding.x, ((rect.height - margins.top - margins.bottom) / 2)+ margins.top) + if label is not None: + for i in label.content: + if isinstance(i, Bitmap): + dc.DrawBitmapPoint(i, (cursor.x, cursor.y - i.Height/2), True) + cursor += Point(i.Width + padding.x, 0) + + elif isinstance(i, basestring): + dc.DrawTruncatedText(i.split('\n', 1)[0], wx.Rect(cursor.x, cursor.y-fontHeight/2, rect.width - cursor.x-margins.right - padding.x, fontHeight), alignment =wx.ALIGN_LEFT| wx.ALIGN_CENTRE_VERTICAL) + cursor += Point(dc.GetTextExtent(i)[0] + padding.x, 0) + + # Draw a background for the textfield + if self.txtfld.Shown:# or self.Parent.ForceTextFieldBackground: +# dc.Brush = wx.Brush(self.Parent.activebg) +# dc.Pen = wx.TRANSPARENT_PEN +# +# dc.DrawRectangleRect(rect) + self.Parent.activebg.Draw(dc,rect) + + def music_note_hack(self, dc, text_rect, text, fontHeight): + if text.startswith(u'\u266b'): + music_icon = skin.get('appdefaults.icons.notes', None) + if music_icon: + icon_rect = wx.Rect(*text_rect) + icon_rect.y += 2 + music_icon = music_icon.Resized(fontHeight+2).WXB + music_icon.Draw(dc, icon_rect, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) + text_rect.x += music_icon.Width + 2 + text = text[1:] + + return text_rect, text + + + def OnLDown(self, event=None): + 'Clicking to type in the combobox if enabled.' + + if self._enabled: + if self.txtfld.Shown: + pass + elif self.typeable: + self.TypeField() + else: + if len(self.Parent.menu): + self.Parent.OpenMenu() + + def TypeField(self, newvalue = None): + 'Show typing field and select all for replacement.' + + #print "TypeField",self.Top.Title,'\n','='*80,'\n','\n'.join(format_stack()),'\n','='*80 + self.lastvalue = self.value + txt = self.txtfld + padding = self.Parent.padding + margins = self.Parent.margins + + txt.Position = wx.Point(padding.x + margins.left, margins.top + padding.y) + + txt.Show(True) + txt.SetFocus() + + + if newvalue is not None: + self.txtfld.ChangeValue(newvalue) + self.SelectAll() + + self.Refresh(False) + + def OnType(self,event): + 'Typing Events' + + if self.editmethod: + self.editmethod() + + event.Id = self.Id + self.Parent.AddPendingEvent(event) + self.Refresh(False) + + def OnLoseFocus(self, event): + 'Textfield hides itself when it loses focus.' + + if self.txtfld.Shown: + event.Id = self.Id + self.GrandParent.AddPendingEvent(event) + + def fireValue(): + if not self.Parent._menuClick: + self.SetValue( self.txtfld.GetValue() ) + else: + self.Parent._menuClick = False + + wx.CallAfter(fireValue) + self.txtfld.Show(False) + self.Refresh(False) diff --git a/digsby/src/gui/uberwidgets/UberEmotiBox.py b/digsby/src/gui/uberwidgets/UberEmotiBox.py new file mode 100644 index 0000000..aefb79e --- /dev/null +++ b/digsby/src/gui/uberwidgets/UberEmotiBox.py @@ -0,0 +1,222 @@ +''' +The emoticon dropdown in the IM window. +''' + +from __future__ import with_statement + +import os.path +import math +import wx + +from gui import skin +from gui.skin.skinobjects import Margins +from gui.toolbox.imagefx import wxbitmap_in_square +from gui.toolbox import Monitor, AutoDC +from wx import TextCtrl + +UberEmotiBase = wx.PopupTransientWindow + +class UberEmotiBox(UberEmotiBase): + """ + This is a box that shows a preview for the available emoticons and allows insertion by click. + """ + def __init__(self, parent, emoticons, textctrl, maxwidth = 7, minwidth = 4): + """ + parent - is required by PopupTransientWindow, + also will likely be used to know what conversation the emoticon belongs to + + emoticons - [(bitmap, ":) :-) :]"), (bitmap2, ":( :-( :["), ...] + """ + UberEmotiBase.__init__(self, parent, style=wx.NO_BORDER) + self.maxwidth = maxwidth + self.minwidth = minwidth + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.SetExtraStyle(wx.WS_EX_PROCESS_IDLE) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.UpdateSkin() + + self.SetTextCtrl(textctrl) + + # create and layout protocol specific emoticons + if emoticons != getattr(self, 'emoticons', None): + self.emoticons = emoticons + self.DestroyChildren() + self.pemoticons, pwidth, pheight = self.Arrange(self, emoticons) + + # Set size of box to fit content + self.lesssize = (pwidth, pheight) + self.SetSize(self.lesssize) + + def SetTextCtrl(self, textctrl): + self.textctrl = textctrl + + def UpdateSkin(self): + skin_elements = [ + ('padding', 'padding', lambda: wx.Point(3, 3)), + ('margins', 'margins', skin.ZeroMargins), + ('esize', 'emoticonsize', 19), + ('espace', 'emoticonspace', 23), + ('bg', 'backgrounds.window', None), + ] + + for attr, skinattr, default in skin_elements: + setattr(self, attr, skin.get('emotibox.' + skinattr, default)) + + def Arrange(self, parent, emots): + """ + Calculates the layout based on the passed in parameters + Creates the emoticons + Returns a list of the emoticons, and width and height or the layout + + parent - what the emoticons should be drawn in + emots - the emoticons to be drawn in said space + """ + + # calculate squarest layout + width = round(math.sqrt(len(emots))) + if width < self.minwidth: width = self.minwidth + if width > self.maxwidth: width = self.maxwidth + + size = self.espace + padding = self.padding + margins = self.margins + cx = padding.x + margins.left + cy = padding.y + margins.top + + emoticons = [] + count = 0 + + for emot in emots: + emoticons.append(Emoticon(parent, emot, (cx, cy))) + + cx += size + padding.x + count += 1 + if count == width: + cx=padding.x + cy += size + padding.y + count = 0 + if cx != padding.x: + cy += size + padding.y + + across = width * (size + padding.x) + padding.x + margins.x + cy += margins.bottom + + if cy < across: + cy = across + + return emoticons, across, cy + + def OnPaint(self, event): + dc = AutoDC(self) + self.bg.Draw(dc, self.ClientRect) + + def Display(self,rect): + 'Set Position and popup' + + pos = rect.BottomLeft + newrect = wx.RectPS(pos, self.Size) + + screenrect = Monitor.GetFromRect(newrect).ClientArea + + if newrect.bottom > screenrect.bottom: + pos.y -= rect.height + self.Size.height + if newrect.right > screenrect.right: + pos.x += rect.width - self.Size.width + + self.SetPosition(pos) # Position property doesn't work for PopTransWin + self.Popup() + + +class Emoticon(wx.Window): + ''' + An object thats a visual and interactive representation of an emoticon with a tooltip + ''' + + def __init__(self, parent, emot, pos): + """ + emote - bitmap and keys + pos - where to draw + size - how big + """ + wx.Window.__init__(self, parent, pos = pos) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + events = [(wx.EVT_PAINT, self.OnPaint), + (wx.EVT_ENTER_WINDOW, self.OnMouseInOrOut), + (wx.EVT_LEAVE_WINDOW, self.OnMouseInOrOut), + (wx.EVT_LEFT_UP, self.OnLeftUp)] + + for evt, meth in events: + self.Bind(evt, meth) + + imgpath, self.keys = emot + self.SetToolTipString(' '.join(self.keys)) + + # TODO: load these more lazily + self.emote = None + try: + bitmap = wx.Bitmap(imgpath) + if bitmap.Ok(): + self.emote = bitmap + except Exception: + from traceback import print_exc; print_exc() + + self.UpdateSkin() + + def UpdateSkin(self): + 'Updates local skin references' + + size = skin.get('emotibox.emoticonspace', 24) + self.Size = wx.Size(size, size) + esize = skin.get('emotibox.maxemoticonsize', 16) + self.normalbg = skin.get('emotibox.backgrounds.emoticon', None) + self.hoverbg = skin.get('emotibox.backgrounds.hover', None) + + emote = self.emote + if emote is not None: + self.bitmap = emote.ResizedSmaller(esize) + else: + self.bitmap = None + + + def OnPaint(self,event): + dc = wx.AutoBufferedPaintDC(self) + + rect = wx.RectS(self.Size) + + if wx.FindWindowAtPointer() is self: + self.hoverbg.Draw(dc, rect) + else: + self.normalbg.Draw(dc, rect) + + W, H = self.GetSize() + w, h = self.bitmap.GetSize() + + # drawing it centered on the box + if self.bitmap is not None: + dc.DrawBitmap(self.bitmap, W/2 - w/2, H/2 - h/2, True) + + def OnMouseInOrOut(self, event): + 'mouse over highlighting' + + event.Skip() + self.Refresh() + + def OnLeftUp(self, event): + 'What to do when an emote is clicked.' + + import hooks + hooks.notify('digsby.statistics.emoticons.chosen') + + textctrl = self.Parent.textctrl + ip = textctrl.InsertionPoint + + if ip != 0 and textctrl.Value[ip-1] and textctrl.Value[ip-1] != ' ': + textctrl.WriteText(' ') + + textctrl.WriteText(''.join([self.keys[0], ' '])) + + self.Parent.Dismiss() + self.Parent.textctrl.SetFocus() diff --git a/digsby/src/gui/uberwidgets/UberEvents.py b/digsby/src/gui/uberwidgets/UberEvents.py new file mode 100644 index 0000000..433f2f9 --- /dev/null +++ b/digsby/src/gui/uberwidgets/UberEvents.py @@ -0,0 +1,13 @@ +#@PydevCodeAnalysisIgnore + +import wx +import wx.lib.newevent + +newevt = wx.lib.newevent.NewEvent + +UBOver, EVT_UB_OVER = newevt() +UBOut, EVT_UB_OUT = newevt() + +DragStart, EVT_DRAG_START = newevt() + +TabNotifiedEvent, EVT_TAB_NOTIFIED = newevt() \ No newline at end of file diff --git a/digsby/src/gui/uberwidgets/UberProgressBar.py b/digsby/src/gui/uberwidgets/UberProgressBar.py new file mode 100644 index 0000000..7286699 --- /dev/null +++ b/digsby/src/gui/uberwidgets/UberProgressBar.py @@ -0,0 +1,168 @@ +import wx + +import config +from gui.textutil import GetTextExtent,default_font +from util.primitives.funcs import do +from util.primitives.error_handling import try_this +from gui import skin +from cgui import SplitImage4 +from gui.windowfx import ApplySmokeAndMirrors +from gui.skin.skinobjects import SkinColor +from gui.uberwidgets import UberWidget + +class UberProgressBar(wx.Gauge, UberWidget): + """ + Skinable progress bar, works simular to wx.Gauge + + -posible features + -optional percentage display + -vertical progress bar? + """ + def __init__(self, parent, id = -1, range = 100, skinkey = None, + showlabel = False, style = wx.GA_SMOOTH, + pos = wx.DefaultPosition, + size = wx.DefaultSize): + + wx.Gauge.__init__(self,parent,-1,range, pos, size, style) + self.style = style + self.range = range + self.value = 0 + self.showlabel = showlabel + + events=[(wx.EVT_PAINT, self.OnPaint), + (wx.EVT_ERASE_BACKGROUND, self.OnBGPaint), + (wx.EVT_SIZE, self.OnSize)] + + do(self.Bind(event, method) for (event, method) in events) + self.SetSkinKey(skinkey,True) + + self.Calcumalate() + + def UpdateSkin(self): + """ + Updates all the local skin references + """ + key = self.skinkey + + s = lambda k, default = sentinel: skin.get(key + '.' + k, default) + + mode = try_this(lambda: str(s('mode', '')), '') + if config.platformName != "mac" and key and mode.lower() != 'native': + self.native=False + + self.repeat = try_this(lambda: str(s('style', '')).lower(), '') == 'repeat' + self.padding = s('padding', 0) + self.Font = s('font', lambda: default_font()) + + self.bg = s('backgrounds.normal', None) + self.fg = s('backgrounds.fill', lambda: SkinColor(wx.BLUE)) + + self.normalfc = s('fontcolors.normal', wx.BLACK) + self.fillfc = s('fontcolors.fill', wx.WHITE) + + else: + self.native = True + + self.OnSize() + + def OnSize(self,event=None): + """ + Reaply smoke and mirrors when the bar is resized + """ + if not self.native and isinstance(self.bg, SplitImage4): + ApplySmokeAndMirrors(self, self.bg.GetBitmap(self.Size)) + else: + ApplySmokeAndMirrors(self) + +#-----------------------Code Ror Centered Label----------------------- +# +# labelsize = wx.Size(*GetTextExtent('100%',self.Font)) +# size=self.Size +# self.labelrect = wx.RectPS((size.x/2-labelsize.x/2,size.y/2-labelsize.y/2),labelsize) + +#--------------------------------------------------------------------- + def OnBGPaint(self,event): + if self.native: + event.Skip() + + def OnPaint(self,event): + """ + Painting when skined, otherwise it lets the system handle it + """ + if self.native: + event.Skip() + return + + dc=wx.AutoBufferedPaintDC(self) + + rect=wx.RectS(self.Size) + + #Background drawing + if self.bg: + self.bg.Draw(dc,rect) + + + rect.width=int(float(self.value)/float(self.range)*self.Size.x) + + #forground drawing + if self.repeat: + curser = wx.Point(self.padding,0) + while curser.x 1: + a = self.panels[accts[1]].Position.y + b = self.panels[accts[0]].Position.y + else: + dist = 0 + except Exception: + dist = 0 + else: + if a is not None and b is not None: + dist = a - b + if dist: + dist = lines * dist - getattr(self, 'borrowed_dist', 0) + self.borrowed_dist = int(round(dist)) - dist + self.Scroll(0, self.ViewStart.y + int(round(dist))) + else: + super(ConnectionList, self).ScrollLines(lines) + + def InitShow(self,*a): + """ + Because accounts are loaded late, the decision to show most happen + later. Decides the initial show based off if the account + """ + + #ShowAll is True if no accounts autoLogin + def show(): + if not self.Parent.button_has_been_pressed: # don't toggle state if user has already clicked. + self.ShowAll = not any(getattr(account, 'autologin', False) for account in profile.account_manager.accounts) + self.Parent.statebutton.icon = self.Parent.iconhide if self.ShowAll else self.Parent.iconshow#Label = 'Hide' if self.ShowAll else 'Show' + + self.showtimer = CallLater(500, show) + + def SetShowAll(self, show): + """ + Set the ShowAll mode True or False + True - Show all accounts + False - Show only accounts in error or between states + """ + self.showall = show + self.UpdateChildrenShown() + + def GetShowAll(self): + "Returns ShowAll mode True or False" + + return self.showall + + ShowAll = property(GetShowAll, SetShowAll) + + def UpdateChildrenShown(self): + """ + Loop through the children showing or hiding the ones affected by the + a ShowAll switch. + """ + for child in self.Children: + if hasattr(child, 'DetermineShow'): + child.DetermineShow() + + wx.CallAfter(self.SelfSize) + + def OnSize(self, event = None): + """ + When the list has a change in size sets the virtual size and calls + SelfSize to firgure out the appropriate MinSize + """ + if event is not None: + event.Skip() + + # Make sure virtual Width is the same as the Client Width + self.SetVirtualSizeHints(self.ClientSize.width, -1, self.ClientSize.width, -1) + + if self.Size.width == self.lastwidth: + return + + wx.CallAfter(self.SelfSize) + + def WhenListChanges(self, *a): + self.Freeze() + try: + scrolly = self.GetScrollPos(wx.VERTICAL) + accts = list(accounts) + + # Early exit hack here if the accounts list is not empty and equal + # to our cached one in self.oldaccounts--the logic for preserving + # the scroll is always a few pixels off, so don't do anything if we + # don't have to. + if accts and self.oldaccounts == accts: + return + + self.Sizer.Clear(deleteWindows = False) + oldaccts, panels = self.oldaccounts, self.panels + old, new = set(oldaccts), set(accts) + + for acct in old - new: + panel = panels.pop(acct) + panel.OnDestroy() + panel.Destroy() + + for acct in new - old: + panels[acct] = ConnectionPanel(self, acct) + + # Add a spacer to make sure the first visible item obeys the spacing between + # itself and the edge of the list + self.Sizer.AddSpacer(self.spacing) + + # create a new panel for each account and place it in the sizer + + # try: + for acct in accts: + self.Sizer.Add(panels[acct], 0, panel_flags, self.spacing) + + self.oldaccounts = accts + + wx.CallAfter(self.SelfSize) + wx.CallAfter(self.Scroll, 0, scrolly) + finally: + wx.CallAfter(self.Thaw) + + def SelfSize(self): + """ + Figures out the maximum height of the list by the smaller of shown elements + or the pref connectionlist.max_height + """ + + #h is the minimum of height of all items + spacing or the height specified in prefs + h = min(sum(child.MinSize.height+self.spacing for child in self.Children if child.Shown) + self.spacing, pref('connectionlist.max_height', 200)) + #Hide if no items shown + self.Show(h > self.spacing) + + self.MinSize = wx.Size(-1, h) + self.GrandParent.Layout() + + if self.Shown: + wx.CallAfter(self.Layout) + wx.CallAfter(self.Refresh) + wx.CallAfter(self.EnsureVirtualHeight) + + self.lastwidth = self.Size.width + + def EnsureVirtualHeight(self): + """ + This is called to make sure that the VirtualHeight is correct + """ + vheight = self.VirtualSize.height + cheight = self.Sizer.MinSize.height + + if vheight != cheight: + self.VirtualSize = (-1, cheight) + + +class ConnectionPanel(SimplePanel, UberWidget): + """ + This panel corresponds to an individual IM account and displays data about it. + It displays the account's username, it's offline reason or state if no + reason is available, a link that will connect if offline or disconnect + if online, a dismiss button if the list is not set to ShowAll and the + account is either online or offline, the protocol icon, and the state icon + if the list is in ShowAll. + """ + def __dtor__(self): + from gui.toolbox.refreshtimer import refreshtimer + refreshtimer().UnRegister(self) + + def __init__(self, parent, account): + + self.initover = False + + SimplePanel.__init__(self, parent) + + self.Show(False) + + self.account=account + + #Refernce for the timer for delayed hide + self.willhide = None + + self.SetSkinKey('AccountPanels',True) + + self.title = account.username + + self.account.add_observer(self.WhenStateChange,'offline_reason') + self.account.add_observer_callnow(self.WhenStateChange,'state') + + #Becasue the width affects the content and the content effects the height + # this is used to store the last size so the widths can be compared + # perventing an infinit loop of size adjustments + self.lastsizedto=self.Size + + #These are used for faking a button + self.buttonstate = 0 #visual state: Idle; 1: hover; 2: down + self.buttonisdown = False #because if the button is down and shown down can differ + self.buttonshown= False #Is the button visible? + + self.linkhovered = False #True if mouse is in the link rect + self.linkdown = False #True if the mouse button was pushed while in link rect + + Bind = self.Bind + + Bind(wx.EVT_PAINT, self.OnPaint) + Bind(wx.EVT_SIZE, self.OnSize) + Bind(wx.EVT_ENTER_WINDOW, self.OnMouseIn) + Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseOut) + Bind(wx.EVT_MOTION, self.OnMouseMotion) + Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + Bind(wx.EVT_RIGHT_UP, self.OnMenu) + Bind(wx.EVT_KEY_DOWN, self.OnKey) + + self.initover = True + + def OnKey(self, event): + self.Parent.ProcessEvent(event) + + def OnMenu(self, e = None): + if e: e.Skip() + menu = self.Parent.menu + account = self.account + + menu.RemoveAllItems() + menu.AddItem(_('&Edit'), callback = lambda: profile.account_manager.edit(account)) + menu.AddSep() + + if account.connection: + actions.menu(self, account.connection, menu) + actions.menu(self, account, menu) + else: + menu.AddItem(_('&Connect'), callback = lambda: account.connect()) + + menu.PopupMenu() + + def WhenStateChange(self, acct, attr, old, new): + """ + When the account state changes this calls everything that sets up the + the elements of the panel and determines whether it shows or hides + """ + # An if self check becasue it's posible the callback could trigger between + # the time the object is destroyed and the observer is removed + account = self.account + + #When in the WILL_RECONNECT state, registered to refresh every 1 second + from gui.toolbox.refreshtimer import refreshtimer + if account.offline_reason == OfflineReason.WILL_RECONNECT: + self.reason = lambda: profile.account_manager.state_desc(self.account) + refreshtimer().Register(self) + else: + self.reason = profile.account_manager.state_desc(account) if account.state==ProtocolStatus.OFFLINE and account.offline_reason else _(account.state) + refreshtimer().UnRegister(self) + + self.stateicon = account.statusicon.ResizedSmaller(16) + + if not wx.IsDestroyed(self): + self.MakeLink() + self.CalcLayout() + self.Refresh() + #Tells DetermineShow to delay the hide if state changed to ONLINE or OFFLINE + delayed = (account.state == ProtocolStatus.ONLINE) or (account.state == ProtocolStatus.OFFLINE and account.offline_reason == OfflineReason.NONE) + self.DetermineShow(delayed) + self.Parent.SelfSize() + self.GrandParent.Parent.Layout() + + def MakeLink(self): + """ + Gets the link information for the current state of the account + """ + self.link,self.linkcallback = self.account.get_link() + + def UpdateSkin(self): + """ + The usual update skin + """ + key = self.skinkey + s = lambda k,d=None: skin.get('%s.%s'%(key,k),d) + + self.bg = s('backgrounds.account',lambda: SkinColor(wx.WHITE)) + self.majorfont = s('Fonts.Account', default_font) + self.minorfont = s('Fonts.StateAndLink',default_font) + self.linkfont = CopyFont(self.minorfont, underline=True) + self.majorfc = s('FontColors.Account', wx.BLACK) + self.statefc = s('FontColors.State',lambda: wx.Colour(125,125,125)) + self.linkfc = s('FontColors.Link', wx.BLUE) + self.closeicon = [None]*3 + self.closeicon[0] = s('Icons.Close',skin.get('AppDefaults.removeicon')).ResizedSmaller(16) + self.closeicon[1] = s('Icons.CloseHover',self.closeicon[0]).ResizedSmaller(16) + self.closeicon[2] = s('Icons.CloseDown',self.closeicon[1]).ResizedSmaller(16) + self.liconsize = s('ServiceIconSize',16) + self.padding = s('Padding',lambda: wx.Point(3,3)) + + + self.stateicon = self.account.statusicon.ResizedSmaller(16) + + self.licon = self.account.serviceicon.Resized(self.liconsize) + + if self.initover: + self.CalcLayout() + + def CalcLayout(self): + """ + Calculates the layout and height of the pannel based off of it's + width and the size of the elements + """ + dc = MemoryDC() + + #x and y are the cursor for were the next item will be placed + #padx and pady are just local refs to padding.x and padding.y + x,y = padx, pady = self.padding + + sz = self.Size + + linkfont = self.linkfont + minorfont = self.minorfont + majorfont = self.majorfont + + licon = self.licon + link = self.link + + #Positiong the service icon and the status/close icon to the left or + # right respectivly + self.liconpos = Point(x, y) + self.riconpos = Point(sz.width - 16 - padx, y) + + #incriment x position to right of icon + x += licon.Width + padx + + #Which is taller, the label or the icon + h = max(majorfont.Height, 16) + + #Place title rect with the erlier calculated values and width whats left + # of the overall width for the width left for the label + self.titlerect = Rect(x, y, sz.width-4*padx-licon.Width-16,h) + + #incriment y to below the filled space + y += h+pady + + #Find the link width because it's needed to know how much room the + # reason rect needs + linkwidth = GetTextWidth(link,linkfont) + + #Determine the horizantal space left for the reason, then wrap it + reasonwidth = sz.width - x - (linkwidth + 2*padx) + reason = self.reason() if callable(self.reason) else self.reason + wrappedreason = self.wrappedreason = Wrap(reason, reasonwidth, minorfont, dc) + + #Get the height needed for the label from the wraped string + reasonheight = dc.GetMultiLineTextExtent(wrappedreason,minorfont)[1] + + #combine into a rectangle + self.reasonrect = Rect(x,y,reasonwidth,reasonheight) + + #Figure the rest of the link size information + self.linksize = Size(linkwidth,linkfont.LineHeight) + + #figure out the height the panel has to be in order to fit the content + h = max(y+reasonheight+pady,self.liconsize+2*padx) + + newSize = Size(self.MinSize.width,h) + if self.MinSize != newSize: + self.MinSize = newSize + + self.Refresh() + + def DetermineShow(self, later=False): + """ + Decides weither the panel should show or hide. + If 'later' is True, hiding is delayed by the pref connectionlist.online_linger + """ + account = self.account + + #Initialise or stop a delayed hide + if later and not self.Parent.ShowAll and self.Shown: + if not self.willhide: + self.willhide = wx.CallLater(pref('connectionlist.online_linger',1000),self.WillShowFalse) + else: + if self.willhide: + self.willhide.Stop() + self.willhide = None + + #figures if it should show if parent is set to ShowAll or Offline with a reason and not achknowledged or the state is not OFFLINE or ONLINE + shouldshow = self.Parent.ShowAll or ((account.state==ProtocolStatus.OFFLINE and account.offline_reason and not account.error_acked) or account.state not in (ProtocolStatus.OFFLINE,ProtocolStatus.ONLINE)) + self.Show(bool(shouldshow)) + self.DoLinkPos() + + def WillShowFalse(self): + """ + Callback for the delayed hide of DetermineShow + Hides the panel and takes the stepss to correct the layout for the + change + """ + self.Show(False) + self.Parent.SelfSize() + self.GrandParent.Parent.Layout() + + def OnSize(self,event): + """ + When the panel is width is change, runs CalcLayout to position elements + and figure new height + """ + event.Skip() + # Make sure the width actualy changed before calculating new positions and height + if self.lastsizedto.width != self.Size.width: + self.lastsizedto = self.Size + self.CalcLayout() + self.DoLinkPos() + + def DoLinkPos(self): + "Positions the link in the lower right hand corner of the panel." + sz = self.Size + linksz = self.linksize + padx,pady = self.padding + + # Link goes in lower right hand corner + self.linkrect = wx.Rect(sz.width - padx - linksz.width, sz.height - pady - linksz.height,*linksz) + + + def OnPaint(self,event): + dc = AutoBufferedPaintDC(self) + rect = RectS(self.Size) + self.bg.Draw(dc, rect) + + # Draw service icon + icon = self.licon if self.account.is_connected else self.licon.Greyed + + dc.DrawBitmapPoint(icon, self.liconpos, True) + + #The right icon can either be the close icon, the state icon, or nothing + #Shows the state icon if the list is in ShowAll or nothing if state is CONNECTING, AUTHENTICATING,INITIALIZING, or LOADING_CONTACT_LIST + # Otherwise it shows the close button + ricon = self.stateicon if self.Parent.ShowAll else (self.closeicon[self.buttonstate] if self.account.state not in ShowNothingStates else None) + if ricon: + rconpos = self.riconpos + Point(8 - ricon.Width // 2, 8 - ricon.Height // 2) + dc.DrawBitmapPoint(ricon, rconpos, True) + self.buttonshown = bool(ricon) + + # Draw the account name + dc.Font = self.majorfont + dc.TextForeground = self.majorfc + dc.DrawTruncatedText(self.title, self.titlerect) + + # Draws the offline reason or state + dc.Font = self.minorfont + dc.TextForeground = self.statefc + if callable(self.reason): + self.wrappedreason = Wrap(self.reason(), self.reasonrect.Width, self.minorfont, dc ) + dc.DrawLabel(self.wrappedreason,self.reasonrect) + + # Draw the link + dc.Font = self.linkfont + dc.TextForeground = self.linkfc + dc.DrawLabel(self.link,self.linkrect) + + def OnDestroy(self): + """ + When the panel is destroyed, this removes the panel as an observer + of the account + """ + self.account.remove_observer(self.WhenStateChange,'offline_reason') + self.account.remove_observer(self.WhenStateChange,'state') + + def OnLeftDown(self,event): + """ + Detects left mouse button down events for emulation of links and + buttons + """ + + event.Skip() + + # if the button is being hovered + if self.buttonstate == 1: + # it is now down + self.buttonstate = 2 + self.buttonisdown = True + self.Refresh() + + elif self.linkhovered: + self.linkdown = True + + def OnLeftUp(self,event): + """ + Detects left mouse button up events for emulation of links and + buttons + """ + + event.Skip() + + #if the buttons state is down, set it to idle and close the panel + # ackknowledging the the error + if self.buttonstate == 2: + self.buttonstate = 0 + self.account.error_acked = True + self.DetermineShow() + self.Parent.SelfSize() + + self.buttonisdown = False + + #If link is down + if self.linkdown: + #And if the pointer is over the link + if self.linkhovered: + #call the callback for the link + self.linkcallback() + self.linkdown = False + + self.OnMouseMotion(event) + + self.Refresh() + + def OnMouseMotion(self,event): + """ + Ensures mouse in and mouse out capture events occure, also handels + hover for the button and link + """ + + #If the left mouse button is not down, make sure the panel gets/losses + # capture accordingly + if not event.LeftIsDown(): + mouseisin = wx.FindWindowAtPointer() is self + + if mouseisin and not self.HasCapture(): + self.OnMouseIn(event) + + elif not mouseisin and self.HasCapture(): + self.OnMouseOut(event) + + #Handle button statethings on mouse over + if not self.Parent.ShowAll and self.HasCapture() and self.buttonshown: + bstate = self.buttonstate + bisdown = self.buttonisdown + mouseinb = wx.RectPS(self.riconpos,(16,16)).Contains(event.Position) + if bstate == 0 and mouseinb: + self.buttonstate = 2 if bisdown else 1 + self.Refresh() + elif bstate in (1,2) and not mouseinb: + self.buttonstate = 0 + self.Refresh() + + #handle link mouseover + mouseinl = self.linkhovered = self.linkrect.Contains(event.Position) + self.SetCursor(wx.StockCursor(wx.CURSOR_HAND if mouseinl else wx.CURSOR_DEFAULT)) + + def OnMouseIn(self,event): + """ + Captures the mouse when the cursor enters the panel + """ + if not self.HasCapture() and not event.LeftIsDown(): + self.CaptureMouse() + + def OnMouseOut(self,event): + """ + Releases the mouse when the cursor leaves the panle + """ + if not event.LeftIsDown(): + while self.HasCapture(): + self.ReleaseMouse() diff --git a/digsby/src/gui/uberwidgets/formattedinput.py b/digsby/src/gui/uberwidgets/formattedinput.py new file mode 100644 index 0000000..58cf192 --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput.py @@ -0,0 +1,987 @@ +''' +Formatted text input. +''' + +from __future__ import with_statement + +import config +from traceback import print_exc +from gui.prototypes.fontdropdown import FontDropDown + +import wx, cgi, cgui +from wx import FONTSTYLE_NORMAL, FONTFAMILY_DEFAULT, FONTWEIGHT_NORMAL, Font, BufferedPaintDC, RectS + +from common import profile, setpref, pref, prefprop +from util import Storage, try_this + +from gui import skin +from simplemenu import SimpleMenu,SimpleMenuItem +from gui.textutil import CopyFont +from gui.skin.skinparse import makeFont +from UberBar import UberBar +from UberButton import UberButton +from UberEmotiBox import UberEmotiBox + +from gui.textutil import default_font +from gui.validators import LengthLimit +from gui import clipboard + +import logging +log = logging.getLogger('formattedinput') + +OUTLINE_COLOR = wx.Colour(213, 213, 213) +DEFAULT_SIZES = ['8', '10', '12', '14', '18', '24', '36'] + +def FontFromFacename(facename): + return Font(10, FONTFAMILY_DEFAULT, FONTSTYLE_NORMAL, FONTWEIGHT_NORMAL, False, facename) + +def FamilyNameFromFont(font): + return font.GetFamilyString()[2:].lower() + +wxMSW = 'wxMSW' in wx.PlatformInfo + +if wxMSW: + from cgui import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED + + from gui.spellchecktextctrlmixin import SpellCheckTextCtrlMixin + + class FormattedExpandoTextCtrl(ExpandoTextCtrl, SpellCheckTextCtrlMixin): + def __init__(self, parent, style = 0, value = '', validator = wx.DefaultValidator): + ExpandoTextCtrl.__init__(self, parent, wx.ID_ANY, value, wx.DefaultPosition, wx.DefaultSize, style, validator, value) + + SpellCheckTextCtrlMixin.__init__(self) + + class FormattedTextCtrl(cgui.InputBox, SpellCheckTextCtrlMixin): + def __init__(self, parent, id = wx.ID_ANY, value = '', + pos = wx.DefaultPosition, + size = wx.DefaultSize, + style = 0, + validator = wx.DefaultValidator, + name = "FormatterTextCtrl"): + cgui.InputBox.__init__(self, parent, id, value, pos, size, style, validator, name) + + SpellCheckTextCtrlMixin.__init__(self) + + +else: + from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED + oldfontset = ExpandoTextCtrl.SetFont + ExpandoTextCtrl.SetFont = lambda s, *a, **k: wx.CallAfter(lambda: oldfontset(s, *a, **k)) + + class FormattedExpandoTextCtrl(ExpandoTextCtrl): + def __init__(self, *a, **k): + ExpandoTextCtrl.__init__(self, *a, **k) + + def HitTestSuggestions(self, *a, **k): + return -1, [] + + def GetWordAtPosition(self, *a, **k): + return None + + class FormattedTextCtrl(wx.TextCtrl): + def __init__(self, *a, **k): + wx.TextCtrl.__init__(self, *a, **k) + + + +def default_umenu(tc): + from gui.uberwidgets.umenu import UMenu + m = UMenu(tc) + + # spelling suggestions and options + if isinstance(tc, SpellCheckTextCtrlMixin): + tc.AddSuggestionsToMenu(m) + + m.AddItem(_('Copy'), id = wx.ID_COPY, callback = tc.Copy) + m.AddItem(_('Paste'), id = wx.ID_PASTE, callback = tc.Paste) + m.AddSep() + + from gui.toolbox import add_rtl_checkbox + add_rtl_checkbox(tc, m) + + return m + +TextAttrs = ('Alignment BackgroundColour Font LeftIndent LeftSubIndent ' + 'RightIndent Tabs TextColour').split() + +FontAttrs = 'pointSize family style weight underline faceName encoding'.split() + +def font_to_tuple(font): + args = [] + for a in FontAttrs: + if a == 'underline': + a = 'Underlined' + else: + a = str(a[0].upper() + a[1:]) # str.title lowers everything but the first + + if a == 'Encoding': + # FontEncoding is an enum, we must int it + args.append(int(getattr(font, a))) + else: + args.append(getattr(font, a)) + return tuple(args) + + +if getattr(wx, 'WXPY', False): + # + # This is a hack for the WXPY bindings until it deals with enums in a more + # sane way. + # + def tuple_to_font(t): + t = list(t) + if len(t) >= 7: + t[6] = wx.FontEncoding(t[6]) + + return Font(*t) + +else: + def tuple_to_font(t): + return Font(*t) + +wx.Colour.__hash__ = lambda c: hash((c[i] for i in xrange(4))) + +#spellcheck: wx.TE_RICH2|wx.TE_MULTILINE| wx.TE_CHARWRAP | wx.NO_BORDER | wx.WANTS_CHARS | wx.TE_NOHIDESEL +txtFlags = (wx.TE_MULTILINE | wx.TE_CHARWRAP | wx.NO_BORDER | wx.WANTS_CHARS | wx.TE_NOHIDESEL) + +def default_msg_font(): + return try_this(lambda: makeFont('Arial 10'), default_font()) + +def load_pref_style(prefname): + + try: + stylepref = pref(prefname) + + if isinstance(stylepref, basestring): + style = Storage(Font = makeFont(stylepref)) + else: + style = Storage(stylepref) + except Exception: + print_exc() + style = {} + + if type(style.Font) is tuple: + try: + font = tuple_to_font(style.Font) + except Exception: + print_exc() + font = default_msg_font() + else: + font = style.Font + fgc = try_this(lambda: wx.Colour(*style.TextColour), None) or wx.BLACK + bgc = try_this(lambda: wx.Colour(*style.BackgroundColour), None) or wx.WHITE + + return font, fgc, bgc + +def font_attrs_from_storage(s): + font = Font(s['size'], + FONTFAMILY_DEFAULT, + wx.FONTSTYLE_ITALIC if s['italic'] else wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_BOLD if s['bold'] else wx.FONTWEIGHT_NORMAL, + s['underline'], + s['face']) + + fgc = s['foregroundcolor'] + bgc = s['backgroundcolor'] + + return font, fgc, bgc + +def textattr_from_storage(s): + font, fgc, bgc = font_attrs_from_storage(s) + fgc = try_this(lambda: wx.Colour(fgc), None) or wx.BLACK + bgc = try_this(lambda: wx.Colour(bgc), None) or wx.WHITE + return wx.TextAttr(fgc, bgc, font) + +def make_format_storage(font, fgc, bgc): + return Storage(backgroundcolor = bgc, + foregroundcolor = fgc, + face = font.FaceName, + size = font.PointSize, + underline = font.Underlined, + bold = font.Weight == wx.BOLD, + italic = font.Style == wx.ITALIC, + family = FamilyNameFromFont(font), + ) + +def get_default_format(prefname = 'messaging.default_style'): + ''' + Returns a format storage for the current pref. + ''' + + return make_format_storage(*load_pref_style(prefname)) + +class FormattedInput(cgui.SimplePanel): + ''' + An RTF2 based input box with a formatting bar and auto sizing, + ''' + def __init__(self, + parent, + pos = wx.DefaultPosition, + size = wx.DefaultSize, + value = '', + entercallback = None, + font = True, + pointsize = True, + bold = True, + italic = True, + underline = True, + textcolor = True, + bgcolor = True, + sizes = DEFAULT_SIZES, + emots = None, + + singleFormat = False, + show_formatting_bar = True, + rtl_input = False, + prefmode = False, + outlined = False, + aimprofile = False, + autosize = True, + default_skin = False, + + validator = None, + + format = None,): + + cgui.SimplePanel.__init__(self, parent) + + self.prefmode = prefmode + self.aimprofile = aimprofile + self.autosize = autosize + + self.UseAppDefaultSkin = (self.prefmode or self.aimprofile) or default_skin + + self.initover = False + self.entercallback = entercallback + + self.sizes = sizes + self.emots = emots + + # create the Formatting bar, buttons, and menus + self.Sizer = wx.BoxSizer(wx.VERTICAL) + csizer = self.csizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(csizer, 1, wx.EXPAND | wx.ALL, 1 if outlined else 0) + + self.UpdateSkin() + + if validator is None: + validator = LengthLimit(20480) + + if self.autosize: + tc = self.tc = FormattedExpandoTextCtrl(self, style=txtFlags, value = value, validator=validator) + tc.Bind(EVT_ETC_LAYOUT_NEEDED, self.expandEvent) + tc.Bind(wx.EVT_SIZE, self.OnSize) + if config.platform == 'win': + tc.Bind(wx.EVT_COMMAND_TEXT_PASTE, self.OnPaste) + tc.SetMaxHeight(100) + + loadedHeight = pref('conversation_window.input_base_height') + baseHeight = self.tc.GetCharHeight() + getattr(self.tc, 'GetExtraHeight', lambda: 0)() + + tc.SetMinHeight(loadedHeight or baseHeight) + + if loadedHeight: + log.info("input_base_height loaded as %s", loadedHeight) + else: + log.info("input_base_height not used, set to baseHeight as %s", baseHeight) + + tc.MinSize = wx.Size(tc.MinSize.width, tc.GetMinHeight()) +# wx.CallAfter(self.tc.HideScrollbars) + self.expandEvent() + else: + tc = self.tc = FormattedTextCtrl(self, style = txtFlags, value = value, validator=validator) + + self.tc.LayoutDirection = wx.Layout_RightToLeft if rtl_input else wx.Layout_LeftToRight + + tc.Bind(wx.EVT_CONTEXT_MENU, lambda e: default_umenu(tc).PopupMenu(event = e)) + + # bind textfield events + tc.Bind(wx.EVT_KEY_DOWN, self.OnKey) + + self.shownbuttons = dict( + font = font, + pointsize = pointsize, + bold = bold, + italic = italic, + underline = underline, + textcolor = textcolor, + bgcolor = bgcolor) + + + if config.platform != 'mac' and show_formatting_bar: + self.construct_formatting_bar() + else: + self.formatbar = None + + csizer.Add(self.tc, 1, wx.EXPAND) + self.single_format = singleFormat + + self.initover = True + + if format is None: + wx.CallAfter(self.LoadStyle) + profile.prefs.add_observer(self.WhenDefaultLayoutChange, 'messaging.default_style') + else: + self.SetFormatStorage(format) + + Bind = self.Bind + if outlined: + Bind(wx.EVT_PAINT,self.OnPaint) + + Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) + + def OnPaste(self, event): + text = clipboard.get_text() + if text is None: + return + + # our rich text control adds a newline to the end of copied text-- + # strip one out if it's there + if text.endswith('\n'): + text = text[:-1] + + self.tc.WriteText(text) + + def OnSize(self, event): + # this is an ugly hack, but unavoidable. For some reason, whenever this is called it + # resets the Mac spell checker state. (i.e. all misspelled words are no longer underlined) + # this code makes sure that doesn't happen. + if config.platform == 'mac': + self.tc.OnTextChanged(event) + self.tc.MacCheckSpelling(True) + + def BindSplitter(self, splitter): + splitter.Bind(wx.EVT_LEFT_DOWN, self.OnSplitterStart) + splitter.Bind(wx.EVT_LEFT_UP, self.OnSplitterSet) + self.sizeID = wx.EVT_SIZE + #SWIG HAX: In Robin's bindings, wx.EVT_SIZE is a function, and wx.wxEVT_SIZE is the int id + if not config.platform == "win": + self.sizeID = wx.wxEVT_SIZE + splitter.Connect(splitter.Id, splitter.Id, self.sizeID, self.OnFirstSplitterSize) + + self.splitter = splitter + + def OnFirstSplitterSize(self, event): + + #HAX: make sure the splitter lays out the first time it get's a real size + + event.Skip() + + splitter = self.splitter + + if splitter.Size.height and splitter.Size.width: + splitter.Disconnect(splitter.Id, splitter.Id, self.sizeID) + wx.CallAfter(self.expandEvent) + + def OnSplitterStart(self, event): + event.Skip() + tc = self.tc + + baseh = min(tc.GetMaxHeight(), tc.GetCharHeight() * tc.GetNumLines() + tc.GetExtraHeight()) + + tc.MinSize = wx.Size(tc.MinSize.width, baseh) + tc.SetMinHeight(baseh) + self.MinSize = wx.Size(-1, (self.formatbar.Size.height if self.FormattingBarIsShown() else 0) + tc.MinSize.height) + + def OnSplitterSet(self,event): + event.Skip() + tc = self.tc + + baseh = min(tc.GetMaxHeight(), self.tc.GetCharHeight() * self.tc.GetNumLines() + self.tc.GetExtraHeight()) + h = tc.Size.height if tc.Size.height != baseh else 0 + + log.info("input_base_height set to %s", h) + + setpref('conversation_window.input_base_height', h) + tc.SetMinHeight(h) + + def OnPaint(self, event): + 'Draws an outline around the control.' + dc = BufferedPaintDC(self) + dc.Brush = wx.TRANSPARENT_BRUSH + dc.Pen = wx.Pen(OUTLINE_COLOR) + dc.DrawRectangleRect(RectS(self.ClientSize)) + + def OnDestroy(self, event): + event.Skip() + profile.prefs.remove_observer(self.WhenDefaultLayoutChange, 'messaging.default_style') + + def expandEvent(self, e=None): + height = (self.formatbar.Size.height if self.FormattingBarIsShown() else 0) + \ + max(self.tc.MinSize.height, self.tc.GetMinHeight()) + + self.MinSize = wx.Size(-1, height) + + # If BindSplitter was called, we have .splitter + if hasattr(self, 'splitter'): + self.splitter.SetSashPosition(self.splitter.ClientSize.height - height) + + def on_fbar_context_menu(self, e): + # TODO: why isn't the IM window's formatting bar its own subclass!? + + from gui.uberwidgets.umenu import UMenu + m = UMenu(self) + m.AddItem(_('Hide Formatting Bar'), callback = lambda: wx.CallAfter(setpref, 'messaging.show_formatting_bar', False)) + m.PopupMenu() + + def construct_formatting_bar(self): + fb = self.formatbar = UberBar(self, skinkey = self.toolbarskin, alignment = wx.ALIGN_LEFT) + + if not self.prefmode and not self.aimprofile: + fb.Bind(wx.EVT_CONTEXT_MENU, self.on_fbar_context_menu) + + self.fontdd = FontDropDown(fb, skinkey = fb.buttonskin) + self.fontdd.Bind(wx.EVT_COMMAND_CHOICE_SELECTED, self.OnFontSelect) + + self.msize = SimpleMenu(self, self.menuskin, maxheight = 10) # TODO: max and min width? + self.msize.SetItems(self.SetSizes(self.sizes)) + self.bsize = UberButton(fb, -1, '10', menu = self.msize, type = 'menu') + self.bsize.SetStaticWidth(self.sizeddwidth) + self.msize.SetWidth(self.sizeddwidth) + self.bbold = UberButton(fb, -1, icon = self.bicon, type = 'toggle') + self.bbold.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleBold) + + self.bitalic = UberButton(fb, -1, icon = self.iicon, type="toggle") + self.bitalic.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleItalic) + + self.bunderline = UberButton(fb, -1, icon = self.uicon, type="toggle") + self.bunderline.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleUnderline) + + self.bcolor = UberButton(fb, -1, icon = self.fcicon ) + self.bcolor.Bind(wx.EVT_BUTTON, self.OnColor) + + self.bbgcolor = UberButton(fb,-1, icon = self.bcicon) + self.bbgcolor.Bind(wx.EVT_BUTTON, self.OnBGColor) + + self.bemote = UberButton(fb, -1, icon = self.eicon) + self.bemote.Bind(wx.EVT_BUTTON, self.on_emote_button) + + self.EnableFormattingButtons(getattr(self, 'formatting_enabled', True)) + + #Add all the buttons to the formating bar + fb.AddMany([self.fontdd, + self.bsize, + self.bbold, + self.bitalic, + self.bunderline, + self.bcolor, + self.bbgcolor, + self.bemote]) + + self.csizer.Insert(0, fb, 0, wx.EXPAND, 0) + self.ShowButtons(**self.shownbuttons) + + self.UpdateDisplay() + + self.formatbar.Bind(wx.EVT_SIZE, lambda e: (e.Skip(), wx.CallAfter(self.expandEvent))) + + def on_emote_button(self, e): + self.display_emotibox(self.bemote.ScreenRect) + + def display_emotibox(self, rect): + ebox = self.get_emotibox() + # position and display the emotibox + ebox.Display(rect) + + def get_emotibox(self): + 'Shares the emoticon box between all instances of this class.' + + b = None + old_name, new_name = getattr(self, '_emotipack_name', None), pref('appearance.conversations.emoticons.pack', type = unicode, default = u'default') + self._emotipack_name = new_name + + try: + b = self.__class__.emotibox + if not wx.IsDestroyed(b): + if old_name != new_name: + b.Destroy() + elif b.Parent is not self: + b.Reparent(self) + + except AttributeError: + pass + + if b is None or wx.IsDestroyed(b): + from gui.imwin.emoticons import get_emoticon_bitmaps + b = self.__class__.emotibox = UberEmotiBox(self, get_emoticon_bitmaps(self._emotipack_name), self.tc, maxwidth = 12) + else: + b.SetTextCtrl(self.tc) + + return b + + def UpdateSkin(self): + s = lambda name, d = None: skin.get('%sFormattingBar.%s' % ('AppDefaults.' if self.UseAppDefaultSkin else '',name),d) + + iconsize = self.iconsize = s('iconsize') + + icons = s('icons').get + + bicon = self.bicon = icons('bold').Resized(iconsize) + iicon = self.iicon = icons('italic').Resized(iconsize) + uicon = self.uicon = icons('underline').Resized(iconsize) + fcicon = self.fcicon = icons('foregroundcolor').Resized(iconsize) + bcicon = self.bcicon = icons('backgroundcolor').Resized(iconsize) + eicon = self.eicon = icons('emote').Resized(iconsize) + + fontddwidth = self.fontddwidth = s('FontDropDownWidth') + sizeddwidth = self.sizeddwidth = s('SizeDropDownWidth') + menuskin = self.menuskin = s('MenuSkin', None) + toolbarskin = self.toolbarskin = s('toolbarskin', None) + + if self.initover and getattr(self, 'formatbar', None) is not None: + self.formatbar.SetSkinKey(toolbarskin) + # new font drop down does not have SetMinWidth + # self.fontdd.SetMinWidth(fontddwidth) + self.bsize.SetStaticWidth(sizeddwidth) + self.msize.SetWidth(sizeddwidth) + self.bbold.SetIcon(bicon) + self.bitalic.SetIcon(iicon) + self.bunderline.SetIcon(uicon) + self.bcolor.SetIcon(fcicon) + self.bbgcolor.SetIcon(bcicon) + self.bemote.SetIcon(eicon) + + wx.CallAfter(self.Layout) + + def __repr__(self): + try: + return '<%s under %r>' % (self.__class__.__name__, self.Parent) + except Exception: + return object.__repr__(self) + + def SetSingleFormat(self, singleformat = True): + ''' + Sets this control to only have one style for all of its contents. The + style used is the first one found. + ''' + + return + + def GetSingleFormat(self): + return True#self.single_format + + SingleFormat = property(GetSingleFormat, SetSingleFormat, None, doc = SetSingleFormat.__doc__) + + def SaveStyle(self): + style = self.GetStyleAsDict() + from pprint import pformat + print 'saving style:\n%s' % pformat(style) + setpref('profile.formatting' if self.aimprofile else 'messaging.default_style',style) + + def GetStyleAsDict(self): + + tc = self.tc + + return dict(BackgroundColour = tuple(tc.BackgroundColour), + TextColour = tuple(tc.ForegroundColour), + Font = font_to_tuple(tc.Font)) + + def WhenDefaultLayoutChange(self,src,pref,old,new): + +# if self.GetStyleAsDict() == old: + self.LoadStyle() + + def LoadStyle(self): + self.SetFormat(*load_pref_style('profile.formatting' if self.aimprofile else 'messaging.default_style')) + + + def SetFormatStorage(self, format_storage): + return self.SetFormat(*font_attrs_from_storage(format_storage)) + + def SetFormat(self, font, fgc, bgc): + tc = self.tc + tc.Font = font + tc.SetFont(font) + tc.ForegroundColour = wx.BLACK + tc.ForegroundColour = wx.Color(*fgc) + tc.BackgroundColour = wx.Color(*bgc) + self.UpdateDisplay() + + def ShowFormattingBar(self, val): + 'Shows or hides the formatting bar.' + + if val and self.formatbar is None: + self.construct_formatting_bar() + + if self.formatbar is not None: + self.csizer.Show(self.formatbar, val, True) + self.Layout() + + if self.autosize: + self.expandEvent() + + def FormattingBarIsShown(self): + if getattr(self, 'formatbar', None) is not None: + return self.formatbar.IsShown() + + return False + + def EnableFormattingButtons(self, enable): + self.formatting_enabled = enable + + if hasattr(self, 'fontdd'): # the formatting bar might not have been constructed yet + self.fontdd.Enable(enable) + self.bsize.Enable(enable) + self.bbold.Enable(enable) + self.bitalic.Enable(enable) + self.bunderline.Enable(enable) + self.bcolor.Enable(enable) + self.bbgcolor.Enable(enable) + + def ShowButtons(self, + font = True, + pointsize = True, + bold = True, + italic = True, + underline = True, + textcolor = True, + bgcolor = True): + + 'Show or hide each button on the format bar.' + + self.fontdd.Show(font) + self.bsize.Show(pointsize) + self.bbold.Show(bold) + self.bitalic.Show(italic) + self.bunderline.Show(underline) + self.bcolor.Show(textcolor) + self.bbgcolor.Show(bgcolor) + #self.bemote.Show(not self.prefmode) + + def GetStyleAsStorage(self): + tc = self.tc + font = tc.Font + + return Storage( + backgroundcolor = tuple(tc.BackgroundColour), + foregroundcolor = tuple(tc.ForegroundColour), + family = FamilyNameFromFont(font), + face = font.FaceName, + size = font.PointSize, + underline = font.Underlined, + bold = font.Weight == wx.BOLD, + italic = font.Style == wx.ITALIC) + + + @property + def Format(self): + return self.GetStyleAsStorage() + + + def GetHTML(self): + 'Does the necessary steps to convert the textfield value into HTML.' + + s = '' + value = self.tc.Value + + s = cgi.escape(value) + + s = s.replace('\n','
').replace('\t','  ') + return s + + HTML = property(GetHTML) + + def GetValue(self): + 'Returns the string in the textfield.' + + return self.tc.GetValue() + + def SetValue(self, value): + 'Sets the string in the textfield.' + + return self.tc.SetValue(value) + + Value = property(GetValue, SetValue) + + @property + def FontData(self): + d = wx.FontData() + tc = self.tc + d.SetInitialFont(tc.GetFont()) + d.SetColour(tc.GetForegroundColour()) + return d + + def ShowModalFontDialog(self, e = None): + ''' + Uses the native Mac font dialog to allow the user to select a font + and a color. + ''' + diag = wx.FontDialog(self, self.FontData) + if wx.ID_OK == diag.ShowModal(): + font_data = diag.GetFontData() + font = font_data.GetChosenFont() + color = font_data.GetColour() + + tc = self.tc + if color.IsOk(): + tc.ForegroundColour = color + if font.IsOk(): + tc.Font = font + + if self.prefmode or self.aimprofile: + wx.CallAfter(self.SaveStyle) + + tc.Refresh() + tc.SetFocus() + + def CreateFontButton(self, parent, label = _('Set Font...')): + ''' + Create a small button that will spawn a font dialog for setting + the properties of this text control. + ''' + font_button = wx.Button(parent, -1, label) + font_button.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + font_button.Bind(wx.EVT_BUTTON, self.ShowModalFontDialog) + return font_button + + def OnColor(self, event = None): + "Calls the color chooser for setting font color." + + newcolor = wx.GetColourFromUser(self, self.tc.ForegroundColour, _('Choose a foreground color')) + self.update_color(newcolor) + + def update_color(self, newcolor): + + if newcolor.IsOk(): + self.tc.ForegroundColour = newcolor + + if self.prefmode or self.aimprofile: + wx.CallAfter(self.SaveStyle) + + #Insure the focus goes back to the TextField + self.tc.Refresh() + self.tc.SetFocus() + + + def OnBGColor(self,event): + 'Calls the color chooser for setting font background color.' + + newcolor = wx.GetColourFromUser(self, self.tc.BackgroundColour, _('Choose a background color')) + self.update_bgcolor(newcolor) + + def update_bgcolor(self, newcolor): + + if newcolor.IsOk(): + self.tc.BackgroundColour = newcolor + + + if self.prefmode or self.aimprofile: + wx.CallAfter(self.SaveStyle) + + self.tc.Refresh() + self.tc.SetFocus() + + + def ApplyStyleGUIless(self, flag=None): + tc = self.tc + + font = tc.GetFont() + fontsize = font.GetPointSize() + + weight = font.Weight == wx.FONTWEIGHT_BOLD + style = font.Style == wx.FONTSTYLE_ITALIC + underline = font.Underlined + + if flag == 'bold': weight = not weight + if flag == 'italic': style = not style + if flag == 'underline': underline = not underline + + font= CopyFont(font, + pointSize = fontsize, + style = wx.ITALIC if style else wx.NORMAL, + weight = wx.FONTWEIGHT_BOLD if weight else wx.NORMAL, + underline = underline) + + # setting the font twices fixes a bug. + tc.Font = font + tc.SetFont(font) + + fgc = tc.ForegroundColour + tc.ForegroundColour = wx.BLACK + tc.ForegroundColour = fgc + + if self.prefmode or self.aimprofile: + wx.CallAfter(self.SaveStyle) + + self.tc.SetFocus() + + shiftToSend = prefprop("messaging.shift_to_send", False) + + def OnKey(self, event): + """ + This detects key presses, runs entercallback if enter or return is pressed + Any other key continues as normal, then refreshes the font and size info + """ + keycode = event.KeyCode + + + shiftToSend = self.shiftToSend + hasModifiers = event.HasModifiers() + shiftIsDown = event.Modifiers == wx.MOD_SHIFT + ctrlIsDown = event.Modifiers == wx.MOD_CONTROL + + if keycode in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): + # if there is a enter callback and no modifiers are down or if + # shift should send and shift is down, call the callback + if self.entercallback and \ + (not (shiftToSend or hasModifiers or shiftIsDown) or \ + (shiftToSend and shiftIsDown)): + return self.entercallback(self) + else: + event.Skip() + + if not ctrlIsDown: + return event.Skip() + + if wxMSW: + # make Ctrl+R and Ctrl+L modify the RTL setting of the rich edit + # control, not just the alignment. + from gui.toolbox import set_rich_layoutdirection + if keycode == ord('R'): + set_rich_layoutdirection(self.tc, wx.Layout_RightToLeft) + return + elif keycode == ord('L'): + set_rich_layoutdirection(self.tc, wx.Layout_LeftToRight) + return + + if keycode == ord('B'): + if hasattr(self, 'bbold'): + self.bbold.Active() + self.ApplyStyle() + else: + self.ApplyStyleGUIless('bold') + return + + elif keycode == ord('I'): + if hasattr(self, 'bitalic'): + self.bitalic.Active() + self.ApplyStyle() + else: + self.ApplyStyleGUIless('italic') + return + + elif keycode == ord('U'): + if hasattr(self, 'bunderline'): + self.bunderline.Active() + self.ApplyStyle() + else: + self.ApplyStyleGUIless('underline') + return + + event.Skip() + + def Clear(self): + """ + Clears the text from the text field + """ + tc = self.tc + + if 'wxMSW' in wx.PlatformInfo: + # Clear() removes any alignment flags that are set in the text control, so + # reset them + alignment = cgui.GetRichEditParagraphAlignment(tc) + tc.Clear() + if cgui.GetRichEditParagraphAlignment(tc) != alignment: + cgui.SetRichEditParagraphAlignment(tc, alignment) + else: + tc.Clear() + + def UpdateDisplay(self): + """ + Update the values of Font and Size buttons and the states of + the bold, italic, and underline buttons based on selection or + cursor posirion + """ + with self.tc.Frozen(): + + tc = self.tc + + font = tc.GetFont() + fontface = font.GetFaceName() + fontsize = font.GetPointSize() + + + bold = font.Weight == wx.FONTWEIGHT_BOLD + italic = font.Style == wx.FONTSTYLE_ITALIC + underline = font.Underlined + + # Set Bold, Italic, and Underline buttons + if self.formatbar is not None: + self.bbold.Active(bold) + self.bitalic.Active(italic) + self.bunderline.Active(underline) + + i = self.fontdd.FindString(fontface, False) + self.fontdd.SetSelection(i) + + self.bsize.SetLabel(str(fontsize)) + self.bsize.Refresh() + + def ToggleBold(self, event): + """ + Toggles the bold value of the selection + or calls Applystyle in no selection + """ + self.ApplyStyle() + + def ToggleItalic(self, event): + """ + Toggles the italics state of the selection + or calls Applystyle in no selection + """ + self.ApplyStyle() + + def ToggleUnderline(self,event): + 'Toggles underline state of selection or calls Applystyle in no selection.' + + self.ApplyStyle() + + def ApplyStyle(self): + 'Sets the style at the cursor.' + + tc = self.tc + + style = self.bitalic.active + weight = self.bbold.active + underline = self.bunderline.active + + font= CopyFont(self.fontdd.GetClientData(self.fontdd.GetSelection()), + pointSize = int(self.bsize.label), + style = wx.ITALIC if style else wx.NORMAL, + weight = wx.FONTWEIGHT_BOLD if weight else wx.NORMAL, + underline = underline) + + tc.Font = font + tc.SetFont(font) + + fgc = tc.ForegroundColour + tc.ForegroundColour = wx.BLACK + tc.ForegroundColour=fgc + + if self.prefmode or self.aimprofile: + wx.CallAfter(self.SaveStyle) + + self.tc.SetFocus() + + def OnFontSelect(self, event): + """ + Updates the button to the new font and applies it to the selection + or calls ApplyStlye + """ + self.ApplyStyle() + + def OnSizeMenu(self,item): + """ + Updates the Size button to the new size and apllies it to the selection + or calls ApplyStyle + """ + self.bsize.SetLabel(item.content[0]) + + self.ApplyStyle() + + def SetSizes(self, sizes = DEFAULT_SIZES): + """ + Sets the list of selectable sizes + If not set sizes default to ['8', '10', '12', '14', '18', '24', '36'] + """ + return [SimpleMenuItem([size], method = self.OnSizeMenu) for size in sizes] + diff --git a/digsby/src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py b/digsby/src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py new file mode 100644 index 0000000..eb6cbfe --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/FormattedExpandoTextCtrl.py @@ -0,0 +1,234 @@ +''' +Prepares FormattedExpandoTextCtrl and FormattedTextCtrl dependant on platform + +On Windows includes spellcheck mixin while on Mac it does not because spellcheck is provided by the OS + +''' + +from gui.uberwidgets.formattedinput2.fromattedinputevents import TextFormatChangedEvent +import wx + +wxMSW = 'wxMSW' in wx.PlatformInfo +wxMac = 'wxMac' in wx.PlatformInfo + +formattedstyle = (wx.TE_RICH2 | wx.TE_MULTILINE | wx.TE_CHARWRAP | wx.NO_BORDER | wx.WANTS_CHARS | wx.TE_NOHIDESEL) + +from gui.textutil import default_font +from gui.uberwidgets.umenu import UMenu +from util.primitives.fmtstr import fmtstr, FormattingException + +from cgui import InputBox + +from cgui import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED + +FONT_FLAGS = (wx.TEXT_ATTR_FONT_FACE | wx.TEXT_ATTR_FONT_SIZE | wx.TEXT_ATTR_FONT_WEIGHT | wx.TEXT_ATTR_FONT_ITALIC | wx.TEXT_ATTR_FONT_UNDERLINE) + +class FormattingInterface(object): + + ''' + Interface to add text formatting related methods to a TextField object + ''' + + SetFormat = None + + def default_attrs(self): + return wx.TextAttr(wx.BLACK, wx.WHITE, default_font()) + + def __init__(self, multiFormat = True, format = None): + self.MultiFormat(multiFormat) + self.BindEvents() + + self.SetFormat(format if format is not None else self.default_attrs()) + + def GetFormat(self): + # FIXME: We need to get the style flags working right under OS X Cocoa + # Right now it seems you need to have at least + attrs = self.GetStyle(self.GetInsertionPoint())[1] + if attrs.IsDefault(): # this will return wx.NullFont, not a very useful thing to use + return self.default_attrs() + + return attrs + + def SetFormat_Single(self, textattr): + ''' + Set format for the entire text content + ''' + self.SetStyle(0, self.GetLastPosition(), textattr) + self.SetDefaultStyle(textattr) + + def SetFormat_Multi(self, textattr): + ''' + Set format for just the current selection + ''' + start, end = self.GetSelection() + self.SetStyle(start, end, textattr) + + + def MultiFormat(self, multi): + ''' + Turn MultiFormat support for a field on and off + ''' + self.isMultiFormat = multi + if multi: + self.SetFormat = self.SetFormat_Multi + else: + self.SetFormat = self.SetFormat_Single + + def ApplyStyle(self, **format): + ''' + Set the font style using human readable key words and simple values + + @param textcolor: wx.Color + @param bgcolor: wx.Color + @param facename: String + @param pointsize: int + @param bold: Bool + @param italic: Bool + @param underline: Bool + ''' + + textattr = self.GetFormat() + font = textattr.GetFont() + + flags = 0 + + if 'textcolor' in format: + flags |= wx.TEXT_ATTR_TEXT_COLOUR + textattr.SetTextColour(format['textcolor']) + if 'bgcolor' in format: + flags |= wx.TEXT_ATTR_BACKGROUND_COLOUR + textattr.SetBackgroundColour(format['bgcolor']) + + if 'facename' in format: + flags |= wx.TEXT_ATTR_FONT_FACE + font.SetFaceName(format['facename']) + if 'pointsize' in format: + flags |= wx.TEXT_ATTR_FONT_SIZE + font.SetPointSize(format['pointsize']) + + if 'bold' in format: + flags |= wx.TEXT_ATTR_FONT_WEIGHT + font.SetWeight(wx.FONTWEIGHT_BOLD if format['bold'] else wx.NORMAL,) + if 'italic' in format: + flags |= wx.TEXT_ATTR_FONT_ITALIC + font.SetStyle(wx.FONTSTYLE_ITALIC if format['italic'] else wx.FONTSTYLE_NORMAL) + if 'underline' in format: + flags |= wx.TEXT_ATTR_FONT_UNDERLINE + font.SetUnderlined(format['underline']) + + if flags & FONT_FLAGS: + textattr.SetFont(font) + + textattr.SetFlags(flags) + + self.SetFormat(textattr) + self.SetFocus() + + self.AddPendingEvent(TextFormatChangedEvent(self.Id, EventObject = self)) + + def GenMenu(self): + m = UMenu(self) + + # spelling suggestions and options + if isinstance(self, SpellCheckTextCtrlMixin): + if self.AddSuggestionsToMenu(m): + m.AddSep() + + m.AddItem(_('Cut'), id = wx.ID_CUT, callback = self.Cut) + m.AddItem(_('Copy'), id = wx.ID_COPY, callback = self.Copy) + m.AddItem(_('Paste'), id = wx.ID_PASTE, callback = self.Paste) + m.AddSep() + m.AddItem(_('Select All'), id = wx.ID_SELECTALL, callback = lambda: self.SetSelection(0, self.GetLastPosition())) + m.AddSep() + + from gui.toolbox import add_rtl_checkbox + add_rtl_checkbox(self, m) + + return m + + + def BindEvents(self): + def OnContextMenu(event): + pt = self.ScreenToClient(wx.GetMousePosition()) + ht = self.HitTest(pt) + self.SetInsertionPoint(self.XYToPosition(ht[1], ht[2])) + menu = self.GenMenu() + menu.PopupMenu() + + + Bind = self.Bind + if not wxMac: + Bind(wx.EVT_CONTEXT_MENU, OnContextMenu) + +def _expand_event(self): + if wx.IsDestroyed(self): + return + + self.AddPendingEvent(wx.CommandEvent(EVT_ETC_LAYOUT_NEEDED, self.Id)) + +if wxMSW: + from gui.spellchecktextctrlmixin import SpellCheckTextCtrlMixin + + class FormattedExpandoTextCtrl(ExpandoTextCtrl, SpellCheckTextCtrlMixin, FormattingInterface): + def __init__(self, parent, style = 0, value = '', multiFormat = True, format = None, validator = wx.DefaultValidator): + ExpandoTextCtrl.__init__(self, parent, wx.ID_ANY, value, wx.DefaultPosition, wx.DefaultSize, style | formattedstyle, validator, value) + FormattingInterface.__init__(self, multiFormat, format) + SpellCheckTextCtrlMixin.__init__(self) + + def ForceExpandEvent(self): + _expand_event(self) + + class FormattedTextCtrl(InputBox, SpellCheckTextCtrlMixin, FormattingInterface): + def __init__(self, parent, style = 0, value = '', multiFormat = True, format = None, validator = wx.DefaultValidator): + InputBox.__init__(self, parent, wx.ID_ANY, value, wx.DefaultPosition, wx.DefaultSize, style | formattedstyle, validator, value) + FormattingInterface.__init__(self, multiFormat, format) + SpellCheckTextCtrlMixin.__init__(self) + +else: + class FormattedExpandoTextCtrl(ExpandoTextCtrl, FormattingInterface): + def __init__(self, parent, style = 0, value = '', multiFormat = True, format = None, validator = wx.DefaultValidator): + ExpandoTextCtrl.__init__(self, parent, wx.ID_ANY, value, wx.DefaultPosition, wx.DefaultSize, style | formattedstyle, validator, value) + FormattingInterface.__init__(self, multiFormat, format) + + def HitTestSuggestions(self, *a, **k): + return -1, [] + + def GetWordAtPosition(self, *a, **k): + return None + + def GetReqHeight(self): + return self.GetBestSize().y + + def ForceExpandEvent(self): + _expand_event(self) + + class FormattedTextCtrl(InputBox, FormattingInterface): + def __init__(self, parent, style = 0, value = '', multiFormat = True, format = None, validator = wx.DefaultValidator): + InputBox.__init__(self, parent, wx.ID_ANY, value, wx.DefaultPosition, wx.DefaultSize, style | formattedstyle, validator, value) + FormattingInterface.__init__(self, multiFormat, format) + + def GetReqHeight(self): + return self.GetBestSize().y + +def add_rtf_methods(cls): + def GetFormattedValue(self): + if wxMSW: + rtf, plaintext = cls.GetRTF(self), cls.GetValue(self) + return fmtstr(rtf=rtf, plaintext=plaintext) + else: + return fmtstr(plaintext=cls.GetValue(self)) + + cls.GetFormattedValue = GetFormattedValue + + def SetFormattedValue(self, fmtstr): + try: + rtf = fmtstr.format_as('rtf') + except FormattingException: + cls.SetValue(self, fmtstr.format_as('plaintext')) + else: + cls.SetRTF(self, rtf) + + cls.SetFormattedValue = SetFormattedValue + +add_rtf_methods(FormattedExpandoTextCtrl) +add_rtf_methods(FormattedTextCtrl) diff --git a/digsby/src/gui/uberwidgets/formattedinput2/__init__.py b/digsby/src/gui/uberwidgets/formattedinput2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/uberwidgets/formattedinput2/fontutil.py b/digsby/src/gui/uberwidgets/formattedinput2/fontutil.py new file mode 100644 index 0000000..6f60f0a --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/fontutil.py @@ -0,0 +1,86 @@ +''' +Various util fictions to convert back and forth between wxFont/wxStyle objects and various python data structures +''' + +from util import Storage, try_this +import wx +from wx import Font, FONTFAMILY_DEFAULT, FONTSTYLE_NORMAL, FONTWEIGHT_NORMAL + + +def FontFromFacename(facename): + return Font(10, FONTFAMILY_DEFAULT, FONTSTYLE_NORMAL, FONTWEIGHT_NORMAL, False, facename) + +def FamilyNameFromFont(font): + return font.GetFamilyString()[2:].lower() + +FontAttrs = 'pointSize family style weight underline faceName encoding'.split() + +def FontToTuple(font): + args = [] + for a in FontAttrs: + if a == 'underline': + a = 'Underlined' + else: + a = str(a[0].upper() + a[1:]) + + if a == 'Encoding': + # FontEncoding is an enum, we must int it + args.append(int(getattr(font, a))) + else: + args.append(getattr(font, a)) + return tuple(args) + +if getattr(wx, 'WXPY', False): + # + # This is a hack for the WXPY bindings until it deals with enums in a more + # sane way. + # + def TupleToFont(t): + t = list(t) + if len(t) >= 7: + t[6] = wx.FontEncoding(t[6]) + + return Font(*t) + +else: + def TupleToFont(t): + return Font(*t) + +def StorageToFont(s): + font = Font(s.size, + FONTFAMILY_DEFAULT, + wx.FONTSTYLE_ITALIC if s.italic else wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_BOLD if s.bold else wx.FONTWEIGHT_NORMAL, + s.underline, + s.face) + + fgc = s.foregroundcolor + bgc = s.backgroundcolor + + return font, fgc, bgc + +def StyleToStorage(textattr): + + font = textattr.Font + + return Storage( + backgroundcolor = tuple(textattr.BackgroundColour), + foregroundcolor = tuple(textattr.TextColour), + family = FamilyNameFromFont(font), + face = font.FaceName, + size = font.PointSize, + underline = font.Underlined, + bold = font.Weight == wx.BOLD, + italic = font.Style == wx.ITALIC) + +def StyleToDict(textattr): + + return dict(TextColour = tuple(textattr.GetTextColour()), + BackgroundColour = tuple(textattr.GetBackgroundColour()), + Font = FontToTuple(textattr.GetFont())) + +def StorageToStyle(s): + font, fgc, bgc = StorageToFont(s) + fgc = try_this(lambda: wx.Colour(*fgc), None) or wx.BLACK + bgc = try_this(lambda: wx.Colour(*bgc), None) or wx.WHITE + return wx.TextAttr(fgc, bgc, font) diff --git a/digsby/src/gui/uberwidgets/formattedinput2/formatprefsmixin.py b/digsby/src/gui/uberwidgets/formattedinput2/formatprefsmixin.py new file mode 100644 index 0000000..cc93a5c --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/formatprefsmixin.py @@ -0,0 +1,86 @@ +from gui.textutil import default_font +from gui.skin.skinparse import makeFont +from util import try_this +from gui.uberwidgets.formattedinput2.formattedinput import FormattedInput + + +import wx +from gui.uberwidgets.formattedinput2.fontutil import StyleToDict, TupleToFont +from common import setpref, pref +from traceback import print_exc + + + +#'profile.formatting' if self.aimprofile else 'messaging.default_style', + +def StyleFromPref(prefname): + + try: + stylepref = pref(prefname) + + if isinstance(stylepref, basestring): + style = dict(Font = makeFont(stylepref)) + else: + style = dict(stylepref) + except Exception: + print_exc() + style = {} + + if type(style['Font']) is tuple: + try: + font = TupleToFont(style['Font']) + except Exception: + print_exc() + font = try_this(lambda: makeFont('Arial 10'), default_font()) + else: + font = style['Font'] + fgc = try_this(lambda: wx.Colour(*style['TextColour']), None) or wx.BLACK #@UndefinedVariable + bgc = try_this(lambda: wx.Colour(*style['BackgroundColour']), None) or wx.WHITE #@UndefinedVariable + + return wx.TextAttr(fgc, bgc, font) + +class FormatPrefsMixin(object): + + def SaveStyle(self, prefname): + style = StyleToDict(self.tc.GetStyle(0)[1]) + from pprint import pformat + print 'saving style:\n%s' % pformat(style) + setpref(prefname, style) + + def WhenDefaultLayoutChange(self,src,pref,old,new): + + # if self.GetStyleAsDict() == old: + self.LoadStyle() + + def LoadStyle(self, prefname): + self.tc.SetFormat_Single(StyleFromPref(prefname)) + +class PrefInput(FormattedInput, FormatPrefsMixin): + def __init__(self, + parent, + value = '', + autosize = True, + formatOptions = None, + multiFormat = True, + showFormattingBar = True, + rtl = False, + skin = None, + entercallback = None, + validator = wx.DefaultValidator, + formatpref = None): + + + FormattedInput.__init__(self, + parent, + value = value, + autosize = autosize, + formatOptions = formatOptions, + multiFormat = multiFormat, + showFormattingBar = showFormattingBar, + rtl = rtl, + skin = skin, + validator = validator) + + self.formatpref = formatpref + if formatpref is not None: + self.LoadStyle(formatpref) #'messaging.default_style' diff --git a/digsby/src/gui/uberwidgets/formattedinput2/formattedinput.py b/digsby/src/gui/uberwidgets/formattedinput2/formattedinput.py new file mode 100644 index 0000000..d41510f --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/formattedinput.py @@ -0,0 +1,230 @@ +''' +Input area with formatting bar +''' + +from gui.uberwidgets.formattedinput2.formattingbar import FormattingBar +from gui.uberwidgets.formattedinput2.fontutil import StorageToFont, StyleToStorage + + +import wx +import config +import cgui + +from gui.uberwidgets.formattedinput2.FormattedExpandoTextCtrl import FormattedExpandoTextCtrl, FormattedTextCtrl, EVT_ETC_LAYOUT_NEEDED + +wxMSW = 'wxMSW' in wx.PlatformInfo + +if wxMSW: + from gui.toolbox import set_rich_layoutdirection + +import logging +log = logging.getLogger('formattedinput') + +class FormattedInput(cgui.SimplePanel): + def __init__(self, + parent, +# pos = wx.DefaultPosition, +# size = wx.DefaultSize, + value = '', + autosize = True, + formatOptions = None, + multiFormat = True, + showFormattingBar = True, + format = None, + rtl = False, + skin = None, + validator = wx.DefaultValidator,): + + cgui.SimplePanel.__init__(self, parent) + + self.skin = skin + self.formatOptions = formatOptions + + sizer = self.Sizer = wx.BoxSizer(wx.VERTICAL) + + if autosize: + tc = self.tc = FormattedExpandoTextCtrl(self, value = value, multiFormat = multiFormat, validator=validator) + tc.SetMaxHeight(100) + else: + tc = self.tc = FormattedTextCtrl(self, value = value, multiFormat = multiFormat, validator=validator) + + if config.platform == 'mac': + self.tc.MacCheckSpelling(True) + + if format is not None: + if isinstance(format, wx.TextAttr): + tc.SetFormat_Single(format) + else: + font, fgc, bgc = StorageToFont(format) + tc.SetFormat_Single(wx.TextAttr(wx.Color(*fgc), wx.Color(*bgc), font)) + + + tc.Bind(wx.EVT_KEY_DOWN, self.OnKey) + + self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnExpandEvent) + + self.fbar = None + + inputsizer = self.inputsizer = wx.BoxSizer(wx.HORIZONTAL) + + inputsizer.Add(tc, 1, wx.EXPAND) + + sizer.Add(inputsizer, 1, wx.EXPAND) + + if showFormattingBar: + self.CreatFormattingBar() + + self.SetFormattedValue = self.tc.SetFormattedValue + self.GetFormattedValue = self.tc.GetFormattedValue + + + def OnKey(self, event): + ctrlIsDown = event.Modifiers == wx.MOD_CONTROL + + if ctrlIsDown: + + font = self.tc.GetFormat().GetFont() + + def IsEnabled(option): + if self.formatOptions is None: + return True + + fO = self.formatOptions + return fO[option] if option in fO else fO['default'] if 'default' in fO else True + + keycode = event.KeyCode + if keycode == ord('B'): + if IsEnabled('bold'): + self.tc.ApplyStyle(bold = font.GetWeight() != wx.FONTWEIGHT_BOLD) + return + elif keycode == ord('I'): + if IsEnabled('italic'): + self.tc.ApplyStyle(italic = font.GetStyle() != wx.FONTSTYLE_ITALIC) + return + elif keycode == ord('U'): + if IsEnabled('underline'): + self.tc.ApplyStyle(underline = not font.GetUnderlined()) + return + + if wxMSW: + # make Ctrl+R and Ctrl+L modify the RTL setting of the rich edit + # control, not just the alignment; disable center alignment. + if keycode == ord('R'): + self.tc.SetRTL(True) + return + elif keycode == ord('L'): + self.tc.SetRTL(False) + return + elif keycode == ord('E'): + return + + return event.Skip() + + def OnExpandEvent(self, event): + height = (self.fbar.BestSize.height if self.FormattingBarIsShown() else 0) + self.tc.MinSize.height + + self.MinSize = wx.Size(-1, height) + + wx.CallAfter(self.Layout) + + def FormattingBarIsShown(self): + return self.fbar is not None and self.fbar.IsShown() + + def CreatFormattingBar(self): + fbar = self.fbar = FormattingBar(self, self.tc, self.skin, self.formatOptions) + self.Sizer.Insert(0, fbar, 0, wx.EXPAND) + wx.CallAfter(fbar.UpdateDisplay) + + def ShowFormattingBar(self, show = True): + + hasFBar = self.fbar is not None + + if hasFBar: + self.fbar.Show(show) + elif show: + self.CreatFormattingBar() + + self.tc.ForceExpandEvent() + + def DoUpdateSkin(self, skin): + self.formattingbar.SetSkinKey(self._skinkey) + + def Clear(self): + """ + Clears the text from the text field + """ + tc = self.tc + + if 'wxMSW' in wx.PlatformInfo: + # Clear() removes any alignment flags that are set in the text control, so + # reset them + textattr = tc.GetFormat() + alignment = cgui.GetRichEditParagraphAlignment(tc) + tc.Clear() + if cgui.GetRichEditParagraphAlignment(tc) != alignment: + cgui.SetRichEditParagraphAlignment(tc, alignment) + tc.SetFormat(textattr) + else: + tc.Clear() + + + def __repr__(self): + try: + return '<%s under %r>' % (self.__class__.__name__, self.Parent) + except Exception: + return object.__repr__(self) + + def GetValue(self): + return self.tc.GetValue() + + def SetValue(self, value): + return self.tc.SetValue(value) + + Value = property(GetValue, SetValue) + + def SetFocus(self): + self.tc.SetFocus() + + def ShowModalFontDialog(self, e = None): + ''' + Uses the native Mac font dialog to allow the user to select a font + and a color. + ''' + diag = wx.FontDialog(self, self.FontData) + if wx.ID_OK == diag.ShowModal(): + font_data = diag.GetFontData() + font = font_data.GetChosenFont() + color = font_data.GetColour() + + tc = self.tc + attrs = tc.GetDefaultStyle() + if color.IsOk(): + attrs.SetTextColour(color) + if font.IsOk(): + attrs.SetFont(font) + tc.SetDefaultStyle(attrs) + + tc.Refresh() + tc.SetFocus() + + def CreateFontButton(self, parent, label = _('Set Font...')): + ''' + Create a small button that will spawn a font dialog for setting + the properties of this text control. + ''' + font_button = wx.Button(parent, -1, label) + font_button.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + font_button.Bind(wx.EVT_BUTTON, self.ShowModalFontDialog) + return font_button + + @property + def FontData(self): + d = wx.FontData() + tc = self.tc + d.SetInitialFont(tc.GetFont()) + d.SetColour(tc.GetForegroundColour()) + return d + + @property + def Format(self): + return StyleToStorage(self.tc.GetDefaultStyle()) diff --git a/digsby/src/gui/uberwidgets/formattedinput2/formattingbar.py b/digsby/src/gui/uberwidgets/formattedinput2/formattingbar.py new file mode 100644 index 0000000..c18ff89 --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/formattingbar.py @@ -0,0 +1,262 @@ +''' +Button bar with text formatting options +''' + +from gui.prototypes.fontdropdown import FontDropDown +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.simplemenu import SimpleMenu, SimpleMenuItem +from gui.uberwidgets.UberEmotiBox import UberEmotiBox +from gui.uberwidgets.formattedinput2.fromattedinputevents import EVT_TEXT_FORMAT_CHANGED + +import sys +import wx +import config + +if config.platform == 'win': + from cgui import EVT_SELECTION_CHANGED + +from gui.uberwidgets.formattedinput2.toolbar import ToolBar, ToolBarSkinDefaults +from gui.prototypes.newskinmodule import NewSkinModule, SkinProxy + +from common import setpref, pref + +DEFAULT_SIZES = [8, 10, 12, 14, 18, 24, 36] + +FormattingBarSkinDefaults = { + 'toolbarskin' : lambda: None, + 'iconsize' : lambda: 15, + 'fontdropdownwidth' : lambda: 100, + 'sizedropdownwidth' : lambda: 33, + 'icons.bold' : lambda: wx.EmptyBitmap(1,1), + 'icons.italic' : lambda: wx.EmptyBitmap(1,1), + 'icons.underline' : lambda: wx.EmptyBitmap(1,1), + 'icons.foregroundcolor' : lambda: wx.EmptyBitmap(1,1), + 'icons.backgroundcolor' : lambda: wx.EmptyBitmap(1,1), + 'icons.emote' : lambda: wx.EmptyBitmap(1,1), +} + + +class FormattingBar(ToolBar, NewSkinModule): + + initover = False + + def __init__(self, parent, textctrl, skinkey, formatOptions): + + ToolBar.__init__(self, parent, skinkey = None, alignment = wx.ALIGN_LEFT) + + self.SetSkinKey(skinkey, FormattingBarSkinDefaults) + + self.textctrl = textctrl + if sys.platform.startswith("win"): + textctrl.Bind(EVT_SELECTION_CHANGED, self.OnCursorMove) + textctrl.Bind(EVT_TEXT_FORMAT_CHANGED, self.OnCursorMove) + + self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) + + self.fontdd = FontDropDown(self, skinkey = self.skinTB['buttonskin']) + self.fontdd.SetMenuSkinKey(self.skinTB["menuskin"]) + self.fontdd.Bind(wx.EVT_COMMAND_CHOICE_SELECTED, self.OnFontSelected) + + icons = self.icons + + self.msize = SimpleMenu(self, self.skinTB['menuskin'], maxheight = 10) + self.msize.SetItems(self.GenSizeItems(DEFAULT_SIZES)) #TODO: None default sizes +# self.msize.Bind(wx.EVT_COMMAND_CHOICE_SELECTED, self.OnSizeSelected) + + self.bsize = UberButton(self, -1, '10', menu = self.msize, type = 'menu') + self.bsize.SetStaticWidth(self.skinFB['sizedropdownwidth']) + self.msize.SetWidth(self.skinFB['sizedropdownwidth']) + + self.bbold = UberButton(self, -1, icon = icons['bold'], type = 'toggle') + self.bbold.Bind(wx.EVT_TOGGLEBUTTON, self.OnBoldButton) + + self.bitalic = UberButton(self, -1, icon = icons['italic'], type="toggle") + self.bitalic.Bind(wx.EVT_TOGGLEBUTTON, self.OnItalicButton) + + self.bunderline = UberButton(self, -1, icon = icons['underline'], type="toggle") + self.bunderline.Bind(wx.EVT_TOGGLEBUTTON, self.OnUnderlineButton) + + self.bcolor = UberButton(self, -1, icon = icons['foregroundcolor'] ) + self.bcolor.Bind(wx.EVT_BUTTON, self.OnColorButton) + + self.bbgcolor = UberButton(self,-1, icon = icons['backgroundcolor']) + self.bbgcolor.Bind(wx.EVT_BUTTON, self.OnBGColorButton) + + self.bemote = UberButton(self, -1, icon = icons['emote']) + self.bemote.Bind(wx.EVT_BUTTON, self.OnEmoteButton) + +# import pdb +# pdb.set_trace() + self.AddMany([self.fontdd, + self.bsize, + self.bbold, + self.bitalic, + self.bunderline, + self.bcolor, + self.bbgcolor, + self.bemote]) + + + self.initover = True + self.EnableFormattingButtons(formatOptions) + self.UpdateDisplay() + + def OnContextMenu(self, event): + from gui.uberwidgets.umenu import UMenu + m = UMenu(self) + m.AddItem(_('Hide Formatting Bar'), callback = lambda: wx.CallAfter(setpref, 'messaging.show_formatting_bar', False)) + m.PopupMenu() + + def OnFontSelected(self, event): + """ + Updates the button to the new font and applies it to the selection + or calls ApplyStlye + """ + self.textctrl.ApplyStyle(facename = self.fontdd.GetClientData(self.fontdd.GetSelection()).GetFaceName()) + + + def OnSizeSelected(self, item): + """ + Updates the Size button to the new size and applies it to the selection + or calls ApplyStyle + """ + self.bsize.label = str(item.id) + self.textctrl.ApplyStyle(pointsize = item.id) + + def OnBoldButton(self, event): + self.textctrl.ApplyStyle(bold = event.EventObject.IsActive()) + + def OnItalicButton(self, event): + self.textctrl.ApplyStyle(italic = event.EventObject.IsActive()) + + def OnUnderlineButton(self, event): + self.textctrl.ApplyStyle(underline = event.EventObject.IsActive()) + + def OnColorButton(self, event): + oldtextcolor = self.textctrl.GetFormat().GetTextColour() + self.textctrl.ApplyStyle(textcolor = wx.GetColourFromUser(self, oldtextcolor, _('Choose a foreground color'))) + + def OnBGColorButton(self, event): + oldbgcolor = self.textctrl.GetFormat().GetTextColour() + self.textctrl.ApplyStyle(bgcolor = wx.GetColourFromUser(self, oldbgcolor, _('Choose a background color'))) + + def OnEmoteButton(self, event): + self.DisplayEmotibox(self.bemote.ScreenRect) + + def DisplayEmotibox(self, rect): + import hooks + hooks.notify('digsby.statistics.emoticons.box_viewed') + ebox = self.GetEmotibox() + # position and display the emotibox + ebox.Display(rect) + + def GetEmotibox(self): + 'Shares the emoticon box between all instances of this class.' + + b = None + old_name, new_name = getattr(self, '_emotipack_name', None), pref('appearance.conversations.emoticons.pack', type = unicode, default = u'default') + self._emotipack_name = new_name + + try: + b = self.__class__.emotibox + if not wx.IsDestroyed(b): + if old_name != new_name: + b.Destroy() + elif b.Parent is not self: + b.Reparent(self) + + except AttributeError: + pass + + if b is None or wx.IsDestroyed(b): + from gui.imwin.emoticons import get_emoticon_bitmaps + b = self.__class__.emotibox = UberEmotiBox(self, get_emoticon_bitmaps(self._emotipack_name), self.textctrl, maxwidth = 12) + else: + b.SetTextCtrl(self.textctrl) + + return b + + def OnCursorMove(self, event): + + event.Skip() + wx.CallAfter(self.UpdateDisplay) + + def UpdateDisplay(self): + if wx.IsDestroyed(self.textctrl): + return + + selection = self.textctrl.GetSelection() + if selection[0] != selection[1]: + return + + textattr = self.textctrl.GetFormat() + font = textattr.GetFont() + + facename = font.GetFaceName() + self.fontdd.SetSelection(self.fontdd.FindString(facename, False)) + self.bsize.SetLabel(str(font.GetPointSize())) + + self.bbold.Active(font.GetWeight() == wx.FONTWEIGHT_BOLD) + self.bitalic.Active(font.GetStyle() == wx.FONTSTYLE_ITALIC) + self.bunderline.Active(font.GetUnderlined()) + + + def EnableFormattingButtons(self, enabledict): + + if enabledict is None: + return + + default = enabledict['default'] if 'default' in enabledict else True + + #TODO: fontdd should be disableable + buttons = [#('font', self.fontdd), + ('size', self.bsize), + ('bold', self.bbold), + ('italic', self.bitalic), + ('underline', self.bunderline), + ('color', self.bcolor), + ('bgcolor', self.bbgcolor), + ('emote', self.bemote)] + + for key, button in buttons: + button.Enable(enabledict[key] if key in enabledict else default) + + def GenSizeItems(self, sizes = DEFAULT_SIZES): + """ + Sets the list of selectable sizes + If not set sizes default to ['8', '10', '12', '14', '18', '24', '36'] + """ + return [SimpleMenuItem([str(size)], id=size, method = self.OnSizeSelected) for size in sizes] + + def DoUpdateSkin(self, skin): + + self.skinFB = skin + + ToolBar.DoUpdateSkin(self, SkinProxy(skin['toolbarskin'], ToolBarSkinDefaults)) + + icons = self.icons = {} + icons['bold'] = skin['icons.bold'] + icons['italic'] = skin['icons.italic'] + icons['underline'] = skin['icons.underline'] + icons['foregroundcolor'] = skin['icons.foregroundcolor'] + icons['backgroundcolor'] = skin['icons.backgroundcolor'] + icons['emote'] = skin['icons.emote'] + + iconsize = skin['iconsize'] + for key in icons: + if icons[key] is not None: + icons[key] = icons[key].Resized(iconsize) + + if self.initover: + self.bsize.SetStaticWidth(skin['sizedropdownwidth']) + self.msize.SetWidth(skin['sizedropdownwidth']) + + self.bbold.SetIcon(icons['bold']) + self.bitalic.SetIcon(icons['italic']) + self.bunderline.SetIcon(icons['underline']) + self.bcolor.SetIcon(icons['foregroundcolor']) + self.bbgcolor.SetIcon(icons['backgroundcolor']) + self.bemote.SetIcon(icons['emote']) + + def GetSkinProxy(self): + return self.skinFB if hasattr(self, 'skinFB') else None diff --git a/digsby/src/gui/uberwidgets/formattedinput2/fromattedinputevents.py b/digsby/src/gui/uberwidgets/formattedinput2/fromattedinputevents.py new file mode 100644 index 0000000..173d6d2 --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/fromattedinputevents.py @@ -0,0 +1,8 @@ +#@PydevCodeAnalysisIgnore + +import wx +import wx.lib.newevent + +newevt = wx.lib.newevent.NewCommandEvent + +TextFormatChangedEvent, EVT_TEXT_FORMAT_CHANGED = newevt() \ No newline at end of file diff --git a/digsby/src/gui/uberwidgets/formattedinput2/iminput.py b/digsby/src/gui/uberwidgets/formattedinput2/iminput.py new file mode 100644 index 0000000..865302c --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/iminput.py @@ -0,0 +1,98 @@ +''' +Additional logic for the input in the IM window +''' + +from gui.uberwidgets.formattedinput2.fromattedinputevents import EVT_TEXT_FORMAT_CHANGED +from gui.uberwidgets.formattedinput2.fontutil import StyleToStorage +from gui.uberwidgets.spacerpanel import SpacerPanel +import wx +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.formattedinput2.formattedinput import FormattedInput +from gui.uberwidgets.formattedinput2.formatprefsmixin import FormatPrefsMixin +from gui.uberwidgets.formattedinput2.splittereventsmixin import SplitterEventMixin +from common import prefprop + + + +class IMInput(FormattedInput, FormatPrefsMixin, SplitterEventMixin): + def __init__(self, + parent, + value = '', + autosize = True, + formatOptions = None, + multiFormat = True, + showFormattingBar = True, + rtl = False, + skin = None, + entercallback = None, + validator = wx.DefaultValidator,): + + + FormattedInput.__init__(self, + parent, + value = value, + autosize = autosize, + formatOptions = formatOptions, + multiFormat = multiFormat, + showFormattingBar = showFormattingBar, + rtl = rtl, + skin = skin, + validator = validator) + + self.LoadStyle('messaging.default_style') + + self.entercallback = entercallback + + + self.tc.Bind(wx.EVT_KEY_DOWN, self.OnEnterKey) + + self.sendbutton = None + if self.showSendButton: + self.CreateSendButton() + + + def ShowSendButton(self, show): + sendbutton = self.sendbutton + hasSendButton = sendbutton is not None + + if hasSendButton: + self.spacer.Show(show) + sendbutton.Show(show) + elif show: + self.CreateSendButton() + + self.Layout() + + def CreateSendButton(self): + + self.spacer = SpacerPanel(self, skinkey = 'inputspacer') + self.inputsizer.Add(self.spacer, 0, wx.EXPAND) + + sendbutton = self.sendbutton = UberButton(self, label = _('Send'), skin='InputButton') #wx.Button(self, label = _('Send')) + self.inputsizer.Add(sendbutton, 0, wx.EXPAND) + sendbutton.Bind(wx.EVT_BUTTON, lambda e: self.entercallback(self)) + + shiftToSend = prefprop("messaging.shift_to_send", False) + showSendButton = prefprop("messaging.show_send_button", False) + + def OnEnterKey(self, event): + """ + This detects key presses, runs entercallback if enter or return is pressed + Any other key continues as normal, then refreshes the font and size info + """ + keycode = event.KeyCode + + shiftToSend = self.shiftToSend + hasModifiers = event.HasModifiers() + shiftIsDown = event.Modifiers == wx.MOD_SHIFT + + if keycode in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): + # if there is a enter callback and no modifiers are down or if + # shift should send and shift is down, call the callback + if self.entercallback and \ + (not (shiftToSend or hasModifiers or shiftIsDown) or \ + (shiftToSend and shiftIsDown)): + return self.entercallback(self) + + event.Skip() + diff --git a/digsby/src/gui/uberwidgets/formattedinput2/splittereventsmixin.py b/digsby/src/gui/uberwidgets/formattedinput2/splittereventsmixin.py new file mode 100644 index 0000000..3d90bb4 --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/splittereventsmixin.py @@ -0,0 +1,141 @@ +''' +Mixin with logic to manage making the autoresizing IMInput play well with the splitter +''' + +import wx +import config #@UnresolvedImport + +from common import setpref, pref + +wxMSW = 'wxMSW' in wx.PlatformInfo +from FormattedExpandoTextCtrl import EVT_ETC_LAYOUT_NEEDED + +#SWIG HAX: In Robin's bindings, wx.EVT_SIZE is a function, and wx.wxEVT_SIZE is the int id +EVT_SIZE = wx.EVT_SIZE if config.platform == "win" else wx.wxEVT_SIZE + +class SplitterEventMixin(object): + def OnExpandEventSplitter(self, event): + + if self.resizing: + return + + #Can't event.Skip() here or a scroll happens + self.OnExpandEvent(event) + if hasattr(self, 'splitter'): + best_size = self.BestSizeControl.BestSize.height + splitterpos = self.splitter.ClientSize.height - best_size - self.splitter.SashSize + self.splitter.SetSashPosition(splitterpos) + self.Layout() + + @property + def BestSizeControl(self): + if 'wxMac' in wx.PlatformInfo: + return self.tc + else: + return self + + def BindSplitter(self, splitter, heightpref = None): + + splitter.Bind(wx.EVT_LEFT_DOWN, self.OnSplitterStart) + splitter.Bind(wx.EVT_LEFT_UP, self.OnSplitterSet) + #splitter.Connect(splitter.Id, splitter.Id, EVT_SIZE, self.OnFirstSplitterSize) + + self.splitter = splitter + + self.heightpref = heightpref + + self.resizing = False + + self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnExpandEventSplitter) + if 'wxMac' in wx.PlatformInfo: + self.tc.Bind(wx.EVT_TEXT, self.OnExpandEventSplitter) + + + tc = self.tc + tc.SetMinHeight(pref(self.heightpref, 0)) + tc.ForceExpandEvent() + + + #Layout on coming out of window hidden - fixes tray and fresh window layouts + self.Top.Bind(wx.EVT_SHOW, self.OnTopShow) + self.Top.Bind(wx.EVT_SET_FOCUS, self.OnTopShow) + + #Layout on page shown, fixes layout on showing tab, notably a new tab when win is minimized + self.splitter.GrandParent.Bind(wx.EVT_SHOW, self.OnTopShow) + + #Layout when window is restored from iconized state + if hasattr(self.Top, 'iconizecallbacks'): + self.Top.iconizecallbacks.add(self.OnRestore) + else: + self.Top.Bind(wx.EVT_ICONIZE, self.OnRestore) + + + +# def OnFirstSplitterSize(self, event): +# +# #HAX: make sure the splitter lays out the first time it get's a real size +# +# event.Skip() +# +# splitter = self.splitter +# +# if splitter.Size.height and splitter.Size.width: +# splitter.Disconnect(splitter.Id, splitter.Id, EVT_SIZE) +# wx.CallAfter(self.OnExpandEvent, event) + + def OnSplitterStart(self, event): + + self.resizing = True + + tc = self.tc + + baseh = tc.GetNatHeight() + + tc.SetMinHeight(baseh) + + #HAX for FormattedInput's extra layer of abstraction from the Expando, should be handled better + self.BestSizeControl.MinSize = self.BestSizeControl.BestSize + + event.Skip() + + def OnSplitterSet(self, event): + + self.resizing = False + + event.Skip() + tc = self.tc + + + natHeight = tc.GetNatHeight(); + setHeight = tc.GetSize().height; + h = -1 if setHeight <= natHeight else setHeight + +# log.info("input_base_height set to %s", h) + + if hasattr(self, 'heightpref') and self.heightpref is not None: + setpref(self.heightpref, h) + + tc.SetMinHeight(h) + + + + def OnTopShow(self, event): + """ + Bound to the top level window of this control + it's a hax to make sure the text control is the the correct height when shown + """ + + event.Skip() + if not hasattr(event, 'GetShow') or event.GetShow(): + self.tc.ForceExpandEvent() + + def OnRestore(self, event=None): + """ + Bound to the top level window of this control + it's a hax to make sure the text control is the the correct height when shown + """ + + if event is not None: + event.Skip() + if event is None or not event.Iconized(): + wx.CallAfter(self.tc.ForceExpandEvent) diff --git a/digsby/src/gui/uberwidgets/formattedinput2/test.py b/digsby/src/gui/uberwidgets/formattedinput2/test.py new file mode 100644 index 0000000..5706530 --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/test.py @@ -0,0 +1,61 @@ +import sys; +sys.path.append('C:\\Users\\Aaron\\workspace\\DigsbyTrunk\\digsby'); + +#import os +#os.chdir('C:\\Users\\Aaron\\workspace\\DigsbyTrunk\\digsby') + +from ctypes import windll +windll.comctl32.InitCommonControls() + +import wx +from tests.testapp import testapp +from win32events import bindwin32 +from gui.uberwidgets.formattedinput2.FormattedExpandoTextCtrl import FormattedExpandoTextCtrl +FormattedExpandoTextCtrl.BindWin32 = bindwin32 + +#wx.Window.BindWin32 = bindwin32 +#import gui.native.win.winhelpers + +from gui.uberwidgets.formattedinput2.fontutil import FontFromFacename +from cgui import EVT_ETC_LAYOUT_NEEDED + +def _(text): + return text +__builtins__._ = _ + +from gui.uberwidgets.formattedinput2.formattedinput import FormattedInput + + +def NewInput(): + f = wx.Frame(None) + + f.Sizer = wx.BoxSizer(wx.VERTICAL) + + font = FontFromFacename('Arial') + font.SetPointSize(10) +# fo = {'default': False, +# 'italic': True} + textattr = wx.TextAttr(wx.BLACK, wx.WHITE, font) #@UndefinedVariable + + i = FormattedInput(f, multiFormat = True)#, formatOptions = fo) + + def OnExpandEvent(event): + height = (i.fbar.Size.height if i.FormattingBarIsShown() else 0) + i.tc.MinSize.height + + i.MinSize = wx.Size(-1, height) + f.Fit() + + i.Bind(EVT_ETC_LAYOUT_NEEDED, OnExpandEvent) + + f.Sizer.Add(i, 0, wx.EXPAND) + + f.Show() + f.Fit() + +if __name__ == '__main__': + app = testapp(plugins = False) + + NewInput() + + app.MainLoop() + diff --git a/digsby/src/gui/uberwidgets/formattedinput2/toolbar.py b/digsby/src/gui/uberwidgets/formattedinput2/toolbar.py new file mode 100644 index 0000000..845697c --- /dev/null +++ b/digsby/src/gui/uberwidgets/formattedinput2/toolbar.py @@ -0,0 +1,120 @@ +from gui.prototypes.newskinmodule import NewSkinModule, SkinProxy +from gui.uberwidgets.UberButton import UberButton + +from gui.skin.skinobjects import Margins, SkinColor +from gui.prototypes.fontdropdown import FontDropDown + +import wx +from cgui import SimplePanel +from util.primitives.funcs import do + +ToolBarSkinDefaults = { + 'padding' : lambda: wx.Point(2,2), + 'margins' : lambda: Margins([2,2,2,2]), + 'background' : lambda: SkinColor(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)), + 'buttonskin' : lambda: None, + 'menuskin' : lambda: None, +} + +class ToolBar(SimplePanel, NewSkinModule): + + def __init__(self, parent, id = wx.ID_ANY, skinkey = None, name = 'ToolBar', alignment = None): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + self.children = [] + + self.content = wx.BoxSizer(wx.HORIZONTAL) + self.Sizer = Margins().Sizer(self.content) + + self.SetSkinKey(skinkey, ToolBarSkinDefaults) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def Insert(self, pos, object, expand = False): + + skin = self.skinTB + + #TODO: This is stupid, should be done some other way + if isinstance(object, UberButton): + object.SetSkinKey(skin['buttonskin'], True) + if object.menu is not None: + object.menu.SetSkinKey(skin["menuskin"]) + elif isinstance(object, FontDropDown): + object.SetSkinKey(skin['buttonskin']) + object.SetMenuSkinKey(skin["menuskin"]) + + self.content.Insert(pos, object, expand, wx.RIGHT | wx.EXPAND, self.skinTB['padding'].x) + self.children.insert(pos, object) + + def Add(self, object, expand = False): + + skin = self.skinTB + + #TODO: Still stupid, see Insert + if isinstance(object, UberButton): + object.SetSkinKey(skin['buttonskin'], True) + if object.menu is not None: + object.menu.SetSkinKey(skin["menuskin"]) + elif isinstance(object, FontDropDown): + object.SetSkinKey(skin['buttonskin']) + object.SetMenuSkinKey(skin["menuskin"]) + + self.content.Add(object, expand, wx.RIGHT | wx.EXPAND, self.skinTB['padding'].x) + self.children.append(object) + + def Detach(self, object): + return self.content.Detach(object) + + def AddMany(self, objects, expand = False): + for object in objects: + self.Add(object, expand) + + def DoUpdateSkin(self, skin): + self.skinTB = skin + + self.Sizer.SetMargins(skin['margins']) + + #Even stupider; see Add and Insert + do(item.SetSkinKey(skin["buttonskin"]) for item in self.children if isinstance(item, (UberButton, FontDropDown))) + for item in self.children: + if isinstance(item, UberButton) and item.menu is not None: + item.menu.SetSkinKey(skin["menuskin"]) + elif isinstance(item, FontDropDown): + item.SetMenuSkinKey(skin["menuskin"]) + + for child in self.content.Children: + child.SetBorder(skin["padding"].x) + + def GetSkinProxy(self): + return self.skinTB if hasattr(self, 'skinTB') else None + + def OnPaint(self, event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + + self.skinTB['background'].Draw(dc, rect) + self.OnPaintMore(dc) + + def OnPaintMore(self, dc): + pass + +class SkinnedToolBar(ToolBar): + ''' + given an indirect_skinkey, looksup the correct toolbar skin on creation and + on UpdateSkin. + ''' + + def __init__(self, *a, **k): + from gui.uberwidgets.formattedinput2.formattingbar import FormattingBarSkinDefaults + skinkey = k.pop('indirect_skinkey') + k['skinkey'] = None + + ToolBar.__init__(self, *a, **k) + self.SetSkinKey(skinkey, FormattingBarSkinDefaults) + + def DoUpdateSkin(self, skin): + self.skinTTB = skin + ToolBar.DoUpdateSkin(self, SkinProxy(skin['toolbarskin'], ToolBarSkinDefaults)) + + def GetSkinProxy(self): + return self.skinTTB if hasattr(self, 'skinTTB') else None + diff --git a/digsby/src/gui/uberwidgets/keycatcher.py b/digsby/src/gui/uberwidgets/keycatcher.py new file mode 100644 index 0000000..0971319 --- /dev/null +++ b/digsby/src/gui/uberwidgets/keycatcher.py @@ -0,0 +1,79 @@ +''' + +A simpler interface than wxAcceleratorTable (which is immutable, unexplainably) +for binding and unbinding keyboard shortcuts. + +''' + +import wx +from collections import defaultdict +from util.primitives.funcs import Delegate + +from gui.input import keycodes + + +# unique id pool since there are a limited amound + +_ids = set() + +def MyNewId(): + global _ids + return wx.NewId() if not len(_ids) else _ids.pop() + +def ReleaseIds(ids): + global _ids + _ids.update(ids) + + +class KeyCatcher(wx.EvtHandler): + def __init__(self, frame): + frame.Bind(wx.EVT_MENU, self._oncommandevent) + + self.frame = frame + self.cbs = defaultdict(lambda: Delegate(ignore_exceptions = wx.PyDeadObjectError)) + self.idcbs = {} + + def OnDown(self, shortcut, callback): + + sc = str(shortcut) + + accel = keycodes(sc, accel = True) + self.cbs[accel].insert(0, callback) + self.update_table() + + def rem(accel=accel, callback=callback, shortcut=sc): + self.cbs[accel].remove(callback) + + return rem + + def update_table(self): + ''' + The wx.AcceleratorTable class used for binding keyboard shortcuts to + a window seems to be read only, so this function rebuilds the + table every time a callback is added. + ''' + idcbs = self.idcbs + ReleaseIds(idcbs.keys()) + idcbs.clear() + + entries = [] + + for (modifiers, key), callback in self.cbs.iteritems(): + wxid = MyNewId() + idcbs[wxid] = callback + entries.append((modifiers, key, wxid)) + + atable = wx.AcceleratorTableFromSequence(entries) + if not atable.IsOk(): print 'warning: accelerator table is not OK' + + self.frame.SetAcceleratorTable(atable) + + def _oncommandevent(self, e): + try: + cbs = self.idcbs[e.Id] + except KeyError: + e.Skip() + else: + cbs(e) + e.Skip() + diff --git a/digsby/src/gui/uberwidgets/panelframe.py b/digsby/src/gui/uberwidgets/panelframe.py new file mode 100644 index 0000000..a92c7cf --- /dev/null +++ b/digsby/src/gui/uberwidgets/panelframe.py @@ -0,0 +1,64 @@ +import wx +from gui.uberwidgets import UberWidget +from gui import skin +from gui.skin.skinobjects import SkinColor,Margins +from cgui import SimplePanel + +objget = object.__getattribute__ + +class PanelFrame(SimplePanel, UberWidget): + ''' + Frame for panels in the buddylist + ''' + + def __init__(self, parent, panel, skinkey): + SimplePanel.__init__(self, parent, wx.FULL_REPAINT_ON_RESIZE) + + self.SetSkinKey(skinkey, True) + if not panel.Parent is self: + panel.Reparent(self) + + self.panel = panel + + sizer = self.Sizer = wx.GridBagSizer() + sizer.SetEmptyCellSize(wx.Size(0,0)) + sizer.Add(panel,(1,1),flag = wx.EXPAND) + sizer.Add(wx.Size(self.framesize.left, self.framesize.top), (0,0)) + sizer.Add(wx.Size(self.framesize.right, self.framesize.bottom), (2,2)) + + sizer.AddGrowableCol(1,1) + sizer.AddGrowableRow(1,1) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def __repr__(self): + return '' % self.panel + + def __getattr__(self,attr): + try: + return objget(self, attr) + except AttributeError: + try: + return getattr(objget(self, 'panel'), attr) + except AttributeError,e: + raise e + + def OnPaint(self,event): + self.framebg.Draw(wx.AutoBufferedPaintDC(self), wx.RectS(self.ClientSize)) + + def UpdateSkin(self): + key = self.skinkey + s = lambda k, default: skin.get('%s.%s' % (key, k), default) + + self.framebg = s('frame', SkinColor(wx.BLACK)) + self.framesize = s('framesize', Margins([0,0,0,0])) + + sz = self.Sizer + if sz: + sz.Detach(1) + sz.Detach(1) + sz.Add(wx.Size(self.framesize.left, self.framesize.top),(0,0)) + sz.Add(wx.Size(self.framesize.right, self.framesize.bottom),(2,2)) + + wx.CallAfter(self.Layout) + wx.CallAfter(self.Refresh) diff --git a/digsby/src/gui/uberwidgets/pseudosizer.py b/digsby/src/gui/uberwidgets/pseudosizer.py new file mode 100644 index 0000000..7ff27ff --- /dev/null +++ b/digsby/src/gui/uberwidgets/pseudosizer.py @@ -0,0 +1,91 @@ +import wx +from util.primitives.funcs import do + +def HackedShow(self,pseudosizer,switch=True): + self.RealShow(switch and pseudosizer.Shown) + self.ShouldShow = switch + pseudosizer.Recalc() + +class PseudoSizer(list): + ''' + An artificial sizer that can be positioned and controls the visibility of all it's children + ''' + def __init__(self): + list.__init__(self) + + self.position = wx.Point(0,0) + self.space = 4 + self.Shown=True + + def Add(self,item): + self.append(item) + item.Show(self.Shown and item.Shown) + item.ShouldShow = item.Shown + item.RealShow = item.Show + item.Show = lambda s: HackedShow(item, self, s) + self.Recalc() + + def SetPosition(self,pos): + if pos[0] != -1: self.position.x = pos[0] + if pos[1] != -1: self.position.y = pos[1] + + self.Recalc() + + def GetPosition(self): + return self.position + +# Position=property(GetPosition,SetPosition) + + def SetSpace(self,space): + self.space=space + self.Recalc() + + def GetSpace(self): + return self._space + +# Space=property(GetSpace,SetSpace) + + def Recalc(self): + x, y = self.position + pos = wx.Point(x, y) + space = self.space + + for item in self: + if item.Shown: + item.Position = pos + item.Size = item.BestSize + pos.x += item.Size.width + space + + def Layout(self): + self.Recalc() + + def Clear(self,delete=False): + for item in self[:]: + item.RealShow(False) + self.remove(item) + item.Show=item.RealShow + if delete: item.Destroy() + + def Remove(self,item): + self.remove(item) + item.Show=item.RealShow + + @property + def Rect(self): + return wx.RectPS(self.position, self.Size) + + @property + def Size(self): + w=0 + h=0 + for item in self: + if item.Shown: + if item.Size.height>h: h=item.Size.height + w+=item.Size.width + + return wx.Size(w,h) + + def Show(self,switch=True): + self.Shown=switch + do(item.RealShow(switch and item.ShouldShow) for item in self) + self.Recalc() diff --git a/digsby/src/gui/uberwidgets/simplemenu.py b/digsby/src/gui/uberwidgets/simplemenu.py new file mode 100644 index 0000000..442111c --- /dev/null +++ b/digsby/src/gui/uberwidgets/simplemenu.py @@ -0,0 +1,926 @@ +from __future__ import with_statement +from gui.toolbox import Monitor + +import wx, sys +import math +import traceback +from wx import Bitmap, Rect, RectS, RectPS, GetMousePosition, \ + MenuEvent, Point, VERTICAL, HORIZONTAL, Pen, Brush, \ + Size + +from gui.skin.skinobjects import SkinColor,Margins +from gui.windowfx import fadein +from gui import skin +from util.primitives.funcs import do, Delegate +from common import pref +from gui.uberwidgets import UberWidget +from gui.textutil import GetTextWidth,default_font +from gui.windowfx import DrawSubMenuArrow, ApplySmokeAndMirrors + +from logging import getLogger; log = getLogger('simplemenu') + +class SMDTimer(wx.Timer): + + def __init__(self,menu): + self.menu = menu + wx.Timer.__init__(self) + + def Start(self,hitrect,*args,**kwargs): + self.hitrect = hitrect + self.args = args + self.kwargs = kwargs + wx.Timer.Start(self, 500, True) + + def Notify(self): + if not self.menu.Shown and self.hitrect.Contains(wx.GetMousePosition()): + self.menu.Display(*self.args,**self.kwargs) + +class RecapTimer(wx.Timer): + "This timer tells the curent lowest level menu to recapture the mouse, along with it's parent tree " + def __init__(self,target): + wx.Timer.__init__(self) + self.target=target + self.Start(10) + + def Notify(self): + + target = self.target + mp = target.Parent.ScreenToClient(wx.GetMousePosition()) + + if (not target.Rect.Contains(mp) or target.ClientRect.Contains(mp)) and not wx.GetMouseState().LeftDown(): + self.Stop(target) + + def Stop(self,target): + wx.Timer.Stop(self) + target.Parent.CascadeCapture() + del target.recaptimer + +class SimpleMenuSpine(wx.VListBox, UberWidget): + 'This is the list handler for the menu.' + + def __init__(self, parent, skin): + 'Generic constructor' + + wx.VListBox.__init__(self,parent) + UberWidget.__init__(self,'LISTBOX') + + self.MinSize = wx.Size(1, 1) #fix for small menus + + events = [ + (wx.EVT_PAINT, self.OnPaint), + (wx.EVT_MOUSEWHEEL, self.OnMouseWheel), + (wx.EVT_MOTION, self.OnMouseMove), + (wx.EVT_LEFT_UP, self.OnLUp), + (wx.EVT_LEFT_DOWN, self.OnLDown), + (wx.EVT_RIGHT_DOWN, self.OnLDown), + (wx.EVT_MIDDLE_DOWN, self.OnLDown), + (wx.EVT_LEFT_DCLICK, lambda e:None), + (wx.EVT_SCROLLWIN, self.OnScroll), + (wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseCaptureLost) + ] + do(self.Bind(event, method) for (event, method) in events) + + #TODO: soft code this + self.itemheight = 20 + + self.items = [] + self.ItemCount = len(self.items) + + self.SetSkinKey(skin,True) + + def OnMouseCaptureLost(self, e): + self.Parent.CloseRoot() + + def UpdateSkin(self): + key = self.skinkey + + self.native = native = not key + if native: + self.OpenNativeTheme() + + self.padding = wx.Point(2,2) + + self.framesize = Margins([1,1,1,1])#[0,0,0,0] if uxthemed else + + sz = self.Parent.Sizer + if sz: + sz.Detach(1) + sz.Detach(1) + sz.Add(wx.Size(self.framesize.left,self.framesize.top),(0,0)) + sz.Add(wx.Size(self.framesize.right,self.framesize.bottom),(2,2)) + + self.framebg = None + self.menubg = None + self.itembg = None + self.selbg = None + + self.Font = default_font() + + self.normalfc = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) + self.selfc = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT) + + self.MakeNativeSubmenuIcons() + + self.separator = None + + else: + + self.CloseNativeTheme() + + s = lambda k, default: skin.get('%s.%s' % (key,k), default) + + self.padding = s('padding', wx.Point(2,2)) + + self.framesize = s('framesize',Margins([0,0,0,0])) + + sz = self.Parent.Sizer + if sz: + sz.Detach(1) + sz.Detach(1) + sz.Add(wx.Size(self.framesize.left,self.framesize.top),(0,0)) + sz.Add(wx.Size(self.framesize.right,self.framesize.bottom),(2,2)) + + self.framebg = s('frame', lambda: SkinColor(wx.BLACK)) + self.menubg = s('backgrounds.menu', None) + self.itembg = s('backgrounds.item', None) + self.selbg = s('backgrounds.selection', None) + + self.Font = s('font', default_font()) + + self.normalfc = s('fontcolors.normal', lambda: wx.BLACK) + self.selfc = s('fontcolors.selection', lambda: wx.BLACK) + + + #TODO: Default? + submenuicon = self.submenuicon = s('submenuicon', None) + if submenuicon is None: + self.MakeNativeSubmenuIcons() + else: + self.submenuiconhot = s('submenuiconhover', submenuicon) + + #TODO: Default? + self.separator = s('separatorimage',None) + + for item in self.items: + if item.menu: + item.menu.spine.SetSkinKey(key) + + + def MakeNativeSubmenuIcons(self): + arrowmask = wx.EmptyBitmap(10,10) + mdc = wx.MemoryDC() + mdc.SelectObject(arrowmask) + + from gui.windowfx import controls + arect = wx.Rect(0,0,10,10) + DrawSubMenuArrow(mdc,arect) + mdc.SelectObject(wx.NullBitmap) + + mdc2 = wx.MemoryDC() + for s in xrange(2): + acolor = self.selfc if s else self.normalfc + arrow = wx.EmptyBitmap(10,10) + arrow.SetMask(wx.Mask(arrowmask,wx.WHITE)) + mdc2.SelectObject(arrow) + mdc2.Brush = wx.Brush(acolor) + mdc2.FloodFill(0,0,wx.BLACK) + mdc2.SelectObject(wx.NullBitmap) + if s: + self.submenuiconhot = arrow + else: + self.submenuicon = arrow#wx.Mask(arrowmask,wx.WHITE)# + + def DrawNativeBackgroundFallback(self,dc,part,state,unusedrect): + + rect = wx.RectS(self.Size) + + dc.Brush = Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) + dc.Pen = wx.TRANSPARENT_PEN#wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWFRAME)) + dc.DrawRectangleRect(rect) + + def OnPaint(self,event): + 'Standard paint handling.' + + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.ClientSize) + + if self.menubg: + self.menubg.Draw(dc, rect) + else: + dc.SetClippingRect(rect) + nrect = wx.Rect(*rect) + nrect.Inflate(1, 1)#wx.RectPS((-self.Rect.x,-self.Rect.y),self.Size) + self.DrawNativeLike(dc,0,0,nrect,self.DrawNativeBackgroundFallback) + dc.DestroyClippingRegion() + +# dc.Brush = wx.RED_BRUSH +# dc.Pen = wx.TRANSPARENT_PEN +# dc.DrawRectangleRect(rect) + + rect.Height = self.itemheight + + i, j = self.FirstVisibleLine, self.LastVisibleLine + if j >= 0 and j != sys.maxint*2+1: + bg, draw, measure = self.OnDrawBackground, self.OnDrawItem, self.OnMeasureItem + for n in xrange(i, j + 1): + bg(dc, rect, n) + draw(dc, rect, n) + rect.SetY(rect.GetY() + measure(n)) + +# def DrawThemelessItemBG(self,dc,part,state,rect): +# +# if state == 2: +# dc.Brush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)) +# dc.Pen = wx.TRANSPARENT_PEN +# dc.DrawRectangleRect(rect) + + def OnDrawBackground(self,dc,rect,n): + 'Draws the background for each item.' + + if self.native: + if self.GetSelection() == n: + dc.Brush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)) + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(rect) +# s = 3 if self.GetSelection() == n else 1 +# self.DrawNativeLike(dc,5,1,rect,self.DrawThemelessItemBG) + else: + if self.GetSelection() == n and self.selbg: + self.selbg.Draw(dc, rect) + elif self.itembg: + self.itembg.Draw(dc, rect) + + def OnDrawItem(self,dc,rect,n): + 'Draws the foreground of each item.' + + curser = Point(rect.x, rect.y) + (self.padding.x, 0) + + if self.items[n].id==-1: + return self.DrawSeparator(dc, rect, n) + + if self.items[n].font: + font = self.items[n].font + else: + font = self.Font + + dc.Font=font + dc.TextForeground=(self.selfc if self.Selection==n else self.normalfc) + + if self.items[n].menu: + dc.Brush=wx.BLACK_BRUSH + dc.Pen=wx.TRANSPARENT_PEN + smi=self.submenuiconhot if self.Selection==n else self.submenuicon + +# if isinstance(smi,wx.Mask): +# acolor = self.selfc if self.Selection==n else self.normalfc +# arrow = wx.EmptyBitmap(10,10) +# arrow.SetMask(smi) +# mdc2 = wx.MemoryDC() +# mdc2.SelectObject(arrow) +# mdc2.Brush = wx.Brush(acolor) +# mdc2.FloodFill(0,0,wx.BLACK) +# mdc2.SelectObject(wx.NullBitmap) +# dc.DrawBitmap(arrow,rect.Width-self.padding.x-arrow.Width,rect.Y+(rect.Height/2)-arrow.Height/2,True) +# endcap=arrow.Width+self.padding.x +# else: + dc.DrawBitmap(smi,rect.Width-self.padding.x-smi.Width,rect.Y+(rect.Height/2)-smi.Height/2,True) + endcap=smi.Width+self.padding.x + else: + endcap=0 + + padx = self.padding.x + txtext = dc.Font.Height + txtext_pad = Point(txtext + padx, 0) + + for i in self.items[n].content: + if type(i) is Bitmap: + curser.y = rect.Y + (rect.height / 2 - i.Height / 2) + imgpad = (self.bitmapwidth - i.Width)//2 if self.bitmapwidth else 0 + try: + dc.DrawBitmapPoint(i, (curser.x + imgpad, curser.y), True) + except Exception: + traceback.print_exc_once() + try: + log.error("Failed drawing bitmap: %r", getattr(i, 'path', None)) + except Exception: + pass + curser += Point(max(self.bitmapwidth, i.Width) + padx, 0) + elif isinstance(i, basestring): + curser.y = rect.Y + (rect.height / 2 - txtext / 2) + text_rect = RectPS(curser, Size(rect.width - curser.x - padx - endcap, txtext)) + dc.DrawTruncatedText(i, text_rect, alignment = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) + curser += txtext_pad + + def DrawSeparator(self, dc, rect, n): + sepwidth = rect.width - self.padding.x * 2 + + if self.separator: + sepheight = self.separator.Size.height + seppos = (self.padding.x, rect.y + rect.height // 2 - sepheight // 2) + self.separator.Draw(dc, RectPS(seppos, (sepwidth, sepheight))) + else: + dc.Pen = Pen(self.normalfc, 1) + seppos = Point(self.padding.x, rect.y + rect.height // 2) + endpos = seppos + Point(sepwidth, 0) + dc.DrawLinePoint(seppos, endpos) + + def OnMouseWheel(self, e): + self.ScrollLines(-math.copysign(1, e.WheelRotation)) + + def OnMouseMove(self,event): + 'Mouse over event handling.' + + mp = self.ScreenToClient(GetMousePosition()) + items = self.items + + if self.ClientRect.Contains(mp): + n = self.HitTest(mp) + if self.Selection != n: + do(item.menu.Show(False) for item in self.items if item.menu and item.menu.IsShown()) + if items[n].id != -1: + self.SetSelection(n) + if items[n].menu: + items[n].menu.DelayedDisplay(self.GetItemRect(n), self) + else: + self.SetSelection(-1) + elif self.Rect.Contains(mp): + self.Parent.CascadeRelease() + self.recaptimer = RecapTimer(self) + else: + self.SetSelection(-1) + gp = self.GrandParent + if isinstance(gp, SimpleMenu) and gp.CheckParentalContact(GetMousePosition()): + gp.spine.AddPendingEvent(event) + + def OnLUp(self,event): + """ + Release left mouse button handling + """ + if self.GetClientRect().Contains(event.Position): + n = self.HitTest(event.Position) + if self.items[n].id != -1: + item = self.items[n] + if not item.menu: + self.TriggerItem(item) + + def TriggerItem(self,item): + 'Steps to take when a item is clicked.' + + if item.method is not None: + wx.CallAfter(item.method, item) + elif self.Parent.callback: + wx.CallAfter(self.Parent.callback, item) + else: + menuevent = MenuEvent(wx.wxEVT_COMMAND_MENU_SELECTED, item.id) + self.Parent.AddPendingEvent(menuevent) + self.Parent.CloseRoot() + + def OnLDown(self,event): + """ + Mouse down handling + """ + if not self.Rect.Contains(event.Position) and not self.Parent.CheckParentalContact(wx.GetMousePosition(),True): + self.Parent.CloseRoot() + + def OnScroll(self,event): + 'What to do when the window is scrolled.' + + self.Refresh() + event.Skip() + + def CalcSize(self): + 'Calculates the size of the menu.' + + self.CalcItemHeight() + if self.Parent.staticwidth: + width = self.Parent.width + else: + self.CalcItemWidth() + width = self.calcedwidth + + if not self.Parent.maxheight or self.ItemCount maxwidth: + width = maxwidth + + minwidth = self.Parent.minwidth + if minwidth and width < minwidth: + width = minwidth + + + p = self.Parent.ScreenRect[:2] +# self.MinSize = wx.Size(self.menuwidth-self.framesize.x, height) + self.Parent.Rect = RectPS(p, wx.Size(width, height+self.framesize.y)) + #self.Parent.Size = (self.menuwidth+self.framesize.x, height+self.framesize.y) + + def CalcItemHeight(self): + 'Calculates tallest item.' +# print 'SimpleMenu CalcItemHeight being ran too much, this is a reminder to create a simpler version for individual changes' + + hset = list(item.font.Height for item in self.items if item.font) + hset.append(self.Font.Height) + + + if max(hset)-min(hset)>10: + + hset = sorted(hset) + lhset = len(hset) + + def Median(set): + lset = len(set) + if lset % 2: + m1=lset//2 + m2=m1+1 + return set[m1] + ((set[m2] - set[m1])//2) + else: + return set[lset//2+1] + + q1 = Median(hset[:lhset//2]) + q3 = Median(hset[(lhset//2)+1:]) + iqr = q3 - q1 + hi = q3 + 1.5*iqr + + hset = set(h for h in hset if h < hi) + + self.bitmapwidth = bitmapwidth = 0 + for item in self.items: + if item.id == -1: + pass + elif item.content and isinstance(item.content[0], Bitmap): + bitmapwidth = max(bitmapwidth, item.content[0].Width) + + for object in item.content: + if type(object) is Bitmap: + hset.append(object.Height) + + # keep track of the biggest bitmap on the left to align plain text + # items + if bitmapwidth: + empty_bitmap = wx.EmptyBitmap(bitmapwidth, 1) + for item in self.items: + if item.id == -1: + pass + elif item.content and isinstance(item.content[0], basestring): + item.content.insert(0, empty_bitmap) + self.bitmapwidth = bitmapwidth + + h=max(hset) + +#HACK: The following section of code is a hax to remove all fonts with extreme sizes +#------------------------------------------------------------------------------- + for item in self.items: + if item.font and item.font.Height>h: + self.items.remove(item) + self.ItemCount = len(self.items) +#------------------------------------------------------------------------------- + + self.itemheight = h+2*self.padding.y + + def CalcItemWidth(self): + if not self.items: + self.calcedwidth=0 + else: + wset = set(item.GetContentWidth(self.Parent) for item in self.items)#set(GetTextWidth(item.GetContentAsString(),item.font or self.Font)+len(item.content)*self.padding.x for item in self.items) + w = max(wset) + w += self.framesize.x + + self.calcedwidth = w + + def OnMeasureItem( self, n ): + 'Returns item height.' + + return self.itemheight + + + def GetSelectionRect(self): + return self.GetItemRect(self.Selection) + + def GetItemRect(self,n): + pos = self.ScreenRect.Position + + x = pos.x + width= self.Size.x + y = pos.y + sum(self.OnMeasureItem(i) for i in xrange(n)) + + height=self.OnMeasureItem(n) + + return Rect(x,y,width,height) + +class SimpleMenu(wx.PopupTransientWindow, UberWidget): + ''' + This is the class for most menus throughout the codebase + The use of this class should be deprecated and replaced by new menus based off the menu code located in gui.prototypes + ''' + + def __init__(self, parent, skinkey = 'simplemenu', maxheight = None, width = 0, minwidth = 0, maxwidth=0, callback = None): + """ + items - Initial items - cut - was never used + """ + + wx.PopupTransientWindow.__init__(self, parent) + + self.BeforeDisplay = Delegate() + Bind = self.Bind + Bind(wx.EVT_PAINT, self.OnPaint) + Bind(wx.EVT_SIZE, self.OnSize) + Bind(wx.EVT_SHOW, self.OnClose) + Bind(wx.EVT_MENU, self.PassEvent) + + self.maxheight = maxheight + + self.staticwidth = bool(width) + self.width = width + + self.maxwidth = maxwidth + self.minwidth = minwidth + + self.spine = SimpleMenuSpine(self,skinkey) + self.displaytimer=SMDTimer(self) + + s = self.Sizer = wx.GridBagSizer() + s.SetEmptyCellSize(wx.Size(0, 0)) + s.Add(self.spine,(1, 1), flag = wx.EXPAND) + s.Add(wx.Size(self.spine.framesize.left,self.spine.framesize.top),(0,0)) + s.Add(wx.Size(self.spine.framesize.right,self.spine.framesize.bottom),(2,2)) + s.AddGrowableCol(1, 1) + s.AddGrowableRow(1, 1) + + self.callback = callback + + self.connected=None + + def SetSkinKey(self, key): + """shortcut to spine.updateskin""" + self.spine.SetSkinKey(key) + + def SetWidth(self,width): + """Set the width of the menu in pixels""" + + self.staticwidth = True + + w=width + + if self.minwidth: + w = max(w,self.minwidth) + + if self.maxwidth: + w = min(w,self.maxwidth) + + self.width = w + self.Size = wx.Size(w,-1) + + def OnSize(self, event): + event.Skip() + +# if hasattr(self.spine,'framebg'): + background= self.spine.framebg + from cgui import SplitImage4 + if isinstance(background,SplitImage4): + ApplySmokeAndMirrors(self, background.GetBitmap(self.Size)) + else: + ApplySmokeAndMirrors(self) + + self.Layout() + + def OnPaint(self,event): + """Draws the background of the menu""" + dc = wx.PaintDC(self) + rect = wx.RectS(self.Size) + + bg = self.spine.framebg + if bg: + bg.Draw(dc, rect) + elif self.spine.native: + dc.Brush = wx.TRANSPARENT_BRUSH + dc.Pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWFRAME)) + dc.DrawRectangleRect(rect) + + #self.DrawNativeLike(dc,0,0,rect) + + def GetIndex(self, item): + try: + return self.spine.items.index(item) + except ValueError: + return -1 + + def Insert(self, index, *args, **kwargs): + 'Insert a new item into the menu.' + + self.InsertItem(index, SimpleMenuItem(*args, **kwargs)) + + def Append(self, *args, **kwargs): + 'Appends a new item to the end of the menu.' + + self.AppendItem(SimpleMenuItem(*args, **kwargs)) + + def InsertItem(self, index, item): + 'Insert an item to an index.' + + self.spine.items.insert(index,item) + self.spine.ItemCount=len(self.spine.items) + + def AppendItem(self, item): + 'Adds an item to the end of the menu.' + + self.spine.items.append(item) + self.spine.ItemCount = len(self.spine.items) + + def RemoveItem(self, item): + 'Remove the item provided from the menu.' + + sp = self.spine + + if isinstance(item, int): + item = sp.items[item] + + sp.Selection=-1 + sp.items.remove(item) + sp.ItemCount = len(self.spine.items) + + def RemoveAll(self): + """ + removes all items from the menu + """ + self.spine.items=[] + self.spine.ItemCount=len(self.spine.items) + + def GetCount(self): + 'Returns the number of items in the menu.' + + return len(self.spine.items) + + Count = property(GetCount) + + __len__ = GetCount + + def SetSelection(self, selection): self.spine.Selection = selection + def GetSelection(self): return self.spine.Selection + + Selection = property(GetSelection, SetSelection) + + def SetItems(self, items): + 'Set the menu to a list of items.' + + if wx.IsDestroyed(self): + print >> sys.stderr, "WARNING: %r is destroyed" % self + return + + with self.Frozen(): + + sp = self.spine + + sp.items = items + sp.ItemCount = len(items) + + if sp.IsShownOnScreen(): + sp.RefreshAll() + + def GetItem(self, index): + return self.spine.items[index] + + def GetItems(self): + return self.spine.items + + def GetItemIndex(self, item): + return self.spine.items.index(item) + + def FindItemById(self, id): + '''Returns a SimpleMenuItem with a matching id, or None.''' + for item in self.spine.items: + if item.id == id: + return item + + def PassEvent(self,event): + """ + A shortcut to self.Parent.AddPendingEvent(event) (Why?) + """ + self.Parent.AddPendingEvent(event) + + def DelayedDisplay(self, rect, *args, **kwargs): + if not self.displaytimer.IsRunning(): + self.displaytimer.Start(rect, *args, **kwargs) + + def Display(self, caller = None, funnle = True,funnlefullscreen=False): + """ + Display the menu + """ + + self.BeforeDisplay() + + if not self.IsShown() and len(self): + + self.spine.CalcSize() + + if caller and isinstance(caller, SimpleMenuSpine): + + self.caller = None + rect = caller.GetSelectionRect() + + position = Point(rect.x+rect.width,rect.y - self.spine.framesize.top) + newrect = RectPS(position, self.ScreenRect.Size) + screenrect = Monitor.GetFromRect(newrect).Geometry + + if newrect.bottom > screenrect.bottom: + position.y=rect.y+rect.height-self.Size.height + if newrect.right > screenrect.right: + position.x=rect.x-self.Size.width + + elif caller: + self.caller = caller + caller_rect = caller.ScreenRect + position = caller_rect.BottomLeft + newrect = RectPS(position, self.ScreenRect.Size) + screenrect = Monitor.GetFromWindow(caller).Geometry + + if newrect.bottom > screenrect.bottom: + position.y -= caller_rect.Height + self.spine.Size.height + if newrect.right > screenrect.right: + position.x += caller_rect.Width - self.spine.Size.width + + else: + self.caller = None + position = wx.GetMousePosition() + newrect = RectPS(position, self.ScreenRect.Size) + screenrect = Monitor.GetFromPoint(position).Geometry + + if newrect.bottom > screenrect.bottom: + position.y -= self.spine.Size.height + if newrect.right > screenrect.right and pref('menus.shift_mode', False): + position.x -= self.spine.Size.width + + newrect = wx.RectPS(position,self.Size) + screenrect = Monitor.GetFromRect(newrect).Geometry + pos = screenrect.Clamp(newrect).Position if funnle else position + self.SetRect(RectPS(pos, self.Size)) + + self.spine.SetSelection(-1) + + fadein(self, 'xfast') + self.spine.RefreshAll() + + if not self.spine.HasCapture(): + self.spine.CaptureMouse() + + wx.CallLater(10, self.Refresh) + + if not isinstance(caller, SimpleMenuSpine): + self.TopConnect() + + def TopConnect(self): + # Find the first TopLevelWindow owner of this menu + # (popup menus are not isinstance(popup, wx.TopLevelWindow) + t = self.Top + while not isinstance(t, wx.TopLevelWindow): + t = t.Parent.Top + + id = t.Id + t.Connect(id, id, wx.wxEVT_ACTIVATE, self.OnActiveChange) + self.connected=(t,id) + + def OnActiveChange(self,event): + self.CloseRoot() + + def OnClose(self,event): + """ + Handels hiding of submenus and deactivating caller if aplicable + when the menu is closed + """ + + if not self.IsShown(): + do(item.menu.Show(False) for item in self.spine.items if item.menu and item.menu.IsShown()) + while self.spine.HasCapture():self.spine.ReleaseMouse() + + if self.caller:# and hasattr(self.caller,'Active'): + event=wx.MenuEvent(wx.wxEVT_MENU_CLOSE,self.Id) + self.caller.AddPendingEvent(event) + self.caller = None + + if self.connected: + window, id = self.connected + window.Disconnect(id, id, wx.wxEVT_ACTIVATE) + self.connected = None + + def CloseRoot(self): + """ + Closes the root menu, that then handles closing of it's children + """ + if isinstance(self.Parent,SimpleMenu): + self.Parent.CloseRoot() + else: + self.Show(False) + + def __repr__(self): + return '' % self.spine.items + + def CheckParentalContact(self,pos,ignoresubmenu=False): + """ + Checks if the mouse is inside one of the parent menus of this menu + """ + rect = RectS(self.spine.Size) + mp=self.spine.ScreenToClient(pos) + if rect.Contains(mp) and (ignoresubmenu or not self.spine.items[self.spine.HitTest(mp)].menu or not self.spine.items[self.spine.HitTest(mp)].menu.IsShown()): + return True + if isinstance(self.Parent, SimpleMenu): + return self.Parent.CheckParentalContact(pos, ignoresubmenu) + return False + + def CascadeRelease(self): + """ + All menus in hierarchy release the mouse + """ + while self.spine.HasCapture(): + self.spine.ReleaseMouse() + if isinstance(self.Parent,SimpleMenu): + self.Parent.CascadeRelease() + + def CascadeCapture(self): + """ + All menus in hierarchy capture the mouse + """ + if isinstance(self.Parent,SimpleMenu): + self.Parent.CascadeCapture() + if not self.spine.HasCapture(): + self.spine.CaptureMouse() + + +class SimpleMenuItem(object): + "Menu Item for the combo box" + + def __init__(self, content = '', method = None, + font = None, id = None, menu = None): + """ + content - string or list of bitmaps and strings that are displayed as item lable + method - methode exicuted when the item is slected + id - unique ID number for the item, if left none is not generated, so + if you need an id set it + """ + + assert isinstance(content,(basestring,list,)), 'content of type %s is not a string or list' % type(content) + + if content is not None: + if isinstance(content, basestring): # For strings, wrap in a list + self.content = [content] + elif isinstance(content,list): + self.content = content + else: + raise TypeError + else: self.content = [] + self.method = method + self.menu = menu + self.font = font + + self.id=id + + def __repr__(self): + return '' % self.GetContentAsString() + + def __str__(self): + return self.GetContentAsString() + + def __eq__(self, o): + return isinstance(o, self.__class__) and self.content == o.content + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(id(self)) + + def GetContentAsString(self): + "Returns first string in this menu item's content, or an empty string." + + for thing in self.content: + if isinstance(thing, basestring): + return thing + + return '' + + def GetContentWidth(self,menu): + if not menu: + return None + + pad = menu.spine.padding + font = self.font or menu.spine.Font + cont = self.content + + count = len(cont) + + w = sum(GetTextWidth(s,font) for s in cont if isinstance(s,basestring)) + w += sum(b.Width for b in cont if type(b) is Bitmap) + + w += (count+1)*pad.x+1 + + return w + + def SetLabel(self, label): + '''Changes the first string item in self.content to the given label.''' + + for i, item in enumerate(self.content): + if isinstance(item, basestring): + self.content[i] = label + return + diff --git a/digsby/src/gui/uberwidgets/skinnedpanel.py b/digsby/src/gui/uberwidgets/skinnedpanel.py new file mode 100644 index 0000000..40f2ccd --- /dev/null +++ b/digsby/src/gui/uberwidgets/skinnedpanel.py @@ -0,0 +1,46 @@ +import wx +from wx import BufferedPaintDC,RectS +from gui.uberwidgets.uberwidget import UberWidget +from gui.uberwidgets.UberButton import UberButton +from util.primitives.funcs import Delegate + +class SkinnedPanel(wx.Panel,UberWidget): + ''' + Simple skinnable wxPanel + Depricated - Replaced with SimplePanel from cgui + ''' + def __init__(self, parent, key): + wx.Panel.__init__(self, parent, style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE) + + self.SetSkinKey(key,True) + + Bind = self.Bind + Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + Bind(wx.EVT_PAINT, self.OnPaint) + + self.ChildPaints = Delegate() + + def UpdateSkin(self): + from gui import skin + + skinkey = self.skinkey + + native = self.native = not skinkey + + if not native: + self.bg = skin.get(skinkey+".background") + self.itemskin = skin.get(skinkey+".itemskin") + + for child in self.Children: + if isinstance(child,UberButton): + child.SetSkinKey(self.itemskin) + + self.Refresh(False) + + + def OnPaint(self, e): + dc = BufferedPaintDC(self) + + if self.bg: + self.bg.Draw(dc, RectS(self.ClientSize)) + self.ChildPaints(dc) diff --git a/digsby/src/gui/uberwidgets/skinsplitter.py b/digsby/src/gui/uberwidgets/skinsplitter.py new file mode 100644 index 0000000..805e7f6 --- /dev/null +++ b/digsby/src/gui/uberwidgets/skinsplitter.py @@ -0,0 +1,55 @@ +import cgui +from gui.skin import get as skinget +from wx import Colour, SYS_COLOUR_WINDOW +import wx +GetColour = wx.SystemSettings.GetColour + +class SkinSplitter(cgui.SkinSplitter): + ''' + UI for two panels that share the same space alowing the user to adjust how much of the space is alotted to each + ''' + def __init__(self, parent, style): + cgui.SkinSplitter.__init__(self, parent, style) + self.UpdateSkin() + + def SplitHorizontally(self, *a, **k): + cgui.SkinSplitter.SplitHorizontally(self, *a, **k) + self.UpdateSkin() + + def SplitVertically(self, *a, **k): + cgui.SkinSplitter.SplitVertically(self, *a, **k) + self.UpdateSkin() + + def UpdateSkin(self): + mode = self.SplitMode + + if mode == wx.SPLIT_HORIZONTAL: + splitskin = skinget('HorizontalSizerBar', None) + else: + splitskin = skinget('VerticalSizerBar', None) + if splitskin is None: + splitskin = skinget('HorizontalSizerBar', None) + + if splitskin is None or (isinstance(splitskin, basestring) and splitskin.lower().strip() == 'native') or not hasattr(splitskin, 'get'): + self.SetSashSize(-1) + self.SetNative(True) + return + + try: sash_size = int(splitskin.thickness) + except: sash_size = -1 + + syscol = GetColour(SYS_COLOUR_WINDOW) + + bgs = splitskin.get('backgrounds', {}) + + normal = bgs.get('normal', syscol) + active = bgs.get('active', syscol) + hover = bgs.get('hover', syscol) + + if not isinstance(normal, Colour): normal = syscol + if not isinstance(active, Colour): active = syscol + if not isinstance(hover, Colour): hover = syscol + + self.SetSashSize(sash_size) + self.SetSplitterColors(normal, active, hover) + self.SetNative(False) \ No newline at end of file diff --git a/digsby/src/gui/uberwidgets/skintextctrl.py b/digsby/src/gui/uberwidgets/skintextctrl.py new file mode 100644 index 0000000..2f42804 --- /dev/null +++ b/digsby/src/gui/uberwidgets/skintextctrl.py @@ -0,0 +1,36 @@ +import wx + + +#TODO: Make an UberWidget? +class SkinTextCtrl(wx.TextCtrl): + ''' + Only used for email subject line + Should be deprecated and replaced + ''' + def __init__(self, parent, **k): + + self.skinkey = k.pop('skinkey', None) + self.skinkey_bg = k.pop('skinkey_bg', None) + + wx.TextCtrl.__init__(self, parent, **k) + self.UpdateSkin() + + def UpdateSkin(self): + from gui import skin + if self.skinkey is not None: + loc, key = self.skinkey + + font = skin.get('%s.Fonts.%s' % (loc, key), lambda: self.Font) + color = skin.get('%s.FontColors.%s' % (loc, key), lambda: self.BackgroundColour) + + + self.SetForegroundColour(color) + + # ignore bold/italics/underline, since this is a text control + f = self.Font + f.SetFaceName(font.FaceName) + f.SetPointSize(font.PointSize) + self.Font = f + + if self.skinkey_bg is not None: + self.SetBackgroundColour(skin.get(self.skinkey_bg, lambda: self.BackgroundColour)) diff --git a/digsby/src/gui/uberwidgets/spacerpanel.py b/digsby/src/gui/uberwidgets/spacerpanel.py new file mode 100644 index 0000000..7882f30 --- /dev/null +++ b/digsby/src/gui/uberwidgets/spacerpanel.py @@ -0,0 +1,40 @@ +from gui.skin.skinobjects import SkinColor +import wx +from gui.prototypes.newskinmodule import NewSkinModule + + +SpacerPanelSkinDefaults = { + 'background' : lambda: SkinColor(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)), + 'size' : lambda: wx.Point(2,2) +} + +class SpacerPanel(wx.Panel, NewSkinModule): + ''' + Small spacer to visually separate UI components + ''' + def __init__(self, parent, skinkey = None): + wx.Panel.__init__(self, parent) + + + self.SetSkinKey(skinkey, SpacerPanelSkinDefaults) + + + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def DoUpdateSkin(self, skin): + self.skinSP = skin + + self.SetMinSize(self.skinSP['size']) + self.Parent.Layout() + + def GetSkinProxy(self): + return self.skinSP if hasattr(self, 'skinSP') else None + + def OnPaint(self, event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + + self.skinSP['background'].Draw(dc, rect) + + + diff --git a/digsby/src/gui/uberwidgets/uberbook/OverlayImage.py b/digsby/src/gui/uberwidgets/uberbook/OverlayImage.py new file mode 100644 index 0000000..ca7abf1 --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/OverlayImage.py @@ -0,0 +1,135 @@ +import wx +from math import radians +from util.primitives.funcs import do +from gui.windowfx import ApplySmokeAndMirrors +from common import pref +from logging import getLogger; log = getLogger('OverlayImage') + +class SimpleOverlayImage(wx.PopupWindow): + """ + Used for tab previews when dragging them around + """ + def __init__(self,parent,host): + """ + Usses the OnPaint function of host to draw and region itself + host.OnPaint(otherdc,otherwindow) + """ + wx.PopupWindow.__init__(self,parent) + + events=[ + (wx.EVT_PAINT, self.OnPaint) + ] + do(self.Bind(event, method) for (event,method) in events) + + self.host=host + self.Size=host.Size + + def OnPaint(self,event): + if not wx.IsDestroyed(self): + self.host.OnPaint(otherdc = wx.PaintDC(self), otherwindow = self) + + def Transition(self,dest): + """ + Animated move to destination (x,y) + NOT YET IMPLEMENTED!!! + """ + pass + + def Teleport(self,dest): + """ + Move to location, but uses center point as opposed to upper left + """ + self.Move((dest[0]-(self.Size.width/2),dest[1]-(self.Size.height/2))) + self.Refresh() + + @property + def alpha(self): + return pref('tabs.preview_alpha',200) + +class OverlayImage(wx.PopupWindow): + """ + Image that overlaps the window + """ + def __init__(self,parent,image,size= wx.Size(-1,-1),rot=0): + """ + image - wx.Image of the item + """ + wx.PopupWindow.__init__(self,parent) + + events=[ + (wx.EVT_PAINT,self.onPaint), + (wx.EVT_MOVE,self.OnMove) + ] + do(self.Bind(event, method) for (event,method) in events) + + self.parent=parent + + self.rot = rot + + if size != wx.Size(-1, -1): + self.SetSize(size) + + if isinstance(image, wx.Bitmap): + self.bitmap = image + else: + self.SetImage(image, size) + + def SetImage(self, image, size = wx.Size(-1,-1)): + log.info('Overlay Image has been updated') + + self.image = image + prebitmap = wx.ImageFromBitmap(image.GetBitmap(size)) + prebitmap.ConvertAlphaToMask() + self.bitmap = wx.BitmapFromImage(prebitmap) + + self.width, self.height = self.bitmap.Width, self.bitmap.Height + self.GenBitmap() + + def OnMove(self,event): + self.Refresh() + + def onPaint(self,event): + dc = wx.PaintDC(self) + dc.DrawBitmap(self.bitmap,0,0,False) + + def GenBitmap(self): + """ + Generates a local cached bitmap from the bitmap + then sets the region + """ + if self.rot: + self.bitmap=wx.BitmapFromImage(self.bitmap.ConvertToImage().Rotate(radians(90*self.rot),(0,0))) + if self.Size != (self.bitmap.Width+1,self.bitmap.Height+1): + wx.PopupWindow.SetSize(self,(self.bitmap.Width+1,self.bitmap.Height+1)) + ApplySmokeAndMirrors(self, self.bitmap) + + + def SetBitmapSize(self,size): + 'Change the size of the image, sizes 0 and lower keep it the same.' + + if size == self.Size: return + + if size[0] > 0: self.width = size[0] + if size[1] > 0: self.height = size[1] + + prebitmap=self.image.GetBitmap((self.width,self.height)).ConvertToImage() + prebitmap.ConvertAlphaToMask() + self.bitmap=wx.BitmapFromImage(prebitmap) + self.GenBitmap() + + + def SetRotation(self,rot=0): + self.rot=rot + + def Transition(self,dest): + """ + Animated move to destination (x,y) + NOT YET IMPLEMENTED!!! + """ + pass + + def Teleport(self,dest): + """ + Move to location, but uses center point as opposed to upper left + """ + self.Move((dest[0]-(self.Size.width//2),dest[1]))#-(self.Size.height//2) diff --git a/digsby/src/gui/uberwidgets/uberbook/SamplePanel.py b/digsby/src/gui/uberwidgets/uberbook/SamplePanel.py new file mode 100644 index 0000000..cb103b7 --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/SamplePanel.py @@ -0,0 +1,25 @@ +import wx +from gui.toolbox import get_wxColor +from util.primitives.funcs import do +class SamplePanel(wx.Panel): + """ + This is a panel that is provided a color, used as a sample place holder + stores one variable, name which is the color provided, and is filled with + that color + """ + def __init__(self, parent, color="red"): + wx.Panel.__init__(self, parent, style=0) + self.name=color + + events=[ + (wx.EVT_PAINT, self.OnPaint), + (wx.EVT_ERASE_BACKGROUND, lambda e:None) + ] + do(self.Bind(event, method) for (event, method) in events) + + def OnPaint(self, event): + dc=wx.AutoBufferedPaintDC(self) + rect=wx.RectS(self.GetSize()) + + dc.SetBrush(wx.Brush(get_wxColor(self.name))) + dc.DrawRectangleRect(rect) diff --git a/digsby/src/gui/uberwidgets/uberbook/UberBook.py b/digsby/src/gui/uberwidgets/uberbook/UberBook.py new file mode 100644 index 0000000..0f96afc --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/UberBook.py @@ -0,0 +1,240 @@ +from __future__ import with_statement +import wx +from containers import PageContainer +from dragtimer import WinDragTimer +from OverlayImage import SimpleOverlayImage +from tab import MultiTab +from tabbar import TabBar +from tabmanager import TabManager +from common import pref, profile, setpref +from logging import getLogger; log = getLogger('uberbook') + +splitStyle = wx.SP_NOBORDER | wx.SP_LIVE_UPDATE + +wdt = WinDragTimer() +tabman = TabManager() + +import config + +if config.platform == 'win': + + import cgui + class UberBookTabController(cgui.TabController): + def OnTabClosed(self, tab): + ubertab = tab.Window.Tab + ubertab.CloseTab() + + + def OnTabActivated(self, tab): + ubertab = tab.Window.Tab + ubertab.SetActive(True) + + tlw = tab.Window.Top + tlw.Show() + if tlw.IsIconized(): + tlw.Iconize(False) + + # fake an EVT_ICONIZE to the im window here + if hasattr(tlw, 'OnIconize'): + from util import Storage as S + tlw.OnIconize(S(Skip=Null, Iconized=lambda: False)) + + tlw.Raise() + +def install_taskbar_tabs(notebook, preview_type): + import gui.native.win.taskbar as tb + + nb = tb.get_tab_notebook(notebook.Top) + notebook.did_add += lambda win: nb.CreateTab(win, preview_type()) + notebook.did_remove += nb.DestroyTab + notebook.did_rearrange += nb.RearrangeTab + notebook.did_activate += nb.SetTabActive + notebook.did_seticon += lambda page, icon: nb.SetTabIcon(page, icon) if icon is not None else wx.NullBitmap + notebook.did_settitle += nb.SetTabTitle + + if False: # debugging + def foo(s): + def handler(*a, **k): + print '#'*80 + print notebook, s + print a, k + return handler + + notebook.did_add += foo('did_add') + notebook.did_remove += foo('did_remove') + notebook.did_rearrange += foo('did_rearrange') + +class NoteBook(wx.SplitterWindow): + ''' + This is a redesign of wxWidgets NoteBook class, specially designed with + skinning amd customizability in mind. Also introduces many new features + such as close buttons on the tab and bar, drag and drop rearranging and + the ability to be broken out into other windows (already existing or spawned) + ''' + + def __init__(self, parent, skinkey, preview=None): + """ + parent - Direct ascendant + skinkey - String key for the skin + """ + #wx.Panel.__init__(self, parent, style = 0) + wx.SplitterWindow.__init__(self, parent, style = splitStyle) + + self.window = parent.Top + + #These notebooks share a singular tabmanager that handels moving tabs between windows + #TODO: This should probably be passed in so there can be other tabbed windows that aren't interchangeable + global tabman + self.manager = tabman + + self.winman = None + self.manager.Register(self) + + self.tabbar = TabBar(self, skinkey) + self.pagecontainer = PageContainer(self) + + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e:None) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.Bind(wx.EVT_LEFT_UP, self.OnSplitMove) + self.Bind(wx.EVT_CLOSE, self.OnClose) + + self.side_tabs_width = pref('tabs.side_tabs_width', 100) + profile.prefs.link('tabs.side_tabs', self.OnSideTabsSwitch) + + from util.primitives.funcs import Delegate + self.did_add = Delegate() + self.did_remove = Delegate() + self.did_rearrange = Delegate() + self.did_activate = Delegate() + self.did_seticon = Delegate() + self.did_settitle = Delegate() + + if config.platform == 'win': + install_taskbar_tabs(self, preview) + + def OnClose(self, event): + 'Disconnect us and prepare for shutdown.' + self.manager.UnRegister(self) + event.Skip() + + @property + def ActiveTab(self): + 'Returns the currently active tab in this notebook.' + + return self.tabbar.ActiveTab + + def GetTabCount(self): + 'Returns the number of tabs.' + + return self.tabbar.GetTabCount() + + def Pages(self): + return [t.page for t in self.tabbar.tabs if t and not wx.IsDestroyed(t)] + + def NextTab(self): + return self.tabbar.NextTab() + + def PrevTab(self): + return self.tabbar.PrevTab() + + def UpdateSkin(self): + pass + + def Split(self, val): + if val: + if self.Window2 is None: + self.OnSideTabsSwitch(pref('tabs.side_tabs')) + else: + if self.Window2 is not None: + self.Unsplit(self.tabbar) + + def OnSideTabsSwitch(self, val): + self.side_tabs = val + split = getattr(self, 'Split' + ('Horizontally' if not val else 'Vertically')) + + if self.IsSplit(): + self.Unsplit() + + pos = self.side_tabs_width if val else self.tabbar.MinSize.height + split(self.tabbar, self.pagecontainer, pos) + + self.SetSashSize(5 if val else 0) + + + def OnSplitMove(self, event): + 'Invoked when the left mouse button is up on the splitter.' + + event.Skip() + + if self.side_tabs and self.IsSplit() and self.IsShownOnScreen(): + pos = self.SashPosition + + self.side_tabs_width = pos + log.info("saving side tabs sash pos of %s", pos) + setpref('tabs.side_tabs_width', pos) + + + def __repr__(self): + return '' % self.tabbar + + def Add(self, panel, focus = None): + """ + Add a panel to the notebook, just call this and the rest of the process + of displaying should be automated + + panel - the panel that will be added to a page in the notebook + focus - should that page take focus away from the current page + """ + with self.Frozen(): + if focus or not self.tabbar.GetTabCount(): + focus = True + + elif not focus: + focus = False + + page = self.pagecontainer.Append(panel) + s = self.tabbar.Add(page, focus) + self.did_add(panel) + if page.icon: + self.did_seticon(panel, page.icon) + self.pagecontainer.Layout() + + return s + + def Insert(self, page, resort = True): + """ + Insert a pre-existing page into the notebook + """ + self.pagecontainer.Append( page ) + self.tabbar.Add(page, True, resort) + self.tabbar.dragorigin = page.tab + self.did_add(page.panel) + self.tabbar.DragFinish(True) + + def Remove(self, page): + 'Remove the specified page from the notebook.' + with self.Frozen(): + if self.ActiveTab == page.tab: + self.tabbar.PrevTab() + self.tabbar.Remove(page.tab) + self.pagecontainer.Layout() + + def CloseTab(self, tab): + 'Remove the specified page from the notebook.' + with self.Frozen(): + if self.ActiveTab == tab: + self.tabbar.PrevTab() + tab.CloseTab() + self.pagecontainer.Layout() + + def StartWindowDrag(self): + 'Called when the window is moved to prep for merging into another window.' + + if wx.LeftDown(): + global wdt + if not wdt.IsRunning(): + self.preview = SimpleOverlayImage(self, MultiTab(self.tabbar.tabs)) + wdt.Start(self) + + self.manager.ReadyBook(self, wx.GetMousePosition()) diff --git a/digsby/src/gui/uberwidgets/uberbook/__init__.py b/digsby/src/gui/uberwidgets/uberbook/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/uberwidgets/uberbook/containers.py b/digsby/src/gui/uberwidgets/uberbook/containers.py new file mode 100644 index 0000000..9016fc5 --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/containers.py @@ -0,0 +1,56 @@ +from page import Page +import wx +from wx import BoxSizer, VERTICAL, EXPAND +from cgui import SimplePanel + +class PageContainer(SimplePanel): + 'Stores and displays pages.' + + def __init__(self, parent): + SimplePanel.__init__(self, parent) + self.SetSizer(BoxSizer(VERTICAL)) + self.Bind(wx.EVT_SIZE, self.OnSize) + self._active = None + + def OnSize(self,event): + event.Skip() + if self._active: + wx.CallAfter(lambda: wx.CallAfter(self._active.SetSize, self.Size)) + + def Append(self, panel_or_page): + ''' + Note: should only be called by UberBook and TabManager + + Creates a page from the panel provided if not already a page + Adds that page to the container + Returns that page to the parent notebook for tab generation + ''' + + if isinstance(panel_or_page, Page): + page = panel_or_page + page.Reparent(self) + else: + page = Page(self, panel_or_page) + + page.Size = self.Size + return page + + def GetActive(self): + 'Returns the active page.' + + return self._active + + def SetActive(self, source): + "A way for a page to set itself active." + + if self._active!=source: + if self._active: + self._active.Hide() + + self._active = source + self._active.Size = self.Size + print 'calling show on', self._active + self._active.Show() + self.Layout() + + active = property(GetActive, SetActive) \ No newline at end of file diff --git a/digsby/src/gui/uberwidgets/uberbook/dragtimer.py b/digsby/src/gui/uberwidgets/uberbook/dragtimer.py new file mode 100644 index 0000000..ffda49e --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/dragtimer.py @@ -0,0 +1,100 @@ +import wx +from gui.windowfx import fadein, fadeout +from weakref import ref + + +class DragTimer(wx.Timer): + """ + A timer handling the movement of the tab preview, Dropmarker hiding, + and showing of hidden tabbars when dragged over a window. + """ + def __init__(self,manager): + wx.Timer.__init__(self) + self.manager=manager + + self.target=None + + def Start(self,overlayimage): + """ + Fade in overlay and start timer + """ + wx.Timer.Start(self,1) + self.overlay=overlayimage + self.overlay.Move(wx.GetMousePosition()+(2,4)) + fadein(self.overlay,to=self.overlay.alpha) + + def GetFrame(self, window): + """ + Finds the frame at the pointer + """ + if window: + while not isinstance(window,wx.Frame): + window=window.Parent + + return window + + def Notify(self): + """ + On the trigger time does D&D operations + """ + + current=self.GetFrame(wx.FindWindowAtPointer()) + + #Toggles tabbar of the window + if current and current != self.target: + if hasattr(self.target,'notebook'): self.target.notebook.tabbar.Toggle(False) + self.target=current + if hasattr(self.target,'notebook'): + self.target.notebook.tabbar.Toggle(True) + elif not current: + if hasattr(self.target,'notebook'): self.target.notebook.tabbar.Toggle(False) + self.target=current + + #move overlay with mouse + self.overlay.Move(wx.GetMousePosition()+(2,4)) + + #hides dropmarker if no place to drop + if (not self.manager.destination or not self.manager.destination.tabbar.Rect.Contains(self.manager.destination.ScreenToClient(wx.GetMousePosition()))) and not self.manager.source.tabbar.Rect.Contains(self.manager.source.ScreenToClient(wx.GetMousePosition())): + self.manager.ShowDropMarker() + #trigers dragand drop stuff if mouse button goes up + if not wx.LeftDown(): + self.manager.Trigger() + self.Stop() + + def Stop(self): + """ + Stops the timer + fade out and del overlay + toggle target tabbar + """ + if self.target and hasattr(self.target,'notebook'): + self.target.notebook.tabbar.Toggle(False) + self.target = None + wx.Timer.Stop(self) + fadeout(self.overlay) + del self.overlay + +class WinDragTimer(wx.Timer): + 'Handles drag and drop release with entire windows.' + + notebook = property(lambda self: self._notebook() if self._notebook is not None else None, + lambda self, nb: setattr(self, '_notebook', ref(nb))) + + def __init__(self,*a,**k): + self._notebook = None + wx.Timer.__init__(self) + + def Start(self,notebook): + 'Sets the notebook and starts the timer.' + + self.notebook = notebook + wx.Timer.Start(self, 1) + + def Notify(self): + 'If mouse is released, start transaction.' + + if not wx.LeftDown(): + self.Stop() + nb = self.notebook + if nb is not None: + nb.manager.Transaction(nb) diff --git a/digsby/src/gui/uberwidgets/uberbook/navigation_arrows.py b/digsby/src/gui/uberwidgets/uberbook/navigation_arrows.py new file mode 100644 index 0000000..f258c52 --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/navigation_arrows.py @@ -0,0 +1,179 @@ +import wx +from gui.uberwidgets.UberButton import UberButton +from util.primitives.funcs import do +from common import pref +#from gui.windowfx import SmokeAndMirrorsBomb + + +CLOSEID=wx.NewId() +PREVID=wx.NewId() +NEXTID=wx.NewId() +UPID=wx.NewId() +DOWNID=wx.NewId() + + +class Navi(wx.Panel): + """ + Container for in bar close button and navigation arrows + """ + def __init__(self, parent): + """ + standard fair + """ + wx.Panel.__init__(self, parent, style=0) + + events=[ + (wx.EVT_PAINT,self.OnPaint), + (wx.EVT_ERASE_BACKGROUND, lambda e:None), + (wx.EVT_BUTTON, self.OnButton) + ] + do(self.Bind(event, method) for (event, method) in events) + + #make sizers + self.Sizer=wx.BoxSizer(wx.HORIZONTAL) + self.hsizer=wx.BoxSizer(wx.HORIZONTAL) + self.vsizer=wx.BoxSizer(wx.VERTICAL) + + #make Buttons + self.closebutton=UberButton(self, CLOSEID, skin=self.Parent.closebuttonskin, icon=self.Parent.closeicon) + self.prevb=UberButton(self, PREVID, skin=self.Parent.scrollbuttonskin, icon=self.Parent.lefticon) + self.nextb=UberButton(self, NEXTID, skin=self.Parent.scrollbuttonskin, icon=self.Parent.righticon) + self.upb=UberButton(self, UPID, skin=self.Parent.scrollbuttonskin, icon=self.Parent.upicon) + self.downb=UberButton(self, DOWNID, skin=self.Parent.scrollbuttonskin, icon=self.Parent.downicon) + + #add butons to sizers + self.hsizer.Add(self.prevb, 0, wx.EXPAND) + self.hsizer.Add(self.nextb, 0, wx.EXPAND) + self.vsizer.Add(self.upb, 1, wx.EXPAND) + self.vsizer.Add(self.downb, 1, wx.EXPAND) + self.Sizer.Add(self.hsizer, 0, wx.EXPAND) + self.Sizer.Add(self.closebutton, 0, wx.CENTER|wx.ALL, 5) + + #Hide all buttons + self.prevb.Show(False) + self.nextb.Show(False) + self.upb.Show(False) + self.downb.Show(False) + self.closebutton.Show(pref('tabs.tabbar_x', False)) + + self.type=None + +# def UpdateSkin(self): +# p = self.Parent +# +# self.closebutton.SetSkinKey(p.closeskin) +# +# self.closebutton.SetIcon(p.closeicon) +# +# scrollskin = p.scrollbuttonskin +# +# self.prevb.SetSkinKey(scrollskin) +# self.nextb.SetSkinKey(scrollskin) +# self.upb.SetSkinKey(scrollskin) +# self.downb.SetSkinKey(scrollskin) +# +# self.prevb.SetIcon(p.lefticon) +# self.nextb.SetIcon(p.righticon) +# self.upb.SetIcon(p.upicon) +# self.downb.SetIcon(p.downicon) + + def Enabler(self): + """ + Enable/Disable each button based off of acceptable scrolling + """ + self.prevb.Enable(self.Parent.tabindex>0) + self.nextb.Enable(self.Parent.tabendex0) + self.downb.Enable(self.Parent.rowindex0: + self.Parent.tabindex-=1 + self.Parent.Regenerate(True) + elif event.GetId()==NEXTID: + endex=self.Parent.tabendex + if endex0: + self.Parent.rowindex-=1 + self.Parent.Regenerate(True) + elif event.GetId()==DOWNID: + if self.Parent.rowindex' % self.GetChildren() diff --git a/digsby/src/gui/uberwidgets/uberbook/tab.py b/digsby/src/gui/uberwidgets/uberbook/tab.py new file mode 100644 index 0000000..ebc8f58 --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/tab.py @@ -0,0 +1,711 @@ +import wx, sys +from util.primitives.funcs import do, Delegate +from util.introspect import debug_property +from common import pref, profile, prefprop +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.UberEvents import TabNotifiedEvent +from gui.textutil import default_font +from gui.windowfx import ApplySmokeAndMirrors +from gui import skin +from gui.skin.skinobjects import Margins + +from logging import getLogger; log = getLogger('tab') + +from wx import EmptyBitmap, Size, ImageFromBitmap, \ + WHITE_BRUSH, TRANSPARENT_PEN, BitmapFromImage, BLACK, NullBitmap, \ + RectS, ClientDC, MemoryDC, Rect, PaintDC, BufferedPaintDC, \ + GetMousePosition, ALIGN_LEFT, ALIGN_CENTER_VERTICAL, GetTopLevelParent, Mask, Point + + +class MultiTab(object): + """ + Used for generating a preview of multiple tabs, such as when a window + is about to be merged with another window + """ + def __init__(self, tabs): + + self.tabwidth = max(tab.Size.width for tab in tabs) + self.tabheight = tabs[0].Size.height + self.Size = Size(self.tabwidth + 14*len(tabs), self.tabheight + 14*len(tabs)) + + self.tabs=tabs + + def OnPaint(self, otherdc, otherwindow): + """ + Paints all tabs into the passed DC and a copy of the backgrounds + into a Bitmap to geneate the SmokeAndMirrors for other window + """ + size = self.Size + bitmap = EmptyBitmap(*size) + mdc = MemoryDC() + tab = self.tabs[0].states[self.tabs[0].mode][0].GetBitmap(Size(self.tabwidth,self.tabheight))#.Draw(mdc,rect) + tab = ImageFromBitmap(tab) + tab.ConvertAlphaToMask() + tab = BitmapFromImage(tab) + mdc.SelectObject(tab) + mdc.Brush = WHITE_BRUSH + mdc.Pen = TRANSPARENT_PEN + mdc.DrawRectangle(0, 0, self.tabwidth, self.tabheight) + mdc.SelectObject(bitmap) + + for i, tabobj in enumerate(self.tabs): + s = 14 * i + rect = Rect(s, s, self.tabwidth, self.tabheight) + mdc.DrawBitmap(tab, s, s, True) + tabobj.OnPaint(forcedrect = rect, otherdc = otherdc) + + mdc.SelectObject(NullBitmap) +# otherdc.DrawBitmap(bitmap,0,0,True) + bitmap.SetMask(Mask(bitmap, BLACK)) + ApplySmokeAndMirrors(otherwindow, bitmap) + + +class NotifiedTimer(wx.Timer): + """ + When a tab is in the notified state this handles blinking back and forth + between the notified look and normal look + """ + def __init__(self,tab): + wx.Timer.__init__(self) + self.drawnotified = False + self.tab = tab + self.click = 0 + + def Start(self): + self.click = 0 + + if not self.IsRunning(): + wx.Timer.Start(self, pref('tabs.notify_duration', 1000)) + + def Notify(self): + """ + When triggered notifies so many times, then sets it permanently + to notified + """ + self.drawnotified = not self.drawnotified + self.RefreshTab() + self.click += 1 + + if self.click >= pref('tabs.notify_count',10): + self.drawnotified = True + wx.Timer.Stop(self) + + def Stop(self): + """Stops the timer.""" + + self.drawnotified = False + self.RefreshTab() + wx.Timer.Stop(self) + + def RefreshTab(self): + if not wx.IsDestroyed(self.tab): + self.tab.Refresh() + +from gui.uberwidgets import UberWidget +class Tab(wx.Window, UberWidget): + """ + Tabs!!! + """ + + # tab states + NORMAL = 0 + ACTIVE = 1 + HOVER = 2 + ACTIVE_HOVER = 3 + NOTIFIED = 4 + + def __init__(self, parent, page, _id = -1, skinkey = ''): + wx.Window.__init__(self, parent, id=_id) + self.Show(False) + """ + icon - icon to place in corner of tab + """ + self.events=[ + (wx.EVT_PAINT, self.OnPaint), + (wx.EVT_ERASE_BACKGROUND, lambda e:None), + (wx.EVT_ENTER_WINDOW, self.OnMouseEnter), + (wx.EVT_LEAVE_WINDOW, self.OnMouseLeave), + (wx.EVT_RIGHT_DOWN, self.OnRightDown), + (wx.EVT_LEFT_DOWN, self.OnLeftDown), + (wx.EVT_MIDDLE_UP, self.OnMidUp), + (wx.EVT_LEFT_UP, self.OnLeftUp), + (wx.EVT_BUTTON, self.OnButton), + (wx.EVT_MOTION,self.OnMotion), + (wx.EVT_CLOSE, self.OnClose), + (wx.EVT_SIZE, self.OnSize) + ] + do(self.Bind(event, method) for (event, method) in self.events) + + + if sys.DEV and not isinstance(page.name, unicode): + msg = 'please only use unicode for labels: %r', page.name + + if pref('errors.nonunicode_buddy_names', type=bool, default=False): + raise TypeError(msg) + else: + log.warning(msg) + + + self.page=page + self.page.tab=self + + if sys.DEV and not isinstance(page.title, unicode): + msg = 'please only use unicode for labels: %r', page.title + + if pref('errors.nonunicode_buddy_names', type=bool, default=False): + raise TypeError(msg) + else: + log.warning(msg) + + self.label1 = self.page.title + + self.row=None + + + + self.notified = page.notified + self.drawnotified = NotifiedTimer(self) + self.focus = False + self.alignment = wx.ALIGN_LEFT + + #TODO: Check parent if should be active or as a pass in argument + self._active=False + self.clickage=False + + self.OnActive = Delegate() + + + self.fontcolors = ([None]*5, [None]*5) + self.states = ([None]*5, [None]*5) + self.state = 0 + self.mode = pref('tabs.side_tabs',False) + + self.SetSkinKey(skinkey,True) + + self.mark=None + + # Keeps a copy of what itself looks like. + self.bufferbitmap = None + + # When dragging, the overlay image preview of tabs + self.previewtabs = None + if self.notified: self.drawnotified.Start() + + #link prefs + profile.prefs.link('tabs.style', self.OnPrefChange, False) + profile.prefs.link('tabs.flip', self.OnPrefChange, False) + profile.prefs.link('tabs.max_width', self.OnPrefChange, False) + + def OnPrefChange(self,val): + self.Calcumalate() + + def UpdateSkin(self): + key = self.skinkey + s = lambda k, default = sentinel,mode=0: skin.get('%s%s.%s' % ('side'*mode,key, k), default) + + self.mode = pref('tabs.side_tabs',False) + + self.maxtabwidth = s('maxwidth',pref('tabs.max_width',100))#TODO: Completly remove pref? + + padd = s('padding', lambda: Point(0, 0)) + marg = s('margins', lambda: Margins([0,0,0,0])) + icsz = s('iconsize', 16) + font = s('font', lambda: default_font()) + spad = s('padding', padd, 1) + smar = s('margins', marg, 1) + sico = s('iconsize', icsz, 1) + sfnt = s('font', font, 1) + + self.padding = (padd,spad) + self.margins = (marg,smar) + self.iconsize = (icsz,sico) + self.font = (font,sfnt) + + states = self.states + states[0][0] = s('backgrounds.normal') + states[0][1] = s('backgrounds.active') + states[0][2] = s('backgrounds.hover', states[0][0]) + states[0][3] = s('backgrounds.activehover', states[0][2]) + states[0][4] = s('backgrounds.notify', states[0][0]) + + states[1][0] = s('backgrounds.normal', states[0][0],1) + states[1][1] = s('backgrounds.active', states[0][1],1) + states[1][2] = s('backgrounds.hover', states[0][2],1) + states[1][3] = s('backgrounds.activehover', states[0][3],1) + states[1][4] = s('backgrounds.notify', states[0][4],1) + + fc = self.fontcolors + fc[0][0] = s('fontcolors.normal', BLACK) + fc[0][1] = s('fontcolors.active', BLACK) + fc[0][2] = s('fontcolors.hover', fc[0][0]) + fc[0][3] = s('fontcolors.activehover', fc[0][2]) + fc[0][4] = s('fontcolors.notify', fc[0][0]) + + fc[1][0] = s('fontcolors.normal', fc[0][0],1) + fc[1][1] = s('fontcolors.active', fc[0][1],1) + fc[1][2] = s('fontcolors.hover', fc[1][0],1) + fc[1][3] = s('fontcolors.activehover', fc[1][2],1) + fc[1][4] = s('fontcolors.notify', fc[1][0],1) + + if pref('tabs.style', 2) and not hasattr(self, 'closebutton'): + self.GenCloseButton() + + if hasattr(self, 'closebutton'): + self.closebutton.SetSkinKey(self.Parent.closebuttonskin) + self.closebutton.SetIcon(self.Parent.closeicon) + + self.Calcumalate() + self.Refresh(False) + + def UpdateMode(self): + self.mode = pref('tabs.side_tabs',False) + self.closebutton.SetSkinKey(self.Parent.closebuttonskin,True) + self.closebutton.SetIcon(self.Parent.closeicon) + + self.Calcumalate() + self.Refresh(False) + + def OnSize(self,event): + self.Refresh(False) + + def __repr__(self): + return '' % self.label1 + + def GenCloseButton(self): + """ + Creates a close button from the skin + """ + self.closebutton = UberButton(self, + skin = self.Parent.closebuttonskin, + icon = self.Parent.closeicon, + size = (self.iconsize[self.mode], + self.iconsize[self.mode])) + if pref('tabs.style',2)==2: self.closebutton.Show(False) + + @debug_property + def Icon(self): + return self.page.Icon.Resized(self.iconsize[self.mode]).WXB + + def Calcumalate(self): + 'Tab layout calculations, sets cursor positions for the label, the icon, and the button.' + + #Create a DC for use as calculation reference + dc = ClientDC(self) + dc.Font=self.font[self.mode] + + #curent Horizantal placement position + xpad = self.padding[self.mode].x + xcurser = xpad + self.margins[self.mode].left + ypad = self.padding[self.mode].y + flip = pref('tabs.flip', False) + style = pref('tabs.style', 2) + icon = self.Icon + iconsize = self.iconsize[self.mode] + + #determine tab height + + label1 = self.label1 + if isinstance(label1, str): + label1 = label1.decode('fuzzy utf8') + + txtwh = dc.GetTextExtent(label1)[0] + ycurser = self.txtht = dc.Font.Height#sum([txtexts[1],txtexts[2],txtexts[3]]) + if (icon or style) and ycurser < iconsize: + ycurser=iconsize + ycurser += 2 * ypad + self.margins[self.mode].y + + + #Icon and button placement if on the left + if not flip: self.iconcurser=Point(xcurser, (ycurser-self.margins[self.mode].y)/2+self.margins[self.mode].top-iconsize/2) + #icon and + #else: self.iconcurser = 0 + if (style == 2 and not flip) or (style==1 and flip): + self.closebutton.Size = Size(iconsize,iconsize) + self.buttoncurser=self.iconcurser or Point(xcurser, (ycurser-self.margins[self.mode].y)/2+self.margins[self.mode].top-iconsize/2) + + if (icon and not flip) or (style==2 and not flip) or (style==1 and flip): + xcurser +=iconsize + xpad + + #Label placement + self.label1curser=Point(xcurser, (ycurser-self.margins[self.mode].y)/2+self.margins[self.mode].top-self.txtht/2) + xcurser += txtwh + xpad + + #adding space for right hand elements to be placed during painting + if (icon and flip) or (style==1 and not flip) or (style==2 and flip): xcurser+=iconsize + xpad + xcurser+=self.margins[self.mode].right + #setting tabs to just fit contents + + maxwidth = self.maxtabwidth + if maxwidth and maxwidth < xcurser: xcurser=maxwidth + self.gensize = (xcurser, ycurser) + self.SetMinSize(self.gensize) + #self.Parent.Generate() + + #print 'hey look, the close button is shown is a',(style==1),'statement' + self.closebutton.Show(style==1 or (style==2 and self.Rect.Contains(self.Parent.ScreenToClient(GetMousePosition())))) + + #print "calced tab size for",repr(self.label1),'at',self.gensize,' / ',self.Size + + + def OnPaint(self, event=None, forcedrect=None, otherdc=None, otherwindow=None): + """ + Painty goodness! + """ + size = forcedrect.Size if forcedrect else self.Size + rect = forcedrect or RectS(size) + iconsize = self.iconsize[self.mode] + + # Prep for clipping calculations + cliptangle = self.Rect + sx, sy = self.Rect.Position + sw, sh = size + ph = self.Parent.Size.height - 16 + + sidetabs, style, flip = pref('tabs.side_tabs', False), pref('tabs.style', 2), pref('tabs.flip', False) + + # calculates the clipping rectangle when in sidetabs mode and the tab + # goes over the bottom of the bar also manualy buffers in this situation + if sidetabs and event and sy + sh > ph : + cliptangle = Rect(sx, sy, sw, ph - sy) + cdc = PaintDC(self) + cdc.SetClippingRegion(0, 0, cliptangle.width, cliptangle.height) + buffer = EmptyBitmap(*self.Size) + dc = MemoryDC() + + dc.SelectObject(buffer) + else: + cdc = None + dc = otherdc or BufferedPaintDC(self) + + # figure out the correct current state + state = Tab.NOTIFIED if not self.active and self.notified and self.drawnotified.drawnotified else self.state + bg = self.states[self.mode][state if event else 0] + + bg.Draw(dc,rect) + if (event or otherwindow): + ApplySmokeAndMirrors(otherwindow or self, bg.GetBitmap(rect.Size)) + + # calc icon and button placement if on the right + icon = self.Icon + iconoffsetx = (iconsize - icon.Size.width) // 2 + iconoffsety = (iconsize - icon.Size.height) // 2 + + xpad = self.padding[self.mode].x + + # Place icon + if icon and flip: + self.iconcurser = Point(size.width - (iconsize + xpad + self.margins[self.mode].right), + (size.height - self.margins[self.mode].y) / 2 + self.margins[self.mode].top - iconsize // 2) + if (style == 1 and not flip) or (style == 2 and flip): + self.buttoncurser = Point(size.width - (iconsize + xpad + self.margins[self.mode].right), + (size.height - self.margins[self.mode].y)/2 + self.margins[self.mode].top - iconsize//2) + # Draw icon + if icon and ((style != 2 or not self.focus) or not event): + dc.DrawBitmap( icon, rect.x + self.iconcurser.x + iconoffsetx, rect.y+self.iconcurser.y + iconoffsety,True) + + # determines how many pixels have been drawn + filled = xpad + self.margins[self.mode].x + + if style: + filled += self.closebutton.Size.width + xpad + if icon and style !=2: + filled += iconsize + xpad + + # set up font properties + dc.SetFont(self.font[self.mode]) + dc.SetTextForeground(self.fontcolors[self.mode][state])#hook up font color + + # figure out space left for text + txtrct = Rect(rect.x + self.label1curser.x, rect.y + self.label1curser.y, + size.width - filled -xpad, self.txtht) + + + # draw label with truncating + dc.DrawTruncatedText(self.label1, txtrct,alignment = ALIGN_LEFT | ALIGN_CENTER_VERTICAL) + + if event: + # Set position of close button if actualy drawing the tab + if style: + self.closebutton.SetPosition(self.buttoncurser) + + # if manualy buffered separate DC then flush buffer + if cdc: + dc.SelectObject(NullBitmap) + cdc.DrawBitmap(buffer, 0, 0, True) + + def GetActive(self): + try: + return self._active + except AttributeError: + return False + + def SetActive(self, switch=None): + """ + If not active tells the page to run display code + should only be used to set active, does not handle all deactivate + functionality, instead use either parent.SetNextActive(self) or run + .SetActive(True) on another tab + """ + self._active = not self._active if switch is None else switch + + if self._active: + self.state = Tab.ACTIVE_HOVER if self.focus else Tab.ACTIVE + self.page.Display() + else: + self.state = Tab.HOVER if self.focus else Tab.NORMAL + + win = GetTopLevelParent(self) + + page = self.page + icon = page.icon + + title = page.window_title if page.window_title is not None else page.title + if title is not None: + win.Title = title + + if icon is not None: + win.SetFrameIcon(icon) + + self.SetNotify(False) + self.OnActive() + + if self.Parent.side_tabs: + self.Parent.ReVgenerate() + else: + self.Parent.Regenerate() + + self.Notebook.did_activate(page) + + self.Refresh(False) + + active = property(GetActive, SetActive) + + left_down_activates = prefprop('messaging.tabs.left_down_activates', False) + + def OnLeftDown(self, event): + """ + When the left mouse button is downed over the tab + if the curser is over the close button, pass the event on + otherwise set mark of where mousedown occured + """ + if hasattr(self, 'closebutton') and self.closebutton.hovered: + self.closebutton.OnLeftDown(event) + else: + self.mark = event.Position + + if self.left_down_activates: + self.active = True + event.Skip() + + def OnLeftUp(self,event): + """ + Removes mark on mouse up + if mouse was downed inside the tab is set as the new + active tab + """ + self.mark = None + if not (self.focus and event.LeftIsDown()): + if self.focus: self.active = True + self.OnMouseEnter(event) + self.OnMotion(event) + event.Skip() + + def OnMidUp(self, event): + """ + Close tab on middle click + """ + self.CloseTab() + + def OnRightDown(self, event): + """ + Temporary right button down behavior + Currently does nothing + Should Open menu + """ + #TODO: Menu, likely in TabBar though + pass + + def OnMouseEnter(self, event): + """ + When mouse enters the tab captures it and sets focus to true + if another tab doesn't have capture and the mouse is within the + cliptangle if there is one + """ + cliptangle = RectS(self.Size) + sy = self.Rect.y + sw, sh = cliptangle.Size + ph = self.Parent.Size.height-16 + if sy + sh > ph and pref('tabs.side_tabs', False): + cliptangle = Rect(0, 0, sw, ph - sy) + + if cliptangle.Contains(event.Position): + #TODO tactiveover conditioning + if not self.HasCapture(): + self.CaptureMouse() + if not event.LeftIsDown(): + self.focus=True + self.state=2 if not self.active else 3 + + if pref('tabs.style',2) == 2: + self.closebutton.Show(True) + + self.Refresh(False) + + def OnMouseLeave(self, event=None): + """ + Unfocus tab and if mouse was dragged out starts dragging tabs + """ + #if draging mouse out of tab and not from button + cliptangle = RectS(self.Size) + sy = self.Rect.y + sw, sh = self.Size + ph = self.Parent.Size.height-16 + if sy + sh > ph and pref('tabs.side_tabs', False): + cliptangle = Rect(0, 0, sw, ph-sy) + + # Make sure the mouse isn't just over the close button + if (not hasattr(self, 'closebutton') or not event or not self.closebutton.Rect.Contains(event.Position)) or not cliptangle.Contains(event.Position): + + self.ReleaseAllCapture() + # if the left button was down and mouse draged out starts drag mode + if not self.Parent.dragorigin and event and self.focus and event.LeftIsDown() and (not self.closebutton or not self.closebutton.hovered): + # go into drag mode by notifying drag + wx.CallAfter(self.Parent.OnDragStart, self) + + # if mouse out but not dragging, unfocus tab + elif self.focus: + self.focus=False + + self.state=1 if self._active else 0 + + if pref('tabs.style', 2) == 2: + self.closebutton.Show(False) + + self.mark = None + + self.Refresh(False) + + def OnMotion(self,event): + + #if mouse is not down and not in focus, run the mouse in stuff + if not (self.focus and event.LeftIsDown()): + self.OnMouseEnter(event) + + position = self.Parent.ScreenToClient(self.ClientToScreen(event.Position)) + mouseisin = self.Rect.Contains(position) + + #If dragging another tab, do D&D calculations + if mouseisin and event.LeftIsDown() and self.Manager.source: + self.Parent.DragCalc(self.Parent.ScreenToClient(GetMousePosition())) + + #Ensures a mouse leave occurs + elif mouseisin and self.mark and ((abs(event.Position.x - self.mark.x) > 5) or (abs(event.Position.y - self.mark.y) > 5)): + self.OnMouseLeave(event) + + #simulates onmouse in event if mouse over button + elif self.HasCapture() and mouseisin and hasattr(self, 'closebutton'): + if self.closebutton.Rect.Contains(event.Position): + self.closebutton.OnMouseIn(event) + elif self.closebutton.hovered: + self.closebutton.OnMouseOut(event) + + #on mouse out insurance + elif self.HasCapture() and not mouseisin: + self.OnMouseLeave(event) + + @property + def Notebook(self): return self.GrandParent + + @property + def Manager(self): + return self.Notebook.manager + + def OnButton(self, event = None): + """ + Events for clicking the close button + Closes tab and page + """ + #self.__log.info('OnButton') + while self.closebutton.HasCapture(): + self.closebutton.ReleaseMouse() + wx.CallAfter(self.Close) + wx.CallAfter(self.page.Close) + + CloseTab = OnButton + + def OnClose(self, event= None): + """ + Call to close a tab, + Automatically: + -sets next tab active if currently active + -tells the page to close itself + -removes self from parent + -hide/destroy itself + """ + self.SetEvtHandlerEnabled(False) + + while self.HasCapture(): + self.ReleaseMouse() + if self.active: + self.Parent.SetNextActive(self) + if self in self.Parent: + self.Parent.Remove(self) + + if self.previewtabs: + self.previewtabs.Stop() + self.previewtabs = None + + if self.closebutton is not None and not wx.IsDestroyed(self.closebutton): + self.closebutton.Close() + self.closebutton = None + + if getattr(self, 'page', None): + self.Notebook.did_remove(self.page.panel) + + if not wx.IsDestroyed(self): + self.Show(False) + #event.Skip() + self.Destroy() + + return True + + def SetLabel(self, label1=None, windowtitle = None): + """ + Sets the label(s) of the tab + label1 is the label alwas shown + """ + if label1 and label1 != self.label1: + self.label1 = label1 + + self.Calcumalate() +# self.Refresh(False) + + if not self.Parent.side_tabs: + self.Parent.Regenerate() + + def GetIcon(self): + icon = self.page.icon + return icon.ResizedSmaller(self.iconsize[self.mode]) if icon is not None else None + + Icon = property(GetIcon) + + def SetNotify(self, switch): + ''' + Sets the notified state, and optionally starts a timer for drawing the + notified state. + ''' + #log.info('%r SetNotify(%r)', self, switch) + + self.notified = switch + + if switch: self.drawnotified.Start() + else: self.drawnotified.Stop() + + self.Parent.UpdateNotify() + + self.page.notified = switch + + self.Top.ProcessEvent(TabNotifiedEvent(tab = self)) + + import hooks + hooks.notify('digsby.overlay_icon_updated', self.page.Content) diff --git a/digsby/src/gui/uberwidgets/uberbook/tabbar.py b/digsby/src/gui/uberwidgets/uberbook/tabbar.py new file mode 100644 index 0000000..c92f32e --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/tabbar.py @@ -0,0 +1,1052 @@ +from __future__ import with_statement +from tab import Tab +from gui import skin +from OverlayImage import OverlayImage +from navigation_arrows import Navi +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.UberEvents import EVT_DRAG_START +import wx +from wx import RectS, Rect, RectPS +from util.primitives.funcs import do +from common import pref, profile, prefprop +from gui.uberwidgets import UberWidget +from cgui import SimplePanel +#from traceback import print_stack,format_stack + +CUPID = wx.NewId() +CDOWNID = wx.NewId() + +CLOSE_TAB = wx.NewId() +CLOSE_OTHER_TABS = wx.NewId() + +class TabBar(SimplePanel, UberWidget): + """ + Where the tabs live, handles all display and organization functionality + """ + def __init__(self, parent, skinkey): + SimplePanel.__init__(self, parent) + + self.tabs = [] # a list of all the tabs + self.rows = [] # a list of all the visible row, each a list of all the tabs in that row + self.rowindex = 0 # the first visible row + self.tabindex = 0 # the first tab of the first visible row + self.tabendex = 0 # the last tab of the last visible row + + events = [(wx.EVT_PAINT, self.OnPaint), + (wx.EVT_SIZE, self.OnSize), + (wx.EVT_BUTTON, self.OnButton), + (wx.EVT_MOUSEWHEEL, self.OnWheel), + (wx.EVT_MOTION,self.OnMotion)] + + for event, method in events: self.Bind(event, method) + + + self.flagedrows = set() + self.lastsize=self.Size + + self.rowheight=0#height of a row in pixels + + self.SetSkinKey(skinkey,True) + + #buttons for verticle alignment + self.cupb = UberButton(self, CUPID, skin=self.scrollbuttonskin, icon=self.upicon) + self.cupb.Show(False) + + self.cdownb = UberButton(self, CDOWNID, skin=self.scrollbuttonskin, icon=self.downicon) + self.cdownb.Show(False) + + #the navigation box + self.navi=Navi(self) + + + self.dragorigin = None#when draging the tab that you are dragging + self.dragtarget = None#when dragging the mouse is over and at that point released on + + # the arrow image shown when dragging tabs + self.dropmarker=OverlayImage(self, self.dropmarkerimage) + + # self.dropmarker = Storage(Show = lambda v=True: None) + self.dragside=None#was the tab droped on the left or right of the target tab + + #linking prefs + link = profile.prefs.link #@UndefinedVariable + link('tabs.rows', self.Generate, False) + link('tabs.tabbar_x', self.Generate, False) + link('tabs.hide_at_1', self.Generate, False) + link('tabs.side_tabs', self.SkinRedirect, False) + + self.Top.Bind(wx.EVT_MENU, self.OnMenuEvent) + + side_tabs = prefprop('tabs.side_tabs') + tab_rows = prefprop('tabs.rows', 2) + + def UpdateSkin(self): + key = self.tabskin = self.skinkey + g = lambda k, default = sentinel: skin.get(key + '.' + k, default) + sg = lambda k, default = sentinel: skin.get('side' + key + '.' + k, default) + + elems = (('spacing', 'spacing', 2), + ('bg', 'backgrounds.bar'), + ('dropmarkerimage', 'dropmarker.image'), +# ('dropmarkeroverlay', 'dropmarker.overlay', 0), + ('dropmarkeroffset', 'dropmarker.offset', 0), + ('closebuttonskin', 'closebuttonskin', ''), + ('closeicon', 'icons.close', None), + ('scrollbuttonskin', 'scrollbuttonskin', ''), + ('lefticon', 'icons.left', ''), + ('righticon', 'icons.right', ''), + ('upicon', 'icons.up', ''), + ('downicon', 'icons.down', '')) + + for elem in elems: + setattr(self, 'top' + elem[0], g(*elem[1:])) + setattr(self, 'side' + elem[0], sg(elem[1],getattr(self,'top' + elem[0]))) + setattr(self, elem[0], getattr(self, ('side' if self.side_tabs else 'top') + elem[0])) + + + if hasattr(self,'dropmarker'): + self.dropmarker.SetImage(self.dropmarkerimage) + self.dropmarker.SetRotation((self.side_tabs and not self.dropmarkerimage)) + + navi = getattr(self, 'navi', None) + if navi is not None: + self.cdownb.SetSkinKey(self.scrollbuttonskin) + self.cupb.SetSkinKey(self.scrollbuttonskin) + self.cdownb.SetIcon(self.downicon) + self.cupb.SetIcon(self.upicon) + + self.navi.closebutton.SetSkinKey(self.closebuttonskin) + + self.navi.closebutton.SetIcon(self.closeicon) + + scrollskin = self.scrollbuttonskin + + navi.prevb.SetSkinKey(scrollskin) + navi.nextb.SetSkinKey(scrollskin) + navi.upb.SetSkinKey(scrollskin) + navi.downb.SetSkinKey(scrollskin) + + navi.prevb.SetIcon(self.lefticon) + navi.nextb.SetIcon(self.righticon) + navi.upb.SetIcon(self.upicon) + navi.downb.SetIcon(self.downicon) + + wx.CallAfter(self.Generate) + + + def SkinRedirect(self,val=None): + elems = ('spacing', + 'bg', + 'dropmarkerimage', + #'dropmarkeroverlay', + 'closebuttonskin', + 'closeicon', + 'scrollbuttonskin', + 'lefticon', + 'righticon', + 'upicon', + 'downicon' + ) + + for elem in elems: + setattr(self, elem, getattr(self,('side' if self.side_tabs else 'top') + elem)) + + self.UpdateChildSkins() + + def UpdateChildSkins(self): + self.cdownb.SetSkinKey(self.scrollbuttonskin,True) + self.cupb.SetSkinKey(self.scrollbuttonskin,True) + + navi, sbs = self.navi, self.scrollbuttonskin + navi.closebutton.SetSkinKey(self.closebuttonskin,True) + navi.prevb.SetSkinKey(sbs, True) + navi.nextb.SetSkinKey(sbs, True) + navi.upb.SetSkinKey(sbs, True) + navi.downb.SetSkinKey(sbs, True) + + self.UpdateChildrenIcons() + + for tab in self.tabs: + tab.UpdateMode() + + self.Generate() + + def __repr__(self): + return '' % self.tabs + + def OnDragStart(self, tab): + 'Catches the tab drag event and starts the tab dragging system.' + + self.NotifyDrag(tab) + + def OnMotion(self,event): + 'Positioning updates during drag and drop' + + if event.LeftIsDown() and (self.dragorigin or self.Manager.source): + self.DragCalc(event.Position) + + def __getitem__(self, index): + return self.tabs[index] + + def OnPaint(self, event): + dc = wx.PaintDC(self) + rect = RectS(self.Size) + if not self.side_tabs: + rcount = min(len(self.rows), pref('tabs.rows', 2)) + height = self.tabs[0].Size.height + + y=0 + for unused_i in xrange(rcount): + self.bg.Draw(dc, Rect(rect.x, y, rect.width, height)) + y += height + + else: + self.bg.Draw(dc,rect) + + def Add(self, page, focus, resort = True): + """ + Adds a tab to the bar. Should only be used by parent NoteBook. + page - page in PageContainer the tab is to be associated with + focus - whether that tab should steal focus from current tab + """ + tab = Tab(self, page, skinkey = self.tabskin) + tab.Bind(wx.EVT_CONTEXT_MENU, self.ShowMenu) + tab.Show(False) + self.tabs.append(tab) + + if focus: + wx.CallAfter(tab.SetActive, True) + + elif resort: + if self.side_tabs: + self.ReVgenerate() + else: + self.Regenerate(True) + + return tab + + def ShowMenu(self, e): + self._menutab = e.EventObject + + try: + menu = self._tabmenu + except AttributeError: + from gui.uberwidgets.umenu import UMenu + menu = self._tabmenu = UMenu(self) + menu.AddItem('Close &Other Tabs', id = CLOSE_OTHER_TABS) + menu.AddSep() + menu.AddItem('&Close Tab', id = CLOSE_TAB) + + menu.PopupMenu() + + def OnMenuEvent(self, e): + '''Invoked when a tab context menu item is clicked.''' + + if e.Id == CLOSE_TAB: + self._menutab.CloseTab() + elif e.Id == CLOSE_OTHER_TABS: + menutab = self._menutab + + # switch to that tab first + menutab.active = True + + with self.Frozen(): + for tab in self.tabs[:]: + if tab is not menutab: + tab.CloseTab() + else: + e.Skip() + + def Generate(self, val=None): + self.navi.closebutton.Show(pref('tabs.tabbar_x', False)) + + if self.side_tabs: + self.ReVgenerate(True) + else: + self.Regenerate() + + def ReVgenerate(self,total=False, safe=False, dotoggle=True): + """ + It's like Doo... err.. Regenerate, only vertical + """ + +# print "Starting: Regenerate",self.Top.Title,'\n'#,'='*80,'\n','\n'.join(format_stack()) +# print '='*80 + + #TODO: Should we be careful about the tab leaving the bar? + tabs = self.tabs + + if not tabs: return + + do(tab.Show(False) for tab in self.tabs) + for tab in self.tabs: tab.row = None + del self.rows[:] + + # Safty precautions prevent list access errors + if self.tabindex < 0 or self.tabindex >= len(tabs): + self.tabindex = 0 + + # Preset variables + n = self.tabindex # the first tab shown + self.rowheight = tabs[0].GetMinHeight() + area = self.Notebook.Size.height - 32 # Height in pixels of the tabbar + + # number of fully visible rows in the given area at the given height + i = area//self.rowheight + + count = len(tabs) + + #one tab per row + for r in xrange(count): tabs[r].row=r + + rows = self.rows + size = self.Size + + #Sets navimode and position + navi = self.navi + navi.ShowNav(4) + navi.Hide() + navi.Position = wx.Point(size.width - navi.Size.width,0) + + # Totally reconstructs the list if it's told to or there are not tabs in the rows or + # if there isn't one more tab than there is room for and there is enough room to fit + # them all and number of tabs in the row equals the number of tabs + if total or not rows or (i + 1 != len(rows[0])) and not (i > len(rows[0])) and len(rows[0]) == len(tabs): + rows.append([]) + col = rows[0] + + #if all tabs fit + if i >= count: + n=0 + self.tabindex=0 + do(col.append(tab) for tab in tabs) + av=col[0].MinSize.height + #calculate and show range + else: + for t in xrange(n,n+i+1): + if t < len(tabs):col.append(tabs[t]) + + # populate with earlier stuff + while len(col) < i and n > 0: + n-=1 + col.insert(0,tabs[n]) + + if col: av = col[0].MinSize.height + else: + #just leave the new values the same as the old + col = rows[0] + av = col[0].MinSize.height + + # Show all tabs in the bar + count = 16 + for t in col: + t.Size = (self.Size.width,av) + t.Position = (0,count) + count += av + t.Show() + + self.tabindex=n + endex = self.tabendex=n+len(col) + + if dotoggle: + self.Toggle() + + cupb, cdownb = self.cupb, self.cdownb + + + cupb.Enable(self.tabindex != 0) + cdownb.Enable(endex < len(tabs) or tabs[endex - 1].Position.y + + tabs[endex-1].Size.height > size.height - 16) + + + self.UpdateNotify() + + def Regenerate(self, safe = False, dotoggle=True): + ''' + Regenerates layout information. + + safe is a flag to indicate if we should try to keep the currently active + tab in view at all times. (This doesn't occur when scrolling, for + instance.) + ''' + +# print "Starting: Regenerate",self.Top.Title,'\n','='*80,'\n','\n'.join(format_stack()) +# print '='*80 + # early exit for when the tabbar isn't visible. + if not self.IsShown() and len(self.tabs) == 1: + return + + with self.Frozen(): + self._Regenerate(safe = safe, dotoggle = dotoggle) + + self.Refresh(False) + + def _Regenerate(self, safe = False, dotoggle = True): + self.cupb.Show(False) + self.cdownb.Show(False) + parentpage = self.Parent.pagecontainer + + # style is the number of rows (or 0 for single) + style = self.tab_rows + + # Should we be careful about the tab leaving the bar? + careful = not safe and parentpage.active + + # Hide all tabs preparation for refilling + for tab in self.tabs: + tab.Show(False) + tab.row = None + del self.rows[:] + + # navi set up, see if arrows are needed and placement + tally = sum(tab.MinSize.width for tab in self.tabs) #total size of tabs + + navi = self.navi + tabs = self.tabs + rows = self.rows + + if not tabs: return + + # Tab alignment calculations + + # Saftey precautions prevent list access errors + if self.tabindex < 0 or self.tabindex >= len(tabs): + self.tabindex = 0 + + # Preset variables + n = self.tabindex # the first tab shown + i = n + row = 0 + self.rowheight = tabs[0].MinHeight + + + my_w, nav_w = self.Size.width, navi.Size.width + + # Decide what kind of navigation panel, if any, to use... + if tally >= my_w - nav_w and not style: + navi.ShowNav(1) # arrows left and right + elif tally >= (my_w - nav_w): + navi.ShowNav(3) # arrows up and down next to the X + else: + navi.ShowNav(0) + + #Where to put navigation panel. + navi.Freeze() + navi.Show(True) + navi.Fit() + navi.Position = wx.Point(self.Size.width-navi.Size.width,0) + navi.Size = wx.Size(-1,self.Size.height) + navi.Thaw() + + #More preparing vars + area = self.Notebook.Size.width - navi.Size.width + + #While more tabs are not in a row + while len(tabs) > i: + tally = tabs[i].MinSize.width + rows.append([]) + + # Loop through each visible tab, fitting tabs on the right. + while i < len(tabs) and tally < area: + i += 1 + if i < len(tabs): + tally += tabs[i].MinSize.width + + #Be carefull that the active tab doesn't scroll off the bar + if careful and not style: + activeindex = tabs.index(parentpage.active.tab) + change=False + + #add tabs until the active tab is visible + while activeindex>=i and n!=i: + i += 1 + tally += tabs[i].MinSize.width + change = True + + #Remove tab if more tabs than room + if tally >= area and change: + tally -= tabs[n].MinSize.width + n += 1 + self.tabindex=n + + + + + # If extra space, fit tabs to the right of the row + if not style: # if single row, + while n > 0 and area - tally > tabs[n-1].MinSize.width: + n -= 1 + self.tabindex = n + tally += tabs[n].MinSize.width + + + # Injects tabs calculated to fit in that row into that row + if range(n, i): + rows[row] = [tabs[t] for t in xrange(n, i)] + for tab in rows[row]: + tab.row=row + else: + rows[row].append(tabs[i]) + i += 1 + + if not style: break # If we're in single row, break now. + row += 1 + n = i + # Row calculation + + if self.rowindex >= len(rows): + self.rowindex = len(rows) - 1 + + #cycle through visible rows + row = self.rowindex + visible = self.tab_rows or 1 + + if careful and style: + #print "Being Careful" + active = parentpage.active.tab + #print active + for ir,r in enumerate(rows): + #print ir,r + if active in r: + if ir= row + visible: + #print "moving index up" + row = ir - (visible - 1) + + + # If we're closing tabs above where is visible, keep the visible + # index "where it is" + if len(rows) - (row + 1) < visible and len(rows) >= visible: + row = len(rows) - visible + self.rowindex = row + + # Place tabs! + while row < len(rows) and row < self.rowindex + visible and len( rows[row] ) != 0: + + # if this is a row that needs to be scrunched... + if rows.index(rows[row]) == len(rows)-1 and \ + (style or len(rows[row]) == len(tabs)): + + for t in xrange(0,len(rows[row])): + thistab = rows[row][t] + thistab.SetSize(thistab.MinSize) + if not t: + # The first tab is set to it's minimum width at x: 0 + thistab.SetPosition((0, self.rowheight*(row-self.rowindex))) + else: + # Every other tab is placed right next to the tab + # before it. + thistab.SetPosition((rows[row][t-1].Position.x \ + + rows[row][t-1].Size.width, + self.rowheight*(row-self.rowindex))) + thistab.Show(True) + + # If there are more rows than the current row... + elif len(rows) > row: + # Get a list of tab indices, widest to smallest. + ordered = [rows[row].index(t) + for t in sorted(rows[row], + key=lambda o: o.MinSize.width, + reverse=True) ] + length = len(ordered) + reserved=0 + o=0 # o_O ? + + # Average width of tab if all tabs are the same size, and + # fill up all the area. + av = (area - reserved) / (length - o) + mark = 0 + while o < length: + # Loop from "current" tab to the end + for t in xrange(o, length): + tab = rows[row][ordered[t]] + + # If this tab is larger than average... + if tab.GetMinSize()[0] > av: + # Make it it's minimum, and keep track of it + tab.SetSize(tab.MinSize) + reserved += tab.MinSize.width + o += 1 + mark = o + + # If we're not on the last tab, recalc average + if (length - o): + av=(area-reserved)/(length-o) + else: + o += 1 + break + + # For tabs less than the average, set them to average + for t in xrange(mark, length): + tab = rows[row][ordered[t]] + tab.SetSize((av, tab.MinSize.height)) + + # For every tab in the row + for t, tab in enumerate(rows[row]): + if not t: # If it's the first tab: + if length==1: + # If the row is so small it can only fit one tab, + # make due. + tab.Size = wx.Size(area, tab.MinSize.height) + tab.Position = wx.Point(0, self.rowheight * (row - self.rowindex)) + else: + tab.Position = wx.Point(rows[row][t-1].Position.x + rows[row][t-1].Size.width, + self.rowheight * (row - self.rowindex)) + + tab.Show(True) + row += 1 + if dotoggle: + self.Toggle() + + # If total rows is less than total rows being shown, shrink the + # tab area so that it's only just big enough. + if len(rows) < style or not style: + rows_shown = len(rows) + else: + rows_shown = style + if self.Parent.SashPosition != rows_shown * self.rowheight:#self.MinSize.height + self.MinSize = wx.Size(-1, rows_shown * self.rowheight) + self.Parent.SetSashPosition(self.MinSize.height) +# self.Size=self.MinSize + + # Determine if the Navi needs to enable or show arrows + navi.Enabler() + #self.Parent.Layout() # Relayout self + self.tabendex = i-1 # final tab being shown + + self.UpdateNotify() + + navi.Size = wx.Size(-1,rows_shown * self.rowheight) + + def Remove(self, target): + 'Removes the tab specified from the bar.' + + index=self.tabs.index(target) + self.tabs.remove(target) + #if no more tabs close window + if len(self.tabs)==0: + self.Notebook.window.Close() + else: + #if index is between index and endex and bring one tab from the left + if index>self.tabindex and index0: + self.tabindex-=1 + + if self.side_tabs: + self.ReVgenerate(total=True) + else: + self.Regenerate(safe = True) + + + def OnSize(self, event): + 'ReLayout the tabs if the bar on event of a resize' + event.Skip() + + if self.side_tabs and self.tabs: + cupb = self.cupb + cdownb = self.cdownb + size = self.Size + tabs = self.tabs + endex = self.tabendex + + # position and size buttons + cupb.Position = (0,0) + cupb.Size = (size.width, 16) + cupb.Show() + cupb.Enable(self.tabindex != 0) + + cdownb.Position = (0, size.height - 16) + cdownb.Size = (size.width, 16) + cdownb.Show() + cdownb.Enable(endex < len(tabs) or tabs[endex - 1].Position.y + + tabs[endex-1].Size.height > size.height - 16) + + + sz = self.Size + if ((sz.width != self.lastsize.width and not self.side_tabs) or (sz != self.lastsize and self.side_tabs)) and self.IsShownOnScreen(): + self.lastsize = sz + if self.side_tabs: + self.ReVgenerate(dotoggle = False) + else: + self.Regenerate(False,dotoggle = False) + + try: + wx.CallAfter(wx.CallAfter,self.Parent.pagecontainer.active.panel.input_area.expandEvent) + except AttributeError: + pass + + self.Refresh(False) + + + def GetTabCount(self): + """ + Returns the number of tabs in the bar + """ + return len([t for t in self if t]) + + def NextTab(self): + self.SetNextActive(self.ActiveTab, wrap = True) + + def PrevTab(self): + self.SetLastActive(self.ActiveTab, wrap = True) + + + def SetNextActive(self, origin,wrap=False): + """ + Sets the tab after the curent active + -if it does not exist does the previbus + -or the first if wrap is true + """ + if origin in self.tabs: + index=self.tabs.index(origin) + if not index < len(self.tabs)-1 and wrap: + self.tabs[0].SetActive(True) + elif index < len(self.tabs)-1: + self.tabs[index+1].SetActive(True) + elif index>0: + self.tabs[index-1].SetActive(True) + self.Refresh(False) + + def SetLastActive(self, origin, wrap = False): + """ + Sets the tab before the curent active + -if it does not exist does the next + -or the last if wrap is true + """ + if origin in self.tabs: + index=self.tabs.index(origin) + if not index > 0 and wrap: + self.tabs[len(self.tabs)-1].SetActive(True) + elif index >0: + self.tabs[index-1].SetActive(True) + elif index<0: + self.tabs[index+1].SetActive(True) + self.Refresh(False) + + def SyncActive(self,atab): + """ + Moves the index and endex so that the active tab is in the bar + """ + if not atab: return + + if self.side_tabs: + if atab < self.tabindex: + self.tabindex=atab + self.ReVgenerate(True) + else: + thetab=self.tabs[atab] + while atab >= self.tabendex or thetab.Position.y+thetab.Size.height > self.Size.height-16: + self.tabindex+=1 + self.ReVgenerate(True) + else: + style = self.tab_rows + if atab < self.rowindex: + self.rowindex=atab + self.Regenerate() + elif atab > self.rowindex+style-1: + self.rowindex=atab-style+1 + self.Regenerate() + + def OnWheel(self,event): + """ + Event that handles mouse wheeling, + maps the events to SetNextActive and SetLastActive + """ + if RectS(self.Size).Contains(event.Position): + direction = event.GetWheelRotation() + if direction<0: + self.SetNextActive(self.ActiveTab, True) + elif direction>0: + self.SetLastActive(self.ActiveTab, True) + + @property + def Notebook(self): + return self.Parent + + @property + def Manager(self): + return self.Notebook.manager + + @property + def ActiveTab(self): + active = self.Notebook.pagecontainer.active + if active is not None: + return active.tab + + def OnButton(self,event): + """ + The button events for vertical alignment for up and down + """ + if event.GetId()==CUPID: + if self.tabindex > 0: + self.tabindex -= 1 + self.ReVgenerate(total = True) + elif event.GetId()==CDOWNID: + if self.tabendexself.Size.height-16: + self.tabindex+=1 + self.ReVgenerate(total=True) + + self.UpdateNotify() + + def NotifyDrag(self, origin): + """ + When a tab is dragged this is called to start the tabbar handling dragging + origin - tab being dragged + """ + # Lets the TabMan know a tab as been dragged + self.dragorigin = origin + origin.SetCursor(wx.StockCursor(wx.CURSOR_NO_ENTRY)) + self.Manager.Notify(self.Notebook) + + + def DragCalc(self,point): + """ + This does the dragging calculations for the tabs + """ + + sidetabs = self.side_tabs + + # if here is no local origin tab but there is a remote source identified in TabMan + if not self.dragorigin and self.Manager.source: + #setting a local drag origin + master = self.Manager.source + dragorigin=master.tabbar.dragorigin + # announcing to TabMan it is expecting a tab + self.Manager.Request(self.Notebook) + # if there is a local origin use that + else: + dragorigin=self.dragorigin + + # if dragtarget is out of date find what you're dragging to + if not self.dragtarget or not self.dragtarget.Rect.Contains(point): + + wap = wx.FindWindowAtPointer() + self.dragtarget = wap if isinstance(wap,Tab) else None + self.dragside = None + + # if there is a tab as target + if self.dragtarget and self.dragtarget != dragorigin: + dtrect=self.dragtarget.Rect + # data to decide what side the tab would be dropped on + if not sidetabs: + x = point[0] - dtrect.x + x2 = dtrect.width / 2 + else: + x = point[1] - dtrect.y + x2 = dtrect.height / 2 + # make the left/top or right/bottom decision + if x <= x2:#left/top + if self.dragside!=False: + self.dragside=False + if not sidetabs: + self.DrawDropMarker(dtrect.x, dtrect.y)# + (dtrect.height // 2) + else: + self.DrawDropMarker(dtrect.x, dtrect.y)# + dtrect.width // 2 + + elif not self.dragside:#right/bottom + self.dragside=True + if not sidetabs: + self.DrawDropMarker(dtrect.x+dtrect.width,dtrect.y)#+(dtrect.height//2) + else: self.DrawDropMarker(dtrect.x,dtrect.y+dtrect.height)#+dtrect.width//2 + self.SetFocus() + # if being dropped in the whitespace of the TabBar + elif (dragorigin and self.dragtarget!=dragorigin) or (dragorigin==None and self.dragtarget==None) and self.Rect.Contains(point): + # find what row the tab is being dropped in + if not sidetabs: + row=self.rows[(point[1]//self.rowheight)+self.rowindex] + tab=row[len(row)-1] + self.dragside=True + # or in vertical if at the beginning or end + else: + if point.y>self.rowheight: + tab=self.rows[0][len(self.rows[0])-1] + self.dragside=True + else: + tab=self.rows[0][0] + self.dragside=False + dtrect=tab.Rect + #Place marker + if not sidetabs: self.DrawDropMarker(dtrect.x+dtrect.width,dtrect.y)#+(dtrect.height//2) + elif self.dragside==True: self.DrawDropMarker(dtrect.x,dtrect.y+dtrect.height)#+dtrect.width//2 + else: self.DrawDropMarker(dtrect.x+dtrect.width/2,dtrect.y) + self.SetFocus() + #cleanup + self.dragtarget=tab + + else:#if not in tabbar anymore don't show arrow + self.dropmarker.Show(False) + + def DragFinish(self,new=False): + """ + Ends dragging and does any rearranging if required + """ + + if not wx.IsDestroyed(self.dragorigin): + self.dragorigin.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + + if self.dragorigin and self.dragorigin.previewtabs: + # Destroy the preview tab + self.dragorigin.previewtabs.Stop() + self.dragorigin.previewtabs = None + + rect = RectPS(self.Notebook.ClientToScreen(self.Position), self.Size) + parentrect = self.Notebook.window.Rect + mousepos = wx.GetMousePosition() + manager = self.Manager + + #if released out of the window... + if not new and ((manager.destination and not parentrect.Contains(mousepos)) or not rect.Contains(mousepos)): + if self.ActiveTab==self.dragorigin: + self.SetNextActive(self.dragorigin) + self.dragorigin.Show(False) + + #If no or invalid destination in manager create a new window and sets it destination + dest = manager.destination + if not dest or not dest.tabbar.Rect.Contains(dest.ScreenToClient(wx.GetMousePosition())): + # FIXME: SWIG doesn't like subtracting a wx.Size from a wx.Point, so do it the hard way + # until the SIP migration is finished. + originsize = self.dragorigin.GetSize() + newpoint = wx.Point(mousepos[0] - originsize[0], mousepos[1] - originsize[1]) + destination = self.Notebook.winman.NewWindow(newpoint, self.Notebook.window.GetSize()).notebook + #else set the destination to the manager's destination + else: + destination = dest + + #clear tabman's destination + manager.Request() + + # Grab a reference to the tab's page + page = self.dragorigin.page + + # Make the tab "forget" about the page + self.Notebook.did_remove(page.panel) + del self.dragorigin.page + del page.tab + + # Remove the tab from the tabs list, and destroy the wxWindow + self.tabs.remove(self.dragorigin) + + self.dragorigin.Close() + + # remove page from this notebook and insert it into the target notebook + #page.Parent.RemoveChild(page) + destination.Insert(page, False) + + # cleanup + manager.Notify() + self.dragorigin=None + + # re-sort tabs + if self.side_tabs: + self.ReVgenerate(True) + else: + self.Regenerate(safe = True) + + # if released inside of the window + # used both for moving within a window and as the last step of a + # interwindow move in case of interwindow tab has already been moved to + # this window at the end of the list and all that is left is to move it + # to the correct position. + elif self.dragtarget and self.dragorigin and self.dragorigin!=self.dragtarget and self.Rect.Contains(self.Notebook.ScreenToClient(mousepos)): + #remove the tab from the list + self.tabs.remove(self.dragorigin) + + #decide which side of the target the tab should be dropped + pos = self.tabs.index(self.dragtarget) + (1 if self.dragside else 0) + + # Reinsert the tab in it's new position + self.tabs.insert(pos, self.dragorigin) + + after = self.tabs[pos+1] if pos+1 < len(self.tabs) else None + if after is not None: + after = after.page.panel + + # call after so that windows are all in their correct places + wx.CallAfter(self.Notebook.did_rearrange, self.dragorigin.page.panel, after) + +# # Resort + if self.side_tabs: + self.ReVgenerate(True) + else: + self.Regenerate() + elif new: + if self.side_tabs: + self.ReVgenerate(True) + else: + self.Regenerate() + + #if there is a dragorigin run onMouseLeave on it to reset it's look + if self.dragorigin: + self.dragorigin.OnMouseLeave() + + #local cleanup + self.dropmarker.Show(False) + self.dragorigin=None + self.dragtarget=None + + #destination and manager cleanup + dest = manager.destination + if dest: + dest.tabbar.dragorigin=None + dest.tabbar.dragtarget=None + + manager.Request() + manager.Notify() + + if len(self.tabs)==0: self.Notebook.window.Close() + + def DrawDropMarker(self,x,y): + """ + Places the marker for where to drop the tab + x and y are center position, saves calculation that way + """ + +# if self.side_tabs: +# self.dropmarker.SetSize((self.Size.width - self.dropmarkeroverlay, -1)) +# else: +# self.dropmarker.SetSize((-1, self.rowheight - self.dropmarkeroverlay)) + self.dropmarker.Teleport(self.ClientToScreen((x, y+self.dropmarkeroffset))) + + if not self.dropmarker.IsShown(): + self.Manager.ShowDropMarker(self.dropmarker) + + def Toggle(self, switch = None): + 'Toggle whether the tabbar is hidden or shown.' + + if pref('tabs.hide_at_1', True) and len(self.tabs) <= 1 and not switch: + self.Notebook.Split(False) + + #make sure the content of the IM win resizes if tabbaar hides + self.ProcessEvent(wx.CommandEvent(wx.wxEVT_SIZE)) + else: + self.Notebook.Split(True) + + + def UpdateNotify(self): + + frows = self.flagedrows + frows.clear() + + # Resort + if self.side_tabs: + tabs=self.tabs + for i, tab in enumerate(tabs): + if tab.notified: + frows.add(i) + elif i in self.flagedrows: + frows.remove(i) + + self.cupb.SetNotify(len(frows) and min(frows) < self.tabindex) + self.cdownb.SetNotify(len(frows) and (max(frows) >= self.tabendex or + tabs[max(frows)].Position.y + tabs[max(frows)].Size.height > self.Size.height - 16)) + else: + for i, row in enumerate(self.rows): + flaged = False + for tab in row: + if tab and tab.notified: + flaged = True + frows.add(i) + self.navi.upb.SetNotify(len(frows) and min(frows)self.rowindex + self.tab_rows - 1) + diff --git a/digsby/src/gui/uberwidgets/uberbook/tabmanager.py b/digsby/src/gui/uberwidgets/uberbook/tabmanager.py new file mode 100644 index 0000000..ac96501 --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberbook/tabmanager.py @@ -0,0 +1,197 @@ +import wx + +from gui.windowfx import fadein, fadeout +from dragtimer import DragTimer,WinDragTimer +from OverlayImage import SimpleOverlayImage + +from common import pref, profile +from weakref import ref + +class TabManager(object): + 'This class allows transfers of tabs between notebooks.' + + def __init__(self): + self.books=[] + self.source=None + self.destination=None + + self.fadeflag=True + self.fadeobject=None + self.dropmarker=None + self.dragtimer=DragTimer(self) + + def Register(self,book): + 'Registers the book in the list of books.' + + self.books.append(ref(book)) + + def UnRegister(self, book): + 'Removes the book from the list of books.' + + for bookref in self.books[:]: + if bookref() is book: + self.books.remove(bookref) + + def ReadyBook(self,currentbook,pos): + """ + For when merging windows + Knowing what window is being dragged figures out what window is being + dragged into and sets up transfer + """ + #if there is no destination or destination is not valid + if not self.destination or not self.destination.window.Rect.Contains(pos): + #Look through all books in the book list + for bookref in self.books[:]: + book = bookref() + + if book is None or wx.IsDestroyed(book): + self.books.remove(bookref) + else: + #if not the same window that's being dragged + if book.window != currentbook.window: + rect = book.window.Rect + #if this is where the destination is but not set as destination yet + if rect.Contains(pos) and self.destination != book: + #Set as destination and show tabbar + self.destination=book + self.destination.tabbar.Toggle(True) + #if this book is the destination and is no longer valid + if self.destination and not rect.Contains(pos) and self.destination==book: + #reset variables back to normal state + tb = self.destination.tabbar + tb.Toggle() + tb.dragtarget=None + tb.dragorigin=None + tb.dropmarker.Show(False) + tb.dragside=None + self.destination=None + #reshow dragging window + if not self.fadeflag: + fadein(currentbook.window,pref('tabs.fade_speed',"normal")) + if currentbook.preview: currentbook.preview.Show(False) + self.fadeflag=True + #if there is a destination + if self.destination: + barrect=self.destination.tabbar.Rect + #if mouse is inside destination's tabbar + if barrect.Contains(self.destination.ScreenToClient(pos)): + #should do drag and drop stuff, showing of drop arrow + self.destination.tabbar.DragCalc(self.destination.tabbar.ScreenToClient(pos)) + self.destination.tabbar.dragorigin=None + #fadeout window + if currentbook.preview: currentbook.preview.Move(wx.GetMousePosition()+(2,4)) + if self.fadeflag: + self.fadeobject=currentbook.window.Show(False) + if currentbook.preview: fadein(currentbook.preview,pref('tabs.fade_speed',"normal"),to=currentbook.preview.alpha) + self.fadeflag=False + #show tabs + #mouse is not in tabbar + else: + self.destination.tabbar.dropmarker.Show(False) + #Fade in window + if not self.fadeflag: + fadein(currentbook.window,pref('tabs.fade_speed',"normal")) + if currentbook.preview: currentbook.preview.Show(False) + self.fadeflag=True + + def Transaction(self,currentbook): + 'Handles the moving of all the pages in one notebook to another notebook.' + + destination = self.destination + #If there is a destination and that destination is still valid + if destination and destination.tabbar.Rect.Contains(self.destination.ScreenToClient(wx.GetMousePosition())): + #get a ordered list of all pages in the source notebook + pages = [tab.page for tab in currentbook.tabbar.tabs] + for i, page in enumerate(pages): + destination.pagecontainer.Append(page)#addpage to destination + destbar = destination.tabbar + destbar.Add(page, page.tab.active, False) #create tab for page just added + destbar.dragorigin=page.tab #set dragorigin to tab to order + + destbar.Notebook.did_add(page.panel) + + destbar.DragFinish(True) #run to do ordering + + destbar.dragtarget = page.tab#set new target to last placed tab + destbar.dragside=1#set side to the right + + for tab in list(currentbook.tabbar.tabs): + currentbook.tabbar.tabs.remove(tab) + tab.Destroy() + + #fade out preview and close the source window + if currentbook.preview: fadeout(currentbook.preview,pref('tabs.fade_speed',"normal"),currentbook.window.Close) + + + self.fadeflag=True + + #if there is a destination do cleanup + if destination: + destination.tabbar.Toggle() + destination.tabbar.dragtarget=None + destination.tabbar.dragorigin=None + destination.tabbar.dropmarker.Show(False) + destination.tabbar.dragside=None + + self.destination=None + + def Notify(self, notebook = None): + """ + Set the source notebook for moving a page between windows + """ + self.source=notebook + if self.source: + self.dragtimer.Start(SimpleOverlayImage(notebook,self.source.tabbar.dragorigin)) + + def Request(self,notebook=None): + """ + Set the destination notebook for moving a page between windows + """ + self.destination=notebook + + def Trigger(self): + 'Tells the source to trigger a dragfinish.' + + if self.source: self.source.tabbar.DragFinish() + + def ShowDropMarker(self, dropmarker = None): + 'Sets the new dropmarker and shows it.' + + if not dropmarker: + if self.dropmarker and not wx.IsDestroyed(self.dropmarker): + self.dropmarker.Show(False) + return + + if self.dropmarker and not wx.IsDestroyed(self.dropmarker) and self.dropmarker is not dropmarker: + self.dropmarker.Show(False) + + self.dropmarker = dropmarker + self.dropmarker.Show(True) + + + +class TabWindowManager(object): + ''' + Generic tab window manager whose only argument is a callable which returns + a new window for when tabs are dragged outside of their parents into empty + space. + ''' + + def __init__(self, create_window_func): + """ + Creates the window drag time and tabmanager. + + create_window_func - function used to generate new windows + """ + self.factory = create_window_func + + def NewWindow(self, pos = wx.DefaultPosition, size = None): + 'Creates a new window with the callback function.' + + win = self.factory(pos, size) + win.Show(False) + fadein(win,'normal') + return win + + + diff --git a/digsby/src/gui/uberwidgets/ubersplit.py b/digsby/src/gui/uberwidgets/ubersplit.py new file mode 100644 index 0000000..ebc27d0 --- /dev/null +++ b/digsby/src/gui/uberwidgets/ubersplit.py @@ -0,0 +1,111 @@ +import wx +from gui import skin +from gui.uberwidgets import UberWidget +from util import try_this + +class UberSplitter(wx.SplitterWindow, UberWidget): + def __init__(self, parent): + wx.SplitterWindow.__init__(self, parent, style = wx.SP_NOBORDER | wx.SP_LIVE_UPDATE) + + b = self.Bind + b(wx.EVT_LEFT_DCLICK, lambda e: None) + + b(wx.EVT_ERASE_BACKGROUND, lambda e: None) + b(wx.EVT_PAINT, self.__paint) + + + b(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.__changed) + + b(wx.EVT_ENTER_WINDOW, self.__enter) + b(wx.EVT_LEAVE_WINDOW, self.__leave) + #b(wx.EVT_MOTION, self.__motion) + b(wx.EVT_LEFT_DOWN, self.__ldown) + + def __ldown(self, e): + e.Skip() + self.Refresh() + + def __enter(self, e): + e.Skip() + #if not self.HasCapture(): self.CaptureMouse() + self.Refresh() + + def __leave(self, e = None): + if e: e.Skip() + #while self.HasCapture(): self.ReleaseMouse() + self.Refresh() + + def __motion(self, e): + e.Skip() + if self.HasCapture() and not self.SplitRect.Inside(e.Position): + self.__leave() + + def __changed(self, e): + wx.CallAfter(self.Refresh) + + def SplitHorizontally(self, w1, w2): + self.SetSkinKey('HorizontalSizerBar',True) + + wx.SplitterWindow.SplitHorizontally(self, w1, w2) + + def SplitVertically(self, w1, w2): + self.SetSkinKey('HorizontalSizerBar',True) + + wx.SplitterWindow.SplitVertically(self, w1, w2) + + def UpdateSkin(self): + key = self.skinkey + s = lambda k, default = sentinel: skin.get('%s.%s' % (key, k), default) + + self.SetSashSize(try_this(lambda: int(s('Thickness')), 4)) + + self.icon = s('Icon', None) + self.bg = s('Backgrounds.Normal') + self.bghover = s('Backgrounds.Hover', lambda: self.normalbg) + self.bgactive = s('Backgrounds.Active', lambda: self.hoverbg) + + def __paint(self, e): + dc2 = wx.PaintDC(self) + r = self.SplitRect + + if r is None: return e.Skip() + + bg = 'bg' + if wx.FindWindowAtPointer() is self: + bg = 'bgactive' if wx.LeftDown() else 'bghover' + + wx.CallAfter(lambda: getattr(self, bg).Draw(wx.ClientDC(self), self.SplitRect)) + + @property + def SplitRect(self): + w1, w2 = self.Window1, self.Window2 + if w1 is None or w2 is None: + return None + + r1, r2 = w1.Rect, w2.Rect + + if self.SplitMode == wx.SPLIT_VERTICAL: + return wx.Rect(r1.Right, 0, r2.X, self.Size.height) + else: + return wx.Rect(0, r1.Bottom, self.Size.width, r2.Y) + + +if __name__ == '__main__': + from tests.testapp import testapp + a = testapp('../../../') + f = wx.Frame(None) + + split = wx.SplitterWindow(f)#UberSplitter(f) + split.SetBackgroundColour(wx.RED) + + p1 = wx.Panel(split) + p1.BackgroundColour = wx.WHITE + + p2 = wx.Panel(split) + p2.BackgroundColour = wx.WHITE + + split.SplitHorizontally(p1, p2) + + #split.Unsplit() + f.Show() + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/gui/uberwidgets/uberwidget.py b/digsby/src/gui/uberwidgets/uberwidget.py new file mode 100644 index 0000000..6d99f0a --- /dev/null +++ b/digsby/src/gui/uberwidgets/uberwidget.py @@ -0,0 +1,62 @@ +import sys +import wx +from types import NoneType +from gui import skin +import gui.native.extensions + +import ctypes +from ctypes import byref +from ctypes import create_unicode_buffer + +try: + uxtheme = ctypes.windll.uxtheme + OpenTheme = uxtheme.OpenThemeData + CloseTheme = uxtheme.CloseThemeData + DrawThemeBG = uxtheme.DrawThemeBackground + IsAppThemed = uxtheme.IsAppThemed + uxthemeable = True +except: + uxthemeable = False + + +class UberWidget(object): + def __init__(self,nativekey): + global uxthemeable + self.uxthemeable = uxthemeable + self.nativekey=nativekey + self.nativetheme = None + self.Bind(wx.EVT_CLOSE,self.OnThisWidgetClose) + + def SetSkinKey(self, key, update = False): + if not isinstance(key, (basestring,NoneType)): + raise TypeError('skin keys must be strings or NoneType; is %s'%type(key)) + + if not key or ((skin.get('%s.mode'%key,'') or '').lower()=='native' or key.lower()=='native'): + key=None + + self.skinkey = key + if update: self.UpdateSkin() + + def OpenNativeTheme(self): + if hasattr(self,'nativetheme') and self.nativetheme: + CloseTheme(self.nativetheme) + try: + self.nativetheme = OpenTheme(self.Handle,byref(create_unicode_buffer(self.nativekey))) if OpenTheme else None + return bool(self.nativetheme) + except: + return False + + def CloseNativeTheme(self): + if self.nativetheme: + CloseTheme(self.nativetheme) + self.nativetheme = None + + def OnThisWidgetClose(self,event): + self.CloseNativeTheme() + event.Skip() + + def DrawNativeLike(self,dc,part,state,rect,DrawNativeFallback=None): + if "wxMSW" in wx.PlatformInfo and hasattr(self, 'nativetheme') and self.nativetheme: + DrawThemeBG(self.nativetheme, dc.GetHDC(), part, state, byref(rect.RECT), None) + elif DrawNativeFallback: + DrawNativeFallback(dc,part,state,rect) diff --git a/digsby/src/gui/uberwidgets/ubutton.py b/digsby/src/gui/uberwidgets/ubutton.py new file mode 100644 index 0000000..37d08ab --- /dev/null +++ b/digsby/src/gui/uberwidgets/ubutton.py @@ -0,0 +1,326 @@ +import wx + +from wx import Size +from gui.skin import get as skinget +from gui.uberwidgets.ucontrol import UControl +from time import time + +class UButton(UControl): + def __init__(self, parent, id = -1, + skinkey = 'Button', + label = '', + bitmap = None, + bitmapSize = None, + menuMode = False, # left down == activate + callback = None, # shortcut for button.Bind(wx.EVT_BUTTON, ... + style = wx.BU_LEFT # see other flags like wx.BU_EXACTFIT + ): + + self.bitmap = bitmap + self.bitmapSize = bitmapSize + self.Active = False + self.menuMode = menuMode + + UControl.__init__(self, parent, skinkey, id, label, + style = style | wx.NO_BORDER | wx.FULL_REPAINT_ON_RESIZE) + + self.BindHover(self.OnHover) + self.BindFocus(self.OnFocus) + + Bind = self.Bind + Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + Bind(wx.EVT_KEY_UP, self.OnKeyUp) + Bind(wx.EVT_SIZE, lambda e: self.Cut(self.BackgroundRegion.GetBitmap(e.Size))) + + + self.UpdateSkin() + + if 'wxMSW' in wx.PlatformInfo: + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) + + if callback is not None: + self.Bind(wx.EVT_BUTTON, callback) + + def __repr__(self): + return '<%s "%s">' % (self.__class__.__name__, self.LabelText) + + def OnFocus(self, focused): + if self._native and focused: + self._nativebutton.SetFocus() + + if self.Active and not focused: + self.Active = False + self.ChooseBG() + + self.Refresh(False) + + def Enable(self, val): + UControl.Enable(self, val) + self.ChooseBG() + self.Refresh(False) + + def OnKeyDown(self, e): + e.Skip() + if self._native or not self.Enabled or not self.IsShown(): return + + if e.KeyCode == wx.WXK_SPACE: self.OnLeftDown() + + def OnKeyUp(self, e): + e.Skip() + if self._native or not self.Enabled or not self.IsShown(): return + + c = e.KeyCode + if c == wx.WXK_SPACE: self.OnLeftUp() + + def OnLeftDown(self, e = None): + if e: e.Skip() + if self._native or not self.Enabled or not self.IsShown(): return + + if not self.menuMode: + if isinstance(e, wx.MouseEvent): + self.CaptureMouse() + + if self.FindFocus() is not self: + self.SetFocus() + else: + self._fire() + + self.Active = True + self.ChooseBG() + self.Refresh(False) + + def OnLeftUp(self, e = None, fire = True): + if e: e.Skip() + if self._native or not self.IsEnabled(): return + + while self.HasCapture(): + self.ReleaseMouse() + + if self.Active and fire and not self.menuMode: self._fire() + self.Active = False + self.ChooseBG() + self.Refresh(False) + + def OnHover(self, hover): + if self.HasCapture(): + self.Active = hover + + self.ChooseBG() + self.Refresh(False) + + def ChooseBG(self): + bgs = self.bgs + if not self.IsEnabled(): + bg = 'disabled' + else: + bg = '' + if self.menuMode: + if self.Active or self.Hover: bg = 'hover' + else: + if self.Active: bg += 'active' + if self.Hover: bg += 'hover' + if not bg: + bg = 'normal' + + newbg = getattr(bgs, bg) + + if newbg is not getattr(self, 'BackgroundRegion', None): + self.BackgroundRegion = newbg + + return bg + + + def UpdateSkin(self): + UControl.UpdateSkin(self) + + if skinget('Button.Native', False): + self.Native = True + else: + self.bgs = skinget('Button.Backgrounds') + self.ChooseBG() + + font = skinget('Button.Font', None) + if isinstance(font, wx.Font): self.Font = font + + + def GetDefaultAttributes(self): + return wx.Button.GetClassDefaultAttributes() + + def GetContentSize(self): + dc = wx.ClientDC(self) + dc.Font = self.Font + size = Size(*dc.GetTextExtent(self.LabelText)) + b = self.bitmap + s = self.WindowStyle + padding = self.Padding + + if b is not None: + imgw, imgh = self.bitmapSize if self.bitmapSize is not None else (b.Width, b.Height) + + if s & wx.BU_LEFT or s & wx.BU_RIGHT: + size.IncBy(padding.width + imgw, 0) + size = Size(size.width, max(imgh, size.height)) + else: + size.IncBy(0, padding.height + imgh) + size = Size(max(imgw, size.width), size.height) + + size.IncBy(padding.width * 2, padding.height * 2) + + if not (s & wx.BU_EXACTFIT): + size = Size(max(size.width, 65), max(size.height, 17)) + + return size + + + def _fire(self, e = None): + if e: e.Skip() + + evt = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.Id) + evt.SetTimestamp(time()) + evt.SetEventObject(self) + + self.AddPendingEvent(evt) + + def Draw(self, dc, rect): + padding = self.Padding + + rect.Deflate(padding.x, padding.y) + + dc.Font = self.Font + + if not self.IsEnabled(): + fg = 'disabled' + else: + fg = '' + if self.Active: fg += 'active' + if self.Hover: fg += 'hover' + if not fg: fg = 'normal' + + dc.SetTextForeground(self.skin.fontcolors.get(fg, self.ForegroundColour)) + + s = self.WindowStyle + bitmap, bitmapSize = self.Bitmap, self.BitmapSize + bitmapSize = self.BitmapSize + + if self.FindFocus() is self and not self.menuMode: + self.DrawFocusRect(dc, rect) + + if bitmap is not None: + bitmap = bitmap.Resized(self.BitmapSize) + + if s & wx.BU_LEFT: + dc.DrawBitmap(bitmap, rect.Left, rect.Top + rect.Height / 2 - bitmapSize.height / 2) + rect.Subtract(left = bitmapSize.width + padding.width) + elif s & wx.BU_RIGHT: + dc.DrawBitmap(bitmap, rect.Right - bitmapSize.width, rect.Top + rect.Height / 2 - bitmapSize.height / 2) + rect.Subtract(right = bitmapSize.width + padding.width) + + dc.DrawLabel(self.LabelText, rect, indexAccel = self.Label.find('&'), + alignment = wx.ALIGN_CENTER) + + def AcceptsFocus(self): + return self.IsShown() and self.IsEnabled() and not self.menuMode + + def SetLabel(self, newLabel): + oldLabel = self.Label + + i = oldLabel.find('&') + if i != -1 and i < len(oldLabel) - 1 and oldLabel[i+1] != '&': + self.KeyCatcher.RemoveDown('alt+%s' % newLabel[i+1], oldLabel[i+1]) + + i = newLabel.find('&') + if i != -1 and i < len(newLabel) - 1 and newLabel[i+1] != '&': + print self, 'adding alt+%s' % newLabel[i+1] + self.KeyCatcher.OnDown('alt+%s' % str(newLabel[i+1]), self._fire) + + UControl.SetLabel(self, newLabel) + + Label = property(UControl.GetLabel, SetLabel) + + def SetBitmap(self, bitmap): + self.bitmap = bitmap + self.Refresh(False) + + Bitmap = property(lambda self: self.bitmap, SetBitmap) + + # + # bitmap size: defaults to None if you haven't set a bitmap + # becomes the image size if you set a bitmap + # can be set to some other size + # + + def SetBitmapSize(self, size): + self.bitmapSize = size + + def GetBitmapSize(self): + if self.bitmapSize is not None: + return self.bitmapSize + elif self.bitmap is not None: + return Size(self.bitmap.Width, self.bitmap.Height) + else: + return None + + BitmapSize = property(GetBitmapSize, SetBitmapSize) + + def SetNative(self, native): + if native and not self._native: + print self.Id + self._nativebutton = wx.Button(self, self.Id, label = self.Label, size = self.Size, pos = self.Position, + style = self.WindowStyle) + self._nativebutton.SetFocus() + + elif not native and self._native: + self._nativebutton.Destroy() + + if self._native != native: + self.Parent.Layout() + self.Parent.Refresh() + + self._native = native + + Native = property(lambda self: self._native, SetNative) + +if __name__ == '__main__': + from tests.testapp import testapp + from gui import skin + a = testapp('../../..') + + #from util import trace + #trace(UButton) + + f = wx.Frame(None, style = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + p = wx.Panel(f) + p.Sizer = s = wx.BoxSizer(wx.VERTICAL) + bitmap = skin.load_bitmap('error.png') + + b = UButton(p, label = '&Digsby', bitmap = bitmap); s.Add(b) + + b.Bind(wx.EVT_BUTTON, lambda e, but=b: setattr(but, 'Native', not getattr(but, 'Native'))) + + b = UButton(p, label = 'Digsby &Rocks', bitmap = bitmap); s.Add(b) + b = UButton(p, label = '&Exact Fit', style = wx.BU_EXACTFIT); s.Add(b) + b = UButton(p, label = '&Cancel'); s.Add(b) + b = UButton(p, label = 'Disab&led'); s.Add(b); b.Enable(False) + + b = UButton(p, label = 'Digs&by', bitmap = bitmap, style = wx.BU_RIGHT, menuMode = True); s.Add(b, 0, wx.EXPAND) + b = UButton(p, label = 'Digsby Rocks', bitmap = bitmap, style = wx.BU_RIGHT); s.Add(b, 0, wx.EXPAND) + b = UButton(p, label = '&OK'); s.Add(b, 0, wx.EXPAND) + b = UButton(p, label = 'Cancel'); s.Add(b, 0, wx.EXPAND) + + s.AddSpacer((30,30)) + + from gui.uberwidgets.UberButton import UberButton + + hs = wx.BoxSizer(wx.HORIZONTAL) + for x in xrange(4): + b = UberButton(p, label = 'UberButton'); hs.Add(b, 1, wx.EXPAND) + + s.Add(hs, 0, wx.EXPAND) + + def printsrc(e): print e.EventObject + p.Bind(wx.EVT_BUTTON, printsrc) + + f.Show() + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/gui/uberwidgets/ucontrol.py b/digsby/src/gui/uberwidgets/ucontrol.py new file mode 100644 index 0000000..57e071f --- /dev/null +++ b/digsby/src/gui/uberwidgets/ucontrol.py @@ -0,0 +1,127 @@ +from gui.skin.skinobjects import Margins +from gui.textutil import default_font +from gui.skin import get as skinget + +import wx +from gui.uberwidgets.keycatcher import KeyCatcher + + +class UControl(wx.PyControl): + def __init__(self, parent, skinkey, id = -1, label = '', + pos = wx.DefaultPosition, + size = wx.DefaultSize, + style = wx.NO_BORDER): + + self.Padding = wx.Size() + + wx.PyControl.__init__(self, parent, id = id, style = style) + + self.skinkey = skinkey + self.margins = Margins((0, 0, 0, 0)) + self._native = False + + + self.Font = default_font() + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + self.Bind(wx.EVT_PAINT, self.__paint) + + self.SetLabel(label) + self.SetInitialSize(size) + self.InheritAttributes() + + @property + def KeyCatcher(self): + try: + return self.Top._keycatcher + except AttributeError: + k = self.Top._keycatcher = KeyCatcher(self.Top) + return k + + def UpdateSkin(self): + self.skin = skinget(self.skinkey) + + self.Padding = wx.Size(*skinget(self.skinkey + '.Padding', (0, 0))) + self.SetMargins(skinget(self.skinkey + '.Margins', lambda: Margins((0, 0, 0, 0)))) + #self.Font = skinget(self.skin) + + + def BindHover(self, callback = None): + self.Hover = False + self.hover_callback = callback + self.Bind(wx.EVT_ENTER_WINDOW, self.__enter) + self.Bind(wx.EVT_LEAVE_WINDOW, self.__leave) + + def BindFocus(self, callback = None): + self.Focus = False + self.focus_callback = callback + self.Bind(wx.EVT_SET_FOCUS, self.__setfocus) + self.Bind(wx.EVT_KILL_FOCUS, self.__killfocus) + + def __setfocus(self, e): + e.Skip() + self.Focus = True + if self.focus_callback is not None: self.focus_callback(True) + + def __killfocus(self, e): + e.Skip() + self.Focus = False + if self.focus_callback is not None: self.focus_callback(False) + + def __enter(self, e): + e.Skip() + self.Hover = True + if self.hover_callback is not None: + self.hover_callback(True) + + def __leave(self, e): + e.Skip() + self.Hover = False + if self.hover_callback is not None: + self.hover_callback(False) + + def DrawBackground(self, dc, rect): + self.BackgroundRegion.Draw(dc, rect) + + def GetMargins(self): + return self.margins + + def SetMargins(self, margins): + self.margins = margins + self.InvalidateBestSize() + + Margins = property(GetMargins, SetMargins) + + def __paint(self, e): + if self._native: return wx.PaintDC(self) + + dc = wx.AutoBufferedPaintDC(self) + rect = self.ClientRect + + if rect.width and rect.height: + self.DrawBackground(dc, rect) + + # offset by margins before passing rect to Draw + m = self.margins + rect.Offset(m[:2]) + rect.SetSize((rect.Width - m.left - m.right, + rect.Height - m.top - m.bottom)) + + self.Draw(dc, rect) + + def DrawFocusRect(self, dc, rect): + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(wx.Pen(wx.BLACK, style=wx.DOT)) + dc.DrawRectangleRect(rect) + + + def DoGetBestSize(self): + m, s = self.margins, self.GetContentSize() + s.IncBy(m.left, m.top) + s.IncBy(m.right, m.bottom) + self.CacheBestSize(s) + return s + + def ShouldInheritColours(self): + return False diff --git a/digsby/src/gui/uberwidgets/umenu.py b/digsby/src/gui/uberwidgets/umenu.py new file mode 100644 index 0000000..1fc94f5 --- /dev/null +++ b/digsby/src/gui/uberwidgets/umenu.py @@ -0,0 +1,1486 @@ +''' + +Menus which can be toggled between skinned/native. + +''' + +from __future__ import with_statement +from gui.skin.skinobjects import SkinColor + +import wx +from wx import RectPS, Rect, ITEM_NORMAL, ITEM_SEPARATOR, ITEM_CHECK, \ + ITEM_RADIO, Point, ALIGN_CENTER_VERTICAL, FindWindowAtPoint, \ + MenuItem, CallLater, FindWindowAtPointer, GetMousePosition, wxEVT_MOTION, \ + StockCursor, CURSOR_DEFAULT, GetMouseState, Window +from wx import PyCommandEvent, wxEVT_MENU_OPEN +from gui import skin +from traceback import print_exc + +from gui.textutil import default_font +from gui.windowfx import fadein +from gui.vlist.skinvlist import SkinVListBox +from gui.uberwidgets.UberButton import UberButton +from gui.skin.skinobjects import Margins +from gui.uberwidgets.skinnedpanel import SkinnedPanel +from gui.uberwidgets.keycatcher import KeyCatcher +from cgui import SplitImage4 +from gui.toolbox import Monitor + +import config + +from util import traceguard, memoize, Storage as S, InstanceTracker +from util.primitives.funcs import Delegate + +from common import prefprop +from weakref import ref + +from logging import getLogger; log = getLogger('umenu') + +wxMSW = 'wxMSW' in wx.PlatformInfo +WM_INITMENUPOPUP = 0x117 + +def MenuItem_repr(item): + text = '(separator)' if item.IsSeparator() else item.Label + return '<%s %s>' % (item.__class__.__name__, text) + +MenuItem.__repr__ = MenuItem_repr +del MenuItem_repr + +MenuItem.SetCallback = lambda item, callback: item.Menu.SetCallback(item.Id, callback) + +if wxMSW: + from ctypes import windll + ReleaseCapture_win32 = windll.user32.ReleaseCapture + +class UMenuTrayTimer(wx.Timer): + 'When summonned by the tray icon.' + + def __init__(self, umenu): + wx.Timer.__init__(self) + self.umenu = umenu + + def Notify(self): + try: + mp = GetMousePosition() + ms = GetMouseState() + except Exception: + return + + if not (ms.LeftDown() or ms.RightDown() or ms.MiddleDown()): + return + + menu = self.umenu + + while menu != None: + if menu.ScreenRect.Contains(mp): + return + else: + submenu = menu.menu._childmenu + menu = submenu.popup.vlist if submenu is not None else None + + self.Stop() + + self.umenu.Dismiss() + + + +class UMenu(wx.Menu, InstanceTracker): + + last_menu = None + + def __init__(self, parent, label = '', id = None, onshow = None, windowless = None): + + if not isinstance(parent, wx.WindowClass): + raise TypeError('UMenu parent must be a wx.Window') + + wx.Menu.__init__(self, label) + + InstanceTracker.track(self) + + if not isinstance(id, (int, type(None))): + raise TypeError + + self._parentmenu = self._childmenu = None + self.Id = wx.NewId() if id is None else id + self._window = ref(parent) + self.OnDismiss = Delegate() + self.cbs = {} + + #self.Bind(wx.EVT_MENU, lambda e: menuEventHandler(self.InvokingWindow).ProcessEvent(e)) + + if onshow is not None: + self.Handler.AddShowCallback(self.Id, lambda menu=ref(self): onshow(menu())) + + if wxMSW: + self.Handler.hwndMap[self.HMenu] = self + + self.Windowless = windowless + + self.UpdateSkin() + + @property + def Window(self): + return self._window() + + def SetWindowless(self, val): + self._windowless = val + + def GetWindowless(self): + return self._parentmenu.Windowless if self._parentmenu else self._windowless + + Windowless = property(GetWindowless, SetWindowless) + + def IsShown(self): + if not self.popup or wx.IsDestroyed(self.popup): + return False + + try: + return self.popup.IsShown() + except AttributeError: + #TODO: how do we know if a native menu is being shown? + return False + + def UpdateSkin(self): + + # no specific skinkey means look it up in MenuBar.MenuSkin + mbskin = skin.get('MenuBar', None) + + if "wxMac" in wx.PlatformInfo or not mbskin or mbskin.get('menuskin', None) is None or mbskin.get('mode', 'skin').lower() == 'native': + self.skin = S(native = True) + native = True + else: + self.skin = skin.get(mbskin.menuskin) + native = False + self.skin.native = False + + if not native and not hasattr(self, 'popup'): + # create a PopupWindow child for displaying a skinned menu + self.popup = MenuPopupWindow(self.Window, self) + elif not native: + # since UpdateSkin is called on UMenus separately and does not descend the widget tree, + # do it manually here. + self.popup.UpdateSkin() + self.popup.vlist.UpdateSkin() + elif native: + if hasattr(self, 'popup'): + # destroy the skinned PopupWindow if we're in native mode + self.popup.Destroy() + del self.popup + # + #TODO: native menus need native-sized icons + # + # for item in self: + # bitmap = item.Bitmap + # if bitmap is not None and bitmap.IsOk(): + # + # print 'resizing', bitmap, nativesz + # item.Bitmap = bitmap.Resized() + + def Display(self, caller = None): + self.PopupMenu(caller.ScreenRect) + + def dismiss_old(self): + 'Dismiss any currently visible root skinned UMenu.' + + menuref = UMenu.last_menu + if menuref is None: return + + menu = menuref() + + if menu is not None and not wx.IsDestroyed(menu): + menu.Dismiss() + + UMenu.last_menu = None + + def PopupMenu(self, pos = None, submenu = False, event = None): + if not submenu: + self.dismiss_old() + + if event is not None: + # mark a WX event as handled + event.Skip(False) + + self._set_menu_event(event) + + if 'wxMSW' in wx.PlatformInfo and self.Windowless: + from gui.native.win.wineffects import _smokeFrame + if _smokeFrame: _smokeFrame.SetFocus() + + self._menuevent = event + try: onshow = self._onshow + except AttributeError: pass + else: onshow(self) + finally: del self._menuevent + + with traceguard: + if self.skin.get('native', False): + + if pos is None: + pos = GetMousePosition() + + elif len(pos) == 4: + # if a rectangle was passed in, get the bottom left coordinate + # since we can only pass a point to the native PopupMenu + pos = pos.BottomLeft + + # wx.Window.PopupMenu expects coordinates relative to the control + popup = lambda pos: self.Window.PopupMenu(self, self.Window.ScreenToClient(pos)) + else: + popup = lambda pos: self.popup.PopupMenu(pos, submenu = submenu) + + # keep a weak reference to the last skinned menu so we can + # Dismiss it later, if needed. + UMenu.last_menu = ref(self) + + return popup(pos) + + def Dismiss(self): + if not self.skin.get('native', False) and not wx.IsDestroyed(self.popup): + return self.popup.vlist.Dismiss() + + if wxMSW: + if hasattr(wx.Menu, 'GetHMenu'): + GetHMenu = wx.Menu.GetHMenu + else: + def GetHMenu(self): + # evil byte offset hack which will almost certainly break on + # every machine but mine -kevin + # (remove after wxPython's _menu.i wraps wxMenu::GetHMENU()) + from ctypes import cast, POINTER, c_long + p = cast(int(self.this), POINTER(c_long)) + return p[25] + + HMenu = property(GetHMenu) + + def AddItem(self, text = '', bitmap = None, callback = None, id = -1): + return self._additem(text, bitmap, callback, id = id) + + def AddItemAt(self, position, text = '', bitmap = None, callback = None, id = -1): + return self._additem(text, bitmap, callback, id = id, position = position) + + def Append(self, id, text, bitmap = None, callback = None): + return self._additem(text, bitmap, callback, id = id) + + def AddCheckItem(self, text, callback = None, id = -1): + return self._additem(text, callback = callback, kind = ITEM_CHECK, id = id) + + def AddRadioItem(self, text, callback = None): + return self._additem(text, callback = callback, kind = ITEM_RADIO) + + def AddPrefCheck(self, pref, text, help = '', updatenow = True): + 'Appends a checkbox menu item tied to preference "pref" with label "text."' + + from common import profile; prefs = profile.prefs + + def callback(): + # called when the user checks the item + prefs[pref] = not prefs[pref] + + item = self._additem(text, callback = callback, kind = ITEM_CHECK) + + # pref changes cause the item to be checked/unchecked + prefs.link(pref, lambda val: item.Check(val), obj = self) + + return item + + def AppendLazyMenu(self, name, callback, bitmap = None): + ''' + Append a menu which calls "callback" (with one argument: the menu) + just before showing. + ''' + if not callable(callback): raise TypeError, repr(callback) + + menu = UMenu(self.Window) + return self.AddSubMenu(menu, name, bitmap = bitmap, onshow = lambda menu=menu: callback(menu)) + + def _additem(self, text, bitmap = None, callback = None, kind = ITEM_NORMAL, id = -1, position = None): + item = MenuItem(self, id, text, kind = kind) + id = item.Id + + if bitmap is not None: + self.SetItemBitmap(item, bitmap) + + if callback is not None: + self.SetCallback(id, callback) + + if position is None: + return self.AppendItem(item) + else: + return self.InsertItem(position, item) + +# return item + + def SetCallback(self, id, callback): + ''' + Sets the callback that is invoked when the menu item identified + by "id" is clicked. + ''' + callback = lambda cb=callback: self._refresh_callback(cb) + self.cbs[id] = callback + self.Handler.AddCallback(id, callback) + + def _refresh_callback(self, cb): + # force a repaint underneath the skinned menu + # before the (potentially lengthy) callback + + m = self._parentmenu + if m is None: + m = self + else: + while m._parentmenu: + m = m._parentmenu + + # Refresh followed by Update means "really paint right now" + if hasattr(m, '_button'): + if wx.IsDestroyed(m._button): + del m._button + else: + m._button.Refresh() + m._button.Update() + + self.Window.Refresh() + self.Window.Update() + return cb() + + + def AddSubMenu(self, submenu, label, bitmap = None, onshow = None): + submenu._parentmenu = self + + if onshow is not None: + self.Handler.AddShowCallback(submenu.Id, onshow) + + item = self.AppendSubMenu(submenu, label) + if bitmap is not None: + self.SetItemBitmap(item, bitmap) + + return item + + def SetItemBitmap(self, item, bitmap): + if self.skin.native and bitmap.Ok(): + bitmap = bitmap.ResizedSmaller(16) + + item.SetBitmap(bitmap) + + + def AddSep(self): + return self.AppendItem(MenuItem(self)) + + def AddSepAt(self, i): + return self.InsertItem(i, MenuItem(self)) + + def RemoveItems(self, items): + return [self.RemoveItem(item) for item in items] + + def RemoveAll(self): + return self.RemoveItems(list(self)) + + def DestroyAll(self): + for item in self.RemoveAll(): + item.Destroy() + + def GetItemById(self, id): + 'Get the position index of a menu item.' + + for i, myitem in enumerate(self): + if myitem.Id == id: + return myitem + + def IndexOf(self, item): + 'Get the position index of a menu item.' + + id = item.Id + for i, myitem in enumerate(self): + if myitem.Id == id: + return i + + return -1 + + def Break(self): + raise NotImplementedError('skinned menus cannot break') + + @property + def Top(self): + w = self.Window + while not isinstance(w, wx.TopLevelWindow): + try: + w = getattr(w, 'Window', getattr(w, 'ParentWindow', w.Parent)) + except AttributeError: + print '***', w, '***' + raise + return w + + @property + def Handler(self): + return menuEventHandler(self.Top) + + def _activate_item(self, item): + return self.popup.vlist._activate_item(item) + + def __iter__(self): + return iter(self.GetMenuItems()) + + def __getitem__(self, n): + return self.GetMenuItems()[n % len(self)] + + def __len__(self): + return self.GetMenuItemCount() + + def __contains__(self, item): + return any(i.Id == item.Id for i in self) + + def __repr__(self): + return '<%s %r>' % (self.__class__.__name__, self.Title) + + def __enter__(self): + self.RemoveAll() + return self + + def __exit__(self, exc, value, tb): + if exc is None: + self.PopupMenu() + + def _set_menu_event(self, e): + self._menu_event = ref(e) + + @staticmethod + def Reuse(parent, attr='menu', event=None): + try: + menu = getattr(parent, attr) + except AttributeError: + menu = UMenu(parent) + setattr(parent, attr, menu) + + menu._set_menu_event(event) + return menu + +class UMenuBar(wx.MenuBar): + ''' + A MenuBar subclass which can appear as a native menubar or as a skinned + menubar. The skinned bar's window can be obtained via the "SizableWindow" + property. + ''' + + def __init__(self, parent, skinkey = 'MenuBar'): + wx.MenuBar.__init__(self) + self.toptitles = {} + self.skinkey = skinkey + self.accelremoves = Delegate() + + self.panel = SkinnedPanel(parent, 'MenuBar',) + self.panel.UpdateSkin = self.UpdateSkin + self.panel.Hide() + self.UpdateSkin() + + def UpdateSkin(self): + s = self.skin = skin.get(self.skinkey) + self.native = s.get('mode', 'skin').lower() == 'native' or "wxMac" in wx.PlatformInfo + + if not hasattr(self, 'panel'): + return + + if self.native: + # destroy all skinning GUI elements + for child in self.panel.Children: + with traceguard: child.Destroy() + + # hide the skin bar and show the native one + self.panel.Hide() + else: + was_shown = self.panel.IsShown() + self._constructSkinElements() + + win = self.ParentWindow.Top + if win.MenuBar is self: + win.SetMenuBar(None) + + self.panel.Sizer.Layout() + self.panel.Show(was_shown) + + + def Append(self, menu, title, onshow = None): + self.toptitles[menu] = title + + i = wx.MenuBar.Append(self, menu, title) + + if not self.native: self._constructSkinElements() + + if onshow is not None: + self.Handler.AddShowCallback(menu.Id, lambda menu=menu: onshow(menu)) + + return i + + def _constructSkinElements(self): + 'Creates the buttons that launch menu dropdowns in the bar.' + + s, p = self.skin, self.panel + p.bg = s.get("background", SkinColor(wx.WHITE)) + + pad = p.padding = s.get("padding",wx.Point(0,0)) + + for child in list(p.Children): + child.Hide() + child.Destroy() + + v = wx.BoxSizer(wx.VERTICAL ) + h = wx.BoxSizer(wx.HORIZONTAL) + v.Add(h,1,wx.EXPAND|wx.TOP|wx.BOTTOM,pad.y) + + p.Sizer = s.get('margins', Margins()).Sizer(v) + + # create a button for each item in the menubar + self.buttons = [] + addb = self.buttons.append + menus, nummenus = self.Menus, len(self.Menus) + for i, (menu, label) in enumerate(menus): + + del menu.OnDismiss[:] + + # the API sucks for getting Titles with mnemonic characters + label = self.toptitles.get(menu, label) + +# (self, parent, id = -1, label='', skin='Button', icon=None, +# pos=wx.DefaultPosition, size=None, style=wx.HORIZONTAL, +# type=None, menu=None, menubarmode=False, onclick = None): + button = UberButton(p, -1, skin = s.itemskin, label = label, + type = "menu", + menu= menu, + menubarmode=True) + addb(button) + + # store some special attributes in the menu, which it will use for + # keyboard/mouse navigation of the menubar + menu._next = menus[(i+1) % nummenus][0] + menu._prev = menus[i-1][0] + menu._button = button + + # add buttons to the size + h.AddMany((b, 0, wx.EXPAND | wx.LEFT, pad.x) for b in self.buttons) + + # bind keyboard accelerators + self._bindAccelerators() + + def Show(self, val): + 'Show or hide the menubar.' + native = self.native + + if native: + win = self.ParentWindow.Top + self.panel.Hide() + + # set or unset the native menubar as needed + if val and not win.MenuBar is self: + win.MenuBar = self + elif not val and win.MenuBar is self: + win.MenuBar = None + else: + self.panel.Show(val) + + def _bindAccelerators(self): + ''' + Skinned menus need to link their accelerators (keyboard shortcuts) + manually. + ''' + accelrems = self.accelremoves + parentproc = self.ParentWindow.ProcessEvent + keybind = self.KeyCatcher.OnDown + + # remove old first + accelrems() + del accelrems[:] + + for menu in self: + for item in menu: + accel = GetAccelText(item) + if accel: + # trigger command events in the parent when the KeyCatcher sees the shortcut + cb = lambda e, item = item, menu = menu: parentproc(menuevt(item)) + accelrems.append(keybind(accel, cb)) + + @property + def KeyCatcher(self): + try: return self.ParentWindow._keycatcher + except AttributeError: + k = self.ParentWindow._keycatcher = KeyCatcher(self.ParentWindow) + return k + + def __len__(self): + "Returns the number of menus in this menubar." + return self.GetMenuCount() + + def __iter__(self): + return iter([self.GetMenu(i) for i in xrange(self.GetMenuCount())]) + + + @property + def SizableWindow(self): + 'The window that should be added to a sizer.' + return self.panel + + @property + def ParentWindow(self): + 'Returns the Parent window owning the menu.' + return self.panel.Parent + + @property + def Handler(self): + 'Returns the event handler for menu accelerators.' + return menuEventHandler(self.panel.Top) + +from wx.lib.pubsub import Publisher + +def _activateapp(message): + ''' + Since windowless menus do not receive capture changed events--we use this as + an indicator to dismiss instead. + ''' + is_active = message.data + vlist = None + if "_lastvlist" in dir(MenuListBox): + vlist = MenuListBox._lastvlist() + + if vlist is not None and not wx.IsDestroyed(vlist) and not wx.IsDestroyed(vlist.menu): + if vlist.menu and vlist.menu.Windowless and not is_active: + vlist.DismissRoot() + +Publisher().subscribe(_activateapp, 'app.activestate.changed') + +class MenuListBox(SkinVListBox): + 'VListBox acting as a view for a wxMenu.' + + def __init__(self, parent, menu): + SkinVListBox.__init__(self, parent, style = wx.NO_BORDER | wx.FULL_REPAINT_ON_RESIZE | wx.WANTS_CHARS) + + + self.menu = menu + self.UpdateSkin() + + self.timer = wx.PyTimer(self._on_submenu_timer) + + + Bind = self.Bind + Bind(wx.EVT_MOUSE_EVENTS, self._mouseevents) + Bind(wx.EVT_MOUSE_CAPTURE_CHANGED, self._capturechanged) + Bind(wx.EVT_LISTBOX, self._listbox) + Bind(wx.EVT_KEY_DOWN, self._keydown) + + self.mouseCallbacks = {wx.wxEVT_MOTION : self._motion, + wx.wxEVT_RIGHT_DOWN: self._rdown, + wx.wxEVT_LEFT_DOWN : self._ldown, + wx.wxEVT_LEFT_UP : self._lup} + + MenuListBox._lastvlist = ref(self) + + def reassign(self, menu): + self.menu = menu + + menuOpenDelayMs = prefprop('menus.submenu_delay', 250) + + def __repr__(self): + return '<%s for %r>' % (self.__class__.__name__, self.menu) + + @property + def ParentPopup(self): + pmenu = self.menu._parentmenu + return None if pmenu is None else pmenu.popup.vlist + + def CalcSize(self): + 'Calculates the width and height of this menu.' + + self.SetItemCount(len(self.menu)) + + height = 0 + dc = wx.MemoryDC() + dc.Font = self.font + s = self.skin + padx = self.padding[0] + iconsize = s.iconsize + subw = s.submenuicon.Width + sepheight = s.separatorimage.Size.height + itemheight = self.itemheight + textExtent = dc.GetTextExtent + + labelw = accelw = 0 + + for item in self.menu: + if item.Kind == ITEM_SEPARATOR: + height += sepheight + else: + height += itemheight + + # keep the heights for the widest label and accelerator + labelw = max(labelw, textExtent(item.Label)[0]) + accelw = max(accelw, textExtent(item.AccelText)[0]) + + # store an x coordinate for where to draw the accelerator + self.accelColumnX = padx + iconsize + padx + padx + labelw + padx + + # sum those biggest widths with the other elements to come up with a total width + width = self.accelColumnX + padx + max(accelw, subw) + padx + self.MinSize = self.Size = (wx.Size(width, height)) + + def OnDrawItem(self, dc, rect, n): + 'Invoked by VListBox to draw one menu item' + + item = self.menu[n] + kind = item.Kind + s = self.skin + iconsize = s.iconsize + submenuicon = s.submenuicon + padx = self.padding.x + selected = self.IsSelected(n) + + drawbitmap, drawlabel = dc.DrawBitmap, dc.DrawLabel + + if kind == ITEM_SEPARATOR: + s.separatorimage.Draw(dc, rect, n) + else: + dc.Font = self.font + + if not item.IsEnabled(): fg = 'disabled' + elif selected: fg = 'selection' + else: fg = 'normal' + dc.TextForeground = getattr(s.fontcolors, fg) + + grect = Rect(*rect) + grect.width = padx + iconsize + padx + + # icon bitmap + bmap = item.Bitmap + if bmap and bmap.Ok(): + bmap = bmap.ResizedSmaller(iconsize) + drawbitmap(bmap, grect.HCenter(bmap), rect.VCenter(bmap), True) + + # checks and radio circles + if item.IsCheckable() and item.IsChecked(): + # if there is a menu icon, show the check in the bottom right + # otherwise center it + if bmap: checkx = grect.Right - s.checkedicon.Width + else: checkx = grect.HCenter(s.checkedicon) + + if kind == ITEM_CHECK: + drawbitmap(s.checkedicon, checkx, rect.VCenter(s.checkedicon), True) + elif kind == ITEM_RADIO: + drawbitmap(s.checkedicon, checkx, rect.VCenter(s.checkedicon), True) + + # main label + rect.Subtract(left = iconsize + 3 * padx) + drawlabel(item.Label, rect, + indexAccel = item.Text.split('\t')[0].find('&'), + alignment = ALIGN_CENTER_VERTICAL) + + # submenu icon + rect.Subtract(right = submenuicon.Width + padx) + if item.SubMenu is not None: + drawbitmap(submenuicon, rect.Right, rect.VCenter(submenuicon), True) + + # accelerator text + acceltext = item.AccelText + if acceltext: + rect.x = self.accelColumnX + padx + drawlabel(acceltext, rect, alignment = ALIGN_CENTER_VERTICAL) + + def OnDrawBackground(self, dc, rect, n): + s = self.skin + bgs = s.backgrounds + + bgname = 'selection' if self.menu[n].Kind != ITEM_SEPARATOR and self.IsSelected(n) else 'item' + bg = getattr(bgs, bgname, None) + if bg: bg.Draw(dc, rect, n) + + def PaintMoreBackground(self, dc, rect): + 'Invoked by SkinnedVList, used to draw the gutter.' + + # draw a gutter down the length of the menu + g = self.skin.backgrounds.gutter + if g: + g.Draw(dc, Rect(rect.x, rect.y, + self.skin.iconsize + self.padding.x * 2, rect.height)) + + def OnMeasureItem(self, n): + item = self.menu[n] + kind = item.Kind + + if kind == ITEM_SEPARATOR: + return self.sepheight + else: + return self.itemheight + + def OnPopup(self): + parentless = self.TopMenu == self.menu + + if parentless: + # call ReleaseCapture here on windows to prevent the control that + # spawned this menu from keeping capture--another right click + # should open a new menu, not the same menu (see #1995) + if wxMSW: + ReleaseCapture_win32() + + if self.menu.Windowless: + if not hasattr(self,'traytimer'): + self.traytimer = UMenuTrayTimer(self) + self.traytimer.Start(50) + + if not self.menu._parentmenu: + self._grabkeyboard() + + if wx.LeftDown(): + self._leftbuttondown = True + + if not self.HasCapture(): + self.CaptureMouse() + self.SetCursor(StockCursor(CURSOR_DEFAULT)) + self.SetFocus() + + def Dismiss(self): + + if hasattr(self,'traytimer'): + self.traytimer.Stop() + + if self.menu._childmenu: + self.menu._childmenu.Dismiss() + self.menu._childmenu = None + + while self.HasCapture(): + self.ReleaseMouse() + + self.Parent.Hide() + + m = self.menu + if m._parentmenu is None: + + if hasattr(self, 'focusHandler'): + self.focusHandler.close() + del self.focusHandler + + wx.CallAfter(self.menu.OnDismiss) + + else: + m._parentmenu._childmenu = None + + + def DismissRoot(self): + self.TopMenu.Dismiss() + + @property + def TopMenu(self): + m = self.menu + while m._parentmenu is not None: + m = m._parentmenu + + return m + + def UpdateSkin(self): + self.SetMargins(wx.Point(0, 0)) + s = self.skin = self.menu.skin + self.sepheight = s.separatorimage.Size.height + + try: self.font = s.font + except KeyError: self.font = default_font() + + self.fontheight = s.font.Height + + try: + self.padding = s.padding + assert isinstance(self.padding.x, int) and isinstance(self.padding.y, int) + except Exception: + self.padding = wx.Point(3, 3) + + self.itemheight = int(self.fontheight + self.padding.y * 2) + + self.Background = s.backgrounds.menu + + + @property + def Window(self): + return self.menu.Window + + def _grabkeyboard(self): + if 'wxMSW' in wx.PlatformInfo: + f = wx.Window.FindFocus() + elif "wxGTK" in wx.Platform: + f = self + else: + f = None + if f: + self.focusHandler = FocusHandler(self, f) + + def _showsubmenu(self, i, highlight = False): + item = self.menu[i] + submenu = item.SubMenu + child = self.menu._childmenu + + if child is submenu: # early exit if menu is already open + return + + if child is not None: # dismiss the current child submenu if there is one + if child: child.Dismiss() + self.menu._childmenu = None + + # open the new submenu if there is one + if i != -1 and submenu is not None: + r = self.ClientRect + r.Y = self.GetItemY(i) + r.Height = self.OnMeasureItem(i) + self.menu._childmenu = submenu + submenu._parentmenu = self.menu + submenu._parentindex = i + submenu.PopupMenu(r.ToScreen(self), submenu = True) + if highlight: submenu.popup.vlist.Selection = 0 + + def _on_submenu_timer(self): + 'Invoked after a certain duration of mouse hover time over a menu item.' + + i = self.Selection + if i != -1 and self.IsShown() and FindWindowAtPointer() is self: + self._showsubmenu(i) + + def _listbox(self, e): + ''' + Invoked on listbox selection (i.e., the mouse moving up or down the + list. + ''' + + self.timer.Start(self.menuOpenDelayMs, True) + + def _emit_menuevent(self, id, type = wx.wxEVT_COMMAND_MENU_SELECTED): + event = wx.CommandEvent(type, id) + self.menu.Handler.AddPendingEvent(event) + + # mouse handling + + def _mouseevents(self, e, wxEVT_MOTION = wxEVT_MOTION, FindWindowAtPoint = FindWindowAtPoint, UberButton = UberButton): + rect = self.ClientRect + pt_original = pt = e.Position + + if not rect.Contains(pt): + menu = self.ParentPopup + + # forward mouse events to Parent, since we're capturing the mouse + oldmenu = self + while menu: + pt = menu.ScreenToClient(oldmenu.ClientToScreen(pt)) + + if menu.ClientRect.Contains(pt): + e.m_x, e.m_y = pt + return menu._mouseevents(e) + + oldmenu, menu = menu, menu.ParentPopup + + # when mousing over a menubar, we need to trigger other buttons + try: + button = self.TopMenu._button + except AttributeError: + # not in a menubar + pass + else: + if e.GetEventType() == wxEVT_MOTION: + ctrl = FindWindowAtPoint(self.ClientToScreen(e.Position)) + if ctrl is not None: + if getattr(self, '_motionswitch', -1) is ctrl: + # use _motionswitch to make sure the mouse actually /moves/ + # over the button, and we're not just receiving an event for when + # it appears. + self._motionswitch = None + self.DismissRoot() + ctrl.menu._showquick = True + ctrl.OnLeftDown() + elif isinstance(ctrl, UberButton) and ctrl.menubarmode and hasattr(ctrl, 'menu'): + # check to see if the button being hovered over is for a menu + # on the same menubar + if ctrl.Parent is button.Parent and not ctrl is button: + self._motionswitch = ctrl + + + e.m_x, e.m_y = pt_original + try: cb = self.mouseCallbacks[e.EventType] + except KeyError: pass + else: cb(e) + + def _motion(self, e): + # changes selection as mouse moves over the list + p = e.Position + i = self.HitTest(p) if self.ClientRect.Contains(p) else -1 + s = self.Selection + + if i != s: + # if we're a submenu and our parent's selection doesn't point to us, + # make it so + p = self.ParentPopup + if p is not None: + pi = getattr(self.menu, '_parentindex', None) + if pi is not None and p.Selection != pi: + p.Selection = pi + + + self.SetSelection(i) + self._emit_lbox_selection(i) + + @property + def LeafMenu(self): + s = self.menu + + while s._childmenu: + s = s._childmenu + + return s + + def _keydown(self, e): + # keys always go to the deepest child menu + self = self.LeafMenu.popup.vlist + + code = e.KeyCode + i, j, m = self.Selection, -1, self.menu + + if code == wx.WXK_DOWN: + j = (i + 1) % len(m) + while j != i and m[j].Kind == wx.ITEM_SEPARATOR: j = (j + 1) % len(m) + + elif code == wx.WXK_UP: + if i == -1: i = len(m) + j = (i - 1) % len(m) + while j != i and m[j].Kind == wx.ITEM_SEPARATOR: j = (j - 1) % len(m) + + elif code == wx.WXK_RETURN: + return self._activate_item(i, submenu = True, highlight = True) + + elif code == wx.WXK_RIGHT: + if i == -1: + pass # skip menus + elif m[i].SubMenu is not None: + self.timer.Stop() + return self._showsubmenu(i, highlight = True) + + # see if the top level menu has a "_next" menu + while m._parentmenu: m = m._parentmenu + next = getattr(m, '_next', None) + + if next is not None: + wx.CallAfter(self.DismissRoot) + next._showquick = True + wx.CallAfter(next._button.OnLeftDown) + + elif code == wx.WXK_ESCAPE: + self.Dismiss() + + elif code == wx.WXK_LEFT: + if m._parentmenu: + self.Dismiss() + else: + prev = getattr(self.menu, '_prev', None) + if prev is not None: + wx.CallAfter(self.DismissRoot) + prev._showquick = True + wx.CallAfter(prev._button.OnLeftDown) + + elif code < 256: + self._on_char(unichr(e.UnicodeKey)) + + if j != -1: + self.SetSelection(j) + + def _on_char(self, char): + ''' + Pressing a printable character on an opened menu... + a) there is an underlined character--pressing the key immediately activates that item + b) there is one item that begins with that key--it is immediately activated + c) there are multiple items beginning with that key--pressing the key repeatedly cycles + selection between them + ''' + char = char.lower() + items = [] + + # Find the first shortcut key preceeded by an & + for item in self.menu: + amp_char = GetAmpChar(item) + if amp_char is not None and char == amp_char.lower(): + return self._activate_item(item, submenu = True, highlight = True) + + # Instead, find all menu items whose first character begins with the + # one we're looking for. + items = [] + + # Get a range of indexes. If there's already a selection, rotate the + # range so that we see items underneath the selection first. + for i in rotated(range(0, len(self.menu)), -self.Selection-1): + item = self.menu[i] + label_text = item.GetItemLabelText() + if label_text and label_text[0].lower() == char: + items.append(i) + + if len(items) == 1: + # only one item--activate it immediately + self._activate_item(items[0], submenu = True, highlight = True) + elif len(items) > 1: + # more than one item--select the first. + self.SetSelection(items[0]) + + def _rdown(self, e): + p, rect = e.Position, self.ClientRect + + # right mouse click outside of any menu dismisses them all + if not rect.Contains(p): return self.DismissRoot() + + def _ldown(self, e): + p, rect = e.Position, self.ClientRect + + # a mouse click outside of a menu causes all the menus to close from + # the top + if not rect.Contains(p): + return self.DismissRoot() + + i = self.HitTest(p) + + if i != -1: + if self.menu[i].SubMenu is not None: + # if clicking a submenu item, show the submenu + self.timer.Stop() + return self._showsubmenu(i) + + def _lup(self, e): + p = e.Position + + i = self.HitTest(e.Position) + if self.ClientRect.Contains(p): + self._activate_item(i) + else: + ctrl = FindWindowAtPointer() + if not isinstance(ctrl, UberButton) or not ctrl.type=='menu': + self.DismissRoot() + + def _activate_item(self, i, submenu = False, highlight = False): + 'Triggers item i (or item at position i).' + + if isinstance(i, int): + if i == -1: return + item = self.menu[i] + else: + item = i + i = self.menu.IndexOf(item) + + if submenu: + if item.SubMenu is not None: + # if clicking a submenu item, show the submenu + self.timer.Stop() + if not self.Selection == i: + self.SetSelection(i) + return self._showsubmenu(i, highlight = highlight) + + if item.Kind != ITEM_SEPARATOR and item.IsEnabled() and item.SubMenu is None: + # clicking anything else that's enabled and not a separator + # emits an event. + if item.IsCheckable(): item.Check(not item.IsChecked()) + self._emit_menuevent(item.Id) + self.DismissRoot() + + def _capturechanged(self, e): + # MouseCaptureChangeEvent never seems to have the captured window...so this + # hack (check for the active window later) will have to do. + + def active(): + try: + if self.menu.Windowless or not hasattr(self.menu.Window.Top, 'IsActive'): + return True # wx.GetApp().IsActive() + else: + return self.menu.Window.Top.IsActive() + except Exception: + print_exc() + return True + + if not active(): + wx.CallAfter(lambda: self.DismissRoot()) + + +class MenuWindowBase(object): + def __init__(self, parent, menu): + self.vlist = MenuListBox(self, menu) + self.UpdateSkin() + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Bind(wx.EVT_PAINT, self._paint) + + def reassign(self, parent, menu): + self.Reparent(parent) + self.vlist.reassign(menu) + + def _paint(self, e): + dc = wx.AutoBufferedPaintDC(self) + self.bg.Draw(dc, self.ClientRect) + + def UpdateSkin(self): + 'Updates frame size margins.' + + s = self.skin = self.vlist.menu.skin + + try: + if self.Sizer and not wx.IsDestroyed(self.Sizer): + self.Sizer.Clear() + except wx.PyDeadObjectError: + return + + self.framesize = s.get('framesize', skin.ZeroMargins) + self.Sizer = self.framesize.Sizer(self.vlist) + self.bg = s.frame + + def _guess_position(self): + # if there's a menu event, and it's Position is equal to + # wxDefaultPosition, that means it was spawned by the context menu key + # so try to guess a good place for the menu: + + # 1) if we're in a text control, use the cursor position. + if self._was_spawned_via_keyboard(): + p = self.vlist.Top.Parent + + if hasattr(p, 'IndexToCoords'): + coords = p.IndexToCoords(p.InsertionPoint) + pt = p.ClientToScreen(coords) + return pt + + # 2) otherwise, use the bottom right of the focused control. + ctrl = wx.Window.FindFocus() + if ctrl is not None: + return ctrl.ScreenRect.BottomRight + + # 3) or just the mouse position + return wx.GetMousePosition() + + def _was_spawned_via_keyboard(self): + menu_event = getattr(self.vlist.menu, '_menu_event', None) + self.vlist.menu._menu_event = None + if menu_event is not None: + menu_event = menu_event() + if menu_event is not None: + return menu_event.Position == wx.DefaultPosition + + return False + + def PopupMenu(self, pos = None, submenu = False): + v = self.vlist + v.menu.Handler._menu_open(menu = v.menu) + + # fit to the menu size + v.SetSelection(-1) + v.CalcSize() + self.Fit() + self.Sizer.Layout() + + if isinstance(self.bg, SplitImage4): + self.Cut(self.bg.GetBitmap(self.Size)) + else: + self.Cut() + + pos = RectPS(self._guess_position(), wx.Size(0, 0)) if pos is None else pos + + try: + try: disp = Monitor.GetFromPoint(pos[:2]).Geometry + except Exception: disp = Monitor.GetFromPoint(pos.BottomRight, find_near=True).Geometry + except Exception: + print_exc() + log.critical('could not find display for %s, falling back to zero', pos) + disp = Monitor.All()[0].Geometry + + size = self.Size + + rects = []; add = lambda *seq: rects.extend([RectPS(p, size) for p in seq]) + + singlepoint = len(pos) == 2 + # turn a point into a rectangle of size (0, 0) + offset = 2 if singlepoint else 0 + if singlepoint: pos = wx.RectPS(pos, (0,0)) + + w, h, wh = Point(size.width - offset, 0), Point(0, size.height-offset), Point(size.width - offset, size.height-offset) + difftop = Point(0, self.framesize.top) + diffbottom = Point(0, self.framesize.bottom) + + if submenu: + add(pos.TopRight - difftop, + pos.TopLeft - w - difftop, + pos.BottomRight - h + diffbottom, + pos.BottomLeft - wh + diffbottom) + else: + add(pos.BottomLeft, + pos.TopLeft - h, + pos.BottomRight - w, + pos.TopRight - h, + pos.TopRight - wh, + pos.BottomLeft - h) + + for rect in rects: + if disp.ContainsRect(rect): + self._showat(rect) + return + + rect = rects[0] + if hasattr(v.menu, '_button'): + # todo: clean up this. + brect = v.menu._button.ScreenRect + if rect.Intersects(brect): + rect.Offset((brect.Width, 0)) + + self._showat(rect) + + def _showat(self, rect, nofade = False): + self.SetRect(rect) + self.EnsureInScreen(client_area = False) + + if nofade: self.Show() + elif getattr(self.vlist.menu, '_showquick', False): + self.vlist.menu._showquick = False + self.Show() + else: + fadein(self, 'xfast') + + # HACK: solves popup Z-fighting + if wxMSW: CallLater(1, lambda: self.Refresh() if self else None) + + self.vlist.OnPopup() + +class MenuPopupWindow(MenuWindowBase, wx.PopupWindow): + def __init__(self, parent, menu): + wx.PopupWindow.__init__(self, parent) + MenuWindowBase.__init__(self, parent, menu) + +class MenuFrameWindow(MenuWindowBase, wx.Frame): + def __init__(self, parent, menu): + wx.Frame.__init__(self, parent) + MenuWindowBase.__init__(self, parent, menu) + +from weakref import WeakValueDictionary + +class MenuEventHandler(wx.EvtHandler): + def __init__(self, parentFrame): + wx.EvtHandler.__init__(self) + + # add this event handler to the frame's list of handlers + parentFrame.PushEventHandler(self) + + self.Bind(wx.EVT_MENU, self.__menu) + # once we've totally moved things over to the new menu impl, + # this will be removed. + if not 'wxMac' in wx.PlatformInfo: + self.Bind(wx.EVT_MENU_OPEN, self._menu_open) + + if 'wxMSW' in wx.PlatformInfo and hasattr(parentFrame, 'BindWin32'): + # EVT_MENU_OPEN is broken and dumb, catch WIN32 messages instead. + parentFrame.BindWin32(WM_INITMENUPOPUP, self._initmenupopup) + + self.cbs = WeakValueDictionary() + self.showcbs = {} + + if wxMSW: + self.hwndMap = WeakValueDictionary() + + def AddCallback(self, id, callback): + self.cbs[id] = callback + + def AddShowCallback(self, id, callback): + self.showcbs[id] = callback + + def __menu(self, e): + id = e.Id + try: cb = self.cbs[id] + except KeyError: e.Skip() + else: cb() + + def _menu_open(self, e = None, menu = None): + if e is not None: + e.Skip() + menu = e.Menu + + if menu is None: return + + try: cb = self.showcbs[menu.Id] + except KeyError: pass + else: cb() + + + if wxMSW: + def _initmenupopup(self, hWnd, msg, wParam, lParam): + 'Invoked when the owner frame gets a WM_INITMENUPOPUP message.' + + try: menu = self.hwndMap[wParam] + except KeyError: return + + if menu._parentmenu: + evt = PyCommandEvent(wxEVT_MENU_OPEN, menu.Id) + evt.Menu = menu + menu.Handler.ProcessEvent(evt) + +class FocusHandler(wx.EvtHandler): + def __init__(self, menu, ctrl): + wx.EvtHandler.__init__(self) + self._menu = menu + self._ctrl = ctrl + + self.Bind(wx.EVT_KEY_DOWN, self._menu._keydown) + self.Bind(wx.EVT_NAVIGATION_KEY, self._menu._keydown) + + self.wantschars = bool(ctrl.WindowStyleFlag & wx.WANTS_CHARS) + + if not self.wantschars: + ctrl.SetWindowStyleFlag(ctrl.WindowStyleFlag | wx.WANTS_CHARS) + + ctrl.PushEventHandler(self) + + def close(self): + ctrl, self._ctrl = self._ctrl, None + ctrl.RemoveEventHandler(self) + + f = ctrl.WindowStyleFlag + if not self.wantschars: + f = f & ~wx.WANTS_CHARS + ctrl.SetWindowStyleFlag(f) + + del self._menu + + + + def SetMenu(self, menu): self._menu = menu + def OnKeyDown(self, event): self._menu.OnKeyDown(event) + #def OnKillFocus(self, event): wx.PostEvent(self._menu, event) + +def menuEventHandler(f): + try: + return f._menuevthandler + except AttributeError: + h = f._menuevthandler = MenuEventHandler(f) + return h + +from gui.toolbox.keynames import keynames, modifiernames + +def menuevt(item): + return wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, item.Id) + +# +# functions for menu accelerators +# + +def GetAccelText(item): + a = item.Accel + return '' if not a else _getacceltext(a.Flags, a.KeyCode) + +if not hasattr(wx, '_MenuItem'): + wx._MenuItem = wx.MenuItem + +# give all menu items an "AccelText" property +wx._MenuItem.AccelText = property(GetAccelText) + +def GetAmpChar(item): + "Returns the character after the ampersand in a menu item's text, or None." + + text = item.Text + amp_index = text.find('&') + if amp_index != -1 and amp_index < len(text)-1: + return text[amp_index + 1] + +@memoize +def _getacceltext(modifiers, key, joinstr = '+'): + return joinstr.join([name for mod, name in modifiernames if mod & modifiers] + \ + [keynames.get(key, chr(key).upper())]) + +from collections import deque + +def rotated(iter, n): + d = deque(iter) + d.rotate(n) + return d + diff --git a/digsby/src/gui/userform.py b/digsby/src/gui/userform.py new file mode 100644 index 0000000..a9948fb --- /dev/null +++ b/digsby/src/gui/userform.py @@ -0,0 +1,125 @@ +import wx +from util import import_function, callany + +from gui.toolbox import GetTextFromUser + + +def getinput(obj, parent, needs, callback, **k): + diag = None + if hasattr(needs, 'Prompt'): + diag = needs(parent, obj) + + + elif isinstance(needs, basestring) and needs.startswith('@'): + diag = import_function(needs[1:])(parent, obj) + + + if diag is not None: + diag.Prompt(callback) + diag.Destroy() + else: + if callable(needs): + needs = needs(obj) + + from pprint import pprint + pprint(needs) + + if len(needs) == 1 and issubclass(needs[0][0], basestring): + type, name, default = needs[0] + val = GetTextFromUser(name, caption = name, default_value = default) + if val is not None: + return callback(val) + return + + FormFrame(obj, parent, needs, callback, **k) + + +def maketext(self, name = '', default = '', obj = None): + if default is None: + default = '' + + if callable(default): + default = callany(default, obj) + ctrl = wx.TextCtrl(self.content, -1, name=name, value=default) + return (name, ctrl) + +typetable = { str : maketext, + int : maketext, + unicode : maketext, } + +class FormFrame(wx.Frame): + + def __init__(self, obj, parent, needs, callback, title = None): + + wx.Frame.__init__( self, parent, title = title or 'Please enter a value' ) + + self.content = content = wx.Panel(self) + content.Sizer = s = wx.FlexGridSizer(0, 2, 15, 15) + self.callback = callback + + # Throw the object at needs if it's callable + + self.needs = needs + + self.gui_items = [self.gentype(need, obj) for need in self.needs] + + for i, item in enumerate(self.gui_items): + if i == 0: + item[1].SetFocus() + + s.Add(wx.StaticText(content, -1, label = _(item[0])), 0, wx.EXPAND | wx.ALL, 3) + s.Add(item[1], 0, wx.EXPAND | wx.ALL, 3) + + b = wx.Button(content, -1, label = 'Submit') + b.Bind(wx.EVT_BUTTON, self.getvalues) + b.SetDefault() + + + s.Add(b, 1, wx.EXPAND | wx.ALL, 7) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(content, 1, wx.EXPAND) + + self.Fit() + self.CenterOnParent() + self.Show(True) + + + def gentype(self, need, obj): + try: + return typetable[need[0]](self, *need[1:], **{'obj': obj}) + except KeyError, e: + raise TypeError("I don't know how to get %r from the user" % need[0]) + + def getvalues(self, e): + vals = [] + for i in range(len(self.gui_items)): + t = self.needs[i][0] if isinstance(self.needs[i], tuple) else self.needs[i] + vals.append(t(self.gui_items[i][1].GetValue())) + + self.Show(False) + self.callback(*vals) + self.Close() + self.Destroy() + + +####Testing#### + +def printstuff(*stuff): + for item in stuff: + print 'type', type(item), 'item', item + + +if __name__ == '__main__': + app = wx.PySimpleApp() + + stuff = ((str,"name1","bleh"), + (int,"name2","45"), + (str,"name3","bleh3"),) + + def callback(): + print 'yay' + + frame = FormFrame( None, None, callback = printstuff, needs=stuff) + app.SetTopWindow( frame ) + app.MainLoop() diff --git a/digsby/src/gui/validators.py b/digsby/src/gui/validators.py new file mode 100644 index 0000000..9e21a8c --- /dev/null +++ b/digsby/src/gui/validators.py @@ -0,0 +1,101 @@ +''' + +Validators for text controls. + +''' + +import wx, string + +class SimpleValidator(wx.PyValidator): + def __init__(self, char_callbacks, str_callbacks): + # Guard agaisnt accidentally calling with a single function + + if callable(char_callbacks): + char_callbacks = (char_callbacks,) + if callable(str_callbacks): + str_callbacks = (str_callbacks,) + + wx.PyValidator.__init__(self) + self.char_callbacks = char_callbacks + self.str_callbacks = str_callbacks + self.Bind(wx.EVT_CHAR, self.OnChar) + self.Bind(wx.EVT_TEXT, self.OnText) + + def OnChar(self, event): + key = event.GetKeyCode() + if key < 0 or key > 255 or chr(key) not in string.printable or self.check_char(chr(key)): + event.Skip() + self._last_ok_val = event.EventObject.Value + return + + if not wx.Validator.IsSilent(): wx.Bell() + + def OnText(self, event): + s = event.EventObject.Value + + if not self.check_string(s): + event.EventObject.Value = self._last_ok_val + else: + self._last_ok_val = s + event.Skip() + + def check_char(self, c): + return all(cb(c) for cb in self.char_callbacks) + + def check_string(self, s): + return all(cb(s) for cb in self.str_callbacks) + + def Clone(self): + return SimpleValidator(self.char_callbacks, self.str_callbacks) + + def Validate(self, win): + return 1 + + def TransferToWindow(self): + """ Transfer data from validator to window. + + The default implementation returns False, indicating that an error + occurred. We simply return True, as we don't do any data transfer. + """ + return True # Prevent wx.Dialog from complaining. + + + def TransferFromWindow(self): + """ Transfer data from window to validator. + + The default implementation returns False, indicating that an error + occurred. We simply return True, as we don't do any data transfer. + """ + return True # Prevent wx.Dialog from complaining. + + def __add__(self, other): + return SimpleValidator(self.char_callbacks + other.char_callbacks, self.str_callbacks + other.str_callbacks) + +def string_check(type): + valid = getattr(string, type) + return lambda s: all(c in valid for c in s) + +def AlphaOnly(): + 'A wxValidator which allows only letters.' + return SimpleValidator((string_check('letters'),), ()) + +def DigitsOnly(): + 'A wxValidator which allows only digits.' + return SimpleValidator((string_check('digits'),), ()) + +def LengthLimit(n): + 'A wxValidator that does not allow more than n characters' + return SimpleValidator((), (lambda s: len(s)<=n,),) + +def NumericLimit(start,stop=None): + if stop is None: + start, stop = 0, start + + return DigitsOnly() + SimpleValidator((), lambda s: ((not s) or (start <= int(s) <= stop))) + + +common_validators = dict( + # Keycode types for formatting strings, i.e. %2(some.pref)d <--- 2 Digits + d = DigitsOnly, + s = AlphaOnly +) \ No newline at end of file diff --git a/digsby/src/gui/vcard/__init__.py b/digsby/src/gui/vcard/__init__.py new file mode 100644 index 0000000..2c40ed4 --- /dev/null +++ b/digsby/src/gui/vcard/__init__.py @@ -0,0 +1 @@ +import vcardgui \ No newline at end of file diff --git a/digsby/src/gui/vcard/vcardgui.py b/digsby/src/gui/vcard/vcardgui.py new file mode 100644 index 0000000..6746a28 --- /dev/null +++ b/digsby/src/gui/vcard/vcardgui.py @@ -0,0 +1,417 @@ +from util.primitives import Storage +from util.primitives.funcs import do +import gettext +gettext.install('digsby') +from gui.controls import * +import jabber + +import wx + +class Holder(object): + pass + +class vcardcontrols(CustomControls): + class _VCardOrg(Holder): + def __init__(self, controls, parent): + self.parent = parent + self.controls = controls + self.namelabel, self.nametext = self.controls.LabeledTextInput(_("Name")) + self.unitlabel, self.unittext = self.controls.LabeledTextInput(_("Unit")) + self.elems = [self.namelabel, self.nametext, self.unitlabel, self.unittext] + def GetValue(self): + if any(getattr(self, attr+'text').Value for attr in ['name', 'unit']): + org = jabber.vcard.VCardOrg('ORG', '') + for attr in ['name', 'unit']: + val = getattr(self, attr+'text').Value + if val: + setattr(org, attr, val) + return org + else: + return None + def SetValue(self, org): + for attr in ['name', 'unit']: + getattr(self, attr+'text').Value = getattr(org, attr) or '' + def clear(self): + for attr in ['name', 'unit']: + getattr(self, attr+'text').Value = '' + Value = property(GetValue, SetValue) + + def VCardOrg(self): + return self._VCardOrg(self, self.parent) + + class _VCardName(Holder): + def __init__(self, controls, parent): + assert False + self.parent = parent + self.controls = controls + l = self.controls.LabeledTextInput + d = dict(family =(_("Last Name"),), + given = (_("First Name"),), + middle = (_("Middle Name"),), + prefix = (_("Prefix"),), + suffix = (_("Suffix"),)) + self.elems = [] + for i in d.iteritems(): + one, two = l(*i[1]); self.elems.append(one); self.elems.append(two) + setattr(self, i[0]+'label', one); setattr(self, i[0]+'text', two); + def GetValue(self): + name = jabber.vcard.VCardName('N', '') + for attr in ['family', 'given', 'middle', 'prefix', 'suffix']: + setattr(name, attr, getattr(self, attr+'text').Value) + return name + def SetValue(self, name): + for attr in ['family', 'given', 'middle', 'prefix', 'suffix']: + getattr(self, attr+'text').Value = getattr(name, attr) + Value = property(GetValue, SetValue) + + def VCardName(self): + return self._VCardName(self, self.parent) + + class _VCardString(object): + _class = jabber.vcard.VCardString + def __init__(self, controls, parent, type_, label): + self.parent = parent + self.controls = controls + self.type_ = type_ + if label is not None: + l = self.controls.LabeledTextInput + self.stringlabel, self.stringtext = l(label) + self.elems = [self.stringlabel, self.stringtext] + else: + self.stringtext = self.controls.TextInput() + self.elems = [self.stringtext] + def GetValue(self): + try: + strng = self._class(self.type_, self.stringtext.Value) + return strng + except: + return None + def SetValue(self,strng): + self.stringtext.Value = strng.value + def clear(self): + self.stringtext.Value = '' + Value = property(GetValue, SetValue) + + class _VCardXString(_VCardString): + _class = jabber.vcard.VCardXString + + def VCardString(self, type_, label): + return self._VCardString(self, self.parent, type_, label) + + def VCardXString(self, type_, label): + return self._VCardXString(self, self.parent, type_, label) + + class _VCardTel(Holder): + def __init__(self, controls, parent): + self.parent = parent + self.controls = controls + self.numberlabel, self.numbertext = self.controls.LabeledTextInput(_("Number")) + self.type = Storage() + self.type.Value = [] +# self.unitlabel, self.unittext = self.controls.LabeledTextInput("Unit") + self.elems = [self.numberlabel, self.numbertext] + def GetValue(self): + if getattr(self, 'numbertext').Value: + tel = jabber.vcard.VCardTel('TEL', '') + val = getattr(self, 'numbertext').Value + tel.number = val + if self.type.Value: + tel.type = self.type.Value[:] + return tel + else: + return None + def SetValue(self, tel): + self.type.Value = tel.type[:] + self.numbertext.Value = tel.number + def clear(self): + self.type.Value = [] + self.numbertext.Value = '' + Value = property(GetValue, SetValue) + + def VCardTel(self): + return self._VCardTel(self, self.parent) + + class _VCardEmail(Holder): + def __init__(self, controls, parent): + self.parent = parent + self.controls = controls + self.addresslabel, self.addresstext = self.controls.LabeledTextInput(_("Email")) + self.type = Storage() + self.type.Value = [] +# self.unitlabel, self.unittext = self.controls.LabeledTextInput("Unit") + self.elems = [self.addresslabel, self.addresstext] + def GetValue(self): + if getattr(self, 'addresstext').Value: + email = jabber.vcard.VCardEmail('EMAIL', '') + val = getattr(self, 'addresstext').Value + email.address = val + if self.type.Value: + email.type = self.type.Value[:] + return email + else: + return None + def SetValue(self, email): + self.type.Value = email.type[:] + self.addresstext.Value = email.address + def clear(self): + self.type.Value = [] + self.addresstext.Value = '' + Value = property(GetValue, SetValue) + + def VCardEmail(self): + return self._VCardEmail(self, self.parent) + + class _VCardAdr(Holder): + def __init__(self, controls, parent): + self.parent = parent + self.controls = controls + l = self.controls.LabeledTextInput + self.poboxtext = Storage() + self.poboxtext.Value = '' + common_ = ('H',) + d = [ + ('street', _("Street")), + ('extadr', ""), +# ('pobox', "PO BOX"), + ('locality', _("City")), + ('region', _("State")), + ('pcode', _("Postal Code")), + ('ctry', _("Country"))] + self.elems = [] + for i in d: + one, two = l(i[1]); self.elems.append(one); self.elems.append(two) + setattr(self, i[0]+'label', one); setattr(self, i[0]+'text', two); +# self.type = "list of etc." + def GetValue(self): + if any(getattr(self, attr+'text').Value for attr in + ['street', 'extadr', 'pobox', 'locality', 'region','pcode','ctry']): + adr = jabber.vcard.VCardAdr('ADR', '') + for attr in ['street', 'extadr', 'pobox', 'locality', 'region','pcode','ctry']: + val = getattr(self, attr+'text').Value + if val: + setattr(adr, attr, val) + return adr + else: + return None + def SetValue(self, adr): + for attr in ['street', 'extadr', 'pobox', 'locality','region','pcode','ctry']: + getattr(self, attr+'text').Value = getattr(adr, attr) or '' + def clear(self): + for attr in ['street', 'extadr', 'pobox', 'locality','region','pcode','ctry']: + getattr(self, attr+'text').Value = '' + Value = property(GetValue, SetValue) + + def VCardAdr(self): + return self._VCardAdr(self, self.parent) + +class VCardGUI(dict): + components={ + #"VERSION": (VCardString,"optional"), + "FN": 'VCardString', #have, finished + "N": 'store', + "NICKNAME": 'VCardString', #have, finished + "PHOTO": 'store', + "BDAY": 'VCardString', #have, finished + "ADR": 'VCardAdr', #have, finished + "LABEL": 'store', + "TEL": 'VCardTel', + "EMAIL": 'VCardEmail', + "JABBERID": 'store', + "MAILER": 'store', + "TZ": 'store', + "GEO": 'store', + "TITLE": 'VCardString', #have, finished + "ROLE": 'VCardString', #have, finished + "LOGO": 'store', +# "AGENT": 'store', + "ORG": 'VCardOrg', #have, finished + "CATEGORIES": 'store', + "NOTE": 'store', + "PRODID": 'store', + "REV": 'store', + "SORT-STRING": 'store', + "SOUND": 'store', + "UID": 'store', + "URL": 'VCardString', #have, finished + "CLASS": 'store', + "KEY": 'store', + "DESC": 'VCardXString', #have, finished + } + def __init__(self, protocol=None, vcard=None): + dict.__init__(self) + self.protocol = protocol + vc = vcard if vcard is not None else self.protocol.vcard + self.init_gui() + self.page1() + self.page2() + self.page3() + self.page4() + self.assign_vc(vc) + f = self.frame + f.Fit() + f.MinSize = wx.Size(f.Size.x * 1.5, f.Size.y) + f.Fit() + from gui import skin + if protocol is None: + f.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon')) + else: + f.SetFrameIcon(protocol.serviceicon) + wx.CallAfter(f.Show) + + def init_gui(self): + name = 'vCard Editor' if self.protocol is not None else 'vCard Viewer' + title = (_('vCard Editor for {username}').format(username=self.protocol.username)) if self.protocol is not None else _('vCard Viewer') + self.frame = f = wx.Frame( None, -1, title = title, name = name) + self.notebook = wx.Notebook(f, -1) + if self.protocol is not None: + #add retrieve/set buttons + f.Sizer = s = wx.BoxSizer(wx.VERTICAL) + s.Add(self.notebook, 1, wx.EXPAND) + + p = wx.Panel(f) + + save = wx.Button(p, wx.ID_SAVE, 'Save') + retrieve = Button(p, _('Retreive'), self.on_retrieve) + cancel = wx.Button(p, wx.ID_CANCEL, 'Cancel') + + save.Bind(wx.EVT_BUTTON, lambda e: self.on_save()) + + p.Sizer = h = wx.BoxSizer(wx.HORIZONTAL) + h.AddStretchSpacer(1) + do(h.Add(b, 0, wx.EXPAND | wx.ALL, 3) for b in [save, retrieve, cancel]) + + s.Add(p, 0, wx.EXPAND) + else: + assert False + #no buttons + pass + + def on_save(self): + print "onsave" + self.protocol.save_vcard(self.retrieve_vcard()) + + def on_retrieve(self): + assert self.protocol is not None + self.protocol.request_vcard('', success=self.handle_vc_stanza) + + def handle_vc_stanza(self, stanza): + q = stanza.get_query() + if not q: return + self.assign_vc(jabber.VCard(q)) + + def assign_vc(self, vc): + for k,v in self.components.items(): + if v == 'store': + self[k] = vc[k] + self.assign_page1(vc) + self.assign_page2(vc) + self.assign_page3(vc) + self.assign_page4(vc) + + def assign_lists(self, names, vc): + for name in names: + self["_" + name] = vc[name][1:] + if vc[name]: + self[name].Value = vc[name][0] + else: + self[name].clear() + + def retrieve_lists(self, names, vc): + for name in names: + if self[name].Value: + vc.content[name] = [self[name].Value] + vc.content[name][1:] = self["_" + name] + + def assign_page1(self, vc): + if vc['FN'] is not None: + self['FN'].Value = vc['FN'] + else: + self['FN'].clear() + self.assign_lists(('NICKNAME', 'BDAY', 'TEL', 'URL','EMAIL'), vc) + + def page1(self): + p = wx.Panel(self.notebook) + self.notebook.AddPage(p, _("General")) + c = vcardcontrols(p) + vcs = c.VCardString + self['FN'] = vcs("FN", _("Full Name")) + self["NICKNAME"] = vcs("NICKNAME", _("Nickname")) + self["BDAY"] = vcs("BDAY", _("Birthday")) + self['TEL'] = c.VCardTel() + self["URL"] = vcs("URL", _("Homepage")) + self['EMAIL'] = c.VCardEmail() + + s = FGridSizer(0, 2, *sum([self['FN'].elems, + self["NICKNAME"].elems, + self["BDAY"].elems, + self['TEL'].elems, + self["URL"].elems, + self['EMAIL'].elems], [])) + s.AddGrowableCol(1) + p.Sizer = s + + def assign_page2(self, vc): + self.assign_lists(('ORG', 'TITLE', 'ROLE'), vc) + + def page2(self): + p = wx.Panel(self.notebook) + self.notebook.AddPage(p, _("Work")) + c = vcardcontrols(p) + vcs = c.VCardString + + self["ORG"] = c.VCardOrg() + self["TITLE"] = vcs("TITLE", _("Position")) + self["ROLE"] = vcs("ROLE", _("Role")) + + s = FGridSizer(0,2, *sum([self["ORG"].elems, + self["TITLE"].elems, + self["ROLE"].elems], [])) + s.AddGrowableCol(1) + p.Sizer = s + + def assign_page3(self, vc): + self.assign_lists(('ADR',), vc) + + def page3(self): + p = wx.Panel(self.notebook) + self.notebook.AddPage(p, _("Location")) + c = vcardcontrols(p) + self["ADR"] = c.VCardAdr() + + s = FGridSizer(0,2,*self["ADR"].elems) + s.AddGrowableCol(1) + p.Sizer = s + + def assign_page4(self, vc): + self.assign_lists(('DESC',), vc) + + def page4(self): + p = wx.Panel(self.notebook) + self.notebook.AddPage(p, _("About")) + c = vcardcontrols(p) + self['DESC'] = c.VCardXString("DESC", None) + + s = FGridSizer(0,1,self['DESC'].stringtext) + s.AddGrowableCol(0) + s.AddGrowableRow(0) + p.Sizer = s + + def retrieve_vcard(self): + vc = jabber.vcard.VCard('') + for k,v in self.components.items(): + if v == 'store': + vc.content[k] = self[k] + vc.content['FN'] = self['FN'].Value + self.retrieve_lists(('NICKNAME', 'BDAY','TEL', + 'URL','EMAIL','ORG', + 'TITLE', 'ROLE','ADR','DESC'), vc) + return vc + + def Prompt(self, callback): + return True + +#abilities: +# liveupdate: func/false +# getvalue +# orientation +# validator diff --git a/digsby/src/gui/video/__init__.py b/digsby/src/gui/video/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/gui/video/webvideo.py b/digsby/src/gui/video/webvideo.py new file mode 100644 index 0000000..9f8a8c7 --- /dev/null +++ b/digsby/src/gui/video/webvideo.py @@ -0,0 +1,76 @@ +''' + +Web based video GUI. + +''' + +from __future__ import with_statement +import wx +from gui import skin +from gui.browser import BrowserFrame, reload_plugins +from gui.toolbox import persist_window_pos, snap_pref +from logging import getLogger; log = getLogger('webvideo') + +video_frame_url = 'http://v.digsby.com/embed.php?id=%(widget_id)s' +video_frame_style = wx.MINIMIZE_BOX | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN | wx.STAY_ON_TOP +video_frame_size = (542, 264) + +def confirm_invite(buddy_name): + msg_header = _('{name} has invited you to an audio/video chat.').format(name=buddy_name) + msg_question = _('Would you like to join?') + + msg = u'{header}\n\n{question}'.format(header=msg_header, question=msg_question) + + return wx.YES == wx.MessageBox(msg, _('Audio/Video Chat Invitation'), style = wx.YES_NO) + +class VideoChatWindow(BrowserFrame): + def __init__(self, title, widget_id, on_close = None): + self.widget_url = video_frame_url % dict(widget_id = widget_id) + reload_plugins() + BrowserFrame.__init__(self, + None, + name = 'Video Chat Window', + title = title, + url = self.widget_url, + style = video_frame_style, + external_links=False) + + self.on_close = on_close + self.SetFrameIcon(skin.get('AppDefaults.TaskBarIcon')) + + self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.__onbeforeload) + + persist_window_pos(self, position_only = True) + snap_pref(self) + self.SetClientSize(video_frame_size) + + log.debug('opening video embed url: %s' % self.widget_url) + + if on_close is not None: + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def __onbeforeload(self, e): + if e.NavigationType == wx.webview.WEBVIEW_NAV_LINK_CLICKED: + url = e.URL + if not any((url.startswith('digsby:'), + url.startswith('javascript:'))): + + # users without flash player get a button linking to Adobe's site. + # however, if the default browser is IE, the site will link to the + # ActiveX version of flash--but we need the "Mozilla" or NPAPI + # plugin for WebKit. intercept the "Get Flash" button click and + # just open the NPAPI installer exe directly. + if url == 'http://www.adobe.com/go/getflashplayer': + url = 'http://fpdownload.macromedia.com/get/flashplayer/current/install_flash_player.exe' + + wx.LaunchDefaultBrowser(url) + e.Cancel() + return + + e.Skip() + + def OnClose(self, e): + self.Hide() + e.Skip() + if self.on_close is not None: + self.on_close() diff --git a/digsby/src/gui/vista.py b/digsby/src/gui/vista.py new file mode 100644 index 0000000..a91f86a --- /dev/null +++ b/digsby/src/gui/vista.py @@ -0,0 +1,239 @@ +''' +Vista visual polish and effects. +''' +from gui.textutil import default_font +from gui.toolbox import TransparentBitmap + +import ctypes, wx + +# Use dwmapi.dll +dwm = ctypes.windll.dwmapi +ux = ctypes.windll.uxtheme + +from ctypes.wintypes import RECT, DWORD, BOOL, HRGN, COLORREF, POINT, LPARAM +from ctypes import byref, c_int32, c_void_p, Structure + +# These controls' default font will be changed. +adjusted_controls = [ + wx.StaticText, + wx.CheckBox, + wx.TextCtrl, + wx.Button, + wx.HyperlinkCtrl, + wx.StaticBox, + wx.RadioBox, + wx.RadioButton, + wx.Choice, + wx.ComboBox, + wx.FilePickerCtrl, + wx.DirPickerCtrl, +] + +# Substitute in a nicer console font for PyCrust +from wx.py.editwindow import FACES +FACES['mono'] = 'Consolas' + +def _change_font(ctrl): + 'Changes the default font of a control after its construction.' + + oldinit = ctrl.__init__ + + def newinit(self, *a, **k): + try: + oldinit(self, *a, **k) + except: + # WXPYHACK + from util import funcinfo + import sys + print >> sys.stderr, '_change_font failed at %s' % funcinfo(oldinit) + raise + self.SetFont(default_font()) + + ctrl.__init__ = newinit + +for ctrl in adjusted_controls: + _change_font(ctrl) + +class DWM_BLURBEHIND(Structure): + _fields_ = [ + ('dwFlags', DWORD), # A bitwise combination of DWM Blur Behind Constants values indiciating which members are set. + ('fEnable', BOOL), # true to register the window handle to DWM blur behind; false to unregister the window handle from DWM blur behind. + ('hRgnBlur', HRGN), # The region within the client area to apply the blur behind. A NULL value will apply the blur behind the entire client area. + ('fTransitionOnMaximized', BOOL) # TRUE if the window's colorization should transition to match the maximized windows; otherwise, FALSE. + ] + +class DTTOPTS(Structure): + _fields_ = [ + ('dwSize', DWORD), + ('dwFlags', DWORD), + ('crText', COLORREF), + ('crBorder', COLORREF), + ('crShadow', COLORREF), + ('iTextShadowType', c_int32), + ('ptShadowOffset', POINT), + ('iBorderSize', c_int32), + ('iFontPropId', c_int32), + ('iColorPropId', c_int32), + ('iStateId', c_int32), + ('fApplyOverlay', BOOL), + ('iGlowSize', c_int32), + ('pfnDrawTextCallback', c_void_p), + ('lParam', LPARAM), + ] + + +DWM_BB_ENABLE = 0x01 +DWM_BB_BLURREGION = 0x02 +DWM_BB_TRANSITIONONMAXIMIZED = 0x04 + +# +# glass effects with dwmapi.dll +# + +def glassExtendInto(win, left = -1, right = -1, top = -1, bottom = -1): + """ + Extends a top level window's frame glass into the client area by the + specified margins. Returns True upon success. + + If desktop composition is not enabled, this function will do nothing + and return False. + """ + rect = RECT(left, right, top, bottom) + + if IsCompositionEnabled(): + dwm.DwmExtendFrameIntoClientArea(win.Handle, byref(rect)) + return True + else: + return False + +def glassBlurRegion(win, region = 0): + bb = DWM_BLURBEHIND() + bb.dwFlags = DWM_BB_ENABLE + bb.fEnable = 1 + bb.hRgnBlur = region + + return dwm.DwmEnableBlurBehindWindow(win.Handle, byref(bb)) + +def IsCompositionEnabled(): + 'Returns True if the WDM is allowing composition.' + + enabled = c_int32(0) + dwm.DwmIsCompositionEnabled(byref(enabled)) + return bool(enabled) +# +# HRESULT DrawThemeTextEx( +# HTHEME hTheme, +# HDC hdc, +# int iPartId, +# int iStateId, +# LPCWSTR pszText, +# int iCharCount, +# DWORD dwFlags, +# LPRECT pRect, +# const DTTOPTS *pOptions +# ); + + +class ThemeMixin(object): + def __init__(self, window): + self._hTheme = ux.OpenThemeData(window.Handle, u"globals") + print 'hTheme', self._hTheme + + def __del__(self): + # Closes the theme data handle. + ux.CloseThemeData(self._hTheme) + +# +# text constants +# + +DTT_COMPOSITED = 8192 +DTT_GLOWSIZE = 2048 +DTT_TEXTCOLOR = 1 + +DT_TOP = 0x00000000 +DT_LEFT = 0x00000000 +DT_CENTER = 0x00000001 +DT_RIGHT = 0x00000002 +DT_VCENTER = 0x00000004 +DT_WORDBREAK = 0x00000010 +DT_SINGLELINE = 0x00000020 +DT_NOPREFIX = 0x00000800 + +def DrawThemeTextEx(win, handle, text): + # Draw the text + dto = DTTOPTS() + uFormat = DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOPREFIX + + dto.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE + dto.iGlowSize = 10 + + rcText2 = RECT(0,0,150,30) + #rcText2 -= rcText2.TopLeft() # same rect but with (0,0) as the top-left + +#HRESULT DrawThemeTextEx( +# HTHEME hTheme, +# HDC hdc, +# int iPartId, +# int iStateId, +# LPCWSTR pszText, +# int iCharCount, +# DWORD dwFlags, +# LPRECT pRect, +# const DTTOPTS *pOptions +#); + + ux.DrawThemeTextEx ( win._hTheme, handle, + 0, 0, unicode(text), -1, + uFormat, byref(rcText2), byref(dto) ); + +class ThemedFrame(wx.Frame, ThemeMixin): + def __init__(self, *a, **k): + wx.Frame.__init__(self, *a, **k) + ThemeMixin.__init__(self, self) + +# when the system changes +#case WM_THEMECHANGED: +# CloseThemeData (hTheme); +# hTheme = OpenThemeData (hwnd, L"MyClassName"); + +if __name__ == '__main__': + app = wx.PySimpleApp() + f = ThemedFrame(None) + + #f.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + + f.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + + + def paint(e): + dc = wx.PaintDC(f) + dc.Brush, dc.Pen = wx.BLACK_BRUSH, wx.TRANSPARENT_PEN + #dc.DrawRectangle(0,0,*f.Size) + + bmap = TransparentBitmap(f.Size) + dc2 = wx.MemoryDC() + dc2.SelectObject(bmap) + dc2.Font = default_font() + dc2.TextForeground = wx.RED + dc2.DrawText('hello', 0, 0) + + #DrawThemeTextEx(f, dc2.GetHDC(), u'Hello World') + dc.DrawBitmap(bmap, 0, 0) + e.Skip(True) + + + + + + + + + + f.Bind(wx.EVT_PAINT, paint) + #glassBlurRegion(f) + + + + f.Show(True) + app.MainLoop() diff --git a/digsby/src/gui/visuallisteditor.py b/digsby/src/gui/visuallisteditor.py new file mode 100644 index 0000000..ee02cab --- /dev/null +++ b/digsby/src/gui/visuallisteditor.py @@ -0,0 +1,334 @@ +import wx +from gui.textutil import CopyFont, default_font +#from gui.toolbox import prnt + +from wx import EXPAND,ALL,TOP,VERTICAL,ALIGN_CENTER_HORIZONTAL,ALIGN_CENTER_VERTICAL,LI_HORIZONTAL + +ALIGN_CENTER = ALIGN_CENTER_HORIZONTAL|ALIGN_CENTER_VERTICAL +TOPLESS = ALL & ~TOP + +bgcolors = [ + wx.Color(238, 238, 238), + wx.Color(255, 255, 255), +] + +hovbgcolor = wx.Color(220, 220, 220) + +#def printlist(list): +# prnt(list) + +class VisualListEditorList(wx.VListBox): + text_alignment = ALIGN_CENTER + min_width = 150 + + def __init__(self, + parent, + list2sort, + prettynames = None, + listcallback = None, + ischecked = None # if given, a function of one argument that determines if an argument is checked + ): + + wx.VListBox.__init__(self, parent) + self.Font = default_font() + self.item_height = self.Font.Height + 12 + + self.oldlist = None + self.prettynames = prettynames or {} + self.listcallback = listcallback + + self.SetList(list2sort) + self.setup_checkboxes(ischecked) + self.BackgroundColour = wx.WHITE + self._hovered = -1 + + Bind = self.Bind + Bind(wx.EVT_MOTION, self.OnMotion) + Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + + Bind(wx.EVT_PAINT,self.OnPaint) + + def CalcMinWidth(self): + return self.min_width + + def SetList(self, seq): + self.thelist = seq[:] + self.SetItemCount(len(self.thelist)) + + height = self.OnMeasureItem(0) * self.ItemCount + self.SetMinSize(wx.Size(self.CalcMinWidth(), height)) + self.RefreshAll() + + def OnPaint(self,event): + event.Skip() + + srect= wx.Rect(*self.Rect) + srect.Inflate(1,1) + pcdc = wx.ClientDC(self.Parent) + pcdc.Brush = wx.TRANSPARENT_BRUSH + + pcdc.Pen = wx.Pen(wx.Colour(213,213,213)) + + pcdc.DrawRectangleRect(srect) + + def GetHovered(self): + return self._hovered + + def GetItem(self, n): + return self.thelist[n] + + def SetHovered(self,i): + slist = self.thelist + if i >= len(slist): + return + + n = self._hovered + self._hovered = i + + if n != -1: + self.RefreshLine(n) + + if i != -1: + self.RefreshLine(i) + + Hovered = property(GetHovered,SetHovered) + + def OnMeasureItem(self,n): + return self.item_height + + def OnDrawBackground(self,dc,rect,n): + dc.Brush = wx.Brush(hovbgcolor if self.Hovered == n else bgcolors[n % len(bgcolors)]) + dc.Pen = wx.TRANSPARENT_PEN + + dc.DrawRectangleRect(rect) + + def OnDrawItem(self,dc,rect,n): + elem = self.thelist[n] + + self._draw_checkbox(dc, rect, n) + + if hasattr(self.prettynames, '__call__'): + name = self.prettynames(elem) + else: + name = self.prettynames.get(self.thelist[n], _('(Unnamed Panel)')) + + dc.Font = self.Font + dc.DrawLabel(name, rect, self.text_alignment) + + def OnMotion(self,event): + rect = self.ClientRect + wap = wx.FindWindowAtPointer() + mp = event.Position + hit = self.HitTest(mp) + dragging = event.Dragging() + selection = self.Selection + thelist = self.thelist + checked = self.checked + + if hit != -1: + cursor = wx.CURSOR_ARROW if self._over_checkbox(mp, hit) else wx.CURSOR_HAND + self.SetCursor(wx.StockCursor(cursor)) + + if not dragging: + if not rect.Contains(mp) or not wap == self: + while self.HasCapture(): + self.ReleaseMouse() + + self.Hovered = -1 + return + + elif not self.HasCapture(): + self.CaptureMouse() + + if dragging and -1 not in (selection, hit) and hit != selection: + self.Selection = hit + item = thelist[selection] + + if checked is not None: + item_checked = checked[selection] + + thelist.pop(selection) + thelist.insert(hit, item) + + if checked is not None: + checked.pop(selection) + checked.insert(hit, item_checked) + + self.Refresh() + + self.Hovered = hit + + def OnLeftDown(self,event): + mp = event.Position + + self.oldlist = list(self.thelist) + hit = self.HitTest(mp) + + if hit != -1 and self._over_checkbox(mp, hit): + self.checked[hit] = not self.checked[hit] + self.listcallback(self.thelist, self.checked) + self.Refresh() + else: + self.Selection = hit + + def OnLeftUp(self,event): + if self.oldlist and self.oldlist != self.thelist and self.listcallback: + if self.checked is not None: + self.listcallback(self.thelist, self.checked) + else: + self.listcallback(self.thelist) + + self.Selection = -1 + self.oldlist = None + + # + # checkbox support + # + + def setup_checkboxes(self, ischecked): + if ischecked is not None: + self.checked = [ischecked(e) for e in self.thelist] + else: + self.checked = None + + self.checkbox_border = 5 + self.checkbox_size = 16 + self.checkbox_rect = wx.Rect(self.checkbox_border, (self.item_height - self.checkbox_size) / 2, self.checkbox_size, self.checkbox_size) + + def _draw_checkbox(self, dc, rect, n): + if self.checked is None: + return + + flag = wx.CONTROL_CHECKED if self.checked[n] else 0 + + # draw a checkbox + cbrect = wx.Rect(*self.checkbox_rect) + cbrect.Offset((rect.x, rect.y)) + wx.RendererNative.Get().DrawCheckBox(self, dc, cbrect, flag) + + rect.x = rect.x + self.checkbox_size + self.checkbox_border * 2 + + def _over_checkbox(self, mp, hit): + if self.checked is None: return False + hitmp = mp - (0, hit * self.item_height) + return self.checkbox_rect.Contains(hitmp) + +class VisualListEditorListWithLinks(VisualListEditorList): + ''' + A "visual list editor" which draws clickable links on the right. + + Subclasses override LinksForRow(n), returning ("Link Text", link_func) + where link_func is a callable taking one argument, the row's item. + + Subclasses should also call PaintLinks(dc, rect, n) in their EVT_PAINT + handlers. + ''' + link_padding = 5 + + def LinksForRow(self, n): + '''Overridden by subclasses''' + return [] + + def PaintLinks(self, dc, rect, n): + '''Should be called by subclassess' EVT_PAINT handler.''' + dc.Font = self.Font + dc.TextForeground = wx.BLUE + + for (link_text, func), rect in self.LinkRectsForRow(n): + rect.y += n * self.item_height + dc.DrawLabel(link_text, rect, wx.ALIGN_CENTER_VERTICAL) + + def __init__(self, *a, **k): + VisualListEditorList.__init__(self, *a, **k) + self.Bind(wx.EVT_LEFT_DOWN, self.__leftdown) + + def __leftdown(self, e): + mp = e.Position + hit = self.HitTest(mp) + link = self._link_hittest(mp, hit) + if link: + link_text, link_func = link + return link_func(self.thelist[hit]) + + e.Skip() + + def _link_hittest(self, mp, hit): + if hit == -1: return + mp = mp - (0, hit * self.item_height) + + for link, rect in self.LinkRectsForRow(hit): + if rect.Contains(mp): + return link + + def LinkRectsForRow(self, hit): + links = self.LinksForRow(hit) + dc = wx.ClientDC(self) + dc.Font = self.Font + + # calculate link rects from the right. + p = self.ClientRect.TopRight + rects = [] + for link_text, func in reversed(links): + w, h = dc.GetTextExtent(link_text) + w += self.link_padding + r = wx.Rect(p.x - w, p.y, w, self.item_height) + rects.append(((link_text, func), r)) + p.x -= w + + rects.reverse() # restore left to right order. + return rects + +class VisualListEditor(wx.Dialog): + dialog_style = wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT + + def __init__(self, parent, list2sort, prettynames=None, listcallback=None, + title=_("Arrange Panels"), + listclass = VisualListEditorList, + ischecked = None): + + wx.Dialog.__init__(self, parent, -1, title, style = self.dialog_style) + + Bind = self.Bind + Bind(wx.EVT_CLOSE, self.Done) + + # construct + panel = wx.Panel(self) + + text = wx.StaticText(panel, -1, _('Drag and drop to reorder'), style = ALIGN_CENTER) + text.Font = CopyFont(text.Font, weight=wx.BOLD) + + self.vle = vle = listclass(panel, list2sort, prettynames, listcallback, ischecked=ischecked) + + Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + self.vle.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + + hline = wx.StaticLine(panel,style = LI_HORIZONTAL) + + done = wx.Button(panel,-1, _('Done')) + done.Bind(wx.EVT_BUTTON,self.Done) + + # layout + main_sizer = self.Sizer = wx.BoxSizer(VERTICAL) + main_sizer.Add(panel, 1, EXPAND) + s = panel.Sizer = wx.BoxSizer(VERTICAL) + + border_size = 6 + s.AddMany([(text, 0, EXPAND|ALL, border_size), + (vle, 1, EXPAND|TOPLESS, border_size), + (hline, 0, EXPAND|TOPLESS, border_size), + (done, 0, EXPAND|TOPLESS, border_size)]) + self.Fit() + + def SetList(self, seq): + return self.vle.SetList(seq) + + def Done(self, event): + self.Hide() + self.Destroy() + + def OnKeyDown(self, e): + if e.KeyCode == wx.WXK_ESCAPE: + self.Close() + else: + e.Skip() diff --git a/digsby/src/gui/vlist/__init__.py b/digsby/src/gui/vlist/__init__.py new file mode 100644 index 0000000..3438a77 --- /dev/null +++ b/digsby/src/gui/vlist/__init__.py @@ -0,0 +1 @@ +from gui.vlist.vlist import VList \ No newline at end of file diff --git a/digsby/src/gui/vlist/scroll.py b/digsby/src/gui/vlist/scroll.py new file mode 100644 index 0000000..04bc547 --- /dev/null +++ b/digsby/src/gui/vlist/scroll.py @@ -0,0 +1,141 @@ +import wx +from wx import HORIZONTAL, VERTICAL, Point, Rect + +lineSize = Point(10, 10) + +class ScrollWindow(wx.Window): + def __init__(self, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.HSCROLL | wx.VSCROLL | wx.FULL_REPAINT_ON_RESIZE, + name = 'ScrollWindow'): + + wx.Window.__init__(self, parent, id, pos, size, style, name) + + evts = ('top bottom lineup linedown pageup pagedown ' + 'thumbtrack thumbrelease').split() + + for e in evts: + self.Bind(getattr(wx, 'EVT_SCROLLWIN_' + e.upper()), + lambda evt, e=e: self._scroll(evt, e)) + + self.Bind(wx.EVT_SIZE, self._size) + + def SetVirtualSize(self, size): + wx.Window.SetVirtualSize(self, size) + print size + self.AdjustScrollbars() + self.Refresh() + + def _scroll(self, e, name): + e.Skip() + + clientRect = self.ClientRect + virtual = self.VirtualSize + + scrollType = e.EventType + orientation = e.Orientation + o_idx = 0 if orientation == HORIZONTAL else 1 + + x, y = self.GetScrollPos(HORIZONTAL), self.GetScrollPos(VERTICAL) + newPos = Point(x, y) + setScrollbars = True + + # THUMBTRACK: dragging the scroll thumb + if scrollType == wx.wxEVT_SCROLLWIN_THUMBTRACK: + newPos[o_idx] = e.Position + #setScrollbars = False + + # THUMBRELEASE: mouse up on the thumb + elif scrollType == wx.wxEVT_SCROLLWIN_THUMBRELEASE: + newPos[o_idx] = e.Position + + # LINEDOWN: clicking the down arrow + elif scrollType == wx.wxEVT_SCROLLWIN_LINEDOWN: + newPos[o_idx] = newPos[o_idx] + lineSize[o_idx] + + # LINEUP: clicking the up arrow + elif scrollType == wx.wxEVT_SCROLLWIN_LINEUP: + newPos[o_idx] = newPos[o_idx] - lineSize[o_idx] + + # PAGEDOWN: clicking below the scroll thumb + elif scrollType == wx.wxEVT_SCROLLWIN_PAGEDOWN: + newPos[o_idx] = newPos[o_idx] + clientRect[2 + o_idx] + + # PAGEUP: clicking above the scroll thumb + elif scrollType == wx.wxEVT_SCROLLWIN_PAGEUP: + newPos[o_idx] = newPos[o_idx] - clientRect[2 + o_idx] + + # keep scroll position within bounds + newPos[0] = max(min(newPos[0], virtual.width - clientRect.width), 0) + newPos[1] = max(min(newPos[1], virtual.height - clientRect.height), 0) + + self.ScrollWindow(-(newPos.x - x), -(newPos.y - y)) + + if setScrollbars: + self.AdjustScrollbars(*newPos) + + def _size(self, e): + self.AdjustScrollbars() + + def AdjustScrollbars(self, x = -1, y = -1): + r = self.Rect + virtual = self.VirtualSize + + if x == -1: x = self.GetScrollPos(HORIZONTAL) + if y == -1: y = self.GetScrollPos(VERTICAL) + + self.SetScrollbar(HORIZONTAL, x, r.Width, virtual.width) + self.SetScrollbar(VERTICAL, y, r.Height, virtual.height) + + def PrepareDC(self, dc): + pt = dc.GetDeviceOrigin() + x, y = self.GetScrollPos(HORIZONTAL), self.GetScrollPos(VERTICAL) + dc.SetDeviceOrigin(pt.x - x, pt.y - y) + + +if __name__ == '__main__': + + def bindPaint(w, paint = None): + w.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + if paint is not None: w.Bind(wx.EVT_PAINT, paint) + + + a = wx.PySimpleApp() + f = wx.Frame(None, title = 'scroll area'); bindPaint(f) + f.Bind(wx.EVT_CLOSE, lambda e: a.ExitMainLoop()) + + f2 = wx.Frame(None, pos = f.Position + tuple(f.Size), size = f.ClientSize, title = 'virtual area', + style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE) + f2.Bind(wx.EVT_SIZE, lambda e: s.SetVirtualSize(f2.Size)) + + + s = ScrollWindow(f) + + def paint(ctrl, e): + dc = wx.AutoBufferedPaintDC(ctrl) + + try: ctrl.PrepareDC(dc) + except AttributeError: pass + + dc.Brush = wx.WHITE_BRUSH + dc.Clear() + p = wx.BLACK_DASHED_PEN + p.SetWidth(10) + dc.Pen = p + + gc = wx.GraphicsContext.Create(dc) + r = Rect(0, 0, *f2.Size) + b = gc.CreateLinearGradientBrush(r.x, r.y, r.width, r.height, + wx.WHITE, wx.BLACK) + gc.SetBrush(b) + gc.DrawRectangle(*r) + + + bindPaint(s, lambda e: paint(s, e)) + bindPaint(f2, lambda e: paint(f2, e)) + + f.Show() + f2.Show() + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/gui/vlist/skinvlist.py b/digsby/src/gui/vlist/skinvlist.py new file mode 100644 index 0000000..194bd3b --- /dev/null +++ b/digsby/src/gui/vlist/skinvlist.py @@ -0,0 +1,177 @@ +from wx import Rect, DCClipper, Brush, RectS, BufferedPaintDC +import wx +from logging import getLogger; log = getLogger('skinvlist') +from traceback import print_exc + + +class SkinVListBox(wx.VListBox): + def __init__(self, parent, id = -1, style = 0): + wx.VListBox.__init__(self, parent, id = id, style = style) + self.Bind(wx.EVT_PAINT, self.paint) + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + self.Bind(wx.EVT_KEY_DOWN, self.__keydown) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.bg = None + self._refreshOnScroll = False + + def GetItemY(self, i): + measure = self.OnMeasureItem + first = self.GetFirstVisibleLine() + last = self.GetLastVisibleLine() + + if i >= 0 and i >= first and i <= last: + try: + return sum(measure(x) for x in xrange(first, i)) + except TypeError: + pass + + return -1 + + def SetBackground(self, background_brush): + self.bg = background_brush + + if not background_brush.ytile and not self._refreshOnScroll: + self._refreshOnScroll = True + self.Bind(wx.EVT_SCROLLWIN, self._onScroll) + elif background_brush.ytile and self._refreshOnScroll: + self._refreshOnScroll = False + self.Unbind(wx.EVT_SCROLLWIN) + + def _onScroll(self, e): + e.Skip() + self.RefreshAll() + + + def GetBackground(self): + return self.bg + + Background = property(GetBackground, SetBackground) + + def PaintMoreBackground(self, dc, rect): + pass + + def __keydown(self, e): + e.Skip() + + scrollPos = self.GetScrollPos(wx.VERTICAL) + def later(): + if scrollPos != self.GetScrollPos(wx.VERTICAL): + self.RefreshAll() + + wx.CallAfter(later) + + # please ignore the excessive "local variables" idiom used in the arguments + # in this function until I move make this class native and move it to CGUI + def paint(self, e): + # + # translation from C++ version in VListBox::OnPaint + # + + + clientSize = self.ClientSize + dc = BufferedPaintDC(self) + + # the update rectangle + rectUpdate = self.GetUpdateClientRect() + + # fill background + crect = self.ClientRect + + if self.bg is not None: + self.bg.Draw(dc, crect) + else: + dc.Brush = Brush(self.BackgroundColour) + dc.Pen = wx.TRANSPARENT_PEN #@UndefinedVariable + dc.DrawRectangleRect(RectS(self.Size)) + + self.PaintMoreBackground(dc, crect) + + # the bounding rectangle of the current line + rectLine = Rect(0, 0, clientSize.x, 0) + + # iterate over all visible lines + lineMax = self.GetVisibleEnd() + lineh = self.OnMeasureItem + drawbg = self.OnDrawBackground + drawsep = self.OnDrawSeparator + drawitem = self.OnDrawItem + margins = self.GetMargins() + + for line in xrange(self.GetFirstVisibleLine(), lineMax): + try: + hLine = lineh(line) + except TypeError: + log.critical('self.OnMeasureItem was None, returning') + del dc + return + + rectLine.height = hLine + + # and draw the ones which intersect the update rect + if rectLine.Intersects(rectUpdate): + # don't allow drawing outside of the lines rectangle + clip = DCClipper(dc, rectLine) + rect = Rect(*rectLine) + + try: + drawbg(dc, Rect(*rect), line) + except Exception: + print_exc() + + try: + drawsep(dc, Rect(*rect), line) + except Exception: + print_exc() + + rect.Deflate(margins.x, margins.y) + + try: + drawitem(dc, rect, line) + except Exception: + print_exc() + del clip + + else: # no intersection + if rectLine.Top > rectUpdate.Bottom: + # we are already below the update rect, no need to continue + # further + break + else: #the next line may intersect the update rect + pass + + rectLine.y += hLine + + return dc + + def _emit_lbox_selection(self, i): + evt = wx.CommandEvent(wx.wxEVT_COMMAND_LISTBOX_SELECTED, self.Id) + evt.SetInt(i) + self.AddPendingEvent(evt) + +if __name__ == '__main__': + + class MyList(SkinVListBox): + def __init__(self, parent): + SkinVListBox.__init__(self, parent) + + def OnGetLineHeight(self, i): + return 20 + + def OnDrawItem(self, dc, rect, n): + dc.SetBrush(wx.RED_BRUSH) #@UndefinedVariable + dc.DrawRectangleRect(rect) + + def OnDrawBackground(self, dc, rect, n): + + pass + + + a = wx.PySimpleApp() + f = wx.Frame(None) + + l = MyList(f) + l.SetItemCount(10) + + f.Show() + a.MainLoop() diff --git a/digsby/src/gui/vlist/vlist.py b/digsby/src/gui/vlist/vlist.py new file mode 100644 index 0000000..e53efed --- /dev/null +++ b/digsby/src/gui/vlist/vlist.py @@ -0,0 +1,306 @@ +from gui.skin.skinparse import makeBrush +import random +import wx +from wx import Rect, RectPS + + + +class VList(wx.ScrolledWindow): + 'A smooth scrolling VListBox.' + + def __init__(self, parent, id = -1, style = wx.NO_BORDER | wx.FULL_REPAINT_ON_RESIZE, + multiple = False, # multiple selection + ): + + wx.ScrolledWindow.__init__(self, parent, style = style) + + if not isinstance(multiple, bool): + raise TypeError('multiple argument must be a bool') + + self._selection = set() + self.visible = list() + self.multiple = multiple + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + Bind = self.Bind + Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + Bind(wx.EVT_PAINT, self.__paint) + Bind(wx.EVT_MOUSEWHEEL, self.__wheel) + Bind(wx.EVT_KEY_DOWN, self.__keydown) + Bind(wx.EVT_LEFT_DOWN, self.__leftdown) + Bind(wx.EVT_LEFT_DCLICK, self.__leftdclick) + + + def SetBackground(self, background_brush): + self.bg = background_brush + + + s = background_brush.ytile + self.EnableScrolling(s, s) + + def GetBackground(self): + return self.bg + + Background = property(GetBackground, SetBackground) + + + def RefreshLine(self, line): + vis = dict((n, (y1, y2)) for y1, y2, n in self.visible) + + if line in vis: + left, top, right, bottom = self.GetItemMargins() + w = self.ClientSize.width + y = self.ViewStart[1] + + y1, y2 = vis[line] + + r = wx.Rect(0, y1 - top - y, w, 0) + r.SetHeight(y2-y1 + top + bottom) + + self.RefreshRect(r, False) + + + def RefreshLines(self, lines): + vis = dict((n, (y1, y2)) for y1, y2, n in self.visible) + left, top, right, bottom = self.GetItemMargins() + w = self.ClientSize.width + y = self.ViewStart[1] + + for line in lines: + if line in vis: + y1, y2 = vis[line] + + r = wx.Rect(0, y1 - top - y, w, 0) + r.SetHeight(y2-y1 + top + bottom) + + self.RefreshRect(r, False) + + def RefreshAll(self): + return self.Refresh() + + def ScrollToLine(self, i): + pass + + def UpdateScrollbars(self): + margins = self.GetItemMargins() + totalHeight = sum(self.OnMeasureItem(n) + for n in xrange(self._itemcount)) + + self.SetScrollbars(0, 1, 0, + (margins[1] + margins[3]) * self._itemcount + + totalHeight) + + def HitTest(self, (x, y)): + ''' + Return the item at the specified (in physical coordinates) position or + wxNOT_FOUND if none, i.e. if it is below the last item. + ''' + + p = wx.Point(x, y) + self.ViewStart + + for y1, y2, n in self.visible: + if p.y >= y1 and p.y < y2: + return n + + return wx.NOT_FOUND + + def GetSelection(self): + if self.multiple: + return self._selection + else: + try: + return list(self._selection)[0] + except IndexError: + return -1 + + + def SetSelection(self, newsel): + if self.multiple: + assert False + else: + if not isinstance(newsel, int): + raise TypeError('single selection lists take an integer argument to SetSelection') + + oldsel = self._selection + self._selection = set([newsel]) + + s = self._selection.union(oldsel) + self.RefreshLines(min(s), max(s)) + + Selection = property(GetSelection, SetSelection) + + def GetFirstVisibleLine(self): + try: + return self.visible[0][-1] + except IndexError: + return -1 + + def GetLastVisibleLine(self): + try: + return self.visible[-1][-1] + except IndexError: + return -1 + + def IsSelected(self, n): + return n in self._selection + + def __wheel(self, e): + self.Scroll(0, self.ViewStart[1] - e.WheelRotation) + + def __keydown(self, e): + c = e.KeyCode + sel = self._selection + + if c == wx.WXK_DOWN: + try: s = sel.pop() + except KeyError: s = -1 # this is random and broken + + if s + 1 == len(self): s = -1 + self._selection = set([s + 1]) + elif c == wx.WXK_UP: + try: s = sel.pop() + except KeyError: s = -1 # this is random and broken + + if s == 0: s = -1 + self._selection = set([s - 1] if s != -1 else [len(self) - 1]) + else: + return e.Skip() + + # update lines which were and are selected + s = self._selection.union(sel) + self.RefreshLines(min(s), max(s)) + + def __paint(self, e): + dc = wx.AutoBufferedPaintDC(self, style = wx.BUFFER_VIRTUAL_AREA) + size = self.ClientSize + viewstartx, viewstarty = self.ViewStart + viewend = viewstarty + size[1] + #region = self.GetUpdateRegion() + #regioniter = wx.RegionIterator(region) + #while regioniter: + # print regioniter.Next() + + self.bg.Draw(dc, RectPS((viewstartx, viewstarty), size)) + + #wxRegionIterator upd(GetUpdateRegion()); // get the update rect list + + left, top, right, bottom = self.GetItemMargins() + + r = Rect(left, top, *size) + r.SetSize((r.Width - right, 0)) + + visible = self.visible + visappend = visible.append + del visible[:] + selection = self._selection + measureitem, drawitem, drawbg = self.OnMeasureItem, self.OnDrawItem, self.OnDrawBackground + offset = r.Offset + + for n in xrange(self._itemcount): + itemHeight = measureitem(n) + + if r.Y > viewend: + break + if r.Y + itemHeight >= viewstarty:# and region.IntersectRect(wx.Rect(r.X, r.Y + viewstarty, r.Width, r.Height)): + r.Height = itemHeight + visappend((r.Y - top, r.Y + r.Height + bottom, n)) + + drawbg(dc, Rect(*r), n, n in selection) + drawitem(dc, Rect(*r), n, n in selection) + + offset((0, itemHeight + top + bottom)) + + def _ensure_visible(self, line): + + for top, bottom, n in self.visible: + if n == line: + return + + + def __leftdown(self, e): + i = self.HitTest(e.Position) + cmd = e.CmdDown() + sel = self._selection + + if set([i]) != sel: + try: + old = self._selection.pop() + except KeyError: + self._selection.add(i) + self.RefreshLine(i) + else: + self._selection.add(i) + s = [old, i] + self.RefreshLines(min(s), max(s)) + + def __leftdclick(self, e): + evt = wx.CommandEvent(wx.wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, self.Id) + evt.SetEventObject(self) + evt.SetInt(self.HitTest(e.Position)) + + self.ProcessEvent(evt) + + @property + def NativeScrollbars(self): + return True + + def GetItemMargins(self): + return (0, 0, 0, 0) + + def SetItemCount(self, n): + self._itemcount = n + self.UpdateScrollbars() + + def GetItemCount(self): + return self._itemcount + + __len__ = GetItemCount + + ItemCount = property(GetItemCount, SetItemCount) + + def OnDrawBackground(self, dc, rect, n, selected): + if selected: dc.Brush = wx.BLUE_BRUSH + else: dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + + dc.DrawRectangleRect(rect) + +class BList(VList): + + def __init__(self, parent): + VList.__init__(self, parent) + + self.itembg = makeBrush('white 40% white 40%') + self.selbg = makeBrush('white 90% white 90%') + + def OnMeasureItem(self, n): + random.seed(n) + return random.randint(15, 80) + + def OnDrawItem(self, dc, rect, n, selected): + if not selected: + self.itembg.Draw(dc, rect, n) + else: + self.selbg.Draw(dc, rect, n) + + + dc.Font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + dc.DrawText('item %d' % n, rect.X, rect.Y) + + + def GetItemMargins(self): + return (3, 3, 3, 20) + +if __name__ == '__main__': + + a = wx.PySimpleApp() + f = wx.Frame(None, size = (240, 650)) + + v = BList(f) + + v.SetItemCount(65) + + f.Show() + a.MainLoop() + diff --git a/digsby/src/gui/widgetlist.py b/digsby/src/gui/widgetlist.py new file mode 100644 index 0000000..c7f8cb8 --- /dev/null +++ b/digsby/src/gui/widgetlist.py @@ -0,0 +1,139 @@ +''' + +GUI for showing a list of widgets. + +''' + +import wx + +from gui.anylists import AnyRow, AnyList +import common + +class WidgetRow(AnyRow): + + checkbox_border = 3 + row_height = 24 + image_offset = (6, 5) + + def __init__(self, parent, widget): + AnyRow.__init__(self, parent, widget, use_checkbox = True) + + self.Bind(wx.EVT_CHECKBOX, self.on_check) + + def on_check(self, e): + row = e.EventObject.Parent + widget = row.data + widget.set_enabled(row.IsChecked()) + + def PopulateControls(self, widget): + self.text = widget.title + self.checkbox.Value = widget.on + + @property + def image(self): + return None + + @property + def popup(self): + if hasattr(self, '_menu') and self._menu: self._menu.Destroy() + from gui.uberwidgets.umenu import UMenu + menu = UMenu(self) + if not self.data.type == 'fb': + menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) + menu.AddItem(_('&Delete'), callback = lambda: self.on_delete()) + menu.AddSep() + common.actions.menu(self, self.data, menu) + + self._menu = menu + return menu + + + def ConstructMore(self): + + # Extra component--the edit hyperlink + + if not self.data.type == 'fb': + edit = self.edit = wx.HyperlinkCtrl(self, -1, _('Edit'), '#') + edit.Hide() + edit.Bind(wx.EVT_HYPERLINK, lambda e: self.on_edit()) + edit.HoverColour = edit.VisitedColour = edit.ForegroundColour + + remove = self.remove = wx.HyperlinkCtrl(self, -1, _('Delete'), '#') + remove.Hide() + remove.Bind(wx.EVT_HYPERLINK, lambda e: self.on_delete()) + remove.HoverColour = remove.VisitedColour = remove.ForegroundColour + + def LayoutMore(self, sizer): + sizer.AddStretchSpacer() + if not self.data.type == 'fb': + sizer.Add(self.edit, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) + + sizer.Add(self.remove, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) + +class WidgetList(AnyList): + 'Widgets list.' + + def __init__(self, parent, widgets, edit_buttons = None): + AnyList.__init__(self, parent, widgets, + row_control = WidgetRow, + edit_buttons = edit_buttons, + draggable_items = False) + Bind = self.Bind + Bind(wx.EVT_LISTBOX_DCLICK, self.on_doubleclick) + Bind(wx.EVT_LIST_ITEM_FOCUSED,self.OnHoveredChanged) + + def on_doubleclick(self, e): + + + self.on_edit(self.GetDataObject(e.Int)) + + def on_edit(self, widget): + + if widget.type == 'fb': + return + + widget.edit() + + + + def OnHoveredChanged(self,e): + row = self.GetRow(e.Int) + + + + if row: + if row.IsHovered(): + if not row.data.type == 'fb': + row.edit.Show() + row.remove.Show() + row.Layout() + row.Refresh() + else: + if not row.data.type == 'fb': + row.edit.Hide() + row.remove.Hide() + row.Layout() + + def OnDelete(self, widget): + 'Called when the minus button above this list is clicked.' + +# widget = self.GetDataObject(self.Selection) + if not widget: return + + # Display a confirmation dialog. + message = _('Are you sure you want to delete widget "{widgetname}"?').format(widgetname=widget.title) + caption = _('Delete Widget') + style = wx.ICON_QUESTION | wx.YES_NO + parent = self + + if wx.MessageBox(message, caption, style, parent) == wx.YES: + widget.delete() + + def OnNew(self, e = None): + 'Called when the plus button above this list is clicked.' + + wx.LaunchDefaultBrowser("http://widget.digsby.com") + +# from digsby.widgets import create +# create() + diff --git a/digsby/src/gui/windowfx.py b/digsby/src/gui/windowfx.py new file mode 100644 index 0000000..e0adf08 --- /dev/null +++ b/digsby/src/gui/windowfx.py @@ -0,0 +1,489 @@ +'''windowfx.py - simple wxWindow effects + +Usage: +>>> import windowfx + +Fade a window in: +>>> myFrame = wx.Frame(None, -1, "My Frame!") +>>> windowfx.fadein(myFrame) + +Fade a window in slowly: +>> windowfx.fadein(myFrame, 'slow') + +Possible speeds are slow, normal, quick, fast, and xfast. You can also specify +a number. + +Set a window to be half transparent: +>>> windowfx.setalpha(myFrame, 128) + +Fade a window out: +>>> windowfx.fadeout(myFrame) # NOTE: This will call myFrame.Destroy() + # see fadeout's docstring for alternatives + +Draw a native button: + dc = wx.PaintDC(f) + windowfx.drawnative(dc.GetHDC(), (10,10,100,40), windowfx.controls.buttonpush) + +''' +from __future__ import division +from __future__ import with_statement +import wx +from traceback import print_exc +import ctypes +from util.vec import vector +from util.primitives.misc import clamp + +from wx import RectPS + +from gui.textutil import default_font +from gui.native.effects import * + +USE_HIRES_TIMER = False + +# adjustable fade speeds +fade_speeds = dict( + xslow = 3, + slow = 7, + normal = 13, + quick = 25, + fast = 37, + xfast = 50 +) + +''' + Native window control values + + for example: + dc = wx.PaintDC(f) + drawnative(dc.GetHDC(), (10,10,100,40), controls.buttonpush) + + also specify an optional state + drawnative(dc.GetHDC(), (10,10,100,40), controls.buttonpush, states.pushed) +''' + +from wx import ImageFromBitmap, BitmapFromImage, RegionFromBitmap + +def ApplyVirtualSmokeAndMirrors(dc,shape): + if shape: + if isinstance(shape, wx.Region): + region=shape + else: + if not shape.GetMask(): + image = ImageFromBitmap(shape) + image.ConvertAlphaToMask() + bitmap = BitmapFromImage(image) + else: + bitmap = shape + region = RegionFromBitmap(bitmap) + + # See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/regions_2h0u.asp + region.Offset(-1,-1) + + dc.SetClippingRegionAsRegion(region) + +def rectdiff(r1, r2): + 'Returns the intersection of two rectangles.' + + tmp = wx.Rect() + if r1.Intersects(r2): + tmp = wx.Rect(max(r1.x, r2.x), max(r1.y, r2.y)) + + myBR, BR = r1.GetBottomRight(), r2.GetBottomRight() + tmp.SetWidth( min(myBR.x, BR.x) - tmp.x ) + tmp.SetHeight( min(myBR.y, BR.y) - tmp.y ) + return tmp + +def fadein(window, speed = 'normal', to = 255): + 'Fades in a window.' + + setalpha(window, 0) + try: + window.ShowNoActivate(True) + except: + window.Show(True) + return Fader(window, step = _speed_val(speed), end = to) + +def fadeout(window, speed = 'normal', on_done = None, from_ = None): + ''' + Fades out a window. + + By default, the window's Destroy function will be invoked when it's + transparency hits zero, but you can provide any callable in on_done. + ''' + return Fader( window, from_ or getalpha(window), 0, -_speed_val(speed), + on_done = on_done or (lambda: window.Destroy()) ) + +# W2K hack +if 'wxMSW' in wx.PlatformInfo: + import ctypes + try: + ctypes.windll.user32.SetLayeredWindowAttributes + except AttributeError: + # + # windows 2000 without one of the newer service packs completely lacks + # SetLayeredWindowAttributes -- just disable fading there. + # + def fadein(window, speed = 'normal', to = 255): + try: + window.Show(True, False) + except: + window.Show(True) + def fadeout(window, speed = 'normal', on_done = None, from_ = None): + on_done = on_done or (lambda: window.Destroy()) + window.Hide() + try: + on_done() + except Exception: + print_exc() + +# the implementations for setalpha (window transparency) vary by platfrom: +if not 'setalpha' in locals(): + raise AssertionError('Sorry, platform is not supported. Your platform info: %s' % \ + ', '.join(wx.PlatformInfo)) + +setalpha.__doc__ = "Sets a window's transparency. 0 is completely transparent, 255 opaque." +setalpha.__name__ = 'setalpha' + +# +# +# + +def _speed_val(speed): + if isinstance(speed, basestring): + if not speed in fade_speeds: + raise KeyError('speed must be one of [%s] or an integer from 0 to 255' % \ + ', '.join(fade_speeds.keys())) + speed = fade_speeds[speed] + + return speed + +class FXTimer(object): + '''Invokes a callback over and over until it returns an expression + that evaluates to False.''' + + + def __init__(self, callback, tick=4, on_done = None): + self.timer = wx.PyTimer(self.on_tick) + self.callback = callback + self.on_done = on_done + self.timer.Start(tick) + + def on_tick(self): + if not self.callback(): + self.timer.Stop() + del self.timer + if self.on_done is not None: + self.on_done() + + def stop(self): + if hasattr(self, 'timer'): + self.timer.Stop() + +class GenTimer(object): + 'Calls a generator over and over until a StopIteration is raised.' + + def __init__(self, gen, callback, on_tick=None, on_done=lambda: None): + self.timer = wx.PyTimer(self.on_tick) + + self.tick_cb = on_tick + self.gen = gen + self.callback = callback + self.on_done = on_done + + def on_tick(self): + try: + val = self.gen.next() + self.callback(val) + + if self.tick_cb: + self.tick_cb() + + except StopIteration: + pass + except Exception: + print_exc() + else: + return + + self.timer.Stop() + del self.timer + self.on_done() + + def start(self, interval_ms): + assert interval_ms > 0 and isinstance(interval_ms, int) + self.timer.Start(interval_ms) + + def stop(self): + if hasattr(self, 'timer'): + self.timer.Stop() + +class Fader(FXTimer): + 'Fades a window from one value to another.' + + def __init__(self, window, start=0, end=255, step=10, tick=8, on_done = None): + if step == 0: + raise ValueError('step cannot be zero') + if end < 0 or end > 255: + raise ValueError('end must be between 0 and 255') + + if hasattr(window, '_fader'): + window._fader.stop() + del window._fader + window._fader = self + + self.range = (start, end, step) + self.tick, self.window, self.value = tick, window, start + self.cback = on_done + FXTimer.__init__(self, self.fade_tick, tick, on_done = self.on_done) + + def fade_tick(self): + start, end, step = self.range + self.value += step + try: + setalpha(self.window, clamp(self.value, 0, 255)) + except wx.PyDeadObjectError: + return self.stop() + + if step < 0: + return self.value > end + elif step > 0: + return self.value < end + else: + raise AssertionError('FXTimer has a zero step!') + + def on_done(self): + if hasattr(self.window, '_fader'): + del self.window._fader + if self.cback: self.cback() + +def interpolate_gen(total_ticks, p1, p2, func = None): + ''' + Will yield [total_ticks] points between p1 and p2, based on the given + interpolation function. + ''' + + + if func is None or func not in globals(): + posfunc = better_logistic + else: + posfunc = globals()[func] if not callable(func) else func + diff = vector(p2) - vector(p1) + normal, total_distance = diff.normal, diff.length + + for n in xrange(total_ticks): + d = total_distance * posfunc(n / total_ticks) + yield p1 + normal * d + + yield p2 # make sure we end on the destination + + +from math import e as math_e, log as math_log + +def logistic(x, mu = 9): + 'Smoothing function (s curve).' + + return 1 - 1 /( 1 + math_e ** ((x - 1/2) * mu)) + +def better_logistic(x): + return (.5-(1.0/(1+math_e**(x*6.5))))*2 + + +def ln_func(x): + return max(0, float(math_log(x+.1) + 2)/2.1) + +def linear(x): + return x + +class InterpTimer(GenTimer): + def __init__(self, func, p1, p2, time=200, interval=10, on_tick=None, on_done = lambda: None, method = None): + total_ticks = int(time / interval) + gen = interpolate_gen(total_ticks, p1, p2, func = method) + GenTimer.__init__(self, gen, func, on_tick=on_tick, on_done=on_done) + self.start(interval) + +def move_smoothly(win, position, oldpos = None, time = 200, interval = 10): + '''Moves a window to a new position smoothly. + + Optionally specify an old position.''' + + if hasattr(win, '_timer'): + win._timer.stop() + + if oldpos is None: + oldpos = vector(win.Rect[:2]) + + if vector(oldpos) == vector(position): + return + + def cb(p): + try: + win.SetRect(RectPS(p, win.Size)) + except wx.PyDeadObjectError: + win._timer.stop() + del win._timer + + win._timer = InterpTimer(cb, oldpos, position, time = time, interval = interval) + win._newpos = position + return win._timer + +def resize_smoothly(win, newsize, oldsize = None, on_tick=None, set_min = False, method=None, time=150): + 'Resizes a window smoothly. Optionally specify an old size.' + + if hasattr(win, '_resizetimer'): + win._resizetimer.stop() + + win.SetAutoLayout(False) + + if oldsize is None: + oldsize = vector(win.Size) + else: + assert len(oldsize) == 2 + oldsize = vector(oldsize) + + if set_min: + def mysize(*a): + #win.SetMinSize(*a) + win.SetLA + win.SetSize(*a) + win.Refresh() + else: + def mysize(*a): + p = win.Rect + win.SetRect(wx.Rect(p[0], p[1], *a[0])) + + try: + win._resizetimer = InterpTimer(mysize, oldsize, newsize, time=time, + on_tick=on_tick, on_done = lambda: (win.SetAutoLayout(True), win.Layout(), win.Refresh()), + method = method) + return win._resizetimer + except wx.PyDeadObjectError: + pass + +wx.Window.FitSmoothly = lambda win, on_tick = None: resize_smoothly(win, win.GetBestSize(), on_tick = win.Refresh(), time=80) + +def getfloat(n, default): + try: return float(n) + except ValueError: return default + +class SlideTimer(wx.Timer): + 'Animated window move, gradually getting slower as it approaches.' + + stop_threshold = 2 + dampen = .4 + tick_ms = 10 + + observing = False + + def __init__(self, window): + if not self.observing: + from common import profile + profile.prefs.link('infobox.animation.slide_speed', + lambda s: setattr(self, 'dampen', getfloat(s, .4))) + self.observing = True + + wx.Timer.__init__(self) + self.window = window + + def Start(self): + if tuple(self.target) != tuple(self.window.ScreenRect[:2]): + wx.Timer.Start(self, self.tick_ms) + + def Notify(self): + ''' + Invoked by the wxTimer. + + Decides how much to move, and if it is time to stop. + ''' + if not self: return + if not self.window: return + winrect = self.window.ScreenRect + w, pos, target = self.window, vector(winrect[:2]), vector(self.target) + move = lambda newpos: w.SetRect((newpos[0], newpos[1], winrect.width, winrect.height)) + + if pos.to(target) < self.stop_threshold: + move(self.target) + self.Stop() + else: + move( pos + (target - pos) * self.dampen ) + + +def slide_to(win, pos): + 'Moves win smoothly to "pos" (a 2-tuple).' + + try: + t = win._spring_timer + except AttributeError: + t = win._spring_timer = SlideTimer(win) + + t.target = pos + t.Start() + return t + +wx.Window.SlideTo = slide_to + +def testSliding(): + app = wx.PySimpleApp() + f = wx.Frame(None, -1, 'Slide Test') + f.Sizer = wx.BoxSizer(wx.HORIZONTAL) + panel = wx.Panel(f) + f.Sizer.Add(panel, 1, wx.EXPAND) + + + panel.BackgroundColour = wx.WHITE + + h = wx.BoxSizer(wx.HORIZONTAL) + sz = wx.BoxSizer(wx.VERTICAL) + + class TestListBox(wx.VListBox): + def __init__(self, parent): + print 'yo' + wx.VListBox.__init__(self, parent, -1, style = wx.SUNKEN_BORDER) + self.SetMinSize((300,0)) + + def OnDrawItem(self, dc, rect, n): + dc.Pen = wx.BLACK_PEN + dc.Brush = wx.WHITE_BRUSH + dc.Font = default_font() + dc.DrawText(str(n), rect.x, rect.y) + + def OnMeasureItem(self, n): + return 13 + + lst = TestListBox(f) + lst.SetItemCount(300) + + + b = wx.Button(panel, -1, 'Slide', style=wx.NO_BORDER) + b2 = wx.Button(panel, -1, 'Resize') + b3 = wx.Button(panel, -1, 'Both!') + + def both(e): + move_smoothly(f, (600,500)) + resize_smoothly(f, (300,300)) + + b.Bind (wx.EVT_BUTTON, lambda e: move_smoothly(f, (400,400))) + b2.Bind(wx.EVT_BUTTON, lambda e: resize_smoothly(f, (600,160))) + b3.Bind(wx.EVT_BUTTON, both) + sz.Add(b, flag = wx.ALL, border = 3) + sz.Add(b2, flag = wx.ALL, border = 3) + sz.Add(b3, flag = wx.ALL, border = 3) + + panel.Sizer = h + h.Add(sz, 1, wx.EXPAND) + h.Add(lst, 0, wx.EXPAND) + f.Sizer.Layout() + + f.Show(True) + app.MainLoop() + +vista = False + +# Is this Windows Vista? +if 'wxMSW' in wx.PlatformInfo and hasattr(ctypes.windll, 'dwmapi'): + glassEnabled = True + vista = True + from gui.vista import * +else: + glassEnabled = False + vista = False + diff --git a/digsby/src/gui/wxextensions.py b/digsby/src/gui/wxextensions.py new file mode 100644 index 0000000..bae2e02 --- /dev/null +++ b/digsby/src/gui/wxextensions.py @@ -0,0 +1,177 @@ +''' +Cross-platform wx API extensions go here. +''' + +import new +import traceback + +def SetMinHeight(self,h): + self.minHeight = h + if h != -1 and self.GetSize().height < h: + self.SetSize((-1, h)) + +def GetMinHeight(self): + return self.minHeight + +import wx.lib.expando + +wx.lib.expando.ExpandoTextCtrl.SetMinHeight = SetMinHeight +wx.lib.expando.ExpandoTextCtrl.GetMinHeight = GetMinHeight +wx.lib.expando.ExpandoTextCtrl.GetExtraHeight = lambda self: self.extraHeight + +del SetMinHeight +del GetMinHeight + +import cgui + +if hasattr(cgui, 'TextCtrlAutoComplete'): + wx.TextCtrl.AutoComplete = new.instancemethod(cgui.TextCtrlAutoComplete, None, wx.TextCtrl) + +if not hasattr(wx, "IsMainThread"): + import threading + def isMainThread(): + return threading.currentThread().getName() == 'MainThread' + wx.IsMainThread = isMainThread + +wx.Rect.Clamp = new.instancemethod(cgui.RectClamp, None, wx.Rect) +wx.Rect.ClampPoint = new.instancemethod(cgui.RectClampPoint, None, wx.Rect) + +wx.ContextMenuEvent.IsKeyPress = lambda e: e.Position == (-1, -1) + +# Easier to remember ways to start repeating and non-repeating timers +wx.Timer.StartRepeating = lambda self, ms: wx.Timer.Start(self, ms, False) +wx.Timer.StartOneShot = lambda self, ms: wx.Timer.Start(self, ms, True) + +def CreateTransparentBitmap(w, h): + bitmap = wx.EmptyBitmap(w, h) + bitmap.UseAlpha() + return bitmap + +wx.TransparentBitmap = CreateTransparentBitmap + +try: + from cgui import SetBold +except ImportError: + import sys + print >>sys.stderr, 'WARNING: using slow SetBold' + def SetBold(win, bold = True): + "Sets the window's font to bold or not bold." + f = win.Font + f.SetWeight(wx.FONTWEIGHT_BOLD if bold else wx.FONTWEIGHT_NORMAL) + win.Font = f + +if getattr(wx, 'WXPY', False): + wx.WindowClass = wx._Window +else: + wx.WindowClass = wx.Window + +wx.WindowClass.SetBold = new.instancemethod(SetBold, None, wx.WindowClass) + +def call_later_repr(self): + from util import funcinfo + fi = funcinfo(self.callable) + if self.running: + return '' % fi + else: + return '' % fi + +wx.CallLater.__repr__ = call_later_repr + +def ReleaseAllCapture(ctrl): + '''Releases all of this control's mouse capture.''' + + while ctrl.HasCapture(): + ctrl.ReleaseMouse() + +wx.WindowClass.ReleaseAllCapture = ReleaseAllCapture + +if not getattr(wx, 'WXPY', False): + wx.Dialog.__enter__ = lambda self: self + wx.Dialog.__exit__ = lambda self, type, value, traceback: self.Destroy() + + def IsDestroyed(win): + return not bool(win) + + from cStringIO import StringIO + def ImageFromString(s): + return wx.ImageFromStream(StringIO(s)) + + wx.ImageFromString = ImageFromString + wx.AcceleratorTableFromSequence = wx.AcceleratorTable + wx.IsDestroyed = IsDestroyed + del IsDestroyed + + +if not hasattr(wx.RendererNative, 'DrawFocusRect'): + # Patched wx on MSW has wxRenderer::DrawFocusRect + wx.RendererNative.DrawFocusRect = lambda *a, **k: None + +try: + import webview +except ImportError: + traceback.print_exc() +else: + if not hasattr(webview.WebView, 'ResetTextSize'): + webview.WebView.ResetTextSize = lambda *a, **k: None + + if not hasattr(webview.WebView, 'SetMouseWheelZooms'): + webview.WebView.SetMouseWheelZooms = lambda *a, **k: None + +if "wxGTK" in wx.PlatformInfo: + orig_bmpeq = wx.Bitmap.__eq__ + def bmpeq(self, other): + if isinstance(other, wx.Bitmap): + return orig_bmpeq(self, other) + return False + wx.Bitmap.__eq__ = bmpeq + + +def RaiseExisting(cls): + ''' + Raises the first existing window of the specified class. + + If one was found, the window is returned. + ''' + + for tlw in wx.GetTopLevelWindows(): + if type(tlw) is cls: + tlw.Raise() + return tlw + +wx.TopLevelWindow.RaiseExisting = classmethod(RaiseExisting) + +def MakeOrShow(cls, parent = None): + win = cls.RaiseExisting() + + if win is None: + win = cls(parent) + wx.CallAfter(win.Show) + + return win + +wx.TopLevelWindow.MakeOrShow = classmethod(MakeOrShow) + +try: + from cgui import LeftDown +except ImportError: + def LeftDown(): + return wx.GetMouseState().LeftDown() + +wx.LeftDown = LeftDown + +if not hasattr(wx.PyTimer, 'SetCallback'): + def SetCallback(self, cb): + assert hasattr(self, '__call__') + self.notify = cb + + wx.PyTimer.SetCallback = SetCallback + +#sized_controls.py:474 "item.SetUserData({"HGrow":0, "VGrow":0})" +def SetUserData(self, data): + self._userData = data + +def GetUserData(self): + return getattr(self, '_userData', None) + +wx.SizerItem.SetUserData = SetUserData +wx.SizerItem.GetUserData = GetUserData diff --git a/digsby/src/hooks.py b/digsby/src/hooks.py new file mode 100644 index 0000000..418544e --- /dev/null +++ b/digsby/src/hooks.py @@ -0,0 +1,64 @@ +from __future__ import with_statement + +from peak.util.plugins import Hook +import traceback + +__all__ = ['register', 'reduce'] + +def register(hook_identifier, cb, impl=None): + return Hook(hook_identifier, impl).register(cb) + +# XXX: isn't this actually foldl ? +def reduce(hook_identifier, arg, *args, **kwargs): + ''' + Passes arg to the first Hook found for hook_identifier. The return value + of that hook goes to the next hook, and so on. The return value of the + last Hook becomes the return value of this function. + ''' + raise_exceptions = kwargs.pop('raise_exceptions', False) + impls = kwargs.pop('impls', [kwargs.pop('impl', None)]) + for impl in impls: + for hook in Hook(hook_identifier, impl): + if raise_exceptions: + arg = hook(arg, *args, **kwargs) + else: + try: + arg = hook(arg, *args, **kwargs) + except Exception: + traceback.print_exc() + return arg + +def notify(hook_identifier, *a, **k): + for res in each(hook_identifier, *a, **k): + pass + +def each(hook_identifier, *a, **k): + impls = k.pop('impls', [k.pop('impl', None)]) + for impl in impls: + for hook in Hook(hook_identifier, impl): + try: + yield hook(*a, **k) + except Exception: + traceback.print_exc() + +def first(hook_identifier, *a, **k): + impls = k.pop('impls', [k.pop('impl', None)]) + raise_exceptions = k.pop('raise_hook_exceptions', False) + + for impl in impls: + for hook in Hook(hook_identifier, impl = impl): + try: + v = hook(*a, **k) + if v is not None: + return v + except Exception: + if raise_exceptions: + raise + + traceback.print_exc() + + return None + +builtin_any = any +def any(hook_identifier, *a, **k): + return builtin_any(each(hook_identifier, *a, **k)) diff --git a/digsby/src/hub.py b/digsby/src/hub.py new file mode 100644 index 0000000..96be7d4 --- /dev/null +++ b/digsby/src/hub.py @@ -0,0 +1,301 @@ +from __future__ import with_statement + +import wx +from gui.authorizationdialog import AuthorizationDialog +from logging import getLogger +log = getLogger('hub'); info = log.info +from util.singletonmixin import Singleton +from util.primitives.error_handling import traceguard +from util.primitives.funcs import Delegate, get +from common import profile, fire, pref +from cStringIO import StringIO +from PIL import Image +import sys +from common.protocolmeta import protocols + +# TODO: this class doesn't really seem like it's worth having around. +# It's sort of a controller between the protocols and the UI but +# we clearly don't use it for every place the two layers interact. We +# should either buff this class up or (more preferably) kill it off. + +PROMOTE_STRING = '

I use digsby!' + +class Hub(Singleton): + def __init__(self): + Singleton.__init__(self) + + self._locale_obj = None + self.getting_profile_for = None + + def signoff(self): + 'Called during signoff.' + + def filter_message(self, mobj=None, *a, **k): + if mobj is None: + return + + conv = mobj.conversation + conn = conv.protocol + buddy = mobj.buddy or mobj.conversation.buddy + + if conn.allow_message(buddy, mobj) is False: # can return None as well. + log.debug('Message from %r is being ignored', buddy) + log.debug_s('The message was %r', mobj) + return Delegate.VETO + + def launchurl(self, url): + wx.LaunchDefaultBrowser(url) + + def windowparent(self): + wins = wx.GetTopLevelWindows() + return wins[0] if wins else None + + def get_file(self, msg = 'Choose a file'): + filediag = wx.FileDialog(self.windowparent(), msg) + if filediag.ShowModal() == wx.ID_OK: + return filediag.GetPath() + + def get_dir(self, msg = 'Choose a directory'): + dirdiag = wx.DirDialog(self.windowparent(), msg) + return dirdiag.GetPath() if dirdiag.ShowModal() == wx.ID_OK else None + + def on_conversation(self, convo, quiet = False): + """ + The network has indicated that a conversation you are involved in is + beginning. + """ + + log.critical('on_conversation is deprecated and does nothing') + + def send_message(self, buddy, message): + buddy.protocol.send_message(buddy=buddy.name, msg=message) + + def user_message(self, message, title = ''): + wx.CallAfter(wx.MessageBox, message, title) + + def on_error(self, e): + import traceback + log.error(traceback.format_exc()) + + title = get(e, 'header', 'Error:') + msg = get(e, 'major', '%s: %s'%(type(e).__name__,str(e))) + details = get(e, 'minor', '') + + close = (_('Close'), lambda: None) + + fire('error', + title = title, msg = msg, details = details, + sticky = True, popupid = Exception, buttons = (close,), update = 'replace') + + + def call_later(self, c, *a, **k): + c(*a, **k) + + + def on_file_request(self, protocol, xferinfo): + 'A protocol is asking you to receive a file.' + + if xferinfo not in profile.xfers: + if pref('filetransfer.auto_accept_from_blist', default=False) and \ + profile.blist.on_buddylist(xferinfo.buddy): + profile.xfers.insert(0, xferinfo) + wx.CallAfter(xferinfo.save) + else: + xferinfo.state = xferinfo.states.WAITING_FOR_YOU + notifies = fire('filetransfer.request', buddy = xferinfo.buddy, + target = xferinfo) + xferinfo.notifications = notifies + profile.xfers.insert(0, xferinfo) + + def on_direct_connect(self, dc): + caption = _('{protocolname} DirectIM').format(protocolname=dc.protocol.name.capitalize()) + msg = _("{name} wants to directly connect with you. (Your IP address will be revealed.)").format(name=dc.buddy.name) + + dc.accept() if self.popup(msg, caption) else dc.decline() + + def on_invite(self, protocol, buddy, room_name, message='', + on_yes = None, + on_no = None): + 'Someone has invited you to a chat room or conference.' + + if not pref('messaging.groupchat.enabled', False): + log.warning('groupchat pref is off, ignoring chat invite') + maybe_safe_call(on_no) + return + + message = u'\n\n' + (message if message else _(u'Would you like to join?')) + buddy_name = getattr(buddy, 'name', unicode(buddy)) + + if buddy is not None: + # an invite from a buddy. + msg = (_(u'{name} has invited you to a group chat.').format(name=buddy_name)) + message + else: + # an anonymous invite. just say (mysteriously) that you have been invited + msg = _(u'You have been invited to a group chat.') + message + + def cb(join): + if join: + on_yes() if on_yes is not None else protocol.join_chat_room(room_name) + else: + maybe_safe_call(on_no) + + res = fire('chatinvite.received', buddy=buddy, minor=msg, + buttons = [ + (_('Join'), lambda *a: on_yes()), + (_('Ignore'), lambda *a: maybe_safe_call(on_no)), + ]) + + @wx.CallAfter # allow popups to fire + def after(): + if res.had_reaction('Popup'): + return + + from gui.toolbox import SimpleMessageDialog + protocol_name = protocol.account.protocol_info().name + title = _('{protocol_name:s} Invite').format(protocol_name=protocol_name) + diag = SimpleMessageDialog(None, + title=title, + message=msg.strip(), + ok_caption=_('Join Chat'), + icon = protocol.serviceicon.Resized(32), + cancel_caption=_('Ignore Invite'), + wrap=450) + diag.OnTop = True + diag.ShowWithCallback(cb) + + def authorize_buddy(self, protocol, buddy, message = "", username_added = None, callback = None): + message = message.strip() + if message: + message = '\n\n"%s"' % message + + bname = getattr(buddy, 'name', None) or buddy + + if callback is None: + callback = protocol.authorize_buddy + + if username_added is None: + username_added = protocol.username + + if bname != protocol.self_buddy.name: + diag_message = _(u'Allow {buddy} to add you ({you}) as a buddy on {protocol}?').format( + buddy=bname, + you=username_added, + protocol=protocols[protocol.service].name) + + diag_message += message + ad = AuthorizationDialog(protocol, buddy, diag_message, username_added, callback) + ad.Show(True) + else: + callback(buddy, True, username_added) + + def on_mail(self, protocol, inbox_count, others_count=None): + log.info('%s has %s new mail messages', protocol.username, inbox_count) + if others_count: + log.info('%s has %s new OTHER mail messages', protocol.username, others_count) + + def send_typing_status(self, buddy, status): + buddy.protocol.send_typing_status(buddy.name,status) + + def set_buddy_icon(self, wximage): + img = wximage.PIL + w, h = img.size + max = profile.MAX_ICON_SIZE + + # resize down to MAXSIZE if necessary. + if w > max or h > max: + img = img.Resized(max) + + # Save as PNG + imgFile = StringIO() + img.save(imgFile, 'PNG', optimize = True) + + self.set_buddy_icon_file(imgFile.getvalue()) + + def set_buddy_icon_file(self, bytes): + if hasattr(bytes, 'read'): + bytes = bytes.read() + if not isinstance(bytes, str): raise TypeError + + maxsz = profile.MAX_ICON_SIZE + from digsby.abstract_blob import MAX_BLOB_SIZE as maxbytes + + nextsize = maxsz + tries = 0 + while len(bytes) > maxbytes and tries < 10: + log.warning("image (%dx%d) is larger than %d bytes, have to resize", + nextsize, nextsize, maxbytes) + + img = Image.open(StringIO(bytes)).Resized(nextsize) + newimg = StringIO() + img.save(newimg, 'PNG', optimize = True) + bytes = newimg.getvalue() + + nextsize = max(20, nextsize - 10) + tries += 1 + + # Save out blob + log.info('setting %d bytes of icon data (max is %d): %s', + len(bytes), maxbytes, bytes[:5]) + + profile.save_blob('icon', bytes) + + for acct in profile.account_manager.connected_accounts: + with traceguard: + acct.connection.set_and_size_icon(bytes) + + def get_locale(self): + # On *nix/Mac, locale.getdefaultlocale() requires LANG to be set in the + # environment, but it appears it isn't set for app bundles, which causes this to + # return (None, None). That in turn trips up ICQ/AIM loading. :( So use the wx + # locale class instead, which gives us a valid result even in the app bundle case. + # FIXME: is getfilesystemencoding() the correct encoding here? + if not self._locale_obj: + self._locale_obj = [wx.Locale(wx.LANGUAGE_DEFAULT).GetCanonicalName(), sys.getfilesystemencoding()] + return self._locale_obj + + def get_lang_country(self): + lang_country = self.get_locale()[0] + lang,country = lang_country.split('_') + + # for things like sr_SR@latin + # wish I knew what locale codes we were dealing with. + # I see sr_SP and sr_SR@latin. + # sr_SP, from python locale settings, I could not find a reference for on the internet, + # though I didn't do much better for sr_SR. The @latin is to differentiate from cyrillic + # -chris + return lang, country.lower().split('@')[0] + + def get_country(self): + return self.get_lang_country()[1] + + country = property(get_country) + + def get_encoding(self): + return self.get_locale()[1] + + def ProcessEvent(self, e): + print 'ProcessEvent', e + + def get_language(self): + return self.get_lang_country()[0] + + language = property(get_language) + + @classmethod + def getThreadsafeInstance(cls): + from events import ThreadsafeGUIProxy + return ThreadsafeGUIProxy(cls.getInstance()) + + +get_instance = Hub.getInstance + +def diskspace_check(size): + return True + +def maybe_safe_call(cb): + v = None + if cb is not None: + with traceguard: + v = cb() + + return v diff --git a/digsby/src/imaccount.py b/digsby/src/imaccount.py new file mode 100644 index 0000000..0e411c8 --- /dev/null +++ b/digsby/src/imaccount.py @@ -0,0 +1,540 @@ +''' +base classes for all IM accounts +''' +# XXX: shouldn't this be in common? +from __future__ import with_statement +from prefs.prefsdata import localprefs +import wx, cPickle +import util +from util.observe import ObservableProperty +from common import AccountBase, StateMixin +from util import try_this +from util.primitives.funcs import Delegate +from common.actions import action +from util.callbacks import callsback +from common import profile + +DEFAULT_JABBER_PRIORITY = 5 +DEFAULT_JABBER_RESOURCE = 'Digsby' + +from logging import getLogger; log = getLogger('imaccount') +info = log.info; warning = log.warning + +from common.protocolmeta import proto_init + +import protocols +class IConnectCallable(protocols.Interface): + protocols.advise(equivalentProtocols = + [protocols.protocolForURI("http://www.dotsyntax.com/protocols/connectcallable")]) + def __call__(callable): + pass + +def get_netcall(*a, **k): + from common import netcall + return netcall + +protocols.declareAdapterForType(IConnectCallable, get_netcall, object) + +class ChatProtocol(object): + def __init__(self): + self.reconnected_callbacks = Delegate() + + def when_reconnect(self, callback): + ''' + Register a callback to be called when this account reconnects. + + It will only be called once--call this method again if you need to be registered + for future reconnects. + ''' + self.reconnected_callbacks += callback + + +class Account(AccountBase, ChatProtocol): + 'Local account object.' + + required = 'name password protocol'.split() + + def __init__(self, name, password=None, **options): + AccountBase.__init__(self, name, password, **options) + ChatProtocol.__init__(self) + + # Make options local to the account + self.update_with_defaults(options) + + # Connection attribute: the Protocol subclass which is this account's active connection + # to the server. + self.connection = None + + self.offline_reason = StateMixin.Reasons.NONE + self.error_acked = True + + self.add_observer(self.offline_changed, 'offline_reason') + + @property + def display_name(self): + return try_this(lambda: getattr(self, self.prefs['account.display_attr']), self.username) + + def offline_changed(self, src, attr, old, new): + + if old == new: + return + + if self.offline_reason != StateMixin.Reasons.NONE: + self.setnotifyif('error_acked', False) + else: + self.setnotifyif('error_acked', True) + + @property + def service(self): + return self.protocol + + @property + def serviceicon(self): + from gui import skin + return skin.get('serviceicons.%s' % self.protocol) + + @property + def statusicon(self): + from gui import skin + from common.Protocol import ProtocolStatus + return skin.get('statusicons.%s'% ('available' if self.state==ProtocolStatus.ONLINE else 'offline')) + + def toggle_connect(self): + self.connect() if not self.connection else self.disconnect() + + def _get_state(self): + return try_this(lambda: self.connection.state, StateMixin.Statuses.OFFLINE) + + def _set_state(self, val): + if self.connection is not None: + self.connection.change_state(val) + + state = property(_get_state, _set_state) + + def connect(self, **connect_args): + # Find the protocol's __init__ method from the import path in ProtocolMeta.py + log.info('Loading protocol for account %r...', self) + connection_class = proto_init(self.protocol) + + # were_connected is a list of Accounts that were connected before the + # status was set to "Offline" + profile_obj = profile() + profile_obj.maybe_return_from_offline() + + self.disconnect() # This will close an old connection and remove it from the reconnect_timers map + + import hub + self.connection = connection_class(self.name, profile_obj.plain_pw(self.password), + hub.get_instance(), + **self.get_options(True)) + self.connection.account = self + + self.connection.add_observer(self.connection_state_changed, 'state', 'offline_reason') + + self.setnotifyif('offline_reason', self.connection.offline_reason) + + # if invisible was not specified, get from the profile's current status + if 'invisible' not in connect_args: + connect_args['invisible'] = profile_obj.status.for_account(self).invisible + + self._connect_args = connect_args + self._reconnect(also_disconnect=False) + + Connect = connect + + def _reconnect(self, also_disconnect=True): + cargs = getattr(self, '_connect_args', {}) + + conn = self.connection + if conn is not None \ + and conn.state == conn.Statuses.ONLINE \ + and conn.offline_reason == conn.Reasons.NONE: + log.info('%r was told to reconnect but is already connected. bailing out.', self) + return + + if also_disconnect and self.connection is not None: + self.disconnect() + + if self.connection is None: + return self.connect(**cargs) + + def go(): + if self.connection is not None: + self.connection.Connect(**cargs) + + self.setnotifyif('state', self.connection.Statuses.CONNECTING) + IConnectCallable(self.connection)(go) + + def connection_state_changed(self, src, attr, old, new): + "Makes a protocol's state trigger a notify in the account." + + log.warning('*'*80) + log.warning('connection_state_changed: %r', src) + + if attr == 'offline_reason': + self._auth_error_msg = getattr(src, '_auth_error_msg', None) + self.setnotifyif(attr, new) + return + + conn = self.connection + new = conn.state if conn is not None else None + + if new == StateMixin.Statuses.ONLINE: + if hasattr(self, '_connect_args'): + del self._connect_args + else: + log.warning('%s had no _connect_args', self) + + status = profile.status + if not status.for_account(conn).invisible or conn.name in ('digsby','gtalk','jabber','fbchat'): #HAX ticket #3880 + # invisible status is handled by Protocol.Connect(invisible = True) + conn._set_status_object(profile.status) + + if profile.icon: + wx.CallAfter(lambda : conn.set_and_size_icon(profile.icon)) + + self.reconnected_callbacks.call_and_clear(conn) + + elif new == StateMixin.Statuses.OFFLINE: + rsn = self.connection.offline_reason + self.connection = None + self.setnotifyif('offline_reason', rsn) + + self.notify(attr, old, new) + + def disconnect(self): + conn = self.connection + if conn is None: + return + + rct_timers = profile.account_manager.reconnect_timers + if self in rct_timers: + timer = rct_timers.pop(self) + log.info('Removed %r from reconnect_timer map', self) + timer.stop() + del timer + + conn.Disconnect() + + def get_is_connected(self): + conn = self.connection + return bool(conn and (conn.state == conn.Statuses.ONLINE or conn.is_connected)) + + is_connected = ObservableProperty(get_is_connected, observe='connection') + connected = property(lambda self: self.is_connected) + + def update_info(self, **options): + "Update this account's information. The server will be notified." + + self.update_with_defaults(options) + + # Tell the server. + from common import profile + profile.update_account(self) + + if self.offline_reason in (StateMixin.Reasons.BAD_PASSWORD, + StateMixin.Reasons.NO_MAILBOX): + self.connect(**getattr(self, '_connect_args', {})) + + def update_with_defaults(self, newoptions): + try: + # this is a new account + protocol = newoptions['protocol'] + except KeyError: + # this is an old account + protocol = self.protocol + + options = self.protocol_info(protocol).defaults.copy() + + for key, value in newoptions.iteritems(): + if key == 'server': + # This tuple is a source of endless pointless trouble + port = value[1] if value[1] != '' else options[key][1] + port = min(65535, port) + + options[key] = (value[0] if value[0] != '' else options[key][0], port) + elif key == 'resource' or key == 'priority' and protocol == 'jabber': + options[key] = value + elif value != '' and value != ('',''): + options[key] = value + + res = options.pop('resource', sentinel) + priority = options.pop('priority', sentinel) + + for k, v in options.iteritems(): + try: + setattr(self, k, v) + except Exception, e: + log.error('Error setting %r.%r to %r: %r', self, k, v, e) + + #storage is dependant on username + protocol, so do this last + if res is not sentinel: + self.resource = res + if priority is not sentinel: + self.priority = priority + + self.notify() + + def _localpref_key(self, key): + return '/'.join([self.protocol, self.username, key]).lower() + + def set_enabled(self, enabled, notify = True): + old = getattr(self, 'autologin', False) + self.autologin = enabled + if notify: + self.notify('enabled', old, enabled) + profile.update_account(self) + return + + def get_enabled(self): + return getattr(self, 'autologin', False) + + enabled = property(get_enabled, set_enabled) + + def enable(self): + log.info("enable: %r", self) + if not self.enabled: + self.enabled = True + elif self.connection is None: + self.connect() + + def disable(self): + log.info("disable: %r", self) + if self.enabled: + self.enabled = False + elif self.connection is not None: + self.disconnect() + + def set_resource(self, res): + if self.protocol == 'jabber': + key = self._localpref_key('resource') + if res and res != DEFAULT_JABBER_RESOURCE: + localprefs()[key] = res + elif key in localprefs(): + del localprefs()[key] + else: + self._resource = res + + def get_resource(self): + if self.protocol == 'jabber': + key = self._localpref_key('resource') + if key in localprefs(): + return localprefs()[key] + else: + return DEFAULT_JABBER_RESOURCE + else: + return self._resource + + resource = property(get_resource, set_resource) + + def set_priority(self, priority): + try: + priority = int(priority) + except ValueError: + priority = DEFAULT_JABBER_PRIORITY + if self.protocol == 'jabber': + key = self._localpref_key('priority') + if priority != DEFAULT_JABBER_PRIORITY: + localprefs()[key] = priority + elif key in localprefs(): + del localprefs()[key] + else: + self._priority = priority + + def get_priority(self): + if self.protocol == 'jabber': + key = self._localpref_key('priority') + if key in localprefs(): + return int(localprefs()[key]) + else: + return 5 + else: + return self._priority + + priority = property(get_priority, set_priority) + + def get_options(self, include_defaults = False): + ''' + Returns options which are unique to this account. (Defaults removed.) + ''' + + # Copy the dictionary + options = self.__dict__.copy() + pop = options.pop + + + defaults = self.protocol_info().defaults + + #if it's not in defaults, it'll get cleared out below anyway. + try: + options['priority'] = self.priority + except AttributeError: + pass + + #if it's not in defaults, it'll get cleared out below anyway. + try: + options['resource'] = self.resource + except AttributeError: + pass + + # Remove unecessary attributes + for attr in (set(options.keys()) - set(defaults.keys())): + pop(attr, None) + + if include_defaults and self.protocol == 'jabber': + if self.priority != DEFAULT_JABBER_PRIORITY: + options['priority'] = self.priority + if include_defaults and self.protocol == 'jabber': + if self.resource != DEFAULT_JABBER_RESOURCE: + options['resource'] = self.resource + + # If options are default, don't send to the server. + if not include_defaults: + for k, v in list(options.iteritems()): + if k in defaults and defaults[k] == v: + pop(k) + + if include_defaults: + getattr(self, 'add_options_%s' % self.protocol, lambda *a: None)(options) + + util.dictrecurse(dict)(options) + + return options + + def add_options_jabber(self, options): + + # Common options + options.update(dict(sasl_md5 = True, use_md5 = True)) + + # The encryption radio + jabber_encryption_options = { + 0: dict(do_tls=True ), # Use TLS if Possible + 1: dict(do_tls=True, require_tls = True), #Require TLS + 2: dict(do_ssl=True), #Force SSL + 3: dict(do_tls=False), #No Encrpytion + } + options.update(jabber_encryption_options[options.pop('encryption')]) + + # Authentication + if options.pop('allow_plaintext'): + options.update(dict(sasl_plain = True, + plain = True)) + + ignore = options.pop('ignore_ssl_warnings') + options['verify_tls_peer'] = not ignore + + def delete_from_server(self, password, on_success, on_fail): + import jabber + if not issubclass(self.protocol_class(), jabber.protocol): + return log.warning('cannot delete from server: %r is not a jabber subclass', self.protocol_class()) + + if password != profile.plain_pw(self.password): + log.error("password didn't match: %r %r", password, profile.plain_pw(self.password)) + return on_fail() + if self.connection is None: + log.error('connection was None') + return on_fail() + + self.connection.delete_account(on_success=on_success, on_fail=on_fail) + + @action(lambda self: True if hasattr(self.connection, 'change_password') else None, + needs = '@gui.protocols.jabbergui.PasswordChangeDialog') + @callsback + def change_password(self, password, callback=None): + self.connection.change_password(password, success= lambda s: + self.change_acct_password(password, + callback=callback), + error=lambda s: + wx.MessageBox("Failed to change password", + "Failed to change password")) + + @callsback + def change_acct_password(self, password, callback=None): + self.password = profile.crypt_pw(str(password)) + profile.update_account(self) + callback.success() + + def __call__(self): return self.toggle_connect() + + @classmethod + def from_net(cls, acct): + if acct.id not in cls._ids: + cls._ids.insert(acct.id, acct.id) + try: + opts = cPickle.loads(acct.data) + except: + opts = {} + return cls(name = acct.username, + password = acct.password, + protocol = acct.protocol, + id=acct.id, + **opts) + + def update(self): + profile.update_account(self) + + def setup_linkrefs(self): + reason = StateMixin.Reasons + state = StateMixin.Statuses + connect, disconnect = lambda *a: self.Connect(), lambda *a: self.disconnect() + + self.linkrefs = { + (state.OFFLINE, reason.SERVER_ERROR) : (_('Connect'), connect), + (state.OFFLINE, reason.BAD_PASSWORD) : (_('Edit Account'), lambda *a: profile.account_manager.edit(self,True)), + (state.OFFLINE, reason.CONN_FAIL) : (_('Retry'), connect), + (state.OFFLINE, reason.OTHER_USER) : (_('Reconnect'), connect), + (state.OFFLINE, reason.CONN_LOST) : (_('Reconnect'), connect), + (state.OFFLINE, reason.RATE_LIMIT) : (_('Reconnect'), connect), + (state.OFFLINE, reason.WILL_RECONNECT) : (_('Cancel'), lambda *a: profile.account_manager.cancel_reconnect(self)), + (state.OFFLINE, reason.NONE) : (_('Connect'), connect), + (state.ONLINE, reason.NONE) : (_('Disconnect'), disconnect), + (state.CONNECTING, reason.NONE) : (_('Cancel'), disconnect), + (state.AUTHENTICATING, reason.NONE) : (_('Cancel'), disconnect), + (state.LOADING_CONTACT_LIST, reason.NONE) : (_('Cancel'), disconnect), + } + + return self.linkrefs + + def get_link(self): + reason = StateMixin.Reasons + state = StateMixin.Statuses + + try: + linkrefs = self.linkrefs + except AttributeError: + linkrefs = self.setup_linkrefs() + + try: + return linkrefs[(self.state, self.offline_reason)] + except KeyError: + log.critical('(%r,%r) not in linkref dictionary', self.state, self.offline_reason) + if self.state == state.ONLINE: + return linkrefs[(state.ONLINE, reason.NONE)] + else: + return '','' + + def get_error_txt(self): + return getattr(self, '_error_txt', False) or getattr(getattr(self, 'connection', None), + 'error_txt', False) + + def set_error_txt(self, value): + self._error_txt = value + + def del_error_txt(self): + self._error_txt = False + try: + del self.connection.error_txt + except Exception: + pass + + error_txt = property(get_error_txt, set_error_txt, del_error_txt) + + @property + def allow_contact_add(self): + if self.protocol_info().get('allow_contact_add', True): + return True + else: + if profile.prefs.get('%s.allow_add' % self.protocol, False): + return True + else: + return False diff --git a/digsby/src/imagehost/__init__.py b/digsby/src/imagehost/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/imagehost/imgur/__init__.py b/digsby/src/imagehost/imgur/__init__.py new file mode 100644 index 0000000..3d5f0f1 --- /dev/null +++ b/digsby/src/imagehost/imgur/__init__.py @@ -0,0 +1 @@ +from imgur import * diff --git a/digsby/src/imagehost/imgur/imgur.py b/digsby/src/imagehost/imgur/imgur.py new file mode 100644 index 0000000..9b3185c --- /dev/null +++ b/digsby/src/imagehost/imgur/imgur.py @@ -0,0 +1,64 @@ +from contextlib import closing +import urllib2 + +import util +import util.net as net +import util.apitools as apitools +import util.callbacks as callbacks + +class ImgurApi(object): + # OPENSOURCE: api key? + api_key = '086876da7b51c619ffddb16eaca51400' + BASE = 'http://imgur.com/' + + @property + def API_BASE(self): + return net.httpjoin(self.BASE, 'api/') + + @apitools.apicall('data', name = 'upload', format = 'json') + def upload(self, image): + pass + + def get_response_handler(self, method_call, callback): + if method_call.spec.format == 'json': + def response_parser(data): + import simplejson as json + return {'url' : json.loads(data)['rsp']['image']['imgur_page']} + else: + def response_parser(data): + import lxml.etree as ET + doc = ET.fromstring(data) + + def keyval(node): + if node.text and node.text.strip(): + return (node.tag, node.text.strip()) + elif node.getchildren(): + return (node.tag, dict(map(keyval, node.getchildren()))) + elif node.attrib: + return (node.tag, dict(node.attrib)) + + print data + res = dict((keyval(doc),)).get('rsp', {}) + print res + return res + + def response_handler(resp): + try: + with closing(resp): + data = resp.read() + api_response = response_parser(data) + except Exception as e: + return callback.error(e) + else: + callback.success(api_response) + + return response_handler + + @callbacks.callsback + def send_method(self, call, callback = None): + url = net.UrlQuery(call.get_endpoint(self.API_BASE) + '.' + call.spec.format) + params = dict((name, arg.value) for name, arg in call.bound_args.items()) + params['key'] = self.api_key + req = urllib2.Request(url, params) + + util.threaded(urllib2.urlopen)(req, callback = callback) diff --git a/digsby/src/imagehost/trim.py b/digsby/src/imagehost/trim.py new file mode 100644 index 0000000..10d3aa6 --- /dev/null +++ b/digsby/src/imagehost/trim.py @@ -0,0 +1,82 @@ +from contextlib import closing +import urllib2 + +import util +import util.net as net +import util.apitools as apitools +import util.callbacks as callbacks + +class TrimApi(object): + # OPENSOURCE: api key? + api_key = 'R36NaKwgl6sSnbUePimqMZNC25hTeD2QZ6EEdf7u7zddq6kD' + BASE = 'http://api.tr.im/' + + @property + def API_BASE(self): + return net.httpjoin(self.BASE, 'api/') + + @apitools.apicall('data', name = 'picim_url.json', format = 'json') + def picim_url(self, media): + pass + + def get_response_handler(self, method_call, callback): + if method_call.spec.format == 'json': + def response_parser(data): + import simplejson as json + return json.loads(data) + else: + def response_parser(data): + import lxml.etree as ET + doc = ET.fromstring(data) + + def keyval(node): + if node.text and node.text.strip(): + return (node.tag, node.text.strip()) + elif node.getchildren(): + return (node.tag, dict(map(keyval, node.getchildren()))) + elif node.attrib: + return (node.tag, dict(node.attrib)) + + print data + res = dict((keyval(doc),)).get('trim', {}) + print res + return res + + def response_handler(resp): + with closing(resp): + data = resp.read() + + api_response = response_parser(data) + + callback.success(api_response) + + return response_handler + + @callbacks.callsback + def send_method(self, call, callback = None): + url = net.UrlQuery(call.get_endpoint(self.API_BASE), api_key = self.api_key) + req = urllib2.Request(url, + dict((name, arg.value) for name, arg in call.bound_args.items()), + ) + + util.threaded(urllib2.urlopen)(req, callback = callback) + +def main(): + tp = util.threadpool.ThreadPool(5) + api = TrimApi() + media = urllib2.urlopen('http://antwrp.gsfc.nasa.gov/apod/image/0904/aprMoon_tanga.jpg').read() + + def success(resp): + print 'success' + print resp + + def error(err): + print 'ohnoes' + print err + + api.picim_url(media, success = success, error = error) + tp.joinAll() + +if __name__ == '__main__': + import digsbysite + main() diff --git a/digsby/src/imagehost/twitpic/__init__.py b/digsby/src/imagehost/twitpic/__init__.py new file mode 100644 index 0000000..97bdb1d --- /dev/null +++ b/digsby/src/imagehost/twitpic/__init__.py @@ -0,0 +1 @@ +from twitpic import * \ No newline at end of file diff --git a/digsby/src/imagehost/twitpic/twitpic.py b/digsby/src/imagehost/twitpic/twitpic.py new file mode 100644 index 0000000..dff5ac2 --- /dev/null +++ b/digsby/src/imagehost/twitpic/twitpic.py @@ -0,0 +1,90 @@ +if __name__ == '__main__': + import sys + sys.path.insert(0, '.') + sys.modules['__builtin__']._ = lambda s: s + import Digsby + +import urllib2 +import util +import util.net as net +import util.apitools as apitools +import util.callbacks as callbacks + +from contextlib import closing + +class TwitPicAPI(object): + BASE = 'http://twitpic.com/' + + @property + def API_BASE(self): + return net.httpjoin(self.BASE, 'api/') + + class TwitPicError(Exception): + pass + + def get_response_handler(self, method_call, callback): + def response_handler(resp): + with closing(resp): + data = resp.read() + import lxml.etree as ET + doc = ET.fromstring(data) + status = doc.get('stat', doc.get('status')) + if status == 'ok': + result = dict((node.tag, node.text) for node in doc.getchildren()) + callback.success(dict((node.tag, node.text) for node in doc.getchildren())) + + else: + errnode = doc.find('./err') + + ecode = emsg = None + if errnode is not None: + ecode = errnode.get('code') + emsg = errnode.get('msg') + callback.error(self.TwitPicError(ecode, emsg)) + return response_handler + + @callbacks.callsback + def send_method(self, call, callback = None): + req = urllib2.Request(call.get_endpoint(self.API_BASE), + dict((name, arg.value) for name, arg in call.bound_args.items()), + ) + + util.threaded(urllib2.urlopen)(req, callback = callback) + + @apitools.apicall('image-noenc', 'data', 'data') + def upload(self, media, username, password): + pass + + @apitools.apicall( 'image-noenc', 'data', 'data', 'data') + def uploadAndPost(self, media, username, password, message = None): + pass + +class YFrogAPI(TwitPicAPI): + ''' + YFrog.com is literally the same API as twitpic. yay! + ''' + BASE = 'http://yfrog.com/' + +def main(): + tp = util.threadpool.ThreadPool(5) + api = TwitPicAPI() + username = u'blahdyblah\u2665' + password = u'unicodepassword\u2665' + username = u'digsby01' + media = urllib2.urlopen('http://antwrp.gsfc.nasa.gov/apod/image/0904/aprMoon_tanga.jpg').read() + message = u'testing image host thingy' + + def success(resp): + print 'success' + print resp + + def error(err): + print 'ohnoes' + print err + + api.upload(media, username, password, success = success, error = error) + tp.joinAll() + +if __name__ == '__main__': + import digsbysite + main() diff --git a/digsby/src/jabber/JabberBuddies.py b/digsby/src/jabber/JabberBuddies.py new file mode 100644 index 0000000..4556286 --- /dev/null +++ b/digsby/src/jabber/JabberBuddies.py @@ -0,0 +1,39 @@ +from jabber import JID +from util.observe import observable_dict +from traceback import print_exc + +import jabber +import logging +log = logging.getLogger('jabber.buddies') + +class JabberBuddies(observable_dict): + def __init__(self, protocol): + observable_dict.__init__(self) + self.protocol = protocol + + def __getitem__(self, jid): + unique = JID(jid).bare() + try: + return dict.__getitem__(self, unique) + except KeyError: + return self.setdefault(unique, self.protocol.buddy_class(self.protocol, jid)) + + def update_presence(self, presence_stanza): + jid = presence_stanza.get_from() + log.debug('update_presence for %r', jid) + + try: + buddy = self[jid] + except Exception, e: + log.warning('update_presence, "buddy = self[jid]": %r', e) + print_exc() + return False + + try: + buddy.update_presence(presence_stanza) + except Exception, e: + log.warning('update_presence, "buddy.update_presence(presence_stanza)": %r' %e ) + print_exc() + return False + + return True diff --git a/digsby/src/jabber/JabberBuddy.py b/digsby/src/jabber/JabberBuddy.py new file mode 100644 index 0000000..dec73b0 --- /dev/null +++ b/digsby/src/jabber/JabberBuddy.py @@ -0,0 +1,553 @@ +from peak.util.imports import lazyModule +import common, hashlib +import jabber +from jabber import JID, Presence +from util import callsback, Storage as S, odict, threaded +from util.cacheable import urlcacheopen, cproperty +from common.actions import action #@UnresolvedImport +from jabber.objects.nick import NICK_NS, Nick +from common import pref +skin = lazyModule('gui.skin') +from urllib import quote + +# if you make a string class...make it act like a string, damnit. +from pyxmpp.jabber.vcard import VCardString +VCardString.decode = lambda s, *a: s.value.decode(*a) +from jabber import VCard +from util.observe import ObservableProperty as oproperty + +import logging; log = logging.getLogger('jabber.buddy') + +GTALK = 'gtalk' +GTALK_MOBILE_DOMAINS = ('sms.talk.google.com',) +GTALK_DOMAINS = ('gmail.com', 'googlemail.com',) + GTALK_MOBILE_DOMAINS +JABBER = 'jabber' + +def isGTalk(resource): + if not isinstance(resource, basestring): + return False + reslower = resource.lower() + return any(reslower.startswith(x) for x in + ('talk.', 'gmail.', 'talkgadget', 'ics')) + +no_widget = lambda self: None if getattr(self, 'iswidget', False) else True + +# vcard lookup keys +ADDRESS_KEYS = 'street,extadr,locality,region,pcode,ctry'.split(',') +WORK_KEYS = 'company department postition role'.split() + +SUBSCRIPTION_TO = ('to', 'both') +SUBSCRIPTION_FROM = ('from', 'both') + +class JabberBuddy(common.buddy): + resource_class = jabber.resource + def __init__(self, jabber_, jid, rosteritem=None): + self.jid = JID(jid).bare() + self._set_username() + + self.resources = {} + self.subscription = rosteritem.subscription if rosteritem else None + self.rostername = rosteritem.name if rosteritem else None + if rosteritem: + self.notify('rostername') + self.notify('remote_alias') + self.ask = rosteritem.ask if rosteritem else None + self.groups = rosteritem.groups if rosteritem else [] + self.profile = False + common.buddy.__init__(self, self.username, jabber_) + self._gen_pretty_profile() + + self.pending_adds = set() + + def _set_username(self): + uname = JID(self.jid).bare().as_unicode() + self.username = uname + + def __call__(self, item): + """ + send a new roster item to the buddy + updates relevant data (subscription/ask status, groups) + """ + + self.subscription = item.subscription + self.ask = item.ask + self.rostername = item.name + self.groups = item.groups + self.check_subscription() + +# log.info('%r info updated:', self) +# log.info(' subscription: %s', self.subscription) +# log.info(' ask: %s', self.ask) +# log.info(' groups: %s', self.groups) + self.notify('rostername') + self.notify('remote_alias') + + return self + + def check_subscription(self): + if self.subscription not in SUBSCRIPTION_TO and self.resources and \ + self is not getattr(self.protocol, 'self_buddy', sentinel): + self.resources.clear() + self.notify() + + @property + def service(self): + if self.jid.domain in GTALK_DOMAINS: + return GTALK + else: + return JABBER + + @property + def serviceicon(self): + #show gtalk, but service will still be jabber (for non gmail gtalk users) + service = self.service + if service is GTALK or self.protocol.service == 'gtalk' \ + or any(isGTalk(j.resource) for j in self.resources): + service = GTALK + return skin.get('serviceicons.' + service) + + @property + def id(self): return self.jid + + def equals_chat_buddy(self, chat_buddy): + if hasattr(chat_buddy, 'user'): + user = chat_buddy.user + if user.real_jid is not None: + return user.real_jid.bare() == self.jid.bare() + + return common.buddy.equals_chat_buddy(self, chat_buddy) + + @property + def away(self): + if self.away_is_idle: + return self.resources and not self.idle and not any(r.available + for r in self.resources.itervalues()) + else: + return self.resources and all(r.away + for r in self.resources.itervalues()) + + @property + def mobile(self): + return (self.jid.domain in GTALK_MOBILE_DOMAINS or + (self.resources and + all(r.mobile for r in self.resources.itervalues()))) + + @property + def blocked(self): return False + + @property + def online(self): + #HAX: Nothing else seemed to work, so now checks if the protocol is connected + return bool(self.resources) and self.protocol.is_connected + + @property + def away_is_idle(self): + return self.service == GTALK + + @property + def idle(self): + if self.away_is_idle: + return self.resources and all(r.idle + for r in self.resources.itervalues()) + else: + return False + + def get_caps(self): + caps = common.buddy.get_caps(self) + + for r in self.resources.values(): + res = r.jid.resource + if res and not isGTalk(res): + #TalkGadget is the flash client and ICS is for the blackberry version + break + else: + caps.discard(common.caps.FILES) + + return caps + + caps = property(get_caps) + + def _gen_pretty_profile(self): + profile = odict() + ok = False + + fullname = self.vcard_get('fn') + if fullname: + profile[_('Full Name:')]=fullname + ok=True + + mapping = {'birthday':('bday',), + 'email_address':('email', 'address'), + 'phone':('tel', 'number'), + 'company':('org', 'name'), + 'department':('org', 'unit'), + 'postition':('title',), + 'role':('role',),} + + keylabels = {'birthday' : _('Birthday:'), 'email_address' : _('Email Address:'), 'phone' : _('Phone:')} + for key in keylabels: + val = self.vcard_get(*mapping[key]) + if val is not None: + profile[keylabels[key]]=val + ok=True + + homepage = self.vcard_get('url') + if homepage: + profile[_('Website')]=(homepage,homepage) + ok=True + + about = self.vcard_get('desc') + if about: + if ok: + profile['sep1']=4 + profile[_('Additional Information:')]=''.join(['\n',about]) + + def prettyaddy(addict): + addy = [] + add = lambda s: addy.insert(0, s) + + + mstr = _('{street} {extra_adress} {locality} {region} {postal_code} {country}').format(street = addict['street'], + extra_address = addict['extadr'], + locality = addict['locality'], + region = addict['region'], + postal_code = addict['pcode'], + country = addict['ctry']) + if isinstance(mstr, unicode): + mstr = mstr.encode('utf-8') + murl='http://maps.google.com/maps?q=' + quote(mstr) + + if addict.ctry: add('\n'+addict.ctry) + + if addict.pcode: add(addict.pcode) + + if addict.region: + if addict.pcode: + add(' ') + add(addict.region) + + if addict.locality: + if (addict.region or addict.pcode): + add(', ') + add(addict.locality) + + if addict.extadr: + if any((addict.locality, addict.region, addict.pcode)): + add('\n') + add(addict.extadr) + + if addict.street: + if any((addict.locality, addict.region, addict.pcode, addict.extadr)): + add('\n') + add(addict.street) + + if addy: add(['(',(murl, 'map'),')\n']) + + return addy + + #horrible + address = self.vcard_get('adr') + if address is not None: + if ok: + profile['sep2']=4 + + ladr = [(key,self.vcard_get('adr', key)) for key in ADDRESS_KEYS] + if any(v for k,v in ladr): + profile[_('Home Address:')]=prettyaddy(S(ladr)) + + if ok: profile['sep3']=4 + + for key in WORK_KEYS: + val = self.vcard_get(*mapping[key]) + profile[key.title()+':']=val + + self.last_pretty_profile = profile + return profile + + @property + def pretty_profile(self): + profile = getattr(self, 'last_pretty_profile', lambda: self._gen_pretty_profile()) + + for key in profile.keys(): + if not key.startswith('sep') and profile[key]: + break + else: + return None + + return profile + + def get_status_message(self): + try: + res = self.get_highest_priority_resource() + if res : return res.status_message or '' + else : return '' + except Exception, e: + log.warning('status_message(self) on %r: %r', self, e) + raise + + def set_status_message(self, value): + r = self.get_highest_priority_resource() + if r: + old = r.status_message + r.status_message = value + self.notify('status_message', old, value) + + status_message = property(get_status_message, set_status_message) + + @property + def status(self): + if self.mobile: + return 'mobile' + elif self.subscription not in SUBSCRIPTION_TO and \ + self is not getattr(self.protocol, 'self_buddy', sentinel): + return 'unknown' + elif not getattr(self, 'resources', []): + return "offline" + elif self.idle: + return 'idle' + elif not self.away: + return "available" + else: + return "away" + + def update_presence(self, presence, notify=True, buddy=None): +# presence = dupe_presence(presence) + buddy = presence.get_from() or buddy + presence_type = presence.get_type() + + if not presence_type or presence_type == "available": + old_length = len(self.resources) + + self.resources[buddy] = self.resource_class(self.protocol, buddy, presence) + + children = presence.xmlnode.get_children() + if children is not None: + photos = [c for c in children if c.name == "photo"] + if photos: self.icon_hash = photos[0].getContent() + + nicks = jabber.jabber_util.xpath_eval(presence.xmlnode, + 'n:nick',{'n':NICK_NS}) +# nicks.extend(jabber.jabber_util.xpath_eval(presence.xmlnode, +# 'ns:nick')) + if nicks: + self.nick = Nick(nicks[0]).nick + self.notify('nick') + self.notify('remote_alias') + + self._presence_updated(presence) + + elif presence_type == "unavailable": + if buddy in self.resources: + del self.resources[buddy] + if notify: + self.notify('status') + + def _presence_updated(self, presence): + if self.jid.domain not in pref('digsby.guest.domains', ['guest.digsby.org']): + # don't retreive VCard/Icons from guests. + self.protocol.get_buddy_icon(self.username) + + def get_highest_priority_resource(self): + return self[0] if getattr(self, 'resources',[]) else None + +# retval = None +# for res in self.resources.itervalues(): +# if not retval or (res.presence.get_priority() > retval.presence.get_priority()): +# retval = res +# return retval + + def set_vcard(self, stanza): + log.info('incoming vcard for %s', self) + self._vcard_incoming = False + + q = stanza.get_query() + if not q: return + try: + vc = self.vcard = jabber.VCard(q) + except ValueError: + pass + else: + self._incoming_icon(vc.photo) + self._gen_pretty_profile() + self.notify('vcard') + self.notify('remote_alias') + + def set_vc(self, vcard): + self._vcard = vcard + + def get_vc(self): return self._vcard + + vcard = property(get_vc, set_vc) + + #this does a good enough job at the moment, + #and means we only need to cache one thing. It is not perfect, + #the old way was worse + _vcard = cproperty(None, (lambda o: o.rfc2426() if o is not None else None), + (lambda o: VCard(o) if o is not None else None)) + + def vcard_get(self, *stuffs): + if self.vcard: + stuff = stuffs[0]; stuffs = stuffs[1:] + thing = getattr(self.vcard, stuff, None) + if isinstance(thing, list) and thing: + thing = thing[0] + while stuffs: + thing = getattr(thing, stuffs[0]) + stuffs = stuffs[1:] + elif thing: + if stuffs: + log.warning_s('%r', stuffs) + log.warning_s(self.vcard.as_xml()) + assert not stuffs + return unicode(thing) if thing else thing + + nick = cproperty(None) + + def _incoming_icon(self, photos): + 'Handles the "photos" section of a VCard.' + + if photos and photos[0].image: + img = photos[0].image + self._update_photo_image(img) + elif photos and photos[0].uri: + def success(result): + _response, content = result + #buddy.icon_data = content + self._update_photo_image(content) + meth = threaded(urlcacheopen) + meth.verbose = False + meth(photos[0].uri, success=success) + + + def _update_photo_image(self, data): + hash = hashlib.sha1(data).hexdigest() + self.cache_icon(data, hash) + self.icon_hash = hash + + def get_remote_alias(self): + for attr in ('rostername','nick',): + nick = getattr(self, attr, None) + if nick: return unicode(nick) + for attr in ('nickname', 'fn'): + nick = self.vcard_get(attr) + if nick: return unicode(nick) + return None + + remote_alias = oproperty(get_remote_alias, observe=['vcard', 'rostername', 'nick']) + + @action(needs = ((unicode, "Group name"),)) + @callsback + def add_to_group(self, groupname, callback = None): + log.info('%s add_to_group %s', self, groupname) + pending = self.pending_adds + + # Prevent excessive add requests. + if groupname in pending: + log.info('ignoring request.') + else: + pending.add(groupname) + + item = self.protocol.roster.get_item_by_jid(self.id).clone() + + if groupname not in item.groups: + item.groups.append(groupname) + query = item.make_roster_push() + + def onsuccess(_s): + pending.discard(groupname) + callback.success() + + def onerror(_s = None): + pending.discard(groupname) + log.warning("error adding %r to %s", self.id, groupname) + + self.protocol.send_cb(query, success = onsuccess, error = onerror, timeout = onerror) + + @action() + @callsback + def remove(self, callback=None): + try: + item = self.protocol.roster.get_item_by_jid(self.id).clone() + except KeyError: + return + else: + item.subscription = 'remove' + self.protocol.send_cb(item.make_roster_push(), callback=callback) + + @action(lambda self: None if no_widget(self) is None else + (None if self.subscription in SUBSCRIPTION_FROM else True)) + def subscribed(self): + 'Send Authorization' + self.protocol.send_presence(Presence(to_jid=self.id, + stanza_type='subscribed')) + + @action(lambda self: None if no_widget(self) is None else + (None if self.subscription not in SUBSCRIPTION_FROM else True)) + def unsubscribed(self): + 'Send Removal of Authorization' + self.protocol.send_presence(Presence(to_jid=self.id, + stanza_type='unsubscribed')) + + @action(lambda self: None if no_widget(self) is None else + (None if self.subscription in SUBSCRIPTION_TO else True)) + def subscribe(self): + 'Re-send Subscription Request' + self.protocol.send_presence(Presence(to_jid=self.id, + stanza_type='subscribe')) + + @action(lambda self: None if no_widget(self) is None else + (None if self.subscription not in SUBSCRIPTION_TO else True)) + def unsubscribe(self): + 'Send Unsubscription Request' + self.protocol.send_presence(Presence(to_jid=self.id, + stanza_type='unsubscribe')) + + + def appear_offline_to(self): + 'Sends an offline presence stanza to this buddy.' + + self.protocol.send_presence(Presence(stanza_type = 'unavailable', + status = 'Logged Out', + to_jid = self.id)) + + @action() + def expand(self): + print "expand %r" % self + + def sorted_resources(self): + return sorted(self.resources.itervalues(), + key=lambda r: (r.priority, r.jid), reverse=True) + + def __iter__(self): + return iter(self.sorted_resources()) + + def __len__(self): + return len(self.resources) + + def __getitem__(self, index): + return self.sorted_resources()[index] + + def __repr__(self): + try: + res = len(self.resources) + except Exception: + res = 0 + try: + n = self.name + except Exception: + n = '' + return '<%s %r %d>' % (type(self).__name__, n, res) + +def dupe_presence(p): + ''' + duplicates a presence object + + @param p: pyxmpp.presence.Presence object + ''' + assert False #gettting rid of this function + #using the node directly makes + # libxml2.xmlNode.docCopyNode(libxml2.newDoc("1.0"),1) + # happen deep inside pyxmpp.stanza, resulting what seems to be a perfect + # and useable copy. p.copy() or Presence(p) does not provide a useable copy + return Presence(p.get_node()) + diff --git a/digsby/src/jabber/JabberChat.py b/digsby/src/jabber/JabberChat.py new file mode 100644 index 0000000..81d02b6 --- /dev/null +++ b/digsby/src/jabber/JabberChat.py @@ -0,0 +1,344 @@ +''' +Jabber multi-user chat (MUC) +''' + +import pyxmpp.jabber.muc +from pyxmpp.message import Message +from pyxmpp.all import JID +from common import Conversation, caps +from util.observe import Observable +from util import callsback +from pprint import pformat + +from logging import getLogger; log = getLogger('JabberChat') +import traceback +import jabber +from pyxmpp import presence +from .objects.nick import NICK_NS +from .objects.nick import Nick + +class user_prop(object): + def __init__(self, name): + self.attr = name + + def __get__(self, obj, objtype=None): + return getattr(obj.user, self.attr) + +class JabberChatBuddy(Observable): + def __init__(self, mucRoomUser, room): + Observable.__init__(self) + + self.user = mucRoomUser + self.room = room + + @property + def info_key(self): + return self.room.name + '/' + self.name + '_' + self.service + + @property + def jid(self): + return self.user.room_jid + + def __getattr__(self, val, default = sentinel): + if val == 'user': + return object.__getattribute__(self, 'user') + try: + return getattr(object.__getattribute__(self, 'user'), val) + except AttributeError: + if default is sentinel: + return object.__getattribute__(self, val) + else: + try: + return object.__getattribute__(self, val) + except AttributeError: + return default + + name = property(lambda self: self.user.nick) + + @property + def service(self): return self.room.protocol.name + + @property + def protocol(self): return self.room.protocol + + def idstr(self): + return u'/'.join([self.service, self.room.name, self.name]) + + def private_message_buddy(self): + return self.protocol.get_buddy(self.user.real_jid or self.user.room_jid) + + def private_message_buddy_attr(self, attr): + b = self.private_message_buddy() + if b is not None: + return getattr(b, attr) + + @property + def alias(self): + if self.user is self.room.room_state.me: + try: + return self.room.protocol.self_buddy.alias + except Exception: + return 'Me' + for attr in ('nickname',): + nick = getattr(self, attr, None) + if nick: return unicode(nick) + try: + return self.room.protocol.buddies[self.user.real_jid].alias + except Exception: + pass + return self.user.nick + + def __repr__(self): + user = self.user + return '' % \ + (user.room_jid, user.role, user.affiliation, user.real_jid or '?') + + # + # TODO: find a way to keep this boilerplate out of individual buddy classes. + # + def increase_log_size(self, num_bytes): + pass + + @property + def icon(self): + return self.private_message_buddy_attr('icon') + + @property + def icon_path(self): + return self.private_message_buddy_attr('icon_path') + + @property + def buddy_icon(self): + ''' + Returns a 32 pixel version of this buddy's icon (or the generic + replacement icon for buddies without icons). + ''' + + from gui.buddylist.renderers import get_buddy_icon + return get_buddy_icon(self, 32, False) + + history = property(lambda self: iter([])) + status_orb = 'available'#user_prop('status_orb') + online = True + @property + def serviceicon(self): + from gui import skin + return skin.get('serviceicons.' + self.service) + + caps = [caps.IM] + blocked = False + sms = False + + +class JabberChat(pyxmpp.jabber.muc.MucRoomHandler, Conversation): + + ischat = True + contact_identities_known = False + + def __init__(self, protocol, jid, callback): + pyxmpp.jabber.muc.MucRoomHandler.__init__(self) + Conversation.__init__(self, protocol) + + self.callback = callback + self.jid = jid + self.protocol.conversations[jid.as_unicode()] = self + + self.buddies = {} + + @property + def chat_room_name(self): + return self.jid.as_unicode() + + @property + def name(self): + name = self.room_state.room_jid.bare() + subject = self.room_state.subject + + if subject: + return u'%s - %s' % (name, subject) + else: + return unicode(name) + + # + # callbacks invoked by pyxmpp informing us of room changes + # + + def user_joined(self, user, stanza): + 'Called when a new participant joins the room.' + + bud = self._buddy(user) + self.room_list.append(bud) + + if user is self.room_state.me and self.callback is not None: + self.callback, cb = None, self.callback + cb.success(self) + + self._log_presence(bud, 'joined') + Conversation.buddy_join(self, bud) + + def user_left(self,user,stanza): + 'Called when a participant leaves the room.' + + bud = self.buddies[user.nick] + try: + self.room_list.remove(bud) + except ValueError: + pass + + self._log_presence(bud, 'left') + Conversation.buddy_leave(self, bud) + + def _log_presence(self, buddy, action): + try: + roomlist = pformat(list(self.room_list)) + except UnicodeError: + try: + roomlist = repr(self.room_list) + except Exception: + roomlist = '?' + + log.info('user %r %s:\n%s', buddy, action, roomlist) + + def nick_changed(self, user, old_nick, stanza): + 'Called after a user nick has been changed.' + + b = self._buddy(user) + b.notify('name', old_nick, b.name) # b.name is a property -> user.nick + + def presence_changed(self,user,stanza): + b = self._buddy(user) + nicks = jabber.jabber_util.xpath_eval(stanza.xmlnode, + 'n:nick',{'n':NICK_NS}) +# nicks.extend(jabber.jabber_util.xpath_eval(presence.xmlnode, +# 'ns:nick')) + if nicks: + b.nickname = Nick(nicks[0]).nick + + def affiliation_changed(self, user, old_aff, new_aff, stanza): + 'Called when a affiliation of an user has been changed.' + + self._buddy(user).notify('affiliation', old_aff, new_aff) + + def role_changed(self, user, old_role, new_role, stanza): + 'Called when a role of an user has been changed.' + + self._buddy(user).notify('role', old_role, new_role) + + def subject_changed(self, user, stanza): + self.notify('name', None, self.room_state.subject) + + + # + # messaging + # + + def message_received(self, user, stanza): + if not user: + return + body = stanza.get_body() + if body is not None: + self.incoming_message(self._buddy(user), body) + + def incoming_message(self, buddy, message): + if buddy.user == self.room_state.me: # own messages already echoed + return + + self.typing_status[buddy] = None + self.received_message(buddy, message) + + def room_configuration_error(self,stanza): + self.error(stanza) + + def error(self,stanza): + from common import fire + #gtalk gets this from conference.jabber.org + if not self.room_state.configured and stanza.get_from() == self.jid and\ + stanza.stanza_type == 'presence': + err = stanza.get_error() + cond = err.get_condition() + if cond is not None and cond.name == 'item-not-found': + return + try: + fire('error', title = self.jid, msg = stanza.get_error().get_message(), details='', + sticky = True, popupid = self.jid, buttons = ((_('Close'), lambda: None),), update = 'replace') + except Exception: + traceback.print_exc() + if not self.room_state.joined: + self.exit() + + @callsback + def _send_message(self, message, callback=None): + self.room_state.send_message(message.format_as('plaintext')) + callback.success() + + @callsback + def invite(self, buddy, message = None, callback = None): + ''' + Sends an invite for this room to "buddy" (its actually a to + the room--the room sends an invite "on your behalf"). + + + + + + Hey Hecate, this is the place for all good witches! + + + + + ''' + room = self + + try: buddy = buddy.jid.as_unicode() + except: buddy = JID(buddy).as_unicode() + + if message is None: + message = _('You have been invited to {roomname}').format(roomname=self.jid.as_unicode()) + + # Send an invitation "by way" of room + m = Message(from_jid = room.protocol.self_buddy.jid, + to_jid = room.jid) + + # + x = m.xmlnode.newTextChild(None, 'x', None) + x.setNs(x.newNs('http://jabber.org/protocol/muc#user', None)) + + # Plz come chat + invite = x.newTextChild(None, 'invite', None) + invite.setProp('to', buddy) + reason = invite.newTextChild(None, 'reason', message) + + self.protocol.send_cb(m, callback = callback) + + + def send_typing_status(self, status): + return None + + def set_subject(self, subject): + self.room_state.set_subject(subject) + + def exit(self): + self.room_state.leave() + + try: + del self.protocol.conversations[self.jid.as_unicode()] + except KeyError: + traceback.print_exc() + + Conversation.exit(self) + + @property + def self_buddy(self): + return self.protocol.self_buddy + + + def _buddy(self, mucuser): + 'Wraps a pyxmpp.jabber.muc.MucRoomUser in an observable JabberChatBuddy.' + + try: + return self.buddies[mucuser.nick] + except KeyError: + return self.buddies.setdefault(mucuser.nick, JabberChatBuddy(mucuser, self)) + diff --git a/digsby/src/jabber/JabberContact.py b/digsby/src/jabber/JabberContact.py new file mode 100644 index 0000000..ea45698 --- /dev/null +++ b/digsby/src/jabber/JabberContact.py @@ -0,0 +1,88 @@ +from common.actions import ActionMeta +import jabber +import common.actions +from common.actions import action #@UnresolvedImport +import contacts +from util import callsback + +import logging +log = logging.getLogger('jabber.contact') + +contact_attrs = ('watched','buddy_changed','__repr__', 'group', 'buddy', 'remove_from_group') + +no_widget = lambda self, *a, **k: None if getattr(self.buddy, 'iswidget', False) else True + +cgetattr = contacts.Contact.__getattribute__ + +objget = object.__getattribute__ + +class JabberContact(common.actions.ActionType, contacts.Contact): + 'An entry on a buddy list.' + + inherited_actions = [jabber.jbuddy] + __metaclass__ = ActionMeta + + _renderer = 'Contact' + + def __init__(self, buddy, group): + self.group = group + contacts.Contact.__init__(self, buddy, buddy.id) + + + @action() + @callsback + def remove(self, callback = None): + if len(self.buddy.groups) <= 1: + # If this buddy is only in one (or no) group(s), unsubscribe. + return self.buddy.remove(callback = callback) + else: + return self.remove_from_group(callback = callback) + + @action(no_widget) + def rename_gui(self): + return contacts.Contact.rename_gui(self) + + @callsback + def remove_from_group(self, callback = None): + 'Only removes this contact from the Group.' + log.info('remove_from_group %s: %s', self.group, self) + + item = self.protocol.roster.get_item_by_jid(self.buddy.jid).clone() + item.groups.remove(self.group) + query = item.make_roster_push() + + self.protocol.send_cb(query, callback=callback) + + @callsback + def replace_group(self, new_group, callback = None): + item = self.protocol.roster.get_item_by_jid(self.buddy.jid).clone() + if self.group is not None: + item.groups.remove(self.group) + if new_group not in item.groups: + item.groups.append(new_group) + query = item.make_roster_push() + self.protocol.send_cb(query, callback = callback) + + @action(no_widget) + def view_past_chats(self, *a, **k): + self.buddy.view_past_chats(*a, **k) + + def __iter__(self): + "Returns an iterator for this contact's resources." + + return iter(self.buddy) + + def __getattr__(self, attr): + if attr in contact_attrs: + return cgetattr(self, attr) + else: + return getattr(cgetattr(self, 'buddy'), attr) + + def __repr__(self): + return '<%s %s>' % (type(self).__name__, self.buddy.name) + + @action(lambda self: None) + def block(self): + pass + + unblock = block diff --git a/digsby/src/jabber/JabberConversation.py b/digsby/src/jabber/JabberConversation.py new file mode 100644 index 0000000..86c7b28 --- /dev/null +++ b/digsby/src/jabber/JabberConversation.py @@ -0,0 +1,234 @@ +from jabber.objects.x_event import X_Event, X_EVENT_NS +from jabber.objects.chatstates import ChatState, CHATSTATES_NS +from common.Conversation import Conversation +from pyxmpp.message import Message +from logging import getLogger +from jabber import JabberBuddy + +from util import callsback +from pyxmpp.utils import from_utf8 +from jabber.objects.x_delay import X_DELAY_NS, X_Delay +import libxml2 +from common import pref +from util.primitives.fmtstr import fmtstr + +log = getLogger('jabber.JabberConversation') + +XHTML_IM_NS = 'http://jabber.org/protocol/xhtml-im' +XHTML_NS = 'http://www.w3.org/1999/xhtml' + +xdata_namespaces = {'xhtmlim': XHTML_IM_NS, + 'xhtml' : XHTML_NS} + +typing_chatstates = dict(typing = 'composing', + typed = 'paused') + +class JabberConversation(Conversation): +# def __init__(self, protocol, buddy, jid_to, jid_from,thread=None): + def __init__(self, protocol, buddy, jid_to, thread=None): + ''' + + @param protocol: a JabberProtocol Instance + @param buddy: the buddy you are talking to + @param jid_to: the jid you are talking to + @param jid_from: the jid you are talking from + @param thread: the thread id, if any (not yet used) + ''' + + if not isinstance(buddy, JabberBuddy.JabberBuddy): + raise TypeError + + + Conversation.__init__(self, protocol) + self.buddy_to = buddy + self.jid_to = jid_to +# self.jid_from = jid_from + self.buddies = protocol.buddies + self.thread = thread + self.name = buddy.alias + + self.reset_chat_states() + + ischat = False + + @property + def self_buddy(self): + return self.protocol.self_buddy + + @property + def buddy(self): return self.buddy_to + + def reset_chat_states(self): + self.chat_states_allowed = None + self.x_events_allowed = None + + @callsback + def _send_message(self, message, auto = False, callback=None, **opts): + assert isinstance(message, fmtstr) + + if self.jid_to not in self.buddy.resources: + self.reset_chat_states() + self.jid_to = self.buddy.jid + # PyXMPP will escape the message for us... + m = Message(stanza_type = 'chat', to_jid = self.jid_to, body = message.format_as('plaintext')) + + #message = unicode(message.encode('xml')) + + #assert isinstance(message, unicode) + + append_formatted_html(m, message) + + if pref('privacy.send_typing_notifications', False): + ChatState('active').as_xml(m.xmlnode) + X_Event(composing = True).as_xml(m.xmlnode) + + try: + self.protocol.send_message(m) + except Exception, e: + callback.error(e) + else: + callback.success() + #self.sent_message(message.replace('\n', '
'), format) + + def send_typing_status(self, status): + if not any((self.x_events_allowed, self.chat_states_allowed)): + return + m = Message(to_jid = self.jid_to, stanza_type='chat') + node = m.xmlnode + if self.x_events_allowed: + X_Event(composing = (status == 'typing')).as_xml(node) + if self.chat_states_allowed: + ChatState(typing_chatstates.get(status, 'active')).as_xml(node) + + self.protocol.send_message(m) + + def buddy_join(self, buddy): + if buddy not in self.room_list: + self.room_list.append(buddy) + self.typing_status[buddy] = None + + def incoming_message(self, buddy, message): + from_jid = message.get_from() + if from_jid != self.jid_to: + self.reset_chat_states() + self.jid_to = from_jid + #self.message = message + body = get_message_body(message) + + if body: + stamp = get_message_timestamp(message) + + if stamp: + did_receive = self.received_message(buddy, body, timestamp = stamp, offline = True, content_type = 'text/html') + else: + did_receive = self.received_message(buddy, body, content_type = 'text/html') + + if did_receive: + Conversation.incoming_message(self) + + chatstate = self.get_message_chatstate(message, body) + if chatstate is False: + chatstate = None + if pref('jabber.system_message.show_gone', type=bool, default=False): + self.system_message(_('{name} has left the conversation.').format(name=from_jid)) + self.typing_status[buddy] = chatstate + + def get_message_chatstate(self, message, body): + 'Returns "typing", "typed", or None for a stanza.' + retval = None + xevents = message.xpath_eval(u"jxe:x",{'jxe':X_EVENT_NS}) + chatstates = message.xpath_eval('cs:*', {'cs': CHATSTATES_NS}) + + if chatstates: + self.chat_states_allowed = True + chatstate = ChatState(chatstates[0]).xml_element_name + + retval = {'composing':'typing', + 'paused' :'typed', + 'gone' : False, #left + 'inactive' : None, #not typing or typed, not nearby? + 'active' : None, #not typing or typed, nearby? + }.get(chatstate) + if xevents: + found_composing = X_Event(xevents[0]).composing + if found_composing: + self.x_events_allowed = True + if not chatstates: + retval = 'typing' if found_composing and not body else None + + return retval + + @property + def id(self): +# return (self.buddy_to, self.jid_to, self.jid_from, self.thread) + return (self.buddy_to,) + + def exit(self): + if self.chat_states_allowed and pref('privacy.send_typing_notifications', False): + m = Message(to_jid = self.jid_to, stanza_type='chat') + node = m.xmlnode + ChatState('gone').as_xml(node) + self.protocol.send_message(m) + self.protocol.conversations.pop(self.id, None) + Conversation.exit(self) + +def append_formatted_html(message_tag, message): + ''' + Inserts an node with formatted XHTML into a message tag. + + message_tag a stanza + message the message (a fmtstr object) + + After this method completes, the message stanza has an additional child node. + + Also returns the as a string. + ''' + html = message_tag.xmlnode.newChild(None, 'html', None) + xhtml_ns = html.newNs(XHTML_IM_NS, None) + + span_text = message.format_as('xhtml') + body_text = '%s' % (XHTML_NS, span_text) + try: + message_doc = libxml2.parseDoc(body_text.encode('utf-8')) + except Exception: + import traceback;traceback.print_exc() + print 'This text failed: %r' % body_text + raise + message_node = message_doc.get_children() + message_node_copy = message_node.docCopyNode(message_tag.xmlnode.doc, 1) + html.addChild(message_node_copy) + message_doc.freeDoc() + + return span_text + + #TODO: do I need to unlink or free nodes here? + + +def get_message_timestamp(message): + 'Returns a timestamp for a stanza, or None.' + + xdelays = message.xpath_eval(u"jxd:x",{'jxd':X_DELAY_NS}) + if xdelays: + delay = X_Delay(xdelays[0]) + if delay.timestamp is not None: + return delay.timestamp + + +def get_message_body(message): + 'Returns the unicode message body from a stanza.' + + jid = message.get_from() + + xdata = message.xpath_eval(u"xhtmlim:html/xhtml:body[1]/node()", xdata_namespaces) + if xdata: + # XHTML formatted message + # TODO: Strip namespaces + body = from_utf8(''.join(child.serialize() for child in xdata)) + else: + # Old style message + body = message.get_body() + body = unicode(body.encode('xml')) if body else None + if body is not None: + body = body.replace('\n', '
') + + return body diff --git a/digsby/src/jabber/JabberProtocol.py b/digsby/src/jabber/JabberProtocol.py new file mode 100644 index 0000000..975f963 --- /dev/null +++ b/digsby/src/jabber/JabberProtocol.py @@ -0,0 +1,1380 @@ +''' +Jabber Protocol. +''' + +from __future__ import with_statement +from common import profile +from common import netcall +import wx +import pyxmpp +from util.callbacks import do_cb_na +from jabber.threadstream import ThreadStream +#from jabber.tlslitestream import TLSLiteStream +from jabber.objects.iq_privacy import List, Privacy +from pyxmpp.utils import to_utf8 +from jabber.JabberResource import JabberResource +from jabber.objects.si_filetransfer import SI_FILETRANSFER_NS +from jabber.filetransfer.socketserver import JabberS5BServerSocket +import common + +from pyxmpp.all import JID, Iq, Presence, Message +from pyxmpp.jabber.client import JabberClient +from pyxmpp.roster import RosterItem + +from util import odict, callsback, CallLater, callany, threaded +from util.primitives.funcs import Delegate, do +from util.primitives.structures import enum +from util.xml_tag import tag_parse +from contacts import Group +from util.observe import observable_dict +import jabber +from pyxmpp.jabber import muc +from common import action +from pyxmpp.jabber.register import Register +from pyxmpp.streamtls import TLSSettings +from jabber.filetransfer.S5BFileXferHandler import S5BRecvHandler +from jabber.objects.bytestreams import BYTESTREAMS_NS +from hashlib import sha1 +from traceback import print_exc +import random, sys +import hooks + +from logging import getLogger +log = getLogger('jabber.protocol') +methods_dict = odict(dict(digsby_login="sasl:DIGSBY-SHA256-RSA-CERT-AES", sasl_md5="sasl:DIGEST-MD5", sasl_plain="sasl:PLAIN", + use_md5="digest", plain="plain")) +methods_dict._keys = ['digsby_login', 'sasl_md5', "sasl_plain", "use_md5", "plain"] + +NAPTIME = 8 + +MUC_URI = 'http://jabber.org/protocol/muc' + +def fmtstanza(stanza): + stanza.xml_node + +class JabberProtocol(common.protocol, JabberClient): + buddy_class = jabber.jbuddy + buddies_class = jabber.jbuddies + contact_class = jabber.jcontact + + name = 'jabber' + + thread_id = 0 + + message_sizes = [10, 12, 14, 18] + + states = enum('Disconnected', + 'Authenticating', + 'Connected') + + status_state_map = jabber.status_state_map + + can_specify_chatserver = True + + bots = common.protocol.bots | set((JID('twitter@twitter.com'),)) + + supports_group_chat = True + + def __init__(self, username, password, user, server, login_as='online', + do_tls=False, require_tls=False, verify_tls_peer=False, + do_ssl=False, alt_conn_lock=None, + resource="Digsby", priority=5, block_unknowns=False, + hide_os=False, alt_connect_opts = [], + dataproxy = None, + **authmethods): + +# from pprint import pprint +# pprint(dict(user=user, server=server, login_as=login_as, +# do_tls=do_tls, require_tls=require_tls, +# verify_tls_peer=verify_tls_peer, +# authmethods=authmethods)) +# authmethods = dict(sasl_md5=False, sasl_plain=False, +# use_md5=False, plain=True) +#assert only: in **authmethods +# sasl_md5=True, sasl_plain=True, +# use_md5=True, plain=True, + + servr, port = server + jid = JID(username) + jid = JID(jid.node, jid.domain, resource) + if isinstance(username, unicode): + username = username.encode('utf8') + if isinstance(password, unicode): + password = password.encode('utf8') + + common.protocol.__init__(self, username, password, user) + + def printargs(ok, store): + return True + return wx.YES == wx.MessageBox(str(store.get_current_cert().as_text()), str(ok), style=wx.YES_NO) + + jkwargs = dict(jid = jid, + password = password, + keepalive = 45, + disco_name = username, # should be fullname? + disco_type = "DigsbyUser",) + + self.alt_connect_opts = alt_connect_opts + self.alt_conn_lock = alt_conn_lock + self.on_alt_no = 0 + self.have_connected = False + self._failed_hosts = set() + + assert not (do_ssl and do_tls) + if do_tls: + tlsset = TLSSettings(require=require_tls, verify_peer=verify_tls_peer, + verify_callback=printargs) + jkwargs.update(tls_settings = tlsset) + + if servr: jkwargs.update(server = servr) + if port: jkwargs.update(port = port) + + self.do_ssl = do_ssl + + jkwargs.update(auth_methods = tuple(methods_dict[k] + for k in + methods_dict.keys() if authmethods.has_key(k) + and authmethods[k])) +# def __init__(self,jid=None, password=None, server=None, port=5222, +# auth_methods=("sasl:DIGEST-MD5","digest"), +# tls_settings=None, keepalive=0, +# disco_name=u"pyxmpp based Jabber client", disco_category=u"client", +# disco_type=u"pc"): + JabberClient.__init__(self, **jkwargs) + self.stream_class = ThreadStream + self.addrcache = {} + + #------------------------------------------------------------------------------ + # Contact List + #------------------------------------------------------------------------------ + self.root_group = Group('Root', self, 'Root') + self.fakegroups = set() + self.buddies = self.buddies_class(self) + self.conversations = observable_dict() + self.block_unknowns = block_unknowns + #------------------------------------------------------------------------------ + # Presence + #------------------------------------------------------------------------------ + self.show = '' + self.status = '' + self.priority = priority + self.photo_hash = None + + self.hide_os = hide_os + self.invisible = False + + self.connect_killed = False + + #------------------------------------------------------------------------------ + # Multi User Chat + #------------------------------------------------------------------------------ + self.room_manager = None + self.known_s5b_proxies = odict() + if dataproxy: + try: + dataproxy = JID(dataproxy) + except ValueError: + print_exc() + else: + self.known_s5b_proxies[dataproxy] = set() + custom_conf_server = authmethods.get('confserver', '').strip() + self.confservers = [custom_conf_server] if custom_conf_server else [] + + do(self.register_feature(feature) for feature in jabber.features_supported) + hooks.notify('digsby.jabber.initialized', self) + + @property + def invisible(self): + return False + + @invisible.setter + def invisible(self, value): + pass + + @property + def service(self): + return 'jabber' + + + def _get_caps(self): + 'Returns the Jabber capability list.' + from common import caps + return [caps.INFO, caps.IM, caps.FILES, caps.EMAIL, caps.VIDEO] + + caps = property(_get_caps) + + def set_invisible(self, invisible = True): + 'Sets invisible.' + return +# self.invisible = invisible +# self.presence_push() + + @action(lambda self, *a, **k: True if self.state == self.Statuses.OFFLINE else None) + @callsback + def Connect(self, register=False, on_success=None, on_fail=None, invisible = False, + do_conn_fail = True, callback=None): + + self.silence_notifications(NAPTIME) + self.register = register + self.change_state(self.Statuses.CONNECTING) + + self.connect_callback = callback + + self.invisible = invisible + self.do_conn_fail = do_conn_fail + self.connect_killed = False + + def conn_attmpt_failed(): + log.debug('conn_attmpt_failed') + if register: + #if we were trying to register, don't try again. + return callback.error() + if do_conn_fail and not self.want_try_again: + #nowhere else to go, + report conn fail + self.set_offline(self.Reasons.CONN_FAIL) + callback.error() + raise + elif not self.want_try_again: + #nowhere else to go + callback.error() + raise + else: + #go somewhere else and try again + return self._reconnect(invisible = invisible, + do_conn_fail = do_conn_fail, callback=callback) + self.connect_attempt_failed = conn_attmpt_failed + try: + self.connect(register=register, on_success=on_success, on_fail=on_fail) + except Exception, e: + print_exc() + self.connect_attempt_failed() + else: + self.connect_attempt_succeeded() + + def connect_attempt_succeeded(self): + log.debug('connect_attempt_succeeded') + with self.lock: + self.idle_loop = Delegate() + self.idle_loop += self.idle + self.idle_looper = jabber.IdleLoopTimer(1, self.idle_loop) + + def connect_attempt_failed(self): + raise AssertionError('connection attempt cannot fail before it is attempted') + + def connected(self): + with self.lock: + if self.state == self.Statuses.CONNECTING: + self.change_state(self.Statuses.AUTHENTICATING) + + def disconnected(self, want_try_again = False): + log.debug('disconnected 1') + with self.lock: + log.debug('disconnected 2') + if not want_try_again and not self.want_try_again: + log.debug('disconnected 3') +# assert False + self.change_state(self.Statuses.OFFLINE) + JabberClient.disconnected(self) + + def _get_stream(self): + return getattr(self, '_stream', None) + + def _set_stream(self, stream): + new = stream + if new is None: + old = self.stream + if old is not None: + netcall(old.close) + for attr in ('process_stream_error', 'state_change', 'owner'): + setattr(old, attr, Null) + self._stream = new + + stream = property(_get_stream, _set_stream) + + def auth_failed(self, reason=''): + if reason: + self._auth_error_msg = reason + self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) + self.Disconnect() + + def _should_attempt_connect(self, opts): + return opts['server'][0] not in self._failed_hosts + + def _get_next_connection_options(self): + opts = None + while opts is None: + try: + opts = self.alt_connect_opts[self.on_alt_no].copy() + except IndexError: + break + + self.on_alt_no += 1 + + if not self._should_attempt_connect(opts): + opts = None + + return opts + + @callsback + def _reconnect(self, invisible = False, + do_conn_fail = True, callback=None): + log.info('jabber _reconnect!') + getattr(getattr(self, 'idle_looper', None), 'stop', lambda: None)() + + #grab next set of connection opts + opts = self._get_next_connection_options() + if opts is None: + return self.fatal_error() + + self.change_state(self.Statuses.CONNECTING) + self.setup_deleted_attrs() + + self.server, self.port = opts.pop('server') + do_tls=opts.pop('do_tls') + require_tls=opts.pop('require_tls') + verify_tls_peer=opts.pop('verify_tls_peer') + self.do_ssl = do_ssl = opts.pop('do_ssl') + assert not (do_ssl and do_tls) + if do_tls: + self.tls_settings = tlsset = TLSSettings(require=require_tls, verify_peer=verify_tls_peer) + else: + self.tls_settings = None + #collect $200 + threaded(lambda: JabberProtocol.Connect(self, invisible = invisible, + do_conn_fail = do_conn_fail, callback=callback))() + + def setup_deleted_attrs(self): + if self.root_group is None: + self.root_group = Group('Root', self, 'Root') + + self.fakegroups = set() + + self.setup_buddies_dict() + + if getattr(self, "interface_providers", None) is None: + self.interface_providers = [self] + + def setup_buddies_dict(self): + if self.buddies is None: + self.buddies = self.buddies_class(self) + + def fatal_error(self): + if not self.want_try_again: + reason = self.offline_reason or self.Reasons.CONN_LOST + log.info('Out of connection options. Changing to %r', reason) + self.set_offline(reason) + return False + else: + #connection lost, go somewhere else and get a new one. + log.info('Connection failed, trying another option') + self._reconnect(invisible = self.invisible, + do_conn_fail = self.do_conn_fail, + callback=self.connect_callback) + return True + + @property + def want_try_again(self): + ''' + True if we should continue trying to connect + ''' + with self.lock: + if self.offline_reason == self.Reasons.BAD_PASSWORD: + return False + + return (not self.connect_killed) and self.want_retry_connect + + @property + def want_retry_connect(self): + ''' + True if we've never started a session and we have other places left to try. + ''' + with self.lock: + return (not self.have_connected) and bool((self.on_alt_no + 1) <= len(self.alt_connect_opts)) + + def connect(self, register = False, on_success=None, on_fail=None): + """Connect to the server and set up the stream. + + Set `self.stream` and notify `self.state_changed` when connection + succeeds. Additionally, initialize Disco items and info of the client. + """ + JabberClient.connect(self, register) + if register: + s = self.stream + s.registration_callback = self.process_registration_form + s.registration_error_callback = on_fail + s.registration_success_callback = lambda: (self.disconnect(), on_success()) + + @action() + def Disconnect(self): + netcall(self._Disconnect) + + def stop_idle_looper(self): + idle_looper, self.idle_looper = getattr(self, 'idle_looper', None), None + if idle_looper is not None: + idle_looper.stop() + + del self.idle_looper + + def stop_timer_loops(self): + self.stop_idle_looper() + + def _Disconnect(self): + log.debug('logging out %r', self.want_try_again) + with self.lock: + pres = Presence(stanza_type="unavailable", + status='Logged Out') + try: + self.stream.send(pres) + except AttributeError: + pass + self._failed_hosts = set() + self.connect_killed = True + self.disconnect() + try: + self.stop_timer_loops() + except AttributeError: + print_exc() + + if getattr(self, 'idle_loop', None) is not None: + del self.idle_loop[:] + + if self.root_group is not None: + self.root_group.observers.clear() + self.root_group.protocol = None + self.root_group = None + + if self.buddies is not None: + self.buddies.protocol = None + self.buddies = None + + if self.interface_providers is not None: + del self.interface_providers[:] + self.interface_providers = None + + if self.stream is not None: + self.stream = None + + log.debug('1logged out %r', self.want_try_again) + self.offline_reason = None +# self.disconnected() + log.debug('1logged out %r', self.want_try_again) + + common.protocol.Disconnect(self) + + def session_started(self): + ''' + Called when the IM session is successfully started (after all the + neccessery negotiations, authentication and authorizasion). + ''' + with self.lock: + self.idle_looper.start() + log.info('session started') + self.service_discovery_init() + self.request_roster() + + s = self.stream + + newstate = self.Statuses.AUTHORIZED if self.name == 'digsby' else self.Statuses.ONLINE + #when this is true, don't try to start a new connection on conn_lost + self.have_connected = True + + if self.alt_connect_opts: + #there may/may not be a lock + if self.alt_conn_lock: + self.alt_conn_lock.acquire() + #really only helpful for gtalk reconnect. Put the working connection at the top of the options. + working_opts = self.alt_connect_opts.pop(self.on_alt_no-1) + self.alt_connect_opts.insert(0, working_opts) + if self.alt_conn_lock: + self.alt_conn_lock.release() + + # set up handlers for supported queries + s.set_iq_get_handler("query", "jabber:iq:version", self.get_version) + + # set up handlers for stanzas + do(s.set_presence_handler(name, func) for name, func in [ + (None, self.buddies.update_presence), + ('unavailable', self.buddies.update_presence), + ('available', self.buddies.update_presence), + ('subscribe', self.subscription_requested), + ('subscribed', self.presence_control), + ('unsubscribe', self.presence_control), + ('unsubscribed', self.presence_control), + ]) + + self.s5b_handler = S5BRecvHandler(self) + self.s5b_handler.register_handlers() + + # set up handler for + s.set_message_handler("normal", self.message) + + self.si_handler = jabber.filetransfer.StreamInitiationHandler(self) + self.si_handler.set_profile_handler(SI_FILETRANSFER_NS, jabber.filetransfer.StreamInitiation.FileTransferSIHandler) + + self.si_handler.set_stream_handler(BYTESTREAMS_NS, self.s5b_handler) + self.s5bserver = JabberS5BServerSocket() + + self.si_handler.register_handlers() + + self.session_started_notify(s) + + self.connect_callback.success() + self.change_state(newstate) + log.info('session started done') + + def session_started_notify(self, s): + ''' + whatever needs to be done after most setup and before the callback/state change happens. + usually that will be plugins doing their own setup. + allows notify to be overridden in subclass + ''' + hooks.notify('digsby.jabber.session_started', self, s) + + def authorized(self): + log.info('authorized1') + JabberClient.authorized(self) # required! + log.info('authorized2') + jabber.objects.bytestreams.register_streamhost_cache_fetchers(self.cache, self.stream) + log.info('authorized3') + self.self_buddy = self.buddies[self.stream.me] + log.info('authorized4') + log.info('self buddy is: %r', self.self_buddy) + + def get_version(self,iq): + """Handler for jabber:iq:version queries. + + jabber:iq:version queries are not supported directly by PyXMPP, so the + XML node is accessed directly through the libxml2 API. This should be + used very carefully!""" + iq = iq.make_result_response() + q = iq.new_query("jabber:iq:version") + q.newTextChild( q.ns(), "name", "Digsby Client" ) + q.newTextChild( q.ns(), "version", ('%s %s' % (sys.REVISION, sys.TAG)).strip()) # strip because sometimes TAG is '' + if not self.hide_os: + import platform + platform_string = platform.platform() + # for some reason, on my XP box, platform.platform() contains both + # the platform AND release in platform.platform(). On Ubuntu, OS X, + # and I believe older versions of Windows, this does not happen, + # so we need to add the release in all other cases. + if platform_string.find("XP") == -1: + platform_string += " " + platform.release() + + q.newTextChild( q.ns(), "os", platform_string ) + self.send(iq) + return True + + def message(self, stanza): + _from = stanza.get_from() + buddy = self.buddies[_from] + _to = stanza.get_to() +# if _to.resource is None: #send from bare to bare +# _from = _from.bare() +# tup = (buddy, _from, _to, stanza.get_thread()) + tup = (buddy,) + if tup in self.conversations: + convo = self.conversations[tup] + else: +# convo = jabber.conversation(self, *tup) + convo = jabber.conversation(self, buddy, _from) + self.conversations[tup] = convo + convo.buddy_join(self.self_buddy) + convo.buddy_join(buddy) +# message = Message(stanza.get_node()) + convo.incoming_message(buddy, stanza) + return True + + def convo_for(self, contact): + return self.chat_with(contact) + + def chat_with(self, buddyobj_or_jid): + buddy_or_jid = getattr(buddyobj_or_jid, 'buddy', buddyobj_or_jid) + jid = getattr(buddy_or_jid, 'jid', buddy_or_jid) + buddy = self.buddies[jid] + +# tup = (buddyobj, jid, self.stream.me.bare(), None) + tup = (buddy,) + if tup in self.conversations: + convo = self.conversations[tup] + + if isinstance(buddyobj_or_jid, jabber.resource): + # asked for a resource specifically. + convo.jid_to = jid + else: + convo = self.conversations.setdefault(tup, + jabber.conversation(self, buddy, jid)) + convo.buddy_join(self.self_buddy) + convo.buddy_join(buddy) + return convo + + def set_message(self, message, status, format = None, default_status='away'): + log.info('set_message(%s): %r', status, message) + + state = self.status_state_map.get(status.lower(), default_status) + + # no tag means normal + self.show = state if state != 'normal' else None + + self.status = message + self.presence_push() + + def presence_push(self, status=None): + if status is None: + status = self.status + + pres = Presence(#stanza_type = 'invisible' if self.invisible else None, + show = self.show or None, + status = status or None, + priority = self.priority or None) + self._add_presence_extras(pres) + self.send_presence(pres) + try: + self.self_buddy.update_presence(pres, buddy=self.stream.me) + except AttributeError: + if self.stream is not None: + print_exc() + + def _add_presence_extras(self, pres): +# xmlns="http://jabber.org/protocol/caps"/> +# c = pres.add_new_content("http://jabber.org/protocol/caps", "c") +#.""" +# +# log.info('presence info received for ' + stanza.get_from()) +# return self.buddies[JID(stanza.get_from())].update_presence(stanza) +# +# +# msg = u"%s has become " % (stanza.get_from()) +# t = stanza.get_type() +# if t=="unavailable": +# msg += u"unavailable" +# else: +# msg += u"available" +# +# show=stanza.get_show() +# if show: +# msg+=u"(%s)" % (show,) +# +# status=stanza.get_status() +# if status: +# msg += u": " + status +# print msg + + def subscription_requested(self, stanza): + 'A contact has requested to subscribe to your presence.' + + assert stanza.get_type() == 'subscribe' + + # Grab XML on this threads + from_jid = stanza.get_to() + to_jid = stanza.get_from() + stanza_id = stanza.get_id() + + log.info('subscription requested from %s to %s', from_jid, to_jid) + + def send_reponse(contact, authorize): + stanza_type = pyxmpp.presence.accept_responses['subscribe'] \ + if authorize else pyxmpp.presence.deny_responses['subscribe'] + pr = Presence(stanza_type = stanza_type, from_jid = from_jid, + to_jid = to_jid, stanza_id = stanza_id) + + self.send_presence(pr) + + if authorize: + pr2 = Presence(stanza_type = 'subscribe', from_jid = from_jid, + to_jid = to_jid) + self.send_presence(pr2) + + contact = stanza.get_from() + self.hub.authorize_buddy(self, contact, callback = lambda contact, authorize, _username_added=None: netcall(lambda: send_reponse(contact, authorize))) + return True + + + def presence_control(self,stanza): + ''' + Handle subscription control stanzas -- acknowledge + them. + ''' + msg = unicode(stanza.get_from()) + presence_messages.get(stanza.get_type(), '') + log.info(msg) + + return True + + def print_roster_item(self,item): + if item.name: + name=item.name + else: + name = u"" + print (u'%s "%s" subscription=%s groups=%s' + % (unicode(item.jid), name, item.subscription, + u",".join(item.groups)) ) + + def filter_contact(self, contact): + return False + + def filter_group(self, group): + return False + + def roster_updated(self, item=None): + roster = self.roster + + with self.root_group.frozen(): + + jcontact = self.contact_class + buddies = self.buddies + root_group = self.root_group + + del root_group[:] + + groups = set(roster.get_groups()) + self.fakegroups -= groups + groups |= self.fakegroups + groups.add(None) + + for group in groups: + if group is None: + g = root_group + else: + g = Group(group, self, group) + + for item in roster.get_items_by_group(group): + contact = jcontact(buddies[item.jid](item), group) + g.append(contact) + + if not self.filter_group(g): + if g is not root_group: + root_group.append(g) + + g[:] = [c for c in g if not self.filter_contact(c)] + + def has_buddy_on_list(self, buddy): + try: + return self.roster.get_item_by_jid(JID(buddy.name)) + except (KeyError, AttributeError): + return None + + @callsback + def add_group(self, groupname, callback = None): + self.fakegroups.add(groupname) + self.roster_updated() + callback.success(groupname) + + @callsback + def remove_group(self, group, callback = None): + + log.info('Removing group: %r', group) + + self.fakegroups.discard(group) + self.roster_updated() + cbs = [] + for buddy in self.buddies.values(): + if group in buddy.groups: + if len(buddy.groups) == 1: + log.info('Buddy %r will be removed from list', buddy) + cbs.append(buddy.remove) + else: + item = self.roster.get_item_by_jid(buddy.jid).clone() + log.info('Buddy %r will be removed from group', buddy) + g = item.groups + if (group in g): + g.remove(group) + query = item.make_roster_push() + + @callsback + def sendit(query=query, callback = None): + self.send_cb(query, callback = callback) + + cbs += [sendit] + + do_cb_na(cbs, callback = callback) + + + @callsback + def move_buddy(self, contact, to_group, from_group=None, pos=0, callback = None): + contact.replace_group(to_group, callback=callback) + + @callsback + def send_cb(self, query, timeout_duration=None, callback=None): + ''' + Given a callback object with callable attributes success, error, and timeout, + sends out a query with response handlers set. + ''' + + def free_stanza(st): + stream = self.get_stream() + with stream.lock: + if stream.doc_out is not None: + st.free() + else: + if st._error: + st._error.xmlnode = None + st.xmlnode = None + + def my_super_callback_success(stanza): + stanza.handler_frees = True + if not isinstance(callback.success, list): + from util import funcinfo + try: + log.info("WTF, callback was %r", funcinfo(callback.success)) + except Exception: + log.error('bad callback.success %r', callback.success) + return free_stanza(stanza) + if len(callback.success) == 0: + return free_stanza(stanza) + + try: + f = callback.success[0].cb + _call_free = CallLater(lambda:free_stanza(stanza)) + def my_hyper_callback_success(st): + try: f(st) + except Exception: + log.error('error processing %r', f) + print_exc() + finally: _call_free() + callback.success[0].cb = my_hyper_callback_success + callany(callback.success, stanza) + except Exception: + print_exc() + log.error('failed to set up success stanza.free for %r, %r', callback, stanza) + + def my_super_callback_error(stanza): + stanza.handler_frees = True + if not isinstance(callback.error, list): + from util import funcinfo + try: + log.info("WTF, callback was %r", funcinfo(callback.error)) + except Exception: + log.error('bad callback.error %r', callback.error) + return free_stanza(stanza) + if len(callback.error) == 0: + return free_stanza(stanza) + + try: + f = callback.error[0].cb + _call_free = CallLater(lambda:free_stanza(stanza)) + def my_hyper_callback_error(st): + try: f(st) + except Exception: + log.error('error processing %r', f) + print_exc() + finally: _call_free() + callback.error[0].cb = my_hyper_callback_error + callany(callback.error, stanza) + except Exception: + print_exc() + log.error('failed to set up error stanza.free for %r, %r', callback, stanza) + + def my_super_callback_timeout(*a): + ''' + consume the arguments, they are from ExpiringDictionary and cause problems + with the number of arguments taken by timeout function, + which in this case is expected to be 0, since we can store the context in it's closure + and hardly anything even uses timeouts. + ''' + return callback.timeout() + + s = self.get_stream() + if s is not None: + try: + if timeout_duration is not None: + self.stream.set_response_handlers(query, my_super_callback_success, + my_super_callback_error, + my_super_callback_timeout, + timeout = timeout_duration) + else: + self.stream.set_response_handlers(query, my_super_callback_success, + my_super_callback_error, + my_super_callback_timeout) + except Exception, e: + print_exc() + log.critical("couln't set stream handlers") + return callany(callback.error) + try: + self.stream.send(query) + except Exception, e: + print_exc() + log.critical("couln't send query") + try: + return callany(callback.error) + except Exception: + log.critical("couln't call callany(callback.error) %r", callback) + + def send_presence(self, pres): + assert isinstance(pres, Presence) + self.send(pres) + + def send_message(self, message): + assert isinstance(message, Message) + self.send(message) + + def send_iq(self, iq): + assert isinstance(iq, Iq) + self.send(iq) + + def send(self, stanza): + s = self.get_stream() + if s is not None: + try: + self.stream.send(stanza) + except Exception, e: + print_exc() + log.critical("couln't send stanza") + + @callsback + def add_buddy(self, jid, group_id=None, _pos=None, service = None, callback = None): + jid = JID(jid).bare() + try: + item = self.roster.get_item_by_jid(jid).clone() + except KeyError, _e: + item = RosterItem(node_or_jid=jid, + subscription='none', + name=None, + groups=(group_id,), + ask=None) + #print item + q = item.make_roster_push() + p = Presence(to_jid=item.jid, stanza_type='subscribe') + + self.send_cb(q, callback=callback) + self.send_presence(p) + else: + #testing subclipse retardedness, again? + self.buddies[jid].add_to_group(group_id) + + def add_new_buddy(self, buddyname, groupname, service = None, alias = None): + buddy = self.get_buddy(buddyname) + if alias: + profile.set_contact_info(buddy, 'alias', alias) + self.add_buddy(buddyname, groupname, service = service) + + @callsback + def rename_group(self, oldname, newname, callback = None): + # If oldname is a fake group, rename it. + if oldname in self.fakegroups: + self.fakegroups.remove(oldname) + self.fakegroups.add(newname) + self.roster_updated() + + items = [] + for buddy in self.buddies.values(): + if oldname in buddy.groups: + item = self.roster.get_item_by_jid(buddy.jid).clone() + + g = item.groups + if (oldname in g): g.remove(oldname) + if (newname not in g): g.append(newname) + items.append(item) + + if items: + iq = items.pop(0).make_roster_push() + q = iq.get_query() + for item in items: + item.as_xml(q) + self.send_cb(iq, callback = callback) + else: + callback.success() + + + + #debug function + def send_display(self, stanza, title=None): + from util.TagViewer import TagViewer + def stanza_print(s): + t = tag_parse(s.xmlnode.serialize()) + self.hub.call_later(TagViewer, t, expand=True, title=title) + self.last_displayed = Iq(s.get_node()) + return True + self.send_cb(stanza, success=stanza_print, error=stanza_print) + + #debug function + def stanza_display(self, stanza, title=None): + from util.TagViewer import TagViewer + self.last_displayed = Iq(stanza.get_node()) + t = tag_parse(stanza.xmlnode.serialize()) + self.hub.call_later(TagViewer, t, expand=True, title=title) + return True + + #debug function + def disco(self, place=None, type_=None): + if not place: + place = self.jid.domain + if not type_ or type_ == 'b': + i1 = Iq(stanza_type='get'); i1.set_to(place) + i1.add_new_content('jabber:iq:browse', 'query') + self.send_display(i1,'browse') + + if not type_ or type_ == 'm': + i2 = Iq(stanza_type='get'); i2.set_to(place) + i2.add_new_content('http://jabber.org/protocol/disco#items', 'query') + self.send_display(i2,'items') + + if not type_ or type_ not in ('b','m'): + i3 = Iq(stanza_type='get'); i3.set_to(place) + i3.add_new_content('http://jabber.org/protocol/disco#info', 'query') + self.send_display(i3,'info') + + def service_discovery_init(self): + self.disco_init = jabber.disco.DiscoNode(self.cache, JID(self.jid.domain)) + self.disco_init.fetch(self.disco_finished, depth=1, timeout_duration = 30) + + def default_chat_server(self): + return self.confservers[0] if self.confservers else 'conference.jabber.org' + + #@action(needs = ((unicode, "Chat Server", 'conference.jabber.org'), + #(unicode, 'Room Name', lambda: 'Digsby' + str(random.randint(0, sys.maxint))), + #(unicode, 'Your Nickname', lambda self: self.self_buddy.name))) + @callsback + def join_chat(self, server = None, room_name = None, + nick = None, convo = None, notify_profile=True, callback = None): + + chat_server_jid = server + if not chat_server_jid: + chat_server_jid = self.default_chat_server() + + room_name = self._get_chat_room_name(room_name) + + nick = self._get_chat_nick(nick) + + chat_server_jid = JID(room_name, domain = JID(chat_server_jid).domain) + + if not self.room_manager: + self.room_manager = muc.MucRoomManager(self.stream) + self.room_manager.set_handlers() + + if notify_profile: + callback.success += profile.on_entered_chat + + convo = jabber.chat(self, chat_server_jid, callback) + self.room_manager.join(chat_server_jid, nick, convo) + +# callback.success(convo) +# profile.on_entered_chat(convo = convo) + + def _get_chat_nick(self, nick): + if not nick: + nick = self.self_buddy.jid.node + return nick + + def _get_chat_room_name(self, room_name): + if not room_name: + room_name = 'Digsby%s' % random.randint(0, sys.maxint) + return room_name + + def join_chat_jid(self, jid, nick = None): + log.info('join_chat_jid %r with nick %s', jid, nick) + + jid = JID(jid) + self.join_chat(jid.domain, jid.node, nick) + + def set_priority(self, priority): + self.priority = priority + self.presence_push() + + def send_file(self, buddy, filestorage): + ''' + Sends a file to a buddy. + + fileobj must be a file like object. + ''' + if isinstance(buddy, JabberResource): + jid = buddy.jid + else: + jid = buddy.get_highest_priority_resource().jid + log.info('sending file to %r', jid) + xfer = jabber.filetransfer.initiateStream.SIsender(self, jid, filestorage) + xfer.send_offer() + return xfer + + def disco_finished(self, disco_node): + log.info('got disco_node %s', disco_node) + self.known_s5b_proxies.update((node.jid, set()) + for node in + disco_node.find_feature(BYTESTREAMS_NS, + depth=1)) + + # find conference nodes + for node in disco_node.find_feature(MUC_URI, depth=1): + self.confservers += [node.jid.as_unicode()] + + self.get_streamhosts() + + def get_streamhosts(self): + for address in self.known_s5b_proxies: + self.cache.request_object(jabber.objects.bytestreams.ByteStreams, + (address, None), state='old', + object_handler = self.update_streamhost_result, + error_handler = self.update_streamhost_error, + timeout_handler = self.update_streamhost_error) + + def update_streamhost_result(self, address, bytestreams_, _state): + print "STREAMHOSTS:",bytestreams_.hosts + try: + proxy= self.known_s5b_proxies[address[0]] + except KeyError: + proxy= self.known_s5b_proxies[address[0]] = set() + finally: + from pprint import pprint + pprint(bytestreams_.hosts) + proxy.update(bytestreams_.hosts) + + def update_streamhost_error(self, address, bytestreams_=None): + if bytestreams_: + print "STREAMHOSTS ERROR:", bytestreams_ + else: + print "STREAMHOSTS TIMEOUT" + try: + self.known_s5b_proxies[address[0]] = set() + except KeyError: + pass + + def get_privacy_lists(self): + i = Iq(to_jid = self.jid.domain, stanza_type="get") + q = Privacy() + q.as_xml(i.xmlnode) + self.send_cb(i, success=self.got_privacy_lists, error=self.privacy_list_fail); + + def got_privacy_lists(self, stanza): + q = Privacy(stanza.get_query()) + self.default_list_name = q.default or "Digsby" + self.default_list_exists = True if q.default else False + if q.default or "Digsby" in [list_.name for list_ in q]: + i = Iq(to_jid = self.jid.domain, stanza_type="set") + q2 = Privacy() + q2.default = q.default or "Digsby" + q2.as_xml(i.xmlnode) + self.send(i) + if self.default_list_exists: + self.get_default_list() + log.info("got privacy lists:\n%s", q) + + def get_default_list(self): + i = Iq(to_jid = self.jid.domain, stanza_type="get") + q = Privacy() + q.append(List(self.default_list_name)) + q.as_xml(i.xmlnode) + self.send_cb(i, success=self.handle_default_list_response, + error=self.privacy_list_fail) + + def handle_default_list_response(self, stanza): + q = Privacy(stanza.get_query()) + self.default_list = q[0] + + def privacy_list_fail(self, stanza): + log.info("failed to get privacy lists") + + def set_idle(self, *a, **k): + pass + + @callsback + def change_password(self, password, callback=None): + reg = Register() + reg.username = self.jid.node + reg.password = password + i = Iq(stanza_type="set") + reg.as_xml(i.xmlnode) + + log.info('changing password for %s', self) + self.send_cb(i, callback=callback) + + @action(lambda self: True if getattr(sys, 'DEV', False) else None) + def xml_console(self): + from gui.protocols.jabbergui import show_xml_console + print show_xml_console + show_xml_console(self) + + def delete_account(self, on_success, on_fail): + reg = Register() + reg.remove = True + i = Iq(stanza_type="set") + reg.as_xml(i.xmlnode) + self.send_cb(i, success=(lambda *a, **k: on_success()), error=(lambda *a, **k: on_fail)) + + def group_for(self, contact): + return contact.group + + def get_groups(self): + return [g.name for g in self.root_group if type(g) is Group] + + def get_group(self, groupname): + for group in self.root_group: + if type(group) is Group and group.name.lower() == groupname.lower(): + return group + + def idle(self): + try: + JabberClient.idle(self) + if not self.stream.socket or self.stream.eof: + raise AssertionError, "if the stream is dead or gone, we can't really send a keep-alive" + except Exception: + self.stop_timer_loops() + else: + self.cache.tick() + + def stream_error(self, err): + if err.get_condition().name == "conflict": + self.change_reason(self.Reasons.OTHER_USER) + else: + self.change_reason(self.Reasons.CONN_LOST) + JabberClient.stream_error(self, err) + + @action(callable_predicate = lambda self: hasattr(self, 'vcard') if self.is_connected else None) + def edit_vcard(self, *a, **k): + import gui.vcard + gui.vcard.vcardgui.VCardGUI(protocol = self) + + @action() + def view_vcard(self): + pass + + @action() + def service_discovery(self): + pass + + @action() + def server_info(self): + pass + + def save_vcard(self, vcard): + i = Iq(stanza_type='set') + n = i.xmlnode + n.addChild(vcard.as_xml(n)) + self.vcard = vcard + self.send_cb(i) + + def allow_message(self, buddy, mobj): + super = common.protocol.allow_message(self, buddy, mobj) + if super in (True, False): + return super + + if not self.block_unknowns: + return True + + jid = JID(buddy.jid).bare() + + try: + item = self.roster.get_item_by_jid(jid) + except KeyError: + return False + else: + return True + + +presence_messages = dict( + subscribe = u" has requested presence subscription.", + subscribed = u" has accepted your presence subscription request." , + unsubscribe = u" has canceled their subscription of our presence.", + unsubscribed = u" has canceled our subscription of their presence.", +) + + + +# def block_jid(self, jid): +# utf8 = JID(jid).as_utf8() +# if not self.default_list_exists: +# self.default_list = List("Digsby") +# found = [item for item in self.default_list if item.type=="jid" and item.value==utf8] +# if found and not found[0].all_blocked(): +# found[0].message = False +# found[0].presence_in = False +# found[0].presence_out = False +# found[0].iq = False +# else: +# self.default_list.append(ListItem(type="jid", value=utf8, action="Deny", order=?)) +# if not found or not found[0].all_blocked(): +# i = Iq(to_jid = self.jid.domain, stanza_type="set", stream=self.stream) +# self.default_list.as_xml(i) +# self.stream.send(i) +# +# if not self.default_list_exists: +# i2 = Iq(to_jid = self.jid.domain, stanza_type="set", stream=self.stream) +# q2 = Privacy() +# q2.default = q.default or "Digsby" +# q2.as_xml(i2.xmlnode) +# self.stream.send(i2) +# self.default_list_exists = True + + +def do_threaded(callable): + threaded(callable)() + +def get_threaded_wrapper(*a, **k): + return do_threaded + +import protocols +protocols.declareAdapterForType(protocols.protocolForURI("http://www.dotsyntax.com/protocols/connectcallable"), + get_threaded_wrapper, JabberProtocol) + +if __name__ == "__main__": + j = JabberProtocol() diff --git a/digsby/src/jabber/JabberResource.py b/digsby/src/jabber/JabberResource.py new file mode 100644 index 0000000..65c3870 --- /dev/null +++ b/digsby/src/jabber/JabberResource.py @@ -0,0 +1,130 @@ +from contacts.BuddyListElement import BuddyListElement +from common.Buddy import fileinfo +from util import strip_html,Storage #@UnresolvedImport + +import common +import jabber +from common.actions import action #@UnresolvedImport +from common.Buddy import get_status_orb + +GTALK = 'gtalk' +JABBER = 'jabber' + +statuses=Storage( + dnd=_('Do Not Disturb'), + chat=_('Free for Chat'), + xa=_('Extended Away') +) + +def MOBILE_RESOURCES(): + if not common.pref('jabber.phone_is_mobile', type=bool, default=False): + return [] + return ["android", "BlackBerry", + "Mobile", #iphone + ] + +class JabberResource(BuddyListElement): + __metaclass__ = common.ActionMeta + def __init__(self, jabber, jid, presence): + self.jid = jid + self._set_presence(presence) +# self.presence = presence + self.name = self.jid.as_unicode() + self.alias = self.name + self.protocol = jabber + self.sms = False + + def _set_presence(self, presence): + self.status_msg = presence.get_status() + self.show = presence.get_show() + self.priority = presence.get_priority() + + + def get_status_message(self): + return self.status_msg + + def set_status_message(self, val): + self.status_msg = val + + status_message = property(get_status_message, set_status_message) + + @property + def stripped_msg(self): + msg = self.status_message + return strip_html(msg) if msg else u'' + + @property + def service(self): + if self.jid.domain == 'gmail.com': + return GTALK + else: + return JABBER + + @property + def away(self): + if self.away_is_idle: + return not self.available and not self.idle + else: + return not self.available + + @property + def available(self): + return self.show in jabber.available_show_types + + @property + def away_is_idle(self): + return self.service == GTALK + + #begin crap to make MetaContact happy + @property + def idle(self): + if self.away_is_idle: + return self.show == "away" + else: + return False + + @property + def mobile(self): + return self.jid.resource is not None and any(self.jid.resource.startswith(r) for r in MOBILE_RESOURCES()) + + @property + def icon(self): return False + + @property + def pending_auth(self): return False + + @property + def online(self): return True + + @property + def blocked(self): return False + #end crap to make MetaContact happy + + @property + def long_show(self): return None + + status_orb = property(lambda self: get_status_orb(self)) + + + @property + def sightly_status(self): + status=self.show + if status: + return statuses.get(status,status.title()) + else: + return _('Available') + + + def __repr__(self): + return '' % self.jid.as_unicode() + + @action() + def send_file(self, filepath = None): + if filepath is None: + from hub import Hub + filepath = Hub.getInstance().get_file('Sending file to %s' % self.name) + + if filepath: + self.protocol.send_file(self, fileinfo(filepath)) + + diff --git a/digsby/src/jabber/__init__.py b/digsby/src/jabber/__init__.py new file mode 100644 index 0000000..c8e5a7e --- /dev/null +++ b/digsby/src/jabber/__init__.py @@ -0,0 +1,128 @@ +status_state_map = { + 'available': 'normal', + 'away': 'away', + 'free for chat': 'chat', + 'do not disturb': 'dnd', + 'extended away': 'xa' +} + +import disco +import filetransfer +import objects +import jabber_util +import threadstreamsocket +from util.primitives.structures import enum +from pyxmpp.jid import JID as JID +from pyxmpp.presence import Presence as Presence +from pyxmpp.jabber.vcard import VCard +from jabber.JabberResource import JabberResource as resource +from jabber.JabberConversation import JabberConversation as conversation +from jabber.JabberBuddy import JabberBuddy as jbuddy +from jabber.JabberContact import JabberContact as jcontact +from jabber.JabberBuddies import JabberBuddies as jbuddies +from jabber.JabberProtocol import JabberProtocol as protocol +from jabber.JabberChat import JabberChat as chat +from jabber.filetransfer.S5BFileXferHandler import SOCKS5Bytestream +from jabber.objects.si import SI_NS +from jabber.objects.bytestreams import BYTESTREAMS_NS +from jabber.objects.si_filetransfer import SI_FILETRANSFER_NS +from jabber.idle_loop import IdleLoopTimer + +from PIL import Image #@UnresolvedImport + +class JabberException(Exception): + pass + +show_types = (None, u"away", u"xa", u"dnd", u"chat") +available_show_types = (None, u"chat", u'normal') + + + +status_states = enum(["Online", + "Away", + "Extended Away", + "Do Not Disturb", + "Free For Chat"]) + +features_supported = ['http://jabber.org/protocol/disco#info', + BYTESTREAMS_NS, + SI_NS, + SI_FILETRANSFER_NS] + +#### pyxmpp overrides #### +import pyxmpp.jabber.vcard as vcard +vcard.VCard.components.update({"FN": (vcard.VCardString,"optional"), + "N" : (vcard.VCardName, "optional")}) + +import pyxmpp.streambase as streambase + +def _process_node(self,xmlnode): + """Process first level element of the stream. + + The element may be stream error or features, StartTLS + request/response, SASL request/response or a stanza. + + :Parameters: + - `xmlnode`: XML node describing the element + """ + ns_uri=xmlnode.ns().getContent() + if ns_uri=="http://etherx.jabber.org/streams": + self._process_stream_node(xmlnode) + return + + if ns_uri==self.default_ns_uri: + stanza=streambase.stanza_factory(xmlnode, self) + stanza.handler_frees = False + self.lock.release() + try: + self.process_stanza(stanza) + finally: + self.lock.acquire() + if not stanza.handler_frees: + stanza.free() + else: + self.__logger.debug("Unhandled node: %r" % (xmlnode.serialize(),)) + +streambase.StreamBase._process_node = _process_node + + +class VCardPhotoData(vcard.VCardField, vcard.VCardImage): + def __init__(self,value): + vcard.VCardField.__init__(self,"PHOTO") + self.image = value + self.type = unicode(image_mimetype(value)) + self.uri=None + +def image_mimetype(imgdata): + 'Returns the MIME type for image data.' + import StringIO + return 'image/' + Image.open(StringIO.StringIO(imgdata)).format.lower() + +class VCardAdr(vcard.VCardField, vcard.VCardAdr): + def __init__(self,value): + vcard.VCardField.__init__(self,"PHOTO") + (self.pobox,self.extadr,self.street,self.locality, + self.region,self.pcode,self.ctry)=[""]*7 + self.type=[] + +def rosterclone(self): + return type(self)(self.jid, self.subscription, self.name, groups=tuple(self.groups)) + +import pyxmpp.roster +pyxmpp.roster.RosterItem.clone = rosterclone + +from pyxmpp.client import Client +from pyxmpp.jabber.client import JabberClient +from pyxmpp.jabber.disco import DISCO_ITEMS_NS,DISCO_INFO_NS +from pyxmpp.jabber import disco as pyxmpp_disco +#this moves Client.authorized to the bottom, so that all handlers are set up +#before the session can start, since that's what requests it. +def authorized(self): + """Handle "authorized" event. May be overriden in derived classes. + By default: request an IM session and setup Disco handlers.""" + self.stream.set_iq_get_handler("query",DISCO_ITEMS_NS,self._JabberClient__disco_items) + self.stream.set_iq_get_handler("query",DISCO_INFO_NS,self._JabberClient__disco_info) + pyxmpp_disco.register_disco_cache_fetchers(self.cache,self.stream) + Client.authorized(self) + +JabberClient.authorized = authorized diff --git a/digsby/src/jabber/disco.py b/digsby/src/jabber/disco.py new file mode 100644 index 0000000..21c0dad --- /dev/null +++ b/digsby/src/jabber/disco.py @@ -0,0 +1,399 @@ +from __future__ import with_statement +from contextlib import contextmanager + +import pyxmpp.jabber.disco as disco_ +import time +from pyxmpp.all import JID +from logging import getLogger +from threading import RLock +log = getLogger('jabber.disco') +from itertools import ifilter +from datetime import timedelta + +def items_start(func): + def wrapper(self, *arg, **kwargs): + self._items_outstanding = True + func(self, *arg, **kwargs) + return wrapper + +def items_end(func): + def wrapper(self, *arg, **kwargs): + func(self, *arg, **kwargs) + self._items_outstanding = False + self.check() + return wrapper + +def info_start(func): + def wrapper(self, *arg, **kwargs): + self._info_outstanding = True + func(self, *arg, **kwargs) + return wrapper + +def info_end(func): + def wrapper(self, *arg, **kwargs): + func(self, *arg, **kwargs) + self._info_outstanding = False + self.check() + return wrapper + +class DiscoInfoStorage(object): + __slots__ = """ + address + identities + features + """.split() + def __init__(self, address, identities, features): + self.address = address + self.identities = identities + self.features = features + +class DiscoIdentStorage(object): + __slots__ = """ + name + category + type + """.split() + def __init__(self, ident): + self.name = ident.get_name(), + self.category = ident.get_category(), + self.type = ident.get_type() + +class Disco(object): + def __init__(self, cache, jid, show=True, finished_handler=None): + self.cache = cache + self.pending = 0 + self.lock = RLock() + self.infos = {} + self.hierarchy = {} + self.jid = JID(jid) + self.show = show + self.finished_handler = finished_handler + + self.start = time.clock() + + self.get((self.jid, None)) + + def fetch_info(self, address): + log.info('fetching %s info', address) + self.cache.request_object(disco_.DiscoInfo, address, state='old', + object_handler=self.info_response, + error_handler=self.info_err, + timeout_handler=self.info_time, + timeout = timedelta(seconds=5)) + + def fetch_items(self, address): + log.info('fetching %s items', address) + self.cache.request_object(disco_.DiscoItems, address, state='old', + object_handler=self.items_response, + error_handler=self.items_err, + timeout_handler=self.items_time, + timeout = timedelta(seconds=5)) + + def get(self, addr): + with self.add(): + self.fetch_info(addr) + self.fetch_items(addr) + + def info_response(self, address, value, state): + with self.sub(): + log.info('info response') + try: + idents = [DiscoIdentStorage(i.get_name(), + i.get_category(), + i.get_type()) + for i in value.identities] + self.infos[address] = DiscoInfoStorage(address, + idents, + value.features) + except Exception, e: + log.info('%r', e) + raise e + + def info_err(self, address, error_data): + with self.sub(): + log.info('info error') + self.infos[address] = str(error_data) + + def info_time(self, address): + with self.sub(): + log.info('info timeout') + self.hierarchy[address] = None + + def items_response(self, address, value, state): + with self.sub(): + log.info('items response') + itms = value.get_items() + if itms: + its = {} + print its + for item in itms: +# print 'setting %r: %r' % ((item.jid, item.node), (item.name, item.action)) + its.__setitem__((item.jid, item.node), (item.name, item.action)) + # [ ] + self.hierarchy[address] = its + for addy in its: + self.get(addy) + + def items_err(self, address, error_data): + with self.sub(): + log.info('items error') + self.hierarchy[address] = None + + def items_time(self, address): + with self.sub(): + log.info('items timeout') + self.hierarchy[address] = None + + @contextmanager + def sub(self): + with self.lock: + try: + yield self + finally: + self.pending -= 1 + log.info('subtracting 1, self.pending is now %d', self.pending) + if not self.pending: + self.end = time.clock() + if self.show: + import wx + wx.CallAfter(DiscoViewer, (self.jid, None), self.hierarchy, self.infos, expand=True, title=None) + if self.finished_handler: + self.finished_handler(self) + + @contextmanager + def add(self): + with self.lock: + try: + self.pending += 2 + log.info('adding 2, self.pending is now %d', self.pending) + yield self + finally: + pass + + def find_feature(self, feature): + return list(self.find_feature_iter(feature)) + + def find_feature_iter(self, feature): + return ifilter(lambda i: feature in i.features + if hasattr(i, 'features') else None, + self.infos.itervalues()) + +import wx +import util + +class DiscoViewer(wx.Frame): + def __init__(self, key, item_dict, info_dict, expand=False, title=None): + wx.Frame.__init__(self, parent=None, id = -1, title = title if title else "DiscoViewer") + util.persist_window_pos(self, close_method=self.on_close) + self.content = wx.BoxSizer(wx.VERTICAL) + self.expand = expand + + self.item_dict = item_dict + self.info_dict = info_dict + + #generate new tree control + tree_id = wx.NewId() + self.tree = wx.TreeCtrl(self, tree_id) + self.content.Add(self.tree,1,wx.GROW) + + self.rootId = self.tree.AddRoot('Root') + if key: self(key) + + self.SetSizer(self.content) + self.Show(True) + + def __call__(self, key, parent_id=None): + parent_id = parent_id or self.rootId + info = self.info_dict[key] + children = self.item_dict[key] if key in self.item_dict else {} + + idents = info.identities + name = "" + for ident in idents: + if name: name += ' / ' + name += ident.name + + child_id = self.tree.AppendItem(parent_id, name) + for c in children: + id = self(c,child_id) + if self.expand: self.tree.Expand(id) + if self.expand: + self.tree.Expand(child_id) + self.tree.Expand(self.rootId) + return child_id + + def on_close(self, e): + self.Destroy() + +from util import lock + +class DiscoNode(object): + #in general, functions in this class are only sub-routines + def __init__(self, cache, jid, node=None, name=None): + self.lock = RLock() + self.cache = cache + #jid of this item + self.jid = JID(jid) + #node part of address + self.node = node + #Item description + self.name = name + #a list of namespaces + self.features = [] + #a list of DiscoIdentStorage with category(req), type(req), and name(opt) + self.identities = [] + #a list of "child" DiscoNode + self.items = [] + self.info_error_data = False + self.items_error_data = False + self._info_outstanding = False + self._items_outstanding = False + self._children_outstanding = 0 + + @property + def address(self): + ''' + Service Discovery address (jid, node) + ''' + return (self.jid, self.node) + + @lock + @info_start + def fetch_info(self, state='old'): + ''' + fetches the disco#info response for this address + @param state: lowest acceptable cache state for the response + ''' + + log.info('fetching info for %r', self) + self.cache.request_object(disco_.DiscoInfo, self.address, state=state, + object_handler=self.__info_response, + error_handler=self.__info_error, + timeout_handler=self.__info_timeout, + timeout = timedelta(seconds=self.timeout_duration)) + + @lock + @info_end + def __info_response(self, address, value, _state): + log.info('info response for %r', self) + try: + self.idents = [DiscoIdentStorage(i) for i in value.identities] + self.features = value.features + except Exception, e: + log.info(repr(e)) + raise + + @lock + @info_end + def __info_error(self, address, error_data="TimeOut"): + log.warning('info error for %r', self) if error_data != "TimeOut" \ + else log.warning('info timout for %r', self) + self.idents = [] + self.features = [] + self.info_error_data = error_data + + __info_timeout = __info_error + + @lock + @items_start + def fetch_items(self, state='old'): + ''' + fetches the disco#items response for this address + @param state: lowest acceptable cache state for the response + ''' + log.info('fetching items for %r', self) + self.cache.request_object(disco_.DiscoItems, self.address, state=state, + object_handler=self.__items_response, + error_handler=self.__items_error, + timeout_handler=self.__items_timeout, + timeout = timedelta(seconds=self.timeout_duration)) + + @lock + @items_end + def __items_response(self, address, value, state): + log.info('items response for %r', self) + del self.items[:] + len_ = len([self.items.append(DiscoNode(self.cache, item.jid, item.node, item.name)) + for item in value.get_items()]) + if self.depth_to_check: + self._children_outstanding += len_ + [item.fetch(callback=self.__child_done, depth=self.depth_to_check-1, + state=self.fetching_state, info = self.fetching_info, + items=self.fetching_items, timeout_duration = self.timeout_duration) + for item in self.items] + + @lock + def __child_done(self, child): + self._children_outstanding -= 1 + self.check() + + @lock + @items_end + def __items_error(self, address, error_data="TimeOut"): + log.warning('items error for %r', self) if error_data != "TimeOut" \ + else log.warning('items timout for %r', self) + del self.items[:] + self.items_error_data = error_data + + __items_timeout = __items_error + + @lock + def fetch(self, callback=None, depth=0, state='old', info=True, items=True, timeout_duration=5): + ''' + fetches information about this and "child" DiscoNodes + + @param callback: function to call when this fetch is complete, + takes one argument, will be this DiscoNode object + @param depth: depth to fetch information: + items depth is depth-1 + @param state: lowest acceptable cache state for the response(s) + @param info: boolean: get #info ? + @param items: boolean: get #items ? if False, depth can only be 0 + ''' + + assert depth >= 0 + if not items: assert depth == 0 + self.fetching_info = info + self.fetching_items = items + self.depth_to_check = depth + self.callback = callback + self.fetching_state = state + self.timeout_duration = timeout_duration + if info: self.fetch_info(state) + if items and depth-1>=0: self.fetch_items(state) + + @lock + def check(self): + ''' + check if conditions are right to call the callback + ''' + if self._info_outstanding \ + or self._items_outstanding \ + or self._children_outstanding > 0: + return + + #magic callback here + if self.callback is not None: + self.callback(self) + + def find_feature(self, feature, depth=0): + ''' + Searches the tree for a certain namespace, + returns a list of matching nodes + + @param feature: namespace to find + @param depth: depth to search (depth 0 = only this node) + ''' + + assert depth >= 0 + l = [] + if feature in self.features: + l.append(self) + if depth > 0: + for child in self.items: + l.extend(child.find_feature(feature, depth-1)) + return l + + def __repr__(self): + return "" % dict(name=self.name, + address=self.address) diff --git a/digsby/src/jabber/filetransfer/S5BFileXferHandler.py b/digsby/src/jabber/filetransfer/S5BFileXferHandler.py new file mode 100644 index 0000000..541f9cd --- /dev/null +++ b/digsby/src/jabber/filetransfer/S5BFileXferHandler.py @@ -0,0 +1,251 @@ +from jabber.objects.si_filetransfer import SI_FileTransfer +from util.Events import EventMixin +from pyxmpp.expdict import ExpiringDictionary +from common.timeoutsocket import TimeoutSocketMulti +from util.net import SocketEventMixin +import common +import util.primitives.structures as structures +from struct import pack, unpack +from functools import partial +from jabber.objects.si import SI_NS +from jabber.objects.bytestreams import BYTESTREAMS_NS, ByteStreams +from hashlib import sha1 +from pyxmpp.jid import JID +from pyxmpp.iq import Iq + +import logging + + +class ProxyFailure(StopIteration): + pass + +class ByteStream(EventMixin): + ''' + A set of events to be implemented by a any sort of ByteStream. + Right now there is only the socks5 stuff, but we may attempt other kinds + of streams in the future. They may not all (probably not all) use sockets + so events are used to indicate when things happen (including data arriving). + ''' + events = EventMixin.events | set(("stream_connected", + "stream_connect_failed", + "stream_rejected", + "stream_data_recieved", + "stream_closed", + "stream_error", + )) + +class INByteStream(ByteStream): + ''' + common class for incoming bytestreams + ''' + def __init__(self, si_ft, jabber_protocol): + ByteStream.__init__(self) + self.si_ft = si_ft + self.j = jabber_protocol + +class SOCKS5Bytestream(INByteStream): + log = logging.getLogger('jabber.filetrans.s5bxferhandler') + + def accept_stream(self, hosts_bytestreams, from_, to_, id): + self.from_ = from_ + self.to_ = to_ + self.respond_id = id + self.hosts_bytestreams = hosts_bytestreams + #print str(hosts_bytestreams) + self.my_sock = S5BOutgoingSocket(hosts_bytestreams, from_, to_) + self.my_sock.bind_event("connected", self.on_succ) + self.my_sock.bind_event("connection_failed", self.on_fail) + self.my_sock.get_connect() + + def on_fail(self): + self.log.warning("S5BFileXferHandler connect failed") + i = Iq(to_jid=self.from_, from_jid = self.to_, stanza_type="error", stanza_id = self.respond_id, + error_cond=u"item-not-found") + self.j.send(i) + self.event("stream_connect_failed") + + def on_succ(self, num_tries_taken, sock): + host_used = self.hosts_bytestreams.hosts[num_tries_taken-1].jid + self.my_sock = sock + + i2 = Iq(to_jid=self.from_, stanza_type="result", stanza_id = self.respond_id) + + b = ByteStreams() + b.host_used = JID(host_used) + b.as_xml(i2.get_node()) + + self.my_sock.found_terminator = self.close + self.my_sock.collect_incoming_data = self.collect_incoming_data + self.my_sock.set_terminator(self.si_ft.file.size) + self.my_sock.bind_event("socket_closed", self.closed) + self.my_sock.bind_event("socket_error", self.stream_error) + + self.j.send(i2) + self.log.info("S5BFileXferHandler connect succeeded to %s", host_used) + self.event("stream_connected") + + def stream_error(self): + self.event("stream_error") + self.unbind_all() + + def collect_incoming_data(self, data): + self.event("stream_data_recieved", data) + + def close(self): + #if my_sock doesn't exist, we probably haven't accepted the stream yet + try: + self.my_sock.close() + except: + pass + self.closed() + + def timed_out(self): + self.event("stream_connect_failed") + self.close() + + def closed(self): + self.event("stream_closed") + self.unbind_all() + + def unbind_all(self): + if hasattr(self, 'my_sock'): + self.my_sock.unbind("connected", self.on_succ) + self.my_sock.unbind("connection_failed", self.on_fail) + self.my_sock.unbind("socket_closed", self.closed) + self.my_sock.unbind("socket_error", self.stream_error) + + +class S5BRecvHandler(object): + def __init__(self, j): + self.d = ExpiringDictionary(60) + self.j = j + + def register_handlers(self): + ''' + register so that we'll get incomming s5b requests. + ''' + self.j.stream.set_iq_set_handler('query',BYTESTREAMS_NS, + self.handleSI) + self.j.idle_loop += self.d.expire + + def handleSI(self, stanza): + ''' + someone wants to open a stream with us + ''' + print 'handleSI called' + b = ByteStreams(stanza.get_query()) + sid = b.sid + try: + s5b = self.d.pop(sid) + except KeyError: + return False + else: + s5b.accept_stream(b, stanza.get_from(), stanza.get_to(), stanza.get_id()) + return True + + def waitfor(self, stanza): + ''' + add a new stream to those we're looking for. + ''' + si_ft = SI_FileTransfer(stanza.xpath_eval('si:si',{'si':SI_NS})[0]) + print 'waiting for stream for ', si_ft + s5b = SOCKS5Bytestream(si_ft, self.j) + self.d.set_item(si_ft.sid,s5b,timeout_callback=s5b.timed_out) + return s5b + + __call__ = waitfor + +#make socket subclass that can fit into timeoutsocket +#makc S5BOutgoingSocket return a socket at the end, and the higher up +#should grab the socket from it and inject into the position S5BOutgoingSocket +#occupies now. + +class S5BOutgoingSocketOne(common.TimeoutSocketOne, SocketEventMixin): + def __init__(self, *a, **k): + SocketEventMixin.__init__(self) + common.TimeoutSocketOne.__init__(self, *a, **k) + + def succ(self): + ''' + successful connection, now see if the proxy stuff will connect + ''' + self.proc( self.s5b_ok() ) + + def s5b_ok(self): + ok = yield (2, pack('BBB', 5,1,0)) + + _head, authmethod = unpack('BB', ok) + + if authmethod != 0x00: + raise ProxyFailure() + + out = pack('!BBBBB40sH', 0x05, 0x01, 0, 3, 40, self.hash, 0) + + in_fmt = ('!BBBB', 's5head', 'status', 'reserved', 'addrtype') + in_ = yield (4, out) + + head = structures.unpack_named(*(in_fmt+ (in_,))) + if head.addrtype == 3: + head.addrlen = yield (1, '') + head.addrlen = ord(head.addrlen) + + if head.addrlen > 0: + address = yield (head.addrlen, '') #@UnusedVariable + else: + address = '' #@UnusedVariable + _port = yield (2, '') + + if head.status != 0x00: + raise ProxyFailure() + + def proc(self, gen): + try: + to_read, out_bytes = gen.send(self.data) + except ProxyFailure: + return self.do_fail() + except StopIteration: + return common.TimeoutSocketOne.succ(self) + bytes = str(out_bytes) + if out_bytes: + self.push(bytes) + self.data = '' + self.found_terminator = partial(self.proc, gen) + if isinstance(to_read, int): + self.set_terminator(to_read) + else: + self.set_terminator(to_read._size()) + +class S5BOutgoingSocket(SocketEventMixin): + ''' + + ''' + def __init__(self, hosts_bytestreams, from_, to_): + SocketEventMixin.__init__(self) + self.log = logging.getLogger('S5BOutgoingSocket') + self.log.warning('S5BOutgoingSocket') + + shosts = hosts_bytestreams.hosts + self.addys = [(host.host, host.port) for host in shosts] + self.sid = hosts_bytestreams.sid + self.hash = sha1(self.sid + from_.as_utf8() + to_.as_utf8()).hexdigest() + + self.t = TimeoutSocketMulti() + + self._on_failure = lambda: self.event("connection_failed") + + def provide_data(self, sock): + sock.hash = self.hash + + def connected(self, sock): + ''' + pass the socket up to the SOCKS5Bytestream waiting for it + ''' + sock.reassign() + self.sock = sock + self.event("connected", self.t.attempts, sock) + + def get_connect(self): + self.t.tryconnect(self.addys, self.connected, self._on_failure, + 2, cls=S5BOutgoingSocketOne, provide_init=self.provide_data) + + diff --git a/digsby/src/jabber/filetransfer/StreamInitiation.py b/digsby/src/jabber/filetransfer/StreamInitiation.py new file mode 100644 index 0000000..6eaa997 --- /dev/null +++ b/digsby/src/jabber/filetransfer/StreamInitiation.py @@ -0,0 +1,242 @@ +from __future__ import with_statement +from jabber.objects.si_filetransfer import File +from common.filetransfer import IncomingFileTransfer +from jabber.objects.si_filetransfer import Feature +from jabber.objects.si_filetransfer import SI_FileTransfer +from jabber.objects.si import SI_NS +from pyxmpp.error import STANZA_ERROR_NS +from pyxmpp.all import Iq +from util.primitives import lock +from jabber.filetransfer.initiateStream import if_not_done, done_states +from path import path +import threading + +from logging import getLogger +log = getLogger('jabber.ft.si') + +class StreamInitiationHandler(object): + def __init__(self, jabber_object): + self.j = jabber_object + self.handlers = {} + self.supported_streams = {} + + def set_stream_handler(self, namespace, handler): + self.supported_streams[namespace] = handler + + def set_profile_handler(self, namespace, handler): + self.handlers[namespace] = handler + + def register_handlers(self): + self.j.stream.set_iq_set_handler('si', SI_NS, self.incoming) + + def incoming(self, stanza): + """dispatch""" + #CAS: error cases + prof = stanza.xpath_eval('si:si/@profile', + {'si':SI_NS})[0].content + if prof not in self.handlers: + self.respond_not_understood(stanza) + else: + self.handlers[prof](self, Iq(stanza.get_node())) + + def respond_no_valid(self, stanza): + """we don't support any suggested stream type""" + e = stanza.make_error_response('bad-request') + n = e.xpath_eval('si:si', {'si':SI_NS})[0] + n.unlinkNode() + n.freeNode() + e.set_from(None) + er = e.xpath_eval('ns:error')[0] + er.newProp('code', '400') + c=er.newChild(None,'no-valid-streams',None) + ns = c.newNs(SI_NS, None) + c.setNs(ns) + t = e.xpath_eval('ns:error/@type')[0] + t.setContent('cancel') + self.j.send(e) + + def respond_not_understood(self, stanza): + """we don't support that Stream Initiation profile; i.e. it wasn't + file transfer""" + e = stanza.make_error_response('bad-request') + n = e.xpath_eval('si:si', {'si':SI_NS})[0] + n.unlinkNode() + n.freeNode() + e.set_from(None) + er = e.xpath_eval('ns:error')[0] + er.newProp('code', '400') + c=er.newChild(None,'bad-profile',None) + ns = c.newNs(SI_NS, None) + c.setNs(ns) + self.j.send(e) + + def reject(self, stanza, reason = None): + """Say 'No, thanks'""" + e = stanza.make_error_response('forbidden') + n = e.xpath_eval('si:si', {'si':SI_NS})[0] + n.unlinkNode() + n.freeNode() + e.set_from(None) + er = e.xpath_eval('ns:error')[0] + er.newProp('code', '403') + c=er.newChild(None, 'text', reason or 'Offer Declined') + ns = c.newNs(STANZA_ERROR_NS, None) + c.setNs(ns) + self.j.send(e) + + def send_accept(self, stanza, payload): + """Say 'Yes, please'""" + i = stanza.make_result_response() + payload.as_xml(i.get_node()) + self.j.send(i) + + def result_accept(self): + #accept + pass + + def result_error(self): + #400 no valid streams + #400 profile not understood + #403 forbidden: Offer Declined + pass + +class FileTransferSIHandler(IncomingFileTransfer): + + def __init__(self, si_handler, iq): + self.si_handler = si_handler + self.stanza = iq + self.si_ft = SI_FileTransfer.from_iq(iq) + self.bytestream = None + self._lock = threading.RLock() + + if self.check_streams(): + #CAS: fix this with fail cases, i.e. check to see if this is a valid offer + + ft = self.si_ft + self.numfiles = 1 + self.name = ft.file.name + self.size = ft.file.size + self.buddy = self.si_handler.j.buddies[iq.get_from()] + file_desc = unicode(ft.file.desc) + IncomingFileTransfer.__init__(self) + si_handler.j.hub.on_file_request(si_handler.j, self) + + self.on_get_buddy(self.buddy) + + @property + def protocol(self): + return self.si_handler.j + + def file_desc(self): + return unicode(self.si_ft.file.desc) + + def decline(self, reason = None): + self.si_handler.reject(self.stanza, reason) + self.state = self.states.CANCELLED_BY_YOU + IncomingFileTransfer.decline(self) + + @lock + @if_not_done + def cancel(self, state=None): + #check if stream exists, and if our state is stransferring + #find out other states and implement them. + if state is None: + state = self.states.CANCELLED_BY_YOU + self.state = state + + if self.bytestream is not None: + self.bytestream.close() + + def accept(self, _openfileobj): + + self.state = self.states.CONNECTING + + for stream_type in self.si_handler.supported_streams: + if stream_type in self.si_ft.feature.possible_streams: + selected = stream_type + break + else: + assert False + + self._fileobj = _openfileobj + self.filepath = path(_openfileobj.name) + + si_ft = SI_FileTransfer(self.si_ft.sid) + si_ft.feature = Feature(selected_stream = selected) + si_ft.file = File() + + self.bytestream = b = self.si_handler.supported_streams[selected](self.stanza) + #bind events here + b.bind("stream_connected", self.transferring) + b.bind("stream_closed", self.stream_closed) + b.bind("stream_connect_failed", self.stream_connect_failed) + b.bind("stream_data_recieved", self.incoming_data) + b.bind("stream_error", self.stream_error) + + #wrap result in feature ns elem + self.si_handler.send_accept(self.stanza, si_ft) + + @lock + @if_not_done + def transferring(self): + self.state = self.states.TRANSFERRING + + @lock + @if_not_done + def stream_connect_failed(self): + self.state = self.states.CONN_FAIL + self.cleanup() + + @lock + @if_not_done + def stream_error(self): + self.state = self.states.CONN_FAIL_XFER + self.cleanup() + + def cleanup(self): + try: self._fileobj.close() + except: pass + self.unbind_all() + + if self.state != self.states.CANCELLED_BY_YOU: + self.on_error() + + @lock + @if_not_done + def stream_closed(self): + if self.completed == self.si_ft.file.size: + try: self._fileobj.close() + except: pass + self.unbind_all() + self._ondone() + else: + log.info('%r: Stream closed. Setting state to CANCELLED_BY_BUDDY and cleaning up.') + self.state = self.states.CANCELLED_BY_BUDDY + self.cleanup() + + def unbind_all(self): + b = self.bytestream + b.unbind("stream_connected", self.transferring) + b.unbind("stream_closed", self.stream_closed) + b.unbind("stream_connect_failed", self.stream_connect_failed) + b.unbind("stream_data_recieved", self.incoming_data) + + def check_streams(self): + """See if we can handle the request, if not, say so""" + if set(self.si_ft.feature.possible_streams) \ + & set(self.si_handler.supported_streams): + return True + else: + self.si_handler.respond_no_valid(self.stanza) + return False + + def incoming_data(self, data): + try: + self._fileobj.write(data) + self._setcompleted(len(data) + self.completed) + except ValueError: + with self._lock: + assert self.state in done_states + #file is dead, using an exception because try is free. + # a lock is not needed because it's a file object + diff --git a/digsby/src/jabber/filetransfer/__init__.py b/digsby/src/jabber/filetransfer/__init__.py new file mode 100644 index 0000000..37a3709 --- /dev/null +++ b/digsby/src/jabber/filetransfer/__init__.py @@ -0,0 +1,7 @@ +from jabber.objects.bytestreams import BYTESTREAMS_NS +import jabber +feature_neg_ns = 'http://jabber.org/protocol/feature-neg' +supported_streams = [BYTESTREAMS_NS] +import S5BFileXferHandler, initiateStream +file_transfer_handlers = {BYTESTREAMS_NS: S5BFileXferHandler.SOCKS5Bytestream } +from jabber.filetransfer.StreamInitiation import StreamInitiationHandler diff --git a/digsby/src/jabber/filetransfer/initiateStream.py b/digsby/src/jabber/filetransfer/initiateStream.py new file mode 100644 index 0000000..c62f85b --- /dev/null +++ b/digsby/src/jabber/filetransfer/initiateStream.py @@ -0,0 +1,403 @@ +from jabber.filetransfer.s5b_proxy_sender import S5B_proxyConnect +from jabber.objects.bytestreams import ByteStreams, BYTESTREAMS_NS +from jabber.objects.si_filetransfer import Feature +from jabber.objects.si_filetransfer import SI_FileTransfer, SI_NS +from jabber.objects.si_filetransfer import File +from jabber.filetransfer.S5BFileXferHandler import ByteStream +import functools +import random +from jabber.filetransfer import supported_streams +from pyxmpp.all import Iq +from util.net import NoneFileChunker + +from logging import getLogger +log = getLogger('jabber.file.send') + +from common import pref +from common.filetransfer import OutgoingFileTransfer, FileTransfer + +from path import path +from util import lock + +done_states = FileTransfer.states.CompleteStates | FileTransfer.states.FailStates + +def if_not_done(f): + @functools.wraps(f) + def wrapper1(self,*args, **kw): + if self.state not in done_states: + return f(self, *args, **kw) + return wrapper1 + + +class SIsender(OutgoingFileTransfer): + + def __init__(self, jabber_protocol, jid, filestorage, message = None): + OutgoingFileTransfer.__init__(self) + + self.j = jabber_protocol + self.jid = jid + self.filestor = filestorage + self.message = message + self.sid = 'si_' + str(id(self)) + str(random.randint(0,100)) + log.info('SIsender created for %r', jid) + + # to conform to the FileTransfer interface + self.completed = 0 + self.size = filestorage.size + self.filepath = path(self.filestor.path) + self.buddy = self.j.buddies[jid] + self.name = self.filestor.name + + self.on_get_buddy(self.buddy) + + @property + def protocol(self): + return self.j + + def send_offer(self): + self.state = self.states.WAITING_FOR_BUDDY + + i = Iq(to_jid=self.jid, stanza_type='set') + + #CAS: id generator for streams + si = SI_FileTransfer(self.sid) + si.file = File(self.filestor.name, self.filestor.size) + si.feature = Feature(possible_streams=supported_streams) + si.as_xml(i.get_node()) + + self.j.send_cb(i, success = self.handle_response, + error = self.handle_error, + timeout = self.timed_out) + + @lock + @if_not_done + def handle_response(self, stanza): + self.state = self.states.CONNECTING + si = SI_FileTransfer.from_iq(stanza) + self.stream = b = stream_connectors[si.feature.selected_stream](self) + b.bind_event("stream_connected", self.transferring) + b.bind_event("stream_connect_failed", self.stream_connect_failed) + b.bind_event("stream_error", self.stream_error) + b.bind_event("stream_closed", self.stream_closed) + b.connect_stream() + + @lock + @if_not_done + def stream_connect_failed(self): + self.unbind_all() + self.state = self.states.CONN_FAIL + self.on_error() + + @lock + @if_not_done + def handle_error(self, stanza): + e = stanza.get_error() + + SI_NS_error = e.get_condition(SI_NS) + if SI_NS_error is not None: + SI_NS_error = SI_NS_error.name + + error = e.get_condition() + if error is not None: + error = error.name + + if SI_NS_error == 'no-valid-streams': + #remote user didn't support our streams + self.state = self.states.CONN_FAIL + elif SI_NS_error == 'bad-profile': + #remote user doesn't support file transfer, but does support SI + self.state = self.states.CONN_FAIL + elif error == 'forbidden': + #remote user rejected the transfer + reason = e.get_text() + log.info('%r: handle_error. Setting state to CANCELLED_BY_BUDDY') + self.state = self.states.CANCELLED_BY_BUDDY + else: + #user probably doesn't support SI, let alone file transfer + self.state = self.states.CONN_FAIL + + if self.state == self.states.CONN_FAIL: + self.on_error() + + @lock + @if_not_done + def timed_out(self): + + log.info('%r: Timed out. Setting state to CONN_FAIL, calling on_error') + + self.unbind_all() + self.state = self.states.CONN_FAIL + self.on_error() + + @lock + @if_not_done + def stream_error(self): + + log.info('%r: Stream error. Setting state to CONN_FAIL_XFER, calling on_error') + + self.unbind_all() + self.close_file() + self.state = self.states.CONN_FAIL_XFER + self.on_error() + + @lock + @if_not_done + def stream_closed(self): + + log.info('%r: Stream closed. Setting state to CANCELLED_BY_BUDDY') + + self.unbind_all() + self.close_file() + + self.state = self.states.CANCELLED_BY_BUDDY + + @lock + def unbind_all(self): + if hasattr(self, 'stream'): + b = self.stream + b.unbind("stream_connected", self.transferring) + b.unbind("stream_connect_failed", self.stream_connect_failed) + b.unbind("stream_error", self.stream_error) + b.unbind("stream_closed", self.stream_closed) + + @lock + @if_not_done + def transferring(self): + self.stream.unbind("stream_connected", self.transferring) + self.state = self.states.TRANSFERRING + #DON'T REACH INTO THE STREAM OBJECT + self.chunker = NoneFileChunker(self.filestor.obj, + close_when_done = True, + progress_cb = self.on_progress) + self.stream.conn.push_with_producer(self.chunker) + self.stream.conn.close_when_done() + + @lock + @if_not_done + def cancel(self, state=None): + self.unbind_all() + if hasattr(self, 'stream'): + self.stream.cancel() + if hasattr(self, 'chunker'): + self.chunker.cancelled = True + self.close_file() + + if state is None: + state = self.states.CANCELLED_BY_YOU + self.state = state + + @lock + @if_not_done + def on_progress(self, bytes): + self._setcompleted(bytes) + if self.completed == self.size: + self.unbind_all() + self.stream.close() + self.state = self.states.FINISHED + self._ondone() + #cleanup + + def close_file(self): + try: self.chunker.fileobj.close() + except Exception: pass + try: self.filestor.obj.close() + except Exception: pass + +class SOCKS5OutBytestream(ByteStream): + + def __init__(self, sender): + ByteStream.__init__(self) + self.j = sender.j + self.jid = sender.jid + self.sid = sender.sid + self.conn = None + + def connect_stream(self): + try: #protection against None stream + self.hash = self.j.s5bserver.conn_id(self.sid, self.j.stream.me, self.jid) + except AttributeError: + self.event("stream_connect_failed") + return + self.j.s5bserver.add_hash(self.hash) + + i = Iq(to_jid=self.jid, stanza_type='set') + b = ByteStreams(sid = self.sid ) + if pref('jabber.use_direct_ft'): + if not pref('jabber.use_faulty_localhost_ips'): + [b.add_host(self.j.stream.me, h[0], h[1]) for h in self.j.s5bserver.hosts] + else: + [b.add_host(self.j.stream.me, h[0].replace("192", "129"), + h[1]) for h in self.j.s5bserver.hosts] + if pref('jabber.use_proxy_ft'): + b.hosts.extend(set(h for hosts in self.j.known_s5b_proxies.values() for h in hosts)) + if pref('jabber.use_jabber_org_proxy', True): + b.add_host("proxy.jabber.org", "208.245.212.98", 7777) + b.as_xml(i.get_node()) + + + self.j.send_cb(i, success=self.handle_ready, error=self.handle_error, timeout=self.timed_out) + + def kill_socket_hash(self): + c = self.j.s5bserver.retrieve_hash(self.hash) + if c not in (False, None): + try: + c.close() + except: pass + + def timed_out(self): + #cleanup + self.kill_socket_hash() + self.event("stream_connect_failed") + + def handle_error(self, stanza): + #cleanup + import traceback;traceback.print_exc() + self.kill_socket_hash() + self.event("stream_connect_failed") + log.info(stanza.serialize()) + + def handle_ready(self, stanza): + log.info(stanza.serialize()) + try: + b = ByteStreams(stanza.get_query()) + used_jid = b.host_used + except: + #cleanup + self.kill_socket_hash() + self.event("stream_connect_failed") + return + #debug: + if not pref('jabber.use_proxy_ft'): assert used_jid == self.j.stream.me + if not pref('jabber.use_direct_ft'): assert used_jid != self.j.stream.me +# assert used_jid != self.j.stream.me + if used_jid == self.j.stream.me: + self.conn = self.j.s5bserver.retrieve_hash(self.hash) + if self.conn not in (False, None): + self.socket_connected() + self.event("stream_connected") + else: + self.event("stream_connect_failed") + #cleanup? + else: + #cleanup socket server + self.kill_socket_hash() + + hosts = set(h for hosts in self.j.known_s5b_proxies.values() for h in hosts) + + if not (used_jid in [h.jid for h in hosts]): + self.event("stream_connect_failed") + return + + streamhost = [h for h in hosts if h.jid == used_jid] + if len(streamhost) != 1: + self.event("stream_connect_failed") + return + self.streamhost = streamhost = streamhost[0] + + #start proxy here: + #same as s5bsocket + self.conn = S5B_proxyConnect((streamhost.host, streamhost.port), + self.hash, streamhost) + self.conn.bind_event("connected", self.handle_proxy_connect) + self.conn.bind_event("connection_failed", self.socket_failed) + self.conn.get_connect() + + log.info('handle_ready done') + + def socket_failed(self): + self.unbind_all() + self.event("stream_connect_failed") + + def socket_connected(self): + self.conn.unbind("connection_failed", self.socket_failed) + self.conn.unbind("connected", self.handle_proxy_connect) + + self.conn.bind_event("socket_error", self.stream_error) + self.conn.bind_event("socket_closed", self.stream_closed) + + def stream_closed(self): + self.unbind_all() + self.event("stream_closed") + + def stream_error(self): + self.unbind_all() + self.event("stream_error") + + def unbind_all(self): + self.conn.unbind("connection_failed", self.socket_failed) + self.conn.unbind("connected", self.handle_proxy_connect) + self.conn.unbind("socket_error", self.stream_error) + self.conn.unbind("socket_closed", self.stream_closed) + + def handle_proxy_failure(self): + log.info('handle proxy failure') + #no cleanup necessary, except unbind + self.unbind_all() + self.event("stream_connect_failed") + + def handle_proxy_failure2(self): + log.info('handle proxy failure2') + #kill socket, unbind + try: + self.conn.close() + except: + import traceback;traceback.print_exc() + self.event("stream_connect_failed") + + def handle_proxy_connect(self): + log.info('handle_proxy_connect called') + self.conn.set_terminator(0) + #activate + streamhost = self.streamhost + sh_jid = streamhost.jid + targ_jid = self.jid + b = ByteStreams(None, self.sid) + b.activate = targ_jid + i = Iq(to_jid=sh_jid, stanza_type='set') + b.as_xml(i.get_node()) + self.j.send_cb(i, success=self.handle_proxy_activate, + error=self.handle_proxy_failure2, + timeout=self.proxy_activate_timeout) + + def proxy_activate_timeout(self): + #kill socket, unbind + self.unbind_all() + try: + self.conn.close() + except: + import traceback;traceback.print_exc() + self.event("stream_connect_failed") + + def handle_proxy_activate(self, stanza): + log.info('handle_proxy_activate called') + self.socket_connected() + self.event("stream_connected") + + def close(self): + self.conn.close_when_done() + #self.event("stream_closed") needs bind on socket + #cleanup + + def cancel(self): + try: + self.conn.close() + except: + pass + + +def dumpfile(conn, filestor, progress_cb): + #send + log.info('dumpfile called') + conn.push_with_producer() + conn.close_when_done() + +stream_connectors = {BYTESTREAMS_NS:SOCKS5OutBytestream} + +# return Storage( +# size = os.path.getsize(filepath), +# obj = open(filepath, 'rb'), +# modtime = filestats[stat.ST_MTIME], +# ctime = filestats[stat.ST_CTIME], +# name = os.path.split(filepath)[1], +# path = filepath, +# ) diff --git a/digsby/src/jabber/filetransfer/s5b_proxy_sender.py b/digsby/src/jabber/filetransfer/s5b_proxy_sender.py new file mode 100644 index 0000000..783159f --- /dev/null +++ b/digsby/src/jabber/filetransfer/s5b_proxy_sender.py @@ -0,0 +1,90 @@ +from struct import unpack +from jabber.filetransfer.S5BFileXferHandler import SocketEventMixin +import util +import util.primitives.structures as structures +from struct import pack +from jabber.filetransfer.socketserver import ProxyFailure +import common +from functools import partial +from logging import getLogger + +log = getLogger('S5B_proxyConnect') + + +class S5B_proxyConnect(common.socket, SocketEventMixin): + """Is a 'Socks 5 Bytestream' Outgoing connection.""" + def __init__(self, addr, hash, streamhost): + SocketEventMixin.__init__(self) + self.handle_expt = self.post_connect_expt + self.handle_error = self.post_connect_error + self.handle_close = self.post_connect_close + self.do_disconnect = self.post_connect_disconnect + self.addr = addr + self.hash = hash + self.streamhost = streamhost + self.data = None + common.socket.__init__(self) + + def get_connect(self): + self.connect(self.addr) + + def handle_connect(self): + """start the generator""" + log.info('connect to %s', self.addr) + self.proc( self.s5b_ok() ) + + def collect_incoming_data(self, data): + if not self.data: + self.data = data + else: + self.data += data + + def s5b_ok(self): + """generator conversation for S5B socket 'proxy' bytes. + This is necessary because it must be done first, and must be done quickly. + This function is really part of the socket setup, not part of the + data which flows across.""" + ok = yield (2, pack('BBB', 5,1,0)) + _head, authmethod = unpack('BB', ok) + + if authmethod != 0x00: + raise ProxyFailure() + + out = pack('!BBBBB40sH', 5,1,0,3, 40,self.hash,0) + in_fmt = ('!BBBB', 's5head', 'status', 'reserved', 'addrtype') + in_ = yield (4, out) + head = structures.unpack_named(*(in_fmt+ (in_,))) + if head.addrtype == 3: + head.addrlen = yield (1, '') + head.addrlen = ord(head.addrlen) + if head.addrlen > 0: + address = yield (head.addrlen, '') #@UnusedVariable + else: + address = '' #@UnusedVariable + _port = yield (2, '') + if head.status != 0x00: + raise ProxyFailure() + log.info('proxy ok, calling on_success') + self.event("connected") + + def proc(self, gen): + """This method runs the s5b_ok generator.""" + try: + to_read, out_bytes = gen.send(self.data) + except ProxyFailure: + self.close() + self.event("connection_failed") + return + except StopIteration: + return + except: + pass + bytes = str(out_bytes) + if out_bytes: + self.push(bytes) + self.data = '' + self.found_terminator = partial(self.proc, gen) + if isinstance(to_read, int): + self.set_terminator(to_read) + else: + self.set_terminator(to_read._size()) diff --git a/digsby/src/jabber/filetransfer/socketserver.py b/digsby/src/jabber/filetransfer/socketserver.py new file mode 100644 index 0000000..51bc6ee --- /dev/null +++ b/digsby/src/jabber/filetransfer/socketserver.py @@ -0,0 +1,298 @@ +from struct import unpack, pack +from jabber.filetransfer.S5BFileXferHandler import SocketEventMixin +import functools +import common +from socket import AF_INET, SOCK_STREAM +import AsyncoreThread +from hashlib import sha1 +from functools import partial +from threading import RLock +from util import lock, get_ips_s + +from logging import getLogger +log = getLogger('jabber.filetransfer.s5bserver') + +class ProxyFailure(StopIteration): + pass + +def this(f): + @functools.wraps(f) + def wrapper1(self,*args, **kw): + return f(self(), *args, **kw) + return wrapper1 + +class JabberS5BServerSocket(common.socket): + ''' + + ''' + _this = None + started = False + _lock = RLock() + bind_addr = ('', 0) + waiting_hashs = [] + connected_hashs = {} + + def __init__(self, addr=None): + ''' + @param addr: the address to bind to + ''' + if self() is None: + if addr is not None: + type(self).bind_addr = ('', 0) + common.socket.__init__(self, False) + type(self)._this = self + + def __call__(self): + return type(self)._this + + @this + @lock + def start(self): + ''' + turns this server on, if not already + ''' + + if not self.started: + self.started = True + self.create_socket(AF_INET, SOCK_STREAM) + try: + self.bind( self.bind_addr ) # GET IP ADDRESS + except Exception, e: + self.log_info("failed to bind on (%r, %r)" % self.bind_addr) + raise e + self.listen(5) + AsyncoreThread.start() + + @this + @lock + def stop(self): + ''' + turns this server off, if not already + ''' + + if self.started: + self.started = False + self.close() + +# @this can't be called anywhere else + def handle_accept(self): + ''' + handle incoming connection, calls _on_accept with the new socket + and its address + ''' + + log.info('handle_accept') + accepted = self.accept() + self.listen(5) + connected_socket, address = accepted + + self._on_accept(connected_socket, address) + +# @this can't be called anywhere else + def _on_accept(self, sock, addr): + ''' + function called when a new + @param sock: + @param addr: + ''' + + log.info("accept from (%r, %r)", sock, addr) + S5BIncomingSocket(sock, addr, self) + +# @this doesn't require self + def conn_id(self, stream_id, initiator_jid, target_sid): + ''' + creates the connection id used in Jabber SOCKS 5 Bytestream connections + @param stream_id: the stream id (a string) + @param initiator_jid: the stream initiator JID + @param target_sid: the stream target JID + ''' + + return sha1(stream_id + initiator_jid.as_unicode() + target_sid.as_unicode()).hexdigest() + + @this + def __del__(self): + self.stop() + superdel = getattr(common.socket, '__del__', None) + if superdel is not None: + superdel(self) + + @this + @lock + def add_hash(self, hash): + ''' + append the hash to the list of connection ids that we expect + to be incomming + @param hash: the expected connection id + ''' + + log.info('adding hash: %s', hash) + self.waiting_hashs.append(hash) + if not self.started: self.start() + + @this + @lock + def hash_waiting(self, hash, conn): + ''' + this function stores a new connection by connection id + when the connection has finished socks5 negotiation + + @param hash: the connection id + @param conn: the S5Bserver instance + ''' + + log.info('waiting hash: %s', hash) + self.waiting_hashs.remove(hash) + if not self.waiting_hashs: + self.stop() + self.connected_hashs[hash] = conn + + @this + @lock + def check_hash(self, hash): + return hash in self.connected_hashs + + @this + @lock + def retrieve_hash(self, hash): + ''' + @param hash: the connection id to retrieve + + @return False if we are still waiting for the hash + None if we know nothing about it + the S5Bserver: if everything is ok + ''' + + log.info('retrieving hash: %s', hash) + if hash in self.connected_hashs: + return self.connected_hashs.pop(hash) + else: + try: + self.waiting_hashs.remove(hash) + return False + except ValueError: + return None + finally: + if not self.waiting_hashs: + self.stop() + + #CAS: fix this to return values from the socket + @property + @this + def hosts(self): + ''' + a list of (host, port) that we are bound to. + ''' + port = self.socket.getsockname()[1] + return [(ip, port) for ip in get_ips_s()] + +class S5BIncomingSocket(common.socket, SocketEventMixin): + ''' + handle an incoming socks5 request + + ''' + def __init__(self, sock, addr, server): + SocketEventMixin.__init__(self) + common.socket.__init__(self, sock) + self.server = server + self.data = None + self.proc( self.s5b_ok() ) + + def s5b_ok(self): + ''' + input and output for the jabber socks5 process. + ''' + + greeting = yield (2, None) + + _head, num_authmethods = unpack('BB', greeting) + methods = yield(num_authmethods, '') + authmethods = unpack('%dB' % num_authmethods, methods) + + if 0 not in authmethods: + yield (0, pack('BB', 0x05, 0xFF)) + raise ProxyFailure('bad auth methods') + else: + auth = yield (4, pack('BB', 0x05, 0x00)) + + _head, tcp, reserved, type_ = unpack('4B', auth) + + if not _head == 0x05 and tcp == 0x01 \ + and reserved == 0x00 and type_ == 0x03: + raise ProxyFailure('bad address header') + + (len_,) = unpack('B', (yield (1, ''))) + addr, _port = unpack('%dsH' % len_, (yield (len_ + 2, ''))) + self.hash = addr + + if self.server.check_hash(self.hash): + raise ProxyFailure('hash already connected') + + #HAX: oh, check more stuff, but I just want it to work! + #hack: +# send 0x05 +# send 0x00 (good status) +# senc 0x00 reserved +# send 0x02 address type +# send pascal address +# send 0x0000 port number + self.collect_incoming_data = lambda _data: None + + strng = pack('=4B%dpH' % (len_+1,), 5,0,0,3,addr,0) + yield (False, strng) + + def proc(self, gen): + ''' + process s5b_ok + continue untill the generator is exhausted + + upon success, the retrieved hash is added to those waiting for more info + @param gen: + ''' + + try: + to_read, out_bytes = gen.send(self.data) + except ProxyFailure: + self.close() + return + except StopIteration: + try: + self.handle_expt = self.post_connect_expt + self.handle_error = self.post_connect_error + self.handle_close = self.post_connect_close + self.do_disconnect = self.post_connect_disconnect + self.server.hash_waiting(self.hash, self) + except ValueError: + self.close() + return + bytes = str(out_bytes) + print 'out_bytes', out_bytes, 'bytes', bytes + if out_bytes: + self.push(bytes) + self.data = '' + self.found_terminator = partial(self.proc, gen) + + if to_read is False: + log.info('found to_read is False, generator exhausted') + self.found_terminator = lambda: None + self.collect_incoming_data = lambda _data: None + self.set_terminator(0) + try: + self.handle_expt = self.post_connect_expt + self.handle_error = self.post_connect_error + self.handle_close = self.post_connect_close + self.do_disconnect = self.post_connect_disconnect + self.server.hash_waiting(self.hash, self) + except ValueError: + self.close() + elif isinstance(to_read, int): + self.set_terminator(to_read) + else: + self.set_terminator(to_read._size()) + + def collect_incoming_data(self, data): + self.data += data + + def __del__(self): + self.close() + common.socket.__del__(self) diff --git a/digsby/src/jabber/idle_loop.py b/digsby/src/jabber/idle_loop.py new file mode 100644 index 0000000..536a54f --- /dev/null +++ b/digsby/src/jabber/idle_loop.py @@ -0,0 +1,21 @@ +from util import default_timer, TimeOut + +class IdleLoopTimer(TimeOut): + def __init__(self, seconds, func, *a, **k): + self.seconds = seconds + self.func = func + self.a = a + self.k = k + TimeOut.__init__(self) + + def start(self): + self.done_at = default_timer() + self.seconds + TimeOut.start(self) + + def compute_timeout(self): + self._last_computed = self.done_at - default_timer() + return self._last_computed + + def process(self): + self.func(*self.a, **self.k) + self.done_at = default_timer() + self.seconds \ No newline at end of file diff --git a/digsby/src/jabber/jabber_util/__init__.py b/digsby/src/jabber/jabber_util/__init__.py new file mode 100644 index 0000000..3ae8dba --- /dev/null +++ b/digsby/src/jabber/jabber_util/__init__.py @@ -0,0 +1 @@ +from functions import * \ No newline at end of file diff --git a/digsby/src/jabber/jabber_util/functions.py b/digsby/src/jabber/jabber_util/functions.py new file mode 100644 index 0000000..4e7b555 --- /dev/null +++ b/digsby/src/jabber/jabber_util/functions.py @@ -0,0 +1,26 @@ +from pyxmpp.xmlextra import common_doc, COMMON_NS + +def xpath_eval(node,expr,namespaces=None): + """Evaluate an XPath expression on the stanza XML node. + + The expression will be evaluated in context where the common namespace + (the one used for stanza elements, mapped to 'jabber:client', + 'jabber:server', etc.) is bound to prefix "ns" and other namespaces are + bound accordingly to the `namespaces` list. + + :Parameters: + - `expr`: XPath expression. + - `namespaces`: mapping from namespace prefixes to URIs. + :Types: + - `expr`: `unicode` + - `namespaces`: `dict` or other mapping + """ + ctxt = common_doc.xpathNewContext() #@UndefinedVariable + ctxt.setContextNode(node) + ctxt.xpathRegisterNs("ns",COMMON_NS) + if namespaces: + for prefix,uri in namespaces.items(): + ctxt.xpathRegisterNs(unicode(prefix),uri) + ret=ctxt.xpathEval(unicode(expr)) + ctxt.xpathFreeContext() + return ret \ No newline at end of file diff --git a/digsby/src/jabber/objects/__init__.py b/digsby/src/jabber/objects/__init__.py new file mode 100644 index 0000000..782cc45 --- /dev/null +++ b/digsby/src/jabber/objects/__init__.py @@ -0,0 +1,8 @@ +import bytestreams +import si +import si_filetransfer +import iq_privacy +import vcard_avatar +import nick +import chatstates +import x_event \ No newline at end of file diff --git a/digsby/src/jabber/objects/bytestreams.py b/digsby/src/jabber/objects/bytestreams.py new file mode 100644 index 0000000..6feaf41 --- /dev/null +++ b/digsby/src/jabber/objects/bytestreams.py @@ -0,0 +1,284 @@ +from pprint import pprint +from pyxmpp.utils import from_utf8 +from pyxmpp.utils import to_utf8 +import libxml2 +from pyxmpp.jid import JID +from pyxmpp.jabber.disco import DiscoCacheFetcherBase +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri + +BYTESTREAMS_NS="http://jabber.org/protocol/bytestreams" +BYTESTREAMS_UDP_NS=BYTESTREAMS_NS+"#udp" + +class StreamHost(StanzaPayloadObject): + """A element of bytestreams reply. + + :Ivariables: + - `jid`: the JID of the StreamHost. + - `host`: the hostname or IP address of the StreamHost. + - `port`: the port associated with the host + - `zeroconf`: the zeroconf identifier (should be `_jabber.bytestreams`) + - `xmlnode`: XML element describing the StreamHost. + :Types: + - `jid`: `JID` + - `host`: `unicode` + - `port`: `int` + - `zeroconf`: `unicode` + - `xmlnode`: `libxml2.xmlNode` + """ + xml_element_name = 'streamhost' + xml_element_namespace = BYTESTREAMS_NS + def __init__(self, xmlnode_or_jid, host=None, port=None, zeroconf=None): + """Initialize an `StreamHost` object. + + Wrap an existing streamhost XML element or create a new one. + + :Parameters: + - `xmlnode_or_jid`: XML element describing the StreamHost or the JID of + the StreamHost. + - `host`: the hostname or IP address of the StreamHost. + - `port`: the port of the StreamHost + - `zeroconf`: the zeroconf identifier of the StreamHost. + :Types: + - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode` + - `host`: `unicode` + - `port`: `int` + - `zeroconf`: `unicode` + """ + if isinstance(xmlnode_or_jid,libxml2.xmlNode): + self.from_xml(xmlnode_or_jid) + else: + self.jid = JID(xmlnode_or_jid) + self.host = host + self.port = port + self.zeroconf = zeroconf + if not (bool(self.port) ^ bool(self.zeroconf)): + raise ValueError, 'StreamHost element requires one of [port, zeroconf]' + + def from_xml(self,node): + #need jid, host, port, zeroconf + """Initialize StreamHost from XML node.""" + if node.type!="element": + raise ValueError,"XML node is not a streamhost (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s descriptor" % self.xml_element_name + jid=JID(node.prop("jid").decode("utf-8")) + self.jid=jid + + host=node.prop("host").decode("utf-8") + self.host=host + + port=node.prop("port") + #py2.5: + self.port = int(port.decode("utf-8")) if port else None + #py2.4: +# if port: +# self.port = int(port.decode("utf-8")) +# else: +# self.port = None + zeroconf=node.prop("zeroconf") + #py2.5: + self.zeroconf = zeroconf.decode("utf-8") if zeroconf else None + #py2.4 +# if zeroconf: +# self.zeroconf=zeroconf.decode("utf-8") +# else: +# self.zeroconf=None + def __eq__(self, other): + if other is self: + return True + if not isinstance(other, self.__class__): + return False + return self.jid == other.jid and self.host == other.host and \ + self.port == other.port and self.zeroconf == other.zeroconf + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.jid, self.host, self.port, self.zeroconf)) + + def complete_xml_element(self, xmlnode, _unused): + """Complete the XML node with `self` content. + + Should be overriden in classes derived from `StanzaPayloadObject`. + + :Parameters: + - `xmlnode`: XML node with the element being built. It has already + right name and namespace, but no attributes or content. + - `_unused`: document to which the element belongs. + :Types: + - `xmlnode`: `libxml2.xmlNode` + - `_unused`: `libxml2.xmlDoc`""" + xmlnode.setProp("jid",JID(self.jid).as_utf8()) + xmlnode.setProp("host", unicode(self.host).encode("utf-8")) + if self.port: + xmlnode.setProp("port", unicode(self.port).encode("utf-8")) + if self.zeroconf: + xmlnode.setProp("zeroconf", unicode(self.zeroconf).encode("utf-8")) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + +class ByteStreams(StanzaPayloadObject): + """A bytestreams response object. + + :Ivariables: + - `node`: node name of the bytestreams element (cached). + - `hosts`: streamhosts in the bytestreams object. + - `xmlnode`: XML element listing the items. + :Types: + - `node`: `unicode` + - `hosts`: `list` of `StreamHost` + - `xmlnode`: `libxml2.xmlNode` + """ + xml_element_name = 'query' + xml_element_namespace = BYTESTREAMS_NS + #CAS: bytestreams#udp not implemented + def __init__(self,xmlnode_or_node=None, sid=None, mode=None): + """Initialize an `ByteStreams` object. + + Wrap an existing bytestreams XML element or create a new one. + + :Parameters: + - `xmlnode_or_node`: XML node to be wrapped into `self` or an item + node name. + :Types: + - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode` + """ + self.hosts = [] + self.sid = sid + self.mode = mode + self.activate = None + self.host_used = None + if isinstance(xmlnode_or_node, libxml2.xmlNode): + self.from_xml(xmlnode_or_node) + else: + self.node = xmlnode_or_node + + def from_xml(self,node,strict=True): + """ + Initialize Roster object from XML node. + + If `strict` is False, than invalid items in the XML will be ignored. + """ + node_=node.prop("node") + sid=node.prop("sid") + self.sid = from_utf8(sid) if sid else None + mode=node.prop("mode") + self.mode = from_utf8(mode) if mode else None + self.mode = self.mode if self.mode != 'tcp' else None + if node_: + self.node = node_.decode("utf-8") + else: + self.node = None + if node.type!="element": + raise ValueError,"XML node is not a bytestreams (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=BYTESTREAMS_NS or node.name!="query": + raise ValueError,"XML node is not a bytestreams query" + n=node.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=BYTESTREAMS_NS: + n=n.next + continue + if n.name=="streamhost": + try: + self.add_host(n) + except ValueError: + if strict: + raise + elif n.name=="streamhost-used": + host_used=JID(n.prop("jid").decode("utf-8")) + self.host_used=host_used + elif n.name=="activate": + activate=JID(n.getContent()) + self.activate=activate + n=n.next + + def complete_xml_element(self, xmlnode, _unused): + """Complete the XML node with `self` content. + + Should be overriden in classes derived from `StanzaPayloadObject`. + + :Parameters: + - `xmlnode`: XML node with the element being built. It has already + right name and namespace, but no attributes or content. + - `_unused`: document to which the element belongs. + :Types: + - `xmlnode`: `libxml2.xmlNode` + - `_unused`: `libxml2.xmlDoc`""" + if self.node: + xmlnode.setProp("node", to_utf8(self.node)) + if self.sid: + xmlnode.setProp("sid", to_utf8(self.sid)) + if self.mode and self.mode != 'tcp': + xmlnode.setProp("mode", to_utf8(self.mode)) + for host in self.hosts: + try: + host.as_xml(xmlnode, _unused) + except: + pprint(host) + raise + if self.activate: + xmlnode.newChild(None, "activate", JID(self.activate).as_utf8()) + if self.host_used: + h = xmlnode.newChild(None, "streamhost-used", None) + h.setProp("jid",JID(self.host_used).as_utf8()) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + + def add_host(self,streamhost_jid, host=None, port=None, zeroconf=None): + """Add a StreamHost to the `ByteStreams` object. + + :Parameters: + - `streamhost_jid`: jid of the streamhost. + - `host`: hostname of the streamhost. + - `port`: the port associated with the host + - `zeroconf`: the zeroconf identifier (should be `_jabber.bytestreams`) + :Types: + - `streamhost_jid`: `JID` + - `host`: `unicode` + - `port`: `int` + - `zeroconf`: `unicode` + + :returns: the streamhost created. + :returntype: `StreamHost`""" + sh = StreamHost(streamhost_jid, host, port, zeroconf) + self.hosts.append(sh) + return sh + +def register_streamhost_cache_fetchers(cache_suite,stream): + tmp=stream + class ByteStreamsCacheFetcher(DiscoCacheFetcherBase): + stream=tmp + disco_class=ByteStreams + __response = DiscoCacheFetcherBase._DiscoCacheFetcherBase__response #@UndefinedVariable + __error = DiscoCacheFetcherBase._DiscoCacheFetcherBase__error #@UndefinedVariable + __timeout = DiscoCacheFetcherBase._DiscoCacheFetcherBase__timeout #@UndefinedVariable + def fetch(self): + """Initialize the Service Discovery process.""" + from pyxmpp.iq import Iq + jid,node = self.address + iq = Iq(to_jid = jid, stanza_type = "get") + disco = self.disco_class(node) + disco.as_xml(iq.xmlnode) #only line that was changed + self.stream.set_response_handlers(iq,self.__response, self.__error, + self.__timeout) + self.stream.send(iq) + """Cache fetcher for ByteStreams.""" + cache_suite.register_fetcher(ByteStreams,ByteStreamsCacheFetcher) diff --git a/digsby/src/jabber/objects/chatstates.py b/digsby/src/jabber/objects/chatstates.py new file mode 100644 index 0000000..e7dbac9 --- /dev/null +++ b/digsby/src/jabber/objects/chatstates.py @@ -0,0 +1,38 @@ +import libxml2 +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri + +CHATSTATES_NS = "http://jabber.org/protocol/chatstates" + +VALID_CHATSTATES = ["active", "composing", "gone", "inactive", "paused"] + +class ChatState(StanzaPayloadObject): + xml_element_namespace = CHATSTATES_NS + + def __init__(self, xmlnode_or_type): + if isinstance(xmlnode_or_type,libxml2.xmlNode): + self.from_xml(xmlnode_or_type) + else: + self.xml_element_name = xmlnode_or_type + assert self.valid_state() + + def valid_state(self): + return self.xml_element_name in VALID_CHATSTATES + + def from_xml(self, xmlnode): + self.xml_element_name = xmlnode.name + if xmlnode.type!="element": + raise ValueError,"XML node is not a chat state (not en element)" + ns=get_node_ns_uri(xmlnode) + if ns and ns!=self.xml_element_namespace: + raise ValueError,"XML node is not a chat state descriptor" + if not self.valid_state(): + import warnings + warnings.warn("XML node with name: %r is not a valid chat state" % self.xml_element_name) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r diff --git a/digsby/src/jabber/objects/gmail/__init__.py b/digsby/src/jabber/objects/gmail/__init__.py new file mode 100644 index 0000000..d7f2dcf --- /dev/null +++ b/digsby/src/jabber/objects/gmail/__init__.py @@ -0,0 +1,7 @@ +from pyxmpp.iq import Iq +GOOGLE_MAIL_NOTIFY_NS ='google:mail:notify' + +def make_get(gtalk_protocol): + iq = Iq(stanza_type="get") + _q = iq.new_query(GOOGLE_MAIL_NOTIFY_NS) + return iq diff --git a/digsby/src/jabber/objects/gmail/mail_thread_info.py b/digsby/src/jabber/objects/gmail/mail_thread_info.py new file mode 100644 index 0000000..da68786 --- /dev/null +++ b/digsby/src/jabber/objects/gmail/mail_thread_info.py @@ -0,0 +1,50 @@ +#tid The thread id of this thread. +#participation A number indicating the user's participation level in this thread: 0 indicates that the user has not participated; 1 indicates that the user is one of many recipients listed in the thread; 2 indicates that the user is the sole recipient for messages in this thread. +#messages The number of messages in the thread. +#date A timestamp of the most recent message, in milliseconds since the UNIX epoch. +#url The URL linking to this thread +# +# +# +# +# +from jabber.objects.gmail.senders import Senders +from pyxmpp.utils import from_utf8 +from jabber.jabber_util.functions import xpath_eval +from pyxmpp.xmlextra import get_node_ns_uri +from jabber.objects.gmail import GOOGLE_MAIL_NOTIFY_NS +from pyxmpp.objects import StanzaPayloadObject + +class MailThreadInfo(StanzaPayloadObject): + xml_element_name = 'mail-thread-info' + xml_element_namespace = GOOGLE_MAIL_NOTIFY_NS + + def __init__(self, xmlnode): + self.__from_xml(xmlnode) + + def __from_xml(self, node): + if node.type!="element": + raise ValueError,"XML node is not a %s element (not en element)" % self.xml_element_name + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not an %s element" % self.xml_element_name + + labelss = xpath_eval(node, 'g:labels',{'g':GOOGLE_MAIL_NOTIFY_NS}) + labels = labelss[0].getContent() if labelss else None + self.labels = from_utf8(labels).split('|') if labels else [] + + senderss = xpath_eval(node, 'g:senders',{'g':GOOGLE_MAIL_NOTIFY_NS}) + self.senders = Senders(senderss[0]) if senderss else [] + + subjects = xpath_eval(node, 'g:subject',{'g':GOOGLE_MAIL_NOTIFY_NS}) + self.subject = from_utf8(subjects[0].getContent()) if subjects else None + + snippets = xpath_eval(node, 'g:snippet',{'g':GOOGLE_MAIL_NOTIFY_NS}) + self.snippet = from_utf8(snippets[0].getContent()) if snippets else None + + self.tid = int(from_utf8(node.prop("tid"))) + self.participation = int(from_utf8(node.prop("participation"))) + self.messages = int(from_utf8(node.prop("messages"))) + self.date = int(from_utf8(node.prop("date"))) + self.url = from_utf8(node.prop("date")) + diff --git a/digsby/src/jabber/objects/gmail/mailbox.py b/digsby/src/jabber/objects/gmail/mailbox.py new file mode 100644 index 0000000..75ffb60 --- /dev/null +++ b/digsby/src/jabber/objects/gmail/mailbox.py @@ -0,0 +1,38 @@ +from jabber.objects.gmail.mail_thread_info import MailThreadInfo +from pyxmpp.utils import from_utf8 +from jabber.jabber_util.functions import xpath_eval +from pyxmpp.xmlextra import get_node_ns_uri +from jabber.objects.gmail import GOOGLE_MAIL_NOTIFY_NS +from pyxmpp.objects import StanzaPayloadObject + + +#result-time The time these results were generated, in milliseconds since the UNIX epoch. This value should be cached and sent as the newer-than-time attribute in the next email query. +#total-matched The number of emails that matched the q attribute search string in the email query, or the number of unread emails if no query was specified. If total-estimate is 1, this will be just an estimate of the number of emails retrieved. +#total-estimate A number indicating whether total-matched is just an estimate: 1 indicates it is; 0 or omitted indicates that it is not. +#url The URL of the Gmail inbox. +#contains n mail-thread-info + +class Mailbox(StanzaPayloadObject, list): + xml_element_name = 'mailbox' + xml_element_namespace = GOOGLE_MAIL_NOTIFY_NS + + def __init__(self, xmlnode): + self.__from_xml(xmlnode) + + def __from_xml(self, node): + if node.type!="element": + raise ValueError,"XML node is not a %s element (not en element)" % self.xml_element_name + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not an %s element" % self.xml_element_name + + self.result_time = int(from_utf8(node.prop("result-time"))) + self.total_matched = int(from_utf8(node.prop("total-matched"))) + self.url = from_utf8(node.prop("url")) + + total_estimate = node.prop("messages") + self.total_estimate = int(from_utf8(total_estimate)) if total_estimate else 0 + + threads = xpath_eval(node, 'g:mail-thread-info',{'g':GOOGLE_MAIL_NOTIFY_NS}) + self.extend(MailThreadInfo(thread) for thread in threads) + diff --git a/digsby/src/jabber/objects/gmail/sender.py b/digsby/src/jabber/objects/gmail/sender.py new file mode 100644 index 0000000..102fb87 --- /dev/null +++ b/digsby/src/jabber/objects/gmail/sender.py @@ -0,0 +1,34 @@ +from jabber.objects.gmail import GOOGLE_MAIL_NOTIFY_NS +from pyxmpp.utils import from_utf8 +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import get_node_ns_uri + +#address The email address of the sender. +#name The display name of the sender. +#originator A number indicating whether this sender originated this thread: 1 means that this person originated this thread; 0 or omitted means that another person originated this thread. +#unread A number indicating whether or not the thread includes an unread message: 1 means yes; 0 or omitted means no. + + +class Sender(StanzaPayloadObject): + xml_element_name = 'sender' + xml_element_namespace = GOOGLE_MAIL_NOTIFY_NS + + def __init__(self, xmlnode): + self.__from_xml(xmlnode) + + def __from_xml(self,node): + if node.type!="element": + raise ValueError,"XML node is not a %s element (not en element)" % self.xml_element_name + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not an %s element" % self.xml_element_name + + self.name = from_utf8(node.prop("name")) + self.address = from_utf8(node.prop("address")) + + originator = node.prop("originator") + self.originator = int(from_utf8(originator)) if originator else 0 + + unread = node.prop("unread") + self.unread = int(from_utf8(unread)) if unread else 0 + diff --git a/digsby/src/jabber/objects/gmail/senders.py b/digsby/src/jabber/objects/gmail/senders.py new file mode 100644 index 0000000..fa15c8e --- /dev/null +++ b/digsby/src/jabber/objects/gmail/senders.py @@ -0,0 +1,26 @@ +from jabber.objects.gmail.sender import Sender +from jabber.jabber_util.functions import xpath_eval +from pyxmpp.xmlextra import get_node_ns_uri +from jabber.objects.gmail import GOOGLE_MAIL_NOTIFY_NS +from pyxmpp.objects import StanzaPayloadObject + +#senders + +#contains n sender + + +class Senders(StanzaPayloadObject, list): + xml_element_name = 'senders' + xml_element_namespace = GOOGLE_MAIL_NOTIFY_NS + + def __init__(self, xmlnode): + self.__from_xml(xmlnode) + + def __from_xml(self, node): + if node.type!="element": + raise ValueError,"XML node is not a %s element (not en element)" % self.xml_element_name + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not an %s element" % self.xml_element_name + senders = xpath_eval(node, 's:sender',{'s':GOOGLE_MAIL_NOTIFY_NS}) + self.extend(Sender(sender) for sender in senders) diff --git a/digsby/src/jabber/objects/iq_browse.py b/digsby/src/jabber/objects/iq_browse.py new file mode 100644 index 0000000..788aebc --- /dev/null +++ b/digsby/src/jabber/objects/iq_browse.py @@ -0,0 +1,21 @@ + + + +IQ_BROWSE_NS = "jabber:iq:browse" + +CATEGORIES = {"application":["bot", "calendar", "editor", + "fileserver", "game", "whiteboard"], + "conference" :["irc", "list", "private", + "public", "topic", "url"], + "headline" :["logger", "notice", "rss", "stock"], + "keyword" :["dictionary", "dns", "software", + "thesaurus", "web", "whois"], + "render" :["en2fr", "*2*", "tts"], + "service" :["aim", "icq", "irc", "jabber", + "jud","msn", "pager", "serverlist", + "sms", "smtp", "yahoo"], + "user" :["client", "forward", "inbox", + "portable", "voice"], + "validate" :["grammar", "spell", "xml"]} + + diff --git a/digsby/src/jabber/objects/iq_privacy.py b/digsby/src/jabber/objects/iq_privacy.py new file mode 100644 index 0000000..30a7c58 --- /dev/null +++ b/digsby/src/jabber/objects/iq_privacy.py @@ -0,0 +1,181 @@ +from pyxmpp.iq import Iq +import libxml2 +import util +from pyxmpp.utils import to_utf8 +from pyxmpp.xmlextra import get_node_ns_uri +from pyxmpp.xmlextra import common_doc +from pyxmpp.objects import StanzaPayloadObject + +PRIVACY_NS = "jabber:iq:privacy" +PRIVACY_TYPES = ("message", "presence-in", "presence-out", "iq") +PRIVACY_ATTRS = ("type", "value", "action","order") + +class ListItem(StanzaPayloadObject): + xml_element_name = "item" + xml_element_namespace = PRIVACY_NS + + def __init__(self, xmlnode=None, type=None, value=None, + action=None, order=0, + message=False, presence_in=False, + presence_out=False, iq=False): + + self.type = type + self.value = value + + self.action = action + self.order = order + + self.message = message + self.presence_in = presence_in + self.presence_out = presence_out + self.iq = iq + + self.__from_xml(xmlnode) if xmlnode else None + assert self.action in ("allow", "deny") + assert self.order + if self.type is not None: assert self.type in ('group', 'jid', 'subscription') + +# @property +# def messages_blocked(self): +# return self.message or self.all_blocked() + def __from_xml(self, xmlnode): + """Initialize ListItem from XML node.""" + if xmlnode.type!="element": + raise ValueError,"XML node is not a list item (not en element)" + ns=get_node_ns_uri(xmlnode) + if ns and ns!=PRIVACY_NS or xmlnode.name!="item": + raise ValueError,"XML node is not a list item" + + [setattr(self, x, xmlnode.prop(x)) for x in PRIVACY_ATTRS] + self.order = int(self.order) if self.order else 0 + + n=xmlnode.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=PRIVACY_NS or n.name not in PRIVACY_TYPES: + n=n.next + continue + setattr(self, util.pythonize(n.name), True) + n=n.next + + def all_blocked(self): + return all(self.message,self.presence_in,self.presence_out,self.iq) or \ + not any(self.message,self.presence_in,self.presence_out,self.iq) + + def complete_xml_element(self, xmlnode, doc): + xmlnode.setProp("type", self.type) if self.type else None + xmlnode.setProp("value", self.value) if self.value else None + xmlnode.setProp("action", self.action) + xmlnode.setProp("order", to_utf8(self.order)) + + [xmlnode.newChild(None, child, None) + for child in PRIVACY_TYPES + if getattr(self, util.pythonize(child))] + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + +class List(StanzaPayloadObject, list): + xml_element_name = "list" + xml_element_namespace = PRIVACY_NS + + def __init__(self, xmlnode_or_name): + list.__init__(self) + if isinstance(xmlnode_or_name,libxml2.xmlNode): + self.__from_xml(xmlnode_or_name) + else: + self.name = xmlnode_or_name + + def __from_xml(self, xmlnode): + """Initialize List from XML node.""" + if xmlnode.type!="element": + raise ValueError,"XML node is not a list (not en element)" + ns=get_node_ns_uri(xmlnode) + if ns and ns!=PRIVACY_NS or xmlnode.name!="list": + raise ValueError,"XML node is not a list" + self.name = xmlnode.prop("name") + n=xmlnode.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=PRIVACY_NS or n.name != "item": + n=n.next + continue + self.append(ListItem(n)) + n=n.next + + def complete_xml_element(self, xmlnode, doc): + xmlnode.setProp("name", self.name) + [item.as_xml(xmlnode, doc) for item in self] + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + +class Privacy(StanzaPayloadObject, list): + xml_element_name = 'query' + xml_element_namespace = PRIVACY_NS + + def __init__(self, xmlnode=None): + self.active = None + self.default = None + + if xmlnode is not None: + self.__from_xml(xmlnode) + + def __from_xml(self, xmlnode): + if xmlnode.type!="element": + raise ValueError,"XML node is not a Privacy (not en element)" + ns=get_node_ns_uri(xmlnode) + if ns and ns!=PRIVACY_NS or xmlnode.name!="query": + raise ValueError,"XML node is not a query" + n=xmlnode.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=PRIVACY_NS or n.name not in ("active", "default", "list"): + n=n.next + continue + if n.name == "active": + self.active = n.prop("name")if libxml2.xmlNode.hasProp("name") else None + elif n.name == "default": + self.default = n.prop("default")if libxml2.xmlNode.hasProp("default") else None + else: + self.append(List(n)) + n = n.next + + + def complete_xml_element(self, xmlnode, doc): + if self.active is not None: + active = xmlnode.newChild(None, "active", None) + if self.active: active.setProp("name", self.active) + if self.default is not None: + default = xmlnode.newChild(None, "default", None) + if self.default: default.setProp("name", self.default) + for list_ in self: + list_.as_xml(xmlnode, doc) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + + + + diff --git a/digsby/src/jabber/objects/nick.py b/digsby/src/jabber/objects/nick.py new file mode 100644 index 0000000..8b58a0c --- /dev/null +++ b/digsby/src/jabber/objects/nick.py @@ -0,0 +1,34 @@ +import libxml2 +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri +from pyxmpp.utils import to_utf8, from_utf8 + +NICK_NS = 'http://jabber.org/protocol/nick' + +class Nick(StanzaPayloadObject): + xml_element_name = 'nick' + xml_element_namespace = NICK_NS + + def __init__(self, xmlnode_or_nick): + if isinstance(xmlnode_or_nick,libxml2.xmlNode): + self.from_xml(xmlnode_or_nick) + else: + self.nick = xmlnode_or_nick + + def from_xml(self,node): + if node.type!="element": + raise ValueError,"XML node is not a nick (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s descriptor" % self.xml_element_name + self.nick = from_utf8(node.getContent()) + + def complete_xml_element(self, xmlnode, _unused): + xmlnode.addContent(to_utf8(self.nick)) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r \ No newline at end of file diff --git a/digsby/src/jabber/objects/shared_status/__init__.py b/digsby/src/jabber/objects/shared_status/__init__.py new file mode 100644 index 0000000..77bb614 --- /dev/null +++ b/digsby/src/jabber/objects/shared_status/__init__.py @@ -0,0 +1,8 @@ +from pyxmpp.iq import Iq +SHARED_STATUS_NS = 'google:shared-status' + +def make_get(gtalk_protocol): + iq = Iq(stanza_type="get") + q = iq.new_query(SHARED_STATUS_NS) + q.setProp("version", '2') + return iq diff --git a/digsby/src/jabber/objects/shared_status/status.py b/digsby/src/jabber/objects/shared_status/status.py new file mode 100644 index 0000000..662bbd0 --- /dev/null +++ b/digsby/src/jabber/objects/shared_status/status.py @@ -0,0 +1,111 @@ +from jabber.objects.shared_status import SHARED_STATUS_NS +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.iq import Iq +import jabber +import libxml2 +import util +from util.primitives.error_handling import try_this as try_ + +class StatusList(StanzaPayloadObject, list): + show = None + xml_element_name = "status-list" + xml_element_namespace = SHARED_STATUS_NS + + def __init__(self, xmlnode_or_sequence, show=None): + list.__init__(self) + if isinstance(xmlnode_or_sequence, libxml2.xmlNode): + self.__from_xml(xmlnode_or_sequence) + else: + if show: + self.show = show + self[:] = xmlnode_or_sequence + + def complete_xml_element(self, xmlnode, doc): + if self.show is not None: + xmlnode.setProp("show", self.show.encode("utf-8")) + + for status in self: + xmlnode.newTextChild(None, "status", status.encode("utf-8")) + + def __from_xml(self, xmlnode, *a, **k): + show_l = jabber.jabber_util.xpath_eval(xmlnode, "@show", namespaces={'gss': SHARED_STATUS_NS}) + if show_l: + show = show_l[0] + self.show = show.getContent().decode('utf-8') + + self[:] = (node.getContent().decode('utf-8') for node in + jabber.jabber_util.xpath_eval(xmlnode, "gss:status/text()", namespaces={'gss': SHARED_STATUS_NS})) + +class SharedStatus(StanzaPayloadObject, util.primitives.mapping.odict): + xml_element_name = 'query' + xml_element_namespace = SHARED_STATUS_NS + version = 2 + + status_max = None + status_list_max = None + status_list_contents_max = None + status_min_ver = None + + invisible = None + + def __init__(self, xmlnode): + util.primitives.mapping.odict.__init__(self) + if isinstance(xmlnode,libxml2.xmlNode): + self.__from_xml(xmlnode) + else: + #not sure + pass + + def __from_xml(self, xmlnode, *a, **k): + sls = [StatusList(node) for node in + jabber.jabber_util.xpath_eval(xmlnode, "gss:query/gss:status-list[@show]", + namespaces={'gss': SHARED_STATUS_NS})] + self.update((l.show, l) for l in sls) + + status = jabber.jabber_util.xpath_eval(xmlnode, "gss:query/gss:status/text()", + namespaces={'gss': SHARED_STATUS_NS}) + show = jabber.jabber_util.xpath_eval(xmlnode, "gss:query/gss:show/text()", + namespaces={'gss': SHARED_STATUS_NS}) + self.status = status[0].getContent() if status else None + self.show = show[0].getContent() if show else None + query_l = jabber.jabber_util.xpath_eval(xmlnode, "gss:query", + namespaces={'gss': SHARED_STATUS_NS}) + query = query_l[0] if query_l else None + + if query is not None: + self.status_max = try_((lambda: int(query.prop("status-max"))), None) + self.status_list_max = try_((lambda: int(query.prop("status-list-max"))), None) + self.status_list_contents_max = try_((lambda: int(query.prop("status-list-contents-max"))), None) + self.status_min_ver = try_((lambda: int(query.prop("status-min-ver"))), None) + + invis = jabber.jabber_util.xpath_eval(xmlnode, "gss:query/gss:invisible/@value", + namespaces={'gss': SHARED_STATUS_NS}) + self.node = xmlnode + self.invisible = invis[0].getContent() == 'true' if invis else None + + def complete_xml_element(self, xmlnode, doc): + xmlnode.setProp("version", unicode(self.version).encode('utf-8')) + + if self.status is not None: + xmlnode.newTextChild(None, 'status', self.status[:self.status_max or None].encode('utf-8')) + if self.show is not None: + xmlnode.newTextChild(None, 'show', self.show.encode('utf-8')) + else: + xmlnode.newTextChild(None, 'show', u'default'.encode('utf-8')) + + for status_list in self.values(): + status_list.as_xml(xmlnode, doc) + + for attr in ("status-max", "status-list-max", "status-list-contents-max", "status-min-ver"): + val = getattr(self, attr.replace('-', '_'), None) + if val is not None: + xmlnode.setProp(attr, str(val)) + + invis = xmlnode.newChild(None, 'invisible', None) + invis.setProp('value', str(bool(self.invisible)).lower()) + + def make_push(self, gtalk_protocol): + iq=Iq(stanza_type="set") + self.as_xml(parent=iq.xmlnode) + return iq + diff --git a/digsby/src/jabber/objects/si.py b/digsby/src/jabber/objects/si.py new file mode 100644 index 0000000..2e6891f --- /dev/null +++ b/digsby/src/jabber/objects/si.py @@ -0,0 +1,41 @@ +from pyxmpp.xmlextra import get_node_ns_uri +from pyxmpp.utils import to_utf8 +import libxml2 +from pyxmpp.objects import StanzaPayloadObject + +SI_NS = 'http://jabber.org/protocol/si' + + +class SI(StanzaPayloadObject): + xml_element_name = 'si' + xml_element_namespace = SI_NS + + def __init__(self, xmlnode_or_id=None, mime_type=None, profile_ns=None): + if isinstance(xmlnode_or_id,libxml2.xmlNode): + self.__from_xml(xmlnode_or_id) + else: + self.sid = xmlnode_or_id + self.mime_type = mime_type + self.profile_ns = profile_ns + + def __from_xml(self, node): + if node.type!="element": + raise ValueError,"XML node is not an si (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=SI_NS or node.name!=self.xml_element_name: + raise ValueError,"XML node is not an si" + sid = node.prop("id") + self.sid = to_utf8(sid) if sid else None + mime_type = node.prop("mime-type") + self.mime_type = to_utf8(mime_type) if mime_type else None + profile_ns = node.prop("profile") + self.profile_ns = to_utf8(profile_ns) if profile_ns else None + + def complete_xml_element(self, xmlnode, _unused): + xmlnode.setProp("id",to_utf8(self.sid)) + xmlnode.setProp("mime-type",to_utf8(str(self.mime_type))) if self.mime_type else None + xmlnode.setProp("profile",to_utf8(self.profile_ns)) if self.profile_ns else None + + @classmethod + def from_iq(cls, iq): + return cls(iq.xpath_eval('si:si',{'si':SI_NS})[0]) diff --git a/digsby/src/jabber/objects/si_filetransfer.py b/digsby/src/jabber/objects/si_filetransfer.py new file mode 100644 index 0000000..daa42c3 --- /dev/null +++ b/digsby/src/jabber/objects/si_filetransfer.py @@ -0,0 +1,245 @@ +from pyxmpp.jabber.dataforms import DATAFORM_NS +from pyxmpp.jabber.dataforms import Form +from pyxmpp.jabber import dataforms +from pyxmpp.utils import to_utf8 +from pyxmpp.xmlextra import common_doc +from pyxmpp.utils import from_utf8 +from pyxmpp.xmlextra import get_node_ns_uri +import libxml2 + +from si import * #@UnusedWildImport +SI_FILETRANSFER_NS = SI_NS + "/profile/file-transfer" +FEATURE_NEG_NS = "http://jabber.org/protocol/feature-neg" + +class SI_FileTransfer(SI): + ''' + Stream initiation file transfer stanza. + ''' + + def __init__(self, xmlnode_or_id=None, mime_type=None): + ''' + + :Parameters: + - `xmlnode_or_id`: + - `mime_type`: + :Types: + - `xmlnode_or_id`: `libxml2.xmlNode` or `unicode` + - `mime_type`: `unicode` + ''' + + SI.__init__(self, xmlnode_or_id=xmlnode_or_id, mime_type=mime_type, + profile_ns=SI_FILETRANSFER_NS) + self.file = None + self.feature = None + if isinstance(xmlnode_or_id,libxml2.xmlNode): + self.from_xml(xmlnode_or_id) + + def from_xml(self,node,strict=True): + """Initialize SI_FileTransfer from XML node.""" + n=node.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=SI_FILETRANSFER_NS and ns!=FEATURE_NEG_NS: + n=n.next + continue + if n.name=="file": + try: + self.file = File(n) + except ValueError: + if strict: + raise + elif n.name =="feature": + try: + self.feature = Feature(n) + except ValueError: + if strict: + raise + n=n.next + + def complete_xml_element(self, xmlnode, _unused): + """Complete the XML node with `self` content. + + Should be overriden in classes derived from `StanzaPayloadObject`. + + :Parameters: + - `xmlnode`: XML node with the element being built. It has already + right name and namespace, but no attributes or content. + - `_unused`: document to which the element belongs. + :Types: + - `xmlnode`: `libxml2.xmlNode` + - `_unused`: `libxml2.xmlDoc`""" + SI.complete_xml_element(self, xmlnode, _unused) + self.file.as_xml(xmlnode, _unused) if self.file else None + self.feature.as_xml(xmlnode, _unused) if self.feature else None + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + + +class File(StanzaPayloadObject): + xml_element_name = 'file' + xml_element_namespace = SI_FILETRANSFER_NS + def __init__(self, xmlnode_or_name=None, size=None, hash=None, date=None, + desc=None, length=None, offset=None): + """ + :Types: + - `xmlnode_or_name`: `libxml2.xmlNode` or `unicode` + - `size`: `int` + - `hash`: `unicode` #skipping this for now + - `date`: #skipping this for now + """ + if isinstance(xmlnode_or_name,libxml2.xmlNode): + self.from_xml(xmlnode_or_name) + else: + self.name = xmlnode_or_name + self.size = size + self.hash = hash + self.date = date + self.length = length + self.desc = desc + self.offset = offset + + def from_xml(self,node): + """Initialize File from XML node.""" + if node.type!="element": + raise ValueError,"XML node is not a file element (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=SI_FILETRANSFER_NS or node.name!="file": + raise ValueError,"XML node is not a file element" + name = node.prop("name") + self.name = from_utf8(name) if name else None + size = node.prop("size") + self.size = int(from_utf8(size)) if size else None + hash = node.prop("hash") + date = node.prop("date") + self.hash = from_utf8(hash) if hash else None + self.date = from_utf8(date) if date else None + desc = None + length = None + offset = None + n=node.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=SI_FILETRANSFER_NS: + n=n.next + continue + if n.name =="desc": + desc = n.getContent() + elif n.name == "range": + offset = n.prop("offset") + length = n.prop("length") + n=n.next + self.desc = from_utf8(desc) if desc else None + self.offset = int(from_utf8(offset)) if offset else None + self.length = int(from_utf8(length)) if length else None + + def complete_xml_element(self, xmlnode, _unused): + """Complete the XML node with `self` content. + + Should be overriden in classes derived from `StanzaPayloadObject`. + + :Parameters: + - `xmlnode`: XML node with the element being built. It has already + right name and namespace, but no attributes or content. + - `_unused`: document to which the element belongs. + :Types: + - `xmlnode`: `libxml2.xmlNode` + - `_unused`: `libxml2.xmlDoc`""" + xmlnode.setProp("name",to_utf8(self.name)) if self.name else None + xmlnode.setProp("size",to_utf8(str(self.size))) if self.size else None + xmlnode.setProp("hash",to_utf8(self.hash)) if self.hash else None +# xmlnode.setProp("date",to_utf8(self.date)) if self.date else None + if self.length or self.offset: + range = xmlnode.newChild(None, 'range', None) + range.setProp("length",to_utf8(str(self.length))) if self.length else None + range.setProp("offset",to_utf8(str(self.offset))) if self.offset else None + xmlnode.newTextChild(None, "desc", to_utf8(self.desc)) if self.desc else None + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + +class Feature(StanzaPayloadObject): + xml_element_name = 'feature' + xml_element_namespace = FEATURE_NEG_NS + + def __init__(self, xmlnode=None, possible_streams=None, selected_stream=None): + if isinstance(xmlnode,libxml2.xmlNode): + self.from_xml(xmlnode) + else: + self.possible_streams = possible_streams + self.selected_stream = selected_stream + + def from_xml(self,node): + """Initialize Feature from XML node.""" + if node.type!="element": + raise ValueError,"XML node is not a feature element (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=FEATURE_NEG_NS or node.name!="feature": + raise ValueError,"XML node is not a feature element" + possible_streams = [] + self.selected_stream = None + n=node.children + while n: + if n.type!="element": + n=n.next + continue + ns=get_node_ns_uri(n) + if ns and ns!=DATAFORM_NS or n.name!="x": + n=n.next + continue + form=dataforms.Form(n, strict=False) + if not form.type: + form.type = "form" + try: + field = form['stream-method'] + except KeyError: + n=n.next + continue + else: #if new_streams: + self.selected_stream = field.value + possible_streams.extend(field.options) + n=n.next + self.possible_streams = [v for o in possible_streams for v in o.values] or None + + def complete_xml_element(self, xmlnode, _unused): + """Complete the XML node with `self` content. + + Should be overriden in classes derived from `StanzaPayloadObject`. + + :Parameters: + - `xmlnode`: XML node with the element being built. It has already + right name and namespace, but no attributes or content. + - `_unused`: document to which the element belongs. + :Types: + - `xmlnode`: `libxml2.xmlNode` + - `_unused`: `libxml2.xmlDoc`""" + if self.possible_streams: + field = dataforms.Field(name='stream-method', field_type='list-single', + options=[dataforms.Option(n) + for n in self.possible_streams]) + f = dataforms.Form(xmlnode_or_type='form', fields=[field]) + else: + f = Form(xmlnode_or_type="submit") + f.add_field(name='stream-method', value=self.selected_stream) + f.as_xml(xmlnode, _unused) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r diff --git a/digsby/src/jabber/objects/vcard_avatar.py b/digsby/src/jabber/objects/vcard_avatar.py new file mode 100644 index 0000000..c9a3a9a --- /dev/null +++ b/digsby/src/jabber/objects/vcard_avatar.py @@ -0,0 +1,45 @@ +import libxml2 +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri +from pyxmpp.utils import to_utf8, from_utf8 + +VCARD_AVATAR_NS="vcard-temp:x:update" + +class X_Update(StanzaPayloadObject): + xml_element_name = "x" + xml_element_namespace = VCARD_AVATAR_NS + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r + +class Photo(StanzaPayloadObject): + xml_element_name = 'photo' + xml_element_namespace = VCARD_AVATAR_NS + + def __init__(self, xmlnode_or_hash): + if isinstance(xmlnode_or_hash,libxml2.xmlNode): + self.from_xml(xmlnode_or_hash) + else: + self.hash = xmlnode_or_hash + + def from_xml(self,node): + if node.type!="element": + raise ValueError,"XML node is not a photo (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=self.xml_element_namespace or node.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s descriptor" % self.xml_element_name + self.hash = from_utf8(node.getContent()) + + def complete_xml_element(self, xmlnode, _unused): + xmlnode.addContent(to_utf8(self.hash)) + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r \ No newline at end of file diff --git a/digsby/src/jabber/objects/x_delay.py b/digsby/src/jabber/objects/x_delay.py new file mode 100644 index 0000000..29987bb --- /dev/null +++ b/digsby/src/jabber/objects/x_delay.py @@ -0,0 +1,50 @@ +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri +from datetime import datetime +from pyxmpp.utils import to_utf8 + +X_DELAY_NS = "jabber:x:delay" + +DELAY_TIME_FORMAT = '%Y%m%dT%H:%M:%S' + +class X_Delay(StanzaPayloadObject): + xml_element_name = "x" + xml_element_namespace = X_DELAY_NS + + def __init__(self, xmlnode=None, from_=None, timestamp=None): + if xmlnode is not None: + self.from_xml(xmlnode) + else: + self.from_ = from_ + self.timestamp = timestamp + + def complete_xml_element(self, xmlnode, _doc): + xmlnode.setProp('from', self.from_) if self.from_ else None + if isinstance(self.timestamp, basestring): + stamp = self.timestamp + else: + stamp = self.timestamp.strftime(DELAY_TIME_FORMAT) + xmlnode.setProp('stamp', stamp) + + def from_xml(self, xmlnode): + if xmlnode.type!="element": + raise ValueError,"XML node is not a photo (not en element)" + ns=get_node_ns_uri(xmlnode) + if ns and ns!=self.xml_element_namespace or xmlnode.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s descriptor" % self.xml_element_name + + from_ = xmlnode.prop("from") + self.from_ = to_utf8(from_) if from_ else None + + timestamp = xmlnode.prop("stamp") + try: + self.timestamp = datetime.strptime(timestamp, DELAY_TIME_FORMAT) + except Exception: + self.timestamp = None + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r \ No newline at end of file diff --git a/digsby/src/jabber/objects/x_event.py b/digsby/src/jabber/objects/x_event.py new file mode 100644 index 0000000..c224423 --- /dev/null +++ b/digsby/src/jabber/objects/x_event.py @@ -0,0 +1,50 @@ +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.xmlextra import common_doc, get_node_ns_uri +from jabber.jabber_util import xpath_eval + +X_EVENT_NS = "jabber:x:event" + +class X_Event(StanzaPayloadObject): + xml_element_name = "x" + xml_element_namespace = X_EVENT_NS + + def __init__(self, xmlnode=None, offline=False, delivered=False, + displayed=False, composing=False, id=None): + if xmlnode is not None: + self.from_xml(xmlnode) + else: + self.offline = offline + self.delivered = delivered + self.displayed = displayed + self.composing = composing + self.id = id + + def complete_xml_element(self, xmlnode, _doc): + xmlnode.newChild(None, 'offline', None) if self.offline else None + xmlnode.newChild(None, 'delivered', None) if self.delivered else None + xmlnode.newChild(None, 'displayed', None) if self.displayed else None + xmlnode.newChild(None, 'composing', None) if self.composing else None + xmlnode.newTextChild(None, "id", self.id ) if self.id else None + + def from_xml(self, xmlnode): + if xmlnode.type!="element": + raise ValueError,"XML node is not a photo (not en element)" + ns=get_node_ns_uri(xmlnode) + if ns and ns!=self.xml_element_namespace or xmlnode.name!=self.xml_element_name: + raise ValueError,"XML node is not a %s descriptor" % self.xml_element_name + + self.offline = bool(xpath_eval(xmlnode, 'x:offline', {'x':X_EVENT_NS})) + self.delivered = bool(xpath_eval(xmlnode, 'x:delivered',{'x':X_EVENT_NS})) + self.displayed = bool(xpath_eval(xmlnode, 'x:displayed',{'x':X_EVENT_NS})) + self.composing = bool(xpath_eval(xmlnode, 'x:composing',{'x':X_EVENT_NS})) + + ids = xpath_eval(xmlnode, 'x:id',{'x':X_EVENT_NS}) + + self.id = ids[0].getContent() if ids else None + + def __str__(self): + n=self.as_xml(doc=common_doc) + r=n.serialize() + n.unlinkNode() + n.freeNode() + return r \ No newline at end of file diff --git a/digsby/src/jabber/pyxmpp.patch b/digsby/src/jabber/pyxmpp.patch new file mode 100644 index 0000000..0068844 --- /dev/null +++ b/digsby/src/jabber/pyxmpp.patch @@ -0,0 +1,302 @@ +Index: pyxmpp/jabber/clientstream.py +=================================================================== +--- pyxmpp/jabber/clientstream.py (revision 676) ++++ pyxmpp/jabber/clientstream.py (working copy) +@@ -123,8 +123,8 @@ + self.__logger.debug("Skipping unknown auth method: %s" % method) + return self._try_auth() + elif self.available_auth_methods is not None: ++ self._auth_methods_left.pop(0) + if method in self.available_auth_methods: +- self._auth_methods_left.pop(0) + self.auth_method_used=method + if method=="digest": + self._digest_auth_stage2(self.auth_stanza) +@@ -134,6 +134,7 @@ + return + else: + self.__logger.debug("Skipping unavailable auth method: %s" % method) ++ return self._try_auth() + else: + self._auth_stage1() + +Index: pyxmpp/jabber/vcard.py +=================================================================== +--- pyxmpp/jabber/vcard.py (revision 676) ++++ pyxmpp/jabber/vcard.py (working copy) +@@ -382,11 +382,14 @@ + if n.name=='TYPE': + self.type=unicode(n.getContent(),"utf-8","replace") + if n.name=='BINVAL': +- self.image=base64.decodestring(n.getContent()) ++ try: ++ self.image=base64.decodestring(n.getContent()) ++ except: ++ self.image=None + if n.name=='EXTVAL': + self.uri=unicode(n.getContent(),"utf-8","replace") + n=n.next +- if (self.uri and self.image) or (not self.uri and not self.image): ++ if (self.uri and self.image): + raise ValueError,"Bad %s value in vcard" % (name,) + if (not self.uri and not self.image): + raise Empty,"Bad %s value in vcard" % (name,) +Index: pyxmpp/jabber/dataforms.py +=================================================================== +--- pyxmpp/jabber/dataforms.py (revision 676) ++++ pyxmpp/jabber/dataforms.py (working copy) +@@ -70,7 +70,8 @@ + - `xmlnode`: `libxml2.xmlNode` + - `doc`: `libxml2.xmlDoc`""" + _unused = doc +- xmlnode.setProp("label", self.label.encode("utf-8")) ++ if self.label is not None: ++ xmlnode.setProp("label", self.label.encode("utf-8")) + for value in self.values: + xmlnode.newTextChild(xmlnode.ns(), "value", value.encode("utf-8")) + return xmlnode +@@ -153,9 +154,9 @@ + and `unicode` for other field types. + """ + self.name = name +- if field_type is not None and field_type not in self.allowed_types: ++ if field_type and field_type not in self.allowed_types: + raise ValueError, "Invalid form field type: %r" % (field_type,) +- self.type = field_type ++ self.type = field_type or None + if value is not None: + if values: + raise ValueError, "values or value must be given, not both" +@@ -259,7 +260,8 @@ + - `doc`: `libxml2.xmlDoc`""" + if self.type is not None and self.type not in self.allowed_types: + raise ValueError, "Invalid form field type: %r" % (self.type,) +- xmlnode.setProp("type", self.type) ++ if self.type is not None: ++ xmlnode.setProp("type", self.type) + if not self.label is None: + xmlnode.setProp("label", self.label) + if not self.name is None: +@@ -470,7 +472,7 @@ + xml_element_namespace = DATAFORM_NS + + def __init__(self, xmlnode_or_type = "form", title = None, instructions = None, +- fields = None, reported_fields = None, items = None): ++ fields = None, reported_fields = None, items = None, strict=True): + """Initialize a `Form` object. + + :Parameters: +@@ -489,7 +491,7 @@ + - `items`: `list` of `Item` + """ + if isinstance(xmlnode_or_type, libxml2.xmlNode): +- self.__from_xml(xmlnode_or_type) ++ self.__from_xml(xmlnode_or_type, strict) + elif xmlnode_or_type not in self.allowed_types: + raise ValueError, "Form type %r not allowed." % (xmlnode_or_type,) + else: +@@ -643,7 +645,7 @@ + for item in self.items: + item.as_xml(xmlnode, doc) + +- def __from_xml(self, xmlnode): ++ def __from_xml(self, xmlnode, strict=True): + """Initialize a `Form` object from an XML element. + + :Parameters: +@@ -658,9 +660,11 @@ + self.instructions = None + if (xmlnode.type != "element" or xmlnode.name != "x" + or xmlnode.ns().content != DATAFORM_NS): +- raise ValueError, "Not a form: " + xmlnode.serialize() ++ raise ValueError, "Not a form: " + xmlnode.serialize() + self.type = xmlnode.prop("type") +- if not self.type in self.allowed_types: ++ if not strict: ++ self.type = self.type or None ++ elif not self.type in self.allowed_types: + raise BadRequestProtocolError, "Bad form type: %r" % (self.type,) + child = xmlnode.children + while child: +Index: pyxmpp/stanzaprocessor.py +=================================================================== +--- pyxmpp/stanzaprocessor.py (revision 676) ++++ pyxmpp/stanzaprocessor.py (working copy) +@@ -109,18 +109,24 @@ + ufr=None + if self._iq_response_handlers.has_key((sid,ufr)): + key=(sid,ufr) +- elif ( (fr==self.peer or fr==self.me) ++ elif ( (fr==self.peer or fr==self.me or fr==self.me.bare()) + and self._iq_response_handlers.has_key((sid,None))): + key=(sid,None) + else: ++ self.__logger.warning('ignoring stanza from %r', fr) ++ self.__logger.warning('I am %r', self.me) ++ self.__logger.warning(self._iq_response_handlers.keys()) + return False + res_handler, err_handler = self._iq_response_handlers[key] + if stanza.get_type()=="result": + response = res_handler(stanza) + else: + response = err_handler(stanza) +- del self._iq_response_handlers[key] +- self.process_response(response) ++ try: ++ del self._iq_response_handlers[key] ++ self.process_response(response) ++ except KeyError: ++ assert not self._iq_response_handlers.keys() + return True + + q=stanza.get_query() +Index: pyxmpp/xmlextra.py +=================================================================== +--- pyxmpp/xmlextra.py (revision 676) ++++ pyxmpp/xmlextra.py (working copy) +@@ -212,7 +212,10 @@ + try: + self._root.addChild(node1) + self._handler.stanza(self._doc, node1) +- except: ++ except Exception: ++ import traceback ++ traceback.print_exc() ++ finally: + node1.unlinkNode() + node1.freeNode() + del node1 +Index: pyxmpp/jid.py +=================================================================== +--- pyxmpp/jid.py (revision 676) ++++ pyxmpp/jid.py (working copy) +@@ -198,7 +198,7 @@ + object.__setattr__(self,"resource",s) + + def __str__(self): +- warnings.warn("JIDs should not be used as strings", DeprecationWarning, stacklevel=2) ++# warnings.warn("JIDs should not be used as strings", DeprecationWarning, stacklevel=2) + return self.as_utf8() + + def __unicode__(self): +Index: pyxmpp/sasl/digest_md5.py +=================================================================== +--- pyxmpp/sasl/digest_md5.py (revision 676) ++++ pyxmpp/sasl/digest_md5.py (working copy) +@@ -267,11 +267,13 @@ + self.__logger.debug("auth not supported") + return Failure("not-implemented") + elif var=="charset": ++ val = _unquote(val) + if val!="utf-8": + self.__logger.debug("charset given and not utf-8") + return Failure("bad-challenge") + charset="utf-8" + elif var=="algorithm": ++ val = _unquote(val) + if val!="md5-sess": + self.__logger.debug("algorithm given and not md5-sess") + return Failure("bad-challenge") +Index: pyxmpp/client.py +=================================================================== +--- pyxmpp/client.py (revision 676) ++++ pyxmpp/client.py (working copy) +@@ -62,7 +62,7 @@ + (stream becomes connected, session established etc.). + - `interface_providers`: list of object providing interfaces that + could be used by the Client object. Initialized to [`self`] by +- the constructor if not set earlier. Put objects providing ++ the constructor if not set earlier. Put objects providing + `IPresenceHandlersProvider`, `IMessageHandlersProvider`, + `IIqHandlersProvider` or `IStanzaHandlersProvider` into this list. + :Types: +@@ -281,7 +281,7 @@ + + def _session_started(self): + """Called when session is started. +- ++ + Activates objects from `self.interface_provides` by installing + their stanza handlers, etc.""" + for ob in self.interface_providers: +@@ -349,8 +349,8 @@ + if not self.roster: + raise ClientError("Roster update, but no roster") + q=iq.get_query() +- item=self.roster.update(q) +- if item: ++ items=self.roster.update(q) ++ for item in items: + self.roster_updated(item) + resp=iq.make_result_response() + self.stream.send(resp) +Index: pyxmpp/roster.py +=================================================================== +--- pyxmpp/roster.py (revision 676) ++++ pyxmpp/roster.py (working copy) +@@ -231,7 +231,7 @@ + n.unlinkNode() + n.freeNode() + return r +- ++ + def __iter__(self): + return self.items_dict.itervalues() + +@@ -346,31 +346,33 @@ + ctxt=common_doc.xpathNewContext() + ctxt.setContextNode(query) + ctxt.xpathRegisterNs("r",ROSTER_NS) +- item=ctxt.xpathEval("r:item") ++ items=ctxt.xpathEval("r:item") + ctxt.xpathFreeContext() +- if not item: ++ if not items: + raise ValueError,"No item to update" +- item=item[0] +- item=RosterItem(item) +- jid=item.jid +- subscription=item.subscription +- try: +- local_item=self.get_item_by_jid(jid) +- local_item.subscription=subscription +- except KeyError: ++ local_items = [] ++ for item in items: ++ item=RosterItem(item) ++ jid=item.jid ++ subscription=item.subscription ++ try: ++ local_item=self.get_item_by_jid(jid) ++ local_item.subscription=subscription ++ except KeyError: ++ if subscription=="remove": ++ return [RosterItem(jid,"remove")] ++ if self.server or subscription not in ("none","from","to","both"): ++ subscription="none" ++ local_item=RosterItem(jid,subscription) + if subscription=="remove": +- return RosterItem(jid,"remove") +- if self.server or subscription not in ("none","from","to","both"): +- subscription="none" +- local_item=RosterItem(jid,subscription) +- if subscription=="remove": +- del self.items_dict[local_item.jid] +- return RosterItem(jid,"remove") +- local_item.name=item.name +- local_item.groups=list(item.groups) +- if not self.server: +- local_item.ask=item.ask +- self.items_dict[local_item.jid]=local_item +- return local_item ++ del self.items_dict[local_item.jid] ++ return [RosterItem(jid,"remove")] ++ local_item.name=item.name ++ local_item.groups=list(item.groups) ++ if not self.server: ++ local_item.ask=item.ask ++ self.items_dict[local_item.jid]=local_item ++ local_items.append(local_item) ++ return local_items + + # vi: sts=4 et sw=4 diff --git a/digsby/src/jabber/threadstream.py b/digsby/src/jabber/threadstream.py new file mode 100644 index 0000000..c065d6e --- /dev/null +++ b/digsby/src/jabber/threadstream.py @@ -0,0 +1,709 @@ +from __future__ import with_statement +import pyxmpp.error +from pyxmpp.exceptions import ClientStreamError#, FatalClientError, ClientError +from util.threads.threadpool2 import threaded +from util.callbacks import callsback +from util import GetSocketType +from util.primitives.funcs import Delegate +from pyxmpp.exceptions import LegacyAuthenticationError, TLSNegotiationFailed +from pyxmpp.streambase import STREAM_NS +import pyxmpp.xmlextra as xmlextra +from util.diagnostic import Diagnostic +import libxml2 +import time + +import Queue +import logging +import socket +from jabber.threadstreamsocket import ThreadStreamSocket #@UnresolvedImport + +from pyxmpp.jabber.clientstream import LegacyClientStream +from pyxmpp.exceptions import StreamError, StreamEncryptionRequired, HostMismatch, ProtocolError, TLSError +from pyxmpp.exceptions import FatalStreamError, StreamParseError, StreamAuthenticationError, SASLAuthenticationFailed +from pyxmpp.jid import JID +from pyxmpp import resolver + +from common import netcall +from threading import currentThread +import traceback +import sys + +log = logging.getLogger("ThreadStream") + +outdebug = logging.getLogger("ThreadStream.out").debug +outdebug_s = getattr(logging.getLogger("ThreadStream.out"), 'debug_s', outdebug) + +indebug = logging.getLogger("ThreadStream.in").debug +indebug_s = getattr(logging.getLogger("ThreadStream.in"), 'debug_s', outdebug) + +try: + import M2Crypto + if M2Crypto.version_info < (0, 16): + tls_available = 0 + else: + from M2Crypto import SSL + from M2Crypto.SSL import SSLError + import M2Crypto.SSL.cb + tls_available = 1 +except ImportError: + tls_available = 0 + +def ignore_xml_error(descr): + # ignore undefined namespace errors + if descr == 'Parser error #201.': + return True + +class ThreadStream(LegacyClientStream): + killonce = False + def __init__(self, *a, **k): + LegacyClientStream.__init__(self, *a, **k) + self.__logger = logging.getLogger("ThreadStream") + self.__shutdown = False + + # Debugging hooks for incoming and outgoing XML nodes. + self.on_incoming_node = Delegate() + self.on_outgoing_node = Delegate() + + +# self._to_call = Queue.Queue() + +# def call_later(self, call, *a, **k): +# if not callable(call): raise TypeError +# self._to_call.put((call, a, k)) +# +# def _idle(self): +# LegacyClientStream._idle(self) +# while not self._to_call.empty(): +# try: +# c, a, k = self._to_call.get_nowait() +# c(*a, **k) +# except Queue.Empty: +# pass + + def stanza(self, _unused, node): + 'All incoming stanzas.' + + self.on_incoming_node(node) + LegacyClientStream.stanza(self, _unused, node) + + def _write_node(self, xmlnode): + 'Write an outgoing node.' + + self.on_outgoing_node(xmlnode) + + ### Copied from streambase.py + if self.eof or not self.socket or not self.doc_out: + self.__logger.debug("Dropping stanza: %r" % (xmlnode,)) + return + xmlnode=xmlnode.docCopyNode(self.doc_out,1) + self.doc_out.addChild(xmlnode) + try: + ns = xmlnode.ns() + except libxml2.treeError: + ns = None + if ns and ns.content == xmlextra.COMMON_NS: + xmlextra.replace_ns(xmlnode, ns, self.default_ns) + s = xmlextra.safe_serialize(xmlnode) + self._write_raw(s) + ### + + ### + # Changed from streambase.py implementation, now we use a subroutine that grabs the lock + # and checks if the doc is not None first. + self.freeOutNode(xmlnode) + ### + + def freeOutNode(self, xmlnode): + with self.lock: + if self.doc_out is not None: + xmlnode.unlinkNode() + xmlnode.freeNode() + + def write_raw(self, data): + netcall(lambda: LegacyClientStream.write_raw(self, data)) + + def idle(self): + netcall(lambda: LegacyClientStream.idle(self)) + + def send(self, stanza): + netcall(lambda: LegacyClientStream.send(self, stanza)) + + def _write_raw(self,data): + """Same as `Stream.write_raw` but assume `self.lock` is acquired.""" + if sys.DEV and currentThread().getName() != 'AsyncoreThread': + try: + raise AssertionError, 'bad thread for _write_raw: %r' % currentThread().getName() + except AssertionError: +# from hub import Hub +# Hub.on_error() + traceback.print_exc() + traceback.print_stack() + import wx + def do_submit(): + d = Diagnostic(description = "Automated: Woah, bad thread") + d.prepare_data() + d.do_post() + from common import profile + uname = profile.username + del profile + wx.MessageBox("Hey %s! Something crazy just happened!\n" + "I submitted a bug report for you. - Chris" % uname) + wx.CallLater(3000, do_submit) + raise + outdebug_s("OUT: %r", data) +# try: + self.socket.push(data) +# except Exception, e: +# self.handle_error(e) + outdebug("OUT: done") + + + def fileno(self): + "Return filedescriptor of the stream socket." + + with self.lock: + return self.socket._fileno + + def connect(self, server = None, port = None): + """Establish a client connection to a server. + + [client only] + + :Parameters: + - `server`: name or address of the server to use. Not recommended -- proper value + should be derived automatically from the JID. + - `port`: port number of the server to use. Not recommended -- + proper value should be derived automatically from the JID. + + :Types: + - `server`: `unicode` + - `port`: `int`""" + + outdebug('connect') + with self.lock: + self._connect1(server,port) + + def _connect1(self,server=None,port=None): + "Same as `ClientStream.connect` but assume `self.lock` is acquired." + + outdebug('_connect1') + + if not self.my_jid.node or not self.my_jid.resource: + raise ClientStreamError("Client JID must have username and resource",self.my_jid) + if not server: + server=self.server + if not port: + port=self.port + if server: + self.__logger.debug("server: %r", (server,port)) + service=None + else: + service="xmpp-client" + if port is None: + port=5222 + if server is None: + self.__logger.debug("server: %r", (server,port)) + server=self.my_jid.domain + self.me=self.my_jid + + def connect_failed(): + self.owner.set_offline(self.owner.Reasons.CONN_FAIL) + self._connect2(server,port,service,self.my_jid.domain, sck_cls = GetSocketType())#, error=connect_failed) + + #@threaded + def _connect2(self,addr1,port1,service=None,to=None, sck_cls=socket.SocketType): + """Same as `Stream.connect` but assume `self.lock` is acquired.""" + + outdebug('_connect2') + self.__logger.debug("server: %r", (addr1,port1)) + + if to is None: + to=str(addr1) + if service is not None: + self.state_change("resolving srv",(addr1,service)) + try: + addrs=resolver.resolve_srv(addr1,service) + except Exception: + traceback.print_exc() + addrs = [] + if not addrs: + addrs=[(addr1, port1)] + else: + addrs.append((addr1, port1)) + else: + addrs=[(addr1, port1)] + msg=None + + self.__logger.debug("addrs: %r", addrs) + + for addr,port in addrs: + if type(addr) in (str, unicode): + self.state_change("resolving",addr) + s=None + try: + resolved = resolver.getaddrinfo(addr,port,0,socket.SOCK_STREAM) + except Exception: + self.__logger.debug('an attempt to resolve %r failed', (addr, port)) + resolved = [] + else: + self.__logger.debug('an attempt to resolve %r succeeded', (addr, port)) + resolved.append((2, 1, 0, '_unused', (addr,port))) + + for res in resolved: + family, socktype, proto, _unused, sockaddr = res + self.__logger.debug('res: %r', res) + try: + s=sck_cls(family,socktype,proto) + s.settimeout(10) + self.state_change("connecting",sockaddr) + s.connect(sockaddr) + if ThreadStream.killonce: + ThreadStream.killonce = False + raise socket.error + if self.owner.do_ssl: + ctx=SSL.Context() + ctx.set_verify(SSL.verify_none, 10) + s.setblocking(True) + ssl = SSL.Connection(ctx, s) + ssl.setup_ssl() + ssl.set_connect_state() + ssl.connect_ssl() + s.setblocking(False) + s = ssl + s.setblocking(False) + self.state_change("connected",sockaddr) + except (socket.error, SSLError), msg: + self.__logger.debug("Connect to %r failed: %r", sockaddr,msg) + traceback.print_exc() + if s: + s.close() + s=None + continue + break + if s: + self.__logger.debug('connected to: %r', (addr, port)) + break + if not s: + if msg: + self.__logger.debug('failed to connect to %r: %r', (addr,port), msg) + raise socket.error, msg + else: + self.__logger.debug('failed to connect to %r: unknown reason', (addr,port)) + raise FatalStreamError,"Cannot connect" + + self.addr=addr + self.port=port + with self.owner.lock: + if self.owner.connect_killed == True: + raise FatalStreamError, "Connect Killed" + self._connect_socket(s,to) + self.last_keepalive=time.time() + + def closed(self): + logging.getLogger("ThreadStream").debug("closed") + self._do_closed() + + def closed_dead(self): + logging.getLogger("ThreadStream").debug("closed_dead") + self._do_closed() + + def _do_closed(self): + want_try_again = self.owner.fatal_error() + self.owner.stream = None + self.close(False) + self.owner.disconnected(want_try_again) +# self.state_change("disconnected", self.peer) + + def __connect_error(self): + pass + + def _connect_socket(self,sock,to=None): + """Initialize stream on outgoing connection. + + :Parameters: + - `sock`: connected socket for the stream + - `to`: name of the remote host + """ + logging.getLogger("ThreadStream").debug("connecting") + def asyncore_stuff(): + logging.getLogger("ThreadStream").debug("creating ThreadStreamSocket") + new_sock = ThreadStreamSocket(sock, self._feed_reader, 0, self.closed, self.closed_dead, ssl=self.owner.do_ssl) + LegacyClientStream._connect_socket(self, new_sock, to) + netcall(asyncore_stuff) + + def _loop_iter(self,timeout): + assert False + + def _process(self): + assert False + + def _read(self): + assert False + + def _process_tls_node(self,xmlnode): + """Process stream element in the TLS namespace. + + :Parameters: + - `xmlnode`: the XML node received + """ + if not self.tls_settings or not tls_available: + self.__logger.debug("Unexpected TLS node: %r" % (xmlnode.serialize())) + return False + if self.initiator: + if xmlnode.name=="failure": + raise TLSNegotiationFailed,"Peer failed to initialize TLS connection" + elif xmlnode.name!="proceed" or not self.tls_requested: + self.__logger.debug("Unexpected TLS node: %r" % (xmlnode.serialize())) + return False + self.tls_requested=0 + self._make_tls_connection(success=self.finish_process, + error=self.fail_process) + return True + + def fail_process(self): + self.owner.fatal_error() + self.close() + + def finish_process(self): + self.socket=self.tls + self.__logger.debug("Restarting XMPP stream") + self._restart_stream() + return True + + def _restart_stream(self): + self.stream_id=None + LegacyClientStream._restart_stream(self) + + @callsback + def _make_tls_connection(self, callback = None): + """Initiate TLS connection. + + [initiating entity only]""" + ctx = None + try: + if not tls_available or not self.tls_settings: + raise TLSError,"TLS is not available" + + tlssettings = self.tls_settings + + self.state_change("tls connecting", self.peer) + self.__logger.debug("Creating TLS context") + + ctx = getattr(tlssettings, 'ctx', None) + if ctx is None: + ctx = SSL.Context('tlsv1') + + verify_callback = tlssettings.verify_callback + + if not verify_callback: + verify_callback = getattr(self, 'tls_default_verify_callback', None) + + if tlssettings.verify_peer: + self.__logger.debug("verify_peer, verify_callback: %r", verify_callback) + ctx.set_verify(SSL.verify_peer, 10, verify_callback) + else: + ctx.set_verify(SSL.verify_none, 10) + + if tlssettings.cert_file: + ctx.use_certificate_chain_file(tlssettings.cert_file) + if tlssettings.key_file: + ctx.use_PrivateKey_file(tlssettings.key_file) + else: + ctx.use_PrivateKey_file(tlssettings.cert_file) + ctx.check_private_key() + + if tlssettings.cacert_file: + try: + ctx.load_verify_location(tlssettings.cacert_file) + except AttributeError: + ctx.load_verify_locations(tlssettings.cacert_file) + except Exception, e: + self.__logger.error('Error with TLS stuff: %r', e) + import traceback; traceback.print_exc() + callback.error() + return + #so, if we do a callback/threaded here, how do we ensure that the socket + #is eventually shut down? the error callback? a try except? + #I'm worried that if we go off to another thread, we won't be able + #to get back on asyncore in non-blocking mode. + #not that the way it is is particularly useful. + # + self.callback = callback + self.socket.make_tls(ctx, success=self.tls_done, error=self.tls_fail) + + tls_fail = fail_process + + def tls_done(self): + self.tls = self.socket + + self.state_change("tls connected", self.peer) + + # clear any exception state left by some M2Crypto broken code + try: + raise Exception + except: + pass + self.callback.success() + + def _got_features(self): + try: + return LegacyClientStream._got_features(self) + except FatalStreamError, e: + if e.__class__ == FatalStreamError: + self.owner.auth_failed(e.message) + else: + raise + + def registration_error(self, stanza): + with self.lock: + ae = None + err = stanza.get_error() + ae = err.xpath_eval("e:*",{"e":"jabber:iq:auth:error"}) + if ae: + ae = ae[0].name + else: + ae = err.get_condition().name + + if self.registration_error_callback is not None: + self.registration_error_callback((ae,) + pyxmpp.error.stanza_errors[ae]) + self.registration_error_callback = None + self.registration_success_callback = None + + def registration_success(self, stanza): + if self.registration_success_callback is not None: + self.registration_success_callback() + + self.registration_success_callback = None + self.registration_error_callback = None + _unused = stanza + + with self.lock: + self.state_change("registered", self.registration_form) + if ('FORM_TYPE' in self.registration_form + and self.registration_form['FORM_TYPE'].value == 'jabber:iq:register'): + if 'username' in self.registration_form: + self.my_jid = JID(self.registration_form['username'].value, + self.my_jid.domain, self.my_jid.resource) + if 'password' in self.registration_form: + self.password = self.registration_form['password'] + self.registration_callback = None + + def disconnect(self): + """Disconnect from the server.""" + LegacyClientStream.disconnect(self) + self.state_change("disconnected",self.peer) + + def stream_end(self, _unused): + LegacyClientStream.stream_end(self, _unused) + self.shutdown() + + def _send_stream_end(self): + LegacyClientStream._send_stream_end(self) + self.shutdown() + + def shutdown(self): + if not self.__shutdown: + outdebug("non-Force shutdown") + self.__shutdown = True + if self.socket: + outdebug("non-Force close_when_done") + self.socket.close_when_done() + else: + outdebug("Force shutdown") + self.close(False) + + def close(self, do_disconnect=True): + "Forcibly close the connection and clear the stream state." + + with self.lock: + return self._close(do_disconnect) + + def _close(self, do_disconnect=True): + "Same as `Stream.close` but assume `self.lock` is acquired." + + if do_disconnect: + self._disconnect() + if self.doc_in: + self.doc_in = None + if self.features: + self.features = None + self._reader = None + self.stream_id = None + if self.socket: + self.socket.close() + self._reset() + + def _process_node(self, stanza): + try: + LegacyClientStream._process_node(self, stanza) + except SASLAuthenticationFailed, e: + self.owner.auth_failed(reason = getattr(e, 'reason', e.message)) + self.__logger.critical("SASLAuthenticationFailed: %r, %r", e.message, getattr(e, 'reason', 'unknown-reason')) + except LegacyAuthenticationError, e: + self.owner.auth_failed(reason = e.message) + self.__logger.critical("LegacyAuthenticationError") + except FatalStreamError, e: + import hub + hub.get_instance().on_error(e) + self.__logger.critical("Stream blew up") + self.owner.fatal_error() + self.close() +# except FatalClientError, e: +# raise e +# except ClientError, e: +# traceback.print_exc() +# raise e +# except Exception, e: +# import hub +# hub.get_instance().on_error(e) +# raise + + def _process_sasl_failure(self, xmlnode): + try: + LegacyClientStream._process_sasl_failure(self, xmlnode) + except SASLAuthenticationFailed, e: + e.reason = getattr(xmlnode.get_children(), 'name', 'unknown-reason') + raise e + + + def error(self, descr): + if ignore_xml_error(descr): + return + + self.__logger.critical("XML parse error: " + descr) + self.owner.fatal_error() + self.close() + + def fix_in_stanza(self,stanza): + LegacyClientStream.fix_in_stanza(self,stanza) + if self.initiator: + to=stanza.get_to() + if to is not None: + p = self.peer + pb = p.bare() if p else None + tob = to.bare() if to else None + if tob == pb or to == p or to == pb or tob == p: + stanza.set_to(False) + + def _feed_reader(self, data): + with self.lock: + if self._reader is not None: + self._super_feed_reader(data) + else: + self.close(False) + + def _super_feed_reader(self,data): #feed reader copied from superclass + """Feed the stream reader with data received. + + If `data` is None or empty, then stream end (peer disconnected) is + assumed and the stream is closed. + + :Parameters: + - `data`: data received from the stream socket. + :Types: + - `data`: `unicode` + """ + indebug_s("IN: %r",data) + + if data: + try: + r=self._reader.feed(data) + while r: + r=self._reader.feed("") + if r is None: + indebug('r was None, setting eof + disconnect') + self.eof=1 + self.disconnect() + except StreamParseError: + self._send_stream_error("xml-not-well-formed") + raise + else: + indebug('no data, setting eof + disconnect') + self.eof=1 + self.disconnect() + if self.eof: + indebug('eof calling stream_end') + self.stream_end(None) + + def stream_start(self,doc): + """Process (stream start) tag received from peer. + + :Parameters: + - `doc`: document created by the parser""" + self.doc_in=doc + log.debug("input document: %r" % (self.doc_in.serialize(),)) + + try: + r=self.doc_in.getRootElement() + if r.ns().getContent() != STREAM_NS: + self._send_stream_error("invalid-namespace") + raise FatalStreamError,"Invalid namespace." + except libxml2.treeError: + self._send_stream_error("invalid-namespace") + raise FatalStreamError,"Couldn't get the namespace." + + self.version=r.prop("version") + if self.version and self.version!="1.0": + self._send_stream_error("unsupported-version") + raise FatalStreamError,"Unsupported protocol version." + + to_from_mismatch=0 + old_peer = None + + assert self.initiator + if self.initiator: + self.stream_id=r.prop("id") + peer=r.prop("from") + if peer: + peer=JID(peer) + if self.peer: + if peer and peer != self.peer:# and not unicode(self.peer).endswith(unicode(peer)) \ + #and not unicode(peer).endswith(unicode(self.peer)): + self.__logger.debug("peer hostname mismatch:" + " %r != %r" % (peer,self.peer)) + to_from_mismatch=1 + #TODO: something here (?), to go along with 'on_host_mismatch' from end of this function + old_peer, self.peer = self.peer, peer + elif peer: + self.peer = peer + else: + self.peer=peer + else: + to=r.prop("to") + if to: + to=self.check_to(to) + if not to: + self._send_stream_error("host-unknown") + raise FatalStreamError,'Bad "to"' + self.me=JID(to) + self._send_stream_start(self.generate_id()) + self._send_stream_features() + self.state_change("fully connected",self.peer) + self._post_connect() + + if to_from_mismatch: + handler = getattr(self, 'on_host_mismatch', None) + if handler is not None: + handler(old_peer, peer) + + if not self.version: + self.state_change("fully connected",self.peer) + self._post_connect() + + def on_host_mismatch(self, mine, theirs): + self._had_host_mismatch = True + self._host_mismatch_info = (mine, theirs) + + def _make_reader(self): + self._reader=IgnoreNSErrorReader(self) + +class IgnoreNSErrorReader(xmlextra.StreamReader): + def feed(self,s): + try: + return xmlextra.StreamReader.feed(self, s) + #super doesn't work on old style classes +# return super(IgnoreNSErrorReader, self).feed(s) + except Exception as e: + if ignore_xml_error(e.message): + return 0 + raise diff --git a/digsby/src/jabber/threadstreamsocket.py b/digsby/src/jabber/threadstreamsocket.py new file mode 100644 index 0000000..bec1c13 --- /dev/null +++ b/digsby/src/jabber/threadstreamsocket.py @@ -0,0 +1,238 @@ +from asynchat import async_chat +from util.threads.threadpool2 import threaded +from common import netcall +from util.callbacks import callsback + +from common import pref + +import sys +import socket +import logging +import common + +from util.primitives.synchronization import lock +from util.primitives.funcs import get + +try: + import M2Crypto + if M2Crypto.version_info < (0, 16): + tls_available = 0 + else: + from M2Crypto import SSL + from M2Crypto.SSL import SSLError + import M2Crypto.SSL.cb + tls_available = 1 + SSL_ERROR_WANT_WRITE = SSL.m2.ssl_error_want_write + SSL_ERROR_WANT_READ = SSL.m2.ssl_error_want_read +except ImportError: + tls_available = 0 + + +class ThreadStreamSocket(common.socket): + + ac_in_buffer_size = 4096 * 16 + ac_out_buffer_size = 4096 * 16 + + def __init__(self, sock, collect, term, on_close, on_error, ssl=False): + self.term = term + self.tls = None if not ssl else sock + self.collect_incoming_data = collect + self.set_terminator(self.term) + self.__logger=logging.getLogger("ThreadStreamSocket") + self.on_close = on_close + self.on_error = on_error + self.killed = False + self.lastbuffer = '' + self.__want_write = False + self.__want_read = False + common.socket.__init__(self, sock) + + def found_terminator(self): + self.set_terminator(self.term) + + def handle_error(self, e=None): + import traceback;traceback.print_exc() + t, v = sys.exc_info()[:2] + if t is not None: + msg = get(get(v.args, 0, 'say what?'), 'message', '') + if msg.startswith('bad write retry'): + assert False + self.__logger.error('Got that weird-ass "bad write retry" message in jabber socket') +# return + + sslzero_closes = pref('jabber.ssl_error_zero.should_close', type = bool, default = True) + if t is SSLError and get(v.args, 0, sentinel) == 0: + self.__logger('SSL error 0!') + if not sslzero_closes: + self.__logger('\tnot closing') + return + + self.__logger.debug('handle_error in %r', self) + async_chat.close(self) + if not self.killed: + self.killed = True + self.on_error() + + + def handle_close(self): + self.__logger.debug('handle_close in %r', self) + async_chat.close(self) + if not self.killed: + self.killed = True + self.on_close() + + @lock + @callsback + def make_tls(self, ctx, callback=None): + self._realfileno = self._fileno + + self.socket.setblocking(True) + self.del_channel() + dbg = self.__logger.debug + + def blocking_connect(): + try: + dbg("Creating TLS connection") + self.tls = SSL.Connection(ctx, self.socket) + dbg("Setting up TLS connection") + self.tls.setup_ssl() + dbg("Setting TLS connect state") + self.tls.set_connect_state() + dbg("Starting TLS handshake") + # self.tls.setblocking(True) + self.tls.connect_ssl() + self.socket.setblocking(False) + self.tls.setblocking(False) + self.ssocket = self.socket + self.socket = self.tls + except Exception, e: + try: + self.socket.close() + self.tls.close() + dbg('There was an exception in TLS blocking_connect: %r', e) + except Exception: + pass + raise e + + def win(): + self._fileno = self._realfileno + self.add_channel() + callback.success() + + def lose(e): + netcall(callback.error) + + threaded(blocking_connect)(success = lambda: netcall(win), error=lose) + + def recv(self, buffer_size=4096): + self.__want_read = False + try: + return common.socket.recv(self, buffer_size) + except SSLError, e: + if e.args[0] == SSL_ERROR_WANT_WRITE: + self.__want_write = True + self.__want_read = False + self.__logger.warning("read_want_write") + return "" + elif e.args[0] == SSL_ERROR_WANT_READ: + self.__want_write = False + self.__want_read = True + self.__logger.warning("read_want_read") + return "" + else: + raise socket.error(e) + + def send(self, buffer): + self.__want_write = False +# buffer = str(buffer) + + if self.tls is None: + return common.socket.send(self, buffer) + +## # M2Crypto returns -1 to mean "retry the last write." It has the +## # strange requirement that exactly the same bytes are tried again +## # during the next write--so we need to keep our own buffer. + r = None + if not self.lastbuffer: + try: + r = self.socket.sendall(buffer) + except SSLError, e: + if e.args[0] == SSL_ERROR_WANT_WRITE: + self.__want_write = True + self.__want_read = False + self.__logger.warning("write_want_write") + self.lastbuffer = buffer # -1: store the bytes for later + return len(buffer) # consume from asyncore + elif e.args[0] == SSL_ERROR_WANT_READ: + self.__want_write = False + self.__want_read = True + self.__logger.warning("write_want_read") + return 0 + else: + raise socket.error(e, r) + else: + if r < 0: + raise socket.error('unknown -1 for ssl send') + return r + else: + try: + # we've got saved bytes--send them first. + r = self.socket.sendall(self.lastbuffer) + except SSLError, e: + if e.args[0] == SSL_ERROR_WANT_WRITE: + self.__want_write = True + self.__want_read = False + self.__logger.warning("write_want_write (buffer)") + elif e.args[0] == SSL_ERROR_WANT_READ: + self.__want_write = False + self.__want_read = True + self.__logger.warning("write_want_read (buffer)") + else: + raise socket.error(e, r) + else: + if r < 0: + raise socket.error('unknown -1 for ssl send (buffer)') + elif r < len(self.lastbuffer): + self.lastbuffer = self.lastbuffer[r:] + else: + self.lastbuffer = '' + return 0 + + def initiate_send(self): + #if there's nothing else in the socket buffer, the super class initiate_send won't call send + # and self.lastbuffer won't be flushed. + if self.lastbuffer: + assert self.tls is not None + assert self.__want_write + self.send(None) + return + return common.socket.initiate_send(self) + + def readable (self): + "predicate for inclusion in the readable for select()" + assert not (self.__want_read and self.__want_write) + return not self.__want_write and (self.__want_read or + common.socket.readable(self))# and not self.lastbuffer + + def writable (self): + assert not (self.__want_read and self.__want_write) + "predicate for inclusion in the writable for select()" + # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected) + # this is about twice as fast, though not as clear. + return (common.socket.writable(self) #async buffer + connection + or self.lastbuffer #out buffer + or self.__want_write) and not self.__want_read + + def _repr(self): + return 'wr:%s ww:%s lb:%s' % (self.__want_read, self.__want_write, self.lastbuffer) + + +class ThreadStreamSSLSocket(common.socket): + def __init__(self, sock, collect, term): + self.collect_incoming_data = collect + self.set_terminator(term) + self.__logger = logging.getLogger("ThreadStreamSSLSocket") + common.socket.__init__(self, sock) + + + diff --git a/digsby/src/jabber/tlslitestream.py b/digsby/src/jabber/tlslitestream.py new file mode 100644 index 0000000..dccd5ea --- /dev/null +++ b/digsby/src/jabber/tlslitestream.py @@ -0,0 +1,360 @@ +from __future__ import with_statement + +from pyxmpp import resolver +from pyxmpp.exceptions import TLSError as XMPPTLSError +from pyxmpp.jabber.clientstream import LegacyClientStream + +import util +import util.primitives.funcs as funcs +import common +from common import netcall, profile +from threading import currentThread + +import traceback +import sys +import time +import Queue +import logging +import socket +import threadstream + +try: + from tlslite.api import TLSError, TLSConnection, TLSAsyncDispatcherMixIn, TLSLocalAlert + tls_available = 1 +except ImportError: + tls_available = 0 + +log = logging.getLogger("tlslitestream") + +outdebug = logging.getLogger("tlslitestream.out").debug +outdebug_s = getattr(logging.getLogger("tlslitestream.out"), 'debug_s', outdebug) + +indebug = logging.getLogger("tlslitestream.in").debug +indebug_s = getattr(logging.getLogger("tlslitestream.in"), 'debug_s', outdebug) + +class AsyncStreamSocket(common.socket): + def __init__(self, sock, collect, on_close, on_error, on_connect, **k): + self.socket = None + self.term = 0 + self.tls = None + self._collector = collect + self._logger = logging.getLogger(type(self).__name__) + + self.on_close = funcs.Delegate() + self.on_close += on_close + + self.on_error = funcs.Delegate() + self.on_error += on_error + + self.on_connect = funcs.Delegate() + self.on_connect += on_connect + + self.lastbuffer = '' + self._closed = False + + common.socket.__init__(self, sock) + self.set_terminator(self.term) + + self.killed = False + + def collect_incoming_data(self, data): + self._collector(data) + + def found_terminator(self): + self.set_terminator(self.term) + + def handle_connect(self): + self._logger.info('handle_connect') + common.socket.handle_connect(self) + self.on_connect[:], on_connect = funcs.Delegate([]), funcs.Delegate(self.on_connect[:]) + + on_connect() + + def handle_error(self, e, force_close=False): + self.killed = True + if (not force_close) and self._closed: + return + + self._closed = True + self._logger.info('handle_error: %r', e) + self.on_error[:], on_error = funcs.Delegate([]), funcs.Delegate(self.on_error[:]) + on_error() + self.clear_delegates() + + if force_close: + # Do it early in this case. + self.close() + + common.socket.handle_error(self, e) + + if not force_close: + self.close() + + def handle_expt(self): + self.handle_error(Exception("OOB Data"), force_close = True) + + def handle_close(self): + if self._closed: + return + + self._closed = True + self._logger.info('handle_close') + self.close() + + common.socket.handle_close(self) + on_close, self.on_close[:] = funcs.Delegate(self.on_close[:]), funcs.Delegate() + + self.clear_delegates() + + if not self.killed: + # We don't want to run close callbacks *and* error callbacks - some of them perform similar tasks + # or are mutually exclusive + on_close() + + def clear_delegates(self): + del self.on_close[:] + del self.on_error[:] + del self.on_connect[:] + + def should_not_be_called(*a, **k): + raise AssertionError("This function should not have been called!") + + self.on_close += should_not_be_called + self.on_error += should_not_be_called + self.on_connect += should_not_be_called + + def fileno(self): + if self.socket is not None: + return self.socket.fileno() + else: + return -1 + +if tls_available: + class TLSLiteStreamSocket(TLSAsyncDispatcherMixIn, AsyncStreamSocket): + ac_in_buffer_size = 16384 + def __init__(self, sock, *a, **k): + AsyncStreamSocket.__init__(self, sock, *a, **k) + TLSAsyncDispatcherMixIn.__init__(self, sock) + self._logger = logging.getLogger(type(self).__name__) + + @util.callsback + def setup_ssl(self, callback = None): + self._logger.info('Setting up SSL') + self._set_tls_opts((3,0)) + self._start_tls() + + @util.callsback + def setup_tls(self, callback = None): + self._logger.info('Setting up TLS') + self._set_tls_opts((3,1)) + self._start_tls() + + def _set_tls_opts(self, version): + self.tlsConnection.version = version + self.tlsConnection.ignoreAbruptClose = True + self.tlsConnection.closeSocket = True + + def _start_tls(self): + self.setHandshakeOp(self.tlsConnection.handshakeClientCert(async=True)) + + def close(self): + try: + TLSAsyncDispatcherMixIn.close(self) + except Exception, e: + traceback.print_exc() + log.error("Error trying to shut down TLSConnection. Un-cleanly closing socket. (the error was: %r)", e) + AsyncStreamSocket.close(self) + + def handle_error(self, e, force_close = False): + if self._closed: + return + + try: + raise e + except TLSLocalAlert: + if getattr(e, 'errorStr', None) is not None: + #sys.stderr.write(e.errorStr) + e.verbose = False + except: + # This is OK because we already have the exception and we're sending it somewhere else + pass + + AsyncStreamSocket.handle_error(self, e, force_close = force_close) + +else: + log.error('Defining a stub for TLSLiteStreamSocket, but I really shouldn\'t be here...') + class TLSLiteStreamSocket(AsyncStreamSocket): + pass + +class TLSLiteStream(threadstream.ThreadStream): + + tls_available = tls_available + + def __init__(self, *a, **k): + threadstream.ThreadStream.__init__(self, *a, **k) + self.do_ssl = self.owner.do_ssl and self.tls_available + self.use_tls = self.tls_settings and any(self.tls_settings.__dict__.values()) and self.tls_available + + self._socket_class = AsyncStreamSocket + + log.info('using _socket_class %r', self._socket_class) + + def _determine_conn_info(self, server, port): + if not server: + server = self.server + if not port: + port = self.port + + if server: + service = None + else: + service = 'xmpp-client' + + if port is None: + port = 5222 + + if server is None: + server = self.my_jid.domain + self.me = self.my_jid + + return server, port, service + + def endpoint_generator(self, server, port, service = None, to = None): + if to is None: + to = str(server) + + addrs = [] + if service is not None: + try: + self.state_change("resolving srv",(server,service)) + addrs = resolver.resolve_srv(server, service) or [] + except Exception, e: + log.debug('Failed to resolve %r: %r', (server, service), e) + + addrs.append((server, port)) + + for address, port in addrs: + if type(address) not in (str, unicode): + continue + self.state_change("resolving", address) + try: + resolved = resolver.getaddrinfo(address, port, 0, socket.SOCK_STREAM) + except Exception: + resolved = [] + + resolved.append((2, 1, 0, '_unused', (address, port))) + + for sock_info in resolved: + yield sock_info + + def _connect1(self, server = None, port = None): + "Same as `ClientStream.connect` but assume `self.lock` is acquired." + outdebug('_connect1') + server, port, service = self._determine_conn_info(server, port) + if getattr(self, '_endpoints', None) is None: + self._endpoints = self.endpoint_generator(server, port, service, self.my_jid.domain) + elif getattr(self, '_endpoints', None) is False: + return + + if getattr(self, 'socket', None) is not None: + self.socket.close() + self.socket = None + + try: + endpoint = self._endpoints.next() + except StopIteration: + if self.socket is not None: + self.socket.close() + self.socket = None + self._endpoints = None + log.info('No more endpoints to try.') + + return self.owner.connect_attempt_failed() + + self.socket = self._socket_class(sock = False, # Don't make one yet. + collect = self._feed_reader, + on_close = self.closed, + on_error = self.closed_dead, + on_connect = lambda *a: None, + ssl = self.do_ssl) + + family, socktype, proto, _unused, sockaddr = endpoint + addr, port = sockaddr + self.socket.create_socket(family, socktype) + self.socket.socket.settimeout(2) + + self.state_change("connecting",sockaddr) + + def setup_success(): + self.addr, self.port = addr, port + self._connect_socket(self.socket, self.my_jid.domain) + self._endpoints.close() + self._endpoints = False + self.last_keepalive = time.time() + + with self.owner.lock: + if self.owner.connect_killed == True: + raise FatalStreamError("Cannot connect") + + def connect_fail(e=None): + log.error('connection to %r failed: %r', (addr, port), e) + self._connect1() + + def connect_success(): + log.info('connection to %r succeeded', (addr, port)) + self.state_change("connected",sockaddr) + if self.do_ssl: + log.debug('\tsetting up socket SSL') + self.setup_ssl(success = setup_success, error = connect_fail) + else: + log.debug('\tinitializing stream connection') + setup_success() + + self.socket.on_connect += connect_success + self.socket.connect(sockaddr, error = connect_fail) + + def change_sock_type(self, new_cls): + sck = self.socket.socket + self.socket.del_channel() + oldsck = self.socket + oldsck.socket = None + self.socket = new_cls(sock = sck, # the new asyncsocket will wrap the socket.socket from the old one. + collect = oldsck._collector, + on_close = oldsck.on_close, + on_error = oldsck.on_error, + on_connect = oldsck.on_connect, + ssl = True) + self.socket.on_connect.extend(oldsck.on_connect) + self.socket.add_channel() + + @util.callsback + def setup_ssl(self, callback = None): + self.change_sock_type(TLSLiteStreamSocket) + self.socket.on_connect += callback.success + self.socket.on_error += callback.error + self.socket.setup_ssl() + + @util.callsback + def _make_tls_connection(self, callback = None): + """Initiate TLS connection. + + [initiating entity only]""" + self.change_sock_type(TLSLiteStreamSocket) + + self.socket.on_connect += callback.success + self.socket.on_connect += lambda *a, **k: self.state_change("tls connected", self.peer) + self.socket.on_error += callback.error + + self.tls = self.socket + self.state_change("tls connecting", self.peer) + self.socket.setup_tls() + + def _connect_socket(self,sock,to=None): + """Initialize stream on outgoing connection. + + :Parameters: + - `sock`: connected socket for the stream + - `to`: name of the remote host + """ + logging.getLogger("ThreadStream").debug("connecting") + netcall(lambda: LegacyClientStream._connect_socket(self, sock, to)) diff --git a/digsby/src/lang.py b/digsby/src/lang.py new file mode 100644 index 0000000..6b75dcf --- /dev/null +++ b/digsby/src/lang.py @@ -0,0 +1,51 @@ +import ctypes +from ctypes import byref +import os + +MUI_LANGUAGE_ID = 4 +MUI_LANGUAGE_NAME = 8 + +if os.name == 'nt': + def get_preferred_languages(): + GetUserPreferredUILanguages = ctypes.windll.kernel32.GetUserPreferredUILanguages + num_languages = ctypes.c_ulong() + buffer_length = ctypes.c_ulong() + if GetUserPreferredUILanguages( + MUI_LANGUAGE_NAME, + byref(num_languages), + None, + byref(buffer_length)) and buffer_length.value: + + buffer = ctypes.create_unicode_buffer(buffer_length.value) + + if GetUserPreferredUILanguages( + MUI_LANGUAGE_NAME, + byref(num_languages), + byref(buffer), + byref(buffer_length)) and 0 != num_languages.value: + + langlist = wszarray_to_list(buffer) + assert num_languages.value == len(langlist) + return langlist + +def wszarray_to_list(array): + ''' + >> wszarray_to_list(ctypes.create_unicode_buffer(u'string1\0string2\0string3\0\0')) + [u'string1', u'string2', u'string3'] + ''' + + offset = 0 + l = [] + while offset < ctypes.sizeof(array): + sz = ctypes.wstring_at(ctypes.addressof(array) + offset*2) + if sz: + l.append(sz) + offset += len(sz)+1 + else: + break + + return l + +if __name__ == '__main__': + print get_preferred_languages() + diff --git a/digsby/src/linecount.py b/digsby/src/linecount.py new file mode 100644 index 0000000..16cb574 --- /dev/null +++ b/digsby/src/linecount.py @@ -0,0 +1,71 @@ +import os +pathname = r'c:\workspace\Digsby' + +# XXX: this isn't part of the program. it should get axed. +filecount = 0 + +from hashlib import md5 + +in3quotes = False + +from time import clock +clock() + +filesizes={} + +bytecount = 0 +for root, dirs, files in os.walk(pathname): + if '.svn' in root: + continue + + for file in files: + + if file.endswith('.py'): + fname = os.path.join(root,file) + filesizes[fname] = 0 + f = open(fname) + for line in f: + filesizes[fname] += 1 + continue + line = line.strip() + + if not line or line.startswith('#'): + continue + + s1 = line.count('"""') + s2 = line.count("'''") + m1 = s1 % 2 + m2 = s2 % 2 + + if s1 and m1 or s2 and m2: + in3quotes = not in3quotes + + if not in3quotes: + filesizes[fname] += 1 + else: + pass + f.close() + +duration = clock() +filesizes = sorted(filesizes.items(), key=lambda x:x[1]) +sizes = [x[1] for x in filesizes] +files = [x[0] for x in filesizes] +total = sum(sizes) +max = sizes[-1] +min = sizes[0] +avg = total/len(sizes) +median = sizes[len(sizes)//2] + +print 'Total = %d' % (total,) +print 'Max = %d (%s)' % (max, files[-1]) +print 'Min = %d (%s)' % (min, files[0]) +print 'Avg = %d' % (avg,) +print 'Med = %d' % (median,) + +print '=' * 80 +n = 10 +print 'Top %d files:' % n +for f,s in reversed(filesizes[-n:]): + print '%4d lines\t\t%s' % (s,f) + + diff --git a/digsby/src/localload.py b/digsby/src/localload.py new file mode 100644 index 0000000..ad0be11 --- /dev/null +++ b/digsby/src/localload.py @@ -0,0 +1,41 @@ +''' +Created on Aug 14, 2012 + +@author: Christopher +''' +from digsby.blobs import name_to_ns +from digsby.digsbylocal import load_local, InvalidUsername, InvalidPassword, \ + load_local_blob + +def get_local_data(username, password): + local_acct_data = load_local(username, password) + #deal with passwords + localblobs = {} + blobnames = name_to_ns.keys() + for blobname in blobnames: + #find cache file path] + #load file + try: + with open('fname', 'rb') as f: + #assign to data + data = f.read() + except Exception: + pass #fail + try: + data = load_local_blob(username, password) + if data is not None: + localblobs[blobname] = data + except Exception: + pass + return {'accounts':local_acct_data, 'blobs':localblobs} + +def get_remote_data(username, password): + pass + #launch new digsbyprotocol + + #prevent actually "going online" + #we can also circumvent the normal blob cache routine by loading directly from data with new handlers + + #request accounts + #request every blob + #disconnect diff --git a/digsby/src/logextensions.py b/digsby/src/logextensions.py new file mode 100644 index 0000000..50840f6 --- /dev/null +++ b/digsby/src/logextensions.py @@ -0,0 +1,206 @@ +''' + +Monkeypatches to Python 2.5's logging module + +- LogRecord: catch exceptions in getMessage method +- StreamHandler: missing streams result in logs going to stderr + +''' + +###################################### +import logging +###################################### + +# XXX: unless py3k's logging module is drastically different, +# a lot of this can be done with subclasses instead of monkeypatches. + +LOGGING_ENABLED = True + +import sys +from threading import currentThread + +_LogRecord = logging.LogRecord +class LogRecord(_LogRecord): + + def __init__(self, *a, **k): + _LogRecord.__init__(self, *a, **k) + self.threadCount = getattr(currentThread(), 'loopcount', 0) + + def getMessage(self): + """ + Return the message for this LogRecord. + + Return the message for this LogRecord after merging any user-supplied + arguments with the message. + """ + msg = self.msg + + if type(msg) not in (unicode, str): + try: + msg = str(self.msg) + except UnicodeError: + msg = repr(self.msg) #Defer encoding till later + + if self.args: + try: + msg = msg % self.args + except Exception, e: + try: + return 'Error in log message (%r:%r): msg=%r, args=%r' % (self.filename, self.lineno, msg, self.args) + except Exception, e2: + return 'Error in log message (%r:%r)' % (self.filename, self.lineno) + + if isinstance(msg, unicode): + msg = msg.encode('utf8') + return msg + +logging.LogRecord = LogRecord + +class StreamHandler(logging.Handler): + """ + A handler class which writes logging records, appropriately formatted, + to a stream. Note that this class does not close the stream, as + sys.stdout or sys.stderr may be used. + """ + def __init__(self, strm=None): + """ + Initialize the handler. + + If strm is not specified, sys.stderr is used. + """ + logging.Handler.__init__(self) + self._stream = strm + self.formatter = None + self.has_stream = True + + @property + def stream(self): + if self._stream is None and self.has_stream: + return sys.stderr + elif self.has_stream: + return self._stream + def flush(self): + """ + Flushes the stream. + """ + try: + self.stream.flush() + except: + if self._stream is not None: + self._stream = None + else: + self.has_stream = False + + def emit(self, record): + """ + Emit a record. + + If a formatter is specified, it is used to format the record. + The record is then written to the stream with a trailing newline + [N.B. this may be removed depending on feedback]. If exception + information is present, it is formatted using + traceback.print_exception and appended to the stream. + """ + if not self.has_stream: + return + try: + msg = self.format(record) + fs = "%s\n" + try: + self.stream.write(fs % msg) + except UnicodeError: + self.stream.write(fs % msg.encode("UTF-8")) + except Exception: + # Assume stream has died. + self.has_stream = False + except (KeyboardInterrupt, SystemExit): + raise + except: + self.handleError(record) + +logging.StreamHandler = StreamHandler +logging.FileHandler.__bases__ = (StreamHandler,) + logging.FileHandler.__bases__[1:] + +_logcolors = [ + (40, 'red bold'), + (30, 'yellow bold'), + (20, 'white'), + (10, 'grey'), +] + +def logcolor_for_level(level): + for lvl, color in _logcolors: + if level >= lvl: + return color + + return color + +class ColorStreamHandler(StreamHandler): + pass + +if getattr(getattr(sys, 'opts', None), 'console_color', False): + try: + from gui.native.win import console + except ImportError: + pass + else: + class ColorStreamHandler(StreamHandler): + def emit(self, record): + with console.color(logcolor_for_level(record.levelno)): + StreamHandler.emit(self, record) + +def setup_sensitive_logs(): + ''' + Add methods like debug_s and info_s to log objects which, in the release build, + do not write out to log files. + ''' + from logging import Logger + dev = getattr(sys, 'DEV', False) + full_log = getattr(getattr(sys, 'opts', None), 'full_log', False) + nolog = lambda self, *a, **k: None + + for log_type in ['critical', 'debug', 'error', 'exception', 'fatal', + 'info', 'log', 'warn', 'warning']: + def make_sensitive(name): + if dev or full_log: + def sensitive(self, *a, **k): + getattr(self, name)(*a, **k) + else: + sensitive = nolog + return sensitive + func = make_sensitive(log_type) + setattr(Logger, log_type + '_s', func) + +setup_sensitive_logs() + + +if not LOGGING_ENABLED: + class NullRoot(object): + stream = None + + setLevel = \ + addHandler = \ + info = \ + lambda *a: None + + class NullLogger(object): + root = NullRoot() + handlers = [] + + debug = debug_s = \ + info = info_s = \ + warning = error = \ + critical = \ + lambda *a: None + + _null_logger = NullLogger() + + class NullManager(object): + def getLogger(self, name): + return _null_logger + + _null_manager = NullManager() + + NullLogger.manager = _null_manager + + logging.Logger = NullLogger diff --git a/digsby/src/mail/__init__.py b/digsby/src/mail/__init__.py new file mode 100644 index 0000000..47bc2a3 --- /dev/null +++ b/digsby/src/mail/__init__.py @@ -0,0 +1,4 @@ +from emailobj import Email + +class MailException(Exception): pass +class AuthenticationError(Exception): pass \ No newline at end of file diff --git a/digsby/src/mail/aolmail.py b/digsby/src/mail/aolmail.py new file mode 100644 index 0000000..a024969 --- /dev/null +++ b/digsby/src/mail/aolmail.py @@ -0,0 +1,285 @@ +from util import UrlQuery, threaded +#from util.ie import * +import common +import mail +import mail.smtp +from mail.imap import IMAPMail + +from common import pref + +import time +import logging +log = logging.getLogger('aolmail') + +class AOLMail(IMAPMail): + protocol = 'aolmail' + + default_domain = 'aol.com' + + AIM_SERVER = "imap.aol.com" + + def __init__(self, *a, **kws): + self._name = None + log.info('aolmail: %r', kws) + + # All AOLMAil accounts use the same server. + kws.update(dict( imapserver = self.AIM_SERVER)) + + IMAPMail.__init__(self, *a, **kws) +# self.ie = None + + get_email_address = common.emailaccount.EmailAccount.get_email_address # We don't want the superclass function because it doesn't do default domains. + + def _get_name(self): + return self._name + + def _set_name(self, name): + self._name = name + + name = property(_get_name, _set_name) + + def _get_mailclient(self): + return pref('privacy.www_auto_signin', False) + + def _not_supported(self, val): + ''' + Not supported. + ''' + return + + mailclient = property(_get_mailclient, _not_supported) + +# def update(self): +# import wx +# wx.CallAfter(self.getie) +# IMAPMail.update(self) + + + can_has_preview = True + + def reportSpam(self, msg): + IMAPMail.reportSpam(self, msg) + self.move(msg, "Spam") + + def delete(self, msg): + IMAPMail.delete(self, msg) + self.move(msg, "Trash") + + def archive(self, msg): + IMAPMail.archive(self, msg) + self.move(msg, "Saved Mail") + + def open(self, msg): +# # this will open a new IE window so we don't need to show ours. +# self.getie().open_msg(msg.id) +# self.ie = None + + OpenAOLMail(self.name, self._decryptedpw(), msg.id) + + def urlForEmail(self, msg): + assert not self.mailclient + return UrlQuery('http://webmail.aol.com/Lite/MsgRead.aspx?', dict(folder='Inbox',uid='1.' +msg.id, seq='1', start='0')) + + def compose(self, to='', subject='', body='', cc='', bcc='', **k): + if self.mailclient: + print 'edit compose', to, subject, body, cc, bcc, k + #self.ie_compose(*a, **k) + body = body.replace('\n', '
') + ComposeAOLMail(self.name, self._decryptedpw(), to=to, subject=subject, body=body, **k) + print 'edit compose', 'done' + else: + print 'return url' + return self._compose(to, subject, body, cc, bcc, **k) + + def _compose(self, to='', subject='', body='', cc='', bcc='', **k): + ##TODO: match kwargs and return url + return UrlQuery("http://webmail.aol.com/25045/aim/en-us/Mail/compose-message.aspx", + to=to, subject=subject, body=body, cc=cc, bcc=bcc, **k) + + #@threaded +# def ie_compose(self, *a, **k): +# #self.getie().compose(*a, **k) +# self.ie = None + + @property + def inbox_url(self): + return "http://mail.aol.com" + + def goto_inbox(self): + if self.mailclient: + SelectAOLMail(self.name, self._decryptedpw()) +# self.getie().select_mail() +# self.getie().show(True) +# self.ie = None + + def start_client_email(self, email=None): + assert self.mailclient + if email is not None: + self.open(email) + else: + self.goto_inbox() + +# self.ie = None + +# def getie(self, f=lambda *a, **k:None): +# if not self.ie: +# self.ie = None#IEAOLMail(self.name, self._decryptedpw(), f) +# else: +# f() +# return self.ie +# +# def ie_lost(self, *a,**k): +# print 'lost ie' +# self.ie = None + + def _get_options(self): + opts = IMAPMail._get_options(self) + opts.pop('email_address', None) + opts.pop('mailclient', None) + return opts + +#class IEAOLMail(object): +# signout_url = 'http://my.screenname.aol.com/_cqr/logout/mcLogout.psp?sitedomain=startpage.aol.com&siteState=OrigUrl%3Dhttp%253A%252F%252Fwww.aol.com%252F' +# def __init__(self, un, pw, ready=lambda:None): +# self.un, self.pw = un, pw +# self.ready = ready +# self.init() +# self.show() +# +# def init(self): +# self.ie = GetIE() +# log.info('%s: Created IE' % self) +# self.ie._Bind('OnDocumentComplete', self.on_logout) +# self.ie.Navigate2(self.signout_url) +# +# self.show = self.ie.show +# self.hide = self.ie.hide +# +# def on_logout(self, evt=None): +# log.info('%s: Logged out' % self) +# self.ie._UnBind('OnDocumentComplete', self.on_logout) +# self.ie._Bind('OnDocumentComplete', self.on_loginpageload) +# self.ie.Navigate2("http://mail.aol.com") +# +# @property +# def select_mail(self): +# return JavaScript('SuiteSvc.SelectTab("mail");', self.ie) +# +# @property +# def open_msg(self): +# return JavaScript('frames["MailFrame"].document.getElementById("message1.%s")' +# '.children[3].children[0].onclick();', self.ie); +# +# def on_loginpageload(self, evt): +# assert not self.logged_in +# try: +# doc = self.ie.Document +# doc.forms(1).loginId.value = self.un +# doc.forms(1).password.value = self.pw +# except: +# return +# else: +# log.info('%s: Login page loaded, submitting' % self) +# self.ie._UnBind('OnDocumentComplete', self.on_loginpageload) +# self.ie._Bind('OnDocumentComplete', self.on_formsubmit) +# doc.forms(1).submit() +# +# def on_formsubmit(self, evt=None): +# if self.logged_in: +# log.info('%s: Logged in' % self) +# self.ie._UnBind('OnDocumentComplete', self.on_formsubmit) +# self.ie._Bind("OnDocumentComplete", self.on_mailboxload) +# +# def on_mailboxload(self, evt=None): +# try: +# #if self.ie.Document.parentWindow.SuiteSvc.IsLoaded +# self.select_mail() +# except: +# return +# else: +# self.ie._UnBind('OnDocumentComplete', self.on_mailboxload) +# +# log.info('%s: Mailbox loaded. Calling ready (%r)' % (self, self.ready)) +# self.ready() +# +# @property +# def logged_in(self): +# try: +# self.open_msg.Config +# except: +# return False +# else: +# return True +# +# def compose(self, to='', cc='', subject='', body=''): +# print 'compose', [to,cc,subject,body] +# try: +# cfg = self.open_msg.Config +# baseurl = cfg.BaseMailPagesURL +# except: +# baseurl = 'http://webmail.aol.com/27618/aim/en-us/Mail/' +# +# kw = dict() +# for name in 'to subject body cc'.split(): +# if vars()[name]: +# kw[name] = vars()[name] +# +# url = UrlQuery(baseurl + 'compose-message.aspx', **kw) +# self.ie.Navigate2(url) +# self.ie.show(True) +# +# def __bool__(self): +# return bool(self.ie) +# +# def __repr__(self): +# return '<%s>' % type(self).__name__ + +def OpenAOLMail(un,password,msgid): + from oscar import login2 + login2.go_to_msg(un.encode('utf-8'), password.encode('utf-8'), msgid) + + +#def OpenAOLMail(un,pw,msgid): +# ie = IEAOLMail(un, pw) +# +# def f(): +# +# #HAX: CallLater to wait for javascript to load :-( +# import wx +# +# def f2(): +# ie.open_msg(msgid) +# wx.CallLater(1000,ie.ie.Quit) +# +# wx.CallLater(2000,f2) +# +# ie.ready = f + +def SelectAOLMail(un,password): + from oscar import login2 + print "opening", un + login2.go_to_mail(un.encode('utf-8'), password.encode('utf-8')) + +#def SelectAOLMail(un,pw): +# ie = IEAOLMail(un, pw) +# def f(): +# ie.select_mail() +# ie.show() +# ie.ready = f + +def ComposeAOLMail(un,password,**k): + from oscar import login2 + login2.go_to_compose(un.encode('utf-8'), password.encode('utf-8'),**k) + +#def ComposeAOLMail(un,pw,*a,**k): +# ie = IEAOLMail(un, pw) +# def f(): +# ie.compose(*a,**k) +# ie.show() +# ie.ready = f + + +if __name__ == '__main__': + from wx.py.PyCrust import main + main() + diff --git a/digsby/src/mail/emailobj.py b/digsby/src/mail/emailobj.py new file mode 100644 index 0000000..cf72823 --- /dev/null +++ b/digsby/src/mail/emailobj.py @@ -0,0 +1,242 @@ +''' +Represents an email. +''' + +from util import autoassign, strip_html_and_tags, replace_newlines, Storage +from util.lrucache import lru_cache +from util.auxencodings import fuzzydecode +from email.utils import parseaddr, parsedate +from email.header import decode_header +from datetime import datetime +import email.message +from common import pref +import sys, traceback + +import logging +log = logging.getLogger('emailobj') + +replace_newlines = lru_cache(100)(replace_newlines) + +UnicodeErrors = (UnicodeEncodeError, UnicodeDecodeError) + +def unicode_hdr(hdr, fallback=None): + more_encs = [fallback] if fallback else [] + try: + return u''.join(fuzzydecode(hdr, [encoding,]+more_encs) if encoding else hdr + for (hdr, encoding) in decode_header(hdr)) + except UnicodeErrors: + # a unicode string with extended ascii characters gets into this function, this is what happens + # because the stdlib's decode_header function calls str() on its argument. + return fuzzydecode(hdr, more_encs+['utf-8']) + except Exception: + # an example header that raises ValueError (because of an int() call in email.header.decode_headers) + # 'You=?UTF8?Q?=E2=80=99?=re HOT, brian@thebdbulls.com =?UTF8?Q?=E2=80=93?= See if Someone Searched for You' + log.warning('decoding an email header failed: %r', hdr) + return fuzzydecode(hdr, more_encs+['utf-8']) + +def find_part(email, types): + if not email.is_multipart(): + # Our work here is done! + return email + + results = dict((part.get_content_type(), part) + for part in email + if part.get_content_type() in types) + + print results + + for ty in types: + if ty in results: + return results[ty] + + return None + +def find_attachments(email): + attachments = {} + + for part in email: + if (('Content-Disposition' in part.keys()) and + ('attachment' in part['Content-Disposition'])): + attachments[part.get_filename()] = Storage(data = part.get_payload(decode=True), + content_type = part.get_content_type()) + + return attachments + +def parse_content(part): + charset = part.get_content_charset() + content_type = part.get_content_type() + payload = part.get_payload(decode = True) + + html = (content_type == 'text/html') + + if payload is None: + payload = '' + + try: + content = payload.decode(charset or 'ascii') + except (UnicodeDecodeError, LookupError): + content = payload.decode('utf-8', 'replace') + + if html: + content = strip_html_and_tags(content, ['style']) + else: + content = content + + return content + + +class Email(object): + + def __init__(self, + id = None, + fromname = None, + fromemail = None, + sendtime = None, + subject = None, + content = None, + attachments = None, + labels = None, ): + autoassign(self, locals()) + + def update(self, email): + if isinstance(email, dict): + attrs = email + else: + attrs = vars(email) + + autoassign(self, dict((k, v) for k, v in attrs.iteritems() if v is not None)) + + @classmethod + def fromEmailMessage(cls, id, email, sendtime_if_error = None): + 'Creates an Email from a Python email.message.Message object.' + encoding = email.get_content_charset() + # parse name, address + realname, email_address = parseaddr(email['From']) + realname = unicode_hdr(realname, encoding) + + # parse date + + _email = email + + try: + datetuple = parsedate(email['Date']) + sendtime = datetime(*datetuple[:7]) + except Exception: + traceback.print_exc() + print >> sys.stderr, 'using %s for "sendtime" instead' % sendtime_if_error + sendtime = sendtime_if_error + + try: + attachments = find_attachments(email) + except: + attachments = {} + part = find_part(email, ('text/plain', 'text/html')) + + if part is None: + content = u'' + + else: + content = parse_content(part) + + content = replace_newlines(content) + + prev_length = pref('email.preview_length', 200) + if len(content) > prev_length: + content = content[:prev_length] + '...' + else: + content + + email = cls(id, realname, email_address, sendtime, email['Subject'], + content = content, attachments=attachments) + + return email + + def __eq__(self, other): + 'Email equality is determined by its "id" attribute.' + + if not isinstance(other, Email): + return False + + return self.id == other.id + + def __cmp__(self, other): + 'Makes emails.sort() newest -> oldest.' + try: + return -cmp(self.sendtime, other.sendtime) + except TypeError: + return -1 + + @property + def domain(self): + f = self.fromemail + if f is not None: + return f[f.find('@')+1:] + +# disabled b/c of #3535 +# +# @property +# def icon_badge(self): +# from common.favicons import favicon +# domain = self.domain +# if domain is not None: +# return favicon(self.domain) + + def __unicode__(self): + 'Returns a simple string representation of this email.' + + lines = [unicode('Subject: %s' % (self.subject or '')).encode("utf-8")] + + if self.fromname: + _fromstr = self.fromname + if self.fromemail: + _fromstr += ' <%s>' % self.fromemail + elif self.fromemail: + _fromstr = self.fromemail + else: + _fromstr = '' + lines.append('From: %s' % _fromstr) + + if self.sendtime: + lines.append('Sent at %s' % self.sendtime) + + if self.content: + lines.append('') + lines.append(unicode(self.content)) + self.lines = lines + return u''.join(lines) + + # Ascii representation...XML entities might be in here, so ignore + # them for console (ASCII). + __str__ = lambda self: self.__unicode__().decode('ascii', 'ignore') + + def __repr__(self): + + try: + return u'' % \ + (self.subject[:30], self.fromemail or self.fromname) + except Exception: + return u'' % self.fromemail + + +class DecodedEmail(email.message.Message): + def __init__(self, myemail): + self.email = myemail + # Why does this line break things? gah. + #email.message.Message.__init__(self) + + def __getattr__(self, attr, val = sentinel): + result = getattr(self.email, attr, val) + if result is sentinel: + raise AttributeError + else: + return result + + def __getitem__(self, header): + s = email.message.Message.__getitem__(self, header) + + if header == 'Content': + return s + else: + return unicode_hdr(s, self.get_content_charset()) + + __iter__ = email.message.Message.walk diff --git a/digsby/src/mail/hotmail/__init__.py b/digsby/src/mail/hotmail/__init__.py new file mode 100644 index 0000000..4885db4 --- /dev/null +++ b/digsby/src/mail/hotmail/__init__.py @@ -0,0 +1 @@ +from .hotmail import Hotmail diff --git a/digsby/src/mail/hotmail/ajax.py b/digsby/src/mail/hotmail/ajax.py new file mode 100644 index 0000000..702dd57 --- /dev/null +++ b/digsby/src/mail/hotmail/ajax.py @@ -0,0 +1,2676 @@ +''' +A partial translation/implementation of hotmail's object heirarchy RPC system. +Original source: http://gfx6.hotmail.com/mail/13.1.0132.0805/wlmcore{N}.js (for N from 1-4) +''' + +import random +import logging +import digsbysite +import common.asynchttp as asynchttp +import re +import util +import util.net as net +import util.primitives.funcs as funcs +import util.callbacks as callbacks +import weakref + +import uuid + +def ntok(): + return str(random.randint(0, 0x7fffffff)) + +log = logging.getLogger('hotmail.ajax') + +def Flags(*args): + return util.Storage(zip(args[::2], args[1::2])) + +# Known released builds: +# 1 - indicates anything prior to 13.3.3227.0707 +# 13.3.3227.0707 +# 15.1.3020.0910 +# 15.3.2495.0616 # Unreleased outside of microsoft as far as we know +# 15.3.2506.0629 # Windows Live Mail / Bing +# 15.4.0327.1028 # Detected on 2010-Nov-03. No changes to API. +# 15.4.0332.1110 # Detected on 2010-Nov-18. No changes to API. +# 15.4.3057.0112 # Detected on 2011-Jan-20. No changes to API. +# 15.4.3079.0223 # Detected on 2011-Mar-25. No changes to API. +# 15.4.3096.0406 # Detected on 2011-Apr-18. No changes to API. +# 16.0.1635.0608 # Detected on 2011-Jun-17. Lots of changes +# 16.2.2978.1206 # Detected on 2011-Dec-16. There don't seem to be any changes to the API, but the content of the page has changed. +# 16.2.4514.0219 # Detected on 2012-Mar-21. +# 16.2.6151.0801 # Detected on 2012-Sep-13. No changes to API + + +XMLPost = 0 +null = None + +import collections +_versioned_classes = collections.defaultdict(dict) + +def ForBuild(buildnum, clsname = None): + def decorator(cls): + _versioned_classes[clsname or cls.__name__][buildnum] = cls + return cls + return decorator + +class FppParamType(object): + String = "_string", + Date = "_date", + Array = "_array", + oArray = "_oArray", + Primitive = "_primitive", + Object = "_object", + Enum = "_enum", + Custom = "_custom" + +class TypeSystem(object): + _isFppObject = True + _default = None + def __init__(self, name_or_type, name=None, val=None): + if name is not None: + type = name_or_type + name = name + else: + type = FppParamType.Custom + name = name_or_type + + self.name = name + self.type = type + self.value = val + + @staticmethod + def escape(instance): + f = {'_string' : FppProxy.escape, + '_date' : lambda a: a, #FppProxy.dateToISO8601, + '_array' : FppProxy.arrayToString, + '_oArray' : FppProxy.objToStringImpl, + '_object' : FppProxy.objToStringImpl, + '_primitive' : FppProxy.primitiveToString, + '_enum' : lambda a: str(a), }.get(funcs.get(instance, 'type', None), FppProxy.objToString) + + escaped_val = f(funcs.get(instance, 'value', None)) + if escaped_val is None: + return 'null' + return escaped_val + + def __str__(self): + return '' % self.name + + def toString(self): + return TypeSystem.escape(self) + + @classmethod + def default(cls): + x = cls('') + if cls._default is None: + x.value = None + else: + x.value = type(cls._default)(cls._default) + return x + +class NamedTypeSystem(TypeSystem): + def __init__(self, name, val = None): + TypeSystem.__init__(self, type(self).__name__, name, val) + +class _string(NamedTypeSystem): + _default = '' +class _array(NamedTypeSystem): + _default = [] +class _enum(NamedTypeSystem): + _default = 0 +class _primitive(NamedTypeSystem): + _default = False +class _oArray(NamedTypeSystem): + _default = {} +class _object(NamedTypeSystem): + _default = {} +_custom = TypeSystem + +class FppProperty(object): + def __init__(self, name, typ, default = Sentinel): + self._name = name + self._type = typ + + if default is not Sentinel: + self._default = FppMetaclass._classes[self._type](name) + self._default.value = default # or .value = default + + def __get__(self, obj, objtype): + default = getattr(self, '_default', FppMetaclass._classes[self._type].default()) + if obj is None: # Getting the attribute from a class (not instance). Return default + return default + + if not hasattr(obj, '_field_%s' % self._name): + setattr(obj, '_field_%s' % self._name, default) + + val = getattr(obj, '_field_%s' % self._name) + if getattr(val, 'type', None) != self._type: + if val is None or val._isFppObject: + return val + raise ValueError("%r has an invalid %r (with type = %r, value = %r)", obj, self._name, val.type, val.value) + return val + + def __set__(self, obj, val): + if (val is not None and getattr(val, 'value', None) is not None) and hasattr(val, 'type'): + if val.type != self._type: + raise TypeError("%r must have type %r for the %r field. Got %r (value = %r) instead.", + obj, self._type, self._name, val.type, val.value) + elif val is None or getattr(val, '_isFppObject', False): + pass + else: + x = FppMetaclass._classes[self._type].default() + x.value = val + val = x + + setattr(obj, '_field_%s' % self._name, val) + +class FppMetaclass(type): + _classes = {'_enum' : _enum, + '_string': _string, + '_array' : _array, + '_primitive': _primitive, + '_object' : _object, + '_oArray' : _oArray, + '_custom' : TypeSystem, + } + def __init__(cls, name, bases, dict): + for field in cls._fields_: + setattr(cls, field[0], FppProperty(*field)) + + @classmethod + def default(_cls): + i = cls() + for _field in i._fields_: + if len(_field) == 2: + _default = FppMetaclass._classes[_field[1]].default() + elif len(_field) == 3: + if _field[2] is None: + _default = None + else: + _default = FppMetaclass._classes[_field[1]](_field[2]) + setattr(i, _field[0], _default) + return i + + dict['default'] = default + FppMetaclass._classes[name] = cls + cls.default = default + cls._isFppObject = True + type.__init__(cls, name, bases, dict) + +class FppClass(object): + _fields_ = [] + __metaclass__ = FppMetaclass + + def __init__(self, *args, **kwds): + fieldnames = [] + for field in self._fields_: + if len(field) == 3: + setattr(self, field[0], field[2]) + else: + setattr(self, field[0], FppMetaclass._classes[field[1]].default()) + + fieldnames.append(field[0]) + + for i in range(len(args)): + setattr(self, self._fields_[i][0], args[i]) + + for key in kwds: + setattr(self, key, kwds[key]) + + def __str__(self): + res = ['{'] + for field in self._fields_: + cls = FppMetaclass._classes[field[1]] + val = getattr(self, field[0]) + if val is None: + res.append('null') + else: + res.append(cls.escape(val)) + res.append(',') + if res[ - 1] == ',': + res.pop(- 1) + + res.append('}') + return ''.join(res) + + toString = escape = __str__ + def __repr__(self): + return '<%s %s>' % (type(self).__name__, ' '.join('%s=%r' % (entry[0], getattr(self, entry[0], None)) for entry in self._fields_)) + +class FppError(FppClass): + _fields_ = [ + ('ErrorCode', '_string'), + ('Message', '_string'), + ('ErrorObj', '_object'), + ('StackTrace', '_string'), + ] + +class FppReturnPackage(FppClass): + _fields_ = [ + ('Status', '_enum'), + ('Value', '_object'), + ('OutRefParams', '_oArray'), + ('Error', 'FppError'), + ('ProfilingInfo', '_object'), + ] + +@ForBuild('1') +class InboxUiData(FppClass): + _fields_ = [ + ('FolderListHtml', '_string'), + ('MessageListHtml', '_string'), + ('MessageHtml', '_string'), + ('RedirectUrl', '_string'), + ] + +@ForBuild('13.3.3227.0707') +class InboxUiData(FppClass): + _fields_ = [ + ('FolderListHtml', '_string'), + ('MessageListHtml', '_string'), + ('MessageHtml', '_string'), + ('InfoPaneHtml', '_string'), + ('RedirectUrl', '_string'), + ] + +@ForBuild("15.3.2495.0616") +class InboxUiData(FppClass): + _fields_ = [ + ('FolderListHtml', '_string'), + ('QuickViewListHtml', '_string'), + ('MessageListHtml', '_string'), + ('MessageHtml', '_string'), + ('SuggestionHtml', '_string'), + ('BatchInfo', 'BatchInfo'), + ('MltRootHtml', '_string'), + ('MltSubHtml', '_string'), + ('InfoPaneHtml', '_string'), + ('RedirectUrl', '_string'), + ('Content', '_object'), + ('BiciJsonConfig', '_string') + ] + +@ForBuild("16.0.1635.0608") +class InboxUiData(FppClass): + _fields_ = [ + ('FolderListHtml', '_string'), + ('QuickViewListHtml', '_string'), + ('MessageListHtml', '_string'), + ('MessageHtml', '_string'), + ('ItemInfo', 'ItemResponseInfo'), + ('SuggestionHtml', '_string'), + ('BatchInfo', 'BatchInfo'), + ('MltRootHtml', '_string'), + ('MltSubHtml', '_string'), + ('InfoPaneHtml', '_string'), + ('RedirectUrl', '_string'), + ('Content', '_object'), + ('BiciJsonConfig', '_string'), + ] + +@ForBuild('16.2.2978.1206') +class InboxUiData(FppClass): + _fields_ = [ + ('FolderListHtml', '_string'), + ('QuickViewListHtml', '_string'), + ('MessageListHtml', '_string'), + ('MessageHtml', '_string'), + ('ItemInfo', 'ItemResponseInfo'), + ('SuggestionHtml', '_string'), + ('BatchInfo', 'BatchInfo'), + ('MltRootHtml', '_string'), + ('MltSubHtml', '_string'), + ('Categories', '_array'), + ('InfoPaneHtml', '_string'), + ('RedirectUrl', '_string'), + ('Content', '_object'), + ('BiciJsonConfig', '_string'), + ] + +class HmCandidacyInfo(FppClass): + _fields_ = [ + ('Status', '_enum'), + ('Guid', '_string'), + ('ShowMakeLiveContact', '_enum'), + ] + +@ForBuild('1') +class HmAuxData(FppClass): + QueryArgName = 'Aux' + _fields_ = [ + ('Value', '_string'), + ("LiveContactCandidacyInfo", 'HmCandidacyInfo') + ] + + @classmethod + def for_msg(cls, msg): + return cls(Value = msg._mad.replace('||', '|'), + LiveContactCandidacyInfo = None) + +@ForBuild('15.1.3020.0910') +class HmAuxData(FppClass): + QueryArgName = 'Aux' + _fields_ = [ + ('Value', '_string'), + ] + + @classmethod + def for_msg(cls, msg): + if msg._mad in (None, ""): + return "" + return cls(Value = msg._mad) + +@ForBuild('16.0.1635.0608') +class ItemResponseInfo(FppClass): + _fields_ = [ + ('IsConversation', '_primitive'), + ('View', '_enum'), + ('ConversationId', '_string'), + ('MessageId', '_string'), + ('MessageIds', '_array'), + ('SenderEmails', '_array'), + ('IsDedup', '_primitive'), + ('IdentityContext', '_string'), + ('PsaType', '_enum'), + ('PsaUrl', '_string'), + ('ReadInstrumentation', '_object'), + ('Html', '_string'), + ] + +@ForBuild('1') # I don't know the build number of the old version of livemail, but this will let the class get used correctly (if it's still necessary) +class MessageRenderingInfo(FppClass): + _fields_ = [ + ('MessageId', '_string'), + ('FolderId', '_string'), + ('OpenMessageBody', '_primitive', True), + ('AllowUnsafeContent', '_primitive', False), + ('OverrideCodepage', '_primitive', -1,), + ('HmAuxData', "HmAuxData"), + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive', False), + ('Action', '_enum'), + ] + + def __init__(self, **k): + + if 'AuxData' in k: + k['HmAuxData'] = k.pop('AuxData') + + FppClass.__init__(self, **k) + +@ForBuild('13.3.3227.0707') +class MessageRenderingInfo(FppClass): + _fields_ = [ + ('MessageId', '_string'), + ('FolderId', '_string'), + ('OpenMessageBody', '_primitive', True), + ('AllowUnsafeContent', '_primitive', False), + ('OverrideCodepage', '_primitive', -1), + ('UnknownArgument', '_primitive', None), # recently added + ('HmAuxData', "HmAuxData"), + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive'), + ('Action', '_enum'), + ('UnknownArgument2', '_primitive', True), # recently added + ] + + def __init__(self, **k): + + if 'AuxData' in k: + k['HmAuxData'] = k.pop('AuxData') + + FppClass.__init__(self, **k) + +@ForBuild("15.3.2495.0616") +class MessageRenderingInfo(FppClass): + _fields_ = [ + ('MessageId', '_string'), + ('AllowUnsafeContent', '_primitive'), + ('OverrideCodepage', '_primitive', -1), + ('MtLang', '_string'), + ('AuxData', 'HmAuxData'), + ('ConversationUpsell', '_primitive'), + ('FolderId', '_string'), + ('MarkAsRead', '_primitive', True), + ('SenderEmail', '_string') + ] + + def __init__(self, **k): + + if 'HmAuxData' in k: + k['AuxData'] = k.pop('HmAuxData') + + FppClass.__init__(self, **k) + +@ForBuild('15.3.2495.0616') +class AdvancedSearch(FppClass): + + _fields_ = [ + ('Keywords', '_string'), + ('From', '_string'), + ('To', '_string'), + ('Subject', '_string'), + ('Folder', '_string'), + ('DateBegin', '_string'), + ('DateEnd', '_string'), + ('HasAttachment', '_primitive') + ] + +@ForBuild('1') +class MessageListRenderingInfo(FppClass): + _fields_ = [ + ('FolderId', '_string'), + ('PageSize', '_primitive', 25), + ('PageDirection', '_enum', 'FirstPage'), + ('PageSkip', '_primitive', 0), + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive'), + ('AnchorMessageId', '_string', str(uuid.UUID(int=0))), + ('AnchorMessageDate', '_string'), + ('PageNumCurrent', '_primitive', 1), + ('PageNumMidStart', '_primitive', 2), + ('IsSearchResults', '_primitive'), + ('SearchKeyword', '_string'), + ('IsRtl', '_primitive'), + ('MessageCount', '_primitive', 99), + ] + +@ForBuild('13.3.3227.0707') +class MessageListRenderingInfo(FppClass): + _fields_ = [ + ('FolderId', '_string'), + ('PageDirection', '_enum', 'FirstPage'), + ('PageSkip', '_primitive', 0), + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive'), + ('AnchorMessageId', '_string', str(uuid.UUID(int=0))), + ('AnchorMessageDate', '_string'), + ('PageNumCurrent', '_primitive', 1), + ('PageNumMidStart', '_primitive', 2), + ('IsSearchResults', '_primitive'), + ('SearchKeyword', '_string'), + ('MessageCount', '_primitive', 99), + ('AutoSelectMessageIndex', '_primitive', -1), + ('ReadingPaneLocation', '_enum', 'None'), + ] + + +@ForBuild("15.3.2495.0616") +class MessageListRenderingInfo(FppClass): + _fields_ = [ + ('FolderId', '_string'), + ('QuickViewId', '_primitive', None), + ('FilterId', '_primitive', None), + ('PageDirection', '_enum', 'FirstPage'), + ('ExtraFetchCount', '_primitive', 99), + ('PageNumCurrent', '_primitive', 1), + ('AnchorId', '_string', str(uuid.UUID(int=0))), + ('AnchorDate', '_string', ""), + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive', False), + ('IsSearchResults', '_primitive', False), + ('SearchKeyword', '_string', ""), + ('AdvancedSearch', 'AdvancedSearch', None), + ('AutoSelectMessageIndex', '_primitive', -1), + ('ReadingPaneLocation', '_enum', 'Off'), + ('MessageCount', '_primitive', -1), + ('BulkSelectAllTimestamp', '_string', None), + ('LastUpdateTimestamp', '_string', None) + ] + +@ForBuild("15.4.0317.0921") +class MessageListRenderingInfo(FppClass): + _fields_ = [ + ('FolderId', '_string'), + ('QuickViewId', '_primitive', null), + ('FilterId', '_primitive', null), + ('PageDirection', '_enum', 'FirstPage'), + ('ExtraFetchCount', '_primitive', 5), + ('PageNumCurrent', '_primitive', 1), + ('AnchorId', '_string', str(uuid.UUID(int=0))), + ('AnchorDate', '_string', null), + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive', False), + ('IsSearchResults', '_primitive'), + ('SearchKeyword', '_string', null), + ('AdvancedSearch', 'AdvancedSearch', null), + ('AutoSelectMessageIndex', '_primitive', -1), + ('ReadingPaneLocationMember', '_enum', 'Off'), + ('NumberOfMessages', '_primitive', -1), + ('BulkSelectAllTimestamp', '_string', null), + ('LastUpdateTimestamp', '_string', null), + ('IsFavoriteSelected', '_primitive', False), + ] + +@ForBuild('16.0.1635.0608') +class MessageListRenderingInfo(FppClass): + _fields_ = [ + ('FolderId', '_string'), + ('QuickViewId', '_primitive', null), + ('FilterId', '_primitive', null), + ('PageDirection', '_enum', 'FirstPage'), + ('ExtraFetchCount', '_primitive', 5), + ('PageNumCurrent', '_primitive', 1), + ('AnchorId', '_string', str(uuid.UUID(int=0))), + ('AnchorDate', '_string', ''), + ('JumpToAnchor', '_string', null), # New + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive', False), + ('IsSearchResults', '_primitive', False), + ('SearchKeyword', '_string', ''), + ('AdvancedSearch', 'AdvancedSearch', null), + ('AutoSelectMessageIndex', '_primitive', -1), + ('ReadingPaneLocationMember', '_enum', 'Right'), + ('NumberOfMessages', '_primitive', -1), + ('BulkSelectAllTimestamp', '_string', null), + ('LastUpdateTimestamp', '_string', null), + ('IsFavoriteSelected', '_primitive', True), + ] + +@ForBuild('16.2.2978.1206') +class MessageListRenderingInfo(FppClass): + _fields_ = [ + ('FolderId', '_string'), + ('QuickViewId', '_primitive', null), + ('FilterId', '_primitive', null), + ('PageDirection', '_enum', 'FirstPage'), + ('ExtraFetchCount', '_primitive', 5), + ('PageNumCurrent', '_primitive', 1), + ('AnchorId', '_string', str(uuid.UUID(int=0))), + ('AnchorDate', '_string', ''), + ('JumpToAnchor', '_string', null), # New + ('SortKey', '_enum', 'Date'), + ('SortAsc', '_primitive', False), + ('IsSearchResults', '_primitive', False), + ('SearchKeyword', '_string', ''), + ('AdvancedSearch', 'AdvancedSearch', null), + ('AutoSelectMessageIndex', '_primitive', -1), + ('ServerAutoSelectMessageIndex', "_primitive", -1), + ('InitializeAutoSelectIndexesToFirstMessage', '_primitive', False), + ('ReadingPaneLocationMember', '_enum', 'Right'), + ('NumberOfMessages', '_primitive', -1), + ('BulkSelectAllTimestamp', '_string', null), + ('LastUpdateTimestamp', '_string', null), + ('IsFavoriteSelected', '_primitive', True), + ] + +class HmSimpleMsg(FppClass): + _fields_ = [ + ('IsBlocking', '_primitive'), + ('YesCode', '_primitive'), + ('NoCode', '_primitive'), + ('Message', '_string'), + ] + +class __2(FppClass): + _fields_ = [ + ('IsBlocking', '_primitive'), + ('YesCode', '_primitive'), + ('NoCode', '_primitive'), + ('Message', '_string'), + ] + +class __5(FppClass): + _fields_ = [ + ("ExistingContacts", "_array"), + ("PotentialContacts", "_array"), + ("HasExistingContacts", "_primitive"), + ("HasPotentialContacts", "_primitive"), + ] + +class __0(FppClass): + _fields_ = [ +('Url', '_string'), ('CommandCode', '_primitive'), ('Text', '_string')] + +class __1(FppClass): + _fields_ = [ + ('MessageType', '_enum'), + ('InfoCode', '_primitive'), + ('Message', '_string'), + ('ExtendedMessage', '_string'), + ('PSValue', '_string') + ] + +class __3(FppClass): + _fields_ = [ + ('FileName', '_string'), + ('FileId', '_string'), + ('Success', '_primitive'), + ('ShowMessage', '_primitive'), + ('ErrorCode', '_primitive')] + +class ABContact(FppClass): + _fields_ = [ + ('DisplayName', '_string'), + ('PreferredEmail', '_string'), + ('ContactType', '_enum'), + ('PassportName', '_string'), + ('Guid', '_string'), + ('IsMessengerUser', '_primitive'), + ('IsFavorite', '_primitive'), + ('Cid', '_string'), + ('Emails', '_array'), + ('GleamState', '_enum')] + +class ABDetailedContact(FppClass): + _fields_ = [ + ('ContactType', '_enum'), + ('FirstName', '_string'), + ('LastName', '_string'), + ('PassportName', '_string'), + ('NickName', '_string'), + ('Comment', '_string'), + ('IsMessengerUser', '_primitive'), + ('IsSmtpContact', '_primitive'), + ('IsFavorite', '_primitive'), + ('Emails', '_array'), + ('Phones', '_array'), + ('Locations', '_array'), + ('WebSites', '_array'), + ('Dates', '_array'), + ('Guid', '_string'), + ('Cid', '_string')] + +class ABGroup(FppClass): + _fields_ = [ + ('Guid', '_string'), + ('QuickName', '_string') + ] + +class ABDetailedGroup(FppClass): + _fields_ = [('Guid', '_string'), ('QuickName', '_string'), ('Count', '_primitive')] + +class __4(FppClass): + _fields_ = [ + ('Groups', '_array'), + ('Contacts', '_array'), + ('FileAs', '_enum'), + ('SelectedGuid', '_string'), + ("SelectedGroup", 'ABDetailedGroup'), + ('SelectedContact', 'ABDetailedContact'), + ('HasSelectedGuid', '_primitive'), + ('HasSelectedGroup', '_primitive')] + +class ABEmail(FppClass): + _fields_ = [('Type', '_enum'), ('Email', '_string')] + +class ABPhone(FppClass): + _fields_ = [('Type', '_enum'), ('Phone', '_string')] + +class ABLocation(FppClass): + _fields_ = [ + ('Name', '_string'), + ('Street', '_string'), + ('City', '_string'), + ('State', '_string'), + ('Country', '_string'), + ('PostalCode', '_string')] + +class ABDate(FppClass): + _fields_ = [('Day', '_string'), ('Month', '_string')] + +class ABWebSite(FppClass): + _fields_ = [] + +class __6(FppClass): + _fields_ = [('MailboxSize', '_string'), ('MailboxQuota', '_string')] + +class __7(FppClass): + _fields_ = [ + ('FolderId', '_string'), + ('Name', '_string'), + ('Icon', '_string'), + ('UnreadMessagesCount', '_primitive'), + ('TotalMessagesCount', '_primitive'), + ('Size', '_string'), + ('IsSystem', '_primitive'), + ('IsHidden', '_primitive'), + ('FolderType', '_enum'), + ('SystemFolderType', '_enum')] + + +class __8(FppClass): + _fields_ = [('Name', '_string'), ('Address', '_string'), ('EncodedName', '_string')] + +class __9(FppClass): + _fields_ = [('Name', '_string')] + +class __10(FppClass): + _fields_ = [ + ('SenderIDResult', '_enum'), + ('SenderEmail', '__8'), + ('IsKnownSender', '_primitive'), + ('ListUnsubscribeEmail', '_string'), + ('IsSenderInContactList', '_primitive')] + +class __12(FppClass): + _fields_ = [ + ('DidSenderIDPass', '_primitive'), + ('DidSenderIDFail', '_primitive'), + ('IsBlockAvailableInBL', '_primitive'), + ('IsSameDomainInBL', '_primitive'), + ('IsSafeListDomain', '_primitive'), + ('IsMailingList', '_primitive'), + ('IsSenderHeaderPresent', '_primitive'), + ('IsListUnsubscribePresent', '_primitive'), + ('IsListUnsubscribeInEmailFormat', '_primitive'), + ('HasReachedMaxFilterLimit', '_primitive'), + ('IsNeverAllowOrBlockDomain', '_primitive'), + ('IsBlockSenderException', '_primitive')] + +class __13(FppClass): + _fields_ = [ + ('IsFromPRAOnBlockList', '_primitive'), + ('HasReachedSafeListLimit', '_primitive'), + ('HasEntriesFromSameDomainInSafeList', '_primitive'), + ('IsDomainSafe', '_primitive'), + ('IsSingleToAndNotRecipient', '_primitive'), + ('HasFilterToJunkToAddress', '_primitive'), + ('IsRecipientAddressRFCCompliant', '_primitive'), + ('HasReachedMailingListLimit', '_primitive'), + ('IsNeverAllowOrBlockDomain', '_primitive'), + ('IsInContacts', '_primitive')] + +class __14(FppClass): + _fields_ = [ + ('Action', '_enum'), + ('Reason', '_string'), + ('CalendarEventUrl', '_string'), + ('Subject', '_string'), + ('To', '_string'), + ('Where', '_string'), + ('When', '_string')] + +class __16(FppClass): + _fields_ = [ + ('Header', '__22'), + ('Body', '_string'), + ('Attachments', '_array'), + ('ToLineString', '_string'), + ('CCLineString', '_string'), + ('BccLineString', '_string'), + ('Rfc822References', '_string'), + ('Rfc822MessageId', '_string'), + ('Rfc822InReplyTo', '_string'), + ('DateSentLocal', '_string'), + ('DateReceivedLocal', '_string'), + ('SafetyLevel', '_enum'), + ("MailSenderInfo", '__10'), + ("MeetingResponseInfo", '__14'), + ('MeetingIcalId', '_string'), + ('ReplyFromAddress', '_string'), + ('HasPhishingLinks', '_primitive'), + ('IsVerifiedMail', '_primitive'), + ('AllowUnsafeContentOverride', '_primitive'), + ('UnsafeContentFiltered', '_primitive'), + ('UnsafeImagesFiltered', '_primitive'), + ('DetectedCodePages', '_array'), + ('CurrentCodePage', '_primitive'), + ('DraftId', '_string') + ] + +class __17(FppClass): + _fields_ = [ + ('ContentType', '_string'), + ('Name', '_string'), + ('Size', '_string'), + ('BodyIndex', '_primitive'), + ('AttachmentIndex', '_primitive'), + ('ForwardId', '_string') + ] + +class __18(FppClass): + _fields_ = [('EOF', '_primitive')] + +class __19(FppClass): + _fields_ = [('Prefix', '_string'), ('Text', '_string')] + +class __21(FppClass): + _fields_ = [ + ('MessageId', '_string'), + ('IsRead', '_primitive'), + ('TimeStamp', '_string'), + ('IsDraft', '_primitive'), + ('CP', '_primitive'), + ('AllowUnsafeContent', '_primitive'), + ('IsVoicemail', '_primitive'), + ('IsCalllog', '_primitive'), + ('IsPrivateVoicemail', '_primitive'), + ('IsMeetingReq', '_primitive')] + +class __22(FppClass): + _fields_ = [ + ('AuxData', 'HmAuxData'), + ('MessageId', '_string'), + ('OriginalMessageId', '_string'), + ('FolderId', '_string'), + ('ExtendedType', '_enum'), + ('TypeData', '_enum'), + ('IsRead', '_primitive'), + ('PopSettingIndex', '_primitive'), + ('OriginalReplyState', '_enum'), + ('IsInWhiteList', '_primitive'), + ('SentState', '_enum'), + ('MessageSize', '_string'), + ('HasAttachments', '_primitive'), + ('From', '__8'), + ('Subject', '__19'), + ('DateReceivedUTC', '_date'), + ('DateReceived', '_date'), + ('Importance', '_enum'), + ('IsDraft', '_primitive'), + ('Marker', '__18'), + ('MessageSizeString', '_string'), + ('DateReceivedLocal', '_string'), + ('TimeStamp', '_string')] + + +class BootstrapSeed(FppClass): + _fields_ = [ + ('Mode', '_enum'), + ('FolderId', '_string'), + ('messageId', '_string'), + ('count', '_primitive'), + ('ascendingOrder', '_primitive'), + ('pageSize', '_primitive'), + ('totalMessages', '_primitive'), + ('renderHtml', '_primitive'), + ('returnHeaders', '_primitive'), + ('sortBy', '_enum')] + +class __23(FppClass): + _fields_ = [ + ('User', '_string'), + ('UserName', '_string'), + ('Timestamp', '_string'), + ('Configuration', '__26'), + ('Folders', '_array'), + ('MessageInfo', '__24'), + ('TodayPage', '_string')] + + +class __24(FppClass): + _fields_ = [ + ('MessageListHtml', '_string'), + ('Headers', '_array'), + ('HeaderTags', '_array'), + ('MessageHtml', '_string'), + ('SelectedFolderId', '_string'), + ('SelectedMessageIndex', '_primitive'), + ('IsPlainText', '_primitive'), + ('OverrideCodePage', '_primitive'), + ('AllowUnsafeContent', '_primitive')] + +class __25(FppClass): + _fields_ = [ + ('Signature', '_string'), ('FromAddresses', '_array')] + +class __26(FppClass): + _fields_ = [ + ('DefaultMsgsInListView', '_primitive'), + ('KeyboardPressesDelay', '_primitive'), + ('CachePagesOfMessageHeaders', '_primitive'), + ('EnableReadingPane', '_primitive'), + ('HasAcceptedJunkReporting', '_primitive'), + ('JunkReportingUISeen', '_primitive'), + ('DefaultContactsInListview', '_primitive'), + ('SpacesContactBindingEnabled', '_primitive'), + ('DoSpellCheckAsYouType', '_primitive'), + ('SpellCheckEnabledInLocale', '_primitive'), + ('ReadingPaneConfiguration', '_enum'), + ('AutoSelectMessage', '_primitive'), + ('MinimumIntervalBetweenSpellChecks', '_primitive'), + ('UserThemeID', '_primitive'), + ('SaveSentMessages', '_primitive'), + ('BalloonTipsEnabled', '_primitive'), + ('BalloonTipUserPreference', '_primitive'), + ('IsBigInbox', '_primitive'), + ('IsAdsDown', '_primitive'), + ('ForwardingOn', '_primitive')] + +class __27(FppClass): + _fields_ = [('ErrorCode', '_string'), ('Folders', '_array'), ('Headers', '_array')] + +@ForBuild("15.3.2495.0616") +class BatchInfo(FppClass): + _fields_ = [ + ('AnchorDate', '_string'), + ('AnchorId', '_string'), + ('ProcessedCount', '_primitive'), + ('IsLastBatch', '_primitive'), + ('Timestamp', '_string'), + ('SenderSearch', '_string') + ] + +@ForBuild("15.3.2495.0616") +class ConversationRenderingInfo(FppClass): + _fields_ = [ + ('ConversationId', '_string'), + ('MessageId', '_string'), + ('FolderId', '_string'), + ('MarkAsRead', '_primitive', True), + ('SenderEmail', '_string') + ] + +@ForBuild("15.3.2495.0616") +class MessagePartsUiData(FppClass): + _fields_ = [ + ('MessagePartData', '_object'), + ('IdentityContext', '_string'), + ('InfoPaneHtml', '_string'), + ('RedirectUrl', '_string'), + ('Content', '_object'), + ('BiciJsonConfig', '_string') + ] + +@ForBuild('15.3.2495.0616') +class HMLiveViewRequestObject(FppClass): + _fields_ = [ + ('Id', '_string'), + ('Sender', '_string'), + ('Type', '_string'), + ('SubType', '_string'), + ('Data', '_string') + ] + +@ForBuild("15.4.0317.0921") +class HMLiveViewRequestObject(FppClass): + _fields_ = [ + ('Id', '_string'), + ('Sender', '_string'), + ('Type', '_string'), + ('SubType', '_string'), + ('Data', '_string'), + ('ExtraData', '_string'), + ] + +@ForBuild('15.3.2495.0616') +class HMLiveViewResponseObject(FppClass): + _fields_ = [ + ('Id', '_string'), + ('StatusCode', '_enum'), + ('Html', '_string') + ] + +@ForBuild('15.3.2495.0616') +class SandboxRequest(FppClass): + _fields_ = [ + ('ID', '_string'), + ('Uri', '_string'), + ('Provider', '_string'), + ('PostBody', '_string'), + ('AuthOption', '_enum'), + ('AuthParam', '_string') + ] + +@ForBuild('15.3.2495.0616') +class SandboxResponse(FppClass): + _fields_ = [ + ('ID', '_string'), + ('Status', '_primitive'), + ('HttpStatusCode', '_primitive'), + ('Body', '_string'), + ('AuthOption', '_enum'), + ('AuthParam', '_string') + ] + +@ForBuild('16.0.1635.0608') +class MiniCalendarUiObject(FppClass): + _fields_ = [ + ('HTML', '_string'), + ('DateString', '_string'), + ('TargetDate', '_oArray'), + ('IsVisible', '_primitive'), + ('PrevMonthString', '_string'), + ('NextMonthString', '_string'), + ('TodayString', '_string'), + ('Type', '_enum'), + ] + +@ForBuild('16.0.1635.0608') +class MiniCalendarOptions(FppClass): + _fields_ = [ + ('NumberOfPastObjects', '_primitive'), + ('NumberOfFutureObjects', '_primitive'), + ('NeedAdditionalDateStrings', '_primitive'), + ('DateStart', '_string'), + ('DateEnd', '_string'), + ] + +@ForBuild('16.0.1635.0608') +class ComposeData(FppClass): + _fields_ = [ + ('AttachmentIdHeaders', '_array'), + ('InlineIdHeaders', '_array'), + ('MessageBody', '_string'), + ('Subject', '_string'), + ('To', '_string'), + ('CC', '_string'), + ('BCC', '_string'), + ('From', '_string'), + ('DraftId', '_string'), + ('MeetingIcalId', '_string'), + ('MessageId', '_string'), + ('OriginalMessageId', '_string'), + ('Priority', '_primitive'), + ('SentState', '_enum'), + ('Rfc822InReplyTo', '_string'), + ('Rfc822MessageId', '_string'), + ('Rfc822References', '_string'), + ] + +@ForBuild('16.0.1635.0608') +class MessagePrefetchData(FppClass): + _fields_ = [ + ('Data', '_array'), + ] + + +class FppProxy(object): + @staticmethod + def escape(b): + if b is None: return b + a = ""; + def slash_escape(m): + return '\\' + m.group(0) + + if isinstance(b, unicode): + _b, b = b, b.encode('utf-8') + + a = ''.join(['"', str(b), '"']) + a = re.sub(r'([\{|\}\[|\]\,\\:])', slash_escape, a).encode('url') + return a + + @staticmethod + def objToStringImpl(a): + t = type(a) + + if t is unicode: + a = a.encode('utf-8') + t = str + + if (a is None): + return 'null' + elif t is str: + return FppProxy.escape(a) + elif t is list: + return FppProxy.arrayToString(a) + elif t is dict or hasattr(a, 'toString') or getattr(a, '_isFppObject', False): + return a.toString() + else: + return FppProxy.objToString(a) + + @staticmethod + def primitiveToString(a): + if a in ("", None, null, Null): + return 'null' + + return str(a).lower() + + @staticmethod + def arrayToString(a): + res = ['['] + for x in a: + res.append(FppProxy.objToStringImpl(x)) + res.append(',') + + if a: + res.pop() # remove last ',' + + res.append(']') + return ''.join(res) + + @staticmethod + def objToString(c): + if c is None: + return 'null' + a = ['{'] + for field in getattr(c, '_fields_'): + #a.append(FppProxy.escape(name)) + #a.append(':') + a.append(FppProxy.objToStringImpl(getattr(c, field[0]))) + a.append(',') + a[ - 1] = '}' + return ''.join(a) + +class Network_Type(object): + configuration = None + + def __init__(self, b, opener=None, start_config = {}): + self._isIE = False + self._isMoz = True + self._requests = [] + self.configuration = b + self.opener = opener + self.configuration.__dict__.update(start_config) + self.configuration.SessionId = self.configuration.SessionId.decode('url') + self.HM = None + + def set_HM(self, hm): + self.HM = hm + + def set_base_url(self, baseurl): + self.base_url = baseurl + + def createRequest(self, url, callback, verb): + return HM_Request(url, callback, self, self.opener, verb) + + @util.callsback + def process_response(self, resp, callback): + data = resp.read() + log.info('Processing hotmail response: %r', data) + + if ((resp.code // 100) != 2): + e = Exception("%r was not successful (code = %r)", resp, resp.code) + log.info('%r', e) + return callback.error(e) + + result = None + try: + data, _data = data.replace('new HM.', ' HM.'), data + d = dict(HM = self.HM, true = True, false = False, null = None) + exec "result = " + data in d, d + result = d['result'] + except Exception, e: + log.info("Could not process javascript response: %r", _data) + log.info("\terror was: %r", e) + data = _data + return self._process_response_re(data, callback) + + if result.Status.value == 0: + return callback.success(result.Value) + else: + log.error("Got a non-zero status code (%r). Here\'s the whole response: %r", result.Status.value, _data) + return callback.error(result.Error) + + def _process_response_re(self, data, callback): + match = re.search(r'new HM\.FppReturnPackage\((\S+?),', data) + if match is None: + e = Exception('Response has unknown status code: %r', data) + log.info('%r', e) + return callback.error(e) + + status = match.group(1) + log.info('Got status code %r for hotmail request. data was: %r', status, data) + + try: + status = int(status) + except (ValueError, TypeError): + e = Exception('Status code could not be int()\'d. it was %r (the whole response was %r)', status, data) + log.info('%r', e) + return callback.error(e) + + if status != 0: + e = Exception('Got a non-zero status code (%r). Here\'s the whole response: %r', status, data) + log.info('%r', e) + return callback.error(e) + + return callback.success(data) + +class HM_Request(object): + def __init__(self, url, cb, network, opener, verb=None): + self.url = url + self.callback = cb + self.verb = verb or 'GET' + self.postString = self.context = None + self.headers = {} + self.opener = weakref.ref(opener) + self.network = network + + def send(self, context): + self.context = context + r = asynchttp.HTTPRequest(self.url, self.postString, self.headers, method=self.verb, adjust_headers = False) + log.debug('opening request: method=%r url=%r post_data=%r headers=%r', self.verb, self.url, self.postString, self.headers) + cbargs = dict(success=self.on_response, error=self.on_error) + if self.opener is None: + asynchttp.httpopen(r, **cbargs) + else: + opener = self.opener() + if opener is None: + log.info('weakref\'d opener is gone, not doing request %r', self.url) + return + opener.open(r, **cbargs) +# util.threaded(lambda: opener.open(r))(**cbargs) + + def on_response(self, req_or_resp = None, resp=None): + network = getattr(self, 'network', None) + resp = resp or req_or_resp + if network is not None: + log.info('Got response for %r: %r', self, resp) + self.network.process_response(resp, callback=self.callback) + self.abort() + else: + log.warning('This request (%r) already got response, so not doing anything with this response: %r', self, resp) + + def on_error(self, e=None, resp = None): + log.error('Error in hotmail request: %r', e) + self.callback.error(resp or e) + self.abort() + + def abort(self): + self.__dict__.clear() + self.__getattr__ = Null + + def __repr__(self): + return '<%s %s>' % (type(self).__name__, ' '.join('%s=%r' % (x, getattr(self, x)) for x in ('verb', 'url', 'postString'))) + +class FPPConfig(object): + RequestHandler = 'mail.fpp' + FppVersion = '1' + SessionId = '' + AuthUser = '' + CanaryToken = 'mt' + Version = '1' + PartnerID = '' + + def __init__(self, hotmail): + + self.hotmail = weakref.ref(hotmail) + + @property + def CanaryValue(self): + hm = self.hotmail() + if hm is None: + return None + + return hm.get_cookie(self.CanaryToken, domain = '.mail.live.com') + +class PageInfo(object): + fppCfg = None + SELF_PATH = '/mail/InboxLight.aspx' + queryString = {'nonce' : "2122195423"} + +def get_build_for(my_build, builds): + build = None + for build in reversed(sorted(builds)): + if my_build >= build: + return build + + return build + +class FppMethod(object): + def __init__(self, impls): + # impls: a str->tuple dictionary mapping build numbers to an FppMethod interface + self.impls = impls + + def __get__(self, obj, objtype): + my_build = obj.build + + build = get_build_for(my_build, self.impls.keys()) + details = self.impls.get(build) + + if details is None: + raise AttributeError("not valid for build %r" % my_build) + + return FppMethodImpl(obj, *details) + +class FppMethodImpl(object): + def __init__(self, HM, params, method_name, tm, g, namespace): + self.HM = HM + self.method_name = self.name = method_name + + new_params = [] + for param in params: + if isinstance(param, tuple): + name, typ = param + cls = FppMetaclass._classes[typ] + if type(cls) is FppMetaclass: + new_params.append(_custom(name)) + else: + new_params.append(cls(name)) + else: + new_params.append(param) + + self.params = new_params + self.tm = tm + self.g = g + self.namespace = namespace + + def __call__(self, *args, **kwargs): + arg_names = [x.name for x in self.params] + arg_names.extend(('cb', 'ctx', 'cbError')) + + vals = dict(zip(arg_names, args)) + vals.update(kwargs) + + cb = ctx = cbError = None + + if 'callback' in kwargs: + cb = kwargs.get('callback') + if 'cbError' in kwargs: + cbError = kwargs.get('cbError') + if 'ctx' in kwargs: + ctx = kwargs.get('ctx') + + for remaining in args[len(vals):]: + if cb is None: + cb = remaining + continue + if ctx is None: + ctx = remaining + continue + if cbError is None: + cbError = remaining + continue + + Network = self.HM.Network + + prev = getattr(Network, '_fppPrevious', None) + if prev is not None and prev._request is not None: + prev._request.abort() + + f = FppMethodInvocation(self.namespace, self.method_name, cb, cbError, self.HM.build) + f.Network = self.HM.Network + f.Network._fppPrevious = f + for param in self.params: + if param.name not in vals: + vals[param.name] = FppMetaclass._classes[param.type].default().value + f.addParameter(param.type, vals[param.name]) + + f.invoke(ctx) + +class FppMethodInvocation(object): + _HTTP_HEADERS = { + "X-FPP-Command": "0", + "Accept" : "text/html,application/xhtml+xml,application/xml,application/x-javascript", + "Accept-Charset" : "utf-8", + "Accept-Encoding" : "gzip,identity", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + } + def __init__(self, className, methodName, cb, cbError, build): + self.className = className + self.methodName = methodName + self.callback = cb + self.callbackError = cbError + self.context = None + self._request = None + self._isInvoked = False + self._params = [] + self.build = build + + def addParameter(self, a, b): + self._params.append({'fppType' : a, 'value' : b}) + + def invoke(self, context): + self.context = context + + if not self.Network.configuration: + raise Exception("Network is not configured") + if self._isInvoked: + raise Exception("FppMethod %r already used", self) + + self._isInvoked = True + + argstring = '' + netconfig = self.Network.configuration + + if self._params: + param_buffer = [] + escape = TypeSystem.escape + for param in self._params: + escaped = escape({'type' : param['fppType'], 'value' : param['value']}) + param_buffer.append(escaped) + param_buffer.append(",") + argstring = ''.join(param_buffer[: - 1]) # don't include the last comma + + url = net.UrlQuery(util.httpjoin(self.Network.base_url, netconfig.RequestHandler), + cnmn = self.className + '.' + self.methodName, + a = netconfig.SessionId, + au = netconfig.AuthUser, + ptid = netconfig.PartnerID or '0') + post_data = dict(cn = self.className, mn = self.methodName, d = argstring, v = netconfig.FppVersion) + hdrs = self._HTTP_HEADERS.copy() + + if self.build >= "15.3.2506.0629": + hdrs[netconfig.CanaryToken] = netconfig.CanaryValue + else: + post_data[netconfig.CanaryToken] = netconfig.CanaryValue + + def clear_request(resp): + self._request = None + self._isInvoked = False + + if self.callback is not None: + self.callback.error.append(clear_request) + self.callback.success.append(clear_request) + + b = self._request = self.Network.createRequest(url, self.callback, 'POST') + b.headers = hdrs + b.postString = '&'.join('%s=%s' % i for i in post_data.items()) + b.send(self) + + del self.Network, self._params + +class HM_Type(FppProxy): + namespace = 'HM' + SortBy = Flags("Sender", 0, + "Subject", 1, + "Size", 2, + "Type", 3, + "Date", 4) + + FppStatus = Flags("SUCCESS", 0, + "ERR_HTTP_MISCONFIGURATION", - 7, + "ERR_HTTP_PARSE_FAILURE", - 6, + "ERR_HTTP_CONNECT_FAILURE", - 5, + "ERR_HTTP_TIMEOUT", - 4, + "ERR_SERVER_UNCAUGHT", - 3, + "ERR_APP_SPECIFIC", - 2, + "ERR_FPP_PROTOCOL", - 1) + + ListNavigateDirection = Flags("FirstPage", 0, + "LastPage", 1, + "NextPage", 2, + "PreviousPage", 3, + "CurrentPage", 4) + + MessageBodyType = Flags('Part', 0, + 'Full', 1) + + ItemView = Flags('Normal', 0, + 'Deleted', 1, + 'Junk', 2, + 'Delayed', 3) + + PsaType = Flags('None', 0, + 'Upsell', 1, + 'Fre', 2, + 'Auth', 3) + + FilterType = Flags("None", 0, + "Unread", 1, + "Contacts", 2, + "Flagged", 3, + "Photos", 4, + "Social", 5, + "Video", 6, + "File", 7, + "MailingList", 8, + "Shipping", 9, + "Other", 10, + "ResponsesToMe", 11, + "DocumentPlus", 12) + + JmrType = Flags("Junk", 0, + "NotJunk", 1, + "AV", 2, + "SVMOptInOptOut", 3, + "SVMClassification", 4, + "Phish", 5, + "Unsubscribe", 6, + 'Compromised', 7, + "Unknown", - 1) + + ReadMessageOperation = Flags("GetMessage", 0, + "NextMessage", 1, + "PreviousMessage", 2, + "MarkAsNotJunk", 3, + "Unsubscribe", 4, + "AddContact", 5, + "None", 6) + + RemoveSenderFromListOption = Flags("None", 0, + "KeepContact", 1, + "RemoveContact", 2, + "KeepSafeSender", 3, + "RemoveSafeSender", 4) + + ImportanceType = Flags('NORMAL', 0, + 'LOW', 1, + 'HIGH', 2) + + MessageSentStateType = Flags("NOACTION", 0, + "REPLIED", 1, + "FORWARDED", 2) + + MiniCalendarUiType = Flags("Month", 0, + "Day", 1) + + MessageActionType = Flags("ViewMessage", 0, + "Reply", 1, + "ReplyAll", 2, + "Forward", 3, + "ResumeDraft", 4, + "GetAttachment", 5, + "MarkAsJunkOrNotJunk", 6, + "Accept", 7, + "Decline", 8, + "Tentative", 9, + "AcceptExchangeSharing", 10, + "DeclineExchangeSharing", 11, + "Prefetch", 12, + "None", 13) + + NounEnum = Flags("SendersEmailAddress", 97, "SendersName", 110, "Subject", 115, "ToOrCCHeader", 116, "HasAttachments", 104) + MessagePartDataType = Flags("Body", 0, "ToList", 1, "Message", 2) + HMLiveViewResultCode = Flags("Success", 0, "Failure", 1) + SandboxAuthenticationOption = Flags("None", 0, "Oauth", 1); + + ReadingPaneLocation = Flags("None", 0, "Off", 1, "Right", 2, "Bottom", 3); + + __11 = Flags("Passed", 0, "Failed", 1, "Unknown", 2, "SoftFail", 3) + __15 = Flags("Ok", 0, "OverQuota", 1, "DoesntExist", 2, "Error", 3, "AccountDoesntExist", 4, "AccountError", 5) + __20 = Flags("Unknown", 0, "MakeLiveContact", 1, "AddLiveContact", 2, "DontShow", 3) + MailActionType = Flags("Move", 0, "Mark", 1, "Flag", 2, "Forward", 3, "AssignCategory", 4, "RemoveCategory", 5) + + @property + def FppReturnPackage(self): + return self.GetFppTypeConstructor('FppReturnPackage') + + @property + def FppError(self): + return self.GetFppTypeConstructor('FppError') + + @property + def HmSimpleMsg(self): + return self.GetFppTypeConstructor('HmSimpleMsg') + + @property + def InboxUiData(self): + return self.GetFppTypeConstructor('InboxUiData') + + @property + def Category(self): + return self.GetFppTypeConstructor('Category') + + @property + def build(self): + return self.app_info.get('BUILD') + + def GetFppTypeConstructor(self, clsname): + if clsname not in _versioned_classes: + return globals()[clsname] + + try: + cls_impls = _versioned_classes.get(clsname, None) + if not cls_impls: + raise Exception("Class %r not found for build %r. here's known classes: %r", clsname, self.build, _versioned_classes) + + build = get_build_for(self.build, cls_impls.keys()) + + if build is None: + raise Exception("Build %r not found for cls = %r. here's known classes: %r", self.build, clsname, _versioned_classes) + cls = cls_impls[build] + + log.debug('Found class %r for version %r (my version is %r)', clsname, build, self.build) + return cls + + except KeyError, e2: + log.error('Error looking for versioned class %r: %r', clsname, e2) + raise e + + def __init__(self, network, base_url, app_info): + self.Network = network + self.app_info = app_info + self.set_base_url(base_url) + self.Network.set_HM(self) + + def set_base_url(self, hostname): + self.base_url = 'https://%s/mail/' % hostname + self.Network.set_base_url(self.base_url) + + AddContactFromMessagePart = FppMethod({ + "15.3.2506.0629": ( + [('conversationId', '_string'), + ('folderId', '_string'), + ('startingMid', '_string'), + ('numParts', '_primitive'), + ('mpIndexMap', '_object'), + ('messageRenderingInfo', 'MessageRenderingInfo'), + ('contactName', '_string'), + ('contactEmail', '_string'), + ('demandLoadContentValues', '_array')], + 'AddContactFromMessagePart', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + + }) + + GetInboxData = FppMethod({ + "1": ( + [_primitive("fetchFolderList"), + _primitive("fetchMessageList"), + _custom("messageListRenderingInfo"), + _primitive("fetchMessage"), + _custom("messageRenderingInfo")], + 'GetInboxData', + XMLPost, + "abortable", + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + '15.1.3020.0910' : ( + [("fetchFolderList", '_primitive'), + ("renderUrlsInFolderList", '_primitive'), + ("fetchMessageList", '_primitive'), + ("messageListRenderingInfo", '_custom'), + ("fetchMessage", '_primitive'), + ("messageRenderingInfo", '_custom')], + 'GetInboxData', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + ClearFolder = FppMethod({ + "1": ( + [_string("clearFolderId"), + _custom("messageListRenderingInfo")], + "ClearFolder", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + }) + + MoveMessagesToFolder = FppMethod({ + "1": ( + [_string("fromFolderId"), + _string("toFolderId"), + _array("messageList"), + _array("messageAuxData"), + _custom("messageListRenderingInfo"), + _custom("messageRenderingInfo")], + "MoveMessagesToFolder", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + '13.3.3227.0707' : ( + [_string("fromFolderId"), + _string("toFolderId"), + _array("messageList"), + _array("messageAuxData"), + _custom("messageListRenderingInfo"),], + "MoveMessagesToFolder", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + '15.3.2495.0616' : ( + [('fromFolderId', '_string'), + ('toFolderId', '_string'), + ('messageList', '_array'), + ('messageAuxData', '_array'), + ('batchInfo', '_custom'), + ('messageListRenderingInfo', '_custom'), + ('fetchMessageList', '_primitive'), + ('refreshFolderAndQuickViewLists', '_primitive'), + ('folderNameForRuleSuggestion', '_string'), + ('messageRenderingInfo', '_custom'), + ('conversationRenderingInfo', '_custom'), + ('fetchItem', '_primitive')], + 'MoveMessagesToFolder', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + + '15.4.0317.0921' : ( + [('fromFolderId', '_string'), + ('toFolderId', '_string'), + ('messageList', '_array'), + ('messageAuxData', '_array'), + ('batchInfo', 'BatchInfo'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('fetchMessageList', '_primitive'), + ('refreshFolderAndQuickViewLists', '_primitive'), + ('folderNameForRuleSuggestion', '_string'), + ('messageRenderingInfo', 'MessageRenderingInfo'), + ('conversationRenderingInfo', 'ConversationRenderingInfo'), + ('fetchItem', '_primitive'), + ('blockIfDelete', '_primitive')], + 'MoveMessagesToFolder', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + + '16.2.2978.1206' : ( + [('fromFolderId', '_string'), + ('toFolderId', '_string'), + ('messageList', '_array'), + ('messageAuxData', '_array'), + ('batchInfo', 'BatchInfo'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('fetchMessageList', '_primitive'), + ('refreshFolderAndQuickViewLists', '_primitive'), + ('folderNameForRuleSuggestion', '_string'), + ('messageRenderingInfo', 'MessageRenderingInfo'), + ('conversationRenderingInfo', 'ConversationRenderingInfo'), + ('fetchItem', '_primitive'), + ('blockIfDelete', '_primitive'), + ("unpinOnMove", '_primitive'), + ("markMessagesAsRead", '_primitive')], + 'MoveMessagesToFolder', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + ViewAllFromSender = FppMethod({ + '15.3.2495.0616' : ( + [('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('senderEmail', '_string')], + 'ViewAllFromSender', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + RestoreHiddenTrash = FppMethod({ + '16.0.1635.0608' : ( + [('messageListRenderingInfo', 'messageListRenderingInfo')], + 'RestoreHiddenTrash', + XMLPost, + None, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + MarkMessagesForJmr = FppMethod({ + "1": ( + [_string("folderId"), + _array("messages"), + _array("auxData"), + _enum("jmrType"), + _primitive("reportToJunk"), + _custom("messageListRenderingInfo"), + _custom("messageRenderingInfo")], + "MarkMessagesForJmr", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + + '15.3.2495.0616' : ( + [('folderId', '_string'), + ('messages', '_array'), + ('auxData', '_array'), + ('BatchInfo', 'BatchInfo'), + ('jmrType', '_enum'), + ('setReportToJunkOK', '_primitive'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('messageRenderingInfo', 'MessageRenderingInfo'), + ('refreshFolderAndQuickViewLists', '_primitive'), + ('removeFromList', '_enum'), + ('suppressListTransition', '_primitive'), + ('markSenderAsSafe', '_primitive')], + 'MarkMessagesForJmr', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + + '16.0.1635.0608' : ( + [('folderId', '_string'), + ('messagesParam', '_array'), + ('auxData', '_array'), + ('BatchInfo', 'BatchInfo'), + ('jmrType', '_enum'), + ('setReportToJunkOK', '_primitive'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('messageRenderingInfo', 'MessageRenderingInfo'), + ('refreshMessagesList', '_primitive'), + ('refreshFolderAndQuickViewLists', '_primitive'), + ('removeFromList', '_enum'), + ('suppressListTransition', '_primitive'), + ('markSenderAsSafe', '_primitive')], + 'MarkMessagesForJmr', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + ReportError = FppMethod({ + "1": ( + [_string("message")], + "ReportError", + XMLPost, + "abortable", + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + }) + + AddContactFromMessage = FppMethod({ + "1" : ( + [_custom("messageRenderingInfo"), + _string("contactName"), + _string("contactEmail")], + "AddContactFromMessage", + XMLPost, + "abortable", + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + + '15.3.2495.0616' : ( + [('messageRenderingInfo', 'MessageRenderingInfo'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('contactName', '_string'), + ('contactEmail', '_string'), + ('demandLoadContentValues', '_array')], + 'AddContactFromMessage', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + + }) + + SvmFeedback = FppMethod({ + "1": ( + [_string("svmUrl"), + _custom("messageRenderingInfo"), + _custom("messageListRenderingInfo")], + "SvmFeedback", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + }) + + MarkMessagesReadState = FppMethod({ + "1": ( + [_primitive("readState"), + _array("messages"), + _custom("messageListRenderingInfo")], + "MarkMessagesReadState", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + '15.1.3020.0910' : ( + [_primitive("readState"), + _array("messages"), + _array("HmAuxData"), #_custom("HmAuxData"), + _custom("messageListRenderingInfo")], + "MarkMessagesReadState", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + + '15.3.2495.0616' : ( + [('readState', '_primitive'), + ('messages', '_array'), + ('auxData', '_array'), + ('batchInfo', '_custom'), + ('messageListRenderingInfo', '_custom'), + ('refreshFolderAndQuickViewLists', '_primitive')], + 'MarkMessagesReadState', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + + '16.2.2978.1206' : ( + [('readState', '_primitive'), + ('messages', '_array'), + ('auxData', '_array'), + ('batchInfo', '_custom'), + ('markFolderId', '_string'), + ('messageListRenderingInfo', '_custom'), + ('refreshFolderAndQuickViewLists', '_primitive')], + 'MarkMessagesReadState', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + MarkMessagesFlagState = FppMethod({ + '15.3.2495.0616' : ( + [('flagState', '_primitive'), + ('messages', '_array'), + ('auxData', '_array'), + ('batchInfo', 'BatchInfo'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('refreshFolderAndQuickViewLists', '_primitive')], + 'MarkMessagesFlagState', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + MarkMessagesFlagState = FppMethod({ + '15.3.2495.0616' : ( + [('flagState', '_primitive'), + ('messages', '_array'), + ('auxData', '_array'), + ('batchInfo', 'BatchInfo'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('refreshFolderAndQuickViewLists', '_primitive')], + 'MarkMessagesFlagState', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + SaveVoicemailOnPhone = FppMethod({ + '15.3.2495.0616' : ( + [('messageIdList', '_array')], + 'SaveVoicemailOnPhone', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + DeleteVoicemailOnPhone = FppMethod({ + '15.3.2495.0616' : ( + [('messageIdList', '_array')], + 'DeleteVoicemailOnPhone', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + GetConversationInboxData = FppMethod({ + '15.3.2495.0616' : ( + [('fetchFolderList', '_primitive'), + ('renderUrlsInFolderList', '_primitive'), + ('fetchConversationList', '_primitive'), + ('conversationListRenderingInfo', 'conversationListRenderingInfo'), + ('fetchConversation', '_primitive'), + ('conversationRenderingInfo', 'ConversationRenderingInfo')], + 'GetConversationInboxData', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + GetSingleMessagePart = FppMethod({ + '15.3.2495.0616' : ( + [('conversationId', '_string'), + ('folderId', '_string'), + ('messageRenderingInfo', 'MessageRenderingInfo'), + ('messagePartIndex', '_primitive'), + ('showUnsubscribeLink', '_primitive')], + 'GetSingleMessagePart', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + GetManyMessageParts = FppMethod({ + '15.3.2495.0616' : ( + [('conversationId', '_string'), + ('folderId', '_string'), + ('startingMessageId', '_string'), + ('numParts', '_primitive'), + ('messageIdToIndexMap', '_object'), + ('showUnsubscribeLink', '_primitive')], + 'GetManyMessageParts', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + GetFullMessagePart = FppMethod({ + '15.3.2495.0616' : ( + [('messageRenderingInfo', 'MessageRenderingInfo'), + ('messagePartIndex', '_primitive'), + ('showUnsubscribeLink', '_primitive')], + 'GetFullMessagePart', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + MarkMessagePartForJmr = FppMethod({ + '15.3.2495.0616' : ( + [('fetchConversation', '_primitive'), + ('cid', '_string'), + ('fid', '_string'), + ('startingMid', '_string'), + ('numParts', '_primitive'), + ('mpIndexMap', '_object'), + ('messageListRenderingInfo', 'MessageListRenderingInfo'), + ('mpMids', '_array'), + ('mpAuxDatas', '_array'), + ('mpFid', '_string'), + ('jmrType', '_enum'), + ('setReportToJunkOK', '_primitive'), + ('removeFromList', '_enum'), + ('markSenderAsSafe', '_primitive')], + 'MarkMessagePartForJmr', + XMLPost, + 'abortable', + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + CreateMoveRule = FppMethod({ + '15.3.2495.0616' : ( + [('ruleNoun', '_enum'), + ('matchingString', '_string'), + ('fid', '_string')], + 'CreateMoveRule', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + CreateSendersMoveRules = FppMethod({ + '15.3.2495.0616' : ( + [('emails', '_array'), + ('fid', '_string')], + 'CreateSendersMoveRules', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + CreateRecipientMoveRule = FppMethod({ + "15.4.0317.0921" :( + [('recipientEmail', '_string'), + ('folderId', '_string'), + ('folderName', '_string')], + 'CreateRecipientMoveRule', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox') + }) + BlockSenders = FppMethod({ + '15.3.2495.0616' : ( + [('emails', '_array')], + 'BlockSenders', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + DemandLoad = FppMethod({ + '15.3.2495.0616' : ( + [('demandLoadContentValues', '_array')], + 'DemandLoad', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + UndoSenderMoveToBlockList = FppMethod({ + '15.3.2495.0616' : ( + [('senderAddress', '_string')], + 'UndoSenderMoveToBlockList', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + ThrowError = FppMethod({ + '16.2.6151.0801' : ( + [('code', '_string'), + ('message', '_string')], + 'ThrowError', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + GetHMLiveViews = FppMethod({ + '15.3.2495.0616' : ( + [('requests', '_array')], + 'GetHMLiveViews', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + DispatchSandboxRequests = FppMethod({ + '15.3.2495.0616' : ( + [('requests', '_array')], + 'DispatchSandboxRequests', + XMLPost, + null, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + SendMessage_ec = FppMethod({ + "1": ( + [_string("to"), + _string("from"), + _string("cc"), + _string("bcc"), + _enum("priority"), + _string("subject"), + _string("message"), + _array("attachments"), + _string("draftId"), + _string("draftFolderId"), + _string("originalMessageId"), + _string("rfc822MessageId"), + _string("rfc822References"), + _string("rfc822InReplyTo"), + _enum("sentState"), + _primitive("sendByPlainTextFormat"), + _array("ignoredWordsFromSpellCheck"), + _string("hipAnswer"), + _string("hipMode"), + _string("meetingIcalId")], + "SendMessage_ec", + XMLPost, + null, + "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox"), + + '15.1.3020.0910' : None, + "15.4.0317.0921" : ( + [('to', '_string'), + ('from', '_string'), + ('cc', '_string'), + ('bcc', '_string'), + ('ImportanceType', '_enum'), + ('subject', '_string'), + ('message', '_string'), + ('messageForPlainTextConversion', '_string'), + ('attachments', '_array'), + ('draftId', '_string'), + ('draftFolderId', '_string'), + ('originalMessageId', '_string'), + ('rfc822MessageId', '_string'), + ('rfc822References', '_string'), + ('rfc822InReplyTo', '_string'), + ('MessageSentStateType', '_enum'), + ('sendByPlainTextFormat', '_primitive'), + ('ignoredWordsFromSpellCheck', '_array'), + ('hipAnswer', '_string'), + ('hipMode', '_string'), + ('meetingIcalId', '_string')], + 'SendMessage_ec', + XMLPost, + None, + 'Microsoft.Msn.Hotmail.Ui.Fpp.MailBox'), + }) + + + + +class MailFolder(list): + def __init__(self, account): + list.__init__(self) + self.account = account + self.cur_page = 0 + self.more_pages = True + + def open(self): + log.info("you clicked the inbox too soon") + return + + def __repr__(self): + return '<%s %s>' % (type(self).__name__, list.__repr__(self)) + + +class LivemaiLFolderBase(MailFolder): + domain = 'mail.live.com' + PAGECOUNT = 25 + + def __init__(self, account, **k): + MailFolder.__init__(self, account) + self.count = 0 + + util.autoassign(self, k) + + if getattr(self, 'link', None) is None: + self.link = self.link_from_fid(self.id) + + self.link = self.link + '&InboxSortAscending=False&InboxSortBy=Date' + + def link_from_fid(self, fid): + return util.UrlQuery(util.httpjoin(self.account.base_url, 'InboxLight.aspx'), + FolderID=fid) + + def relative_url(self): + return util.UrlQuery('/mail/InboxLight.aspx', FolderID = self.id) + + def login_link(self): + return net.UrlQuery('https://login.live.com/login.srf', + wa = 'wsignin1.0', + id = '23456', + lc = '1033', + ct = '1278964289', + mkt = 'en-us', + rpsnv = '11', + rver = '6.0.5285.0', + wp = 'MBI', + wreply = self.link) + + @classmethod + def from_doc(cls, doc, account): + li = doc + id = uuid.UUID(li.get('id')) + try: + count = int(li.get('count')) + except Exception, _e: + pass + + link = util.UrlQuery(doc.base_url or account.base_url, FolderID = id) + + caption_span = doc.find(cls._caption_xpath) + if caption_span is not None: + name = caption_span.text.strip() + + log.info('Folder %r for %s has %d new messages', name, account.name, count) + + return cls(account, id = id, name = name, link = link, count = count) + + def link_for_page(self, pagenum): + return util.UrlQuery(self.link, + InboxPage = 'Next', + Page = pagenum, + InboxPageAnchor = getattr(self, 'last_seen_message', ''), + NotAutoSelect = '', + n = ntok()) + + @util.callsback + def open(self, callback = None): + return callback.error('deprecated') + + def process_one_page(self, doc): + max_ = self.count + num_pages = self.get_num_pages(doc) or (max_//self.PAGECOUNT)+1 + self.more_pages = (self.cur_page < num_pages) + + log.info('Parsing page %d of %d of folder %s', self.cur_page, num_pages, self.name) + + last_id_on_page = '' + unreadcount = 0 + all_msgs = doc.findall('.//tr[@id]') + + for msg in all_msgs: + if 'staticItem' in msg.attrib.get('class', ''): + continue + msg = self.account.HM.GetFppTypeConstructor('LivemailMessage').from_doc(msg, self) + + unread = self.process_message(msg) + if unread: + unreadcount += 1 + self.append(msg) + + msgid = msg.id + if msgid is not None: + last_id_on_page = msgid or last_id_on_page + + self.last_seen_message = last_id_on_page + return unreadcount + + def process_message(self, msg): + return not msg.opened + + def get_num_pages(self, doc): + num_pages = None + try: + lastpage_li = doc.find('.//*[@pndir="LastPage"]') + if lastpage_li is not None: + lastpage_num_str = lastpage_li.get('pncur', None) + try: + num_pages = int(lastpage_num_str) + except (TypeError, ValueError): + pass + except Exception: + traceback.print_exc() + return num_pages + +@ForBuild("1") +class LivemailFolder(LivemaiLFolderBase): + _caption_xpath = './/span[@class="Caption"]' + +@ForBuild('16.2.2978.1206') +class LivemailFolder(LivemaiLFolderBase): + _caption_xpath = './/span[@class="FolderLabel"]' + +import mail +class LivemailMessageBase(mail.emailobj.Email): + + def parsedate(self, datestring): + return datestring + + @classmethod + def from_doc(cls, doc, folder): + raise NotImplementedError + + def __init__(self, folder, **k): + self.folder = folder + self.account = folder.account + + util.autoassign(self, k) + + mail.emailobj.Email.__init__(self, + id=self.id, + fromname=self.sender, + sendtime=self.parsedate(self.date), + subject=self.subject, + attachments=self.attach) + + self.set_link() + self.opened = False + + def set_link(self): + self.link = util.UrlQuery(util.httpjoin(self.account.base_url, 'InboxLight.aspx'), + Aux=self._mad, ReadMessageId=self.id, n = ntok()) + + + def mark_as_read(self): + self.open() + + def open(self): + log.info('Opening %r for %s', self, self.account.name) + if self.opened: + return + + try: + response = self.account.http.open(self.link) + response.read() + response.close() + except Exception: + pass + self.opened = True + + log.info('Done opening %r for %s', self, self.account.name) + + return self.link + + def delete(self): + log.info('Deleting %r', self) + self.mark_as_read() + + def spam(self): + log.info('Marking %r as spam', self) + self.open() # to mark as read + + @callbacks.callsback + def Messages_remove(self, messageList, messageAuxData, callback = None): + self.Messages_move(messageList, messageAuxData, self.folder.id, self.account.trash.id, callback = callback) + + @callbacks.callsback + def Messages_move(self, messageList, messageAuxData, fromFolderId, toFolderId, callback = None): + messageIds = [str(self.id)] + messageListRenderingInfo = self.make_mlri() + messageRenderingInfo = None + + kwargs = dict( + fromFolderId = fromFolderId, + toFolderId = toFolderId, + messageList = messageList, + messageAuxData = messageAuxData, + messageListRenderingInfo = messageListRenderingInfo, + messageRenderingInfo = messageRenderingInfo, + callback = callback, + ) + + self.account.HM.MoveMessagesToFolder(**kwargs) + + @callbacks.callsback + def MarkMessagesReadState(self, callback = None): + messageListRenderingInfo = self.make_mlri() + + mad = self.make_message_aux_data() + self.account.HM.MarkMessagesReadState( + readState = True, # readState + messages = [str(self.id)], # messages + HmAuxData = [mad], # HMAuxData + messageListRenderingInfo = messageListRenderingInfo, + markFolderId = self.folder.id, + callback = callback, # callback + ) + + def login_link(self): + return '/cgi-bin/getmsg?msg=%s' % (str(self.id).upper()) + +@ForBuild("1", "LivemailMessage") +class LivemailMessageOld(LivemailMessageBase): + @classmethod + def from_doc(cls, doc, folder): + id = doc.get('id') + mad = doc.get('mad', "") + + opened = "InboxContentItemUnread" not in doc.get('class', '') + + attach = () + if doc.find('.//img[@class="i_attach"]') is not None: + attach = (True,) + + if doc.find('.//*[@class="SubjectCol"]') is not None: + kws = cls.process_new_doc(doc, folder, 'Col') + old = False + elif doc.find('.//*[@class="SubjectBox"]') is not None: + kws = cls.process_new_doc(doc, folder, 'Box') + old = False + elif doc.find('.//*[@class="Sbj"]') is not None: + kws = cls.process_new_doc_short(doc, folder) + old = False + else: + kws = cls.process_old_doc(doc, folder) + old = True + + msg = cls(folder, id = id, _mad = mad, attach = attach, **kws) + msg._is_old = old + msg.opened = opened + return msg + + @classmethod + def process_old_doc(cls, doc, folder): + tds = doc.findall('.//td') + if len(tds) < 8: + log.warning('Not sure if [4:7] are the right TDs to get from this TR: %r', HTML.tostring(doc)) + + snd, sub, date = tds[4:7] + try: + sender = snd.find('a').text.strip() + except Exception: + sender = None + + try: + subject = sub.find('a').text.strip() + except Exception: + subject = None + + return dict(date = date.text, sender = sender, subject = subject) + + @classmethod + def process_new_doc_short(cls, doc, folder): + def find_str(k): + text_bits = doc.xpath('.//td[@class="%s"]//text()' % k, smart_strings = False) + if len(text_bits) != 0: + return text_bits[0] + else: + return None + + sender = find_str("Frm") + subject = find_str('Sbj') + date = find_str('Dat') + + return dict(date = date, sender = sender, subject = subject) + + @classmethod + def process_new_doc(cls, doc, folder, style): + date_div = doc.find('.//*[@class="Date%s"]' % style) + subject_div = doc.find('.//*[@class="Subject%s"]' % style) + from_div = doc.find('.//*[@class="From%s"]' % style) + + try: + date = date_div.text.strip() + except AttributeError: + date = None + + try: + subject = subject_div.find('a').text.strip() + except AttributeError: + subject = None + + try: + elem = from_div.find('.//a') + if elem is None: + elem = from_div + + sender = elem.text.strip() + except AttributeError: + sender = None + + return dict(sender = sender, subject = subject, date = date) + + def mark_as_read(self): + LivemailMessageBase.mark_as_read(self) + + if self._is_old: + messageRenderingInfo = self.account.make_mri(self, self._is_old) + self.account.GetInboxData(message = True, mri = messageRenderingInfo) + else: + self.MarkMessagesReadState() + + @util.threaded + def delete(self): + LivemailMessageBase.delete(self) + self.Messages_remove([self.id], [self.make_message_aux_data()]) + + log.info('Done deleting %r', self) + + def make_message_aux_data(self): + cls = self.account.HM.GetFppTypeConstructor('HmAuxData') + return cls.for_msg(self) + + @callbacks.callsback + def spam(self, callback = None): + LivemailMessageBase.spam(self) + messageIds = [str(self.id)] + messageAuxData = [self.make_message_aux_data()] + + messageListRenderingInfo = self.make_mlri() + messageRenderingInfo = None + self.account.HM.MarkMessagesForJmr(folderId = self.folder.id, + messagesParam = messageIds, + messages = messageIds, + auxData = messageAuxData, + jmrType = 0, # HM.JmrType.Junk + reportToJunk = True, # reportToJunk + messageListRenderingInfo = messageListRenderingInfo, + messageRenderingInfo = messageRenderingInfo, + cb = callback, # For callback + ctx = {"jmrType": 0 } # HM.JmrType.Junk + ) + log.info('Done marking %r as spam', self) + + def make_mlri(self): + return self.account.make_mlri(self.folder, timestamp = False) + +@ForBuild("15.3.2495.0616") +class LivemailMessage(LivemailMessageOld): + @classmethod + def from_doc(cls, doc, folder): + + opened = "mlUnrd" not in doc.attrib.get("class", '') + id = doc.attrib.get("id", None) + mad = doc.attrib.get("mad", "") + sender_el = doc.xpath(".//*[contains(@class, 'Fm')]//*[@email]", smart_strings = False) + if sender_el is not None: + sender_el = sender_el[0] + + fromemail = sender_el.attrib.get("email", None) + fromname = (sender_el.text or '').strip() + + subject_el = doc.xpath(".//*[contains(@class, 'Sb')]/a/text()", smart_strings = False) + + if subject_el is not None: + subject = subject_el[0].strip() + else: + subject = None + + date_el = doc.xpath(".//*[contains(@class, 'Dt')]//text()", smart_strings = False) + if date_el is not None: + date = date_el[0].strip() + else: + date = None + + attach_el = doc.xpath(".//*[contains(@class, 'At')]//*[contains(@class, 'i_att_s')]", smart_strings = False) + attach = [True] if attach_el else [] + + msg = cls(folder, id = id, _mad = mad, attach = attach, sender = fromname, subject = subject, date = date, is_conv = doc.attrib.get("conv", None) is not None) + msg.fromemail = fromemail + msg.opened = opened + msg._is_old = False + + return msg + + def set_link(self): + d = dict(baseurl = self.account.HM.base_url, + mad = self._mad, + id_name = "ReadConversationId" if self.is_conv else "ReadMessageId", + id = self.id, + ntok = ntok(), + inboxid = str(uuid.UUID(int=1)), + ) + + self.link = "%(baseurl)sInboxLight.aspx?Aux=%(mad)s&%(id_name)s=%(id)s&n=%(ntok)s&FolderID=%(inboxid)s" % d + + def make_mlri(self): + return self.account.make_mlri(self.folder, timestamp = True) + + @callbacks.callsback + def MarkMessagesReadState(self, callback = None): + messageListRenderingInfo = self.make_mlri() + + mad = self.make_message_aux_data() + self.account.HM.MarkMessagesReadState( + readState = True, # readState + messages = [str(self.id)], # messages + auxData = [mad], # HMAuxData + messageListRenderingInfo = messageListRenderingInfo, + callback = callback, # callback + ) + + def mark_as_read(self): + LivemailMessageBase.mark_as_read(self) + self.MarkMessagesReadState() + + def conv_login_link(self): + return net.UrlQuery('https://login.live.com/login.srf', + wa = 'wsignin1.0', + id = '23456', + lc = '1033', + ct = '1278964289', + mkt = 'en-us', + rpsnv = '11', + rver = '6.0.5285.0', + wp = 'MBI', + wreply = self.link) + def login_link(self): + if self.is_conv: + return self.conv_login_link() + else: + return LivemailMessageOld.login_link(self) + +@ForBuild('16.2.2978.1206') +class LivemailMessage(LivemailMessage): + def set_link(self): + d = dict(baseurl = self.account.HM.base_url, + id_name = "mid", + id = self.id, + ntok = ntok(), + ) + + self.link = "%(baseurl)sInboxLight.aspx?%(id_name)s=%(id)s&n=%(ntok)s" % d + + def login_link(self): + return self.conv_login_link() + +@ForBuild('16.2.2978.1206') +class Category(FppClass): + _fields_ = [ + ('Id', '_primitive'), + ("Name", '_string'), + ("Visibility", '_enum'), + ("IsCategorizable", '_primitive'), + ("UnreadMessagesCount", '_primitive'), + ("TotalMessagesCount", '_primitive'), + ("ColorCode", '_string'), + ("IsSenderBased", '_primitive'), + ("IsReserved", '_primitive'), + ] +# Added to suppress "new build" warnings. +@ForBuild('16.2.6151.0801') +class __BuildVersionDummy(FppClass): + _fields_ = [] + +def get_classname(js): + return re.search(r'HM\.registerFppClass\("(.+?)",', js).group(1) +def get_fields(js): + fields_re = re.compile(r'Web\.Network\.FppProxy\._(_(?:.+?))\("(.+?)"\),') + return [x[:: - 1] for x in fields_re.findall(js)] +def fppclass_to_python(js): + from pprint import pformat + cname = get_classname(js) + fields = get_fields(js) + return 'class %s(FppClass):\n _fields_ = %s\n' % (cname, pformat(fields)) + +def main(): + print str(HmAuxData("10\\|0\\|8CA6DDB632DFE40\\|", null)) + +def main2(): + from tests.testapp import testapp + a = testapp() + + Page = PageInfo() + Page.fppCfg = FPPConfig(None) + Network = Network_Type(Page.fppCfg) + + HM = HM_Type(Network) + + @util.callsback + def doit(callback = None): + HM.GetInboxData(True, True, None, True, None, Null, None, None) + + def success(resp): + print 'success', resp + def error(e): + print 'error', e + + doit(success = success, error = error) + +if __name__ == '__main__': + main() diff --git a/digsby/src/mail/hotmail/hotmail.py b/digsby/src/mail/hotmail/hotmail.py new file mode 100644 index 0000000..a063b22 --- /dev/null +++ b/digsby/src/mail/hotmail/hotmail.py @@ -0,0 +1,929 @@ +''' +Hotmail.py + +Provides functionality for logging into hotmail, retrieving messages +and common operations such as mark as read, delete, mark as spam, etc. +''' +from __future__ import with_statement + +import logging, traceback, time, re, random, datetime +import cookielib, urlparse, urllib2, cgi +import uuid +import hmac, hashlib +import base64 + +import lxml.html as HTML +import util, util.httptools, util.xml_tag +import util.net as net +import util.callbacks as callbacks +import util.primitives.bits as bits +import common + +import mail +import mail.passport + +from contextlib import closing + +WebScraper = util.httptools.WebScraperBase + +def LXMLPARSE(s, base_url): + return HTML.fromstring(s, base_url) + +import ajax + + +def LaunchDefaultBrowserLater(url): + import wx # XXX: gtfo plz + log.warning('Opening the following URL in browser: %s', url) + wx.CallAfter(wx.LaunchDefaultBrowser, url) + +def ntok(): + return str(random.randint(0, 0x7fffffff)) + +class NotInbox(Exception): + 'This exception is raised when the page loaded from the inbox URL is not the inbox' + +numregex = re.compile(r'(\d+)') + +log = logging.getLogger('hotmail') + +folderids = util.LowerStorage(dict(zip('inbox trash sent drafts junk'.split(), range(1,6)))) +folderids.deleted = folderids.trash + +class HotmailUUID(uuid.UUID): + pass + +class Hotmail(common.emailaccount.EmailAccount, WebScraper): + ''' + Hotmail object. Contains all functionality of hotmail account + ''' + + MAX_REDIRECTS = 5 + + domain = 'live.com' + + nicename = 'Hotmail' + protocol = 'hotmail' + livemail = True + + default_domain = 'hotmail.com' + + AUTH_URL = 'https://login.live.com/ppsecure/sha1auth.srf?lc=1033' + PP_AUTH_VERSION = None + LAST_KNOWN_PP_AUTH_VERSION = '1100' + + def __init__(self, *args, **kwargs): + self.urls = { + 'inbox': lambda : 'https://mail.live.com/?rru=inbox', + 'prelogin' : lambda : 'http://login.live.com', + } + + WebScraper.__init__(self) + common.emailaccount.EmailAccount.__init__(self, *args, **kwargs) + self.name = self.email_address + + self._reset_state() + + self.folders = [] + + self.inbox = ajax.MailFolder(self) + self.base_url = 'https://mail.live.com/default.aspx' + + self.HM = None + + def timestamp_is_time(self, tstamp): + return not self.livemail + + def init_http(self): + WebScraper.init_http(self) + self.set_cookie('lr', '1', domain = '.live.com', path = '/') + self.http.addheaders = [('User-Agent', + net.user_agent()), + ('Referer', 'https://mail.live.com/default.aspx?' + 'FolderID=00000000-0000-0000-0000-000000000001&' + 'InboxSortAscending=False&InboxSortBy=Date&n=' + ntok()), + ] + + + def build_request_default(self, name, **req_options): + req = req_options.get('_request', None) + if req is not None: + if name == req.get_full_url(): + return req + + return super(type(self), self).build_request_default(name, **req_options) + + def open(self, req, *a, **k): + return self.request(req.get_full_url(), _request = req, *a, **k) + + @util.callsback + def _goto_hotmail_url(self, relative_url, data=None, callback=None): + ''' + relative_url = '/cgi-bin/HoTMaiL' or '/cgi-bin/compose?mailto=1&to=email@domain.com' + ''' + + if data is not None: + data = (relative_url, data) + new_relative_url = '/cgi-bin/HoTMaiL' + else: + new_relative_url = relative_url + + def sso_success(s,t): + log.info('sso_success: %r, %r', s, t) + callback.success() + self._open_url_2(hotmail_token(s, t, rru = new_relative_url), data) + + callback.error += lambda e : self._open_url_err(relative_url, e) + SSO_Auth(self.username, self._decryptedpw(), pp_auth_version = self.PP_AUTH_VERSION, + success=sso_success, + error =callback.error, + ) + + def _open_url_err(self, url, err): + log.error('Error (%r) opening url %s', err, url) + + def _open_url_2(self, token, data): + + url = self.AUTH_URL + '&'+ token + + if data is None: + LaunchDefaultBrowserLater(url) + else: + self.post_data(url, *data) + + def check_for_redirect(self, data): + try: + return self.inbox_re.search(data).groups()[0] + except Exception: + return None + + def post_data(self, login_url, post_url, data): + with closing(self.http.open(login_url)) as resp: + redirect = resp.content + + assert 'window.location.replace' in redirect + + redirect_url = self.check_for_redirect(redirect) + if redirect_url is None: + raise Exception("Expected redirect url, but it wasn't there. Data = %r", redirect) + + with closing(self.http.open(redirect_url)): + pass + + with closing(self.http.open(util.httpjoin(redirect_url, post_url), data)): + pass + + def _get_mailclient(self): + return common.pref('privacy.www_auto_signin', False) + + def _set_mailclient(self, val): + ''' + Not supported. + ''' + return + + mailclient = property(_get_mailclient, _set_mailclient) + + def start_client_email(self, email = None, use_client = True): + + if email is None: + url = self.inbox.login_link() + elif isinstance(email, basestring): + url = email + else: + if self.mailclient: + url = email.login_link() + else: + url = email.link + + if url and url.startswith("http"): + LaunchDefaultBrowserLater(url) + return None + + if url == '': + url = self.inbox.login_link() + + if use_client and self.mailclient: + self._goto_hotmail_url(url, error = lambda *a: self.start_client_email(url, use_client = False)) + return None + else: + + url = 'https://mail.live.com/default.aspx?n=123456789' + LaunchDefaultBrowserLater(url) + + return url + + @property + def inbox_url(self): + ''' + Returns the URL for this email account's inbox + ''' + try: + return self.inbox.link + except AttributeError: + return 'https://mail.live.com/default.aspx?n=' + ntok() + + @property + def logged_in(self): + if bool(self.get_cookie('MSPAuth', default = False, domain = '.live.com')): + return True + elif bool(self.get_cookie('PPAuth', default = False, domain = '.login.live.com')): + return True + else: + return False + + def build_request_loginpage(self, *a): + + if _is_login_domain_exception(self.username): + url = 'https://msnia.login.live.com/pp%s/login.srf' + else: + url = 'https://login.live.com/pp%s/login.srf' + url %= self.PP_AUTH_VERSION + return self.RequestFactory(util.UrlQuery(url, vv = self.PP_AUTH_VERSION, svc='mail', bk=int(time.time()))) + + def handle_success_loginpage(self, _name, resp): + doc = resp.document + if doc is None: + raise NotInbox() + if int(self.PP_AUTH_VERSION) < 900: + + form = None + for form in doc.forms: + if form.get('name') == 'f1': # it would be great if this had a real name, like "login" or something. + break + + if form is None: + self.handle_error(_name, Exception('No login form found')) + log.error("Document: %r", HTML.tostring(doc)) + return + + form.fields['login'] = self.username + form.fields['passwd'] = self._decryptedpw() + + self._login_form = form + else: + data = HTML.tostring(doc) + log.error("Document: %r", data) + ppft_html = eval(util.get_between(data, "sFTTag:", ",") or 'None') + ppft_token = HTML.fromstring(ppft_html).attrib['value'] + bk = str(int(time.time())) + action_url = net.UrlQuery('https://login.live.com/ppsecure/post.srf', + **{'bk': bk, + 'cbcxt': 'mai', + 'ct': bk, + 'id': '64855', + 'lc': '1033', + 'mkt': 'en-us', + 'rpsnv': '11', + 'rver': '6.1.6206.0', + 'snsc': '1', + 'wa': 'wsignin1.0', + 'wp': 'MBI', + 'wreply': 'https://mail.live.com/default.aspx'}) + + if _is_login_domain_exception(self.username): + action_url = action_url.replace('login.live.com', 'msnia.login.live.com') + self._login_form = util.Storage(form_values = lambda: + dict(login = self.username, + passwd = self._decryptedpw(), + type = '11', + LoginOptions = '2', + NewUser='1', + MEST='', + PPSX='Passp', + PPFT= ppft_token, + idsbho = '1', + PwdPad = '', + sso = '', + i1 = '1', + i2 = '1', + i3 = '161605', + i4 = '', + i12 = '1', + ), + action = action_url) + + log.info('Got loginform for %r, submitting', self) + self.request('sendauth') + + def handle_error_loginpage(self, _name, e): + del self.PP_AUTH_VERSION + log.error('Error getting loginpage') + self._login_form = None + self.handle_error_default(_name, e) + self.set_offline(self.Reasons.CONN_FAIL) + + def build_request_sendauth(self, *a): + if getattr(self, '_login_form', None) is None: + return None + + form, self._login_form = self._login_form, None + return self.RequestFactory(form.action, data = form.form_values()) # method? urllib2.Request doesn't accept a method override + + def handle_success_sendauth(self, _name, resp): + if self.logged_in: + on_login, self._on_login = getattr(self, '_on_login', None), None + if on_login is not None: + on_login() + else: + self.set_offline(self.Reasons.BAD_PASSWORD) + + def authenticate(self, task=None): + ''' + Log in to the email account represented by this object with the given username and password. + + ''' + self.change_state(self.Statuses.AUTHENTICATING) + + if task is None: + task = self.update + + if not self.logged_in: + log.info('beginning authentication for %s', self.name) + self._on_login = task + if self.PP_AUTH_VERSION is None: + self.request('prelogin', error = lambda *a: self.bad_pw()) + else: + self.request('loginpage', error = lambda *a: self.bad_pw()) + + else: + task() + + def find_pp_auth(self, resp): + doc = HTML.fromstring(resp.read()) + base = doc.base + if base is not None: + urlparts = net.UrlQuery.parse(base) + base_path = urlparts.get('path') + match = re.match('/pp(\d+).*', base_path) + if match and match.group(1): + return match.group(1) + + return self.LAST_KNOWN_PP_AUTH_VERSION + + def handle_success_prelogin(self, _name, resp): + self.PP_AUTH_VERSION = self.find_pp_auth(resp) + log.debug('Got PPAuth version: %r', self.PP_AUTH_VERSION) + + self.request('loginpage', + error = lambda *a: self.bad_pw()) + + def check_message_at_login(self, resp): + doc = resp.document + + form = doc.find('.//form[@id="MessageAtLoginForm"]') + if form is not None: + self._loginmsg_form = form + self.request('loginmsg') + return True + else: + return False + + def build_request_loginmsg(self, *a): + if getattr(self, '_loginmsg_form', None) is None: + return None + + form, self._loginmsg_form = self._loginmsg_form, None + return self.RequestFactory(form.action, data = form.form_values()) # method? urllib2.Request doesn't accept a method override + + def handle_success_loginmsg(self, _name, resp): + log.info('Got success response for loginmsg, requesting inbox') + self.request('inbox') + + def handle_error_loginmsg(self, *args): + log.info('Got error response for loginmsg, requesting inbox anyway (args=%r)', args) + self.request('inbox') + + def check_success_inbox(self, _name, resp): + doc = resp.document + log.debug("Inbox page src: %r", HTML.tostring(doc, pretty_print = True)) + iframe = doc.find('.//iframe[@id="UIFrame"]') + if iframe is not None: + iframe_url = iframe.get('src', '').decode('xml') + if iframe_url == '' or iframe_url.startswith("javascript"): + self.urls['inbox'] = 'https://mail.live.com/?rru=inbox' + log.info("using default url for inbox: %r", self.urls['inbox']) + return True + else: + log.info('Found iframe. src = %r', iframe_url) + self.urls['inbox'] = iframe_url + return True + else: + return False + + def handle_success_inbox(self, _name, resp): + + if self.check_message_at_login(resp): + return + + data = resp.content + redirect_url = self.check_for_redirect(data) + if redirect_url is not None: + log.info("Detected redirect in inbox: %r", redirect_url) + + if self.check_success_inbox(_name, resp): + self.clear_waiting('inbox') + self.request('inbox') + return + + got_page_info = False + got_app_info = False + + self.base_url = resp.geturl() + doc = resp.document + + for script in doc.findall('.//script'): + if not script.text: + continue + if not got_app_info: + app_info_raw = util.get_between(script.text, 'App =', ';') + if app_info_raw is not None: + got_app_info = True + + if not got_page_info: + page_info_raw = util.get_between(script.text, 'Page =', ';') + if page_info_raw not in (None, ' {}'): + got_page_info = True + + if got_app_info and got_page_info: + break + else: + raise NotInbox("Could not find app info or page info. Here's status, headers, raw data: (%r, %r, %r)", + resp.code, str(resp.headers), resp.content) + + try: + app_info = jsdict_to_python(app_info_raw) + except Exception: + raise NotInbox("Error parsing app info. Here's raw data: (%r, %r)", + app_info_raw, resp.content) + try: + page_info = jsdict_to_python(page_info_raw) + except Exception: + raise NotInbox("Error parsing page info. Here's raw data: (%r, %r)", + page_info_raw, resp.content) + + log.info('Got app & page info: App = %r, Page = %r', app_info, page_info) + + self._app_info = app_info + self._cfg_info = page_info + cfg_info = page_info.get('fppCfg', {}) + trash_id = app_info.get('config', {}).get('sysFldrs', {}).get('trashFid', None) + if trash_id is not None: + self._trash_id = uuid.UUID(trash_id) + else: + self._trash_id = uuid.UUID(int=folderids.trash) + + network = ajax.Network_Type(ajax.FPPConfig(self), self, cfg_info) + self.HM = ajax.HM_Type(network, urlparse.urlparse(self.base_url).hostname, app_info) + + log.info("Hotmail site version: %r", self.HM.build) + if self.HM.build > max(sum((x.keys() for x in ajax._versioned_classes.values()), [])): + if not getattr(self, 'warned_newbuild', False): + self.warned_newbuild = True + try: + raise Exception("New hotmail build detected: %r" % self.HM.build) + except: + import traceback; traceback.print_exc() + + self._process_folderlist(doc) + + folders = self.folders + + inboxid = uuid.UUID(int=folderids.inbox) + self.inbox = ([f for f in folders if f.id == inboxid] or [None])[0] + self.trash = ([f for f in folders if f.id == self._trash_id] or [None])[0] + + self.GetInboxData(folders = True, messages = True, success = self._process_ajax_inbox) + + def _process_folderlist(self, doc): + folderlist = doc.find('.//*[@id="folderList"]') + if folderlist is None: + raise NotInbox('Couldnt find folderList in %r', HTML.tostring(doc)) + self.folders = [self.HM.GetFppTypeConstructor('LivemailFolder').from_doc(x, self) for x in folderlist.findall('.//*[@count]')] + + def _process_messagelist(self, doc): + self.inbox.process_one_page(doc) + + def _process_ajax_inbox(self, result): + + folderlist_doc = HTML.fromstring("
" + result.FolderListHtml.value + "
") + self._process_folderlist(folderlist_doc) + + messagelist_doc = HTML.fromstring(result.MessageListHtml.value) + self._process_messagelist(messagelist_doc) + + self.on_good_inbox() # clear bad inbox counter + + if self.state == self.Statuses.OFFLINE: + log.error('finish_update exiting early, state is %s', self.state) + return + + self._received_emails(self.inbox[:25], len(self.inbox)) + self.setnotifyif('count', self.inbox.count) + self.change_state(self.Statuses.ONLINE) + + def preprocess_resp_inbox(self, name, resp, **req_options): + if not isinstance(resp, NotInbox): + resp = self.preprocess_resp_default(name, resp, **req_options) + redirect_url = self.check_for_redirect(resp.content) + if redirect_url is not None: + return Redirect(redirect_url) + + return resp + + def handle_error_inbox(self, _name, e): + self._login_form = None + + def fatal_error(): + self.handle_error_default(_name, e) + self.set_offline(self.Reasons.CONN_FAIL) + + try: + raise e + except NotInbox: + self.on_bad_inbox(e) + if self._inbox_request_error_count < 3: + self.request('inbox') + else: + fatal_error() + except: + self.on_good_inbox() # it's not really a 'good' inbox, but we need to clear the counter + fatal_error() + + def on_bad_inbox(self, e = None): + log.error('Got a bad inbox page, exception was: %r', e) + setattr(self, '_inbox_request_error_count', getattr(self, '_inbox_request_error_count', 0) + 1) + + def on_good_inbox(self): + setattr(self, '_inbox_request_error_count', 0) + + def detect_livemail(self, html): + return True # All microsoft mail accounts are live enabled now. + + def open_folders(self): + log.info('Opening %s\'s folders', self.name) + for folder in self.folders: + folder.open() + log.info('Done opening folders for %s', self.name) + + def update(self): + common.emailaccount.EmailAccount.update(self) + + if not self.logged_in: + log.info('%s has not yet authenticated', self.name) + return self.authenticate() + + self.request('inbox') + + def bad_pw(self): + if self.offline_reason != self.Reasons.CONN_FAIL: + common.emailaccount.EmailAccount.bad_pw(self) + else: + log.error('Skipping call to "bad_pw" because %r is already offline with CONN_FAIL', self) + + def sort_emails(self, new=None): + # Already sorted by date- thanks hotmail! + pass + + def filter_new(self, new, old): + return super(Hotmail, self).filter_new(new, False) + + def compose_link(self, email='', subject='', body='', cc='',bcc='',): + + kw = dict() + + if email: + kw['to'] = email + + for name in 'subject body cc bcc'.split(): + if vars()[name]: + kw[name] = vars()[name] + + if kw: + kw['mailto'] = 1 + + kw['n'] = '123456789' + + url = util.UrlQuery(util.httpjoin(self.base_url, '/mail/EditMessageLight.aspx'), + fti='yes', **kw) + + log.info('compose_link returning: %s', url) + + return url + + def compose(self, *a, **k): + use_client = k.pop('use_client', True) + url = self.compose_link(*a, **k) + + if self.mailclient and use_client and not url.startswith('http'): + self._goto_hotmail_url(url, error = lambda *a: self.start_client_email(url, use_client = False)) + else: + return util.httpjoin(self.base_url, url, True) + + def __iter__(self): + for email in self.inbox: + yield email + + def markAsRead(self, email): + common.emailaccount.EmailAccount.markAsRead(self, email) + util.threaded(email.mark_as_read)() + + def delete(self, email): + common.emailaccount.EmailAccount.delete(self, email) + util.threaded(email.delete)() + + def reportSpam(self, email): + common.emailaccount.EmailAccount.reportSpam(self, email) + util.threaded(email.spam)() + + def urlForEmail(self, email): + ''' + Returns the URL for the provided email object. + ''' + return email.link + + #### ajax stuff + @util.callsback + def send_email(self, to='', subject='', body='', cc='', bcc='', callback = None): + + def _try_form(*a): + self._send_email_form(to, subject, body, cc, bcc, callback = callback) + return True + + self._send_email_ajax(to, subject, body, cc, bcc, success = callback.success, error = _try_form) + + @util.callsback + def _send_email_ajax(self,to, subject, body, cc, bcc, callback = None): + if not hasattr(self.HM, 'SendMessage_ec'): + return callback.error("Sending email with AJAX call is not supported for build %r", + util.try_this(lambda:self.HM.app_info['BUILD'], "unknown")) + + self.HM.SendMessage_ec( + to, # to + self.name, # from + '', # cc + '', # bcc + 0, # priority + subject, # subject + body, # message + [], # attachments + None, # draftId + str(uuid.UUID(int=folderids.drafts)), # draftFolderId + None, # originalMessageId + None, # rfc822MessageId + None, # rfc822References + None, # rfc822InReplyTo + 0, # sentState + True, # sendByPlainTextFormat + [], # ignoredWordsFromSpellCheck + None, # hipAnswer + 'audio', # hipMode + None, # meetingIcalId + callback, + ) + + @util.callsback + def _send_email_form(self, to, subject, body, cc, bcc, callback = None): + cfg = self.HM.Network.configuration + import ClientForm + + form = ClientForm.HTMLForm(util.UrlQuery(util.httpjoin(self.inbox_url, '/mail/SendMessageLight.aspx'), _ec = 1, n = ntok()), + method="POST", request_class = self.RequestFactory) + form.new_control('hidden', '__VIEWSTATE', {'value' : ''}) + form.new_control('hidden', cfg.CanaryToken, {'value' : cfg.CanaryValue}) + form.new_control('hidden', 'MsgPriority', {'value' : '0'}) + form.new_control('hidden', 'ToolbarActionItem', {'value' : 'SendMessage'}) + form.new_control('hidden', 'InfoPaneActionItem', {'value' : ''}) + form.new_control('hidden', 'folderCache', {'value' : ''}) + form.new_control('hidden', 'fDraftId', {'value' : ''}) + form.new_control('hidden', 'fMsgSentState', {'value' : 'NOACTION'}) + form.new_control('hidden', 'IsSpellChecked', {'value' : 'false'}) + form.new_control('hidden', 'fFrom', {'value' : self.username}) + form.new_control('hidden', 'cpselectedAutoCompleteTo', {'value' : '[]'}) + form.new_control('hidden', 'fTo', {'value' : '"" <%s>;' % to}) + form.new_control('hidden', 'cpselectedAutoCompleteCc', {'value' : ''}) + form.new_control('hidden', 'fCc', {'value' : cc}) + form.new_control('hidden', 'cpselectedAutoCompleteBcc', {'value' : ''}) + form.new_control('hidden', 'fBcc', {'value' : bcc}) + form.new_control('hidden', 'fSubject', {'value' : subject}) + form.new_control('hidden', 'fAttachment_data', {'value' : ''}) + form.new_control('hidden', 'isFirstPL', {'value' : ''}) + form.new_control('hidden', 'RTE_MessageType', {'value' : 'PlainText'}) + form.new_control('hidden', 'fMessageBody', {'value' : body}) + + request = form.click() + + log.info('Request obj = %r, vars = %r', request, vars(request)) + + def check_success_send(resp): + if resp is None: + # assume it worked? + return callback.success() + + data = resp.read() + if 'SentMailConfirmation' in data: + log.info('sent email!') + callback.success() + else: + log.info('failed to send email: %r', data) + callback.error() + + def check_error_send(exc): + log.info('failed to send email: %r', exc) + callback.error() + + util.threaded(self.open)(request, success = check_success_send, error = check_error_send) + + def make_mlri(self, folder, timestamp = False): + if timestamp: + ts = datetime.datetime.utcnow().isoformat() + 'Z' + else: + ts = None + + return self.HM.GetFppTypeConstructor('MessageListRenderingInfo')( + FolderId = folder.id, + MessageCount = 99, + LastUpdateTimestamp = ts, + ) + + def make_mri(self, msg, old = True): + return self.HM.GetFppTypeConstructor('MessageRenderingInfo')( + MessageId = msg.id, + FolderId = msg.folder.id, + HmAuxData = msg.make_message_aux_data(), + ) + + @callbacks.callsback + def GetInboxData(self, folders = False, messages = False, message = False, mri = None, callback = None): + mlri = self.make_mlri(self.inbox) + self.HM.GetInboxData( + fetchFolderList = folders, + renderUrlsInFolderList = True, + fetchMessageList = messages, + messageListRenderingInfo = mlri, + fetchMessage = message, + messageRenderingInfo = mri, + callback = callback) + + #### end of ajax stuff + + + + +def createMessageListRenderingInfo(defaults_from_message_list): + ajax.MessageListRenderingInfo() + b = defaults_from_message_list + return b + + +def parsedate(datestring): + return datestring + +class SSO_Authorizer(object): + def __init__(self, username, password, pp_auth_version): + self.username = username + self.password = password + self.callback = None + self.pp_auth_version = pp_auth_version + + @property + def url(self): + if _is_login_domain_exception(self.username): + url = 'https://msnia.login.live.com/pp%s/RST.srf' + else: + url = 'https://login.live.com/pp%s/RST.srf' + + url %= self.pp_auth_version + return url + + @util.callsback + def auth(self, callback = None): + self.callback = callback + + sectokreq = mail.passport.SecurityTokenRequest(0, 'http://Passport.NET/tb', '') + env = mail.passport.make_auth_envelope(self.username, self.password, [sectokreq]) + url = self.url + self.username = self.password = None + + try: + util.xml_tag.post_xml(url, + env._to_xml(pretty = False), + success = self.finish, + error = self.error) + except Exception, e: + traceback.print_exc() + self.error(e) + + def finish(self, resp): + RSTS = resp.Body.RequestSecurityTokenResponseCollection._children + assert len(RSTS) == 1, resp._to_xml() + + RST = RSTS[0] + callback, self.callback = self.callback, None + + if callback: + callback.success(RST, resp) + + def error(self, e = None): + callback, self.callback = self.callback, None + + if callback: + callback.error(e) + +@util.callsback +def SSO_Auth(username, password, pp_auth_version, callback = None): + SSO_Authorizer(username, password, pp_auth_version).auth(callback = callback) + +def hotmail_token(token, tag, rru='/cgi-bin/HoTMaiL'): + nonce = bits.getrandbytes(24) + + secret = base64.b64decode(str(token.RequestedProofToken.BinarySecret)) + + key = mail.passport.derive_key(secret, 'WS-SecureConversation'+nonce) + info = ( + ('ct',str(int(time.time()))), + ('bver','4'), + ('id','2'), + ('rru', rru), + ('svc','mail'), + ('js','yes'), + ('pl','?id=2'), + ('da', extract_encrypted(tag)), + ('nonce',base64.b64encode(nonce)), + ) + + message = [] + add = message.append + for k,v in info: + add('&') + add(k) + add('=') + + v = v.encode('url') + + if k == 'rru': + v = v.replace('/', '%2F').replace('.', '%2E') + + if k == 'da': + v = v.replace('%3A', ':') + + add(v) + + message.pop(0) + + message = ''.join(message) + hash = hmac.HMAC(key, message, hashlib.sha1).digest() + + message += '&hash=%s' % base64.b64encode(hash).encode('url') + + return 'token=%s' % message.encode('url').replace('%3A', ':') + +def extract_encrypted(t): + src = t._source + + if src: + token = util.get_between(src, '', '') + else: + token = t.RequestedSecurityToken.EncryptedData._to_xml(pretty=False) + + return token + +def jsdict_to_python(s): + ''' use sparingly. ''' + class dumb(dict): + def __getitem__(self, x): + try: + return dict.__getitem__(self, x) + except KeyError: + return x + + s = _remove_js_functions(s) + + s = ''.join(s.strip().splitlines()) + _globals = dumb(false = False, true = True, null = None) + try: + exec s in _globals, _globals + except: + pass + + for key in ('__builtins__', 'false', 'true', 'null'): + _globals.pop(key, None) + + if len(_globals) == 0: + return eval(s, _globals, _globals) + return dict(_globals) + +def _remove_js_functions(s): + return re.sub('function\s*\(.*?\)\s*\{.*?\}', 'null', s) + +def _is_login_domain_exception(username): + return False + +def main(un, password): + Hotmail._decryptedpw = lambda h: h.password + html = Hotmail(name=un, password=password) + html.authenticate() + +if __name__ == '__main__': + #from wx.py.PyCrust import main + main('digsby03@hotmail.com', 'passwords dont go here') diff --git a/digsby/src/mail/hotmail/tools.py b/digsby/src/mail/hotmail/tools.py new file mode 100644 index 0000000..a54ed93 --- /dev/null +++ b/digsby/src/mail/hotmail/tools.py @@ -0,0 +1,64 @@ +import path + +def a(name): + return (name, '_string') + +def c(name): + return (name, '_primitive') + +def e(name): + return (name, '_array') + +def d(clsname, name = None): + return (name or clsname, clsname) + +def f(name): + return (name, '_enum') + +#def j(name): +# return (name, '_date') + +def g(name): + return (name, '_object') + +def j(name): + return (name, '_oArray') + +class B(object): + def rfc(self, name, args): + args_string = '\n'.join('''\ + (%r, %r),''' % arg for arg in args) + + return '''\ +class %s(FppClass): + _fields_ = [ +%s + ] +''' % (name, args_string) + + def rfm(self, name, args, method_name, tm, g, namespace): + return (args, name, tm, g, namespace) + + def __getattr__(self, attr): + try: + return object.__getattribute__(self, attr) + except AttributeError: + return attr + +b = B() +null = None +class Network: + class Type: + XMLPost = 0 + +known_js_filenames = ['i0a.js', 'i1a.js', 'i2a.js', 'i3a.js', 'i4a.js'] +def download_src(buildnum, localdir = "d:\\src\\hotmail\\", remote_base="http://gfx6.hotmail.com/mail/"): + import urllib2, js_scrape + build_dir = path.path(localdir) / buildnum + if not build_dir.isdir(): + build_dir.makedirs() + for fname in known_js_filenames: + with open(build_dir / fname, 'wb') as f: + f.write(js_scrape.Cleanup_JS(urllib2.urlopen(remote_base + buildnum + "/" + fname).read())) + +import sys; sys.path.append("d:\\workspace\\js_scrape") diff --git a/digsby/src/mail/imap.py b/digsby/src/mail/imap.py new file mode 100644 index 0000000..80bfdad --- /dev/null +++ b/digsby/src/mail/imap.py @@ -0,0 +1,183 @@ +''' +IMAP mail +''' +import re +import traceback +from mail import AuthenticationError +from mail.smtp import SMTPEmailAccount + +from util import ResetTimer +from util.primitives.funcs import get +from util.command_queue import CommandQueue, callback_cmdqueue, cmdqueue +from common import pref + +from logging import getLogger; log = getLogger('imap'); info = log.info + +class IMAPMail(SMTPEmailAccount): + protocol = 'imap' + + max_fetch = 25 # will only fetch contents for the last max_fetch messages + + default_port = 143 + default_ssl_port = 993 + default_timeout = 20 + + opening_email_marks_as_read = False + + def __init__(self, **options): + d = self.default + log.info("imap options: %r", options) + self.imapserver = options.get('imapserver') + self.require_ssl = options.get('require_ssl', d('require_ssl')) + self.imapport = options.get('imapport', d('default_ssl_port') if + self.require_ssl else + d('imapport')) + + self.timeouttimer = ResetTimer(pref('imap.timeout', self.default_timeout), + self.timeout_check) + + from mail.imapcheck import IMAPCheck + self.imap = IMAPCheck(self.max_fetch) + self.cmdq = CommandQueue([], [], 30, 1) + #print 'IMAPMail:', repr(options['name']), repr(options['password']) + SMTPEmailAccount.__init__(self, **options) + + can_has_preview = True + + def update(self): + SMTPEmailAccount.update(self) + info('starting timeout timer') + self.timeouttimer.start() + + self.real_update(success = self.finish_update, + error = self.on_error) + + def finish_update(self, result): + self.error_count = 0 + + if isinstance(result, tuple): + count, emails = result + self._received_emails(emails, count) + else: + log.warning('finish_update expects a tuple, got a %s: %r', type(result), result) + + info('stopping timeout timer') + self.timeouttimer.stop() + + @callback_cmdqueue() + def real_update(self): + password = self._decryptedpw() + if isinstance(password, unicode): + password = password.encode('utf-8') + + self.imap.login(self.imapserver, self.imapport, self.require_ssl, + self.name, password) + + if self.state == self.Statuses.OFFLINE: + if self.offline_reason == self.Reasons.NONE: + return + else: + raise Exception + + try: + result = self.imap.update() + except AuthenticationError, e: + traceback.print_exc() + return self.bad_pw() + + return result + + def timeout_check(self): + info('Checking server connection for %r', self) + if self.state in (self.Statuses.OFFLINE, self.Statuses.ONLINE): + info('%s is not currently checking', self) + return True + + if get(self, 'imap', False): + try: + return self.imap.srv.check() + except: + self.on_error() + log.error('%s\'s server connection has failed', self) + + else: + log.error('%s has no imap attribute', self) + self.on_error() + + return False + + + def _get_options(self): + opts = SMTPEmailAccount._get_options(self) + opts.update(dict((a, getattr(self, a)) for a in + 'imapserver imapport require_ssl'.split())) + d = self.protocol_info()['defaults'] + if opts['require_ssl'] and opts['imapport'] == d['default_ssl_port']: + opts.pop('imapport') + return opts + + def copy(self, msg, new_mailbox): + self.imap.srv.uid("COPY", msg.id, new_mailbox) + + @cmdqueue() + def move(self, msg, new_mailbox): + self.copy(msg, new_mailbox) + self.imap.delete(msg, error = lambda: self.on_error(lambda: self.move(msg,new_mailbox))) + + def markAsRead(self, msg): + SMTPEmailAccount.markAsRead(self, msg) + self._markAsRead(msg) + + @cmdqueue() + def _markAsRead(self, msg): + self.imap.markAsRead(msg, error = lambda: self.on_error(lambda: self.markAsRead(msg))) + + def delete(self, msg): + SMTPEmailAccount.delete(self, msg) + self._delete(msg) + + @cmdqueue() + def _delete(self, msg): + SMTPEmailAccount.delete(self, msg) + self.imap.delete(msg, error = lambda:self.on_error(lambda:self.delete(msg))) + +_uidMatcher = re.compile(r'UID ([0-9]+?) ') +def getUid(s): + ''' + Given strings like '3 (UID 16407497 RFC822 {2410}' returns 16407497. + ''' + + match = _uidMatcher.search(s) + if match: return match.group(1) + else: raise ValueError('no uid found') + +if __name__ == '__main__': + from tests.testapp import testapp + from pprint import pprint + + acct = IMAPMail('digsby04', 'notreallythepassword', + imapserver = 'imap.aim.com', + imapport = 143, + require_ssl = False) + print acct._connect() + print acct.real_update() + + import wx + a = testapp('../..') + f = wx.Frame(None, -1, 'IMAP Test') + f.Sizer = s = wx.BoxSizer(wx.VERTICAL) + + b = wx.Button(f, -1, 'noop', style = wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP) + b.Bind(wx.EVT_BUTTON, lambda e: acct.server.noop()) + + b2 = wx.Button(f, -1, 'recent') + b2.Bind(wx.EVT_BUTTON, lambda e: pprint(acct.server.recent())) + + b3 = wx.Button(f, -1, 'update') + b3.Bind(wx.EVT_BUTTON, lambda e: pprint(acct.real_update(), f.SetTitle(str(acct.updated_count)))) + + s.AddMany([b, b2, b3]) + + f.Show() + f.imap = acct + a.MainLoop() diff --git a/digsby/src/mail/imapcheck.py b/digsby/src/mail/imapcheck.py new file mode 100644 index 0000000..c581dbc --- /dev/null +++ b/digsby/src/mail/imapcheck.py @@ -0,0 +1,271 @@ +''' +Simple interface for IMAP checking. +''' + +import imaplib, time, re, email +from datetime import datetime +from pprint import pformat +from mail.emailobj import DecodedEmail +from mail import Email, AuthenticationError +from logging import getLogger; log = getLogger('imapcheck') +from util import autoassign, callsback, traceguard +import traceback + +import ssl +from imaplib import IMAP4_SSL_PORT +import socket + +class IMAP4_SSL_Fixed(imaplib.IMAP4_SSL): + '''Fixed because SSL objects may return an empty string even with suppress_ragged_eofs=True.''' + def open(self, host = '', port = IMAP4_SSL_PORT): + """Setup connection to remote server on "host:port". + (default: localhost:standard IMAP4 SSL port). + This connection will be used by the routines: + read, readline, send, shutdown. + """ + self.host = host + self.port = port + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((host, port)) + self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, suppress_ragged_eofs=False) + + def readline(self): + """Read line from remote.""" + line = [] + while 1: + char = self.sslobj.read(1) + if not char: + raise ssl.SSLError(ssl.SSL_ERROR_EOF) + line.append(char) + if char == "\n": return ''.join(line) + + def read(self, size): + """Read 'size' bytes from remote.""" + # sslobj.read() sometimes returns < size bytes + chunks = [] + read = 0 + while read < size: + data = self.sslobj.read(min(size-read, 16384)) + if not data: + raise ssl.SSLError(ssl.SSL_ERROR_EOF) + read += len(data) + chunks.append(data) + + return ''.join(chunks) + +class IMAPCheck(object): + + def __init__(self, maxfetch = 25): + self.maxfetch = maxfetch + self.cache = {} + self.srv = None + + def login(self, host, port, ssl, user, password): + autoassign(self, locals()) + + def update(self): + if self.srv is not None: + srv, self.srv = self.srv, None + try: + srv.logout() + except ssl.SSLError, e: + log.error('got SSLError during logout: %r', e) + except Exception: + traceback.print_exc() + + if self.ssl: + cls = IMAP4_SSL_Fixed + else: + cls = imaplib.IMAP4 + + srv = cls(self.host, self.port) + + try: + srv.login(srv._checkquote(self.user.encode('ascii')), self.password) + except (imaplib.IMAP4.error, imaplib.IMAP4_SSL.error): + raise AuthenticationError + + + srv.select() + + # retrieve UIDs for messages without \Seen or \Deleted flags + res, lst = srv.uid('search', None, '(UNSEEN UNDELETED)') + if res != 'OK': + raise Exception('could not retrieve unseen messages from the server') + + uids = ' '.join(lst).split(' ') if lst[0] else [] + log.info('%d unseen uids: %r', len(uids), uids) + + msg_sendtimes = {} + unread_msgs = [] + + # retrieve INTERNALDATEs for each message in that list + if uids: + res, lst = srv.uid('FETCH', ','.join(uids), '(INTERNALDATE FLAGS)') + if res != 'OK': + raise Exception('could not retrieve message dates from the server') + + for resp in lst: + try: + # keep only responses to the fetch + dt, uid, flags = getDatetime(resp), getUid(resp), getFlags(resp) + + log.info('%s %r', uid, flags) + + if (dt is not None and uid is not None and + ('\\Seen' not in flags and '\\Deleted' not in flags)): + unread_msgs.append((dt, uid)) + msg_sendtimes[uid] = dt + except Exception: + traceback.print_exc() + + count = len(unread_msgs) + + # Newest messages first. + unread_msgs.sort(reverse = True) + + # ask for a limited number of newest messages, excluding ones + # we've already downloaded + + uids_to_fetch = [] + uids_already_fetched = [] + + for dt, uid in unread_msgs[:self.maxfetch]: + try: + if uid not in self.cache: + uids_to_fetch += [uid] + else: + uids_already_fetched += [uid] + except Exception: + traceback.print_exc() + + emailobjs = [] + + from common import pref + + if uids_to_fetch: + res, lst = srv.uid('fetch', ','.join(uids_to_fetch), + '(BODY.PEEK[]<0.%d>)' % pref('imaplib.max_fetch_bytes', default=5120, type=int)) + if res != 'OK': + raise Exception('could not retrieve message contents from the server') + + #need a real parser here. + #assert not (len(lst) % len(uids_to_fetch)) + #print len(lst), len(uids_to_fetch) + + # This mess is due to Exchange servers returning data where + # UID comes after the message, which is valid, but mangled + # by imaplib.py. + lst2 = [] + currinfo = [] + currdata = None + for resp in lst + [(None, None)]: + if isinstance(resp, tuple): + if currdata: + lst2.append((currinfo, currdata)) + currdata = None + currinfo = [] + assert len(resp) == 2 + currinfo.append(resp[0]) + currdata = resp[1] + else: + assert isinstance(resp, str) + currinfo.append(resp) + + for resp in lst2: + try: + if isinstance(resp, tuple): + nfo, msg = resp + + uid = getUid(nfo) + sndtime = msg_sendtimes[uid] + emailobj = emailFromString(uid, msg, sndtime) + self.cache[uid] = emailobj + emailobjs += [emailobj] + except Exception: + traceback.print_exc() + + for uid in uids_already_fetched: + emailobjs += [self.cache[uid]] + + + log.info('untagged responses') + log.info(pformat(srv.untagged_responses)) + srv.untagged_responses.clear() + + self.srv = srv + return count, emailobjs + + #TODO: why isn't this handled more cleanly in EmailAccount?? + + @callsback + def markAsRead(self, msg, callback = None): + try: + self.srv.uid("STORE", msg.id, "+FLAGS.SILENT", r"(\SEEN)") + except: + import traceback + traceback.print_exc() + + return callback.error() + + callback.success() + + @callsback + def delete(self, msg, callback = None): + try: + self.srv.uid("STORE", msg.id, "+FLAGS.SILENT", r"(\DELETED \SEEN)") + except: + import traceback + traceback.print_exc() + + return callback.error() + + callback.success() + +# +# utility functions for parsing information out of server responses +# + +def emailFromString(uid, s, sendtime_if_error): + 'Email RFC822 string -> Email object' + + return Email.fromEmailMessage(uid, DecodedEmail(email.message_from_string(s)), + sendtime_if_error) + + +def getDatetime(s): + 'Returns a datetime object for an INTERNALDATE response.' + + timetuple = imaplib.Internaldate2tuple(s) + + if timetuple is not None: + return datetime.fromtimestamp(time.mktime(timetuple)) + +_uidMatcher = re.compile(r'UID ([0-9]+)\D') +def getUid(s): + "Given strings like '3 (UID 16407497 RFC822 {2410}' returns 16407497." + if isinstance(s, basestring): + vals = [s] + else: + vals = s + for val in vals: + match = _uidMatcher.search(val) + if match: + return match.group(1) + else: + log.error('coult not find uid in %r', s) + +def getFlags(s): + 'Returns a tuple of flag strings.' + + return imaplib.ParseFlags(s) + + +if __name__ == '__main__': + i = IMAPCheck() + i.login('imap.aol.com', 143, False, 'digsby04', 'thisisnotapassword') + print + print 'DONE' + print + print repr(i.update()) + diff --git a/digsby/src/mail/passport.py b/digsby/src/mail/passport.py new file mode 100644 index 0000000..e7c0682 --- /dev/null +++ b/digsby/src/mail/passport.py @@ -0,0 +1,422 @@ +from urllib2 import urlopen, quote, unquote, Request, HTTPError, URLError + +from hashlib import sha1 +from hmac import HMAC + +import logging +log = logging.getLogger('mail.passport') + +import util +import util.xml_tag +import util.packable as packable +import util.primitives.bits as bits +import util.primitives.funcs as funcs + +csd_to_dict = util.fmt_to_dict(',','=') + +def escape(s): + return s.encode('xml').replace("'", ''') + +def unescape(s): + return s.decode('xml') + +class SecurityTokenRequest(object): + def __init__(self, id, domain, policyref): + id = 'RST%d'%id + util.autoassign(self, locals()) + self.__gotdata = False + self._received = None + + def _set_received(self, val): + self._received = val + self.__gotdata = True + + def _get_received(self): + return self._received + + received = property(_get_received, _set_received) + + def _get_Token(self): + if self.__gotdata: + return str(self.received.RequestedSecurityToken.BinarySecurityToken) + else: + return None + + def _set_Token(self, newtoken): + if self.__gotdata: + self.received.RequestedSecurityToken.BinarySecurityToken._cdata = newtoken + + else: + raise AttributeError("can't set attribute") + + Token = property(_get_Token, _set_Token) + + @property + def number(self): + return int(self.id[3:]) + +def csd_to_storage(str): + ''' + turn mime headers into a storage object + ''' + info = csd_to_dict(str) + for k in info.keys(): + info[util.pythonize(k)] = info.pop(k) + + return util.to_storage(info) + +@util.callsback +def do_tweener_auth_2(username, password, twn_str, callback=None): + ''' + TWN (Tweener) authorization + + MSN's authorization procedure to login with a passport. + This makes 2 https requests, no wonder it takes so long to login + to MSN. + + @param twn_str: The TWN ticket from an earlier logon stage + ''' + twn_str, = twn_str + data = urlopen("https://nexus.passport.com/rdr/pprdr.asp" ).headers.get('passporturls') + + info = csd_to_storage(data) + + login_url = info['DALogin'] + if login_url.find("https://" ) == -1: + login_url = 'https://' + login_url + + login_request = Request(login_url) + + login_request.add_header('Authorization', + 'Passport1.4 OrgVerb=GET,OrgURL=%s,sign-in=%s,pwd=%s,%s' % + (quote("http://messenger.msn.com"), + quote(username), + quote(password), + twn_str)) + login_response = None + try: + login_response = urlopen(login_request).headers + except (HTTPError, URLError): + log.critical(login_response) + log.critical("Login failed") + callback.error('Error connecting to login server (%s)' % login_response) + + if 'www-authenticate' in login_response: + response_data = login_response['www-authenticate'].split()[1] + elif 'authentication-info' in login_response: + response_data = login_response['authentication-info'].split()[1] + + info = csd_to_storage(response_data) + + status = info.da_status + + if status == 'success': + callback.success(info.from_pp.strip("'")) + elif status == 'failed': + log.error('Login response status was "failed". heres the info.cbtxt: %r', info.cbtxt) + callback.error(unquote(info.cbtxt)) + +@util.callsback +def do_tweener_auth_3(username, password, twn_str, use_ssl=True, callback=None): + ''' + use_ssl is ignored in this version of passport; + all authentication is done via ssl + ''' + twn_str, = twn_str + twn_str = unquote(twn_str).replace(',','&') + + if not twn_str.startswith('?'): + twn_str = '?'+twn_str + + sectoks = ( + SecurityTokenRequest(id, dom, pol) + for id,(dom,pol) in + enumerate((('http://Passport.NET/tb',''), + ('messengerclear.live.com', twn_str))) + ) + + env = make_auth_envelope(username, password, sectoks) + + t = util.xml_tag.post_xml('https://loginnet.passport.com/RST.srf', + env._to_xml(), + success=lambda _t: _tweener_auth_3_success(username, password, callback, _t, twn_str), + error= lambda _e: callback.error('Authentication error: %s' % str(_e))) + +def _tweener_auth_3_success(username, password, callback, t, twn_str): + try: + ticket = str(t._find('BinarySecurityToken')[0]) + assert ticket, 'no ticket: <%r>'%ticket + except: + log.warning('Passport v3.0 failed, trying passport 2.0') + return do_tweener_auth_2(username, password,(twn_str,), callback=callback) + + return callback.success(ticket.replace('&','&')) + +@util.callsback +def do_tweener_auth_4(username, password, tweener, iv=None, xml=None, callback=None): + + ### + # Build and send SOAP request + # + policy_uri, nonce = tweener + sectoks = [ + SecurityTokenRequest(id, dom, pol) + for id,(dom,pol) in + enumerate((('http://Passport.NET/tb',''), + ('contacts.msn.com','?fs=1&id=24000&kv=9&rn=93S9SWWw&tw=0&ver=2.1.6000.1'), + ('spaces.live.com', 'MBI'), + ('messenger.msn.com', '?id=507'), + ('messengersecure.live.com','MBI_SSL'), + ('messengerclear.live.com', policy_uri), + )) + ] + + env = make_auth_envelope(username, password, sectoks) + + request_tokens(env, sectoks, xml, + success=lambda tok,sec, toks:callback.success(tok,mbi_crypt(sec, nonce,iv), toks), + error=callback.error) + +# ### +# # Now that we have the nonce and "binary secret" key, +# # we need to do fancy crypto to create the challenge response. +# +# #mbi_blob = mbi_crypt(secret, nonce) +# #result = '%s %s' % (token,mbi_blob) +# #callback.success(result) +# +# callback.success(token, mbi_crypt(secret, nonce)) + +@util.callsback +def request_tokens(env, sectoks, xml=None, login_check=True, callback=None, url='https://login.live.com/RST.srf'): + ### + # Received SOAP response is now in xml. Get out the important bits. + # + + return util.xml_tag.post_xml(url, + env._to_xml(), + success=lambda _t: _handle_token_response(env,sectoks, _t,callback=callback), + error =lambda _t: _handle_token_request_error(_t, (env, sectoks, xml, login_check), callback)) + +@util.callsback +def _handle_token_response(env, sectoks, xml, login_check = True, callback = None, url = 'https://login.live.com/RST.srf'): + + if type(xml) is not util.xml_tag.tag: + t = util.xml_tag.tag(xml) + else: + t = xml + + if t.Fault: + errmsg = 'SOAP Fault trying to authenticate. Here\'s the fault xml: %r' % t.Fault._to_xml() + log.error(errmsg) + callback.error(Exception(errmsg)) + return + + tokens = {} + + RSTS = t.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse + + #assert len(RSTS) == len(sectoks) + + login_token = None + + for sectok, RST in zip(sectoks, RSTS): + tokenref = RST.RequestedTokenReference + + sectok.received = RST + + tokens[sectok.domain] = sectok + + if (funcs.get(tokenref.KeyIdentifier,'ValueType',None) == 'urn:passport:compact' and + funcs.get(tokenref.Reference, 'URI', None) == ('#Compact%d'% sectok.number)): + login_token = sectok + if login_token is None: + errmsg = 'Could not find binary secret in response. Heres the response: %r' % t._to_xml() + log.error(errmsg) + return callback.error(Exception(errmsg)) + + token = str(login_token.received.RequestedSecurityToken.BinarySecurityToken).strip() + secret = str(login_token.received.RequestedProofToken.BinarySecret).strip() + + callback.success(token, secret, tokens) + +def _handle_token_request_error(soapexc, args, callback): + ''' + + + + psf:Redirect + + + https://msnia.login.live.com/pp550/RST.srf + + + Authentication Failure + + + + ''' + + if not isinstance(soapexc, util.xml_tag.SOAPException): + import sys + print >>sys.stderr, soapexc + import traceback;traceback.print_exc() + callback.error(soapexc) + + elif 'Redirect' in str(soapexc.fault.faultcode): + redirect_url = soapexc.fault.redirectUrl._cdata + request_tokens(callback=callback, url=redirect_url, *args) + else: + log.error('Exception when requesting tokens. heres the response XML: %r', soapexc.t._to_xml()) + callback.error(soapexc) + + +def mbi_crypt(key, nonce, iv=None): + wssec = "WS-SecureConversation" + from util.cryptography import DES3 + + if iv is None: + iv = bits.getrandbytes(8) + + key1 = key.decode('base64') + key2 = derive_key(key1, wssec+'SESSION KEY HASH') + key3 = derive_key(key1, wssec+'SESSION KEY ENCRYPTION') + + hash = HMAC(key2, nonce, sha1).digest() + + des = DES3(key3, 2, iv) + + # wincrypt pads with '\x08' bytes + pad = lambda s: s+((8-(len(s)%8))*chr(8)) + cipher = des.encrypt(pad(nonce)) + + return keystruct(iv, hash, cipher).pack().encode('base64').replace('\n','') + +def derive_key(key, message): + hmac_attack = lambda s: HMAC(key, s, sha1).digest() + + hash1 = hmac_attack(message) + hash2 = hmac_attack(hash1 + message) + hash3 = hmac_attack(hash1) + hash4 = hmac_attack(hash3 + message) + + return hash2 + hash4[:4] + +class keystruct(packable.Packable): + byteorder = '<' + fmt = util.strlist(''' + size L # always 28 + mode L # always 1 for CBC + cipheralg L # always 0x6603 for 3DES + hashalg L # always 0x8004 for SHA-1 + ivlen L # always 8 + hashlen L # always 20 + cipherlen L # always 72 + + iv 8s # random data for initialization vector + hash 20s # SHA-1 result + cipher 72s # Crypted data + ''') + + def __init__(self, *args): + if len(args) == 3: + iv, hash, cipher = args + packable.Packable.__init__(self, 28, 1, 0x6603, 0x8004, 8, 20, 72, iv, hash, cipher) + else: + packable.Packable.__init__(self, *args) + +ns = lambda ns, name: '%s:%s' % (ns, name) + +ns_map = dict(ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL", + wsse='http://schemas.xmlsoap.org/ws/2003/06/secext', + saml='urn:oasis:names:tc:SAML:1.0:assertion', + wsp="http://schemas.xmlsoap.org/ws/2002/12/policy", + wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", + wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing", + wssc="http://schemas.xmlsoap.org/ws/2004/04/sc", + wst="http://schemas.xmlsoap.org/ws/2004/04/trust",) + +def make_auth_envelope(uname, password, sec_toks): +# uname = escape(uname) +# print 'password before escape:',repr(password) +# password = escape(password) +# print 'password after escape:',repr(password) + + #tag._set_ns_dict(ns_map) + + authinfo = util.xml_tag.tag('AuthInfo', Id='PPAuthInfo', _ns_dict=ns_map) + auth_tags = (('HostingApp', '{7108E71A-9926-4FCB-BCC9-9A9D3D32E423}'), + ('BinaryVersion', 4), + ('UIVersion', 1), + ('Cookies', ''), + + ('RequestParams', 'AQAAAAIAAABsYwQAAAAzMDg0'))#'AQAAAAIAAABsYwQAAAAyMDUy')) # 'AQAAAAIAAABsYwQAAAAxMDMz' + + for atag in auth_tags: + authinfo += atag + + authinfo._recurse_ns('ps') + + security = util.xml_tag.tag('Security', _ns_dict=ns_map) + utoken = util.xml_tag.tag('UsernameToken', Id='user', _ns_dict=ns_map) + + utoken += 'Username', uname + utoken += 'Password', password + + security += utoken + + security._recurse_ns('wsse') + + tokens = util.xml_tag.tag(('ps', 'RequestMultipleSecurityTokens'), Id= 'RSTS', _ns_dict=ns_map) + + for tok in sec_toks: + tokens += SecTok_to_tag(tok) + + env = util.xml_tag.tag('Envelope', xmlns="http://schemas.xmlsoap.org/soap/envelope/", _ns_dict=ns_map) + env.Header += authinfo + env.Header += security + env.Body += tokens + + #tag._set_ns_dict({}) + + return env + +def SecTok_to_tag(tok): + token = util.xml_tag.tag(('wst', 'RequestSecurityToken'), Id=tok.id, _ns_dict=ns_map) + token += ('wst', 'RequestType'), 'http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue' + token += ('wsp', 'AppliesTo'), + token.AppliesTo += ('wsa', 'EndpointReference'), + token.AppliesTo.EndpointReference += ('wsa', 'Address'), tok.domain + + if tok.policyref: + token += util.xml_tag.tag(('wsse','PolicyReference'), URI=escape(tok.policyref), _ns_dict=ns_map) + + return token + + +if __name__ == '__main__': + import util.primitives.bits as bits + data_sets = [ + ('pBsAH1PE97Iapn9KSBgnwhXrYSMW4pR8owDHuEl2uHHYOcuIkTlXJv/He09hM8EK','26 54 8a 6d 78 ef b6 a0', '7d6QketNdj77tbJ60OYrfRaFxK8wHDVU', '''t=EwDYAW+jAAAU7/9PdvvYJ23zPPtor/FYp6zOOb+AAMIw/mhop1kwJEXzEh3RL9m1NtZSQjKhl5VdZ+YORglKpsZkjaDMp4OxbT4k2DycwGJp0TOm9NrHMRkMlfBxyTuoSz7ykvPxcA7aGRFzpKSb/9qFHrrouhOM0xmfNG+bse1mLJZBvJ8arY97Sl+bknzkzK7OWTkjYYatCWC/HA6GA2YAAAi5hIbJSpr+aigBVtdThK4FdpmbcFCEOV0n5sZSzbIPgDXJshII7WO8hHy6YT6W671nHJ6u0biRQEAcK6StXHTMT9XjesBd22m6vU0Zd84fZBm4rA/5yP0aeAjbLDELznzL0nga2J1HxPQzh3GrGGuncOYS3s8VgdMuBEkkQu6COooKE47D08wIvsMLqqrzuMTdrgJs23yabcw6IKtW+h7Umzod8eW2PAsoFDeMnQqPMPlK+WkqVXwrN5BSInq6TQOSPPiuVbFVeipozqTrwUxm7HYytyOJCCXJEC7+RNCfYGDNvHCYqzg6YtXsqRKqCqcfYVVoyEOzme41+sjdezYG3hp7UxYQd622SkVtG4iL/x15ElmTFricGa4b3aI5nnT9JkejpxtDu9QXJLri92tDEZ5AAQ==&p=''', '''HAAAAAEAAAADZgAABIAAAAgAAAAUAAAASAAAACZUim1477agqOu2i/I8hk+Pg/+E7NqzIuMdhhKttQYvu/iTM1R5ffPn4lWJ97ffVqadDf9AV2AWaSSmNqTqat1Zkt1lmmB1pV+zSS+RG6v3wxOtnqOTrOwIuLScAACurFwszUw='''), + ('B/zj+2hwriu8x+Pw/bJIouMbeVy0QOVeFw3Lr+aHbXN8oyMxgpddC2eRMequq+g+','11 e9 f7 9e 7c 14 a8 b9', '2yF3I/LHM6+2RnUgZKbM9eBN/ufdJysB', '''t=EwDYAW+jAAAU7/9PdvvYJ23zPPtor/FYp6zOOb+AADbMUMrwrh00M/sccjmDtcfq0lf20h1At/eQQJfL5K7+ouDgJEd/GuRpe3vopy9jT/U0YNVmZyIfQyQnPYKWGU8pHkXMhcuh/HRnFZu7mJWJAFIS/+wpr+7F1LfOnXjrunJnRxZq3y3nwWDLEkh+x+tQOGD7M0B93KcXODigkydFA2YAAAhywJ/PdD2v2igBV+o6Htfb83lgo8pH9Wlpra7pAUb7MK5L9NjvwWxUk7sEVbNErEJZXuvTfkhZcEhTKGrkZpJRzzx4Qmy3K6317uT3+pVd/Dv4bGxC3ZD/BPWfo9Sj1XXZL8bgGMDgI/rJCBKSAL2nM+gpjGbtTdW8q0QhNzy8WD6FeHrFdOzDcc/339ckMjQvkE5wNieCoRUDpFRjKFr7rVytmAe+8vzecQ2TibxZp5mAke192hbIfa6H8PUUMyKK/mhFqTdfV2HyjZY5YPGXLMrpnmr3fdfv92+a2CIwzSFfhRMVOxnu3X3Gbn0YPryAdiA0gv5Nwuf7wEwFkdQQeebHDSjMsvSZNwJH5SoV92lyiLKicLIPlpaO54PnQ3Y9/JOYdf2d0gYfxSW6JaK+K8tAAQ==&p=''', '''HAAAAAEAAAADZgAABIAAAAgAAAAUAAAASAAAABHp9558FKi5q1MxG97qu/j2H+kxfUff2sq9xTGIpx71DCKtX40WmvnvuuUPGhNyViNKtAnksnnhsQuGVhw+ZCV9vBHoN8oNYBy10cHUyTxLzOVcbCtS55rWlt4pQU+6CGpOPHU='''), + ] + + for nonce, iv, key, their_ticket, their_token in data_sets: + + def checker(ticket, token, alltokens): + mine = '%s %s' % (ticket, token) + print ticket == their_ticket, ticket, their_ticket + print token == their_token, token, their_token + assert ticket == their_ticket + assert token == their_token + + def error(*a): + print 'there was error:',a + raise a + + mine = mbi_crypt(nonce, key, bits.hex2bin(iv)) + print mine + print their_token + assert mine == their_token + #do_tweener_auth_4('', '', ('MBI', nonce), iv=bits.hex2bin(iv), xml=xml, success=checker, error=error) + print 'success' diff --git a/digsby/src/mail/pop.py b/digsby/src/mail/pop.py new file mode 100644 index 0000000..b86f9f6 --- /dev/null +++ b/digsby/src/mail/pop.py @@ -0,0 +1,210 @@ +''' +POP mail +''' + +from mail.smtp import SMTPEmailAccount +from common import pref +from util import ResetTimer +from util.primitives.funcs import get +from logging import getLogger; log = getLogger('popmail'); info = log.info +from mail.emailobj import DecodedEmail +from mail.emailobj import Email +from traceback import print_exc +import email + +from hashlib import sha1 +from util.command_queue import CommandQueue, cmdqueue, callback_cmdqueue + +class PopMail(SMTPEmailAccount): + protocol = 'pop' + default_timeout = 20 + + opening_email_marks_as_read = False + + def __init__(self, **options): + d = self.default + self.popserver = options.get('popserver', '') + self.require_ssl = options.get('require_ssl', d('require_ssl')) + self.popport = options.get('popport', d('popport')) + self.uidlworks = None + self.topworks = True #assume it does until proven otherwise + + self.cmdq = CommandQueue(start_hooks=[self._connect], end_hooks=[self._quit]) + self.timeouttimer = ResetTimer(pref('pop.timeout',self.default_timeout), self.timeout_check) + + SMTPEmailAccount.__init__(self, **options) + + can_has_preview = True + + def timeout_check(self): + log.info('Checking server connection for %r', self) + if self.state in (self.Statuses.OFFLINE, self.Statuses.ONLINE): + log.info('%s is not currently checking', self) + return True + + if get(self, 'conn', False): + try: + self.conn.noop() + except: + self.on_error() + log.error('%s\'s server connection has failed', self) + return False + else: + log.error('%s has no conn attribute', self) + self.on_error() + return False + + def update(self): + SMTPEmailAccount.update(self) + log.info('starting timeout timer') + self.timeouttimer.start() + self.real_update(success = self.finish_update) + + def finish_update(self, updates): + import time + if self.state == self.Statuses.OFFLINE: + log.error('finish_update exiting early, state is %s', self.state) + return + + (updated_emails, updated_count) = updates + log.info("%s got %d new messages %s", self, updated_count, time.ctime(time.time())) + #self.change_state(self.Statuses.ONLINE) + self._received_emails(updated_emails[:25], updated_count) +# if self.state in (self.Statuses.CONNECTING, self.Statuses.CHECKING): +# self.change_state(self.Statuses.ONLINE) + self.error_count = 0 + log.info('stopping timeout timer') + self.timeouttimer.stop() + + + + @callback_cmdqueue() + def real_update(self): + #self.change_state(self.Statuses.CHECKING) + if self.state == self.Statuses.OFFLINE: + return + conn = self.conn + num_emails, box_size = conn.stat() + num_emails = int(num_emails) + emails = [] + + def retr(mid): + if self.topworks: + try: + return conn.top(mid, 100) + except: + self.topworks = False + return conn.retr(mid) + + uidl = conn.uidl() + if uidl[0].startswith("+"): + self.uidlworks = True + msg_tups = [tuple(tup.split()) for tup in uidl[1]][-25:] + + for tup in msg_tups: + try: + mailmsg = retr(tup[0]) + except Exception: + print_exc() + else: + try: + email_id = tup[1] + except IndexError: + email_id = None #someone had '1 ' -> ('1',) None seems to work fine. + emails.append( + Email.fromEmailMessage(email_id, + DecodedEmail( + email.message_from_string( + "\n".join(mailmsg[1]) + )))) + else: + self.uidlworks = False + num_to_get = min(num_emails, 25) + for i in xrange(num_to_get, max(num_to_get-25, -1), -1): + try: + mailmsg = retr(str(i)) + except Exception: + print_exc() + else: + emailstring = "\n".join(mailmsg[1]) + de = DecodedEmail(email.message_from_string(emailstring)) + emails.append(Email.fromEmailMessage( + sha1(emailstring).hexdigest() + "SHA"+str(i)+"SHA", de)) + + + return emails, num_emails + + #self.change_state(self.Statuses.ONLINE) +# import time +# print num_emails, time.time() + + def _connect(self): + if self.require_ssl: + from poplib import POP3_SSL as pop + else: + from poplib import POP3 as pop + + try: + conn = pop(self.popserver, self.popport) + except Exception, e: + log.error('There was an error connecting: %s', e) + self.on_error() + raise + self.conn = conn + log.info(conn.user(self.name)) + try: + password = self._decryptedpw().encode('utf-8') + log.info(conn.pass_(password)) + except Exception, e: + log.error('Bad password: %s', e) + self._auth_error_msg = e.message + self.set_offline(self.Reasons.BAD_PASSWORD) + self.timer.stop() + raise + return conn + + def _quit(self): + try: + self.conn.quit() + except Exception, e: + log.error('Error when disconnecting: %s', str(e)) + if self.state != self.Statuses.ONLINE: + self.set_offline(self.Reasons.CONN_FAIL) + + @cmdqueue() + def delete(self, msg): + SMTPEmailAccount.delete(self, msg) + conn = self.conn + + if self.uidlworks: + uidl = conn.uidl() + #check if response is ok + mids = [mid for mid, uid in + [tuple(tup.split()) for tup in uidl[1]] + if uid == msg.id] + if mids: + mid = mids[0] + conn.dele(mid) + else: + hash, msgid, _ = msg.id.split("SHA") + newmsg = conn.retr(msgid) + #check if response is ok + newstring = "\n".join(newmsg[1]) + newhash = sha1(newstring).hexdigest() + if hash == newhash: + conn.dele(msgid) + else: + num_emails, box_size = conn.stat() + num_emails = int(num_emails) + for i in xrange(num_emails): + emailhash = sha1("\n".join(conn.retr(str(i))[1])).hexdigest() + if hash == emailhash: + conn.dele(msgid) + break + + def _get_options(self): + opts = SMTPEmailAccount._get_options(self) + opts.update(dict((a, getattr(self, a)) for a in + 'popserver popport require_ssl'.split())) + return opts + diff --git a/digsby/src/mail/smtp.py b/digsby/src/mail/smtp.py new file mode 100644 index 0000000..1fc8fff --- /dev/null +++ b/digsby/src/mail/smtp.py @@ -0,0 +1,369 @@ +#:25 +import traceback +from common import profile +from util import unpack_pstr, pack_pstr +from common.emailaccount import EmailAccount, localprefs_key +from AccountManager import NETWORK_FLAG +from prefs import localprefprop + +SMTP_UPGRADING = True + +import smtplib, re + +import logging +log = logging.getLogger('smtp') + +#**************************************************************** +#below is from +#http://trac.edgewall.org/browser/trunk/trac/notification.py +#**************************************************************** +MAXHEADERLEN = 76 + +class SMTPsender(object): + _ignore_domains = [] + addrfmt = r'[\w\d_\.\-\+=]+\@(?:(?:[\w\d\-])+\.)+(?:[\w\d]{2,4})' + shortaddr_re = re.compile(r'%s$' % addrfmt) + longaddr_re = re.compile(r'^\s*(.*)\s+<(%s)>\s*$' % addrfmt) + + def __init__(self, name, password, server, port=25, use_tls=False, + from_name=None, reply_to=None): + self.user_name = name + self.password = password + + self.smtp_server = server + self.smtp_port = port + + #intelligent defaults, for now + self.from_name = from_name if from_name is not None else name #self.user_name + self.replyto_email = reply_to if reply_to is not None else name #self.user_name + + self._use_tls = use_tls + + self._init_pref_encoding() + + + + def format_header(self, key, name, email=None): + from email.Header import Header #@UnresolvedImport + maxlength = MAXHEADERLEN-(len(key)+2) + # Do not sent ridiculous short headers + if maxlength < 10: + raise AssertionError, "Header length is too short" + try: + tmp = name.encode('utf-8') if isinstance(name, unicode) else name + header = Header(tmp, 'utf-8', maxlinelen=maxlength) + except UnicodeEncodeError: + header = Header(name, self._charset, maxlinelen=maxlength) + if not email: + return header + else: + return '"%s" <%s>' % (header, email) + + def add_headers(self, msg, headers): + for h in headers: + msg[h] = self.encode_header(h, headers[h]) + + def get_smtp_address(self, address): + if not address: + return None + + def is_email(address): + pos = address.find('@') + if pos == -1: + return False + if address[pos+1:].lower() in self._ignore_domains: + return False + return True + + if not is_email(address): + if address == 'anonymous': + return None + if self.email_map.has_key(address): + address = self.email_map[address] + elif SMTPsender.nodomaddr_re.match(address): + if self.config.getbool('notification', 'use_short_addr'): + return address + domain = self.config.get('notification', 'smtp_default_domain') + if domain: + address = "%s@%s" % (address, domain) + else: + self.env.log.info("Email address w/o domain: %s" % address) + return None + + mo = self.shortaddr_re.search(address) + if mo: + return mo.group(0) + mo = self.longaddr_re.search(address) + if mo: + return mo.group(2) + self.env.log.info("Invalid email address: %s" % address) + return None + + def _init_pref_encoding(self): + from email.Charset import Charset, QP, BASE64 #@UnresolvedImport + self._charset = Charset() + self._charset.input_charset = 'utf-8' + pref = 'base64' #self.env.config.get('notification', 'mime_encoding').lower() + if pref == 'base64': + self._charset.header_encoding = BASE64 + self._charset.body_encoding = BASE64 + self._charset.output_charset = 'utf-8' + self._charset.input_codec = 'utf-8' + self._charset.output_codec = 'utf-8' + elif pref in ['qp', 'quoted-printable']: + self._charset.header_encoding = QP + self._charset.body_encoding = QP + self._charset.output_charset = 'utf-8' + self._charset.input_codec = 'utf-8' + self._charset.output_codec = 'utf-8' + elif pref == 'none': + self._charset.header_encoding = None + self._charset.body_encoding = None + self._charset.input_codec = None + self._charset.output_charset = 'ascii' + else: + raise AssertionError, 'Invalid email encoding setting: %s' % pref + + def encode_header(self, key, value): + if isinstance(value, tuple): + return self.format_header(key, value[0], value[1]) + if isinstance(value, list): + items = [] + for v in value: + items.append(self.encode_header(v)) + return ',\n\t'.join(items) + mo = self.longaddr_re.match(value) + if mo: + return self.format_header(key, mo.group(1), mo.group(2)) + return self.format_header(key, value) + + def begin_send(self): + self.server = smtplib.SMTP(self.smtp_server, self.smtp_port) + # self.server.set_debuglevel(True) + if self._use_tls: + self.server.ehlo() + if not self.server.esmtp_features.has_key('starttls'): + raise AssertionError, "TLS enabled but server does not support TLS" + self.server.starttls() + self.server.ehlo() + if self.user_name: + try: + self.server.login(self.user_name, self.password) + except: + pass + + def send_email(self, to='', subject='', body='', cc='', bcc=''): + self.begin_send() + self.send([to], [], subject, body) + try: + self.finish_send() + except: + pass + + def send(self, torcpts, ccrcpts, subject, body, mime_headers={}): + from email.MIMEText import MIMEText #@UnresolvedImport +# from email.Utils import formatdate #@UnresolvedImport +# public_cc = self.config.getbool('notification', 'use_public_cc') + headers = {} + headers['Subject'] = subject + headers['From'] = (self.from_name, self.from_name) + + def build_addresses(rcpts): + """Format and remove invalid addresses""" + return filter(lambda x: x, \ + [self.get_smtp_address(addr) for addr in rcpts]) + + def remove_dup(rcpts, all): + """Remove duplicates""" + tmp = [] + for rcpt in rcpts: + if not rcpt in all: + tmp.append(rcpt) + all.append(rcpt) + return (tmp, all) + + toaddrs = build_addresses(torcpts) +# ccaddrs = build_addresses(ccrcpts) +# bccaddrs = build_addresses(bccrcpts) +# accaddrs = build_addresses(accparam.replace(',', ' ').split()) or [] +# bccaddrs = build_addresses(bccparam.replace(',', ' ').split()) or [] + + recipients = [] + (toaddrs, recipients) = remove_dup(toaddrs, recipients) +# (ccaddrs, recipients) = remove_dup(ccaddrs, recipients) +# (bccaddrs, recipients) = remove_dup(bccaddrs, recipients) + + # if there is not valid recipient, leave immediately + if len(recipients) < 1: +# self.env.log.info('no recipient for a ticket notification') + return + +# pcc = accaddrs +# if public_cc: +# pcc += ccaddrs +# if toaddrs: + headers['To'] = ', '.join(toaddrs) +# if pcc: +# headers['Cc'] = ', '.join(pcc) +# headers['Date'] = formatdate() + # sanity check +# if not self._charset.body_encoding: +# try: +# dummy = body.encode('ascii') +# except UnicodeDecodeError: +# raise AssertionError, "Ticket contains non-Ascii chars. " \ +# "Please change encoding setting" + msg = MIMEText(body.encode('utf-8'), 'plain') + # Message class computes the wrong type from MIMEText constructor, + # which does not take a Charset object as initializer. Reset the + # encoding type to force a new, valid evaluation + del msg['Content-Transfer-Encoding'] + msg.set_charset(self._charset) + self.add_headers(msg, headers); + self.add_headers(msg, mime_headers); +# self.env.log.info("Sending SMTP notification to %s:%d to %s" +# % (self.smtp_server, self.smtp_port, recipients)) + msgtext = msg.as_string() + # Ensure the message complies with RFC2822: use CRLF line endings + recrlf = re.compile("\r?\n") + msgtext = "\r\n".join(recrlf.split(msgtext)) + self.server.sendmail(msg['From'], recipients, msgtext) + + def finish_send(self): + if self._use_tls: + # avoid false failure detection when the server closes + # the SMTP connection with TLS enabled + import ssl + try: + self.server.quit() + except ssl.SSLError: + pass + else: + self.server.quit() + + +###back to new code +from common.emailaccount import EmailAccount +from util import threaded +class SMTPEmailAccount(EmailAccount): + DEFAULT_SMTP_REQUIRE_SSL = False + DEFAULT_SMTP_PORT = 25 + + def __init__(self, **options): + d = self.default + self.smtp_server = options.get('smtp_server', d('smtp_server')) + self.smtp_require_ssl = options.get('smtp_require_ssl', d('smtp_require_ssl')) + self.smtp_port = options.get('smtp_port', d('smtp_port')) + self.smtp_username = options.get('smtp_username', d('smtp_username')) + self._email_address = options.get('email_address', d('email_address')) + self._encrypted_smtppw = profile.crypt_pw(options.get('smtp_password', d('smtp_password'))) + self._encrypted_pw = options.pop('password') + EmailAccount.__init__(self, password=self.password, **options) + + def get_email_address(self): + return self._email_address + def set_email_address(self, val): + self._email_address = val + + @property + def from_name(self): + return self.email_address + + @classmethod + def _unglue_pw(cls, password): + passwordstr = profile.plain_pw(password).encode('utf-8') + if passwordstr: + password, r = unpack_pstr(passwordstr) + try: + smtppassword, r = unpack_pstr(r) + except Exception: + smtppassword, r = '', '' + else: + raise ValueError("Can't decrypt %r", password) + + return profile.crypt_pw(password.decode('utf-8')), profile.crypt_pw(smtppassword.decode('utf-8')) + + def _set_password(self, password): + try: + self._encrypted_pw, self._encrypted_smtppw = self._unglue_pw(password) + except Exception, e: + traceback.print_exc() + assert SMTP_UPGRADING + self._encrypted_pw = password + + + @classmethod + def _glue_pw(cls, encrypted_pw, encrypted_smtppw): + password = pack_pstr(profile.plain_pw(encrypted_pw).encode('utf-8')).decode('utf-8') + smtppw = pack_pstr(profile.plain_pw(encrypted_smtppw).encode('utf-8')).decode('utf-8') + return profile.crypt_pw(password + smtppw) + + def _get_password(self): + return self._glue_pw(self._encrypted_pw, self._encrypted_smtppw) + + password = property(_get_password, _set_password) + + def _decryptedpw(self): + return profile.plain_pw(self._encrypted_pw) + + def _decrypted_smtppw(self): + return profile.plain_pw(self._encrypted_smtppw) + + smtp_password = property(_decrypted_smtppw) + + def update_info(self, **info): + if not self.isflagged(NETWORK_FLAG): + if 'smtp_password' in info: + self._encrypted_smtppw = profile.crypt_pw(info.pop('smtp_password')) + + if 'password' in info: + self._encrypted_pw = info['password'] + + if '_encrypted_pw' in info and '_encrypted_smtppw' in info: + self._encrypted_pw = info.pop('_encrypted_pw') + self._encrypted_smtppw = info.pop('_encrypted_smtppw') + + info['password'] = self._glue_pw(self._encrypted_pw, self._encrypted_smtppw) + else: + self.password = info.pop('password') + + log.info("smtp update_info: %r", info) + EmailAccount.update_info(self, **info) + + def _get_options(self): + opts = EmailAccount._get_options(self) + opts.update(dict((a, getattr(self, a)) for a in + 'smtp_server smtp_port smtp_require_ssl smtp_username email_address'.split())) + return opts + + @classmethod + def from_net(cls, info): + password = info.password #IS BOTH, NEEDS TO BE ONE + try: + encrypted_pw, encrypted_smtppw = cls._unglue_pw(password) + except Exception, e: + traceback.print_exc() + assert SMTP_UPGRADING + encrypted_pw = password + encrypted_smtppw = u'' + info.password = encrypted_pw + smtppw = profile.plain_pw(encrypted_smtppw) + return EmailAccount.from_net(info, smtp_password=smtppw) + + @threaded + def send_email(self, to='', subject='', body='', cc='', bcc=''): + un = self.smtp_username or self.username + f = self.from_name + password = self._decrypted_smtppw() or (self._decryptedpw() if not self.smtp_username else '') + srv = self.smtp_server + if srv in ("smtp.aol.com", "smtp.aim.com"): + if un.endswith('aol.com') or un.endswith('aim.com'): + f = un + else: + f = un + u'@aol.com' + s = SMTPsender(un, password, srv, from_name=f, use_tls=self.smtp_require_ssl) + s.send_email(to=to, subject=subject, body=body, cc=cc, bcc=bcc) + + # Override the "mailclient" property here so that the default + # is "sysdefault" instead of None + mailclient = localprefprop(localprefs_key('mailclient'), 'sysdefault') diff --git a/digsby/src/mail/ymail.py b/digsby/src/mail/ymail.py new file mode 100644 index 0000000..b873291 --- /dev/null +++ b/digsby/src/mail/ymail.py @@ -0,0 +1,523 @@ +from __future__ import with_statement + +import traceback +import cookielib +import urllib2 + +from threading import Lock +from datetime import datetime +from urlparse import urlparse +from urllib import quote +from logging import getLogger + +import util.net +from mail import Email, MailException +from util import UrlQuery, WebFormData, get_func_name, GetDefaultHandlers, threaded +from util.primitives.error_handling import try_this +from common.emailaccount import EmailAccount + +log = getLogger('YahooMail') + +class YahooMailException(MailException): pass +class YahooMailAuthException(YahooMailException): pass +class YahooMailAuthRedirect(YahooMailException): pass +class YahooMailBadDataException(YahooMailException): pass +class YahooMailNoAccountException(YahooMailBadDataException): pass + +SessionIdReissue = 'Client.ClientRedirect.SessionIdReissue' +ExpiredCredentials = 'Client.ExpiredCredentials' +backup_server = 'us.mg1.mail.yahoo.com' #should be calculated with "accellerator" i.e. uk.mg... + +def ymail_action(func): + def ymail_action_wrapper(self, *a, **k): + + if self._current_action is None: + self._current_action = func.func_name + + try: + return func(self, *a,**k) + except YahooMailAuthException, e: + self.bad_pw() + except YahooMailNoAccountException, e: + self.no_mailbox() + except Exception, e: + traceback.print_exc() + finally: + self._current_action = None + + return threaded(ymail_action_wrapper) + + + +class YahooMail(EmailAccount): + protocol = 'ymail' + default_domain = 'yahoo.com' + def __init__(self, *args, **kwargs): + EmailAccount.__init__(self, *args, **kwargs) + self.init_jar() + self.update_lock = Lock() + self.updated_emails = None + self.updated_count = None + self.isBeta = True + self._current_action = None + + def timestamp_is_time(self, tstamp): + return False + + def get_email_address(self): + val = getattr(self, '_default_send_address', None) + return val or EmailAccount.get_email_address(self) + + def init_jar(self): + try: + del self.u + except AttributeError: + pass + try: + del self._json_endpoint + except AttributeError: + pass + + self.jar = cookielib.CookieJar() + self.http_opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.jar), *GetDefaultHandlers()) + self.http_opener.addheaders = [('User-Agent', util.net.user_agent())] + + def get_json_endpoint(self): + try: + return self._json_endpoint + except AttributeError: + newurl = UrlQuery('http://'+ self.hostname + '/ws/mail/v1.1/jsonrpc', appid='YahooMailRC') + #hostname is likely to do an auth in this case, at which point, we might have a real endpoint to return + try: + return self._json_endpoint + except AttributeError: + self._json_endpoint = newurl + return self._json_endpoint + + def set_json_endpoint(self, val): + self._json_endpoint = val + + json_endpoint = property(get_json_endpoint, set_json_endpoint) + + def _reset_state(self): + self.init_jar() + try: + del self.u + except AttributeError: + pass + try: + del self._json_endpoint + except AttributeError: + pass + + def update(self): + log.error('ymail.update called from %s', get_func_name(2)) + if self.offline_reason == self.Reasons.BAD_PASSWORD and hasattr(self, 'u'): + self.init_jar() + EmailAccount.update(self) + self.real_update(success = self.finish_update, error=self.warning) + + def warning(self, e=None): + log.warning("yahoo blew up: %s", e) + log.error('ymail.warning called from %s', get_func_name(2)) + + if isinstance(e, YahooMailAuthException): + self.bad_pw() + else: + self.on_error(self._current_action) + + return True + + def bad_pw(self): + self.init_jar() + EmailAccount.bad_pw(self) + + def finish_update(self, update): + if self.state == self.Statuses.OFFLINE: + log.error('finish_update exiting early, state is %r, reason is %r', self.state, self.offline_reason) + return + if update is None: + log.warning('two updates were running at the same time') + return + try: + (updated_emails, updated_count) = update + self.updated_emails = True + self._received_emails(updated_emails, updated_count) + except (TypeError, ValueError), e: + # all the proper error reporting functions should have been called by now + log.error('Invalid response from real_update: %r', update) + + @threaded + def real_update(self): + # don't change to with, the idea is a non-blocking get on the lock + if self.update_lock.acquire(False): + try: + result = self.get_yMsgs() + if result: + return self._process_update_result(result) + else: + log.info('Got bad result from get_yMsgs: %r', result) + raise Exception('bad result') + finally: + self.update_lock.release() + + def _process_update_result(self, result): + (msgs, incount) = result + updated_count = incount + + emails = [] + for msg in msgs: + if msg.get('flags', {}).get('isRead', False): + continue + + from_ = msg.get('from', {}) + if not from_: + fromname = '' + else: + fromname = from_.get('name', from_.get('email', '')) + + e = Email(id = msg['mid'], + fromname = fromname, + sendtime = try_this(lambda: datetime.fromtimestamp(msg['receivedDate']), None), + subject = msg.get('subject', u''), + attachments= [True] * msg.get('flags', {}).get('hasAttachment', 0)) + + emails.append(e) + + updated_emails = emails + log.info('reporting %d emails', incount) + #self.change_state(self.Statuses.ONLINE) + return updated_emails, updated_count + + def get_auth_form(self): + import ClientForm + try: + forms = ClientForm.ParseResponse(self.http_opener.open('https://mail.yahoo.com'), backwards_compat=False) + for f in forms: + if f.action == 'https://login.yahoo.com/config/login': + form = f + break + else: + raise AssertionError('there should be a login form here') + except Exception, e: + traceback.print_exc() + + form = ClientForm.HTMLForm('https://login.yahoo.com/config/login?', method="POST") + form.new_control('hidden', '.done', {'value' : 'http://mail.yahoo.com'}) + form.new_control('text', 'login', {'value' : self.name.encode('utf-8')}) + form.new_control('text', 'passwd', {'value' : self._decryptedpw().encode('utf-8')}) + form.new_control('hidden', '.save', {'value' : 'Sign+In'}) + form.new_control('hidden', '.done', {'value' : 'http://mail.yahoo.com'}) + else: + form['login'] = self.name.encode('utf-8') + form['passwd'] = self._decryptedpw().encode('utf-8') + return form + + def authenticate(self, tries=1): + form = self.get_auth_form() + try: + resp = self.http_opener.open(form.click()) + except Exception, e: + raise YahooMailException("failed to load url: %r" % e) +# #check to see that we logged in? + respdata = resp.read() + resp.close() + respurl = resp.geturl() + if '/login' in respurl: + with self.jar._cookies_lock: + try: + self.jar._cookies['.yahoo.com']['/']['Y'] + self.jar._cookies['.yahoo.com']['/']['T'] + except KeyError: + log.warning('url was: %r', respurl) + log.warning('html was: %r', respdata) + if tries <= 0: + raise YahooMailAuthException("failed to authenticate") + else: + import time + time.sleep(2) + return self.authenticate(tries - 1) + elif 'replica_agree?' in respurl: + pass + elif 'verify?' in respurl: + pass + elif 'update?' in respurl: + log.info('update? in resp url.') + else: + raise YahooMailAuthException("failed to authenticate, response url not expected", respurl) + try: + resp = self.http_opener.open('http://mail.yahoo.com/') + except Exception, e: + raise YahooMailException("failed to load url: %r" % e) + self.u = urlparse(resp.geturl()) + resp.read() + resp.close() + try: + self.user_data = self.api('GetUserData', _recursed=1000) + self._default_send_address = self.user_data['result']['data']['userSendPref']['defaultFromAddress'] + try: + self.isBeta = int(self.user_data['result']['data']['userFeaturePref'].get('optInState', '2')) == 2 + except ValueError: + pass + except Exception: + traceback.print_exc() + log.info('Authenticated successfully') + + @property + def hostname(self): + if not hasattr(self, 'u'): + self.authenticate() + return self.u.hostname + + def get_yMsgs(self): + log.info("get_yMsgs") + try: + nummessages = [folder for folder in self.api('ListFolders')['result']['folder'] + if folder['folderInfo']['fid'] == 'Inbox'][0]['unread'] + except YahooMailAuthException: + raise + except Exception: + nummessages = 25 + nummessages = min(nummessages, 25) + result = self.api('ListMessages', + fid='Inbox', + numMid=0, + groupBy='unRead', + startMid=0, + numInfo=nummessages, + sortKey='date', + sortOrder='down') + incount = result['result']['folder']['unread'] + incount = int(incount) #floats are stupid for countables. + return (result['result']['messageInfo'], incount) + + def sort_emails(self, new=None): + pass + + def open_url(self, url, data=None): + log.debug('open_url called from %s', get_func_name(2)) + return self._open_url(url, data) + + def _open_url(self, url, data=None): + datastring = '' if not data else " with data: " + data + log.info("opening url: " + url + datastring) + try: + response = self.http_opener.open(url, data) + except Exception, e: + ex = YahooMailException("failed to load url: %r" % e) + log.error('%r', ex) + raise ex + else: + log.info('httpopen succeeded') + respurl = urlparse(response.geturl()) + if 'login.yahoo.com' in respurl.hostname: + log.info('login.yahoo.com in URL -- calling authenticate') + self.authenticate() + return self.open_url(url, data) + + log.info('reading data from httpresponse') + strdata = response.read() + return strdata, respurl + finally: + if 'response' in locals(): + log.debug('closing response') + response.close() + + def delete(self, msg): + EmailAccount.delete(self, msg) + self._delete(msg) + + @ymail_action + def _delete(self, msg): + self.api('MoveMessages', sourceFid='Inbox', destinationFid='Trash', mid=[msg.id]) + + def markAsRead(self, msg): + EmailAccount.markAsRead(self, msg) + self._markAsRead(msg) + + @ymail_action + def _markAsRead(self, msg): + self.api('FlagMessages', fid='Inbox', mid=[msg.id], setFlags={'read':1}) + + def reportSpam(self, msg): + EmailAccount.reportSpam(self, msg) + self._reportSpam(msg) + + @ymail_action + def _reportSpam(self, msg): + mark = dict(FlagMessages = dict(fid='Inbox', mid=[msg.id], setFlags={'spam':1, 'read':1})) + move = dict(MoveMessages = dict(sourceFid='Inbox', + destinationFid='%40B%40Bulk', + mid=[msg.id])) + self.api('BatchExecute', call=[mark, move]) + + def open(self, msg): + EmailAccount.open(self, msg) + + mid = msg.id + + return UrlQuery('http://mrd.mail.yahoo.com/msg?', + mid=mid, + fid='Inbox') + + def urlForEmail(self, msg): + try: + if self.web_login: + return str(self.make_login_string() + '&.done=' + quote(self.open(msg), safe='')) + else: + return self.open(msg) + except Exception, e: + self.warning(e) + return self.open(msg) + + def compose_link(self, to='', subject='', body='', cc='', bcc=''): + extra = dict() + Body = body + subj = Subj = subject + To = to + Cc = cc + Bcc = bcc + + #depending on what kind of account we're dealing with, they might want one or the other + #easier just to send them all than figure out which + for name in 'to To subj Subj subject body Body cc Cc bcc Bcc'.split(): + if vars()[name]: + val = vars()[name] + # TODO: This may result in junk data. Which encoding is Yahoo! Mail + # expecting? + if isinstance(val, unicode): + val = val.encode('utf-8') + extra[name.title()] = val + + + return UrlQuery('http://compose.mail.yahoo.com/', **extra) + + def compose(self, to='', subject='', body='', cc='', bcc=''): + link = self.compose_link(to=to, subject=subject, + body=body, cc=cc, bcc=bcc) + try: + if self.web_login: + return str(self.make_login_string() + '&.done=' + quote(link, safe='')) + else: + return link + except Exception, e: + self.warning(e) + return link + + @threaded + def send_email(self, to='', subject='', body='', cc='', bcc=''): + result = self.api('SendMessage', + message={'subject':subject, + 'from':{'email':self.email_address}, #needs to come from user info + 'to':{'email':to}, + 'simplebody':{'text':body}}) + if result['error'] is not None: + log.error('send_email(to=%r, subject=%r, body=%r) = %r', + to, subject, body, result['error']) + raise YahooMailException(result['error']['message']) + return True + + def make_login_string(self): + with self.jar._cookies_lock: + y = yBrowserCookie(self.jar._cookies['.yahoo.com']['/']['Y']) + t = yBrowserCookie(self.jar._cookies['.yahoo.com']['/']['T']) + return "http://msg.edit.yahoo.com/config/reset_cookies?&" + y + "&" + \ + t + '&.ver=2' + + @property + def inbox_url(self): + try: + if self.web_login and hasattr(self, 'u'): + link = UrlQuery('http://mrd.mail.yahoo.com/inbox') + loginstr = self.make_login_string() + log.debug('returning login URL for yahoo inbox') + return str(loginstr + '&.done=' + quote(link, safe='')) + else: + return "http://mrd.mail.yahoo.com/inbox" + except Exception, e: + self.warning(e) + return "http://mrd.mail.yahoo.com/inbox" + + def api(self, method, **params): + foo = None + from simplejson import loads, dumps + recursed = params.pop('_recursed', 0) + recurse = False + try: + foo = self.http_opener.open(self.json_endpoint, dumps(dict(method=method, params=[params]))).read() + log.debug_s("got data from yahoo: %r", foo) + if not foo.startswith('{'): + import zlib + try: + foo = foo.decode('z') + except zlib.error: + raise YahooMailAuthRedirect + foo = loads(foo) + except YahooMailAuthException: + raise + except Exception, e: + if hasattr(e, 'read') or isinstance(e, YahooMailAuthRedirect): + if hasattr(e, 'read'): + foo = e.read() + log.debug("got error data from yahoo: %r for %r", foo, self.json_endpoint) + if isinstance(e, YahooMailAuthRedirect) or getattr(e, 'code', None) in (404, 403): + self._json_endpoint = UrlQuery('http://'+ backup_server + '/ws/mail/v1.1/jsonrpc', appid='YahooMailRC') + recurse = True + else: + try: + if not foo.startswith('{'): + foo = foo.decode('z') + foo = loads(foo) + code = foo['error']['code'] + if code == SessionIdReissue: + recurse = True + self.json_endpoint = foo['error']['detail']['url'] + elif code in (ExpiredCredentials, 'Client.MissingCredentials'): + recurse = True + self.init_jar() + else: + raise YahooMailException(foo['error']['message']) + except YahooMailException: + raise + except Exception: + raise e + else: + raise + finally: + if recurse and recursed < 5: + params['_recursed'] = recursed + 1 + return self.api(method, **params) + return foo + +class FakeYmail(YahooMail): + count = 1337 + def __init__(self, username, password): + self.name = username + self.password = password + self.init_jar() + + def _decryptedpw(self): + return self.password + + def __len__(self): + return 1337 + +if __name__ == '__main__': + import main + main.setup_log_system() + f = FakeYmail('username','passwordShouldNotBeInSourceCode') + f.authenticate() +# print f.hostname +# print f._get_inbox_count() + from pprint import pprint + msgs = [m for m in f.get_yMsgs()[0]] + print len(msgs) + pprint(msgs) + +class yBrowserCookie(str): + def __new__(cls, cookie): + return str.__new__(cls, '.' + cookie.name.lower() + '=' + cookie.name + '=' + + quote(cookie.value, safe='/=') + ';+path=' + cookie.path + + ';+domain=' + cookie.domain) + + diff --git a/digsby/src/main.py b/digsby/src/main.py new file mode 100644 index 0000000..6d95b5c --- /dev/null +++ b/digsby/src/main.py @@ -0,0 +1,1650 @@ +''' + +The Digsby wxApp, and startup and shutdown functionality. + +''' +from __future__ import with_statement + +import sys + +import wx +import os.path, logging, platform +import traceback +from time import time +from path import path +from traceback import print_exc +from threading import currentThread + +from config import platformName, newMenubar + +from logging import getLogger +import hooks +log = getLogger(); info = log.info + +import digsbysite + +IDLE_CHECK_MS = 2000 +IDLE_CHECK_MOUSE_DISTANCE = 10 + +# on release builds, the name of the log file. see "get_logdir" for its location +LOG_FILENAME = 'digsby.log.csv' + +LOG_SPEED_LIMIT = .4 * 1024 * 1024 # bytes/sec +LOG_SPEED_WINDOW = 10 # seconds + +# hanging thread exit checker +USE_HANGING_THREAD_DAEMON = True +EXIT_TIMEOUT_SECS = 30 + +# only use single instance mode in release builds, or if +# --single is specified on the command line. additionally, +# --multi in a release build allows multiple instances to run +USE_SINGLE_INSTANCE = (not getattr(sys, 'DEV', False)) and '--multi' not in sys.argv or '--single' in sys.argv + +APP_NAME = u'Digsby' + +def init_threadpool(): + if getattr(wx, 'WXPY', False): + from util.threads.bgthread import add_before_cb, add_after_cb + add_before_cb(wx.SipNewThread) + add_after_cb(wx.SipEndThread) + + from util.threads.threadpool import ThreadPool + ThreadPool(5) + +if USE_SINGLE_INSTANCE: + from singleinstance import SingleInstanceApp + DigsbyAppBase = SingleInstanceApp +else: + DigsbyAppBase = wx.App + +from gui.app.mainMenuEvents import MainMenuEventHandler + +class DigsbyApp(DigsbyAppBase, MainMenuEventHandler): + 'The main Digsby wxApp.' + + def OnFatalException(self): + ''' + Called when a fatal exception (divide by zero, null pointer dereference, + access violation, etc) occurs. + ''' + + # prevent reentry or calling more than once + if getattr(self, '_did_fatal_exception', False): return + self._did_fatal_exception = True + + # Attempt to dump Python stack traces for each thread out to stderr + try: + from common.commandline import where_string + print >> sys.stderr, where_string(duplicates = True) + sys.stderr.flush() + except Exception, e: + print_exc() + + def __init__(self, plugins=None): + self.plugins = plugins + if USE_SINGLE_INSTANCE: + DigsbyAppBase.__init__(self, appname = APP_NAME, redirect = False) + else: + DigsbyAppBase.__init__(self, redirect = False) + + # Add thread-safety checks to wx methods in dev mode. It's not comprehensive, + # but it checks over 3000 methods, so it's a lot better than nothing :) + if sys.DEV: + import gui.app + gui.app.addThreadChecksToClassRecursive(wx) + MainMenuEventHandler.__init__(self) + + self.Bind(wx.EVT_ACTIVATE_APP, self.OnAppActivate) + + self.global_hotkeys = {} + self.next_hotkey_id = 0 + + self.PreShutdown = [] + + + self.SetStatusPrompt = SetStatusPrompt + + def OnAppActivate(self, event): + ''' + Send out a notification with the current state, as there are at least three + targets in various areas that need to respond to app activation events: + + - MenuListBox (to dismiss windowless menus) + - Mac (and probably Linux) docking (for autohide) + - Mac notifications (e.g. for dock bouncing) + ''' + from wx.lib.pubsub import Publisher + Publisher().sendMessage(('app', 'activestate', 'changed'), (event.Active)) + + def Restart(self): + oldval = getattr(self, 'restarting', False) + + self.restarting = True + if not self.DigsbyCleanupAndQuit(): + self.restarting = oldval + + def AfterStartup(self): + if getattr(self, '_afterstartup', False): + return + + self._afterstartup = True + + log.info('AfterStartup') + import wx + import util.callbacks + #do not confuse w/ wx.CallLater + util.callbacks.register_call_later('MainThread', wx.CallAfter) + + import hooks + hooks.notify('proxy.info_changed', util.GetProxyInfo()) + + import urllib + log.debug('system default proxy information: %r', urllib._getproxies()) + + if 'wxMSW' in wx.PlatformInfo: + self.setup_fullscreen() + import gui.native.win.winutil as winutil + winutil.disable_callback_filter() + + self.init_hotkeys() + self.plugins = self.plugins if self.plugins is not None else init_plugins() + + # start the CPU monitor + import wx + if 'wxMSW' in wx.PlatformInfo and sys.opts.cpuwatch: + from util.perfmon import CPUWatch + self.cpu_watcher = CPUWatch() + + def OnInit(self): + self.SetExitOnFrameDelete(False) + log.info('SetExitOnFrameDelete(False)') + + set_app_info(self) + + if platformName == 'win': + self.Bind(wx.EVT_POWER_SUSPENDED, self.OnSuspend) + self.Bind(wx.EVT_POWER_RESUME, self.OnResume) + + self.Bind(wx.EVT_MENU, self.DigsbyCleanupAndQuit, id = wx.ID_EXIT) + + wx.IdleEvent.SetMode(wx.IDLE_PROCESS_SPECIFIED) + wx.UpdateUIEvent.SetMode(wx.UPDATE_UI_PROCESS_SPECIFIED) + + wx.SystemOptions.SetOptionInt("mac.textcontrol-use-mlte", 1) + wx.SystemOptions.SetOptionInt("mac.textcontrol-use-spell-checker", 1) + + self.setup_crash_reporting() + + if os.name != 'nt': + init_stdpaths() + + import stdpaths + sys.comtypes_dir = stdpaths.userlocaldata + + if platformName == 'win': + # ensure comtypes interface generation happens in a user writable location + log.info('set_comtypes_gendir()') + set_comtypes_gendir() + + if not getattr(sys, 'DEV', False): + try: + self._get_release_type() + except Exception, e: + log.error('Error getting release type %r', e) + + try: + self.init_branding() + except Exception, e: + log.error("Error getting brand type: %r", e) + + # Give the _MainThread class a getter for loopcount (which gets logged) + if hasattr(self, 'MainLoopCount'): + _MainThread = currentThread().__class__ # Thread subclass + _MainThread.loopcount = property(lambda thread: self.MainLoopCount) + + import hooks + hooks.register('proxy.info_changed', on_proxy_info_changed) + import gui.toolbox as toolbox + lsp = toolbox.local_settings_path() + + import metrics + metrics.register_hooks() + + self.local_settings_exist_at_startup = lsp.isfile() + + # these hooks can prevent the app from starting + start = True + for hook in hooks.Hook("digsby.app.gui.pre"): + if hook() is False: + start = False + break + + if start: + force_autologin = getattr(getattr(sys, 'opts', None), 'measure') == 'startup' or None + self.ShowSplash(force_autologin) + else: + wx.CallAfter(self.DigsbyCleanupAndQuit) + + return True + + def GetCmdLine(self): + exe = sys.executable.decode(sys.getfilesystemencoding()) + + if getattr(sys, 'frozen', False) in ('windows_exe', 'macosx_app'): + args = sys.argv[1:] # slice off the executable name, since in Py2exe'd runs it's there. + else: + args = sys.argv[:] + + args = map(lambda x: x.decode(sys.getfilesystemencoding()), args) + # We need to use the open command on Mac to ensure the new process is brought to the front + # and that the old process is shut down. + if sys.platform.startswith('darwin'): + exe = exe.replace('/Contents/MacOS/python', '') + return "open --new %s --args %s" % (exe, ' '.join(args)) + + return u' '.join([exe] + args) + + def setup_crash_reporting(self, username = None): + if not sys.opts.crashreporting: + print >> sys.stderr, 'Crash reporting is disabled.' + return + + crash_cmd = self.GetCmdLine() + + if username is not None: + crash_cmd += ' --crashuser %s' % username + + # the app will fill in %s with the name of the generated dump file + crash_cmd += ' --crashreport ' + if not sys.platform.startswith('darwin'): + crash_cmd += '%s' + + sys.crashuniquekey = int(time()) # if this format changes, see diagnostic.py + crash_cmd += ' --crashuniquekey %s' % sys.crashuniquekey + + # Process to be invoked upon crash + if sys.opts.submit_crashdump: + log.info("setting crash command to %s" % crash_cmd) + self.SetCrashCommand(crash_cmd) + + if sys.opts.full_crashdump: + # Send full crash dumps with process memory + flag = (wx.CRASH_REPORT_LOCATION | wx.CRASH_REPORT_STACK | + wx.CRASH_REPORT_LOCALS | wx.CRASH_REPORT_GLOBALS) + + self.crash_report_flags = flag + + # Location of crash file can be specified on the command line. + crashdump_dir = sys.opts.crashdump_dir + if crashdump_dir: + from os.path import isdir, abspath, join as pathjoin, normpath + + try: + crashdump_dir = abspath(crashdump_dir) + if not isdir(sys.opts.crashdump_dir): + os.makedirs(crashdump_dir) + except Exception: + print_exc() + else: + filename = 'Digsby_%s_%s.dmp' % (username or '', str(time())) + filename = normpath(pathjoin(crashdump_dir, filename)) + wx.CrashReport.SetFileName(filename) + print >> sys.stderr, 'Dumpfile: %r' % wx.CrashReport.GetFileName() + + # Indicate to WX that we'd like to catch C exceptions + wx.HandleFatalExceptions() + + + def OnSuspend(self, e): + from common import profile + p = profile() + if p is not None: + p.hibernate() + + def OnResume(self, e): + from common import profile + p = profile() + if p is not None: + p.unhibernate(20) + + def setup_fullscreen(self): + from gui.native.helpers import FullscreenApp + from gui.imwin import imhub + from util.primitives.funcs import Delegate + + self.on_fullscreen = Delegate() + self._fs_app_running = wx.PyTimer(lambda: self.on_fullscreen(FullscreenApp())) + self._fs_app_running.Start(1000, False) + + # when returning from fullscreen, flush the IM window show delegate + self.on_fullscreen += lambda val: wx.CallAfter(imhub.im_show.call_and_clear) if not val else None + + finish_init = False + + def ShowSplash(self, autologin_override = None, kicked=False): + log = getLogger('splash') + + init_threadpool() + + self.OnBuddyListShown = [] + + def splash_success(info): + self.AfterStartup() + import digsbyprofile + digsbyprofile.initialized += self.FinishInit + + return digsbyprofile.signin(hooks.first('digsby.identity.active')) + + if 'wxMSW' in wx.PlatformInfo: + preload_comctrls() + + self._show_splash(splash_success, autologin_override) + + if kicked: + wx.CallAfter(wx.MessageBox, message = _('Your digsby password has been changed. ' + 'Please log back in with the new password.'), + caption = _('Password Changed')) + self.finish_init = False + return True + + def _show_splash(self, splash_success, autologin_override): + from digsbysplash import LoginController + login_controller = LoginController(splash_success, autologin_override=autologin_override) + self._login_controller = login_controller + + opts = getattr(sys, 'opts', None) + username, password = getattr(opts, 'username', ''), getattr(opts, 'password', '') + + # passing --username and --password on the commandline prefills the splash screen. + if username: login_controller.set_username(username) + if password: login_controller.set_password(password) + if username and password: + login_controller.signin() + + self.SetTopWindow(login_controller.window) + wx.CallAfter(login_controller.ShowWindow) + + def SetupTaskbarIcon(self): + # The icon + assert(self.buddy_frame) + + from gui.trayicons import BuddyListTaskBarIcon + ticon = self.tbicon = BuddyListTaskBarIcon() + + def OpenUrl(self, url): + wx.LaunchDefaultBrowser(url) + + def FinishInit(self, on_gui_load=None): + log.info('FinishInit enter') + if self.finish_init: + return + self.finish_init = True + + wx.CallAfter(self._finish_init_gui, on_gui_load) + + def _finish_init_gui(self, on_gui_load=None): + import digsbyprofile + digsbyprofile.initialized -= self.FinishInit + profile = digsbyprofile.profile + login_controller = self._login_controller + del self._login_controller + + if 'WX_WEBKIT_LOG' not in os.environ: + os.environ['WX_WEBKIT_LOG'] = 'SQLDatabase' + + initialize_webkit() + + if login_controller.cancelling: + log.info('splash screen is cancelled...returning profile.disconnect()') + return profile.disconnect() + + log.info('wx.CallAfter(login_controller.DestroyWindow)') + wx.CallAfter(login_controller.DestroyWindow) + + autologin = not wx.GetKeyState(wx.WXK_SHIFT) + log.info('autologin for IM accounts: %r', autologin) + + from gui.imwin.imtabs import explodeAllWindows + # link on/off preference + # It would be nice if we could put this somewhere else, + # like a sort of place for things that should happen during + # "real" app load, i.e. post splash screen. + profile.prefs.link('messaging.tabs.enabled', + lambda val: (wx.CallAfter(explodeAllWindows) if not val else None), + callnow = False, + obj = wx.GetApp()) + + import gui.skin + + # preload fonts + log.info('gui.textutil.default_font()') + gui.textutil.default_font() + def preload_fonts(): + log.info('preloading fonts') + n = len(list(gui.textutil.GetFonts())) + log.info('preloaded %d fonts', n) + + log.info('wx.CallAfter(preload_fonts)') + wx.CallAfter(preload_fonts) + + # when skinning prefs change, update the skin + + import stdpaths + import util + log.info('setting resource paths') + gui.skin.set_resource_paths([ + util.program_dir() / 'res', # Apparently this has to be first? + stdpaths.userdata, + stdpaths.config, + ]) + + changeskin = gui.skin.set_active + + def on_skin_load(): + log.info('on_skin_load callback called, calling PostSkin') + self.PostSkin(autologin = autologin, success=on_gui_load) + + log.info('setting up crash reporting') + self.setup_crash_reporting(username = profile.username) + + from common import pref + def foo(): + log.info('calling gui.skin.set_active') + if sys.platform.startswith('darwin'): + changeskin('native', None, + callback = on_skin_load) + else: + changeskin(pref('appearance.skin'), pref('appearance.variant'), + callback = on_skin_load) + log.info('wx.CallAfter(foo)') + wx.CallAfter(foo) + + #@callsback + def PostSkin(self, autologin=False, success=None): + log.info('PostSkin enter, creating BuddyListFrame') + + initialize_jumplist() + + from gui.buddylist.buddylistframe import BuddyListFrame + self.buddy_frame = BuddyListFrame(None, title = _('Buddy List'), name = 'Buddy List', + style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_NO_TASKBAR) + + # register the main menu events + # TODO: Enable this for Win too once I confirm the automated tests pass + # and do some testing myself. + if newMenubar: + self.register_handlers() + + log.info('BuddyListFrame created') + + # gui for saving snapshots + if sys.opts.savesnapshots: + wx.GetApp().AddGlobalHotkey((wx.MOD_CMD | wx.MOD_ALT, ord('S')), save_webkit_snapshot_gui) + + def muchlater(): + log.info('muchlater enter') + self.idletimer = wx.PyTimer(self.not_idle) + self.idletimer.Start(IDLE_CHECK_MS) + + def show_buddylist_frame(): + from common import pref + if pref('buddylist.show_on_startup', type=bool, default=True): + self.buddy_frame.Show(True) + + from gui.toast.toasthelp import enable_help + enable_help() + + self.SetTopWindow(self.buddy_frame) + + from util.primitives.funcs import Delegate + log.info('calling and clearing OnBuddyListShown w/ %r', autologin) + Delegate(self.OnBuddyListShown)(autologin = autologin) + del self.OnBuddyListShown[:] + + # do RAM page out 1 minute after buddylist shows. + def memevent(): + from gui.native import memory_event + memory_event() + + self.memtimer = wx.CallLater(60 * 1000, memevent) + + if platformName == 'win': + from gui.native.win.process import page_out_ram + wx.CallLater(1000, page_out_ram) + + wx.CallAfter(show_buddylist_frame) + + wx.CallAfter(muchlater) + log.info('setting up task bar icon') + self.SetupTaskbarIcon() + + def MacOpenFile(self, filename): + log.info('File dragged to dock icon: ' + filename) + + def toggle_prefs(self): + import digsbyprofile + if (not hasattr(digsbyprofile, 'profile')) or (digsbyprofile.profile is None): + return wx.MessageBox(_('Please login first.'), _('Advanced Preferences')) + + from common import pref + + if not (__debug__ or sys.REVISION == 'dev' or pref('debug.advanced_prefs', False)): + return + + profile = digsbyprofile.profile + import prefs + + prefs.edit(profile.prefs, profile.defaultprefs, self.buddy_frame or None) + + def toggle_crust(self): + 'Pops up or hides the Python console.' + + from common import pref + + can_show_console = getattr(sys, 'DEV', False) or pref('debug.console', False) + if not can_show_console: + return + self.make_crust() + self.crust.toggle_shown() + + # keyboard focus goes to shell prompt + if self.crust.IsShown(): + self.crust.FocusInput() + + def make_crust(self): + if not hasattr(self, 'crust') or not self.crust: + import gui.shell + tlws = wx.GetTopLevelWindows() + self.crust = gui.shell.PyCrustFrame(None) + + if tlws: + parent = tlws[0] + parent.crust = self.crust + + def toggle_sorting(self): + from common import profile + prefs = profile.prefs + + if prefs: + if prefs.get('buddylist.allow_sorting_change', False): + prefs['buddylist.sorting'] = not prefs.get('buddylist.sorting', True) + + def not_idle(self, e=None): + from common import pref, profile + import digsbyprofile as d + + if not profile.prefs: return self.idletimer.Start() + + set_idle_after = pref('messaging.idle_after', 20 * d.IDLE_UNIT, type=int) + + from gui.native.helpers import GetUserIdleTime + t = GetUserIdleTime() + + min_idle = IDLE_CHECK_MS + from peak.util.plugins import Hook + for hook in Hook('digsby.app.idle'): + try: + next = int(hook(t)) + except Exception: + traceback.print_exc() + else: + if next < min_idle and next > 0: + min_idle = next + + if t < (set_idle_after*1000): + profile.reset_timers() + profile.return_from_idle() + + self.idletimer.Start(min_idle) + + def OnSaved(self): + log.info('profile.save SUCCESS, shutting down') + return self.do_shutdown() + + def OnSaveError(self): + log.warning("error saving blobs") + return self.do_shutdown() + + def DigsbyCleanupAndQuit(self, e=None): + from util.primitives.error_handling import traceguard + from util.primitives.funcs import Delegate + + # first close the IM windows: they might veto the app closing. + with traceguard: + for win in wx.GetTopLevelWindows(): + from gui.imwin.imtabs import ImFrame + if isinstance(win, ImFrame): + if not win.Close(): + return False + + # prevent reentracy + if getattr(self, 'shutting_down', False): + return False + + self.shutting_down = True + + from peak.util.plugins import Hook + for hook in Hook('digsby.app.exit'): + hook() + + # Do stuff from OnExit, and then self.ExitMainLoop() + # Keep OnExit empty-ish + log.info('DigsbyCleanupAndQuit') + + log.info('shutting down single instance server') + with traceguard: + # stop the single instance server so that if we have an error on shutdown, + # other instances will still run. + if USE_SINGLE_INSTANCE: + self.StopSingleInstanceServer() + + if USE_HANGING_THREAD_DAEMON and sys.opts.force_exit: + from util import HangingThreadDaemon + log.info('starting shutdown timer daemon') + HangingThreadDaemon(wait = EXIT_TIMEOUT_SECS, sysexit = True).start() + + log.info('calling PreShutdown') + Delegate(getattr(self, 'PreShutdown', []))() + + log.info(' saving and disconnecting profiles and accounts') + + # Hide the buddylist immediately. + with traceguard: + # FIXME: On Mac, we need to delete the TaskBarIcon before calling Close() + # on TLWs, because for some odd reason wxTaskBarIcon uses a hidden wxTLW + # instead of wxEvtHandler to catch and forward events. Unfortunately, we + # can't keep a wxTLW from appearing in wx.GetTopLevelWindows(), but at the + # same time, wxTaskBarIcon expects the wxTLW to still be alive when it + # is destroyed. This change can be reverted once I've found a suitable + # fix for the Mac problem. Phew. ;-) + log.info("destroying main tray icon...") + if getattr(self, 'tbicon', None) is not None: + self.tbicon.Destroy() + self.tbicon = None + + frame = getattr(self, 'buddy_frame', None) + if frame: + if frame.IsShown(): + frame.on_close(exiting = True) + + # TODO: decouple account tray icons from the buddylist frame + # until then, this code needs to run. + log.info('destroying %r', frame) + frame.on_destroy() + + # Close all popups + with traceguard: + from gui import toast + toast.cancel_all() + + # Close any remaining top level windows. This way EVT_CLOSE will always run + # even if the window is never closed while the app is running. This way + # we can always use on_close for window cleanup and persistence. + for tlw in wx.GetTopLevelWindows(): + # TODO: allow veto by dialog if there are unread messages? + tlw.Close(True) # force = True means EVT_CLOSE handlers cannot veto + + if sys.opts.quickexit: + log.info('exiting forcefully') + os._exit(1) + + from digsbyprofile import profile + if profile and profile.connection: + try: + profile.save(success = self.OnSaved, + error = self.OnSaveError) # Save account info. + except Exception: + print_exc() + self.OnSaveError() + else: + self.OnSaved() + + + return True + + def OnExit(self, event = None): + log.info('OnExit') + log.info(' replacing wx.CallAfter') + + wx.CallAfter = lambda f, *a, **k: f(*a, **k) + + def do_shutdown(self): + from util import traceguard + log.info('do_shutdown') + + dbg = log.debug + + # stuff to do after profile is disconnected + def _after_disconnect(): + log.info('joining with TimeoutThread') + with traceguard: + from util.threads import timeout_thread + timeout_thread.join() + log.info('joined with TimeoutThread') + + log.info('joining with AsyncoreThread') + with traceguard: + import AsyncoreThread + AsyncoreThread.end_thread() + AsyncoreThread.join() + + if __debug__ and getattr(sys, 'REVISION', None) == 'dev': + with traceguard: + if os.path.exists('logs/lastlogfile'): + with open('logs/lastlogfile', 'ab') as f: + f.write('yes\n') + + log.info('joining ThreadPool') + from util.threads.threadpool import ThreadPool + ThreadPool().joinAll() + log.info('done joining ThreadPool') + + # Goodbye, cruel world! + dbg('exit main loop...') + self.ExitMainLoop() + dbg('clean exit.') + + dbg('disconnecting profile') + with traceguard: + from common import profile + if profile and hasattr(profile, 'disconnect'): + profile.disconnect(success = _after_disconnect) + else: + _after_disconnect() + + if "wxMSW" in wx.PlatformInfo: + dbg('calling CoUnInitialize') + with traceguard: + import atexit + for f, a, k in atexit._exithandlers: + if f.__module__ == 'comtypes' and f.__name__ == 'shutdown': + atexit._exithandlers.remove((f, a, k)) + f(*a, **k) + break + + # If we're restarting, spawn the process again + if getattr(self, 'restarting', False): + cmdline = self.GetCmdLine() + if '--noautologin' not in cmdline: + cmdline += ' --noautologin' + + if '--updated' in cmdline: + cmdline = cmdline.replace('--updated', '') + + wx.Execute(cmdline) + + def init_hotkeys(self): + from gui.input.inputmanager import input_manager as input + from gui import skin + + # Add common shortcut contexts. + input.AddGlobalContext(_('Global Shortcuts'), 'Global') + + # Debug actions only enabled for dev mode + if getattr(sys, 'DEV', False): + input.AddGlobalContext(_('Debug Global Shortcuts'), 'Debug') + + # setup actions starting with "TextControls" + input.AddClassContext(_('Text Controls'), 'TextControls', wx.TextCtrl) + + # Load all keyboard shortcuts from res/keys.yaml and res/platformName/keys.yaml + resdir = skin.resourcedir() + KEYS_YAML = 'keys.yaml' + + yaml_sources = (resdir / KEYS_YAML, resdir / platformName / KEYS_YAML) + for yaml in yaml_sources: + if yaml.isfile(): + log.info('loading keyboard shortcuts from %r', yaml) + input.LoadKeys(yaml) + + # Link some callbacks to actions. + actions = [('TextControls.SelectAll', lambda textctrl: textctrl.SetSelection(-1, -1)), + ('TextControls.DeletePreviousWord', 'gui.toolbox.DeleteWord'), + ('Global.DigsbyShell.ToggleShow', lambda win: self.toggle_crust()), + ('Global.AdvancedPrefs.ToggleShow', lambda win: self.toggle_prefs()), + ('Global.Skin.Reload', lambda win: skin.reload()), + ('BuddyList.Sorting.Toggle', lambda win: self.toggle_sorting()), + ('Debug.XMLConsole', 'common.commandline.xmlconsole'), + ('Debug.JavaScriptConsole', 'gui.browser.jsconsole.show_console'), + ('Debug.ClearConsole', 'cgui.cls')] + + addcb = input.AddActionCallback + for name, action_cb in actions: + addcb(name, action_cb) + + input.BindWxEvents(self) + + def AddGlobalHotkey(self, keys, callback): + ''' + >> wx.GetApp().AddGlobalHotkey('ctrl+alt+d', MyAwesomeCallback) + ''' + + # TODO: hook this into the input manager system in the above method + + id = self.next_hotkey_id + self.next_hotkey_id += 1 + + modifiers, key = keys + + self.buddy_frame.RegisterHotKey(id, modifiers, key) + self.buddy_frame.Bind(wx.EVT_HOTKEY, callback, id = id) + + self.global_hotkeys[(modifiers, key, callback)] = id + + def RemoveGlobalHotkey(self, keys, callback): + from gui.toolbox import keycodes + modifiers, key = keycodes(keys) + + id = self.global_hotkeys.pop((modifiers, key, callback)) + self.buddy_frame.UnregisterHotKey(id) + + def init_branding(self): + import stdpaths, util, syck + brand_fname = 'brand.yaml' + for fpath in ((stdpaths.userlocaldata / brand_fname), (util.program_dir() / brand_fname)): + try: + with open(fpath, 'rb') as f: + yaml = syck.load(f) + brand = yaml['brand'] + except Exception, e: + log.debug("Didn't get a brand from %r: %r", fpath, e) + + log.info("Got brand: %r", brand) + sys.BRAND = brand + return brand + + # + # taskbar icon methods: + # + + def OnRaiseBuddyList(self, e = None): + frame = self.buddy_list + + docker = frame.docker + if docker.AutoHide and docker.docked: + if docker.autohidden: + docker.ComeBack() + + frame.Raise() + elif not frame.IsActive(): + frame.Raise() + + +# apply a filter to the console handler so that it doesn't duplicate +# stderr by displaying the stderr logger +class StdErrFilter(object): + def filter(self, record): + return record.name != 'stderr' + + +class MyStdErr(object): + has_stderr = True + def write(self, s): + try: + self.stderrlog.error(s.rstrip()) + except: + pass + + if MyStdErr.has_stderr: + try: + sys.__stderr__.write(s) + except: + MyStdErr.has_stderr = False + + + def flush(self): + if MyStdErr.has_stderr: + try: + sys.__stderr__.flush() + except: + MyStdErr.has_stderr = False + +def get_logdir(): + """Returns the location for Digsby's log file to be created. After creation + the directory can be more easily found with `sys.LOGFILE_NAME.parent` + """ + + # On Vista: C:\Users\%USERNAME%\AppData\Local\Digsby\Logs + # On XP: C:\Documents and Settings\%USERNAME%\Local Settings\Application Data\Digsby\Logs + # On Mac: /Users/$USER/Library/Application Support/Digsby/Logs + # (internationalizations may apply) + import sys, stdpaths + if getattr(sys, 'is_portable', False): + return stdpaths.temp / 'Digsby Logs' + else: + return stdpaths.userlocaldata / 'Logs' + +def get_last_log_file_name(): + if __debug__: + with open('logs/lastlogfile', 'r') as f: + lines = [line for line in f] + return lines[0][:-1] + else: + return os.path.join(get_logdir(), LOG_FILENAME) + +def get_std_redirect_file(force_release=False): + '''Return a logfile name for the system log.''' + + if __debug__ and not force_release: + return os.path.abspath('logs/systemlog.txt') + else: + return str((path(get_logdir()) / 'systemlog.txt').abspath()) + +def setup_log_system(force_release=False): + # don't popup dialogs for wxLogError calls-- + # go to stderr instead + wx.Log.SetActiveTarget(wx.LogStderr()) + + if sys.opts.no_log: + return + + from path import path + import cgui + + from fileutil import DisablingStream, DelayedStreamLimiter, LimitedFileSize + sys.stdout, sys.stderr = DisablingStream(sys.__stdout__), MyStdErr() + + stderrlog = logging.getLogger('stderr') + sys.stderr.stderrlog = stderrlog + + root = logging.Logger.root + root.stream = sys.stderr + root.setLevel(1) + + from csvlogging import gzipFileHandler, CloseFileHandler, CSVFormatter, CSV_HEADERS + + _log_debug = __debug__ and not force_release + sys.STDERR_LOGFILE = stderr_log_redirect = get_std_redirect_file(force_release) + + if _log_debug: + # In debug mode, log every time to a new file. + + # set up logging to file - see previous section for more details + t = str(int(time())) + + fn = path('logs/digsby-%s.log.csv' % t) + + sys.LOGFILE_NAME = fn + + if not os.path.exists('logs'): + os.mkdir('logs') + + + f = open(fn, 'wb') + f.write(CSV_HEADERS) + + if getattr(sys.opts, 'limit_log', True): + f = DelayedStreamLimiter(f, limit=LOG_SPEED_LIMIT, window=LOG_SPEED_WINDOW) + + hdlr = CloseFileHandler(f) + hdlr.setFormatter(CSVFormatter()) + root.addHandler(hdlr) + + # define a Handler which writes INFO messages or higher to the sys.stderr + global full_loggers + gzip_file = gzipFileHandler(t) + gzip_file.setFormatter(CSVFormatter()) + try: + delete = False + with open('logs/lastlogfile', 'r') as f: + lines = [line for line in f] + delete = len(lines) == 2 + if delete: + path(lines[0][:-1]).remove() + except Exception, e: + pass + + with open('logs/lastlogfile', 'wb') as f: + f.write(fn+'\n') + + global current_log + + current_log = fn + full_loggers = [gzip_file] + + # add the handler to the root logger + logging.getLogger('').addHandler(gzip_file) + sys.CSTDERR_FILE = None + else: + # In release mode, only store the previous log, overwriting it each time. + logdir = path(get_logdir()) + + if not logdir.isdir(): + logdir.makedirs() + + logfilebase = logfile = logdir / LOG_FILENAME + N = 2 + + while True: + try: + f = open(logfile, 'wb') + break + except: + print_exc() + logfile = logfilebase + ' (%d)' % N + N += 1 + if N > 100: raise AssertionError + f = open(logfile, 'wb') + + sys.LOGFILE_NAME = logfile + + cstderr = (logdir / 'cstderr.txt').abspath() + cgui.redirectStderr(cstderr) + sys.CSTDERR_FILE = cstderr + + f.write(CSV_HEADERS) + f.close() + + hdlr = CloseFileHandler(DelayedStreamLimiter + (LimitedFileSize(sys.LOGFILE_NAME, 20*1024*1024, 10*1024*1024, initmode='ab'), + limit=LOG_SPEED_LIMIT, window=LOG_SPEED_WINDOW)) + hdlr.setFormatter(CSVFormatter()) + + # allow buffered logging via --log-buffer on the console + capacity = getattr(sys.opts, 'log_buffer', 0) + if capacity: + from logging.handlers import MemoryHandler + hdlr = MemoryHandler(capacity, target=hdlr) + + full_loggers = [hdlr] + root.addHandler(hdlr) + + if hasattr(wx, 'SetLogFile'): + if not wx.SetLogFile(stderr_log_redirect): + print >> sys.stderr, "Could not redirect log file to %s" % stderr_log_redirect + else: + import datetime + wx.LogMessage(datetime.datetime.now().isoformat()) + + global logging_to_stdout + if not _log_debug: + logging.info('logger started - rev %s', sys.REVISION) + logging_to_stdout = False + else: + # don't log to the console in release mode + init_stdout() + logging_to_stdout = True + + print >> sys.stderr, "testing stderr" + print >> sys.stdout, "testing stdout" + +class ConsoleFormatter(logging.Formatter): + def format(self, record): + if record.exc_info and not record.exc_text: + record.exc_text = self.formatException(record.exc_info) + + exc = record.exc_text if record.exc_text else '' + + return \ + ''.join(['%-20s' % record.name, + record.threadName[:4], + '-', + '%-4s | ' % record.levelname[:4], + '%s' % record.getMessage(), + exc + ]) + +def redirect_c_stderr(stderr_log_redirect): + ''' + when C libraries fprintf(stderr, ...), we lose it. redirect it to systemlog.txt + ''' + STDERR = 2 + if platformName == 'win': + try: + import ctypes + STD_ERROR_HANDLE = -12 + STDERR = ctypes.windll.kernel32.GetStdHandle(STD_ERROR_HANDLE) + if STDERR == 0: + se = os.open(stderr_log_redirect, os.O_WRONLY|os.O_APPEND|os.O_CREATE) + if not ctypes.windll.kernel32.SetStdHandle(STD_ERROR_HANDLE, se): + raise ctypes.WinErr() + return + except Exception: + print_exc() + pass + + se = os.open(stderr_log_redirect, os.O_WRONLY|os.O_APPEND) + os.dup2(se, STDERR) + os.close(se) + + +def init_stdout(): + from fileutil import DelayedStreamLimiter + console = None + root = logging.Logger.root + + try: + sys.stdout.write('Testing if sys.stdout is safe...\n') + except: + root.info('stdout disabled') + else: + root.info("stdout enabled") + import logextensions + console = logextensions.ColorStreamHandler(DelayedStreamLimiter(sys.stdout,limit=LOG_SPEED_LIMIT, window=LOG_SPEED_WINDOW)) + console.setLevel(1) + + # set a format which is simpler for console use + console.setFormatter(ConsoleFormatter()) + + if console is not None: + logging.getLogger('').addHandler(console) + + if console is not None: + console.addFilter(StdErrFilter()) + +def setup_gettext(plugins): + + # allow !_ construction for translatable strings in YAML + import syck + syck.Loader.construct__ = lambda self, node: _(node.value) + syck.Loader.construct_N_ = lambda self, node: N_(node.value) + + lang = getattr(getattr(sys, 'opts', None), 'lang', None) + if lang and lang.strip(): + set_language(lang, plugins) + else: + set_language(None) + +def get_v(plugin): + try: + return plugin.info.get('bzr_revno') + except Exception: + traceback.print_exc() + return -1 + +def set_language(language, plugins=None): + import babel.support as support + DOMAIN = 'digsby' + if plugins is None: + plugins = [] + matches = [] + for plugin in plugins: + try: + if plugin.info.get('type') == 'lang' and \ + plugin.info.get('domain')== 'digsby' and \ + plugin.info.get('language') == language: + matches.append(plugin) + except Exception: + traceback.print_exc() + matches.sort(key = get_v, reverse = True) + for match in matches: + cat = match.get_catalog() + translation = support.Translations.load(cat, domain=DOMAIN) + else: + import gettext + translation = gettext.NullTranslations() + translation.install(unicode=True, names='') + return + + # TODO: do we need to set wxLocale? how do we do it if there is not + # a language code for it? + + thisdir = os.path.dirname(__file__) + catalogpath = os.path.normpath(os.path.abspath(os.path.join(thisdir, '../locale'))) + wx.Locale.AddCatalogLookupPathPrefix(catalogpath) + l = wx.Locale(wx.LANGUAGE_SPANISH_DOMINICAN_REPUBLIC) + l.AddCatalog(DOMAIN) + assert l.IsOk() + +startup_begin = 0 + +def check_dependencies(): + try: + import tlslite + except ImportError: + pass + else: + print >> sys.stderr, 'WARNING! tlslite is on PYTHONPATH, and will cause SSL problems. Please remove it: %r' % tlslite.__file__ + + +def set_update_tag(): + tag = sys.opts.updatetag + import syck + if tag is not None: + import stdpaths + try: + with open(stdpaths.userlocaldata / 'tag.yaml', 'w') as f: + syck.dump({'tag':tag}, f) + except Exception: + pass + +def init_stdpaths(): + # initialize util.stdpaths variables + import stdpaths + stdpaths.init() + +APP_ID = u'com.dotSyntax.Digsby' +# append DEV if in devmode +if getattr(sys, 'DEV', False): + APP_ID += '_DEV_%s' % time() + +def set_app_id(): + import ctypes + try: + SetAppID = ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID + except AttributeError: + return + + SetAppID(APP_ID) + +def main(): + global startup_begin + startup_begin = time() + + add_dev_plugins_path() + + plugins = None + if os.name == 'nt': # init stdpaths early on windows. + init_stdpaths() + + from bootstrap import install_N_ + install_N_() + plugins = init_plugins() + setup_gettext(plugins) + set_app_id() + + import sys + if getattr(sys, 'DEV', False): + digsbysite.SHOW_TRACEBACK_DIALOG = getattr(sys.opts, 'traceback_dialog', True) + + # Digsby spawns itself with --crashreport if it crashes. + if sys.opts.crashreport: + # Remove this hack when we figure out manifests for dev mode + if 'wxMSW' in wx.PlatformInfo: + preload_comctrls() + sys.util_allowed = True + return send_crash_report() + + if sys.opts.profile: + sys.util_allowed = True + from util import set_profilers_enabled + set_profilers_enabled(True) + + import hooks + if hooks.any('digsby.app.init.pre'): + return + digsby_app = DigsbyApp(plugins) + hooks.notify('digsby.app.init.post') + + + wx.ConfigBase.Set(wx.FileConfig()) + + setup_log_system(force_release=getattr(sys.opts, 'release_logging', False)) + log_system_info() + + set_update_tag() + + if sys.opts.profile: + from util import use_profiler, all_profilers + + if sys.opts.profile > 1: + # this option causes profiling information to be logged every thirty seconds + from util import RepeatTimer + + def get_reports(): + print '\n\n'.join(p.report() for p in all_profilers().itervalues()) + + digsby_app._profile_timer = RepeatTimer(30, get_reports) + digsby_app._profile_timer.start() + + return use_profiler(currentThread(), digsby_app.MainLoop) + else: + return digsby_app.MainLoop() + +def send_crash_report(): + '''Prompts the user to send a crash report, then sends it.''' + + a = wx.PySimpleApp() + from traceback import format_exc + + set_app_info(a) + from crashgui import CrashDialog + + if os.name != 'nt': + init_stdpaths() + + msg = _('There was an error submitting your crash report.') + + try: + diag = CrashDialog() + diag.CenterOnScreen() + diag.RequestUserAttention() + if wx.ID_OK == diag.ShowModal(): + import util.diagnostic as d + last_log = get_last_log_file_name() + diagobj = d.load_crash(sys.opts.crashreport, last_log, + username = sys.opts.crashuser, + description = diag.Description) + if diagobj.succeeded: + msg = _('Crash report submitted successfully.') + else: + return + except Exception: + print_exc() + + if False: # disabled until we can handle the single instance checker still running + msg += '\n\n' + _('Would you like to restart Digsby now?') + + if wx.YES == wx.MessageBox(msg, _('Crash Report'), style = wx.YES_NO | wx.YES_DEFAULT): + cmd = sys.executable.decode(sys.getfilesystemencoding()) + + if not getattr(sys, 'frozen', False) == 'windows_exe': + cmd += ' ' + sys.argv[0] # the script name + + wx.Execute(cmd) + +def log_system_info(): + 'Logs basic info about WX, Python, and the system at startup.' + + import locale, stdpaths + from time import clock + + global startup_begin + startup_time = (clock()) * 1000 + + opts_copy = dict(sys.opts.__dict__) + opts_copy.pop('password', None) + info = log.info + info('Digsby rev %s', sys.REVISION) + info('sys.opts: %r', opts_copy) + info("started up in %s ms", startup_time) + info(' '.join(wx.PlatformInfo) + ' wx v' + '.'.join(str(c) for c in wx.VERSION)) + info('Platform: %r', ' '.join(platform.uname()).strip()) + info('Python ' + sys.version) + info('locale: ' + str(locale.getdefaultlocale())) + info('user local data dir: %r', stdpaths.userlocaldata) + info('__debug__ is %r', __debug__) + +def set_app_info(app): + app.SetAppName(APP_NAME) + app.SetVendorName(u'dotSyntax') + app.SetAppName(APP_NAME) + app.SetClassName(APP_NAME) + +if 'wxMSW' in wx.PlatformInfo: + def preload_comctrls(): + # preload comctrls to prevent a TrackMouseEvent crash + # http://trac.wxwidgets.org/ticket/9922 + from ctypes import windll + windll.comctl32.InitCommonControls() + + def set_comtypes_gendir(): + 'Ensure sure any COM interface generation happens in a user writable location.' + info = log.info + + info('in set_comtypes_gendir') + import stdpaths + cache_root = stdpaths.userlocaldata / 'cache' + info('in set_comtypes_gendir 2') + + info('importing comtypes') + import comtypes + info('comtypes.initialize()') + comtypes.initialize() + + info('in set_comtypes_gendir 3') + import comtypes.client + info('in set_comtypes_gendir 4') + + gendir = cache_root / 'comtypes_generated' + info('in set_comtypes_gendir 5') + info('gendir %r', gendir) + + if not gendir.isdir(): + info('creating comtypes gendir at %r', gendir) + gendir.makedirs() + info('created comtypes gendir at %r', gendir) + + info('setting gen_dir') + comtypes.client.gen_dir = unicode(gendir).encode(sys.getfilesystemencoding()) + info('comtypes gen_dir is now %r', comtypes.client.gen_dir) + + # monkeypatch comtypes on dev to ensure no double deletes happen. (known bug?) + # http://thread.gmane.org/gmane.comp.python.comtypes.user/476 + from comtypes import _compointer_base + _cpbDel = _compointer_base.__del__ + def newCpbDel(self): + deleted = getattr(self, '_deleted', False) + assert not deleted, "compointer already deleted" + if not deleted: + _cpbDel(self) + self._deleted = True + newCpbDel.__name__ = "__del__" + _compointer_base.__del__ = newCpbDel + del _compointer_base + +def webkit_database_dir(): + from util.cacheable import get_cache_root + database_dir = get_cache_root(user=True) / 'html5storage' + return database_dir + +def copytree(src, dest): + if dest.isdir(): + dest.rmtree() + src.copytree(dest) + +webkit_initialized = False + +def is_webkit_initialized(): + return webkit_initialized + +def initialize_webkit(): + assert wx.IsMainThread() + + # This allows wxWebKit to valid ssl certs + import gui.skin + ssl_cert_bundle = os.path.join(gui.skin.resourcedir(), 'ca-bundle.crt') + assert os.path.exists(ssl_cert_bundle) + + if not isinstance(ssl_cert_bundle, bytes): + ssl_cert_bundle = ssl_cert_bundle.encode('filesys') + + try: + os.environ['CURL_CA_BUNDLE_PATH'] = ssl_cert_bundle + except Exception: + import traceback; traceback.print_exc() + + try: + try: + import webview + except ImportError: + import wx.webview as webview + except Exception: + print >> sys.stderr, "Warning: error preloading webkit" + else: + webview.WebView.InitializeThreading() + WebView = webview.WebView + + _original_runscript = WebView.RunScript + + # Make RunScript asynchronous if we're already in a RunScript call + # further down the stack. + # + # We haven't actually proven that WebKit doesn't tolerate reentrant + # RunScript calls, but many WebKit crashers do have that in common. + def RunScript(webview, *a, **k): + if getattr(webview, '_in_run_script', False): + wx.CallAfter(RunScript, webview, *a, **k) + return + + webview._in_run_script = True + try: + return _original_runscript(webview, *a, **k) + finally: + webview._in_run_script = False + + WebView.RunScript = RunScript + + # limit webkit cache size + if hasattr(webview.WebView, 'SetCachePolicy'): + WEBKIT_CACHE_CAPACITY = 4 * 1024 * 1024 + log.info('setting webkit cache capacity to %d', WEBKIT_CACHE_CAPACITY) + cachePolicy = WebView.GetCachePolicy() + cachePolicy.MaxDeadCapacity = cachePolicy.Capcity = WEBKIT_CACHE_CAPACITY + WebView.SetCachePolicy(cachePolicy) + + # snapshots + database_dir = webkit_database_dir() + if sys.opts.dbsnapshot: + log.info('loading webkit snapshot %r', sys.opts.dbsnapshot) + + snapshot = database_dir.parent / sys.opts.dbsnapshot + if not snapshot.isdir(): + print >> sys.stderr, 'Snapshot does not exist: %r' % snapshot + sys.exit(-1) + + database_dir = snapshot + '_running' + copytree(snapshot, database_dir) + + # set location of WebKit HTML5 databases + if hasattr(WebView, 'SetDatabaseDirectory'): + WebView.SetDatabaseDirectory(database_dir) + + global webkit_initialized + webkit_initialized = True + +def save_webkit_snapshot_gui(_arg): + from gui.toolbox import GetTextFromUser + name = GetTextFromUser('Enter a snapshot name:', 'Save database snapshot') + if name.strip(): + save_webkit_snapshot(name) + +def save_webkit_snapshot(name): + p = path(wx.webview.WebView.GetDatabaseDirectory()) + dest = p.parent / name + log.info('saving webkit snapshot %r to %r', name, dest) + copytree(p, dest) + +def dev_plugins_path(): + if not getattr(sys, 'DEV', False): + return None + + try: + p = path(__file__.decode(sys.getfilesystemencoding())).parent.parent / u'devplugins' # TODO: don't use __file__ + except Exception: + traceback.print_exc() + return None + else: + return p + +def add_dev_plugins_path(): + devplugins = dev_plugins_path() + if devplugins is not None: + devplugins = str(devplugins) + if devplugins not in sys.path: + sys.path.append(devplugins) + +def init_plugins(): + if not sys.opts.load_plugins: + return [] + + import plugin_manager, common + + paths = [] + try: + PLUGINS_PATH = path(__file__.decode(sys.getfilesystemencoding())).parent / u'plugins' # TODO: don't use __file__ + except Exception: + traceback.print_exc() + PLUGINS_PATH = None + else: + paths.append(PLUGINS_PATH) + + DEV_PLUGINS_PATH = dev_plugins_path() + if DEV_PLUGINS_PATH is not None: + paths.append(DEV_PLUGINS_PATH) + + for pth in paths: + pth = str(pth) + if pth not in sys.path: + sys.path.append(pth) + + import features + paths.extend(features.find_feature_dirs()) + + sys.path.extend(map(str, paths)) # 'paths' contains path.path objects, str(p) calls p.encode('filesys') + + plugins = [] + for pth in paths: + try: + if pth is not None: + plugins.extend(plugin_manager.scan(pth)) + except Exception: + traceback.print_exc() + + try: + import common.protocolmeta + common.protocolmeta.proto_update_types() + except Exception: + traceback.print_exc() + + return plugins + +did_create_webview = False + +def set_did_create_webview(): + global did_create_webview + if not did_create_webview: + did_create_webview = True + import hooks, util + hooks.notify('proxy.info_changed', util.GetProxyInfo()) + +def on_proxy_info_changed(proxy_info): + import wx.webview as webview, socks + + # Tell socks module about proxy info + socks.setdefaultproxy(**proxy_info) + + if not did_create_webview: + # HACK: for a yet-to-be-determined reason, calling SetProxyInfo + # before constructing a WebView breaks curl. did_create_webview + # is set to True by the infobox construction code. + return + + if not proxy_info: + # empty dict means no proxy + webview.WebView.SetProxyInfo() + return + + # Tell WebKit (which does its own HTTP requests) about the proxy settings. + proxy_types = {socks.PROXY_TYPE_SOCKS4: webview.Socks4, + socks.PROXY_TYPE_SOCKS5: webview.Socks5, + socks.PROXY_TYPE_HTTP: webview.HTTP} + + proxy_type = proxy_types.get(proxy_info['proxytype'], webview.HTTP) + + webview.WebView.SetProxyInfo( + proxy_info['addr'] or '', + proxy_info['port'] or 0, + proxy_type, + proxy_info['username'] or '', + proxy_info['password'] or '') + +def SetStatusPrompt(accounts = None, initial_text = u'', **k): + import gui.social_status_dialog as ssd + from common import profile + gsc = ssd.GlobalStatusController.GetInstance(profile(), accounts, initial_text, **k) + gsc.ShowDialog() + gsc.SetFocus() + +def ExitDigsby(): + wx.GetApp().DigsbyCleanupAndQuit() + +def initialize_metrics(): + import metrics + metrics.register_hooks() + +def initialize_jumplist(): + import cgui + if not cgui.isWin7OrHigher(): + return + + try: + from gui.native.win.jumplist import set_app_jumplist + set_app_jumplist() + except Exception: + print_exc() + +if __name__ == '__main__': + raise AssertionError('run Digsby.py') + diff --git a/digsby/src/metrics.py b/digsby/src/metrics.py new file mode 100644 index 0000000..f488665 --- /dev/null +++ b/digsby/src/metrics.py @@ -0,0 +1,66 @@ +''' +A simple performance tracker. +''' + +import time +import hooks + +DISABLE_STATS = False + +def get_tzoffset(): + daylight = time.gmtime(time.time()).tm_isdst + return time.altzone if time.daylight and daylight else time.timezone + +def dump(): + import simplejson + return simplejson.dumps(dict( + tzOffset = get_tzoffset(), + events = stats.events(), + eventNames = stats.eventNames(), + samples = stats.samples(), + sampleNames = stats.sampleNames(), + )) + + +class MockStatistics(object): + def event(self, eventName): + pass + + def start(self): + pass + + def stop(self): + pass + + def events(self): return [] + def samples(self): return [] + def eventNames(self): return {} + def sampleNames(self): return {} + +def on_account_state_changed(acct, state): + if acct.protocol is not None and state: + event('.'.join(['account', acct.protocol, state])) + +metrics_hooks = [('account.state', on_account_state_changed), + ('imwin.created', None)] + +def register_hooks(): + for hook, cb in metrics_hooks: + if cb is None: cb = lambda *a, **k: event(hook) + hooks.register(hook, cb) + +try: + stats +except NameError: + if DISABLE_STATS: + stats = MockStatistics() + else: + try: + import cgui + stats = cgui.Statistics(15 * 1000) + except: + stats = MockStatistics() + + stats.start() + event = stats.event + diff --git a/digsby/src/msn/AddressBook.py b/digsby/src/msn/AddressBook.py new file mode 100644 index 0000000..1fcb1d4 --- /dev/null +++ b/digsby/src/msn/AddressBook.py @@ -0,0 +1,2555 @@ +import traceback + +import uuid +import simplejson as json +import logging + +import util +import util.net as net +import util.network.soap as soap +import util.primitives.structures as structures + +log = logging.getLogger('msn.ab') + +import msn.SOAP.services as SOAPServices +import msn.SOAP.pysoap_util as pysoap + +class MemberRole: + Allow = "Allow" + Block = "Block" + Reverse = "Reverse" + Pending = "Pending" + Admin = "Admin" + Contributor = "Contributor" + ProfileGeneral = "ProfileGeneral" + ProfilePersonalContact = "ProfilePersonalContact" + ProfileProfessionalContact = "ProfileProfessionalContact" + ProfileSocial = "ProfileSocial" + ProfileExpression = "ProfileExpression" + ProfileEducation = "ProfileEducation" + OneWayRelationship = "OneWayRelationship" + TwoWayRelationship = "TwoWayRelationship" + ApplicationRead = "ApplicationRead" + ApplicationWrite = "ApplicationWrite" + +class MessengerContactType: + Me = "Me" + Regular = "Regular" + Messenger = "Messenger" + Live = "Live" + LivePending = "LivePending" + LiveRejected = "LiveRejected" + LiveDropped = "LiveDropped" + Circle = "Circle" + +class PropertyString: + PropertySeparator = " " + Email = "Email" + IsMessengerEnabled = "IsMessengerEnabled" + Capability = "Capability" + Number = "Number" + Comment = "Comment" + DisplayName = "DisplayName" + Annotation = "Annotation" + IsMessengerUser = "IsMessengerUser" + MessengerMemberInfo = "MessengerMemberInfo" + ContactType = "ContactType" + ContactEmail = "ContactEmail" + ContactPhone = "ContactPhone" + GroupName = "GroupName" + HasSpace = "HasSpace" + +class AddressBookType: + Group = "Group" + Individual = "Individual" + +class AnnotationNames: + MSN_IM_InviteMessage = "MSN.IM.InviteMessage" + MSN_IM_MPOP = "MSN.IM.MPOP" + MSN_IM_BLP = "MSN.IM.BLP" + MSN_IM_GTC = "MSN.IM.GTC" + MSN_IM_RoamLiveProperties = "MSN.IM.RoamLiveProperties" + MSN_IM_MBEA = "MSN.IM.MBEA" + MSN_IM_Display = "MSN.IM.Display" + MSN_IM_BuddyType = "MSN.IM.BuddyType" + AB_NickName = "AB.NickName" + AB_Profession = "AB.Profession" + Live_Locale = "Live.Locale" + Live_Profile_Expression_LastChanged = "Live.Profile.Expression.LastChanged" + Live_Passport_Birthdate = "Live.Passport.Birthdate" + +class Role(structures.EnumValue, pysoap.Serializable): + def __init__(self, str, int, role): + structures.EnumValue.__init__(self, str, int, role = role) + pysoap.Serializable.__init__(self) + + def serialize(self): + return str(int(self)) + +class MSNList(structures._Enum): + ValueType = Role + + Forward = (1, 'FL') + Allow = (2, 'AL') + Block = (4, 'BL') + Reverse = (8, 'RL') + Pending = (16, 'PL') + Hidden = (64, 'HL') + +MSNList = MSNList() + +class MembershipType: + Passport = 'Passport' + Email = 'Email' + Phone = 'Phone' + Role = 'Role' + Service = 'Service' + Everyone = 'Everyone' + Partner = 'Partner' + Domain = 'Domain' + Circle = 'Circle' + +class DomainIds: + WLDomain = 1 + ZUNEDomain = 3 + FBDomain = 7 + LinkedIn = 8 + Myspace = 9 + +class RelationshipTypes: + IndividualAddressBook = 3 + CircleGroup = 5 + +class RelationshipStates: + NONE = 'None' + WaitingResponse = 'WaitingResponse' + Left = 'Left' + Accepted = 'Accepted' + Rejected = 'Rejected' + + @classmethod + def from_int(cls, i): + return { + 0 : cls.NONE, + 1 : cls.WaitingResponse, + 2 : cls.Left, + 3 : cls.Accepted, + 4 : cls.Rejected, + }.get(i) + +class Scenario: + NONE = 0 + Restore = 1 + Initial = 2 + DeltaRequest = 4 + NewCircles = 8 + ModifiedCircles = 16 + SendInitialContactsADL = 32 + SendInitialCirclesADL = 64 + ContactServeAPI = 128 + InternalCall = 256 + +class CirclePersonalMembershipRole: + NONE = 'None' + Admin = 'Admin' + AssistantAdmin = 'AssistantAdmin' + Member = 'Member' + StatePendingOutbound = "StatePendingOutbound" + + @classmethod + def from_int(cls, i): + return { + 0: 'None', + 1: 'Admin', + 2: 'AssistantAdmin', + 3: 'Member', + 4: 'StatePendingOutbound', + }.get(i) + + @classmethod + def to_int(cls, s): + return { + 'None': 0, + 'Admin': 1, + 'AssistantAdmin': 2, + 'Member': 3, + 'StatePendingOutbound': 4, + }.get(s) + +class IMAddressInfoType: + NONE = 0 + WindowsLive = 1 + OfficeCommunicator = 2 + Telephone = 4 + MobileNetwork = 8 + Circle = 9 + TemporaryGroup = 10 + Cid = 11 + Connect = 13 + RemoteNetwork = 14 + Smtp = 16 + Yahoo = 32 + +class _ClientType(structures.EnumValue, pysoap.Serializable): + def __init__(self, name, int, *equivs): + structures.EnumValue.__init__(self, name, int, **dict(('equiv%d' % i, e) for i,e in enumerate(equivs))) + pysoap.Serializable.__init__(self) + + def serialize(self): + return str(int(self)) + +class ClientType(structures._Enum): + ValueType = _ClientType + NONE = 0 + Passport = (1, MessengerContactType.Me, + MessengerContactType.Messenger, + MessengerContactType.Live, + MessengerContactType.LivePending, + MessengerContactType.LiveRejected, + MessengerContactType.LiveDropped) + LCS = 2 + Phone = 4 + Circle = 9 + Chat = 10 + Email = (32, MessengerContactType.Regular) + +ClientType = ClientType() +ClientType.PassportMember = ClientType.Passport +ClientType.EmailMember = ClientType.Email +ClientType.PhoneMember = ClientType.Phone +ClientType.CircleMember = ClientType.Circle +ClientType.ChatMember = ClientType.Chat + +class ServiceFilters: + Messenger = "Messenger" + Invitation = "Invitation" + SocialNetwork = "SocialNetwork" + Profile = "Profile" + Folder = "Folder" + Event = "Event" + OfficeLiveWebNotification = "OfficeLiveWebNotification" + CommunityQuestionAnswer = "CommunityQuestionAnswer" + +class Service(pysoap.Serializable): + +# def __init__(self, id, type, lastChange, foreign_id): +# pysoap.Serializable.__init__(self, id = id, type = type, lastChange = lastChange, foreign_id = foreign_id) + + #lastChange = pysoap.DateAttr('lastChange') + pass + +class Annotation(pysoap.Serializable): + pass + +class MemberLocation(pysoap.Serializable): + '''Attributes: + Id + isPassportNameHidden + CID + ''' + +class GroupInfo(pysoap.Serializable): + groupType = "C8529CE2-6EAD-434d-881F-341E17DB3FF8" + name = None + + annotations = [Annotation] + '''Attributes: + annotations + groupType + name + IsNotMobileVisible + IsNotMobileVisibleSpecified + IsPrivate + IsPrivateSpecified + IsFavorite + IsFavoriteSpecified + fMessenger + fMessengerSpecified + ''' + +class Group(pysoap.Serializable): + groupInfo = GroupInfo + +class Member(pysoap.Serializable): + nonserialize_attributes = ['States'] + class States: + Accepted = 'Accepted' + Pending = 'Pending' + Removed = 'Removed' + + location = MemberLocation + Annotations = [Annotation] + + #joinedDate = pysoap.DateAttr("joinedDate") + #expirationDate = pysoap.DateAttr("expirationDate") + #lastChange = pysoap.DateAttr("lastChange") + + '''Attributes: + membershipId + type + location + displayName + state + annotations + deleted + lastChange + joinedDate + expirationDate + changes + ''' + + @classmethod + def deserialize(cls, s): + self = pysoap.Serializable.deserialize(s) + if hasattr(self, 'PassportName'): + newtype = PassportMember + elif hasattr(self, 'Email'): + newtype = EmailMember + elif hasattr(self, 'PhoneNumber'): + newtype = PhoneMember + elif hasattr(self, 'CircleId'): + newtype = CircleMember + elif hasattr(self, 'DomainName'): + newtype = DomainMember + else: + return self + + return newtype(**vars(self)) + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + cls = { + MembershipType.Passport : PassportMember, + MembershipType.Email : EmailMember, + MembershipType.Phone : PhoneMember, + MembershipType.Domain : DomainMember, + MembershipType.Circle : CircleMember, + }.get(obj.Type, cls) + + attrs = pysoap.extract_zsi_properties(obj) + attrs.update(kwds) + + attrs['Annotations'] = map(Annotation.from_zsi, getattr(attrs.get('Annotations'), 'Annotation', None) or []) + attrs['location'] = MemberLocation.from_zsi(attrs.get('location')) + + return cls(**attrs) + + def GetAnnotation(self, key, default = None): + for anno in self.Annotations: + if type(anno) is Annotation: + name, value = anno.Name, anno.Value + else: + assert type(anno) is dict + name, value = anno.get('Name'), anno.get('Value') + + if name == key: + return value + + return default + +class PassportMember(Member): + '''Attributes: + PassportName + IsPassportNameHidden + IsPassportNameHiddenSpecified + PassportId + PassportIdSpecified + CID + CIDSpecified + PassportChanges + ''' + +class EmailMember(Member): + '''Attributes: + Email + ''' + +class PhoneMember(Member): + '''Attributes: + PhoneNumber + ''' + +class CircleMember(Member): + '''Attributes: + CircleId + ''' + +class DomainMember(Member): + '''Attributes: + DomainName + ''' + +class ServiceMembership(pysoap.Serializable): + Service = Service + Memberships = {'' : {'' : Member}} + + def __init__(self, Service = None, Memberships = None): + pysoap.Serializable.__init__(self, Service = Service, Memberships = Memberships) + if self.Memberships is type(self).Memberships: + self.Memberships = {} + +class ContactEmail(pysoap.Serializable): + nonserialize_attributes = ['Types'] + class Types: + Personal = 'ContactEmailPersonal' + Business = 'ContactEmailBusiness' + Other = 'ContactEmailOther' + Messenger = 'ContactEmailMessenger' + Messenger2 = 'Messenger2' + Messenger3 = 'Messenger3' + Messenger4 = 'Messenger4' + Passport = 'Passport' + +# type = None, email = None, isMessengerEnabled = None, +# capability = None, messengerEnabledExternally = None, propertiesChanged = None): + + +class ContactPhone(pysoap.Serializable): + nonserialize_attributes = ['Types'] + class Types: + Personal = 'ContactPhonePersonal' + Business = 'ContactPhoneBusiness' + Mobile = 'ContactPhoneMobile' + Page = 'ContactPhonePager' + Other = 'ContactPhoneOther' + Fax = 'ContactPhoneFax' + Personal2 = 'Personal2' + Business2 = 'Business2' + BusinessFax = 'BusinessFax' + BusinessMobile = 'BusinessMobile' + +# type = None, number = None, isMessengerEnabled = None, propertiesChanged = None): + +class ContactLocation(pysoap.Serializable): + nonserialize_attributes = ['Types'] + class Types: + Personal = 'ContactLocationPersonal' + Business = 'ContactLocationBusiness' + +# type = None, name = None, street = None, city = None, state = None, +# country = None, postalCode = None, department = None, changes = None): + +class ContactWebsite(pysoap.Serializable): + nonserialize_attributes = ['Types'] + class Types: + Personal = 'ContactWebSitePersonal' + Business = 'ContactWebSiteBusiness' + +# def __init__(self, type = None, webURL = None): + +class NetworkInfo(pysoap.Serializable): + ''' + Attributes: + domainId + domainTag + displayName + userTileURL + profileURL + relationshipType + relationshipState + relationshipStateDate + relationshipRole + nDRCount + inviterName + inviterMessage + inviterCID + inviterEmail + createDate + lastChange + propertiesChanged + sourceId + + domainIdSpecified + relationshipTypeSpecified + relationshipStateSpecified + relationshipRoleSpecified + nDRCountSpecified + inviterCIDSpecified + ''' + + @property + def DomainIdSpecified(self): + return hasattr(self, 'DomainId') + @property + def RelationshipTypeSpecified(self): + return hasattr(self, 'RelationshipType') + @property + def RelationshipStateSpecified(self): + return hasattr(self, 'RelationshipState') + @property + def RelationshipRoleSpecified(self): + return hasattr(self, 'RelationshipRole') + @property + def NDRCountSpecified(self): + return hasattr(self, 'NDRCount') + @property + def InviterCIDSpecified(self): + return hasattr(self, 'InviterCID') + +class MessengerMemberInfo(pysoap.Serializable): + pendingAnnotations = [Annotation] + +# def __init__(self, pendingAnnotations = None, displayName = None): + +class ContactInfo(pysoap.Serializable): + emails = [ContactEmail] + phones = [ContactPhone] + locations = [ContactLocation] + webSites = [ContactWebsite] + annotations = [Annotation] + NetworkInfoList = [NetworkInfo] + messengerMemberInfo = [MessengerMemberInfo] + + isMessengerUser = False + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + attrs = pysoap.extract_zsi_properties(obj) + + attrs['webSites'] = [ContactWebsite.from_zsi(x) for x in getattr(attrs.get('webSites'), 'ContactWebsite', [])] + attrs['emails'] = [ContactEmail.from_zsi(x) for x in getattr(attrs.get('emails'), 'ContactEmail', [])] + attrs['locations'] = [ContactLocation.from_zsi(x) for x in getattr(attrs.get('locations'), 'ContactLocation', [])] + attrs['annotations'] = [Annotation.from_zsi(x) for x in getattr(attrs.get('annotations'), 'Annotation', [])] + attrs['phones'] = [ContactPhone.from_zsi(x) for x in getattr(attrs.get('phones'), 'ContactPhone', [])] + attrs['NetworkInfoList'] = [NetworkInfo.from_zsi(x) for x in getattr(attrs.get('NetworkInfoList'), 'NetworkInfo', None) or []] + attrs['groupIds'] = [str(x) for x in getattr(attrs.get('groupIds', None), 'Guid', [])] + attrs['groupIdsDeleted'] = [str(x) for x in getattr(attrs.get('groupIdsDeleted', None), 'Guid', [])] + + attrs.update(kwds) + + return cls(**attrs) + + + ''' + Attributes: + emails + phones + locations + websites + annotations + network + messenger_info + groupIds + groupIdsDeleted + contactType + quickName + firstName + MiddleName + lastName + Suffix + NameTitle + passportName + IsPassportNameHidden + IsPassportNameHiddenSpecified + displayName + puid + puidSpecified + CID + CIDSpecified + BrandIdList + comment + IsNotMobileVisible + IsNotMobileVisibleSpecified + isMobileIMEnabled + isMobileIMEnabledSpecified + isMessengerUser + isMessengerUserSpecified + isFavorite + isFavoriteSpecified + isSmtp + isSmtpSpecified + hasSpace + hasSpaceSpecified + spotWatchState + birthdate + primaryEmailType + primaryEmailTypeSpecified + PrimaryLocation + PrimaryLocationSpecified + PrimaryPhone + PrimaryPhoneSpecified + IsPrivate + IsPrivateSpecified + Anniversary + Gender + TimeZone + PublicDisplayName + IsAutoUpdateDisabled + IsAutoUpdateDisabledSpecified + PropertiesChanged + clientErrorData + IsHidden + IsHiddenSpecified + ''' + +import util.Events as Events +class Contact(pysoap.Serializable, Events.EventMixin): + DirectBridge = None + + serialize_attributes = ['type'] + nonserialize_attributes = ['client', '_type', 'EndPointData', + 'ADLCount', 'events', 'handlers', + 'DirectBridge', 'P2PVersionSupported', + 'dcType', 'dcPlainKey', 'dcLocalHashedNonce', + 'dcRemoteHashedNonce', + ] + + contactInfo = ContactInfo + ADLCount = 1 + + dcPlainKey = uuid.UUID(int = 0) + dcLocalHashedNonce = uuid.UUID(int = 0) + dcRemoteHashedNonce = uuid.UUID(int = 0) + dcType = 0 + + events = Events.EventMixin.events | set(( + 'DirectBridgeEstablished', + )) + + def __repr__(self): + return "%s(%r, type = %r)" % (type(self).__name__, self.account, str(self.type)) + + def _get_P2PVersionSupported(self): + import msn.P2P as P2P + if not self.EndPointData: + return P2P.Version.V1 + + if int(self.SelectRandomEPID()) == 0: + return P2P.Version.V1 + else: + return P2P.Version.V2 + + def _set_P2PVersionSupported(self, val): + pass + + P2PVersionSupported = property(_get_P2PVersionSupported, _set_P2PVersionSupported) + + def SelectRandomEPID(self): + for epid in self.EndPointData: + if epid != uuid.UUID(int = 0): + return epid + + return uuid.UUID(int = 0) + + def SelectBestEndPointId(self): + for epid in self.EndPointData: + if int(epid) == 0: + continue + + epdata = self.EndPointData[epid]['PE'] + try: + client_caps = int(str(epdata.Capabilities).split(':')[0]) + except Exception: + continue + + if client_caps == 0: + continue + + return epid + + return uuid.UUID(int = 0) + + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + attrs = pysoap.extract_zsi_properties(obj) + attrs['contactInfo'] = ContactInfo.from_zsi(attrs['contactInfo']) + attrs.update(kwds) + + return cls(**attrs) + + def __init__(self, abid = None, account = None, type = None, client = None, **kw): + if type is None: + raise Exception + self.EndPointData = {} + self.fDeleted = False + Lists = set() + type = ClientType(type) + l = locals() + self = l.pop('self') + l.update(l.pop('kw')) + pysoap.Serializable.__init__(self, **l) + + if self.contactInfo is Contact.contactInfo: + self.contactInfo = ContactInfo() + elif isinstance(self.contactInfo, dict): + self.contactInfo = ContactInfo.deserialize(self.contactInfo) + + self.Lists = set(self.Lists) + + Events.EventMixin.__init__(self) + self.GenerateNewDCKeys() + + def GenerateNewDCKeys(self): + import msn.P2P.P2PSession as P2PS + import msn.MSNUtil as MSNU + self.dcType = P2PS.DCNonceType.Sha1 + self.dcPlainKey = uuid.uuid4() + self.dcLocalHashedNonce = MSNU.HashNonce(self.dcPlainKey) + self.dcRemoteHashedNonce = uuid.UUID(int=0) + log.info("Generated new nonce keys for %r: plainKey = %r, hashedKey = %r", self.account, self.dcPlainKey, self.dcLocalHashedNonce) + +# create_date = pysoap.DateAttr('create_date') +# lastChange = pysoap.DateAttr('lastChange') + + @classmethod + def MakeHash(cls, account, type, abid = uuid.UUID(int=0)): + return "%s:%s;via=%s" % (int(ClientType(type)), account.lower(), str(abid).lower()) + + @property + def Hash(self): + return type(self).MakeHash(self.account, self.type, self.abid) + + def SetName(self, name): + self.DisplayName = name + + def HasLists(self, lists): + # self.Lists contains RL, AL, etc. (not ints or long-names) + if not util.isiterable(lists): + lists = [lists] + + return all(str(MSNList(x)) in self.Lists for x in lists) + + def HasList(self, list): + return self.HasLists([list]) + + def RemoveFromList(self, list): + list = str(MSNList(list)) + self.Lists.discard(list) + self.client.contact_remove(self.account, list, None) + + def AddToList(self, list): + self.Lists.add(str(MSNList(list))) + + @classmethod + def GetConflictLists(self, current, newlists): + if isinstance(newlists, basestring): + newlists = [newlists] + + conflict = set() + if MSNList.Allow in current and MSNList.Block in newlists: + conflict.add(str(MSNList.Allow)) + + if MSNList.Block in current and MSNList.Allow in newlists: + conflict.add(str(MSNList.Block)) + + return conflict + + @property + def Mail(self): + return self.account.lower() + + def _get_type(self): + return self._type + def _set_type(self, val): + self._type = ClientType(val) + + type = property(_get_type, _set_type) + + def RemoveFromGroup(self, g_id): + if g_id in self.contactInfo.groupIds: + self.contactInfo.groupIds.remove(g_id) + self.client.contact_remove(self.account, str(MSNList.Forward), g_id) + + def _get_OnAllowedList(self): + return self.HasList(MSNList.Allow) + + def _set_OnAllowedList(self, value): + if value == self.OnAllowedList: + return + + if value: + self.Blocked = False + elif not self.OnReverseList: + self.client.RemoveContactFromList(self, MSNList.Allow) + + OnAllowedList = property(_get_OnAllowedList, _set_OnAllowedList) + + def _get_OnBlockedList(self): + return self.HasList(MSNList.Block) + def _set_OnBlockedList(self, value): + if value == self.OnBlockedList: + return + + if value: + self.Blocked = True + elif not self.OnReverseList: + self.client.RemoveContactFromList(self, MSNList.Block) + + OnBlockedList = property(_get_OnBlockedList, _set_OnBlockedList) + + def _get_OnForwardList(self): + return self.HasList(MSNList.Forward) + + def _set_OnForwardList(self, value): + if self.OnForwardList == value: + return + + if value: + self.client.AddContactToList(self, MSNList.Forward) + else: + self.client.RemoveContactFromList(self, MSNList.Forward) + + OnForwardList = property(_get_OnForwardList, _set_OnForwardList) + + def _get_OnReverseList(self): + return self.HasList(MSNList.Reverse) + + OnReverseList = property(_get_OnReverseList) + + def _get_OnPendingList(self): + return self.HasList(MSNList.Pending) + def _set_OnPendingList(self, value): + if value != self.OnPendingList and value == False: + self.client.RemoveContactFromList(self, MSNList.PL) + + OnPendingList = property(_get_OnPendingList, _set_OnPendingList) + + def _get_IsMessengerUser(self): + return self.contactInfo.isMessengerUser + def _set_IsMessengerUser(self, value): + guid_empty = str(uuid.UUID(int=0)) + guid = getattr(self, 'Guid', getattr(self, 'contactId', guid_empty)) + if guid != guid_empty and value != self.IsMessengerUser: + self.contactInfo.isMessengerUser = value + if getattr(self, 'client', None) is not None: + self.client.UpdateContact(self, getattr(self, 'abid', guid_empty)) + + IsMessengerUser = property(_get_IsMessengerUser, _set_IsMessengerUser) + + def getCID(self): + return getattr(self, 'CID', getattr(getattr(self, 'contactInfo', None), 'CID', 0)) + +class CircleInviter(Contact): + def __init__(self, account, invite_message, client, **kw): + Contact.__init__(self, abid = str(uuid.UUID(int=0)), account = account, type = ClientType.PassportMember, client = client, **kw) + self.invite_message = invite_message + +class ContentHandle(pysoap.Serializable): + '''Attributes: + Id + ''' + +class ContentInfo(pysoap.Serializable): + '''Attributes: + domain + hostedDomain + type + membershipAccess + isPresenceEnabled + requestMembershipOption + displayName + profileLastUpdated + changes + createDate + lastChange + ''' + +# createDate = pysoap.DateAttr('createDate') +# lastChange = pysoap.DateAttr('lastChange') + +class Content(pysoap.Serializable): + Handle = ContentHandle + Info = ContentInfo + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + attrs = pysoap.extract_zsi_properties(obj) + attrs.update(kwds) + + attrs['Info'] = cls.Info.from_zsi(attrs['Info']) + attrs['Handle'] = cls.Handle.from_zsi(attrs['Handle']) + + return cls(**attrs) + +class CirclePersonalMembership(pysoap.Serializable): + '''Attributes: + Role + State + ''' + +class MembershipInfo(pysoap.Serializable): + CirclePersonalMembership = CirclePersonalMembership + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + attrs = pysoap.extract_zsi_properties(obj) + attrs.update(kwds) + + attrs['CirclePersonalMembership'] = cls.CirclePersonalMembership.from_zsi(attrs['CirclePersonalMembership']) + + return cls(**attrs) + +class PersonalInfo(pysoap.Serializable): + '''Attributes: + MembershipInfo + Name + IsNotMobileVisible + IsFavorite + IsFamily + Changes + ''' + MembershipInfo = MembershipInfo + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + attrs = pysoap.extract_zsi_properties(obj) + attrs.update(kwds) + + attrs['MembershipInfo'] = cls.MembershipInfo.from_zsi(attrs['MembershipInfo']) + + return cls(**attrs) + +class CircleInverseInfo(pysoap.Serializable): + Content = Content + PersonalInfo = PersonalInfo +# def __init__(self, content = None, personalInfo = None, deleted = None): + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + attrs = pysoap.extract_zsi_properties(obj) + attrs.update(kwds) + + attrs['PersonalInfo'] = cls.PersonalInfo.from_zsi(attrs['PersonalInfo']) + attrs['Content'] = cls.Content.from_zsi(attrs['Content']) + + return cls(**attrs) + +class CircleInfo(pysoap.Serializable): + circle_member = Contact + circle_result = CircleInverseInfo +# def __init__(self, member_role = None, circle_member = None, circle_result = None): + +class ProfileField(pysoap.Serializable): +# def __init__(self, date_modified, resource_id): + +# DateModified = pysoap.DateAttr('DateModified') + pass + +class ProfilePhoto(ProfileField): +# def __init__(self, pre_auth_url = None, name = None, image = None): + pass + +class OwnerProfile(ProfileField): + photo = ProfilePhoto + expression = ProfileField +# def __init__(self, display_name = None, message = None, photo = None, has_expression = False, expression = None): + pass + +class Owner(Contact): + def __init__(self, abid = None, account = None, client = None, **kw): + kw.pop('type', None) + Contact.__init__(self, abid = abid, account = account, type = ClientType.PassportMember, client = client, **kw) + +class ABInfo(pysoap.Serializable): + name = None + ownerPuid = None + OwnerCID = 0 + ownerEmail = None + fDefault = None + joinedNamespace = None + IsBot = None + IsParentManaged = None + SubscribeExternalPartner = None + NotifyExternalPartner = None + AddressBookType = None + MessengerApplicationServiceCreated = None + IsBetaMigrated = None + MigratedTo = 0 +# lastChange = pysoap.DateAttr('lastChange') + +class ABFindContactsPagedResult(pysoap.Serializable): + abId = None + abInfo = ABInfo +# lastChange = pysoap.DateAttr('lastChange') +# DynamicItemLastChanged = pysoap.DateAttr('DynamicItemLastChanged') +# RecentActivityItemLastChanged = pysoap.DateAttr('RecentActivityItemLastChanged') +# createDate = pysoap.DateAttr('createDate') + propertiesChanged = '' + +class ContactList(pysoap.Serializable): + nonserialize_attributes = ['client'] + #serialize_attributes + contacts = {'' : Contact} + groups = {'' : Group} + abid = None + + owner = Owner + + def __init__(self, client = None, abid = None, contacts = None, groups = None, **kw): + self.client = client + + super(ContactList, self).__init__(abid = str(abid).lower(), contacts = contacts or {}, groups = groups or {}, **kw) + if self.owner is ContactList.owner: + self.owner = None + if self.contacts is ContactList.contacts: + self.contacts = {} + if self.groups is ContactList.groups: + self.groups = {} + + def GetContact(self, account, type = None): + type = ClientType(type) + if type == ClientType.CircleMember: + return self.client.GetCircle(account) + + if type is None: + for type in (ClientType.PassportMember, ClientType.EmailMember, ClientType.PhoneMember, ClientType.LCS): + if self.HasContact(account, type = type): + return self.GetContact(account, type = type) + + circle = self.client.GetCircle(account) + if circle is not None: + return circle + + return None + + hash = Contact.MakeHash(account, type, self.abid) + if hash in self.contacts: + contact = self.contacts[hash] + else: + contact = self.contacts[hash] = Contact(self.abid, account, type, self.client) + + if contact.client is None: + contact.client = self.client + return contact + + def HasContact(self, account, type = None): + if type is None: + return any(self.HasContact(account, type) for type in (ClientType.PassportMember, ClientType.EmailMember, ClientType.PhoneMember, ClientType.LCS)) + else: + return Contact.MakeHash(account, type, self.abid) in self.contacts + + def GetGroup(self, id): + return self.groups.get(id, None) + + def GroupAdded(self, name, id, is_favorite): + self.groups[id] = Group(name = name, id = id, groupInfo = GroupInfo(IsFavorite = is_favorite)) + self.client.group_receive(name.decode('utf8'), id) + + def GroupRemoved(self, group): + self.groups.pop(group.id, None) + self.client.group_remove(group.id) + + def GetContactByGuid(self, id): + id = str(id).lower() + for contact in self.contacts.values(): + if getattr(contact, 'contactId', sentinel) == id: + return contact + + return None + + def ContactAdded(self, contact, list_id, groups = None): + groups = groups or [] + if contact.account is None: + return + + if getattr(contact, 'client', None) is None: + contact.client = self.client + + role = MSNList(list_id) + contact.AddToList(role) + conflict_lists = Contact.GetConflictLists(contact.Lists, role) + contact.Lists.update(conflict_lists) + conflict_lists.add(str(role)) + list_flags = 0 + for list in conflict_lists: + list_flags |= int(role) + + self.client.contact_btype(contact.account, contact.type) + self.client.recv_contact(contact.account.decode('utf8'), + list_flags, + groups if str(MSNList.Forward) in conflict_lists else None, + id = getattr(contact, 'contactId', getattr(contact, 'CID', 0) or contact.account)) + + def ContactRemoved(self, contact, list_id, groups = None): + if getattr(contact, 'client', None) is None: + contact.client = self.client + + if groups is not None and list_id == MSNList.Forward: + for group in groups: + contact.RemoveFromGroup(group) + + if not groups: + messengerServiceMembership = self.client.address_book.SelectTargetMemberships(ServiceFilters.Messenger) + + role = MSNList(list_id).role + + if messengerServiceMembership is not None and role is not None: + roleMembers = messengerServiceMembership.Memberships.get(role, {}) + log.info("Removing %r from role %r", contact.Hash, role) + roleMembers.pop(contact.Hash, None) + + contact.RemoveFromList(list_id) + + def OnCreateCircle(self, circle): + log.info("Circle created: %r", circle) + +class Circle(Contact): + contactList = None + hostDomain = 'live.com' + segmentCounter = 0 + meContact = None + hiddenRep = None + abinfo = None + contactList = ContactList + circleInfo = CircleInverseInfo + + @property + def HostDomain(self): + return self.hostDomain + + @property + def ContactList(self): + return self.contactList + + @property + def LastChanged(self): + if self.abinfo is None: + return soap.MinTime + else: + return self.abinfo.lastChange + + def __init__(self, meContact = None, hiddenRep = None, circleInfo = None, client = None, **kw): + if isinstance(circleInfo, dict): + circleInfo = CircleInverseInfo.deserialize(circleInfo) + if isinstance(meContact, dict): + meContact = Contact.deserialize(meContact) + if isinstance(hiddenRep, dict): + hiddenRep = Contact.deserialize(hiddenRep) + + Contact.__init__(self, + abid = kw.pop('abid', circleInfo.Content.Handle.Id.lower()), + account = kw.pop('account', circleInfo.Content.Handle.Id.lower() + '@' + circleInfo.Content.Info.HostedDomain.lower()), + type = kw.pop('type', IMAddressInfoType.Circle), + CID = kw.pop('CID', meContact.contactInfo.CID), + client = kw.pop('client', client), + circleInfo = circleInfo, + **kw) + self.meContact = meContact + self.hiddenRep = hiddenRep + self.hostDomain = circleInfo.Content.Info.HostedDomain.lower() + + self.CircleRole = getattr(CirclePersonalMembershipRole, circleInfo.PersonalInfo.MembershipInfo.CirclePersonalMembership.Role) + self.SetName(circleInfo.Content.Info.DisplayName) + self.SetNickName(self.Name) + self.contactList = ContactList(abid = self.abid, + owner = Owner(abid = self.abid, + account = meContact.contactInfo.passportName, + CID = meContact.contactInfo.CID, + client = client), + client = client) + + self.AddToList(MSNList.Allow) + self.AddToList(MSNList.Forward) + + @property + def AddressBookId(self): + return self.abid + + def SetName(self, name): + self.Name = name + + def SetNickName(self, nick): + self.NickName = nick + + @classmethod + def MakeHash(cls, abid_or_mail, domain = hostDomain): + if abid_or_mail.startswith("9:"): + abid_or_mail = abid_or_mail[2:] + + try: + email = net.EmailAddress(abid_or_mail) + except (TypeError, ValueError): + abid = abid_or_mail + else: + abid = email.name + domain = email.domain + + abid = str(abid).lower() + circleMail = abid + '@' + domain + circleHash = Contact.MakeHash(circleMail, ClientType.CircleMember, abid) + return circleHash + + @property + def Hash(self): + return type(self).MakeHash(self.abid, self.hostDomain) + + @property + def MemberNames(self): + return [x.account for x in self.GetContactsForState(RelationshipStates.Accepted)] + + @property + def PendingNames(self): + return [x.account for x in self.GetContactsForState(RelationshipStates.WaitingResponse)] + + def GetContactsForState(self, relState): + ab = self.client.address_book + contacts = [] + for contact in ab.AddressBookContacts[self.AddressBookId].values(): + if ab.GetCircleMemberRelationshipStateFromNetworkInfo(contact.contactInfo.NetworkInfoList) == relState: + contacts.append(contact) + + return contacts + + def GetContactsForRole(self, relRole): + ab = self.client.address_book + contacts = [] + for contact in ab.AddressBookContacts[self.AddressBookId].values(): + if ab.GetCircleMemberRoleFromNetworkInfo(contact.contactInfo.NetworkInfoList) == relRole: + contacts.append(contact) + + return contacts + +class AddressBook(pysoap.Serializable): + nonserialize_attributes = ['initialized', 'client', 'MyProperties', 'PendingAcceptionCircleList'] + + MembershipList = {'' : ServiceMembership} + groups = {'' : Group} + + AddressBookContacts = {'' : {'' : Contact}} + AddressBooksInfo = {'' : ABInfo} + CircleResults = {'' : CircleInverseInfo} + + def Save(self): + try: + if self.client.address_book is self: + self.client.address_book = self + except Exception: + import traceback; traceback.print_exc() + + def __init__(self, client = None, **kw): + self.client = client + self.initialized = False + self.request_circle_count = 0; + self.WLConnections = {} + self.WLInverseConnections = {} + self.MyProperties = { + AnnotationNames.MSN_IM_MBEA : '0', + AnnotationNames.MSN_IM_GTC: '1', + AnnotationNames.MSN_IM_BLP : '0', + AnnotationNames.MSN_IM_MPOP : '1', + AnnotationNames.MSN_IM_RoamLiveProperties : '1', + AnnotationNames.Live_Profile_Expression_LastChanged: soap.MinTime, + } + + if self.groups is AddressBook.groups: + self.groups = {} + + if self.AddressBookContacts is AddressBook.AddressBookContacts: + self.AddressBookContacts = {} + + self.contactTable = {} + + if self.MembershipList is AddressBook.MembershipList: + self.MembershipList = {} + + if self.AddressBooksInfo is AddressBook.AddressBooksInfo: + self.AddressBooksInfo = {} + + if self.CircleResults is AddressBook.CircleResults: + self.CircleResults = {} + + pysoap.Serializable.__init__(self, **kw) + + self.PendingAcceptionCircleList = {} + + if not hasattr(self, 'PendingCreateCircleList'): + self.PendingCreateCircleList = {} + + def load_from_file(self, filename): + pass + + def initialize(self): + if getattr(self, 'initialized', False): + return + + intialized = True + service_ms = self.SelectTargetMemberships(ServiceFilters.Messenger) + if service_ms is not None: + ms = service_ms.Memberships + else: + ms = None + + if ms is not None: + for role in ms.keys(): + msnlist = getattr(MSNList, role, None) + + for member in ms[role].values(): + cid = 0 + account = None + type = None + + if isinstance(member, PassportMember): + if not getattr(member, 'IsPassportNameHidden', False): + account = member.PassportName + cid = getattr(member, 'CID', 0) + type = 'Passport' + elif isinstance(member, EmailMember): + type = 'Email' + account = member.Email + elif isinstance(member, PhoneMember): + type = 'Phone' + account = member.PhoneNumber + + if account is not None and type is not None: + contact = self.client.contact_list.GetContact(account, type) + contact.CID = cid + self.client.contact_list.ContactAdded(contact, msnlist) + + for group in self.groups.values(): + self.client.contact_list.GroupAdded(group.groupInfo.name, group.id, group.groupInfo.IsFavorite) + + for abId in self.AddressBookContacts.keys(): + self.SaveContactTable(self.AddressBookContacts[abId].values()) + + self.RestoreWLConnections() + for State in (RelationshipStates.Accepted, RelationshipStates.WaitingResponse): + CIDs = self.FilterWLConnections(self.WLConnections.keys(), State) + log.info("Restoring Circles: %r, %r", CIDs, State) + self.RestoreCircles(CIDs, State) + + default_id = str(uuid.UUID(int=0)) + default_page = self.AddressBookContacts.get(default_id, None) + + if default_page is not None: + for contactType in default_page.values(): + self.UpdateContact(contactType) + + def UpdateContact(self, contact, abid = uuid.UUID(int=0), circle = None): + info = getattr(contact, 'contactInfo', None) + if info is None: + return + + log.debug("Updating contactInfo: %r", contact) + + type = IMAddressInfoType.WindowsLive + + account = info.passportName + displayName = info.displayName + nickname = self.GetContactNickName(contact) + userTileUrl = self.GetUserTileURLFromWindowsLiveNetworkInfo(contact) + isMessengerUser = info.isMessengerUser or False + lowerid = str(abid).lower() + fDeleted = contact.fDeleted + + isDefaultAddressBook = lowerid == str(uuid.UUID(int=0)).lower() + + if info.emails is not None and account is None: + for ce in info.emails: + log.info("process email: %r", ce) + if ce.contactEmailType == info.primaryEmailType or account is None: + log.info("using email as primary: %r", ce) + type = ClientType(int(ce.Capability)) + if account is None: + account = ce.email + isMessengerUser |= ce.isMessengerEnabled + displayName = account + + if info.phones is not None and account is None: + type = ClientType.PhoneMember + for cp in info.phones: + log.info("process phone: %r", cp) + if cp.contactPhoneType == info.PrimaryPhone or account is None: + log.info("using phone as primary: %r", cp) + if account is None: + account = cp.number + isMessengerUser |= cp.isMessengerEnabled + displayName = account + + if account is not None: + account = account.lower() + if info.contactType != MessengerContactType.Me: + if isDefaultAddressBook: + contactList = self.client.contact_list + contact = contactList.GetContact(account, type=type) + else: + if circle is not None: + mRole = self.GetCircleMemberRoleFromNetworkInfo(info.NetworkInfoList) + contactList = circle.contactList + contact = contactList.GetContact(account, type=type) + contact.CircleRole = mRole + nickname = self.GetCircleMemberDisplayNameFromNetworkInfo(info.NetworkInfoList) or nickname + + contact.CID = info.CID + contact.type = type + contact.hasSpace = info.hasSpace + contact.IsMessengerUser = isMessengerUser + contact.UserTileURL = userTileUrl + contact.NickName = nickname + self.SetContactPhones(contact, info) + + if True or contact.IsMessengerUser: + contact.AddToList(MSNList.Forward) + log.debug("Forcing %r to forward list", contact) + contactList.ContactAdded(contact, MSNList.Forward, getattr(contact.contactInfo, 'groupIds', [])) + + needsDelete = False + relState = self.GetCircleMemberRelationshipStateFromNetworkInfo(info.NetworkInfoList) + if (relState in (RelationshipStates.Rejected, RelationshipStates.NONE)) and not isDefaultAddressBook: + needsDelete = True + + if getattr(info, 'IsHidden', False): + needsDelete = True + + owner = self.client.contact_list.owner +# if account == owner.Mail.lower() and info.NetworkInfoList and type == owner.type and not isDefaultAddressBook: +# needsDelete = True + + if fDeleted: + needsDelete = True + + if needsDelete and len(contact.Lists) == 0: + log.debug("Contact %r needs delete", contact) + contactList.contacts.pop(contact.Hash, None) + + else: + owner = None + if not isDefaultAddressBook: + if circle is None: + log.info("Can't update owner %r in addressbook: %r", account, abid) + return + owner = circle.contactList.owner + mRole = self.GetCircleMemberRoleFromNetworkInfo(info.NetworkInfoList) + contactList = circle.contactList + contact.CircleRole = mRole + if contact.account == self.client.self_buddy.name: + circle.CircleRole = mRole + log.info("Got new circle role for mRole = %r, circle %r", mRole, circle) + nickname = self.GetCircleMemberDisplayNameFromNetworkInfo(info.NetworkInfoList) or nickname + + else: + owner = self.client.contact_list.owner + if owner is None: + owner = Owner() + owner.__dict__.update(vars(contact)) + owner.abid = lowerid + owner.account = info.passportName + owner.CID = int(info.CID) + owner.client = self.client + log.info("Set owner for contact list: %r", owner) + self.client.contact_list.owner = owner + + if displayName == owner.Mail and bool(owner.name): + displayName == owner.name + + owner.Guid = str(uuid.UUID(contact.contactId)).lower() + owner.CID = int(info.CID) + owner.contactType = info.contactType + + if nickname and not getattr(owner, 'NickName', ''): + owner.NickName = nickname + + owner.UserTileURL = userTileUrl + self.SetContactPhones(owner, info) + + if info.annotations and isDefaultAddressBook: + for anno in info.annotations: + self.MyProperties[anno.Name] = anno.Value + + def SaveContactTable(self, contacts): + if not contacts: + return + + for contact in contacts: + if contact.contactInfo is not None: + self.contactTable[contact.contactInfo.CID] = contact.contactId + + def RestoreWLConnections(self): + self.WLInverseConnections = {} + + for CID in self.WLConnections.keys(): + self.WLInverseConnections[self.WLConnections[CID]] = CID + + def FilterWLConnections(self, cids, state): + to_return = [] + for cid in cids: + if self.HasWLConnection(cid) and self.HasContact(cid): + contact = self.SelectContact(cid) + if state == RelationshipStates.NONE: + to_return.append(cid) + else: + repRelState = self.GetCircleMemberRelationshipStateFromNetworkInfo(contact.contactInfo.NetworkInfoList) + if repRelState == state: + to_return.append(cid) + + return to_return + + def RestoreCircles(self, cids, state): + for cid in cids: + self.RestoreCircleFromAddressBook(self.SelectWLConnectionByCID(cid), self.SelectContact(cid), state) + + def SelectWLConnectionByCID(self, CID): + if not self.HasWLConnection(CID): + return None + return self.WLConnections.get(CID) + + def SelectWLConnectionByAbId(self, abid): + abid = abid.lower() + if not self.HasWLConnection(abid): + return None + return self.WLInverseConnections.get(abid) + + SelectWLConnection = SelectWLConnectionByAbId + + def SelectWLConnectionsByCIDs(self, CIDs, state = RelationshipStates.NONE): + abids = [] + for CID in CIDs: + if self.HasWLConnection(CID) and self.HasContact(CID): + abid = self.WLConnections.get(CID) + contact = self.SelectContact(CID) + if state == RelationshipStates.NONE: + abids.append(abid) + else: + cid_state = self.GetCircleMemberRelationshipStateFromNetworkInfo(contact.contactInfo.NetworkInfoList) + if cid_state == state: + abids.append(abid) + + return abids + + def RestoreCircleFromAddressBook(self, abId, hidden, state): + lowerid = abId.lower() + if lowerid == str(uuid.UUID(int=0)).lower(): + log.error("AddressBook is not a circle") + return True + + if not self.HasAddressBook(lowerid): + log.error("Don't have circle with ID = %r", lowerid) + return False + + if not lowerid in self.AddressBookContacts: + log.error("Don't have circle contact with ID = %r", lowerid) + return False + + me = self.SelectMeFromContactList(self.AddressBookContacts[lowerid].values()) + inverseInfo = self.SelectCircleInverseInfo(lowerid) + + if me is None: + log.error("Don't have a 'me' contact for %r", lowerid) + return False + if hidden is None: + log.error("Need a hidden contact for %r", lowerid) + return False + if inverseInfo is None: + log.error("No circle info for %r", lowerid) + return False + + circleHash = Circle.MakeHash(lowerid, inverseInfo.Content.Info.HostedDomain) + + if self.client.CircleList.get(circleHash, None) is None: + log.error("No circle object found for %r", circleHash) + return False + + circle = self.CreateCircle(me, hidden, inverseInfo) + if circle is None: + log.error("Incorrect info to create circle") + return False + self.UpdateCircleMembersFromAddressBookContactPage(circle, Scenario.Restore) + + CPMR = CirclePersonalMembershipRole + if circle.CircleRole in (CPMR.Admin, CPMR.AssistantAdmin, CPMR.Member): + self.AddCircleToCircleList(circle) + elif circle.CircleRole == CPMR.StatePendingOutbound: + self.FireJoinCircleInvitationReceivedEvents(circle) + else: + raise Exception("Unknown circleRole: %r", circle.CircleRole) + + def SelectTargetMemberships(self, servicefilter): + return self.MembershipList.get(servicefilter, None) + + def SelectCircleInverseInfo(self, abid): + if not abid: + return None + + abid = abid.lower() + + return self.CircleResults.get(abid, None) + + def SelectContact(self, cid): + guid = self.contactTable.get(cid, cid) + if guid is None: + return None + + for abid in sorted(self.AddressBookContacts.keys()): + if guid in self.AddressBookContacts[abid]: + return self.AddressBookContacts[abid][guid] + + return None + + def Merge(self, fmresult): + self.initialize() + + if fmresult is None: + return + + for serviceType in fmresult.Services.Service: + oldService = self.SelectTargetService(serviceType.Info.Handle.Type) + if oldService is None or (oldService.lastChange <= serviceType.LastChange): + log.debug("Merging service %r", serviceType.Info.Handle.Type) + if serviceType.Deleted: + self.MembershipList.pop(serviceType.Info.Handle.Type, None) + else: + updatedService = Service(id = int(serviceType.Info.Handle.Id), + type = serviceType.Info.Handle.Type, + lastChange = serviceType.LastChange, + foreign_id = serviceType.Info.Handle.ForeignId) + + if oldService is None: + self.MembershipList[updatedService.type] = ServiceMembership(Service = updatedService) + + if serviceType.Memberships: + if updatedService.type == ServiceFilters.Messenger: + self.ProcessMessengerServiceMemberships(serviceType, updatedService) + else: + self.ProcessOtherMemberships(serviceType, updatedService) + + self.MembershipList[updatedService.type].Service = updatedService + else: + log.debug("Service %r is up to date (%r >= %r)", + serviceType.Info.Handle.Type, + oldService.lastChange, + serviceType.LastChange) + + def ProcessMessengerServiceMemberships(self, service, clone): + for mship in service.Memberships.Membership: + if mship.Members and mship.Members.Member: + role = mship.MemberRole + log.info("Processing Membership: %r", role) + members = list(mship.Members.Member) + members = map(Member.from_zsi, members) + log.info("ProcessMessengerServiceMemberships: Role = %r, members = %r", role, members) + + for bm in sorted(members, key = lambda x: getattr(x, 'lastChange', soap.MinTime)): + cid = 0 + account = None + type = ClientType.NONE + + if isinstance(bm, PassportMember): + type = ClientType.PassportMember + if not bm.IsPassportNameHidden: + account = bm.PassportName + cid = bm.CID + elif isinstance(bm, EmailMember): + type = ClientType.EmailMember + account = bm.Email + elif isinstance(bm, PhoneMember): + type = ClientType.PhoneMember + account = bm.PhoneNumber + elif isinstance(bm, CircleMember): + type = ClientType.CircleMember + account = bm.CircleId + self.circlesMembership.setdefault(role, []).append(bm) + + if account is not None and type != ClientType.NONE: + #log.info("\t%r member: %r", role, account) + account = account.lower() + ab = self.client.getService(SOAPServices.AppIDs.AddressBook) + cl = self.client.contact_list + msnlist = getattr(MSNList, role, None) + + if type == ClientType.CircleMember: + continue + + + if bm.Deleted: + if self.HasMembership(clone.ServiceType, account, type, role): + contact = self.MembershipList[clone.type].Memberships[role][Contact.MakeHash(account, type)] + contact_lastChange = contact.lastChange + if contact_lastChange < bm.lastChange: + self.RemoveMembership(clone.type, account, type, role, Scenario.DeltaRequest) + + if cl.HasContact(account, type): + contact = ab.GetContact(account, type = type) + contact.CID = cid + if contact.HasLists(msnlist): + contact.RemoveFromList(msnlist) + #if msnlist == 'RL': + # ab.OnReverseRemoved(contact) + + self.OnContactRemoved(contact, msnlist) + else: + contact_lastChange = getattr((self.MembershipList[clone.type].Memberships or {}) + .get(role, {}) # might not have this role + .get(Contact.MakeHash(account, type), None), # or this contact + 'LastChanged', soap.MinTime) # and so we might not have this value + if getattr(bm, 'LastChanged', soap.MinTime) >= contact_lastChange: + self.AddMembership(clone.type, account, type, role, bm, Scenario.DeltaRequest) + + displayname = bm.DisplayName or account + contact = cl.GetContact(account, type) + contact.CID = cid + self.client.contact_list.ContactAdded(contact, msnlist) + + def ProcessOtherMemberships(self, service, clone): + for mship in service.Memberships: + if mship.Members and mship.Members.Member: + role = mship.MemberRole + + for bm in sorted(mship.Members.Member, key = lambda x: x.lastChange): + account = None + type = ClientType.NONE + + if bm.Type == MembershipType.Passport: + type = ClientType.PassportMember + if not bm.IsPassportNameHidden: + account = bm.PassportName + elif bm.Type == MembershipType.Email: + type = ClientType.EmailMember + account = bm.Email + elif bm.Type == MembershipType.Phone: + type = ClientType.PhoneMember + account = bm.PhoneNumber + elif bm.Type in (MembershipType.Role, MembershipType.Service, MembershipType.Everyone, MembershipType.Partner): + account = bm.Type + "/" + bm.MembershipId + elif bm.Type == MembershipType.Domain: + account = bm.DomainName + elif bm.Type == MembershipType.Circle: + type = ClientType.CircleMember + account = bm.CircleId + + if account is not None and type != ClientType.NONE: + if bm.Deleted: + self.RemoveMembership(clone.type, account, type, role, Scenario.DeltaRequest) + else: + self.AddMembership(clone.type, account, type, role, bm, Scenario.DeltaRequest) + + def AddMembership(self, servicetype, account, type, memberrole, member, scene): + #log.debug("AddMembership(%r, %r, %r, %r, %r, %r)", servicetype, account, type, memberrole, member, scene) + service = self.SelectTargetMemberships(servicetype) + ms = service.Memberships + if not ms: + ms = service.Memberships = {} + if memberrole not in ms: + ms[memberrole] = {} + ms[memberrole][Contact.MakeHash(account, type)] = member + + if scene == Scenario.DeltaRequest: + if memberrole == MemberRole.Allow: + self.RemoveMembership(servicetype, account, type, MemberRole.Block, Scenario.InternalCall) + + if memberrole == MemberRole.Block: + self.RemoveMembership(servicetype, account, type, MemberRole.Allow, Scenario.InternalCall) + + contact = self.client.contact_list.GetContact(account, type = type) + msnlist = getattr(MSNList, memberrole, None) + #log.info("AddMembership: role = %r, contact = %r", memberrole, contact) + self.client.contact_list.ContactAdded(contact, msnlist) + + def RemoveMembership(self, servicetype, account, type, role, scenario = None): + ms = self.SelectTargetMemberships(servicetype).Memberships + if ms: + hash = Contact.MakeHash(account, type) + ms.get(role, {}).pop(hash, None) + + def RemoveContactFromAddressBook(self, abid, contactid): + abid = str(abid).lower() + return self.AddressBookContacts.get(abid, {}).pop(contactid, None) + + def RemoveContactFromContacttable(self, cid): + return self.contactTable.pop(cid, None) + + def RemoveAddressBookInfo(self, abid): + return self.AddressBooksInfo.pop(str(abid).lower(), None) + + def RemoveAddressBookContactPage(self, abid): + return self.AddressBookContacts.pop(str(abid).lower(), None) + + def SetContactToAddressBookContactPage(self, abid, contact): + abid = str(abid).lower() + ab_created = False + + if abid not in self.AddressBookContacts: + self.AddressBookContacts[abid] = {} + ab_created = True + + log.debug("SetContactToAddressBookContactPage: %r", contact) + self.AddressBookContacts[abid][str(uuid.UUID(contact.contactId)).lower()] = contact + + return ab_created + + def SetAddressBookInfoToABInfoList(self, abid, ab): + abid = str(abid).lower() + if self.AddressBooksInfo is None: + return False + + abinfo = ab.AbInfo + + self.AddressBooksInfo[abid] = ABInfo(id = ab.AbId, + lastChange = ab.LastChange, + name = abinfo.Name, + ownerPuid = abinfo.OwnerPuid, + OwnerCID = abinfo.OwnerCID, + ownerEmail = abinfo.OwnerEmail, + fDefault = abinfo.FDefault, + joinedNamespace = abinfo.JoinedNamespace, + IsBot = abinfo.IsBot, + IsParentManaged = abinfo.IsParentManaged, + SubscribeExternalPartner = abinfo.SubscribeExternalPartner, + NotifyExternalPartner = abinfo.NotifyExternalPartner, + AddressBookType = abinfo.AddressBookType, + MessengerApplicationServiceCreated = abinfo.MessengerApplicationServiceCreated, + IsBetaMigrated = abinfo.IsBetaMigrated, + MigratedTo = abinfo.MigratedTo, + ) + return True + + def HasContact(self, abid = None, guid = None): + return self.SelectContactFromAddressBook(abid, guid) is not None + + def HasWLConnection(self, cid_or_abid): + return (cid_or_abid in self.WLConnections) or (str(cid_or_abid).lower() in self.WLInverseConnections) + + def HasAddressBook(self, abid): + if self.AddressBooksInfo is None: + return False + + return str(abid).lower() in self.AddressBooksInfo + + def HasAddressBookContactPage(self, abid): + abid = str(abid).lower() + if self.AddressBookContacts is None: + return False + + return self.AddressBookContacts.get(abid, None) is not None + + def HasMembership(self, servicetype, account, type, role): + return self.SelectBaseMember(servicetype, account, type, role) is not None + + def SelectTargetService(self, servicetype): + return getattr(self.MembershipList.get(servicetype, None), 'Service', None) + + def SelectBaseMember(self, servicetype, account, type, role): + hash = Contact.MakeHash(account, type) + ms = self.SelectTargetMemberships(servicetype) + + return ms.Memberships.get(role, {}).get(hash, None) + + def SelectContactFromAddressBook(self, abid, guid = None): + if guid is None: + guid = abid + return self.SelectContact(guid) + else: + contact = self.AddressBookContacts.get(str(abid).lower(), {}).get(guid, None) + if contact is None: + return self.SelectContact(guid) + + def Add(self, range): + for svc in range: + for role in range[svc]: + for hash in range[svc][role]: + if svc.type not in self.mslist: + self.mslist[svc.type] = ServiceMembership(svc) + + if role not in self.mslist[svc.type].Memberships: + self.mslist[svc.type].Memberships[role] = {} + + if hash in self.mslist[svc.type].Memberships[role]: + if mslist[svc.type].Memberships[role][hash].lastChange < range[svc][role][hash].lastChange: + mslist[svc.type].Memberships[role][hash] = range[svc][role][hash] + else: + mslist[svc.type].Memberships[role][hash] = range[svc][role][hash] + + def GetAddressBookLastChange(self, abid = uuid.UUID(int=0)): + abid = str(abid).lower() + if self.HasAddressBook(abid): + return self.AddressBooksInfo[abid].lastChange + + return soap.MinTime + + def SetAddressBookInfo(self, abid, abHeader): + abid = str(abid).lower() + mytime = self.GetAddressBookLastChange(abid) + newtime = abHeader.LastChange + if mytime > newtime: + return + + self.SetAddressBookInfoToABInfoList(abid, abHeader) + + def IsContactTableEmpty(self): + ct = getattr(self, 'contactTable', None) + if ct is None: + ct = self.contactTable = {} + return len(ct) == 0 + + def MergeIndividualAddressBook(self, forwardList): + log.debug("MergeIndividualAddressBook") + if forwardList.Ab is None: + log.debug("\tNo AddressBook information in result") + return + + if SOAPServices.strptime_highres(self.GetAddressBookLastChange(forwardList.Ab.AbId)) > \ + SOAPServices.strptime_highres(forwardList.Ab.LastChange): + log.debug("\tAddressBook information is out of date (%r > %r)", self.GetAddressBookLastChange(forwardList.Ab.AbId), forwardList.Ab.LastChange) + return + + if str(forwardList.Ab.AbId).lower() != str(uuid.UUID(int=0)).lower(): + log.debug("\tWrong address book ID") + return + + scene = Scenario.NONE + + if self.IsContactTableEmpty(): + scene = Scenario.Initial + else: + scene = Scenario.DeltaRequest + + # Process groups + groups = getattr(getattr(forwardList, 'Groups', None), 'Group', []) + for group in groups: + key = str(uuid.UUID(str(group.GroupId))).lower() + if group.FDeleted: + self.groups.pop(key, None) + contact_group = self.client.contact_list.GetGroup(group.GroupId) + if contact_group is not None: + self.client.contact_list.GroupRemoved(contact_group) + else: + + contact_group = Group(id = key, + groupInfo = GroupInfo(name = group.GroupInfo.Name, + groupType = group.GroupInfo.GroupType, + IsNotMobileVisible = getattr(group.GroupInfo, 'IsNotMobileVisible', None), + IsPrivate = getattr(group.GroupInfo, 'IsPrivate', None), + IsFavorite = getattr(group.GroupInfo, 'IsFavorite', None), + fMessenger = getattr(group.GroupInfo, 'FMessenger', None), + ) + ) + + self.groups[key] = contact_group + self.client.contact_list.GroupAdded(group.GroupInfo.Name, group.GroupId, group.GroupInfo.IsFavorite) + + circle_infos = getattr(getattr(getattr(forwardList, 'CircleResult', None), 'Circles', None), 'CircleInverseInfo', None) or [] + modifiedConnections = {} + newInverseInfos = {} + newCIDList = {} + for info in circle_infos: + abId = str(info.Content.Handle.Id).lower() + CID = self.SelectWLConnection(abId) + info = CircleInverseInfo.from_zsi(info) + if self.HasWLConnection(abId): + if CID is not None and CID not in modifiedConnections: + log.info("Modified connection found: %r", info) + modifiedConnections[CID] = info + else: + log.info("New circle info found: %r", info) + newInverseInfos[abId] = info + + transformed_contacts = [] + + CL = self.client.contact_list + + for cx in getattr(getattr(forwardList, 'Contacts', None), 'Contact', []): + contact = self.TransformZSIContact(CL, cx, forwardList.Ab.AbId) + if contact is None: + continue + transformed_contacts.append(contact) + + self.SetContactToAddressBookContactPage(forwardList.Ab.AbId, contact) + CID = contact.contactInfo.CID + if self.HasWLConnection(CID): + modifiedConnections[CID] = self.SelectCircleInverseInfo(self.SelectWLConnectionByCID(CID)) + savedContact = self.SelectContact(CID) + if savedContact.contactInfo.contactType == MessengerContactType.Circle: + if contact.contactInfo.contactType != MessengerContactType.Circle: + # Owner deleted circles found + log.info("Deleted circles found: %r", savedContact) + + else: + # Removed from the circle + log.info("Circle removal found: %r", savedContact) + else: + if contact.contactInfo.contactType == MessengerContactType.Circle: + state = self.GetCircleMemberRelationshipStateFromNetworkInfo(contact.contactInfo.NetworkInfoList) + if state in (RelationshipStates.Accepted, RelationshipStates.WaitingResponse): + newCIDList[CID] = str(uuid.UUID(int=CID)).lower() + + if getattr(contact, 'fDeleted', False): + log.debug("Deleted contact: %r", contact) + old_contact = self.RemoveContactFromAddressBook(forwardList.Ab.AbId, uuid.UUID(contact.contactId)) + if old_contact is not None: + old_contact.RemoveFromList(MSNList.Forward) + self.client.contact_list.contact_remove(old_contact, str(MSNList.Forward)) + old_contact.Guid = uuid.UUID(int=0) + old_contact.isMessengerUser = False + old_contact.status = 'offline' + if not len(old_contact.Lists): + self.client.contact_list.Remove(old_contact.Mail, old_contact.ClientType) + + else: + + self.UpdateContact(contact) + for list in contact.Lists: + self.client.contact_list.ContactAdded(contact, list) + + if forwardList.Ab is not None: + self.SetAddressBookInfo(forwardList.Ab.AbId, forwardList.Ab) + + self.SaveContactTable(transformed_contacts) + inverse_info = getattr(getattr(getattr(forwardList, 'CircleResult', None), 'Circles', None), 'CircleInverseInfo', None) + if inverse_info is not None: + self.SaveCircleInverseInfo(inverse_info) + + log.info("ProcessCircles(%r, %r, %r, %r)", modifiedConnections, newCIDList, newInverseInfos, scene) + self.ProcessCircles(modifiedConnections, newCIDList, newInverseInfos, scene) + + def TransformZSIContact(self, CL, cx, abid): + if cx.ContactInfo is None: + return None + + abid = str(abid).lower() + + info = cx.ContactInfo + guid = uuid.UUID(cx.ContactId) + contact = CL.GetContactByGuid(guid) + if contact is None: + contact_type = info.ContactType + clitype = ClientType(contact_type) + + contact_account_name = info.PassportName + if contact_account_name is None: + primaryEmailType = info.PrimaryEmailType + emails = getattr(info.Emails, 'ContactEmail', []) + for email in emails: + if email.ContactEmailType == primaryEmailType: + contact_account_name = email.Email + break + + if email.IsMessengerEnabled: + contact_account_name = email.Email + + if contact_account_name is None: + for email in emails: + if email.ContactEmailType in (ContactEmail.Types.Messenger, + ContactEmail.Types.Messenger2, + ContactEmail.Types.Messenger3, + ContactEmail.Types.Messenger4): + contact_account_name = email.Email + break + + if contact_account_name is None: + contact = Contact.from_zsi(cx, + abid = abid, + account = contact_account_name, + type = clitype, + client = self.client) + + log.info("No idea what the contact's account name is is for %r", contact) + return + + clitype = ClientType.EmailMember + + contact = Contact.from_zsi(cx, + abid = abid, + account = contact_account_name, + type = clitype, + client = self.client) + + log.info("Got contact: %r", contact) + CL.contacts[contact.Hash] = contact + else: + contact.contactInfo = ContactInfo.from_zsi(info) + + contact.fDeleted = getattr(cx, 'FDeleted', False) + + return contact + + + def MergeGroupAddressBook(self, forwardList): + + log.debug("MergeGroupAddressBook(%r)", forwardList) + + if forwardList.Ab is None: + return + if forwardList.Ab.AbId == str(uuid.UUID(int=0)): + return + if forwardList.Ab.AbInfo.AddressBookType != AddressBookType.Group: + return + if SOAPServices.strptime_highres(self.GetAddressBookLastChange(forwardList.Ab.AbId)) > \ + SOAPServices.strptime_highres(forwardList.Ab.LastChange): + log.debug("\tAddressBook information is out of date (%r > %r)", self.GetAddressBookLastChange(forwardList.Ab.AbId), forwardList.Ab.LastChange) + return + + self.SetAddressBookInfo(forwardList.Ab.AbId, forwardList.Ab) + self.SaveAddressBookContactPage(forwardList.Ab.AbId, forwardList.Contacts.Contact) + + circle = self.UpdateCircleFromAddressBook(forwardList.Ab.AbId) + + if circle is None: + self.RemoveCircleInverseInfo(forwardList.Ab.AbId) + self.RemoveAddressBookContactPage(forwardList.Ab.AbId) + self.RemoveAddressBookInfo(forwardList.Ab.AbId) + return + + log.debug("UpdateCircleMembersFromAddressBookContactPage: circle = %r", circle) + self.UpdateCircleMembersFromAddressBookContactPage(circle, Scenario.Initial) + + if circle.CircleRole in (CirclePersonalMembershipRole.Admin, + CirclePersonalMembershipRole.AssistantAdmin, + CirclePersonalMembershipRole.Member): + self.AddCircleToCircleList(circle) + elif circle.CircleRole == CirclePersonalMembershipRole.StatePendingOutbound: + self.FireJoinCircleInvitationReceivedEvents(circle) + else: + raise Exception("Unknown circleRole: %r", circle.CircleRole) + + if self.IsPendingCreateConfirmCircle(circle.AddressBookId): + self.FireCreateCircleCompletedEvent(circle) + + + log.debug("Got Non-default AddressBook:") + log.debug("\tId: %r", forwardList.Ab.AbId) + log.debug("\tName: %r", forwardList.Ab.AbInfo.Name) + log.debug("\tType: %r", forwardList.Ab.AbInfo.AddressBookType) + log.debug("\tMembers:") + + transformed_contacts = [] + for contact in forwardList.Contacts.Contact: + + contact = self.TransformZSIContact(circle.contactList, contact, forwardList.Ab.AbId) + + if contact is None: + log.info("Got none for contact: %r", contact) + continue + transformed_contacts.append(contact) + + log.debug("\t\t%r", contact.Hash) + + self.SaveContactTable(transformed_contacts) + + def IsPendingCreateConfirmCircle(self, abid): + return abid in self.PendingCreateCircleList + + def FireCreateCircleCompletedEvent(self, circle): + self.RemoveABIdFromPendingCreateCircleList(circle.AddressBookId) + self.client.contact_list.OnCreateCircle(circle) + + def RemoveABIdFromPendingCreateCircleList(self, abid): + self.PendingCreateCircleList.pop(abid, None) + + def UpdateCircleMembersFromAddressBookContactPage(self, circle, scene): + abid = str(circle.AddressBookId).lower() + + if not self.HasAddressBookContactPage(abid): + return + + newContactList = {} + oldContactInverseList = {} + oldContactList = [] + + isRestore = bool(scene & Scenario.Restore) + + if not isRestore: + oldContactList[:] = circle.ContactList.contacts.values()[:] + + for contact in oldContactList: + oldContactInverseList[contact.getCID()] = contact + + page = self.AddressBookContacts[abid] + for contact in page.values(): + if not isRestore: + newContactList[contact.getCID()] = contact + + self.UpdateContact(contact, abid, circle) + + if isRestore: + return True + + for contact in newContactList.values(): + if contact.contactInfo is None: + continue + + if contact.getCID() not in oldContactInverseList and \ + circle.contactList.HasContact(contact.contactInfo.passportName, IMAddressInfoType.WindowsLive): + + log.info("CircleMember joined: %r, %r", circle, contact.contactInfo.passportName) + self.client.on_circle_member_joined(circle, circle.contactList.GetContact(contact.contactInfo.passportName, type = IMAddressInfoType.WindowsLive)) + + for contact in oldContactList: + if contact.getCID() not in newContactList: + circle.ContactList.remove(contact.Mail, contact.type) + log.info("CircleMember left: %r, %r", circle, contact.contactInfo.PassportName) + self.client.on_circle_member_left(circle, contact) + + def SaveAddressBookContactPage(self, abid, contacts): + abid = str(abid).lower() + + old_contacts = self.AddressBookContacts.get(abid, {}) + new_contacts = self.AddressBookContacts[abid] = {} + + abc = self.AddressBookContacts[abid] + + for c_node in contacts: + ctype = ClientType(c_node.ContactInfo.ContactType) + contact = Contact.from_zsi(c_node, abid = abid, + account = c_node.ContactInfo.PassportName, + type = ctype, + client = self.client) + abc[c_node.ContactId] = contact + +# for old in self.AddressBookContacts: +# if old not in new_contacts: +# self.OnContactLeaveCircle(abid, old) + +# def OnContactLeaveCircle(self, abid, contact_id): +# pass + + def SaveCircleInverseInfo(self, inverseInfoList): + iil = inverseInfoList + if iil is not None: + for circle in iil: + lowerid = str(circle.Content.Handle.Id).lower() + #class CircleInverseInfo(pysoap.Serializable): + # content = Content + # personalInfo = PersonalInfo + self.CircleResults[lowerid] = CircleInverseInfo.from_zsi(circle) + + def RemoveCircleInverseInfo(self, abid): + self.CircleResults.pop(str(abid).lower(), None) + + def UpdateCircleFromAddressBook(self, abid): + abid = str(abid).lower() + + if abid == str(uuid.UUID(int = 0)): + return + + meContact = self.SelectMeContactFromAddressBookContactPage(abid) + hiddenCID = self.SelectWLConnection(abid) + hidden = self.SelectContact(hiddenCID) + inverseInfo = self.SelectCircleInverseInfo(abid) + + if hidden is None: + log.warning("Cannot create circle since hidden representative not found in addressbook. ABID = %r", abid) + return None + + if meContact is None: + log.warning("Cannot create circle since Me not found in addressbook. ABID = %r", abid) + return None + + if inverseInfo is None: + log.warning("Cannot create circle since inverse info not found in circle result list. ABID = %r", abid) + return None + + circleHash = Circle.MakeHash(abid, inverseInfo.Content.Info.HostedDomain) + + circle = self.client.CircleList.get(circleHash, None) + if circle is None: + log.info("Creating circle: %r", circleHash) + circle = self.CreateCircle(meContact, hidden, inverseInfo) + self.client.CircleList[circle.Hash] = circle + +# circle.CircleRole = self.GetCircleMemberRoleFromNetworkInfo(circle.contactInfo.networkInfoList) + + return circle + + def CreateCircle(self, me, hidden, inverseInfo): + if hidden.contactInfo is None: + log.error("No contact info for hidden contact") + return + if len(hidden.contactInfo.NetworkInfoList) == 0: + log.error("No network info in hidden contact") + return + if hidden.contactInfo.contactType != MessengerContactType.Circle: + log.error("Not a circle contact!") + return + + return Circle(me, hidden, inverseInfo, self.client) + + def SelectSelfContactGuid(self, abid): + me = self.SelectSelfContactFromAddressBookContactPage(abid) + if me is None: + return None + + return me.contactId + + def SelectSelfContactFromAddressBookContactPage(self, abid): + return self.SelectSelfContactFromContactList(self.AddressBookContacts.get(abid, {}).values(), self.client.contact_list.owner.Mail) + + def SelectSelfContactFromContactList(self, contacts, username): + for contact in contacts: + if getattr(contact, 'contactInfo', None) is None: + continue + + if getattr(contact.contactInfo, 'passportName', None) == username: + return contact + + return None + + def SelectMeContactFromAddressBookContactPage(self, abid): + if not self.HasAddressBookContactPage(abid): + return None + return self.SelectMeFromContactList(self.AddressBookContacts[abid].values()) + + def SelectMeFromContactList(self, contacts): + for contact in contacts: + if getattr(contact, 'contactInfo', None) is not None: + if contact.contactInfo.contactType == MessengerContactType.Me: + return contact + + return None + + def ProcessCircles(self, modifiedConnections, newCIDList, newInverseInfos, scene): + self.ProcessModifiedCircles(modifiedConnections, scene | Scenario.ModifiedCircles) + self.ProcessNewConnections(newCIDList, newInverseInfos, scene | Scenario.NewCircles) + + def ProcessNewConnections(self, newCIDs, newInfos, scene): + added = 0 + pending = 0 + + if not newCIDs: + return added, pending + + CIDs, infos = zip(*dict((x, newInfos.get(newCIDs[x], None)) for x in newCIDs.keys()).items()) + CIDs = filter(None, CIDs) + infos = filter(None, infos) + + self.SaveWLConnection(CIDs, infos) + + abIds = self.SelectWLConnectionsByCIDs(newCIDs, RelationshipStates.Accepted) + self.RequestCircles(abIds, RelationshipStates.Accepted, scene) + added = len(abIds) + + abIds = self.SelectWLConnectionsByCIDs(newCIDs, RelationshipStates.WaitingResponse) + self.RequestCircles(abIds, RelationshipStates.WaitingResponse, scene) + pending = len(abIds) + + return added, pending + + def ProcessModifiedCircles(self, modifiedConnections, scene): + deleted = 0 + reAdded = 0 + + connectionClone = modifiedConnections.copy() + for CID in modifiedConnections.keys(): + hidden = self.SelectContact(CID) + if (modifiedConnections[CID].Deleted or # User deleted circle + hidden.contactInfo.contactType != MessengerContactType.Circle or # Circle owner deleted circle + self.GetCircleMemberRelationshipStateFromNetworkInfo(hidden.contactInfo.NetworkInfoList) == RelationshipStates.Left): # Circle owner removed this user + + self.RemoveCircle(CID, modifiedConnections[CID].Content.Handle.Id) + connectionClone.pop(CID, None) + deleted += 1 + + if len(connectionClone): + CIDs, infos = zip(*connectionClone.items()) + self.SaveWLConnection(CIDs, infos) + + abIds = self.SelectWLConnectionsByCIDs(CIDs, RelationshipStates.Accepted) + self.RequestCircles(abIds, RelationshipStates.Accepted, scene) + reAdded = len(abIds) + + return deleted, reAdded + + def RemoveCircle(self, CID, circleId): + if not self.HasWLConnection(CID): + return + + self.RemoveAddressBookContactPage(circleId) + self.RemoveAddressBookInfo(circleId) + self.RemoveCircleInverseInfo(circleId) + self.BreakWLConnection(CID) + + self.client.circle_removed(Circle.MakeHash(circleId)) + + def BreakWLConnection(self, CID): + if not self.HasWLConnection(CID): + return + abid = self.SelectWLConnectionByCID(CID) + + self.WLConnections.pop(CID, None) + self.WLInverseConnections.pop(abid, None) + + def SaveWLConnection(self, CIDs, inverseList): + log.info("SaveWLConnection(%r, %r)", CIDs, inverseList) + if not inverseList: + return + + if len(CIDs) != len(inverseList): + return + + for cid, info in zip(CIDs, inverseList): + self.WLConnections[cid] = str(info.Content.Handle.Id).lower() + self.WLInverseConnections[self.WLConnections[cid]] = cid + + def GetContactNickName(self, contact): + annotations = getattr(getattr(getattr(contact, 'ContactInfo', None), 'Annotations', None), 'Annotation', []) + for anno in annotations: + if anno.Name == AnnotationNames.AB_NickName: + return anno.Value + + return u'' + + def GetUserTileURLFromWindowsLiveNetworkInfo(self, contact, domainId = 1): + netinfos = getattr(getattr(getattr(contact, 'ContactInfo', None), 'NetworkInfoList', None), 'NetworkInfo', []) + for info in netinfos: + if info.DomainIdSpecified and info.DomainId == domainId: + if info.UserTileURL: + return info.UserTileURL + + return u'' + + def GetCircleMemberRelationshipStateFromNetworkInfo(self, infoList, domain = DomainIds.WLDomain, relationship = RelationshipTypes.CircleGroup): + if not infoList: + return RelationshipStates.NONE + + for info in infoList: + if info.RelationshipTypeSpecified and info.DomainIdSpecified and info.RelationshipStateSpecified: + if info.DomainId == domain and info.RelationshipType == relationship: + return RelationshipStates.from_int(info.RelationshipState) + + return RelationshipStates.NONE + + def GetCircleMemberRoleFromNetworkInfo(self, netInfoList): + return CirclePersonalMembershipRole.from_int(self.GetContactRelationshipRoleFromNetworkInfo(netInfoList, DomainIds.WLDomain, RelationshipTypes.CircleGroup)) + + def GetContactRelationshipRoleFromNetworkInfo(self, netInfoList, domainId, relationshipType): + if not netInfoList: + return CirclePersonalMembershipRole.NONE + + for info in netInfoList: + if info.RelationshipTypeSpecified and info.DomainIdSpecified and info.RelationshipRoleSpecified: + if info.DomainId == domainId and info.RelationshipType == relationshipType: + return info.RelationshipRole + +# log.error("Couldn't get CircleMember RelationshipRole from NetworkInfo: %r, %r, %r", netInfoList, domainId, relationshipType) + + return 0 + + def GetCircleMemberDisplayNameFromNetworkInfo(self, infoList): + return self.GetContactDisplayNameFromNetworkInfo(infoList, DomainIds.WLDomain, RelationshipTypes.CircleGroup) + + def GetContactDisplayNameFromNetworkInfo(self, infoList, domainId, relationshipType): + if not infoList: + return '' + + for info in infoList: + displayname = getattr(info, 'DisplayName', '') + if info.RelationshipTypeSpecified and info.DomainIdSpecified and bool(displayname): + return displayname + + return '' + + def RequestCircles(self, abids, relationshipState, scenario): + log.info("RequestCircles: %r, %r, %r", abids, relationshipState, scenario) + for abid in abids: + self.client.RequestAddressBookById(abid, relationshipState, scenario) + + def FireJoinCircleInvitationReceivedEvents(self, circle): + inviter = self.GetCircleInviterFromNetworkInfo(self.SelectContact(self.SelectWLConnection(circle.AddressBookId))) + + if inviter is None: + log.error("Inviter for circle is None: %r", circle) + return + + log.info("Inviter for circle found: %r (circle = %r)", inviter, circle) + + if circle.AddressBookId not in self.PendingAcceptionCircleList: + self.PendingAcceptionCircleList[circle.AddressBookId] = circle + self.client.OnCircleInvite(circle, inviter) + else: + log.info("Already asked about circle this session (%r)", circle.AddressBookId) + + def AddCircleToCircleList(self, circle): + self.client.CircleList[circle.Hash] = circle + if circle.AddressBookId in self.PendingAcceptionCircleList: + self.client.OnJoinCircle(circle) + else: + self.client.OnRecvCircle(circle) + + if circle.ADLCount <= 0: + circle.ADLCount = 1 + + self.PendingAcceptionCircleList.pop(circle.AddressBookId, None) + + def SetContactPhones(self, contact, info): + for phone in getattr(getattr(info, 'phones', None), 'phone', []): + if phone.ContactPhoneType1 == ContactPhone.Types.Mobile: + contact.MobilePhone = phone.number + elif phone.ContactPhoneType1 == ContactPhone.Types.Personal: + contact.HomePhone = phone.number + elif phone.ContactPhoneType1 == ContactPhone.Types.Business: + contact.WorkPhone = phone.number + + def GetCircleInviterFromNetworkInfo(self, contact): + + if len(contact.contactInfo.NetworkInfoList) == 0: + return None + + for networkInfo in contact.contactInfo.NetworkInfoList: + if networkInfo.DomainId != 1: + continue + InviterCID = getattr(networkInfo, 'InviterCID', None) + if InviterCID is not None: + inviter = self.SelectContact(InviterCID) + if inviter is None: + return None + inviterEmail = inviter.contactInfo.passportName + else: + inviterEmail = networkInfo.InviterEmail + + return CircleInviter(inviterEmail, networkInfo.InviterMessage, client = self.client) + + return None + +def _main(): + class A(pysoap.Serializable): + pass + class B(pysoap.Serializable): + a = A + class C(pysoap.Serializable): + b = [B] + class D(pysoap.Serializable): + x = {'' : {'' : A}} + c = C + + data = \ + {"x": + {"test": {"a-": {'foo':'bar'}, "this": {'spam':'eggs'}}, + "test2": {"another": {'steak': 'potatos'}}}, + "c": + {"b": + [{"a": {"y": 2, "x": 1, "z": 3}, + "fruit": True}, + {"a": {"vegetable": False}}], + "whatever": "something"}} + + deserialized = D.deserialize(data) + print deserialized + serialized = deserialized.serialize() + print serialized + print json.loads(serialized) == data + +if __name__ == '__main__': + _main() diff --git a/digsby/src/msn/MSN.py b/digsby/src/msn/MSN.py new file mode 100644 index 0000000..5cc0e47 --- /dev/null +++ b/digsby/src/msn/MSN.py @@ -0,0 +1,2620 @@ +from __future__ import with_statement + +import logging + +import urllib2, socket as socketmodule +import struct +import hashlib +import uuid +import traceback +import time + +import hooks +import util +import util.xml_tag +import util.net as net +import common +from common import protocol, action +from common.sms import validate_sms +from common.Buddy import icon_path_for +from util.callbacks import callsback +from util.primitives.funcs import Delegate, get, isint +from util.primitives.error_handling import try_this +from contacts import Group + +import msn +import msn.AddressBook as MSNAB + +log = logging.getLogger('msn.client') +#log.setLevel(logging.DEBUG) + +require_connected = lambda self, *a, **kw : self.state == self.Statuses.ONLINE + +def is_circle_name(name): + try: + email = net.EmailAddress(name) + uuid.UUID(email.name) + except (TypeError, ValueError): + return False + + return True + +class MSNClient(protocol): + name = 'msn' + + message_format = 'html' + + message_bg = False + message_sizes = [] + + mobile_enable_url = 'http://mobile.live.com/signup' + mobile_edit_url = 'http://mobile.live.com/signup' + + needs_update = [ + ('remote_alias', 'set_display_name'), + ] + + ROOT_ID = None + + supports_group_chat = True + + __slots__ = \ + ''' + username password hub name server + status buddies disconnecting logging_on sockets + version keep_alive_thread gtc_state num_groups + num_buddies allow_unknown_contacts last_buddy + forward_list reverse_list allow_list block_list + pending_list list_types groups root_group + status_to_code code_to_status self_info + versions props m_buddies icon_requests + icon_obj _ft remote_alias + '''.split() + + auth_type = 'SSO' + # flags: 1=Forward, 2=Allow, 4=Block, 8=Reverse and 16=Pending + list_types = \ + {'FL' : 'forward_list', + 'RL' : 'reverse_list', + 'AL' : 'allow_list', + 'BL' : 'block_list', + 'PL' : 'pending_list', + 'HL' : 'hidden_list', } + + status_to_code = \ + {'available' : 'NLN', + 'online' : 'NLN', + 'busy' : 'BSY', + 'idle' : 'IDL', + 'brb' : 'BRB', + 'away' : 'AWY', + 'phone' : 'PHN', + 'lunch' : 'LUN', + 'invisible' : 'HDN', + 'offline' : 'FLN'} + code_to_status = util.dictreverse(status_to_code) + status_to_code.update({ + 'be right back': 'BRB', + 'on the phone': 'PHN', + 'out to lunch': 'LUN', + }) + + client_id = msn.MSNClientID.IM_DEFAULT_CAPABILITIES + #client_id = 0 + require_connected = require_connected + + message_formatting = 'simple' + + @property + def SOCKTYPES(self): + scks = [] + if common.pref('msn.socket.use_direct', True) and not self.use_http_only: + scks.append(msn.MSNSocket) + if common.pref('msn.socket.use_http', True) or self.use_http_only: + scks.append(msn.MsnHttpSocket) + + return scks + + @classmethod + def email_hint(cls, contact): + return contact.name # the passport IS an email + + def __init__(self, username, password, user, + server, login_as = "invisible", remote_alias = None, + use_http_only = False, allow_unknown_contacts = False, **extra_args): + + protocol.__init__(self, username, password, user) + remote_alias = remote_alias or username + + if '@' not in self.username: + self.username = self.username + '@hotmail.com' + + self.use_http_only = use_http_only + self.__socktypes = self.SOCKTYPES[:] + self._conn_args = ((), {}) + + self.server, self.status = server, login_as + + self.allow_unknown_contacts = allow_unknown_contacts + + self.mail = 0 + self.groups_to_add = {} + self.buddies = msn.MSNBuddies.MSNBuddies(self) + self.m_buddies = msn.MSNBuddies.MSNBuddies(self) # mobile buddies + self.circle_buddies = msn.MSNBuddies.CircleBuddies(self) + self.self_buddy = self.get_buddy(self.username) + self.self_buddy.client_id = self.client_id + self.remote_alias = remote_alias + self.sockets = {} + self.icon_requests = [] + self.version = None + self.keep_alive_thread = util.ResetTimer(0, lambda:None) + self.keep_alive_thread.start() + self.rl_notify_user = False + + #init groups + self.root_group = Group('__Root__', self, None) + self.groups = {'__Root__' : self.root_group, None : self.root_group} + + self.disconnecting = False + + self.add_observer(self.state_change, 'state') + + self._old_status = None + + #init lists + self.forward_list, \ + self.reverse_list, \ + self.allow_list, \ + self.block_list, \ + self.pending_list, \ + self.hidden_list, \ + self.self_info = set(), set(), set(), set(), set(), set(), dict(), + + self.hub = user.getThreadsafeInstance() + + self._ns_class = msn.Notification + self._sb_class = None + self.ns = None + self.__authenticating = False + self._conversations = [] + + self._waiting_for_sb = [] + + self.waiting_for_auth = set() + self.status_message = '' + + self._requesting_icons = set() + self._pending_icon_requests = {} + + self.self_buddy.status = self.status + +# def init_p2p(self): +# self.P2PHandler = P2P.P2PHandler.P2PHandler(self) +# +# # Registers P2P application types +# import msn.P2P.P2PApplication +# import msn.P2P.P2PActivity +# import msn.P2P.P2PObjectTransfer +# import msn.P2P.P2PFileTransfer + +# bridges = [ +# P2P.Bridges.UdpBridge, +# P2P.Bridges.TcpBridge, +# ] +# +# for b in bridges: +# self._p2p_manager.register_bridge(b.bridge_name, b) + +# def get_bridge_names(self): +# return self._p2p_manager.get_bridge_names() +# +# def get_bridge_class(self, name): +# return self._p2p_manager.get_bridge_class(name) + + @property + def caps(self): + 'Returns the MSN capability list.' + + from common import caps + return [caps.INFO, caps.IM, caps.FILES, caps.EMAIL, caps.SMS, caps.BLOCKABLE, caps.VIDEO] + + connected = property(lambda self: self.state == self.Statuses.ONLINE) + + def __repr__(self): + return '<%s %s (%s)>' % (type(self).__name__, self.username, self.state) + + def buddy_dictionaries(self): + return [self.buddies, self.m_buddies, self.circle_buddies] + + @action(lambda self, *a, **k: + not any((self.disconnecting,)) + and self.state != self.Statuses.ONLINE) + def Connect(self, *a, **k): + """ + Connect() + + Begin the connection process for this protocol + """ + + assert self.ns is None + self._conn_args = (a, k) + + invisible = k.get('invisible', False) + + self.set_status('invisible' if invisible else 'available', on_ns = False) + + if not self.__socktypes: + log.info('No more socket types to try.') + self.on_conn_fail() + return + + self.__socktype = self.__socktypes.pop(0) + + self.ns = self._ns_class(self.__socktype, self.server, self.self_buddy) + self.ns.protocol = self + + log.info("self.ns is now: %r", self.ns) + + self.ns.bind('on_connect', self._on_ns_connect) + self.ns.bind('on_conn_error', self._on_ns_error) + self.ns.bind('on_require_auth', self._on_ns_needs_auth) + self.ns.bind('on_receive_version', self._store_new_version) + + if self.ns.needs_login_timer(): + self._cancel_login_check_timer() + + login_timeout = common.pref('msn.login.timeout', default = 60, type = int) + self._login_check_timer = util.ResetTimer(login_timeout, self._check_login) + self._login_check_timer.start() + log.info("Starting login timer for %r", self) + + self.change_state(self.Statuses.CONNECTING) + self.ns.connect() + + def change_state(self, newstate): + lct = getattr(self, '_login_check_timer', None) + if lct is not None: + lct.reset() + + return protocol.change_state(self, newstate) + + def _store_new_version(self, num): + self.version = 'MSNP%d' % num + log.info("Got target protocol version: %r", self.version) + + exec('from msn.p%(num)s import Notification, Switchboard' % locals()) + self._sb_class = Switchboard + self._ns_class = Notification + + def _cancel_login_check_timer(self): + timer, self._login_check_timer = getattr(self, '_login_check_timer', None), None + if timer is not None: + timer.cancel() + log.info("Stopping login timer for %r", self) + + def _check_login(self): + if self.state not in (self.Statuses.ONLINE, self.Statuses.OFFLINE): + self._on_ns_error(self.ns, Exception('MSN login took more than a minute')) + + def _check_offline(self): + if (self.ns is None or not self.ns.connected()) and not self._conversations: + self.set_offline(getattr(self, '_future_offline_reason', self.Reasons.NONE)) + protocol.Disconnect(self) + log.info('%r is offline', self) + self.disconnecting = False + else: + log.info('%r not offline yet: ns=%r, conversations=%r', self, self.ns, self._conversations) + if not self.disconnecting: + return + for conv in self._conversations: + conv.exit(force_close = True) + if self.ns is not None and self.ns.connected(): + self.ns.disconnect() + + def swap_ns(self): + self.ns.unbind('on_connect', self._on_ns_connect) + self.ns.unbind('on_conn_error', self._on_ns_error) + self.ns.unbind('on_require_auth', self._on_ns_needs_auth) + self.ns.unbind('on_receive_version', self._store_new_version) + + socket = self.ns.unhook_socket() + self._clean_up_ns() + + if self.disconnecting: + return + + xfrcount = getattr(self.ns, '_xfrcount', 0) + self.ns = self._ns_class(socket, buddy = self.self_buddy) + self.ns.protocol = self + log.info("self.ns is now: %r", self.ns) + self.ns._auth_type = self.auth_type + self.ns._username = self.username + self.ns._password = self.password + self.ns._xfrcount = xfrcount + + if not self.ns.needs_login_timer(): + self._cancel_login_check_timer() + + self.ns._sb_class = self._sb_class + self.ns.bind('on_require_auth', self._on_ns_needs_auth) + self.ns.bind('on_conn_error', self.on_conn_error) + self.ns.bind('disconnect', self._on_ns_disconnect) + self.ns.connect() + + @property + def P2PHandler(self): + return self.ns.P2PHandler + + def _on_ns_error(self, ns, error): + log.error('MSN Disconnecting. Received the following error from transport: %s', error) + self.on_conn_fail() + + def _clean_up_ns(self): + if self.ns is not None: + ns, self.ns = self.ns, None + if getattr(ns, 'protocol', None) is not None: + del ns.protocol + ns.clear() + ns.disconnect() + + def on_conn_fail(self): + self._clean_up_ns() + self.__authenticating = False + + if self.__socktypes: + conn_args, conn_kwargs = self._conn_args + self.Connect(*conn_args, **conn_kwargs) + return + + self.Disconnect(self.Reasons.CONN_FAIL) + + def on_conn_error(self, ns, error = None): + log.info('Got connection error: %r', error) + self.Disconnect(self.Reasons.CONN_LOST) + + def _on_ns_disconnect(self): + log.debug('NS disconnected') + self._clean_up_ns() + self._check_offline() + + def _on_ns_connect(self, socket): + self.__socktypes[:] = [] + self._conn_args = ((), {}) + self.ns.unbind('on_connect', self._on_ns_connect) + + def _on_ns_needs_auth(self, ver = None): + if ver is not None: + assert str(ver) in self.version + + self.change_state(self.Statuses.AUTHENTICATING) + + #self.ns.unbind('on_require_auth', self._on_ns_needs_auth) + self.ns.bind('on_auth_challenge', self.do_tweener_auth) + self.ns.authenticate(self.username, self.password, self.auth_type) + + @property + def appid(self): + return self.ns.client_chl_id + + @property + def appcode(self): + return self.ns.client_chl_code + + def get_token(self, domain): + return self.ns.get_token(domain) + + def set_token(self, domain, token): + return self.ns.set_token(domain, token) + + def do_tweener_auth(self, auth_type, twn_args): + self.ns.unbind('on_auth_challenge', self.do_tweener_auth) + if self.__authenticating: + return + self.__authenticating = True + + if not self.ns: + self.on_conn_fail() + return + + self.swap_ns() + + tweener = self.get_authorizer(auth_type) + log.info('authorizer is: %r', tweener) + tweener(self.username, self.password, twn_args, + success = self.finish_auth, error = self.auth_error) + + def get_authorizer(self, auth_type): + return self.ns.get_authorizer(auth_type) + + def finish_auth(self, *ticket): + log.info('Finishing authorization process') + + if self.ns: + if self.disconnecting and self.ns is None: + return + self.ns.bind('on_auth_success', self._on_ns_authorize) + self.ns.complete_auth(*ticket) + else: + self.on_conn_fail() + + def _on_ns_authorize(self): + self.ns.unbind('on_require_auth', self._on_ns_needs_auth) + self.ns.unbind('on_auth_success', self._on_ns_authorize) + + self._bind_ns_events() + + self.ns.init_p2p() + + self.change_state(self.Statuses.LOADING_CONTACT_LIST) + self.root_group.freeze() + self.ns._load_contact_list() + + def _bind_ns_events(self): + evt_table = ( + ('on_recv_profile', self.on_recv_profile), + ('on_rl_notify', self.got_rlnotify_behavior), + ('on_blist_privacy', self.got_blp), + ('recv_prop', self.set_prop), + ('recv_contact', self.on_contact_recv), + ('recv_status', self.on_set_status), + ('recv_clientid', self.on_set_client_id), + ('group_receive', self.on_recv_group), + ('on_contact_add', self.on_contact_add), + ('contact_online_initial', self.on_buddy_status), + ('contact_online', self.on_buddy_status), + ('contact_offline', self.on_buddy_status), + ('contact_alias', self.on_contact_alias_changed), + ('contact_remove', self.on_contact_remove), + ('contact_list_details', self.on_blist_info), + ('contact_id_recv', self.on_contact_id), + ('contact_status_msg', self.on_buddy_status_msg), + ('challenge', self.on_challenge), + ('challenge_success', self.on_challenge_success), + ('other_user', self.on_other_user), + ('connection_close', self.on_conn_close), + ('initial_mail', self.on_init_mail), + ('subsequent_mail', self.on_more_mail), + ('group_remove', self.on_group_remove), + ('group_add', self.on_group_add), + ('group_rename', self.on_group_name_changed), + ('ping_response', self.on_ping_response), + ('switchboard_invite', self.on_switchboard_invite), + ('switchboard_request', self.on_switchboard_request), + ('sb_request_error', self.on_switchboard_error), + ('recv_sms', self.on_recv_sms), + ('contact_icon_info', self.on_bicon_info), + ('contact_role_info', self.on_buddy_role_info), + ('contact_cid_recv', self.on_contact_cid), + ('contact_profile_update', self.get_profile_for_cid), + ('received_oims', self.on_received_oims), + ('soap_info', self.on_soap_info), + ('contact_btype', self.on_contact_type), + ('on_connect', self.on_connect), + ('fed_message', self.on_federated_msg), + ('buddy_authed', self.on_authorize_buddy), + ('needs_status_message', self.on_needs_status_message), + ('needs_self_buddy', self.on_needs_self_buddy), + ('on_circle_member_joined', self.on_circle_member_joined), + ('circle_roster_recv', self.on_circle_roster_recv), + ('circle_roster_remove', self.on_circle_roster_remove), + ) + + events = self.ns.events + bind = self.ns.bind + + for name, callback in evt_table: + if name in events: + bind(name, callback) + else: + log.warning('Can\'t bind event %s to %r', name, self.ns) + + def auth_error(self, e): + log.error('%s exception: %s', self, e) + + try: + raise e + except (urllib2.URLError, socketmodule.error): + reason = self.Reasons.CONN_FAIL + except util.xml_tag.SOAPException: + log.debug('SOAPException when trying to authenticate: %r', e.t._to_xml(pretty = False)) + reason = self.Reasons.BAD_PASSWORD + except Exception: + log.error("Failed to authenticate %r: %r", self, e) + reason = self.Reasons.BAD_PASSWORD + + self.Disconnect(reason) + return True + + def on_init_mail(self, newmail): + self.setnotifyif('mail', newmail) + + def on_more_mail(self, count): + self.setnotifyif('mail', self.mail + count) + + def on_conn_close(self): + log.info('Connection closed. Calling disconnect...') + if not self.disconnecting: + self.Disconnect(self.Reasons.CONN_LOST) + + def on_other_user(self): + log.error('Logged in from another location. Disconnecting...') + self.Disconnect(self.Reasons.OTHER_USER) + + def on_recv_profile(self, prof): + for k in prof: + self.self_buddy.info[k] = prof[k] + + self.ip = self.self_buddy.info.get('clientip', util.myip()) + self.port, = struct.unpack('H', struct.pack + ('!H', int(self.self_buddy.info.get ('clientport', self.server[-1])))) + + def got_rlnotify_behavior(self, value): + ''' + This is the GTC property. It specifies whether the user should be notified + of reverse-list additions, or if they should happen silently + ''' + self.rl_notify_user = value + + def got_blp(self, value): + if self.version > 'MSNP12': + self.on_connect() + + def set_blist_privacy(self, allow_unknowns): + self.ns.send_blp('AL' if allow_unknowns else 'BL') + + self.setnotify('allow_unknown_contacts', allow_unknowns) + self.account.allow_unknown_contacts, old = self.allow_unknown_contacts, self.account.allow_unknown_contacts + if old != self.account.allow_unknown_contacts: + self.account.update() + + @callsback + def request_sb(self, callback = None): + if self.ns is not None: + self._waiting_for_sb.append(callback) + self.ns.request_sb() + # Must return a value such that bool(v) == True but v itself cannot be True, due to callsback semantics + return 1 + else: + callback.error() + return False + + #@action(lambda self, *a, **k: not(self.state == self.Statuses.OFFLINE or self.disconnecting)) + def Disconnect(self, reason = None): + """ + Disconnect() + + Disconnect from all servers + """ + + if self.ns is not None: + if getattr(self.ns, 'protocol', None) is not None: + del self.ns.protocol + self.ns.disconnect() + else: + self._check_offline() + + if reason is None: + reason = self.offline_reason + elif isinstance(reason, Exception): + reason = self.Reasons.CONN_FAIL + + if not self.disconnecting: + log.critical('disconnecting') + self.disconnecting = True + self._future_offline_reason = reason + if self.keep_alive_thread is not None: + self.keep_alive_thread.cancel() + + if hasattr(self, 'slp_call_master'): + self.slp_call_master.cancel_all() + + if self._conversations: + for conv in self._conversations[:]: + conv.exit(force_close = True) + else: + self._check_offline() + + self._cancel_login_check_timer() + + def reconnect_ns(self, server, version): + pass + + def authenticate(self, *args): + self.change_state(self.Statuses.AUTHENTICATING) + self.do_tweener_auth(self.username, self.password, args, + success = self.ns.complete_auth, + error = self._auth_error) + + def _auth_error(self, errmsg): + + log.error('msn had an auth error: %r, %r' , type(errmsg), errmsg) + + self.logging_on = False + self.Disconnect(self.Reasons.BAD_PASSWORD) + self.hub.get_instance().on_error(errmsg) + raise Exception(errmsg) + + def on_authenticate(self): + self.change_state(self.Statuses.LOADING_CONTACT_LIST) + self.ns._load_contact_list() + + def on_recv_group(self, name, id): + log.debug('on_recv_group: name=%r, id=%r', name, id) + + if id in self.groups: + new_group = self.groups[id] + else: + new_group = Group(name, self, id) + + rglist = list(self.root_group) + if new_group not in rglist: + self.root_group.append(new_group) + + self.groups[id] = new_group + + num_groups = len(self.groups) - 1 + if hasattr(self, 'num_groups'): + log.debug('Got group %d/%d', num_groups, self.num_groups) + if num_groups == self.num_groups: + log.info('Got all groups!') + self._check_connected() + + def on_blist_info(self, num_buddies, num_groups): + log.info('Got buddy list information: %d buddies, %d groups', num_buddies, num_groups) + + self.num_buddies = num_buddies + self.num_groups = num_groups + + self._check_connected() + + def on_soap_info(self, name, typ, soap, role): + raise Exception + log.info('Got soap info for buddy: %r', (name, typ, soap, role)) + + if typ == 'Passport': + d = self.buddies + t = 1 + elif typ == 'Email': + d = self.buddies + t = 32 + elif typ == 'Phone': + d = self.m_buddies + t = 4 + + d[name].mships[role] = soap + d[name]._btype = t + + def has_buddy_on_list(self, buddy): + return buddy.name in set(x.name for x in self.forward_list) + + def on_contact_add(self, name, id, flags, groups, soap = None): + + buddy = self.on_contact_id(name, id) + + if soap is not None: + buddy.contactsoap = soap + + if isint(flags): + ltypes = self.apply_list_flags(flags, buddy) + else: + ltypes = self.apply_list_types(flags, buddy) + +# if buddy is self.self_buddy: +# log.error('Not putting self buddy in a group, that\'s silly') +# return buddy + + root_contact = msn.Contact(buddy, self.ROOT_ID) + + if 'FL' in ltypes: + if groups: + added = [] + + if root_contact in self.root_group: + self.root_group.remove(root_contact) + + for id in groups: + g = self.groups[id] + c = msn.Contact(buddy, id) + #if c not in g: + g.append(c) + added.append(g.name) + + log.info('%s is in groups: %r', buddy.name, added) + + else: + if buddy in self.forward_list and root_contact not in self.root_group: + self.root_group.append(root_contact) + + self.root_group.notify() + + buddy.notify() + return buddy + + def get_authorization_for(self, buddy): + bname = getattr(buddy, 'name', buddy) + buddy = self.get_buddy(bname) + if buddy not in self.waiting_for_auth: + buddy.pending_auth = False + self.waiting_for_auth.add(buddy) + self.hub.authorize_buddy(self, buddy, message = self.ns.get_auth_message_for(buddy)) + + def on_contact_recv(self, name, flags, groups, soap = None, id = None): + log.debug('on_contact_recv: name=%s, flags=%s, groups=%s', name, flags, groups) + + if self.state == self.Statuses.LOADING_CONTACT_LIST: + setattr(self, '_recieved_contacts', getattr(self, '_recieved_contacts', 0) + 1) + + try: + buddy = self.on_contact_add(name, name or id, flags, groups, soap) + except Exception: + log.error('Error processing buddy: name = %r, flags = %r, groups = %r, soap = %r, id = %r', + name, flags, groups, soap, id) + traceback.print_exc() + if hasattr(self, 'num_buddies'): + self.num_buddies -= 1 + buddy = None + self._check_connected() + + return buddy + + def on_contact_type(self, name, btype): + + import msn.AddressBook as MSNAB + buddies = {MSNAB.ClientType.PassportMember : self.buddies, + MSNAB.ClientType.PhoneMember : self.m_buddies, + MSNAB.ClientType.EmailMember : self.buddies}.get(MSNAB.ClientType(btype), None) + + if buddies is None: + return + + buddy = buddies[name] + buddy._btype = int(btype) + + def _check_connected(self): + num_buddies = getattr(self, '_recieved_contacts', 0) + + self._cancel_login_check_timer() + + if self.state != self.Statuses.ONLINE: + if not hasattr(self, 'num_buddies'): + return + log.debug('Got buddy %d/%d', num_buddies, self.num_buddies) + if num_buddies >= self.num_buddies: + log.info('Got all buddies!') + if (len(self.groups) - 1) >= self.num_groups: + + log.info('Got all buddies and groups.') + + if self.version <= 'MSNP12': + self.on_connect() + + def on_load_contact_list(self): + pass + + def on_set_status(self, newstatus): + sta = self.code_to_status[newstatus] + + if sta == 'online': + sta = 'available' + + for x in (self, self.self_buddy): + x.setnotifyif('status', sta) + + def on_set_client_id(self, client_id): + for x in (self, self.self_buddy): + x.setnotifyif('client_id', client_id) + + def on_buddy_status(self, name, nick, status, id): + log.debug('Got status change for %r (%r). newstatus=%r, id=%r', name, nick, status, id) + buddy = self.get_buddy(name) + if nick is None: + nick = buddy.alias + else: + self.on_contact_alias_changed(name, nick) + + log.debug('Got buddy %r for status change', buddy) + sta = self.code_to_status[status] + + if sta == 'online': + sta = 'available' + + buddy.status = sta + buddy.client_id = id + buddy.notify('status'); buddy.notify('client_id') + + def on_buddy_status_msg(self, bname, message): + log.info('Got Status Message for %s: %r', bname, message) + b = self.get_buddy(bname) + b.status_message = message + b.notify('status_message') + + def on_challenge(self, nonce): + self.ns.do_challenge(nonce) + + def on_challenge_success(self): + ''' + yay still connected + ''' + pass + + def on_contact_id(self, name, guid): + + log.info('Got ID for %r: %r', name, guid) + + if not name: + for buddy in (self.buddies.values() + self.m_buddies.values()): + if str(getattr(buddy, 'guid', None)) == str(guid): + return buddy + else: + raise ValueError("No buddy with %r was found and no name was supplied." % guid) + + if name.startswith('tel:'): + name = name[4:] + buddy = self.get_buddy(name) + + if not guid or guid == name: + return buddy + + try: + if not isinstance(guid, self.ns.cid_class): + _guid = self.ns.cid_class(guid) + else: + _guid = guid + buddy.guid = _guid + except (ValueError, AttributeError, TypeError), e: + log.info('%r is not a valid ID. (error was %r)', guid, e) + + return buddy + + def on_contact_cid(self, name, cid): + self.get_buddy(name).CID = cid + + def get_buddy_by_id(self, id_name): + if id_name not in self.buddies: + + possibles = filter(lambda x: (str(x.id) == str(id_name)) or (x.name == id_name), + self.buddies.values() + + self.m_buddies.values()) + + if len(possibles) > 1: + log.warning('Found multiple buddies with id %r', id_name) + + if possibles: + return possibles[0] + + if isinstance(id_name, basestring): + + if not util.is_email(id_name): + if id_name.startswith('tel:'): + return self.m_buddies[id_name[4:]] + elif id_name.startswith('fed:'): + raise Exception + else: + assert False, (type(id_name), id_name) + + else: + assert False, (type(id_name), id_name) + + + assert util.is_email(id_name), (type(id_name), id_name) + return self.buddies[id_name] + + def on_contact_remove(self, name, l_id, g_id): + + log.info('Contact has been removed: name=%s, lid=%s, gid=%s', name, l_id, g_id) + + buddy = self.get_buddy_by_id(name) + l_id = MSNAB.MSNList(l_id) + l = getattr(self, self.list_types.get(l_id.role)) + + if not g_id or g_id not in self.groups: + # Contact is being removed from block, allow, reverse, or pending list + + if buddy in l: + log.info('removing buddy %s from list %s', name, l_id) + l.remove(buddy) + + if l_id == MSNAB.MSNList.Forward: + root_contact = msn.Contact(buddy, self.ROOT_ID) + if root_contact in self.root_group: + self.root_group.remove(root_contact) + self.root_group.notify() + + buddy.notify() + return + + # Contact being removed from forward list -- need to deal with groups + + groups = [self.groups[g_id]] + + log.info('Removing %r from groups %r', buddy, groups) + + for group in groups: + contact = msn.Contact(buddy, group.id) + try: + group.remove(contact) + except ValueError, e: + log.info('%r wasn\'t in %r, ignoring. (exception was: %r)', buddy, group, e) + + + if not self.groups_containing_buddy(buddy) and l_id == 'FL': + log.info('%r has been removed from all groups. Removing from forward list.', buddy) + self.ns._remove_buddy(l_id, buddy, None, service = buddy.service) + + self.root_group.notify() + + def groups_containing_buddy(self, buddy): + groups = [] + + for group in self.groups.values(): + c = msn.Contact(buddy, group.id) + if c in group: + groups.append(group) + + return groups + + def on_group_remove(self, g_id): + group = self.groups.pop(g_id, None) + if group is not None: + self.root_group.remove(group) + + for contact in list(group): + if not self.groups_containing_buddy(contact.buddy): + if None in self.groups: + self.groups[self.ROOT_ID].append(msn.Contact(contact.buddy, self.ROOT_ID)) + + group[:] = [] + + self.root_group.notify() + + def on_group_add(self, name, id): + new_group = Group(name, self, id) + #new_group.add_observer(self.group_changed) + self.groups[id] = new_group + self.root_group.insert(0, new_group) + self.root_group.notify() + return new_group + + def on_contact_alias_changed(self, name, nick): + self.get_buddy(name).setnotifyif('remote_alias', nick) + + def on_group_name_changed(self, id, name): + g = self.groups[id] + g.name = name + self.root_group.notify() + + def on_ping_response(self, next = sentinel): + if next is sentinel or not next: + return + + next += 5 + log.debug('next ping in %d seconds', next) + self.keep_alive_thread.reset(next) + + self.clear_unknown_statuses() + + def on_switchboard_request(self, server, cookie): + log.info('Got response for switchboard request, creating %r', self._sb_class) + f = self._waiting_for_sb.pop(0) + f.success(self.make_sb(server = server, cookie = cookie)) + + def make_sb(self, server = None, sessionid = None, cookie = None): + if self.version < 'MSNP21': + socktype = self.__socktype + else: + socktype = self.ns + + sb = self._sb_class(socktype, server = server, sessionid = sessionid, cookie = cookie) + return sb + + def make_sb_adapter(self, to_invite = ()): + if self.version < 'MSNP21': + return msn.NSSBAdapter(self.ns, to_invite = to_invite) + else: + return self._sb_class(self.ns, to_invite = to_invite) + + def on_switchboard_error(self, emsg): + log.info('Received error for switchboard request: %r', emsg) + cb = self._waiting_for_sb.pop(0) + cb.error(emsg) + + def on_switchboard_invite(self, name, session = None, server = None, auth_type = None, cookie = None): + # verify user ('name') is OK to talk to + # find conversation + + # if not found: + + sb = self.make_sb(server = server, sessionid = session, cookie = cookie) + con = self.convo_for(name, sb) + if not con.connected(): + con.connect() + return + + def on_federated_msg(self, bname, msg): + self.convo_for(bname).fed_message(msg) + + @callsback + def set_status(self, status = None, callback = None, on_ns = True): + status = status or self.status + + if status == 'online' and status not in self.status_to_code: + status = 'available' + + if status not in self.code_to_status: + code = self.status_to_code.get(status, 'AWY') + else: + code = status + + log.info('setting status to %s (%s)', status, code) + + if self.status == 'invisible' and status != 'invisible': # If moving from invis to not invis, enable icons again + for buddy in self.buddies.values(): + buddy.icon_disabled = False + + self.status = self.code_to_status[code] + self.self_buddy._got_presence = True + + if on_ns: + self.ns._set_status(code, self.client_id, callback) + + +################################### + + + @callsback + def connect_socket(self, sock_id, server_tuple, callback = None): + """ + connect_socket(sock_id, server, connect_func, *args) + + Create a socket, store it as sockets[sock_id], connect it + to server, and call connect_func(*args) when it has connected. + """ + + if sock_id.startswith('sb'): + sock_id = server_tuple + sock_type = 'sb' + else: + sock_type = 'ns' + if sock_id not in self.sockets: + host, port = server_tuple + log.info('opening %r', sock_id) + self.sockets[sock_id] = self.__socktype(self, (host, port), callback = callback) + else: + log.fatal("something's broken! see MSNP.connect_socket") + sck = self.sockets[sock_id] + assert (sck.server == server_tuple), sock_id + + def close_socket(self, socket): + """ + This function is called when a socket is closed from the other end. + + If socket is in sockets, it is removed. + + If we not connected to any sockets after this, an alert is sent + to the GUI. + """ + log.debug('closing socket %r', socket) + for k in self.sockets.keys(): + if self.sockets[k] is socket: + socket.close_when_done() + del self.sockets[k] + + if (len(self.sockets) == 0 or (self.sockets.get('NS', None) in (socket, None))) and \ + (self.state != self.Statuses.OFFLINE) and \ + (not self.disconnecting): + log.debug('no sockets left, disconnecting') + + if self.state == self.Statuses.CONNECTING: + raisin = self.Reasons.CONN_FAIL + else: + raisin = self.Reasons.CONN_LOST + + self.Disconnect(raisin) + + #raise msn.GeneralException("%s was disconnected!" % self.username) + + def close_sock_id(self, sock_id): + if sock_id in self.sockets: + self.close_socket(self.sockets[sock_id]) +# log.warning('closing and deleting %s', sock_id) +# try: +# self.sockets[sock_id].close_when_done() +# del self.sockets[sock_id] +# except KeyError: +# if not self.state != self.state['Disconnecting']: raise + + def apply_list_flags(self, flags, buddy = None): + """ + Figure out the list flags for specified buddy (or self.last_buddy) + and put them in the appropriate lists. + """ + buddy = buddy or self.last_buddy + + # flags: 1=Forward, 2=Allow, 4=Block, 8=Reverse and 16=Pending + lists = [] + if flags & 1: + lists.append('FL') + if flags & 2: + lists.append('AL') + if flags & 4: + lists.append('BL') + if flags & 8: + lists.append('RL') + if flags & 16: + lists.append('PL') + buddy.pending_auth = True + + return self.apply_list_types(lists, buddy) + + def apply_list_types(self, types, buddy = None): + buddy = try_this(lambda: buddy or self.last_buddy, buddy) + if buddy is None: + return + + log.debug('Putting %r in the following lists: %r', buddy, types) + + for l in types: + #log.debug(' adding %r to %r', buddy, self.list_types[l]) + getattr(self, self.list_types[l]).add(buddy) + + #log.debug(' done') + + if self.state == self.Statuses.ONLINE: + if buddy in self.reverse_list and not (buddy in self.block_list or buddy in self.allow_list): + #log.debug("Buddy in reverse list but not block or allow. NOT Adding to pending.") + pass +# log.debug("Buddy in reverse list but not block or allow. Adding to pending.") +# self.pending_list.add(buddy) +# buddy.pending_auth = True + + if buddy in self.pending_list and (buddy in self.allow_list or buddy in self.block_list): + log.debug("Buddy was in pending list but was already in %s list. Not asking for auth.", + ('block' if buddy in self.block_list else 'allow')) + self.pending_list.discard(buddy) + buddy.pending_auth = False + + for buddy in self.pending_list: + #print 'not doing auth thingy', buddy + log.info('%r is in pending list (apply_list_types). Asking for authorization...', buddy) + self.get_authorization_for(buddy) + + return types + + def get_buddy_by_sms(self, sms): + is_sms = validate_sms(sms) + if not is_sms: + return None + + if sms in self.m_buddies: + return self.m_buddies[sms] + + for buddy in self.buddies.values(): + if buddy.sms == sms: + return buddy + + return self.m_buddies[sms] + + def get_buddy(self, name): + if isinstance(name, msn.MSNBuddy.MSNBuddy): + return name + + name = ''.join(name.split()).encode('utf-8') + + is_sms = validate_sms(name) + if name.startswith('tel:'): + name = name[4:] + + sms_buddy = self.get_buddy_by_sms(name) + if sms_buddy is not None: + return sms_buddy + + if is_circle_name(name): + buddy = self.circle_buddies.get(name, None) + ns_circle = self.ns.GetCircle(name) + if buddy is None: + buddy = self.circle_buddies[name] = msn.MSNBuddy.CircleBuddy.from_msnab(ns_circle, self) + else: + if ns_circle is not None: + buddy.update_from_msnab(ns_circle) + + return buddy + + if name.startswith('fed:'): + raise Exception + + try: + if not util.is_email(name): + raise AssertionError('Bad buddy name! %r' % name) + except AssertionError: + import traceback;traceback.print_exc() + return self.buddies[name] + + def on_buddy_role_info(self, name, l_ids, role_id): + + b = self.get_buddy(name) + + self.apply_list_types(l_ids, b) + + for role in l_ids: + b.role_ids[role] = role_id + + def get_list_flags(self, b): + + buddy = self.get_buddy(getattr(b, 'name', b)) + + l_flags = (b in self.forward_list) * 1 + l_flags |= (b in self.allow_list) * 2 + l_flags |= (b in self.block_list) * 4 + + # check if b on allow and block lists, if so, don't + # report as in block list + if l_flags & 6 >= 6: l_flags -= 4 + + return l_flags + + def on_connect(self): + log.info('on_connect called. setting buddy icon and requesting profiles') + if self.ns is not None: + self.ns.unbind('on_connect', self.on_connect) + + if self.state == self.Statuses.ONLINE: + return + + self.root_group.thaw() + self.change_state(self.Statuses.ONLINE) + + self.set_blist_privacy(self.allow_unknown_contacts) + + for buddy in self.buddies.values(): + self.get_profile(buddy) + +# if self.rl_notify_user: +# for buddy in self.pending_list: +# #print 'not doing auth thingy', buddy +# log.info('%r is in pending list (on_connect). Asking for authorization...', buddy) +# self.get_authorization_for(buddy) + + #for buddy in self.buddies.values(): + # buddy.icon_disabled = False + + log.info('Setting display name') + self.set_display_name(self.remote_alias) + log.info(' done setting display name') + + #self.clear_unknown_statuses() + self.set_status() + log.info('on_connect done') + + + def clear_unknown_statuses(self): + for buddy in (self.buddies.values() + self.m_buddies.values()): + if buddy.status != 'unknown': + continue + + changed = False + if not buddy._got_presence: + buddy.status_message = '' + changed = True + if buddy._status == 'unknown': + buddy._got_presence = True + buddy.status_message = '' + buddy.status = 'offline' + changed = True + + if changed: buddy.notify('status') + + log.info('Set all unknown buddies to offline') + + @callsback + def set_message_object(self, messageobj, callback = None): + if hasattr(self.ns, 'set_message_object') and getattr(messageobj, 'media', None) is not None: + log.info('MSN.set_status_object: setting CurrentMedia') + self.set_status(messageobj.status.lower()) + self.ns.set_message_object(messageobj, callback = callback) + else: + log.info('MSN.set_status_object: setting PSM') + return protocol.set_message_object(self, messageobj, callback = callback) + + @callsback + def set_message(self, message, status, format = None, callback = None): + self.status = status + self.status_message = message + + self.set_status(status, callback = callback) + self.set_status_message(message, callback = callback) + + def on_needs_status_message(self, callable): + callable(self.status_message) + + def on_needs_self_buddy(self, callable): + self.self_buddy._got_presence = True + callable(self.self_buddy) + + def set_invisible(self, invis = True): + if invis: + sta = 'HDN' + else: + sta = 'NLN' + self.set_status(sta) + + def stop_keepalive_thread(self): + kat, self.keep_alive_thread = getattr(self, 'keep_alive_thread', None), None + if kat is not None: + kat.stop() + + def send_keepalive(self): + ns = self.ns + if ns is not None and ns.connected(): + ns.send_png() + else: + log.error('NS is not connected but keepalive timer is still notifying') + + def state_change(self, obj, attr, old, new): + assert attr is 'state' + + log.debug('%r was %s', self, old) + + if self.state == self.Statuses.ONLINE: + self._cancel_login_check_timer() + self.stop_keepalive_thread() + self.keep_alive_thread = util.RepeatTimer(5, self.send_keepalive) + self.keep_alive_thread.start() + + if self.state == self.Statuses.OFFLINE: + self._cancel_login_check_timer() + self.stop_keepalive_thread() + + def set_prop(self, buddy, prp_type, val, args = ()): + log.info('Got %r for %r', prp_type, buddy) + bname = getattr(buddy, 'name', buddy) + buddy = self.get_buddy(bname) + attr = self.ns.props[prp_type.lower()] + + if isinstance(val, basestring): + val = val.decode('url').decode('fuzzy utf8') + + buddy.setnotifyif(attr, val) + + if attr == 'phone_mobile' and args: + args = list(args) + try: + enabled = int(args.pop(0)) + if buddy.allow_mobile is None: + buddy.allow_mobile = ('N', 'Y')[enabled] + except Exception: + return + + def buddy_for_cid(self, cid): + for buddy in self.buddies.values(): + if int(buddy.CID) == int(cid): + return buddy + + assert False, 'CID %d not found! My buddies are: %r' % (cid, self.buddies) + + def get_profile_for_cid(self, cid): + buddy = self.buddy_for_cid(cid) + self.get_profile(buddy) + + def set_profile(self, *a, **k): + pass + + def set_idle(self, since = 0): + if since: + self._old_status = self.status + self.set_status('idle') + else: + if self._old_status is not None: + self.set_status(self._old_status) + self._old_status = 'idle' + + def group_for_name(self, groupname): + res = None + for group in self.groups.values(): + if group.name == groupname: + res = group + + #log.debug('Group for name %s found %r', groupname, res) + return res + + def group_for(self, contact): + gid = contact.id[-1] + + if gid is None: + return None #self.root_group.name + + return self.groups[gid].name + + get_group = group_for_name + + def get_groups(self): + 'Returns a list of group names.' + + return [g.name for g in self.root_group if type(g) is Group] + + def get_ticket(self, key = None): + if key is None: + return self.ticket + return msn.util.fmt_to_dict('&', '=')(self.ticket)[key] + + @callsback + def join_chat(self, convo, room_name = None, server = None, callback = None): + if convo.type == 'sb': + return callback.success(convo) + + callback.error() + + @action() + def search_for_contact(self): + self.hub.launchurl('http://spaces.live.com/Default.aspx?page=Interests&mkt=en-us') + + @action() + def address_book(self): + self.hub.launchurl('http://mail.live.com/mail/ContactMainLight.aspx?n=') + + @action() + def mobile_settings(self): + self.hub.launchurl('http://mobile.live.com') + + @action() + def my_profile(self): + self.hub.launchurl('http://account.live.com') + + @action() + def save_contact_list(self): + pass + + @action() + def load_contact_list(self): + pass + + def contact_list_to_file(self): + xml_str = '' + messenger = util.xml_tag.tag('messenger') + messenger.service = util.xml_tag.tag('service', name = '.NET Messenger Service') + + for bname in self.buddies: + messenger.service.contactlist += util.xml_tag.tag('contact', bname) + + return xml_str + messenger._to_xml() + + def file_to_contact_list(self, f_obj): + s = f_obj.read() + t = util.xml_tag.tag(s) + + if isinstance(t.service, list): + messenger = filter(lambda x: x['name'] == '.NET Messenger Service', + t.service) + messenger = messenger[0] if messenger else None + elif t.service['name'] == '.NET Messenger Service': + messenger = t.service + else: + messenger = None + + if messenger is None: return [] + contacts = messenger.contactlist + + added_contacts = [] + for contact in contacts: + bname = str(contact) + b = self.get_buddy(bname) + # add them all but only if they're not already in the list + if b not in self.forward_list: + self.add_buddy(contact) + added_contacts.append(contact) + + + # let caller know which were added + return added_contacts + + def on_received_oims(self, oims = None): + + if oims is None: + oims = self.oims + + while oims: + oim = oims.pop(0) + b = self.get_buddy(oim.email) + conv = self.convo_for(b.name) + conv.received_message(b, unicode(oim.msg), offline = True, timestamp = oim.time, content_type = 'text/plain') + + def on_recv_sms(self, phone, message): + b = self.get_buddy(phone) + c = self.convo_for(b) + c.on_message_recv(b.name, message, sms = True) + + def on_bicon_info(self, name, msnobj): + + buddy = self.get_buddy(name) + if isinstance(msnobj, basestring): + buddy.icon_disabled = False + buddy.setnotifyif('icon_hash', msnobj) + else: + buddy.msn_obj = msnobj + + if msnobj is None: + buddy.icon_disabled = True + buddy.setnotifyif('icon_hash', '') + elif int(msnobj.type) == 3: + buddy.icon_disabled = False + buddy._icon_disabled_until = 0 + buddy.setnotifyif('icon_hash', msnobj.sha1d.decode('base64')) + + ### Features not supported by MSN + + def send_direct_IM_req(self, *args): + log.warning('MSN does not support send_direct_IM_req yet') + print '%s%s' % (util.get_func_name(), repr(args)) + + def send_buddy_list_request(self, *args): + log.warning('MSN does not support send_buddy_list_request yet') + print '%s%s' % (util.get_func_name(), repr(args)) + + ### End unsupported features + + + @callsback + def _get_default_p2p_transport(self, bname, callback = None): + return self.convo_for(bname, callback = callback) + +############################################################################################# +# +# Determine common signature for the following functions +# +############################################################################################# + + def chat_with(self, buddy): + ###TODO: Use conversation manager? + ''' + chat_with(buddy) + + Chat with a buddy. Ensures that a conversation exists that has buddy in it + (or at least, one that is capable of inviting buddy when appropriate). + + @param buddy: The C{buddy} to chat with + @type buddy: L{msn.Buddy} + ''' + + self.convo_for(buddy.name) + + @callsback + def convo_for(self, bname, sb = None, callback = None): + ''' + convo_for(bname) + + Returns the conversation for a buddy with name bname, creating it if necessary + + @param bname: name of the buddy to get a conversation for. + @type bname: basestring (passport) + + @rtype: L{msn.Conversation} + @returns: A conversation for the buddy + ''' + if sb is None: + return self._find_sb(bname, sb, callback) + else: + return self._create_conv(bname, sb, callback) + + def find_conv(self, bname): + is_circle = is_circle_name(bname) + buddy = self.get_buddy(bname) + for conv in self._conversations[:]: + if conv.ischat and not is_circle: + continue + if conv.ischat and is_circle and buddy.name == conv._chatbuddy: + return conv + + if buddy.name == conv._chat_target_name: + return conv + + if buddy.name == self.self_buddy.name: + if len(conv._clean_list()) == 1: + return conv + else: + continue + + if buddy in conv.room_list: + return conv + elif bname in conv._clean_list(): + return conv + + return None + + def _find_sb(self, bname, sb, callback): + log.info('Finding conversation for %s', bname) + bname = getattr(bname, 'name', bname) + buddy = self.get_buddy(bname) + + conv = self.find_conv(bname) + + if bname == self.self_buddy.name: + if conv is None: + return self._create_conv(bname, sb, callback) + else: + callback.success(conv) + return conv + + if conv is not None: + if conv.ischat: + callback.success(conv) + return conv + + if buddy in conv.room_list: + if sb is not None: + conv._set_switchboard(sb) + conv.connect(callback = callback) + callback.success(conv) + return conv + elif bname in conv._clean_list(): + log.info("%r already in %r's clean_list. disconnecting %r", bname, conv, conv) + self._conversations.remove(conv) + conv.Disconnect() + conv.exit() + conv = None + else: + conv = None + + return self._create_conv(bname, sb, callback) + + @callsback + def rejoin_chat(self, old_conversation, callback = None): + bud = old_conversation._chatbuddy + conf = self.convo_for(bud, callback = callback) + connect = getattr(conf, 'connect', None) + if connect is not None: + connect() + + @callsback + def make_chat_and_invite(self, buddies_to_invite, convo = None, room_name = None, server = None, notify = False, callback = None): + if convo is None: + buds = filter(lambda b: b != self.self_buddy, buddies_to_invite) + conf = self._create_conv_invite(buds, callback = callback) + conf.connect() # connect now explicitly + + if notify: + from common import profile + profile.on_entered_chat(conf) + else: + buds = filter(lambda b: b != self.self_buddy and b not in convo.room_list, buddies_to_invite) + for b in buds: + self.invite_to_chat(b, convo) + + + def get_conversation_class(self): + if self.version < 'MSNP21': + return msn.Conversation + else: + import msn.p21.MSNP21Conversation as p21 + return p21.MSNP21Conversation + + def _create_conv(self, bname, sb, connect_cb): + return self._create_conv_invite((bname,), sb = sb, success = connect_cb) + + @callsback + def _create_conv_invite(self, buddies, sb = None, callback = None): + buddies = tuple(getattr(b, 'name', b) for b in buddies) + log.info('_create_conv_invite: buddies=%r, connect_cb=%r', buddies, callback) + c = self.get_conversation_class()(self, switchboard = sb, to_invite = buddies) + c._connect_cb = callback + self.register_conv(c) + return c + + def register_conv(self, c): + if c not in self._conversations: + self._conversations.append(c) + + def unregister_conv(self, c): + log.info('removing %r from %r', c, self._conversations) + try: + while c in self._conversations: + self._conversations.remove(c) + except ValueError, e: + pass + + if not self._conversations and self.disconnecting: + self._check_offline() + + @callsback + def add_buddy(self, bname, g_id, pos = 0, service = None, callback = None): + ''' + @callsback + add_buddy(bname, gname, pos=0) + + Used to add a buddy to a group + + @param bname: passport of the buddy to add + @type bname: basestring (passport) + + @param gname: id of the group to add + @type gname: L{msn.GroupId} + + @param pos: Ignored. Present for interface adherence. (Not supported by protocol) + @type pos: int + @default pos: 0 + ''' + + log.debug('add_buddy(%r, %r, %r, %r, %r)', bname, g_id, pos, service, callback) + + if bname == self.self_buddy.name and self.version < "MSNP21": + log.error('Not adding self buddy, that\'s silly') + callback.success() + return + + group = self.groups[g_id] + buddy = self.get_buddy(bname) + + def setstatus(*a, **k): + if buddy.status == 'unknown': + buddy.setnotifyif('status', 'offline') + + real_success = callback.success + real_success += setstatus + callback.success = Delegate() + callback.success += lambda * a, **k: real_success(group) + + if buddy not in self.forward_list: + success = lambda * a, **k: self.add_buddy(bname, g_id, pos, service = service, callback = callback) + log.info('Adding %s to forward list and allow list', buddy) + self.ns._add_buddy_to_list(buddy.name, service = service, success = success, error = callback.error) + self.ns._add_buddy('AL', buddy.name, buddy.id, None, service = service) + return + + #check if they're in the group already + if msn.Contact(buddy, group.id) in group: + print 'buddy in group already!' + return callback.success() + + if group.id == None: + g_id = None + + log.info('Adding %s to group %r', buddy, g_id) + + self.ns._add_buddy_to_group(buddy.name, buddy.id, g_id, service = service or buddy.type, callback = callback) + +# def on_add_buddy(self, buddy, group): +# ''' +# on_add_buddy(buddy, group) +# +# Called when a buddy is successfully added to a group +# +# @param buddy: Buddy object added to group +# @type buddy: L{msn.Buddy} +# +# @param group: Group object buddy was added to +# @type group: L{msn.Group} +# ''' +# pass + + @callsback + def remove_buddy(self, buddy_id, callback = None): + ''' + @callsback + remove_buddy(contact, group) + + Remove contact from group + + @param contact: the contact to remove from group + @type contact: L{msn.Contact} + + @param group: the group to remove contact from + @type group: L{msn.Group} + ''' + + bname, gid = buddy_id + + if bname == self.self_buddy.name and self.version < 'MSNP21': + log.error("Not going to try to remove self buddy, that's silly") + callback.success() + return + + if gid == None: + gid = None + + buddy = self.get_buddy(bname) + bid = buddy.id + + if gid is None: + return self.ns._remove_buddy('FL', buddy, gid, service = buddy.service) + + # Ugh, fake group. This is really just a display hack so pretend we actually succeeded and just take it out. + if gid == 'Root': + g = self.get_group(gid) + b = get([_b for _b in g if _b.id == buddy_id], 0, None) + if b is not None: + print 'removing %r from fake group' % (b,) + g.remove(b) + self.root_group.notify() + + buddy = b.buddy + if not self.groups_containing_buddy(buddy): + return self.ns._remove_buddy('FL', buddy, None, service = buddy.service, callback = callback) + else: + return callback.success() + + return self.ns._remove_buddy_from_group(bname, bid, gid, service = buddy.service, callback = callback) + + def on_remove_buddy(self, contact, group): + ''' + on_remove_buddy(contact, group) + + contact has been removed from group + + @param contact: the contact that was removed from group + @type contact: L{msn.Contact} + + @param group: the group contact was removed from + @type group: L{msn.Group} + ''' + pass + + @callsback + def authorize_buddy(self, bname, authorize = True, username_added = None, callback = None): + ''' + @callsback + authorize_buddy(bname, authorize=True) + + Authorize a buddy (or block them), removing them from the pending list. + + @param bname: name of the buddy to authorize + @type bname: basestring (passport) + + @param authorize: authorize them? + @type authorize: bool + @default authorize: True + ''' + + buddy = self.get_buddy(bname) + self.ns._authorize_buddy(buddy, authorize, callback = callback) + + def on_authorize_buddy(self, buddy, authed): + ''' + on_authorize_buddy(buddy, authed) + + Called when a buddy is authorized (or de-authorized) + + @param buddy: buddy who was (de)authorized + @type buddy: L{msn.Buddy} + + @param authed: True if they were authorized, False otherwise + @type authed: bool + ''' + self.waiting_for_auth.discard(buddy) + + @callsback + def block_buddy(self, buddy, block = True, callback = None): + ''' + @callsback + block_buddy(buddy, block=True) + + Block buddy if block is True else unblock + + @param buddy: the buddy to block/unblock + @type buddy: L{msn.Buddy} + + @param block: Block them? + @type block: bool + @default block: True + ''' + if block: return self.ns._block_buddy (buddy, callback = callback) + else: return self.ns._unblock_buddy(buddy, callback = callback) + + @callsback + def unblock_buddy(self, buddy, callback = None): + ''' + @callsback + unblock_buddy(buddy) + + Unblock buddy. Equivalent to block_buddy(buddy, False) + + @param buddy: the buddy to unblock + @type buddy: msn.Buddy + ''' + return self.block_buddy(buddy, False, callback = callback) + + def on_block_buddy(self, bid, blocked): + ''' + on_block_buddy(buddy, blocked) + + Called when a buddy is blocked or un-blocked + + @param buddy: buddy who was (un-)blocked + @type buddy: L{msn.Buddy + + @param blocked: True if they were blocked, False otherwise + @type blocked: bool + ''' + self.get_buddy(bid).notify('blocked', not blocked, blocked) + + @callsback + def move_buddy(self, contact, to_groupname, from_groupname = None, pos = 0, callback = None): + ''' + @callsback + move_buddy(contact, to_groupname, from_groupid=None, pos=0) + + Move a buddy to group with name to_groupname, creating if necessary. The buddy + is moved from group with id from_groupid (if it is provided), else the root group + (or is added if buddy is not in root group and from_groupid is not provided) + + @param contact: Contact to be moved + @type contact: L{msn.Contact} + + @param to_groupname: Name of the group to move contact to + @type to_groupname: basestring + + @param from_groupid: GroupId of the group the contact is moving from + @type from_groupid: L{msn.GroupId} + + @param pos: Ignored. Present for interface adherence. (Not supported by protocol) + @type pos: int + @default pos: 0 + ''' + + togroup = self.get_group(to_groupname) + fromgroup = self.get_group(from_groupname) + + buddy = self.get_buddy(contact.name) + + if buddy.name == self.self_buddy.name and self.version < 'MSNP21': + log.error("Not going to try to move self buddy, that's silly") + callback.success() + return + + if fromgroup and ((fromgroup is not self.root_group) or from_groupname is not None): + def success(*a, **k): + self.remove_buddy(contact.id, callback = callback) + else: + success = callback.success + + self.add_buddy(buddy.name, togroup.id, + success = success, + error = callback.error) + + @callsback + def set_display_name(self, new_alias, callback = None): + ''' + @callsback + @set_display_name(new_alias) + + Set the friendly name other clients will see. + + @param new_alias: the new alias to set + @type new_alias: basestring + ''' + return self.ns._set_display_name(new_alias or self.username, callback) + + def on_set_display_name(self, alias): + ''' + on_set_display_name(new_alias) + + Called when display name has been set. + + @param alias: the C{alias} that was set + @type alias: basestring + ''' + + log.info('Got response for display name set') + + @callsback + def set_remote_alias(self, buddy, new_alias, callback = None): + ''' + @callsback + set_remote_alias(buddy, new_alias) + + Set the remote alias of a buddy. + + @param buddy: The C{buddy} whose alias to set + @type buddy: L{msn.Buddy} + + @param new_alias: Alias to set for C{buddy} + @type new_alias: basestring + ''' + return self.ns._set_remote_alias(buddy, new_alias, callback) + + def on_set_remote_alias(self, buddy, alias): + ''' + on_set_remote_alias(buddy, alias) + + Called when the remote C{alias} of C{buddy} has been set. + + @param buddy: The buddy whose alias was set + @type buddy; L{msn.Buddy} + + @param alias: The alias that was set + @type alias: basestring + ''' + pass + + @callsback + def add_group(self, gname, callback = None): + ''' + @callsback + add_group(gname, callback) + + Add a group of name C{gname} to the buddy list + + @param gname: name of group to add + @type gname: basestring + ''' +# self.groups_to_add[gname] = callback.success +# callback.success = lambda group: (log.debug('received response for adding group %r: %r', gname, group) + return self.ns._add_group(gname, callback = callback) + +# This is unused currently +# def on_add_group(self, group): +# ''' +# on_add_group(group) +# +# Called when a group is added to the buddylist +# +# @param group: The C{group} object that was added +# @type group: L{msn.Group} +# +# ''' +# f = self.groups_to_add.pop(group.name, lambda g:None) +# f(group) + + @callsback + def remove_group(self, group_id, callback = None): + ''' + @callsback + remove_group(group) + + Remove group from the buddylist, pushing any buddies in it to the root group + + @param group: The C{group} to be removed + @type group: L{msn.Group} + ''' + + + from util import CallCounter + from util.callbacks import Callback + + group = self.groups[group_id] + + if group_id in ('Root', None): + log.info('It\'s a fake root group! Removing all buddies from it and then wiping it out.') + for contact in list(group): + if len(self.groups_containing_buddy(contact.buddy)) == 1: + self.remove_buddy((contact.buddy.name, None)) + + group[:] = [] + self.groups[None].remove(group) + self.groups.pop(group_id) + return callback.success() + + log.info('Going to remove group %r with id %r', group, group_id) + + if not len(group): + log.info(' that group is already empty. Removing....') + return self.ns._remove_group(group_id, callback = callback) + + log.info(' that group has %d things in it, going to make a call counter and remove all of them seperately', len(group)) + cc = CallCounter(len(group), lambda * a, **k: self.ns._remove_group(group_id, callback = callback)) + + cb = Callback(success = (lambda * a, **k: cc()), error = callback.error) + + for contact in list(group): + __, gid = contact.id + bid = str(contact.buddy.guid) + name = contact.buddy.name + log.info(' Removing buddy %r (name=%r, id=%r) from group %r (id=%r)', contact.buddy, name, bid, group, group_id) + if gid is None: + self.ns._remove_buddy('FL', contact.buddy, None, service = contact.buddy.service, callback = cb) + else: + self.ns._remove_buddy_from_group(name, bid, gid, service = contact.buddy.service, callback = cb) + + def on_remove_group(self, group): + ''' + @callsback + on_remove_group(group) + + Called when a group is removed from the buddylist + + @param group: The C{group} that was removed + @type group: L{msn.Group} + ''' + self.groups.pop(group.id, None) + if group in self.root_group: + self.root_group.remove(group) + self.root_group.notify() + + @callsback + def set_status_message(self, message, callback = None): + ''' + @callsback + set_status_message(message) + + Sets status message to C{message} + + @param message: Status message to set. + @type message: basestring + ''' + return self.ns._set_status_message(message, callback = callback) + + def on_set_status_message(self, message): + ''' + on_set_status_messaage(message) + + Called when status message is successfully set + + @param message: the message that has been set + @type message: basestring + ''' + pass + + @callsback + def get_profile(self, buddy, callback = None): + ''' + @callsback + get_profile(buddy) + + Retrieve buddy's profile + + @param buddy: The buddy whose profile to get + @type buddy: L{msn.Buddy} + ''' + return self.ns._get_profile(buddy, callback) + + def on_get_profile(self, buddy, profile): + ''' + on_get_profile(buddy) + + Called when a buddy's profile is retrieved + + @param buddy: The buddy whose profile was retrieved + @type buddy: L{msn.Buddy} + + @param profile: The profile that was retrieved + @type profile: The raw data that was received + ''' + pass + + @callsback + def rename_group(self, groupid, name, callback = None): + ''' + @callsback + rename_group(group, name) + + Change the name of a group + + @param group: Group to be renamed + @type group: L{msn.Group} + + @param name: new name of the group + @type name: basestring + ''' + + return self.ns._rename_group(groupid, name, callback = callback) + + def on_rename_group(self, groupid, name): + ''' + on_rename_group(group, name) + + Called when a group has been renamed + + @param group: Group that was renamed + @type group: L{msn.Group} + + @param name: The current name of the group + @type name: basestring + ''' + self.groups[groupid].setnotifyif('name', name) + + @callsback + def set_buddy_icon(self, icon = None, callback = None): + ''' + @callsback + set_buddy_icon(icon=None) + + Sets the client's buddy icon to icon + If icon is None, there will be an attempt to retreive the cached icon + for self_buddy from disk. If it is not found, then the icon is cleared. + + This function resizes and re-formats the icon to a 96x96 png, as per + MSN "spec". It will be saved to disk temporarily for this operation. + + @param icon: The icon to set + @type icon: NoneType or string (binary data) + @default icon: None + ''' + + import os.path + import io + log.info('setting buddy icon: %d bytes', len(icon or '')) + pth = icon_path_for(self.self_buddy) + icon_data = icon + if icon_data is None: + if os.path.exists(pth): + icon_file = open(pth, 'rb') + else: + icon_file = None + + else: + icon_file = io.BytesIO(icon_data) + + if icon_file is None: + self.icon_obj = None + icon_data = None + + if (icon_file, icon_data) != (None, None): # make sure both aren't None + + if icon_file is not None: + import wx + icon_img = wx.ImageFromString(icon_file.read()) + if not icon_img.IsOk(): return + + if (icon_img.Size.x, icon_img.Size.y) > (96, 96): + icon_img.Rescale(96, 96, wx.IMAGE_QUALITY_HIGH) + if not icon_img.IsOk(): return + + if not os.path.exists(os.path.split(pth)[0]): + os.makedirs(os.path.split(pth)[0]) + temp_fn = os.path.join(os.path.split(pth)[0], 'temp_resize') + icon_img.SaveFile(temp_fn, wx.BITMAP_TYPE_PNG) + + with open(temp_fn, 'rb') as f: + icon_data = f.read() + + os.remove(temp_fn) + + if icon_data is not None: + hash = hashlib.sha1(icon_data).digest() + self.self_buddy.cache_icon(icon_data, hash) + + sta = self.status_to_code.get(self.status, 'AWY') + cli = self.client_id + return self.ns._set_buddy_icon(sta, cli, icon_data, callback) + + def on_set_buddy_icon(self, icon): + ''' + on_set_buddy_icon(data) + + Called when the buddy icon has been set + + @param data: The image data the icon has been set to + @type data: string (binary data) + ''' + pass + + @callsback + def get_buddy_icon(self, name, callback = None): + ''' + @callsback + get_buddy_icon(name) + + Get the buddy icon for buddy with name C{name} + + @param name: The name of the buddy whose icon to get + @type name: string (passport) + ''' + buddy = self.get_buddy(name) + + if buddy is self.self_buddy: + log.debug("Not requesting self-buddy icon") + return + + if not buddy.online: + callback.error() + log.debug("Not requesting offline buddy icon") + return + + if getattr(buddy, 'icon_disabled', False) and getattr(buddy, '_icon_disabled_until', 0xffffffff) > time.time(): + callback.error() + log.debug("Not requesting disabled buddy icon") + return + + bname = buddy.name + + log.info('Adding %r to pending icon requests', bname) + self._pending_icon_requests[bname] = callback + self._process_icon_request() + + def _process_icon_request(self): + if len(self._requesting_icons) >= common.pref('msn.max_icon_requests', type = int, default = 2): + log.info('Too many active buddy icon requests') + return + + try: + bname, callback = self._pending_icon_requests.popitem() + except KeyError: + log.info('No pending buddy icon requests') + return + + buddy = self.get_buddy(bname) + + if bname in self._requesting_icons: + log.info('already requesting icon for %r, trying then next one.', buddy) + return self._process_icon_request() + + def _request_resolved(): + self._requesting_icons.discard(bname) + self._pending_icon_requests.pop(bname, None) + self._process_icon_request() + + if buddy._getting_image: + cancel_get('timeout') + + def on_icon_receive(objtrans, data): + if data is not None: + buddy.cache_icon(data, hashlib.sha1(data).digest()) + log.info('Got buddy icon for %r', buddy) + + callback.success += on_icon_receive + + def cancel_get(objtrans = None, by_who = None): + log.info('Failed to get buddy icon for %r: transfer object: %r', buddy, objtrans) + if not buddy.icon_disabled: + buddy.icon_disabled = True + # Don't try again for 10 minutes + buddy._icon_disabled_until = time.time() + (10 * 60) + buddy.icon_bitmap = None + buddy._cached_hash = None + buddy._getting_image = False + + #_request_resolved() + + callback.error += cancel_get + failsafe_timer = util.Timer(60, _request_resolved) + failsafe_timer.start() + + self._requesting_icons.add(bname) + log.info('Requesting icon for %r', buddy) + + self.P2PHandler.RequestMsnObject(getattr(buddy, 'contact', buddy), buddy.msn_obj, callback = callback) + + + def on_get_buddy_icon(self, buddy, icon): + ''' + on_get_buddy_icon(buddy, icon) + + @param buddy: The buddy object whose icon we got + @type buddy: L{msn.Buddy} + + @param icon: The icon data we found for buddy + @type icon: string (binary data) + ''' + pass + + @callsback + def send_file(self, buddy, fileinfo, callback = None): + ''' + @callsback + send_file(buddy, filepath) + + Begins a file transfer session with buddy to send file located at + filepath to them. + + @param buddy: The buddy to send the file to + @type buddy: L{msn.Buddy} + + @param filepath: Absolute path to the file + @type filepath: string (filepath) + ''' + + bname = get(buddy, 'name', buddy) + buddy = self.get_buddy(bname) + if buddy == self.self_buddy: + callback.error() + + return self.P2PHandler.SendFile(buddy.contact, fileinfo.path, fileinfo.obj) + + def on_send_file(self, buddy, filepath): + ''' + on_send_file(buddy, filepath) + + @param buddy: The buddy the file was sent to + @type buddy: L{msn.Buddy} + + @param filepath: the local address of the file + @type filepath: string (filepath) + ''' + pass + + @callsback + def send_sms(self, phone, message, callback = None): + ''' + @callsback + send_sma(phone, message) + + @param phone: The phone number to send the message to + @type phone: string (Phone number - all digits, may start with '+') + @param message: The message to send + @type message: string (may have length limitations) + ''' + #TODO: See revision 6350 for reference + +# print 'Totally not sending sms to %s (message was %s)' % (phone, message) +# return + + self.ns._send_sms(phone, message, callback) + +# for convo in self.conversations: +# if convo.type == 'sms' or phone in (convo.buddy.name, convo.buddy.phone_mobile): +# convo.buddy_says(convo.buddy, message) +# break +# else: +# #somehow we sent an sms without a conversation?! +# pass + + def on_send_sms(self, phone, message): + ''' + on_send_sms(phone, message) + + @param phone: Phone number message was sent to + @type phone: string + @param message: The message that was sent + @type message: string + ''' + pass + + @callsback + def add_to_block(self, buddy, callback = None): + if isinstance(buddy, basestring): + buddy = self.get_buddy(buddy) + + self.ns.add_to_block(buddy, callback = callback) + + @callsback + def rem_from_block(self, buddy, callback = None): + if isinstance(buddy, basestring): + buddy = self.get_buddy(buddy) + + self.ns.rem_from_block(buddy, callback = callback) + + @callsback + def add_to_allow(self, buddy, callback = None): + if isinstance(buddy, basestring): + buddy = self.get_buddy(buddy) + + self.ns.add_to_allow(buddy, callback = callback) + + @callsback + def rem_from_allow(self, buddy, callback = None): + if isinstance(buddy, basestring): + buddy = self.get_buddy(buddy) + + self.ns.rem_from_allow(buddy, callback = callback) + + def add_new_buddy(self, buddyname, groupname, service = None, alias = None): + if service == 'yahoo' and self.version < 'MSNP14': + return False + + if service == 'yahoo' and not '@yahoo' in buddyname: + buddyname = buddyname + '@yahoo.com' + return protocol.add_new_buddy(self, buddyname, groupname, service, alias) + + def get_local_sockname(self): + return self.ns.get_local_sockname() + + def allow_message(self, buddy, mobj): + super = protocol.allow_message(self, buddy, mobj) + if super: # Don't want to catch None or False here + return super + + if buddy is None: + return True + + if buddy.blocked: + return False + + if super is None: + return True + + def get_contact_info(self, name): + contact_list = getattr(self.ns, 'contact_list', None) + if contact_list is None: + return self.get_buddy(name) + else: + return contact_list.GetContact(name) + + def get_machine_guid(self): + return self.ns.get_machine_guid() + + def on_circle_member_joined(self, circle_id, buddy_name): + members = self.circle_buddies[circle_id].Members + if buddy_name not in members: + members.append(buddy_name) + + log.info("Got new member for circle: %r in %r", buddy_name, circle_id) + + def on_circle_roster_remove(self, circle_id, ctype, name): + circle = self.circle_buddies[circle_id] + + if name in circle.Members: + circle.Members.remove(name) + if name in circle.Pending: + circle.Pending.remove(name) + + convo = self.find_conv(circle_id) + + bname = name.split(':', 1)[-1] + + if convo is not None: + convo.invite_failure(bname) + convo.on_buddy_leave(bname) + + def on_circle_roster_recv(self, circle_id, ctype, names, pending_names, nonpending_names, full): + circle = self.circle_buddies[circle_id] + if full: + circle.Members = names + circle.Pending = pending_names + else: + circle.Members = list(set(circle.Members) | set(names)) + circle.Pending = list(set(circle.Pending) | set(pending_names)) + + conv = self.find_conv(circle_id) + for name in nonpending_names: + if name in circle.Pending: + circle.Pending.remove(name) + + if conv is not None: + bnames_in_room = [x.name for x in conv.room_list] + log.debug("Names in room: %r / circle names: %r", bnames_in_room, circle.buddy_names) + for name in circle.buddy_names: + if name not in bnames_in_room: + conv.buddy_join(name) + + my_id = '1:%s' % self.self_buddy.name + + from common import netcall + def on_yes(): + netcall(lambda: (common.profile.on_entered_chat + (convo = self.accept_circle_invite(circle_id = circle_id, ctype = ctype)))) + + def on_no(): + netcall(lambda: self.reject_circle_invite(circle_id = circle_id, ctype = ctype)) + + log.debug("Got names for circle %r roster: %r", circle_id, names) + if my_id in pending_names and full and len(names) > 1: + # on chat invite + common.profile.on_chat_invite(protocol = self, + buddy = None, + message = None, + room_name = circle_id, + on_yes = on_yes, + on_no = on_no) + + elif my_id in pending_names and full and len(names) == 1: + # This is the result from our room creation. "accept" it + on_yes() + + def accept_circle_invite(self, circle_id, ctype): + self.ns.JoinCircleConversation(circle_id) + return self.convo_for(circle_id) + + def reject_circle_invite(self, circle_id, ctype): + self.ns.leave_temp_circle(circle_id) + + @action(lambda self, *a, **k: (self.state == self.Statuses.ONLINE) or None) + def CreateCircle(self): + hooks.notify("digsby.msn.circle_create_prompt", success = self.DoCreateCircle) + + def DoCreateCircle(self, name): + self.ns.CreateCircle(name) + + def LeaveCircle(self, circle_id_pair): + circleId, _gid = circle_id_pair + circle_contact = self.ns.GetCircle(circleId) + circle_buddy = self.circle_buddies.get(circle_contact.account) + + abid = circle_contact.abid + + admins = circle_buddy.get_role_names('Admin') + if len(admins) == 1 and self.self_buddy.name in admins: + url_cid = ''.join(abid.split('-'))[-16:] + self.hub.launchurl('http://cid-%s.groups.live.com/options/deletegroup/' % url_cid) + else: + self.ns.LeaveCircle(abid) + + def appear_offline_to(self, name): + self.ns.appear_offline_to(name) + + def appear_online_to(self, name): + self.ns.appear_online_to(name) + + def OnInvitationReceived(self, session): + import msn.P2P.P2PFileTransfer as FT + activity = session.App + log.info("OnInvitationReceived(session = %r)", session) + if activity.EufGuid == FT.FileTransfer.EufGuid: + self.hub.on_file_request(self, activity) diff --git a/digsby/src/msn/MSNBuddies.py b/digsby/src/msn/MSNBuddies.py new file mode 100644 index 0000000..8a679ad --- /dev/null +++ b/digsby/src/msn/MSNBuddies.py @@ -0,0 +1,72 @@ +from MSNBuddy import MSNBuddy, CircleBuddy +from util.observe import ObservableDict + +from common.sms import * + +import util +import util.primitives.funcs as funcs +from logging import getLogger +log = getLogger('msn.buddiesdict') + +class MSNBuddies(ObservableDict): + DefaultClass = MSNBuddy + ''' + A custom dictionary that will always return an MSNBuddy, even if it + did not exist when it was asked for. + ''' + def __init__(self, protocol): + ''' + MSNBuddies(msnobj) + + Create a new MSNBuddies dictionary with msnobj as the owner of all + buddies that will be in this dictionary + + @param protocol this dictionaries owner + ''' + ObservableDict.__init__(self) + self.protocol = protocol + + def __getitem__(self, buddy): + ''' + This is where the magic happens -- if the buddy does not exist, + then a new 'blank' buddy will be returned with only the name and + protocol set. + + @param buddy the name to use for the buddy + ''' + if not buddy: raise NameError + + if (util.is_email(buddy) and self in (self.protocol.buddies, self.protocol.circle_buddies)): + try: + return dict.__getitem__(self, buddy) + except KeyError: + return self.setdefault(str(buddy), + self.DefaultClass(name=buddy, msn=self.protocol)) + else: + is_sms = validate_sms(buddy) + is_int = funcs.isint(buddy) + + if (is_sms or is_int) and self is self.protocol.m_buddies: + try: + return dict.__getitem__(self, buddy) + except KeyError: + return dict.setdefault(self, str(buddy), + self.DefaultClass(name=buddy, msn=self.protocol)) + + log.critical('Unknown buddy was requested: %r, %r', type(buddy), buddy) + raise KeyError(buddy) + + def __delitem__(self, buddy): + ''' + Delete an item from this dictionary + ''' + return dict.__delitem__(self, str(buddy)) + + def __contains__(self, buddy): + ''' + Check if a buddy is in the dictionary + ''' + return dict.__contains__(self, str(buddy)) + +class CircleBuddies(MSNBuddies): + DefaultClass = CircleBuddy diff --git a/digsby/src/msn/MSNBuddy.py b/digsby/src/msn/MSNBuddy.py new file mode 100644 index 0000000..7d8e2cc --- /dev/null +++ b/digsby/src/msn/MSNBuddy.py @@ -0,0 +1,1346 @@ +import common +import contacts +import util.threads as threads +from util import Storage, to_storage, dyn_dispatch, odict, threaded +from util.primitives.funcs import get +from util.xml_tag import tag +from util.cacheable import urlcacheopen, cproperty +from util.observe import ObservableProperty +from common.sms import validate_sms, normalize_sms +from util.callbacks import callsback +from MSNUtil import url_decode +import time, datetime +import logging +log = logging.getLogger('msn.buddy') + +yesterday = lambda: datetime.datetime.today() - datetime.timedelta(1) + +import sys; thismod = sys.modules[__name__]; del sys +try: + _ +except: + _ = lambda s:s + + +statuses=Storage( + brb=_('Be Right Back'), + phone=_('On the Phone'), + lunch=_('Out to Lunch') +) + +class MSNBuddy(common.buddy): + ''' + MSNBuddy class, inherits from Buddy + + An abstract representation of an MSNBuddy + ''' + + __slots__ = \ + """ + status_message + msn_obj + _idle_start + pending_auth + phone_home + phone_work + phone_mobile + allow_mobile + enable_wireless + remote_alias + has_blog + client_id + role_ids + get_profile + _space + membersoap + contactsoap + """.split() + + def __init__(self, msn, name=None): + ''' + MSNBuddy(msn, name=None) + + Create a new MSNBuddy object with an MSN 'owner' object and + (optionally) a name. MSNBuddies default to not blocked, offline, + not mobile, and idle since the start of the UNIX epoch. + + @param msn: + @param name: + ''' + self._status = 'unknown' + self._status_message = '' + self._got_presence = False + + self.phone_home = \ + self.phone_work = \ + self.phone_mobile = \ + self.allow_mobile = \ + self.enable_wireless = \ + self.remote_alias = \ + self.has_blog = None + + self.msn_obj = None + self._idle_start = 0 + self.info = {} + self.role_ids = {} + self.pending_auth = False + + fixedname = ''.join(url_decode(name).split()) + common.buddy.__init__(self, fixedname, msn) + + if self is self.protocol.self_buddy: + from common import profile + profile.account_manager.buddywatcher.unregister(self) + + #assert is_email(fixedname), fixedname + self.CID = 0 + + self.space = None + if self._space: + try: + self.update_contact_card(tag(self._space)) + except: + self._space = None + + self.mships = {} + self.contactsoap = None + self.membersoap = None + + self._btype = 1 + + #self.get_profile() + + @property + def supports_group_chat(self): + import msn.AddressBook as MSNAB + return (self.protocol.supports_group_chat and + self._btype in (MSNAB.ClientType.PassportMember, + MSNAB.ClientType.ChatMember, + MSNAB.ClientType.CircleMember,)) + + @property + def type(self): + return self._btype + + def __hash__(self): + # TODO: figure out why this is necessary. + return common.buddy.__hash__(self) + + def _get_status_code(self): + import MSN + return MSN.MSNClient.status_to_code.get(self.status, 'AWY') + + def _set_status_code(self, val): + import MSN + self.status = MSN.MSNClient.code_to_status.get(val, 'away') + + status_code = property(_get_status_code, _set_status_code) + + @property + def sms(self): + if validate_sms(self.phone_mobile): + return self.phone_mobile + else: + return False + + def _get_phone_mobile(self): + return getattr(self, '_phone_mobile', None) + def _set_phone_mobile(self, val): + if val and val.startswith('tel:'): + val = val[4:] + self._phone_mobile = val + + phone_mobile = property(_get_phone_mobile, _set_phone_mobile) + + @property + def id(self): + + if self.contactsoap is not None: + v = self.contactsoap.ContactId + if v: return v + + return self.guid or self.name + + def _get_guid(self): + return getattr(self, '_guid', None) + def _set_guid(self, val): + if not (val is None or isinstance(val, self.protocol.ns.cid_class)): + raise TypeError("Was expecting type %r for guid, got %r (type=%r)", self.protocol.ns.cid_class, val, type(val)) + + self._guid = val + guid = property(_get_guid, _set_guid, doc = + 'Contact ID, which takes the form of a UUID in modern versions of MSNP.' + 'In the past, it was simply the passport name of the contact.' + ) + + def get_caps(self): + "Returns this buddy's capabilities." + from common import caps + + # otherwise fall back to the protocol + buddy_caps = set(self.protocol.caps) + + # remove "files" if offline + if not self.online: + buddy_caps.discard(caps.FILES) + + return buddy_caps + + caps = property(get_caps) + + def _set_contactsoap(self, val): + + self._contactsoap = val + + if self.contactsoap is not None: + phs = self.contactsoap.ContactInfo.Phones + if phs is not None and phs.ContactPhone is not None: + for phone in phs.ContactPhone: + if phone.ContactPhoneType == 'ContactPhoneMobile': + try: + num = normalize_sms(phone.Number) + except: + continue + else: + if not validate_sms(num): + continue + + self.phone_mobile = num + + def _get_contactsoap(self): + return self._contactsoap + + contactsoap = property(_get_contactsoap, _set_contactsoap) + + @property + def online(self): + ''' + get_online() + + returns True if this buddy is not offline + ''' + # XXX: comment these two lines below to have mobile count as "offline" + if self.mobile: + return True + + return self._got_presence and self._status not in ('offline', 'unknown') + + def set_status(self, newval): + oldstatus, self._status = self._status, newval + if oldstatus != newval and self._got_presence: + self.notify('status', oldstatus, newval) + + def get_status(self): + if not self._got_presence: + return 'unknown' + + if self._status == 'idle': + return 'idle' + if self.away: + return 'away' + + # XXX: comment these two lines below to have mobile count as "offline" + if self.mobile: + return 'mobile' + + return self._status + + status = ObservableProperty(get_status, set_status, observe=('_status',)) + + @property + def profile(self): + return None + + @property + def away(self): + return self._status in ['busy', 'brb', 'away', 'phone', 'lunch',] + + @property + def blocked(self): + return self in self.protocol.block_list + + def _set_status_message(self, val): + self._got_presence = True + self._status_message = val + + def _get_status_message(self): + return self._status_message + + status_message = property(_get_status_message, _set_status_message) + + @property + def mobile(self): + return (self.sms and (self.allow_mobile == 'Y')) and self._status == 'offline' + + @common.action(lambda self: True) + def get_profile(self): + #self.update_contact_card(tag(CONTACT_CARD)) + self.protocol.get_profile(self) + + @property + def sightly_status(self): + + if self.status == 'mobile': + return _('Mobile') + else: + return statuses.get(self._status,self._status.title()) + + @callsback + def block(self, _block=True, callback = None): + self.protocol.block_buddy(self, _block, callback = callback) + + @callsback + def unblock(self, callback = None): + self.protocol.block_buddy(self, False, callback = callback) + + @property + def service(self): + + num_or_str = get(self, '_btype', 1) + num = get(dict(msn=1,mob=4,fed=32), num_or_str, num_or_str) + + if num == 32 and self.name.endswith('yahoo.com'): + prot_name = 'yahoo' + else: + prot_name = self.protocol.name + + return prot_name + + def __repr__(self): + ''' + A string representation of this buddy + ''' + return "<%s %s>" % (type(self).__name__, self.name) + + def __str__(self): + ''' + How this buddy should be printed or otherwise turned into a string + ''' + return repr(self) +# +# def __setattr__(self, field, val): +# ''' +# buddy.field = val +# setattr(buddy, field, val) +# +# set a member of this buddy. If status is not idle, the _idle_start time +# will be set to now. +# +# @param field: the field to set. must be a __slot__ of MSNBuddy or Buddy +# @param val: the val to set it to. +# ''' +# common.buddy.__setattr__(self, field, val) +# if hasattr(self, 'status') and self.status != 'online': +# common.buddy.__setattr__(self, '_idle_start', int(time.time() - 60)) +# else: +# common.buddy.__setattr__(self, '_idle_start', False) + + def get_idle(self): + 'returns whether or not this buddy is idle.' + + return self.status == 'idle' + + def set_idle(self, val): + pass + + idle = ObservableProperty(get_idle, set_idle, observe=('status',)) + + def update(self, new): + ''' + update(new) + + update this buddy from another buddy. Not really needed any more, but + still around + + @param new: the buddy to get the new information from + ''' + if not new: return + + for field in new.__dict__: + newval = getattr(new, field) + if newval: + setattr(self, field, newval) + self.setnotifyif() + + def update_contact_card(self, card): + if not card: return + self._space = card._to_xml(pretty=False)#.replace('&', '&') + self.space = MSNSpace(self, card) +# self.space.displayName = card._attrs.get('displayName', self.alias) +# self.space.displayPictureUrl = card._attrs.get('displayPictureUrl','') +# +# for elt in card.elements: +# self._update_ccard_elt(elt, elt['type']) + + def _update_ccard_elt(self, elt, kind): + return dyn_dispatch(self, '_update_ccard_%s' % kind.lower(), elt) + + def _update_ccard_spacetitle(self, elt): + self.space.update((e._name, e._cdata) for e in elt if 'type' not in e._attrs) + + def _update_ccard_album(self, elt): + photos = [] + for subel in elt: + if subel._attrs.get('type', '') == 'Photo': + photos.append(Storage((e._name, e._cdata) for e in subel)) + + album = self.space.setdefault('album',Storage()) + album.update((e._name, e._cdata) for e in elt if 'type' not in e._attrs) + album.photos = photos + + _space = cproperty('') + + def _update_ccard_musiclist(self, elt): + musiclist = self.space.setdefault('musiclist', Storage()) + songs = [] + + for song in elt: + if song._attrs.get('type', '') == 'MusicListEntry': + songs.append(Storage((e._name, e._cdata) for e in song)) + + musiclist.update((e._name, e._cdata) for e in elt if 'type' not in e._attrs) + musiclist.songs = songs + + def _update_ccard_booklist(self, elt): + booklist = self.space.setdefault('booklist',Storage()) + books = [] + + for book in elt: + if book._attrs.get('type','') == 'BookListEntry': + books.append(Storage((e._name, e._cdata) for e in book)) + + booklist.update((e._name, e._cdata) for e in elt if 'type' not in e._attrs) + booklist.books = books + + def _update_ccard_genericlist(self, elt): + gen_lists = self.space.setdefault('gen_lists', []) + + entries = [] + for entry in elt: + if entry._attrs.get('type','') == 'GenericListEntry': + entries.append(Storage((e._name, e._cdata) for e in entry)) + + new_list = Storage((e._name, e._cdata) for e in elt if 'type' not in e._attrs) + new_list.entries = entries + gen_lists.append(new_list) + + def _update_ccard_blog(self, elt): + blog = self.space.setdefault('blog',Storage()) + + posts = [] + + for post in elt: + if post._attrs.get('type','') == 'Post': + posts.append(Storage((e._name, e._cdata) for e in post)) + + blog.update((e._name, e._cdata) for e in elt if 'type' not in e._attrs) + blog.posts = posts + + def _update_ccard_profile(self, elt): + profiles = self.space.setdefault('profiles',Storage()) + + for profile in elt: + p_type = profile._attrs.get('type','') + if p_type.endswith('Profile'): + prof = profiles.setdefault(p_type.lower()[:-7], Storage()) + prof.update((e._name, e._cdata) for e in profile) + + profiles.update((e._name, e._cdata) for e in elt if 'type' not in e._attrs) + + def _update_ccard_livecontact(self, elt): +# print elt._to_xml() + pass + + def __cmp__(self, other): + try: + if other is self: + return 0 + + return cmp((self.name, self.protocol), (other.name, other.protocol)) + + except: + return -1 + + + + @property + def pretty_profile(self): +# p = odict() +# +# if self.space is None: +# return p +# +# booklists = [] +# +# for elt in self.space.contents: +# if isinstance(elt, BookListElt): +# books = [] +# booklists.append(books) +# for entry in elt: +# pass +# +# +# return p + + d = {} + + if self.remote_alias and self.alias != self.remote_alias: + d[_('Display Name:')] = self.remote_alias + + if self.space is None: return d + else: + p = self.space.pretty_profile + + for key in [validkey for validkey in p.keys() if p[validkey] and filter(None,p[validkey])]: + i=p.keys().index(key) + if i!=0: + sepkey=''.join('sepb4'+key) + p[sepkey]=4 + p.move(sepkey,i) + + + return p + + @property + def contact(self): + return self.protocol.ns.contact_list.GetContact(self.name, type = self.type) + +class CircleBuddy(MSNBuddy): + def __init__(self, *a, **k): + import msn.AddressBook as MSNAB + MSNBuddy.__init__(self, *a, **k) + self._Members = [] + self._Pending = [] + self.circle = None + self.remote_alias = 'TempCircle' + self.guid = None + self._got_presence = True + self.status = 'online' + self._btype = MSNAB.ClientType.Chat + self.sem = threads.InvertedSemaphore(0) + + @property + def nice_name(self): + return self.remote_alias + + def get_role_names(self, role): + names = [] + if self.circle is None: + return names + + return [x.account for x in self.circle.GetContactsForRole(role)] + + def get_state_names(self, state): + return [x.account for x in self.circle.GetContactsForState(state)] + + @property + def pretty_profile(self): + d = odict() + + import msn.AddressBook as MSNAB + CPMR = MSNAB.CirclePersonalMembershipRole + + members = self.get_role_names(CPMR.Member) + admins = self.get_role_names(CPMR.Admin) + assistant_admins = self.get_role_names(CPMR.AssistantAdmin) + pending = list(set(self.get_role_names(CPMR.StatePendingOutbound) + + self._get_invited_names() + + self.get_state_names(MSNAB.RelationshipStates.Left) + + self.get_state_names(MSNAB.RelationshipStates.WaitingResponse))) + + for name in pending: + for _list in (members, admins, assistant_admins): + if name in _list: + _list.remove(name) + + names_to_pp = lambda l: [['\n', name] for name in l] + ['\n'] + + if admins: + d[_('Administrators:')] = names_to_pp(admins) + if assistant_admins: + d[_('Assistant Administrators:')] = names_to_pp(assistant_admins) + if members: + d[_('Members:')] = names_to_pp(members or [_('(none)')]) + if pending: + d[_('Invited:')] = names_to_pp(pending) + + return d + + def _get_Members(self): + if True or self.type == 'temp': + return self._Members + else: + return self.circle.MemberNames + + def _set_Members(self, val): + if True or self.type == 'temp': + self._Members = val + else: + raise AttributeError + + Members = property(_get_Members, _set_Members) + + def _get_Pending(self): + if True or self.type == 'temp': + return self._Pending + else: + return self.circle.PendingNames + + def _set_Pending(self, val): + if True or self.type == 'temp': + self._Pending = val + else: + raise AttributeError + + Pending = property(_get_Pending, _set_Pending) + + @classmethod + def from_msnab(cls, C, protocol): + B = cls(protocol, C.account) + B.update_from_msnab(C) + return B + + def update_from_msnab(self, C): + import msn.AddressBook as MSNAB + self.remote_alias = C.NickName + self.guid = self.protocol.ns.cid_class(C.abid) + self._btype = MSNAB.ClientType.Circle + self.circle = C + + @property + def buddy_names(self): + if False and self.type != 'temp': + assert self.circle is not None + return self.circle.MemberNames + + names = [] + + if self.circle is None: + more_members = [] + else: + more_members = [c.account for c in self.circle.contactList.contacts.values()] + + for x in self.Members: # + more_members: + if x in self.Pending: + continue + + name = x.split(':', 1)[-1] + if not name.lower().startswith(str(self.guid)) and name not in names: + names.append(name) + + return names + + def _get_invited_names(self): + names = [] + for x in self.Pending: + name = x.split(':', 1)[-1] + if not name.lower().startswith(str(self.guid)) and name not in names: + names.append(name) + return names + +class MSNSpaceElement(object): + def __init__(self, elt): + object.__init__(self) + for attr in ('title', 'url','description', 'tooltip'): + setattr(self, attr, str(getattr(elt, attr, '')).decode('utf-8')) + + self.last_check = yesterday() + self.last_update = yesterday() + + def __repr__(self): + res = ['<%s ' % type(self).__name__] + for attr in ('title', 'url','description', 'tooltip'): + myval = getattr(self, attr) + if myval: res.append('%s=%s, ' % (attr.capitalize(), myval)) + + res[-1] = res[-1][:-2] + '>' + + return ''.join(res) + + def __iter__(self): + def attrs(): + for attr in ('title', 'url','description', 'tooltip'): + val = getattr(self, attr) + if val: + yield (attr, val) + + import itertools + return itertools.chain(attrs(), iter(self.contents)) + + @property + def pretty_profile(self, p=None): + p = p or odict() + #p[(self.title, self.url)] = self.description + p[self.title+':'] = ['\n',(self.url, self.description)] + return p + + def to_tag(self): + table = tag('table') + tr = tag('tr') + + a = tag('a', href=self.url) + a._add_child(tag('b', self.title + ':')) + + tr._add_child(a) + tr._add_child(tag('td',self.description)) + + table._add_child(tr) + return table + +class SpaceTitleElt(MSNSpaceElement): + def to_tag(self): + a = tag('a', href=self.url) + a._add_child('b', self.title) + return a + + @property + def pretty_profile(self): + return {} + +class GenericListElt(list, MSNSpaceElement): + def __init__(self, elt): + + MSNSpaceElement.__init__(self, elt) + list.__init__(self, [getattr(thismod, '%sElt'%subel['type'])(subel) + for subel in elt if 'type' in subel._attrs]) + +# def __iter__(self): +# def attrs(): +# for attr in ('title', 'url','description', 'tooltip'): +# val = getattr(self, attr) +# if val: +# yield (attr, val) +# +# import itertools +# return itertools.chain(attrs(), list.__iter__(self)) + + def __repr__(self): + return '<%s: %s>' % (type(self).__name__, list.__repr__(self)) + + def __iter__(self): + return list.__iter__(self) + + @property + def pretty_profile(self): + return odict(MSNSpaceElement.pretty_profile.fget(self).items()) + + def to_tag(self): + p = MSNSpaceElement.to_tag(self) + + for thing in self: + p._add_child(thing.to_tag()) + + return p + +class MSNSpace(MSNSpaceElement): + _name = 'MSN Space' + def __init__(self, buddy, contact_card): + MSNSpaceElement.__init__(self, contact_card) + self.last_update = str(contact_card.lastUpdate) + self.title = contact_card._attrs.get('displayName', buddy.name).strip().decode('fuzzy utf-8') + self.buddy = buddy + self.dp_url = contact_card._attrs.get('displayPictureUrl', '') + self.contents = [] + + for element in contact_card.elements: + type_ = element['type'] + cls = getattr(thismod, '%sElt'% type_, MSNSpaceElement) + self.contents.append(cls(element)) + + @property + def pretty_profile(self): + #p = odict(MSNSpaceElement.pretty_profile.fget(self).items()) + p = odict() + for thing in self.contents: + p.update(thing.pretty_profile.items()) + + url = self.contents[0].url + if not url and self.buddy.CID: + url = ('http://spaces.live.com/Profile.aspx?cid=%s' % self.buddy.CID) + elif not self.buddy.CID: + return p + + assert url + + p['Profile URL:'] = ['\n', (url, url)] + return p + + def to_tag(self): + p = tag('p')#MSNSpaceElement.to_tag(self) + + for thing in self.contents: + p._add_child(thing.to_tag()) + + return p + +class GenericListEntryElt(MSNSpaceElement): + def __init__(self, elt): + MSNSpaceElement.__init__(self, elt) + self.last_update = elt['lastUpdated'] + self.title = str(elt.title).strip().decode('utf-8') + + @property + def pretty_profile(self): + return odict({self.title:'\n'}) + +class MusicListElt(GenericListElt): + @property + def pretty_profile(self): + songlist=[x.pretty_profile for x in self] + + songlist.insert(0,'\n') + return odict({'Songs:': songlist}) + +class MusicListEntryElt(GenericListEntryElt): + def __init__(self, elt): + GenericListEntryElt.__init__(self, elt) + self.artist = str(elt.artist).decode('utf-8') + self.song = str(elt.song).decode('utf-8') + + @property + def pretty_profile(self): + return (self.url, _(u'{song:s} by {artist:s}').format(song=self.song, artist=self.artist) +u'\n') + + +class BookListElt(GenericListElt): + def to_tag(self): + p = tag('p') + + p._add_child(tag('a', self.title, href=self.url)) + + for thing in self: + p._add_child(thing.to_tag()) + + return p + +class BookListEntryElt(GenericListEntryElt): + def to_tag(self): + return tag('p', '%s
%s' % (self.title, self.description)) + +class BlogElt(GenericListElt): + def to_tag(self): + p = tag('p') + + for thing in self: + p._add_child(thing.to_tag()) + + return p + + @property + def pretty_profile(self): + return odict([post.pretty_profile for post in self]) + +class PostElt(GenericListEntryElt): + def to_tag(self): + link = tag('a', self.title, href=self.url) + return tag('p', 'New post: %s
%s' % (link._to_xml(), self.description)) + + @property + def pretty_profile(self): + return (self.title+':',['\n',(self.url, self.description)]) + +class AlbumElt(GenericListElt): + def to_tag(self): + p = tag('p') + tr = tag('b', 'Photos Album:') + for photo in self: + tr._add_child(photo.to_tag()) + p._add_child(tr) + return p + + @property + def pretty_profile(self): + piclist=[x.pretty_profile for x in self] + + piclist.insert(0,'\n') + return odict({'Photos:': piclist}) + +class PhotoElt(GenericListEntryElt): + WIDTH, HEIGHT = 20,20 + def __init__(self, elt): + GenericListEntryElt.__init__(self, elt) + self.thumbnail_url = str(elt.thumbnailUrl) + self.web_url = str(elt.webReadyUrl) + self.album_name = str(elt.albumName).decode('utf-8') + + self.img_sm = None + self.img_lg = None + + self.loadimages() + + def to_tag(self): + a = tag('a', href=self.url) + a._add_child(tag('img', src=self.web_url, alt=self.tooltip, + width=self.WIDTH, height=self.HEIGHT)) + return a + + @property + def pretty_profile(self): + from common import pref + sz = pref('msn.spaces.photosize',20) + + if self.img_sm is None: + return (self.url, u'%s - %s' % (self.album_name, self.title+u'\n')) + + return dict(data=self.img_sm, alt=self.tooltip, + height=sz, width=sz, href=self.url) + + @threaded + def loadimages(self): + try: + self.img_sm = urlcacheopen(self.thumbnail_url) + except: + self.imb_sm = 'javascript' + + if self.img_sm and 'javascript' in self.img_sm: + self.img_sm = None + + try: + self.img_lg = urlcacheopen(self.web_url) + except: + self.img_lg = 'javascript' + + if self.img_lg and 'javascript' in self.img_lg: + self.img_lg = None + +class MSNContact(contacts.Contact): + _renderer = 'Contact' + inherited_actions = [MSNBuddy] + + def __init__(self, buddy, group_obj_or_id): + group_id = getattr(group_obj_or_id, 'name', group_obj_or_id) + + contacts.Contact.__init__(self, buddy, (buddy.name, group_id)) + + def __repr__(self): + return ' + + 1pqFlR36RzjW-X1jmTbKjKRsUpCe4cq9KHls4whqQIXlnjXVMidNbandYSmy0QEqm17M7xLIb2Fvo + + + + + Steve's Space + + + http://shaps776.spaces.live.com/?owner=1 + + + 0 + + + + + + stuff for description + + + book title 3 + + + Title: book title 3 + Author: author + Description: stuff for description + + + http://shaps776.spaces.live.com/Lists/cns!2DB0770EAE61F13!106?owner=1 + + + + + lkj lkj l + + + book title 2 + + + Title: book title 2Author: author 2Description: lkj lkj l + + + http://shaps776.spaces.live.com/Lists/cns!2DB0770EAE61F13!106?owner=1 + + + + Book List + + + http://shaps776.spaces.live.com/Lists/cns!2DB0770EAE61F13!106?owner=1 + + + book title 3 + + + 0 + + + + + + Photos + + + 10.png + + + January 1710.png + + + http://shaps776.spaces.live.com/photos/cns!2DB0770EAE61F13!138/cns!2DB0770EAE61F13!151?owner=1 + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxszOikmaP3r3dXbFG7ToyO0hMKW-WHHHJ2D9Lmff30X2Jo-2STreLVcNjaEogTUoBTZHMxhbwHv0ZBeHMwOnEdJcPytZWhE0nfg + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxszOikmaP3r3dlq-_0irNxa_2G2Gzp9ZgHOfSTUGN79t1IYnjSY5DKTXfP4ADxmBKN0Z5m0I7P6TUjGRafqU4NPz05iaCqzwJRA + + + January 17 + + + + + Photos + + + 9.gif + + + January 179.gif + + + http://shaps776.spaces.live.com/photos/cns!2DB0770EAE61F13!138/cns!2DB0770EAE61F13!150?owner=1 + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxswE_zjzklXeXVqyaBoOfjid5rjfn8g0bSHcgQmQ5AoqTuNtwMzT-XNFSFv_IKj8mcZZ-ENJcOYB3XWJYb3U0wC4RSwlFUqeGfw + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxswE_zjzklXeXVJfE1Gdeox-zkKou9QwXud_dSI32qXsAnPABY4K57fs1bpWbe7JSZdhHJwEQ0psi7lGPBDLpdW0_3sUHaRDjuA + + + January 17 + + + + + Photos + + + 8.gif + + + January 178.gif + + + http://shaps776.spaces.live.com/photos/cns!2DB0770EAE61F13!138/cns!2DB0770EAE61F13!149?owner=1 + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxs1s-Updfv0-X7Rk9c9exySfJ-2PozaK6BKKyP9v8DAcv5xxyZSZ0OK9wirfRd2yWEEX7VzZS2mvvBkUWbtz0VXbLead2Ybs5OA + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxs1s-Updfv0-XLAU_ZUH4P33y-NaJII-4uupQ0uoxjOCQJwxrL6sA1Xa9X3mdKUNYrj95_CVyrr5QVP7BXWFFyd2avflir6OhOA + + + January 17 + + + + + Photos + + + 7.png + + + January 177.png + + + http://shaps776.spaces.live.com/photos/cns!2DB0770EAE61F13!138/cns!2DB0770EAE61F13!148?owner=1 + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxsyrdesm1gzDOdiW3uJUFNjo3H7f9H4uLtkj7u-akJEORhy2lLsKpM01wmbQvBUA-OEZdfQLdJi-NouMArPt5N0CW4nYHxW51UA + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxsyrdesm1gzDO9TYtPpGT2JsdnYIUZw7Lo5-yTazqwfIGHa6vu-Ku9OIMA0gLptXjy6IMXfr-CV5cag2FJKcqo_rVuPRT2t_t7w + + + January 17 + + + + + Photos + + + 6.jpg + + + January 176.jpg + + + http://shaps776.spaces.live.com/photos/cns!2DB0770EAE61F13!138/cns!2DB0770EAE61F13!147?owner=1 + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxs-wyD9TggUJ_4XM7tDqJ6CINNBLXvtT4Lfwr7ikxskuBzMQNerpK-oV8CvyWk9BbashoZg1H9afsvyl576NJp4I0FWfrI4hwBw + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxs-wyD9TggUJ_C3BxBY1m6WsjbKXjpxDULsHHHEg8yAl2WLKDzSqb99OBxXaSetCdU-p8o6ScLb807p2QFAWr4gtH6OvBof2EBg + + + January 17 + + + + + Photos + + + 5.jpg + + + January 175.jpg + + + http://shaps776.spaces.live.com/photos/cns!2DB0770EAE61F13!138/cns!2DB0770EAE61F13!146?owner=1 + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxs49JgAmSpKyqet5yR2Upi4Nn_X5ewL5gr6Tst7QFxHUmmjxQJpMpldLBUCWxs5dXLddQfHtjnSJTjycfX0vvZYvb9vVzlON9OA + + + http://blufiles.storage.msn.com/x1pPHu2K6HCG6qh_ATHNyPxs49JgAmSpKyq-H9kk6kls9jjKFGLEr9AWb3NdQAxCcI6ta_72H6Ct25Dzjm1lkz7_xgUUuKboUy37TR0hveNy5ZRjhJ1eMUzcw + + + January 17 + + + + Photos: + + + http://shaps776.spaces.live.com/Photos/?owner=1 + + + 0 + + + + + + sfljks dflkjsdflkjsdflkjs dflkjsad flks jdaflsa dkjflsadkfjsadlfkj + + + NEWNEWNEW + + + sfljks dflkjsdflkjsdflkjs dflkjsad flks jdaflsa dkjflsadkfjsadlfkj + + + http://shaps776.spaces.live.com/Blog/cns!2DB0770EAE61F13!130.entry?owner=1 + + + + Blog: + + + http://shaps776.spaces.live.com/?owner=1 + + + 0 + + + + + + + General profile info updated + + + This person has recently added or updated General profile information. + + + http://shaps776.spaces.live.com/Profile.aspx?cid=205766389534170899&mkt=en-us&action=view + + + + + + Public profile info updated + + + This person has recently added or updated Public profile information. + + + http://shaps776.spaces.live.com/Profile.aspx?cid=205766389534170899&mkt=en-us&action=view + + + + + + Social profile info updated + + + This person has recently added or updated Social profile information. + + + http://shaps776.spaces.live.com/Profile.aspx?cid=205766389534170899&mkt=en-us&action=view + + + + Profile + + + http://shaps776.spaces.live.com/Profile.aspx?cid=205766389534170899&mkt=en-us&action=view + + + General profile info updated + + + 3 + + + + + + + Business contact info updated + + + This person has recently added or updated Business contact information. + + + http://shaps776.spaces.live.com/Profile.aspx?cid=205766389534170899&mkt=en-us&action=view&mode=activecontacts + + + + + + Personal contact info updated + + + This person has recently added or updated Personal contact information. + + + http://shaps776.spaces.live.com/Profile.aspx?cid=205766389534170899&mkt=en-us&action=view&mode=activecontacts + + + + Contact Info + + + http://shaps776.spaces.live.com/Profile.aspx?cid=205766389534170899&mkt=en-us&action=view&mode=activecontacts + + + Business contact info updated + + + 2 + + + + + 2007-04-18T08:20:01.167-07:00 + + + + personalspacegree + + + + + + + + + personalspacegree + + + + + + +'''.strip().replace('&', '&') + +if __name__ == '__main__': + + b = Storage(name='shaps776@hotmail.com') + card = MSNSpace(b, tag(CONTACT_CARD)) + print card.to_tag()._to_xml() diff --git a/digsby/src/msn/MSNClientID.py b/digsby/src/msn/MSNClientID.py new file mode 100644 index 0000000..e8e6a22 --- /dev/null +++ b/digsby/src/msn/MSNClientID.py @@ -0,0 +1,202 @@ +class MSNCapabilities(object): + CLIENT_MOBILE = 0x00000001 + MSN_EXPLORER8 = 0x00000002 + INK_GIF = 0x00000004 + INK_ISF = 0x00000008 + WEBCAM = 0x00000010 + MULTIPACKET_MSG = 0x00000020 + CLIENT_MSNMOBILE = 0x00000040 + CLIENT_MSNDIRECT = 0x00000080 + CLIENT_WEB = 0x00000200 + CLIENT_INTERNAL = 0x00000800 + SPACES = 0x00001000 + CLIENT_WINXPMCE = 0x00002000 + DIRECT_IM = 0x00004000 + WINKS = 0x00008000 + MSN_SEARCH = 0x00010000 + IS_BOT = 0x00020000 + VOICE_CLIPS = 0x00040000 + SECURE_IM = 0x00080000 + SIP_INVITES = 0x00100000 + SIP_TUNNEL = 0x00200000 + FILE_SHARING = 0x00400000 + ONE_CARE = 0x01000000 + P2P_TURN = 0x02000000 + P2P_UUN = 0x04000000 + MSNC1 = 0x10000000 + MSNC2 = 0x20000000 + MSNC3 = 0x30000000 + MSNC4 = 0x40000000 + MSNC5 = 0x50000000 + MSNC6 = 0x60000000 + MSNC7 = 0x70000000 + MSNC8 = 0x80000000 + MSNC9 = 0x90000000 + MSNC10 = 0xA0000000 + MSNC11 = 0xB0000000 + MSNC12 = 0xC0000000 + MSNC13 = 0xD0000000 + MSNC14 = 0xE0000000 + + num_to_str = { + 0x00000001 : 'mobile', + 0x00000002 : 'msn explorer 8', + 0x00000004 : 'ink gif support', + 0x00000008 : 'ink isf support', + 0x00000010 : 'shared webcam', + 0x00000020 : 'multipacket messaging', + 0x00000040 : 'msn mobile device', + 0x00000080 : 'msn direct', + 0x00000200 : 'web based client', + 0x00000800 : 'office live client', + 0x00001000 : 'msn space', + 0x00002000 : 'windows xp media center', + 0x00004000 : 'direct im', + 0x00008000 : 'recv winks', + 0x00010000 : 'msn search', + 0x00020000 : 'is bot', + 0x00040000 : 'recv voice clips', + 0x00080000 : 'secure channel communications', + 0x00100000 : 'SIP invitations', + 0x00200000 : 'sip tunnel', + 0x00400000 : 'file sharing', + 0x01000000 : 'one care', + 0x02000000 : 'p2p turn', + 0x04000000 : 'p2p uun', + 0x10000000 : 'msnc1', + 0x20000000 : 'msnc2', + 0x30000000 : 'msnc3', + 0x40000000 : 'msnc4', + 0x50000000 : 'msnc5', + 0x60000000 : 'msnc6', + 0x70000000 : 'msnc7', + 0x80000000 : 'msnc8', + 0x90000000 : 'msnc9', + 0xa0000000 : 'msnc10', + 0xb0000000 : 'msnc11', + 0xc0000000 : 'msnc12', + 0xd0000000 : 'msnc13', + 0xe0000000 : 'msnc14', + } + + +class MSNCapabilitiesEx(object): + NONE = 0x00 + IsSmsOnly = 0x01 + SupportsVoiceOverMsnp = 0x02 + SupportsUucpSipStack = 0x04 + SupportsApplicationMessages = 0x08 + RTCVideoEnabled = 0x10 + SupportsPeerToPeerV2 = 0x20 + IsAuthenticatedWebIMUser = 0x40 + Supports1On1ViaGroup = 0x80 + SupportsOfflineIM = 0x100 + SupportsSharingVideo = 0x200 + SupportsNudges = 0x400 + CircleVoiceIMEnabled = 0x800 + SharingEnabled = 0x1000 + MobileSuspendIMFanoutDisable = 0x2000 + _0x4000 = 0x4000 + SupportsPeerToPeerMixerRelay = 0x8000 + _0x10000 = 0x10000 + ConvWindowFileTransfer = 0x20000 + VideoCallSupports16x9 = 0x40000 + SupportsPeerToPeerEnveloping = 0x80000 + _0x100000 = 0x100000 + _0x200000 = 0x200000 + YahooIMDisabled = 0x400000 + SIPTunnelVersion2 = 0x800000 + VoiceClipSupportsWMAFormat = 0x1000000 + VoiceClipSupportsCircleIM = 0x2000000 + SupportsSocialNewsObjectTypes = 0x4000000 + CustomEmoticonsCapable = 0x8000000 + SupportsUTF8MoodMessages = 0x10000000 + FTURNCapable = 0x20000000 + SupportsP4Activity = 0x40000000 + SupportsMultipartyConversations = 0x80000000 + + num_to_str = {0x00000001: 'IsSmsOnly', + 0x00000002: 'SupportsVoiceOverMsnp', + 0x00000004: 'SupportsUucpSipStack', + 0x00000008: 'SupportsApplicationMessages', + 0x00000010: 'RTCVideoEnabled', + 0x00000020: 'SupportsPeerToPeerV2', + 0x00000040: 'IsAuthenticatedWebIMUser', + 0x00000080: 'Supports1On1ViaGroup', + 0x00000100: 'SupportsOfflineIM', + 0x00000200: 'SupportsSharingVideo', + 0x00000400: 'SupportsNudges', + 0x00000800: 'CircleVoiceIMEnabled', + 0x00001000: 'SharingEnabled', + 0x00002000: 'MobileSuspendIMFanoutDisable', + 0x00004000: '_0x4000', + 0x00008000: 'SupportsPeerToPeerMixerRelay', + 0x00010000: '_0x10000', + 0x00020000: 'ConvWindowFileTransfer', + 0x00040000: 'VideoCallSupports16x9', + 0x00080000: 'SupportsPeerToPeerEnveloping', + 0x00100000: '_0x100000', + 0x00200000: '_0x200000', + 0x00400000: 'YahooIMDisabled', + 0x00800000: 'SIPTunnelVersion2', + 0x01000000: 'VoiceClipSupportsWMAFormat', + 0x02000000: 'VoiceClipSupportsCircleIM', + 0x04000000: 'SupportsSocialNewsObjectTypes', + 0x08000000: 'CustomEmoticonsCapable', + 0x10000000: 'SupportsUTF8MoodMessages', + 0x20000000: 'FTURNCapable', + 0x40000000: 'SupportsP4Activity', + 0x80000000: 'SupportsMultipartyConversations'} + +def parse_client_id(num): + ''' + Take the stupid msn client id and turn it into a string + + @param num: the client id to parse. + @returns: a string containing all of the properties based on the above + dictionary. + ''' + + try: + caps_basic = int(num) + caps_ex = 0 + except ValueError: + caps_basic, caps_ex = map(int, str(num).split(':', 1)) + + to_str = lambda i, cls: ', '.join(cls.num_to_str[k] for k in cls.num_to_str if k & i == k) + basic_str = to_str(caps_basic, MSNCapabilities) + ex_str = to_str(caps_ex, MSNCapabilitiesEx) + + return ' : '.join(filter(None, [basic_str, ex_str])) + +IM_DEFAULT_CAPABILITIES = (MSNCapabilities.MSNC11 | + MSNCapabilities.SIP_INVITES | + MSNCapabilities.DIRECT_IM | + MSNCapabilities.MULTIPACKET_MSG | + MSNCapabilities.WINKS | + 0 + ) +IM_DEFAULT_CAPABILITIES_EX = ( + MSNCapabilitiesEx.Supports1On1ViaGroup | +# MSNCapabilitiesEx.ConvWindowFileTransfer | + MSNCapabilitiesEx.SupportsOfflineIM | + MSNCapabilitiesEx.SupportsNudges | + MSNCapabilitiesEx.SupportsUTF8MoodMessages | + MSNCapabilitiesEx.SupportsMultipartyConversations | + MSNCapabilitiesEx.SupportsSocialNewsObjectTypes | +# MSNCapabilitiesEx._0x10000 | +# MSNCapabilitiesEx._0x4000 | +# MSNCapabilitiesEx._0x100000 + MSNCapabilitiesEx.ConvWindowFileTransfer | + 0 + ) + +PE_DEFAULT_CAPABILITIES = 0 +PE_DEFAULT_CAPABILITIES_EX = (MSNCapabilitiesEx.SupportsP4Activity | + MSNCapabilitiesEx.SupportsPeerToPeerEnveloping | + MSNCapabilitiesEx.SupportsPeerToPeerMixerRelay | + MSNCapabilitiesEx.SupportsPeerToPeerV2 | + MSNCapabilitiesEx.ConvWindowFileTransfer | + 0 + ) + diff --git a/digsby/src/msn/MSNCommands.py b/digsby/src/msn/MSNCommands.py new file mode 100644 index 0000000..60ad964 --- /dev/null +++ b/digsby/src/msn/MSNCommands.py @@ -0,0 +1,531 @@ +import io +import logging +import msn +import traceback +import email + +from rfc822 import Message as RfcMsg +from util import pythonize, Storage +from util.primitives.funcs import autoassign, get, isint + +_payload_commands = "MSG UUX UBX PAG IPG NOT GCF ADL UUN UBN RML FQY 241 508 UBM UUM PUT NFY SDG".split() +def is_payload_command(dlist, payload_commands = _payload_commands): + cmd = dlist[0] + if cmd in payload_commands: + return True + if cmd in ('201', '204', '500', '801', '252', '731', '240', '933', '715') and len(dlist) == 3: + return True + + return False + +def three_part_email_parse(data): + return MultiPartMime.parse(data) + +class CommandProcessor(object): + def __init__(self, log = None): + self.log = log or logging.getLogger(type(self).__name__) + self.process_cmd = Storage(success=self.on_message, error=self.on_error) + + def on_message(self, msg): + """ + This function is called by MSNSockets when a new + command is receieved + """ + if self.log: self.log.log_s(1,"Got message: %r", str(msg)[:100].strip('\r\n')) + mname = 'recv_%s' % pythonize(msg.cmd) + getattr(self, mname, self.unknown_cmd)(msg) + + def on_error(self, msg): + assert msg.is_error + self.log.error('MSNError! %r', msg) + + def unknown_cmd(self, msg): + self.log.warning('Received unknown message from transport. Message is <%r>', msg) + + +class MsgPayload(RfcMsg): + def __len__(self): + return len(self.body()) + +class Message(object): + argnames = [] + def __init__(self, cmd, *args, **kws): + ''' + @param cmd: Three letter (or number) code for which command this is + @type cmd: string + + @param *args: The arguments for this command. Must be str()able + @type *args: Any object with a __str__ method + + @keyword trid: The TrID to use for this command. Default is 0 and means there is none. + @type trid: int + + @keyword payload: Payload for this command. If None, then the payload command + form will not be used + @type payload: None (for no payload) or string + ''' + self.cmd = cmd.upper() + self.args = list(args) + self.trid = int(kws.pop('trid',0)) + self.payload = kws.pop('payload',None) or None + assert not kws + + + @property + def length(self): + if self.is_payload: + return len(str(self.payload)) + else: + return 0 + + @property + def is_trid(self): + return bool(isint(self.trid) and int(self.trid)) + + @property + def is_payload(self): + return self.payload is not None + + @property + def is_error(self): + return bool(self.error_code) + + @property + def error_str(self): + return msn.error_codes.get(self.error_code, 'Undocumented error') + + @property + def error_code(self): + try: return int(self.cmd) + except: return None + + def __iter__(self): + return iter(self.args) + + def __str__(self): + res = [self.cmd] + if self.is_trid: + res.append(str(self.trid)) + + res.extend(map(str,self.args)) + + if self.is_payload: + res.append(str(self.length)+'\r\n'+str(self.payload)) + + return ' '.join(filter(None,res)) + ('\r\n' if not self.is_payload else '') + + def __repr__(self): + + err_str = ('error_str=%s, error_code=%s, ' % (self.error_str, self.error_code)) \ + if self.is_error else '' + + if type(self) is Message: + typestr = Message.__name__ + else: + typestr = '%s(%s)' % (Message.__name__, type(self).__name__) + return '<%s %s trid=%s, %sargs=%r, len(payload)=%s>' % \ + (typestr, self.cmd, self.trid, err_str, self.args, + len(self.payload) if self.is_payload else None) + + def __getattr__(self, attr): + if attr in self.argnames: + return self.args[self.argnames.index(attr)] + else: + return object.__getattribute__(self, attr) + + def get(self, key, default=sentinel): + try: + return self[key] + except KeyError: + if default is sentinel: raise + else: return default + + + def __getitem__(self, *a): + return self.args.__getitem__(*a) + + @classmethod + def from_net(cls, data, is_payload = None): + if is_payload is None: + is_payload = is_payload_command(data.split(' ', 1)) + + if is_payload: + cmdline, payload = data.split('\r\n',1) + else: + cmdline, payload = data, None + + cmdline = cmdline.strip('\r\n ') + dlist = cmdline.split(' ') + cmd = dlist.pop(0) + + if dlist and isint(dlist[0]): + trid = int(dlist.pop(0)) + else: + trid = 0 + + if is_payload: + try: + l = dlist.pop() + except IndexError: + # oops, the TrID was the payload length + l,trid = trid, 0 + try: + assert isint(l) and int(l) == len(payload), (cmdline, len(payload)) + except AssertionError: + assert cmd in ('ADL', 'RML') and (not isint(l) or len(payload) == int(l)), (cmdline, dlist, l, len(payload)) + dlist.append(l) + payload = payload.strip() or None + + args = list(dlist) + + if cmd == 'MSG': + ctype = MsgPayload(payload).get('content-type', '').split(';')[0] + subcmd = msn.util.msgtypes.get(ctype, '') + cls = globals().get(cmd+subcmd, MSG) + else: + cls = globals().get(cmd, cls) + + return cls(cmd, trid=trid, payload=payload, *args) + + def copy(self): + return type(self)(self.cmd, trid = self.trid, payload = self.payload, *self.args) + + +class OUT(Message): + argnames = ['reason'] + +class SDG(Message): + argnames = ['length'] + + def __init__(self, *a, **k): + if not a or a[0] != 'SDG': + a = ("SDG",) + a + + Message.__init__(self, *a, **k) + self.payload = three_part_email_parse(self.payload) + + @property + def type(self): + return pythonize(self.payload.get("Message-Type")) + + @property + def name(self): + return self.payload.get("From").split(':', 1)[-1].split(';')[0] + + @classmethod + def from_net(cls, data, is_payload = None): + obj = Message.from_net(data, is_payload) + obj.payload = three_part_email_parse(obj.payload) + + return obj + + def __repr__(self): + return '%s(%s)' % (type(self).__name__, + ' '.join('%s=%r' % x for x in vars(self).items())) + +class MSG(Message): + argnames = 'name nick length'.split() + + def __init__(self, *a, **k): + if a[0] != 'MSG': + a = ('MSG',) + a + + Message.__init__(self, *a, **k) + self.type = msn.util.msgtypes.setdefault(self.payload['content-type'].split(';')[0], + 'unknown') + def __getitem__(self, *a, **k): + try: + return Message.__getitem__(self, *a, **k) + except: + return self.payload.__getitem__(*a, **k) + + def _get_payload(self): + return self._payload + + def _set_payload(self, payload): + self._payload = MsgPayload(payload) + + payload = property(_get_payload, _set_payload) + + def from_net(self, data, is_payload = None): + assert data[:4] == type(self).__name__ + data = data[4:] + res = Message.from_net(data, is_payload) + if res.trid: + tr, res.trid = res.trid, 0 + res.args = ('%d' % tr,) + res.args + return res + + + def __repr__(self): + super = Message.__repr__(self) + return '%s type=%s>' % (super.rstrip('>'), self.type) + +class MSGdatacast(MSG): + + def _get_payload(self): + return MSG._get_payload(self) + + def _set_payload(self, payload): + MSG._set_payload(self, payload) + self.contents = MsgPayload(self.payload.body()) + + payload = property(_get_payload, _set_payload) + + def __getattr__(self, attr): + if attr in ('id','data'): + return get(self.contents, attr) + else: + return MSG.__getattr__(self,attr) + +class UBM(Message): + + def __init__(self, *a, **k): + Message.__init__(self, *a, **k) + self.type = msn.util.msgtypes.setdefault(self.payload['content-type'].split(';')[0], + 'unknown') + + def _get_payload(self): + return self._payload + + def _set_payload(self, payload): + self._payload = MsgPayload(payload) + + payload = property(_get_payload, _set_payload) + + def __getitem__(self, *a, **k): + try: + return Message.__getitem__(self, *a, **k) + except: + return self.payload.__getitem__(*a, **k) + + + +class MSNSB_Message(Message): + def __init__(self, contenttype, body, acktype='N', headers={}, **moreheaders): + moreheaders.update(headers) + content = [] + + content.append('MIME-Version: 1.0') + content.append('Content-Type: text/x-%s' % contenttype) + + for header in moreheaders.items(): + content.append('%s: %s' % header) + + content.append('') + payload = '\r\n'.join(content) + body + + Message.__init__(self, 'MSG', acktype, payload=payload) + + +class MSNTextMessage(object): + def __init__(self, body, fontname=None, color=None, rightalign=False, + bold=False, italic=False, underline=False, strike=False, + charset=0, family=22): + + autoassign(self, locals()) + + if isinstance(self.fontname, str): + self.fontname = self.fontname.decode('fuzzy utf8') + if isinstance(self.body, str): + self.body = self.body.decode('fuzzy utf8') + + self.effects = '' + if self.bold: + self.effects += 'B' + if self.italic: + self.effects += 'I' + if self.underline: + self.effects += 'U' + if self.strike: + self.effects += 'S' + + if self.color is not None: + self.r, self.g, self.b = self.color[:3] + else: + self.r = self.g = self.b = None + + self.__html = u'' + + def __str__(self): + s = (u'\r\n'.join(['MIME-Version: 1.0', + 'Content-Type: text/plain; charset=UTF-8', + 'X-MMS-IM-Format: ' + + ('FN=%s; ' % self.fontname.encode('utf-8').encode('url').replace('%', '%%') if self.fontname is not None else '') + + ('EF=%(effects)s; ' if self.effects else '') + + ('CO=%(b)02X%(g)02X%(r)02X; ' if self.color else '') + + 'CS=%(charset)d; PF=%(family)d', + '', + '%(body)s'])) + + return (s % vars(self)).encode('utf-8') + + @classmethod + def from_net(cls, rfc822msg): + ''' + FN: Font Name. URL-encoded name of font. + EF: Effects. B, I, U, S [bold, italic, underline, strikethru] + CO: Color. BBGGRR format (FFFFFF is white, 000000 is black. etc.) + CS: Charset (old pre-unicode windows stuff) + PF: Pitch and family - for figuring out a font if it's not available + RL: Right alignment. If 0, left-align. otherwise, right-align. + ''' + m = rfc822msg + fmt = m.get('X-MMS-IM-Format', '') + body = m.get_payload().decode('fuzzy utf-8') + + msg = cls(body) + + + if fmt: + if fmt.strip().endswith(";"): + fmt = fmt.strip()[:-1] + try: + fmt = msn.util.mime_to_dict(fmt) + except Exception, e: + traceback.print_exc() + return msg + + _g = lambda k: get(fmt, k, None) + + fontname = _g('FN') + msg.fontname = None if fontname is None else fontname.decode('url').decode('utf-8') + + color = _g('CO') + effects = _g('EF') + charset = _g('CS') + family = _g('PF') + ralign = _g('RL') + + if color is not None: + try: + color = int(msn.util.bgr_to_rgb(color), 16) + except ValueError: + color = None + else: + r = (color & 0xFF0000) >> 16 + g = (color & 0x00FF00) >> 8 + b = (color & 0x0000FF) + msg.color = (r,g,b) + msg.r = r + msg.g = g + msg.b = b + + if charset is not None: + msg.charset = int(charset,16) + + if family is not None: + msg.family = int(family) + + if ralign is not None: + msg.rightalign = bool(int(ralign)) + + if effects is not None: + ef = lambda x: x in effects + msg.bold = ef('B') + msg.italic = ef('I') + msg.underline = ef('U') + msg.strike = ef('S') + msg.effects = effects + return msg + + def html(self): + + if self.__html: + return self.__html + + from util.xml_tag import tag + t = tag(u'font') + + if self.fontname: + t[u'face'] = self.fontname + if self.color: + t[u'color'] = u'#' + (u''.join(map(lambda x: '%02x' % (x or 0), self.color[:3]))) + + if self.rightalign: + t[u'align'] = u'right' + + innermost = top = t + for attr in ('bold','italic','underline','strike'): + if getattr(self,attr): + _tag = tag(attr[0]) + innermost += _tag + innermost = _tag + + innermost._cdata = self.body + + self.__html = top._to_xml(pretty=False).strip() + + return self.__html + +class MSNMime(email.Message.Message): + def as_string(self, unixfrom = False, **k): + from email.Generator import Generator + fp = io.BytesIO() + if 'maxheaderlen' not in k: + k['maxheaderlen'] = 0 + g = Generator(fp, **k) + g.flatten(self, unixfrom=unixfrom) + return fp.getvalue() + +class MultiPartMime(object): + def __init__(self, headers, body): + self.parts = [] + for header_block in headers: + self.add_header_block(header_block) + + del self.parts[-1]['Content-Length'] + self.parts[-1]['Content-Length'] = str(len(body)) + + self.parts[-1].set_payload(body) + + def get_payload(self): + return self.parts[-1].get_payload() + + def add_header_block(self, block): + part = MSNMime() + for header in block: + key, val = header + part.add_header(key, val) + + self.parts.append(part) + + def get(self, key, fallback = None): + for part in self.parts: + val = part.get(key, None) + if val is not None: + return val + + return fallback + + def __str__(self): + result = [] + payload = self.get_payload() + self.parts[-1].set_payload(None) + msg_to_string = lambda x: x.as_string().replace('\n', '\r\n') + for part in self.parts: + result.append(msg_to_string(part)) + + if payload is not None: + result.append(str(payload)) + self.parts[-1].set_payload(payload) + + return ''.join(result) + + @classmethod + def parse(cls, data): + if not isinstance(data, basestring): + return data + + content_length = None + + header_blocks = [] + + while content_length is None: + message = email.message_from_string(data, _class = MSNMime) + header_blocks.append(message._headers) + content_length = message.get('Content-Length') + data = message.get_payload() + message.set_payload(None) + + return cls(header_blocks, data) diff --git a/digsby/src/msn/MSNConversation.py b/digsby/src/msn/MSNConversation.py new file mode 100644 index 0000000..3448f6c --- /dev/null +++ b/digsby/src/msn/MSNConversation.py @@ -0,0 +1,883 @@ +import time +import logging +import traceback + +import util.allow_once as once +from util import callsback, RoundRobinProducer, strip_html, Timer +from util.primitives.funcs import get +from util.Events import event + +import common +from util.observe import ObservableDict +from common import Conversation, pref + +from msn import NSSBAdapter, oim + +from msn.MSNCommands import MSNTextMessage + +log = logging.getLogger('msn.conv') + +class MSNConversation(Conversation): + MAX_MESSAGE_LENGTH = 1664 + CONNECT_TIME_LIMIT = 10 + + class States: + OFFLINE = 'offline' # offline + IDLE = 'idled' # offline due to idle or other buddies left + READY = 'ready' # ready to chat + EMPTY = 'empty' # ready but no other principals + CALLING = 'calling' # calling another principal (for the first time) + CONNECTING = 'connecting' # opening socket and authenticating + + reset_allow_once = once.reset_allow_once + _runonce_verbose = False + + def __init__(self, msn, switchboard=None, to_invite=()): + self.client = msn + self._to_invite = set(to_invite) + + # if inviting multiple people, make "ischat" immediately true. + if len(self._to_invite) > 1: + self._waschat = True + + Conversation.__init__(self, msn) + self.protocol.register_conv(self) + + self.__prodq = [] + + self._connect_cb = None + + self._type_override = None + + log.info('Added %r to msn\'s conversations list', self) + + if len(to_invite) >= 1: + self.__chatbuddy = to_invite[0] + else: + self.__chatbuddy = None + + self.sb = None + self._set_switchboard(switchboard) + + self.state = self.States.CONNECTING if self.sb else self.States.OFFLINE + self._queued = [] + self.buddies = {} + self.typing_status = ObservableDict() + self.__connecting_time = 0 + + self.room_list.append(self.self_buddy) + + chat_room_name = None + + def _bind_reconnect(self): + pass # do not reconnect to MSN conferences. + + def _check_connect_time(self): + now = time.time() + conntime = self.__connecting_time + + if not conntime: + if self.state == self.States.CONNECTING: + log.info('Was in connecting but didnt have a __connecting_time set, starting over') + self.state = self.States.OFFLINE + self.connect() + return + + if (now - conntime) > self.CONNECT_TIME_LIMIT: + self._connection_error(Exception('timeout')) + + @property + def _closed(self): + return (not self.sb) or (self.sb._closed) + + def maybe_queue(self, f, *args, **kws): + operation = (f, args, kws) + if operation not in self._queued: + self._queued.append(operation) + + @property + def name(self): + + names = self._clean_list() + count = len(names) + aliases = [self.protocol.get_buddy(n).alias for n in names if n != self.self_buddy.name] + if count == 2: + who = aliases[0] + elif count == 3: + who = '%s and %s' % tuple(sorted(aliases)) + else: + who = '%d people' % (count - 1) + + return who + + @property + def type(self): + + if self._type_override == 'offline' and self.sb is not None: + # we set this as an offline conversation before + if self.buddy.online: + # but the buddy is back online! revert back to 'fresh' conversation + log.info('%r is online, clearing OfflineSBAdapter and resetting conversation state.', self.buddy) + self.state = self.States.OFFLINE + self._type_override = None + self.Disconnect() + + if self._type_override is not None: + # This is the ONLY way that a conversatino can become 'offline' + return self._type_override + + try: + if self.ischat: + return 'chat' + + if self.buddy._btype == 'fed': + return 'fed' + + # XXX: comment these two lines to never send mobile messages from the "IM" tab of the im window + if self.buddy._status == 'offline' and self.buddy.mobile: + return 'mobile' + + return 'im' + + except Exception, e: + log.error('Got error while trying to determine type. Returning IM.') + log.error(' Error was: %r', e) + + return 'im' + + _waschat = False + + @property + def ischat(self): + self._waschat = self._waschat or len(self._clean_list()) > 2 + return self._waschat + + @property + def buddy(self): + l = self._clean_list() + + try: + l.remove(self.self_buddy.name) + except ValueError: + pass + + if len(l) == 1: + answer = l[0] + if isinstance(answer, basestring): + answer = self.protocol.get_buddy(answer) + return answer + + return self.protocol.get_buddy(self.__chatbuddy) + + def _clean_list(self): + l = set(x.name for x in self.room_list) | set(self._to_invite) + + return list(l) + + @property + def self_buddy(self): + return self.protocol.self_buddy + + def _set_switchboard(self, switchboard): + self.reset_allow_once(self._request) + log.debug('_set_switchboard: self=%r, self.sb=%r, new switchboard=%r', self, self.sb, switchboard) + + if switchboard is None and self.sb is None: + return + + evt_table = ( + ('on_conn_success', self.sb_connected), + ('needs_auth', self.provide_auth), + ('on_authenticate', self.sb_authed), + ('recv_action', self.on_action_recv), + ('recv_text_msg', self.on_message_recv), + ('send_text_msg', self.on_message_send), + ('on_buddy_join', self.on_buddy_join), + ('on_buddy_leave', self.on_buddy_leave), + ('on_buddy_timeout',self.on_buddy_timeout), + ('typing_info', self.on_typing_notification), + ('disconnect', self.exit), + ('recv_p2p_msg', self.on_p2p_recv), + ('recv_error', self.on_proto_error), + ('transport_error', self._connection_error), + ) + + oldsb, self.sb = self.sb, switchboard + + if oldsb: + log.info('Unbinding events from %r', oldsb) + for evt, handler in evt_table: + oldsb.unbind(evt, handler) + oldsb.leave() + + self.sb = switchboard + + if self.sb: + log.info('Binding events to %r', self.sb) + for evt, handler in evt_table: + self.sb.bind(evt, handler) + + elif self.state != self.States.OFFLINE: + self.state = self.States.OFFLINE + + def provide_auth(self): + self.sb.authenticate(self.self_buddy.name) + + def sb_connected(self, sb): + #assert self.state == self.States.CONNECTING, self.state + assert self.sb is sb + + def sb_authed(self): + self.reset_allow_once('_request') + if self.sb.principals: + self.state = self.States.READY + log.info('SB Authed and buddies present. Flushing queue.') + self._flush_queue() + else: + self.state = self.States.EMPTY + + self.__connecting_time = 0 + + log.info('Got auth event. State is now: %s', self.state) + self._process_invites() + + if self._connect_cb is not None: + ccb, self._connect_cb = self._connect_cb, None + ccb.success(self) + + def _process_invites(self): + log.info('Processing invites:') + + present_names = set(x.name for x in self.room_list) + if self.__chatbuddy not in present_names: # If they're not in here, they need to be invited + self._to_invite.add(self.__chatbuddy) + + if self._to_invite: + for contact in set(self._to_invite): + if contact not in present_names: + log.info(' %r', contact) + self.invite(contact) + else: + log.info(' %r skipped, already present', contact) + else: + log.info(' No one to invite.') + + def _flush_queue(self): + self._invite_timeout = 0 + self.just_had_error = False + q, self._queued[:] = self._queued[:], [] + while q: + f, a, k = q.pop(0) + log.info('Calling %s%s', f,a) + f(*a, **k) + + log.debug('Done flushing call queue, moving on to prodq') + self._processq() + + @callsback + def _send_message(self, text, callback=None, **k): + self._stop_exit_timer() + getattr(self, '_send_message_%s' % self.type, self._send_message_im)(text, callback=callback, **k) + + def _send_message_fed(self, text, **k): + self._send_message_im(text) + + def on_chat_connect_error(self, e=None): + if not getattr(self, 'just_had_error', False): + self.system_message(_('There was a network error creating a chat session.')) + self.just_had_error = True + self.queue_error(e) + + def _send_message_im(self, msg, callback=None, **k): + + cl = set(self._clean_list()) + cl -= set([self.self_buddy.name]) + if not cl: + callback.error() + self.system_message('You can\'t message yourself using MSN.') + return + + if not self.sb: + log.info('_send_message_im was called but there is no SB. Setting %r state to offline.', self) + self.state = self.States.OFFLINE + + if self.state != self.States.READY: + k.update(format=format) + self.maybe_queue(self._send_message, msg, callback=callback, **k) + + if self.state != self.States.CALLING: + if self.state == self.States.OFFLINE: + self.connect(error=self.on_chat_connect_error) + elif self.state in (self.States.IDLE, self.States.EMPTY): + self._process_invites() + else: + log.info('Not sending message because state is %r', self.state) + self._check_connect_time() + return + else: + + body = msg.format_as('msn') + + def check_nak(emsg): + log.error('Error sending message: %r', emsg) + cmd = getattr(emsg, 'cmd', None) + if cmd == 'NAK': + self._send_message_im(msg, callback=callback) + else: + callback.error(emsg) + + self.sb.send_text_message(body, error=check_nak) + callback.success() + + def _send_message_mobile(self, fmsg, **k): + self.client.send_sms(self.buddy.phone_mobile, fmsg.format_as('plaintext')), + + def _send_message_offline(self, fmsg, callback=None, **k): + log.info('MSNConversation._send_message_offline got %r, %r', fmsg, k) + self.sb.send_message(fmsg, callback=callback) + + @callsback + def invite(self, name, callback=None): + getattr(self, 'invite_%s' % self.type, self.invite_im)(name, callback=callback) + + @callsback + def invite_im(self, name, callback=None): + # allow passing buddies + name = getattr(name, 'name', name) + + if name == self.self_buddy.name: + log.info('Not inviting self_buddy, that\'s silly') + return + + if self.state in (self.States.OFFLINE, self.States.CONNECTING): + self.maybe_queue(self.invite, name) + + if self.state != self.States.CALLING or (self.state == self.States.CALLING and self.type == "chat"): + + if self.sb and not self.sb.connected(): + log.info('%r\'s SB socket is gone. Setting switchboard to None', self) + self._set_switchboard(None) + + if self.state == self.States.CONNECTING: + if self.sb is not None: + self._set_switchboard(None) + self.maybe_queue(self.invite, name) + elif self.sb is None: + log.info('%r has no switchboard. Requesting one from %r.', self, self.protocol) + if not self.client.request_sb(success=lambda sb: (self._set_switchboard(sb), sb.connect()), + error =self.connection_error): + log.info('Forcing exit because client said %r couldn\'t have a switchboard', self) + self.state = self.States.OFFLINE + self.exit(force_close = True) + return + else: + log.info('Calling invite(%s) on SB. State is now: %s', name, self.state) + self.sb.invite(name, error=lambda sck,e: self._invite_error(name,e)) + + def timeout(): + if self.state == self.States.CALLING: + self._invite_error(name, 'timeout') + + wait_for = pref('msn.messaging.invite_wait_time', type=int, default=25) + log.info('Starting timeout for invite. duration: %d seconds', wait_for) + Timer(wait_for, timeout).start() + + if self.state in (self.States.EMPTY, self.States.IDLE): + self.state = self.States.CALLING + + def invite_mobile(self, name): + pass + + def _invite_error(self, name, e): + log.info('Error inviting %r: %r', name, e) + if (self.room_list == [self.self_buddy]) or (self.room_list == []): + self.state = self.States.EMPTY + + if getattr(e, 'error_code', None) == 215: + # Already in list! this means they're here already. + log.info('Invite error indicates buddy is already in room. Assuming they\'re here and carrying on...') + self.on_buddy_join(name) + return + + if self.sb is not None: + self._set_switchboard(None) + + if e == 'timeout': + log.info('%r never responded to invite', name) + setattr(self, '_invite_timout', getattr(self, '_invite_timeout', 0)) + if getattr(self, '_invite_timeout', 0) < common.pref('msn.messaging.max_invite_timeouts', type=int, default=3): + # just... try again. /sigh + log.info('Attempting invite for %r again...', name) + return self.invite(name) + + elif e.error_code == 217: # Contact offline or self appearing invisible + log.info('Disconnecting old switchboard- don\'t need it because buddy is offline') + self.Disconnect() + self._type_override = 'offline' + # process queue now that we're offline + log.info('%r\'s type is now: %r', self, self.type) + + self.connect() # this will call connect_offline + self._flush_queue() + return + + self.queue_error(e) + + @callsback + def invite_offline(self, name, callback = None): + log.info('Calling invite(%r) on %r', name, self.sb) + self.sb.invite(name) + callback.success() + + def queue_error(self, e=None): + log.info('Message queue for %r is being cleared, calling error callbacks', self) + + ccb, self._connect_cb = self._connect_cb, None + log.info('calling connect_cb.error: %r (error = %r)', ccb, e) + + if ccb is not None: + ccb.error() + + old_queued, self._queued[:] = self._queued[:], [] + for (f, args, kws) in old_queued: + cb = kws.get('callback', None) + if cb is not None: + try: + log.info('\t\tCalling %r(%r)', cb.error, e) + cb.error(e) + except Exception, ex: + traceback.print_exc() + continue + + def connection_error(self, emsg=None): + log.info('Error requesting SB server: %r', emsg) + ecode = getattr(emsg, 'error_code', None) + estr = getattr(emsg, 'error_str', None) + msg = None + if estr and ecode: + msg = "Error(%s:%s)" % (emsg.error_code, emsg.error_str) + elif estr: + msg = "Error(%s)" % estr + + if msg is not None: + self.system_message(msg) + + if ecode == 800: + self.system_message(_("Your messages could not be sent because too many conversation sessions have been requested.")) + + self._connection_error(emsg) + + def _connection_error(self, e=None): + self.reset_allow_once('_request') + self.state = self.States.OFFLINE + if self.sb is not None: + self._set_switchboard(None) + self.queue_error(e) + + def _cleanup(self, force_close = False): + ''' + returns true if connection should stay active + ''' + + if force_close: + del self._queued[:] + return False + + if self._queued: + self.maybe_queue(self.exit) + + if self.state == self.States.READY: + self._flush_queue() + elif self.state in (self.States.EMPTY, self.States.IDLE): + self._process_invites() + elif self.state in (self.States.CALLING, self.States.CONNECTING): + pass # _flush_queue will be called once this state is exited. + elif self.state == self.States.OFFLINE: + self.connect() + + return True + else: + return False + + @once.allow_once + def _exit(self): + log.info('exiting conversation') + if self._closed: + log.info('SB is already closed. Adding all participants into "invite" list.') + self._to_invite.update(x.name for x in self.room_list) + del self.room_list[:] + else: + self._to_invite = set() + + log.info('_to_invite is now %r (for SB = %r)', self._to_invite, self) + + self._to_invite.add(self.__chatbuddy) + self._type_override = None + + def exit(self, force_close = False): + is_chat = self.type == 'chat' + + more_to_send = self._cleanup(force_close) + if more_to_send and not is_chat: + return + + def really_exit(): + log.info('Checking if %r should "really_exit"', self) + self._exit() + + if self.p2p_clients > 0 and not force_close and not is_chat: + log.info('P2P clients still active. Not disconnecting.') + return + + self.Disconnect() + + if force_close or is_chat: + self._stop_exit_timer() + really_exit() + + else: + self._start_exit_timer(really_exit) + + Conversation.exit(self) + + def _start_exit_timer(self, f): + log.info('Starting exit timer for %r. will call %r in %r seconds', self, f, 10) + self._stop_exit_timer() + + self._exit_timer = Timer(10, f) + self._exit_timer.start() + + def _stop_exit_timer(self): + et, self._exit_timer = getattr(self, '_exit_timer', None), None + if et is not None: + log.info('Exit timer for %r has been stopped', self) + et.cancel() + + @once.allow_once + def Disconnect(self): + self.p2p_clients = 0 + log.info('Disconnecting. unregistering %r from client (%r)', self, self.client) + self.client.unregister_conv(self) + self.client._p2p_manager._unregister_transport(self) + + if get(self, 'sb', None): + self._set_switchboard(None) + self._to_invite = set(self._clean_list()) + self._to_invite.add(self.__chatbuddy) + self._to_invite.discard(self.self_buddy.name) + self.room_list[:] = [] + self.state = self.States.OFFLINE + + def on_message_send(self, msg): + pass + + def on_message_recv(self, name, msg, sms=False): + self._stop_exit_timer() + buddy = self.buddies[name] = self.protocol.get_buddy(name) + self.typing_status[buddy] = None + + if hasattr(msg, 'html'): + message = msg.html().replace('\n', '
') + content_type = 'text/html' + else: + message = msg + content_type = 'text/plain' + + did_receive = self.received_message(buddy, message, sms=sms, content_type = content_type) + + if name != self.self_buddy.name and did_receive: + Conversation.incoming_message(self) + + def on_action_recv(self, name, action_type, action_text): + self._stop_exit_timer() + + buddy = self.buddies[name] = self.protocol.get_buddy(name) + + if action_type == 'custom': + if action_text is not None: + #Translators: ex: Frank nudged you! + message = _('{name} {action}').format(name = buddy.alias, action = action_text) + self.system_message(message) + else: + text = dict( + wink = _('{name} winked at you!'), + nudge = _('{name} nudged you!'), + ).get(action_type, None) + if text is not None: + message = text.format(name = buddy.alias) + self.system_message(message) + + def on_typing_notification(self, name, typing): + self._stop_exit_timer() + buddy = self.buddies[name] = self.protocol.get_buddy(name) + self.typing_status[buddy] = 'typing' if typing else None + + log.info('%s is %styping', name, '' if typing else 'not ') + + def on_proto_error(self, errmsg): + log.error('Unexpected protocol error from switchboard(%r): %r', self.sb, errmsg) + self.queue_error(errmsg) + + def on_buddy_join(self, name): + + buddy = self.buddies[name] = self.protocol.get_buddy(name) +# if not buddy.online: +# buddy.setnotifyif('status','available') + + if buddy is not self.self_buddy and self.self_buddy not in self.room_list: + self.on_buddy_join(self.self_buddy.name) + + if buddy not in self.room_list: + self.room_list.append(buddy) + if not self.__chatbuddy: + self.__chatbuddy = name + + self.event('contacts_changed') + + if self.state not in (self.States.READY,): + self.state = self.States.READY + log.debug('Calling flush_queue') + self._flush_queue() + + log.info('Got buddy join event (%s). State is now: %s. self.ischat = %r', name, self.state, self.ischat) + + self.notify('ischat') + + super(MSNConversation, self).buddy_join(buddy) + + #assert self.state in (self.States.READY, self.States.CONNECTING), self.state + + def on_buddy_leave(self, name, notify=True): + self._type_override = None + buddy = self.buddies[name] = self.protocol.get_buddy(name) + + try: + self.room_list.remove(buddy) + except ValueError: + log.info('Buddy %r wasn\'t in room but left anyway (?)', name) + + in_room = set(self._clean_list()) - self._to_invite + in_room.discard(self.self_buddy.name) + if not in_room: + # this is not really a timeout, but the desired behavior + # is the same. + self.on_buddy_timeout(name) + elif in_room and name != self.self_buddy.name: + self._to_invite.discard(name) + + self.typing_status.pop(buddy, None) + self.event('contacts_changed') + super(MSNConversation, self).buddy_leave(buddy) + + self.notify('ischat') + + def on_buddy_timeout(self, name): + self._type_override = None + buddy = self.buddies[name] = self.protocol.get_buddy(name) + + if buddy in self.room_list: + # since this function gets called from buddy_leave, buddy + # might have already been removed + self.room_list.remove(buddy) + + # Since they timed out, they will be reinvited on the next action + if name not in self._to_invite: + self._to_invite.add(name) + + # If out chatbuddy is missing, this will be our new chatbuddy + if self.protocol.get_buddy(self.__chatbuddy) not in self.room_list: + self.__chatbuddy = name + + # if our roomlist is empty, our last buddy left and our session is idle + if (self.room_list == [self.self_buddy]) or (self.room_list == []): + self.state = self.States.IDLE + log.info('All buddies have disconnected. _to_invite is %r. State is now: %s', + self._to_invite, self.state) + log.info('Producers: %r', self.__prodq) + log.info('Queued: %r', self._queued) + if self._queued: + self._process_invites() + else: + log.info('No invites to perform and room list is empty. Disconnecting...') + self.Disconnect() # So we don't break stupid WLM9beta + + self.typing_status.pop(buddy, None) + self.event('contacts_changed') + + def zero_connect_time(self, *a): + self.__connecting_time = 0 + + @callsback + def connect(self, callback=None): + self._stop_exit_timer() + + if self.state == self.States.OFFLINE: + self.reset_allow_once() + self.__connecting_time = time.time() + + callback.success += self.zero_connect_time + + if self._connect_cb is None: + self._connect_cb = callback + else: + self._connect_cb.success += callback.success + self._connect_cb.error += callback.error + + log.info('msnconv.connect got callback: %r', callback) + getattr(self, 'connect_%s' % self.type, self.connect_im)() + + def connect_im(self): + + if self._closed and self.sb is not None: + self._set_switchboard(None) + + if self.sb is None: + if self.state == self.States.CONNECTING: + log.warning('No SB but state was connecting') + return + self._request() + elif self.state == self.States.CONNECTING: + log.info(' got connect request but one already queued') + else: + log.info('Not doing anything about connection request because there\'s already a SB ' + '(or an active request for one): sb=%r, self.state=%r', self.sb, self.state) + + if (self.room_list == [self.self_buddy]) or (self.room_list == []): + self.state = self.States.EMPTY + self._process_invites() + else: + self.state = self.States.READY + self._flush_queue() + + def _request_connection_error(self, e = None): + log.info('connection error for SB: %r', e) + self.reset_allow_once('_request') + + @once.allow_once + def _request(self): + if self._request_connection_error not in self._connect_cb.error: + self._connect_cb.error += self._request_connection_error + + log.info('%r Requesting SB. State was %s, changing to CONNECTING', self, self.state) + self.state = self.States.CONNECTING + if not self.protocol.request_sb(success=lambda s: (self._set_switchboard(s), s.connect()), + error =self.connection_error): + self.state = self.States.OFFLINE + self.exit(force_close=True) + return + + wait_for = pref('msn.messaging.connect_wait_time', type=int, default=25) + log.info('Starting timeout for connect. duration: %d seconds', wait_for) + Timer(wait_for, self.request_timeout).start() + + def request_timeout(self): + if self.sb is None: + log.error('SB request for %r has timed out', self) + self._connection_error(Exception('timeout')) + self.reset_allow_once('_request') + + + def connect_fed(self, to_invite = ()): + if self.state in (self.States.OFFLINE, self.States.CONNECTING): + self.state = self.States.CONNECTING + log.info('Setting up NSSBAdapter. State is now: %s', self.state) + sb = self.protocol.make_sb_adapter(to_invite = to_invite) + self._set_switchboard(sb) + sb.connect() + else: + log.info('Didn\'t set up NSSBAdapter- state was incorrect (%r)', self.state) + + def connect_offline(self): + + if self.sb and self.sb.connected(): + log.info('Disconnecting old switchboard before connect_offline') + self.Disconnect() + + if self.state in (self.States.OFFLINE, self.States.CONNECTING): + self.state = self.States.CONNECTING + log.info('Setting up OfflineSBAdapter. State is now: %s', self.state) + sb = oim.OfflineSBAdapter(self.client, self.buddy) + self._type_override = 'offline' + self._set_switchboard(sb) + sb.connect() + log.info('OfflineSBAdapter all set up') + else: + log.info('Didn\'t set up OfflineSBAdapter- state was incorrect (%r)', self.state) + + def connect_mobile(self): + pass + + def send_typing_status(self, status): + """ + Status can be None, 'typed' or 'typing'. But, for MSN + only 'typing' is understood. + """ + self._stop_exit_timer() + getattr(self, 'send_typing_status_%s' % self.type, self.send_typing_status_im)(status) + + def send_typing_status_im(self, status): + if not all((self.sb, getattr(self.sb, 'connected', lambda: False)())): + self.state = self.States.OFFLINE + + if status != 'typing': + return + + if self.state != self.States.READY: + if self.state == self.States.OFFLINE: + self.connect() + elif self.state in (self.States.IDLE, self.States.EMPTY): + self._process_invites() + else: + log.info('Not sending typing status because state is %r', self.state) + self._check_connect_time() + return + + log.info('Typing status: %s (%r)', status, self) + self.sb.send_typing_status(self.self_buddy.name, status) + + def send_typing_status_mobile(self, status): + pass + + def send_typing_status_offline(self, status): + log.debug('No typing status for offline contacts') + + def on_p2p_recv(self, name, data): + self._stop_exit_timer() + self.event('recv_data', self, name, data) + #self._processq() + + @callsback + def _processq(self, callback=None): + + while self.__prodq: + next = self.__prodq[0] + data = next.more() + if data is None: + log.debug('Data was None, popping producer') + if self.__prodq: + self.__prodq.pop(0) + continue + else: + recip = next.recipient + log.log(1, 'Got %d bytes of data (+%d overhead) to send to %s', + len(data)-self.p2p_overhead, self.p2p_overhead, recip) + assert recip is not None + callback.success += lambda *a, **k:self._processq() + self.p2p_send(recip, data, callback=callback) + return + + log.info('Producer queue is empty') + + @callsback + def push_with_producer(self, prod, callback=None): + self.__prodq.append(prod) + self._processq(callback=callback) + + def fed_message(self, msg): + bname = msg.args[0] + if self.sb is None: + self.connect_fed(to_invite = (bname,)) + self.sb.incoming(msg) + + def connected(self): + return self.sb is not None and self.sb.connected() diff --git a/digsby/src/msn/MSNErrcodes.py b/digsby/src/msn/MSNErrcodes.py new file mode 100644 index 0000000..149fbc1 --- /dev/null +++ b/digsby/src/msn/MSNErrcodes.py @@ -0,0 +1,81 @@ +""" +List of error codes and the readable messages assocatiated + +From U{http://msnpiki.msnfanatic.com/index.php/Reference:Error_List} +""" + +codes = { + None:None, + 100: 'Unknown', + 200: 'Invalid Syntax', + 201: 'Invalid parameter', + 205: 'Invalid principal', + 206: 'Domain name missing', + 207: 'Already logged in', + 208: 'Invalid principal', + 209: 'Nickname change illegal', + 210: 'Principal list full', + 213: 'Invalid rename request?', + 215: 'User already on list', + 216: 'User not on list', + 217: 'User not online', + 218: 'Already in mode', + 219: 'User is in the opposite list', + 223: 'Too many groups', + 224: 'Invalid group', + 225: 'User not in group', + 227: 'Group not empty', + 228: 'Group with same name already exists', + 229: 'Group name too long', + 230: 'Cannot remove group zero', + 231: 'Invalid group', + 240: 'Invalid RML/ADL command', + 280: 'Switchboard failed', + 281: 'Transfer to switchboard failed', + 282: 'P2P Error?', + 300: 'Required field missing', + 302: 'Not logged in', + 402: 'Error accessing contact list', + 403: 'Error accessing contact list', + 420: 'Invalid Account Permissions', + 500: 'Internal server error', + 501: 'Database server error', + 502: 'Command disabled', + 504: 'Service temporarily disabled', + 510: 'File operation failed', + 511: 'Banned', + 520: 'Memory allocation failed', + 540: 'Challenge response failed', + 600: 'Server is busy', + 601: 'Server is unavailable', + 602: 'Peer nameserver is down', + 603: 'Database connection failed', + 604: 'Server is going down', + 605: 'Server unavailable', + 700: 'Could not create connection', + 710: 'Bad CVR parameters sent', + 711: 'Write is blocking', + 712: 'Session is overloaded', + 713: 'Calling too rapidly', + 714: 'Too many sessions', + 715: 'Not expected', + 717: 'Bad friend file', + 731: 'Not expected', + 800: 'Requesting too often', + 910: 'Server too busy', + 911: 'Server is busy', + 912: 'Server too busy', + 913: 'Not allowed while invisible', + 914: 'Server unavailable', + 915: 'Server unavailable', + 916: 'Server unavailable', + 917: 'Authentication failed', + 918: 'Server too busy', + 919: 'Server too busy', + 920: 'Not accepting new principals', + 921: 'Server too busy', + 922: 'Server too busy', + 923: 'Kids\' Passport without parental consent', + 924: 'Passport account not yet verified', + 928: 'Bad ticket', + } \ No newline at end of file diff --git a/digsby/src/msn/MSNObject.py b/digsby/src/msn/MSNObject.py new file mode 100644 index 0000000..88e1589 --- /dev/null +++ b/digsby/src/msn/MSNObject.py @@ -0,0 +1,187 @@ +import hashlib + +import util +import util.xml_tag +import msn +from logging import getLogger + +log = getLogger('msn.object') + +class MSNObject(object): + ''' + MSNObject + + An abstract representation of an 'MSN object' which can refer to a + wink, nudge, display picture, file transfer, scribble, sound clip, + or some other stuff. + ''' + __slots__ = '''creator size type location friendly sha1d sha1c + _friendly _sha1d _sha1c stamp _stamp contenttype contentid avatarid + avatarcontentid _avatarcontentid _xml _extra'''.split() + + EUF = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}" + + types = { + '2' : 'emoticons', + '3' : 'icon', + '5' : 'bg_image', + '8' : 'wink', + '11': 'sound', + '12': 'state', + } + def __init__(self, Creator, Type, Location, Size, SHA1D, Friendly=u'\0', SHA1C=None, + contenttype=None, contentid=None, stamp=None, avatarid=None, avatarcontentid=None, **kwds): + ''' + MSNObject(dict) + + Build an msn object from a dictionary. the good keys are: + creator + size + type + location + friendly + sha1d + sha1c + + Generally, the required fields are creator, size, type, location, + and sha1d. Or, create an empty MSNObject (no args) and call from_xml() + on it + ''' + object.__init__(self) + self.creator = Creator + self.type = Type + self.size = Size + self.location = Location + self._sha1d = SHA1D + self._friendly = Friendly + self._sha1c = SHA1C or self.calc_sha1c() + self._stamp = stamp + self._avatarcontentid = avatarcontentid + self.contenttype = contenttype + self.contentid = contentid + self.avatarid = avatarid + self._xml = None + self._extra = kwds + + def get_sha1d(self): + return self._sha1d.encode('base-64').strip() + + def set_sha1d(self, new_val): + self._sha1d = new_val.replace(' ', '+').decode('base-64') + + def get_friendly(self): + return self._friendly.encode('utf-16-le').encode('base-64').strip() + + def set_friendly(self, new_val): + self._friendly = new_val.decode('base-64').decode('utf-16-le') + + def calc_sha1c(self): + to_hash = 'Creator%sSize%sType%sLocation%sFriendly%sSHA1D%s' % \ + (self.creator, self.size, self.type, self.location, self.friendly, self.sha1d) + return hashlib.sha1(to_hash).digest() + + def get_sha1c(self): + return self.calc_sha1c().encode('base-64').strip() + + def set_sha1c(self, new_val): + if new_val != self.sha1c: + raise ValueError, 'SHA1C hash is not correct' + + def get_stamp(self): + return self._stamp if self._stamp is None else self._stamp.encode('base-64').strip() + + def set_stamp(self, new_val): + if new_val is None: + self._stamp = None + else: + self._stamp = new_val.decode('base-64') + + def get_avatarcontentid(self): + return self._avatarcontentid \ + if self._avatarcontentid is None \ + else msn.util.base64_encode(self._avatarcontentid) + + def set_avatarcontentid(self, new_val): + if new_val is None: + self._avatarcontentid = None + else: + self._avatarcontentid = msn.util.base64_decode(new_val) + + + friendly = property(get_friendly, set_friendly) + sha1d = property(get_sha1d, set_sha1d) + sha1c = property(get_sha1c, set_sha1c) + stamp = property(get_stamp, set_stamp) + avatarcontentid = property(get_avatarcontentid, set_avatarcontentid) + + def to_xml(self): + #for attr in 'stamp avatarcontentid contenttype contentid avatarid'.split(): + # val = getattr(self, attr) + # if val is not None: + # attrs[attr] = val + + if self._xml is None: + + # stupid libpurple doesnt know how to do XML. + # so we have to put the attrs in the right order, + # and we replace the " />" at the end with "/>". + # note the missing space. + + t = util.xml_tag.tag('msnobj') + t._attrs = util.odict() + t._attrs['Creator'] = self.creator + t._attrs['Size'] = self.size + t._attrs['Type'] = self.type + t._attrs['Location'] = self.location + t._attrs['Friendly'] = self.friendly + t._attrs['SHA1D'] = self.sha1d + t._attrs['SHA1C'] = self.sha1c + + xml = t._to_xml(pretty=False).strip() + xml = xml[:-3] + '/>' + self._xml = xml + + return self._xml + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): + try: + for attr in ('creator', 'size', 'sha1d',): + mine = getattr(self, attr) + theirs = getattr(other, attr) + if str(getattr(self, attr)) != str(getattr(other, attr)): + return False + except AttributeError, e: + return False + else: + return True + + def __repr__(self): + try: + return self.to_xml() + except: + return object.__repr__(self) + + @classmethod + def parse(cls, xml): + ''' + Build an MSNObject from an xml snippet that looks something like this: + + + ''' + t = util.xml_tag.tag(xml) + + o = cls(**t._attrs) + o.stamp = t._attrs.get('stamp', None) + o.avatarcontentid = t._attrs.get('avatarcontentid', None) + o.friendly, o.sha1d = [t[k] for k in ['Friendly', 'SHA1D']] + try: + o.sha1c = t['SHA1C'] + except: + o.sha1c = o.sha1c + o._xml = xml + return o diff --git a/digsby/src/msn/MSNSocket.py b/digsby/src/msn/MSNSocket.py new file mode 100644 index 0000000..9a018af --- /dev/null +++ b/digsby/src/msn/MSNSocket.py @@ -0,0 +1,490 @@ +import socket +import threading +import logging +import collections + +import common +import util +import util.Events as Events + +log = logging.getLogger('msn.sock') +#log.setLevel(1) + +import msn +import msn.Msnifier + +dummy = lambda *a, **k: None + +def trid(max=0x7FFFFFFF, i=0): + while True: + i += 1 + yield i + if i == max: i = 0 + +class MSNSocketBase(Events.EventMixin): + events = Events.EventMixin.events | set(( + 'on_connect', + 'on_send', + 'on_conn_error', + 'on_close', + 'on_message', + + )) + + delim = '\r\n' + + def __init__(self): + Events.EventMixin.__init__(self) + + self.trid = trid() + self.callbacks = collections.defaultdict(list) + if not hasattr(self, '_lock'): + self._lock = threading.RLock() + + self.timeouts = {} + + def is_payload_command(self, dlist): + import msn.MSNCommands as MSNC + return MSNC.is_payload_command(dlist) + + def set_trid(self, msgobj, trid): + if trid is True: + msgobj.trid = self.trid.next() + elif type(trid) is int: + msgobj.trid = trid + + def set_callbacks(self, msgobj, callback): + if callback is sentinel: + callback = None + + if msgobj.is_trid: + self.set_timeout(msgobj) + self.callbacks[msgobj.trid].append(callback) + else: + self.callbacks[msgobj.cmd].append(callback) + + def set_timeout(self, msgobj): + timeout = getattr(msgobj, 'timeout', None) + if timeout is not None and common.pref('msn.socket.use_timeout', type = bool, default = False): + log.info('Starting timeout for %r', msgobj) + timer = util.Timer(timeout, self.timeout_handler(msgobj)) + self.timeouts[msgobj.trid] = timer + timer.start() + + def timeout_handler(self, msgobj): + def handler(): + log.debug('This message has timed out: %r', msgobj) + msgcopy = msgobj.copy() + msgcopy.timeout = msgobj.timeout + msgcopy.retries = msgobj.retries - 1 + msgcopy.trid = 0 + if msgcopy.retries == 0: # yes, this does mean you can retry infinitely with a negative number + return + + log.debug('Retrying this message that timed out: %r', msgcopy) + with self._lock: + callback = self.callbacks.pop(msgobj.trid, None) + self.send(msgcopy, trid = True, callback = callback) + + return handler + + def unset_timeout(self, msgobj, include_previous = True): + if not msgobj.is_trid: + return + + if include_previous: + for i in range(msgobj.trid): + self.unset_timeout_single(i) + + self.unset_timeout_single(msgobj.trid) + + def unset_timeout_single(self, key): + try: + timer = self.timeouts.pop(key, None) + if timer is not None: + timer.stop() + except (IndexError, KeyError): + pass + + def pause(self): + return + def unpause(self): + return + + @Events.event + def on_connect(self): + return self + + @Events.event + def on_send(self, data): + "'data' has been sent" + + @Events.event + def on_conn_error(self, e=None, **k): + log.info('%r had a connection error: %r', self, e) + return self, e + + @Events.event + def on_close(self): + return self + + @util.lock + def unset_callbacks(self, msg): + callback = None + try: + callback = self.callbacks[msg.trid or msg.cmd][0] + except (KeyError, IndexError), e: + pop = False + else: + pop = True + + if pop: + if msg.is_trid: + self.unset_timeout(msg, include_previous = False) + self.callbacks.pop(msg.trid, None) + + elif not msg.trid: + self.callbacks[msg.cmd].pop(0) + + return callback + + def adjust_message(self, msg): + if msg.cmd == 'QNG': + msg.cmd = 'PNG' + msg.trid = 0 + + return msg + + def on_message(self, msg): + self.event('on_message', msg) + + msg = self.adjust_message(msg) + callback = self.unset_callbacks(msg) + if callback is None: + return + + try: + if msg.is_error: f = callback.error + else: f = callback.success + except AttributeError, e: + log.error('AttributeError in msnsocket.on_message: %r\ncallback was: %r', e, callback) + + log.debug('MSNSocket calling %r', f) + + try: + f(self,msg) + except Exception, e: + log.error('Error in callback') + import traceback; traceback.print_exc() + + def close(self): + while self.timeouts: + try: + k,v = self.timeouts.popitem() + except KeyError: + break + else: + if v is not None: + v.stop() + +class MSNSocket(MSNSocketBase, common.socket): + speed_limit = None + speed_limit = 1 + speed_window = .150 + + ac_in_buffer_size = 16384 + ac_out_buffer_size = 16384 + + #@callsback + def __init__(self, + #processor, + #server, + #callback=None + ): + common.socket.__init__(self) + MSNSocketBase.__init__(self) + +# assert isinstance(processor, CommandProcessor) +# self.processor = processor + self.set_terminator(self.delim) + self.data = '' + self.expecting = 'command' + + #self.connect_cb = callback + + self._server = None + + self.rater = msn.Msnifier.Msnifier(self) + self.rater.start() + + self._bc_lock = threading.RLock() + self.bytecount = [(0, util.default_timer())] + + log.debug('%r created', self) + + def get_local_sockname(self): + return self.socket.getsockname() + + def connect_args_for(self, type, addr): + return type, addr + + def connect(self, type, host_port): + self._scktype = type + try: + host, port = host_port + except (ValueError, TypeError): + raise TypeError('%r address must be (host, port) not %r (%r)', + type(self).__name__, type(host_port), host_port) + + if self._server is not None: + raise ValueError("Don't know which server to use! self._server = %r, host_port = %r.", + self._server, host_port) + self._server = host_port + log.info('connecting socket to %r', self._server) + try: + common.socket.connect(self, self._server, + error=self.on_conn_error, + ) + except Exception, e: + self.on_conn_error(e) + return + + self.bind_event('on_message', lambda msg, **k: log.debug('Received %r', msg)) + + _connect = connect + + def _disconnect(self): + self.close_when_done() + + @property + def _closed(self): + return not (getattr(self.socket, 'connected', False)) + + def __repr__(self): + + try: + s = 'connected to %r' % (self.socket.getpeername(),) + except socket.error: + s = 'not connected' + + return "<%s %s>" % (type(self).__name__, s,) + + @util.callsback + def test_connection(self, callback=None): + if self._scktype == 'NS': + self.send(msn.Message('PNG'), callback=callback) + else: + log.info('Not testing connection because this is not an NS socket.') + callback.success() + + def handle_connect(self): + log.debug("connection established") + self.on_connect() + + def handle_expt(self): + log.warning('OOB data. self.data = %r', self.data) + self.close() + + @util.lock + def collect_incoming_data(self, data): + self.data += data + + def set_terminator(self, term): + assert term + common.socket.set_terminator(self, term) + + def found_terminator(self): + + self.data += self.delim + try: + with self._lock: + self.data, data = '', self.data + + dlist = data.split(' ') + cmd = dlist[0] + + if self.expecting == 'command' and self.is_payload_command(dlist): + self.expecting, self.data = 'payload', data + + try: + new_term = int(dlist[-1]) + except ValueError: + new_term = 0 + + if not new_term: + self.found_terminator() + new_term = self.delim + + return self.set_terminator(new_term) + elif self.expecting == 'payload': + self.expecting = 'command' + data = data[:-len(self.delim)] # strip off newline that was appended + payload = True + else: + assert self.expecting == 'command' + payload = False + + self.set_terminator(self.delim) + if True or len(data) < 5000: + log.info_s('IN : %r', data) + msg = msn.Message.from_net(data, payload) + # Release lock + except Exception, e: + log.info('error parsing message, testing connection\nError was %r', e) + self.test_connection(success=self.conn_ok, error=self.conn_error) + import traceback + traceback.print_exc() + else: + self.on_message(msg) + + def handle_close(self): + log.warning('socket closed, self.data = %r', self.data) + if self.rater is not None: + self.rater.stop() + self.rater = None +# self.processor.close_transport(self) + + self.close() + + def close(self): + log.warning('socket closing, self.data = %r', self.data) + MSNSocketBase.close(self) + common.socket.close(self) + self.on_close() + + def send_gen(self, gen, priority=5): + if self.rater is not None: + self.rater.send_pkt(gen, priority) + else: + log.error('Can\'t send generator after rater has been stopped') + + def send(self, msgobj, trid=sentinel, callback=None, **kw): + if isinstance(msgobj, buffer): + try: + data = str(msgobj) + if True or len(data) < 5000: + log.info_s("OUT : %r" % (data,)) + retval = common.socket.send(self, data) + except Exception as e: + raise e + else: + getattr(callback, 'after_send', lambda:None)() + return retval + + self.set_trid(msgobj, trid) + self.set_callbacks(msgobj, callback) + log.debug('Sending %r', msgobj) + if self.rater is not None: + self.rater.send_pkt(str(msgobj), callback = callback, **kw) + else: + if not self._send(str(msgobj)): + callback.error("Message dropped") + else: + log.warning("Calling after_send: %r", callback.after_send) + callback.after_send() + + send = util.callsback(send, ('success', 'error', 'after_send')) + + def conn_ok(self): + log.info('connection test passed') + + def conn_error(self): + log.warning('connection test failed') + self.close_when_done() + self.on_conn_error() + + def _send(self, data, *a, **k): + log.log_s(0,'sent: %s' % data) + message_sent = False + with self._lock: + try: + + common.socket.push(self, data, *a, **k) + message_sent = True +# if common.socket.send(self, data, *a, **k): +# message_sent = True +# else: +# log.critical('Message dropped in %r, data = %r', self, data) + + except Exception, e: + log.critical('Error sending message in %r. error was %r, data = %r', self, e, data) + + try: + if data == "OUT\r\n": + e.verbose = False + except Exception: + pass + + self.handle_error(e) + if self.connected: + self.close() + return + + if message_sent: + self.on_send(data) + + now = util.default_timer() + with self._bc_lock: + self.bytecount.append((len(data), now)) + else: + log.info("recursively calling _send... watch out!") + self._send(data, *a, **k) + + return message_sent + + def time_to_send(self, data): + if self.speed_limit is None: + return 0 + + now = util.default_timer() + with self._bc_lock: + self.bytecount = filter(lambda t:(now-t[1]) self.speed_limit: break + + tts = (bytes/self.speed_limit*self.speed_window) + interval + #tts = 0 if tts < .005 else tts + log.log(5, 'currently sending at %d bytes/sec', send_rate) + log.debug('sleeping for %r seconds' % tts) + return tts + + def close_when_done(self): + ''' + close_when_done() + + sends the 'OUT' command and then closes itself (when done!) + ''' + if self.rater is not None: + self.rater.stop() + self.rater = None + + if getattr(self, 'closed', False): + return + self.closed = True + + try: + self.send(msn.Message('OUT')) + except socket.error: + pass + + try: + self.close() + except socket.error: + pass + +#class MSNSSL(MSNSocket): +# +# @lock +# def make_socket(self): +# MSNSocket.make_socket(self) +# from socket import ssl +# self.socket = ssl(self.socket) diff --git a/digsby/src/msn/MSNUtil.py b/digsby/src/msn/MSNUtil.py new file mode 100644 index 0000000..b601873 --- /dev/null +++ b/digsby/src/msn/MSNUtil.py @@ -0,0 +1,236 @@ +from __future__ import with_statement + +from struct import pack, unpack +from urllib2 import quote, unquote +from base64 import b64encode, b64decode +from string import zfill +from util import get_func_name, get_func, pythonize, to_storage, Timer, default_timer, fmt_to_dict +from util.auxencodings import fuzzydecode +from logging import getLogger + +log = getLogger('msn.util') + +from mail.passport import make_auth_envelope + +msgtypes = \ +{'text/x-msmsgsprofile' : 'profile', + 'text/x-msmsgsinitialmdatanotification' : 'notification', + 'text/x-msmsgscontrol' : 'control', + 'text/plain' : 'plain', + 'text/x-msmsgsinitialemailnotification' : 'init_email', + 'text/x-msmsgsemailnotification' : 'new_email', + 'text/x-msmsgsinvite' : 'invite', + 'text/x-msnmsgr-datacast' : 'datacast', + 'application/x-msnmsgrp2p' : 'p2p', + 'text/x-clientcaps' : 'caps', + 'text/x-msmsgsoimnotification' : 'oims',} + +def utf8_encode(str): + ''' + utf8_encode(str) + + function for lazy programmers to utf8 encode a string. + ''' + return unicode(str, "utf-8") + +def utf8_decode(str): + ''' + utf8_decode(str) + + function for lazy programmers to utf8 decode a string. + ''' + + return str.encode("utf-8") + +def url_encode(str): + ''' + url_encode(str) + + function for lazy programmers to url encode a string. + ''' + + return quote(str) + +def url_decode(str): + ''' + url_decode(str) + + function for lazy programmers to url decode a string. + ''' + return unquote(str) + +def base64_encode(s): + return s.encode('base64').replace('\n', '') + +def base64_decode(s): + return s.decode('base64') + +def utf16_encode(str): + try: + return unicode(str, 'utf-16') + except TypeError: + if isinstance(str, unicode): + return str.encode('utf-16') + else: + return fuzzydecode(s, 'utf-8').encode('utf-16') + +def utf16_decode(str): + return str.decode('utf-16') + +mime_to_dict = fmt_to_dict(';','=') +csd_to_dict = fmt_to_dict(',','=') + +def mime_to_storage(str): + ''' + turn mime headers into a storage object + ''' + info = mime_to_dict(str) + for k in info.keys(): + info[pythonize(k)] = info[k] + + return to_storage(info) + +def csd_to_storage(str): + ''' + turn mime headers into a storage object + ''' + info = csd_to_dict(str) + for k in info.keys(): + info[pythonize(k)] = info.pop(k) + + return to_storage(info) + +def gen_msg_payload(obj, socket, trid, msg, src_account, src_display, *params): + """ + MSG (MeSsaGe) with a payload. + There are different payload types, so the appropriate function is called + to handle that type of payload. + """ + type = msg.get('Content-Type', None) + if type: + type = type.split(';')[0] + if type not in msgtypes: + log.critical("Can't handle type %s", type) + return + func = get_func(obj, get_func_name(2) + '_%s' % msgtypes[type]) + if func: func(socket, msg, src_account, src_display, *params) + else: assert False + +def dict_to_mime_header(d): + hdr = ['MIME-Version: 1.0'] + ctype = 'Content-Type' + ctype_val = d.pop(ctype, 'text/plain; charset=UTF-8') + hdr.append('%s: %s' % (ctype,ctype_val)) + + for k,v in d.items(): + if isinstance(v, dict): + v = dict_to_mime_val(v) + hdr.append('%s: %s' % (k,v)) + + return '\r\n'.join(hdr) + '\r\n' + +def dict_to_mime_val(d): + s = [] + for k,v in d.items(): + s.append( (('%s=' % k) if k else '') + '%s' % v) + + return '; '.join(s) + +def bgr_to_rgb(c): + assert len(c) <= 6 + s = ('0'*6+c)[-6:] + b,g,r = [a+b for a,b in zip(s[::2], s[1::2])] + return r+g+b + +def rgb_to_bgr(s): + assert len(s) == 6 + r,g,b = [a+b for a,b in zip(s[::2], s[1::2])] + s = b+g+r + while s.startswith('0'): s = s[1:] + return s + +class FuncProducer(object): + def __init__(self, f): + object.__init__(self) + self.f = f + def more(self): + try: v = self.f() + except: v = None + finally: return v + + +# +#class MSNTimer(ResetTimer): +# +# def start(self): +# Timer.start(self) +# #self.set_time(self._interval) +# +# def compute_timeout(self): +# if self.done_at == None: +# self._last_computed = default_timer() + 5 +# return self._last_computed +# else: +# return Timer.compute_timeout(self) +# +# def process(self): +# self.done_at = None +# self._func(*self._args, **self._kwargs) +# +# def set_time(self, t): +# import threading +# print 'msntimer:', threading.currentThread().getName() +# with self._cv: +# self.done_at = default_timer() + t +# self._cv.notifyAll() +# +# print 'msntimer out' + +def q_untilready(func): + def wrapper(self, *a, **k): + + if self.state == 'ready' and self.session_id == None and self.type == 'sb': + print "ERROR STATE DETECTED: CALLING DISCONNECT", self + self.disconnect() + + print 'in quntilready -- %r' % self + + if self.state != 'ready': + self._q.append((func, (self,)+a, k)) + + if self.state == 'disconnected': + self.connect() + elif self.state != 'calling': + self.invite(self.buddy) + else: + return func(self, *a, **k) + + return wrapper + +import functools +def dispatch(f): + @functools.wraps(f) + def wrapper(self,*a,**k): + print 'DISPATCH type:',self.type + fname = '_%s_%s' %(f.func_name.lstrip('_'), self.type) + if f(self, *a,**k): + print 'dispatch: calling %s' %fname + return getattr(self,fname)(*a,**k) + else: + print 'dispatch: not calling %s' %fname + return False + return wrapper + +def HashNonce(nonce): + import hashlib + hash = hashlib.sha1(nonce.bytes_le).digest() + return CreateGuidFromData(2, hash) + +def CreateGuidFromData(ver, data): + import uuid + import msn.P2P as P2P + + if ver == P2P.Version.V1: + return uuid.UUID(bytes_le = data[-16:]) + else: + return uuid.UUID(bytes_le = data[:16]) diff --git a/digsby/src/msn/MsnHttpSocket.py b/digsby/src/msn/MsnHttpSocket.py new file mode 100644 index 0000000..739ab3f --- /dev/null +++ b/digsby/src/msn/MsnHttpSocket.py @@ -0,0 +1,304 @@ +import threading +import traceback +import collections +import time +import logging +import httplib +import pprint +import threading + +import util +import util.allow_once as once +import util.httplib2 as httplib2 +import util.threads.threadpool as threadpool +import common +import common.asynchttp as asynchttp + +log = logging.getLogger('msnhttp') + +import msn + +MIMEParse = msn.util.mime_to_dict + +class MsnHttpSocket(msn.MSNSocketBase): + POLLINTERVAL = 3 + proto = 'http' + gateway_ip = 'gateway.messenger.hotmail.com' + gateway_path = '/gateway/gateway.dll' + gateway_port = 80 + + def get_local_sockname(self): + return ('localhost', 0) + + @property + def endpoint(self): + if self.gateway_port != httplib.HTTP_PORT: + s = '%s://%s:%s%s' % (self.proto, self.gateway_ip, self.gateway_port, self.gateway_path) + else: + s = '%s://%s%s' % (self.proto, self.gateway_ip, self.gateway_path) + + return s + + def __init__(self, *a, **k): + self._session_id = None + msn.MSNSocketBase.__init__(self, *a, **k) + self._q = [] + self._waiting = False + self._poller = util.RepeatTimer(self.POLLINTERVAL, self._poll) + self._poller._verbose = False + self._closed = False + self._poll_in_queue = False + + self._paused = False + + def connect(self, type_host): + type, host = self._parse_addr(type_host) + self.typehost = type_host + self.type = type + self.host = self._server = host + self.on_connect() + + _connect = connect + + def _parse_addr(self, type_addr): + try: + type, addr = type_addr + except (ValueError, TypeError): + raise TypeError('%r.connect argument must be (type, addr) not %r (%r)', type(self).__name__, type(type_addr), type_addr) + + bad_addr = False + port = None + if len(addr) == 1: + host, port = addr[0], 80 + elif isinstance(addr, basestring): + host, port = util.srv_str_to_tuple(addr, 80) + elif len(addr) == 2: + host, port = addr + else: + bad_addr = True + + try: + port = int(port) + except ValueError: + bad_addr = True + + if bad_addr: + raise TypeError('%r.connect argument\'s second element must be either string ("srv" or "srv:port") or tuple (("srv", port) or ("srv",)).'\ + "Got %r instead" % addr) + + return type, host + + def connect_args_for(self, type, addr): + return (type.upper(), addr), + + def _poll(self): + if not self._waiting and not self._poll_in_queue: + self._poll_in_queue = True + self.send(None) + + def pause(self): + self._paused = True + def unpause(self): + self._paused = False + common.netcall(self.process) + + @util.callsback + def send(self, msgobj, trid=sentinel, callback=None, **kw): + self._q.append((msgobj, trid, callback, kw)) + + if not self._paused: + common.netcall(self.process) + + def process(self): + if not self._q or self._waiting: + return + + self._waiting = True + + data = [] + sending = [] + queue, self._q[:] = self._q[:], [] + + while queue: + msgobj, trid, callback, kw = queue.pop(0) + if msgobj is not None: + self.set_trid(msgobj, trid) + self.set_callbacks(msgobj, callback) + data.append(str(msgobj)) + else: + self._poll_in_queue = False + + sending.append(callback) + + if self._session_id is None: + url_kws = dict(Action = 'open', Server = self.type, IP = self.host) + elif len(data) == 0: + url_kws = dict(Action = 'poll', SessionID = self._session_id) + else: + url_kws = dict(SessionID = self._session_id) + + data = ''.join(data) + + req = self.make_request(url_kws, data = data) + #log.debug(req.get_selector()) + + def _transport_error(_req = None, _resp = None): + log.error('Transport error in MsnHttpSocket: req = %r, resp = %r', _req, _resp) + + if isinstance(_req, Exception): + e = _req + elif isinstance(_resp, Exception): + e = _resp + else: + e = _resp + + for cb in sending: + cb_error = getattr(callback, 'error', None) + if cb_error is not None: + cb_error(self, e) + + try: + del self.gateway_ip # reset to class default- maybe host is bad? + except AttributeError: + pass + self._on_send_error(e) + + asynchttp.httpopen(req, success = self._on_response, error = _transport_error) + + def fix_session_id(self, sess): + + return sess + + #if sess is None: + # return None + + #parts = sess.split('.') + #if len(parts) > 3: + # parts = parts[-3:] + + #return '.'.join(parts) + + def _on_response(self, request, response): + if request.get_data(): + log.debug_s('OUT : %r', request.get_data()) + + if self._session_id is None: + self._poller.start() + + session_info = MIMEParse(response['x-msn-messenger']) + self.gateway_ip = session_info.get('GW-IP', self.gateway_ip) + self._session_id = self.fix_session_id(session_info.get('SessionID', None)) + close = session_info.get('Session', '').lower() == 'close' + + if self._session_id is None and not close: + raise Exception("Didn't get a session ID!") + + self._waiting = False + if not close: + common.netcall(self.process) + + if close: + # this way if the socket is closed from within "_process_data" it won't be counted as "unexpected" + self._session_id = None + + data = response.body + self._process_data(data) + + if close: + self.on_close() + + def _process_data(self, data): + line = data.readline() + while line: + payload = False + line.rstrip('\r\n') + dlist = line.split() + + if self.is_payload_command(dlist): + payload = True + + try: + sz = int(dlist[-1]) + except ValueError: + sz = 0 + line += data.read(sz) + + try: + msg = msn.Message.from_net(line, payload) + self.on_message(msg) + except Exception, e: + log.error('Error handling %r. e = %r', line, e) + traceback.print_exc() + + line = data.readline() + + def _on_send_error(self, e): + log.error('Something bad happened in MsnHttpSocket: %r', e) + self.on_conn_error(e) + + def make_request(self, url_kws, data = None): + url = util.UrlQuery(self.endpoint, url_kws) + + headers = { + 'Accept' : '*/*', + 'Content-Type' : 'text/xml; charset=utf-8', # 'application/x-msn-messenger', + + # Don't switch this to util.net.user_agent() + 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SIMBAR={0B74DA00-76D2-11DD-9ABA-0016CFF93348}; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 3.5.21022; .NET CLR 1.1.4322; Windows Live Messenger BETA 9.0.1407)', + 'Cache-Control' : 'no-cache', + "Accept-Language": "en-us", + } + + req = asynchttp.HTTPRequest.make_request(url, data = data, headers = headers, method = 'POST') + return req + + @once.allow_once + def close(self): + log.info('Closing %r', self) + msn.MSNSocketBase.close(self) + del self._q[:] + if self._session_id is None: + self.on_close() + else: + self.send(msn.Message('OUT')) + + def on_close(self): + log.info('on_close: %r', self) + self._closed = True + self._poller.stop() + self._on_response = Null + self._on_send_error = Null + self._session_id = None + self.gateway_ip = type(self).gateway_ip + del self._q[:] + self.pause() + + msn.MSNSocketBase.on_close(self) + + _disconnect = close_when_done = close + + def __repr__(self): + return '<%s session_id=%r gateway_ip=%r>' % (type(self).__name__, self._session_id, self.gateway_ip) + +def main(): + scktype = MsnHttpSocket + + sck = scktype() + args = sck.connect_args_for('NS', ('messenger.hotmail.com', 1863)) + print args + + sck.connect(*args) + sck.send(msn.Message('VER', 'MSNP8', 'CVR0'), trid=True) + + app.toggle_crust() + app.MainLoop() + +if __name__ == '__main__': + import digsbysite + import netextensions + from tests.testapp import testapp + logging.getLogger('events').setLevel(1) + + app = testapp('.') + threadpool.ThreadPool(5) + main() diff --git a/digsby/src/msn/Msnifier.py b/digsby/src/msn/Msnifier.py new file mode 100644 index 0000000..9da7646 --- /dev/null +++ b/digsby/src/msn/Msnifier.py @@ -0,0 +1,73 @@ +from __future__ import with_statement +''' +Msnifier.py +''' +__author__ = 'dotSyntax' + +import itertools +from time import sleep +from types import GeneratorType as generator +import logging + +from util import TimeOut, default_timer +from util.primitives.structures import PriorityQueue + +log = logging.getLogger('msn.msnifier') +#log.setLevel(logging.DEBUG) + +class Msnifier(TimeOut): + 'Rate limiting MSN packet consumer.' + + def __init__(self, socket): + TimeOut.__init__(self) + self.to_send = None + self.socket = socket + + # create a PriorityQueue for packets + self.queue = PriorityQueue() + + def send_pkt(self, pkt, priority=5, **kw): + + with self._cv: + self.queue.append((pkt, kw), priority) + #self._cv.notifyAll() + + def compute_timeout(self): + if self._finished: + self._last_computed = -1 + else: + self._last_computed = 5 + (pkt, kw) = self.next_pkt() + if pkt is not None: + self._last_computed = self.socket.time_to_send(str(pkt)) + self.queue.append((iter([pkt]), kw), priority=1) + + return self._last_computed + + def process(self): + pkt, kw = self.next_pkt() + + if pkt is not None: + callback = kw.pop('callback', None) + if not self.socket._send(str(pkt), **kw): + getattr(callback, 'error', lambda: None)() + else: + log.warning("calling after_send: %r", getattr(callback, 'after_send', None)) + getattr(callback, 'after_send', lambda: None)() + + def next_pkt(self): + pkt = None + kw = {} + q = self.queue + while pkt is None and q: + try: + x, kw = q.peek() + try: + pkt = x.next() + except AttributeError: + pkt, kw = q.next() # this is x, but now we are removing it from the queue + except (GeneratorExit, StopIteration, ValueError): + # head of q is a completed generator, remove it + q.next() + + return pkt, kw diff --git a/digsby/src/msn/NSSBAdapter.py b/digsby/src/msn/NSSBAdapter.py new file mode 100644 index 0000000..c77757a --- /dev/null +++ b/digsby/src/msn/NSSBAdapter.py @@ -0,0 +1,216 @@ +from logging import getLogger +log = getLogger('msn.nssb') + +import msn + +from msn import MSNTextMessage + +from util import callsback +from util.primitives.funcs import get +from util.Events import EventMixin + +class NSSBAdapter(EventMixin): + ''' + Chatting with federated (yahoo) buddies happens over the NS protocol, but + MSNConversations are made to work with Switchboard protocol implementations. + + This class exists to provide a switchboard interface to the NS. + ''' + + _instances = [] + + events = EventMixin.events | set (( + 'on_buddy_join', + 'on_buddy_leave', + 'on_buddy_timeout', + 'on_conn_success', + 'on_authenticate', + 'disconnect', + 'contact_alias', + 'needs_auth', + 'recv_error', + 'recv_text_msg', + 'send_text_msg', + 'typing_info', + 'recv_action', + + 'recv_p2p_msg', + 'transport_error', + )) + + def __init__(self, ns, to_invite=()): + + self.ns = ns + # bind events to NS + self.principals = [] + self.to_invite = to_invite + + self.__chatbuddy = get(to_invite, 0, None) + + EventMixin.__init__(self) + + @property + def _chatbuddy(self): + return self.__chatbuddy + + @property + def _closed(self): + return not self.connected + + @property + def self_buddy(self): + return self.ns.self_buddy + + @callsback + def invite(self, bname, callback=None): + self.buddy_join(bname) + callback.success() + + def buddy_join(self, bname): + if self.__chatbuddy is None: + self.__chatbuddy = bname + else: + assert self.__chatbuddy == bname + + if bname not in self.principals: + self.principals.append(bname) + self.event('on_buddy_join', bname) + + def send_text_message(self, message): + + #payload = MSNTextMessage(message) + payload = message + + netid = 32 + msg = msn.Message('UUM', self.__chatbuddy, netid, 1, payload = str(payload)) + + self.ns.socket.send(msg, trid=True, callback=sentinel) + self.event('send_text_msg', payload) + + def on_send_message(self, msg): + return NotImplemented + + def leave(self): + if self in self._instances: + self._instances.remove(self) + self.event('disconnect') + + @callsback + def connect(self, callback=None): + log.info('NSSB.connect()') + self._instances.append(self) + + self.event('on_conn_success', self) + log.info('NSSB.on_conn_success()') + + for bname in self.to_invite: + self.buddy_join(bname) + + self.event('on_authenticate') + log.info('NSSB.on_authenticate()') + callback.success() + + def connected(self): + return self.ns.connected() + + def disconnect(self): + self.event('disconnect') + + def close_transport(self): + pass + + def on_conn_fail(self): + self.event('recv_error') + + def authenticate(self, bname): + self.event('on_authenticate') + + def send_typing_status(self, name, status): + ''' + UUM 0 bob@yahoo.com 32 2 87\r\n + MIME-Version: 1.0\r\n + Content-Type: text/x-msmsgscontrol\r\n + TypingUser: alice@live.com\r\n + \r\n + ''' + + payload = [] + line = lambda k,v: '%s: %s' % (k,v) + add = payload.append + add(line('MIME-Version', '1.0')) + add(line('Content-Type', 'text/x-msmsgscontrol')) + add(line('TypingUser', name)) + add('') + add('') + + payload = '\r\n'.join(payload) + + netid = 32 + + msg = msn.Message('UUM', self.__chatbuddy, netid, 2, payload = payload) + + self.ns.socket.send(msg, trid=True, callback=sentinel) + + def on_error(self, msg): + pass + + def recv_msg(self, msg): + #type = msg.type + + try: + getattr(self, 'recv_msg_%s' % msg.type, self.recv_msg_unknown)(msg) + except Exception, e: + import traceback + traceback.print_exc() + + log.error('Exception handling MSG: %r, msg = %r', e, msg) + + def recv_msg_plain(self, msg): + ''' + msg_plain(msg, src_account, src_display) + + this is called when a msg comes in with type='text/plain' + + @param socket: the socket it arrived from (better be self.socket!) + @param msg: the rfc822 object representing the MIME headers and such + @param src_account: the email address/passport this comes from + @param src_display: the display name of the buddy who sent it + @param *params: more stuff! + ''' + + name, nick = msg.args[:2] + msg = MSNTextMessage.from_net(msg.payload) + +# self.event('contact_alias', name, nick) + self.event('recv_text_msg', name, msg) + + def recv_msg_control(self, msg): + ''' + msg_control(msg, src_account, src_display) + + This is called when a message comes in with type='text/x-msmsgscontrol' + Generally, these are typing indicators. + + @param msg: msnmessage + ''' + name = msg.args[0] + + self.event('typing_info', name, bool(msg.payload.get('TypingUser', False))) + + def recv_msg_unknown(self, msg): + log.error("ohnoes don't know this message type: %r, %r", msg.type, msg) + + @classmethod + def incoming(cls, msg): + name = msg.args[0] + + for x in cls._instances: + if x.__chatbuddy == name: + # match + break + + else: + x = cls(None) + + x.buddy_join(name) + x.recv_msg(msg) diff --git a/digsby/src/msn/P2P/Bridges.py b/digsby/src/msn/P2P/Bridges.py new file mode 100644 index 0000000..cd54089 --- /dev/null +++ b/digsby/src/msn/P2P/Bridges.py @@ -0,0 +1,694 @@ +from util.packable import Packable +from util import Events +import asynchat +import socket +import random +import collections +import struct +import common +import logging + +import util +import util.net as net +import util.Events as events +import util.callbacks as callbacks + +log = logging.getLogger('msn.p2p.bridges') + +def simplest_producer(data): + yield data + yield None + +class Bridge(events.EventMixin): + coolness = -1 + bridge_name = None + + def get_connecter_factory(self, serving): + raise NotImplementedError + + def get_socket_factory(self): + raise NotImplementedError + + def set_ips(self, addrlist): + pass + +class TcpBridge(Bridge): + bridge_name = 'TCPv1' + coolness = 10 + + def get_connecter_factory(self, serving): + if serving: + return MSNTcpServer + else: + return MSNTcpClient + + def get_socket_factory(self): + return MSNDirectTcpSocket + +class UdpBridge(Bridge): + coolness = 5 + bridge_name = 'TRUDPv1' + + events = Bridge.events | set(( + 'on_message', + 'on_close', + 'on_error', + 'on_send', + )) + + def __init__(self): + pass + + def get_connecter_factory(self, serving): + if serving: + return MSNUdpServer + else: + return MSNUdpClient + + def get_socket_factory(self): + # TODO: Return UdpBridge instance? + def fac(connecter, data): + log.info('udp socket factory got %r, socket = %r', connecter, connecter.socket) + return connecter.socket + + return fac + +class MSNDCSocket(common.socket, events.EventMixin): + events = events.EventMixin.events | set(( + 'on_message', + 'on_close', + 'on_error', + 'on_send', + )) + + def __init__(self, conn, prev_data = ''): + common.socket.__init__(self, conn) + self.set_terminator(self.hdr_size) + self.ac_in_buffer = prev_data + events.EventMixin.__init__(self) + self.data = '' + self.getting_hdr = True + + def collect_incoming_data(self, data): + self.data += data + + def handle_close(self): + self.event('on_close') + common.socket.handle_close(self) + self.close() + + def handle_expt(self): + self.event('on_error') + common.socket.handle_expt(self) + + def handle_error(self, e=None): + import traceback; traceback.print_exc() + self.event('on_error') + self.close() + common.socket.handle_error(self, e) + + @property + def localport(self): + try: + return self.socket.getsockname()[1] + except: + return 0 + + def __repr__(self): + pn = None + try: pn = self.socket.getpeername() + finally: return "<%s connected to %r>" % (self.__class__.__name__,pn) + +class MSNDirectUdpSocket(MSNDCSocket, common.AsyncSocket.AsyncUdpSocket): + send_delay_base = .2 + send_delay_min = .00001 + timeout = 45 + + p2p_overhead = 68 + p2p_max_msg_size = 1384 + + @property + def send_delay(self): + delay = self.send_delay_base + if self._last_ack_time: + dt_last_ack = util.default_timer() - self._last_ack_time + delay = self.send_delay_base * dt_last_ack + + if dt_last_ack > self.timeout: + self._do_timeout = True + + return max(self.send_delay_min, delay) + + def build_data(self, header, body, footer): + return ''.join((header, body)) + + events = MSNDCSocket.events | set(( + 'on_udp_message', + )) + class Header(Packable): + class Flags(object): + SYN = 0x00010020 + ACK = 0x00010080 + SYN_ACK = SYN | ACK + + fmt = ( + 'sender_seq', 'I', + 'recver_seq', 'I', + 'flags', 'I', + 'sender_msg_id', 'I', + 'recver_msg_id', 'I', + ) + byteorder = "<" + + hdr_size = Header.size + + def __init__(self, conn = None): + self._ips = None + self._connected = False + + MSNDCSocket.__init__(self, None) + common.AsyncSocket.AsyncUdpSocket.__init__(self, conn) + self.re_init() + + def re_init(self, sender_seq = None): + log.info('initializing %r', self) + self.state = self.Header( + sender_seq = 0, + recver_seq = 0, + flags = self.Header.Flags.SYN, + sender_msg_id = 0, + recver_msg_id = 0, + ) + self._do_timeout = False + self._current_sending = None + self._last_send = 0 + self._next_msgid_incr = 484 + self.state.sender_seq = sender_seq or random.randint(0, 0x7FFFFFFF) + self.state.sender_msg_id = random.randint(0, 0x7FFFFFFF) + self.discard_buffers() + log.info('%r initialized. self.state = %r', self, self.state) + + def on_session_completed(self): + self.re_init() + + def close(self): + log.info('Closing %r', self) + self.connected = False + self.discard_buffers() + common.AsyncSocket.AsyncUdpSocket.close(self) + + def _send(self, data): + if data and self._current_sending and not self._current_sending[0][1]: + #log.info('Got data to send, NOT clearing old non-data packet from queue') + #self._current_sending = None + pass + + while self.ac_out_buffer and self.ac_out_buffer[0][0] == '': + if data: + oldpkt = self.ac_out_buffer.pop(0) + log.info('old packet %r will not be sent because of data: %r', oldpkt, data) + elif data == '': + return + + x = self.push_with_producer(net.GeneratorProducer(simplest_producer(data))) + return x + + def set_ips(self, iplist): + if not self._ips: + self._ips = iplist + else: + raise ValueError("Can't set IPs again", self, self._ips, iplist) + + @property + def endpoint(self): + try: + return self._ips[0] + except Exception: + return None + + def discard_buffers(self): + self._last_ack_time = util.default_timer() + common.AsyncSocket.AsyncUdpSocket.discard_buffers(self) + + def readable(self): + return bool(self._ips) and common.AsyncSocket.AsyncUdpSocket.readable(self) + + def writable(self): + if (util.default_timer() - self._last_send) < self.send_delay: + return False + return bool(self._ips) and common.AsyncSocket.AsyncUdpSocket.writable(self) + + @callbacks.callsback + def connect(self, callback = None): + log.info('%r.connect() called', self) + self._connect_cb = callback + + try: + log.info('binding udp socket') + self.socketbind(('', 0)) + except Exception, e: + log.info('omg it broke: %r', e) + callback.error(e) + return + else: + log.info('bind worked') + callback.success(self) + self.on_connect() + + def getsockname(self): + log.info('Getting socket name for %r: %r', self, self.socket.getsockname()) + return self.socket.getsockname() + + def collect_incoming_data(self, data, addr): + if addr not in self._ips: + log.error("Ignoring data from unexpected source %r", addr) + return + + if len(self._ips) > 1: + old_ips = self._ips[:] + + try: + old_ips.remove(addr) + except ValueError: + pass + + self._ips[:] = [addr] + log.info('Made initial contact with peer: %r. current_sending was: %r', addr, self._current_sending) + while self.ac_out_buffer and self.ac_out_buffer[0][1] != self.endpoint: + self.ac_out_buffer.pop(0) + self._current_sending = None + + self._process_data(data, addr) + + def get_next_message_id(self, hdr): + return hdr.sender_msg_id + self._next_msgid_incr + + def _process_data(self, _data, addr): + hdr, data = _data + #log.info('_process_data: hdr = %r, data = %r', hdr, data) + self.state.recver_msg_id = hdr.sender_msg_id + + if self.state.recver_seq == 0 or \ + (self.state.recver_seq <= hdr.sender_seq and + self.state.sender_seq <= hdr.recver_seq): + is_new_message = True + else: + is_new_message = False + + self.state.recver_seq = hdr.sender_seq + + self._next_msgid_incr = 15 if self._next_msgid_incr == 16 else 16 + + if is_new_message: + self.state.flags = hdr.Flags.ACK + + if hdr.recver_seq == 0: + self.state.flags |= hdr.flags + + self.ack_message(hdr, data) + + if not is_new_message: + # probably just an ack of what we're sending, or it could be a retransmission + #log.debug('Received a message again (header = %r, data = %r)', hdr, data) + return + + self.event('on_udp_message', hdr, data) + if data: + self.event('on_message', data) + + def ack_message(self, hdr, data): + if data or hdr.flags != hdr.Flags.ACK: + self._send('') + + def close_when_done(self): + self.close() + + def initiate_send(self): + if self._do_timeout: + self._do_timeout = False + raise socket.timeout() + + if not self.ac_out_buffer: + self.refill_buffer() + + if self._current_sending: + (hdr, data), addr = self._current_sending + if addr not in self._ips: + self._current_sending = None + return + if not data and self.ac_out_buffer: + self._current_sending = None + return + else: + data = addr = None + while addr not in self._ips and self.ac_out_buffer: + data, addr = self.ac_out_buffer[0] + if addr not in self._ips: + self.ac_out_buffer.pop(0) + data = addr = None + + if not self.ac_out_buffer: + return + + data, addr = self.ac_out_buffer.pop(0) + hdr = None + + if not data and self.ac_out_buffer: + return + + if data is None: + data = '' + + header, final_data = self.build_packet(hdr, data) + + if hdr is None: + self._current_sending = (header, data), addr + + if not final_data: + return + + #log.info('sendto(%r, (%r, %r))', addr, header, data) + try: + num_sent = self.sendto(final_data, addr) + except socket.error, why: + self.handle_error(why) + return + else: + self._last_send = util.default_timer() + + def build_packet(self, header, data): + if header is None: + if data: + self.state.sender_seq += 1 + header = self.state.copy() + + header.recver_msg_id = self.state.recver_msg_id + header.sender_msg_id = self.state.sender_msg_id = self.get_next_message_id(header) + + #log.info('build_packet: %r + %r', header, data) + return header, header.pack() + data + + def handle_read(self): + if self._do_timeout: + self._do_timeout = False + raise socket.timeout() + + try: + data, addr = self.recvfrom(8192) + except socket.error, why: + self.handle_error(why) + return + + if not data: + return + + header, pktdata = self.Header.unpack(data) + #log.info('recvd %r: %r + %r', addr, header, pktdata) + self.check_ack(header, addr) + self.collect_incoming_data((header, pktdata), addr) + + def check_ack(self, header, addr): + if self._current_sending is None: + #log.info('no current sending, but got an ack: %r (self.state = %r)', header, self.state) +# if self.ac_out_buffer: +# log.info('assuming ack, current_sending is None. popping %r', self.ac_out_buffer[0]) +# self.ac_out_buffer.pop(0) +# else: +# log.info('No current sending and no out buffer, but got an ack. producer_fifo = %r', self.producer_fifo) + return + + (myhdr, mydata), dest = self._current_sending + + if header.flags == header.Flags.ACK: + self.state.flags = myhdr.flags = header.flags + + if dest == addr and header.recver_seq == myhdr.sender_seq: + # ack! + #log.info('got ack for %r: %r', (myhdr, mydata), header) + try: + self.ac_out_buffer.remove((mydata, dest)) + except ValueError: + pass + + self._last_send = 0 + self._last_ack_time = util.default_timer() + self._current_sending = None + self.event('on_send') + else: + if header.recver_seq < myhdr.sender_seq: + #log.info('got old ack. recvd: %r, mystate: %r', header, myhdr) + pass + else: + log.info('bad ack: %r != %r or (recvd %r) != (expected %r)', dest, addr, header.recver_seq, myhdr.sender_seq) + + def handle_error(self, e=None): + import traceback; traceback.print_exc() + self.on_error() + self.event('on_error') + self.close() + common.AsyncSocket.AsyncUdpSocket.handle_error(self, e) + + def on_error(self, e=None): + ccb, self._connect_cb = self._connect_cb, None + if ccb is not None: + ccb.error(e) + +class MSNDirectTcpSocket(MSNDCSocket): + hdr_size = 4 + p2p_overhead = 52 + p2p_max_msg_size = 1400 + + def build_data(self, header, body, footer): + return ''.join((struct.pack('', '')) + + @property + def ToEmailAccount(self): + return self.GetEmailAccount(self.To.replace('', '')) + + @property + def FromEndPoint(self): + return self.GetEndPointIDFromMailEPIDString(self.From.replace('', '')) + + @property + def ToEndPoint(self): + return self.GetEndPointIDFromMailEPIDString(self.To.replace('', '')) + + + def Source(): + def fget(self): + return self.From.replace('', '') + def fset(self, value): + self.From = '' % value + + return locals() + Source = property(**Source()) + + def Target(): + def fget(self): + return self.To.replace('', '') + def fset(self, value): + self.To = '' % value + + return locals() + Target = property(**Target()) + + def Via(): + def fget(self): + return self.mimeHeaders['Via'] + def fset(self, value): + del self.mimeHeaders['Via'] + self.mimeHeaders['Via'] = value + return locals() + + Via = property(**Via()) + + def Branch(): + def fget(self): + via_parts = self.mimeHeaders.get('Via', '').split('branch=', 1) + if len(via_parts) == 2: + return uuid.UUID(via_parts[1]) + else: + return uuid.UUID(int = 0) + def fset(self, value): + old_via = self.mimeHeaders.get('Via') + via = old_via.split(';')[0] + new_via = (via+';branch={%s}') % (str(value).upper()) + del self.mimeHeaders['Via'] + self.mimeHeaders['Via'] = new_via + return locals() + + Branch = property(**Branch()) + + def CSeq(): + def fget(self): + return int(self.mimeHeaders.get('CSeq', 0)) + + def fset(self, value): + del self.mimeHeaders['CSeq'] + self.mimeHeaders['CSeq'] = str(value) + + return locals() + + CSeq = property(**CSeq()) + + def CallId(): + def fget(self): + return uuid.UUID(self.mimeHeaders.get('Call-ID', str(uuid.UUID(int=0)))) + + def fset(self, value): + del self.mimeHeaders['Call-ID'] + self.mimeHeaders['Call-ID'] = '{' + str(value).upper() + '}' + + return locals() + + CallId = property(**CallId()) + + def ContentType(): + def fget(self): + return self.mimeHeaders.get('Content-Type') + + def fset(self, value): + del self.mimeHeaders['Content-Type'] + self.mimeHeaders['Content-Type'] = value + + return locals() + + ContentType = property(**ContentType()) + + @property + def BodyValues(self): + return self.mimeBodies + + def GetBytes(self, appendNull = True): + body = self.mimeBodies.as_string().replace('\n', '\r\n') + ('\0' * appendNull) + del self.mimeHeaders['Content-Length'] + self.mimeHeaders['Content-Length'] = str(len(body)) + + return ''.join((self.StartLine.strip(), '\r\n', + self.mimeHeaders.as_string().replace('\n', '\r\n'), + body)) + + def ParseBytes(self, data): + self.StartLine, data = data.split('\r\n', 1) + self.mimeHeaders = email.message_from_string(data, _class = MSNC.MSNMime) + self.mimeBodies = email.message_from_string(self.mimeHeaders.get_payload(), _class = MSNC.MSNMime) + self.mimeHeaders.set_payload(None) + + @classmethod + def Parse(cls, data): + if '\r\n' not in data: + return None + + firstline = data.split('\r\n', 1)[0] + if "MSNSLP" not in firstline: + return None + + if firstline.startswith("MSNSLP/1.0"): + return SLPStatusMessage(data) + else: + return SLPRequestMessage(data) + + +class SLPRequestMessage(SLPMessage): + Method = 'UNKNOWN' + Version = 'MSNSLP/1.0' + + def _get_StartLine(self): + return '%s %s:%s %s' % (self.Method, 'MSNMSGR', self.Target, self.Version) + + def _set_StartLine(self, value): + parts = value.split() + self.Method = parts[0] + self.Version = parts[2] + + def __init__(self, Data_or_To, Method = None): + if Method is None: + super(SLPRequestMessage, self).__init__(Data_or_To) + else: + super(SLPRequestMessage, self).__init__() + self.Target = Data_or_To + self.Method = Method + +class SLPStatusMessage(SLPMessage): + Version = 'MSNSLP/1.0' + Code = 0 + Phrase = 'Unknown' + + def _get_StartLine(self): + return '%s %s %s' % (self.Version, self.Code, self.Phrase) + + def _set_StartLine(self, value): + parts = value.split() + self.Version = parts.pop(0) + self.Code = int(parts.pop(0)) + self.Phrase = ' '.join(parts).strip() + + def __init__(self, Data_or_To, Code = None, Phrase = None): + if Code is None and Phrase is None: + # Data to parse + super(SLPStatusMessage, self).__init__(Data_or_To) + else: + super(SLPStatusMessage, self).__init__() + self.Target = Data_or_To + self.Code = Code or self.Code + self.Phrase = Phrase or self.Phrase diff --git a/digsby/src/msn/P2P/P2PActivity.py b/digsby/src/msn/P2P/P2PActivity.py new file mode 100644 index 0000000..d59a602 --- /dev/null +++ b/digsby/src/msn/P2P/P2PActivity.py @@ -0,0 +1,86 @@ +import uuid +import msn.P2P as P2P +import msn.P2P.P2PApplication as P2PApp +import msn.P2P.P2PMessage as P2PMessage + +@P2PApp.P2PApplication.RegisterApplication +class P2PActivity(P2PApp.P2PApplication): + AppId = 0 + EufGuid = uuid.UUID("6A13AF9C-5308-4F35-923A-67E8DDA40C2F") + + sending = False + ActivityData = '' + ActivityName = '' + + def __init__(self, session = None, ver = None, remote = None, remoteEP = None, activityName = '', activityData = ''): + self.ActivityName = activityName + self.ActivityData = activityData + + if session is None: + super(P2PActivity, self).__init__(ver = remote.P2PVersionSupported, remote = remote, remoteEP = remote.SelectRandomEPID()) + self.sending = True + else: + super(P2PActivity, self).__init__(ver = session.Version, remote = session.Remote, remoteEP = session.RemoteContactEndPointID) + + try: + activityUrl = session.Invitation.BodyValues['Context'].decode('base64').decode('utf-16-le') + activityProperties = filter(None, activityUrl.split(';')) + if len(activityProperties) > 3: + AppId = int(activityProperties[0]) + self.ActivityName = activityProperties[2] + + except Exception: + pass + + self.sending = False + + def _get_InvitationContext(self): + return (self.AppId + ';1;' + self.ActivityName).encode('utf-16-le').encode('base64') + + def ValidateInvitation(self, invitation): + ret = super(P2PActivity, self).ValidateInvitation(invitation) + + if not ret: + return ret + + try: + ret = len(filter(None, invitation.BodyValues['Context'].decode('base64').decode('utf-16-le').split(';'))) > 3 + except Exception: + pass + + return ret + + def Start(self): + if not super(P2PActivity, self).Start(): + return False + + if not self.sending: + return + + if not self.ActivityData: + return + + self.ActivityData += u'\0' + urlLength = len(self.ActivityData.encode('utf-16-le')) + + prepData = P2PMessage.P2PDataMessage(self.version) + if self.version == P2P.Version.V1: + header = '\x80\0\0\0' + else: + header = '\x80\x3f\x14\x05' + + data = ''+header + data += struct.pack('" % (self.__class__.__name__,pn) + + def getpeername(self): + return self.socket.getpeername() + +class MSNDirectTcpSocket(MSNDCSocket): + hdr_size = 4 + p2p_overhead = 52 + p2p_max_msg_size = 1400 + + def build_data(self, header, body, footer): + return ''.join((struct.pack(' (32 * 1024): + log.warning("FileTransfer got chunk larger than 32k, re-evaluate buffering (size = %r)", len(data)) + + if len(data): + self.dataStream.write(data) + + self.OnProgressed(self.dataStream.tell()) + + log.info("Received %r / %r", self.dataStream.tell(), self.context.filesize) + if self.dataStream.tell() == self.context.filesize: + self.OnTransferFinished() + if self.P2PSession is not None: + self.P2PSession.Close() + + return True + + def OnTransferFinished(self): + super(FileTransfer, self).OnTransferFinished() + self._ondone() + + def OnTransferError(self): + if self.dataStream is not None: + self.dataStream.close() + super(FileTransfer, self).OnTransferError() + if self.state not in (self.states.FailStates | self.states.CompleteStates): + self.state = self.states.CONN_FAIL + self.on_error() + + def OnTransferAborted(self, who): + if self.state not in self.states.CompleteStates: + if who is self.Local: + self.state = self.states.CANCELLED_BY_YOU + elif who is self.Remote: + self.state = self.states.CANCELLED_BY_BUDDY + + self._ondone() + + super(FileTransfer, self).OnTransferAborted(who) + + def cancel(self, state = None): + if getattr(self, 'dataStream', None) is not None: + self.dataStream.close() + + if self.state == self.states.FINISHED: + state = self.states.FINISHED + + if state is None: + state = self.states.CANCELLED_BY_YOU + self.sendingData = False + + self.state = state + + if state == self.states.BUDDY_GONE: + self.OnTransferError() + elif state == self.states.CONN_FAIL: + self.OnTransferError() + else: + log.info("Aborting P2PFileTransfer because: %r", state) + self.OnTransferError() + + if self.P2PSession is not None: + self.P2PSession.SendBye() + +class FTContext(object): + def __init__(self, filename, filesize, preview = ''): + import path + self.filename = path.path(filename).name + self.filesize = filesize + self._preview = preview + + @classmethod + def parse(cls, data): + _data = data + (length, version, filesize, type), data = struct.unpack(' length: + preview = data[length:] + else: + preview = '' + + return cls(filename, filesize, preview) + + def GetBytes(self): + version = 2 + + if version == 3: + length = 638 + len(self._preview) + else: + length = 574 + len(self._preview) + + data = struct.pack(' length: + return data[:length] + if len(data) == length: + return data + + missing = length - len(data) + return data + ('\0' * missing) diff --git a/digsby/src/msn/P2P/P2PHandler.py b/digsby/src/msn/P2P/P2PHandler.py new file mode 100644 index 0000000..aeb85af --- /dev/null +++ b/digsby/src/msn/P2P/P2PHandler.py @@ -0,0 +1,343 @@ +import logging; _log = log = logging.getLogger('msn.p2p.handler') +log.setLevel(11) +import path +import util.Events as Events +import util.callbacks as callbacks + +import msn +import msn.P2P as P2P +import msn.P2P.P2PMessagePool as P2PMessagePool +import msn.P2P.P2PSession as P2PSession +import msn.P2P.MSNSLPMessages as MSNSLP + +class P2PHandler(Events.EventMixin): + events = Events.EventMixin.events | set(( + 'InvitationReceived', + )) + + def OnInvitationReceived(self, session): + self.event('InvitationReceived', self, session) + + def __init__(self, nsMessageHandler): + self.protocol = self.nsMessageHandler = nsMessageHandler + + self.slpMessagePool = P2PMessagePool.P2PMessagePool() + self.bridges = [] + self.v1sessions = [] + self.v2sessions = [] + self.v1ackHandlers = {} + self.v2ackHandlers = {} + + @callbacks.callsback + def RequestMsnObject(self, contact, msnobj, callback = None): + if isinstance(contact, msn.buddy): + contact = contact.contact + import msn.P2P.P2PObjectTransfer as OT + if msnobj is None: + return callback.error() + + objtrans = OT.ObjectTransfer(obj = msnobj, remote = contact) + + objtrans.bind_event('TransferFinished', callback.success) + objtrans.bind_event('TransferAborted', callback.error) + objtrans.bind_event('TransferError', callback.error) + + self.AddTransfer(objtrans) + return objtrans + + def SendFile(self, contact, filename, file): + import msn.P2P.P2PFileTransfer as FT + filetrans = FT.FileTransfer(contact = contact, fileobj = file, filename = path.path(filename)) + self.AddTransfer(filetrans) + return filetrans + + def AddTransfer(self, app): + session = P2PSession.P2PSession(app = app) + session.bind('Closed', self.P2PSessionClosed) + session.bind('Messages', self.P2PSessionMessages) + + if app.Version == P2P.Version.V2: + self.v2sessions.append(session) + else: + self.v1sessions.append(session) + + session.Invite() + return session + + def ProcessP2PMessage(self, bridge, source, sourceGuid, message): + #_log.info("Got P2PMessage: bridge = %r, source = %r, sourceGuid = %r, message = %r", bridge, source, sourceGuid, message) + requireAck = self.HandleRAK(bridge, source, sourceGuid, message) + incomplete, message = self.slpMessagePool.BufferMessage(message) + if incomplete and not message.Header.IsAck: + #_log.info("Message not complete yet, buffered") + return + + slp = None + if message.IsSLPData: + slp = message.InnerMessage + if slp is None: + assert message.InnerBody == '\0'*4 + log.info("Got Data prep message") + return + + #_log.info("It's a slp message") + if not self.CheckSLPMessage(bridge, source, sourceGuid, message, slp): + log.info("invalid slp message") + return + + else: + #_log.error("non-slp message: header = %r", message.Header) + pass + + if self.HandleAck(message): + #_log.info("Message acked") + return + + session = self.FindSession(message, slp) + #_log.info("Got session for slp message: %r", session) + if session is not None and session.ProcessP2PMessage(bridge, message, slp): + #_log.info("Session processed message: %r / %r", message, slp) + return + elif session is not None: + log.error("Session rejected message: %r", message) + + if slp is not None: + if self.ProcessSLPMessage(bridge, source, sourceGuid, message, slp): + log.info("Created new session based on SLP message") + return + elif session is None: + log.info("Non-SLP message had no session to go to: %r", message) + + if not requireAck: + log.error("Message was not processed") + + def Cleanup(self): + del self.slpMessagePool, self.v1sessions, self.v2sessions, self.bridges, self.v1ackHandlers, self.v2ackHandlers + Dispose = Cleanup + + def RegisterAckHandler(self, msg, handler): + if msg.Version == P2P.Version.V2: + ident = msg.Header.Identifier + msg.Header.MessageSize + + if msg.SetRAK(): + if ident in self.v2ackHandlers: + log.debug("Merging ack handler for ID = %r (Identifier = %r, MessageSize = %r, TotalSize = %r)", ident, msg.Header.Identifier, msg.Header.MessageSize, msg.Header.TotalSize) + self.v2ackHandlers[ident][0].append(msg) + old_handler = self.v2ackHandlers[ident][1] + old_handler.success += handler.success + old_handler.error += handler.error + old_handler.progress += handler.progress + old_handler.after_send += handler.after_send + log.debug("\tHandler = %r", old_handler) + else: + log.debug("Setting ack handler for ID = %r (Identifier = %r, MessageSize = %r, TotalSize = %r)", ident, msg.Header.Identifier, msg.Header.MessageSize, msg.Header.TotalSize) + log.debug("\tHandler = %r", handler) + self.v2ackHandlers[ident] = [msg], handler + + else: + log.error("Could not set ack handler for msg = %r", msg) + elif msg.Version == P2P.Version.V1: + ident = msg.Header.AckSessionId + self.v1ackHandlers[msg.Header.AckSessionId] = [msg], handler + + def GetBridge(self, session): + for bridge in self.bridges: + if bridge.SuitableFor(session): + break + + return self.protocol.SDGBridge + + def BridgeClosed(self, bridge): + if bridge not in self.bridges: + return + + self.bridges.remove(bridge) + + def HandleAck(self, message): + isAckOrNak = False + + if message.Header.IsAck or message.Header.IsNak: + ackNakId = 0 + isAckOrNak = True + if message.Version == P2P.Version.V1: + handlers = self.v1ackHandlers + if message.Header.AckIdentifier in handlers: + ackNakId = message.Header.AckIdentifier + msgs, handler = handlers.pop(ackNakId, (None, None)) + elif message.Version == P2P.Version.V2: + handlers = self.v2ackHandlers + msgs = [] + if message.Header.AckIdentifier in handlers: + ackNakId = message.Header.AckIdentifier + msgs, handler = handlers.pop(ackNakId, (None, None)) + elif message.VHeader.NakIdentifier in handlers: + ackNakId = message.VHeader.NakIdentifier + msgs, handler = handlers.pop(ackNakId, (None, None)) + + if msgs: + new_msgs = msgs[1:] + + if new_msgs: + handlers[ackNakId] = new_msgs, handler + + if ackNakId != 0: + log.debug("Got ack for message id = %r: %r", ackNakId, msgs) + if handler is not None: + log.debug('\tcalling handler %r', handler) + handler.success(message) + else: + log.warning("Couldn't find message this ack is for: %r. pending ack ids are %r. message = %r", message.Header.AckIdentifier, handlers.keys(), message) + + return isAckOrNak + + def HandleRAK(self, bridge, source, sourceGuid, msg): + if not msg.Header.RequireAck: + return False + log.debug("Acking message: %r", msg) + ack = msg.CreateAck() + ack.Header.Identifier = bridge.localTrackerId + if ack.Header.RequireAck: + def success(sync): + log.debug("Sent ack for SYN") + bridge.SyncId = sync.Header.AckIdentifier + + bridge.Send(None, source, sourceGuid, ack, success = success) + else: + bridge.Send(None, source, sourceGuid, ack) + + return True + + def FindSession(self, msg, slp): + sessionId = msg.Header.SessionId + if sessionId == 0 and slp is not None: + if 'SessionID' in slp.BodyValues: + try: + sessionId = int(slp.BodyValues['SessionID']) + except: + sessionId = 0 + + if sessionId == 0: + if msg.Version == P2P.Version.V2: + sessions = self.v2sessions + else: + sessions = self.v1sessions + + for session in sessions: + if session.Invitation.CallId == slp.CallId: + return session + + if sessionId == 0 and msg.Header.Identifier: + if msg.Version == P2P.Version.V2: + for session in self.v2sessions: + expected = session.RemoteIdentifier + if msg.Header.Identifier == expected: + return session + else: + for session in self.v1sessions: + expected = session.RemoteIdentifier + 1 + if expected == session.RemoteBaseIdentifier: + expected += 1 + + if msg.Header.Identifier == expected: + return session + + if sessionId != 0: + sessions = self.v1sessions if msg.Version == P2P.Version.V1 else self.v2sessions + for session in sessions: + if session.SessionId == sessionId: + return session + + return None + + def P2PSessionClosed(self, session, contact): + session.unbind('Closed', self.P2PSessionClosed) + session.unbind('Messages', self.P2PSessionMessages) + if session.Version == P2P.Version.V2: + if getattr(self, 'v2sessions'): + self.v2sessions.remove(session) + else: + if getattr(self, 'v1sessions'): + self.v1sessions.remove(session) + + session.Dispose() + self.P2PSession = None + + def P2PSessionMessages(self, session): + try: + session.MigrateToOptimalBridge() + session.AttemptBridgeSend() + except Exception as e: + import traceback; traceback.print_exc() + session.OnError() + + def CheckSLPMessage(self, bridge, source, sourceGuid, msg, slp): + src = source.account.lower() + # target = self.protocol.ns.contact_list.owner.account.lower() + target = self.protocol.self_buddy.name + + if msg.Version == P2P.Version.V2: + src += ';{' + str(sourceGuid).lower() + '}' + target += ';{' + str(self.protocol.get_machine_guid()).lower() + '}' + + if slp.Source.lower() != src: + log.info("Source doesn't match: %r != %r", slp.Source.lower(), src) + return False + + elif slp.Target.lower() != target: + if slp.Source == target: + log.info("Got a message from ourselves") + else: + log.info("Got a message addressed to someone else (%r != %r)", slp.Target.lower(), target) + self.SendSLPStatus(bridge, msg, source, sourceGuid, 404, "Not Found") + + return False + + #_log.debug("SLP Message is valid") + return True + + def ProcessSLPMessage(self, bridge, source, sourceGuid, msg, slp): + if getattr(slp, 'Method', None) == 'INVITE' and slp.ContentType == 'application/x-msnmsgr-sessionreqbody': + + if msg.Version == P2P.Version.V2: + sessions = self.v2sessions + else: + sessions = self.v1sessions + + for session in sessions: + if session.Invitation.CallId == slp.CallId: + break + else: + session = P2PSession.P2PSession(slp, msg, self.protocol, bridge) + log.info("Session created") + session.bind("Closed", self.P2PSessionClosed) + session.bind('Messages', self.P2PSessionMessages) + sessions.append(session) + + return True + return False + + def SendSLPStatus(self, bridge, msg, dest, destGuid, code, phrase): + target = dest.name.lower() + if msg.Version == P2P.Version.V2: + target += ';{' + str(destGuid).lower() + '}' + + slp = MSNSLP.SLPStatusMessage(target, code, phrase) + if msg.IsSLPData: + msgSLP = SLPMessage.Parse(msg.InnerMessage) + slp.Branch = msgSLP.Branch + slp.Source = msgSLP.Target + slp.CallId = msgSLP.CallId + slp.ContentType = msgSLP.ContentType + else: + slp.ContentType = 'null' + + response = P2PMessage(msg.Version) + response.InnerMessage = slp + + if msg.Version == P2P.Version.V1: + response.VHeader.Flags = P2P.Flags.MSNSLPInfo + else: + response.VHeader.OperationCode = P2P.OperationCode.NONE + response.VHeader.TFCombination = P2P.TFCombination.First + + bridge.Send(None, dest, destGuid, response, None) diff --git a/digsby/src/msn/P2P/P2PHeader.py b/digsby/src/msn/P2P/P2PHeader.py new file mode 100644 index 0000000..9acb5c7 --- /dev/null +++ b/digsby/src/msn/P2P/P2PHeader.py @@ -0,0 +1,303 @@ +import logging; log = logging.getLogger('msn.p2p.header') +import msn.P2P as P2P + +import struct +import util.primitives.funcs as funcs +import util.primitives.structures as structures + +class P2PHeader(object): + HeaderLength = funcs.iproperty('_get_HeaderLength') + Identifier = 0 + MessageSize = 0 + TotalSize = 0 + SessionId = 0 + AckIdentifier = funcs.iproperty('_get_AckIdentifier', '_set_AckIdentifier') + IsNak = funcs.iproperty('_get_IsNak') + IsAck = funcs.iproperty('_get_IsAck') + RequireAck = funcs.iproperty('_get_RequireAck') + + def CreateAck(self): + raise NotImplementedError + + def CreateNak(self): + raise NotImplementedError + + def ParseHeader(self, data): + raise NotImplementedError + + def GetBytes(self): + raise NotImplementedError + + def __repr__(self): + varstr = ', ' .join('%s=%r' % i for i in vars(self).items()) + return '%s(%s)' % (type(self).__name__, varstr) + + def Copy(self): + new = type(self)() + new.ParseHeader(self.GetBytes()) + return new + +class V1Header(P2PHeader): + Offset = 0 + Flags = 0 + AckSessionId = 0 + AckTotalSize = 0 + ackIdentifier = 0 + + def _get_AckIdentifier(self): + return self.ackIdentifier + + def _set_AckIdentifier(self, value): + self.ackIdentifier = value + + def _get_HeaderLength(self): + return 48 + + def _get_IsAck(self): + return self.AckIdentifier != 0 and ((self.Flags & P2P.Flags.Ack) == P2P.Flags.Ack) + + def _get_IsNak(self): + return self.AckIdentifier != 0 and ((self.Flags & P2P.Flags.Nak) == P2P.Flags.Nak) + + def _get_RequireAck(self): + if self.AckIdentifier != 0: + return False + if self.Flags == P2P.Flags.Ack: + return False + if (self.MessageSize + self.Offset) == self.TotalSize: + return True + + return False + + def CreateAck(self): + ack = V1Header() + ack.SessionId = self.SessionId + ack.TotalSize = 0 + ack.Flags = P2P.Flags.Ack + ack.AckSessionId = self.Identifier + ack.AckIdentifier = self.AckSessionId + ack.AckTotalSize = self.TotalSize + return ack + + def CreateNak(self): + nak = self.CreateAck() + nak.Flags = P2P.Flags.Nak + return nak + + def ParseHeader(self, data): + (self.SessionId, + self.Identifier, + self.Offset, + self.TotalSize, + self.MessageSize, + self.Flags, + self.AckSessionId, + self.AckIdentifier, + self.AckTotalSize) = struct.unpack('I', self.HeaderTLVs[2])[0] + + return self.ackIdentifier + + def _set_AckIdentifier(self, value): + self.HeaderTLVs[2] = struct.pack('>I', value) + + def _get_NakIdentifier(self): + if self.nakIdentifier == 0 and (3 in self.HeaderTLVs): + self.nakIdentifier = struct.unpack('>I', self.HeaderTLVs[3])[0] + + return self.nakIdentifier + + def _set_NakIdentifier(self, value): + self.HeaderTLVs[3] = struct.pack('>I', value) + + NakIdentifier = funcs.iproperty('_get_NakIdentifier', '_set_NakIdentifier') + + def _get_IsAck(self): + return 2 in self.HeaderTLVs + + def _get_IsNak(self): + return 3 in self.HeaderTLVs + + def _get_RequireAck(self): + rak = (self.OperationCode & P2P.OperationCode.RAK) != 0 + +# if rak and self.MessageSize == 0 and (len(self.HeaderTLVs) - (self.IsAck + self.IsNak)) == 0 and len(self.DataPacketTLVs) == 0: +# log.error("Ignoring RAK flag because packet has no data") +# return False + + return rak + + def _get_DataRemaining(self): + if self.dataRemaining == 0 and (1 in self.DataPacketTLVs): + self.dataRemaining = struct.unpack('>Q', self.DataPacketTLVs[1])[0] + + return self.dataRemaining + + def _set_DataRemaining(self, value): + self.dataRemaining = value + if value == 0: + self.DataPacketTLVs.pop(1, None) + else: + self.DataPacketTLVs[1] = struct.pack('>Q', value) + + DataRemaining = funcs.iproperty('_get_DataRemaining', '_set_DataRemaining') + + def AppendPeerInfoTLV(self): + self.OperationCode |= P2P.OperationCode.SYN + self.HeaderTLVs[1] = self.CreatePeerInfoValue() + + def CreateAck(self): + ack = V2Header() + if self.RequireAck: + ack.AckIdentifier = self.Identifier + self.MessageSize + ack.OperationCode = P2P.OperationCode.NONE + + if self.MessageSize > 0: + if not self.IsAck: + if (self.OperationCode & P2P.OperationCode.SYN) != 0: + ack.OperationCode |= P2P.OperationCode.RAK + + if 1 in self.HeaderTLVs: + ack.HeaderTLVs[1] = self.HeaderTLVs[1] + ack.OperationCode |= P2P.OperationCode.SYN + else: + raise Exception("Can't ack a non-RAK header") + + return ack + + def CreateNak(self): + nak = V2Header() + nak.NakIdentifier = self.Identifier + self.MessageSize + return nak + + def ParseHeader(self, data): + (headerLen, self.OperationCode, self.MessageSize, self.Identifier), data = struct.unpack('>BBHI', data[:8]), data[8:] + + if headerLen > 8: + TLVs_data, data = data[:headerLen - 8], data[headerLen - 8:] + tlvs = unpack_tlvs(TLVs_data) + for tlv in tlvs: + self.ProcessHeaderTLVData(*tlv) + + dataHeaderLen = 0 + if self.MessageSize > 0: + (dataHeaderLen, self.TFCombination, self.PackageNumber, self.SessionId), data = struct.unpack('>BBHI', data[:8]), data[8:] + if dataHeaderLen > 8: + TLVs_data, data = data[:dataHeaderLen - 8], data[dataHeaderLen - 8:] + tlvs = unpack_tlvs(TLVs_data) + for tlv in tlvs: + self.ProcessDataPacketTLVData(*tlv) + + return headerLen + dataHeaderLen + + def ProcessHeaderTLVData(self, t, l, v): + self.HeaderTLVs[t] = v + + if t == 1: + pass + elif t == 2: + return + if L == 4: + pass + elif t == 3: + pass + + def ProcessDataPacketTLVData(self, t, l, v): + self.DataPacketTLVs[t] = v + + def CreatePeerInfoValue(self): + return struct.pack('BBHI', headerLen, self.OperationCode, self.MessageSize, self.Identifier) + for key, val in self.HeaderTLVs.items(): + data += struct.pack('BB', key, len(val)) + val + + missing_bytes = 4 - len(data) % 4 + if missing_bytes != 4: + data += '\0' * missing_bytes + + data += struct.pack('>BBHI', dataHeaderLen, self.TFCombination, self.PackageNumber, self.SessionId) + for key, val in self.DataPacketTLVs.items(): + data += struct.pack('BB', key, len(val)) + val + + missing_bytes = 4 - len(data) % 4 + if missing_bytes != 4: + data += '\0' * missing_bytes + + return data + +def unpack_tlvs(TLVs_data): + tlvs = [] + index = 0 + while index < len(TLVs_data): + T = ord(TLVs_data[index]) + if T == 0: + break + L = ord(TLVs_data[index+1]) + V = TLVs_data[index+2:index+2+L] + tlvs.append((T, L, V)) + index += 2 + L + + return tlvs diff --git a/digsby/src/msn/P2P/P2PMessage.py b/digsby/src/msn/P2P/P2PMessage.py new file mode 100644 index 0000000..2f24c5a --- /dev/null +++ b/digsby/src/msn/P2P/P2PMessage.py @@ -0,0 +1,368 @@ +import time +import io +import struct +import logging +log = logging.getLogger('msn.p2p.message') +import util.primitives.funcs as funcs +import msn.MSNUtil as MSNU +import msn.P2P as P2P +import msn.P2P.P2PHeader as Header +import msn.P2P.MSNSLPMessages as SLPMessage + +class P2PMessage(object): + Version = P2P.Version.V1 + Header = None + Footer = 0 + + innerMessage = None + innerBody = None + + nextRak = None + RAK_WAIT_TIME = 8 + + def __repr__(self): + varstr = ', ' .join('%s=%r' % i for i in sorted(vars(self).items())) + return '%s(%s)' % (type(self).__name__, varstr) + + @classmethod + def Copy(cls, message): + copy = cls(message.Version) + copy.Header = message.Header.Copy() + copy.Footer = message.Footer + copy.InnerBody = message.InnerBody + + return copy + + def __init__(self, version): + self._gen = None + self.Finished = False + self.sendingData = True + self.Version = version + if version == P2P.Version.V1: + self.Header = Header.V1Header() + elif version == P2P.Version.V2: + self.Header = Header.V2Header() + + def IsFinished(self): + return self.Finished + + @property + def VHeader(self): + return self.Header + + def _get_InnerBody(self): + # TODO: make innerbody a file-like object + return self.innerBody + + def _set_InnerBody(self, value): + # TODO: make innerbody a file-like object + self.innerBody = value + self.innerMessage = None + + if self.Version == P2P.Version.V1: + self.Header.MessageSize = len(value) + self.Header.TotalSize = max(self.Header.TotalSize, len(value)) + + else: + if value is not None and len(value): + self.Header.MessageSize = len(value) + self.Header.MessageSize += self.Header.DataPacketHeaderLength + + self.Header.TotalSize = max(self.Header.TotalSize, len(value)) + + else: + self.Header.MessageSize = 0 + self.Header.TotalSize = 0 + + InnerBody = property(_get_InnerBody, _set_InnerBody) + + def _get_InnerMessage(self): + if self.innerMessage is None and self.InnerBody is not None and len(self.InnerBody): + if self.Version == P2P.Version.V1 and self.Header.MessageSize == self.Header.TotalSize: + self.innerMessage = SLPMessage.SLPMessage.Parse(self.InnerBody) + elif self.Version == P2P.Version.V2 and self.Header.DataRemaining == 0 and self.Header.TFCombination == P2P.TFCombination.First: + self.innerMessage = SLPMessage.SLPMessage.Parse(self.InnerBody) + + return self.innerMessage + + def _set_InnerMessage(self, value): + if hasattr(value, 'GetBytes'): + self.InnerBody = value.GetBytes() + else: + self.InnerBody = str(value) + + InnerMessage = property(_get_InnerMessage, _set_InnerMessage) + + @property + def IsSLPData(self): + if self.Header.MessageSize > 0 and self.Header.SessionId == 0: + if self.Version == P2P.Version.V1: + if self.Header.Flags in (P2P.Flags.Normal, P2P.Flags.MSNSLPInfo): + return True + elif self.Version == P2P.Version.V2: + if self.Header.TFCombination in (P2P.TFCombination.NONE, P2P.TFCombination.First): + return True + + return False + + def CreateAck(self): + ack = P2PMessage(self.Version) + ack.Header = self.Header.CreateAck() + if self.Version == P2P.Version.V1: + ack.Footer = self.Footer + + return ack + + def GetNextMessage(self, maxSize): + if self.sendingData: + if self._gen is None: + self._gen = self.SplitMessage(maxSize) + return self._gen.next() + + try: + message, callback_dict = self._gen.send(maxSize) + except StopIteration: + assert self.Finished + return None, {} + else: + return message, callback_dict + else: + return None, {} + + def SplitMessage(self, maxSize): + # Generator. Yields packets + payloadMessageSize = 0 + version1 = self.Version == P2P.Version.V1 + version2 = self.Version == P2P.Version.V2 + + if version1: + payloadMessageSize = self.Header.MessageSize + elif version2: + payloadMessageSize = self.Header.MessageSize - self.Header.DataPacketHeaderLength + + if payloadMessageSize <= maxSize: + maxSize = (yield self, {}) + self.Finished = True + raise StopIteration + + if self.InnerBody is not None: + totalMessage = self.InnerBody + else: + totalMessage = self.InnerMessage.GetBytes() + + if not hasattr(totalMessage, 'seek'): + messageLen = len(totalMessage) + totalMessage = io.BytesIO(totalMessage) + else: + messageLen = len(totalMessage.getvalue()) + + offset = 0 + if version1: + firstMessage = True + while offset < messageLen: + chunkMessage = P2PMessage(self.Version) + messageSize = min(maxSize, messageLen - offset) + totalMessage.seek(offset) + chunk = totalMessage.read(messageSize) + + chunkMessage.Header.Flags = self.Header.Flags + chunkMessage.Header.AckIdentifier = self.Header.AckIdentifier + chunkMessage.Header.AckTotalSize = self.Header.AckTotalSize + chunkMessage.Header.Identifier = self.Header.Identifier + chunkMessage.Header.SessionId = self.Header.SessionId + chunkMessage.Header.TotalSize = self.Header.TotalSize + chunkMessage.Header.Offset = offset + chunkMessage.Header.MessageSize = messageSize + chunkMessage.InnerBody = chunk + + chunkMessage.Header.AckSessionId = self.Header.AckSessionId + chunkMessage.Footer = self.Footer + +# chunkMessage.PrepareMessage() + + if firstMessage: + firstMessage = False + + maxSize = (yield chunkMessage, {}) + + offset += messageSize + + elif version2: + nextId = self.Header.Identifier + dataRemain = self.Header.DataRemaining + firstMessage = True + while offset < messageLen: + chunkMessage = P2PMessage(self.Version) + maxDataSize = maxSize + + if offset == 0 and len(self.Header.HeaderTLVs): + for key, value in self.Header.HeaderTLVs.items(): + chunkMessage.Header.HeaderTLVs[key] = value + + maxDataSize = maxSize - chunkMessage.Header.HeaderLength + + dataSize = min(maxDataSize, messageLen - offset) + totalMessage.seek(offset) + chunk = totalMessage.read(dataSize) + + if offset == 0: + chunkMessage.Header.OperationCode = self.Header.OperationCode + + chunkMessage.Header.SessionId = self.Header.SessionId + chunkMessage.Header.TFCombination = self.Header.TFCombination + chunkMessage.Header.PackageNumber = self.Header.PackageNumber + + if (messageLen + dataRemain - (dataSize + offset)) > 0: + chunkMessage.Header.DataRemaining = messageLen + dataRemain - (dataSize + offset) + + if offset and ((self.Header.TFCombination & P2P.TFCombination.First) == P2P.TFCombination.First): + chunkMessage.Header.TFCombination = self.Header.TFCombination - P2P.TFCombination.First + + now = time.time() + if firstMessage or self.nextRak < now: + self.nextRak = now + self.RAK_WAIT_TIME + chunkMessage.SetRAK() + firstMessage = False + self.sendingData = False + + def _resume_sending(*a): + log.info("Resume sending!") + self.sendingData = True + + callbacks = dict(success = _resume_sending) + else: + callbacks = {} + + chunkMessage.InnerBody = chunk +# chunkMessage.Header.Identifier = nextId + chunkMessage.Header.Identifier = 0 + + nextId += chunkMessage.Header.MessageSize + + maxSize = (yield chunkMessage, callbacks) + + offset += dataSize + + self.Finished = True + + def SetRAK(self): + if self.Version == P2P.Version.V2 and not self.Header.IsAck: + self.Header.OperationCode |= P2P.OperationCode.RAK + return (self.Header.OperationCode & P2P.OperationCode.RAK) == P2P.OperationCode.RAK + + def GetBytes(self, append_footer = True): + self.InnerBody = self.GetInnerBytes() + if append_footer: + footer = struct.pack('>I', self.Footer) + else: + footer = '' + + return self.Header.GetBytes() + self.InnerBody + footer + + def ParseBytes(self, data): + header_len = self.Header.ParseHeader(data) + bodyAndFooter = data[header_len:] + + innerBodyLen = 0 + if self.Header.MessageSize > 0 or self.Header.TotalSize > 0: + if self.Version == P2P.Version.V1: + self.InnerBody = bodyAndFooter[:(self.Header.MessageSize or self.Header.TotalSize)] + innerBodyLen = len(self.InnerBody) + elif self.Version == P2P.Version.V2: + self.InnerBody = bodyAndFooter[:self.Header.MessageSize - self.Header.DataPacketHeaderLength] + innerBodyLen = len(self.InnerBody) + else: + self.InnerBody = '' + + footer_data = bodyAndFooter[innerBodyLen:] + if len(footer_data) >= 4: + self.Footer = struct.unpack('>I', footer_data[:4])[0] + + def GetInnerBytes(self): + if self.InnerBody is not None: + return self.InnerBody + else: + return getattr(self.InnerMessage, 'GetBytes', lambda:'')() + +class P2PDataMessage(P2PMessage): + def WritePreparationBytes(self): + self.InnerBody = '\0'*4 + + def feed(self, stream, maxread = -1): + position = stream.tell() + # figure out length + stream.seek(0, 2) # 2 is relative to EOF + length = stream.tell() + stream.seek(position) + + read_amount = min(maxread, length - position) + if self.Version == P2P.Version.V1: + self.Header.Offset = position + self.Header.TotalSize = length + else: + self.Header.DataRemaining = length - (position + read_amount) + + bytes = stream.read(read_amount) + assert len(bytes) == read_amount + self.InnerBody = bytes + + return len(bytes) + +class P2PDCMessage(P2PDataMessage): + def GetBytes(self): + data = super(P2PDCMessage, self).GetBytes(False) + return struct.pack(' 0: + #log.debug("Message not buffered because size is 0") + return False, msg + + if msg.Version == P2P.Version.V2: + if ((msg.Header.TFCombination == P2P.TFCombination.First and msg.Header.DataRemaining == 0) or + (msg.Header.TFCombination > P2P.TFCombination.First)): + #log.info("Message not buffered: first and complete") + return False, msg + + if msg.Header.TFCombination == P2P.TFCombination.First and msg.Header.DataRemaining > 0: + totalMessage = msg.Copy(msg) + totalSize = msg.Header.MessageSize - msg.Header.DataPacketHeaderLength + msg.Header.DataRemaining + # TODO: make innerbody a file-like object + totalMessage.InnerBody = msg.InnerBody + + self.messages[msg.Version][msg.Header.Identifier + msg.Header.MessageSize] = totalMessage + #log.info("Message buffered with id = %r. data remaining: %r", msg.Header.Identifier + msg.Header.MessageSize, msg.Header.DataRemaining) + return True, msg + + if msg.Header.TFCombination == P2P.TFCombination.NONE: + totalMessage = self.messages[msg.Version].get(msg.Header.Identifier, None) + if totalMessage is None: + log.info("Message incomplete. unknown buffered message with id = %r. known ids are: %r", msg.Header.Identifier, self.messages[msg.Version].keys()) + return True, msg + + if totalMessage.Header.PackageNumber != msg.Header.PackageNumber: + log.info("Message incomplete. unknown package number") + return True, msg + + dataSize = min(msg.Header.MessageSize - msg.Header.DataPacketHeaderLength, totalMessage.Header.DataRemaining) + offset = len(totalMessage.InnerBody) #- totalMessage.Header.DataRemaining + + if ((msg.Header.DataRemaining + dataSize == totalMessage.Header.DataRemaining) + and ((dataSize + offset + msg.Header.DataRemaining) == (totalMessage.Header.TotalSize + totalMessage.Header.DataRemaining))): + + # TODO: make innerbody a file-like object + totalMessage.InnerBody += msg.InnerBody + originalIdentifier = msg.Header.Identifier + newIdentifier = msg.Header.Identifier + msg.Header.MessageSize + + totalMessage.Header.DataRemaining = msg.Header.DataRemaining + totalMessage.Header.Identifier = newIdentifier + + if originalIdentifier != newIdentifier: + self.messages[msg.Version].pop(originalIdentifier, None) + + if msg.Header.DataRemaining > 0: + self.messages[msg.Version][newIdentifier] = totalMessage + #log.info("message incomplete. newId = %r, data remaining: %r", newIdentifier, msg.Header.DataRemaining) + return True, msg + else: + totalMessage.InnerBody = totalMessage.InnerBody + totalMessage.Header.Identifier = newIdentifier - totalMessage.Header.MessageSize + + log.info("message complete. total size = %r", len(totalMessage.InnerBody)) + + return False, totalMessage + else: + pass + # V1 + else: + if (msg.Header.MessageSize == msg.Header.TotalSize) or ((msg.Header.Flags & P2P.Flags.Data) == P2P.Flags.Data): + log.info("message complete. total size = %r", msg.Header.TotalSize) + return False, msg + + totalMessage = self.messages[msg.Version].get(msg.Header.Identifier, None) + if totalMessage is None: + # TODO: make innerbody a file-like object + totalPayload = msg.InnerBody + copy = msg.Copy(msg) + copy.InnerBody = totalPayload + copy.Header.Offset = msg.Header.Offset + msg.Header.MessageSize + + self.messages[msg.Version][msg.Header.Identifier] = copy + #log.info("message incomplete. data remaining = %r", copy.Header.TotalSize - copy.Header.Offset) + return True, msg + + if (msg.Header.TotalSize == totalMessage.Header.TotalSize) and ((msg.Header.Offset + msg.Header.MessageSize) <= totalMessage.Header.TotalSize): + # TODO: make innerbody a file-like object, seek to offset + totalMessage.InnerBody += msg.InnerBody + totalMessage.Header.Offset = msg.Header.Offset + msg.Header.MessageSize + + if totalMessage.Header.Offset == msg.Header.TotalSize: + totalMessage.Header.Offset = 0 + self.messages[msg.Version].pop(msg.Header.Identifier, None) + log.info("message complete. total size = %r", msg.Header.TotalSize) + return False, totalMessage + + #log.info("message incomplete. total size = %r", totalMessage.Header.TotalSize - totalMessage.Header.Offset) + return True, msg + + self.messages[msg.Version].pop(msg.Header.Identifier, None) + + return True, msg + + def Clear(self): + self.messages.clear() diff --git a/digsby/src/msn/P2P/P2PObjectTransfer.py b/digsby/src/msn/P2P/P2PObjectTransfer.py new file mode 100644 index 0000000..56253fb --- /dev/null +++ b/digsby/src/msn/P2P/P2PObjectTransfer.py @@ -0,0 +1,160 @@ +import logging +log = logging.getLogger('msn.p2p.msnobj') +import hashlib +import sys +import io +import random +import uuid +import msn.P2P as P2P +import msn.MSNObject as MSNObject +import msn.P2P.P2PApplication as P2PApp +import msn.P2P.P2PMessage as P2PMessage + +@P2PApp.P2PApplication.RegisterApplication +class ObjectTransfer(P2PApp.P2PApplication): + AppId = 12 + EufGuid = uuid.UUID("A4268EEC-FEC5-49E5-95C3-F126696BDBF6") + + sending = False + msnObject = None + + def _get_AutoAccept(self): + return True + + def _get_InvitationContext(self): + return (str(self.msnObject) + '\0').encode('b64') + + def __init__(self, session = None, obj = None, remote = None): + if session is not None: + # We're sending + super(ObjectTransfer, self).__init__(session = session) + self.msnObject = MSNObject.parse(self.P2PSession.Invitation.BodyValues['Context'].decode('b64').strip('\0')) + if self.msnObject.type == '3': + self.msnObject = self.client.self_buddy.msn_obj + self.data_buffer = self.client.self_buddy.icon_path.open('rb') + + self.sending = True + AppId = self.P2PSession.Invitation.BodyValues.get('AppID', None) + if AppId is not None: + self.AppId = int(AppId) + + else: + super(ObjectTransfer, self).__init__(ver = remote.P2PVersionSupported, remote = remote, remoteEP = remote.SelectRandomEPID()) + self.msnObject = obj + self.data_buffer = io.BytesIO() + if obj.type == '3': + self.AppId = 12 + + self.sending = False + + def SetupInviteMessage(self, slp): + slp.BodyValues['RequestFlags'] = '18' + super(ObjectTransfer, self).SetupInviteMessage(slp) + + def ValidateInvitation(self, invite): + ret = super(ObjectTransfer, self).ValidateInvitation(invite) + if not ret: + return ret + + return MSNObject.parse(invite.BodyValues['Context'].decode('b64').strip('\0')).type == '3' + + def Start(self): + if not super(ObjectTransfer, self).Start(): + return False + +# if self.Remote.DirectBridge is None: +# log.error("Don't have a direct bridge (MSNObject)") +# return False + + if self.sending: + log.info("Starting send for %r", self) + self.P2PSession.Bridge.packageNumber += 1 + packNum = self.P2PSession.Bridge.packageNumber + prepData = P2PMessage.P2PDataMessage(self.version) + prepData.WritePreparationBytes() + + if self.version == P2P.Version.V2: + prepData.Header.TFCombination = P2P.TFCombination.First + + def after_data_prep_send(ack = None): + allData = self.data_buffer.read() + self._close_data_buffer() + + msg = P2PMessage.P2PDataMessage(self.version) + if self.version == P2P.Version.V1: + msg.Header.Flags = P2P.Flags.Data + msg.Header.AckSessionId = random.randint(50, sys.maxint) + else: + msg.Header.TFCombination = P2P.TFCombination.MsnObject | P2P.TFCombination.First + msg.Header.PackageNumber = packNum + + msg.InnerBody = allData + + if self.version == P2P.Version.V1: + log.info("Sending data message for (V1) %r", self) + self.SendMessage(msg, success = lambda ack: self.OnTransferFinished()) + else: + log.info("Sending data message for (V2) %r", self) + + def after_data_send(ack = None): + rak = P2PMessage.P2PMessage(self.version) + rak.SetRAK() + log.info("Sending RAK message for (V2). rak = %r, app = %r", rak, self) + self.SendMessage(rak, success = lambda ack: self.OnTransferFinished()) + + self.SendMessage(msg, success = after_data_send) + + log.info("Sending data prep message for %r", self) + + self.SendMessage(prepData, after_send = after_data_prep_send) + + else: + log.info("Starting receive for %r", self) + self.data_buffer = io.BytesIO() + + def ProcessData(self, bridge, data, reset): + if hasattr(data, 'getvalue'): + data = data.getvalue() + + log.info('ProcessData: bridge = %r, data = %r, reset = %r', bridge, data, reset) + if self.sending: + return False + + if reset: + self.data_buffer = io.BytesIO() + + if len(data): + self.data_buffer.write(data) + + log.info("Got %s/%s bytes", self.data_buffer.tell(), self.msnObject.size) + if str(self.data_buffer.tell()) == self.msnObject.size: + log.info("\t finished!") + allData = self.data_buffer.getvalue() + dataSha = hashlib.sha1(allData).digest() + if dataSha.encode('b64') != self.msnObject.sha1d: + log.error("dataSha = %r, expectedSha = %r", dataSha.encode('b64'), self.msnObject.sha1d) + return False + + self.data_buffer = io.BytesIO() + self.OnTransferFinished(allData) + if self.P2PSession: + self.P2PSession.Close() # Sends BYE + + return True + + def _close_data_buffer(self): + if self.sending: + getattr(getattr(self, 'data_buffer', None), 'close', lambda:None)() + + def OnTransferFinished(self, *a): + self._close_data_buffer() + super(ObjectTransfer, self).OnTransferFinished(*a) + + def OnTransferAborted(self, who): + self._close_data_buffer() + super(ObjectTransfer, self).OnTransferAborted(who) + + def OnTransferError(self): + self._close_data_buffer() + super(ObjectTransfer, self).OnTransferError() + diff --git a/digsby/src/msn/P2P/P2PSendQueue.py b/digsby/src/msn/P2P/P2PSendQueue.py new file mode 100644 index 0000000..e640578 --- /dev/null +++ b/digsby/src/msn/P2P/P2PSendQueue.py @@ -0,0 +1,86 @@ +import types +import Queue + +class P2PSendItem(object): + + def __init__(self, remote, remoteGuid, message, callback = None): + self.Remote = remote + self.RemoteGuid = remoteGuid + self.p2pMessage = message + self.callback = callback + + def __repr__(self): + return '%s(remote = %r, remoteGuid = %r, message = %r)' % (type(self).__name__, + self.Remote.account, + self.RemoteGuid, + self.p2pMessage) + +class P2PSendQueue(Queue.Queue): + def Enqueue(self, remote, remoteGuid = None, message = None, callback = None): + if remoteGuid is None and message is None: + assert type(remote) is P2PSendItem + item = remote + else: + item = P2PSendItem(remote, remoteGuid, message, callback) + + return self.put(item) + + def Dequeue(self): + if not self.queue: + return None + + sendItem = self.queue[0] + + if isinstance(sendItem.p2pMessage, types.GeneratorType): + try: + message = sendItem.p2pMessage.next() + except StopIteration: + self.get_nowait() + return self.Dequeue() + else: + return P2PSendItem(sendItem.Remote, sendItem.RemoteGuid, message, sendItem.callback) + else: + try: + return self.get_nowait() + except Queue.Empty: + return None + + def __len__(self): + return self.qsize() + + +class P2PSendList(list): + def append(self, remote, remoteGuid = None, message = None, callback = None): + + if remoteGuid is None and message is None: + assert type(remote) is P2PSendItem + item = remote + else: + item = P2PSendItem(remote, remoteGuid, message, callback) + + return list.append(self, item) + + def __contains__(self, thing): + if list.__contains__(self, thing): + return True + + if thing in [x.p2pMessage for x in self]: + return True + + return False + + def remove(self, thing): + if list.__contains__(self, thing): + list.remove(self, thing) + + mything = None + for mything in self: + if mything.p2pMessage == thing: + break + else: + mything = None + + if mything is None: + raise ValueError("x not in list: %r" % thing) + else: + list.remove(self, mything) diff --git a/digsby/src/msn/P2P/P2PSession.py b/digsby/src/msn/P2P/P2PSession.py new file mode 100644 index 0000000..355b3c9 --- /dev/null +++ b/digsby/src/msn/P2P/P2PSession.py @@ -0,0 +1,1022 @@ +import io +import msn.P2P as P2P +import msn.P2P.P2PMessage as P2PMessage +import msn.P2P.MSNSLPMessages as MSNSLP +import logging +log = logging.getLogger('msn.p2p.session') +import sys +import uuid +import struct +import common +import msn.AddressBook as MSNAB +import util +import util.net as net +import util.Events as Events +import util.primitives.funcs as funcs +import util.primitives.structures as structures +import util.callbacks as callbacks + +import msn.P2P.P2PApplication as P2PApplication + +def randid(): + import random + return random.randint(5000, sys.maxint//2) + +class P2PSessionStatus: + Error = 0 + WaitingForLocal = 1 + WaitingForRemote = 2 + Active = 3 + Closing = 4 + Closed = 5 + +class DCNonceType: + NONE = 0 + Plain = 1 + Sha1 = 2 + +class P2PSession(Events.EventMixin): + events = Events.EventMixin.events | set(( + 'Error', + 'Activated', + 'Closing', + 'Closed', + + 'Messages', + )) + + SessionId = 0 + LocalBaseIdentifier = 0 + LocalIdentifier = 0 + RemoteBaseIdentifier = 0 + RemoteIdentifier = 0 + + LocalContact = None + RemoteContact = None + LocalContactEPID = uuid.UUID(int = 0) + remoteContactEPID = uuid.UUID(int = 0) + + Invitation = None + Bridge = None + App = None + _sent_bye = False + + Version = P2P.Version.V1 + Status = P2PSessionStatus.Closed + protocol = None + + def __repr__(self): + varstr = ', ' .join('%s=%r' % i for i in vars(self).items()) + return '%s(%s)' % (type(self).__name__, varstr) + + @property + def Local(self): + return self.LocalContact + + @property + def Remote(self): + return self.RemoteContact + + @property + def LocalContactId(self): + if self.Version == P2P.Version.V1: + return self.Local.account + + return self.Local.account + ';{' + str(self.LocalContactEPID).lower() + '}' + + @property + def RemoteContactId(self): + if self.Version == P2P.Version.V1: + return self.Remote.account + + return self.Remote.account + ';{' + str(self.RemoteContactEPID).lower() + '}' + + def _get_RemoteContactEPID(self): + if int(self.remoteContactEPID) == 0: + self.remoteContactEPID = self.RemoteContact.SelectRandomEPID() + + return self.remoteContactEPID + + def _set_RemoteContactEPID(self, val): + if not isinstance(val, uuid.UUID): + if isinstance(val, int): + val = uuid.UUID(int = val) + else: + # Assume string + val = uuid.UUID(val) + + if int(val) == 0: + val = self.RemoteContact.SelectRandomEPID() + + self.remoteContactEPID = val + + RemoteContactEPID = property(_get_RemoteContactEPID, _set_RemoteContactEPID) + + @property + def NSMessageHandler(self): + return self.protocol + + + def __init__(self, slp = None, msg = None, ns = None, bridge = None, app = None): + Events.EventMixin.__init__(self) + self.directNegotiationTimer = None + self.timeoutTimer = None + + self.sessionMessages = [] + self.dataMessages = [] + + if app is not None: + self.LocalInit(app) + else: + self.RemoteInit(slp, msg, ns, bridge) + + log.info("New session: %r", vars(self)) + + + def KillDirectNegotiationTimer(self): + if self.directNegotiationTimer is not None: + self.directNegotiationTimer.stop() + self.directNegotiationTimer = None + #log.info("Killing directNegotiationTimer") + + self.ResetTimeoutTimer() + + def DirectNegotiationSuccessful(self): + self.KillDirectNegotiationTimer() + + def DirectNegotiationTimedOut(self, session = None): + log.debug("DirectNegotiationTimedOut") + self.DirectNegotiationFailed() + + def DirectNegotiationFailed(self): + log.debug("DirectNegotiationFailed") + self.KillDirectNegotiationTimer() + + if self.Bridge is not None: + log.info("Resume sending on %r", self.Bridge) + self.Bridge.ResumeSending(self) + else: + log.error("Can't resume sending, no bridge!") + + if self.App is not None: + self.App.Start() + + def ConnectionType(self, protocol): + connType = 'Unknown-Connect' + + local_ip = net.ip_from_bytes(net.myip()) + local_port = protocol.get_local_sockname()[1] + + netId = struct.unpack('>I', net.myip())[0] + + remote_ip = protocol.ip + remote_port = protocol.port + + ip_restrict = local_ip != remote_ip + port_restrict = local_port != remote_port + + if ip_restrict: + if port_restrict: + return 'Symmetric-NAT', netId + else: + return 'IP-Restrict-NAT', netId + else: + if port_restrict: + return 'Port-Restrict-NAT', netId + else: + return 'Direct-Connect', netId + + def SendDirectInvite(self): + if not common.pref('msn.p2p.allow_direct', type = bool, default = True): + return False + + if self.Remote.DirectBridge is not None and self.Remote.DirectBridge.IsOpen: + return False + + self.ResetDCTimer() + + connType, netId = self.ConnectionType(self.protocol) + + remote = self.Remote + ver = self.Version + message = P2PMessage.P2PMessage(ver) + + slp = MSNSLP.SLPRequestMessage(self.RemoteContactId, MSNSLP.RequestMethod.INVITE) + slp.Source = self.LocalContactId + slp.CSeq = 0 + slp.CallId = self.Invitation.CallId + slp.MaxForwards = 0 + slp.ContentType = 'application/x-msnmsgr-transreqbody' + + slp.BodyValues['Bridges'] = 'TCPv1 SBBridge' + slp.BodyValues['Capabilities-Flags'] = '1' + slp.BodyValues['NetID'] = str(netId) + slp.BodyValues['Conn-Type'] = connType + slp.BodyValues['TCP-Conn-Type'] = connType + slp.BodyValues['UPnPNat'] = 'false' + slp.BodyValues['ICF'] = 'true' if connType == 'Firewall' else 'false' + slp.BodyValues['Nat-Trav-Msg-Type'] = 'WLX-Nat-Trav-Msg-Direct-Connect-Req' + + remote.GenerateNewDCKeys() + slp.BodyValues['Hashed-Nonce'] = '{%s}' % str(remote.dcLocalHashedNonce).upper() + + message.InnerMessage = slp + + if ver == P2P.Version.V2: + message.Header.TFCombination = P2P.TFCombination.First + else: + message.Header.Flags = P2P.Flags.MSNSLPInfo + + Bridge = self.protocol.ns.SDGBridge + Bridge.StopSending(self) + self.ResetDCTimer() + + log.info("Sending direct invite") + Bridge.Send(None, remote, self.RemoteContactEPID, message) + + return True + + def ResetDCTimer(self): + self.KillTimeoutTimer() + #log.info("Resetting directNegotiationTimer") + if self.directNegotiationTimer is None: + self.directNegotiationTimer = util.ResetTimer(17, self.DirectNegotiationTimedOut) + self.directNegotiationTimer.start() + else: + self.directNegotiationTimer.reset() + + def InactivityClose(self): + log.info("Closing session due to inactivity") + if self.Status == P2PSessionStatus.Active and \ + self.App is not None and \ + self.App.status == P2PApplication.AppStatus.Active and \ + hasattr(self.App, 'Sending'): + + # Other client stopped sending data. + if not self.App.Sending: + self.OnClosed(self.Remote) + self.SendBye() + return + + self.Close() + self.KillTimeoutTimer() + + def KillTimeoutTimer(self): + if self.timeoutTimer is not None: + #log.info("Killing timeoutTimer") + self.timeoutTimer.stop() + self.timeoutTimer = None + + def RemoteInit(self, slp, msg, client, bridge): + self.protocol = client = getattr(client, 'ns', client).protocol + if bridge is None: + bridge = client.ns.SDGBridge + + self.Invitation = slp + version1 = version2 = False + if uuid.UUID(int = 0) in (slp.FromEndPoint, slp.ToEndPoint): + version1 = True + self.Version = P2P.Version.V1 + else: + version2 = True + self.Version = P2P.Version.V2 + + if slp.ToEmailAccount == client.self_buddy.name: + self.LocalContact = client.ns.contact_list.owner + else: + self.LocalContact = client.ns.contact_list.GetContact(slp.ToEmailAccount, MSNAB.IMAddressInfoType.WindowsLive) + + self.RemoteContact = client.ns.contact_list.GetContact(slp.FromEmailAccount, MSNAB.IMAddressInfoType.WindowsLive) + + if version2: + self.LocalContactEPID = slp.ToEndPoint + self.RemoteContactEPID = slp.FromEndPoint + + try: + self.SessionId = int(slp.BodyValues.get('SessionID', '0')) + except (TypeError, ValueError): + self.SessionId = 0 + + self.Bridge = bridge + self.LocalIdentifier = self.LocalBaseIdentifier = bridge.localTrackerId + + self.Status = P2PSessionStatus.WaitingForLocal + self.RemoteContact.bind_event('DirectBridgeEstablished', self.RemoteDirectBridgeEstablished) + + if msg is not None: + self.RemoteIdentifier = self.RemoteBaseIdentifier = msg.Header.Identifier + if version2: + self.RemoteIdentifier += msg.Header.MessageSize + + appId = int(slp.BodyValues.get('AppID', 0)) + eufGuid = uuid.UUID(slp.BodyValues.get('EUF-GUID', str(uuid.UUID(int=0)))) + self.App = P2PApplication.P2PApplication.CreateInstance(eufGuid, appId, self) + + log.info("Attempting invitation validation for app = %r...", self.App) + if self.App is not None and self.App.ValidateInvitation(self.Invitation): + if self.App.AutoAccept: + log.info("Valid invitation and AutoAccept") + self.Accept(True) + else: + log.info("Valid invitation and asking user...") + self.KillTimeoutTimer() + self.protocol.OnInvitationReceived(self) + else: + log.info("Invalid invitation") + self.Decline() + + def LocalInit(self, app): + self.App = app + self.Version = app.Version + self.LocalContact = app.Local + self.RemoteContact = app.Remote + + self.LocalContactEPID = app.client.get_machine_guid() + self.RemoteContactEPID = app.Remote.SelectRandomEPID() + + self.protocol = getattr(app.Local.client, 'ns', app.Local.client).protocol + self.SessionId = randid() + + invite = self.Invitation = MSNSLP.SLPRequestMessage(self.RemoteContactId, MSNSLP.RequestMethod.INVITE) + invite.Target = self.RemoteContactId + invite.Source = self.LocalContactId + invite.ContentType = 'application/x-msnmsgr-sessionreqbody' + invite.BodyValues['SessionID'] = str(self.SessionId) + app.SetupInviteMessage(invite) + app.P2PSession = self + + self.RemoteContact.bind('DirectBridgeEstablished', self.RemoteDirectBridgeEstablished) + self.Status = P2PSessionStatus.WaitingForRemote + + def _PrepSLPMessage(self, slpMessage): + slpMessage.Target = self.RemoteContactId + slpMessage.Source = self.LocalContactId + slpMessage.Branch = self.Invitation.Branch + slpMessage.CallId = self.Invitation.CallId + slpMessage.CSeq = 1 + slpMessage.ContentType = 'application/x-msnmsgr-sessionreqbody' + slpMessage.BodyValues['SessionID'] = str(self.SessionId) + + def _MakeSLPStatusMessage(self, for_who, code, phrase): + slpMessage = MSNSLP.SLPStatusMessage(for_who, code, phrase) + self._PrepSLPMessage(slpMessage) + return slpMessage + + def _MakeSLPRequestMessage(self, for_who, method): + slpMessage = MSNSLP.SLPRequestMessage(for_who, method) + self._PrepSLPMessage(slpMessage) + return slpMessage + + def Invite(self): + if self.Status != P2PSessionStatus.WaitingForRemote: + log.info("My status is %r, should be %r. (self=%r)", self.Status, P2PSessionStatus.WaitingForRemote, self) + return False + + self.MigrateToOptimalBridge() + log.info("Setting LocalIdentifier to bridge's: %r", self.Bridge.localTrackerId) + self.LocalIdentifier = self.LocalBaseIdentifier = self.Bridge.localTrackerId + + message = self.WrapSLPMessage(self.Invitation) + + if self.Version == P2P.Version.V2: + if not self.Bridge.Synced: + message.Header.OperationCode = P2P.OperationCode.SYN | P2P.OperationCode.RAK + message.Header.AppendPeerInfoTLV() + + message.InnerMessage = self.Invitation + + def after_send(ack): + if self.Remote.DirectBridge is None: + self.SendDirectInvite() + + log.info("Invite send success. Got RemoteBaseIdentifier (%r)", ack.Header.Identifier) + self.RemoteIdentifier = self.RemoteBaseIdentifier = ack.Header.Identifier + + log.info("Sending invitation for %r / %r", self, self.App) + self.KillTimeoutTimer() + self.Send(message, success = after_send) + + def Accept(self, sendDCInvite): + if self.Status != P2PSessionStatus.WaitingForLocal: + return False + + if sendDCInvite and self.Remote.DirectBridge is None: + self.SendDirectInvite() + + slpMessage = self._MakeSLPStatusMessage(self.RemoteContactId, 200, 'OK') + + log.info("Accepting invite for %r / %r", self, self.App) + + def after_200(ack = None): + log.info("Invite accept sent.") + self.OnActive() + + log.info("\tStarting app: %r", self.App) + if self.App is not None: + self.App.Start() + + self.Send(self.WrapSLPMessage(slpMessage), success = after_200) + + def Decline(self): + if self.Status != P2PSessionStatus.WaitingForLocal: + return False + + slpMessage = self._MakeSLPStatusMessage(self.RemoteContactId, 603, 'Decline') + + msg = P2PMessage.P2PMessage(self.Version) + if self.Version == P2P.Version.V1: + msg.Header.Flags = P2P.Flags.MSNSLPInfo + else: + msg.Header.OperationCode = P2P.OperationCode.RAK + msg.Header.TFCombination = P2P.TFCombination.First + msg.Header.PackageNumber = self.Bridge.IncrementPackageNumber() + + msg.InnerMessage = slpMessage + + self.Send(msg, success = lambda ack: self.Close()) + + def Close(self): + if self.Status == P2PSessionStatus.Closing: + self.OnClosed(self.Local) + return + + self.OnClosing(self.Local) + + self.SendBye() + + def SendBye(self): + if self._sent_bye: + log.info("Already sent bye message. not re-sending") + return + self._sent_bye = True + log.info("Constructing and queueing BYE message for %r", self) + slpMessage = self._MakeSLPRequestMessage(self.RemoteContactId, 'BYE') + slpMessage.MaxForwards = 0 + slpMessage.CSeq = 0 + slpMessage.Branch = self.Invitation.Branch + slpMessage.ContentType = 'application/x-msnmsgr-sessionclosebody' + + msg = P2PMessage.P2PMessage(self.Version) + if msg.Version == P2P.Version.V1: + msg.Header.Flags = P2P.Flags.MSNSLPInfo + else: + msg.Header.OperationCode = P2P.OperationCode.RAK + msg.Header.TFCombination = P2P.TFCombination.First + if self.Bridge is not None: + msg.Header.PackageNumber = self.Bridge.IncrementPackageNumber() + + msg.InnerMessage = slpMessage + self.Send(msg, after_send = lambda ack=None: self.OnClosed(self.Local)) + + def Dispose(self): + self.KillTimeoutTimer() + self.Remote.unbind('DirectBridgeEstablished', self.RemoteDirectBridgeEstablished) + self.DisposeApp() + self.Migrate(None) + + def OnClosing(self, contact): + self.KillTimeoutTimer() + self.Status = P2PSessionStatus.Closing + self.event('Closing', self, contact) + self.DisposeApp() + + def OnClosed(self, contact): + self.KillTimeoutTimer() + self.Status = P2PSessionStatus.Closed + + self.event('Closed', self, contact) + self.DisposeApp() + + def OnError(self): + self.Status = P2PSessionStatus.Error + self.KillTimeoutTimer() + self.event('Error', self) + self.DisposeApp() + + def OnActive(self): + self.Status = P2PSessionStatus.Active + self.ResetTimeoutTimer() + self.event('Activated', self) + + def DisposeApp(self): + if self.App is not None: + self.App.Dispose() + self.App = None + + def ResetTimeoutTimer(self): + #log.info("Resetting timeoutTimer") + if self.Status not in (P2PSessionStatus.Closed, P2PSessionStatus.Active,): + #log.debug("Wrong state for starting timeout timer") + return + + if self.timeoutTimer is None: + self.timeoutTimer = util.ResetTimer(12, self.InactivityClose) + self.timeoutTimer.start() + else: + self.timeoutTimer.reset() + + def ProcessP2PMessage(self, bridge, message, slp): + #log.info("Got message for %r: %r", self, message) + self.ResetTimeoutTimer() + + self.RemoteIdentifier = message.Header.Identifier + if self.Version == P2P.Version.V2: + self.RemoteIdentifier += message.Header.MessageSize + + if self.Status in (P2PSessionStatus.Closed, P2PSessionStatus.Error): + log.info("Session is closed / error'd. Not handling message %r", message) + return False + + if slp is not None: + if hasattr(slp, 'Method'): + # Request + if slp.ContentType == 'application/x-msnmsgr-sessionclosebody' and slp.Method == 'BYE': + log.info("Got BYE message from %r", self.Remote.account) + if message.Version == P2P.Version.V1: + byeAck = message.CreateAck() + byeAck.Header.Flags = P2P.Flags.CloseSession + self.Send(byeAck) + else: + slpMessage = MSNSLP.SLPRequestMessage(self.RemoteContactId, 'BYE') + slpMessage.Target = self.RemoteContactId + slpMessage.Source = self.LocalContactId + slpMessage.Branch = self.Invitation.Branch + slpMessage.CallId = self.Invitation.CallId + slpMessage.ContentType = 'application/x-msnmsgr-sessionclosebody' + slpMessage.BodyValues['SessionID'] = str(self.SessionId) + + log.info("Sending my own BYE message") + self.Send(self.WrapSLPMessage(slpMessage)) + + self.OnClosed(self.Remote) + return True + + elif (slp.ContentType == 'application/x-msnmsgr-sessionreqbody') and (slp.Method == 'INVITE'): + slpMessage = MSNSLP.SLPStatusMessage(self.RemoteContactId, 500, 'Internal Error') + slpMessage.Target = self.RemoteContactId + slpMessage.Source = self.LocalContactId + slpMessage.Branch = self.Invitation.Branch + slpMessage.CallId = self.Invitation.CallId + slpMessage.ContentType = 'application/x-msnmsgr-sessionreqbody' + slpMessage.BodyValues['SessionID'] = str(self.SessionId) + + errorMessage = self.WrapSLPMessage(slpMessage) + bridge.Send(None, self.Remote, self.RemoteContactEPID, errorMessage) + return True + elif slp.ContentType in ('application/x-msnmsgr-transreqbody', + 'application/x-msnmsgr-transrespbody', + 'application/x-msnmsgr-transdestaddrupdate'): + ProcessDirectInvite(slp, self.protocol, self) + return True + else: + if slp.Code == 200: + if slp.ContentType == 'application-x-msnmsgr-transrespbody': + ProcessDirectInvite(slp, self.protocol, self) + + else: + log.info("Got 200 OK for invite. Starting app = %r", self.App) + self.OnActive() + self.App.Start() + + return True + + elif slp.Code == 603: + self.OnClosed(self.Remote) + return True + + elif slp.Code == 500: + return True + + if self.App is None: + log.error("No app set up. Ignoring message = %r", message) + return False + + if message.Header.MessageSize > 0 and message.Header.SessionId > 0: + reset = False + appData = io.BytesIO() + if message.Header.MessageSize == 4 and message.InnerBody == ('\0'*4): + reset = True + + else: + appData.write(message.InnerBody) + + if message.Version == P2P.Version.V2 and P2P.TFCombination.First == (message.Header.TFCombination & P2P.TFCombination.First): + reset = True + + return self.App.ProcessData(bridge, appData, reset) + + log.error("Nothing to do for message = %r", message) + return False + + def Send(self, msg, callback = None, **kw): + self.ResetTimeoutTimer() + + if msg is None: + import traceback; traceback.print_stack() + + self.sessionMessages.append((msg, callback, kw)) + util.call_later(0, self.event, 'Messages', self) + + Send = callbacks.callsback(Send, ('success', 'error', 'after_send', 'progress')) + + def GetNextMessage(self, maxDataSize): + messageSource = self.sessionMessages or self.dataMessages + if not messageSource: + return None, None, {} + else: + currentMessage, callback, kw = messageSource[0] + if currentMessage.IsFinished(): + messageSource.pop(0) + return self.GetNextMessage(maxDataSize) + + nextMessage, more_callbacks = currentMessage.GetNextMessage(maxDataSize) + for key in more_callbacks: + callback_part = getattr(callback, key, None) + if callback_part is not None: + callback_part += more_callbacks[key] + + callback.progress() + + return nextMessage, callback, kw + + def AttemptBridgeSend(self): + self.MigrateToOptimalBridge() + + message, callback, kw = self.GetNextMessage(self.Bridge.MaxDataSize) + if message is None: + return + + self.Bridge.SendOnePacket(self, self.Remote, self.RemoteContactEPID, message, callback = callback) + #log.debug("Session.Bridge.Send(message = %r)", message) + #self.Bridge.Send(self, self.Remote, self.RemoteContactEPID, message, callback = callback) + + def WrapSLPMessage(self, slp): + message = P2PMessage.P2PMessage(self.Version) + message.InnerMessage = slp + + if message.Version == P2P.Version.V2: + message.Header.TFCombination = P2P.TFCombination.First + message.Header.PackageNumber = self.Bridge.IncrementPackageNumber() + else: + message.Header.Flags = P2P.Flags.MSNSLPInfo + + return message + + def RemoteDirectBridgeEstablished(self): + self.MigrateToOptimalBridge() + + def MigrateToOptimalBridge(self): + if self.Remote.DirectBridge is not None and self.Remote.DirectBridge.IsOpen: + self.Migrate(self.Remote.DirectBridge) + + else: + self.Migrate(self.protocol.P2PHandler.GetBridge(self)) + + def Migrate(self, bridge): + if bridge is self.Bridge: + return + + if self.Bridge is not None: + self.Bridge.unbind('BridgeOpened', self.BridgeOpened) + self.Bridge.unbind('BridgeSynced', self.BridgeSynced) + self.Bridge.unbind('BridgeClosed', self.BridgeClosed) + self.Bridge.unbind('BridgeSent', self.BridgeSent) + + self.Bridge.StopSending(self) + self.Bridge.MigrateQueue(self, bridge) + + self.Bridge = bridge + + if self.Bridge is not None: + self.Bridge.bind('BridgeOpened', self.BridgeOpened) + self.Bridge.bind('BridgeSynced', self.BridgeSynced) + self.Bridge.bind('BridgeClosed', self.BridgeClosed) + self.Bridge.bind('BridgeSent', self.BridgeSent) + self.LocalIdentifier = self.LocalBaseIdentifier = bridge.localTrackerId + + if self.directNegotiationTimer is not None and self.Bridge is not self.protocol.ns.SDGBridge: + self.DirectNegotiationSuccessful() + + def BridgeOpened(self): + log.info("Bridge opened: %r", self.Bridge) + if self.Bridge is self.Remote.DirectBridge: + self.DirectNegotiationSuccessful() + + if self.Bridge.Ready(self) and self.App is not None: + self.App.BridgeIsReady() + + def BridgeSynced(self): + self.LocalIdentifier = self.LocalBaseIdentifier = self.Bridge.SyncId + + if self.Bridge.Ready(self) and self.App is not None: + self.App.BridgeIsReady() + + def BridgeClosed(self): + buddy = self.protocol.get_buddy(self.Remote.account) + if buddy.status == 'offline': + self.OnClosed(self.Remote) + else: + self.MigrateToOptimalBridge() + + def BridgeSent(self, session, message): + if self.Bridge is None: + self.MigrateToOptimalBridge() + + if self.Bridge.Ready(self) and self.App is not None: + self.App.BridgeIsReady() + + def NextLocalIdentifier(self, correction): + if self.Version == P2P.Version.V1: + self.IncreaseLocalIdentifier() + return self.LocalIdentifier + else: + self.LocalIdentifier += correction + return (self.LocalIdentifier - correction) + + def CorrectLocalIdentifier(self, correction): + self.LocalIdentifier += correction + + def IncreaseLocalIdentifier(self): + self.LocalIdentifier += 1 + if self.LocalIdentifier == self.LocalBaseIdentifier: + self.LocalIdentifier += 1 + + def IncreaseRemoteIdentifier(self): + self.RemoteIdentifier += 1 + if self.RemoteIdentifier == self.RemoteBaseIdentifier: + self.RemoteIdentifier += 1 + +def ProcessDirectInvite(slp, protocol, session): + protocol = getattr(protocol, 'ns', protocol) + + if common.pref('msn.p2p.allow_direct', type = bool, default = True): + + try: + if slp.ContentType == 'application/x-msnmsgr-transreqbody': + return ProcessDCReqInvite(slp, protocol, session) + elif slp.ContentType == 'application/x-msnmsgr-transrespbody': + return ProcessDCRespInvite(slp, protocol, session) + elif slp.ContentType == 'application/x-msnmsgr-transdestaddrupdate': + return ProcessDirectAddrUpdate(slp, protocol, session) + except Exception: + import traceback; traceback.print_exc() + + if session is None: + session = P2PSession(slp = slp, msg = None, ns = protocol) + log.info("Sending 603 for Direct Invite") + session.Decline() + +def ProcessDCReqInvite(message, ns, session): + if session is not None and session.Bridge is not None and session.Bridge.BridgeType == 'TCPv1': + return + + if 'TCPv1' not in message.BodyValues.get('Bridges', message.BodyValues.get('Bridge', '')): + if session is not None: + session.DirectNegotiationFailed() + return + + remoteGuid = message.FromEndPoint + remote = ns.contact_list.GetContact(message.FromEmailAccount, MSNAB.IMAddressInfoType.WindowsLive) + + dcNonceType, remoteNonce = ParseDCNonce(message.BodyValues) + if remoteNonce == uuid.UUID(int = 0): + remoteNonce = remote.dcPlainKey + + hashed = dcNonceType == DCNonceType.Sha1 + nonceFieldName = 'Hashed-Nonce' if hashed else 'Nonce' + myHashedNonce = remote.dcLocalHashedNonce if hashed else remoteNonce + myPlainNonce = remote.dcPlainKey + + if dcNonceType == DCNonceType.Sha1: + remote.dcType = dcNonceType + remote.dcRemoteHashedNonce = remoteNonce + + else: + remote.dcType = DCNonceType.Plain + myPlainNonce = remote.dcPlainKey = remote.dcLocalHashedNonce = remote.dcRemoteHashedNonce = remoteNonce + + ipAddress = util.ip_from_bytes(util.myip()) + port = 0 + + if (message.FromEndPoint != uuid.UUID(int=0) and message.ToEndPoint != uuid.UUID(int=0)): + ver = P2P.Version.V2 + else: + ver = P2P.Version.V1 + + try: + remote.DirectBridge = ListenForDirectConnection(remote, remoteGuid, ns, ver, session, ipAddress, port, myPlainNonce, remoteNonce, hashed) + except Exception as e: + import traceback; traceback.print_exc() + log.error("Error setting up direct bridge: %r", e) + port = 0 + else: + port = remote.DirectBridge.LocalEndPoint[1] + + slp = MSNSLP.SLPStatusMessage(message.Source, 200, 'OK') + slp.Target = message.Source + slp.Source = message.Target + slp.Branch = message.Branch + slp.CSeq = 1 + slp.CallId = message.CallId + slp.MaxForwards = 0 + slp.ContentType = 'application/x-msnmsgr-transrespbody' + slp.BodyValues['Bridge'] = 'TCPv1' + + log.info("port = %r, ipaddress = %r, protocol.ip == %r, other_listening = %r", port, ipAddress, ns.protocol.ip, message.BodyValues.get("Listening", None)) + if port == 0 and message.BodyValues.get("Listening", None) != "false": + slp.BodyValues['Listening'] = 'false' + slp.BodyValues[nonceFieldName] = '{%s}' % str(uuid.UUID(int = 0)) + + else: + slp.BodyValues['Listening'] = 'true' + slp.BodyValues['Capabilities-Flags'] = '1' + slp.BodyValues['IPv6-global'] = '' + slp.BodyValues['Nat-Trav-Msg-Type'] = 'WLX-Nat-Trav-Msg-Direct-Connect-Resp' + slp.BodyValues['UPnPNat'] = 'false' + + slp.BodyValues['NeedConnectingEndpointInfo'] = 'true' + slp.BodyValues['Conn-Type'] = 'Direct-Connect' + slp.BodyValues['TCP-Conn-Type'] = 'Direct-Connect' + + slp.BodyValues[nonceFieldName] = '{%s}' % str(myHashedNonce).upper() + slp.BodyValues['IPv4Internal-Addrs'] = ipAddress + slp.BodyValues['IPv4Internal-Port'] = str(port) + + if ipAddress != ns.protocol.ip: + slp.BodyValues['IPv4External-Addrs'] = ns.protocol.ip + slp.BodyValues['IPv4External-Port'] = str(port) + + p2pmessage = P2PMessage.P2PMessage(ver) + p2pmessage.InnerMessage = slp + + if ver == P2P.Version.V2: + p2pmessage.Header.TFCombination = P2P.TFCombination.First + else: + p2pmessage.Header.Flags = P2P.Flags.MSNSLPInfo + + if session is not None: + session.ResetDCTimer() + session.Bridge.Send(None, session.Remote, session.RemoteContactID, p2pmessage) + session.Bridge.StopSending(session) + else: + ns.SDGBridge.Send(None, remote, remoteGuid, p2pmessage) + +def ProcessDCRespInvite(message, ns, session): + body = message.BodyValues + if body.get('Bridge', None) == 'TCPv1' and body.get('Listening', 'false').lower() == 'true': + remote = ns.contact_list.GetContact(message.FromEmailAccount, MSNAB.IMAddressInfoType.WindowsLive) + remoteGuid = message.FromEndPoint + + dcNonceType, remoteNonce = ParseDCNonce(body) + hashed = dcNonceType == DCNonceType.Sha1 + replyGuid = remote.dcPlainKey if hashed else remoteNonce + + selectedPoints = SelectIPEndPoints(body, ns) + log.info("SelectedPoints = %r", selectedPoints) + + if selectedPoints is None or len(selectedPoints) == 0: + if session is not None: + session.DirectNegotiationFailed() + return + + if uuid.UUID(int = 0) in (message.FromEndPoint, message.ToEndPoint): + version1 = True + ver = P2P.Version.V1 + else: + version2 = True + ver = P2P.Version.V2 + + remote.DirectBridge = CreateDirectConnection(remote, remoteGuid, ver, selectedPoints, replyGuid, remoteNonce, hashed, ns, session) + + needConnectingEndpointInfo = body.get('NeedConnectingEndpointInfo', 'false') == 'true' + + if needConnectingEndpointInfo: + ipep = remote.DirectBridge.LocalEndPoint # Host, port + if ipep is not None: + port = ipep[-1] + else: + log.info("Not sending my address info to other client") + return + + ips = map(util.ip_from_bytes, util.myips()) +# if ns.protocol.ip not in ips: +# ips.append(ns.protocol.ip) + + hkey = 'IPv4InternalAddrsAndPorts'[::-1] + hval = ' '.join(('%s:%s' % (ip, port) for ip in ips))[::-1] + + slp = MSNSLP.SLPRequestMessage(message.Source, MSNSLP.RequestMethod.ACK) + slp.Source = message.Target + slp.Via = message.Via + slp.CSeq = 0 + slp.CallId = str(uuid.UUID(int=0)) + slp.MaxForards = 9 + slp.ContentType = 'application/x-msnmsgr-transdestaddrupdate' + slp.BodyValues[hkey] = hval + slp.BodyValues['Nat-Trav-Msg-Type'] = "WLX-Nat-Trav-Msg-Updated-Connecting-Port" + + msg = P2PMessage.P2PMessage(ver) + msg.InnerMessage = slp + Bridge = ns.SDGBridge + Bridge.Send(None, remote, remoteGuid, msg) + else: + log.info("Sending transrespbody through transreqbody handler...") + return ProcessDCReqInvite(message, ns, session) + +def ProcessDirectAddrUpdate(msg, ns, session): + import msn.AddressBook as MSNAB + sender = ns.contact_list.GetContact(msg.FromEmailAccount, MSNAB.IMAddressInfoType.WindowsLive) + ipeps = SelectIPEndPoints(msg.BodyValues, ns) + if sender.DirectBridge is not None and ipeps: + sender.DirectBridge.OnDestinationAddressUpdated(ipeps) + +def ParseDCNonce(bodyValues): + dcNonceType = DCNonceType.NONE + nonce = uuid.UUID(int = 0) + + if 'Hashed-Nonce' in bodyValues: + hnonce = bodyValues.get('Hashed-Nonce') + #log.info("Got hashed-nonce from message: %r", hnonce) + nonce = uuid.UUID(hnonce) + dcNonceType = DCNonceType.Sha1 + + elif 'Nonce' in bodyValues: + _nonce = bodyValues.get('Nonce') + #log.info("Got nonce from message: %r", _nonce) + nonce = uuid.UUID(_nonce) + dcNonceType = DCNonceType.Plain + else: + log.warning("Didn't get any nonce from message") + + return dcNonceType, nonce + +def ListenForDirectConnection(remote, remoteGuid, ns, ver, session, host, port, replyGuid, remoteNonce, hashed): + log.info("ListenForDirectConnection(remote = %r, ns = %r, ver = %r, session = %r, host = %r, port = %r, replyGuid = %r, remoteNonce = %r, hashed = %r)", + remote, ns, ver, session, host, port, replyGuid, remoteNonce, hashed) + + import msn.P2P.TCPBridge as TCPBridge + tcpBridge = TCPBridge.TCPBridge(ver, replyGuid, remoteNonce, hashed, session, ns, remote, remoteGuid) + tcpBridge.Listen((host, port)) + return tcpBridge + +def CreateDirectConnection(remote, remoteGuid, ver, endpoints, replyGuid, remoteNonce, hashed, ns, session): + log.info("CreateDirectConnection(remote = %r, ver = %r, endpoints = %r, replyGuid = %r, remoteNonce = %r, hashed = %r, ns = %r, session = %r)", + remote, ver, endpoints, replyGuid, remoteNonce, hashed, ns, session) + + import msn.P2P.TCPBridge as TCPBridge + tcpBridge = TCPBridge.TCPBridge(ver, replyGuid, remoteNonce, hashed, session, ns, remote, remoteGuid) + tcpBridge.Connect(endpoints) + return tcpBridge + +def SelectIPEndPoints(body, ns): + external = _SelectSomeIPEndPoints(body, ns, 'External') + internal = _SelectSomeIPEndPoints(body, ns, 'Internal') + + return internal + external + +def _SelectSomeIPEndPoints(body, ns, whence): + endpoints = [] + Addrs = 'IPv4%s-Addrs' % whence + Ports = 'IPv4%s-Port' % whence + AddrsAndPorts = 'IPv4%sAddrsAndPorts' % whence + + addrs = body.get(Addrs, '').split() + ports = [int(body.get(Ports, '0'))] + + #log.info("Forward addrs, ports = (%r, %r)", addrs, ports) + + def JoinAddrPortList(addrs, ports): + result = [] + for i, addr in enumerate(addrs): + if i < len(ports): + port = ports[i] + if port > 0: + result.append((addr, port)) + return result + + endpoints.extend(JoinAddrPortList(addrs, ports)) + + addrsAndPorts = filter(None, body.get(AddrsAndPorts, '').split(' ')) + #log.info("Forward AddrsAndPorts = %r", addrsAndPorts) + for addrport in addrsAndPorts: + addr, port = addrpoort.split(':') + port = int(port) + endpoints.append((addr, port)) + + # weird reversed things + addrs = map(lambda x: x[::-1], body.get(Addrs[::-1], '').split()) + ports = map(lambda x: int(x[::-1]), body.get(Ports[::-1], '').split()) + + #log.info("Reversed addrs, ports = (%r, %r)", addrs, ports) + endpoints.extend(JoinAddrPortList(addrs, ports)) + + addrsAndPorts = map(lambda x: x[::-1], filter(None, body.get(AddrsAndPorts[::-1], '').split())) + #log.info("Reverse AddrsAndPorts = %r", addrsAndPorts) + for addrport in addrsAndPorts: + addr, port = addrport.split(':') + port = int(port) + endpoints.append((addr, port)) + + log.info("Endpoints = %r", endpoints) + return endpoints + diff --git a/digsby/src/msn/P2P/SDGBridge.py b/digsby/src/msn/P2P/SDGBridge.py new file mode 100644 index 0000000..dadfe3e --- /dev/null +++ b/digsby/src/msn/P2P/SDGBridge.py @@ -0,0 +1,91 @@ +import logging +log = logging.getLogger('msn.p2p.sdg') +import util.callbacks as callbacks +import msn.P2P.P2PBridge as Bridge +import msn.MSNCommands as MSNC + +class SDGBridge(Bridge.P2PBridge): + BridgeType = 'SDGBridge' + nsHandler = None + + def _get_IsOpen(self): + return self.nsHandler is not None and self.nsHandler.CONNECTED + + def _get_MaxDataSize(self): + return 11748 + + def _get_Synced(self): + return False + + def SuitableFor(self, session): + return True + + def _get_Remote(self): + raise Exception() + + def Dispose(self): + super(SDGBridge, self).Dispose() + self.client = self.nsHandler = None + + def __init__(self, client): + self.client = self.nsHandler = client + super(SDGBridge, self).__init__(queueSize = 8) + + def SendOnePacket(self, session, remote, remoteGuid, message, callback = None): + message = self.SetSequenceNumberAndRegisterAck(session, remote, message, callback) + + to = "%d:%s;epid={%s}" % (int(remote.type), remote.account, remoteGuid) + owner = self.nsHandler.contact_list.owner + from_ = '%d:%s;epid={%s}' % (int(owner.type), owner.account, self.nsHandler.get_machine_guid()) + + if message.Header.Identifier == 0: + log.error("Sending message with no identifier: %r", message) + + slp = message.InnerMessage if message.IsSLPData else None + if slp is not None and slp.ContentType in ("application/x-msnmsgr-transreqbody", + "application/x-msnmsgr-transrespbody", + "application/x-msnmsgr-transdestaddrupdate"): + content = (('Message-Type', 'Signal/P2P'), + ('Content-Type', 'text/plain; charset=UTF-8'), + ) + body = slp.GetBytes(False) + else: + content = ( + ('Content-Type', 'application/x-msnmsgrp2p'), + ('Content-Transfer-Encoding', 'binary'), + ('Message-Type', 'Data'), + ('Pipe', str(self.packageNumber)), + ('Bridging-Offsets', '0'), + ) + + body = message.GetBytes(True) + + mmMessage = MSNC.MultiPartMime(( + (('Routing', '1.0'), + ('To', to), + ('From', from_), + ('Service-Channel', 'PE'), + ('Options', '0'), + ), + + ( + ('Reliability', '1.0'), + ), + + (('Messaging', '2.0'),) + + content, + ), + + body = body, + ) + + def success(*a, **k): + self.FireSendCompleted(message, session) + + callback.success += success + self.nsHandler.socket.send(MSNC.SDG(payload = str(mmMessage)), trid = True, callback = callback) + + SendOnePacket = callbacks.callsback(SendOnePacket, ('success', 'error', 'after_send', 'progress')) + + def FireSendCompleted(self, message, session): + self.OnBridgeSent(session, message) diff --git a/digsby/src/msn/P2P/TCPBridge.py b/digsby/src/msn/P2P/TCPBridge.py new file mode 100644 index 0000000..ded0630 --- /dev/null +++ b/digsby/src/msn/P2P/TCPBridge.py @@ -0,0 +1,144 @@ +import logging; log = logging.getLogger("msn.p2p.tcpbridge") +import util +import util.callbacks as callbacks +import util.Events as Events +import uuid +import msn.P2P as P2P +import msn.P2P.P2PBridge as Bridge +import common + +import msn.P2P.P2PDirectProcessor as DirectProcessor + +class TCPBridge(Bridge.P2PBridge): + events = Bridge.P2PBridge.events | set(( + 'DestinationAddressUpdated', + )) + + BridgeType = 'TCPv1' + + ns = None + session = None + dc = None + remote = None + remoteEpid = uuid.UUID(int = 0) + + def _get_IsOpen(self): + return self.dc is not None and self.dc.DCState == DirectProcessor.DirectConnectionState.Established + + def _get_MaxDataSize(self): + return 11748 + + def _get_Remote(self): + if self.remote is not None: + return self.remote + + if self.session is not None: + return self.session.Remote + + for session in self.SendQueues.keys(): + return session.Remote + + return None + + def _get_RemoteEpid(self): + return self.remoteEpid + + RemoteEpid = property(_get_RemoteEpid) + + def SuitableFor(self, session): + return super(TCPBridge, self).SuitableFor(session) and session.RemoteContactEndPointID == self.RemoteEpid + + @property + def RemoteEndPoint(self): + if self.dc is None: + return '', 0 + return self.dc.RemoteEndPoint + + @property + def LocalEndPoint(self): + if self.dc is None: + return '', 0 + return self.dc.LocalEndPoint + + def __init__(self, ver, replyGuid, remoteNonce, hashed, session, ns, remote, remoteGuid): + super(TCPBridge, self).__init__(queueSize = 0) + + self.session = session + self.ns = ns + self.remote = remote + self.remoteEpid = remoteGuid + + self.dc = DirectProcessor.P2PDirectProcessor(ver, replyGuid, remoteNonce, hashed, session, ns) + self.dc.bind_event('HandshakeCompleted', self.dc_HandshakeCompleted) + self.dc.bind_event('P2PMessageReceived', self.dc_P2PMessageReceived) + self.dc.bind_event('SendCompleted', self.dc_SendCompleted) + + self.dc.bind_event('DirectNegotiationTimedOut', self.dc_DirectNegotiationTimedOut) + self.dc.bind_event('ConnectionClosed', self.dc_ConnectionClosed) + self.dc.bind_event('ConnectingException', self.dc_ConnectingException) + self.dc.bind_event('ConnectionException', self.dc_ConnectionException) + + def OnDestinationAddressUpdated(self, endpoints): + if self.dc is not None and self.dc.Connected and self.dc.RemoteEndPoint != ('', 0): + remoteEP = self.dc.RemoteEndPoint + trustedPeer = False + for endpoint in endpoints: + if endpoint == remoteEP: + trustedPeer = True + break + + if not trustedPeer: + log.info("Shutting down because unknown peer") + self.Shutdown() + + self.DestinationAddressUpdated(endpoints) + + def Shutdown(self): + if self.dc is not None: + self.dc, dc = None, self.dc + dc.Disconnect() + self.OnBridgeClosed() + if self is self.Remote.DirectBridge: + self.Remote.DirectBridge = None + + def dc_HandshakeCompleted(self): + self.OnBridgeOpened() + self.Remote.DirectBridgeEstablished() + + def dc_DirectNegotiationTimedOut(self): + self.Shutdown() + + def dc_ConnectingException(self): + self.Shutdown() + + def dc_ConnectionException(self): + self.Shutdown() + + def dc_ConnectionClosed(self): + self.Shutdown() + + def Listen(self, where): + host, port = where + self.dc.Listen(host, port) + + def Connect(self, endpoints): + self.dc.Connect(endpoints) + + def dc_P2PMessageReceived(self, message): + if self.ns.P2PHandler is not None: + self.ns.P2PHandler.ProcessP2PMessage(self, self.Remote, self.RemoteEpid, message) + else: + self.Shutdown() + + def SendOnePacket(self, session, remote, remoteGuid, msg, callback = None): + message = self.SetSequenceNumberAndRegisterAck(session, remote, msg, callback) + + if msg.Header.Identifier == 0: + log.error("Sending message with no identifier: %r", msg) + + self.dc.SendMessage(session, msg, callback) + SendOnePacket = callbacks.callsback(SendOnePacket, ('success', 'error', 'after_send', 'progress')) + + def dc_SendCompleted(self, session, msg): + util.call_later(0, self.OnBridgeSent, session, msg) + diff --git a/digsby/src/msn/P2P/__init__.py b/digsby/src/msn/P2P/__init__.py new file mode 100644 index 0000000..acae036 --- /dev/null +++ b/digsby/src/msn/P2P/__init__.py @@ -0,0 +1,36 @@ + +class TFCombination: + NONE = 0 + First = 1 + Unknown2 = 2 + MsnObject = 4 + FileTransfer = 6 + +TFCombo = TFCombination + +class OperationCode: + NONE = 0 + SYN = 1 + RAK = 2 + +OpCode = OperationCode + +class Version: + V1 = 1 + V2 = 2 + +class Flags: + Normal = 0 + Nak = 1 + Ack = 2 + RAK = 4 + Error = 8 + File = 0x10 + Data = 0x20 + CloseSession = 0x40 + TlpError = 0x80 + DirectHandshake = 0x100 + MSNSLPInfo = 0x01000000 + FileData = MSNSLPInfo | Data | File + MSNObjectData = MSNSLPInfo | Data +Flag = Flags diff --git a/digsby/src/msn/SOAP/MSNABSharingService/SharingService_client.py b/digsby/src/msn/SOAP/MSNABSharingService/SharingService_client.py new file mode 100644 index 0000000..4b4e5fa --- /dev/null +++ b/digsby/src/msn/SOAP/MSNABSharingService/SharingService_client.py @@ -0,0 +1,422 @@ +################################################## +# file: SharingService_client.py +# +# client stubs generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +from SharingService_types import * +import urlparse, types +from ZSI.TCcompound import ComplexType, Struct +from ZSI import client +from ZSI.schema import GED, GTD +import ZSI + +import util.callbacks as callbacks +import util.network.soap as soap + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS, MSNBindingSOAP + +# Locator +class SharingServiceLocator: + FindMembershipPort_address = "https://contacts.msn.com/abservice/SharingService.asmx" + def getFindMembershipPortAddress(self): + return SharingServiceLocator.FindMembershipPort_address + def getFindMembershipPort(self, url=None, **kw): + return SharingServiceBindingSOAP(url or SharingServiceLocator.FindMembershipPort_address, **kw) + AddMemberPort_address = "https://contacts.msn.com/abservice/SharingService.asmx" + def getAddMemberPortAddress(self): + return SharingServiceLocator.AddMemberPort_address + def getAddMemberPort(self, url=None, **kw): + return SharingServiceBindingSOAP(url or SharingServiceLocator.AddMemberPort_address, **kw) + DeleteMemberPort_address = "https://contacts.msn.com/abservice/SharingService.asmx" + def getDeleteMemberPortAddress(self): + return SharingServiceLocator.DeleteMemberPort_address + def getDeleteMemberPort(self, url=None, **kw): + return SharingServiceBindingSOAP(url or SharingServiceLocator.DeleteMemberPort_address, **kw) + CreateCirclePort_address = "https://contacts.msn.com/abservice/SharingService.asmx" + def getCreateCirclePortAddress(self): + return SharingServiceLocator.CreateCirclePort_address + def getCreateCirclePort(self, url=None, **kw): + return SharingServiceBindingSOAP(url or SharingServiceLocator.CreateCirclePort_address, **kw) + +# Methods +class SharingServiceBindingSOAP(MSNBindingSOAP): + + # op: FindMembership + @callbacks.callsback + def FindMembership(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, FindMembershipMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/FindMembership", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: AddMember + @callbacks.callsback + def AddMember(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, AddMemberMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/AddMember", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: DeleteMember + @callbacks.callsback + def DeleteMember(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, DeleteMemberMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/DeleteMember", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: CreateCircle + @callbacks.callsback + def CreateCircle(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, CreateCircleMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/CreateCircle", soapheaders=soapheaders, + callback = callback, + **kw) + +FindMembershipMessage = GED(MSNS.MSWS.ADDRESS, "FindMembership").pyclass +FindMembershipResponseMessage = GED(MSNS.MSWS.ADDRESS, "FindMembershipResponse").pyclass +AddMemberMessage = GED(MSNS.MSWS.ADDRESS, "AddMember").pyclass +AddMemberResponseMessage = GED(MSNS.MSWS.ADDRESS, "AddMemberResponse").pyclass +DeleteMemberMessage = GED(MSNS.MSWS.ADDRESS, "DeleteMember").pyclass +DeleteMemberResponseMessage = GED(MSNS.MSWS.ADDRESS, "DeleteMemberResponse").pyclass +CreateCircleMessage = GED(MSNS.MSWS.ADDRESS, "CreateCircle").pyclass +CreateCircleResponseMessage = GED(MSNS.MSWS.ADDRESS, "CreateCircleResponse").pyclass + +# Locator +class ABServiceLocator: + ABFindAllPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABFindAllPortAddress(self): + return ABServiceLocator.ABFindAllPort_address + def getABFindAllPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABFindAllPort_address, **kw) + ABContactAddPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABContactAddPortAddress(self): + return ABServiceLocator.ABContactAddPort_address + def getABContactAddPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABContactAddPort_address, **kw) + ABContactDeletePort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABContactDeletePortAddress(self): + return ABServiceLocator.ABContactDeletePort_address + def getABContactDeletePort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABContactDeletePort_address, **kw) + ABGroupContactAddPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABGroupContactAddPortAddress(self): + return ABServiceLocator.ABGroupContactAddPort_address + def getABGroupContactAddPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABGroupContactAddPort_address, **kw) + ABGroupAddPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABGroupAddPortAddress(self): + return ABServiceLocator.ABGroupAddPort_address + def getABGroupAddPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABGroupAddPort_address, **kw) + ABGroupUpdatePort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABGroupUpdatePortAddress(self): + return ABServiceLocator.ABGroupUpdatePort_address + def getABGroupUpdatePort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABGroupUpdatePort_address, **kw) + ABGroupDeletePort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABGroupDeletePortAddress(self): + return ABServiceLocator.ABGroupDeletePort_address + def getABGroupDeletePort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABGroupDeletePort_address, **kw) + ABGroupContactDeletePort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABGroupContactDeletePortAddress(self): + return ABServiceLocator.ABGroupContactDeletePort_address + def getABGroupContactDeletePort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABGroupContactDeletePort_address, **kw) + ABContactUpdatePort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABContactUpdatePortAddress(self): + return ABServiceLocator.ABContactUpdatePort_address + def getABContactUpdatePort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABContactUpdatePort_address, **kw) + ABAddPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABAddPortAddress(self): + return ABServiceLocator.ABAddPort_address + def getABAddPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABAddPort_address, **kw) + UpdateDynamicItemPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getUpdateDynamicItemPortAddress(self): + return ABServiceLocator.UpdateDynamicItemPort_address + def getUpdateDynamicItemPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.UpdateDynamicItemPort_address, **kw) + ABFindContactsPagedPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getABFindContactsPagedPortAddress(self): + return ABServiceLocator.ABFindContactsPagedPort_address + def getABFindContactsPagedPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ABFindContactsPagedPort_address, **kw) + CreateContactPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getCreateContactPortAddress(self): + return ABServiceLocator.CreateContactPort_address + def getCreateContactPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.CreateContactPort_address, **kw) + ManageWLConnectionPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getManageWLConnectionPortAddress(self): + return ABServiceLocator.ManageWLConnectionPort_address + def getManageWLConnectionPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.ManageWLConnectionPort_address, **kw) + BreakConnectionPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getBreakConnectionPortAddress(self): + return ABServiceLocator.BreakConnectionPort_address + def getBreakConnectionPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.BreakConnectionPort_address, **kw) + AddDynamicItemPort_address = "https://contacts.msn.com/abservice/abservice.asmx" + def getAddDynamicItemPortAddress(self): + return ABServiceLocator.AddDynamicItemPort_address + def getAddDynamicItemPort(self, url=None, **kw): + return ABServiceBindingSOAP(url or ABServiceLocator.AddDynamicItemPort_address, **kw) + +# Methods +class ABServiceBindingSOAP(MSNBindingSOAP): + + # op: ABFindAll + @callbacks.callsback + def ABFindAll(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABFindAllMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABFindAll", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABContactAdd + @callbacks.callsback + def ABContactAdd(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABContactAddMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABContactAdd", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABContactDelete + @callbacks.callsback + def ABContactDelete(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABContactDeleteMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABContactDelete", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABGroupContactAdd + @callbacks.callsback + def ABGroupContactAdd(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABGroupContactAddMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABGroupContactAdd", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABGroupAdd + @callbacks.callsback + def ABGroupAdd(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABGroupAddMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABGroupAdd", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABGroupUpdate + @callbacks.callsback + def ABGroupUpdate(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABGroupUpdateMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABGroupUpdate", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABGroupDelete + @callbacks.callsback + def ABGroupDelete(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABGroupDeleteMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABGroupDelete", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABGroupContactDelete + @callbacks.callsback + def ABGroupContactDelete(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABGroupContactDeleteMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABGroupContactDelete", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABContactUpdate + @callbacks.callsback + def ABContactUpdate(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABContactUpdateMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABContactUpdate", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABAdd + @callbacks.callsback + def ABAdd(self, request, soapheaders=(), **kw): + if isinstance(request, ABAddMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABAdd", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: UpdateDynamicItem + @callbacks.callsback + def UpdateDynamicItem(self, request, soapheaders=(), **kw): + if isinstance(request, UpdateDynamicItemMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/UpdateDynamicItem", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ABFindContactsPaged + @callbacks.callsback + def ABFindContactsPaged(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ABFindContactsPagedMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ABFindContactsPaged", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: CreateContact + @callbacks.callsback + def CreateContact(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, CreateContactMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/CreateContact", soapheaders=soapheaders, + callback = callback, + **kw) + + + # op: ManageWLConnection + @callbacks.callsback + def ManageWLConnection(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ManageWLConnectionMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/ManageWLConnection", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: BreakConnection + @callbacks.callsback + def BreakConnection(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, BreakConnectionMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/BreakConnection", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: AddDynamicItem + @callbacks.callsback + def AddDynamicItem(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, AddDynamicItemMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/AddDynamicItem", soapheaders=soapheaders, + callback = callback, + **kw) + +ABFindAllMessage = GED(MSNS.MSWS.ADDRESS, "ABFindAll").pyclass +ABFindAllResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABFindAllResponse").pyclass +ABContactAddMessage = GED(MSNS.MSWS.ADDRESS, "ABContactAdd").pyclass +ABContactAddResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABContactAddResponse").pyclass +ABContactDeleteMessage = GED(MSNS.MSWS.ADDRESS, "ABContactDelete").pyclass +ABContactDeleteResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABContactDeleteResponse").pyclass +ABGroupContactAddMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupContactAdd").pyclass +ABGroupContactAddResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupContactAddResponse").pyclass +ABGroupAddMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupAdd").pyclass +ABGroupAddResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupAddResponse").pyclass +ABGroupUpdateMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupUpdate").pyclass +ABGroupUpdateResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupUpdateResponse").pyclass +ABGroupDeleteMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupDelete").pyclass +ABGroupDeleteResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupDeleteResponse").pyclass +ABGroupContactDeleteMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupContactDelete").pyclass +ABGroupContactDeleteResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABGroupContactDeleteResponse").pyclass +ABContactUpdateMessage = GED(MSNS.MSWS.ADDRESS, "ABContactUpdate").pyclass +ABContactUpdateResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABContactUpdateResponse").pyclass +ABAddMessage = GED(MSNS.MSWS.ADDRESS, "ABAdd").pyclass +ABAddResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABAddResponse").pyclass +UpdateDynamicItemMessage = GED(MSNS.MSWS.ADDRESS, "UpdateDynamicItem").pyclass +UpdateDynamicItemResponseMessage = GED(MSNS.MSWS.ADDRESS, "UpdateDynamicItemResponse").pyclass +ABFindContactsPagedMessage = GED(MSNS.MSWS.ADDRESS, "ABFindContactsPaged").pyclass +ABFindContactsPagedResponseMessage = GED(MSNS.MSWS.ADDRESS, "ABFindContactsPagedResponse").pyclass +CreateContactMessage = GED(MSNS.MSWS.ADDRESS, "CreateContact").pyclass +CreateContactResponseMessage = GED(MSNS.MSWS.ADDRESS, "CreateContactResponse").pyclass +ManageWLConnectionMessage = GED(MSNS.MSWS.ADDRESS, "ManageWLConnection").pyclass +ManageWLConnectionResponseMessage = GED(MSNS.MSWS.ADDRESS, "ManageWLConnectionResponse").pyclass +BreakConnectionMessage = GED(MSNS.MSWS.ADDRESS, "BreakConnection").pyclass +BreakConnectionResponseMessage = GED(MSNS.MSWS.ADDRESS, "BreakConnectionResponse").pyclass +AddDynamicItemMessage = GED(MSNS.MSWS.ADDRESS, "AddDynamicItem").pyclass +AddDynamicItemResponseMessage = GED(MSNS.MSWS.ADDRESS, "AddDynamicItemResponse").pyclass + +# Locator +class WhatsUpServiceLocator: + GetContactsRecentActivityPort_address = "http://sup.live.com/whatsnew/whatsnewservice.asmx" + def getGetContactsRecentActivityPortAddress(self): + return WhatsUpServiceLocator.GetContactsRecentActivityPort_address + def getGetContactsRecentActivityPort(self, url=None, **kw): + return WhatsUpServiceBindingSOAP(url or WhatsUpServiceLocator.GetContactsRecentActivityPort_address, **kw) + +# Methods +class WhatsUpServiceBindingSOAP(MSNBindingSOAP): + + # op: GetContactsRecentActivity + @callbacks.callsback + def GetContactsRecentActivity(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, GetContactsRecentActivityMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/AddressBook/GetContactsRecentActivity", soapheaders=soapheaders, + callback = callback, + **kw) + # no output wsaction + +GetContactsRecentActivityMessage = GED(MSNS.MSWS.ADDRESS, "GetContactsRecentActivity").pyclass +GetContactsRecentActivityResponseMessage = GED(MSNS.MSWS.ADDRESS, "GetContactsRecentActivityResponse").pyclass + diff --git a/digsby/src/msn/SOAP/MSNABSharingService/SharingService_types.py b/digsby/src/msn/SOAP/MSNABSharingService/SharingService_types.py new file mode 100644 index 0000000..78dfdc2 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNABSharingService/SharingService_types.py @@ -0,0 +1,4510 @@ +################################################## +# file: SharingService_types.py +# +# schema types generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +from msn.SOAP import Namespaces as MSNS + +############################## +# targetNamespace +# http://www.msn.com/webservices/AddressBook +############################## + +class msnab: + targetNamespace = MSNS.MSWS.ADDRESS + + class abInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "abInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.abInfoType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"name"), aname="_name", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ownerPuid"), aname="_ownerPuid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"OwnerCID"), aname="_OwnerCID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ownerEmail"), aname="_ownerEmail", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"fDefault"), aname="_fDefault", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"joinedNamespace"), aname="_joinedNamespace", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsBot"), aname="_IsBot", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsParentManaged"), aname="_IsParentManaged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"SubscribeExternalPartner"), aname="_SubscribeExternalPartner", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"NotifyExternalPartner"), aname="_NotifyExternalPartner", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"AddressBookType"), aname="_AddressBookType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"MessengerApplicationServiceCreated"), aname="_MessengerApplicationServiceCreated", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsBetaMigrated"), aname="_IsBetaMigrated", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"MigratedTo"), aname="_MigratedTo", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._name = None + self._ownerPuid = None + self._OwnerCID = None + self._ownerEmail = None + self._fDefault = None + self._joinedNamespace = None + self._IsBot = None + self._IsParentManaged = None + self._SubscribeExternalPartner = None + self._NotifyExternalPartner = None + self._AddressBookType = None + self._MessengerApplicationServiceCreated = None + self._IsBetaMigrated = None + self._MigratedTo = None + return + Holder.__name__ = "abInfoType_Holder" + self.pyclass = Holder + + class HandleType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "HandleType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.HandleType_Def.schema + TClist = [ZSI.TCnumbers.Iinteger(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Type"), aname="_Type", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ForeignId"), aname="_ForeignId", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Id = None + self._Type = None + self._ForeignId = None + return + Holder.__name__ = "HandleType_Holder" + self.pyclass = Holder + + class ServiceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ServiceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ServiceType_Def.schema + TClist = [self.__class__.Memberships_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"InfoType",lazy=False)(pname=(ns,"Info"), aname="_Info", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Changes"), aname="_Changes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastChange"), aname="_LastChange", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"Deleted"), aname="_Deleted", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Memberships = None + self._Info = None + self._Changes = None + self._LastChange = None + self._Deleted = None + return + Holder.__name__ = "ServiceType_Holder" + self.pyclass = Holder + + + class Memberships_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Memberships" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ServiceType_Def.Memberships_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Membership",lazy=False)(pname=(ns,"Membership"), aname="_Membership", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Memberships") + kw["aname"] = "_Memberships" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Membership = [] + return + Holder.__name__ = "Memberships_Holder" + self.pyclass = Holder + + + + + class Membership_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "Membership") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.Membership_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"MemberRole"), aname="_MemberRole", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.Members_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"MembershipIsComplete"), aname="_MembershipIsComplete", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._MemberRole = None + self._Members = None + self._MembershipIsComplete = None + return + Holder.__name__ = "Membership_Holder" + self.pyclass = Holder + + + class Members_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Members" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.Membership_Def.Members_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"BaseMember",lazy=False)(pname=(ns,"Member"), aname="_Member", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Members") + kw["aname"] = "_Members" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Member = [] + return + Holder.__name__ = "Members_Holder" + self.pyclass = Holder + + + + + class BaseMember_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "BaseMember") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.BaseMember_Def.schema + TClist = [ZSI.TCnumbers.InonNegativeInteger(pname=(ns,"MembershipId"), aname="_MembershipId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Type"), aname="_Type", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.Location_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.ADDRESS,"MemberState",lazy=False)(pname=(ns,"State"), aname="_State", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.Annotations_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"Deleted"), aname="_Deleted", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"LastChanged"), aname="_LastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"JoinedDate"), aname="_JoinedDate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"ExpirationDate"), aname="_ExpirationDate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Changes"), aname="_Changes", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._MembershipId = None + self._Type = None + self._Location = None + self._DisplayName = None + self._State = None + self._Annotations = None + self._Deleted = None + self._LastChanged = None + self._JoinedDate = None + self._ExpirationDate = None + self._Changes = None + return + Holder.__name__ = "BaseMember_Holder" + self.pyclass = Holder + + + class Location_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Location" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.BaseMember_Def.Location_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsPassportNameHidden"), aname="_IsPassportNameHidden", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Ilong(pname=(ns,"CID"), aname="_CID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Location") + kw["aname"] = "_Location" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Id = None + self._IsPassportNameHidden = None + self._CID = None + return + Holder.__name__ = "Location_Holder" + self.pyclass = Holder + + + + + + class Annotations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Annotations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.BaseMember_Def.Annotations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Annotation",lazy=False)(pname=(ns,"Annotation"), aname="_Annotation", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Annotations") + kw["aname"] = "_Annotations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Annotation = [] + return + Holder.__name__ = "Annotations_Holder" + self.pyclass = Holder + + + + + class CircleMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "CircleMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.CircleMember_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"CircleId"), aname="_CircleId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.CircleMember_Def.__bases__: + bases = list(msnab.CircleMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.CircleMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class PassportMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "PassportMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.PassportMember_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"PassportName"), aname="_PassportName", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"IsPassportNameHidden"), aname="_IsPassportNameHidden", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"PassportId"), aname="_PassportId", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Ilong(pname=(ns,"CID"), aname="_CID", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"PassportChanges"), aname="_PassportChanges", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.PassportMember_Def.__bases__: + bases = list(msnab.PassportMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.PassportMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class EmailMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "EmailMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.EmailMember_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Email"), aname="_Email", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.EmailMember_Def.__bases__: + bases = list(msnab.EmailMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.EmailMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class PhoneMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "PhoneMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.PhoneMember_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"PhoneNumber"), aname="_PhoneNumber", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.PhoneMember_Def.__bases__: + bases = list(msnab.PhoneMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.PhoneMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class RoleMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "RoleMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.RoleMember_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.DefiningService_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"MaxRoleRecursionDepth"), aname="_MaxRoleRecursionDepth", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"MaxDegreesSeparation"), aname="_MaxDegreesSeparation", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.RoleMember_Def.__bases__: + bases = list(msnab.RoleMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.RoleMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + + class DefiningService_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "DefiningService" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.RoleMember_Def.DefiningService_Dec.schema + TClist = [ZSI.TCnumbers.Iinteger(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Type"), aname="_Type", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ForeignId"), aname="_ForeignId", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"DefiningService") + kw["aname"] = "_DefiningService" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Id = None + self._Type = None + self._ForeignId = None + return + Holder.__name__ = "DefiningService_Holder" + self.pyclass = Holder + + + + + class ServiceMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "ServiceMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.ServiceMember_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"HandleType",lazy=False)(pname=(ns,"Service"), aname="_Service", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.ServiceMember_Def.__bases__: + bases = list(msnab.ServiceMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.ServiceMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class DomainMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "DomainMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.DomainMember_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"DomainName"), aname="_DomainName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.DomainMember_Def.__bases__: + bases = list(msnab.DomainMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.DomainMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class EveryoneMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "EveryoneMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.EveryoneMember_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.EveryoneMember_Def.__bases__: + bases = list(msnab.EveryoneMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.EveryoneMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class PartnerMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "PartnerMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.PartnerMember_Def.schema + TClist = [ZSI.TCnumbers.Ilong(pname=(ns,"AppId"), aname="_AppId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"Scope"), aname="_Scope", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.PartnerMember_Def.__bases__: + bases = list(msnab.PartnerMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.PartnerMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class GroupMember_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "GroupMember") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.GroupMember_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseMember_Def not in msnab.GroupMember_Def.__bases__: + bases = list(msnab.GroupMember_Def.__bases__) + bases.insert(0, msnab.BaseMember_Def) + msnab.GroupMember_Def.__bases__ = tuple(bases) + + msnab.BaseMember_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class Guid_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "Guid") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class MemberState_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "MemberState") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class Annotation_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "Annotation") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.Annotation_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Name"), aname="_Name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Value"), aname="_Value", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Name = None + self._Value = None + return + Holder.__name__ = "Annotation_Holder" + self.pyclass = Holder + + class ContactType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContactType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ContactType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"contactId"), aname="_contactId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"contactInfoType",lazy=False)(pname=(ns,"contactInfo"), aname="_contactInfo", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"propertiesChanged"), aname="_propertiesChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"fDeleted"), aname="_fDeleted", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"lastChange"), aname="_lastChange", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"CreateDate"), aname="_CreateDate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastModifiedBy"), aname="_LastModifiedBy", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"CreatedBy"), aname="_CreatedBy", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._contactId = None + self._contactInfo = None + self._propertiesChanged = None + self._fDeleted = None + self._lastChange = None + self._CreateDate = None + self._LastModifiedBy = None + self._CreatedBy = None + return + Holder.__name__ = "ContactType_Holder" + self.pyclass = Holder + + class ContactIdType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContactIdType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ContactIdType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"contactId"), aname="_contactId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._contactId = None + return + Holder.__name__ = "ContactIdType_Holder" + self.pyclass = Holder + + class contactInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "contactInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.contactInfoType_Def.schema + TClist = [self.__class__.emails_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.phones_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.locations_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.webSites_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.annotations_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.groupIds_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.groupIdsDeleted_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"contactType"), aname="_contactType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"quickName"), aname="_quickName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"firstName"), aname="_firstName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"MiddleName"), aname="_MiddleName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"lastName"), aname="_lastName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Suffix"), aname="_Suffix", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"NameTitle"), aname="_NameTitle", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"passportName"), aname="_passportName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"IsPassportNameHidden"), aname="_IsPassportNameHidden", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"displayName"), aname="_displayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Ilong(pname=(ns,"puid"), aname="_puid", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Ilong(pname=(ns,"CID"), aname="_CID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.AnyType(pname=(ns,"BrandIdList"), aname="_BrandIdList", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"comment"), aname="_comment", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"IsNotMobileVisible"), aname="_IsNotMobileVisible", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"isMobileIMEnabled"), aname="_isMobileIMEnabled", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"isMessengerUser"), aname="_isMessengerUser", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"isFavorite"), aname="_isFavorite", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"isSmtp"), aname="_isSmtp", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"hasSpace"), aname="_hasSpace", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"spotWatchState"), aname="_spotWatchState", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"birthdate"), aname="_birthdate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.ADDRESS,"ContactEmailTypeType",lazy=False)(pname=(ns,"primaryEmailType"), aname="_primaryEmailType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.ADDRESS,"ContactLocationTypeType",lazy=False)(pname=(ns,"PrimaryLocation"), aname="_PrimaryLocation", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.ADDRESS,"ContactPhoneTypeType",lazy=False)(pname=(ns,"PrimaryPhone"), aname="_PrimaryPhone", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"IsPrivate"), aname="_IsPrivate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Anniversary"), aname="_Anniversary", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Gender"), aname="_Gender", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"TimeZone"), aname="_TimeZone", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.NetworkInfoList_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"PublicDisplayName"), aname="_PublicDisplayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"IsAutoUpdateDisabled"), aname="_IsAutoUpdateDisabled", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.ADDRESS,"MessengerMemberInfo",lazy=False)(pname=(ns,"MessengerMemberInfo"), aname="_MessengerMemberInfo", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.AnyType(pname=(ns,"PropertiesChanged"), aname="_PropertiesChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"clientErrorData"), aname="_clientErrorData", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"IsHidden"), aname="_IsHidden", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._emails = None + self._phones = None + self._locations = None + self._webSites = None + self._annotations = None + self._groupIds = None + self._groupIdsDeleted = None + self._contactType = None + self._quickName = None + self._firstName = None + self._MiddleName = None + self._lastName = None + self._Suffix = None + self._NameTitle = None + self._passportName = None + self._IsPassportNameHidden = None + self._displayName = None + self._puid = None + self._CID = None + self._BrandIdList = None + self._comment = None + self._IsNotMobileVisible = None + self._isMobileIMEnabled = None + self._isMessengerUser = None + self._isFavorite = None + self._isSmtp = None + self._hasSpace = None + self._spotWatchState = None + self._birthdate = None + self._primaryEmailType = None + self._PrimaryLocation = None + self._PrimaryPhone = None + self._IsPrivate = None + self._Anniversary = None + self._Gender = None + self._TimeZone = None + self._NetworkInfoList = None + self._PublicDisplayName = None + self._IsAutoUpdateDisabled = None + self._MessengerMemberInfo = None + self._PropertiesChanged = None + self._clientErrorData = None + self._IsHidden = None + return + Holder.__name__ = "contactInfoType_Holder" + self.pyclass = Holder + + + class emails_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "emails" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.emails_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"contactEmailType",lazy=False)(pname=(ns,"ContactEmail"), aname="_ContactEmail", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"emails") + kw["aname"] = "_emails" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ContactEmail = [] + return + Holder.__name__ = "emails_Holder" + self.pyclass = Holder + + + + + + class phones_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "phones" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.phones_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"contactPhoneType",lazy=False)(pname=(ns,"ContactPhone"), aname="_ContactPhone", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"phones") + kw["aname"] = "_phones" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ContactPhone = [] + return + Holder.__name__ = "phones_Holder" + self.pyclass = Holder + + + + + + class locations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "locations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.locations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"contactLocationType",lazy=False)(pname=(ns,"ContactLocation"), aname="_ContactLocation", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"locations") + kw["aname"] = "_locations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ContactLocation = [] + return + Holder.__name__ = "locations_Holder" + self.pyclass = Holder + + + + + + class webSites_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "webSites" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.webSites_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"contactWebSiteType",lazy=False)(pname=(ns,"ContactWebSite"), aname="_ContactWebSite", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"webSites") + kw["aname"] = "_webSites" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ContactWebSite = [] + return + Holder.__name__ = "webSites_Holder" + self.pyclass = Holder + + + + + + class annotations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "annotations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.annotations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Annotation",lazy=False)(pname=(ns,"Annotation"), aname="_Annotation", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"annotations") + kw["aname"] = "_annotations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Annotation = [] + return + Holder.__name__ = "annotations_Holder" + self.pyclass = Holder + + + + + + class groupIds_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groupIds" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.groupIds_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"guid"), aname="_guid", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groupIds") + kw["aname"] = "_groupIds" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._guid = [] + return + Holder.__name__ = "groupIds_Holder" + self.pyclass = Holder + + + + + + class groupIdsDeleted_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groupIdsDeleted" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.groupIdsDeleted_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"guid"), aname="_guid", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groupIdsDeleted") + kw["aname"] = "_groupIdsDeleted" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._guid = [] + return + Holder.__name__ = "groupIdsDeleted_Holder" + self.pyclass = Holder + + + + + + class NetworkInfoList_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "NetworkInfoList" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.contactInfoType_Def.NetworkInfoList_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"NetworkInfoType",lazy=False)(pname=(ns,"NetworkInfo"), aname="_NetworkInfo", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"NetworkInfoList") + kw["aname"] = "_NetworkInfoList" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._NetworkInfo = [] + return + Holder.__name__ = "NetworkInfoList_Holder" + self.pyclass = Holder + + + + + class contactEmailType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "contactEmailType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.contactEmailType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactEmailTypeType",lazy=False)(pname=(ns,"contactEmailType"), aname="_contactEmailType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"email"), aname="_email", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"isMessengerEnabled"), aname="_isMessengerEnabled", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"Capability"), aname="_Capability", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"MessengerEnabledExternally"), aname="_MessengerEnabledExternally", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"propertiesChanged"), aname="_propertiesChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._contactEmailType = None + self._email = None + self._isMessengerEnabled = None + self._Capability = None + self._MessengerEnabledExternally = None + self._propertiesChanged = None + return + Holder.__name__ = "contactEmailType_Holder" + self.pyclass = Holder + + class ContactEmailTypeType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContactEmailTypeType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class contactPhoneType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "contactPhoneType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.contactPhoneType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactPhoneTypeType",lazy=False)(pname=(ns,"contactPhoneType"), aname="_contactPhoneType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"number"), aname="_number", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"isMessengerEnabled"), aname="_isMessengerEnabled", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"propertiesChanged"), aname="_propertiesChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._contactPhoneType = None + self._number = None + self._isMessengerEnabled = None + self._propertiesChanged = None + return + Holder.__name__ = "contactPhoneType_Holder" + self.pyclass = Holder + + class ContactPhoneTypeType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContactPhoneTypeType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class contactLocationType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "contactLocationType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.contactLocationType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactLocationTypeType",lazy=False) + (pname=(ns,"contactLocationType"), aname="_contactLocationType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"name"), aname="_name", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"street"), aname="_street", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"city"), aname="_city", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"state"), aname="_state", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"country"), aname="_country", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"postalCode"), aname="_postalCode", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Department"), aname="_Department", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Changes"), aname="_Changes", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._contactLocationType = None + self._name = None + self._street = None + self._city = None + self._state = None + self._country = None + self._postalCode = None + self._Department = None + self._Changes = None + return + Holder.__name__ = "contactLocationType_Holder" + self.pyclass = Holder + + class ContactLocationTypeType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContactLocationTypeType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class contactWebSiteType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "contactWebSiteType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.contactWebSiteType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactWebSiteTypeType",lazy=False)(pname=(ns,"contactWebSiteType"), aname="_contactWebSiteType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"webURL"), aname="_webURL", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._contactWebSiteType = None + self._webURL = None + return + Holder.__name__ = "contactWebSiteType_Holder" + self.pyclass = Holder + + class ContactWebSiteTypeType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContactWebSiteTypeType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class GroupType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "GroupType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.GroupType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"groupId"), aname="_groupId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"groupInfoType",lazy=False)(pname=(ns,"groupInfo"), aname="_groupInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"propertiesChanged"), aname="_propertiesChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"fDeleted"), aname="_fDeleted", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"lastChange"), aname="_lastChange", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._groupId = None + self._groupInfo = None + self._propertiesChanged = None + self._fDeleted = None + self._lastChange = None + return + Holder.__name__ = "GroupType_Holder" + self.pyclass = Holder + + class groupInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "groupInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.groupInfoType_Def.schema + TClist = [self.__class__.annotations_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"groupType"), aname="_groupType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"name"), aname="_name", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsNotMobileVisible"), aname="_IsNotMobileVisible", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsPrivate"), aname="_IsPrivate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsFavorite"), aname="_IsFavorite", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"fMessenger"), aname="_fMessenger", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._annotations = None + self._groupType = None + self._name = None + self._IsNotMobileVisible = None + self._IsPrivate = None + self._IsFavorite = None + self._fMessenger = None + return + Holder.__name__ = "groupInfoType_Holder" + self.pyclass = Holder + + + class annotations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "annotations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.groupInfoType_Def.annotations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Annotation",lazy=False)(pname=(ns,"Annotation"), aname="_Annotation", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"annotations") + kw["aname"] = "_annotations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Annotation = [] + return + Holder.__name__ = "annotations_Holder" + self.pyclass = Holder + + + + + class groupFilterType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "groupFilterType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.groupFilterType_Def.schema + TClist = [self.__class__.groupIds_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._groupIds = None + return + Holder.__name__ = "groupFilterType_Holder" + self.pyclass = Holder + + + class groupIds_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groupIds" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.groupFilterType_Def.groupIds_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"guid"), aname="_guid", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groupIds") + kw["aname"] = "_groupIds" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._guid = [] + return + Holder.__name__ = "groupIds_Holder" + self.pyclass = Holder + + + + + class MessengerMemberInfo_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "MessengerMemberInfo") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.MessengerMemberInfo_Def.schema + TClist = [self.__class__.PendingAnnotations_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._PendingAnnotations = None + self._DisplayName = None + return + Holder.__name__ = "MessengerMemberInfo_Holder" + self.pyclass = Holder + + + class PendingAnnotations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "PendingAnnotations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.MessengerMemberInfo_Def.PendingAnnotations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Annotation",lazy=False)(pname=(ns,"Annotation"), aname="_Annotation", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"PendingAnnotations") + kw["aname"] = "_PendingAnnotations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Annotation = [] + return + Holder.__name__ = "PendingAnnotations_Holder" + self.pyclass = Holder + + + + + class InfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "InfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.InfoType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"HandleType",lazy=False)(pname=(ns,"Handle"), aname="_Handle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"InverseRequired"), aname="_InverseRequired", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"AuthorizationCriteria"), aname="_AuthorizationCriteria", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.URI(pname=(ns,"RSSUrl"), aname="_RSSUrl", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsBot"), aname="_IsBot", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Handle = None + self._DisplayName = None + self._InverseRequired = None + self._AuthorizationCriteria = None + self._RSSUrl = None + self._IsBot = None + return + Holder.__name__ = "InfoType_Holder" + self.pyclass = Holder + + class NotificationDataType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "NotificationDataType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.NotificationDataType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ServiceType",lazy=False)(pname=(ns,"StoreService"), aname="_StoreService", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Status"), aname="_Status", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastChanged"), aname="_LastChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"Gleam"), aname="_Gleam", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"InstanceId"), aname="_InstanceId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._StoreService = None + self._Status = None + self._LastChanged = None + self._Gleam = None + self._InstanceId = None + return + Holder.__name__ = "NotificationDataType_Holder" + self.pyclass = Holder + + class BaseDynamicItemType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "BaseDynamicItemType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.BaseDynamicItemType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Type"), aname="_Type", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"Deleted"), aname="_Deleted", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastChanged"), aname="_LastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.Notifications_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Changes"), aname="_Changes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Type = None + self._Deleted = None + self._LastChanged = None + self._Notifications = None + self._Changes = None + return + Holder.__name__ = "BaseDynamicItemType_Holder" + self.pyclass = Holder + + + class Notifications_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Notifications" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.BaseDynamicItemType_Def.Notifications_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"NotificationDataType",lazy=False)(pname=(ns,"NotificationData"), aname="_NotificationData", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Notifications") + kw["aname"] = "_Notifications" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._NotificationData = [] + return + Holder.__name__ = "Notifications_Holder" + self.pyclass = Holder + + + + + class CircleDynamicItem_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "CircleDynamicItem") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.CircleDynamicItem_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseDynamicItemType_Def not in msnab.CircleDynamicItem_Def.__bases__: + bases = list(msnab.CircleDynamicItem_Def.__bases__) + bases.insert(0, msnab.BaseDynamicItemType_Def) + msnab.CircleDynamicItem_Def.__bases__ = tuple(bases) + + msnab.BaseDynamicItemType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class PassportDynamicItem_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "PassportDynamicItem") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.PassportDynamicItem_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"CID"), aname="_CID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"PassportName"), aname="_PassportName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"PassportId"), aname="_PassportId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"SpaceStatus"), aname="_SpaceStatus", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"SpaceLastChanged"), aname="_SpaceLastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"SpaceLastViewed"), aname="_SpaceLastViewed", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"SpaceGleam"), aname="_SpaceGleam", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ProfileLastChanged"), aname="_ProfileLastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ProfileLastView"), aname="_ProfileLastView", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ProfileStatus"), aname="_ProfileStatus", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"ProfileGleam"), aname="_ProfileGleam", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ContactProfileStatus"), aname="_ContactProfileStatus", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ContactProfileLastChanged"), aname="_ContactProfileLastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ContactProfileLastViewed"), aname="_ContactProfileLastViewed", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LiveContactLastChanged"), aname="_LiveContactLastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.BaseDynamicItemType_Def not in msnab.PassportDynamicItem_Def.__bases__: + bases = list(msnab.PassportDynamicItem_Def.__bases__) + bases.insert(0, msnab.BaseDynamicItemType_Def) + msnab.PassportDynamicItem_Def.__bases__ = tuple(bases) + + msnab.BaseDynamicItemType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class abType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "abType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.abType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"abInfoType",lazy=False)(pname=(ns,"abInfo"), aname="_abInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"lastChange"), aname="_lastChange", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DynamicItemLastChanged"), aname="_DynamicItemLastChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"RecentActivityItemLastChanged"), aname="_RecentActivityItemLastChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"createDate"), aname="_createDate", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyType(pname=(ns,"propertiesChanged"), aname="_propertiesChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._abInfo = None + self._lastChange = None + self._DynamicItemLastChanged = None + self._RecentActivityItemLastChanged = None + self._createDate = None + self._propertiesChanged = None + return + Holder.__name__ = "abType_Holder" + self.pyclass = Holder + + class CircleResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CircleResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CircleResultType_Def.schema + TClist = [self.__class__.Circles_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"CircleTicket"), aname="_CircleTicket", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Circles = None + self._CircleTicket = None + return + Holder.__name__ = "CircleResultType_Holder" + self.pyclass = Holder + + + class Circles_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Circles" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.CircleResultType_Def.Circles_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"CircleInverseInfoType",lazy=False)(pname=(ns,"CircleInverseInfo"), aname="_CircleInverseInfo", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Circles") + kw["aname"] = "_Circles" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CircleInverseInfo = [] + return + Holder.__name__ = "Circles_Holder" + self.pyclass = Holder + + + + + class NetworkInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "NetworkInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.NetworkInfoType_Def.schema + TClist = [ZSI.TCnumbers.Iint(pname=(ns,"DomainId"), aname="_DomainId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DomainTag"), aname="_DomainTag", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"UserTileURL"), aname="_UserTileURL", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ProfileURL"), aname="_ProfileURL", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"RelationshipType"), aname="_RelationshipType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"RelationshipState"), aname="_RelationshipState", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"RelationshipStateDate"), aname="_RelationshipStateDate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"RelationshipRole"), aname="_RelationshipRole", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"NDRCount"), aname="_NDRCount", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"InviterName"), aname="_InviterName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"InviterMessage"), aname="_InviterMessage", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Ilong(pname=(ns,"InviterCID"), aname="_InviterCID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"InviterEmail"), aname="_InviterEmail", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"CreateDate"), aname="_CreateDate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastChanged"), aname="_LastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyType(pname=(ns,"PropertiesChanged"), aname="_PropertiesChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"SourceId"), aname="_SourceId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DomainId = None + self._DomainTag = None + self._DisplayName = None + self._UserTileURL = None + self._ProfileURL = None + self._RelationshipType = None + self._RelationshipState = None + self._RelationshipStateDate = None + self._RelationshipRole = None + self._NDRCount = None + self._InviterName = None + self._InviterMessage = None + self._InviterCID = None + self._InviterEmail = None + self._CreateDate = None + self._LastChanged = None + self._PropertiesChanged = None + self._SourceId = None + return + Holder.__name__ = "NetworkInfoType_Holder" + self.pyclass = Holder + + class ContactFilterType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContactFilterType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ContactFilterType_Def.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"IncludeHiddenContacts"), aname="_IncludeHiddenContacts", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._IncludeHiddenContacts = None + return + Holder.__name__ = "ContactFilterType_Holder" + self.pyclass = Holder + + class filterOptionsType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "filterOptionsType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.filterOptionsType_Def.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"DeltasOnly"), aname="_DeltasOnly", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastChanged"), aname="_LastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"ContactFilterType",lazy=False)(pname=(ns,"ContactFilter"), aname="_ContactFilter", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DeltasOnly = None + self._LastChanged = None + self._ContactFilter = None + return + Holder.__name__ = "filterOptionsType_Holder" + self.pyclass = Holder + + class entityHandle_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "entityHandle") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.entityHandle_Def.schema + TClist = [ZSI.TCnumbers.Ilong(pname=(ns,"Cid"), aname="_Cid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Cid = None + return + Holder.__name__ = "entityHandle_Holder" + self.pyclass = Holder + + class NotationType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "NotationType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.NotationType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Name"), aname="_Name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Value"), aname="_Value", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Name = None + self._Value = None + return + Holder.__name__ = "NotationType_Holder" + self.pyclass = Holder + + class ListTemplateVariableItemType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ListTemplateVariableItemType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ListTemplateVariableItemType_Def.schema + TClist = [self.__class__.Values_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Values = None + return + Holder.__name__ = "ListTemplateVariableItemType_Holder" + self.pyclass = Holder + + + class Values_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Values" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ListTemplateVariableItemType_Def.Values_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"SimpleTemplateVariableBaseType",lazy=False)(pname=(ns,"Value"), aname="_Value", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Values") + kw["aname"] = "_Values" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Value = [] + return + Holder.__name__ = "Values_Holder" + self.pyclass = Holder + + + + + class TemplateVariableBaseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "TemplateVariableBaseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.TemplateVariableBaseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Name"), aname="_Name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Name = None + return + Holder.__name__ = "TemplateVariableBaseType_Holder" + self.pyclass = Holder + + class SimpleTemplateVariableBaseType_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "SimpleTemplateVariableBaseType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.SimpleTemplateVariableBaseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Value"), aname="_Value", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.TemplateVariableBaseType_Def not in msnab.SimpleTemplateVariableBaseType_Def.__bases__: + bases = list(msnab.SimpleTemplateVariableBaseType_Def.__bases__) + bases.insert(0, msnab.TemplateVariableBaseType_Def) + msnab.SimpleTemplateVariableBaseType_Def.__bases__ = tuple(bases) + + msnab.TemplateVariableBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class PublisherIdTemplateVariable_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "PublisherIdTemplateVariable") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.PublisherIdTemplateVariable_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"NameHint"), aname="_NameHint", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.TemplateVariableBaseType_Def not in msnab.PublisherIdTemplateVariable_Def.__bases__: + bases = list(msnab.PublisherIdTemplateVariable_Def.__bases__) + bases.insert(0, msnab.TemplateVariableBaseType_Def) + msnab.PublisherIdTemplateVariable_Def.__bases__ = tuple(bases) + + msnab.TemplateVariableBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class TargetIdTemplateVariable_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "TargetIdTemplateVariable") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.TargetIdTemplateVariable_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"IdOwner"), aname="_IdOwner", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.PublisherIdTemplateVariable_Def not in msnab.TargetIdTemplateVariable_Def.__bases__: + bases = list(msnab.TargetIdTemplateVariable_Def.__bases__) + bases.insert(0, msnab.PublisherIdTemplateVariable_Def) + msnab.TargetIdTemplateVariable_Def.__bases__ = tuple(bases) + + msnab.PublisherIdTemplateVariable_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class TextTemplateVariable_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "TextTemplateVariable") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.TextTemplateVariable_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.SimpleTemplateVariableBaseType_Def not in msnab.TextTemplateVariable_Def.__bases__: + bases = list(msnab.TextTemplateVariable_Def.__bases__) + bases.insert(0, msnab.SimpleTemplateVariableBaseType_Def) + msnab.TextTemplateVariable_Def.__bases__ = tuple(bases) + + msnab.SimpleTemplateVariableBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class HlinkTemplateVariable_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "HlinkTemplateVariable") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.HlinkTemplateVariable_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Text"), aname="_Text", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.Notations_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.SimpleTemplateVariableBaseType_Def not in msnab.HlinkTemplateVariable_Def.__bases__: + bases = list(msnab.HlinkTemplateVariable_Def.__bases__) + bases.insert(0, msnab.SimpleTemplateVariableBaseType_Def) + msnab.HlinkTemplateVariable_Def.__bases__ = tuple(bases) + + msnab.SimpleTemplateVariableBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + + class Notations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Notations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.HlinkTemplateVariable_Def.Notations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"NotationType",lazy=False)(pname=(ns,"Notation"), aname="_Notation", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Notations") + kw["aname"] = "_Notations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Notation = [] + return + Holder.__name__ = "Notations_Holder" + self.pyclass = Holder + + + + + class ListTemplateVariable_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "ListTemplateVariable") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.ListTemplateVariable_Def.schema + TClist = [self.__class__.Items_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.TemplateVariableBaseType_Def not in msnab.ListTemplateVariable_Def.__bases__: + bases = list(msnab.ListTemplateVariable_Def.__bases__) + bases.insert(0, msnab.TemplateVariableBaseType_Def) + msnab.ListTemplateVariable_Def.__bases__ = tuple(bases) + + msnab.TemplateVariableBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + + class Items_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Items" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ListTemplateVariable_Def.Items_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ListTemplateVariableItemType",lazy=False)(pname=(ns,"ListTemplateVariableItem"), aname="_ListTemplateVariableItem", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Items") + kw["aname"] = "_Items" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ListTemplateVariableItem = [] + return + Holder.__name__ = "Items_Holder" + self.pyclass = Holder + + + + + class ImageTemplateVariable_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.ADDRESS + type = (schema, "ImageTemplateVariable") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnab.ImageTemplateVariable_Def.schema + TClist = [ZSI.TC.URI(pname=(ns,"Href"), aname="_Href", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.Notations_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnab.SimpleTemplateVariableBaseType_Def not in msnab.ImageTemplateVariable_Def.__bases__: + bases = list(msnab.ImageTemplateVariable_Def.__bases__) + bases.insert(0, msnab.SimpleTemplateVariableBaseType_Def) + msnab.ImageTemplateVariable_Def.__bases__ = tuple(bases) + + msnab.SimpleTemplateVariableBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + + class Notations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Notations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ImageTemplateVariable_Def.Notations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"NotationType",lazy=False)(pname=(ns,"Notation"), aname="_Notation", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Notations") + kw["aname"] = "_Notations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Notation = [] + return + Holder.__name__ = "Notations_Holder" + self.pyclass = Holder + + + + + class ActivityDetailsType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ActivityDetailsType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ActivityDetailsType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"OwnerCID"), aname="_OwnerCID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ObjectId"), aname="_ObjectId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ApplicationId"), aname="_ApplicationId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ChangeType"), aname="_ChangeType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"PublishDate"), aname="_PublishDate", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.TemplateVariables_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._OwnerCID = None + self._ObjectId = None + self._ApplicationId = None + self._ChangeType = None + self._PublishDate = None + self._TemplateVariables = None + return + Holder.__name__ = "ActivityDetailsType_Holder" + self.pyclass = Holder + + + class TemplateVariables_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "TemplateVariables" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ActivityDetailsType_Def.TemplateVariables_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"TemplateVariableBaseType",lazy=False)(pname=(ns,"TemplateVariable"), aname="_TemplateVariable", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"TemplateVariables") + kw["aname"] = "_TemplateVariables" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._TemplateVariable = [] + return + Holder.__name__ = "TemplateVariables_Holder" + self.pyclass = Holder + + + + + class RecentActivityTemplateType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "RecentActivityTemplateType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.RecentActivityTemplateType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Cardinality"), aname="_Cardinality", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Data"), aname="_Data", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Title"), aname="_Title", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Cardinality = None + self._Data = None + self._Title = None + return + Holder.__name__ = "RecentActivityTemplateType_Holder" + self.pyclass = Holder + + class RequestedLocalesType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "RequestedLocalesType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.RequestedLocalesType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"string"), aname="_string", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._string = [] + return + Holder.__name__ = "RequestedLocalesType_Holder" + self.pyclass = Holder + + class RecentActivityTemplateContainerType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "RecentActivityTemplateContainerType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.RecentActivityTemplateContainerType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"ApplicationId"), aname="_ApplicationId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ApplicationName"), aname="_ApplicationName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"ChangeType"), aname="_ChangeType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Locale"), aname="_Locale", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"RequestedLocalesType",lazy=False)(pname=(ns,"RequestedLocales"), aname="_RequestedLocales", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"TemplateRevision"), aname="_TemplateRevision", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.Templates_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"CollapseConditionType",lazy=False)(pname=(ns,"CollapseCondition"), aname="_CollapseCondition", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ApplicationId = None + self._ApplicationName = None + self._ChangeType = None + self._Locale = None + self._RequestedLocales = None + self._TemplateRevision = None + self._Templates = None + self._CollapseCondition = None + return + Holder.__name__ = "RecentActivityTemplateContainerType_Holder" + self.pyclass = Holder + + + class Templates_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Templates" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.RecentActivityTemplateContainerType_Def.Templates_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"RecentActivityTemplateType",lazy=False)(pname=(ns,"RecentActivityTemplate"), aname="_RecentActivityTemplate", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Templates") + kw["aname"] = "_Templates" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._RecentActivityTemplate = [] + return + Holder.__name__ = "Templates_Holder" + self.pyclass = Holder + + + + + class CollapseConditionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CollapseConditionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CollapseConditionType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"string"), aname="_string", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._string = [] + return + Holder.__name__ = "CollapseConditionType_Holder" + self.pyclass = Holder + + class CirclePersonalMembershipType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CirclePersonalMembershipType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CirclePersonalMembershipType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Role"), aname="_Role", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"State"), aname="_State", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Role = None + self._State = None + return + Holder.__name__ = "CirclePersonalMembershipType_Holder" + self.pyclass = Holder + + class abHandleType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "abHandleType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.abHandleType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"ABId"), aname="_ABId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Ilong(pname=(ns,"Puid"), aname="_Puid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Ilong(pname=(ns,"Cid"), aname="_Cid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ABId = None + self._Puid = None + self._Cid = None + return + Holder.__name__ = "abHandleType_Holder" + self.pyclass = Holder + + class contactHandleType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "contactHandleType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.contactHandleType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Email"), aname="_Email", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Ilong(pname=(ns,"Puid"), aname="_Puid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Ilong(pname=(ns,"Cid"), aname="_Cid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"CircleId"), aname="_CircleId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Email = None + self._Puid = None + self._Cid = None + self._CircleId = None + return + Holder.__name__ = "contactHandleType_Holder" + self.pyclass = Holder + + class MembershipInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "MembershipInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.MembershipInfoType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"CirclePersonalMembershipType",lazy=False)(pname=(ns,"CirclePersonalMembership"), aname="_CirclePersonalMembership", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CirclePersonalMembership = None + return + Holder.__name__ = "MembershipInfoType_Holder" + self.pyclass = Holder + + class PersonalInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "PersonalInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.PersonalInfoType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"MembershipInfoType",lazy=False)(pname=(ns,"MembershipInfo"), aname="_MembershipInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Name"), aname="_Name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsNotMobileVisible"), aname="_IsNotMobileVisible", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsFavorite"), aname="_IsFavorite", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsFamily"), aname="_IsFamily", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyType(pname=(ns,"Changes"), aname="_Changes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._MembershipInfo = None + self._Name = None + self._IsNotMobileVisible = None + self._IsFavorite = None + self._IsFamily = None + self._Changes = None + return + Holder.__name__ = "PersonalInfoType_Holder" + self.pyclass = Holder + + class ContentInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContentInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ContentInfoType_Def.schema + TClist = [ZSI.TCnumbers.Iint(pname=(ns,"Domain"), aname="_Domain", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"HostedDomain"), aname="_HostedDomain", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"Type"), aname="_Type", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"MembershipAccess"), aname="_MembershipAccess", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsPresenceEnabled"), aname="_IsPresenceEnabled", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"RequestMembershipOption"), aname="_RequestMembershipOption", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"ProfileLastUpdated"), aname="_ProfileLastUpdated", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyType(pname=(ns,"Changes"), aname="_Changes", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"CreateDate"), aname="_CreateDate", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastChanged"), aname="_LastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Domain = None + self._HostedDomain = None + self._Type = None + self._MembershipAccess = None + self._IsPresenceEnabled = None + self._RequestMembershipOption = None + self._DisplayName = None + self._ProfileLastUpdated = None + self._Changes = None + self._CreateDate = None + self._LastChanged = None + return + Holder.__name__ = "ContentInfoType_Holder" + self.pyclass = Holder + + class ContentHandleType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContentHandleType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ContentHandleType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Id = None + return + Holder.__name__ = "ContentHandleType_Holder" + self.pyclass = Holder + + class ContentType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ContentType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ContentType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContentHandleType",lazy=False)(pname=(ns,"Handle"), aname="_Handle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"ContentInfoType",lazy=False)(pname=(ns,"Info"), aname="_Info", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Handle = None + self._Info = None + return + Holder.__name__ = "ContentType_Holder" + self.pyclass = Holder + + class CircleInverseInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CircleInverseInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CircleInverseInfoType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContentType",lazy=False)(pname=(ns,"Content"), aname="_Content", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"PersonalInfoType",lazy=False)(pname=(ns,"PersonalInfo"), aname="_PersonalInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"Deleted"), aname="_Deleted", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Content = None + self._PersonalInfo = None + self._Deleted = None + return + Holder.__name__ = "CircleInverseInfoType_Holder" + self.pyclass = Holder + + class callerInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "callerInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.callerInfoType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"PublicDisplayName"), aname="_PublicDisplayName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._PublicDisplayName = None + return + Holder.__name__ = "callerInfoType_Holder" + self.pyclass = Holder + + class OwnerNamespaceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "OwnerNamespaceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.OwnerNamespaceType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"OwnerNamespaceInfoType",lazy=False)(pname=(ns,"Info"), aname="_Info", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Changes"), aname="_Changes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"CreateDate"), aname="_CreateDate", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LastChange"), aname="_LastChange", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Info = None + self._Changes = None + self._CreateDate = None + self._LastChange = None + return + Holder.__name__ = "OwnerNamespaceType_Holder" + self.pyclass = Holder + + class CircleAttributesType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CircleAttributesType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CircleAttributesType_Def.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"IsPresenceEnabled"), aname="_IsPresenceEnabled", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsEvent"), aname="_IsEvent", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Domain"), aname="_Domain", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._IsPresenceEnabled = None + self._IsEvent = None + self._Domain = None + return + Holder.__name__ = "CircleAttributesType_Holder" + self.pyclass = Holder + + class OwnerNamespaceInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "OwnerNamespaceInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.OwnerNamespaceInfoType_Def.schema + TClist = [self.__class__.Handle_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"CreatorPuid"), aname="_CreatorPuid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"CreatorCID"), aname="_CreatorCID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"CreatorPassportName"), aname="_CreatorPassportName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"CircleAttributesType",lazy=False)(pname=(ns,"CircleAttributes"), aname="_CircleAttributes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"MessengerApplicationServiceCreated"), aname="_MessengerApplicationServiceCreated", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Handle = None + self._CreatorPuid = None + self._CreatorCID = None + self._CreatorPassportName = None + self._CircleAttributes = None + self._MessengerApplicationServiceCreated = None + return + Holder.__name__ = "OwnerNamespaceInfoType_Holder" + self.pyclass = Holder + + + class Handle_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Handle" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.OwnerNamespaceInfoType_Def.Handle_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsPassportNameHidden"), aname="_IsPassportNameHidden", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"CID"), aname="_CID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Handle") + kw["aname"] = "_Handle" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Id = None + self._IsPassportNameHidden = None + self._CID = None + return + Holder.__name__ = "Handle_Holder" + self.pyclass = Holder + + + + + class FindMembershipRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "FindMembershipRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.FindMembershipRequestType_Def.schema + TClist = [self.__class__.serviceFilter_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"View"), aname="_View", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"deltasOnly"), aname="_deltasOnly", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"lastChange"), aname="_lastChange", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._serviceFilter = None + self._View = None + self._deltasOnly = None + self._lastChange = None + return + Holder.__name__ = "FindMembershipRequestType_Holder" + self.pyclass = Holder + + + class serviceFilter_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "serviceFilter" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.FindMembershipRequestType_Def.serviceFilter_Dec.schema + TClist = [self.__class__.Types_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"serviceFilter") + kw["aname"] = "_serviceFilter" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Types = None + return + Holder.__name__ = "serviceFilter_Holder" + self.pyclass = Holder + + + class Types_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Types" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.FindMembershipRequestType_Def.serviceFilter_Dec.Types_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"ServiceType"), aname="_ServiceType", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Types") + kw["aname"] = "_Types" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ServiceType = [] + return + Holder.__name__ = "Types_Holder" + self.pyclass = Holder + + + + + + + + class FindMembershipResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "FindMembershipResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.FindMembershipResultType_Def.schema + TClist = [self.__class__.Services_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"OwnerNamespaceType",lazy=False)(pname=(ns,"OwnerNamespace"), aname="_OwnerNamespace", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Services = None + self._OwnerNamespace = None + return + Holder.__name__ = "FindMembershipResultType_Holder" + self.pyclass = Holder + + + class Services_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Services" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.FindMembershipResultType_Def.Services_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ServiceType",lazy=False)(pname=(ns,"Service"), aname="_Service", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Services") + kw["aname"] = "_Services" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Service = [] + return + Holder.__name__ = "Services_Holder" + self.pyclass = Holder + + + + + class ABFindAllRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABFindAllRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABFindAllRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"abView"), aname="_abView", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"deltasOnly"), aname="_deltasOnly", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"lastChange"), aname="_lastChange", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"dynamicItemView"), aname="_dynamicItemView", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"dynamicItemLastChange"), aname="_dynamicItemLastChange", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._abView = None + self._deltasOnly = None + self._lastChange = None + self._dynamicItemView = None + self._dynamicItemLastChange = None + return + Holder.__name__ = "ABFindAllRequestType_Holder" + self.pyclass = Holder + + class ABFindAllResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABFindAllResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABFindAllResultType_Def.schema + TClist = [self.__class__.groups_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.contacts_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.DynamicItems_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.CircleResult_Dec(minOccurs=0, maxOccurs=1, nillable=True, encoded=kw.get("encoded")), + self.__class__.ab_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._groups = None + self._contacts = None + self._DynamicItems = None + self._CircleResult = None + self._ab = None + return + Holder.__name__ = "ABFindAllResultType_Holder" + self.pyclass = Holder + + + class groups_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groups" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindAllResultType_Def.groups_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"GroupType",lazy=False)(pname=(ns,"Group"), aname="_Group", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groups") + kw["aname"] = "_groups" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Group = [] + return + Holder.__name__ = "groups_Holder" + self.pyclass = Holder + + + + + + class contacts_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "contacts" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindAllResultType_Def.contacts_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"Contact"), aname="_Contact", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"contacts") + kw["aname"] = "_contacts" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Contact = [] + return + Holder.__name__ = "contacts_Holder" + self.pyclass = Holder + + + + + + class DynamicItems_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "DynamicItems" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindAllResultType_Def.DynamicItems_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"BaseDynamicItemType",lazy=False)(pname=(ns,"DynamicItem"), aname="_DynamicItem", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"DynamicItems") + kw["aname"] = "_DynamicItems" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DynamicItem = [] + return + Holder.__name__ = "DynamicItems_Holder" + self.pyclass = Holder + + + + + + class CircleResult_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "CircleResult" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindAllResultType_Def.CircleResult_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"CircleTicket"), aname="_CircleTicket", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"CircleResult") + kw["aname"] = "_CircleResult" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CircleTicket = None + return + Holder.__name__ = "CircleResult_Holder" + self.pyclass = Holder + + + + + + class ab_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "ab" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindAllResultType_Def.ab_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"abInfoType",lazy=False)(pname=(ns,"abInfo"), aname="_abInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"lastChange"), aname="_lastChange", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DynamicItemLastChanged"), aname="_DynamicItemLastChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"RecentActivityItemLastChanged"), aname="_RecentActivityItemLastChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"createDate"), aname="_createDate", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"propertiesChanged"), aname="_propertiesChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ab") + kw["aname"] = "_ab" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._abInfo = None + self._lastChange = None + self._DynamicItemLastChanged = None + self._RecentActivityItemLastChanged = None + self._createDate = None + self._propertiesChanged = None + return + Holder.__name__ = "ab_Holder" + self.pyclass = Holder + + + + + class ABContactAddRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABContactAddRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABContactAddRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.contacts_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), self.__class__.options_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._contacts = None + self._options = None + return + Holder.__name__ = "ABContactAddRequestType_Holder" + self.pyclass = Holder + + + class contacts_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "contacts" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABContactAddRequestType_Def.contacts_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"Contact"), aname="_Contact", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"contacts") + kw["aname"] = "_contacts" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Contact = [] + return + Holder.__name__ = "contacts_Holder" + self.pyclass = Holder + + + + + + class options_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "options" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABContactAddRequestType_Def.options_Dec.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"EnableAllowListManagement"), aname="_EnableAllowListManagement", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"options") + kw["aname"] = "_options" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._EnableAllowListManagement = None + return + Holder.__name__ = "options_Holder" + self.pyclass = Holder + + + + + class ABContactAddResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABContactAddResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABContactAddResultType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"guid"), aname="_guid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._guid = None + return + Holder.__name__ = "ABContactAddResultType_Holder" + self.pyclass = Holder + + class ABContactDeleteRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABContactDeleteRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABContactDeleteRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.contacts_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._contacts = None + return + Holder.__name__ = "ABContactDeleteRequestType_Holder" + self.pyclass = Holder + + + class contacts_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "contacts" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABContactDeleteRequestType_Def.contacts_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactIdType",lazy=False)(pname=(ns,"Contact"), aname="_Contact", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"contacts") + kw["aname"] = "_contacts" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Contact = [] + return + Holder.__name__ = "contacts_Holder" + self.pyclass = Holder + + + + + class ABGroupContactAddRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABGroupContactAddRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABGroupContactAddRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"groupFilterType",lazy=False)(pname=(ns,"groupFilter"), aname="_groupFilter", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.contacts_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), self.__class__.groupContactAddOptions_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._groupFilter = None + self._contacts = None + self._groupContactAddOptions = None + return + Holder.__name__ = "ABGroupContactAddRequestType_Holder" + self.pyclass = Holder + + + class contacts_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "contacts" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupContactAddRequestType_Def.contacts_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"Contact"), aname="_Contact", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"contacts") + kw["aname"] = "_contacts" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Contact = [] + return + Holder.__name__ = "contacts_Holder" + self.pyclass = Holder + + + + + + class groupContactAddOptions_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groupContactAddOptions" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupContactAddRequestType_Def.groupContactAddOptions_Dec.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"fGenerateMissingQuickName"), aname="_fGenerateMissingQuickName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"EnableAllowListManagement"), aname="_EnableAllowListManagement", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groupContactAddOptions") + kw["aname"] = "_groupContactAddOptions" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._fGenerateMissingQuickName = None + self._EnableAllowListManagement = None + return + Holder.__name__ = "groupContactAddOptions_Holder" + self.pyclass = Holder + + + + + class ABGroupContactAddResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABGroupContactAddResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABGroupContactAddResultType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"guid"), aname="_guid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._guid = None + return + Holder.__name__ = "ABGroupContactAddResultType_Holder" + self.pyclass = Holder + + class ABGroupAddRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABGroupAddRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABGroupAddRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.groupAddOptions_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), self.__class__.groupInfo_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._groupAddOptions = None + self._groupInfo = None + return + Holder.__name__ = "ABGroupAddRequestType_Holder" + self.pyclass = Holder + + + class groupAddOptions_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groupAddOptions" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupAddRequestType_Def.groupAddOptions_Dec.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"fRenameOnMsgrConflict"), aname="_fRenameOnMsgrConflict", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groupAddOptions") + kw["aname"] = "_groupAddOptions" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._fRenameOnMsgrConflict = None + return + Holder.__name__ = "groupAddOptions_Holder" + self.pyclass = Holder + + + + + + class groupInfo_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groupInfo" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupAddRequestType_Def.groupInfo_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"groupInfoType",lazy=False)(pname=(ns,"GroupInfo"), aname="_GroupInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groupInfo") + kw["aname"] = "_groupInfo" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._GroupInfo = None + return + Holder.__name__ = "groupInfo_Holder" + self.pyclass = Holder + + + + + class ABGroupAddResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABGroupAddResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABGroupAddResultType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"guid"), aname="_guid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._guid = None + return + Holder.__name__ = "ABGroupAddResultType_Holder" + self.pyclass = Holder + + class ABGroupUpdateRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABGroupUpdateRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABGroupUpdateRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.groups_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._groups = None + return + Holder.__name__ = "ABGroupUpdateRequestType_Holder" + self.pyclass = Holder + + + class groups_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "groups" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupUpdateRequestType_Def.groups_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"GroupType",lazy=False)(pname=(ns,"Group"), aname="_Group", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"groups") + kw["aname"] = "_groups" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Group = [] + return + Holder.__name__ = "groups_Holder" + self.pyclass = Holder + + + + + class ABGroupDeleteRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABGroupDeleteRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABGroupDeleteRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"groupFilterType",lazy=False)(pname=(ns,"groupFilter"), aname="_groupFilter", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._groupFilter = None + return + Holder.__name__ = "ABGroupDeleteRequestType_Holder" + self.pyclass = Holder + + class ABContactUpdateRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABContactUpdateRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABContactUpdateRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.contacts_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._contacts = None + return + Holder.__name__ = "ABContactUpdateRequestType_Holder" + self.pyclass = Holder + + + class contacts_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "contacts" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABContactUpdateRequestType_Def.contacts_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"Contact"), aname="_Contact", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"contacts") + kw["aname"] = "_contacts" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Contact = [] + return + Holder.__name__ = "contacts_Holder" + self.pyclass = Holder + + + + + class ABGroupContactDeleteRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABGroupContactDeleteRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABGroupContactDeleteRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.contacts_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"groupFilterType",lazy=False)(pname=(ns,"groupFilter"), aname="_groupFilter", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._contacts = None + self._groupFilter = None + return + Holder.__name__ = "ABGroupContactDeleteRequestType_Holder" + self.pyclass = Holder + + + class contacts_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "contacts" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupContactDeleteRequestType_Def.contacts_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"Contact"), aname="_Contact", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"contacts") + kw["aname"] = "_contacts" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Contact = [] + return + Holder.__name__ = "contacts_Holder" + self.pyclass = Holder + + + + + class AddMemberRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "AddMemberRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.AddMemberRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"HandleType",lazy=False)(pname=(ns,"serviceHandle"), aname="_serviceHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.memberships_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._serviceHandle = None + self._memberships = None + return + Holder.__name__ = "AddMemberRequestType_Holder" + self.pyclass = Holder + + + class memberships_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "memberships" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.AddMemberRequestType_Def.memberships_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Membership",lazy=False)(pname=(ns,"Membership"), aname="_Membership", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"memberships") + kw["aname"] = "_memberships" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Membership = [] + return + Holder.__name__ = "memberships_Holder" + self.pyclass = Holder + + + + + class DeleteMemberRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "DeleteMemberRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.DeleteMemberRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"HandleType",lazy=False)(pname=(ns,"serviceHandle"), aname="_serviceHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.memberships_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"ContentHandleType",lazy=False)(pname=(ns,"nsHandle"), aname="_nsHandle", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._serviceHandle = None + self._memberships = None + self._nsHandle = None + return + Holder.__name__ = "DeleteMemberRequestType_Holder" + self.pyclass = Holder + + + class memberships_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "memberships" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.DeleteMemberRequestType_Def.memberships_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Membership",lazy=False)(pname=(ns,"Membership"), aname="_Membership", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"memberships") + kw["aname"] = "_memberships" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Membership = [] + return + Holder.__name__ = "memberships_Holder" + self.pyclass = Holder + + + + + class ABAddResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABAddResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABAddResponseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"ABAddResult"), aname="_ABAddResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ABAddResult = None + return + Holder.__name__ = "ABAddResponseType_Holder" + self.pyclass = Holder + + class ABAddRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABAddRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABAddRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"abInfoType",lazy=False)(pname=(ns,"abInfo"), aname="_abInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abInfo = None + return + Holder.__name__ = "ABAddRequestType_Holder" + self.pyclass = Holder + + class UpdateDynamicItemRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "UpdateDynamicItemRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.UpdateDynamicItemRequestType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.dynamicItems_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._dynamicItems = None + return + Holder.__name__ = "UpdateDynamicItemRequestType_Holder" + self.pyclass = Holder + + + class dynamicItems_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "dynamicItems" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.UpdateDynamicItemRequestType_Def.dynamicItems_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"BaseDynamicItemType",lazy=False)(pname=(ns,"DynamicItem"), aname="_DynamicItem", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"dynamicItems") + kw["aname"] = "_dynamicItems" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DynamicItem = [] + return + Holder.__name__ = "dynamicItems_Holder" + self.pyclass = Holder + + + + + class ABFindContactsPagedRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABFindContactsPagedRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABFindContactsPagedRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"filterOptionsType",lazy=False)(pname=(ns,"filterOptions"), aname="_filterOptions", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"abView"), aname="_abView", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"extendedContent"), aname="_extendedContent", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.ADDRESS,"abHandleType",lazy=False)(pname=(ns,"abHandle"), aname="_abHandle", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._filterOptions = None + self._abView = None + self._extendedContent = None + self._abHandle = None + return + Holder.__name__ = "ABFindContactsPagedRequestType_Holder" + self.pyclass = Holder + + class ABFindContactsPagedResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ABFindContactsPagedResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ABFindContactsPagedResultType_Def.schema + TClist = [self.__class__.Groups_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.Contacts_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.ADDRESS,"CircleResultType",lazy=False)(pname=(ns,"CircleResult"), aname="_CircleResult", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded")), + self.__class__.Ab_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Groups = None + self._Contacts = None + self._CircleResult = None + self._Ab = None + return + Holder.__name__ = "ABFindContactsPagedResultType_Holder" + self.pyclass = Holder + + + class Groups_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Groups" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindContactsPagedResultType_Def.Groups_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"GroupType",lazy=False)(pname=(ns,"Group"), aname="_Group", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Groups") + kw["aname"] = "_Groups" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Group = [] + return + Holder.__name__ = "Groups_Holder" + self.pyclass = Holder + + + + + + class Contacts_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Contacts" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindContactsPagedResultType_Def.Contacts_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"Contact"), aname="_Contact", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Contacts") + kw["aname"] = "_Contacts" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Contact = [] + return + Holder.__name__ = "Contacts_Holder" + self.pyclass = Holder + + + + + + class Ab_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Ab" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindContactsPagedResultType_Def.Ab_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"abInfoType",lazy=False)(pname=(ns,"abInfo"), aname="_abInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"lastChange"), aname="_lastChange", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"DynamicItemLastChanged"), aname="_DynamicItemLastChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"RecentActivityItemLastChanged"), aname="_RecentActivityItemLastChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"createDate"), aname="_createDate", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"propertiesChanged"), aname="_propertiesChanged", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Ab") + kw["aname"] = "_Ab" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._abInfo = None + self._lastChange = None + self._DynamicItemLastChanged = None + self._RecentActivityItemLastChanged = None + self._createDate = None + self._propertiesChanged = None + return + Holder.__name__ = "Ab_Holder" + self.pyclass = Holder + + + + + class GetContactsRecentActivityRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "GetContactsRecentActivityRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.GetContactsRecentActivityRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"entityHandle",lazy=False)(pname=(ns,"entityHandle"), aname="_entityHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.locales_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"count"), aname="_count", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._entityHandle = None + self._locales = None + self._count = None + return + Holder.__name__ = "GetContactsRecentActivityRequestType_Holder" + self.pyclass = Holder + + + class locales_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "locales" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.GetContactsRecentActivityRequestType_Def.locales_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"string"), aname="_string", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"locales") + kw["aname"] = "_locales" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._string = [] + return + Holder.__name__ = "locales_Holder" + self.pyclass = Holder + + + + + class GetContactsRecentActivityResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "GetContactsRecentActivityResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.GetContactsRecentActivityResultType_Def.schema + TClist = [self.__class__.Activities_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), self.__class__.Templates_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.URI(pname=(ns,"FeedUrl"), aname="_FeedUrl", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Activities = None + self._Templates = None + self._FeedUrl = None + return + Holder.__name__ = "GetContactsRecentActivityResultType_Holder" + self.pyclass = Holder + + + class Activities_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Activities" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.GetContactsRecentActivityResultType_Def.Activities_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ActivityDetailsType",lazy=False)(pname=(ns,"ActivityDetails"), aname="_ActivityDetails", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Activities") + kw["aname"] = "_Activities" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ActivityDetails = [] + return + Holder.__name__ = "Activities_Holder" + self.pyclass = Holder + + + + + + class Templates_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Templates" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.GetContactsRecentActivityResultType_Def.Templates_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"RecentActivityTemplateContainerType",lazy=False)(pname=(ns,"RecentActivityTemplateContainer"), aname="_RecentActivityTemplateContainer", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"Templates") + kw["aname"] = "_Templates" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._RecentActivityTemplateContainer = [] + return + Holder.__name__ = "Templates_Holder" + self.pyclass = Holder + + + + + class ManageWLConnectionRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "ManageWLConnectionRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.ManageWLConnectionRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"abHandleType",lazy=False)(pname=(ns,"abHandle"), aname="_abHandle", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"contactId"), aname="_contactId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"connection"), aname="_connection", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"presence"), aname="_presence", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"action"), aname="_action", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"relationshipType"), aname="_relationshipType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"relationshipRole"), aname="_relationshipRole", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.annotations_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abHandle = None + self._contactId = None + self._connection = None + self._presence = None + self._action = None + self._relationshipType = None + self._relationshipRole = None + self._annotations = None + return + Holder.__name__ = "ManageWLConnectionRequestType_Holder" + self.pyclass = Holder + + + class annotations_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "annotations" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ManageWLConnectionRequestType_Def.annotations_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Annotation",lazy=False)(pname=(ns,"Annotation"), aname="_Annotation", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"annotations") + kw["aname"] = "_annotations" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Annotation = [] + return + Holder.__name__ = "annotations_Holder" + self.pyclass = Holder + + + + + class CreateContactType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CreateContactType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CreateContactType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"abHandleType",lazy=False)(pname=(ns,"abHandle"), aname="_abHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"contactHandleType",lazy=False)(pname=(ns,"contactHandle"), aname="_contactHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abHandle = None + self._contactHandle = None + return + Holder.__name__ = "CreateContactType_Holder" + self.pyclass = Holder + + class CreateCircleRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CreateCircleRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CreateCircleRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContentInfoType",lazy=False)(pname=(ns,"properties"), aname="_properties", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"callerInfoType",lazy=False)(pname=(ns,"callerInfo"), aname="_callerInfo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._properties = None + self._callerInfo = None + return + Holder.__name__ = "CreateCircleRequestType_Holder" + self.pyclass = Holder + + class CreateCircleResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "CreateCircleResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.CreateCircleResponseType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"Id"), aname="_Id", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Id = None + return + Holder.__name__ = "CreateCircleResponseType_Holder" + self.pyclass = Holder + + class BreakConnectionRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "BreakConnectionRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.BreakConnectionRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"abHandleType",lazy=False)(pname=(ns,"abHandle"), aname="_abHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"contactId"), aname="_contactId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"deleteContact"), aname="_deleteContact", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"blockContact"), aname="_blockContact", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abHandle = None + self._contactId = None + self._deleteContact = None + self._blockContact = None + return + Holder.__name__ = "BreakConnectionRequestType_Holder" + self.pyclass = Holder + + class BreakConnectionResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "BreakConnectionResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.BreakConnectionResponseType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "BreakConnectionResponseType_Holder" + self.pyclass = Holder + + class AddDynamicItemRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "AddDynamicItemRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.AddDynamicItemRequestType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"abId"), aname="_abId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.dynamicItems_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._abId = None + self._dynamicItems = None + return + Holder.__name__ = "AddDynamicItemRequestType_Holder" + self.pyclass = Holder + + + class dynamicItems_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "dynamicItems" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.AddDynamicItemRequestType_Def.dynamicItems_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"BaseDynamicItemType",lazy=False)(pname=(ns,"DynamicItem"), aname="_DynamicItem", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"dynamicItems") + kw["aname"] = "_dynamicItems" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DynamicItem = [] + return + Holder.__name__ = "dynamicItems_Holder" + self.pyclass = Holder + + + + + class AddDynamicItemResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.ADDRESS + type = (schema, "AddDynamicItemResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnab.AddDynamicItemResponseType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "AddDynamicItemResponseType_Holder" + self.pyclass = Holder + + class MemberType_Dec(ZSI.TC.String, ElementDeclaration): + literal = "MemberType" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"MemberType") + kw["aname"] = "_MemberType" + ZSI.TC.String.__init__(self, **kw) + class IHolder(str): typecode=self + self.pyclass = IHolder + IHolder.__name__ = "_MemberType_immutable_holder" + + class InvalidPassportUser_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "InvalidPassportUser" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.InvalidPassportUser_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"errorcode"), aname="_errorcode", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"errorstring"), aname="_errorstring", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.additionalDetails_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"InvalidPassportUser") + kw["aname"] = "_InvalidPassportUser" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._errorcode = None + self._errorstring = None + self._additionalDetails = None + return + Holder.__name__ = "InvalidPassportUser_Holder" + self.pyclass = Holder + + + class additionalDetails_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "additionalDetails" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.InvalidPassportUser_Dec.additionalDetails_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"originalExceptionErrorMessage"), aname="_originalExceptionErrorMessage", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"additionalDetails") + kw["aname"] = "_additionalDetails" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._originalExceptionErrorMessage = None + return + Holder.__name__ = "additionalDetails_Holder" + self.pyclass = Holder + + + + + class ABApplicationHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABApplicationHeader" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABApplicationHeader_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"ApplicationId"), aname="_ApplicationId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsMigration"), aname="_IsMigration", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"PartnerScenario"), aname="_PartnerScenario", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Token(pname=(ns,"CacheKey"), aname="_CacheKey", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"BrandId"), aname="_BrandId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABApplicationHeader") + kw["aname"] = "_ABApplicationHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ApplicationId = None + self._IsMigration = None + self._PartnerScenario = None + self._CacheKey = None + self._BrandId = None + return + Holder.__name__ = "ABApplicationHeader_Holder" + self.pyclass = Holder + + class ABAuthHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABAuthHeader" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABAuthHeader_Dec.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"ManagedGroupRequest"), aname="_ManagedGroupRequest", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"TicketToken"), aname="_TicketToken", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABAuthHeader") + kw["aname"] = "_ABAuthHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ManagedGroupRequest = None + self._TicketToken = None + return + Holder.__name__ = "ABAuthHeader_Holder" + self.pyclass = Holder + + class ServiceHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ServiceHeader" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ServiceHeader_Dec.schema + TClist = [ZSI.TC.Token(pname=(ns,"Version"), aname="_Version", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Token(pname=(ns,"CacheKey"), aname="_CacheKey", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"CacheKeyChanged"), aname="_CacheKeyChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"PreferredHostName"), aname="_PreferredHostName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"SessionId"), aname="_SessionId", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ServiceHeader") + kw["aname"] = "_ServiceHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Version = None + self._CacheKey = None + self._CacheKeyChanged = None + self._PreferredHostName = None + self._SessionId = None + return + Holder.__name__ = "ServiceHeader_Holder" + self.pyclass = Holder + + class FindMembership_Dec(ElementDeclaration): + literal = "FindMembership" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"FindMembership") + kw["aname"] = "_FindMembership" + if msnab.FindMembershipRequestType_Def not in msnab.FindMembership_Dec.__bases__: + bases = list(msnab.FindMembership_Dec.__bases__) + bases.insert(0, msnab.FindMembershipRequestType_Def) + msnab.FindMembership_Dec.__bases__ = tuple(bases) + + msnab.FindMembershipRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "FindMembership_Dec_Holder" + + class FindMembershipResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "FindMembershipResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.FindMembershipResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"FindMembershipResultType",lazy=False)(pname=(ns,"FindMembershipResult"), aname="_FindMembershipResult", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"FindMembershipResponse") + kw["aname"] = "_FindMembershipResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._FindMembershipResult = None + return + Holder.__name__ = "FindMembershipResponse_Holder" + self.pyclass = Holder + + class ABFindAll_Dec(ElementDeclaration): + literal = "ABFindAll" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABFindAll") + kw["aname"] = "_ABFindAll" + if msnab.ABFindAllRequestType_Def not in msnab.ABFindAll_Dec.__bases__: + bases = list(msnab.ABFindAll_Dec.__bases__) + bases.insert(0, msnab.ABFindAllRequestType_Def) + msnab.ABFindAll_Dec.__bases__ = tuple(bases) + + msnab.ABFindAllRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABFindAll_Dec_Holder" + + class ABFindAllResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABFindAllResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindAllResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ABFindAllResultType",lazy=False)(pname=(ns,"ABFindAllResult"), aname="_ABFindAllResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABFindAllResponse") + kw["aname"] = "_ABFindAllResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ABFindAllResult = None + return + Holder.__name__ = "ABFindAllResponse_Holder" + self.pyclass = Holder + + class ABContactAdd_Dec(ElementDeclaration): + literal = "ABContactAdd" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABContactAdd") + kw["aname"] = "_ABContactAdd" + if msnab.ABContactAddRequestType_Def not in msnab.ABContactAdd_Dec.__bases__: + bases = list(msnab.ABContactAdd_Dec.__bases__) + bases.insert(0, msnab.ABContactAddRequestType_Def) + msnab.ABContactAdd_Dec.__bases__ = tuple(bases) + + msnab.ABContactAddRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABContactAdd_Dec_Holder" + + class ABContactAddResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABContactAddResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABContactAddResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ABContactAddResultType",lazy=False)(pname=(ns,"ABContactAddResult"), aname="_ABContactAddResult", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABContactAddResponse") + kw["aname"] = "_ABContactAddResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ABContactAddResult = None + return + Holder.__name__ = "ABContactAddResponse_Holder" + self.pyclass = Holder + + class ABContactDelete_Dec(ElementDeclaration): + literal = "ABContactDelete" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABContactDelete") + kw["aname"] = "_ABContactDelete" + if msnab.ABContactDeleteRequestType_Def not in msnab.ABContactDelete_Dec.__bases__: + bases = list(msnab.ABContactDelete_Dec.__bases__) + bases.insert(0, msnab.ABContactDeleteRequestType_Def) + msnab.ABContactDelete_Dec.__bases__ = tuple(bases) + + msnab.ABContactDeleteRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABContactDelete_Dec_Holder" + + class ABContactDeleteResponse_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "ABContactDeleteResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABContactDeleteResponse") + kw["aname"] = "_ABContactDeleteResponse" + ZSI.TC.AnyType.__init__(self, **kw) + + class ABGroupContactAdd_Dec(ElementDeclaration): + literal = "ABGroupContactAdd" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupContactAdd") + kw["aname"] = "_ABGroupContactAdd" + if msnab.ABGroupContactAddRequestType_Def not in msnab.ABGroupContactAdd_Dec.__bases__: + bases = list(msnab.ABGroupContactAdd_Dec.__bases__) + bases.insert(0, msnab.ABGroupContactAddRequestType_Def) + msnab.ABGroupContactAdd_Dec.__bases__ = tuple(bases) + + msnab.ABGroupContactAddRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABGroupContactAdd_Dec_Holder" + + class ABGroupContactAddResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABGroupContactAddResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupContactAddResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ABGroupContactAddResultType",lazy=False)(pname=(ns,"ABGroupContactAddResult"), aname="_ABGroupContactAddResult", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupContactAddResponse") + kw["aname"] = "_ABGroupContactAddResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ABGroupContactAddResult = None + return + Holder.__name__ = "ABGroupContactAddResponse_Holder" + self.pyclass = Holder + + class ABGroupAdd_Dec(ElementDeclaration): + literal = "ABGroupAdd" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupAdd") + kw["aname"] = "_ABGroupAdd" + if msnab.ABGroupAddRequestType_Def not in msnab.ABGroupAdd_Dec.__bases__: + bases = list(msnab.ABGroupAdd_Dec.__bases__) + bases.insert(0, msnab.ABGroupAddRequestType_Def) + msnab.ABGroupAdd_Dec.__bases__ = tuple(bases) + + msnab.ABGroupAddRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABGroupAdd_Dec_Holder" + + class ABGroupAddResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABGroupAddResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupAddResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ABGroupAddResultType",lazy=False)(pname=(ns,"ABGroupAddResult"), aname="_ABGroupAddResult", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupAddResponse") + kw["aname"] = "_ABGroupAddResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ABGroupAddResult = None + return + Holder.__name__ = "ABGroupAddResponse_Holder" + self.pyclass = Holder + + class ABGroupUpdate_Dec(ElementDeclaration): + literal = "ABGroupUpdate" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupUpdate") + kw["aname"] = "_ABGroupUpdate" + if msnab.ABGroupUpdateRequestType_Def not in msnab.ABGroupUpdate_Dec.__bases__: + bases = list(msnab.ABGroupUpdate_Dec.__bases__) + bases.insert(0, msnab.ABGroupUpdateRequestType_Def) + msnab.ABGroupUpdate_Dec.__bases__ = tuple(bases) + + msnab.ABGroupUpdateRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABGroupUpdate_Dec_Holder" + + class ABGroupUpdateResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABGroupUpdateResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupUpdateResponse_Dec.schema + TClist = [] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupUpdateResponse") + kw["aname"] = "_ABGroupUpdateResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "ABGroupUpdateResponse_Holder" + self.pyclass = Holder + + class ABGroupDelete_Dec(ElementDeclaration): + literal = "ABGroupDelete" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupDelete") + kw["aname"] = "_ABGroupDelete" + if msnab.ABGroupDeleteRequestType_Def not in msnab.ABGroupDelete_Dec.__bases__: + bases = list(msnab.ABGroupDelete_Dec.__bases__) + bases.insert(0, msnab.ABGroupDeleteRequestType_Def) + msnab.ABGroupDelete_Dec.__bases__ = tuple(bases) + + msnab.ABGroupDeleteRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABGroupDelete_Dec_Holder" + + class ABGroupDeleteResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABGroupDeleteResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupDeleteResponse_Dec.schema + TClist = [] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupDeleteResponse") + kw["aname"] = "_ABGroupDeleteResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "ABGroupDeleteResponse_Holder" + self.pyclass = Holder + + class ABContactUpdate_Dec(ElementDeclaration): + literal = "ABContactUpdate" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABContactUpdate") + kw["aname"] = "_ABContactUpdate" + if msnab.ABContactUpdateRequestType_Def not in msnab.ABContactUpdate_Dec.__bases__: + bases = list(msnab.ABContactUpdate_Dec.__bases__) + bases.insert(0, msnab.ABContactUpdateRequestType_Def) + msnab.ABContactUpdate_Dec.__bases__ = tuple(bases) + + msnab.ABContactUpdateRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABContactUpdate_Dec_Holder" + + class ABContactUpdateResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABContactUpdateResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABContactUpdateResponse_Dec.schema + TClist = [] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABContactUpdateResponse") + kw["aname"] = "_ABContactUpdateResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "ABContactUpdateResponse_Holder" + self.pyclass = Holder + + class ABGroupContactDelete_Dec(ElementDeclaration): + literal = "ABGroupContactDelete" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupContactDelete") + kw["aname"] = "_ABGroupContactDelete" + if msnab.ABGroupContactDeleteRequestType_Def not in msnab.ABGroupContactDelete_Dec.__bases__: + bases = list(msnab.ABGroupContactDelete_Dec.__bases__) + bases.insert(0, msnab.ABGroupContactDeleteRequestType_Def) + msnab.ABGroupContactDelete_Dec.__bases__ = tuple(bases) + + msnab.ABGroupContactDeleteRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABGroupContactDelete_Dec_Holder" + + class ABGroupContactDeleteResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABGroupContactDeleteResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABGroupContactDeleteResponse_Dec.schema + TClist = [] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABGroupContactDeleteResponse") + kw["aname"] = "_ABGroupContactDeleteResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "ABGroupContactDeleteResponse_Holder" + self.pyclass = Holder + + class AddMember_Dec(ElementDeclaration): + literal = "AddMember" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"AddMember") + kw["aname"] = "_AddMember" + if msnab.AddMemberRequestType_Def not in msnab.AddMember_Dec.__bases__: + bases = list(msnab.AddMember_Dec.__bases__) + bases.insert(0, msnab.AddMemberRequestType_Def) + msnab.AddMember_Dec.__bases__ = tuple(bases) + + msnab.AddMemberRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AddMember_Dec_Holder" + + class AddMemberResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "AddMemberResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.AddMemberResponse_Dec.schema + TClist = [] + kw["pname"] = (MSNS.MSWS.ADDRESS,"AddMemberResponse") + kw["aname"] = "_AddMemberResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "AddMemberResponse_Holder" + self.pyclass = Holder + + class DeleteMember_Dec(ElementDeclaration): + literal = "DeleteMember" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"DeleteMember") + kw["aname"] = "_DeleteMember" + if msnab.DeleteMemberRequestType_Def not in msnab.DeleteMember_Dec.__bases__: + bases = list(msnab.DeleteMember_Dec.__bases__) + bases.insert(0, msnab.DeleteMemberRequestType_Def) + msnab.DeleteMember_Dec.__bases__ = tuple(bases) + + msnab.DeleteMemberRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DeleteMember_Dec_Holder" + + class DeleteMemberResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "DeleteMemberResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.DeleteMemberResponse_Dec.schema + TClist = [] + kw["pname"] = (MSNS.MSWS.ADDRESS,"DeleteMemberResponse") + kw["aname"] = "_DeleteMemberResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "DeleteMemberResponse_Holder" + self.pyclass = Holder + + class ABAddResponse_Dec(ElementDeclaration): + literal = "ABAddResponse" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABAddResponse") + kw["aname"] = "_ABAddResponse" + if msnab.ABAddResponseType_Def not in msnab.ABAddResponse_Dec.__bases__: + bases = list(msnab.ABAddResponse_Dec.__bases__) + bases.insert(0, msnab.ABAddResponseType_Def) + msnab.ABAddResponse_Dec.__bases__ = tuple(bases) + + msnab.ABAddResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABAddResponse_Dec_Holder" + + class ABAdd_Dec(ElementDeclaration): + literal = "ABAdd" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABAdd") + kw["aname"] = "_ABAdd" + if msnab.ABAddRequestType_Def not in msnab.ABAdd_Dec.__bases__: + bases = list(msnab.ABAdd_Dec.__bases__) + bases.insert(0, msnab.ABAddRequestType_Def) + msnab.ABAdd_Dec.__bases__ = tuple(bases) + + msnab.ABAddRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABAdd_Dec_Holder" + + class UpdateDynamicItem_Dec(ElementDeclaration): + literal = "UpdateDynamicItem" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"UpdateDynamicItem") + kw["aname"] = "_UpdateDynamicItem" + if msnab.UpdateDynamicItemRequestType_Def not in msnab.UpdateDynamicItem_Dec.__bases__: + bases = list(msnab.UpdateDynamicItem_Dec.__bases__) + bases.insert(0, msnab.UpdateDynamicItemRequestType_Def) + msnab.UpdateDynamicItem_Dec.__bases__ = tuple(bases) + + msnab.UpdateDynamicItemRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "UpdateDynamicItem_Dec_Holder" + + class UpdateDynamicItemResponse_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "UpdateDynamicItemResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"UpdateDynamicItemResponse") + kw["aname"] = "_UpdateDynamicItemResponse" + ZSI.TC.AnyType.__init__(self, **kw) + + class ABFindContactsPaged_Dec(ElementDeclaration): + literal = "ABFindContactsPaged" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABFindContactsPaged") + kw["aname"] = "_ABFindContactsPaged" + if msnab.ABFindContactsPagedRequestType_Def not in msnab.ABFindContactsPaged_Dec.__bases__: + bases = list(msnab.ABFindContactsPaged_Dec.__bases__) + bases.insert(0, msnab.ABFindContactsPagedRequestType_Def) + msnab.ABFindContactsPaged_Dec.__bases__ = tuple(bases) + + msnab.ABFindContactsPagedRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ABFindContactsPaged_Dec_Holder" + + class ABFindContactsPagedResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ABFindContactsPagedResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ABFindContactsPagedResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ABFindContactsPagedResultType",lazy=False)(pname=(ns,"ABFindContactsPagedResult"), aname="_ABFindContactsPagedResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ABFindContactsPagedResponse") + kw["aname"] = "_ABFindContactsPagedResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ABFindContactsPagedResult = None + return + Holder.__name__ = "ABFindContactsPagedResponse_Holder" + self.pyclass = Holder + + class WNApplicationHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "WNApplicationHeader" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.WNApplicationHeader_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"Guid",lazy=False)(pname=(ns,"ApplicationId"), aname="_ApplicationId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"WNApplicationHeader") + kw["aname"] = "_WNApplicationHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ApplicationId = None + return + Holder.__name__ = "WNApplicationHeader_Holder" + self.pyclass = Holder + + class WNAuthHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "WNAuthHeader" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.WNAuthHeader_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"TicketToken"), aname="_TicketToken", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"WNAuthHeader") + kw["aname"] = "_WNAuthHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._TicketToken = None + return + Holder.__name__ = "WNAuthHeader_Holder" + self.pyclass = Holder + + class WNServiceHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "WNServiceHeader" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.WNServiceHeader_Dec.schema + TClist = [ZSI.TC.Token(pname=(ns,"Version"), aname="_Version", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Token(pname=(ns,"CacheKey"), aname="_CacheKey", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"CacheKeyChanged"), aname="_CacheKeyChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"PreferredHostName"), aname="_PreferredHostName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"WNServiceHeader") + kw["aname"] = "_WNServiceHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Version = None + self._CacheKey = None + self._CacheKeyChanged = None + self._PreferredHostName = None + return + Holder.__name__ = "WNServiceHeader_Holder" + self.pyclass = Holder + + class GetContactsRecentActivity_Dec(ElementDeclaration): + literal = "GetContactsRecentActivity" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"GetContactsRecentActivity") + kw["aname"] = "_GetContactsRecentActivity" + if msnab.GetContactsRecentActivityRequestType_Def not in msnab.GetContactsRecentActivity_Dec.__bases__: + bases = list(msnab.GetContactsRecentActivity_Dec.__bases__) + bases.insert(0, msnab.GetContactsRecentActivityRequestType_Def) + msnab.GetContactsRecentActivity_Dec.__bases__ = tuple(bases) + + msnab.GetContactsRecentActivityRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetContactsRecentActivity_Dec_Holder" + + class GetContactsRecentActivityResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "GetContactsRecentActivityResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.GetContactsRecentActivityResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"GetContactsRecentActivityResultType",lazy=False)(pname=(ns,"GetContactsRecentActivityResult"), aname="_GetContactsRecentActivityResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"GetContactsRecentActivityResponse") + kw["aname"] = "_GetContactsRecentActivityResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._GetContactsRecentActivityResult = None + return + Holder.__name__ = "GetContactsRecentActivityResponse_Holder" + self.pyclass = Holder + + class ManageWLConnection_Dec(ElementDeclaration): + literal = "ManageWLConnection" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"ManageWLConnection") + kw["aname"] = "_ManageWLConnection" + if msnab.ManageWLConnectionRequestType_Def not in msnab.ManageWLConnection_Dec.__bases__: + bases = list(msnab.ManageWLConnection_Dec.__bases__) + bases.insert(0, msnab.ManageWLConnectionRequestType_Def) + msnab.ManageWLConnection_Dec.__bases__ = tuple(bases) + + msnab.ManageWLConnectionRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ManageWLConnection_Dec_Holder" + + class ManageWLConnectionResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "ManageWLConnectionResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.ManageWLConnectionResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"ManageWLConnectionResult"), aname="_ManageWLConnectionResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"ManageWLConnectionResponse") + kw["aname"] = "_ManageWLConnectionResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ManageWLConnectionResult = None + return + Holder.__name__ = "ManageWLConnectionResponse_Holder" + self.pyclass = Holder + + class CreateContact_Dec(ElementDeclaration): + literal = "CreateContact" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"CreateContact") + kw["aname"] = "_CreateContact" + if msnab.CreateContactType_Def not in msnab.CreateContact_Dec.__bases__: + bases = list(msnab.CreateContact_Dec.__bases__) + bases.insert(0, msnab.CreateContactType_Def) + msnab.CreateContact_Dec.__bases__ = tuple(bases) + + msnab.CreateContactType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "CreateContact_Dec_Holder" + + class CreateContactResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "CreateContactResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.CreateContactResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"ContactType",lazy=False)(pname=(ns,"CreateContactResult"), aname="_CreateContactResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"CreateContactResponse") + kw["aname"] = "_CreateContactResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CreateContactResult = None + return + Holder.__name__ = "CreateContactResponse_Holder" + self.pyclass = Holder + + class CreateCircle_Dec(ElementDeclaration): + literal = "CreateCircle" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"CreateCircle") + kw["aname"] = "_CreateCircle" + if msnab.CreateCircleRequestType_Def not in msnab.CreateCircle_Dec.__bases__: + bases = list(msnab.CreateCircle_Dec.__bases__) + bases.insert(0, msnab.CreateCircleRequestType_Def) + msnab.CreateCircle_Dec.__bases__ = tuple(bases) + + msnab.CreateCircleRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "CreateCircle_Dec_Holder" + + class CreateCircleResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "CreateCircleResponse" + schema = MSNS.MSWS.ADDRESS + def __init__(self, **kw): + ns = msnab.CreateCircleResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.ADDRESS,"CreateCircleResponseType",lazy=False)(pname=(ns,"CreateCircleResult"), aname="_CreateCircleResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.ADDRESS,"CreateCircleResponse") + kw["aname"] = "_CreateCircleResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CreateCircleResult = None + return + Holder.__name__ = "CreateCircleResponse_Holder" + self.pyclass = Holder + + class BreakConnection_Dec(ElementDeclaration): + literal = "BreakConnection" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"BreakConnection") + kw["aname"] = "_BreakConnection" + if msnab.BreakConnectionRequestType_Def not in msnab.BreakConnection_Dec.__bases__: + bases = list(msnab.BreakConnection_Dec.__bases__) + bases.insert(0, msnab.BreakConnectionRequestType_Def) + msnab.BreakConnection_Dec.__bases__ = tuple(bases) + + msnab.BreakConnectionRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "BreakConnection_Dec_Holder" + + class BreakConnectionResponse_Dec(ElementDeclaration): + literal = "BreakConnectionResponse" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"BreakConnectionResponse") + kw["aname"] = "_BreakConnectionResponse" + if msnab.BreakConnectionResponseType_Def not in msnab.BreakConnectionResponse_Dec.__bases__: + bases = list(msnab.BreakConnectionResponse_Dec.__bases__) + bases.insert(0, msnab.BreakConnectionResponseType_Def) + msnab.BreakConnectionResponse_Dec.__bases__ = tuple(bases) + + msnab.BreakConnectionResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "BreakConnectionResponse_Dec_Holder" + + class AddDynamicItem_Dec(ElementDeclaration): + literal = "AddDynamicItem" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"AddDynamicItem") + kw["aname"] = "_AddDynamicItem" + if msnab.AddDynamicItemRequestType_Def not in msnab.AddDynamicItem_Dec.__bases__: + bases = list(msnab.AddDynamicItem_Dec.__bases__) + bases.insert(0, msnab.AddDynamicItemRequestType_Def) + msnab.AddDynamicItem_Dec.__bases__ = tuple(bases) + + msnab.AddDynamicItemRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AddDynamicItem_Dec_Holder" + + class AddDynamicItemResponse_Dec(ElementDeclaration): + literal = "AddDynamicItemResponse" + schema = MSNS.MSWS.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.ADDRESS,"AddDynamicItemResponse") + kw["aname"] = "_AddDynamicItemResponse" + if msnab.AddDynamicItemResponseType_Def not in msnab.AddDynamicItemResponse_Dec.__bases__: + bases = list(msnab.AddDynamicItemResponse_Dec.__bases__) + bases.insert(0, msnab.AddDynamicItemResponseType_Def) + msnab.AddDynamicItemResponse_Dec.__bases__ = tuple(bases) + + msnab.AddDynamicItemResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AddDynamicItemResponse_Dec_Holder" + +# end class msnab (tns: http://www.msn.com/webservices/AddressBook) diff --git a/digsby/src/msn/SOAP/MSNABSharingService/__init__.py b/digsby/src/msn/SOAP/MSNABSharingService/__init__.py new file mode 100644 index 0000000..0af2681 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNABSharingService/__init__.py @@ -0,0 +1,4 @@ + +from SharingService_client import * +from SharingService_types import * + diff --git a/digsby/src/msn/SOAP/MSNABSharingService/msnab_datatypes.xsd b/digsby/src/msn/SOAP/MSNABSharingService/msnab_datatypes.xsd new file mode 100644 index 0000000..f84f297 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNABSharingService/msnab_datatypes.xsd @@ -0,0 +1,883 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A space (ASCII #32) separated list of properties that + have changed as part of an update request. The property + names don't always match the name of the associated + element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether the contact has a Windows Live + Space or not. + + + + + + + + + + + + + Seen is YYYY/MM/DD format. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A space (ASCII #32) separated list of properties that + have changed as part of an update request. The property + names don't always match the name of the associated + element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNABSharingService/msnab_servicetypes.xsd b/digsby/src/msn/SOAP/MSNABSharingService/msnab_servicetypes.xsd new file mode 100644 index 0000000..8150434 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNABSharingService/msnab_servicetypes.xsd @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNABSharingService/msnab_sharingservice.wsdl b/digsby/src/msn/SOAP/MSNABSharingService/msnab_sharingservice.wsdl new file mode 100644 index 0000000..3c3d5b7 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNABSharingService/msnab_sharingservice.wsdl @@ -0,0 +1,587 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNOIMStoreService/OIMStoreService_client.py b/digsby/src/msn/SOAP/MSNOIMStoreService/OIMStoreService_client.py new file mode 100644 index 0000000..721df08 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNOIMStoreService/OIMStoreService_client.py @@ -0,0 +1,44 @@ +################################################## +# file: OIMStoreService_client.py +# +# client stubs generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +from OIMStoreService_types import * +import urlparse, types +from ZSI.TCcompound import ComplexType, Struct +from ZSI import client +from ZSI.schema import GED, GTD +import ZSI + +import util.callbacks as callbacks +import util.network.soap as soap + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS, MSNBindingSOAP + +# Locator +class OIMStoreServiceLocator: + StorePort_address = "https://ows.messenger.msn.com/OimWS/oim.asmx" + def getStorePortAddress(self): + return OIMStoreServiceLocator.StorePort_address + def getStorePort(self, url=None, **kw): + return OIMBindingSOAP(url or OIMStoreServiceLocator.StorePort_address, **kw) + +# Methods +class OIMBindingSOAP(MSNBindingSOAP): + # op: Store + @callbacks.callsback + def Store(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, StoreMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://messenger.live.com/ws/2006/09/oim/Store2", soapheaders=soapheaders, + callback = callback, + **kw) + +StoreMessage = GED(MSNS.HMNS.OIM, "MessageType").pyclass +StoreResponseMessage = GED(MSNS.HMNS.OIM, "StoreResponse").pyclass diff --git a/digsby/src/msn/SOAP/MSNOIMStoreService/OIMStoreService_types.py b/digsby/src/msn/SOAP/MSNOIMStoreService/OIMStoreService_types.py new file mode 100644 index 0000000..d81fe3f --- /dev/null +++ b/digsby/src/msn/SOAP/MSNOIMStoreService/OIMStoreService_types.py @@ -0,0 +1,212 @@ +################################################## +# file: OIMStoreService_types.py +# +# schema types generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS + +############################## +# targetNamespace +# http://messenger.msn.com/ws/2004/09/oim/ +############################## + +class oim: + targetNamespace = MSNS.HMNS.OIM + + class StoreResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.OIM + type = (schema, "StoreResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = oim.StoreResultType_Def.schema + TClist = [ZSI.TCnumbers.Iinteger(pname=(ns,"PointsConsumed"), aname="_PointsConsumed", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._PointsConsumed = None + return + Holder.__name__ = "StoreResultType_Holder" + self.pyclass = Holder + + class AuthenticationFailedType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.OIM + type = (schema, "AuthenticationFailedType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = oim.AuthenticationFailedType_Def.schema + TClist = [ZSI.TC.AnyType(pname=(ns,"faultcode"), aname="_faultcode", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.HMNS.OIM,"detailType",lazy=False)(pname=(ns,"detail"), aname="_detail", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"faultstring"), aname="_faultstring", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"faultactor"), aname="_faultactor", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._faultcode = None + self._detail = None + self._faultstring = None + self._faultactor = None + return + Holder.__name__ = "AuthenticationFailedType_Holder" + self.pyclass = Holder + + class detailType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.OIM + type = (schema, "detailType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = oim.detailType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"TweenerChallenge"), aname="_TweenerChallenge", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"LockKeyChallenge"), aname="_LockKeyChallenge", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._TweenerChallenge = None + self._LockKeyChallenge = None + return + Holder.__name__ = "detailType_Holder" + self.pyclass = Holder + + class From_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "From" + schema = MSNS.HMNS.OIM + def __init__(self, **kw): + ns = oim.From_Dec.schema + TClist = [] + kw["pname"] = (MSNS.HMNS.OIM,"From") + kw["aname"] = "_From" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict["memberName"] = ZSI.TC.String() + self.attribute_typecode_dict["friendlyName"] = ZSI.TC.String() + self.attribute_typecode_dict[(NS.XMLNS.XML,"lang")] = ZSI.TC.AnyType() + self.attribute_typecode_dict["proxy"] = ZSI.TC.String() + self.attribute_typecode_dict["msnpVer"] = ZSI.TC.String() + self.attribute_typecode_dict["buildVer"] = ZSI.TC.String() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "From_Holder" + self.pyclass = Holder + + class To_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "To" + schema = MSNS.HMNS.OIM + def __init__(self, **kw): + ns = oim.To_Dec.schema + TClist = [] + kw["pname"] = (MSNS.HMNS.OIM,"To") + kw["aname"] = "_To" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict["memberName"] = ZSI.TC.String() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "To_Holder" + self.pyclass = Holder + + class Ticket_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "Ticket" + schema = MSNS.HMNS.OIM + def __init__(self, **kw): + ns = oim.Ticket_Dec.schema + TClist = [] + kw["pname"] = (MSNS.HMNS.OIM,"Ticket") + kw["aname"] = "_Ticket" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict["passport"] = ZSI.TC.String() + self.attribute_typecode_dict["appid"] = ZSI.TC.String() + self.attribute_typecode_dict["lockkey"] = ZSI.TC.String() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "Ticket_Holder" + self.pyclass = Holder + + class StoreResponse_Dec(ElementDeclaration): + literal = "StoreResponse" + schema = MSNS.HMNS.OIM + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.OIM,"StoreResponse") + kw["aname"] = "_StoreResponse" + if oim.StoreResultType_Def not in oim.StoreResponse_Dec.__bases__: + bases = list(oim.StoreResponse_Dec.__bases__) + bases.insert(0, oim.StoreResultType_Def) + oim.StoreResponse_Dec.__bases__ = tuple(bases) + + oim.StoreResultType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "StoreResponse_Dec_Holder" + + class AuthenticationFailed_Dec(ElementDeclaration): + literal = "AuthenticationFailed" + schema = MSNS.HMNS.OIM + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.OIM,"AuthenticationFailed") + kw["aname"] = "_AuthenticationFailed" + if oim.AuthenticationFailedType_Def not in oim.AuthenticationFailed_Dec.__bases__: + bases = list(oim.AuthenticationFailed_Dec.__bases__) + bases.insert(0, oim.AuthenticationFailedType_Def) + oim.AuthenticationFailed_Dec.__bases__ = tuple(bases) + + oim.AuthenticationFailedType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AuthenticationFailed_Dec_Holder" + + class MessageType_Dec(ZSI.TC.String, ElementDeclaration): + literal = "MessageType" + schema = MSNS.HMNS.OIM + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.OIM,"MessageType") + kw["aname"] = "_MessageType" + ZSI.TC.String.__init__(self, **kw) + class IHolder(str): typecode=self + self.pyclass = IHolder + IHolder.__name__ = "_MessageType_immutable_holder" + + class Content_Dec(ZSI.TC.String, ElementDeclaration): + literal = "Content" + schema = MSNS.HMNS.OIM + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.OIM,"Content") + kw["aname"] = "_Content" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_Content_immutable_holder" + ZSI.TC.String.__init__(self, **kw) + +# end class oim (tns: http://messenger.msn.com/ws/2004/09/oim/) + diff --git a/digsby/src/msn/SOAP/MSNOIMStoreService/__init__.py b/digsby/src/msn/SOAP/MSNOIMStoreService/__init__.py new file mode 100644 index 0000000..ef37efc --- /dev/null +++ b/digsby/src/msn/SOAP/MSNOIMStoreService/__init__.py @@ -0,0 +1,2 @@ +from OIMStoreService_client import * +from OIMStoreService_types import * diff --git a/digsby/src/msn/SOAP/MSNOIMStoreService/oim_servicetypes.xsd b/digsby/src/msn/SOAP/MSNOIMStoreService/oim_servicetypes.xsd new file mode 100644 index 0000000..550ae55 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNOIMStoreService/oim_servicetypes.xsd @@ -0,0 +1,68 @@ + + + + + + + + + + This field must be encoded according to RFC 2047. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNOIMStoreService/oim_ws.wsdl b/digsby/src/msn/SOAP/MSNOIMStoreService/oim_ws.wsdl new file mode 100644 index 0000000..d1c7da9 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNOIMStoreService/oim_ws.wsdl @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNOIMStoreService/utility.xsd b/digsby/src/msn/SOAP/MSNOIMStoreService/utility.xsd new file mode 100644 index 0000000..69f1261 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNOIMStoreService/utility.xsd @@ -0,0 +1,203 @@ + + + + + + +This type defines the fault code value for Timestamp message expiration. + + + + + + + + + +This type defines the fault code values for context-related faults. + + + + + + + + + + + + + +This global attribute supports annotating arbitrary elements with an ID. + + + + + + +This global attribute is used on extensions to distinguish mandatory vs. optional extensions. + + + + + + +Convenience attribute group used to simplify this schema. + + + + + + + + +This type is for elements whose [children] is a psuedo-dateTime and can have arbitrary attributes. + + + + + + + +This attribute indicates the actual schema type of the element [children]. +If the ValueType attribute is present, conforming processors must process the string value of [children] as if it were affiliated with the type indicated by this attribute. +If the ValueType attribute is absent, the implied value of this attribute is xsd:dateTime. + + + + + + + + + + +This type extends AnnotatedDateTime to add a Delay attribute. + + + + + + + +This attribute indicates the number of milliseconds that this actor processed this message. + + + + + + +This attribute indicates the intermediary that processed this message. + + + + + + + + + +This type is for elements whose [children] is an anyURI and can have arbitrary attributes. + + + + + + + + + + + +This complex type ties together the timestamp related elements into a composite type. + + + + + + + + + + + + + + + +This element allows Timestamps to be applied anywhere element wildcards are present, +including as a SOAP header. + + + + + + +This element allows an expiration time to be applied anywhere element wildcards are present. + + + + + + +This element allows a creation time to be applied anywhere element wildcards are present. + + + + + + +This element allows the ReceviedType to be applied anywhere element wildcards are present, including a Timestamp header. + + + + + + + +This type is the generic base type for context headers. + + + + + + + + + + + +This element allows Contexts to be applied anywhere element wildcards are present, +including as a SOAP header. + + + + + + + + + + + + + + + +This complex type defines a lightweight type for transmitting ports. + + + + + + + + + + + +This element allows port references to be applied anywhere element wildcards are present. + + + + diff --git a/digsby/src/msn/SOAP/MSNOIMStoreService/wsrm.xsd b/digsby/src/msn/SOAP/MSNOIMStoreService/wsrm.xsd new file mode 100644 index 0000000..6b66d96 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNOIMStoreService/wsrm.xsd @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNRSIService/RSIService_client.py b/digsby/src/msn/SOAP/MSNRSIService/RSIService_client.py new file mode 100644 index 0000000..f11c92d --- /dev/null +++ b/digsby/src/msn/SOAP/MSNRSIService/RSIService_client.py @@ -0,0 +1,86 @@ +################################################## +# file: RSIService_client.py +# +# client stubs generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +from RSIService_types import * +import urlparse, types +from ZSI.TCcompound import ComplexType, Struct +from ZSI import client +from ZSI.schema import GED, GTD +import ZSI + +import util.callbacks as callbacks +import util.network.soap as soap + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS, MSNBindingSOAP + +# Locator +class RSIServiceLocator: + GetMetadataPort_address = "https://rsi.hotmail.com/rsi/rsi.asmx" + def getGetMetadataPortAddress(self): + return RSIServiceLocator.GetMetadataPort_address + def getGetMetadataPort(self, url=None, **kw): + return RSIBindingSOAP(url or RSIServiceLocator.GetMetadataPort_address, **kw) + GetMessagePort_address = "https://rsi.hotmail.com/rsi/rsi.asmx" + def getGetMessagePortAddress(self): + return RSIServiceLocator.GetMessagePort_address + def getGetMessagePort(self, url=None, **kw): + return RSIBindingSOAP(url or RSIServiceLocator.GetMessagePort_address, **kw) + DeleteMessagesPort_address = "https://rsi.hotmail.com/rsi/rsi.asmx" + def getDeleteMessagesPortAddress(self): + return RSIServiceLocator.DeleteMessagesPort_address + def getDeleteMessagesPort(self, url=None, **kw): + return RSIBindingSOAP(url or RSIServiceLocator.DeleteMessagesPort_address, **kw) + +class RSIBinding(MSNBindingSOAP.BindingClass): + def get_default_headers(self): + headers = super(RSIBinding, self).get_default_headers() + headers['Content-Type'] = 'text/xml; charset="utf-8"' + return headers + +# Methods +class RSIBindingSOAP(MSNBindingSOAP): + BindingClass = RSIBinding + # op: GetMetadata + @callbacks.callsback + def GetMetadata(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, GetMetadataMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.Send(None, None, request, soapaction="http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata", soapheaders=soapheaders, + callback = callback, + **kw) + # op: GetMessage + @callbacks.callsback + def GetMessage(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, GetMessageMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.Send(None, None, request, soapaction="http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: DeleteMessages + @callbacks.callsback + def DeleteMessages(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, DeleteMessagesMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.Send(None, None, request, soapaction="http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages", soapheaders=soapheaders, + callback = callback, + **kw) + +GetMetadataMessage = GED(MSNS.HMNS.RSI, "GetMetadata").pyclass +GetMetadataResponseMessage = GED(MSNS.HMNS.RSI, "GetMetadataResponse").pyclass +GetMessageMessage = GED(MSNS.HMNS.RSI, "GetMessage").pyclass +GetMessageResponseMessage = GED(MSNS.HMNS.RSI, "GetMessageResponse").pyclass +DeleteMessagesMessage = GED(MSNS.HMNS.RSI, "DeleteMessages").pyclass +DeleteMessagesResponseMessage = GED(MSNS.HMNS.RSI, "DeleteMessagesResponse").pyclass diff --git a/digsby/src/msn/SOAP/MSNRSIService/RSIService_types.py b/digsby/src/msn/SOAP/MSNRSIService/RSIService_types.py new file mode 100644 index 0000000..2acf97f --- /dev/null +++ b/digsby/src/msn/SOAP/MSNRSIService/RSIService_types.py @@ -0,0 +1,382 @@ +################################################## +# file: RSIService_types.py +# +# schema types generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS + +############################## +# targetNamespace +# http://www.hotmail.msn.com/ws/2004/09/oim/rsi +############################## + +class rsi: + targetNamespace = MSNS.HMNS.RSI + + class MetadataMessage_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "MetadataMessage") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.MetadataMessage_Def.schema + TClist = [ZSI.TCnumbers.Iinteger(pname=(ns,"T"), aname="_T", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"S"), aname="_S", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCtimes.gDateTime(pname=(ns,"RT"), aname="_RT", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"RS"), aname="_RS", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"SZ"), aname="_SZ", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"E"), aname="_E", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"I"), aname="_I", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"F"), aname="_F", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"N"), aname="_N", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"SU"), aname="_SU", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._T = None + self._S = None + self._RT = None + self._RS = None + self._SZ = None + self._E = None + self._I = None + self._F = None + self._N = None + self._SU = None + return + Holder.__name__ = "MetadataMessage_Holder" + self.pyclass = Holder + + class MetaData_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "MetaData") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.MetaData_Def.schema + TClist = [GTD(MSNS.HMNS.RSI,"MetadataMessage",lazy=False)(pname=(ns,"M"), aname="_M", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.Q_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._M = [] + self._Q = None + return + Holder.__name__ = "MetaData_Holder" + self.pyclass = Holder + + + class Q_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Q" + schema = MSNS.HMNS.RSI + def __init__(self, **kw): + ns = rsi.MetaData_Def.Q_Dec.schema + TClist = [ZSI.TCnumbers.Iinteger(pname=(ns,"QTM"), aname="_QTM", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"QNM"), aname="_QNM", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.HMNS.RSI,"Q") + kw["aname"] = "_Q" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._QTM = None + self._QNM = None + return + Holder.__name__ = "Q_Holder" + self.pyclass = Holder + + + + + class GetMetadataRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "GetMetadataRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.GetMetadataRequestType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "GetMetadataRequestType_Holder" + self.pyclass = Holder + + class GetMetadataResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "GetMetadataResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.GetMetadataResponseType_Def.schema + TClist = [GTD(MSNS.HMNS.RSI,"MetaData",lazy=False)(pname=(ns,"MD"), aname="_MD", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._MD = None + return + Holder.__name__ = "GetMetadataResponseType_Holder" + self.pyclass = Holder + + class GetMessageRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "GetMessageRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.GetMessageRequestType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"messageId"), aname="_messageId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"alsoMarkAsRead"), aname="_alsoMarkAsRead", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._messageId = None + self._alsoMarkAsRead = None + return + Holder.__name__ = "GetMessageRequestType_Holder" + self.pyclass = Holder + + class GetMessageResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "GetMessageResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.GetMessageResponseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"GetMessageResult"), aname="_GetMessageResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._GetMessageResult = None + return + Holder.__name__ = "GetMessageResponseType_Holder" + self.pyclass = Holder + + class DeleteMessagesRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "DeleteMessagesRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.DeleteMessagesRequestType_Def.schema + TClist = [self.__class__.messageIds_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._messageIds = None + return + Holder.__name__ = "DeleteMessagesRequestType_Holder" + self.pyclass = Holder + + + class messageIds_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "messageIds" + schema = MSNS.HMNS.RSI + def __init__(self, **kw): + ns = rsi.DeleteMessagesRequestType_Def.messageIds_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"messageId"), aname="_messageId", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.HMNS.RSI,"messageIds") + kw["aname"] = "_messageIds" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._messageId = [] + return + Holder.__name__ = "messageIds_Holder" + self.pyclass = Holder + + + + + class DeleteMessagesResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.HMNS.RSI + type = (schema, "DeleteMessagesResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = rsi.DeleteMessagesResponseType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "DeleteMessagesResponseType_Holder" + self.pyclass = Holder + + class PassportCookie_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "PassportCookie" + schema = MSNS.HMNS.RSI + def __init__(self, **kw): + ns = rsi.PassportCookie_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"t"), aname="_t", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"p"), aname="_p", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.HMNS.RSI,"PassportCookie") + kw["aname"] = "_PassportCookie" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._t = None + self._p = None + return + Holder.__name__ = "PassportCookie_Holder" + self.pyclass = Holder + + class GetMetadata_Dec(ElementDeclaration): + literal = "GetMetadata" + schema = MSNS.HMNS.RSI + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.RSI,"GetMetadata") + kw["aname"] = "_GetMetadata" + if rsi.GetMetadataRequestType_Def not in rsi.GetMetadata_Dec.__bases__: + bases = list(rsi.GetMetadata_Dec.__bases__) + bases.insert(0, rsi.GetMetadataRequestType_Def) + rsi.GetMetadata_Dec.__bases__ = tuple(bases) + + rsi.GetMetadataRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetMetadata_Dec_Holder" + + class GetMetadataResponse_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "GetMetadataResponse" + schema = MSNS.HMNS.RSI + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.RSI,"GetMetadataResponse") + kw["aname"] = "_GetMetadataResponse" + ZSI.TC.AnyType.__init__(self, **kw) + + class GetMetadataResponse2_Dec(ElementDeclaration): + literal = "GetMetadataResponse2" + schema = MSNS.HMNS.RSI + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.RSI,"GetMetadataResponse2") + kw["aname"] = "_GetMetadataResponse2" + if rsi.GetMetadataResponseType_Def not in rsi.GetMetadataResponse2_Dec.__bases__: + bases = list(rsi.GetMetadataResponse2_Dec.__bases__) + bases.insert(0, rsi.GetMetadataResponseType_Def) + rsi.GetMetadataResponse2_Dec.__bases__ = tuple(bases) + + rsi.GetMetadataResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetMetadataResponse2_Dec_Holder" + + class GetMessage_Dec(ElementDeclaration): + literal = "GetMessage" + schema = MSNS.HMNS.RSI + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.RSI,"GetMessage") + kw["aname"] = "_GetMessage" + if rsi.GetMessageRequestType_Def not in rsi.GetMessage_Dec.__bases__: + bases = list(rsi.GetMessage_Dec.__bases__) + bases.insert(0, rsi.GetMessageRequestType_Def) + rsi.GetMessage_Dec.__bases__ = tuple(bases) + + rsi.GetMessageRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetMessage_Dec_Holder" + + class GetMessageResponse_Dec(ElementDeclaration): + literal = "GetMessageResponse" + schema = MSNS.HMNS.RSI + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.RSI,"GetMessageResponse") + kw["aname"] = "_GetMessageResponse" + if rsi.GetMessageResponseType_Def not in rsi.GetMessageResponse_Dec.__bases__: + bases = list(rsi.GetMessageResponse_Dec.__bases__) + bases.insert(0, rsi.GetMessageResponseType_Def) + rsi.GetMessageResponse_Dec.__bases__ = tuple(bases) + + rsi.GetMessageResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetMessageResponse_Dec_Holder" + + class DeleteMessages_Dec(ElementDeclaration): + literal = "DeleteMessages" + schema = MSNS.HMNS.RSI + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.RSI,"DeleteMessages") + kw["aname"] = "_DeleteMessages" + if rsi.DeleteMessagesRequestType_Def not in rsi.DeleteMessages_Dec.__bases__: + bases = list(rsi.DeleteMessages_Dec.__bases__) + bases.insert(0, rsi.DeleteMessagesRequestType_Def) + rsi.DeleteMessages_Dec.__bases__ = tuple(bases) + + rsi.DeleteMessagesRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DeleteMessages_Dec_Holder" + + class DeleteMessagesResponse_Dec(ElementDeclaration): + literal = "DeleteMessagesResponse" + schema = MSNS.HMNS.RSI + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.HMNS.RSI,"DeleteMessagesResponse") + kw["aname"] = "_DeleteMessagesResponse" + if rsi.DeleteMessagesResponseType_Def not in rsi.DeleteMessagesResponse_Dec.__bases__: + bases = list(rsi.DeleteMessagesResponse_Dec.__bases__) + bases.insert(0, rsi.DeleteMessagesResponseType_Def) + rsi.DeleteMessagesResponse_Dec.__bases__ = tuple(bases) + + rsi.DeleteMessagesResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DeleteMessagesResponse_Dec_Holder" + + class AuthenticationFailed_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "AuthenticationFailed" + schema = MSNS.HMNS.RSI + def __init__(self, **kw): + ns = rsi.AuthenticationFailed_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"TweenerChallenge"), aname="_TweenerChallenge", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.HMNS.RSI,"AuthenticationFailed") + kw["aname"] = "_AuthenticationFailed" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._TweenerChallenge = None + return + Holder.__name__ = "AuthenticationFailed_Holder" + self.pyclass = Holder + +# end class rsi (tns: http://www.hotmail.msn.com/ws/2004/09/oim/rsi) diff --git a/digsby/src/msn/SOAP/MSNRSIService/__init__.py b/digsby/src/msn/SOAP/MSNRSIService/__init__.py new file mode 100644 index 0000000..3288077 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNRSIService/__init__.py @@ -0,0 +1,2 @@ +from RSIService_types import * +from RSIService_client import * diff --git a/digsby/src/msn/SOAP/MSNRSIService/rsi_datatypes.xsd b/digsby/src/msn/SOAP/MSNRSIService/rsi_datatypes.xsd new file mode 100644 index 0000000..cff35a3 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNRSIService/rsi_datatypes.xsd @@ -0,0 +1,67 @@ + + + + + + + + 11 + + + + + 6 + + + + + The time the message was received. + + + + + Read set. + + + + + The size of the message. + + + + + The passport name of the sender. + + + + + The unique ID for this message. Seems to be a GUID. + + + + + Hotmail folder GUID. 000009 or .!!OIM + + + + + RFC 2047-encoded friendly name of the sender. + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNRSIService/rsi_faulttypes.xsd b/digsby/src/msn/SOAP/MSNRSIService/rsi_faulttypes.xsd new file mode 100644 index 0000000..1be76c3 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNRSIService/rsi_faulttypes.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNRSIService/rsi_servicetypes.xsd b/digsby/src/msn/SOAP/MSNRSIService/rsi_servicetypes.xsd new file mode 100644 index 0000000..7d74d33 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNRSIService/rsi_servicetypes.xsd @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNRSIService/rsi_ws.wsdl b/digsby/src/msn/SOAP/MSNRSIService/rsi_ws.wsdl new file mode 100644 index 0000000..2c0db3a --- /dev/null +++ b/digsby/src/msn/SOAP/MSNRSIService/rsi_ws.wsdl @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/WS-Trust.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/WS-Trust.xsd new file mode 100644 index 0000000..634a727 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/WS-Trust.xsd @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + Actual content model is non-deterministic, hence wildcard. The following shows intended content model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Actual content model is non-deterministic, hence wildcard. The following shows intended content model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/addressing-04-08.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/addressing-04-08.xsd new file mode 100644 index 0000000..996e1bd --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/addressing-04-08.xsd @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + If "Policy" elements from namespace "http://schemas.xmlsoap.org/ws/2002/12/policy#policy" are used, they must appear first (before any extensibility elements). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/addressing.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/addressing.xsd new file mode 100644 index 0000000..996e1bd --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/addressing.xsd @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + If "Policy" elements from namespace "http://schemas.xmlsoap.org/ws/2002/12/policy#policy" are used, they must appear first (before any extensibility elements). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/oasis-200401-wss-wssecurity-secext-1.0.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/oasis-200401-wss-wssecurity-secext-1.0.xsd new file mode 100644 index 0000000..7a3cb49 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/oasis-200401-wss-wssecurity-secext-1.0.xsd @@ -0,0 +1,196 @@ + + + + + + + + + + This type represents an element with arbitrary attributes. + + + + + + + + + + + This type is used for password elements per Section 4.1. + + + + + + + + + + This type is used for elements containing stringified binary data. + + + + + + + + + + This type represents a username token per Section 4.1 + + + + + + + + + + + A security token that is encoded in binary + + + + + + + + + + A security token key identifier + + + + + + + + + + Typedef to allow a list of usages (as URIs). + + + + + + This global attribute is used to indicate the usage of a referenced or indicated token within the containing context + + + + + This type represents a reference to an external security token. + + + + + + + + This type represents a reference to an embedded security token. + + + + + + + + + + This type is used reference a security token. + + + + + + + + + + + This complexType defines header block to use for security-relevant data directed at a specific SOAP actor. + + + + + The use of "any" is to allow extensibility and different forms of security data. + + + + + + + + This complexType defines a container for elements to be specified from any namespace as properties/parameters of a DSIG transformation. + + + + + The use of "any" is to allow extensibility from any namespace. + + + + + + + + This element defines the wsse:UsernameToken element per Section 4.1. + + + + + This element defines the wsse:BinarySecurityToken element per Section 4.2. + + + + + This element defines a security token reference + + + + + This element defines a security token embedded reference + + + + + This element defines a key identifier reference + + + + + This element defines the wsse:SecurityTokenReference per Section 4.3. + + + + + This element defines the wsse:Security SOAP header element per Section 4. + + + + + This element contains properties for transformations from any namespace, including DSIG. + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/oasis-200401-wss-wssecurity-utility-1.0.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/oasis-200401-wss-wssecurity-utility-1.0.xsd new file mode 100644 index 0000000..f8d74e9 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/oasis-200401-wss-wssecurity-utility-1.0.xsd @@ -0,0 +1,108 @@ + + + + + + + +This type defines the fault code value for Timestamp message expiration. + + + + + + + + + + +This global attribute supports annotating arbitrary elements with an ID. + + + + + + +Convenience attribute group used to simplify this schema. + + + + + + + + + +This type is for elements whose [children] is a psuedo-dateTime and can have arbitrary attributes. + + + + + + + + + + + +This type is for elements whose [children] is an anyURI and can have arbitrary attributes. + + + + + + + + + + + + +This complex type ties together the timestamp related elements into a composite type. + + + + + + + + + + + + + + +This element allows Timestamps to be applied anywhere element wildcards are present, +including as a SOAP header. + + + + + + + +This element allows an expiration time to be applied anywhere element wildcards are present. + + + + + + +This element allows a creation time to be applied anywhere element wildcards are present. + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/soap-env-03-05.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/soap-env-03-05.xsd new file mode 100644 index 0000000..85a6d8e --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/soap-env-03-05.xsd @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + Elements replacing the wildcard MUST be namespace qualified, but can be in the targetNamespace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/ws-policy.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/ws-policy.xsd new file mode 100644 index 0000000..e2e04d9 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/ws-policy.xsd @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/ws-secureconversation.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/ws-secureconversation.xsd new file mode 100644 index 0000000..7ac25b0 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/ws-secureconversation.xsd @@ -0,0 +1,111 @@ + + + + + + + + + + + Actual content model is non-deterministic, hence wildcard. The following shows intended content model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/xmldsig-core-schema.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/xmldsig-core-schema.xsd new file mode 100644 index 0000000..df126b3 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/Origin/xmldsig-core-schema.xsd @@ -0,0 +1,318 @@ + + + + + + ]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/SecurityTokenService_client.py b/digsby/src/msn/SOAP/MSNSecurityTokenService/SecurityTokenService_client.py new file mode 100644 index 0000000..f42a887 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/SecurityTokenService_client.py @@ -0,0 +1,62 @@ +################################################## +# file: SecurityTokenService_client.py +# +# client stubs generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +from SecurityTokenService_types import * +import urlparse, types +from ZSI.TCcompound import ComplexType, Struct +from ZSI import client +from ZSI.schema import GED, GTD +import ZSI + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS, MSNBindingSOAP + +import util.callbacks as callbacks +import util.network.soap as soap + +# Locator +class SecurityTokenServiceLocator: + SecurityTokenServicePort_address = "https://login.live.com/RST2.srf" + def getRequestSecurityTokenPortAddress(self): + return SecurityTokenServiceLocator.SecurityTokenServicePort_address + def getRequestSecurityTokenPort(self, url=None, **kw): + return SecurityTokenServicePortBindingSOAP(url or SecurityTokenServiceLocator.SecurityTokenServicePort_address, **kw) + def getRequestMultipleSecurityTokensPortAddress(self): + return SecurityTokenServiceLocator.SecurityTokenServicePort_address + def getRequestMultipleSecurityTokensPort(self, url=None, **kw): + return SecurityTokenServicePortBindingSOAP(url or SecurityTokenServiceLocator.SecurityTokenServicePort_address, **kw) + +# Methods +class SecurityTokenServicePortBindingSOAP(MSNBindingSOAP): + + # op: RequestMultipleSecurityTokens + @callbacks.callsback + def RequestMultipleSecurityTokens(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, RequestMultipleSecurityTokensMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction=kw.get('soapaction', ''), soapheaders=soapheaders, + callback = callback, + **kw) + + # op: RequestSecurityToken + @callbacks.callsback + def RequestSecurityToken(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, RequestSingleSecurityTokenMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction=kw.get('soapaction', ''), soapheaders=soapheaders, + callback = callback, + **kw) + +RequestMultipleSecurityTokensMessage = GED(MSNS.PPCRL.BASE, "RequestMultipleSecurityTokens").pyclass +RequestSecurityTokenResponseCollectionMessage = GED(NS.WSTRUST.BASE, "RequestSecurityTokenResponseCollection").pyclass +RequestSingleSecurityTokenMessage = GED(NS.WSTRUST.BASE, "RequestSecurityToken").pyclass +RequestSingleSecurityTokenResponseMessage = GED(NS.WSTRUST.BASE, "RequestSecurityTokenResponse").pyclass diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/SecurityTokenService_types.py b/digsby/src/msn/SOAP/MSNSecurityTokenService/SecurityTokenService_types.py new file mode 100644 index 0000000..2649b17 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/SecurityTokenService_types.py @@ -0,0 +1,105 @@ +################################################## +# file: SecurityTokenService_types.py +# +# schema types generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS + +############################## +# targetNamespace +# http://schemas.microsoft.com/Passport/SoapServices/PPCRL +############################## + +class ps: + targetNamespace = MSNS.PPCRL.BASE + + class RequestMultipleSecurityTokensType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.BASE + type = (schema, "RequestMultipleSecurityTokensType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ps.RequestMultipleSecurityTokensType_Def.schema + TClist = [GED(NS.WSTRUST.BASE,"RequestSecurityToken",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._RequestSecurityToken = None + return + Holder.__name__ = "RequestMultipleSecurityTokensType_Holder" + self.pyclass = Holder + + class AuthInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.BASE + type = (schema, "AuthInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ps.AuthInfoType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"HostingApp"), aname="_HostingApp", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"BinaryVersion"), aname="_BinaryVersion", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"UIVersion"), aname="_UIVersion", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Cookies"), aname="_Cookies", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"RequestParams"), aname="_RequestParams", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._HostingApp = None + self._BinaryVersion = None + self._UIVersion = None + self._Cookies = None + self._RequestParams = None + return + Holder.__name__ = "AuthInfoType_Holder" + self.pyclass = Holder + + class RequestMultipleSecurityTokens_Dec(ElementDeclaration): + literal = "RequestMultipleSecurityTokens" + schema = MSNS.PPCRL.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.BASE,"RequestMultipleSecurityTokens") + kw["aname"] = "_RequestMultipleSecurityTokens" + if ps.RequestMultipleSecurityTokensType_Def not in ps.RequestMultipleSecurityTokens_Dec.__bases__: + bases = list(ps.RequestMultipleSecurityTokens_Dec.__bases__) + bases.insert(0, ps.RequestMultipleSecurityTokensType_Def) + ps.RequestMultipleSecurityTokens_Dec.__bases__ = tuple(bases) + + ps.RequestMultipleSecurityTokensType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestMultipleSecurityTokens_Dec_Holder" + + class AuthInfo_Dec(ElementDeclaration): + literal = "AuthInfo" + schema = MSNS.PPCRL.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.BASE,"AuthInfo") + kw["aname"] = "_AuthInfo" + if ps.AuthInfoType_Def not in ps.AuthInfo_Dec.__bases__: + bases = list(ps.AuthInfo_Dec.__bases__) + bases.insert(0, ps.AuthInfoType_Def) + ps.AuthInfo_Dec.__bases__ = tuple(bases) + + ps.AuthInfoType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AuthInfo_Dec_Holder" + +# end class ps (tns: http://schemas.microsoft.com/Passport/SoapServices/PPCRL) diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/__init__.py b/digsby/src/msn/SOAP/MSNSecurityTokenService/__init__.py new file mode 100644 index 0000000..9930662 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/__init__.py @@ -0,0 +1,2 @@ +from SecurityTokenService_types import * +from SecurityTokenService_client import * diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/addressing-04-08.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/addressing-04-08.xsd new file mode 100644 index 0000000..996e1bd --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/addressing-04-08.xsd @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + If "Policy" elements from namespace "http://schemas.xmlsoap.org/ws/2002/12/policy#policy" are used, they must appear first (before any extensibility elements). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/addressing.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/addressing.xsd new file mode 100644 index 0000000..b359c84 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/addressing.xsd @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/ps-fault.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/ps-fault.xsd new file mode 100644 index 0000000..80b3f8d --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/ps-fault.xsd @@ -0,0 +1,120 @@ + + + + + + Comment describing your root element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/ps.wsdl b/digsby/src/msn/SOAP/MSNSecurityTokenService/ps.wsdl new file mode 100644 index 0000000..401a250 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/ps.wsdl @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/ps.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/ps.xsd new file mode 100644 index 0000000..a9c0643 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/ps.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/soap-env-03-05.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/soap-env-03-05.xsd new file mode 100644 index 0000000..2615a46 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/soap-env-03-05.xsd @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Elements replacing the wildcard MUST be namespace qualified, but can be in the targetNamespace + + + + + + + + + + + + + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/soap-env.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/soap-env.xsd new file mode 100644 index 0000000..e06a3e6 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/soap-env.xsd @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Prose in the spec does not specify that attributes are allowed on the Body element + + + + + + + + + + + + + + + + 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/sstc-saml-schema-assertion-1.1-cs.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/sstc-saml-schema-assertion-1.1-cs.xsd new file mode 100644 index 0000000..c20cb1c --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/sstc-saml-schema-assertion-1.1-cs.xsd @@ -0,0 +1,202 @@ + + + + + + + Document identifier: sstc-saml-schema-assertion-1.1-cs + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (November, 2002): + Initial standard schema. + V1.1 (May, 2003): + * Note that V1.1 of this schema has the same XML namespace as V1.0. + Rebased ID content directly on XML Schema types + Added DoNotCacheCondition element and DoNotCacheConditionType + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/sstc-saml-schema-protocol-1.1-cs.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/sstc-saml-schema-protocol-1.1-cs.xsd new file mode 100644 index 0000000..386c8b5 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/sstc-saml-schema-protocol-1.1-cs.xsd @@ -0,0 +1,133 @@ + + + + + + + + Document identifier: sstc-saml-schema-protocol-1.1-cs + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (November, 2002): + Initial standard schema. + V1.1 (May, 2003): + * Note that V1.1 of this schema has the same XML namespace as V1.0. + Rebased ID content directly on XML Schema types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-policy.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-policy.xsd new file mode 100644 index 0000000..5a09353 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-policy.xsd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-secext.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-secext.xsd new file mode 100644 index 0000000..e6e4685 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-secext.xsd @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This element defines a security token reference + + + + + This type represents a reference to an external security token. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-secureconversation.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-secureconversation.xsd new file mode 100644 index 0000000..7a47a9f --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-secureconversation.xsd @@ -0,0 +1,111 @@ + + + + + + + + + + + Actual content model is non-deterministic, hence wildcard. The following shows intended content model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-trust.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-trust.xsd new file mode 100644 index 0000000..b2425f2 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/ws-trust.xsd @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/wss-utility.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/wss-utility.xsd new file mode 100644 index 0000000..3c0d8f0 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/wss-utility.xsd @@ -0,0 +1,104 @@ + + + + + + + + +This type defines the fault code value for Timestamp message expiration. + + + + + + + + + + +This global attribute supports annotating arbitrary elements with an ID. + + + + + + +Convenience attribute group used to simplify this schema. + + + + + + + + + +This type is for elements whose [children] is a psuedo-dateTime and can have arbitrary attributes. + + + + + + + + + + + +This type is for elements whose [children] is an anyURI and can have arbitrary attributes. + + + + + + + + + + + + +This complex type ties together the timestamp related elements into a composite type. + + + + + + + + + + + + + + +This element allows Timestamps to be applied anywhere element wildcards are present, +including as a SOAP header. + + + + + + + +This element allows an expiration time to be applied anywhere element wildcards are present. + + + + + + +This element allows a creation time to be applied anywhere element wildcards are present. + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/xenc-schema.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/xenc-schema.xsd new file mode 100644 index 0000000..0c85bf2 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/xenc-schema.xsd @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/xml.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/xml.xsd new file mode 100644 index 0000000..ed2fac7 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/xml.xsd @@ -0,0 +1,288 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+
+
+ + + + +
+ +

lang (as an attribute name)

+

+ denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + +
+ + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + +
+ + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+
+ + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema . . .>
+           . . .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type . . .>
+           . . .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+
+
+
+
+ + + +
+

Versioning policy for this schema document

+
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
+ diff --git a/digsby/src/msn/SOAP/MSNSecurityTokenService/xmldsig-core-schema.xsd b/digsby/src/msn/SOAP/MSNSecurityTokenService/xmldsig-core-schema.xsd new file mode 100644 index 0000000..df126b3 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSecurityTokenService/xmldsig-core-schema.xsd @@ -0,0 +1,318 @@ + + + + + + ]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSpaceService/SpaceService_client.py b/digsby/src/msn/SOAP/MSNSpaceService/SpaceService_client.py new file mode 100644 index 0000000..d43c1ed --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSpaceService/SpaceService_client.py @@ -0,0 +1,45 @@ +################################################## +# file: SpaceService_client.py +# +# client stubs generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +from SpaceService_types import * +import urlparse, types +from ZSI.TCcompound import ComplexType, Struct +from ZSI import client +from ZSI.schema import GED, GTD +import ZSI + +import util.callbacks as callbacks +import util.network.soap as soap + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS, MSNBindingSOAP + +# Locator +class SpaceServiceLocator: + GetXmlFeedPort_address = "http://cc.services.spaces.live.com/contactcard/contactcardservice.asmx" + def getGetXmlFeedPortAddress(self): + return SpaceServiceLocator.GetXmlFeedPort_address + def getGetXmlFeedPort(self, url=None, **kw): + return SpaceServiceBindingSOAP(url or SpaceServiceLocator.GetXmlFeedPort_address, **kw) + +# Methods +class SpaceServiceBindingSOAP(MSNBindingSOAP): + + # op: GetXmlFeed + @callbacks.callsback + def GetXmlFeed(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, GetXmlFeedMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/spaces/v1/GetXmlFeed", soapheaders=soapheaders, + callback = callback, + **kw) + +GetXmlFeedMessage = GED(MSNS.MSWS.SPACES, "GetXmlFeed").pyclass +GetXmlFeedResponseMessage = GED(MSNS.MSWS.SPACES, "GetXmlFeedResponse").pyclass diff --git a/digsby/src/msn/SOAP/MSNSpaceService/SpaceService_types.py b/digsby/src/msn/SOAP/MSNSpaceService/SpaceService_types.py new file mode 100644 index 0000000..743d917 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSpaceService/SpaceService_types.py @@ -0,0 +1,504 @@ +################################################## +# file: SpaceService_types.py +# +# schema types generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS + +############################## +# targetNamespace +# http://www.msn.com/webservices/spaces/v1/ +############################## + +class spaces: + targetNamespace = MSNS.MSWS.SPACES + + class elementType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "elementType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.elementType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"title"), aname="_title", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"url"), aname="_url", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"totalNewItems"), aname="_totalNewItems", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"subelementBaseType",lazy=False)(pname=(ns,"subElement"), aname="_subElement", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["type"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._title = None + self._url = None + self._totalNewItems = None + self._subElement = [] + return + Holder.__name__ = "elementType_Holder" + self.pyclass = Holder + + class subelementBaseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "subelementBaseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.subelementBaseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"description"), aname="_description", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"tooltip"), aname="_tooltip", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"title"), aname="_title", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"url"), aname="_url", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["lastUpdated"] = ZSI.TCtimes.gDateTime() + self.attribute_typecode_dict["type"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._description = None + self._tooltip = None + self._title = None + self._url = None + return + Holder.__name__ = "subelementBaseType_Holder" + self.pyclass = Holder + + class elementsType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "elementsType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.elementsType_Def.schema + TClist = [GTD(MSNS.MSWS.SPACES,"elementType",lazy=False)(pname=(ns,"element"), aname="_element", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["totalMatches"] = ZSI.TCnumbers.Iinteger() + self.attribute_typecode_dict["returnedMatches"] = ZSI.TCnumbers.Iinteger() + self.attribute_typecode_dict["displayName"] = ZSI.TC.String() + self.attribute_typecode_dict["displayPictureUrl"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._element = [] + return + Holder.__name__ = "elementsType_Holder" + self.pyclass = Holder + + class themeType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "themeType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.themeType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"name"), aname="_name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"surfaceType",lazy=False)(pname=(ns,"titleBar"), aname="_titleBar", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"clientAreaType",lazy=False)(pname=(ns,"clientArea"), aname="_clientArea", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"surfaceType",lazy=False)(pname=(ns,"toolbar"), aname="_toolbar", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"borderType",lazy=False)(pname=(ns,"border"), aname="_border", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._name = None + self._titleBar = None + self._clientArea = None + self._toolbar = None + self._border = None + return + Holder.__name__ = "themeType_Holder" + self.pyclass = Holder + + class surfaceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "surfaceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.surfaceType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["background"] = ZSI.TC.String() + self.attribute_typecode_dict["foreground"] = ZSI.TC.String() + self.attribute_typecode_dict["fontFace"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "surfaceType_Holder" + self.pyclass = Holder + + class clientAreaType_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.SPACES + type = (schema, "clientAreaType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = spaces.clientAreaType_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["backgroundImage"] = ZSI.TC.String() + if spaces.surfaceType_Def not in spaces.clientAreaType_Def.__bases__: + bases = list(spaces.clientAreaType_Def.__bases__) + bases.insert(0, spaces.surfaceType_Def) + spaces.clientAreaType_Def.__bases__ = tuple(bases) + + spaces.surfaceType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class borderType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "borderType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.borderType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["topLeftImage"] = ZSI.TC.String() + self.attribute_typecode_dict["bottomLeftImage"] = ZSI.TC.String() + self.attribute_typecode_dict["topRightImage"] = ZSI.TC.String() + self.attribute_typecode_dict["bottomRightImage"] = ZSI.TC.String() + self.attribute_typecode_dict["outline"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "borderType_Holder" + self.pyclass = Holder + + class liveThemeType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "liveThemeType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.liveThemeType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"themeName"), aname="_themeName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"headType",lazy=False)(pname=(ns,"head"), aname="_head", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"bodyType",lazy=False)(pname=(ns,"body"), aname="_body", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"linkType",lazy=False)(pname=(ns,"actions"), aname="_actions", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._themeName = None + self._head = None + self._body = None + self._actions = None + return + Holder.__name__ = "liveThemeType_Holder" + self.pyclass = Holder + + class linkType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "linkType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.linkType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["linkColor"] = ZSI.TC.String() + self.attribute_typecode_dict["backgroundColor"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "linkType_Holder" + self.pyclass = Holder + + class linkTextType_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.SPACES + type = (schema, "linkTextType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = spaces.linkTextType_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["textColor"] = ZSI.TC.String() + if spaces.linkType_Def not in spaces.linkTextType_Def.__bases__: + bases = list(spaces.linkTextType_Def.__bases__) + bases.insert(0, spaces.linkType_Def) + spaces.linkTextType_Def.__bases__ = tuple(bases) + + spaces.linkType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class headType_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.SPACES + type = (schema, "headType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = spaces.headType_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["backgroundImage"] = ZSI.TC.String() + if spaces.linkTextType_Def not in spaces.headType_Def.__bases__: + bases = list(spaces.headType_Def.__bases__) + bases.insert(0, spaces.linkTextType_Def) + spaces.headType_Def.__bases__ = tuple(bases) + + spaces.linkTextType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class bodyType_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.SPACES + type = (schema, "bodyType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = spaces.bodyType_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["secondaryLinkColor"] = ZSI.TC.String() + self.attribute_typecode_dict["dividerColor"] = ZSI.TC.String() + self.attribute_typecode_dict["accordionHoverColor"] = ZSI.TC.String() + if spaces.headType_Def not in spaces.bodyType_Def.__bases__: + bases = list(spaces.bodyType_Def.__bases__) + bases.insert(0, spaces.headType_Def) + spaces.bodyType_Def.__bases__ = tuple(bases) + + spaces.headType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class refreshInformationType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "refreshInformationType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.refreshInformationType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"cid"), aname="_cid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"storageAuthCache"), aname="_storageAuthCache", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.AnyType(pname=(ns,"market"), aname="_market", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"brand"), aname="_brand", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iinteger(pname=(ns,"maxElementCount"), aname="_maxElementCount", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iinteger(pname=(ns,"maxCharacterCount"), aname="_maxCharacterCount", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iinteger(pname=(ns,"maxImageCount"), aname="_maxImageCount", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"applicationId"), aname="_applicationId", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"updateAccessedTime"), aname="_updateAccessedTime", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCtimes.gDateTime(pname=(ns,"spaceLastViewed"), aname="_spaceLastViewed", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"isActiveContact"), aname="_isActiveContact", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCtimes.gDateTime(pname=(ns,"profileLastViewed"), aname="_profileLastViewed", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCtimes.gDateTime(pname=(ns,"contactProfileLastViewed"), aname="_contactProfileLastViewed", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCtimes.gDateTime(pname=(ns,"activeContactLastChanged"), aname="_activeContactLastChanged", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._cid = None + self._storageAuthCache = None + self._market = None + self._brand = None + self._maxElementCount = None + self._maxCharacterCount = None + self._maxImageCount = None + self._applicationId = None + self._updateAccessedTime = None + self._spaceLastViewed = None + self._isActiveContact = None + self._profileLastViewed = None + self._contactProfileLastViewed = None + self._activeContactLastChanged = None + return + Holder.__name__ = "refreshInformationType_Holder" + self.pyclass = Holder + + class contactCardType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "contactCardType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.contactCardType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"storageAuthCache"), aname="_storageAuthCache", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"elementsType",lazy=False)(pname=(ns,"elements"), aname="_elements", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"themeType",lazy=False)(pname=(ns,"theme"), aname="_theme", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.SPACES,"liveThemeType",lazy=False)(pname=(ns,"liveTheme"), aname="_liveTheme", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCtimes.gDateTime(pname=(ns,"lastUpdate"), aname="_lastUpdate", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._storageAuthCache = None + self._elements = None + self._theme = None + self._liveTheme = None + self._lastUpdate = None + return + Holder.__name__ = "contactCardType_Holder" + self.pyclass = Holder + + class spaceContactCardElementsElementPhotoSubElement_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.SPACES + type = (schema, "spaceContactCardElementsElementPhotoSubElement") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = spaces.spaceContactCardElementsElementPhotoSubElement_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"thumbnailUrl"), aname="_thumbnailUrl", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"webReadyUrl"), aname="_webReadyUrl", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"albumName"), aname="_albumName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if spaces.subelementBaseType_Def not in spaces.spaceContactCardElementsElementPhotoSubElement_Def.__bases__: + bases = list(spaces.spaceContactCardElementsElementPhotoSubElement_Def.__bases__) + bases.insert(0, spaces.subelementBaseType_Def) + spaces.spaceContactCardElementsElementPhotoSubElement_Def.__bases__ = tuple(bases) + + spaces.subelementBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class GetXmlFeedResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "GetXmlFeedResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.GetXmlFeedResultType_Def.schema + TClist = [GTD(MSNS.MSWS.SPACES,"contactCardType",lazy=False)(pname=(ns,"contactCard"), aname="_contactCard", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._contactCard = None + return + Holder.__name__ = "GetXmlFeedResultType_Holder" + self.pyclass = Holder + + class GetXmlFeedRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "GetXmlFeedRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.GetXmlFeedRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.SPACES,"refreshInformationType",lazy=False)(pname=(ns,"refreshInformation"), aname="_refreshInformation", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._refreshInformation = None + return + Holder.__name__ = "GetXmlFeedRequestType_Holder" + self.pyclass = Holder + + class GetXmlFeedResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.SPACES + type = (schema, "GetXmlFeedResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = spaces.GetXmlFeedResponseType_Def.schema + TClist = [GTD(MSNS.MSWS.SPACES,"GetXmlFeedResultType",lazy=False)(pname=(ns,"GetXmlFeedResult"), aname="_GetXmlFeedResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._GetXmlFeedResult = None + return + Holder.__name__ = "GetXmlFeedResponseType_Holder" + self.pyclass = Holder + + class AuthTokenHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "AuthTokenHeader" + schema = MSNS.MSWS.SPACES + def __init__(self, **kw): + ns = spaces.AuthTokenHeader_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"Token"), aname="_Token", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.SPACES,"AuthTokenHeader") + kw["aname"] = "_AuthTokenHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Token = None + return + Holder.__name__ = "AuthTokenHeader_Holder" + self.pyclass = Holder + + class GetXmlFeed_Dec(ElementDeclaration): + literal = "GetXmlFeed" + schema = MSNS.MSWS.SPACES + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.SPACES,"GetXmlFeed") + kw["aname"] = "_GetXmlFeed" + if spaces.GetXmlFeedRequestType_Def not in spaces.GetXmlFeed_Dec.__bases__: + bases = list(spaces.GetXmlFeed_Dec.__bases__) + bases.insert(0, spaces.GetXmlFeedRequestType_Def) + spaces.GetXmlFeed_Dec.__bases__ = tuple(bases) + + spaces.GetXmlFeedRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetXmlFeed_Dec_Holder" + + class GetXmlFeedResponse_Dec(ElementDeclaration): + literal = "GetXmlFeedResponse" + schema = MSNS.MSWS.SPACES + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.SPACES,"GetXmlFeedResponse") + kw["aname"] = "_GetXmlFeedResponse" + if spaces.GetXmlFeedResponseType_Def not in spaces.GetXmlFeedResponse_Dec.__bases__: + bases = list(spaces.GetXmlFeedResponse_Dec.__bases__) + bases.insert(0, spaces.GetXmlFeedResponseType_Def) + spaces.GetXmlFeedResponse_Dec.__bases__ = tuple(bases) + + spaces.GetXmlFeedResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetXmlFeedResponse_Dec_Holder" + +# end class spaces (tns: http://www.msn.com/webservices/spaces/v1/) + diff --git a/digsby/src/msn/SOAP/MSNSpaceService/__init__.py b/digsby/src/msn/SOAP/MSNSpaceService/__init__.py new file mode 100644 index 0000000..f3695c5 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSpaceService/__init__.py @@ -0,0 +1,2 @@ +from SpaceService_types import * +from SpaceService_client import * diff --git a/digsby/src/msn/SOAP/MSNSpaceService/space_ws.wsdl b/digsby/src/msn/SOAP/MSNSpaceService/space_ws.wsdl new file mode 100644 index 0000000..a572f46 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSpaceService/space_ws.wsdl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSpaceService/space_ws_datatype.xsd b/digsby/src/msn/SOAP/MSNSpaceService/space_ws_datatype.xsd new file mode 100644 index 0000000..56191f0 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSpaceService/space_ws_datatype.xsd @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNSpaceService/space_ws_servicetype.xsd b/digsby/src/msn/SOAP/MSNSpaceService/space_ws_servicetype.xsd new file mode 100644 index 0000000..4596157 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNSpaceService/space_ws_servicetype.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNStorageService/StorageService_client.py b/digsby/src/msn/SOAP/MSNStorageService/StorageService_client.py new file mode 100644 index 0000000..011c68d --- /dev/null +++ b/digsby/src/msn/SOAP/MSNStorageService/StorageService_client.py @@ -0,0 +1,188 @@ +################################################## +# file: StorageService_client.py +# +# client stubs generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +from StorageService_types import * +import urlparse, types +from ZSI.TCcompound import ComplexType, Struct +from ZSI import client +from ZSI.schema import GED, GTD +import ZSI + +import util.callbacks as callbacks +import util.network.soap as soap +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS, MSNBindingSOAP + +# Locator +class StorageServiceLocator: + GetProfilePort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getGetProfilePortAddress(self): + return StorageServiceLocator.GetProfilePort_address + def getGetProfilePort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.GetProfilePort_address, **kw) + UpdateProfilePort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getUpdateProfilePortAddress(self): + return StorageServiceLocator.UpdateProfilePort_address + def getUpdateProfilePort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.UpdateProfilePort_address, **kw) + FindDocumentsPort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getFindDocumentsPortAddress(self): + return StorageServiceLocator.FindDocumentsPort_address + def getFindDocumentsPort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.FindDocumentsPort_address, **kw) + CreateProfilePort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getCreateProfilePortAddress(self): + return StorageServiceLocator.CreateProfilePort_address + def getCreateProfilePort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.CreateProfilePort_address, **kw) + ShareItemPort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getShareItemPortAddress(self): + return StorageServiceLocator.ShareItemPort_address + def getShareItemPort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.ShareItemPort_address, **kw) + CreateDocumentPort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getCreateDocumentPortAddress(self): + return StorageServiceLocator.CreateDocumentPort_address + def getCreateDocumentPort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.CreateDocumentPort_address, **kw) + UpdateDocumentPort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getUpdateDocumentPortAddress(self): + return StorageServiceLocator.UpdateDocumentPort_address + def getUpdateDocumentPort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.UpdateDocumentPort_address, **kw) + CreateRelationshipsPort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getCreateRelationshipsPortAddress(self): + return StorageServiceLocator.CreateRelationshipsPort_address + def getCreateRelationshipsPort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.CreateRelationshipsPort_address, **kw) + DeleteRelationshipsPort_address = "https://storage.msn.com/storageservice/SchematizedStore.asmx" + def getDeleteRelationshipsPortAddress(self): + return StorageServiceLocator.DeleteRelationshipsPort_address + def getDeleteRelationshipsPort(self, url=None, **kw): + return StorageServiceBindingSOAP(url or StorageServiceLocator.DeleteRelationshipsPort_address, **kw) + +# Methods +class StorageServiceBindingSOAP(MSNBindingSOAP): + # op: GetProfile + @callbacks.callsback + def GetProfile(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, GetProfileMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/GetProfile", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: UpdateProfile + @callbacks.callsback + def UpdateProfile(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, UpdateProfileMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/UpdateProfile", soapheaders=soapheaders, + callback = callback, + **kw) + + + # op: FindDocuments + @callbacks.callsback + def FindDocuments(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, FindDocumentsMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/FindDocuments", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: CreateProfile + @callbacks.callsback + def CreateProfile(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, CreateProfileMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/CreateProfile", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: ShareItem + @callbacks.callsback + def ShareItem(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, ShareItemMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/ShareItem", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: CreateDocument + @callbacks.callsback + def CreateDocument(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, CreateDocumentMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/CreateDocument", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: UpdateDocument + @callbacks.callsback + def UpdateDocument(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, UpdateDocumentMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/UpdateDocument", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: CreateRelationships + @callbacks.callsback + def CreateRelationships(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, CreateRelationshipsMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/CreateRelationships", soapheaders=soapheaders, + callback = callback, + **kw) + + # op: DeleteRelationships + @callbacks.callsback + def DeleteRelationships(self, request, soapheaders=(), callback = None, **kw): + if isinstance(request, DeleteRelationshipsMessage) is False: + raise TypeError, "%s incorrect request type" % (request.__class__) + # no input wsaction + # TODO: Check soapheaders + self.binding.RPC(None, None, request, soapaction="http://www.msn.com/webservices/storage/2008/DeleteRelationships", soapheaders=soapheaders, + callback = callback, + **kw) + +GetProfileMessage = GED(MSNS.MSWS.STORAGE, "GetProfile").pyclass +GetProfileResponseMessage = GED(MSNS.MSWS.STORAGE, "GetProfileResponse").pyclass +UpdateProfileMessage = GED(MSNS.MSWS.STORAGE, "UpdateProfile").pyclass +UpdateProfileResponseMessage = GED(MSNS.MSWS.STORAGE, "UpdateProfileResponse").pyclass +FindDocumentsMessage = GED(MSNS.MSWS.STORAGE, "FindDocuments").pyclass +FindDocumentsResponseMessage = GED(MSNS.MSWS.STORAGE, "FindDocumentsResponse").pyclass +CreateProfileMessage = GED(MSNS.MSWS.STORAGE, "CreateProfile").pyclass +CreateProfileResponseMessage = GED(MSNS.MSWS.STORAGE, "CreateProfileResponse").pyclass +ShareItemMessage = GED(MSNS.MSWS.STORAGE, "ShareItem").pyclass +ShareItemResponseMessage = GED(MSNS.MSWS.STORAGE, "ShareItemResponse").pyclass +CreateDocumentMessage = GED(MSNS.MSWS.STORAGE, "CreateDocument").pyclass +CreateDocumentResponseMessage = GED(MSNS.MSWS.STORAGE, "CreateDocumentResponse").pyclass +UpdateDocumentMessage = GED(MSNS.MSWS.STORAGE, "UpdateDocument").pyclass +UpdateDocumentResponseMessage = GED(MSNS.MSWS.STORAGE, "UpdateDocumentResponse").pyclass +CreateRelationshipsMessage = GED(MSNS.MSWS.STORAGE, "CreateRelationships").pyclass +CreateRelationshipsResponseMessage = GED(MSNS.MSWS.STORAGE, "CreateRelationshipsResponse").pyclass +DeleteRelationshipsMessage = GED(MSNS.MSWS.STORAGE, "DeleteRelationships").pyclass +DeleteRelationshipsResponseMessage = GED(MSNS.MSWS.STORAGE, "DeleteRelationshipsResponse").pyclass diff --git a/digsby/src/msn/SOAP/MSNStorageService/StorageService_types.py b/digsby/src/msn/SOAP/MSNStorageService/StorageService_types.py new file mode 100644 index 0000000..30c4bca --- /dev/null +++ b/digsby/src/msn/SOAP/MSNStorageService/StorageService_types.py @@ -0,0 +1,1221 @@ +################################################## +# file: StorageService_types.py +# +# schema types generated by "ZSI.generate.wsdl2python.WriteServiceModule" +# D:\workspace\digsby\Digsby.py --no-traceback-dialog --multi --server=api5.digsby.org +# +################################################## + +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS + +############################## +# targetNamespace +# http://www.msn.com/webservices/storage/2008 +############################## + +class msnss: + targetNamespace = MSNS.MSWS.STORAGE + + class Alias_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "Alias") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.Alias_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"Name"), aname="_Name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"NameSpace"), aname="_NameSpace", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Name = None + self._NameSpace = None + return + Holder.__name__ = "Alias_Holder" + self.pyclass = Holder + + class Handle_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "Handle") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.Handle_Def.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"Alias",lazy=False)(pname=(ns,"Alias"), aname="_Alias", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"RelationshipName"), aname="_RelationshipName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Alias = None + self._RelationshipName = None + self._ResourceID = None + return + Holder.__name__ = "Handle_Holder" + self.pyclass = Holder + + class profileAttributes_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "profileAttributes") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.profileAttributes_Def.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"DateModified"), aname="_DateModified", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.STORAGE,"ExpressionProfileAttributesType",lazy=False)(pname=(ns,"ExpressionProfileAttributes"), aname="_ExpressionProfileAttributes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._DateModified = None + self._ExpressionProfileAttributes = None + return + Holder.__name__ = "profileAttributes_Holder" + self.pyclass = Holder + + class DocumentStream_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "DocumentStream") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.DocumentStream_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"DocumentStreamName"), aname="_DocumentStreamName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"MimeType"), aname="_MimeType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Base64String(pname=(ns,"Data"), aname="_Data", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"DataSize"), aname="_DataSize", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.URI(pname=(ns,"PreAuthURL"), aname="_PreAuthURL", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.URI(pname=(ns,"PreAuthURLPartner"), aname="_PreAuthURLPartner", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DocumentStreamType"), aname="_DocumentStreamType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"WriteMode"), aname="_WriteMode", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"StreamVersion"), aname="_StreamVersion", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Base64String(pname=(ns,"SHA1Hash"), aname="_SHA1Hash", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"Genie"), aname="_Genie", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DocumentStreamName = None + self._MimeType = None + self._Data = None + self._DataSize = None + self._PreAuthURL = None + self._PreAuthURLPartner = None + self._DocumentStreamType = None + self._WriteMode = None + self._StreamVersion = None + self._SHA1Hash = None + self._Genie = None + return + Holder.__name__ = "DocumentStream_Holder" + self.pyclass = Holder + + class PhotoStream_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.STORAGE + type = (schema, "PhotoStream") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnss.PhotoStream_Def.schema + TClist = [ZSI.TCnumbers.Iint(pname=(ns,"SizeX"), aname="_SizeX", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"SizeY"), aname="_SizeY", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnss.DocumentStream_Def not in msnss.PhotoStream_Def.__bases__: + bases = list(msnss.PhotoStream_Def.__bases__) + bases.insert(0, msnss.DocumentStream_Def) + msnss.PhotoStream_Def.__bases__ = tuple(bases) + + msnss.DocumentStream_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class Relationship_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "Relationship") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.Relationship_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"SourceID"), aname="_SourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"SourceType"), aname="_SourceType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"TargetID"), aname="_TargetID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"TargetType"), aname="_TargetType", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"RelationshipName"), aname="_RelationshipName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SourceID = None + self._SourceType = None + self._TargetID = None + self._TargetType = None + self._RelationshipName = None + return + Holder.__name__ = "Relationship_Holder" + self.pyclass = Holder + + class ExpressionProfileAttributesType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "ExpressionProfileAttributesType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.ExpressionProfileAttributesType_Def.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"DateModified"), aname="_DateModified", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"DisplayNameLastModified"), aname="_DisplayNameLastModified", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"PersonalStatus"), aname="_PersonalStatus", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"PersonalStatusLastModified"), aname="_PersonalStatusLastModified", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"StaticUserTilePublicURL"), aname="_StaticUserTilePublicURL", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"Photo"), aname="_Photo", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"Attachments"), aname="_Attachments", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"Flag"), aname="_Flag", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._DateModified = None + self._DisplayName = None + self._DisplayNameLastModified = None + self._PersonalStatus = None + self._PersonalStatusLastModified = None + self._StaticUserTilePublicURL = None + self._Photo = None + self._Attachments = None + self._Flag = None + return + Holder.__name__ = "ExpressionProfileAttributesType_Holder" + self.pyclass = Holder + + class documentBaseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "documentBaseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.documentBaseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"Name"), aname="_Name", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"ItemType"), aname="_ItemType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DateModified"), aname="_DateModified", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.DocumentStreams_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._Name = None + self._ItemType = None + self._DateModified = None + self._DocumentStreams = None + return + Holder.__name__ = "documentBaseType_Holder" + self.pyclass = Holder + + + class DocumentStreams_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "DocumentStreams" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.documentBaseType_Def.DocumentStreams_Dec.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"DocumentStream",lazy=False)(pname=(ns,"DocumentStream"), aname="_DocumentStream", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"DocumentStreams") + kw["aname"] = "_DocumentStreams" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DocumentStream = [] + return + Holder.__name__ = "DocumentStreams_Holder" + self.pyclass = Holder + + + + + class Photo_Def(TypeDefinition): + #complexType/complexContent extension + schema = MSNS.MSWS.STORAGE + type = (schema, "Photo") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = msnss.Photo_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if msnss.documentBaseType_Def not in msnss.Photo_Def.__bases__: + bases = list(msnss.Photo_Def.__bases__) + bases.insert(0, msnss.documentBaseType_Def) + msnss.Photo_Def.__bases__ = tuple(bases) + + msnss.documentBaseType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class ExpressionProfile_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "ExpressionProfile") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.ExpressionProfile_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"FreeText"), aname="_FreeText", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"PersonalStatus"), aname="_PersonalStatus", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"Flags"), aname="_Flags", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"RoleDefinitionName"), aname="_RoleDefinitionName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._FreeText = None + self._DisplayName = None + self._PersonalStatus = None + self._Flags = None + self._RoleDefinitionName = None + return + Holder.__name__ = "ExpressionProfile_Holder" + self.pyclass = Holder + + class GetProfileRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "GetProfileRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.GetProfileRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"Handle",lazy=False)(pname=(ns,"profileHandle"), aname="_profileHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.STORAGE,"profileAttributes",lazy=False)(pname=(ns,"profileAttributes"), aname="_profileAttributes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._profileHandle = None + self._profileAttributes = None + return + Holder.__name__ = "GetProfileRequestType_Holder" + self.pyclass = Holder + + class GetProfileResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "GetProfileResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.GetProfileResultType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DateModified"), aname="_DateModified", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.ExpressionProfile_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._DateModified = None + self._ExpressionProfile = None + return + Holder.__name__ = "GetProfileResultType_Holder" + self.pyclass = Holder + + + class ExpressionProfile_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "ExpressionProfile" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.GetProfileResultType_Def.ExpressionProfile_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DateModified"), aname="_DateModified", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"Version"), aname="_Version", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"Flags"), aname="_Flags", minOccurs=0, maxOccurs=1, nillable=True, typed=False, encoded=kw.get("encoded")), + GTD(MSNS.MSWS.STORAGE,"documentBaseType",lazy=False)(pname=(ns,"Photo"), aname="_Photo", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.Attachments_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"PersonalStatus"), aname="_PersonalStatus", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"PersonalStatusLastModified"), aname="_PersonalStatusLastModified", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DisplayName"), aname="_DisplayName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.String(pname=(ns,"DisplayNameLastModified"), aname="_DisplayNameLastModified", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.URI(pname=(ns,"StaticUserTilePublicURL"), aname="_StaticUserTilePublicURL", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + + kw["pname"] = (MSNS.MSWS.STORAGE,"ExpressionProfile") + kw["aname"] = "_ExpressionProfile" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._DateModified = None + self._Version = None + self._Flags = None + self._Photo = None + self._Attachments = None + self._PersonalStatus = None + self._PersonalStatusLastModified = None + self._DisplayName = None + self._DisplayNameLastModified = None + self._StaticUserTilePublicURL = None + return + Holder.__name__ = "ExpressionProfile_Holder" + self.pyclass = Holder + + + class Attachments_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Attachments" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.GetProfileResultType_Def.ExpressionProfile_Dec.Attachments_Dec.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"documentBaseType",lazy=False)(pname=(ns,"Document"), aname="_Document", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"Attachments") + kw["aname"] = "_Attachments" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Document = [] + return + Holder.__name__ = "Attachments_Holder" + self.pyclass = Holder + + + + + + + + class UpdateProfileRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "UpdateProfileRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.UpdateProfileRequestType_Def.schema + TClist = [self.__class__.profile_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), self.__class__.profileAttributesToDelete_Dec(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._profile = None + self._profileAttributesToDelete = None + return + Holder.__name__ = "UpdateProfileRequestType_Holder" + self.pyclass = Holder + + + class profile_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "profile" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.UpdateProfileRequestType_Def.profile_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.STORAGE,"ExpressionProfile",lazy=False)(pname=(ns,"ExpressionProfile"), aname="_ExpressionProfile", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"profile") + kw["aname"] = "_profile" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._ExpressionProfile = None + return + Holder.__name__ = "profile_Holder" + self.pyclass = Holder + + + + + + class profileAttributesToDelete_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "profileAttributesToDelete" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.UpdateProfileRequestType_Def.profileAttributesToDelete_Dec.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"ExpressionProfileAttributesType",lazy=False)(pname=(ns,"ExpressionProfileAttributes"), aname="_ExpressionProfileAttributes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"profileAttributesToDelete") + kw["aname"] = "_profileAttributesToDelete" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ExpressionProfileAttributes = None + return + Holder.__name__ = "profileAttributesToDelete_Holder" + self.pyclass = Holder + + + + + class FindDocumentsRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "FindDocumentsRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.FindDocumentsRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"Handle",lazy=False)(pname=(ns,"objectHandle"), aname="_objectHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + self.__class__.documentAttributes_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.documentFilter_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.documentSort_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + self.__class__.findContext_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._objectHandle = None + self._documentAttributes = None + self._documentFilter = None + self._documentSort = None + self._findContext = None + return + Holder.__name__ = "FindDocumentsRequestType_Holder" + self.pyclass = Holder + + + class documentAttributes_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "documentAttributes" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.FindDocumentsRequestType_Def.documentAttributes_Dec.schema + TClist = [ZSI.TC.Boolean(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Boolean(pname=(ns,"Name"), aname="_Name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"documentAttributes") + kw["aname"] = "_documentAttributes" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._Name = None + return + Holder.__name__ = "documentAttributes_Holder" + self.pyclass = Holder + + + + + + class documentFilter_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "documentFilter" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.FindDocumentsRequestType_Def.documentFilter_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"FilterAttributes"), aname="_FilterAttributes", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"documentFilter") + kw["aname"] = "_documentFilter" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._FilterAttributes = None + return + Holder.__name__ = "documentFilter_Holder" + self.pyclass = Holder + + + + + + class documentSort_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "documentSort" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.FindDocumentsRequestType_Def.documentSort_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"SortBy"), aname="_SortBy", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"documentSort") + kw["aname"] = "_documentSort" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SortBy = None + return + Holder.__name__ = "documentSort_Holder" + self.pyclass = Holder + + + + + + class findContext_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "findContext" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.FindDocumentsRequestType_Def.findContext_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"FindMethod"), aname="_FindMethod", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TCnumbers.Iint(pname=(ns,"ChunkSize"), aname="_ChunkSize", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"findContext") + kw["aname"] = "_findContext" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._FindMethod = None + self._ChunkSize = None + return + Holder.__name__ = "findContext_Holder" + self.pyclass = Holder + + + + + class FindDocumentsResultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "FindDocumentsResultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.FindDocumentsResultType_Def.schema + TClist = [self.__class__.Document_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Document = None + return + Holder.__name__ = "FindDocumentsResultType_Holder" + self.pyclass = Holder + + + class Document_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "Document" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.FindDocumentsResultType_Def.Document_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"ResourceID"), aname="_ResourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Name"), aname="_Name", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"Document") + kw["aname"] = "_Document" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ResourceID = None + self._Name = None + return + Holder.__name__ = "Document_Holder" + self.pyclass = Holder + + + + + class CreateProfileRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "CreateProfileRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.CreateProfileRequestType_Def.schema + TClist = [self.__class__.profile_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._profile = None + return + Holder.__name__ = "CreateProfileRequestType_Holder" + self.pyclass = Holder + + + class profile_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "profile" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.CreateProfileRequestType_Def.profile_Dec.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"ExpressionProfile",lazy=False)(pname=(ns,"ExpressionProfile"), aname="_ExpressionProfile", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"profile") + kw["aname"] = "_profile" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ExpressionProfile = None + return + Holder.__name__ = "profile_Holder" + self.pyclass = Holder + + + + + class ShareItemRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "ShareItemRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.ShareItemRequestType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"resourceID"), aname="_resourceID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"displayName"), aname="_displayName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._resourceID = None + self._displayName = None + return + Holder.__name__ = "ShareItemRequestType_Holder" + self.pyclass = Holder + + class ShareItemResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "ShareItemResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.ShareItemResponseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"ShareItemResult"), aname="_ShareItemResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ShareItemResult = None + return + Holder.__name__ = "ShareItemResponseType_Holder" + self.pyclass = Holder + + class UpdateDocumentRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "UpdateDocumentRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.UpdateDocumentRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"documentBaseType",lazy=False)(pname=(ns,"document"), aname="_document", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._document = None + return + Holder.__name__ = "UpdateDocumentRequestType_Holder" + self.pyclass = Holder + + class CreateDocumentRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "CreateDocumentRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.CreateDocumentRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"Handle",lazy=False)(pname=(ns,"parentHandle"), aname="_parentHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.MSWS.STORAGE,"documentBaseType",lazy=False)(pname=(ns,"document"), aname="_document", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"relationshipName"), aname="_relationshipName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._parentHandle = None + self._document = None + self._relationshipName = None + return + Holder.__name__ = "CreateDocumentRequestType_Holder" + self.pyclass = Holder + + class CreateDocumentResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "CreateDocumentResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.CreateDocumentResponseType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"CreateDocumentResult"), aname="_CreateDocumentResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CreateDocumentResult = None + return + Holder.__name__ = "CreateDocumentResponseType_Holder" + self.pyclass = Holder + + class CreateRelationshipsRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "CreateRelationshipsRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.CreateRelationshipsRequestType_Def.schema + TClist = [self.__class__.relationships_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._relationships = None + return + Holder.__name__ = "CreateRelationshipsRequestType_Holder" + self.pyclass = Holder + + + class relationships_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "relationships" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.CreateRelationshipsRequestType_Def.relationships_Dec.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"Relationship",lazy=False)(pname=(ns,"Relationship"), aname="_Relationship", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"relationships") + kw["aname"] = "_relationships" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Relationship = [] + return + Holder.__name__ = "relationships_Holder" + self.pyclass = Holder + + + + + class DeleteRelationshipsRequestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.MSWS.STORAGE + type = (schema, "DeleteRelationshipsRequestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = msnss.DeleteRelationshipsRequestType_Def.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"Handle",lazy=False)(pname=(ns,"sourceHandle"), aname="_sourceHandle", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), self.__class__.targetHandles_Dec(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._sourceHandle = None + self._targetHandles = None + return + Holder.__name__ = "DeleteRelationshipsRequestType_Holder" + self.pyclass = Holder + + + class targetHandles_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "targetHandles" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.DeleteRelationshipsRequestType_Def.targetHandles_Dec.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"Handle",lazy=False)(pname=(ns,"ObjectHandle"), aname="_ObjectHandle", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"targetHandles") + kw["aname"] = "_targetHandles" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ObjectHandle = [] + return + Holder.__name__ = "targetHandles_Holder" + self.pyclass = Holder + + + + + class StorageApplicationHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "StorageApplicationHeader" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.StorageApplicationHeader_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"ApplicationID"), aname="_ApplicationID", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"Scenario"), aname="_Scenario", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"StorageApplicationHeader") + kw["aname"] = "_StorageApplicationHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ApplicationID = None + self._Scenario = None + return + Holder.__name__ = "StorageApplicationHeader_Holder" + self.pyclass = Holder + + class StorageUserHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "StorageUserHeader" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.StorageUserHeader_Dec.schema + TClist = [ZSI.TCnumbers.Iint(pname=(ns,"Puid"), aname="_Puid", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname=(ns,"Cid"), aname="_Cid", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Token(pname=(ns,"TicketToken"), aname="_TicketToken", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Boolean(pname=(ns,"IsAdmin"), aname="_IsAdmin", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"StorageUserHeader") + kw["aname"] = "_StorageUserHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Puid = None + self._Cid = None + self._TicketToken = None + self._IsAdmin = None + return + Holder.__name__ = "StorageUserHeader_Holder" + self.pyclass = Holder + + class AffinityCacheHeader_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "AffinityCacheHeader" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.AffinityCacheHeader_Dec.schema + TClist = [ZSI.TC.Token(pname=(ns,"CacheKey"), aname="_CacheKey", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"AffinityCacheHeader") + kw["aname"] = "_AffinityCacheHeader" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CacheKey = None + return + Holder.__name__ = "AffinityCacheHeader_Holder" + self.pyclass = Holder + + class GetProfile_Dec(ElementDeclaration): + literal = "GetProfile" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"GetProfile") + kw["aname"] = "_GetProfile" + if msnss.GetProfileRequestType_Def not in msnss.GetProfile_Dec.__bases__: + bases = list(msnss.GetProfile_Dec.__bases__) + bases.insert(0, msnss.GetProfileRequestType_Def) + msnss.GetProfile_Dec.__bases__ = tuple(bases) + + msnss.GetProfileRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "GetProfile_Dec_Holder" + + class GetProfileResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "GetProfileResponse" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.GetProfileResponse_Dec.schema + TClist = [GTD(MSNS.MSWS.STORAGE,"GetProfileResultType",lazy=False)(pname=(ns,"GetProfileResult"), aname="_GetProfileResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"GetProfileResponse") + kw["aname"] = "_GetProfileResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._GetProfileResult = None + return + Holder.__name__ = "GetProfileResponse_Holder" + self.pyclass = Holder + + class UpdateProfile_Dec(ElementDeclaration): + literal = "UpdateProfile" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"UpdateProfile") + kw["aname"] = "_UpdateProfile" + if msnss.UpdateProfileRequestType_Def not in msnss.UpdateProfile_Dec.__bases__: + bases = list(msnss.UpdateProfile_Dec.__bases__) + bases.insert(0, msnss.UpdateProfileRequestType_Def) + msnss.UpdateProfile_Dec.__bases__ = tuple(bases) + + msnss.UpdateProfileRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "UpdateProfile_Dec_Holder" + + class UpdateProfileResponse_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "UpdateProfileResponse" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"UpdateProfileResponse") + kw["aname"] = "_UpdateProfileResponse" + ZSI.TC.AnyType.__init__(self, **kw) + + class FindDocuments_Dec(ElementDeclaration): + literal = "FindDocuments" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"FindDocuments") + kw["aname"] = "_FindDocuments" + if msnss.FindDocumentsRequestType_Def not in msnss.FindDocuments_Dec.__bases__: + bases = list(msnss.FindDocuments_Dec.__bases__) + bases.insert(0, msnss.FindDocumentsRequestType_Def) + msnss.FindDocuments_Dec.__bases__ = tuple(bases) + + msnss.FindDocumentsRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "FindDocuments_Dec_Holder" + + class FindDocumentsResponse_Dec(ElementDeclaration): + literal = "FindDocumentsResponse" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"FindDocumentsResponse") + kw["aname"] = "_FindDocumentsResponse" + if msnss.FindDocumentsResultType_Def not in msnss.FindDocumentsResponse_Dec.__bases__: + bases = list(msnss.FindDocumentsResponse_Dec.__bases__) + bases.insert(0, msnss.FindDocumentsResultType_Def) + msnss.FindDocumentsResponse_Dec.__bases__ = tuple(bases) + + msnss.FindDocumentsResultType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "FindDocumentsResponse_Dec_Holder" + + class CreateProfile_Dec(ElementDeclaration): + literal = "CreateProfile" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"CreateProfile") + kw["aname"] = "_CreateProfile" + if msnss.CreateProfileRequestType_Def not in msnss.CreateProfile_Dec.__bases__: + bases = list(msnss.CreateProfile_Dec.__bases__) + bases.insert(0, msnss.CreateProfileRequestType_Def) + msnss.CreateProfile_Dec.__bases__ = tuple(bases) + + msnss.CreateProfileRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "CreateProfile_Dec_Holder" + + class CreateProfileResponse_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "CreateProfileResponse" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + ns = msnss.CreateProfileResponse_Dec.schema + TClist = [ZSI.TC.String(pname=(ns,"CreateProfileResult"), aname="_CreateProfileResult", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + kw["pname"] = (MSNS.MSWS.STORAGE,"CreateProfileResponse") + kw["aname"] = "_CreateProfileResponse" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CreateProfileResult = None + return + Holder.__name__ = "CreateProfileResponse_Holder" + self.pyclass = Holder + + class ShareItem_Dec(ElementDeclaration): + literal = "ShareItem" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"ShareItem") + kw["aname"] = "_ShareItem" + if msnss.ShareItemRequestType_Def not in msnss.ShareItem_Dec.__bases__: + bases = list(msnss.ShareItem_Dec.__bases__) + bases.insert(0, msnss.ShareItemRequestType_Def) + msnss.ShareItem_Dec.__bases__ = tuple(bases) + + msnss.ShareItemRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ShareItem_Dec_Holder" + + class ShareItemResponse_Dec(ElementDeclaration): + literal = "ShareItemResponse" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"ShareItemResponse") + kw["aname"] = "_ShareItemResponse" + if msnss.ShareItemResponseType_Def not in msnss.ShareItemResponse_Dec.__bases__: + bases = list(msnss.ShareItemResponse_Dec.__bases__) + bases.insert(0, msnss.ShareItemResponseType_Def) + msnss.ShareItemResponse_Dec.__bases__ = tuple(bases) + + msnss.ShareItemResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ShareItemResponse_Dec_Holder" + + class UpdateDocument_Dec(ElementDeclaration): + literal = "UpdateDocument" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"UpdateDocument") + kw["aname"] = "_UpdateDocument" + if msnss.UpdateDocumentRequestType_Def not in msnss.UpdateDocument_Dec.__bases__: + bases = list(msnss.UpdateDocument_Dec.__bases__) + bases.insert(0, msnss.UpdateDocumentRequestType_Def) + msnss.UpdateDocument_Dec.__bases__ = tuple(bases) + + msnss.UpdateDocumentRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "UpdateDocument_Dec_Holder" + + class UpdateDocumentResponse_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "UpdateDocumentResponse" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"UpdateDocumentResponse") + kw["aname"] = "_UpdateDocumentResponse" + ZSI.TC.AnyType.__init__(self, **kw) + + class CreateDocument_Dec(ElementDeclaration): + literal = "CreateDocument" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"CreateDocument") + kw["aname"] = "_CreateDocument" + if msnss.CreateDocumentRequestType_Def not in msnss.CreateDocument_Dec.__bases__: + bases = list(msnss.CreateDocument_Dec.__bases__) + bases.insert(0, msnss.CreateDocumentRequestType_Def) + msnss.CreateDocument_Dec.__bases__ = tuple(bases) + + msnss.CreateDocumentRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "CreateDocument_Dec_Holder" + + class CreateDocumentResponse_Dec(ElementDeclaration): + literal = "CreateDocumentResponse" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"CreateDocumentResponse") + kw["aname"] = "_CreateDocumentResponse" + if msnss.CreateDocumentResponseType_Def not in msnss.CreateDocumentResponse_Dec.__bases__: + bases = list(msnss.CreateDocumentResponse_Dec.__bases__) + bases.insert(0, msnss.CreateDocumentResponseType_Def) + msnss.CreateDocumentResponse_Dec.__bases__ = tuple(bases) + + msnss.CreateDocumentResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "CreateDocumentResponse_Dec_Holder" + + class CreateRelationships_Dec(ElementDeclaration): + literal = "CreateRelationships" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"CreateRelationships") + kw["aname"] = "_CreateRelationships" + if msnss.CreateRelationshipsRequestType_Def not in msnss.CreateRelationships_Dec.__bases__: + bases = list(msnss.CreateRelationships_Dec.__bases__) + bases.insert(0, msnss.CreateRelationshipsRequestType_Def) + msnss.CreateRelationships_Dec.__bases__ = tuple(bases) + + msnss.CreateRelationshipsRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "CreateRelationships_Dec_Holder" + + class CreateRelationshipsResponse_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "CreateRelationshipsResponse" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"CreateRelationshipsResponse") + kw["aname"] = "_CreateRelationshipsResponse" + ZSI.TC.AnyType.__init__(self, **kw) + + class DeleteRelationships_Dec(ElementDeclaration): + literal = "DeleteRelationships" + schema = MSNS.MSWS.STORAGE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"DeleteRelationships") + kw["aname"] = "_DeleteRelationships" + if msnss.DeleteRelationshipsRequestType_Def not in msnss.DeleteRelationships_Dec.__bases__: + bases = list(msnss.DeleteRelationships_Dec.__bases__) + bases.insert(0, msnss.DeleteRelationshipsRequestType_Def) + msnss.DeleteRelationships_Dec.__bases__ = tuple(bases) + + msnss.DeleteRelationshipsRequestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DeleteRelationships_Dec_Holder" + + class DeleteRelationshipsResponse_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "DeleteRelationshipsResponse" + schema = MSNS.MSWS.STORAGE + def __init__(self, **kw): + kw["pname"] = (MSNS.MSWS.STORAGE,"DeleteRelationshipsResponse") + kw["aname"] = "_DeleteRelationshipsResponse" + ZSI.TC.AnyType.__init__(self, **kw) + +# end class msnss (tns: http://www.msn.com/webservices/storage/2008) + diff --git a/digsby/src/msn/SOAP/MSNStorageService/__init__.py b/digsby/src/msn/SOAP/MSNStorageService/__init__.py new file mode 100644 index 0000000..ef25739 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNStorageService/__init__.py @@ -0,0 +1,2 @@ +from StorageService_types import * +from StorageService_client import * diff --git a/digsby/src/msn/SOAP/MSNStorageService/msnstorage_datatypes.xsd b/digsby/src/msn/SOAP/MSNStorageService/msnstorage_datatypes.xsd new file mode 100644 index 0000000..e779353 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNStorageService/msnstorage_datatypes.xsd @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNStorageService/msnstorage_servicetypes.xsd b/digsby/src/msn/SOAP/MSNStorageService/msnstorage_servicetypes.xsd new file mode 100644 index 0000000..e7f9c63 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNStorageService/msnstorage_servicetypes.xsd @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSNStorageService/msnstorage_ws.wsdl b/digsby/src/msn/SOAP/MSNStorageService/msnstorage_ws.wsdl new file mode 100644 index 0000000..33dd738 --- /dev/null +++ b/digsby/src/msn/SOAP/MSNStorageService/msnstorage_ws.wsdl @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/msn/SOAP/MSSOAPFault/__init__.py b/digsby/src/msn/SOAP/MSSOAPFault/__init__.py new file mode 100644 index 0000000..e67a222 --- /dev/null +++ b/digsby/src/msn/SOAP/MSSOAPFault/__init__.py @@ -0,0 +1 @@ +from psf import psf diff --git a/digsby/src/msn/SOAP/MSSOAPFault/psf.py b/digsby/src/msn/SOAP/MSSOAPFault/psf.py new file mode 100644 index 0000000..3c39761 --- /dev/null +++ b/digsby/src/msn/SOAP/MSSOAPFault/psf.py @@ -0,0 +1,489 @@ +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +import ZSI.wstools.Namespaces as NS +from msn.SOAP import Namespaces as MSNS + +class psf: + targetNamespace = MSNS.PPCRL.FAULT + + class ppHeaderType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "ppHeaderType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = psf.ppHeaderType_Def.schema + TClist = [GED(MSNS.PPCRL.FAULT,"serverVersion",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"PUID",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"configVersion",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"uiVersion",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"authstate",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"reqstatus",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"serverInfo",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"cookies",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"browserCookies",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"credProperties",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"extProperties",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(MSNS.PPCRL.FAULT,"response",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._serverVersion = None + self._PUID = None + self._configVersion = None + self._uiVersion = None + self._authstate = None + self._reqstatus = None + self._serverInfo = None + self._cookies = None + self._browserCookies = None + self._credProperties = None + self._extProperties = None + self._response = None + return + Holder.__name__ = "ppHeaderType_Holder" + self.pyclass = Holder + + class serverVersionType_Def(ZSI.TCnumbers.Iinteger, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "serverVersionType") + def __init__(self, pname, **kw): + ZSI.TCnumbers.Iinteger.__init__(self, pname, pyclass=None, **kw) + class Holder(int): + typecode = self + self.pyclass = Holder + + class PUIDType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "PUIDType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class configVersionType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "configVersionType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class uiVersionType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "uiVersionType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class authstateType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "authstateType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class reqstatusType_Def(ZSI.TC.String, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "reqstatusType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class serverInfoType_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = MSNS.PPCRL.FAULT + type = (schema, "serverInfoType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["ServerTime"] = ZSI.TCtimes.gDateTime() + self.attribute_typecode_dict["LocVersion"] = ZSI.TCnumbers.Iinteger() + self.attribute_typecode_dict["RollingUpgradeState"] = ZSI.TC.String() + self.attribute_typecode_dict["Path"] = ZSI.TC.String() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class browserCookieType_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = MSNS.PPCRL.FAULT + type = (schema, "browserCookieType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Name"] = ZSI.TC.String() + self.attribute_typecode_dict["URL"] = ZSI.TC.URI() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class browserCookieCollectionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "browserCookieCollectionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = psf.browserCookieCollectionType_Def.schema + TClist = [GED(MSNS.PPCRL.FAULT,"browserCookie",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._browserCookie = None + return + Holder.__name__ = "browserCookieCollectionType_Holder" + self.pyclass = Holder + + class credPropertyType_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = MSNS.PPCRL.FAULT + type = (schema, "credPropertyType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Name"] = ZSI.TC.String() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class credPropertyCollectionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "credPropertyCollectionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = psf.credPropertyCollectionType_Def.schema + TClist = [GED(MSNS.PPCRL.FAULT,"credProperty",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._credProperty = None + return + Holder.__name__ = "credPropertyCollectionType_Holder" + self.pyclass = Holder + + class extPropertyType_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = MSNS.PPCRL.FAULT + type = (schema, "extPropertyType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["IgnoreRememberMe"] = ZSI.TC.Boolean() + self.attribute_typecode_dict["Domains"] = ZSI.TC.String() + self.attribute_typecode_dict["Expiry"] = ZSI.TC.String() + self.attribute_typecode_dict["Name"] = ZSI.TC.String() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class extPropertyCollectionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "extPropertyCollectionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = psf.extPropertyCollectionType_Def.schema + TClist = [GED(MSNS.PPCRL.FAULT,"extProperty",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._extProperty = None + return + Holder.__name__ = "extPropertyCollectionType_Holder" + self.pyclass = Holder + + class internalerrorType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "internalerrorType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = psf.internalerrorType_Def.schema + TClist = [ZSI.TCnumbers.IunsignedInt(pname=(ns,"code"), aname="_code", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"text"), aname="_text", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._code = None + self._text = None + return + Holder.__name__ = "internalerrorType_Holder" + self.pyclass = Holder + + class errorType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = MSNS.PPCRL.FAULT + type = (schema, "errorType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = psf.errorType_Def.schema + TClist = [ZSI.TCnumbers.IunsignedInt(pname=(ns,"value"), aname="_value", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(MSNS.PPCRL.FAULT,"internalerrorType",lazy=False)(pname=(ns,"internalerror"), aname="_internalerror", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._value = None + self._internalerror = None + return + Holder.__name__ = "errorType_Holder" + self.pyclass = Holder + + class pp_Dec(ElementDeclaration): + literal = "pp" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"pp") + kw["aname"] = "_pp" + if psf.ppHeaderType_Def not in psf.pp_Dec.__bases__: + bases = list(psf.pp_Dec.__bases__) + bases.insert(0, psf.ppHeaderType_Def) + psf.pp_Dec.__bases__ = tuple(bases) + + psf.ppHeaderType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "pp_Dec_Holder" + + class serverVersion_Dec(ElementDeclaration): + literal = "serverVersion" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"serverVersion") + kw["aname"] = "_serverVersion" + if psf.serverVersionType_Def not in psf.serverVersion_Dec.__bases__: + bases = list(psf.serverVersion_Dec.__bases__) + bases.insert(0, psf.serverVersionType_Def) + psf.serverVersion_Dec.__bases__ = tuple(bases) + + psf.serverVersionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "serverVersion_Dec_Holder" + + class PUID_Dec(ElementDeclaration): + literal = "PUID" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"PUID") + kw["aname"] = "_PUID" + if psf.PUIDType_Def not in psf.PUID_Dec.__bases__: + bases = list(psf.PUID_Dec.__bases__) + bases.insert(0, psf.PUIDType_Def) + psf.PUID_Dec.__bases__ = tuple(bases) + + psf.PUIDType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "PUID_Dec_Holder" + + class configVersion_Dec(ElementDeclaration): + literal = "configVersion" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"configVersion") + kw["aname"] = "_configVersion" + if psf.configVersionType_Def not in psf.configVersion_Dec.__bases__: + bases = list(psf.configVersion_Dec.__bases__) + bases.insert(0, psf.configVersionType_Def) + psf.configVersion_Dec.__bases__ = tuple(bases) + + psf.configVersionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "configVersion_Dec_Holder" + + class uiVersion_Dec(ElementDeclaration): + literal = "uiVersion" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"uiVersion") + kw["aname"] = "_uiVersion" + if psf.uiVersionType_Def not in psf.uiVersion_Dec.__bases__: + bases = list(psf.uiVersion_Dec.__bases__) + bases.insert(0, psf.uiVersionType_Def) + psf.uiVersion_Dec.__bases__ = tuple(bases) + + psf.uiVersionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "uiVersion_Dec_Holder" + + class authstate_Dec(ElementDeclaration): + literal = "authstate" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"authstate") + kw["aname"] = "_authstate" + if psf.authstateType_Def not in psf.authstate_Dec.__bases__: + bases = list(psf.authstate_Dec.__bases__) + bases.insert(0, psf.authstateType_Def) + psf.authstate_Dec.__bases__ = tuple(bases) + + psf.authstateType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "authstate_Dec_Holder" + + class reqstatus_Dec(ElementDeclaration): + literal = "reqstatus" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"reqstatus") + kw["aname"] = "_reqstatus" + if psf.reqstatusType_Def not in psf.reqstatus_Dec.__bases__: + bases = list(psf.reqstatus_Dec.__bases__) + bases.insert(0, psf.reqstatusType_Def) + psf.reqstatus_Dec.__bases__ = tuple(bases) + + psf.reqstatusType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "reqstatus_Dec_Holder" + + class serverInfo_Dec(ElementDeclaration): + literal = "serverInfo" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"serverInfo") + kw["aname"] = "_serverInfo" + if psf.serverInfoType_Def not in psf.serverInfo_Dec.__bases__: + bases = list(psf.serverInfo_Dec.__bases__) + bases.insert(0, psf.serverInfoType_Def) + psf.serverInfo_Dec.__bases__ = tuple(bases) + + psf.serverInfoType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "serverInfo_Dec_Holder" + + class cookies_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "cookies" + schema = MSNS.PPCRL.FAULT + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"cookies") + kw["aname"] = "_cookies" + ZSI.TC.AnyType.__init__(self, **kw) + + class browserCookie_Dec(ElementDeclaration): + literal = "browserCookie" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"browserCookie") + kw["aname"] = "_browserCookie" + if psf.browserCookieType_Def not in psf.browserCookie_Dec.__bases__: + bases = list(psf.browserCookie_Dec.__bases__) + bases.insert(0, psf.browserCookieType_Def) + psf.browserCookie_Dec.__bases__ = tuple(bases) + + psf.browserCookieType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "browserCookie_Dec_Holder" + + class browserCookies_Dec(ElementDeclaration): + literal = "browserCookies" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"browserCookies") + kw["aname"] = "_browserCookies" + if psf.browserCookieCollectionType_Def not in psf.browserCookies_Dec.__bases__: + bases = list(psf.browserCookies_Dec.__bases__) + bases.insert(0, psf.browserCookieCollectionType_Def) + psf.browserCookies_Dec.__bases__ = tuple(bases) + + psf.browserCookieCollectionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "browserCookies_Dec_Holder" + + class credProperty_Dec(ElementDeclaration): + literal = "credProperty" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"credProperty") + kw["aname"] = "_credProperty" + if psf.credPropertyType_Def not in psf.credProperty_Dec.__bases__: + bases = list(psf.credProperty_Dec.__bases__) + bases.insert(0, psf.credPropertyType_Def) + psf.credProperty_Dec.__bases__ = tuple(bases) + + psf.credPropertyType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "credProperty_Dec_Holder" + + class credProperties_Dec(ElementDeclaration): + literal = "credProperties" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"credProperties") + kw["aname"] = "_credProperties" + if psf.credPropertyCollectionType_Def not in psf.credProperties_Dec.__bases__: + bases = list(psf.credProperties_Dec.__bases__) + bases.insert(0, psf.credPropertyCollectionType_Def) + psf.credProperties_Dec.__bases__ = tuple(bases) + + psf.credPropertyCollectionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "credProperties_Dec_Holder" + + class extProperty_Dec(ElementDeclaration): + literal = "extProperty" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"extProperty") + kw["aname"] = "_extProperty" + if psf.extPropertyType_Def not in psf.extProperty_Dec.__bases__: + bases = list(psf.extProperty_Dec.__bases__) + bases.insert(0, psf.extPropertyType_Def) + psf.extProperty_Dec.__bases__ = tuple(bases) + + psf.extPropertyType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "extProperty_Dec_Holder" + + class extProperties_Dec(ElementDeclaration): + literal = "extProperties" + schema = MSNS.PPCRL.FAULT + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"extProperties") + kw["aname"] = "_extProperties" + if psf.extPropertyCollectionType_Def not in psf.extProperties_Dec.__bases__: + bases = list(psf.extProperties_Dec.__bases__) + bases.insert(0, psf.extPropertyCollectionType_Def) + psf.extProperties_Dec.__bases__ = tuple(bases) + + psf.extPropertyCollectionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "extProperties_Dec_Holder" + + class response_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "response" + schema = MSNS.PPCRL.FAULT + def __init__(self, **kw): + kw["pname"] = (MSNS.PPCRL.FAULT,"response") + kw["aname"] = "_response" + ZSI.TC.AnyType.__init__(self, **kw) + +# end class psf (tns: http://schemas.microsoft.com/Passport/SoapServices/SOAPFault) + diff --git a/digsby/src/msn/SOAP/Namespaces.py b/digsby/src/msn/SOAP/Namespaces.py new file mode 100644 index 0000000..4561ba8 --- /dev/null +++ b/digsby/src/msn/SOAP/Namespaces.py @@ -0,0 +1,17 @@ +from ZSI.wstools.Namespaces import * + + +class PPCRL: + BASE = "http://schemas.microsoft.com/Passport/SoapServices/PPCRL" + FAULT = "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault" + IDS = "http://schemas.microsoft.com/passport/IDS" + +class MSWS: + STORAGE = "http://www.msn.com/webservices/storage/2008" + SPACES = "http://www.msn.com/webservices/spaces/v1/" + ADDRESS = "http://www.msn.com/webservices/AddressBook" + +class HMNS: + RSI = "http://www.hotmail.msn.com/ws/2004/09/oim/rsi" + OIM = "http://messenger.msn.com/ws/2004/09/oim/" + diff --git a/digsby/src/msn/SOAP/__init__.py b/digsby/src/msn/SOAP/__init__.py new file mode 100644 index 0000000..e1680b8 --- /dev/null +++ b/digsby/src/msn/SOAP/__init__.py @@ -0,0 +1,98 @@ +import ZSI.schema as Schema +from wsdl2py import main as generate + +import util.network.soap as soap + +class MSNBinding(soap.Binding): + def get_default_headers(self): + headers = super(MSNBinding, self).get_default_headers() + headers['User-Agent'] = 'MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + # 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.1; WOW64; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; IDCRL 5.000.810.6; IDCRL-cfg 6.0.11409.0; App msnmsgr.exe, 14.0.8089.726, {7108E71A-9926-4FCB-BCC9-9A9D3F32E423})', + return headers + +class MSNBindingSOAP(soap.BindingSOAP): + BindingClass = MSNBinding + +import xml.wsu +import xml.wsu_oasis +import xml.wsrm +import xml.ds +import xml.soapenv +import xml.soapwsa +import xml.wsa +import xml.wsc +import xml.wsp +import xml.wsse +import xml.wst + +import Namespaces + +import MSNABSharingService +import MSNOIMStoreService +import MSNRSIService +import MSNSecurityTokenService +import MSNSpaceService +import MSNStorageService +import MSSOAPFault + +import logging +log = logging.getLogger('msn.SOAP') + +MODULES = ( + xml.wsu, + xml.wsu_oasis, + xml.wsrm, + xml.ds, + xml.soapenv, + xml.soapwsa, + xml.wsa, + xml.wsc, + xml.wsp, + xml.wsse, + xml.wst, + MSNABSharingService, + MSNOIMStoreService, + MSNRSIService, + MSNSecurityTokenService, + MSNSpaceService, + MSNStorageService, + MSSOAPFault, + ) + +#def do_gen(module): +# import path +# mod_dir = path.path(module.__file__).parent +# for wsdlfile in mod_dir.files("*.wsdl"): +# print wsdlfile +# generate(file=wsdlfile, output_directory = mod_dir) +# +#for module in (MODULES): +# do_gen(module) + +def set_module_types(module, namespace): + for clsname in namespace.__dict__: + if clsname.endswith('_Def') or clsname.endswith('_Dec'): + name = clsname[:-4] + + if clsname.endswith('_Dec'): + pyclass = namespace.__dict__[clsname]().pyclass + else: + try: + pname = (namespace.targetNamespace, name) + pyclass = Schema.GTD(*pname)(pname).pyclass + except AttributeError, e: + log.debug("Couldn't find name %s in namespace %s. The underlying error was: %r", name, namespace.targetNamespace, e) + continue + + if name in vars(module): + if vars(module)[name] is not pyclass: + log.debug('Found two instances of the name "%s". (%r and %r)', name, vars(module)[name], pyclass) + continue + + setattr(module, name, pyclass) + +for module in MODULES: + for k, v in vars(module).items(): + if hasattr(v, 'targetNamespace'): + set_module_types(module, v) + diff --git a/digsby/src/msn/SOAP/pysoap_util.py b/digsby/src/msn/SOAP/pysoap_util.py new file mode 100644 index 0000000..fafdad7 --- /dev/null +++ b/digsby/src/msn/SOAP/pysoap_util.py @@ -0,0 +1,124 @@ +import uuid +import simplejson as json +import logging + +import util +log = logging.getLogger('msn.storage') + +import msn.SOAP.services as SOAPServices + +def extract_zsi_properties(zsiobj): + return dict((k[1:], v) for k, v in vars(zsiobj).items()) + +class Serializer(json.JSONEncoder): + def default(self, o): + if isinstance(o, Serializable): + d = vars(o).copy() + + for k in d.keys(): + if k in getattr(o, 'nonserialize_attributes', []) or k in ("nonserialize_attributes", "serialize_attributes"): + d.pop(k) + + sentinel = object() + for k in getattr(o, 'serialize_attributes', []): + if k not in d: + v = getattr(o, k, sentinel) + if v is not sentinel: + d[k] = v + + return d + + if isinstance(o, set): + return list(o) + + try: + return json.JSONEncoder.default(self, o) + except Exception, e: + log.error("error json encoding %r", o) + raise e + return {} + +class Serializable(object): + def __init__(self, **k): + if not hasattr(self, 'nonserialize_attributes'): + self.nonserialize_attributes = [] + if not hasattr(self, 'serialize_attributes'): + self.serialize_attributes = [] + + util.autoassign(self, k) + + selfvars = vars(self) + typevars = vars(type(self)) + for k in typevars: + if k in selfvars and selfvars.get(k, None) == typevars.get(k, None): + setattr(self, k, None) + + def serialize(self): + return json.dumps(self, cls = Serializer) + + @classmethod + def deserialize(cls, s): + if isinstance(s, basestring): + loaded = json.loads(s) + else: + loaded = s + + self_dict = {} + for k, v in loaded.items(): + valtype = vars(cls).get(k, None) + if valtype is not None: + v = deserialize_item(valtype, v) + + self_dict[k] = v + return cls(**self_dict) + + def copy(self): + return type(self).deserialize(self.serialize()) + + def __repr__(self): + return '%s(**%r)' % (type(self).__name__, json.loads(self.serialize())) + + @classmethod + def from_zsi(cls, obj, **kwds): + if obj is None: + if kwds: + return cls(**kwds) + return None + + attrs = extract_zsi_properties(obj) + attrs.update(kwds) + return cls(**attrs) + +def deserialize_item(valtype, v): + if v is None: + return None + if type(v) in (bool, int, basestring): + return v + if type(valtype) is type and issubclass(valtype, Serializable): + return valtype.deserialize(v) + elif type(valtype) is list: + if len(valtype) == 1: + return [deserialize_item(valtype[0], x) for x in v] + elif type(valtype) is dict: + if len(valtype) == 1: + return dict((k,deserialize_item(valtype[''], v[k])) for k in v) + + return v + +class DateAttr(object): + def __init__(self, attrname): + self.attrname = attrname + + def __get__(self, obj, objtype = None): + return getattr(obj, '_' + self.attrname, 0) + + def __set__(self, obj, val): + if self.attrname not in obj.serialize_attributes: + obj.serialize_attributes.append(self.attrname) + if '_' + self.attrname not in obj.nonserialize_attributes: + obj.nonserialize_attributes.append('_' + self.attrname) + + if isinstance(val, basestring): + val = SOAPServices.strptime_highres(val) + + setattr(obj, '_' + self.attrname, val) diff --git a/digsby/src/msn/SOAP/services.py b/digsby/src/msn/SOAP/services.py new file mode 100644 index 0000000..229e538 --- /dev/null +++ b/digsby/src/msn/SOAP/services.py @@ -0,0 +1,1157 @@ +import logging +log = logging.getLogger("msn.soap.services") +import time, datetime +import urlparse +import uuid +import util +import util.net as net +import util.callbacks as callbacks +import util.network.soap as soap + +import MSNSecurityTokenService.SecurityTokenService_client as STS_Client +import MSNSecurityTokenService.SecurityTokenService_types as STS_Types + +import msn.SOAP.xml.wsa as wsa +#import msn.SOAP.xml.soapwsa as wsa +import msn.SOAP.xml.wst as wst +import msn.SOAP.xml.wsse as wsse +import msn.SOAP.xml.wsu_oasis as wsu +import msn.SOAP.xml.soapenv as soapenv + +import ZSI.schema as Schema +import ZSI.wstools.Namespaces as NS +import msn.SOAP.Namespaces as MSNS + +def strtime_highres(t = None): + if t is None: + t = time.time() + return time.strftime('%Y-%m-%dT%H:%M:%S.0000000' + + ('%+03d:%02d' % divmod(-(time.altzone if time.daylight else time.timezone), 60*60)), + time.gmtime(t)) + +def strptime_highres(s): + tz_delim, direction = ('+', 1) if '+' in s else ('-', -1) + tstamp, tz_str = s.rsplit(tz_delim, 1) + if 'T' in tz_str: + # There is no TZ portion. + tz_offset = 0 + else: + tz_struct = time.strptime(tz_str, '%H:%M') + tz_offset = (60 * 60 * tz_struct.tm_hour) + (60 * tz_struct.tm_min) * direction + s = tstamp + + if '.' in s: + s, micro = s.split('.', 1) + + ts = time.strptime(s, '%Y-%m-%dT%H:%M:%S') + if ts.tm_year < 1971: + # Out of range for time.mktime. + return 0 + + return time.mktime(ts) + tz_offset + +class SsoDomains: + Clear = 'messengerclear.live.com' + STS = 'http://Passport.NET/tb' + WhatsUp = 'sup.live.com' + Messenger = 'messenger.msn.com' + Storage = 'storage.msn.com' + OfflineIM = 'messengersecure.live.com' + Contacts = 'contacts.msn.com' + Spaces = 'spaces.live.com' + Web = 'messenger.msn.com' + Profiles = 'profile.live.com' + +class AppIDs: + STS = uuid.UUID("7108E71A-9926-4FCB-BCC9-9A9D3F32E423") + WhatsUp = uuid.UUID('3B119D87-1D76-4474-91AD-0D7267E86D04') + #AddressBook = uuid.UUID("09607671-1C32-421F-A6A6-CBFAA51AB5F4") + AddressBook = uuid.UUID("CFE80F9D-180F-4399-82AB-413F33A1FA11") + Sharing = uuid.UUID("AAD9B99B-58E6-4F23-B975-D9EC1F9EC24A") + Storage = "Messenger Client 15" + Spaces = 'Messenger Client 8.0' + RSIService = 'RSIService' + Profiles = uuid.UUID('F679201A-DBC9-4011-B81B-3D05F0797EA9') + +class PartnerScenario: + NONE = 'None' + Initial = 'Initial' + Timer ='Timer' + BlockUnblock = 'BlockUnblock' + GroupSave = 'GroupSave' + GeneralDialogApply = 'GeneralDialogApply' + ContactSave = 'ContactSave' + ContactMsgrAPI = 'ContactMsgrAPI' + MessengerPendingList = 'MessengerPendingList' + PrivacyApply = 'PrivacyApply' + NewCircleDuringPull = 'NewCircleDuringPull' + CircleInvite = 'CircleInvite' + CircleIdAlert = 'CircleIdAlert' + CircleStatus = 'CircleStatus' + CircleSave = 'CircleSave' + CircleLeave = 'CircleLeave' + JoinedCircleDuringPush = 'JoinedCircleDuringPush' + ABChangeNotifyAlert = 'ABChangeNotifyAlert' + RoamingSeed = 'RoamingSeed' + RoamingIdentityChanged = 'RoamingIdentityChanged' + SyncChangesToServer = 'LivePlatform!SyncChangesToServer(0)' + +class MSNServiceBase(object): + def __init__(self, transport = None, **k): + self.Transport = transport + +class SSOService(MSNServiceBase): + SSO = True + SSO_Domain = None + SSO_PolicyRef = None + + @callbacks.callsback + def CheckAuth(self, client, callback = None): + if client.is_ticket_expired(self.SSO_Domain): + return client.renew_auth(success = lambda new_tickets: callback.success(), + error = callback.error) + + return callback.success() + + def _CheckError(self, client, msg, response, **k): + if not (hasattr(response, 'typecode') and response.typecode.pname == 'Fault'): + return + + fault = response + code = fault.get_element_Code() + if code is not None: + value = eval(code.get_element_Value(), {}, {}) + if value == (MSNS.PPCRL.IDS, "BadContextToken"): + log.error("SSO Token expired. Must authenticate and send request again!") + + client.clearservice_auth(client._username, client._password, ) + +class ClearService(SSOService): + SSO_Domain = SsoDomains.Clear + +class CacheKeyService(MSNServiceBase): + CacheKeyName = None + + def __init__(self, **k): + super(CacheKeyService, self).__init__(**k) + self.SessionId = None + self.PreferredHostName = None + + def add_cachekey(self, client, appheader): + cachekey = client.get_cachekey(self.CacheKeyName) + if cachekey is not None: + appheader.CacheKey = cachekey + + def handleHeaders(self, client, headers): + serviceHeader = headers.get((MSNS.MSWS.ADDRESS, 'ServiceHeader'), None) + if serviceHeader is None: + return + + if serviceHeader.CacheKeyChanged: + client.set_cachekey(self.CacheKeyName, str(serviceHeader.CacheKey)) + + self.SessionId = serviceHeader.SessionId or self.SessionId + self.PreferredHostName = serviceHeader.PreferredHostName or self.PreferredHostName + + def getPort(self, *a, **k): + client = k.get('client') + name = k.get('soapName') + locator = k.get('locator') + getPort = getattr(locator, 'get%sPort' % name, lambda *a, **k: None) + default_port = getPort(**k) + default_port_url = getattr(getattr(default_port, 'binding', None), 'url', None) + + if client.get_cachekey(self.CacheKeyName) is None: + parsed = urlparse.urlparse(default_port_url) + cachekeyurl = urlparse.urlunparse(('https', self.CacheKeyDomain) + parsed[2:]) + + log.info("Changing endpoint of request from %r to %r for cachekey", default_port_url, cachekeyurl) + k.pop('url', None) + return getPort(cachekeyurl, **k) + else: + return default_port + +class SecurityTokenService(SSOService): + AppName = 'SecurityTokenService' + AppId = AppIDs.STS + Soap = STS_Client + Locator = STS_Client.SecurityTokenServiceLocator + + SSO_Domain = SsoDomains.STS + + @callbacks.callsback + def CheckAuth(self, client, callback = None): + callback.success() + + def serviceurl_for_user(self, username): + if username.endswith('msn.com'): + return 'https://msnia.login.live.com/RST2.srf' + + # returning None will allow the service to use its default URL as defined in the WSDL, + # "https://login.live.com/RST2.srf" + + def getCredProperty(self, key): + return getattr(self, 'credProperties', {}).get(key, None) + + def handleHeaders(self, client, headers): + self.credProperties = credprops = getattr(self, 'credProperties', {}) + + pp = headers.get((MSNS.PPCRL.FAULT, 'pp'), None) + if pp is not None: + log.info("SecurityTokenService got pp header: %r", pp) + for cp in pp.CredProperties.CredProperty: + name = cp.get_attribute_Name() + log.info("\tcredProperty: %s = %r", name, str(cp)) + credprops[name] = str(cp) + + # TODO: extProperties ? anything useful in there? + + def serviceHeaders(self, + client, + actionValue = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue', + toValue = "HTTPS://login.live.com:443//RST2.srf", + *a, **k): + ai = STS_Types.ps.AuthInfo_Dec().pyclass() + ai.set_attribute_Id("PPAuthInfo") + ai.HostingApp = "{" + str(self.AppId).upper() + "}" + ai.BinaryVersion = 5 + ai.Cookies = '' + ai.UIVersion = 1 + # ai.RequestParams = "AQAAAAIAAABsYwQAAAAyMDUy" + ai.RequestParams = 'AQAAAAIAAABsYwQAAAAxMDMz' # Observed from msnc14 + + sec = wsse.Security() + sec.UsernameToken = sec.new_UsernameToken() + sec.UsernameToken.set_attribute_Id('user') + sec.UsernameToken.Username = sec.UsernameToken.new_Username(client.get_username()) + password = client.get_password() # set it to a local so it can be discovered and removed by the traceback printer if anything happens. + sec.UsernameToken.Password = sec.UsernameToken.new_Password(password) + + now = datetime.datetime.utcnow().replace(microsecond = 0) + + ts = sec.Timestamp = sec.new_Timestamp() + ts.set_attribute_Id("Timestamp") + ts.Created = ts.new_Created(now.isoformat() + 'Z') + ts.Expires = ts.new_Expires((now + datetime.timedelta(minutes=60)).isoformat() + 'Z') + + messageid = wsa.MessageID(str(int(time.time()))) + + mustunderstand_attrs = { + (soapenv.wssoapenv.targetNamespace, 'mustUnderstand') : '1', + } + + class Action: + typecode = wsa.wsa.Action_Dec(mixed = True) + _attrs = mustunderstand_attrs + _text = actionValue + + class To: + typecode = wsa.wsa.To_Dec(mixed = True) + _attrs = mustunderstand_attrs + _text = toValue + + return Action, To, messageid, ai, sec + + def rst_for_service(self, msg, client, service, id): + domain = service.SSO_Domain + policyref = service.SSO_PolicyRef + + rtok = msg.new_RequestSecurityToken() + rtok.set_attribute_Id(id) + rtok.RequestType = NS.WSTRUST.ISSUE + + # wsp:AppliesTo + at = rtok.AppliesTo = rtok.new_AppliesTo() + at.EndpointReference = at.new_EndpointReference() + at.EndpointReference.Address = at.EndpointReference.new_Address(domain) + + + if policyref is not None: + rtok.PolicyReference = rtok.new_PolicyReference() + rtok.PolicyReference.set_attribute_URI(policyref) + + return rtok + + @soap.soapcall(STS_Client) + def RequestSecurityToken(self, + msg, + client, + actionValue = "", + toValue = "HTTPS://login.live.com:443//RST2.srf", + service = None, + *a, **k): + if service is None: + service = self + + msg.RequestSecurityToken = rst_for_service(msg, client, service, "RST1") + + @soap.soapcall(STS_Client) + def RequestMultipleSecurityTokens(self, + msg, + client, + actionValue = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue', + toValue = "HTTPS://login.live.com:443//RST2.srf", + services = None, + *a, **k): + msg.set_attribute_Id('RSTS') + tokens = [] + + for i, service in enumerate(services or client.getSsoServices()): + + #SecurityToken = http://Passport.NET/tb, "" + #Web = messenger.msn.com, ?id=507 + #Storage = storage.msn.com, MBI + #Clear = messengerclear.live.com, ?? ## make it a prop of the service that can change at runtime. comes from the ticket token + #OIM = messengersecure.live.com, MBI_SSL + #Contact = contacts.msn.com, MBI + #WhatsUp = sup.live.com, MBI + + rtok = self.rst_for_service(msg, client, service, id = "RST" + str(i)) + tokens.append(rtok) + + msg.RequestSecurityToken = tokens + + +import MSNABSharingService as MSNABSS +import MSNABSharingService.SharingService_client as Sharing_Client +import MSNABSharingService.SharingService_types as Sharing_Types + +# msnab_sharingservice.wsdl +# SharingService_client.py + +class WhatsUpService(SSOService): + AppName = "WhatsUpService" + AppId = AppIDs.WhatsUp + Soap = Sharing_Client + Locator = Sharing_Client.WhatsUpServiceLocator + + SSO_Domain = SsoDomains.WhatsUp + SSO_PolicyRef = 'MBI' + + def serviceHeaders(self, client, *a, **k): + ''' + + + + 3B119D87-1D76-4474-91AD-0D7267E86D04 + + + + + t=EwCYAebpAwAUXYHvLVryvkoZZmChP4TpdQV2xi2AAHGTaHqADpfC+4DlHVPURA4KhB0LQXd9qlo80h3pZpjUSZALqMApTC4rrYvvG+14K1LrBSsa5pR5Cp07GxynXRqObdNNa7czt/VV17I4wQ5M74QTy0hpNpZbRIRfIi/Sa39qtq3wf6YSXjLqRiTWy770gTTgkRJnhBPJsu8/91ZVA2YAAAjRQDTT6OxfH+gApUj0LRT6c2jsxqxJHb7Ufcv5s9QoWooL/6NhGUtlXUW7onuhrP+kkfHmFYVRrDS9ObRio8i3hxOB67PusWCY4+eSOWmcX3t1E1zXPfjIjWjrdJQutYsYaSqAv2++3iEeYfazgW4pGV3MyI9x7025zHjW9FjuSi/jHOt+Be+fueSQ8CaPTpp1dJ8aBS9/uVHL6I/HxQ1sBwkG/b/tGRD2ZyAt3MZHTCXtEIQDnLeadR72HDOGGtL8YI/oxhme3eETgUKsNaJaFN7tXrxDzUtJiXzbQfFxYUWS5o8/K8rrxs+sf+vsgWupug8B&p= + + + + ''' + app = Sharing_Types.WNApplicationHeader() + app.ApplicationId = app.new_ApplicationId(str(self.AppId).upper()) + + auth = Sharing_Types.WNAuthHeader() + auth.TicketToken = client.get_ticket(self.SSO_Domain).token + + return (app, auth) + + @soap.soapcall(Sharing_Client) + def GetContactsRecentActivity(self, msg, client, cid, count = 10, locales = ["en-US"]): + ''' + + + -3649768326646796772 + + + en-US + + 50 + + ''' + msg.EntityHandle = msg.new_entityHandle() + msg.EntityHandle.Cid = cid + + msg.Locales = msg.new_locales() + msg.Locales.String.extend(locales) + + msg.Count = count + + return True + + +class SharingServiceBase(CacheKeyService): + CacheKeyName = 'omega' + CacheKeyDomain = 'byrdr.omega.contacts.msn.com' + + def handleHeaders(self, client, headers): + super(SharingServiceBase, self).handleHeaders(client = client, headers = headers) + self._recv_headers = headers + + def serviceHeaders(self, client, *a, **k): + ticket = client.get_ticket(SsoDomains.Contacts) + + app = Schema.GED(MSNS.MSWS.ADDRESS,"ABApplicationHeader").pyclass() + + # tried using self.AppId for this but it failed for addressbook requests. + app.ApplicationId = app.new_ApplicationId(str(AppIDs.Sharing).upper()) + app.IsMigration = False + app.PartnerScenario = getattr(PartnerScenario, k.get('PartnerScenario', 'Initial'), 'Initial') + app.BrandId = getattr(ticket, "BrandId", 'MSFT') + + self.add_cachekey(client, app) + + auth = Schema.GED(MSNS.MSWS.ADDRESS,"ABAuthHeader").pyclass() + auth.TicketToken = ticket.token + auth.ManagedGroupRequest = False + + return app, auth + +class SharingService(SharingServiceBase, SSOService): + AppName = "SharingService" + AppId = AppIDs.Sharing + Soap = Sharing_Client + Locator = Sharing_Client.SharingServiceLocator + + SSO_Domain = SsoDomains.Contacts + SSO_PolicyRef = 'MBI' + + @soap.soapcall() + def FindMembership(self, msg, client, view = 'Full', deltasOnly = False, lastChange = 0, **k): + msg.View = view + msg.DeltasOnly = deltasOnly +# msg.LastChange = strtime_highres(lastChange) + log.info("SharingService.FindMembership using lastChange = %r", lastChange) + msg.LastChange = lastChange + + msg.ServiceFilter = msg.new_serviceFilter() + msg.ServiceFilter.Types = msg.ServiceFilter.new_Types() + msg.ServiceFilter.Types.ServiceType.append("Messenger") + #msg.ServiceFilter.Types.ServiceType.extend(["Messenger", 'SocialNetwork', 'Space', 'Profile']) + + @soap.soapcall() + def AddMember(self, msg, client, contact, role, **k): + import msn.AddressBook as MSNAB + + messengerService = client.address_book.SelectTargetService(MSNAB.ServiceFilters.Messenger) + + msg.ServiceHandle = msg.new_serviceHandle() + msg.ServiceHandle.Id = messengerService.id + msg.ServiceHandle.Type = messengerService.type + + msg.Memberships = msg.new_memberships() + mship = msg.Memberships.new_Membership() + msg.Memberships.Membership.append(mship) + + mship.MemberRole = role.role + ctype = contact.type + if isinstance(contact.type, int): + ctype = MSNAB.ClientType(ctype) + + if ctype == MSNAB.ClientType.PassportMember: + member = MSNABSS.PassportMember() + member.PassportName = contact.Mail + member.State = MSNAB.RelationshipStates.Accepted + member.Type = MSNAB.MembershipType.Passport + elif ctype == MSNAB.ClientType.EmailMember: + member = MSNABSS.EmailMember() + member.State = MSNAB.RelationshipStates.Accepted + member.Type = MSNAB.MembershipType.Email + member.Email = contact.Mail + member.Annotations = member.new_Annotations() + anno = member.Annotations.new_Annotation() + member.Annotations.Annotation.append(anno) + anno.Name = MSNAB.AnnotationNames.MSN_IM_BuddyType + anno.Value = '%d:' % int(ctype) + elif ctype == MSNAB.ClientType.PhoneMember: + member = MSNABSS.PhoneMember() + member.State = MSNAB.RelationshipStates.Accepted + member.Type = MSNAB.MembershipType.Phone + member.PhoneNumber = contact.Mail + elif ctype == MSNAB.ClientType.CircleMember: + member = MSNABSS.CircleMember() + member.Type = MSNAB.MembershipType.Circle + member.State = MSNAB.RelationshipStates.Accepted + member.CircleId = str(contact.AddressBookId) + + mship.Members = mship.new_Members() + mship.Members.Member.append(member) + + return member + + @soap.soapcall() + def DeleteMember(self, msg, client, contact_type, contact_mail, role = None, callback = None, **k): + import msn.AddressBook as MSNAB + messengerService = client.address_book.SelectTargetService(MSNAB.ServiceFilters.Messenger) + msg.ServiceHandle = msg.new_serviceHandle() + msg.ServiceHandle.Id = 0 #messengerService.id + msg.ServiceHandle.Type = messengerService.type + msg.ServiceHandle.ForeignId = messengerService.foreign_id + ' ' + + msg.Memberships = msg.new_memberships() + mship = msg.Memberships.new_Membership() + role = MSNAB.MSNList(role) + mship.MemberRole = role.role + + mship.Members = mship.new_Members() + + baseMember = client.address_book.SelectBaseMember(MSNAB.ServiceFilters.Messenger, contact_mail, contact_type, role.role) + mship_id = 0 + if baseMember is not None and baseMember.MembershipId: + try: + mship_id = int(baseMember.MembershipId) + except ValueError: + pass + + deleteMember = None + str_type = MSNAB.ClientType(contact_type) + if str_type == MSNAB.ClientType.PassportMember: + deleteMember = MSNABSS.PassportMember() + deleteMember.Type = getattr(baseMember, 'Type', MSNAB.MembershipType.Passport) + deleteMember.State = getattr(baseMember, 'State', MSNAB.RelationshipStates.Accepted) + + if mship_id == 0: + deleteMember.PassportName = contact_mail + + elif str_type == MSNAB.ClientType.EmailMember: + deleteMember = MSNABSS.EmailMember() + deleteMember.Type = getattr(baseMember, 'Type', MSNAB.MembershipType.Email) + deleteMember.State = getattr(baseMember, 'State', MSNAB.RelationshipStates.Accepted) + + if mship_id == 0: + deleteMember.Email = contact_mail + + elif str_type == MSNAB.ClientType.PhoneMember: + deleteMember = MSNABSS.PhoneMember() + deleteMember.Type = getattr(baseMember, 'Type', MSNAB.MembershipType.Phone) + deleteMember.State = getattr(baseMember, 'State', MSNAB.RelationshipStates.Accepted) + + if mship_id == 0: + deleteMember.PhoneMember = contact_mail + + deleteMember.MembershipId = mship_id + mship.Members.Member.append(deleteMember) + msg.Memberships.Membership.append(mship) + + @soap.soapcall() + def CreateCircle(self, msg, client, displayName = None, **k): + assert displayName is not None + import msn.AddressBook as MSNAB + msg.Properties = msg.new_properties() + msg.Properties.Domain = MSNAB.DomainIds.WLDomain # 1 + msg.Properties.HostedDomain = MSNAB.Circle.hostDomain # live.com + msg.Properties.IsPresenceEnabled = True + msg.Properties.DisplayName = displayName + + # The meaning of these values is unknown. Copied from captured WLM client operation + msg.Properties.Type = 2 + msg.Properties.MembershipAccess = 0 + msg.Properties.RequestMembershipOption = 2 + + msg.CallerInfo = msg.new_callerInfo() + msg.CallerInfo.PublicDisplayName = client.contact_list.owner.contactInfo.displayName + +class ABService(SharingServiceBase, SSOService): + AppName = "ABService" + AppId = AppIDs.AddressBook + Soap = Sharing_Client + Locator = Sharing_Client.ABServiceLocator + + abId = uuid.UUID(int=0) + + SSO_Domain = SsoDomains.Contacts + SSO_PolicyRef = 'MBI' + + @soap.soapcall() + def ABFindAll(self, msg, client, view = 'Full', deltas = False, lastChange = 0, **k): + msg.set_element_abId(msg.new_abId(str(self.abId))) + msg.AbView = view + msg.DeltasOnly = deltas + log.info("ABService.ABFindALl using lastChange = %r", lastChange) + msg.LastChange = lastChange + + @soap.soapcall() + def ABFindContactsPaged(self, msg, client, abid = None, view = 'Full', deltas = False, lastChange = 0, **k): + msg.set_element_abView("MessengerClient8") + + if abid is None: + abid = str(uuid.UUID(int=0)) + + abid = str(abid).lower() + + if abid == str(self.abId): + msg.set_element_extendedContent("AB AllGroups CircleResult") + fo = msg.new_filterOptions() + msg.set_element_filterOptions(fo) + fo.set_element_ContactFilter(fo.new_ContactFilter()) + + fo.DeltasOnly = deltas + fo.LastChanged = lastChange + fo.ContactFilter.IncludeHiddenContacts = True + fo.ContactFilter.IncludeShellContacts = True + + else: + msg.set_element_extendedContent("AB") + handle = msg.new_abHandle() + handle.ABId = abid + handle.Cid = 0 + handle.Puid = 0 + + msg.AbHandle = handle + + log.info("requesting AddressBook %r", abid) + log.info("ABService.ABFindContactsPaged(%r) using lastChange = %r", abid, lastChange) + + @soap.soapcall() + def ABContactAdd(self, msg, client, account, pending = False, invitation = None, network = None, otheremail = None, **k): + import msn.AddressBook as MSNAB + + msg.AbId = self.abId + msg.Contacts = msg.new_contacts() + c = msg.Contacts.new_Contact() + msg.Contacts.Contact.append(c) + + ci = c.ContactInfo = c.new_contactInfo() + + if isinstance(network, int): + network = MSNAB.ClientType(network) + + if network == MSNAB.ClientType.PassportMember: + ci.ContactType = MSNAB.MessengerContactType.Regular + ci.PassportName = account + ci.IsMessengerUser = True + ci.IsMessengerUserSpecified = True + mmi = ci.MessengerMemberInfo = ci.new_MessengerMemberInfo() + + if not pending and invitation: + mmi.PendingAnnotations = mmi.new_PendingAnnotations() + anno = mmi.PendingAnnotations.new_Annotation() + mmi.PendingAnnotations.Annotation.append(anno) + + anno.Name = MSNAB.AnnotationNames.MSN_IM_InviteMessage + anno.Value = invitation + + mmi.DisplayName = client.contact_list.owner.account + msg.Options = msg.new_options() + msg.Options.EnableAllowListManagement = True + + elif network == MSNAB.ClientType.EmailMember: + emails = ci.Emails = ci.new_emails() + + if otheremail: + email = emails.new_ContactEmail() + emails.ContactEmail.append(email) + email.ContactEmailType = MSNAB.ContactEmailType.ContactEmailOther + email.Email = otheremail + email.PropertiesChanged = MSNAB.PropertyString.Email + email.IsMessengerEnabled = True + email.MessengerEnabledExternally = False + + email = emails.new_ContactEmail() + emails.ContactEmail.append(email) + email.ContactEmailType = MSNAB.ContactEmail.Types.Messenger2 + email.Email = account + email.IsMessengerEnabled = True + email.Capability = int(network) + email.MessengerEnabledExternally = False + email.PropertiesChanged = MSNAB.PropertyString.PropertySeparator.join([ + MSNAB.PropertyString.Email, + MSNAB.PropertyString.IsMessengerEnabled, + MSNAB.PropertyString.Capability, + ]) + + elif network == MSNAB.ClientType.PhoneMember: + ci.Phones = ci.new_phones() + phone = ci.Phones.new_Phone() + ci.Phones.Phone.append(phone) + + phone.ContactPhoneType1 = MSNAB.ContactPhoneType.ContactPhoneMobile + phone.Number = account + phone.IsMessengerEnabled = True + phone.PropertiesChanged = ' '.join([MSNAB.PropertyString.Number, + MSNAB.PropertyString.IsMessengerEnabled]) + + @soap.soapcall() + def ABContactDelete(self, msg, client, abid = None, contacts = None, **k): + if abid is None: + abid = self.abId + + msg.set_element_abId(str(abid).lower()) + + if contacts is None: + contact_ids = [] + else: + contact_ids = [str(c.contactId).lower() for c in contacts] + + msg.Contacts = msg.new_contacts() + for cid in contact_ids: + c = msg.Contacts.new_Contact() + c.set_element_contactId(c.new_contactId(cid)) + msg.Contacts.Contact.append(c) + + @soap.soapcall() + def ABGroupAdd(self, msg, client, name, **k): + import msn.AddressBook as MSNAB + + msg.AbId = self.abId + opts = msg.GroupAddOptions = msg.new_groupAddOptions() + opts.FRenameOnMsgrConflict = False + opts.FRenameOnMsgrConflictSpecified = True + + msg.GroupInfo = msg.new_groupInfo() + gi = msg.GroupInfo.GroupInfo = msg.GroupInfo.new_GroupInfo() + gi.Name = name + gi.FMessenger = False + gi.FMessengerSpecified = True + gi.GroupType = MSNAB.GroupInfo.groupType + gi.Annotations = gi.new_annotations() + anno = gi.Annotations.new_Annotation() + gi.Annotations.Annotation.append(anno) + anno.Name = MSNAB.AnnotationNames.MSN_IM_Display + anno.Value = '1' + + @soap.soapcall() + def ABGroupUpdate(self, msg, client, group_id, name, **k): + msg.AbId = self.abId + msg.Groups = msg.new_groups() + g = msg.Groups.new_Group() + msg.Groups.Group.append(g) + + g.GroupId = group_id + g.PropertiesChanged = "GroupName" + g.GroupInfo = g.new_groupInfo() + g.GroupInfo.Name = name + + @soap.soapcall() + def ABGroupDelete(self, msg, client, group_id, **k): + msg.AbId = self.abId + msg.GroupFilter = msg.new_groupFilter() + msg.GroupFilter.GroupIds = msg.GroupFilter.new_groupIds() + msg.GroupFilter.GroupIds.Guid.append(group_id) + + @soap.soapcall() + def ABGroupContactAdd(self, msg, client, contact, group_id, **k): + msg.AbId = self.abId + msg.GroupFilter = msg.new_groupFilter() + msg.GroupFilter.GroupIds = msg.GroupFilter.new_groupIds() + msg.GroupFilter.GroupIds.Guid.append(group_id) + + msg.Contacts = msg.new_contacts() + msg.Contacts.Contact.append(msg.Contacts.new_Contact()) + msg.Contacts.Contact[-1].ContactId = contact.contactId + + @soap.soapcall() + def ABGroupContactDelete(self, msg, client, abid = None, contact = None, group_id = None, **k): + msg.AbId = str(abid or self.abId).lower() + msg.GroupFilter = msg.new_groupFilter() + msg.GroupFilter.GroupIds = msg.GroupFilter.new_groupIds() + msg.GroupFilter.GroupIds.Guid.append(group_id) + msg.Contacts = msg.new_contacts() + + c = msg.Contacts.new_Contact() + c.set_element_contactId(c.new_contactId(contact.contactId)) + msg.Contacts.Contact.append(c) + + @soap.soapcall() + def ABContactUpdate(self, msg, client, old_contact, new_contact, **kw): + import msn.AddressBook as MSNAB + msg.AbId = self.abId + msg.Contacts = msg.new_contacts() + c = msg.Contacts.new_Contact() + msg.Contacts.Contact.append(c) + + ci = c.ContactInfo = c.new_contactInfo() + + propertiesChanged = [] + + ## Simple Properties + if old_contact.contactInfo.comment != new_contact.contactInfo.comment: + propertiesChanged.append(MSNAB.PropertyString.Comment) + ci.Comment = new_contact.contactInfo.comment + + if old_contact.contactInfo.displayName != new_contact.contactInfo.displayName: + propertiesChanged.append(MSNAB.PropertyString.DisplayName) + ci.DisplayName = new_contact.contactInfo.displayName + + if old_contact.contactInfo.hasSpace != new_contact.contactInfo.hasSpace: + propertiesChanged.append(MSNAB.PropertyString.HasSpace) + ci.HasSpace = new_contact.contactInfo.hasSpace + + ## Annotations + annotationsChanged = ci.new_annotations() + oldAnnotations = {} + for anno in getattr(old_contact.contactInfo, 'annotations', None) or []: + oldAnnotations[anno.Name] = anno.Value + + oldNickName = oldAnnotations.get(MSNAB.AnnotationNames.AB_NickName, None) + if new_contact.DisplayName != oldNickName: + anno = annotationsChanged.new_Annotation() + annotationsChanged.Annotation.append(anno) + anno.Name = MSNAB.AnnotationNames.AB_NickName + anno.Value = new_contact.DisplayName + + if len(annotationsChanged): + propertiesChanged.Add(MSNAB.PropertyString.Annotation) + ci.Annotations = annotationsChanged + + ## Client Type Changes + + ct = MSNAB.ClientType(old_contact.type) + if ct == MSNAB.ClientType.PassportMember: + if old_contact.contactInfo.isMessengerUser != new_contact.contactInfo.isMessengerUser: + propertiesChanged.append(MSNAB.PropertyString.IsMessengerUser) + ci.IsMessengerUser = new_contact.contactInfo.isMessengerUser + ci.IsMessengerUserSpecified = True + propertiesChanged.append(MSNAB.PropertyString.MessengerMemberInfo) + mmi = ci.MessengerMemberInfo = ci.new_MessengerMemberInfo() + mmi.DisplayName = client.contact_list.owner.account + + if old_contact.contactInfo.contactType != new_contact.contactInfo.contactType: + propertiesChanged.append(MSNAB.PropertyString.ContactType) + ci.ContactType = new_contact.contactInfo.contactType + + elif ct == MSNAB.ClientType.EmailMember: + for em in getattr(old_contact.contactInfo, 'emails', None) or []: + if em.email.lower() == new_contact.Mail.lower() and em.isMessengerEnabled and not new_contact.contactInfo.isMessengerUser: + propertiesChanged.append(MSNAB.PropertyString.ContactEmail) + ci.Emails = ci.new_emails() + email = ci.Emails.new_email() + ci.Emails.Email.append(email) + email.ContactEmalType1 = MSNAB.ContactEmailType.Messenger2 + email.IsMessengerEnabled = new_contact.contactInfo.isMessengerUser + email.PropertiesChanged = MSNAB.PropertyString.IsMessengerEnabled + break + + elif ct == MSNAB.ClientType.PhoneMember: + for ph in getattr(old_contact.contactInfo, 'phones', None) or []: + if ph.number == contact.Mail and ph.isMessengerEnabled != new_contact.contactInfo.isMessengerUser: + propertiesChanged.append(MSNAB.PropertiesChanged.ContactPhone) + ci.Phones = ci.new_phones() + phone = ci.Phones.new_phone() + ci.Phones.Phone.append(phone) + phone.ContactPhoneType1 = MSNAB.ContactPhoneType.ContactPhoneMobile + phone.IsMessengerEnabled = new_contact.contactInfo.isMessengerUser + phone.PropertiesChanged = MSNAB.PropertyString.IsMessengerEnabled + break + + if len(propertiesChanged) == 0: + return False + + c.PropertiesChanged = ' '.join(propertiesChanged) + + @soap.soapcall() + def ABAdd(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def UpdateDynamicItem(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def CreateContact(self, msg, client, abid = abId, circleId = abId, email = None, **k): + msg.AbHandle = msg.new_abHandle() + msg.AbHandle.ABId = abid + msg.AbHandle.Puid = 0 + msg.AbHandle.Cid = 0 + + msg.ContactHandle = msg.new_contactHandle() + msg.ContactHandle.Email = email + msg.ContactHandle.Puid = 0 + msg.ContactHandle.Cid = 0 + msg.ContactHandle.CircleId = circleId + + @soap.soapcall() + def ManageWLConnection(self, msg, client, + contactId, + connection, + presence, + action, + relationshipRole, + relationshipType, + Annotations = None, + abid = None, + **k): + + if Annotations is None: + Annotations = [] + + for name, value in Annotations: + if getattr(msg, 'Annotations', None) is None: + msg.Annotations = msg.new_annotations() + + anno = msg.Annotations.new_Annotation() + anno.Name = name + anno.Value = value + msg.Annotations.Annotation.append(anno) + + msg.ContactId = contactId + msg.Connection = connection + msg.Presence = presence + msg.Action = action + msg.RelationshipRole = relationshipRole + msg.RelationshipType = relationshipType + + if abid is not None: + msg.AbHandle = msg.new_abHandle() + msg.AbHandle.ABId = str(abid).lower() + msg.AbHandle.Puid = 0 + msg.AbHandle.Cid = 0 + + @soap.soapcall() + def BreakConnection(self, msg, client, abid = None, contactId = None, block = False, delete = True, **k): + if abid is not None: + msg.AbHandle = msg.new_abHandle() + msg.AbHandle.ABId = str(abid).lower() + msg.AbHandle.Puid = 0 + msg.AbHandle.Cid = 0 + + msg.ContactId = contactId + msg.BlockContact = block + msg.DeleteContact = delete + + @soap.soapcall() + def AddDynamicItem(self, msg, client, **k): + raise NotImplementedError + +import MSNStorageService as MSNSS +import MSNStorageService.StorageService_client as Storage_Client +import MSNStorageService.StorageService_types as Storage_Types + +class StorageService(CacheKeyService, SSOService): + CacheKeyName = 'storage' + CacheKeyDomain = 'tkrdr.storage.msn.com' + + SSO_PolicyRef = 'MBI' + SSO_Domain = SsoDomains.Storage + + AppName = 'StorageService' + AppId = AppIDs.Storage + Soap = Storage_Client + Locator = Storage_Client.StorageServiceLocator + + def serviceHeaders(self, client, scenario = "Initial", **k): + App = Schema.GED(MSNS.MSWS.STORAGE,"StorageApplicationHeader").pyclass() + User = Schema.GED(MSNS.MSWS.STORAGE,"StorageUserHeader").pyclass() + Affinity = Schema.GED(MSNS.MSWS.STORAGE,"AffinityCacheHeader").pyclass() + + App.ApplicationID = self.AppId + App.Scenario = scenario + + User.Puid = 0 + User.TicketToken = client.get_ticket(self.SSO_Domain).token + + self.add_cachekey(client, Affinity) + + return App, User, Affinity + + @soap.soapcall() + def FindDocuments(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def CreateProfile(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def GetProfile(self, msg, client, scenario, **k): + ph = msg.ProfileHandle = msg.new_profileHandle() + alias = ph.Alias = ph.new_Alias() + alias.NameSpace = "MyCidStuff" + alias.Name = str(client.contact_list.owner.CID) + ph.RelationshipName = 'MyProfile' + + pa = msg.ProfileAttributes = msg.new_profileAttributes() + pa.ResourceID = True + pa.DateModified = True + expattr = pa.ExpressionProfileAttributes = pa.new_ExpressionProfileAttributes() + + for attr in ('DateModified', + 'DateModifiedSpecified', + 'DisplayName', + 'DisplayNameLastModified', + 'DisplayNameLastModifiedSpecified', + 'DisplayNameSpecified', + 'Flag', + 'FlagSpecified', + 'PersonalStatus', + 'PersonalStatusLastModified', + 'PersonalStatusLastModifiedSpecified', + 'PersonalStatusSpecified', + 'Photo', + 'PhotoSpecified', + 'Attachments', + 'AttachmentsSpecified', + 'ResourceID', + 'ResourceIDSpecified', + 'StaticUserTilePublicURL', + 'StaticUserTilePublicURLSpecified',): + setattr(expattr, attr, True) + + + @soap.soapcall() + def CreateRelationships(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def UpdateProfile(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def ShareItem(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def UpdateDocument(self, msg, client, resource_id = None, name = None, docstream = None, **k): + msg.Document = msg.new_document() + msg.Document.ResourceID = resource_id + msg.Document.Name = name + docs = msg.Document.DocumentStreams = msg.Document.new_DocumentStreams() + + doctype_name = docstream.get('xsitype') + doctype = getattr(MSNSS, doctype_name, MSNSS.DocumentStream) + + doc = doctype() + docs.DocumentStream.append(doc) + + doc.DocumentStreamType = docstream.get('type') + doc.MimeType = docstream.get('mimetype') + doc.Data = docstream.get('data') + doc.DataSize = docstream.get('datasize', 0) + + @soap.soapcall() + def CreateDocument(self, msg, client, **k): + raise NotImplementedError + + @soap.soapcall() + def DeleteRelationships(self, msg, client, **k): + raise NotImplementedError + +import MSNSpaceService.SpaceService_client as Spaces_Client +import MSNSpaceService.SpaceService_types as Spaces_Types + +class SpacesService(SSOService): +# CacheKeyDomain = 'spaces.live.com' + AppName = "SpaceService" + + SSO_PolicyRef = 'MBI' + SSO_Domain = SsoDomains.Spaces + + AppId = AppIDs.Spaces + Soap = Spaces_Client + Locator = Spaces_Client.SpaceServiceLocator + + def serviceHeaders(self, client, *a, **k): + ticket = client.get_ticket(SsoDomains.Contacts) + + auth = Schema.GED(MSNS.MSWS.SPACES, 'AuthTokenHeader').pyclass() + auth.Token = ticket.token + #auth.AuthPolicy = self.SSO_PolicyRef + + return auth, + + @soap.soapcall() + def GetXmlFeed(self, msg, client, CID, **k): + ri = msg.RefreshInformation = msg.new_refreshInformation() + ri.Cid = CID + ri.StorageAuthCache = '' + ri.Market = 'en-US' #'%s-%s' % (hub.language.lower(), hub.country.upper()) + ri.Brand = '' + ri.MaxElementCount = 15 + ri.MaxCharacterCount = 200 + ri.MaxImageCount = 6 + ri.ApplicationId = 'Messenger Client 8.0' + ri.UpdateAccessedTime = False + + import datetime + yesterday = time.mktime((datetime.datetime.today() - datetime.timedelta(1)).timetuple()) + + ri.SpaceLastViewed = yesterday + ri.ProfileLastViewed = yesterday + ri.ContactProfileLastViewed = yesterday + + ri.IsActiveContact = False + +# fs = ri.ForeignStore = ri.new_foreignStore() +# fs.ItemType = 'Profile' +# fs.ForeignId = 'MyProfile' +# fs.LastChanged = yesterday.isoformat() +# fs.LastViewed = yesterday.isoformat() + + +import MSNOIMStoreService.OIMStoreService_client as OIMStore_Client +import MSNOIMStoreService.OIMStoreService_types as OIMStore_Types + +class OIMService(SSOService): + last_message_number = -1 + + Soap = OIMStore_Client + AppName = "OIMService" + SSO_PolicyRef = "" + SSO_Domain = SsoDomains.OfflineIM + + def serviceHeaders(self, client, to = None, **k): + To = Schema.GED(MSNS.HMNS.OIM, "To").pyclass() + To.set_attribute_memberName(to) + + From = Schema.GED(MSNS.HMNS.OIM, "From").pyclass() + From.set_attribute_memberName(client.self_buddy.name) + From.set_attribute_proxy(client.ns.client_name) + From.set_attribute_msnpVer(client.version) + From.set_attribute_buildVer(client.ns.client_software_version) + + Ticket = Schema.GED(MSNS.HMNS.OIM, "Ticket").pyclass() + Sequence = Schema.GED(NS.WSA.RM, "Sequence").pyclass() + + Sequence.Identifier = Sequence.new_Identifier('http://' + SsoDomains.Messenger) + self.last_message_number = Sequence.MessageNumber = self.last_message_number + 1 + + return To, From, Ticket, Sequence + + def handleHeaders(self, client, headers, **k): + super(OIMService, self).handleHeaders(client = client, headers = headers) + self._recv_headers = headers + + @soap.soapcall() + def Store(self, msg, client, text = None, **k): + pass + +import MSNRSIService.RSIService_client as RSIService_client +import MSNRSIService.RSIService_types as RSIService_types + +class RSIService(SSOService): + Soap = RSIService_client + AppName = 'RSIService' + AppId = AppIDs.RSIService + SSO_PolicyRef = '?id=507' + SSO_Domain = SsoDomains.Web + + + def serviceHeaders(self, client, to = None, **k): + PC = Schema.GED(MSNS.HMNS.RSI, "PassportCookie").pyclass() + ticket = client.get_ticket(self.SSO_Domain) + + token_parts = util.fmt_to_dict('&', '=')(ticket.token) + + PC.T = token_parts.get('t', '') + PC.P = token_parts.get('p', '') + + return PC, + + + @soap.soapcall() + def GetMetadata(self, msg, client, **k): + pass + + @soap.soapcall() + def GetMessage(self, msg, client, message_id, markread = False, **k): + + msg.MessageId = message_id + msg.AlsoMarkAsRead = markread + + @soap.soapcall() + def DeleteMessages(self, msg, client, message_ids, **k): + pass + +class LiveAPIService(SSOService): + # ? http://login.live.com/controls/WebAuth.htm + SSO_PolicyRef = 'MBI' # Just a guess + SSO_Domain = 'api.live.net' + + AppId = '1275182653' + + #def diff --git a/digsby/src/msn/SOAP/wsdl2py.py b/digsby/src/msn/SOAP/wsdl2py.py new file mode 100644 index 0000000..4d3c2ba --- /dev/null +++ b/digsby/src/msn/SOAP/wsdl2py.py @@ -0,0 +1,236 @@ +#!/bin/env python +############################################################################ +# Joshua Boverhof, LBNL +# Monte Goode , LBNL +# See Copyright for copyright notice! +########################################################################### +import exceptions, sys, optparse, os, warnings +import ZSI +from ConfigParser import ConfigParser +from ZSI.generate.wsdl2python import WriteServiceModule, ServiceDescription +from ZSI.wstools import WSDLTools, XMLSchema +from ZSI.wstools.logging import setBasicLoggerDEBUG +from ZSI.generate import containers, utility +from ZSI.generate.utility import NCName_to_ClassName as NC_to_CN, TextProtect + +""" +wsdl2py + +A utility for automatically generating client interface code from a wsdl +definition, and a set of classes representing element declarations and +type definitions. This will produce two files in the current working +directory named after the wsdl definition name. + +eg. + SampleService.py + SampleService_types.py + +""" +warnings.filterwarnings('ignore', '', exceptions.UserWarning) +def SetDebugCallback(option, opt, value, parser, *args, **kwargs): + setBasicLoggerDEBUG() + warnings.resetwarnings() + + +def SetPyclassMetaclass(option, opt, value, parser, *args, **kwargs): + """set up pyclass metaclass for complexTypes""" + from ZSI.generate.containers import TypecodeContainerBase, TypesHeaderContainer + TypecodeContainerBase.metaclass = kwargs['metaclass'] + TypesHeaderContainer.imports.append(\ + 'from %(module)s import %(metaclass)s' %kwargs + ) + + + +def formatSchemaObject(fname, schemaObj): + """ In the case of a 'schema only' generation (-s) this creates + a fake wsdl object that will function w/in the adapters + and allow the generator to do what it needs to do. + """ + + class fake: + pass + + f = fake() + + if fname.rfind('/'): + tmp = fname[fname.rfind('/') + 1 :].split('.') + else: + tmp = fname.split('.') + + f.name = tmp[0] + '_' + tmp[1] + f.types = { schemaObj.targetNamespace : schemaObj } + + return f + +if __name__ == '__main__': + def doCommandLine(**kw): + op = optparse.OptionParser() + + # Basic options + op.add_option("-f", "--file", + action="store", dest="file", default=None, type="string", + help="file to load wsdl from") + op.add_option("-u", "--url", + action="store", dest="url", default=None, type="string", + help="URL to load wsdl from") + op.add_option("-x", "--schema", + action="store_true", dest="schema", default=False, + help="process just the schema from an xsd file [no services]") + op.add_option("-d", "--debug", + action="callback", callback=SetDebugCallback, + help="debug output") + + # WS Options + op.add_option("-a", "--address", + action="store_true", dest="address", default=False, + help="ws-addressing support, must include WS-Addressing schema.") + + # pyclass Metaclass + op.add_option("-b", "--complexType", + action="callback", callback=SetPyclassMetaclass, + callback_kwargs={'module':'ZSI.generate.pyclass', + 'metaclass':'pyclass_type'}, + help="add convenience functions for complexTypes, including Getters, Setters, factory methods, and properties (via metaclass).") + + # Extended generation options + op.add_option("-e", "--extended", + action="store_true", dest="extended", default=False, + help="Do Extended code generation.") + op.add_option("-z", "--aname", + action="store", dest="aname", default=None, type="string", + help="pass in a function for attribute name creation") + op.add_option("-t", "--types", + action="store", dest="types", default=None, type="string", + help="file to load types from") + op.add_option("-o", "--output-dir", + action="store", dest="output_directory", default=".", type="string", + help="file to load types from") + op.add_option("-s", "--simple-naming", + action="store_true", dest="simple_naming", default=False, + help="Simplify generated naming.") + op.add_option("-c", "--clientClassSuffix", + action="store", dest="clientClassSuffix", default=None, type="string", + help="Suffix to use for service client class (default \"SOAP\")") + op.add_option("-m", "--pyclassMapModule", + action="store", dest="pyclassMapModule", default=None, type="string", + help="Python file that maps external python classes to a schema type. The classes are used as the \"pyclass\" for that type. The module should contain a dict() called mapping in the format: mapping = {schemaTypeName:(moduleName.py,className) }") + + (options, args) = op.parse_args() + + print options + + return options, args + +else: + def doCommandLine(complexType=True, debug=False, **kw): + from util import Storage + + if debug: + SetDebugCallback(None,None,None,None) + + if complexType: + SetPyclassMetaclass(None,None,None,None, + **{'module':'ZSI.generate.pyclass', + 'metaclass':'pyclass_type'} + ) + + options = Storage( + file=None, + url=None, + schema=False, + simple_naming=False, + clientClassSuffix=None, + aname=None, + pyclassMapModule=None, + address=False, + extended=False, + types=None, + output_directory='.', + ) + + options.update(kw) + + return options, () + + +def main(**kw): + """ From a wsdl definition create a wsdl object and run the wsdl2python + generator. + """ + + options, args = doCommandLine(**kw) + + location = options.file or options.url + if options.schema is True: + reader = XMLSchema.SchemaReader(base_url=location) + else: + reader = WSDLTools.WSDLReader() + + wsdl = None + if options.file is not None: + wsdl = reader.loadFromFile(location) + elif options.url is not None: + wsdl = reader.loadFromURL(location) + + if options.simple_naming: + # Use a different client suffix + WriteServiceModule.client_module_suffix = "_client" + # Write messages definitions to a separate file. + ServiceDescription.separate_messages = True + # Use more simple type and element class names + containers.SetTypeNameFunc( lambda n: '%s_' %(NC_to_CN(n)) ) + containers.SetElementNameFunc( lambda n: '%s' %(NC_to_CN(n)) ) + # Don't add "_" to the attribute name (remove when --aname works well) + containers.ContainerBase.func_aname = lambda instnc,n: TextProtect(str(n)) + # write out the modules with their names rather than their number. + utility.namespace_name = lambda cls, ns: utility.Namespace2ModuleName(ns) + + if options.clientClassSuffix: + from ZSI.generate.containers import ServiceContainerBase + ServiceContainerBase.clientClassSuffix = options.clientClassSuffix + + assert wsdl is not None, 'Must specify WSDL either with --file or --url' + if options.schema is True: + wsdl = formatSchemaObject(location, wsdl) + + if options.aname is not None: + args = options.aname.rsplit('.',1) + assert len(args) == 2, 'expecting module.function' + # The following exec causes a syntax error. + #exec('from %s import %s as FUNC' %(args[0],args[1])) + assert callable(FUNC),\ + '%s must be a callable method with one string parameter' %options.aname + from ZSI.generate.containers import TypecodeContainerBase + TypecodeContainerBase.func_aname = staticmethod(FUNC) + + if options.pyclassMapModule != None: + mod = __import__(options.pyclassMapModule) + components = options.pyclassMapModule.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + extPyClasses = mod.mapping + else: + extPyClasses = None + + wsm = WriteServiceModule(wsdl, addressing=options.address, do_extended=options.extended, extPyClasses=extPyClasses) + if options.types != None: + wsm.setTypesModuleName(options.types) + if options.schema is False: + fd = open(os.path.join(options.output_directory, '%s.py' %wsm.getClientModuleName()), 'w+') + # simple naming writes the messages to a separate file + if not options.simple_naming: + wsm.writeClient(fd) + else: # provide a separate file to store messages to. + msg_fd = open(os.path.join(options.output_directory, '%s.py' %wsm.getMessagesModuleName()), 'w+') + wsm.writeClient(fd, msg_fd=msg_fd) + msg_fd.close() + fd.close() + + fd = open( os.path.join(options.output_directory, '%s.py' %wsm.getTypesModuleName()), 'w+') + wsm.writeTypes(fd) + fd.close() + + +if __name__ == '__main__': + main() diff --git a/digsby/src/msn/SOAP/xml/__init__.py b/digsby/src/msn/SOAP/xml/__init__.py new file mode 100644 index 0000000..a92628d --- /dev/null +++ b/digsby/src/msn/SOAP/xml/__init__.py @@ -0,0 +1,11 @@ +#from wsu import wsu +#from wsu_oasis import wsu_oasis +#from wsrm import wsrm +#from ds import ds +##from saml import saml +#from soapenv import wssoapenv as soapenv +#from wsa import wsa +#from wsc import wsc +#from wsp import wsp +#from wsse import wsse +#from wst import wst diff --git a/digsby/src/msn/SOAP/xml/ds.py b/digsby/src/msn/SOAP/xml/ds.py new file mode 100644 index 0000000..9d74c47 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/ds.py @@ -0,0 +1,911 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://www.w3.org/2000/09/xmldsig# +############################## + +class ds: + targetNamespace = NS.DSIG.BASE + + class CryptoBinary_Def(ZSI.TC.Base64String, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "CryptoBinary") + def __init__(self, pname, **kw): + ZSI.TC.Base64String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class SignatureType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "SignatureType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.SignatureType_Def.schema + TClist = [GED(NS.DSIG.BASE,"SignedInfo",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"SignatureValue",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"KeyInfo",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"Object",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SignedInfo = None + self._SignatureValue = None + self._KeyInfo = None + self._Object = None + return + Holder.__name__ = "SignatureType_Holder" + self.pyclass = Holder + + class SignatureValueType_Def(ZSI.TC.Base64String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.DSIG.BASE + type = (schema, "SignatureValueType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + ZSI.TC.Base64String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class SignedInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "SignedInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.SignedInfoType_Def.schema + TClist = [GED(NS.DSIG.BASE,"CanonicalizationMethod",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"SignatureMethod",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"Reference",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CanonicalizationMethod = None + self._SignatureMethod = None + self._Reference = None + return + Holder.__name__ = "SignedInfoType_Holder" + self.pyclass = Holder + + class CanonicalizationMethodType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "CanonicalizationMethodType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.CanonicalizationMethodType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="strict")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Algorithm"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "CanonicalizationMethodType_Holder" + self.pyclass = Holder + + class SignatureMethodType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "SignatureMethodType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.SignatureMethodType_Def.schema + TClist = [GTD(NS.DSIG.BASE,"HMACOutputLengthType",lazy=False)(pname=(ns,"HMACOutputLength"), aname="_HMACOutputLength", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="strict")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Algorithm"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._HMACOutputLength = None + self._any = [] + return + Holder.__name__ = "SignatureMethodType_Holder" + self.pyclass = Holder + + class ReferenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "ReferenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.ReferenceType_Def.schema + TClist = [GED(NS.DSIG.BASE,"Transforms",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"DigestMethod",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"DigestValue",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + self.attribute_typecode_dict["URI"] = ZSI.TC.URI() + self.attribute_typecode_dict["Type"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Transforms = None + self._DigestMethod = None + self._DigestValue = None + return + Holder.__name__ = "ReferenceType_Holder" + self.pyclass = Holder + + class TransformsType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "TransformsType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.TransformsType_Def.schema + TClist = [GED(NS.DSIG.BASE,"Transform",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Transform = None + return + Holder.__name__ = "TransformsType_Holder" + self.pyclass = Holder + + class TransformType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "TransformType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.TransformType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax"), ZSI.TC.String(pname=(ns,"XPath"), aname="_XPath", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Algorithm"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + self._XPath = [] + return + Holder.__name__ = "TransformType_Holder" + self.pyclass = Holder + + class DigestMethodType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "DigestMethodType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.DigestMethodType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Algorithm"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "DigestMethodType_Holder" + self.pyclass = Holder + + class DigestValueType_Def(ZSI.TC.Base64String, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "DigestValueType") + def __init__(self, pname, **kw): + ZSI.TC.Base64String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class KeyInfoType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "KeyInfoType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.KeyInfoType_Def.schema + TClist = [GED(NS.DSIG.BASE,"KeyName",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"KeyValue",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"RetrievalMethod",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"X509Data",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"PGPData",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"SPKIData",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"MgmtData",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._KeyName = None + self._KeyValue = None + self._RetrievalMethod = None + self._X509Data = None + self._PGPData = None + self._SPKIData = None + self._MgmtData = None + self._any = [] + return + Holder.__name__ = "KeyInfoType_Holder" + self.pyclass = Holder + + class KeyValueType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "KeyValueType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.KeyValueType_Def.schema + TClist = [GED(NS.DSIG.BASE,"DSAKeyValue",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"RSAKeyValue",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs=1, nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._DSAKeyValue = None + self._RSAKeyValue = None + self._any = None + return + Holder.__name__ = "KeyValueType_Holder" + self.pyclass = Holder + + class RetrievalMethodType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "RetrievalMethodType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.RetrievalMethodType_Def.schema + TClist = [GED(NS.DSIG.BASE,"Transforms",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["URI"] = ZSI.TC.URI() + self.attribute_typecode_dict["Type"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Transforms = None + return + Holder.__name__ = "RetrievalMethodType_Holder" + self.pyclass = Holder + + class X509DataType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "X509DataType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.X509DataType_Def.schema + TClist = [GTD(NS.DSIG.BASE,"X509IssuerSerialType",lazy=False)(pname=(ns,"X509IssuerSerial"), aname="_X509IssuerSerial", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Base64String(pname=(ns,"X509SKI"), aname="_X509SKI", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname=(ns,"X509SubjectName"), aname="_X509SubjectName", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Base64String(pname=(ns,"X509Certificate"), aname="_X509Certificate", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.Base64String(pname=(ns,"X509CRL"), aname="_X509CRL", minOccurs=0, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._X509IssuerSerial = [] + self._X509SKI = [] + self._X509SubjectName = [] + self._X509Certificate = [] + self._X509CRL = [] + self._any = [] + return + Holder.__name__ = "X509DataType_Holder" + self.pyclass = Holder + + class X509IssuerSerialType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "X509IssuerSerialType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.X509IssuerSerialType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"X509IssuerName"), aname="_X509IssuerName", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iinteger(pname=(ns,"X509SerialNumber"), aname="_X509SerialNumber", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._X509IssuerName = None + self._X509SerialNumber = None + return + Holder.__name__ = "X509IssuerSerialType_Holder" + self.pyclass = Holder + + class PGPDataType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "PGPDataType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.PGPDataType_Def.schema + TClist = [ZSI.TC.Base64String(pname=(ns,"PGPKeyID"), aname="_PGPKeyID", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.Base64String(pname=(ns,"PGPKeyPacket"), aname="_PGPKeyPacket", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._PGPKeyID = None + self._PGPKeyPacket = None + self._any = [] + self._PGPKeyPacket = None + self._any = [] + return + Holder.__name__ = "PGPDataType_Holder" + self.pyclass = Holder + + class SPKIDataType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "SPKIDataType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.SPKIDataType_Def.schema + TClist = [ZSI.TC.Base64String(pname=(ns,"SPKISexp"), aname="_SPKISexp", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SPKISexp = [] + self._any = [] + return + Holder.__name__ = "SPKIDataType_Holder" + self.pyclass = Holder + + class ObjectType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "ObjectType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.ObjectType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + self.attribute_typecode_dict["MimeType"] = ZSI.TC.String() + self.attribute_typecode_dict["Encoding"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "ObjectType_Holder" + self.pyclass = Holder + + class ManifestType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "ManifestType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.ManifestType_Def.schema + TClist = [GED(NS.DSIG.BASE,"Reference",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Reference = None + return + Holder.__name__ = "ManifestType_Holder" + self.pyclass = Holder + + class SignaturePropertiesType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "SignaturePropertiesType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.SignaturePropertiesType_Def.schema + TClist = [GED(NS.DSIG.BASE,"SignatureProperty",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SignatureProperty = None + return + Holder.__name__ = "SignaturePropertiesType_Holder" + self.pyclass = Holder + + class SignaturePropertyType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "SignaturePropertyType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.SignaturePropertyType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Target"] = ZSI.TC.URI() + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, mixed=True, mixed_aname="_text", **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "SignaturePropertyType_Holder" + self.pyclass = Holder + + class HMACOutputLengthType_Def(ZSI.TCnumbers.Iinteger, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "HMACOutputLengthType") + def __init__(self, pname, **kw): + ZSI.TCnumbers.Iinteger.__init__(self, pname, pyclass=None, **kw) + class Holder(int): + typecode = self + self.pyclass = Holder + + class DSAKeyValueType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "DSAKeyValueType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.DSAKeyValueType_Def.schema + TClist = [GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"P"), aname="_P", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"Q"), aname="_Q", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"G"), aname="_G", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"Y"), aname="_Y", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"J"), aname="_J", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"Seed"), aname="_Seed", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"PgenCounter"), aname="_PgenCounter", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._P = None + self._Q = None + self._G = None + self._Y = None + self._J = None + self._Seed = None + self._PgenCounter = None + return + Holder.__name__ = "DSAKeyValueType_Holder" + self.pyclass = Holder + + class RSAKeyValueType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.DSIG.BASE + type = (schema, "RSAKeyValueType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = ds.RSAKeyValueType_Def.schema + TClist = [GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"Modulus"), aname="_Modulus", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.DSIG.BASE,"CryptoBinary",lazy=False)(pname=(ns,"Exponent"), aname="_Exponent", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Modulus = None + self._Exponent = None + return + Holder.__name__ = "RSAKeyValueType_Holder" + self.pyclass = Holder + + class Signature_Dec(ElementDeclaration): + literal = "Signature" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"Signature") + kw["aname"] = "_Signature" + if ds.SignatureType_Def not in ds.Signature_Dec.__bases__: + bases = list(ds.Signature_Dec.__bases__) + bases.insert(0, ds.SignatureType_Def) + ds.Signature_Dec.__bases__ = tuple(bases) + + ds.SignatureType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Signature_Dec_Holder" + + class SignatureValue_Dec(ElementDeclaration): + literal = "SignatureValue" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"SignatureValue") + kw["aname"] = "_SignatureValue" + if ds.SignatureValueType_Def not in ds.SignatureValue_Dec.__bases__: + bases = list(ds.SignatureValue_Dec.__bases__) + bases.insert(0, ds.SignatureValueType_Def) + ds.SignatureValue_Dec.__bases__ = tuple(bases) + + ds.SignatureValueType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SignatureValue_Dec_Holder" + + class SignedInfo_Dec(ElementDeclaration): + literal = "SignedInfo" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"SignedInfo") + kw["aname"] = "_SignedInfo" + if ds.SignedInfoType_Def not in ds.SignedInfo_Dec.__bases__: + bases = list(ds.SignedInfo_Dec.__bases__) + bases.insert(0, ds.SignedInfoType_Def) + ds.SignedInfo_Dec.__bases__ = tuple(bases) + + ds.SignedInfoType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SignedInfo_Dec_Holder" + + class CanonicalizationMethod_Dec(ElementDeclaration): + literal = "CanonicalizationMethod" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"CanonicalizationMethod") + kw["aname"] = "_CanonicalizationMethod" + if ds.CanonicalizationMethodType_Def not in ds.CanonicalizationMethod_Dec.__bases__: + bases = list(ds.CanonicalizationMethod_Dec.__bases__) + bases.insert(0, ds.CanonicalizationMethodType_Def) + ds.CanonicalizationMethod_Dec.__bases__ = tuple(bases) + + ds.CanonicalizationMethodType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "CanonicalizationMethod_Dec_Holder" + + class SignatureMethod_Dec(ElementDeclaration): + literal = "SignatureMethod" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"SignatureMethod") + kw["aname"] = "_SignatureMethod" + if ds.SignatureMethodType_Def not in ds.SignatureMethod_Dec.__bases__: + bases = list(ds.SignatureMethod_Dec.__bases__) + bases.insert(0, ds.SignatureMethodType_Def) + ds.SignatureMethod_Dec.__bases__ = tuple(bases) + + ds.SignatureMethodType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SignatureMethod_Dec_Holder" + + class Reference_Dec(ElementDeclaration): + literal = "Reference" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"Reference") + kw["aname"] = "_Reference" + if ds.ReferenceType_Def not in ds.Reference_Dec.__bases__: + bases = list(ds.Reference_Dec.__bases__) + bases.insert(0, ds.ReferenceType_Def) + ds.Reference_Dec.__bases__ = tuple(bases) + + ds.ReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Reference_Dec_Holder" + + class Transforms_Dec(ElementDeclaration): + literal = "Transforms" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"Transforms") + kw["aname"] = "_Transforms" + if ds.TransformsType_Def not in ds.Transforms_Dec.__bases__: + bases = list(ds.Transforms_Dec.__bases__) + bases.insert(0, ds.TransformsType_Def) + ds.Transforms_Dec.__bases__ = tuple(bases) + + ds.TransformsType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Transforms_Dec_Holder" + + class Transform_Dec(ElementDeclaration): + literal = "Transform" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"Transform") + kw["aname"] = "_Transform" + if ds.TransformType_Def not in ds.Transform_Dec.__bases__: + bases = list(ds.Transform_Dec.__bases__) + bases.insert(0, ds.TransformType_Def) + ds.Transform_Dec.__bases__ = tuple(bases) + + ds.TransformType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Transform_Dec_Holder" + + class DigestMethod_Dec(ElementDeclaration): + literal = "DigestMethod" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"DigestMethod") + kw["aname"] = "_DigestMethod" + if ds.DigestMethodType_Def not in ds.DigestMethod_Dec.__bases__: + bases = list(ds.DigestMethod_Dec.__bases__) + bases.insert(0, ds.DigestMethodType_Def) + ds.DigestMethod_Dec.__bases__ = tuple(bases) + + ds.DigestMethodType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DigestMethod_Dec_Holder" + + class DigestValue_Dec(ElementDeclaration): + literal = "DigestValue" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"DigestValue") + kw["aname"] = "_DigestValue" + if ds.DigestValueType_Def not in ds.DigestValue_Dec.__bases__: + bases = list(ds.DigestValue_Dec.__bases__) + bases.insert(0, ds.DigestValueType_Def) + ds.DigestValue_Dec.__bases__ = tuple(bases) + + ds.DigestValueType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DigestValue_Dec_Holder" + + class KeyInfo_Dec(ElementDeclaration): + literal = "KeyInfo" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"KeyInfo") + kw["aname"] = "_KeyInfo" + if ds.KeyInfoType_Def not in ds.KeyInfo_Dec.__bases__: + bases = list(ds.KeyInfo_Dec.__bases__) + bases.insert(0, ds.KeyInfoType_Def) + ds.KeyInfo_Dec.__bases__ = tuple(bases) + + ds.KeyInfoType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "KeyInfo_Dec_Holder" + + class KeyName_Dec(ZSI.TC.String, ElementDeclaration): + literal = "KeyName" + schema = NS.DSIG.BASE + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"KeyName") + kw["aname"] = "_KeyName" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_KeyName_immutable_holder" + ZSI.TC.String.__init__(self, **kw) + + class MgmtData_Dec(ZSI.TC.String, ElementDeclaration): + literal = "MgmtData" + schema = NS.DSIG.BASE + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"MgmtData") + kw["aname"] = "_MgmtData" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_MgmtData_immutable_holder" + ZSI.TC.String.__init__(self, **kw) + + class KeyValue_Dec(ElementDeclaration): + literal = "KeyValue" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"KeyValue") + kw["aname"] = "_KeyValue" + if ds.KeyValueType_Def not in ds.KeyValue_Dec.__bases__: + bases = list(ds.KeyValue_Dec.__bases__) + bases.insert(0, ds.KeyValueType_Def) + ds.KeyValue_Dec.__bases__ = tuple(bases) + + ds.KeyValueType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "KeyValue_Dec_Holder" + + class RetrievalMethod_Dec(ElementDeclaration): + literal = "RetrievalMethod" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"RetrievalMethod") + kw["aname"] = "_RetrievalMethod" + if ds.RetrievalMethodType_Def not in ds.RetrievalMethod_Dec.__bases__: + bases = list(ds.RetrievalMethod_Dec.__bases__) + bases.insert(0, ds.RetrievalMethodType_Def) + ds.RetrievalMethod_Dec.__bases__ = tuple(bases) + + ds.RetrievalMethodType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RetrievalMethod_Dec_Holder" + + class X509Data_Dec(ElementDeclaration): + literal = "X509Data" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"X509Data") + kw["aname"] = "_X509Data" + if ds.X509DataType_Def not in ds.X509Data_Dec.__bases__: + bases = list(ds.X509Data_Dec.__bases__) + bases.insert(0, ds.X509DataType_Def) + ds.X509Data_Dec.__bases__ = tuple(bases) + + ds.X509DataType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "X509Data_Dec_Holder" + + class PGPData_Dec(ElementDeclaration): + literal = "PGPData" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"PGPData") + kw["aname"] = "_PGPData" + if ds.PGPDataType_Def not in ds.PGPData_Dec.__bases__: + bases = list(ds.PGPData_Dec.__bases__) + bases.insert(0, ds.PGPDataType_Def) + ds.PGPData_Dec.__bases__ = tuple(bases) + + ds.PGPDataType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "PGPData_Dec_Holder" + + class SPKIData_Dec(ElementDeclaration): + literal = "SPKIData" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"SPKIData") + kw["aname"] = "_SPKIData" + if ds.SPKIDataType_Def not in ds.SPKIData_Dec.__bases__: + bases = list(ds.SPKIData_Dec.__bases__) + bases.insert(0, ds.SPKIDataType_Def) + ds.SPKIData_Dec.__bases__ = tuple(bases) + + ds.SPKIDataType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SPKIData_Dec_Holder" + + class Object_Dec(ElementDeclaration): + literal = "Object" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"Object") + kw["aname"] = "_Object" + if ds.ObjectType_Def not in ds.Object_Dec.__bases__: + bases = list(ds.Object_Dec.__bases__) + bases.insert(0, ds.ObjectType_Def) + ds.Object_Dec.__bases__ = tuple(bases) + + ds.ObjectType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Object_Dec_Holder" + + class Manifest_Dec(ElementDeclaration): + literal = "Manifest" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"Manifest") + kw["aname"] = "_Manifest" + if ds.ManifestType_Def not in ds.Manifest_Dec.__bases__: + bases = list(ds.Manifest_Dec.__bases__) + bases.insert(0, ds.ManifestType_Def) + ds.Manifest_Dec.__bases__ = tuple(bases) + + ds.ManifestType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Manifest_Dec_Holder" + + class SignatureProperties_Dec(ElementDeclaration): + literal = "SignatureProperties" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"SignatureProperties") + kw["aname"] = "_SignatureProperties" + if ds.SignaturePropertiesType_Def not in ds.SignatureProperties_Dec.__bases__: + bases = list(ds.SignatureProperties_Dec.__bases__) + bases.insert(0, ds.SignaturePropertiesType_Def) + ds.SignatureProperties_Dec.__bases__ = tuple(bases) + + ds.SignaturePropertiesType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SignatureProperties_Dec_Holder" + + class SignatureProperty_Dec(ElementDeclaration): + literal = "SignatureProperty" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"SignatureProperty") + kw["aname"] = "_SignatureProperty" + if ds.SignaturePropertyType_Def not in ds.SignatureProperty_Dec.__bases__: + bases = list(ds.SignatureProperty_Dec.__bases__) + bases.insert(0, ds.SignaturePropertyType_Def) + ds.SignatureProperty_Dec.__bases__ = tuple(bases) + + ds.SignaturePropertyType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SignatureProperty_Dec_Holder" + + class DSAKeyValue_Dec(ElementDeclaration): + literal = "DSAKeyValue" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"DSAKeyValue") + kw["aname"] = "_DSAKeyValue" + if ds.DSAKeyValueType_Def not in ds.DSAKeyValue_Dec.__bases__: + bases = list(ds.DSAKeyValue_Dec.__bases__) + bases.insert(0, ds.DSAKeyValueType_Def) + ds.DSAKeyValue_Dec.__bases__ = tuple(bases) + + ds.DSAKeyValueType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DSAKeyValue_Dec_Holder" + + class RSAKeyValue_Dec(ElementDeclaration): + literal = "RSAKeyValue" + schema = NS.DSIG.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.DSIG.BASE,"RSAKeyValue") + kw["aname"] = "_RSAKeyValue" + if ds.RSAKeyValueType_Def not in ds.RSAKeyValue_Dec.__bases__: + bases = list(ds.RSAKeyValue_Dec.__bases__) + bases.insert(0, ds.RSAKeyValueType_Def) + ds.RSAKeyValue_Dec.__bases__ = tuple(bases) + + ds.RSAKeyValueType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RSAKeyValue_Dec_Holder" + +# end class ds (tns: http://www.w3.org/2000/09/xmldsig#) diff --git a/digsby/src/msn/SOAP/xml/saml.py b/digsby/src/msn/SOAP/xml/saml.py new file mode 100644 index 0000000..afa57ca --- /dev/null +++ b/digsby/src/msn/SOAP/xml/saml.py @@ -0,0 +1,781 @@ +import ZSI +import ZSI.TCcompound +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# urn:oasis:names:tc:SAML:1.0:assertion +############################## + +class saml: + targetNamespace = "urn:oasis:names:tc:SAML:1.0:assertion" + + class DecisionType_Def(ZSI.TC.String, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "DecisionType") + def __init__(self, pname, **kw): + ZSI.TC.String.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class AssertionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AssertionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.AssertionType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","Conditions",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","Advice",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","Statement",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","SubjectStatement",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","AuthenticationStatement",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","AuthorizationDecisionStatement",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","AttributeStatement",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"Signature",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["MajorVersion"] = ZSI.TCnumbers.Iinteger() + self.attribute_typecode_dict["MinorVersion"] = ZSI.TCnumbers.Iinteger() + self.attribute_typecode_dict["AssertionID"] = ZSI.TC.AnyType() + self.attribute_typecode_dict["Issuer"] = ZSI.TC.String() + self.attribute_typecode_dict["IssueInstant"] = ZSI.TCtimes.gDateTime() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Conditions = None + self._Advice = None + self._Statement = None + self._SubjectStatement = None + self._AuthenticationStatement = None + self._AuthorizationDecisionStatement = None + self._AttributeStatement = None + self._Signature = None + return + Holder.__name__ = "AssertionType_Holder" + self.pyclass = Holder + + class ConditionsType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "ConditionsType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.ConditionsType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","AudienceRestrictionCondition",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","DoNotCacheCondition",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","Condition",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["NotBefore"] = ZSI.TCtimes.gDateTime() + self.attribute_typecode_dict["NotOnOrAfter"] = ZSI.TCtimes.gDateTime() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._AudienceRestrictionCondition = None + self._DoNotCacheCondition = None + self._Condition = None + return + Holder.__name__ = "ConditionsType_Holder" + self.pyclass = Holder + + class ConditionAbstractType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "ConditionAbstractType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.ConditionAbstractType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "ConditionAbstractType_Holder" + self.pyclass = Holder + + class AudienceRestrictionConditionType_Def(TypeDefinition): + #complexType/complexContent extension + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AudienceRestrictionConditionType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = saml.AudienceRestrictionConditionType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","Audience",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if saml.ConditionAbstractType_Def not in saml.AudienceRestrictionConditionType_Def.__bases__: + bases = list(saml.AudienceRestrictionConditionType_Def.__bases__) + bases.insert(0, saml.ConditionAbstractType_Def) + saml.AudienceRestrictionConditionType_Def.__bases__ = tuple(bases) + + saml.ConditionAbstractType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class DoNotCacheConditionType_Def(TypeDefinition): + #complexType/complexContent extension + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "DoNotCacheConditionType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = saml.DoNotCacheConditionType_Def.schema + TClist = [] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if saml.ConditionAbstractType_Def not in saml.DoNotCacheConditionType_Def.__bases__: + bases = list(saml.DoNotCacheConditionType_Def.__bases__) + bases.insert(0, saml.ConditionAbstractType_Def) + saml.DoNotCacheConditionType_Def.__bases__ = tuple(bases) + + saml.ConditionAbstractType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class AdviceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AdviceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.AdviceType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","AssertionIDReference",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","Assertion",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._AssertionIDReference = None + self._Assertion = None + self._any = [] + return + Holder.__name__ = "AdviceType_Holder" + self.pyclass = Holder + + class StatementAbstractType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "StatementAbstractType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.StatementAbstractType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "StatementAbstractType_Holder" + self.pyclass = Holder + + class SubjectStatementAbstractType_Def(TypeDefinition): + #complexType/complexContent extension + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "SubjectStatementAbstractType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = saml.SubjectStatementAbstractType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","Subject",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if saml.StatementAbstractType_Def not in saml.SubjectStatementAbstractType_Def.__bases__: + bases = list(saml.SubjectStatementAbstractType_Def.__bases__) + bases.insert(0, saml.StatementAbstractType_Def) + saml.SubjectStatementAbstractType_Def.__bases__ = tuple(bases) + + saml.StatementAbstractType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class SubjectType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "SubjectType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.SubjectType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","NameIdentifier",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","SubjectConfirmation",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","SubjectConfirmation",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._NameIdentifier = None + self._SubjectConfirmation = None + self._SubjectConfirmation = None + return + Holder.__name__ = "SubjectType_Holder" + self.pyclass = Holder + + class NameIdentifierType_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "NameIdentifierType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["NameQualifier"] = ZSI.TC.String() + self.attribute_typecode_dict["Format"] = ZSI.TC.URI() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class SubjectConfirmationType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "SubjectConfirmationType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.SubjectConfirmationType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","ConfirmationMethod",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","SubjectConfirmationData",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"KeyInfo",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._ConfirmationMethod = None + self._SubjectConfirmationData = None + self._KeyInfo = None + return + Holder.__name__ = "SubjectConfirmationType_Holder" + self.pyclass = Holder + + class AuthenticationStatementType_Def(TypeDefinition): + #complexType/complexContent extension + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AuthenticationStatementType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = saml.AuthenticationStatementType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","SubjectLocality",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","AuthorityBinding",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["AuthenticationMethod"] = ZSI.TC.URI() + self.attribute_typecode_dict["AuthenticationInstant"] = ZSI.TCtimes.gDateTime() + if saml.SubjectStatementAbstractType_Def not in saml.AuthenticationStatementType_Def.__bases__: + bases = list(saml.AuthenticationStatementType_Def.__bases__) + bases.insert(0, saml.SubjectStatementAbstractType_Def) + saml.AuthenticationStatementType_Def.__bases__ = tuple(bases) + + saml.SubjectStatementAbstractType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class SubjectLocalityType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "SubjectLocalityType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.SubjectLocalityType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["IPAddress"] = ZSI.TC.String() + self.attribute_typecode_dict["DNSAddress"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "SubjectLocalityType_Holder" + self.pyclass = Holder + + class AuthorityBindingType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AuthorityBindingType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.AuthorityBindingType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["AuthorityKind"] = ZSI.TC.QName() + self.attribute_typecode_dict["Location"] = ZSI.TC.URI() + self.attribute_typecode_dict["Binding"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "AuthorityBindingType_Holder" + self.pyclass = Holder + + class AuthorizationDecisionStatementType_Def(TypeDefinition): + #complexType/complexContent extension + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AuthorizationDecisionStatementType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = saml.AuthorizationDecisionStatementType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","Action",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","Evidence",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Resource"] = ZSI.TC.URI() + self.attribute_typecode_dict["Decision"] = saml.DecisionType_Def(None) + if saml.SubjectStatementAbstractType_Def not in saml.AuthorizationDecisionStatementType_Def.__bases__: + bases = list(saml.AuthorizationDecisionStatementType_Def.__bases__) + bases.insert(0, saml.SubjectStatementAbstractType_Def) + saml.AuthorizationDecisionStatementType_Def.__bases__ = tuple(bases) + + saml.SubjectStatementAbstractType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class ActionType_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "ActionType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Namespace"] = ZSI.TC.URI() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class EvidenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "EvidenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.EvidenceType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","AssertionIDReference",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), GED("urn:oasis:names:tc:SAML:1.0:assertion","Assertion",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._AssertionIDReference = None + self._Assertion = None + return + Holder.__name__ = "EvidenceType_Holder" + self.pyclass = Holder + + class AttributeStatementType_Def(TypeDefinition): + #complexType/complexContent extension + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AttributeStatementType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = saml.AttributeStatementType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","Attribute",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if saml.SubjectStatementAbstractType_Def not in saml.AttributeStatementType_Def.__bases__: + bases = list(saml.AttributeStatementType_Def.__bases__) + bases.insert(0, saml.SubjectStatementAbstractType_Def) + saml.AttributeStatementType_Def.__bases__ = tuple(bases) + + saml.SubjectStatementAbstractType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class AttributeDesignatorType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AttributeDesignatorType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = saml.AttributeDesignatorType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["AttributeName"] = ZSI.TC.String() + self.attribute_typecode_dict["AttributeNamespace"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "AttributeDesignatorType_Holder" + self.pyclass = Holder + + class AttributeType_Def(TypeDefinition): + #complexType/complexContent extension + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + type = (schema, "AttributeType") + def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw): + ns = saml.AttributeType_Def.schema + TClist = [GED("urn:oasis:names:tc:SAML:1.0:assertion","AttributeValue",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + attributes = self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + if saml.AttributeDesignatorType_Def not in saml.AttributeType_Def.__bases__: + bases = list(saml.AttributeType_Def.__bases__) + bases.insert(0, saml.AttributeDesignatorType_Def) + saml.AttributeType_Def.__bases__ = tuple(bases) + + saml.AttributeDesignatorType_Def.__init__(self, pname, ofwhat=TClist, extend=True, attributes=attributes, **kw) + + class AssertionIDReference_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "AssertionIDReference" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AssertionIDReference") + kw["aname"] = "_AssertionIDReference" + ZSI.TC.AnyType.__init__(self, **kw) + + class Assertion_Dec(ElementDeclaration): + literal = "Assertion" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Assertion") + kw["aname"] = "_Assertion" + if saml.AssertionType_Def not in saml.Assertion_Dec.__bases__: + bases = list(saml.Assertion_Dec.__bases__) + bases.insert(0, saml.AssertionType_Def) + saml.Assertion_Dec.__bases__ = tuple(bases) + + saml.AssertionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Assertion_Dec_Holder" + + class Conditions_Dec(ElementDeclaration): + literal = "Conditions" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Conditions") + kw["aname"] = "_Conditions" + if saml.ConditionsType_Def not in saml.Conditions_Dec.__bases__: + bases = list(saml.Conditions_Dec.__bases__) + bases.insert(0, saml.ConditionsType_Def) + saml.Conditions_Dec.__bases__ = tuple(bases) + + saml.ConditionsType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Conditions_Dec_Holder" + + class Condition_Dec(ElementDeclaration): + literal = "Condition" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Condition") + kw["aname"] = "_Condition" + if saml.ConditionAbstractType_Def not in saml.Condition_Dec.__bases__: + bases = list(saml.Condition_Dec.__bases__) + bases.insert(0, saml.ConditionAbstractType_Def) + saml.Condition_Dec.__bases__ = tuple(bases) + + saml.ConditionAbstractType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Condition_Dec_Holder" + + class AudienceRestrictionCondition_Dec(ElementDeclaration): + literal = "AudienceRestrictionCondition" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AudienceRestrictionCondition") + kw["aname"] = "_AudienceRestrictionCondition" + if saml.AudienceRestrictionConditionType_Def not in saml.AudienceRestrictionCondition_Dec.__bases__: + bases = list(saml.AudienceRestrictionCondition_Dec.__bases__) + bases.insert(0, saml.AudienceRestrictionConditionType_Def) + saml.AudienceRestrictionCondition_Dec.__bases__ = tuple(bases) + + saml.AudienceRestrictionConditionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AudienceRestrictionCondition_Dec_Holder" + + class Audience_Dec(ZSI.TC.URI, ElementDeclaration): + literal = "Audience" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Audience") + kw["aname"] = "_Audience" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_Audience_immutable_holder" + ZSI.TC.URI.__init__(self, **kw) + + class DoNotCacheCondition_Dec(ElementDeclaration): + literal = "DoNotCacheCondition" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","DoNotCacheCondition") + kw["aname"] = "_DoNotCacheCondition" + if saml.DoNotCacheConditionType_Def not in saml.DoNotCacheCondition_Dec.__bases__: + bases = list(saml.DoNotCacheCondition_Dec.__bases__) + bases.insert(0, saml.DoNotCacheConditionType_Def) + saml.DoNotCacheCondition_Dec.__bases__ = tuple(bases) + + saml.DoNotCacheConditionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DoNotCacheCondition_Dec_Holder" + + class Advice_Dec(ElementDeclaration): + literal = "Advice" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Advice") + kw["aname"] = "_Advice" + if saml.AdviceType_Def not in saml.Advice_Dec.__bases__: + bases = list(saml.Advice_Dec.__bases__) + bases.insert(0, saml.AdviceType_Def) + saml.Advice_Dec.__bases__ = tuple(bases) + + saml.AdviceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Advice_Dec_Holder" + + class Statement_Dec(ElementDeclaration): + literal = "Statement" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Statement") + kw["aname"] = "_Statement" + if saml.StatementAbstractType_Def not in saml.Statement_Dec.__bases__: + bases = list(saml.Statement_Dec.__bases__) + bases.insert(0, saml.StatementAbstractType_Def) + saml.Statement_Dec.__bases__ = tuple(bases) + + saml.StatementAbstractType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Statement_Dec_Holder" + + class SubjectStatement_Dec(ElementDeclaration): + literal = "SubjectStatement" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","SubjectStatement") + kw["aname"] = "_SubjectStatement" + if saml.SubjectStatementAbstractType_Def not in saml.SubjectStatement_Dec.__bases__: + bases = list(saml.SubjectStatement_Dec.__bases__) + bases.insert(0, saml.SubjectStatementAbstractType_Def) + saml.SubjectStatement_Dec.__bases__ = tuple(bases) + + saml.SubjectStatementAbstractType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SubjectStatement_Dec_Holder" + + class Subject_Dec(ElementDeclaration): + literal = "Subject" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Subject") + kw["aname"] = "_Subject" + if saml.SubjectType_Def not in saml.Subject_Dec.__bases__: + bases = list(saml.Subject_Dec.__bases__) + bases.insert(0, saml.SubjectType_Def) + saml.Subject_Dec.__bases__ = tuple(bases) + + saml.SubjectType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Subject_Dec_Holder" + + class NameIdentifier_Dec(ElementDeclaration): + literal = "NameIdentifier" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","NameIdentifier") + kw["aname"] = "_NameIdentifier" + if saml.NameIdentifierType_Def not in saml.NameIdentifier_Dec.__bases__: + bases = list(saml.NameIdentifier_Dec.__bases__) + bases.insert(0, saml.NameIdentifierType_Def) + saml.NameIdentifier_Dec.__bases__ = tuple(bases) + + saml.NameIdentifierType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "NameIdentifier_Dec_Holder" + + class SubjectConfirmation_Dec(ElementDeclaration): + literal = "SubjectConfirmation" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","SubjectConfirmation") + kw["aname"] = "_SubjectConfirmation" + if saml.SubjectConfirmationType_Def not in saml.SubjectConfirmation_Dec.__bases__: + bases = list(saml.SubjectConfirmation_Dec.__bases__) + bases.insert(0, saml.SubjectConfirmationType_Def) + saml.SubjectConfirmation_Dec.__bases__ = tuple(bases) + + saml.SubjectConfirmationType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SubjectConfirmation_Dec_Holder" + + class SubjectConfirmationData_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "SubjectConfirmationData" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","SubjectConfirmationData") + kw["aname"] = "_SubjectConfirmationData" + ZSI.TC.AnyType.__init__(self, **kw) + + class ConfirmationMethod_Dec(ZSI.TC.URI, ElementDeclaration): + literal = "ConfirmationMethod" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","ConfirmationMethod") + kw["aname"] = "_ConfirmationMethod" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_ConfirmationMethod_immutable_holder" + ZSI.TC.URI.__init__(self, **kw) + + class AuthenticationStatement_Dec(ElementDeclaration): + literal = "AuthenticationStatement" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AuthenticationStatement") + kw["aname"] = "_AuthenticationStatement" + if saml.AuthenticationStatementType_Def not in saml.AuthenticationStatement_Dec.__bases__: + bases = list(saml.AuthenticationStatement_Dec.__bases__) + bases.insert(0, saml.AuthenticationStatementType_Def) + saml.AuthenticationStatement_Dec.__bases__ = tuple(bases) + + saml.AuthenticationStatementType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AuthenticationStatement_Dec_Holder" + + class SubjectLocality_Dec(ElementDeclaration): + literal = "SubjectLocality" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","SubjectLocality") + kw["aname"] = "_SubjectLocality" + if saml.SubjectLocalityType_Def not in saml.SubjectLocality_Dec.__bases__: + bases = list(saml.SubjectLocality_Dec.__bases__) + bases.insert(0, saml.SubjectLocalityType_Def) + saml.SubjectLocality_Dec.__bases__ = tuple(bases) + + saml.SubjectLocalityType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SubjectLocality_Dec_Holder" + + class AuthorityBinding_Dec(ElementDeclaration): + literal = "AuthorityBinding" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AuthorityBinding") + kw["aname"] = "_AuthorityBinding" + if saml.AuthorityBindingType_Def not in saml.AuthorityBinding_Dec.__bases__: + bases = list(saml.AuthorityBinding_Dec.__bases__) + bases.insert(0, saml.AuthorityBindingType_Def) + saml.AuthorityBinding_Dec.__bases__ = tuple(bases) + + saml.AuthorityBindingType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AuthorityBinding_Dec_Holder" + + class AuthorizationDecisionStatement_Dec(ElementDeclaration): + literal = "AuthorizationDecisionStatement" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AuthorizationDecisionStatement") + kw["aname"] = "_AuthorizationDecisionStatement" + if saml.AuthorizationDecisionStatementType_Def not in saml.AuthorizationDecisionStatement_Dec.__bases__: + bases = list(saml.AuthorizationDecisionStatement_Dec.__bases__) + bases.insert(0, saml.AuthorizationDecisionStatementType_Def) + saml.AuthorizationDecisionStatement_Dec.__bases__ = tuple(bases) + + saml.AuthorizationDecisionStatementType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AuthorizationDecisionStatement_Dec_Holder" + + class Action_Dec(ElementDeclaration): + literal = "Action" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Action") + kw["aname"] = "_Action" + if saml.ActionType_Def not in saml.Action_Dec.__bases__: + bases = list(saml.Action_Dec.__bases__) + bases.insert(0, saml.ActionType_Def) + saml.Action_Dec.__bases__ = tuple(bases) + + saml.ActionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Action_Dec_Holder" + + class Evidence_Dec(ElementDeclaration): + literal = "Evidence" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Evidence") + kw["aname"] = "_Evidence" + if saml.EvidenceType_Def not in saml.Evidence_Dec.__bases__: + bases = list(saml.Evidence_Dec.__bases__) + bases.insert(0, saml.EvidenceType_Def) + saml.Evidence_Dec.__bases__ = tuple(bases) + + saml.EvidenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Evidence_Dec_Holder" + + class AttributeStatement_Dec(ElementDeclaration): + literal = "AttributeStatement" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AttributeStatement") + kw["aname"] = "_AttributeStatement" + if saml.AttributeStatementType_Def not in saml.AttributeStatement_Dec.__bases__: + bases = list(saml.AttributeStatement_Dec.__bases__) + bases.insert(0, saml.AttributeStatementType_Def) + saml.AttributeStatement_Dec.__bases__ = tuple(bases) + + saml.AttributeStatementType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AttributeStatement_Dec_Holder" + + class AttributeDesignator_Dec(ElementDeclaration): + literal = "AttributeDesignator" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AttributeDesignator") + kw["aname"] = "_AttributeDesignator" + if saml.AttributeDesignatorType_Def not in saml.AttributeDesignator_Dec.__bases__: + bases = list(saml.AttributeDesignator_Dec.__bases__) + bases.insert(0, saml.AttributeDesignatorType_Def) + saml.AttributeDesignator_Dec.__bases__ = tuple(bases) + + saml.AttributeDesignatorType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AttributeDesignator_Dec_Holder" + + class Attribute_Dec(ElementDeclaration): + literal = "Attribute" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","Attribute") + kw["aname"] = "_Attribute" + if saml.AttributeType_Def not in saml.Attribute_Dec.__bases__: + bases = list(saml.Attribute_Dec.__bases__) + bases.insert(0, saml.AttributeType_Def) + saml.Attribute_Dec.__bases__ = tuple(bases) + + saml.AttributeType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Attribute_Dec_Holder" + + class AttributeValue_Dec(ZSI.TC.AnyType, ElementDeclaration): + literal = "AttributeValue" + schema = "urn:oasis:names:tc:SAML:1.0:assertion" + def __init__(self, **kw): + kw["pname"] = ("urn:oasis:names:tc:SAML:1.0:assertion","AttributeValue") + kw["aname"] = "_AttributeValue" + ZSI.TC.AnyType.__init__(self, **kw) + +# end class saml (tns: urn:oasis:names:tc:SAML:1.0:assertion) + diff --git a/digsby/src/msn/SOAP/xml/soapenv.py b/digsby/src/msn/SOAP/xml/soapenv.py new file mode 100644 index 0000000..3d45fe5 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/soapenv.py @@ -0,0 +1,373 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://www.w3.org/2003/05/soap-envelope +############################## + +class wssoapenv: + targetNamespace = NS.SOAP.ENV12 + + class Envelope_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "Envelope") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.Envelope_Def.schema + TClist = [GED(NS.SOAP.ENV12,"Header",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.SOAP.ENV12,"Body",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Header = None + self._Body = None + return + Holder.__name__ = "Envelope_Holder" + self.pyclass = Holder + + class Header_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "Header") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.Header_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "Header_Holder" + self.pyclass = Holder + + class Body_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "Body") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.Body_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "Body_Holder" + self.pyclass = Holder + + class Fault_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "Fault") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.Fault_Def.schema + TClist = [GTD(NS.SOAP.ENV12,"faultcode",lazy=False)(pname=(ns,"Code"), aname="_Code", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.SOAP.ENV12,"faultreason",lazy=False)(pname=(ns,"Reason"), aname="_Reason", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.URI(pname=(ns,"Node"), aname="_Node", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.URI(pname=(ns,"Role"), aname="_Role", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.SOAP.ENV12,"detail",lazy=False)(pname=(ns,"Detail"), aname="_Detail", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Code = None + self._Reason = None + self._Node = None + self._Role = None + self._Detail = None + return + Holder.__name__ = "Fault_Holder" + self.pyclass = Holder + + class faultreason_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "faultreason") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.faultreason_Def.schema + TClist = [GTD(NS.SOAP.ENV12,"reasontext",lazy=False)(pname=(ns,"Text"), aname="_Text", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Text = [] + return + Holder.__name__ = "faultreason_Holder" + self.pyclass = Holder + + class reasontext_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.SOAP.ENV12 + type = (schema, "reasontext") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.XMLNS.XML,"lang")] = ZSI.TC.AnyType() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class faultcode_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "faultcode") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.faultcode_Def.schema + TClist = [GTD(NS.SOAP.ENV12,"faultcodeEnum",lazy=False)(pname=(ns,"Value"), aname="_Value", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.SOAP.ENV12,"subcode",lazy=False)(pname=(ns,"Subcode"), aname="_Subcode", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Value = None + self._Subcode = None + return + Holder.__name__ = "faultcode_Holder" + self.pyclass = Holder + + class faultcodeEnum_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "faultcodeEnum") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class subcode_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "subcode") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.subcode_Def.schema + TClist = [ZSI.TC.QName(pname=(ns,"Value"), aname="_Value", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + #GTD(NS.SOAP.ENV12,"subcode",lazy=False)(pname=(ns,"Subcode"), aname="_Subcode", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")) + ] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Value = None + self._Subcode = None + return + Holder.__name__ = "subcode_Holder" + self.pyclass = Holder + + class detail_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "detail") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.detail_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "detail_Holder" + self.pyclass = Holder + + class NotUnderstoodType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "NotUnderstoodType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.NotUnderstoodType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["qname"] = ZSI.TC.QName() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "NotUnderstoodType_Holder" + self.pyclass = Holder + + class SupportedEnvType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "SupportedEnvType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.SupportedEnvType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["qname"] = ZSI.TC.QName() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "SupportedEnvType_Holder" + self.pyclass = Holder + + class UpgradeType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.SOAP.ENV12 + type = (schema, "UpgradeType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wssoapenv.UpgradeType_Def.schema + TClist = [GTD(NS.SOAP.ENV12,"SupportedEnvType",lazy=False)(pname=(ns,"SupportedEnvelope"), aname="_SupportedEnvelope", minOccurs=1, maxOccurs="unbounded", nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SupportedEnvelope = [] + return + Holder.__name__ = "UpgradeType_Holder" + self.pyclass = Holder + + class Envelope_Dec(ElementDeclaration): + literal = "Envelope" + schema = NS.SOAP.ENV12 + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.SOAP.ENV12,"Envelope") + kw["aname"] = "_Envelope" + if wssoapenv.Envelope_Def not in wssoapenv.Envelope_Dec.__bases__: + bases = list(wssoapenv.Envelope_Dec.__bases__) + bases.insert(0, wssoapenv.Envelope_Def) + wssoapenv.Envelope_Dec.__bases__ = tuple(bases) + + wssoapenv.Envelope_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Envelope_Dec_Holder" + + class Header_Dec(ElementDeclaration): + literal = "Header" + schema = NS.SOAP.ENV12 + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.SOAP.ENV12,"Header") + kw["aname"] = "_Header" + if wssoapenv.Header_Def not in wssoapenv.Header_Dec.__bases__: + bases = list(wssoapenv.Header_Dec.__bases__) + bases.insert(0, wssoapenv.Header_Def) + wssoapenv.Header_Dec.__bases__ = tuple(bases) + + wssoapenv.Header_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Header_Dec_Holder" + + class Body_Dec(ElementDeclaration): + literal = "Body" + schema = NS.SOAP.ENV12 + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.SOAP.ENV12,"Body") + kw["aname"] = "_Body" + if wssoapenv.Body_Def not in wssoapenv.Body_Dec.__bases__: + bases = list(wssoapenv.Body_Dec.__bases__) + bases.insert(0, wssoapenv.Body_Def) + wssoapenv.Body_Dec.__bases__ = tuple(bases) + + wssoapenv.Body_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Body_Dec_Holder" + + class Fault_Dec(ElementDeclaration): + literal = "Fault" + schema = NS.SOAP.ENV12 + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.SOAP.ENV12,"Fault") + kw["aname"] = "_Fault" + if wssoapenv.Fault_Def not in wssoapenv.Fault_Dec.__bases__: + bases = list(wssoapenv.Fault_Dec.__bases__) + bases.insert(0, wssoapenv.Fault_Def) + wssoapenv.Fault_Dec.__bases__ = tuple(bases) + + wssoapenv.Fault_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Fault_Dec_Holder" + + class NotUnderstood_Dec(ElementDeclaration): + literal = "NotUnderstood" + schema = NS.SOAP.ENV12 + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.SOAP.ENV12,"NotUnderstood") + kw["aname"] = "_NotUnderstood" + if wssoapenv.NotUnderstoodType_Def not in wssoapenv.NotUnderstood_Dec.__bases__: + bases = list(wssoapenv.NotUnderstood_Dec.__bases__) + bases.insert(0, wssoapenv.NotUnderstoodType_Def) + wssoapenv.NotUnderstood_Dec.__bases__ = tuple(bases) + + wssoapenv.NotUnderstoodType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "NotUnderstood_Dec_Holder" + + class Upgrade_Dec(ElementDeclaration): + literal = "Upgrade" + schema = NS.SOAP.ENV12 + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.SOAP.ENV12,"Upgrade") + kw["aname"] = "_Upgrade" + if wssoapenv.UpgradeType_Def not in wssoapenv.Upgrade_Dec.__bases__: + bases = list(wssoapenv.Upgrade_Dec.__bases__) + bases.insert(0, wssoapenv.UpgradeType_Def) + wssoapenv.Upgrade_Dec.__bases__ = tuple(bases) + + wssoapenv.UpgradeType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Upgrade_Dec_Holder" + +# end class wssoapenv (tns: http://www.w3.org/2003/05/soap-envelope) diff --git a/digsby/src/msn/SOAP/xml/soapwsa.py b/digsby/src/msn/SOAP/xml/soapwsa.py new file mode 100644 index 0000000..e088674 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/soapwsa.py @@ -0,0 +1,337 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://schemas.xmlsoap.org/ws/2004/08/addressing +############################## + +class soapwsa: + targetNamespace = NS.WSA200408.ADDRESS + + class EndpointReferenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA200408.ADDRESS + type = (schema, "EndpointReferenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = soapwsa.EndpointReferenceType_Def.schema + TClist = [GTD(NS.WSA200408.ADDRESS,"AttributedURI",lazy=False)(pname=(ns,"Address"), aname="_Address", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.WSA200408.ADDRESS,"ReferencePropertiesType",lazy=False)(pname=(ns,"ReferenceProperties"), aname="_ReferenceProperties", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.WSA200408.ADDRESS,"ReferenceParametersType",lazy=False)(pname=(ns,"ReferenceParameters"), aname="_ReferenceParameters", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.WSA200408.ADDRESS,"AttributedQName",lazy=False)(pname=(ns,"PortType"), aname="_PortType", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.WSA200408.ADDRESS,"ServiceNameType",lazy=False)(pname=(ns,"ServiceName"), aname="_ServiceName", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Address = None + self._ReferenceProperties = None + self._ReferenceParameters = None + self._PortType = None + self._ServiceName = None + self._any = [] + return + Holder.__name__ = "EndpointReferenceType_Holder" + self.pyclass = Holder + + class ReferencePropertiesType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA200408.ADDRESS + type = (schema, "ReferencePropertiesType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = soapwsa.ReferencePropertiesType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "ReferencePropertiesType_Holder" + self.pyclass = Holder + + class ReferenceParametersType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA200408.ADDRESS + type = (schema, "ReferenceParametersType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = soapwsa.ReferenceParametersType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "ReferenceParametersType_Holder" + self.pyclass = Holder + + class ServiceNameType_Def(ZSI.TC.QName, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA200408.ADDRESS + type = (schema, "ServiceNameType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["PortName"] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.QName.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class Relationship_Def(ZSI.TC.URI, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA200408.ADDRESS + type = (schema, "Relationship") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["RelationshipType"] = ZSI.TC.QName() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.URI.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class RelationshipTypeValues_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSA200408.ADDRESS + type = (schema, "RelationshipTypeValues") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class ReplyAfterType_Def(ZSI.TCnumbers.InonNegativeInteger, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA200408.ADDRESS + type = (schema, "ReplyAfterType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCnumbers.InonNegativeInteger.__init__(self, pname, **kw) + class Holder(int): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class RetryAfterType_Def(ZSI.TCnumbers.InonNegativeInteger, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA200408.ADDRESS + type = (schema, "RetryAfterType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCnumbers.InonNegativeInteger.__init__(self, pname, **kw) + class Holder(int): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class FaultSubcodeValues_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSA200408.ADDRESS + type = (schema, "FaultSubcodeValues") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class AttributedQName_Def(ZSI.TC.QName, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA200408.ADDRESS + type = (schema, "AttributedQName") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.QName.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class AttributedURI_Def(ZSI.TC.URI, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA200408.ADDRESS + type = (schema, "AttributedURI") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.URI.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class EndpointReference_Dec(ElementDeclaration): + literal = "EndpointReference" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"EndpointReference") + kw["aname"] = "_EndpointReference" + if soapwsa.EndpointReferenceType_Def not in soapwsa.EndpointReference_Dec.__bases__: + bases = list(soapwsa.EndpointReference_Dec.__bases__) + bases.insert(0, soapwsa.EndpointReferenceType_Def) + soapwsa.EndpointReference_Dec.__bases__ = tuple(bases) + + soapwsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "EndpointReference_Dec_Holder" + + class MessageID_Dec(ElementDeclaration): + literal = "MessageID" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"MessageID") + kw["aname"] = "_MessageID" + if soapwsa.AttributedURI_Def not in soapwsa.MessageID_Dec.__bases__: + bases = list(soapwsa.MessageID_Dec.__bases__) + bases.insert(0, soapwsa.AttributedURI_Def) + soapwsa.MessageID_Dec.__bases__ = tuple(bases) + + soapwsa.AttributedURI_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "MessageID_Dec_Holder" + + class RelatesTo_Dec(ElementDeclaration): + literal = "RelatesTo" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"RelatesTo") + kw["aname"] = "_RelatesTo" + if soapwsa.Relationship_Def not in soapwsa.RelatesTo_Dec.__bases__: + bases = list(soapwsa.RelatesTo_Dec.__bases__) + bases.insert(0, soapwsa.Relationship_Def) + soapwsa.RelatesTo_Dec.__bases__ = tuple(bases) + + soapwsa.Relationship_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RelatesTo_Dec_Holder" + + class To_Dec(ElementDeclaration): + literal = "To" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"To") + kw["aname"] = "_To" + if soapwsa.AttributedURI_Def not in soapwsa.To_Dec.__bases__: + bases = list(soapwsa.To_Dec.__bases__) + bases.insert(0, soapwsa.AttributedURI_Def) + soapwsa.To_Dec.__bases__ = tuple(bases) + + soapwsa.AttributedURI_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "To_Dec_Holder" + + class Action_Dec(ElementDeclaration): + literal = "Action" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"Action") + kw["aname"] = "_Action" + if soapwsa.AttributedURI_Def not in soapwsa.Action_Dec.__bases__: + bases = list(soapwsa.Action_Dec.__bases__) + bases.insert(0, soapwsa.AttributedURI_Def) + soapwsa.Action_Dec.__bases__ = tuple(bases) + + soapwsa.AttributedURI_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Action_Dec_Holder" + + class From_Dec(ElementDeclaration): + literal = "From" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"From") + kw["aname"] = "_From" + if soapwsa.EndpointReferenceType_Def not in soapwsa.From_Dec.__bases__: + bases = list(soapwsa.From_Dec.__bases__) + bases.insert(0, soapwsa.EndpointReferenceType_Def) + soapwsa.From_Dec.__bases__ = tuple(bases) + + soapwsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "From_Dec_Holder" + + class ReplyTo_Dec(ElementDeclaration): + literal = "ReplyTo" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"ReplyTo") + kw["aname"] = "_ReplyTo" + if soapwsa.EndpointReferenceType_Def not in soapwsa.ReplyTo_Dec.__bases__: + bases = list(soapwsa.ReplyTo_Dec.__bases__) + bases.insert(0, soapwsa.EndpointReferenceType_Def) + soapwsa.ReplyTo_Dec.__bases__ = tuple(bases) + + soapwsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ReplyTo_Dec_Holder" + + class FaultTo_Dec(ElementDeclaration): + literal = "FaultTo" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"FaultTo") + kw["aname"] = "_FaultTo" + if soapwsa.EndpointReferenceType_Def not in soapwsa.FaultTo_Dec.__bases__: + bases = list(soapwsa.FaultTo_Dec.__bases__) + bases.insert(0, soapwsa.EndpointReferenceType_Def) + soapwsa.FaultTo_Dec.__bases__ = tuple(bases) + + soapwsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "FaultTo_Dec_Holder" + + class ReplyAfter_Dec(ElementDeclaration): + literal = "ReplyAfter" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"ReplyAfter") + kw["aname"] = "_ReplyAfter" + if soapwsa.ReplyAfterType_Def not in soapwsa.ReplyAfter_Dec.__bases__: + bases = list(soapwsa.ReplyAfter_Dec.__bases__) + bases.insert(0, soapwsa.ReplyAfterType_Def) + soapwsa.ReplyAfter_Dec.__bases__ = tuple(bases) + + soapwsa.ReplyAfterType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ReplyAfter_Dec_Holder" + + class RetryAfter_Dec(ElementDeclaration): + literal = "RetryAfter" + schema = NS.WSA200408.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA200408.ADDRESS,"RetryAfter") + kw["aname"] = "_RetryAfter" + if soapwsa.RetryAfterType_Def not in soapwsa.RetryAfter_Dec.__bases__: + bases = list(soapwsa.RetryAfter_Dec.__bases__) + bases.insert(0, soapwsa.RetryAfterType_Def) + soapwsa.RetryAfter_Dec.__bases__ = tuple(bases) + + soapwsa.RetryAfterType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RetryAfter_Dec_Holder" + +# end class soapwsa (tns: http://schemas.xmlsoap.org/ws/2004/08/addressing) diff --git a/digsby/src/msn/SOAP/xml/wsa.py b/digsby/src/msn/SOAP/xml/wsa.py new file mode 100644 index 0000000..e9ca53c --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wsa.py @@ -0,0 +1,418 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://www.w3.org/2005/08/addressing +############################## + +class wsa: + targetNamespace = NS.WSA.ADDRESS + + class EndpointReferenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.ADDRESS + type = (schema, "EndpointReferenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsa.EndpointReferenceType_Def.schema + TClist = [GTD(NS.WSA.ADDRESS,"AttributedURIType",lazy=False)(pname=(ns,"Address"), aname="_Address", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GED(NS.WSA.ADDRESS,"ReferenceParameters",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSA.ADDRESS,"Metadata",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Address = None + self._ReferenceParameters = None + self._Metadata = None + self._any = [] + return + Holder.__name__ = "EndpointReferenceType_Holder" + self.pyclass = Holder + + class ReferenceParametersType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.ADDRESS + type = (schema, "ReferenceParametersType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsa.ReferenceParametersType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "ReferenceParametersType_Holder" + self.pyclass = Holder + + class MetadataType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.ADDRESS + type = (schema, "MetadataType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsa.MetadataType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "MetadataType_Holder" + self.pyclass = Holder + + class RelatesToType_Def(ZSI.TC.URI, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA.ADDRESS + type = (schema, "RelatesToType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["RelationshipType"] = wsa.RelationshipTypeOpenEnum_Def(None) + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.URI.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class RelationshipTypeOpenEnum_Def(ZSI.TC.Union, TypeDefinition): + memberTypes = [(NS.WSA.ADDRESS, u'RelationshipType'), (NS.SCHEMA.BASE, u'anyURI')] + schema = NS.WSA.ADDRESS + type = (schema, "RelationshipTypeOpenEnum") + def __init__(self, pname, **kw): + ZSI.TC.Union.__init__(self, pname, **kw) + + class RelationshipType_Def(ZSI.TC.URI, TypeDefinition): + schema = NS.WSA.ADDRESS + type = (schema, "RelationshipType") + def __init__(self, pname, **kw): + ZSI.TC.URI.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class AttributedURIType_Def(ZSI.TC.URI, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA.ADDRESS + type = (schema, "AttributedURIType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.URI.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class FaultCodesOpenEnumType_Def(ZSI.TC.Union, TypeDefinition): + memberTypes = [(NS.WSA.ADDRESS, u'FaultCodesType'), (NS.SCHEMA.BASE, u'QName')] + schema = NS.WSA.ADDRESS + type = (schema, "FaultCodesOpenEnumType") + def __init__(self, pname, **kw): + ZSI.TC.Union.__init__(self, pname, **kw) + + class FaultCodesType_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSA.ADDRESS + type = (schema, "FaultCodesType") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class AttributedUnsignedLongType_Def(ZSI.TCnumbers.IunsignedLong, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA.ADDRESS + type = (schema, "AttributedUnsignedLongType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCnumbers.IunsignedLong.__init__(self, pname, **kw) + class Holder(long): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class AttributedQNameType_Def(ZSI.TC.QName, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSA.ADDRESS + type = (schema, "AttributedQNameType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.QName.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class ProblemActionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.ADDRESS + type = (schema, "ProblemActionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsa.ProblemActionType_Def.schema + TClist = [GED(NS.WSA.ADDRESS,"Action",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.URI(pname=(ns,"SoapAction"), aname="_SoapAction", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Action = None + self._SoapAction = None + return + Holder.__name__ = "ProblemActionType_Holder" + self.pyclass = Holder + + class EndpointReference_Dec(ElementDeclaration): + literal = "EndpointReference" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"EndpointReference") + kw["aname"] = "_EndpointReference" + if wsa.EndpointReferenceType_Def not in wsa.EndpointReference_Dec.__bases__: + bases = list(wsa.EndpointReference_Dec.__bases__) + bases.insert(0, wsa.EndpointReferenceType_Def) + wsa.EndpointReference_Dec.__bases__ = tuple(bases) + + wsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "EndpointReference_Dec_Holder" + + class ReferenceParameters_Dec(ElementDeclaration): + literal = "ReferenceParameters" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"ReferenceParameters") + kw["aname"] = "_ReferenceParameters" + if wsa.ReferenceParametersType_Def not in wsa.ReferenceParameters_Dec.__bases__: + bases = list(wsa.ReferenceParameters_Dec.__bases__) + bases.insert(0, wsa.ReferenceParametersType_Def) + wsa.ReferenceParameters_Dec.__bases__ = tuple(bases) + + wsa.ReferenceParametersType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ReferenceParameters_Dec_Holder" + + class Metadata_Dec(ElementDeclaration): + literal = "Metadata" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"Metadata") + kw["aname"] = "_Metadata" + if wsa.MetadataType_Def not in wsa.Metadata_Dec.__bases__: + bases = list(wsa.Metadata_Dec.__bases__) + bases.insert(0, wsa.MetadataType_Def) + wsa.Metadata_Dec.__bases__ = tuple(bases) + + wsa.MetadataType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Metadata_Dec_Holder" + + class MessageID_Dec(ElementDeclaration): + literal = "MessageID" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"MessageID") + kw["aname"] = "_MessageID" + if wsa.AttributedURIType_Def not in wsa.MessageID_Dec.__bases__: + bases = list(wsa.MessageID_Dec.__bases__) + bases.insert(0, wsa.AttributedURIType_Def) + wsa.MessageID_Dec.__bases__ = tuple(bases) + + wsa.AttributedURIType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "MessageID_Dec_Holder" + + class RelatesTo_Dec(ElementDeclaration): + literal = "RelatesTo" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"RelatesTo") + kw["aname"] = "_RelatesTo" + if wsa.RelatesToType_Def not in wsa.RelatesTo_Dec.__bases__: + bases = list(wsa.RelatesTo_Dec.__bases__) + bases.insert(0, wsa.RelatesToType_Def) + wsa.RelatesTo_Dec.__bases__ = tuple(bases) + + wsa.RelatesToType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RelatesTo_Dec_Holder" + + class ReplyTo_Dec(ElementDeclaration): + literal = "ReplyTo" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"ReplyTo") + kw["aname"] = "_ReplyTo" + if wsa.EndpointReferenceType_Def not in wsa.ReplyTo_Dec.__bases__: + bases = list(wsa.ReplyTo_Dec.__bases__) + bases.insert(0, wsa.EndpointReferenceType_Def) + wsa.ReplyTo_Dec.__bases__ = tuple(bases) + + wsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ReplyTo_Dec_Holder" + + class From_Dec(ElementDeclaration): + literal = "From" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"From") + kw["aname"] = "_From" + if wsa.EndpointReferenceType_Def not in wsa.From_Dec.__bases__: + bases = list(wsa.From_Dec.__bases__) + bases.insert(0, wsa.EndpointReferenceType_Def) + wsa.From_Dec.__bases__ = tuple(bases) + + wsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "From_Dec_Holder" + + class FaultTo_Dec(ElementDeclaration): + literal = "FaultTo" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"FaultTo") + kw["aname"] = "_FaultTo" + if wsa.EndpointReferenceType_Def not in wsa.FaultTo_Dec.__bases__: + bases = list(wsa.FaultTo_Dec.__bases__) + bases.insert(0, wsa.EndpointReferenceType_Def) + wsa.FaultTo_Dec.__bases__ = tuple(bases) + + wsa.EndpointReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "FaultTo_Dec_Holder" + + class To_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "To" + schema = NS.WSA.ADDRESS + def __init__(self, **kw): + ns = wsa.To_Dec.schema + TClist = [] + kw["pname"] = (NS.WSA.ADDRESS,"To") + kw["aname"] = "_To" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "To_Holder" + self.pyclass = Holder + + class Action_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "Action" + schema = NS.WSA.ADDRESS + def __init__(self, **kw): + ns = wsa.Action_Dec.schema + TClist = [] + kw["pname"] = (NS.WSA.ADDRESS,"Action") + kw["aname"] = "_Action" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "Action_Holder" + self.pyclass = Holder + + class RetryAfter_Dec(ElementDeclaration): + literal = "RetryAfter" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"RetryAfter") + kw["aname"] = "_RetryAfter" + if wsa.AttributedUnsignedLongType_Def not in wsa.RetryAfter_Dec.__bases__: + bases = list(wsa.RetryAfter_Dec.__bases__) + bases.insert(0, wsa.AttributedUnsignedLongType_Def) + wsa.RetryAfter_Dec.__bases__ = tuple(bases) + + wsa.AttributedUnsignedLongType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RetryAfter_Dec_Holder" + + class ProblemHeaderQName_Dec(ElementDeclaration): + literal = "ProblemHeaderQName" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"ProblemHeaderQName") + kw["aname"] = "_ProblemHeaderQName" + if wsa.AttributedQNameType_Def not in wsa.ProblemHeaderQName_Dec.__bases__: + bases = list(wsa.ProblemHeaderQName_Dec.__bases__) + bases.insert(0, wsa.AttributedQNameType_Def) + wsa.ProblemHeaderQName_Dec.__bases__ = tuple(bases) + + wsa.AttributedQNameType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ProblemHeaderQName_Dec_Holder" + + class ProblemIRI_Dec(ElementDeclaration): + literal = "ProblemIRI" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"ProblemIRI") + kw["aname"] = "_ProblemIRI" + if wsa.AttributedURIType_Def not in wsa.ProblemIRI_Dec.__bases__: + bases = list(wsa.ProblemIRI_Dec.__bases__) + bases.insert(0, wsa.AttributedURIType_Def) + wsa.ProblemIRI_Dec.__bases__ = tuple(bases) + + wsa.AttributedURIType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ProblemIRI_Dec_Holder" + + class ProblemAction_Dec(ElementDeclaration): + literal = "ProblemAction" + schema = NS.WSA.ADDRESS + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.ADDRESS,"ProblemAction") + kw["aname"] = "_ProblemAction" + if wsa.ProblemActionType_Def not in wsa.ProblemAction_Dec.__bases__: + bases = list(wsa.ProblemAction_Dec.__bases__) + bases.insert(0, wsa.ProblemActionType_Def) + wsa.ProblemAction_Dec.__bases__ = tuple(bases) + + wsa.ProblemActionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ProblemAction_Dec_Holder" + +# end class wsa (tns: http://www.w3.org/2005/08/addressing) diff --git a/digsby/src/msn/SOAP/xml/wsc.py b/digsby/src/msn/SOAP/xml/wsc.py new file mode 100644 index 0000000..ab783d7 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wsc.py @@ -0,0 +1,192 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://schemas.xmlsoap.org/ws/2005/02/sc +############################## + +class wsc: + # We use the proper name WSTRUST200502 instead of the alias WSTRUST + # because not all WSTRUST versions have the CONV namespace. + targetNamespace = NS.WSTRUST200502.CONV + + class SecurityContextTokenType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST200502.CONV + type = (schema, "SecurityContextTokenType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsc.SecurityContextTokenType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.OASIS.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "SecurityContextTokenType_Holder" + self.pyclass = Holder + + class DerivedKeyTokenType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST200502.CONV + type = (schema, "DerivedKeyTokenType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsc.DerivedKeyTokenType_Def.schema + TClist = [GED(NS.OASIS.WSSE,"SecurityTokenReference",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(NS.WSTRUST200502.CONV,"PropertiesType",lazy=False)(pname=(ns,"Properties"), aname="_Properties", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.IunsignedLong(pname=(ns,"Generation"), aname="_Generation", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.IunsignedLong(pname=(ns,"Offset"), aname="_Offset", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.IunsignedLong(pname=(ns,"Length"), aname="_Length", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GED(NS.WSTRUST200502.CONV,"Label",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST200502.CONV,"Nonce",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.OASIS.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict["Algorithm"] = ZSI.TC.URI() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SecurityTokenReference = None + self._Properties = None + self._Generation = None + self._Offset = None + self._Length = None + self._Label = None + self._Nonce = None + return + Holder.__name__ = "DerivedKeyTokenType_Holder" + self.pyclass = Holder + + class PropertiesType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST200502.CONV + type = (schema, "PropertiesType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsc.PropertiesType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "PropertiesType_Holder" + self.pyclass = Holder + + class FaultCodeType_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSTRUST200502.CONV + type = (schema, "FaultCodeType") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class FaultCodeOpenEnumType_Def(ZSI.TC.Union, TypeDefinition): + memberTypes = [(NS.WSTRUST200502.CONV, u'FaultCodeType'), (NS.SCHEMA.BASE, u'QName')] + schema = NS.WSTRUST200502.CONV + type = (schema, "FaultCodeOpenEnumType") + def __init__(self, pname, **kw): + ZSI.TC.Union.__init__(self, pname, **kw) + + class SecurityContextToken_Dec(ElementDeclaration): + literal = "SecurityContextToken" + schema = NS.WSTRUST200502.CONV + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST200502.CONV,"SecurityContextToken") + kw["aname"] = "_SecurityContextToken" + if wsc.SecurityContextTokenType_Def not in wsc.SecurityContextToken_Dec.__bases__: + bases = list(wsc.SecurityContextToken_Dec.__bases__) + bases.insert(0, wsc.SecurityContextTokenType_Def) + wsc.SecurityContextToken_Dec.__bases__ = tuple(bases) + + wsc.SecurityContextTokenType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SecurityContextToken_Dec_Holder" + + class Identifier_Dec(ZSI.TC.URI, ElementDeclaration): + literal = "Identifier" + schema = NS.WSTRUST200502.CONV + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST200502.CONV,"Identifier") + kw["aname"] = "_Identifier" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_Identifier_immutable_holder" + ZSI.TC.URI.__init__(self, **kw) + + class Instance_Dec(ZSI.TC.String, ElementDeclaration): + literal = "Instance" + schema = NS.WSTRUST200502.CONV + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST200502.CONV,"Instance") + kw["aname"] = "_Instance" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_Instance_immutable_holder" + ZSI.TC.String.__init__(self, **kw) + + class DerivedKeyToken_Dec(ElementDeclaration): + literal = "DerivedKeyToken" + schema = NS.WSTRUST200502.CONV + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST200502.CONV,"DerivedKeyToken") + kw["aname"] = "_DerivedKeyToken" + if wsc.DerivedKeyTokenType_Def not in wsc.DerivedKeyToken_Dec.__bases__: + bases = list(wsc.DerivedKeyToken_Dec.__bases__) + bases.insert(0, wsc.DerivedKeyTokenType_Def) + wsc.DerivedKeyToken_Dec.__bases__ = tuple(bases) + + wsc.DerivedKeyTokenType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "DerivedKeyToken_Dec_Holder" + + class Name_Dec(ZSI.TC.URI, ElementDeclaration): + literal = "Name" + schema = NS.WSTRUST200502.CONV + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST200502.CONV,"Name") + kw["aname"] = "_Name" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_Name_immutable_holder" + ZSI.TC.URI.__init__(self, **kw) + + class Label_Dec(ZSI.TC.String, ElementDeclaration): + literal = "Label" + schema = NS.WSTRUST200502.CONV + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST200502.CONV,"Label") + kw["aname"] = "_Label" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_Label_immutable_holder" + ZSI.TC.String.__init__(self, **kw) + + class Nonce_Dec(ZSI.TC.Base64String, ElementDeclaration): + literal = "Nonce" + schema = NS.WSTRUST200502.CONV + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST200502.CONV,"Nonce") + kw["aname"] = "_Nonce" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_Nonce_immutable_holder" + ZSI.TC.Base64String.__init__(self, **kw) + +# end class wsc (tns: http://schemas.xmlsoap.org/ws/2005/02/sc) diff --git a/digsby/src/msn/SOAP/xml/wsp.py b/digsby/src/msn/SOAP/xml/wsp.py new file mode 100644 index 0000000..e88bc8d --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wsp.py @@ -0,0 +1,56 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://schemas.xmlsoap.org/ws/2004/09/policy +############################## + +class wsp: + targetNamespace = NS.WSP.POLICY + + class AppliesTo_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "AppliesTo" + schema = NS.WSP.POLICY + def __init__(self, **kw): + ns = wsp.AppliesTo_Dec.schema + TClist = [GED(NS.WSA.ADDRESS,"EndpointReference",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + kw["pname"] = (NS.WSP.POLICY,"AppliesTo") + kw["aname"] = "_AppliesTo" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._EndpointReference = None + return + Holder.__name__ = "AppliesTo_Holder" + self.pyclass = Holder + + class PolicyReference_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "PolicyReference" + schema = NS.WSP.POLICY + def __init__(self, **kw): + ns = wsp.PolicyReference_Dec.schema + TClist = [] + kw["pname"] = (NS.WSP.POLICY,"PolicyReference") + kw["aname"] = "_PolicyReference" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict["URI"] = ZSI.TC.String() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "PolicyReference_Holder" + self.pyclass = Holder + +# end class wsp (tns: http://schemas.xmlsoap.org/ws/2004/09/policy) diff --git a/digsby/src/msn/SOAP/xml/wsrm.py b/digsby/src/msn/SOAP/xml/wsrm.py new file mode 100644 index 0000000..a747500 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wsrm.py @@ -0,0 +1,389 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://schemas.xmlsoap.org/ws/2003/03/rm +############################## + +class wsrm: + targetNamespace = NS.WSA.RM + + class SequenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "SequenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsrm.SequenceType_Def.schema + TClist = [GED(NS.WSU.UTILITY,"Identifier",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TCnumbers.IunsignedLong(pname=(ns,"MessageNumber"), aname="_MessageNumber", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyType(pname=(ns,"LastMessage"), aname="_LastMessage", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Identifier = None + self._MessageNumber = None + self._LastMessage = None + self._any = [] + return + Holder.__name__ = "SequenceType_Holder" + self.pyclass = Holder + + class AckRequestedType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "AckRequestedType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsrm.AckRequestedType_Def.schema + TClist = [GED(NS.WSU.UTILITY,"Identifier",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Identifier = None + self._any = [] + return + Holder.__name__ = "AckRequestedType_Holder" + self.pyclass = Holder + + class PolicyAssertionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "PolicyAssertionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsrm.PolicyAssertionType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "PolicyAssertionType_Holder" + self.pyclass = Holder + + class DeliveryAssuranceEnum_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "DeliveryAssuranceEnum") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class FaultCodes_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "FaultCodes") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class SequenceFaultType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "SequenceFaultType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsrm.SequenceFaultType_Def.schema + TClist = [GED(NS.WSU.UTILITY,"Identifier",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.QName(pname=(ns,"FaultCode"), aname="_FaultCode", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=1, maxOccurs=1, nillable=False, processContents="strict")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Identifier = None + self._FaultCode = None + self._any = None + return + Holder.__name__ = "SequenceFaultType_Holder" + self.pyclass = Holder + + class SequenceRefType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "SequenceRefType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsrm.SequenceRefType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Identifier"] = ZSI.TC.URI() + self.attribute_typecode_dict["Match"] = wsrm.MatchChoiceType_Def(None) + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "SequenceRefType_Holder" + self.pyclass = Holder + + class MatchChoiceType_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSA.RM + type = (schema, "MatchChoiceType") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class Sequence_Dec(ElementDeclaration): + literal = "Sequence" + schema = NS.WSA.RM + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.RM,"Sequence") + kw["aname"] = "_Sequence" + if wsrm.SequenceType_Def not in wsrm.Sequence_Dec.__bases__: + bases = list(wsrm.Sequence_Dec.__bases__) + bases.insert(0, wsrm.SequenceType_Def) + wsrm.Sequence_Dec.__bases__ = tuple(bases) + + wsrm.SequenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Sequence_Dec_Holder" + + class SequenceTerminate_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "SequenceTerminate" + schema = NS.WSA.RM + def __init__(self, **kw): + ns = wsrm.SequenceTerminate_Dec.schema + TClist = [GED(NS.WSU.UTILITY,"Identifier",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + kw["pname"] = (NS.WSA.RM,"SequenceTerminate") + kw["aname"] = "_SequenceTerminate" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Identifier = None + self._any = [] + return + Holder.__name__ = "SequenceTerminate_Holder" + self.pyclass = Holder + + class SequenceAcknowledgment_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "SequenceAcknowledgment" + schema = NS.WSA.RM + def __init__(self, **kw): + ns = wsrm.SequenceAcknowledgment_Dec.schema + TClist = [GED(NS.WSU.UTILITY,"Identifier",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), self.__class__.AcknowledgmentRange_Dec(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + kw["pname"] = (NS.WSA.RM,"SequenceAcknowledgment") + kw["aname"] = "_SequenceAcknowledgment" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Identifier = None + self._AcknowledgmentRange = [] + self._any = [] + return + Holder.__name__ = "SequenceAcknowledgment_Holder" + self.pyclass = Holder + + + class AcknowledgmentRange_Dec(ZSI.TCcompound.ComplexType, LocalElementDeclaration): + literal = "AcknowledgmentRange" + schema = NS.WSA.RM + def __init__(self, **kw): + ns = wsrm.SequenceAcknowledgment_Dec.AcknowledgmentRange_Dec.schema + TClist = [] + kw["pname"] = (NS.WSA.RM,"AcknowledgmentRange") + kw["aname"] = "_AcknowledgmentRange" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict["Upper"] = ZSI.TCnumbers.IunsignedLong() + self.attribute_typecode_dict["Lower"] = ZSI.TCnumbers.IunsignedLong() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "AcknowledgmentRange_Holder" + self.pyclass = Holder + + + + + class AckRequested_Dec(ElementDeclaration): + literal = "AckRequested" + schema = NS.WSA.RM + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.RM,"AckRequested") + kw["aname"] = "_AckRequested" + if wsrm.AckRequestedType_Def not in wsrm.AckRequested_Dec.__bases__: + bases = list(wsrm.AckRequested_Dec.__bases__) + bases.insert(0, wsrm.AckRequestedType_Def) + wsrm.AckRequested_Dec.__bases__ = tuple(bases) + + wsrm.AckRequestedType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "AckRequested_Dec_Holder" + + class InactivityTimeout_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "InactivityTimeout" + schema = NS.WSA.RM + def __init__(self, **kw): + ns = wsrm.InactivityTimeout_Dec.schema + TClist = [] + kw["pname"] = (NS.WSA.RM,"InactivityTimeout") + kw["aname"] = "_InactivityTimeout" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "InactivityTimeout_Holder" + self.pyclass = Holder + + class BaseRetransmissionInterval_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "BaseRetransmissionInterval" + schema = NS.WSA.RM + def __init__(self, **kw): + ns = wsrm.BaseRetransmissionInterval_Dec.schema + TClist = [] + kw["pname"] = (NS.WSA.RM,"BaseRetransmissionInterval") + kw["aname"] = "_BaseRetransmissionInterval" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "BaseRetransmissionInterval_Holder" + self.pyclass = Holder + + class ExponentialBackoff_Dec(ElementDeclaration): + literal = "ExponentialBackoff" + schema = NS.WSA.RM + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.RM,"ExponentialBackoff") + kw["aname"] = "_ExponentialBackoff" + if wsrm.PolicyAssertionType_Def not in wsrm.ExponentialBackoff_Dec.__bases__: + bases = list(wsrm.ExponentialBackoff_Dec.__bases__) + bases.insert(0, wsrm.PolicyAssertionType_Def) + wsrm.ExponentialBackoff_Dec.__bases__ = tuple(bases) + + wsrm.PolicyAssertionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "ExponentialBackoff_Dec_Holder" + + class AcknowledgementInterval_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "AcknowledgementInterval" + schema = NS.WSA.RM + def __init__(self, **kw): + ns = wsrm.AcknowledgementInterval_Dec.schema + TClist = [] + kw["pname"] = (NS.WSA.RM,"AcknowledgementInterval") + kw["aname"] = "_AcknowledgementInterval" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "AcknowledgementInterval_Holder" + self.pyclass = Holder + + class DeliveryAssurance_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "DeliveryAssurance" + schema = NS.WSA.RM + def __init__(self, **kw): + ns = wsrm.DeliveryAssurance_Dec.schema + TClist = [] + kw["pname"] = (NS.WSA.RM,"DeliveryAssurance") + kw["aname"] = "_DeliveryAssurance" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "DeliveryAssurance_Holder" + self.pyclass = Holder + + class SequenceFault_Dec(ElementDeclaration): + literal = "SequenceFault" + schema = NS.WSA.RM + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.RM,"SequenceFault") + kw["aname"] = "_SequenceFault" + if wsrm.SequenceFaultType_Def not in wsrm.SequenceFault_Dec.__bases__: + bases = list(wsrm.SequenceFault_Dec.__bases__) + bases.insert(0, wsrm.SequenceFaultType_Def) + wsrm.SequenceFault_Dec.__bases__ = tuple(bases) + + wsrm.SequenceFaultType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SequenceFault_Dec_Holder" + + class SequenceRef_Dec(ElementDeclaration): + literal = "SequenceRef" + schema = NS.WSA.RM + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSA.RM,"SequenceRef") + kw["aname"] = "_SequenceRef" + if wsrm.SequenceRefType_Def not in wsrm.SequenceRef_Dec.__bases__: + bases = list(wsrm.SequenceRef_Dec.__bases__) + bases.insert(0, wsrm.SequenceRefType_Def) + wsrm.SequenceRef_Dec.__bases__ = tuple(bases) + + wsrm.SequenceRefType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SequenceRef_Dec_Holder" + +# end class wsrm (tns: http://schemas.xmlsoap.org/ws/2003/03/rm) diff --git a/digsby/src/msn/SOAP/xml/wsse.py b/digsby/src/msn/SOAP/xml/wsse.py new file mode 100644 index 0000000..c2f9344 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wsse.py @@ -0,0 +1,304 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + + +############################## +# targetNamespace +# http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd +############################## + +class wsse: + targetNamespace = NS.OASIS.WSSE + + class SecurityTokenReferenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.OASIS.WSSE + type = (schema, "SecurityTokenReferenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsse.SecurityTokenReferenceType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax"), GED(NS.OASIS.WSSE,"Reference",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.String() + self.attribute_typecode_dict["Usage"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + self._Reference = None + return + Holder.__name__ = "SecurityTokenReferenceType_Holder" + self.pyclass = Holder + + class SecurityHeaderType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.OASIS.WSSE + type = (schema, "SecurityHeaderType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsse.SecurityHeaderType_Def.schema + TClist = [GED(NS.OASIS.WSSE,"UsernameToken",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + GED(NS.OASIS.UTILITY,"Timestamp",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + #GED("urn:oasis:names:tc:SAML:1.0:assertion","Assertion",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")) + ] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._UsernameToken = None + self._Timestamp = None + self._Assertion = None + return + Holder.__name__ = "SecurityHeaderType_Holder" + self.pyclass = Holder + + class UsernameTokenType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.OASIS.WSSE + type = (schema, "UsernameTokenType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsse.UsernameTokenType_Def.schema + TClist = [GTD(NS.OASIS.WSSE,"AttributedString",lazy=False)(pname=(ns,"Username"), aname="_Username", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD(NS.OASIS.WSSE,"PasswordString",lazy=False)(pname=(ns,"Password"), aname="_Password", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.OASIS.UTILITY,"Id")] = ZSI.TC.AnyType() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Username = None + self._Password = None + return + Holder.__name__ = "UsernameTokenType_Holder" + self.pyclass = Holder + + class AttributedString_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.OASIS.WSSE + type = (schema, "AttributedString") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.String() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class PasswordString_Def(TypeDefinition): + # ComplexType/SimpleContent derivation of user-defined type + schema = NS.OASIS.WSSE + type = (schema, "PasswordString") + def __init__(self, pname, **kw): + ns = wsse.PasswordString_Def.schema + if wsse.AttributedString_Def not in wsse.PasswordString_Def.__bases__: + bases = list(wsse.PasswordString_Def.__bases__) + bases.insert(0, wsse.AttributedString_Def) + wsse.PasswordString_Def.__bases__ = tuple(bases) + + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + wsse.AttributedString_Def.__init__(self, pname, **kw) + + class EncodedString_Def(TypeDefinition): + # ComplexType/SimpleContent derivation of user-defined type + schema = NS.OASIS.WSSE + type = (schema, "EncodedString") + def __init__(self, pname, **kw): + ns = wsse.EncodedString_Def.schema + if wsse.AttributedString_Def not in wsse.EncodedString_Def.__bases__: + bases = list(wsse.EncodedString_Def.__bases__) + bases.insert(0, wsse.AttributedString_Def) + wsse.EncodedString_Def.__bases__ = tuple(bases) + + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + wsse.AttributedString_Def.__init__(self, pname, **kw) + + class ReferenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.OASIS.WSSE + type = (schema, "ReferenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsse.ReferenceType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["URI"] = ZSI.TC.URI() + self.attribute_typecode_dict["ValueType"] = ZSI.TC.QName() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "ReferenceType_Holder" + self.pyclass = Holder + + class KeyIdentifierType_Def(TypeDefinition): + # ComplexType/SimpleContent derivation of user-defined type + schema = NS.OASIS.WSSE + type = (schema, "KeyIdentifierType") + def __init__(self, pname, **kw): + ns = wsse.KeyIdentifierType_Def.schema + if wsse.EncodedString_Def not in wsse.KeyIdentifierType_Def.__bases__: + bases = list(wsse.KeyIdentifierType_Def.__bases__) + bases.insert(0, wsse.EncodedString_Def) + wsse.KeyIdentifierType_Def.__bases__ = tuple(bases) + + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + wsse.EncodedString_Def.__init__(self, pname, **kw) + + class BinarySecurityTokenType_Def(TypeDefinition): + # ComplexType/SimpleContent derivation of user-defined type + schema = NS.OASIS.WSSE + type = (schema, "BinarySecurityTokenType") + def __init__(self, pname, **kw): + ns = wsse.BinarySecurityTokenType_Def.schema + if wsse.EncodedString_Def not in wsse.BinarySecurityTokenType_Def.__bases__: + bases = list(wsse.BinarySecurityTokenType_Def.__bases__) + bases.insert(0, wsse.EncodedString_Def) + wsse.BinarySecurityTokenType_Def.__bases__ = tuple(bases) + + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + wsse.EncodedString_Def.__init__(self, pname, **kw) + + class FaultcodeEnum_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.OASIS.WSSE + type = (schema, "FaultcodeEnum") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class SecurityTokenReference_Dec(ElementDeclaration): + literal = "SecurityTokenReference" + schema = NS.OASIS.WSSE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.WSSE,"SecurityTokenReference") + kw["aname"] = "_SecurityTokenReference" + if wsse.SecurityTokenReferenceType_Def not in wsse.SecurityTokenReference_Dec.__bases__: + bases = list(wsse.SecurityTokenReference_Dec.__bases__) + bases.insert(0, wsse.SecurityTokenReferenceType_Def) + wsse.SecurityTokenReference_Dec.__bases__ = tuple(bases) + + wsse.SecurityTokenReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "SecurityTokenReference_Dec_Holder" + + class Security_Dec(ElementDeclaration): + literal = "Security" + schema = NS.OASIS.WSSE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.WSSE,"Security") + kw["aname"] = "_Security" + if wsse.SecurityHeaderType_Def not in wsse.Security_Dec.__bases__: + bases = list(wsse.Security_Dec.__bases__) + bases.insert(0, wsse.SecurityHeaderType_Def) + wsse.Security_Dec.__bases__ = tuple(bases) + + wsse.SecurityHeaderType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Security_Dec_Holder" + + class UsernameToken_Dec(ElementDeclaration): + literal = "UsernameToken" + schema = NS.OASIS.WSSE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.WSSE,"UsernameToken") + kw["aname"] = "_UsernameToken" + if wsse.UsernameTokenType_Def not in wsse.UsernameToken_Dec.__bases__: + bases = list(wsse.UsernameToken_Dec.__bases__) + bases.insert(0, wsse.UsernameTokenType_Def) + wsse.UsernameToken_Dec.__bases__ = tuple(bases) + + wsse.UsernameTokenType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "UsernameToken_Dec_Holder" + + class KeyIdentifier_Dec(ElementDeclaration): + literal = "KeyIdentifier" + schema = NS.OASIS.WSSE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.WSSE,"KeyIdentifier") + kw["aname"] = "_KeyIdentifier" + if wsse.KeyIdentifierType_Def not in wsse.KeyIdentifier_Dec.__bases__: + bases = list(wsse.KeyIdentifier_Dec.__bases__) + bases.insert(0, wsse.KeyIdentifierType_Def) + wsse.KeyIdentifier_Dec.__bases__ = tuple(bases) + + wsse.KeyIdentifierType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "KeyIdentifier_Dec_Holder" + + class Reference_Dec(ElementDeclaration): + literal = "Reference" + schema = NS.OASIS.WSSE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.WSSE,"Reference") + kw["aname"] = "_Reference" + if wsse.ReferenceType_Def not in wsse.Reference_Dec.__bases__: + bases = list(wsse.Reference_Dec.__bases__) + bases.insert(0, wsse.ReferenceType_Def) + wsse.Reference_Dec.__bases__ = tuple(bases) + + wsse.ReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Reference_Dec_Holder" + + class BinarySecurityToken_Dec(ElementDeclaration): + literal = "BinarySecurityToken" + schema = NS.OASIS.WSSE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.WSSE,"BinarySecurityToken") + kw["aname"] = "_BinarySecurityToken" + if wsse.BinarySecurityTokenType_Def not in wsse.BinarySecurityToken_Dec.__bases__: + bases = list(wsse.BinarySecurityToken_Dec.__bases__) + bases.insert(0, wsse.BinarySecurityTokenType_Def) + wsse.BinarySecurityToken_Dec.__bases__ = tuple(bases) + + wsse.BinarySecurityTokenType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "BinarySecurityToken_Dec_Holder" + + class PolicyReference_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "PolicyReference" + schema = NS.OASIS.WSSE + def __init__(self, **kw): + ns = wsse.PolicyReference_Dec.schema + TClist = [] + kw["pname"] = (NS.OASIS.WSSE,"PolicyReference") + kw["aname"] = "_PolicyReference" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + # attribute handling code + self.attribute_typecode_dict["URI"] = ZSI.TC.URI() + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "PolicyReference_Holder" + self.pyclass = Holder + +# end class wsse (tns: http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd) diff --git a/digsby/src/msn/SOAP/xml/wst.py b/digsby/src/msn/SOAP/xml/wst.py new file mode 100644 index 0000000..3194a79 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wst.py @@ -0,0 +1,479 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + + +############################## +# targetNamespace +# http://schemas.xmlsoap.org/ws/2005/02/trust +############################## + +class wst: + targetNamespace = NS.WSTRUST.BASE + + class RequestSecurityTokenType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestSecurityTokenType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.RequestSecurityTokenType_Def.schema + TClist = [GED(NS.WSTRUST.BASE,"TokenType",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST.BASE,"RequestType",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSP.POLICY,"AppliesTo",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSP.POLICY,"PolicyReference",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._TokenType = None + self._RequestType = None + self._AppliesTo = None + self._PolicyReference = None + self._any = [] + return + Holder.__name__ = "RequestSecurityTokenType_Holder" + self.pyclass = Holder + + class RequestTypeOpenEnum_Def(ZSI.TC.URI, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestTypeOpenEnum") + def __init__(self, pname, **kw): + ZSI.TC.URI.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class RequestTypeEnum_Def(ZSI.TC.URI, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestTypeEnum") + def __init__(self, pname, **kw): + ZSI.TC.URI.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class RequestSecurityTokenResponseType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestSecurityTokenResponseType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.RequestSecurityTokenResponseType_Def.schema + TClist = [GED(NS.WSTRUST.BASE,"TokenType",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSP.POLICY,"AppliesTo",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST.BASE,"Lifetime",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST.BASE,"RequestedSecurityToken",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST.BASE,"RequestedAttachedReference",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST.BASE,"RequestedUnattachedReference",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST.BASE,"RequestedTokenReference",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSTRUST.BASE,"RequestedProofToken",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._TokenType = None + self._AppliesTo = None + self._Lifetime = None + self._RequestedSecurityToken = None + self._RequestedAttachedReference = None + self._RequestedUnattachedReference = None + self._RequestedTokenReference = None + self._RequestedProofToken = None + return + Holder.__name__ = "RequestSecurityTokenResponseType_Holder" + self.pyclass = Holder + + class RequestedTokenReferenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestedTokenReferenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.RequestedTokenReferenceType_Def.schema + TClist = [GED(NS.OASIS.WSSE,"KeyIdentifier",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.OASIS.WSSE,"Reference",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._KeyIdentifier = None + self._Reference = None + return + Holder.__name__ = "RequestedTokenReferenceType_Holder" + self.pyclass = Holder + + class RequestedProofTokenType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestedProofTokenType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.RequestedProofTokenType_Def.schema + TClist = [GED(NS.WSTRUST.BASE,"BinarySecret",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._BinarySecret = None + return + Holder.__name__ = "RequestedProofTokenType_Holder" + self.pyclass = Holder + + class BinarySecretType_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSTRUST.BASE + type = (schema, "BinarySecretType") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class BinarySecretTypeEnum_Def(ZSI.TC.URI, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "BinarySecretTypeEnum") + def __init__(self, pname, **kw): + ZSI.TC.URI.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class BinarySecretTypeOpenEnum_Def(ZSI.TC.Union, TypeDefinition): + memberTypes = [(NS.WSTRUST.BASE, u'BinarySecretTypeEnum'), (NS.SCHEMA.BASE, u'anyURI')] + schema = NS.WSTRUST.BASE + type = (schema, "BinarySecretTypeOpenEnum") + def __init__(self, pname, **kw): + ZSI.TC.Union.__init__(self, pname, **kw) + + class RequestedSecurityTokenType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestedSecurityTokenType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.RequestedSecurityTokenType_Def.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=1, maxOccurs=1, nillable=False, processContents="lax"), GTD(NS.WSTRUST.BASE,"EncryptedDataType",lazy=False)(pname=(ns,"EncryptedData"), aname="_EncryptedData", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), + GED(NS.OASIS.WSSE,"BinarySecurityToken",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), + #GED("urn:oasis:names:tc:SAML:1.0:assertion","Assertion",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")) + ] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = None + self._EncryptedData = None + self._BinarySecurityToken = None + self._Assertion = None + return + Holder.__name__ = "RequestedSecurityTokenType_Holder" + self.pyclass = Holder + + class LifetimeType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "LifetimeType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.LifetimeType_Def.schema + TClist = [GED(NS.OASIS.UTILITY,"Created",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.OASIS.UTILITY,"Expires",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Created = None + self._Expires = None + return + Holder.__name__ = "LifetimeType_Holder" + self.pyclass = Holder + + class RequestSecurityTokenResponseCollectionType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "RequestSecurityTokenResponseCollectionType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.RequestSecurityTokenResponseCollectionType_Def.schema + TClist = [GED(NS.WSTRUST.BASE,"RequestSecurityTokenResponse",lazy=False, isref=True)(minOccurs=1, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._RequestSecurityTokenResponse = None + return + Holder.__name__ = "RequestSecurityTokenResponseCollectionType_Holder" + self.pyclass = Holder + + class CipherDataType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "CipherDataType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.CipherDataType_Def.schema + TClist = [ZSI.TC.String(pname=(ns,"CipherValue"), aname="_CipherValue", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._CipherValue = None + return + Holder.__name__ = "CipherDataType_Holder" + self.pyclass = Holder + + class EncryptedDataType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "EncryptedDataType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.EncryptedDataType_Def.schema + TClist = [GTD(NS.WSTRUST.BASE,"EncryptionMethodType",lazy=False)(pname=(ns,"EncryptionMethod"), aname="_EncryptionMethod", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GED(NS.DSIG.BASE,"KeyInfo",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GTD(NS.WSTRUST.BASE,"CipherDataType",lazy=False)(pname=(ns,"CipherData"), aname="_CipherData", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.String() + self.attribute_typecode_dict["Type"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._EncryptionMethod = None + self._KeyInfo = None + self._CipherData = None + return + Holder.__name__ = "EncryptedDataType_Holder" + self.pyclass = Holder + + class EncryptionMethodType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSTRUST.BASE + type = (schema, "EncryptionMethodType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wst.EncryptionMethodType_Def.schema + TClist = [] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Algorithm"] = ZSI.TC.String() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + return + Holder.__name__ = "EncryptionMethodType_Holder" + self.pyclass = Holder + + class RequestSecurityToken_Dec(ElementDeclaration): + literal = "RequestSecurityToken" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"RequestSecurityToken") + kw["aname"] = "_RequestSecurityToken" + if wst.RequestSecurityTokenType_Def not in wst.RequestSecurityToken_Dec.__bases__: + bases = list(wst.RequestSecurityToken_Dec.__bases__) + bases.insert(0, wst.RequestSecurityTokenType_Def) + wst.RequestSecurityToken_Dec.__bases__ = tuple(bases) + + wst.RequestSecurityTokenType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestSecurityToken_Dec_Holder" + + class TokenType_Dec(ZSI.TC.URI, ElementDeclaration): + literal = "TokenType" + schema = NS.WSTRUST.BASE + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"TokenType") + kw["aname"] = "_TokenType" + class IHolder(str): typecode=self + kw["pyclass"] = IHolder + IHolder.__name__ = "_TokenType_immutable_holder" + ZSI.TC.URI.__init__(self, **kw) + + class RequestType_Dec(ElementDeclaration): + literal = "RequestType" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"RequestType") + kw["aname"] = "_RequestType" + if wst.RequestTypeOpenEnum_Def not in wst.RequestType_Dec.__bases__: + bases = list(wst.RequestType_Dec.__bases__) + bases.insert(0, wst.RequestTypeOpenEnum_Def) + wst.RequestType_Dec.__bases__ = tuple(bases) + + wst.RequestTypeOpenEnum_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestType_Dec_Holder" + + class RequestSecurityTokenResponse_Dec(ElementDeclaration): + literal = "RequestSecurityTokenResponse" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"RequestSecurityTokenResponse") + kw["aname"] = "_RequestSecurityTokenResponse" + if wst.RequestSecurityTokenResponseType_Def not in wst.RequestSecurityTokenResponse_Dec.__bases__: + bases = list(wst.RequestSecurityTokenResponse_Dec.__bases__) + bases.insert(0, wst.RequestSecurityTokenResponseType_Def) + wst.RequestSecurityTokenResponse_Dec.__bases__ = tuple(bases) + + wst.RequestSecurityTokenResponseType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestSecurityTokenResponse_Dec_Holder" + + class RequestedAttachedReference_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "RequestedAttachedReference" + schema = NS.WSTRUST.BASE + def __init__(self, **kw): + ns = wst.RequestedAttachedReference_Dec.schema + TClist = [GED(NS.OASIS.WSSE,"SecurityTokenReference",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + kw["pname"] = (NS.WSTRUST.BASE,"RequestedAttachedReference") + kw["aname"] = "_RequestedAttachedReference" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SecurityTokenReference = None + return + Holder.__name__ = "RequestedAttachedReference_Holder" + self.pyclass = Holder + + class RequestedUnattachedReference_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "RequestedUnattachedReference" + schema = NS.WSTRUST.BASE + def __init__(self, **kw): + ns = wst.RequestedUnattachedReference_Dec.schema + TClist = [GED(NS.OASIS.WSSE,"SecurityTokenReference",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + kw["pname"] = (NS.WSTRUST.BASE,"RequestedUnattachedReference") + kw["aname"] = "_RequestedUnattachedReference" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._SecurityTokenReference = None + return + Holder.__name__ = "RequestedUnattachedReference_Holder" + self.pyclass = Holder + + class RequestedTokenReference_Dec(ElementDeclaration): + literal = "RequestedTokenReference" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"RequestedTokenReference") + kw["aname"] = "_RequestedTokenReference" + if wst.RequestedTokenReferenceType_Def not in wst.RequestedTokenReference_Dec.__bases__: + bases = list(wst.RequestedTokenReference_Dec.__bases__) + bases.insert(0, wst.RequestedTokenReferenceType_Def) + wst.RequestedTokenReference_Dec.__bases__ = tuple(bases) + + wst.RequestedTokenReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestedTokenReference_Dec_Holder" + + class RequestedProofToken_Dec(ElementDeclaration): + literal = "RequestedProofToken" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"RequestedProofToken") + kw["aname"] = "_RequestedProofToken" + if wst.RequestedProofTokenType_Def not in wst.RequestedProofToken_Dec.__bases__: + bases = list(wst.RequestedProofToken_Dec.__bases__) + bases.insert(0, wst.RequestedProofTokenType_Def) + wst.RequestedProofToken_Dec.__bases__ = tuple(bases) + + wst.RequestedProofTokenType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestedProofToken_Dec_Holder" + + class BinarySecret_Dec(ElementDeclaration): + literal = "BinarySecret" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"BinarySecret") + kw["aname"] = "_BinarySecret" + if wst.BinarySecretType_Def not in wst.BinarySecret_Dec.__bases__: + bases = list(wst.BinarySecret_Dec.__bases__) + bases.insert(0, wst.BinarySecretType_Def) + wst.BinarySecret_Dec.__bases__ = tuple(bases) + + wst.BinarySecretType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "BinarySecret_Dec_Holder" + + class RequestedSecurityToken_Dec(ElementDeclaration): + literal = "RequestedSecurityToken" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"RequestedSecurityToken") + kw["aname"] = "_RequestedSecurityToken" + if wst.RequestedSecurityTokenType_Def not in wst.RequestedSecurityToken_Dec.__bases__: + bases = list(wst.RequestedSecurityToken_Dec.__bases__) + bases.insert(0, wst.RequestedSecurityTokenType_Def) + wst.RequestedSecurityToken_Dec.__bases__ = tuple(bases) + + wst.RequestedSecurityTokenType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestedSecurityToken_Dec_Holder" + + class Lifetime_Dec(ElementDeclaration): + literal = "Lifetime" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"Lifetime") + kw["aname"] = "_Lifetime" + if wst.LifetimeType_Def not in wst.Lifetime_Dec.__bases__: + bases = list(wst.Lifetime_Dec.__bases__) + bases.insert(0, wst.LifetimeType_Def) + wst.Lifetime_Dec.__bases__ = tuple(bases) + + wst.LifetimeType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Lifetime_Dec_Holder" + + class RequestSecurityTokenResponseCollection_Dec(ElementDeclaration): + literal = "RequestSecurityTokenResponseCollection" + schema = NS.WSTRUST.BASE + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSTRUST.BASE,"RequestSecurityTokenResponseCollection") + kw["aname"] = "_RequestSecurityTokenResponseCollection" + if wst.RequestSecurityTokenResponseCollectionType_Def not in wst.RequestSecurityTokenResponseCollection_Dec.__bases__: + bases = list(wst.RequestSecurityTokenResponseCollection_Dec.__bases__) + bases.insert(0, wst.RequestSecurityTokenResponseCollectionType_Def) + wst.RequestSecurityTokenResponseCollection_Dec.__bases__ = tuple(bases) + + wst.RequestSecurityTokenResponseCollectionType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "RequestSecurityTokenResponseCollection_Dec_Holder" + +# end class wst (tns: http://schemas.xmlsoap.org/ws/2005/02/trust) + diff --git a/digsby/src/msn/SOAP/xml/wsu.py b/digsby/src/msn/SOAP/xml/wsu.py new file mode 100644 index 0000000..c024037 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wsu.py @@ -0,0 +1,281 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://schemas.xmlsoap.org/ws/2002/07/utility +############################## + +class wsu: + targetNamespace = NS.WSU.UTILITY + + class tTimestampFault_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSU.UTILITY + type = (schema, "tTimestampFault") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class tContextFault_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.WSU.UTILITY + type = (schema, "tContextFault") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class AttributedDateTime_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSU.UTILITY + type = (schema, "AttributedDateTime") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["ValueType"] = ZSI.TC.QName() + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class ReceivedType_Def(TypeDefinition): + # ComplexType/SimpleContent derivation of user-defined type + schema = NS.WSU.UTILITY + type = (schema, "ReceivedType") + def __init__(self, pname, **kw): + ns = wsu.ReceivedType_Def.schema + if wsu.AttributedDateTime_Def not in wsu.ReceivedType_Def.__bases__: + bases = list(wsu.ReceivedType_Def.__bases__) + bases.insert(0, wsu.AttributedDateTime_Def) + wsu.ReceivedType_Def.__bases__ = tuple(bases) + + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Delay"] = ZSI.TCnumbers.Iint() + self.attribute_typecode_dict["Actor"] = ZSI.TC.URI() + wsu.AttributedDateTime_Def.__init__(self, pname, **kw) + + class AttributedURI_Def(ZSI.TC.URI, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.WSU.UTILITY + type = (schema, "AttributedURI") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.URI.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class TimestampType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSU.UTILITY + type = (schema, "TimestampType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsu.TimestampType_Def.schema + TClist = [GED(NS.WSU.UTILITY,"Created",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSU.UTILITY,"Expires",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSU.UTILITY,"Received",lazy=False, isref=True)(minOccurs=0, maxOccurs="unbounded", nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Created = None + self._Expires = None + self._Received = None + self._any = [] + return + Holder.__name__ = "TimestampType_Holder" + self.pyclass = Holder + + class ContextType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSU.UTILITY + type = (schema, "ContextType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsu.ContextType_Def.schema + TClist = [GED(NS.WSU.UTILITY,"Expires",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.WSU.UTILITY,"Identifier",lazy=False, isref=True)(minOccurs=1, maxOccurs=1, nillable=False, encoded=kw.get("encoded"))] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Expires = None + self._Identifier = None + return + Holder.__name__ = "ContextType_Holder" + self.pyclass = Holder + + class PortReferenceType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.WSU.UTILITY + type = (schema, "PortReferenceType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsu.PortReferenceType_Def.schema + TClist = [GTD(NS.WSU.UTILITY,"AttributedURI",lazy=False)(pname=(ns,"Address"), aname="_Address", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + self.attribute_typecode_dict[(NS.WSU.UTILITY,"Id")] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Address = None + self._any = [] + return + Holder.__name__ = "PortReferenceType_Holder" + self.pyclass = Holder + + class Timestamp_Dec(ElementDeclaration): + literal = "Timestamp" + schema = NS.WSU.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSU.UTILITY,"Timestamp") + kw["aname"] = "_Timestamp" + if wsu.TimestampType_Def not in wsu.Timestamp_Dec.__bases__: + bases = list(wsu.Timestamp_Dec.__bases__) + bases.insert(0, wsu.TimestampType_Def) + wsu.Timestamp_Dec.__bases__ = tuple(bases) + + wsu.TimestampType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Timestamp_Dec_Holder" + + class Expires_Dec(ElementDeclaration): + literal = "Expires" + schema = NS.WSU.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSU.UTILITY,"Expires") + kw["aname"] = "_Expires" + if wsu.AttributedDateTime_Def not in wsu.Expires_Dec.__bases__: + bases = list(wsu.Expires_Dec.__bases__) + bases.insert(0, wsu.AttributedDateTime_Def) + wsu.Expires_Dec.__bases__ = tuple(bases) + + wsu.AttributedDateTime_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Expires_Dec_Holder" + + class Created_Dec(ElementDeclaration): + literal = "Created" + schema = NS.WSU.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSU.UTILITY,"Created") + kw["aname"] = "_Created" + if wsu.AttributedDateTime_Def not in wsu.Created_Dec.__bases__: + bases = list(wsu.Created_Dec.__bases__) + bases.insert(0, wsu.AttributedDateTime_Def) + wsu.Created_Dec.__bases__ = tuple(bases) + + wsu.AttributedDateTime_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Created_Dec_Holder" + + class Received_Dec(ElementDeclaration): + literal = "Received" + schema = NS.WSU.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSU.UTILITY,"Received") + kw["aname"] = "_Received" + if wsu.ReceivedType_Def not in wsu.Received_Dec.__bases__: + bases = list(wsu.Received_Dec.__bases__) + bases.insert(0, wsu.ReceivedType_Def) + wsu.Received_Dec.__bases__ = tuple(bases) + + wsu.ReceivedType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Received_Dec_Holder" + + class Identifier_Dec(ElementDeclaration): + literal = "Identifier" + schema = NS.WSU.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSU.UTILITY,"Identifier") + kw["aname"] = "_Identifier" + if wsu.AttributedURI_Def not in wsu.Identifier_Dec.__bases__: + bases = list(wsu.Identifier_Dec.__bases__) + bases.insert(0, wsu.AttributedURI_Def) + wsu.Identifier_Dec.__bases__ = tuple(bases) + + wsu.AttributedURI_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Identifier_Dec_Holder" + + class Context_Dec(ZSI.TCcompound.ComplexType, ElementDeclaration): + literal = "Context" + schema = NS.WSU.UTILITY + def __init__(self, **kw): + ns = wsu.Context_Dec.schema + TClist = [ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + kw["pname"] = (NS.WSU.UTILITY,"Context") + kw["aname"] = "_Context" + self.attribute_typecode_dict = {} + ZSI.TCcompound.ComplexType.__init__(self,None,TClist,inorder=0,**kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._any = [] + return + Holder.__name__ = "Context_Holder" + self.pyclass = Holder + + class PortReference_Dec(ElementDeclaration): + literal = "PortReference" + schema = NS.WSU.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.WSU.UTILITY,"PortReference") + kw["aname"] = "_PortReference" + if wsu.PortReferenceType_Def not in wsu.PortReference_Dec.__bases__: + bases = list(wsu.PortReference_Dec.__bases__) + bases.insert(0, wsu.PortReferenceType_Def) + wsu.PortReference_Dec.__bases__ = tuple(bases) + + wsu.PortReferenceType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "PortReference_Dec_Holder" + +# end class wsu (tns: http://schemas.xmlsoap.org/ws/2002/07/utility) + diff --git a/digsby/src/msn/SOAP/xml/wsu_oasis.py b/digsby/src/msn/SOAP/xml/wsu_oasis.py new file mode 100644 index 0000000..ffed272 --- /dev/null +++ b/digsby/src/msn/SOAP/xml/wsu_oasis.py @@ -0,0 +1,125 @@ +import ZSI +import ZSI.TCcompound +import ZSI.wstools.Namespaces as NS +from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED +from ZSI.generate.pyclass import pyclass_type + +############################## +# targetNamespace +# http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd +############################## + +class wsu_oasis: + targetNamespace = NS.OASIS.UTILITY + + class tTimestampFault_Def(ZSI.TC.QName, TypeDefinition): + schema = NS.OASIS.UTILITY + type = (schema, "tTimestampFault") + def __init__(self, pname, **kw): + ZSI.TC.QName.__init__(self, pname, pyclass=None, **kw) + class Holder(str): + typecode = self + self.pyclass = Holder + + class AttributedDateTime_Def(ZSI.TC.String, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.OASIS.UTILITY + type = (schema, "AttributedDateTime") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.String.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class AttributedURI_Def(ZSI.TC.URI, TypeDefinition): + # ComplexType/SimpleContent derivation of built-in type + schema = NS.OASIS.UTILITY + type = (schema, "AttributedURI") + def __init__(self, pname, **kw): + if getattr(self, "attribute_typecode_dict", None) is None: self.attribute_typecode_dict = {} + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TC.URI.__init__(self, pname, **kw) + class Holder(str): + __metaclass__ = pyclass_type + typecode = self + self.pyclass = Holder + + class TimestampType_Def(ZSI.TCcompound.ComplexType, TypeDefinition): + schema = NS.OASIS.UTILITY + type = (schema, "TimestampType") + def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw): + ns = wsu_oasis.TimestampType_Def.schema + TClist = [GED(NS.OASIS.UTILITY,"Created",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), GED(NS.OASIS.UTILITY,"Expires",lazy=False, isref=True)(minOccurs=0, maxOccurs=1, nillable=False, encoded=kw.get("encoded")), ZSI.TC.AnyElement(aname="_any", minOccurs=0, maxOccurs="unbounded", nillable=False, processContents="lax")] + self.attribute_typecode_dict = attributes or {} + if extend: TClist += ofwhat + if restrict: TClist = ofwhat + else: + # attribute handling code + self.attribute_typecode_dict["Id"] = ZSI.TC.AnyType() + self.attribute_typecode_dict[(NS.SCHEMA.BASE,"anyAttribute")] = ZSI.TC.AnyElement() + ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw) + class Holder: + __metaclass__ = pyclass_type + typecode = self + def __init__(self): + # pyclass + self._Created = None + self._Expires = None + self._any = [] + return + Holder.__name__ = "TimestampType_Holder" + self.pyclass = Holder + + class Timestamp_Dec(ElementDeclaration): + literal = "Timestamp" + schema = NS.OASIS.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.UTILITY,"Timestamp") + kw["aname"] = "_Timestamp" + if wsu_oasis.TimestampType_Def not in wsu_oasis.Timestamp_Dec.__bases__: + bases = list(wsu_oasis.Timestamp_Dec.__bases__) + bases.insert(0, wsu_oasis.TimestampType_Def) + wsu_oasis.Timestamp_Dec.__bases__ = tuple(bases) + + wsu_oasis.TimestampType_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Timestamp_Dec_Holder" + + class Expires_Dec(ElementDeclaration): + literal = "Expires" + schema = NS.OASIS.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.UTILITY,"Expires") + kw["aname"] = "_Expires" + if wsu_oasis.AttributedDateTime_Def not in wsu_oasis.Expires_Dec.__bases__: + bases = list(wsu_oasis.Expires_Dec.__bases__) + bases.insert(0, wsu_oasis.AttributedDateTime_Def) + wsu_oasis.Expires_Dec.__bases__ = tuple(bases) + + wsu_oasis.AttributedDateTime_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Expires_Dec_Holder" + + class Created_Dec(ElementDeclaration): + literal = "Created" + schema = NS.OASIS.UTILITY + substitutionGroup = None + def __init__(self, **kw): + kw["pname"] = (NS.OASIS.UTILITY,"Created") + kw["aname"] = "_Created" + if wsu_oasis.AttributedDateTime_Def not in wsu_oasis.Created_Dec.__bases__: + bases = list(wsu_oasis.Created_Dec.__bases__) + bases.insert(0, wsu_oasis.AttributedDateTime_Def) + wsu_oasis.Created_Dec.__bases__ = tuple(bases) + + wsu_oasis.AttributedDateTime_Def.__init__(self, **kw) + if self.pyclass is not None: self.pyclass.__name__ = "Created_Dec_Holder" + +# end class wsu_oasis (tns: http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd) diff --git a/digsby/src/msn/Storage.py b/digsby/src/msn/Storage.py new file mode 100644 index 0000000..829d4a4 --- /dev/null +++ b/digsby/src/msn/Storage.py @@ -0,0 +1,89 @@ +import uuid +import simplejson as json +import logging + +import util +log = logging.getLogger('msn.storage') + +from msn.SOAP import Namespaces as MSNS +import msn.SOAP.services as SOAPServices +import msn.SOAP.pysoap_util as pysoap + +def extract_zsi_properties(zsiobj): + return dict((k[1:], v) for k, v in vars(zsiobj).items()) + +class DocumentStream(pysoap.Serializable): + @classmethod + def from_zsi(cls, obj, **kwds): + real_cls = { + (MSNS.MSWS.STORAGE, 'PhotoStream') : PhotoStream, + (MSNS.MSWS.STORAGE, 'DocumentStream') : DocumentStream, + }.get(obj.typecode.type) + + if cls is DocumentStream: + DS = pysoap.Serializable.from_zsi.im_func(real_cls, obj, **kwds) + # more stuff? + else: + DS = real_cls.from_zsi(obj, **kwds) + + DS.SHA1Hash = DS.SHA1Hash.encode('b64') + + return DS + +class PhotoStream(pysoap.Serializable): + pass + +class Photo(pysoap.Serializable): + ResourceID = None + DocumentStreams = [DocumentStream] + @classmethod + def from_zsi(cls, obj, **kwds): + P = pysoap.Serializable.from_zsi.im_func(cls, obj, **kwds) + P.DocumentStreams = [DocumentStream.from_zsi(X) for X in P.DocumentStreams.DocumentStream] + return P + +class ExpressionProfile(pysoap.Serializable): + Photo = Photo + @classmethod + def from_zsi(cls, obj, **kwds): + EP = pysoap.Serializable.from_zsi.im_func(cls, obj, **kwds) + EP.Photo = Photo.from_zsi(EP.Photo) + return EP + +class Profile(pysoap.Serializable): + nonserialize_attributes = ['client'] + ExpressionProfile = ExpressionProfile + def __init__(self, client = None, **kw): + self.client = client + super(Profile, self).__init__(**kw) + + @classmethod + def from_zsi(cls, obj, **kwds): + P = pysoap.Serializable.from_zsi.im_func(cls, obj, **kwds) + P.ExpressionProfile = ExpressionProfile.from_zsi(P.ExpressionProfile) + return P + +def __main(): + import ZSI, ZSI.schema as schema + data = '1p8CXNxj8mTbyfRy5cdbxUbNpOw5IAof_RjSy7XAotTIAs1eKcah9MQg000t=EwDoAW+jAAAUVwo8hY/Rg6HvEdmXr19KcNgO+7mAAA3oPdbFO1wSu82lRckR/LUL2fSAHbA7skahr3eq37BfYX1s7Dcj6PKkLco7bUKCEP9NPZGXeM5zLb6SgsnDwnrrgs6rjZrOAyz2Nf1Jg8tj0uDg4BzCtEm2lQi7P1LjGrHfvkD4bJKQTRW2Ot8W4zop6cCSPKjmKxH15v/cXqlkA2YAAAh2WM/FIDyMijgBvRY/ln+7e+GXZ+0dIb1AlHP47KUaEcX4HI1UgJMnKQ7Z2ybcWP19kthAYQLmc1QhCMfocKWTbniLzWO+H4EEwIf7VQGyGzgCsQ+L9Oz+oxad4A7WYOcmO58yIYEYETk7rYhRzPXYKbLM2fM/WVg6rrPC3LG/oyqMkbmQf6I0ebQxOPv/k7npSwpMnj4bMmMh/TjoA+4lSQrjx4iCT6ZJq9jmLRyRH5mlJ/ultaXCAFVttQ7dAEGYu7YuZOActLa5DyUYQ1/F/AK21k3VX+5YvTVvIXFE1ourC+YumAJlouczBvtGIy5DNvAwpCIXeqHDP6iYGQpID8cTiCf95Ctq3oK5pHmHSfCPi+wymbpBVzlVNlRF6kVuR64oxMFX+CojJLyQmv6UYSlU0YVZ1e9zi9pcBa42huJ5dQE=&p=falseC9133A4085E57C34!1012011-03-01T06:24:21.577-08:00C9133A4085E57C34!1302011-02-28T15:00:25.977-08:00PhotoC9133A4085E57C34!1322011-02-28T15:00:26.713-08:00WebcamUserTileStaticimage/jpeg2347http://sn2files.storage.msn.com/y1pSLNg0PlgMWDC0wQ8Qeriz4OKCmrd1fT-P6-GIgReDdlxsCQxX6c0-j_35MhEGCk0K1n8et_j8xjXVjSDxQ7dQghttp://internal.sn2.pvt-livefilestore.com/y1pSLNg0PlgMWDC0wQ8Qeriz4OKCmrd1fT-P6-GIgReDdlxsCQxX6c0-j_35MhEGCk0K1n8et_j8xjXVjSDxQ7dQgOverwriterG952Xtm0+Ctn0o/LLeOorBBtik=false258UserTileStatic9696- on www.ebuddy.com Web Messenger2010-05-27T13:05:25-07:00Michael2008-01-10T16:02:26-08:00http://sn2files.storage.msn.com/y1pvl2Jn-4-_9Ofj9SL1vLwNeWQ5OcYt-g4qxVurMMylKIhNnQs6kWAFBc3RiEws2KTD8AV8EfPw6E' + + ps = ZSI.ParsedSoap(data) + pname = ps.body_root.namespaceURI, ps.body_root.localName + el_type = schema.GTD(*pname) + if el_type is not None: + tc = el_type(pname) + else: + tc = schema.GED(*pname) + + reply = ps.Parse(tc) + + profile = Profile.from_zsi(reply.GetProfileResult) + try: + print profile.serialize() + except: + import traceback; traceback.print_exc() + + return profile + +if __name__ == '__main__': + __main() diff --git a/digsby/src/msn/__init__.py b/digsby/src/msn/__init__.py new file mode 100644 index 0000000..6e78921 --- /dev/null +++ b/digsby/src/msn/__init__.py @@ -0,0 +1,50 @@ +""" + +MSN Protocol! + +""" +import MSNUtil as util +import MSNClientID +import oim +from MSNCommands import CommandProcessor, Message, MSNTextMessage + +from MSNSocket import MSNSocket, MSNSocketBase +from MsnHttpSocket import MsnHttpSocket +import MSNBuddies +from MSN import MSNClient as protocol +from MSNConversation import MSNConversation as Conversation + +from MSNBuddy import MSNBuddy as buddy +from MSNBuddy import MSNContact as Contact +from MSNErrcodes import codes as error_codes +from MSNObject import MSNObject +import P2P +import MSNCommands + +from p import Notification +from NSSBAdapter import NSSBAdapter + + +class MSNException(Exception): + def __init__(self, code, *args): + if isinstance(code, basestring): + try: + code = int(code) + except (ValueError,): + code = 100 + elif isinstance(code, message): + code = code.error_code + assert not args + else: + from util.primitives.funcs import isint + assert isint(code) + + msg = error_codes.get(code, 'Unknown') + Exception.__init__(self, code, msg, *args) +exception = MSNException + +class LoginException(Exception): pass +class GeneralException(Exception): pass +class WrongVersionException(Exception): pass + +errors = (LoginException, GeneralException, MSNException, WrongVersionException) diff --git a/digsby/src/msn/conversationmanager.py b/digsby/src/msn/conversationmanager.py new file mode 100644 index 0000000..96fc4d7 --- /dev/null +++ b/digsby/src/msn/conversationmanager.py @@ -0,0 +1,70 @@ +import util.primitives.funcs as funcs + +from logging import getLogger +log = getLogger('msn.conversationmanager') + +class ConversationManager(object): + def __init__(self, msn): + ''' + msn is an MSNProtocol object + ''' + self.msn = msn + self._convs = {} + + def find_convo(self, name=(), count=(), type=(), f=None): + ''' + Finds all conversation such that: + All names in the sequence 'name' are also in the conversation, and vice versa + the number of buddies is in the sequence 'count' + the type of the conversation is in the sequence 'type' + f(conversation) is True + + If an argument is not provided, it is ignored. + + A list is returned. (Empty if no conversations match) + ''' + result = [] + + if f is None: + f = lambda x:True + + if not funcs.isiterable(name): + name = name, + if not funcs.isiterable(type): + type = type, + if not funcs.isiterable(count): + count = count, + + functions = [f] + + def namechecker(c): + mynames = set(name) + cnames = set(c.buddies) + + mynames.discard(self.self_buddy.name) + cnames. discard(self.self_buddy.name) + + return mynames == cnames + + if name: functions.append(namechecker) + + def typechecker(c): + return c.type in type + + if type: functions.append(typechecker) + + def countchecker(c): + return len(c.room_list) in count + + if count: functions.append(countchecker) + + import inspect + log.debug('find_convo: name=%s,count=%s,type=%s,f=%s', + name,count,type, inspect.getsource(f).strip()) + for conv in self._convs.values(): + log.debug('%r: names=%r,count=%s,type=%s,id=%s', + conv, conv.buddies, len(conv.room_list), conv.type, id(conv)) + if all(_f(conv) for _f in functions): + result.append(conv) + + return result diff --git a/digsby/src/msn/msngui.py b/digsby/src/msn/msngui.py new file mode 100644 index 0000000..b11e96a --- /dev/null +++ b/digsby/src/msn/msngui.py @@ -0,0 +1,71 @@ +import util +import util.callbacks as callbacks +import wx +import gui.toolbox as toolbox +import gui.skin as skin + +import logging +log = logging.getLogger("msn.gui") + +class MSNGroupInviteDialog(toolbox.SimpleMessageDialog): + + def __init__(self, circle, inviter): + icon = skin.get('serviceicons.msn', None) + super(MSNGroupInviteDialog, self).__init__(wx.GetApp().TopWindow, + _("Group Invite"), + message = self.get_message(circle.NickName, + inviter.account, + inviter.invite_message), + icon = icon, + ok_caption = _('Yes'), + cancel_caption = _('No')) + + def get_message(self, circle_name, inviter_email, invite_message): + if invite_message: + invite_segment = '\n' + _('{inviter_email} says: "{invite_message}"').format(inviter_email = inviter_email, invite_message = invite_message) + else: + invite_segment = u"" + return _('{inviter_email} has invited you to join the group {circle_name}.{invite_segment}\nWould you like to join?').format(inviter_email = inviter_email, circle_name = circle_name, invite_segment = invite_segment) + +class MSNCreateCircleDialog(wx.TextEntryDialog, toolbox.NonModalDialogMixin): + def __init__(self): + icon = skin.get('serviceicons.msn', None) + toolbox.NonModalDialogMixin.__init__(self) + wx.TextEntryDialog.__init__(self, wx.GetApp().TopWindow, + caption = _('Create Group Chat'), + message = _("Please enter a name for your group chat:"), + style = wx.OK | wx.CANCEL | wx.CENTRE) + + self.icon = icon + if icon is not None: + self.SetFrameIcon(self.icon) + + def on_button(self, e): + ok = e.Id == wx.ID_OK + self.Hide() + cb, self.cb = self.cb, None + txt = self.FindWindowByName('text') + if cb is not None: + with util.traceguard: + cb(ok, txt.Value) + + wx.CallAfter(self.Destroy) + +def on_circle_invite(circle, inviter, cb): + @wx.CallAfter + def make_and_show_dialog(): + dlg = MSNGroupInviteDialog(circle, inviter) + dlg.ShowWithCallback(cb) + +@callbacks.callsback +def on_circle_create_prompt(callback = None): + def cb(result, text): + if result and text: + callback.success(text) + else: + callback.error() + + @wx.CallAfter + def make_and_show_dialog(): + dlg = MSNCreateCircleDialog() + dlg.ShowWithCallback(cb) diff --git a/digsby/src/msn/oim.py b/digsby/src/msn/oim.py new file mode 100644 index 0000000..af89f64 --- /dev/null +++ b/digsby/src/msn/oim.py @@ -0,0 +1,499 @@ +from __future__ import with_statement +import re, sys, itertools +from util import threaded, traceguard, CallCounter, fmt_to_dict +from util.xml_tag import tag, post_xml +from uuid import UUID +from datetime import datetime +from email import message_from_string +from email.header import Header, decode_header +from base64 import b64decode + +from common import pref + +from logging import getLogger +log = getLogger('msn.oim') + +import msn +import uuid +import util +from util.auxencodings import fuzzydecode +from util.Events import EventMixin, event + +SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/" +POST_URL = "https://rsi.hotmail.com/rsi/rsi.asmx" +NS = "http://www.hotmail.msn.com/ws/2004/09/oim/rsi" +GET = NS + "/GetMessage" +DEL = NS + "/DeleteMessages" +META = NS + '/GetMetadata' + +import msn.SOAP.services as SOAPServices + +def soap(ns): + env = tag((('soap',SOAP_NS), 'Envelope'), xmlns=ns) + env += ('soap','Header'), + env += ('soap','Body'), + + return env + +def make_header(s): + return str(Header(s, 'utf-8')) + +class OIMMessages(list): + def __init__(self, acct, t): + log.info('OIMMessages created') + + self.acct = acct + list.__init__(self) + + if t is None: + # SOAPRequest the XML data and store it in messages + messages = None + else: + messages = t.M + + self.msginit(messages) + + def msginit(self, meta): + if meta is None: + return self.request_meta() + + if not meta: + return + + if type(meta) is tag: + messages = [meta] + else: + messages = meta + + del self[:] + + for message in messages: + with traceguard: + self.append(OIM(self.acct, message)) + self.get_messages(pref('msn.oim.markasread',False), True) + + + def get_messages(self, markread=False, delete=True): + for oim in self: + callback = lambda _oim=oim: (self.received(_oim), log.info_s('Received: %r', oim)) + oim.get_message(markread=markread, success=callback, error=self.get_message_error) + + if False: #delete: + self.delete_messages() + + def get_message_error(self, e): + log.info("Error getting message %r", e) + try: + log.info('\t%r', e.body.getvalue()) + except: + pass + + def received(self, oim): + log.info('Received OIM (%d/%d): %r', len(filter(None, [x.received for x in self])), len(self), oim) + if all(x.received for x in self): + log.info('Received all OIMs. Telling acct') + self.acct.received_oims(list(sorted(self))) + + def delete_messages(self): + rsi = self.acct.getService(SOAPServices.AppIDs.RSIService) + rsi.DeleteMessages(message_ids = [oim.id for oim in self], + success = self.delete_success) + + def delete_success(self, response): + + fault = response._findOne('Fault') + + + if response._find('DeleteMessagesResponse'): + for oim in self: + oim.deleted = True + log.info('OIMs deleted from server') + elif fault: + log.info('OIMs were not deleted. Fault occurred: %r', fault._to_xml(pretty=False)) + + def request_meta(self): + rsi = self.acct.getService(SOAPServices.AppIDs.RSIService) + rsi.GetMetadata(success = lambda response: self.msginit(response.MD.M)) + +class OIM(object): + + time_re = re.compile('(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})(Z?)') + def __init__(self, acct, m_tag): + ''' + http://msnpiki.msnfanatic.com/index.php/MSNP13:Offline_IM + + * T: Unknown, but has so far only been set to 11. + * S: Unknown, but has so far only been set to 6. + * RT: The date/time stamp for when the message was received by the server. + This stamp can be used to sort the message in the proper order, + although you are recommended to use a different method instead + which will be explained later. + * RS: Unknown, but most likely is set to 1 if the message has been read + before ("Read Set"). + * SZ: The size of the message, including headers + * E: The e-mail address of the sender + * I: This is the ID of the message, which should be used later on to retrieve + the message. Note that the ID is a GUID in the form + XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. It was previously (the change + was first noticed in March 2007) in the format of + "MSGunix-timestamp.millseconds" (for example MSG1132093467.11) and + the Message ID format could change again anytime. + * F: Unknown, but has so far only been observed as either a GUID with a + single 9 at the end, or as ".!!OIM" (in case you are already online + when receiving the notification). + * N: This field contains the friendlyname of the person, wrapped in a special + encoding. This encoding is defined in RFC 2047, but to get you started + there is a quick overview of the format below (see #Field_encoding). + You are recommended however to implement a fully able e-mail parser to + handle OIMs! + o Note! When this field is found in a non-initial notification it will + contain a space in the data field. You must filter this space (trim + the string) in order to correctly decode this field! + * SU: Unknown, has only been observed to contain one space. + + Example: + + 11 + 6 + 2007-05-14T15:52:53.377Z + 0 + 950 + x2ndshadow@hotmail.com + 08CBD8BE-9972-433C-A9DA-84A0A725ABFA + 00000000-0000-0000-0000-000000000009 + =?utf-8?B?QWFyb24=?= + + ''' + + self.acct = acct + self.size = int(str(m_tag.SZ)) + self.email = str(m_tag.E) + + self.name = u'' + + for val, encoding in decode_header(m_tag.N.text.strip()): + self.name += fuzzydecode(val, encoding) + + try: + self.time = self.parse_time(str(m_tag.RT)) + except Exception: + self.time = None + self.id = UUID(str(m_tag.I)) + self.msg = '' + self.deleted = False + self._had_error = False + self.received = False + + self.runid = None + self.seqnum = 0 + + log.info_s('%r created', self) + + @util.callbacks.callsback + def get_message(self, markread=False, callback=None): + rsi = self.acct.getService(SOAPServices.AppIDs.RSIService) + rsi.GetMessage(client = self.acct, message_id = self.id, markread = markread, + success = lambda resp: (self.parse_msg(resp), callback.success()), + error = callback.error) + + def parse_msg(self, response): + log.debug('Parsing this OIM: %r',response._to_xml(pretty=False)) + + self._content = (response.Body.GetMessageResponse.GetMessageResult._cdata.strip()).encode('utf-8') + + fault = response._findOne('Fault') + + if fault: + log.error('Error retrieving message (%r): %r', self, fault._to_xml(pretty=False)) + self._had_error = True + return + + self._msgobj = message_from_string(self._content) + oim_proxy = self._msgobj.get('X-OIMProxy', None) + + if oim_proxy == 'MOSMS': # MObile SMS + self._parse_mobile_oim() + + payload = self._msgobj + # rfc822 messages have a pretty bad API. We call get_payload(0) on it (to get the first part of a multipart message + # as long as it continues to work. When we get a TypeError, it's because it tried call something on a string (the + # real content) instead of a list (which is what there is when the message is_multipart()). By the end of this loop + # payload will be the our rfc822 object that has the *real* message as it's payload. + while True: + try: + payload = payload.get_payload(0) + except TypeError: + break + + msgtext = payload.get_payload() + charset = payload.get_content_charset() or '' + + msgtext = msgtext.strip() + msgtext = msgtext.decode('base64') + msgtext = msgtext.decode('fuzzy %s' % charset) + + self.msg = msgtext + + self.received = True + + self.runid = self._msgobj.get('X-OIM-Run-Id', None) + if self.runid is not None: + self.runid = uuid.UUID(self.runid) + + try: + self.seqnum = int(self._msgobj.get('X-OIM-Sequence-Num', '0')) + except ValueError: + self.seqnum = 0 + + newtime = self._msgobj.get('X-OriginalArrivalTime', self.time) + if isinstance(newtime, basestring): + try: + timestr = newtime.split(' (UTC) ')[0] + # ex: '27 Feb 2008 23:20:21.0425'...cut off the last 2 digits since datetime doesnt support that resolution + timestr = timestr[:-2] + dt = datetime.strptime(timestr, '%d %b %Y %H:%M:%S.%f') + + self.time = dt + except Exception: + import traceback;traceback.print_exc() + log.error('Error parsing time: %r', newtime) + + log.debug('\t\tMessage successfully parsed') + return self.msg + + def _parse_mobile_oim(self): + self.name = self.email = (self._msgobj.get('From', self.name)).strip('<>') + + def parse_time(self, timestr): + yr, mo, da, hr, mi, se, ms, tz = self.time_re.search(timestr).groups() + + args = map(int, (yr,mo,da,hr,mi,se,ms)) + args[-1] = args[-1] * 1000 + + if not tz: + log.warning_s('no time zone for %r', self) + + return datetime(*args) + + def __repr__(self): + return '' % (self.name, self.email, self.time, + ': %r'%(self.msg) if self.msg else '') + + def __cmp__(self, other): + try: + return cmp((self.time, self.runid, self.seqnum), (other.time, other.runid, other.seqnum)) + except Exception: + return -1 + + +class OIMExceptions: + AuthFailed = 'AuthenticationFailed' + +class OfflineSBAdapter(EventMixin): + events = EventMixin.events | set (( + 'on_buddy_join', + 'on_buddy_leave', + 'on_buddy_timeout', + 'on_conn_success', + 'on_authenticate', + 'disconnect', + 'contact_alias', + 'needs_auth', + 'recv_error', + 'recv_text_msg', + 'send_text_msg', + 'typing_info', + 'recv_action', + + 'recv_p2p_msg', + 'transport_error', + )) + + POST_URL = "https://ows.messenger.msn.com/OimWS/oim.asmx" + #SOAP_ACT = "http://messenger.msn.com/ws/2004/09/oim/Store" + SOAP_ACT = 'http://messenger.live.com/ws/2006/09/oim/Store2' + OIM_NS = ('oim',"http://messenger.msn.com/ws/2004/09/oim/") + WSRM_NS = ('wsrm',"http://schemas.xmlsoap.org/ws/2003/03/rm") + WSUTIL_NS = ('wsutil',"http://schemas.xmlsoap.org/ws/2002/07/utility") + + # Don't switch this to util.net.user_agent() + USER_AGENT= 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger (BETA) 8.0.0328)' + CLIENT_STR = '8.1.0178' + + def __init__(self, client, buddy): + EventMixin.__init__(self) + + self.lockkey = '' + self.buildver = 'Digsby %r' % sys.REVISION + self.run_id = str(uuid.uuid4()).upper() + self.msgnum = 1 + self.client = client + self.buddy = buddy + self.version = self.client.version + self._closed = False + self._connected = False + + @property + def appid(self): + return self.client.appid + + @property + def appcode(self): + return self.client.appcode + + def get_token(self): + #return self.client.get_token('messenger.msn.com') + return self.client.get_token('messengersecure.live.com') + + def set_token(self, newtoken): + #return self.client.set_token('messenger.msn.com', newtoken) + return self.client.set_token('messengersecure.live.com', newtoken) + + token = property(get_token, set_token) + + @property + def self_buddy(self): + return self.client.self_buddy + + def invite(self, name): + self.on_buddy_join(name) + + @event + def on_buddy_join(self, name): + "the buddy named 'name' has joined" + + def connected(self): + return self._connected + + @util.callbacks.callsback + def connect(self, callback): + log.info('Connecting OfflineMessageSender') + log.info('OfflineSBAdapter "connected"') + self.event('on_conn_success', self) + self._connected = True + callback.success() + + @util.callbacks.callsback + def send_message(self, fmsg, callback=None): + text = fmsg.format_as('plaintext') + + log.info('OfflineSBAdapter send_message: %r', text) + + env = soap(self.OIM_NS[1]) + env.Header += tag('From', + memberName=self.self_buddy.name, + #friendlyName=make_header(self.self_buddy.remote_alias), + proxy='MSNMSGR', + msnpVer=self.version, + buildVer=self.CLIENT_STR) + env.Header += tag('To',memberName=self.buddy.name) + env.Header += tag('Ticket', + passport=self.token.encode('xml'), + appid=self.appid, + lockkey = self.lockkey, + ) + env.Header += (tag((self.WSRM_NS, 'Sequence')) + (tag((self.WSUTIL_NS, 'Identifier'), 'http://messenger.msn.com'), + tag('MessageNumber',self.msgnum)) + ) + + env.Body += tag('MessageType','text') + env.Body += tag('Content',self._build_message(text)) + + self.event('send_text_msg', text) + + def post_success(result): + log.info('Post result: %r', result._to_xml(pretty=False)) + fault = result._findOne("Fault") + if fault: + if (OIMExceptions.AuthFailed in fault.faultcode._cdata.strip()): + # try authentication again... + self.authenticate(fault, + success=lambda: self.send_message(fmsg, callback=callback), + error =lambda e,*a,**k: (callback.error(e), log.info('Error from authenticate: %r, %r', a,k)) + ) + else: + log.info('Sending message failed: %r', result._to_xml(pretty=False)) + callback.error(result) + elif result.Header.SequenceAcknowledgment: + log.info('Got SequenceAcknowledgment') + self.msgnum += 1 + callback.success() + else: + log.info('Unknown response from posting OIM: %r', result._to_xml(pretty=False)) + + def post_error(exception): + log.info('Post exception: %r, %r, %r', type(exception), (exception._to_xml(pretty=False) if hasattr(exception, '_to_xml') else ''), vars(exception)) + callback.error(exception) + + self.post(env, success=post_success, error=post_error) + + @util.callbacks.callsback + def authenticate(self, fault, callback=None): + lockcode = fault.detail.LockKeyChallenge._cdata.strip() + twnchal = fault.detail.TweenerChallenge._cdata.strip() + if not (lockcode or twnchal): + #assert lockcode or twnchal, (lockcode, twnchal, t._to_xml()) + callback.error(fault) + return + + log.info('OIM LockKey=%r, TweenerChallenge=%r', lockcode, twnchal) + + if twnchal: + self.token = '' + if lockcode: + self.lockkey = '' + + # Don't do this 'til we have both lockkey and tweener ticket + success = util.CallCounter(2, callback.success) + + if lockcode: + log.info('Making lockkey from LockKeyChallenge') + self.lockkey = self.client.ns._challenge_response(lockcode, self.appcode) + success() + #env.Header.Ticket['lockkey'] = self.lockkey + else: + # knock the callcounter down one anyway + success() + + if twnchal: + log.info('Requesting tweener authentication with TweenerChallenge') + + def set_ticket(tck): + log.info('Got tweener ticket. Setting it on protocol and calling success()') + self.token = tck.decode('xml') + success() + + import mail.passport + mail.passport.do_tweener_auth_3(self.client.username, self.client.password, + (twnchal,), success = set_ticket, error=callback.error) + else: + # knock the callcounter down one anyway. this will definitely call callback.success if we get here. + success() + + @util.callbacks.callsback + def post(self, env, callback=None): + post_xml(self.POST_URL, env, + callback=callback, + Accept='*/*', + SOAPAction=self.SOAP_ACT, + ContentType='text/xml; charset=utf-8', + **{'User-Agent':self.USER_AGENT}) + + def _build_message(self, msg): + return '\r\n'.join([ + 'MIME-Version: 1.0', + 'Content-Type: text/plain; charset=UTF-8', + 'Content-Transfer-Encoding: base64', + 'X-OIM-Message-Type: OfflineMessage', + 'X-OIM-Run-Id: {%s}' % self.run_id, + 'X-OIM-Sequence-Num: %d' % self.msgnum, + '', + msg.encode('utf-8').encode('base64'), + ]) + + def leave(self): + self._closed = True + self._connected = False + +# ------------------------------------------------------------------------------ diff --git a/digsby/src/msn/p/MSNPNotification.py b/digsby/src/msn/p/MSNPNotification.py new file mode 100644 index 0000000..f4eea73 --- /dev/null +++ b/digsby/src/msn/p/MSNPNotification.py @@ -0,0 +1,370 @@ +from logging import getLogger + +import util +from util.callbacks import callsback +from util.primitives.funcs import isiterable + +import msn +from msn.MSNCommands import CommandProcessor, Message + +from util.Events import event, EventMixin + +log = getLogger('msn.p.ns') + +defcb = dict(trid=True, callback=sentinel) + +class MSNNotification(CommandProcessor, EventMixin): + ''' + An MSNNotification object's purpose is to implement handlers for the various message + types and to implement the interface specifed below. There should be one subclass of + this class for each variation of the protocol. This implementation holds the basics + of connection management and stubs for the previously mentioned interface. + ''' + cid_class = str + events = EventMixin.events | set(( + 'on_conn_error', + 'on_connect', + 'on_require_auth', + 'on_receive_version', + 'on_auth_challenge', + 'disconnect', + ) + ) + + client_name = "MSNMSGR" + client_software_version = "15.4.3502.0922" + client_branding = "MSNMSGR" + + versions = \ + [ + 'MSNP21', + #'MSNP15', + #'MSNP14', + #'MSNP13', + #'MSNP12', + #'MSNP11', + #'MSNP10', + #'MSNP9', + #'MSNP8', + ] + + props = \ + {'phh':'phone_home', + 'phw':'phone_work', + 'phm':'phone_mobile', + 'mob':'allow_mobile', + 'mbe':'enable_mobile', + 'utl':'unknown', + 'wwe':'enable_wireless', + 'wpl':'unknown', + 'wpc':'unknown', + 'mfn':'remote_alias', + 'cid':'unknown', + 'hsb':'has_blog',} + + def __init__(self, SckCls_or_sck, server=None, buddy=None): + ''' + @param client: Client instance to report to + @type client: L{msn.Client} (or similar) + @param server: Server address (host,port) + @type server: tuple (string,int) + + @param SckCls_or_sck: Type of socket or instance of socket to use for transport + @type SckCls_or_sck: Class or instance of socket to use + ''' + + CommandProcessor.__init__(self, log) + EventMixin.__init__(self) + + self._server = server + self._sb_class = None + self._last_bname = None + + self.self_buddy = buddy + self.allow_unknown_contacts = False + + if type(SckCls_or_sck) is type: + self.socket = None + self._socktype = SckCls_or_sck + else: + self.socket = SckCls_or_sck + self._socktype = type(self.socket) + self._server = self.socket._server + self.socket.bind('on_message', self.on_message) + self.socket.bind('on_conn_error', self.on_sck_conn_error) + self.socket.bind('on_close', self.on_sck_close) + + import mail.passport + + self._authorizers = { + 'default' : mail.passport.do_tweener_auth_4, + 'SSO' : mail.passport.do_tweener_auth_4, + 'TWN' : mail.passport.do_tweener_auth_3, + } + + def connect(self): + ''' + Connect the protocol's transport layer + ''' + if self.socket is None: + log.info('Creating new %r to connect to %r', self._socktype, self._server) + self.socket = self._socktype() + self.socket.bind('on_connect', self.on_sck_connect) + self.socket.bind('on_conn_error', self.on_sck_conn_error) + self.socket.bind('on_close', self.on_sck_close) + + conn_args = self.socket.connect_args_for('NS', self._server) + self.socket._connect(*conn_args) + else: + #self.event('on_require_auth') + pass + + def get_local_sockname(self): + return self.socket.get_local_sockname() + + def get_token(self, domain): + return self.tokens[domain].Token + + def set_token(self, domain, token): + self.tokens[domain].Token = token + + def connected(self): + return self.socket is not None + + def disconnect(self, do_event = True): + if self.socket is not None: + log.info('%r disconnecting socket: %r', self, self.socket) + sck, self.socket = self.socket, None + sck._disconnect() + if do_event: + self.event('disconnect') + + self.CONNECTED = False + + def close_transport(self, xport, switching = False): + log.info('Closing transport %r (self.socket is %r)', xport, self.socket) + if self.socket is xport: + self.disconnect(do_event = not switching) + + else: + xport.clear() + xport.close() + + def get_authorizer(self, auth_type): + return self._authorizers.get(auth_type, self._authorizers['default']) + + def on_sck_close(self, sck): + if self.socket not in (None, sck): + log.info('An old socket got disconnected (%r)', sck) + sck.clear() + return + + if self.socket is None: + log.info('Disconnecting normally %r: %r', self, sck) + self.event('disconnect') + else: + log.info('Disconnecting unexpectedly %r: %r', self, sck) + self.socket.clear() + self.disconnect(do_event = False) + self.event('on_conn_error', self, 'disconnected unexpectedly') + + def on_sck_conn_error(self, sck, e): + if self.socket is not None: + self.socket.unbind('on_conn_error', self.on_sck_conn_error) + self.event('on_conn_error', self, e) + + def on_sck_connect(self, s): + + old_s = self.socket + self.socket = s + + if old_s not in (self.socket, None): + self.close_transport(old_s) + + self.socket.unbind('on_connect', self.on_sck_connect) + self.socket.bind('on_message', self.on_message) + + from common import pref + user_vers = pref('msn.versions', None) + + if user_vers is not None and isiterable(user_vers) and len(user_vers) > 0: + self.versions = user_vers + + self.init_ns_connection(self.versions) + + def init_ns_connection(self, versions): + self.socket.pause() + self.send_ver(versions) + self.event('on_require_auth') + + def unhook_socket(self): + sck, self.socket = self.socket, None + if sck is not None: + sck.unbind('on_conn_error', self.on_sck_conn_error) + sck.unbind('on_message', self.on_message) + sck.unbind('on_close', self.on_sck_close) + return sck + + def send_ver(self, versions): + """ + send_ver(socket) + + Send our supported versions to socket + """ + self.socket.send(Message('VER', *(versions+['CVR0'])), **defcb) + + def recv_ver(self, msg): + ver = msg.args[0] + num = int(ver[4:]) + + self.event('on_receive_version', num) + + def build_cvr(self, username): + msg = Message('cvr', '0x0409', 'winnt', '6.1.0', 'i386', + getattr(self, 'client_name', 'WLMSGRBETA'), + getattr(self, 'client_software_version', '9.0.1407'), + getattr(self, 'client_branding', 'msmsgs'), + + username) + + log.info("setting up versions: %r", self.versions) + if self.versions and self.versions[0] == 'MSNP21': + xfrcount = getattr(self, '_xfrcount', 0) + if xfrcount == 0: + msg.args.append('0') + else: + msg.args.append(('Version: 1\r\nXfrCount: %s\r\n' % self._xfrcount).encode('b64')) + self._xfrcount = xfrcount + 1 + + return msg + + + def send_cvr(self, username): + self.socket.send(self.build_cvr(username), **defcb) + +# self.socket.send(Message('cvr', '0x0409', 'winnt', '5.1', 'i386', +# 'MSNMSGR', '8.5.1302', 'MSMSGS', username), +# **defcb) + + def recv_cvr(self, msg): + log.info('got cvr') + + def send_usr(self, username, password, auth_type): + + self._username = username + self._password = password + self._auth_type = auth_type + + self.socket.send(Message('usr', auth_type, 'I', username), + trid = True, + success =self._recv_usr_success, + error =self._recv_usr_error,) + + def _recv_usr_error(self, sck, e): + log.info('Got an error when waiting for USR response: %r', e) + self.event('on_conn_error', self, e) + + def _recv_usr_success(self, sck, msg): + log.info('Got a success when waiting for USR response: %r', msg) + return True + + def recv_usr(self, msg): + auth_type = msg[0] + type_ = msg[1] + + if auth_type == 'OK': + self.event('on_auth_success') + return + + if type_ == 'S': + self.event('on_auth_challenge', auth_type, msg[2:]) + else: + assert False, (self, msg) + + def recv_xfr(self, msg): + type_, new_addr = msg.args[:2] + + server = util.srv_str_to_tuple(new_addr, 1863) + + if type_ == 'NS': + log.info('Switching NS servers to %r', server) + self.close_transport(self.socket, switching = True) + self._server = server + self.connect() + else: + assert type_ == 'SB', msg + cookie = msg.args[3] + + self.switchboard_request(server, cookie) + + if self.versions and self.versions[0] == 'MSNP21': + try: + self._xfrcount = int(email.message_from_string(msg.args[-1].decode('base64')).get('XfrCount')) + except Exception: + pass + + def recv_xfr_error(self, msg): + self.sb_request_error(msg) + + def authenticate(self, username, password, auth_type): + self.send_cvr(username) + self.send_usr(username, password, auth_type) + self.socket.unpause() + + def send_png(self): + return NotImplemented + + def needs_login_timer(self): + return True + + def _load_contact_list(self): + return NotImplemented + def _add_buddy(self, bname, bid, gname, callback): + return NotImplemented + def _add_buddy_to_group(self, bname, bid, gid, callback): + return NotImplemented + def _remove_buddy(self, lid, buddy, group, callback): + return NotImplemented + def _remove_buddy_from_group(self, name, bid, g_id, callback): + return NotImplemented + def _authorize_buddy(self, buddy, authorize, callback): + return NotImplemented + def _block_buddy(self, buddy, callback): + return NotImplemented + def _unblock_buddy(self, buddy, callback): + return NotImplemented + def _move_buddy(self, bname, bid, to_groupid, from_groupid, callback): + return NotImplemented + def _set_display_name(self, new_alias, callback): + return NotImplemented + def _set_remote_alias(self, buddy, new_alias, callback): + return NotImplemented + def _get_profile(self, buddy, callback): + return NotImplemented + def _rename_group(self, group, name, callback): + return NotImplemented + def _set_buddy_icon(self, icon, callback): + return NotImplemented + def _get_buddy_icon(self, name, callback): + return NotImplemented + def _send_file(self, buddy, filepath, callback): + return NotImplemented + def _send_sms(self, phone, message, callback): + return NotImplemented + def _set_status(self, code, callback): + return NotImplemented + def _set_status_message(self, message, callback): + return NotImplemented + + def init_p2p(self): + import msn.P2P as P2P + import msn.P2P.P2PHandler + import msn.P2P.SDGBridge + self.P2PHandler = P2P.P2PHandler.P2PHandler(self) + self.SDGBridge = P2P.SDGBridge.SDGBridge(self) + + # Registers P2P application types + import msn.P2P.P2PApplication + import msn.P2P.P2PActivity + import msn.P2P.P2PObjectTransfer + import msn.P2P.P2PFileTransfer diff --git a/digsby/src/msn/p/MSNPSwitchboard.py b/digsby/src/msn/p/MSNPSwitchboard.py new file mode 100644 index 0000000..4ffa041 --- /dev/null +++ b/digsby/src/msn/p/MSNPSwitchboard.py @@ -0,0 +1,98 @@ +import util.Events as Events + +from msn import CommandProcessor + +import logging +log = logging.getLogger('msn.p.sb') + + +class MSNSwitchboard(Events.EventMixin, CommandProcessor): + + events = Events.EventMixin.events | set (( + 'on_buddy_join', + 'on_buddy_leave', + 'on_buddy_timeout', + 'on_conn_success', + 'on_authenticate', + 'disconnect', + 'contact_alias', + 'needs_auth', + 'recv_error', + 'recv_action', + 'transport_error', + )) + + def __init__(self, SckCls_or_sck, to_invite=(), + server=('',0),cookie=None, sessionid=None): + + Events.EventMixin.__init__(self) + CommandProcessor.__init__(self, log) + + self._cookie = cookie + self._session = sessionid + + self.has_connected = False + + self.principals = [] + + if type(SckCls_or_sck) is type: + self.socket = None + self._socktype = SckCls_or_sck + self._server = server + else: + self.socket = SckCls_or_sck + self._socktype = type(self.socket) + self._server = self.socket.getpeername() + + self._p2p_transport = None + + self.bind('on_conn_success', lambda this: setattr(this, 'has_connected', True)) + + @property + def _closed(self): + return self.has_connected and ((not self.socket) or self.socket._closed) + + @property + def self_buddy(self): + return NotImplemented + + def invite(self, bname): + #cal + return NotImplemented + + def send_text_message(self, message, callback): + #msg + return NotImplemented + + def on_send_message(self, msg): + return NotImplemented + + def leave(self): + #out + return NotImplemented + + def send(self, msg): + # For transport. NOT for protocol + return NotImplemented + + def connect(self): + return NotImplemented + def disconnect(self): + return NotImplemented + def close_transport(self): + return NotImplemented + def on_conn_fail(self): + return NotImplemented + def on_conn_success(self): + return NotImplemented + def on_complete_auth(self): + return NotImplemented + def close_connection(self): + return NotImplemented + + + def on_error(self, msg): + CommandProcessor.on_error(self, msg) + self.event('recv_error', msg) + + diff --git a/digsby/src/msn/p/__init__.py b/digsby/src/msn/p/__init__.py new file mode 100644 index 0000000..3ea4790 --- /dev/null +++ b/digsby/src/msn/p/__init__.py @@ -0,0 +1,2 @@ +from MSNPSwitchboard import MSNSwitchboard as Switchboard +from MSNPNotification import MSNNotification as Notification diff --git a/digsby/src/msn/p10/MSNP10Notification.py b/digsby/src/msn/p10/MSNP10Notification.py new file mode 100644 index 0000000..210def3 --- /dev/null +++ b/digsby/src/msn/p10/MSNP10Notification.py @@ -0,0 +1,297 @@ +import logging +from uuid import UUID + +from util import callsback +from util.primitives.funcs import get + +import msn +from msn import Message +from msn.p9 import Notification as Super + +log = logging.getLogger('msn.p10.ns') +defcb = dict(trid=True, callback=sentinel) + + +class GroupId(UUID): + ''' + GroupIds in MSNP10 are GUIDs + ''' + def __init__(self, id, *a, **k): + + if isinstance(id, UUID): + id = str(id) + + try: + UUID.__init__(self, id, *a, **k) + except Exception, e: + print 'error initializing contact id: ', a, k + raise e + + def __repr__(self): + return '' % UUID.__repr__(self) + + def __eq__(self, other): + return str(other) == str(self) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(str(self)) + +class ContactId(UUID): + def __init__(self, *a, **k): + try: + UUID.__init__(self, *a, **k) + except Exception, e: + print 'error initializing contact id: ', a, k + raise e + + def __repr__(self): + return '' % UUID.__repr__(self) + + def __eq__(self, other): + return str(other) == str(self) + + def __ne__(self, other): + return not self.__eq__(other) + +def _rtrim_utf8url_bytes(s, maxlen): + encode = lambda s: msn.util.url_encode(s.encode('utf-8')) + + bytes = encode(s) + while len(bytes) > maxlen: + s = s[:-1] + bytes = encode(s) + + return bytes + +class MSNP10Notification(Super): + ''' + MSNP10 introduces GUIDs/CLSIDs as identifiers for buddies and groups. + + A side effect of this is that the ADD command is no longer allowed, + being replaced by ADC. REA is also no longer allowed, in favor of + PRP instead. + ''' + cid_class = ContactId + events = Super.events | set( + ('contact_id_recv', + ) + ) + + versions = ['MSNP10'] + + MAX_MFN_LENGTH = 387 + + def _set_display_name(self, new_alias, callback): + self.send_prp('MFN', _rtrim_utf8url_bytes(new_alias, self.MAX_MFN_LENGTH), callback) + + def _set_remote_alias(self, buddy, new_alias, callback): +# assert isinstance(new_alias, unicode) + self.send_sbp(buddy.guid, 'MFN', _rtrim_utf8url_bytes(new_alias, self.MAX_MFN_LENGTH), callback) + + def recv_syn(self, msg): + __, __, num_buddies, num_groups = msg.args + self.event('contact_list_details', int(num_buddies), int(num_groups)) + + def recv_lsg(self, msg): + log.debug('got lsg: %r', msg) + # same arguments, different order + if msg.trid: + msg.args = [str(msg.trid),] + msg.args + msg.trid = 0 + + try: + name, group_id = msg.args[:2] + except ValueError, e: + if msg.args: + try: + group_id = GroupId(msg.args[0]) + except Exception, e2: + name = msg.args[0] + import uuid + group_id = str(uuid.uuid4()) + else: + name = '' + else: + return + + self.event('group_receive', name.decode('url').decode('fuzzy utf8'), GroupId(group_id)) + + def recv_lst(self, msg): + args = list(msg.args) + + try: + list_flags = int(args[-1]) + groups = [] + args.pop() + except ValueError: + groups = map(GroupId, args[-1].split(',')) + list_flags = int(args[-2]) + args.pop() + args.pop() + + # The next 15 lines used to be 1: "info = dict(arg.split('=', 1) for arg in args)" + # But apparently the server can send crappy data that includes spaces where they don't belong??? + split_args = [] + for arg in args: + split = arg.split('=', 1) + if len(split) != 2: + split_args[-1][-1] += (' ' + split[0]) + else: + split_args.append(split) + + try: + info = dict(split_args) + except ValueError: + # ugh forget it. this message is screwed + log.error("This data is so screwed up, maybe you can do something with it: %r", msg) + return + + name, nick, guid = (info.get(k,'') for k in 'NFC') + nick = nick.decode('url').decode('fuzzy utf8') + + self._last_bname = name + + self.event('recv_contact', name, list_flags, groups, None, guid) + self.event('contact_alias', name, nick) + + if guid: + self.event('contact_id_recv', name, ContactId(guid)) + + def recv_adc(self, msg): + ''' + ADC 16 FL N=passport@hotmail.com F=Display%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n + ADC 13 AL N=passport@hotmail.com F=Display%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n + ADC 19 FL C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n + ''' + l_id = msg.args[0] + info = list(msg.args[1:]) + + if l_id == 'FL': + if '=' not in info[-1]: + g_id = GroupId(info.pop()) + else: + g_id = None + else: + g_id = None + + has_equals = lambda i: '=' in i + d = dict(arg.split('=',1) for arg in info if has_equals(arg)) + name, nick, guid = (d.get(k,'') for k in list('NFC')) + + nick = nick.decode('url').decode('fuzzy utf8') + list_flags = dict(FL=1, AL=2, BL=4, RL=8, PL=16)[l_id] + + if guid: + guid = ContactId(guid) + id = guid + else: + id = name + + self.event('on_contact_add', name, id, list_flags, [g_id]) + + if name and guid: + self.event('contact_id_recv', name, guid) + + if name: + self.event('contact_alias', name, nick) + + def recv_rem(self, msg): + l_id = msg.args[0] + try: + c_id = ContactId(msg.args[1]) + except ValueError: + c_id = msg.args[1] # buddy name! + + g_id = get(msg.args, 2, None) + + if g_id is not None: + g_id = GroupId(g_id) + + self.event('contact_remove', c_id, l_id, g_id) + + def recv_adg(self, msg): + log.debug('got adg') + name, g_id = msg.args + + name = name.decode('url').decode('fuzzy utf8') + g_id = GroupId(g_id) + + self.event('group_add',name, g_id) + + def _remove_group(self, groupid, callback=None): + self.send_rmg(groupid, callback=callback) + + def recv_rmg(self, msg): + log.debug('got rmg') + + g_id, = msg.args + g_id = GroupId(g_id) + + self.event('group_remove', g_id) + + def recv_reg(self, msg): + log.debug('got reg') + g_id, name = msg.args + g_id = GroupId(g_id) + name = name.decode('url').decode('fuzzy utf8') + + self.event('group_rename', g_id, name) + + def recv_rea(self, msg): + raise msn.WrongVersionException + + def recv_add(self, msg): + raise msn.WrongVersionException + + def send_syn(self): + log.debug('sending syn') + self.socket.send(Message('SYN', '0','0'), **defcb) + + @callsback + def send_adc(self, l_id, bname, bid, g_id='', callback=None): + bname = getattr(bname, 'name', bname) + binfo = 'N=%s' % bname + if l_id == 'FL': + binfo += ' F=%s' % bname + + if g_id and bid: + binfo = 'C=%s' % bid + + adc_message = Message('ADC',l_id, binfo, g_id or '') + adc_message.retries = 5 + adc_message.timeout = 4 + self.socket.send(adc_message, trid=True, callback=callback) + + send_add = send_adc + + @callsback + def send_rem(self, l_id, bname, bid, g_id='', callback = None): + + if l_id == 'FL': + id = bid + else: + id = bname + + if g_id is None: + g_id = '' + + if not (id or g_id): + log.info('Didnt get an id or gid. returning from send_rem. traceback to follow...') + import traceback;traceback.print_stack() + callback.success() + return + + self.socket.send(Message('REM', l_id, id, g_id), trid=True, callback=callback) + + def send_prp(self, prp_type, new_val, callback): + self.socket.send(Message('PRP', prp_type, new_val), trid=True, callback=callback) + + def send_sbp(self, b_guid, prp_type, new_val, callback): + self.socket.send(Message('SBP', b_guid, prp_type, new_val), trid=True, callback=callback) + + def send_rea(self, *args, **kwargs): + raise msn.WrongVersionException + diff --git a/digsby/src/msn/p10/MSNP10Switchboard.py b/digsby/src/msn/p10/MSNP10Switchboard.py new file mode 100644 index 0000000..a7c133e --- /dev/null +++ b/digsby/src/msn/p10/MSNP10Switchboard.py @@ -0,0 +1,5 @@ + +from msn.p9 import Switchboard as Super + +class MSNP10Switchboard(Super): + pass \ No newline at end of file diff --git a/digsby/src/msn/p10/__init__.py b/digsby/src/msn/p10/__init__.py new file mode 100644 index 0000000..9f2d343 --- /dev/null +++ b/digsby/src/msn/p10/__init__.py @@ -0,0 +1,2 @@ +from MSNP10Switchboard import MSNP10Switchboard as Switchboard +from MSNP10Notification import MSNP10Notification as Notification diff --git a/digsby/src/msn/p11/MSNP11Notification.py b/digsby/src/msn/p11/MSNP11Notification.py new file mode 100644 index 0000000..90d11b7 --- /dev/null +++ b/digsby/src/msn/p11/MSNP11Notification.py @@ -0,0 +1,326 @@ +import logging +import hashlib +import rfc822 +import struct +import string +import re + +import util +import util.primitives.funcs as funcs +import util.xml_tag +import msn + +from util.Events import event + +from msn.p10 import Notification as Super + + +log = logging.getLogger('msn.p11.ns') +defcb = dict(trid=True, callback=sentinel) + +MAX_SINT = 0x7fffffff +psmurl_re = re.compile(r'\(.*?)\', re.IGNORECASE) +format_re = re.compile(r'%\((.*?)\)s') + +def psm_url_fix(datatag_str): + match = psmurl_re.search(datatag_str) + + if match: + url = match.group(1) + start, end = match.span() + newmsg = datatag_str[:start] + datatag_str[end:] + else: + url = '' + newmsg = datatag_str + + return newmsg, url + +def transform_format_groups(fmtstring): + ''' + Takes a python formatting string and returns a .NET style formatting string, and a list of keys. + + >>> fmt = '%(title)s - %(artist)s - %(title)s [stopped]' + >>> transform_format_groups(fmt) == ('{0} - {1} - {0} [stopped]', ['title', 'artist']) + True + ''' + fixed, keys = [], [] + last_end = 0 + match = format_re.search(fmtstring, last_end) + while match: + key = match.group(1) + if key not in keys: + keys.append(key) + fixed.append(fmtstring[last_end:match.start()]) + fixed.append('{%d}' % keys.index(key)) + last_end = match.end() + match = format_re.search(fmtstring, last_end) + + fixed.append(fmtstring[last_end:]) + + return ''.join(fixed), keys + + +def format_mediainfo(info): + ''' + * Application - This is the app you are using. Usually empty (iTunes and Winamp are the only ones known to be accepted) + * Type - This is the type of PSM, either 'Music', 'Games' or 'Office' + * Enabled - This is a boolean value (0/1) to enable/disable the Current Media setting + * Format - A formatter string (you may be familiar with this syntax if you've used .NET); for example, "{0} - {1}" + * First line - The first line (Matches {0} in the Format) + * Second line - The second line (Matches {1} in the Format) + * Third line - The third line (Matches {2} in the Format) + ''' + + return repr(info) + +class MSNP11Notification(Super): + + versions = ['MSNP11'] + client_chl_id = challenge_id = "PROD0090YUAUV{2B" + client_chl_code = "YMM8C_H7KCQ2S_KL" + + events = Super.events | set ( + ('contact_status_msg', + 'received_oims', + ) + ) + + def __init__(self, *a, **k): + self.oims = [] + Super.__init__(self, *a, **k) + + def recv_sbs (self, msg): + """ + SBS (???) + + There is talk that this has something to do with 'mobile credits'. + See U{http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#SBS} + """ + log.warning("Got SBS command") + + def recv_ubx(self, msmsg): + ''' + UBX mike.dougherty@gmail.com 87\r\n + this is status messagehttp://bugs.digsby.com/?act=view&id=121\r\n + + UBX name@host 222\r\n + Xbox 360: Kameo (axiom1985)http://live.xbox.com/profile/profile.aspx?GamerTag=axiom1985&appid=messenger66262\r\n + ''' + bname, = msmsg.args + msg = msmsg.payload + log.info('Got UBX for %s: %r', bname, str(msg)) + + # Take PSMUrl tag out and snag the URL from it if it exists. + # Seems like sometimes the URL is not XML escaped and so it breaks the parser for util.xml_tag.tag + + if not msg: + msg = '' + + msg, url = psm_url_fix(msg) + + try: + msg = util.xml_tag.tag(msg) + except Exception: + import traceback;traceback.print_exc() + + msg = util.xml_tag.tag('') + + status_message = '' + now_playing = '' + + if msg.PSM: + status_message = msg.PSM._cdata.decode('xml') + log.info('%r has status message of: %r', bname, status_message) + if msg.CurrentMedia: + media_str = msg.CurrentMedia._cdata.decode('xml') + media = media_str.split('\\0') + unused_application, type, enabled, format = [media.pop(0) for i in range(4)] + + for i in range(len(media)): + format = format.replace('{%d}'%i, media[i]) + + if int(enabled): + + if type.lower() == 'music': + try: + from nowplaying.nowplaying import NOTES_SYMBOL + type = NOTES_SYMBOL + except: + type = unichr(9835) + + else: + type = type+':' + + now_playing = u"%s %s" % (type, format) + log.info('%r has NowPlaying status of: %r', bname, now_playing) + + if status_message and now_playing: + status_message = u'%s\n%s' % (status_message, now_playing) + else: + status_message = status_message or now_playing + + self.event('contact_status_msg', bname, status_message) + + return msg + + def recv_chl(self, msg): + log.debug('got chl') + self.event('challenge', msg.args[0]) + + def recv_uux(self, msg): + unused_message = msg.payload + + def send_uux(self, message = None, mediainfo = None, url = None, callback=None): + mtag = util.xml_tag.tag('Data') + if message is not None: + mtag.PSM._cdata = message + else: + mtag.PSM._cdata = '' + + if mediainfo is not None: + mtag.CurrentMedia._cdata = mediainfo + else: + mtag.CurrentMedia._cdata = '' + + if url is not None: + mtag.PSMUrl._cdata = url + else: + mtag.PSMUrl._cdata = '' + + message = mtag._to_xml(pretty=False).encode('utf-8') + self.socket.send(msn.Message('UUX', payload=message), trid=True, callback=callback) + + def _set_status_message(self, *a, **k): + return self.send_uux(*a, **k) + + def set_message_object(self, messageobj, callback): + media = getattr(messageobj, 'media', None) + log.debug('set_message_object got this for (messageobj, media): %r', (messageobj, media)) + if media is not None: + # + # fmt is + # + # %(title)s - %(artist)s + # or maybe just + # %(title)s + # + # args is a dictionary for splatting into fmt + # + + fmt, args = funcs.get(media,'format_string',''), funcs.get(media, 'format_args', {}) + + if fmt and args: + + fmtstring, keys = transform_format_groups(fmt) + values = [args[key] for key in keys] + + application = media.get('app', '') + type = media.get('type', 'Music') + enabled = '1' + # ITunes\\0Music\\01\\0{0} - {1}\\0Crownless\\0Nightwish\\0Wishmaster\\0 + array = '\\0'.join([application, type, enabled, fmtstring] + values + ['']) + + ''' + * Application - This is the app you are using. Usually empty (iTunes and Winamp are the only ones known to be accepted) + * Type - This is the type of PSM, either 'Music', 'Games' or 'Office' + * Enabled - This is a boolean value (0/1) to enable/disable the Current Media setting + * Format - A formatter string (you may be familiar with this syntax if you've used .NET); for example, "{0} - {1}" + * First line - The first line (Matches {0} in the Format) + * Second line - The second line (Matches {1} in the Format) + * Third line - The third line (Matches {2} in the Format) + ''' + + self.send_uux(mediainfo = array, callback = callback) + else: + log.debug('msn not sending CurrentMedia because no fmt or args. (fmt=%r, args=%r)', fmt, args) + self.send_uux(message = messageobj.message, callback=callback) + + else: + log.debug('msn not sending CurrentMedia because media is None') + self.send_uux(messageobj.message, callback = callback) + + def recv_msg_notification(self, msg): + #name, passport = msg.args + if msg.name == 'Hotmail': + MD = self.extract_oim_info(msg) + self.oims = msn.oim.OIMMessages(self, MD) + else: + log.warning('unknown msg/notification') + + def extract_oim_info(self, oim_info_msg): + msg_obj = rfc822.Message(oim_info_msg.payload.body()) + maildata = msg_obj['Mail-Data'] + if 'too-large' in maildata: + MD = None + else: + MD = util.xml_tag.tag(maildata) + +# unread = int(str(MD.E.IU)) +# others = int(str(MD.E.OU)) + + return MD + + def recv_msg_oims(self, msg): + if msg.name == 'Hotmail': + MD = self.extract_oim_info(msg) + self.oims += msn.oim.OIMMessages(self, MD) + + + + @event + def received_oims(self, oims): + return oims + + def _challenge_response(self, chl_str, challenge_key, mystery_num=0x0E79A9C1): + ''' + the crazyness below was created from the pseudocode at: + U{http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges} + + horrible, horrible math, bitshifting, and other suchness + to stay connected to MSN + ''' + + hash = hashlib.md5(chl_str + challenge_key).digest() + + hash_ints = struct.unpack("" + f, struct.pack("<" + f, i))[0] + + hi = byteswap((hi + hash_ints[1]) % MAX_SINT, 'L') + lo = byteswap((lo + hash_ints[3]) % MAX_SINT, 'L') + key = byteswap((hi << 32L) + lo, 'Q') + + ls = [byteswap(abs(byteswap(x,'Q') ^ key), 'Q') + for x in struct.unpack(">QQ", hash)] + + return ''.join(('%x' % x).zfill(16).lower() for x in ls) + + +def __test(): + import doctest + doctest.testmod(verbose=True) + +if __name__ == '__main__': + __test() diff --git a/digsby/src/msn/p11/MSNP11Switchboard.py b/digsby/src/msn/p11/MSNP11Switchboard.py new file mode 100644 index 0000000..4cbfafd --- /dev/null +++ b/digsby/src/msn/p11/MSNP11Switchboard.py @@ -0,0 +1,5 @@ + +from msn.p10 import Switchboard as Super + +class MSNP11Switchboard(Super): + pass \ No newline at end of file diff --git a/digsby/src/msn/p11/__init__.py b/digsby/src/msn/p11/__init__.py new file mode 100644 index 0000000..cb80a60 --- /dev/null +++ b/digsby/src/msn/p11/__init__.py @@ -0,0 +1,2 @@ +from MSNP11Switchboard import MSNP11Switchboard as Switchboard +from MSNP11Notification import MSNP11Notification as Notification diff --git a/digsby/src/msn/p12/MSNP12Notification.py b/digsby/src/msn/p12/MSNP12Notification.py new file mode 100644 index 0000000..2339464 --- /dev/null +++ b/digsby/src/msn/p12/MSNP12Notification.py @@ -0,0 +1,24 @@ +from msn.p11 import Notification as Super +class MSNP12Notification(Super): + versions = ['MSNP12'] + def recv_lst(self, msg): + ''' + MSNP12's LST command is the same as MSNP11's except there is an + additional mystery flag for all buddies. Its value is always 1 and + does not appear to mean anything. + ''' + args = list(msg.args) + if args[-1].find('-') != -1: + # pop groups off + groups = args.pop() + mystery_flag = args.pop() + + # now put the groups back for our super()'s function + args.append(groups) + else: + mystery_flag = args.pop() + + msg.args = list(args) + + return Super.recv_lst(self, msg) + diff --git a/digsby/src/msn/p12/MSNP12Switchboard.py b/digsby/src/msn/p12/MSNP12Switchboard.py new file mode 100644 index 0000000..99246e7 --- /dev/null +++ b/digsby/src/msn/p12/MSNP12Switchboard.py @@ -0,0 +1,7 @@ + +from msn.p11 import Switchboard as Super + +class MSNP12Switchboard(Super): + def recv_joi(self, msg): + msg.args = msg.args[:2] + Super.recv_joi(self, msg) diff --git a/digsby/src/msn/p12/__init__.py b/digsby/src/msn/p12/__init__.py new file mode 100644 index 0000000..9e6c039 --- /dev/null +++ b/digsby/src/msn/p12/__init__.py @@ -0,0 +1,2 @@ +from MSNP12Switchboard import MSNP12Switchboard as Switchboard +from MSNP12Notification import MSNP12Notification as Notification diff --git a/digsby/src/msn/p13/MSNP13Notification.py b/digsby/src/msn/p13/MSNP13Notification.py new file mode 100644 index 0000000..cb1f441 --- /dev/null +++ b/digsby/src/msn/p13/MSNP13Notification.py @@ -0,0 +1,1453 @@ +from logging import getLogger +log = getLogger('msn.p13.ns') +import sys +import datetime +import urlparse +import functools +import uuid + +import util +import util.callbacks as callbacks +import util.xml_tag as xml_tag +import util.cacheable as cacheable +import lxml.etree as ET +import lxml.builder as B +from util.Events import event +from util.primitives.error_handling import try_this +from util.primitives.funcs import get, isint +from util.primitives.mapping import Storage + + +from hub import Hub; hub = Hub.getInstance() +import ZSI + +import msn +from msn.p12 import Notification as Super +from msn import Message +#from msn.SOAP import MSNABSharingService as MSNAB +MSNAB = Null + +from mail.passport import escape, unescape + +from msn.p10.MSNP10Notification import GroupId, ContactId + +from ZSI import FaultException + +defcb = dict(trid=True, callback=sentinel) + +def get_fault(exc): + return try_this(lambda: exc.fault.detail[0], None) + +def zsiparse(soap, locatorname, name, raw): + loc = getattr(soap, '%sLocator' % locatorname)() + port = getattr(loc, 'get%sPort' % name)() + binding = port.binding + + binding.reply_headers = Storage(type='text/xml') + binding.data = raw + return binding.Receive(getattr(soap,'%sResponseMessage' % name).typecode) + +def soapcall(soap, locatorname, Locator='%sLocator', getPort='get%sPort', + Message='%sMessage', Result='%sResult', Success='%sSuccess', Error='%sError', + _name=None, cache=False): + def wrapper(f): + def wrapper2(*a, **k): + log.error("%s.%s(%s, %s) was called", locatorname, f.func_name, + ', '.join('%r'%x for x in a), + ', '.join('%s=%r'%x for x in k.items())) + raise NotImplementedError + return wrapper2 + return wrapper +# locator = getattr(soap, Locator % locatorname)() +# +# def wrapper(f): +# @functools.wraps(f) +# def wrapper2(self, *a, **k): +# +# name = f.__name__ if _name is None else _name +# log.debug('%s.%s(%s %s)', locatorname, name, ', '.join(map(str,a+('',))), +# ', '.join('%s=%s' %i for i in k.items())) +# port = getattr(locator, getPort % name)(tracefile = sys.stdout) +# +# url = port.binding.url +# domain = urlparse.urlparse(url)[1] +# +# auth_header = soap.AddressBook.ABAuthHeader_Dec().pyclass() +# auth_header.ManagedGroupRequest = False +# auth_header.TicketToken = self.tokens[domain].Token +# +# app_header = soap.AddressBook.ABApplicationHeader_Dec().pyclass() +# app_header.ApplicationId = str(self.app_id) +# app_header.IsMigration = False +# app_header.PartnerScenario = self.partner_scenario +# +# request = getattr(soap, Message % name)() +# +# e = None +# try: +# do_request = f(self, request, *a, **k) +# except Exception, e: +# do_request = False +# +# if not do_request: +# _v = 'BadRequest', request, a, k, e +# if e: +# import traceback +# traceback.print_stack() +# traceback.print_exc() +# +# log.debug('%s.%s failed: %r', locatorname, name, e) +# return +# +# try: +# response = getattr(port, name)(request, soapheaders=(app_header, auth_header)) +# except FaultException, e: +# log.debug('Error calling %s.%s: %r', locatorname, name, e) +# +# if 'response' not in locals(): +# response = None +# try: +# err_handler = getattr(self, Error % name) +# # self may not have this attribute - in which case there is no error handler. so raise it again +# except AttributeError, __: +# raise e +# return err_handler(request, response, e, *a, **k) +# +# result = getattr(response, Result % name, None) +# +# should_cache = getattr(self, Success % name, lambda *a, **k:None)(request, result, *a, **k) +# if cache and should_cache: +# self._cache_soap(name, response) +# +# return result +# return wrapper2 +# return wrapper + +class MSNP13Notification(Super): + versions = ['MSNP13'] + client_chl_id = challenge_id = "PROD01065C%ZFN6F" + client_chl_code = "O4BG@C7BWLYQX?5G" + app_id = uuid.UUID('CFE80F9D-180F-4399-82AB-413F33A1FA11') + + # This is the old UUID, and it seems to be invalid now. ('09607671-1C32-421F-A6A6-CBFAA51AB5F4') + + events = Super.events | set( + ('contact_role_info', + 'contact_cid_recv', + 'contact_profile_update', + 'soap_info', + 'contact_btype', + 'buddy_authed', + 'needs_status_message', + ) + ) + + def __init__(self, *a, **k): + Super.__init__(self, *a, **k) + self.members = {} + self.partner_scenario = 'Initial' + + self.CONNECTED = False + self.abId = uuid.UUID(int=0) + + + @property + def cache_path(self): + + import os, os.path + val = os.path.join('msn', self.self_buddy.name, 'soapcache.dat') + return val + + _soapcache = cacheable.cproperty({}) + + @callbacks.callsback + def _add_buddy_to_list(self, bname, callback=None): + + try: + self.ABContactAdd(bname) + except FaultException, e: + print e.fault.detail + if get_fault(e) == 'InvalidPassportUser': + log.error('%s is not a valid passport user', bname) + self.ABContactAdd(bname, type='federated') + + return True + + self.AddMember(bname, 'Allow') + + return True + + @callbacks.callsback + def _add_buddy_to_group(self, bname, bid, gid, callback=None): + if bname != self.self_buddy.name: + self.ABGroupContactAdd(bname, bid, gid) + return True + + @callbacks.callsback + def _remove_buddy_from_group(self, name, bid, g_id, callback=None): + self.ABGroupContactDelete(name, bid, g_id) + return True + + @callbacks.callsback + def _remove_buddy(self, lid, buddy, group, callback=None): + + role = dict(F='Forward',P='Pending', A='Allow',R='Reverse',B='Block')[lid[0]] + + if group and role in ('Forward',): + log.info('Removing %r (%r) from %r (%r)', buddy, buddy.id, group, getattr(group, 'id',group)) + self.ABGroupContactDelete(buddy.name, buddy.id, get(group, 'id',group)) + #return True + +# else: + + + if buddy.contactsoap is not None: + self.ABContactDelete(buddy) + else: + log.info('Not calling ABContactDelete because no contact soap was received for %r', buddy) + + if role in buddy.mships: + self.DeleteMember(buddy, role) + else: + log.info('Not calling DeleteMember because %s not in %r\'s member roles', role, buddy) + + callback.success() + + @callbacks.callsback + def _add_buddy(self, lid, buddy, bid, gid, callback=None): + + lid = dict(AL='Allow', + FL='Forward', + BL='Block', + RL='Reverse', + PL='Pending').get(lid, lid) + + try: + self.AddMember(getattr(buddy, 'name', buddy), lid) + except FaultException, e: + if get_fault(e) == 'InvalidPassportUser': + try: + self.AddMember(getattr(buddy, 'name', buddy), lid, member_type='Email') + except FaultException, e2: + print 'fault:',vars(e2.fault) + raise e2 + + + return True + + + @callbacks.callsback + def _block_buddy(self, buddy, callback=None): + if 'Allow' in buddy.mships: + self.DeleteMember(buddy, 'Allow') + + if 'Block' not in buddy.mships: + self._add_buddy('Block', buddy, buddy.id, None, callback=callback) +# try: +# self.AddMember(buddy.name, 'Block') +# except FaultException, e: +# print 'fault', e.fault.detail +# if e.fault.detail[0] == 'InvalidPassportUser': +# err_str = '%s is not a valid passport user, trying to add as federated', buddy.name +# log.error(errstr) +# hub.on_error(e) +# +# try: +# self.AddMember(buddy.name, 'Block', member_type='Email') +# except FaultException, e: +# if e.fault.detail[0] =='MemberAlreadyExists': +# log.info('%r already on %s list', buddy, 'Block') + +# callback.success() + + @callbacks.callsback + def _unblock_buddy(self, buddy, callback=None): + if 'Block' in buddy.mships: + self.DeleteMember(buddy, 'Block') + + if 'Allow' not in buddy.mships: + try: + self.AddMember(buddy.name, 'Allow') + except FaultException, e: + print e.fault.detail + if get_fault(e) == 'InvalidPassportUser': + log.error('%s is not a valid passport user', buddy.name) + + try: + self.AddMember(buddy.name, 'Allow', member_type='Email') + except FaultException, e: + if get_fault(e) =='MemberAlreadyExists': + log.info('%r already on %s list', buddy, 'Allow') + + + return True + + @callbacks.callsback + def _authorize_buddy(self, buddy, authorize, callback=None): + + if authorize: + self._unblock_buddy(buddy, callback=callback) + bname = buddy.name + try: + self.ABContactAdd(bname) + except FaultException, e: + print e.fault.detail + if get_fault(e) == 'InvalidPassportUser': + log.error('%s is not a valid passport user', bname) + self.ABContactAdd(bname, type='federated') + + else: + self._block_buddy(buddy, callback=callback) + + try: + if 'Pending' in buddy.mships: + self.DeleteMember(buddy, 'Pending') + except: + import traceback;traceback.print_exc() + raise + else: + self.event('buddy_authed', buddy, authorize) + + def _add_group(self, groupname, callback): + val = self.ABGroupAdd(groupname) + callback.success() + + def _remove_group(self, groupid, callback=None): + self.ABGroupDelete(groupid) + if callback: + callback.success() + return True + + def _rename_group(self, group, newname, callback): + self.ABGroupUpdate(group, newname) + if callback: + callback.success() + return True + + def _get_profile(self, buddy, callback): + request = xml_tag.tag('GetXmlFeed') + + ri = request.refreshInformation + + ri.cid = buddy.CID + ri.storageAuthCache = '' + ri.market = '%s-%s' % (hub.language.lower(), hub.country.upper()) + ri.brand = '' + ri.maxElementCount = 15 + ri.maxCharacterCount = 200 + ri.maxImageCount = 6 + ri.applicationId = 'Messenger Client 8.0' + ri.updateAccessedTime = False + + yesterday = datetime.datetime.today() - datetime.timedelta(1) + ri.spaceLastViewed = yesterday.isoformat() + ri.profileLastViewed = yesterday.isoformat() + ri.contactProfileLastViewed = yesterday.isoformat() + + ri.isActiveContact = False + + fs = ri.foreignStore + fs.itemType = 'Profile' + fs.foreignId = 'MyProfile' + fs.lastChanged = yesterday.isoformat() + fs.lastViewed = yesterday.isoformat() + + auth = xml_tag.tag('AuthTokenHeader') + #auth.Token = self.ticket.encode('xml') + token = str(self.tokens['spaces.live.com'].received.RequestedSecurityToken.BinarySecurityToken) + assert token, token.received._to_xml() + + auth.Token = escape(token) + auth.AuthPolicy = self.tokens['spaces.live.com'].policyref + + env = xml_tag.tag("envelope") #soap_envelope("http://www.msn.com/webservices/spaces/v1/") + + env.Header(auth) + env.Body(request) + #del env._children[0] + print env._to_xml(pretty=False) + response = xml_tag.post_xml("http://cid-%X.cc.services.spaces.live.com/contactcard/contactcardservice.asmx" % int(self.self_buddy.CID), + env, + success=lambda t: self.incoming_profile(t, buddy), + error =lambda e: self.incoming_profile(e, buddy, True) + #Cookie=token + ) + + def incoming_profile(self, t, buddy, error=False): + if error: + print 'Bad profile response for buddy profile', buddy, repr(t) + raise t + #print t._to_xml() + buddy.update_contact_card(t.Body.GetXmlFeedResponse.GetXmlFeedResult.contactCard) + + def recv_not(self, msg): + data = xml_tag.tag(xml_tag.tag(msg.payload).MSG.BODY._cdata) + + if not data.OwnerCID: + return + + cid = int(data.OwnerCID) + #last_mod = str(data.LastModifiedDate) + has_new = bool(data.HasNewItem) + + if has_new: + self.event('contact_profile_update', cid) + + def recv_gcf(self, msg): + ''' + hooray for XML verbosity telling us nothing is blocked + ''' + log.debug('got gcf') + + def adl(self, msg): + log.debug('got adl: %r', (msg.args,msg.payload)) + + if not msg.trid: # from the server + + self._sync_memberships(False) +# ml = tag(msg.payload) +# +# contacts = [] +# for d in ml: +# if d._name != 'd': continue +# +# for c in d: +# if c._name != 'c': continue +# +# bname = '%s@%s' % (c['n'], d['n']) +# +# self.event('on_contact_add', bname, None, int(c['l']), None) +# self.event('on_contact_alias', bname, msn.util.url_decode(c['f'])) +# #self.apply_list_flags(int(c['l']), buddy) + + def recv_rml(self, msg): + + log.info('RML: %s', msg) + if msg.args: + msg, = msg.args + if msg != 'OK': + raise msn.GeneralException('Unknown RML error') + else: + print 'UNKNOWN RML MSG:',msg + + def recv_uun(self, msg): + name, __ = msg.args + raise NotImplementedError + + def recv_ubn(self, msg): + name, __ = msg.args + raise NotImplementedError + + def recv_cvq(self, msg): + cur_ver, max_ver, min_ver, dl_link, homepage = msg.args + raise NotImplementedError + +# def xfr(self, *a, **k): +# #log.warning('Got MSNP13 XFR, which may not be fully implemented!') +# return Super.xfr(self, *a, **k) + + def recv_rng(self, msg): + log.info('Got a RNG from %s', msg.args[3]) + msg.args = msg.args[:5] + return Super.recv_rng(self, msg) + + def recv_adc(self, *a, **k): + raise msn.WrongVersionException + +# def send_adc(self, *a, **k): +# raise msn.WrongVersionException + + def recv_rem(self, *a, **k): + raise msn.WrongVersionException + +# def send_rem(self, *a, **k): +# raise msn.WrongVersionException + + def recv_syn(self, *a, **k): + #raise msn.WrongVersionException + pass + + def recv_uux(self, msg): + # confirmation that our message was set right + pass + + def recv_gtc(self, *a, **k): + raise msn.WrongVersionException + + def usr_ok(self, msg): + + username, verified = msg.args[:2] + log.debug('got usr_ok') + assert username == self.self_buddy.name + #self.self_buddy.remote_alias = username + self.self_buddy.verified = bool(verified) + + self._get_connected() + + def send_adl(self, ml): + log.debug('sending adl') + self.socket.send(Message('ADL', payload=ml._to_xml(pretty=False).strip()), **defcb) + + def recv_adl(self, msg): + ''' + ADL 0 86\r\n + + + ADL 19 60\r\n + + ''' + 'ADL 0 81\r\n\r\n' + + t = xml_tag.tag(str(msg.payload)) + + for d in t._children: + domain = d['n'] + + for c in d._children: + username = c['n'] + type = int(c['t']) + l_id = int(c['l']) + mfn = c['f'].decode('url').decode('fuzzy utf8') + + name = '%s@%s' % (username,domain) + + if not msg.trid: + self._sync_memberships(False) +# self.event('recv_contact', name, l_id, None, None) +# self.event('contact_alias', name, mfn) + + if not self.CONNECTED: + log.info('got ADL, changing to connected') + self.CONNECTED = True + #self.event('on_connect') + + + def send_rml(self, buddy, l_id): + + if buddy._btype == 'mob': + return + + n = buddy.name + + u, d = n.split('@') + ml = xml_tag.tag('ml') + ml.d['n']=d + ml.d.c['n']=u + ml.d.c['l']=dict(forward=1, allow=2, block=4, reverse=8, pending=16)[l_id.lower()] + ml.d.c['t']=dict(im=1, msn=1, mob=4, fed=32)[buddy._btype] + + self.socket.send(Message('RML', payload=ml._to_xml(pretty=False).strip()), **defcb) + + def send_cvq(self, *a, **k): + raise NotImplementedError + + def send_uux(self, msg='', callback=sentinel): + data = xml_tag.tag('Data') + data.PSM = msg + data.CurrentMedia = '' + data.MachineGuid = uuid.uuid1() + + self.socket.send(Message('UUX', payload=data._to_xml(pretty=False).encode('utf-8')), trid=True, callback=callback) + + def send_gtc(self, *a, **k): + raise msn.WrongVersionException + + def process_groups(self, groups): + #util.tag_view(groups) + for g in groups: + gid = GroupId(str(g.GroupId)) + gname = str(g.GroupInfo.Name) + + self.event('group_receive', gname, gid) + + def process_contacts(self, contacts): + + for c in contacts: + name, alias, guid, cid, groups, bprops = self.process_contact(c) + if not name: continue + + self.event('recv_contact', name, ['FL'], groups, c, guid) + self.event('contact_id_recv', name, guid) + + if alias: + self.event('contact_alias', name, alias) + + if cid: + self.event('contact_cid_recv', name, cid) + + for prop, val in bprops: + self.event('recv_prop', name, prop, val) + + def process_contact(self, c): + guid = ContactId(str(c.ContactId)) + i = c.ContactInfo + if str(i.ContactType) == 'Me': + for annotation in i.Annotations.Annotation: + _name = str(annotation.Name) + try: + _val = int(annotation.Value) + except ValueError: + _val = str(annotation.Value) + + if _name == 'MSN.IM.MBEA': + self.event('recv_prop', self.self_buddy.name, 'mbe', bool(_val)) + elif _name == 'MSN.IM.GTC': + self.event('on_rl_notify', bool(_val)) + elif _name == 'MSN.IM.BLP': + self.allow_unknown_contacts = bool(_val) + self.event('on_blist_privacy', bool(_val)) + elif _name == 'MSN.IM.RoamLiveProperties': + pass + + name = i.PassportName + + if name is None and i.Emails is not None: + es = i.Emails.ContactEmail + if es: + for e in es: + if e.ContactEmailType == 'Messenger2': + name = 'fed:'+e.Email + + alias = i.DisplayName + if alias: + alias = alias.decode('url').decode('fuzzy utf8') + + cid = i.CID + + groups = [GroupId(gid) for gid in i.GroupIds.Guid] if i.GroupIds else [] + + bprops = [] + bprops.append(('mob', i.IsNotMobileVisible)) + bprops.append(('mbe', i.IsMobileIMEnabled)) + bprops.append(('hsb', i.HasSpace)) + + return name, alias, guid, cid, groups, bprops + + @soapcall(MSNAB, 'ABService', cache=False) + def ABFindAll(self, request, deltas=False, lastChange='0001-01-01T00:00:00.0000000-08:00'): + request.set_element_abId(request.new_abId(self.abId)) + request.AbView = 'Full' + #request.DeltasOnly = True + #request.LastChange = datetime.datetime(2007, 11, 12, 19, 9, 54, 63).timetuple() + request.DeltasOnly = deltas + request.LastChange = lastChange + + return True + + def ABFindAllError(self, request, result, exc, deltas=False, lastChange='0001-01-01T00:00:00.0000000-08:00'): + if get_fault(exc) == 'ABDoesNotExist': +# self.abId = uuid.UUID(str(exc.fault.detail[1]).split('=')[-1].strip()) +# +# # now that we have the right abId, we should be able to complete this successfully. +# return self.ABFindAll(deltas=deltas, lastChange=lastChange) + self.ABAdd() + self.ABFindAll() + else: + raise exc + + def ABFindAllSuccess(self, request, result, deltas=False, lastChange='0001-01-01T00:00:00.0000000-08:00'): + + should_cache = True + + if deltas and (result.Groups or result.Contacts): + # We used our cache timestamp but there were changes! we need to request the whole thing again =( + log.info('ABFindAllResult is out of date, getting new version') + should_cache = False # this result should not be cached. + self.ABFindAll() # but this one will be + elif deltas: + # we used our cache timestamp and there were no changes (yay). Use the cached copy. + log.info('ABFindAllResult was empty, using cached version') + cached_data = self._soapcache.get('ABFindAll') + old_response = zsiparse(MSNAB, 'ABService', 'ABFindAll', cached_data) + result = old_response.ABFindAllResult + # don't cache the (empty) response we just got. + should_cache = False + else: + # we didnt ask for the cached version. result is the right result + + log.info('Got new ABFindAllResult') + pass + + self.process_blist(result) + return should_cache + + @soapcall(MSNAB, 'ABService') + def ABAdd(self, request, default=True): + info = request.AbInfo = request.new_abInfo() + info.Name = '' + info.OwnerPuid = 0 + info.OwnerEmail = self.self_buddy.name + info.FDefault = default + + return True + + def ABAddSuccess(self, request, result): + self.abid = uuid.UUID(result) + + @soapcall(MSNAB, 'ABService') + def ABContactAdd(self, request, buddy_or_name, type='LivePending'): + ''' + + 00000000-0000-0000-0000-000000000000 + + + + LivePending + XXX@YYY.com + true + + petit + + + + + + true + + + ''' + name = getattr(buddy_or_name, 'name', buddy_or_name) + + request.AbId = str(self.abId) + contacts = request.Contacts = request.new_contacts() + Contact = contacts.new_Contact(); contacts.Contact.append(Contact) + + info = Contact.ContactInfo = Contact.new_contactInfo() + + getattr(self, '_setup_info_%s' % type.lower())(info, name) + + options = request.Options = request.new_options() + options.EnableAllowListManagement = True + + return True + + def _setup_info_livepending(self, info, name): + info.ContactType = 'LivePending' + info.PassportName = name + + def _setup_info_federated(self, info, name): + ''' + + + Messenger2 + digsby04@yahoo.com + true + 32 + Email IsMessengerEnabled Capability + + + ''' + + changed = [] + + info.Emails = info.new_emails() + email = info.Emails.new_ContactEmail(); info.Emails.ContactEmail.append(email) + email.ContactEmailType = 'Messenger2' + + def set(attr, val): + changed.append(attr) + setattr(email, attr, val) + + set("IsMessengerEnabled", True) + set("Email", name) + set('Capability', 0x20) + + email.PropertiesChanged = ' '.join(changed) + + def ABContactAddSuccess(self, request, result, name, type='LivePending'): + if type == 'federated': + tagged_name = 'fed:'+name + else: + tagged_name = name + + self.event('on_contact_add', tagged_name, ContactId(result.Guid), ['FL'], []) + + def ABContactAddError(self, request, response, exc, name, type='LivePending'): + if get_fault(exc) == 'ContactAlreadyExists': + self.ABContactAddSuccess(request, response, name, type) + else: + raise exc + + @soapcall(MSNAB, 'ABService') + def ABContactDelete(self, request, buddy): + request.AbId = str(self.abId) + request.Contacts = request.new_contacts() + c = request.Contacts.new_Contact(); request.Contacts.Contact.append(c) + + c.ContactId = str(buddy.id) + + return True + + def ABContactDeleteSuccess(self, request, response, buddy): + self.event('contact_remove', buddy.id, 'FL', None) + self.send_rml(buddy, 'Forward') + + def ABContactDeleteError(self, request, response, exc, buddy): + if get_fault(exc) == 'ContactDoesNotExist': + self.ABContactDeleteSuccess(request, response, buddy) + else: + raise exc + + @soapcall(MSNAB, 'ABService') + def ABGroupContactAdd(self, request, bname, bid, groupid): + ''' + 00000000-0000-0000-0000-000000000000 + + + 62b9fd12-df18-4b39-837b-035eef22df29 + + + + + 239abf4f-64a8-4c7b-bc5d-78875e00798d + + + ''' + request.AbId = str(self.abId) + gfilter = request.GroupFilter = request.new_groupFilter() + gids = gfilter.GroupIds = gfilter.new_groupIds() + gids.Guid.append(gids.new_guid(str(groupid))) + + contacts = request.Contacts = request.new_contacts() + contact = contacts.new_Contact(); contacts.Contact.append(contact) + contact.ContactId = str(bid) + + return True + + def ABGroupContactAddSuccess(self, request, result, bname, bid, groupid): + + if not isinstance(groupid, GroupId): + groupid = GroupId(groupid) + + self.event('on_contact_add', bname, bid, ['FL'], [groupid]) + + @soapcall(MSNAB, 'ABService') + def ABGroupAdd(self, request, gname): + + request.AbId = str(self.abId) + options = request.GroupAddOptions = request.new_groupAddOptions() + options.FRenameOnMsgrConflict = False + + info_ = request.GroupInfo = request.new_groupInfo() + info = info_.GroupInfo = info_.new_GroupInfo() + + info.Name = gname + info.GroupType = "C8529CE2-6EAD-434d-881F-341E17DB3FF8" + info.FMessenger = False + + annots = info.Annotations = info.new_annotations() + annot = annots.new_Annotation(); annots.Annotation.append(annot) + annot.Name = 'MSN.IM.Display' + annot.Value = '1' + + return True + + def ABGroupAddError(self, request, result, exc, gname): + if get_fault(exc) == 'GroupAlreadyExists': + detail = exc.fault.detail + gid = None + try: + gid = detail[3]['conflictObjectId'] + except (AttributeError, KeyError): + log.info('Couldn\'t get conflict ID, here\'s the detail: %r', detail) + + if gid is not None: + return self.ABGroupAddSuccess(request, Storage(Guid=gid), gname) + else: + raise exc + else: + raise exc + + + ''' + + GroupAlreadyExists + Group Already Exists + BAYABCHWBB143 + + D06C117E-F8A8-4167-B693-5241CE0CAAF3 + + + ''' + + def ABGroupAddSuccess(self, request, result, gname): + gid = GroupId(result.Guid) + self.event('group_add', gname, gid) + + return gid + + @soapcall(MSNAB, 'ABService') + def ABGroupUpdate(self, request, gid, newname): + ''' + + 00000000-0000-0000-0000-000000000000 + + + + + 4e851eb6-4714-4cfd-9216-a5886d3b4201 + + + + bbbbbbbbbbbbb + + + + GroupName + + + + ''' + + request.AbId = str(self.abId) + + gs = request.Groups = request.new_groups() + g = gs.new_Group(); gs.Group.append(g) + g.GroupId = gid + gi = g.GroupInfo = g.new_groupInfo() + gi.Name = newname + g.PropertiesChanged = 'GroupName' + + return True + + def ABGroupUpdateSuccess(self, request, result, gid, newname): + self.event('group_rename', gid, newname) + + @soapcall(MSNAB, 'ABService') + def ABGroupDelete(self, request, gid): + ''' + 00000000-0000-0000-0000-000000000000 + + + 8fd9b0fc-7abf-4211-9122-bef3b8a326c1 + + + ''' + request.AbId = str(self.abId) + + gfilter = request.GroupFilter = request.new_groupFilter() + gids = gfilter.GroupIds = gfilter.new_groupIds() + gids.Guid.append(gids.new_guid(str(gid))) + + return True + + def ABGroupDeleteSuccess(self, request, result, gid): + self.event('group_remove', gid) + + def ABGroupDeleteError(self, request, result, exc, gid): + if get_fault(exc) == 'GroupDoesNotExist': + self.ABGroupDeleteSuccess(request, result, gid) + else: + raise exc + + @soapcall(MSNAB, 'ABService') + def ABGroupContactDelete(self, request, name, bid, groupid): + ''' + 00000000-0000-0000-0000-000000000000 + + + 239abf4f-64a8-4c7b-bc5d-78875e00798d + + + + + 4e851eb6-4714-4cfd-9216-a5886d3b4201 + + + ''' + + request.AbId = str(self.abId) + gfilter = request.GroupFilter = request.new_groupFilter() + gids = gfilter.GroupIds = gfilter.new_groupIds() + gids.Guid.append(gids.new_guid(str(groupid))) + + contacts = request.Contacts = request.new_contacts() + contact = contacts.new_Contact(); contacts.Contact.append(contact) + contact.ContactId = str(bid) + + return True + + def ABGroupContactDeleteSuccess(self, request, result, name, bid, groupid): + self.event('contact_remove', bid, 'FL', groupid) + + def ABGroupContactDeleteError(self, request, result, exc, name, bid, groupid): + if get_fault(exc) == 'ContactDoesNotExist': + self.ABGroupContactDeleteSuccess(request, result, name, bid, groupid) + else: + raise exc + + @soapcall(MSNAB, 'ABService') + def ABContactUpdate(self, request, contact): + request.AbId = str(self.abId) + contacts = request.Contacts = request.new_contacts() + + contacts.Contact.append(contact) + + return True + + def ABContactUpdateSuccess(self, request, result, contact): + log.info('Got ABContactUpdateSuccess') + + @soapcall(MSNAB, 'SharingService', cache=False) + def FindMembership(self, request, services=None, view='Full', deltas=False, lastchange='0001-01-01T00:00:00.0000000-08:00'): + + if services is None: + services = ('Messenger', + 'Invitation', + 'SocialNetwork', + 'Space', + 'Profile') + else: + if not isinstance(services, (tuple, list)): + services = (services,) + + request.View = view + request.DeltasOnly = deltas + request.LastChange = lastchange + request.ServiceFilter = filter = request.new_serviceFilter() + request.ServiceFilter.Types = types = filter.new_Types() + types.ServiceType.extend(services) + + return True + + def FindMembershipError(self, request, result, exc, services=None, view='Full', deltas=False, + lastchange='0001-01-01T00:00:00.0000000-08:00'): + + if get_fault(exc) == 'ABDoesNotExist': + self.abId = uuid.UUID(str(exc.fault.detail[1]).split('=')[-1].strip()) + + # now that we have the right abId, we should be able to complete this successfully. + #return self.FindMembership(services, view, deltas, lastchange) + elif get_fault(exc) == 'FullSyncRequired': + self.FindMembership(services, view='Full', deltas=False) + else: + raise exc + + + def FindMembershipSuccess(self, request, result, services=None, view='Full', deltas=False, lastchange='0001-01-01T00:00:00.0000000-08:00'): + + # cache result + #self._cache_soap('FindMembership', result) + + # this will be returned + should_cache = True + + + + if deltas and result is not None: + # We used our cache timestamp but there were changes! we need to request the whole thing again =( + log.info('FindMembershipResult is out of date, getting new version') + should_cache = False # this result should not be cached + self.FindMembership() # this one will be + elif deltas: + # we used our cache timestamp and there were no changes (yay). Use the cached copy. + log.info('FindMembershipResult was empty, using cached version') + cached_data = self._soapcache.get('FindMembership') + old_response = zsiparse(MSNAB, 'SharingService', 'FindMembership', cached_data) + result = old_response.FindMembershipResult + # don't cache the (empty) response we just got. + should_cache = False + else: + # we didnt ask for the cached version. result is the right result + log.info('Got new FindMembershipResult') + pass + + self.process_memberships(result) + + return should_cache + + @soapcall(MSNAB, 'SharingService') + def AddMember(self, request, name, role, state='Accepted', + handle_type='Messenger', member_type='Passport'): + + request.ServiceHandle = handle = request.new_serviceHandle() + handle.Id = 0 + handle.Type = handle_type + handle.ForeignId = '' + + memberships = request.Memberships = request.new_memberships() + membership = memberships.new_Membership() + memberships.Membership.append(membership) + membership.MemberRole = role + members = membership.Members = membership.new_Members() + + member = getattr(MSNAB, '%sMember' % member_type, MSNAB.BaseMember)() + member.Type = member_type + member.State = state + + ds = member.DefiningService = member.new_DefiningService() + ds.Id = 0 + ds.Type = handle_type + ds.ForeignId = '' + ''' + + + 0 + + + Messenger + + + + ''' + + name_name = MSNAB.member_names[type(member)] + setattr(member, name_name, name) + + if member_type == 'Email': + annots = member.Annotations = member.new_Annotations() + annot = annots.new_Annotation(); annots.Annotation.append(annot) + + annot.Name = 'MSN.IM.BuddyType' + annot.Value = '32:' + + members.Member.append(member) + + return True + + def AddMemberSuccess(self, request, result, name, role, state='Accepted', + handle_type='Messenger', member_type='Passport'): + assert result is None + + # the Membership and Member sequences should nearly always be of length 1 + + for mship in request.Memberships.Membership: + + names = [] + mrole = mship.MemberRole + + for member in mship.Members.Member: + + _name = getattr(member, MSNAB.member_names[member.Type], name) or name + + print _name, mrole + self.event('soap_info', _name, + member.Type, member, mrole) + + self.event('on_contact_add', _name, None, [mrole[0]+'L'], None) + names.append(_name) + + log.error('Going to send ADL (%s) for %r', mrole, names) + + def AddMemberError(self, request, result, exc, name, role, state='Accepted', + handle_type = 'Messenger', member_type='Passport'): + + if get_fault(exc) == 'MemberAlreadyExists': + return self.AddMemberSuccess(request, result, name, role, state, handle_type, member_type) + else: + raise exc + + @soapcall(MSNAB, 'SharingService') + def DeleteMember(self, request, buddy, role, handle_type='Messenger'): + + request.ServiceHandle = handle = request.new_serviceHandle() + handle.Id = 0 + handle.Type = handle_type + handle.ForeignId = '' + + memberships = request.Memberships = request.new_memberships() + membership = memberships.new_Membership() + memberships.Membership.append(membership) + members = membership.Members = membership.new_Members() + + membership.MemberRole = role + + if role not in buddy.mships: + log.error('%s not in buddy\'s roles: %r', role, buddy.mships.keys()) + return False + + old = buddy.mships[role] + + copy = type(old)() + + copy.MembershipId = old.MembershipId + copy.Type = old.Type + copy.State = old.State + + name_name = MSNAB.member_names[copy.Type] + + if not copy.MembershipId: + the_name = getattr(old, name_name, None) or \ + getattr(buddy.contactsoap.ContactInfo, name_name, None) + + if the_name: + setattr(copy, name_name, the_name) + else: + copy.CID = old.CID or buddy.contactsoap.ContactInfo.CID + + members.Member.append(copy) + + return True + + def DeleteMemberError(self, request, result, exc, buddy, role, handle_type="Messenger"): + if get_fault(exc) == 'MemberDoesNotExist': + buddy.mships.pop(role, None) + self.event('contact_remove', buddy.name, role[0]+'L', None) + self.send_rml(buddy, role) + else: + raise exc + + def DeleteMemberSuccess(self, request, result, buddy, role, handle_type="Messenger"): + assert result is None, result + +# if buddy._btype != 'msn': +# return + + # the Membership and Member sequences should nearly always be of length 1 + + try: + for mship in request.Memberships.Membership: + for member in mship.Members.Member: + name = getattr(member, MSNAB.member_names[member.Type], buddy.name) or buddy.name + role = mship.MemberRole + buddy.mships.pop(role, None) + self.event('contact_remove', name, role[0]+'L', None) + self.send_rml(buddy, role) + except Exception: + import traceback;traceback.print_exc() + + + def process_members(self, role, members): + + # List ids are AL, PL, BL, RL, FL. + # Roles are Allow, Block, Reverse, Pending. Allow means AL and FL + + #l = getattr(self, '%s_list' % role.lower()) +# util.tag_view(members) +# print members._to_xml() + for m in members.Member: + typ = m.Type + + mshipid = m.MembershipId + alias = (m.DisplayName or '').decode('url').decode('fuzzy utf8') + + cid = None + + if typ == 'Passport': + name = m.PassportName + cid = m.CID + btype = 0x01 + elif typ == 'Phone': + name = m.PhoneNumber + btype = 0x04 + elif typ == 'Email': + name = m.Email + btype = 0x20 + else: + log.error('Unknown member.Type: %s', m.Type) + btype = 1 + + self.event('contact_btype', name, btype) + self.event('soap_info', name, typ, m, role) + self.event('contact_alias', name, alias or name) + + if cid: + self.event('contact_cid_recv', name, cid) + + l_ids = set() + l_ids.add(role[0] + 'L') + + if role == 'Reverse' and str(m.State) != 'Accepted': + l_ids.add('PL') + + self.event('contact_role_info', name, l_ids, mshipid) + + def _set_persist_blp(self, bool): + + log.info('Setting persistent BLP to %d', int(bool)) + + s = self.self_buddy.contactsoap + + if s.ContactInfo is None: + s.ContactInfo = s.new_ContactInfo() + + if s.ContactInfo.Annotations is None: + s.ContactInfo.Annotations = s.ContactInfo.new_annotations() + + + annos = s.ContactInfo.Annotations + + for anno in annos.Annotation: + if anno.Name == 'MSN.IM.BLP': + break + else: + anno = annos.new_Annotation(); annos.Annotation.append(anno) + anno.Name = 'MSN.IM.BLP' + + anno.Value = str(int(bool)) + + s.PropertiesChanged = "Annotation" + self.ABContactUpdate(s) + + def _load_contact_list(self): + CACHE = True + self._sync_addressbook(CACHE) + ### + self._sync_memberships(CACHE) + + def _sync_addressbook(self, use_cache): + + # Load cache, get timestamp + + # Load cache, get timestamp + + kwargs = {} + if use_cache: + cached = self._soapcache.get('ABFindAll', None) + + log.debug('Cached ABFindAllResult: %r', cached) + + if cached is not None: + try: + oldresp = zsiparse(MSNAB, 'ABService', 'ABFindAll', cached) + except: + import traceback;traceback.print_exc() + else: + lastchange = oldresp.ABFindAllResult.Ab.LastChange + kwargs = dict(deltas=True, lastChange=lastchange) + + + try: + self.ABFindAll(**kwargs) + except ZSI.FaultException: + import traceback;traceback.print_exc() + if kwargs: + self.ABFindAll() + except Exception, e: + self.blist_error(e) + +# try: +# address_book = self.ABFindAll() +# except Exception, e: +# log.info('Error getting address book') +# print 'Error getting address book', repr(e) +# import traceback;traceback.print_exc() +# else: +# log.info('Got address book result') +# self.process_blist(address_book) +# log.info('Processed address book') + + def _sync_memberships(self, use_cache): + + # Load cache, get timestamp + + kwargs = {} + if use_cache: + cached = self._soapcache.get('FindMembership', None) + log.debug('Cached ABFindAllResult: %r', cached) + if cached is not None: + try: + oldresp = zsiparse(MSNAB, 'SharingService', 'FindMembership', cached) + except: + import traceback;traceback.print_exc() + else: + if oldresp.FindMembershipResult.Services is not None: + lastchange = max(x.LastChange for x in oldresp.FindMembershipResult.Services.Service) + kwargs = dict(deltas=True, lastchange=lastchange) + + try: + self.FindMembership(**kwargs) + except Exception, e: + self.blist_error(e) +# +# try: +# memberships = self.FindMembership() +# except Exception, e: +# log.info('Error getting memberships') +# else: +# log.info('Got memberships result') +# self.process_memberships(memberships) +# log.info('Processed memberships') + + + def blist_error(self, e): + #self.hub.on_error(e) + log.error('Error getting buddy list: %r, %r, %r', type(e), str(e), repr(e)) + import traceback;traceback.print_exc() + + if not self.CONNECTED: + self.event('on_conn_error', self, e) + + def process_blist(self, t): +# print repr(t._to_xml()) +# util.tag_view(t) + + log.info('Got ABFindAllResult') + + try: + groups = t.Groups.Group or [] + except AttributeError: + groups = [] + + try: + contacts = t.Contacts.Contact or [] + except AttributeError: + contacts = [] + + log.info('Got %d buddies and %d groups', int(len(contacts)), int(len(groups))) + self.event('contact_list_details', int(len(contacts)), int(len(groups))) + log.info('Adding groups') + self.process_groups(groups) + log.info('Adding contacts') + self.process_contacts(contacts) + + def process_memberships(self, result): + + if result.Services is not None: + services = dict((str(svc.Info.Handle.Type).strip(),svc) for svc in + result.Services.Service) + + try: + for mship in services['Messenger'].Memberships.Membership: + role = mship.MemberRole + members = mship.Members + self.process_members(role, members) + + except (AttributeError,KeyError), e: + log.warning('Error when trying to add memberships: %r', e) + + if not self.CONNECTED: + self.socket.send(Message('BLP', 'AL' if self.allow_unknown_contacts else 'BL'), **defcb) + + if not self.CONNECTED: + self.event('needs_status_message', self.send_uux) + + def send_fqy(self, n, flags): + 'FQY 10 51\r\n' + 'FQY 17 53\r\n' + u, d = n.split('@') + ml = xml_tag.tag('ml') + ml.d['n']=d + ml.d.c['n']=u + + if not isint(flags): + d = dict(f=1, a=2, b=4, r=8, p=16) + if isinstance(flags, basestring): + flags = [flags] + + flags = sum(d[f[0].lower()] for f in flags) + + if flags == 1: + flags = 2 + + ml['l']=flags + + xml = ml._to_xml(pretty=False) + self.socket.send(Message('FQY', payload=xml), trid=True, callback=sentinel) + + def send_blp(self, value): + assert value in ('AL', 'BL') + bool = 'AL' == value + self._set_persist_blp(bool) + + Super.send_blp(self, value) + + def _cache_soap(self, name, res): + from ZSI import SoapWriter + sw = SoapWriter() + xml = str(sw.serialize(res)) + self._soapcache[name] = xml + + # hack to get the cache to save + self._soapcache = self._soapcache + diff --git a/digsby/src/msn/p13/MSNP13Switchboard.py b/digsby/src/msn/p13/MSNP13Switchboard.py new file mode 100644 index 0000000..e372399 --- /dev/null +++ b/digsby/src/msn/p13/MSNP13Switchboard.py @@ -0,0 +1,10 @@ +from msn.p12 import Switchboard as Super +class MSNP13Switchboard(Super): +# def joi(self, socket, trid, name, nick, client_id): +# buddy = msn.p12.conversation.joi(self, socket, trid, name, nick) +# buddy.client_id = client_id +# +# def iro(self, socket, trid, r_num, r_count, name, nick, client_id): +# buddy = msn.p12.conversation.iro(self, socket, trid, name, nick) +# buddy.client_id = client_id + pass diff --git a/digsby/src/msn/p13/__init__.py b/digsby/src/msn/p13/__init__.py new file mode 100644 index 0000000..ee52b94 --- /dev/null +++ b/digsby/src/msn/p13/__init__.py @@ -0,0 +1,2 @@ +from MSNP13Switchboard import MSNP13Switchboard as Switchboard +from MSNP13Notification import MSNP13Notification as Notification diff --git a/digsby/src/msn/p14/MSNP14Notification.py b/digsby/src/msn/p14/MSNP14Notification.py new file mode 100644 index 0000000..1020c04 --- /dev/null +++ b/digsby/src/msn/p14/MSNP14Notification.py @@ -0,0 +1,79 @@ +from logging import getLogger +log = getLogger('msn.p14.ns') + +from util.primitives.funcs import get + +import msn +from msn.p13 import Notification as Super + + + +class MSNP14Notification(Super): + versions = ['MSNP14'] + client_chl_id = challenge_id = "PROD0112J1LW7%NB" + client_chl_code = "RH96F{PHI8PPX_TJ" + + events = Super.events | set(( + 'fed_message', + )) + + def recv_iln(self, msg): + 'ILN 8 NLN digsby09@hotmail.com 1 digsby09@hotmail.com 1073791028 0' + + log.debug('got iln') + + (status, name, btype, nick, client_id), __args = msg.args[:5], (msg.args[5:] or []) + + nick = nick.decode('url').decode('fuzzy utf8') or None + btype = int(btype) + client_id = self.parse_caps(client_id) + + iconinfo = msn.util.url_decode(get(__args, 0, '')) + + if '<' in iconinfo and '>' in iconinfo: + msnobj = msn.MSNObject.parse(iconinfo) + else: + msnobj = None + + self.event('contact_online_initial', name, nick, status, client_id) + self.event('contact_icon_info', name, msnobj) + + self.event('contact_btype', name, btype) + + def recv_nln(self, msg): + 'NLN IDL digsby09@hotmail.com 1 digsby09@hotmail.com 1073791028 0 \r\n' + log.debug('got nln') + + (status, name, btype, nick, client_id), __args = msg.args[:5], (msg.args[5:] or []) + + nick = nick.decode('url').decode('fuzzy utf8') or None + btype = int(btype) + client_id = self.parse_caps(client_id) + + iconinfo = msn.util.url_decode(get(__args, 0, '')) + + if '<' in iconinfo and '>' in iconinfo: + msnobj = msn.MSNObject.parse(iconinfo) + else: + msnobj = None + + self.event('contact_online', name, nick, status, client_id) + self.event('contact_icon_info', name, msnobj) + + self.event('contact_btype', name, btype) + +# def recv_fln(self, msg): +# pass + + def recv_ubx(self, msg): + bname, btype = msg.args + msg.args = [bname] + + self.event('contact_btype', bname, int(btype)) + + Super.recv_ubx(self, msg) + + def recv_ubm(self, msg): + name = msg.args[0] + self.event('fed_message', name, msg) + diff --git a/digsby/src/msn/p14/MSNP14Switchboard.py b/digsby/src/msn/p14/MSNP14Switchboard.py new file mode 100644 index 0000000..44d0bfe --- /dev/null +++ b/digsby/src/msn/p14/MSNP14Switchboard.py @@ -0,0 +1,6 @@ + +import msn +from msn.p13 import Switchboard as Super + +class MSNP14Switchboard(Super): + pass \ No newline at end of file diff --git a/digsby/src/msn/p14/__init__.py b/digsby/src/msn/p14/__init__.py new file mode 100644 index 0000000..c7d0da1 --- /dev/null +++ b/digsby/src/msn/p14/__init__.py @@ -0,0 +1,2 @@ +from MSNP14Switchboard import MSNP14Switchboard as Switchboard +from MSNP14Notification import MSNP14Notification as Notification diff --git a/digsby/src/msn/p15/MSNP15Notification.py b/digsby/src/msn/p15/MSNP15Notification.py new file mode 100644 index 0000000..50498bd --- /dev/null +++ b/digsby/src/msn/p15/MSNP15Notification.py @@ -0,0 +1,8 @@ + +from msn.p14 import Notification as Super + +class MSNP15Notification(Super): + versions = ['MSNP15'] + client_chl_id = challenge_id = 'PROD0119GSJUC$18' + client_chl_code = "ILTXC!4IXB5FB*PX" + #CHL_MAGIC_NUM = 0x0E79A9C1 diff --git a/digsby/src/msn/p15/MSNP15Switchboard.py b/digsby/src/msn/p15/MSNP15Switchboard.py new file mode 100644 index 0000000..a8620b6 --- /dev/null +++ b/digsby/src/msn/p15/MSNP15Switchboard.py @@ -0,0 +1,5 @@ + +from msn.p14 import Switchboard as Super + +class MSNP15Switchboard(Super): + pass \ No newline at end of file diff --git a/digsby/src/msn/p15/__init__.py b/digsby/src/msn/p15/__init__.py new file mode 100644 index 0000000..e01187a --- /dev/null +++ b/digsby/src/msn/p15/__init__.py @@ -0,0 +1,2 @@ +from MSNP15Switchboard import MSNP15Switchboard as Switchboard +from MSNP15Notification import MSNP15Notification as Notification diff --git a/digsby/src/msn/p21/MSNP21Conversation.py b/digsby/src/msn/p21/MSNP21Conversation.py new file mode 100644 index 0000000..aff468d --- /dev/null +++ b/digsby/src/msn/p21/MSNP21Conversation.py @@ -0,0 +1,499 @@ +import sys +import time +import datetime +import struct +import logging +import traceback +import email +import uuid + +import lxml.etree as etree + +import util.allow_once as once +import util.callbacks as callbacks +from util import callsback, RoundRobinProducer, strip_html, Timer +from util.primitives.funcs import get +import util.Events as Events + +import common +from util.observe import ObservableDict +from common import Conversation, pref + +import msn +import msn.AddressBook as MSNAB +import msn.SOAP.services as SOAPServices +from msn import NSSBAdapter, oim + +import msn.P2P as P2P +import msn.P2P.P2PMessage as P2PMessage + +import msn.MSNCommands as MSNC +from msn.MSNCommands import MSNTextMessage + +log = logging.getLogger('msn.p21.conv') + +class CircleNotReadyException(Exception): + pass + + +class MSNP21Conversation(Conversation, Events.EventMixin): + events = Events.EventMixin.events | set(( + 'buddy_join', + 'buddy_leave', + 'AllContactsLeft', + 'MessageAckReceived', + 'ServerErrorReceived', + )) + def _repr(self): + return ' chatbuddy=%r' % getattr(self, '_chatbuddy', None) + + def __init__(self, msn, to_invite = (), **k): + Events.EventMixin.__init__(self) + self.Bridge = None + self.client = msn + self._to_invite = set(to_invite) + self._closed = False + + self._waschat = len(self._to_invite) > 1 + Conversation.__init__(self, msn) + + self.check_invite_list() + + self.protocol.register_conv(self) + + log.info("Added %r to msn's conversation list", self) + + self.buddies = {} + self.typing_status = ObservableDict() + + self._pending_invite_callbacks = {} + + self.room_list.append(self.self_buddy) + if self.ischat: + cb = self.client.circle_buddies[self._chatbuddy] + cb.sem.bind("resource_release", self._circle_unused) + cb.sem.acquire() + self.client.ns.JoinCircleConversation(self._chatbuddy) + for bname in cb.buddy_names: + if bname.lower().startswith(str(cb.guid).lower()): + continue + self.buddy_join(bname) + + def check_invite_list(self): + to_invite = tuple(self._to_invite) + + if len(to_invite) >= 1: + self._chatbuddy = to_invite[0] + if len(to_invite) == 1: + self._chat_target_name = to_invite[0] + cinfo = self.protocol.get_contact_info(self._chat_target_name) + if cinfo is None: + self._chat_target_type = MSNAB.ClientType.ChatMember + else: + self._chat_target_type = cinfo.type + else: + self._chat_target_name = None + self._chat_target_type = MSNAB.ClientType.ChatMember + else: + self._chatbuddy = None + + @property + def chat_room_name(self): + if self.ischat: + return self.name + + return None + + def connect(self): + pass + + def connected(self): + return self.protocol.ns is not None and self.protocol.ns.connected() + + def Disconnect(self): + log.info('Disconnecting. unregistering %r from client (%r)', self, self.client) + if self.Bridge is not None: + self.event('AllContactsLeft') # Will cleanup bridge + self.Bridge = None + # cleanup bridge + self.client.unregister_conv(self) + + def exit(self, force_close = False): + log.info("%r exiting", self) + self._closed = True + + if self.ischat and self._chat_target_type == MSNAB.IMAddressInfoType.TemporaryGroup: + circle = self.protocol.circle_buddies[self._chatbuddy] + circle.sem.release() + self._chatbuddy = None + self._chat_target_name = None + Conversation.exit(self) + self.Disconnect() + + def _circle_unused(self): + log.info("Circle no longer in use. Leaving...") + circle = self.protocol.circle_buddies[self._chatbuddy] + circle.sem.unbind('resource_release', self._circle_unused) + self.protocol.ns.leave_temp_circle(circle.name) + + @property + def name(self): + if self.ischat: + return self.buddy.alias + + names = self._clean_list() + count = len(names) + aliases = [self.protocol.get_buddy(n).alias for n in names if n != self.self_buddy.name] + if count == 2: + who = aliases[0] + elif count == 3: + who = '%s and %s' % tuple(sorted(aliases)) + else: + who = '%d people' % (count - 1) + + return who + + @property + def chat_id(self): + if self._chatbuddy is None: + raise CircleNotReadyException() + return '%s:%s' % (int(self._chat_target_type), self._chatbuddy) + + @property + def ischat(self): + return getattr(self, '_chat_target_type', 1) in (MSNAB.IMAddressInfoType.Circle, MSNAB.IMAddressInfoType.TemporaryGroup) + + @property + def buddy(self): + if self.ischat: + return self.protocol.circle_buddies.get(self._chatbuddy) + + l = self._clean_list() + + try: + l.remove(self.self_buddy.name) + except ValueError: + pass + + if len(l) == 1: + answer = l[0] + if isinstance(answer, basestring): + answer = self.protocol.get_buddy(answer) + return answer + + return self.protocol.get_buddy(self._chatbuddy) + + def _clean_list(self): + l = set(x.name for x in self.room_list) | set(self._to_invite) + + circle = self.client.circle_buddies.get(self._chatbuddy, None) + if circle is not None: + l.update(circle.buddy_names) + l.discard(circle.name) + + return list(l) + + @property + def self_buddy(self): + return self.protocol.self_buddy + + @callbacks.callsback + def _send_message(self, text, callback = None, **k): + pass + + @callbacks.callsback + def invite(self, buddy, callback = None): + name = getattr(buddy, 'name', buddy) + self._pending_invite_callbacks[name] = callback + + is_new_circle = False + + def do_invites(circle_name): + circle = self.protocol.circle_buddies[circle_name] + + if is_new_circle: + circle.sem.bind("resource_release", self._circle_unused) + circle.sem.acquire() + + old_name, self._chatbuddy = self._chatbuddy, circle_name + self._chat_target_name = circle_name + self._chat_target_type = (MSNAB.IMAddressInfoType.Circle + if getattr(circle, 'circle', None) is not None + else MSNAB.IMAddressInfoType.TemporaryGroup) + + if old_name != self._chatbuddy: + self.protocol.ns.invite_to_circle(circle_name, old_name) + self.protocol.ns.invite_to_circle(circle_name, name) + + if not self.ischat: + is_new_circle = True + self.protocol.ns.make_temp_circle(success = do_invites, + error = callback.error) + + else: + do_invites(self._chatbuddy) + + def on_message_recv(self, name, msg, sms = False): + buddy = self.buddies[name] = self.protocol.get_buddy(name) + self.typing_status[buddy] = None + + if hasattr(msg, 'html'): + message = msg.html().replace('\n', '
') + content_type = 'text/html' + else: + message = msg + content_type = 'text/plain' + + did_receive = self.received_message(buddy, message, sms = sms, content_type = content_type) + + if name != self.self_buddy.name and did_receive: + Conversation.incoming_message(self) + + def on_action_recv(self, name, action_type, action_text): + self._stop_exit_timer() + + buddy = self.buddies[name] = self.protocol.get_buddy(name) + + if action_type == 'custom': + if action_text is not None: + #Translators: ex: Frank nudged you! + message = _('{name} {action}').format(name = buddy.alias, action = action_text) + self.system_message(message) + else: + text = dict( + wink = _('{name} winked at you!'), + nudge = _('{name} nudged you!'), + ).get(action_type, None) + if text is not None: + message = text.format(name = buddy.alias) + self.system_message(message) + + def on_typing_notification(self, name, typing): + buddy = self.buddies[name] = self.protocol.get_buddy(name) + self.typing_status[buddy] = 'typing' if typing else None + + log.info('%s is %styping', name, '' if typing else 'not ') + + @Events.event + def buddy_join(self, name): + buddy = self.buddies[name] = self.protocol.get_buddy(name) + + if buddy is not self.self_buddy and self.self_buddy not in self.room_list: + self.on_buddy_join(self.self_buddy.name) + + if buddy not in self.room_list: + self.room_list.append(buddy) + if not self._chatbuddy: + self._chatbuddy = name + + log.info('Got buddy join event (%s). self.ischat = %r', name, self.ischat) + + self.notify('ischat') + super(MSNP21Conversation, self).buddy_join(buddy) + + self.invite_success(name) + + return name + + def invite_success(self, name): + cb = self._pending_invite_callbacks.pop(name, None) + if cb is not None: + cb.success() + + def invite_failure(self, name): + cb = self._pending_invite_callbacks.pop(name, None) + if cb is not None: + cb.error() + + def on_buddy_leave(self, name, notify = True): + self._type_override = None + buddy = self.buddies[name] = self.protocol.get_buddy(name) + + try: + self.room_list.remove(buddy) + except ValueError: + log.info('Buddy %r wasn\'t in room but left anyway (?)', name) + + in_room = set(self._clean_list()) - self._to_invite + in_room.discard(self.self_buddy.name) + self.typing_status.pop(buddy, None) + self.event('contacts_changed') + + super(MSNP21Conversation, self).buddy_leave(buddy) + self.notify('ischat') + + def fed_message(self, msg): + self.recv_msg(msg) + + def recv_msg(self, msg): + if msg.name not in self.room_list: + if not self.ischat: + assert msg.name == self._chat_target_name, (msg.name, self._chat_target_name) + self.buddy_join(msg.name) + + try: + getattr(self, 'recv_msg_%s' % msg.type, self.recv_msg_unknown)(msg) + except Exception, e: + import traceback + traceback.print_exc() + + log.error('Exception handling MSG: %r, msg = %r', e, msg) + + def recv_msg_control_typing(self, msg, typing = True): + name = msg.name + buddy = self.buddies[name] = self.protocol.get_buddy(name) + self.typing_status[buddy] = 'typing' if typing else None + + log.info('%s is %styping', name, '' if typing else 'not ') + + def recv_msg_unknown(self, msg): + log.info("Got an unknown message: %r (%r)", msg.type, str(msg)) + + def recv_msg_signal_forceabchsync(self, msg): + if msg.name == self.self_buddy.name: + payload = msg.payload.get_payload() + log.info("Got addressbook sync signal from a different endpoint: %r", payload) + doc = etree.fromstring(payload) + self.protocol.ns._sync_addressbook(abid = doc.find('.//Service').attrib.get('id', str(uuid.UUID(int = 0))), + PartnerScenario = SOAPServices.PartnerScenario.ABChangeNotifyAlert) + + def recv_msg_wink(self, msg): + self.on_action_recv(msg.name, 'wink', None) + + def recv_msg_nudge(self, msg): + self.on_action_recv(msg.name, 'nudge', None) + + def recv_msg_text(self, msg): + name = msg.name + textmsg = MSNTextMessage.from_net(msg.payload) + + buddy = self.buddies[name] = self.protocol.get_buddy(name) + self.typing_status[buddy] = None + + if hasattr(textmsg, 'html'): + message = textmsg.html().replace('\n', '
') + content_type = 'text/html' + else: + message = textmsg + content_type = 'text/plain' + + service_channel = offline = msg.payload.get("Service-Channel", None) + sms = service_channel == 'IM/Mobile' + offline = service_channel == "IM/Offline" + timestamp_str = msg.payload.get('Original-Arrival-Time', None) + if timestamp_str is None: + timestamp = None + else: + timestamp = datetime.datetime.fromtimestamp(SOAPServices.strptime_highres(timestamp_str)) + + did_receive = self.received_message(buddy, message, sms = sms, content_type = content_type, + offline = offline, timestamp = timestamp) + + if name != self.self_buddy.name and did_receive: + Conversation.incoming_message(self) + + def send(self, *a, **k): + return self.protocol.ns.socket.send(*a, **k) + + def message_header(self, first = None, second = None, path = 'IM', epid = None): + if epid is not None: + to_value = '%s;epid={%s}' % (self.chat_id, epid) + else: + to_value = '%s;path=%s' % (self.chat_id, path) + + message_headers = ( + (('Routing', '1.0'), + ('To', to_value), + ('From', '%d:%s;epid={%s}' % (MSNAB.IMAddressInfoType.WindowsLive, self.self_buddy.name, str(self.client.get_machine_guid()).lower())), + ) + (tuple(first or ())), + + (('Reliability', '1.0'), + + ) + (tuple(second or ())), + ) + + return message_headers + + def send_typing_status(self, status): + if status != 'typing': + return + + if not self.buddy.online: + return + + payload = MSNC.MultiPartMime(self.message_header() + + ((('Messaging', '2.0'), + ('Message-Type', 'Control/Typing'),),), + body = '') + + self.send(MSNC.SDG(payload = str(payload)), trid = True, callback = sentinel) + + @callbacks.callsback + def send_text_message(self, body, callback = None): + log.info("message body: %r, %r", type(body), body) + if isinstance(body, unicode): + body = body.encode('utf8') + + msnt = email.message_from_string(body) + + header_args = [] + if not self.buddy.online: + header_args.append(("Service-Channel", "IM/Offline")) + elif self.buddy.mobile and self.buddy.sms: + header_args.append(("Service-Channel", "IM/Mobile")) + + payload = MSNC.MultiPartMime(self.message_header(header_args) + + ((('Messaging', '2.0'), + ('Message-Type', 'Text'), + ('IM-Display-Name', (self.protocol.self_buddy.remote_alias or self.protocol.self_buddy.name).encode('utf8')), + ('X-MMS-IM-Format', msnt.get('X-MMS-IM-Format', 'EF=;')), + ('Content-Type', msnt.get('Content-Type', 'text/plain; charset=UTF-8')), + ),), + body = msnt.get_payload()) + + + self.send(MSNC.SDG(payload = str(payload)), trid = True, callback = callback) + + @callbacks.callsback + def _send_message(self, msg, callback = None, **k): + + if not self.ischat: + cl = set(self._clean_list()) + cl -= set([self.self_buddy.name]) + if not cl: + callback.error() + self.system_message('You can\'t message yourself using MSN.') + return + + body = msg.format_as('msn') + + def check_nak(sck, emsg): + log.error('Error sending message: %r', emsg) + cmd = getattr(emsg, 'cmd', None) + if cmd == 'NAK': + self._send_message_im(msg, callback = callback) + elif cmd == '217': + # user was offline. use self.protocol.ns.getService('OIMService').Store(...) + self.system_message("Offline messaging not yet supported") + else: + callback.error(emsg) + + self.send_text_message(body, error = check_nak) + callback.success() + + @callbacks.callsback + def p2p_send(self, mime_headers, data, epid = None, callback = None): + payload = MSNC.MultiPartMime(self.message_header((('Options','0'), ('Service-Channel', 'PE')), path = 'PE', epid = epid) + + (mime_headers,), + body = data) + cmd = msn.MSNCommands.SDG(payload = str(payload)) + + def handle_error(sck = None, e = None): + # if recoverable(e): + # callback.error() + # else: + # callback.error("fatal") + callback.error(e) + + self.send(cmd, trid = True, success = callback.success, error = handle_error) + diff --git a/digsby/src/msn/p21/MSNP21Notification.py b/digsby/src/msn/p21/MSNP21Notification.py new file mode 100644 index 0000000..c88a6de --- /dev/null +++ b/digsby/src/msn/p21/MSNP21Notification.py @@ -0,0 +1,2230 @@ +import logging +_log = log = logging.getLogger('msn.p21.ns') + +import traceback + +import io +import time +import uuid +import hashlib +import sysident +import socket +import email +import rfc822 +import copy +import operator + +import lxml.etree as etree +import lxml.builder as B +import lxml.objectify as objectify + +import hooks + +import util +import util.net as net +import util.Events as Events +import util.cacheable as cacheable +import util.callbacks as callbacks +import util.network.soap as soap +import util.primitives.funcs as funcs +import util.primitives.strings as strings + +import common +import common.asynchttp as asynchttp + +import msn +import msn.MSNCommands as MSNC +import msn.MSNClientID as MSNClientID +import msn.P2P as P2P +import msn.P2P.P2PHandler as P2PHandler + +from msn.p15 import Notification as Super + +import mail.passport as passport + +import msn.SOAP.services as SOAPServices + +import msn.AddressBook as MSNAB +import msn.Storage as MSNStorage + +defcb = dict(trid=True, callback=sentinel) +empty_guid = str(uuid.UUID(int=0)) + +def deprecated(f): + def _deprecated(*a, **k): + log.info("Deprecated method called: %s", f.func_name) + return + return _deprecated + +def userinfo_edit(f): + def wrapper(self, *args, **kwds): + def edit_and_push(buddy, **k): + f(self, buddy, *args, **kwds) + self.put_userinfo(buddy, **k) + self.event('needs_self_buddy', edit_and_push) + return wrapper + +@callbacks.callsback +def zsiparse(soap, locatorname, name, raw, callback = None): + loc = getattr(soap, '%sLocator' % locatorname)() + port = getattr(loc, 'get%sPort' % name)() + binding = port.binding + + binding.reply_headers = util.Storage(type='text/xml') + binding.data = raw + response = util.Storage(body = io.StringIO(raw), headers = util.Storage(get_content_type = lambda : 'text/xml')) + return binding.Receive(response, callback = callback) + +class SSOTicket(object): + domain = None + token = None + binarySecret = None + created = 0 + expires = 0 +# type = None + _timefmt = '%Y-%m-%dT%H:%M:%SZ' + + def __init__(self, domain, token, binarySecret, created, expires): + if created is not None: + created = time.mktime(time.strptime(created, self._timefmt)) + if expires is not None: + expires = time.mktime(time.strptime(expires, self._timefmt)) + + util.autoassign(self, locals()) + +class MSNP21Notification(Super): + events = Super.events | set(( + 'needs_self_buddy', + 'on_circle_member_joined', + 'circle_roster_recv', + 'circle_roster_remove', + 'P2PMessageReceived', + )) + + versions = ['MSNP21'] + client_chl_id = challenge_id = "PROD0120PW!CCV9@" + client_chl_code = "C1BX{V4W}Q3*10SM" + + def __init__(self, *a, **k): + Super.__init__(self, *a, **k) + + self._authorizers = { + 'default' : self.initial_auth, + } + + self.machine_guid = None + + self.init_http() + + self.services = [] + self.init_services() + + # Tokens is the old name but tickets is more accurate, especially for SOAP stuff + self.tokens = self.tickets = {} + self.cachekeys = {} + if self.address_book is None: + self.address_book = MSNAB.AddressBook(self) + + if getattr(self.address_book, 'client', None) is None: + self.address_book.client = self + + if self.contact_list is None: + self.contact_list = MSNAB.ContactList(self, uuid.UUID(int=0)) + + if getattr(self.contact_list, 'client', None) is None: + self.contact_list.client = self + + if self.profile is None: + self.profile = MSNStorage.Profile(self) + self.fetched_profile = False + else: + self.profile.client = self + self.fetched_profile = True + + self.requestCircleCount = 0 + + if self.CircleList is None: + self.CircleList = {} + + self.PresenceScenario = 0 + self.sent_initial_adls = False + + self._abFindCallback = funcs.Delegate() + + self.pending_put = [] + + address_book = cacheable.cproperty(None, + lambda x: None if x is None else x.serialize(), + lambda x: None if x is None else MSNAB.AddressBook.deserialize(x), + user = True) + + contact_list = cacheable.cproperty(None, + lambda x: None if x is None else x.serialize(), + lambda x: None if x is None else MSNAB.ContactList.deserialize(x), + user = True) + + profile = cacheable.cproperty(None, + lambda x: None if x is None else x.serialize(), + lambda x: None if x is None else MSNStorage.Profile.deserialize(x), + user = True) + + CircleList = cacheable.cproperty(None, + lambda x: None if x is None else dict((k, v.serialize()) for k,v in x.items()), + lambda x: None if x is None else dict((k, MSNAB.Circle.deserialize(v)) for k,v in x.items()), + user = True) + + @Events.event + def on_contact_add(self, name, b_id, l_id, g_id): + return name, b_id, l_id, g_id + + def init_http(self): + self.http = asynchttp.cookiejartypes.CookieJarHTTPMaster() + + def _create_conv_invite(self, *a, **k): + return self.protocol._create_conv_invite(*a, **k) + + def init_services(self): + + self.services.extend([SOAPServices.SecurityTokenService(transport = self.http), + SOAPServices.WhatsUpService(transport = self.http), + SOAPServices.SharingService(transport = self.http), + SOAPServices.ABService(transport = self.http), + SOAPServices.ClearService(transport = self.http), +# SOAPServices.SpacesService(transport = self.http), + SOAPServices.StorageService(transport = self.http), + SOAPServices.OIMService(transport = self.http), + SOAPServices.RSIService(transport = self.http), + ]) + + def get_cachekey(self, key): + return self.cachekeys.get(key, None) + + def set_cachekey(self, key, cachekey): + self.cachekeys[key] = cachekey + + def getSsoService(self, domain): + for s in self.getSsoServices(): + if s.SSO_Domain == domain: + return s + return None + + def getService(self, appid): + for s in self.services: + if getattr(s, 'AppId', None) == appid: + return s + return None + + def getSsoServices(self): + return [x for x in self.services if getattr(x, 'SSO', False)] + + @callbacks.callsback + def initial_auth(self, username, password, sso_data, callback = None): + _policy_ref, nonce = self._sso_data = sso_data + self.clearservice_auth(username, password, sso_data, + success = lambda new_tickets: self._after_initial_auth(nonce = nonce, new_tickets = new_tickets, callback = callback), + error = callback.error) + + @callbacks.callsback + def clearservice_auth(self, username, password, sso_data, callback = None): + clear = self.getSsoService(SOAPServices.SsoDomains.Clear) + policy_ref, nonce = sso_data + clear.SSO_PolicyRef = policy_ref + log.info_s("SSO_Auth: %r, %r", username, sso_data) + + sts = self.getSsoService(SOAPServices.SsoDomains.STS) + + services = self.get_auth_required_services() + client = util.Storage(get_username = lambda: username, + get_password = lambda: password, + getSsoServices = lambda: services) + + if not services: + log.debug("No tickets need renewing. calling success") + return callback.success([]) + + sts.RequestMultipleSecurityTokens(client = client, services = services, + success = lambda resp: self.process_tickets(resp, callback = callback), + error = lambda *a: self.request_ticket_error(callback = callback, *a)) + + @callbacks.callsback + def _after_initial_auth(self, nonce, new_tickets, callback = None): + clearticket = self.get_ticket(SOAPServices.SsoDomains.Clear) + if clearticket is None: + callback.error(Exception("No ticket for %r", SOAPServices.SsoDomains.Clear)) + return + + token = clearticket.token + secret = clearticket.binarySecret + + callback.success(token, passport.mbi_crypt(secret, nonce), new_tickets) + + @callbacks.callsback + def renew_auth(self, callback = None): + return self.clearservice_auth(self._username, self._password, self._sso_data, callback = callback) + + def get_auth_required_services(self, force = None): + if force is None: + force = [] + + required = [] + requesting_domains = set() + for service in self.getSsoServices(): + if service in force: + continue + if not (self.has_current_ticket(service.SSO_Domain) or service.SSO_Domain in requesting_domains): + requesting_domains.add(service.SSO_Domain) + required.append(service) + + required.extend(force) + + return required + + @callbacks.callsback + def process_tickets(self, tickets, callback = None): + new_tickets = [] + for token in tickets.RequestSecurityTokenResponse: + try: + ticket = SSOTicket(token.AppliesTo.EndpointReference.Address, + str(getattr(getattr(token, 'RequestedSecurityToken', ''), 'BinarySecurityToken', '')) or None, + str(getattr(getattr(token, 'RequestedProofToken', ''), 'BinarySecret', '')) or None, + str(getattr(getattr(token, 'Lifetime', ''), 'Created', '')) or None, + str(getattr(getattr(token, 'Lifetime', ''), 'Expires', '')) or None, + ) + + self.set_ticket(ticket) + new_tickets.append(ticket) + log.info("Got ticket for domain %r", ticket.domain) + except Exception: + traceback.print_exc() + + callback.success(new_tickets) + + def complete_auth(self, token, mbi, tickets): + self.send_usr_s(token, mbi, self.get_machine_guid()) + + def get_machine_guid(self): + if self.machine_guid is None: + self.generate_machine_guid() + + return self.machine_guid + + def generate_machine_guid(self): + self.machine_guid = uuid.UUID(bytes=hashlib.md5(sysident.sysident() + str(time.time())).digest()) + + @callbacks.callsback + def request_ticket_error(self, *a, **k): + self._ticket_error = (a, k) + k['callback'].error(Exception("No tickets")) + + def has_current_ticket(self, domain): + if self.get_ticket(domain) is None: + return False + + if self.is_ticket_expired(domain): + return False + + return True + + def is_ticket_expired(self, domain): + ticket = self.get_ticket(domain) + if ticket is None: + return True + + return ticket.expires < time.time() + + def get_ticket(self, domain): + return self.tickets.get(domain, None) + + def set_ticket(self, ticket, domain = None): + if domain is None: + domain = ticket.domain + + self.tickets[domain] = ticket + + def recv_ubx(self, msg): + msg.args = msg.args[0].split(':')[::-1] + return Super.recv_ubx(self, msg) + + def _finish_connect(self): + if not self.CONNECTED: + + def connected(*a, **k): + if not self.CONNECTED: + self.CONNECTED = True + log.info("connected!") + self.event('on_connect') + + log.info('putting userinfo -> changing to connected') + self.event('needs_self_buddy', lambda b: self.put_userinfo(b, success = connected)) + + if self.sent_initial_adls: + for content, callback in self.pending_put: + self.send_put(content, callback = callback) + + @callbacks.callsback + def _sync_addressbook(self, fullsync = False, abid = None, PartnerScenario = SOAPServices.PartnerScenario.Initial, callback = None): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + + if abid is None: + abid = str(uuid.UUID(int=0)) + + if fullsync: + lastchange = soap.MinTime + else: + lastchange = self.address_book.GetAddressBookLastChange(abid) + + self._abFindCallback += callback + + ab.ABFindContactsPaged(client = self, + abid = abid, + deltas = lastchange != soap.MinTime, + lastChange = lastchange, + PartnerScenario = PartnerScenario, + success = lambda resp: self._abFindSuccess(resp, callback = callback), + error = lambda e: self._abFindError(e, callback = callback)) + + @callbacks.callsback + def _abFindSuccess(self, response, callback = None): +# if response.UserState.PartnerScenario == MSNAB.Scenario.Initial: +# log.info("Need to do a full sync") + + default_ab = lowerid = str(uuid.UUID(int=0)) + if response.ABFindContactsPagedResult.Ab is not None: + lowerid = str(response.ABFindContactsPagedResult.Ab.AbId).lower() + + if self.address_book.client is None: + self.address_book.client = self + if self.contact_list.client is None: + self.contact_list.client = self + if lowerid == default_ab: + self.address_book.MergeIndividualAddressBook(response.ABFindContactsPagedResult) + else: + self.address_book.MergeGroupAddressBook(response.ABFindContactsPagedResult) + + if self.requestCircleCount > 0: + self.requestCircleCount -= 1 + + if self.requestCircleCount == 0: + if self.PresenceScenario and not self.sent_initial_adls: + self.send_initial_adl(MSNAB.Scenario.SendInitialCirclesADL) + + self._abFindCallback.call_and_clear() + + # Force a cache save + self.address_book = self.address_book + self.contact_list = self.contact_list + + if not self.fetched_profile: + self.sync_profile() + + ticket = getattr(getattr(response.ABFindContactsPagedResult, 'CircleResult', None), 'CircleTicket', None) + if ticket is not None: + self.socket.send(msn.Message('USR', 'SHA', 'A', ticket.encode('utf8').encode('b64')), trid = True) + + @callbacks.callsback + def _abFindError(self, e, callback = None): + log.info("abFindError: %r", e) + import sys + sys._abe = e + if fault_check(e, "Full sync required") or fault_check(e, "Need to do full sync"): + log.info("FullSyncRequired") + self._sync_addressbook(fullsync = True, callback = callback) + return + + if not self.CONNECTED: + self.event('on_conn_error', self, e) + + callback.error(e) + + def _load_contact_list(self): + self._sync_addressbook() + self._sync_memberships() + + if self.address_book.GetAddressBookLastChange() != soap.MinTime: + log.info("Logging on early since we have cached addressbook") + self.address_book.initialize() + self.send_initial_adl(MSNAB.Scenario.SendInitialContactsADL) + self.send_initial_adl(MSNAB.Scenario.SendInitialCirclesADL) + self._finish_connect() + + def _sync_memberships(self, fullsync = False, PartnerScenario = SOAPServices.PartnerScenario.Initial): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + if fullsync: + lastchange = soap.MinTime + else: + lastchange = self.address_book.GetAddressBookLastChange() + + sharing = self.getService(SOAPServices.AppIDs.Sharing) + sharing.FindMembership(client = self, + deltas = lastchange != soap.MinTime, + lastChange = lastchange, + PartnerScenario = PartnerScenario, + success = self._findMembershipSuccess, + error = self._findMembershipError) + + def _findMembershipSuccess(self, response): + result = response.FindMembershipResult + self.address_book.Merge(result) + + # Force a cache save + self.address_book = self.address_book + self.contact_list = self.contact_list + + if self.contact_list is not None and self.contact_list.owner is not None: + self.send_initial_adl(MSNAB.Scenario.SendInitialContactsADL) + self.send_initial_adl(MSNAB.Scenario.SendInitialCirclesADL) + else: + raise Exception("contact list or owner is None") + + def _findMembershipError(self, e): + log.info("findMembershipError: %r", e) + + if fault_check(e, "Full sync required") or fault_check(e, "Need to do full sync"): + log.info("FullSyncRequired") + self._sync_memberships(fullsync = True) + return + + self.event('on_conn_error', self, e) + + def AddGroup(self, name, id, is_favorite): + # todo: create groupid object out of id? + self.event('group_receive', name, id) + + def recv_fln(self, msg): + 'FLN 1:urapns55@hotmail.com 0:0\r\n' + log.debug('got fln') + + abc = self.get_ab_contact(msg.args[0]) + name = msg.args[0] + nick = None + status = 'FLN' + client_id = 0 + self.event('contact_offline', abc.account, nick, status, client_id) + + def get_ab_contact(self, type_name): + if ':' in type_name: + type, name = type_name.split(':', 1) + + else: + type = 1 + name = type_name + + return self.contact_list.GetContact(name, type = type) + + def recv_nln(self, msg): + 'NLN NLN 1:urapns55@hotmail.com Jeffrey 1074004004:2281833472 %3cmsnobj%20Creator%3d%22urapns55%40hotmail.com%22%20Size%3d%224767%22%20Type%3d%223%22%20Location%3d%22TFR2C2.tmp%22%20Friendly%3d%22AAA%3d%22%20SHA1D%3d%22R%2baq3gIarGx4uC%2frXBW32DgC5EE%3d%22%20SHA1C%3d%22Ne6p6df%2f%2fIwdO8gyKmooCiHocJg%3d%22%2f%3e\r\n' + log.debug('got nln') + + (status, typename, nick, capab_ex), __args = msg.args[:4], (msg.args[4:] or []) + + abc = self.get_ab_contact(typename) + abc.nickname = nick = nick.decode('url').decode('fuzzy utf8') or None + client_id = self.parse_caps(capab_ex) + + msnobj = None + if __args: + iconinfo = msn.util.url_decode(__args[0]) + + if '<' in iconinfo and '>' in iconinfo: + msnobj = msn.MSNObject.parse(iconinfo) + + self.event('contact_btype', abc.account, abc.type) + + self.event('contact_online', abc.account, nick, status, client_id) + self.event('contact_icon_info', abc.account, msnobj) + + def parse_caps(self, caps): + return int(caps.split(':')[0]) + + def _set_persist_blp(self, val): + + if self.contact_list.owner is None: + return + + annos = self.contact_list.owner.contactInfo.annotations + for anno in annos: + if anno.Name == MSNAB.AnnotationNames.MSN_IM_BLP: + break + else: + anno = MSNAB.Annotation(Name = MSNAB.AnnotationNames.MSN_IM_BLP) + + anno.Value = str(int(val)) + + self.contact_list.owner.PropertiesChanged = "Annotation" + #self.ABContactUpdate(self.contact_list.owner) + + def _get_profile(self, buddy, callback): + log.info("get profile: %r", buddy) + + def disconnect(self, do_event = True): + self.save_and_destroy_address_book() + self.P2PHandler, P2PHandler = None, getattr(self, 'P2PHandler', None) + if P2PHandler is not None: + P2PHandler.Dispose() + + return Super.disconnect(self, do_event) + + def save_and_destroy_address_book(self): + self.address_book = self.address_book + self.contact_list = self.contact_list + self.CircleList = self.CircleList + + for contact in self.contact_list.contacts.values(): + if contact.DirectBridge is not None: + contact.DirectBridge.Shutdown() + + self.address_book.client = None + self.contact_list.client = None + + def needs_login_timer(self): + return False + + def sync_profile(self): + storage = self.getSsoService(SOAPServices.SsoDomains.Storage) + + if ((not getattr(self.profile, 'DateModified', None)) or + (self.profile.DateModified < self.address_book.MyProperties.get(MSNAB.AnnotationNames.Live_Profile_Expression_LastChanged, soap.MinTime))): + scene = SOAPServices.PartnerScenario.Initial + else: + scene = SOAPServices.PartnerScenario.RoamingIdentityChanged + + storage.GetProfile(client = self, scenario = scene, success = self._profile_success, error = self._profile_error) + + def _profile_success(self, resp): + self.fetched_profile = True + log.info("Got profile: %r", resp) + + pr = getattr(resp, 'GetProfileResult', None) + ep = getattr(pr, 'ExpressionProfile', None) + if pr is None or ep is None: + self._CreateProfile() + return + + self.profile = MSNStorage.Profile.from_zsi(resp.GetProfileResult, client = self) + + if ep.DisplayName is not None: + displayname = ep.DisplayName + if isinstance(displayname, str): + displayname = displayname.decode('utf8') + self.contact_alias(self.self_buddy.name, displayname) + + if ep.PersonalStatus is not None: + self.contact_status_msg(self.self_buddy.name, ep.PersonalStatus) + + if ep.StaticUserTilePublicURL is not None: + self.contact_icon_info(self.self_buddy.name, ep.StaticUserTilePublicURL) + + def _profile_error(self, e): + log.info("profile error: %r", e) + self._profile_e = e + + def _CreateProfile(self): + pass + + def send_uux(self, msg='', callback=sentinel): + guid = self.get_machine_guid() + + doc = B.E("EndpointData", + B.E('Capabilities', '%d:0' % self.self_buddy.client_id)) + + self.socket.send(msn.Message('UUX', payload=etree.tostring(doc)), trid=True, callback=callback) + + self_status = self.self_buddy.protocol.status_to_code.get(self.self_buddy.status, 'NLN') + if self_status == "FLN": + self_status = "HDN" + + doc = B.E("PrivateEndpointData", + B.E("EpName", socket.gethostname()), + B.E("Idle", str(common.profile().idle).lower()), + B.E("ClientType", "1"), + B.E("State", self_status), + ) + + self.socket.send(msn.Message('UUX', payload=etree.tostring(doc)), trid=True, callback=callback) + + def recv_msg_notification(self, msg): + #name, passport = msg.args + if msg.name == 'Hotmail': + MD = self.extract_oim_info(msg) + self._oim_info = MD + self.oims = [] #msn.oim.OIMMessages(self, MD) + else: + log.warning('unknown msg/notification') + + def extract_oim_info(self, oim_info_msg): + msg_obj = rfc822.Message(oim_info_msg.payload.body()) + maildata = msg_obj['Mail-Data'] + if 'too-large' in maildata: + MD = None + else: + MD = objectify.fromstring(maildata) + + return MD + + @userinfo_edit + def send_chg(self, *a, **k): + pass + + @userinfo_edit + def _set_status(self, buddy, code, _client_id, _callback): + buddy.status_code = code + + @callbacks.callsback + def put_userinfo(self, buddy, callback = None, **k): + + message_headers = ( + (('Routing', '1.0'), + ('To', '%d:%s' % (MSNAB.IMAddressInfoType.WindowsLive, buddy.name)), + ('From', '%d:%s;epid={%s}' % (MSNAB.IMAddressInfoType.WindowsLive, buddy.name, str(self.get_machine_guid()))), + ), + (('Reliability', '1.0'), + ('Stream', '1'), + #('Segment', '0'), + ('Flags', 'ACK'), + ), + (('Publication', '1.0'), + ('Uri', '/user'), + ('Content-Type', 'application/user+xml'), + ), + ) + + if buddy.msn_obj is None: + msnobj_txt = '' + elif hasattr(buddy.msn_obj, 'to_xml'): + msnobj_txt = buddy.msn_obj.to_xml() + else: + msnobj_txt = buddy.msn_obj + + body_doc = B.E("user", + B.E("s", + B.E("UserTileLocation", msnobj_txt), + B.E("FriendlyName", buddy.alias), + B.E("PSM", buddy.status_message), + #B.E("DDP", ''), + #B.E("Scene", ''), + #B.E("ASN", ''), + #B.E("ColorScheme", "-3"), + #B.E("BDG", ''), + B.E("RUM", ''), + #B.E("RUL", ''), + #B.E("RLT", "0"), + #B.E("RID", ''), + #B.E("SUL", ''), + B.E("MachineGuid", "{%s}" % self.get_machine_guid()), + n="PE", + ), + B.E("s", + B.E("Status", 'HDN' if buddy.status_code == 'FLN' else buddy.status_code), + B.E("CurrentMedia", ''), #buddy.current_media + n="IM", + ), + B.E("sep", + B.E("VER", "%s:%s" % (self.client_name, self.client_software_version)), + B.E("TYP", "1"), + B.E("Capabilities", + #"67108864:1074298880"), + '%s:%s' % (MSNClientID.PE_DEFAULT_CAPABILITIES, MSNClientID.PE_DEFAULT_CAPABILITIES_EX)), + n="PE", + ), + B.E("sep", + B.E("ClientType", '1'), + B.E("EpName", socket.gethostname()), + B.E("Idle", 'true' if buddy.idle else 'false'), + B.E("State", 'HDN' if buddy.status_code == 'FLN' else buddy.status_code), + n="PD", + ), + B.E("sep", + B.E("Capabilities", + #"2955186480:2609258384"), + '%s:%s' % (MSNClientID.IM_DEFAULT_CAPABILITIES, MSNClientID.IM_DEFAULT_CAPABILITIES_EX)), + n="IM", + ), + ) + + body = etree.tostring(body_doc, encoding = 'utf8') + + payload = str(MSNC.MultiPartMime(message_headers, body = body)) + self.send_put(payload, callback = callback) + + @callbacks.callsback + def send_put(self, content, callback = None): + if self.sent_initial_adls: + log.info("PUT content: %r", content) + self.socket.send(msn.Message('PUT', payload = content), trid = True, + success = lambda sck, msg: callback.success(msg)) + else: + self.pending_put.append((content, callback)) + + def send_del(self, content): + log.info("DEL content: %r", content) + self.socket.send(msn.Message('DEL', payload = content), **defcb) + + def send_initial_adl(self, scene): + first_adl_key = 0 + hashlist = {} + + adls = [] + pending_joins = [] + if (scene & MSNAB.Scenario.SendInitialContactsADL): + log.warning("Prepping contact ADLS!") + contacts = self.contact_list.contacts.values() + elif (scene & MSNAB.Scenario.SendInitialCirclesADL): + log.warning("Prepping circle ADLS!") + contacts = self.CircleList.values() + + for c in contacts: + if getattr(c, 'pending_join', False): + pending_joins.append(c) + + for c in contacts[:]: + if c.CircleRole == MSNAB.CirclePersonalMembershipRole.StatePendingOutbound: + contacts.remove(c) + + for contact in contacts: + if contact.ADLCount == 0: + continue + + contact.ADLCount -= 1 + ch = contact.Hash + l = 0 + + if contact.IsMessengerUser or (contact.type == MSNAB.ClientType.CircleMember and contact.OnForwardList): + l |= int(MSNAB.MSNList.Forward) + if contact.OnAllowedList: + l |= int(MSNAB.MSNList.Allow) + elif contact.OnBlockedList: + l |= int(MSNAB.MSNList.Block) + if contact.HasList(MSNAB.MSNList.Hidden): + l |= int(MSNAB.MSNList.Hidden) + + if l not in (0, int(MSNAB.MSNList.Block)) and ch not in hashlist: + hashlist[ch] = hashlist.get(ch, 0) | l + + def success(): + for c in pending_joins: + log.info("Completing delayed circle join for %r", c) + self.JoinCircleConversation(c.abid + '@' + c.hostDomain) + + self.PresenceScenario |= scene + log.info("PresenceScenario = %r", self.PresenceScenario) + desired_scenario = (MSNAB.Scenario.SendInitialCirclesADL | MSNAB.Scenario.SendInitialContactsADL) + if (self.PresenceScenario & desired_scenario) == desired_scenario and not self.sent_initial_adls: + self.sent_initial_adls = True + self._finish_connect() + + if hashlist: + adls.extend(self.make_adls(hashlist, (scene & MSNAB.Scenario.SendInitialContactsADL) == MSNAB.Scenario.SendInitialContactsADL)) + log.info("got adls!: %r", adls) + self.send_adl_sequence(adls, success = success) + else: + success() + + def send_rml_sequence(self, adls): + return self.send_adl_sequence(adls, cmd = 'RML') + + @callbacks.callsback + def send_adl_sequence(self, adls, cmd = 'ADL', callback = None): + log.info("initiating send of %r %ss", len(adls), cmd) + _adls = adls[:] + def send_adl(*a): + if not adls: + log.info("All %ss sent", cmd) + return callback.success() + adl = adls.pop(0) + self.socket.send(msn.Message(cmd, payload = adl), trid = True, success = send_adl, error = send_adl) + send_adl() + + def make_adls(self, contacts, initial = False): + mls = [] + ml = B.E("ml") + + if initial: + ml.attrib['l'] = '1' + + domain_contact_count = 0 + currentDomain = None + domtelElement = None + + log.debug("contacts.keys(): %r", contacts.keys()) + for contact_hash in sorted(contacts.keys(), cmp = cmp_contact_domain): + split = split_contact_hash(contact_hash) + sendlist = contacts[contact_hash] | int(MSNAB.MSNList.Forward) + + _type = MSNAB.ClientType.EmailMember + + if len(split) > 0: + _type = MSNAB.ClientType(int(split[0])) + + try: + _emailaddr = util.net.EmailAddress(split[1]) + name, domain = _emailaddr.name, _emailaddr.domain + except Exception: + domain = '' + name = 'tel:' + split[1] + if _type != MSNAB.ClientType.PhoneMember: + log.info("Non-phone member could not be parsed as email address: %r", contact_hash) + else: + if _type == MSNAB.ClientType.PhoneMember: + log.info("Phone member was parseable as email address: %r", contact_hash) + + if sendlist != 0: + if currentDomain != domain: + currentDomain = domain + domain_contact_count = 0 + if _type == MSNAB.ClientType.PhoneMember: + domtelElement = B.E("t") + else: + domtelElement = B.E("d") + domtelElement.attrib['n'] = currentDomain + + ml.append(domtelElement) + + contactElement = B.E("c", n = name) + if _type != MSNAB.ClientType.PhoneMember: + contactElement.attrib['t'] = str(int(_type)) + + contact = self.contact_list.contacts.get(contact_hash) + if contact is None: + contact = self.CircleList.get(contact_hash) + + epdata = getattr(contact, 'EndPointData', None) + + def s_el(n, sendlist): + el = B.E('s', l = str(sendlist), n = n) + for epid in epdata: + if n in epdata[epid]: + if int(epid) != 0: + el.attrib['epid'] = str(epid) + el.attrib['n'] = n + + return el + + if split[1] == self.contact_list.owner.account: + sendlist = 3 + + contactElement.append(s_el('PD', sendlist)) + + contactElement.append(s_el('IM', sendlist)) + + if sendlist & int(MSNAB.MSNList.Hidden) == 0: + contactElement.append(s_el('PE', sendlist)) + + if _type != MSNAB.ClientType.PhoneMember: + contactElement.append(s_el('PF', sendlist)) + + domtelElement.append(contactElement) + domain_contact_count += 1 + + serialized = etree.tostring(ml) + if len(serialized) > 7300: + ml.append(domtelElement) + mls.append(etree.tostring(ml, encoding = 'utf-8')) + + ml = B.E('ml') + if initial: + ml.attrib['l'] = '1' + currentDomain = None + domain_contact_count = 0 + + if domain_contact_count > 0 and domtelElement is not None: + ml.append(domtelElement) + + mls.append(etree.tostring(ml, encoding = 'utf-8')) + + return mls + +# def recv__241(self, msg): +# log.info("Got 241 message. Echoing it back...") +# self.socket.send(msn.Message("ADL", payload = msg.payload), **defcb) + + def recv_adl(self, msg): + log.debug("Got ADL: %r", msg) + + def recv_nfy(self, msg): + message = MSNC.MultiPartMime.parse(msg.payload) + to = message.get('To') + from_ = message.get('From') + via = message.get('Via', None) + if via is not None: + circle = self.GetCircle(via) + else: + circle = None + + payload = message.get_payload() + if message.get('Content-Type') in ('application/user+xml', 'application/circles+xml') and len(payload) != 0: + payload = objectify.fromstring(payload) + else: + payload = None + + ctype, name = split_contact_hash(from_)[:2] + ctype = int(ctype) + if message.get('Uri') == '/user': + if msg.args[0] == 'PUT' and payload is not None: + return self.update_user_nfy(name, ctype, payload, full = message.get("NotifType", "Partial") == "Full", circle = circle) + elif msg.args[0] == 'DEL': + return self.delete_user_nfy(name, ctype, payload, circle = circle) + elif message.get('Uri').startswith('/circle'): + if msg.args[0] == 'PUT' and payload is not None: + return self.update_circle_roster(name, ctype, payload, full = message.get('NotifType', 'Partial') == 'Full', circle = circle) + elif msg.args[0] == 'DEL': + return self.remove_circle_roster(name, ctype, message.get('Uri'), circle = circle) + + log.info("Unknown NFY command: %r / %r", msg.args, msg.payload) + + def remove_circle_roster(self, circle_name, ctype, uri, circle = None): + if not uri.startswith ('/circle/roster(IM)/user('): + return + + name = uri[len('/circle/roster(IM)/user('):-1] + self.event('circle_roster_remove', circle_name, ctype, name) + + def update_circle_roster(self, circle_id, ctype, payload, full = False, circle = None): + names = [] + pending_names = [] + nonpending_names = [] + roster = getattr(payload, 'roster', None) + if roster is None: + return + + for user in payload.roster.user: + name = str(user.id) + names.append(name) + state = str(getattr(user, 'state', '')) + if state == 'Pending': + pending_names.append(name) + else: + nonpending_names.append(name) + + self.event('circle_roster_recv', circle_id, ctype, names, pending_names, nonpending_names, full) + + def update_user_nfy(self, name, ctype, payload, full, circle = None): + contact = self.contact_list.GetContact(name, type = ctype) + + if full: + contact.EndPointData.clear() + + tag = 'sep' + for attr in ('IM', 'PE', "PF", 'PD'): + new = payload.find(".//%s[@n='%s']" % (tag, attr)) + if new is None: + continue + + epid = new.attrib.get('epid') + if epid is None: + continue + + epid = uuid.UUID(epid) + + if epid not in contact.EndPointData: + contact.EndPointData[epid] = {} + contact.EndPointData[epid][attr] = new + + self.update_user_presence(contact, payload, None, circle) + + def update_user_presence(self, contact, nfy, old_status, circle): + log.info("Update user presence: %r %r", contact.account, etree.tostring(nfy, pretty_print = True)) + + name = contact.account + nick = status = None + psm = None + msnobj = None + + for s in nfy.iterfind('s'): + _status = s.find('Status') + if status is None and _status is not None: + status = _status.text + + _nick = s.find("FriendlyName") + if nick is None and _nick is not None: + nick = _nick.text + + _psm = s.find('PSM') + if psm is None and _psm is not None: + # If the tag is present but empty, we need to clear the status message. + psm = _psm.text or u'' + + _msnobj = s.find('UserTileLocation') + if msnobj is None and _msnobj is not None: + msnobj = _msnobj.text + + log.debug("processing presence for %r", name) + if None not in (name, status): + log.debug("\tpresence: friendly-name=%r, status=%r", nick, status) + if circle is None: + self.event('contact_online', name, nick or name, status, 0) + else: + names = ['%d:%s' % (contact.type, contact.account)] + self.event('circle_roster_recv', circle.account, contact.type, names, [], names, False) + + if psm is not None: + log.debug("\tpsm: %r", psm) + self.event('contact_status_msg', name, psm) + + if msnobj is not None: + try: + msnobj = msn.MSNObject.parse(msnobj) + except Exception: + msnobj = None + + log.debug("\ticon: %r", msnobj) + self.event('contact_icon_info', name, msnobj) + + def delete_user_nfy(self, name, ctype, payload, circle = None): + contact = self.contact_list.GetContact(name, type = ctype) + + if getattr(contact, 'IsMessengerUser', False) and \ + ctype != MSNAB.IMAddressInfoType.WindowsLive and \ + self.contact_list.HasContact(name, MSNAB.IMAddressInfoType.WindowsLive): + + log.info("Ignoring non-WindowsLive info about a windows live contact %r (contact = %r)", name, contact) + return + + epdata = getattr(contact, 'EndPointData', None) + + if payload is None: + log.info("Got no payload for NFY DEL: name = %r, ctype = %r, circle = %r", name, ctype, circle) + return + + for child in payload.findall('.//sep'): + epid_key = uuid.UUID(child.attrib['epid']) + epid_data = contact.EndPointData.get(epid_key, {}) + + if epid_data.pop(child.attrib['n'], None) is not None: + log.debug("removed user %s's nfy node: %r", name, etree.tostring(child)) + + if not epid_data: + contact.EndPointData.pop(epid_key, None) + + if payload.find('.//s[@n="IM"]') is not None and circle is None: + self.event('contact_offline', name, None, 'FLN', 0) + + elif circle is not None: + self.event('circle_roster_remove', circle.account, ctype, name) + + if name == self.self_buddy.name: + my_epid_nodes = payload.xpath('.//sep[@n="IM" and @epid="{%s}"]' % self.get_machine_guid()) + if my_epid_nodes: + log.critical("Being sent offline by another endpoint") + self.event('other_user') + + @deprecated + def send_blp(self): + # Deprecated + return + + @deprecated + def send_uux(self): + # Deprecated + return + + @deprecated + def send_prp(self): + return + + @deprecated + def send_xfr(self): + raise Exception() + return + + def recv_sdg(self, sdg): + msg = sdg.payload + + from_epid = msg.parts[0].get_param('epid', None, header = 'From') + if from_epid is None: + from_epid = uuid.UUID(int = 0) + else: + from_epid = uuid.UUID(from_epid) + + msg.parts[0].del_param('epid', header = 'From') + from_ = msg.parts[0].get('From') + from_type, from_name = from_.split(':', 1) + from_type = int(from_type) + + contact = self.contact_list.GetContact(from_name, from_type) + ver = P2P.Version.V1 if int(from_epid) == 0 else P2P.Version.V2 + + mtype = sdg.payload.get('Message-Type') + if mtype == 'Data': + self.recv_sdg_data(sdg, contact, from_epid, ver) + elif mtype == 'Signal/P2P': + self.recv_sdg_signalp2p(sdg, contact, from_epid, ver) + + else: + bname = sdg.payload.get('From').split(':', 1)[-1].split(';', 1)[0] + self.event('fed_message', from_name, sdg) + + def recv_sdg_data(self, sdg, contact, from_epid, ver): + msg = sdg.payload + + offsets = msg.get('Bridging-Offsets', None) + if offsets is None: + data_payloads = [msg.get_payload()] + else: + if offsets != '0': + log.info("Splitting incoming message according to offsets: %r", offsets) + + total_payload = msg.get_payload() + data_payloads = [] + offsets = map(int, offsets.split(',')) + start_end = zip(offsets, offsets[1:] + [-1]) + for start, end in start_end: + data_payloads.append(total_payload[start:end]) + + assert len(offsets) == len(data_payloads) + + pipe = msg.get('Pipe', None) + if pipe is not None: + try: pipe = int(pipe) + except: pass + else: + self.SDGBridge.packageNumber = pipe + + for data_payload in data_payloads: + p2pmessage = P2P.P2PMessage.P2PMessage(ver) + p2pmessage.ParseBytes(data_payload) + #_log.debug("Got P2PMessage: %r", p2pmessage) + self.P2PHandler.ProcessP2PMessage(self.SDGBridge, contact, from_epid, p2pmessage) + + def recv_sdg_signalp2p(self, sdg, contact, from_epid, ver): + msg = sdg.payload + slp_data = msg.get_payload() + import msn.P2P.MSNSLPMessages as SLP + slp = SLP.SLPMessage.Parse(slp_data) + + if slp is None: + log.error("Invalid SLP message in body: %r", str(msg)) + return + + if slp.ContentType in ("application/x-msnmsgr-transreqbody", + "application/x-msnmsgr-transrespbody", + "application/x-msnmsgr-transdestaddrupdate"): + + import msn.P2P.P2PSession as Session + Session.ProcessDirectInvite(slp, self, None) + + def recv_not(self, msg): + doc = etree.fromstring(msg.payload) + body = getattr(doc.find('.//MSG/BODY'), 'text', None) + if body is None: + return + + doc = etree.fromstring(body) + + # Maybe there's a circle with new people in it: + has_new_item = getattr(doc.find('./HasNewItem'), 'text', None) == 'true' + circle_id = getattr(doc.find('./CircleId'), 'text', None) + if has_new_item and circle_id is not None: + log.info("Got notification about updated circle (id = %r). syncing addresbook...", circle_id) + + self.RequestAddressBookById(abid = circle_id) + return + + elif getattr(doc.find('./Service'), 'text', None) == 'ABCHInternal': + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.ABChangeNotifyAlert) + return + + log.info("Unknown notification: %r", msg.payload) + + def request_sb(self): + self.event('switchboard_request', None, None) + + @callbacks.callsback + def AddNewContact(self, account, client_type = None, invitation = None, otheremail = None, callback = None): + + if client_type is None: + otheremail = None + client_type = self.determine_client_type(account) + + if self.contact_list.HasContact(account, client_type): + contact = self.contact_list.GetContact(account, type = client_type) + log.info("Already have this contact: %r = %r", account, contact) + if contact.OnPendingList: + self.AddPendingContact(contact, callback = callback) + elif not getattr(contact, 'contactId', None) or contact.contactId == str(uuid.UUID(int=0)) or not contact.HasList(MSNAB.MSNList.Forward): + self.AddNonPendingContact(account, client_type, invitation, otheremail, callback = callback) + elif contact.contactId and contact.contactId != str(uuid.UUID(int=0)): + if not contact.contactInfo.isMessengerUser: + log.info("Setting contact as messenger user") + contact.contactInfo.isMessengerUser = True + if not contact.OnBlockedList: + log.info("Putting contact on AL") + contact.OnAllowedList = True + self.contact_list.ContactAdded(contact, MSNAB.MSNList.Forward) + callback.success() + else: + log.warning("Cannot add contact: %r", contact.Hash) + else: + self.AddNonPendingContact(account, client_type, invitation, otheremail, callback = callback) + + @callbacks.callsback + def AddNonPendingContact(self, account, client_type, invitation = None, otheremail = None, callback = None): + log.info("Adding non-pending account: %r, %r", account, client_type) + ct = MSNAB.ClientType(client_type) + if '@' in account and ct == MSNAB.ClientType.PassportMember: + email = net.EmailAddress(account) + fed_query = etree.tostring( + B.E('ml', + B.E('d', + B.E('c', n = email.name), + n = email.domain) + ) + ) + self.socket.send(msn.Message('FQY', payload = fed_query), **defcb) + + def after_add_contact(resp = None, guid = None): + contact = self.contact_list.GetContact(account, type = ct) + + if resp is not None: + guid = resp.ABContactAddResult.Guid + + contact.contactId = guid + log.info("Non-pending contact added: %r", contact) + + if not contact.HasList(MSNAB.MSNList.Block): + # Add to AL + if ct == MSNAB.ClientType.PassportMember: + adls = self.make_adls({contact.Hash : int(MSNAB.MSNList.Allow) | int(MSNAB.MSNList.Forward)}) + self.send_adl_sequence(adls) + else: + self.contact_list.ContactAdded(contact, MSNAB.MSNList.Allow) + + self.contact_list.ContactAdded(contact, MSNAB.MSNList.Forward) + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.ContactSave, callback = callback) + + self.AddNewOrPendingContact(account, False, invitation, ct, otheremail, success = after_add_contact, error = callback.error) + + @callbacks.callsback + def AddPendingContact(self, contact, callback = None): + log.info("Adding pending contact: %r", contact) + self.RemoveContactFromList(contact, MSNAB.MSNList.Pending) + + def after_add_contact_rl(resp = None): + if not contact.OnBlockedList: + if MSNAB.ClientType(contact.type) == MSNAB.ClientType.EmailMember: + contact.OnAllowedList = True + else: + adls = self.make_adls({contact.Hash : int(MSNAB.MSNList.Allow)}) + self.send_adl_sequence(adls) + self.contact_list.ContactAdded(contact, MSNAB.MSNList.Allow) + + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.ContactMsgrAPI, callback = callback) + + def after_add_contact_fl(resp = None, guid = None): + if resp is None: + assert guid is not None + contact.contactId = guid + else: + assert guid is None + contact.contactId = resp.ABContactAddResult.Guid + + contact.OnForwardList = True + + if not contact.OnReverseList: + self.AddContactToList(contact, + MSNAB.MSNList.Reverse, + success = after_add_contact_rl) + else: + after_add_contact_rl(None) + + self.AddNewOrPendingContact(contact.Mail, True, None, MSNAB.ClientType(contact.type), None, + success = after_add_contact_fl, + error = callback.error) + + @callbacks.callsback + def AddContactToList(self, contact, list_int, callback = None): + role = MSNAB.MSNList(list_int) + + if role == MSNAB.MSNList.Pending: + return + + if contact.HasList(role): + return + + adls = self.make_adls({contact.Hash : int(role)}) + + def send_adl_and_update_contact_list(): + if role in (MSNAB.MSNList.Allow, MSNAB.MSNList.Block, MSNAB.MSNList.Forward): + self.send_adl_sequence(adls) + self.contact_list.ContactAdded(contact, str(role)) + + callback.success() + + if role == MSNAB.MSNList.Forward: + return send_adl_and_update_contact_list() + + ps = SOAPServices.PartnerScenario.ContactMsgrAPI if role == MSNAB.MSNList.Reverse else SOAPServices.PartnerScenario.BlockUnblock + + member = None + member_zsi = None + + def after_add(resp): + if self.address_book.HasMembership(MSNAB.ServiceFilters.Messenger, contact.account, contact.type, role.role): + log.warning("Contact already exists in %r. Not calling AddMembership") + contact.AddToList(role) + return send_adl_and_update_contact_list() + + self.contact_list.ContactAdded(contact, role) + self.address_book.AddMembership(MSNAB.ServiceFilters.Messenger, + contact.Mail, + contact.type, + role.role, + member if member is not None else MSNAB.Member.from_zsi(member_zsi), + MSNAB.Scenario.ContactServeAPI) + self.on_contact_add(contact.account, getattr(contact, 'contactId', contact.account), int(role), None) + ######## + send_adl_and_update_contact_list() + + def add_member_error(e): + if fault_check(e, 'Member already exists'): + log.debug("Member already exists on list %r. Calling succces", role) + after_add(None) + else: + callback.error() + + if getattr(contact, 'contactId', None) is not None and self.address_book.HasContact(contact.contactId): + log.warning("Already got this contact in the address book, not calling AddMember: %r") + member = self.address_book.SelectBaseMember(MSNAB.ServiceFilters.Messenger, contact.account, contact.type, role.role) + after_add(None) + else: + sharing = self.getService(SOAPServices.AppIDs.Sharing) + member_zsi = sharing.AddMember(client = self, contact = contact, role = role, success = after_add, error = add_member_error) + + @callbacks.callsback + def AddNewOrPendingContact(self, account, pending = False, invitation = None, network = None, otheremail = None, callback = None): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + ps = SOAPServices.PartnerScenario.ContactMsgrAPI if pending else SOAPServices.PartnerScenario.ContactSave + + def contact_add_error(e): + if fault_check(e, "Contact Already Exists"): + log.debug("Contact already exists. Calling succces") + try: + guid = getattr(getattr(e, 'Detail', None), 'AdditionalDetails', None).ConflictObjectId + except Exception: + try: + contact = self.contact_list.GetContact(account, type = self.determine_client_type(account, network)) + guid = contact.contactId + except Exception: + guid = None + + callback.success(guid = guid) + else: + callback.error() + + ab.ABContactAdd(client = self, + account = account, + pending = pending, + invitation = invitation, + network = network, + otheremail = otheremail, + PartnerScenario = ps, + success = callback.success, + error = contact_add_error) + + @callbacks.callsback + def RemoveContact(self, contact, callback = None): + empty_guid = str(uuid.UUID(int=0)) + if getattr(contact, 'contactId', empty_guid) == empty_guid: + return + + ab = self.getService(SOAPServices.AppIDs.AddressBook) + + def after_delete(resp): + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.ContactSave, callback = callback) + + ab.ABContactDelete(client = self, + contacts = [contact], + PartnerScenario = SOAPServices.PartnerScenario.Timer, + success = after_delete, + error = callback.error) + + @callbacks.callsback + def RemoveContactFromList(self, contact, list_int, callback = None): + role = MSNAB.MSNList(list_int) + + sharing = self.getService(SOAPServices.AppIDs.Sharing) + + log.info("Removing contact from list: %r / %r", contact, role) + + if role == MSNAB.MSNList.Reverse: + return + + if not contact.HasLists(role): + self.contact_list.ContactRemoved(contact, role) + return callback.success() + + rmls = self.make_adls({contact.Hash : int(role)}, False) + + def send_rml_and_update_contact_list(): + if role in (MSNAB.MSNList.Allow, MSNAB.MSNList.Block, MSNAB.MSNList.Forward, MSNAB.MSNList.Hidden): + self.send_rml_sequence(rmls) + + self.contact_list.ContactRemoved(contact, role) + + if role == MSNAB.MSNList.Forward or role == MSNAB.MSNList.Hidden: + log.info("No DeleteMember required for forward or hidden list. sending RML") + send_rml_and_update_contact_list() + callback.success() + return + + def delete_member_error(e): + log.info("Error calling DeleteMember: %r", e) + + if fault_check(e, "Member does not exist"): + log.debug("Member already deleted. Calling succces") + after_delete_member(None) + else: + callback.error() + + def after_delete_member(response): + log.info("DeleteMember completed for %r. sending RML", contact) + self.contact_list.ContactRemoved(contact, role) + self.address_book.RemoveMembership(MSNAB.ServiceFilters.Messenger, contact.Mail, contact.type, role) + send_rml_and_update_contact_list() + callback.success() + + ps = SOAPServices.PartnerScenario.ContactMsgrAPI if (role == MSNAB.MSNList.Pending) else SOAPServices.PartnerScenario.BlockUnblock + + sharing = self.getService(SOAPServices.AppIDs.Sharing) + sharing.DeleteMember(client = self, + contact_type = contact.type, + contact_mail = contact.account, + role = role.role, + PartnerScenario = ps, + success = after_delete_member, + error = delete_member_error) + + @callbacks.callsback + def BlockContact(self, contact, callback = None): + + if contact.type == MSNAB.IMAddressInfoType.Circle: + return self.BlockCircle(contact, callback = callback) + + def after_al_remove(resp = None): + if not contact.OnBlockedList: + self.AddContactToList(contact, MSNAB.MSNList.Block, callback = callback) + else: + callback.success() + + if contact.OnAllowedList: + self.RemoveContactFromList(contact, + MSNAB.MSNList.Allow, + success = after_al_remove, + error = callback.error) + elif not contact.OnBlockedList: + after_al_remove() + + @callbacks.callsback + def _block_buddy(self, buddy, callback = None): + contact = self.contact_list.GetContact(buddy.name, type = self.determine_client_type(buddy.name, buddy.service)) + if contact is None: + log.error("No contact with name = %r found", buddy.name) + else: + return self.BlockContact(contact, callback = callback) + + @callbacks.callsback + def UnblockContact(self, contact, callback = None): + if contact.type == MSNAB.IMAddressInfoType.Circle: + return self.UnblockCircle(contact, callback = callback) + + def after_bl_remove(resp = None): + if not contact.OnAllowedList: + self.AddContactToList(contact, MSNAB.MSNList.Allow, callback = callback) + else: + callback.success() + + if contact.OnBlockedList: + self.RemoveContactFromList(contact, + MSNAB.MSNList.Block, + success = after_bl_remove, + error = callback.error) + + elif not contact.OnAllowedList: + after_bl_remove() + + @callbacks.callsback + def _unblock_buddy(self, buddy, callback=None): + contact = self.contact_list.GetContact(buddy.name, type = self.determine_client_type(buddy.name, buddy.service)) + if contact is None: + log.error("No contact with name = %r found", buddy.name) + else: + return self.UnblockContact(contact, callback = callback) + + @callbacks.callsback + def _authorize_buddy(self, buddy, authorize, callback=None): + + def after_block(*a): + self.AddPendingContact(self.contact_list.GetContact(buddy.name, type = self.determine_client_type(buddy.name, buddy.service)), callback = callback) + self.event('buddy_authed', buddy, authorize) + + if not authorize: + self._block_buddy(buddy, success = after_block, error = after_block) + else: + after_block() + + @callbacks.callsback + def BlockCircle(self, circle, callback = None): + if circle.OnBlockedList: + return + + def after_circle_block(resp): + self.SendBlockCircleNSCommands(circle.AddressBookId, circle.HostDomain) + self.contact_list.CircleRemoved(circle, MSNAB.MSNList.Allow) + self.contact_list.CircleAdded(circle, MSNAB.MSNList.Block) + self.contact_list.SetCircleStatus(circle, 'Offline') + callback.success() + + self.AddContactToList(circle, + MSNAB.MSNList.BL, + success = after_circle_block, + error = callback.error) + + @callbacks.callsback + def UnblockCircle(self, circle, callback = None): + if not circle.OnBlockedList: + return + + def after_circle_unblock(resp): + self.SendUnblockCircleNSCommands(circle.AddressBookId, circle.HostDomain) + self.contact_list.CircleRemoved(circle, MSNAB.MSNList.Block) + self.contact_list.CircleAdded(circle, MSNAB.MSNList.Allow) + callback.success() + + self.RemoveContactFromList(circle, + MSNAB.MSNList.BL, + success = after_circle_unblock, + error = callback.error) + + def circle_removed(self, circleId): + circle = self.CircleList.pop(circleId, None) + if circle is None: + return + + self.contact_remove(circle.account, str(MSNAB.MSNList.Forward), None) + + @callbacks.callsback + def _add_buddy(self, lid, bname, bid, gid, service = 'msn', callback = None): + # sharing.AddMember + call _add_buddy_to_list and/or _add_buddy_to_group + bname = getattr(bname, 'name', bname) + contact = self.contact_list.GetContact(bname, type = self.determine_client_type(bname, service)) + + log.debug("_add_buddy(%r, %r, %r, %r)", lid, bname, bid, gid) + + if gid is not None: + def after_add_member(resp = None): + return self._add_buddy_to_group(bname, bid, gid, service = service, callback = callback) + else: + def after_add_member(resp = None): + callback.success() + + def after_add_contact(resp = None): + contact = self.contact_list.GetContact(bname, type = self.determine_client_type(bname, service = service)) + log.info("Contact successfully added to AddressBook. Adding to list=%r (contact = %r)", lid, contact) + self.AddContactToList(contact, + lid, + success = after_add_member, + error = callback.error) + + if lid in ('FL', 'Forward', 1, None): + self._add_buddy_to_list(bname, + service = service, + success = after_add_member, + error = callback.error) + else: + after_add_contact() + + def determine_client_type(self, bname, service = 'msn'): + ctype = MSNAB.ClientType(service) + if ctype is not None: + return ctype + + if service == 'msn': + if common.sms.validate_sms(bname): + bname = common.normalize_sms(bname) + return MSNAB.ClientType.PhoneMember + else: + return MSNAB.ClientType.PassportMember + elif service == 'yahoo': + return MSNAB.ClientType.EmailMember + + @callbacks.callsback + def _add_buddy_to_list(self, bname, service = 'msn', callback=None): + # ab.ABContactAdd + self.AddNewContact(bname, client_type = self.determine_client_type(bname, service), callback = callback) + + @callbacks.callsback + def _add_buddy_to_group(self, bname, bid, gid, service = 'msn', callback = None): + # ab.ABGroupContactAdd + contact = self.contact_list.GetContact(bname, type = self.determine_client_type(bname, service)) + self.AddContactToGroup(contact, gid, callback = callback) + + @callbacks.callsback + def _remove_buddy(self, lid, buddy, group, service = 'msn', callback=None): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + bname = getattr(buddy, 'name', buddy) + contact = self.contact_list.GetContact(bname, type = self.determine_client_type(bname, service)) + + role = MSNAB.MSNList(lid) + + def after_contact_remove(*a, **k): + self.RemoveContactFromList(contact, int(role), + success = lambda resp = None: self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.ContactSave, callback = callback), + error = callback.error) + + if role != MSNAB.MSNList.Forward: + # Removing from AL / BL / PL + return after_contact_remove() + + def contact_remove_error(e): + log.info("error removing buddy from contact list: %r", e) + if fault_check(e, 'Contact Does Not Exist'): + log.info("Got fault from ABContactDelete stating contact already deleted. Continuing remove process") + return after_contact_remove() + callback.error() + + def after_group_remove(*a, **k): + log.info("buddy removed from group. now removing from contact list: %r", contact) + self.RemoveContact(contact, + success = after_contact_remove, + error = contact_remove_error) + + if group is None: + # Removing from contact list + return after_group_remove() + else: + # removing from group + self._remove_buddy_from_group(buddy, buddy.id, group, success = after_group_remove, error = callback.error) + +# @callbacks.callsback +# def _remove_buddy_from_list(self, buddy, lid, callback = None): +# log.info("remove buddy from list: %r, %r", contact, lid) +# bname = getattr(buddy, 'name', buddy) +# contact = self.contact_list.GetContact(bname, type = self.determine_client_type(bname, buddy.service)) +# self.RemoveContactFromList(contact, lid, callback = callback) + + @callbacks.callsback + def _remove_buddy_from_group(self, name, bid, g_id, service = 'msn', callback = None): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + + bname = getattr(name, 'name', name) + contact = self.contact_list.GetContact(bname, type = self.determine_client_type(bname, service)) + + def gcd_success(response): + log.info("buddy removed from group. %r, %r", contact, g_id) + if contact.client is None: + contact.client = self + self.contact_list.ContactRemoved(contact, MSNAB.MSNList.Forward, groups = [g_id]) + callback.success() + + if g_id is None: + return gcd_success(None) + + def gcd_error(e, *a,**k): + if fault_check(e, 'Contact Does Not Exist'): + log.debug("Contact already removed from group.") + return gcd_success(None) + else: + log.info("error removing buddy from group: %r, %r", a, k) + callback.error() + + log.info("removing buddy from group. %r, %r", contact, g_id) + ab.ABGroupContactDelete(client = self, + contact = contact, + group_id = g_id, + success = gcd_success, + error = gcd_error) + + + @callbacks.callsback + def UpdateContact(self, contact, abid = empty_guid, callback = None): + abid = abid.lower() + if getattr(contact, 'contactId', empty_guid) == empty_guid: + return + + if not self.address_book.HasContact(abid, contact.contactId): + return + + is_messenger_user = contact.contactInfo.isMessengerUser + + old_contact = self.address_book.SelectContactFromAddressBook(abid, contact.contactId) + + def after_update(resp): + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.ContactSave, callback = callback) + + ab = self.getService(SOAPServices.AppIDs.AddressBook) + ab.ABContactUpdate(client = self, + old_contact = old_contact, + new_contact = contact, + PartnerScenario = SOAPServices.PartnerScenario.ContactSave if is_messenger_user else SOAPServices.PartnerScenario.Timer, + success = after_update, + error = callback.error) + + @callbacks.callsback + def AddContactGroup(self, name, callback = None): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + + def after_group_add(resp): + new_group = self.group_add(name, resp.ABGroupAddResult.Guid) + callback.success(new_group) + + def group_add_error(e): + if fault_check(e, 'Group Already Exists'): + log.info("Got fault from ABGroupAdd stating group already existed. Continuing add process") + return after_group_add() + else: + callback.error() + + ab.ABGroupAdd(client = self, + name = name, + PartnerScenario = SOAPServices.PartnerScenario.GroupSave, + success = after_group_add, + error = group_add_error) + + _add_group = AddContactGroup + + @callbacks.callsback + def RemoveContactGroup(self, group, callback = None): + g_id = getattr(group, 'id', group) + ab = self.getService(SOAPServices.AppIDs.AddressBook) + + def after_remove(resp): + self.group_remove(g_id) + self.address_book.groups.pop(g_id, None) + callback.success() + + ab.ABGroupDelete(client = self, + group_id = g_id, + PartnerScenario = SOAPServices.PartnerScenario.Timer, + success = after_remove, + error = callback.error) + + _remove_group = RemoveContactGroup + + @callbacks.callsback + def RenameGroup(self, group, new_name, callback = None): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + + g_id = getattr(group, 'id', group) + + def after_update(resp): + self.group_rename(g_id, new_name) + callback.success() + + ab.ABGroupUpdate(client = self, group_id = g_id, name = new_name, + PartnerScenario = SOAPServices.PartnerScenario.GroupSave, + success = after_update, error = callback.error) + + _rename_group = RenameGroup + + @callbacks.callsback + def AddContactToGroup(self, contact, group, callback = None): + ab = self.getService(SOAPServices.AppIDs.AddressBook) + g_id = getattr(group, 'id', group) + + def after_add(resp): + self.contact_list.ContactAdded(contact, MSNAB.MSNList.Forward, groups = [g_id]) + callback.success() + + ab.ABGroupContactAdd(client = self, + contact = contact, + group_id = g_id, + PartnerScenario = SOAPServices.PartnerScenario.GroupSave, + success = after_add, + error = callback.error) + + @callbacks.callsback + def RemoveContactFromGroup(self, contact, group, callback = None): + g_id = getattr(group, 'id', group) + ab = self.getService(SOAPServices.AppIDs.AddressBook) + + def after_remove(resp): + self.contact_list.ContactRemoved(contact, MSNAB.MSNList.Forward, groups = [g_id]) + callback.success() + + ab.ABGroupContactDelete(client = self, + contact = contact, + group_id = g_id, + PartnerScenario = SOAPServices.PartnerScenario.GroupSave, + success = after_remove, + error = callback.error) + + + def RequestAddressBookById(self, abid, relationshipState = None, scenario = None): + if relationshipState == MSNAB.RelationshipStates.Accepted: + self.requestCircleCount += 1 + + if relationshipState not in (None, MSNAB.RelationshipStates.Accepted, MSNAB.RelationshipStates.WaitingResponse): + return + + self._sync_addressbook(abid = abid, PartnerScenario = SOAPServices.PartnerScenario.Initial) + + def GetCircle(self, name): + return self.CircleList.get(MSNAB.Circle.MakeHash(name), None) + + @callbacks.callsback + def InviteCircleMember(self, circleId, bname, message = None, callback = None): + circle = self.GetCircle(circleId) + if circle is not None: + circleId = circle.abid # GetCircle accepts a couple of formats, but from here on out we just want the GUID + else: + uuid.UUID(circleId) + + if circle.OnBlockedList: + log.error("Circle is blocked") + return callback.error() + + if circle.CircleRole not in (MSNAB.CirclePersonalMembershipRole.Admin, MSNAB.CirclePersonalMembershipRole.AssistantAdmin): + log.error("You are not an administrator") + return callback.error() + + def after_manage(resp): + log.info("invitation complete (invite %r to %r)", bname, circleId) + callback.success() + + def manage_error(e): + log.error("error occurred managing WL connection: %r", e) + callback.error() + + def after_contact_create(resp): + log.info("Created contact, now managing WL connection") + + annos = [] + if message is not None: + annos.append((MSNAB.AnnotationNames.MSN_IM_InviteMessage, message)) + + ab.ManageWLConnection(client = self, + contactId = str(resp.CreateContactResult.ContactId).lower(), + connection = True, + presence = False, + action = 1, + relationshipRole = MSNAB.CirclePersonalMembershipRole.to_int(MSNAB.CirclePersonalMembershipRole.Member), + relationshipType = MSNAB.RelationshipTypes.CircleGroup, + PartnerScenario = SOAPServices.PartnerScenario.CircleInvite, + Annotations = annos, + abid = circleId, + success = after_manage, + error = manage_error) + + def contact_create_error(e): + log.error("Error creating contact: %r", e) + callback.error() + + ab = self.getService(SOAPServices.AppIDs.AddressBook) + ab.CreateContact(client = self, abid = circleId, email = bname, + success = after_contact_create, + error = contact_create_error) + + @callbacks.callsback + def CreateContact(self, email = None, abid = None, callback = None): + + def create_contact_success(resp): + callback.success() + + def create_contact_error(e = None): + callback.error() + + sharing = self.getService(SOAPServices.AppIDs.Sharing) + sharing.CreateContact(client = self, email = email, abid = abid, + success = create_contact_success, + error = create_contact_error,) + + @callbacks.callsback + def CreateCircle(self, name, callback = None): + + def after_create_circle(resp = None): + circleId = resp.CreateCircleResult.Id + self.address_book.PendingCreateCircleList[resp.CreateCircleResult.Id] = name + + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.JoinedCircleDuringPush, + success = lambda: self.InviteCircleMember(circleId, self.self_buddy.name, callback = callback)) + + def create_circle_error(e = None): + callback.error() + + sharing = self.getService(SOAPServices.AppIDs.Sharing) + sharing.CreateCircle(client = self, + displayName = name, + PartnerScenario = SOAPServices.PartnerScenario.CircleSave, + success = after_create_circle, + error = create_circle_error, + ) + + @callbacks.callsback + def LeaveCircle(self, circle_id, callback = None): + circle = self.GetCircle(circle_id) + + def after_leave_circle(resp): + self.SendCircleNotifyRML(circle.abid, circle.hostDomain, circle.Lists, True) + self.address_book.RemoveCircle(circle.CID, circle.abid) + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.ABChangeNotifyAlert, callback = callback) + + ab = self.getService(SOAPServices.AppIDs.AddressBook) + ab.BreakConnection(client = self, + abid = circle.abid, + contactId = self.address_book.SelectSelfContactGuid(circle.abid), + PartnerScenario = SOAPServices.PartnerScenario.CircleLeave, + success = after_leave_circle, + ) + + def SendCircleNotifyRML(self, abid, hostDomain, lists, block): + li = 0 + for l in lists: + li |= int(MSNAB.MSNList(l)) + + circleHash = MSNAB.Circle.MakeHash(abid, hostDomain) + rmls = self.make_adls({circleHash : int(li)}, not block) + self.send_adl_sequence(rmls, 'RML') + + def OnJoinCircle(self, circle): + log.info("Got new circle: %r", circle) + self.on_contact_add(circle.account, circle.account, int(MSNAB.MSNList('FL')) | int(MSNAB.MSNList('RL')), None) + + if self.sent_initial_adls: + self.send_initial_adl(MSNAB.Scenario.SendInitialCirclesADL) + + def OnRecvCircle(self, circle): + log.info("Got existing circle: %r", circle) + self.recv_contact(circle.account, int(MSNAB.MSNList('FL')) | int(MSNAB.MSNList('RL')), None, None, circle.account) + + if self.sent_initial_adls: + self.send_initial_adl(MSNAB.Scenario.SendInitialCirclesADL) + + def OnCircleInvite(self, circle, inviter): + log.debug("Circle Invite: inviter = %r, circle = %r", inviter, circle) + + def cb(join): + if join: + self._accept_circle_invite(circle, inviter) + else: + self._decline_circle_invite(circle, inviter) + + hooks.notify("digsby.msn.circle_invite", circle, inviter, cb) + + def _accept_circle_invite(self, circle, inviter): + if circle.CircleRole != MSNAB.CirclePersonalMembershipRole.StatePendingOutbound: + log.error("Not a pending circle! %r", circle) + return + + def accept_invite_success(resp): + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.JoinedCircleDuringPush) + + def accept_invite_error(e): + log.error("Error accepting circle invite: %r", e) + + ab = self.getService(SOAPServices.AppIDs.AddressBook) + ab.ManageWLConnection(client = self, + contactId = str(circle.hiddenRep.contactId).lower(), + connection = True, + presence = False, + action = 1, + relationshipRole = MSNAB.CirclePersonalMembershipRole.to_int(MSNAB.CirclePersonalMembershipRole.NONE), + relationshipType = MSNAB.RelationshipTypes.CircleGroup, + PartnerScenario = SOAPServices.PartnerScenario.CircleStatus, + success = accept_invite_success, + error = accept_invite_error) + + def _decline_circle_invite(self, circle, inviter): + def decline_invite_success(resp): + self._sync_addressbook(PartnerScenario = SOAPServices.PartnerScenario.JoinedCircleDuringPush) + + def decline_invite_error(e): + log.error("Error declining circle invite: %r", e) + + ab = self.getService(SOAPServices.AppIDs.AddressBook) + ab.ManageWLConnection(client = self, + contactId = str(circle.hiddenRep.contactId).lower(), + connection = True, + presence = False, + action = 2, + relationshipRole = MSNAB.CirclePersonalMembershipRole.to_int(MSNAB.CirclePersonalMembershipRole.NONE), + relationshipType = MSNAB.RelationshipTypes.CircleGroup, + PartnerScenario = SOAPServices.PartnerScenario.CircleStatus, + success = decline_invite_success, + error = decline_invite_error) + + @Events.event + def on_circle_member_joined(self, circle, contact): + log.info("on_circle_member_joined(%r, %r)", circle, contact) + return circle.account, contact.account + + @Events.event + def recv_contact(self, name, lists_int, group_ids, soap = None, id = None): + pass + + @callbacks.callsback + def make_temp_circle(self, callback = None): + message_headers = ( + (('Routing', '1.0'), + ('To', '%d:%s@%s' % (MSNAB.IMAddressInfoType.TemporaryGroup, self.contact_list.abid, MSNAB.Circle.hostDomain)), + ('From', '%d:%s;epid={%s}' % (MSNAB.IMAddressInfoType.WindowsLive, self.self_buddy.name, str(self.get_machine_guid()))), + ), + (('Reliability', '1.0'), + ('Stream', '0'), + ('Segment', '0'), + ), + (('Publication', '1.0'), + ('Uri', '/circle'), + ('Content-Type', 'application/multiparty+xml'), + ), + ) + + def success(msg): + circle_id = email.message_from_string(msg.payload).get('From').split(':', 1)[-1] + self.JoinCircleConversation(circle_id) + callback.success(circle_id) + + payload = str(MSNC.MultiPartMime(headers = message_headers, body = '')) + self.send_put(payload, success = success, error = callback.error) + + def leave_temp_circle(self, circle_id): + message_headers = ( + (('Routing', '1.0'), + ('To', '%d:%s' % (MSNAB.IMAddressInfoType.TemporaryGroup, circle_id)), + ('From', '%d:%s;epid={%s}' % (MSNAB.IMAddressInfoType.WindowsLive, self.self_buddy.name, str(self.get_machine_guid()))), + ), + (('Reliability', '1.0'), + ('Stream', '0'), + ('Segment', '1'), + ), + (('Publication', '1.0'), + ('Uri', '/circle/roster(IM)/user(%d:%s)' % (MSNAB.IMAddressInfoType.WindowsLive, self.self_buddy.name)), + ('Content-Type', 'application/circles+xml'), + ), + ) + + payload = str(MSNC.MultiPartMime(message_headers, body = '')) + self.send_del(payload) + + def JoinCircleConversation(self, circleId): + return self.invite_to_circle(circleId, self.self_buddy.name) + + def invite_to_circle(self, circle_id, bname, message = None): + log.info("Invite %r to %r", bname, circle_id) + circle = self.GetCircle(circle_id) + contact = self.contact_list.GetContact(bname) + + if circle is None: + circle_type = MSNAB.IMAddressInfoType.TemporaryGroup + circle_name, hostDomain = circle_id.lower().split('@') + else: + if circle.ADLCount != 0: + circle.pending_join = True + if bname != self.self_buddy.name: + log.error("Circle invite for %r to %r has been lost", bname, circle_id) + return + + circle_type = circle.type + circle_name = circle.abid.lower() + hostDomain = circle.hostDomain + + def do_ns_invite(): + log.info("Perform invite %r to %r", bname, circle_id) + message_headers = ( + (('Routing', '1.0'), + ('From', '%d:%s;epid={%s}' % (MSNAB.IMAddressInfoType.WindowsLive, self.self_buddy.name, str(self.get_machine_guid()))), + ('To', '%d:%s@%s' % (circle_type, str(circle_name).lower(), hostDomain)), + ), + (('Reliability', '1.0'), + ('Segment', '0' if self.self_buddy.name == bname else '1'), + ('Stream', '0'), + ), + (('Publication', '1.0'), + ('Content-Type', "application/circles+xml"), + ('Uri', '/circle'), + ), + ) + + payload_doc = B.E('circle', + B.E('roster', + B.E('id', 'IM'), + B.E('user', + B.E('id', '%d:%s' % (contact.type, contact.account))))) + + payload_data = etree.tostring(payload_doc, encoding = 'utf8') + self.send_put(str(MSNC.MultiPartMime(message_headers, payload_data))) + + if circle_type == MSNAB.IMAddressInfoType.TemporaryGroup: + return do_ns_invite() + + elif bname == self.self_buddy.name: + return do_ns_invite() + else: + self.InviteCircleMember(circle_id, bname, message = message) + + def appear_offline_to(self, buddy): + name = buddy.name + c = self.contact_list.GetContact(buddy.name, type = self.determine_client_type(buddy.name, buddy.service)) + c.AddToList(MSNAB.MSNList.Hidden) + adls = self.make_adls({c.Hash : int(MSNAB.MSNList.Hidden)}) + self.send_adl_sequence(adls) + + def appear_online_to(self, buddy): + name = buddy.name + c = self.contact_list.GetContact(buddy.name, type = self.determine_client_type(buddy.name, buddy.service)) + c.RemoveFromList(MSNAB.MSNList.Hidden) + rmls = self.make_adls({c.Hash : int(MSNAB.MSNList.Hidden)}) + self.send_adl_sequence(rmls, cmd = 'RML') + + def get_auth_message_for(self, buddy): + c = self.contact_list.GetContact(buddy.name, type = self.determine_client_type(buddy.name, buddy.service)) + if c is None: + return "" + + if not self.address_book.HasMembership(MSNAB.ServiceFilters.Messenger, c.account, c.type, MSNAB.MSNList.Pending.role): + return '' + + member = self.address_book.SelectBaseMember(MSNAB.ServiceFilters.Messenger, c.account, c.type, MSNAB.MSNList.Pending.role) + + message = member.GetAnnotation(MSNAB.AnnotationNames.MSN_IM_InviteMessage, '') + if message.startswith('"') and message.endswith('"'): + message = message[1:-1] + + return message + + def _set_buddy_icon(self, status, clientid, icon_data, callback = None): +# super(MSNP21Notification, self)._set_buddy_icon(status, clientid, icon_data, callback) + + finish = lambda *a: super(MSNP21Notification, self)._set_buddy_icon(status, clientid, icon_data, callback) + + storage = self.getSsoService(SOAPServices.SsoDomains.Storage) + storage.UpdateDocument(client = self, resource_id = self.getProfileAttr('ExpressionProfile.Photo.ResourceID'), + docstream = dict(xsitype = 'PhotoStream', + type = 'UserTileStatic', + mimetype = 'image/png', + data = icon_data), + scenario = SOAPServices.PartnerScenario.SyncChangesToServer, + success = finish, + error = finish) + + def getProfileAttr(self, attr): + return operator.attrgetter(attr)(self.profile) + +def split_contact_hash(a): + return util.flatten(x.split(';via=') for x in a.split(':', 1)) + +def cmp_contact_domain(a, b): + a_split = split_contact_hash(a) + b_split = split_contact_hash(b) + + if len(a_split) == 0: + return 1 + elif len(b_split) == 0: + return -1 + + a_name = a_split[1] + b_name = b_split[1] + + return cmp(list(reversed(a_name.split('@', 1))), list(reversed(b_name.split('@', 1)))) + + +def fault_check(fault, text): + if hasattr(fault, 'typecode'): + # ZSI object + if fault.typecode.pname == 'Fault': + Text = fault.Reason.Text + if not isinstance(Text, list): + Text = [Text] + + if any(text in T for T in Text): + return True + return False + diff --git a/digsby/src/msn/p21/MSNP21Switchboard.py b/digsby/src/msn/p21/MSNP21Switchboard.py new file mode 100644 index 0000000..10b945f --- /dev/null +++ b/digsby/src/msn/p21/MSNP21Switchboard.py @@ -0,0 +1,48 @@ +import util.Events as Events +import msn.MSNCommands as MSNCommands + +import logging +log = logging.getLogger("msn.p21.sb") + +import uuid +import hashlib +import sysident + +import msn +import msn.MSNCommands as MSNC +from msn.p15 import Switchboard as Super + +defcb = dict(trid=True, callback=sentinel) + +class MSNP21Switchboard(msn.NSSBAdapter): + def __init__(self, ns, to_invite = (), **kw): + raise Exception() + super(MSNP21Switchboard, self).__init__(ns, to_invite) + + @classmethod + def incoming(cls, msg): + log.info("incoming: %r", msg) + name = msg.name + + for x in cls._instances: + if x._chatbuddy == name: + # match + break + + else: + assert False + x = cls(None) + + x.buddy_join(name) + x.recv_msg(msg) + + def recv_msg_control_typing(self, msg): + self.event('typing_info', msg.name, True) + + def recv_msg_unknown(self, msg): + log.info("Got an unknown message: %r (%r)", msg, str(msg)) + + def recv_msg_text(self, msg): + textmsg = msn.MSNTextMessage.from_net(msg.payload) + self.event('recv_text_msg', msg.name, textmsg) + diff --git a/digsby/src/msn/p21/__init__.py b/digsby/src/msn/p21/__init__.py new file mode 100644 index 0000000..10faf9d --- /dev/null +++ b/digsby/src/msn/p21/__init__.py @@ -0,0 +1,3 @@ +from MSNP21Switchboard import MSNP21Switchboard as Switchboard +from MSNP21Notification import MSNP21Notification as Notification + diff --git a/digsby/src/msn/p8/MSNP8Notification.py b/digsby/src/msn/p8/MSNP8Notification.py new file mode 100644 index 0000000..55d4fcd --- /dev/null +++ b/digsby/src/msn/p8/MSNP8Notification.py @@ -0,0 +1,653 @@ +from hashlib import md5 +from logging import getLogger +log = getLogger('msn.p8.ns') + +import struct + +import util +import util.xml_tag +from util import callsback, pythonize, myip, srv_str_to_tuple, fmt_to_dict +from util.primitives.funcs import do, get +from rfc822 import Message as RfcMsg + +import msn +from msn.p import Notification as Super +from msn import Message +from util.Events import event + +defcb = dict(trid=True, callback=sentinel) + + +class GroupId(int): + ''' + GroupIds in MSNP8 are a number + ''' + def __repr__(self): + return '' % int.__repr__(self) + + def __eq__(self, other): + return str(other) == str(self) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(int(self)) + +class MSNP8Notification(Super): + + events = Super.events | set(( + 'on_contact_add', + 'on_auth_success', + 'on_recv_profile', + 'on_rl_notify', + 'on_blist_privacy', + 'recv_prop', + 'recv_contact', + 'recv_status', + 'recv_clientid', + 'challenge', + 'challenge_success', + 'connection_close', + 'other_user', + + 'initial_mail', + 'subsequent_mail', + + 'contact_list_details', + + 'contact_remove', + 'contact_alias', + 'contact_offline', + 'contact_online_initial', + 'contact_online', + + 'group_remove', + 'group_add', + 'group_rename', + 'group_receive', + + 'ping_response', + + 'switchboard_invite', + 'switchboard_request', + 'sb_request_error', + + )) + + versions = ['MSNP8'] + client_chl_id = 'msmsgs@msnmsgr.com' + client_chl_code = 'Q1P7W2E4J9R8U3S5' + + def complete_auth(self, *tick): + self.ticket = tick[0] + + if len(tick) is 3: + self.tokens = tick[-1] + tick = tick[:-1] + log.info('Got tokens: %r', self.tokens) + else: + self.tokens = {} + + self.send_usr_s(*tick) + + + def get_ticket(self, key=None): + if key is None: + return self.ticket + + else: + return fmt_to_dict('&','=')(self.ticket)[key] + + + def send_usr_s(self, *tickets): + """ + send_usr_s(socket) + + send USeR (Subsequent) to socket to (hopefully) finish authentication + """ + log.debug('sending usr_s') + + split = tuple(tickets[0][2:].split('&p=')) + + self.cookie = 'MSPAuth=%s' % split[0] + + if split[1].strip(): + self.cookie += '; MSPProf=%s' % split[1] + + self.socket.send(Message('usr', self._auth_type, 'S', *tickets), **defcb) + + def on_auth_error(self, error): + log.error(error) + raise Exception(error) + + def on_complete_auth(self, msg): + auth_type, username, mfn, verified = msg.args[:4] + assert username == self.self_buddy.name + self.self_buddy.remote_alias = mfn.decode('url').decode('fuzzy utf8') or None + self.self_buddy.verified = bool(int(verified)) + + Super.on_complete_auth(self, msg) + + def request_sb(self): + if self.socket is None: + self.recv_xfr_error(Exception('No NS socket. Request during shutdown?')) + else: + self.send_xfr() + + def send_xfr(self): + self.socket.send(Message('XFR', 'SB'), trid = True, error = lambda sck,msg: self.recv_xfr_error(msg)) + + def send_syn(self): + log.debug('sending syn') + self.socket.send(Message('SYN', '0'), **defcb) + + def recv_ver(self, msg): + self.event('on_require_auth') + + def recv_syn(self, msg): + log.debug('got syn') + ver, num_buddies, num_groups = msg.args + + self.event('contact_list_details', int(num_buddies), int(num_groups)) + + def recv_msg(self, msg): + try: + getattr(self, 'recv_msg_%s' % msg.type, self.recv_msg_unknown)(msg) + except Exception as e: + import traceback + traceback.print_exc() + + log.error('Exception handling MSG: %r, msg = %r', e, msg) + + def recv_msg_unknown(self, msg): + log.warning('Got an unknown type of MSG message: %r', msg) + + def recv_msg_profile(self, msg): + log.debug('got msg_payload_profile') + self.event('on_recv_profile', msg.payload) + + def recv_msg_init_email(self, msg): + + msg.contents = RfcMsg(msg.payload.body()) + + inbox = int(msg.contents['Inbox-Unread']) + other = int(msg.contents['Folders-Unread']) + + log.info('initial mail notification: %d in inbox, %d in folders', + inbox, other) + + self.event('initial_mail', (inbox, other)) + + def recv_msg_new_email(self, msg_obj): + log.info('Got a new mail message notification: %r', msg_obj) + self.event('subsequent_mail', 1) + + def recv_out(self, msg): + log.debug('got out') + if msg.reason == 'OTH': + self.event('other_user') + else: + self.event('connection_close') + log.error('Unimplemented OUT reason: %r', msg.reason) + + def recv_gtc(self, msg): + log.debug('got gtc') + self.event('on_rl_notify', msg.args[0] == 'A') + + def recv_blp(self, msg): + log.debug('got blp') + + v = msg.args[0] == 'AL' + + self.allow_unknown_contacts = v + self.event('on_blist_privacy', v) + + def recv_lsg(self, msg): + log.debug('got lsg') + msg.args = (msg.trid,) + msg.args + group_id, name = msg.args[:2] + + self.event('group_receive', name.decode('url').decode('fuzzy utf8'), GroupId(group_id)) + + def recv_prp(self, msg): + log.debug('got prp') + type, val = msg.args + type = pythonize(type) + self.event('recv_prop', self._username, type, val) + + def recv_bpr(self, msg): + log.debug('got bpr: %r', msg) + args = list(msg.args) + # 4 different forms: + # + # BPR HSB 1 + # BPR name@domain.com HSB 1 + # BPR PHM tel:+5551234567 0 + # BPR name@domain.com PHM tel:+5551234567 0 + # + prop_or_name = args.pop(0) + if prop_or_name.lower() in self.props: + name = self._last_bname + prop = prop_or_name + else: + name = prop_or_name + prop = args.pop(0) + + val = args.pop(0) + + self.event('recv_prop', name, prop, val, args) + + def recv_lst(self, msg): + """ + LST (LiST item) + + This command comes from the server after SYN, and gives you info + about who is on your buddy list (and where). + """ + log.debug('got lst') + + (name, nick, list_flags), groups = msg.args[:3], (msg.args[3:] or [''])[0] + list_flags = int(list_flags) + nick = nick.decode('url').decode('fuzzy utf8') or None + + if groups: + groups = map(GroupId, groups.split(',')) + else: + groups = [] + + self._last_bname = name + + self.event('contact_alias', name, nick) + self.event('recv_contact', name, list_flags, groups) + + def recv_chg(self, msg): + log.debug('got chg') + status = msg.args.pop(0) + id = msg.args.pop(0) + + self.event('recv_status', status) + self.event('recv_clientid', self.parse_caps(id)) + + def _parse_iln_nln(self, msg): + log.debug('got iln/nln') + (status, name, nick, client_id), __args = msg.args[:4], (msg.args[4:] or []) + + nick = nick.decode('url').decode('fuzzy utf8') or None + client_id = self.parse_caps(client_id) + + return name, nick, status, client_id + + def parse_caps(self, caps): + return int(caps) + + def recv_iln(self, msg): + """ + ILN (Initial onLiNe) + Tells us that a buddy was online before us + + NLN (oNLiNe) + Tells us that a buddy has signed on or changed thier status. + """ + + args = self._parse_iln_nln(msg) + self.event('contact_online_initial', *args) + + def recv_nln(self, msg): + args = self._parse_iln_nln(msg) + self.event('contact_online', *args) + + def recv_fln(self, msg): + log.debug('got fln') + name = msg.args[0] + nick = None + status = 'FLN' + client_id = 0 + self.event('contact_offline', name, nick, status, client_id) + + def recv_chl(self, msg): + log.debug('got chl') + self.event('challenge', msg.args[0]) + + def do_challenge(self, nonce): + id = self.client_chl_id + code = self.client_chl_code + + self.send_qry(id, self._challenge_response(nonce, code)) + + def _challenge_response(self, nonce, code): + return md5(nonce + code).hexdigest() + + def send_qry(self, id, resp): + log.debug('sending qry %r %r', id, resp) + self.socket.send(Message('QRY', id, payload=resp), **defcb) + + def recv_qry(self, msg): + ''' + yay we're still connected + ''' + log.debug('got qry') + self.event('challenge_success') + + def recv_add(self, msg): + log.debug('got add') + (l_id, ver, name, nick), g_id = msg[:4], (msg[4:] or [None])[0] + + nick = nick.decode('url').decode('fuzzy utf8') or None + + if g_id is not None: + g_id = GroupId(g_id) + + list_flags = dict(FL=1, AL=2, BL=4, RL=8, PL=16)[l_id] + + self.event('contact_alias', name, nick) + self.event('recv_contact', name, list_flags, [g_id]) + + @event + def on_contact_add(self, name, nick,l_id, g_id): + return name, name, nick,l_id, g_id + + def recv_rem(self, msg): + log.debug('got rem') + (l_id, ver, name), g_id = msg.args[:3], get(msg, 3, None) + + if g_id is not None: + g_id = GroupId(g_id) + + list_flags = dict(FL=1, AL=2, BL=4, RL=8, PL=16)[l_id] + + self.event('contact_remove', name, l_id, g_id) + + def recv_rmg(self, msg): + log.debug('got rmg') + ver, g_id = msg.args + + g_id = GroupId(g_id) + self.event('group_remove', g_id) + + def recv_adg(self, msg): + log.debug('got adg') + ver, name, g_id, zero = msg.args + name = name.decode('url').decode('fuzzy utf8') + g_id = GroupId(g_id) + + self.event('group_add',name, g_id) + + def recv_rea(self, msg): + log.debug('got rea') + ver, name, nick = msg.args + nick = nick.decode('url').decode('fuzzy utf8') + + self.event('contact_alias', name, nick) + + def recv_reg(self, msg): + log.debug('got reg') + ver, g_id, name, zero = msg.args + g_id = GroupId(g_id) + name = name.decode('url').decode('fuzzy utf8') + + self.event('group_rename', g_id, name) + + def recv_qng(self, msg): + log.debug('got qng') + self.event('ping_response', msg.trid) + + def recv_rng(self, msg): + if msg.trid: + msg.args = [msg.trid,] + msg.args + msg.trid = 0 + + session, server, auth_type, cookie, name, nick = msg.args[:6] + server = srv_str_to_tuple(server, 1863) + + # The SB servers are mangling the encoding of names, so ignore it. If they ever fix themselves, + # uncomment the two lines below. + #nick = nick.decode('url').decode('fuzzy utf8') + #self.event('contact_alias', name, nick) + + self.switchboard_invite(name, session, server, auth_type, cookie) + + @event + def switchboard_invite(self, name, session, server, auth_type, cookie): + return name, session, server, auth_type, cookie + + @event + def switchboard_request(self, server, cookie): + return server, cookie + + @event + def sb_request_error(self, msg): + return msg + + def send_chg(self, statuscode, client_id): + """ + set_status(status) + + tells the Notification Server that we want to CHanGe our status to + status. Valid statuses (stati?) are: online, busy, idle, brb, away, + phone, lunch, and invisible(appear offline) + """ + self.socket.send(Message('CHG', statuscode, str(client_id)), **defcb) + + def send_blp(self, new_val): + assert new_val in ('AL','BL') + self.socket.send(Message('BLP', new_val), **defcb) + + def send_gtc(self, new_val): + assert new_val in 'AN' + self.socket.send(Message('GTC', new_val), **defcb) + + def send_png(self): + return NotImplemented + + def send_add(self, l_id, name, bid, g_id, callback): + + args = (l_id, name, name) + if g_id is not None: + args += (g_id,) + + self.socket.send(Message('ADD',*args), trid=True, callback=callback) + + def send_rem(self, lid, name, bid, gid, callback): + args = (lid, name) + if gid is not None: + args += (gid,) + + self.socket.send(Message('REM', *args), trid=True, callback=callback) + + def send_rea(self, name, nick, callback): + log.debug('sending rea') + nick = msn.util.url_encode(nick.encode('utf-8')) + + if len(nick) > 128: nick = nick[:128]; log.warning('trimming nickname') + self.socket.send(Message ('REA',name,nick), trid=True, callback=callback) + + def _add_group(self, name, callback): + return self.send_adg(name, callback) + + def send_adg(self, name, callback): + log.debug('sending adg') + name = name.encode('utf-8').encode('url') + + if len(name) > 128: + #name = name[:128]; + log.warning('name should have been trimmed') + if len(name) > 61: log.warning('groupname too big, should get error') + + self.socket.send(Message('ADG', name, 0), trid=True, callback=callback) + + def send_rmg(self, gid, callback): + log.debug('sending rmg') + self.socket.send(Message('RMG', gid), trid=True, callback=callback) + + def send_reg(self, g_id, new_name, callback): + log.debug('sending reg') + new_name = msn.util.url_encode(new_name.encode('utf-8')) + + if len(new_name) > 128: new_name = new_name[:128]; log.warning('trimming groupname') + + self.socket.send(Message('REG', g_id, new_name, 0), trid=True, callback=callback) + + def _load_contact_list(self): + self.send_syn() + + @callsback + def _add_buddy(self, lid, bname, bid, gid, callback = None): + self.send_add(lid, bname, bid, gid, callback=callback) + + @callsback + def _add_buddy_to_list(self, bname, callback=None): + self.send_add('FL', bname, None, None, callback=callback) + + def _add_buddy_to_group(self, bname, bid, gid, callback): + self.send_add('FL', bname, bid, gid, callback=callback) + + @callsback + def _remove_buddy(self, lid, buddy, group, callback=None): + bname = getattr(buddy, 'name', buddy) + bid = getattr(buddy,'id',None) + self.send_rem(lid, bname, bid, group, callback=callback) + + def _remove_buddy_from_group(self, name, bid, g_id, callback): + self.send_rem('FL',name, bid, g_id, callback=callback) + + def _authorize_buddy(self, buddy, authorize, callback): + + + bname = buddy.name + bid = buddy.id + + if authorize: + self._add_buddy('AL', bname, bid, None) + self._add_buddy('RL', bname, bid, None, + success = lambda *a: self._remove_buddy('PL', buddy, None, callback = callback), + error = callback.error) + else: + self._add_buddy('BL', bname, bid, None, + success = lambda *a: self._remove_buddy('PL', buddy, None, callback = callback), + error = callback.error) + + def _block_buddy(self, buddy, callback): + bname = getattr(buddy, 'name', buddy) + success = lambda sck, msg: self._remove_buddy('AL', bname, None, callback = callback) + self._add_buddy('BL', bname, None, None, success=success, error=callback.error) + + + def _unblock_buddy(self, buddy, callback): + bname = getattr(buddy, 'name', buddy) + success = lambda sck, msg: self._remove_buddy('BL', bname, None, callback=callback) + self._add_buddy('AL', bname, None, None, success=success, error=callback.error) + + @callsback + def add_to_block(self, buddy, callback=None): + self._add_buddy('BL', buddy, None, None, callback=callback) + + @callsback + def rem_from_block(self, buddy, callback=None): + self._remove_buddy('BL', buddy, None, callback=callback) + + @callsback + def add_to_allow(self, buddy, callback=None): + self._add_buddy('AL', buddy, None, None, callback=callback) + + @callsback + def rem_from_allow(self, buddy, callback=None): + self._remove_buddy('AL', buddy, None, callback=callback) + + def _move_buddy(self, bname, bid, to_groupid, from_groupid, callback): + #TODO: Implement, or move to MSNClient + return NotImplemented + + def _set_display_name(self, new_alias, callback): + self._set_remote_alias(self._username, new_alias, callback) + + def _set_remote_alias(self, buddy, new_alias, callback): + if isinstance(buddy, basestring): + name = buddy + else: + name = buddy.name + + if not new_alias.strip(): + new_alias = name + + self.send_rea(name, new_alias, callback) + + def _rename_group(self, group_id, name, callback): + self.send_reg(group_id, name, callback) + + def _send_file(self, buddy, filepath, callback): + return NotImplemented + + def _send_sms(self, phone, message, callback): + #TODO: move to SB class? + msgenc = message.encode('utf8') if isinstance(message, unicode) else message + msgtag = util.xml_tag.tag('TEXT', msgenc, _ns_dict = {'xml': None}, **{'enc':'utf-8','xml:space':'preserve'}) + lcidtag = util.xml_tag.tag('LCID','1033') + cstag = util.xml_tag.tag('CS','UTF-8') + xml = lambda t:t._to_xml(pretty=False) + + import string + _orig_phone = phone + + if not phone: + return callback.error('Invalid phone number: %r' % _orig_phone) + + phone = ''.join(c for c in phone if c in string.digits) + + if not phone: + return callback.error('Invalid phone number: %r' % _orig_phone) + + phone = 'tel:+' + phone + + msg = Message('pgd', phone, '1', payload=' '.join((xml(msgtag), xml(lcidtag), xml(cstag)))) + self.socket.send(msg, trid=True, error=self._sms_error(callback.error)) + log.info('Sent SMS. Waiting on response.') + callback.success() + + def _sms_error(self, errorcb): + def ohnoes(sck, e): + errorcb(e.error_str) + return ohnoes + + def _set_status(self, code, client_id, callback): + self.send_chg(code, client_id) + + def _get_profile(self, buddy, callback): + return NotImplemented + def _set_buddy_icon(self, status, clientid, icon_data, callback): + return NotImplemented + def _get_buddy_icon(self, name, callback): + return NotImplemented + def _set_status_message(self, message, callback): + return NotImplemented + + + +# def _move_buddy(self, contact, to_groupname, from_groupid, pos, callback): +# (buddy, (bname, g_id)) = contact.buddy, contact.id +# +# to_group = self.group_for_name(to_groupname) +# +## if to_group is None: +## return callback.error() +# +# to_id = to_group.id +# +# from_id = self.group_for_name(from_id).id +# +# # omg teh logix! +# #log.info('groups: %s' % self.groups) +# if (contact in to_group): +## if (contact in to_group or not (buddy in self.forward_list and from_id is None)): +# callback.error() +# return log.warning('move_buddy: could not find contact %s' % contact) +# +# if to_id == 'Root': +# callback.error() +# raise msn.GeneralException("Can't move buddies to root group in MSNP8") +# +# if from_id is not None : +# self.remove_buddy(contact.id, from_id, +# success=lambda sck=None, msg=None: self.add_buddy(bname, to_id, callback=callback)) +# + diff --git a/digsby/src/msn/p8/MSNP8Switchboard.py b/digsby/src/msn/p8/MSNP8Switchboard.py new file mode 100644 index 0000000..00db470 --- /dev/null +++ b/digsby/src/msn/p8/MSNP8Switchboard.py @@ -0,0 +1,383 @@ +import logging + +from util.callbacks import callsback +from util.Events import event + +import msn +from msn import Message, MSNTextMessage +from msn.p import Switchboard as Super + +log = logging.getLogger('msn.p8.sb') + +defcb = dict(trid=True, callback=sentinel) + +class MSNP8Switchboard(Super): + events = Super.events | set(( + 'recv_text_msg', + 'send_text_msg', + 'typing_info', + + )) + + def connect(self): + if self.socket is None: + log.info('Creating %s', self._socktype) + self.socket = self._socktype() + self.socket.bind('on_connect', self.on_conn_success) + self.socket.bind('on_close', self.leave) + self.socket.bind('on_conn_error', self.conn_failed) + self.socket.bind('on_message', self.on_message) + log.info('Bound events to %s', self.socket) + conn_args = self.socket.connect_args_for('SB', self._server) + self.socket._connect(*conn_args) + + else: + log.critical('Connect called on a switchboard that already has a socket') + + @callsback + def invite(self, bname, callback=None): + self._invite(bname, callback) + + def conn_failed(self, sck, e = None): + log.error('Failed to connect!: %r, %r', sck, e) + self.event('transport_error', e or sck) + + def on_conn_success(self, s): + log.info('Connection success. Calling authenticate.') + self.event('on_conn_success', self) + self.event('needs_auth') + #self.authenticate() + + def authenticate(self, username): + if self._session is None: + log.info('Authenticating for new session.') + self.socket.send(Message('USR', username, self._cookie), **defcb) + else: + log.info('Authenticating for session in progress.') + self.socket.send(Message('ANS', username, self._cookie, self._session), **defcb) + + def leave(self, sck=None): + # unbind events + log.debug('leaving %r', self) + if self.socket is not None: + self.socket._disconnect() + self.on_disconnect() + + def on_disconnect(self): + del self.principals[:] + self.event('disconnect') + self.socket = None + self._session = None + #self._cookie = None + + def connected(self): + return (self.socket is not None) + + def recv_usr(self, msg): + ok, name, nick = msg.args + nick = nick.decode('url').decode('utf-8') or None + self.event('contact_alias', name, nick) + assert ok == 'OK' + self._session = self._cookie.split('.',1)[0] + self.on_authenticate() + + @event + def on_authenticate(self): + log.info('Authenticated.') + + def _invite(self, bname, callback): + self.socket.send(Message('CAL', bname), trid=True, callback=callback) + + def recv_ack(self, msg): + log.debug('Got ack: %s', str(msg).strip()) + + def recv_ans(self, msg): + ''' + ANS (ANSwer) + + This command is sent by the server after we send our ANS to login. + + @param socket: the socket the command came from + @param trid: trid assocated with this command + @param status: the status of our ANS....has to be OK or we would + have been disconnected already! + ''' + + status, = msg.args + assert status == 'OK' + self.on_authenticate() + + def recv_msg(self, msg): + + try: + getattr(self, 'recv_msg_%s' % msg.type, self.recv_msg_unknown)(msg) + except Exception, e: + import traceback + traceback.print_exc() + log.error('Exception handling MSG! error = %r, msg = %r', e, msg) + + def recv_msg_plain(self, msg): + ''' + msg_plain(msg, src_account, src_display) + + this is called when a msg comes in with type='text/plain' + + @param socket: the socket it arrived from (better be self.socket!) + @param msg: the rfc822 object representing the MIME headers and such + @param src_account: the email address/passport this comes from + @param src_display: the display name of the buddy who sent it + @param *params: more stuff! + ''' + + name, nick = msg.args[:2] + nick = msn.util.url_decode(nick).decode('utf-8') or None + msg = MSNTextMessage.from_net(msg.payload) + + self.event('contact_alias', name, nick) + self.event('recv_text_msg', name, msg) + + def recv_msg_control(self, msg): + ''' + msg_control(msg, src_account, src_display) + + This is called when a message comes in with type='text/x-msmsgscontrol' + Generally, these are typing indicators. + + @param msg: msnmessage + ''' + name, nick = msg.args[:2] + nick = msn.util.url_decode(nick).decode('utf-8') or None + + self.event('contact_alias', name, nick) + self.event('typing_info', name, bool(msg.payload.get('TypingUser', False))) + + def recv_msg_notification(self, msg): + ''' + msg_notification(msg, src_account, src_display) + + No idea what these are. So, raise for now... + + @param msg: msnmessage + ''' + #TODO: find out what this is for. + name, nick = msg.args[:2] + self.event('contact_alias', name, nick) + + @event + def on_buddy_join(self, name): +# if name in self._to_invite: +# self._to_invite.remove(name) + + self.principals.append(name) + return name + + def recv_cal(self, msg): + ringing, session = msg.args + # event: on_invite + + def recv_iro (self, msg): + ''' + IRO (In ROom) + + This is sent to us when we connect to notify us of who is in the room. + + @param socket: the socket the message arrived from + @param trid: the trid associated with this command + @param rooster: the 'rooster number' for this buddy + @param roostercount: the total number of 'roosters' to expect + @param passport: the passport name of the buddy (email address) + @param buddyname: the friendly name of the buddy + @param *args: more stuff! + ''' + + rooster, roostercount, name, nick= msg.args[:4] + rooster = int(rooster) + roostercount = int(roostercount) + nick = msn.util.url_decode(nick).decode('utf-8') or None + + self.event('contact_alias', name,nick) + self.on_buddy_join(name) + + def recv_joi (self, msg): + ''' + JOI (Buddy JOIn) + + A buddy is joining the conversation + + @param socket: the socket the command came from + @param trid: trid assocated with this command + @param acct: the email addresss of the joining buddy + @param fn: the friendly name (display name) of the buddy + @param sessid: the session ID of this session + ''' + + name, nick = msg.args[:2] + nick = msn.util.url_decode(nick).decode('utf-8') or None + + self.event('contact_alias', name, nick) + self.on_buddy_join(name) + + def recv_bye (self, msg): + ''' + BYE (BYE) + + A buddy is leaving the conversation (saying bye!) + + @param socket: the socket the command came from + @param trid: trid assocated with this command + @param buddy: the buddyname leaving + ''' + + notify = True + try: + name, = msg.args + except: + name, reason = msg.args + if int(reason) == 1: + self.on_buddy_timeout(name) + notify = False + + self.on_buddy_leave(name, notify) + + @event + def on_buddy_leave(self, name, notify=True): + if name in self.principals: + self.principals.remove(name) + return name, notify + + @event + def on_buddy_timeout(self, name): + #self._to_invite.append(name) + return name + + def recv_msg_unknown(self, msg): + log.error('Unknown message type! %r', msg) + + def recv_msg_invite(self, msg): + ''' +--- +Exception handling MSG! +MSG digsby03@hotmail.com digsby%20oh%20threesus 502 +MIME-Version: 1.0 +Content-Type: text/x-msmsgsinvite; charset=UTF-8 + +Application-Name: an audio conversation +Application-GUID: {02D3C01F-BF30-4825-A83A-DE7AF41648AA} +Session-Protocol: SM1 +Context-Data: Requested:SIP_A,;Capabilities:SIP_A,; +Invitation-Command: INVITE +Avm-Support: 7 +Avm-Request: 2 +Invitation-Cookie: 24443504 +Session-ID: {EE086C37-D672-44C2-9AA9-57151CEB0BEF} +Conn-Type: IP-Restrict-NAT +Sip-Capability: 1 +Public-IP: 129.21.160.34 +Private-IP: 192.168.1.102 +UPnP: FALSE + +--- +Exception handling MSG! +MSG digsby03@hotmail.com digsby%20oh%20threesus 317 +MIME-Version: 1.0 +Content-Type: text/x-msmsgsinvite; charset=UTF-8 + +Invitation-Command: CANCEL +Cancel-Code: TIMEOUT +Invitation-Cookie: 24443504 +Session-ID: {EE086C37-D672-44C2-9AA9-57151CEB0BEF} +Conn-Type: IP-Restrict-NAT +Sip-Capability: 1 +Public-IP: 129.21.160.34 +Private-IP: 192.168.1.102 +UPnP: FALSE + ''' + return log.info('msg invite %s', msg) + m = Message(msg.body()) + + c = m['Invitation-Cookie'] + try: + a = self.activities[c] + except KeyError: + a = self.activities.setdefault(c, msn.slp.SLP(self, name, c)) + + a.incoming(m) + + def recv_msg_datacast(self, msg): + log.info('Received datacast') + try: + name, nick = msg.args + nick = nick.decode('url').decode('utf-8') + body = msg.payload + + id = int(msg.id) + + if id == 1: + action_type = 'nudge' + action_text = None + elif id == 2: + action_type = 'wink' + action_text = None + elif id == 4: + action_type = 'custom' + action_text = msg.data + else: + return + #action_text = 'sent an unknown datacat' + + self.event('recv_action', name, action_type, action_text) + #self.system_message('%s %s' % (b.alias, action_text)) + except Exception,e : + import traceback; traceback.print_exc() + raise e + + def recv_msg_caps(self, msg): + log.info(msg.name + ' is using gaim/pidgin') + +# def msg_p2p(self, msg, name, nick, len): +# msg = msg.body() +# +# h, b, f = msg[:48], \ +# msg[48:-4],\ +# msg[-4:] +# +# sess_id, base_id = struct.unpack('>II', h[:8]) +# +# if not sess_id and base_id not in self.activities: +# self.activities[base_id] = msn.slp.SLP(self, name, base_id) +# self.activities[base_id].incoming(h,b,f) +# + + @callsback + def send_text_message(self, body, callback): + if not self.connected: + callback.error('connection lost') + raise Exception('connection lost') + + if isinstance(body, unicode): + _body, body = body, body.encode('utf8') + + cmd = msn.MSNCommands.MSG('N', payload = str(body)) + + def check_nak(sck, msg): + if msg.cmd == 'NAK': + msg.source_message = body + callback.error(msg) + else: + callback.success() + + self.socket.send(cmd, trid=True, success=check_nak, error=lambda sck, emsg: callback.error(emsg)) + #self.event('send_text_msg', body) + + def send_typing_status(self, name, status): + if not self.connected: + return + + if status: + body = "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msmsgscontrol\r\n" \ + "TypingUser: %s\r\n\r\n\r\n" % name + + self.socket.send(Message('MSG', 'U', payload=body), **defcb) + + diff --git a/digsby/src/msn/p8/__init__.py b/digsby/src/msn/p8/__init__.py new file mode 100644 index 0000000..f88fed9 --- /dev/null +++ b/digsby/src/msn/p8/__init__.py @@ -0,0 +1,2 @@ +from MSNP8Switchboard import MSNP8Switchboard as Switchboard +from MSNP8Notification import MSNP8Notification as Notification diff --git a/digsby/src/msn/p9/MSNP9Notification.py b/digsby/src/msn/p9/MSNP9Notification.py new file mode 100644 index 0000000..0ca98da --- /dev/null +++ b/digsby/src/msn/p9/MSNP9Notification.py @@ -0,0 +1,137 @@ +from __future__ import with_statement +import logging + +from util.primitives.funcs import get, isint +from util.xml_tag import tag + +import msn +from msn import Message +from msn.p8 import Notification as Super + + +log = logging.getLogger('msn.p9.ns') + +defcb = dict(trid=True, callback=sentinel) + +class MSNP9Notification(Super): + + events = Super.events | set(( + 'recv_sms', + 'contact_icon_info', + )) + + versions = ['MSNP9'] + def _set_buddy_icon(self, status, clientid, icon_data, callback): + if icon_data is not None: + + import hashlib + hash = hashlib.sha1(icon_data).digest() + + import random + fn = 'temp%d.dat' % random.randint(1,1000) + self.icon_obj = msn.MSNObject(Creator=self.self_buddy.name, + Type='3', + Location=fn, + Size=len(icon_data), + SHA1D=hash) + self.self_buddy.msn_obj = self.icon_obj + self.self_buddy.notify('icon_hash') + log.debug('setting icon obj') + self.send_chg(status, clientid, callback=callback) + + def _get_buddy_icon(self, name, callback): + log.info('get buddy icon: %s',name) +# return +# b = self.buddies[bname] +# if not b.online: +# log.info('Not retrieving buddy icon for %s -- they are offline', bname) +# return +# +## c = self.conv_class(self, bname, success=self.conv_class.get_buddy_icon) +## self.icon_requests.append(c) +# c = BuddyIconGetter(self, b) +# c.connect() + +# def _send_file(self, buddy, file): +# #TODO: scrap this and only use P2P objects +# c = FileSender(self, buddy, fstruct=file) +# c.connect() +## c = self.convo_for(buddy)#, self.conv_class.send_file, fstruct=file) +## c.on_connect = lambda x:x.send_file(buddy.name, fstruct=file) + + + def _parse_iln_nln(self, msg): + log.debug('got iln/nln') + args = Super._parse_iln_nln(self, msg) + + iconinfo = msn.util.url_decode(get(msg.args, 4, '')) + + msnobj = None + if '<' in iconinfo and '>' in iconinfo: + try: + msnobj = msn.MSNObject.parse(iconinfo) + except Exception, e: + log.error('Error parsing msn object (%r). here\'s data: %r', e, iconinfo) + + return args, msnobj + + def recv_iln(self, msg): + args, msnobj = self._parse_iln_nln(msg) + + name = args[0] + + self.event('contact_online_initial', *args) + self.event('contact_icon_info', name, msnobj) + + def recv_nln(self, msg): + args, msnobj = self._parse_iln_nln(msg) + + name = args[0] + + self.event('contact_online', *args) + self.event('contact_icon_info', name, msnobj) + + def recv_chg(self, msg): + Super.recv_chg(self, msg) + if not msg.args: + self.icon_obj = None + else: + msnobj = msg.args[-1] + self.icon_obj = msn.MSNObject.parse(msn.util.url_decode(msnobj)) + + self.event('contact_icon_info', self._username, self.icon_obj) + + def recv_ipg(self, msg): + log.debug('Received IPG command') + log.debug(str(msg)) + + n = tag(msg.payload) + + sender = n.FROM['name'] + message = n.MSG.BODY.TEXT + + if sender.startswith('tel:+'): + sender = sender[4:] + + self.event('recv_sms', sender, unicode(message)) + + def send_png(self): + log.debug('ping') + + self.socket.send(Message('PNG')) + log.info('pinged') + + def send_chg(self, code, client_id, callback=None): + + if code.lower() == 'fln': + code = 'HDN' + + iconobj = getattr(self, 'icon_obj', None) + if iconobj: + msnobj = msn.util.url_encode(iconobj.to_xml()) + else: + msnobj = '' + + self.socket.send(Message('CHG', code, str(client_id), msnobj), + trid=True, callback=(callback or sentinel)) + diff --git a/digsby/src/msn/p9/MSNP9Switchboard.py b/digsby/src/msn/p9/MSNP9Switchboard.py new file mode 100644 index 0000000..64f7ff0 --- /dev/null +++ b/digsby/src/msn/p9/MSNP9Switchboard.py @@ -0,0 +1,65 @@ +from util import callsback + + +import msn +from msn.p8 import Switchboard as Super + +defcb = dict(trid=True, callback=sentinel) + +class MSNP9Switchboard(Super): + events = Super.events | set(( + 'send_p2p_msg', + 'recv_p2p_msg', + + )) + +# def msg_p2p(self, socket, msg): +# return +# name = msg.args[0] +# body = msg.payload.body() +# +# try: +# self.layer.incoming(body) +# except AttributeError, e: +# print 'P2PException!', str(e), repr(e), repr(self), type(self) +# if 'INVITE' not in body: +# log.critical('got p2p message but don\'t know where to send it') +# return +# print 'making new p2player + invited generator' +# out_func = functools.partial(self.p2psend, name) +# layer = msn.p2player.P2PLayer(self, None, out_func, 0) +# recvr = msn.p2p.invited(layer, name, self) +# layer.recv = recvr +# +# #self.activities[name] = layer +# +# layer.incoming(body) +# +# if self.layer is not None: +# print "Two p2players in one msnp9conversation?! UNPOSSIBLE!" +# self.extra_layers.append(layer) +# else: +# self.layer = layer + + def send_p2p_message(self, to, data, callback): + body = MSNP2PMessage(to, data) + cmd = msn.MSNCommands.MSG('D', payload=str(body)) + + self.socket.send(cmd, trid=True, callback=callback) + self.event('send_p2p_msg', body) + + def recv_msg_p2p(self, msg): + self.event('recv_p2p_msg', msg.args[0], msg.payload.body()) + +class MSNP2PMessage(object): + + def __init__(self, recvr, body): + self.recvr = recvr + self.body = body + + def __str__(self): + return ('\r\n'.join(['MIME-Version: 1.0', + 'Content-Type: application/x-msnmsgrp2p', + 'P2P-Dest: %(recvr)s', + '', + '%(body)s'])) % vars(self) diff --git a/digsby/src/msn/p9/__init__.py b/digsby/src/msn/p9/__init__.py new file mode 100644 index 0000000..c45f118 --- /dev/null +++ b/digsby/src/msn/p9/__init__.py @@ -0,0 +1,5 @@ +from MSNP9Switchboard import MSNP9Switchboard as Switchboard +from MSNP9Notification import MSNP9Notification as Notification + +#from MSNObject import MSNObject as object +#from MSNObject import from_xml as object_from_xml \ No newline at end of file diff --git a/digsby/src/netextensions.py b/digsby/src/netextensions.py new file mode 100644 index 0000000..26717ef --- /dev/null +++ b/digsby/src/netextensions.py @@ -0,0 +1,632 @@ +''' +Monkeypatches are made in these files: + +urllib.py + - URLopener.open_data: the original function uses %T in a call to strftime, + which doesn't exist (at least on windows) + - the addbase class: replaced with one that has a tell method + - OneProxy / getproxies_registry + +urllib2.py + - ProxyHandler: "Proxy-Authorization" case change, and later binding for + proxy objects +''' + +from __future__ import with_statement + +############################################# +import urllib +############################################# + +import base64 +import mimetools +import os +import time +import httplib + + + +import os +if os.name == 'nt': + + # The Subprocess module uses CreateProcess with bInheritHandles = TRUE + # so that it can redirect stdout, stdin, and stderr. Unfortunately this + # means that the subprocess also inherits all of our sockets. If + # the process is long living, it will keep those sockets connected. + # + # These method patches use the kernel32.dll function SetHandleInformation + # to make all new sockets uninheritable. + # + # TODO: Note that if another thread spawns a process in between the socket + # creation and the call to SetHandleInformation, we're still out of luck. + + from msvcrt import get_osfhandle + from ctypes import windll, WinError + SetHandleInformation = windll.kernel32.SetHandleInformation + + if __debug__: + def set_noninheritable(socket): + if not SetHandleInformation(socket.fileno(), 1, 0): + raise WinError() + else: + # don't raise WinErrors on release mode. + def set_noninheritable(socket): + SetHandleInformation(socket.fileno(), 1, 0) + + import socket + original_socket = socket.socket + _orig_connect = original_socket.connect + _orig_connect_ex = original_socket.connect_ex + _orig_accept = original_socket.accept + + def connect(self, address): + res = _orig_connect(self, address) + set_noninheritable(self) + return res + + def connect_ex(self, address): + res = _orig_connect_ex(self, address) + set_noninheritable(self) + return res + + def accept(self): + conn, addr = _orig_accept(self) + set_noninheritable(conn) + return conn, addr + + original_socket.connect = connect + original_socket.connect_ex = connect_ex + original_socket.accept = accept + +def open_data(self, url, data=None): + """Use "data" URL.""" + if not isinstance(url, str): + raise IOError, ('data error', 'proxy support for data protocol currently not implemented') + # ignore POSTed data + # + # syntax of data URLs: + # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data + # mediatype := [ type "/" subtype ] *( ";" parameter ) + # data := *urlchar + # parameter := attribute "=" value + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + try: + [type, data] = url.split(',', 1) + except ValueError: + raise IOError, ('data error', 'bad data URL') + if not type: + type = 'text/plain;charset=US-ASCII' + semi = type.rfind(';') + if semi >= 0 and '=' not in type[semi:]: + encoding = type[semi+1:] + type = type[:semi] + else: + encoding = '' + msg = [] + msg.append('Date: %s'% time.strftime('%a, %d %b %Y %H:%M:%S GMT', + time.gmtime(time.time()))) + msg.append('Content-type: %s' % type) + if encoding == 'base64': + data = base64.decodestring(data) + else: + data = urllib.unquote(data) + msg.append('Content-Length: %d' % len(data)) + msg.append('') + msg.append(data) + msg = '\n'.join(msg) + f = StringIO(msg) + headers = mimetools.Message(f, 0) + #f.fileno = None # needed for addinfourl + return urllib.addinfourl(f, headers, url) + +urllib.URLopener.open_data = open_data + +# Methods to be patched into urllib.addbase + +def ab___init__(self, fp): + self.fp = fp + self._amt_read = 0 + if hasattr(self.fp, "readlines"): self.readlines = self.fp.readlines + if hasattr(self.fp, "fileno"): + self.fileno = self.fp.fileno + else: + self.fileno = lambda: None + if hasattr(self.fp, "__iter__"): + self.__iter__ = self.fp.__iter__ + if hasattr(self.fp, "next"): + self.next = self.fp.next + +def ab_read(self, *a, **k): + v = self.fp.read(*a, **k) + self._amt_read += len(v) + return v +def ab_readline(self, *a, **k): + v = self.fp.readline(*a, **k) + self._amt_read += len(v) + return v + +def ab_tell(self): + return self._amt_read + +def ab___repr__(self): + return '<%s at %r whose fp = %r>' % (self.__class__.__name__, + id(self), self.fp) + +def ab_close(self): + self._amt_read = 0 + self.read = None + self.readline = None + self.readlines = None + self.fileno = None + if self.fp: self.fp.close() + self.fp = None + +for meth in '__init__ read readline tell __repr__ close'.split(): + setattr(urllib.addbase, meth, globals()['ab_' + meth]) + +# +#urllib.addbase = addbase + +if os.name == 'nt': + class OneProxy(dict): + @property + def proxyServer(self): + return self._proxyServer + def __missing__(self, key): + val = '%s://%s' % (key, self.proxyServer) + self.__setitem__(key, val) + return val + + + def getproxies_registry(): + """Return a dictionary of scheme -> proxy server URL mappings. + + Win32 uses the registry to store proxies. + + """ + proxies = {} + try: + import _winreg + except ImportError: + # Std module, so should be around - but you never know! + return proxies + try: + internetSettings = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') + proxyEnable = _winreg.QueryValueEx(internetSettings, + 'ProxyEnable')[0] + if proxyEnable: + # Returned as Unicode but problems if not converted to ASCII + proxyServer = str(_winreg.QueryValueEx(internetSettings, + 'ProxyServer')[0]) + if '=' in proxyServer: + # Per-protocol settings + for p in proxyServer.split(';'): + protocol, address = p.split('=', 1) + # See if address has a type:// prefix + import re + if not re.match('^([^/:]+)://', address): + address = '%s://%s' % (protocol, address) + proxies[protocol] = address + else: + # Use one setting for all protocols + if proxyServer[:5] == 'http:': + proxies['http'] = proxyServer + else: + proxies = OneProxy() + proxies._proxyServer = proxyServer + + # these 2 lines put the keys/values + proxies['https'] + proxies['http'] + proxies['ftp'] + + internetSettings.Close() + except (WindowsError, ValueError, TypeError): + # Either registry key not found etc, or the value in an + # unexpected format. + # proxies already set up to be empty so nothing to do + pass + return proxies + + urllib.OneProxy = OneProxy + urllib.getproxies_registry = getproxies_registry + +###################################### +import urllib2 +###################################### + + +class ProxyHandler(urllib2.BaseHandler): + # Proxies must be in front + handler_order = 100 + + def __init__(self, proxies=None): + self._proxies = proxies + + def _get_proxies(self): + import urllib + return self._proxies or urllib.getproxies() + + def _set_proxies(self, val): + self._proxies = val + + proxies = property(_get_proxies, _set_proxies) + + def http_open(self, req): + return self.proxy_open(req, 'http') + + def https_open(self, req): + return self.proxy_open(req, 'https') + + def ftp_open(self, req): + return self.proxy_open(req, 'ftp') + + def proxy_open(self, req, type): + orig_type = req.get_type() + try: + proxy = self.proxies[type] + except KeyError: + return None + + proxy_type, user, password, hostport = urllib2._parse_proxy(proxy) + if proxy_type is None: + proxy_type = orig_type + if user and password: + user_pass = '%s:%s' % (urllib2.unquote(user), urllib2.unquote(password)) + creds = base64.b64encode(user_pass).strip() + req.add_header('Proxy-Authorization', 'Basic ' + creds) + + hostport = urllib2.unquote(hostport) + req.set_proxy(hostport, proxy_type) + if orig_type == proxy_type: + # let other handlers take care of it + return None + else: + # need to start over, because the other handlers don't + # grok the proxy's URL type + # e.g. if we have a constructor arg proxies like so: + # {'http': 'ftp://proxy.example.com'}, we may end up turning + # a request for http://acme.example.com/a into one for + # ftp://proxy.example.com/a + return self.parent.open(req) + +urllib2.ProxyHandler = ProxyHandler + +# FileHandler, the class to handle local file:// urls, attempts FTP if +# the url doesn't quite look like a file:// url (that is, if it doesn't +# have the right number of slashes). +# +# Defaulting to FTP when trying to open a localfile is not a good idea. +def _no_ftp_file_open(self, req): + return self.open_local_file(req) + +urllib2.FileHandler.file_open = _no_ftp_file_open + +# Now, a new build_opener to replace urllib2's. +# The difference here is that you can specify opener_class by keyword +# and use an OpenerDirector subclass (or something). This is exactly copy/pasted +# from python 2.5.1 urllib2, with the exception of: +# * the function takes **kwds and gets the opener class to instantiate from there +# * default_classes are accessible from OUTSIDE of the function, in case they need to be modified. +# * default classes are also modifiable via a keyword argument. + +urllib2.default_opener_classes = map( + lambda x: getattr(urllib2, x), + ('UnknownHandler', + 'HTTPHandler', + 'HTTPDefaultErrorHandler', + 'HTTPRedirectHandler', + 'FTPHandler', + 'FileHandler', + 'HTTPErrorProcessor' + )) + +def build_opener(*handlers, **kwds): + """Create an opener object from a list of handlers. + + The opener will use several default handlers, including support + for HTTP and FTP. + + If any of the handlers passed as arguments are subclasses of the + default handlers, the default handlers will not be used. + """ + import types + + opener_class = kwds.pop('opener_class', urllib2.OpenerDirector) + classes = kwds.pop('default_classes', urllib2.default_opener_classes)[:] + + if kwds: + raise TypeError("Only opener_class and default_classes are accepted as keyword arguments for build_opener") + + def isclass(obj): + return isinstance(obj, types.ClassType) or hasattr(obj, "__bases__") + + opener = opener_class() + + if hasattr(httplib, 'HTTPS'): + classes.append(urllib2.HTTPSHandler) + skip = [] + for klass in classes: + for check in handlers: + if isclass(check): + if isclass(klass) and issubclass(check, klass): + skip.append(klass) + elif not isclass(klass) and issubclass(check, type(klass)): + skip.append(klass) + elif isinstance(check, type(klass)): + skip.append(klass) + elif isclass(klass) and isinstance(check, klass): + skip.append(klass) + + for klass in skip: + classes.remove(klass) + + for klass in classes: + try: + instance = klass() + except (AttributeError, TypeError): + instance = klass + opener.add_handler(instance) + + for h in handlers: + if isclass(h): + h = h() + opener.add_handler(h) + return opener + +urllib2.build_opener = build_opener + +# +# asynchat monkeypatches +# +# the only thing that's changed from the standard library impl is to pass +# the exception object to handle_error +# + +import socket +from asynchat import find_prefix_at_end +import sys + +if sys.hexversion > 0x02060000: + def handle_read (self): + + try: + data = self.recv (self.ac_in_buffer_size) + except socket.error, why: + self.handle_error(why) + return + + self.ac_in_buffer = self.ac_in_buffer + data + + # Continue to search for self.terminator in self.ac_in_buffer, + # while calling self.collect_incoming_data. The while loop + # is necessary because we might read several data+terminator + # combos with a single recv(4096). + + while self.ac_in_buffer: + lb = len(self.ac_in_buffer) + terminator = self.get_terminator() + if not terminator: + # no terminator, collect it all + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + elif isinstance(terminator, int) or isinstance(terminator, long): + # numeric terminator + n = terminator + if lb < n: + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + self.terminator = self.terminator - lb + else: + self.collect_incoming_data (self.ac_in_buffer[:n]) + self.ac_in_buffer = self.ac_in_buffer[n:] + self.terminator = 0 + self.found_terminator() + else: + # 3 cases: + # 1) end of buffer matches terminator exactly: + # collect data, transition + # 2) end of buffer matches some prefix: + # collect data to the prefix + # 3) end of buffer does not match any prefix: + # collect data + terminator_len = len(terminator) + index = self.ac_in_buffer.find(terminator) + if index != -1: + # we found the terminator + if index > 0: + # don't bother reporting the empty string (source of subtle bugs) + self.collect_incoming_data (self.ac_in_buffer[:index]) + self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:] + # This does the Right Thing if the terminator is changed here. + self.found_terminator() + else: + # check for a prefix of the terminator + index = find_prefix_at_end (self.ac_in_buffer, terminator) + if index: + if index != lb: + # we found a prefix, collect up to the prefix + self.collect_incoming_data (self.ac_in_buffer[:-index]) + self.ac_in_buffer = self.ac_in_buffer[-index:] + break + else: + # no prefix, collect it all + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + + from sys import py3kwarning + from warnings import filterwarnings, catch_warnings + + def initiate_send(self): + while self.producer_fifo and self.connected: + first = self.producer_fifo.popleft() + # handle empty string/buffer or None entry + if not first: + if first is None: + self.handle_close() + return + + # handle classic producer behavior + obs = self.ac_out_buffer_size + try: + with catch_warnings(): + if py3kwarning: + filterwarnings("ignore", ".*buffer", DeprecationWarning) + data = buffer(first, 0, obs) + except TypeError: + data = first.more() + if data is not None: + self.producer_fifo.appendleft(first) + + if data: + self.producer_fifo.appendleft(data) + continue + + # send the data + try: + num_sent = self.send(data) + except socket.error, why: + self.handle_error(why) + self.producer_fifo.appendleft(first) + return + + if num_sent: + if num_sent < len(data) or obs < len(first): + self.producer_fifo.appendleft(first[num_sent:]) + # we tried to send some actual data + return +else: #sys.hexversion > 0x02060000 + + def handle_read (self): + + try: + data = self.recv (self.ac_in_buffer_size) + except socket.error, why: + self.handle_error(why) + return + + self.ac_in_buffer = self.ac_in_buffer + data + + # Continue to search for self.terminator in self.ac_in_buffer, + # while calling self.collect_incoming_data. The while loop + # is necessary because we might read several data+terminator + # combos with a single recv(1024). + + while self.ac_in_buffer: + lb = len(self.ac_in_buffer) + terminator = self.get_terminator() + if not terminator: + # no terminator, collect it all + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + elif isinstance(terminator, int) or isinstance(terminator, long): + # numeric terminator + n = terminator + if lb < n: + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + self.terminator = self.terminator - lb + else: + self.collect_incoming_data (self.ac_in_buffer[:n]) + self.ac_in_buffer = self.ac_in_buffer[n:] + self.terminator = 0 + self.found_terminator() + else: + # 3 cases: + # 1) end of buffer matches terminator exactly: + # collect data, transition + # 2) end of buffer matches some prefix: + # collect data to the prefix + # 3) end of buffer does not match any prefix: + # collect data + terminator_len = len(terminator) + index = self.ac_in_buffer.find(terminator) + if index != -1: + # we found the terminator + if index > 0: + # don't bother reporting the empty string (source of subtle bugs) + self.collect_incoming_data (self.ac_in_buffer[:index]) + self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:] + # This does the Right Thing if the terminator is changed here. + self.found_terminator() + else: + # check for a prefix of the terminator + index = find_prefix_at_end (self.ac_in_buffer, terminator) + if index: + if index != lb: + # we found a prefix, collect up to the prefix + self.collect_incoming_data (self.ac_in_buffer[:-index]) + self.ac_in_buffer = self.ac_in_buffer[-index:] + break + else: + # no prefix, collect it all + self.collect_incoming_data (self.ac_in_buffer) + self.ac_in_buffer = '' + + def initiate_send (self): + obs = self.ac_out_buffer_size + # try to refill the buffer + if (len (self.ac_out_buffer) < obs): + self.refill_buffer() + + if self.ac_out_buffer and self.connected: + # try to send the buffer + try: + num_sent = self.send (self.ac_out_buffer[:obs]) + if num_sent: + self.ac_out_buffer = self.ac_out_buffer[num_sent:] + + except socket.error, why: + self.handle_error(why) + return + + +import asynchat +asynchat.async_chat.handle_read = handle_read +asynchat.async_chat.initiate_send = initiate_send +del handle_read, initiate_send + +def dispatcher_getattr(self, attr): + if attr == 'socket': + return object.__getattribute__(self, 'socket') + return getattr(self.socket, attr) + +import asyncore +asyncore.dispatcher.__getattr__ = dispatcher_getattr +del dispatcher_getattr + +def make_request(cls, full_url, data=None, *a, **k): + ''' + Convenience constructor, you can provide a (possibly full) URL (and data, optionally) + or another request. + ''' + # construct a urllib2.Request object, set appropriate headers for connection, and return it + if not isinstance(full_url, cls): + default_host = k.pop('default_host', None) + ssl = k.pop('ssl', False) + + proto = 'http%s' % ('s' if ssl else '') + + if default_host and not (full_url.startswith(proto) or full_url.startswith(default_host)): + proto_host = ('%s://%s' % (proto, default_host.rstrip('/'))) + full_url = proto_host + '/' + full_url.lstrip('/') + if not full_url.startswith(proto): + full_url = ('%s://%s' % (proto, full_url)) + + req = cls(full_url, *a, **k) + else: + req = full_url + + if data is not None: + if not isinstance(data, str): + data = urllib.urlencode(data) + + req.add_data(data) + + return req + +urllib2.Request.make_request = classmethod(make_request) diff --git a/digsby/src/options.py b/digsby/src/options.py new file mode 100644 index 0000000..ef640ea --- /dev/null +++ b/digsby/src/options.py @@ -0,0 +1,296 @@ +import optparse +import sys + +# TODO: switch to argparse? optparse is deprecated in later versions of python +# and the switch to argparse is very straightforward. + +class NonExitingOptionParser(optparse.OptionParser): + def exit(self, code = None, message = None): + if code is None and message is None: + # --help calls exit() with no arguments + return optparse.OptionParser.exit(self, code, message) + + print >>sys.stderr, "Error processing options: code = %r, message = %r" % (code, message) + +parser = NonExitingOptionParser(prog="Digsby", + version="%%prog r%s" % + getattr(sys, "REVISION", 'dev')) +parser.add_option('--noautologin', + action="store_false", + dest='autologin', + help="Disable autologin of Digsby") + +parser.add_option('--noautologin-accounts', + '--noautologin_accounts', + action="store_false", + dest='autologin_accounts', + help="Disable autologin of accounts") + +parser.add_option('--measure', type='string', help='Measure timings of certain actions') + +parser.add_option('--register', + action='store_true', + help = 'Show the register page on startup') + +# This is an override for dev mode. non-dev will always attempt to load portable settings. +parser.add_option('--portable', + action='store_true', + dest = 'allow_portable', + help = 'Allows the program to load info for "portable" mode') + +parser.add_option('--username', help = 'Username to login with') +parser.add_option('--password', help = 'Password to login with') +parser.add_option('--resource', help = 'Resource to login with') + +parser.add_option('--lang', + '--language', + dest = 'lang', + help = 'Language code (i.e., es_DO) to show interface in') + +parser.set_defaults(autologin=True, + autologin_accounts=True, + register = False, + allow_portable = False) + +parser.add_option('--action') + +#=============================================================================== +# Behavioral +#=============================================================================== +behave_group = optparse.OptionGroup(parser, 'Behavioral Options') +behave_group.add_option('--multi', + action = 'store_false', + dest = 'single_instance', + help = 'disable single-instance checker') +behave_group.add_option('--single', + action = 'store_true', + dest = 'single_instance', + help = 'enable single-instance checker on dev') +behave_group.add_option('--start-offline', '--offline', + action = 'store_true', + dest = 'start_offline', + help = 'forces the initial login to be an offline one. (only works in DEV mode)') +behave_group.add_option('--server', + help = 'adds a server to the start login sequence') +behave_group.add_option('--loadbalancer', + help = 'forces digsby to use a specific load balancer') + +parser.set_defaults(start_offline = False) +#=============================================================================== +# Debugging +#=============================================================================== +debug_group = optparse.OptionGroup(parser, 'Debugging Options') +debug_group.add_option('--profile', + type='int', + help="profiling level [default: %default]") +debug_group.add_option('--profile1', + action='store_const', + const=1, + dest='profile', + help="profiling level 1") +debug_group.add_option('--profile2', + action='store_const', + const=2, + dest='profile', + help="profiling level 2") +debug_group.add_option('--nocpuwatch', '--no-cpuwatch', + action="store_false", + dest='cpuwatch', + help="disable CPUWatch") +debug_group.add_option('--no-force-exit', + action='store_false', + dest='force_exit', + help='disable forceful exit if Digsby hangs on quit') +debug_group.add_option('--quickexit', + action='store_true', + help='always exit forcefully') +debug_group.add_option('--full-crashdump', + action = 'store_true', + dest = 'full_crashdump', + help = 'enable full crash dumps') +debug_group.add_option('--crashdump-dir', + dest = 'crashdump_dir', + help = 'specify the directory crash dumps will be saved to') +debug_group.add_option('--no-crashsubmit', + action = 'store_false', + dest = 'submit_crashdump', + help = 'disable crash minidump submission') +debug_group.add_option('--color', + action='store_true', + dest='console_color', + help='enable color console output') +debug_group.add_option('--no-plugins','--no_plugins', + action='store_false', + dest='load_plugins', + help='disable plugin loading') +debug_group.add_option('--no-traceback-dialog', + action='store_false', + dest='traceback_dialog', + help='disables the traceback dialog') +debug_group.add_option('--heapy', + action='store_true', + dest='heapy', + help='enables Heapy on the console') +debug_group.add_option('--track-windowids', + action='store_true', + dest='track_windowids', + help='sends window id allocation locations in bug reports') +debug_group.add_option('--debugads', + action='store_true', + help='logs more information about ads being loaded') + + +parser.set_defaults(profile=0, + cpuwatch=True, + force_exit=True, + submit_crashdump=True, + console_color=False, + load_plugins=True, + traceback_dialog=True, + heapy=False, + track_windowids=False, + debugads=False) + +#=============================================================================== +# Logging +#=============================================================================== +logging_group = optparse.OptionGroup(parser, 'Logging Options') + +logging_group.add_option('-v', '--verbose', + '--full-log', + '--full_log', + dest='full_log', + action='store_true', + help='Tells the logger to log everything. ' + 'Disables the hiding (from the logger) of many ' + 'possibly sensitive pieces of information, ' + 'such as IM messages and the contents of streams to the server. ' + 'WARNING: increases CPU and disk activity. ' + '[default: %default]') + +logging_group.add_option('--no-log', + dest = 'no_log', + action= 'store_true', + help = 'Tells the logger to log nothing.') + +logging_group.add_option('--no-log-limit', + dest='limit_log', + action='store_false', + help='The log will not be rate limited on stdout.') + +logging_group.add_option('--release-logging', + dest='release_logging', + action='store_true') + +logging_group.add_option('--log-buffer', + type='int', + dest='log_buffer', + help='The number of log messages to keep in memory before writing to disk') + +parser.set_defaults(full_log=False, + limit_log=True, + release_logging=False, + log_buffer=0) + +#=============================================================================== +# Update +#=============================================================================== +update_group = optparse.OptionGroup(parser, + 'Update Options', + "Options dealing with behavior " + "after an update.") + +update_group.add_option('--force-update', + action = 'store_true', + dest = 'force_update', + help = 'Force an update & integrity check of application files') +update_group.add_option('--updated', + action="store_true", + help="Update Successful") + +update_group.add_option('--norestart', '--no-restart', '--no_restart', + action="store_false", + dest="do_restart", + help="disable restart after an update") +update_group.add_option('--noupdate', + '--no-update', + '--no_update', + action='store_false', + dest='allow_update', + help='disable update check') + +update_group.add_option('--update_failed', + action = 'store_true', + dest = 'update_failed', + help = 'signifies an update has failed to be installed') + +update_group.add_option('--updatetag') + +parser.set_defaults(updated=False, + do_restart=True, + force_update = False, + allow_update = True, + update_failed = False, + updatetag = None) + +#=============================================================================== +# Reflexive +#=============================================================================== +reflexive_group = optparse.OptionGroup(parser, 'Reflexive Options', + "Options designed for Digsby to call itself with. " + "It is believed that some of them bite.") +reflexive_group.add_option('--crashreport', type="string") +reflexive_group.add_option('--crashuser', type='string') +reflexive_group.add_option('--crashuniquekey', type='string') +reflexive_group.add_option('--no-crashreport', action = 'store_false', dest = 'crashreporting') + +parser.set_defaults(crashreporting = True) + +#=============================================================================== +# plura +#=============================================================================== +plura_group = optparse.OptionGroup(parser, 'Plura options', + "These options are to cause the app to set/unset plura processing. " + "Pretty much only used for the installer to pass in the user's choice.") +plura_group.add_option('--set-plura-enabled', action = 'store_true', dest = 'set_plura_option') +plura_group.add_option('--set-plura-disabled', action = 'store_false', dest = 'set_plura_option') + +parser.set_defaults(set_plura_option = None) + +#=============================================================================== +# twitter +#=============================================================================== +twitter_group = optparse.OptionGroup(parser, 'Twitter Options') +twitter_group.add_option('--dbsnapshot', type='string') +twitter_group.add_option('--savesnapshots', action='store_true') + +parser.set_defaults(twitter_offline=False, + dnsnapshot='', + savesnapshots=False) + +#=============================================================================== +# no-op +#=============================================================================== + +noop_group = optparse.OptionGroup(parser, 'No-ops', + description = + "quiets some problems when running unit tests") + +noop_group.add_option('--verbosity', action='store') + +#=============================================================================== +# Option group ordering +#=============================================================================== +parser.add_option_group(behave_group) +parser.add_option_group(debug_group) +parser.add_option_group(logging_group) +parser.add_option_group(update_group) +parser.add_option_group(reflexive_group) +parser.add_option_group(plura_group) +parser.add_option_group(twitter_group) +parser.add_option_group(noop_group) + +if __name__=="__main__": + parser.print_help() + print parser.parse_args() + diff --git a/digsby/src/oscar/OscarBuddies.py b/digsby/src/oscar/OscarBuddies.py new file mode 100644 index 0000000..a1ff96b --- /dev/null +++ b/digsby/src/oscar/OscarBuddies.py @@ -0,0 +1,1027 @@ +from __future__ import with_statement + +digsby = frozenset(('digsby',)) +ebuddy_caps = frozenset(('chat_service', 'file_xfer', 'utf8_support')) +aim5_caps = frozenset(('add_ins', 'avatar', 'buddy_list_transfer', 'chat_service', 'direct_im', 'file_xfer', 'voice_chat')) +pidgin_caps = frozenset(('avatar', 'chat_service', 'direct_im', 'file_xfer')) # used to include 'utf8_support' +ichat_caps = frozenset(list(pidgin_caps) + ['file_share']) +meebo_caps = frozenset(('avatar', 'chat_service', 'extended_msgs', 'icq_xtraz', 'mtn', 'utf8_support')) +aim6_caps = frozenset(('avatar', 'chat_service', 'direct_im', 'file_xfer', 'voice_chat')) +icq6_caps = frozenset(('buddy_list_transfer', 'chat_service', 'icq_html_msgs', 'icq_tzers', 'mtn', 'rtc_audio')) + +icq7_caps = frozenset(('icq7_unknown', )) + +clients_supporting_html = frozenset(('aim60', 'aim59', 'purple', 'digsby', 'miranda-aim', 'ichat', 'icq7')) + +import struct +import time, datetime + +from util.observe import ObservableDict, ObservableProperty; oproperty = ObservableProperty +import common +from common import profile +from common.actions import action +from logging import getLogger; log = getLogger('oscar.buddies') +import util +import oscar +from util.cacheable import cproperty +from util.callbacks import callsback +from util.lrucache import lru_cache +from util.primitives.funcs import get +from util.primitives.mapping import Storage, FilterDict +from util.primitives.strings import dequote, preserve_whitespace, strip_html2 + +from traceback import print_exc + +strip_html2 = lru_cache(80)(strip_html2) + +import re +body_pattern = re.compile('<(body|BODY)[^>]*?((bgcolor|BGCOLOR)=(.*?))? ?>') +font_back_pattern = re.compile('<(font|FONT)[^>]*?((back|BACK)=\s*[\'"]?(#[0-f]{6})[\'"]?).*?>') +blast_group_re = re.compile(r'\[.*\]') + +import oscar.capabilities + +statuses=Storage({ + 'free for chat':_('Free for Chat'), + 'busy':_('Occupied'), + 'unknown': _('Offline'), +}) + +# used to replace special strings in statuses and profiles +special_msg_strings = { + '%n': lambda buddy: buddy.protocol.username, + '%d': lambda buddy: datetime.date.today().strftime('%m/%d/%Y'), # 3/7/2007 + '%t': lambda buddy: datetime.datetime.now().strftime('%I:%M:%S %p') #12:20:07 PM +} + +def magic_string_repl(s, buddy): + # TODO: re.replace + for key, substitute_func in special_msg_strings.iteritems(): + if key in s: + s = s.replace(key, substitute_func(buddy)) + return s + +# if set to True, action preconditions will allow operations like file transfers +# and direct IMs regardless of the capabilities buddies are reporting. +IGNORE_CAPABILITIES = False + +def sanitize_aim_html(html): + html, bgcolor = _remove_html_cruft(html) + return html + +def _remove_html_cruft(s): + ''' + Attempts to fix up some of the badly formatted HTML catastrophes that emerge + from Pidgin and other AIM clients. + + For example: + +class/work + + Returns a tuple: (bgcolor, sanitized_string) + ''' + lower = s.lower() + + # Strip HTML tags + s = s[6:] if lower.startswith('') else s + s = s[:-7] if lower.endswith('') else s + + match = body_pattern.search(s) + + # find last BODY tag with a bgcolor, and save it + bgcolor = u'' + while match: + # remove body tags as we go + s = s[:match.start()] + s[match.end():] + groups = match.groups() + if groups[-1]: + bgcolor = groups[-1].strip() or bgcolor + match = body_pattern.search(s) + + # remove body end tag(s!) + s = s.replace('','').replace('','').replace('
', '
') + + # "BACK" isn't a real attribute to a FONT tag, replace it with the CSS equivalent + match = font_back_pattern.search(s) + while match: + backcolor = match.group(4) + s = ''.join((s[:match.start(2)], 'style="background-color:', backcolor, ';"', s[match.end(2):])) + match = font_back_pattern.search(s) + + if isinstance(s, str): + s = s.decode('fuzzy utf8') + + return s, bgcolor + +def aim_to_xhtml(s): + '''Wraps AIM HTML which might have and with a .''' + + s, bgcolor = _remove_html_cruft(s) + + if bgcolor: + bgstyle = ' style="background: %s;"' % dequote(bgcolor) + else: + bgstyle = '' + + return u'%s
' % (bgstyle, preserve_whitespace(s)) + +def make_dgetter(obj): + def dgetter(key): + ''' + Returns a unicode string by getting the attribute/item + 'key' from 'obj'. in debug mode will raise assertion errors + if the returned value is not unicode. + ''' + val = get(obj, key, u'') # will getitem or getattr + if not val: + # To fix None-type objects + val = u'' + + try: + assert isinstance(val, unicode), (key, val, obj) + except: + print_exc() + + if not isinstance(val, unicode): + return val.decode('fuzzy utf8') + return val + return dgetter + +def make_pretty_addr(d): + addrkeys = ('street', 'city', 'state', 'zip', 'country') + get = make_dgetter(d) + + def addytemplate(d): + fields = [] + for k in addrkeys: + fields.append(get(k)) + + return 'http://maps.google.com/maps?q=' + (u' '.join(filter(None, fields)).encode('utf-8').encode('url')) + + country = get('country') + zip = get('zip') + state = get('state') + city = get('city') + street = get('street') + + res = [] + add = lambda s: res.insert(0, s) + + if country: + add(u'\n' + country) + + if zip: + add(zip) + + if state: + if zip: + add(u', ') + add(state) + + if city: + if (state or zip): + add(u', ') + add(city) + + if street: + if (city or state or zip): + add(u'\n') + add(street) + + if res: add([u'(', (addytemplate(d), u'map'), u')\n']) + + return res + + +class OscarBuddy(common.buddy): + _name = None + def __init__(self, name, protocol): + self._status = 'unknown' + self._idle_time = None + self._friendly_name = None # will be set by OscarContact + self._avail_msg = self._away_msg = None + + self._user_class = \ + self.create_time = \ + self.signon_time = 0 + + common.buddy.__init__(self, oscar._lowerstrip(name), protocol) + if isinstance(self.name, unicode): + self.name = self.name.encode('utf8') + + self.account_creation_time = \ + self.online_time = None + + self.user_status_icq = 'offline' + self.external_ip_icq = 0 + + self._dc_info = util.Storage() + self._capabilities = [] + self.userinfo = {} + + if self._profile is False: + self._profile = None + + self._away_updated = self._mystery_updated = 0 + + self._waiting_for_presence = True + + def _get_name(self): + return self._name + def _set_name(self, val): + self._name = val + assert isinstance(val, bytes) + name = property(_get_name, _set_name) + + def __hash__(self): + return common.buddy.__hash__(self) + + @property + def sightly_status(self): + if self.mobile: + return _('Mobile') + else: + if self.service=='aim': + return statuses.get(self.status, _(self.status.title())) + else: + return statuses.get(self._status, _(self._status.title())) + + @property + def pretty_profile(self): + from util import odict + + if self.service=='aim': + try: + return self._pretty_profile + except AttributeError: + prof = [(aim_to_xhtml(self.profile) if self.profile else None)] + + from common import pref + if pref('infobox.aim.show_profile_link', True): + # shows an aimpages.com profile link + linkage=odict() + linkage['space']=4 + url=''.join(['http://www.aimpages.com/',self.name,'/profile.html']) + linkage[_('Profile URL:')]=['\n',(url,url)] + + prof += [linkage] + + self._pretty_profile = prof + return prof + else: + + ok=False + + profile=odict() + + bget = make_dgetter(self) + + if bget('first') or bget('last'): + profile[_('Full Name:')]=' '.join([self.first,self.last]) + ok=True + + personal = getattr(self, 'personal', {}) + if personal: + pget = make_dgetter(personal) + homeaddr = make_pretty_addr(personal) + else: + pget = lambda s:'' + homeaddr = '' + + keylabels = {'gender' : _('Gender'), + 'birthday' : _('Birthday')} + for key in keylabels: + if getattr(personal,key,False): + profile[_('{label}:').format(label = keylabels[key])] = pget(key) + ok=True + + + p_web = pget('website') + if p_web: + profile[_('Website:')] = (p_web, p_web) + ok=True + + + prof = bget('profile') + if prof: + if ok: profile['sep1']=4 + + try: + profstr = u'\n'+ prof + except Exception: + pass + else: + profile[_('Additional Information:')] = profstr + + ok=True + + if homeaddr: + if ok: profile['sep2']=4 + profile[_('Home Address:')] = homeaddr + ok=True + + work = getattr(self, 'work', {}) + if work: + wget = make_dgetter(work) + workaddr = make_pretty_addr(work) + else: + wget = lambda s:'' + workaddr ='' + + if workaddr: + if ok: profile['sep3']=4 + profile[_('Work Address:')] = workaddr + ok=True + + if ok: + profile['sep4']=4 + ok=False + + keylabels = {'company' : _('Company'), 'department' : _('Department'), 'position' : _('Position')} + for key in keylabels: + if getattr(work,key,False): + profile[_('{label}:').format(label = keylabels[key])] = wget(key) + ok=True + + w_web = wget('website') + if w_web: + profile[_('Work Website:')] = (w_web, w_web) + ok=True + + if ok: + ok=False + profile['sep5']=4 + + url=''.join([u'http://people.icq.com/people/about_me.php?uin=',self.name]) + profile[_('Profile URL:')]=['\n',(url,url)] + + + return profile + + @property + def service(self): + return 'icq' if self.name.isdigit() else 'aim' + + @property + def icq(self): + return self.service == 'icq' + + def update(self, userinfo): + self._idle_time = None + + notifyattrs = set() + notify_append = notifyattrs.add + is_self_buddy = self.protocol.self_buddy is self + + with self.frozen(): + for k, v in userinfo.iteritems(): + if isinstance(k, basestring): + if k == 'status': + if is_self_buddy: + continue + if v == 'online': + v = 'available' + self.status = v + notify_append('status') + elif k == 'avail_msg': + self._set_avail_msg(v, False) + notify_append('status_message') + else: + try: + setattr(self, k, v) + notify_append(k) + except AttributeError, e: + print_exc() + + else: + self.userinfo[k] = v + + if self._status != 'away': + self._waiting_for_presence = False + + notify = self.notify + + if self._status == 'unknown': + notify_append('status') + self.status = 'offline' + + for attr in notifyattrs: + notify(attr) + + def _get_status_orb(self): + + if self.idle: + return 'idle' + elif self.away: + return 'away' + elif self.status == 'unknown': + return 'offline' + else: + from common.Buddy import get_status_orb + return get_status_orb(self) + + status_orb = oproperty(_get_status_orb, observe = 'status') + + def get_profile(self): + p = self._profile + if p is not None: + p = magic_string_repl(p, self) + + return p + + def set_profile(self, profile): + 'Invoked by the network upon a new incoming profile for this buddy.' + + try: + del self._pretty_profile + except AttributeError: + pass + + self._profile = profile + + profile = property(get_profile, set_profile, + doc = "This buddy's AIM profile or ICQ 'about me'.") + + # stores a timestamp for the profile's last updated time. + _profile_updated = cproperty(0) + + def set_profile_updated(self, netint): + self._profile_updated = struct.unpack('!I', netint)[0] \ + if isinstance(netint, basestring) else netint + + profile_updated = property(lambda b: b._profile_updated, set_profile_updated) + + def set_away_updated(self, netint): + old = self._away_updated + self._away_updated = struct.unpack('!I', netint)[0] \ + if isinstance(netint, basestring) else netint + + if old != self._away_updated: + self.protocol.get_buddy_info(self) + + away_updated = property(lambda b: b._away_updated, set_away_updated) + + def set_mystery_updated(self, netint): + self._mystery_updated = struct.unpack('!I', netint)[0] \ + if isinstance(netint, basestring) else netint + + mystery_updated = property(lambda b: b._mystery_updated, set_mystery_updated) + + @property + def sms(self): + return self.name.startswith('+') + + # stores the profile string, cached to disk. + _profile = cproperty(None) + + def request_info(self, profile, away): + "Request this buddy's profile or away message from the server." + + if not profile and not away: return + + self.protocol.get_buddy_info(self, profile = profile, away = away) + + + def set_nice_name(self, name): + self._nice_name = name + + def get_nice_name(self): + return getattr(self, '_nice_name', self.name) + + nice_name = property(get_nice_name, set_nice_name) + + + def _set_capabilities(self, newval): + if self is self.protocol.self_buddy: + # + # incoming family_x01_x0f packets were calling self_buddy.update and setting this + # value to None, I think...clearing the list. + # + # TODO: can the server ever set our capabilities for us? + # + return + + caps = [] + while newval: + caphi, caplo, newval = oscar.unpack((('caphi', 'Q'), + ('caplo', 'Q')), + newval) + caps.append(struct.pack('!QQ', caphi, caplo)) + + + if (self._capabilities) and (len(caps) == 1) and (caps[0] == oscar.capabilities.by_name['chat_service']): + log.info('Received dummy capabilities for %r, not setting them', self.name) + return + + self._capabilities = caps + + log.debug('%r\'s guessed client is %r. caps are: %r', self, self.guess_client(), self.pretty_caps) + + capabilities = ObservableProperty(lambda self: self._capabilities, _set_capabilities, observe = '_capabilities') + + @property + def pretty_caps(self): + return map(lambda x:oscar.capabilities.by_bytes.get(x, x), self._capabilities) + + @property + def caps(self): + + from common import caps as digsbycaps + import oscar.capabilities as oscarcaps + protocaps = list(self.protocol.caps) # copy it so we don't modify the protocol's copy. + mycaps = [oscarcaps.by_bytes.get(x, None) for x in self.capabilities] + + if 'file_xfer' not in mycaps: + #protocaps.remove(digsbycaps.FILES) + pass + + if oscar._lowerstrip(self.name) in self.protocol.bots or blast_group_re.match(oscar._lowerstrip(self.name)): + protocaps.append(digsbycaps.BOT) + + return protocaps + + @property + def accepts_html_messages(self): + # This should technically be: + # return 'xhtml_support' in self.pretty_caps + # BUT pidgin supports HTML (their IM window is an HTML widget) + # and they don't report that capability. So, we have to guess what + # type of client they are on based on the capabilities. + # + # Lucky for us, the main thing to be concerned about is official + # ICQ clients, and they have very bizarre capability sets. + + # If any of these are true, they probably have HTML support + client = self.guess_client() + selficq = self.protocol.icq + + if client == 'digsby': + return True + + if client == 'miranda-icq' and not selficq: + return True + +# if selficq and not self.icq and client == 'purple': +# return True + if client == 'purple' and selficq: + return not self.icq + + if client == 'ebuddy': + return True + + if client == 'icq7': + return False + + if client == 'qip-2005': + return not (selficq and self.icq) + + caps = set(self.pretty_caps) + if 'xhtml_support' in caps: + return True + + if 'icq_html_msgs' in caps and not selficq: + return True + + if client in clients_supporting_html: + return True + + if 'rtf_support' in caps: + return False + + if client == 'unknown': + return not (selficq and self.icq) + + return False + + @property + def sends_html_messages(self): + return self.get_sends_html_messages(False) + + def get_sends_html_messages(self, ischat=False): + client = self.guess_client() + selficq = self.protocol.icq + + if client == 'digsby': + return True + + if (not selficq) and client in ('miranda-icq',): + return True + + if client == 'miranda-aim': + return True + + if client in ('icq7',): + return False + + if selficq ^ self.icq and client in ('icq6', 'purple'): + return True + + if self.icq and client in ('purple', 'icq6', 'qip-2005', 'miranda-icq', 'icq7'): + if client == 'purple' and ischat: # meebo sends ICQ chat messages as HTML + return True + elif client == 'icq7' and ischat: + return True + else: + return False + + if selficq and ischat: + return True + + if client == 'qip-2005': + return False + + if client == 'ebuddy': + return True + + return self.accepts_html_messages + + + def guess_client(self): + ''' + Guess a buddy's client based on capabilities. + ''' + + # Hopefully this combination will seperate the official ICQ clients + # from others + caps = set(self.pretty_caps) + + might_be_icq = False + if caps.issuperset(icq6_caps): + might_be_icq = True + + if any(x.startswith('Miranda') for x in caps): + if caps.issuperset(('mtn', 'icq_xtraz')): + return 'miranda-icq' + else: + return 'miranda-aim' + elif caps.issuperset(meebo_caps): + return 'purple' + elif caps.issuperset(icq7_caps): + return 'icq7' + elif any('QIP 2005' in x for x in caps): + return 'qip-2005' + elif caps.issuperset(aim5_caps): + return 'aim59' + elif 'digsby' in caps: + return 'digsby' + elif caps.issuperset(aim6_caps) or 'aim6_unknown1' in caps: + return 'aim60' + elif caps.issuperset(ichat_caps): + return 'ichat' + elif caps.issuperset(pidgin_caps): + return 'purple' + elif self.mobile or self.sms: + return 'mobile' + elif caps.issuperset(ebuddy_caps): + if might_be_icq: + return 'icq6' + else: + return 'ebuddy' + else: + if might_be_icq: + return 'icq6' + else: + return 'unknown' + + def _set_dc_info(self, newval): + dc_info, data = oscar.unpack((('_', 'dc_info'),), newval) + if data: print 'extra data on dc_info for %s: %s' % (self.name, util.to_hex(data)) + + self._dc_info = dc_info + + dc_info = ObservableProperty(lambda self: self._dc_info, _set_dc_info, observe = '_dc_info') + + def _set_user_class(self, newval): + self._user_class = struct.unpack('!H', newval)[0] + + user_class = ObservableProperty(lambda self: self._user_class, + _set_user_class, observe = '_user_class') + + def _set_avail_msg(self, newval, notify=True): + + old = self._avail_msg + tflvs = {} # type, flag, length, value + try: + tflvs_list, newval = oscar.unpack((('values', 'tflv_list'),), newval) + except Exception: + return + + for tflv in tflvs_list: + tflvs[tflv.t] = tflv.v + + if 1 in tflvs: + self.setnotifyif('icon_hash', tflvs[1]) + if 2 in tflvs: + + if len(tflvs[2]) > 0: + fmt = ( + ('msglen','H'), + ('msg', 's', 'msglen'), + ('numtlvs', 'H'), + ('tlvs', 'tlv_list', 'numtlvs'), + ) + + try: + __, msg, __, tlvs, tflvs[2] = oscar.unpack(fmt, tflvs[2]) + except Exception, e: + log.error('Error unpacking available message. (exc=%r, data=%r)', e, tflvs[2]) + msg = None + + if self is self.protocol.self_buddy: + # Much like capabilities, we don't want to allow this to be + # set from the network. we know what our status message is, + # thank you very much. + + # Of course this doesn't really work when signed in from another location. + # Keep that a secret, 'kay? + return + + if msg is None: + self._avail_msg = None + self._cached_status_message = None + return + + codecs = ['fuzzy', 'utf-8'] + + if tlvs: + log.info('Got extra tlvs for availmsg: %r', tlvs) + codecs[1:1] = [tlv.v for tlv in tlvs] + + self._avail_msg = msg.decode(' '.join(codecs)) + self._cached_status_message = None + else: + self._avail_msg = None +# else: +# notify = self._avail_msg is not None +# self._avail_msg = None + + if notify: + self.notify('avail_msg', old, self._avail_msg) + + def _get_avail_msg(self): + return self._avail_msg + avail_msg = property(_get_avail_msg, _set_avail_msg) + + def __repr__(self): + return '' % oscar._lowerstrip(self.name) + + def get_idle(self): + 'Returns a unix time or None.' + + return self._idle_time + + def set_idle(self, val): + self._idle_time = val + + idle = ObservableProperty(get_idle, set_idle, observe = '_idle_time') + + def set_idle_time(self, netidle): + if isinstance(netidle, basestring): + self._idle_time = int(time.time() - 60*struct.unpack('!H', netidle)[0]) + elif isinstance(netidle, int): + self._idle_time = netidle + elif netidle is None: + self._idle_time = None + else: + log.warning('set_idle_time received %r', netidle) + + def get_idle_time(self): + i = self._idle_time + return None if i is None else int(i) + + idle_time = property(get_idle_time, set_idle_time, None, 'Set by the network.') + + def get_status(self): + if self._status == 'offline': + if self.mobile: + return 'mobile' + else: + return 'offline' + + if self._status == 'unknown' or self._waiting_for_presence: + return 'unknown' + elif self.away or self._status == 'away': + return 'away' + elif self.idle or self._status == 'idle': + return 'idle' + else: + return 'available' + + def set_status(self, newval): + if self._status != newval: + try: + # invalidate any cached formatted profile + del self._pretty_profile + except AttributeError: + pass + + self._status = newval + + status = ObservableProperty(get_status, set_status, observe = '_status') + + def set_away_msg(self, away_msg): + if not isinstance(away_msg, (basestring, type(None))): + raise TypeError(str(type(away_msg))) + + self._waiting_for_presence = False + self._away_msg = away_msg + + def get_away_msg(self): + return self._away_msg + + away_msg = property(get_away_msg, set_away_msg) + + + + def get_online(self): + 'Returns True if the buddy is not offline' + return self._status != 'offline' and self._status != 'unknown' + + online = ObservableProperty(get_online, observe = '_status') + + # + # away: a buddy is online and the away flag is set + # + + def _away(self): + if self.user_class: + v = bool(self.user_class & 0x20) + else: + v = self._status == 'away' + + return self.online and v + + away = ObservableProperty(_away, observe = 'user_class') + +# @action(lambda self: IGNORE_CAPABILITIES or +# oscar.capabilities.by_name['direct_im'] in self.capabilities) + def direct_connect(self): + return self.protocol.direct_connect(self) + + def _get_invisible(self): + return bool(self.user_class & 0x00000100) + + def _set_invisible(self, invis): + if invis: + self._user_class |= 0x00000100 + else: + self._user_class &= 0xFFFFFEFF + + invisible = ObservableProperty(_get_invisible, _set_invisible, observe = 'user_class') + + def _get_stripped_msg(self): + msg = strip_html2(self.status_message or '') + try: + return msg.decode('xml') + except Exception: + return msg + + stripped_msg = ObservableProperty(_get_stripped_msg, observe = 'status_message') + + def get_status_message(self): + if self.away and self.away_msg and self.away_msg.strip(): + return aim_to_xhtml(magic_string_repl(self.away_msg, self)) + elif self.avail_msg and self.avail_msg.strip(): + return aim_to_xhtml(magic_string_repl(self.avail_msg.encode('xml'), self)) + else: + return None + +# def get_status_message(self): +# if getattr(self, '_cached_status_message', None) is None: +# self._cached_status_message = self._cache_status_message() +# +# return self._cached_status_message + + def set_status_message(self, val): + if not isinstance(val, (basestring, type(None))): + raise TypeError(str(type(val))) + self.away_msg = self._avail_msg = val + + status_message = ObservableProperty(get_status_message, set_status_message, + observe = ['away_msg', '_avail_msg']) + + + def _mobile(self): + # Don't include self buddy as it can never be mobile if we are logged in - however, + # something gets confused sometimes and it appears mobile without this check. + return (len(self.capabilities) == 0) and (self.user_class and (self.user_class & 0x80 != 0)) and self.protocol.self_buddy != self + mobile = ObservableProperty(_mobile, observe = 'user_class') + + def _set_online_time(self, val): + if isinstance(val, str) and len(val) == 4: + self._online_time = time.time() - struct.unpack('!I', val)[0] + else: + self._online_time = val + + def _get_online_time(self): + return self._online_time + + online_time = ObservableProperty(_get_online_time, _set_online_time, observe = '_online_time') + + def __str__(self): + return self.name + + def __cmp__(self, other): + + if not isinstance(other, self.__class__): + return -1 + + a, b = oscar._lowerstrip(self.name), oscar._lowerstrip(other.name) + return cmp(a,b) + + def get_buddy_icon(self): + self.protocol.get_buddy_icon(self.name) + + @property + def blocked(self): + if not self.protocol.icq: + return oscar._lowerstrip(self.name) in self.protocol.block_list + else: + return oscar._lowerstrip(self.name) in self.protocol.ignore_list + + @property + def pending_auth(self): + '''Subclasses can override this to indicate that the buddy is waiting + for a user's authorization.''' + return False + + def get_remote_alias(self): + a = getattr(self, 'nick', None) + + if not a: + a = '%s %s' % (getattr(self, 'first', ''), getattr(self, 'last', '')) + a = a.strip() or None + + return a + + remote_alias = oproperty(get_remote_alias, observe=['first', 'last', 'nick']) + + first = cproperty() + last = cproperty() + nick = cproperty() + personal = cproperty() + work = cproperty() + + def get_alias(self): + # overridden from Buddy.alias property to show nice_name + val = None + a = profile.get_contact_info(self, 'alias') + if a and a.strip(): + val = a + else: + for attr in ('local_alias', 'remote_alias', '_friendly_name', 'nice_name'): + val = getattr(self, attr, None) + if val: + break + else: + val = self.name + + return val.decode('fuzzy utf-8') + + alias = oproperty(get_alias, observe = ['local_alias', 'remote_alias', 'nice_name']) + + @callsback + def block(self, set_blocked=True, callback = None): + return self.protocol.block(self, set_blocked, callback = callback) + + @callsback + def unblock(self, callback = None): + return self.block(False, callback = callback) + + def permit(self, set_allowed=True): + return self.protocol.permit(self, set_allowed) + + def unpermit(self): + return self.permit(False) + + + def warn(self, anon=True): + return self.protocol.warn(self, anon) + + +class OscarBuddies(ObservableDict, FilterDict): + _dead = False + + def __init__(self, protocol): + ObservableDict.__init__(self) + FilterDict.__init__(self, oscar._lowerstrip) + self.protocol = protocol + + def __getitem__(self, k): + #you shouldn't be getting a buddy from a disconnected protocol + #note: this assert will get hit if you disconnect while still logging in. + assert not self._dead + try: + return FilterDict.__getitem__(self, k) + except (KeyError,): + return self.setdefault(self.ff(k), OscarBuddy(k, self.protocol)) + + def update_buddies(self, infos): + 'Updates one or more buddies.' + + for info in infos: + self[info.name].update(info) + + def kill(self): + self._dead = True + diff --git a/digsby/src/oscar/OscarConversation.py b/digsby/src/oscar/OscarConversation.py new file mode 100644 index 0000000..3dd8049 --- /dev/null +++ b/digsby/src/oscar/OscarConversation.py @@ -0,0 +1,405 @@ +from __future__ import with_statement +import struct, logging, os.path +import oscar, common, util +S = util.Storage + +from util import callsback, gen_sequence +from util.primitives.funcs import get +log = logging.getLogger('oscar.conversation'); info = log.info + + +class OscarConversation(common.Conversation): + + def __init__(self, o, type='im', cookie=None, roomname=None): + common.Conversation.__init__(self, o) + self.type = type + self.buddies = oscar.buddies(self.protocol) + self.self_buddy = o.self_buddy + if self.type != 'chat': + return + + self.sock = None + self.encoding = o.encoding + self.lang = o.lang + self.country = o.country + + self.cookie = cookie + self.roomname = roomname + self.name = 'OscarChat' + self.cookie + + @property + def chat_room_name(self): + return self.roomname + + def _update_chat_info(self, chat_info): + self.roomname = chat_info.name + + def __repr__(self): + return '' % \ + ((' (chatroom %s)' % self.name) if self.ischat else '', id(self), getattr(self.buddy, 'name', '??')) + + def __contains__(self, buddy): + bname = get(buddy, 'name', buddy) + + if bname in self.buddies: + buddy = self.buddies[bname] + + else: + buddy = self.buddies[bname] = self.protocol.buddies[bname] + + return buddy in self.room_list + + @property + def ischat(self): + return self.type == 'chat' + + @callsback + def invite(self, buddyname, message = None, callback=None): + if not self.ischat: + return log.warning('this conversation is not a chat room') + + if not isinstance(buddyname, basestring): + buddyname = buddyname.name + + log.info('%r INVITE %r', self, buddyname) + + import oscar.rendezvous.chat + oscar.rendezvous.chat.invite(self.protocol, buddyname, self.cookie, callback=callback) + + @gen_sequence + @callsback + def connect_chat_socket(self, callback = None): + 'Initializes the socket for the chatroom.' + + log.info('connect_chat_socket') + + me = (yield None); assert me + if self.cookie not in self.protocol.sockets: + + log.info('creating chat room...') + self._create_chat_room(me()) + log.info('returned from create chat room') + exchange, cookie, instance, detail, tlvs = (yield None) + + #if not cookie == self.cookie: + #log.warning('cookies did not match:\nself.cookie: %r\n cookie: %r', self.cookie, cookie) + self.exchange = exchange + self.cookie = cookie + if not self.cookie.startswith('!'): + self.cookie = '!' + self.cookie + + log.info('room created') + + self.room_info = tlvs + + log.info('requesting service...') + self.protocol.service_request(self.cookie, me()) + self.sock = (yield None) + log.info('...service acquired: %r', self.sock) + else: + self.sock = self.protocol.sockets[self.cookie] + + callback.success(self) + + @gen_sequence + def _create_chat_room(self, parent): + me = (yield None); assert me + self.protocol.send_snac(*oscar.snac.x0d_x08(self.protocol, self.roomname, self.cookie), + req=True, cb=me.send) + parent.send(self.protocol.gen_incoming((yield None))[-1]) + + def send_snac(self, *args, **kwargs): + self.protocol.send_snac(self.cookie, *args, **kwargs) + + @callsback + def _send_message(self, message, callback=None, **kwargs): + getattr(self, 'send_message_%s' %self.type, self.send_message_unknown)(message, callback=callback, **kwargs) + + def send_message_unknown(self, message, callback, **kwargs): + log.warning('Unknown type in OscarConversation: %r. args were: %r', self.type, (message, callback, kwargs)) + + def send_message_im(self, fmsg, auto=False, callback=None, **options): + buddy = self.get_im_buddy() + sn = buddy.name + buddy = self.protocol.buddies[sn] + + if isinstance(sn, unicode): + _sn, sn = sn, sn.encode('utf-8') + + if buddy.accepts_html_messages: + log.info('buddy accepts html messages') + html = True + network_msg = fmsg.format_as('html') + network_msg = network_msg.replace('\n', '
') + else: + log.info('buddy does not accept html messages. not encoding html') + html = False + network_msg = fmsg.format_as('plaintext') + + if self.protocol.icq and not buddy.online: + save = True + else: + save = False + # Send messages out on a higher priority than usual. + fam, sub, data = oscar.snac.snd_channel1_message(self.protocol, sn, network_msg, + req_ack = not auto, save = save, + auto = auto, html = html) + + if auto: + sock_callback = lambda *a, **k: None + else: + + def sock_callback(sock, snac): + self._sent_msg(sock, snac, fmsg, auto, save, callback) + + self.protocol.send_snac(fam, sub, data, priority=4, req=True, cb=sock_callback) + + # echo auto messages back to the GUI - we don't ask for an ack so it will never come. + if auto: + callback.success() + + + def _sent_msg(self, sock, snac, fmsg, auto, save, callback): + fam = snac.hdr.fam + sub = snac.hdr.sub + + def error(): + log.error('Bad snac in message callback: %r', (sock, snac, fmsg, auto, save)) + callback.error(snac) + + if fam != 4: + return error() + + if sub == 1: + + errcode, = struct.unpack('!H', snac.data[:2]) + + if errcode == 0x4: + # Recipient not logged in + + if auto: + log.info('Could not send auto-response') + return + if save: + log.info('Could not save message') + return + + log.info('Message could not be sent, trying to send again with "save" flag set.') + + sn = self.get_im_buddy().name + # Send messages out on a higher priority than usual. + fam, sub, data = oscar.snac.snd_channel1_message(self.protocol, sn, fmsg.format_as('html'), save=True, auto=auto) + if self.protocol.icq: + self.protocol.send_snac(fam, sub, data, priority=4, req=True, cb=lambda sock,snac:None) + log.info("Message sent (not waiting for confirmation because account is ICQ)") + + else: + self.protocol.send_snac(fam, sub, data, priority=4, req=True, + cb=lambda sock, snac: self._sent_msg(sock, snac, fmsg.format_as('html'), auto, True, callback)) + + elif errcode == 0xA: + # Message refused by client -- disabled offline messaging. + log.info('Buddy does not accept offline messages. Could not send message.') + log.info_s('\t\tBy the way, the message was: %r', fmsg) + error() + else: + return error() + + elif sub == 0xC: + log.info("Message successfully sent") + callback.success() + + elif sub == 0xE: + log.info('Message was incorrectly formatted! Check oscar.send_message') + return error() + + else: + return error() + + + + def send_message_dc(self, msg, callback): + self.dc.send_message(msg.encode('utf-8')) + self.sent_message(msg) + + def send_message_chat(self, msg, public=True, reflect=True, callback=None): + self.send_snac(*oscar.snac.x0e_x05(self, msg.format_as('html'), public, reflect)) + + def send_image(self, imgs): + ''' + Oscar DirectIM images look like + + + + + GIF89..[binary data].. + ''' + #TODO: move to directim.py + + if not self.type == 'dc': + return log.warning('only direct connections can send images') + + if not isinstance(imgs, list): imgs = [imgs] + + msg, bin = '', '' + + for i, img in enumerate(imgs): + with open(img.filename, 'rb') as f: imgdata = f.read() # grab bytes + imgname = os.path.split(img.filename)[1] # get filename part of path + + id = i + 1 # ID's start at 1. + w, h = img.size + size = os.path.getsize(img.filename) # size in bytes of image file + + msg += str('' \ + % (imgname, id, w, h, size)) + bin += '' % (id, size) + imgdata + '' + + msg += '' + bin += '' + + self.dc.send_message( [msg, bin] ) + + # This message will be displayed on our own IM window. + selfmessage = '' % img.filename + self.sent_message(selfmessage) + + def set_typing_status(self, bname, status): + if bname not in self: + self.buddy_join(bname) + + if bname is self.self_buddy.name: + self.send_typing_status(status) + + self.typing_status[self.buddies[bname]] = status + + def send_typing_status(self, status=None, channel=1): + if self.type == 'chat': + return + + if self.type == 'im': + bname = self.get_im_buddy().name + + if isinstance(bname, unicode): + _bname, bname = bname, bname.encode('utf-8') + + if not self.protocol.buddies[bname].online or self.protocol.buddies[bname].isbot: + return + + self.protocol.send_snac(*oscar.snac.x04_x14(status=status, + bname=bname, + channel=channel)) + if self.type == 'dc': + self.dc.send_typing(status) + + def get_im_buddy(self): + others = [b for b in self.room_list if b.name + != self.self_buddy.name] + + if others: return others[0] + else: return self.self_buddy + + def incoming(self, sock, snac): + assert self.type == 'chat' and sock is self.sock + f = getattr(oscar.snac, 'x%02x_x%02x' % (snac.hdr.fam, snac.hdr.sub), None) + if f is None: + log.warning('%r got an unknown snac: %r', self, snac) + else: + f(self, sock, snac.data) + + def buddy_join(self, buddy): + if isinstance(buddy, basestring): + buddy = self.protocol.buddies[buddy] + assert isinstance(buddy, oscar.buddy) + + if not self.ischat and buddy not in self and len(self.room_list) >= 2: + self.system_message(buddy.name + ' joined the room') + + self.buddies[buddy.name] = self.protocol.buddies[buddy.name] + + if buddy is self.protocol.self_buddy: + self.self_buddy = buddy + + self.typing_status[buddy] = None + self.update_list() + + super(OscarConversation, self).buddy_join(buddy) + + def buddy_leave(self, buddy): + self.buddies.pop(buddy.name.lower().replace(' ','')) + self.typing_status.pop(buddy) + self.update_list() + + if not self.ischat and buddy is not self.self_buddy: + self.system_message(buddy.name + ' left the room') + + super(OscarConversation, self).buddy_leave(buddy) + + def incoming_message(self, bname, msg, auto = False, offline = False, timestamp = None, **opts): + assert bname in self + + if not isinstance(msg, unicode): + log.warning('incoming_message: msg is not unicode, trying a last resort .decode(replace)') + msg = msg.decode('utf-8', 'replace') + + if self.protocol.icq: + msg = msg.replace('\n', '
') + + # AIM's iPhone client sends "
message
" + if msg[:5] == '
' and msg[-6:] == '
': + msg = msg[5:-6] + + b = self.buddies[bname] + + did_receive = self.received_message(b, msg, sms = bname.startswith('+'), + auto = auto, offline = offline, timestamp = timestamp, **opts) + + if b in self.typing_status: + self.typing_status[b] = None + + if b is self.protocol.buddies['aolsystemmsg']: + # Regardless of all other conditions, this one makes sure we won't respond to aolsytsemmsg + autoresp = False + else: + # this flag does not mean auto response will happen, just that it is allowed + autoresp = True + if did_receive: + common.Conversation.incoming_message(self, autoresp) + + def update_list(self): + self.room_list[:] = sorted(self.buddies.values()) + + def get_name(self): + if self.ischat: + return self._room_name + else: + if self.room_list and len(self.room_list) < 3: + buddy = self.get_im_buddy() + name = buddy.alias + status = self.typing_status.get(buddy, None) + if status: name += ' (%s)' % status + return name + else: + return self._room_name + + def set_name(self, new_name): + self._room_name = new_name + + name = property (get_name, set_name) + + _did_exit = False + def exit(self): + if self._did_exit: + return + self._did_exit = True + + self.buddy_leave(self.self_buddy) + self.maybe_send_typing_status(None) + + if self.type == 'dc': + # If this conversation window had a direct IM connection, then + # close it. + self.dc.disconnect() + + self.protocol.exit_conversation(self) + common.Conversation.exit(self) diff --git a/digsby/src/oscar/OscarProtocol.py b/digsby/src/oscar/OscarProtocol.py new file mode 100644 index 0000000..04f51d6 --- /dev/null +++ b/digsby/src/oscar/OscarProtocol.py @@ -0,0 +1,1724 @@ +''' + +OSCAR protocol + +''' +from __future__ import with_statement + +import logging +import traceback + +from hashlib import md5 +import hub, util.observe as observe, common, util +import random, sys +from common import profile, netcall +from common.actions import ActionMeta, action + +import oscar +from oscar import OscarException +import oscar.snac as snac +import oscar.ssi as ssi + +from util import callsback, Timer +from util.primitives.error_handling import traceguard +from util.primitives.funcs import do +from util.rtf import rtf_to_plain + +import oscar.rendezvous.filetransfer as filetransfer + +from traceback import print_exc +from datetime import datetime +import time +import struct +from oscar.api import ICQ_API, AIM_API, get_login_cookie +from util.threads.threadpool2 import threaded + +log = logging.getLogger('oscar'); info = log.info + +hub = hub.get_instance() + +aim_only = lambda self, *a: None if self.icq else True +icq_only = lambda self, *a: True if self.icq else None + +AVAIL_MAX_LEN = 250 +SERVICE_REQUEST_TIME_SECS = 30 * 60 + +class OscarProtocol(common.protocol): + __metaclass__ = ActionMeta + + USE_NEW_MD5 = True + + name = 'oscar' + client_string = 'ICQ Client' #"Digsby v0.99 alpha" + + def _get_locale_info(self, key, default): + try: + return getattr(hub, 'get_%s' % key)().encode('utf-8') + except Exception, e: + log.error('There was an error getting the %r for oscar: %r', key, e) + print_exc() + return default + + def get_lang(self): + return self._get_locale_info('language', 'en') + + def get_country(self): + return self._get_locale_info('country', 'us') + + def get_encoding(self): + return 'utf-8' + + lang = property(get_lang) + country = property(get_country) + encoding = property(get_encoding) + + is_ordered = True + + max_icon_bytes = 7168 + max_icon_size = (48, 48) + icon_formats = ['JPEG', 'BMP', 'GIF'] # server rejects PNG + + bots = common.protocol.bots | set(['aolsystemmsg', 'twitterim']) + + supports_group_chat = True + + def email_hint(self, contact): + if self.icq: return '' + else: return contact.name + '@aol.com' + + contact_order = True + + def __init__(self, username, password, user, server, login_as = 'online', protocol = None, **extra_args): + common.protocol.__init__(self, username, password, user) + + #before init_buddy_vars, so that we're not still "oscar" when the self buddy is created. + + if protocol == 'icq': + self.icq = True + self.name = 'icq' + elif protocol == 'aim': + self.icq = False + self.name = 'aim' + else: + assert protocol is None + try: + int(self.username) + except Exception: + self.icq = False + self.name = 'aim' + else: + self.icq = True + self.name = 'icq' + if self.icq: + self.icq_number = extra_args.pop('icq_number', None) + if self.icq_number is not None: + self.username = self.icq_number + + self.log = log + + self.init_buddy_vars() + self.disconnected = False + + self.init_server = server + self.status = login_as + self.user = user + + self._search_disclosure = sentinel + + self.icq_req_cbs = {} + + self.block_unknowns = extra_args.get('block_unknowns', False) + + self.webaware = extra_args.get('webaware', False) + self.auth_required = extra_args.get('auth_required', False) + + self._keepalive_timer = Timer(common.pref('oscar.keepalive', type = int, default = 30), + self.on_keepalive) + + self.endpoints = None + + self.block_url_requests = extra_args.get('block_url_requests', True) + + def has_buddy_on_list(self, buddy): + bname = oscar._lowerstrip(buddy.name) + if bname in self.buddies: + for group in self.root_group: + for listbuddy in group: + if oscar._lowerstrip(listbuddy.name) == bname and buddy.service == listbuddy.service: + return True + + def init_buddy_vars(self): + self.buddies = oscar.buddies(self) + self.self_buddy = self.buddies[self.username] + + self.sockets = {} + self.conversations = observe.observable_dict() + self.ssimanager = ssi.manager(self) + self.root_group = self.ssimanager.ssis.root_group + + def get_login_info(self, pass_hash): + info = self.client_info() + + data = ((1, oscar._lowerstrip(self.username)), + (0x3, self.client_string), + (0x25, pass_hash), + (0x16, 2, info[0]), # client id + (0x17, 2, info[1]), # client major version + (0x18, 2, info[2]), # client minor version + (0x19, 2, info[3]), # client lesser version + (0x1a, 2, info[4]), # client build number + (0x14, 4, info[5]), # distribution number + (0x0f, self.lang), # client language + (0x0e, self.country), # client country + ) + + if self.USE_NEW_MD5: + data += ((0x4c, ''), # new authentication method?! + ) + + if self.icq: + pass + else: + data += ((0x4a, 1, True), + ) + + return data + def client_info(self): + if self.icq: + info = (0x010a, 0x06, 0x05, 0, 0x03ed, 0x7537) # ICQ6.5 + #info = (0x010a, 0x14, 0x34, 0, 0x0c18, 0x043d) # ICQBasic + #info = (0x010a, 0x06, 0x00, 0x00, 0x1bb7, 0x7535) # ICQ6.0 + #info = (0x010a, 0x07, 0x00, 0, 0x0410, 0x7538) + #info = (0x010a, 0x14, 0x34, 0, 0x0bb8, 0x043d) + else: + info = (0x0109, 0x05, 0x09, 0, 0x0f15, 0x0000) + + return info + + def on_keepalive(self): + def do_keepalive(socket): #this is here to pull the closure away from the for loop below + log.info('sending keepalive on %r', socket) + # Channel 5 flap is keep alive + netcall(lambda: socket.send_flap(0x5, '')) + + for socket in set(filter(lambda x: isinstance(x, oscar.socket), self.sockets.values())): + do_keepalive(socket) + + for k in self.sockets: + if isinstance(self.sockets[k], SnacQueue): + if k in (1, 'bos',): + continue + log.info('requesting service %r', k) + self.service_request(k) + + self._keepalive_timer.start() + + @property + def caps(self): + from common import caps + + return [caps.INFO, + caps.IM, + caps.FILES, + caps.EMAIL, + caps.SMS, + caps.BLOCKABLE, + caps.VIDEO] + + def __repr__(self): + return '' % (self.username, self.state) + + @action(lambda self, *a, **k: True if self.state == self.Statuses.OFFLINE else None) + def Connect(self, invisible = False): + log.info('connecting %s (as %s)...', self, 'invisible' if invisible else 'available') + + # set the invisible flag in the self buddy if necessary. + self.self_buddy.invisible = invisible + + self.change_state(self.Statuses.CONNECTING) + self._icq_check() + + def _icq_number(self): + try: + int(self.username) + except Exception: + pass + else: + return self.username + return self.icq_number + + def _icq_check(self): + if self.icq: + icq_number = self._icq_number() + if not icq_number: + from .api import get_login_data, ICQ_API + from util import callbacks + callbacks.callsback(get_login_data)(self.username, self.password, ICQ_API, + success = self._icq_check_success, + error = self._icq_check_fail) + else: + self._set_icq_username(icq_number) + self._icq_check_done() + else: + self._icq_check_bypass() + + def _icq_check_success(self, req, ret=None): + if ret is None: + ret = req + try: + data = ret.read() + import simplejson + data = simplejson.loads(data) + if self.icq: + username = data['response']['data']['loginId'] + acct = self.account + self.icq_number = acct.icq_number = username + acct.update() + self._set_icq_username(username) + except Exception: + print_exc() + self._icq_check_done() + + def _set_icq_username(self, username): + if username == self.username: + return + self_buddy_name = self.self_buddy.name + self.buddies[username] = self.self_buddy + self.self_buddy.name = username + self.username = username + del self.buddies[self_buddy_name] + + def _icq_check_fail(self, *a, **k): + self._icq_check_done() + + def _icq_check_done(self): + if getattr(self, 'endpoints', None) is None: + self.endpoints = self.endpoint_generator() + threaded(self.get_cookie)(success = lambda resp: netcall(lambda: self._reconnect_icq(resp)), + error = lambda *_a: netcall(self._icq_check_bypass)) + + def _icq_check_bypass(self): + if getattr(self, 'endpoints', None) is None: + self.endpoints = self.endpoint_generator() + self.server = self.endpoints.next() + self._reconnect() + + def get_cookie(self): + if self.icq: + api = ICQ_API + else: + api = AIM_API + #async goes here!!!!~! + return get_login_cookie(self.username, self.password, api) + + def _reconnect_icq(self, resp): + try: + data = resp['response']['data'] + host = data['host'] + port = data['port'] + cookie = data['cookie'].decode('b64') + except Exception: + traceback.print_exc() + return self._icq_check_bypass() + else: + #this is used as a flag for "don't connect anywhere else", + # sometimes interpreted as "we've already connected" or vice versa + self.endpoints = None + if hasattr(self,'cancel'): + log.critical('cancelled connect') + self.set_offline(None) + return + if self._keepalive_timer is None: + self._keepalive_timer = Timer(45, self.on_keepalive) + self._keepalive_timer.start() + sck = oscar.socket() + self.sockets['bos'] = sck + self.server = (host, port) + sck.connect((host, port), + cookie, + success = self.init_session, + close = self.close_socket, + bos = True) + + def _reconnect(self): + #HAX: mcd + if hasattr(self,'cancel'): + log.critical('cancelled connect') + self.set_offline(None) + return + if self._keepalive_timer is None: + self._keepalive_timer = Timer(45, self.on_keepalive) + self._keepalive_timer.start() + sck = oscar.socket() + self.sockets['bos'] = sck + sck.connect(self.server, + success = self.auth_process, + error = lambda *a: self.logon_err(sck, *a), + close = self.close_socket, + bos = False) + + def endpoint_generator(self): + yield self.init_server + default_host = 'login.icq.com' if self.icq else 'login.oscar.aol.com' + hosts = (self.init_server[0],) + if default_host not in hosts: + hosts = hosts + (default_host,) + + for host in hosts: + if self.init_server[1] != 443: + yield (host,443) + yield (host, 5190) + yield (host, 80) + yield (host, 25) + yield (host, 21) + + def _close_sockets(self): + info('closing sockets') + do(sock.close() for sock in set(self.sockets.values())) + self.sockets.clear() + + def Disconnect(self, reason=None): + return netcall(lambda: self._Disconnect(reason)) + + #@action(lambda self, *a, **k: (self.state != self.Statuses.OFFLINE) or None) + def _Disconnect(self, reason=None): + + if self.disconnected: + log.info('Already disconnected %r.', self) + return + + # mcd: HAX! + log.info('reason was: %s, state was: %s', reason, self.state) + if reason is None or self.state != self.Statuses.CONNECTING: + self.cancel = object() + if self._keepalive_timer is not None: + self._keepalive_timer.stop() + self._keepalive_timer = None + + log.info('disconnecting...') + if self.state == self.Statuses.OFFLINE: + self._close_sockets() + return + + if getattr(self, 'endpoints', None) is None: + log.warning('offline reason: %r', self.offline_reason) + self.set_offline(reason) + do_reconnect = False + else: + try: + self.server = self.endpoints.next() + except StopIteration: + self.endpoints = None + log.warning('offline reason: %r', reason) + self.set_offline(reason) + do_reconnect = False + else: + do_reconnect = True + + cs = self.conversations + + info('exiting conversations') + do(c.exit() for c in set(cs.values())) + cs.clear() + + self._close_sockets() + + info('signing off buddies') + for buddy in self.buddies.values(): + buddy.protocol = None + buddy.observers.clear() + buddy.status = 'offline' + + self.buddies.observers.clear() + self.buddies.clear() + self.buddies.kill() + + if self.root_group is not None: + for group in self.root_group: + group.protocol = None + group.observers.clear() + + self.root_group.protocol = None + self.root_group.observers.clear() + self.root_group = None + + common.protocol.Disconnect(self) + + if do_reconnect: + self.init_buddy_vars() + if self.icq: + self._icq_check() + else: + self._reconnect() + else: + log.info('disconnected') + self.disconnected = True + + if False: + # disabled for now, since some GUI elements hold onto + # buddy objects temporarily. + self._cleanup_references() + + def _cleanup_references(self): + ''' + this shouldn't technically be necessary, but OscarProtocol leaks without it. + TODO: more investigating as to why this is the case. + ''' + + with traceguard: + self.ssimanager.o = None + self.ssimanager = None + self.self_buddy.protocol = None + del self.self_buddy + self.buddies.protocol = None + + def close_socket(self, sock, reason=None): + log.info('close_socket for %r with reason %r', sock, reason) + sockets = self.sockets + + if reason is None: + reason = self.Reasons.CONN_LOST + + isbos = sock.bos + log.info(' is bos? %r', isbos) + + dead_fams = [] + for key, sck in sockets.items(): + if sck is sock: + sockets.pop(key) + dead_fams.append(key) + log.info(' it was for these services: %r', dead_fams) + + if ((isbos or not sockets or 'bos' not in sockets) + and (self.state != self.Statuses.OFFLINE or reason == self.Reasons.OTHER_USER)): + log.info(' no bos socket, disconnecting') + self.Disconnect(reason) + else: + log.info(' not calling disconnect, but sending a keep alive') + + if 'bos' in self.sockets: + # send a keep alive to make sure BOS is still alive + try: + self.sockets['bos'].send_flap(5) + except Exception, e: + # the keep alive won't be sent immediately, but catch errors here anyways + log.critical('could not send keep alive') + print_exc() + self.Disconnect(reason) + + sock.close_when_done() + + def logon_err(self, sock=None, *a): + if sock is not None: + sock.close() + + log.critical('Could not connect to authentication server: %r', sock) + + rsn = self.Reasons.CONN_FAIL + self.Disconnect(rsn) + raise LoginError + + @property + def block_list(self): + return self.ssimanager.blocklist() + + @property + def ignore_list(self): + return self.ssimanager.ignorelist() + + @callsback + def send_sms(self, sms_number, message, callback = None): + 'Send an SMS message to sms_number.' + + from common.sms import normalize_sms + sms_number = normalize_sms(sms_number) + + message = message.encode('utf-8') + snd = snac.snd_channel1_message + + # send the message + auto = False + fam, sub, data = snd(self, '+' + sms_number, message, save=(not auto), auto=auto, req_ack = True) + self.send_snac(fam, sub, data, priority=4) + callback.success() + + def send_file(self, buddy, filestorage): + ''' + Sends a file to a buddy. + + fileobj must be a file like object. + ''' + if hasattr(buddy, 'name'): + buddy = buddy.name + + if self.buddies[buddy] == self.self_buddy: + raise OscarException("You can't send files to yourself.") + + return filetransfer.send(self, buddy, filestorage) + + def send_folder(self, buddy, filestorage): + 'Sends a folder to a buddy.' + + self.send_file(buddy, filestorage) + + def direct_connect(self, buddy): + if hasattr(buddy, 'name'): + buddy = buddy.name + + import oscar.rendezvous + return oscar.rendezvous.directconnect(self, buddy) + + def auth_requested(self, bname, msg=''): + + b = self.buddies[bname] + + if self.auth_required: + if self.block_url_requests and util.linkify(msg) != msg: + log.info("Auth request from %r was blocked because it has a URL in it. Message: %r", bname, msg) + self.authorize_buddy(b, False) + return + self.hub.authorize_buddy(self, b, msg) + else: + self.authorize_buddy(b, True) + + def authorize_buddy(self, buddy, authorize, _username_added=None): + auth_snac = snac.x13_x1a(buddy, +# 'I like friends. p.s. get digsby' +# if authorize else +# "I'll be your friend when you get digsby", + '', + authorize) + self.send_snac('bos', *auth_snac) + + @util.gen_sequence + def auth_process(self, sock): + try: + self.endpoints.close() + self.endpoints = None + except AttributeError: + assert False, "Why wasn't there an 'endpoints' attribute?!" + me = (yield None); assert me + + old_incoming, sock.on_incoming = sock.on_incoming, me.send + + self.change_state(self.Statuses.AUTHENTICATING) + + try: + sock.send_snac(*snac.x17_x06(self.username)) + key = self.gen_incoming((yield None)) + + if self.USE_NEW_MD5: + pw_to_hash = md5(self.password).digest() + else: + pw_to_hash = self.password + + pass_hash = md5(key + pw_to_hash + + "AOL Instant Messenger (SM)").digest() + sock.send_snac(*snac.x17_x02(self, pass_hash)) + server_str, cookie = self.gen_incoming((yield None)) + except OscarException, e: + try: + if e.code in (0x1, 0x4, 0x5): + r = self.Reasons.BAD_PASSWORD + elif e.code in (0x18, 0x1D): + r = self.Reasons.RATE_LIMIT + else: + r = self.Reasons.CONN_FAIL + log.critical('doing disconnect with reason %r', r) + self.Disconnect(r) + except common.ActionError: + log.critical("couldn't call disconnect because of an action error. should really fix that...") + return + + host, port = server_str.split(":") + port = int(port) + + sock.on_incoming = old_incoming + sock.close_when_done() + sck = oscar.socket() + self.sockets['bos'] = sck + sck.connect((host, port), + cookie, + success = self.init_session, + close = self.close_socket, + bos = True) + + + @util.gen_sequence + def init_session(self, sock): + ''' + This is just a wrapper around init_socket that sets flags + appropriately + ''' + me = (yield None); assert me + sock.on_incoming = me.send + _sock, snac = (yield None) + + while (snac.hdr.fam, snac.hdr.sub) != (0x01, 0x03): + log.info('Got unexpected snac for socket initialization, ignoring it: %r', snac) + _sock, snac = (yield None) + + self.init_socket((_sock,snac)) + + @util.gen_sequence + def init_socket(self, (sock, first_snac)): + me = (yield None); assert me + log.info('first snac = %r', first_snac) + serv_families = self.incoming(sock, first_snac) + + sock.on_incoming = me.send + sock.send_snac(*snac.x01_x17(self, serv_families, sock), req=True, cb=me.send) + + while True: + sck, snc = (yield None) + val = self.gen_incoming((sck, snc)) + if (snc.hdr.fam, snc.hdr.sub) == (0x01, 0x18): + break + + continue_ = val + + if not continue_: + raise LoginError(['\xff'], 'Error when initializing socket.') + + def when_done(): + sock.send_snac_first(*snac.x01_x02(serv_families)) + + # connect any pending chat sockets + serv_families.extend(k for k in self.sockets.iterkeys() + if oscar.util.is_chat_cookie(k)) + + for fam in serv_families: + if isinstance(self.sockets[fam], SnacQueue): + q = self.sockets.get(fam, None) + if q is None: + continue + + assert hasattr(q, 'sock') and q.sock is sock + q.flush() + self.sockets[fam] = q.sock + + if self.state != self.Statuses.ONLINE: + self.get_search_response() + #self.send_snac(*snac.x01_x1e(self)) + self.change_state(self.Statuses.ONLINE) + self.request_offline_messages() + + log.info('%r done logging in. online and all SNAC families set up.', self) + + for buddy in self.buddies.values(): + if buddy._status == 'unknown': + buddy._status = 'offline' + + ready_counter = util.CallCounter(len(serv_families), when_done) + + for family in serv_families[:]: + if not self.icq and family == 0x15: + ready_counter._trigger -= 1 + continue + + fam_init = getattr(snac, 'x%02x_init' % family, None) + if fam_init is None: + log.error('Don\'t know about oscar family %r, counting it as ready.', family) + ready_counter._trigger -= 1 + try: + serv_families.remove(family) + except ValueError: + pass + else: + old_counter = ready_counter._trigger + try: + fam_init(self, sock, ready_counter) + except Exception: + traceback.print_exc() + log.error('Error initializing oscar family %r, counting it as ready.', family) + if ready_counter._trigger == old_counter: + ready_counter -= 1 + + # send client ready + sock.on_incoming = self.incoming + + @util.gen_sequence + def service_request(self, service_id, parent=None): + me = (yield None); assert me + + if service_id in self.sockets: + assert isinstance(self.sockets[service_id], SnacQueue) + + sock_id = service_id + + if isinstance(service_id, tuple): + raise ValueError('I think you forgot to splat a snac...') + + if isinstance(service_id, basestring): + assert service_id != 'bos' + service_id = 0x0e # chat + + assert isinstance(service_id, int) + + self.send_snac('bos', *snac.x01_x04(sock_id), req=True, cb=me.send) + + log.info('Requesting service with id: %r', service_id) + try: + s_id, address, cookie = self.gen_incoming((yield None)) + except (TypeError, ValueError): + log.error('Error getting service %r', service_id) + self.sockets[sock_id] = DisabledSocket(sock_id, time.time()) + return + + assert s_id == service_id + + server = util.srv_str_to_tuple(address, self.server[-1]) + self.connect_socket(server, cookie, [sock_id], parent) + + @util.gen_sequence + def connect_socket(self, server, cookie, sock_ids, parent=None, bos = False): + me = (yield None); assert me + sock = oscar.socket() + sock.connect(server, + cookie, + success=me.send, + incoming=me.send, + error=RedirectError, + close = self.close_socket, + bos = bos) + yield None # pause for socket to connect + + #give first packet to init socket + _sock, snac = (yield None) + while (snac.hdr.fam, snac.hdr.sub) != (0x01, 0x03): + log.info('Got unexpected snac for socket initialization, ignoring it: %r', snac) + _sock, snac = (yield None) + + self.init_socket((_sock,snac)) + + #sock.on_incoming = self.incoming + for sock_id in sock_ids: + if sock_id in self.sockets: + if isinstance(self.sockets[sock_id], SnacQueue): + self.sockets[sock_id].sock = sock + else: + self.sockets[sock_id] = sock + else: + self.sockets[sock_id] = SnacQueue(sock) + #sock.on_close = self.close_socket + if parent is not None: + parent.send(sock) + + def send_snac_cb(self, snac, cb): + self.send_snac(*snac) + + def send_snac(self, sock_id, *args, **kwargs): + if isinstance(sock_id, int): + args = (sock_id,) + args + + def request(sock_id): + self.sockets[sock_id] = SnacQueue() + self.service_request(sock_id) + + if sock_id not in self.sockets: + if sock_id == 'bos': + log.info(' no bos socket for send_snac, disconnecting') + self.Disconnect(self.Reasons.CONN_LOST) + else: + log.debug('requesting service %r', sock_id) + request(sock_id) + + # if we've got a DisabledSocket for this service, and enough time + # has passed, try re-requesting. + if getattr(self.sockets[sock_id], 'should_rerequest', False): + request(sock_id) + + log.debug_s('sending snac to %r, snac args = %r, %r', self.sockets[sock_id], args, kwargs) + self.sockets[sock_id].send_snac(*args, **kwargs) + + def gen_incoming(self, (s, snac)): + return self.incoming(s, snac) + + def incoming(self, sock, snac): + log.debug('incoming: calling snac.x%02x_x%02x', snac.hdr.fam, snac.hdr.sub) + f = getattr(oscar.snac, 'x%02x_x%02x' % (snac.hdr.fam, snac.hdr.sub), None) + if f is None: + return log.warning('%r got an unknown snac: %r', self, snac) + else: + return f(self, sock, snac.data) + + def pause_service(self, sock, fam_list): + if not fam_list: + families = [s_id for (s_id, s) in self.sockets.items() + if s is sock] + + sock.send_snac(*snac.x01_x0c(fam_list)) + + q = SnacQueue(sock) + + for id, s in self.sockets.items(): + if s is sock: self.sockets[id] = q + + def group_for(self, contact): + 'Returns the group name the specified contact is in.' + + return self.ssimanager.ssis[contact.id[0]].name.decode('utf-8', 'replace') + + def unpause_service(self, sock, fam_list): + if not fam_list: + families = [s_id for (s_id, s) in self.sockets.items() + if s.sock is sock] + + sock.send_snac(*snac.x01_x0c(fam_list)) + + q = self.sockets[families[0]] + for fam in families: + assert q is self.sockets[fam] and q.sock is sock + self.sockets[fam] = sock + + q.flush() + + def incoming_message(self, userinfo, message, is_auto_response=False, offline=False, timestamp=None, html=True): + log.info_s('Got a message: %r', (userinfo, message, is_auto_response, offline, timestamp, html)) + screenname = userinfo.name.lower().replace(' ','') + + if isinstance(userinfo, type(self.self_buddy)): + b = userinfo + else: + b = self.buddies[screenname] + b.update(userinfo) + + try: + c = self.conversations[screenname] + except KeyError: + c = self.conversations.setdefault(screenname, oscar.conversation(self)) + + if screenname not in c: + c.buddy_join(screenname) + if self.self_buddy.name not in c: + c.buddy_join(self.self_buddy.name) + + self._forward_message_to_convo(c, b, message, + is_auto_response=is_auto_response, + offline=offline, + timestamp=timestamp, + html=html, + screenname=screenname) + + def _forward_message_to_convo(self, convo, buddy, message, is_auto_response=False, offline=False, timestamp=None, html=True, screenname=None): + b = buddy + c = convo + + if screenname is None: + screenname = b.name + + client = b.guess_client() + _test_message = message.strip().upper() + has_html_tags = lambda: _test_message.startswith('') + + if not (client == 'mobile' and is_auto_response): # AIM servers send an HTML message auto-response when you send a message to a phone. + if b.capabilities and not b.get_sends_html_messages(convo.ischat): + if not (client == 'mobile' and has_html_tags()): + log.info('Overriding "html" for this message because the buddy doesn\'t send html messages. (b.guess_client() == %r)', client) + html = False + if any(_x in client for _x in ('miranda', 'ebuddy', 'digsby')) and b.sends_html_messages: + # miranda doesn't know how to use html capabilities for messages. + html = True + + # offline messages may come in from buddies we don't yet have capabilities for. cheat and look for + # Also, some TERRIBLE AWFUL clients like lotus sametime don't send any capabilities. + if client == 'unknown' and has_html_tags(): + log.info('overriding HTML to be True for this message, because client is unknown and it starts and ends with tags') + html = True + + if not html: + log.info('xml encoding message') + message = message.encode('xml') + log.info_s('encoded message: %r', message) + + if timestamp is not None: + if isinstance(timestamp, str): + with traceguard: + posixtime = struct.unpack('!I', timestamp)[0] + timestamp = datetime.utcfromtimestamp(posixtime) + else: + assert isinstance(timestamp, datetime) + + message = message.replace('\x00', '') + + c.incoming_message(screenname, message, + auto = is_auto_response, + offline = offline, + timestamp = timestamp, + content_type = 'text/html', + ) + + def incoming_rtf_message(self, userinfo, message, is_auto_response=False): + log.info('received RTF message') + try: + plain_message = rtf_to_plain(message) + except Exception, e: + # Error parsing RTF, assume plaintext + plain_message = message + self.incoming_message(userinfo, plain_message, is_auto_response = is_auto_response, html=False) + + def convo_for(self, buddyname): + if hasattr(buddyname, 'name'): buddyname = buddyname.name + if not isinstance(buddyname, basestring): + raise TypeError(str(type(buddyname))) + + bname = str(buddyname).lower().replace(' ','') + + try: + return self.conversations[bname] + except KeyError: + c = self.conversations.setdefault(bname, oscar.conversation(self)) + c.buddy_join(self.self_buddy.name) + c.buddy_join(bname) + return c + + def chat_with(self, bname): + info('chat_with %r', bname) + bname = common.get_bname(bname).lower().replace(' ','') + + try: + c = self.conversations[bname] + except KeyError: + c = self.conversations.setdefault(bname, oscar.conversation(self)) + + if bname not in c: + c.buddy_join(bname) + if self.self_buddy not in c: + c.buddy_join(self.self_buddy.name) + return c + + get_buddy_icon = snac.get_buddy_icon + set_buddy_icon = snac.set_buddy_icon + + def get_self_bart_data(self): + ssim = self.ssimanager + ssis = ssim.find(type = 0x14) + res = [] + for ssi in ssis: + try: + type = int(ssi.name) + except ValueError: + continue + + if type & 1: + res.append((type, ssi.tlvs.get(0xD5, ''))) + + return ''.join([(struct.pack('!H', t) + val) for t,val in sorted(res)]) + + def get_buddy(self, name): + if not isinstance(name, basestring): raise AssertionError() + name = name.encode('utf-8') + + return self.buddies[name] + + def get_buddy_info(self, b, profile = True, away = True, caps = True, cert = False): + bname = common.get_bname(b) + if isinstance(bname, unicode): + bname = bname.encode('utf8') + b = self.buddies[bname] + + getting = ','.join(filter(None, ['profile' if profile else None, + 'away' if away else None])) + log.info('retreiving [%s] for %r', getting, bname) + + self.send_snac(*snac.x02_x15(bname, + profile = profile, away = away, + caps = caps, cert = cert)) + + if b.service == 'icq': + log.info('requesting additional icq info') + from common import pref + if pref('icq.profile_unicode', True): + req = snac.icq_request_profile_unicode(self, b.name) + else: + req = snac.request_more_icq_info(self, b.name) + + if req: + self.send_snac(*req) + + def exit_conversation(self, c): + if c.type == 'chat': + self.close_socket(c.sock) + + cs = self.conversations + [cs.pop(k) for k in cs.keys() if c is cs[k]] + + @callsback + def _do_rejoin_chat(self, old_conversation, callback=None): + self.join_chat(cookie=old_conversation.cookie, notify_profile=False, callback=callback) + + @callsback + def join_chat(self, convo = None, room_name = None, cookie=None, server=None, notify_profile=True, callback = None): + if cookie is None: + cookie, room_name = generate_chatroom_cookie(room_name) + + if cookie and not cookie.startswith('!'): + cookie = '!' + cookie + + try: + c = self.conversations[cookie] + except KeyError: + c = self.conversations.setdefault(cookie, oscar.conversation(self, 'chat', cookie=cookie, roomname=room_name)) + c.connect_chat_socket(callback = callback) + else: + callback.success(c) + + if notify_profile: + profile.on_entered_chat(c) + + def set_message(self, message='', status='away', format = None): + self.status = status.lower() + self.self_buddy.setnotify('status', self.status) + + self.status_message = message + + if self.status in ('online', 'available', 'free for chat'): + # do not apply formatting to available messages--they have a maximum + # length of 255 and clients seemingly do not expect formatting anyways + self.set_invisible(False) + self.set_away() + message = message.encode('utf-8') + self.set_avail(message) + + elif self.status in ('offline', 'invisible'): + log.debug('setting invisible on %r', self) + self.set_invisible(True) + self.set_away() + if self.icq: self.update_extended_info() + + else: + # only apply formatting to away messages + if message == '': + message = _("Away") + + if message[:5].lower() != ' AVAIL_MAX_LEN: + message = message[:AVAIL_MAX_LEN] + log.warning('avail message is too long, chopping to %s chars', AVAIL_MAX_LEN) + self.self_buddy._avail_msg = message + + def update_infos(*a): + self.update_location_info() + self.update_extended_info() + + if self.icq: + self.set_icq_psm(message, success = update_infos) + else: + update_infos() + + + def update_extended_info(self, *args): + self.send_snac('bos', *snac.x01_x1e(self)) + + def update_location_info(self): + self.send_snac('bos', *snac.x02_x04(self)) + + def set_away(self, message=''): + self.self_buddy.away_msg = self.status_message = message + if not self.self_buddy.idle: + self.set_idle() + + def update_infos(*a): + self.update_location_info() + self.update_extended_info() + + if self.icq: + self.set_icq_psm(message, success = update_infos) + else: + update_infos() + + def set_profile(self, value = '', format = None): + from common import pref + if self.icq and pref('oscar.icq.set_about', False): + if not isinstance(value, basestring): value = value.format_as('html') + self.send_snac(*snac.send_icq_profile(self, value)) + self.self_buddy.profile = value + + elif not self.icq: + + if isinstance(value, basestring): + if format is not None: + # Add AIM formatting. + from oscar.oscarformat import to_aimhtml + value = to_aimhtml(value, format, body_bgcolor = True) + else: + value = value.format_as('html') + + self.self_buddy.profile = value.replace('\n', '
')[:self.MAX_PROF_HTML] + + self.update_location_info() + + def set_idle(self, how_long = 0): + + self._enable_idle_privacy() + + how_long = int(how_long ) + self.self_buddy.setnotifyif('idle', time.time() - how_long) + self.send_snac('bos', *snac.x01_x11(int(how_long))) + + def _enable_idle_privacy(self): + # TODO: see ticket #2668 + pass + + def set_invisible(self, invis = True): + self.self_buddy.invisible = invis + +# if self.icq: +# self.set_privacy(not invis, 'all') + if self.icq and not invis: + self.set_privacy(True, 'all') + elif self.icq: + self.set_privacy(True, 'list') + + self.update_extended_info() + + def request_offline_messages(self): + if self.icq: + self.send_snac(*snac.request_offline_messages_icq(self)) + else: + self.send_snac(*snac.x04_x10()) + + def is_mobile_buddy(self, bname): + bname = getattr(bname, 'name', bname) + buddy = self.get_buddy(bname) + return buddy.sms or buddy.mobile + + @callsback + def remove_buddy(self, ssi_ids, gpo=None, callback = None): + self.ssimanager.remove_buddy_ssi(ssi_ids, callback = callback) + + @callsback + def move_buddy(self, contact, to_group, from_group=None, pos=0, callback = None): + ssi = contact.id + to_group_orig = to_group + + if isinstance(to_group, basestring): + to_group = self.get_group(to_group) + + # Change the Contact's id + old = callback.success + + def on_move(ssi_tuple, old=old): + assert isinstance(ssi_tuple, tuple) + contact.id = ssi_tuple + old(ssi_tuple) + + callback.success = on_move + + if to_group is not None: + if to_group != from_group: + for c in to_group: + if c.name == contact.name: + return self.remove_buddy(contact.id, success = lambda *a: callback.success(c.id)) + + self.ssimanager.move_ssi_to_position(ssi, pos, to_group.my_ssi, callback = callback) + else: + callback.error() + + def set_remote_alias(self, *args): + self.ssimanager.alias_ssi(*args) + + def rename_group(self, protocol_object, name): + self.ssimanager.rename_ssi(protocol_object, name) + + + + @callsback + def add_buddy(self, buddy_name, groupid, service = None, callback = None): + buddy_name = str(buddy_name) + +# # This function assumes the group is already there. +# def doadd(buddy_name, group): +# #group = self.ssimanager.find(name=group, type=1)[0] + self.ssimanager.add_new_ssi(buddy_name, groupid, error=lambda *a: + self.add_buddy_try_auth(buddy_name, groupid, service=service, callback=callback)) + +# if isinstance(group, basestring): +# groupobj = self.get_group(group) +# +# # if the group isn't there, add it first, then callback and add the buddy. +# if groupobj is None: +# self.add_group(group, success = lambda *a: doadd(buddy_name, group)) +# else: +# doadd(buddy_name, group) + + + add_contact = add_buddy + + def request_auth_for(self, ): + pass + + @callsback + def add_buddy_try_auth(self, buddy_name, group, service=None, callback = None): + + + print "add_buddy_try_auth" + self.ssimanager.add_new_ssi(buddy_name, group, authorization=True, + callback = callback) + self.request_authorization(buddy_name, 'Digsby rulz') + + def request_authorization(self, buddy_name, reason=''): + self.send_snac(req=True, cb=lambda *a,**k: None, *snac.x13_x18(buddy_name, reason)) + + @callsback + def add_group(self, group_name, callback = None): + self.ssimanager.add_new_ssi(group_name, callback = callback) + + def get_group(self, groupname): + ''' + Returns a Group object for a given groupname. Case does not matter, + as per Oscar protocol. + + If the group doesn't exist this function will return None. + ''' + groupname = groupname.encode('utf-8') + group = self.ssimanager.find(name=groupname, type=1) + if group: + return self.ssimanager.ssis.get_group(group[0]) + + def remove_group(self, gpo): + self.ssimanager.remove_group(gpo) + + @action(aim_only) + def format_screenname(self, new_name): + ''' + Change the formatting of your screenname. + + You can add or remove spaces or change capitalization. + ''' + new_name = str(new_name) + + if new_name.replace(' ','').lower() != \ + self.username.replace(' ','').lower(): + raise ScreennameError('Formatted name is not equal to account name') + + if new_name != self.self_buddy.nice_name: + self.send_snac(*snac.x07_x04(screenname=new_name)) + + @callsback + def get_account_email(self, callback = None): + if self.icq: + return + + def success(sock, snc): + if (0x07, 0x03) == (snc.hdr.fam, snc.hdr.sub): + result = snac.x07_x03(self, sock, snc.data) + if 17 in result: + return callback.success(result[17].decode('utf-8')) + + return callback.error() + + cb_args = dict(req=True, cb=success) + self.send_snac(*snac.x07_x02('email'), **cb_args) + + #@action(aim_only, needs = lambda self: ((str, _('AIM Account Email'), ''),)) + @action(aim_only) + def set_account_email(self, new_email): + if isinstance(new_email, unicode): + new_email = new_email.encode('utf-8') + + #TODO: validate email? + self.send_snac(*snac.x07_x04(email=new_email)) + + @action(aim_only) + def request_account_confirm(self): + self.send_snac(*snac.x07_x06()) + + @action() + def change_password(self): + self.hub.launchurl('http://aim.aol.com/redirects/password/change_password.adp') + + @action(aim_only) + def im_forwarding(self): + self.hub.launchurl('http://www.aim.com/redirects/inclient/imforwarding.adp') + + @action(aim_only) + def aol_alerts(self): + self.hub.launchurl('http://alerts.aol.com/ar/directory/noauth.ev') + + @action(icq_only) + def my_icq_page(self): + self.hub.launchurl('http://www.icq.com/people/about_me.php?uin=%s' % self.self_buddy.name) + + + def move_group(self, gpo, pos=0): + # gpo should be an ssi tuple... + self.ssimanager.move_ssi_to_position(gpo, pos) + + @callsback + def block(self, buddy, set_block = True, callback = None): + if set_block: + # block them + if self.icq: + f = self.ssimanager.ignore_buddy + else: + f = self.ssimanager.block_buddy + + f(buddy, callback = callback) + self.set_privacy(False, 'list') + else: + # unblock them + if self.icq: + f = self.ssimanager.unignore_buddy + else: + f = self.ssimanager.unblock_buddy + + f(buddy, callback = callback) + + @callsback + def ignore(self, buddy, set_ignore=True, callback=None): + if set_ignore: + if self.icq: + f = self.ssimanager.block_buddy + else: + f = self.ssimanager.ignore_buddy + + f(buddy, callback=callback) + else: + if self.icq: + f = self.ssimanager.unblock_buddy + else: + f = self.ssimanager.unignore_buddy + + f(buddy, callback=callback) + + @callsback + def unignore(self, buddy, callback=None): + self.ignore(buddy, False, callback=callback) + + def permit(self, buddy, set_allow): + if set_allow: + self.ssimanager.allow_buddy(buddy) + else: + self.ssimanager.unallow_buddy(buddy) + + def warn(self, buddy, anon=True): + if hasattr(buddy, 'name'): buddy = buddy.name + self.send_snac(*snac.x04_x08(buddy, anon)) + + def _get_chat(self, cookie): + '''Returns the OscarConversation chat for the given cookie.''' + + return self.conversations.get(cookie, None) + + def buddy_list_from_file(self, open_fh): + import re + result = [] + last_opened = [] + depth = 0 + depth_to_tag = [ + ('rah','blah'), + ('<%s>',''), + ('<%s>',''), + ('',''), + ('',''), + ('%s','%s'), + ] + for line in open_fh: + line = line.strip() + if line.endswith('{'): + depth += 1 + line = line.strip('{').strip() + if line.strip('"') == line: + line = '"%s"' % line + #last_opened.append(line) + try: line = depth_to_tag[depth][0] % line + except TypeError: pass + except Exception: print depth;raise + elif line.endswith('}'): + depth -= 1 + try: line = depth_to_tag[depth][1] % line + except TypeError: pass + else: + if line.strip('"') == line: + line = '"%s"' % line + result.append(line) + + return result + + def buddy_list_to_file(self, open_fh): + open_fh.write('Buddy {\nlist {\n') + for group in self.ssimanager.ssis.groups[0,0]: + open_fh.write('"%s" {\n' % group.name) + for contact in group: + open_fh.write('%s\n' % contact.buddy.name.lower().replace(' ','')) + open_fh.write('}\n') + + + def set_privacy(self, allow, who, _ignore = True): + ''' + allow is a boolean. True for allow, false for block + who is a string from the following list: ['all','contacts','list'] + + (True, 'contacts') and (False, 'contacts') are semantically the same. + + (True, 'all') means everyone can message you and (False, 'all') means no one. + ''' + + + ''' + [TLV(0x00CA), itype 0x04, size 01] - This is the byte that tells the AIM servers your + privacy setting. If 1, then allow all users to see you. If 2, then block all users from + seeing you. If 3, then allow only the users in the permit list. If 4, then block only the + users in the deny list. If 5, then allow only users on your buddy list. + ''' + allow = bool(allow) + assert who in ['all','contacts','list'] + + ps = self.ssimanager.get_privacy_ssi() + + userclass = 0xFFFFFFFF + see = 1 + if who == 'all': + val = 1 if allow else 2 + if who == 'list': + val = 3 if allow else 4 + if who == 'contacts': + val = 5 + + if (allow, who) == (False, 'all'): + userclass = 4 + + ps.tlvs[0xca] = util.Storage(type=0xca, length=1, value=val) + ps.tlvs[0xcb] = util.Storage(type=0xcb, length=4, value=userclass) + ps.tlvs[0xcc] = util.Storage(type=0xcc, length=4, value=see) + + self.ssimanager.add_modify(ps) + + def get_privacy(self): + ps = self.ssimanager.get_privacy_ssi() + return ps.tlvs +# +# from util import Storage as S +# +# privacy = {1: 'allowall', +# 2: 'blockall', +# 3: 'permitlist', +# 4: 'denylist', +# 5: 'buddylist'} +# +# return S(privacy = privacy[struct.unpack('B', tlvs[0xca])[0]]) +# #ps.tlvs + + def set_webaware(self, webaware): + self.webaware = webaware + self.send_snac(*snac.set_icq_privacy(self)) + + self.update_extended_info() + + def set_auth_required(self, auth_required): + self.auth_required = auth_required + self.send_snac(*snac.set_icq_privacy(self)) + + self.update_extended_info() + + @callsback + def get_icq_privacy(self, callback=None): + pass + #self.send_snac(*snac.get_icq_privacy(callback=callback)) + + + def save_icq_privacy(self): + self.send_snac(*snac.save_icq_privacy(self)) + + def set_search_response(self, disclosure): + ''' + What happens when a user searches for your email in AIM directory search. + full means they can see whatever they want, + limited means just your screenname + none means no info at all. + + disclosure: True = full + False= limited + None = none + ''' + self._search_disclosure = disclosure + disclosure = {True: 3, False: 2, None: 1}[disclosure] + self.send_snac(*snac.x07_x04(reg_status=disclosure)) + + @callsback + def get_search_response(self, callback=None): + if self.icq: return + + def success(sock, snc): + if (0x07, 0x03) != (snc.hdr.fam, snc.hdr.sub): + return callback.error() + else: + value, = struct.unpack('!H', snac.x07_x03(self, sock, snc.data)[0x13]) + disclosure = {3:True, 2:False, 1:None}[value] + log.info('Got disclosure response: %r ( => %r)', value, disclosure) + + self._search_disclosure = disclosure + callback.success(disclosure) + + cb_args = dict(req=True, cb=success) + self.send_snac(*snac.x07_x02('registration'), **cb_args) + + def set_require_auth(self, auth=True): + self.send_snac(*snac.set_require_auth(self, auth)) + + def set_allow_list(self, L): + for bname in L: + b = self.buddies[bname] + if b.blocked: + b.block(False) + + def set_block_list(self, L): + for bname in L: + b = self.buddies[bname] + if not b.blocked: + b.block(True) + + def add_to_visible_list(self, bname): + b = self.buddies[bname] + if b.blocked: + b.block(False) + + PERMIT = 0x02 + if not self.ssimanager.find(name=bname,type=PERMIT): + new_id = self.ssimanager.new_ssi_item_id(0) + self.ssimanager.add_modify(oscar.ssi.item(bname, 0, new_id, type_=PERMIT)) + + def allow_message(self, buddy, mobj): + ''' + Blocking is handled by the server, except for icq's "block_unknowns" + ''' + super = common.protocol.allow_message(self, buddy, mobj) + if super in (True, False): + return super + + search = lambda **k: self.ssimanager.find(name=oscar.util.lowerstrip(buddy.name), **k) + + if self.icq and self.block_unknowns and not (search(type=0) or search(type=2)): + return False + + # AOLSystemMsg can go on your blocklist, but the server ignores it and + # gives you its messages anyways. + # + # TODO: implement get_privacy_tlv so we can know the current privacy settings. + # this may not always be accurate! + if buddy.name == 'aolsystemmsg' and buddy.blocked: + return False + + return True + + def should_log(self, messageobj): + return messageobj.buddy.name not in self.bots + +class SnacQueue(list): + def __init__(self, sock=None): + list.__init__(self) + self.sock = sock + + def send_snac_first(self, *args, **kwargs): + self.insert(0, ('snac', args, kwargs)) + + def send_snac(self, *args, **kwargs): + self.append(('snac', args, kwargs)) + + def send_flap(self, *args, **kwargs): + self.append(('flap', args, kwargs)) + + def flush(self, out=None): + if not self: return + + if out is None: + assert self.sock + out = self.sock + log.debug('flushing SnacQueue to %r', out) + while self: + what, args, kwargs = self.pop(0) + if what == 'flap': + out.send_flap(*args, **kwargs) + elif what == 'snac': + out.send_snac(*args, **kwargs) + else: + raise ValueError("%r is not a valid thing to send. Was expecting 'flap' or 'snac'. Args/kwargs were: %r", what, (args, kwargs)) + log.debug('done flushing') + + def close_when_done(self): + del self[:] + + close = close_when_done + + def __hash__(self): + return hash((self.sock, id(self))) + + def __repr__(self): + return '' % list.__repr__(self) + +class DisabledSocket(object): + ''' + a mock OscarSocket that dumps calls into the void. used if a service is down. + ''' + def __init__(self, sock_id, time): + self.sock_id = sock_id + self.time = time + self.ignored_calls = 0 + + @property + def should_rerequest(self): + ''' + returns True if enough time has elapsed since the creation of this + DisabledSocket + ''' + return time.time() - self.time > SERVICE_REQUEST_TIME_SECS + + def _do_nothing(self, *a, **k): + self.ignored_calls += 1 + + send_snac = \ + send_flap = \ + flush = \ + close_when_done = \ + close = \ + _do_nothing + + def __hash__(self): + return hash((self.sock_id, self.time)) + + def __repr__(self): + return '>= 3 + s = (s + i) % 0xFFFFFFFF + + return ((((0 - s) ^ (n & 0xFF)) & 7 ^ n) + 2) & 0x7FFF + +def flap_sequence_number(start = 0): + i = start + while 1: + yield i + i += 1 + if i >= 0x8000: + i = 1 + +def default_cb(*a, **kw): + log.debug_s('Socket ignoring (%s, %s)', a, kw) + +class OscarSocket(common.socket): + flap_hdr_size = 6 + snac_hdr_size = 10 + id = 0x2A + + func_templ = \ + ''' + def %(name)s (self): + print "%(name)s is not implemented!" + print self.hdr + print self.data + ''' + + def _repr(self): + try: + return repr(self.__oserver) + except Exception: + return '??' + + def __init__(self): + common.socket.__init__(self) + + self.on_connect = default_cb + self.on_incoming = default_cb + self.on_close = default_cb + + self.cookie = None + self.bos = False + + self.callbacks = collections.defaultdict(list) + self.error_callbacks = collections.defaultdict(list) + + self.rate_lock = RLock() + + self.hdr = None + self.buf = '' + self.data = '' + self.seq = flap_sequence_number(_generate_flap_sequence_start()) + self.req = flap_sequence_number(_generate_flap_sequence_start()) + self.rate_classes = [] + self.rates = {} + self.rate_lvl_incr = False + + # At first, don't use rate limiting. + self.snactivate = self._send_snac + self.snactivator = None + + + @callsback + def connect(self, server, cookie = None, incoming = None, close = None, callback = None, bos = False): + self.on_connect = callback or default_cb + self.on_incoming = incoming or default_cb + self.on_close = close or default_cb + + self.cookie = cookie + self.bos = bos # True if this is the main connection + + self.set_terminator(self.flap_hdr_size) + log.info('oscar socket connecting to %s', server) + self.__oserver = server + + common.socket.connect(self, server, error=callback.error) + + def handle_error(self, e = None): + if isinstance(e, socket.error): + if self.on_close is not None: + log.error('Socket error for %r, calling on_close (= %r): %r', self, self.on_close, e) + self.on_close(self) + else: + log.info('handle_error in %r but on_close is None' % self) + + common.socket.handle_error(self, e) + + def test_connection(self): + # send a keep alive packet + try: + self.send_flap(5) + except Exception, e: + print_exc() + + # usually will not fail, but if it does, we need to close now + (self.on_close or default_cb)(self) + + def apply_rates(self, rate_classes, rate_groups): + '''The socket init process obtains rate info from the server, and calls + this function.''' + + with self.rate_lock: + if not self.rate_classes: + self.rate_classes = rate_classes + else: + for rates in rate_classes: + rates.pop('last_time', 0) # The format of the time provided by oscar servers is not compatible with the format we use locally. + self.rate_classes[rates.id-1].update(rates) + + self.rates.update(rate_groups) + + with self._lock: + # Now use rate limiting. + if self.snactivator is None: + self.snactivator = Snactivator(self) + self.snactivator.start() + + self.snactivate = self.snactivator.send_snac + + def calc_rate_level(self, rate_class): + ''' + If we send a packet with the specified family and subtype right now, + we would have the returned rate level, which is a tuple of the rate + level (a number), and the last time we sent a packet. + ''' + old_level = rate_class.current_level + window = rate_class.window + + now = int(time.time()) + time_diff = (now - rate_class.last_time) * 1000 + new_level = min(int(((window-1) * old_level + time_diff)/window), + rate_class.max_level) + return (new_level, now) + + def snac_rate_class(self, fam, sub, *a): + try: + return self.rate_classes[self.rates[(fam, sub)]-1] + except KeyError: + return None + + #figure out lock here! + def _get_rate_lvls(self, rclass): + return rclass.max_level, rclass.current_level, rclass.alert_level, \ + rclass.clear_level, rclass.window + + def time_to_send(self, s): + 'Returns the number of milliseconds this snac should be sent in.' + + fam, sub = s[0], s[1] + rclass = self.snac_rate_class(fam, sub) + + ml, curl, al, clrl, w = self._get_rate_lvls(rclass) + + assert clrl >= al + + # don't use a threshold higher than we can reach + threshold = min(ml, (al + ((clrl - al)*2))) + + with self.rate_lock: + if ((curl < al) or self.rate_lvl_incr) and curl < threshold: + self.rate_lvl_incr = True + else: + # We've hit (or are above the threshold, send now. + self.rate_lvl_incr = False + return 0 + + k = 500.0 + step = ml / k + wait = w * step + curl + delta = rclass.last_time - int(time.time()) + to_send = delta + wait/1000 + +# log.info('k = %r, ml = %r, step = %r, w = %r, curl = %r, wait = %r, last_time = %r, delta = %r, to_send = %r', +# k, ml, step, w, curl, wait, rclass.last_time, delta, to_send) + + return max(0, to_send) + + ######################### + # Begin AsynSocket interface + + def handle_connect(self): + log.debug('connected') + + def handle_close(self): + log.info('closed. calling on_close=%r', self.on_close) + (self.on_close or default_cb)(self) + self.close() + + def handle_expt(self): + log.warning('oob data') + self.handle_close() + + def collect_incoming_data(self, data): + if self.hdr is None: + self.buf += data + else: + self.data += data + + def set_terminator(self, term): + assert term != 0 + common.socket.set_terminator(self, term) + + def found_terminator(self): + try: + if self.hdr is None: + with self._lock: + self.hdr = unpack_named('!BBHH', 'id', 'chan', 'seq', 'size', self.buf) + self.buf = '' + if self.hdr.size == 0: + # SNACs without any data will have no more data. Handle them immediately, + # and DONT set the terminator to 0. + self.found_terminator() + else: + self.set_terminator(self.hdr.size) + else: + try: + assert len(self.data) == self.hdr.size + getattr(self, 'channel_%d' % self.hdr.chan, self.unknown_channel)() + except oscar.errors, e: + # pass oscar errors up to the user + hub.get_instance().on_error(e) + except Exception: + log.critical("Error handling FLAP 0x%x (DATA: %s) " % + (self.hdr.seq, repr(self.data))) + raise + finally: + with self._lock: + self.hdr, self.data = None, '' + self.set_terminator(self.flap_hdr_size) + except socket.error: + # reraise socket errors, assuming we are disconnected + raise + except Exception, e: + # all other exceptions may not be fatal to the connection + log.critical('%r had a non-socket error', self) + + print_exc() + finally: + if self.terminator == 0: + log.critical('terminator was 0, closing socket!') + self.handle_close() + + def close(self): + self._cleanup() + common.socket.close(self) + + def _cleanup(self): + self.on_incoming = None + self.on_close = None + + if self.snactivator: + self.snactivator.stop() + del self.snactivator + self.snactivator = None + + def close_when_done(self): + self._cleanup() + try: + self.send_flap(0x04) + except socket.error, (errno, desc): + if errno not in [9, 10054, 10057]: + # 9 is bad file descriptor, means socket is closed + # and cannot be written to, something quite likely + # when closing. + + # 10057 is "socket is already closed" + + # 10054 is connection reset by peer, something + # to be expected if both ends are closing + raise + finally: + common.socket.close_when_done(self) + + def send_flap(self, chan, data=''): + log.debug_s('Sending FLAP on channel %d, data is < %r >', chan, data) + netcall(lambda: common.socket.push(self, pack('!BBHH', self.id, chan, self.seq.next(), len(data)) + data)) + + # end AsyncSocket interface + ######################### + + def send_snac(self, fam, sub, data='', priority=5, req=False, cb=None, *args, **kwargs): + ''' + Sends a snac. + + If req is True, cb must be a callable which will be invoked later with the + specifed *args and **kwargs--this function will be called when a SNAC with + the correct request ID comes back from the network. + ''' + req_id = self.req.next() + if req: + # don't leak memory by accumulating lists in the defaultdict + remove_empty_lists(self.callbacks) + remove_empty_lists(self.error_callbacks) + + if cb: + self.callbacks[req_id].append((cb, args, kwargs)) + + err_cb = kwargs.pop('err_cb', None) + if err_cb is not None: + self.error_callbacks[req_id].append((err_cb, args, kwargs)) + + # Fire the Snactivator! (maybe) + + snac = (fam, sub, req_id, data) + if self.snactivator is None: + self._send_snac(snac, priority) + else: + self.snactivator.send_snac(snac, priority) + + send_snac_first = send_snac + + def _send_snac(self, (fam, sub, req_id, data), priority=None): + + server_version = getattr(self, 'server_snac_versions', {}).get(fam, None) + if server_version is None: + version = None + else: + my_version = getattr(getattr(oscar.snac, 'family_x%02x' % fam, None), 'version', None) + if (my_version == server_version) or my_version is None: + version = None + else: + version = my_version + + flags = 0 if version is None else 0x8000 + + if version: + ver_tlv = oscar.util.tlv(1,2,version) + ver_tlv = pack('!H', len(ver_tlv)) + ver_tlv + else: + ver_tlv = '' + + log.debug('sending snac: fam=0x%02x, sub=0x%02x, req=0x%04x', fam, sub, req_id) + log.debug_s('\t\tdata=%r', data) + to_send = pack('!HHHI', fam, sub, flags, req_id) + ver_tlv + data + self.send_flap(0x02, to_send) + + # Set rate class's last time + if (fam, sub) in self.rates: + rclass = self.snac_rate_class(fam,sub) + rclass.current_level, rclass.last_time = self.calc_rate_level(rclass) + clevel = rclass.current_level + i = sorted(list(self._get_rate_lvls(rclass)) + [clevel]).index(clevel) + + try: + names = ('disconnect','limit','alert','clear', 'max')[i:i+2] + hi, lo = names[0], names[-1] + if not (hi == 'clear' and lo == 'max'): + log.debug('current rate level is: %s < %d < %s', hi, clevel, lo) + except Exception: + import traceback + traceback.print_exc() + + def channel_1 (self): + log.info('got channel 1 (new connection) flap') + to_send = pack('!I', 1) + if self.cookie is not None: + to_send += oscar.util.tlv(0x06, self.cookie) + with self._lock: self.cookie = None + +# else: +# to_send += oscar.util.tlv(0x8003, 4, 0x100000) + + self.send_flap(0x01, to_send) + try: + (self.on_connect or default_cb)(self) + except StopIteration: + pass + + del self.on_connect + self.on_connect = None + + def channel_2(self): + hdr, data = unpack_named('!HHHI', + 'fam', 'sub', 'flags', 'req', + self.data[:self.snac_hdr_size]), self.data[self.snac_hdr_size:] + log.debug('got channel 2 (snac data) flap. fam=0x%02x, sub=0x%02x, req=0x%04x', hdr.fam, hdr.sub, hdr.req) + log.debug_s('\t\tdata=%r', data) + + snac = Storage(hdr=hdr, data=data) + + if snac.hdr.flags & 0x8000: + log.debug('got version data for snac, trimming') + snac_ver_fmt = (('tlvs_len', 'H'), + ('tlvs', 'tlv_list_len', 'tlvs_len') + ) + tlvs_len, ver, snac.data = oscar.util.apply_format(snac_ver_fmt, snac.data) + + if self.is_ignored(snac): + log.debug('Ignored snac: %r', snac) + return + + cbs = self.callbacks + req_id = snac.hdr.req + + try: + if req_id in cbs: + call_later = [] + try: + for func, args, kwargs in cbs[req_id]: + if snac.hdr.flags & 0x0001: + call_later.append((func, args, kwargs)) + if isgeneratormethod(func): + assert not kwargs + try: func((self, snac)+args) + except StopIteration: pass + else: + func(self, snac, *args, **kwargs) + finally: + with self._lock: + if not call_later: + cbs.pop(req_id) + else: + cbs[req_id] = call_later + + else: + if self.on_incoming is None: + default_cb(self, snac) + elif isgeneratormethod(self.on_incoming): + try: self.on_incoming((self, snac)) + except StopIteration: pass + except Exception: + print repr(snac) + raise + else: + self.on_incoming(self, snac) + except oscar.snac.SnacError, e: + if self.handle_error_callbacks(snac, e): + log.info('ignoring SnacError because it was handled by a callback') + return + + (fam, _), (sub, _) = e.args[:2] + if (fam, sub) in self.ignored_errors: + log.error('SNAC error occured and was ignored: %r', snac) + else: + # tell user + hub.get_instance().on_error(e) + + def handle_error_callbacks(self, snac, exc): + handled = False + if snac.hdr.req in self.error_callbacks: + for func, args, kwargs in self.error_callbacks[snac.hdr.req]: + handled = handled or func(snac, exc, *args, **kwargs) + + return handled + + def is_ignored(self, snac): + if (snac.hdr.fam, snac.hdr.sub) in self.ignored_snacs: + return True + + ignored_snacs= [(0x01, 0x13), # MOTD + ] + + ignored_errors = [(0x01, 0x0d), # Request denied- usually for requesting buddy icon service + (0x15, 0x02), # ICQ family rate limit + (0x13, 0x01), # SSI invalid snac header error, seems to happen on 'edit start' packets (0x13, 0x11) + (0x15, 0x05), # ICQ service requested unavailable + (0x13, 0x05), # SSI service requested unavailable + (0x07, 0x15), # Admin service error, "invalid account". + ] + + def channel_4 (self): + log.info('got channel 4 (close connection) flap') + fmt = (('tlvs', 'tlv_dict'),) + tlvs, data = oscar.unpack(fmt, self.data) + if try_this(lambda:ord(tlvs[0x09][-1]), False): + (self.on_close or default_cb)(self, oscar.protocol.Reasons.OTHER_USER) + else: + (self.on_close or default_cb)(self) + + del self.on_close + self.on_close = None + + self.close_when_done() + + def unknown_channel(self): + log.warning('Unknown channel for data: %r', self.data) + +def remove_empty_lists(mapping): + for k, v in list(mapping.iteritems()): + if not v: + mapping.pop(k) + diff --git a/digsby/src/oscar/OscarUtil.py b/digsby/src/oscar/OscarUtil.py new file mode 100644 index 0000000..6043223 --- /dev/null +++ b/digsby/src/oscar/OscarUtil.py @@ -0,0 +1,542 @@ +from peak.util.imports import lazyModule +from util.primitives import odict +import struct +util = lazyModule('util') +auxencodings = lazyModule('util.auxencodings') +import sys +import array +import logging +import string + +from collections import defaultdict + +import oscar + +log = logging.getLogger('oscar.util') + +flagged = lambda f, v: f&v==f + +len_to_fmt = {0: '', 1: 'B', + 2: 'H', 4: 'I', + 8: 'Q', 16:'16s',} + +def lowerstrip(s): + return s.lower().replace(' ','') + +def tflv(type_, flags=1, data=''): + return struct.pack('!HBB', type_, flags, len(data)) + data + +def tflv_list(*tflvs): return ''.join(tflv(*t) for t in tflvs) + +def tlv(type_, length=0, value=0): + ''' + Constructs a TLV, returning the bytes that would go out on the network. + + TLV stands for "type, length, value" and is one of the building blocks of + OSCAR binary data packets. + + >>> tlv(0x05) + >>> tlv(0x03, 'some string') + >>> tlv(0x02, 4, 13) + ''' + + assert isinstance(type_, int) + if isinstance(length, basestring): + value = length + length = len(value) + else: + assert isinstance(length, int) + + if not isinstance(value, (int, long)): + raise AssertionError('%r is not an integer' % value) + assert length in [0,1,2,4,8,16] + if isinstance(value, basestring): + assert length == len(value) + fmt_string = '%ds' % length if value else '' + else: + fmt_string = len_to_fmt[length] + + assert struct.calcsize(fmt_string) == length + + args = [type_] + args += [length] if length else [0] + args += [value] if value else [0] if length else [] + + try: + # this causes DeprecationWarning: + # 'H' format requires 0 <= number <= 65535. + # what to do when the overflow is desired? + return struct.pack('!HH'+fmt_string, *args) + except Exception: + print fmt_string, args + raise + + +def tlv_list(*tlvs): + return ''.join(tlv(*t) for t in tlvs) + +def s_tlv(data, byte_order='!'): + ''' + Creates a TLV object from network data. + + The returned object is a Storage with t, l, and v attributes. + ''' + + t, l = struct.unpack(byte_order + 'HH', data[:4]) + data = data[4:] + v, data = data[:l], data[l:] + return util.Storage(t=t, l=l, v=v), data + +def tlv_list_to_dict(tlv_list): + return odict((tlv.t, tlv.v) for tlv in tlv_list) + +def s_tflv(data, byte_order = '!'): + fmt = ( + ('type', 'H'), + ('flags', 'B'), + ('length', 'B'), + ('value', 's', 'length'), + ) + t, f, l, v, data = apply_format(fmt, data) + return util.Storage(t=t, f=f, l=l, v=v), data + +def list_reader(of_what, byte_order = '!'): + def read_list(data, count = -1): + objs = [] + while data and count: + thing, data = of_what(data, byte_order) + objs.append(thing) + try: + count -= 1 + except TypeError: + print count + raise + + return objs, data + return read_list + +s_tflv_list = list_reader(s_tflv) +s_tlv_list = list_reader(s_tlv) + +def decode(s, enc): + ''' + Returns a unicode string for a Oscar network string and an Oscar encoding. + + Encodings look like + text/x-aolrtf; charset="unicode-2-0" + ''' + encodings = {'unicode-2-0':'utf-16be', + 'utf-8': 'utf-8', + 'us-ascii': 'ascii' } + if enc.find('; charset="') != -1: + msgtype, encoding = enc.split('; charset="') + encoding = encoding[:-1] + elif enc in encodings: + encoding = enc + else: + log.warning('oscar.decode encountered "%s", no charset--assuming utf-8', enc) + encoding = 'utf-8' + + encoding = encoding.split('\0', 1)[0] + encoding = encodings.get(encoding, encoding) + + return auxencodings.fuzzydecode(s, encoding) + +struct_types = set('xcbhilqfdsp') +oscar_types = set(['tlv', 'tlv_list', 'tlv_dict', 'named_tlvs', 'list', 'tflv', 'tflv_list', + 'rate_class_list', 'rate_group_list', 'pstring', 'message_block', + 'ssi_dict','ssi','tlv_list_len','tlv_dict_len','userinfo', + 'dc_info', 'rate_class', 'msg_fragment','lnts']) +all_format_types = struct_types | oscar_types + +digits = set(string.digits) + +def apply_format(format, data, byte_order='!'): + if not (isinstance(format, (tuple, list)) and + isinstance(format[0], (tuple, list))): + raise TypeError('apply_format needs a tuple of tuples') + + fields = {} + to_return = [] + for item in format: + name, kind, args = item[0], item[1], item[2:] + + # for format strings like "4B"...strip numbers + skind = kind + while len(skind) > 0 and skind[0] in digits: + skind = skind[1:] + + assert skind.lower() in all_format_types, 'bad format: %s' % kind.lower() + + if kind.lower() in oscar_types: + f = globals().get('apply_format_%s' % kind, None) + if f is None: + raise Exception('%r is not a valid format type' % kind) + fields[name], data = f(data, fields, byte_order, *args) + to_return.append(fields[name]) + continue + + prev_name = args[0] if args else None + if prev_name is not None: + fmt = '%d%s' % (prev_name if isinstance(prev_name, int) \ + else fields[prev_name], kind) + else: + fmt = kind + + fmt = byte_order + fmt + + try: + sz = struct.calcsize(fmt) + except Exception: + print 'BAD FORMAT:', fmt + raise + + try: + fields[name] = struct.unpack(fmt, data[:sz]) + except Exception: + print name, fmt, util.to_hex(data) + raise + + #make single-element tuples/lists into the value instead + if len(fields[name]) == 1: + fields[name] = fields[name][0] + data = data[sz:] + to_return.append(fields[name]) + + to_return.append(data) + return to_return + +# ah! the eyes get lost in the sea of underscores. -10 style points + +def apply_format_tlv(data, fields, byte_order): + return s_tlv(data) + +def apply_format_tlv_dict(data, fields, byte_order, num_tlvs_s=-1): + tlvs, data = apply_format_tlv_list(data, fields, byte_order, num_tlvs_s) + return tlv_list_to_dict(tlvs), data + +def apply_format_tlv_dict_len(data, fields, byte_order, byte_count_s): + tlvs, data = apply_format_tlv_list_len(data, fields, byte_order, byte_count_s) + return tlv_list_to_dict(tlvs), data + +def apply_format_tlv_list(data, fields, byte_order, num_tlvs_s=-1): + if isinstance(num_tlvs_s, basestring): + num_tlvs = fields[num_tlvs_s] + else: + assert isinstance(num_tlvs_s, int) + num_tlvs = num_tlvs_s + return s_tlv_list(data, num_tlvs) + +def apply_format_tlv_list_len(data, fields, byte_order, byte_count_s): + if isinstance(byte_count_s, basestring): + byte_count = fields[byte_count_s] + else: + assert isinstance(byte_count_s, int) + byte_count = byte_count_s + + indata, rdata = data[:byte_count], data[byte_count:] + tlvs, outdata = apply_format_tlv_list(indata, fields, byte_order, -1) + assert not outdata + return tlvs, rdata + +def apply_format_named_tlvs(data, fields, byte_order, tlv_count_s, tlv_types): + if isinstance(tlv_count_s, basestring): + tlv_count = fields[tlv_count_s] + else: + assert isinstance(tlv_count_s, int) + tlv_count = tlv_count_s + tlvs, data = s_tlv_list(data, tlv_count) + + bynumber = tlv_list_to_dict(tlvs) + + named_tlvs = util.Storage() + for type, tlv in bynumber.iteritems(): + named_tlvs[tlv_types.get(type, type)] = tlv + + return named_tlvs, data + +def apply_format_tflv(data, fields, byte_order): + return s_tflv(data) + +def apply_format_tflv_list(data, fields, byte_order, num_tflvs_s=-1): + if isinstance(num_tflvs_s, basestring): + num_tflvs = fields[num_tflvs_s] + else: + assert isinstance(num_tflvs_s, int) + num_tflvs = num_tflvs_s + + return s_tflv_list(data, num_tflvs) + +def apply_format_userinfo(data, fields, byte_order): + ''' + Turns a user info block into a Storage. + + U{http://iserverd.khstu.ru/oscar/info_block.html} + ''' + + userinfo_types = { # the Storage has some (or all) of these keys: + 0x01: 'user_class', + 0x02: 'create_time', + 0x03: 'signon_time', + 0x04: 'idle_time', + 0x05: 'account_creation_time', + 0x06: 'user_status_icq', + 0x0a: 'external_ip_icq', + 0x0c: 'dc_info', + 0x0d: 'capabilities', + 0x0f: 'online_time', + 0x1d: 'avail_msg', + 0x23: 'profile_updated', # 4 byte timestamps + 0x26: 'mystery_updated', + 0x27: 'away_updated', + } + + fmt = (('sn', 'pstring'), + ('warning', 'H'), + ('num_tlvs','H'), + ('tlvs', 'named_tlvs', 'num_tlvs', userinfo_types)) + + sn, warning, __, userdict, data = apply_format(fmt, data) + + userdict.nice_name = sn + userdict.name = sn.lower().replace(' ', '') + userdict.warning_level = warning + return userdict, data + +def apply_format_dc_info(data, fields, byte_order): + names = 'ip port type version cookie web_port features info_update_time '.split() + sizes = '4B I B H I I I I '.split() + + names +='ext_update_time status_update_time unknown'.split() + sizes +='I I H '.split() + + stuff = apply_format(zip(names, sizes), data) + return util.Storage(zip(names, stuff[:-1])), data[-1] + +def apply_format_list(data, fields, byte_order, kind, count_s=-1): + ''' + works for any kind of data that apply_format accepts + ''' + + if isinstance(count_s, basestring): + count = fields[count_s] + else: + assert isinstance(count_s, int) + count = count_s + + result = [] + while data and count: + info, data = apply_format((('info', kind),), data, byte_order) + result.append(info) + count -= 1 + return result, data + +def apply_format_rate_class(data, fields, byte_order): + + fmt_str1 = 'id H '\ + 'window I '\ + 'clear_level I '\ + 'alert_level I '\ + 'limit_level I '\ + 'disconnect_level I '\ + 'current_level I '\ + 'max_level I '\ + 'last_time I '\ + 'state B'.split() + + rate_class_fmt = zip(fmt_str1[::2], fmt_str1[1::2]) + + info = apply_format(rate_class_fmt, data, byte_order) + data, rate_class = info.pop(), util.Storage(zip(fmt_str1[::2],info)) +# if ord(data[1:2]) != int(rate_class.id) + 1: +# fmt_str2 ='last_time I '\ +# 'state B '.split() +# +# last_time, state, data = apply_format(zip +# (fmt_str2[::2], +# fmt_str2[1::2]), +# data) +# +# rate_class.last_time, rate_class.state = last_time, state +# +# else: +# rate_class.last_time, rate_class.state = 0,0 + + return rate_class, data + +def apply_format_rate_class_list(data, fields, byte_order, num_classes_s): + if isinstance(num_classes_s, basestring): + num_classes = fields[num_classes_s] + else: + assert isinstance(num_classes_s, int) + num_classes = num_classes_s + + classes = [] + while data and num_classes: + rate_class, data = apply_format_rate_class(data, fields, byte_order) + classes.append(rate_class) + num_classes -= 1 + + return classes, data + +def apply_format_pstring(data, fields, byte_order): + 'Unpacks a Pascal string. Which the struct module does NOT do.' + + l = struct.unpack('B', data[:1])[0] # grab the length of the string + string_ = struct.unpack(str(l) + 's', data[1:1+l])[0] + + return string_, data[l+1:] + +def apply_format_message_block(data, fields, byte_order): + 'Channel 1 message block.' + msg = util.Storage() + + fmt = (('msg_fragments', 'list', 'msg_fragment'),) + msg_fragments, data = apply_format(fmt, data) + msgs = [x[1] for x in msg_fragments if x[0] == 1] + + res = dict(msg_fragments) + if msgs: + res[1] = msgs + return res, data + +def apply_format_msg_fragment(data, fields, byte_order): + fmt = (('type', 'B'), + ('version', 'B'), + ('length', 'H'), + ('data','s','length')) + + type, __, __, data, remaining_data = apply_format(fmt, data, + byte_order) + + return (type, data), remaining_data + + +def apply_format_rate_group(data, fields, byte_order): + _data = data[:] + rate_group_fmt = (('id', 'H'), + ('num_pairs', 'H'), + ('pairs_list', 'list', 'I', 'num_pairs')) + try: + stuff = apply_format(rate_group_fmt, data, byte_order) + except ValueError, e: + stuff = apply_format(rate_group_fmt, _data, '<') + id, num_pairs, pairs_list, data = stuff + pairs = [] + for pair in pairs_list: + fam, sub = struct.unpack(byte_order+'HH', struct.pack(byte_order+'I', pair)) + pairs.append((fam, sub)) + + return dict.fromkeys(pairs, id), data + +def apply_format_rate_group_list(data, fields, byte_order, num_groups_s): + if isinstance(num_groups_s, basestring): + num_groups = fields[num_groups_s] + else: + assert isinstance(num_groups_s, int) + num_groups = num_groups_s + + rate_groups = {} + while data and num_groups: + rate_group, data = apply_format_rate_group(data, fields, byte_order) + rate_groups.update(rate_group) + num_groups -= 1 + + return rate_groups, data + +def apply_format_ssi(data, fields, byte_order): + ssi_fmt = (('name_len', 'H'), + ('name', 's', 'name_len'), + ('group_id', 'H'), + ('item_id', 'H'), + ('type_', 'H'), + ('data_len', 'H'), + ('tlvs','tlv_dict_len','data_len')) + __, name, group_id, item_id, type_, __, tlvs, data = \ + apply_format(ssi_fmt, data, byte_order) + + return oscar.ssi.item(name, group_id, item_id, type_, tlvs), data + +def apply_format_ssi_list(data, fields, byte_order, num_ssis_s=-1): + + if isinstance(num_ssis_s, basestring): + num_ssis = fields[num_ssis_s] + else: + assert isinstance(num_ssis_s, int) + num_ssis = num_ssis_s + + l = [] + while data and num_ssis != 0: + ssi, data = apply_format_ssi(data, fields, byte_order) + l.append(ssi) + num_ssis -= 1 + return l, data + +def apply_format_ssi_dict(*args): + l, data = apply_format_ssi_list(*args) + d = {} + for item in l: + d[(item.group_id, item.item_id)] = item + return d, data + +def apply_format_lnts(data, fields, byte_order='<'): + if byte_order != '<': + import warnings + warnings.warn('oscar.apply_format_lnts got a byte order other than little-endian') + length, data = struct.unpack(byte_order+'H', data[:2])[0], data[2:] + val, data = data[:length], data[length:] + if length: + assert val[-1] == '\x00' + return val[:-1], data + +def chat_cookie(room_name, exchange=4): + ''' + make_chat_cookie(room_name, exchange=4) + + returns a valid aol chat cookie + ''' + cookie = "!aol://2719:10-" + str(exchange)+ "-" + str(room_name.replace(" ","").lower()) + assert is_chat_cookie(cookie) + return cookie + +def process_status_bits(user_status_icq): + offline = 0xffffffff + webaware = 0x00010000 + invisible = 0x00000100 + dnd = 0x00000002 + busy = 0x00000010 + na = 0x00000004 + away = 0x00000001 + ffc = 0x00000020 + online = 0x00000000 + + webaware = False + status = None + flags, status_bits = struct.unpack('!HH', user_status_icq) + + webaware = flagged(webaware, status_bits) + + if flagged(offline, status_bits): + status = 'offline' + elif flagged(invisible, status_bits): + status = 'invisible' + elif flagged(dnd, status_bits): + status = 'do not disturb' + elif flagged(busy, status_bits): + status = 'busy' + elif flagged(na, status_bits): + status = 'not available' + elif flagged(away, status_bits): + status = 'away' + elif flagged(ffc, status_bits): + status = 'free for chat' + elif flagged(online, status_bits): + status = 'available' + + return status, webaware + +def is_chat_cookie(cookie): + return isinstance(cookie, basestring) and ( + cookie.startswith('aol://') or + cookie.startswith('!aol://')) + diff --git a/digsby/src/oscar/Snactivator.py b/digsby/src/oscar/Snactivator.py new file mode 100644 index 0000000..14a81e5 --- /dev/null +++ b/digsby/src/oscar/Snactivator.py @@ -0,0 +1,78 @@ +''' + +Snactivator.py + +''' +from __future__ import with_statement +from itertools import count +from traceback import print_exc +from util import TimeOut +from util.primitives.structures import PriorityQueue, EmptyQueue + +import logging +log = logging.getLogger('oscar.snactivator') + +class Snactivator(TimeOut): + 'Rate limiting SNAC consumer.' + + ids = count() + def __init__(self, socket): + TimeOut.__init__(self) + self.socket = socket + # create a PriorityQueue for each rate class + self.queues = [PriorityQueue() + for __ in xrange(len(socket.rate_classes))] + + def send_snac(self, snac, priority = 5): + if self.finished(): + log.error('Snactivator can\'t send this snac, because it\'s finished: %r', snac) + return + rclass = self.socket.snac_rate_class(*snac) + if not rclass: + return self.socket._send_snac(snac) + + with self._cv: + queue = self.queues[self.socket.rate_classes.index(rclass)] + queue.append(snac, priority) + self._cv.notify() + + send_snac_first = send_snac + + def compute_timeout(self): + if self.finished(): + self._last_computed = -1 + self.socket = None + else: + # Find the smallest time to wait. + times = [] + for q in (queue for queue in self.queues if queue): + snac = q.peek() + t = self.socket.time_to_send(snac) + times.append(t) + + self._last_computed = min(times or [5]) + + return self._last_computed + + def process(self): + for q in self.queues: + if not q: continue + + try: + snac = q.peek() + except EmptyQueue: + continue + t = self.socket.time_to_send(snac) + + # If any snacs are ready to send, send them. + if t == 0 and not self.finished(): + try: + snac = q.next() + except Exception: + return + else: + try: + self.socket._send_snac(snac) + except Exception: + print_exc() + self.socket.test_connection() diff --git a/digsby/src/oscar/__init__.py b/digsby/src/oscar/__init__.py new file mode 100644 index 0000000..8ec89d7 --- /dev/null +++ b/digsby/src/oscar/__init__.py @@ -0,0 +1,72 @@ +''' +OSCAR is the protocol used by AIM and ICQ. +''' +from common.Protocol import ProtocolException +class OscarException(ProtocolException): pass + +import capabilities +from oscar.OscarSocket import OscarSocket as socket +from oscar.OscarConversation import OscarConversation as conversation +from oscar.OscarBuddies import OscarBuddies as buddies +from oscar.OscarBuddies import OscarBuddy as buddy +import oscar.OscarUtil as util +from oscar.OscarUtil import apply_format as unpack, decode +from OscarProtocol import SnacQueue, LoginError, ScreennameError, RedirectError +from oscar.snac import SnacError +from oscar.ssi import SSIException + +import snac + +from oscar.OscarProtocol import OscarProtocol as protocol + +errors = (OscarException) + +auth_errcode = { + 0x0001: "Invalid nick or password", + 0x0002: "Service temporarily unavailable", + 0x0003: "All other errors", + 0x0004: "Incorrect screenname or password.", + 0x0005: "The username and password you entered do not match.", + 0x0006: "Internal client error (bad input to authorizer)", + 0x0007: "Invalid account", + 0x0008: "Deleted account", + 0x0009: "Expired account", + 0x000A: "No access to database", + 0x000B: "No access to resolver", + 0x000C: "Invalid database fields", + 0x000D: "Bad database status", + 0x000E: "Bad resolver status", + 0x000F: "Internal error", + 0x0010: "Service temporarily offline", + 0x0011: "Suspended account", + 0x0012: "DB send error", + 0x0013: "DB link error", + 0x0014: "Reservation map error", + 0x0015: "Reservation link error", + 0x0016: "The users num connected from this IP has reached the maximum", + 0x0017: "The users num connected from this IP has reached the maximum (reservation)", + 0x0018: "You are trying to connect too frequently. Please try to reconnect in a few minutes.", + 0x0019: "User too heavily warned", + 0x001A: "Reservation timeout", + 0x001B: "You are using an older version of ICQ. Upgrade required", + 0x001C: "You are using an older version of ICQ. Upgrade recommended", + 0x001D: "Rate limit exceeded. Please try to reconnect in a few minutes", + 0x001E: "Can't register on the ICQ network. Reconnect in a few minutes", + 0x0020: "Invalid SecurID", + 0x0022: "Account suspended because of your age (age < 13)" +} + +def _lowerstrip(name): + if isinstance(name, bytes): + name = name.decode('utf8') + + if not isinstance(name, unicode): + # not a string? + return name + + name = ''.join(name.split()) + name = name.lower() + name = name.encode('utf8') + + return name + diff --git a/digsby/src/oscar/api.py b/digsby/src/oscar/api.py new file mode 100644 index 0000000..04a56f2 --- /dev/null +++ b/digsby/src/oscar/api.py @@ -0,0 +1,61 @@ +''' +Created on Dec 13, 2010 + +@author: Christopher +''' + +from util.net import WebFormData, wget +from util.primitives.mapping import LazySortedDict +import hashlib +import hmac +import simplejson +import urllib +import pprint +from common import asynchttp + +ICQ_API_KEY = 'gu19PNBblQjCdbMU' + +ICQ_API = 'icq.com' +AIM_API = 'aol.com' + + +def hmac_sha256_base64(secret, password): + return hmac.new(password, secret, digestmod=hashlib.sha256).digest().encode('b64') + +def get_login_data(login, password, api = 'icq.com', callback=None): + if callback is None: + return simplejson.loads(wget('https://api.screenname.%s/auth/clientLogin?f=json' % api, + dict(k=ICQ_API_KEY, s=login, pwd=password))) + else: + return asynchttp.httpopen('https://api.screenname.%s/auth/clientLogin?f=json' % api, + data = dict(k=ICQ_API_KEY, s=login, pwd=password), callback=callback) + +def get_login_cookie(login, password, api = 'icq.net', api2 = None): + if api2 is None: + api2 = api + r = get_login_data(login,password,api2) + sessionSecret = r['response']['data']['sessionSecret'] + token = r['response']['data']['token']['a'] + sessionKey = hmac_sha256_base64(sessionSecret, password) + uri = "https://api.oscar.%s/aim/startOSCARSession" % api + + d = dict(a = token, + f = 'json', + k = ICQ_API_KEY, + ts = r['response']['data']['hostTime'], + useTLS = 1 + ) + + queryString = WebFormData(d=LazySortedDict(d)).replace('+', '%20') + hashData= "GET&" + urllib.quote(uri, safe = '') + "&" + queryString.encode('url') + + digest = hmac_sha256_base64(hashData, sessionKey) + + url = uri + "?" + queryString + "&sig_sha256=" + digest + ret = simplejson.loads(wget(url)) + return ret + + +if __name__ == "__main__": + ret = get_login_cookie('digsby01', '%%%', AIM_API) + pprint.pprint(ret) diff --git a/digsby/src/oscar/capabilities.py b/digsby/src/oscar/capabilities.py new file mode 100644 index 0000000..2b8847d --- /dev/null +++ b/digsby/src/oscar/capabilities.py @@ -0,0 +1,121 @@ +from struct import pack +from common import pref + +''' +Capabilities utility data. Also contains our client capabilities (enabled_names) + +useful: http://micq.alpha345.com/ICQ-OSCAR-Protocol-v7-v8-v9/Define/CAPABILITIES.html +''' + +# +# List enabled capabilities here. +# +enabled_names = """ +digsby + +avatar + +ichatav_info + +file_xfer + +icq_to_aim +utf8_support +xhtml_support +extended_msgs + +buddy_list_transfer + +chat_service + +""".split() + + + +# temporarily removed: +# +# direct_im +# rtf_support + +by_name = {} +by_bytes = {} + +by_name = dict( + + # Stuff we do support + avatar = '094613464c7f11d18222444553540000' .decode('hex'), + buddy_list_transfer = '0946134b4c7f11d18222444553540000' .decode('hex'), + file_xfer = '094613434c7f11d18222444553540000' .decode('hex'), + ichatav_info = '094601054c7f11d18222444545535400' .decode('hex'), + + # Coming soon! + chat_service = '748f2420628711d18222444553540000' .decode('hex'), + + # Stuff we might support + file_share = '094613484c7f11d18222444553540000' .decode('hex'), + livevideo = '094601014c7f11d18222444553540000' .decode('hex'), + voice_chat = '094613414c7f11d18222444553540000' .decode('hex'), + camera = '094601024c7f11d18222444553540000' .decode('hex'), + + # Stuff we'll probably never support + games_1 = '0946134a4c7f11d18222444553540000' .decode('hex'), + games_2 = '0946134a4c7f11d12282444553540000' .decode('hex'), + direct_play = '094613424c7f11d18222444553540000' .decode('hex'), + add_ins = '094613474c7f11d18222444553540000' .decode('hex'), + + # Messaging stuff + icq_to_aim = '0946134d4c7f11d18222444553540000' .decode('hex'), + utf8_support = '0946134e4c7f11d18222444553540000' .decode('hex'), + rtf_support = '97b12751243c4334ad22d6abf73f1492' .decode('hex'), + xhtml_support = '094600024C7F11D18222444553540000' .decode('hex'), + direct_im = '094613454c7f11d18222444553540000' .decode('hex'), + extended_msgs = '094613494c7f11d18222444553540000' .decode('hex'), # wtf is this? it's also known as "icq server relay" + + # Other clients + miranda = '4d6972616e6461410004033100000002' .decode('hex'), + #miranda = '4d6972616e64614100070a0000070200' .decode('hex'), + trillian_encrypt = 'f2e7c7f4fead4dfbb23536798bdf0000' .decode('hex'), + # Us! + digsby = 'digsby' + ('\0'*10), + + # Shit we don't know about + short_caps = '094600004c7f11d18222444553540000' .decode('hex'), # short caps? + route_finder = '094613444c7f11d18222444553540000' .decode('hex'), + microphone = '094601034c7f11d18222444553540000' .decode('hex'), + multi_audio = '094601074c7f11d18222444553540000' .decode('hex'), # was aim6_unknown1 + rtc_audio = '094601044c7f11d18222444553540000' .decode('hex'), + mtn = '563fc8090b6f41bd9f79422609dfa2f3' .decode('hex'), # typing notifications + #icq_direct = '094613444c7f11d18222444553540000' .decode('hex'), # aka 'route_finder' + icq_lite = '178c2d9bdaa545bb8ddbf3bdbd53a10a' .decode('hex'), # icq lite / was icq6_unknown1 + icq_html_msgs = '0138ca7b769a491588f213fc00979ea8' .decode('hex'), # HTML messages?! / was icq6_unknown2 + icq_xtraz_muc = '67361515612d4c078f3dbde6408ea041' .decode('hex'), # XTraz in multi-user chat / was icq6_unknown3 + icq_xtraz = '1a093c6cd7fd4ec59d51a6474e34f5a0' .decode('hex'), # XTraz / was icq6_unknown4 + icq_tzers = 'b2ec8f167c6f451bbd79dc58497888b9' .decode('hex'), # TZers / was icq6_unknown5 + aim_file_xfer = '0946134c4c7f11d18222444553540000' .decode('hex'), # specifies an AIM specific file xfer? trillian sends it. + secure_im = '094600014C7F11D18222444553540000' .decode('hex'), + new_status_msg = '0946010A4C7F11D18222444553540000' .decode('hex'), + realtime_im = '0946010B4C7F11D18222444553540000' .decode('hex'), + smart_caps = '094601FF4C7F11D18222444553540000' .decode('hex'), + + icq7_unknown = 'C8953A9F21F14FAAB0B26DE663ABF5B7' .decode('hex'), # if it has this it's either ICQ lite 1.0 or ICQ7 + +) + +by_bytes = dict((v,k) for (k,v) in by_name.items()) + +feature_capabilities = [] + +enabled_capabilities = {}.fromkeys(by_name.keys(), False) + +for cap in enabled_names: + assert cap in by_name + enabled_capabilities[cap] = True + +if pref('videochat.report_capability', True): + enabled_capabilities['livevideo'] = True + +for (capability, enabled) in enabled_capabilities.items(): + assert capability in by_name + if enabled: + feature_capabilities.append(by_name[capability]) + diff --git a/digsby/src/oscar/login2.py b/digsby/src/oscar/login2.py new file mode 100644 index 0000000..7cbcd72 --- /dev/null +++ b/digsby/src/oscar/login2.py @@ -0,0 +1,251 @@ +''' + +AOLMail login cookie stuff + +''' + + +HOST = 'kdc.uas.aol.com' +HOST2 = 'localhost:50000' + +top = 'POST / HTTP/1.1\r\n' +\ + 'Accept: application/x-snac\r\n' +\ + 'Content-Type: application/x-snac\r\n' +\ + 'User-Agent: CLC/1.0\r\n' +\ + 'Host: '+ HOST + '\r\n' +\ + 'Content-Length: %d' + +top2 ='\r\n' +\ + 'Connection: Keep-Alive\r\n' +\ + 'Cache-Control: no-cache\r\n' +\ + '\r\n' + +top3 ='\x05\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00' + + +#2 bytes + +middle = '\xc0\xa8\xd7\x80\x00\x00\x00\x01\x00\x00\x00\x06' +\ + '\x05' +\ + '\x00\x00\x00\x00\x02' +\ + '\x00\n' +\ + '\x00\x02\x00\x01\x00\x0b\x00\x04\x00\x10' +\ + '\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00' +\ + '\x00\x02US' +\ + '\x00\x02en' +\ + '\x00\x02\x00\x03' +\ + '\x00\x02US' +\ + '\x00\x04\x00\x02en' +\ + '\x00\x00\x00\x00\x00\x00' + +#pstring(un) + +m2 = '\x00\x0dTritonService' +\ + '\x00\x00\x00\x00\x00\x00\x00\x01' +\ + '\x00\x02' #len(remainder) 2bytes + + +remainder = '@\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00' +\ + '\x00\x14urfaceisnotapassword' +\ + '\x00\x03\x00\x01' +\ + '\x00\x05UTF-8' +\ + '\x00\x02' +\ + '\x00\x02en' +\ + '\x00\x03' +\ + '\x00\x02US' + +from util import pack_pstr, unpack_pstr +def make_remainder(password): + return '@\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00' + pack_pstr( + '\x00' + pack_pstr(password) +\ + '\x00\x03\x00\x01' +\ + '\x00\x05UTF-8' +\ + '\x00\x02' +\ + '\x00\x02en' +\ + '\x00\x03' +\ + '\x00\x02US') + +import socket, ssl +from struct import pack + +from util import fmt_to_dict + +SiteState = fmt_to_dict('|',':') + +def do_https(v): + s = socket.SocketType() + s.connect(('kdc.uas.aol.com', 443)) + s = ssl.wrap_socket(s) + s.write(v) + return s + +def make_packet(un, password): + #i0 are two "random bytes", not sure what the restrictions here are + n = top3 + 'i0' + middle + n += pack_pstr(un) + n += m2 + r = make_remainder(password) + n += pack('!H', len(r)) + n += r + t = top % (len(n)) + return t + top2 + n + +def get_krbtgt(un, password): + n = make_packet(un, password) + s = do_https(n); + return s.read() + +def make_packet2(un, password): + n = top3 + 'i0' + middle + n += pack_pstr(un.encode('ascii')) + n += m2 + r = make_remainder(password) + n += pack('!H', len(r)) + n += r + return n + +def get_krbtgt2(un, password): + import urllib2 + n = make_packet2(un, password) + loc = 'https://' + HOST + '/' + req = urllib2.Request(loc, n, headers={'Content-Type': 'application/x-snac', + 'Accept': 'application/x-snac'}) + return urllib2.urlopen(req).read() + + + +from struct import unpack +from datetime import datetime +import base64 +from util import UrlQuery +from OscarUtil import s_tlv, s_tlv_list, tlv #@UnresolvedImport + +def readshort(bytes): + return unpack('!H', bytes[:2])[0], bytes[2:] + +def readlen(bytes, _len): + return bytes[:_len], bytes[_len:] + +from logging import getLogger; xsnaclog = getLogger('X_SNAC') + +class X_SNAC(object): + def __init__(self, bytes): + xsnaclog.info('bytes were %r', bytes) + self.family, self.subtype = unpack('!HH', bytes[:4]) + bytes = bytes[4:] + self.flags, bytes = bytes[:8], bytes[8:] + self.reqid, bytes = bytes[:2], bytes[2:] + self.date1 = datetime.fromtimestamp(unpack('!I', bytes[:4])[0]) + bytes = bytes[4:] + self.unknown1, bytes = readlen(bytes, 4) + plen, bytes = readshort(bytes) + self.principal1, bytes = readlen(bytes, plen) + plen, bytes = readshort(bytes) + self.principal2, bytes = readlen(bytes, plen) + num_tokens, bytes = readshort(bytes) + self.tokens = [] + for i in range(num_tokens): + d = {} + self.tokens.append(d) + d['main'], bytes = s_tlv(bytes) + d['strs'] = [] + for j in range(4): + l, bytes = readshort(bytes) + s, bytes = readlen(bytes, l) + d['strs'].append(s) + d['0x10'], bytes = unpack('!B', bytes[0]), bytes[1:] + l, bytes = readshort(bytes) + d['footer_junk1'], bytes = readlen(bytes, l) + d['footer_dates'], bytes = readlen(bytes, 0x18) + dates = [d['footer_dates'][x:x+4] for x in range(0, 0x18, 4)] + dates = [unpack('!I', date)[0] for date in dates] + d['footer_dates'] = [datetime.fromtimestamp(date) for date in dates] + d['footer_junk2'], bytes = readlen(bytes, 12) + num_tlvs, bytes = readshort(bytes) + d['footer_tlvs'], bytes = s_tlv_list(bytes, num_tlvs) + num_tlvs, bytes = readshort(bytes) + self.footer = s_tlv_list(bytes, num_tlvs) + +#https://my.screenname.aol.com/_cqr/login/login.psp? +#sitedomain=sns.webmail.aol.in +#siteState=OrigUrl%3dhttp%3A%2F%2Fwebmail%2Eaol%2Ein%2FSuite%2Easpx%3Fapp%253Dmail +#mcState=doAAMAuth +#authToken=%2FBcAG0cFBg0AAPdsAapki0cFBkkIbpTcSQ3QwQAAAA%3D%3D + +def go_to_mail(un="digsby01", password="thisisapassword", + baseurl='https://my.screenname.aol.com/_cqr/login/login.psp?', + sitedomain='sns.webmail.aol.com', + OrigUrl='http://webmail.aol.com/Suite.aspx?'): + OrigUrl = UrlQuery(OrigUrl, app='mail') + t = X_SNAC(get_krbtgt2(un, password)) + mytlv = t.tokens[1]['main']; + authTok = base64.b64encode(tlv(mytlv.t, mytlv.v)); + out = UrlQuery(baseurl, + sitedomain=sitedomain, + lang='en', + locale='us', + siteState='OrigUrl=' + OrigUrl, + mcState='doAAMAuth', + authToken=authTok); + import wx + wx.LaunchDefaultBrowser(out) + +def go_to_mail2(un="digsby01", password="passwordsshouldntbeinsourcecode", remainder=''): + t = X_SNAC(get_krbtgt2(un, password)) + #should check the type of the tlv to find the right one. + mytlv = t.tokens[1]['main'] + authTok = base64.b64encode(tlv(mytlv.t, mytlv.v)) + baseurl='https://my.screenname.aol.com/_cqr/login/login.psp?' + out = UrlQuery(baseurl,authToken=authTok) + import wx + wx.LaunchDefaultBrowser(out+remainder) + + + +def go_to_compose(un="digsby01", password="theresalotofthesepasswords", **k): + xsnaclog.debug_s('go_to_compose %s', k) + t = X_SNAC(get_krbtgt2(un, password)) + #should check the type of the tlv to find the right one. + mytlv = t.tokens[1]['main'] + authTok = base64.b64encode(tlv(mytlv.t, mytlv.v)) + baseurl='https://my.screenname.aol.com/_cqr/login/login.psp?' + from urllib import quote + out = UrlQuery(baseurl,authToken=authTok, + mcState='doAAMAuth', + sitedomain='sns.webmail.aol.com', + siteState=SiteState({},ver='2', + ac='WS', + at='SNS', + ld='webmail.aol.com', + rp=quote(UrlQuery('mail/composemessage.aspx?', **k), safe=''), + uv='AIM', + lc='en-us')) + import wx + xsnaclog.debug_s('go_to_compose out: %r', out) + wx.LaunchDefaultBrowser(out) + +def go_to_msg(un="digsby01", password="howmanypasswordsdoesittaketochangealightbulb", msg='18282583'): + t = X_SNAC(get_krbtgt2(un, password)) + #should check the type of the tlv to find the right one. + mytlv = t.tokens[1]['main'] + authTok = base64.b64encode(tlv(mytlv.t, mytlv.v)) + baseurl='https://my.screenname.aol.com/_cqr/login/login.psp?' + from urllib import quote + out = UrlQuery(baseurl,authToken=authTok, + mcState='doAAMAuth', + sitedomain='sns.webmail.aol.com', + lang='en', + locale='us', + siteState='ver:2|ac:WS|at:SNS|ld:webmail.aol.com|rp:' + + quote(UrlQuery('Lite/MsgRead.aspx?', + dict(folder='Inbox',uid='1.' +msg, seq='1', start='0')), safe='') + +'|uv:AIM|lc:en-us') + import wx + wx.LaunchDefaultBrowser(out) +#'Lite/MsgRead.aspx%3Ffolder%3DInbox%26uid%3D1.18282583%26seq%3D1%26start%3D0' + +#login2.go_to_mail2(un='digsby01', password='three',remainder='&mcState=doAAMAuth&sitedomain=sns.webmail.aol.com&lang=en&locale=us&siteState=ver:2|ac:WS|at:SNS|ld:webmail.aol.com|rp:mail%2fcomposemessage.aspx?to=test%26subject=test2%26body=test3|uv:AIM|lc:en-us') + + +#http://webmail.aol.com/30978/aim/en-us/Lite/MsgRead.aspx?folder=Inbox&uid=1.15292301&seq=1&start=0 +#siteState='ver:2|ac:WS|at:SNS|ld:webmail.aol.com|rp:mail%2fcomposemessage.aspx?|uv:AIM|lc:en-us' diff --git a/digsby/src/oscar/misc_consts.py b/digsby/src/oscar/misc_consts.py new file mode 100644 index 0000000..276a0ad --- /dev/null +++ b/digsby/src/oscar/misc_consts.py @@ -0,0 +1,166 @@ +#======================================================================================================================= +# Message types and flags +#======================================================================================================================= + +#======================================================================================================================= +# Message types. +# Each OSCAR message has type. It can be just plain message, url message, contact list, wwp, +# email express or another. Only one byte used for message type. Here is the list of known message types: +#======================================================================================================================= + +class MType: + PLAIN = 0x01 # Plain text (simple) message + CHAT = 0x02 # Chat request message + FILEREQ = 0x03 # File request / file ok message + URL = 0x04 # URL message (0xFE formatted) + AUTHREQ = 0x06 # Authorization request message (0xFE formatted) + AUTHDENY = 0x07 # Authorization denied message (0xFE formatted) + AUTHOK = 0x08 # Authorization given message (empty) + SERVER = 0x09 # Message from OSCAR server (0xFE formatted) + ADDED = 0x0C # "You-were-added" message (0xFE formatted) + WWP = 0x0D # Web pager message (0xFE formatted) + EEXPRESS = 0x0E # Email express message (0xFE formatted) + CONTACTS = 0x13 # Contact list message + PLUGIN = 0x1A # Plugin message described by text string + AUTOAWAY = 0xE8 # Auto away message + AUTOBUSY = 0xE9 # Auto occupied message + AUTONA = 0xEA # Auto not available message + AUTODND = 0xEB # Auto do not disturb message + AUTOFFC = 0xEC # Auto free for chat message + +#======================================================================================================================= +# Message flags. +# Message flag used to indicate additional message properties. +# like auto message, multiple recipients message, etc. +# Message flag field occupy 1 byte. Here is the list of known message flag codes: +#======================================================================================================================= +class MFlag: + NORMAL = 0x01 # Normal message + AUTO = 0x03 # Auto-message flag + MULTI = 0x80 # This is multiple recipients message + + + +#======================================================================================================================= +# User classes +# AOL users are divided into several classes. User class field is a 2 byte bitmask. +# For example ICQ non-commercial account with away status has user-class=0x0070 +# (CLASS_FREE | CLASS_AWAY | CLASS_ICQ = 0x0070). +# Here is the list of known bit values in user class bitmask: +#======================================================================================================================= +class UserClass: + UNCONFIRMED = 0x0001 # AOL unconfirmed user flag + ADMINISTRATOR = 0x0002 # AOL administrator flag + AOL = 0x0004 # AOL staff user flag + COMMERCIAL = 0x0008 # AOL commercial account flag + FREE = 0x0010 # ICQ non-commercial account flag + AWAY = 0x0020 # Away status flag + ICQ = 0x0040 # ICQ user sign + WIRELESS = 0x0080 # AOL wireless user + UNKNOWN100 = 0x0100 # Unknown bit + UNKNOWN200 = 0x0200 # Unknown bit + UNKNOWN400 = 0x0400 # Unknown bit + UNKNOWN800 = 0x0800 # Unknown bit + + + +#======================================================================================================================= +# User status +# ICQ service presence notifications use user status field which consist of two parts. +# First is a various flags (birthday flag, webaware flag, etc). +# Second is a user status (online, away, busy, etc) flags. Each part is a two bytes long. +# Here is the list of masks for both parts: +#======================================================================================================================= +class UserStatus: + WEBAWARE = 0x0001 # Status webaware flag + SHOWIP = 0x0002 # Status show ip flag + BIRTHDAY = 0x0008 # User birthday flag + WEBFRONT = 0x0020 # User active webfront flag + DCDISABLED = 0x0100 # Direct connection not supported + DCAUTH = 0x1000 # Direct connection upon authorization + DCCONT = 0x2000 # DC only with contact users + ONLINE = 0x0000 # Status is online + AWAY = 0x0001 # Status is away + DND = 0x0002 # Status is no not disturb (DND) + NA = 0x0004 # Status is not available (N/A) + OCCUPIED = 0x0010 # Status is occupied (BISY) + FREE4CHAT = 0x0020 # Status is free for chat + INVISIBLE = 0x0100 # Status is invisible + + + +#======================================================================================================================= +# Direct connection type +# ICQ clients can send messages and files using peer-to-peer connection called "direct connection" (DC). +# Each ICQ client may have different internet connection: +# direct, proxy, firewall or other and to establish DC one client should know connection type of another client. +# This connection type also used by direct connections and called "DC type". Here is the list of values: +#======================================================================================================================= +class DCType: + DISABLED = 0x0000 # Direct connection disabled / auth required + HTTPS = 0x0001 # Direct connection thru firewall or https proxy + SOCKS = 0x0002 # Direct connection thru socks4/5 proxy server + NORMAL = 0x0004 # Normal direct connection (without proxy/firewall) + WEB = 0x0006 # Web client - no direct connection + + + +#======================================================================================================================= +# Direct connection protocol version +# ICQ clients can send messages and files using peer-to-peer connection called "direct connection" (DC). +# Here is the list of direct connection protocol versions: +#======================================================================================================================= + +class DCProtoVersion: + ICQ98 = 0x0004 # ICQ98 + ICQ99 = 0x0006 # ICQ99 + ICQ2000 = 0x0007 # ICQ2000 + ICQ2001 = 0x0008 # ICQ2001 + ICQLITE = 0x0009 # ICQ Lite + ICQ2003B = 0x000A # ICQ2003B + + + +#======================================================================================================================= +# Random chat groups +# ICQ service has ability to search a random user in specific group and each ICQ client +# may choose group where another client can find it. Here is the list of groups and their codes: +#======================================================================================================================= +class RandomChat: + GENERAL = 0x0001 # General chat group + ROMANCE = 0x0002 # Romance random chat group + GAMES = 0x0003 # Games random chat group + STUDENTS = 0x0004 # Students random chat group + SOMETHING20 = 0x0006 # 20 something random chat group + SOMETHING30 = 0x0007 # 30 something random chat group + SOMETHING40 = 0x0008 # 40 something random chat group + PLUS50 = 0x0009 # 50+ random chat group + SWOMEN = 0x000A # Seeking women random chat group + SMAN = 0x000B # Seeking man random chat group + +#======================================================================================================================= +# Motd types list +# ICQ/AIM services use special SNAC(01,13) for motd (message of the day) notices during login. +# Here is the list of known motd types: +#======================================================================================================================= +class MOTD: + MDT_UPGRAGE = 0x0001 # Mandatory upgrade needed notice + ADV_UPGRAGE = 0x0002 # Advisable upgrade notice + SYS_BULLETIN = 0x0003 # AIM/ICQ service system announcements + NORMAL = 0x0004 # Standart notice + NEWS = 0x0006 # Some news from AOL service + +#======================================================================================================================= +# Marital status code list +# There was some new fields added to ICQ client data. One of them is marital status field. +# Here is the marital status code list: +#======================================================================================================================= +class MaritalStatus: + NONE = 0x0000 # Marital status not specified + SINGLE = 0x000A # User is single + LONGRS = 0x000B # User is in a long-term relationship + ENGAGED = 0x000C # User is engaged + MARRIED = 0x0014 # User is married + DIVORCED = 0x001E # User is divorced + SEPARATED = 0x001F # User is separated + WIDOWED = 0x0028 # User is widowed diff --git a/digsby/src/oscar/oscarformat.py b/digsby/src/oscar/oscarformat.py new file mode 100644 index 0000000..b9ee45e --- /dev/null +++ b/digsby/src/oscar/oscarformat.py @@ -0,0 +1,89 @@ +from util import Point2HTMLSize, Storage as S + +DEFAULTS = S(FONT = 'Times New Roman', + SIZE = 12, + BACK = (255, 255, 255), + FORE = (0, 0, 0)) + +def tohex(t, n = 3): + return ''.join('%02x' % c for c in t[:n]) + + +pointsize_map = { + 8: 1, + 10: 2, + 12: 3, + 14: 4, + 18: 5, + 24: 6, + 36: 7, + 38: 7 +} + +def aimsize(n): + n = int(n) + + try: + return pointsize_map[n] + except KeyError: + return Point2HTMLSize(n) + + +def to_aimhtml(s, fmt, body_bgcolor = False, replace_newlines=False): + ''' + Given a string, and a fmt dictionary containing color and font information, returns "AIM" html. + + AIM html is a limited subset of HTML displayed by AIM clients. + ''' + before, after = '', '' + + fontface = fontsize = fontfore = fontback = None + + face = fmt.get('face', DEFAULTS.FONT) + if face != DEFAULTS.FONT: fontface = face + + size = fmt.get('size', DEFAULTS.SIZE) + if size != DEFAULTS.SIZE: fontsize = size + + back = fmt.get('backgroundcolor', DEFAULTS.BACK) + if back != DEFAULTS.BACK: fontback = back + + fore = fmt.get('foregroundcolor', DEFAULTS.FORE) + if fore != DEFAULTS.FORE: fontfore = fore + + bodyattrs = None + + if fontface or fontsize or fontback or fontfore: + if body_bgcolor and fontback and fontback != (255, 255, 255): + bodyattrs = 'BGCOLOR="#%s"' % tohex(fontback) + + fontattrs = ' '.join(filter(lambda s: bool(s), + [(('face="%s"' % fontface) if fontface else ''), + (('size="%s"' % aimsize(fontsize)) if fontsize else ''), + (('color="#%s"' % tohex(fontfore)) if fontfore else ''), + (('back="#%s"' % tohex(fontback)) if (not body_bgcolor and fontback and + fontback[:3] != (255, 255, 255)) else '') + ])) + + fontattrs = fontattrs.strip() + + if fontattrs: + before += '' % fontattrs + after += '' + + for a in ('bold', 'underline', 'italic'): + if fmt.get(a, False): + tag = a[0] + before += '<%s>' % tag + after = '' % tag + after + + if bodyattrs: + before = ('' % bodyattrs) + before + after += '' + + if replace_newlines: + last = lambda _s: _s.replace('\n', '
') + else: + last = lambda _s: _s + + return last(''.join([before, s, after])) diff --git a/digsby/src/oscar/rendezvous/__init__.py b/digsby/src/oscar/rendezvous/__init__.py new file mode 100644 index 0000000..bb9c90b --- /dev/null +++ b/digsby/src/oscar/rendezvous/__init__.py @@ -0,0 +1,7 @@ +""" +Rendezvous is the subset of OSCAR that handles chat, and also peer-to-peer +operations like direct connections, file transfers, and video chats. +""" +from oscar.rendezvous.peer import handlech2 +from oscar.rendezvous.directim import directconnect +from oscar.rendezvous.rendezvous import rdv_snac, rdv_types diff --git a/digsby/src/oscar/rendezvous/chat.py b/digsby/src/oscar/rendezvous/chat.py new file mode 100644 index 0000000..8966136 --- /dev/null +++ b/digsby/src/oscar/rendezvous/chat.py @@ -0,0 +1,99 @@ +''' + +Oscar chat functionality + +''' + +from oscar.rendezvous.rendezvous import rdv_snac, rdv_types, rendezvous_tlvs as rdv_tlvs +from oscar import capabilities +from oscar.OscarUtil import tlv, s_tflv +import oscar +from util import callsback +from common import profile +from logging import getLogger; log = getLogger('oscar.rdv.chat') +from common.Protocol import ChatInviteException + +import hooks +import struct + +@callsback +def invite(o, screenname, chatcookie, message = None, callback=None): + ''' + Given a chat cookie (see OscarConversation) and a screenname, sends an + invite to the buddy asking them to join the + ''' + + clen = len(chatcookie) + xdata = struct.pack('!HB%ds' % clen, 4, clen, chatcookie) + '\x00\x00' + + if message is None: + message = 'Join me in this chat!' + + data = ''.join([ + tlv(0x0a, 2, 1), + tlv(0x0f), + tlv(0x0c, message), + tlv(rdv_tlvs.extended_data, xdata)]) + + snac = rdv_snac(screenname = screenname, + capability = capabilities.by_name['chat_service'], + type = rdv_types.request, + data = data) + + def on_error(snac, exc): + try: + (fam, fam_name), (errcode, errmsg), (_, _) = exc.args + except Exception: + pass + else: + if (fam, errcode) == (4, 4): # buddy is offline + reason = ChatInviteException.REASON_OFFLINE + elif (fam, errcode) == (4, 9): # buddy doesn't support groupchat + reason = ChatInviteException.REASON_GROUPCHAT_UNSUPPORTED + else: + reason = ChatInviteException.REASON_UNKNOWN + + callback.error(ChatInviteException(reason)) + return True # stops snac error popup + + o.send_snac(*snac, **dict(req=True, cb=callback.success, err_cb=on_error)) + +def unpack_chat_invite(data): + fmt = (('rendtlvs', 'named_tlvs', -1, rdv_tlvs),) + rendtlvs, data = oscar.unpack(fmt, data) + invite_msg = oscar.decode(rendtlvs.user_message, getattr(rendtlvs, 'encoding', 'ascii')) + xdata = rendtlvs.extended_data + + cfmt = (('four', 'H'), ('cookie', 'pstring')) + _four, chatcookie, _extra = oscar.util.apply_format(cfmt, xdata) + return invite_msg, chatcookie + +class OscarChatConnection(object): + def __init__(self, oscar, inviting_buddy): + self.oscar = oscar + self.buddy = inviting_buddy + + def handlech2(self, message_type, data): + invite_msg, chatcookie = unpack_chat_invite(data) + + def on_yes(): + from common import netcall + netcall(lambda: self.oscar.join_chat(cookie=chatcookie)) + + profile.on_chat_invite( + protocol = self.oscar, + buddy = self.buddy, + message = invite_msg, + room_name = chatcookie, + on_yes=on_yes) + +def chat_invite_handler(o, screenname, cookie): + return OscarChatConnection(o, o.get_buddy(screenname)) + +def initialize(): + log.info('\tloading rendezvous handler: chat service') + import oscar.rendezvous.peer as peer + peer.register_rdv_factory('chat_service', chat_invite_handler) + +hooks.register('oscar.rdv.load', initialize) + diff --git a/digsby/src/oscar/rendezvous/directim.py b/digsby/src/oscar/rendezvous/directim.py new file mode 100644 index 0000000..1e94c01 --- /dev/null +++ b/digsby/src/oscar/rendezvous/directim.py @@ -0,0 +1,270 @@ +''' +Messaging and photo sharing over Oscar direct connections. +''' + +from __future__ import with_statement +from util import strlist, lookup_table, bitflags_enabled, myip, callsback +from util.packable import Packable +from oscar.rendezvous.peer import OscarPeer +from oscar.rendezvous.rendezvous import rendezvous_tlvs +from oscar.OscarUtil import tlv_list +import oscar, common, time, struct +from logging import getLogger; log = getLogger('oscar.rdv.directim'); info=log.info +import os.path, wx +from util.BeautifulSoup import BeautifulSoup +from functools import partial +from oscar import OscarException + +from common.Protocol import StateMixin + +import hooks + +def directconnect(protocol, screenname): + ''' + Requests a direct connect connection. + + Returns the DirectIM object. + ''' + + if not isinstance(screenname, str): raise TypeError('screenname must be a str') + + # Make sure the buddy isn't yourself + ostrip = lambda s: s.lower().replace(' ','') + if ostrip(screenname) == ostrip(protocol.self_buddy.name): + raise OscarException("You cannot direct connect with yourself.") + + cookie = int(time.time()**2) #TODO: there's a very small chance this will suck + protocol.rdv_sessions[cookie] = dim = OscarDirectIM(protocol, screenname, cookie) + dim.request() + + return dim + +class ODCHeader(Packable): + ''' + Sent over direct IM connections for text messages, typing notifications, + and photos. + ''' + + # All DirectIM communication is preceded by these headers: + + fmt = strlist(''' + version 4s # always ODC2 + hdrlen H # length of header + one H # 1 + six H # 6 + zero H # 0 + cookie Q # sixteen byte rendezvous cookie + null Q + length I + encoding H + subset H + flags I + zero I + screenname 16s + null2 16s ''') + + @classmethod + def make(cls, *a, **k): + 'Makes a packable with default values filled in.' + + k.update(dict(version = 'ODC2', static = 76, one = 1, six = 6, zero = 0, + null = 0, null2 = '\0'*16)) + return cls(*a, **k) + + # for the "flags" field above. + bitflags = lookup_table({ + 0x01: 'autoresponse', + 0x02: 'typingpacket', # has typing info + 0x04: 'typed', + 0x08: 'typing', + 0x20: 'confirmation', + 0x40: 'mac_confirmation', + }) + + invars = [lambda o: o.version == 'ODC2',] + +class OscarDirectIM( OscarPeer, StateMixin ): + + class Statuses: + DISCONNECTED = 'Disconnected' + CONNECTING = 'Connecting' + ERROR = 'Error' + CONNECTED = 'Connected' + OFFLINE = DISCONNECTED + + class Reasons: + NONE = 'None' + + def __init__(self, protocol, screenname, cookie): + StateMixin.__init__(self, self.Statuses.CONNECTING) + direct_im_cap = oscar.capabilities.by_name['direct_im'] + OscarPeer.__init__(self, protocol, screenname, cookie, direct_im_cap) + self.buddy = protocol.buddies[screenname] + + + def accept(self): + info('%r accepted', self) + self.establish_dc() + + def handle_request(self, rendtlvs): + 'Logic for incoming direct connect requests.' + + # + # until DirectIM is fully implemented... + # + # self.protocol.hub.on_direct_connect(self) + # + self.send_rdv('cancel') + + def setup_convo(self): + # Set the buddy's conversation to a Direct Connect convo + self.convo = self.protocol.convo_for(self.screenname) + self.convo.setnotifyif('type', 'dc') + self.convo.dc = self + self.change_state(self.Statuses.CONNECTED) + + def on_odc_connection(self): + self.socket.receive_next( ODCHeader, self.odc_header_received ) + self.setup_convo() + + # Rendezvous ACCEPT, and a confirmation ODC message. + self.send_odc(flags = 0x60) + + def odc_header_received(self, data): + packet, data = ODCHeader.unpack(data) + flags = bitflags_enabled(ODCHeader.bitflags, packet.flags) + info('incoming ODC header - enabled flags: %s', ', '.join(flags)) + + # Set typing status flags in the conversation if typing notification + # bitflags are set. + typeset = partial(self.convo.set_typing_status, self.screenname) + if 'typingpacket' in flags: + if 'typing' in flags: typeset('typing') + elif 'typed' in flags: typeset('typed') + else: typeset(None) + + next = self.socket.receive_next + leftover = packet.hdrlen - ODCHeader._struct.size + if leftover: + # There are extra padding bytes after the header, read those first. + next( leftover, self.read_leftover(packet.length) ) + elif packet.length > 0: + # There is a message body to receive. + next( packet.length, self.odc_body_received ) + else: + # Otherwise, prepare for the next header. + next( ODCHeader, self.odc_header_received ) + + def read_leftover(self, paklen): + info('read %d leftover bytes', paklen) + next = self.socket.receive_next + def callback(data): + if paklen > 0: next( paklen, self.odc_body_received ) + else: next( ODCHeader, self.odc_header_received ) + return callback + + def odc_body_received(self, data): + info('odc_body_received') + + # Get a place to store the images. + import stdpaths + assetdir = stdpaths.userdata + + # Did the message include an inline image? + if '' in data: + j = data.find('') + + # Parse the HTML _before_ + soup = BeautifulSoup(data[:j]) + for img in soup.html.body('img'): # may have more than one + + # For each tag + imgdata = data[j:] + findme = ' ID="%s" SIZE="%s">' % (str(img['id']), str(img['datasize'])) + i = imgdata.find(findme) + imgbytes = imgdata[i + len(findme):int(img['datasize'])+33] + + # os.path.split the img src, because some clients send their + # full paths. (file:///c:/blah.jpg) + imgpath = os.path.join(assetdir, os.path.split(img['src'])[1]) + + img['src'] = imgpath + del img['width']; del img['height'] + + with open(imgpath, 'wb') as f: f.write(imgbytes) + + msg = unicode(soup.html) + else: + msg = data + + self.convo.incoming_message(self.screenname, msg) + self.socket.receive_next( ODCHeader, self.odc_header_received ) + + def send_odc(self, data = '', flags = 0): + bname = self.protocol.self_buddy.name + + # Create an ODC header with our cookie and (buddy's) screenname + packet = ODCHeader.make(cookie = self.cookie, screenname = bname) + packet.flags = flags + if isinstance(data, list): + packet.length = sum(len(d) for d in data) + info('send_odc got a list, summed length is %d', packet.length) + else: + packet.length = len(data) + info('send_odc got a string, length is %d', packet.length) + packet.hdrlen = len(packet) + + info('sending ODC header to screenname %s (%d bytes of %r)', + bname, len(packet), str(type(packet))) + + if isinstance(data, list): + self.socket.push( packet.pack() + ''.join(data) ) + else: + self.socket.push( packet.pack() + data ) + + def send_typing(self, status): + ''' + status must be 'typing', 'typed', or None + ''' + + flag = {'typing':0x8, + 'typed' :0x4, + None :0x0}[status] + + self.send_odc(flags=0x2 | flag) + + def request(self): + self.establish_out_dc() + + def ch2accept(self, data): + info('incoming dIM got channel 2 accept') + + def send_message(self, message): + self.send_odc(message) + + def decline(self): + self.send_rdv('cancel') + + def ch2cancel(self, data): + info('%r cancelled', self) + if hasattr(self, 'convo'): + self.convo.setnotifyif('type', 'im') + self.change_state(self.Statuses.DISCONNECTED) + + def disconnect(self): + 'DirectIMSocket will invoke this method when the socket is closed.' + + self.socket.close() + del self.convo.dc + self.convo.setnotifyif('type', 'im') + self.change_state(self.Statuses.DISCONNECTED) + + def __repr__(self): + return '' % self.screenname + +def initialize(): + log.info('\tloading rendezvous handler: direct IM') + import oscar.rendezvous.peer as peer + peer.register_rdv_factory('direct_im', OscarDirectIM) + +hooks.Hook('oscar.rdv.load').register(initialize) diff --git a/digsby/src/oscar/rendezvous/filetransfer.py b/digsby/src/oscar/rendezvous/filetransfer.py new file mode 100644 index 0000000..0665be3 --- /dev/null +++ b/digsby/src/oscar/rendezvous/filetransfer.py @@ -0,0 +1,703 @@ +''' +Oscar File Transfer + +the definitive reference: +U{http://gaim.sourceforge.net/summerofcode/jonathan/ft_doc.pdf} +''' +from __future__ import with_statement + +__metaclass__ = type + +from oscar.rendezvous.peer import OscarPeer +from oscar.rendezvous.rendezvous import oscarcookie, map_intarg, rendezvous_tlvs +import oscar.capabilities as capabilities +from util import Storage, strlist, NoneFileChunker, to_hex +from util.packable import Packable +import util, oscar.snac, oscar.OscarUtil +import common +import hooks +from path import path +import os.path +import traceback +import struct + +from logging import getLogger +log = getLogger('oscar.rdv.ft'); info = log.info + +def prettyoft(oft): + return '\n'.join('%20s: %r' % line for line in list(iter(oft))) + + +rz, tlv, tlv_list = rendezvous_tlvs, oscar.OscarUtil.tlv, oscar.OscarUtil.tlv_list + + + +# +# For sending a file or folder +# +def send(oscar, screenname, filestorage): + 'Request for sending a file to a buddy.' + cookie = oscarcookie() + oscar.rdv_sessions[cookie] = transfer = \ + OutgoingOscarFileTransfer(oscar, screenname, cookie, filestorage) + transfer.request() + return transfer + +# +# Oscar file transfer packets - headers sent and received on an oscar direct +# connection. +# +class OFTHeader(Packable): + fmt = strlist(''' + protocol_version 4s # Always 'OFT2' + length H # includes all data, including version and length + type H # one of "types" below + ''') + + invars = [lambda self: self.protocol_version == 'OFT2', + lambda self: self.type in self.types.values()] + + types = Storage(prompt = 0x0101, + ack = 0x0202, + done = 0x0204, + receiver_resume = 0x0205, + sender_resume = 0x0106, + rcv_resume_ack = 0x0207, + ) + + +assert len(OFTHeader()) == 8 + +class OFTBody(Packable): + fmt = strlist(''' + cookie Q + encryption H + compression H + num_files H + files_left H + num_parts H + parts_left H + total_size I + file_size I + + modification_time I # since unix epoch + checksum I # see OscarFileTransferChecksum + recv_fork_checksum I + fork_size I + creation_time I + fork_checksum I + bytes_received I + recv_checksum I + id_string 32s # 32 byte right padded string: usually 'CoolFileXfer' + + flags B # Flags: 0x20 - Negotiation (not complete), 0x01 - Done + list_name_offset B # always 0x1c + list_size_offset B # always 0x11 + + dummy_block 69s # Dummy Block - large null block for future expansion of OFT + + mac_file_info 16s # Mac File Info + + charset H # charset + subset H # subset: 0 for ASCII, 2 for UTF-16BE, 3 for ISO-8859-1 + ''') + + default_checksum = 0xffff0000 + + @staticmethod + def padfilename(filename): + '''Following an OFT body is a padded filename which at least 64 + characters and maybe more.''' + + if len(filename) < 64: + filename += '\0' * (64 - len(filename)) + assert len(filename) == 64 + return filename + +# Right pad an ID string for the header +OFTId = 'Cool FileXfer' +OFTId = OFTId + ( '\0' * (32 - len(OFTId)) ) + +# The two glued together should always equal 192 bytes. +assert len(OFTHeader()) + len(OFTBody()) == 192 + +# +# file transfer logic +# + +class OscarFileTransfer( OscarPeer ): + 'Base class for file transfers, providing channel 2 messaging.' + + def __init__(self, protocol, screenname, cookie): + OscarPeer.__init__(self, protocol, screenname, cookie, + capabilities.by_name['file_xfer']) + common.FileTransfer.__init__(self) + self.cancelled = False + self.resuming = False + self.on_get_buddy(self.buddy) + + def ch2cancel(self, data): + log.info('%r cancelled by buddy', self) + self.cancelled = True + self.cancel_by_buddy() + if data: + log.info('data in a ch2 cancel: %s', to_hex(data)) + + def cancel_by_buddy(self): + log.info('cancel_by_buddy') + if self.state != self.states.CANCELLED_BY_YOU: + self.state = self.states.CANCELLED_BY_BUDDY + + try: + self.fileobj.close() + except Exception: + traceback.print_exc() + + + def on_close(self): + log.info('%r on_close', self) + + try: + self.fileobj.close() + except AttributeError: + pass + + if self.state == self.states.TRANSFERRING: + self.state = self.states.CONN_FAIL_XFER + + self.on_error() + + + def close(self): + log.info('close') + try: + self.socket.close() + del self.socket + except Exception: + pass + self.done = True + try: + self.fileobj.close() + except AttributeError: + pass + + + def cancel(self, msg = '', state=None): + if hasattr(self, 'socket') and self.socket: + try: + self.socket.cancel_timeout() + except Exception: + #temporary until I figure out how to cancel + traceback.print_exc() + + try: + self.filechunker.cancelled = True + except Exception: + traceback.print_exc() + info('$$$ sending cancel') + self.send_rdv('cancel') + self.close() + + self.setnotifyif('cancelled', True) + + info('%r cancelled. %s', self, msg) + if state is None: + state = self.states.CANCELLED_BY_YOU + self.state = state + + def ch2accept(self, data): + if data: + log.error('got data in a channel 2 accept: data = %r', data) + self.cancel() + else: + log.info('ch2accept data = %r', data) + + def decline_transfer(self, reason = None): + 'Cancels this file transfer.' + log.info('decline_transfer: reason = %r', reason) + self.cancel() + + def send_oft(self, type, setvalues=True): + # Use most of the values received from the sender, but set some: + + + oft = self.oft + + if not oft: + return + + if setvalues: + oft.id_string = OFTId + oft.cookie = self.cookie + oft.list_name_offset = 0x1c + oft.list_size_offset = 0x11 + oft.flags = 0x01 if 'type' == 'done' else 0x20 + oft.checksum = OFTBody.default_checksum #TODO: incomplete transfers + + info('sending oft %s for %s', type, self.filename ) + info(prettyoft(oft) + '\n filename: %s', self.filename) + self.socket.push(oftpacket(type, oft, self.filename)) + +# +# sending +# + +class OutgoingOscarFileTransfer(OscarFileTransfer, common.OutgoingFileTransfer): + + def __init__(self, o, screenname, cookie, fileinfo): + OscarFileTransfer.__init__(self, o, screenname, cookie) + + [setattr(self, a, fileinfo[a]) for a in \ + 'name files size numfiles'.split()] + + if fileinfo.numfiles == 1 and 'obj' in fileinfo: + fileinfo.obj.close() + + else: + self.rootpath = fileinfo.path + + self.filepath = path(fileinfo.path) + + + # If obj is an open file object, it's raw contents are dumped to the + # socket after the next + self.fileobj = None + self.oft = self.next_file() + + self.accepted = False + self.connected = False + self.completed = 0 + self.state = self.states.WAITING_FOR_BUDDY + + def next_file(self): + if self.files: + self.file = self.files.pop(0) + + # When sending folders in AIM, path elements are separated by + # ASCII character 1. + if hasattr(self, 'rootpath'): + p = self.rootpath.relpathto(self.file) + self.filename = p.normpath().replace('\\','/').replace('/', chr(1)) + else: + self.filename = self.file.name + + return self.oft_for_file(self.file) + else: + self.file = None + return False + + def __repr__(self): + return '' % \ + (self.screenname, getattr(self.file, 'name', None) or '') + + def request(self, message = ''): + # Assemble information about the file or directory we are sending. + xdata = xdata_block(self.name, self.size, self.numfiles) + + self.establish_out_dc(message=message, extratlvs=[ + (rz.extended_data, xdata), + (rz.filename_encoding, 'utf-8'), + ]) + + def on_odc_connection(self): + info('%r connected!', self) + self.connected = True + self.maybe_start() + + def received_oft_header(self, data): + 'Received the first part of an OFT packet.' + header, data = OFTHeader.unpack(data) + assert data == '' + log.debug('received OFT header: oft=%(protocol_version)s length=0x%(length)x type=0x%(type)x', dict(header)) + + bytes_left = header.length - OFTHeader._struct.size + + # ACK - the receiver is ready to receive binary file data. + # Resume ack - receiver is ready to receive file data, starting from offset + if header.type in (OFTHeader.types.ack, OFTHeader.types.rcv_resume_ack,):#OFTHeader.types.receiver_resume): + #self.state = self.states.CONNECTING + self.open_file() + # DONE - the receiver received all the bytes of a file successfully. + elif header.type == OFTHeader.types.done: + try: + self.fileobj.close() + except AttributeError: + pass + self.fileobj = None + self.oft = self.next_file() + + if not self.oft: + if self.completed: + self.state = self.states.FINISHED + self._ondone() + else: + self.cancel_by_buddy() + self.close() + return + + # Receiver wants to resume + elif header.type == OFTHeader.types.receiver_resume: + log.info('Going to resume file %s' % self.file) + self.resuming = True + else: + log.warning('Error! OFT type %r', header.type) + + + self.socket.receive_next(bytes_left, self.received_oft_body) + + def open_file(self): + try: + self.fileobj = self.file.open('rb') + return True + except IOError: + self.cancel('Could not open file %s' % self.file) + return False + + + def received_oft_body(self, data): + 'The remainder of an OFT packet has come in.' + oft, data = OFTBody.unpack(data) + filename, data = read_cstring(data) + info(prettyoft(oft) + '\n filename: %s', filename) + self.oft = oft + self.socket.receive_next(OFTHeader, self.received_oft_header) + + # After an OFT body, we need to either... + if self.fileobj: + if self.resuming: + self.fileobj.seek(oft.bytes_received) + self._setcompleted(self.fileobj.tell()) + # Send file data, + self.filechunker = NoneFileChunker(self.fileobj, close_when_done=True, + progress_cb = self._setcompleted) + log.info('Pushing FileChunker onto socket') + self.socket.push_with_producer(self.filechunker) + elif self.oft: + # Sned a new prompt, + if self.resuming: + self.send_oft('sender_resume', False) + else: + self.send_oft('prompt') + else: + # Or close the socket. + info('DONE!') + self.state = self.states.FINISHED + self.close() + + def ch2accept(self, data): + info('received CH2 accept') + self.accepted = True + self.maybe_start() + + def maybe_start(self): + if not self.accepted or not self.connected: + if not self.accepted: info('no RDV accept yet') + if not self.connected: info('no connection yet') + return + + if self.state == self.states.FINISHED: + info('got "maybe_start" but already finished') + return + + self.state = self.states.TRANSFERRING + if not getattr(self, 'sent_first', False): + info('sending first oft prompt') + self.sent_first = True + self.send_oft('prompt') + self.socket.receive_next(OFTHeader, self.received_oft_header) + + def oft_for_file(self, file): + return OFTBody( + cookie = self.cookie, + num_files = self.numfiles, + files_left = 1 + len(self.files), + num_parts = 1, + parts_left = 1, + total_size = self.size, + file_size = file.size, + modification_time = int(file.mtime), + creation_time = int(file.ctime), + checksum = OFTBody.default_checksum, + recv_fork_checksum = OFTBody.default_checksum, + fork_checksum = OFTBody.default_checksum, + recv_checksum = OFTBody.default_checksum, + id_string = OFTId, + dummy_block = '\0' * 69, + mac_file_info = '\0' * 16, + charset = 0, + subset = 0, + ) +# +# receiving +# + +class IncomingOscarFileTransfer( OscarFileTransfer, common.IncomingFileTransfer ): + 'Negotiates a peer connection, and begins receiving a file.' + + direction = 'incoming' + + def __init__(self, o, screenname, cookie): + OscarFileTransfer.__init__(self, o, screenname, cookie) + self.completed = 0 + + def handle_request(self, rendtlvs): + '''A rendezvous packet with request number 1 has come in with the + specified TLVs.''' + + # unpack TLV 0x2711 (the last entry in the rendezvous block) + info, data = unpack_extended_data( rendtlvs.extended_data ) + + # ...and store file count, name, size + self.__dict__.update(info) + + self._onrequest() + + def accept(self, file_obj): + 'The UI accepts this file transfer.' + + # If we've already been cancelled, display an error alert. + if self.cancelled: + e = OscarFileTransferError('The sender has already cancelled this ' + 'file request.') + self.protocol.hub.on_error(e) + else: + if isinstance(file_obj, file) or self.numfiles == 1: + # For single files, the hub gives this method an open file + # object. For consistency, just grab it's path and use that, + # so that it operates like receiving a folder. + self.filepath = path(file_obj.name) + self.rootpath = self.filepath.parent + + file_obj.close() + else: + # Otherwise, make a new directory with the folder name where + # the user specified. + self.rootpath = os.path.join(file_obj, self.name) + if not os.path.exists(self.rootpath): + os.makedirs(self.rootpath) + + self.filepath = path(self.rootpath) + + assert isinstance(self.rootpath, basestring) + info('self.rootpath = %r', self.rootpath) + info('accepting incoming file transfer, saving %s to %s', + 'file' if self.numfiles == 1 else 'files', self.rootpath) + + self.state = self.states.CONNECTING + self.establish_dc() + + def decline(self): + self.state = self.states.CANCELLED_BY_YOU + self.send_rdv('cancel') + common.IncomingFileTransfer.decline(self) + + def on_odc_connection(self): + '''At this point self.socket should be a connected socket to either + another AIM user, or a proxy server with initialization already taken + care of.''' + self.state = self.states.TRANSFERRING + self.socket.receive_next(OFTHeader, self.received_oft_header) + + def received_oft_header(self, data): + 'Received the first part of an OFT packet.' + header, data = OFTHeader.unpack(data) + assert data == '' + + log.debug('received OFT header: %r', dict(header)) + if header.type == OFTHeader.types.prompt: + bytes_left = header.length - OFTHeader._struct.size + log.debug('receiving %d more OFT body bytes', bytes_left) + self.socket.receive_next(bytes_left, self.received_oft_body) + else: + self.fail('Error! OFT type ' + str(header.type)) + + def received_oft_body(self, data): + 'The remainder of an OFT packet has come in.' + oft, data = OFTBody.unpack(data) + self.oft = oft + info('incoming oft body...\n' + prettyoft(oft)) + + nullindex = data.find('\0') + if nullindex == -1 and len(data) >= 64: + self.filename = filename = data + elif nullindex == -1: + raise AssertionError("couldn't find a null byte in the padded filename") + else: + self.filename = filename = data[:nullindex] + + self.fileobj = openpath(self.rootpath, self.filepath.name) + + info('incoming file: %s (%d bytes), %d left', + filename, oft.file_size, oft.files_left) + self.send_oft('ack') + + self.socket.push_collector(self.collect_file_bytes) + if oft.file_size > 0: + self.socket.receive_next(oft.file_size, self.received_file) + else: + self.received_file() + + def collect_file_bytes(self, data): + 'When receiving file bytes, this is the asynchat collector.' + + try: + self.fileobj.write(data) + except IOError: + # probably closed file + traceback.print_exc() + return + else: + completed = self.fileobj.tell() + self.oft.bytes_received = completed + self._setcompleted(completed) + finally: + self.data = '' + + def received_file(self, *data): + self.socket.pop_collector() + + info('received file %s', self.fileobj.name) + self.fileobj.close() + self.send_oft('done') + + if self.oft.files_left == 1: + info('done!') + self._ondone() + self.socket.close() + else: + # repeat the process if there are files left. + self.socket.receive_next(OFTHeader, self.received_oft_header) + + def fail(self, msg): + log.error(msg) + self.close() + + def __repr__(self): + return '' % self.screenname + + +def oftpacket(type, body, filename): + 'Constructs a complete OFT packet.' + + #TODO: unicode + if isinstance(filename, unicode): + filename = filename.encode('utf8') + else: + filename = str(filename) + + padded = OFTBody.padfilename(filename) + return ''.join([ OFTHeader('OFT2', + length = 192 + len(padded), + type = map_intarg(type, OFTHeader.types)).pack(), + body.pack(), + padded ]) + +def oft_filename(fn): + ''' + OFT filenames are null terminated, must be at least 64 bytes long, but may + be longer. + ''' + fn += chr(0) + if len(fn) < 64: + fn += (64 - len(fn)) * chr(0) + assert len(fn) == 64 + return fn + +def read_cstring(data): #TODO: should go in OscarUtil + ''' + Reads a null terminated string from data, returning the string, and the + remainder of the data, not including the null byte. + + >>> s = 'some string' + chr(0) + 'extra data' + >>> foo, data = read_cstring(s) + >>> print foo + 'some string' + ''' + i = data.find(chr(0)) + if i == -1: + raise ValueError('not a null terminated string') + + return data[:i], data[i+1:] + +def unpack_extended_data(data): + 'Unpacks the capabilities/extra data (TLV 0x2711) in a rendezvous packet.' + + multiple, filecount, totalbytes = struct.unpack('!HHI', data[:8]) + data = data[8:] + filename, data = read_cstring(data) + + return util.Storage( numfiles = filecount, + multiple = multiple == 0x002, + size = totalbytes, + name = filename ), data + +def xdata_block(filename, filesize, filecount = 1): + 'builds TLV 0x2711 in Ch2 RDV requests, which shows filename, count' + + assert filecount > 0 and isinstance(filename, basestring) + return struct.pack('!HHI', + 0x001 if filecount == 1 else 0x002, + filecount, + filesize) + filename.encode('utf-8') + '\0' + +def openpath(rootpath, filename): + ''' + Given an "OSCAR" path, returns an open file object for the + specified file. + + In folder transfers, ASCII character 0x01 splits directories and filenames, + i.e. dir(0x01)subdir(0x01)filename.txt. This function will create any + directory hierarchy needed to open the file. + ''' + + PATHSEP = chr(1) + needdirs = filename.find(PATHSEP) != -1 + path = filename.split(PATHSEP) + + filename = path.pop(-1) + path = [rootpath] + path + + if needdirs: + pathstr = os.path.join(*path) + if not os.path.exists(pathstr): + info('calling makedirs(%s)', pathstr) + os.makedirs(pathstr) + + filepath = path + [filename] + return open(os.path.join(*filepath), 'wb') + + + +class OscarFileTransferError(Exception): pass + +class OFTChecksum(object): + starting_value = 0xffff0000L + + def __init__(self, data = None): + + self.checksum = self.starting_value + + if data is not None: + self.update(data) + + def update(self, data, offset = 0): + check = (self.checksum >> 16) & 0xffffL + + for i in xrange(len(data)): + oldcheck = check + byteval = ord(data[offset + i]) & 0xff + check -= byteval if (i & 1) != 0 else byteval << 8 + + if check > oldcheck: check -= 1 + + check = ((check & 0x0000ffff) + (check >> 16)) + check = ((check & 0x0000ffff) + (check >> 16)) + + checksum = check << 16 & 0xffffffff + +def initialize(): + log.info('\tloading rendezvous handler: file transfer') + import oscar.rendezvous.peer as peer + peer.register_rdv_factory('file_xfer', IncomingOscarFileTransfer) + +hooks.Hook('oscar.rdv.load').register(initialize) diff --git a/digsby/src/oscar/rendezvous/icqrelay.py b/digsby/src/oscar/rendezvous/icqrelay.py new file mode 100644 index 0000000..fa4c1f4 --- /dev/null +++ b/digsby/src/oscar/rendezvous/icqrelay.py @@ -0,0 +1,135 @@ +import logging + +import struct +import hooks + +import oscar +import oscar.rendezvous as RDV + +log = logging.getLogger('oscar.rdv.icqrelay') + +class IcqServerRelayer(RDV.peer.OscarPeer): + def __init__(self, protocol, screenname, cookie): + RDV.peer.OscarPeer.__init__(self, protocol, screenname, cookie, oscar.capabilities.by_name['extended_msgs']) + + def handle_request(self, reqtlvs): + log.info("request: %r", reqtlvs) + + data = reqtlvs.extended_data + + fmt = ( ('length1', 'H'), + ('chunk1', 's', 'length1'), + ('length2', 'H'), + ('chunk2', 's', 'length2') + ) + + # chunk1 and 2 don't seem to have any useful information. + # XXX: Not sure if the number of chunks is always the same or not + _, chunk1, _, chunk2, data = oscar.unpack(fmt, data, byte_order='<') + + fmt = ( ('type', 'B'), + ('flags', 'B'), + ('status', 'H'), + ('priority', 'H'), + ('length', 'H'), + ('message', 's', 'length'), + ) + + type,flags,status,priority,_,message,data = oscar.unpack(fmt, data, byte_order='<') + + if message: + assert message[-1] == '\0' + message = message[:-1] + + # this is wrong...seems to always be true + auto = (flags & 0x2) == 0x2 + + if message: + self.protocol.incoming_rtf_message(self.buddy, message, )#auto) + return + + fmt = (('length', 'H'), + ('unknown', 's', 18), + ('req_length', 'I'), + ('request', 's', 'req_length'), + ('phase', 'B'), + ('unknown2', 's', 16), + ('length2', 'I'), + ('response_length', 'I'), + ('response', 's', 'response_length'), + ('enc_length', 'I'), + ('encoding', 's', 'enc_length'), + ) + + try: + _, _, _, request, phase, _, _, _, response, _, content_type, data = oscar.unpack(fmt, data, byte_order = '<') + log.info('request = %r, phase = %r, response = %r, encoding = %r, data = %r', request, phase, response, content_type, data) + except Exception: + import traceback; traceback.print_exc() + + log.info('parsed request! %r', locals()) + + # All observed headers for this data have been identical, but instead of having a magic byte string, + # here it is broken down (as wireshark does): + ex_data_header = struct.pack(' 0: + timeoutfunc = partial(self._tryagain, timetowait) + else: + # This is the last one. + timeoutfunc = self.on_fail + + self.timeout = Timer(timetowait, timeoutfunc) + info('%r attempting conn: %s:%d', self, *addr) + + self.make_socket() + self.connect(addr, error=timeoutfunc) + + info('timeout is %r seconds...', timetowait) + if self.timeout is not None: + self.timeout.start() + + def handle_expt(self): + info('handle_expt in %r', self) + self.handle_disconnect() + + def handle_error(self, e=None): + info('handle_error in %r', self) + + import traceback + traceback.print_exc() + + if not self._connectedonce: + self.handle_disconnect() + else: + self.close() + + def handle_disconnect(self): + self.cancel_timeout() + self.close() + + if len(self.ips) > 0: + info('got an error, trying next ip immediately: ' + \ + repr(self.ips[0])) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self._tryagain(self.timetowait) + elif not self._connectedonce: + info('no more ips to attempt, calling on_fail (%r)', self.on_fail) + self.on_fail() + + def handle_connect(self): + info('connected!') + self.cancel_timeout() + #self.handle_disconnect = lambda: None + self._connectedonce = True + self.on_connect() + self.on_fail = Sentinel + + def handle_accept(self): + self.cancel_timeout() + conn, address = self.accept() + info('%r connection accepted (%r), canceling timeout and calling %r', self, address, self.on_connect) + self._connectedonce = True + self.on_connect(conn) + + def cancel_timeout(self): + # Cancel any timeout. + if hasattr(self, 'timeout') and self.timeout is not None: + self.timeout.cancel() + self.timeout = None + + def iptuples(self, ips): + if not hasattr(ips, '__len__'): + raise TypeError('ips must be (host, port) or [(host,port), (host,port)]') + if not hasattr(ips[0], '__len__'): + ips = tuple([ips]) + + # ips is now a sequence of (host, port) tuples + assert all(isinstance(a, basestring) and isinstance(p, int) for a, p in ips) + return ips + + def __repr__(self): + try: pn = self.getpeername() + except Exception: pn = None + return '' % (pn, getattr(self, 'ips', None), id(self)) + + +class ReactSocket(OscarTimeoutSocket): + 'Wrapper for asynchat with packables.' + + def __init__(self, connected_socket = None, on_close = lambda: None): + if connected_socket is None: OscarTimeoutSocket.__init__( self ) + else: OscarTimeoutSocket.__init__( self, connected_socket ) + + self._connectedonce = True + self.data = '' + self.original_collector = self.collect_incoming_data + self.collectors = [] + self.on_close = on_close + + def handle_close(self): + log.info('%r handle_close', self) + self.on_close() + self.close() + + def collect_incoming_data(self, data): + self.data += data + + def push_collector(self, collector): + self.collectors.append(collector) + self.collect_incoming_data = self.collectors[-1] + + def pop_collector(self): + self.collectors.pop(-1) + self.collect_incoming_data = \ + self.collectors[-1] if self.collectors else self.original_collector + + def receive_next(self, size, callable_func): + # Pulls sizes out of packables + if hasattr(size, '_struct'): size = size._struct.size + assert isinstance(size, (int, long)) + assert callable(callable_func) + + self.found_terminator = lambda: callable_func(self.data) + self.data = '' + self.set_terminator( size ) + +class ReactSocketOne(TimeoutSocketOne, SocketEventMixin): + def __init__(self, *a, **k): + common.TimeoutSocketOne.__init__(self, *a, **k) + SocketEventMixin.__init__(self) + self.data = '' + self.original_collector = self.collect_incoming_data + self.collectors = [] + + def collect_incoming_data(self, data): + self.data += data + + def push_collector(self, collector): + self.collectors.append(collector) + self.collect_incoming_data = self.collectors[-1] + + def pop_collector(self): + self.collectors.pop(-1) + self.collect_incoming_data = \ + self.collectors[-1] if self.collectors else self.original_collector + + def receive_next(self, size, callable_func): + # Pulls sizes out of packables + if hasattr(size, '_struct'): size = size._struct.size + assert isinstance(size, (int, long)) + assert callable(callable_func) + + self.found_terminator = lambda: callable_func(self.data) + self.data = '' + self.set_terminator( size ) + + #in case we don't inherit this + def __getattr__(self, attr): + return getattr(self.socket, attr) + diff --git a/digsby/src/oscar/rendezvous/rendezvous.py b/digsby/src/oscar/rendezvous/rendezvous.py new file mode 100644 index 0000000..cff09ca --- /dev/null +++ b/digsby/src/oscar/rendezvous/rendezvous.py @@ -0,0 +1,65 @@ +import oscar +from util import lookup_table +import time, struct + +rendezvous_tlvs = lookup_table({ + 0x000a: 'request_num', + 0x000f: 'mystery', + 0x000e: 'locale', + 0x000d: 'encoding', + 0x000c: 'user_message', + 0x0002: 'proxy_ip', + 0x0016: 'proxy_ip_check', + 0x0003: 'client_ip', + 0x0005: 'external_port', + 0x0017: 'port_check', + 0x0010: 'proxy_flag', + 0x2711: 'extended_data', + 0x2712: 'filename_encoding', + 0x0004: 'verified_ip', +}) + +# Rendezvous file transfer messages are either REQUEST, CANCEL, or ACCEPT. +rdv_types = rendezvous_message_types = lookup_table(dict( + request = 0, + cancel = 1, + accept = 2, +)) + + +def oscarcookie(): + ''' + Generate an oscar peer session cookie, which is handed back and forth + in rendezvous packets during a direct IM session or filetransfer. + ''' + + return int(time.time()**2) + + +def rendezvous_header(message_type, cookie, capability, data = ''): + 'Constructs a rendezvous TLV block header.' + + type = map_intarg(message_type, rendezvous_message_types) + return struct.pack('!HQ16s', type, cookie, capability) + data + + +def rdv_snac(screenname, capability, type, cookie = None, data = ''): + + if cookie is None: + cookie = int(time.time()**2) + + header = rendezvous_header(type, cookie, capability) + rendezvous_data = oscar.OscarUtil.tlv(0x05, header + data) + return oscar.snac.x04_x06(screenname, cookie, 2, rendezvous_data) + + +def map_intarg(arg, map): + 'Dictionary lookup with extra type checking.' + + if isinstance(arg, basestring): + if not arg in map: + raise ValueError('%s is not a valid argument' % arg) + return map[arg] + elif not isinstance(arg, int): + raise ValueError('integer or string expected') + return arg diff --git a/digsby/src/oscar/snac/__init__.py b/digsby/src/oscar/snac/__init__.py new file mode 100644 index 0000000..0e8bfc6 --- /dev/null +++ b/digsby/src/oscar/snac/__init__.py @@ -0,0 +1,92 @@ +from oscar import OscarException + +# +# why not this? +# +#[__import__('oscar.snac.family_%s' % fam) +# for fam in ['%02x' % x for x in range(1,17)] + \ +# ['13','15','17','22','85']] +# +# '__import__(...)' doesn't do everything 'import ...' does, there's a bit of +# extra work to be done. not a bad idea though. + + +from family_x01 import * +from family_x02 import * +from family_x03 import * +from family_x04 import * +from family_x05 import * +from family_x06 import * +from family_x07 import * +from family_x08 import * +from family_x09 import * +from family_x0a import * +from family_x0b import * +from family_x0c import * +from family_x0d import * +from family_x0e import * +from family_x0f import * +from family_x10 import * +from family_x13 import * +from family_x15 import * +from family_x17 import * +from family_x22 import * +from family_x25 import * +from family_x85 import * + +errcodes = { + 0x01 :"Invalid SNAC header.", + 0x02 :"Server rate limit exceeded", + 0x03 :"Client rate limit exceeded", + 0x04 :"Recipient is not logged in", + 0x05 :"Requested service unavailable", + 0x06 :"Requested service not defined", + 0x07 :"You sent obsolete SNAC", + 0x08 :"Not supported by server", + 0x09 :"Not supported by client", + 0x0A :"Refused by client", + 0x0B :"Reply too big", + 0x0C :"Responses lost", #request denied + 0x0D :"Request denied", + 0x0E :"Incorrect SNAC format", + 0x0F :"Insufficient rights", + 0x10 :"In local permit/deny (recipient blocked)", + 0x11 :"Sender too evil", + 0x12 :"Receiver too evil", + 0x13 :"User temporarily unavailable", + 0x14 :"No match", #item not found + 0x15 :"List overflow", #too many items specified in a list + 0x16 :"Request ambiguous", + 0x17 :"Server queue full", + 0x18 :"Not while on AOL", + 0x1A :"Timeout", + 0x1C :"General Failure", + 0x1F :"Restricted by parental controls", + 0x20 :"Remote user is restricted by parental controls", + } + +class SnacError(OscarException): + def __init__(self, fam, *args): + name = getattr(oscar.snac, 'x%02x_name' % fam, None) + OscarException.__init__(self, (fam, name), *args) + +import struct +def error(data): + errcode, data = oscar.unpack((('err', 'H'),), data) + from util.primitives.funcs import get + errmsg = get(errcodes,errcode,'Unknown') + + tlv = None + if data: + tlv, data = oscar.unpack((('tlv', 'tlv'),), data) + # ICQ (family x15) has an additional tlv + if data: + tlv2, data = oscar.unpack(('tlv', 'tlv'),data) + assert not data + if tlv and tlv.t == 8: + (subcode,) = struct.unpack('!H', tlv.v) + else: subcode = None + + return errcode, errmsg, subcode + +version, dll_high, dll_low = 1, 0x0110, 0x164f diff --git a/digsby/src/oscar/snac/family_x01.py b/digsby/src/oscar/snac/family_x01.py new file mode 100644 index 0000000..1568977 --- /dev/null +++ b/digsby/src/oscar/snac/family_x01.py @@ -0,0 +1,501 @@ +from __future__ import with_statement + +import struct +import logging +import util +import oscar + +x01_name="Generic" +log = logging.getLogger('oscar.snac.x01') + +version = 4 + +tlv_types = {0x05: 'server_addr', + 0x06: 'cookie', + 0x0d: 'service_id',} + +subcodes = {} + +ignorable = [ + (1,8), # not supported by user + ] + + + +@util.gen_sequence +def x01_init(o, sock, cb): + log.info('initializing') + me = (yield None) + sock.send_snac(*oscar.snac.x01_x06(), req=True, cb=me.send) + rate_classes, rate_groups = o.gen_incoming((yield None)) + sock.send_snac(*oscar.snac.x01_x08(rate_classes)) + status = (o.status != 'online') * 0x100 + + sock.send_snac(*oscar.snac.x01_x1e(o)) + cb() + log.info('finished initializing') + +def x01_x01(o, sock, data): + ''' + SNAC (x1, x1): Generic Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_01.html} + ''' + + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + + e = oscar.snac.SnacError(0x01, (errcode, errmsg), (subcode, submsg)) + log.warning('SNACError: %r', e) + + if (1, errcode) not in ignorable: + raise e + +def x01_x02(families): + ''' + SNAC (x1, x2): Client ready + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_02.html} + ''' + to_send = '' + for family in families: + fam = getattr(oscar.snac, 'family_x%02x' % family, None) + + if fam is None: + continue + + version = getattr(fam, 'version', oscar.snac.version) + dll_high = getattr(fam, 'dll_high', oscar.snac.dll_high) + dll_low = getattr(fam, 'dll_low', oscar.snac.dll_low) + + to_send += struct.pack('!HHHH', family, version, dll_high, dll_low) + + log.debug('sending client ready') + return 0x01, 0x02, to_send + +def x01_x03(o, sock, data): + ''' + SNAC (x1, x3): Server ready + + This is a list of families the server supports. Generally, + The BOS server supports about 10-12 families, and other + servers support family x01 and one other (applicable to the + kind of server). + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_03.html} + ''' + snac_format = (('families', 'list', 'H'),) + families, data = oscar.unpack(snac_format, data) + assert not data + return families + +def x01_x04(service_id): + ''' + SNAC (x1, x4): New service request + + Used to request a new service from BOS. In most cases this is just + sending the family number to the server, but for chat an additional + TLV structure must be sent. + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_04.html} + ''' + if isinstance(service_id, basestring): + assert service_id != 'bos' + family = 0x0e + slen = len(service_id) + exch = int(service_id.split('-')[1]) + additional = oscar.util.tlv(1, struct.pack + ('!HB%dsH' % slen, + exch, slen, service_id, 0)) + elif isinstance(service_id, int): + family = service_id + additional = '' + else: + assert False + return 0x01, 0x04, struct.pack('!H', family) + additional + +def x01_x05(o, sock, data): + ''' + SNAC (x1, x5): Service redirect + + The server is telling is to go to another server. This happens + initially at signon and any time a service is successfully requested + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_05.html} + ''' + format = (('tlvs', 'named_tlvs', -1, tlv_types),) + tlvs, data = oscar.unpack(format, data) + id, addr, cookie = tlvs.service_id, tlvs.server_addr, tlvs.cookie + (id,) = struct.unpack('!H', id) + + assert all([id, addr, cookie]) + return id, addr, cookie + +def x01_x06(): + ''' + SNAC (x1, x6): Rate limit request + + This is just a request, so there is no data. + The server should respond with x01, x07 + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_06.html} + ''' + + return 0x01, 0x06 + +def x01_x07(o, sock, data): + ''' + SNAC (x1, x7): Rate limit information + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_07.html} + ''' + log.info('got rate info') + + ch_ids = [k for k,v in o.sockets.items() + if v is sock and isinstance(k, basestring)] + + snac_format = (('num_rates', 'H'), + ('rate_classes', 'rate_class_list', 'num_rates'), + ('rate_groups', 'rate_group_list', 'num_rates')) + num_rates, \ + rate_classes, \ + rate_groups, \ + data = oscar.unpack(snac_format, data) + log.info('Received %d rate classes, %d rate groups', + len(rate_classes), len(rate_groups)) + + assert not data + sock.apply_rates(rate_classes, rate_groups) + return rate_classes, rate_groups + +def x01_x08(rate_classes): + ''' + SNAC (x1, x8): Rate info acknowledgement + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_08.html} + ''' + log.info('sending rate acknowledgement') + ids = [rate.id for rate in rate_classes] + to_send = ''.join(struct.pack('!H', id) for id in ids) + return 0x01, 0x08, to_send + +def x01_x09(o, sock, data): + ''' + SNAC (x1, x9): 0x1, 0x9: Server deleted a rate group and you didn't handle it + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_09.html} + ''' + rate_classes = oscar.unpack((('rate_classes', 'list', 'H'),), data) + with sock.rate_lock: + for (id, index) in ((rc.id, sock.rate_classes.index(rc)) for rc in sock.rate_classes): + if id in rate_classes: del sock.rate_classes[index] + +def x01_x0a(o, sock, data): + ''' + SNAC (x1, xa): Rate info change + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_0a.html} + ''' + rate_codes = {0x0001: 'Rate limits parameters changed', + 0x0002: 'Rate limits warning (current level < alert level)', + 0x0003: 'Rate limit hit (current level < limit level)', + 0x0004: 'Rate limit clear (current level become > clear level)',} + + msg, data = oscar.unpack((('msg','H'),), data) + log.warning(rate_codes.get(msg, 'Unknown rate message %r' % msg)) + + rates, data = oscar.unpack((('rate_info', 'rate_class_list', -1),), data) + sock.apply_rates(rates, {}) + + + assert not data, repr(data) + + +def x01_x0b(o, sock, data): + ''' + SNAC (x1, xb): Server pause + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_0b.html} + ''' + if data: + format = (('families', 'list', 'H'),) + families, data = oscar.unpack(format, data) + assert not data + else: + families = [] + + o.pause_service(sock, families) + + +def x01_x0c(families): + ''' + SNAC (x1, xc): Client pause ack + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_0c.html} + ''' + return 0x01, 0x0c, ''.join(struct.pack('!H', fam) for fam in families) + +def x01_x0d(o, sock, data): + ''' + SNAC (x1, xd): Server resume + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_0d.html} + ''' + if data: + format = (('families', 'list', 'H'),) + families, data = oscar.unpack(format, data) + assert not data + else: + families = [] + + o.unpause_service(sock, families) + +def x01_x0e(): + ''' + SNAC (x1, xe): Request self info + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_0e.html} + ''' + return 0x01, 0x0e + +def x01_x0f(o, sock, data): + ''' + SNAC (x1, xf): Self info reply + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_0f.html} + ''' + selfinfo, data = oscar.unpack((('info', 'userinfo'),), data) + + if 'user_status_icq' in selfinfo: + selfinfo.status, selfinfo.webaware = oscar.util.process_status_bits(selfinfo.user_status_icq) + log.info('self buddy status: %r', selfinfo.status) + + try: + o.self_buddy.update(selfinfo) + except Exception: + import traceback;traceback.print_exc() + +def x01_x10(o, sock, data): + ''' + SNAC (x1, x10): 0x1, 0x10: You've been eviled! + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_10.html} + ''' + warn_level, data = oscar.unpack((('warn', 'H'),), data) + + user_infos = [] + while data: + uinfo, data = oscar.unpack((('info', 'userinfo'),), data) + user_infos.append(uinfo) + + return warn_level, user_infos + +def x01_x11(time): + ''' + SNAC (x1, x11): Set idle time + + time is how long you've been idle, so 60 means you've been + idle for a minute + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_11.html} + ''' + return 0x01, 0x11, struct.pack('!I', time) + +def x01_x12(o, sock, data): + ''' + SNAC (x1, x12): Server migration notice and information + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_12.html} + ''' + format = (('num_fams', 'H'), + ('families', 'H', 'num_fams'), + ('tlvs', 'tlv_dict')) + + __, families, tlvs, data = oscar.unpack(format, data) + server_addr, cookie = tlvs[5], tlvs[6] + + assert not any(o.sockets[fam] + for fam in families + if fam in o.sockets and + isinstance(o.sockets[fam], oscar.SnacQueue)) + + sock_ids = set(list(families) + [s_id for (s_id, s) in o.sockets.items() if s is sock]) + + server = util.srv_str_to_tuple(server_addr, o.server[-1]) + bos, sock.bos = sock.bos, False # If our old socket was the BOS socket, it isn't anymore. + + if bos: + sock_ids.add('bos') + + o.connect_socket(server, cookie, sock_ids, bos = bos) + +def x01_x13(o, sock, data): + ''' + SNAC (x1, x13): Got message of the day + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_13.html} + ''' + # this is ignored in oscar socket, so we should never get here! + assert (0x01, 0x13) in sock.ignored_snacs + +def x01_x14(idle_time=True, member_time=True): + ''' + SNAC (x1, x14): Set privacy flags + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_14.html} + ''' + idle = idle_time*1 + member = member_time*2 + + return 0x01, 0x14, struct.pack('!I', idle | member) + +def x01_x15(o, sock, data): + ''' + SNAC (x1, x15): Well-known URLs + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_15.html} + ''' + return NotImplemented + +def x01_x16(o, sock, data): + ''' + SNAC (x1, x16): NOP (keep-alive packet) + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_16.html} + ''' + return 0x01, 0x16 + +def x01_x17(o, families, sock): + ''' + SNAC (x1, x17): Tell server what families/versions we support + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_17.html} + ''' + to_send = [] + for family in families: + try: + fam = getattr(oscar.snac, 'family_x%02x' % family) + except AttributeError: + log.info('Unknown/unsupported family: %d (0x%02x)', family, family) + continue + + if family in o.sockets and isinstance(o.sockets[family], oscar.SnacQueue): + o.sockets[family].sock = sock + else: + o.sockets[family] = sock + version = getattr(fam, 'version', oscar.snac.version) + to_send.append(struct.pack('!HH', family, version)) + + return 0x01, 0x17, ''.join(to_send) + +def x01_x18(o, sock, data): + ''' + SNAC (x1, x18): Server service versions response + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_18.html} + ''' + snac_format = (('fams', 'list', 'H'),) + fams_vers, data = oscar.unpack(snac_format, data) + assert not data + family_versions = dict(zip(fams_vers[::2], fams_vers[1::2])) + + #assert all(fam in o.sockets for fam in family_versions) + + sock.server_snac_versions = family_versions + + return True + +def x01_x1e(o): + ''' + SNAC (x1, x1e): Set extended status/location info (also for direct connect info) + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_1e.html} + ''' + + ''' + From http://www.micq.org/ICQ-OSCAR-Protocol-v7-v8-v9/Packets/Fam1/Com30.html + ''' + statuses = { + 'available' : 0x00000000, + 'online' : 0x00000000, #The user is online. / Set status to online. + 'offline' : 0xffffffff, #The user is offline. / Set status to offline. + 'invisible' : 0x00000100, #Set status to invisible. + 'do not disturb' : 0x00000013, #Set status to do not disturb. + 'busy' : 0x00000011, #Set status to occupied. + 'not available' : 0x00000005, #Set status to not available. + 'away' : 0x00000001, #Set status to away. + 'free for chat' : 0x00000020, #Set status to free for chat. + } + + b = o.self_buddy + if b.invisible: + status = statuses['invisible'] + else: + status = statuses.get(o.status.lower(), statuses['away']) + + webaware = 0x00010000 * o.webaware + + status |= 0x10000000 + status |= webaware + + if b.avail_msg is not None: + message = b.avail_msg + if isinstance(message, unicode): + message = message.encode('utf8') + + extraflag = 0 + if o.icq: + message += oscar.util.tlv(0x01, 'utf-8') + message += oscar.util.tlv(0x0e, '') + extraflag = 1 + + mlen = len(message) + mbytes = struct.pack('!H%dsH' % mlen, mlen, message, extraflag) + else: + mbytes = '' + + tlvs = ((0x1d, oscar.util.tflv(2,4,mbytes)), + (0x06, 4, status)) + + return 0x01, 0x1e, oscar.util.tlv_list(*tlvs) + +def x01_x1f(o, sock, data): + ''' + SNAC (x1, x1f): Evil AIM prove yourself packet + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_1f.html} + ''' + raise oscar.LoginError('Evil AIM prove yourself packet') + +def x01_x20(o, sock, data): + ''' + SNAC (x1, x20): Response to prove yourself + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_20.html} + ''' + raise NotImplementedError + +def x01_x21(o, sock, data): + ''' + SNAC (x1, x21): Set Extended status info (buddy icon and available message) + + reference: U{http://iserverd.khstu.ru/oscar/snac_01_21.html} + ''' + tlvs, data = oscar.unpack((('tlvs', 'tlv_list'),), data) + assert not data + + if 1 in tlvs: + fmt = (('flags', 'B'), + ('hash_len', 'B'), + ('hash', 'B', 'hash_len'),) + icon_flags, __, hash, tlvs[1] = oscar.unpack(fmt, tlvs[1]) + assert not tlvs[1] + o.self_buddy.icon_hash = hash + + if 2 in tlvs: + fmt = (('len', 'H'), + ('msg', 's', 'len'),) + + __, msg, tlvs[2] = oscar.unpack(fmt, tlvs[2]) + assert not tlvs[2] + o.self_buddy._avail_msg = msg diff --git a/digsby/src/oscar/snac/family_x02.py b/digsby/src/oscar/snac/family_x02.py new file mode 100644 index 0000000..2bfc6a7 --- /dev/null +++ b/digsby/src/oscar/snac/family_x02.py @@ -0,0 +1,258 @@ +import struct +import logging + +import util +import oscar + +x02_name="Location" +log = logging.getLogger('oscar.snac.x02') +subcodes = {} + +# ignorable error messages +ignorable = [ + (2,4), # Recipient is not logged in + ] + +@util.gen_sequence +def x02_init(o, sock, cb): + #### + #>>>SNAC(02,02) Client ask server location service limitations + #<<>>SNAC(02,04) Client sends its capabilities / profile to server + ### + log.info('initializing') + me = (yield None); assert me + sock.send_snac(*oscar.snac.x02_x02(), req=True, cb=me.send) + tlvs = o.gen_incoming((yield None)) + + o.MAX_PROF_HTML = struct.unpack('!H', tlvs[1])[0] + max_caps = tlvs[2] + + caps = oscar.capabilities.feature_capabilities + + my_caps = [] + for i in range(min(len(caps), max_caps)): + my_caps.append(caps[i]) + + o.self_buddy._capabilities = my_caps + + sock.send_snac(*oscar.snac.x02_x04(o)) + cb() + log.info('finished initializing') + + +def x02_x01(o, sock, data): + ''' + SNAC (x2, x1): Location Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + if (2, errcode) not in ignorable: + raise oscar.snac.SnacError(0x02, (errcode, errmsg), (subcode, submsg)) + +def x02_x02(): + ''' + SNAC (x2, x2): Request limitation params for Location group + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_02.html} + ''' + return 0x02, 0x02 + +def x02_x03(o, sock, data): + ''' + SNAC (x2, x3): Location limitations response + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_03.html} + ''' + format = (('tlvs', 'tlv_dict'),) + tlvs, data = oscar.util.apply_format(format, data) + assert not data + + return tlvs + +def x02_x04(o): + ''' + SNAC (x2, x4): Set user info + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_04.html} + ''' + b = o.self_buddy + encoding = 'text/x-aolrtf; charset="utf-8"' + profile = b.profile + if profile is None: + profile = '' + if isinstance(profile, unicode): + profile = profile.encode('utf8') + + away_msg = b.away_msg + if away_msg is None: + away_msg = '' + if isinstance(away_msg, unicode): + away_msg = away_msg.encode('utf-8') + + capabilities = b.capabilities + + tlvs = ((1, encoding), + (2, profile), + (3, encoding), + (4, away_msg), + (5, ''.join(capabilities))) + + return 0x2, 0x4, oscar.util.tlv_list(*tlvs) + +def x02_x05(sn, infotype): + ''' + SNAC (x2, x5): Request user info + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_05.html} + ''' + assert infotype in range(1,5) + + snlen = len(sn) + return 0x02, 0x05, struct.pack('!HB%ds' % snlen, infotype, snlen, sn) + +def x02_x06(o, sock, data): + ''' + SNAC (x2, x6): Requested User information + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_06.html} + ''' + + tlv_types = { 1:'prof_enc', + 2:'profile', + 3:'away_enc', + 4:'away_msg', + 5:'capabilities', } + fmt = (('_1', 'userinfo'), + ('_2', 'named_tlvs', -1, tlv_types)) + + userinfo, tlvs, data = oscar.unpack(fmt, data) + + # Decode profile and away message. + if 'profile' in tlvs: + tlvs['profile'] = oscar.decode(tlvs['profile'], tlvs['prof_enc']) + + if 'away_msg' in tlvs and tlvs['away_msg']: + tlvs['away_msg'] = oscar.decode(tlvs['away_msg'], tlvs['away_enc']) + + userinfo.update(tlvs) + buddy = o.buddies[userinfo.name] + log.debug('Got userinfo: %r', userinfo) + buddy.update(userinfo) + #buddy._set_status(buddy.status) + +def x02_x07(): + ''' + SNAC (x2, x7): Watcher sub request (??) + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_07.html} + ''' + return 0x02, 0x07, struct.pack('!H',0) + +def x02_x08(o, sock, data): + ''' + SNAC (x2, x8): Watcher notification + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_08.html} + ''' + format = (('names', 'list', 'pstring'),) + names, data = oscar.apply_format(format, data) + assert not data + +def x02_x09(**kwargs): + ''' + SNAC (x2, x9): Request to Update directory info + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_09.html} + ''' + tlv_names = { + 'first_name': 0x01, + 'last_name': 0x02, + 'middle_name': 0x03, + 'maiden_name': 0x04, + 'country': 0x06, + 'state': 0x07, + 'city': 0x08, + 'unknown': 0x0a, + 'nickname': 0x0c, + 'zipcode': 0x0d, + 'street_addr': 0x21, + } + + return 0x02, 0x09, ''.join(oscar.util.tlv(tlv_names[k], len(v), v) + for (k,v) in kwargs.items() if k in tlv_names) + +def x02_x0a(o, sock, data): + ''' + SNAC (x2, xa): Reply to update directory info + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_0a.html} + ''' + teh_win, data = oscar.unpack((('result','H'),), data) + assert not data + + if not teh_win: + raise oscar.SnacError(0x02, (None, 'Directory info update failed'), (None, None)) + +def x02_x0b(sn): + ''' + SNAC (x2, xb): Unknown info request + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_0b.html} + ''' + return 0x02, 0x0b, struct.pack('!B%ds'%len(sn), len(sn), sn) + +def x02_x0c(o, sock, data): + ''' + SNAC (x2, xc): Unknown info response (may contain tlv) + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_0c.html} + ''' + raise NotImplementedError + +def x02_x0f(interests): + ''' + SNAC (x2, xf): Update user directory interests + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_0f.html} + ''' + to_send = oscar.util.tlv(0x0a, 4, 0) + if len(interests) > 5: + interests = interests[:5] + + for interest in interests: + to_send += oscar.util.tlv(0x0b, interest) + + return 0x02, 0x0f, to_send + +def x02_x10(o, sock, data): + ''' + SNAC (x2, x10): User directory interest reply + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_10.html} + ''' + teh_win, data = oscar.unpack((('result','H'),), data) + assert not data + + if not teh_win: + raise oscar.SnacError(0x02, (None, 'Interest info update failed'), (None, None)) + +def x02_x15(sn, profile = True, away = True, caps = True, cert = True): + ''' + SNAC (x2, x15): User info query + + reference: U{http://iserverd.khstu.ru/oscar/snac_02_15.html} + ''' + assert any([profile, away, caps, cert]) + + prof = (1 << 0) * profile + away = (1 << 1) * away + caps = (1 << 2) * caps + cert = (1 << 3) * cert + + request_bits = prof | away | caps | cert + + snlen = len(sn) + return 0x02, 0x15, struct.pack('!IB%ds' % snlen, request_bits, snlen, sn) diff --git a/digsby/src/oscar/snac/family_x03.py b/digsby/src/oscar/snac/family_x03.py new file mode 100644 index 0000000..5ffffe3 --- /dev/null +++ b/digsby/src/oscar/snac/family_x03.py @@ -0,0 +1,202 @@ +''' +Family 3 + +buddy list management +''' + +import struct, logging, oscar +from util.primitives.funcs import do +from datetime import datetime +unpack_int = lambda v: struct.unpack('!I', v)[0] + +subcodes = {} +log = logging.getLogger('oscar.snac.x03') + +x03_name="Buddy List management" + +def x03_init(o, sock, cb): + ### + #>>>SNAC(03,02) Client ask server BLM service limitations + #<< %s', info.name, old, info.status) + + # + # profile and away message timestamps + # + up_profile = up_away = False + + if 'profile_updated' in info: + tstamp = unpack_int(info.profile_updated) + up_profile = tstamp > b.profile_updated + if 'away_updated' in info: + tstamp = unpack_int(info.away_updated) + up_away = tstamp > b.away_updated + if 'mystery_updated' in info: + tstamp = unpack_int(info.mystery_updated) + up_profile = up_profile or tstamp > b.mystery_updated + + up_both = up_away or up_profile + b.request_info(profile = up_both, away = up_both) + + o.buddies.update_buddies(infos) + + +def x03_x0c(o, sock, data): + ''' + SNAC (x3, xc): Offgoing buddy + + reference: U{http://iserverd.khstu.ru/oscar/snac_03_0c.html} + ''' + infos, data = oscar.unpack((('_', 'list', 'userinfo'),), data) + assert not data + for info in infos: + info.status = 'offline' + o.buddies.update_buddies(infos) + +def sn_list(names): + return ''.join(struct.pack('!B%ds' % len(sn), len(sn), sn) for sn in names) diff --git a/digsby/src/oscar/snac/family_x04.py b/digsby/src/oscar/snac/family_x04.py new file mode 100644 index 0000000..117042b --- /dev/null +++ b/digsby/src/oscar/snac/family_x04.py @@ -0,0 +1,590 @@ +''' +Family 4 + +Messaging +''' + +import struct, time, logging + +import hooks +import oscar +import util.auxencodings +import oscar.capabilities as capabilities +import oscar.rendezvous as rendezvous +log = logging.getLogger('oscar.snac.x04'); info = log.info; warn = log.warning + +from common import pref + +x04_name="ICBM" + +tlv_types = {0x01: 'class', + 0x02: 'message_data', + 0x03: 'account_created', + 0x04: 'auto', + 0x06: 'offline', + 0x0b: 'typing', + 0x0f: 'idletime', + 0x16: 'timestamp', + } + +subcodes = { + 0x04 : 'you are trying to send message to offline client', + 0x09 : 'message not supported by client', + 0x0E : 'your message is invalid (incorrectly formatted)', + 0x10 : 'receiver/sender blocked', + } + +@util.gen_sequence +def x04_init(o, sock, cb): + log.info('initializing') + me = (yield None); assert me + sock.send_snac(*oscar.snac.x04_x04(), req=True, cb=me.send) + channel_params = o.gen_incoming((yield None)) + sock.send_snac(*oscar.snac.x04_x02(channel_params)) + cb() + log.info('finished initializing') + +def x04_x01(o, sock, data): + ''' + SNAC (x4, x1): ICBM Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x04, (errcode, errmsg), (subcode, submsg)) + +def x04_x02(channel_params): + ''' + SNAC (x4, x2): Set ICBM params + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_02.html} + + ''' + all_channels = 0 + + # features + allow = 1 + missed_calls = 2 + no_typing_notifications = 4 + unknown = 8 # i don't know what this is, but aim does it... + + # set values for all channels + channel_params[0] = all_channels + + # enable all features + + # This value is what AIM 6 sends + channel_params[1] = 0x3db + + # increase message size limit. This is what AIM 5.9 sets, + # and it allows the wall street journal bot to work. + channel_params[2] = 8000 + + # This is what we used to send. + #channel_params[1] = allow | missed_calls | unknown + + log.info('message snac size limit: %d', channel_params[2]) + + channel_params[-1] = 0 + + return 0x04, 0x02, struct.pack('!HIHHHHH', *channel_params) + +def x04_x03(o, sock, data): + ''' + SNAC (x4, x3): Reset ICBM params + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_03.html} + ''' + return 0x04, 0x03 + +def x04_x04(): + ''' + SNAC (x4, x4): Request ICBM params + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_04.html} + ''' + return 0x04, 0x04 + +def x04_x05(o, sock, data): + ''' + SNAC (x4, x5): Messaging limits + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_05.html} + ''' + + format = (('channel', 'H'), + ('flags', 'I'), + ('max_snac_size', 'H'), + ('max_snd_warn', 'H'), + ('max_rcv_warn', 'H'), + ('min_msg_interval', 'H'), + ('unknown', 'H')) + + info = oscar.unpack(format, data) + + assert not info[-1] # leftover data + return info[:-1] # real info + +def x04_x06(sn, cookie, chan, chan_data): + ''' + SNAC (x4, x6): Outgoing message + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_06.html} + ''' + snlen = len(sn) + + return 0x04, 0x06, struct.pack('!QHB%ds'%snlen, cookie, chan, snlen, sn) + chan_data + +def x04_x07(o, sock, data): + ''' + SNAC (x4, x7): Incoming message + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_07.html} + ''' + snac_format = (('msgcookie', 'Q'), + ('channel', 'H'), + ('userinfo', 'userinfo')) + info = oscar.unpack(snac_format, data) + channel = info[1] + # There are more TLVs, but they depend on the channels + log.info('Received channel %d message', channel) + globals().get('rcv_channel%d_message' % channel, rcv_unknown_channel)(o, *info) + +def x04_x08(sn, anon=True): + ''' + SNAC (x4, x8): Evil request + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_08.html} + ''' + snlen = len(sn) + return 0x04, 0x08, struct.pack('!HB%ds' %snlen, anon, snlen, sn) + +def x04_x09(o, sock, data): + ''' + SNAC (x4, x9): Server evil ack + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_09.html} + ''' + fmt = (('incr_val', 'H'), + ('new_val', 'H')) + + incr_val, new_val, data = oscar.unpack(fmt, data) + print 'yay warn', incr_val, new_val + assert not data + +def x04_x0a(o, sock, data): + ''' + SNAC (x4, xa): Msg not delivered + + Someone tried to send a message to you but server did not deliver + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_0a.html} + ''' + fmt = (('chan', 'H'), + ('info', 'userinfo'), + ('num_missed', 'H'), + ('reason', 'H')) + + reasons = {0: 'Invalid message', + 1: 'Message too large', + 2: 'Message rate exceeded', + 3: 'Sender is too evil', + 4: 'You are too evil',} + + infolist = [] + while data: + chan, info, num_missed, reason, data = oscar.unpack(fmt, data) + log.warning('could not deliver %d ch%d messages, %r, %r' % \ + (num_missed, chan, info, reasons.get(reason, 'unknown'))) + +def x04_x0b(o, sock, data): + ''' + SNAC (x4, xb): client/server message error (or data!) + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_0b.html} + ''' + + fmt = (('cookie', 'Q'), + ('channel','H'), + ('screenname','pstring'), + ('reason', 'H')) + reasons = {1: 'Unsupported channel', + 2: 'Busted payload', + 3: 'Channel specific'} + + cookie, channel, screenname, reason, data = oscar.unpack(fmt, data) + + warn("ch%d message error for cookie %r, screenname %s:", channel, cookie, screenname) + warn('\t\t' + reasons.get(reason, '')) + warn('\t\t' + repr(data)) + + if reason == 3: + channel, data = oscar.unpack((('channel','H'),), data) + if channel == 2: + messagetype, data = oscar.unpack((('msgtype','H'),), data) + rendezvous.handlech2(o, None, screenname, cookie, messagetype, data) + + return + + log.error("data not handled: %r", data) + + +def send_x04_x0b(cookie, channel, bname, reason, ex_data): + ''' + Constructs "client auto response" message for sending. + ''' + return 0x04, 0x0b, (struct.pack('!QHB%dsH' % len(bname), cookie, channel, len(bname), bname, reason) + ex_data) + +def x04_x0c(o, sock, data): + ''' + SNAC (x4, xc): Server message ack + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_0c.html} + ''' + format = ( + ('cookie', 'Q'), + ('channel', 'H'), + ('screenname', 'pstring') + ) + + cookie, channel, screenname, data = oscar.unpack(format, data) + assert not data + info('ACK for ch%d message to %s', channel, screenname) + +def x04_x10(): + ''' + Request offline messages + ''' + log.info('Requesting offline messages...') + return 0x04, 0x10, '' + +def x04_x14(o=None, sock=None, data=None, status=None, bname=None, channel=None): + ''' + SNAC (x4, x14): MTN + + reference: U{http://iserverd.khstu.ru/oscar/snac_04_14.html} + ''' + + status_to_num = { None: 0, 'typed': 1, 'typing': 2 } + num_to_status = util.dictreverse(status_to_num) + + if all([o, sock, data]): + # Incoming typing notifications. + assert not any([status, bname, channel]) + fmt = ('cookie Q ' + 'channel H ' + 'bname pstring ' + 'code H').split() + + fmt = zip(fmt[::2], fmt[1::2]) + __,__, bname, code, data = oscar.unpack(fmt, data) + assert not data + bname = bname.lower().replace(' ','') + + if not code in num_to_status: + return log.warning("x04_x14: num_to_status doesn't have key %r", code) + status = num_to_status[code] + + if bname in o.conversations: + o.conversations[bname].set_typing_status(bname, status) + elif status == 'typing' and pref('messaging.psychic', False): + # If "psychic" mode is on, and there is no active conversation for + # the buddy, make one. + c = o.convo_for(bname) + o.hub.on_conversation(c, quiet = True) + c.tingle() + c.set_typing_status(bname, status) + + else: + # Sending typing notifications. + assert not any([o,sock,data]) and all([bname, channel]) + + if status not in status_to_num: + raise ValueError('Typing status must be one of: "typing", "typed", None') + + state = status_to_num[status] + cookie = int(time.time()**2) + to_send = struct.pack("!QHB%dsH" % len(bname), cookie, channel, + len(bname), bname, state) + return 0x04, 0x14, to_send + +def x04_x17(o, sck, data): + ''' + End of offline messages + ''' + + assert not data + log.info('All offline messages have been retrieved') + +def rcv_channel1_message(o, cookie, chan, userinfo, data): + '''Returns the message from a channel 1 message block. + + The result can be a string or unicode object.''' + msgtlvs, data = oscar.unpack((('msgtlvs','named_tlvs', -1, tlv_types),), + data) + assert not data + assert 'message_data' in msgtlvs + + is_auto_response = 'auto' in msgtlvs + is_offline = 'offline' in msgtlvs + timestamp = msgtlvs.get('timestamp', None) + + message_fragments, data = oscar.unpack((('msg', 'message_block'),), + msgtlvs.message_data) + assert not data + assert 1 in message_fragments + + required = map(ord, message_fragments.get(5, '')) + + if any(x in required for x in (2,6)) or required == [1]: + # Also observed: [5,1] from eBuddy android client; is_html should be True. + is_html = True + else: + is_html = False + + fmt = (('charset', 'H'),('subset', 'H'),) + for msg in message_fragments[1]: + charset, subset, msg = oscar.unpack(fmt, msg) + + # multipart SMS messages come in with message-NULL-mysterious_ascii_characters + # so just grab everything up to null + + # (disabled b/c of #4677 -- ICQ messages get cut off) + # msg = msg.split('\x00')[0] + + codec = {3 : 'locale', + 2 : 'utf-16-be', + 0 : 'utf-8', + 0xd : 'utf-8'}.get(charset, 'utf-8') + + log.info('incoming channel1 message:') + log.info('\tCharset=%d, subset=%d, codec=%s, is_html=%r, msg[:4]=%r', charset, subset, codec, is_html, msg[:4]) + log.info('\tRequired types for message fragments are: %r', required) + log.info_s('\tdata=%r', msg) + + msg = util.auxencodings.fuzzydecode(msg, [codec, 'utf-8']) + + o.incoming_message(userinfo, msg, is_auto_response, offline=is_offline, timestamp=timestamp, html=is_html) + +def rcv_channel2_message(o, cookie, chan, userinfo, data): + 'Handle channel two (rendezvous) messages.' + + # At this point, data should be the rendezvous TLV only. + rendtlv, data = oscar.util.s_tlv(data) + assert 0x05 == rendtlv.t + + data = rendtlv.v + c2format = (('message_type', 'H'), + ('cookie', 'Q'), + ('capability', 's', 16),) + message_type, cookie, capability, data = oscar.unpack(c2format, data) + + rendezvous_type = capabilities.by_bytes[capability] + + # Direct Connection, File Transfer, Chat Rooms + rendezvous.handlech2( o, rendezvous_type, userinfo.name, cookie, + message_type, data ) + +def rcv_extended_message(o, userinfo, cookie, msg_type, data): + log.info('Got fancy message from %s', userinfo.name) + + fmt = (('tlvs','tlv_dict'),) + tlvs, data = oscar.unpack(fmt, data) + + log.info('Fancy message TLVS: %r', tlvs) + assert not data + + if 0x2711 not in tlvs: + log.warning(' Not sure what to do with those fancy tlvs.') + return + + data = tlvs[0x2711] + + fmt = ( ('length1', 'H'), + ('chunk1', 's', 'length1'), + ('length2', 'H'), + ('chunk2', 's', 'length2') + ) + + # chunk1 and 2 don't seem to have any useful information. + # XXX: Not sure if the number of chunks is always the same or not + length1, chunk1, length2, chunk2, data = oscar.unpack(fmt, data, byte_order='<') + + # data now holds the message block + fmt = ( ('type', 'B'), + ('flags', 'B'), + ('status', 'H'), + ('priority', 'H'), + ('length', 'H'), + ('message', 's', 'length'), + ) + + type,flags,status,priority,length,message,data = oscar.unpack(fmt, data, byte_order='<') + + log.info('type=%r,flags=%r,status=%r,priority=%r,length=%r,message=%r,data=%r', + type,flags,status,priority,length,message,data) + + if message: + assert message[-1] == '\0' + message = message[:-1] + + # this is wrong...seems to always be true + auto = (flags & 0x2) == 0x2 + + if message: + o.incoming_rtf_message(userinfo, message, )#auto) + else: + # Possibly a 'TZer' ? + + log.error("message not handled, unknown type") + ''' + With type 0x1a (26) the following data was received: + + '0\x00O\xa6\xf3L\t\xb7\xfdH\x92\x08~\x85z\xe0s0\x00\x00\t\x00\x00\x00Send Tzer\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb3\x00\x00\x00\xaf\x00\x00\x00\r\n' + + on another occasion this was received: + type=26,flags=0,status=0,priority=1,length=0,message='', data: + ':\x00\x81\x1a\x18\xbc\x0el\x18G\xa5\x91o\x18\xdc\xc7o\x1a\x01\x00\x13\x00\x00\x00Away Status Message\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00text/x-aolrtf' + ''' +from oscar.misc_consts import MType + +def rcv_channel4_message(o, cookie, chan, userinfo, data): + 'Returns the message from a channel 4 message block.' + + mblock, data = oscar.util.s_tlv(data) + + mblock = mblock.v + + # watch out! little endian data. + snd_uin, msg_type, msg_flags, msg_len = struct.unpack(" + # ICQ + # 230391135 + # +19177577555 + # Test + # + # + import util.xml_tag + t = util.xml_tag.tag(msg) + sender = t.sender._cdata + userinfo.nice_name = userinfo.name = sender + msg = t.text._cdata + + o.incoming_message(userinfo, msg, offline=offline, timestamp=timestamp) + +def rcv_unknown_channel(o, *info): + log.warning('Unknown channel/message received for oscar=%r. data=%r', o, info) + +def snd_channel1_message(o, sn, message, cookie=None, req_ack=False, save=True, auto=False, html=False): + if message is None: + raise TypeError('message must be a string or unicode') + + try: + charset = 0 + msg = str(message) + +# charset = 0xd +# if isinstance(message, unicode): +# msg = message.encode('utf-8') +# else: +# msg = message + + except UnicodeEncodeError: + charset = 2 + msg = message + msg = msg.encode('utf-16-be') + + subset = 0 + message_data = struct.pack('!HH%ds' % len(msg), charset, subset, msg) + + tlv = oscar.util.tlv + if html: + fragments = [1, 1, 1, 2] # mimic aim 6 + else: + fragments = [1, 6] # mimic pidgin as icq + + mobile = o.is_mobile_buddy(sn) + if mobile: + fragments = [1] + save = False + + to_send = ''.join([ + tlv(2, required_fragment(fragments) + fragment_header(1, message_data)), + tlv(3) if req_ack or save else '', + tlv(6) if save else '', + tlv(4) if auto else '', + tlv(0x0d, o.get_self_bart_data()) if mobile else '', + tlv(0x08, '\x00\x00\n\xa6\x00\x01\xb0\xd1<1BP') if mobile else '', # mystery bytes! + ]) + + if cookie is None: + cookie = int(time.time()**2) + + return oscar.snac.x04_x06(sn, cookie, 1, to_send) + +def snd_channel2_message(sn, cookie=None, data=''): + if cookie is None: + cookie = int(time.time()**2) + + return x04_x06(sn, cookie, 2, data) + +def required_fragment(required): + return fragment_header(5, ''.join(struct.pack('!B', req) for req in required)) + +def fragment_header(id, data, ver=1): + return struct.pack('!BBH', id, ver, len(data))+data diff --git a/digsby/src/oscar/snac/family_x05.py b/digsby/src/oscar/snac/family_x05.py new file mode 100644 index 0000000..0a1a3a2 --- /dev/null +++ b/digsby/src/oscar/snac/family_x05.py @@ -0,0 +1,44 @@ +''' +Ads family. This is deprecated, so it's not implemented. + +Besides, who wants ads anyway? +''' +import logging + + +import oscar + +x05_name="ads - deprecated" +log = logging.getLogger('oscar.snac.x05') +subcodes = {} +def x05_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x05_x01(o, sock, data): + ''' + SNAC (x5, x1): ads Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_05_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x05, (errcode, errmsg), (subcode, submsg)) + +def x05_x02(o, sock, data): + ''' + SNAC (x5, x2): Request ads + + reference: U{http://iserverd.khstu.ru/oscar/snac_05_02.html} + ''' + raise NotImplementedError + +def x05_x03(o, sock, data): + ''' + SNAC (x5, x3): Ads response + + reference: U{http://iserverd.khstu.ru/oscar/snac_05_03.html} + ''' + raise NotImplementedError + diff --git a/digsby/src/oscar/snac/family_x06.py b/digsby/src/oscar/snac/family_x06.py new file mode 100644 index 0000000..4ebe75c --- /dev/null +++ b/digsby/src/oscar/snac/family_x06.py @@ -0,0 +1,37 @@ +import logging + +import oscar + +x06_name="Invite" +log = logging.getLogger('oscar.snac.x06') +subcodes = {} +def x06_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x06_x01(o, sock, data): + ''' + SNAC (x6, x1): Invite Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_06_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x06, (errcode, errmsg), (subcode, submsg)) + +def x06_x02(email, message): + ''' + SNAC (x6, x2): Invite a friend + + reference: U{http://iserverd.khstu.ru/oscar/snac_06_02.html} + ''' + return 0x06, 0x02, oscar.util.tlv(0x11, email) + oscar.util.tlv(0x15, message) + +def x06_x03(o, sock, data): + ''' + SNAC (x6, x3): Invitation ack + + reference: U{http://iserverd.khstu.ru/oscar/snac_06_03.html} + ''' + assert not data \ No newline at end of file diff --git a/digsby/src/oscar/snac/family_x07.py b/digsby/src/oscar/snac/family_x07.py new file mode 100644 index 0000000..81600f0 --- /dev/null +++ b/digsby/src/oscar/snac/family_x07.py @@ -0,0 +1,205 @@ +import logging + +import struct +import oscar + +x07_name="Admin" +log = logging.getLogger('oscar.snac.x07') +subcodes = { + 0x01: 'validate nickame', + 0x02: 'validate password', + 0x03: 'validate e-mail', + 0x04: 'service temporarily unavailable', + 0x05: 'field change temporarily unavailable', + 0x06: 'invalid nickname', + 0x07: 'invalid password', + 0x08: 'invalid e-mail', + 0x09: 'invalid registration preference', + 0x0A: 'invalid old password', + 0x0B: 'invalid nickname length', + 0x0C: 'invalid password length', + 0x0D: 'invalid e-mail length', + 0x0E: 'invalid old password length', + 0x0F: 'need old password', + 0x10: 'read only field', + 0x11: 'write only field', + 0x12: 'unsupported type', + 0x13: 'all other errors', + 0x14: 'bad snac', + 0x15: 'invalid account', + 0x16: 'deleted account', + 0x17: 'expired account', + 0x18: 'no database access', + 0x19: 'invalid database fields', + 0x1A: 'bad database status', + 0x1B: 'migration cancel', + 0x1C: 'internal error', + 0x1D: 'pending request', + 0x1E: 'not dt status', + 0x1F: 'outstanding confirm', + 0x20: 'no e-mail address', + 0x21: 'over limit', + 0x22: 'e-mail host fail', + 0x23: 'dns fail', +} + +tlv_types = { + 0x01:'screenname', + 0x11:'email', + 0x13:'reg_status', + 0x08:'errcode', + 0x04:'errurl', + + } + +def x07_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x07_x01(o, sock, data): + ''' + SNAC (x7, x1): Admin Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x07, (errcode, errmsg), (subcode, submsg)) + +def x07_x02(tlv_id): + ''' + SNAC (x7, x2): Request account info + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_02.html} + ''' + + ''' + tlv_ids: 1 is request nickname, x11 is email request, + x13 is registration status + ''' + + if isinstance(tlv_id, basestring): + tlv_id = dict(nickname = 0x1, + email = 0x11, + registration = 0x13)[tlv_id] + + return 0x07, 0x02, oscar.util.tlv(tlv_id) + +def x07_x03(o, sock, data): + ''' + SNAC (x7, x3): Requested account info + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_03.html} + ''' + fmt = ( + ('flags', 'H'), + ('num_tlvs', 'H'), + ('tlvs', 'tlv_dict', 'num_tlvs') + ) + + #TODO: this may not be correct, docs say 1 and 2 are both read + read = 1 + write = 2 + + flags, __, tlvs, data = oscar.unpack(fmt, data) + + assert not data + + can_read = flags & read + can_write = flags & read + + if 8 in tlvs: + subcode = struct.unpack('!H', tlvs[8])[0] + errmsg = tlvs[4] + + raise oscar.SnacError(0x07, (subcode, errmsg), (subcode, subcodes.get(subcode, 'unknown'))) + + return tlvs + +def x07_x04(screenname=None, email=None, password=(None, None), reg_status=None): + ''' + SNAC (x7, x4): Request change account info + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_04.html} + ''' + to_send = [] + if screenname is not None: + to_send.append(oscar.util.tlv(0x01, screenname)) + + if email is not None: + to_send.append(oscar.util.tlv(0x11, email)) + + old_password, new_password = password + if all(password): + to_send.append(oscar.util.tlv(0x12, old_password)) + to_send.append(oscar.util.tlv(0x02, new_password)) + + if reg_status is not None: + to_send.append(oscar.util.tlv(0x13, 2, reg_status)) + + return 0x07, 0x04, ''.join(to_send) + +def x07_x05(o, sock, data): + ''' + SNAC (x7, x5): Change account info ack + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_05.html} + ''' + return x07_x03(o, sock, data) # they look the same according to the docs... + +def x07_x06(): + ''' + SNAC (x7, x6): Account confirm request + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_06.html} + ''' + return 0x07, 0x06 + +def x07_x07(o, sock, data): + ''' + SNAC (x7, x7): account confirm response + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_07.html} + ''' + stat_codes = {0x00: _('You should receive an email with confirmation instructions shortly.'), + 0x1e: _('Your account is already confirmed.'), + 0x23: _('There was an unknown server error.'), + } + + error, data = oscar.unpack((('code', 'H'),), data) + if data: + tlv, data = oscar.unpack((('tlv', 'tlv'),), data) + url = tlv.v + else: + url = 'This is not an error.' + + assert not data + status_msg = stat_codes[error] + + o.hub.user_message(status_msg, _('Confirm Account: {username}').format(username=o.self_buddy.name)) + +def x07_x08(o, sock, data): + ''' + SNAC (x7, x8): account delete request + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_08.html} + ''' + return 0x07, 0x08 + +def x07_x09(o, sock, data): + ''' + SNAC (x7, x9): Account delete ack + + reference: U{http://iserverd.khstu.ru/oscar/snac_07_09.html} + ''' + if data: + fmt = (('tlvs', 'tlv_dict'),) + + tlvs, data = oscar.unpack(fmt, data) + assert not data + + subcode, tlvs[8] = oscar.unpack((('code', 'H'),),tlvs[8]) + errmsg = tlvs[4] + + raise oscar.SnacError((0x07, x07_name), (None, errmsg), (subcode, subcodes[subcode])) diff --git a/digsby/src/oscar/snac/family_x08.py b/digsby/src/oscar/snac/family_x08.py new file mode 100644 index 0000000..00db07c --- /dev/null +++ b/digsby/src/oscar/snac/family_x08.py @@ -0,0 +1,38 @@ +import logging + +import oscar + +x08_name="Popup" +log = logging.getLogger('oscar.snac.x08') +subcodes = {} +def x08_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x08_x01(o, sock, data): + ''' + SNAC (x8, x1): Popup Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_08_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x08, (errcode, errmsg), (subcode, submsg)) + +def x08_x02(o, sock, data): + ''' + SNAC (x8, x2): Display popup + + reference: U{http://iserverd.khstu.ru/oscar/snac_08_02.html} + ''' + tlv_types = { + 1:'message', + 2:'url', + 3:'width', + 4:'height', + 5:'delay', + } + + popup, data = oscar.unpack((('tlvs', 'tlv_dict'),), data) + assert not data diff --git a/digsby/src/oscar/snac/family_x09.py b/digsby/src/oscar/snac/family_x09.py new file mode 100644 index 0000000..07b5584 --- /dev/null +++ b/digsby/src/oscar/snac/family_x09.py @@ -0,0 +1,165 @@ +import struct +import logging + +import util +import util.primitives.bits as bits +import oscar + +x09_name="Privacy management" +subcodes = {} + +log = logging.getLogger('oscar.snac.x09') +@util.gen_sequence +def x09_init(o, sock, cb): + log.info('initializing') + me = (yield None); assert me + + sock.send_snac(*oscar.snac.x09_x02(), req=True, cb=me.send) + o.gen_incoming((yield None)) + cb() + log.info('finished initializing') + +def x09_x01(o, sock, data): + ''' + SNAC (x9, x1): Privacy management Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x09, (errcode, errmsg), (subcode, submsg)) + +def x09_x02(): + ''' + SNAC (x9, x2): Request privacy params + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_02.html} + ''' + return 0x09, 0x02 + +def x09_x03(o, sock, data): + ''' + SNAC (x9, x3): Privacy limits response + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_03.html} + ''' + format = (('tlvs', 'tlv_dict'),) + tlvs, data = oscar.unpack(format, data) + assert not data + + max_visible = max_invisible = None + if 1 in tlvs: + max_visible = tlvs[1] + if 2 in tlvs: + max_invisible = tlvs[2] + + return max_visible, max_invisible + +def x09_x04(groups=None): + ''' + SNAC (x9, x4): Set group permissions + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_04.html} + ''' + +# 0x0001 UNCONFIRMED# AOL unconfirmed user flag +# 0x0002 ADMINISTRATOR# AOL administrator flag +# 0x0004 AOL_STAFF# AOL staff user flag +# 0x0008 COMMERCIAL# AOL commercial account flag +# 0x0010 FREE# ICQ non-commercial account flag +# 0x0020 AWAY# Away status flag +# 0x0040 ICQ# ICQ user sign +# 0x0080 WIRELESS# AOL wireless user +# 0x0100 UNKNOWN100# Unknown bit +# 0x0200 UNKNOWN200# Unknown bit +# 0x0400 UNKNOWN400# Unknown bit +# 0x0800 UNKNOWN800# Unknown bit + + names = 'unconfirmed admin aol_staff commercial '\ + 'free away icq wireless unknown1 unknown2 '\ + 'unknown4 unknown8'.split() + + bitflags = bits.bitfields(*names) + + groups = groups or names + result = reduce(lambda a,b:a|b, (getattr(bitflags, name, 0) for name in groups)) + + return 0x09, 0x04, struct.pack('!I', result) + +def x09_x05(names): + ''' + SNAC (x9, x5): Add to visible list + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_05.html} + ''' + return 0x09, 0x05, sn_list(names) + +def x09_x06(names): + ''' + SNAC (x9, x6): Delete from visible list + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_06.html} + ''' + return 0x09, 0x06, sn_list(names) + +def x09_x07(names): + ''' + SNAC (x9, x7): Add to invisible list + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_07.html} + ''' + return 0x09, 0x07, sn_list(names) + +def x09_x08(names): + ''' + SNAC (x9, x8): Delete from invisible list + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_08.html} + ''' + return 0x09, 0x09, sn_list(names) + +def x09_x09(o, sock, data): + ''' + SNAC (x9, x9): Service error + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_09.html} + ''' + errcode, data = oscar.unpack((('_', 'H'),), data) + + if data: + fmt = (('tlvs', 'tlv_dict'),) + + tlvs, data = oscar.unpack(fmt, data) + assert not data + + subcode, tlvs[8] = oscar.unpack((('code', 'H'),),tlvs[8]) + errmsg = tlvs[4] + + if errcode == 1: + errmsg = 'Wrong mode' + else: + errmsg = None + errcode = 'Unknown Error' + else: + subcode = None + raise oscar.SnacError((0x09, x09_name), (errcode, errmsg), (subcode, subcodes[subcode])) + +def x09_x0a(names): + ''' + SNAC (x9, xa): Add to visible(?) + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_0a.html} + ''' + + return 0x09, 0x0a, sn_list(names) + +def x09_x0b(names): + ''' + SNAC (x9, xb): Delete from visible(?) + + reference: U{http://iserverd.khstu.ru/oscar/snac_09_0b.html} + ''' + return 0x09, 0x0b, sn_list(names) + +def sn_list(names): + return ''.join(struct.pack('!B%ds' % len(sn), len(sn), sn) for sn in names) diff --git a/digsby/src/oscar/snac/family_x0a.py b/digsby/src/oscar/snac/family_x0a.py new file mode 100644 index 0000000..0603e6b --- /dev/null +++ b/digsby/src/oscar/snac/family_x0a.py @@ -0,0 +1,40 @@ +import logging + +import oscar + +x0a_name="User lookup" +log = logging.getLogger('oscar.snac.x0a') +subcodes = {} +def x0a_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x0a_x01(o, sock, data): + ''' + SNAC (xa, x1): User lookup Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_0a_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x0a, (errcode, errmsg), (subcode, submsg)) + +def x0a_x02(email): + ''' + SNAC (xa, x2): Search by email + + reference: U{http://iserverd.khstu.ru/oscar/snac_0a_02.html} + ''' + return 0x0a, 0x02, email + +def x0a_x03(o, sock, data): + ''' + SNAC (xa, x3): Search response + + reference: U{http://iserverd.khstu.ru/oscar/snac_0a_03.html} + ''' + fmt = (('tlvs', 'tlv_list'),) + name_tlvs, data = oscar.unpack(fmt, data) + assert not data + names = [tlv.v for tlv in name_tlvs] diff --git a/digsby/src/oscar/snac/family_x0b.py b/digsby/src/oscar/snac/family_x0b.py new file mode 100644 index 0000000..6565b3f --- /dev/null +++ b/digsby/src/oscar/snac/family_x0b.py @@ -0,0 +1,53 @@ +import logging + +import oscar + +''' +This is remaining unimplemented +''' + +x0b_name="Usage stats" +log = logging.getLogger('oscar.snac.x0b') +subcodes = {} +def x0b_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x0b_x01(o, sock, data): + ''' + SNAC (xb, x1): Usage stats Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_0b_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x0b, (errcode, errmsg), (subcode, submsg)) + +def x0b_x02(o, sock, data): + ''' + SNAC (xb, x2): Set minimum report interval + + reference: U{http://iserverd.khstu.ru/oscar/snac_0b_02.html} + ''' + interval, data = oscar.unpack((('interval', 'H'),), data) + o.log.debug('Minimum report interval: %d' % interval) + + return interval + +def x0b_x03(o, sock, data): + ''' + SNAC (xb, x3): Usage stats report + + reference: U{http://iserverd.khstu.ru/oscar/snac_0b_03.html} + ''' + raise NotImplementedError + +def x0b_x04(o, sock, data): + ''' + SNAC (xb, x4): Stats report ack + + reference: U{http://iserverd.khstu.ru/oscar/snac_0b_04.html} + ''' + raise NotImplementedError + diff --git a/digsby/src/oscar/snac/family_x0c.py b/digsby/src/oscar/snac/family_x0c.py new file mode 100644 index 0000000..c780d29 --- /dev/null +++ b/digsby/src/oscar/snac/family_x0c.py @@ -0,0 +1,43 @@ +import logging + +import oscar + +''' +This family is deprecated, so it is remaining unimplemented +''' + + +x0c_name="Translation - deprecated" +log = logging.getLogger('oscar.snac.x0c') +subcodes = {} +def x0c_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x0c_x01(o, sock, data): + ''' + SNAC (xc, x1): Translation Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_0c_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x0c, (errcode, errmsg), (subcode, submsg)) + +def x0c_x02(o, sock, data): + ''' + SNAC (xc, x2): client translate request + + reference: U{http://iserverd.khstu.ru/oscar/snac_0c_02.html} + ''' + raise NotImplementedError + +def x0c_x03(o, sock, data): + ''' + SNAC (xc, x3): translate response + + reference: U{http://iserverd.khstu.ru/oscar/snac_0c_03.html} + ''' + raise NotImplementedError + diff --git a/digsby/src/oscar/snac/family_x0d.py b/digsby/src/oscar/snac/family_x0d.py new file mode 100644 index 0000000..fb8ea3b --- /dev/null +++ b/digsby/src/oscar/snac/family_x0d.py @@ -0,0 +1,221 @@ +''' +family 0x0d + +Chat navigation. +''' +import struct +import logging + +import util +import oscar + +x0d_name="Chat nav" + +log = logging.getLogger('oscar.snac.x0d') +subcodes = {} +@util.gen_sequence +def x0d_init(o, sock, cb): + log.info('initializing') + me = (yield None); assert me + sock.send_snac(*oscar.snac.x0d_x02(), req=True, cb=me.send) + o.gen_incoming((yield None)) + cb() + log.info('finished initializing') + +def x0d_x01(o, sock, data): + ''' + SNAC (xd, x1): Chat nav Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x0d, (errcode, errmsg), (subcode, submsg)) + +def x0d_x02(): + ''' + SNAC (xd, x2): Request limits + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_02.html} + ''' + return 0x0d, 0x02 + +def x0d_x03(): + ''' + SNAC (xd, x3): Request exchange info + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_03.html} + ''' + return 0x0d, 0x03, '\0\0' + +def x0d_x04(exchange, cookie, instance=1, detail=0): + ''' + SNAC (xd, x4): Request room info + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_04.html} + ''' + clen = len(cookie) + return 0x0d, 0x04, struct.pack('!HB%dsHB' % clen, exchange, clen, cookie, instance, detail) + +def x0d_x05(o, sock, data): + ''' + SNAC (xd, x5): Request extended room info + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_05.html} + ''' + raise NotImplementedError + +def x0d_x06(exchange, cookie, instance=1): + ''' + SNAC (xd, x6): Request member list + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_06.html} + ''' + clen = len(cookie) + return 0x0d, 0x06, struct.pack('!HB%dsH' % clen, exchange, clen, cookie, instance) + +def x0d_x07(o, sock, data): + ''' + SNAC (xd, x7): Search for room + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_07.html} + ''' + raise NotImplementedError + +def x0d_x08(o, roomname=None, cookie=None): + ''' + SNAC (xd, x8): Create room + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_08.html} + ''' + exch = 4 + + #if roomname in o.sockets: + #log.warning('Already in chat room %s, joining anyway' % roomname) + + if roomname is None: + assert cookie is not None + roomname = cookie.split('-', 2)[-1] + + if isinstance(roomname, unicode): + roomname = roomname.encode('utf8') + + cookie = cookie or 'create' + instance = 0xFFFF + detail = 1 + tlvs = [(0xD3, roomname), + (0xD6, o.encoding), + (0xD7, o.lang),] + c_len = len(cookie) + to_send = struct.pack('!HB%dsHBH' % c_len, + exch, c_len, cookie, + instance, detail, len(tlvs)) + \ + oscar.util.tlv_list(*tlvs) + + return 0x0d, 0x08, to_send + +def x0d_x09(o, sock, data): + ''' + SNAC (xd, x9): Chat navigation info + + reference: U{http://iserverd.khstu.ru/oscar/snac_0d_09.html} + ''' + tlv_names = { + 0x01:'redirect', + 0x02:'max_concurrent', + 0x03:'exchange', + 0x04:'room', + } + + fmt = (('tlvs', 'named_tlvs', -1, tlv_names),) + tlvs, data = oscar.unpack(fmt, data) + assert not data + + redirect = max_concurrent = exchange = room = None + if 'redirect' in tlvs: + redirect = x0d_x09_redirect(o, sock, tlvs.redirect) + if 'max_concurrent' in tlvs: + max_concurrent = x0d_x09_max_concurrent(o, sock, tlvs.max_concurrent) + if 'exchange' in tlvs: + exchange = x0d_x09_exchange(o, sock, tlvs.exchange) + if 'room' in tlvs: + room = x0d_x09_room(o, sock, tlvs.room) + return redirect, max_concurrent, exchange, room + +def x0d_x09_redirect(o, sock, data): + #TODO: figure out what goes in here + pass + +def x0d_x09_max_concurrent(o, sock, data): + #TODO: figure out what goes here + pass + +def x0d_x09_exchange(o, sock, data): + fmt = (('id', 'H'), + ('num_tlvs','H'), + ('tlvs','named_tlvs', 'num_tlvs', x0d_tlv_names)) + + id, __, tlvs, data = oscar.unpack(fmt, data) + assert not data + #TODO: something with this info + return id, tlvs + +def x0d_x09_room(o, sock, data): + fmt = (('exchange', 'H'), + ('cookie','pstring'), + ('instance', 'H'), + ('detail', 'B'), + ('num_tlvs', 'H'), + ('tlvs','named_tlvs', 'num_tlvs', x0d_tlv_names)) + exchange, cookie, instance, detail, __, tlvs, data = oscar.unpack(fmt, data) + assert not data + return exchange, cookie, instance, detail, tlvs + +x0d_tlv_names = { + 0x01 : 'tree', + 0x02 : 'for_class', + 0x03 : 'max_concurrent', + 0x04 : 'max_name_length', + 0x05 : 'root', + 0x06 : 'search_tags', + 0x65 : 'child_rooms', + 0x66 : 'contain_user_class', + 0x67 : 'contain_user_array', + 0x68 : 'evil_generated', + 0x69 : 'evil_generated_array', + 0x6A : 'qualified_name', + 0x6B : 'moderator', + 0x6C : 'more_info', + 0x6D : 'num_children', + 0x6E : 'num_instances', + 0x6F : 'occupancy', + 0x70 : 'occupancy_array', + 0x71 : 'occupant_array', + 0x72 : 'occupant_evil_array', + 0x73 : 'occupants', + 0x74 : 'parent', + 0x75 : 'activity', + 0x76 : 'activity_array', + 0x77 : 'gross_evil', + 0x78 : 'net_evil', + 0x79 : 'speaker', + 0xC9 : 'chat_flag', + 0xCA : 'create_time', + 0xCB : 'creator', + 0xCC : 'description', + 0xCD : 'description_url', + 0xCE : 'closed', + 0xCF : 'language', + 0xD0 : 'mandatory_chan', + 0xD1 : 'max_html_length', + 0xD2 : 'max_occupants', + 0xD3 : 'name', + 0xD4 : 'optional_chan', + 0xD5 : 'create_permission', + 0xD6 : 'encoding_1', + 0xD7 : 'language_1', + 0xD8 : 'encoding_2', + 0xD9 : 'language_2', + 0xDA : 'max_msg_length', + 0xDB : 'encoding', + } diff --git a/digsby/src/oscar/snac/family_x0e.py b/digsby/src/oscar/snac/family_x0e.py new file mode 100644 index 0000000..6b8507c --- /dev/null +++ b/digsby/src/oscar/snac/family_x0e.py @@ -0,0 +1,198 @@ +import logging + +import time +import traceback +import struct + +import oscar.snac +from pprint import pformat + +x0e_name = "Chat" +log = logging.getLogger('oscar.snac.x0e') + +subcodes = {} + +def x0e_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x0e_x01(o, sock, data): + ''' + SNAC (xe, x1): Chat Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x0e, (errcode, errmsg), (subcode, submsg)) + +def x0e_x02(o, sock, data): + ''' + SNAC (xe, x2): Chat room info update + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_02.html} + ''' + fmt = (('exchange', 'H'), + ('cookie','pstring'), + ('instance', 'H'), + ('detail', 'B'), + ('num_tlvs', 'H'), + ('tlvs','named_tlvs', 'num_tlvs', oscar.snac.x0d_tlv_names)) + + exchange, cookie, instance, detail, __, tlvs, data = oscar.unpack(fmt, data) + assert not data + + convo = o._get_chat(cookie) + if convo is not None: + convo._update_chat_info(tlvs) + + #TODO: something with this info + return exchange, cookie, instance, detail, tlvs + +def x0e_x03(o, sock, data): + ''' + SNAC (xe, x3): User joined chat room + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_03.html} + ''' + user_infos, data = oscar.unpack((('user_infos', 'list', 'userinfo'),), data) + o.buddies.update_buddies(user_infos) + for info in user_infos: + sock_convo(o, sock).buddy_join(info.name) + +def x0e_x04(o, sock, data): + ''' + SNAC (xe, x4): User left chat room + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_04.html} + ''' + user_infos, data = oscar.unpack((('user_infos', 'list', 'userinfo'),), data) + log.info('user left room: %r', user_infos) + assert not data + o.buddies.update_buddies(user_infos) + for info in user_infos: + sock_convo(o, sock).buddy_leave(o.buddies[info.name]) + + +def x0e_x05(o, message, whisper=False, reflect=True): + ''' + SNAC (xe, x5): Outgoing chat message + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_05.html} + ''' + cookie = int(time.time()) + channel = 3 + content_type = 'text/x-aolrtf' + mode = 'binary' + message = message.encode('utf-16be') + to_send = struct.pack('!IIH', cookie, cookie, channel) + if whisper: to_send += oscar.util.tlv(1) + if reflect: to_send += oscar.util.tlv(6) + to_send += oscar.util.tlv(5, + oscar.util.tlv_list + ((4, content_type), + (2, 'unicode-2-0'), + (3, o.lang), + (5, mode), + (1, message))) + + return 0x0e, 0x05, to_send + +def x0e_x06(o, sock, data): + ''' + SNAC (xe, x6): Incoming chat message + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_06.html} + ''' + tlv_types = {1:'reflection', 3:'sender', 5:'message'} + + fmt = (('cookie', 'Q'), + ('chan', 'H'), + ('tlvs', 'named_tlvs', -1, tlv_types)) + + cookie, chan, tlvs, data = oscar.unpack(fmt, data) + assert not data + + sender, data = oscar.unpack((('info', 'userinfo'),),tlvs.sender) + assert not data + + tlv_types = {1:'message', 2:'encoding', 3:'lang'} + tlvs.message, data = oscar.unpack((('info', 'named_tlvs', -1, tlv_types),), + tlvs.message) + assert not data + message = tlvs.message['message'] + + try: + encoding = tlvs.message['encoding'] + message = oscar.decode(message, encoding) + except Exception: + traceback.print_exc() + + buddy = o.get_buddy(sender.name) + convo = sock_convo(o, sock) + + if buddy is not o.self_buddy: + o._forward_message_to_convo(convo, buddy, message) + +def x0e_x07(o, sock, data): + ''' + SNAC (xe, x7): Evil request + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_07.html} + ''' + raise NotImplementedError + +def x0e_x08(o, sock, data): + ''' + SNAC (xe, x8): Evil response + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_08.html} + ''' + raise NotImplementedError + +def x0e_x09(o, sock, data): + ''' + SNAC (xe, x9): Chat error or data + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_09.html} + ''' + raise oscar.SnacError((0x0e, x0e_name), (None, 'Chat error'), (None, None)) + +def x0e_x26(o, sock, data): + ''' + SNAC (xe, x26): Chat room info? + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_26.html} + ''' + fmt = (('num', 'H'), + ('tlvs', 'tlv_list', 'num')) + + __, tlvs, data = oscar.unpack(fmt, data) + assert not data + + log.info('chat room info?') + log.info(pformat(tlvs)) + +def x0e_x30(o, sock, data): + ''' + SNAC (xe, x30): Room information? + + reference: U{http://iserverd.khstu.ru/oscar/snac_0e_30.html} + ''' + + return # unknown data: room information? unpacking as buddynames is incorrect. + + fmt = (('bnames', 'list', 'pstring'),) + bnames, data = oscar.unpack(fmt, data) + assert not data + c = sock_convo(o, sock) + for bname in bnames: c.buddy_join(bname) + +def sock_convo(o, sock): + for k, v in o.sockets.items(): + v = getattr(v, 'sock', v) # if it's a SnacQueue + + if v is sock and isinstance(k, basestring) and k != 'bos': + assert k in o.conversations + return o.conversations[k] diff --git a/digsby/src/oscar/snac/family_x0f.py b/digsby/src/oscar/snac/family_x0f.py new file mode 100644 index 0000000..07a42a1 --- /dev/null +++ b/digsby/src/oscar/snac/family_x0f.py @@ -0,0 +1,59 @@ +import logging + +import oscar + +x0f_name="Directory user search" + +''' +There is no documentation for this family, and it has very +limited use. So, it is remaining unimplemented. +''' +log = logging.getLogger('oscar.snac.x0f') +subcodes = {} +def x0f_init(o, sock,cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x0f_x01(o, sock, data): + ''' + SNAC (xf, x1): Directory user search Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_0f_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x0f, (errcode, errmsg), (subcode, submsg)) + +def x0f_x02(o, sock, data): + ''' + SNAC (xf, x2): Client search request + + reference: U{http://iserverd.khstu.ru/oscar/snac_0f_02.html} + ''' + raise NotImplementedError + +def x0f_x03(o, sock, data): + ''' + SNAC (xf, x3): Search reply + + reference: U{http://iserverd.khstu.ru/oscar/snac_0f_03.html} + ''' + raise NotImplementedError + +def x0f_x04(o, sock, data): + ''' + SNAC (xf, x4): Request interests list + + reference: U{http://iserverd.khstu.ru/oscar/snac_0f_04.html} + ''' + raise NotImplementedError + +def x0f_x05(o, sock, data): + ''' + SNAC (xf, x5): Interest list response + + reference: U{http://iserverd.khstu.ru/oscar/snac_0f_05.html} + ''' + raise NotImplementedError + diff --git a/digsby/src/oscar/snac/family_x10.py b/digsby/src/oscar/snac/family_x10.py new file mode 100644 index 0000000..5b0aed6 --- /dev/null +++ b/digsby/src/oscar/snac/family_x10.py @@ -0,0 +1,212 @@ +'Buddy icons.' + +from __future__ import with_statement +import functools + +import logging +import struct +import hashlib + +import util +import oscar + +MAX_ICON_SIZE = 7168 + +x10_name="SSBI" + +log = logging.getLogger('oscar.snac.x10') + +subcodes = {} +icon_queue = [] + +__icon_data = None +def x10_init(o, sock, cb): + log.info('initializing') + cb() + log.info('finished initializing') + +def x10_x01(o, sock, data): + ''' + SNAC (x10, x1): SSBI Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_10_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x10, (errcode, errmsg), (subcode, submsg)) + +def x10_x02(icon_data): + ''' + SNAC (x10, x2): Upload your icon + + reference: U{http://iserverd.khstu.ru/oscar/snac_10_02.html} + ''' + + # send an SSI request for changing 'self' icon. + + icon_len = len(icon_data) + + return 0x10, 0x02, struct.pack('!HH%ds' % icon_len, 1, icon_len, icon_data) + +def x10_x03(o, sock, data): + ''' + SNAC (x10, x3): Upload buddy icon reply + + reference: U{http://iserverd.khstu.ru/oscar/snac_10_03.html} + ''' + + fmt = (('_1', 'H'), + ('_2', 'H'), + ('icon_hash', 'pstring')) + + __,__, hash, data = oscar.unpack(fmt, data) + return hash + +def x10_x04(o, sn): + ''' + SNAC (x10, x4): Request buddy icon + + reference: U{http://iserverd.khstu.ru/oscar/snac_10_04.html} + ''' + snlen = len(sn) + hash = o.buddies[sn].icon_hash + hashlen = len(hash) + + to_send = struct.pack('!B%dsBHBB%ds' % + (snlen, hashlen), + snlen, sn, + 1, 1, 1, # mystery bytes! + hashlen, hash) + + return 0x10, 0x04, to_send + +def x10_x05(o, sock, data): + ''' + SNAC (x10, x5): Requested buddy icon + + reference: U{http://iserverd.khstu.ru/oscar/snac_10_05.html} + ''' + + fmt = (('sn', 'pstring'), + ('unknown', 'H'), + ('flags', 'B'), + ('hash', 'pstring'), + ('icon_len', 'H'), + ('icon', 's', 'icon_len')) + + sn, __,__, hash, __, icon, data = oscar.unpack(fmt, data) + assert not data, util.to_hex(data) + return sn, hash, icon + +def x10_x06(o, sock, data): + ''' + SNAC (x10, x6): SNAC(0x10, 0x06) + + reference: U{http://iserverd.khstu.ru/oscar/snac_10_06.html} + ''' + raise NotImplementedError + +def x10_x07(o, sock, data): + ''' + SNAC (x10, x7): SNAC(0x10, 0x07) + + reference: U{http://iserverd.khstu.ru/oscar/snac_10_07.html} + ''' + raise NotImplementedError + +def set_buddy_icon(o, icon_data): + + log.info('set_buddy_icon called with %d bytes of data', len(icon_data)) + + if len(icon_data) > MAX_ICON_SIZE: + raise AssertionError('Oscar can only set buddy icon data less than 8k') + + hash = hashlib.md5(icon_data).digest() + hashlen = len(hash) + + #globals().get('set_buddy_icon_ssi_%s' % o.name)(o, hash) + set_buddy_icon_ssi(o, hash) + + def on_success(sock, snac): + # On a successful icon set, server returns with the icon hash. + icon_hash = x10_x03(o, sock, snac.data) + + if icon_hash: + log.info('upload self icon successful, received hash %r (%d bytes)', icon_hash, len(icon_hash)) + + from util import Timer + Timer(3, o.self_buddy.cache_icon, icon_data, icon_hash).start() + else: + log.warning('buddy icon server did not return a hash.') + + log.info('sending snac 0x10 0x02 (icon data)') + o.send_snac(*oscar.snac.x10_x02(icon_data), **{'req': True, 'cb': on_success}) + +def set_buddy_icon_ssi_aim(o, iconhash): + + ssi_info = dict(type = 0x0014, name = '1', group_id = 0) + existing_ssis = o.ssimanager.find(**ssi_info) + + if existing_ssis: + ssi = existing_ssis[0].clone() + else: + ssi = oscar.ssi.item(item_id = o.ssimanager.new_ssi_item_id(0), + **ssi_info) + + hashlen = len(iconhash) + ssi.tlvs.update({0x131: "", + 0x0d5: struct.pack('!2B%ds' % hashlen, 1, hashlen, iconhash)}) + + log.info('modifying self icon SSI') + o.ssimanager.add_modify(ssi) + +set_buddy_icon_ssi = set_buddy_icon_ssi_aim + +#def set_buddy_icon_ssi_icq(o, iconhash): +# ssi_info = dict(type = 0x0014, name = '1', group_id = 0) +# +# existing_ssis = o.ssimanager.find(**ssi_info) +# +# if existing_ssis: +# ssi = existing_ssis[0].clone() +# else: +# ssi = oscar.ssi.item(item_id = o.ssimanager.new_ssi_item_id(0), +# **ssi_info) +# +# hashlen = len(iconhash) +# ssi.tlvs.update({0x131: "", +# 0x0d5: struct.pack('!2B%ds' % hashlen, 1, hashlen, iconhash)}) +# +# log.info('modifying self icon SSI') +# o.ssimanager.add_modify(ssi) + +@util.gen_sequence +def get_buddy_icon(o, sn): + me = (yield None); assert me + + if not isinstance(sn, basestring): + raise TypeError('get_buddy_icon requires a string: %r %r' % (type(sn), sn)) + + if not o.buddies[sn].icon_hash: + log.warning('get_buddy_icon called for %s but that buddy has no ' + 'icon_hash', sn) + raise StopIteration + + # Send the icon request. + log.info("Requesting %s's icon", sn) + o.send_snac(req=True, cb=me.send, *oscar.snac.x10_x04(o, sn)) + + # Wait for response. + rcv_sn, rcv_hash, rcv_icon = o.gen_incoming((yield 'waiting for 0x10, 0x05')) + log.debug('Received %d bytes of hash and %d bytes of icon for ' + '%s', len(rcv_hash), len(rcv_icon), rcv_sn) + + if rcv_sn != sn: + log.debug('Requested buddy icon for %s, but got one for %s', + sn, rcv_sn) + + # store the hash/icon for later + update the gui + buddy = o.buddies[rcv_sn] + log.info('caching icon for %s', buddy) + buddy.cache_icon(rcv_icon, rcv_hash) + buddy.notify('icon') diff --git a/digsby/src/oscar/snac/family_x13.py b/digsby/src/oscar/snac/family_x13.py new file mode 100644 index 0000000..1008b9e --- /dev/null +++ b/digsby/src/oscar/snac/family_x13.py @@ -0,0 +1,331 @@ +''' +Family 13 - SSI +''' +import logging +import struct + +import util +import util.primitives.funcs as funcs +import oscar + +log = logging.getLogger('oscar.snac.x13') +subcodes = {} +x13_name="SSI" +version = 4 + +@util.gen_sequence +def x13_init(o, sock, cb): + me = (yield None); assert me + log.info('initializing') + o.change_state(o.Statuses.LOADING_CONTACT_LIST) + # request params + sock.send_snac(*oscar.snac.x13_x02(), req=True, cb=me.send) + o.gen_incoming((yield 'waiting for x13, x03')) + o.buddies.freeze() + # request buddy list + oscar.snac.request_buddy_list(o, sock, me()) + yield None + # SSI activate + from util import Timer + Timer(2.0, o.buddies.thaw).start() + sock.send_snac(*oscar.snac.x13_x07()) + cb() + log.info('finished initializing') + +def x13_x01(o, sock, data): + ''' + SNAC (x13, x1): SSI Family Error + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_01.html} + ''' + errcode, errmsg, subcode = oscar.snac.error(data) + submsg = subcodes.setdefault(subcode, 'Unknown') if subcode else None + raise oscar.snac.SnacError(0x13, (errcode, errmsg), (subcode, submsg)) + +def x13_x02(): + ''' + SNAC (x13, x2): Request SSI params + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_02.html} + ''' + return 0x13, 0x02 + +def x13_x03(o, sock, data): + ''' + SNAC (x13, x3): SSI Limitations + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_03.html} + ''' + tlvs, data = oscar.unpack((('tlvs','tlv_dict'),), data) + return tlvs[0x04] + +def x13_x04(o, sock, data): + ''' + SNAC (x13, x4): Request contact list (first time) + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_04.html} + ''' + return 0x13, 0x04 + +def x13_x05(): + ''' + SNAC (x13, x5): Contact list request + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_05.html} + ''' + return 0x13, 0x05, struct.pack("!IH", 0, 0) + +def x13_x06(o, sock, data): + ''' + SNAC (x13, x6): Server contact list reply + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_06.html} + ''' + roster_reply_fmt=(('ssi_protocol_ver','B'), + ('num_ssis', 'H'), + ('ssis', 'ssi_dict', 'num_ssis'),) + __,__,ssis,data = oscar.unpack(roster_reply_fmt, data) + + return ssis, data + print "13 6 update with" + repr(ssis) +def x13_x07(): + ''' + SNAC (x13, x7): Request contact list (after login) + + After this, the server will send presence notifications. + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_07.html} + ''' + + return 0x13, 0x07 + +def x13_x08(o, sock, data): + ''' + SNAC (x13, x8): SSI add item + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_08.html} + ''' + ssis, data = oscar.unpack((('ssis', 'ssi_dict'),), data) + o.ssimanager.ssis.update(ssis) + +def x13_x09(o, sock, data): + ''' + SNAC (x13, x9): SSI modify item + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_09.html} + ''' + ssis, data = oscar.unpack((('ssis', 'ssi_dict'),), data) + o.ssimanager.ssis.update(ssis, modify=True) + +def send_x13_x09(ssi_item): + return 0x13, 0x09, ssi_item.to_bytes() + +def x13_x0a(o, sock, data): + ''' + SNAC (x13, xa): SSI delete item + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_0a.html} + ''' + d, data = oscar.unpack((('ssis', 'ssi_dict'),), data) + [o.ssimanager.ssis.pop(k) for k in d if k in o.ssimanager.ssis] + +def x13_x0e(o, sock, data): + ''' + SNAC (x13, xe): SSI acknowledgement + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_0e.html} + ''' + fmt = (('errcodes', 'list', 'H'),) + errcodes, data = oscar.unpack(fmt, data) + assert not data + +# errors = filter(None, errcodes) + return errcodes +# +# if not errors: +# return True +# else: +# #TODO: raise snacerrors? +# return False + +def x13_x0f(o, sock, data): + ''' + SNAC (x13, xf): client local SSI is up-to-date + + xx xx xx xx dword modification date/time of server SSI + xx xx word number of items in server SSI + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_0f.html} + ''' + + time, num, data = oscar.unpack((('time','I'), + ('num','H')), + data) + + log.info('SSIs up to date:') + + return {},'' + +def x13_x11(o, sock, data): + ''' + SNAC (x13, x11): Contact edit start + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_11.html} + ''' + ## append 0x00010000 for import transaction (ICQ?): used for contacts + ## requiring authorization. + d, data = oscar.unpack((('ssis', 'ssi_dict'),), data) + o.ssimanager.ssis.update(d) + +def send_x13_x11(import_transaction=False): + ''' + SNAC (x13, x11): Contact edit start + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_11.html} + ''' + if not import_transaction: + return 0x13, 0x11 + else: + return 0x13, 0x11, struct.pack("!I", 0x00010000) + +def x13_x12(o, sock, data): + ''' + SNAC (x13, x12): Contact edit end + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_12.html} + ''' + d, data = oscar.unpack((('ssis', 'ssi_dict'),), data) + o.ssimanager.ssis.update(d) + +def send_x13_x12(): + ''' + SNAC (x13, x12): Contact edit end + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_12.html} + ''' + return 0x13, 0x12 + + +def icq_reason_string(uin, reason): + uin = str(uin) + (lu, lr) = len(uin), len(reason) + return struct.pack("!B%dsH%dsH" % (lu, lr), lu, uin, lr, reason, 0) + +def unpack_icq_reason_string(o, sock, data): + name, __, reason, __, data = oscar.unpack((("name", "pstring"), + ("reason_len", "H"), + ("reason", "s", "reason_len"),("zero", "H"), ), + data) + return name, reason + +def x13_x14(uin, reason): + ''' + SNAC (x13, x14): Grant future authorization to client + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_14.html} + ''' + return 0x13, 0x14, icq_reason_string(uin, reason) + +def x13_x15(o, sock, data): + ''' + SNAC (x13, x15): Future authorization granted + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_15.html} + ''' + name, reason = unpack_icq_reason_string(o, sock, data) + log.info("You were Future authorized by %s, with reason %s", name, reason) + +def x13_x16(uin): + ''' + SNAC (x13, x16): Delete yourself from another list (supported?) + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_16.html} + ''' + return 0x13, 0x16, struct.pack("B", len(str(uin)), str(uin)) + +def x13_x18(uin, reason): + ''' + SNAC (x13, x18): Send authorization request + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_18.html} + ''' + return 0x13, 0x18, icq_reason_string(uin, reason) + +def x13_x19(o, sock, data): + ''' + SNAC (x13, x19): incoming authorization request + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_19.html} + ''' + name, reason = unpack_icq_reason_string(o, sock, data) + reason_d = reason.decode('fuzzy utf-8') + log.info("%r wants to add you to their contact list, with reason %r", name, reason_d) + o.auth_requested(name, reason_d) + +def x13_x1a(uin, reason, accept=True): + ''' + SNAC (x13, x1a): Send authorization reply + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_1a.html} + ''' + uin = str(uin) + (lu, lr) = len(uin), len(reason) + mysnac = struct.pack("!B%dsBH%ds" % (lu, lr), lu, uin, accept, lr, reason) + return 0x13, 0x1a, mysnac + +def x13_x1b(o, sock, data): + ''' + SNAC (x13, x1b): incoming authorization reply + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_1b.html} + ''' + name, accept, __, reason, data = oscar.unpack((("name", "pstring"), + ("accept", "B"), + ("reason_len", "H"), + ("reason", "s", "reason_len"), ), + data) + + assert not data + log.info("%s has %s you with reason: %s", name, + "Accepted" if accept else "Denied", reason) + +def x13_x1c(o, sock, data): + ''' + SNAC (x13, x1c): "You were added" message + + reference: U{http://iserverd.khstu.ru/oscar/snac_13_1c.html} + ''' + name, data = oscar.unpack((("name", "pstring"),), data) + assert not data + log.info("You were added by", name) + +@util.gen_sequence +def request_buddy_list(o, sock, parent): + me = (yield None) + + ssis = {} + + sock.send_snac(*oscar.snac.x13_x05(), req=True, cb=me.send) + while True: + ssis_, data = o.gen_incoming((yield 'waiting for x13, x06')) + if not ssis_: break + ssis.update(ssis_) + + # if there is data left, it must be last change time, signaling the end + # of this packet + if data: + last_change, data = oscar.unpack((('last_change', 'I'),), data) + # if last_change is not 0, then we have the whole list. + if last_change: break + assert not data + o.ssimanager.ssis.update(ssis) + try: + grouped = sorted(funcs.groupby(ssis.values(), key = lambda s: s.type)) + + log.info('SSI report:\nTotal ssis: %d\nBy type:\n%s', + len(ssis),'\n'.join('%r = %r items' % (id, len(items)) for (id, items) in grouped)) + except Exception, e: + log.error("Error generating SSI report: %r", e) + + parent.next() + diff --git a/digsby/src/oscar/snac/family_x15.py b/digsby/src/oscar/snac/family_x15.py new file mode 100644 index 0000000..f11f8d7 --- /dev/null +++ b/digsby/src/oscar/snac/family_x15.py @@ -0,0 +1,769 @@ +import struct +import logging +import datetime +import util +import traceback +import oscar +import icq_country + +x15_name="ICQ extensions" +version = 2 +log = logging.getLogger('oscar.snac.x15') +subcodes = {} + + +SUCCESS = 10 + +def icq_reqid(): + i = 0 + while True: + i = (i + 1) % 0xFFFF + yield i +reqids = icq_reqid() + +def lnts(s): + ''' + Make a Little eNdian null-Terminated String + ''' + # TODO: assert unicode entering, encode to unicode. + s = s + '\x00' + return struct.pack('H', x))[0] + +class OscarContact(Contact): + _renderer = 'Contact' + + inherited_actions = [OscarBuddy] + + def __init__(self, *a, **k): + Contact.__init__(self, *a, **k) + self.ssis = self.protocol.ssimanager.ssis + self.remote_alias #side effects on buddy +# self.buddy.notify('remote_alias') + + @property + def group(self): + return self.ssis.get_group(self.id[0]) + + def get_remote_alias(self): + try: + friendly = self.ssis[self.id].get_alias().decode('fuzzy utf-8') + + # hack: set the friendly name in the buddy + if self.buddy._friendly_name is None: + self.buddy._friendly_name = friendly + + return friendly + except KeyError: + # GUI/model out of sync...return None + return None + + def set_remote_alias(self, newname): + self.protocol.set_remote_alias(self, newname) + + remote_alias = property(get_remote_alias, set_remote_alias) + + @common.action() + def rename_gui(self): + s = Contact.rename_gui(self) + if s is not None: + self.remote_alias = s + + @common.action(Contact._block_pred) + def block(self, *a,**k): + return Contact.block(self, *a, **k) + @common.action(Contact._unblock_pred) + def unblock(self, *a,**k): + return Contact.unblock(self, *a, **k) + +class SSIItem(object): + def __init__(self, name, group_id, item_id, type_=None, tlvs=None, **k): + if not isinstance(name, str): + raise TypeError('SSIItem.name must be str, not %s' % type(name)) + + type_ = k.pop('type', type_) + if k: + raise Exception('Only one extra keyword argument ("type") is allowed for SSIItems') + + self.name = name + self.group_id = group_id + self.item_id = item_id + self.type = type_ or 0 #default to a Buddy + self.tlvs = odict(tlvs or {}) #default to no tlvs (maybe this should be storage/named tlvs) + + self.c8_to_ints() + + def set_name(self, name): + if not isinstance(name, str): + raise TypeError('setting SSIItem.name to something that is not str: %s' % type(name)) + self._name = name + + def get_name(self): + return self._name + + name = property(get_name, set_name) + + @property + def tlv_tuple(self): + return (self.group_id, self.item_id) + + + def to_bytes(self): + tlvs_string = '' + for type, val in self.tlvs.items(): + if type != 0xc8: + if hasattr(val, 'value'): + tlvs_string += oscar.util.tlv(val.type, val.length, val.value) + else: + tlvs_string += oscar.util.tlv(type, val) + else: + tlvs_string += oscar.util.tlv(type, "".join([struct.pack('!H', short) + for short in val])) + + + if not isinstance(self.name, str): + raise TypeError('SSIItem.name should always be str, but it is %s' % type(self.name)) + + nlen, tlen = len(self.name), len(tlvs_string) + return struct.pack("!H%dsHHHH%ds" % (nlen, tlen), + nlen, self.name, self.group_id, + self.item_id, self.type, tlen, tlvs_string) + + + def c8_to_ints(self): + if 0xc8 in self.tlvs and isinstance(self.tlvs[0xc8], basestring): + + try: + self.tlvs[0xc8] = oscar.unpack((('list', 'list', 'H'),), + self.tlvs[0xc8])[0] + except Exception: + import traceback + traceback.print_exc() + # ssis.update will be called afterwards and fix the C8 tlv + self.tlvs[0xc8] = [] + + def clone(self): + return oscar.unpack((('ssi','ssi'),),self.to_bytes())[0] + + def add_item_to_group(self, id_to_add, position=0): + if self.type != 1: + raise AssertionError(repr(self) + " is not a group") + else: + + self.tlvs.setdefault(0xc8, []).insert(position, id_to_add) + + + def remove_item_from_group(self, id_to_remove): + if self.type != 1: + raise AssertionError(repr(self) + " is not a group") + + + try: + self.tlvs.setdefault(0xc8, [id_to_remove]).remove(id_to_remove) + except ValueError: + # id not in list, so our job is done + pass + + if not self.tlvs[0xc8]: del self.tlvs[0xc8] + + def get_item_position(self, id_to_find): + if self.type != 1: + raise AssertionError(repr(self)+ " is not a group") + try: + return self.tlvs.get(0xc8, []).index(id_to_find) + except ValueError: + # id not in list + return None + + def move_item_to_position(self, id_to_move, position): + curposition = self.get_item_position(id_to_move) + if curposition is None: + raise AssertionError(repr(self) + + " does not contain %d" % id_to_move) + if position != curposition: + self.remove_item_from_group(id_to_move) + self.add_item_to_group(id_to_move, position) + + def get_alias(self): + return self.tlvs.get(0x131, None) + + def set_alias(self, alias): + if alias: self.tlvs[0x131] = alias + else: self.remove_alias() + + def remove_alias(self): + self.remove_tlv(0x131) + + alias = property(get_alias, doc="this is the alias of the buddy") + + def get_comment(self): + return self.tlvs.get(0x013C, None) + + def set_comment(self, comment): + if comment: self.tlvs[0x013C] = comment + else: self.remove_comment() + + def remove_comment(self): + self.remove_tlv(0x013C) + + comment = property(get_comment, doc="this is the comment about the buddy") + + def remove_tlv(self, type): + try: del self.tlvs[type] + except KeyError: pass + + + def __repr__(self): + return "" % \ + (self.name, self.group_id, self.item_id, self.type) + +class OscarSSIs(dict): + def __init__(self, manager): + self.manager = manager + self.root_group = SSIGroup(None, self.manager) + self.groups = {(0,0):self.root_group} + return dict.__init__(self) + + def __setitem__(self, key, ssi, modify=False): + tupled_key = tuple_key(ssi) + if isinstance(key, SSIItem): + assert(key == ssi) + else: + assert(key == tupled_key) + if ssi.type == 1: + with self.root_group.frozen(): + if tupled_key in self.groups: + self.groups[tupled_key].set_ssi(ssi) + else: + self.groups[tupled_key] = SSIGroup(ssi, self.manager) + elif modify and ssi.type == 0: + if 0x015c in ssi.tlvs or 0x015d in ssi.tlvs: + self.manager.o.get_buddy_info(ssi) + else: + pass #log.info("skipping %r / %r", ssi.name, ssi) + return dict.__setitem__(self, tupled_key, ssi) + + def get_group(self, key): + #try: + return self.groups[tuple_key(key)] + #except (KeyError,): + # return contacts.Group('Loading...', self, None) + + def __getitem__(self, key): + return dict.__getitem__(self, tuple_key(key)) + + def __contains__(self, key): + return dict.__contains__(self, tuple_key(key)) + + def __delitem__(self, key): + return dict.__delitem__(self, tuple_key(key)) + + def update(self, hash, modify=False): + with self.root_group.frozen(): + for k,v in hash.items(): + self.__setitem__(k,v, modify=modify) + self.fix_group_c8() + + def fix_group_c8(self): + ''' + Sometimes SSI group index ids (0xc8) are little endian instead of the + expected big endian. + + This function attempts to guess when that is happening, and fix them. + ''' + groups = [key for key in self if key[1] == 0] + dgi = dict.__getitem__ + + for g_id, i_id in groups: + if not g_id: #root group + #find all group ssis + members = [key for key in groups if key != (0, 0)] + #extract group ids + m_ids = set(x[0] for x in members) + assert (g_id, i_id) == (0, 0) + #get group ids from root group + gm_ids = dgi(self, (0, 0)).tlvs.get(0xc8, []) + else: + #find all the ssis which match this group id + members = [key for key in self if key[0] == g_id and key[1] != 0] + #extract member item ids + m_ids = set(x[1] for x in members) + #grab the member ids the group thinks it has + gm_ids = dgi(self, (g_id, i_id)).tlvs.get(0xc8, []) + #if they're the same, move on. + if m_ids == set(gm_ids) and len(m_ids) == len(gm_ids): + continue + + #map the group's ids to their position in the known list. + known_locs = dict((y,x) for x,y in enumerate(oset(gm_ids))) + locations = {} + for possible in m_ids: + #for each real id: + if possible in known_locs: + #if the group has a location for it, use that one. + locations[possible] = known_locs[possible] + continue + #otherwise, see if we have an inverted location for it. + inverted = byteswap(possible) + #even if somehow there was a collision, they'll just be put next to each other. + #close enough. + if inverted in known_locs: + locations[possible] = known_locs[inverted] + continue + #otherwise, throw it at the end. + locations[possible] = len(m_ids) + new_gm_ids = sorted(m_ids, key = locations.__getitem__) + #setting this should do no harm, since the only thing that can happen to an + #ssi is deletion or modification. deleted doesn't matter, modified fixes this + #on the server as well. + dgi(self, (g_id, i_id)).tlvs[0xc8] = new_gm_ids + +def tuple_key(key): + try: + # it's already a tuple of ints + a, b = key + int(a), int(b) + return key + except (TypeError, ValueError): + try: + # it's an SSIItem: return (key.group_id, key.item_id) + t = key.group_id, key.item_id + return t + except AttributeError: + try: + # it's a group: return (key, 0) + int(key) + return key, 0 + except TypeError: + raise AssertionError(repr(key) + " is not a valid ssi key") + +class SSIGroup(contacts.Group): + + _renderer = 'Group' + + def __init__(self, new_ssi, ssi_manager): + self.my_ssi = new_ssi + self.ssi_manager = ssi_manager + if self.my_ssi is None: + self.my_ssi = SSIItem('root', 0,0,1) + assert(self.my_ssi.type == 1) + + # Group objects get unicode + groupname = self.my_ssi.name.decode('utf-8', 'replace') + + contacts.Group.__init__(self, groupname, ssi_manager.o, self.my_ssi.group_id) + + def set_ssi(self, new_ssi): + oldname = self.my_ssi.name + self.my_ssi = new_ssi + assert(self.my_ssi.type == 1) + self.name = self.my_ssi.name.decode('utf-8', 'replace') + if oldname != self.name: + self.notify('name', oldname, self.name) + + def __getitem__(self, index): + assert(type(index) == int) + g_id = self.my_ssi.group_id + tlv = self.my_ssi.tlvs.get(0xc8, []) + start_index = index + ssi = None + while ssi is None: + try: + i_id = tlv[index] + except IndexError: + break + + if g_id: + try: + ssi = self.ssi_manager.ssis[(g_id, i_id)] + except KeyError, e: + index += 1 + continue + else: + return OscarContact(self.ssi_manager.o.buddies[ssi.name], (g_id, i_id)) + else: + return self.ssi_manager.ssis.get_group((i_id, 0)) + + if ssi is None: # pretty much guaranteed to be if we're here... + warnings.warn('Error finding SSI %r in group (id=%r)' % (start_index, g_id)) # use warnings so it only prints once. + raise IndexError("SSI Item not found in group(id=%r): %r", index, g_id) + + + + def __iter__(self): + for i in xrange(len(self)): + try: + thing = self[i] + if self.my_ssi.group_id == 0 and not getattr(thing, 'should_show', lambda: True)(): + continue + yield thing + except (KeyError, IndexError), e: + # this really only happens when we are doing + # privacy edits and stuff. usually the cause is + # removing an SSI before changing the order tlv (0xc8) + warnings.warn("Error iterating over group: %r" % e) + continue + + @property + def online(self): + return bool(self.num_online) + + @property + def num_online(self): + ss = self.ssi_manager.ssis + bs = self.ssi_manager.o.buddies + total = 0 + g_id = self.my_ssi.group_id + tlv = self.my_ssi.tlvs.get(0xc8, []) + if g_id: + total += len([s for s in (ss.get((g_id, elem), None) for elem in tlv) + if s and bs[s.name].online]) + else: + groups = [ss.get_group(group_id) for group_id in tlv] + for group in groups: + total += group.num_online + return total + + def find(self, obj): + return list.find(self, obj) + + def __len__(self): + return len(self.my_ssi.tlvs.get(0xc8, [])) + + def is_facebook_group(self): + # AOL recently added facebook connect support. it populates the SSIs with groups named + # the same as your facebook friendlists, and also adds an SSI of type 0x1c and name = "FB SG:groupname". + # So, we look for those SSIs and if found, this is a facebook group. + return len(self.ssi_manager.find(type = 0x1c, name = "FB SG:%s" % self.my_ssi.name)) > 0 + + def should_show(self): + return bool(len(self) or (not self.is_facebook_group())) diff --git a/digsby/src/oscar/ssi/SSIManager.py b/digsby/src/oscar/ssi/SSIManager.py new file mode 100644 index 0000000..5edd6d6 --- /dev/null +++ b/digsby/src/oscar/ssi/SSIManager.py @@ -0,0 +1,580 @@ +from __future__ import with_statement +from contextlib import contextmanager +import oscar.snac as snac +import oscar +import common +import struct +from oscar.ssi import item, OscarSSIs, SSIException, ssi_err_types +from util import gen_sequence, lookup_table, callsback +from oscar.ssi.SSIItem import tuple_key + +from logging import getLogger +log = getLogger("oscar.ssis") + +#from functools import partial + +property_tlv_types = lookup_table(alias=0x0131, + email=0x0137, + SMSnum=0x013A, + comment=0x013C) + +def _lowerstrip(s): + return oscar._lowerstrip(s) + + +class SSIManager(object): + def __init__(self, protocol): + self.o = protocol + self.ssis = OscarSSIs(self) + self.ssi_edits_out = 0 + self.generated_ssi_ids = [] + +# @gen_sequence +# @callsback + @gen_sequence + def add_modify(self, new_ssi): #, callback=None): + ''' + not to be used for things you need to hear back from! + @param new_ssi: + ''' + me = (yield None) + with self.ssi_edit(): + if new_ssi in self.ssis: self._modify_ssi(new_ssi, me()) + else: self._add_ssi(new_ssi, me()) + + errors = (yield None) + if not errors[0]: + self.ssis[new_ssi] = new_ssi +# callback.success() + else: +# callback.error() + raise SSIException("Error adding/modifying SSI: " + + ",".join([ssi_err_types[err] for err in errors])) + + def new_ssi_item_id(self, group): + newid = 0x100 + used_ids = set(self.generated_ssi_ids + self.ssis.keys()) + while (group, newid) in used_ids: + newid += 1 + self.generated_ssi_ids += [(group, newid)] + return newid + + def new_ssi_group_id(self): + newid = 0x100 + used_ids = set(self.generated_ssi_ids + self.ssis.keys()) + while (newid, 0) in used_ids: + newid += 1 + self.generated_ssi_ids += [(newid,0)] + return newid + + def get_ssis_in_group(self, group_id, with_group=False): + ''' + get all SSIs in a group + ''' + if with_group: + return [ssi for ssi in self.ssis.values() + if ssi.group_id == group_id] + else: + return [ssi for ssi in self.ssis.values() + if ssi.group_id == group_id + and ssi.item_id != 0] + + def get_ssis_by_type(self, type): + return [ssi for ssi in self.ssis.values() + if ssi.type == type] + + @gen_sequence + def _ssi_action(self, action, new_ssis, parent): + me = (yield None) + with self.ssi_edit(): + if isinstance(new_ssis, item): new_ssis = [new_ssis] + self.o.send_snac(0x13, action, + "".join(s.to_bytes() for s in new_ssis), + req=True,cb=me.send) + parent.send(self.o.gen_incoming((yield None))) + + @gen_sequence + def _ssi_double_action(self, action1, new_ssis1, + action2, new_ssis2, parent): + me = (yield None) + with self.ssi_edit(): + if isinstance(new_ssis1, item): new_ssis1 = [new_ssis1] + if isinstance(new_ssis2, item): new_ssis2 = [new_ssis2] + + #these have expanded arguments because me.send can't take kwargs + #priority is explicitly set to the default of 5 + self.o.send_snac(0x13, action1, + "".join(s.to_bytes() for s in new_ssis1), + 5,True, me.send, action1) + self.o.send_snac(0x13, action2, + "".join(s.to_bytes() for s in new_ssis2), + 5,True, me.send, action2) + sock1, snac1, ret_action1 = (yield None) + sock2, snac2, ret_action2 = (yield None) + incoming1 = self.o.gen_incoming((sock1, snac1)) + incoming2 = self.o.gen_incoming((sock2, snac2)) + parent.send((incoming1, ret_action1, incoming2, ret_action2)) + + def _add_ssi(self, *a, **k): + self._ssi_action(0x08, *a, **k) + + def _modify_ssi(self, *a, **k): + self._ssi_action(0x09, *a, **k) + + def _remove_ssi(self, *a, **k): + self._ssi_action(0x0a, *a, **k) + + @gen_sequence + @callsback + def add_new_ssi(self, name, group_protocol_object=None, position=0, type_=None, + authorization=False, callback = None): + me = (yield None) + if not isinstance(name, str): + name = name.encode('utf-8') + + if group_protocol_object is not None: + #adding a buddy: + group_protocol_object = tuple_key(group_protocol_object) + group_id=group_protocol_object[0] + item_id = self.new_ssi_item_id(group_protocol_object[0]) + if type_ is None: + type_ = 0 + else: + #adding a group + group_id = self.new_ssi_group_id(); item_id=0; + if type_ is None: + type_ = 1 + + #create ssi+ + new_ssi = item(name, group_id, item_id, type_) + + if group_protocol_object is not None and authorization: + new_ssi.tlvs[0x66] = "" + with self.ssi_edit(): #needed until group mod is sent + #send buddy to server + errors = (yield self._add_ssi(new_ssi, me())) + + if not errors[0]: + #update local buddylist + self.ssis[new_ssi] = new_ssi + ids = group_protocol_object or (0,0) + #buddy if adding to a group, else new group + id_to_add = new_ssi.item_id if group_protocol_object \ + else new_ssi.group_id + self._add_to_group(ids, id_to_add, position) #end with block + else: + callback.error() + if errors[0] != 0xE: + raise SSIException('%s: Error adding SSI %r to server list' % ( + ",".join([ssi_err_types[err] for err in errors]), new_ssi)) + + try: log.info (','.join(g.name for g in self.ssis.root_group)) + except: log.error('error repr-ing groups') + + self.ssis.root_group.notify() + callback.success(new_ssi) + + @gen_sequence + @callsback + def add_privacy_record(self, buddy, type_, callback = None): + """ + Adds a buddy to your blocklist. + + buddy can be an OscarBuddy object or a string screenname. + """ + me = (yield None) + name = common.get_bname(buddy) + + with self.ssi_edit(): + if not self.find(lambda s: _lowerstrip(s.name) == _lowerstrip(name), type=type_): + log.critical("adding " + name + " to your privacy list") + buddy_ssi = item(name, 0 , self.new_ssi_item_id(0), + type_) + buddy_errs = (yield self._add_ssi(buddy_ssi, me())) + log.critical("ACK PRIVACY MOD!" + name) + if not buddy_errs[0]: + self.ssis[buddy_ssi] = buddy_ssi + #self.o.buddies[name].setnotify('status', 'offline') + else: + callback.error() + raise SSIException("Error adding buddy to privacy list. " + + ",".join([ssi_err_types[err] for err in buddy_errs])) + else: + callback.error() + raise SSIException("Buddy already in that privacy list.") + + callback.success() + self.ssis.root_group.notify() + + @callsback + def block_buddy(self, buddy, callback = None): + self.add_privacy_record(buddy, oscar.ssi.deny_flag, callback = callback) + + def allow_buddy(self, buddy): + self.add_privacy_record(buddy, oscar.ssi.permit_flag) + + + @callsback + def ignore_buddy(self, buddy, callback=None): + self.add_privacy_record(buddy, 0xe, callback=callback) + + @callsback + def unignore_buddy(self, buddy, callback=None): + self.remove_privacy_record(buddy, type_=0xe, callback = callback) + + @gen_sequence + @callsback + def remove_privacy_record(self, buddy, type_, callback = None): + """ + Remove the specified buddy from your block list. + """ + me = (yield None) + name = _lowerstrip(common.get_bname(buddy)) + + buds_matching = self.find(name=name, type=type_) + errors = [] + if buds_matching: + with self.ssi_edit(): + log.critical("REMOVING PRIVACY RECORD FOR " + name) + self._remove_ssi(buds_matching, me()) + errors = (yield None) + + for err, ssi in zip(errors, buds_matching): + if not err: + del self.ssis[ssi] + + real_errors = filter(None, errors) + if not buds_matching: + log.critical("Can't remove privacy record; no ssi in root group for %r (type = %r).", name, type_) + if real_errors: + callback.error() + raise SSIException('Problem removing privacy ssi for %s.' % name + + ",".join([ssi_err_types[err] for err in errors]) ) + + # On success, notify buddy listeners. + callback.success() + self.o.buddies[name].notify('blocked') + self.ssis.root_group.notify() + + @callsback + def unblock_buddy(self, buddy, callback = None): + self.remove_privacy_record(buddy, oscar.ssi.deny_flag, callback = callback) + + def unallow_buddy(self, buddy): + self.remove_privacy_record(buddy, oscar.ssi.permit_flag) + +# @gen_sequence +# def ignore_buddy(self, buddy): +# me = (yield None) +# name = common.get_bname(buddy) +# with self.ssi_edit(): +# [self.remove_buddy_ssi(ssi) for ssi in self.find(type=0, name=buddy)] +# new_ssi = item(name, 0, self.new_ssi_item_id(0), type_=0x0e) +# errors = (yield self._add_ssi(new_ssi, me)) +# if errors[0]: +# raise SSIException("Error adding %s to ignore list" % name) + + + def get_privacy_ssi(self): + # search for PDINFO ssi items + PDINFO = 0x04 + privacy_infos = [s for s in self.ssis.values() if s.type == PDINFO] + + # if there's no privacy entry + if len(privacy_infos) == 0: + # Add one, with "block list" enabled + pinfo_ssi = item('', 0, self.new_ssi_item_id(0), PDINFO) + elif len(privacy_infos) == 1: + # there's already one--modify it to include "block list" + pinfo_ssi = privacy_infos[0] + else: + log.critical("There was more than one privacy SSI:") + log.critical(str(privacy_infos)) + raise SSIException("There was more than one privacy SSI:") + + return pinfo_ssi + + def blocklist(self): + """ + Returns a list of stripped buddy names which are blocked. + + This list is defined as any SSI item in group 0 (root) with an item type + of 3 (deny). + """ + return [s.name.lower().replace(' ','') + for s in self.ssis.values() + if s.group_id == 0 and s.type == 3] + + def ignorelist(self): + return [s.name.lower().replace(' ','') + for s in self.ssis.values() + if s.group_id == 0 and s.type == 0xe] + + def find(self, f=lambda x:True, **kwds): + results = [] + for ssi in self.ssis.values(): + for kwd in kwds: + if kwd == "name": + if _lowerstrip(getattr(ssi, kwd, sentinel)) != _lowerstrip(kwds[kwd]): + break + elif getattr(ssi, kwd, sentinel) != kwds[kwd]: + break + else: + if f(ssi): results.append(ssi) + + return results + + @gen_sequence + def _add_to_group(self, group_ids, id_to_add, position): + me = (yield None) + try: + groupclone = self.ssis[group_ids].clone() + except KeyError: + raise SSIException("Could not find SSI with group_id == %r", group_ids) + groupclone.add_item_to_group(id_to_add, position) + errors = (yield self._modify_ssi(groupclone, me())) + if errors[0] == 0x0000: + self.ssis[group_ids].add_item_to_group(id_to_add, position) + else: + raise SSIException('Error adding item to group: '+ + ", ".join([ssi_err_types[err] for err in errors])) + self.ssis.root_group.notify() + + @gen_sequence + def _remove_from_group(self, key): + me = (yield None) + group_id, item_id = tuple_key(key) + log.info('removing (%d, %d)', group_id, item_id) + #if it's a set of group ids we got, flip them, because then the + #rest of the code is identical + if not item_id: + group_id, item_id = item_id, group_id + group_clone = self.ssis[(group_id, 0)].clone() + group_clone.remove_item_from_group(item_id) + error = (yield self._modify_ssi(group_clone, me())) + if not error[0]: + self.ssis[(group_id, 0)].remove_item_from_group(item_id) + else: + raise SSIException('Error removing item from group: '+ + ",".join([ssi_err_types[err] for err in error])) + self.ssis.root_group.notify() + + @gen_sequence + def remove_group(self, group_protocol_object): + me = (yield None) + group_protocol_object = getattr(group_protocol_object, 'id', group_protocol_object) + ssis_to_del = self.get_ssis_in_group(group_protocol_object)#[0]) + log.info('Going to remove: %r', ssis_to_del) + group_to_del = self.ssis[tuple_key(group_protocol_object)] + groupclone = group_to_del.clone() + groupclone.tlvs = {} + ssis_to_del.append(groupclone) + with self.ssi_edit(): #needed untill group mod is sent out + self._remove_ssi(ssis_to_del, me()) + errors = (yield None) + for (ssi, error) in zip(ssis_to_del, errors): + if not error and ssi in self.ssis: del self.ssis[ssi] + if group_protocol_object not in self.ssis: + self._remove_from_group(group_protocol_object) #end with block + real_errors = filter(None, errors) + if real_errors: raise SSIException("Error removing group from list: "+ + ",".join(ssi_err_types[err] for err in real_errors)) + self.ssis.root_group.notify() + + @gen_sequence + @callsback + def remove_buddy_ssi(self, ids, callback = None): + me = (yield None) + with self.ssi_edit(): #needed untill group mod is sent out + buddy_clone = self.ssis[ids].clone() + error = (yield self._remove_ssi(buddy_clone, me())) + if not error[0]: + self._remove_from_group(ids) #end with block + del self.ssis[ids] + callback.success() + else: + callback.error() + raise SSIException("Error removing object from list: "+ + ",".join([ssi_err_types[err] for err in error])) + self.ssis.root_group.notify() + + @gen_sequence + def rename_ssi(self, protocol_object, name): + me = (yield None) + new_ssi = self.ssis[protocol_object].clone() + new_ssi.name = name.encode('utf-8') + errors = (yield self._modify_ssi(new_ssi, me())) + if errors[0]: + raise SSIException("Error renaming object: "+ + ",".join([ssi_err_types[err] for err in errors])) + else: + ssiobj = self.ssis[protocol_object] + ssiobj.name = name.encode('utf-8') + self.ssis.get_group(protocol_object).set_ssi(ssiobj) + self.ssis.root_group.notify() + + @gen_sequence + def alias_ssi(self, contact, name): + me = (yield None) + buddy, id = contact.buddy, contact.id + new_ssi = self.ssis[id].clone() + + name = name.encode('utf-8') if name else None + new_ssi.set_alias(name) # accepts None to delete + + errors = (yield self._modify_ssi(new_ssi, me())) + if errors[0]: + raise SSIException("Error setting alias: " + + ",".join(ssi_err_types[err] for err in errors)) + else: + self.ssis[id].set_alias(name) + + self.ssis.root_group.notify() + + + @gen_sequence + @callsback + def move_ssi_to_position(self, item_ids, position, group_to_ids=None, callback = None): + me = (yield None) + + # If we are passed numbers for groups, turn them into group tuples. + item_ids = tuple_key(item_ids) + if group_to_ids: + group_to_ids = tuple_key(group_to_ids) + if group_to_ids[1]: + raise SSIException("Can't move items into something which is " + "not a group.") + + if not item_ids[0]: + #if group == root group + raise AssertionError("Atttempted to move something in the " + + "SSI root group (this is impossible, " + + "since they don't have position).") + elif not item_ids[1]: + # moving a group + group_from_ids = (0,0) + if group_to_ids and group_to_ids != (0,0): + raise SSIException("Can't move group into a group which is " + "not the root group.") + id_to_move = item_ids[0] + else: + # moving a buddy + group_from_ids = (item_ids[0],0) + id_to_move = item_ids[1] + if not group_to_ids or group_from_ids == group_to_ids: + #move within group/move a group within root group + groupclone = self.ssis[group_from_ids].clone() + groupclone.move_item_to_position(id_to_move, position) + errors = (yield self._modify_ssi(groupclone, me())) + if not errors[0]: + self.ssis[group_from_ids]. \ + move_item_to_position(id_to_move, position) + else: + raise SSIException('Error moving item: '+ + ",".join([ssi_err_types[err] for err in errors])) + else: + #moving between groups + del id_to_move + if not group_to_ids[0]: + #if there is a group to go to, make sure it's not the root group + raise AssertionError("atttempted to move something to the " + + "SSI root group (this is impossible, " + + "since they don't have position)") + else: + # valid from, valid to + # do crazy delete/add/modify x2 here + old_ssi = self.ssis[item_ids] + new_ssi = old_ssi.clone() + new_ssi.group_id = group_to_ids[0] + new_ssi.item_id = self.new_ssi_item_id(group_to_ids[0]) + with self.ssi_edit(): #needed untill last group mod is sent out + del_errors, action1, add_errors, action2 = \ + (yield self._ssi_double_action( + 0x0a, old_ssi, 0x08, new_ssi, me())) + if action1 != 0x0a: + del_errors, add_errors = add_errors, del_errors + + del_group_clone, add_group_clone = None, None + + if not del_errors[0]: + del self.ssis[old_ssi] + del_group_clone = self.ssis[(old_ssi.group_id,0)].clone() + del_group_clone.remove_item_from_group(old_ssi.item_id) + if not add_errors[0]: + self.ssis[new_ssi] = new_ssi + add_group_clone = self.ssis[(new_ssi.group_id,0)].clone() + add_group_clone.add_item_to_group(new_ssi.item_id, position) + mod_ssis = filter( None, [del_group_clone, add_group_clone]) + self._modify_ssi(mod_ssis, me()) #end with block + mod_errors = (yield None) + + del_mod_error = None + add_mod_error = None + if not del_errors[0]: + del_mod_error = mod_errors[0] + mod_errors = mod_errors[1:] + if not del_mod_error: + self.ssis[(old_ssi.group_id,0)] \ + .remove_item_from_group(old_ssi.item_id) + if not add_errors[0]: + add_mod_error = mod_errors[0] + if not add_mod_error: + self.ssis[(new_ssi.group_id,0)] \ + .add_item_to_group(new_ssi.item_id, position) + + # error handling + + errors = filter(None, (add_errors[0], del_errors[0], del_mod_error, add_mod_error)) + + if errors: + err_string = '' + if del_errors[0]: + if err_string: err_string += ", " + err_string += "deleting " + old_ssi.name + " in group " + \ + self.ssis[(old_ssi.group_id,0)].name + if del_mod_error: + if err_string: err_string += ", " + err_string += "removing " + old_ssi.name + " from group " + \ + self.ssis[(old_ssi.group_id,0)].name + " list" + if add_errors[0]: + if err_string: err_string += ", " + err_string += "adding " + old_ssi.name + " in group " + \ + self.ssis[(new_ssi.group_id,0)].name + if add_mod_error: + if err_string: err_string += ", " + err_string += "adding " + old_ssi.name + " to group " + \ + self.ssis[(new_ssi.group_id,0)].name + " list" + + callback.error() + raise SSIException("ERROR %s: %r" % (err_string, + ",".join([ssi_err_types[err] for err in errors]))) + + #from util import Timer + #Timer(1, callback.success).start() + callback.success((new_ssi.group_id, new_ssi.item_id)) + self.ssis.root_group.notify() + + + def _edit_server_list_start(self, import_transaction=False): + ''' + start editing SSIs! + ''' + if not self.ssi_edits_out: + self.o.send_snac(*snac.send_x13_x11(import_transaction)) + self.ssi_edits_out += 1 + + def _edit_server_list_end(self): + ''' + done editing SSIs + ''' + self.ssi_edits_out -= 1 + if not self.ssi_edits_out: + self.o.send_snac(*snac.send_x13_x12()) + + @contextmanager + def ssi_edit(self, import_transaction=False): + self._edit_server_list_start(import_transaction) + try: + yield self + finally: + self._edit_server_list_end() diff --git a/digsby/src/oscar/ssi/SSIviewer.py b/digsby/src/oscar/ssi/SSIviewer.py new file mode 100644 index 0000000..b3c2d4b --- /dev/null +++ b/digsby/src/oscar/ssi/SSIviewer.py @@ -0,0 +1,137 @@ +import wx +import string +import time +import struct +import oscar.ssi +import gui + +class SSIViewer(wx.Frame): + def __init__(self, o, parent, id = -1): + print "new ssiViewer" + self.o = o + wx.Frame.__init__(self, parent, id, "SSIs") + gui.toolbox.persist_window_pos(self, close_method=self.on_close) + self.content = wx.BoxSizer(wx.VERTICAL) + + #generate new tree control + tree_id = wx.NewId() + self.tree = wx.TreeCtrl(self, tree_id) + self.content.Add(self.tree,1,wx.GROW) + + #populate tree with root of tree + rootId = self.tree.AddRoot("Root") + #get groups (not including root group + rootgroup = self.o.ssimanager.ssis[(0,0)] + + groups = [] + #sort groups by data in TLV 0xc8 in root gropu + if rootgroup.tlvs and 0xc8 in rootgroup.tlvs: + for group_id in rootgroup.tlvs[0xc8]: + groups.append(self.o.ssimanager.ssis[(group_id,0)]) + rootMemberId = self.tree.AppendItem(rootId, rootgroup.name) + #add tlvs to root group item (treeroot->rootitem->tlvs) + if rootgroup.tlvs: + for key in rootgroup.tlvs.keys(): + self.tree.AppendItem(rootMemberId, printTLV(key,rootgroup.tlvs[key])) + #populate stuff in root (treeroot->rootitem->items) + self.addrootgroup(rootMemberId) + #add groups (treeroot->groups) + for group in groups: + groupitem_id = self.tree.AppendItem(rootId, "Name: \"" + group.name + "\" Group: 0x%04X" % group.group_id) + #populate group (treeroot->group->items) + self.addgroup(group,groupitem_id) + + self.SetSizer(self.content); + + def addgroup(self,group,treeId): + ssilist = [] + if group.tlvs and 0xc8 in group.tlvs: + for short in group.tlvs[0xc8]: + ssilist.append(self.o.ssimanager.ssis[(group.group_id, short)]) + #add items in order (treeroot->group->items) + for member in ssilist: + memberId = self.tree.AppendItem(treeId, "Name: \"" + member.name + "\" " + + " Item #: 0x%04X" % member.item_id) + #populate tlvs for this item (treeroot->group->item->tlvs) + if member.tlvs: + for key in member.tlvs.keys(): + self.tree.AppendItem(memberId, printTLV(key,member.tlvs[key])) + + def addrootgroup(self,treeId): + #get the junk in the root group + ssilist = self.o.ssimanager.get_ssis_in_group(0) + for member in ssilist: + member = member.clone() + #add items (treeroot->rootitem->items) + if member.type in oscar.ssi.buddy_types: + type = oscar.ssi.buddy_types[member.type] + else: + type = "Unknown flag type 0x%04X" % member.type + memberId = self.tree.AppendItem(treeId, "Name: \"" + member.name + "\" " + + " Item #: 0x%04X" % member.item_id + + " Type: " + type + ) + if member.tlvs: + #add tlvs for this item (treeroot->rootitem->item->tlvs) + for key in member.tlvs.keys(): + try: + self.tree.AppendItem(memberId, printTLV(key,member.tlvs[key])) + except Exception: + print 'couldnt add tlv to tree: %r' % ((key, member.tlvs[key]),) + + def on_close(self, e): + self.Destroy() + +def printTLV(tlvtype, tlvdata): + import util + tlvstring = "0x%04X" % tlvtype + ": " + if tlvtype == 0x006D: + (dateint,) = struct.unpack("!I", tlvdata[:4]) + tlvstring = tlvstring + '"' + time.asctime(time.localtime(dateint)) + '" + ' + tlvdata = tlvdata[4:] + if tlvtype in (0x0067, 0x154, 0x0160, 0x01C3, 0x01C4, 0x01C5): + (dateint,) = struct.unpack("!I", tlvdata) + tlvstring = tlvstring + '"' + time.asctime(time.localtime(dateint)) + '"' + tlvdata = None + if tlvtype == 0x006E: + pass + if tlvtype == 0x00C8: + for short in tlvdata: + tlvstring += "%04X" % short + " " + tlvstring = tlvstring[:-1] + tlvdata = None + if tlvtype == 0x0131: + tlvstring += "Alias: \"" + return_printable(tlvdata) + tlvdata = None + if tlvtype == 0x0137: + tlvstring += "mail: \"" + return_printable(tlvdata) + tlvdata = None + if tlvtype == 0x0137: + tlvstring += "SMS: \"" + return_printable(tlvdata) + tlvdata = None + if tlvtype == 0x013C: + tlvstring += "Comment: \"" + return_printable(tlvdata) + tlvdata = None + if tlvtype == 0x014B: + tlvstring += "Metadata: " + repr(tlvdata) + tlvdata = None + if tlvtype == 0x0D5: + pass + if tlvdata: + try: + tlvstring += '"' + util.to_hex(tlvdata) + '"' + except Exception: +# import traceback;traceback.print_exc() + print tlvtype, tlvdata + raise + return tlvstring + +def return_printable(string_): + retval = "" + if not string_: return '' + for char in string_: + if char in string.printable: + retval += char + else: + retval += "." + return retval + "\"" diff --git a/digsby/src/oscar/ssi/__init__.py b/digsby/src/oscar/ssi/__init__.py new file mode 100644 index 0000000..8a4fd27 --- /dev/null +++ b/digsby/src/oscar/ssi/__init__.py @@ -0,0 +1,83 @@ +import oscar + +ssi_types = {0x0000:'Buddy record (name: uin for ICQ and screenname for AIM)', + 0x0001:'Group record', + 0x0002:'Permit record ("Allow" list in AIM, and "Visible" list in ICQ)', + 0x0003:'Deny record ("Block" list in AIM, and "Invisible" list in ICQ)', + 0x0004:'Permit/deny settings or/and bitmask of the AIM classes', + 0x0005:'Presence info (if others can see your idle status, etc)', + 0x0009:'Unknown. ICQ2k shortcut bar items ?', + 0x000E:'Ignore list record.', + 0x000F:'Last update date (name: "LastUpdateDate").', + 0x0010:'Non-ICQ contact (to send SMS). Name: 1#EXT, 2#EXT, etc', + 0x0013:'Item that contain roster import time (name: "Import time")', + 0x0014:'Own icon (avatar) info. Name is an avatar id number as text', + 0x0017:'Linked screen name order', + 0x0018:'Linked screenname', + 0x001c:'Facebook group?', + } + +ssi_tlv_types = {0x00C8:'Group Header', + 0x00CA:'Privacy Settings', + 0x00CB:'bitmask of visibility to user classes', + 0x00CC:'bitmask of what other users can see', + 0x0131:'Alias', + 0x0137:'email address', + 0x013A:'SMS number', + 0x013C:'buddy comment', + 0x013D:'Alert settings', + 0x013E:'Alert sound' + } + +buddy_types = {0x0000:"Buddy", + 0x0001:"Group", + 0x0002:"Allow/Visible", + 0x0003:"Block/InvisibleTo", + 0x0004:"Permit/deny settings", + 0x0005:"Presence info", + 0x0009:"Unknown", + 0x000E:"Ignore list record", + 0x000F:"LastUpdateDate", + 0x0010:"Non-ICQ contact (to send SMS)", + 0x0013:"Roster Import time", + 0x0014:"Self icon info. Name is an avatar id number as text", + 0x0017:'Linked screen name order', + 0x0018:'Linked screenname', + 0x0019:'Virtual Contact Metadata', + 0x001C:'Virtual Group Metadata', + } + +ssi_err_types = { + # TODO: (maybe): import official list from http://dev.aol.com/aim/oscar/#FEEDBAG__STATUS_CODES + # except... some of them use wording like "Some kind of database error". great. + 0x0000:"No errors (success)", + 0x0001:'Unknown error (0x01)', # Dont know what this is, got it on release-eve + 0x0002:"Item you want to modify not found in list", + 0x0003:"Item you want to add already exists", + 0x000A:"Error adding item (invalid id, already in list, invalid data)", + 0x000C:"Can't add item. Limit for this type of items exceeded", + 0x000D:"Trying to add ICQ contact to an AIM list", + 0x000E:"Can't add this contact because it requires authorization", + 0x0010:"Invalid Login ID", + } + +# +# Blocking / Privacy +# + +# Attached to a root group SSI item buddy to indicate it is blocked +deny_flag = 3 +permit_flag = 2 + +# types for privacy settings +privacy_info_types = { "all":1, "invisible":2, "permit":3, "block":4 } + +# TLV in SSI for privacy settings +tlv_privacy_settings = 0x0ca + +class SSIException(oscar.OscarException): pass + +from oscar.ssi.SSIItem import SSIItem as item +from oscar.ssi.SSIItem import OscarSSIs as OscarSSIs +from oscar.ssi.SSIManager import SSIManager as manager +from oscar.ssi.SSIviewer import SSIViewer as viewer diff --git a/digsby/src/plugin_manager/__init__.py b/digsby/src/plugin_manager/__init__.py new file mode 100644 index 0000000..36c820f --- /dev/null +++ b/digsby/src/plugin_manager/__init__.py @@ -0,0 +1 @@ +from plugin_registry import scan diff --git a/digsby/src/plugin_manager/plugin_hub.py b/digsby/src/plugin_manager/plugin_hub.py new file mode 100644 index 0000000..1ee22cd --- /dev/null +++ b/digsby/src/plugin_manager/plugin_hub.py @@ -0,0 +1,124 @@ +from peak.util.plugins import Hook +from util.threads import threaded +from logging import getLogger; _log = log = getLogger('plugin_hub') +import traceback + +# call each hook method async +def asyn(identifier, *args, **kwargs): + threaded(syn)(identifier, *args, **kwargs) + +# call each method sync +def syn(identifier, *args, **kwargs): + ret = True + + for hook in Hook(identifier): + #_log.info('HOOK: %s',repr(hook)) + try: + ret = hook(*args, **kwargs) + if not ret: + ret = False + except Exception: + traceback.print_exc() + return ret + + + #return all(try_this(lambda: hook(*args, **kwargs), False) for hook in Hook(identifier)) + +# call to a hook with the specified ID and args +def act(id, *args, **kwargs): + #_log.info('ACT: %s',id) + #log.info('%s',repr(args)) + + + if id.endswith('.async'): + asyn(id, *args, **kwargs) + else: + ret = syn(id, *args, **kwargs) + + #_log.info('%s',repr(ret)) + return ret + + +hooks = \ +''' +im: + filetransfer: syn, buddy, file, state + alert: + pre: syn, proto, type, param + msg: + pre: syn, message, type + async: syn, msg, type + my_status_change: + pre: syn, msg + async: syn, msg + setprofile: + pre: syn, proto, profile + async: syn, proto, profile + status_change: + pre: syn, proto, buddy + async: syn, proto, buddy + info: + pre: syn, proto, buddy, info + async: syn, proto, buddy, info + conversation: + start: + pre: syn, proto, buddy + async: syn, proto, buddy + end: + async: syn, proto, buddy + addcontact: + pre: syn, proto, name, alias + async: syn, proto, name, alias + addaccount: + async: syn, proto_name, acct_name +updateaccount: + async: syn, account +social: + addaccount: + async: syn, proto_name, acct_name + alert: syn, proto, type, msg +protocol: + statechange: + async: syn, state +plugin: + load: + async: syn + unload: + async: syn +goidle: + pre: syn + async: syn +unidle: + pre: syn + async: syn +email: + newmail: + pre: syn, proto, address, count, msg + async: syn, proto, address, count, msg + addaccount: + async: syn, proto_name, acct_name +popup: + pre: syn +''' + +functemp = \ +'''def %(func_name)s(%(def_args)s): + return %(type)s('%(func_id)s'%(func_args)s) +''' + +from prefs import flatten +from syck import load +funcs = flatten(load(hooks)) + +for func_id, args in funcs: + func_id = 'digsby.' + func_id + args = args.split(', ') + type, func_args = args[0], args[1:] + def_args = ', '.join(func_args) + if func_args: + func_args = ', ' + def_args + else: + func_args = '' + func_name = func_id.replace('.', '_') + exec(functemp % locals(), globals(), locals()) + diff --git a/digsby/src/plugin_manager/plugin_registry.py b/digsby/src/plugin_manager/plugin_registry.py new file mode 100644 index 0000000..54a9314 --- /dev/null +++ b/digsby/src/plugin_manager/plugin_registry.py @@ -0,0 +1,393 @@ +''' +Tools to scan for, find, register, and setup plugins. +''' + +from __future__ import with_statement +import traceback +import logging +import config + +import util.primitives +import util.data_importer as importers +from contextlib import closing +from util.data_importer import zipopen +from babel.messages.pofile import read_po +from babel.messages.mofile import write_mo +from StringIO import StringIO +import peak.util.plugins as Plugins + +from path import path + +log = logging.getLogger('plugin_registry') + +METAINFO_FILENAME = 'info.yaml' + +type_handlers = {} + +def register_plugin_default(name, metainfo): + ''' + Default handler for a discovered plugin. + ''' + log.error('Not registering type %r because of unknown "type" key (%r). its metainfo is: %r', name, metainfo.get('type', None), metainfo) + +def register_type_handler(typename, handler): + ''' + Sets 'handler' as the callable for plugins with type == typename. 'handler' should be a callable + that takes two arguments, (str name, mapping metainfo) + ''' + old_handler = type_handlers.get(typename, None) + if old_handler is not None: + log.warning('Overwriting handler for type %r: was %r, now %r', typename, old_handler, handler) + + type_handlers[typename] = handler + +def get_type_handler(typename): + ''' + Returns the handler for 'typename', or the default handler if none is registerd. + ''' + try: + return type_handlers[typename] + except KeyError: + return register_plugin_default + +def type_handler(typename): + ''' + Decorator to mark a function as the type handler for 'typename' + ''' + def register_handler(f): + register_type_handler(typename, f) + return f + return register_handler + +def register_type(name, metainfo): + ''' + str name: name of the plugin, ex: 'oscar', 'nowplaying' + mapping metainfo: information about the plugin, ex: {'type' : 'im', 'shortname' : 'aim'} + ''' + # TODO: move protocolmeta.protocols to this module and get rid of protolmeta? + # use this as a central hub for all protocol meta info. + # XXX: what should we do with functions like is_compatible and things like that from + # protocolmeta? + + a_type = metainfo.get('type', None) + if a_type is None: + log.error('Not registering type %r because it did not have a "type" key. its metainfo is: %r', name, metainfo) + return + else: + platforms = metainfo.get('platforms', None) + if platforms is None or config.platformName in platforms: + PluginType = get_type_handler(a_type) + + # TODO: store these things somewhere? + plugin = PluginType(metainfo.__file__.parent, metainfo) + plugin.init() + + return plugin + +class PluginLoader(object): + def __init__(self, dirpath, metainfo): + self.path = dirpath + self.shortname = metainfo.get('shortname', self.path.name) + self.info = metainfo + + + def init(self): + self.name = self.info.name + + self.init_skin() + self.init_notifications() + self.init_actions() + + log.info('registered plugin type: %r', self.name) + + def init_skin(self): + # self.skin contains transformed data with paths made relative to the + # plugin's directory, while + # self.info.skin contains the "raw info" from the plugin's metadata. + # + # when looking up skin values, you should really use self.skin + self.skin = self.resolve_paths(self.get_component('skin')) + + def resolve_paths(self, d): + '''recursively transform any string leafs in a mapping into paths + relative to this plugin's directory, if those paths exist.''' + + if not isinstance(d, dict): + return d + ret = {} + for k,v in d.items(): + if isinstance(v, basestring): + pth = self.path / v + if pth.isfile() or pth.isdir(): + v = pth + elif isinstance(v, dict): + v = self.resolve_paths(v) + ret[k] = v + return ret + + def init_actions(self): + actions = self.get_component('actions') + if actions is None: + return + + import common.actions + common.actions.add_actions(actions) + + def init_notifications(self): + nots = self.get_component('notifications') + + if nots is None: + return + + import common.notifications as n + #import gui.skin.skintransform as gst + #nots.notifications = map(lambda x: gst.transform(x, False), nots.notifications) + + defaults = {} + for d in nots: + for k in d: + not_ = d[k] + default = not_.get('default', {}) + default_reactions = [{'reaction' : reaction} for reaction in default.get('reaction', [])] + defaults[k.replace('_','.')] = default_reactions + + n.add_notifications(nots) + n.add_default_notifications(defaults) + self.set_component('notifications', nots) + + def get_component(self, attr, yamlname = None): + attempts = [ + lambda: getattr(self, attr, None), + lambda: getattr(self.info, attr, None), + lambda: getattr(self.yaml_load(yamlname or attr), '__content__', None), + ] + + for attempt in attempts: + thing = attempt() + if thing is not None: + break + else: + return None + + self.set_component(attr, thing) + return thing + + def set_component(self, attr, thing): + setattr(self, attr, thing) + setattr(self.info, attr, thing) + + def yaml_load(self, yamlname): + try: + return importers.yaml_import(yamlname, loadpath = [self.path]) + except ImportError: + return None + + def load_entry_points(self): + """Connects plugin entry points defined in info.yaml""" + + import pkg_resources + from .plugin_resources import PluginDistribution + import config as digsbyconfig + platforms = self.get_component('platforms') + if platforms is None or digsbyconfig.platformName in platforms: + pkg_resources.working_set.add(PluginDistribution(location = str(self.path.parent), + project_name = self.info.name, + ep_yaml = self.get_component('entry_points'))) + + def __repr__(self): + return "<%s %r>" % (type(self).__name__, self.name) + +class ProtocolPluginLoader(PluginLoader): + def init(self, dictnames = None): + PluginLoader.init(self) + self.init_info(dictnames) + + def init_info(self, dictnames = None): + import common.protocolmeta as pm + + plugin_info = util.primitives.mapping.Storage(self.info) + + if dictnames is not None: + for dictname in dictnames: + d = getattr(pm, dictname, None) + if d is not None: + d[self.shortname] = plugin_info + + pm.protocols[self.shortname] = plugin_info + +@type_handler('im') +class IMProtocolPluginLoader(ProtocolPluginLoader): + def init(self): + return ProtocolPluginLoader.init(self, ['improtocols']) + +@type_handler('email') +class EmailProtocolPluginLoader(ProtocolPluginLoader): + def init(self): + return ProtocolPluginLoader.init(self, ['emailprotocols']) + +@type_handler('social') +class SocialProtocolPluginLoader(ProtocolPluginLoader): + def init(self): + result = ProtocolPluginLoader.init(self, ['socialprotocols']) + self.load_entry_points() + return result + +@type_handler('meta') +class MetaProtocolPluginLoader(ProtocolPluginLoader): + def init(self): + return ProtocolPluginLoader.init(self) + +@type_handler('platform') +@type_handler('pure') +class PurePluginLoader(PluginLoader): + def init(self): + self.load_entry_points() + super(PurePluginLoader, self).init() + +@type_handler('multi') +class MultiPluginLoader(PurePluginLoader): + def init(self): + self.plugins = [] + plugin_dicts = self.info['plugins'] + for name, plugin in plugin_dicts.items(): + plugin_obj = register_type(name, plugin) + if plugin_obj is not None: + self.plugins.append(plugin_obj) + super(MultiPluginLoader, self).init() + + +@type_handler("service_provider") +class ServiceProviderPlugin(PurePluginLoader): + def init(self): + res = super(ServiceProviderPlugin, self).init() + self.type = self.info.type + self.name = self.info.name + self.provider_id = self.info.provider_id + + return res + +@type_handler("service_component") +class ServiceComponentPlugin(ProtocolPluginLoader): + def init(self): + pm_key = { + 'social' : 'socialprotocols', + 'email' : 'emailprotocols', + 'im' : 'improtocols', + }.get(self.info.component_type, None) + + dictnames = [] + if pm_key is not None: + dictnames.append(pm_key) + result = ProtocolPluginLoader.init(self, dictnames) + self.load_entry_points() + self.service_provider = self.provider_id = self.info.service_provider + self.component_type = self.info.component_type + return result + +@type_handler('lang') +class LangPluginLoader(PurePluginLoader): + def init(self): + super(LangPluginLoader, self).init() +# + def get_catalog(self): + if self.info.get('catalog_format') == 'po': + return self.get_po_catalog() + return self.get_mo_catalog() + + @property + def file_base(self): + return self.path / (self.info['domain'] + '-' + self.info['language']) + + def get_mo_catalog(self): + with closing(zipopen(self.file_base + '.mo')) as f: + return StringIO(f.read()) + + def get_po_catalog(self): + cat = StringIO() + with closing(zipopen(self.file_base + '.po')) as f: + po = read_po(f) + write_mo(cat, po) + cat.seek(0) + return cat + +pkg_dirs = set() + +def scan(dirname): + ''' + Search 'dirname' for .zip, .egg, or directories with an info.yaml. + for each found, loads the plugin info and calls register_type with + the discovered name and information. + ''' + root = path(dirname) + plugins = [] + for pkg_dir in root.dirs() + root.files('*.zip') + root.files('*.egg'): + name = pkg_dir.namebase + if exclude_dir(name): + continue + +# sys.path.append(pkg_dir) + try: + plugin = _load_plugin_info_from_item(pkg_dir) + + if plugin is None: + # sys.path.remove(pkg_dir) + log.info("No protocol info found in %r", pkg_dir) + else: + pkg_dirs.add(pkg_dir.abspath()) + ret = register_type(name, plugin) + if ret is None: + continue + + plugins.append(ret) + if hasattr(ret, 'plugins'): + plugins.extend(ret.plugins) + except Exception: + traceback.print_exc() + + return plugins + +def exclude_dir(dirname): + return dirname.startswith('.') + +def _load_plugin_info_from_item(pkgdir): + ''' + Tries really hard to load the plugin info from the specified item. + ''' + + try: + return importers.yaml_import('info', loadpath = [pkgdir]) + except ImportError: + try: + return importers.yaml_load(pkgdir / 'info.yaml') + except Exception: + return None + +def plugins_skintrees(): + 'All info.yamls can define a "skin:" section that gets merged with the skin tree.' + + import wx + plugins = wx.GetApp().plugins + + trees = [] + for plugin in plugins: + skin = plugin.get_component('skin') or None + if skin is not None: + trees.append(skin) + + return trees + +def plugins_skinpaths(): + 'Returns paths images for plugins can be loaded from.' + + return list(pkg_dirs) # todo: limit skin lookups for plugins to their own res/ directory + +Plugins.Hook('digsby.skin.load.trees', 'plugins_skin').register(plugins_skintrees) +Plugins.Hook('digsby.skin.load.skinpaths', 'plugins_skin').register(plugins_skinpaths) + +if __name__ == '__main__': + import wx + a = wx.App() + logging.basicConfig() + log.setLevel(1) + scan('c:\\workspace\\digsby\\src\\plugins') + diff --git a/digsby/src/plugin_manager/plugin_resources.py b/digsby/src/plugin_manager/plugin_resources.py new file mode 100644 index 0000000..0482d89 --- /dev/null +++ b/digsby/src/plugin_manager/plugin_resources.py @@ -0,0 +1,20 @@ +from pkg_resources import Distribution, EGG_DIST, PY_MAJOR, EntryPoint + +class PluginDistribution(Distribution): + def __init__(self, + location=None, metadata=None, project_name=None, version=None, + py_version=PY_MAJOR, platform=None, precedence = EGG_DIST, + ep_yaml=None + ): + if ep_yaml is None: + ep_yaml = {} + self._ep_map = {} + + for group, impl in ep_yaml.items(): + i = {} + for im, loc in impl.items(): + ep = EntryPoint.parse('%s=%s' % (im, loc), dist=self) + i[ep.name] = ep + self._ep_map[group] = i + Distribution.__init__(self, location, metadata, project_name, version, py_version, platform, precedence) + diff --git a/digsby/src/plugins/__init__.py b/digsby/src/plugins/__init__.py new file mode 100644 index 0000000..430ff25 --- /dev/null +++ b/digsby/src/plugins/__init__.py @@ -0,0 +1,7 @@ +''' + +Plugins + + +''' +__import__('pkg_resources').declare_namespace(__name__) diff --git a/digsby/src/plugins/component_gmail/__init__.py b/digsby/src/plugins/component_gmail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/component_gmail/gmail.py b/digsby/src/plugins/component_gmail/gmail.py new file mode 100644 index 0000000..f989139 --- /dev/null +++ b/digsby/src/plugins/component_gmail/gmail.py @@ -0,0 +1,556 @@ +''' +A Gmail account. + +Uses the ATOM feed for inbox checking, and negotiates an authorization token +from the server for opening mail in a web browser. +''' + +from __future__ import with_statement + +import re +import time +import urllib2 +import cookielib +import threading + +from threading import Lock +from urllib import urlencode +from datetime import datetime +from traceback import print_exc + +from util.net import UrlQuery, GetDefaultHandlers, WebFormData +from util.threads.timeout_thread import call_later +from util import threaded, scrape_clean, EmailAddress, Storage + +from mail import Email +from common import pref +from common.emailaccount import EmailAccount +from logging import getLogger; log = getLogger('gmail'); info = log.info + +class BadResponse(Exception): + def __nonzero__(self): + return False +class BadPassword(BadResponse): + pass + +class Gmail(EmailAccount): + 'Checks Gmail accounts.' + + protocol = 'gmail' + + baseAuthUrl = 'https://www.google.com' + authUrl = '/accounts/ClientAuth' + tokenUrl = '/accounts/IssueAuthToken' + + messageIdMatcher = re.compile(r'message_id=([a-z0-9]+?)&') + jsredirectMatcher = re.compile('location\.replace\("(.*)"\)') + + default_domain = 'gmail.com' + + def __init__(self, **k): + EmailAccount.__init__(self, **k) + self.internal_token = '' + self.external_token = '' + self.token_lock = threading.RLock() + self.datatoken = '' + self.updated_emails = None + self.updated_count = None + self.update_lock = Lock() + self.emailaddress = EmailAddress(self.name, 'gmail.com') + self._hosted = None + self._multi = None + + if self.emailaddress.domain in ('gmail.com', 'googlemail.com'): + self.baseMailUrl = '://mail.google.com/mail/' + else: + self.baseMailUrl = '://mail.google.com/a/' + self.emailaddress.domain + '/' + + self.browser_http = 'https' + + self.init_jar() + + can_has_preview = True + + def _reset_state(self): + self.init_jar() + self.internal_token = self.external_token = '' + + @property + def browserBaseMailUrl(self): + return self.browser_http + self.baseMailUrl + + @property + def internalBaseMailUrl(self): + return 'https' + self.baseMailUrl + + def init_jar(self): + self.internal_jar = cookielib.CookieJar() + self.internal_http_opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.internal_jar), *GetDefaultHandlers()) + self.internal_http_opener.addheaders = \ + [("Content-type", "application/x-www-form-urlencoded"), + ("Cache-Control", "no-cache")] + + self.external_jar = cookielib.CookieJar() + self.external_http_opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.external_jar), *GetDefaultHandlers()) + self.external_http_opener.addheaders = \ + [("Content-type", "application/x-www-form-urlencoded"), + ("Cache-Control", "no-cache")] + + @property + def inbox_url(self): + return self._external_url(UrlQuery(self.browserBaseMailUrl)) + + def urlForEmail(self, email): + return self._external_url(UrlQuery(self.browserBaseMailUrl + "#all/" + str(email.id))) + + def popup_buttons(self, item): + if not pref('email.popup_actions', default=False): + return [] + + try: + return self._popup_buttons + except AttributeError: + def action(name): + def cb(item): + from common import netcall + netcall(lambda: getattr(self, name)(item.email)) + cb.takes_popup_item = True + cb.disables_button = True + return cb + + self._popup_buttons = [(label, action(actionname)) + for label, actionname in [ + (_('Mark as Read'), 'markAsRead'), + (_('Archive'), 'archive'), + ]] + + return self._popup_buttons + + def markAsRead(self, email): + EmailAccount.markAsRead(self, email) + self._do_action('read', email) + + def archive(self, email): + EmailAccount.archive(self, email) + self._do_action('archive', email) + + if pref('gmail.markive', False): + self.markAsRead(email) + + def delete(self, email): + EmailAccount.delete(self, email) + self._do_action('delete', email) + + def reportSpam(self, email): + EmailAccount.reportSpam(self, email) + self._do_action('spam', email) + + @threaded + def _do_action(self, action, email, tries=0): + try: + self.gmail_at + except KeyError: + self.new_token() + try: + self.gmail_at + except KeyError: + if tries < 3: + log.debug_s('Action %r being retried', action) + return call_later(2, self._do_action, action, email, tries+1) + url, params = self._actionUrl(action, email.id) + response = self.webrequest(url, **params) + log.debug_s('Action %r result: %r', action, response) + + def compose(self, to='', subject='', body='', cc='', bcc=''): +# extra = dict() if not self.hosted else dict(fs='1', view='cm') + extra = dict(fs='1', tf='1', view='cm')#, ui='1')# if not self.hosted else dict() + su = subject + + for name in 'to su body cc bcc'.split(): + if vars()[name]: + extra[name] = vars()[name] + return self._external_url(UrlQuery(self.browserBaseMailUrl, **extra)) + + def _external_url(self, url): + if self.web_login: + self.new_token(internal=False) + if self.web_login and self.external_token: + return UrlQuery('https://www.google.com/accounts/TokenAuth?', + **{'auth':self.external_token, + 'service':'mail', + 'continue':url, + 'source':'googletalk'}) + else: + return url + + def _actionUrl(self, action, message_id): + action_names = dict(archive = 'rc_^i', + delete = 'tr', + read = 'rd', + spam = 'sp', + star = 'st') + + if not action in action_names.values(): + action = action_names[action] + at = self.gmail_at + url = UrlQuery(self.internalBaseMailUrl, + ik='', + search='all', + view='tl', + start='0') + params = dict(act=action, at=at, vp='', msq='', ba='false', + t=message_id, fs='1') + return url, params + + @threaded + def send_email(self, to='', subject='', body='', cc='', bcc=''): + log.info('sending a mail') + data = dict(nvp_bu_send='Send') + for name in 'to subject body cc bcc'.split(): + if vars()[name]: + data[name] = vars()[name].encode('utf-8') + + if not hasattr(self, 'sendpath'): + response = self.internal_http_opener.open(self.internalBaseMailUrl + '?ui=html') + from urllib2 import urlparse + respurl = urlparse.urlparse(response.geturl()) + try: + response.close() + except: pass + del response + self.sendpath = respurl.path + url = 'https://mail.google.com' + self.sendpath + try: + at = self.gmail_at + except KeyError: + at = '' + params = dict(at=at, v='b', pv='tl', s='s', fv='b', cpt='c', cs='c') + if not self.hosted: + params.update(fv='b', cpt='c', cs='c') + else: + params.update(cs='b', s='s') + + url = UrlQuery(url, params) + + response = self.webrequest(url, follow_js_redirects=True, **data) + log.info('sent a mail') + assert response and ('Your message has been sent.' in response) + log.info('send mail success: %r', bool('Your message has been sent.' in response)) + return True + + def _get_notifier_data(self): + return self.webrequest(url = UrlQuery(self.internalBaseMailUrl, ui='pb'), + data = '') + + @property + def hosted(self): + if self._hosted is False: + return False + domain = self.emailaddress.domain + return domain if domain not in ('gmail.com', 'googlemail.com') else None + + + def authenticate(self, task=None): + self.internal_token = token = self.new_token(internal=True) + + if not token: + return False + + webreq_result = self.webrequest(UrlQuery('https://www.google.com/accounts/TokenAuth?', + **{'auth':token, + 'service':'mail', + 'continue':self.internalBaseMailUrl, + 'source':'googletalk'}), internal=True) + try: + self.gmail_at + except Exception: + log.debug('gmail_at failed in authenticate') + if webreq_result: + self.new_token(False) + return True + else: + return webreq_result + + def new_token(self, internal = True): + password = self._decryptedpw() + data = self.webrequest(self.baseAuthUrl + self.authUrl, + internal = internal, + data = WebFormData( + Email = self.name, + Passwd = password, + PersistentCookie='false', + accountType = 'HOSTED_OR_GOOGLE', + skipvpage='true')) + if not data: + return False + + if not data or data.find('Error=badauth') != - 1: + log.warning('Invalid username or password: badauth token') + self.bad_pw() + return False + d1 = dict(b.split('=') for b in data.split()) + d1.update(Session='true',skipvpage='true', service = 'gaia') + token = self.webrequest(self.baseAuthUrl + self.tokenUrl, WebFormData(**d1)) + token = token.strip() + if not internal: + self.external_token = token + + return token + + @property + def gmail_at(self): + hosted = self.hosted + if hosted: + at_path = ('/a/' + hosted) + try: + return self.get_at_path(at_path) + except KeyError: + try: + try: + ret = self.get_at_path('/mail/u/0') + except KeyError: + ret = self.get_at_path('/mail') + else: + self._multi = True + except KeyError: + raise + else: + self._hosted = False + if self._multi: + self.baseMailUrl = '://mail.google.com/mail/u/0/' + else: + self.baseMailUrl = '://mail.google.com/mail/' + return ret + if self._multi: + return self.get_at_path('/mail/u/0') + try: + return self.get_at_path('/mail') + except KeyError: + try: + ret = self.get_at_path('/mail/u/0') + except Exception: + raise + else: + self._multi = True + return ret + + def get_at_path(self, path): + return self.internal_jar._cookies['mail.google.com'][path]['GMAIL_AT'].value + + def update(self): + log.info("update at %s", time.ctime(time.time())) + EmailAccount.update(self) + self.real_update(success = self.finish_update, error=self.on_error) + + def finish_update(self, updates): +# if self.state == self.Statuses.OFFLINE: +# log.error('finish_update exiting early, state is %s', self.state) +# self.on_error(self.update) +# return + if updates is sentinel: + log.warning('two updates were running at the same time') + return + + try: + (updated_emails, updated_count) = updates + except (TypeError, ValueError): + assert updates is None + log.error('Update failed for %s, assuming auth error', self.name) + if self.offline_reason != self.Reasons.BAD_PASSWORD: + self.on_error(self.update) + return + log.info("%s got %d new messages %s", self, updated_count, time.ctime(time.time())) + #self.change_state(self.Statuses.ONLINE) + self._received_emails(updated_emails[:pref('gmail.max_messages', default=25, type=int)], updated_count) + + @threaded + def real_update(self): + #don't change to with, the idea is a non-blocking get on the lock + if self.update_lock.acquire(False): + try: + if not self.internal_token: + info('no auth token yet, authenticating') + if not self.authenticate(): + log.info('auth failed, returning None from real_update') + return None + info('updating Gmail account %r at %s' % (self.name, time.ctime(time.time()))) + notifier_data = self._get_notifier_data() + if isinstance(notifier_data, BadResponse): + log.critical('bad response for notifier_data: %r', notifier_data) + raise notifier_data + + try: + updated_emails, updated_count, _data = \ + parse_datapacks(notifier_data) + updated_emails = chunks_to_emails(updated_emails) + except Exception: + log.critical('could not transform notifier_data: %r', notifier_data) + raise + return updated_emails, updated_count + finally: + self.update_lock.release() + else: + return sentinel + + def webrequest(self, url, data = '', follow_js_redirects = False, internal=True, **kwparams): + http_opener = self.internal_http_opener if internal else self.external_http_opener + try: + response = http_opener.open(url, data + urlencode(kwparams.items())) + + resp = response.read() + + if follow_js_redirects: + match = self.jsredirectMatcher.search(resp) + + if match: + # response may contain a Javascript location.replace="url" type + # redirect that is supposed to work on browsers. follow it! + new_url = match.groups()[0] + response = http_opener.open(self.baseAuthUrl + new_url) + resp = response.read() + return resp + except (urllib2.HTTPError, urllib2.URLError), e: + if getattr(e, 'code', None) == 403: + log.warning('Invalid username or password: HTTP code 403') + self.bad_pw() + return BadPassword() + else: + print_exc() + import sys + print >> sys.stderr, "url: %s" % url + except Exception, e: + print_exc() + + #self.on_error()#e) + + return BadResponse() + +from util.primitives.bits import utf7_to_int as u7i + +def chunk_datapack(data): + assert data[0] == '\n' #first char is \n + data = data[1:] + num, numbytes = u7i(data) #next bit is utf7 number +# print "numbytes", numbytes, repr(data[:numbytes]) + data = data[numbytes:] #take off the number + return data[:num], data[num:] #return value, remainder + +def get_chunk(data): + type_, numbytes = u7i(data) + data = data[numbytes:] + if type_ == 184: #number of participants, value is next number + value, length_ = u7i(data) + elif type_ == 152: #to me/me only, mailing list + value, length_ = u7i(data) + else: + length_, numbytes = u7i(data) #else, assume it has a length + value + data = data[numbytes:] #remove length number +# if type_ == 146: +# length_ += 3 + return type_, data[:length_], data[length_:] #return type, value, remainder + +def get_mid_date(data): + orig_length = len(data) + length_, numbytes = u7i(data) #length of chunk + expected_length = (orig_length - length_) - numbytes + data = data[numbytes:] + msgid, numbytes = u7i(data) #msgid is first number + data = data[numbytes:] + _unknown, numbytes = u7i(data) #mystery byte + data = data[numbytes:] + time_in_ms, numbytes = u7i(data) #next is time in milliseconds + data = data[numbytes:] +# print "len is", len(data), "expected", expected_length + assert len(data) == expected_length + return msgid, time_in_ms, data #msgid, time, remainder + +from collections import defaultdict +def parse_chunk(chunk): + retval = defaultdict(list) + mid, time_in_ms, data = get_mid_date(chunk) + retval["mid"] = mid + retval["time"] = time_in_ms + while data: + t, v, data = get_chunk(data) + if t == 146: #email + v = parse_from(v) + elif t in (152, 184): #personal level, conversation size +# print repr(v) + v = u7i(v)[0] + retval[t].append(v) + return retval + +def chunks_to_emails(dictionaries): + def safe_transform(x): + try: + return dict_to_email(x) + except Exception, e: + log.error('Could not transform this dictionary into an email: %r', x) + raise e + + return filter(bool, map(safe_transform, dictionaries)) + +def parse_datapacks(data): + retval = [] + while data[0] == '\n': #while the next thing is a message + chunk, data = chunk_datapack(data) #get a chunk + retval.append(parse_chunk(chunk)) + num_messages = 0 + type_, numbytes = u7i(data) #the thing after the last msg is usually num msgs + data = data[numbytes:] + if type_ == 136: #num_messages + num_messages, numbytes = u7i(data) + data = data[numbytes:] + return retval, num_messages, data + +def parse_from(from_): + retval = {} + assert from_[0] == "\n" #first byte is \n + from_ = from_[1:] + retval['mystery_bytes1'], from_ = from_.split("\n", 1) #something, and then \n +# retval['mystery_bytes1'] = ord(retval['mystery_bytes1']) #seems to be a number + length_, numbytes = u7i(from_) #length of address + from_ = from_[numbytes:] + retval['email_addr'], from_ = from_[:length_], from_[length_:] #address is next + type_, numbytes = u7i(from_) + if type_ == 0x12: #text version of from, i.e. the name + from_ = from_[numbytes:] + length_, numbytes = u7i(from_) + from_ = from_[numbytes:] + retval['from_text'], from_ = from_[:length_], from_[length_:] + retval['remainder_bytes'] = from_ + return retval + +# email text comes in as UTF-8, and +# scrape_clean replaces HTML/unicode entities with their correct characters +decode = lambda s: scrape_clean(s.decode('utf-8')) + + +class GMAIL(object): + '''Constants used to identify parts of messages.''' + LABELS = 130 + AUTHOR = 146 + PERSONAL_LEVEL = 152 + SUBJECT = 162 + SNIPPET = 170 + ATTACHMENTS = 178 + NUM_MSGS_IN_THREAD = 184 + +def dict_to_email(d): + msgid = d['mid'] + author_email = d[GMAIL.AUTHOR][-1]['email_addr'] + + author_name = decode(d[GMAIL.AUTHOR][-1].get('from_text', '')) + subject = decode(d[GMAIL.SUBJECT][-1]) + snippet = decode(d[GMAIL.SNIPPET][-1]) + attachments = [Storage(name = a) for a in d[GMAIL.ATTACHMENTS]] + labels = [decode(l) for l in d[GMAIL.LABELS] if not l.startswith('^')] + + return Email(id = ('%x' % msgid), + fromname = author_name, + fromemail = author_email, + sendtime = datetime.fromtimestamp(d["time"]//1000), + subject = subject, + content = snippet, + attachments = attachments, + labels = labels) diff --git a/digsby/src/plugins/component_gmail/gmail_ui.py b/digsby/src/plugins/component_gmail/gmail_ui.py new file mode 100644 index 0000000..d16a34f --- /dev/null +++ b/digsby/src/plugins/component_gmail/gmail_ui.py @@ -0,0 +1,2 @@ +def construct_basic_subpanel(parent, MSP, MSC): + return None diff --git a/digsby/src/plugins/component_gmail/info.yaml b/digsby/src/plugins/component_gmail/info.yaml new file mode 100644 index 0000000..9cf4fe9 --- /dev/null +++ b/digsby/src/plugins/component_gmail/info.yaml @@ -0,0 +1,22 @@ +service_provider: &service_provider google +path: component_gmail.gmail.Gmail +name: !_ "Gmail" +shortname: gmail +name_truncated: "gmai" +popularity: 1000 +username_desc: !_ "Gmail Account" +form: email +type: service_component +component_type: email +newuser_url: "http://mail.google.com/mail/signup" +password_url: "https://www.google.com/accounts/ForgotPasswd?service=mail" +needs_webclient: no + +defaults: + updatefreq: 300 + +whitelist_opts: ["enabled", "updatefreq", "alias"] + +entry_points: + digsby.component.email: + *service_provider: "component_gmail.gmail:Gmail" diff --git a/digsby/src/plugins/component_gtalk/__init__.py b/digsby/src/plugins/component_gtalk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/component_gtalk/gtalk.py b/digsby/src/plugins/component_gtalk/gtalk.py new file mode 100644 index 0000000..bb5b1f8 --- /dev/null +++ b/digsby/src/plugins/component_gtalk/gtalk.py @@ -0,0 +1,191 @@ +from pyxmpp.jid import JID +from jabber.objects.shared_status.status import SharedStatus, SHARED_STATUS_NS +import jabber +from logging import getLogger +from common.actions import action +from .gtalkStream import GoogleTalkStream + +import util.callbacks as callbacks +import uuid +from common import pref + +log = getLogger('gtalk.protocol') + +GTALK_SMS = False + +status_state_map = { + 'available': 'normal', + 'free for chat': 'normal', + 'do not disturb': 'dnd', +} + +GTALK_WAY = True + +from jabber import jbuddy +class GtalkBuddy(jbuddy): + sortservice = 'gtalk' + +class GoogleTalk(jabber.protocol): + name = 'gtalk' + service = 'gtalk' + + status_state_map = status_state_map + + supports_group_chat = True + + buddy_class = GtalkBuddy + + def __init__(self, username, *a, **k): + self._idle = None + self._show = None + if '@' not in username: + username += '@gmail.com' + jabber.protocol.__init__(self, username, *a, **k) + self.stream_class = GoogleTalkStream + self._invisible = False + + @jabber.protocol.invisible.getter + def invisible(self): + return self._invisible + + @invisible.setter + def invisible(self, value): + self._invisible = value + + if not GTALK_WAY: + def set_idle(self, yes): + if yes: + self._idle = self.show + self.show = 'away' + else: + self.show = self._idle + self.presence_push() + + def set_message(self, message, status, format=None): + log.info('set_message(%s): %r', status, message) + + state = status_state_map.get(status.lower(), 'dnd') + + # no tag means normal + self.show = state if state != 'normal' else None + +# self.invisible = status.lower() == 'Invisible'.lower() + + self._idle = self.show + self.status = message + self.presence_push() + else: + def set_idle(self, yes): + self._idle = bool(yes) + self.presence_push() + + def set_message(self, message, status, format=None, default_status='dnd'): + jabber.protocol.set_message(self, message, status, format, default_status) + + def __set_show(self, state): + self._show = state + + def __get_show(self): + if self._idle: + return 'away' + else: + return self._show + + show = property(__get_show, __set_show) + + @action(lambda self: None) + def change_password(self, *a): pass + + @action(lambda self: None) + def edit_vcard(self, *a, **k): pass + + if GTALK_SMS: + def _get_caps(self): + import common + return jabber.protocol._get_caps(self) + [common.caps.SMS] + caps = property(_get_caps) + + @callbacks.callsback + def send_sms(self, phone_number, message, callback = None): + jid = jabber.JID(phone_number, 'sms.talk.google.com') + # TODO: # self.send_message(to = jid, message = message) + + def session_started(self): + s = self.stream + s.set_iq_set_handler("query", SHARED_STATUS_NS, self.shared_status_set) + jabber.protocol.session_started(self) + + def service_discovery_init(self): + self.disco_init = jabber.disco.DiscoNode(self.cache, JID(self.jid.domain)) + self.disco_init.fetch(self.disco_finished, depth=1, timeout_duration = 1) + self.disco_init2 = jabber.disco.DiscoNode(self.cache, JID("google.com")) + self.disco_init2.fetch(super(GoogleTalk, self).disco_finished, depth=1, timeout_duration = 1) + + def disco_finished(self, disco_node): + from jabber.objects.shared_status import make_get as status_make_get + i = status_make_get(self) + self.send_cb(i, success=self.unfreeze_presence, + error=self.unfreeze_presence_error, + timeout=self.unfreeze_presence_error, + timeout_duration=2) + jabber.protocol.disco_finished(self, disco_node) + + def unfreeze_presence(self, stanza): + #parse status thing, edit, store on self. + self.shared_status_set(stanza) + # pass + #push presence + self._presence_push() + + def unfreeze_presence_error(self, *a, **k): + self._presence_push() + + def presence_push(self): + pass + + def _presence_push(self): + self.presence_push = self._presence_push + shared = getattr(self, 'shared_status', None) + if shared is not None: + shared.show = self.show + shared.status = self.status + shared.invisible = self.invisible + i = shared.make_push(self) + cb = lambda *a, **k: jabber.protocol.presence_push(self) + self.send_cb(i, success = cb, error = cb, timeout=cb, timeout_duration=5) + else: + jabber.protocol.presence_push(self) + + def shared_status_set(self, stanza): + old_shared = getattr(self, 'shared_status', None) + new_shared = SharedStatus(stanza.xmlnode) + for attr in ("status_max", "status_list_max", "status_list_contents_max"): + val = getattr(new_shared, attr.replace('-', '_'), None) + if val is None: + setattr(new_shared, attr, getattr(old_shared, attr, None)) + self.shared_status = new_shared + + def set_invisible(self, invisible = True): + 'Sets invisible.' + self.invisible = invisible + self.presence_push() + + def default_chat_server(self): + return self.confservers[0] if self.confservers else 'groupchat.google.com' + + def _get_chat_nick(self, nick): + if not nick: + nick = unicode(self.self_buddy.jid).replace('@', '_') + return nick + + def _get_chat_room_name(self, room_name): + if not room_name: + room_name = u'private-chat-' + unicode(uuid.uuid4()) + return room_name + + def _add_presence_extras(self, pres): + c = pres.add_new_content("http://jabber.org/protocol/caps", "c") + c.setProp('node',"http://www.google.com/xmpp/client/caps") + c.setProp('ver',"1.1") + c.setProp('ext',"pmuc-v1" + "voice-v1 video-v1 camera-v1" if pref('videochat.report_capability', True) else '') + return super(GoogleTalk, self)._add_presence_extras(pres) diff --git a/digsby/src/plugins/component_gtalk/gtalkStream.py b/digsby/src/plugins/component_gtalk/gtalkStream.py new file mode 100644 index 0000000..998478f --- /dev/null +++ b/digsby/src/plugins/component_gtalk/gtalkStream.py @@ -0,0 +1,125 @@ +from pyxmpp import sasl +from pyxmpp.exceptions import SASLNotAvailable, SASLMechanismNotAvailable, SASLAuthenticationFailed, \ + FatalStreamError +from jabber.threadstream import ThreadStream +from pyxmpp.streamsasl import SASL_NS +from pyxmpp.streambase import STREAM_NS +from pyxmpp.jid import JID +import libxml2 + +from logging import getLogger +log = getLogger('gtalkStream') + +GTALK_AUTH_NS = 'http://www.google.com/talk/protocol/auth' + +class GoogleTalkStream(ThreadStream): + def stream_start(self,doc): + """Process (stream start) tag received from peer. + + :Parameters: + - `doc`: document created by the parser""" + self.doc_in=doc + log.debug("input document: %r" % (self.doc_in.serialize(),)) + + try: + r=self.doc_in.getRootElement() + if r.ns().getContent() != STREAM_NS: + self._send_stream_error("invalid-namespace") + raise FatalStreamError,"Invalid namespace." + except libxml2.treeError: + self._send_stream_error("invalid-namespace") + raise FatalStreamError,"Couldn't get the namespace." + + self.version=r.prop("version") + if self.version and self.version!="1.0": + self._send_stream_error("unsupported-version") + raise FatalStreamError,"Unsupported protocol version." + +# to_from_mismatch=0 + assert self.initiator + if self.initiator: + self.stream_id=r.prop("id") + peer=r.prop("from") + if peer: + peer=JID(peer) +# if self.peer: +# if peer and peer!=self.peer: +# self.__logger.debug("peer hostname mismatch:" +# " %r != %r" % (peer,self.peer)) +# to_from_mismatch=1 +# else: + self.peer=peer + else: + to=r.prop("to") + if to: + to=self.check_to(to) + if not to: + self._send_stream_error("host-unknown") + raise FatalStreamError,'Bad "to"' + self.me=JID(to) + self._send_stream_start(self.generate_id()) + self._send_stream_features() + self.state_change("fully connected",self.peer) + self._post_connect() + + if not self.version: + self.state_change("fully connected",self.peer) + self._post_connect() + +# if to_from_mismatch: +# raise HostMismatch + + def _sasl_authenticate(self,username,authzid,mechanism=None): + """Start SASL authentication process. + + [initiating entity only] + + :Parameters: + - `username`: user name. + - `authzid`: authorization ID. + - `mechanism`: SASL mechanism to use.""" + if not self.initiator: + raise SASLAuthenticationFailed,"Only initiating entity start SASL authentication" + while not self.features: + self.__logger.debug("Waiting for features") + self._read() + if not self.peer_sasl_mechanisms: + raise SASLNotAvailable,"Peer doesn't support SASL" + + if not mechanism: + mechanism=None + for m in self.sasl_mechanisms: + if m in self.peer_sasl_mechanisms: + mechanism=m + break + if not mechanism: + raise SASLMechanismNotAvailable,"Peer doesn't support any of our SASL mechanisms" + self.__logger.debug("Our mechanism: %r" % (mechanism,)) + else: + if mechanism not in self.peer_sasl_mechanisms: + raise SASLMechanismNotAvailable,"%s is not available" % (mechanism,) + + self.auth_method_used="sasl:"+mechanism + + self.authenticator=sasl.client_authenticator_factory(mechanism,self) + + initial_response=self.authenticator.start(username,authzid) + if not isinstance(initial_response,sasl.Response): + raise SASLAuthenticationFailed,"SASL initiation failed" + + root=self.doc_out.getRootElement() + xmlnode=root.newChild(None,"auth",None) + ns=xmlnode.newNs(SASL_NS,None) + xmlnode.setNs(ns) + xmlnode.setProp("mechanism",mechanism) + + ga=xmlnode.newNs(GTALK_AUTH_NS,'ga') + xmlnode.newNsProp(ga, 'client-uses-full-bind-result', 'true') + + if initial_response.data: + xmlnode.setContent(initial_response.base64()) + + self._write_raw(xmlnode.serialize(encoding="UTF-8")) + + self.freeOutNode(xmlnode) + \ No newline at end of file diff --git a/digsby/src/plugins/component_gtalk/gtalkVideo.py b/digsby/src/plugins/component_gtalk/gtalkVideo.py new file mode 100644 index 0000000..3233988 --- /dev/null +++ b/digsby/src/plugins/component_gtalk/gtalkVideo.py @@ -0,0 +1,92 @@ +from logging import getLogger +from peak.util.addons import AddOn +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.iq import Iq + + + +log = getLogger('plugins.gtalk_video') +GTALK_SESSION_NS = 'http://www.google.com/session' +GTALK_VIDEO_SESSION_NS = 'http://www.google.com/session/video' + + + +class gtalk_iq_video_initiate(AddOn): + def __init__(self, subject): + self.protocol = subject + super(gtalk_iq_video_initiate, self).__init__(subject) + + def setup(self, stream): + self.stream = stream + log.debug('setting up iq session') + stream.set_iq_set_handler('session', GTALK_SESSION_NS, self.video_initiate) + + def video_initiate(self, iq): + d = dict(ses = GTALK_SESSION_NS, + vid = GTALK_VIDEO_SESSION_NS) + + vid_initiates = iq.xpath_eval("ses:session[@type='initiate']/vid:description", d) + + if vid_initiates: + + sessions = iq.xpath_eval("ses:session", d) + + iq_from = iq.get_from() + + if sessions: + session = sessions[0] + session_id = session.prop('id') + if not session_id: + log.error('No ID found for videochat session initiation request') + + session_initiator = session.prop('initiator') + if not session_initiator: + log.error('No initiator found for videochat session initiation request') + + def vidoechat_response_callback(accepted = False): + if not accepted: + self.protocol.send_iq(GoogleSessionReject(session_id, session_initiator).make_set(iq_from)) + else: + log.error("Video Session accepted? But we don't support that yet!") + + + self.protocol.convo_for(iq.get_from()).received_native_videochat_request(vidoechat_response_callback) + + self.protocol.send_iq(iq.make_result_response()) + + return True + +class GoogleSessionReject(StanzaPayloadObject): + xml_element_name = 'session' + xml_element_namespace = GTALK_SESSION_NS + + def __init__(self, id, initiator): + self.id = id + self.initiator = initiator + +# def __from_xml(self, node): +# pass + + def complete_xml_element(self, xmlnode, doc): + xmlnode.setProp("type", "reject") + xmlnode.setProp("initiator", self.initiator) + xmlnode.setProp("id", self.id) + + def make_set(self, to): + iq = Iq(stanza_type="set") + iq.set_to(to) + self.as_xml(parent=iq.get_node()) + return iq + + + +def session_started(protocol, stream, *a, **k): + if getattr(protocol, 'name', None) != 'gtalk': + return + log.debug('registering "%s" feature', GTALK_SESSION_NS) + gtalk_iq_video_initiate(protocol).setup(stream) + +def initialized(protocol, *a, **k): + if getattr(protocol, 'name', None) != 'gtalk': + return + protocol.register_feature(GTALK_SESSION_NS) diff --git a/digsby/src/plugins/component_gtalk/gtalk_ui.py b/digsby/src/plugins/component_gtalk/gtalk_ui.py new file mode 100644 index 0000000..d16a34f --- /dev/null +++ b/digsby/src/plugins/component_gtalk/gtalk_ui.py @@ -0,0 +1,2 @@ +def construct_basic_subpanel(parent, MSP, MSC): + return None diff --git a/digsby/src/plugins/component_gtalk/info.yaml b/digsby/src/plugins/component_gtalk/info.yaml new file mode 100644 index 0000000..c690ecb --- /dev/null +++ b/digsby/src/plugins/component_gtalk/info.yaml @@ -0,0 +1,63 @@ +name: !_ 'Google Talk' +service_provider: &service_provider google +type: service_component +component_type: im +shortname: gtalk +name_truncated: 'gtal' +statuses: [[!N_ 'Available'], + [!N_ 'Away']] +needs_dataproxy: yes +has_invisible: yes + +defaults: + ignore_ssl_warnings: yes + do_tls: yes + priority: 25 + resource: Digsby + sasl_plain: yes + server: ['talk.google.com', 5222] + dataproxy: "" + hide_os: no + allow_plaintext: yes + plain: yes + autologin: no + block_unknowns: no + + alt_connect_opts: + - require_tls: no + verify_tls_peer: no + server: ['talk.google.com', 443] + do_ssl: yes + do_tls: no + + - require_tls: yes + verify_tls_peer: no + server: ['talk.google.com', 80] + do_ssl: no + do_tls: yes + + - require_tls: no + verify_tls_peer: no + server: ['talk.google.com', 5223] + do_ssl: yes + do_tls: no + +compatible: !set + - jabber + - gtalk + +entry_points: + digsby.component.im: + *service_provider: component_gtalk.gtalk:GoogleTalk + digsby.jabber.session_started: + *service_provider: component_gtalk.gtalkVideo:session_started + digsby.jabber.initialized: + *service_provider: component_gtalk.gtalkVideo:initialized + + +path: component_gtalk.gtalk.GoogleTalk +popularity: 1000 +username_desc: !_ 'Google Account' +password_desc: !_ 'Password' +newuser_url: 'https://www.google.com/accounts/NewAccount' +password_url: 'https://www.google.com/accounts/ForgotPasswd' diff --git a/digsby/src/plugins/component_tagged/TaggedAccount.py b/digsby/src/plugins/component_tagged/TaggedAccount.py new file mode 100644 index 0000000..fa43775 --- /dev/null +++ b/digsby/src/plugins/component_tagged/TaggedAccount.py @@ -0,0 +1,277 @@ +from logging import getLogger; log = getLogger('tagged.account') +from social.network import SocialNetwork +import protocols +import path +from common import action +from common import pref +from common.notifications import fire +from util.callbacks import callsback +import wx +import TaggedUtil as TU +import gui.infobox.interfaces as gui_interfaces +import gui.infobox.providers as gui_providers + +class TaggedAccount(SocialNetwork): + service = protocol = 'tagged' + + alert_keys = [ + 'actions', + 'birthdays', + 'cafe', + 'comments', + 'elections', + 'farm', + 'friends', + 'gifts', + 'gold_2', + 'groups', + 'luv', + 'meetme', + 'mob', + 'mobile_new_update', + 'pets', + 'cr_photo_warning', + 'poker_2', + 'profile', + 'questions', + 'sororitylife', + 'tags', + 'topics', + 'unread_messages', + 'videos', + 'wink', + 'zoosk', + ] + + group_filters = { + 'actions' : ['action_slots', + 'action_tabs'], + 'cafe' : ['cafe_food_ready', + 'cafe_food_rotting', + 'cafe_needs_love', + 'cafe_visit', + 'cafe_visit_clean', + 'cafe_visit_eat', + 'cafe_waiter_contract_expired'], + 'elections' : ['elections_job_completed', + 'elections_favor', + 'elections_hired', + 'elections_new_round', + 'elections_promo'], + 'farm' : ['farm_ready', + 'farm_visit'], + 'friends' : ['find_friend', + 'new_friends', + 'friend_requests', + 'invite_friend'], + 'groups' : ['groups_updated', + 'group_forum_reply', + 'group_invite'], + 'meetme' : ['meetme_headliner_reup', + 'meetme_spotlight_reup', + 'meetme_match', + 'meetme_yes'], + 'mob' : ['mob_boost', + 'mob_fight', + 'mob_hire', + 'mob_promo', + 'mob_promo_simple', + 'mob_prop_rdy'], + 'profile' : ['profile_photo', + 'profile_viewers'], + 'questions' : ['questions_answer', + 'questions'], + 'topics' : ['topics_promo', + 'topics_new', + 'topics_response'], + 'zoosk' : ['zoosk_coin', + 'zoosk_message'], + 'sororitylife' : ['sororitylife', + 'soriritylife_new_version'] + } + + @property + def header_funcs(self): + return ((_('Home'), TU.weblink()), + (_('Profile'), TU.weblink('profile.html')), + (_('Messages'), TU.weblink('messages.html')), + (_('People'), TU.weblink('people.html')), + (_('Games'), TU.weblink('games.html'))) + + @property + def extra_header_func(self): + return (_('Invite Friends'), TU.weblink('friends.html#tab=contacts&type=0&filterpg=All_0')) + + def __init__(self, **options): + self.connection = None + self._dirty = True + filters = options.pop('filters', {}) + self.update_filters(filters) + super(TaggedAccount, self).__init__(**options) + + def set_dirty(self): + self._dirty = True + + def Connect(self): + log.info('Connect') + self.change_state(self.Statuses.CONNECTING) + if self.enabled: + self.update_now() + else: + self.set_offline(self.Reasons.NONE) + + def update_now(self): + log.info('Updating %r', self) + self.start_timer() + if self.state == self.Statuses.OFFLINE or self.connection is None: + self.change_state(self.Statuses.CONNECTING) + self.create_connection() + self.connect(success = lambda *a: self.change_state(self.Statuses.ONLINE), # S1 + error = lambda *a: self.set_offline(self.Reasons.CONN_FAIL)) # E1 + else: + self.update() + + def update_filters(self, filters): + alerts = filters if filters else [True]*len(self.alert_keys) + self.filters = dict(zip(self.alert_keys, alerts)) + self.whitelist = dict(zip(self.alert_keys, alerts)) + for key in self.group_filters: + alert_enabled = self.whitelist[key] + del self.whitelist[key] + for i in self.group_filters[key]: + self.whitelist[i] = alert_enabled + + def update_info(self, **info): + filters = info.pop('filters', None) + if filters is not None: + self.update_filters(filters) + self.set_dirty() + SocialNetwork.update_info(self, **info) + + def get_options(self): + options = super(TaggedAccount, self).get_options() + options['filters'] = [bool(self.filters[x]) for x in self.alert_keys] + return options + + @callsback + def connect(self, callback = None): + def on_connect_success(*a): + self.update(success = lambda *a: self.set_dirty(), # S3 + error = lambda *a: self.set_offline(self.Reasons.CONN_LOST)) # E3 + callback.success() # S1 + + self.connection.login(success = on_connect_success, + error = callback.error) # E1 + + @callsback + def update(self, callback = None): + self.connection.getStatus(callback = callback) + self.connection.getAlerts(callback = callback) + self.connection.getFriendsInfo(callback = callback) + + def on_getCandidate_success(*a): + self.connection.getElectionsNewsfeed(callback = callback) + + self.connection.getCandidate(success = on_getCandidate_success, + error = callback.error) # E3 + + def on_getPet_success(*a): + self.connection.getPetsNewsfeed(callback = callback) + + self.connection.getPet(success = on_getPet_success, + error = callback.error) # E3 + + # Create a connection + def create_connection(self): + if self.connection is not None: + raise Exception('Already have a connection') + + import TaggedProtocol as TP + self.connection = TP.TaggedProtocol(self, self.name, self._decryptedpw()) + log.info('Connection created') + + # Gets called by SocialNetwork + def Disconnect(self, reason = None): + if reason is None: + reason = self.Reasons.NONE + self.connection = None + self.set_offline(reason) + log.info('Disconnected') + + @action() + def openurl_Home(self): + TU.launchbrowser('') + + @action() + def openurl_Profile(self): + TU.launchbrowser('profile.html') + + @action() + def openurl_Messages(self): + TU.launchbrowser('messages.html') + + @action() + def openurl_People(self): + TU.launchbrowser('people.html') + + @action() + def openurl_Games(self): + TU.launchbrowser('games.html') + + @action() + def SetStatus(self): + if pref('social.use_global_status', default = False, type = bool): + wx.GetApp().SetStatusPrompt([self]) + else: + log.error('No alternative to global status dialog for TaggedAccount') + + @callsback + def SetStatusMessage(self, new_message, callback = None, **k): + def success(*a): + self.set_dirty() + callback.success() + + self.connection.setStatus(new_message, + success = success, + error = callback.error) + + def DefaultAction(self): + self.SetStatus() + + def SendMessage(self, user_id, display_name, *a): + event = dict(sender_uid = int(user_id)) + fire('tagged.toast', + title = _('New Message to: %s') % display_name, + msg = '', + sticky = True, + input = lambda text, opts, *a: self.connection.send_message(text, opts, event), + popupid = ('tagged_toast!%r!%r' % (int(user_id), id(self.connection)))) + +## Infobox + +class TaggedIB(gui_providers.InfoboxProviderBase): + @property + def _dirty(self): + return self.acct._dirty + + protocols.advise(asAdapterForTypes = [TaggedAccount], instancesProvide = [gui_interfaces.ICacheableInfoboxHTMLProvider]) + def __init__(self, acct): + gui_providers.InfoboxProviderBase.__init__(self) + self.acct = acct + + def get_html(self, htmlfonts = None, **opts): + self.acct._dirty = False + return gui_providers.InfoboxProviderBase.get_html(self, **opts) + + def get_app_context(self, ctxt_class): + return ctxt_class(path.path(__file__).parent.parent, 'component_tagged') + + def get_context(self): + ctxt = gui_providers.InfoboxProviderBase.get_context(self) + + conn = self.acct.connection + ctxt.update( + conn = conn + ) + + return ctxt \ No newline at end of file diff --git a/digsby/src/plugins/component_tagged/TaggedApi.py b/digsby/src/plugins/component_tagged/TaggedApi.py new file mode 100644 index 0000000..d809630 --- /dev/null +++ b/digsby/src/plugins/component_tagged/TaggedApi.py @@ -0,0 +1,56 @@ +from logging import getLogger; log = getLogger('tagged.api') +from util import AttrChain +import simplejson +from util.callbacks import callsback +from util.net import WebFormData +import TaggedUtil as TU + +class TaggedApi(AttrChain): + @property + def API_URL(self): + return 'http://www' + TU.TAGGED_DOMAIN() + '/api/?' + + def __init__(self, opener, session_token): + AttrChain.__init__(self, 'tagged') + self.opener = opener + self.session_token = session_token + log.info('Tagged api initialized') + + @callsback + def __call__(self, method, callback = None, **kwds): + callargs = {'session_token' : self.session_token, + 'application_id' : 'user', + 'format' : 'json'} + callargs.update(method = method) + callargs.update(kwds) + + def success(req, resp): + resp = resp.read() + json = simplejson.loads(resp) + if json['stat'] == 'ok': + callback.success(json['result'] if 'result' in json else json['results']) + elif json['stat'] == 'fail': + raise Exception('API call failed %r', json) + elif json['stat'] == 'nobot': + d = {'x*=' : (lambda x, y: x *y), + 'x-=' : (lambda x, y: x -y), + 'x+=' : (lambda x, y: x +y), + 'x=x%' : (lambda x, y: x % y)} + + result = json['result'] + + fnc = result['jsfunc'].replace('(function(){var ', '').replace('return x;})()', '') + init = fnc.split(';')[0] + rest = fnc.split(';')[1:] + x = int(init[2:]) + for val in rest: + for key in d: + if val.startswith(key): + y = int(val[len(key):]) + x = d[key](x, y) + + self.security.nobot.submitAnswer(answer = x, + origCallObj = simplejson.dumps(result['origCallObj']), + callback = callback) + + self.opener.open(self.API_URL, WebFormData(**callargs), success = success, error = callback.error) diff --git a/digsby/src/plugins/component_tagged/TaggedGui.py b/digsby/src/plugins/component_tagged/TaggedGui.py new file mode 100644 index 0000000..bec4b65 --- /dev/null +++ b/digsby/src/plugins/component_tagged/TaggedGui.py @@ -0,0 +1,92 @@ +from logging import getLogger; log = getLogger('tagged.gui') +import wx + +import gui.skin as skin +import gui.toolbox as toolbox +import gui.textutil as textutil + +import digsby_service_editor.default_ui as default_ui + +def construct_advanced_subpanel_social(panel, SP, MSP, MSC): + l_sz = wx.BoxSizer(wx.VERTICAL) + leftPanel_lbl = wx.StaticText(panel, -1, _("Show Alerts:"), style = wx.ALIGN_LEFT) + leftPanel_lbl.Font = textutil.CopyFont(leftPanel_lbl.Font, weight = wx.BOLD) + + left_labels = [ + _('Actions'), + _('Birthdays'), + _('Cafe'), + _('Comments'), + _('Elections'), + _('Farm'), + _('Friends'), + _('Gifts'), + _('Gold'), + _('Groups'), + _('Luv'), + _('Meetme'), + _('Mob'), + ] + + l_sz.Add(leftPanel_lbl, 0, wx.BOTTOM, leftPanel_lbl.GetDefaultBorder()) + + leftPanel = panel.controls['leftPanel'] = [] + filters = getattr(SP, 'filters', []) + values = filters[:len(left_labels)] if filters else None + + if values is None: + values = [True] * len(left_labels) + + for i, label in enumerate(left_labels): + chk = wx.CheckBox(panel, label = label) + chk.Value = values[i] + leftPanel.append(chk) + default_ui.ezadd(l_sz, chk, 0, wx.ALL) + r_sz = wx.BoxSizer(wx.VERTICAL) + rightPanel_lbl = wx.StaticText(panel, -1, '', style = wx.ALIGN_LEFT) + rightPanel_lbl.Font = textutil.CopyFont(rightPanel_lbl.Font, weight = wx.BOLD) + right_labels = [ + _('Mobile Updates'), + _('Pets'), + _('Photo Violations'), + _('Poker'), + _('Profile'), + _('Questions'), + _('Sorority Life'), + _('Tags'), + _('Topics'), + _('Unread Messages'), + _('Videos'), + _('Winks'), + _('Zoosk'), + ] + + r_sz.Add(rightPanel_lbl, 0, wx.BOTTOM, rightPanel_lbl.GetDefaultBorder()) + + rightPanel = panel.controls['rightPanel'] = [] + + values = filters[len(left_labels):len(left_labels)+len(right_labels)] if filters else None + if values is None: + values = [True] * len(right_labels) + + for i, label in enumerate(right_labels): + chk = wx.CheckBox(panel, label = label) + chk.Value = values[i] + rightPanel.append(chk) + default_ui.ezadd(r_sz, chk, 0, wx.ALL) + + h_sz = wx.BoxSizer(wx.HORIZONTAL) + h_sz.Add(l_sz) + h_sz.Add(r_sz) + + fx = panel.controls['advanced_sz'] + fx.Add(h_sz, (fx.row, 0), (11, 4)) + + fx.row += 11 + return True + +def extract_advanced_subpanel_social(panel, info, SP, MSP, MSC): + alerts = [x.Value for x in panel.controls['leftPanel']] + alerts.extend([x.Value for x in panel.controls['rightPanel']]) + info['filters'] = alerts + return True \ No newline at end of file diff --git a/digsby/src/plugins/component_tagged/TaggedProtocol.py b/digsby/src/plugins/component_tagged/TaggedProtocol.py new file mode 100644 index 0000000..d9ad7fc --- /dev/null +++ b/digsby/src/plugins/component_tagged/TaggedProtocol.py @@ -0,0 +1,450 @@ +from logging import getLogger; log = getLogger('tagged.protocol') +import util.httptools as httptools +import common.asynchttp as AsyncHttp +from util.callbacks import callsback +import ClientForm +from common.notifications import fire +from util.primitives import strings +from res.strings import elections +from res.strings import pets +from res.strings import alerts +import TaggedUtil as TU + +class TaggedProtocol(httptools.WebScraper): + @property + def LOGIN_URL(self): + return 'http' + TU.SECURE() + '://secure' + TU.TAGGED_DOMAIN() + '/secure_login.html' + + def __init__(self, acct, username, password): + super(TaggedProtocol, self).__init__() + self.opener = self.http + self.api = None + self.realtime = None + self.acct = acct + self.username = username + self.password = password + self.session_token = None + + # data fetched from api calls + self.status = None + self.alerts = None + self.online_friends = None + self.elections_candidate = None + self.elections_staff = None + self.elections_projects = None + self.elections_newsfeed = None + self.pets_pet = None + self.pets_owner = None + self.pets_newsfeed = None + log.info('Tagged protocol Initialized') + + @callsback + def login(self, callback = None): + def on_form_fetch_success(req, resp): + loginForm = ClientForm.ParseResponse(resp, backwards_compat = False, request_class = AsyncHttp.HTTPRequest)[0] + loginForm['username'] = self.username + loginForm['password'] = self.password + log.info('Form fetch successful') + + def on_login_success(*a): + self.session_token = self.get_cookie('S', domain=TU.TAGGED_DOMAIN(), default=False) + self.create_api() + log.info('Login successful') + + def on_getSelfInfo_success(*a): + callback.success() # on_connect_success + self.create_realtime() + self.realtime_check(success = lambda *a: log.info('Realtime connection successful'), # S2 + error = lambda *a: log.warning('Realtime connection failed')) # E2 + + self.getSelfInfo(success = on_getSelfInfo_success, + error = callback.error) # E1 + + self.opener.open(loginForm.click(), + success = on_login_success, + error = callback.error) # E1 + + self.opener.open(self.LOGIN_URL, + success = on_form_fetch_success, + error = callback.error) # E1 + +## Api related + def create_api(self): + if self.api is not None: + raise Exception('Already have an API') + + import TaggedApi as TA + self.api = TA.TaggedApi(self.opener, self.session_token) + log.info('Api created') + + @callsback + def getSelfInfo(self, callback = None): + '''return : {Object} {'user_id', 'opt_out'}''' + def success(result): + self.user_id = result['user_id'] + callback.success() + + self.api.util.selfInfo(success = success, + error = callback.error) + + @callsback + def getAlerts(self, callback = None): + '''return : {Object} {'cached', 'drop_down', 'lightbox', 'icons'}''' + def success(result): + self.alerts = result + callback.success() + + self.api.header.renderAlerts(success = success, + error = callback.error) + + @callsback + def getFriendsInfo(self, callback = None): + '''return : {Object} {'total_count', 'top', 'online', 'page_offset', + 'new_only', 'friends_info', 'starts_with'}''' + def success(result): + friends_info = result.get('friends_info') + self.online_friends = friends_info[0] if friends_info else None + callback.success() + + self.api.friends.getFriendsInfo(page_offset = 0, + num_pages = 1, + user_id = self.user_id, + online = True, + thumb_size = 's', + success = success, + error = callback.error) + + @callsback + def getStatus(self, callback = None): + '''return : {Object} {'status', 'timestamp'}''' + def success(result): + self.status = result + callback.success() + + self.api.newsfeed.user.status(other_user_id = self.user_id, + success = success, + error = callback.error) + + @callsback + def setStatus(self, new_message, callback = None): + '''param : {String} new_message New Status Message + return : {Object} {'status', 'displayname', 'uid', 'timestamp', 'photo', 'type'}''' + def success(result): + self.getStatus(callback = callback) + callback.success() + + self.api.newsfeed.event.post(data = new_message, + facebook_post = False, + myspace_post = False, + twitter_post = False, + success = success, + error = callback.error) + + @callsback + def getCandidate(self, callback = None): + def success(result): + self.elections_candidate = result['data'][0] + self.elections_staff = result['data'][1] + self.elections_projects = result['data'][2] + callback.success() # on_getCandidate_success + + self.api.apps.elections.getCandidate(candidate_id = self.user_id, + success = success, + error = callback.error) # E3 + + @callsback + def getElectionsNewsfeed(self, callback = None): + def success(result): + events = result['data'][0] + + # Catch bad results + if (len(events) <= 0): + return + else: + # Set up list of newsfeed events + self.elections_newsfeed = [] + + # Used for collapsing sequential events that are identical + prev_type = None; + prev_string = None; + + for event in events: + # Extract the data from the event + data = event['data'] + date = event['date'] + type = event['type'] + + # Get the string for this event + strings = elections.newsfeed_strings[type] + obj = strings['primary'] + + # Create the array of substitutable sprintf params + params = [] + + switch = {'project_title' : lambda *a: params.append(data['project']['title']), + 'target_name' : lambda *a: params.append('%s' % (data['target']['elections_link'], data['target']['name'])), + 'displayname' : lambda *a: params.append('%s' % (self.elections_candidate['elections_link'], self.candidate['name'])), + 'issue_title' : lambda *a: params.append(data['issue']['title']), + 'issue_vote' : lambda *a: params.append(data['issue']['pro'] if data['vote'] else data['issue']['con']), + 'party' : lambda *a: params.append(data['party_id'])} + + for param in obj['params']: + # Emulate switch statement + if param in switch: + switch[param]() + if param in ['votes', 'fame', 'funds', 'collaborators', 'party_line']: + params.append(data[param]) + + string = obj['string'] % tuple(params) + + if prev_type == type and prev_string == string: + last_event = self.elections_newsfeed[len(self.elections_newsfeed) - 1] + last_event['numTimes'] += 1; + last_event['time'] = TU.format_event_time(date) + else: + self.elections_newsfeed.append({'feed_type' : 'candidate', + 'event_type' : type, + 'string' : string, + 'numTimes' : 1, + 'time' : TU.format_event_time(date)}) + prev_type = type + prev_string = string + + callback.success() # S3 + + self.api.apps.elections.getNews(feed_type = 'candidate', + offset = 0, + count = 50, + success = success, + error = callback.error) # E3 + + @callsback + def getPet(self, callback = None): + def success(result): + self.pets_pet = result['pet'] + self.pets_owner = result['owner'] + callback.success() # on_getPet_success + + self.api.apps.pets.getPetAndOwnerInfo(pet_id = self.user_id, + success = success, + error = callback.error) # E3 + + @callsback + def getPetsNewsfeed(self, callback = None): + def format_link(id, name): + link = 'noowner' if id == 0 else '#/pet/%s' % id + return '%s' % (link, name) + + def format_cash(amount): + return '%s' % TU.format_currency(amount) + + def format_bonus(amount): + return '%s' % TU.format_currency(amount) + + def success(result): + events = result['events_html'] + + # Catch bad results + if (len(events) <= 0): + return + else: + # Set up list of newsfeed events + self.pets_newsfeed = [] + + # Used for collapsing sequential events that are identical + prev_type = None; + prev_string = None; + + for event in events: + # Extract the data from the event + date = event['event_date'] + type = event['event_type'] + + # Get the string for the event + strings = pets.newsfeed_strings[type] + obj = strings['primary'](event) + + # Create the array of substitutable sprintf params + params = [] + + switch = {'pet_link' : lambda *a: params.append(format_link(event['pet_id'], event['pet_display_name'])), + 'owner_link' : lambda *a: params.append(format_link(event['owner_id'], event['owner_display_name'])), + 'target_link' : lambda *a: params.append(format_link(self.pets_pet['user_id'], self.pets_pet['display_name']))} + + for param in obj['params']: + # Emulate switch statement + if param in switch: + switch[param]() + elif param in ['purchase_price', 'setfree_price', 'earned_amount', 'profit_amount']: + params.append(format_cash(event[param])) + elif param in ['bonus_price', 'bonus_amount']: + params.append(format_bonus(event[param])) + elif param == 'achievement_name': + params.append(pets.achievement_strings[event['achievement_type']]) + elif param == 'gender': + params.append('himself' if event[param] == 'M' else 'herself') + + string = obj['string'] % tuple(params) + + if prev_type == type and prev_string == string: + last_event = self.pets_newsfeed[len(self.pets_newsfeed) - 1] + last_event['numTimes'] += 1 + last_event['time'] = TU.format_event_time(date) + else: + self.pets_newsfeed.append({'event_type' : type, + 'string' : string, + 'numTimes' : 1, + 'time' : TU.format_event_time(date)}) + prev_type = type + prev_string = string + + callback.success() # S3 + + self.api.apps.pets.getNewsForUser(num_events = 50, + return_as_html = False, + success = success, + error = callback.error) # E3 + +## Realtime related + def create_realtime(self): + log.info('Create realtime') + if self.realtime is not None: + raise Exception('Already have Realtime') + + import TaggedRealtime as TR + self.realtime = TR.TaggedRealtime(self.opener, self.session_token, self.user_id) + + @callsback + def realtime_check(self, callback = None): + log.info('Realtime check') + def on_query_event_id_success(data, *a): + self.register_client(data['next_event_id'], callback = callback) + + self.realtime.query_event_id(country = 'US', # TODO add to prefs + success = on_query_event_id_success, + error = callback.error) # E2 + + @callsback + def register_client(self, event_id, callback = None): + log.info('Realtime register') + def on_register_client_success(data, *a): + # Register again, if it fails, do a check on the server + self.register_client(data['next_event_id'], + success = callback.success, + error = lambda *a: self.realtime_check(callback = callback)) + + # Switch + switch = {'alerts_update' : lambda event_data, *a: self.alerts_update(event_data), + 'toast_update' : lambda event_data, *a: self.toast_update(event_data), + 'elections_project_contribution' : lambda event_data, *a: self.elections_project_contribution(event_data)} + + for event in data['events_data']: + switch[event['event_type']](event['event_data']) + + callback.success() # S2 + + self.realtime.register_client(event_types = ['alerts_update', 'toast_update', 'elections_project_contribution'], # TODO form this list dynamically from prefs + next_event_id = event_id, + success = on_register_client_success, + error = callback.error) # E2 + + def alerts_update(self, event, *a): + def onclick(link): + if link != '': + TU.launchbrowser(link) + + # event : {'alerts_updated', 'alert_type'} + if event['alerts_updated']: + self.getAlerts(success = lambda *a: self.acct.set_dirty(), + error = lambda *a: self.set_offline(self.acct.Reasons.CONN_LOST)) # To re-render the Alerts in the infobox + + type = event['alert_type'] + strings = alerts.popup_strings + + if self.acct.whitelist[type]: + # TODO strings[type]['link'] sometimes look like something.html?a=A&b=B. the ? part seems to get lost after the weblink + fire('tagged.alert', + title = 'Tagged Alert', + msg = strings[type]['string'], + popupid = 'tagged.alert!%r' % id(self), + onclick = lambda *a: onclick(strings[type]['link'])) + + def send_message(self, text, opts, event, *a): + callargs = dict(to_id = event['sender_uid'], + subject = event['subject'] if 'subject' in event else 'digsby message', + message = text) + + if 'message_id' in event: + self.api.messages.read(msg_ids = event['message_id'], + success = lambda *a: log.info('Message read'), + error = lambda *a: log.warning('Message not read')) + callargs.update(parent_msg_id = event['message_id']) + + self.api.messages.send(success = lambda *a: log.info('Message sent'), + error = lambda *a: log.error('Send message failed'), + **callargs) + return '> ' + text + + def toast_update(self, event, *a): + def meetme(*a): + '''meetme : {'age', 'gender', 'location', 'sender_display_name', 'sender_url', + 'sender_thumbnail', 'sender_uid', 'meetme_url', 'isMatch'}''' + fire_opts.update(title = _('Meet Me') + _('Match from: %s') if event['isMatch'] else _('Interest from: %s') % event['sender_display_name'], + msg = '') + if event['isMatch']: + fire_opts.update(input = lambda text, opts, *a: self.send_message(text, opts, event)) + + def message(*a): + '''message : {'sender_display_name', 'sender_url', 'sender_uid', + 'subject', 'message', 'message_id', 'sender_thumbnail'}''' + fire_opts.update(title = _('New Message from: %s') % event['sender_display_name'], + msg = strings.strip_html(event['message']).strip(), + sticky = True, + input = lambda text, opts, *a: self.send_message(text, opts, event)) + + def friend_request(*a): + '''friend_request : {'isNewFriend', 'age', 'gender', 'location', 'sender_display_name', + 'sender_url', 'sender_uid', 'sender_thumbnail'}''' + fire_opts.update(title = _('%s is now your friend') if event['isNewFriend'] else _('Friend Request from: %s') % event['sender_display_name'], + msg = '') + if event['isNewFriend']: + fire_opts.update(input = lambda text, opts, *a: self.send_message(text, opts, event)) + + def topics(*a): + '''topics : {'topics_type', 'conv_id', 'post_id', 'init_text', 'text', 'sender_displayName', + 'sender_url', 'sender_thumbnail', 'sender_uid'}''' + pass # TODO implement topics + + fire_opts = dict(onclick = lambda *a: TU.launchbrowser(event['sender_url']), + popupid = 'tagged_toast!%r!%r' % (event['sender_uid'], id(self))) + + {'meetme' : meetme, + 'message' : message, + 'friend_request' : friend_request, + 'topics' : topics + }[event['sub_type']]() + + if event['sub_type'] != 'topics': # TODO implement topics + fire('tagged.toast', **fire_opts) + + def elections_project_contribution(self, event, *a): + '''project : {'hash', 'total_contributions', 'contributors', 'max_contribution', + 'starter_id', 'contributions', 'id', 'starter', 'num_contributors', + 'finish_time', 'state', 'catalog_id', 'time_remaining'}''' + + project = event['project'] + state = project['state'] + + if state == -1: # FAILED + msg = _('not able to get fully funded') + + elif state == 0: # ACTIVE + msg = _('contributed') + + elif state == 1: # COMPLETED + msg = _('completed') + + fire('tagged.elections', + title = _('Elections'), + msg = _('A project was %s') % msg, # TODO we need the projects catalog to be more specific + onclick = lambda *a: TU.launchbrowser('apps/elections.html')) diff --git a/digsby/src/plugins/component_tagged/TaggedRealtime.py b/digsby/src/plugins/component_tagged/TaggedRealtime.py new file mode 100644 index 0000000..caa856f --- /dev/null +++ b/digsby/src/plugins/component_tagged/TaggedRealtime.py @@ -0,0 +1,61 @@ +from logging import getLogger; log = getLogger('tagged.realtime') +from util import AttrChain +import simplejson +import common.asynchttp as AsyncHttp +from util.callbacks import callsback +from util.net import WebFormData +import TaggedUtil as TU + +class TaggedRealtime(AttrChain): + @property + def REALTIME_URL(self): + return 'http://dpush01.tag-dev.com:8001' if TU.TAGGED_DOMAIN() == '.tag-local.com' else 'http://push' + TU.TAGGED_DOMAIN() + + def __init__(self, opener, session_token, user_id): + AttrChain.__init__(self) + self.opener = opener + self.session_token = session_token + self.user_id = user_id + log.info('Tagged realtime initialized') + + @callsback + def __call__(self, method, callback = None, **kwds): + callargs = {'session_token' : self.session_token, + 'user_id' : self.user_id} + callargs.update(method = method) + if 'event_types' in kwds: + kwds['event_types'] = simplejson.dumps(kwds['event_types']) + callargs.update(kwds) + + req = AsyncHttp.HTTPRequest(self.REALTIME_URL, WebFormData(**callargs), {'X-T-Uid': self.user_id}) + + def success(req, resp): + resp = resp.read() + + # TODO temporary until prod nodeServer gets updated + if resp.startswith('undefined'): + resp = resp[10:-2] + + json = simplejson.loads(resp) + data = json['data'] + + if method == 'query_event_id': + if json['status'] == 'ok': + log.info('Realtime initialization successful') + callback.success(data) + else: + log.warning('Realtime initialization failed') + callback.error() + + elif method == 'register_client': + if 'success' in json: + log.info('Realtime pushed data %r', data) + callback.success(data) + elif data['message'] == 'Authentication failed: Bad session token.': + log.warning("We're likely active on a browser, hop on their userObj") + callback.error() + else: + log.warning('Realtime register failed %r', data) + callback.error() + + self.opener.open(req, success = success, error = callback.error) diff --git a/digsby/src/plugins/component_tagged/TaggedUtil.py b/digsby/src/plugins/component_tagged/TaggedUtil.py new file mode 100644 index 0000000..fc8e081 --- /dev/null +++ b/digsby/src/plugins/component_tagged/TaggedUtil.py @@ -0,0 +1,59 @@ +from common import pref +import util.net as net +import wx +import time + +def TAGGED_DOMAIN(): + return pref('tagged.domain', '.tagged.com') + +def SECURE(): + return '' if TAGGED_DOMAIN() == '.tag-local.com' else 's' + +def WEBROOT(): + return 'http://www' + TAGGED_DOMAIN() + +def weblink(resource = ''): + return net.httpjoin(WEBROOT(), resource) + +def launchbrowser(where): + wx.LaunchDefaultBrowser(weblink(where)) + +def format_currency(amount): + amount = str(amount) + segments = reversed([amount[max(0, i-3):i] for i in range(len(amount), 0, -3)]) + return '$' + ','.join(segments) + +def format_event_time(event_time): + MINUTE = 60 + HOUR = 60 * MINUTE + DAY = 24 * HOUR + WEEK = 7 * DAY + MONTH = 30 * DAY + + # In seconds + delta = int(time.time()) - int(event_time) + + if delta < MINUTE: + return _('{seconds} seconds ago').format(seconds = delta) + elif delta < MINUTE * 2: + return _('A minute ago') + elif delta < HOUR: + return _('{minutes} minutes ago').format(minutes = delta / MINUTE) + elif delta < HOUR * 2: + return _('An hour ago') + elif delta < DAY: + return _('{hours} hours ago').format(hours = delta / HOUR) + elif delta < DAY * 2: + return _('A day ago') + elif delta < WEEK: + return _('{days} days ago').format(days = delta / DAY) + elif delta < WEEK * 2: + return _('A week ago') + elif delta < MONTH: + return _('{weeks} weeks ago').format(weeks = delta / WEEK) + elif delta < MONTH * 2: + return _('A month ago') + elif delta < MONTH * 3: + return _('Over 2 months ago') + else: + return _('Over 3 months ago') diff --git a/digsby/src/plugins/component_tagged/__init__.py b/digsby/src/plugins/component_tagged/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/component_tagged/actions.yaml b/digsby/src/plugins/component_tagged/actions.yaml new file mode 100644 index 0000000..0ff557b --- /dev/null +++ b/digsby/src/plugins/component_tagged/actions.yaml @@ -0,0 +1,19 @@ +TaggedAccount: + - call: openurl_Home + name: !_ '&Home' + - call: openurl_Profile + name: !_ '&Profile' + - call: openurl_Messages + name: !_ '&Messages' + - call: openurl_People + name: !_ 'P&eople' + - call: openurl_Games + name: !_ '&Games' + - ------------------------ + - call: update_now + name: !_ '&Check Now' + - call: rename_gui + name: !_ "&Rename" + - ------------------------ + - call: SetStatus + name: !_ '&Set Status' diff --git a/digsby/src/plugins/component_tagged/info.yaml b/digsby/src/plugins/component_tagged/info.yaml new file mode 100644 index 0000000..34bdaff --- /dev/null +++ b/digsby/src/plugins/component_tagged/info.yaml @@ -0,0 +1,32 @@ +name: !_ "Tagged" +shortname: tagged +name_truncated: tag +service_name: !_ Tagged Updates +service_provider: &service_provider tagged +popularity: 125 +type: service_component +component_type: social +username_desc: !_ "Email Address" +newuser_url: http://www.tagged.com/register.html +needs_password: true +path: component_tagged.TaggedAccount.TaggedAccount +form: social +whitelist_opts: ['filters', 'enabled'] + +skin: + serviceicons: + tagged: res/tagged.png + elections: res/elections.png + +infobox: + maxheight: True + hosted: True + +entry_points: + digsby.services.edit.advanced.extract_sub.social: + *service_provider: component_tagged.TaggedGui:extract_advanced_subpanel_social + digsby.services.edit.advanced.construct_sub.social: + *service_provider: component_tagged.TaggedGui:construct_advanced_subpanel_social + + digsby.component.social: + *service_provider: component_tagged.TaggedAccount:TaggedAccount \ No newline at end of file diff --git a/digsby/src/plugins/component_tagged/notifications.yaml b/digsby/src/plugins/component_tagged/notifications.yaml new file mode 100644 index 0000000..3c03714 --- /dev/null +++ b/digsby/src/plugins/component_tagged/notifications.yaml @@ -0,0 +1,28 @@ +- tagged_alert: + description: !_ 'Tagged Alert' + header: '${title}' + minor: '${msg}' + icon: 'skin:serviceicons.tagged' + update: paged + max_lines: 6 + default: + reaction: ['Popup'] + +- tagged_toast: + description: !_ 'Tagged Toast' + header: '${title}' + minor: '${msg}' + icon: 'skin:serviceicons.tagged' + max_lines: 6 + default: + reaction: ['Popup'] + +- tagged_elections: + description: !_ 'Tagged Elections' + header: '${title}' + minor: '${msg}' + icon: 'skin:serviceicons.elections' + update: paged + max_lines: 6 + default: + reaction: ['Popup'] diff --git a/digsby/src/plugins/component_tagged/res/__init__.py b/digsby/src/plugins/component_tagged/res/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/component_tagged/res/alerts_icons.png b/digsby/src/plugins/component_tagged/res/alerts_icons.png new file mode 100644 index 0000000..c91dec7 Binary files /dev/null and b/digsby/src/plugins/component_tagged/res/alerts_icons.png differ diff --git a/digsby/src/plugins/component_tagged/res/content.tenjin b/digsby/src/plugins/component_tagged/res/content.tenjin new file mode 100644 index 0000000..b27b48f --- /dev/null +++ b/digsby/src/plugins/component_tagged/res/content.tenjin @@ -0,0 +1,93 @@ +
+ +
+ ${{_('Status:')}} + + #{util.linkify(conn.status['status'].encode('xml'))} + + ${{_('You have no status message')}} + + (${{_('update')}}) +
+
+ +
+ ${{_('Alerts')}} + +
+
+ +
+ ${{_('Chat')}} + +
+
+ +
+ + + +
    + + +
  • + #{event['string']} + 1: ?> + #{event['numTimes']} ${{_('times')}} + + - #{event['time']}
  • + + + ${{_('Elections Feed is currently not available')}} + +
+ +
    + + +
  • + #{event['string']} + 1: ?> + #{event['numTimes']} ${{_('times')}} + + - #{event['time']}
  • + + + ${{_('Pets Feed is currently not available')}} + +
+ +
+ +
\ No newline at end of file diff --git a/digsby/src/plugins/component_tagged/res/elections_icons.png b/digsby/src/plugins/component_tagged/res/elections_icons.png new file mode 100644 index 0000000..509c9fd Binary files /dev/null and b/digsby/src/plugins/component_tagged/res/elections_icons.png differ diff --git a/digsby/src/plugins/component_tagged/res/infobox.css b/digsby/src/plugins/component_tagged/res/infobox.css new file mode 100644 index 0000000..3bf1cee --- /dev/null +++ b/digsby/src/plugins/component_tagged/res/infobox.css @@ -0,0 +1,343 @@ +a { + color: #000098; + cursor: pointer; + outline: medium none; + text-decoration: none; +} + +ul { + list-style: none outside none; + margin: 3px 0 0 0; + padding: 0; +} + +li { + padding: 2px 0; +} + +.tag-state-hidden { + display: none; +} + +.cash { + color: #006600; + font-weight: bold; +} + +/* News Feed Menu */ +.menu li { + display: inline-block; + padding: 1px; +} + +.menu .title:hover { + cursor: pointer; + border: 1px solid black; +} + +/* Alerts */ +.tagged-alerts a { padding: 1px 0 1px 20px; } + + /* Add / Remove / etc */ +.set_top { background-position:0 0; } +.unset_top { background-position:0 -100px; } +.remove { background-position:0 -200px; } +.add_small_grey { background-position:0 -400px; } +.add_small, .add_small_grey:hover { background-position:0 -300px; } +.remove_small_grey { background-position:0 -600px; } +.remove_small, .remove_small_grey:hover, .block_user { background-position:0 -500px; } +.report { background-position:0 -7098px; } +.close { background-position:0 -5600px; } +.close:hover { background-position:0 -5500px; } + /* Friends related */ +.add_friend, +.friend_requests { background-position:0 -700px; } +.invite_friend { background-position:0 -4900px; } +.find_friend { background-position:0 -5000px; } +.new_friends { background-position:0 -1700px; } +.viewfriends { background-position:0 -800px; } +.message, +.unread_messages { background-position:0 -900px; } +.comment, .comments { background-position:0 -1000px; } +.pending_comment { background-position: 0 -5400px; } +.instantmsg, +.chat_invites { background-position:0 -1100px; } + /* Profile icons */ +.profile_edit { background-position:0 -2500px; } +.friendsview, +.view_profile { background-position:0 -2600px; } +.changeskin { background-position:0 -2700px; } +.addbox { background-position:0 -2800px; } +.widgets { background-position:0 -2900px; } +.journals { background-position:0 -3000px; } +.videos { background-position:0 -3100px; } +.pets { background-position:0 -2000px; } +.luv { background-position:0 -2100px; } +.gift, +.gifts { background-position:0 -1500px; } +.wink { background-position:0 -2200px; } +.tags, +.new_tags { background-position:0 -2300px; } +.gold, +.gold_2 { background-position:0 -2400px; } +.gold_small { background-position:0 -4800px; } + /* Photo icons */ +.photos, +.photo_comments, +.profile_photo { background-position:0 -1200px; } +.photos_edit { background-position:0 -1300px; } + /* Group icons */ +.group_invite { background-position:0 -3200px; } +.group_edit { background-position:0 -3300px; } +.group_members { background-position:0 -3400px; } +.group_leave { background-position:0 -3500px; } +.group_forum_reply { background-position:2px -3702px; } +.group_alert { background-position:0 -4600px; } +.groups_updated { background-position:0 -4600px; } +.cr_photo_warning { background-position:0 -7900px; } + + /* Misc other icons/alerts */ +.birthday, +.birthdays { background-position:0 -1400px; } +.new_friend { background-position:0 -1700px; } +.meetme_match { background-position:0 -1800px; } +.meetme_yes { background-position:0 -1900px; } +.mob_app, +.mob_hire, +.mob_fight, +.mob_promo, +.mob_promo_simple, +.mob_prop_rdy, +.mob_boost { background-position:0 -4000px; } +.sororitylife { background-position:0 -4300px; } +.sororitylife_new_version { background-position:0 -4300px; } +.poker, +.poker_2 { background-position:0 -4400px; } +.adventure_quest { background-position: 0 -5100px; } +.profile_viewers { background-position:0 -4500px; } +.top_8 { background-position:0 -4700px; } +.games { background-position:0 -6600px; } +.adv_quest { background-position:0 -5100px; } +.action_slots { background-position:0 -5200px;} +.zoosk_coin { background-position: 0 -5700px; } +.zoosk_message { background-position: 0 -5800px; } +.voted { background-position: 0 -6000px; } +.fav { background-position: 0 -6100px; } +.farm_ready, .farm_visit { background-position: 0 -6200px; } +.fightordie { background-position: 0 -6300px; } +.happyaquarium { background-position: 0 -6497px; } +.questions { background-position: 0 -6700px; } +.questions_answer { background-position: 0 -6800px; } +.vampire { background-position: 0 -6900px; } +.action_tabs { background-position: 0 -7000px; } +.cafe { background-position: 0 -7800px; } +.cafe_food_ready, .cafe_visit { background-position: 0 -7200px; } +.cafe_food_rotting { background-position: 0 -7300px; } +.cafe_visit_eat { background-position: 0 -7400px; } +.cafe_needs_love, .cafe_waiter_contract_expired { background-position: 0 -7500px; } +.cafe_visit_clean { background-position: 0 -7600px; } +.online { background-position: 0 -7700px; } +.topics_promo { background-position: 0 -8000px; } +.topics_new { background-position: 0 -8000px; } +.topics_response { background-position: 0 -8000px; } +.mobile_new_update { background-position: 0 -8100px; } +.elections_job_completed, +.elections_favor, +.elections_hired, +.elections_new_round { background-position: 0 -8200px; } + +/* Elections */ + +.elections-feed li { + line-height: 16px; + margin: 0 0 5px; + padding: 0 0 0 20px; +} + +.ui-icon-job-16, +.ui-icon-job-32 { background-position:0 0; } + +.ui-icon-thanks-16, +.ui-icon-thanks-32 { background-position:0 -100px; } + +.ui-icon-favors-16, +.ui-icon-favors-32 { background-position:0 -200px; } + +.ui-icon-funds-16, +.ui-icon-funds-32 { background-position:0 -300px; } + +.ui-icon-votes-16, +.ui-icon-candidate-0-16, +.ui-icon-world-5-16, +.ui-icon-world-6-16, +.ui-icon-votes-32, +.ui-icon-candidate-0-32, +.ui-icon-world-5-32, +.ui-icon-world-6-32 { background-position:0 -400px; } + +.ui-icon-fame-16, +.ui-icon-fame-32 { background-position:0 -500px; } + +.ui-icon-project-16, +.ui-icon-project-complete-16, +.ui-icon-candidate-5-16, +.ui-icon-candidate-17-16, +.ui-icon-party-0-16, +.ui-icon-project-32, +.ui-icon-project-complete-32, +.ui-icon-candidate-5-32, +.ui-icon-candidate-17-32, +.ui-icon-party-0-32 { background-position:0 -600px; } + +.ui-icon-got-collaborator-16, +.ui-icon-candidate-7-16, +.ui-icon-candidate-18-16, +.ui-icon-got-collaborator-32, +.ui-icon-candidate-7-32, +.ui-icon-candidate-18-32 { background-position:0 -700px; } + +.ui-icon-got-fame-16, +.ui-icon-candidate-6-16, +.ui-icon-got-fame-32, +.ui-icon-candidate-6-32 { background-position:0 -800px; } + +.ui-icon-got-vote-16, +.ui-icon-candidate-8-16, +.ui-icon-world-1-16, +.ui-icon-got-vote-32, +.ui-icon-candidate-8-32, +.ui-icon-world-1-32 { background-position:0 -900px; } + +.ui-icon-got-staff-16, +.ui-icon-candidate-1-16, +.ui-icon-got-staff-32, +.ui-icon-candidate-1-32 { background-position:0 -1000px; } + +.ui-icon-project-start-16, +.ui-icon-candidate-3-16, +.ui-icon-project-start-32, +.ui-icon-candidate-3-32 { background-position:0 -1100px; } + +.ui-icon-contributed-16, +.ui-icon-candidate-4-16, +.ui-icon-contributed-32, +.ui-icon-candidate-4-32 { background-position:0 -1200px; } + +.ui-icon-gave-favor-16, +.ui-icon-candidate-9-16, +.ui-icon-gave-favor-32, +.ui-icon-candidate-9-32 { background-position:0 -1300px; } + +.ui-icon-got-favor-16, +.ui-icon-candidate-10-16, +.ui-icon-candidate-19-16, +.ui-icon-got-favor-32, +.ui-icon-candidate-10-32, +.ui-icon-candidate-19-32 { background-position:0 -1400px; } + +.ui-icon-gave-thanks-16, +.ui-icon-candidate-11-16, +.ui-icon-gave-thanks-32, +.ui-icon-candidate-11-32 { background-position:0 -1500px; } + +.ui-icon-got-thanks-16, +.ui-icon-candidate-12-16, +.ui-icon-candidate-20-16, +.ui-icon-got-thanks-32, +.ui-icon-candidate-12-32, +.ui-icon-candidate-20-32 { background-position:0 -1600px; } + +.ui-icon-staff-change-16, +.ui-icon-candidate-2-16, +.ui-icon-staff-change-32, +.ui-icon-candidate-2-32 { background-position:0 -1700px; } + +.ui-icon-level-up-16, +.ui-icon-candidate-13-16, +.ui-icon-level-up-32, +.ui-icon-candidate-13-32 { background-position:0 -1800px; } + +.ui-icon-party-captain-16, +.ui-icon-candidate-15-16, +.ui-icon-party-2-16, +.ui-icon-world-3-16, +.ui-icon-party-captain-32, +.ui-icon-candidate-15-32, +.ui-icon-party-2-32, +.ui-icon-world-3-32 { background-position:0 -1900px; } + +.ui-icon-party-leader-16, +.ui-icon-candidate-16-16, +.ui-icon-party-leader-32, +.ui-icon-candidate-16-32 { background-position:0 -2000px; } + +.ui-icon-party-join-16, +.ui-icon-candidate-14-16, +.ui-icon-party-1-16, +.ui-icon-party-join-32, +.ui-icon-candidate-14-32, +.ui-icon-party-1-32 { background-position:0 -2100px; } + +.ui-icon-party-line-16, +.ui-icon-world-4-16, +.ui-icon-party-line-32, +.ui-icon-world-3-32 { background-position:0 -2200px; } + +.ui-icon-contact-16, +.ui-icon-contact-32 { background-position:0 -2300px; } + +/* Pets */ +.pets-feed li { + margin: 0 0 5px; + padding: 0 0 1em 25px; + width: 28em; +} + +.pets-icon-0 { + background-position: 0 0; + width: 19px; + height: 20px; +} + +.pets-icon-1, +.pets-icon-8, +.pets-icon-10, +.pets-icon-12 { + background-position: 0 -30px; + width: 19px; + height: 14px; +} + +.pets-icon-2 { + background-position: 0 -54px; + width: 21px; + height: 14px; +} + +.pets-icon-3, +.pets-icon-9 { + background-position: 0 -78px; + width: 19px; + height: 14px; +} + +.pets-icon-4, +.pets-icon-5, +.pets-icon-6, +.pets-icon-7 { + background-position: 0 -102px; + width: 19px; + height: 12px; +} + +.pets-icon-11 { + background-position: 0 -124px; + width: 24px; + height: 20px; +} + +.pets-icon-13 { + background-position: 0 -154px; + width: 15px; + height: 15px; +} diff --git a/digsby/src/plugins/component_tagged/res/infobox.js b/digsby/src/plugins/component_tagged/res/infobox.js new file mode 100644 index 0000000..59c315b --- /dev/null +++ b/digsby/src/plugins/component_tagged/res/infobox.js @@ -0,0 +1,22 @@ +/* TODO Fix menu */ +newsfeed = $('.tagged-newsfeed'); +menu = newsfeed.find('.menu'); + +elections_feed = newsfeed.find('.elections-feed'); +pets_feed = newsfeed.find('.pets-feed'); + +selected = elections_feed; + +$(function() { + menu.find('.id-button-elections').click(function() { + selected.hide(); + elections_feed.show(); + selected = elections_feed; + }); + + menu.find('.id-button-pets').click(function() { + selected.hide(); + pets_feed.show(); + selected = pets_feed; + }); +}); \ No newline at end of file diff --git a/digsby/src/plugins/component_tagged/res/pets_icons.png b/digsby/src/plugins/component_tagged/res/pets_icons.png new file mode 100644 index 0000000..b211b92 Binary files /dev/null and b/digsby/src/plugins/component_tagged/res/pets_icons.png differ diff --git a/digsby/src/plugins/component_tagged/res/strings/__init__.py b/digsby/src/plugins/component_tagged/res/strings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/component_tagged/res/strings/alerts.py b/digsby/src/plugins/component_tagged/res/strings/alerts.py new file mode 100644 index 0000000..fc4da72 --- /dev/null +++ b/digsby/src/plugins/component_tagged/res/strings/alerts.py @@ -0,0 +1,122 @@ +# Alerts pop up messages (Note. some are missing) + +popup_strings = { + 'action_slots' : {'string' : _('Action Slots bonus'), + 'link' : 'apps/actionslots.html'}, + 'action_tabs' : {'string' : _('Action Tabs bonus'), + 'link' : 'apps/actiontabs.html'}, + 'birthdays' : {'string' : _('Upcoming birthday'), + 'link' : 'birthdays.html'}, + 'cafe_food_ready' : {'string' : _('Food is ready to serve'), + 'link' : 'apps/cafe.html?source=A&type=R'}, + 'cafe_food_rotting' : {'string' : _('Food is rotting'), + 'link' : 'apps/cafe.html?source=A&type=O'}, + 'cafe_needs_love' : {'string' : _('Your Cafe needs love'), + 'link' : 'apps/cafe.html?source=A&type=L'}, + 'cafe_visit' : {'string' : _('Your Cafe is open!'), + 'link' : 'apps/cafe.html?source=A&type=V'}, + 'cafe_visit_clean' : {'string' : _('Someone helped you'), + 'link' : 'apps/cafe.html?source=A&type=C'}, + 'cafe_visit_eat' : {'string' : _('Someone left you a tip'), + 'link' : 'apps/cafe.html?source=A&type=E'}, + 'cafe_waiter_contract_expired' : {'string' : _('Cafe contract expired'), + 'link' : 'apps/cafe.html?source=A&type=X'}, + 'comments' : {'string' : _('New comment'), + 'link' : ''}, + 'cr_photo_warning' : {'string' : _('Photo Violation'), + 'link' : 'rejected_photos.html'}, + 'elections_job_completed' : {'string' : _('Collect your funds'), + 'link' : 'apps/elections.html?so=a0'}, + 'elections_favor' : {'string' : _('Thank your collaborator'), + 'link' : 'apps/elections.html?so=a1'}, + 'elections_hired' : {'string' : _("You've been hired!"), + 'link' : 'apps/elections.html?so=a2'}, + 'elections_new_round' : {'string' : _('New Elections round!'), + 'link' : 'apps/elections.html?so=a3'}, + 'elections_promo' : {'string' : _('New Game! Play Elections!'), + 'link' : 'apps/elections.html?so=a4'}, + 'farm_ready' : {'string' : _('Crops are ready to harvest!'), + 'link' : 'farm.html'}, + 'farm_visit' : {'string' : _('Visit your Farm'), + 'link' : 'farm.html'}, + 'find_friend' : {'string' : _('Find more friends'), + 'link' : 'friends.html?source=findalert#tab=3'}, + 'new_friends' : {'string' : _('New friend'), + 'link' : 'friends.html?new_friends=1#tab=0&type=0&filterpg=New_0'}, + 'friend_requests' : {'string' : _('New friend request'), + 'link' : 'friends.html#tab=2'}, + 'gifts' : {'string' : _('New gift'), + 'link' : 'gifts.html#t=t1'}, + 'gold_2' : {'string' : _('New Gold offers'), + 'link' : 'gold.html?ll=digsby_alerts#t=tearn_gold'}, + 'groups_updated' : {'string' : _('Groups updated'), + 'link' : 'groups.html#h=1_0_0'}, + 'group_forum_reply' : {'string' : _('Forum topic replies'), + 'link' : 'my_forum_topics.html#h=0_0_0_0_0'}, + 'group_invite' : {'string' : _('New group invite'), + 'link' : 'group_invites.html'}, + 'invite_friend' : {'string' : _('New invite to send'), + 'link' : 'friends.html?source=invitealert#tab=contacts'}, + 'luv' : {'string' : _('New Luv'), + 'link' : 'luv.html'}, + 'meetme_headliner_reup' : {'string' : _('Headliner Reup'), + 'link' : ''}, + 'meetme_spotlight_reup' : {'string' : _('Spotlight Reup'), + 'link' : ''}, + 'meetme_match' : {'string' : _('New Meet Me match'), + 'link' : 'meetme.html?match=1#t=t2&y=0&m=0'}, + 'meetme_yes' : {'string' : _('Meet Me interests'), + 'link' : 'meetme.html'}, + 'unread_messages' : {'string' : _('New message'), + 'link' : 'messages.html'}, + 'mob_boost' : {'string' : _('Mafia booster ready!'), + 'link' : 'apps/mafia.html?source=B'}, + 'mob_fight' : {'string' : _('New Mafia fight'), + 'link' : 'apps/mafia.html?source=F#h=/fight/fight_enemies'}, + 'mob_hire' : {'string' : _('New Mafia boss'), + 'link' : 'apps/mafia.html?source=H#h=/character'}, + 'mob_promo' : {'string' : _('New Mafia jobs'), + 'link' : 'apps/mafia.html?source=P'}, + 'mob_promo_simple' : {'string' : _('New Mafia jobs'), + 'link' : 'apps/mafia.html?source=Z'}, + 'mob_prop_rdy' : {'string' : _('Mafia property ready'), + 'link' : 'apps/mafia.html?source=R#h=/properties'}, + 'mobile_new_update' : {'string' : _('New Mobile Update'), + 'link' : 'tagged_mobile.html'}, + 'pending_comment' : {'string' : _('New pending comment'), + 'link' : 'view_comments.html#t=pending_1'}, + 'pets' : {'string' : _('Pets updates'), + 'link' : 'pets.html'}, + 'photo_comments' : {'string' : _('New photo comment'), + 'link' : 'notifications.html'}, + 'poker_2' : {'string' : _('Bonus poker chips'), + 'link' : 'poker.html'}, + 'profile_photo' : {'string' : _('No profile photo!'), + 'link' : 'add_photo_interstitial.html'}, + 'profile_viewers' : {'string' : _('New profile viewer'), + 'link' : 'friends.html#tab=5'}, + 'questions_answer' : {'string' : _('New answer'), + 'link' : 'questions.html?tab=answer'}, + 'questions' : {'string' : _('New question'), + 'link' : 'questions.html?tab=my_questions'}, + 'sororitylife' : {'string' : _('New Sorority events'), + 'link' : 'sororitylife.html'}, + 'sororitylife_new_version' : {'string' : _('Sorority: new version'), + 'link' : 'sororitylife.html'}, + 'tags' : {'string' : _('New Tag'), + 'link' : 'tags.html#t=t1&mt=0_0&st=0_All_0'}, + 'topics_promo' : {'string' : _('New Topics for you'), + 'link' : 'conversations.html?source=lp'}, + 'topics_new' : {'string' : _('Conversations for you'), + 'link' : 'conversations.html?source=ln'}, + 'topics_response' : {'string' : _('New conversation post'), + 'link' : 'conversations.html?source=lr'}, + 'videos' : {'string' : _('New shared video'), + 'link' : 'http://video.tag-local.com/#op=to_you'}, + 'wink' : {'string' : _('New wink'), + 'link' : 'wink.html'}, + 'zoosk_coin' : {'string' : _('Free Zoosk coins'), + 'link' : 'apps/zoosk.html?z_page=home.php&from=digsby-coinalert&api_signature=8e49b20dcf4f6eeded15553909608617'}, + 'zoosk_message' : {'string' : _('New Zoosk messages'), + 'link' : 'apps/zoosk.html?z_page=inbox.php&from=digsby-flirtalert&api_signature=233cd9eb02f9a19d427ebac3f511dda4'} +} \ No newline at end of file diff --git a/digsby/src/plugins/component_tagged/res/strings/elections.py b/digsby/src/plugins/component_tagged/res/strings/elections.py new file mode 100644 index 0000000..003b3a3 --- /dev/null +++ b/digsby/src/plugins/component_tagged/res/strings/elections.py @@ -0,0 +1,119 @@ +# Elections newsfeed. Seconday strings are not being used at this point + +newsfeed_strings = [ + { # VOTED + 'primary' : {'string' : _('You voted "%s" for %s'), + 'params' : ['issue_vote', 'issue_title']}, + 'secondary' : {'string' : _('%s voted "%s" for %s'), + 'params' : ['displayname', 'issue_vote', 'issue_title']} + }, { # ADDED STAFF + 'primary' : {'string' : _('You hired %s'), + 'params' : ['target_name']}, + 'secondary' : {'string' : _('%s hired %s'), + 'params' : ['displayname', 'target_name']} + }, { # ADDED AS STAFF + 'primary' : {'string' : _('%s hired you'), + 'params' : ['target_name']}, + 'secondary' : {'string' : _('%s hired %s'), + 'params' : ['target_name', 'displayname']}, + }, { # STARTED PROJECT + 'primary' : {'string' : _('You started the project "%s"'), + 'params' : ['project_title']}, + 'secondary' : {'string' : _('%s started the project "%s"'), + 'params' : ['displayname', 'project_title']} + }, { # CONTRIBUTED + 'primary' : {'string' : _('You contributed to the project "%s"'), + 'params' : ['project_title']}, + 'secondary' : {'string' : _('%s contributed to the project "%s"'), + 'params' : ['displayname', 'project_title']} + }, { # PROJECT COMPLETED + 'primary' : {'string' : _('Project "%s" completed'), + 'params' : ['project_title']}, + 'secondary' : {'string' : _('Project "%s" completed'), + 'params' : ['project_title']} + }, { # EARNED FAME + 'primary' : {'string' : _('You earned %s Fame from completing Project "%s"'), + 'params' : ['fame', 'project_title']}, + 'secondary' : {'string' : _('%s earned %s Fame from completing Project "%s"'), + 'params' : ['displayname', 'fame', 'project_title']} + }, { # GOT COLLABORATORS + 'primary' : {'string' : _('You gained %s new collaborators'), + 'params' : ['collaborators']}, + 'secondary' : {'string' : _('%s gained %s new collaborators'), + 'params' : ['displayname', 'collaborators']} + }, { # EARNED VOTES + 'primary' : {'string' : _('Your party gained %s votes'), + 'params' : ['votes']}, + 'secondary' : {'string' : _('%s gained %s votes'), + 'params' : ['party', 'votes']} + }, { # GAVE FAVOR + 'primary' : {'string' : _('You gave %s a favor'), + 'params' : ['target_name']}, + 'secondary' : {'string' : _('%s gave %s a favor'), + 'params' : ['displayname', 'target_name']} + }, { # GOT FAVOR + 'primary' : {'string' : _('%s did a favor for you'), + 'params' : ['target_name']}, + 'secondary' : {'string' : _('%s did a favor for %s'), + 'params' : ['target_name', 'displayname']} + }, { # THANKED + 'primary' : {'string' : _('You thanked %s'), + 'params' : ['target_name']}, + 'secondary' : {'string' : _('%s thanked %s'), + 'params' : ['displayname', 'target_name']} + }, { # GOT THANKED + 'primary' : {'string' : _('%s thanked you'), + 'params' : ['target_name']}, + 'secondary' : {'string' : _('%s thanked %s'), + 'params' : ['target_name', 'displayname']} + }, { # LEVELED UP + 'primary' : {'string' : _('You leveled up'), + 'params' : []}, + 'secondary' : {'string' : _('%s leveled up'), + 'params' : ['displayname']} + }, { # JOINED PARTY + 'primary' : {'string' : _('You joined the %s party'), + 'params' : ['party']}, + 'secondary' : {'string' : _('%s joined the %s party'), + 'params' : ['displayname', 'party']} + }, { # PARTY CAPTAIN + 'primary' : {'string' : _('You became a Party Captain'), + 'params' : []}, + 'secondary' : {'string' : _('%s became a Party Captain'), + 'params' : ['displayname']} + }, { # PARTY LEADER + 'primary' : {'string' : _('You became a Party Leader'), + 'params' : []}, + 'secondary' : {'string' : _('%s became a Party Leader'), + 'params' : ['displayname']} + }, { # PROJECT FAILED + 'primary' : {'string' : _('The "%s" you contributed to was unable to get fully funded'), + 'params' : ['project_title']}, + 'secondary' : {'string' : _('The "%s" %s contributed to was unable to get fully funded'), + 'params' : ['project_title', 'displayname']} + }, { # GOT NEW COLLABORATORS + 'primary' : {'string' : _('You earned %s new Collaborators from completing "%s"'), + 'params' : ['collaborators', 'project_title']}, + 'secondary' : {'string' : _('%s earned %s new Collaborators from completing "%s"'), + 'params' : ['displayname', 'collaborators', 'project_title']} + }, { # GOT FAVOR BONUS + 'primary' : {'string' : _('%s called in a Favor for your Staff Job earning you a bonus of %s funds'), + 'params' : ['target_name', 'funds']}, + 'secondary' : {'string' : _('%s called in a Favor for %s\'s Staff Job earning them a bonus of %s funds'), + 'params' : ['target_name', 'displayname', 'funds']} + }, { # GOT THANKED BONUS + 'primary' : {'string' : _('%s thanked you for your Favor earning you a bonus of %s Fame'), + 'params' : ['target_name', 'fame']}, + 'secondary' : {'string' : _('%s thanked %s for their Favor, earning them a bonus of %s Fame'), + 'params' : ['target_name', 'displayname', 'fame']} + }, { # GOT STARTING FUNDS + 'primary' : {'string' : _('You received %s funds for the new Election!'), + 'params' : ['funds']}, + 'secondary' : {'string' : _('%s received %s funds for the new Election!'), + 'params' : ['displayname', 'funds']} + }, { # THANKED BONUS + 'primary' : {'string' : _('You thanked %s for calling in a favor, earning you a bonus of %s funds'), + 'params' : ['target_name', 'funds']}, + 'secondary' : {'string' : _('%s thanked %s for calling in a favor, earning them a bonus of %s funds'), + 'params' : ['displayname', 'target_name', 'funds']}, + }] \ No newline at end of file diff --git a/digsby/src/plugins/component_tagged/res/strings/pets.py b/digsby/src/plugins/component_tagged/res/strings/pets.py new file mode 100644 index 0000000..30d0855 --- /dev/null +++ b/digsby/src/plugins/component_tagged/res/strings/pets.py @@ -0,0 +1,122 @@ +# Pets newsfeed. Seconday strings are not being used at this point + +achievement_strings = { + 'ready_to_play' : _('Ready To Play'), + 'pets_lover' : _('Pets Lover'), + 'frequent_shopper' : _('Frequent Shopper'), + 'perfect_attendance' : _('Perfect Attendance'), + 'successful_tycoon' : _('Successful Tycoon'), + 'hot_stuff' : _('Hot Stuff'), + 'big_spender' : _('Big Spender') +} + +newsfeed_strings = [ + { # EARNED + 'primary' : lambda *a: + {'string' : _('You earned a %s bonus for signing in to Tagged!'), + 'params' : ['earned_amount']}, + 'secondary' : lambda *a: + {'string' : _('%s earned a %s bonus for signing in to Tagged!'), + 'params' : ['target_link', 'earned_amount']} + }, { # BOUGHT PET + 'primary' : lambda event, *a: + {'string' : _('You bought %s from %s for %s.') if event['owner_id'] else + _('You bought %s for %s.'), + 'params' : ['pet_link', 'owner_link', 'purchase_price'] if event['owner_id'] else + ['pet_link', 'purchase_price']}, + 'secondary' : lambda event, *a: + {'string' : _('%s bought %s from %s for %s.') if event['owner_id'] else + _('%s bought %s for %s.'), + 'params' : ['target_link', 'pet_link', 'owner_link', 'purchase_price'] if event['owner_id'] else + ['target_link', 'pet_link', 'purchase_price']} + }, { # BOUGHT YOU + 'primary' : lambda *a: + {'string' : _('%s bought you for %s earning you %s.'), + 'params' : ['owner_link', 'purchase_price', 'earned_amount']}, + 'secondary' : lambda event, *a: + {'string' : _('%s bought %s for %s earning %s %s.') if event.earned_amount else + _('%s bought %s for %s.'), + 'params' : ['owner_link', 'target_link', 'purchase_price', 'target_link', 'earned_amount'] if event.earned_amount else + ['owner_link', 'target_link', 'purchase_price']} + }, { # BOUGHT YOUR PET + 'primary' : lambda *a: + {'string' : _('%s bought your pet %s for %s earning you %s (%s profit).'), + 'params' : ['owner_link', 'pet_link', 'purchase_price', 'earned_amount', 'profit_amount']}, + 'secondary' : lambda *a: + {'string' : _("%s bought %s's pet %s for %s earning %s %s (%s profit)."), + 'params' : ['owner_link', 'target_link', 'pet_link', 'purchase_price' 'target_link', 'earned_amount', 'profit_amount']} + }, { # PET WAS SET FREE + 'primary' : lambda *a: + {'string' : _('Your pet %s was set free for %s earning you %s (%s profit).'), + 'params' : ['pet_link', 'setfree_price', 'earned_amount', 'profit_amount']}, + 'secondary' : lambda *a: + {'string' : _("%s's pet %s was set free for %s earning %s %s (%s profit)."), + 'params' : ['target_link', 'pet_link', 'setfree_price', 'target_link', 'earned_amount', 'profit_amount']} + }, { # WERE SET FREE + 'primary' : lambda *a: + {'string' : _('%s set you free.'), + 'params' : ['owner_link']}, + 'secondary' : lambda *a: + {'string' : _('%s set %s free.'), + 'params' : ['owner_link', 'target_link']} + }, { # SET SELF FREE + 'primary' : lambda *a: + {'string' : _('You set yourself free from %s for %s.'), + 'params' : ['owner_link', 'setfree_price']}, + 'secondary' : lambda *a: + {'string' : _('%s set %s free from %s for %s.'), + 'params' : ['owner_link', 'gender', 'owner_link', 'setfree_price']} + }, { # SET PET FREE + 'primary' : lambda *a: + {'string' : _('You set %s free.'), + 'params' : ['pet_link']}, + 'secondary' : lambda *a: + {'string' : _('%s set %s free.'), + 'params' : ['target_link', 'pet_link']} + }, { # FIRST TO OWN + 'primary' : lambda *a: + {'string' : _('You bought %s for %s and earned a %s bonus for being the first owner.'), + 'params' : ['pet_link', 'purchase_price', 'bonus_price']}, + 'secondary' : lambda *a: + {'string' : _('%s bought %s for %s and earned a %s bonus for being the first owner.'), + 'params' : ['target_link', 'pet_link', 'purchase_price', 'bonus_price']} + }, { # PET WAS DELETED + 'primary' : lambda *a: + {'string' : _('%s has been deleted so you have been credited %s.'), + 'params' : ['pet_name', 'refund_amount']}, + 'secondary' : lambda *a: + {'string' : _('%s has been deleted so %s has been credited %s.'), + 'params' : ['pet_name', 'target_link', 'refund_amount']} + }, { # BONUS FROM BUYING PET + 'primary' : lambda *a: + {'string' : _('You earned %s bonus after buying %s.'), + 'params' : ['bonus_amount', 'pet_link']}, + 'secondary' : lambda *a: + {'string' : _('%s earned %s bonus after buying %s.'), + 'params' : ['owner_link', 'bonus_amount', 'pet_link']} + }, { # EARNED ACHIEVEMENT + 'primary' : lambda *a: + {'string' : _('You earned a "%s" achievement!'), + 'params' : ['achievement_name']}, + 'secondary' : lambda *a: + {'string' : _('%s earned a "%s" achievement!'), + 'params' : ['target_link', 'achievement_name']} + }, { # BOUGHT AND COLLECTED PURCHASE BONUS + 'primary' : lambda event, *a: + {'string' : _('You bought %s from %s for %s and earned a %s bonus.') if event.owner_id else + _('You bought %s for %s and earned a %s bonus.'), + 'params' : ['pet_link', 'owner_link', 'purchase_price', 'bonus_amount'] if event.owner_id else + ['pet_link', 'purchase_price', 'bonus_amount']}, + 'secondary' : lambda event, *a: + {'string' : _('%s bought %s from %s for %s and earned a %s bonus.') if event.owner_id else + _('%s bought %s for %s and earned a %s bonus.'), + 'params' : ['target_link', 'pet_link', 'owner_link', 'purchase_price', 'bonus_amount'] if event.owner_id else + ['target_link', 'pet_link', 'purchase_price', 'bonus_amount']} + }, { # UNLOCKED NEWSFEED + 'primary' : lambda *a: + {'string' : _('You activated your newsfeed!'), + 'params' : []}, + 'secondary' : lambda *a: + {'string' : _('%s activated their newsfeed!'), + 'params' : ['target_link']} + }] diff --git a/digsby/src/plugins/component_yahooim/__init__.py b/digsby/src/plugins/component_yahooim/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/component_yahooim/info.yaml b/digsby/src/plugins/component_yahooim/info.yaml new file mode 100644 index 0000000..230ae07 --- /dev/null +++ b/digsby/src/plugins/component_yahooim/info.yaml @@ -0,0 +1,40 @@ +service_provider: &service_provider yahoo +name: !_ "Yahoo" +name_truncated: yaho +service_name: !_ "Yahoo Messenger" +shortname: yahoo +popularity: 860 +path: yahoo.protocol +username_desc: !_ "Yahoo! ID" +newuser_url: https://edit.yahoo.com/registration +password_url: https://edit.yahoo.com/forgot +needs_httponly: yes +compatible: + - yahoo + - msn + - lcs + - sametime + - pingbox +component_type: im +type: service_component +defaults: + use_http_only: False + autologin: False + server: !python/tuple + - scs.msg.yahoo.com + - 5050 + block_unknowns: False +has_invisible: True +statuses: + - - Available + - - Away + +entry_points: + digsby.component.im: + *service_provider: yahoo.YahooProtocol:YahooProtocol + +skin: + serviceicons: + lcs: res/lcs.png + pingbox: res/icn_wdgt_pbx_48_1.gif + sametime: res/sametime.png diff --git a/digsby/src/plugins/component_yahooim/res/icn_wdgt_pbx_48_1.gif b/digsby/src/plugins/component_yahooim/res/icn_wdgt_pbx_48_1.gif new file mode 100644 index 0000000..d136573 Binary files /dev/null and b/digsby/src/plugins/component_yahooim/res/icn_wdgt_pbx_48_1.gif differ diff --git a/digsby/src/plugins/component_yahooim/res/lcs.png b/digsby/src/plugins/component_yahooim/res/lcs.png new file mode 100644 index 0000000..5ca66bc Binary files /dev/null and b/digsby/src/plugins/component_yahooim/res/lcs.png differ diff --git a/digsby/src/plugins/component_yahooim/res/sametime.png b/digsby/src/plugins/component_yahooim/res/sametime.png new file mode 100644 index 0000000..0b31276 Binary files /dev/null and b/digsby/src/plugins/component_yahooim/res/sametime.png differ diff --git a/digsby/src/plugins/component_ymail/__init__.py b/digsby/src/plugins/component_ymail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/component_ymail/info.yaml b/digsby/src/plugins/component_ymail/info.yaml new file mode 100644 index 0000000..f4e3f80 --- /dev/null +++ b/digsby/src/plugins/component_ymail/info.yaml @@ -0,0 +1,21 @@ +service_provider: &provider_id yahoo +path: mail.ymail.YahooMail +name: !_ "Yahoo! Mail" +service_name: !_ "Yahoo Mail" +shortname: ymail +name_truncated: "ymai" +popularity: 599 +username_desc: !_ "Yahoo! ID" +form: email +type: service_component +component_type: email +newuser_url: "https://edit.yahoo.com/config/eval_register?new=1&.done=http%3A//mail.yahoo.com&.src=ym" +password_url: "https://edit.yahoo.com/forgot?login=&intl=us&done=http%3a//mail.yahoo.com&src=ym" +needs_webclient: no +defaults: + updatefreq: 300 +whitelist_opts: ["enabled", "updatefreq", "alias"] + +entry_points: + digsby.component.email: + *provider_id: mail.ymail:YahooMail diff --git a/digsby/src/plugins/digsby_about/__init__.py b/digsby/src/plugins/digsby_about/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsby_about/aboutdialog.py b/digsby/src/plugins/digsby_about/aboutdialog.py new file mode 100644 index 0000000..abeb952 --- /dev/null +++ b/digsby/src/plugins/digsby_about/aboutdialog.py @@ -0,0 +1,174 @@ +''' +An addon that controls the about dialog. + +The about dialog is a window that can be opened from the help menu of the main +buddy list frame. The main content of the window is in HTML, displayed in a +webkit window. + +When opened, or when the digsby logo is clicked, an update check is initiated. +While the check is being performed a spinner is displayed. +When it completes, and there's an update available, the update is initiated in +the background. Clicking the link will open the file transfer dialog which shows +the update progress. If there's no update available, that'll be displayed and +the user can initiate another update if they want. + +This is all accomplished with some neato hook magic. I believe this is the first +GUI component of the app that qualifies as a plugin :) +''' +import wx +import sys +import os +import util +import path +import hooks +import common +import gui.browser.webkit as webkit +import rpc.jsonrpc as jsonrpc + +import protocols +import peak.util.addons as addons + +import gui.infobox.interfaces as gui_interfaces +import gui.infobox.providers as gui_providers + +import logging +log = logging.getLogger('aboutdlg') + +class AboutAddon(addons.AddOn): + def setup(self, *a): + self.dlg = None + + def Show(self): + self.maybe_make_dialog() + self.dlg.Show() + self.dlg.js("get_update_status();") + + def maybe_make_dialog(self): + if self.dlg is None: + self.dlg = AboutDialog(None) + + def update_check_result(self, update_required): + if self.dlg is not None: + self.dlg.update_check_result(update_required) + +class AboutDialog(wx.Dialog, jsonrpc.RPCClient): + + _rpc_handlers = { + 'check_for_updates' : 'check_for_updates', + 'get_update_status' : 'get_update_status', + 'show_filetransfer_window': 'show_filetransfer_window', + } + + def __init__(self, parent, *a, **k): + wx.Dialog.__init__(self, parent, *a, **k) + jsonrpc.RPCClient.__init__(self) + self.SetTitle(_("About Digsby")) + self._update_check_rpc_id = None + self.construct() + self.bind_events() + + def construct(self): + + self.webview = webkit.WebKitWindow(self) + self.webview.WebSettings.SetAllowUniversalAccessFromFileURLs(True) + self.webview.SetSize((191, 264)) + self.refresh_content() + + self.bridge = jsonrpc.JSPythonBridge(self.webview) + self.bridge.on_call += lambda x: self.json(x, self.webview) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.webview, 1, wx.EXPAND | wx.ALL) + + self.Fit() + + def bind_events(self): + pass + + def refresh_content(self): + acp = AboutContentProvider() + self.webview.SetHTML(acp.get_html()) + + def _default_rpc(self, rpc, webview, id, *a, **k): + log.info("jscall: %r, %r, %r, %r, %r", rpc, webview, id, a, k) + + def check_for_updates(self, rpc, webview, id): + if self._update_check_rpc_id is not None: + return self.Derror(self.webview, id, message="checking") + + self._update_check_rpc_id = id + log.info("Requesting update check from about dialog") + hooks.notify('digsby.updater.check') + + def get_update_status(self, rpc, webview, id): + for hook in hooks.Hook("digsby.updater.status"): + res = hook() + if res: + self.Dsuccess(webview, id, status = res) + break + else: + self.Dsuccess(webview, id, status = 'idle') + + def update_check_result(self, update_required): + if self._update_check_rpc_id is None: + return + + id, self._update_check_rpc_id = self._update_check_rpc_id, None + + self.Dsuccess(self.webview, id, update_required = update_required) + + def show_filetransfer_window(self, rpc, webview, id, *a): + import gui.filetransfer as ft + wx.CallAfter(ft.FileTransferDialog.Display) + + self.Dsuccess(webview, id) + self.Show(False) + + def js(self, s): + self.webview.RunScript(s) + +class AboutContentProvider(gui_providers.InfoboxProviderBase): + javascript_libs = [ + 'jquery', + 'json', + 'utils', + ] + + def get_app_context(self, ctxt_class): + return ctxt_class(path.path(__file__).parent.parent, 'digsby_about') + + def get_context(self): + ctxt = super(AboutContentProvider, self).get_context() + ctxt.update( + tag = sys.TAG, + revision = sys.REVISION, + ) + return ctxt + +def help_menu_items(*a): + return [(_("&About Digsby"), show_dialog)] + +def get_addon(): + p = common.profile() + if p is not None: + return AboutAddon(p) + else: + return None + +def show_dialog(*a): + a = get_addon() + if a is not None: + a.Show() + +def on_update_check_complete(update_required, *a): + a = get_addon() + if a is not None: + a.update_check_result(update_required) + +if __name__=='__main__': + + from tests.testapp import testapp + a = testapp('..\\..\\', username = 'mike') + + AboutDialog(None).Show() + a.MainLoop() diff --git a/digsby/src/plugins/digsby_about/info.yaml b/digsby/src/plugins/digsby_about/info.yaml new file mode 100644 index 0000000..fddfe1d --- /dev/null +++ b/digsby/src/plugins/digsby_about/info.yaml @@ -0,0 +1,11 @@ +name: 'DigsbyAbout' +path: 'digsby_about' +type: 'pure' + +entry_points: + digsby.profile.addons: + digsby_about: digsby_about.aboutdialog:AboutAddon + digsby.help.actions: + digsby_about: digsby_about.aboutdialog:help_menu_items + digsby.updater.update_check_results: + digsby_about: digsby_about.aboutdialog:on_update_check_complete diff --git a/digsby/src/plugins/digsby_about/res/ajax-loader.gif b/digsby/src/plugins/digsby_about/res/ajax-loader.gif new file mode 100644 index 0000000..5b33f7e Binary files /dev/null and b/digsby/src/plugins/digsby_about/res/ajax-loader.gif differ diff --git a/digsby/src/plugins/digsby_about/res/content.tenjin b/digsby/src/plugins/digsby_about/res/content.tenjin new file mode 100644 index 0000000..3cfa067 --- /dev/null +++ b/digsby/src/plugins/digsby_about/res/content.tenjin @@ -0,0 +1,35 @@ +
+
+ +
+
+ Digsby + - Build + ${revision} +
+
+
+ + + ${{_('Checking for updates...')}} + +
+
+ + + ${{_('Digsby is up to date')}} + +
+
+ + + ${{_('Update Available')}} + +
+
+ +
diff --git a/digsby/src/plugins/digsby_about/res/download.png b/digsby/src/plugins/digsby_about/res/download.png new file mode 100644 index 0000000..dd6199e Binary files /dev/null and b/digsby/src/plugins/digsby_about/res/download.png differ diff --git a/digsby/src/plugins/digsby_about/res/head.tenjin b/digsby/src/plugins/digsby_about/res/head.tenjin new file mode 100644 index 0000000..70ed876 --- /dev/null +++ b/digsby/src/plugins/digsby_about/res/head.tenjin @@ -0,0 +1,6 @@ + diff --git a/digsby/src/plugins/digsby_about/res/infobox.css b/digsby/src/plugins/digsby_about/res/infobox.css new file mode 100644 index 0000000..96956c9 --- /dev/null +++ b/digsby/src/plugins/digsby_about/res/infobox.css @@ -0,0 +1,43 @@ +body { + font-family: "MS Shell Dlg 2", sans-serif; + -webkit-user-select: none; + cursor: default; +} + +div { + font-family: "MS Shell Dlg 2", sans-serif; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +#need-update { + cursor: pointer; +} +.smalltext { + font-size: 8pt; +} +.green { + font-size: 12pt; + color: #009600; +} + +#copyright { + padding-top: .6em; +} + +#maintext { + padding-top: .6em; +} + +#update { + padding-top: .6em; +} +#update div { + display: none; +} + +#update div * { + vertical-align: middle; +} + diff --git a/digsby/src/plugins/digsby_about/res/infobox.js b/digsby/src/plugins/digsby_about/res/infobox.js new file mode 100644 index 0000000..e4ee2ff --- /dev/null +++ b/digsby/src/plugins/digsby_about/res/infobox.js @@ -0,0 +1,53 @@ +function check_for_updates() { + show_update_section("checking"); + D.rpc("check_for_updates", {}, + function(args) { + if (args.update_required) { + show_update_section("need-update"); + } else { + show_update_section("up-to-date"); + } + }, + function(error) { + if (error.message=="already checking") { + //show_update_section("checking"); + } + } + ); +} + +function get_update_status() { + D.rpc("get_update_status", {}, + function (args) { + console.log("updating? " + args.status); + if (args.status == "checking") { + show_update_section("checking"); + } else if ((args.status == "downloading") || (args.status == "filechecking")) { + show_update_section("need-update"); + } else { + check_for_updates(); + } + } + ); +} + +function perform_update() { + D.rpc("show_filetransfer_window"); + show_update_section("none"); +} + +var update_sections = ["checking", "up-to-date", "need-update"]; + +function show_update_section(which) { + for (var i in update_sections) { + var name = update_sections[i]; + var node = $("#update #" + name); + if (name == which) { + node.show(); + } else { + node.hide(); + } + } +} + +show_update_section("none"); diff --git a/digsby/src/plugins/digsby_about/res/latest.png b/digsby/src/plugins/digsby_about/res/latest.png new file mode 100644 index 0000000..cd9529e Binary files /dev/null and b/digsby/src/plugins/digsby_about/res/latest.png differ diff --git a/digsby/src/plugins/digsby_branding/__init__.py b/digsby/src/plugins/digsby_branding/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsby_branding/info.yaml b/digsby/src/plugins/digsby_branding/info.yaml new file mode 100644 index 0000000..ce5bafa --- /dev/null +++ b/digsby/src/plugins/digsby_branding/info.yaml @@ -0,0 +1,19 @@ +name: 'Branding' +path: 'digsby_branding' +type: 'pure' + +entry_points: + digsby.status.url.for_protocol: + iac: digsby_branding.urls:iac_url + digsby.status.url.for_protocol_re: + iac: digsby_branding.urls:iac_url + digsby.promote.url: + iac: digsby_branding.urls:iac_url + digsby.inviter.email.code: + iac: digsby_branding.urls:iac_code + digsby.facebook.newsfeed.campaign: + iac: digsby_branding.urls:iac_code + digsby.myspace.newsfeed.campaign: + iac: digsby_branding.urls:iac_code + digsby.twitter.demovideo.link: + iac: digsby_branding.urls:iac_url diff --git a/digsby/src/plugins/digsby_branding/urls.py b/digsby/src/plugins/digsby_branding/urls.py new file mode 100644 index 0000000..1d33c7b --- /dev/null +++ b/digsby/src/plugins/digsby_branding/urls.py @@ -0,0 +1,5 @@ +def iac_url(*a, **k): + return "http://bit.ly/GetDigsby" + +def iac_code(*a, **k): + return 'iac' diff --git a/digsby/src/plugins/digsby_browsersettings/__init__.py b/digsby/src/plugins/digsby_browsersettings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsby_browsersettings/info.yaml b/digsby/src/plugins/digsby_browsersettings/info.yaml new file mode 100644 index 0000000..ed1eaed --- /dev/null +++ b/digsby/src/plugins/digsby_browsersettings/info.yaml @@ -0,0 +1,12 @@ +name: 'Browser Settings' +path: 'digsby_browsersettings' +type: 'pure' +platforms: ['win'] + +entry_points: + digsby.profile.addons: + browsersettings: digsby_browsersettings.settings:BrowserSettingsAddon + digsby.browsersettings.set_homepage: + browsersettings: digsby_browsersettings.settings:set_homepage + digsby.browsersettings.set_search: + browsersettings: digsby_browsersettings.settings:set_search diff --git a/digsby/src/plugins/digsby_browsersettings/settings.py b/digsby/src/plugins/digsby_browsersettings/settings.py new file mode 100644 index 0000000..99d34a8 --- /dev/null +++ b/digsby/src/plugins/digsby_browsersettings/settings.py @@ -0,0 +1,432 @@ +import logging +import ConfigParser as CF +import json + +import stdpaths +import path + +import common +import util +import peak.util.addons as addons +import gui.native.win.process as processes + +from contextlib import closing + +log = logging.getLogger("browsersettings") + +HOMEPAGE_URL = 'http://search.digsby.com' +DIGSBY_SEARCH_URL = 'http://searchbox.digsby.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t' +DIGSBY_SEARCH_UUID_IE = "{3326ab56-742e-5603-906f-290517220122}" + +class BrowserSettingsEditor(object): + process_name = None + + def is_installed(self): + return False + + def can_edit(self): + return not self.is_running() + + def is_running(self): + return self.process_name in processes.process_list() + + def set_homepage(self): + return False + + def set_search(self): + return False + + def __repr__(self): + return '<%s>' % (type(self).__name__,) + +class InternetExplorer(BrowserSettingsEditor): + process_name = 'iexplore.exe' + + def can_edit(self): + return True + + def is_installed(self): + # even if it's not (EU? user uninstalled?) we can still edit the registry. + return True + + def set_homepage(self): + ''' + Function AddGoogleHomePage_IE + WriteRegStr HKCU "Software\Microsoft\Internet Explorer\Main" "Start Page" "http://search.digsby.com" + FunctionEnd + ''' + log.info("setting homepage for %r", self.process_name) + import _winreg + + with util.traceguard: + k = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, '''Software\\Microsoft\\Internet Explorer\\Main\\''', 0, _winreg.KEY_SET_VALUE) + _winreg.SetValueEx(k, 'Start Page', None, _winreg.REG_SZ, 'http://search.digsby.com') + k.Close() + + return True + + def set_search(self): + ''' + Function AddGoogleSearchEngine_IE + WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\SearchScopes\${DIGSBY_SEARCH_UUID_IE}" DisplayName "Google Powered Digsby Search" + WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\SearchScopes\${DIGSBY_SEARCH_UUID_IE}" URL "http://searchbox.digsby.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" + WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\SearchScopes" DefaultScope ${DIGSBY_SEARCH_UUID_IE} + WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\SearchUrl" "" http://searchbox.digsby.com/search?q=%s + WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\Main" "Use Search Asst" no + WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\Main" "Search Page" http://searchbox.digsby.com/ + WriteRegStr HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\Main" "Search Bar" http://searchbox.digsby.com/ie + WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Internet Explorer\Search" SearchAssistant http://searchbox.digsby.com/ie + WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Internet Explorer\Main" "Search Page" http://searchbox.digsby.com/ + FunctionEnd + ''' + log.info("setting search for %r", self.process_name) + + import _winreg + + with util.traceguard: + k = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 'Software\\Microsoft\\Internet Explorer\\SearchScopes\\', 0, _winreg.KEY_ALL_ACCESS) + _winreg.SetValueEx(k, 'DefaultScope', None, _winreg.REG_SZ, DIGSBY_SEARCH_UUID_IE) + k2 = _winreg.CreateKey(k, DIGSBY_SEARCH_UUID_IE) + _winreg.SetValueEx(k2, 'DisplayName', None, _winreg.REG_SZ, "Google Powered Digsby Search") + _winreg.SetValueEx(k2, 'URL', None, _winreg.REG_SZ, DIGSBY_SEARCH_URL.encode('xml')) + k3 = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 'Software\\Microsoft\\Internet Explorer\\Main', 0, _winreg.KEY_ALL_ACCESS) + k4 = _winreg.CreateKey(k3, "Main") + _winreg.SetValueEx(k4, "Use Search Asst", None, _winreg.REG_SZ, "no") + _winreg.SetValueEx(k4, "Search Page", None, _winreg.REG_SZ, "http://searchbox.digsby.com/") + _winreg.SetValueEx(k4, "Search Bar", None, _winreg.REG_SZ, "http://searchbox.digsby.com/ie") + #FaviconURLFallback http://www.live.com/favicon.ico + #SuggestionsURLFallback http://api.search.live.com/qsml.aspx?Query={searchTerms}&Market={Language}&FORM=IE8SSC + k.Close() + k2.Close() + k3.Close() + k4.Close() + + with util.traceguard: + l = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Internet Explorer\\Main', 0, _winreg.KEY_ALL_ACCESS) + _winreg.SetValueEx(l, "Search Page", None, _winreg.REG_SZ, "http://searchbox.digsby.com/") + l2 = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Internet Explorer\\Search', 0, _winreg.KEY_ALL_ACCESS) + _winreg.SetValueEx(l2, "SearchAssistant", None, _winreg.REG_SZ, "http://searchbox.digsby.com/ie") + l.Close() + l2.Close() + + return True + +class Chrome(BrowserSettingsEditor): + process_name = 'chrome.exe' + keyword = 'digsby-searchbox' + + @property + def profile_dir(self): + return (stdpaths.userlocalconfig / 'Google' / 'Chrome' / 'User Data'/ 'Default') + + def is_installed(self): + return self.profile_dir.isdir() + + def write_prefs(self, new_prefs): + self.update_json_file('Preferences', new_prefs) + + def update_json_file(self, fname, new_dict): + fpath = self.profile_dir / fname + with fpath.open('rb') as f: + info = json.loads(f.read()) + + info.update(new_dict) + + with fpath.open('wb') as f: + f.write(json.dumps(info)) + + def set_homepage(self): + log.info("setting homepage for %r", self.process_name) + self.write_prefs({'homepage' : HOMEPAGE_URL, + "homepage_is_newtabpage": False,}) + return True + + def set_search(self): + log.info("setting search for %r", self.process_name) + db_fpath = self.profile_dir / 'Web Data' + import sqlite3 as sqlite + with closing(sqlite.connect(db_fpath)) as c: + id = None + with closing(c.execute('SELECT id FROM keywords WHERE keyword = "%(keyword)s";' % dict(keyword = self.keyword))) as r: + log.info("chrome checking existing row") + for row in r: + id, = row + + search_provider_info = dict( + short_name = "Digsby Powered Google", + url = "http://searchbox.digsby.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t", + suggest_url = "{google:baseSuggestURL}search?client=chrome&hl={language}&q={searchTerms}", + favicon_url = "http://www.google.com/favicon.ico", + keyword = self.keyword, + input_encodings = 'UTF-8', + id = id, + show_in_default_list = 1, + safe_for_autoreplace = 0, + originating_url = "", + ) + + if id is None: + # must create + search_provider_info.pop('id') + with closing(c.execute("INSERT INTO keywords (%s) VALUES (%s)" % + (','.join(x[0] for x in sorted(search_provider_info.items())), + ','.join(repr(x[1]) for x in sorted(search_provider_info.items()))))) as r: + log.info("chrome creating row") + pass + + with closing(c.execute('SELECT id FROM keywords WHERE short_name = "%(short_name)s"' % search_provider_info)) as r: + log.info("chrome getting new id") + for row in r: + id = search_provider_info['id'], = row + log.info("\tresult = %r", id) + + search_provider_info['search_url'] = search_provider_info['url'] + search_provider_info['encodings'] = search_provider_info['input_encodings'] + search_provider_info['icon_url'] = search_provider_info['favicon_url'] + + # Update fields to make sure they're right + with closing(c.execute(( + 'UPDATE keywords SET ' + 'url="%(url)s", ' + 'short_name="%(short_name)s", ' + 'keyword="%(keyword)s", ' + 'suggest_url="%(suggest_url)s", ' + 'favicon_url="%(icon_url)s" ' + 'WHERE id=%(id)d') + % search_provider_info)) as r: + log.info("chrome updating row") + pass + + c.commit() + + with closing(sqlite.connect(db_fpath)) as c: + with closing(c.execute('UPDATE meta SET value = %(id)d WHERE key = "Default Search Provider ID";' % search_provider_info)) as r: + log.info("chrome updating meta table") + pass + c.commit() + + search_provider_info.update(enabled = True) + self.write_prefs({"default_search_provider": search_provider_info}) + + return True + +class Firefox(BrowserSettingsEditor): + process_name = 'firefox.exe' + default_profile = None + + SEARCH_PLUGIN_TXT = \ +''' + + Google Powered Digsby Search + Google Powered Digsby Search + UTF-8 + http://digsby.com/digsbysearch.xml + 7 + data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAaRJREFUeNpiVIg5JRURw0A0YAHio943kYV%2B%2Ff33%2BdvvX7%2F%2FMjEx8nKycrGzwKXOiPKzICvdeezLhCV3jp15%2Bfv%2FX0YGhv8MDDxMX2qKTIw0RK10eYD6QYqATvoPBkt3f5K0W9Ew4fjTFz%2F%2Bw8Dm3W8UPeZxqFa%2BevsFyD0twgfVsOfkRxHrtfV9u5BVQ8Crd98%2FffkGYQM1QJ20%2FfSPv79eNxQGYfpSVJADmcvEAHbr7oOX2dj%2FERNKIA2%2F%2F%2Fz%2FxfCDhYVoDUDw5P6vf9%2B5iY0HVmZGQWm%2BN3fff%2Fn2k4eLHS739x%2FDiRs%2Ff%2F%2F5x8HO%2FOHzN3djfqgNjIwMgc6qzLx%2Fpy47j2zY%2Feff06tXhOUucgxeun33AUZGpHh4%2Bvo7t8EyIJqz%2FhpasD59%2B5dNrqdnznZIsEL9ICXCsWuBCwvTv%2FymS5PWPP32ExEALz%2F%2BB5r848cPCJcRaMP9xaYQzofPPzfuvrnj0Jst%2B5%2F8%2Bc4sLPeDkYlRgJc93VPE18NIXkYUmJYQSQMZ%2FP3379uPH7%2F%2F%2FEETBzqJ0WqLGvFpe2LCC4AAAwAyjg7ENzDDWAAAAABJRU5ErkJggg%3D%3D + + + http://searchbox.digsby.com +''' + + SEARCH_PREF_LINES = [ + 'user_pref("browser.search.defaultenginename", "Google Powered Digsby Search");', + 'user_pref("keyword.URL", "http://searchbox.digsby.com/search?sourceid=navclient&gfns=1&q=");', + ] + + HOMEPAGE_PREF_LINES = [ + 'user_pref("browser.startup.homepage", "%s");' % HOMEPAGE_URL, + ] + + + @property + def app_dir(self): + return (stdpaths.userconfig / 'Mozilla' / 'Firefox') + + @property + def profile_dir(self): + if self.default_profile is None: + self.find_default_profile() + + return self.app_dir / self.default_profile + + def find_default_profile(self): + cf = CF.ConfigParser() + if not cf.read(self.app_dir / 'profiles.ini'): + return + for section in cf.sections(): + if section == 'General': + continue + if cf.get(section, 'name') == 'default': + self.default_profile = path.path(cf.get(section, 'path')) + break + + def is_installed(self): + return self.app_dir.isdir() + + def set_search(self): + log.info("setting search for %r", self.process_name) + searchplugins = self.profile_dir / 'searchplugins' + if not searchplugins.isdir(): + with util.traceguard: + searchplugins.makedirs() + if searchplugins.isdir(): + with (searchplugins / 'digsby.xml').open('w') as f: + f.write(self.SEARCH_PLUGIN_TXT) + + return self.write_prefs(self.SEARCH_PREF_LINES) + + def set_homepage(self): + log.info("setting homepage for %r", self.process_name) + return self.write_prefs(self.HOMEPAGE_PREF_LINES) + + def write_prefs(self, pref_lines): + profileprefs = self.profile_dir / 'prefs.js' + if not profileprefs.isfile(): + return False + + with util.traceguard: + with profileprefs.open('a') as f: + f.writelines(pref_lines) + return True + +class BrowserSettingsAddon(addons.AddOn): + _setup = False + interval = 5 * 60 + + known_browsers = [ + InternetExplorer, + Chrome, + Firefox, + ] + + def setup(self, *a): + if self._setup: + return + self._setup = True + self.timer = util.Timer(0, self.check) + self.timer.start() + + all_browsers = [B() for B in self.known_browsers] + self.browsers = [b for b in all_browsers if b.is_installed()] + log.info("detected browsers: %r", self.browsers) + + def check(self): + wrote_task_info = self.check_installer_files() + tasks_remaining = self.check_browser_tasks() + + if tasks_remaining: + self.timer = util.Timer(self.interval, self.check) + self.timer.start() + log.debug("rescheduling browser settings task for %r", self.interval) + else: + log.debug("all browser settings tasks complete. not rescheduling") + + def check_installer_files(self): + search = _get_search_filepath() + homepage = _get_homepage_filepath() + dosearch = search.isfile() + dohomepage = homepage.isfile() + + if dosearch or dohomepage: + log.info("new tasks discovered: dosearch = %r, dohomepage = %r", dosearch, dohomepage) + my_browser_names = [b.process_name for b in self.browsers] + self.write_browser_task_info(my_browser_names if dosearch else [], my_browser_names if dohomepage else []) + if dosearch: + try: + search.remove() + except Exception: + pass + else: + log.debug("removed dosearch flag") + if dohomepage: + try: + homepage.remove() + except Exception: + pass + else: + log.debug("removed dohomepage flag") + + return True + return False + + def write_browser_task_info(self, need_search, need_homepage): + taskinfo_filepath = _get_taskinfo_filepath() + if not need_search and not need_homepage: + if taskinfo_filepath.isfile(): + taskinfo_filepath.remove() + log.info("clearing browser task info data (nothing left to do)") + return False + + info = {'search' : need_search, + 'homepage' : need_homepage} + + with _get_taskinfo_filepath().open('wb') as f: + f.write(json.dumps(info)) + + log.info("wrote browser task info: %r", info) + + def read_browser_task_info(self): + taskinfo_path = _get_taskinfo_filepath() + if not taskinfo_path.isfile(): + return None + + with taskinfo_path.open('rb') as f: + info = json.loads(f.read()) + + return info + + def check_browser_tasks(self): + info = self.read_browser_task_info() + log.info("browser settings task info loaded: %r", info) + if info is None: + return False + + need_search = info.get('search', []) + need_homepage = info.get('homepage', []) + + for browser in self.browsers: + if browser.process_name in need_search: + if browser.can_edit() and browser.set_search(): + need_search.remove(browser.process_name) + if browser.process_name in need_homepage: + if browser.can_edit() and browser.set_homepage(): + need_homepage.remove(browser.process_name) + + # if the file was written + browser_names = [b.process_name for b in self.browsers] + for browser in need_search[:]: + if browser not in browser_names: + need_search.remove(browser) + + for browser in need_homepage[:]: + if browser not in browser_names: + need_homepage.remove(browser) + + self.write_browser_task_info(need_search, need_homepage) + + return bool(need_search or need_homepage) + +def _get_taskinfo_filepath(): + return (stdpaths.userdata / 'taskinfo.json') + +def _get_search_filepath(): + return (stdpaths.userdata / 'dosearch') + +def _get_homepage_filepath(): + return (stdpaths.userdata / 'dohomepage') + +def set_homepage(): + pass + +def set_search(): + with _get_search_filepath().open('wb') as f: + f.write("yes") + + BrowserSettingsAddon(common.profile()).check() + +def set_homepage(): + with _get_homepage_filepath().open('wb') as f: + f.write("yes") + + BrowserSettingsAddon(common.profile()).check() diff --git a/digsby/src/plugins/digsby_email/__init__.py b/digsby/src/plugins/digsby_email/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsby_email/imap_sp.py b/digsby/src/plugins/digsby_email/imap_sp.py new file mode 100644 index 0000000..02908e5 --- /dev/null +++ b/digsby/src/plugins/digsby_email/imap_sp.py @@ -0,0 +1,37 @@ +import logging +log = logging.getLogger('imap_sp') + +import services.service_provider as SP +import digsby_email.smtp_sp as SMTP_SP + +class IMAPServiceProvider(SMTP_SP.SMTPServiceProvider): + def update_info(self, info): + self.require_ssl = info.get('require_ssl') + + if 'imapport' in info: + try: + info['imapport'] = int(info['imapport']) + except ValueError: + log.error("port is not an int, it is %r", info['imapport']) + raise SP.AccountException() + if 'imapport' in info: + self.imapport = info['imapport'] + else: + if not hasattr(self, 'imapport'): + self.imapport = self.get_metainfo('email')[1].info.defaults['imapport_ssl' if self.require_ssl else 'imapport'] + + if not info.get('imapserver'): + log.debug("imapserver not provided") + raise SP.AccountException() + + self.imapserver = info['imapserver'] + + super(IMAPServiceProvider, self).update_info(info) + + def get_options(self, ctype = 'email'): + options = super(IMAPServiceProvider, self).get_options(ctype) + options['imapserver'] = self.imapserver + options['imapport'] = self.imapport + options['require_ssl'] = self.require_ssl + + return options diff --git a/digsby/src/plugins/digsby_email/info.yaml b/digsby/src/plugins/digsby_email/info.yaml new file mode 100644 index 0000000..1cae1cc --- /dev/null +++ b/digsby/src/plugins/digsby_email/info.yaml @@ -0,0 +1,124 @@ +type: "multi" +name: !_ "Mail" + +common: + email_accounts_opts: &email_accounts_opts ["enabled", "updatefreq", "alias"] + email_defaults: &email_defaults + updatefreq: 300 + +plugins: + provider_pop: + name: !_ "POP Account" + type: "service_provider" + provider_id: "pop" + entry_points: + digsby.service_provider: + pop: "digsby_email.pop_sp:POPServiceProvider" + provider_info: + popularity: 77 + username_desc: !_ 'Username' + password_desc: !_ 'Password' + needs_smtp: yes + + + pop: + type: service_component + component_type: email + service_name: !_ "Mail" + service_provider: pop + needs_webclient: True + path: mail.pop.PopMail + needs_server: !N_ 'POP' + name: POP Email + smtp_pw_type: True + whitelist_opts: + - enabled + - updatefreq + - alias + - smtp_server + - smtp_port + - smtp_require_ssl + - smtp_username + - email_address + - popserver + - popport + - require_ssl + form: email + defaults: + smtp_password: "" + smtp_server: "" + updatefreq: 300 + popport_ssl: 995 + require_ssl: False + smtp_require_ssl: False + smtp_username: "" + smtp_port_ssl: 465 + email_address: "" + popport: 110 + smtp_port: 25 + compatible: + - pop + name_truncated: pop + shortname: pop + popularity: 77 + entry_points: + digsby.component.email: + pop: mail.pop:PopMail + + provider_imap: + name: !_ "IMAP Account" + type: "service_provider" + provider_id: "imap" + entry_points: + digsby.service_provider: + imap: "digsby_email.imap_sp:IMAPServiceProvider" + provider_info: + popularity: 37 + username_desc: !_ 'Username' + password_desc: !_ 'Password' + needs_smtp: yes + + imap: + type: service_component + component_type: email + defaults: + require_ssl: False + default_ssl_port: 993 + email_address: "" + updatefreq: 300 + imapport_ssl: 993 + smtp_port_ssl: 465 + smtp_username: "" + smtp_require_ssl: False + smtp_server: "" + smtp_port: 25 + smtp_password: "" + imapport: 143 + path: mail.imap.IMAPMail + name_truncated: imap + compatible: + - imap + popularity: 37 + needs_server: !N_ 'IMAP' + whitelist_opts: !set + - enabled + - updatefreq + - alias + - smtp_server + - smtp_port + - smtp_require_ssl + - smtp_username + - email_address + - imapserver + - imapport + - require_ssl + needs_webclient: True + name: IMAP Email + service_name: !_ "Mail" + shortname: imap + service_provider: imap + smtp_pw_type: True + form: email + entry_points: + digsby.component.email: + imap: mail.imap:IMAPMail diff --git a/digsby/src/plugins/digsby_email/pop_sp.py b/digsby/src/plugins/digsby_email/pop_sp.py new file mode 100644 index 0000000..eac4f3a --- /dev/null +++ b/digsby/src/plugins/digsby_email/pop_sp.py @@ -0,0 +1,37 @@ +import logging +log = logging.getLogger('pop_sp') + +import services.service_provider as SP +import digsby_email.smtp_sp as SMTP_SP + +class POPServiceProvider(SMTP_SP.SMTPServiceProvider): + def update_info(self, info): + self.require_ssl = info.get('require_ssl') + + if 'popport' in info: + try: + info['popport'] = int(info['popport']) + except ValueError: + log.error("port is not an int, it is %r", info['popport']) + raise SP.AccountException() + if 'popport' in info: + self.popport = info['popport'] + else: + if not hasattr(self, 'popport'): + self.popport = self.get_metainfo('email')[1].info.defaults['popport_ssl' if self.require_ssl else 'popport'] + + if not info.get('popserver'): + log.debug("popserver not provided") + raise SP.AccountException() + + self.popserver = info['popserver'] + + super(POPServiceProvider, self).update_info(info) + + def get_options(self, ctype = 'email'): + options = super(POPServiceProvider, self).get_options(ctype) + options['popserver'] = self.popserver + options['popport'] = self.popport + options['require_ssl'] = self.require_ssl + + return options diff --git a/digsby/src/plugins/digsby_email/smtp_sp.py b/digsby/src/plugins/digsby_email/smtp_sp.py new file mode 100644 index 0000000..e90f1a6 --- /dev/null +++ b/digsby/src/plugins/digsby_email/smtp_sp.py @@ -0,0 +1,101 @@ +import logging +log = logging.getLogger("smtp_sp") + +import cPickle +import traceback +import services.service_provider as SP +import hooks +import common +import common.protocolmeta as protocolmeta +import prefs + +import util + +import mail.smtp as smtp + +def localprefs_key(name): + def get(acct): + return '/'.join([acct.provider_id, acct.name, name]).lower() + + return get + +class SMTPServiceProvider(SP.UsernamePasswordServiceProvider): + def update_info(self, kwds): + if '_encrypted_smtppw' in kwds and '_encrypted_pw' in kwds: + kwds['password'] = self._encrypted_pw = e_pw = kwds['_encrypted_pw'] + self._encrypted_smtppw = e_spw = kwds['_encrypted_smtppw'] + kwds['smtp_password'] = common.profile.plain_pw(e_spw) + + elif 'password' in kwds: + try: + self._encrypted_pw, self._encrypted_smtppw = smtp.SMTPEmailAccount._unglue_pw(kwds.get('password', '')) + kwds['smtp_password'] = common.profile.plain_pw(self._encrypted_smtppw) + kwds['password'] = self._encrypted_pw + except ValueError: + if 'smtp_password' in kwds: + self._encrypted_smtppw = common.profile.crypt_pw(kwds['smtp_password']) + else: + self._encrypted_smtppw = '' + self._encrypted_pw = kwds['password'] + + self.smtp_require_ssl = kwds.get('smtp_require_ssl') + + if 'email_address' in kwds: + self.email_address = kwds.get('email_address', self.name) + if 'smtp_username' in kwds: + self.smtp_username = kwds.get('smtp_username', self.name) + if kwds.get('smtp_server'): + self.smtp_server = kwds.get('smtp_server', '') + else: + log.debug("smtp_server not provided") + raise SP.AccountException() + if 'smtp_port' in kwds: + try: + kwds['smtp_port'] = int(kwds['smtp_port']) + + except ValueError: + log.error("port is not an int, it is %r", kwds['smtp_port']) + raise SP.AccountException() + else: + kwds['smtp_port'] = self.get_metainfo('email')[1].info.defaults['smtp_port_ssl' if self.smtp_require_ssl else 'smtp_port'] + + self.smtp_port = kwds.get('smtp_port') + + super(SMTPServiceProvider, self).update_info(kwds) + + mailclient = kwds.get('mailclient', None) + if mailclient is None and not hasattr(self, 'mailclient'): + self.mailclient = 'sysdefault' + custom_inbox_url = kwds.get('custom_inbox_url', None) + if custom_inbox_url is None and not hasattr(self, 'custom_inbox_url'): + self.custom_inbox_url = u'' + custom_compose_url = kwds.get('custom_compose_url', None) + if custom_compose_url is None and not hasattr(self, 'custom_compose_url'): + self.custom_compose_url = u'' + + def _decryptedpw(self): + return common.profile.plain_pw(self._encrypted_pw) + + def _decrypted_smtppw(self): + return common.profile.plain_pw(self._encrypted_smtppw) + + mailclient = prefs.localprefprop(localprefs_key('mailclient'), None) + custom_inbox_url = prefs.localprefprop(localprefs_key('custom_inbox_url'), None) + custom_compose_url = prefs.localprefprop(localprefs_key('custom_compose_url'), None) + + def get_options(self, ctype = "email"): + options = super(SMTPServiceProvider, self).get_options(ctype) + options['email_address'] = self.email_address + options['smtp_username'] = getattr(self, 'smtp_username', self.name) + options['smtp_server'] = self.smtp_server + options['smtp_port'] = self.smtp_port + options['smtp_require_ssl'] = self.smtp_require_ssl + options['mailclient'] = self.mailclient + options['custom_inbox_url'] = self.custom_inbox_url + options['custom_compose_url'] = self.custom_compose_url + + return options + + @property + def display_name(self): + return self.email_address diff --git a/digsby/src/plugins/digsby_geoip/__init__.py b/digsby/src/plugins/digsby_geoip/__init__.py new file mode 100644 index 0000000..abbc3f4 --- /dev/null +++ b/digsby/src/plugins/digsby_geoip/__init__.py @@ -0,0 +1,89 @@ +from decimal import Decimal +from logging import getLogger +from peak.util.addons import AddOn +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.utils import from_utf8 +from pyxmpp.xmlextra import get_node_ns_uri +import hooks +import libxml2 +import traceback + +log = getLogger('plugins.digsby_geoip') + +DIGSBY_GEOIP_NS = "digsby:geoip" +#=============================================================================== +# jabber setup +#=============================================================================== +class Digsby_GeoIP(AddOn): + def __init__(self, subject): + self.protocol = subject + super(Digsby_GeoIP, self).__init__(subject) + + def setup(self, stream): + self.stream = stream + log.debug('setting up geoip') + stream.set_iq_set_handler('query', DIGSBY_GEOIP_NS, self.geoip_set) + + def geoip_set(self, stanza): + try: + f = stanza.get_from() + if f is not None and f.bare() != self.stream.peer.bare(): + return False + node = stanza.xpath_eval('g:query/g:geoip',{'g':DIGSBY_GEOIP_NS})[0] + geo = GeoIP(node) + except Exception: + traceback.print_exc() + return False + else: + #=================================================================== + # push the information to anyone interested + #=================================================================== + hooks.notify('digsby.geoip.received', geo.AsDict(), self.protocol, self.stream) + return True + +def setup(protocol, stream, *a, **k): + Digsby_GeoIP(protocol).setup(stream) + +#=============================================================================== +# jabber class +#=============================================================================== +class GeoIP(StanzaPayloadObject): + xml_element_name = 'geoip' + xml_element_namespace = DIGSBY_GEOIP_NS + + strfields = ['ip', 'city', 'country', 'region', 'state', 'postal'] + decfields = ['lat', 'lng'] + + def __init__(self, xmlnode): + if isinstance(xmlnode,libxml2.xmlNode): + self.__from_xml(xmlnode) + else: + assert False + + def __from_xml(self, node): + if node.type!="element": + raise ValueError,"XML node is not a geoip (not en element)" + ns=get_node_ns_uri(node) + if ns and ns!=DIGSBY_GEOIP_NS or node.name!=self.xml_element_name: + raise ValueError,"XML node is not a geoip" + + for fields, convert in [(self.strfields, from_utf8), (self.decfields, Decimal)]: + for field in fields: + val = None + try: + val2 = node.prop(field) + val = convert(val2) if val2 else None + except Exception: + traceback.print_exc() + setattr(self, field, val) + + def complete_xml_element(self, xmlnode, _unused): + assert False + + def AsDict(self): + d = dict((field, getattr(self, field)) for field in self.strfields) + d.update((field, getattr(self, field)) for field in self.decfields) + return d + +def print_geoip(geoip, *a, **k): + log.debug('got geoip information %r', geoip) diff --git a/digsby/src/plugins/digsby_geoip/info.yaml b/digsby/src/plugins/digsby_geoip/info.yaml new file mode 100644 index 0000000..7461b79 --- /dev/null +++ b/digsby/src/plugins/digsby_geoip/info.yaml @@ -0,0 +1,9 @@ +name: 'Digsby GeoIP' +path: 'digsby_geoip' +type: 'pure' + +entry_points: + digsby.jabber.session_started: + digsby_geoip: digsby_geoip:setup + digsby.geoip.received: + digsby_geoip: digsby_geoip:print_geoip diff --git a/digsby/src/plugins/digsby_identity/__init__.py b/digsby/src/plugins/digsby_identity/__init__.py new file mode 100644 index 0000000..de6c635 --- /dev/null +++ b/digsby/src/plugins/digsby_identity/__init__.py @@ -0,0 +1 @@ +from .identity import Identity diff --git a/digsby/src/plugins/digsby_identity/identity.py b/digsby/src/plugins/digsby_identity/identity.py new file mode 100644 index 0000000..da280eb --- /dev/null +++ b/digsby/src/plugins/digsby_identity/identity.py @@ -0,0 +1,248 @@ +import time +import path +import random +import hashlib +import simplejson + +import common +import stdpaths + +import util.cryptography as crypto +import util.primitives.files as files +import digsby.blobs as blobs +import digsby.accounts as accounts +import digsby.digsbylocal as digsbylocal +from util.json import pyloads, pydumps + + +class Identity(object): + softcrypt_salt = 'digsby-softcrypt' + name = None + password = None + _active = None + + @classmethod + def activate(cls, active): + if not active.is_valid: + raise digsbylocal.InvalidPassword + if cls._active is not None: + cls.deactivate() + + active.set('last_login', time.time()) + cls._active = active + + @classmethod + def deactivate(cls): + cls._active = None + + @classmethod + def active(cls): + return cls._active + + @classmethod + def save_data(cls, key, value): + active = cls.active() + if active is None: + return + return active.set(key, value) + + @classmethod + def load_data(cls, key, default = None): + active = cls.active() + if active is None: + return default + return active.get(key, default) + + @classmethod + def delete(cls, name, password): + if not cls.exists(name): + # TODO: should this just cleanly return? + # the expected post-conditions of this function are satisfied... + raise digsbylocal.InvalidUsername + + identity = cls(name, password) + if not identity.is_valid: + raise digsbylocal.InvalidPassword + + identity.storage.rmtree() + + @classmethod + def generate_salt(cls): + return hex(random.getrandbits(256)).lstrip('0').lstrip('x').rstrip('L') + + @classmethod + def exists(cls, name): + return cls(name).storage.isdir() + + @classmethod + def all(cls): + ''' + Return all available profiles + ''' + return iter(cls(unicode(pth.name)) for pth in cls._storage_dir().dirs()) + + @classmethod + def last(cls): + all_identities = list(cls.all()) + return sorted(all_identities, key = lambda identity: identity.get('last_login', 0))[-1] + + @classmethod + def _storage_dir(cls): + ''' + Returns the location where all profiles are stored. Creates the directory if necessary. + ''' + pth = path.path(common.pref('digsby.profiles.location', default=stdpaths.userdata / 'profiles')) + if not pth.isdir(): + pth.makedirs() + return pth + + @classmethod + def create(cls, name, password): + if cls.exists(name): + raise Exception('Profile %r already exists', name) + + identity = cls(name, password) + + identity.set('accounts', []) + identity.set('prefs', {}) + identity.set('sentinel', hashlib.sha256(identity.key).hexdigest()) + + return identity + + def __init__(self, name, password=None): + self.name = name + self.password = password or self.get('saved_password', None) + + @property + def is_valid(self): + try: + return self.get('sentinel', None) == hashlib.sha256(self.key).hexdigest() + except Exception: + return False + + def set(self, key, value): + ''' + Put 'value' in '_storage' as 'key' + ''' + if not self.storage.isdir(): + self.storage.makedirs() + + data = self.serialize(key, value) + + with files.atomic_write(self.storage / key, 'wb') as f: + f.write(data) + + def get(self, key, default = None): + ''' + Retrieve stored value associated with 'key' from '_storage' + ''' + + fname = self.storage / key + if not fname.isfile(): + return default + + with open(fname, 'rb') as f: + data = f.read() + + # TODO: what happens if deserialize fails? + return self.deserialize(key, data) + + def keys(self): + if self.storage.isdir(): + return (unicode(f.name) for f in self.storage.files()) + else: + return iter() + + @property + def storage(self): + return type(self)._storage_dir() / self.name + + def serialize(self, key, value): + serializer = getattr(self, 'serialize_%s' % key, self.serialize_default) + return serializer(key, value) + + def deserialize(self, key, data): + deserializer = getattr(self, 'deserialize_%s' % key, self.deserialize_default) + return deserializer(key, data) + + def serialize_nojson(self, key, value): + assert isinstance(value, bytes) + return self.encrypt(value) + serialize_icon = serialize_nojson + + def deserialize_nojson(self, key, data): + value = self.decrypt(data) + assert isinstance(value, bytes) + return value + deserialize_icon = deserialize_nojson + + def serialize_nocrypt(self, key, value): + return simplejson.dumps(value) + serialize_salt = serialize_nocrypt + serialize_autologin = serialize_nocrypt + serialize_save = serialize_nocrypt + serialize_sentinel = serialize_nocrypt + serialize_last_login = serialize_nocrypt + + def deserialize_nocrypt(self, key, data): + return simplejson.loads(data) + deserialize_salt = deserialize_nocrypt + deserialize_autologin = deserialize_nocrypt + deserialize_save = deserialize_nocrypt + deserialize_sentinel = deserialize_nocrypt + deserialize_last_login = deserialize_nocrypt + + def serialize_softcrypt(self, key, value): + return self.serialize_default(key, value, self.softcrypt_key) + serialize_saved_password = serialize_softcrypt + + def deserialize_softcrypt(self, key, data): + return self.deserialize_default(key, data, self.softcrypt_key) + deserialize_saved_password = deserialize_softcrypt + + def serialize_accounts(self, key, value): + stored_value = digsbylocal.serialize_local(accts = value, order = [x.id for x in value]) + return self.serialize_default(key, stored_value) + + def deserialize_accounts(self, key, data): + stored_value = self.deserialize_default(key, data) + stored_value = digsbylocal.unserialize(stored_value) + for a in stored_value['accts']: + password = a['password'] + assert (isinstance(password, str) or password is None) + if password is not None: + password = password.decode('base64') + a['password'] = password + return accounts.Accounts.from_local_store(stored_value) + + def serialize_default(self, key, value, crypt_key = None): + return self.encrypt(pydumps(value), crypt_key) + + def deserialize_default(self, key, data, crypt_key = None): + return pyloads(self.decrypt(data, crypt_key)) + + @property + def softcrypt_key(self): + return hashlib.sha256(self.softcrypt_salt + self.name).digest() + + @property + def key(self): + if self.password is None: + raise Exception('identity.password cannot be None. set no password explicitly with emptystring ("")') + + salt = self.get('salt') + if salt is None: + salt = self.generate_salt() + self.set('salt', salt) + + return hashlib.sha256(salt + self.password + salt + self.name + 'digsby').digest() + + def encrypt(self, plaintext, key = None): + if key is None: + key = self.key + return crypto.encrypt(key, plaintext, mode = crypto.Mode.CBC) + + def decrypt(self, ciphertext, key = None): + if key is None: + key = self.key + return crypto.decrypt(key, ciphertext, mode = crypto.Mode.CBC) diff --git a/digsby/src/plugins/digsby_identity/info.yaml b/digsby/src/plugins/digsby_identity/info.yaml new file mode 100644 index 0000000..bfb54b6 --- /dev/null +++ b/digsby/src/plugins/digsby_identity/info.yaml @@ -0,0 +1,27 @@ +type: pure +name: "Digsby Identity Management" +shortname: digsby_identity + +entry_points: + digsby.identity.exists: + 'local': digsby_identity:Identity.exists + digsby.identity.create: + 'local': digsby_identity:Identity.create + digsby.identity.delete: + 'local': digsby_identity:Identity.delete + digsby.identity.all: + 'local': digsby_identity:Identity.all + digsby.identity.last: + 'local': digsby_identity:Identity.last + digsby.identity.activate: + 'local': digsby_identity:Identity.activate + digsby.identity.deactivate: + 'local': digsby_identity:Identity.deactivate + digsby.identity.active: + 'local': digsby_identity:Identity.active + digsby.identity.save_data: + 'local': digsby_identity:Identity.save_data + digsby.identity.load_data: + 'local': digsby_identity:Identity.load_data + digsby.identity.get: + 'local': digsby_identity:Identity diff --git a/digsby/src/plugins/digsby_iq_version/__init__.py b/digsby/src/plugins/digsby_iq_version/__init__.py new file mode 100644 index 0000000..cffc6e0 --- /dev/null +++ b/digsby/src/plugins/digsby_iq_version/__init__.py @@ -0,0 +1,53 @@ +from decimal import Decimal +from logging import getLogger +from peak.util.addons import AddOn +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.utils import to_utf8 +from pyxmpp.xmlextra import get_node_ns_uri +import hooks +import libxml2 +import traceback +import sys + +log = getLogger('plugins.digsby_geoip') + +DIGSBY_VERSION_NS = 'digsby:iq:version' + +class Digsby_IqVersion(AddOn): + def __init__(self, subject): + self.protocol = subject + super(Digsby_IqVersion, self).__init__(subject) + + def setup(self, stream): + self.stream = stream + log.debug('setting up digsby:iq:version') + stream.set_iq_get_handler('query', DIGSBY_VERSION_NS, self.version_get) + + def version_get(self, iq): + iq = iq.make_result_response() + q = iq.new_query(DIGSBY_VERSION_NS) + q.newTextChild( q.ns(), "name", "Digsby Client" ) + q.newTextChild( q.ns(), "version", ('%s' % (getattr(sys, 'REVISION', '?') or '?'))) + for k in ('TAG', 'BRAND'): + v = getattr(sys, k, None) + if v: + q.newTextChild( q.ns(), k.lower(), str(v)) + if not self.protocol.hide_os: + import platform + platform_string = platform.platform() + # for some reason, on my XP box, platform.platform() contains both + # the platform AND release in platform.platform(). On Ubuntu, OS X, + # and I believe older versions of Windows, this does not happen, + # so we need to add the release in all other cases. + if platform_string.find("XP") == -1: + platform_string += " " + platform.release() + + q.newTextChild( q.ns(), "os", platform_string ) + self.protocol.send(iq) + return True + +def session_started(protocol, stream, *a, **k): + Digsby_IqVersion(protocol).setup(stream) + +def initialized(protocol, *a, **k): + protocol.register_feature(DIGSBY_VERSION_NS) diff --git a/digsby/src/plugins/digsby_iq_version/info.yaml b/digsby/src/plugins/digsby_iq_version/info.yaml new file mode 100644 index 0000000..9bbc48e --- /dev/null +++ b/digsby/src/plugins/digsby_iq_version/info.yaml @@ -0,0 +1,9 @@ +name: 'Digsby IQ Version' +path: 'digsby_iq_version' +type: 'pure' + +entry_points: + digsby.jabber.session_started: + digsby_iq_version: digsby_iq_version:session_started + digsby.jabber.initialized: + digsby_iq_version: digsby_iq_version:initialized diff --git a/digsby/src/plugins/digsby_muc_invite/__init__.py b/digsby/src/plugins/digsby_muc_invite/__init__.py new file mode 100644 index 0000000..0678885 --- /dev/null +++ b/digsby/src/plugins/digsby_muc_invite/__init__.py @@ -0,0 +1,133 @@ +from decimal import Decimal +from jabber.jabber_util.functions import xpath_eval +from logging import getLogger +from peak.util.addons import AddOn +from pyxmpp.jid import JID +from pyxmpp.objects import StanzaPayloadObject +from pyxmpp.utils import from_utf8, to_utf8 +from pyxmpp.xmlextra import get_node_ns_uri +import hooks +import libxml2 +import sys +import traceback + +log = getLogger('plugins.digsby_muc_invitations') + +CONFERENCE_NS = 'jabber:x:conference' +MUC_USER_NS = 'http://jabber.org/protocol/muc#user' + +class MUCInvitations(AddOn): + def __init__(self, subject): + self.protocol = subject + super(MUCInvitations, self).__init__(subject) + +class DirectMUCInvitations(MUCInvitations): + def setup(self, stream): + self.stream = stream + log.debug('setting up XEP 0249 message handler') + stream.set_message_handler('normal', self.handle_message, + namespace = CONFERENCE_NS, + priority = 98) + + def handle_message(self, stanza): + ''' + + + + ''' + try: + fromjid = stanza.get_from() + x = stanza.xpath_eval('c:x',{'c':CONFERENCE_NS})[0] + roomjid = JID(from_utf8(x.prop('jid'))) + roomname = JID(roomjid).node + password = x.prop('password') + password = from_utf8(password) if password else None + reason = x.prop('reason') + reason = from_utf8(reason) if reason else None + except Exception: + traceback.print_exc() + return False + else: + if not all((roomname, fromjid)): + return False + self.protocol.hub.on_invite( + protocol = self.protocol, + buddy = fromjid, + room_name = roomname, + message = reason, + on_yes = lambda: self.protocol.join_chat_jid(roomjid, + self.protocol.self_buddy.jid.node)) + return True # don't let other message handlers do it + +def session_started(protocol, stream, *a, **k): + DirectMUCInvitations(protocol).setup(stream) + MediatedMUCInvitations(protocol).setup(stream) + +def initialized(protocol, *a, **k): + protocol.register_feature(CONFERENCE_NS) + +class MediatedMUCInvitations(MUCInvitations): + def setup(self, stream): + self.stream = stream + log.debug('setting up MUC invite message handler') + stream.set_message_handler('normal', self.handle_message, + namespace = MUC_USER_NS, + priority = 99) + + def handle_message(self, stanza): + ''' + + + + + Hey Hecate, this is the place for all good witches! + + + cauldronburn + + + ''' + self.stanza = stanza + try: + roomjid = JID(stanza.get_from()) + roomname = roomjid.node + except Exception: + traceback.print_exc() + return False + else: + if not roomname: + return False + for invite in stanza.xpath_eval('user:x/user:invite', + {'user':MUC_USER_NS}): + frm = invite.prop('from') + if not frm: + continue + try: + frm = JID(from_utf8(frm)) + except Exception: + continue + else: + break + else: + return False + + reason = None + for rsn in xpath_eval(invite, 'user:reason/text()', + {'user':MUC_USER_NS}): + if rsn: + reason = reason + reason = reason or '' + self.protocol.hub.on_invite(protocol = self.protocol, + buddy = frm, + room_name = roomname, + message = reason, + on_yes = lambda: self.protocol.join_chat_jid(roomjid, + self.protocol.self_buddy.jid.node)) + return True diff --git a/digsby/src/plugins/digsby_muc_invite/info.yaml b/digsby/src/plugins/digsby_muc_invite/info.yaml new file mode 100644 index 0000000..a780f14 --- /dev/null +++ b/digsby/src/plugins/digsby_muc_invite/info.yaml @@ -0,0 +1,9 @@ +name: 'XEP 0249 + MUC Mediated invite' +path: 'digsby_muc_invite' +type: 'pure' + +entry_points: + digsby.jabber.session_started: + muc_invite: digsby_muc_invite:session_started + digsby.jabber.initialized: + muc_invite: digsby_muc_invite:initialized diff --git a/digsby/src/plugins/digsby_platform_1_0_0/__init__.py b/digsby/src/plugins/digsby_platform_1_0_0/__init__.py new file mode 100644 index 0000000..783f801 --- /dev/null +++ b/digsby/src/plugins/digsby_platform_1_0_0/__init__.py @@ -0,0 +1,5 @@ +__version__ = (1,0,0) + +def version(): + return "digsby-%s.%s.%s" % __version__ + diff --git a/digsby/src/plugins/digsby_platform_1_0_0/info.yaml b/digsby/src/plugins/digsby_platform_1_0_0/info.yaml new file mode 100644 index 0000000..4eb83cb --- /dev/null +++ b/digsby/src/plugins/digsby_platform_1_0_0/info.yaml @@ -0,0 +1,8 @@ +type: pure +name: "Digsby Plugin Platform v1.0.0" +version: [1, 0, 0] +shortname: digsby_platform_1_0_0 + +entry_points: + digsby.platform.version: + "digsby-1.0.0": digsby_platform_1_0_0:version diff --git a/digsby/src/plugins/digsby_service_editor/__init__.py b/digsby/src/plugins/digsby_service_editor/__init__.py new file mode 100644 index 0000000..57a10b4 --- /dev/null +++ b/digsby/src/plugins/digsby_service_editor/__init__.py @@ -0,0 +1,67 @@ +''' +This module is responsible for building the create / edit / delete account dialogs, which are created and controlled by +a non-trivial amount of hooks. + +The default_ui module handles nearly everything for the 'standard' service providers / components and should be used +as the primary example for implementing future hook handlers used in this plugin. + +Notes: + * All hooks begin with 'digsby.services.edit.' unless otherwise specifed. + * the 'parent' argument is passed to wx controls as their parent window and as such follows the rules of the wx library. + * 'SP' indicates a concrete service provider instance + * 'MSP' indicates meta information (usually a dictionary) about the service provider + * 'info' is usually a mapping of information that will be used to create (or update) a concrete service component instance. + * 'MSC' indicates meta information about the service component in question. + +The categories of hooks are broken down in to 5 categories: +1. Dialog instantiation and saving. + This is the main entry / exit point of this feature. A service provider can be created, edited, or deleted. +2. UI construction. The UI is broken down into two main panels: basic and advanced. the basic panel is always + shown, while the advanced panel is hidden in a collapsable section. Additionally, for both panels, there are + separate hooks for each component of the service provider, as well as the provider itself, to make changes to + the panel. +3. UI Layout. In some cases it may be necessary to touch-up, or 'polish' the UI to look *just right*. For this, + the 'layout.polish' hook is used. +3.5. Event binding. There are currently no hooks for event binding, but they may be needed in the future if + the family of UI controls in these dialogs (specifically, the events they generate) gets larger. +4. Validation. When user input occurs, the data in the dialog is validated and feedback may be given to the user + if appropriate. +5. Data extraction. In the same manner that the dialog was built, data needs to be extracted from it. This follows + a similar pattern as #2. + +Dialog instantiation and saving: (full hook names given in this section) + digsby.services.create(parent, sp_info) + invoked when creating a new service provider instance. + digsby.services.edit(parent, sp) + same as create, but instead of metainfo we have the service provider's details. + digsby.services.edit.save(SP, MSP, info, callback = None) + called when saving an edited account. + digsby.services.delete.build_dialog(parent, SP) + similar to edit, except the service provider will be deleted when finished. + +UI Construction: + {basic|advanced}.construct_panel(parent, SP, MSP) + creates the specified panel and UI components and sizers that other hooks will add to. + {basic|advanced}.construct_sub.{provider|im|email|social}(panel, SP, MSP, MSC) + extension point for the provider or each type of component to add controls to the specified panel. + +UI Layout: + layout.polish(basic_panel, advanced_panel) + here the panels may be modified however necessary to provide the desired appearance. + +Validation: (full hook name given) + digsby.services.validate(info, MSP, is_new) + called on ui events that may require data to be verified. is_new specifies if the account is being created or edited. + +Data extraction: + {basic|advanced}.extract_panel(parent, SP, MSP) + {basic|advanced}.extract_sub.{provider|im|email|social}(panel, SP, MSP, MSC) + These hooks should retrieve data from UI components created in the analogous 'construct' hooks + +Misc: (full hook names given) + digsby.services.normalize(info, MSP, is_new) + Used to modify any data before it's used to update / create anything. + digsby.services.colorize_name(name) + Used to split the name into 'tagged' components (i.e. name, base) for coloring in the UI. + +''' diff --git a/digsby/src/plugins/digsby_service_editor/default_ui.py b/digsby/src/plugins/digsby_service_editor/default_ui.py new file mode 100644 index 0000000..2d7a1a2 --- /dev/null +++ b/digsby/src/plugins/digsby_service_editor/default_ui.py @@ -0,0 +1,756 @@ +import logging +log = logging.getLogger("service_editor.default_ui") +import wx +import gui.toolbox as toolbox +import gui.controls as controls + +import common + +## Some defaults and constants for building the dialogs: + +txtSize = (130, -1) +halfTxtSize = (60, -1) # Not actually half, but appropriate for the layout +qtrTxtSize = (30, -1) + +center_right_all = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.ALL +center_left_all = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL + +def ezadd(sz, ctrl, *a, **k): + ''' + Add a ctrl to a sz (sizer). Additional arguments will be passed to the sizer's Add function. Respects the control's DefaultBorder. + ''' + do_border = k.pop('border', True) + if do_border is True or do_border is False: + border = getattr(ctrl, 'GetDefaultBorder', lambda:0)() * do_border + else: + border = do_border + k['border'] = border + sz.Add(ctrl, *a, **k) + +def AddRowToGridBag(gb, stuff, spans=None): + ''' + Adds a new row to a grid bag sizer containing "stuff", which is a mapping of strings to wx controls. The controls will span columns + specified by 'spans' (a mapping of strings to (int, int) tuples). + + Valid keys in 'stuff' and 'spans' are: + check + label + choice + url + portlabel + porttext + + Spans are not required, and defaults are given in the code below. + ''' + if stuff is None: + return + if spans is None: + spans = {} + + gb.row = getattr(gb, 'row', 0) + + if 'check' in stuff: + ezadd(gb, stuff['check'], (gb.row, 1), spans.get('check', (1, 3)), flag = wx.EXPAND | wx.ALL) + else: + ezadd(gb, stuff['label'], (gb.row, 0), spans.get('label', (1, 1)), flag = center_right_all) + if 'text' in stuff: + ezadd(gb, stuff['text'], (gb.row, 1), spans.get('text', (1, 1)), flag = wx.ALL) + elif 'choice' in stuff: + ezadd(gb, stuff['choice'], (gb.row, 1), spans.get('choice', (1, 3)), flag = wx.ALL) + + if 'url' in stuff: + ezadd(gb, stuff['url'], (gb.row, 2), spans.get('url', (1, 2)), flag = center_left_all) + + elif "portlabel" in stuff: + ezadd(gb, stuff['portlabel'], (gb.row, 2), spans.get('portlabel', (1, 1)), flag = center_right_all) + ezadd(gb, stuff['porttext'], (gb.row, 3), spans.get('porttext', (1, 1)), flag = wx.ALL) + + gb.row += 1 + +def AddLineToGridBag(gb, parent): + ''' + Add a static line control to a grid bag sizer. + ''' + gb.row = getattr(gb, 'row', 0) + ezadd(gb, wx.StaticLine(parent), (gb.row, 0), (1, 4), flag = wx.EXPAND | wx.ALL, border = 5) + gb.row += 1 + +def title_for_service(sp, sp_info): + ''' + The title for the dialog. + ''' + if sp is None: + title = unicode(sp_info.name) + else: + title = _(u"{account_name:s} - {service_name:s} Settings").format(account_name=sp.name, + service_name=sp_info.name) + return title + +def LabelText(parent, label, labelStyle = wx.ALIGN_RIGHT, textStyle = 0, textSize = txtSize): + ''' + Create a label with a text control next to it. + ''' + label = wx.StaticText(parent, -1, label, style = labelStyle) + text = wx.TextCtrl(parent, -1, style = textStyle, size = textSize) + + return dict(label = label, text = text) + +def LabelChoice(parent, label, labelStyle = wx.ALIGN_RIGHT): + ''' + Create a label with a wx.Choice control next to it + ''' + label = wx.StaticText(parent, -1, _('Mail Client:'), style = labelStyle) + choice = wx.Choice(parent) + + return dict(label = label, choice = choice) + +def HyperlinkControl(parent, label, url): + ''' + Creates a hyperlink control with the given label and destination URL. Sets all display colors to be the same. + ''' + hlc = wx.HyperlinkCtrl(parent, -1, label, url) + hlc.HoverColour = hlc.VisitedColour = hlc.NormalColour + + return hlc + +def LabelTextUrl(parent, label, url_label, url, + labelStyle = wx.ALIGN_RIGHT, + textStyle = 0, + textSize = txtSize): + ''' + Create a trio of label, text field, and URL controls. + ''' + controls = LabelText(parent, label, labelStyle, textStyle, textSize) + + controls['url'] = HyperlinkControl(parent, url_label, url) + controls['url'].MoveBeforeInTabOrder(parent.Children[0]) + + return controls + +def ServerPort(parent, server_label, port_label = _("Port:")): + ''' + Creates two (label, text) pairs for use in entering a server address and port number. + ''' + server = LabelText(parent, server_label) + port = LabelText(parent, port_label, textSize = halfTxtSize) + + server['portlabel'] = port['label'] + server['porttext'] = port['text'] + return server + +# Construct +## Basic +def construct_basic_panel(parent, SP, MSP): + # TODO: bind and populate + panel = getattr(parent, 'basic_panel', None) + if panel is None: + panel = wx.Panel(parent = parent) + + if getattr(panel, 'controls', None) is None: + panel.controls = {} + + sz = panel.Sizer = wx.BoxSizer(wx.VERTICAL) + + # Warnings label + warnings_lbl = panel.controls['warnings_lbl'] = wx.StaticText(panel, -1, '', style = wx.ALIGN_CENTER) + warnings_lbl.SetForegroundColour(wx.Colour(224, 0, 0)) + warnings_lbl.Show(False) + warn_sz = wx.BoxSizer(wx.HORIZONTAL) + warn_sz.Add(warnings_lbl, 1, flag = wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border = warnings_lbl.GetDefaultBorder()) + sz.Add(warn_sz, flag = wx.EXPAND | wx.TOP, border = panel.GetDefaultBorder()) + + fx = wx.GridBagSizer(0, 0) + fx.SetEmptyCellSize((0, 0)) + fx.AddGrowableCol(1,1) + fx.row = 0 + + panel.controls['basic_sz'] = fx + + sz.Add(fx, 1, wx.EXPAND | wx.ALL, panel.Top.GetDialogBorder()) + + return panel + +def construct_basic_subpanel_provider(panel, SP, MSP, MSC): + # TODO: bind and populate + fx = panel.controls['basic_sz'] + + emailaddress_stuff = username_stuff = password_stuff = None + + if MSP.info.provider_info.get('needs_smtp', False): + emailaddress_stuff = LabelText(panel, _("Email Address:")) + emailaddress_stuff['text'].Value = getattr(SP, 'email_address', u'') + emailaddress_stuff['text'].SetFocus() + + if SP is None and MSP.info.provider_info.get('newuser_url', None) is not None: + username_stuff = LabelTextUrl(panel, + MSP.info.provider_info.get('username_desc', _("Username")) + ':', + _("New User?"), + MSP.info.provider_info.newuser_url) + else: + username_stuff = LabelText(panel, + MSP.info.provider_info.get('username_desc', _("Username")) + ':') + + if SP is not None: + username_stuff['text'].Value = getattr(SP, 'name', getattr(SP, 'label', u'')) + username_stuff['text'].Enabled = False + elif emailaddress_stuff is None: + username_stuff['text'].SetFocus() + + needs_password = MSP.info.provider_info.get('needs_password', True) + + if needs_password and MSP.info.provider_info.get('password_url', None) is not None: + password_stuff = LabelTextUrl(panel, + MSP.info.provider_info.get('password_desc', _("Password")) + ":", + _("Forgot Password?"), + MSP.info.provider_info.password_url, + textStyle = wx.TE_PASSWORD, + ) + elif needs_password: + password_stuff = LabelText(panel, + MSP.info.provider_info.get('password_desc', _("Password")) + ":", + textStyle = wx.TE_PASSWORD, + ) + + if password_stuff is not None and SP is not None: + try: + password_stuff['text'].Value = SP._decryptedpw() + except UnicodeError: + log.error("Error decrypting password") + password_stuff['text'].Value = u'' + + if emailaddress_stuff is None: + password_stuff['text'].SetFocus() + password_stuff['text'].SetSelection(-1, -1) + + panel.controls.update( + emailaddress = emailaddress_stuff, + username = username_stuff, + password = password_stuff, + ) + + AddRowToGridBag(fx, emailaddress_stuff) + AddRowToGridBag(fx, username_stuff) + AddRowToGridBag(fx, password_stuff) + + if password_stuff is not None and emailaddress_stuff is not None: + AddLineToGridBag(fx, panel) + +def construct_basic_subpanel_im(panel, SP, MSP, MSC): + # TODO: bind and populate + fx = panel.controls['basic_sz'] + + remotealias_stuff = register_stuff = None + if MSC.info.get('needs_remotealias', None) is not None: + remotealias_stuff = LabelText(panel, _("Display Name:")) + remotealias_stuff['text'].Value = getattr(SP, 'remote_alias', u'') + + if MSC.info.get('needs_register', False) and SP is None: + register_stuff = dict(check = wx.CheckBox(panel, -1, _("&Register New Account"))) + + panel.controls.update( + remotealias = remotealias_stuff, + register = register_stuff, + ) + + AddRowToGridBag(fx, remotealias_stuff) + AddRowToGridBag(fx, register_stuff) + +def construct_basic_subpanel_email(panel, SP, MSP, MSC): + # TODO: bind and populate + fx = panel.controls['basic_sz'] + + emailserver_stuff = emailssl_stuff = smtpserver_stuff = smtpssl_stuff = None + + def ssl_port_swap_handler(checkbox, portfield, default_value, ssl_value): + def handler(e = None): + if e is not None: + e.Skip() + if checkbox.Value and portfield.Value == str(default_value): + portfield.Value = str(ssl_value) + elif (not checkbox.Value) and portfield.Value == str(ssl_value): + portfield.Value = str(default_value) + return handler + + needs_server = MSC.info.get('needs_server', None) + if needs_server is not None: + server_type = needs_server.lower() + + default_port = MSC.info.defaults.get('%sport' % server_type, u'') + default_ssl_port = MSC.info.defaults.get('%sport_ssl' % server_type, u'') + require_ssl = getattr(SP, 'require_ssl', MSC.info.defaults.get('require_ssl', False)) + + port = getattr(SP, '%sport' % server_type, default_ssl_port if require_ssl else default_port) + + emailserver_stuff = ServerPort(panel, _('&{server_type} Server:').format(server_type = _(needs_server))) + emailssl_stuff = dict(check = wx.CheckBox(panel, -1, _('&This server requires SSL'))) + emailserver_stuff['text'].Value = getattr(SP, '%sserver' % server_type, MSC.info.defaults.get('%sserver' % server_type, u'')) + emailserver_stuff['porttext'].Value = str(port) + emailssl_stuff['check'].Value = bool(require_ssl) + ssl_chk = emailssl_stuff['check'] + + handler = ssl_port_swap_handler(ssl_chk, emailserver_stuff['porttext'], default_port, default_ssl_port) + ssl_chk.Bind(wx.EVT_CHECKBOX, handler) + handler() + + if MSP.info.provider_info.get('needs_smtp', False): + default_port = MSC.info.defaults.get('smtp_port', u'') + default_ssl_port = MSC.info.defaults.get('smtp_port_ssl', u'') + require_ssl = getattr(SP, 'smtp_require_ssl', MSC.info.defaults.get('smtp_require_ssl', False)) + + port = getattr(SP, 'smtp_port', default_ssl_port if require_ssl else default_port) + + smtpserver_stuff = ServerPort(panel, _('SMTP Server:')) + smtpssl_stuff = dict(check = wx.CheckBox(panel, -1, _('This server requires SSL'))) + smtpserver_stuff['text'].Value = getattr(SP, 'smtp_server', MSC.info.defaults.get('smtp_server', u'')) + smtpserver_stuff['porttext'].Value = str(port) + smtpssl_stuff['check'].Value = bool(require_ssl) + ssl_chk = smtpssl_stuff['check'] + + handler = ssl_port_swap_handler(ssl_chk, smtpserver_stuff['porttext'], default_port, default_ssl_port) + ssl_chk.Bind(wx.EVT_CHECKBOX, handler) + handler() + + panel.controls.update( + emailserver = emailserver_stuff, + emailssl = emailssl_stuff, + smtpserver = smtpserver_stuff, + smtpssl = smtpssl_stuff, + ) + + AddRowToGridBag(fx, emailserver_stuff) + AddRowToGridBag(fx, emailssl_stuff) + + AddRowToGridBag(fx, smtpserver_stuff) + AddRowToGridBag(fx, smtpssl_stuff) + +def construct_basic_subpanel_social(panel, SP, MSP, MSC): + # TODO: bind and populate + pass + +## Advanced +def construct_advanced_panel(parent, SP, MSP): + + panel = getattr(parent, 'advanced_panel', None) + if panel is None: + panel = wx.Panel(parent = parent) + + panel.Label = "advanced panel" + + if getattr(panel, 'controls', None) is None: + panel.controls = {} + + sz = panel.Sizer = wx.BoxSizer(wx.VERTICAL) + + fx = wx.GridBagSizer(0, 0) + fx.SetEmptyCellSize((0, 0)) + fx.AddGrowableCol(1,1) + fx.row = 0 + + panel.controls['advanced_sz'] = fx + + sz.Add(fx, 1, wx.EXPAND | wx.ALL, panel.Top.GetDialogBorder()) + + return panel + +def construct_advanced_subpanel_provider(panel, SP, MSP, MSC): + return None + +def construct_advanced_subpanel_im(panel, SP, MSP, MSC): + fx = panel.controls['advanced_sz'] + + imserver_stuff = dataproxy_stuff = httponly_stuff = resource_stuff = None + + imserver_stuff = ServerPort(panel, _("IM Server:")) + + host, port = getattr(SP, 'server', MSC.info.defaults.get('server')) + imserver_stuff['text'].Value = host + imserver_stuff['porttext'].Value = str(port) + + if MSC.info.get('needs_resourcepriority', False): + resource_stuff = ServerPort(panel, _("Resource:"), _("Priority:")) + resource_stuff['text'].Value = getattr(SP, 'resource', MSC.info.defaults.get('resource', u'Digsby')) + resource_stuff['porttext'].Value = str(getattr(SP, 'priority', MSC.info.defaults.get('priority', 5))) + + if MSC.info.get('needs_dataproxy', False): + dataproxy_stuff = LabelText(panel, _("Data Proxy:")) + dataproxy_stuff['text'].Value = getattr(SP, 'dataproxy', MSC.info.defaults.get('dataproxy', u'')) + + panel.controls.update( + imserver = imserver_stuff, + dataproxy = dataproxy_stuff, + resource = resource_stuff, + ) + + if MSC.info.get('needs_httponly'): + httponly_stuff = dict(check = wx.CheckBox(panel, -1, _("Always connect over HTTP"))) + httponly_stuff['check'].Value = getattr(SP, 'use_http_only', MSC.info.defaults.get('use_http_only')) + panel.controls['httponly'] = httponly_stuff + + for detail in MSC.info.get('more_details', []): + raise Exception("Implement a custom UI builder for this plugin: %r", MSP.provider_id) + + AddRowToGridBag(fx, imserver_stuff) + AddRowToGridBag(fx, resource_stuff) + AddRowToGridBag(fx, dataproxy_stuff, spans=dict(text=(1,3))) + AddRowToGridBag(fx, httponly_stuff) + +def construct_advanced_subpanel_email(panel, SP, MSP, MSC): + fx = panel.controls['advanced_sz'] + + textparts = _("Check for new mail every {minutes_input} minutes").split('{minutes_input}') + + + updatefreq_stuff = dict( + label1 = wx.StaticText(panel, -1, textparts[0].strip()), + text = wx.TextCtrl(panel, -1, size = qtrTxtSize), + label2 = wx.StaticText(panel, -1, textparts[1].strip()) + ) + + h = wx.BoxSizer(wx.HORIZONTAL) + ezadd(h, updatefreq_stuff['label1'], 0, flag = center_right_all) + ezadd(h, updatefreq_stuff['text'], 0, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL) + ezadd(h, updatefreq_stuff['label2'], 0, flag = center_left_all) + ezadd(fx, h, (getattr(fx, 'row', 0), 1), (1, 3), flag = wx.EXPAND) + fx.row = getattr(fx, 'row', 0) + 1 + + updatefreq_stuff['text'].Value = str(getattr(SP, 'updatefreq', + MSC.info.defaults.get + ('updatefreq', 300)) / 60) + + webclient_stuff = mailclient = None + if MSC.info.get('needs_webclient', True): + webclient_stuff = LabelChoice(panel, _("Mail Client:")) + _setup_mailclient_choice(webclient_stuff['choice'], getattr(SP, 'mailclient', MSC.info.defaults.get('mailclient', 'sysdefault')) or 'sysdefault') + webclient_stuff['choice'].custom_inbox_url = getattr(SP, 'custom_inbox_url', '') + webclient_stuff['choice'].custom_compose_url = getattr(SP, 'custom_compose_url', '') + webclient_stuff['choice'].Bind(wx.EVT_CHOICE, _mailclient_select_handler(webclient_stuff['choice'])) + + AddRowToGridBag(fx, webclient_stuff) + AddLineToGridBag(fx, panel) + + servertype = MSC.info.get('needs_server', None) + smtppassword_stuff = smtpusername_stuff = None + if servertype is not None: + same = panel.controls['smtpsame_rdo'] = wx.RadioButton(panel, -1, _("SMTP username/password are the same as {servertype}").format(servertype=servertype), style = wx.RB_GROUP) + diff = panel.controls['smtpdiff_rdo'] = wx.RadioButton(panel, -1, _("Log on using:")) + + ezadd(fx, panel.controls['smtpsame_rdo'], (fx.row, 0), (1, 4), flag = wx.EXPAND | wx.ALL) + fx.row += 1 + + ezadd(fx, panel.controls['smtpdiff_rdo'], (fx.row, 0), (1, 4), flag = wx.EXPAND | wx.ALL) + fx.row += 1 + + smtpusername_stuff = LabelText(panel, _("Username:")) + smtppassword_stuff = LabelText(panel, _("Password:"), textStyle = wx.TE_PASSWORD) + + def set_textfields_enabled(e = None): + enabled = diff.Value + smtppassword_stuff['text'].Enabled = smtpusername_stuff['text'].Enabled = enabled + if e is not None: + e.Skip() + + if not enabled: + smtppassword_stuff['text'].Value = smtpusername_stuff['text'].Value = u'' + + same.Value = True + diff.Value = False + set_textfields_enabled() + + same.Bind(wx.EVT_RADIOBUTTON, set_textfields_enabled) + diff.Bind(wx.EVT_RADIOBUTTON, set_textfields_enabled) + + AddRowToGridBag(fx, smtpusername_stuff) + AddRowToGridBag(fx, smtppassword_stuff) + + if SP is None: + smtpuser = smtppass = u'' + else: + smtpuser, smtppass = getattr(SP, 'smtp_username', u''), SP._decrypted_smtppw() + + same = (not (smtpuser or smtppass)) or (smtpuser == getattr(SP, 'username', None) and smtppass == getattr(SP, 'password', None)) + panel.controls['smtpsame_rdo'].Value = same + panel.controls['smtpdiff_rdo'].Value = not same + if not same: + smtpusername_stuff['text'].Value = smtpuser + smtppassword_stuff['text'].Value = smtppass + + set_textfields_enabled() + + panel.controls.update( + updatefreq = updatefreq_stuff, + mailclient = webclient_stuff, + smtpusername = smtpusername_stuff, + smtppassword = smtppassword_stuff, + ) + +def construct_advanced_subpanel_social(panel, SP, MSP, MSC): + return None + +def layout_polish(basic_panel, advanced_panel): + + if advanced_panel: + labels = [] + label_align_w = -1 + for name, control_set in basic_panel.controls.items() + advanced_panel.controls.items(): + if not isinstance(control_set, dict): + continue + if 'port' not in name and control_set.get('label', None) is not None: + labels.append(control_set['label']) + label_align_w = max(label_align_w, control_set['label'].GetBestSize().x) + + for c in labels: + c.SetMinSize((label_align_w, c.GetBestSize().y)) + +def extract_basic_panel(panel, info, SP, MSP): + if SP is not None: + # Start with the previous settings as the base. Changed options will get overridden in + # the methods that follow. This also preserves account options not set by the dialog. + info.update(SP.get_options('im')) + info.update(SP.get_options('email')) + info.update(SP.get_options('social')) + +def extract_basic_subpanel_provider(panel, info, SP, MSP, MSC): + emailaddress = panel.controls.get('emailaddress', None) + if emailaddress is not None: + info['email_address'] = emailaddress['text'].Value + + info['name'] = info['label'] = panel.controls['username']['text'].Value + password = panel.controls.get('password', None) + if password is not None: + try: + info['password'] = common.profile.crypt_pw(password['text'].Value) + info['_real_password_'] = password['text'].Value + except UnicodeError: + log.error("Error encrypting password") + info['password'] = '' + + return True + +def extract_basic_subpanel_im(panel, info, SP, MSP, MSC): + remotealias = panel.controls.get('remotealias', None) + if remotealias is not None: + info['remote_alias'] = remotealias['text'].Value + + register = panel.controls.get('register', None) + if register is not None: + info['register'] = register['check'].Value + + return True + +def extract_basic_subpanel_email(panel, info, SP, MSP, MSC): + emailserver = panel.controls.get('emailserver', None) + if emailserver is not None: + server_type = MSC.info.get('needs_server', '').lower() + + host = emailserver['text'].Value + port = emailserver['porttext'].Value + ssl = panel.controls['emailssl']['check'].Value + + info[server_type + 'server'] = host + info[server_type + 'port'] = port + info['require_ssl'] = ssl + + smtpserver = panel.controls.get('smtpserver', None) + if smtpserver is not None: + host = smtpserver['text'].Value + port = smtpserver['porttext'].Value + ssl = panel.controls['smtpssl']['check'].Value + + info['smtp_server'] = host + info['smtp_port'] = port + info['smtp_require_ssl'] = ssl + + return True + +def extract_basic_subpanel_social(panel, info, SP, MSP, MSC): + pass + +def extract_advanced_panel(panel, info, SP, MSP): + pass +def extract_advanced_subpanel_provider(panel, info, SP, MSP, MSC): + pass +def extract_advanced_subpanel_im(panel, info, SP, MSP, MSC): + host, port = panel.controls['imserver']['text'].Value, panel.controls['imserver']['porttext'].Value + + info['server'] = (host, port) + + httponly = panel.controls.get('httponly', None) + if httponly is not None: + info['use_http_only'] = httponly['check'].Value + + resource = panel.controls.get('resource', None) + if resource is not None: + info['resource'] = resource['text'].Value + info['priority'] = resource['porttext'].Value + + dataproxy = panel.controls.get('dataproxy', None) + if dataproxy is not None: + info['dataproxy'] = dataproxy['text'].Value + + return True + +def extract_advanced_subpanel_email(panel, info, SP, MSP, MSC): + try: + val = int(panel.controls['updatefreq']['text'].Value) * 60 + except ValueError: + val = 300 + info['updatefreq'] = val + + mailclient = panel.controls.get('mailclient', None) + if mailclient is not None: + mailclient = _extract_mailclient_choice(mailclient['choice']) + info.update(mailclient) + + smtpusername = panel.controls.get('smtpusername', None) + if smtpusername is not None: + unctrl = smtpusername['text'] + info['smtp_username'] = unctrl.Value if unctrl.Enabled else u'' + pwctrl = panel.controls['smtppassword']['text'] + info['_encrypted_pw'] = info.pop('password', '') + info['_encrypted_smtppw'] = common.profile.crypt_pw(pwctrl.Value) if pwctrl.Enabled else u'' + elif MSP.info.get('protocol_info', {}).get('needs_smtp', False): + info['smtp_username'] = info['username'] + info['_encrypted_smtppw'] = info['_encrypted_pw'] = info.pop('password') + + return True + +def extract_advanced_subpanel_social(panel, info, SP, MSP, MSC): + pass + +MAIL_CLIENT_SYSDEFAULT = _('System Default') +MAIL_CLIENT_OTHER = _('Other Mail Client...') +MAIL_CLIENT_URL = _('Launch URL...') + +def _setup_mailclient_choice(ch, mc): + + with ch.Frozen(): + ch.Clear() + ch.mailclient = mc + choices = [MAIL_CLIENT_SYSDEFAULT] + + file_entry = 0 + if mc.startswith('file:'): + import os.path + if not os.path.exists(mc[5:]): + mc == 'sysdefault' + else: + choices += [_('Custom ({mailclient})').format(mailclient=mc[5:])] + file_entry = len(choices) - 1 + + choices += [MAIL_CLIENT_OTHER, + MAIL_CLIENT_URL] + + for s in choices: + ch.Append(s) + + if mc == 'sysdefault': + selection = 0 + elif mc == '__urls__': + selection = ch.Count - 1 + else: + selection = file_entry + + ch.SetSelection(selection) + ch.Layout() + +def _mailclient_select_handler(ch): + def evt_handler(evt = None): + val = ch.StringSelection + if val.startswith(MAIL_CLIENT_SYSDEFAULT): + ch._Value = dict(mailclient = 'sysdefault') + elif val == MAIL_CLIENT_OTHER: + import os, sys + defaultDir = os.environ.get('ProgramFiles', '') + + wildcard = '*.exe' if sys.platform == 'win32' else '*.*' + filediag = wx.FileDialog(ch.Top, _('Please choose a mail client'), + defaultDir = defaultDir, + wildcard = wildcard, + style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if filediag.ShowModal() == wx.ID_OK: + ch._Value = dict(mailclient = 'file:' + filediag.Path) + elif val == MAIL_CLIENT_URL: + diag = LaunchURLDialog(ch.Top, ch.custom_inbox_url, ch.custom_compose_url) + try: + if wx.ID_OK == diag.ShowModal(): + mailclient = '__urls__' + ch.custom_inbox_url = diag.InboxURL + ch.custom_compose_url = diag.ComposeURL + ch._Value = dict(mailclient = '__urls__', custom_inbox_url = diag.InboxURL, custom_compose_url = diag.ComposeURL) + + finally: + diag.Destroy() + + return evt_handler + +def _extract_mailclient_choice(ch): + val = getattr(ch, '_Value', None) + if val is not None: + return val + + return dict(mailclient = ch.mailclient, custom_inbox_url = ch.custom_inbox_url, custom_compose_url = ch.custom_compose_url) + + +class LaunchURLDialog(toolbox.OKCancelDialog): + ''' + email accounts let you specify custom URLs for inbox and compose actions. + this dialog lets you enter those URLs. + ''' + + MINSIZE = (350, 1) + + inbox_tooltip = _('Enter the URL that will be launched when you click "Inbox" for this email account.') + compose_tooltip = _('Enter the URL that will be launched when you click "Compose" for this email account.') + + def __init__(self, parent, inbox_url = None, compose_url = None): + toolbox.OKCancelDialog.__init__(self, parent, title=_('Launch URL')) + + self.construct(inbox_url, compose_url) + self.layout() + + @property + def InboxURL(self): return self.inbox_text.Value + + @property + def ComposeURL(self): return self.compose_text.Value + + def construct(self, inbox_url = None, compose_url = None): + # construct GUI + self.inbox_label = wx.StaticText(self, -1, _('Enter a URL for the Inbox')) + self.inbox_text = wx.TextCtrl(self, -1, inbox_url or '') + + self.compose_label = wx.StaticText(self, -1, _('Enter a URL for the Compose window')) + self.compose_text = wx.TextCtrl(self, -1, compose_url or '') + + # add tooltips + self.inbox_label.SetToolTipString(self.inbox_tooltip) + self.inbox_text.SetToolTipString(self.inbox_tooltip) + self.compose_label.SetToolTipString(self.compose_tooltip) + self.compose_text.SetToolTipString(self.compose_tooltip) + + # connect event handlers for disabling OK when there is missing + # content. + self.inbox_text.Bind(wx.EVT_TEXT, self.on_text) + self.compose_text.Bind(wx.EVT_TEXT, self.on_text) + self.on_text() + + def on_text(self, e = None): + if e is not None: + e.Skip() + + self.OKButton.Enable(bool(self.inbox_text.Value and self.compose_text.Value)) + + def layout(self): + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.AddMany([ + (self.inbox_label, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5), + (self.inbox_text, 0, wx.EXPAND | wx.LEFT, 7), + (self.compose_label, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 5), + (self.compose_text, 0, wx.EXPAND | wx.LEFT, 7), + self.MINSIZE, + ]) + + self.set_component(sizer) + + self.Fit() + diff --git a/digsby/src/plugins/digsby_service_editor/info.yaml b/digsby/src/plugins/digsby_service_editor/info.yaml new file mode 100644 index 0000000..2203550 --- /dev/null +++ b/digsby/src/plugins/digsby_service_editor/info.yaml @@ -0,0 +1,74 @@ +name: 'DigsbyServiceDialog' +path: 'digsby_service_editor' +type: 'pure' + +entry_points: + digsby.services.create: + digsby_service_editor: digsby_service_editor.service_editor:create + digsby.services.edit: + digsby_service_editor: digsby_service_editor.service_editor:edit + digsby.services.edit.title: + digsby_service_editor: digsby_service_editor.default_ui:title_for_service + digsby.services.delete.build_dialog: + default: digsby_service_editor.service_editor:delete_dialog + digsby.services.edit.save: + digsby_service_editor: digsby_service_editor.service_editor:on_save_account + + # Construct + ## Basic + digsby.services.edit.basic.construct_panel: + digsby_service_editor: digsby_service_editor.default_ui:construct_basic_panel + digsby.services.edit.basic.construct_sub.provider: + digsby_service_editor: digsby_service_editor.default_ui:construct_basic_subpanel_provider + digsby.services.edit.basic.construct_sub.im: + digsby_service_editor: digsby_service_editor.default_ui:construct_basic_subpanel_im + digsby.services.edit.basic.construct_sub.email: + digsby_service_editor: digsby_service_editor.default_ui:construct_basic_subpanel_email + digsby.services.edit.basic.construct_sub.social: + digsby_service_editor: digsby_service_editor.default_ui:construct_basic_subpanel_social + ## Advanced + digsby.services.edit.advanced.construct_panel: + digsby_service_editor: digsby_service_editor.default_ui:construct_advanced_panel + digsby.services.edit.advanced.construct_sub.provider: + digsby_service_editor: digsby_service_editor.default_ui:construct_advanced_subpanel_provider + digsby.services.edit.advanced.construct_sub.im: + digsby_service_editor: digsby_service_editor.default_ui:construct_advanced_subpanel_im + digsby.services.edit.advanced.construct_sub.email: + digsby_service_editor: digsby_service_editor.default_ui:construct_advanced_subpanel_email + digsby.services.edit.advanced.construct_sub.social: + digsby_service_editor: digsby_service_editor.default_ui:construct_advanced_subpanel_social + + digsby.services.edit.layout.polish: + digsby_service_editor: digsby_service_editor.default_ui:layout_polish + + # Extract + ## Basic + digsby.services.edit.basic.extract_panel: + digsby_service_editor: digsby_service_editor.default_ui:extract_basic_panel + digsby.services.edit.basic.extract_sub.provider: + digsby_service_editor: digsby_service_editor.default_ui:extract_basic_subpanel_provider + digsby.services.edit.basic.extract_sub.im: + digsby_service_editor: digsby_service_editor.default_ui:extract_basic_subpanel_im + digsby.services.edit.basic.extract_sub.email: + digsby_service_editor: digsby_service_editor.default_ui:extract_basic_subpanel_email + digsby.services.edit.basic.extract_sub.social: + digsby_service_editor: digsby_service_editor.default_ui:extract_basic_subpanel_social + ## Advanced + digsby.services.edit.advanced.extract_panel: + digsby_service_editor: digsby_service_editor.default_ui:extract_advanced_panel + digsby.services.edit.advanced.extract_sub.provider: + digsby_service_editor: digsby_service_editor.default_ui:extract_advanced_subpanel_provider + digsby.services.edit.advanced.extract_sub.im: + digsby_service_editor: digsby_service_editor.default_ui:extract_advanced_subpanel_im + digsby.services.edit.advanced.extract_sub.email: + digsby_service_editor: digsby_service_editor.default_ui:extract_advanced_subpanel_email + digsby.services.edit.advanced.extract_sub.social: + digsby_service_editor: digsby_service_editor.default_ui:extract_advanced_subpanel_social + + digsby.services.validate: + digsby_service_editor: digsby_service_editor.service_editor:validate + digsby.services.normalize: + digsby_service_editor: digsby_service_editor.service_editor:normalize + digsby.services.colorize_name: + digsby_service_editor: digsby_service_editor.service_editor:colorize_name + diff --git a/digsby/src/plugins/digsby_service_editor/service_editor.py b/digsby/src/plugins/digsby_service_editor/service_editor.py new file mode 100644 index 0000000..97e5276 --- /dev/null +++ b/digsby/src/plugins/digsby_service_editor/service_editor.py @@ -0,0 +1,281 @@ +import services.service_provider as SP +import wx +import wx.lib.sized_controls as sc +import gui.skin as skin +import gui.toolbox as toolbox +import gui.chevron as chevron + +import hooks +import config +import util +import util.callbacks as callbacks +import common + +import logging +log = logging.getLogger('digsby_service_editor') + +class ServiceEditor(sc.SizedDialog): + @classmethod + def create(cls, parent = None, sp_info = None): + return cls(parent = parent, sp_info = sp_info) + + @classmethod + def edit(cls, parent = None, sp = None): + return cls(parent = parent, sp = sp) + + def __init__(self, parent = None, sp = None, sp_info = None): + self.new = sp is None + + if sp_info is None: + if sp is None: + raise Exception() + + sp_info = SP.get_meta_service_provider(sp.provider_id) + + self.sp = sp + self.sp_info = sp_info + + self.component_names = list(x.component_type for x in SP.get_meta_components_for_provider(self.sp_info.provider_id)) + ['provider'] + self.component_names.sort(key = lambda x: ("provider", "im", "email", "social").index(x)) + + title = self.hook("digsby.services.edit.title", self.sp, self.sp_info) + + wx.Dialog.__init__(self, parent, title = title) + + self.SetFrameIcon(skin.get("serviceprovidericons.%s" % self.sp_info.provider_id)) + + self.construct() + self.layout() + self.bind_events() + self.DoValidate() + + self.Fit() + + def hook(self, hookname, *hookargs, **hookkwargs): + return hooks.first(hookname, impls = (self.sp_info.provider_id, 'digsby_service_editor'), *hookargs, **hookkwargs) + + def construct(self): + for panel_type in ("basic", "advanced"): + parent = self + if panel_type == "advanced": + self.chevron = chevron.ChevronPanel(self, _("Advanced")) + parent = self.chevron.contents + panel = self.hook("digsby.services.edit.%s.construct_panel" % panel_type, + parent = parent, + SP = self.sp, + MSP = self.sp_info) + + if panel is False: + panel = None + setattr(self, "%s_panel" % panel_type, panel) + if panel is None: + continue + + panel.Label = "%s_panel" % panel_type + + for partname in self.component_names: + self.hook("digsby.services.edit.%s.construct_sub.%s" % (panel_type, partname), + panel = panel, + SP = self.sp, + MSP = self.sp_info, + MSC = SP.get_meta_component_for_provider(self.sp_info.provider_id, partname)) + + assert hasattr(self, 'basic_panel') + assert hasattr(self, 'advanced_panel') + + if not self.advanced_panel: + self.chevron.Destroy() + self.chevron = None + + self.save_btn = wx.Button(self, wx.ID_SAVE, _("&Save")) + self.save_btn.SetDefault() + + self.cancel_btn = wx.Button(self, wx.ID_CANCEL, _("&Cancel")) + + def layout(self): + if self.basic_panel is not None: + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.basic_panel) + + if self.advanced_panel: + self.Sizer.Add(self.chevron, 1, wx.EXPAND | wx.LEFT, 3) + self.chevron.contents.Sizer.Add(self.advanced_panel, 1, wx.EXPAND) + + self.Sizer.Add(toolbox.build_button_sizer(self.save_btn, self.cancel_btn, border = self.save_btn.GetDefaultBorder()), 0, wx.EXPAND | wx.ALL, self.GetDefaultBorder()) + + self.hook('digsby.services.edit.layout.polish', self.basic_panel, getattr(self, 'advanced_panel', None)) + + def bind_events(self): + + self.Bind(wx.EVT_TEXT, self.DoValidate) + self.Bind(wx.EVT_CHOICE, self.DoValidate) + self.Bind(wx.EVT_CHECKBOX, self.DoValidate) + self.Bind(wx.EVT_RADIOBUTTON, self.DoValidate) + # TODO: bind all event types that might get fired. + self.save_btn.Bind(wx.EVT_BUTTON, self.OnSave) + + def OnSave(self, evt): + success = lambda: self.EndModal(wx.ID_SAVE) + error = lambda: self.EndModal(wx.ID_CANCEL) + + self.hook("digsby.services.edit.save", success = success, error = error, info = self.extract(), SP = self.sp, MSP = self.sp_info) + + def on_success_register(self): + self.EndModal(wx.ID_SAVE) + + def on_fail_register(self, error): + textcode, text, kind, codenum = error + wx.MessageBox("Error %(codenum)d: %(text)s" % locals(), textcode) + self.EndModal(wx.ID_CANCEL) + + def extract(self): + info = {'provider_id' : self.sp_info.provider_id} + for panel_type in ('basic', 'advanced'): + panel = getattr(self, '%s_panel' % panel_type, None) + if panel is None: + continue + + self.hook('digsby.services.edit.%s.extract_panel' % panel_type, + panel = panel, + info = info, + SP = self.sp, + MSP = self.sp_info) + + for component in self.component_names: + + self.hook('digsby.services.edit.%s.extract_sub.%s' % (panel_type, component), + panel = panel, + info = info, + SP = self.sp, + MSP = self.sp_info, + MSC = SP.get_meta_component_for_provider(self.sp_info.provider_id, component)) + + return info + + def DoValidate(self, e = None): + if e is not None: + e.Skip() + + self.validate() + + def SetWarning(self, message = None): + warnings = self.basic_panel.controls.get('warnings_lbl', None) + + if warnings is None: + return + + if not message: + message = '' + + if warnings.Label == message: + return + warnings.Label = message + + # FIXME: this throws the sizing on Mac all out of whack. Perhaps some native API gets + # messed up when called on a hidden control? + if not config.platformName == "mac": + if not message: + warnings.Show(False) + else: + warnings.Show(True) + + self.Layout() + self.Fit() + self.Refresh() + + def validate(self): + info = self.extract() + info = self.hook('digsby.services.normalize', info, self.sp_info, self.new) + try: + self.hook('digsby.services.validate', info, self.sp_info, self.new, raise_hook_exceptions = True) + except SP.AccountException, e: + fatal = e.fatal + self.save_btn.Enabled = not fatal + self.SetWarning(getattr(e, 'message', u'')) + return False + else: + self.save_btn.Enabled = True + self.SetWarning(None) + return True + + def RetrieveData(self): + info = self.extract() + info.pop("_real_password_", None) + return info + +def edit(parent = None, sp = None): + return ServiceEditor.edit(parent = parent, sp = sp) + +def create(parent = None, sp_info = None): + return ServiceEditor.create(parent = parent, sp_info = sp_info) + +def delete_dialog(parent = None, SP = None): + + message = _('Are you sure you want to delete account "{name}"?').format(name=SP.name) + caption = _('Delete Account') + style = wx.ICON_QUESTION | wx.YES_NO + + msgbox = wx.MessageDialog(parent, message, caption, style) + + return msgbox + +def validate(info, MSP, is_new): + spc = SP.ServiceProviderContainer(common.profile()) + if is_new and spc.has_account(info): + raise SP.AccountException(_("That account already exists.")) + + try: + sp = hooks.first('digsby.service_provider', impl = MSP.provider_id, raise_hook_exceptions = True, **info) + except SP.AccountException, e: + raise e + except Exception, e: + import traceback; traceback.print_exc() + raise SP.AccountException(e.args[0] if e.args else "Unknown error", True) + + return True + +def normalize(info, MSP, is_new): + + return info + +def colorize_name(name): + # The sum of the strings returned by this should be less + # than or equal to the length of the input + if '@' in name: + split = name.split('@', 1) + return (('name', split[0]), + ('base', '@' + split[1])) + else: + return (('name', name),) + +@callbacks.callsback +def on_save_account(SP, MSP, info, callback = None): + if SP is not None: + for ctype in SP.get_component_types(): + comp = SP.get_component(ctype, create = False) + if comp is None: + continue + conn = getattr(comp, 'connection', None) + if conn is None: + continue + for updatee in getattr(conn, 'needs_update', []): + try: + attr, fname = updatee + f = getattr(conn, fname) + except (TypeError,ValueError) as e: + attr = updatee + f = lambda v: setattr(conn, attr, v) + + f(info.get(attr)) + + if info.get('register', False): + log.info_s('adding account: %r', info) + common.profile().register_account( + on_success = callback.success, + on_fail = callback.error, + **info + ) + else: + callback.success() + + diff --git a/digsby/src/plugins/digsby_service_providers/__init__.py b/digsby/src/plugins/digsby_service_providers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsby_service_providers/info.yaml b/digsby/src/plugins/digsby_service_providers/info.yaml new file mode 100644 index 0000000..ba891c7 --- /dev/null +++ b/digsby/src/plugins/digsby_service_providers/info.yaml @@ -0,0 +1,16 @@ +name: Service Provider Icons Package +type: pure +skin: + serviceprovidericons: + aol: res/aol.png + facebook: res/facebook.png + google: res/google.png + icq: res/icq.png + imap: res/imap.png + jabber: res/jabber.png + linkedin: res/linkedin.png + myspace: res/myspace.png + pop: res/pop.png + twitter: res/twitter.png + windows_live: res/windows_live.png + yahoo: res/yahoo.png diff --git a/digsby/src/plugins/digsby_service_providers/res/aol.png b/digsby/src/plugins/digsby_service_providers/res/aol.png new file mode 100644 index 0000000..f9d9ecf Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/aol.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/facebook.png b/digsby/src/plugins/digsby_service_providers/res/facebook.png new file mode 100644 index 0000000..ba68022 Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/facebook.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/google.png b/digsby/src/plugins/digsby_service_providers/res/google.png new file mode 100644 index 0000000..ff919dc Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/google.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/icq.png b/digsby/src/plugins/digsby_service_providers/res/icq.png new file mode 100644 index 0000000..11c2636 Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/icq.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/imap.png b/digsby/src/plugins/digsby_service_providers/res/imap.png new file mode 100644 index 0000000..e5c0341 Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/imap.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/jabber.png b/digsby/src/plugins/digsby_service_providers/res/jabber.png new file mode 100644 index 0000000..c331dea Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/jabber.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/linkedin.png b/digsby/src/plugins/digsby_service_providers/res/linkedin.png new file mode 100644 index 0000000..0c7adf4 Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/linkedin.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/myspace.png b/digsby/src/plugins/digsby_service_providers/res/myspace.png new file mode 100644 index 0000000..39ff604 Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/myspace.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/pop.png b/digsby/src/plugins/digsby_service_providers/res/pop.png new file mode 100644 index 0000000..499cab9 Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/pop.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/twitter.png b/digsby/src/plugins/digsby_service_providers/res/twitter.png new file mode 100644 index 0000000..33682ba Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/twitter.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/windows_live.png b/digsby/src/plugins/digsby_service_providers/res/windows_live.png new file mode 100644 index 0000000..c37aec9 Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/windows_live.png differ diff --git a/digsby/src/plugins/digsby_service_providers/res/yahoo.png b/digsby/src/plugins/digsby_service_providers/res/yahoo.png new file mode 100644 index 0000000..c72f93f Binary files /dev/null and b/digsby/src/plugins/digsby_service_providers/res/yahoo.png differ diff --git a/digsby/src/plugins/digsby_status/__init__.py b/digsby/src/plugins/digsby_status/__init__.py new file mode 100644 index 0000000..764585f --- /dev/null +++ b/digsby/src/plugins/digsby_status/__init__.py @@ -0,0 +1 @@ +import status_tag_urls diff --git a/digsby/src/plugins/digsby_status/info.yaml b/digsby/src/plugins/digsby_status/info.yaml new file mode 100644 index 0000000..e2c5190 --- /dev/null +++ b/digsby/src/plugins/digsby_status/info.yaml @@ -0,0 +1,15 @@ +name: 'DigsbyStatusTagging' +path: 'digsby_status' +type: 'pure' + +entry_points: + digsby.status.tagging.tag_url: + text: digsby_status.status_tag_urls:tag_status + digsby.status.tagging.strip_tag: + text: digsby_status.status_tag_urls:remove_tag_text + digsby.status.url.for_protocol: + digsby_status: digsby_status.status_tag_urls:url_for_protocol + digsby.profile.addons: + status_tag: digsby_status.status_tag_urls:StatusPrefUpgrader + digsby.status.url.for_protocol_re: + digsby_status: digsby_status.status_tag_urls:re_url_for_protocol diff --git a/digsby/src/plugins/digsby_status/status_tag_urls.py b/digsby/src/plugins/digsby_status/status_tag_urls.py new file mode 100644 index 0000000..61a74b3 --- /dev/null +++ b/digsby/src/plugins/digsby_status/status_tag_urls.py @@ -0,0 +1,118 @@ +from common import pref, setpref +from gui import skin, toolbox +from logging import getLogger +from peak.util.addons import AddOn +from threading import RLock +import branding, hooks +import re +import warnings + +def url_for_protocol(protocol): + return "http://digsby.com/" + protocol + +def re_url_for_protocol(): + return "http://digsby.com/\S*" + +def url_append_for_protocol(protocol): + url = branding.get('digsby.status.url.for_protocol', 'digsby_status', default=url_for_protocol(protocol), protocol=protocol) + if protocol == 'yahoo': + url = url + " " + url + return url + +def tag_status(message, protocol, status=None): + if pref('digsby.status.promote_tag.enabled', default=True) and \ + (pref('digsby.status.promote_tag.upgrade_response', default=None) is not None): + if protocol == 'msim': + protocol = 'myspaceim' + if not message and status: + message = status + return message + " - I use " + url_append_for_protocol(protocol) + else: + return message + +def branding_re(): + return '( ((' + ')|('.join(hooks.each('digsby.status.url.for_protocol_re')) + ')))' + +BASE_RE = lambda: r"- I use" + branding_re() + "+" +TAGLINE_RE = lambda: r"\s*" + BASE_RE() + +SPECIAL_STATUSES = ''' +Available +Away +Idle +Invisible +Offline +'''.split() + +def remove_tag_text(msg, status=None): + for st in SPECIAL_STATUSES + ([status] if status else []): + msg = re.sub(st + ' ' + BASE_RE(), '', msg) + msg = re.sub(TAGLINE_RE(), '', msg) + return msg + +class StatusTagDialog(toolbox.UpgradeDialog): + faq_link_label = _('Learn More') + faq_link_url = 'http://wiki.digsby.com/doku.php?id=faq#q34' + + def __init__(self, parent, title, message): + icon = skin.get('serviceicons.digsby', None) + if icon is not None: + icon = icon.Resized(32) + + super(StatusTagDialog, self).__init__(parent, title, + message = message, + icon = icon, + ok_caption = _('Yes, Spread the Word!'), + cancel_caption = _('No Thanks'), + link = (self.faq_link_label, self.faq_link_url)) + +class StatusPrefUpgrader(AddOn): + did_setup = False + check_fired = False + def __init__(self, subject): + self.profile = subject + self.lock = RLock() + super(StatusPrefUpgrader, self).__init__(subject) + + def setup(self): + with self.lock: + if self.did_setup: + warnings.warn('reinitialized AddOn StatusPrefUpgrader') + return + self.did_setup = True + hooks.Hook('digsby.accounts.released.async', 'status_tag').register(self.check_accounts) + + def check_accounts(self, *a, **k): + with self.lock: + if self.check_fired: + return + self.check_fired = True + from util.threads.timeout_thread import Timer + Timer(5, self.do_check_accounts, *a, **k).start() + + def do_check_accounts(self, *a, **k): + with self.lock: + if pref('digsby.status.promote_tag.upgrade_response', default=None) is not None: + return + if len(self.profile.all_accounts) == 0: + setpref('digsby.status.promote_tag.upgrade_response', 'skipped_ok') + else: + import wx + @wx.CallAfter + def after(): + from gui.toast.toast import popup + popup(header = _('Spread the Word!'), + icon = skin.get('appdefaults.taskbaricon', None), + major = None, + minor = _("We've made it easier to spread the word about Digsby by adding a link to your IM status. You can disable this option in Preferences > Status."), + sticky = True, + max_lines=10, + onclose=self.response, + buttons=[(_('Close'), lambda *a, **k: None)]) + + + def response(self): + with self.lock: + setpref('digsby.status.promote_tag.upgrade_response', 'ok') + setpref('digsby.status.promote_tag.enabled', True) + diff --git a/digsby/src/plugins/digsby_status/unittests/__init__.py b/digsby/src/plugins/digsby_status/unittests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsby_status/unittests/test_status.py b/digsby/src/plugins/digsby_status/unittests/test_status.py new file mode 100644 index 0000000..449a106 --- /dev/null +++ b/digsby/src/plugins/digsby_status/unittests/test_status.py @@ -0,0 +1,129 @@ +import unittest + +URL = "http://digsby.com/" +_TAGLINE = " - I use " + +MESSAGE_TAGLINE = _TAGLINE + URL +STATUS_TAGLINE = _TAGLINE + URL + +TESTAPP = False + +def fakeprefs(): +# from common import setfakeprefs + global TESTAPP + if not TESTAPP: + TESTAPP = True + from tests.testapp import testapp + testapp(prefs={'digsby.status.promote_tag.upgrade_response':True}, + logging = False) +# setfakeprefs({'digsby.status.promote_tag.upgrade_response':True}) + +class StatusTests(unittest.TestCase): + def setUp(self): + self.message = "Hi!" + self.protocol = 'foo' + self.protoname = 'foo' + self.status = '' + fakeprefs() + + def tearDown(self): + pass + + def testTextAdd(self): + import digsby_status + status = self.message + new_status = digsby_status.status_tag_urls.tag_status(status, self.protocol) + self.assertEqual(new_status, status + MESSAGE_TAGLINE + self.protoname) + + def testRoundTrip(self): + import digsby_status + status = self.message + new_status = digsby_status.status_tag_urls.tag_status(status, self.protocol) + stripped_status = digsby_status.status_tag_urls.remove_tag_text(new_status, self.status) + self.assertEqual(status, stripped_status) + +class MyspaceIm(StatusTests): + def setUp(self): + super(MyspaceIm, self).setUp() + self.protocol = 'msim' + self.protoname = 'myspaceim' + +class YahooIm(StatusTests): + def setUp(self): + super(YahooIm, self).setUp() + self.protocol = 'yahoo' + self.protoname = 'yahoo' + + def testTextAdd(self): + import digsby_status + status = self.message + new_status = digsby_status.status_tag_urls.tag_status(status, self.protocol) + self.assertEqual(new_status, status + MESSAGE_TAGLINE + self.protoname + ' ' + URL + self.protoname) + +class OddMessage(StatusTests): + def setUp(self): + super(OddMessage, self).setUp() + self.message = STATUS_TAGLINE + + def testRoundTrip(self): + self.assertRaises(self.failureException, + super(OddMessage, self).testRoundTrip) + +class OddMessage2(OddMessage): + def setUp(self): + super(OddMessage2, self).setUp() + self.message = STATUS_TAGLINE + STATUS_TAGLINE + +class RemoveAvailable(unittest.TestCase): + + def setUpMessage(self): + self.message = self.status + STATUS_TAGLINE + 'asdkfjlskdljf' + + def setUpStatus(self): + self.status = "Available" + + def setUp(self): + self.setUpStatus() + self.setUpMessage() + fakeprefs() + + def testStripAvailable(self): + import digsby_status + stripped_status = digsby_status.status_tag_urls.remove_tag_text(self.message, self.status) + self.assertEqual(stripped_status, '') + +class RemoveAway(RemoveAvailable): + def setUpStatus(self): + super(RemoveAway, self).setUpStatus() + self.status = "Away" + +class DoNotRemoveMessage(unittest.TestCase): + def setUpMessage(self): + self.basemessage = "Foo" + self.message = self.basemessage + MESSAGE_TAGLINE + 'alsdkfj;' + + def setUpStatus(self): + self.status = "Available" + + def setUp(self): + self.setUpStatus() + self.setUpMessage() + fakeprefs() + + def testStripMessage(self): + import digsby_status + stripped_status = digsby_status.status_tag_urls.remove_tag_text(self.message, self.status) + self.assertEqual(stripped_status, self.basemessage) + +class DoRemoveFoo(DoNotRemoveMessage): + def setUpStatus(self): + self.status = "Foo" + + def testStripMessage(self): + self.assertRaises(self.failureException, + super(DoRemoveFoo, self).testStripMessage) + +class DoNotRemoveMessageSpace(DoNotRemoveMessage): + def setUpStatus(self): + self.status = "Foo " + diff --git a/digsby/src/plugins/digsby_updater/UpdateProgress.py b/digsby/src/plugins/digsby_updater/UpdateProgress.py new file mode 100644 index 0000000..26ba22f --- /dev/null +++ b/digsby/src/plugins/digsby_updater/UpdateProgress.py @@ -0,0 +1,206 @@ +''' +A progress monitor for the app's updater. Works via the file transfer window. + +Displays number of files completed out of number of files to be processed (checked or downloaded) instead of byte +count. Due to differing file sizes, the 'time remaining' feature may not be entirely correct, especially when +downloading. + +Also supported is 'fast mode', which (under normal circumstances, i.e. app run-time) removes any timing-based limiters +on the integrity check and download mechanisms. +''' +import sys +import logging + +import peak.util.addons as addons + +import gui + +import hooks +import util +import common +import common.filetransfer as FT + +log = logging.getLogger("d_updater.progress") + +class UpdateStates(FT.FileTransferStates): + CHECKING_FILES = "Checking files" + FAILED = "Update failed" + + TransferringStates = FT.FileTransferStates.TransferringStates | set(( + CHECKING_FILES, + )) + + ErrorStates = FT.FileTransferStates.ErrorStates | set(( + FAILED, + )) + + FailStates = ErrorStates | set(( + FT.FileTransferStates.CANCELLED_BY_YOU, + )) + +class UpdateProgress(addons.AddOn, FT.FileTransfer): + ATROPHY_TIME = sys.maxint + + direction = 'incoming' + states = UpdateStates + + autoshow = False + autoremove = True + autoremove_states = set((UpdateStates.CANCELLED_BY_YOU, + UpdateStates.FINISHED)) + + use_serviceicon = False + + @property + def icon(self): + return gui.skin.get('serviceicons.digsby') + + def setup(self): + FT.FileTransfer.__init__(self) + self.name = _("Digsby Update") + self.buddy = util.Storage(name = "Digsby Servers", + alias = "Digsby Servers", + icon = None) + + self.xfer_display_strings[self.states.CHECKING_FILES] = \ + self.xfer_display_strings[self.states.TRANSFERRING] = \ + lambda x, d=None: _('%s: %s of %s -- %s remain') % (x.state, x.completed, x.size, util.nicetimecount(x.ETA)) + + log.info("UpdateProgress setup") + + def on_file_check_start(self, updater): + try: + self.buddy.icon = self.icon + except gui.skin.SkinException: + pass + + log.debug("File check starting") + self.updater = updater + self.completed = 0 + self.size = len(updater.unchecked_files) + self.state = self.states.CHECKING_FILES + + def on_file_checked(self, file): + self._setcompleted(self.completed + 1) + + def on_file_check_complete(self, updater): + log.debug("File check complete") + self.updater = updater + self.state = self.states.WAITING_FOR_YOU + + def on_file_download_complete(self, file): + log.debug("File download completed: %r", file.path) + self._setcompleted(self.completed + 1) + + def on_update_download_start(self, files_to_download): + log.debug("Update download start: %r", files_to_download) + self.size = len(self.updater.update_files) + self.updater = None + self.state = self.states.TRANSFERRING + self.bytecounts = [] + self._bytes_per_sec = 0 + self.completed = 0 + self._starttime = None + self._done = False + + def on_update_download_complete(self, downloaded_files): + log.debug("All files downloaded") + self.state = self.states.FINISHED + + def on_update_failed(self): + log.debug("Update failed") + self.autoremove = False + self.state = self.states.FAILED + + util.Timer(10, lambda: (setattr(self, 'autoremove', True), setattr(self, 'state', self.states.FINISHED))).start() + + def cancel(self): + hooks.notify("digsby.updater.cancel") + + def allow_cancel(self): + return self.state not in (self.states.FailStates | self.states.CompleteStates) + + def allow_remove(self): + return self.state in (self.states.FailStates | self.states.CompleteStates) + + def allow_open(self): + return False + + def allow_open_folder(self): + return False + + def allow_save(self): + return False + def allow_save_as(self): + return False + def allow_reject(self): + return False + + def on_cancel(self): + self.state = self.states.CANCELLED_BY_YOU + + def get_bottom_links(self): + return [] + + def get_right_links(self): + return [ + ('remove', _('Remove'), lambda *a: None, self.allow_remove), + ('slower', _('Slower'), self.slower, self.allow_slower), + ('faster', _('Faster!'), self.faster, self.allow_faster), + ('cancel', _('Cancel'), self.cancel, self.allow_cancel), + ] + + def faster(self): + hooks.notify("digsby.updater.fast_mode.enable") + + def slower(self): + hooks.notify("digsby.updater.fast_mode.disable") + + def allow_slower(self): + return (self.allow_cancel() and + self.updater is not None and + self.updater.fast_mode) + + def allow_faster(self): + return (self.allow_cancel() and + self.updater is not None and + not self.updater.fast_mode) + +def up(): + if not common.pref("digsby.updater.use_transfer_window", type = bool, default = False): + return + + p = common.profile() + up = UpdateProgress(p) + + return up + +def _insert_up(up): + if up.updater is not None or up.state in up.states.TransferringStates: + p = common.profile() + if up not in p.xfers: + log.info("putting UpdateProgress in profile.xfers") + p.xfers.insert(0, up) + +def _set_update_hook_handler(name): + def handler(*a, **k): + u = up() + if u is None: + return + getattr(u, name)(*a, **k) + if u.state not in (UpdateStates.CANCELLED_BY_YOU, UpdateStates.FAILED): + _insert_up(u) + + setattr(sys.modules[__name__], name, handler) + +for name in ('on_file_check_start', + 'on_file_checked', + 'on_file_check_complete', + 'on_file_download_complete', + 'on_update_download_start', + 'on_update_download_complete', + 'on_cancel_update', + 'on_update_failed', + ): + + _set_update_hook_handler(name) diff --git a/digsby/src/plugins/digsby_updater/__init__.py b/digsby/src/plugins/digsby_updater/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsby_updater/downloader.py b/digsby/src/plugins/digsby_updater/downloader.py new file mode 100644 index 0000000..a19cf64 --- /dev/null +++ b/digsby/src/plugins/digsby_updater/downloader.py @@ -0,0 +1,200 @@ +''' +Initialize a Downloader with an UpdateManager instance: +>>> my_updater + +>>> d = Downloader(my_updater) + +The download process can be kicked off by calling the `start` method: +>>> d.start(success = lambda list_of_downloaded_files: None, + error = lambda list_of_error_files, list_of_success_files: None) + +Instances of this class also trigger certain hooks: + - digsby.updater.update_download_start(list_of_file_descriptions_to_download) + When the first file is about to be processed. + - digsby.updater.file_download_start(file_description) + When any file is about to be processed. + - digsby.updater.file_download_complete(file_description) + When a file is moved to the 'done' state. + - digsby.updater.file_download_error(file_description) + When a file is moved to the 'error' state. + - digsby.updater.update_download_complete(list_of_done_file_descriptions) + When all files are in the 'done' state. + - digsby.updater.update_download_error(list_of_failed_file_descriptions, list_of_done_file_descriptions) + When all files are in the 'done' or 'failed' state. + +Manages a queue of files (file description objects) that need to be downloaded. The source +should be a web URL and the destination should be a directory - usually a temporary one. + +For each file, the destination directory is scanned for an existing and equivalent copy +(equivalence is determined the file description we have). If found, the file is considered +'done'. If an equivalent copy is not found, downloading begins. + +Downloading is performed with asynchttp, which by default may attempt the a single request up +to 3 times. If the first request fails, the backup host is used. + +If there is an error downloading, the file is considered 'failed'. + +If downloading succeeds, the file content is checked against the description we have for it. +If the description matches, the file is considered 'done'. If it does not, the backup host will +be attempted. If the file was retrieved from the backup host, the file is considered 'failed'. + +At this point the next file is attempted. Only one file is downloaded at a time (the process is +not parallelized). + +Once all files are in either 'done' or 'failed' state, callbacks are called (success if all files +are done, error otherwise). +''' +import path +import hooks +import time +import common +import util +import util.net as net +import util.callbacks as callbacks +import util.httptools as httptools +import common.asynchttp as asynchttp + +import logging +log = logging.getLogger('d_updater.download') + +def append_ts(url): + ''' + Append a query string to the end of a URL that contains the timestamp. The + purpose for this is to force proxies to not return cached data. Amazon S3 (and perhaps + other web servers?) don't do anything with the query string so the original file is the one + requested. + >>> append_ts('http://www.google.com/thing.txt') + 'http://www.google.com/thing.txt?12345678' + ''' + if isinstance(url, basestring) and '?' not in url and not url.startswith('file:'): + url = url + '?' + str(int(time.time())) + + return url + +def httpopen(url, *a, **k): + url = append_ts(url) + return asynchttp.httpopen(url, *a, **k) + +class Downloader(object): + remote_root_base = path.path('http://update.digsby.com/') + backup_root_base = path.path('http://updatebackup.digsby.com/') + + def __init__(self, updater): + self.updater = updater + self.unchecked_files = list(updater.update_files) + self.errored_files = [] + self.downloaded_files = [] + self.num_files = len(self.unchecked_files) + relpath = self.remote_root_base.relpathto(path.path(updater.manifest_path).parent) + self.local_root = path.path(updater.temp_dir) + self.remote_root = net.httpjoin(self.remote_root_base, relpath) + "/" + self.backup_root = net.httpjoin(self.backup_root_base, relpath) + "/" + self.callback = None + self.cancelling = self.updater.cancelling + + @callbacks.callsback + def start(self, callback = None): + self.callback = callback + hooks.notify("digsby.updater.update_download_start", self.unchecked_files) + self.next_file() + + def cancel(self): + self.cancelling = True + + def next_file(self): + if self.cancelling: + return + + if self.unchecked_files: + file = self.unchecked_files.pop() + hooks.notify("digsby.updater.file_download_start", file) + if not file.match_local(self.local_path_for_file(file)): + self._download_file(file) + else: + log.debug("%r was already downloaded", file.path) + self.downloaded_files.append(file) + hooks.notify("digsby.updater.file_download_complete", file) + self.queue_next_file() + else: + if len(self.errored_files) + len(self.downloaded_files) == self.num_files: + self.finished() + + def queue_next_file(self): + interval = common.pref('digsby.updater.file_download_interval', type = int, default = 0.1) + if interval == 0: + interval = 0.1 + + t = util.Timer(interval, self.next_file) + t._verbose = False + t.start() + + def _download_file(self, file): + httpopen(self.remote_path_for_file(file), + success = lambda req, resp: self.got_file(file, resp), + error = lambda req, e: self.try_backup(file, e)) + + def got_file(self, file, resp, backup_exception = None): + dest_path = self.local_path_for_file(file) + if not dest_path.parent.isdir(): + dest_path.parent.makedirs() + with open(dest_path, 'wb') as f: + data = resp.read(32768) + while data: + f.write(data) + data = resp.read(32768) + + resp.close() + + if file.match_local(dest_path): + log.debug("successful download: %r", file.path) + self.downloaded_files.append(file) + hooks.notify("digsby.updater.file_download_complete", file) + else: + try: + dest_path.remove() + except Exception, e: + log.debug("Error deleting bad file: %r", e) + + if backup_exception is None: + self.try_backup(file, Exception("Bad data from primary server")) + return # Don't queue next file yet. + else: + log.error("Error downloading %r. Bad data from backup server; primary server error was: %r", + file.path, backup_exception) + self.errored_files.append(file) + hooks.notify("digsby.updater.file_download_error", file) + + self.queue_next_file() + + def try_backup(self, file, e): + log.error("Error downloading %r from primary server. Error was %r", file.path, e) + httpopen(self.remote_path_for_file(file, backup = True), + success = lambda req, resp: self.got_file(file, resp, e), + error = lambda req, e2: self.file_error(file, e, e2)) + + def file_error(self, file, e1, e2): + log.error("Errors occurred fetching %r from primary and backup servers. Errors were (%r, %r)", + file.path, e1, e2) + self.errored_files.append(file) + self.queue_next_file() + + def local_path_for_file(self, file): + return self.local_root / file.path + + def remote_path_for_file(self, file, backup = False): + if backup: + root = self.backup_root + else: + root = self.remote_root + return net.httpjoin(root, file.path.replace("\\", "/").encode('url')) + + def finished(self): + cb, self.callback = self.callback, None + if self.errored_files: + hooks.notify("digsby.updater.update_download_error", self.errored_files, self.downloaded_files) + if cb is not None: + cb.error(self.errored_files, self.downloaded_files) + else: + hooks.notify("digsby.updater.update_download_complete", self.downloaded_files) + if cb is not None: + cb.success(self.downloaded_files) diff --git a/digsby/src/plugins/digsby_updater/file_integrity.py b/digsby/src/plugins/digsby_updater/file_integrity.py new file mode 100644 index 0000000..0151ed7 --- /dev/null +++ b/digsby/src/plugins/digsby_updater/file_integrity.py @@ -0,0 +1,443 @@ +''' +Tools for determining the integrity of a file and the app. + +The application is considered intact when all files that are expected are found, and no 'bad files' are detected. + +Application integrity check in dev mode is achieved with svn status. Modified or deleted files are considered not +intact, and new files are considered 'bad'. svn:externals are ignored for simplicity. + +Application integrity check in release mode (or dev mode with --force-update) is achieved by scanning the directory +and checking the integrity of each file. + +If there are any files that are not intact or if there are any bad files (see below), the application is considered not +intact. This will trigger an update. + +File integrity is determined by comparing file metadata from a manifest file to a file on disk. +A missing file is not intact. +A file that is not a 'regular' file (such as a symlink, a directory matching the expected file name, etc) is not intact. +A file with a different size from the metadata is not intact. +A file with a different hash (MD5) is not intact. + +Bad files are files that are not in the manifest and and whose file extension is in the 'bad extensions' list, which is +currently: ('.py', '.pyo', '.pyc', '.dll', '.pyd', '.pth', '.so', '.dylib', '.exe', '.manifest', '.yaml') +There are also platform specific 'bad files' which are determined from platform_cleanup function in the the appropriate +{win|mac}helpers.py module. + +Integrity checking can be perfomed as follows: +>>> pic = ProgramIntegrityChecker( +... path_to_application_root, +... path_to_temp_dir, +... manifest_path, # required only if manifest_data is None +... manifest_data, # required only if manifest_path is None +... whitelist, # optional, sequence of more allowed files +... ) +>>> pic.start(success = lambda program_integrity_checker: None) +Also supported is a synchronous check, which is used when collecting data for bug reports: +>>> pic.synchronous_check() # pic.get_update_paths() + pic.get_delete_paths() will contain all files when this returns + +Note that only success is called, when all files have been checked. The integrity checker itself is returned. It should +be inspected to determine the results of the scan. Use the get_update_paths and get_delete_paths methods. Another useful +property is expected_download_size. + +The following hooks are also triggered: + - digsby.updater.file_check_start(checker) + When the first file is about to be processed + - digsby.updater.file_checked(file_description) + When any file is about to be processed + - digsby.updater.file_check_complete(checker) + When all files have been checked + +ProgramIntegrityChecker also supports a 'fastmode' property. By default, the file checking happens in order, with a +delay between each file so as to avoid using as much of the system resources as possible. Setting fastmode to True will +disable this delay. It can be disabled by setting the property back to False. + +NOTE: synchronous_check functions by setting fastmode to True before starting the check. If another thread sets fastmode +to False while the synchronous check is executing, then the synchronous check will return prematurely! Also, since +synchronous_check is performed without a callback, you will not receive notification when the check *actually* completes +(unless your code has registered for the digsby.updater.file_check_complete hook). + +### Manifest (metadata) generationg +Generating a manifiest is easy: +>>> manifest_data = generate_manifest(directory, hashes=('md5',), ignore=()) + +hashes is a tuple that should contain keys that exist in the HashLib dictionary. +ignore can be a list of prefixes that should be ignored - this was once used to avoid including MessageStyles in the +manifest. + +The data returned is a string with XML content. This is traditionally saved to a file named 'manifest'. +''' +import sys +import os +import stat +import hashlib +import traceback +import logging + +log = logging.getLogger("d_updater.integrity") + +import hooks +import path +import config +import util +import util.callbacks as callbacks + +import lxml.etree as etree +import lxml.objectify as objectify +import lxml.builder as B + +if config.platform == 'win': + import winhelpers as helpers +elif config.platform == 'mac': + import machelpers as helpers + +needs_contents = dict (md5=True, + sha1=True, + adler32=True, + crc32=True, + mtime=False, + fsize=False) + +def GetUserTempDir(): + import stdpaths + + if getattr(sys, 'is_portable', False): + base_temp = stdpaths.temp / 'digsby' + else: + base_temp = stdpaths.userlocaldata + + pth = path.path(base_temp) / 'temp' + if not pth.isdir(): + os.makedirs(pth) + return pth + +class MTime(object): + def __init__(self, filename=None): + + self.__val = 0 + + self.filename = filename + + if self.filename is not None: + self.fromfilename(self.filename) + + def update(self, bytes): + pass + + def digest(self, final=None): + import struct + return struct.pack('!I', self.__val) + + def hexdigest(self, final=None): + return util.to_hex(self.digest(final),'').lstrip('0') + + def fromfilename(self, fname): + self.filename = fname + self.__val = int(path.path(self.filename).mtime) + +class FSize(object): + def __init__(self, filename=None): + self.__val = -1 + self.filename = filename + + if self.filename is not None: + self.fromfilename(self.filename) + + def update(self, bytes): + pass + + def digest(self, final=None): + import struct + return struct.pack('!Q', self.__val) + + def hexdigest(self, final=None): + return util.to_hex(self.digest(final), '').lstrip('0') + + def fromfilename(self, fname): + self.filename = fname + self.__val = int(path.path(self.filename).size) + + +HashLib = util.Storage(md5=hashlib.md5, + sha1=hashlib.sha1, + mtime=MTime, + fsize=FSize, + ) + +@util.memoize +def _hashfile(pth, algs, mtime): + hs = list(HashLib.get(alg)() for alg in algs) + + if any(needs_contents[x] for x in algs): + with pth.open('rb') as f: + bytes = f.read(4096) + while bytes: + for h in hs: + h.update(bytes) + bytes = f.read(4096) + + for h in hs: + if hasattr(h, 'fromfilename'): + h.fromfilename(pth) + + return [h.hexdigest() for h in hs] + +def hashfile(pth, algs): + return _hashfile(pth, algs, os.path.getmtime(pth)) + +class FileMeta(object): + def __init__(self, pth, size=-1, hashes=None): + + if hashes is None: + hashes = {} + + self.size = size + self.hashes = dict(hashes) + self.path = path.path(pth) + + @classmethod + def from_xml(cls, el): + pth = unicode(el.path) + size = int(getattr(el, 'size', None) or -1) + hashes = {} + for hash in el.hash.getchildren(): + hashes[hash.tag] = unicode(hash) + + return cls(pth, size, hashes) + + def to_tag(self): + return B.E.file( + B.E.path(unicode(self.path)), + *(([B.E.size(str(self.size))] if self.size >= 0 else []) + + [B.E.hash(B.E(hash, hval)) + for (hash, hval) in self.hashes.items()] + ) + ) + + def match_local(self, fname): + try: + p = path.path(fname) + try: + st = p.stat() + except Exception: + return False + + if not stat.S_ISREG(st.st_mode): + # Is it a regular file? + return False + + if self.size >= 0 and st.st_size != self.size: + # We have a size but the on-disk size doesn't match it + return False + + hashnames = tuple(self.hashes.keys()) + l_hashes = dict(zip(hashnames, hashfile(p, hashnames))) + for alg in self.hashes: + if l_hashes[alg] != self.hashes[alg]: + return False + else: + return True + + except Exception, e: + traceback.print_exc() + return False + + def __repr__(self): + return '<%s %s>' % (type(self).__name__, + ' '.join('%s=%r' % i for i in vars(self).items())) + +def generate_manifest(root, hashes=('md5',), ignore=()): + root = path.path(root) + hashes = tuple(hashes) + + file_tags = [] + for file in root.walkfiles(): + if any(file.startswith(d) for d in ignore): + continue + file = path.path(file) + short = root.relpathto(file) + f = FileMeta(short, file.size, (zip(hashes, hashfile(file, hashes)))) + file_tags.append(f.to_tag()) + + manifest = B.E('manifest', *file_tags) + + return etree.tostring(manifest) + +def dev_integrity_check(dir): + import subprocess + process = subprocess.Popen(["svn", "status", "--ignore-externals", dir], + stdout = subprocess.PIPE, stderr = subprocess.PIPE) + stdout, stderr = process.communicate() + + updated_files = [] + deleted_files = [] + + for line in stdout.splitlines(): + if line == '': break + flags, fname = line[:7], line[8:] + if flags[0] != 'X': + if flags[0] in ("D!"): + deleted_files.append(fname) + else: + updated_files.append(fname) + + return deleted_files, updated_files + +class ProgramIntegrityChecker(object): + ''' + Uses manifest data to determine what files need to be updated and deleted. + ''' + + whitelist = set(['uninstall.exe']) + '''Files that are not in the manifest but are considered allowed anyway''' + + def __init__(self, local_dir = None, temp_dir = None, manifest_path = None, manifest_data = None, + whitelist = None): + + self.cancelling = False + self.fast_mode = False + self.quiet_mode = False + self.unchecked_files = None + self.delete_files = [] + self.update_files = [] + self.good_files = [] + + if local_dir is None: + local_dir = util.program_dir() + if manifest_path is None: + manifest_path = local_dir / 'manifest' + if manifest_data is None and manifest_path.isfile(): + manifest_data = manifest_path.bytes() + + self.manifest_path = manifest_path + self.temp_dir = temp_dir or path.path(GetUserTempDir()) + self.local_dir = local_dir + + self.got_manifest_data(manifest_data) + + if whitelist is not None: + self.whitelist = set(whitelist) + + known_files = set((self.local_dir/file.path).abspath() for file in self.unchecked_files) + known_files.update((self.local_dir / file).abspath() for file in self.whitelist) + self.delete_files = Cleanup(self.local_dir, known_files) + self.callback = None + + @callbacks.callsback + def start(self, callback = None): + self.callback = callback + hooks.notify('digsby.updater.file_check_start', self) + self.next_file() + + def synchronous_check(self): + self.fast_mode = True + self.quiet_mode = True + self.start() + + def cancel(self): + self.cancelling = True + + def got_manifest_data(self, mdata): + if mdata is None: + self.unchecked_files = [] + return + + self.unchecked_files = map(FileMeta.from_xml, objectify.fromstring(mdata).file) + + self.write_manifest_data(mdata) + + def write_manifest_data(self, data): + with open(self.temp_dir/'manifest', 'wb') as f: + f.write(data) + + def _check_file(self): + file = self.unchecked_files.pop() + if file.match_local(self.local_dir / file.path): + if not self.quiet_mode: + log.debug("is latest: %r", file.path) + self.good_files.append(file) + elif file.path in self.whitelist: + if not self.quiet_mode: + log.debug("is whitelisted: %r", file.path) + self.good_files.append(file) + else: + log.debug("is changed: %r", file.path) + self.update_files.append(file) + + hooks.notify("digsby.updater.file_checked", file) + + def next_file(self): + if self.cancelling: + return + + if sys.DEV and not sys.opts.force_update: + self.unchecked_files = [] + self.delete_files, self.update_files = dev_integrity_check(self.local_dir) + self.files_processed() + return + + if self.unchecked_files: + if self.fast_mode: + while self.fast_mode and self.unchecked_files and not self.cancelling: + self._check_file() + else: + self._check_file() + + import common + interval = common.pref('digsby.updater.file_integrity_interval', type = int, default = 0.01) + if interval == 0: + interval = 0.01 + + t = util.Timer(interval, self.next_file) + t._verbose = False + t.start() + else: + self.files_processed() + + def files_processed(self): + hooks.notify('digsby.updater.file_check_complete', self) + cb, self.callback = self.callback, None + if cb is not None: + cb.success(self) + + @property + def expected_download_size(self): + return sum(f.size for f in self.update_files if f.size > 0) + + def get_update_paths(self): + return [getattr(x, 'path', x) for x in self.update_files] + + def get_delete_paths(self): + return list(self.delete_files) + +def Cleanup(local_root, known_files): + if not getattr(sys, 'frozen', False): + return [] + + to_delete = set() + + local_root = path.path(local_root) + + # now find out which files shouldn't be there + + badexts = ('.py', '.pyo', '.pyc', '.dll', '.pyd', '.pth', '.so', '.dylib', '.exe', '.manifest', '.yaml') + + for file in local_root.walk(): + file = file.abspath() + + if file.isdir(): + continue + + if (file not in known_files) and any(file.endswith(ext) for ext in badexts): + if file.endswith('.yaml') and \ + (file.parent == local_root or # update.yaml, tag.yaml, branch.yaml, etc. + 'res' in local_root.relpathto(file).splitall()): # skin.yaml, sounds.yaml, etc + + continue + + to_delete.add(file) + + more_bad_files = helpers.platform_cleanup() + to_delete.update(more_bad_files) + + return sorted(to_delete, reverse=True) + diff --git a/digsby/src/plugins/digsby_updater/info.yaml b/digsby/src/plugins/digsby_updater/info.yaml new file mode 100644 index 0000000..30d4a3e --- /dev/null +++ b/digsby/src/plugins/digsby_updater/info.yaml @@ -0,0 +1,60 @@ +name: 'DigsbyUpdater' +path: 'digsby_updater' +type: 'pure' + +entry_points: + digsby.profile.addons: + digsby_updater: digsby_updater.updater:UpdateManager + digsby_updater_progress: digsby_updater.UpdateProgress:UpdateProgress + digsby.app.gui.pre: + digsby_updater: digsby_updater.updater:load_tag_and_install_update + digsby.updater.status: + digsby_updater: digsby_updater.updater:update_status + digsby.updater.check: + digsby_updater: digsby_updater.updater:update_check + digsby.updater.pushed: + digsby_updater: digsby_updater.updater:update_check + digsby.server.announcement: + digsby_updater: digsby_updater.updater:update_check + digsby.app.exit: + digsby_updater: digsby_updater.updater:update_cancel + digsby.updater.cancel: + digsby_updater: digsby_updater.updater:update_cancel + digsby.updater.cancelled: + digsby_updater_progress: digsby_updater.UpdateProgress:on_cancel_update + digsby.updater.file_check_start: + digsby_updater_progress: digsby_updater.UpdateProgress:on_file_check_start + digsby.updater.file_checked: + digsby_updater_progress: digsby_updater.UpdateProgress:on_file_checked + digsby.updater.file_check_complete: + digsby_updater_progress: digsby_updater.UpdateProgress:on_file_check_complete + digsby.updater.file_download_complete: + digsby_updater_progress: digsby_updater.UpdateProgress:on_file_download_complete + digsby.updater.file_download_error: + digsby_updater_progress: digsby_updater.UpdateProgress:on_file_download_complete + digsby.updater.update_download_start: + digsby_updater_progress: digsby_updater.UpdateProgress:on_update_download_start + digsby.updater.update_download_complete: + digsby_updater_progress: digsby_updater.UpdateProgress:on_update_download_complete + digsby.updater.update_failed: + digsby_updater_progress: digsby_updater.UpdateProgress:on_update_failed + digsby.updater.update_complete: + digsby_updater: digsby_updater.updater:on_update_complete + digsby.help.actions: + digsby_updater: digsby_updater.updater:help_menu_items + digsby.accounts.released.async: + digsby_updater: digsby_updater.updater:update_check_later + digsby_release_notes: digsby_updater.updater:was_updated + digsby.updater.fast_mode.enable: + digsby_updater: digsby_updater.updater:start_fastmode + digsby.updater.fast_mode.disable: + digsby_updater: digsby_updater.updater:stop_fastmode + digsby.jabber.session_started: + digsby_updater: digsby_updater.xmpp:session_started + digsby.jabber.initialized: + digsby_updater: digsby_updater.xmpp:initialized + +skin: + pluginicons: + updater: "res/digsby_updater_outline.png" + diff --git a/digsby/src/plugins/digsby_updater/machelpers.py b/digsby/src/plugins/digsby_updater/machelpers.py new file mode 100644 index 0000000..03e9131 --- /dev/null +++ b/digsby/src/plugins/digsby_updater/machelpers.py @@ -0,0 +1,35 @@ +# XXX: 2012-02-21: I don't believe this has been tested in a long time. It may need to be completely re-tooled. -md +import os +import sys + +def update_and_restart(tempdir): + import wx + + updater = os.path.join(os.getcwd(), "mac_updater.pyc") + + from Authorization import Authorization, kAuthorizationFlagDestroyRights + auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,)) + try: + python = sys.executable + pipe = auth.executeWithPrivileges(python, updater, tempdir) + + output = pipe.read() + + if output.find("error") != -1: + wx.MessageBox(_("Error while updating Digsby. Please restart and try again, or grab the latest version from digsby.com. Digsby will now shut down.")) + pipe.close() + wx.GetApp().ExitMainLoop() + return + + pipe.close() + + wx.MessageBox(_("Updated successfully. Digsby now needs to restart.")) + + os.spawnv(os.P_NOWAIT, python, ["python", updater, "restart"]) + wx.GetApp().ExitMainLoop() + + except: + wx.MessageBox(_("Unable to authenticate. Please restart and try again.")) + +def platform_cleanup(): + return [] diff --git a/digsby/src/plugins/digsby_updater/res/digsby_updater.png b/digsby/src/plugins/digsby_updater/res/digsby_updater.png new file mode 100644 index 0000000..d083a51 Binary files /dev/null and b/digsby/src/plugins/digsby_updater/res/digsby_updater.png differ diff --git a/digsby/src/plugins/digsby_updater/res/digsby_updater_outline.png b/digsby/src/plugins/digsby_updater/res/digsby_updater_outline.png new file mode 100644 index 0000000..a504277 Binary files /dev/null and b/digsby/src/plugins/digsby_updater/res/digsby_updater_outline.png differ diff --git a/digsby/src/plugins/digsby_updater/updater.py b/digsby/src/plugins/digsby_updater/updater.py new file mode 100644 index 0000000..be59800 --- /dev/null +++ b/digsby/src/plugins/digsby_updater/updater.py @@ -0,0 +1,667 @@ +import logging +log = logging.getLogger("d_updater") +#log.setLevel(logging.INFO) + +import sys +import traceback +import io +import time + +import wx +import gui +import hashlib +import path +import syck +import config +import stdpaths +import hooks +import peak.util.addons as addons +import lxml.objectify as objectify + +import util +import util.net as net +import util.callbacks as callbacks +import common +import common.asynchttp as asynchttp +import file_integrity, downloader + +if config.platform == 'win': + import winhelpers as helpers +elif config.platform == 'mac': + import machelpers as helpers + +def rm_emptydirs(dir): + for d in dir.dirs(): + rm_emptydirs(d) + + if not dir.files() or dir.dirs(): + try: + dir.rmdir() + except Exception: + pass + +class UpdateChecker(object): + ''' + Determines if there is an update. + + Usage: + uc = UpdateChecker('alpha') + uc.update(success = lambda needs_update, remote_manifest_path, manifest_data: log.info("update check complete"), + error = lambda exc: log.info("oh no, update check failed") + ''' + def __init__(self, release_type = None): + self.remote_manifest_path = None + self.release_type = release_type or self.get_release_type() + self.callback = None + + @callbacks.callsback + def update(self, callback = None): + self.callback = callback + self.get_manifest_path() + + def manifest_path_error(self, e): + log.info("Error getting manifest path: %r", e) + self._error(e) + + def manifest_request_error(self, e): + log.error("Error retrieving manifest file: %r", e) + self._error(e) + + def manifest_check_error(self, e): + log.error("Error checking manifest integrity: %r", e) + self._error(e) + + def update_check_complete(self, needs_update, manifest_data): + log.info("Update check complete. Need update? %r", needs_update) + self._success(needs_update, self.remote_manifest_path, manifest_data) + + def _error(self, e): + cb, self.callback = self.callback, None + if cb is not None: + cb.error(e) + + def _success(self, *a): + cb, self.callback = self.callback, None + if cb is not None: + cb.success(*a) + + def check_manifest_integrity(self, req, resp): + headers = resp.headers + + rmtime = headers.get('x-amz-meta-mtime', None) + + if rmtime is None: + rmtime = headers.get('last-modified', None) + if rmtime is not None: + rmtime = net.http_date_to_timestamp(rmtime) + + if rmtime is not None: + try: + rmtime = int(rmtime) + except (TypeError, ValueError): + rmtime = None + + prog_dir = util.program_dir() + self.local_manifest_path = path.path(prog_dir) / path.path(self.remote_manifest_path).splitpath()[-1] + + needs_update = True + + if self.local_manifest_path.isfile(): + lmtime = int(self.local_manifest_path.mtime) + else: + lmtime = 0 + + if sys.opts.force_update or lmtime != rmtime: + log.info("MTime mismatch or sys.opts.force_update is True. Downloading manifest. (local=%r, remote=%r)", lmtime, rmtime) + downloader.httpopen(self.remote_manifest_path, success = self.got_manifest_response, error = self.manifest_request_error) + else: + log.info("Local MTime matches remote. Not downloading manifest. (local=%r, remote=%r)", lmtime, rmtime) + self.update_check_complete(False, None) + + def got_manifest_response(self, req, resp): + if self.local_manifest_path.isfile(): + local_manifest_digest = self.local_manifest_path.read_md5() + else: + local_manifest_digest = None + + log.info("Got manifest response. Comparing hashes.") + + manifest_data = resp.read() + remote_manifest_digest = hashlib.md5(manifest_data).digest() + + needs_update = local_manifest_digest != remote_manifest_digest + + if sys.opts.force_update: + needs_update = True + + if not needs_update: + manifest_data = None + + self.update_check_complete(needs_update, manifest_data) + + def get_release_type(self): + ''' + Returns a cached tag name (from sys.TAG) if present, otherwise loads it from tag.yaml in the program directory. + ''' + return get_client_tag() + + def get_manifest_path(self): + ''' + Figure out where the manfiest is supposed to be. Since this may make an HTTP request, control flow + continues asynchronously into got_updateyaml(file_obj). + ''' + program_dir = util.program_dir() + + local_info_file = program_dir / 'update.yaml' + if local_info_file.isfile(): + try: + local_info = open(local_info_file, 'rb') + except Exception: + pass + else: + self.got_updateyaml(fobj = local_info) + return + + log.info("Manifest path not found in %r. checking web for update.yaml", local_info_file) + asynchttp.httpopen("http://s3.amazonaws.com/update.digsby.com/update.yaml?%s" % int(time.time()), success = self.got_updateyaml, error = self.manifest_path_error) + + def got_updateyaml(self, req = None, fobj = None): + ''' + an open fileobject that contains yaml with manifest locations in it. + ''' + try: + data = fobj.read() + except Exception as e: + return self.manifest_path_error(e) + + try: + ui = syck.load(data) + except Exception as e: + return self.manifest_path_error(e) + + all = ui.get('all', {}) + mine = ui.get(config.platform, None) or {} + + merged = all.copy() + merged.update(mine) + + manifest_path = merged.get(self.release_type, merged.get('release', None)) + if manifest_path is None: + self.update_check_error(Exception("No manifest URL for %r in %r" % (self.release_type, all))) + else: + log.info("Got manifest path: %r", manifest_path) + self.remote_manifest_path = manifest_path + downloader.httpopen(self.remote_manifest_path, method = 'HEAD', success = self.check_manifest_integrity, error = self.manifest_check_error) + +def help_menu_items(*a): + ''' + Returns zero or one items representing menu options to be placed in the help menu. + The text is to include the app tag if we're not in 'release'. + ''' + if not common.pref("digsby.updater.help_menu", type = bool, default = True): + return [] + + if sys.TAG and sys.TAG != "release": + txt = _("Check for %s updates") % sys.TAG + else: + txt = _("Check for Updates") + + return [(txt, update_check)] + +def update_check(*a): + p = common.profile() + if p is not None: + UpdateManager(p).update() + +def update_check_later(*a): + ''' + The app performs an initial update check 5 minutes after launching. + (After that, it's every 6 hours - see UpdateManager.setup) + ''' + util.Timer(common.pref('digsby.updater.initial_delay', + type = int, default = 5*60), + update_check).start() + +def on_update_complete(*a): + ''' + Save the current application log to a 'digsby_update_download.log.csv' file in case there's an error while updating. + Otherwise these problems are very hard to debug ;) + ''' + # make sure all log statements are written to disk + for handler in logging.root.handlers: + handler.flush() + + # limit the logfile size sent in the diagnostic report + src_file = path.path(sys.LOGFILE_NAME) + dst_file = src_file.parent / 'digsby_update_download.log.csv' + try: + data = src_file.bytes() + except Exception, e: + f = io.BytesIO() + traceback.print_exc(file = f) + data = f.getvalue() + del f + + with dst_file.open('wb') as f: + f.write(data) + +def update_cancel(*a): + p = common.profile() + if p is not None: + UpdateManager(p).cancel() + +def start_fastmode(*a): + p = common.profile() + if p is not None: + UpdateManager(p).set_fastmode(True) + +def stop_fastmode(*a): + p = common.profile() + if p is not None: + UpdateManager(p).set_fastmode(False) + +def load_tag_and_install_update(*a): + get_client_tag() + return install_update() + +def was_updated(): + ''' + Show the release notes (if any) after an update. + (the updater starts the app with --updated after it completes) + ''' + if not sys.opts.updated: + return + + p = common.profile() + # TODO: create some sort of dummy buddy object, this is silly + buddy = util.Storage(name = "digsby.org", + service = "digsby", + protocol = p.connection, + increase_log_size = lambda *a, **k: None, + icon = None) + # make message object, + release_notes = _get_release_notes() + if not release_notes.strip(): + return + + msg = common.message.Message(buddy = buddy, + message = release_notes, + content_type = "text/html", + conversation = util.Storage(protocol = p.connection, + buddy = buddy, + ischat = False)) + p.on_message(msg) + +def _get_release_notes(): + try: + with open(util.program_dir() / 'res' / "release_notes.html", 'rb') as f: + data = f.read() + + return data + except Exception, e: + log.error("Release notes not found. %r", e) + return '' + +def install_update(*a): + ''' + Install an update. To do this we need: + - the downloaded manifest data + - a list of all the files that need to be written (updateme.txt) + - a list of all the files that need to be deleted (deleteme.txt) + - updates must be enabled (--no-update, sys.DEV, or --update-failed can all disable updates) + + If all of this is true, call the appropriate restart_and_update method for our platform. + Otherwise, delete the aforementioned files so that we don't accidentally start an update again later. This is + especially important in the case of --update-failed; the app would go into a loop. + ''' + temp_dir = file_integrity.GetUserTempDir() + manifest = (temp_dir / 'manifest') + updateme = (temp_dir / 'updateme.txt') + deleteme = (temp_dir / 'deleteme.txt') + + if UpdateManager(Null()).should_check_for_updates() and not sys.opts.update_failed: + if updateme.isfile() and deleteme.isfile() and manifest.isfile(): + print >>sys.stderr, 'Installing update' + log.info("Installing update") + helpers.restart_and_update(temp_dir) + return False # To veto the GUI + else: + _delete_updater_files() + + print >>sys.stderr, 'Not installing update' + log.info("Not installing update") + +def _delete_updater_files(): + ''' + Clear out the files that indicate an update is available. After this is done, we can attempt updates again (so + sys.opts.update_failed is set to False). + ''' + temp_dir = file_integrity.GetUserTempDir() + manifest = (temp_dir / 'manifest') + updateme = (temp_dir / 'updateme.txt') + deleteme = (temp_dir / 'deleteme.txt') + if updateme.isfile(): + updateme.remove() + if deleteme.isfile(): + deleteme.remove() + if manifest.isfile(): + manifest.remove() + + sys.opts.update_failed = False + +def update_status(): + p = common.profile() + if p is None: + return False + + um = UpdateManager(p) + return um.status + +def get_client_tag(): + if getattr(sys, 'DEV', False): + tag = '' + else: + tag = 'release' + + tag_fname = 'tag.yaml' + for fpath in ((stdpaths.userlocaldata / tag_fname), (util.program_dir() / tag_fname)): + try: + # If the location or name of this file changes, also update the installer (DigsbyInstaller/DigsbyInstall.nsi) + # since it deletes it. + with open(fpath, 'rb') as f: + yaml = syck.load(f) + tag = yaml['tag'] + + except Exception, e: + log.debug('Didn\'t get a release tag from %r: %r', fpath, e) + else: + log.info("Got release tag %r from %r", tag, fpath) + break + else: + log.info('Using default release tag: %r', tag) + + sys.TAG = tag + return tag + + +class UpdateManager(addons.AddOn): + ''' + Controller for the update process. + ''' + updating = False + + @property + def status(self): + if self.updating: + if self.downloader: + return 'downloading' + if self.updater: + return 'filechecking' + return 'checking' + return 'idle' + + def setup(self): + log.info("UpdateManager setup") + + self.updating = False + self.cancelling = False + self.updater = None + self.downloader = None + self.update_checker = UpdateChecker() + + self._timer = util.RepeatTimer(common.pref("digsby.updater.update_interval", + type = int, default = 6 * 60 * 60), + self.update) + self._timer.start() + + self.fast_mode = False + + def set_fastmode(self, v): + self.fast_mode = v + if self.updater is not None: + self.updater.fast_mode = v + + def cancel(self): + if not self.updating: + return + + self.updating = False + self.cancelling = True + + if self.updater is not None: + self.updater.cancel() + self.updater = None + if self.downloader is not None: + self.downloader.cancel() + self.downloader = None + + hooks.notify("digsby.updater.cancelled") + + def should_check_for_updates(self): + + retval = True + reason = None + if not sys.opts.allow_update: + retval = False + reason = 'sys.opts.allow_updates == False' + + if sys.DEV and not sys.opts.force_update: + retval = False + reason = "sys.DEV and not sys.opts.force_update" + + if self.updating: + retval = False + reason = "already checking for updates, or currently updating" + + if not retval: + log.info('Not checking for updates because: %r', reason) + + return retval + + def update(self): + if not self.should_check_for_updates(): + if not self.updating: + # If we're not updating for a reason other than that we're already updating, notify + # that the update check is complete. + hooks.notify('digsby.updater.update_check_results', False, None, None) + return + + self.delete_file_changes() + + self.cancelling = False + self.updating = True + hooks.notify("digsby.updater.update_start") + self.update_checker.release_type = get_client_tag() + self.update_checker.update(success = self.update_check_success, error = self.update_check_error) + + def update_check_success(self, update_required, manifest_path, manifest_data): + log.info("Got result for update check. update_required = %r, manifest_path = %r", update_required, manifest_path) + + hooks.notify('digsby.updater.update_check_results', update_required, manifest_path, manifest_data) + + if not update_required: + self.cancel() + return + + if self.cancelling: + return + log.info("Starting updater.") + self.updater = file_integrity.ProgramIntegrityChecker(manifest_path = manifest_path, + manifest_data = manifest_data) + + self.updater.start(success = self.file_check_complete) + + def update_check_error(self, *a): + self.cancel() + hooks.notify('digsby.updater.update_check_error', *a) + + def file_check_complete(self, updater): + assert updater is self.updater + + if not (updater.update_files or updater.delete_files): + self.cancel() + return + + self.downloader = downloader.Downloader(updater) + + auto_download = common.pref("digsby.updater.auto_download", type = bool, default = True) + res = [] + + def after_popup(): + if auto_download: + self.start_downloader() + elif (not res) or res[0] is None: # No popup was fired. + def dialog_cb(ok): + if ok: + self.start_downloader() + else: + self.stop_timer() + + diag = gui.toolbox.SimpleMessageDialog( + None, + title= _('Update Available'), + message= _("A Digsby update is available to download.\nWould you like to begin downloading it now?"), + icon = gui.skin.get('serviceicons.digsby').Resized(32), + ok_caption=_('Yes'), + cancel_caption=_('No'), + wrap=450 + ) + diag.OnTop = True + diag.ShowWithCallback(dialog_cb) + + def do_popup(): + res.append(gui.toast.popup(icon = gui.skin.get("serviceicons.digsby"), + header = _("Update Available"), + minor = _("A Digsby update is available to download.\nWould you like to begin downloading it now?"), + sticky = True, + buttons = [(_("Yes"), lambda *a: self.start_downloader()), + (_("No"), self.stop_timer)], + + size = util.nicebytecount(updater.expected_download_size), + onclose = self._download_popup_closed, +# popupid = 'digsby.update', + )) + + wx.CallAfter(after_popup) + + + if not auto_download: + wx.CallAfter(do_popup) + else: + wx.CallAfter(after_popup) + + + def write_file_changes(self, temp, to_update, to_delete): + if not temp.isdir(): + temp.makedirs() + + with open(temp/'deleteme.txt', 'wb') as out: + if to_delete: + write = lambda s: out.write(unicode(s).encode('filesys', 'replace') + '\n') + for f in sorted(to_delete, reverse=True): + rel_path = self.updater.local_dir.relpathto(f) + if rel_path.lower() in ('uninstall.exe', 'digsby.exe', + 'lib\\msvcr90.dll', 'lib\\msvcp90.dll', + 'lib\\digsby-app.exe','lib\\digsby.exe', + 'lib\\digsby updater.exe', 'lib\\digsby preupdater.exe'): + continue + + write(rel_path) + + with open(temp/'updateme.txt', 'wb') as out: + write = lambda s: out.write(unicode(s).encode('filesys', 'replace') + '\n') + + write('manifest') + for f in sorted(to_update, reverse = True): + if f.path.lower() != helpers.get_exe_name().lower(): + # this is updated in the 'digsby.clone' process that the updater.exe takes care of. + write(f.path) + + def delete_file_changes(self): + _delete_updater_files() + + def start_downloader(self): + if self.cancelling: + return + + self.downloader.start(success = self.download_success, error = self.download_error) + + def stop_timer(self, *a): + log.info("Stopping update checker timer.") + self._timer.stop() + self.cancel() + + def _download_popup_closed(self, *a, **k): + if k.get("userClose", False): + self.stop_timer() + + def download_error(self, error_files, success_files): + log.error("Update incomplete. %d successful files, %d errored files", len(success_files), len(error_files)) + for f in error_files: + log.error("\t%r", f.path) + + self.cancel() + hooks.notify('digsby.updater.update_failed') + + auto_download = common.pref("digsby.updater.auto_download", type = bool, default = True) + + if not auto_download: + popup = gui.toast.popup( + icon = gui.skin.get('serviceicons.digsby'), + header = _("Update Failed"), + minor = _("Digsby was unable to complete the download. This update will be attempted again later."), + sticky = True, + buttons = [(_("Manual Update"), lambda: wx.LaunchDefaultBrowser("http://install.digsby.com")), + (_("Close"), lambda: popup.cancel())], +# popupid = 'digsby.update', + ) + + def download_success(self, success_files): + log.info("Downloaded files. %d files downloaded", len(success_files)) + + updater = self.updater + if updater is None: + self.cancel() + return + self.write_file_changes(updater.temp_dir, updater.update_files, updater.delete_files) + + hooks.notify('digsby.updater.update_complete') + if not common.pref("digsby.updater.install_prompt", type = bool, default = False): + log.debug("Update install prompt disabled. Scheduling install") + self.schedule_install() + else: + res = [] + + def after_popup(): + if (not res) or res[0] is None: + log.debug("Popup was not shown. Scheduling install") + self.schedule_install() + + @wx.CallAfter + def do_popup(): + res.append(gui.toast.popup( + icon = gui.skin.get('serviceicons.digsby'), + header = _("Update Ready"), + minor = _("A new version of Digsby is ready to install. Restart Digsby to apply the update."), + sticky = True, + buttons = [(_("Restart Now"), self.do_install), + (_("Restart Later"), self.schedule_install)], + onclose = self._install_popup_closed, +# popupid = 'digsby.update', + )) + wx.CallAfter(after_popup) + + def do_install(self): + log.info("Doing install!") + if self.cancelling: + log.info("\tNevermind, we're cancelling. Install not happening now.") + return + install_update() + + def _install_popup_closed(self, *a, **k): + if k.get('userClose', False): + self.schedule_install() + + def schedule_install(self): + log.info("Schedule install somehow!") + self.stop_timer() diff --git a/digsby/src/plugins/digsby_updater/winhelpers.py b/digsby/src/plugins/digsby_updater/winhelpers.py new file mode 100644 index 0000000..49fa546 --- /dev/null +++ b/digsby/src/plugins/digsby_updater/winhelpers.py @@ -0,0 +1,164 @@ +import os +import sys +import shutil +import locale + +import wx +import util +import stdpaths +import path + +from ctypes import windll + +import logging +log = logging.getLogger('AutoUpdate') + +def platform_cleanup(): + return _remove_virtual_store() + +def get_exe_name(): + # XXX: this is horrible, can't we have this in a nice clean attribute somewhere? + return util.program_dir().relpathto(path.path(sys._real_exe.decode(locale.getpreferredencoding()))) + +def _remove_virtual_store(): + ''' + Since Vista, the operating system has provided a mechanism to allow apps to seamlessly write to "program files", + even if UAC is on. However, the actual on-disk location is not in %PROGRAMFILES%. It's something called the "virtual + store", and if a file exists in the virtual store, we can read it even if we're actually trying to read from program + files. This can cause issues when updating if modules have been removed but still exist in the virtual store. + + This function returns a list of all files in the app's virtual store for the purposes of having the updater process + delete them. + ''' + if not os.name == 'nt': + return [] + + import gui.native.win.winutil as winutil + if not winutil.is_vista(): + return [] + + # remove c:\Users\%USER%\AppData\Local\VirtualStore\Program Files\Digsby + # if it exists + + to_remove = [] + # TODO: remove app name here, get it from the app object or something + for virtual_store in ((stdpaths.userlocaldata / 'VirtualStore' / 'Program Files' / 'Digsby'), + (stdpaths.userlocaldata / 'VirtualStore' / 'Program Files (x86)' / 'Digsby')): + + with util.traceguard: + if virtual_store.isdir(): + to_remove.extend(virtual_store.walkfiles()) + + return to_remove + +def get_top_level_hwnd(): + ''' + When calling ShellExecute, we need a window handle to associate the new process with. Here, we attempt to find it + by getting the first TopLevelWindow (should be the buddy list). In the event that the buddy list is not shown, + we assume that the login window is still around somewhere, so we'll use that for the window handle. + ''' + splash = wx.FindWindowByName('Digsby Login Window') + + if splash is None: + topwins = wx.GetTopLevelWindows() + if isinstance(topwins, list) and topwins: + topwins = topwins[0] + + splash = topwins if topwins else None + + if splash is not None: + return splash.Handle + +def restart_and_update(tempdir): + ''' + Registers _launch_updater as an atexit function, attempts to get the app to quit, and in the event the interpreter + is still running in 7 seconds, calls _launch_updater anyway. + + Special care is also taken to make sure we're not still in the OnInit handler of the wxApp. + ''' + import atexit + atexit.register(_launch_updater, tempdir) + force_restart_timer = util.Timer(7, _launch_updater, tempdir) + app = wx.GetApp() + # Don't call DigsbyCleanupAndQuit if we're not yet out of OnInit - the startup sequence will do it for us. + if app.IsMainLoopRunning() and app.DigsbyCleanupAndQuit(): + force_restart_timer.start() + +def _launch_updater(tempdir): + ''' + Runs a small executable + + digsby_updater.exe supersecret SRC EXEC + + which will + - copy digsby_updater.exe to a temp directory, since the file itself may need to be updated + - attempt to kill any instances of digsby that are found + - copy the contents of directory SRC into the current directory + - remove the SRC directory + - start the program named by EXEC and exit + + The updater needs to do this since DLLs are locked during program execution + and can't be updated. + ''' + ShellExecute = windll.shell32.ShellExecuteW + + POST_UPDATE_EXE = 'Digsby Updater.exe' + UPDATE_EXE = 'Digsby PreUpdater.exe' + + tempdir = path.path(tempdir) + + # grab the path to the currently running executable + exc = get_exe_name() + + for EXE in (POST_UPDATE_EXE, UPDATE_EXE): # Order matters - the last one needs to be "Digsby Update.exe" so that it is run at the end + + if sys.DEV: + # if running the dev version, Python.exe is probably somewhere else + # than the Digsby directory. + updater = path.path('ext') / 'msw' / EXE + else: + # in the release build its at Digsby/lib/digsby_updater.exe + updater = path.path(sys.executable.decode(locale.getpreferredencoding())).parent / 'lib' / EXE + + if not updater.isfile(): + raise AssertionError('could not find %s' % updater) + + # copy the executable to the temp directory, in case it needs to update itself + updater.copy2(tempdir.parent) + updater = tempdir.parent / updater.name + + if not updater.isfile(): + raise AssertionError('could not copy %s to %s' % updater.name, tempdir.parent) + + log.info('updater path is %r', updater) + + # invoke it + hwnd = get_top_level_hwnd() + + log.info('top level window HWND is %s', hwnd) + + SW_SHOWNORMAL = 1 + SE_ERR_ACCESSDENIED = 5 + + params = u'supersecret "%s" "%s"' % (tempdir.parent, exc) + log.info('%s %s', updater, params) + + # we have to use ShellExecute instead of Popen, since we need to pass a HWND to get + # vista's UAC to come to the foreground + # see http://msdn2.microsoft.com/en-us/library/bb762153.aspx + res = ShellExecute(hwnd, None, updater, params, None, SW_SHOWNORMAL) + + if res > 32: + log.info('ShellExecute successful: %s', res) + return + + if res == SE_ERR_ACCESSDENIED: + log.info('access denied') + # vista UAC: user clicked "cancel"? + else: + log.info('ShellExecute error: %s', res) + + # If we got to this point, the updater EXEs failed to run and we need to + # clear the pending update so the program will start again next time. + import updater + updater._delete_updater_files() diff --git a/digsby/src/plugins/digsby_updater/xmpp.py b/digsby/src/plugins/digsby_updater/xmpp.py new file mode 100644 index 0000000..5125182 --- /dev/null +++ b/digsby/src/plugins/digsby_updater/xmpp.py @@ -0,0 +1,55 @@ +''' +A handler for the following xmpp stanza: + + + + + +When it's received over the digsby stream, it notifies the hook 'digsby.updater.pushed'. Under normal circumstances, +the updater module is listening for this hook and when received, it will begin the update check process. +''' +from peak.util.addons import AddOn +import hooks +import logging + +log = logging.getLogger('d_updater.xmpp') + +DIGSBY_UPDATER_NS = "digsby:updater" + +class DigsbyUpdateMessageHandler(AddOn): + def setup(self, stream): + self.stream = stream + log.debug('setting up "%s" message handler', DIGSBY_UPDATER_NS) + stream.set_message_handler('normal', self.handle_message, + namespace = DIGSBY_UPDATER_NS, + priority = 90) + + def handle_message(self, stanza): + log.debug('update pushed') + hooks.notify('digsby.updater.pushed') + return True + +def session_started(protocol, stream, *a, **k): + if getattr(protocol, 'name', None) != 'digsby': + # TODO: move this check of here, maybe by using impl='digsby' + # when notifying? + return + DigsbyUpdateMessageHandler(protocol).setup(stream) + +def initialized(protocol, *a, **k): + if getattr(protocol, 'name', None) != 'digsby': + # TODO: move this check of here, maybe by using impl='digsby' + # when notifying? + return + log.debug('registering "%s" feature', DIGSBY_UPDATER_NS) + protocol.register_feature(DIGSBY_UPDATER_NS) + +def send_update(protocol, name): + ''' + Used to push an update stanza to all logged in digsby users. + ''' + import pyxmpp + m = pyxmpp.all.Message(to_jid=pyxmpp.all.JID(name, 'digsby.org')) + update = m.xmlnode.newChild(None, 'update', None) + update_ns = update.newNs(DIGSBY_UPDATER_NS, None) + protocol.send(m) diff --git a/digsby/src/plugins/digsbyipcaction/__init__.py b/digsby/src/plugins/digsbyipcaction/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/digsbyipcaction/funcs.py b/digsby/src/plugins/digsbyipcaction/funcs.py new file mode 100644 index 0000000..e5d6bc5 --- /dev/null +++ b/digsby/src/plugins/digsbyipcaction/funcs.py @@ -0,0 +1,47 @@ +import sys +import simplejson + +IPC_HOOK = 'digsby.ipcaction' + +def quote_escape(s): + return s.replace('"', '\\"') + +def quote_unescape(s): + return s.replace('\\"', '"') + +def register_ipc_handlers(): + '''Registers an IPC handler for --action commands.''' + + import config as digsbyconfig + if digsbyconfig.platformName != 'win': + return + + import wx, hooks + from .ipc import listen + + def on_ipc(msg): + try: + method, kwargs = msg.split(':', 1) + except ValueError: + method, kwargs = msg, '{}' + + kwargs = simplejson.loads(quote_unescape(kwargs)) + wx.CallAfter(hooks.notify, IPC_HOOK, impl=method, **kwargs) + + listen(on_ipc) + +def funccall(method, **kwargs): + if kwargs: + argstring = quote_escape(simplejson.dumps(kwargs)) + return '"%s"' % ':'.join([method, argstring]) + else: + return method + +def handle_ipc_action(): + '''Looks for --action=foo on the command line, and sends an IPC message to + a running Digsby instance.''' + if sys.opts.action: + import ipc + ipc.send_message(sys.opts.action) + return True + diff --git a/digsby/src/plugins/digsbyipcaction/info.yaml b/digsby/src/plugins/digsbyipcaction/info.yaml new file mode 100644 index 0000000..63289e4 --- /dev/null +++ b/digsby/src/plugins/digsbyipcaction/info.yaml @@ -0,0 +1,25 @@ +name: 'DigsbyIPCAction' +path: 'digsbyipcaction' +type: 'pure' + +platforms: ['win'] + +entry_points: + #these 'digsby.ipcaction' would really get registered by something like Digsby.core + digsby.ipcaction: + prefsdialog: gui.pref.prefsdialog:show + globalstatus: main:SetStatusPrompt + newim: gui.imdialogs:ShowNewIMDialog + chat: gui.imwin.imhub:open + status: digsbyprofile:set_simple_status + exit: main:ExitDigsby + #these stay here + digsby.app.init.post: + ipcaction: digsbyipcaction.funcs:register_ipc_handlers + digsby.app.init.pre: + ipcaction: digsbyipcaction.funcs:handle_ipc_action + buddylist.sorted: + ipcaction: gui.native.win.jumplist:buddylist_sorted + digsby.im.statusmessages.set.post: + ipcaction: gui.native.win.jumplist:status_set + diff --git a/digsby/src/plugins/digsbyipcaction/ipc.py b/digsby/src/plugins/digsbyipcaction/ipc.py new file mode 100644 index 0000000..8f0b760 --- /dev/null +++ b/digsby/src/plugins/digsbyipcaction/ipc.py @@ -0,0 +1,68 @@ +''' +simple IPC + +todo: multiple servers +''' +import config + +if config.platform == 'win': + import win32events as win + import ctypes + import wx + + hidden_frame = None + receiver = None + + WINDOW_CLASS = 'wxWindowClassNR' + RECEIVER_GUID = '60210be0-de97-11de-8a39-0800200c9a66' + MESSAGE_GUID = '7e01e6f0-de99-11de-8a39-0800200c9a66' + WM_COPYDATA = 0x4a + + class COPYDATASTRUCT(ctypes.Structure): + _fields_ = [ + ('dwData', ctypes.wintypes.DWORD), # will fail on 64 bit + ('cbData', ctypes.wintypes.DWORD), + ('lpData', ctypes.c_void_p), + ] + + def listen(receiver_cb): + global hidden_frame, receiver + if hidden_frame is not None: + hidden_frame.Destroy() + + receiver = receiver_cb + + hidden_frame = wx.Frame(None, title=RECEIVER_GUID) + win.bindwin32(hidden_frame, WM_COPYDATA, on_copydata) + + def on_copydata(hWnd, msg, wParam, lParam): + if receiver is not None and lParam: + cds = COPYDATASTRUCT.from_address(lParam) + if cds.dwData == hash(MESSAGE_GUID): + message = ctypes.string_at(cds.lpData, cds.cbData-1) # minus one for NULL byte at end + receiver(message) + + def send_message(message): + assert isinstance(message, str) + + hwnd = ctypes.windll.user32.FindWindowA(WINDOW_CLASS, RECEIVER_GUID) + if not hwnd: + return False + + sender_hwnd = 0 + buf = ctypes.create_string_buffer(message) + + copydata = COPYDATASTRUCT() + copydata.dwData = hash(MESSAGE_GUID) + copydata.cbData = buf._length_ + copydata.lpData = ctypes.cast(buf, ctypes.c_void_p) + + return ctypes.windll.user32.SendMessageA(hwnd, WM_COPYDATA, sender_hwnd, ctypes.byref(copydata)) + +else: + def listen(receiver_cb): + pass + + def send_message(message): + pass + diff --git a/digsby/src/plugins/facebook/__init__.py b/digsby/src/plugins/facebook/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/facebook/action_links.py b/digsby/src/plugins/facebook/action_links.py new file mode 100644 index 0000000..a00003e --- /dev/null +++ b/digsby/src/plugins/facebook/action_links.py @@ -0,0 +1,78 @@ +import util.net +from util.primitives.structures import oset +from util.primitives.mapping import odict +import branding +UrlQuery = util.net.UrlQuery + +__all__ = [] + +def BASE(): + return UrlQuery('http://www.digsby.com/') + +#def STATUS_CAMPAIGN(): +# return UrlQuery(BASE(), utm_medium='fb', utm_campaign='fbstatus') + +def NEWSFEED_CAMPAIGN(): + campaign = branding.get('digsby.facebook.newsfeed.campaign', 'digsby_facebook', 'fbnewsfeed') + return UrlQuery(BASE(), utm_medium='fb', utm_campaign=campaign) + +#ACTION_LINKS1 =[ +# [('Sent using Digsby', UrlQuery(STATUS_CAMPAIGN, utm_source='fbVIA', utm_term='VIA'))], +# [('Download Digsby', UrlQuery(STATUS_CAMPAIGN, utm_source='fbDL', utm_term='DL' ))], +# [('Get Digsby', UrlQuery(STATUS_CAMPAIGN, utm_source='fbGET', utm_term='GET'))], +# * Two action links v1: +# [('Sent using Digsby', UrlQuery(STATUS_CAMPAIGN, utm_source='fbBothDL', utm_term='VIA')), +# ('Download Digsby', UrlQuery(STATUS_CAMPAIGN, utm_source='fbBothDL', utm_term='DL' ))], +# * Two action links v2 +# [('Sent using Digsby', UrlQuery(STATUS_CAMPAIGN, utm_source='fbBothGet', utm_term='VIA')), +# ('Get Digsby', UrlQuery(STATUS_CAMPAIGN, utm_source='fbBothGet', utm_term='GET'))], +# ] + +#ACTION_LINKS = [[dict(text=text, href=href) for text,href in l] for l in ACTION_LINKS1] + +def MERGED_URL(): + return UrlQuery(NEWSFEED_CAMPAIGN(), utm_source='merged') + +def ACCT_BASE(protocol): + return UrlQuery(NEWSFEED_CAMPAIGN(), utm_source=str(protocol)) + +def COUNT_BASE(type_): + return UrlQuery(NEWSFEED_CAMPAIGN(), utm_source=type_) + +def clicksrc(base, source): + return UrlQuery(base, utm_term=source) + +protocol_ordering = oset(['fbchat', 'facebook']) + +def get_acct_name(protocol): + from common import protocolmeta + return protocolmeta.nice_name_for_proto(protocol) + +def get_accts_text(type_=''): + from common import profile, protocolmeta + protos = oset(a.protocol for a in getattr(profile.account_manager, type_ + 'accounts')) + protos = (protocol_ordering & protos) | protos + return ', '.join(protocolmeta.nice_name_for_proto(name) for name in protos) + +def get_acct_properties(href): + s_text = get_accts_text('social') + im_text = get_accts_text('') + e_text = get_accts_text('email') + props = odict() + + if s_text: + props['Social Networks'] = {'text': s_text, + 'href': clicksrc(href, 'SOCIAL')} + if im_text: + props['IM Accounts'] = {'text': im_text, + 'href': clicksrc(href, 'IM')} + if e_text: + props['Email Accounts'] = {'text': e_text, + 'href': clicksrc(href, 'EMAIL')} + assert props + return props + +def his_her_their(s): + if s == 'female': return 'her' + elif s == 'male': return 'his' + else: return 'their' diff --git a/digsby/src/plugins/facebook/actions.yaml b/digsby/src/plugins/facebook/actions.yaml new file mode 100644 index 0000000..f52141d --- /dev/null +++ b/digsby/src/plugins/facebook/actions.yaml @@ -0,0 +1,21 @@ +FacebookAccount: + - call: update_now + name: !_ '&Update Now' + - call: tell_me_again + name: !_ '&Tell Me Again' + - call: OpenHome + name: !_ '&Home' + - call: OpenProfile + name: !_ 'Pro&file' + - call: OpenFriends + name: !_ '&Friends' + - call: OpenPhotos + name: !_ '&Photos' + - call: OpenMessages + name: !_ '&Messages' + - ------------------------ + - call: rename_gui + name: !_ "&Rename" + - ------------------------ + - call: edit_status + name: !_ '&Set Status' diff --git a/digsby/src/plugins/facebook/content_translation.py b/digsby/src/plugins/facebook/content_translation.py new file mode 100644 index 0000000..5c922d8 --- /dev/null +++ b/digsby/src/plugins/facebook/content_translation.py @@ -0,0 +1,34 @@ +''' +Created on Aug 20, 2009 + +@author: Christopher +''' +def get_birthdays(bdays, now): + import datetime + today = datetime.date.fromtimestamp(now) + new = {} + for uid, d_ in bdays.items(): + bd = d_.get('birthday_date') + if bd is not None: + mdy = [None]*3 + vals = [int(i) for i in bd.split('/')] + mdy[:len(vals)] = vals + m, d, y = mdy + bd_date_this = datetime.date(today.year, m, d) + bd_date_next = datetime.date(today.year + 1, m, d) + keep = False + if -1 < (bd_date_this-today).days <= 7: + keep = True + bd_date = bd_date_this + elif -1 < (bd_date_next-today).days <= 7: + keep = True + bd_date = bd_date_next + if keep: + new[uid] = dict(d_) + new[uid]['bday'] = bd_date + if y is not None: + born_on = datetime.date(y,m,d) + #I figure leap-year shouldn't matter, who d'you know that has been around the sun 1460+ times? + new[uid]['age'] = int(((bd_date - born_on).days + 1) / 365) + + return sorted(new.items(), key=lambda foo: foo[1]['bday']) diff --git a/digsby/src/plugins/facebook/facebookapi.py b/digsby/src/plugins/facebook/facebookapi.py new file mode 100644 index 0000000..2f01ce1 --- /dev/null +++ b/digsby/src/plugins/facebook/facebookapi.py @@ -0,0 +1,310 @@ +from __future__ import with_statement +from . import fberrors +from .fbutil import signature +from logging import getLogger +from util import AttrChain, default_timer +from util.callbacks import callsback, callback_adapter +from util.primitives.mapping import Ostorage +from util.xml_tag import tag +import common.asynchttp +from common import pref +import simplejson +import time +import traceback +import urllib +import urllib2 +S = Ostorage +log = getLogger("facebook.api") + +DIGSBY_APP_ID = '' +WIDGET_APP_ID = '' +DIGSBY_ACHIEVEMENTS_APP_ID = '' +DIGSBY_APP_SECRET = '' +WIDGET_APP_SECRET = '' +DIGSBY_ACHIEVEMENTS_APP_SECRET = '' +DIGSBY_API_KEY = '' +WIDGET_API_KEY = '' +DIGSBY_ACHIEVEMENTS_API_KEY = '' + + +class FacebookAPI(AttrChain): + server_url = 'http://api.facebook.com/restserver.php' + secure_server_url = 'https://api.facebook.com/restserver.php' + facebook_url = 'http://www.facebook.com/' + add_app_url = facebook_url + 'add.php' + + request_limited_at = None + + def __init__(self, api_key, app_id, app_secret, session_key=None, secret=None, uid=None, format='JSON', + mode='async', name=None, **_k): + self.api_key = api_key + self.app_id = app_id + self.app_secret = app_secret + self.session_key = session_key + self.secret = secret + self.format = format + self.mode = mode + self.name = name + + self.uid = uid + + self.logged_in = bool(session_key and secret and uid) + self.httpmaster = common.asynchttp.HttpMaster() + AttrChain.__init__(self, 'facebook') + + def set_session(self, session_json_str): + for f in (lambda s: s, lambda s: s.decode('url')): + try: + session_json_str_decoded = f(session_json_str) + d = simplejson.loads(session_json_str_decoded) + except ValueError as e: + if e.args == ('No JSON object could be decoded',): + log.debug('%r', e) + else: + traceback.print_exc() + except Exception: + traceback.print_exc() + else: + log.info('JSON object successfully decoded') + log.info_s('%r', d) + self.uid = int(d['uid']) + self.session_key = d['session_key'] + self.secret = d['secret'] + + def copy(self): + ret = type(self)(**self.__dict__) + ret.uid = self.uid + return ret + + def console(self): + new = self.copy() + new.mode = "console" + return new + + def query(self, query, **k): + ''' + convenience method for executing fql queries + ''' + return self.fql.query(query=query, **k) + + @callsback + def multiquery(self, callback=None, prepare=False, **k): + return self.fql.multiquery(callback=callback, prepare=prepare, queries=k) + + def set_status(self, status=u''): + if status: + assert isinstance(status, unicode) + self.users.setStatus(status=status.encode('utf-8'), + status_includes_verb = 1) + else: + self.users.setStatus(clear=1) + + def _call_id(self): + return int(time.time()*1000) + + def prepare_call(self, method, **k): + k['api_key'] = self.api_key.encode('utf-8') #should be ascii already + k['method'] = method + k['v'] = '1.0' + format = self.format + if format != 'XML': + k['format'] = format + + k['session_key'] = self.session_key.encode('utf-8') #should be able to .encode('ascii'), but I'd rather not have that error + k['call_id'] = str(int(self._call_id())) + assert all(isinstance(val, bytes) for val in k.values()) + k['sig'] = signature(self.secret.encode('utf-8'), **k) # .encode same as session key, they come from webkit/json as unicode + return urllib.urlencode(k) + + @callsback + def __call__(self, method, callback=None, prepare=False, **k): + k = prepare_args(k) + data = self.prepare_call(method, **k) + if prepare: + return data + + if pref('facebook.api.https', False, bool): + url = self.secure_server_url + else: + url = self.server_url + log.info("calling method: %s", method) + log.info_s("calling: %s with %r", url, k) + callback.error += self.handle_error + if self.mode == 'async': + return self.call_asynchttp(url, data, callback=callback) + elif self.mode == 'console': + return call_urllib2(url, data) + elif self.mode == 'threaded': + from util.threads import threaded + return threaded(call_urllib2)(url, data, callback=callback) + else: + return callback_adapter(call_urllib2)(url, data, callback=callback) + log.info("called method: %s", method) + + def handle_error(self, error): + if getattr(error, 'code', None) == 4: + self.request_limited_at = default_timer() + + def request_limited(self): + if self.request_limited_at is None: + return False + + def __repr__(self): + return "<{0.__class__.__name__!s} {0.name!r}>".format(self) + + @callsback + def call_asynchttp(self, url, data, callback=None): + return self.httpmaster.open(url, data=data, + headers={'Accept-Encoding': 'bzip2;q=1.0, gzip;q=0.8, compress;q=0.7, identity;q=0.1'}, + success=(lambda *a, **k: + callback_adapter(parse_response, do_return=False)(callback=callback, *a, **k)), + error = callback.error, + timeout = callback.timeout) + +class DigsbyAPI(FacebookAPI): + def __init__(self, session_key=None, secret=None, uid=None, format='JSON', + mode='async', name=None, **_k): + + FacebookAPI.__init__(self, DIGSBY_API_KEY, DIGSBY_APP_ID, DIGSBY_APP_SECRET, + session_key=session_key, secret=secret, uid=uid, format=format, + mode=mode, name=name, **_k) + +class DigsbyAchievementsAPI(FacebookAPI): + def __init__(self, session_key=None, secret=None, uid=None, format='JSON', + mode='async', name=None, **_k): + + FacebookAPI.__init__(self, DIGSBY_ACHIEVEMENTS_API_KEY, DIGSBY_ACHIEVEMENTS_APP_ID, DIGSBY_ACHIEVEMENTS_APP_SECRET, + session_key=session_key, secret=secret, uid=uid, format=format, + mode=mode, name=name, **_k) + +class WidgetAPI(FacebookAPI): + def __init__(self, session_key=None, secret=None, uid=None, format='JSON', + mode='async', name=None, **_k): + + FacebookAPI.__init__(self, WIDGET_API_KEY, WIDGET_APP_ID, WIDGET_APP_SECRET, + session_key=session_key, secret=secret, uid=uid, format=format, + mode=mode, name=name, **_k) + + +def call_urllib2(url, data): + resp = urllib2.urlopen(url, data=data) + return parse_response(resp) + + + +PARSE_FUNCS = ((lambda s: simplejson.loads(s, object_hook=storageify)),)# tag_parse) +STR_FUNCS = ((lambda response: response), (lambda response: response.decode('z'))) + +def parse_response(req, resp=None): + #call_asynchttp + if resp is None: + resp = req + del req + if hasattr(resp, 'read'): + response = resp.read() + #if not asynchttp: + if hasattr(resp, 'close'): + resp.close() + else: + #parsing a string directly + response = resp + assert isinstance(response, basestring) + log.info("got response, len:%d", len(response)) + for parse_attempt in PARSE_FUNCS: + for str_attempt in STR_FUNCS: + try: + s = str_attempt(response) + log.info("string len:%d", len(s)) + if len(s) < 1024: + log.info("short response: %r" % s) + assert log.info("zipped len:%d", len(s.encode('gzip'))) or True + res = parse_attempt(s) + except Exception: + continue + else: + break + else: + continue + break + else: + raise fberrors.FacebookParseFail('no idea what this data is: %r' % response) + + log.info("parsed response, checking for errors") + try: + if isinstance(res, list): + to_check = res + else: + to_check = [res] + map(check_error_response, to_check) + except fberrors.FacebookError as e: + log.debug_s("resp: %r", res) + log.warning('FacebookError: %r, %r', e, vars(e)) + raise e + except Exception, e: + log.warning_s('resp: %r', res) + log.warning('exception: %r, %r', e, vars(e)) + raise e + log.info('\tsuccessful request') + return res + +def check_error_response(t): + #does not simplify, 'in' doesn't work on ints + if isinstance(t, tag): + if 'error_code' in t: + raise fberrors.FacebookError(t) + elif getattr(t, 'error_code', False): + raise fberrors.FacebookError(t) + else: + error_data = simplejson.loads(getattr(t, 'body', '{}')).get('error', None) + if error_data is not None: + raise fberrors.FacebookGraphError(error_data) + +def storageify(d): + if isinstance(d, dict): + return S(d) + return d + +def prepare_args(d): + assert type(d) is dict + ret = dict(d) + import simplejson as json + for k,v in d.items(): + if isinstance(v, (list, dict, tuple)): + ret[k] = json.dumps(v, sort_keys=False, use_speedups=False, separators=(',', ':')) + else: + ret[k] = v + return ret + +def simplify_multiquery(multi_result, keys=None): + if keys is None: + keys = {} + + assert (not keys) or (len(multi_result) == len(keys)) + res = S() + for d in multi_result: + name = d['name'] + result = d.get('fql_result_set') or d.get('results') or [] + if keys and keys[name] is not None: + result = db_rows_to_useful(result, keys[name]) + res[name] = result + return res + +def db_rows_to_useful(result, key): + if key is list: + return db_rows_to_list(result) + else: + return db_rows_to_dict(result, key) + +def db_rows_to_list(result): + res = [] + for row in result: + assert len(row.values()) == 1 + res.append(row.values()[0]) + return res + +def db_rows_to_dict(l, key): + res = S() + for row in l: + row = type(row)(row) #copy + res[row.pop(key)] = row + return res diff --git a/digsby/src/plugins/facebook/facebookprotocol.py b/digsby/src/plugins/facebook/facebookprotocol.py new file mode 100644 index 0000000..dacf0cd --- /dev/null +++ b/digsby/src/plugins/facebook/facebookprotocol.py @@ -0,0 +1,439 @@ +from util.callbacks import callsback +from util.threads.timeout_thread import Timer +from util.primitives import Storage as S +from traceback import print_exc +import util.primitives.structures as structures +from .fbutil import trim_profiles, extract_profile_ids +import traceback +import simplejson +import facebookapi +import hooks +from social.network import SocialFeed +from util.Events import EventMixin +import util.primitives.mapping as mapping + +from logging import getLogger +from util.primitives.mapping import Ostorage +from gui import skin +import graphapi +log = getLogger("Facebook2.0") + +POSTS_LIMIT = 100 + +FORCED_APPS = {'News Feed': 'nf', + 'Status Updates': 'app_2915120374', + 'Photos': 'app_2305272732', + 'Links': 'app_2309869772'} + +FORCED_KEYS = { + '__notification__':{'name':'Notifications', + 'icon_url':'facebookicons.notifications_icon'} + } + +KNOWN_APPS_LOOKUP = mapping.dictreverse(FORCED_APPS) + +#COMMENTS_QUERY = "SELECT fromid, text, time, post_id, id FROM comment WHERE post_id IN (SELECT post_id FROM #posts)" +PROFILES_QUERY = """SELECT id, name, pic_square, url FROM profile WHERE id IN (SELECT viewer_id FROM #posts) OR id IN(SELECT actor_id FROM #posts) OR id in (SELECT target_id FROM #posts) OR id in (SELECT source_id FROM #posts) OR id IN (SELECT likes.sample FROM #posts) OR id IN (SELECT likes.friends FROM #posts) OR id IN (SELECT sender_id FROM #notifications)""" +#ALL_POSTS_QUERY = 'SELECT post_id, comments, permalink, created_time, updated_time, viewer_id, actor_id, target_id, source_id, message, attachment, action_links, likes FROM stream where filter_key="nf" and is_hidden=0 LIMIT 100' +BIRTHDAY_QUERY = 'select name, birthday_date, profile_url, uid from user where uid IN (select uid2 from friend where uid1=%d)' +NOW_QUERY = 'select now() from user where uid=%d' +EVENTS_QUERY = 'select eid from event where eid in (select eid from event_member where uid=me() and rsvp_status="not_replied") and start_time > now()' +STATUS_QUERY = 'select message, status_id, time, uid from status where uid=me() limit 1' + +NOTIFICATIONS_QUERY = 'select notification_id,sender_id,created_time,updated_time,title_html,title_text,href,is_unread,app_id from notification where recipient_id=me()' +APP_QUERY = 'SELECT app_id,icon_url FROM application WHERE app_id IN (SELECT app_id from #notifications)' + +POST_FILTER_KEY_QUERY = "select post_id, filter_key from stream where post_id in (select post_id from #latest_posts) and filter_key in (select filter_key from #filter_keys)" +FILTER_KEY_QUERY = "select filter_key, name, icon_url from stream_filter where uid=me() and ((is_visible=1 and type='application') or filter_key in ('" + "', '".join(FORCED_APPS.values()) + "')) ORDER BY rank ASC" +POST_QUERY = 'SELECT post_id, comments, permalink, created_time, updated_time, viewer_id, actor_id, target_id, source_id, message, attachment, action_links, likes FROM stream where post_id="%s"' + +#UPDATED_POSTS_QUERY = 'SELECT post_id, comments, permalink, created_time, updated_time, viewer_id, actor_id, target_id, source_id, message, attachment, action_links, likes FROM stream where filter_key="nf" and is_hidden=0 and updated_time > %s LIMIT 100' +LATEST_POSTS_QUERY = ' '.join(x.strip() for x in ''' + SELECT post_id, updated_time + FROM stream + WHERE filter_key="%%s" %%s ORDER BY created_time DESC + LIMIT %d + '''.strip().splitlines()) % POSTS_LIMIT +UPDATED_POSTS_QUERY = ' '.join(x.strip() for x in '''SELECT post_id, comments, permalink, created_time, updated_time, viewer_id, actor_id, target_id, source_id, message, attachment, action_links, likes + FROM stream + WHERE post_id in + ( + SELECT post_id + FROM #latest_posts + WHERE updated_time > %s + ) ORDER BY created_time DESC'''.strip().splitlines()) + +UPDATE_STREAM_QUERY = { + #'comments':COMMENTS_QUERY, + 'profiles':PROFILES_QUERY} + +from facebook.fbacct import FBIB + +class FacebookProtocol(EventMixin): + events = EventMixin.events | set([ + 'stream_requested', + 'not_logged_in', + 'got_stream', + 'status_updated', + 'conn_error', + 'infobox_dirty', + ]) + def __init__(self, acct): + self.stream_request_outstanding = True + self.acct = acct + self._init_apis() + self.last_stream = True + self.last_filter_key = self.filter_key + EventMixin.__init__(self) + + self.social_feed = SocialFeed('facebook_' + self.acct.username, + 'newsfeed', + self.get_post_feed, + self.htmlize_posts, + self.set_infobox_dirty) + + def set_infobox_dirty(self): + self.event('infobox_dirty') + + def htmlize_posts(self, posts, stream_context): + '''Convert one facebook newsfeed post into infobox HTML.''' + t = FBIB(self.acct) + #CAS: pull out the context stuff, the default FBIB grabs self.last_stream, not the one we have context for! + return t.get_html(None, set_dirty=False, + file='posts.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=S(posts=posts)) + + def get_post_feed(self): + # TODO bring back feed context. + return iter(self.last_stream.posts) + + @property + def filter_key(self): + return ['nf', 'lf', 'h'][self.acct.preferred_filter_key] + + @property + def hidden_posts(self): + return "and is_hidden=0" if self.acct.show_hidden_posts else '' + + def get_stream(self): + self.stream_request_outstanding = True + self.do_get_stream() + + def _init_apis(self): + self._init_digsby() + + def _init_digsby(self, session_key='', secret='', uid=None): + access_token = getattr(self.acct, 'access_token', None) + uid = getattr(self.acct, 'uid', None), + self.digsby = graphapi.LegacyRESTAPI(access_token, uid=uid) + + def do_get_stream(self, num_tries=0): + from util import default_timer + self.start_get_stream = default_timer() + if not self.digsby.logged_in: + return self.event('not_logged_in') + #refresh full stream if pref has changed + prev_filter_key, self.last_filter_key = self.last_filter_key, self.filter_key + if not isinstance(self.last_stream, dict) or prev_filter_key != self.filter_key: + kw = dict(success=lambda *a: self.get_stream_success(num_tries=num_tries, *a), + error = lambda *a: self.get_stream_error(num_tries, *a)) + updated_time = 0 + else: + kw = dict(success=self.update_stream_success, + error = lambda *a: self.get_stream_error(num_tries, *a)) + updated_time = max(self.last_stream.posts + [S(updated_time=0)], key=lambda v: v.updated_time).updated_time +# query = self.digsby.multiquery(prepare=True, + self.last_run_multi = dict( +# birthdays = BIRTHDAY_QUERY % self.digsby.uid, + latest_posts = LATEST_POSTS_QUERY % (self.filter_key, self.hidden_posts), + posts = UPDATED_POSTS_QUERY % (('%d' % updated_time) + '+0'), +# now = NOW_QUERY % self.digsby.uid, + events = EVENTS_QUERY, + status = STATUS_QUERY, + notifications = NOTIFICATIONS_QUERY, + apps = APP_QUERY, + post_filter_keys = POST_FILTER_KEY_QUERY, + filter_keys = FILTER_KEY_QUERY, + **UPDATE_STREAM_QUERY) + self.digsby.fql.multiquery(queries=self.last_run_multi, **kw) +# alerts = self.digsby.notifications.get(prepare=True) +# self.digsby.batch.run(method_feed=[alerts, query], **kw) + + def update_status(self): + self.digsby.query(STATUS_QUERY, success=self.status_updated) + + def status_updated(self, status): + status = status[0] + if status is not None: + status['uid'] = self.digsby.uid + self.last_status = status + self.event('status_updated') + + def update_stream_success(self, value): + return self.get_stream_success(value, update=True) + + def get_stream_success(self, value, update=False, num_tries=0): + from util import default_timer + self.end_get_stream = default_timer() + log.debug('stream get took %f seconds', self.end_get_stream - self.start_get_stream) + stream = value +# v = [] +# for val in value: +# v.append(simplejson.loads(val, object_hook=facebookapi.storageify)) +# alerts, stream = v[:2] + self.last_alerts = Alerts(self.acct) + from facebookapi import simplify_multiquery + try: +# print stream + new_stream = simplify_multiquery(stream,keys={'posts':None, +# 'comments':None, + 'latest_posts':None, + 'profiles':'id', +# 'now':None, + 'events':list, + 'status':None, + 'notifications': None, + 'apps' : 'app_id', + 'post_filter_keys':None, + 'filter_keys':'filter_key'})# 'birthdays':'uid',}) + import util.primitives.funcs as funcs +# new_stream['comments'] = dict(funcs.groupby(new_stream['comments'], lambda x: x['post_id'])) + new_stream['comments'] = {} + new_stream['post_ids'] = post_ids = {} + for k, v in new_stream['filter_keys'].iteritems(): + if not v.get('name'): + v['name'] = KNOWN_APPS_LOOKUP.get(k, v.get('name')) + new_stream['filter_keys'].update([(k, dict(name=d['name'], + icon_url=skin.get(d['icon_url']).path.url())) for k,d in FORCED_KEYS.items()]) + new_stream['post_filter_keys'] = dict((post_id, structures.oset(p['filter_key'] for p in vals)) + for post_id, vals in + funcs.groupby(new_stream['post_filter_keys'], lambda x: x['post_id'])) + for post in new_stream['posts']: + post['comments']['count'] = int(post['comments']['count']) + new_stream['apps'], apps_str = {}, new_stream['apps'] + for app_id, app_dict in apps_str.items(): + new_stream['apps'][int(app_id)] = app_dict + try: + new_stream['now'] = new_stream['now'][0].values()[0] + except (IndexError, KeyError) as _e: +# print_exc() + import time + new_stream['now'] = time.time() + self.last_alerts.event_invites &= set(new_stream['events']) + self.last_status = (new_stream['status'][:1] or [Ostorage([('message', ''), ('status_id', 0), ('time', 0)])])[0] + self.last_status['uid'] = self.digsby.uid + if not isinstance(new_stream['posts'], list): + log.error('stream: %r', stream) + raise ValueError('Facebook returned type=%r of posts' % type(new_stream['posts'])) + for post in new_stream['posts']: #get the new ones + post_ids[post['post_id']] = post + if 'notifications' in new_stream: + import lxml + for notification in new_stream['notifications']: + title_html = notification.get('title_html', None) + if title_html is None: + continue + s = lxml.html.fromstring(title_html) + s.make_links_absolute('http://www.facebook.com', resolve_base_href = False) + for a in s.findall('a'): + a.tag = 'span' +# _c = a.attrib.clear() + a.attrib['class'] = 'link notification_link' + [x.attrib.pop("data-hovercard", None) for x in s.findall(".//*[@data-hovercard]")] + notification['title_html'] = lxml.etree.tostring(s) + self.last_alerts.update_notifications(new_stream['notifications']) + if update: + latest_posts = filter(None, (post_ids.get(post_id, self.last_stream.post_ids.get(post_id)) for post_id in + structures.oset([post['post_id'] for post in new_stream['latest_posts']] + + [post['post_id'] for post in self.last_stream.posts])))[:POSTS_LIMIT] + new_stream['posts'] = latest_posts + for post in new_stream['posts']: #update the dict with the combined list + post_ids[post['post_id']] = post + for key in self.last_stream.comments: + if key in post_ids and key not in new_stream.comments: + new_stream.comments[key] = self.last_stream.comments[key] + for key in self.last_stream.profiles: + if key not in new_stream.profiles: + new_stream.profiles[key] = self.last_stream.profiles[key] + trim_profiles(new_stream) + for p in new_stream.posts: p.id = p.post_id # compatability hack for ads + self.last_stream = new_stream + self.social_feed.new_ids([p['post_id'] for p in self.last_stream.posts]) + except Exception, e: + traceback.print_exc() + return self.get_stream_error(num_tries=num_tries, error=e) + self.event('got_stream') + + def get_stream_error(self, num_tries, error=None, *a): #*a, **k for other kinds of errors. + if not_logged_in(error): #doesn't matter if it's really a facebook error; should fail this test if not + return self.event('not_logged_in') + elif num_tries < 2: + Timer(2, lambda: self.do_get_stream(num_tries + 1)).start() + else: + self.event('conn_error') + + @callsback + def addComment(self, post_id, comment, callback=None): + self.digsby.stream.addComment(post_id=post_id, comment=comment, + success = lambda resp: self.handle_comment_resp(resp, post_id, comment, callback), + error = lambda resp: self.handle_comment_error(resp, post_id, comment, callback)) + + @callsback + def removeComment(self, comment_id, callback=None): + self.digsby.stream.removeComment(comment_id=comment_id, + success = lambda resp: self.handle_comment_remove_resp(resp, comment_id, callback), + error = lambda resp: self.handle_comment_remove_error(resp, comment_id, callback)) + + @callsback + def getComments(self, post_id, callback=None, limit=50, **k): + self.digsby.multiquery( + comments = 'SELECT fromid, text, time, post_id, id FROM comment WHERE post_id="%s" ORDER BY time DESC LIMIT %d' % (post_id, limit), + count = 'SELECT comments.count FROM stream where post_id="%s"' % post_id, + profiles = """SELECT id, name, pic_square, url FROM profile WHERE id IN (SELECT fromid FROM #comments)""", + success = lambda resp: self.handle_get_comments_resp(resp, post_id, callback), + error = lambda req, resp = None: self.handle_get_comments_error(resp or req, post_id, callback) + ) + + def handle_get_comments_resp(self, resp, post_id, callback): + from facebookapi import simplify_multiquery + resp = simplify_multiquery(resp, + {'comments':None, + 'count':None, + 'profiles':'id'} + ) + resp['comments'].sort(key = lambda c: c['time']) + try: + count = resp['count'][0]['comments']['count'] + try: + self.last_stream['post_ids'][post_id]['comments']['count'] = int(count) + except Exception: + traceback.print_exc() + except Exception: + num_comments = len(resp['comments']) + if num_comments >= 50: + count = -1 + else: + count = num_comments + self.last_stream['comments'][post_id] = resp['comments'] + self.last_stream['profiles'].update(resp['profiles']) + callback.success(post_id, count) + + def handle_get_comments_error(self, resp, post_id, callback): + callback.error(resp) + + def handle_comment_remove_resp(self, resp, comment_id, callback): + if resp: + for post_id, comments in self.last_stream['comments'].items(): + for i, comment in enumerate(comments): + if comment['id'] == comment_id: + c = comments.pop(i) + post = self.last_stream['post_ids'][post_id] + post['comments']['count'] -= 1 + callback.success(post_id) + hooks.notify('digsby.facebook.comment_removed', c) + return + + def handle_comment_remove_error(self, resp, comment_id, callback): + callback.error() + + @callsback + def addLike(self, post_id, callback): + self.digsby.stream.addLike(post_id=str(post_id), + success = (lambda resp: self.handle_like_resp(resp, post_id, callback)), + error = (lambda resp: self.handle_like_error(resp, post_id, callback))) + + @callsback + def removeLike(self, post_id, callback): + self.digsby.stream.removeLike(post_id=post_id, + success = (lambda resp: self.handle_unlike_resp(resp, post_id, callback)), + error = (lambda resp: self.handle_unlike_error(resp, post_id, callback))) + + def handle_like_resp(self, resp, post_id, callback): + post = self.last_stream['post_ids'][post_id] + post['likes'].update(user_likes=True) + post['likes']['count'] += 1 + callback.success(post_id) + hooks.notify('digsby.facebook.like_added', post_id) + + def handle_unlike_resp(self, resp, post_id, callback): + post = self.last_stream['post_ids'][post_id] + post['likes'].update(user_likes=False) + post['likes']['count'] -= 1 + callback.success(post_id) + hooks.notify('digsby.facebook.like_removed', post_id) + #regen likes block, regen likes link block, send to callback + #regen cached post html + + def handle_comment_resp(self, response, post_id, comment, callback): + comment_id = response + post = self.last_stream['post_ids'][post_id] + post['comments']['count'] += 1 + import time + comment_dict = S({'fromid': post['viewer_id'], + 'id': comment_id, + 'post_id': post_id, + 'text': comment, + 'time': time.time()}) + self.last_stream['comments'].setdefault(post_id, []).append(comment_dict) + callback.success(post_id, comment_dict) + hooks.notify('digsby.facebook.comment_added', comment_dict) + #regen comment, regen comment link block + #regen cached post html + + def handle_comment_error(self, response, post_id, comment, callback): + callback.error(response) + + def handle_like_error(self, response, post_id, callback): + callback.error(response) + + def handle_unlike_error(self, response, post_id, callback): + callback.error(response) + + @callsback + def get_user_name_gender(self, callback=None): + def success(info): + try: + info = info[0] + except Exception: + traceback.print_exc() + callback.error(info) + else: + if isinstance(info, dict): + callback.success(info) + else: + callback.error(info) + self.digsby.query('SELECT first_name, last_name, sex FROM user WHERE uid=' + str(self.digsby.uid), success=success, error=callback.error) + + +from .objects import Alerts + +#not ready to mess with code that's 17000 revisions old. +#minimal subclass to get rid of the reference to a facebook object +#the only reason it is there is to grab the filters; not up to that point yet here. +#class Alerts(Alerts_Super): +# def __init__(self, notifications_get_xml=None): +# super(Alerts, self).__init__(None, notifications_get_xml) +# if hasattr(self, 'fb'): +# del self.fb +# +# def __sub__(self, other): +# ret = Alerts() +# for attr in self.stuff: +# setattr(ret, attr, getattr(self, attr) - getattr(other, attr)) +# return ret +# +# def __getitem__(self, key): +# return getattr(self, key) + +login_error_codes = frozenset( +[100, #no session key + 102, #session invalid + 104, #signature invalid (likely the secret is messed up) + ] + + range(450, 455 + 1) + #session errors + [612] #permission error +) + +def not_logged_in(fb_error): + return getattr(fb_error, 'code', None) in login_error_codes diff --git a/digsby/src/plugins/facebook/fbacct.py b/digsby/src/plugins/facebook/fbacct.py new file mode 100644 index 0000000..84f1e23 --- /dev/null +++ b/digsby/src/plugins/facebook/fbacct.py @@ -0,0 +1,1109 @@ +import os +import path +import traceback +import logging + +import social +import protocols + +import common +import common.actions as actions +import common.protocolmeta as protocolmeta +from common.notifications import fire + +import util +import util.callbacks as callbacks +import util.Events as Events +import util.cacheable as cacheable +import util.net as net +import util.primitives.strings as string_helpers + +import gui.infobox.interfaces as gui_interfaces +import gui.infobox.providers as gui_providers +import common.AchievementMixin as AM + +from . import fberrors +from . import action_links +from . import objects +from . import fbconnectlogin + +import provider_facebook.facebook_service_provider as fb_sp +from facebook import oauth2login + +log = logging.getLogger('facebook.acct') + +DESKTOP_APP_POPUP = 'http://www.facebook.com/desktopapp.php?popup' +DISLIKE = u"Dislike! (via http://digsby.com/fb)" + +class input_callback(object): + close_button = True + spellcheck = True + + def __init__(self, cb): + self.input_cb = cb + + def __eq__(self, other): + return getattr(other, 'input_cb', object()) == self.input_cb + +class FacebookAccount(AM.AchievementMixin, social.network, Events.EventMixin): + + @property + def updatefreq(self): + return common.pref('facebook.updatefreq', 5 * 60) # in seconds + + events = Events.EventMixin.events | set([ + 'connect_requested', + 'connect', + 'update_requested', + 'update', + ]) + _dirty = True + facebook_url = 'http://www.facebook.com/' + + alerts_keys = 'num_msgs num_pokes num_shares num_friend_requests num_group_invites num_event_invites num_notifications'.split() + header_funcs= [(k,lambda v=v: FacebookAccount.launchfacebook(v)) for k,v in + (('Home',''), + ('Profile','profile.php'), + ('Friends','friends.php'), + ('Photos','photos.php'), + ('Inbox','inbox/') + )] + + extra_header_func = ("Invite Friends", lambda: launchbrowser('http://apps.facebook.com/digsbyim/invite.php')) + + def __init__(self, **options): + self.connection = False + options.setdefault('password', None) + self.secrets = options.setdefault('secrets', '') + self.uid = options.setdefault('uid', None) + self.show_hidden_posts = options.setdefault('show_hidden_posts', False) + self.preferred_filter_key = options.setdefault('preferred_filter_key', 1) #1 = live feed, the second key known to work. + + AM.AchievementMixin.__init__(self, **options) + + self.have_logged_in = False + + Events.EventMixin.__init__(self) + self.bind_events() + if not getattr(FBIB, '_tmpl', None): + try: + import time + a = time.clock() + t = FBIB(self) + context = gui_providers.InfoboxProviderBase.get_context(t) + t.load_files(context) + t.get_template() + FacebookAccount.preload_time = time.clock() - a + except Exception: + traceback.print_exc() + + filters = options.pop('filters', {}) + self._update_filters(filters) + + social.network.__init__(self, **options) + + @property + def access_token(self): + return self.secrets + + @access_token.setter + def access_token(self, val): + self.secrets = val + + def _update_filters(self, filters): + found_filters = filters.get('alerts', [True]*len(self.alerts_keys)) + alert_filters = [True]*len(self.alerts_keys) + alert_filters[:len(found_filters)] = found_filters + self.filters = dict(alerts=dict(zip(self.alerts_keys, alert_filters))) + return len(found_filters) != len(self.alerts_keys) + + def update_info(self, **info): + filters = info.pop('filters', None) + if filters is not None: + self._update_filters(filters) + self._dirty = True + social.network.update_info(self, **info) + + def bind_events(self): + self.bind('connect', self.call_on_connect) + self.bind('connect_requested', self.do_login) + self.bind('update_requested', self._update) + self.bind('update', self.update_complete) + + def call_on_connect(self): + self.on_connect() + + def should_update(self): + return self.state is not self.Statuses.AUTHENTICATING + + def _update(self): + if not self.should_update(): + return + elif self.state is self.Statuses.OFFLINE: + self.change_state(self.Statuses.CONNECTING) + elif self.state not in (self.Statuses.CONNECTING, self.Statuses.CHECKING): + self.change_state(self.Statuses.CHECKING) + if self.connection: + self.connection.clear() + else: + self.new_connection() + self.connection.bind('not_logged_in', self.do_login) + self.connection.bind('got_stream', self.update_complete) + self.connection.bind('status_updated', self.set_dirty) + self.connection.bind('conn_error', self.conn_error) + self.connection.bind('infobox_dirty', self.set_dirty) + self.connection.get_stream() + + def conn_error(self, *a, **k): + self.set_offline(self.Reasons.CONN_LOST) + + def new_connection(self): + import facebookprotocol + self.connection = facebookprotocol.FacebookProtocol(self) + + def update_complete(self): + self._dirty = True + if self.OFFLINE: + return + self.change_state(self.Statuses.ONLINE) + if self.achievements_paused: #don't need the lock to check here. + self.unpause_achievements() + self.fire_notifications(self.alerts if hasattr(self, 'alerts') else objects.Alerts(), self.connection.last_alerts) + self.alerts = self.connection.last_alerts + self.notification_popups() + self.newsfeed_popups() + + @property + def cache_dir(self): + 'directory for cache of this facebook account' + assert self.uid + return os.path.join('facebook', str(self.uid)) + + @property + def cache_path(self): + return os.path.join(self.cache_dir, 'facebook.dat') + + old_stream_ids = cacheable.cproperty(set, user=True) + + def newsfeed_popups(self, filter_old=True): + log.info('doing newsfeed popups') + s = self.connection.last_stream + old_ids = self.old_stream_ids if filter_old else () + feed = s['posts'] + new_posts = [] + for post in feed: + if post['post_id'] in old_ids: + log.info('post %r is old', post['post_id']) + break + source = post.get('source_id') + actor = post.get('actor_id') + viewer = post.get('viewer_id') + try: + do_add = source and viewer and int(source) != int(viewer) and int(actor) != int(viewer) + except ValueError: + do_add = True + if do_add: + new_posts.append(post) + else: + log.info('filtered message %s because it came from this account', post['post_id']) + + options = dict(buttons=self.get_popup_buttons) + + if new_posts: + from gui import skin + if common.pref('facebook.popups.user_icons', default=True): + from gui.browser.webkit.imageloader import LazyWebKitImage + default_icon = skin.get('serviceicons.facebook', None) + for post in new_posts: + try: + url = s.profiles[int(post['actor_id'])].pic_square + except Exception: + traceback.print_exc() + else: + post.icon = LazyWebKitImage(url, default_icon) + + fire( + 'facebook.newsfeed', + profiles = s['profiles'], + posts=new_posts, + popupid='%d.facebook' % id(self), + update='paged', + badge=skin.get('serviceicons.facebook', None), + scrape_clean=string_helpers.scrape_clean, + onclick = self.on_popup_click_post, **options + ) + self.old_stream_ids = set(self.connection.last_stream['post_ids']) + + old_notification_ids = cacheable.cproperty(set, user=True) + + def notification_popups(self, filter_old=True, filter_read=True): + log.info('doing notification popups') + s = self.connection.last_stream + old_ids = self.old_notification_ids if filter_old else () + new_notifications = [] + notifications = s['notifications'] + for notification in notifications: + if filter_read and int(notification['is_unread']) != 1: + continue + if notification['notification_id'] in old_ids: + log.info('notification %r is old', notification['notification_id']) + break + if not notification['title_text']: + continue + new_notifications.append(notification) + + if new_notifications: + from gui import skin + options = {} + if common.pref('facebook.popups.user_icons', default=True): + from gui.browser.webkit.imageloader import LazyWebKitImage + default_icon = skin.get('serviceicons.facebook', None) + for notification in new_notifications: + try: + url = s.profiles[int(notification['sender_id'])].pic_square + except Exception: + traceback.print_exc() + else: + notification.icon = LazyWebKitImage(url, default_icon) + fire( + 'facebook.notifications', + profiles = s['profiles'], + notifications=new_notifications, + popupid='%d.facebook' % id(self), + update='paged', + sticky = common.pref('facebook.notifications.sticky_popup', type = bool, default = False), + badge=skin.get('serviceicons.facebook', None), + onclick = self.on_popup_click_notification, **options + ) + + self.old_notification_ids = set(n['notification_id'] for n in notifications) + + def get_popup_buttons(self, item): + popup_buttons = [] + + post = item.post + post_id = post['post_id'] + post = self.connection.last_stream['post_ids'][post_id] + enabled = not post.get('pending', False) + + def count_str(count): + return (' (%s)' % count) if count and count != '0' else '' + + # comment button + comments = post.get('comments', {}) + can_post = comments.get('can_post') + if can_post: + num_comments = int(comments.get('count', 0)) + popup_buttons.append((_('Comment') + count_str(num_comments), input_callback(self.on_popup_comment))) + + # like button + likes = post.get('likes', {}) + can_like = likes.get('can_like', True) + if can_like: + liked = likes.get('user_likes', False) + num_likes = int(likes.get('count', 0)) + like_caption = _('Like') if not liked else _('Unlike') + popup_buttons.append((like_caption + count_str(num_likes), self.on_popup_like, enabled)) + +# print popup_buttons + + return popup_buttons + + def on_popup_comment(self, text, options): + post_id = options['post']['post_id'] + self.connection.addComment(post_id, text.encode('utf-8'), + success=(lambda *a, **k: self.set_dirty()), + error=self.on_popup_error(_('comment'))) + + def on_popup_like(self, item, control): + post = item.post + post_id = post['post_id'] + + if post['likes']['user_likes']: + func = self.connection.removeLike + else: + func = self.connection.addLike + + def set_pending(pending): + post['pending'] = pending + control.update_buttons() + + def on_success(*a, **k): + self.set_dirty() + set_pending(False) + + def on_error(*a, **k): + self.on_popup_error(_('like'))(*a, **k) + set_pending(False) + + func(post_id, success=on_success, + error=self.on_popup_error(_('like'))) + + set_pending(True) + + + on_popup_like.takes_popup_item = True + on_popup_like.takes_popup_control = True + + def on_popup_error(self, thing): + def on_error(response, *a, **k): + # if we don't have stream publish permissions, show a popup allowing + # the user to grant it. + if isinstance(response, fberrors.FacebookError) and int(response.tag.error_code) == 200: + fire( + 'error', title=_('Facebook Error'), + major = _('Error posting {thing}').format(thing=thing), + minor = _('Digsby does not have permission to publish to your stream.'), + buttons = [(_('Grant Permissions'), self.do_grant), + (_('Close'), lambda: None)] + ) + return on_error + + @actions.action(lambda self, *a, **k: ((self.state == self.Statuses.ONLINE) and common.pref('can_has_social_update',False)) or None) + def tell_me_again(self): + self.fire_notifications(objects.Alerts(), self.connection.last_alerts) + self.notification_popups(filter_old=False) + return self.newsfeed_popups(filter_old=False) + + def fire_notifications(self, old_alerts, new_alerts): + ''' + fires notifications for new information from facebook. + ''' + log.debug('doing alert notifications') + + alerts = new_alerts - old_alerts + if alerts and self.enabled: + + # newer, and still exists + TITLE = _("Facebook Alerts") + + fire_alerts = list() + def firealert(msg, onclick): + fire_alerts.append((msg, onclick)) + + if alerts.msgs_time > 0 and new_alerts['num_msgs']: + firealert(_('You have new messages.'), 'msgs') + + if alerts.pokes_time > 0 and new_alerts['num_pokes']: + firealert(_('You have new pokes.'), 'pokes') + + if alerts.shares_time > 0 and new_alerts['num_shares']: + firealert(_('You have new shares.'), 'shares') + + if alerts['num_friend_requests']: + firealert(_('You have new friend requests.'), 'friend_requests') + + if alerts['num_group_invites']: + firealert(_('You have new group invites.'), 'group_invites') + + if alerts['num_event_invites']: + firealert(_('You have new event invites.'), 'event_invites') + + #this one isn't really useful by itself + #we should check if the popups for notifications are on + if alerts['num_notifications'] and fire_alerts: + firealert(_('You have new notifications.'), 'notifications') + + if fire_alerts: + if len(fire_alerts) > 1: + # With more than one type of new alert, just go to the main facebook page. + onclick = self.facebook_url + else: + # Otherwise, get a more specific URL from Alerts.urls + msg, url_type = fire_alerts[0] + onclick = alerts.urls[url_type] + + message = u'\n'.join(k for k,_v in fire_alerts) + + fire( + 'facebook.alert', + title = TITLE, + msg = message, + buttons = lambda item: [], # this is necessary until popups recognize "None" as clearing buttons. + update='paged', + popupid = '%d.facebook' % id(self), + onclick = onclick + ) + + if self.enabled: + self.notify('count') + + @classmethod + def launchfacebook(cls, strng): + launchbrowser(cls.facebook_url + strng) + + def get_options(self): + opts = super(FacebookAccount, self).get_options() + secrets = self.secrets + if secrets: + opts['secrets'] = secrets + uid = self.uid + if uid: + opts['uid'] = uid + alrts = [bool(self.filters['alerts'][x]) for x in self.alerts_keys] + opts['filters'] = dict(alerts=alrts) + + #CAS: needs to be abstracted + for attr in ('show_hidden_posts', 'preferred_filter_key'): + if getattr(self, attr, None) != self.default(attr): + opts[attr] = getattr(self, attr, None) + + return opts + +#======================================================================================================================== +# begin socialnetwork/account interface +#======================================================================================================================== + # account boilerplate + service = protocol = 'facebook' + def Connect(self): + self.change_state(self.Statuses.CONNECTING) + + def _ready_to_connect(): + if self.enabled: + self.event('connect_requested') + else: + self.set_offline(self.Reasons.NONE) + + AM.AchievementMixin.try_connect(self, _ready_to_connect) + + def show_ach_dialog(self, success): + from .fbgui import FacebookAchievementsDialog + FacebookAchievementsDialog.show_dialog(None, + u'New Facebook Integration (%s)' % self.name, + message=UPGRADE_QUESTION, + success=success, + ) + + + def Disconnect(self, *a, **k): + self.set_offline(self.Reasons.NONE) + disconnect = Disconnect + + def DefaultAction(self): + self.edit_status() + + @actions.action(lambda self, *a, **k: ((self.state == self.Statuses.ONLINE) and common.pref('can_has_social_update',False)) or None) + def update_now(self, *a, **k): + if not self.have_logged_in: + self.have_logged_in = True + self.unbind('connect_requested', self.do_login) + self.bind('connect_requested', self.update_now) + self.event('update_requested') + + def observe_count(self,callback): + self.add_gui_observer(callback, 'count') + + def unobserve_count(self, callback): + self.remove_gui_observer(callback, 'count') + + def observe_state(self, callback): + self.add_gui_observer(callback, 'enabled') + self.add_gui_observer(callback, 'state') + + def unobserve_state(self, callback): + self.remove_gui_observer(callback, 'enabled') + self.remove_gui_observer(callback, 'state') + + @property + def count(self): + return getattr(getattr(getattr(self, 'connection', None), 'last_alerts', None), 'count', 0) + + def login_success(self, check_instance, did_login=False, *a, **k): + with fb_sp.FacebookLogin(self) as foo: + self.connection.digsby = foo.loginmanager.digsby + self.uid = foo.loginmanager.digsby.uid + if did_login: + self.loginproto = oauth2login.LoginCheck(api=self.connection.digsby, + login_success=self.real_login_success, + login_error=self.fail, + username=self.username, + acct=self) + self.loginproto.initiatiate_check(False) + else: + self.change_state(self.Statuses.CONNECTING) + return self.update_now(*a, **k) + + def real_login_success(self, *a, **k): + try: + self.access_token = self.connection.digsby.access_token + self.password = None + self.uid = self.connection.digsby.uid + self.update_info() + except Exception: + traceback.print_exc() + self.change_state(self.Statuses.CONNECTING) + return self.update_now(*a, **k) + + def do_login(self): + if not self.connection: + self.new_connection() + if self.OFFLINE or self.AUTHENTICATING: + return + self.change_state(self.Statuses.AUTHENTICATING) + + #ask the service provider to do this, somewhere we need a delay. + + with fb_sp.FacebookLogin(self) as foo: + foo.do_check( + login_success=self.login_success, + login_error=self.fail, + ) + return + + def fail(self, check, answer=None, *a, **k): + if isinstance(answer, dict) and answer.get('read_stream', None) == 0: + try: + fire( + 'error', + title="Facebook Error", + major="Permission Missing - News Feed", + minor=('Digsby requires permission to access your News Feed to function properly.' + ' This permission was not allowed.') + ) + except Exception: + traceback.print_exc() + self.set_offline(self.Reasons.BAD_PASSWORD) + + def json(self, rpc, webview): + self.webview = webview + method = rpc.pop('method') + args = rpc.pop('params')[0] + if method == 'do_like': + self.do_like(args['post_id'], rpc.pop('id')) + elif method == 'do_unlike': + self.do_unlike(args['post_id'], rpc.pop('id')) + elif method == 'do_dislike': + self.do_dislike(args['post_id'], rpc.pop('id')) + elif method == 'do_undislike': + self.do_undislike(args['post_id'], rpc.pop('id')) + elif method == 'post_comment': + self.post_comment(args['post_id'], args['comment'], rpc.pop('id')) + elif method == 'get_comments': + self.get_comments(args['post_id'], rpc.pop('id')) + elif method == 'remove_comment': + self.remove_comment(args['comment_id'], rpc.pop('id')) + elif method == 'do_grant': + self.do_grant() + elif method == 'initialize_feed': + return self.connection.social_feed.jscall_initialize_feed(webview, rpc.pop('id')) + elif method == 'next_item': + return self.connection.social_feed.jscall_next_item(webview, rpc.pop('id')) + elif method == 'edit_status': + self.edit_status() + elif method == 'hook': + import hooks + hooks.notify(args) + elif method == 'link': + link = args.pop('href') + launchbrowser(link) + elif method == 'get_album': + self.get_album(args['aid'].encode('utf-8'), rpc.pop('id')) + elif method == 'foo': + print 'bar:', len(args['bar']) + elif method == 'notifications_markRead': + self.notifications_markRead(args['notification_ids']) +# elif method == 'stop': +# foo = 'wtf' +# print foo #put breakpoint here; put "D.notify('stop');" in JavaScript. + def get_album(self, aid, id): + self.connection.digsby.query('select pid, src_big, link, caption from photo where aid="%s"' % aid, + success=(lambda *a, **k:self.got_album(id=id, *a, **k)),) + + def notifications_markRead(self, notification_ids): + self.connection.digsby.notifications.markRead(notification_ids=notification_ids) + try: + self.connection.last_alerts.update_notifications() + except AttributeError: + traceback.print_exc() + else: + self.notify('count') + + def got_album(self, album, id): + if isinstance(album, list): + self.Dsuccess(id, album=album) + + def post_comment(self, post_id, comment, id): + comment = comment.encode('utf-8') + post_id = post_id.encode('utf-8') + self.connection.addComment(post_id, comment, + success=(lambda *a, **k:self.append_comment(id=id, *a, **k)), + error=(lambda *a, **k: self.Dexcept(id, *a, **k)) + ) + + def get_comments(self, post_id, id): + post_id = post_id.encode('utf-8') + self.connection.getComments(post_id, + success=(lambda *a, **k:self.render_comments(id=id, *a, **k)), + error=(lambda *a, **k: self.Dexcept(id, *a, **k))) + + def render_comments(self, post_id, count, id): + t = FBIB(self) + context = {} + context['post'] = self.connection.last_stream['post_ids'][post_id] + context['feed'] = self.connection.last_stream + context['comment_count'] = count + comments_html = t.get_html(None, set_dirty=False, + dir=t.get_context()['app'].get_res_dir('base'), + file='comments.py.xml', + context=context) + comment_link_html = t.get_html(None, set_dirty=False, + file='comment_link.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) +# print comments_html +# print comment_link_html +# print count + self.Dsuccess(id, comments_html=comments_html, comment_link_html=comment_link_html, count=count) + + def append_comment(self, post_id, comment_dict, id): + t = FBIB(self) + context = {} + context['post'] = self.connection.last_stream['post_ids'][post_id] + context['comment'] = comment_dict + comment_html = t.get_html(None, set_dirty=False, + file='comment.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) + comment_link_html = t.get_html(None, set_dirty=False, + file='comment_link.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) + comment_post_link = t.get_html(None, set_dirty=False, + file='comment_post_link.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) +# print comment_html +# print comment_link_html +# print comment_post_link + self.Dsuccess(id, comment_html=comment_html, comment_link_html=comment_link_html, + comment_post_link=comment_post_link) + + def do_like(self, post_id, id): + #regen likes block, regen likes link block, send to callback + #regen cached post html + post_id = post_id.encode('utf-8') + self.connection.addLike(post_id, + success=(lambda *a, **k: self.refresh_likes(post_id, id)), + error=(lambda *a, **k: self.Dexcept(id, *a, **k)) + ) + + def do_unlike(self, post_id, id): + post_id = post_id.encode('utf-8') + self.connection.removeLike(post_id, + success=(lambda *a, **k: self.refresh_likes(post_id, id)), + error=(lambda *a, **k: self.Dexcept(id, *a, **k)) + ) + + def do_dislike(self, post_id, id): + comment = DISLIKE.encode('utf-8') + post_id = post_id.encode('utf-8') + self.connection.addComment(post_id, comment, + success=(lambda *a, **k:self.dislike_added(post_id, id)), + error=(lambda *a, **k: self.Dexcept(id, *a, **k)) + ) + + + def do_undislike(self, post_id, id): + try: + comments = self.connection.last_stream.comments[post_id] + comments = [c for c in comments if int(c.fromid) == int(self.uid) and c.text == DISLIKE] + comment_id = comments[0].id + except Exception: + return self.Dexcept(id) + comment_id = comment_id.encode('utf-8') + self.connection.removeComment(comment_id, + success=(lambda *a, **k: self.dislike_removed(post_id, id)), + error=(lambda *a, **k: self.Dexcept(id, *a, **k)) + ) + + def dislike_added(self, post_id, id): + self.refresh_likes(post_id, id, True) + import hooks + hooks.notify('digsby.facebook.dislike_added', post_id) + + def dislike_removed(self, post_id, id): + self.refresh_likes(post_id, id, True) + import hooks + hooks.notify('digsby.facebook.dislike_removed', post_id) + + def refresh_likes(self, post_id, id, dis=False): + #regen likes block, regen likes link block, send to callback + #regen cached post html + dis = (dis and 'dis') or '' + t = FBIB(self) + context = {} + context['post'] = self.connection.last_stream['post_ids'][post_id] + link_html = t.get_html(None, set_dirty=False, + file=dis + 'like_link.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) +# print repr(link_html) + likes_html = t.get_html(None, set_dirty=False, + file=dis + 'likes.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) +# print repr(likes_html) + self.Dsuccess(id, link_html=link_html, likes_html=likes_html) + + def Dsuccess(self, id, **k): + import wx + if not wx.IsMainThread(): raise AssertionError('Dsuccess called from a subthread') + + import simplejson + script = '''Digsby.resultIn(%s);''' % simplejson.dumps({'result':[k], 'error':None, 'id':id}) + self.webview.RunScript(script) + + def Dexcept(self, id, response=None, *a, **k): + if isinstance(response, fberrors.FacebookError): + from .facebookprotocol import not_logged_in + assert not k.pop('msg', False) + if int(response.tag.error_code) == 200: + k['grant'] = 'yes' + elif not_logged_in(response): + self.do_login() #TODO: short circuit the checks here. + self.Derror(id, dict(error_code=response.tag.error_code, error_msg=response.tag.error_msg, **k)) + else: + self.Derror(id, *a, **k) + + def Derror(self, id, error_obj=None, *a, **k): + import wx + if not wx.IsMainThread(): raise AssertionError('Derror called from a subthread') + + import simplejson + if error_obj is None: + error_obj = "error" #need something, and None/null isn't something. + script = '''Digsby.resultIn(%s);''' % simplejson.dumps({'result':None, 'error':error_obj, 'id':id}) + self.webview.RunScript(script) + + def remove_comment(self, comment_id, id): + comment_id = comment_id.encode('utf-8') + self.connection.removeComment(comment_id, + success=(lambda post_id: self.del_comment(comment_id, id, post_id)), + error=(lambda *a, **k: self.Dexcept(id, *a, **k)) + ) + + def del_comment(self, comment_id, id, post_id): + t = FBIB(self) + context = util.Storage() + context['post'] = self.connection.last_stream['post_ids'][post_id] + comment_link_html = t.get_html(None, set_dirty=False, + file='comment_link.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) + comment_post_link = t.get_html(None, set_dirty=False, + file='comment_post_link.py.xml', + dir=t.get_context()['app'].get_res_dir('base'), + context=context) + self.Dsuccess(id, comment_id=comment_id, comment_link_html=comment_link_html, + comment_post_link=comment_post_link) + + def grant_url(self, permission): + url = net.UrlQuery('http://www.facebook.com/connect/prompt_permissions.php', + api_key=self.connection.digsby.api_key, + skipcookie='', + fbconnect='true', + v='1.0', + display='popup', + extern='1', + next=DESKTOP_APP_POPUP, + ext_perm=permission) + + url = net.UrlQuery('http://www.facebook.com/login.php', + next=url, + display='popup', + skipcookie='') + + return url + + def do_grant(self): + from .fbconnectlogin import FBLoginWindow + url = self.grant_url('publish_stream') + + window = FBLoginWindow(self.name, self) + window.LoadURL(url) + + @actions.action() + def OpenHome(self): + self.launchfacebook('') + + @actions.action() + def OpenProfile(self): + self.launchfacebook('profile.php') + + @actions.action() + def OpenFriends(self): + self.launchfacebook('friends.php') + + @actions.action() + def OpenPhotos(self): + self.launchfacebook('photos.php') + + @actions.action() + def OpenMessages(self): + self.launchfacebook('inbox/') + + @actions.action(lambda self: (self.state in (self.Statuses.ONLINE, self.Statuses.CHECKING))) + def edit_status(self): + if common.pref('social.use_global_status', default = False, type = bool): + import wx + wx.GetApp().SetStatusPrompt([self]) + + def on_popup_click_post(self, post): + link = post.get('permalink') + + # posts may not have a permalink. try the first media link + if not link: + attachment = post.get('attachment') + if attachment: + media = attachment.get('media') + if media: + href = media[0].get('href') + if href: + link = href + + # as a last try, just link to the person + if not link: + actor_id = post.get('actor_id') + if actor_id: + profile = self.connection.last_stream['profiles'][int(actor_id)] + link = profile.url + + launchbrowser(link) + + def on_popup_click_notification(self, notification): + link = notification.get('href') + launchbrowser(link) + self.notifications_markRead([notification['notification_id']]) + + @callbacks.callsback + def SetStatusMessage(self, message, callback = None, **k): + message = message.encode('utf-8') + def phase2(permission): + if permission: + do_set() + else: + import wx + wx.CallAfter(get_permission) + + def get_permission(): + from .fbconnectlogin import FBLoginWindow + url = self.grant_url('publish_stream') + window = FBLoginWindow(self.name, self) + def on_close(*a, **k): + do_set() + window.set_callbacks(close=on_close) + window.LoadURL(url) + + def do_set(): + def update(_set_response): + if not message: + if _set_response is True: + self.connection.last_status = None + self.set_dirty() + callback.success() + else: + callback.error() + else: + self.connection.update_status() + callback.success() + self.do_set_status(message, success=update, error=callback.error) + + self.connection.digsby.users.hasAppPermission(ext_perm='publish_stream', success = phase2, error = callback.error) + + @callbacks.callsback + def do_set_status(self, message, callback=None): + if message: + import hooks + callback.success += lambda *a: hooks.notify('digsby.facebook.status_updated', self, message, *a) + self.connection.digsby.users.setStatus(status=message, callback=callback, + status_includes_verb='true') + else: + self.connection.digsby.users.setStatus(clear='1', callback=callback) + + def set_dirty(self): + self._dirty = True + self.notify('dirty') + + def account_post_triggered(self, protocol=None, name=None): + self.get_user_info(success=lambda info: self.do_post_account_trigged(info, protocol, name)) + + def do_post_account_trigged(self, info, protocol=None, name=None): + if protocol is None: + message= (("is using Digsby to manage all %(his)s instant messaging, email, " + "and social network accounts from one application!") % + {'his':action_links.his_her_their(info['gender'])}) + URL = action_links.MERGED_URL() + media=[ + dict(type="image", + src=protocolmeta.web_icon_url("merged_%d" % i), + href=action_links.clicksrc(URL, 'image%d' % i)) + for i in range(5)] + else: + message = "added %(his)s %(acct_type)s account into Digsby!" % {'his':action_links.his_her_their(info['gender']), + 'acct_type':action_links.get_acct_name(protocol)} + URL = action_links.ACCT_BASE(protocol) + media=[dict(type="image", + src=protocolmeta.web_icon_url(protocol), + href=action_links.clicksrc(URL, 'image'))] + self.do_publish_newsfeed(info, message, URL, media) + + def count_triggered(self, type_, number): + self.get_user_info(success=lambda info: self.do_post_count_triggered(info, type_, number)) + + def do_post_count_triggered(self, info, type_, number): + if type_ not in self.MILESTONE_MESSAGES: + return + + message = self.MILESTONE_MESSAGES[type_][0] % {'his':action_links.his_her_their(info['gender']), + 'number':number} + img_type = self.MILESTONE_MESSAGES[type_][1] + + URL = action_links.COUNT_BASE(type_) + media=[dict(type="image", + src=protocolmeta.web_icon_url(img_type), + href=action_links.clicksrc(URL, 'image'))] + + self.do_publish_newsfeed(info, message, URL, media) + + def do_publish_newsfeed(self, info, message, link_base, media): + if info['num_accts'] < 1: + return + + post_title = "%(name_str)s using Digsby to stay connected on %(acct_str)s:" % info + + URL = link_base + self.connection.digsby_ach.stream.publish( + message=message, + action_links=[{'text':"Download Digsby", 'href':action_links.clicksrc(URL, 'DL')}], + attachment=dict( + name=post_title, + href=action_links.clicksrc(URL, 'HREF'), + properties=action_links.get_acct_properties(URL), + media=media + ) + ) + + @callbacks.callsback + def get_user_info(self, callback=None): + self.connection.get_user_name_gender(success = lambda *a: self.handle_user_info(*a, callback=callback), + error = callback.error) + + @callbacks.callsback + def handle_user_info(self, info, callback=None): + name_ = info.get('first_name') + if name_: + name_str = name_ + " is " + else: + name_str = "I'm " + info['name_str'] = name_str + info['gender'] = info.get('sex') + from common import profile + info['num_accts'] = num_accts = len(profile.all_accounts) + info['acct_str'] = ('%d account' if num_accts == 1 else '%d accounts') % num_accts + callback.success(info) + + def AchieveAccountAdded(self, protocol=None, name=None, *a, **k): + super(FacebookAccount, self).AchieveAccountAdded(protocol, name, *a, **k) + self.doAchieve(lambda: self.account_post_triggered(protocol=protocol, name=name)) + + def AchieveAccounts(self, *a, **k): + super(FacebookAccount, self).AchieveAccounts(*a, **k) + self.doAchieve(lambda: self.account_post_triggered()) + + def AchieveThreshold(self, type=None, threshold_passed=None, current_value=None, *a, **k): + super(FacebookAccount, self).AchieveThreshold(type, threshold_passed, current_value, *a, **k) + self.doAchieve(lambda: self.count_triggered(type, threshold_passed)) + + @property + def console(self): + return self.connection.digsby.console() + + @property + def stream(self): + return self.connection.last_stream + +class FBIB(gui_providers.InfoboxProviderBase): + _tmpl = {} + loaders = {} + protocols.advise(asAdapterForTypes=[FacebookAccount], instancesProvide=[gui_interfaces.ICacheableInfoboxHTMLProvider]) + def __init__(self, acct, cache=True): + gui_providers.InfoboxProviderBase.__init__(self) + self.cache = cache + self.acct = acct + + def get_html(self, *a, **k): + if k.pop('set_dirty', True): + self.acct._dirty = False + return gui_providers.InfoboxProviderBase.get_html(self, *a, **k) + + @property + def _dirty(self): + if self.cache and common.pref('facebook.infobox.cache', True): + return self.acct._dirty + else: + return True + + def get_app_context(self, ctxt_class): + return ctxt_class(path.path(__file__).parent.parent, self.acct.protocol) + + def get_context(self): + ctxt = gui_providers.InfoboxProviderBase.get_context(self) + import wx + from .content_translation import get_birthdays + from util.primitives import preserve_newlines + from gui import skin + ctxt.update(feed=self.acct.connection.last_stream, + posts=[], + alerts=self.acct.connection.last_alerts, + status=self.acct.connection.last_status, + get_birthdays=get_birthdays, + skin=skin, + linkify=net.linkify, + wx=wx, + preserve_newlines=preserve_newlines, + strip_whitespace=getattr(self.acct, 'strip_whitespace', False)) + return ctxt + + def get_loader(self, dir=None): + try: + if self.cache and common.pref('facebook.infobox.cache', True): + return FBIB.loaders[dir] + except KeyError: + pass + FBIB.loaders[dir] = gui_providers.InfoboxProviderBase.get_loader(self, dir) + return FBIB.loaders[dir] + +def pstr_to_list(s): + ret = [] + while s: + try: + next, s = upstring(s) + except Exception: + break + else: + ret.append(next) + return ret + +def pstring(s): +# assert type(s) is bytes + assert 0 <= len(s) < 1<<8 + return chr(len(s)) + s + +def upstring(s): + l = ord(s[0]) + return s[1:1+l], s[1+l:] + +def launchbrowser(url): + if url: + import wx + wx.LaunchDefaultBrowser(url) + + +UPGRADE_QUESTION = ''' +Digsby's Facebook integration just got better. It now has the complete +newsfeed including the ability to comment/like and publish back to +Facebook. + +Would you like to post your achievements within Digsby to your Wall? +(eg: Commented on 100 Facebook Posts) +'''.strip() diff --git a/digsby/src/plugins/facebook/fbbenchmark.py b/digsby/src/plugins/facebook/fbbenchmark.py new file mode 100644 index 0000000..4bec86b --- /dev/null +++ b/digsby/src/plugins/facebook/fbbenchmark.py @@ -0,0 +1,37 @@ +from tests.testapp import testapp +from time import clock +import os.path + +def benchmark(fbdata): + from util import Storage + account = Storage( + protocol = 'fb20', + connection = Storage( + last_stream = fbdata['stream'], + last_alerts = fbdata['alerts'], + last_status = fbdata['status'])) + + + from fb20.fbacct import FBIB + + def doit(): + before = clock() + FBIB(account, cache=False).get_html(None) + return clock() - before + + print 'first ', doit() + print 'second', doit() + +def main(): + app = testapp() + + import cPickle + + fbdata_file = os.path.join(os.path.dirname(__file__), r'fbdata.dat') + fbdata = cPickle.loads(open(fbdata_file, 'rb').read()) + + benchmark(fbdata) + + +if __name__ == '__main__': + main() diff --git a/digsby/src/plugins/facebook/fbconnectlogin.py b/digsby/src/plugins/facebook/fbconnectlogin.py new file mode 100644 index 0000000..392d9aa --- /dev/null +++ b/digsby/src/plugins/facebook/fbconnectlogin.py @@ -0,0 +1,399 @@ +from time import clock +from threading import RLock +import wx.webview +from util.primitives.mapping import dictdiff +from .permchecks import PermCheck +import sys +import threading +import types +import facebookapi +import traceback +import util.net as net +from logging import getLogger +import util.callbacks as callbacks +import simplejson +from provider_facebook import facebookapp +log = getLogger('fb20.connect') + +from facebookapi import \ +DIGSBY_API_KEY, DIGSBY_APP_SECRET, DIGSBY_APP_ID, \ +DIGSBY_ACHIEVEMENTS_API_KEY, DIGSBY_ACHIEVEMENTS_APP_SECRET, DIGSBY_ACHIEVEMENTS_APP_ID + +HTTP_FBLOGIN = "http://www.facebook.com/login.php" +INVITE_URL_FAKE = "http://apps.facebook.com/digsbyim/invite.php" + +DIGSBY_LOGIN_URL = 'https://accounts.digsby.com/login.php' + +FACEBOOK_URL = 'http://www.facebook.com/' +ADD_APP_URL = FACEBOOK_URL + 'add.php' + +LOGIN_SUCCESS_PAGE = 'https://apps.facebook.com/digsbyim/index.php?c=skipped' + +FB_CONNECT_OPTS = dict(fbconnect='true', v='1.0', connect_display='popup', return_session='true') + +DPERMS_DA = ['read_stream'] + ['publish_stream'] + ['user_events'] + ['xmpp_login'] + ['manage_notifications'] +DPERMS_D = DPERMS_DA #+ ['publish_stream'] +DPERMS_REQUIRED = ['read_stream'] + ['user_events'] + ['xmpp_login'] + ['manage_notifications'] +APERMS = ['publish_stream'] +APERMS_REQUIRED = APERMS[:] + +DIGSBY_LOGIN_PERMS = net.UrlQuery(HTTP_FBLOGIN, api_key = DIGSBY_API_KEY, **FB_CONNECT_OPTS) +DIGSBY_ACHIEVEMENTS_LOGIN_PERMS = net.UrlQuery(HTTP_FBLOGIN, api_key = DIGSBY_ACHIEVEMENTS_API_KEY, **FB_CONNECT_OPTS) + +def print_fn(func): + def wrapper(*a, **k): + print clock(), func.__name__ + return func(*a, **k) + return wrapper + +class MethodPrinter(type): + def __init__(cls, name, bases, dict): + for k,v in list(dict.items()): #explicit copy + if isinstance(v, types.FunctionType): + setattr(cls, k, print_fn(v)) + super(MethodPrinter, cls).__init__(name, bases, dict) + +class FBProto(object): +# __metaclass__ = MethodPrinter + def __init__(self): + self.lock = RLock() + self._init_apis() + + def _init_apis(self): + self._init_digsby() + self._init_digsby_ach() + + def _init_digsby(self, session_key='', secret=''): + self.digsby = facebookapi.DigsbyAPI(session_key, secret, name='digsby') + + def _init_digsby_ach(self, session_key='', secret=''): + self.digsby_ach = facebookapi.DigsbyAchievementsAPI(session_key, secret, name='digsby_ach') + +class LoginCheck(FBProto): + + def __init__(self, digsby_api=None, digsby_ach_api=None, login_success=None, login_error=None, username=None, do_ach=True, acct=None, ask_ach=None, *a, **k): + FBProto.__init__(self) + if digsby_api is not None: self.digsby = digsby_api + if digsby_ach_api is not None: self.digsby_ach = digsby_ach_api + self.ask_ach = ask_ach if ask_ach is not None else do_ach + self.login_success_cb = login_success + self.login_error_cb = login_error + self.dead = False + self.do_ach = do_ach + self.username = username + self.window = None + + self.acct = acct + + self.try_login = False + #need locks + self.waiting_d_init = False + self.d_init_succ = False + self.waiting_da_init = False + self.da_init_succ = False + self.lock = threading.RLock() + + def _finished(self): + if self.window is not None: + window, self.window = self.window, None + window.close() + self.dead = True + + def login_success(self, *a, **k): + # close window somehow? + self._finished() + self.login_success_cb(*a, **k) + + def login_error(self, *a, **k): + # close window somehow? + self._finished() + self.login_error_cb(*a, **k) + + def initiatiate_check(self, try_login = False): + self.try_login = try_login + if not self.digsby.logged_in: + log.info('digsby api not logged in') + log.info_s('not logged in: api: %r, session: %r', self.digsby.name, self.digsby.session_key) + return self.do_not_logged_in() + if self.do_ach and not self.digsby_ach.logged_in: + log.info('ach api not logged in') + log.info_s('not logged in: api: %r, session: %r', self.digsby_ach.name, self.digsby_ach.session_key) + return self.do_not_logged_in() + + d_p = PermCheck(self.digsby, perms=DPERMS_REQUIRED) + self.waiting_d_init = True + if self.do_ach: + da_p = PermCheck(self.digsby_ach, perms=APERMS_REQUIRED) + self.waiting_da_init = True + da_p.check(success=self.da_init_check_succ, error=self.da_init_check_fail) + d_p.check(success=self.d_init_check_succ, error=self.d_init_check_fail) + + def d_init_check_succ(self, answer): + do_success = False + with self.lock: + self.d_init_succ = True + self.waiting_d_init = False + log.info('d_succ do_ach %r, waiting_da_init %r, da_init_succ %r', self.do_ach, self.waiting_da_init, self.da_init_succ) + if not self.do_ach: + do_success = True + elif not self.waiting_da_init and self.da_init_succ: + do_success = True + if do_success: + self.login_success(self) + + def d_init_check_fail(self, answer): + do_fail = False + with self.lock: + self.d_init_succ = False + self.waiting_d_init = False + log.info('d_fail do_ach %r, waiting_da_init %r, da_init_succ %r', self.do_ach, self.waiting_da_init, self.da_init_succ) + if not self.do_ach: + do_fail = True + elif self.waiting_da_init or ((not self.waiting_da_init) and self.da_init_succ): + do_fail = True + if do_fail: + self.do_not_logged_in(answer) + + def da_init_check_succ(self, answer): + do_success = False + with self.lock: + self.da_init_succ = True + self.waiting_da_init = False + log.info('da_succ waiting_d_init %r, d_init_succ %r', self.waiting_d_init, self.d_init_succ) + if not self.waiting_d_init and self.d_init_succ: + do_success = True + + if do_success: + self.login_success(self) + + def da_init_check_fail(self, answer): + do_fail = False + with self.lock: + self.da_init_succ = False + self.waiting_da_init = False + log.info('waiting_d_init %r, d_init_succ, %r', self.waiting_d_init, self.d_init_succ) + if self.waiting_d_init: + do_fail = True + if do_fail: + self.do_not_logged_in() + + def do_not_logged_in(self, answer=None): + if self.try_login: + wx.CallAfter(self.do_initial_login) + else: + self.login_error(self) + + def do_initial_login(self): + self.continue_login2(LOGIN_SUCCESS_PAGE) + + def continue_login2(self, forward_to): + from common import pref + next = forward_to + ach_next = '' + if self.ask_ach: + ach_next = next + next = ach_url = net.UrlQuery(DIGSBY_ACHIEVEMENTS_LOGIN_PERMS, next=ach_next, req_perms=','.join(APERMS)) + if not pref('facebook.webkitbrowser', default=False, type=bool): + next = net.UrlQuery(LOGIN_SUCCESS_PAGE, next=next) + + digsby_next = next + if self.ask_ach: + d_req_perms = DPERMS_DA + else: + d_req_perms = DPERMS_D + if self.ask_ach: + url = net.UrlQuery(DIGSBY_LOGIN_PERMS, next=next, skipcookie='true', req_perms=','.join(d_req_perms), + cancel_url = ach_url) + else: + url = net.UrlQuery(DIGSBY_LOGIN_PERMS, next=next, skipcookie='true', req_perms=','.join(d_req_perms), + ) + log.info("facebook creating window") + window = self.window = FBLoginWindow(self.username, self.acct) + + def on_nav(e = None, b = None, url=None, *a, **k): + if not window.ie: + e.Skip() + #careful with name collision + url = e.URL + try: + parsed = net.UrlQuery.parse(url) + except Exception: + traceback.print_exc() + else: + log.info('url: %r', url) + session = parsed['query'].get('session') + auth_token = parsed['query'].get('auth_token') + + log.info('has session?: %r', session is not None) + log.info('has auth_token?: %r', auth_token is not None) + + target = None + if auth_token or session: + parsed_base = dict(parsed) + parsed_base.pop('query') + parsed_base.pop('scheme') + digsby_next_parsed = net.UrlQuery.parse(digsby_next) + log.info('digsby_next_parsed: %r', digsby_next_parsed) + digsby_next_parsed['query'].pop('', None) #this happens for urls with no query, which we may/maynot have + digsby_next_parsed.pop('scheme') #http/https is not what interests us + digsby_next_parsed_base = dict(digsby_next_parsed) + digsby_next_parsed_base.pop('query') + ach_next_parsed = net.UrlQuery.parse(ach_next) + log.info('ach_next_parsed: %r', ach_next_parsed) + ach_next_parsed['query'].pop('', None) #this happens for urls with no query, which we may/maynot have + ach_next_parsed.pop('scheme') #http/https is not what interests us + ach_next_parsed_base = dict(ach_next_parsed) + ach_next_parsed_base.pop('query') + + if parsed_base == digsby_next_parsed_base: + target = 'digsby' + elif parsed_base == ach_next_parsed_base: + target = 'digsby_ach' + + if target is None: + return + + if auth_token: + log.info('parsed: %r', parsed) + elif session: + #not sure how to clean this up right now. + log.info('parsed: %r', parsed) + log.info('Parsing url: %r, %r', parsed_base, parsed['query']) + if target == 'digsby': + log.info('got digsby session') + log.info_s('\tsession = %r, auth_token = %r', session, auth_token) + self.digsby.set_session(session) + self.digsby.logged_in = True + if not self.ask_ach and not self.dead: + self.login_success(self, did_login = True) + if not self.dead: + if not pref('facebook.webkitbrowser', default=False, type=bool): + b.Stop() + b.LoadUrl(digsby_next_parsed['query']['next']) + elif target == 'digsby_ach': + log.info('got ach session') + log.info_s('\tsession = %r, auth_token = %r', session, auth_token) + self.digsby_ach.set_session(session) + self.digsby_ach.logged_in = True + if not (self.digsby.logged_in and self.digsby.uid == self.digsby_ach.uid): + #wtf happened if these fail? - IE cookies + return self.login_error(self) + if not self.dead: + return self.login_success(self, did_login = True) + + def on_close(*a, **k): + if not self.dead: + return self.login_success(self, did_login = True) +# self.login_error(self) + if window.webkit: + def on_load(e, browser): + if e.URL == url: + browser.RunScript('document.getElementById("offline_access").checked=true;') + browser.RunScript('document.getElementById("email").value = %s;' % simplejson.dumps(self.username)) + else: + def on_load(*a, **k): + pass + + window.set_callbacks(on_nav, on_load, on_close) + window.clear_cookies() + window.LoadURL(url) + +class FBLoginWindow(object): +# __metaclass__ = MethodPrinter + def clear_cookies(self): + if hasattr(self.browser, 'ClearCookies'): self.browser.ClearCookies() + def __init__(self, account_name = '', acct=None): + fbSize = (720, 640) + if account_name: + account_name = ' (' + account_name + ')' + self._browser_frame = frame = wx.Frame(None, size = fbSize, title = 'Facebook Login' + account_name, name = 'Facebook Login' + account_name) + self.acct = acct + if acct is not None: + bmp = getattr(acct, 'icon', None) + if bmp is not None: + frame.SetIcon(wx.IconFromBitmap(bmp.Resized(32))) + from common import pref + if pref('facebook.webkitbrowser', default=True, type=bool) or sys.platform.startswith('darwin'): + self.webkit = True + self.ie = False + from gui.browser.webkit.webkitwindow import WebKitWindow as Browser + else: + self.webkit = False + self.ie = True + from gui.browser import Browser + frame.CenterOnScreen() + frame.fblogin = self + self.browser = b = Browser(frame) + + if self.webkit: + b.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.on_nav) + b.Bind(wx.webview.EVT_WEBVIEW_LOAD, self._on_load) + elif self.ie: + b.OnNav += self.on_nav + b.OnBeforeNav += self.on_nav + b.OnDoc += self.on_nav #note the difference from _on_load + b.OnDoc += self.on_loaded #note the difference from _on_load + + frame.Bind(wx.EVT_CLOSE, self.on_close) + self._browser = b + self.closed = False + self.have_loaded = False + + def set_callbacks(self, nav=None, load=None, close=None): + self.nav = nav + self.load = load + self.close = close + + def LoadURL(self, url): + if self.webkit: + self._browser.LoadURL(url) + elif self.ie: + self._browser.LoadUrl(url) + + def on_close(self, e): + assert not self.closed + self.closed = True + e.Skip() + if self.close: + self.close(e, self._browser) + + def on_nav(self, e): + if self.ie: + if self.nav: + self.nav(e, self._browser, url=e) + return + e.Skip() + if self.nav: + self.nav(e, self._browser) + + def _on_load(self, e): + e.Skip() + if e.State == wx.webview.WEBVIEW_LOAD_DOC_COMPLETED: + self.have_loaded = True + return self.on_loaded(e) + elif e.State == wx.webview.WEBVIEW_LOAD_FAILED: + if self.have_loaded == False \ + and self._browser_frame is not None \ + and not wx.IsDestroyed(self._browser_frame) \ + and not self._browser_frame.Shown: + self._browser_frame.Close() + + def on_loaded(self, e): + if self._browser_frame is None: + return + + if not self._browser_frame.Shown: + self._browser_frame.Show() + if self.load: +# load, self.load = self.load, None + if self.webkit: + ret = self.load(e, self._browser) + elif self.ie: + ret = self.load(e, self._browser, url=e) +# if not ret: +# self.load = load + + def Close(self): + if self._browser_frame is not None: + bf, self._browser_frame = self._browser_frame, None + bf.Close() diff --git a/digsby/src/plugins/facebook/fberrors.py b/digsby/src/plugins/facebook/fberrors.py new file mode 100644 index 0000000..a07e186 --- /dev/null +++ b/digsby/src/plugins/facebook/fberrors.py @@ -0,0 +1,239 @@ +class FacebookError(Exception): + def __init__(self, tag): + self.tag = tag + self.code = int(self.tag.error_code) if self.tag.error_code else 0 + Exception.__init__(self) + def __repr__(self): + return "<%s %s>" % (type(self).__name__, ' '.join('%s=%r' % i for i in vars(self).items())) + + @property + def message(self): + return self.tag.error_msg + +class FacebookGraphError(FacebookError): + def __init__(self, data): + Exception.__init__(self) + self.__dict__.update(data) + +class FacebookParseFail(FacebookError): + def __init__(self, msg): + self.msg = msg + def __repr__(self): + return repr(self.msg) + + +error_codes = \ +{ +#>>> b = lxml.html.parse('http://fbdevwiki.com/wiki/Error_codes') +#>>> tables = b.xpath("//table[@class='error_codes']") +#>>> sections = b.xpath('//li[@class="toclevel-2"]/a[span]/span[@class="toctext"]/text()') +#>>> res = res = zip(sections, (dict((int(tr.findall('td')[0].text.strip()), (''.join([tr.findall('td')[2].text]) + ' '.join(tr.findall('td')[2].xpath('*/text()'))).strip()) for tr in t.findall('tr')[1:]) for t in tables)) +#>>> for section_name, errors in res: +#... print "#", section_name +#... for k,v in sorted(errors.items()): +#... print "%5d: '%s'," % (k,v) + + +# General Errors + 0: 'Success', + 1: 'An unknown error occurred', + 2: 'Service temporarily unavailable', + 3: 'Unknown method', + 4: 'Application request limit reached', + 5: 'Unauthorized source IP address', + 6: 'This method must run on api.facebook.com', + 7: 'This method must run on api-video.facebook.com', + 8: 'This method requires an HTTPS connection', + 9: 'User is performing too many actions', + 10: 'Application does not have permission for this action', + 11: 'This method is deprecated', + 12: 'This API version is deprecated', +# Parameter Errors + 100: 'Invalid parameter', + 101: 'Invalid API key', + 102: 'Session key invalid or no longer valid', + 103: 'Call_id must be greater than previous', + 104: 'Incorrect signature', + 105: 'The number of parameters exceeded the maximum for this operation', + 110: 'Invalid user id', + 111: 'Invalid user info field', + 112: 'Invalid user field', + 113: 'Invalid email', + 120: 'Invalid album id', + 121: 'Invalid photo id', + 130: 'Invalid feed publication priority', + 140: 'Invalid category', + 141: 'Invalid subcategory', + 142: 'Invalid title', + 143: 'Invalid description', + 144: 'Malformed JSON string', + 150: 'Invalid eid', + 151: 'Unknown city', + 152: 'Invalid page type', +# User Permission Errors + 200: 'Permissions error', + 210: 'User not visible', + 211: 'Application has no developers.', + 220: 'Album or albums not visible', + 221: 'Photo not visible', + 230: 'Permissions disallow message to user', + 240: 'Desktop applications cannot set FBML for other users', + 250: 'Updating status requires the extended permission status_update', + 260: 'Modifying existing photos requires the extended permission photo_upload', + 270: 'Permissions disallow sms to user.', + 280: 'Creating and modifying listings requires the extended permission create_listing', + 281: 'Managing notes requires the extended permission create_note.', + 282: 'Managing shared items requires the extended permission share_item.', + 290: 'Creating and modifying events requires the extended permission create_event', + 291: 'FBML Template isn\'t owned by your application.', + 292: 'An application is only allowed to send LiveMessages to users who have accepted the TOS for that application.', + 299: 'RSVPing to events requires the extended permission create_rsvp', +# Data Editing Errors + 300: 'Edit failure', + 310: 'User data edit failure', + 320: 'Photo edit failure', + 321: 'Album is full', + 322: 'Invalid photo tag subject', + 323: 'Cannot tag photo already visible on Facebook', + 324: 'Missing or invalid image file', + 325: 'Too many unapproved photos pending', + 326: 'Too many photo tags pending', + 327: 'Input array contains a photo not in the album', + 328: 'Input array has too few photos', + 329: 'Template data must be a JSON-encoded dictionary, of the form {\'key-1\': \'value-1\', \'key-2\': \'value-2\', ...}', + 330: 'Failed to set markup', + 340: 'Feed publication request limit reached', + 341: 'Feed action request limit reached', + 342: 'Feed story title can have at most one href anchor', + 343: 'Feed story title is too long', + 344: 'Feed story title can have at most one fb:userlink and must be of the user whose action is being reported', + 345: 'Feed story title rendered as blank', + 346: 'Feed story body is too long', + 347: 'Feed story photo could not be accessed or proxied', + 348: 'Feed story photo link invalid', + 350: 'Video file is too large', + 351: 'Video file was corrupt or invalid', + 352: 'Video file format is not supported', + 360: 'Feed story title_data argument was not a valid JSON-encoded array', + 361: 'Feed story title template either missing required parameters, or did not have all parameters defined in title_data array', + 362: 'Feed story body_data argument was not a valid JSON-encoded array', + 363: 'Feed story body template either missing required parameters, or did not have all parameters defined in body_data array', + 364: 'Feed story photos could not be retrieved, or bad image links were provided', + 365: 'The template for this story does not match any templates registered for this application', + 366: 'One or more of the target ids for this story are invalid. They must all be ids of friends of the acting user', + 370: 'The email address you provided is not a valid email address', + 371: 'The email address you provided belongs to an existing account', + 372: 'The birthday provided is not valid', + 373: 'The password provided is too short or weak', + 374: 'The login credential you provided is invalid.', + 375: 'Failed to send confirmation message to the specified login credential.', + 376: 'The login credential you provided belongs to an existing account', + 377: 'Sorry, we were unable to process your registration.', + 378: 'Your password cannot be blank. Please try another.', + 379: 'Your password contains invalid characters. Please try another.', + 380: 'Your password must be at least 6 characters long. Please try another.', + 381: 'Your password should be more secure. Please try another.', + 382: 'Our automated system will not approve this name.', + 383: 'You must fill in all of the fields.', + 384: 'You must indicate your full birthday to register.', + 385: 'Please enter a valid email address.', + 386: 'The email address you entered has been disabled. Please contact disabled@facebook.com with any questions.', + 387: 'There was an error with your registration. Please try registering again.', + 388: 'Please select either Male or Female.', +# Authentication Errors + 400: 'Invalid email address', + 401: 'Invalid username or password', + 402: 'Invalid application auth sig', + 403: 'Invalid timestamp for authentication', +# Session Errors + 450: 'Session key specified has passed its expiration time', + 451: 'Session key specified cannot be used to call this method', + 452: 'Session key invalid. This could be because the session key has an incorrect format, or because the user has revoked this session', + 453: 'A session key is required for calling this method', + 454: 'A session key must be specified when request is signed with a session secret', + 455: 'A session secret is not permitted to be used with this type of session key', +# Application Messaging Errors + 500: 'Message contains banned content', + 501: 'Missing message body', + 502: 'Message is too long', + 503: 'User has sent too many messages', + 504: 'Invalid reply thread id', + 505: 'Invalid message recipient', + 510: 'Invalid poke recipient', + 511: 'There is a poke already outstanding', + 512: 'User is poking too fast', +# FQL Errors + 600: 'An unknown error occurred in FQL', + 601: 'Error while parsing FQL statement', + 602: 'The field you requested does not exist', + 603: 'The table you requested does not exist', + 604: 'Your statement is not indexable', + 605: 'The function you called does not exist', + 606: 'Wrong number of arguments passed into the function', + 607: 'FQL field specified is invalid in this context.', + 608: 'An invalid session was specified', +# Ref Errors + 700: 'Unknown failure in storing ref data. Please try again.', +# Application Integration Errors + 750: 'Unknown Facebook application integration failure.', + 751: 'Fetch from remote site failed.', + 752: 'Application returned no data. This may be expected or represent a connectivity error.', + 753: 'Application returned user had invalid permissions to complete the operation.', + 754: 'Application returned data, but no matching tag found. This may be expected.', + 755: 'The database for this object failed.', +# Data Store API Errors + 800: 'Unknown data store API error', + 801: 'Invalid operation', + 802: 'Data store allowable quota was exceeded', + 803: 'Specified object cannot be found', + 804: 'Specified object already exists', + 805: 'A database error occurred. Please try again', + 806: 'Unable to add FBML template to template database. Please try again.', + 807: 'No active template bundle with that ID or handle exists.', + 808: 'Template bundle handles must contain less than or equal to 32 characters.', + 809: 'Template bundle handle already identifies a previously registered template bundle, and handles can not be reused.', + 810: 'Application has too many active template bundles, and some must be deactivated before new ones can be registered.', + 811: 'One of more of the supplied action links was improperly formatted.', + 812: 'One or more of your templates is using a token reserved by Facebook, such as {*mp3*} or {*video*}.', +# Mobile/SMS Errors + 850: 'Invalid sms session.', + 851: 'Invalid sms message length.', + 852: 'Over user daily sms quota.', + 853: 'Unable to send sms to user at this time.', + 854: 'Over application daily sms quota/rate limit.', + 855: 'User is not registered for Facebook Mobile Texts', + 856: 'User has SMS notifications turned off', + 857: 'SMS application disallowed by mobile operator', +# Application Information Errors + 900: 'No such application exists.', +# Batch API Errors + 950: 'Each batch API can not contain more than 20 items', + 951: 'begin_batch already called, please make sure to call end_batch first.', + 952: 'end_batch called before begin_batch.', + 953: 'This method is not allowed in batch mode.', +# Events API Errors + 1000: 'Invalid time for an event.', +# Info Section Errors + 1050: 'No information has been set for this user', + 1051: 'Setting info failed. Check the formatting of your info fields.', +# LiveMessage Errors + 1100: 'An error occurred while sending the LiveMessage.', + 1101: 'The event_name parameter must be no longer than 128 bytes.', + 1102: 'The message parameter must be no longer than 1024 bytes.', +# Chat Errors + 1200: 'An error occurred while sending the message.', +# Facebook Page Errors + 1201: 'You have created too many pages', +# Facebook Links Errors + 1500: 'The url you supplied is invalid', +# Facebook Notes Errors + 1600: 'The user does not have permission to modify this note.', +# Comment Errors + 1700: 'An unknown error has occurred.', + 1701: 'The specified post was too long.', + 1702: 'The comments database is down.', + 1703: 'The specified xid is not valid. xids can only contain letters, numbers, and underscores', + 1704: 'The specified user is not a user of this application', + 1705: 'There was an error during posting.', + +} diff --git a/digsby/src/plugins/facebook/fbgui.py b/digsby/src/plugins/facebook/fbgui.py new file mode 100644 index 0000000..4761973 --- /dev/null +++ b/digsby/src/plugins/facebook/fbgui.py @@ -0,0 +1,66 @@ +import wx + +from gui import skin +import gui.toolbox as toolbox +import gui.textutil as textutil + +import digsby_service_editor.default_ui as default_ui + +class FacebookAchievementsDialog(toolbox.UpgradeDialog): + faq_link_label = _('Learn More') + faq_link_url = 'http://wiki.digsby.com/doku.php?id=faq#q32' + + def __init__(self, parent, title, message): + icon = skin.get('serviceicons.facebook', None) + super(FacebookAchievementsDialog, self).__init__(parent, title, + message=message, + icon=icon, + ok_caption = _('Post Achievements'), + cancel_caption = _('No Thanks'), + link=(self.faq_link_label, self.faq_link_url)) + + +def construct_basic_subpanel_social(panel, SP, MSP, MSC): + return True + +def construct_advanced_subpanel_social(panel, SP, MSP, MSC): + sz = wx.BoxSizer(wx.VERTICAL) + alerts_lbl = wx.StaticText(panel, -1, _("Show Alerts:"), style = wx.ALIGN_LEFT) + alerts_lbl.Font = textutil.CopyFont(alerts_lbl.Font, weight = wx.BOLD) + + labels = [_('Messages'), + _('Pokes'), + _('Shares'), + _('Friend Requests'), + _('Group Invitations'), + _('Event Invitations'), + _('Notifications')] + + sz.Add(alerts_lbl, 0, wx.BOTTOM, alerts_lbl.GetDefaultBorder()) + + alerts = panel.controls['alerts'] = [] + + values = getattr(SP, 'filters', {}).get('alerts', None) + if values is None: + values = [True] * len(labels) + + for i, label in enumerate(labels): + chk = wx.CheckBox(panel, label = label) + chk.Value = values[i] + alerts.append(chk) + default_ui.ezadd(sz, chk, 0, wx.ALL) + + fx = panel.controls['advanced_sz'] + fx.Add(sz, (fx.row, 1), (6, 4)) + fx.row += 6 + return True + +def extract_basic_subpanel_social(panel, info, SP, MSP, MSC): + info['post_ach_all'] = False + + return True + +def extract_advanced_subpanel_social(panel, info, SP, MSP, MSC): + info['filters'] = {'alerts' : [x.Value for x in panel.controls['alerts']]} + info['informed_ach'] = True + return True diff --git a/digsby/src/plugins/facebook/fbutil.py b/digsby/src/plugins/facebook/fbutil.py new file mode 100644 index 0000000..0749339 --- /dev/null +++ b/digsby/src/plugins/facebook/fbutil.py @@ -0,0 +1,59 @@ +from hashlib import md5 +from util.primitives.error_handling import traceguard, try_this +import logging + +def signature(secret, **kw): + return md5("".join('='.join((str(k),str(v))) + for (k,v) in sorted(kw.items()))+secret).hexdigest() + +def trim_profiles(stream_dict): + post_profiles = extract_profile_ids(stream_dict) + trimmed = set() + for key in stream_dict['profiles']: + if key not in post_profiles: + trimmed.add(key) + profiles_len = len(stream_dict['profiles']) + for key in trimmed: + stream_dict['profiles'].pop(key) + if len(trimmed): + log = logging.getLogger('fbutil').info + else: + log = logging.getLogger('fbutil').debug + log('trimmed %d of %d profiles, %d remain', len(trimmed), profiles_len, len(stream_dict['profiles'])) + log('trimmed %r', trimmed) + log('profiles %r', stream_dict['profiles'].keys()) + +POST_ID_NAMES = frozenset(('viewer_id', 'actor_id', 'target_id', 'source_id')) + +IGNORED = (KeyError, IndexError, TypeError) + +def extract_profile_ids(stream): + ret = set() + for post in stream['posts']: + """(SELECT viewer_id FROM #posts)""" + """(SELECT actor_id FROM #posts)""" + """(SELECT target_id FROM #posts)""" + """(SELECT source_id FROM #posts)""" + for key in POST_ID_NAMES: + with traceguard: + try_this(lambda: ret.add(post[key]), ignore = IGNORED) + """(SELECT likes.sample FROM #posts)""" + with traceguard: + try_this(lambda: ret.update(post['likes']['sample']), ignore = IGNORED) + """(SELECT likes.friends FROM #posts)""" + with traceguard: + try_this(lambda: ret.update(post['likes']['friends']), ignore = IGNORED) + + """(SELECT fromid FROM #comments)""" + for post_comments in stream['comments'].values(): + for comment in post_comments: + with traceguard: + try_this(lambda: ret.add(comment['fromid']), ignore = IGNORED) + + """(SELECT sender_id FROM #notifications)""" + for notification in stream.get('notifications', []): + with traceguard: + try_this(lambda: ret.add(notification['sender_id']), ignore = IGNORED) + + return ret + diff --git a/digsby/src/plugins/facebook/graphapi.py b/digsby/src/plugins/facebook/graphapi.py new file mode 100644 index 0000000..7b068b6 --- /dev/null +++ b/digsby/src/plugins/facebook/graphapi.py @@ -0,0 +1,156 @@ +''' +Created on Apr 9, 2012 + +@author: Christopher +''' + +from util.net import UrlQuery +from facebook.facebookapi import DIGSBY_APP_ID +from facebook.fbconnectlogin import LOGIN_SUCCESS_PAGE +from util.primitives.functional import AttrChain +from util.callbacks import callsback, callback_adapter +import common.asynchttp +from facebookapi import parse_response +from facebook.facebookapi import prepare_args +from facebook.facebookapi import call_urllib2 +from logging import getLogger +import time +import urllib +log = getLogger("graphapi") + + +def build_oauth_dialog_url(perms=None): + params = dict(client_id=DIGSBY_APP_ID, + redirect_uri=LOGIN_SUCCESS_PAGE, + response_type='token', + display='popup') + if perms is not None: + params['scope'] = ','.join(perms) + return UrlQuery('https://www.facebook.com/dialog/oauth/?', + **params) + + +class GraphAPI(AttrChain): + graph_http = 'https://' + graph_endpoint = 'graph.facebook.com' + legacy_endpoint = 'api.facebook.com/method' + + def __init__(self, access_token=None, *a, **k): + self.access_token = access_token + self.logged_in = False + self.mode = 'async' + self.httpmaster = common.asynchttp.HttpMaster() + super(GraphAPI, self).__init__(*a, **k) + + def copy(self): + ret = type(self)(**self.__dict__) + ret.uid = self.uid + return ret + + def console(self): + new = self.copy() + new.mode = "console" + return new + + @callsback + def _do_call(self, endpoint, method, callback=None, **k): + k = prepare_args(k) + if self.access_token: + k['access_token'] = self.access_token.encode('utf8') + url = UrlQuery(self.graph_http + endpoint + '/' + method, **k) + log.info("calling method: %s", method) + log.info_s("calling: %s with %r", url, k) + if self.mode == 'async': + return self.call_asynchttp(url, None, callback=callback) + elif self.mode == 'console': + return call_urllib2(url, None) + elif self.mode == 'threaded': + from util.threads import threaded + return threaded(call_urllib2)(url, None, callback=callback) + else: + return callback_adapter(call_urllib2)(url, None, callback=callback) + + def _call_id(self): + return int(time.time() * 1000) + + @callsback + def batch(self, *a, **k): + callback = k.pop('callback') + k['batch'] = list(a) + k = prepare_args(k) + if self.access_token: + k['access_token'] = self.access_token.encode('utf-8') + url = UrlQuery(self.graph_http + self.graph_endpoint) + data = self.prepare_values(k) + if self.mode == 'async': + return self.call_asynchttp(url, data, callback=callback) + elif self.mode == 'console': + return call_urllib2(url, data) + elif self.mode == 'threaded': + from util.threads import threaded + return threaded(call_urllib2)(url, data, callback=callback) + else: + return callback_adapter(call_urllib2)(url, data, callback=callback) + + def prepare_call(self, method, **k): + k['api_key'] = DIGSBY_APP_ID + k['method'] = method + k['v'] = '1.0' + #new + k['access_token'] = self.access_token.encode('utf-8') + k['call_id'] = str(self._call_id()) + k = prepare_args(k) + return self.prepare_values(k) + + def prepare_values(self, k): + assert all(isinstance(val, bytes) for val in k.values()) + return urllib.urlencode(k) + + @staticmethod + def GET(relative_url): + return dict(method='GET', relative_url=relative_url) + + @callsback + def __call__(self, method, callback=None, **k): + #TODO: make this not a hack + if method.endswith('legacy'): + method = method[:-(len('legacy') + 1)] + endpoint = self.legacy_endpoint + else: + endpoint = self.graph_endpoint + return self._do_call(endpoint=endpoint, + method=method, + callback=callback, + **k) + + def query(self, query, **k): + ''' + convenience method for executing fql queries + ''' + return self.fql.query(query=query, **k) + + @callsback + def multiquery(self, callback=None, **k): + assert self.access_token + return self.fql.multiquery(callback=callback, queries=k) + + @callsback + def call_asynchttp(self, url, data, callback=None): + return self.httpmaster.open(url, data=data, + headers={'Accept-Encoding': 'bzip2;q=1.0, gzip;q=0.8, compress;q=0.7, identity;q=0.1'}, + success=(lambda *a, **k: + callback_adapter(parse_response, do_return=False)(callback=callback, *a, **k)), + error = callback.error, + timeout = callback.timeout) + + +class LegacyRESTAPI(GraphAPI): + + @callsback + def __call__(self, method, callback=None, **k): + k['format'] = 'JSON' + endpoint = self.legacy_endpoint + return self._do_call(endpoint=endpoint, + method=method, + callback=callback, + **k) diff --git a/digsby/src/plugins/facebook/info.yaml b/digsby/src/plugins/facebook/info.yaml new file mode 100644 index 0000000..d6e3b1a --- /dev/null +++ b/digsby/src/plugins/facebook/info.yaml @@ -0,0 +1,83 @@ +name: !_ "Facebook" +shortname: facebook +name_truncated: face +service_provider: &service_provider facebook +service_name: !_ 'News Feed' +component_type: social +popularity: 807 +type: service_component +form: social +username_desc: !_ "Email Address" +newuser_url: http://www.facebook.com/r.php +path: facebook.fbacct.FacebookAccount +needs_password: false +whitelist_opts: ['enabled', 'uid', 'secrets', 'filters', 'post_ach_all', 'informed_ach', 'show_hidden_posts', 'preferred_filter_key', 'alias'] +skin: + serviceicons: + facebook: serviceicons/facebook.png + FaceBookIcons: + Like: res/facebookicons/like.png + Dislike: res/facebookicons/dislike.png + notifications_icon: res/facebookicons/notifications_icon.png + +infobox: + maxheight: True + hosted: True + +show_alerts_label : !_ "Show Alerts" +needs_alerts : yes +alerts: + - 'Messages' + - 'Pokes' + - 'Shares' + - 'Friend Requests' + - 'Group Invitations' + - 'Event Invitations' + - 'Notifications' + +defaults: + alias: ~ + informed_ach: no + post_ach_all: yes + show_hidden_posts: no + preferred_filter_key: 1 #live feed + +#more_details: +# - type: bool +# store: show_hidden_posts +# label: Show Hidden Posts +# default: no +# - type: enum +# store: preferred_filter_key +# elements: +# - Default Feed +# - Live Feed +# - News Feed (not in the right order) +# label: Preferred Filter Key +# help: http://forum.digsby.com/viewtopic.php?id=5547 + +new_details: + - type: meta + store: informed_ach + value: yes + +basic_details: #basic to the account (read: important) + - type: bool + store: post_ach_all + label: !_ Post achievements to my Wall + default: yes + help: http://wiki.digsby.com/doku.php?id=faq#q32 + +entry_points: + digsby.services.edit.advanced.extract_sub.social: + *service_provider: facebook.fbgui:extract_advanced_subpanel_social + digsby.services.edit.basic.extract_sub.social: + *service_provider: facebook.fbgui:extract_basic_subpanel_social + digsby.services.edit.advanced.construct_sub.social: + *service_provider: facebook.fbgui:construct_advanced_subpanel_social + digsby.services.edit.basic.construct_sub.social: + *service_provider: facebook.fbgui:construct_basic_subpanel_social + + digsby.component.social: + *service_provider: facebook.fbacct:FacebookAccount + diff --git a/digsby/src/plugins/facebook/notifications.yaml b/digsby/src/plugins/facebook/notifications.yaml new file mode 100644 index 0000000..2c1cc1c --- /dev/null +++ b/digsby/src/plugins/facebook/notifications.yaml @@ -0,0 +1,41 @@ +- facebook_alert: + description: !_ 'Facebook Alert' + header: "${title}" + minor: '${msg}' + icon: 'skin:serviceicons.facebook' + max_lines: 6 + default: + reaction: ['Popup'] + +- facebook_notifications: + description: !_ 'Facebook Notification' + header: !_ 'Facebook Notification' + minor: ${notification['title_text']} + icon: 'skin:serviceicons.facebook' + pages: 'notifications' + max_lines: 5 + update: paged + default: + reaction: ['Popup'] + +- facebook_newsfeed: + description: !_ 'Facebook Newsfeed' + header: |- + ${(profiles[int(post['actor_id'])].name + u' \xbb ' + profiles[int(post['target_id'])].name + if ('source_id' in post and 'actor_id' in post and 'target_id' in post and post.target_id and post.source_id and post.actor_id and int(post.target_id) != int(post.actor_id)) + else profiles[int(post['actor_id'])].name)} + minor: |- + ${('message' in post and post.message.strip() + or ('attachment' in post and post.attachment and + (post.attachment.get('name', '').strip() or + post.attachment.get('caption', '').strip() or + (post.attachment.get('description') and + scrape_clean(post.attachment.get('description', '').strip()) or + (post.attachment.get('media') and any(media.get('type') == 'photo' for media in post.attachment.get('media')) and 'Uploaded photos') + ) or '')))} + icon: 'skin:serviceicons.facebook' + pages: 'posts' + max_lines: 5 + update: paged + default: + reaction: ['Popup'] diff --git a/digsby/src/plugins/facebook/oauth2login.py b/digsby/src/plugins/facebook/oauth2login.py new file mode 100644 index 0000000..8aa71e4 --- /dev/null +++ b/digsby/src/plugins/facebook/oauth2login.py @@ -0,0 +1,167 @@ +''' +Created on Apr 10, 2012 + +@author: Christopher +''' +from facebook.fbconnectlogin import FBLoginWindow +import util.net as net +import traceback +import simplejson +from facebook.graphapi import build_oauth_dialog_url +from facebook.facebookapi import DIGSBY_APP_ID +from facebook.fbconnectlogin import DPERMS_D +from logging import getLogger +import wx +from common import profile +from permchecks import PermCheck +from fbconnectlogin import DPERMS_REQUIRED +from util import callbacks +log = getLogger('fb.oauth2login') + + +class GraphPermCheck(PermCheck): + @callbacks.callsback + def check(self, callback=None): + log.critical('checking with callback: %r', callback) + self.callback = callback + if not self.perms: + return self.callback.success({}) + self.api.batch(self.api.GET('me/permissions'), + self.api.GET('me'), + self.api.GET('app'), + success=self.check_success, error=self.check_error) + + def check_success(self, ret): + log.debug('check_success: %r', ret) + try: + perms = simplejson.loads(ret[0]['body'])['data'] + me = simplejson.loads(ret[1]['body']) + app = simplejson.loads(ret[2]['body']) + if app['id'] != DIGSBY_APP_ID: + log.debug('app id has changed') + return self.callback.error(None) + self.api._me = me + self.api.uid = me['id'] + return super(GraphPermCheck, self).check_success(perms) + except Exception as e: + log.debug('check_success Exception: %r', e) + return self.check_error(e) + + +class LoginCheck(object): + _windows = {} + + @classmethod + def window_for_username(cls, username): + return cls._windows.get(username, None) + + def __init__(self, api, login_success=None, login_error=None, username=None, acct=None, *a, **k): + self.dead = False + self.api = api + self.access_token = getattr(self.api, 'access_token', None) + self.login_success_cb = login_success + self.login_error_cb = login_error + self.username = username + self.window = None + self.acct = acct + + def _finished(self): + self.dead = True + log.debug('_finished, self.window = %r', self.window) + if self.window is not None: + window, self.window, _cls_window = self.window, None, self._windows.pop(self.username, None) + window.Close() + + def login_success(self, *a, **k): + log.debug('login_success: %r, %r', a, k) + self.api.logged_in = True + self._finished() + log.debug('login_success_cb: %r', self.login_success_cb) + self.login_success_cb(*a, **k) + + def login_error(self, *a, **k): + log.debug('login_error: %r, %r', a, k) + self.api.access_token = self.access_token = None + self.access_token = None + self._finished() + log.debug('login_error_cb: %r', self.login_error_cb) + self.login_error_cb(*a, **k) + + def initiatiate_check(self, try_login = False): + log.debug('initiatiate_check: try_login: %r', try_login) + self.try_login = try_login + d_p = GraphPermCheck(self.api, perms=DPERMS_REQUIRED) + d_p.check(success=self._init_check_succ, error=self._init_check_fail) + + def _init_check_succ(self, answer): + self.login_success(self) + + def _init_check_fail(self, answer): + self.do_not_logged_in(answer) + + def _verify_check_succ(self, answer): + self.login_success(self, did_login = True) + + def _verify_check_fail(self, answer): + self.login_error(self) + + def do_not_logged_in(self, answer=None): + log.debug('do_not_logged_in: answer: %r', answer) + if self.try_login: + wx.CallAfter(self.start) + else: + self.login_error(self) + + def start(self): + log.debug('start') + + window = self.window = type(self).window_for_username(self.username) + if window is None: + self._windows[self.username] = window = self.window = FBLoginWindow(self.username) + + url = build_oauth_dialog_url(DPERMS_D) + + def on_nav(e = None, b = None, url = None, *a, **k): + if not window.ie or url is None: + e.Skip() + #careful with name collision + url = e.URL + + log.debug('on_nav: e: %r, b: %r, url: %r, a: %r, k: %r', e, b, url, a, k) + try: + parsed = net.UrlQuery.parse(url) + except Exception: + traceback.print_exc() + else: + if not parsed['fragment']: + return + frag_parsed = net.WebFormData.parse(parsed['fragment']) + access_token = frag_parsed.get('access_token') + log.debug_s("access_token from url: %r", access_token) + if not access_token: + return + + log.debug_s("self.access_token: %r", self.access_token) + if self.access_token != access_token: + self.api.access_token = self.access_token = access_token + + d_p = GraphPermCheck(self.api, perms=DPERMS_REQUIRED) + return d_p.check(success=self._verify_check_succ, error=self._verify_check_fail) + + def on_close(*a, **k): + log.debug('on_close: %r, %r', a, k) + log.debug('on_close: dead: %r, access_token: %r', self.dead, self.access_token) + if not self.dead: + if self.access_token is None: + self.login_error(self) + + on_load = lambda *a, **k: None + if window.webkit: + def on_load(e, browser): + pass +# if e.URL == url: +# browser.RunScript('document.getElementById("email").value = %s;' % simplejson.dumps(self.username)) + + window.set_callbacks(on_nav, on_load, on_close) + window.clear_cookies() + window.LoadURL(url) diff --git a/digsby/src/plugins/facebook/objects.py b/digsby/src/plugins/facebook/objects.py new file mode 100644 index 0000000..7d2b035 --- /dev/null +++ b/digsby/src/plugins/facebook/objects.py @@ -0,0 +1,123 @@ +from logging import getLogger +log = getLogger("facebook.objects") + +class Alerts(object): + stuff = ['num_msgs', 'msgs_time', 'num_pokes', 'pokes_time', + 'num_shares', 'shares_time', 'friend_requests', + 'group_invites', 'event_invites', 'notifications'] + + urls = dict(msgs = 'http://www.facebook.com/inbox/', + pokes = 'http://www.facebook.com/home.php', + shares = 'http://www.facebook.com/posted.php', + friend_requests = 'http://www.facebook.com/reqs.php', + group_invites = 'http://www.facebook.com/reqs.php#group', + event_invites = 'http://www.facebook.com/?sk=events', + notifications = 'http://www.facebook.com/notifications.php') + + def __init__(self, fb=None, notifications_get_xml=None, notifications = None): + log.debug("type(notifications_get_xml) ==> %r", type(notifications_get_xml)) + self.fb = fb + + if notifications_get_xml is not None: + log.debug("here") + t = notifications_get_xml + self.num_msgs = int(t.messages.unread) + self.msgs_time = int(t.messages.most_recent) + self.num_pokes = int(t.pokes.unread) + self.pokes_time = int(t.pokes.most_recent) + self.num_shares = int(t.shares.unread) + self.shares_time = int(t.shares.most_recent) + self.friend_requests = set(int(uid) for uid in t.friend_requests) + self.group_invites = set(int(gid) for gid in t.group_invites) + self.event_invites = set(int(eid) for eid in t.event_invites) + + else: + + self.num_msgs = 0 + self.msgs_time = 0 + self.num_pokes = 0 + self.pokes_time = 0 + self.num_shares = 0 + self.shares_time = 0 + self.friend_requests = set() + self.group_invites = set() + self.event_invites = set() + + log.debug("there") + self.update_notifications(notifications) + + def update_notifications(self, notifications=None): + if notifications is None: + notifications = [] + self.notifications = set(n['notification_id'] for n in notifications + if ((n.get('title_html', None) is not None) + and (int(n['is_unread']) == 1))) + + def __repr__(self): + from pprint import pformat + s = pformat([(a, getattr(self, a)) for a in self.stuff]) + + return '' % s + + def __sub__(self, other): + ret = Alerts(self.fb) + for attr in self.stuff: + setattr(ret, attr, getattr(self, attr) - getattr(other, attr)) + return ret + + def __cmp__(self, other): + if type(self) != type(other): + return 1 + for attr in self.stuff: + if getattr(self, attr) != getattr(other, attr): + return (self.num_all - other.num_all) or 1 + return 0 + + @property + def num_all(self): + return sum([ + self.num_msgs, + self.num_pokes, + self.num_shares, + len(self.friend_requests), + len(self.group_invites), + len(self.event_invites), + len(self.notifications) + ]) + + @property + def count(self): + return sum([ + self['num_msgs'], + self['num_pokes'], + self['num_shares'], + self['num_friend_requests'], + self['num_group_invites'], + self['num_event_invites'], + self['num_notifications'] + ]) + + @property + def num_friend_requests(self): + return len(self.friend_requests) + + @property + def num_group_invites(self): + return len(self.group_invites) + + @property + def num_event_invites(self): + return len(self.event_invites) + + @property + def num_notifications(self): + return len(self.notifications) + + def __nonzero__(self): + return any(getattr(self, attr) for attr in self.stuff) + + def __getitem__(self, key): + if not hasattr(self, 'fb') or self.fb is None: + raise KeyError(key) + return self.fb.filters['alerts'][key] * getattr(self, key) + diff --git a/digsby/src/plugins/facebook/permchecks.py b/digsby/src/plugins/facebook/permchecks.py new file mode 100644 index 0000000..8a89633 --- /dev/null +++ b/digsby/src/plugins/facebook/permchecks.py @@ -0,0 +1,66 @@ +import util.callbacks as callbacks +from util.threads.timeout_thread import call_later +from .fberrors import FacebookError +import traceback + +import logging +log = logging.getLogger('facebook.permchecks') + +PERM_QUERY = "SELECT %s FROM permissions WHERE uid=%d" + +class PermCheck(object): + max_errcount = 3 + def __init__(self, api, perms=None): + self.api = api + self.perms = perms or [] + self.errcount = 0 + + @callbacks.callsback + def check(self, callback=None): + log.critical('checking with callback: %r', callback) + self.callback = callback + if not self.perms: + return self.callback.success({}) + self.api.query(PERM_QUERY % (','.join(self.perms), int(self.api.uid)), + success=self.check_success, error=self.check_error) + + def check_success(self, ret): + log.info('check_success(%r)', ret) + if not ret: #200 (http) empty, fix your damned API, Facebook! #4465, b80854 + return self.check_error(ret) + try: + perms = ret[0] + except (TypeError, ValueError, AttributeError, KeyError): #200 empty is usually {}, KeyError here. + traceback.print_exc() + return self.check_error(ret) + try: + perms = dict(perms) + log.info('perms: %r', perms) + except (TypeError, ValueError): + traceback.print_exc() + return self.check_error(ret) + if not all(perms.get(perm) for perm in self.perms): + log.info('not all') + return self.not_all_perms(perms) + return self.callback.success(perms) + + def not_all_perms(self, perms): + return self.callback.error(perms) + + def not_logged_in(self, ret): + return self.callback.error(ret) + + def check_error(self, ret, *a): + log.info("check_error: %r, %r", ret, a) + if isinstance(ret, FacebookError): + from .facebookprotocol import not_logged_in + if not_logged_in(ret): + log.info_s('not logged in: api: %r, session: %r', self.api.name, self.api.session_key) + return self.not_logged_in(ret) + self.errcount += 1 + if self.errcount >= self.max_errcount: + return self.callback.error(ret) + if self.api.mode == 'async': + return call_later(1, self.check, callback = self.callback) + else: + return self.check(callback = self.callback) diff --git a/digsby/src/plugins/facebook/res/ajax-loader.gif b/digsby/src/plugins/facebook/res/ajax-loader.gif new file mode 100644 index 0000000..5b33f7e Binary files /dev/null and b/digsby/src/plugins/facebook/res/ajax-loader.gif differ diff --git a/digsby/src/plugins/facebook/res/alerts.py.xml b/digsby/src/plugins/facebook/res/alerts.py.xml new file mode 100644 index 0000000..2805c7c --- /dev/null +++ b/digsby/src/plugins/facebook/res/alerts.py.xml @@ -0,0 +1,27 @@ + +
${{_('Alerts')}}
+ +
${{_('No Alerts')}}
+ + + + + + + + + +
+ + + + + + + +
+ +
+
diff --git a/digsby/src/plugins/facebook/res/attachment.py.xml b/digsby/src/plugins/facebook/res/attachment.py.xml new file mode 100644 index 0000000..90ba374 --- /dev/null +++ b/digsby/src/plugins/facebook/res/attachment.py.xml @@ -0,0 +1,38 @@ + +
+ + + + + 1: ?> +
+ +
+ + + ${post.attachment.name} + + #{linkify(escape(to_str(post.attachment.name)))} + +
+ + 1: ?> + - + +
+ +
#{linkify(post.attachment.caption)}
+ + + +
+ #{linkify(post.attachment.description)} +
+ + +
+ +
+ +
+ diff --git a/digsby/src/plugins/facebook/res/birthdays.py.xml b/digsby/src/plugins/facebook/res/birthdays.py.xml new file mode 100644 index 0000000..6222469 --- /dev/null +++ b/digsby/src/plugins/facebook/res/birthdays.py.xml @@ -0,0 +1,24 @@ + + + +
+
${{_('Upcoming Birthdays')}}
+ + +
+ ${birthday['name']} + ${strftime_u(birthday['bday'], pref("facebook.newsfeed.dateformat", "%B %d"))} + + (${birthday['age']}) + +
+ + +
+
+ + diff --git a/digsby/src/plugins/facebook/res/comment.py.xml b/digsby/src/plugins/facebook/res/comment.py.xml new file mode 100644 index 0000000..31bf50e --- /dev/null +++ b/digsby/src/plugins/facebook/res/comment.py.xml @@ -0,0 +1,22 @@ +
+
+ +
+
+ + + +
+ +${feed.profiles[comment.fromid].name or _("Facebook User")} + +${feed.profiles[comment.fromid].name or _("Facebook User")} + + · +
+
+
+ #{linkify(preserve_newlines(escape(to_str(comment.text))))} +
+
+
diff --git a/digsby/src/plugins/facebook/res/comment_link.py.xml b/digsby/src/plugins/facebook/res/comment_link.py.xml new file mode 100644 index 0000000..77e96d8 --- /dev/null +++ b/digsby/src/plugins/facebook/res/comment_link.py.xml @@ -0,0 +1,27 @@ + + + + + · + 0: +?>${{_('Comment')}} (${num_non_dislike_comments})${{_('Comment')}} + + + + + diff --git a/digsby/src/plugins/facebook/res/comment_post_link.py.xml b/digsby/src/plugins/facebook/res/comment_post_link.py.xml new file mode 100644 index 0000000..510b6ec --- /dev/null +++ b/digsby/src/plugins/facebook/res/comment_post_link.py.xml @@ -0,0 +1,18 @@ + + + 50: ?> +
${_('See all %d comments') % num_non_dislike_comments}
+ +
${{_('See all comments')}}
+ diff --git a/digsby/src/plugins/facebook/res/comments.py.xml b/digsby/src/plugins/facebook/res/comments.py.xml new file mode 100644 index 0000000..7b70ef7 --- /dev/null +++ b/digsby/src/plugins/facebook/res/comments.py.xml @@ -0,0 +1,18 @@ + + +
+ +
+ + + + + + + + + +
+ diff --git a/digsby/src/plugins/facebook/res/comments2.py.xml b/digsby/src/plugins/facebook/res/comments2.py.xml new file mode 100644 index 0000000..75ff4c6 --- /dev/null +++ b/digsby/src/plugins/facebook/res/comments2.py.xml @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/digsby/src/plugins/facebook/res/content.tenjin b/digsby/src/plugins/facebook/res/content.tenjin new file mode 100644 index 0000000..6023481 --- /dev/null +++ b/digsby/src/plugins/facebook/res/content.tenjin @@ -0,0 +1,42 @@ + + diff --git a/digsby/src/plugins/facebook/res/dislike_link.py.xml b/digsby/src/plugins/facebook/res/dislike_link.py.xml new file mode 100644 index 0000000..ab427eb --- /dev/null +++ b/digsby/src/plugins/facebook/res/dislike_link.py.xml @@ -0,0 +1,35 @@ + + + + + + + + · + ${{_('Dislike')}} + + + + · + ${{_('Undislike')}} + + + 0: ?> + (${num_dislikes}) + + + 0: ?> + · + ${{_('Dislikes')}} (${num_dislikes}) + + + diff --git a/digsby/src/plugins/facebook/res/dislikes.py.xml b/digsby/src/plugins/facebook/res/dislikes.py.xml new file mode 100644 index 0000000..10c374b --- /dev/null +++ b/digsby/src/plugins/facebook/res/dislikes.py.xml @@ -0,0 +1,79 @@ + + + +
+ + +
+ + = 2: ?> + You, + + +${feed.profiles[uid].name or _("Facebook User")}, + + ${feed.profiles[uid].name or _("Facebook User")} + + + You + + = 1: ?> + and + + +${feed.profiles[uid].name or _("Facebook User")} + + ${feed.profiles[uid].name or _("Facebook User")} + + + disliked this +
+ +
+ + = 3: ?> + + +${feed.profiles[uid].name or _("Facebook User")}, + + ${feed.profiles[uid].name or _("Facebook User")} + + + = 2: ?> + + +${feed.profiles[uid].name or _("Facebook User")} + + ${feed.profiles[uid].name or _("Facebook User")} + + and + + + +${feed.profiles[uid].name or _("Facebook User")} + + ${feed.profiles[uid].name or _("Facebook User")} + + disliked this +
+ + +
diff --git a/digsby/src/plugins/facebook/res/facebookicons/dislike.png b/digsby/src/plugins/facebook/res/facebookicons/dislike.png new file mode 100644 index 0000000..3a92831 Binary files /dev/null and b/digsby/src/plugins/facebook/res/facebookicons/dislike.png differ diff --git a/digsby/src/plugins/facebook/res/facebookicons/down_arrow_solid.png b/digsby/src/plugins/facebook/res/facebookicons/down_arrow_solid.png new file mode 100644 index 0000000..fc76fdd Binary files /dev/null and b/digsby/src/plugins/facebook/res/facebookicons/down_arrow_solid.png differ diff --git a/digsby/src/plugins/facebook/res/facebookicons/like.png b/digsby/src/plugins/facebook/res/facebookicons/like.png new file mode 100644 index 0000000..64e9b5f Binary files /dev/null and b/digsby/src/plugins/facebook/res/facebookicons/like.png differ diff --git a/digsby/src/plugins/facebook/res/facebookicons/notifications_icon.png b/digsby/src/plugins/facebook/res/facebookicons/notifications_icon.png new file mode 100644 index 0000000..0a2010a Binary files /dev/null and b/digsby/src/plugins/facebook/res/facebookicons/notifications_icon.png differ diff --git a/digsby/src/plugins/facebook/res/head.tenjin b/digsby/src/plugins/facebook/res/head.tenjin new file mode 100644 index 0000000..6dfd37f --- /dev/null +++ b/digsby/src/plugins/facebook/res/head.tenjin @@ -0,0 +1,6 @@ + diff --git a/digsby/src/plugins/facebook/res/infobox.css b/digsby/src/plugins/facebook/res/infobox.css new file mode 100644 index 0000000..7171f3c --- /dev/null +++ b/digsby/src/plugins/facebook/res/infobox.css @@ -0,0 +1,349 @@ +body { + word-wrap: break-word; +} +table{ + table-layout: fixed; + display:table; +} +a:hover { + text-decoration:underline; +} +.status_title, .status_name { + display:inline; +} +.alerts, .status { + margin-bottom:0em; + width:100%; +} +.alerts .content_hr { + margin-top:1em; + margin-bottom:.5em; +} +.alert_section { + padding-left:1em; +} + +.filter_keys { + margin: -1px; + margin-left: -2px; + padding: 1px; + -webkit-user-select:none; +} +.filter_key { + padding: 1px; + padding-right:4px; +} +.filter_key_text { + display: inline-block; +} + +.filter_keys:hover, .filter_keys_expanded { + border: 1px solid black; + padding: 0; +} +.filter_keys_expanded .filter_key { + padding:2px; + /*padding-bottom:2px;*/ +} +div.feed_icon { + vertical-align:top; +} +.filter_keys_expanded div.feed_icon { + width:16px; + height:16px; + display: inline-block; + +} +div.feed_icon { + vertical-align:middle; +} +img.filter_menu_arrow { + vertical-align:50%; +} +.filter_keys_expanded img.filter_menu_arrow { + display:none; +} +.filter_key_val { + display:none; +} + +.post_row { + /* + border: 1px solid blue; + */ + max-width:100%; + margin-bottom:.6em; +} +.post_divider { + background: none; + background-color:#d7d7d7; + margin-bottom: 0.4em; + margin-left:54px; +} +.buddyicon { + /* + -webkit-border-radius:6px; + -moz-border-radius: 6px; + -moz-border-radius-topleft: 6px; + + border-radius:6px; + */ + max-width: 50px; + max-height: 50px; + width: 50px; + height: 50px; + margin-top:2px; +} +.buddyiconcell { + float:left; + clear:both; + vertical-align:top; + padding:0px; + width:54px; + height:52px; +} +.messagecell { + vertical-align:top; + max-width:100%; + margin-left:54px; + min-height:52px; +/* + border: 1px solid red; + width:100%; + position:relative; +*/ +} +.message { + /* + border: 1px solid blue; + */ +} +.message_block { + display:block; + /*margin-top:-.2em;*/ + /*padding-bottom:.1666em;*/ +} +.time { + display:inline-block; + max-width:100%; +/* border: 1px solid blue;*/ +} +.notification_app_block img, .notification_app_block span { + vertical-align:top; +} +.notification_app_block { + margin-top: .5em; +} +.commentlike { + white-space:nowrap; +/* border: 1px solid green; */ + vertical-align:text-top; + display:inline-block; +} +img.skinimage { + border:none; +} +img.picturelink { + border:none; +} +.attachment { + margin-top:.3em; +} +.multimedia_attachment_name { + display:inline-block; + max-width:100%; +} +.one_zero_media_attachment_name { + display:inline; + max-width:100%; +} +.attachment_caption { + display:inline-block; + max-width:100%; +} +.attachment_description { + width:100%; +} +.attachment_end { +} +.media_attachment_end { + clear:left; +} +.media1 { + float:left; +} +.media_row { + vertical-align:bottom; +} +.post_table { + margin:0px; + margin-top: 1.7em; + padding:0px; + border:0px; + border-spacing:0px .7em; +} +.media_many .media_table{ + border-spacing:0px; + border:0px; + padding:0px; +} +.media1 .media_table{ + border-spacing:0px; + border:0px; + padding:0px; +} +.media_row td { + padding:0px; + padding-right:3px; +} +.reload_button { + display:none; +} +hr { + border:0px; + padding:0px; +} +.content_hr { + /* + height:1px; + */ +} +.alerts, .birthdays, .status { + display:inline-block; +} +.CopyBody { + max-height:2.5em; + overflow:hidden; + text-overflow: ellipsis; +} +.input_area { + margin-top:.3em; + width:100%; +} +.form_wrapper { + position:relative; /*when adding a centered spinner, this seems to be needed */ +} +.post_row .link_block .link_section, .post_row .commentlike { + /* + opacity: 0; + -webkit-transition: opacity .1s linear; + visibility:hidden; + */ +} +.post_row:hover .link_block .link_section, .post_row:hover .commentlike{ + /* + visibility:visible; + -webkit-transition: opacity .1s linear; + */ + opacity: 1; +} +.likes, .dislikes { + margin-top:.4em; + margin-bottom:.5em; +} +.likes img, .dislikes img { + vertical-align:text-top; +} + +.loader_img { + position:absolute; + top:50%; + left:50%; + margin-top:-8px; + margin-left:-8px; +} + +.comment:hover .del_comment { + opacity: 1; +} +.comment_block, .likes { +/* + display:none; +*/ +} +.comments { + /* + display:table; + table-layout: fixed; + */ + padding:0px; + border:0px; + border-spacing:0px; +} +.comment { + /* + display:table-row; + */ + margin-top:.3em; + /*min-height:30px;*/ +} +.comment_icon_cell { + float:left; + /*clear:right;*/ + min-height:30px; + min-width:30px; +} +.comment_icon { + margin-top:2px; + max-width:28px; +} +.comment_text_cell { + min-height:30px; + margin-left:32px; + /*margin-top:-.2em;*/ + /*margin-bottom:.1666em;*/ +} +.comment_name, .comment_time { + display:inline; +} +.del_comment { + text-align:right; + float:right; + vertical-align:top; + opacity: 0; +} +.post_row .link_block { + display:inline; + float:left; +} +.count_block { + display:inline; + float:right; + vertical-align:text-top; +} +.count_block .action_button img { + vertical-align:text-top; +} +.comments_end { +} +.error_section { + display:none; + color: red; +} +.bottom_row { + + margin-top:.3em; + /* + padding-bottom:.3em; + */ +} +.likes_comments_section { + display:none; +} +.link_section { + display:inline-block; +} +.no_media_bar { + border-left-style:solid; + border-left-color:#d7d7d7; + padding-left:.5em; + border-left-width:2px; +} +.date-separator { + display:block; + margin-bottom:.4em; +} + +.uiTooltipText { + display:none; +} diff --git a/digsby/src/plugins/facebook/res/infobox.js b/digsby/src/plugins/facebook/res/infobox.js new file mode 100644 index 0000000..cc44c4f --- /dev/null +++ b/digsby/src/plugins/facebook/res/infobox.js @@ -0,0 +1,901 @@ +var TODAY_TEXT = ""; + +var ACTIVE_FILTERS = ['nf', '__trend__']; + +function fb_comments_updater(self, slide, clear_error) { + return function(args) { + var comments_html = $($(args.comments_html)[0]); + var comment_link_html = $($(args.comment_link_html)[0]); + var comments = $(comments_html[0]).find(".comment"); + var count = args.count; + + var new_comment; + + if ((comments.length) && (slide == "new")) { + new_comment = $(comments[comments.length-1]); + new_comment.hide(); + } else { + new_comment = null; + } + comment_link_html.hide(); + //comments_html.hide(); + comments_html.find(".time").each(do_convert_time); + fb_init_comment_a(comments_html); + $(self).parents('.messagecell').find('.comments').replaceWith(comments_html); + //only want the "Comment ()" chunk, not the "()" after likes -----v + $($(self).parents(".messagecell").find('.comment_button_section')[0]).replaceWith(comment_link_html); + comment_box = $($(self).parents(".form_wrapper")[0]).find("textarea[name='foo']"); + + if (slide == "new") { + slide_element = new_comment; + } else if (slide == "all") { + slide_element = comments_html; + } + + if (clear_error) { + fb_clear_error(function(){ + slide_element.slideDown(transitionDefault, function(){ + $($(self).parents(".form_wrapper")[0]).find('.loader_img').remove(); + comment_box[0].value = ''; + comment_box[0].disabled = false; + comment_box.jGrow(); + }); + }); + } else { + //slide_element.slideDown(transitionDefault); + } + comment_box.focus(); + }; +} + +var show_comment_box = function(self){ + var me = $(self); + me.parents(".messagecell").find('.comment_button_section').hide(); // placeholder + me.parents(".messagecell").find('.likes_comments_section').css('-webkit-user-select','none'); + + var end = me.parents(".messagecell").find(".comments_end"); + var txt; + if (end.text() =="placeholder"){ + end.html('
+ + + + + + + diff --git a/digsby/src/plugins/twitter/res/tweets.tenjin b/digsby/src/plugins/twitter/res/tweets.tenjin new file mode 100644 index 0000000..cca0efb --- /dev/null +++ b/digsby/src/plugins/twitter/res/tweets.tenjin @@ -0,0 +1,10 @@ + + + + + + + +
+ + diff --git a/digsby/src/plugins/twitter/res/twitter.js b/digsby/src/plugins/twitter/res/twitter.js new file mode 100644 index 0000000..9a7e029 --- /dev/null +++ b/digsby/src/plugins/twitter/res/twitter.js @@ -0,0 +1,2755 @@ +/** + * An HTML5 twitter client operating in a WebKit window as Digsby's twitter plugin. + */ + +var TRENDS_UPDATE_MINS = 60; +var IDLE_THRESHOLD_MS = 10 * 60 * 1000; + +var INVITE_TEXT = "Hey, I've been using Digsby and thought you might find it useful. Check out the demo video: "; +var INVITE_URL = "http://bit.ly/clMDW5"; + +// javascript's RegExp \w character class doesn't include common extended characters. this string +// includes some common latin characters, at least. +var wordchars = '\\w'; + +/* linkify @usernames */ +var atify_regex = new RegExp('(?:(?:^@([\\w]+))|(?:([^\\w])@([' + wordchars + ']+)))(/[' + wordchars + ']+)?', 'g'); + +var atifyOnClick = 'onclick="javascript:return tweetActions.openUser(this);"'; + +function atify(s) { + return s.replace(atify_regex, function(m, sn1, extra, sn2, listName) { + var screenname = (sn1 || sn2) + (listName ? listName : ''); + return (extra || '') + '@' + screenname + ''; + }); +} + +/* linkify #hastags */ +var hashifyRegex = new RegExp('(?:(?:^)|(?:([^/&' + wordchars + '])))(#([' + wordchars + ']{2,}))', 'g'); + +function hashify(s) { + return s.replace(hashifyRegex, function(m, extra, outerSearch, search) { + return (extra || '') + '' + outerSearch + ''; + }); +} + +function twitterLinkify(s) { + return atify(hashify(linkify(s))); +} + +var infoColumns = [ + ['key', 'TEXT'], + ['value', 'TEXT'] +]; + +// detect malformed databases and ask the account to delete all local data if we do +function executeSql(tx, query, args, success, error) { + function _error(tx, err) { + if (err.message === 'database disk image is malformed') + D.notify('corrupted_database'); + else + return error(tx, err); + } + + return tx.executeSql(query, args, success, _error); +} + +var tweetColumns = [ + // [JSON name, sqlite datatype] + ['id', 'TEXT UNIQUE'], + ['created_at', 'TEXT'], + ['text', 'TEXT'], + ['source', 'TEXT'], + ['truncated', 'TEXT'], + ['in_reply_to_status_id', 'TEXT'], + ['in_reply_to_user_id', 'INTEGER'], + ['favorited', 'BOOLEAN'], + ['in_reply_to_screen_name', 'TEXT'], + ['user', 'INTEGER'], + + // custom + ['read', 'INTEGER'], // is read + ['mention', 'BOOLEAN'], // has @username in text + ['search', 'TEXT'], // came from a search + + // search tweets are returned with just the username, not an ID, so for these + // tweets we only have from_user and profile_image_url + ['from_user', 'TEXT'], + ['profile_image_url', 'TEXT'] +]; + +var directColumns = [ + ['id', 'TEXT UNIQUE'], + ['sender_id', 'INTEGER'], + ['text', 'TEXT'], + ['recipient_id', 'INTEGER'], + ['created_at', 'TEXT'], + + ['read', 'INTEGER'], +]; + +var userColumns = [ + ['id', 'INTEGER UNIQUE'], + ['name', 'TEXT'], + ['screen_name', 'TEXT'], + ['location', 'TEXT'], + ['description', 'TEXT'], + ['profile_image_url', 'TEXT'], + ['url', 'TEXT'], + ['protected', 'TEXT'], + ['followers_count', 'INTEGER'], + ['profile_background_color', 'TEXT'], + ['profile_text_color', 'TEXT'], + ['profile_link_color', 'TEXT'], + ['profile_sidebar_fill_color', 'TEXT'], + ['profile_sidebar_border_color', 'TEXT'], + ['friends_count', 'INTEGER'], + ['created_at', 'TEXT'], + ['favourites_count', 'INTEGER'], + ['utc_offset', 'INTEGER'], + ['time_zone', 'TEXT'], + ['profile_background_image_url', 'TEXT'], + ['profile_background_tile', 'TEXT'], + ['statuses_count', 'INTEGER'], + ['notifications', 'TEXT'], + ['following', 'TEXT'] +]; + +var tweetsByTime = cmpByKey('created_at_ms'); +var tweetsById = cmpByKey('id'); + +function twitterPageLoaded(window) +{ + guard(function() { + // TODO: account should not be a global on the window. + var account = window.opener.account; + account.window = window; + window.db = window.opener.db; + window.timeline.account = account; + var timeline = account.timeline = window.timeline; + timeline.dataMap = account.tweets; + + // update timestamps every minute. + window.timestampTimer = window.setInterval(function() { timeline.updateTimestamps(); }, 1000 * 60); + + // use a temporary function on the window here hardcoded into feed.html's handler + // until http://dev.jquery.com/ticket/4791 is fixed (jquery child window unloads get registered + // on parent windows) + window.feedUnload = function() { + console.log('unload handler called'); + account.recordReadTweets(); + }; + + var feedToOpen = window.opener.feedToOpen || 'timeline'; + + //console.warn('calling changeView from twitterPageLoaded'); + account.changeView(feedToOpen); + account.notifyFeeds(); + delete window.opener.feedToOpen; + }); +} + +function TwitterAccount(username, password, opts) { + if (!username) + throw "Need username"; + + //console.warn('TwitterAccount(' + username + ', ' + password + ', ' + oauthToken + ', ' + oauthConsumerSecret + ')'); + + var self = this; + this.showAllRealtime = false; + this.clients = []; + this.username = username; + //this.selfScreenNameLower = username.toLowerCase(); + this.password = password; + + // OAUTH + if (opts.oauthTokenKey) + this.oauth = { + tokenKey: opts.oauthTokenKey, + tokenSecret: opts.oauthTokenSecret, + consumerKey: opts.oauthConsumerKey, + consumerSecret: opts.oauthConsumerSecret + }; + + this.users = {}; // {user id: user object} + + this.sql = { + 'tweets': new SQLDesc('Tweets', tweetColumns), + 'users': new SQLDesc('Users', userColumns), + 'info': new SQLDesc('Info', infoColumns), + 'directs': new SQLDesc('Directs', directColumns) + }; +} + +function dropAllTables() { + var tables = objectKeys(window.account.sql); + + window.db.transaction(function(tx) { + console.log('dropping tables: ' + tables.join(', ')); + $.each(tables, function (i, table) { + executeSql(tx, 'drop table if exists ' + table); + }); + }, function(error) { console.error(error.message); }); +} + + +function tweetsEqual(t1, t2) { return t1.id === t2.id; } + +function errorFunc(msg) { + return function (err) { + var extra = ''; + if (err !== undefined && err.message !== undefined) + extra = ': ' + err.message; + console.error(msg + extra); + }; +} + +TwitterAccount.prototype = { + timerMs: 1000 * 60, + webRoot: 'http://twitter.com/', + apiRoot: 'https://api.twitter.com/1/', + searchApiRoot: 'http://search.twitter.com/', + + showTimelinePopup: function(n) { + var items = this.feeds.timeline.items; + if (n !== undefined) + items = items.slice(0, n); + + showPopupsForTweets(this, items); + }, + + stopTimers: function(reason) { + console.warn('stopTimers:' + reason); + if (this.updateTimer) + clearInterval(this.updateTimer); + }, + + maybeUpdate: function(forceUpdate, sources) { + var self = this; + var now = new Date().getTime(); + var srcs; + var checkIdle = false; + + if (sources) { + srcs = sources; + } else if (forceUpdate) { + console.warn('forcing update'); + srcs = $.map(self.sources, function(s) { return s.feed.autoUpdates ? s : null; }); + } else { + checkIdle = true; + // only update sources whose needsUpdate returns true + srcs = $.map(self.sources, function(s) { return s.needsUpdate(now) ? s : null; }); + } + + if (srcs.length === 0) + return; + + function _doUpdate() { + if (self.returnFromIdleTimer) { + clearInterval(self.returnFromIdleTimer); + self.returnFromIdleTimer = undefined; + } + + // any successful update triggers online state + function success() { + self.didUpdate = true; + self.notifyClients('didReceiveUpdates'); + } + console.debug('UpdateNotification being created with ' + srcs.length); + var updateNotification = new UpdateNotification(self, srcs.length, { + success: success, + onDone: function(tweets) { + self.notifyClients('didReceiveWholeUpdate'); + }, + }); + + var markRead = false; + if (self.firstUpdate) { + self.firstUpdate = false; + markRead = true; + } + + for (var c = 0; c < srcs.length; ++c) { + var src = srcs[c]; + if (markRead) + src.markNextUpdateAsRead(); + src.update(updateNotification); + } + } + + if (!checkIdle) { + _doUpdate(); + return; + } + + D.rpc('get_idle_time', {}, function(args) { + if (args.idleTime > IDLE_THRESHOLD_MS) { + if (!self.returnFromIdleTimer) { + // once idle, check idle time every 10 seconds so that + // we can update soon after you come back. + self.returnFromIdleTimer = setInterval(function() { + self.maybeUpdate(); + }, 10 * 1000); + } + } else _doUpdate(); + }, _doUpdate); + }, + + discardOldTweets: function() { + var self = this, + idsToDelete = [], + usersToKeep = {}; + + var doc, toInsertMap; + if (this.timeline) { + doc = this.timeline.container.ownerDocument; + console.debug('** discardOldTweets this.timeline.toInsert: ' + this.timeline.toInsert); + if (this.timeline.toInsertMap) + toInsertMap = this.timeline.toInsertMap; + } + + doc = this.timeline ? this.timeline.container.ownerDocument : undefined; + + //console.warn('** discardOldTweets toInsertMap: ' + toInsertMap); + + function shouldDelete(tweet) { + if (!doc || !doc.getElementById(tweet.id)) + if (!tweet.feeds || !objectKeys(tweet.feeds).length) + if (!toInsertMap || !(tweet.id in toInsertMap)) + return true; + } + + function discard(items) { + // discard references to all tweets not in a feed + $.each(items, function (i, tweet) { + if (shouldDelete(tweet)) + idsToDelete.push(tweet.id); + else { + $.each(['user', 'recipient_id', 'sender_id'], function (i, attr) { + if (tweet[attr]) + usersToKeep[tweet[attr]] = true; + }); + } + }); + + $.each(idsToDelete, function (i, id) { + //console.log('DELETING ' + id + ' ' + items[id].text + ' ' + objectLength(items[id].feeds)) + delete items[id]; + }); + } + + discard(this.tweets); + + // discard all users not referenced by tweets or directs or groups + + $.each(this.getUsersToKeep(), function(i, id) { + usersToKeep[id] = true; + }); + + var usersToDelete = []; + $.each(this.users, function (id, user) { + if (user.screen_name.toLowerCase() !== this.selfScreenNameLower) + if (!(id in usersToKeep)) + usersToDelete.push(id); + }); + + if (usersToDelete.length) { + $.each(usersToDelete, function (i, id) { delete self.users[id]; }); + } + }, + + forceUpdate: function() { + this.maybeUpdate(true); + }, + + setupTimers: function() { + var self = this; + this.updateTimer = setInterval(function() { self.maybeUpdate(); }, this.timerMs); + this.maybeUpdate(); + + // update trends periodically. + this.trendsTimer = setInterval(function() { self.getTrends(); }, + 1000 * 60 * TRENDS_UPDATE_MINS); + }, + + initialize: function(feeds, accountopts) { + var self = this; + + if (accountopts) { + console.warn('apiRoot: ' + accountopts.apiRoot); + console.debug(JSON.stringify(accountopts)); + if (accountopts.timeCorrectionSecs !== undefined) { + var diffMs = accountopts.timeCorrectionSecs * 1000; + var d = new Date(new Date().getTime() - diffMs); + receivedCorrectTimestamp(d, true); + } + + if (accountopts.webRoot) self.webRoot = accountopts.webRoot; + if (accountopts.apiRoot) self.apiRoot = accountopts.apiRoot; + if (accountopts.searchApiRoot) self.searchApiRoot = accountopts.searchApiRoot; + + } + + function success(tx) { + self.initializeFeeds(tx, feeds, accountopts, function() { + if (self.offlineMode) { + self.didUpdate = true; + self.notifyClients('didReceiveUpdates'); + self.notifyFeeds(); + self.doNotifyUnread(); + } else { + self.getFollowing(function() { + // we don't know which users to discard until we know who we're following. + self.discardOldUsers(); + }); + self.getTrends(); + self.setupTimers(); + self.notifyFeeds(); + } + }, function() { + self.connectionFailed(); + }); + } + + function error(tx, error) { + console.error('error creating tables: ' + error.message); + } + + account.setupSql(success, error); + }, + + refreshFeedItems: function(feed) { + feed.removeAllItems(); + this.addAllSorted(feed, this.timeline && this.timeline.feed === feed); + }, + + addAllSorted: function(feed, deleted) { + var tweets = objectValues(this.tweets); + tweets.sort(tweetsByTime); + + console.debug('##########\nsorting all tweets for addSorted on ' + feed + ' ' + tweets.length + ' tweets'); + var scroll = this.timeline && this.timeline.feed === this.feeds.timeline; + feed.addSorted(tweets, undefined, true, { + scroll: scroll, + setSorted: deleted, + account: this + }); + }, + + deleteFeed: function(opts) { + var self = this, name = opts.feedName; + if (!name) return; + + console.log('deleteFeed: ' + name); + + var didFind = false; + $.each(this.customFeeds, function (i, feed) { + if (feed.name !== name) return; + didFind = true; + + if (feed.source) { + if (!arrayRemove(self.sources, feed.source)) + console.error('did not remove source ' + feed.source); + } + + self.customFeeds.splice(i, 1); + + // when deleting a custom feed, the timeline may change + // b/c of filtering and merging + var timeline = self.feeds.timeline; + if (timeline && feed !== timeline) { + var deleted = timeline.feedDeleted(feed.serialize(), self.customFeeds); + self.addAllSorted(timeline, deleted); + } + + delete self.feeds[name]; + self.notifyFeeds(); + self.doNotifyUnread(); + return false; + }); + + if (!didFind) console.warn('deleteFeed ' + name + ' did not find a feed to delete'); + }, + + uniqueFeedName: function(type) { + var name; + var i = 1; + do { name = type + ':' + i++; + } while (name in this.feeds); + return name; + }, + + editFeed: function(feedDesc) { + var name = feedDesc.name; assert(name); + var feed = this.feeds[name]; assert(feed); + var timeline = this.feeds.timeline; + + timeline.feedDeleted(feed.serialize(), this.customFeeds); + + var needsUpdate = feed.updateOptions(feedDesc); + + timeline.feedAdded(this, feed.serialize()); + + if (needsUpdate) { + this.refreshFeedItems(feed); + this.addAllSorted(timeline, true); + } + + this.notifyFeeds(); + if (needsUpdate) + this.doNotifyUnread(); + }, + + findFeed: function(feedDesc) { + console.log('findFeed: ' + JSON.stringify(feedDesc)); + + if (feedDesc['type'] === 'search' && feedDesc['query']) { + var foundFeed; + $.each(this.feeds, function(i, feed) { + if (feed.query && feed.query === feedDesc['query']) { + foundFeed = feed; + return false; + } + }); + + if (foundFeed) + return foundFeed; + } + }, + + getAllGroupIds: function() { + var allIds = []; + $.each(this.customFeeds, function (i, feed) { + var userIds = feed.userIds; + if (userIds) + for (var i = 0; i < userIds.length; ++i) + allIds.push(userIds[i]); + }); + return allIds; + }, + + addFeed: function(feedDesc, shouldChangeView) { + console.debug('addFeed(' + JSON.stringify(feedDesc) + ')'); + console.log(JSON.stringify(feedDesc)); + + var existingFeed = this.findFeed(feedDesc); + if (existingFeed) { + console.log('returning existing feed ' + existingFeed); + if (this.timeline.feed !== existingFeed) + this.changeView(existingFeed['name']); + return existingFeed; + } + + if (shouldChangeView === undefined) + shouldChangeView = true; + + // assign user created feeds a unique name + if ((feedDesc.type === 'group' || feedDesc.type === 'search' || feedDesc.type === 'user') && + feedDesc.name === undefined) + feedDesc.name = this.uniqueFeedName(feedDesc.type); + + if (feedDesc.type === 'search' && !feedDesc.save) + D.notify('hook', 'digsby.statistics.twitter.new_search') + + var self = this, + feed = createFeed(this.tweets, feedDesc); + + if (shouldChangeView) + this.addAllSorted(feed); + + var source = feed.makeSource(self); + if (source) { + this.sources.push(source); + if (shouldChangeView) { + var done = function() { self.timeline.finish(); }; + var obj = {success: done, error: done}; + console.log('in addFeed, updating source ' + source + ' now'); + source.update(obj); + } + } + + this.feeds[feed.name] = feed; + this.customFeeds.push(feed); + + if (shouldChangeView) { + this.notifyFeeds(); + this.changeView(feed['name']); + } + + if (feed !== this.feeds.timeline) { + //console.debug('calling feedAdded: ' + JSON.stringify(feedDesc)); + this.feeds.timeline.feedAdded(this, feedDesc); + } else + console.warn('feed is timeline, skipping feedAdded'); + + return feed; + }, + + addGroup: function(feedDesc, shouldChangeView) { + return this.addFeed(feedDesc, shouldChangeView); + }, + + // map of {feedName: [account option name, default minutes to update]} + account_opts_times: {timeline: ['friends_timeline', 2], + directs: ['direct_messages', 10], + mentions: ['replies', 2]}, + + setAccountOptions: function(opts) { + if (!this.feeds) + return; + + console.warn('setAccountOptions: apiRoot ' + opts.apiRoot); + if (opts.apiRoot) + this.apiRoot = opts.apiRoot; + + var invite_url = opts.demovideo_link ? opts.demovideo_link : INVITE_URL; + this.invite_message = INVITE_TEXT + invite_url; + + // opts may have values for the updateFrequency of the main feeds. + var self = this; + console.debug('setAccountOptions'); + $.each(this.feeds, function (name, feed) { + if (feed.name in self.account_opts_times) { + var feedopts = self.account_opts_times[feed.name]; + var optName = feedopts[0]; + var defaultUpdateMins = feedopts[1]; + var minutes = optName in opts ? opts[optName] : defaultUpdateMins; + + console.debug('feed: ' + feed.name + ' ' + minutes); + feed.source.setUpdateFrequency(1000 * 60 * minutes); + } + }); + + this.searchUpdateFrequency = 1000 * 60 * get(opts, 'search_updatefreq', 2); + + settings.autoscroll_when_at_bottom = get(opts, 'autoscroll_when_at_bottom', true); + + this.offlineMode = opts.offlineMode; + if (this.offlineMode) + console.warn('twitter is in offline mode'); + }, + + initializeFeeds: function(tx, userFeeds, opts, success, error) { + var self = this; + + if (!this.selfUser && !this.selfScreenNameLower) { + // if we haven't looked up our own id yet, then do so first + this.verifyCredentials(function() { self.initializeFeeds(undefined, userFeeds, opts, success, error); }, + error); + return; + } + + var tweets = this.tweets = {}; + + // the base set of all feeds + this.customFeeds = []; + var feeds = this.feeds = { + favorites: new TwitterFavoritesFeedModel(tweets), + history: new TwitterHistoryFeedModel(tweets, {screen_name: this.selfScreenNameLower}) + }; + + self.sources = $.map(objectValues(feeds), function(f) { return f.makeSource(self) || null; }); + console.warn('created ' + self.sources.length + ' self.sources'); + + if (userFeeds.length) { + // Construct feeds here. Take care to construct the main timeline + // view first, since it needs to be around when we construct the other + // feeds, which may affect it via filtering and merging. + var userFeedsRearranged = Array.apply(null, userFeeds); + userFeedsRearranged.sort(function(a, b) { + // timeline goes first + return -cmp(a.type==='timeline', b.type==='timeline'); + }); + $.each(userFeedsRearranged, function (i, feedOpts) { self.addFeed(feedOpts, false); }); + + // Rearrange the feeds back into their original user order. + this.setFeeds(userFeeds); + } + + this.setAccountOptions(opts); + + var successCount = 0; + function _success(tablename, tweets) { + console.log('loaded ' + tweets.length + ' cached tweets'); + assert(feeds); + self.newTweets(tweets); + + var toDeleteIds = []; + + assert(tweets); + + // discard any tweets that we pulled out of the cache, but + // not added to any timelines + $.each(tweets, function (i, tweet) { + if (!tweet.feeds || !objectKeys(tweet.feeds).length) + toDeleteIds.push(tweet.id); + }); + + console.debug('discarding ' + toDeleteIds.length + ' ' + tablename); + self.discard(tablename, toDeleteIds); + if (++successCount === 2 && success) + success(); + } + + function _error(err) { + console.error('error: ' + err.message); + if (error) error(err); + } + + self.loadAllCached({tx: tx, + success: _success, + error: _error}); + }, + + setFeeds: function(newFeeds) { + // rearrange feeds + var feeds = this.feeds; + this.customFeeds = $.map(newFeeds, function(f) { + return feeds[f.name]; + }); + this.notifyFeeds(); + }, + + changeView: function (feed) { + console.warn('changeView(' + JSON.stringify(feed) + ')'); + console.warn(' typeof feed is ' + (typeof feed)); + + if (typeof feed !== 'string') { + // can be a new feed description + console.log('got a feed description, calling addFeed'); + feed = this.addFeed(feed, true); + return; + } + + var self = this; + + var timeline = this.timeline; + timeline.pauseMarkAsRead(true); + timeline.viewChanged(feed); + + timeline.pauseMarkAsRead(false, function() { + if (self.feeds[feed].scrollToBottom) + // history and favorites start scrolled to the bottom + timeline.scrollToBottom(); + else + timeline.scrollToNew(); + }); + + console.warn('changeView: ' + feed); + + // switching away from a non-saved search feed deletes it. + var toDelete = []; + $.each(this.feeds, function (i, f) { + if (f.query && !f.save && f.name !== feed) + toDelete.push(f.name); + }); + + if (toDelete.length) + $.each(toDelete, function (i, name) { self.deleteFeed({feedName: name}); }); + + this.notifyClients('viewChanged', feed); + }, + + timelineClosing: function() { + console.log('timelineClosing'); + var self = this; + var feed = this.timeline.feed; + if (feed.query && !feed.save) { + console.log('yes'); + setTimeout(function() { self.deleteFeed({feedName: feed.name}); }, 50); + } + }, + + setupSql: function (success, error) { + function _success(tx) { + executeSql(tx, + 'create table if not exists info_keyed (' + + 'key text primary key, ' + + 'value text)', [], + success, error); + } + + var self = this; + var makeTables = function (tx, sqls) { + if (sqls.length === 0) + _success(tx); + else + sqls[0].ensureTableExists(tx, function (tx, result) { + makeTables(tx, sqls.slice(1)); + }, error); + }; + + function didCheckForExistingTweets(tx) { + makeTables(tx, objectValues(self.sql)); + } + + // Assume that if the "Tweets" table does not exist, this is a new account. We'll mark + // tweets from the first update as already read. + window.db.transaction(function (tx) { + executeSql(tx, "select * from Tweets limit 1", [], + didCheckForExistingTweets, + function (tx, err) { self.firstUpdate = true; didCheckForExistingTweets(tx); } + ); + }); + }, + + isSelfTweet: function(t) { + var user = this.users[t.user]; + if (user) { + if (this.selfUser) + return user.id === this.selfUser.id; + else + return user.screen_name.toLowerCase() === this.selfScreenNameLower; + } + }, + + isMention: function(t) { + // consider tweets with your @username not at the beginning + // still a mention + return ( + t.text.toLowerCase().search('@' + this.selfScreenNameLower) !== -1) + ? true : false; + }, + + isDirect: function(t) { + return t.recipient_id ? true : false; + }, + + verifyCredentials: function(done, onError, retried) { + var self = this; + + loadServerTime(function() { + + function onJSON(json) { + self.selfUser = self.users[json.id] = self.sql.users.fromJSON(json); + self.selfScreenNameLower = self.selfUser.screen_name.toLowerCase(); + if (done) done(); + } + + function cacheCredentials(data) { + var json = JSON.stringify(data); + console.log('caching credentials'); + window.db.transaction(function(tx) { + executeSql(tx, "insert or replace into info_keyed values (?, ?)", + [self.username, json]); + }, errorFunc('error caching credentials')); + } + + function doVerifyRequest() { + var url = self.apiRoot + 'account/verify_credentials.json'; + self.urlRequest(url, + function (data) { + console.log('verify_credentials call returned successfully'); + cacheCredentials(data); + onJSON(data); + }, + function (error) { + console.error('could not verify credentials: ' + error.message); + if (retried) { + console.error('onError is ' + onError); + if (onError) onError(); + } else { + console.error('retrying in 1min.'); + setTimeout(function() { + self.verifyCredentials(done, onError, true); + }, 1000 * 60); + } + } + ); + } + + console.log('checking for credentials in database.'); + window.db.transaction(function(tx) { + function success(tx, result) { + if (result.rows.length) { + onJSON(JSON.parse(result.rows.item(0).value)); + } else { + doVerifyRequest(); + } + } + executeSql(tx, "select value from info where key == ?", + [self.username], success, doVerifyRequest); + }); + }); + }, + + getTweetType: function(t) { + var tweet_type = 'timeline'; + + if (this.isSelfTweet(t)) + tweet_type = 'sent'; + else if (t.mention) + tweet_type = 'mention'; + else if (t.search) + tweet_type = 'search'; + else if (t.sender_id) + tweet_type = 'direct'; + return tweet_type; + }, + + getFollowingUsers: function() { + var self = this, + url = this.apiRoot + 'statuses/friends.json'; + this.pagedUrlRequest(url, { + itemsKey: 'users', + success: function (users) { + if (!users.length) + console.warn('no users returned in success callback for getFollowingUsers'); + else + // save users out to database. + window.db.transaction(function (tx) { + $.each(users, function(i, user) { + self.sql.users.insertOrReplace(tx, user); + }); + }, function (err) { + console.error('error caching users following: ' + err); + }); + }, + error: function (xhr, error) { + console.error('error getting users we are following: ' + error); + } + }); + }, + + getFollowing: function(opts) { + var opts = opts || {}; + + var self = this; + if (!this.didGetFollowing) { + this.didGetFollowing = true; + var followingUrl = this.apiRoot + 'friends/ids.json'; + function success(data) { + self.notifyClients('didReceiveFollowing', data); + var following = JSON.parse('['+data+']'); + self.maybeGetAllUsers(following); + self.following_ids = set(following); + if (opts.success) + opts.success(); + } + this.urlRequest(followingUrl, success, errorFunc('retreiving IDs the user is following'), + {dataType: undefined}); + } + }, + + maybeGetAllUsers: function(following) { + // if there are any IDs in following that we don't have users for, go + // get them from the network. + var self = this, missing = false; + $.each(following, function (i, id) { + if (!(id in self.users)) { + self.getFollowingUsers(); + return false; + } + }); + }, + + getTrends: function() { + var self = this; + function success(data) { self.notifyClients('didReceiveTrends', data); } + var url = this.searchApiRoot + 'trends/current.json'; + this.urlRequest(url, success, errorFunc('error retrieving trends')); + }, + + onRealTimeTweet: function(tweetjson) { + var self = this; + + if ('delete' in tweetjson) + return this.onRealTimeDeleteTweet(tweetjson); + + // realtime tweet stream also includes @replys to people in the following + // list, so filter out any tweets from users not in our following_ids set + if (this.showAllRealtime || + (this.following_ids && tweetjson.user.id in this.following_ids)) { + + // filter out replies from people we're following to people we are + // NOT following, to match the website. (settting?) + var reply_to = tweetjson.in_reply_to_user_id; + if (!reply_to || reply_to in this.following_ids) { + var tweet = this.makeTweetFromNetwork(tweetjson); + assert(tweet !== undefined); + self.appendTweet(tweet); + if (!this.isSelfTweet(tweet)) + showPopupsForTweets(this, [tweet]); + this.cacheTweets([tweet]); + this.doNotifyUnread(); + } + } + }, + + onRealTimeDeleteTweet: function(tweetjson) { + console.log('TODO: streaming API sent delete notice for status ' + tweetjson.status.id); + }, + + getUsers: function(opts, justFollowing) { + if (justFollowing === undefined) + justFollowing = true; + + // if justFollowing is true (the default), only return users with ids + // in this.following_ids + var usersMap; + if (justFollowing && this.following_ids) { + usersMap = {}; + var followingIds = this.following_ids; + $.each(this.users, function (id, user) { + if (id in followingIds) usersMap[id] = user; + }); + } else + usersMap = this.users; + + if (opts.success) + opts.success(usersMap); + }, + + addClient: function(client) { + this.clients.push(client); + }, + + changeState: function(state) { + console.warn('changeState: ' + state); + + if (state === 'oautherror' && this.state !== 'oautherror') { + this.state = state; + this.notifyClients('stateChanged', 'oautherror'); + this.stopTimers('oautherror'); + } else if (state === 'autherror') { + this.state = state; + this.notifyClients('stateChanged', 'autherror'); + this.stopTimers('autherror'); + } else if (state == 'connfail') { + if (this.state !== 'autherror') { // do not go from autherror to connfail + this.state = state; + this.notifyClients('stateChanged', 'connfail'); + this.stopTimers('connection fail'); + } + } else if (state == 'online') { + if (this.state === undefined) { + this.state = 'online'; + this.notifyClients('stateChanged', 'online'); + } + } else { + console.error('changeState got unknown state: ' + state); + } + }, + + notifyClients: function(funcName/*, *args */) { + var args = Array.apply(null, arguments); + args.shift(); + + $.each(this.clients, function(i, client) { + client[funcName].apply(client, args); + }); + }, + + notifyFeeds: function(name) { + var feedsJSON = $.map(this.customFeeds, function (feed) { + return feed.serialize ? feed.serialize() : null; + }); + this.notifyClients(name || 'feedsUpdated', feedsJSON); + }, + + getTweet: function(id, success, error) { + var self = this; + + // first, try loading the tweet from the cache + var cacheSuccess = function(tx, tweets) { + if (tweets.length !== 1) { + console.log('cacheSuccess expected one tweet, got ' + tweets.length); + return loadFromNetwork(); + } + success(tweets[0]); + }; + + var cacheError = function(err) { + console.error('could not retreive tweet ' + id + ' from the database: ' + err.message); + loadFromNetwork(); + }; + + // if that fails, grab it from the network + var loadFromNetwork = function() { + function urlSuccess(data) { + var tweet = self.makeTweetFromNetwork(data); + self.cacheTweets([tweet], function() { success(tweet); }); + } + + var url = self.apiRoot + 'statuses/show/' + id + '.json'; + self.urlRequest(url, urlSuccess, error); + }; + + this.loadCachedTweets({id: id, limit: 1, success: cacheSuccess, error: cacheError}); + }, + + /** + * Handles X-RateLimit- HTTP response headers indicating API request + * limits. + */ + handleRateLimits: function(xhr, textStatus) { + var r = {limit: xhr.getResponseHeader('X-RateLimit-Limit'), + remaining: xhr.getResponseHeader('X-RateLimit-Remaining'), + reset: xhr.getResponseHeader('X-RateLimit-Reset')}; + + var dateStr = xhr.getResponseHeader('Date'); + if (dateStr) { + var date = new Date(dateStr); + if (date) receivedCorrectTimestamp(date, true); + } + + + if (r.limit && r.remaining && r.reset) { + this.rateLimit = r; + if (parseInt(r.remaining, 10) < 20) + console.log('remaining API requests: ' + r.remaining); + } + }, + + /** + * Sends a tweet or direct. + * replyTo can be an id that the tweet is in reply to + * success is called with the tweet object + * error is called with an exception object + */ + tweet: function(text, replyTo, success, error) { + // make "d screen_name message" a direct message + var direct = text.match(/^d\s+(\S+)\s+(.+)/); + if (direct) { + var screen_name = direct[1]; + if (screen_name.charAt(screen_name.length-1) === ':') + screen_name = screen_name.slice(0, screen_name.length-1); + text = direct[2]; + return this.direct(screen_name, text, success, error); + } + + var self = this; + var opts = {status: text, + source: 'digsby'}; + if (replyTo) + opts.in_reply_to_status_id = replyTo; + + var url = this.apiRoot + 'statuses/update.json'; + + function _success(data) { + self.notifyClients('statusUpdated'); + var tweet = self.makeTweetFromNetwork(data); + self.appendTweet(tweet, {ignoreIds: true}); + self.cacheTweets([tweet], function() { + var t = notifyTweet(self, tweet); + self.notifyClients('selfTweet', t); + if (success) success(t); + }); + } + + this.urlRequest(url, _success, error, {type: 'POST', data: opts}); + }, + + direct: function(screen_name, text, success, error) { + var self = this, + opts = {text: text, screen_name: screen_name}, + url = this.apiRoot + 'direct_messages/new.json'; + + var _success = function(data) { + self.notifyClients('directSent'); + var direct = self.makeDirectFromNetwork(data); + self.appendTweet(direct); + self.cacheDirects([direct], function() { + if (success) success(notifyTweet(self, direct)); + }); + }; + + this.urlRequest(url, _success, extractJSONError('Error sending direct message', error), {type: 'POST', data: opts}); + }, + + deleteTweet: function(opts) { + return this.deleteItem('tweets', 'statuses/destroy/', opts); + }, + + deleteDirect: function(opts) { + return this.deleteItem('directs', 'direct_messages/destroy/', opts); + }, + + deleteItem: function(sql, urlPart, opts) { + var self = this; + + function success(tweet) { + useStringIdentifiers(tweet, ['id', 'in_reply_to_status_id']); + console.log('successfully deleted ' + sql + ' ' + tweet.id); + self.discard(sql, [tweet.id]); // remove from sql cache + delete self.tweets[tweet.id]; // remove from memory cache + // remove from any feeds. + $.each(self.feeds, function (i, feed) { feed.removeItem(tweet); }); + + if (opts.success) { + // scroll to bottom if necessary. + if (self.timeline) + self.timeline.scrollToBottomIfAtBottom(function() { opts.success(tweet); }); + else + opts.success(tweet); + } + } + + return this.urlRequest(this.apiRoot + urlPart + opts.id + '.json', + success, + opts.error, + {type: 'POST'}); + }, + + follow: function(opts) { + console.warn('follow: ' + JSON.stringify(opts)); + + if (opts.screen_name) { + function success(data) { + console.warn('follow success:'); + console.warn(JSON.stringify(data)); + if (opts.success) + opts.success(); + } + + function error(err) { + console.error('error following' + opts.screen_name); + printException(err); + if (opts.error) opts.error(err); + } + + this.urlRequest(this.apiRoot + 'friendships/create.json', + success, opts.error || errorFunc('error following'), + {type: 'POST', data: {screen_name: opts.screen_name}}); + } + }, + + appendTweet: function(tweet, opts) { + if (this.timeline) { + var self = this; + this.timeline.scrollToBottomIfAtBottom(function() { + opts = opts || {}; + opts.scroll = false; + self.newTweets([tweet], undefined, true, opts); + }); + } + }, + + newTweets: function (tweets, source, updateNow, opts) { + if (opts === undefined) opts = {}; + opts.account = this; + callEach(this.feeds, 'addSorted', tweets, source, updateNow, opts); + var timeline = this.feeds.timeline; + if (this.lastNotifiedMaxId === undefined || + this.lastNotifiedMaxId !== timeline.source.maxId) { + var recentTweets = timeline.items.slice(-200); + this.notifyClients('recentTimeline', recentTweets); + } + }, + + /** + * twitter API has paged requests using a "cursor" parameter + */ + pagedUrlRequest: function(url, opts) { + if (opts.itemsKey === undefined) + throw 'pagedUrlRequest: must provide "itemsKey" in opts' + + var self = this, + nextCursor = -1; + allData = []; + + function _success(data) { + // append data + arrayExtend(allData, data[opts.itemsKey]); + nextCursor = data.next_cursor; + + if (nextCursor === 0 || nextCursor === undefined) // next_cursor 0 means we hit the end + return opts.success(allData); + else + nextRequest(); + } + + function nextRequest() { + opts.data = {cursor: nextCursor}; + self.urlRequest(url, _success, opts.error, opts); + } + + nextRequest(); + }, + + /* currently unused */ + getFollowedBy: function(_success) { + var self = this; + var url = this.apiRoot + 'followers/ids.json'; + this.pagedUrlRequest(url, { + itemsKey: 'ids', + success: function (ids) { + self.followers = set(ids); + if (_success) + _success(ids); + }, + error: errorFunc('retreiving followers') + }); + }, + + urlRequest: function(url, success, error, opts) { + var self = this; + opts = opts || {}; + + function _error(xhr, textStatus, errorThrown) { + if (xhr.status === 401) { + console.error('authentication error'); + + if (!self.didUpdate) { + if (self.oauth) + self.changeState('oautherror'); + else + // ignore auth errors after already connected + self.changeState('autherror'); + } + } + + if (error) + error(xhr, textStatus, errorThrown); + } + + var httpType = opts.type || 'GET'; + console.log(httpType + ' ' + url); + if (opts.data) + console.log(JSON.stringify(opts.data)); + + var basicAuthUsername, basicAuthPassword; + var authHeader; + + if (url.substr(0, this.searchApiRoot.length) !== this.searchApiRoot) { + if (this.oauth) { + if (!opts.data) opts.data = {}; + authHeader = this.getOAuthHeader({url: url, method: httpType, data: opts.data}); + } else { + basicAuthUsername = encodeURIComponent(this.username); + basicAuthPassword = encodeURIComponent(this.password); + } + } + + return $.ajax({ + url: url, + success: success, + error: _error, + type: httpType, + data: opts.data, + + dataType: opts.dataType || 'json', + dataFilter: function (data, type) { + var obj; + if (type.toLowerCase() === 'json') { + try { + obj = JSON.parse(data); + } catch (err) { + // show what the bad data was, if it wasn't JSON + console.error('ERROR parsing JSON response, content was:'); + console.error(data); + throw err; + } + + return obj; + } else + return data; + }, + + username: basicAuthUsername, + password: basicAuthPassword, + + complete: this.handleRateLimits, + beforeSend: function(xhr) { + xhr.setRequestHeader('User-Agent', 'Digsby'); + if (authHeader) + xhr.setRequestHeader('Authorization', authHeader); + } + }); + }, + + getOAuthHeader: function(opts) { + var paramList = []; + for (var k in opts.data) + paramList.push([k, opts.data[k]]); + + paramList.sort(function(a, b) { + if (a[0] < b[0]) return -1; + if (a[0] > b[0]) return 1; + return 0; + }); + + var message = { + action: opts.url, + method: opts.method, + parameters: paramList + }; + + var accessor = { + consumerKey: this.oauth.consumerKey, + consumerSecret: this.oauth.consumerSecret, + + token: this.oauth.tokenKey, + tokenSecret: this.oauth.tokenSecret + }; + + OAuth.completeRequest(message, accessor); + return OAuth.getAuthorizationHeader(undefined, message.parameters) + }, + + favorite: function(tweet, onSuccess, onError) { + var self = this; + var url, favoriting; + if (tweet.favorited) { + url = this.apiRoot + 'favorites/destroy/' + tweet.id + '.json'; + favoriting = false; + } else { + url = this.apiRoot + 'favorites/create/' + tweet.id + '.json'; + favoriting = true; + } + + // TODO: this is the same as getTweet + function urlSuccess(data) { + var tweet = self.makeTweetFromNetwork(data); + tweet.favorited = favoriting; + self.cacheTweets([tweet], function() { if (onSuccess) {onSuccess(tweet);} }); + } + + this.urlRequest(url, urlSuccess, onError, {type: 'POST'}); + }, + + cacheFavorited: function(tweetIds, favorited) { + var ids = '(' + joinSingleQuotedStringArray(tweetIds, ', ') + ')'; + var sql = 'update or ignore tweets set favorited = ' + (favorited ? '1' : '0') + ' where id in ' + ids; + window.db.transaction(function(tx) { + executeSql(tx, sql, [], null, errorFunc('error unfavoriting items')); + }); + }, + + makeTweetFromNetwork: function(item, markRead) { + useStringIdentifiers(item, ['id', 'in_reply_to_status_id']); + + if (item.id in this.tweets) + return this.tweets[item.id]; + + item = transformRetweet(item); + + var tweet = this.makeTweet(item); + var user = tweet.user; + // tweets from network have full User JSON + this.users[user.id] = user; + // but we'll just store ID in memory and on disk + tweet.user = user.id; + // categorize mentions as such if they contain @username + tweet.mention = this.isMention(tweet); + + if (this.isSelfTweet(tweet)) { + this.maybeNotifySelfTweet(tweet); + tweet.read = 1; + } else if (markRead) { + tweet.read = 1; + } else { + tweet.read = 0; + } + + return tweet; + }, + + makeDirect: function(item, override) { + var direct = this.sql.directs.fromJSON(item, override); + this.tweets[direct.id] = direct; + direct.toString = directToString; + direct.created_at_ms = new Date(direct.created_at).getTime(); + direct.user = direct.sender_id; + return direct; + }, + + makeDirectFromNetwork: function(item, markRead) { + useStringIdentifiers(item, ['id']); + if (item.id in this.tweets) + return this.tweets[item.id]; + var direct = this.makeDirect(item); + + this.users[direct.user] = item.sender; + direct.read = (markRead || this.isSelfTweet(direct)) ? 1 : 0; + + return direct; + }, + + makeTweet: function(item, override) { + var tweet = this.sql.tweets.fromJSON(item, override); + this.tweetIn(tweet); + return tweet; + }, + + tweetIn: function(tweet) { + var oldTweet = this.tweets[tweet.id]; + if (oldTweet && oldTweet.read !== undefined) + tweet.read = oldTweet.read; + this.tweets[tweet.id] = tweet; + tweet.toString = tweetToString; + tweet.created_at_ms = new Date(tweet.created_at).getTime(); + }, + + cacheTweets: function (tweets, ondone) { + return this.cacheItems(tweets, ondone, this.sql.tweets); + }, + + cacheDirects: function (directs, ondone) { + return this.cacheItems(directs, ondone, this.sql.directs); + }, + + cacheItems: function (tweets, ondone, sql) { + if (tweets.length === 0) { + if (ondone) guard(ondone); + return; + } + + console.log("cacheItems() is saving " + tweets.length + " items to " + sql); + + var self = this; + window.db.transaction(function(tx) { + // insert tweets + $.each(tweets, function (i, tweet) { + + sql.insertOrReplace(tx, tweet); + }); + + // update users + $.each(tweets, function (i, tweet) { + if (tweet.user !== null) + self.sql.users.insertOrReplace(tx, self.users[tweet.user]); + }); + + }, function (error) { + console.error('Failed to cache tweets to database: ' + error.message); + if (ondone) ondone(); + }, function () { + if (ondone) ondone(); + }); + }, + + discard: function(tablename, ids) { + if (ids.length === 0) return; + ids = '(' + joinSingleQuotedStringArray(ids, ', ') + ')'; + var deleteStatement = 'delete from ' + tablename + ' where id in ' + ids; + window.db.transaction(function (tx) { + executeSql(tx, deleteStatement, [], null, errorFunc('discarding ' + tablename)); + }); + }, + + markAllAsRead: function() { + this.markTweetsAsRead(this.tweets); + }, + + markFeedAsRead: function(name) { + if (name in this.feeds) + this.markTweetsAsRead(this.feeds[name].items); + }, + + toggleAddsToCount: function(name) { + var feed = this.feeds[name]; + if (feed) { + feed.noCount = !feed.noCount; + this.notifyFeeds(); + this.doNotifyUnread(); + } + }, + + markTweetsAsRead: function(tweets) { + var self = this; + $.each(tweets, function (i, t) { self.markAsRead(t); }); + }, + + markAsRead: function(item) { + var self = this; + + if (!item || item.read) return; + var id = item.id; + + //console.log('markAsRead: ' + item.text); + item.read = 1; + if (item.feeds) + $.each(item.feeds, function(i, feed) { feed.markedAsRead(item); }); + + self.doNotifyUnread(); + + if (this.markAsReadLater === undefined) + this.markAsReadLater = {}; + + this.markAsReadLater[id] = true; + + if (this.markAsReadTimer === undefined) { + var unreadTimerCb = function () { + self.recordReadTweets(); + }; + this.markAsReadTimer = this.ownerWindow.setTimeout(unreadTimerCb, 1000); + } + }, + + connectionFailed: function() { + this.notifyClients('connectionFailed'); + if (!this.didUpdate) + // if this is the first login attempt, just stop timers and die + this.stopTimers('connectionFailed'); + }, + /** + * calls onRead on all clients with {name: unreadCount, ...} for + * each feed. + */ + doNotifyUnread: function(force) { + var self = this; + if (this.notifyUnreadTimer === undefined) + this.notifyUnreadTimer = setTimeout(function() { + self._notifyUnread(self); + }, 200); + }, + + _notifyUnread: function(self) { + delete self.notifyUnreadTimer; + var unread = {}; + var feeds = $.map(self.customFeeds, function (feed) { + if (!feed.addsToUnreadCount()) + // don't include tweets from unsaved searches + return null; + + $.each(feed.items, function (i, tweet) { + if (!tweet.read) + unread[tweet.id] = true; + }); + + return feed.serialize(); + }); + + self.notifyClients('onUnread', { + feeds: feeds, + total: objectKeys(unread).length + }); + }, + + recordReadTweets: function() { + delete this.markAsReadTimer; + + var ids = []; + for (var id in this.markAsReadLater) { + if (this.markAsReadLater[id]) + ids.push(id); + } + + this.markAsReadLater = []; + + if (ids.length) { + var idsSql = joinSingleQuotedStringArray(ids, ', '); + var sql1 = 'UPDATE Tweets SET read=1 WHERE Tweets.id IN (' + idsSql + ')'; + // TODO: hack. we're using two tables, don't do this + var sql2 = 'UPDATE Directs SET read=1 WHERE Directs.id IN (' + idsSql + ')'; + window.db.transaction(function(tx) { + executeSql(tx, sql1); + executeSql(tx, sql2); + }, function (error) { + console.error('failed to mark as read: ' + error.message); + console.error('sql was:'); + console.error(sql1); + console.error(sql2); + }); + } + }, + + loadAllCached: function(opts) { + var self = this, allTweets = []; + + self.loadCachedUsers({tx: opts.tx, error: opts.error, success: function(tx) { + self.loadCachedTweets({tx: tx, error: opts.error, success: function(tx, tweets) { + opts.success('tweets', tweets); + + // immediately notify the GUI of the newest self tweet we know about. + self.possibleSelfTweets(tweets); + + self.loadCachedDirects({tx: tx, error: opts.error, success: function (tx, directs) { + opts.success('directs', directs); + }}); + }}); + }}) + }, + + possibleSelfTweets: function (tweets) { + var selfTweet = this.findNewestSelfTweet(tweets); + if (selfTweet) this.maybeNotifySelfTweet(selfTweet); + }, + + maybeNotifySelfTweet: function(tweet) { + if (!this.newestSelfTweet || this.newestSelfTweet.created_at_ms < tweet.created_at_ms) { + this.newestSelfTweet = notifyTweet(this, tweet); + this.notifyClients('selfTweet', this.newestSelfTweet); + } + }, + + findNewestSelfTweet: function (tweets) { + var self = this, newest; + $.each(tweets, function (i, tweet) { + if (self.isSelfTweet(tweet) && tweet.created_at_ms && + (!newest || tweet.created_at_ms > newest.created_at_ms)) + newest = tweet; + }); + + if (newest) + return newest; + }, + + addGroupsFromLists: function() { + var self = this; + this.getLists(function (groups) { + $.each(groups, function (i, group) { + self.addGroup(group, false); + }); + }); + }, + + getLists: function(success) { + var self = this, + urlPrefix = this.apiRoot + this.selfScreenNameLower + '/'; + + var getListsSuccess = success; + var groups = []; + + self.pagedUrlRequest(urlPrefix + 'lists.json', { + itemsKey: 'lists', + error: errorFunc('could not get list ids'), + success: function (lists) { + var i = 0; + + function nextGroup() { + var list = lists[i++]; + if (!list) return getListsSuccess(groups); + var group = {type: 'group', groupName: list.name, + filter: false, popups: false, ids: []}; + var url = urlPrefix + list.slug + '/members.json'; + self.pagedUrlRequest(url, { + itemsKey: 'users', + error: errorFunc('error retrieving ' + url), + success: function (users) { + $.each(users, function (i, user) { group.ids.push(user.id); }); + groups.push(group); + nextGroup(); + } + }); + } + + nextGroup(); + } + }); + }, + + cachedDirectColumnNames: 'directs.id as direct_id, users.id as user_id, *', + + loadCachedDirects: function(opts) { + var self = this; + + function success(tx, result) { + var directs = []; + for (var i = 0; i < result.rows.length; ++i) { + var row = result.rows.item(i); + var direct_id = row.direct_id, user_id = row.user_id; + if (user_id) + self.users[user_id] = self.sql.users.fromJSON(row, {id: user_id}); + var direct = self.makeDirect(row, {id: direct_id}); + directs.push(direct); + } + directs.reverse(); + if (opts.success) opts.success(tx, directs); + } + + function error(tx, err) { + if (opts.error) opts.error(err); + else console.error('could not load cached directs: ' + err.message); + } + + return this.loadCachedItems(opts, this.cachedDirectColumnNames, 'Directs', 'sender_id', success, error); + }, + + cachedTweetColumnNames: 'tweets.id as tweet_id, users.id as user_id, tweets.profile_image_url as tweet_image, users.profile_image_url as user_image, *', + + loadCachedTweets: function(opts) { + var self = this; + + function success(tx, result) { + var tweets = []; + for (var i = 0; i < result.rows.length; ++i) { + var row = result.rows.item(i); + + // avoid id name clash + var tweet_id = row.tweet_id, + user_id = row.user_id, + user_image = row.user_image, + tweet_image = row.tweet_image; + + if (user_id) + self.users[user_id] = self.sql.users.fromJSON(row, {id: user_id, profile_image_url: user_image}); + var tweet = self.makeTweet(row, {id: tweet_id, profile_image_url: tweet_image}); + + tweets.push(tweet); + } + + tweets.reverse(); + + if (opts.success) + opts.success(tx, tweets); + } + + function error(tx, err) { + if (opts.error) opts.error(err); + else console.error('could not load cached tweets: ' + err.message); + } + + return this.loadCachedItems(opts, this.cachedTweetColumnNames, 'Tweets', 'user', success, error); + }, + + loadCachedItems: function(opts, columnNames, tableName, userColumn, success, error) { + /* id: adds 'WHERE tweet.id == ' + limit: adds 'LIMIT ' */ + + var self = this; + + var args = []; + var innerQuery = "SELECT " + columnNames + " FROM " + tableName + + " LEFT OUTER JOIN Users ON " + tableName + "." + userColumn + " = Users.id"; + + // WHERE tweet_id + if (opts.id) { + innerQuery += ' WHERE tweet_id==?'; + args.push(opts.id); + } + + innerQuery += " ORDER BY " + tableName + ".id DESC"; + + // LIMIT clause + if (opts.limit) { + innerQuery += ' LIMIT ?'; + args.push(opts.limit); + } + + var query = "SELECT * FROM (" + innerQuery + ")"; + + console.log(query); + useOrCreateTransaction(opts.tx, query, args, success, error); + }, + + getUsersToKeep: function() { + var excludedUserIds = []; + if (this.following_ids) + arrayExtend(excludedUserIds, objectKeys(this.following_ids)); + var groupIds = this.getAllGroupIds(); + if (groupIds) + arrayExtend(excludedUserIds, groupIds); + if (this.selfUser) + excludedUserIds.push(this.selfUser.id); + return excludedUserIds; + }, + + discardOldUsers: function(opts) { + // discards all users not referenced by tweets or directs + opts = opts || {}; + + var excludedUserIds = this.getUsersToKeep(); + + // don't discard users we're following either + var appendClause = ''; + if (excludedUserIds.length) { + var usersFollowing = excludedUserIds.join(', '); + appendClause = 'and id not in (' + usersFollowing + ')'; + } + + var query = 'delete from users where id not in (select sender_id from directs union select user from tweets union select recipient_id from directs)' + appendClause; + useOrCreateTransaction(opts.tx, query, [], opts.success, opts.error); + }, + + loadCachedUsers: function(opts) { + var self = this, + query = 'SELECT * FROM Users'; + + function success(tx, result) { + var added = 0; + for (var i = 0; i < result.rows.length; ++i) { + var row = result.rows.item(i); + if (!(row.id in self.users)) { + self.users[row.id] = self.sql.users.fromJSON(row); + added += 1; + } + } + + console.log('loadCachedUsers loaded ' + added + ' users'); + if (opts.success) + opts.success(tx); + } + + function error(err) { + console.error('error loading cached users: ' + err); + if (opts.error) + opts.error(err); + } + + useOrCreateTransaction(opts.tx, query, [], success, error); + }, + + inviteFollowers: function() { + var self = this; + function cb(followers) { + followers = $.grep(followers, function() { return true; }); + console.log('followers:'); + console.log(JSON.stringify(followers)); + + if (!followers.length) + return; + + // pick up to 200 random + arrayShuffle(followers); + var f2 = []; + for (var i = 0; i < 200 && i < followers.length; ++i) + f2.push(followers[i]); + followers = f2; + + var idx = 0, errCount = 0; + var errCount = 0; + + function _err() { if (++errCount < 5) _direct(); } + + function _direct() { + var id = followers[idx++]; + if (id === undefined) return; + var data = {text: self.invite_message, user_id: id}; + self.urlRequest(self.apiRoot + 'direct_messages/new.json', + _direct, _err, {type: 'POST', data: data}); + } + + _direct(); + } + + if (this.followers) + cb(this.followers); + else + this.getFollowedBy(cb); + } +}; + +function tweetUserImage(account, tweet) { + var userId = tweet.user; + if (userId) { + var user = account.users[userId]; + if (user) + return user.profile_image_url; + } + + return tweet.profile_image_url; +} + +function tweetUserName(account, tweet) { + var userId = tweet.user; + if (userId) { + var user = account.users[userId]; + if (user) return user.name; + } + + return tweet.from_user; +} + +function tweetScreenName(account, tweet) { + var userId = tweet.user; + if (userId) { + var user = account.users[userId]; + if (user) return user.screen_name; + } + + return tweet.from_user; +} + +function directTargetScreenName(account, direct) { + var userId = direct.recipient_id; + if (userId) { + var user = account.users[userId]; + if (user) return user.screen_name; + } +} + +function TimelineSource() { + this.maxId = '0'; +} + +TimelineSource.prototype = { + toString: function() { + return '<' + this.constructor.name + ' ' + this.url + '>'; + } +}; + +function TwitterTimelineSource(account, url, data) { + if (account === undefined) // for inheritance + return; + + this.account = account; + this.url = url; + this.data = data; + this.count = 100; + this.lastUpdateTime = 0; + this.lastUpdateStatus = undefined; + this.limitById = true; + this._updateFrequency = 1000 * 60; +} + +TwitterTimelineSource.prototype = new TimelineSource(); +TwitterTimelineSource.prototype.constructor = TwitterTimelineSource; + +TwitterTimelineSource.prototype.updateFrequency = function() { + return this._updateFrequency; +}; + +TwitterTimelineSource.prototype.setUpdateFrequency = function(updateFreqMs) { + this._updateFrequency = updateFreqMs; +}; + +TwitterTimelineSource.prototype.shouldMarkIncomingAsRead = function() { + var markReadNow = this.markRead; + if (this.markRead) + this.markRead = false; + return markReadNow; +}; + +TwitterTimelineSource.prototype.markNextUpdateAsRead = function() { + this.markRead = true; +} + +TwitterTimelineSource.prototype.ajaxSuccess = function(data, status, success, error, since_id) { + try { + var self = this; + data.reverse(); + + var newTweets = []; + + var markRead = self.shouldMarkIncomingAsRead(); + $.each(data, function (i, item) { + var tweet = self.account.makeTweetFromNetwork(item, markRead); + if (since_id && compareIds(tweet.id, since_id) < 0) { + console.warn('IGNRORING TWEET WITH ID ' + tweet.id + ' LESS THAN since_id ' + since_id); + } else { + if (self.feed.alwaysMentions) + tweet.mention = 1; + self.updateMinMax(tweet.id); + newTweets.push(tweet); + } + }); + + console.log("requestTweets loaded " + data.length + " tweets from the network"); + + if (success) guard(function() { + success(newTweets); + }); + } catch (err) { + if (error) { + printStackTrace(); + error(err); + } + else throw err; + } +}; + +/** + * returns a simple jsonable object that gets passed to fire() + */ +var notifyTweet = function(account, t) { + return {text: t.text, + user: {screen_name: tweetScreenName(account, t), + profile_image_url: tweetUserImage(account, t)}, + id: t.id, + favorited: t.favorited, + created_at: t.created_at, + created_at_ms: t.created_at_ms, + tweet_type:account.getTweetType(t), + + // directs + sender_id: t.sender_id, + recipient_id: t.recipient_id}; +}; + +function UpdateNotification(account, count, opts) +{ + assert(account); + assert(count !== undefined); + + this.account = account; + this.count = count; + this.tweets = []; + this.opts = opts || {}; + this.successCount = 0; +} + +UpdateNotification.prototype = { + success: function(source, tweets) { + this.successCount++; + + assert(source && tweets); + arrayExtend(this.tweets, tweets); + this.onDone(); + + if (this.opts.success) + this.opts.success(source, tweets); + }, + + error: function(source) { + this.onDone(); + }, + + onDone: function() { + //console.warn('UpdateNotification.onDone: ' + (this.count-1)); + var account = this.account; + if (--this.count === 0) { + if (!this.successCount) + account.connectionFailed(); + else { + showPopupsForTweets(account, this.tweets); + if (account.timeline) + account.timeline.finish(false); + + if (this.opts.onDone) + this.opts.onDone(this.tweets); + + account.discardOldTweets(); + account.doNotifyUnread(); + } + } + } +}; + +function uniqueSortedTweets(tweets, timeline) { + function tweetCmp(a, b) { + // directs first, then mentions, then by id, then if it's in the timeline + return (-cmp(account.isDirect(a), account.isDirect(b)) + || -cmp(account.isMention(a), account.isMention(b)) + || -cmp(timeline.hasTweet(a), timeline.hasTweet(b)) + || cmp(a.created_at_ms, b.created_at_ms) + || compareIds(a.id, b.id)); + } + + tweets.sort(tweetCmp); + return uniquify(tweets, function(a) { return a.id; }); +} + +var shownPopupIds = {}; +var lastPopupIdTrim = 0; +var ONE_HOUR_MS = 60 * 1000 * 60; + +function maybeTrimPopupIds(now) { + + if (now - lastPopupIdTrim < ONE_HOUR_MS) + return; + + lastPopupIdTrim = now; + + var toDelete = []; + $.each(shownPopupIds, function (id, time) { + if (now - time > ONE_HOUR_MS) + toDelete.push(id); + }); + + $.each(toDelete, function (i, id) { delete shownPopupIds[id]; }); +} + +function firePopup(tweets, opts) { + if (opts === undefined) + opts = {}; + + opts.topic = 'twitter.newtweet'; + opts.tweets = tweets; + + D.rpc('fire', opts); +} + +var _postfix_count = 0; +function showPopupForTweet(account, tweet) { + _postfix_count++; + return firePopup([notifyTweet(tweet)], {popupid_postfix: _postfix_count}); +} + +function showPopupsForTweets(account, tweets) { + var feedsFilteringPopups = []; + + // collect feeds with popups: false + $.each(account.feeds, function (name, feed) { + if (feed.popups !== undefined && !feed.popups) + feedsFilteringPopups.push(feed); + }); + + var notifyTweets = $.map(tweets, function (t) { + // never show self tweets + if (account.isSelfTweet(t)) + return null; + + // don't show repeats + if (t.id in shownPopupIds) + return null; + + if (t.read) + return null; + + // exclude popups for feeds with popups: false + for (var j = 0; j < feedsFilteringPopups.length; ++j) { + if (feedsFilteringPopups[j].hasTweet(t, account)) + return null; + } + + return notifyTweet(account, t); + }); + + if (!notifyTweets.length) + return; + + notifyTweets = uniqueSortedTweets(notifyTweets, account.feeds.timeline); + + var now = new Date().getTime(); + $.each(notifyTweets, function (i, tweet) { shownPopupIds[tweet.id] = now; }); + maybeTrimPopupIds(now); + + return firePopup(notifyTweets); +} + +TwitterTimelineSource.prototype.cache = function(tweets) { + this.account.cacheTweets(tweets); +}; + +TwitterTimelineSource.prototype.update = function(updateNotification) { + var self = this, account = this.account; + + if (this.updating) { + console.warn('WARNING: ' + this + ' is already updating'); + this.updating.error(self); + } + + this.updating = updateNotification; + + this.loadNewer(function (tweets, info) { + if (info) { + console.warn('url request got ' + tweets.length + ' tweets: ' + info.url); + } + var extraUpdate = (self.feed.extraUpdate || function(t, d) { d(t); }); + extraUpdate.call(self, tweets, function(tweets) { + self.cache(tweets); + account.newTweets(tweets, self); + if (self.onUpdate) + guard(function() { self.onUpdate(tweets); }); + if (updateNotification) + updateNotification.success(self, tweets); + }); + self.updating = undefined; + }, function (error) { + console.error('error updating source: ' + error.message + ' ' + error); + self.updating = undefined; + + if (updateNotification) + updateNotification.error(self, error); + }); +}; + +TwitterTimelineSource.prototype.didUpdate = function(status) { + this.lastUpdateTime = new Date().getTime(); + this.lastUpdateStatus = status; +}; + +TwitterTimelineSource.prototype.needsUpdate = function(now) { + if (!this.feed.autoUpdates || + !this.updateFrequency()) // updateFrequency may be 0 (never update this feed) + return false; + + return now - this.lastUpdateTime >= this.updateFrequency(); +}; + +TwitterTimelineSource.prototype.loadNewer = function(success, error, opts) { + var self = this, account = this.account, url = this.url; + + function ajaxError(xhr, textStatus, errorThrown) { + self.didUpdate('error'); + + /* + * search.twitter.com servers can sometimes return 403s if they don't like the since_id you're sending. see + * http://groups.google.com/group/twitter-development-talk/browse_thread/thread/ed72429eef055cb3/23cf597ef030ca62?lnk=gst&q=search+403#23cf597ef030ca62 + * + * if we get a 403 from search.twitter.com, try clearing maxId and trying again. + */ + if (!self._didSearchHack && xhr.status === 403) { + var searchUrl = 'http://search.twitter.com/search.json'; + if (url.substr(0, searchUrl.length) === searchUrl) { + self.maxId = '0'; + self._didSearchHack = true; + console.warn('clearing max id and restarting search'); + return self.loadNewer(success, error, opts); + } + } + + console.log('error for url: ' + url); + console.log(' xhr.status: ' + xhr.status); + console.log(' xhr.statusText: ' + xhr.statusText); + if (errorThrown) + console.log("error exception: " + errorThrown.message); + if (textStatus) + console.log("error with status: " + textStatus); + + if (error) + error(xhr, textStatus, errorThrown); + } + + var oldMaxId = self.maxId; + console.info('requesting tweets, maxId is ' + oldMaxId + ': ' + url); + + function _success(data) { + self.didUpdate('success'); + success(data, {url: url}); + } + + this.request = account.urlRequest(url, function(data, status) { + //for (var i = 0; i < data.length; ++i) + //if (compareIds(data[i].id_str, oldMaxId) < 0) + //console.warn('OLDER ID: ' + data[i].id_str + ' older than old max ' + oldMaxId); + return self.ajaxSuccess(data, status, _success, error, data.since_id); + }, ajaxError, {data: this.makeData()}); +}; + +TwitterTimelineSource.prototype.makeData = function() { + var args = shallowCopy(this.data); + args.count = this.count; + if (this.limitById && compareIds(this.maxId, '0') > 0) + args.since_id = this.maxId; + + return args; +}; + +TwitterTimelineSource.prototype.updateMinMax = function(id) { + if (compareIds(id, this.maxId) > 0) + this.maxId = id; +}; + +function TwitterTimelineDirectSource(account) { + TwitterTimelineSource.call(this, account, account.apiRoot + 'direct_messages.json'); +} + +TwitterTimelineDirectSource.inheritsFrom(TwitterTimelineSource, { + cache: function(directs) { + this.account.cacheDirects(directs); + }, + + ajaxSuccess: function(data, status, success, error) { + var self = this; + var newDirects = []; + data.reverse(); + + var markRead = self.shouldMarkIncomingAsRead(); + function onData(data) { + $.each(data, function (i, item) { + var direct = self.account.makeDirectFromNetwork(item, markRead); + self.updateMinMax(direct.id); + newDirects.push(direct); + }); + } + + onData(data); + + try { + + // 2nd, load send directs. this is a hack and needs to be abstracted into the idea of feeds having multiple sources. + var _interim_success = function(data, status) { + try { + onData(data); + console.log("loaded " + data.length + " directs from the network"); + newDirects.sort(tweetsByTime); + + if (success) + guard(function() { success(newDirects); }); + } catch (err) { + if (error) { + printException(err); + error(err); + } else throw err; + } + }; + + var url2 = self.account.apiRoot + 'direct_messages/sent.json'; + var data = {}; + if (this.maxId && this.maxId !== '0') + data.since_id = this.maxId; + this.request = self.account.urlRequest(url2, _interim_success, error, {data: data}); + + } catch (err) { + if (error) { + printException(err); + error(err); + } else throw err; + } + } +}); + +function TwitterTimelineSearchSource(account, searchQuery) { + this.searchQuery = searchQuery; + TwitterTimelineSource.call(this, account, account.searchApiRoot + 'search.json'); +} + +var searchTweetAttributes = ['id', 'text', 'from_user', 'source', 'profile_image_url', 'created_at']; + + +TwitterTimelineSearchSource.prototype = new TwitterTimelineSource(); + +TwitterTimelineSearchSource.prototype.updateFrequency = function() { + // all searches get their update frequency from the account + return this.account.searchUpdateFrequency; +} + +TwitterTimelineSearchSource.prototype.makeData = function() { + var args = shallowCopy(this.data); + args.rpp = Math.min(100, this.count); + args.q = this.searchQuery; + if (compareIds(this.maxId, '0') > 0) + args.since_id = this.maxId; + + return args; +}; + +TwitterTimelineSearchSource.prototype.ajaxSuccess = function(_data, status, success, error) { + var self = this, data = _data.results, tweets = []; + var account = self.account; + + data.reverse(); + //console.log('TwitterTimelineSearchSource.ajaxSuccess ' + data.length + ' results'); + + window.db.transaction(function (tx) { + function recurseBuild(i) { + if (i === data.length) { + //console.log('search retreived ' + i + ' tweets, calling success'); + if (success) success(tweets); + return; + } + + // TODO: use account.makeTweet here + + var searchTweet = data[i]; + useStringIdentifiers(searchTweet, ['id', 'in_reply_to_status_id']); + var tweet = {truncated: null, + in_reply_to: null, + favorited: null, + in_reply_to_status_id: null, + in_reply_to_user_id: null, + in_reply_to_screen_name: null, + + read: 0, + mention: account.isMention(searchTweet), + search: self.searchQuery}; + + var username = searchTweet.from_user; + var userId = null; + + executeSql(tx, 'SELECT * FROM Users WHERE Users.screen_name == ?', [username], function (tx, result) { + if (result.rows.length) { + // if we already know about the user, use its real id. + var row = result.rows.item(0); + userId = row.id; + if (!account.users[userId]) + account.users[row.user] = account.sql.users.fromJSON(row); + } + + $.each(searchTweetAttributes, function (i, attr) { tweet[attr] = searchTweet[attr]; }); + tweet.user = userId; + if (userId === null) + tweet.from_user = username; + + self.updateMinMax(tweet.id); + account.tweetIn(tweet); + tweets.push(tweet); + + return recurseBuild(i + 1); + }, function (tx, errorObj) { + console.error('error retrieving user w/ screen_name ' + username); + if (error) + error(errorObj); + }); + } + + recurseBuild(0); + }); +}; + +function TwitterHTTPClient(account) { + this.account = account; +} + +TwitterHTTPClient.prototype = { + didFirstUpdate: false, + + didReceiveFollowing: function(following_json) { + D.notify('following', {following: following_json}); + }, + + didReceiveTrends: function(trends) { + D.notify('trends', {trends: trends}); + }, + + serverMessage: function(url, opts) { + D.ajax({url: urlQuery('digsby://' + url, opts), + type: 'JSON'}); + }, + + onReply: function(tweet) { + this.serverMessage('reply', + {id: tweet.id, + screen_name: tweetScreenName(this.account, tweet), + text: tweet.text}); + }, + + onRetweet: function(tweet) { + this.serverMessage('retweet', + {id: tweet.id, + screen_name: tweetScreenName(this.account, tweet), + text: tweet.text}); + }, + + onDirect: function(tweet) { + this.serverMessage('direct', + {screen_name: tweetScreenName(this.account, tweet)}); + }, + + onUnread: function(info) { + D.notify('unread', info); + }, + + selfTweet: function(tweet) { + D.notify('selfTweet', {tweet: tweet}); + }, + + feedsUpdated: function(feeds) { + D.notify('feeds', feeds); + }, + + viewChanged: function(feedName) { + D.notify('viewChanged', {feedName: feedName}); + }, + + stateChanged: function(state) { + D.notify('state', {state: state}); + }, + + didReceiveUpdates: function() { + if (this.didFirstUpdate) + return; + + this.didFirstUpdate = true; + this.account.changeState('online'); + }, + + didReceiveWholeUpdate: function() { + D.notify('received_whole_update'); + }, + + connectionFailed: function() { + if (this.didFirstUpdate) + // connection problems after first update are ignored. + return; + + this.account.changeState('connfail'); + }, + + statusUpdated: function() { + D.notify('hook', 'digsby.twitter.status_updated'); + }, + + directSent: function() { + D.notify('hook', 'digsby.twitter.direct_sent'); + }, + + editFeed: function(feed) { D.notify('edit_feed', feed); }, + + recentTimeline: function(tweets) { + var acct = this.account; + D.notify('recentTimeline', { + tweets: $.map(tweets, function(t) { return notifyTweet(acct, t); }) + }); + } +}; + +var returnFalse = function() { return false; }; + +var tweetActions = { + + container: function(elem) { return $(elem).parents('.container')[0]; }, + + favorite: function(elem) { + var node = this.container(elem); + var skin = account.timeline.skin; + skin.setFavorite(node, 'pending'); + + var tweet = account.timeline.nodeToElem(node); + var originalFavorite = tweet.favorited; + account.favorite(tweet, function (tweet) { + skin.setFavorite(node, tweet.favorited); + }, function (error) { + console.log('error setting favorite'); + skin.setFavorite(node, originalFavorite); + }); + }, + + trash: function (elem) { + var self = this; + guard(function() { + var node = self.container(elem); + var tweet = account.timeline.nodeToElem(node); + var skin = account.timeline.skin; + var opts = { + id: tweet.id, + success: function (tweet) { + // remove it visually + node.parentNode.removeChild(node); // TODO: animate this + }, + error: function(err) { + printException(err); + console.log('error deleting tweet'); + skin.setDelete(node); + }, + }; + + skin.setDelete(node, 'pending'); + if (tweet.sender_id) + account.deleteDirect(opts); + else + account.deleteTweet(opts); + }); + }, + + action: function(action, elem) { + account.notifyClients(action, account.timeline.nodeToElem(this.container(elem))); + }, + + reply: function(elem) { this.action('onReply', elem); }, + retweet: function(elem) { this.action('onRetweet', elem); }, + direct: function(elem) { this.action('onDirect', elem); }, + + popupInReplyTo: function(id) { + id = this.tweets[id].in_reply_to_status_id; + if (!id) return; + account.getTweet(id, function (tweet) { + showPopupForTweet(tweet); + }); + }, + + inReplyTo: function(a) { + var self = this; + + guard(function() { + var timeline = account.timeline; + var container = self.container(a); + var tweet = timeline.nodeToElem(container); + var id = tweet.in_reply_to_status_id; + + // if the CSS transformations here change, please also modify + // feedSwitch in timeline2.js + a.style.display = 'none'; + + account.getTweet(id, function(tweet) { + container.className += ' reply'; + timeline.insertAsParent(tweet, container); + //spinner.parentNode.removeChild(spinner); + }, function (error) { + console.log('error retreiving reply tweet'); + printException(error); + }); + + }); + }, + + openUser: function(a) { + if (!settings.user_feeds) + return true; + + guard(function() { + var screen_name = a.innerHTML; + console.log('openUser ' + screen_name); + var feedDesc = {type: 'user', screen_name: screen_name}; + account.addFeed(feedDesc, true); + }); + + return false; + }, +}; + +function tweetToString() { return ''; } +function directToString() { return ''; } + +/** + * twitter error responses come with nicely formatted error messages in + * a JSON object's "error" key. this function returns an AJAX error handler + * that passes that error message to the given function. + */ +function extractJSONError(errorMessage, errorFunc) { + function error(xhr, textStatus, errorThrown) { + console.error('xhr.repsonseText: ' + xhr.responseText); + + try { + var err = JSON.parse(xhr.responseText).error; + if (err) errorMessage += ': ' + err; + } catch (err) {} + + console.error(errorMessage); + errorFunc(errorMessage); + } + return error; +} + +function transformRetweet(tweet) { + var rt = tweet.retweeted_status; + if (rt && rt.user && rt.user.screen_name) + tweet.text = 'RT @' + rt.user.screen_name + ': ' + rt.text; + + return tweet; +} + +function useOrCreateTransaction(tx, query, values, success, error) { + function execQuery(tx) { executeSql(tx, query, values, success, error); } + if (tx) execQuery(tx); + else db.transaction(execQuery); +} + +var didCacheTimestamp = false; + +// called with the Twitter API server's current time +function receivedCorrectTimestamp(date, cache) { + if (cache || !didCacheTimestamp) { + OAuth.correctTimestamp(Math.floor(date.getTime() / 1000)); + cacheDiffMs(new Date().getTime() - date.getTime(), true); + } +} + +// caches the difference between our time and Twitter server time +lastCachedToDisk = null; + +function cacheDiffMs(diffMs, cache) { + diffMs = parseInt(diffMs, 10); + + // don't recache to disk if unnecessary. + if (!cache && lastCachedToDisk !== null && Math.abs(lastCachedToDisk - diffMs) < 1000*60*2) + return; + + console.log('cacheDiffMs(' + diffMs + ')'); + + window.db.transaction(function(tx) { + executeSql(tx, 'insert or replace into info_keyed values (?, ?)', ['__servertimediff__', diffMs], function() { + didCacheTimestamp = true; + lastCachedToDisk = diffMs; + }, + function(e) { + console.warn('cacheDiffMs DATABASE ERROR: ' + e); + }); + }, errorFunc('error caching server time diff')); +} + +function loadServerTime(done) { + window.db.transaction(function(tx) { + function success(tx, result) { + if (result.rows.length) { + var diffMs = result.rows.item(0).value; + if (diffMs) { + diffMs = parseInt(diffMs, 10); + if (diffMs) { + var d = new Date(new Date().getTime() - diffMs); + receivedCorrectTimestamp(d); + } + } + } else + console.warn('**** no server time cached'); + done(); + } + executeSql(tx, "select value from info_keyed where key == ?", ['__servertimediff__'], success, done); + }); +} + diff --git a/digsby/src/plugins/twitter/res/twittermock.js b/digsby/src/plugins/twitter/res/twittermock.js new file mode 100644 index 0000000..b3fcc37 --- /dev/null +++ b/digsby/src/plugins/twitter/res/twittermock.js @@ -0,0 +1,217 @@ + +function FakeTwitterSource(account, numTweets) { + this.account = account; + this.numTweets = numTweets; + this.usedIds = {}; +} + +FakeTwitterSource.prototype = { + fakeTweetId: function () { + var items = this.account.feeds.timeline.items; + var highestId = items[items.length-1].id; + + var newId = highestId + 1000; + while (newId in this.account.tweets || newId in this.usedIds) + newId += 1000; + + this.usedIds[newId] = true; + return newId; + }, + + fakeUser: function () { + var account = this.account; + var userId; + while (!userId || userId in account.users) + userId = Math.floor(Math.random() * 10000000); + return { + "id": userId, + "verified": false, + "profile_sidebar_fill_color": "e0ff92", + "profile_text_color": "000000", + "followers_count": Math.floor(Math.random() * 500), + "protected": false, + "location": "San Diego, CA", + "profile_background_color": "9ae4e8", + "utc_offset": -28800, + "statuses_count": 8, + "description": "Classical Vocalist, Life Coach, Business Coach, Lover of Life", + "friends_count": 44, + "profile_link_color": "0000ff", + "profile_image_url": "http://a1.twimg.com/profile_images/467719152/Heidi-IMG_8247F-twitter_normal.jpg", + "notifications": null, + "geo_enabled": false, + "profile_background_image_url": "http://s.twimg.com/a/1255384803/images/themes/theme1/bg.png", + "screen_name": "1classicaldiva", + "profile_background_tile": false, + "favourites_count": 0, + "name": "Heidi Lee", + "url": "http://google.com", + "created_at": "Mon Oct 12 16:11:39 +0000 2009", + "time_zone": "Pacific Time (US & Canada)", + "profile_sidebar_border_color": "87bc44", + "following": null + }; + }, + + update: function(updateNotification) { + var tweets = []; + + for (var i = 0; i < this.numTweets; ++i) { + var date = new Date().toString().slice(0, 33); + tweets.push({ + "favorited": false, + "truncated": false, + "text": loremIpsumParagraph(24), + "created_at": date, + "source": "web", + "in_reply_to_status_id": null, + "in_reply_to_screen_name": null, + "in_reply_to_user_id": null, + "geo": null, + "id": this.fakeTweetId(), + user: this.fakeUser(), + }); + } + + console.log('created ' + tweets.length + ' fake tweets'); + + var self = this, account = this.account; + tweets = $.map(tweets, function (t) { return account.makeTweetFromNetwork(t); }); + account.newTweets(tweets, self); + if (updateNotification) + updateNotification.success(self, tweets); + } +}; + +function fakeTweets(numTweets) { + var src = new FakeTwitterSource(account, numTweets); + account.maybeUpdate(false, [src]); + account.discardOldTweets(); +} + + +/* Lorem Ipsum Generator + * (CC-BY) Fredrik Bridell 2009 + * Version 0.21 - multilingual + * Released under a Creative Commons Attribution License + * + * You are welcome to use, share, modify, print, frame, or do whatever you like with this + * software, including commercial use. My only request is that you tell the world where you found it. + * + * One way is to include the phrase: + * "Using the The Lorem Ipsum Generator by Fredrik Bridell (http://bridell.com/loremipsum/)" + * + * To use this on your web page: download the .js file and place it on your web server (please + * do not include it from my server). In your html file, include the markup + * + * The number is the number of words in the paragraph. + */ + +/* Latin words, These are all the words in the first 100 lines of Ovid's Metamorphoses, Liber I. */ +var latin =["ab", "aberant", "abscidit", "acervo", "ad", "addidit", "adhuc", "adsiduis", "adspirate", "aequalis", "aer", "aera", "aere", "aeris", "aestu", "aetas", "aethera", "aethere", "agitabilis", "aliis", "aliud", "alta", "altae", "alto", "ambitae", "amphitrite", "animal", "animalia", "animalibus", "animus", "ante", "aquae", "arce", "ardentior", "astra", "aurea", "auroram", "austro", "bene", "boreas", "bracchia", "caeca", "caecoque", "caeleste", "caeli", "caelo", "caelum", "caelumque", "caesa", "calidis", "caligine", "campoque", "campos", "capacius", "carentem", "carmen", "cepit", "certis", "cesserunt", "cetera", "chaos:", "cingebant", "cinxit", "circumdare", "circumfluus", "circumfuso", "coegit", "coeperunt", "coeptis", "coercuit", "cognati", "colebat", "concordi", "congeriem", "congestaque", "consistere", "contraria", "conversa", "convexi", "cornua", "corpora", "corpore", "crescendo", "cum", "cuncta", "cura", "declivia", "dedit", "deducite", "deerat", "dei", "densior", "deorum", "derecti", "descenderat", "deus", "dextra", "di", "dicere", "diffundi", "diremit", "discordia", "dispositam", "dissaepserat", "dissociata", "distinxit", "diu", "diversa", "diverso", "divino", "dixere", "dominari", "duae", "duas", "duris", "effervescere", "effigiem", "egens", "elementaque", "emicuit", "ensis", "eodem", "erant", "erat", "erat:", "erectos", "est", "et", "eurus", "evolvit", "exemit", "extendi", "fabricator", "facientes", "faecis", "fecit", "feras", "fert", "fidem", "figuras", "finxit", "fixo", "flamina", "flamma", "flexi", "fluminaque", "fontes", "foret", "forma", "formaeque", "formas", "fossae", "fratrum", "freta", "frigida", "frigore", "fronde", "fuerant", "fuerat", "fuit", "fulgura", "fulminibus", "galeae", "gentes", "glomeravit", "grandia", "gravitate", "habendum", "habentem", "habentia", "habitabilis", "habitandae", "haec", "hanc", "his", "homini", "hominum", "homo", "horrifer", "humanas", "hunc", "iapeto", "ignea", "igni", "ignotas", "illas", "ille", "illi", "illic", "illis", "imagine", "in", "inclusum", "indigestaque", "induit", "iners", "inmensa", "inminet", "innabilis", "inposuit", "instabilis", "inter", "invasit", "ipsa", "ita", "iudicis", "iuga", "iunctarum", "iussit", "lacusque", "lanient", "lapidosos", "lege", "legebantur", "levitate", "levius", "liberioris", "librata", "ligavit:", "limitibus", "liquidas", "liquidum", "litem", "litora", "locavit", "locis", "locoque", "locum", "longo", "lucis", "lumina", "madescit", "magni", "manebat", "mare", "margine", "matutinis", "mea", "media", "meis", "melior", "melioris", "membra", "mentes", "mentisque", "metusque", "militis", "minantia", "mixta", "mixtam", "moderantum", "modo", "moles", "mollia", "montes", "montibus", "mortales", "motura", "mundi", "mundo", "mundum", "mutastis", "mutatas", "nabataeaque", "nam", "natura", "naturae", "natus", "ne", "nebulas", "nec", "neu", "nisi", "nitidis", "nix", "non", "nondum", "norant", "nova", "nubes", "nubibus", "nullaque", "nulli", "nullo", "nullus", "numero", "nunc", "nuper", "obliquis", "obsistitur", "obstabatque", "occiduo", "omni", "omnia", "onerosior", "onus", "opifex", "oppida", "ora", "orba", "orbe", "orbem", "orbis", "origine", "origo", "os", "otia", "pace", "parte", "partim", "passim", "pendebat", "peragebant", "peregrinum", "permisit", "perpetuum", "persidaque", "perveniunt", "phoebe", "pinus", "piscibus", "plagae", "pluvialibus", "pluviaque", "poena", "pondere", "ponderibus", "pondus", "pontus", "porrexerat", "possedit", "posset:", "postquam", "praebebat", "praecipites", "praeter", "premuntur", "pressa", "prima", "primaque", "principio", "pro", "pronaque", "proxima", "proximus", "pugnabant", "pulsant", "quae", "quam", "quanto", "quarum", "quem", "qui", "quia", "quicquam", "quin", "quinta", "quisque", "quisquis", "quod", "quoque", "radiis", "rapidisque", "recens", "recepta", "recessit", "rectumque", "regat", "regio", "regna", "reparabat", "rerum", "retinebat", "ripis", "rudis", "sanctius", "sata", "satus", "scythiam", "secant", "secrevit", "sectamque", "secuit", "securae", "sed", "seductaque", "semina", "semine", "septemque", "sibi", "sic", "siccis", "sidera", "silvas", "sine", "sinistra", "sive", "sole", "solidumque", "solum", "sorbentur", "speciem", "spectent", "spisso", "sponte", "stagna", "sua", "subdita", "sublime", "subsidere", "sui", "suis", "summaque", "sunt", "super", "supplex", "surgere", "tanta", "tanto", "tegi", "tegit", "tellure", "tellus", "temperiemque", "tempora", "tenent", "tepescunt", "terra", "terrae", "terram", "terrarum", "terras", "terrenae", "terris", "timebat", "titan", "tollere", "tonitrua", "totidem", "totidemque", "toto", "tractu", "traxit", "triones", "tuba", "tum", "tumescere", "turba", "tuti", "ubi", "ulla", "ultima", "umentia", "umor", "unda", "undae", "undas", "undis", "uno", "unus", "usu", "ut", "utque", "utramque", "valles", "ventis", "ventos", "verba", "vesper", "videre", "vindice", "vis", "viseret", "vix", "volucres", "vos", "vultus", "zephyro", "zonae"]; + +/* Swedish words. These are all the words in the two first paragraphs of August Strindberg's Rda Rummet. */ +var swedish = ["afton", "allmänheten", "allting", "arbetat", "att", "av", "bakom", "barge-lappar", "berberisbär", "Bergsund", "bersåer", "beströdd", "bjödo", "blev", "blivit", "blom", "blommor", "bofinkarne", "bon", "bort", "bosättningsbekymmer", "branta", "bygga", "bänkfot", "både", "började", "början", "börjat", "Danviken", "de", "del", "deltogo", "den", "det", "detsamma", "djur", "draga", "drog", "drogos", "där", "därför", "därifrån", "därinne", "då", "efter", "ej", "eklärerade", "emot", "en", "ett", "fjolårets", "fjor", "fjärran", "for", "fortsatte", "fram", "friska", "från", "färd", "fästningen", "få", "fönstervadden", "fönstren", "för", "förbi", "fördes", "förfärligt", "förut", "genom", "gick", "gingo", "gjorde", "granris", "gren", "gripa", "gråsparvarne", "gå", "gångarne", "gått", "gömde", "hade", "halmen", "havet", "hela", "hittade", "hon", "hundar", "hus", "Hästholmen", "hårtappar", "höllo", "höstfyrverkeriet", "i", "icke", "igen", "ilade", "illuminerade", "in", "ingen", "innanfönstren", "Josefinadagen", "just", "kastade", "kiv", "klistringen", "klättrade", "knoppar", "kol", "kom", "korset", "korta", "kunde", "kvastar", "kände", "kärleksfilter", "köksan", "lavklädda", "lekte", "levdes", "Lidingöskogarne", "ligger", "Liljeholmen", "lilla", "lindarne", "liv", "luften", "lukten", "lämna", "långt", "lövsamlingar", "maj", "med", "medan", "mellan", "men", "moln", "Mosebacke", "mot", "mänskofot", "navigationsskolans", "nu", "näsan", "obesvärat", "obrustna", "och", "ofruktsamt", "om", "os", "paljetter", "passade", "piga", "plats", "plockade", "päronträd", "på", "rabatterna", "rakethylsor", "Riddarfjärden", "Riddarholmskyrkan", "ringdans", "rivit", "Rosendal", "rosenfärgat", "rusade", "rökarne", "saffransblommorna", "samla", "samma", "sandgångarne", "sedan", "sig", "Siklaön", "sin", "sina", "sista", "Sjötullen", "Sjötulln", "Skeppsbrobåtarne", "skolan", "skrämd", "skräp", "skydd", "sköt", "slagits", "slog", "sluppit", "sluta", "snart", "snö", "snödropparne", "solen", "som", "sommarnöjena", "spillror", "Stadsgården", "stam", "stekflott", "stickorna", "stod", "stor", "stora", "stranden", "strålar", "störtade", "sydlig", "syrenerna", "sågo", "sågspån", "sålunda", "södra", "tagit", "tak", "takpannorna", "till", "tillbaka", "tittade", "tjära", "tonade", "trampat", "tran", "träd", "trädgården", "Tyskans", "törnade", "törnrosblad", "undanröjda", "under", "unga", "upp", "uppför", "uppgrävda", "ur", "ut", "utefter", "utmed", "var", "Vaxholm", "verksamhet", "vilka", "vilken", "vimplarne", "vind", "vinden", "vinterns", "voro", "vägg", "väggen", "väntade", "ännu", "året", "åt", "ölskvättar", "ömtåligare", "öppnad", "öppnades", "öster", "över"]; + +// just switch language like this! You can also do this in a script block on the page. +var loremLang = latin; + +/* Characters to end a sentence with. Repeat for frequencies (i.e. most sentences end in a period) */ +var endings = "................................??!"; + +/* randomly returns true with a certain chance (a percentage) */ +function chance(percentage) { + return (Math.floor(Math.random() * 100) < percentage); +} + +/* capitalizes a word */ +function capitalize(aString) { + return aString.substring(0,1).toUpperCase() + aString.substring(1, aString.length); +} + +/* returns a random lorem word */ +function getLoremWord() { + return loremLang[Math.floor(Math.random()*loremLang.length)]; +} + +function getLoremEnding() { + var i = Math.floor(Math.random()*endings.length); + return endings.substring(i, i+1); +} + +/* inserts a number of lorem words. Does not append a space at the end. */ +function loremIpsum(s, numWords) { + for(var i=0; i 0){ + if(words > 10){ + w = Math.floor(Math.random() * 8) + 2; + loremIpsumSentence2(s, w); + words = words - w; + } else { + loremIpsumSentence2(s, words); + words = 0; + } + } + return s.join(''); +} + + +function debugCounts() { + var info = [ + 'objectKeys(account.tweets).length', + 'objectKeys(account.users).length', + 'account.timeline.toInsert.length', + 'account.timeline.toDelete.length', + 'timelineWindows[0].document.all.length', + 'account.timeline.container.childNodes.length', + 'objectLength(Digsby.callbacks)', + ]; + + var infos = []; + $.each(info, function (i, s) { + var val = ''; + try { val = eval(s); } catch(err) {} + infos.push(s + ': ' + val); + }); + + return infos.join('\n'); +} + diff --git a/digsby/src/plugins/twitter/res/utils.js b/digsby/src/plugins/twitter/res/utils.js new file mode 100644 index 0000000..8a75b1d --- /dev/null +++ b/digsby/src/plugins/twitter/res/utils.js @@ -0,0 +1,835 @@ + +function useStringIdentifiers(obj, properties) { + for (var i = 0; i < properties.length; ++i) + useStringIdentifier(obj, properties[i]); +} + +// adaptive function for the new string IDs for Twitter API objects (Snowflake) +function useStringIdentifier(obj, property) { + var property_str = property + "_str"; + if (!obj) { + return; + } + + if (obj[property_str]) { + obj[property] = obj[property_str].toString(); + delete obj[property_str]; + } else if (obj[property]) { + obj[property] = obj[property].toString(); + } +} + +function shallowCopy(obj) { + var o = {}; + for (var k in obj) + o[k] = obj[k]; + return o; +} +function last(array) { + return array[array.length-1]; +} + +function get(obj, attr, def) { + if (typeof obj === 'undefined') + return def; + else + var val = obj[attr]; + return typeof val === 'undefined' ? def : val; +} + +function sum(array, start) { + if (typeof start === 'undefined') + start = 0; + for (var i = array.length-1; i >= 0; --i) start += array[i]; + return start; +} + +function joinSingleQuotedStringArray(arr, joinStr) { + var res = ""; + for (var i = 0; i < arr.length; ++i) { + res += "'" + arr[i] + "'"; + if (i != arr.length - 1) + res += joinStr; + } + return res; +} + +function arraysEqual(a, b, cmp) { + if (a.length !== b.length) + return false; + + for (var i = 0; i < a.length; ++i) { + if (cmp(a[i], b[i]) !== 0) + return false; + } + + return true; +} + +function isSorted(a, sortFunc) { + var copy = Array.apply(null, a); + copy.sort(sortFunc); + return arraysEqual(a, copy, sortFunc); +} + +function objectLength(self) { + var count = 0; + for (var key in self) + ++count; + return count; +} + +function objectKeys(self) { + assert(self !== undefined); + var keys = []; + for (var key in self) + keys.push(key); + return keys; +} + +function objectValues(self) { + assert(self !== undefined); + var values = []; + for (var key in self) + values.push(self[key]); + return values; +} + +/** + * Inserts the items in array2 into array at pos. If pos is not given, the + * elements are inserted at the end. + */ +function arrayExtend(array, array2, pos) { + if (pos === undefined) + pos = array.length; + + array.splice.apply(array, [pos, 0].concat(array2)); +} + +Function.prototype.inheritsFrom = function(superClass, functions) { + var proto = this.prototype = new superClass(); + proto.constructor = this; + if (functions) + $.extend(proto, functions); +}; + + +function AssertionError(message) { this.message = message; } +AssertionError.prototype.toString = function() { return 'AssertionError(' + this.message + ')'; }; + +function assert(x, message) { + if (!x) { + console.error('ASSERT FAILED: ' + (message || '')); + printStackTrace(); + throw new AssertionError(message || ''); + } +} + +/** + * given an Array, makes an Object where each element in the array is a key with a value of true + */ +function set(seq) { + var set = {}; + for (var i = seq.length - 1; i >= 0; --i) + set[seq[i]] = true; + return set; +} + +function setsEqual(a, b) { + for (var key in a) + if (!(key in b)) + return false; + for (var key in b) + if (!(key in a)) + return false; + return true; +} + +function urlQuery(url, args, do_escape) { + do_escape = (do_escape === undefined ? true : do_escape); + var pairs = []; + for (var k in args) + pairs.push(k + '=' + (do_escape ? encodeURIComponent(args[k]) : args[k])); + + var separator = url.search(/\?/) == -1 ? '?' : '&'; + return url + separator + pairs.join('&'); +} + +/** + * Turns '?key=value&foo' into {key: 'value', foo: true} + */ +function queryParse(query) { + if (query.indexOf('?') === 0) + query = query.substring(1); + + query = query.split('&'); + var args = {}; + + for (var i = 0; i < query.length; ++i) { + var arg = query[i]; + var j = arg.indexOf('='); + var key, value; + if (j == -1) { + key = arg; + value = true; + } else { + key = arg.substring(0, j); + value = arg.substring(j + 1); + } + + args[key] = value; + } + + return args; +} + +/* + * JavaScript Pretty Date + * Copyright (c) 2008 John Resig (jquery.com) + * Licensed under the MIT license. + */ +// Takes an ISO time and returns a string representing how +// long ago the date represents. +function prettyDate(time){ + var date = new Date((time || "").replace(/-/g,"/").replace(/[TZ]/g," ")), + diff = (((new Date()).getTime() - date.getTime()) / 1000), + day_diff = Math.floor(diff / 86400); + + if ( isNaN(day_diff) || day_diff < 0 ) + return 'just now';//longDateFormat(date); + + return day_diff === 0 && ( + diff < 60 && "just now" || + diff < 120 && "1 minute ago" || + diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || + diff < 7200 && "1 hour ago" || + diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || + day_diff === 1 && "Yesterday" || + day_diff < 7 && day_diff + " days ago" || + Math.ceil( day_diff / 7 ) + " weeks ago"; +} + +function longDateFormat(date) { + return date.toLocaleDateString() + ' ' + date.toLocaleTimeString() +} + +function uniquify(seq, key) { + var seen = {}; + var items = []; + for (var i = 0; i < seq.length; ++i) { + var k = key(seq[i]); + if (!(k in seen)) { + seen[k] = true; + items.push(seq[i]); + } + } + + return items; +} + +function all(seq) { + for (var i = 0; i < seq.length; ++i) { + if (!seq[i]) + return false; + } + + return true; +} + +function binarySearch(o, v, key) { + /* + * o: an ordered Array of elements + * v: the value to search for + * key: an optional key function + * + * thanks http://jsfromhell.com/array/search + */ + if (key === undefined) key = function(o) { return o; }; + var vkey = key(v); + var h = o.length, l = -1, m; + + while(h - l > 1) + if (key(o[m = h + l >> 1]) < vkey) l = m; + else h = m; + + return h; +}; + +function stackTrace() { + var curr = arguments.callee.caller, + FUNC = 'function', ANON = "{anonymous}", + fnRE = /function\s*([\w\-$]+)?\s*\(/i, + stack = [],j=0, + fn,args,i; + + var count = 0; + while (curr && count++ < 40) { + fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; + if (fn === ANON) + fn = curr.toString(); + args = stack.slice.call(curr.arguments); + i = args.length; + + while (i--) { + switch (typeof args[i]) { + case 'string' : args[i] = '"'+args[i].replace(/"/g,'\\"')+'"'; break; + case 'function': args[i] = FUNC; break; + } + } + + stack[j++] = fn + '(' + args.join().toString().slice(0, 80) + ')'; + stack[j++] = '--------------------------------'; + curr = curr.caller; + } + + return stack; +} + +function printStackTrace() { + console.error(stackTrace().join('\n')); +} + +function printException(e) { + console.error(e.name + ' ' + e.sourceURL + '(' + e.line + '): ' + e.message); +} + +function guard(f) { + try { + f(); + } catch (err) { + printStackTrace(); + printException(err); + } +} + +function callEach(seq, funcName) { + var args = Array.apply(null, arguments); + var results = []; + args.shift(); + args.shift(); + + $.each(seq, function (i, obj) { + results.push(obj[funcName].apply(obj, args)); + }); + + return results; +} + + +// Mozilla 1.8 has support for indexOf, lastIndexOf, forEach, filter, map, some, every +// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf +function arrayIndexOf (array, obj, fromIndex) { + if (fromIndex == null) { + fromIndex = 0; + } else if (fromIndex < 0) { + fromIndex = Math.max(0, array.length + fromIndex); + } + for (var i = fromIndex; i < array.length; i++) { + if (array[i] === obj) + return i; + } + return -1; +}; + +function arrayRemove(array, obj) { + var i = arrayIndexOf(array, obj); + if (i != -1) { + array.splice(i, 1); + return true; + } + + return false; +}; + +(function($){ + + var $preload = $.preload = function( original, settings ){ + if( original.split ) // selector + original = $(original); + + settings = $.extend( {}, $preload.defaults, settings ); + var sources = $.map( original, function( source ){ + if( !source ) + return; // skip + if( source.split ) // URL Mode + return settings.base + source + settings.ext; + var url = source.src || source.href; // save the original source + if( typeof settings.placeholder == 'string' && source.src ) // Placeholder Mode, if it's an image, set it. + source.src = settings.placeholder; + if( url && settings.find ) // Rollover mode + url = url.replace( settings.find, settings.replace ); + return url || null; // skip if empty string + }); + + var data = { + loaded:0, // how many were loaded successfully + failed:0, // how many urls failed + next:0, // which one's the next image to load (index) + done:0, // how many urls were tried + /* + index:0, // index of the related image + found:false, // whether the last one was successful + */ + total:sources.length // how many images are being preloaded overall + }; + + if( !data.total ) // nothing to preload + return finish(); + + var imgs = $(Array(settings.threshold+1).join('')) + .load(handler).error(handler).bind('abort',handler).each(fetch); + + function handler( e ){ + data.element = this; + data.found = e.type == 'load'; + data.image = this.src; + data.index = this.index; + var orig = data.original = original[this.index]; + data[data.found?'loaded':'failed']++; + data.done++; + + // This will ensure that the images aren't "un-cached" after a while + if( settings.enforceCache ) + $preload.cache.push( + $('').attr('src',data.image)[0] + ); + + if( settings.placeholder && orig.src ) // special case when on placeholder mode + orig.src = data.found ? data.image : settings.notFound || orig.src; + if( settings.onComplete ) + settings.onComplete( data ); + if( data.done < data.total ) // let's continue + fetch( 0, this ); + else{ // we are finished + if( imgs && imgs.unbind ) + imgs.unbind('load').unbind('error').unbind('abort'); // cleanup + imgs = null; + finish(); + } + }; + function fetch( i, img, retry ){ + // IE problem, can't preload more than 15 + if( img.attachEvent /* msie */ && data.next && data.next % $preload.gap == 0 && !retry ){ + setTimeout(function(){ fetch( i, img, true ); }, 0); + return false; + } + if( data.next == data.total ) return false; // no more to fetch + img.index = data.next; // save it, we'll need it. + img.src = sources[data.next++]; + if( settings.onRequest ){ + data.index = img.index; + data.element = img; + data.image = img.src; + data.original = original[data.next-1]; + settings.onRequest( data ); + } + }; + function finish(){ + if( settings.onFinish ) + settings.onFinish( data ); + }; + }; + + // each time we load this amount and it's IE, we must rest for a while, make it lower if you get stack overflow. + $preload.gap = 14; + $preload.cache = []; + + $preload.defaults = { + threshold:2, // how many images to load simultaneously + base:'', // URL mode: a base url can be specified, it is prepended to all string urls + ext:'', // URL mode:same as base, but it's appended after the original url. + replace:'' // Rollover mode: replacement (can be left empty) + /* + enforceCache: false, // If true, the plugin will save a copy of the images in $.preload.cache + find:null, // Rollover mode: a string or regex for the replacement + notFound:'' // Placeholder Mode: Optional url of an image to use when the original wasn't found + placeholder:'', // Placeholder Mode: url of an image to set while loading + onRequest:function( data ){ ... }, // callback called every time a new url is requested + onComplete:function( data ){ ... }, // callback called every time a response is received(successful or not) + onFinish:function( data ){ ... } // callback called after all the images were loaded(or failed) + */ + }; + + $.fn.preload = function( settings ){ + $preload( this, settings ); + return this; + }; + +})(jQuery); + + +function cmp(a, b) { + if (a < b) + return -1; + else if (b < a) + return 1; + else + return 0; +} + +function cmpByKey(key, a, b) { + return function(a, b) { return cmp(a[key], b[key]); }; +} + +function pp(obj) { + var s = []; + for (var key in obj) + s.push(key + ': ' + obj[key]); + + console.log('{' + s.join(', ') + '}'); +} + +/*! + * linkify - v0.3 - 6/27/2009 + * http://benalman.com/code/test/js-linkify/ + * + * Copyright (c) 2009 "Cowboy" Ben Alman + * Licensed under the MIT license + * http://benalman.com/about/license/ + * + * Some regexps adapted from http://userscripts.org/scripts/review/7122 + */ + +// Turn text into linkified html. +// +// var html = linkify( text, options ); +// +// options: +// +// callback (Function) - default: undefined - if defined, this will be called +// for each link- or non-link-chunk with two arguments, text and href. If the +// chunk is non-link, href will be omitted. +// +// punct_regexp (RegExp | Boolean) - a RegExp that can be used to trim trailing +// punctuation from links, instead of the default. +// +// This is a work in progress, please let me know if (and how) it fails! + +window.linkify = (function(){ + var + PROTOCOLS = 'ftp|https?|gopher|msnim|icq|telnet|nntp|aim|file|svn', + SCHEME = "(?:" + PROTOCOLS + ")://", + IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])", + HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+", + TLD = "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)", + HOST_OR_IP = "(?:" + HOSTNAME + TLD + "|" + IPV4 + ")", + PATH = "(?:[;/][^#?<>\\s]*)?", + QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s\\u3000]*)?", // include \\u3000 here and in the line below--webkit's js engine does not for \s + URI1 = "\\b" + SCHEME + "[^<>\\s\\u3000]+", + URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)", + + MAILTO = "mailto:", + EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)", + + URI_RE = new RegExp( "(?:" + URI1 + "|" + URI2 + "|" + EMAIL + ")", "ig" ), + SCHEME_RE = new RegExp( "^" + SCHEME, "i" ), + + quotes = { + "'": "`", + '>': '<', + ')': '(', + ']': '[', + '}': '{', + '»': '«', + '›': '‹' + }, + + default_options = { + callback: function( text, href ) { + return href ? '' + text + '<\/a>' : text; + }, + punct_regexp: /(?:[!?.,:;'"]|(?:&|&)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/ + }; + + return function( txt, options ) { + if (!txt) return ''; + options = options || {}; + + // Temp variables. + var arr, + i, + link, + href, + + // Output HTML. + html = '', + + // Store text / link parts, in order, for re-combination. + parts = [], + + // Used for keeping track of indices in the text. + idx_prev, + idx_last, + idx, + link_last, + + // Used for trimming trailing punctuation and quotes from links. + matches_begin, + matches_end, + quote_begin, + quote_end; + + // Initialize options. + for ( i in default_options ) { + if ( options[ i ] === undefined ) { + options[ i ] = default_options[ i ]; + } + } + + // Find links. + while ( arr = URI_RE.exec( txt ) ) { + + link = arr[0]; + idx_last = URI_RE.lastIndex; + idx = idx_last - link.length; + + // Not a link if preceded by certain characters. + if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) { + continue; + } + + // Trim trailing punctuation. + do { + // If no changes are made, we don't want to loop forever! + link_last = link; + + quote_end = link.substr( -1 ) + quote_begin = quotes[ quote_end ]; + + // Ending quote character? + if ( quote_begin ) { + matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) ); + matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) ); + + // If quotes are unbalanced, remove trailing quote character. + if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) { + link = link.substr( 0, link.length - 1 ); + idx_last--; + } + } + + // Ending non-quote punctuation character? + if ( options.punct_regexp ) { + link = link.replace( options.punct_regexp, function(a){ + idx_last -= a.length; + return ''; + }); + } + } while ( link.length && link !== link_last ); + + href = link; + + // Add appropriate protocol to naked links. + if ( !SCHEME_RE.test( href ) ) { + href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO ) + : !href.indexOf( 'irc.' ) ? 'irc://' + : !href.indexOf( 'ftp.' ) ? 'ftp://' + : 'http://' ) + + href; + } + + // Push preceding non-link text onto the array. + if ( idx_prev != idx ) { + parts.push([ txt.slice( idx_prev, idx ) ]); + idx_prev = idx_last; + } + + // Push massaged link onto the array + parts.push([ link, href ]); + }; + + // Push remaining non-link text onto the array. + parts.push([ txt.substr( idx_prev ) ]); + + // Process the array items. + for ( i = 0; i < parts.length; i++ ) { + html += options.callback.apply( window, parts[i] ); + } + + // In case of catastrophic failure, return the original text; + return html || txt; + }; + +})(); + +// t: current time, b: begInnIng value, c: change In value, d: duration +jQuery.easing['jswing'] = jQuery.easing['swing']; + +jQuery.extend( jQuery.easing, +{ + def: 'easeOutQuad', + swing: function (x, t, b, c, d) { + //alert(jQuery.easing.default); + return jQuery.easing[jQuery.easing.def](x, t, b, c, d); + }, + easeInQuad: function (x, t, b, c, d) { + return c*(t/=d)*t + b; + }, + easeOutQuad: function (x, t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + easeInOutQuad: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + }, + easeInCubic: function (x, t, b, c, d) { + return c*(t/=d)*t*t + b; + }, + easeOutCubic: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + }, + easeInOutCubic: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + }, + easeInQuart: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t + b; + }, + easeOutQuart: function (x, t, b, c, d) { + return -c * ((t=t/d-1)*t*t*t - 1) + b; + }, + easeInOutQuart: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t + b; + return -c/2 * ((t-=2)*t*t*t - 2) + b; + }, + easeInQuint: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t*t + b; + }, + easeOutQuint: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t*t*t + 1) + b; + }, + easeInOutQuint: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; + return c/2*((t-=2)*t*t*t*t + 2) + b; + }, + easeInSine: function (x, t, b, c, d) { + return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + }, + easeOutSine: function (x, t, b, c, d) { + return c * Math.sin(t/d * (Math.PI/2)) + b; + }, + easeInOutSine: function (x, t, b, c, d) { + return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + }, + easeInExpo: function (x, t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + }, + easeOutExpo: function (x, t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + }, + easeInOutExpo: function (x, t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInCirc: function (x, t, b, c, d) { + return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + }, + easeOutCirc: function (x, t, b, c, d) { + return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + }, + easeInOutCirc: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; + return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + }, + easeInElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + }, + easeOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; + }, + easeInOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; + }, + easeInBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*(t/=d)*t*((s+1)*t - s) + b; + }, + easeOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + }, + easeInOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; + return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + }, + easeInBounce: function (x, t, b, c, d) { + return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b; + }, + easeOutBounce: function (x, t, b, c, d) { + if ((t/=d) < (1/2.75)) { + return c*(7.5625*t*t) + b; + } else if (t < (2/2.75)) { + return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; + } else if (t < (2.5/2.75)) { + return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; + } else { + return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; + } + }, + easeInOutBounce: function (x, t, b, c, d) { + if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; + return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; + } +}); + +function nodeInAll(n) { + for (var i = 0; i < document.all.length; ++i) + if (document[i] === n) + return true; + + return false; +} + +function arrayShuffle(o) { + for(var j, x, i = o.length; i; + j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); + + return o; +} + +function htmlEntitiesEscape(html) { + return html. + replace(/&/gmi, '&'). + replace(/"/gmi, '"'). + replace(/>/gmi, '>'). + replace(/ b.length) + return 1; + else if (b.length > a.length) + return -1; + + for (var i = 0; i < a.length; ++i) { + var x = a[i], y = b[i]; + if (x > y) return 1; + else if (y > x) return -1; + } + + return 0; +} + diff --git a/digsby/src/plugins/twitter/twgui.py b/digsby/src/plugins/twitter/twgui.py new file mode 100644 index 0000000..a012e19 --- /dev/null +++ b/digsby/src/plugins/twitter/twgui.py @@ -0,0 +1,52 @@ +import util + +import wx + +import gui.skin as skin +import gui.toolbox as toolbox +import gui.textutil as textutil + +import digsby_service_editor.default_ui as default_ui + +import twitter_account_gui + +def construct_basic_subpanel_social(panel, SP, MSP, MSC): + fx = panel.controls['basic_sz'] + if SP is None: + # new account + follow_digsby = dict(check = wx.CheckBox(panel, -1, _("Follow Digsby on Twitter"))) + follow_digsby['check'].Value = True + panel.controls['follow_digsby'] = follow_digsby + default_ui.ezadd(fx, follow_digsby['check'], (fx.row, 1), (1, 2), flag = wx.EXPAND | wx.ALL) + fx.row += 1 + + return True + +def construct_advanced_subpanel_social(panel, SP, MSP, MSC): + freq_defaults = dict((k, MSC.info.defaults.get(k, 2)) for k in ['friends_timeline', 'replies', 'direct_messages', 'search_updatefreq']) + freq_settings = dict((k, getattr(SP, k, freq_defaults[k])) for k in ['friends_timeline', 'replies', 'direct_messages', 'search_updatefreq']) + fake_account = util.Storage(update_frequencies = freq_settings, + auto_throttle = getattr(SP, 'auto_throttle', MSC.info.defaults.get('auto_throttle', True)), + api_server = getattr(SP, 'api_server', MSC.info.defaults.get('api_server', u'')), + ) + + twap = twitter_account_gui.TwitterAccountPanel(panel, fake_account) + panel.controls.update(twitterpanel = twap) + + fx = panel.controls['advanced_sz'] + fx.Add(twap, (fx.row, 1), (1, 4), wx.EXPAND) + + return True + +def extract_basic_subpanel_social(panel, info, SP, MSP, MSC): + follow_digsby = panel.controls.get('follow_digsby', None) + if follow_digsby is not None: + info['do_follow_digsby'] = follow_digsby['check'].Value + + info['post_ach_all'] = False + return True + +def extract_advanced_subpanel_social(panel, info, SP, MSP, MSC): + info.update(panel.controls['twitterpanel'].info()) + info['informed_ach'] = True + return True diff --git a/digsby/src/plugins/twitter/twitter.py b/digsby/src/plugins/twitter/twitter.py new file mode 100644 index 0000000..829b075 --- /dev/null +++ b/digsby/src/plugins/twitter/twitter.py @@ -0,0 +1,1348 @@ +from __future__ import print_function +import os +from time import time +import sys +import branding +import common +import hooks +import fileutil +import metrics +import protocols +import social +import string +import threading +import traceback +import simplejson +from rpc.jsonrpc import Dsuccess + +from social.network import SocialFeed + +from util import callbacks +from util.net import UrlQuery +from util.primitives.error_handling import traceguard +from util.primitives.funcs import Delegate +from util.primitives.mapping import Storage, to_storage +from path import path +from copy import deepcopy + +import gui.infobox.providers as gui_providers +import gui.infobox.interfaces as gui_interfaces +import wx.webview + +from oauth.oauth import OAuthToken + +from logging import getLogger; log = getLogger('twitter') + +import twitter_auth +#import twitter_xauth as twitter_auth + +USE_REALTIME_STREAM = False or '--realtime' in sys.argv +SHOW_INVITE_DM_DIALOG_ON_CREATE = False + +RES_PATH = (path(__file__).parent.abspath() / 'res').normpath() +APP_PATH = (RES_PATH / 'app.html').normpath() + +#from twitter_tweets import DIGSBY_TWEET_MESSAGE + +def LINK(): + return branding.get('digsby.twitter.achievements.link', 'digsby_twitter', 'http://bit.ly/r2d24u') + +def DEMO_VIDEO_LINK(): + return branding.get('digsby.twitter.demovideo.link', 'digsby_twitter', 'http://bit.ly/clMDW5') + +def new_reply_text(screen_name, text): + import twitter_util as tutil + s = '@' + screen_name + ' ' + cursor_position = len(s) + + hashtags = ' '.join(set(tutil.hashtag.findall(text))) + if hashtags: s += ' ' + hashtags + + return s, cursor_position + +def prefill_reply(options): + '''Returns the text the popup reply button input field is prefilled with.''' + + tweet = options['tweet'] + return new_reply_text(tweet.user.screen_name, tweet.text.decode('xml')) + +def prefill_retweet(options): + '''Returns the text the popup retweet button input field is prefilled with.''' + tweet = options['tweet'] + return 'RT @' + tweet.user.screen_name + ' ' + tweet.text.decode('xml') + +def prefill_direct(options): + '''Returns the text the popup retweet button input field is prefilled with.''' + tweet = options['tweet'] + return 'd ' + tweet.user.screen_name + ' ' + + +class TwitterAccount(social.network): + service = protocol = 'twitter' + _dirty = True + update_mixin_timer = False + + @callbacks.callsback + def SetStatusMessage(self, message, reply_to=None, callback=None, **k): + @wx.CallAfter + def after(): + def error(err): + callback.error(Exception('Error sending tweet')) + + self.twitter_protocol.on_status(message, + reply_id=reply_to, + success=callback.success, + error=error) + + # console shortcuts + + @property + def j(self): + return self.connection.webkitcontroller.evaljs + + @property + def w(self): + return self.connection.webkitcontroller.webview + + def update_now(self): + # if we're in "failed to connect" then just try reconnecting. + if self.state == self.Statuses.OFFLINE and \ + self.offline_reason == self.Reasons.CONN_FAIL: + self.Connect() + else: + self.twitter_protocol.update() + + @classmethod + def tray_icon_class(cls): + from .twitter_gui import TwitterTrayIcon + return TwitterTrayIcon + + def menu_actions(self, menu): + from .twitter_gui import menu_actions + menu_actions(self, menu) + + @property + def connection(self): + return self.twitter_protocol + + def update_info(self, **info): + '''new account info arrives from network/account dialog''' + for item in ['do_follow_digsby', 'do_tweet_about_digsby']: + info.pop(item, None) + + # if the user changes the password at runtime, then clear the oauth token + if info.get('password', None) and self.password and info['password'] != self.password: + log.critical('clearing oauth token') + info['oauth_token'] = None + + super(TwitterAccount, self).update_info(**info) + self.set_account_opts() + + def get_options(self): + '''return the set of values to be serialized to the server''' + + opts = super(TwitterAccount, self).get_options() + opts.update({'informed_ach': True, 'post_ach_all': False}) + for k in self.protocol_info()['defaults'].iterkeys(): + v = getattr(self, k) + if v != self.default(k): + opts[k] = v + + if self.oauth_token is not None: + opts['oauth_token'] = self.oauth_token + + api_server = getattr(self, 'api_server', None) + if api_server is not None: + opts['api_server'] = api_server + + return opts + + def account_prefs(self): + return [('autoscroll_when_at_bottom', 'twitter.autoscroll.when_at_bottom', True), ] + + def set_account_opts(self): + if self.twitter_protocol is not None: + opts = self.get_account_opts() + self.twitter_protocol.set_options(opts) + + def on_pref_change(self, *a, **k): + @wx.CallAfter + def after(): + try: + timer = self._preftimer + except AttributeError: + timer = self._preftimer = wx.PyTimer(self.set_account_opts) + + timer.StartOneShot(500) + + @property + def update_frequencies(self): + return dict((a, getattr(self, a)) for a in + ('friends_timeline', + 'direct_messages', + 'replies', + 'search_updatefreq')) + + def __init__(self, **options): + for key in self.protocol_info()['defaults'].iterkeys(): + try: val = options[key] + except KeyError: val = self.default(key) + setattr(self, key, val) + + self.oauth_token = options.pop('oauth_token', None) + + self._on_online = Delegate() + + self.count = None + self.twitter_protocol = None + + if False: + # TODO: this will take an hour to come back from idle + @guithread + def later(): + self.idle_timer = wx.PyTimer(self.on_idle_timer) + MINUTE_MS = 60 * 1000 * 60 + self.idle_timer.StartRepeating(30 * MINUTE_MS) + + social.network.__init__(self, **options) + + self.header_funcs = [ + (_('Home'), 'http://twitter.com'), + (_('Profile'), lambda: wx.LaunchDefaultBrowser('http://twitter.com/' + self.twitter_username)), + (_('Followers'), 'http://twitter.com/followers'), + (_('Following'), 'http://twitter.com/following'), + ] + + import twitter_notifications as twitter_notifications + twitter_notifications._register_hooks() + + # options that affect first creation of the account. + # account dialog will call onCreate + self.do_follow_digsby = options.pop('do_follow_digsby', False) +# self.do_tweet_about_digsby = options.pop('do_tweet_about_digsby', False) + + for twitter_pref, digsby_pref, default in self.account_prefs(): + common.profile.prefs.link(digsby_pref, self.on_pref_change) + + #self.extra_header_func = (_('Invite Friends'), self.on_invite_friends) + + self.api_server = options.get('api_server', None) + + @property + def twitter_username(self): + assert wx.IsMainThread() + return self.j('account.selfScreenNameLower') + + def on_invite_friends(self): + '''show a dialog asking ot direct message followers, inviting them to digsby''' + + from .twitter_gui import show_acheivements_dialog + show_acheivements_dialog(lambda: self.j('inviteFollowers();')) + + def on_idle_timer(self): + if self.twitter_protocol is not None: + import gui.native.helpers + from common import pref + idle = gui.native.helpers.GetUserIdleTime() > pref('twitter.idle_time', type=int, default=(10 * 60 * 1000)) + val = 'true' if idle else 'false' + self.j('window.userIdle = %s;' % val) + + def onCreate(self): + '''called just after this account type is created by the user''' + + if self.do_follow_digsby: + self._on_online += lambda: self.twitter_protocol.webkitcontroller.JSCall('follow', screen_name='digsby') +# if self.do_tweet_about_digsby: +# self._on_online += lambda: self.twitter_protocol.on_status(DIGSBY_TWEET_MESSAGE()) + if SHOW_INVITE_DM_DIALOG_ON_CREATE: + wx.CallAfter(self.on_invite_friends) + + def get_account_opts(self): + opts = dict((a, getattr(self, a)) + for a in self.protocol_info()['defaults']) + + for twitter_pref, digsby_pref, default in self.account_prefs(): + opts[twitter_pref] = common.pref(digsby_pref, default) + + opts['demovideo_link'] = DEMO_VIDEO_LINK() + + api_server = getattr(self, 'api_server', None) + log.warning('api_server: %r', api_server) + if api_server is not None: + opts['apiRoot'] = api_server + + return opts + + def on_state_change(self, state): + log.info('on_state_change: %r', state) + + if state == 'online': + self.change_state(self.Statuses.ONLINE) + self._on_online.call_and_clear() + + elif state == 'autherror': + self.set_offline(self.Reasons.BAD_PASSWORD) + + elif state == 'oautherror': + if self._should_retry_oauth(): + log.warning('negotiating new OAuth token') + metrics.event('Twitter OAuth Refresh Token') + self.Disconnect(set_state=False) + self.oauth_token = None + self.Connect() + else: + self.set_offline(self.Reasons.BAD_PASSWORD) + + elif state == 'connfail': + self.set_offline(self.Reasons.CONN_FAIL) + self.Disconnect(set_state=False) + + _last_oauth_retry_time = 0 + + def _should_retry_oauth(self): + now = time() + if now - self._last_oauth_retry_time > 60*2: + self._last_oauth_retry_time = now + return True + + def Connect(self): + @guithread + def _connect(): + log.warning('twitter Connect') + self.change_state(self.Statuses.CONNECTING) + + if self.twitter_protocol is not None: + self.twitter_protocol.disconnect() + + self.twitter_protocol = TwitterProtocol(self.username, self._decryptedpw()) + self.json = self.twitter_protocol.json + self.twitter_protocol.account = self + self.twitter_protocol.connect(self.get_account_opts()) + e = self.twitter_protocol.events + e.state_changed += self.on_state_change + e.on_unread_counts += self.on_unread_counts + e.on_feeds += self.on_feeds + e.recent_timeline += self.invalidate_infobox + e.self_tweet += self.invalidate_infobox + e.status_update_clicked += self.update_status_window_needed + e.on_corrupted_database += self.on_corrupted_database + e.update_social_ids += self.on_update_social_ids + e.received_whole_update += self.on_received_whole_update + + def _get_database_path(self): + return self.connection._get_database_path() + + def on_corrupted_database(self): + ''' + The webkit control window signals this method via "D.rpc('on_corrupted_database');" + when sqlite indicates that the database is corrupted, or if the openDatabase call + returns undefined. + + We try to remove the database file entirely, and then reconnect. + ''' + + if getattr(self, 'did_attempt_recovery', False): + log.info('skipping on_corrupted_database, already done once') + return + + log.info('corrupted_database detected') + log.info('free disk space: %r', fileutil.free_disk_space()) + + if self.connection: + dbpath = self._get_database_path() + log.info(' path to database: %r', dbpath) + if dbpath: + result = try_opening_tempfile(os.path.dirname(dbpath)) + log.info('opening tempfile: %r', result) + + self.Disconnect() + + def disconnected(): + try: + log.info(' attempting delete') + os.remove(dbpath) + except Exception: + traceback.print_exc() + else: + log.info('success! reconnecting') + self.Connect() + + self.did_attempt_recovery = True + + wx.CallLater(1000, disconnected) + + + def on_feeds(self, feeds): + self.invalidate_infobox() + + def observe_count(self, callback): + self.add_gui_observer(callback, 'count') + + def unobserve_count(self, callback): + self.remove_gui_observer(callback, 'count') + + def on_unread_counts(self, opts): + self.setnotify('count', opts.get('total')) + self.invalidate_infobox() + + def invalidate_infobox(self, *a, **k): + self.on_update_social_ids() + self.set_infobox_dirty() + + def on_received_whole_update(self): + self.did_receive_whole_update = True + + def on_update_social_ids(self): + if self.state == self.Statuses.ONLINE and getattr(self, 'did_receive_whole_update', False): + self.twitter_protocol.update_social_ids() + + def set_infobox_dirty(self): + self._dirty = True + self.notify('dirty') + + def disconnect(self): + self.Disconnect() + + def Disconnect(self, *a, **k): + log.warning('twitter Disconnect') + if self.twitter_protocol is not None: + @guithread + def after(): + p, self.twitter_protocol = self.twitter_protocol, None + e = p.events + e.state_changed -= self.on_state_change + e.on_unread_counts -= self.on_unread_counts + e.recent_timeline -= self.invalidate_infobox + e.self_tweet -= self.invalidate_infobox + e.on_feeds -= self.on_feeds + e.status_update_clicked -= self.update_status_window_needed + e.on_corrupted_database -= self.on_corrupted_database + e.update_social_ids -= self.on_update_social_ids + e.received_whole_update -= self.on_received_whole_update + p.disconnect() + + @guithread + def after2(): + set_state = k.pop('set_state', True) + if set_state: + self.set_offline(self.Reasons.NONE) + + self.did_receive_whole_update = False + + success = k.pop('success', None) + if success is not None: + success() + + def DefaultAction(self): + if self.twitter_protocol is not None and self.state == self.Statuses.ONLINE: + self.twitter_protocol.open_timeline_window() + + def update_status_window_needed(self): + if common.pref('social.use_global_status', default=False, type=bool): + wx.GetApp().SetStatusPrompt([self]) + else: + self.twitter_protocol.open_timeline_window() + + def _enable_unread_counts(self): + self.connection.set_account_pref('show_unread_count', True) + self.on_unread_counts({'total':self.count}) + + def _disable_unread_counts(self): + self.connection.set_account_pref('show_unread_count', False) + self.on_unread_counts({'total':self.count}) + + def should_show_unread_counts(self): + return _get_account_pref(self.username, 'show_unread_count', True) + + def count_text_callback(self, txt): + if self.should_show_unread_counts() and self.count is not None: + return txt + (' (%s)' % self.count) + else: + return txt + + def mark_all_as_read(self): + self.connection.mark_all_as_read() + +class TwitterProtocol(object): + event_names = ''' + state_changed + following + reply + trends + on_unread_counts + on_feeds + on_edit_feed + on_view + on_change_view + status_update_clicked + recent_timeline + self_tweet + on_corrupted_database + update_social_ids + received_whole_update +'''.split() + + def __init__(self, username, password): + self.username = username + self.password = password + + self.recent_timeline = [] + self.self_tweet = None + self.trends = {} + + self.feeds = [] + self.feeds_by_name = {} + self.unread_counts = [] + + e = self.events = Storage((name, Delegate()) for name in self.event_names) + + e.following += self.on_following + e.trends += self.on_trends + e.on_unread_counts += self.on_unread_counts + e.recent_timeline += self.on_recent_timeline + e.self_tweet += self.on_self_tweet + e.on_feeds += self.on_feeds + e.on_change_view += self.on_change_view + e.on_view += self.on_view_changed + + def render_tweets(tweets, render_context): + return htmlize_tweets(self, tweets) + + self.social_feed = SocialFeed('twitter_' + self.username, + 'twitter_' + self.username, + self.get_tweet_feed, + render_tweets, + lambda: self.account.set_infobox_dirty) + + def _get_database_path(self): + webview = self.webkitcontroller.webview + return webview.GetDatabasePath('digsbysocial_' + self.username) + + def set_options(self, options): + guithread(lambda: self.webkitcontroller.JSCall('setAccountOptions', **options)) + + def on_change_view(self, feed_name): + log.info('on_change_view %r', feed_name) + window = self.webkitcontroller.FeedWindow + if window is not None: + log.info(' found a window, calling switch_to_view') + window.switch_to_view(feed_name) + tlw = window.Top + if tlw.IsIconized(): tlw.Iconize(False) + window.Top.Raise() + else: + log.info(' no window found, calling open_timeline_window') + self.webkitcontroller.open_timeline_window(feed_name) + + def on_view_changed(self, feed_name): + feed = self.feeds_by_name.get(feed_name, None) + if feed is not None and feed.get('query', None) is not None and feed.get('save', False): + hooks.notify('digsby.statistics.twitter.viewed_search') + + def on_feeds(self, feeds): + self.feeds = feeds + self.feeds_by_name = dict((f['name'], f) for f in feeds) + self.feeds_by_name.update(favorites=dict(name='favorites', label=_('Favorites')), + history=dict(name='history', label=_('History'))) + + import twitter_notifications as tnots + tnots._update_notifications(self, feeds) + + self._save_feeds(feeds) + + def _save_feeds(self, feeds): + # don't include non-saved searches + def should_save(f): + return f['type'] not in ('search', 'user') or f.get('save', False) + + feeds_pref = filter(should_save, deepcopy(feeds)) + + # don't serialize certain attributes out to prefs + for feed in feeds_pref: + for attr in ('count', 'label'): + feed.pop(attr) + + self.set_account_pref('feeds', feeds_pref) + + @property + def account_prefix(self): + return 'twitter.' + self.username + + def account_pref_key(self, name): + return _account_pref_key(self.username, name) + + def set_account_pref(self, name, value): + from common import setpref + value = simplejson.dumps(value) + setpref(self.account_pref_key(name), value) + + def get_account_pref(self, name, default): + return _get_account_pref(self.username, name, default) + + def on_unread_counts(self, opts): + self.unread_counts = opts.get('feeds') + self.unread_total = opts.get('total') + + def on_recent_timeline(self, tweets): + self.recent_timeline = [to_storage(t) for t in tweets] + self.recent_timeline.reverse() + self.events.update_social_ids() + + def update_social_ids(self): + try: + t = self._socialtimer + except AttributeError: + def later(): + ids = [p['id'] for p in self.recent_timeline] + self.social_feed.new_ids(ids) + + t = self._socialtimer = wx.PyTimer(later) + + if not t.IsRunning(): + t.StartOneShot(1000) + + def on_self_tweet(self, tweet): + self.self_tweet = to_storage(tweet) + + def on_following(self, ids): + # TODO: stop should actually do something + if hasattr(self, 'stream'): + self.stream.stop() + + if common.pref('twitter.streaming', default=False): + from twitterstream import TwitterStream + self.stream = TwitterStream(self.username, self.password, ids) + self.stream.on_tweet += self.on_stream_tweet + self.stream.start() + + def on_trends(self, trends): + # TODO: store trends over time? + #self.trends.update(trends['trends']) + + trends = trends['trends'] + self.trends = trends[trends.keys()[0]] + + def on_stream_tweet(self, tweet): + if self.webkitcontroller is not None: + wx.CallAfter(self.webkitcontroller.realtime_tweet, tweet) + + def connect(self, accountopts): + @guithread + def later(): + self.webkitcontroller = TwitterWebKitController(self) + self.webkitcontroller.initialize(self.username, + self.password, + self.get_user_feeds(), + accountopts) + self.init_webkit_methods() + + def _verify_databases(self): + # webkit doesn't release file object locks for corrupted databases, + # so check the integrity of the databases we care about here first. + # upon any errors, they are deleted. + + import sqlite3 + + def try_query_remove_on_error(dbpath, query): + '''try a query on database dbpath. dbpath is deleted on any + exception.''' + + dbpath = path(dbpath) + log.info('verifying db %r', dbpath) + if not dbpath.isfile(): + log.info('not a file') + return + + try: + conn = sqlite3.connect(dbpath) + with conn: + conn.execute(query) + conn.close() + except Exception: + traceback.print_exc() + with traceguard: + log.warning('exception encountered, removing %r', dbpath) + dbpath.remove() + log.warning('remove completed') + + # check the integrity of the "index" database that webkit uses to track + # each site's database + try_query_remove_on_error( + path(self.webkitcontroller.webview.GetDatabaseDirectory()) / 'Databases.db', + 'select * from Databases limit 1') + + # calling window.openDatabase is necessary once for the below + # _get_database_path() call to work. + self.webkitcontroller.webview.RunScript( + '''var test_db = window.openDatabase('_test_db_', "1.0", "test db", 1024);''') + + # ensure the twitter database is okay. + try_query_remove_on_error( + self._get_database_path(), + 'create table if not exists _test (foo int)') + + def get_user_feeds(self): + def deffeed(n): + return dict(name=n, type=n) + + default_feeds = [deffeed(n) for n in + ('timeline', 'mentions', 'directs')] + + userfeeds = self.get_account_pref('feeds', default_feeds) + + def revert(): + log.warning('REVERTING user feeds, was %r:', userfeeds) + self.set_account_pref('feeds', default_feeds) + return default_feeds + + from pprint import pprint; pprint(userfeeds) + + if not isinstance(userfeeds, list): + return revert() + + try: + if userfeeds is not default_feeds: + for feed in default_feeds: + for ufeed in userfeeds: + if feed['type'] == ufeed['type']: + break + else: + return revert() + except Exception: + traceback.print_exc() + return revert() + + return userfeeds + + def init_webkit_methods(self): + # forward some methods to webkitcontroller + for method_name in ''' + open_timeline_window + clear_cache + update + on_status + on_status_with_error_popup + add_feed + edit_feed + delete_feed + set_feeds + add_group + get_users + get_prefs'''.split(): + + setattr(self, method_name, getattr(self.webkitcontroller, method_name)) + + def json(self, *a, **k): + self.webkitcontroller.json(*a, **k) + + def disconnect(self): + self.webkitcontroller.disconnect() + + def mark_all_as_read(self): + self.webkitcontroller.evaljs('markAllAsRead();') + + def on_reply(self, id, screen_name, text): + from .twitter_gui import TwitterFrame + TwitterFrame.Reply(id, screen_name, text) + + def on_retweet(self, id, screen_name, text): + from .twitter_gui import TwitterFrame + TwitterFrame.Retweet(id, screen_name, text) + + def on_direct(self, screen_name): + from .twitter_gui import TwitterFrame + TwitterFrame.Direct(screen_name) + + def mark_feed_as_read(self, feed_name): + self.webkitcontroller.JSCall('markFeedAsRead', feedName=feed_name) + + def toggle_addstocount(self, feed_name): + self.webkitcontroller.JSCall('toggleAddsToCount', feedName=feed_name) + + def get_ids_and_context(self, _feed_context): + #_feed_context ?= tab + return list(t['id'] for t in self.get_tweet_feed()), self.recent_timeline + + def get_tweet_feed(self): + self_id = self.self_tweet['id'] if self.self_tweet is not None else None + for tweet in self.recent_timeline: + if self_id is None or self_id != tweet['id']: + yield tweet + +class TwitterWebKitController(object): + def __init__(self, protocol): + from .twitter_gui import TwitterWebView + + self.hidden_frame = wx.Frame(None) + + self.protocol = protocol + w = self.webview = TwitterWebView(self.hidden_frame, protocol) + w._setup_logging(log) + + from rpc.jsonrpc import JSPythonBridge + self.bridge = JSPythonBridge(w) + self.bridge.on_call += self.on_call + + w.Bind(wx.webview.EVT_WEBVIEW_LOAD, self.on_load) + self.when_load = None + + # button callbacks for popups + self.tweet_buttons = [(_('Reply'), input_callback(self.on_popup_reply, prefill_reply)), + (_('Retweet'), input_callback(self.on_popup_retweet, prefill_retweet))] + self.direct_buttons = [(_('Direct'), input_callback(self.on_popup_direct, prefill_direct))] + + @property + def FeedWindow(self): + from .twitter_gui import TwitterFrame + for win in wx.GetTopLevelWindows(): + if isinstance(win, TwitterFrame): + if win.Parent.Top is self.hidden_frame: + return win.panel.webview + + def JSCall(self, method, **opts): + if not wx.IsMainThread(): + raise AssertionError('JSCall called from thread ' + threading.current_thread().name) + + return self.bridge.Call(method, **opts) + + def on_popup_click(self, tweet): + from common import pref + from util import net + + url = None + if pref('twitter.scan_urls', type=bool, default=True): + links = net.find_links(tweet.text) + if links and len(links) == 1: + url = links[0] + + if url is None: + if tweet.tweet_type == 'direct': + url = 'http://twitter.com/#inbox' + else: + url = 'http://twitter.com/%s/statuses/%s' % (tweet.user.screen_name, tweet.id) + + wx.LaunchDefaultBrowser(url) + + def on_popup_reply(self, text, options): + self.protocol.on_status_with_error_popup(text, options['tweet'].id) + + def on_popup_retweet(self, text, options): + self.protocol.on_status_with_error_popup(text) + + def on_popup_direct(self, text, options): + tweet = options['tweet'] + prefix = 'd ' + tweet.user.screen_name + ' ' + + if not text.startswith(prefix): + text = prefix + text + + self.protocol.on_status_with_error_popup(text) + + def on_call_corrupted_database(self, params): + log.info('on_call_corrupted_database %r', params) + self.protocol.events.on_corrupted_database() + + def on_call_edit_feed(self, feed): + self.protocol.events.on_edit_feed(feed) + + def on_call_feeds(self, feeds): + self.protocol.events.on_feeds(feeds) + + def on_call_unread(self, feeds): + self.protocol.events.on_unread_counts(feeds) + + def on_call_change_view(self, feed_name): + self.protocol.events.on_change_view(feed_name) + + def on_call_send_tweet(self, param, id_): + param = dict((str(k), v) for k, v in param.items()) + self.on_status_with_error_popup(**param) + + def on_call_favorite_tweet(self, param, id_, webview): + param = dict((str(k), v) for k, v in param.items()) + dumps = simplejson.dumps + def run_success(result, **k): + webview.RunScript('''Digsby.successIn(%s, %s);''' % (dumps(result), dumps(id_))) + def run_error(error, **k): + webview.RunScript('''Digsby.errorIn(%s, %s);''' % (dumps(error), dumps(id_))) + self.JSCall('favorite', success=run_success, error=run_error, **param) + + def on_call_delete_tweet(self, param, id_, webview): + param = dict((str(k), v) for k, v in param.items()) + dumps = simplejson.dumps + def run_success(result, **k): + webview.RunScript('''Digsby.successIn(%s, %s);''' % (dumps(result), dumps(id_))) + def run_error(error, **k): + webview.RunScript('''Digsby.errorIn(%s, %s);''' % (dumps(error), dumps(id_))) + self.JSCall('deleteTweet', success=run_success, error=run_error, **param) + + def on_call_get_idle_time(self, params, id_, webview): + from gui.native.helpers import GetUserIdleTime + t = GetUserIdleTime() + wx.CallAfter(Dsuccess, id_, self.webview, idleTime=t) + + def json(self, rpc, webview): + # Javascript calls to D from the infobox get sent here + self.on_call(rpc, webview) + + def on_call(self, json_obj, webview=None): + params = json_obj.get('params') + method = json_obj.get('method') + id_ = json_obj.get('id') + events = self.protocol.events + + try: + call = getattr(self, 'on_call_' + method) + call.__call__ + except AttributeError: + pass + else: + if call.func_code.co_argcount < 3: + return call(params[0]) + elif call.func_code.co_argcount < 4: + return call(params[0], id_) + else: + return call(params[0], id_, webview) + + if method == 'viewChanged': + feedName = params[0].get('feedName') + events.on_view(feedName) + elif method == 'following': + following = params[0].get('following') + events.following(following) + elif method == 'state': + state = params[0].get('state') + if state is not None: + events.state_changed(state) + elif method == 'received_whole_update': + events.received_whole_update() + elif method == 'trends': + trends = params[0].get('trends', None) + if trends is not None: + events.trends(trends) + elif method == 'recentTimeline': + tweets = params[0].get('tweets') + events.recent_timeline(tweets) + elif method == 'selfTweet': + tweet = params[0].get('tweet') + events.self_tweet(tweet) + elif params: + param = params[0] + if param is not None and isinstance(param, dict): + url = param.get('url') + if url and url.startswith('digsby:'): + url = UrlQuery.parse('http' + url[6:], utf8=True) # UrlQuery doesn't like digsby:// + q = url['query'].get + + netloc = url['netloc'] + if netloc == 'reply': + id, screen_name, text = q('id'), q('screen_name'), q('text') + if id and screen_name: + self.protocol.on_reply(id, screen_name, text) + elif netloc == 'retweet': + id, screen_name, text = q('id'), q('screen_name'), q('text') + if id and screen_name: + self.protocol.on_retweet(id, screen_name, text) + elif netloc == 'direct': + screen_name = q('screen_name') + if screen_name: + self.protocol.on_direct(screen_name) + + def on_call_next_item(self, params, id_, webview): + return self.protocol.social_feed.jscall_next_item(webview, id_) + + def on_call_initialize_feed(self, params, id_, webview): + return self.protocol.social_feed.jscall_initialize_feed(webview, id_) + + def on_call_status_update_clicked(self, *a, **k): + self.protocol.events.status_update_clicked() + + def on_call_hook(self, hook_name): + '''Allows Javascript to call Hooks.''' + + hooks.notify(hook_name) + + def on_call_fire(self, opts, id=None, buttons=None, onclick=None): + from common import fire, pref + from gui import skin + + # stringify keys, so that they can be keywords. + # also turn dicts into storages + opts = to_storage(dict((str(k), v) + for k, v in opts.iteritems())) + + if pref('twitter.popups.user_icons', default=True): + from gui.browser.webkit.imageloader import LazyWebKitImage + twitter_icon = skin.get('serviceicons.twitter', None) + for tweet in opts.tweets: + tweet.icon = LazyWebKitImage(tweet.user.profile_image_url, twitter_icon) + + def buttons_cb(item): + if hasattr(item.tweet, 'sender_id'): + return self.direct_buttons + else: + return self.tweet_buttons + + opts.update(onclick=onclick or self.on_popup_click, + popupid='twitter20_' + self.username + str(opts.get('popupid_postfix', '')), + buttons=buttons or buttons_cb, + max_lines=10) + + if pref('twitter.popups.mark_as_read', default=True): + opts.update(mark_as_read=self.mark_as_read) + + opts.update(badge=skin.get('serviceicons.twitter', None)) + + fire(**opts) + + def mark_as_read(self, item): + self.JSCall('markAsRead', tweet_id=item.tweet['id']) + + def initialize(self, username, password, userfeeds=None, accountopts=None): + self.username = username + self.password = password + userfeeds = [] if userfeeds is None else userfeeds + + + def when_load(): + self.protocol._verify_databases() + self.evaljs('window.resdir = %s' % simplejson.dumps((path(__file__).parent / 'res').url())) + def success(token): + opts = dict(username=self.username, + password=self.password, + feeds=userfeeds, + accountopts=accountopts or {}) + + if token is not None: + assert hasattr(token, 'key'), repr(token) + opts.update(oauthTokenKey = token.key, + oauthTokenSecret = token.secret, + + oauthConsumerKey = twitter_auth.CONSUMER_KEY, + oauthConsumerSecret = twitter_auth.CONSUMER_SECRET) + + time_correction = twitter_auth.get_time_correction() + if time_correction is not None: + opts['accountopts'].update(timeCorrectionSecs=-time_correction) + + self.JSCall('initialize', **opts) + + api_server = getattr(self.protocol.account, 'api_server', None) + if api_server is not None: + return success(None) + + if self.oauth_token is not None: + try: + token = OAuthToken.from_string(self.oauth_token) + except Exception: + traceback.print_exc() + else: + log.info('using token stored in account') + return success(token) + + def on_token(token): + token_string = token.to_string() + log.info('on_token received token from network: %r', token_string[:5]) + self.protocol.account.update_info(oauth_token=token_string) + success(token) + + def on_token_error(e): + errcode = getattr(e, 'code', None) + + # if obtaining an token fails, it may be because our time is set incorrectly. + # we can use the Date: header returned by Twitter's servers to adjust for + # this. + if errcode == 401: + server_date = getattr(e, 'hdrs', {}).get('Date', None) + retries_after_401 = getattr(self.protocol, 'retries_after_401', 0) + if server_date and retries_after_401 < 1: + self.protocol.retries_after_401 = retries_after_401 + 1 + log.warning('on_token_error: server date is %r', server_date) + server_date = parse_http_date(server_date) + log.warning('on_token_Error: RETRYING WITH NEW SERVER DATE %r', server_date) + twitter_auth.set_server_timestamp(server_date) + return twitter_auth.get_oauth_token(self.username, self.password, success=on_token, error=on_token_error) + + state = 'autherror' if errcode == 401 else 'connfail' + log.error('on_token_error: e.code is %r', errcode) + log.error(' changing state to %r', state) + self.protocol.events.state_changed(state) + + log.info('getting new oauth token from network') + twitter_auth.get_oauth_token(self.username, self.password, success=on_token, error=on_token_error) + + self.when_load = when_load + + url = APP_PATH.url() + + from gui.browser import webkit + webkit.update_origin_whitelist(url, 'https', 'twitter.com', True) + webkit.update_origin_whitelist(url, 'http', 'twitter.com', True) + + api_server = getattr(self.protocol.account, 'api_server', None) + if api_server is not None: + api = UrlQuery.parse(api_server) + webkit.update_origin_whitelist(url, api['scheme'], api['netloc'], True) + + self.bridge.LoadURL(url) + + def set_oauth_token(self, token): + self.protocol.account.oauth_token = token + + def get_oauth_token(self): + return self.protocol.account.oauth_token + + oauth_token = property(get_oauth_token, set_oauth_token) + + def disconnect(self): + @guithread + def _disconnect(): + if not wx.IsDestroyed(self.hidden_frame): + self.hidden_frame.Destroy() + + def open_timeline_window(self, feed_name=None): + from .twitter_gui import TwitterFrame + + frame = TwitterFrame.ForProtocol(self.protocol) + if frame is not None: + frame.Iconize(False) + frame.Raise() + else: + if feed_name is not None: + js = 'openWindow(%s);' % simplejson.dumps(feed_name) + else: + js = 'openWindow();' + + self.evaljs(js) + + def update(self): + self.evaljs('update();'); + + def clear_cache(self): + self.evaljs('dropAllTables();'); + + def get_users(self, success=None, error=None): + self.JSCall('getUsers', success=success, error=error) + + def get_prefs(self, success=None): + self.JSCall('getPrefs', success=success) + + def on_status_with_error_popup(self, status, reply_id=None, success=None, error=None): + '''sends a direct or tweet. on error, fire('error') is called (and + optionally your own error callback.''' + + def _error(e): + from common import fire + import gui.clipboard + + if isinstance(e, basestring): + error_message = u''.join([e, u'\n\n', u'"', status, u'"']) + else: + error_message = status + + + fire('error', + title=_('Twitter Error'), + major=_('Send Tweet Failed'), + minor=error_message, + buttons=[(_('Retry'), lambda: self.on_status_with_error_popup(status)), + (_('Copy'), lambda: gui.clipboard.copy(status)), + (_('Close'), lambda * a, **k: None)], + sticky=True) + + if error is not None: + error(e) + + return self.on_status(status, reply_id, success, _error) + + def on_status(self, status, reply_id=None, success=None, error=None): + return self.JSCall('tweet', + status=status, + replyTo=reply_id, + success=success, + error=error) + + def evaljs(self, js): + if not wx.IsMainThread(): + raise AssertionError('evaljs called from thread ' + threading.current_thread().name) + + return self.webview.RunScript(js) + + def realtime_tweet(self, tweet_json): + script = 'onTweet(' + tweet_json.strip() + ');' + self.evaljs(script) + + def on_load(self, e): + e.Skip() + if e.GetState() == wx.webview.WEBVIEW_LOAD_ONLOAD_HANDLED: + if self.when_load is not None: + when_load, self.when_load = self.when_load, None + when_load() + + def add_feed(self, feed_info): + self.JSCall('addFeed', feed=feed_info) + + def edit_feed(self, feed_info): + self.JSCall('editFeed', feed=feed_info) + + def delete_feed(self, feed_name): + self.JSCall('deleteFeed', feedName=feed_name) + + def set_feeds(self, feeds): + self.JSCall('setFeeds', feeds=feeds) + + def add_group(self, group_info): + self.JSCall('addGroup', group=group_info) + +class TwitterIB(gui_providers.InfoboxProviderBase): + protocols.advise(asAdapterForTypes=[TwitterAccount], instancesProvide=[gui_interfaces.ICacheableInfoboxHTMLProvider]) + + def __init__(self, acct): + gui_providers.InfoboxProviderBase.__init__(self) + self.acct = acct + + def get_html(self, *a, **k): + if k.pop('set_dirty', True): + self.acct._dirty = False + return gui_providers.InfoboxProviderBase.get_html(self, *a, **k) + + def get_app_context(self, ctxt_class): + return ctxt_class(path(__file__).parent.parent, self.acct.protocol) + + def get_context(self): + ctxt = gui_providers.InfoboxProviderBase.get_context(self) + conn = self.acct.twitter_protocol + import twitter_util as tutil + from path import path + resdir = path(__file__).dirname() / 'res' + ctxt.update(acct=self.acct, + conn=conn, + trends=conn.trends, + tweets=[], + counts=conn.unread_counts, + self_tweet=conn.self_tweet, + res=lambda p: (resdir / p).url(), + twitter_linkify=tutil.twitter_linkify, + format_tweet_date=tutil.format_tweet_date) + return ctxt + + @property + def _dirty(self): + # TODO: no + return True + +def title_from_query(query): + ''' + attempts to return a "title" for a search query + + >>> title_from_query('"Happy Labor Day" OR "Labour Day"') + 'Happy Labor Day' + ''' + + def dequote(s): + if s.count('"') == 2 and s.startswith('"') and s.endswith('"'): + return s[1:-1] + + title = dequote(query) + if title is None: + title = query.split(' OR ')[0].split(' AND ')[0] + title = dequote(title) or title.split()[0].strip(string.punctuation) + + return title + +def guithread(func): + '''Calls func now if we're on the GUI thread; else calls it later on the + GUI thread.''' + + if wx.IsMainThread(): + with traceguard: + func() + else: + wx.CallAfter(func) + +class input_callback(object): + '''Passed to fire() as handlers for "buttons" callbacks. Causes popups to + show input fields after pressing those buttons.''' + + # TODO: document this interface and place an abstract class in toast.py + + close_button = True + spellcheck = True + + def spellcheck_regexes(self): + import twitter_util + return twitter_util.spellcheck_regex_ignores + + def __init__(self, cb, value_cb): + self.input_cb = cb + self.get_value = value_cb + self.char_limit = 140 + +def _account_pref_key(username, name): + return '.'.join(['twitter.prefs', username, name]) + +def _get_account_pref(username, name, default): + from common import pref + p = pref(_account_pref_key(username, name), default=default) + if isinstance(p, basestring): p = simplejson.loads(p) + return p + +def get_users(callback, accts=None): + if accts is None: + from common import profile + accts = [a for a in profile.socialaccounts + if a.connection is not None + and isinstance(a, TwitterAccount)] + + ctx = dict(count=0) + all_users = {} + + for acct in accts: + def cb(users): + all_users.update(users) + ctx['count'] += 1 + if ctx['count'] == len(accts): + callback(all_users) + + acct.connection.get_users(cb) + +def try_opening_tempfile(dirpath): + ''' + some users see WebKit returning undefined from the openDatabase call. this + function attempts to open a file in the database directory and write to it- + to see if they don't have permission. + ''' + + try: + tempfile = os.path.join(dirpath, 'test.tmp') + with open(tempfile, 'w') as f: + f.write('test') + if not os.path.isfile(tempfile): + raise Exception('file wasn\'t found after write: %r' % tempfile) + + try: + os.remove(tempfile) + except Exception: + pass + + except Exception: + traceback.print_exc() + return False + else: + log.info('wrote to %r successfully', dirpath) + return True + +def htmlize_tweets(protocol, tweets, self_tweet=None): + '''Given a protocol and a sequence of tweets, returns infobox HTML for them.''' + + t = TwitterIB(Storage(twitter_protocol=protocol, protocol='twitter')) + return t.get_html(None, + set_dirty=False, + file='tweets.tenjin', + dir=t.get_context()['app'].get_res_dir('base'), + context=Storage(tweets=tweets, self_tweet=self_tweet)) + +def parse_http_date(s): + import email.utils + return email.utils.mktime_tz(email.utils.parsedate_tz(s)) + diff --git a/digsby/src/plugins/twitter/twitter_account_gui.py b/digsby/src/plugins/twitter/twitter_account_gui.py new file mode 100644 index 0000000..12b5c59 --- /dev/null +++ b/digsby/src/plugins/twitter/twitter_account_gui.py @@ -0,0 +1,189 @@ +import wx +import sys +from copy import deepcopy +from wx import StaticText, EXPAND, TOP, ALIGN_CENTER, VERTICAL, HORIZONTAL, \ + Color, ALIGN_CENTER_VERTICAL, CheckBox, Choice +from gettext import ngettext +from gui.uberwidgets.autoheightstatictext import AutoHeightStaticText + +UPDATE_DESC_TEXT = _('Twitter allows for 150 requests per hour. Make sure to leave room for manual updates and other actions.') + +# A choice appears for each of these update type +UPDATE_TYPES = [ + # protocolmeta key gui string + ('friends_timeline', _('Friends:')), + ('replies', _('Mentions:')), + ('direct_messages', _('Directs:')), + ('search_updatefreq',_('Searches:')), +] + +def adds_to_total(updatetype): + return updatetype != 'search_updatefreq' # searches don't cost API calls + +# Options you can pick for each update type. +UPDATE_CHOICES = [('1', 1), + ('2', 2), + ('3', 3), + ('4', 4), + ('5', 5), + ('10', 10), + ('15', 15), + ('20', 20), + ('30', 30)] + +# append "minutes" to all the strings above +UPDATE_CHOICES = [(ngettext('{mins} minute', '{mins} minutes', n).format(mins=n), n) for s, n in UPDATE_CHOICES] + +# add a Never option +UPDATE_CHOICES.append((_('Never'), 0)) + +UPDATE_GUI_STRINGS = [s for s, n in UPDATE_CHOICES] + +UPDATE_VALUES = dict((n, i) for i, (s, n) in enumerate(UPDATE_CHOICES)) + +def color_for_total(total): + 'Returns a wx color for an update count per hour.' + + if total > 150: return Color(0xFF, 0, 0) + elif total > 125: return Color(0xFF, 0xA5, 0x00) + else: return Color(0, 0, 0) + +def protocolinfo(): + from common.protocolmeta import protocols + return protocols['twitter'] + +class TwitterAccountPanel(wx.Panel): + ''' + Selects update frequencies for different Twitter update types. + + Shows in the account dialog when editing Twitter accounts. + ''' + def __init__(self, parent, account): + wx.Panel.__init__(self, parent) + + self.construct() + self.layout() + self.Bind(wx.EVT_CHOICE, self.SelectionChanged) + + # force an initial update + self.set_values(account) + self.SelectionChanged() + + @property + def show_server_options(self): + return getattr(sys, 'DEV', False) + + def set_values(self, account): + # set initial values based on information from a Twitter Account + defaults = deepcopy(protocolinfo().defaults) + defaults.update(getattr(account, 'update_frequencies', {})) + + self.auto_throttle.Value = getattr(account, 'auto_throttle', defaults.get('auto_throttle')) + + for name, _ in UPDATE_TYPES: + self.choices[name].Selection = UPDATE_VALUES.get(defaults.get(name), 4) + + if self.show_server_options: + api_server = getattr(account, 'api_server', None) + if api_server is not None: + self.server.Value = api_server + + def construct(self): + 'Construct GUI components.' + + self.header = StaticText(self, -1, _('Update Frequency:')) + self.header.SetBold() + + self.desc = AutoHeightStaticText(self, -1, UPDATE_DESC_TEXT) + self.desc.MinSize = wx.Size(40, -1) + + self.update_texts = {} # "Tweets every" + self.choices = {} # "0 - 10 minutes" + + # Build StaticText, Choice for each update option. + for i, (name, guistring) in enumerate(UPDATE_TYPES): + self.update_texts[name] = StaticText(self, -1, guistring) + self.choices[name] = Choice(self, choices = UPDATE_GUI_STRINGS) + + self.total = StaticText(self, style = ALIGN_CENTER) + self.total.SetBold() + + self.auto_throttle = CheckBox(self, label=_('Auto-throttle when Twitter lowers the rate limit.'), name='auto_throttle') + + if self.show_server_options: + self.server_text = StaticText(self, -1, _('Server')) + self.server = wx.TextCtrl(self, -1) + + def info(self): + """ + Returns a mapping for this dialog's info. + + This looks like + {'friends_timeline': 4, + 'replies': 4, + 'directs': 4} + """ + + d = dict((name, UPDATE_CHOICES[self.choices[name].Selection][1]) + for name, _gui in UPDATE_TYPES) + d['auto_throttle'] = self.auto_throttle.Value + if self.show_server_options: + d['api_server'] = self.server.Value.strip() or None + return d + + def layout(self): + 'Layout GUI components.' + + gb = wx.GridBagSizer(hgap = 3, vgap = 3) + + for i, (name, guistring) in enumerate(UPDATE_TYPES): + gb.Add(self.update_texts[name], (i, 0), flag = ALIGN_CENTER_VERTICAL) + gb.Add(self.choices[name], (i, 1)) + + gb_h = wx.BoxSizer(HORIZONTAL) + gb_h.Add(gb, 0, EXPAND) + + t_v = wx.BoxSizer(VERTICAL) + t_v.AddStretchSpacer(1) + t_v.Add(self.total, 0, EXPAND | ALIGN_CENTER) + t_v.AddStretchSpacer(1) + + gb_h.Add(t_v, 1, EXPAND | ALIGN_CENTER) + + inner_sz = wx.BoxSizer(VERTICAL) + + if self.show_server_options: + hsz = wx.BoxSizer(HORIZONTAL) + hsz.Add(self.server_text, 0, EXPAND | wx.RIGHT, 7) + hsz.Add(self.server, 1, EXPAND | wx.BOTTOM, 7) + inner_sz.Add(hsz, 0, EXPAND) + + inner_sz.AddMany([ + (self.header, 0, EXPAND), + (self.desc, 1, EXPAND | TOP, 5), + (gb_h, 0, EXPAND | TOP, 7), + (self.auto_throttle, 0, EXPAND | TOP, 7), + ]) + + sz = self.Sizer = wx.BoxSizer(VERTICAL) + sz.Add(inner_sz, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 7) + + self.Fit() + + def SelectionChanged(self, e = None): + 'Invoked when the user changes an update frequence Choice control.' + + # sum up 60/minutes for each choice to find how many updates + # per hour we will request. + mins = [UPDATE_CHOICES[c.Selection][1] for name, c in self.choices.items() + if adds_to_total(name)] + total_updates = sum(((60./min) if min else 0) for min in mins) + + # the function color_for_total will use color to warn the user + # if there is going to be too many updates. + total_txt = self.total + total_txt.SetLabel(_('{total_updates} / hour').format(total_updates=int(total_updates))) + total_txt.ForegroundColour = color_for_total(total_updates) + + self.Layout() + diff --git a/digsby/src/plugins/twitter/twitter_auth.py b/digsby/src/plugins/twitter/twitter_auth.py new file mode 100644 index 0000000..c7c23e4 --- /dev/null +++ b/digsby/src/plugins/twitter/twitter_auth.py @@ -0,0 +1,175 @@ +import time +import datetime +import logging +import lxml.html as html +import util +import util.net as net +import util.callbacks as callbacks +import oauth.oauth as oauth +import common.oauth_util as oauth_util +import rauth.service + +log = logging.getLogger('twitter.auth') + +CONSUMER_KEY = '' +CONSUMER_SECRET = '' + +class DigsbyTwitterAuthHandler(rauth.service.OAuth1Service): + name = 'twitter' + consumer_key = CONSUMER_KEY + consumer_secret = CONSUMER_SECRET + request_token_url = 'https://api.twitter.com/oauth/request_token' + access_token_url = 'https://api.twitter.com/oauth/access_token' + authorize_url = 'https://api.twitter.com/oauth/authorize' + + def __init__(self): + super(DigsbyTwitterAuthHandler, self).__init__( + name = self.name, + consumer_key = self.consumer_key, + consumer_secret = self.consumer_secret, + request_token_url = self.request_token_url, + access_token_url = self.access_token_url, + authorize_url = self.authorize_url, + header_auth = True + ) + self.callback = None + + @callbacks.callsback + def get_oauth_access_token(self, username, password, callback = None): + self.username = username + self.password = password + self.callback = callback + self.send_user_to_oauth(success = self.process_authorize_response, + error = self.handle_error) + + @callbacks.callsback + def send_user_to_oauth(self, callback = None): + try: + self.request_token, self.request_token_secret = self.get_request_token('GET') + self.authorize_url = self.get_authorize_url(self.request_token) + except Exception as e: + log.error("Error requesting token or getting auth url: %r", e) + import traceback; traceback.print_exc() + return callback.error(e) + + log.info('got authorize url: %r', self.authorize_url) + + # TODO: send user to authorize_url, wait for them to hit callback URL? + self._auth = TwitterPinAuthenticator( + self.username, + lambda url: url, + '/twitter/{username}/oauth'.format(username = self.username), + _('Twitter Login - %s') % self.username, + self.authorize_url, + 'serviceicons.twitter' + ) + + self._auth.bind('on_done', self._on_auth_done) + self._auth.authenticate(success = self.process_authorize_response, + error = callback.error) + + def _on_auth_done(self): + auth, self._auth = self._auth, None + if auth is not None: + auth.unbind('on_done', self._on_auth_done) + auth.done() + self._auth = None + + def handle_error(self, *a): + log.error('error during twitter auth: %r', a) + cb, self.callback = self.callback, None + if cb is not None: + cb.error(*a) + + def process_authorize_response(self, verifier): + log.info('got success during twitter auth: %r', verifier) + self.verifier = verifier + response = self.get_access_token(self.request_token, self.request_token_secret, http_method='GET', oauth_verifier=self.verifier) + data = response.content + self.access_token = data['oauth_token'] + self.access_token_secret = data['oauth_token_secret'] + cb, self.callback = self.callback, None + + if cb is not None: + cb.success(oauth.OAuthToken(self.access_token, self.access_token_secret)) + +def to_utf8(s): + if isinstance(s, unicode): + return s.encode('utf-8') + else: + return s + +_time_correction = None + +def get_time_correction(): + return _time_correction + +def set_server_timestamp(server_time_now): + assert isinstance(server_time_now, float) + global _time_correction + _time_correction = server_time_now - time.time() + +def generate_corrected_timestamp(): + now = time.time() + if _time_correction is not None: + t = now + _time_correction + log.warn('using corrected timestamp: %r', datetime.fromtimestamp(t).isoformat()) + else: + t = now + log.warn('using UNcorrected timestamp: %r', datetime.fromtimestamp(t).isoformat()) + return t + +class TwitterPinAuthenticator(oauth_util.InternalBrowserAuthenticator): + frame_size = (700, 675) + close_rpc = 'digsby://digsbyjsonrpc/close' + def do_close_frame(self): + import wx + wx.CallAfter(self.frame.Close) + self.frame = None + self.done() + + def on_title_changed(self, browser, event, callback): + if event.Title.startswith(self.close_rpc): + if not self.success and callback: + callback.error(oauth_util.UserCancelled("User cancelled.")) + self.do_close_frame() + return + + def before_load(self, browser, navurl, callback): + import wx + + if wx.IsDestroyed(browser): + if callback: + callback.error() + return + + close_rpc = 'digsby://digsbyjsonrpc/close' + if navurl.startswith(close_rpc) or navurl.startswith('http://www.digsby.com/'): + if not self.success and callback: + callback.error(oauth_util.UserCancelled("User cancelled.")) + self.do_close_frame() + return + + log.info('before_load: %r', navurl) + doc = html.fromstring(browser.HTML) + verifier = getattr(doc.find('.//*[@id="oauth_pin"]//code'), 'text', None) + log.info("success calling %r with %r", getattr(callback, 'success', None), verifier) + + if verifier: + self.success = True + if callback: + callback.success(verifier) + self.do_close_frame() + else: + browser.RunScript(''' + using('imports/jquery', function($) { + $('#username_or_email').val('%(username)s'); + $('#password').focus(); + var close_window_rpc = function() { document.title = '%(close_rpc)s'; }; + $('a.deny').live('click', close_window_rpc); + $('input#deny').live('click', close_window_rpc); + });''' % dict(username = str(self.username), close_rpc = close_rpc)) + +@callbacks.callsback +def get_oauth_token(username, password, callback = None): + DigsbyTwitterAuthHandler().get_oauth_access_token(username, password, callback = callback) diff --git a/digsby/src/plugins/twitter/twitter_gui.py b/digsby/src/plugins/twitter/twitter_gui.py new file mode 100644 index 0000000..ac9b728 --- /dev/null +++ b/digsby/src/plugins/twitter/twitter_gui.py @@ -0,0 +1,2313 @@ +from gui.uberwidgets.spacerpanel import SpacerPanel +from gui.uberwidgets.formattedinput2.formatprefsmixin import StyleFromPref +from gui.toolbox.scrolling import WheelScrollCtrlZoomMixin, \ + WheelShiftScrollFastMixin,\ + ScrollWinMixin, FrozenLoopScrollMixin +if __name__ == '__main__': + import __builtin__ + __builtin__._ = lambda s: s + +import config +import hooks +import wx.webview +import unicodedata +from traceback import print_exc +import twitter_util +from common import pref, setpref + +from functools import wraps +from collections import namedtuple +from time import time +from util import threaded, traceguard, odict +from util.primitives.funcs import Delegate +from util.callbacks import CallbackStream +from util.net import isurl + +from gui.uberwidgets.formattedinput2.FormattedExpandoTextCtrl import FormattedExpandoTextCtrl +from gui.uberwidgets.formattedinput2.splittereventsmixin import SplitterEventMixin +from gui.uberwidgets.UberBar import UberBar +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.umenu import UMenu +from gui.uberwidgets.simplemenu import SimpleMenuItem +from gui.toolbox import OKCancelFrame, GetDoubleClickTime, std_textctrl_menu, \ + pick_image_file, paint_outline, insert_text +from gui.anylists import AnyList, AnyRow +from gui.social_status_dialog import color_for_message, _msg_color_thresholds +from gui import skin, clipboard +import gui.windowfx as fx + +from logging import getLogger; log = getLogger('twitter_gui') + +import mimetypes + +if config.platform == 'win': + import gui.native.win.winhelpers + gui.native.win.winhelpers.__file__ # gettattr actually causes it to load (for side effects...) + +TWITTER_MAX_CHARCOUNT = 140 +SEARCH_HELP_URL = 'http://search.twitter.com/operators' + +KEEP_ON_TOP_LABEL = _('&Keep on Top') + +WEBKIT_TRENDS_MINSIZE = (350, 50) +GROUP_LIST_MINSIZE = (WEBKIT_TRENDS_MINSIZE[0], 180) +flags = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND + +ID_FAVORITES = wx.NewId() +ID_HISTORY = wx.NewId() + +ID_NEWGROUP = wx.NewId() +ID_NEWSEARCH = wx.NewId() +ID_EDITANDREARRANGE = wx.NewId() +ID_DELETE_FEED = wx.NewId() +ID_EDIT_FEED = wx.NewId() +ID_TOGGLE_ADDSTOCOUNT = wx.NewId() + +ID_SHORTEN_LINKS = wx.NewId() +ID_IMAGE = wx.NewId() +ID_SHRINK = wx.NewId() +ID_GLOBAL_STATUS = wx.NewId() + +ID_HIDE_INPUT_BAR = wx.NewId() + +ID_MARKFEEDASREAD = wx.NewId() + +ID_KEEPONTOP = wx.NewId() +ID_INCREASE_TEXT_SIZE = wx.NewId() +ID_DECREASE_TEXT_SIZE = wx.NewId() +ID_RESET_TEXT_SIZE = wx.NewId() +ID_UPDATE = wx.NewId() +ID_WEBVIEW_COPY = wx.NewId() + +TwitterUser = namedtuple('TwitterUser', 'id screen_name selected profile_image_url') + +def message_color(m): + return wx.Color(*color_for_message(m, _msg_color_thresholds)) + +permanent_feed_types = set(('timeline', 'mentions', 'directs')) +def is_perm_feed(feed): + return feed['type'] in permanent_feed_types + +class TwitterDialog(OKCancelFrame): + default_style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT + def __init__(self, *a, **k): + if 'style' not in k: + k['style'] = TwitterDialog.default_style + + OKCancelFrame.__init__(self, *a, **k) + + self.on_info = Delegate() + self.Bind(wx.EVT_BUTTON, self.on_button) + self.SetFrameIcon(skin.get('serviceicons.twitter')) + + def set_component(self, panel): + self.SetBackgroundColour(panel.BackgroundColour) + OKCancelFrame.set_component(self, panel, border=5, line=True) + self.Fit() + self.SetMinSize(self.Size) + + def on_button(self, e): + self.Hide() + if e.Id == wx.ID_OK: + self.on_info(self.info()) + + self.Destroy() + +class TwitterSearchDialog(TwitterDialog): + def __init__(self, parent, options): + + if options.get('title', None): + ok_caption = _('&Save') + saving = True + else: + ok_caption = _('&Search') + saving = False + + TwitterDialog.__init__(self, parent, + title = _('Twitter Search'), + ok_caption = ok_caption, + cancel_caption = _('&Cancel'), + style = TwitterDialog.default_style & ~wx.RESIZE_BORDER) + + self.search_panel = TwitterSearchPanel(self, options, saving) + self.set_component(self.search_panel) + + def info(self): + return self.search_panel.info() + +class TwitterDialogPanel(wx.Panel): + def __init__(self, *a, **k): + wx.Panel.__init__(self, *a, **k) + + def escape_closes(self, e): + if e.KeyCode == wx.WXK_ESCAPE: + self.SimulateCommandEvent(wx.ID_CANCEL) + else: + e.Skip() + + def SimulateCommandEvent(self, id): + event = wx.CommandEvent(wx.EVT_COMMAND_BUTTON_CLICKED, id) + return self.ProcessEvent(event) + + +class TwitterSearchPanel(TwitterDialogPanel): + 'The main panel for the Twitter search dialog.' + + def __init__(self, parent, options, saving): + TwitterDialogPanel.__init__(self, parent) + self.feed_name = options.get('name', None) + + self.setup_help_button() + + self.construct(options) + self.layout(options) + self.saving = saving + + def info(self): + info = dict() + + for attr in ('query', 'title', 'merge', 'popups'): + ctrl = getattr(self, attr, None) + if ctrl is not None: + info[attr] = ctrl.Value + + if self.feed_name is not None: + info['name'] = self.feed_name + + info.update(type = 'search', + save = self.saving) + + return info + + def construct(self, options): + trends = options.get('trends', None) + + # Search For + search_for_string = options.get('query', '') + self.search_for_header = header(self, _('Search For:')) + self.query = wx.TextCtrl(self, value=search_for_string) + self.query.Bind(wx.EVT_KEY_DOWN, self.escape_closes) + focus_on_show(self.query) + + # Title + title = options.get('title', None) + if title is not None: + self.title_header = header(self, _('Title:')) + self.title = wx.TextCtrl(self, value=make_title(title, search_for_string)) + self.title.Bind(wx.EVT_KEY_DOWN, self.escape_closes) + + # Trending Topics + if trends is not None: + self.trending_topics_header = header(self, _('Trending Topics:')) + webview = self.trending_topics_html = WebKitDisplay(self) + webview.SetMinSize(WEBKIT_TRENDS_MINSIZE) + + webview.SetPageSource(trending_topics_html(trends)) + webview.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.__beforeload) + + # Search Options + search_opts = options.get('search_opts', None) + if search_opts is not None: + self.search_opts_header = header(self, _('Search &Options')) + self.merge = wx.CheckBox(self, -1, _('Merge search results into Timeline view')) + self.merge.Value = bool(search_opts.get('merge', False)) + self.popups = wx.CheckBox(self, -1, _('Popup notifications for new search results')) + self.popups.Value = bool(search_opts.get('popups', True)) + + def layout(self, options): + items = [] + + # Title + if options.get('title', None) is not None: + items.extend([self.title_header, self.title]) + + # Search For + items.extend([(1, 3), + (self.search_for_header, 0), + (1, 3), + self.query]) + + # Trending Topics + if options.get('trends', None) is not None: + items.extend([self.trending_topics_header, + (self.trending_topics_html, 0, wx.EXPAND, 0)]) + else: + items.extend((WEBKIT_TRENDS_MINSIZE[0], 0)) + + # Search Options + if options.get('search_opts', None) is not None: + items.extend([self.search_opts_header, self.merge, self.popups]) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.AddMany((item, 0, wx.BOTTOM | wx.EXPAND, 5) if not isinstance(item, tuple) else item + for item in items) + + self.Sizer = outer_sizer = wx.BoxSizer(wx.HORIZONTAL) + outer_sizer.Add(sizer, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 4) + + def __beforeload(self, e): + e.Cancel() + + trend = e.URL.decode('utf8url') + self.query.Value = trend + + if self._is_double_click(trend): + self.SimulateCommandEvent(wx.ID_OK) + else: + self.query.SetInsertionPointEnd() + self.query.SetFocus() + + def _is_double_click(self, trend): + # keep track of the last time a trend was clicked to simulate double clicks + is_double_click = False + + now = time() + if hasattr(self, 'last_link_click'): + last_trend, last_time = self.last_link_click + if last_trend == trend and now - last_time <= GetDoubleClickTime(): + is_double_click = True + + self.last_link_click = (trend, now) + + return is_double_click + + # painting/clicking the help button + + def setup_help_button(self): + self.help_bitmap = wx.Bitmap(skin.resourcedir() / 'skins/default/help.png') + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.in_help = False + + def OnMotion(self, e): + e.Skip() + if self.HelpRectangle.Contains(e.Position): + if not self.in_help: + self.in_help = True + self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + else: + if self.in_help: + self.in_help = False + self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + + def OnLeftDown(self, e): + if self.HelpRectangle.Contains(e.Position): + wx.LaunchDefaultBrowser(SEARCH_HELP_URL) + else: + e.Skip() + + def OnPaint(self, e): + dc = wx.PaintDC(self) + r = self.HelpRectangle + dc.DrawBitmap(self.help_bitmap, r.x, r.y, True) + + @property + def HelpRectangle(self): + b = self.help_bitmap + r = wx.RectPS(self.query.Rect.TopRight, (b.Width, b.Height)) + r.Position = r.Position - wx.Point(*r.Size) - wx.Point(0, 3) + return r + +from gui.browser.webkit import WebKitDisplay + +if config.platform == 'win': + FONT_CSS = 'font-family: Tahoma, MS Sans Serif; font-size: 11px;' +else: + FONT_CSS = '' # TODO: platform GUI fonts + +def trending_topics_html(trends): + 'Returns the HTML that gets put into the webview for showing trending topics.' + + # try to match the default GUI dialog color + dialog_html_color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE).GetAsString(wx.C2S_HTML_SYNTAX) + + links = [] + for trend in trends: + links.append('{name}'.format( + query = trend['query'].encode('utf8url'), + name = trend['name'].encode('utf8'))) + + topics_html = ', '.join(links) + + css = '''\ +body { + margin: 0; + background-color: %s; + %s + -webkit-text-size-adjust: none; + -webkit-user-select: none; +}''' % (dialog_html_color, FONT_CSS) + + html = '
{body}' + + return html.format(css=css, body=topics_html) + +def make_title(title, search_for): + if isinstance(title, basestring) and title: + return title + else: + return search_for.split()[0] + +class TwitterGroupDialog(TwitterDialog): + def __init__(self, parent, options=None): + TwitterDialog.__init__(self, parent, + title = _('Twitter Group'), + ok_caption = _('&Save')) + + self.group_panel = TwitterGroupPanel(self, options) + self.set_component(self.group_panel) + + def info(self): + return self.group_panel.info() + +class TwitterGroupPanel(TwitterDialogPanel): + def __init__(self, parent, options = None): + TwitterDialogPanel.__init__(self, parent) + + if options is None: + options = {} + + self.users = options.get('users') + self.feed_name = options.get('name', None) + self.construct(options) + self.layout(options) + + # disable the OK button if there is no group name + self.groupName.Bind(wx.EVT_TEXT, self.on_text) + self.on_text() + self.Bind(wx.EVT_PAINT, self.__onpaint) + + def __onpaint(self, e): + e.Skip() + dc = wx.PaintDC(self) + + paint_outline(dc, self.group_members_list) + + def on_text(self, e=None): + if e is not None: e.Skip() + self.Top.OKButton.Enable(bool(self.groupName.Value)) + + def info(self): + info = dict(ids = [row.data.id for row in self.group_members_list + if row.IsChecked()]) + + for attr in ('groupName', 'filter', 'popups'): + ctrl = getattr(self, attr, None) + if ctrl is not None: + info[attr] = ctrl.Value + + if self.feed_name is not None: + info['name'] = self.feed_name + + info['type'] = 'group' + + return info + + def construct(self, options): + self.group_name_header = header(self, _('&Group Name')) + self.groupName = wx.TextCtrl(self, -1, options.get('groupName', '')) + focus_on_show(self.groupName) + self.groupName.Bind(wx.EVT_KEY_DOWN, self.escape_closes) + + self.group_members_header = header(self, _('Group &Members')) + self.group_members_list = TwitterUserList(self, self.users, + row_control = TwitterUserRow, + edit_buttons = None, + draggable_items = False, + velocity = None) + + self.group_members_list.SetMinSize(GROUP_LIST_MINSIZE) + + if options.get('search', False): + self.group_members_search = wx.SearchCtrl(self, -1) + self.group_members_search.ShowSearchButton(True) + + self.group_options_header = header(self, _('Group &Options')) + self.filter = wx.CheckBox(self, -1, _("&Filter this group's tweets out of the Timeline view")) + self.filter.Value = options.get('filter', False) + self.popups = wx.CheckBox(self, -1, _("Show &popup notifications for this group's tweets")) + self.popups.Value = options.get('popups', False) + + def layout(self, options): + members_header_sizer = wx.BoxSizer(wx.HORIZONTAL) + members_header_sizer.Add(self.group_members_header, 0, wx.ALIGN_BOTTOM) + if options.get('search', False): + members_header_sizer.AddStretchSpacer(1) + members_header_sizer.Add(self.group_members_search, 0, wx.EXPAND) + + padding = 5 + ctrls = [ + self.group_name_header, + self.groupName, + members_header_sizer, + (self.group_members_list, 1, flags, padding), + (1, 3), + self.group_options_header, + self.filter, + self.popups, + ] + + sizer = self.Sizer = wx.BoxSizer(wx.VERTICAL) + sizer.AddMany((c, 0, flags, padding) if not isinstance(c, tuple) else c + for c in ctrls) + sizer.Add((1,6)) + +class TwitterUserList(AnyList): + SelectionEnabled = False + ClickTogglesCheckbox = True + + def __init__(self, *a, **k): + AnyList.__init__(self, *a, **k) + + self.image_map = {} + + from gui.browser.webkit.imageloader import WebKitImageLoader + self.image_loader = WebKitImageLoader() + self.image_loader.on_load += self.on_load + + self.Top.Bind(wx.EVT_CLOSE, self.__OnClosing) + + def __OnClosing(self, e): + e.Skip() + self.disconnect() + + def disconnect(self): + self.image_loader.on_load -= self.on_load + + def on_load(self, img, src): + if wx.IsDestroyed(self): + self.disconnect() + return + + data = self.image_map[src] + data['bitmap'] = img + + # repaint all rows with this url + for ctrl in data['ctrls']: + ctrl.Refresh() + + def get_image(self, row): + url = row.profile_image_url + if not url: return None + + try: + imgdata = self.image_map[url] + except KeyError: + imgdata = self.image_map[url] = dict( + bitmap=self.image_loader.get(url), + ctrls=set([row])) + + imgdata['ctrls'].add(row) + return imgdata['bitmap'] + +class TwitterUserRow(AnyRow): + checkbox_border = 3 + min_row_height = 26 + image_offset = (6, 0) + + def __init__(self, *a, **k): + AnyRow.__init__(self, *a, **k) + + def PopulateControls(self, user): + self.checkbox.Value = getattr(user, 'selected', False) + self.text = user.screen_name + self.profile_image_url = twitter_util.twitter_mini_img_url(user.profile_image_url) if user.profile_image_url else None + + @property + def image(self): + return None + + def PaintMore(self, dc): + r = self.ClientRect + + ICON_SIZE = 24 + padding = 1 + x = r.Right - ICON_SIZE - padding + y = r.Top + padding + + icon = self.Parent.get_image(self) + if icon is None: + + try: + icon = self.Parent._noicon + except AttributeError: + icon = self.Parent._noicon = get_twitter_noicon().Resized(ICON_SIZE) + + if icon: + dc.DrawBitmap(icon, x, y, True) + + def on_right_up(self, *a, **k): pass + +def get_twitter_noicon(): + from twitter import RES_PATH + return wx.Bitmap(RES_PATH / 'img' / 'twitter_no_icon.png') + +def header(parent, txt): + ctrl = wx.StaticText(parent, -1, txt) + ctrl.SetBold() + return ctrl + +def count_twitter_characters(unistr): + ''' + returns what twitter considers the "character count" of the specified + unicode string. + + see http://dev.twitter.com/pages/counting_characters for details--but the + short version is that twitter counts the number of code points in an NFC + normalized version of the string. + ''' + + assert isinstance(unistr, unicode) + return len(unicodedata.normalize('NFC', unistr)) + +def textctrl_limit_func(val): + ''' + used by VisualCharacterLimit to highlight text red if it's over the character limit. + returns the count limit of characters + ''' + l = TWITTER_MAX_CHARCOUNT + + try: + # if the text starts with "d username " then the text can be longer. + match = twitter_util.direct_msg.match(val) + if match: + grp = match.groups()[0] # the username + l += len(grp) + l += 3 # for "d[username]" -- the d and two spaces + except Exception: + import traceback + traceback.print_exc_once() + + return l + + +from gui.uberwidgets.formattedinput2.toolbar import SkinnedToolBar + +class TwitterInputToolBar(SkinnedToolBar): + def __init__(self, *a, **k): + self.count = '' + SkinnedToolBar.__init__(self, *a, **k) + self.construct() + #self.Bind(wx.EVT_CONTEXT_MENU, self.on_context_menu) + self.Top.Bind(wx.EVT_MENU, self.on_hide, id=ID_HIDE_INPUT_BAR) + + self.count = str(TWITTER_MAX_CHARCOUNT) + + def on_hide(self, e): + e.Skip() + self.Show(False) + self.Parent.Layout() + + def on_context_menu(self, e): + try: menu = self._context_menu + except AttributeError: + menu = self._context_menu = UMenu(self) + menu.AddItem(_('&Hide Toolbar'), id=ID_HIDE_INPUT_BAR) + menu.PopupMenu() + + def construct(self): + buttons = [] + + action_buttons = [(_('Shorten &Links'), ID_SHORTEN_LINKS, 'link'), + (_('Image'), ID_IMAGE, 'image'), + (_('Shrink'), ID_SHRINK, 'shrink'), + (_('Global Status'), ID_GLOBAL_STATUS, 'global')] + + for label, id, icon in action_buttons: + button = UberButton(self, id, label, + icon=skin.get('twitter.toolbaricons.' + icon)) + setattr(self, icon + '_button', button) + buttons.append(button) + + self.AddMany(buttons) + + def construct_progress_bar(self): + bar = self.progress_bar = wx.Gauge(self, style = wx.GA_SMOOTH) + self.Add(bar, expand=True) + self.content.Layout() + return bar + + def destroy_progress_bar(self): + self.Detach(self.progress_bar) + self.progress_bar.Destroy() + self.Refresh() + + def LinkTextControl(self, text_ctrl): + # update the character count and text color when text changes + self._text_ctrl = text_ctrl + text_ctrl.Bind(wx.EVT_TEXT, self._on_input_text) + + def CountRect(self, dc = None): + dc = dc if dc is not None else wx.MemoryDC() + w, h = dc.GetTextExtent(self.count) + crect = self.ClientRect + xpad = 3 + return wx.Rect(crect.Right - w - xpad, crect.VCenterH(h), w + xpad, h) + + def DoUpdateSkin(self, skinobj): + super(TwitterInputToolBar, self).DoUpdateSkin(skinobj) + + # grab the color to draw the character counter with--we use + # the bottom bar button normal color + toolbar_skin = skin.get(self.skinTTB.localitems['toolbarskin'] or '', {}) + button_skin_name = toolbar_skin.get('buttonskin', '') + button_skin = skin.get(button_skin_name, {}) + font_colors = button_skin.get('fontcolors', {}) + self.text_color = font_colors.get('normal', wx.BLACK) + + def OnPaintMore(self, dc): + # paint the character count + if self.count: + r = self.CountRect(dc) + dc.Font = self.Font + dc.TextForeground = self.text_color + dc.DrawText(self.count, r.x, r.y) + +def toolbaricon(name): + return skin.get('twitter.toolbaricons.' + name) + +def feed_label(feed): + if feed.get('noCount', False): + count = 0 + else: + count = max(0, int(feed.get('count', 0))) + + count_string = (' (%s)' % count) if count else '' + return feed['label'] + count_string + +class TwitterActionsBar(UberBar): + def __init__(self, parent, twitter_panel): + self.twitter_panel = twitter_panel + UberBar.__init__(self, parent, skinkey = skin.get('ActionsBar.ToolBarSkin', None), overflowmode=True) + self.construct() + + def UpdateSkin(self): + self.SetSkinKey(skin.get('ActionsBar.ToolBarSkin', None)) + UberBar.UpdateSkin(self) + + def update_unread_counts(self, counts): + for feed in counts: + label = feed_label(feed) + feed = self.twitter_panel.protocol.feeds_by_name.get(feed['name'], None) + if feed is not None: + id = self.twitter_panel.ids[feed['name']] + self.UpdateItemLabel(id, label) + + self.OnUBSize() + wx.CallAfter(self.Parent.Layout) + + def construct(self): + def menu_item(name): + id, label = dict(favorites = (ID_FAVORITES, _('Favorites')), + history = (ID_HISTORY, _('History')))[name] + return ([toolbaricon(name), label], id) + + overflow_items = [ + menu_item('favorites'), + menu_item('history'), + ('', -1), # separator + ([toolbaricon('group'), _('New Group...')], ID_NEWGROUP), + ([toolbaricon('search'), _('New Search...')], ID_NEWSEARCH), + (_('Edit and Rearrange...'), ID_EDITANDREARRANGE), + ] + + for content, id in overflow_items: + self.AddMenuItem(SimpleMenuItem(content, id=id)) + +class HoverFrame(wx.Frame): + def __init__(self, parent, label): + wx.Frame.__init__(self, parent, style = wx.FRAME_SHAPED | wx.NO_BORDER | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR) + self.label = label + #self.icon = icon + self.border = wx.Point(1, 1) + + parent.Top.Bind(wx.EVT_MOVE, self.OnParentMove) + self.Bind(wx.EVT_SHOW, self.OnShow) + + dc = wx.ClientDC(self) + dc.Font=self.Font + + w, h = dc.GetTextExtent(label) + self.SetSize((w + self.border.x*2, h + self.border.y*2)) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def OnShow(self, e): + e.Skip() + if e.GetShow(): + self.offset = self.Position - self.Parent.ScreenRect.Position + + def OnParentMove(self, e): + e.Skip() + if self.IsShown(): + self.SetPosition(self.Parent.ScreenRect.Position + self.offset) + + def OnPaint(self, e): + r = self.ClientRect + dc = wx.PaintDC(self) + + # draw a border + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.Pen(wx.Color(200, 200, 200)) + dc.DrawRectangleRect(r) + + dc.Font = self.Font + dc.TextForeground = wx.Color(128, 128, 128) + dc.DrawText(self.label, self.border.x, self.border.y) + # draw the icon + #dc.DrawBitmap(self.icon, self.border.x, self.border.y, True) + +class URLShortenerPopup(object): + def __init__(self, textctrl): + self.textctrl = textctrl + + textctrl.Bind(wx.EVT_TEXT, self.__OnText) + textctrl.Bind(wx.EVT_KEY_DOWN, self.__OnKeyDown) + + self.last_value = None + textctrl.Bind(wx.EVT_MOTION, self.__OnMotion) + textctrl.Bind(wx.EVT_LEAVE_WINDOW, self.__OnLeave) + + WM_PASTE = 0x302 + textctrl.BindWin32(WM_PASTE, self.__OnPaste) + + self.hover_icon = None + + def __OnLeave(self, e): + win = wx.FindWindowAtPointer() + if win is None or win.Parent is not self.textctrl: + self.ShowShortenerPopup(False) + + def __OnMotion(self, e): + e.Skip() + txt = self.textctrl + val = txt.Value + if val != self.last_value: + self.last_value = val + + from util.net import LinkAccumulator + self.links = LinkAccumulator(val) + + p = txt.XYToPosition(*txt.HitTest(e.Position)[1:]) + if p != -1: + for n, (i, j) in enumerate(self.links.spans): + if p >= i and p < j: + pos = txt.ClientToScreen(txt.IndexToCoords(i)) + self.ShowShortenerPopup(True, pos) + + def __OnPaste(self, hWnd, msg, wParam, lParam): + txt = clipboard.get_text() + if txt and isurl(txt): + self.delay_hide = True + wx.CallAfter(self.ShowShortenerPopup, True) + wx.CallAfter(lambda: setattr(self, 'delay_hide', False)) + + def ShowShortenerPopup(self, show, pos=None): + if show and self.hover_icon is None: + self.hover_icon = HoverIcon(self.textctrl, skin.get('twitter.toolbaricons.shrink')) + if pos is None: + pos = self.textctrl.ScreenRect.BottomLeft + self.textctrl.IndexToCoords(self.textctrl.InsertionPoint) + self.hover_icon.SetPosition(pos) + fx.fadein(self.hover_icon, speed='fast') + elif not show and self.ShortenerPopupIsShown(): + icon, self.hover_icon = self.hover_icon, None + fx.fadeout(icon, speed='fast') + + def ShortenerPopupIsShown(self): + return self.hover_icon is not None and self.hover_icon.IsShown() + + def __OnText(self, e): + e.Skip() + if self.ShortenerPopupIsShown() and not getattr(self, 'delay_hide', False): + self.ShowShortenerPopup(False) + + def __OnKeyDown(self, e): + e.Skip() + + if self.hover_icon is None or not self.hover_icon.IsShown(): + return + + if e.KeyCode not in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): + return + + e.Skip(False) + + +from peak.util.addons import AddOn + +def twitter_autocomplete(textctrl): + TwitterAutoCompleter(textctrl).bind() + +class TwitterAutoCompleter(AddOn): + def __init__(self, subject): + self.textctrl = subject + super(TwitterAutoCompleter, self).__init__(subject) + + def bind(self): + if getattr(self, 'bound', False): + import warnings + warnings.warn('attempted to re-bind events on %r' % self.textctrl) + return + self.bound = True + self.textctrl.Bind(wx.EVT_TEXT, self.__OnText) + self._autocomplete_timer = wx.PyTimer(self.on_autocomplete_users) + self._autocomplete_enabled = True + self.accts = None + + initial_show_delay_ms = 500 + + def on_autocomplete_users(self): + def callback(users): + users = sorted(users.values(), key=lambda user: user['screen_name'].lower()) + controller = TwitterAutoCompleteController(users) + results = [TwitterSearchResult(u) for u in users] + + from gui.autocomplete import autocomplete + autocomplete(self.textctrl, results, controller) + + import twitter + twitter.get_users(callback, self.accts) + + def delayed_show(self): + self._autocomplete_timer.StartOneShot(self.initial_show_delay_ms) + + def immediate_show(self): + self._autocomplete_timer.Stop() + self.on_autocomplete_users() + + def __OnText(self, e): + e.Skip() + if not self._autocomplete_enabled: + return + + ip = self.textctrl.InsertionPoint + val = self.textctrl.Value + if (ip > 0 and val[ip - 1] == '@' and \ + (ip == 1 or val[ip - 2] in (' ', '.', ','))) \ + or (ip == 2 and val[:2] == 'd '): + self.delayed_show() + elif ip > 1 and val[ip-2] == '@' and at_someone.match(val[ip-2:ip]): + self.immediate_show() + elif ip == 3 and val[:2] == 'd ' and direct.match(val[:ip]): + self.immediate_show() + +class TwitterInputBoxBase(FormattedExpandoTextCtrl, SplitterEventMixin): + def __init__(self, *a, **k): + self.tc = self + FormattedExpandoTextCtrl.__init__(self, *a, **k) + SplitterEventMixin.__init__(self) + +class TwitterInputBox(TwitterInputBoxBase): + def CanPaste(self): return True + + def __init__(self, *a, **k): + super(TwitterInputBox, self).__init__(*a, **k) + + textattr = StyleFromPref('messaging.default_style') + textattr.TextColour = wx.BLACK + textattr.BackgroundColour = wx.WHITE + self.SetFormat_Single(textattr) + + from gui.toolbox import add_shortened_url_tooltips + add_shortened_url_tooltips(self) + + for regex in twitter_util.spellcheck_regex_ignores: + self.AddRegexIgnore(regex) + self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) + + @property + def BestSizeControl(self): + return self.Parent + + def OnContextMenu(self, e): + with UMenu.Reuse(self, event=e) as menu: + self.AddSuggestionsToMenu(menu) + + from gui.toolbox import add_rtl_checkbox, maybe_add_shorten_link + maybe_add_shorten_link(self, menu) + + std_textctrl_menu(self, menu) + add_rtl_checkbox(self, menu) + + def OnExpandEvent(self, event): + event.Skip() + height = self.MinSize.height + min_size = wx.Size(-1, height) + self.MinSize = min_size + +class TwitterPanel(object): + def __init__(self, parent, protocol): + self.ids = {'favorites': ID_FAVORITES, 'history': ID_HISTORY} + self.protocol = protocol + + self.connect_events() + + self.custom_feed_buttons = odict() + self.construct(parent, protocol) + self.bind_events() + + self.Control.Top.OnTop = self.AlwaysOnTop + + def _events(self): + return [('on_unread_counts', self.on_unread_counts), + ('on_feeds', self.on_feeds), + ('on_view', self.on_view), + ('on_edit_feed', self.OnEditFeed)] + + def connect_events(self): + for event_name, method in self._events(): + event_delegate = getattr(self.protocol.events, event_name) + event_delegate += method + + def disconnect_events(self): + for event_name, method in self._events(): + event_delegate = getattr(self.protocol.events, event_name) + event_delegate -= method + + @property + def Control(self): return self.splitter + + @property + def WebView(self): return self.webview + + def bind_events(self): + bind = self.actions_bar.Bind + bind(wx.EVT_MENU, self.on_new_group, id=ID_NEWGROUP) + bind(wx.EVT_MENU, self.on_new_search, id=ID_NEWSEARCH) + bind(wx.EVT_TOGGLEBUTTON, self.on_button) # for view buttons + bind(wx.EVT_BUTTON, self.on_button) # for overflow menu items + bind(wx.EVT_MENU, self.on_button) # for overflow menu items + bind(wx.EVT_MENU, self.on_rearrange_feeds, id=ID_EDITANDREARRANGE) + + # TODO: umenu events go to the top level frame :( + bind = self.Control.Top.Bind + bind(wx.EVT_MENU, self.on_delete_feed, id=ID_DELETE_FEED) + bind(wx.EVT_MENU, self.on_edit_feed, id=ID_EDIT_FEED) + bind(wx.EVT_MENU, self.on_rearrange_feeds, id=ID_EDITANDREARRANGE) + bind(wx.EVT_MENU, self.on_mark_feed_as_read, id=ID_MARKFEEDASREAD) + bind(wx.EVT_MENU, self.on_toggle_addstocount, id=ID_TOGGLE_ADDSTOCOUNT) + bind(wx.EVT_MENU, self.toggle_always_on_top, id=ID_KEEPONTOP) + bind(wx.EVT_MENU, lambda e: self.webview.Copy(), id=ID_WEBVIEW_COPY) + bind(wx.EVT_MENU, lambda e: self.webview.IncreaseTextSize(), id=ID_INCREASE_TEXT_SIZE) + bind(wx.EVT_MENU, lambda e: self.webview.DecreaseTextSize(), id=ID_DECREASE_TEXT_SIZE) + bind(wx.EVT_MENU, lambda e: self.webview.ResetTextSize(), id=ID_RESET_TEXT_SIZE) + bind(wx.EVT_MENU, lambda e: self.protocol.update(), id=ID_UPDATE) + + bind = self.Control.Top.Bind + bind(wx.EVT_MENU, self.on_shorten_links, id=ID_SHORTEN_LINKS) + bind(wx.EVT_MENU, self.on_shrink, id=ID_SHRINK) + bind(wx.EVT_MENU, self.on_global_status, id=ID_GLOBAL_STATUS) + bind(wx.EVT_MENU, self.on_image, id=ID_IMAGE) + + from gui.toolbox import bind_special_paste + + hover = HoverFrame(self.input_area, _('Shortening...')) + + def onshorten(cancel): + hover.SetPosition(self.CursorScreenPosition) + hover.ShowNoActivate(True) + + def _on_cancel(): + hover.Hide() + cancel() + self.cancel_mode(False) + + return self.cancel_mode(_on_cancel) + + def onshorten_done(url): + hover.Hide() + self.cancel_mode(False) + + bind_special_paste(self.input_area, + shorten_urls=lambda: pref('twitter.paste.shorten_urls', default=True), + onbitmap = self.upload_image, + onfilename = self.upload_image, + onshorten = onshorten, + onshorten_done = onshorten_done) + + ## this function doesn't get called. wtf? i dont understand dnd +# def emit_paste_event(_): +# log.info("dropped some junk: %r", _) +# +# self.input_area.SetDropTarget(gui.toolbox.dnd.SimpleDropTarget +# (files = emit_paste_event, +# bitmap = emit_paste_event, +# text = emit_paste_event, +# )) + + def activate_feed_button(self, feed_id): + button = self.actions_bar.FindWindowById(feed_id) + if hasattr(button, 'Active'): + button.Active(True) + + # untoggle other feed buttons + for feed_name, id in self.ids.iteritems(): + if id != feed_id: + button = self.actions_bar.FindWindowById(id) + if hasattr(button, 'Active'): + button.Active(False) + + def on_button(self, e): + feed = self.feed_for_id(e.Id) + if feed is not None: + self.switch_to_feed(feed) + else: + e.Skip() + + def switch_to_feed(self, feed): + self.activate_feed_button(self.ids[feed['name']]) + + # CallAfter(1) so that the button redraws before the webview switch + wx.CallLater(1, lambda: self.webview.switch_to_view(feed['name'])) + + def on_image(self, e=None): + '''uploads an image to pic.im, inserting the shortened url into the input + area when finished''' + + # select a file + filename = pick_image_file(self.Control.Top) + if filename is None: return + + self.upload_image(filename, auto = False) + + def cancel_mode(self, callback): + if not callback: + self.cancel_button.Hide() + changed = self.input_button.Show() + self.cancel_callback = None + else: + changed = self.cancel_button.Show() + self.input_button.Hide() + self.cancel_callback = callback + + if changed: + self.bottom_pane.Sizer.Layout() + + def on_cancel(self, *a, **k): + if getattr(self, 'cancel_callback', None) is not None: + self.cancel_callback() + + @property + def CursorScreenPosition(self): + i = self.input_area + return i.ClientToScreen(i.IndexToCoords(i.InsertionPoint)) + + def upload_image(self, filename, bitmap=None, auto = True): + if auto and not pref('twitter.paste.upload_images', True): + return + + if auto: + message = _(u"Are you sure you want to upload the image in your clipboard?") + title = _(u"Image Upload") + if bitmap is None: + bitmap = wx.Bitmap(filename) + from gui.imagedialog import show_image_dialog + if not show_image_dialog(self.Control, message, bitmap, title=title): + return + + mtype, _encoding = mimetypes.guess_type(filename) + if mtype is None or not mtype.startswith('image'): + # Not an image file + return + + hover = HoverFrame(self.input_area, 'Uploading...') + + def disable(): + self.input_area.Disable() + hover.SetPosition(self.CursorScreenPosition) + hover.ShowNoActivate(True) + + def enable(): + self.input_area.Enable() + if not wx.IsDestroyed(hover): + hover.Destroy() + + # TODO: timeout needed here. + # setup callbacks + def progress(value): + pass + #wx.CallAfter(bar.SetValue, value) + + def cancel(): + cancel._called = True + self.cancel_mode(False) + enable() + + def done(): + if not getattr(done, '_called', False): + #self.input_toolbar.destroy_progress_bar() + done._called = True + self.cancel_mode(False) + enable() + + def success(resp): + @wx.CallAfter + def after(): + done() + if not getattr(cancel, '_called', False): + insert_text(self.input_area, resp['url']) + self.input_area.SetFocus() + + def error(e): + wx.CallAfter(done) + + disable() + self.cancel_mode(cancel) + + # start transfer + try: + from imagehost.imgur import ImgurApi + #bar.SetRange(os.path.getsize(filename)) + stream = CallbackStream(open(filename, 'rb'), progress, lambda: None) + ImgurApi().upload(stream, success = success, error = error) + except Exception as e: + print_exc() + error(e) + + def on_shrink(self, e=None): + inp = self.input_area + val = inp.Value + if not val: return + + def success(shrunk_text): + @wx.CallAfter + def after(): + # don't change the value if the user has changed the text field + # in the meantime + if not wx.IsDestroyed(inp) and val == inp.Value: + inp.Value = shrunk_text + inp.SetInsertionPointEnd() + inp.SetFocus() + + def error(e): + log.error('error shrinking tweet: %r', e) + + # todo: timeout + threaded(twitter_util.shrink_tweet)(val, success=success, error=error) + + def on_global_status(self, e=None): + wx.GetApp().SetStatusPrompt(initial_text=self.input_area.Value) + self.input_area.Clear() + + def on_shorten_links(self, e=None): + '''shortens all links in the input area''' + + from gui.textutil import shorten_all_links + shorten_all_links(self.input_area, ondone=self.input_area.SetFocus) + + def on_new_group(self, e): + self.new_or_edit_group() + + def new_or_edit_group(self, feed=None): + if feed is None: + new = True + feed = dict(ids=[], filter=True, popups=False) + else: + new = False + + ids = set(str(i) for i in feed.get('ids')) + + @self.get_users + def success(users): + users = sorted((TwitterUser(id=u['id'], + screen_name=u['screen_name'], + profile_image_url=u['profile_image_url'], + selected=str(u['id']) in ids) + for u in users.itervalues()), + key=lambda u: u.screen_name.lower()) + + opts = feed.copy() + opts['users'] = users + + cb = getattr(self.protocol, 'add_feed' if new else 'edit_feed') + self._show_dialog_with_opts(TwitterGroupDialog, opts, cb) + + def on_new_search(self, e): + self.new_or_edit_search() + + def new_or_edit_search(self, feed=None): + if feed is not None: + new = False + from pprint import pprint; pprint(feed) + else: + new = True + feed = dict(merge=False, popups=True) + + opts = dict(query = feed.get('query', '')) + + if new: + if self.protocol.trends: + opts.update(trends=self.protocol.trends) + else: + from .twitter import title_from_query + title = feed.get('title', None) + if title is None: + title = title_from_query(feed.get('query', '')) + search_opts = dict(merge=feed.get('merge', False), popups=feed.get('popups')) + opts.update(name = feed.get('name', ''), + search_opts = search_opts, + title = title) + + cb = getattr(self.protocol, 'add_feed' if new else 'edit_feed') + self._show_dialog_with_opts(TwitterSearchDialog, opts, cb) + + def OnEditFeed(self, feed): + print 'OnEditFeed' + from pprint import pprint; pprint(feed) + + dict(group=self.new_or_edit_group, + search=self.new_or_edit_search)[feed['type']](feed) + + def OnRemoveFeed(self, feed): + self.protocol.delete_feed(feed['name']) + + def get_users(self, callback): + if hasattr(self, '_cached_users'): + callback(self._cached_users) + return + + def cb(users): + self._cached_users = users + callback(users) + + self.protocol.get_users(cb) + + def on_delete_feed(self, e): + feed = self.feed_for_id(self._feed_menu_target.Id) + if feed is not None: + self.protocol.delete_feed(feed['name']) + + def on_edit_feed(self, e): + feed = self.feed_for_id(self._feed_menu_target.Id) + if feed is not None: + self.OnEditFeed(feed) + + @property + def ExistingFeedEditor(self): + return find_tlw(TwitterFeedsEditDialog, lambda w: w.protocol is self.protocol) + + def on_rearrange_feeds(self, e): + dialog = self.ExistingFeedEditor + if dialog is not None: + return dialog.Raise() + + protocol = self.protocol + dialog = TwitterFeedsEditDialog(self.Control, + protocol, + protocol.feeds, + protocol.set_feeds) + + dialog.on_edit_feed += self.OnEditFeed + dialog.on_remove_feed += self.OnRemoveFeed + + dialog.CenterOnParent() + dialog.Show() + + def on_mark_feed_as_read(self, e): + feed = self.feed_for_id(self._feed_menu_target.Id) + if feed is not None: + self.protocol.mark_feed_as_read(feed['name']) + + def on_toggle_addstocount(self, e): + feed = self.feed_for_id(self._feed_menu_target.Id) + if feed is not None: + self.protocol.toggle_addstocount(feed['name']) + + def _show_dialog_with_opts(self, dialog_cls, opts, info_cb=None): + if not wx.IsMainThread(): + return wx.CallAfter(self._show_dialog_with_opts, dialog_cls, opts) + + if not dialog_cls.RaiseExisting(): + diag = dialog_cls(self.Control.Top, opts) + if info_cb is not None: + diag.on_info = info_cb + diag.CenterOnParent() + diag.Show() + + def on_unread_counts(self, opts): + counts = opts.get('feeds') + self.counts = counts + + count_timer = getattr(self, '_count_timer', None) + if count_timer is None: + def update(): + self.actions_bar.update_unread_counts(self.counts) + self._count_timer = count_timer = wx.PyTimer(update) + if not count_timer.IsRunning(): + count_timer.StartOneShot(10) + + def feed_for_id(self, id): + for feed_name, feed_id in self.ids.iteritems(): + if feed_id == id: + try: + return self.protocol.feeds_by_name[feed_name] + except KeyError: + from pprint import pprint; pprint(self.protocol.feeds_by_name) + raise + + def id_for_feed(self, feed): + if not isinstance(feed, basestring): + feed = feed['name'] + + try: + id = self.ids[feed] + except KeyError: + id = self.ids[feed] = wx.NewId() + + return id + + def construct_feed_button(self, feed): + id = self.id_for_feed(feed) + + icon = skin.get('twitter.toolbaricons.' + feed['type']) + button = UberButton(self.actions_bar, id, feed_label(feed), + icon=icon, type='toggle') + button.Bind(wx.EVT_CONTEXT_MENU, self.on_feed_button_menu) + return button + + def on_feed_button_menu(self, e): + self._feed_menu_target = e.EventObject + + try: + menu = self._feed_button_menu + except AttributeError, e: + menu = UMenu(self.Control) + + menu.RemoveAllItems() + + feed = self.feed_for_id(self._feed_menu_target.Id) + if feed['name'] not in permanent_feed_types: + menu.AddItem(_('&Edit'), id=ID_EDIT_FEED) + menu.AddItem(_('&Delete'), id=ID_DELETE_FEED) + menu.AddSep() + menu.AddItem(_('&Rearrange'), id=ID_EDITANDREARRANGE) + else: + menu.AddItem(_('Edit and &Rearrange'), id=ID_EDITANDREARRANGE) + + menu.AddSep() + menu.AddItem(_('&Mark As Read'), id=ID_MARKFEEDASREAD) + + if feed.get('type', None) in ('group', 'search'): + item = menu.AddCheckItem(_('&Adds to Unread Count'), id=ID_TOGGLE_ADDSTOCOUNT) + item.Check(not feed.get('noCount', False)) + + menu.PopupMenu() + + def select_view(self, n): + '''select the nth view''' + + try: + button = self.custom_feed_buttons.values()[n] + except IndexError: + pass + else: + self.switch_to_feed(self.feed_for_id(button.Id)) + + def page_view(self, delta): + ''' + switches feeds forward or backward based on the visual order of buttons + ''' + keys = self.custom_feed_buttons.keys() + if not keys: return + + # find index of active feed button + index = -1 + for i, k in enumerate(keys): + if self.custom_feed_buttons[k].IsActive(): + index = i + break + + # go to next or previous button + if delta < 0 and index == -1: + newkey = keys[len(keys)-1] + else: + index += delta + newkey = keys[index % len(keys)] + + self.switch_to_feed(self.feed_for_id(self.custom_feed_buttons[newkey].Id)) + + def on_view(self, view=None): + if view is None: + view = getattr(self, '_view', None) + if view is None: + return + + self._view = view + + log.warning('on_view %r', view) + feed = self.protocol.feeds_by_name.get(view, None) + if feed is not None: + self.active_view = view + self.activate_feed_button(self.id_for_feed(feed)) + title = u'%s - %s' % (self.protocol.username, feed['label']) + self.Control.Top.Title = title + else: + self.activate_feed_button(0) + + self.actions_bar.OnUBSize() + + def on_feeds(self, feeds): + log.info('on_feeds: %r', feeds) + self.counts = feeds + # TODO: remove old ids + + # Send new feeds to the edit/rearrange dialog, if it exists. + editor = self.ExistingFeedEditor + if editor is not None: + editor.SetList(feeds) + editor.Fit() + + bar = self.actions_bar + with self.Control.Frozen(): + active_name = None + for name, button in self.custom_feed_buttons.items(): + if button.IsActive(): + active_name = name + bar.Remove(button, calcSize=False) + button.Destroy() + + self.custom_feed_buttons.clear() + + for feed in feeds: + name = feed['name'] + if feed['type'] in ('search', 'user') and not feed.get('save', False): + continue # don't show buttons for unsaved search feeds + button = self.construct_feed_button(feed) + self.custom_feed_buttons[name] = button + if active_name is not None and name == active_name: + button.Active(True) + bar.Add(button, calcSize=False) + + bar.OnUBSize() + wx.CallAfter(bar.Parent.Layout) + self.on_view() + + def on_webview_menu(self, e): + w = self.webview + try: + menu = self._menu + except AttributeError: + menu = self._menu = UMenu(self.webview) + + menu.RemoveAllItems() + if self.webview.CanCopy(): + item = menu.AddItem(_('Copy'), id=ID_WEBVIEW_COPY) + menu.AddSep() + + menu.AddItem(_('&Update Now'), id=ID_UPDATE) + + menu.AddSep() + item = menu.AddCheckItem(KEEP_ON_TOP_LABEL, id=ID_KEEPONTOP) + item.Check(self.AlwaysOnTop) + + # text size submenu + textsizemenu = UMenu(self.webview) + textsizemenu.AddItem(_('&Increase Text Size\tCtrl+='), id=ID_INCREASE_TEXT_SIZE) + textsizemenu.AddItem(_('&Decrease Text Size\tCtrl+-'), id=ID_DECREASE_TEXT_SIZE) + textsizemenu.AddSep() + textsizemenu.AddItem(_('&Reset Text Size\tCtrl+0'), id=ID_RESET_TEXT_SIZE) + menu.AddSubMenu(textsizemenu, _('Text Size')) + + menu.PopupMenu() + + @property + def AlwaysOnTopKey(self): + return self.protocol.account_pref_key('always_on_top') + + @property + def AlwaysOnTop(self): + return pref(self.AlwaysOnTopKey, default=False) + + def toggle_always_on_top(self, e=None): + newval = not self.AlwaysOnTop + setpref(self.AlwaysOnTopKey, newval) + self.Control.Top.OnTop = newval + + def construct(self, parent, protocol): + from gui.uberwidgets.skinsplitter import SkinSplitter + + self.splitter = spl = SkinSplitter(parent, wx.SP_NOBORDER | wx.SP_LIVE_UPDATE) + spl.SetMinimumPaneSize(10) + spl.SetSashGravity(1) + + self.top_pane = wx.Panel(spl) + self.top_pane.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.bottom_pane = wx.Panel(spl) + self.bottom_pane.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.actions_bar = TwitterActionsBar(self.top_pane, self) + + self.webview = TwitterWebView(self.top_pane, protocol) + self.webview.Bind(wx.EVT_CONTEXT_MENU, self.on_webview_menu) + #self.input_toolbar = TwitterInputToolBar(self.bottom_pane, indirect_skinkey = 'FormattingBar') + + self.input_area = TwitterInputBox(self.bottom_pane, + multiFormat = False) + #fbar = self.input_toolbar) + + import hooks + hooks.notify('digsby.status_textctrl.created', self.input_area) + + from gui.textutil import VisualCharacterLimit + VisualCharacterLimit(self.input_area, textctrl_limit_func, count_twitter_characters) + + self.setup_focus() + + self.input_area.Bind(wx.EVT_KEY_DOWN, self.__onkey) + self.webview.BindWheel(self.input_area) + self.webview.BindWheel(self.webview) + self.webview.BindScrollWin(self.input_area) + self.webview.BindScrollWin(self.webview) + self.input_area.BindSplitter(spl, protocol.account_pref_key('input_ctrl_height')) + self.input_area.Bind(wx.EVT_TEXT, self._on_input_text) + + #self.input_toolbar.LinkTextControl(self.input_area) + + self.top_pane.Sizer = wx.BoxSizer(wx.VERTICAL) + self.top_pane.Sizer.AddMany([ + (self.actions_bar, 0, wx.EXPAND), + (self.webview, 1, wx.EXPAND), + ]) + + self.construct_input_button() + + self.bottom_pane.Sizer = wx.BoxSizer(wx.HORIZONTAL) + + self.spacer_panel = SpacerPanel(self.bottom_pane, skinkey = 'inputspacer') + + self.bottom_pane.Sizer.AddMany([ + #(self.input_toolbar, 0, wx.EXPAND), + (self.input_area, 1, wx.EXPAND), + (self.spacer_panel, 0, wx.EXPAND), + (self.input_button, 0, wx.EXPAND), + (self.cancel_button, 0, wx.EXPAND), + ]) + + spl.SplitHorizontally(self.top_pane, self.bottom_pane) + spl.SetSashPosition(2400) + + def construct_input_button(self): + def onshow(menu): + ontop_item.Check(self.AlwaysOnTop) + + m = UMenu(self.bottom_pane, onshow = onshow) + + def g(skinkey): + return skin.get('twitter.toolbaricons.' + skinkey) + + m.AddItem(_('Shorten URLs\tCtrl+L'), id=ID_SHORTEN_LINKS, bitmap=g('link')) + m.AddItem(_('Share Picture\tCtrl+P'), id=ID_IMAGE, bitmap=g('image')) + m.AddItem(_('TweetShrink\tCtrl+S'), id=ID_SHRINK, bitmap=g('shrink')) + m.AddSep() + m.AddItem(_('Set Global Status\tCtrl+G'), id=ID_GLOBAL_STATUS, bitmap=g('global')) + m.AddSep() + m.AddPrefCheck('twitter.paste.shorten_urls', _('Auto Shorten Pasted URLs')) + m.AddPrefCheck('twitter.paste.upload_images', _('Auto Upload Pasted Images')) + m.AddPrefCheck('twitter.autoscroll.when_at_bottom', _('Auto Scroll When At Bottom')) + m.AddSep() + ontop_item = m.AddCheckItem(KEEP_ON_TOP_LABEL, id=ID_KEEPONTOP) + + self.input_button = UberButton(self.bottom_pane, + skin='InputButton', + label=str(TWITTER_MAX_CHARCOUNT), + menu=m, + type='menu') + + self.cancel_button = UberButton(self.bottom_pane, + skin='InputButton', + label=_('Cancel'), + onclick = self.on_cancel) + self.cancel_button.Hide() + + def setup_focus(self): + # HACK: uberbuttons steal focus from us, get it back every 500 ms + def on_focus_timer(): + if wx.IsDestroyed(self.Control): + self.focus_timer.Stop() + return + + w = wx.Window.FindFocus() + if w is not None and w.Top is self.Control.Top and w is not self.input_area: + s = wx.GetMouseState() + if not (s.LeftDown() or s.MiddleDown() or s.RightDown()): + self.input_area.SetFocus() + + self.focus_timer = wx.PyTimer(on_focus_timer) + self.focus_timer.StartRepeating(500) + + # clicking the webview focuses the input box + self.webview.Bind(wx.EVT_SET_FOCUS, lambda e: (e.Skip(), self.input_area.SetFocus())) + + def __onkey(self, e): + e.Skip(False) + + key, mod = e.KeyCode, e.Modifiers + webview = self.webview + + # catch ctrl+tab and shift+ctrl+tab + if key == wx.WXK_TAB and mod == wx.MOD_CMD: + self.page_view(1) + elif key == wx.WXK_TAB and mod == wx.MOD_CMD | wx.MOD_SHIFT: + self.page_view(-1) + + # ctrl+1-9 select buttons + elif mod == wx.MOD_CMD and key >= ord('1') and e.KeyCode <= ord('9'): + self.select_view(key - ord('1')) + + # up and down scroll the webview when there's only one line + elif key in (wx.WXK_UP, wx.WXK_DOWN) and self.input_area.GetNumberOfLines() == 1: + webview.ScrollLines(-1 if wx.WXK_UP == key else 1) + + # page up/down always go to the webview + elif key == wx.WXK_PAGEUP: + webview.ScrollLines(-3) + elif key == wx.WXK_PAGEDOWN: + webview.ScrollLines(3) + + # Catch enter on the input box and send a tweet + elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and not mod & wx.MOD_SHIFT: + self.send_tweet() + + # escape and ctrl+w closes window + elif (key == wx.WXK_ESCAPE and mod == 0) or \ + (key == ord('W') and mod == wx.MOD_CMD): + self.Control.Top.Close() + + elif mod == wx.MOD_CMD and key == ord('L'): + self.on_shorten_links() + + elif mod == wx.MOD_CMD and key == ord('P'): + self.on_image() + + elif mod == wx.MOD_CMD and key == ord('S'): + self.on_shrink() + + elif mod == wx.MOD_CMD and key == ord('G'): + self.on_global_status() + + elif mod == wx.MOD_CMD and key == ord('C'): + if webview.CanCopy(): + webview.Copy() + elif self.input_area.CanCopy(): + self.input_area.Copy() + else: + e.Skip(True) + + # text size + elif mod == wx.MOD_CMD and key == ord('0'): + webview.ResetTextSize() + elif mod == wx.MOD_CMD and key == ord('+'): + webview.IncreaseTextSize() + elif mod == wx.MOD_CMD and key == ord('-'): + webview.DecreaseTextSize() + + else: + e.Skip(True) + + def send_tweet(self): + if not self._should_send_tweet(): + return + + inp = self.input_area + text, reply_id = inp.Value, getattr(inp, 'reply_id', None) + self.protocol.on_status_with_error_popup(text, reply_id) + inp.reply_id = None + inp.Clear() + + def _should_send_tweet(self): + if not self.input_area.Value.strip(): + return False + + if (pref('twitter.spell_guard', False) and + pref('messaging.spellcheck.enabled', True) and + self.input_area.HasSpellingErrors()): + + from gui.toolbox import SimpleMessageDialog + + msg1 = _('Your tweet has spelling errors.') + msg2 = _("Are you sure you'd like to send it?") + msg = u'%s\n\n%s' % (msg1, msg2) + + dialog = SimpleMessageDialog(self.top_pane.Top, + title = _("Tweet spelling errors"), + message = msg, + icon = wx.ArtProvider.GetBitmap(wx.ART_QUESTION), + ok_caption = _('Send Anyways')) + + dialog.CenterOnParent() + res = dialog.ShowModal() + + return res == wx.ID_OK + + return True + + def _on_input_text(self, e): + e.Skip() + val = self.input_area.Value + + # try to subtract a "d screen_name" from the count + count = len(val) + try: + match = twitter_util.direct_msg.match(val) + if match: + count = len(match.groups()[1]) + except Exception: + import traceback + traceback.print_exc_once() + + self.set_charcount(TWITTER_MAX_CHARCOUNT - count) + + def set_charcount(self, count): + self.count = str(count) + self.input_button.SetLabel(str(count)) + + +def Active(func): + @wraps(func) + def wrapper(*a, **k): + active = wx.GetActiveWindow() + if isinstance(active, TwitterFrame): + return func(active, *a, **k) + + return staticmethod(wrapper) + +def _saveinput(): + return pref('twitter.save_input', default=False) + +class TwitterFrame(wx.Frame): + default_size = (400, 700) + + @staticmethod + def ForProtocol(protocol): + return find_tlw(TwitterFrame, lambda w: w.panel.protocol is protocol) + + def __init__(self, parent, protocol): + wx.Frame.__init__(self, parent, wx.ID_ANY, protocol.username, name='Twitter ' + protocol.username) + self.SetFrameIcon(skin.get('serviceicons.twitter')) + self.SetMinSize((260, 250)) + self.panel = TwitterPanel(self, protocol) + self.Sizer = wx.BoxSizer(wx.VERTICAL) + self.Sizer.Add(self.panel.Control, 1, wx.EXPAND) + self.Layout() + + self.Bind(wx.EVT_ACTIVATE, self.OnActivate) + + with traceguard: + from gui.toolbox import persist_window_pos, snap_pref + persist_window_pos(self, defaultPos = wx.Point(50, 50), defaultSize = self.default_size) + snap_pref(self) + + hooks.notify('digsby.statistics.twitter.feed_window.shown') + self.load_input() + self.Show() + + # CallAfter here so that the splitter gets the right minsize for the text control + wx.CallAfter(self.panel.input_area.RequestResize) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnClose(self, e): + e.Skip() + self.Hide() + self._closing = True + self.save_input() + self.panel.disconnect_events() + + def save_input(self): + if not _saveinput(): return + self.unfinished_tweet.save(dict(text=self.InputBox.Value, + cursorpos=self.InputBox.InsertionPoint)) + + def load_input(self): + if not _saveinput(): return + with traceguard: + info = self.unfinished_tweet.safe_load() + if info is None: return + + self.InputBox.Value = info['text'] + self.InputBox.InsertionPoint = info['cursorpos'] + + @property + def unfinished_tweet(self): + try: + return self._unfinished_tweet + except AttributeError: + from util.cacheable import DiskCache + self._unfinished_tweet = DiskCache('Unfinished Tweet') + return self._unfinished_tweet + + def OnActivate(self, e): + e.Skip() + if getattr(self, '_closing', False) or wx.IsDestroyed(self): + return + + # wxWebKit's document.onblur/onfocus is broken, so simulate it here + script = "onFrameActivate(%s);" % ('true' if e.GetActive() else 'false') + self.RunScript(script); + + if e.GetActive(): + hooks.notify('digsby.statistics.twitter.feed_window.activated') + + def RunScript(self, script): + return self.panel.webview.RunScript(script) + + @property + def WebView(self): return self.panel.WebView + + @property + def InputBox(self): + return self.panel.input_area + + def SetValueAndReplyId(self, value, reply_id=None, cursor_pos=None): + inp = self.InputBox + oldval = inp.Value + + with inp.Frozen(): + inp.Replace(0, inp.LastPosition, value) # preserves undo, unlike SetValue + inp.reply_id = reply_id + + if cursor_pos: + inp.SetInsertionPoint(cursor_pos) + else: + inp.SetInsertionPointEnd() + + inp.SetFocus() + + @Active + def Reply(self, id, screen_name, text): + from .twitter import new_reply_text + val, cursor = new_reply_text(screen_name, text) + self.SetValueAndReplyId(val, id, cursor) + + @Active + def Retweet(self, id, screen_name, text): + self.SetValueAndReplyId('RT @' + screen_name + ': ' + text.decode('xml'), None) + + @Active + def Direct(self, screen_name): + self.SetValueAndReplyId('d ' + screen_name + ' ') + +class TwitterWebView(FrozenLoopScrollMixin, ScrollWinMixin, WheelShiftScrollFastMixin, WheelScrollCtrlZoomMixin, wx.webview.WebView): + '''WebView subclass implementing a CreateWindow function that creates a + TwitterFrame.''' + + def __init__(self, parent, protocol): + style = wx.WANTS_CHARS # send arrow keys, enter, etc to the webview + super(TwitterWebView, self).__init__(parent, wx.ID_ANY, wx.DefaultPosition, + wx.DefaultSize, style) + + self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.on_before_load) + self.Bind(wx.webview.EVT_WEBVIEW_NEW_WINDOW, self._on_new_window) + self.protocol = protocol + self.SetMouseWheelZooms(True) + + def switch_to_view(self, view): + log.info('switch_to_view %r on %r', view, self) + from simplejson import dumps as jsenc + self.RunScript('''guard(function() { + window.opener.account.changeView(%s); + });''' % jsenc(view)) + + def on_before_load(self, e): + url = e.URL + for protocol in ('mailto:', 'http://', 'https://', 'ftp://'): + if url.lower().startswith(protocol): + wx.LaunchDefaultBrowser(url) + e.Cancel() + return + + e.Skip() + + def _on_new_window(self, e): + frame = TwitterFrame(self.Parent, self.protocol) + e.SetWebView(frame.WebView) + + def CreateWindow(self, *a): + frame = TwitterFrame(self.Parent, self.protocol) + return frame.WebView + + def _setup_logging(self, log): + # shorten filenames in twitter webkit log messages by making + # them relative to this file's directory. + from path import path + twitter_root_dir = path(__file__).parent + + from gui.browser.webkit import setup_webview_logging + setup_webview_logging(self, log, logbasedir=twitter_root_dir) + +from gui.visuallisteditor import VisualListEditor, VisualListEditorListWithLinks + +class TwitterFeedsEditList(VisualListEditorListWithLinks): + def __init__(self, *a, **k): + self.on_edit_feed = Delegate() + self.on_remove_feed = Delegate() + self.links = [(_('Edit'), self.on_edit_feed), + (_('Remove'), self.on_remove_feed)] + + VisualListEditorListWithLinks.__init__(self, *a, **k) + + def LinksForRow(self, n): + feed = self.thelist[n] + return self.links if not is_perm_feed(feed) else [] + + def ItemText(self, item): + return item['label'] + + def OnDrawItem(self, dc, rect, n): + dc.Font = self.Font + feed = self.thelist[n] + icon = skin.get('twitter.toolbaricons.' + feed['type'], None) + + x, y = rect.TopLeft + (3, 3) + if icon: dc.DrawBitmap(icon, x, y, True) + + textrect = wx.Rect(x + 16 + 3, rect.y, rect.Width - x - 38, rect.Height) + dc.TextForeground = wx.BLACK + dc.DrawLabel(feed['label'], textrect, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + + if self.Hovered == n: + self.PaintLinks(dc, rect, n) + + def CalcMinWidth(self): + dc = wx.ClientDC(self) + dc.Font = self.Font + txtwidth = lambda s: dc.GetTextExtent(s)[0] + + padding_x = 3 + min_width = 0 + for n, item in enumerate(self.thelist): + w = 30 + padding_x + txtwidth(self.ItemText(item)) + for link_label, _linkfunc in self.LinksForRow(n): + w += padding_x + txtwidth(link_label) + + min_width = max(w, min_width) + + return max(220, min_width) + +class TwitterFeedsEditDialog(VisualListEditor): + def __init__(self, parent, protocol, feeds, set_feeds): + self.protocol = protocol + VisualListEditor.__init__(self, parent, feeds, + listcallback = set_feeds, + listclass = TwitterFeedsEditList, + title = _('Twitter Groups and Searches')) + self.on_edit_feed = self.vle.on_edit_feed + self.on_remove_feed = self.vle.on_remove_feed + +def find_tlw(cls, func): + for tlw in wx.GetTopLevelWindows(): + if isinstance(tlw, cls) and func(tlw): + return tlw + +def focus_on_show(ctrl): + '''Calls SetFocus on ctrl when its top level parent receives EVT_SHOW.''' + + def onshow(e): + e.Skip() + ctrl.SetFocus() + + ctrl.Top.Bind(wx.EVT_SHOW, onshow) + +from gui.buddylist.accounttray import SocialAccountTrayIcon + +USE_WEBPAGE_LINKS = True + +def menu_actions(acct, m): + + m.AddItem(_('Refresh Now'), callback = acct.update_now) + m.AddItem(_('Mark All As Read'), callback = acct.mark_all_as_read) + m.AddSep() + + if USE_WEBPAGE_LINKS: + for label, url in acct.header_funcs: + m.AddItem(label, callback=lambda url=url: wx.LaunchDefaultBrowser(url)) + else: + # disabled until counts are correct. + proto = acct.twitter_protocol + + if proto: + for feed in proto.feeds: + label = feed['label'] + try: + if feed['count']: + label += ' (%d)' % max(0, feed['count']) + except Exception: + pass + def cb(name=feed['name']): + proto.on_change_view(name) + m.AddItem(label, callback=cb) + + m.AddSep() + m.AddItem(_('&Rename'), callback = acct.rename_gui) + m.AddSep() + m.AddItem(_('Set Status'), callback = acct.update_status_window_needed) + +class TwitterTrayIcon(SocialAccountTrayIcon): + def __init__(self, *a, **k): + SocialAccountTrayIcon.__init__(self, *a, **k) + + # refresh every five minutes so that the icon doesn't become inactive + if config.platform == 'win': + self.tray_refresh_timer = wx.PyTimer(self.Refresh) + self.tray_refresh_timer.StartRepeating(60 * 1000 * 5) + + def update_menu(self, event=None): + self._menu.RemoveAllItems() + menu_actions(self.acct, self._menu) + + @property + def Tooltip(self): + # twitter doesn't have a count + return _('Twitter (%s)') % self.acct.name + + def should_show_count(self): + return pref('trayicons.email.show_count', True) and self.acct.should_show_unread_counts() + +def sorted_screen_names(users): + return sorted((unicode(user['screen_name']) for user in users.itervalues()), key=unicode.lower) + + +import re +at_someone = re.compile(r'(?%s' + +def _at_repl(match): + name = match.group(0)[1:] # strip the @ + return '@' + (href % ('http://twitter.com/' + name, name)) + +def at_linkify(s): + return at_someone.sub(_at_repl, s) + +# Regexes that are given to the spellchecker so that it knows to ignore certain +# words like @atsomeone and #hashtags. +spellcheck_regex_ignores = [at_someone, hashtag] + +def add_regex_ignores_to_ctrl(ctrl): + for r in spellcheck_regex_ignores: + ctrl.AddRegexIgnore(r) + +def old_twitter_linkify(s): + pieces = filter(None, at_someone.split(preserve_newlines(s))) + s = ''.join(namelink(linkify(piece)) for piece in pieces) + return hashtag_linkify(s) + +def search_link(term): + return ''.join(['#', term, '']) + +def _hashtag_repl(match): + 're.sub function for hashtag_linkify' + + tag = match.group(1) + if all_numbers.match(tag) and len(tag) == 2: + return match.group(0) + return search_link(tag[1:]) + +def hashtag_linkify(text): + 'turn #hashtags into links to search.twitter.com' + + return hashtag.sub(_hashtag_repl, text) + +twitter_linkify = compose([ + preserve_newlines, + at_linkify, + linkify, + hashtag_linkify +]) + +def namelink(name): + if not name.startswith('@'): + return name + if len(name) == 1: + return name + if name[1] in string.whitespace: + return name + + return '@' + (href % ('http://twitter.com/' + name[1:], name[1:])) + +def at_linkified_text(self): + pieces = filter(None, at_someone.split(self.text)) + return u''.join(namelink(linkify(piece)) for piece in pieces) + +def d_linkified_text(self): + return linkify(self.text) + +def format_tweet_date(tweet): + fudge = 1.5 + seconds = calendar.timegm(rfc822.parsedate(tweet.created_at)) + delta = int(time.time()) - int(seconds) + + if delta < (60 * fudge): + return _('about a minute ago') + elif delta < (60 * 60 * (1/fudge)): + return _('about {minutes} minutes ago').format(minutes=(delta / 60) + 1) + elif delta < (60 * 60 * fudge): + return _('about an hour ago') + elif delta < (60 * 60 * 24 * (1/fudge)): + return _('about {hours} hours ago').format(hours=(delta / (60 * 60)) + 1) + elif delta < (60 * 60 * 24 * fudge): + return _('about a day ago') + else: + return _('about {days} days ago').format(days=(delta / (60 * 60 * 24)) + 1) + +def twitter_mini_img_url(url): + ''' + twitter users have profile_image_url. + if you add a _mini to the end of the filename, you get a 20x20 version. + ''' + i = url.rfind('.') + first = url[:i] + if i != -1 and first.endswith('_normal'): + first = first[:-7] + url = first + '_mini' + url[i:] + return url + +class TweetShrink(UrlShortener): + endpoint = 'http://tweetshrink.com/shrink' + + def get_args(self, url): + return dict(text=url) + + def process_response(self, resp): + ret = UrlShortener.process_response(self, resp) + import simplejson + return simplejson.loads(ret)['text'] + +shrink_tweet = TweetShrink().shorten + diff --git a/digsby/src/plugins/twitter/twitter_xauth.py b/digsby/src/plugins/twitter/twitter_xauth.py new file mode 100644 index 0000000..c3efeb3 --- /dev/null +++ b/digsby/src/plugins/twitter/twitter_xauth.py @@ -0,0 +1,176 @@ +from urllib2 import Request, urlopen +import oauth.oauth as oauth +from util import threaded, callsback +from datetime import datetime +import time + +# for digsby app +CONSUMER_KEY = '' +CONSUMER_SECRET = '' + +@callsback +def get_oauth_token(username, password, callback=None): + auth = OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) + + @threaded + def on_thread(): + return auth.get_xauth_access_token(username, password) + + on_thread(callback=callback) + +class AuthHandler(object): + + def apply_auth(self, url, method, headers, parameters): + """Apply authentication headers to request""" + raise NotImplementedError + + def get_username(self): + """Return the username of the authenticated user""" + raise NotImplementedError + + +class OAuthHandler(AuthHandler): + """OAuth authentication handler""" + + OAUTH_HOST = 'twitter.com' + OAUTH_ROOT = '/oauth/' + + def __init__(self, consumer_key, consumer_secret, callback=None, secure=False): + self._consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) + self._sigmethod = oauth.OAuthSignatureMethod_HMAC_SHA1() + self.request_token = None + self.access_token = None + self.callback = callback + self.username = None + self.secure = secure + + def _get_oauth_url(self, endpoint, secure=False): + if self.secure or secure: + prefix = 'https://' + else: + prefix = 'http://' + + return prefix + self.OAUTH_HOST + self.OAUTH_ROOT + endpoint + + def apply_auth(self, url, method, headers, parameters): + request = oauth.OAuthRequest.from_consumer_and_token( + self._consumer, http_url=url, http_method=method, + token=self.access_token, parameters=parameters + ) + request.sign_request(self._sigmethod, self._consumer, self.access_token) + headers.update(request.to_header()) + + def _get_request_token(self): + url = self._get_oauth_url('request_token') + request = oauth.OAuthRequest.from_consumer_and_token( + self._consumer, http_url=url, callback=self.callback + ) + request.sign_request(self._sigmethod, self._consumer, None) + resp = urlopen(Request(url, headers=request.to_header())) + return oauth.OAuthToken.from_string(resp.read()) + + def set_request_token(self, key, secret): + self.request_token = oauth.OAuthToken(key, secret) + + def set_access_token(self, key, secret): + self.access_token = oauth.OAuthToken(key, secret) + + def get_authorization_url(self, signin_with_twitter=False): + """Get the authorization URL to redirect the user""" + + # get the request token + self.request_token = self._get_request_token() + + # build auth request and return as url + if signin_with_twitter: + url = self._get_oauth_url('authenticate') + else: + url = self._get_oauth_url('authorize') + request = oauth.OAuthRequest.from_token_and_callback( + token=self.request_token, http_url=url + ) + + return request.to_url() + + def get_access_token(self, verifier=None): + """ + After user has authorized the request token, get access token + with user supplied verifier. + """ + url = self._get_oauth_url('access_token') + + # build request + request = oauth.OAuthRequest.from_consumer_and_token( + self._consumer, + token=self.request_token, http_url=url, + verifier=str(verifier) + ) + request.sign_request(self._sigmethod, self._consumer, self.request_token) + + # send request + resp = urlopen(Request(url, headers=request.to_header())) + self.access_token = oauth.OAuthToken.from_string(resp.read()) + return self.access_token + + def get_xauth_access_token(self, username, password): + """ + Get an access token from an username and password combination. + In order to get this working you need to create an app at + http://twitter.com/apps, after that send a mail to api@twitter.com + and request activation of xAuth for it. + """ + url = self._get_oauth_url('access_token', secure=True) # must use HTTPS + request = oauth.OAuthRequest.from_consumer_and_token( + oauth_consumer=self._consumer, + http_method='POST', http_url=url, + parameters = { + 'x_auth_mode': 'client_auth', + 'x_auth_username': to_utf8(username), + 'x_auth_password': to_utf8(password), + 'oauth_timestamp': generate_corrected_timestamp() + } + ) + request.sign_request(self._sigmethod, self._consumer, None) + + resp = urlopen(Request(url, data=request.to_postdata())) + self.access_token = oauth.OAuthToken.from_string(resp.read()) + return self.access_token + + def get_username(self): + #if self.username is None: + #api = API(self) + #user = api.verify_credentials() + #if user: + #self.username = user.screen_name + #else: + #raise Exception("Unable to get username, invalid oauth token!") + return self.username + +def to_utf8(s): + if isinstance(s, unicode): + return s.encode('utf-8') + else: + return s + +_time_correction = None + +def get_time_correction(): + return _time_correction + +def set_server_timestamp(server_time_now): + assert isinstance(server_time_now, float) + global _time_correction + _time_correction = server_time_now - time.time() + +def generate_corrected_timestamp(): + import logging + now = time.time() + if _time_correction is not None: + t = now + _time_correction + logging.getLogger('twitter').warn('using corrected timestamp: %r', datetime.fromtimestamp(t).isoformat()) + else: + t = now + logging.getLogger('twitter').warn('using UNcorrected timestamp: %r', datetime.fromtimestamp(t).isoformat()) + return t + + diff --git a/digsby/src/plugins/twitter/twitterstream.py b/digsby/src/plugins/twitter/twitterstream.py new file mode 100644 index 0000000..c8027d6 --- /dev/null +++ b/digsby/src/plugins/twitter/twitterstream.py @@ -0,0 +1,42 @@ +''' +connects to Twitter's streaming API + http://apiwiki.twitter.com/Streaming-API-Documentation +''' + +import netextensions +import common.asynchttp as asynchttp +from common import netcall +from util.primitives.funcs import Delegate +from logging import getLogger; log = getLogger('twstrm') + +class TwitterStream(object): + def __init__(self, username, password, follow_ids): + self.follow_ids = follow_ids + self.httpmaster = asynchttp.HttpMaster() + + uris = ['stream.twitter.com', 'twitter.com'] + realm = 'Firehose' + self.httpmaster.add_password(realm, uris, username, password) + + self.post_data = 'follow=' + ','.join(str(i) for i in follow_ids) + + self.on_tweet = Delegate() + + def start(self): + url = 'http://stream.twitter.com/1/statuses/filter.json' + req = asynchttp.HTTPRequest.make_request(url, + data=self.post_data, method='POST', accumulate_body=False) + req.bind_event('on_chunk', self._on_chunk) + + log.info('starting twitter stream, following %d people', len(self.follow_ids)) + netcall(lambda: self.httpmaster.request(req)) + + def _on_chunk(self, chunk): + if len(chunk) < 5 and not chunk.strip(): + return # ignore keep alives + + self.on_tweet(chunk) + + def stop(self): + log.warning('TODO: TwitterStream.stop') + diff --git a/digsby/src/plugins/twitter/unittests/__init__.py b/digsby/src/plugins/twitter/unittests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/plugins/twitter/unittests/sample_users.json b/digsby/src/plugins/twitter/unittests/sample_users.json new file mode 100644 index 0000000..6b1a0d4 --- /dev/null +++ b/digsby/src/plugins/twitter/unittests/sample_users.json @@ -0,0 +1 @@ +{"16253146": {"id": 16253146, "profile_sidebar_fill_color": "d11a2b", "profile_text_color": "000000", "followers_count": 1621, "protected": "false", "location": "Rochester, NY", "profile_background_color": "ffffff", "utc_offset": -18000, "statuses_count": 523, "description": "Digital Rochester is upstate NY area's pre-eminent technology business community.", "friends_count": 1503, "profile_link_color": "570971", "profile_image_url": "http://a3.twimg.com/profile_images/72862297/swish_normal.gif", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/4567844/DR-twitter.jpg", "name": "Digital Rochester", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "Digital_Roch", "url": "http://www.digitalrochester.com/", "created_at": "Thu Dec 03 18:48:03 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "551c82", "following": "true"}, "9723672": {"id": 9723672, "profile_sidebar_fill_color": "dce3ea", "profile_text_color": "333333", "followers_count": 1546, "protected": "false", "location": "Connecticut, USA", "profile_background_color": "000000", "utc_offset": -18000, "statuses_count": 558, "description": "Trillian Astra combines Twitter, Facebook, RSS, AIM, ICQ, WLM, Y!M, GTalk, Skype, MySpace, IRC, Jabber, Email. Get yours: http://www.trillian.im", "friends_count": 2, "profile_link_color": "004080", "profile_image_url": "http://a3.twimg.com/profile_images/62704393/Final_Icon_-_Test_8_-_256_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/3241594/DBox-Simple.png", "screen_name": "trillianastra", "profile_background_tile": "true", "favourites_count": 0, "name": "Trillian Astra", "url": "http://www.trillian.im", "created_at": "Fri Oct 26 23:24:49 +0000 2007", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "bfc9cf", "following": "true"}, "20416212": {"id": 20416212, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 3204, "protected": "false", "location": "Williamsburg Houses, New York", "profile_background_color": "9ae4e8", "utc_offset": null, "statuses_count": 117, "description": null, "friends_count": 24, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/296606506/IMG_0191_normal.JPG", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "crbear", "profile_background_tile": "false", "favourites_count": 1, "name": "christopher bear", "url": null, "created_at": "Mon Feb 09 04:38:16 +0000 2009", "time_zone": null, "profile_sidebar_border_color": "87bc44", "following": "true"}, "22999569": {"id": 22999569, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 413, "protected": "false", "location": "Rochester, NY", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 535, "description": "", "friends_count": 724, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/481606568/BreakRoom-150_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "wcmfbreakroom", "profile_background_tile": "false", "favourites_count": 1, "name": "The Break Room ", "url": "http://www.wcmf.com/pages/1420057.php", "created_at": "Thu Mar 05 23:46:18 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "14075928": {"id": 14075928, "profile_sidebar_fill_color": "FFF7CC", "profile_text_color": "0C3E53", "followers_count": 1981027, "protected": "false", "location": "New York, NY", "profile_background_color": "006933", "utc_offset": -18000, "statuses_count": 3641, "description": "America's Finest News Source", "friends_count": 407492, "profile_link_color": "FF0000", "profile_image_url": "http://a1.twimg.com/profile_images/334357688/onion_logo_03_L_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/25775614/background4.png", "name": "The Onion", "profile_background_tile": "false", "favourites_count": 11, "screen_name": "TheOnion", "url": "http://www.theonion.com", "created_at": "Thu Dec 03 16:38:10 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "F2E195", "following": "true"}, "712733": {"id": 712733, "profile_sidebar_fill_color": "7ea9e2", "profile_text_color": "3d3d3d", "followers_count": 1241, "protected": "false", "location": "San Francisco, CA", "profile_background_color": "386299", "utc_offset": -28800, "statuses_count": 3256, "description": "Square Co-founder, iPhone Engineer", "friends_count": 245, "profile_link_color": "0021b3", "profile_image_url": "http://a3.twimg.com/profile_images/60307963/headshot_recent_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/41722/Aqua_Blue.jpg", "name": "Tristan O'Tierney", "profile_background_tile": "false", "favourites_count": 276, "screen_name": "tristan", "url": "http://www.otierney.net", "created_at": "Thu Dec 03 20:03:12 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "484848", "following": "true"}, "15473958": {"id": 15473958, "profile_sidebar_fill_color": "95E8EC", "profile_text_color": "3C3940", "followers_count": 10943, "protected": "false", "location": "Currently in the cleanroom at ", "profile_background_color": "0099B9", "utc_offset": -28800, "statuses_count": 53, "description": "The next mission to Mars. I launch in 2011!", "friends_count": 5, "profile_link_color": "0099B9", "profile_image_url": "http://a1.twimg.com/profile_images/64950950/SuperLaser_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/3274232/MSL_Bg_v1_export.jpg", "screen_name": "MarsScienceLab", "profile_background_tile": "false", "favourites_count": 0, "name": "Curiosity Rover", "url": "http://mars.jpl.nasa.gov/msl/", "created_at": "Thu Jul 17 21:18:10 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "5ED4DC", "following": "true"}, "7341872": {"id": 7341872, "profile_sidebar_fill_color": "2A3A53", "profile_text_color": "000000", "followers_count": 181, "protected": "false", "location": "\u00dcT: 43.102575,-77.623477", "profile_background_color": "000000", "utc_offset": -18000, "statuses_count": 1836, "description": "Born and Raised in NYC, RIT Alum, Digsby Dev, Gamer, Blackberry Lover!", "friends_count": 194, "profile_link_color": "bfbfbf", "profile_image_url": "http://a1.twimg.com/profile_images/490603420/Screen_shot_2009-10-26_at_1.25.31_AM_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/3583044/star_tile.gif", "screen_name": "jeffreyrr", "profile_background_tile": "true", "favourites_count": 14, "name": "Jeffrey R", "url": "http://www.digsby.com", "created_at": "Fri Nov 20 09:35:16 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "000000", "following": "true"}, "52087269": {"id": 52087269, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 32564, "protected": "false", "location": "", "profile_background_color": "9ae4e8", "utc_offset": 0, "statuses_count": 28, "description": "", "friends_count": 8, "profile_link_color": "0000ff", "profile_image_url": "http://s.twimg.com/a/1259808911/images/default_profile_3_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "noelfielding11", "profile_background_tile": "false", "favourites_count": 0, "name": "Noel Fielding", "url": null, "created_at": "Mon Jun 29 15:26:12 +0000 2009", "time_zone": "London", "profile_sidebar_border_color": "87bc44", "following": "true"}, "53921137": {"id": 53921137, "profile_sidebar_fill_color": "265223", "profile_text_color": "42ab3b", "followers_count": 12116, "protected": "false", "location": "help me!", "profile_background_color": "ffffff", "utc_offset": 3600, "statuses_count": 192, "description": "Follow me and I'll send you a DM when someone unfollows you. And hey, I've busted 1962181 unfollowers so far!", "friends_count": 0, "profile_link_color": "8DC1D6", "profile_image_url": "http://a3.twimg.com/profile_images/298298989/7_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/21552797/mqspringtimegreen.br.jpg", "screen_name": "goodbyebuddy", "profile_background_tile": "false", "favourites_count": 9, "name": "Goodbye, Buddy!", "url": "http://bit.ly/3FA1H9", "created_at": "Sun Nov 22 04:37:08 +0000 2009", "time_zone": "Berlin", "profile_sidebar_border_color": "42ab3b", "following": "true"}, "14679475": {"id": 14679475, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 996, "protected": "false", "location": "San Francisco, CA", "profile_background_color": "000000", "utc_offset": -28800, "statuses_count": 490, "description": "An independent game company.", "friends_count": 181, "profile_link_color": "0084B4", "profile_image_url": "http://a1.twimg.com/profile_images/63454310/logo4bLarge_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/3293195/RabbitConcept4e.jpg", "name": "Jeffrey Rosen", "profile_background_tile": "false", "favourites_count": 2, "screen_name": "Wolfire", "url": "http://www.wolfire.com", "created_at": "Thu Dec 03 18:50:32 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "12863272": {"id": 12863272, "profile_sidebar_fill_color": "BEB7BD", "profile_text_color": "000000", "followers_count": 188673, "protected": "false", "location": "San Francisco, California", "profile_background_color": "5C5C5C", "utc_offset": -28800, "statuses_count": 1418, "description": "Moving and Storing Tweets at Twitter.", "friends_count": 241, "profile_link_color": "67281C", "profile_image_url": "http://a3.twimg.com/profile_images/53302375/john48x48_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/7105194/02-n9-003-0014-aranislandinisheerplassyshipwreck3.jpg", "screen_name": "jkalucki", "profile_background_tile": "false", "favourites_count": 47, "name": "John Kalucki", "url": null, "created_at": "Wed Jan 30 06:02:57 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "67281C", "following": "true"}, "24585498": {"id": 24585498, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 7801, "protected": "false", "location": "San Francisco, CA", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 188, "description": "", "friends_count": 62, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/105313836/twitter_smokes_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "TimOfLegend", "profile_background_tile": "false", "favourites_count": 2, "name": "Tim Schafer", "url": "http://www.doublefine.com", "created_at": "Sun Mar 15 21:03:29 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "71876190": {"id": 71876190, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 294853, "protected": "false", "location": "Los Angeles", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 466, "description": "I'm an actor, director and producer. ", "friends_count": 5, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/400232403/Photo_6_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "name": "Danny DeVito", "profile_background_tile": "false", "favourites_count": 4, "screen_name": "Danny_DeVito", "url": "http://www.imdb.com/name/nm0000362/", "created_at": "Thu Dec 03 19:23:10 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "19613515": {"id": 19613515, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 143, "protected": "false", "location": "RIT", "profile_background_color": "9ae4e8", "utc_offset": -21600, "statuses_count": 37, "description": "An [UNOFFICIAL] Twitter Community for GCCIS Students", "friends_count": 63, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/73573178/450px-RIT_building_-_Golisano_Building_copy_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "GCCISstudents", "profile_background_tile": "false", "favourites_count": 0, "name": "GCCIS", "url": null, "created_at": "Tue Jan 27 20:21:19 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "14261383": {"id": 14261383, "profile_sidebar_fill_color": "e8e8e8", "profile_text_color": "333333", "followers_count": 206, "protected": "false", "location": "Redmond, WA", "profile_background_color": "4887e0", "utc_offset": -28800, "statuses_count": 1735, "description": "RIT Student, Microsoft Engineer, Cyclist", "friends_count": 73, "profile_link_color": "425796", "profile_image_url": "http://a3.twimg.com/profile_images/329159647/me_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/24889666/navshape.jpg", "name": "Dave Amenta", "profile_background_tile": "false", "favourites_count": 6, "screen_name": "davux", "url": "http://www.daveamenta.com/", "created_at": "Thu Sep 03 20:23:23 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "000000", "following": "true"}, "34028868": {"id": 34028868, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 153, "protected": "false", "location": "The Mushroom Kingdom", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 392, "description": "The continuing story of the love between two men and their consoles.", "friends_count": 339, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/463633376/Photo_on_2009-10-10_at_17.42__2_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "cartridgeblower", "profile_background_tile": "false", "favourites_count": 0, "name": "Richard and Eric", "url": "http://cartridgeblowers.com", "created_at": "Tue Apr 21 20:26:32 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "22412376": {"id": 22412376, "profile_sidebar_fill_color": "252429", "profile_text_color": "666666", "followers_count": 19045, "protected": "false", "location": "\u00dcT: -33.873288,151.210039", "profile_background_color": "1A1B1F", "utc_offset": -21600, "statuses_count": 431, "description": "fail.", "friends_count": 1, "profile_link_color": "2FC2EF", "profile_image_url": "http://a3.twimg.com/profile_images/219702175/image1_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme9/bg.gif", "screen_name": "Deadmau5", "profile_background_tile": "false", "favourites_count": 0, "name": "Joel Zimmerman", "url": "http://www.deadmau5.com", "created_at": "Sun Mar 01 21:59:41 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "181A1E", "following": "true"}, "14583400": {"id": 14583400, "profile_sidebar_fill_color": "FFFFFF", "profile_text_color": "000000", "followers_count": 117255, "protected": "false", "location": "", "profile_background_color": "9ae4e8", "utc_offset": 0, "statuses_count": 428, "description": "", "friends_count": 0, "profile_link_color": "11A5B2", "profile_image_url": "http://a1.twimg.com/profile_images/54449056/bandtwitterimage_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/2548478/logoblack.jpg", "name": "muse", "profile_background_tile": "true", "favourites_count": 0, "screen_name": "muse", "url": "http://www.muse.mu", "created_at": "Thu Dec 03 16:43:51 +0000 2009", "time_zone": "London", "profile_sidebar_border_color": "11A5B2", "following": "true"}, "59947180": {"id": 59947180, "profile_sidebar_fill_color": "99CC33", "profile_text_color": "3E4415", "followers_count": 132, "protected": "false", "location": "San Francisco, CA", "profile_background_color": "352726", "utc_offset": -28800, "statuses_count": 67, "description": "Pixel Poet on GayGamer", "friends_count": 23, "profile_link_color": "D02B55", "profile_image_url": "http://a1.twimg.com/profile_images/330826848/pixelpoet_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1258667182/images/themes/theme5/bg.gif", "screen_name": "pixelpoetgg", "profile_background_tile": "false", "favourites_count": 0, "name": "Pixel Poet", "url": "http://gaygamer.net/pixelpoet/", "created_at": "Sat Jul 25 01:46:09 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "829D5E", "following": "true"}, "782113": {"id": 782113, "profile_sidebar_fill_color": "EADEAA", "profile_text_color": "333333", "followers_count": 3465, "protected": "false", "location": "", "profile_background_color": "8B542B", "utc_offset": -18000, "statuses_count": 74, "description": "", "friends_count": 5, "profile_link_color": "9D582E", "profile_image_url": "http://a1.twimg.com/profile_images/24188322/logo_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme8/bg.gif", "screen_name": "A345d75iu765m", "profile_background_tile": "false", "favourites_count": 0, "name": "Adiumy", "url": null, "created_at": "Tue Feb 20 02:47:09 +0000 2007", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "D9B17E", "following": "true"}, "10926202": {"id": 10926202, "profile_sidebar_fill_color": "efefef", "profile_text_color": "333333", "followers_count": 60827, "protected": "false", "location": "Los Angeles", "profile_background_color": "000000", "utc_offset": -28800, "statuses_count": 152, "description": "The only official Twitter of Adam Carolla. Be sure to follow for daily updates! ", "friends_count": 38, "profile_link_color": "1c458c", "profile_image_url": "http://a1.twimg.com/profile_images/525176636/Adam-Carolla_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/54765380/AC-Twitter.jpg", "screen_name": "adamcarolla", "profile_background_tile": "false", "favourites_count": 0, "name": "Adam Carolla", "url": "http://www.adamcarolla.com", "created_at": "Fri Dec 07 04:14:37 +0000 2007", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "ffffff", "following": "true"}, "27924344": {"id": 27924344, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 14, "protected": "false", "location": "", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 302, "description": "", "friends_count": 12, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/186192770/Digsby_3D_Robber_64_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "ninjadigsby", "profile_background_tile": "false", "favourites_count": 1, "name": "ninjadigsby", "url": null, "created_at": "Tue Aug 25 22:43:19 +0000 2009", "time_zone": "Quito", "profile_sidebar_border_color": "87bc44", "following": "true"}, "14302178": {"id": 14302178, "profile_sidebar_fill_color": "99CC33", "profile_text_color": "3E4415", "followers_count": 566, "protected": "false", "location": "Digsby Universe", "profile_background_color": "e6e6e6", "utc_offset": 3600, "statuses_count": 189, "description": "The digsby community website", "friends_count": 242, "profile_link_color": "D02B55", "profile_image_url": "http://a3.twimg.com/profile_images/64809465/followers_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/3395559/manydigsbies.png", "screen_name": "digsbies", "profile_background_tile": "false", "favourites_count": 1, "name": "digsbies", "url": "http://digsbies.org", "created_at": "Fri Apr 04 12:37:28 +0000 2008", "time_zone": "Stockholm", "profile_sidebar_border_color": "829D5E", "following": "true"}, "16453340": {"id": 16453340, "verified": false, "profile_sidebar_fill_color": "EADEAA", "profile_text_color": "000000", "followers_count": 2216, "protected": false, "location": "Rochester, NY", "profile_background_color": "0d2877", "utc_offset": -18000, "statuses_count": 5099, "description": "WROC-TV: Covering local news in Rochester and Western New York", "friends_count": 1960, "profile_link_color": "0d2877", "profile_image_url": "http://a1.twimg.com/profile_images/346338604/twitterlogo_normal.jpg", "notifications": false, "geo_enabled": false, "profile_background_image_url": "http://a3.twimg.com/profile_background_images/3113331/wroc.gif", "screen_name": "News_8", "profile_background_tile": false, "favourites_count": 1, "name": "News 8 - WROC-TV", "url": "http://www.rochesterhomepage.net", "created_at": "Thu Sep 25 16:42:56 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "EADEAA", "following": false}, "11340982": {"id": 11340982, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 42244, "protected": "false", "location": "los angeles", "profile_background_color": "9ae4e8", "utc_offset": -21600, "statuses_count": 562, "description": "", "friends_count": 35358, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/51892855/093007_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "name": "zefrank", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "zefrank", "url": "http://www.zefrank.com", "created_at": "Thu Dec 03 19:04:03 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "27457788": {"id": 27457788, "profile_sidebar_fill_color": "e1e0e1", "profile_text_color": "35343c", "followers_count": 1453331, "protected": "false", "location": "London - NYC - LA", "profile_background_color": "8ca7d4", "utc_offset": -28800, "statuses_count": 1009, "description": "Former shoe salesman now making a go at film and theater. Wish me luck...", "friends_count": 4, "profile_link_color": "123cc4", "profile_image_url": "http://a3.twimg.com/profile_images/114075583/Picture_4_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/7016037/TwitterBgnd2.jpg", "screen_name": "KevinSpacey", "profile_background_tile": "false", "favourites_count": 1, "name": "Kevin Spacey", "url": "http://www.TriggerStreet.com", "created_at": "Sun Mar 29 16:55:27 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "655ee8", "following": "true"}, "18393773": {"id": 18393773, "profile_sidebar_fill_color": "DAECF4", "profile_text_color": "663B12", "followers_count": 1341465, "protected": "false", "location": "mostly near minneapolis", "profile_background_color": "c4e0ec", "utc_offset": -21600, "statuses_count": 7164, "description": "will eventually grow up and get a real job. Until then, will keep making things up and writing them down.", "friends_count": 463, "profile_link_color": "1F98C7", "profile_image_url": "http://a3.twimg.com/profile_images/532096093/P1010763-1_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme2/bg.gif", "name": "Neil Gaiman", "profile_background_tile": "false", "favourites_count": 27, "screen_name": "neilhimself", "url": "http://www.neilgaiman.com", "created_at": "Thu Dec 03 20:37:15 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "C6E2EE", "following": "true"}, "54343977": {"id": 54343977, "profile_sidebar_fill_color": "0e0e0e", "profile_text_color": "9d9d9d", "followers_count": 21890, "protected": "false", "location": "", "profile_background_color": "000000", "utc_offset": -28800, "statuses_count": 97, "description": "Dave Grohl, Josh Homme, John Paul Jones", "friends_count": 0, "profile_link_color": "363636", "profile_image_url": "http://a1.twimg.com/profile_images/356175790/Vulture_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/28570642/bg_over.jpg", "name": "ThemCrookedVultures", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "crookedvultures", "url": "http://themcrookedvultures.com", "created_at": "Thu Dec 03 19:00:35 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "0e0e0e", "following": "true"}, "62581962": {"id": 62581962, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 889169, "protected": "false", "location": "", "profile_background_color": "9AE4E8", "utc_offset": -28800, "statuses_count": 80, "description": "I'm 29. I live with my 73-year-old dad. He is awesome. I just write down shit that he says", "friends_count": 1, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/362705903/dad_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme16/bg.gif", "screen_name": "shitmydadsays", "profile_background_tile": "false", "favourites_count": 0, "name": "Justin", "url": null, "created_at": "Mon Aug 03 18:20:34 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "15489212": {"id": 15489212, "profile_sidebar_fill_color": "FFFFF5", "profile_text_color": "1C1C1C", "followers_count": 1510, "protected": "false", "location": "Worldwide", "profile_background_color": "3F3F3F", "utc_offset": 0, "statuses_count": 175, "description": "Updates from the Miranda IM OpenSource Project", "friends_count": 480, "profile_link_color": "3E6196", "profile_image_url": "http://a1.twimg.com/profile_images/424471420/miranda_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/38151722/bg.gif", "screen_name": "miranda_im", "profile_background_tile": "true", "favourites_count": 0, "name": "Miranda IM", "url": "http://www.miranda-im.org", "created_at": "Sat Jul 19 02:56:43 +0000 2008", "time_zone": "Dublin", "profile_sidebar_border_color": "F3F3E2", "following": "true"}, "13373512": {"id": 13373512, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 24, "protected": "false", "location": "", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 29, "description": "", "friends_count": 11, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/527373715/avatar_1__normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme1/bg.png", "screen_name": "Stelminator", "profile_background_tile": "false", "favourites_count": 0, "name": "Christopher Stelma", "url": null, "created_at": "Tue Feb 12 02:32:24 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "46576295": {"id": 46576295, "profile_sidebar_fill_color": "252429", "profile_text_color": "666666", "followers_count": 258056, "protected": "false", "location": "Hollywood", "profile_background_color": "1A1B1F", "utc_offset": -28800, "statuses_count": 427, "description": "Yes its the guy from American Idol. On tour now across the US ", "friends_count": 157, "profile_link_color": "2FC2EF", "profile_image_url": "http://a1.twimg.com/profile_images/530723042/shh_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/54313086/double_2.jpg", "name": "Adam Lambert", "profile_background_tile": "true", "favourites_count": 3, "screen_name": "adamlambert", "url": "http://www.adamofficial.com", "created_at": "Thu Dec 03 21:43:28 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "181A1E", "following": "true"}, "24447643": {"id": 24447643, "profile_sidebar_fill_color": "e0dedf", "profile_text_color": "424242", "followers_count": 1328115, "protected": "false", "location": "Our Planet", "profile_background_color": "566079", "utc_offset": 0, "statuses_count": 719, "description": "I'm a British European, I think like an American and I was born in an Arabic country", "friends_count": 67, "profile_link_color": "3288e3", "profile_image_url": "http://a3.twimg.com/profile_images/124932069/Triffids_Trailer_3_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/6670026/izzard-bkg.jpg", "screen_name": "eddieizzard", "profile_background_tile": "true", "favourites_count": 9, "name": "Eddie Izzard", "url": "http://www.eddieizzard.com", "created_at": "Sat Mar 14 23:13:44 +0000 2009", "time_zone": "London", "profile_sidebar_border_color": "aeaeae", "following": "true"}, "15901190": {"id": 15901190, "profile_sidebar_fill_color": "252429", "profile_text_color": "666666", "followers_count": 635958, "protected": "false", "location": "Los Angeles", "profile_background_color": "1A1B1F", "utc_offset": -28800, "statuses_count": 500, "description": "Nine Inch Nails. Not reading replies at this time. If you follow known trolls, you will get blocked.", "friends_count": 68, "profile_link_color": "2FC2EF", "profile_image_url": "http://a3.twimg.com/profile_images/58499973/robo1_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme9/bg.gif", "screen_name": "trent_reznor", "profile_background_tile": "false", "favourites_count": 0, "name": "Trent Reznor", "url": "http://www.nin.com/", "created_at": "Tue Aug 19 05:53:47 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "181A1E", "following": "true"}, "32218792": {"id": 32218792, "profile_sidebar_fill_color": "616161", "profile_text_color": "000000", "followers_count": 856, "protected": "false", "location": "Rochester, NY", "profile_background_color": "000000", "utc_offset": -18000, "statuses_count": 180, "description": "CEO @ Digsby", "friends_count": 81, "profile_link_color": "b6b6b9", "profile_image_url": "http://a1.twimg.com/profile_images/141558060/face_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/47056817/mqnewyorkmadness.br.jpg", "screen_name": "shapirosteve", "profile_background_tile": "false", "favourites_count": 6, "name": "Steve Shapiro", "url": "http://www.digsby.com", "created_at": "Fri Oct 09 15:11:04 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "000000", "following": "true"}, "682903": {"id": 682903, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 19219, "protected": "false", "location": "California", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 83, "description": "Former hardcore/punk singer/songwriter; spoken word artist, book author (prose and poetry), radio and TV personality. Former vocalist for Black Flag.", "friends_count": 15, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/64499571/rollins_narrowweb__300x460_0_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/3370456/1557911647_6502380a91.jpg", "screen_name": "NotHenryRollins", "profile_background_tile": "true", "favourites_count": 14, "name": "Fake Henry Rollins", "url": "http://www.henryrollins.com", "created_at": "Mon Jan 22 22:26:49 +0000 2007", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "43164252": {"id": 43164252, "verified": false, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 879, "protected": false, "location": "", "profile_background_color": "9ae4e8", "utc_offset": -32400, "statuses_count": 260, "description": "The latest independent games news, interviews, and features, from the makers of Gamasutra.", "friends_count": 145, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/388075140/indiegamestwit_normal.png", "notifications": false, "geo_enabled": false, "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "indiegamescom", "profile_background_tile": false, "favourites_count": 0, "name": "IndieGames.com", "url": "http://www.indiegames.com/blog", "created_at": "Thu May 28 18:21:32 +0000 2009", "time_zone": "Alaska", "profile_sidebar_border_color": "87bc44", "following": false}, "3141991": {"id": 3141991, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 1289, "protected": "false", "location": "Washington, D.C. ", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 490, "description": "Political reporter for Slate Magazine.", "friends_count": 185, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/358253784/beamc_normal.gif", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "name": "Chris Beam", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "jcbeam", "url": "http://www.slate.com", "created_at": "Thu Dec 03 18:13:13 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "45526316": {"id": 45526316, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 4419, "protected": "false", "location": "Brussels", "profile_background_color": "9AE4E8", "utc_offset": 0, "statuses_count": 372, "description": "Your democratically elected, indigenous, bonk-eyed representitive. I'm a danger to others and a national fucking disgrace.", "friends_count": 19, "profile_link_color": "0084B4", "profile_image_url": "http://a1.twimg.com/profile_images/256035758/nick2_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/17213303/back.jpg", "name": "Real Nick Griffin", "profile_background_tile": "true", "favourites_count": 0, "screen_name": "realnickgriffin", "url": null, "created_at": "Thu Dec 03 22:12:26 +0000 2009", "time_zone": "London", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "84351228": {"id": 84351228, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 14909, "protected": "false", "location": "Boston", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 50, "description": "Director of the World Wide Web Consortium (W3C) w3.org, the place to agree on web standards. Founded new webfoundation.org - let the web serve humanity", "friends_count": 45, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/484567818/2001-europaeum-eighth_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "timberners_lee", "profile_background_tile": "false", "favourites_count": 1, "name": "Tim Berners-Lee", "url": "http://www.w3.org/People/Berners-Lee/", "created_at": "Thu Oct 22 15:29:47 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "15808604": {"id": 15808604, "profile_sidebar_fill_color": "f8f7ed", "profile_text_color": "513127", "followers_count": 1092, "protected": "false", "location": "Henrietta, NY", "profile_background_color": "f8f7ed", "utc_offset": -18000, "statuses_count": 458, "description": "RIT University News is the news and public relations division of Rochester Institute of Technology.", "friends_count": 113, "profile_link_color": "F36E21", "profile_image_url": "http://a1.twimg.com/profile_images/58063118/itunes_icon_web_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/26207145/twitter_rit_cream.jpg", "name": "RIT NEWS", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "RITNEWS", "url": "http://www.rit.edu/news", "created_at": "Thu Dec 03 18:02:00 +0000 2009", "time_zone": "Quito", "profile_sidebar_border_color": "F36E21", "following": "true"}, "9853162": {"id": 9853162, "profile_sidebar_fill_color": "252429", "profile_text_color": "666666", "followers_count": 189, "protected": "false", "location": "Rochester, NY", "profile_background_color": "1A1B1F", "utc_offset": -18000, "statuses_count": 1630, "description": "i make digsby!", "friends_count": 154, "profile_link_color": "2FC2EF", "profile_image_url": "http://a1.twimg.com/profile_images/51923316/me_study_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259867295/images/themes/theme9/bg.gif", "screen_name": "synae", "profile_background_tile": "false", "favourites_count": 10, "name": "Mike Dougherty", "url": null, "created_at": "Fri Jul 17 13:58:20 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "181A1E", "following": "true"}, "15804774": {"id": 15804774, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 7080, "protected": "false", "location": "San Francisco Bay Area", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 226, "description": "Creator of Python", "friends_count": 155, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/424495004/GuidoAvatar_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme1/bg.png", "screen_name": "gvanrossum", "profile_background_tile": "false", "favourites_count": 18, "name": "Guido van Rossum", "url": "http://python.org/~guido/", "created_at": "Mon Aug 11 04:02:18 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "14465485": {"id": 14465485, "profile_sidebar_fill_color": "E3E2DE", "profile_text_color": "634047", "followers_count": 25, "protected": "false", "location": "", "profile_background_color": "EDECE9", "utc_offset": -18000, "statuses_count": 311, "description": "", "friends_count": 28, "profile_link_color": "0055a5", "profile_image_url": "http://a3.twimg.com/profile_images/440924621/scm_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/40648716/SCR.png", "screen_name": "rosasaul", "profile_background_tile": "false", "favourites_count": 0, "name": "Saul Rosa", "url": null, "created_at": "Tue Aug 25 21:28:29 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "D3D2CF", "following": "true"}, "43736146": {"id": 43736146, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 30550, "protected": "false", "location": "Venice Beach", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 326, "description": "The dick on Sunny", "friends_count": 36, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/412558616/IMG_2473_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "Glenn_Howerton", "profile_background_tile": "false", "favourites_count": 61, "name": "Glenn Howerton", "url": null, "created_at": "Sun May 31 19:07:19 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "813286": {"id": 813286, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 2793047, "protected": "false", "location": "Washington, DC", "profile_background_color": "3266CD", "utc_offset": -18000, "statuses_count": 437, "description": "44th President of the United States", "friends_count": 747725, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/219314140/obama_4color_omark_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/57145311/twitterbamaNEW.jpg", "screen_name": "BarackObama", "profile_background_tile": "false", "favourites_count": 0, "name": "Barack Obama", "url": "http://www.barackobama.com", "created_at": "Mon Mar 05 22:08:25 +0000 2007", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "38693515": {"id": 38693515, "profile_sidebar_fill_color": "f1f3e2", "profile_text_color": "333333", "followers_count": 188, "protected": "false", "location": "Rochester, NY", "profile_background_color": "d5d3ba", "utc_offset": -18000, "statuses_count": 255, "description": "", "friends_count": 7, "profile_link_color": "513127", "profile_image_url": "http://a3.twimg.com/profile_images/224626385/ritits_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "RIT_ITSStatus", "profile_background_tile": "false", "favourites_count": 0, "name": "RIT ITS SystemStatus", "url": "http://www.rit.edu/its", "created_at": "Fri May 08 16:16:35 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "513127", "following": "true"}, "7567712": {"id": 7567712, "verified": false, "profile_sidebar_fill_color": "99CC33", "profile_text_color": "3E4415", "followers_count": 1926, "protected": false, "location": "Los Angeles!", "profile_background_color": "352726", "utc_offset": -28800, "statuses_count": 3805, "description": "Community Manager of Buzznet.com, vegan, cyclist, activist, straight edge, and gay. Whew!.", "friends_count": 685, "profile_link_color": "D02B55", "profile_image_url": "http://a1.twimg.com/profile_images/551567550/Photo_10_normal.jpg", "notifications": false, "geo_enabled": false, "profile_background_image_url": "http://s.twimg.com/a/1259867295/images/themes/theme5/bg.gif", "screen_name": "panasonicyouth", "profile_background_tile": false, "favourites_count": 32, "name": "panasonicyouth", "url": "http://panasonicyouth.buzznet.com/user/", "created_at": "Wed Jul 18 19:21:05 +0000 2007", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "829D5E", "following": true}, "643653": {"id": 643653, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 8718, "protected": "false", "location": "Easthampton, MA 01027", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 11294, "description": "I make cartoons and t-shirts!", "friends_count": 394, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/545930020/rstevens-1up_normal.gif", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/35067210/hulkphonedesk1600.jpg", "name": "rstevens", "profile_background_tile": "false", "favourites_count": 1096, "screen_name": "rstevens", "url": "http://www.dieselsweeties.com", "created_at": "Thu Dec 03 16:40:41 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "16129920": {"id": 16129920, "profile_sidebar_fill_color": "FFFFFF", "profile_text_color": "000000", "followers_count": 1501458, "protected": "false", "location": "new York, NY USA", "profile_background_color": "000000", "utc_offset": -18000, "statuses_count": 358, "description": "I see political people...", "friends_count": 558, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/59437078/icon-200x200_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/20155255/bkgd-maddow-newlogo.jpg", "name": "Rachel Maddow MSNBC", "profile_background_tile": "false", "favourites_count": 5, "screen_name": "maddow", "url": "http://rachel.msnbc.com", "created_at": "Thu Dec 03 20:10:31 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "F3F3F3", "following": "true"}, "17566946": {"id": 17566946, "profile_sidebar_fill_color": "99CC33", "profile_text_color": "3E4415", "followers_count": 293, "protected": "false", "location": "Rochester, NY", "profile_background_color": "000000", "utc_offset": -18000, "statuses_count": 1743, "description": "Web developer (PHP Javascript HTML CSS SQL Java), Linux sys admin, entrepreneur, car nut, amature bodybuilder, massive eater. The spicier the food the better.", "friends_count": 343, "profile_link_color": "D02B55", "profile_image_url": "http://a3.twimg.com/profile_images/538632211/headshot_suit_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/33550403/bg.png", "screen_name": "IAmDriverDan", "profile_background_tile": "true", "favourites_count": 7, "name": "Dan DeFelippi", "url": "http://driverdan.com", "created_at": "Sun Nov 23 01:53:42 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "829D5E", "following": "true"}, "15479538": {"id": 15479538, "profile_sidebar_fill_color": "99CC33", "profile_text_color": "3E4415", "followers_count": 35, "protected": "true", "location": "New York", "profile_background_color": "352726", "utc_offset": -18000, "statuses_count": 5, "description": "If you wouldn't put it in your mouth, don't put it on your skin!", "friends_count": 71, "profile_link_color": "D02B55", "profile_image_url": "http://s.twimg.com/a/1259808911/images/default_profile_4_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/4722443/Olivier_NY_final.jpg", "screen_name": "OLIVIER_NY", "profile_background_tile": "true", "favourites_count": 1, "name": "Gabriella", "url": "http://www.oliviernewyork.com", "created_at": "Fri Jul 18 10:35:11 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "829D5E", "following": "true"}, "9892692": {"id": 9892692, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 4744, "protected": "false", "location": "Boston", "profile_background_color": "9AE4E8", "utc_offset": -18000, "statuses_count": 594, "description": "Start-up guy; Forte, AltaVista, Napster, Bowstreet, Groove,Microsoft, Google", "friends_count": 83, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/51929803/blog_photo_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/6778382/p1010302.jpg", "screen_name": "dondodge", "profile_background_tile": "true", "favourites_count": 3, "name": "dondodge", "url": "http://dondodge.typepad.com/the_next_big_thing/", "created_at": "Fri Nov 02 17:35:11 +0000 2007", "time_zone": "Quito", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "42110634": {"id": 42110634, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 360, "protected": "false", "location": "The Internet", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 49, "description": "The official Idle Thumbs computer voice", "friends_count": 4, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/293052602/thumblogo300_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "idlenews", "profile_background_tile": "false", "favourites_count": 0, "name": "Idle Thumbs", "url": "http://www.idlethumbs.net", "created_at": "Sat May 23 22:30:45 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "13612732": {"id": 13612732, "verified": false, "profile_sidebar_fill_color": "777777", "profile_text_color": "222222", "followers_count": 2311, "protected": false, "location": "Rochester, New York", "profile_background_color": "333333", "utc_offset": -18000, "statuses_count": 9445, "description": "Developer, designer, photographer, marketer @ RIT. UI designer, product designer, technologist @ deviantART.com. Founder of BookMaid.com", "friends_count": 759, "profile_link_color": "55251c", "profile_image_url": "http://a1.twimg.com/profile_images/552736970/twitterProfilePhoto_normal.jpg", "notifications": null, "geo_enabled": true, "profile_background_image_url": "http://a1.twimg.com/profile_background_images/23734060/twitterbg.png", "screen_name": "danlev", "profile_background_tile": false, "favourites_count": 198, "name": "Dan Leveille", "url": "http://dan-lev.com", "created_at": "Mon Feb 18 07:02:44 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "606060", "following": null}, "17201709": {"id": 17201709, "verified": false, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 2050, "protected": false, "location": "San Francisco", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 1310, "description": "Inventor of BitTorrent and many mechanical puzzles. Father to Reilly, Cantor, and Briar", "friends_count": 79, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/393095002/Photo_23_normal.jpg", "notifications": null, "geo_enabled": false, "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme1/bg.png", "name": "Bram Cohen", "profile_background_tile": false, "favourites_count": 0, "screen_name": "bramcohen", "url": "http://bramcohen.com/", "created_at": "Thu Nov 06 00:45:54 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": null}, "17220934": {"id": 17220934, "profile_sidebar_fill_color": "96bc59", "profile_text_color": "333333", "followers_count": 1946398, "protected": "false", "location": "Nashville, TN", "profile_background_color": "000000", "utc_offset": -28800, "statuses_count": 112, "description": "", "friends_count": 9, "profile_link_color": "1200E6", "profile_image_url": "http://a3.twimg.com/profile_images/533245685/AlPhoto_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme1/bg.png", "screen_name": "algore", "profile_background_tile": "false", "favourites_count": 0, "name": "Al Gore", "url": "http://algore.com/", "created_at": "Thu Nov 06 22:21:18 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "333333", "following": "true"}, "17461978": {"id": 17461978, "profile_sidebar_fill_color": "252429", "profile_text_color": "666666", "followers_count": 2606224, "protected": "false", "location": "CLEVELAND/EVERYWHERE", "profile_background_color": "ad071d", "utc_offset": -32400, "statuses_count": 2501, "description": "VERY QUOTATIOUS, I PERFORM RANDOM ACTS OF SHAQNESS", "friends_count": 564, "profile_link_color": "2FC2EF", "profile_image_url": "http://a3.twimg.com/profile_images/282574177/Shaq_avatar_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/20601281/shaq_back_Cavs_Twitter_33.jpg", "screen_name": "THE_REAL_SHAQ", "profile_background_tile": "false", "favourites_count": 1, "name": "THE_REAL_SHAQ", "url": "http://www.Facebook.com/Shaq", "created_at": "Tue Nov 18 10:27:25 +0000 2008", "time_zone": "Alaska", "profile_sidebar_border_color": "181A1E", "following": "true"}, "1527091": {"id": 1527091, "profile_sidebar_fill_color": "E3E2DE", "profile_text_color": "634047", "followers_count": 133, "protected": "true", "location": "San Mateo, CA", "profile_background_color": "EDECE9", "utc_offset": -28800, "statuses_count": 3317, "description": "", "friends_count": 167, "profile_link_color": "088253", "profile_image_url": "http://a3.twimg.com/profile_images/532790783/4111117620_2945c576c4_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259692436/images/themes/theme3/bg.gif", "name": "Cheston Lee", "profile_background_tile": "false", "favourites_count": 8, "screen_name": "Cheston", "url": null, "created_at": "Fri Sep 04 03:39:22 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "D3D2CF", "following": "true"}, "16206646": {"id": 16206646, "profile_sidebar_fill_color": "7AC3EE", "profile_text_color": "3D1957", "followers_count": 1150, "protected": "false", "location": "Rochester, NY", "profile_background_color": "642D8B", "utc_offset": -18000, "statuses_count": 2658, "description": "Executive Producer, 13WHAM News (addicted to news, always strive to do the right thing)", "friends_count": 1458, "profile_link_color": "FF0000", "profile_image_url": "http://a3.twimg.com/profile_images/538122949/ally_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/55266348/250px-Chanel-1.jpg", "screen_name": "AllisonWatts", "profile_background_tile": "true", "favourites_count": 2, "name": "AllisonWattsABC", "url": "http://www.linkedin.com/in/allisonkwatts", "created_at": "Tue Sep 09 18:15:56 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "65B0DA", "following": "true"}, "58527755": {"id": 58527755, "profile_sidebar_fill_color": "252429", "profile_text_color": "666666", "followers_count": 133, "protected": "false", "location": "", "profile_background_color": "1A1B1F", "utc_offset": -10800, "statuses_count": 159, "description": "prospecting promisingly good freeware games in development", "friends_count": 6, "profile_link_color": "2FC2EF", "profile_image_url": "http://a1.twimg.com/profile_images/523104070/logo2_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme9/bg.gif", "name": "PixelProspector.com", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "PixelProspector", "url": "http://www.pixelprospector.com/indev/", "created_at": "Thu Dec 03 21:05:30 +0000 2009", "time_zone": "Greenland", "profile_sidebar_border_color": "181A1E", "following": "true"}, "14643059": {"id": 14643059, "profile_sidebar_fill_color": "7decf0", "profile_text_color": "3a245c", "followers_count": 2019, "protected": "false", "location": "New Jersey", "profile_background_color": "000000", "utc_offset": -18000, "statuses_count": 520, "description": "I make games w/ stories: adventure, RPG, strategy, Zelda-like, experimental. Occasionally review for indiegames.com and TIGSource. Enjoys Starcraft.", "friends_count": 1615, "profile_link_color": "0099B9", "profile_image_url": "http://a3.twimg.com/profile_images/310344707/paupau_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/25262607/twitterbg.png", "screen_name": "rinkuhero", "profile_background_tile": "false", "favourites_count": 6, "name": "Paul Eres", "url": "http://StudioEres.com/games", "created_at": "Sun May 04 00:45:33 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "266366", "following": "true"}, "30258039": {"id": 30258039, "profile_sidebar_fill_color": "efefef", "profile_text_color": "333333", "followers_count": 112, "protected": "false", "location": "Rochester NY", "profile_background_color": "131516", "utc_offset": -18000, "statuses_count": 48, "description": "Water Street Music Hall...where the music comes alive", "friends_count": 111, "profile_link_color": "009999", "profile_image_url": "http://a1.twimg.com/profile_images/400346310/n86659589746_669_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme14/bg.gif", "screen_name": "waterstreetny", "profile_background_tile": "true", "favourites_count": 0, "name": "WaterStreetMusicHall", "url": "http://www.facebook.com/WaterStreetMusicHall", "created_at": "Fri Apr 10 16:58:00 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "eeeeee", "following": "true"}, "15938093": {"id": 15938093, "profile_sidebar_fill_color": "DAECF4", "profile_text_color": "663B12", "followers_count": 406, "protected": "false", "location": "Winnipeg, MB, Canada", "profile_background_color": "C6E2EE", "utc_offset": -21600, "statuses_count": 398, "description": "Indie Games by Alec 'n Friends!", "friends_count": 72, "profile_link_color": "1F98C7", "profile_image_url": "http://a1.twimg.com/profile_images/58698832/infamm_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme2/bg.gif", "screen_name": "infinite_ammo", "profile_background_tile": "false", "favourites_count": 3, "name": "Infinite Ammo", "url": "http://infiniteammo.ca", "created_at": "Thu Aug 21 22:53:21 +0000 2008", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "C6E2EE", "following": "true"}, "6793172": {"id": 6793172, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 934, "protected": "false", "location": "Gruaro", "profile_background_color": "9AE4E8", "utc_offset": -7200, "statuses_count": 53, "description": "", "friends_count": 1905, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/231128123/avatar_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "stefanogrini", "profile_background_tile": "false", "favourites_count": 2, "name": "Stefano Grini", "url": "http://www.jabbin.com", "created_at": "Wed Jun 13 15:53:06 +0000 2007", "time_zone": "Mid-Atlantic", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "19747295": {"id": 19747295, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 565, "protected": "false", "location": "Rochester, NY", "profile_background_color": "ffffff", "utc_offset": -21600, "statuses_count": 855, "description": "On September 12th, 2009 - An Official Twestival Local will take place. Shoot me your thoughts on which non-profit to benefit!", "friends_count": 531, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/372459039/twestivalicon180_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/33283205/Untitled-1twitterbackground.gif", "screen_name": "RocTwestival", "profile_background_tile": "false", "favourites_count": 28, "name": "@MatthewRay", "url": "http://www.facebook.com/pages/Rochester-Twestival/67457546600", "created_at": "Fri Jan 30 01:23:06 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "14749606": {"id": 14749606, "profile_sidebar_fill_color": "F4FAFF", "profile_text_color": "333333", "followers_count": 36868, "protected": "false", "location": "sf, ca", "profile_background_color": "f4faff", "utc_offset": -28800, "statuses_count": 150, "description": "File syncing, sharing and backup that rocks. ", "friends_count": 15, "profile_link_color": "1F75cc", "profile_image_url": "http://a1.twimg.com/profile_images/480262550/box_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/34066589/twitterbg.png", "name": "Dropbox", "profile_background_tile": "true", "favourites_count": 0, "screen_name": "Dropbox", "url": "http://dropbox.com", "created_at": "Thu Dec 03 21:57:21 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "C6E2EE", "following": "true"}, "16189858": {"id": 16189858, "profile_sidebar_fill_color": "f6dadf", "profile_text_color": "b30000", "followers_count": 27, "protected": "false", "location": "Springfield, Illinois", "profile_background_color": "FFFFFF", "utc_offset": -21600, "statuses_count": 56, "description": "Belief is a delusion.", "friends_count": 19, "profile_link_color": "006cc7", "profile_image_url": "http://a1.twimg.com/profile_images/59656364/logo1_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "atheinsurgency", "profile_background_tile": "false", "favourites_count": 0, "name": "atheinsurgency", "url": "http://www.atheistinsurgency.com", "created_at": "Mon Sep 08 19:22:32 +0000 2008", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "ff0000", "following": "true"}, "15074004": {"id": 15074004, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 620, "protected": "false", "location": "California", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 499, "description": "Of pipettes and pen tablets, tales of science and webcomics", "friends_count": 44, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/359725929/madmen_icon_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/17682787/twitter_background_2.jpg", "name": "Digital Unrest", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "digitalunrest", "url": "http://www.digitalunrestcomic.com", "created_at": "Thu Dec 03 21:05:04 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "14578741": {"id": 14578741, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 88, "protected": "false", "location": "London & Rochester, NY", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 138, "description": "Doctoral candidate at Henley Business School. Researching: virtual communities, social media, CRM, branding, lovemarks. Wife of the genius behind Digsby.com ", "friends_count": 49, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/462785347/Photo_on_2009-10-08_at_12.28__2_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "melshapiro", "profile_background_tile": "false", "favourites_count": 0, "name": "Melanie Shapiro", "url": "http://www.melanieshapiro.com", "created_at": "Tue Apr 29 01:43:43 +0000 2008", "time_zone": "Quito", "profile_sidebar_border_color": "87bc44", "following": "true"}, "9499402": {"id": 9499402, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 74010, "protected": "false", "location": "Rochester, NY", "profile_background_color": "9AE4E8", "utc_offset": -18000, "statuses_count": 3780, "description": "Digsby lets you manage all your IM, email, and social network accounts from one easy-to-use application.", "friends_count": 12, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/51448141/digsby_64x64_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259867295/images/themes/theme1/bg.png", "screen_name": "digsby", "profile_background_tile": "false", "favourites_count": 6, "name": "Digsby", "url": "http://www.digsby.com", "created_at": "Wed Oct 17 14:07:35 +0000 2007", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "7498162": {"id": 7498162, "profile_sidebar_fill_color": "E5507E", "profile_text_color": "362720", "followers_count": 32, "protected": "false", "location": "", "profile_background_color": "FF6699", "utc_offset": -18000, "statuses_count": 412, "description": "Boy.", "friends_count": 41, "profile_link_color": "B40B43", "profile_image_url": "http://a3.twimg.com/profile_images/331426761/eyes_normal_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme11/bg.gif", "name": "spongey", "profile_background_tile": "true", "favourites_count": 0, "screen_name": "spongey", "url": "http://chilledgreen.blogspot.com/", "created_at": "Wed Sep 09 03:29:33 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "CC3366", "following": "true"}, "19003372": {"id": 19003372, "profile_sidebar_fill_color": "141513", "profile_text_color": "6a6262", "followers_count": 3254, "protected": "false", "location": "Rochester, NY", "profile_background_color": "ccd1d1", "utc_offset": -21600, "statuses_count": 29, "description": "Everything Rochester", "friends_count": 3169, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/75326041/rochester_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme1/bg.png", "screen_name": "rochester", "profile_background_tile": "false", "favourites_count": 0, "name": "Rochester, New York", "url": "http://rochesterny.mobi", "created_at": "Thu Jan 15 00:24:19 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "636561", "following": "true"}, "15078306": {"id": 15078306, "verified": false, "profile_sidebar_fill_color": "dee3df", "profile_text_color": "635540", "followers_count": 567, "protected": false, "location": "SFCA", "profile_background_color": "BFBAC0", "utc_offset": -28800, "statuses_count": 5128, "description": "game designer so deal w/ it", "friends_count": 112, "profile_link_color": "558208", "profile_image_url": "http://a1.twimg.com/profile_images/536503198/meDDLM_normal.PNG", "notifications": false, "geo_enabled": false, "profile_background_image_url": "http://a1.twimg.com/profile_background_images/18080140/img_1598745_48577786_0.jpg", "screen_name": "fullbright", "profile_background_tile": true, "favourites_count": 2, "name": "Steve gaynor", "url": "http://fullbright.blogspot.com", "created_at": "Tue Jun 10 21:53:41 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "cfd3cf", "following": false}, "25598419": {"id": 25598419, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 1172, "protected": "false", "location": "", "profile_background_color": "9AE4E8", "utc_offset": -36000, "statuses_count": 138, "description": "Not really IGN, but people from Idle Thumbs. Will still blow you away.", "friends_count": 11, "profile_link_color": "0084B4", "profile_image_url": "http://a1.twimg.com/profile_images/105178306/ign_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/49206768/idlebackground.png", "screen_name": "igndotcom", "profile_background_tile": "true", "favourites_count": 2, "name": "IGN", "url": "http://www.idlethumbs.net/", "created_at": "Fri Mar 20 23:27:06 +0000 2009", "time_zone": "Hawaii", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "14842270": {"id": 14842270, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 32, "protected": "false", "location": "Seattle", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 11, "description": "Living in the intersection of music, sports and computers", "friends_count": 7, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/54487042/coke_headshot_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "scramblor", "profile_background_tile": "false", "favourites_count": 1, "name": "brandon wilson", "url": null, "created_at": "Tue May 20 05:11:43 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "29756727": {"id": 29756727, "profile_sidebar_fill_color": "E5507E", "profile_text_color": "362720", "followers_count": 411, "protected": "false", "location": "Sweden", "profile_background_color": "FF6699", "utc_offset": 3600, "statuses_count": 71, "description": "I'm a man.", "friends_count": 18, "profile_link_color": "B40B43", "profile_image_url": "http://a1.twimg.com/profile_images/129360964/3397510698_d52e438ea9_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme11/bg.gif", "name": "Jonatan S\u00f6derstr\u00f6m", "profile_background_tile": "true", "favourites_count": 0, "screen_name": "cactusquid", "url": "http://www.cactusquid.com/", "created_at": "Thu Dec 03 19:24:26 +0000 2009", "time_zone": "Brussels", "profile_sidebar_border_color": "CC3366", "following": "true"}, "28889701": {"id": 28889701, "profile_sidebar_fill_color": "302822", "profile_text_color": "ec4613", "followers_count": 1733, "protected": "false", "location": "Texas", "profile_background_color": "000000", "utc_offset": -21600, "statuses_count": 103, "description": "Official id Software twitter account for RAGE", "friends_count": 189, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/144839235/RAGE_twitter_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/23463383/afterimpact3.jpg", "screen_name": "RAGEgame", "profile_background_tile": "false", "favourites_count": 0, "name": "RAGEgame", "url": "http://www.aftertheimpact.com", "created_at": "Sat Apr 04 23:01:14 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "000000", "following": "true"}, "80700514": {"id": 80700514, "profile_sidebar_fill_color": "efefef", "profile_text_color": "333333", "followers_count": 80146, "protected": "false", "location": "NYC", "profile_background_color": "131516", "utc_offset": -28800, "statuses_count": 20, "description": "This is really Tracy Morgan", "friends_count": 26, "profile_link_color": "009999", "profile_image_url": "http://a1.twimg.com/profile_images/459795704/Tracy_20Morgan-SGY-002808_1__normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme14/bg.gif", "screen_name": "RealTracyMorgan", "profile_background_tile": "true", "favourites_count": 0, "name": "Tracy Morgan", "url": "http://twitter.com/RealTracyMorgan", "created_at": "Wed Oct 07 22:53:26 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "eeeeee", "following": "true"}, "12": {"id": 12, "profile_sidebar_fill_color": "EADEAA", "profile_text_color": "333333", "followers_count": 1492617, "protected": "false", "location": "NYC & San Francisco", "profile_background_color": "8B542B", "utc_offset": -28800, "statuses_count": 5890, "description": "Creator, Co-founder and Chairman of Twitter; CEO of Square.", "friends_count": 733, "profile_link_color": "9D582E", "profile_image_url": "http://a1.twimg.com/profile_images/54668082/Picture_2_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme8/bg.gif", "name": "Jack Dorsey", "profile_background_tile": "false", "favourites_count": 692, "screen_name": "jack", "url": null, "created_at": "Thu Dec 03 19:19:08 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "D9B17E", "following": "true"}, "16792453": {"id": 16792453, "profile_sidebar_fill_color": "b30909", "profile_text_color": "000000", "followers_count": 1878, "protected": "false", "location": "San Francisco", "profile_background_color": "232323", "utc_offset": -28800, "statuses_count": 1791, "description": "I'm Gamasutra's Editor at Large and a co-founder of Idle Thumbs. Yeah! Video games!", "friends_count": 144, "profile_link_color": "000000", "profile_image_url": "http://a3.twimg.com/profile_images/348215291/gafavatar_square_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/3202682/Background2.png", "name": "Chris Remo", "profile_background_tile": "false", "favourites_count": 1, "screen_name": "IdleThumbs", "url": "http://www.idlethumbs.net/", "created_at": "Thu Dec 03 19:54:57 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "000000", "following": "true"}, "14950071": {"id": 14950071, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 159, "protected": "false", "location": "Minnesota, USA", "profile_background_color": "9ae4e8", "utc_offset": -21600, "statuses_count": 686, "description": "", "friends_count": 51, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/54835222/74978-004_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "gutworth", "profile_background_tile": "false", "favourites_count": 0, "name": "Benjamin Peterson", "url": "http://pybites.blogspot.com", "created_at": "Fri May 30 00:55:41 +0000 2008", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "8126322": {"id": 8126322, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 3303, "protected": "false", "location": "Brooklyn", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 8070, "description": "Indigenous kicking thing \u2022 Editor of the Onion \u2022 'No. 8 All-Time'", "friends_count": 488, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/333787026/Joe3_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/55424225/burgertini.jpg", "name": "Joe Randazzo", "profile_background_tile": "true", "favourites_count": 166, "screen_name": "Randazzoj", "url": "http://www.therandazzo.com ", "created_at": "Thu Dec 03 20:27:03 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "14337372": {"id": 14337372, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 181, "protected": "false", "location": "Rochester, NY, USA", "profile_background_color": "000000", "utc_offset": -18000, "statuses_count": 1048, "description": "Digsby Dev", "friends_count": 86, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/52576235/DigsbyOLantern_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/2755098/Digsbytwitterbg.jpg", "name": "Aaron Costello", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "Brok3n_Halo", "url": "http://www.google.com/profiles/108296962338916324371", "created_at": "Tue Sep 22 21:59:37 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "27186158": {"id": 27186158, "profile_sidebar_fill_color": "f0f0f0", "profile_text_color": "333333", "followers_count": 406, "protected": "false", "location": "Houston, TX", "profile_background_color": "ffffff", "utc_offset": -21600, "statuses_count": 197, "description": "Most powerful web crawling service ever!", "friends_count": 156, "profile_link_color": "0071bc", "profile_image_url": "http://a1.twimg.com/profile_images/458025680/spider_-_blue_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/26704653/twitter_background.jpg", "screen_name": "80legs", "profile_background_tile": "false", "favourites_count": 1, "name": "80legs", "url": "http://www.80legs.com", "created_at": "Sat Mar 28 05:30:44 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "ffffff", "following": "true"}, "8517312": {"id": 8517312, "profile_sidebar_fill_color": "DDFFCC", "profile_text_color": "333333", "followers_count": 195, "protected": "false", "location": "New York", "profile_background_color": "9AE4E8", "utc_offset": -18000, "statuses_count": 1292, "description": "PR for @Digsby, Co-founder of @AntiHype. Technology enthusiast and university student aiming to change the world. Also loves going to concerts.", "friends_count": 210, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/410142897/n24414074_31190485_7235_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "name": "Erick", "profile_background_tile": "false", "favourites_count": 81, "screen_name": "erickd", "url": "http://erickd.tumblr.com/", "created_at": "Fri Sep 04 14:16:39 +0000 2009", "time_zone": "Quito", "profile_sidebar_border_color": "BDDCAD", "following": "true"}, "33837478": {"id": 33837478, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 3, "protected": "false", "location": "", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 0, "description": "", "friends_count": 5, "profile_link_color": "0000ff", "profile_image_url": "http://s.twimg.com/a/1259808911/images/default_profile_3_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "dwatters24", "profile_background_tile": "false", "favourites_count": 0, "name": "David Watters", "url": null, "created_at": "Tue Apr 21 06:54:40 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "48773664": {"id": 48773664, "profile_sidebar_fill_color": "95E8EC", "profile_text_color": "3C3940", "followers_count": 35205, "protected": "false", "location": "Los Angeles", "profile_background_color": "0099B9", "utc_offset": -28800, "statuses_count": 34, "description": "I is a twit now.", "friends_count": 4, "profile_link_color": "0099B9", "profile_image_url": "http://a1.twimg.com/profile_images/271575562/Photo_15_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme4/bg.gif", "screen_name": "TheCharlieDay", "profile_background_tile": "false", "favourites_count": 0, "name": "Charlie Day", "url": null, "created_at": "Fri Jun 19 17:47:25 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "5ED4DC", "following": "true"}, "19743487": {"id": 19743487, "profile_sidebar_fill_color": "99CC33", "profile_text_color": "3E4415", "followers_count": 50, "protected": "false", "location": "Washington, DC, USA", "profile_background_color": "352726", "utc_offset": -21600, "statuses_count": 259, "description": "Geospatial Analyst, Mac freak, Blackberry Storm / Tour owner, Avid Mountain and Road Biker / Kayaker", "friends_count": 61, "profile_link_color": "D02B55", "profile_image_url": "http://a1.twimg.com/profile_images/74117268/Photo_18_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1258674567/images/themes/theme5/bg.gif", "name": "Ian Buggey", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "GISdork", "url": null, "created_at": "Thu Sep 03 19:57:35 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "829D5E", "following": "true"}, "6253282": {"id": 6253282, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 26880, "protected": "false", "location": "San Francisco, CA", "profile_background_color": "c1dfee", "utc_offset": -28800, "statuses_count": 1233, "description": "The Real Twitter API. I tweet about API changes, service issues and happily answer questions about Twitter and our API. Don't get an answer? It's on my website.", "friends_count": 6, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/66883316/twitter_57_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/39216023/twitterapi-background-2.png", "screen_name": "twitterapi", "profile_background_tile": "false", "favourites_count": 0, "name": "Twitter API", "url": "http://apiwiki.twitter.com", "created_at": "Wed May 23 06:01:13 +0000 2007", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "64776896": {"id": 64776896, "profile_sidebar_fill_color": "efefef", "profile_text_color": "333333", "followers_count": 469, "protected": "false", "location": "California", "profile_background_color": "71a9f2", "utc_offset": -28800, "statuses_count": 78, "description": "Let's Be Friends! ^_^", "friends_count": 61, "profile_link_color": "009999", "profile_image_url": "http://a1.twimg.com/profile_images/359736382/nicinc_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/51821861/ncls_bg.png", "screen_name": "nicalis", "profile_background_tile": "false", "favourites_count": 0, "name": "Nicalis Inc.", "url": "http://www.nicalis.com", "created_at": "Tue Aug 11 17:39:53 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "eeeeee", "following": "true"}, "54450488": {"id": 54450488, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 15, "protected": "false", "location": "", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 27, "description": "", "friends_count": 14, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/357571924/Photo_092408_002_normal.JPG", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "name": "Michael St. Martin", "profile_background_tile": "false", "favourites_count": 1, "screen_name": "stmartinmj", "url": null, "created_at": "Tue Sep 01 03:15:37 +0000 2009", "time_zone": "Quito", "profile_sidebar_border_color": "87bc44", "following": "true"}, "15485441": {"id": 15485441, "profile_sidebar_fill_color": "DAECF4", "profile_text_color": "663B12", "followers_count": 2292144, "protected": "false", "location": "New York, New York", "profile_background_color": "C6E2EE", "utc_offset": -18000, "statuses_count": 1712, "description": "Snork. ", "friends_count": 161, "profile_link_color": "1F98C7", "profile_image_url": "http://a3.twimg.com/profile_images/66869845/AJIMMY2_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme2/bg.gif", "screen_name": "jimmyfallon", "profile_background_tile": "false", "favourites_count": 7, "name": "Jimmy Fallon", "url": "http://www.latenightwithjimmyfallon.com", "created_at": "Fri Jul 18 19:46:50 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "C6E2EE", "following": "true"}, "14587137": {"id": 14587137, "profile_sidebar_fill_color": "FDF5D5", "profile_text_color": "444444", "followers_count": 47, "protected": "false", "location": "Charlotte, NC", "profile_background_color": "CEE3E6", "utc_offset": -18000, "statuses_count": 228, "description": "", "friends_count": 50, "profile_link_color": "839124", "profile_image_url": "http://a3.twimg.com/profile_images/112148423/n24406958_30892585_6920_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/12720478/bg_twittergallery.jpg", "name": "Jess", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "jessLDavis", "url": null, "created_at": "Tue Nov 03 21:55:10 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "839124", "following": "true"}, "15165502": {"id": 15165502, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 17739, "protected": "false", "location": "Mars: Gusev Crater & Meridiani", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 266, "description": "Roaming the Red Planet on six wheels. The official mission Twitter of Spirit and Opportunity", "friends_count": 4, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/59121811/rover1_th50_normal.gif", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/2951594/Twitter_BG_5.jpg", "screen_name": "MarsRovers", "profile_background_tile": "false", "favourites_count": 2, "name": "Spirit and Oppy ", "url": "http://marsrovers.jpl.nasa.gov", "created_at": "Thu Jun 19 03:18:45 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "54645160": {"id": 54645160, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 1494, "protected": "false", "location": "Arlington, VA", "profile_background_color": "9ae4e8", "utc_offset": -18000, "statuses_count": 17, "description": "Defense Advanced Research Projects Agency", "friends_count": 8, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/495213237/logo-for-twitter_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "screen_name": "DARPA_News", "profile_background_tile": "false", "favourites_count": 0, "name": "DARPA", "url": "http://www.darpa.mil", "created_at": "Tue Jul 07 19:13:34 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "788245": {"id": 788245, "profile_sidebar_fill_color": "c2f3fa", "profile_text_color": "000000", "followers_count": 3085, "protected": "false", "location": "Silicon Valley, CA", "profile_background_color": "C6E2EE", "utc_offset": -28800, "statuses_count": 4499, "description": "Used Protoculture Salesman. Elite Developer of Fluid, Cruz and ParseKit.", "friends_count": 680, "profile_link_color": "1f90c7", "profile_image_url": "http://a3.twimg.com/profile_images/484085257/tod_head_big_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/53330536/Mark7.jpg", "screen_name": "iTod", "profile_background_tile": "true", "favourites_count": 778, "name": "Todd Ditchendorf", "url": "http://parsekit.com", "created_at": "Thu Feb 22 08:30:53 +0000 2007", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "C6E2EE", "following": "true"}, "14204918": {"id": 14204918, "profile_sidebar_fill_color": "EBEBEB", "profile_text_color": "000000", "followers_count": 651, "protected": "false", "location": "Brooklyn, NY", "profile_background_color": "f2f9fb", "utc_offset": -18000, "statuses_count": 6740, "description": "Director of Consumer Strategy at carrot creative", "friends_count": 495, "profile_link_color": "F39840", "profile_image_url": "http://a1.twimg.com/profile_images/480297708/maury_100_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/11152962/maurynew.png", "screen_name": "mopostal", "profile_background_tile": "false", "favourites_count": 20, "name": "Maury Postal", "url": "http://carrotcreative.com", "created_at": "Mon Feb 02 17:19:14 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "CCCCCC", "following": "true"}, "15983724": {"id": 15983724, "profile_sidebar_fill_color": "252429", "profile_text_color": "666666", "followers_count": 497, "protected": "false", "location": "Indiana", "profile_background_color": "1A1B1F", "utc_offset": -18000, "statuses_count": 224, "description": "Like VI or VIM? So do we, so use it more effectively with daily tips", "friends_count": 18, "profile_link_color": "1f9e10", "profile_image_url": "http://a3.twimg.com/profile_images/64545277/vim_logo_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259867295/images/themes/theme9/bg.gif", "screen_name": "vimtips", "profile_background_tile": "false", "favourites_count": 0, "name": "vimtips", "url": null, "created_at": "Mon Aug 25 16:47:56 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "181A1E", "following": "true"}, "15029296": {"id": 15029296, "profile_sidebar_fill_color": "E0FF91", "profile_text_color": "000000", "followers_count": 2924, "protected": "false", "location": "San Francisco, CA", "profile_background_color": "FFFFFF", "utc_offset": -18000, "statuses_count": 3991, "description": "Guitars and code. That's it.", "friends_count": 203, "profile_link_color": "0000FF", "profile_image_url": "http://a1.twimg.com/profile_images/55125844/empty_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/2604594/empty.png", "name": "zedshaw", "profile_background_tile": "true", "favourites_count": 0, "screen_name": "zedshaw", "url": "http://www.zedshaw.com/", "created_at": "Thu Dec 03 16:37:33 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "14693823": {"id": 14693823, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 45515, "protected": "false", "location": "Mars, Solar System", "profile_background_color": "9ae4e8", "utc_offset": -28800, "statuses_count": 726, "description": "I dig Mars! ", "friends_count": 4, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/55133915/PIA09942_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/3069906/PSP_008591_2485_RGB_Lander_Detail_516-387.jpg", "screen_name": "MarsPhoenix", "profile_background_tile": "true", "favourites_count": 6, "name": "MarsPhoenix", "url": "http://tinyurl.com/5wwaru", "created_at": "Thu May 08 00:17:54 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "13334762": {"id": 13334762, "profile_sidebar_fill_color": "dddddd", "profile_text_color": "000000", "followers_count": 8880, "protected": "false", "location": "San Francisco, CA", "profile_background_color": "eeeeee", "utc_offset": -28800, "statuses_count": 1111, "description": "Social coding? Pretty awesome.", "friends_count": 9, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/53128678/octocat_fluid_normal.png", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/4229589/header_bg.png", "screen_name": "github", "profile_background_tile": "false", "favourites_count": 13, "name": "GitHub", "url": "http://github.com", "created_at": "Mon Feb 11 04:41:50 +0000 2008", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "bbbbbb", "following": "true"}, "752673": {"id": 752673, "verified": false, "profile_sidebar_fill_color": "F2EEBB", "profile_text_color": "333333", "followers_count": 22991, "protected": false, "location": "Somerville, MA", "profile_background_color": "8B542B", "utc_offset": -18000, "statuses_count": 2222, "description": "Creator of jQuery, JavaScript programmer, blogger, author, work for Mozilla.", "friends_count": 576, "profile_link_color": "F13D38", "profile_image_url": "http://a1.twimg.com/profile_images/150604006/3450728563_69b0bd0743_normal.jpg", "notifications": false, "geo_enabled": false, "profile_background_image_url": "http://a1.twimg.com/profile_background_images/57396444/x7511eb8ec62bb8da1b0c61888eb01d0.png", "screen_name": "jeresig", "profile_background_tile": true, "favourites_count": 0, "name": "John Resig", "url": "http://ejohn.org/", "created_at": "Sat Feb 03 20:17:32 +0000 2007", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "F2EEBB", "following": true}, "19757371": {"id": 19757371, "profile_sidebar_fill_color": "c7c7c7", "profile_text_color": "595959", "followers_count": 2701441, "protected": "false", "location": "USA", "profile_background_color": "030303", "utc_offset": -28800, "statuses_count": 2627, "description": "Recording/Sandwich Artist", "friends_count": 70, "profile_link_color": "c72c2c", "profile_image_url": "http://a1.twimg.com/profile_images/378590406/twitterpic1_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/43692840/Picture_11_copy.png", "name": "John Mayer", "profile_background_tile": "false", "favourites_count": 7, "screen_name": "johncmayer", "url": "http://www.johnmayer.com", "created_at": "Thu Dec 03 20:54:11 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "000000", "following": "true"}, "15164565": {"id": 15164565, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 23676, "protected": "false", "location": "", "profile_background_color": "670033", "utc_offset": -18000, "statuses_count": 2714, "description": "What Slate is reading and discussing", "friends_count": 60, "profile_link_color": "0000ff", "profile_image_url": "http://a3.twimg.com/profile_images/57789927/s_normal.gif", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259808911/images/themes/theme1/bg.png", "name": "Slate", "profile_background_tile": "false", "favourites_count": 1, "screen_name": "Slate", "url": "http://www.slate.com", "created_at": "Thu Dec 03 18:07:24 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "40113147": {"id": 40113147, "profile_sidebar_fill_color": "ffffff", "profile_text_color": "333333", "followers_count": 178, "protected": "false", "location": "", "profile_background_color": "526e6f", "utc_offset": -18000, "statuses_count": 32, "description": "The tiny but swingin' javascript framework.", "friends_count": 6, "profile_link_color": "0084B4", "profile_image_url": "http://a3.twimg.com/profile_images/213252187/sammy_davis_jr_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1258674567/images/themes/theme1/bg.png", "screen_name": "sammy_js", "profile_background_tile": "false", "favourites_count": 0, "name": "Sammy", "url": "http://code.quirkey.com/sammy", "created_at": "Thu May 14 23:11:41 +0000 2009", "time_zone": "Quito", "profile_sidebar_border_color": "e1e8de", "following": "true"}, "16788900": {"id": 16788900, "profile_sidebar_fill_color": "bfbfbf", "profile_text_color": "444444", "followers_count": 1819, "protected": "false", "location": "Rochester, NY", "profile_background_color": "050505", "utc_offset": -18000, "statuses_count": 305, "description": "International Museum of Photography & Film", "friends_count": 759, "profile_link_color": "000000", "profile_image_url": "http://a3.twimg.com/profile_images/62148433/GEH-blackandwhite_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/29375036/twitter.jpg", "screen_name": "EastmanHouse", "profile_background_tile": "false", "favourites_count": 3, "name": "EastmanHouse", "url": "http://www.eastmanhouse.org", "created_at": "Wed Oct 15 18:50:56 +0000 2008", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "ffffff", "following": "true"}, "44570946": {"id": 44570946, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 40356, "protected": "false", "location": null, "profile_background_color": "9ae4e8", "utc_offset": null, "statuses_count": 169, "description": null, "friends_count": 5, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/260989298/Twitter_1_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259091217/images/themes/theme1/bg.png", "screen_name": "MrTeller", "profile_background_tile": "false", "favourites_count": 0, "name": "Teller", "url": null, "created_at": "Thu Jun 04 07:54:44 +0000 2009", "time_zone": null, "profile_sidebar_border_color": "87bc44", "following": "true"}, "74842285": {"id": 74842285, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 5, "protected": "false", "location": null, "profile_background_color": "9ae4e8", "utc_offset": null, "statuses_count": 30, "description": null, "friends_count": 23, "profile_link_color": "0000ff", "profile_image_url": "http://s.twimg.com/a/1259704211/images/default_profile_0_normal.png", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme1/bg.png", "screen_name": "piratedigsby", "profile_background_tile": "false", "favourites_count": 0, "name": "pirate digsby", "url": null, "created_at": "Wed Sep 16 21:24:57 +0000 2009", "time_zone": null, "profile_sidebar_border_color": "87bc44", "following": "true"}, "19297751": {"id": 19297751, "profile_sidebar_fill_color": "DAECF4", "profile_text_color": "663B12", "followers_count": 348, "protected": "false", "location": "", "profile_background_color": "C6E2EE", "utc_offset": -21600, "statuses_count": 117, "description": "learn git one commit at a time. updates weekly or when you send in awesome tips! brought to you by @qrush", "friends_count": 2, "profile_link_color": "1F98C7", "profile_image_url": "http://a1.twimg.com/profile_images/72335082/2776359417_fa33869cd0_o_normal.png", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/3999190/bandwidth.gif", "screen_name": "gitready", "profile_background_tile": "true", "favourites_count": 0, "name": "Git Ready", "url": "http://gitready.com", "created_at": "Wed Jan 21 17:45:47 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "C6E2EE", "following": "true"}, "12597722": {"id": 12597722, "profile_sidebar_fill_color": "e0ff92", "profile_text_color": "000000", "followers_count": 939, "protected": "false", "location": "Bangor, Maine, USA", "profile_background_color": "9ae4e8", "utc_offset": -21600, "statuses_count": 974, "description": "Person behind Hog Bay Software (WriteRoom and TaskPaper)", "friends_count": 68, "profile_link_color": "0000ff", "profile_image_url": "http://a1.twimg.com/profile_images/45870602/jesse_normal.jpg", "notifications": "false", "profile_background_image_url": "http://s.twimg.com/a/1259704211/images/themes/theme1/bg.png", "name": "Jesse Grosjean", "profile_background_tile": "false", "favourites_count": 0, "screen_name": "jessegrosjean", "url": "http://hogbaysoftware.com", "created_at": "Thu Dec 03 17:42:03 +0000 2009", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "87bc44", "following": "true"}, "713263": {"id": 713263, "profile_sidebar_fill_color": "fff", "profile_text_color": "000", "followers_count": 4875, "protected": "false", "location": "sf", "profile_background_color": "fff", "utc_offset": -28800, "statuses_count": 7105, "description": "I work at GitHub", "friends_count": 5239, "profile_link_color": "000", "profile_image_url": "http://a1.twimg.com/profile_images/500320366/Desktop_2_normal.gif", "notifications": "false", "profile_background_image_url": "http://a3.twimg.com/profile_background_images/2888965/smaller.jpg", "name": "Chris Wanstrath", "profile_background_tile": "false", "favourites_count": 357, "screen_name": "defunkt", "url": "http://defunkt.github.com/", "created_at": "Thu Dec 03 16:51:01 +0000 2009", "time_zone": "Pacific Time (US & Canada)", "profile_sidebar_border_color": "fff", "following": "true"}, "19123124": {"id": 19123124, "profile_sidebar_fill_color": "EADEAA", "profile_text_color": "333333", "followers_count": 633, "protected": "false", "location": "Rochester, NY", "profile_background_color": "b25310", "utc_offset": -18000, "statuses_count": 1150, "description": "Rochester's destination for fine craftbeer and fresh pub food. Twitter updates by Joe McBane.", "friends_count": 110, "profile_link_color": "9D582E", "profile_image_url": "http://a3.twimg.com/profile_images/254985171/photo_normal.jpg", "notifications": "false", "profile_background_image_url": "http://a1.twimg.com/profile_background_images/32448146/taptiled.jpg", "name": "TapandMallet", "profile_background_tile": "true", "favourites_count": 0, "screen_name": "TapandMallet", "url": "http://www.tapandmallet.com", "created_at": "Thu Dec 03 21:24:00 +0000 2009", "time_zone": "Eastern Time (US & Canada)", "profile_sidebar_border_color": "D9B17E", "following": "true"}, "14089195": {"id": 14089195, "verified": false, "profile_sidebar_fill_color": "171717", "profile_text_color": "456e87", "followers_count": 1355036, "protected": false, "location": "Chicago/Brooklyn", "profile_background_color": "000000", "utc_offset": -21600, "statuses_count": 2797, "description": "The essential guide to independent music and beyond.", "friends_count": 271, "profile_link_color": "458a64", "profile_image_url": "http://a3.twimg.com/profile_images/89697603/pforklogo_normal.png", "notifications": false, "geo_enabled": false, "profile_background_image_url": "http://a3.twimg.com/profile_background_images/24472025/IMG_1729.jpg", "screen_name": "pitchforkmedia", "profile_background_tile": false, "favourites_count": 7, "name": "Pitchfork", "url": "http://pitchfork.com", "created_at": "Thu Mar 06 15:34:41 +0000 2008", "time_zone": "Central Time (US & Canada)", "profile_sidebar_border_color": "030303", "following": true}} \ No newline at end of file diff --git a/digsby/src/plugins/twitter/unittests/test_autocomplete.py b/digsby/src/plugins/twitter/unittests/test_autocomplete.py new file mode 100644 index 0000000..0307e06 --- /dev/null +++ b/digsby/src/plugins/twitter/unittests/test_autocomplete.py @@ -0,0 +1,32 @@ +import wx + +def sample_users(): + from os.path import dirname, abspath, join as pathjoin + with open(pathjoin(dirname(abspath(__file__)), 'sample_users.json')) as f: + users_json = f.read() + import simplejson + return simplejson.loads(users_json) + + +def main(): + from tests.testapp import testapp + + with testapp(skinname='Windows 7'): + f = wx.Frame(None, size=(400, 140)) + from twitter.twitter_gui import TwitterInputBoxBase + #screen_names = sorted_screen_names(sample_users()) + users = sorted(sample_users().values(), key=lambda user: user['screen_name'].lower()) + t = TwitterInputBoxBase(f) + + from twitter.twitter_gui import TwitterAutoCompleteController + + def complete(): + from gui.autocomplete import autocomplete + from twitter.twitter_gui import TwitterSearchResult + t.a = autocomplete(t, [TwitterSearchResult(u) for u in users], TwitterAutoCompleteController(users)) + + f.Show() + +if __name__ == '__main__': + main() + diff --git a/digsby/src/plugins/twitter/unittests/test_linkify.js b/digsby/src/plugins/twitter/unittests/test_linkify.js new file mode 100644 index 0000000..535330c --- /dev/null +++ b/digsby/src/plugins/twitter/unittests/test_linkify.js @@ -0,0 +1,28 @@ +LinkifyTest = TestCase('LinkifyTest'); + +LinkifyTest.prototype.testLinkify = function() { + function link(url) { + return '' + url + ''; + } + + function check(expected, s) { + assertEquals(expected, linkify(s)); + } + + check('pics...' + link('http://bit.ly/3PxLCn'), + 'pics...http://bit.ly/3PxLCn'); + + check('check it out ' + link('http://digs.by/a'), + 'check it out http://digs.by/a') + + //unicode checks disabled--something with loading utf8 files is wrong? + + //check(' ' + link('http://google.com') + ' ', + //' http://google.com '); + + // this example comes from b150107. the spaces before and after the link are "full width spaces" and should not + // be considered part of the link. + //check('がぬいぐるみ化 ' + link('http://journal.mycom.co.jp/news/2010/10/20/008/index.html') + ' あら', + // 'がぬいぐるみ化 http://journal.mycom.co.jp/news/2010/10/20/008/index.html あら'); +}; + diff --git a/digsby/src/plugins/twitter/unittests/test_timeline.js b/digsby/src/plugins/twitter/unittests/test_timeline.js new file mode 100644 index 0000000..5344b78 --- /dev/null +++ b/digsby/src/plugins/twitter/unittests/test_timeline.js @@ -0,0 +1,111 @@ +TimelineTest = TestCase('TimelineTest'); + +function MockAccount() { +} + +MockAccount.prototype.isSelfTweet = function(t) { return false; } + +TimelineTest.prototype.testTimeline = function() { + /*:DOC +=
*/ + + var container = document.getElementById('container'); + assertNotNull(container); + + var skin = new SmoothOperator(document); + + var t = new Timeline(container, skin); + + var account = t.account = new MockAccount(); + + + return; // broken since the advent of timeline2.js + + var tweets = []; + t.insertSorted(tweets); + + var children = container.childNodes; + assertEquals(0, children.length); + + arrayExtend(tweets, [ + {id: 7, created_at_ms: 122, text: 'test2'}, + {id: 3, created_at_ms: 123, text: 'test'}, + ]); + + t.dataMap = account.tweets = {}; + + function addTweets(tweets) { + $.each(tweets, function(i, t) { + account.tweets[t.id] = t; + }); + } + + addTweets(tweets); + + t.insertSorted(tweets); + + assertEquals(2, container.childNodes.length); + assertEquals(7, children[0].id); + assertEquals(3, children[1].id); +} + +TimelineTest.prototype.testFeedView = function() { + var self = this; + function letterId(letter) { return 'letter_' + letter; } + function toNode(letter) { + var div = document.createElement('div'); + div.id = letterId(letter); + div.innerHTML = letter; + div.title = letter; + return div; + } + function nodeKey(letterDiv) { + return letterDiv.title; + } + function itemKey(letter) { return letter; } + + + function makeFeedView() { + var container = document.createElement('div'); + document.body.appendChild(container) + return new FeedView(container, toNode, letterId, nodeKey, itemKey); + } + + var f = makeFeedView(); + + function getVisual(feedView) { + var visual = []; + var children = feedView.container.childNodes; + for (var i = 0; i < children.length; ++i) { + visual.push(children[i].title); + } + return visual; + } + + function syncAndCheck(feed, items, resetStats) { + if (resetStats || resetStats === undefined) + feed.resetStats(); + + feed.sync(items); + assertEquals(getVisual(feed), items); + } + + syncAndCheck(f, ['A', 'B', 'D']); + assertEquals(1, f.stats.inserts); + + syncAndCheck(f, ['E']); + assertEquals(3, f.stats.deletes); + assertEquals(1, f.stats.inserts); + + f2 = makeFeedView(); + syncAndCheck(f2, ['D', 'E', 'F']); + + syncAndCheck(f2, ['A', 'B', 'C', 'D', 'E']); + assertEquals(1, f2.stats.inserts); + assertEquals(1, f2.stats.deletes); + + syncAndCheck(f2, ['C']); + syncAndCheck(f2, ['C', 'E', 'F']); + assertEquals(1, f2.stats.inserts); + assertEquals(0, f2.stats.deletes); +} + diff --git a/digsby/src/plugins/twitter/unittests/test_twitter.js b/digsby/src/plugins/twitter/unittests/test_twitter.js new file mode 100644 index 0000000..5e57a88 --- /dev/null +++ b/digsby/src/plugins/twitter/unittests/test_twitter.js @@ -0,0 +1,49 @@ +TwitterTest = TestCase('TwitterTest'); + +TwitterTest.prototype.testAtify = function() { + function nameLink(name) { + return '@' + name + ''; + } + + function testOneName(name) { + assertEquals(nameLink(name), atify('@' + name)); + } + + testOneName('digsby'); + testOneName('r2d2'); + testOneName('_why'); + + assertEquals('Name in a sentence ' + nameLink('elvis') + '.', + atify('Name in a sentence @elvis.')); +}; + +TwitterTest.prototype.testHashify = function() { + function searchLink(term) { + return '#' + term + ''; + } + + assertEquals(searchLink('hashtag'), hashify('#hashtag')); + assertEquals(searchLink('with4numbers'), hashify('#with4numbers')); + assertEquals('http://google.com/#test', hashify('http://google.com/#test')); + assertEquals('http://google.com/#test', twitterLinkify('http://google.com/#test')); +}; + +TwitterTest.prototype.testCompareIds = function() { + assertEquals('comparing 1 and 2', compareIds('1', '2'), -1); + assertEquals('comparing 2 and 1', compareIds('2', '1'), 1); + + assertEquals('comparing 3 and 3', compareIds('3', '3'), 0); + + assertEquals('checking to see if strip leading strips zeroes', stripLeading('0000123'), '123'); + + assertEquals('cmp big to small', 1, compareIds('10705970246197248', '9999625058521088')); + assertEquals('cmp small to big', -1, compareIds('9999625058521088', '10705970246197248')); + + assertEquals('cmp number to string', 1, compareIds(5, '4')); + assertEquals('cmp number to string', -1, compareIds(5, '70')); + assertEquals('cmp number to string', 0, compareIds(5, '5')); + + assertEquals('cmp number to number', 1, compareIds(5, 3)); +} + diff --git a/digsby/src/plugins/twitter/unittests/test_twitter.py b/digsby/src/plugins/twitter/unittests/test_twitter.py new file mode 100644 index 0000000..568cd9c --- /dev/null +++ b/digsby/src/plugins/twitter/unittests/test_twitter.py @@ -0,0 +1,48 @@ +from tests import TestCase, test_main + +class TestTwitter(TestCase): + def test_twitter(self): + import twitter.twitter_util as tu + l = tu.twitter_linkify + + # don't linkify #1, etc + self.assertEquals("we're #1", l("we're #1")) + + self.assertEquals('test #foo', + l('test #foo')) + + self.assertEquals('@ninjadigsby', + l('@ninjadigsby')) + + self.assertEquals('@ninjadigsby/test', + l('@ninjadigsby/test')) + + self.assertEquals('this is a @test.', + l('this is a @test.')) + + self.assertEquals('L.A. http://www.creationent.com/cal/serenity.htm#events Even', + l('L.A. http://www.creationent.com/cal/serenity.htm#events Even')) + + self.assertEquals('http://google.com/#test', + l('http://google.com/#test')) + + hashtag_linkify = tu.hashtag_linkify + + self.expect_equal('yo #dawg', + hashtag_linkify('yo #dawg')) + + self.expect_equal('test #dash-tag', + hashtag_linkify('test #dash-tag')) + + self.expect_equal('test #underscore_tag', + hashtag_linkify('test #underscore_tag')) + + self.expect_equal('test #with_numbers123', + hashtag_linkify('test #with_numbers123')) + + self.expect_equal( u'daring fireball shortener: http://\u272adf.ws/g8g', + l(u'daring fireball shortener: http://\u272adf.ws/g8g')) + + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/prefs/__init__.py b/digsby/src/prefs/__init__.py new file mode 100644 index 0000000..d758e87 --- /dev/null +++ b/digsby/src/prefs/__init__.py @@ -0,0 +1,2 @@ +from prefsdata import * +from prefsgui import * \ No newline at end of file diff --git a/digsby/src/prefs/localdefaults.py b/digsby/src/prefs/localdefaults.py new file mode 100644 index 0000000..057ecea --- /dev/null +++ b/digsby/src/prefs/localdefaults.py @@ -0,0 +1,47 @@ +''' +The __dict__ attribute of this module is used to back the localprefs dictionary (as defaults). + +if localprefs gets something from this that is callable, it calls it (with no arguments). otherwise it just returns +whatever value it found. +''' + +def save_to_dir(): + import wx, gui.native + gui.native.extendStdPaths() + + import stdpaths + return stdpaths.userdesktop + +def chatlogdir(): + import wx, gui.native + gui.native.extendStdPaths() + + import stdpaths + from common import pref + return pref('log.outputdir') or stdpaths.documents + +buddylist_dock_enabled = False +buddylist_dock_autohide = False +buddylist_dock_revealms = 300 + +def get_research_enabled(): + import wx + from common import pref + had_prefs = wx.GetApp().local_settings_exist_at_startup + if had_prefs: + value = pref('research.enabled', default = True) + else: + value = pref('research.last_known_local', default = False) + _set_research_pref(value) + + return value + +def _set_research_pref(val): + import common + common.setprefif('research.enabled', val) + common.setprefif('research.last_known_local', val) + + return val + +def set_research_enabled(val): + return _set_research_pref(val) diff --git a/digsby/src/prefs/prefsdata.py b/digsby/src/prefs/prefsdata.py new file mode 100644 index 0000000..9fa72aa --- /dev/null +++ b/digsby/src/prefs/prefsdata.py @@ -0,0 +1,212 @@ +from __future__ import with_statement +import os.path, sys +import util +from util import dictadd, is_all +from util.observe import observable_dict + +import syck +from logging import getLogger; log = getLogger('PrefsData') +info = log.info; warning = log.warning + +def flatten(mapping, parent_name=""): + """ + Flattens a mapping tree so that all leaf nodes appears as tuples in a list + containing a path and a value, like: + + >>> flatten({'a': 5, 'b': {'c': 6, 'd': 7}}) + [('a', 5), ('b.c', 6), ('b.d', 7)] + """ + + if not mapping: return [] + + result_list = [] + for key, value in mapping.iteritems(): + path = parent_name + key +# print path + + if isinstance(value, dict) or hasattr(key, 'iteritems'): + result_list.extend( flatten(value, path + '.') ) + else: + result_list.append( (path, value) ) + + return result_list + +def inflate(mappings_list, root=None, dict_factory=dict): + """ + Expands a list of tuples containing paths and values into a mapping tree. + + >>> inflate([('some.long.path.name','value')]) + {'some': {'long': {'path': {'name': 'value'}}}} + """ + root_map = root or dict_factory() + + for path, value in mappings_list: + path = path.split('.') + + # traverse the dotted path, creating new dictionaries as needed + parent = root_map + for name in path: + #if path.index(name) == len(path) - 1: + if path[-1] == name: + # this is the last name in the path - set the value! + parent[name] = value + else: + # this is not a leaf, so create an empty dictionary if there + # isn't one there already + parent.setdefault(name, dict_factory()) + parent = parent[name] + + return root_map + +nice_type_names = { + unicode: 'unicode', + str: "str", + bool: "boolean", + int: "integer", + list: "list", + float: "float", + dict: 'dict', + type(None):'none', +} + +from util import dictreverse +nice_name_types = dictreverse(nice_type_names) + +__localprefs = None + +class localprefprop(object): + ''' + Property stored as a local pref. + + "key" can be a string key or a callable with one argument--the object + the property is being called on--that returns a string key used for lookup + and storage in the local prefs dictionary. + ''' + def __init__(self, key, default): + if isinstance(key, basestring): + key = lambda s: key + + assert callable(key) + self.key = key + self.default = default + + def __get__(self, obj, objtype=None): + try: + return localprefs()[self.key(obj)] + except KeyError: + return self.default + + def __set__(self, obj, val): + localprefs()[self.key(obj)] = val + +def defaultprefs(): + ''' + Returns the default prefs as a Storage object + ''' + import prefs + import stdpaths + import config + + the_prefs = util.Storage() + + resdir = os.path.join(util.program_dir(), 'res') + filenames = [ + os.path.join(resdir, 'defaults.yaml'), + os.path.join(resdir, config.platformName, 'defaults.yaml'), + stdpaths.config / 'prefs.yaml', + stdpaths.userdata / 'prefs.yaml', + ] + + for filename in filenames: + info('loading prefs from %r', filename) + + try: + with open(filename, 'rb') as f: + prefdict = syck.load(f) + except Exception, e: + log.info('Error loading prefs from %r: %r', filename, e) + continue + + if not isinstance(prefdict, dict): + continue + + if not sys.DEV: + prefdict.pop('debug', None) + + prefdict = prefs.flatten(prefdict) + the_prefs.update(prefdict) + + return the_prefs + +def localprefs(): + # Returns a dictionary of local prefs for the CURRENT ACTIVE profile. If you still have a reference + # to the return value of this function when the CURRENT ACTIVE profile changes you may mess things up. + + global __localprefs + + import common + if __localprefs is not None and __localprefs.name == common.profile.name: + return __localprefs + + from gui.toolbox import local_settings + from util.observe import ObservableDict as SavingDictBase + ls = local_settings() + + _section = lambda: 'localprefs %s' % common.profile.name + + section = _section() + if not ls.has_section(section): + ls.add_section(section) + + class SavingDict(SavingDictBase): + import localdefaults + name = common.profile.name + + def __init__(self, save_func, *a, **k): + self.save = save_func + SavingDictBase.__init__(self, *a, **k) + + def __setitem__(self, key, val): + set_func = getattr(self.localdefaults, 'set_%s' % key.replace('.', '_'), None) + if set_func is not None: + log.info('calling localpref setter function: %r', set_func) + val = set_func(val) + + SavingDictBase.__setitem__(self, key, val) + self.save(self) + + def __delitem__(self, key): + SavingDictBase.__delitem__(self, key) + self.save(self) + + def __getitem__(self, key): + try: + return SavingDictBase.__getitem__(self, key) + except KeyError, e: + try: + v = getattr(self.localdefaults, key.replace('.', '_')) + except AttributeError: + try: + get_func = getattr(self.localdefaults, 'get_%s' % key.replace('.', '_')) +# log.info('calling localpref getter function: %r', get_func) + v = get_func() + except AttributeError: + raise e + + return v() if callable(v) else v + + def save(d): + defaults = type(d)(save) + d = d.copy() + for k in d.keys(): + try: + if d[k] == defaults[k]: + d.pop(k) + except (KeyError, AttributeError) as _e: + pass + ls._sections[_section()] = d + ls.save() + + __localprefs = SavingDict(save, ls.iteritems(section)) + + return __localprefs diff --git a/digsby/src/prefs/prefsgui.py b/digsby/src/prefs/prefsgui.py new file mode 100644 index 0000000..e137873 --- /dev/null +++ b/digsby/src/prefs/prefsgui.py @@ -0,0 +1,331 @@ +''' + +advanced prefs editor + +''' + +from __future__ import with_statement +from util import dictadd, boolify as bool_from_string, is_all +import wx.lib.mixins.listctrl +from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin +from wx import VERTICAL, HORIZONTAL, TOP, BOTTOM, LEFT, RIGHT, EXPAND, ALL, ALIGN_CENTER, ALIGN_RIGHT, \ + FONTWEIGHT_BOLD, WXK_DOWN +from prefsdata import nice_name_types, nice_type_names +from common import profile, delpref + +class PrefsViewer(wx.Panel): + """ + View and edit preferences. + + Digsby's about:config. + """ + + columns = ('Preference Name', 'Status', 'Type', 'Value') + + def __init__(self, parent, prefs, defaults): + wx.Panel.__init__(self, parent, -1) + + self.prefs = prefs + self.defaults = defaults + + self.construct_gui() + + prefs.add_observer(self.pref_changed) + parent.Bind(wx.EVT_CLOSE, self.on_frame_closed) + + self.sort = (0, False) # sort by column zero, ascending + self.on_filter_txt() + + # do some nitpicky platform specific cosmetic adjustments + self.prefs_list.SetColumnWidth(0,-1) + if wx.Platform != '__WXMAC__': offset = 20 + else: offset = 25 + self.prefs_list.SetColumnWidth(0,self.prefs_list.GetColumnWidth(0) + offset) + + def pref_changed(self, source, pref, old, new): + wx.CallAfter(self.on_filter_txt) + + def on_frame_closed(self, e = None): + if hasattr(profile, 'localprefs'): + profile.localprefs['advanced_prefs.filter'] = self.filter_txt.Value + self.prefs.remove_observer(self.pref_changed) + if getattr(self, '_did_change_pref', False): + profile.save('prefs') + e.Skip(True) + + def filtered_prefs(self, substr=""): + sum = dictadd(self.defaults, self.prefs) + + # find all keys not containing substr + substr = substr.lower() + remove_these_keys = [key for key in sum if key.lower().find(substr) == -1] + + # remove them + for key in remove_these_keys: + sum.pop(key) + + return sum + + def construct_gui(self): + # create and setup the fiter text box + if hasattr(profile, 'localprefs'): + filter = profile.localprefs.get('advanced_prefs.filter', u'') + else: + filter = u'' + + self.filter_txt = wx.TextCtrl(self, -1, filter) + + [self.filter_txt.Bind(e,m) for e,m in [ + (wx.EVT_TEXT, self.on_filter_txt), + (wx.EVT_KEY_DOWN, self.on_filter_text_key) + ]] + + # create and setup the preferences list control + self.prefs_list = PrefsListCtrl(self) + for e, m in [(wx.EVT_LIST_ITEM_ACTIVATED, self.on_dclick), + (wx.EVT_LIST_COL_CLICK, self.on_col_click), + (wx.EVT_RIGHT_UP, self.on_right_up), + (wx.EVT_RIGHT_DOWN, self.on_right_down)]: + self.prefs_list.Bind(e, m) + + # insert columns + for i in xrange(len(self.columns)): + self.prefs_list.InsertColumn(i, self.columns[i]) + + self.show_all = wx.Button(self, -1, "Sho&w All") + self.show_all.Bind(wx.EVT_BUTTON, lambda e: self.filter_txt.SetValue('') if self.filter_txt.GetValue() != '' else None) + + self.add_new = wx.Button(self, -1, '&Add') + self.add_new.Bind(wx.EVT_BUTTON, self.add_new_pref) + + self.push_now = wx.Button(self, -1, '&Push Now') + self.push_now.Bind(wx.EVT_BUTTON, lambda e: profile.save('prefs')) + + # layout components + hbox = wx.BoxSizer(HORIZONTAL) + + + hbox.AddMany([(wx.StaticText(self, -1, "Filter:", style=ALIGN_CENTER), 0, TOP | LEFT | BOTTOM | ALIGN_CENTER, 8), + (self.filter_txt, 100, EXPAND | ALL, 8), + (self.show_all, 0, TOP | BOTTOM, 8), + (self.add_new, 0, TOP | BOTTOM | RIGHT, 8)]) + + vbox = wx.BoxSizer(VERTICAL) + vbox.AddMany([(hbox, 0, EXPAND), + (self.prefs_list, 1, EXPAND), + (self.push_now, 0, ALL | ALIGN_RIGHT, 8)]) + + self.SetSizer(vbox) + + def foo(e): + print e + self.Bind(wx.EVT_BUTTON, foo) + + def add_new_pref(self, e): + key = str(wx.GetTextFromUser('Please enter the name for the pref', 'Add pref', self.filter_txt.Value)) + if not key: return + typ = str(wx.GetSingleChoice('Please choose the type for the pref', 'Add pref', nice_type_names.values())) + if not typ: return + typ = nice_name_types[typ] + + if typ is list: + from gui.toolbox import edit_list + ok, val = edit_list(self, [], 'Editing %s' % key) + if not ok: return + else: + val = str(wx.GetTextFromUser('Please enter the value for the pref','Add pref')) + val = bool_from_string(val) if typ is bool else typ(val) + if val == '': return + + self.prefs[key] = val + + + def on_filter_txt(self, e = None): + """ + Called when the filter text changes. + + Updates the table to reflect the new filter. + """ + self.update_list(self.filtered_prefs(self.filter_txt.Value)) + + def get_default_string(self, key): + "Returns 'default' or 'user-set' for the indicated preference." + + try: + val = self.prefs[key] + except KeyError: + return 'default' + else: + return 'default' if val == self.defaults.get(key, sentinel) else 'user-set' + + def update_list(self, prefs_dict, n = -1): + ''' + Called everytime text is entered in the filter text control. + + Rebuilds and resorts the list. + ''' + + self.shown = [] + append = self.shown.append + plist = self.prefs_list + sel = plist.GetFirstSelected() + getdefstr = self.get_default_string + + + with plist.Frozen(): + plist.DeleteAllItems() + + SetStringItem = plist.SetStringItem + + for i, (key, value) in enumerate(prefs_dict.iteritems()): + def_string = getdefstr(key) + + plist.InsertStringItem(i, key) + SetStringItem(i, 1, def_string) + SetStringItem(i, 2, nice_type_names[type(value)]) + SetStringItem(i, 3, str(value)) + plist.SetItemData(i, i) + + if def_string == 'user-set': + item = plist.GetItem(i) + f = item.Font + f.SetWeight(FONTWEIGHT_BOLD) + item.SetFont(f) + plist.SetItem(item) + + append((key, value)) + i += 1 + + plist.SortItems(self.on_sort) + # don't call EnsureVisible if we don't have a selection + if sel != -1: + plist.EnsureVisible(sel) + + def on_filter_text_key(self, e): + """ + Catches down arrows from the filter box to allow jumping to the table + via keyboard. + """ + if e.KeyCode == WXK_DOWN: + self.prefs_list.SetFocus() + else: + e.Skip(True) + + def on_col_click(self, e): + c = e.GetColumn() + + if c == self.sort[0]: ascending = not self.sort[1] + else: ascending = False + + self.sort = (c, ascending) + self.prefs_list.SortItems(self.on_sort) + + def on_sort(self, one, two): + one, two = self.shown[one], self.shown[two] + + column, ascending = self.sort + + _cmp = {0 : lambda x: x[0], + 1 : lambda x: self.get_default_string(x[0]), + 2 : lambda x: nice_type_names[type(x[1])], + 3 : lambda x: str(x[1])}.get(column, None) + + # XNOR! + # equivalent to: + # v = _cmp(one)<_cmp(two) + # return v if ascending else not v + return ascending == (_cmp(one) < _cmp(two)) + + def on_dclick(self, e): + key = e.GetText() + from util.primitives.funcs import get + mysentinel = Sentinel() #@UndefinedVariable + defval = get(self.defaults, key, mysentinel) + val = self.prefs.setdefault(key, defval) + + preftype = defval.__class__ if defval is not mysentinel else val.__class__ + + if issubclass(preftype, bool): + val = not val + + elif isinstance(val, list): + if is_all(val, (str, unicode))[0]: + from gui.toolbox import edit_string_list + ok, new_list = edit_string_list(self, val, 'Editing ' + key) + if ok and new_list: val = new_list + elif ok: val = defval + elif is_all(val)[0]: + from gui.toolbox import edit_list + ok, new_list = edit_list(self, val, 'Editing ' + key) + if ok and new_list: val = new_list + elif ok: val = defval + else: + print is_all(val) + raise AssertionError, key + \ + ' is not a homogenous list :( tell Kevin to make this more gooder' + + if val == defval and defval is mysentinel: + delpref(str(key)) + return + + elif isinstance(defval, (str,unicode,int,float)) or defval is mysentinel: + t = type(val) if defval is mysentinel else type(defval) + + print 'editing pref of type',t + + diag = wx.TextEntryDialog(self, key, 'Enter %s' % nice_type_names[t], str(val)) + + if diag.ShowModal() == wx.ID_OK: + val = diag.GetValue() + + if t is bool: + val = bool_from_string(val) + if val != '': + val = t(val) + elif defval is not mysentinel: + val = defval + else: + delpref(str(key)) + return + + self.prefs[str(key)] = val + self.on_filter_txt() + self._did_change_pref = True + + def on_right_up(self, e): + i = self.prefs_list.GetFirstSelected() + + def on_right_down(self, e): + e.Skip(True) + +class PrefsFrame(wx.Frame): + def __init__(self, prefs, defaults, parent=None, id=-1, title="Advanced Preferences", name = 'Advanced Preferences'): + wx.Frame.__init__(self, parent, id, title, + style = wx.DEFAULT_FRAME_STYLE & ~(wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX)) + + self.prefs_viewer = PrefsViewer(self, prefs, defaults) + + from gui.toolbox import persist_window_pos, snap_pref + persist_window_pos(self, unique_id = 'PrefsFrame', + defaultPos = wx.Point(100, 100), + defaultSize = wx.Size(600, 500), + position_only = True) + snap_pref(self) + +class PrefsListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): + list_style = wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_SINGLE_SEL + + def __init__(self, parent, id=-1, style = None): + wx.ListCtrl.__init__(self, parent, id, style=style or self.list_style) + ListCtrlAutoWidthMixin.__init__(self) + + +def edit(prefs, defaults, parent=None): + 'Toggles the advanced preference frame.' + + for win in wx.GetTopLevelWindows(): + if isinstance(win, PrefsFrame): + return win.Close() + + f = PrefsFrame(parent=parent, prefs=prefs, defaults=defaults) + wx.CallAfter(f.Show) diff --git a/digsby/src/ratelimited.py b/digsby/src/ratelimited.py new file mode 100644 index 0000000..141bf0d --- /dev/null +++ b/digsby/src/ratelimited.py @@ -0,0 +1,123 @@ +# XXX: shouldn't this be in util? or common? +from __future__ import division +import sys, time +if sys.platform == "win32": + # On Windows, the best timer is time.clock() + default_timer = time.clock +else: + # On most other platforms the best timer is time.time() + default_timer = time.time + +time = time.time + +class RateMonitor(object): + time_threshold = .5 # Must have collected data for at least .5 seconds before a BPS value can be calc'd + + def __init__(self, processor): + ''' + Keeps track of how fast data is being processed by 'processor' + ''' + + self.f_process = processor + + self.bytecounts = [] + self.written = 0 + self._bps = 0 + + def handle_data(self, data): + self._process(len(data)) + self.f_process(data) + + def _process(self, num_bytes): + if num_bytes > 0: + self._add_byte_data(time(), self.written + num_bytes) + + @property + def bps(self): + ''' + Bytes per second. + ''' + now = time() + + self._add_byte_data(now, self.written) + + if len(self.bytecounts) <= 1: + self._add_byte_data(now, self.written) + self._bps = 0 + return self._bps + + + oldest, lowest = self.bytecounts[0] + newest, highest = self.bytecounts[-1] + + time_diff = newest - oldest + byte_diff = highest - lowest + + if byte_diff and (time_diff > self.time_threshold): + self._bps = byte_diff/time_diff + else: + self._bps = 0 + + return self._bps + + def _add_byte_data(self, tstamp, bytecount): + self.written = bytecount + + tstamp = tstamp + bytecounts = self.bytecounts + + if not bytecounts: + bytecounts.append((tstamp, bytecount)) + + oldtime, oldcount = bytecounts[-1] + + if oldcount == bytecount: + bytecounts[-1] = (tstamp, bytecount) + elif tstamp > oldtime: + bytecounts.append((tstamp, bytecount)) + elif tstamp == oldtime: + bytecounts[-1] = (tstamp, bytecount) + + now = time() + + while bytecounts and ((now - bytecounts[0][0]) > self.window): + bytecounts.pop(0) + + +class RateLimiter(RateMonitor): + def __init__(self, processor, limit, window=1): + ''' + limit: bytes per second + window: how large of a sliding window to use to compute speed + + Calls self.too_fast(data) when data is being sent too fast. + ''' + + RateMonitor.__init__(self, processor) + self.limit = limit + self.window = window + self._called_too_fast = False + + def handle_data(self, data): + self._process(len(data)) + + if self.bps > self.limit: + if not self._called_too_fast: + self.too_fast(data) + self._called_too_fast = True + return False + else: + self.f_process(data) + self._called_too_fast = False + return True + +if __name__ == '__main__': + class TooFastPrinter(RateLimiter): + def too_fast(self, data): + print ('was sending %d bytes too fast!' % len(data)), self._bps + + rl = TooFastPrinter(lambda d: None, 20480, 1) + + for i in xrange(2048): + rl.write('a') + print len(rl.bytecounts), rl.bps, rl.written diff --git a/digsby/src/rpc/__init__.py b/digsby/src/rpc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/rpc/jsonrpc.py b/digsby/src/rpc/jsonrpc.py new file mode 100644 index 0000000..81eb210 --- /dev/null +++ b/digsby/src/rpc/jsonrpc.py @@ -0,0 +1,164 @@ +import simplejson +import time +import wx.webview +import traceback +from util.primitives.funcs import Delegate +from logging import getLogger +log = getLogger('jsonrpc') + +class JSPythonBridge(object): + def __init__(self, webview): + self.id_count = 0 + self.callbacks = {} # TODO: expire timeouts + self.webview = webview + self.webview.Bind(wx.webview.EVT_WEBVIEW_RECEIVED_TITLE, self.on_before_load) + self.specifiers = {} + self.on_call = Delegate() + + def SetPageSource(self, source, baseurl): + self._update_origin_whitelist(baseurl) + return self.webview.SetPageSource(source, baseurl) + + def LoadURL(self, url): + self._update_origin_whitelist(url) + return self.webview.LoadURL(url) + + def _update_origin_whitelist(self, url): + # in the future, we may want to whitelist URL for accessing "digsby:" + # URLS. but for now we just use title changes as a communication channel + pass + + def on_before_load(self, e): + url = e.Title + prefix = 'digsby://digsbyjsonrpc/' + if not url.startswith(prefix): + return + if url == prefix + "clear": + return + + url = url[len(prefix):] + + if not url.startswith('json='): + return + + url = url[len('json='):] + + json_data = url.decode('utf8url') + json_obj = simplejson.loads(json_data) + self.json(json_obj) + return True + + def Call(self, call, success = None, error = None, **k): + assert success is None or hasattr(success, '__call__') + assert error is None or hasattr(error, '__call__') + + id = self.gen_id() + self.callbacks[id] = d = dict(success = success, + error = error) + self.Dcallback(call, id, **k) + + def gen_id(self): + id = '%s_%s' % (int(time.time()*1000), self.id_count) + self.id_count += 1 + return id + + def Dcallback(self, method, id, **k): + args = simplejson.dumps({'params':[{'method':method, 'args':k}], 'method':'callbackCall', 'id':id}) + script = '''Digsby.requestIn(%s);''' % args + self.evaljs(script) + + def evaljs(self, js): + assert wx.IsMainThread() + return self.webview.RunScript(js) + + def register_specifier(self, specifier, func): + self.specifiers[specifier] = func + + def json(self, json_decoded): + d = json_decoded + if 'result' not in d: + if not self.on_call: + specifier = d.pop('specifier') + s = self.specifiers.get(specifier) + if s is not None: + try: + return s(d, self) + except AttributeError: + traceback.print_exc() + return self.on_call(d) + + cbs = self.callbacks.pop(d.pop('id')) + if not cbs: + return + elif d['error'] is not None: + assert d['result'] is None + if cbs['error'] is not None: + cbs['error'](d['error']) + elif d['result'] is not None: + assert d['error'] is None + if cbs['success'] is not None: + cbs['success'](d['result']) + + def RunScript(self, script): + wx.CallAfter(self.webview.RunScript, script) + +def Dsuccess(id, webview, **k): + if not wx.IsMainThread(): + raise AssertionError('subthread called Dsuccess') + + val = simplejson.dumps({'result':[k], 'error':None, 'id':id}) + script = '''Digsby.resultIn(%s);''' % val + webview.RunScript(script) + +def Derror(id, webview, error_obj=None, *a, **k): + if not wx.IsMainThread(): + raise AssertionError('subthread called Derror') + + if error_obj is None: + error_obj = "error" #need something, and None/null isn't something. + + val = simplejson.dumps({'result':None, 'error':error_obj, 'id':id}) + script = '''Digsby.resultIn(%s);''' % val + webview.RunScript(script) + + +class RPCClient(object): + _rpc_handlers = {} + + def json(self, rpc, webview): + method = rpc.get('method') + args = rpc.get('params')[0] + + if hasattr(args, 'items'): + kwargs = dict((k.encode('utf8'), v) for k, v in args.items()) + args = () + else: + args = tuple(rpc.get('params', ())) + kwargs = {} + + try: + getattr(self, self._rpc_handlers.get(method, '_default_rpc'), self._default_rpc)(rpc, webview, rpc.get('id'), *args, **kwargs) + except Exception, e: + import traceback; traceback.print_exc() + self._rpc_error(rpc, webview, e) + + def _rpc_error(self, rpc, webview, e): + self.Dexcept(webview, rpc.get('id', 0), "oh noes!") + + def _default_rpc(self, rpc, webview, *a, **k): + raise Exception("Unknown RPC call: extra args = %r, extra kwargs = %r", rpc, a, k) + + def rpc_hook(self, rpc, webview, id, *args, **kwargs): + #CAS: yes, this needs to be abstracted. + import hooks + hooks.notify(*args, **kwargs) + + def Dsuccess(self, webview, id, **k): + Dsuccess(id, webview, **k) + + def Dexcept(self, webview, id, response=None, *a, **k): + self.Derror(webview, id, *a, **k) + + def Derror(self, webview, id, *a, **k): + Derror(id, webview, error_obj=k.pop('error', k)) + diff --git a/digsby/src/services/__init__.py b/digsby/src/services/__init__.py new file mode 100644 index 0000000..a03d110 --- /dev/null +++ b/digsby/src/services/__init__.py @@ -0,0 +1,58 @@ +''' +Digsby Services + +This module is used to detect and load services providers and components that are to be supported by the app. + + * A 'service provider' is a company (or other group) that has a single set of credentials for a given user account. + For example, Google. Usually though, we'll be referring to the in-app abstraction of this concept, and not to the + actual remote entity (in code, it may also be referred to as an 'account'). One set of credentials will allow a + user to access all of their services - chat, mail, g+, maps, etc. The specified credentials should also be unique + for that service (if correct). Usually this means a unique username, but in some cases more fields may determine + uniqueness. + + Digsby currently supports the following service providers: + - AOL + - Facebook + - Google + - LinkedIn + - MySpace + - Tagged + - Twitter + - Windows Live (aka MSN) + - Yahoo + - XMPP (Jabber) + - POP + - IMAP + + Note the final three are not companies but are all server software that can be run on nearly any computer. These + are examples of service providers that may require more than just usernames for uniqueness, depending on server + configuration. + + * A 'service component' is one of those services - e.g., GMail. We have chosen the name service component in order to + remain unambiguous. A service provider may have many service components (though it's also technically possible to + have 0 components), but generally at most one of each type of IM, email, and social are used. + + A user can enable or disable a given component of a service. They should not be required to add any additional + information to do so - the service provider object should handle everything necessary for account setup. An + exception is if there is any first-time setup when attempting to connect *any* component of the service. For + example, when enabling either the IM or social component of a Facebook account, the user must complete OAuth + authentication - the results of that authentication are stored in the account (service provider). + + Currently supported service components (divided by provider) are: + + AOL: AIM, AOL Mail + Facebook: Chat, Social Newsfeed + Google: Mail, Talk + LinkedIn: Social newsfeed + MySpace: Chat, Social newsfeed + Tagged: Social newsfeed + Twitter: Timeline + Windows Live: MSN (chat), Hotmail + Yahoo: Chat, Yahoo Mail + XMPP: Chat + POP: mail + IMAP: mail + +Both service providers and service components are implemented as plugins. For examples, see existing plugins. The API +is constantly growing and is not currently documented. (TODO: fix that!) +''' diff --git a/digsby/src/services/service_component.py b/digsby/src/services/service_component.py new file mode 100644 index 0000000..c2ee99a --- /dev/null +++ b/digsby/src/services/service_component.py @@ -0,0 +1,75 @@ +import cPickle +import common + +class ServiceComponent(object): + ''' + A ServiceComponent instance is responsible for de/serializing all of the data that needs to be stored in order to + maintain a user's settings for a given service feature. This also contains some legacy fields due to the way + they're stored in the server database. + + This class is also responsible for providing access to the plaintext password of the account. + ''' + _attrfields_ = [('protocol', 'protocol'), + ('name', 'username'), + ('password', 'password_crypted'), + ('id', 'id')] + + _datafields_ = [] + + @classmethod + def deserialize(cls, netacct): + attrs = {} + + msc = common.protocolmeta.get(netacct.protocol) + subcls = hooks.first('digsby.services.component.%s.factory' % msc.component_type, + impls = (msc.service_provider, 'default')) + attrs['provider_id'] = msc.service_provider + attrs['component_type'] = msc.component_type + + assert issubclass(cls, subcls) + + default = lambda k: msc.info.defaults.get(k, sentinel) + for netkey, mykey in subcls._attrfields_: + attrs[mykey] = getattr(netacct, netkey) + + options = cPickle.loads(netacct.data) + for netkey, mykey in subcls._datafields_: + val = options.get(netkey) + if val is None: + val = default(netkey) + if val is sentinel: + raise KeyError("%r is required but is not present.", netkey) + attrs[mykey] = val + + return subcls(attrs) + + def serialize(self): + attrs = {} + for netkey, mykey in self._attrfields_: + attrs[netkey] = getattr(self, mykey) + + options = {} + for netkey, mykey in self._datafields_: + options[netkey] = getattr(self, mykey) + + attrs['data'] = cPickle.dumps(options) + + return attrs + + def __init__(self, **options): + + if options: + self.init(options) + + def init(self, options): + for k,v in options.items(): + setattr(self, k, v) + + def _get_password(self): + return common.profile.plain_pw(self.password_crypted) + + def _set_password(self, val): + self.password_crypted = common.profile.crypt_pw(val) + + password = property(_get_password, _set_password) + diff --git a/digsby/src/services/service_provider.py b/digsby/src/services/service_provider.py new file mode 100644 index 0000000..be67f24 --- /dev/null +++ b/digsby/src/services/service_provider.py @@ -0,0 +1,603 @@ +''' +This file contains several helper methods for working with service provider and component metainfo, as well as basic +service provider functionality and subclasses appropriate for real service provider types. +''' +import cPickle + +import logging +log = logging.getLogger("service_provider") + +import wx +import hooks +import util +import util.net as net +import common.protocolmeta as protocolmeta +from peak.util.addons import AddOn +import common +from util.primitives.structures import oset +from util.primitives.funcs import Delegate +import threading +from contextlib import contextmanager + +import plugin_manager.plugin_hub as plugin_hub + +class AccountException(Exception): + def __init__(self, message = u'', fatal = True, debug_message = ''): + Exception.__init__(self, debug_message) + self.message = message + self.fatal = fatal + +class DuplicateAccountException(Exception): + pass + +def get_meta_service_providers(): + plugins = wx.GetApp().plugins + return [plugin for plugin in plugins if getattr(getattr(plugin, 'info', None), 'type', None) == 'service_provider'] + +def get_meta_service_provider(provider_id): + for msp in get_meta_service_providers(): + if getattr(getattr(msp, 'info', None), 'provider_id', None) == provider_id: + return msp + + return None + +def get_meta_components_for_provider(service_provider): + plugins = wx.GetApp().plugins + return [plugin for plugin in plugins if getattr(getattr(plugin, 'info', None), 'type', None) == 'service_component' + and getattr(getattr(plugin, 'info', None), 'service_provider', None) == service_provider] + +def get_meta_component_for_provider(service_provider, component_type): + for msc in get_meta_components_for_provider(service_provider): + if getattr(getattr(msc, 'info', None), 'component_type', None) == component_type: + return msc + +def get_service_provider_factory(provider_id): + ProviderFactory = None + for impl in (provider_id, None): + pf = hooks.first("digsby.service_provider", impl = provider_id, provider_id = provider_id) + if pf is not None: + ProviderFactory = pf + break + + return ProviderFactory + +def get_provider_for_account(acct, profile=None): + if profile is None: + profile = common.profile() + msp = get_meta_provider_for_account(acct) + if msp is None: + return None + sp = get_service_provider_factory(msp.provider_id) + if sp is None: + return None + + return sp.from_account(acct, profile) + +def get_meta_provider_for_account(acct): + meta = protocolmeta.protocols.get(acct.protocol) + msp = get_meta_service_provider(getattr(meta, 'service_provider', None)) + return msp + +#class IIMComponent(protocols.Interface): +# pass +# +#class IIMComponentFactory(protocols.Interface): +# pass + +class ServiceProvider(object): + + def __init__(self, provider_id, **kwds): + self.provider_id = provider_id + self.accounts = {} + + if kwds: + #log.info("Create Service provider: %r", kwds) + self.update_info(kwds) + + def get_component_factory(self, type): + return list(hooks.Hook("digsby.component.%s" % type, impl = self.provider_id))[0] + + def construct_component(self, type): + msp = get_meta_service_provider(self.provider_id) + msc = get_meta_component_for_provider(self.provider_id, type) + cf = self.get_component_factory(type) + + component = hooks.first("digsby.component.%s.construct" % type, + cf, self, msp, msc, + impls = (self.provider_id, 'default')) + + return component + + def get_metainfo(self, component_type = None): + msp = get_meta_service_provider(self.provider_id) + if component_type is None: + return msp + return msp, get_meta_component_for_provider(self.provider_id, component_type) + + def get_component_types(self): + return [x.info.component_type for x in self.get_meta_components()] + + def get_meta_components(self): + return get_meta_components_for_provider(self.provider_id) + + def get_component(self, type, create = True): + if type not in self.get_component_types(): + return None + + if type not in self.accounts and create: + component = self.construct_component(type) + if component is not None: + self.add_account(type, component) + plugin_hub.act('digsby.%s.addaccount.async' % type, component.protocol, component.name) + + return self.accounts.get(type, None) + + @classmethod + def from_account(cls, acct, profile): + container = ServiceProviderContainer(profile) + meta = protocolmeta.protocols.get(acct.protocol) + component_type = meta.component_type + provider_id = meta.service_provider + + username = getattr(acct, 'username', getattr(acct, 'name', None)) + info = acct.get_options() + info['username'] = acct.username + sp = container.existing_sps.get(normalized_id(provider_id, info), None) + if sp is None: + sp = container.existing_sps[normalized_id(provider_id, info)] = cls(provider_id = provider_id) + + sp.add_account(component_type, acct) + comp = sp.get_component(component_type, create = False) + + return sp + + def add_account(self, type, acct): + + acct_options = acct.get_options() + + options = copy.deepcopy(getattr(self.get_metainfo(), 'info', {}).get('defaults', {})) + options.update(copy.deepcopy(getattr(self.get_metainfo(type)[1], 'info', {}).get('defaults', {}))) + options.update(acct.get_options()) + options['name'] = acct.name + + if acct.password: + options['password'] = acct.password + + options['id'] = acct.id + + if type in self.accounts: + if self.accounts[type] is not acct: + self.accounts[type].update_info(**options) + else: + self.accounts[type] = acct + + self.update_info(options) + getattr(self, 'add_account_%s' % type, lambda a: None)(acct) + + def add_account_im(self, acct): + pass + def add_account_email(self, acct): + pass + def add_account_social(self, acct): + pass + + def remove_account(self, type): + self.accounts.pop(type, None) + + def update_info(self, info): + info.pop('id', None) + for k, v in info.items(): + setattr(self, k, v) + + def update_components(self, info): + for type in self.get_component_types(): + comp = self.get_component(type, create = False) + if comp is not None: + info.pop('username', None) + comp.update_info(**info) + + def get_options(self, type): + import hub + + opts = dict(user = hub.get_instance()) + opts.update(vars(self)) + opts.pop('accounts') + + acct = self.accounts.get(type, None) + if acct is not None: + opts.update(acct.get_options()) + + opts.update(name = acct.name, + password = acct.password) + + opts.pop('enabled', None) + opts.pop('protocol', None) + opts.pop('id', None) + return opts + + def rebuilt(self): + pass + + def __repr__(self): + return "<%s %12r: %r>" % (type(self).__name__, getattr(self, 'provider_id', None), getattr(self, 'name', None)) + +class UsernameServiceProvider(ServiceProvider): + ''' + A service provider with a username. Most service providers will have this. + ''' + name = None + def update_info(self, kwds): + if self.name is not None: + kwds.pop("name", None) + kwds.pop('username', None) + else: + self.name = kwds.get('username', kwds.get('name', None)) or self.name + + if not self.name: + raise AccountException(debug_message = "No username provided") + + if 'server' in kwds: + try: + host, port = kwds['server'] + port = int(port) + kwds['server'] = host, port + if not host: + raise ValueError(host) + except (ValueError, TypeError): + #raise AccountException(debug_message = "server tuple is invalid: %r" % kwds['server']) + pass + + super(UsernameServiceProvider, self).update_info(kwds) + + def add_account(self, type, acct): + super(UsernameServiceProvider, self).add_account(type = type, acct = acct) + + if self.name is None: + self.name = acct.name + + opts = acct.get_options() + opts['username'] = acct.name + acct_id = normalized_id(self.provider_id, opts) + my_id = normalized_id(self.provider_id, self.get_options(type)) + if acct_id != my_id: + raise ValueError("Can't add a different username") + + def get_options(self, type): + opts = super(UsernameServiceProvider, self).get_options(type = type) + opts.update(name = self.name) + return opts + + @property + def display_name(self): + meta = self.get_metainfo().info.provider_info + display_domain = meta.get('display_domain', meta.get('default_domain', None)) + if display_domain is None: + return self.name + else: + try: + return str(net.EmailAddress(self.name, display_domain)) + except ValueError: + return self.name + +class PasswordServiceProvider(ServiceProvider): + ''' + A service provider with a password. Many, but not all, service providers will require this behavior. + ''' + password = None + def update_info(self, kwds): + super(PasswordServiceProvider, self).update_info(kwds) + + if 'password' in kwds: + self.password = kwds.get('password', None) + + if self.password == '': + raise AccountException(debug_message = "No password provided") + + def add_account(self, type, acct): + super(PasswordServiceProvider, self).add_account(type = type, acct = acct) + + if self.password is None: + self.password = acct.password + + def get_options(self, type): + opts = super(PasswordServiceProvider, self).get_options(type = type) + opts.update(password = self.password) + return opts + + def _decryptedpw(self): + return common.profile.plain_pw(self.password) + +class UsernamePasswordServiceProvider(UsernameServiceProvider, PasswordServiceProvider): + ''' + Convenience class to combine UsernameServiceProvider and PasswordServiceProvider. + ''' + pass + +class EmailPasswordServiceProvider(UsernamePasswordServiceProvider): + ''' Same as a UsernamePasswordServiceProvider but has an email address property ''' + + def update_info(self, kwds): + super(EmailPasswordServiceProvider, self).update_info(kwds) + try: + self._get_email_address() + except ValueError: + raise AccountException("Invalid email address", debug_message = "No oauth account label or username provided") + + def _get_email_address(self): + + default_domain = self.get_metainfo().info.provider_info.get('default_domain', None) + if default_domain is not None: + return str(net.EmailAddress(''.join(self.name.split()).lower(), default_domain)) + else: + return self.name + + def _set_email_address(self, v): + pass + + email_address = property(_get_email_address, _set_email_address) + + @property + def display_name(self): + if self.name.lower() != self.email_address.lower(): + meta = self.get_metainfo().info.provider_info + return ''.join(self.name.split()).lower() + '@' + meta.get('display_domain', meta.get('default_domain')) + + else: + return self.name + + def get_options(self, type): + opts = super(EmailPasswordServiceProvider, self).get_options(type = type) + opts.update(email_address = self.email_address) + return opts + +class OAuthServiceProvider(ServiceProvider): + ''' + Does not have a password and only sort-of has a username. The username is treated as a 'label', since we have + no reasonable way of checking the username entered into the OAuth field when login happens. + ''' + label = None + oauth_token = None + def update_info(self, kwds): + super(OAuthServiceProvider, self).update_info(kwds) + + self.label = kwds.get('label', kwds.get('name', kwds.get('username', getattr(self, 'name', None)))) + if not self.label: + raise AccountException(debug_message = "No oauth account label or username provided") + + self.oauth_token = kwds.get('oauth_token', None) + + def get_options(self, type): + opts = super(OAuthServiceProvider, self).get_options(type = type) + opts.update(label = opts.get('name', None), name = self.label) + + return opts + + def add_account(self, type, acct): + super(OAuthServiceProvider, self).add_account(type = type, acct = acct) + + if self.label is None: + self.name = self.label = getattr(acct, 'name', None) + + if self.oauth_token is None: + self.oauth_token = getattr(acct, 'oauth_token', None) + + @property + def display_name(self): + meta = self.get_metainfo().info.provider_info + display_domain = meta.get('display_domain', meta.get('default_domain', None)) + if display_domain is None: + return self.name + else: + try: + return str(net.EmailAddress(self.name, display_domain)) + except ValueError: + return self.name + +class MixedAuthServiceProvided(UsernamePasswordServiceProvider, OAuthServiceProvider): + ''' + In some rare cases, we might actually have a username/password combination and an OAuth token. (myspace comes to + mind). + ''' + pass + +import copy +def default_component_constructor(cf, SP, MSP, MSC): + ''' + Create a service component by flattening 3 options dictionaries together: + meta info for the service provider + meta info for the service component + info for the service component instance we're creating + ''' + options = copy.deepcopy(getattr(MSP, 'info', {}).get('defaults', {})) + options.update(copy.deepcopy(getattr(MSC, 'info', {}).get('defaults', {}))) + options.update(SP.get_options(MSC.info.component_type)) + + return cf(**options) + +def default_component_constructor_im(cf, SP, MSP, MSC): + ''' + Like default_component_constructor but also includes a 'protocol' key, which is required for IM accounts. + This also forces the class imaccount.Account for the result (instead of the provided cf argument) + ''' + import imaccount + options = copy.deepcopy(getattr(MSP, 'info', {}).get('defaults', {})) + options.update(copy.deepcopy(getattr(MSC, 'info', {}).get('defaults', {}))) + options.update(SP.get_options(MSC.info.component_type)) + if 'protocol' not in options: + options.update(protocol = MSC.info.shortname) + + util.dictrecurse(dict)(options) + + # TODO: cf is ignored - should we have a way for it to be overridden? perhaps let it default to imaccount if not + # provided. + return imaccount.Account(**options) + +hooks.register("digsby.component.im.construct", default_component_constructor_im, impl = 'default') +hooks.register("digsby.component.email.construct", default_component_constructor, impl = 'default') +hooks.register("digsby.component.social.construct", default_component_constructor, impl = 'default') + +class ServiceProviderContainer(AddOn): + ''' + Used to contain all ServiceProvider data - this is essentially the user's account list. It is also responsible + for maintaining order of accounts. + ''' + def __init__(self, subject): + self.profile = subject + self.existing_sps = {} + self.order = None + self.on_order_changed = Delegate() + self.lock = threading.RLock() + self.rebuild_count = 0 + self.accounts_to_rebuild = None + self.new = set() + + def has_account(self, info): + return normalized_id(info['provider_id'], info) in self.existing_sps + + def get_ordered(self, new=()): + if self.order is None: + return + order = self.order + acct_position = dict((v,k) for (k,v) in enumerate(order)) + from collections import defaultdict + types = defaultdict(list) + provider_list = self.existing_sps.values() + provider_lookup = dict() + account_lookup = dict() + for provider in provider_list: + for type_, acct in provider.accounts.items(): + if acct in new: + assert len(new) == 1 + assert len(provider.accounts) > len(new) + continue + types[type_].append(acct) + provider_lookup[acct.id] = provider + account_lookup[acct.id] = acct + for val in types.values(): + val.sort(key = lambda a: acct_position.get(a.id, 1000)) + loc = dict(enumerate(provider_list)) + from util.primitives.mapping import dictreverse + loc2 = dictreverse(loc) + chains = [types['im'], types['email'], types['social']] + + #this adds just a little more information about the relationship between + #im/email/social accounts. + total_chain = oset(account_lookup[id_] for id_ in order if id_ in account_lookup) + for chain in chains: + total_chain.update(chain) + chains.append(total_chain) + + chains = [oset([loc2[provider_lookup[acct.id]] for acct in type_]) for type_ in chains] + + #enforce that if there is a previous ordering between two nodes, + #then the reverse ordering is discarded +# partial = set() +# chains2 = [] +# for chain in chains: +# chain2 = [] +# blacklist = [] +# for i, a in enumerate(chain): +# if a in blacklist: +# continue +# for b in chain[i:]: +# if a == b: +# continue +# node = (a,b) +# revnode = (b,a) +# if revnode in partial: +# #the conflict doesn't exist until we get to b +# #and it's farther down than this one, so discard b. +# _none = blacklist.append(b) +# else: +# _none = partial.add(node) +# else: +# _none = chain2.append(a) +# _none = chains2.append(chain2) +# #_none is for pasting into the console, consumed return values aren't shown. +# import util.primitives.topological_sort as tsort +# order = tsort.topological_sort_chains(chains2) + import util.primitives.topo_sort as tsort + order = tsort.topological_sort_chains(chains) + provider_accts = [loc[i] for i in order] + + out = [] + for prov in provider_accts: + [out.append(a.id) for a in + [prov.accounts.get(t, None) for t in + ['im', 'email', 'social']] + if a is not None] + self.order = out + return provider_accts + + def get_order(self): + return self.order[:] if self.order else [] + + def set_order(self, order): + if order == self.order: + return + self.order = order[:] + self.rebuild_order() + + def rebuild_order(self, new=()): + self.get_ordered(new=new) + #again, to flush all inconsistency + self.get_ordered() + self.on_order_changed(self.order) + + def rebuild(self, accounts=None, new=()): + with self.lock: + if accounts is not None: + self.accounts_to_rebuild = accounts + if self.rebuild_count != 0: + return + else: + accounts = self.accounts_to_rebuild + self.accounts_to_rebuild = None + self.existing_sps.clear() + for a in accounts: + try: + get_provider_for_account(a, self.profile) + except Exception: + import traceback; traceback.print_exc() + + if self.order is not None: + self.rebuild_order(new=new) + + @contextmanager + def rebuilding(self, new=()): + with self.lock: + self.rebuild_count += 1 + self.new.update(new) + yield self + with self.lock: + self.rebuild_count -= 1 + if self.rebuild_count == 0: + self.new, new = set(), self.new + self.rebuild(new=new) + for sp in self.existing_sps.values(): + sp.rebuilt() + +def normalized_id(provider_id, info): + MSP = get_meta_service_provider(provider_id) + + id = info.get('name', info.get('username')) + id = ''.join(id.split()).lower() + + default_domain = MSP.info.provider_info.get('default_domain', None) + if default_domain is not None: + try: + email = net.EmailAddress(id, default_domain) + if email.domain in MSP.info.provider_info.get('equivalent_domains', [default_domain]): + email = net.EmailAddress(email.name, default_domain) + id = str(email) + except ValueError: + pass + + if provider_id != 'aol': # xxx: gtfo + server = info.get('imapserver', info.get('popserver', None)) + if server is not None: + id = server + '_' + id + + return provider_id, id + +hooks.register("digsby.profile.addons", ServiceProviderContainer, impl = 'services') diff --git a/digsby/src/singleinstance.py b/digsby/src/singleinstance.py new file mode 100644 index 0000000..b735238 --- /dev/null +++ b/digsby/src/singleinstance.py @@ -0,0 +1,302 @@ +''' +singleinstance.py + +Uses wxSingleInstanceChecker and loopback socket communication to ensure +only one instance of an application runs at the same time. + +For example: + + app = SingleInstanceApp(0) + f = wx.Frame(None, -1, "This app only runs once!") + f.Show(True) + app.SetTopWindow(f) + app.MainLoop() + +Try to run this app more than once, and the first frame will pop up. +''' + +from __future__ import with_statement +from contextlib import closing +import sys, socket, wx, logging, threading +from traceback import print_exc + +def log(msg): # logging not setup yet + pass + +class SingleInstanceApp(wx.App): + ''' + Single instance checking and notification through inheritance. + + Inherit your app from this class and be sure to call SetTopWindow with + the frame you wish to be raised. + ''' + def __init__(self, appname, *args, **kws): + appname = '%s-%s' % (appname, wx.GetUserId()) + mgr = InstanceChecker(appname, 'localhost', + InstanceChecker.default_port) + self.instance_checker = mgr + + try: + should_quit = self._check_and_raise_other() + if should_quit: + log('instance already running. quitting!') + self._do_quit() + except Exception: + print_exc() + + # wx.App's __init__ calls OnInit, which may spawn frames, so + # we do this last + wx.App.__init__(self, *args, **kws) + + def start_server(self): + if self.instance_checker.isServerRunning(): + return + + port_taken = self.instance_checker.startServer() + if port_taken: + if self._check_and_raise_other(): + self._do_quit() + + def _do_quit(self): + sys.exit(0) + + def _check_and_raise_other(self): + another = self.instance_checker.isAnotherRunning() + log('another instance running: %r' % another) + + if another: + sent_raise = self.instance_checker.sendRaisePreviousFrameCommand() + log('sent raise command: %r' % sent_raise) + + if another and sent_raise: + return True + + def StopSingleInstanceServer(self): + return self.instance_checker.stopServer() + + def SetTopWindow(self, w): + wx.App.SetTopWindow(self, w) + self.instance_checker.setFrame( w ) + self.start_server() + + def MainLoop(self, *args, **kws): + if not hasattr(self, 'instance_checker'): + raise AssertionError('must call SetTopWindow on this app first') + + try: + wx.App.MainLoop(self, *args, **kws) + finally: + self.instance_checker.stopServer() + +SERVER_NOT_STARTED = 0 +SERVER_STARTED = 1 +SERVER_PORT_TAKEN = 2 + +class ServerThread(threading.Thread): + ''' + Class which simply runs a socket server in its own thread. + + It exposes some basic functionality, such as starting and stopping. + ''' + backlog = 5 + + def __init__(self, host, port, function, timeout = None, cv=None): + threading.Thread.__init__(self, name=self.__class__.__name__ + \ + "-" + host + ":" + str(port)) + self.host, self.port = host,port + self.function = function + self.die = False + self.timeout = timeout or 1 + self.cv = cv + self.status = SERVER_NOT_STARTED + + def _notify_status(self, status): + if not self.cv: + self.status = status + else: + with self.cv: + self.status = status + self.cv.notify() + + def run(self): + ''' + Sets socket up and listens. A timeout occurs every 'timeout' seconds + (defaults to .2) so that it can check to see if it has been ordered to + die. If a connection is made then it calls the function that was passed + in. + ''' + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind((self.host, self.port)) + except socket.error, e: + self._notify_status(SERVER_PORT_TAKEN) + return + else: + self._notify_status(SERVER_STARTED) + + s.listen(self.backlog) + s.settimeout(self.timeout) + + while not self.die: + try: + client, address = s.accept() + if self.die: + break + + try: + log('accepted a single instance ping, sending "ok"') + client.sendall('ok') + finally: + client.close() + + try: + self.function() + except Exception: + print_exc() + + # catches the timeout exception + except socket.timeout, e: + if self.die: + log('singleinstance s.accept()') + break + + # when stopped, close the socket + try: + s.close() + except: + pass + + def isRunning(self): + # die and running status are opposite + return not self.die + + def stop(self): + self.die = True + +def poke_client_port(host, port): + ''' + Opens a connection with the specified host/port pair, then immediately + closes the socket. + + Returns True if the connection was successfully made. + ''' + try: + # use an unproxied socket to localhost + log('connecting to (%r, %r)' % (host, port)) + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.connect((host, port)) + data = s.recv(512) + log('received bytes from other digsby process: %r' % (data, )) + if data != 'ok': + return False + except Exception: + print_exc() + return False + else: + return True + +class InstanceChecker(object): + ''' + Class for calling a given function (defaults to Raise) on a given frame + when another instance of program starts. + ''' + + default_port = 8791 + + def __init__( self, name, host, port, frame = None, func = None ): + self.name = name + self.frame = frame + self.port = port + self.host = host + self.logger = logging.getLogger( "" ) + if not func: + self.func = lambda: wx.CallAfter(self.__raiseFrame) + + self.s_checker = wx.SingleInstanceChecker( self.name ) + + def startServer( self ): + ''' + This is a small server on its own thread that waits for a connection + and once it recieves it calls the function passed into it. Defaults to + __raiseFrame. + ''' + self.logger.info( "Server stuff" ) + + self._quit_cv = threading.Condition() + self.server = ServerThread(self.host, self.port, self.func, cv=self._quit_cv) + + try: + with self._quit_cv: + self.server.start() + while self.server.status == SERVER_NOT_STARTED: + self._quit_cv.wait() + + if self.server.status == SERVER_PORT_TAKEN: + self.logger.info('instance checker port was already taken, quitting') + return True + + except Exception, e: + self.logger.error('Couldn\'t start single instance checker server because: %r', e) + raise e + + def sendRaisePreviousFrameCommand( self ): + ''' + Starts up a simple client that opens up a connection on a pre-defined + port and closes again. + ''' + self.logger.info('Poking IPC loopback connection') + return poke_client_port(self.host, self.port) + + def isServerRunning( self ): + return hasattr(self, 'server') and self.server.isRunning() + + def stopServer(self): + # Delete the reference to the wxSingleInstanceChecker + if hasattr(self, 's_checker'): + del self.s_checker + + if hasattr(self, 'server') and self.server.isRunning(): + self.server.stop() + return True + else: + self.logger.warning( "Tried to stop a server that wasn't running" ) + return False + + def setFrame(self, f): + self.frame = f + + def setFunc(self, func): + self.func = func + + def __raiseFrame(self): + if self.frame is None: + log('wxApp.SetTopWindow was not called, cannot raise frame') + return + + # 1) if the window is hidden, show it + self.frame.Show(True) + + # 2) if the window is minimized, restore it + self.frame.Iconize(False) + + # 3) bring it to the top of the window hierachy. + self.frame.Raise() + + # 4) if it's autohidden (and docked), bring it back. + if hasattr(self.frame, 'ComeBackFromAutoHide'): + self.frame.ComeBackFromAutoHide() + + def isAnotherRunning( self ): + return self.s_checker.IsAnotherRunning() + +# +# +# + +if __name__ == '__main__': + app = SingleInstanceApp(0) + f = wx.Frame(None, -1, "This app only runs once!") + f.Show(True) + app.SetTopWindow(f) + app.MainLoop() diff --git a/digsby/src/social/__init__.py b/digsby/src/social/__init__.py new file mode 100644 index 0000000..6630582 --- /dev/null +++ b/digsby/src/social/__init__.py @@ -0,0 +1 @@ +from network import SocialNetwork as network diff --git a/digsby/src/social/network.py b/digsby/src/social/network.py new file mode 100644 index 0000000..c347df3 --- /dev/null +++ b/digsby/src/social/network.py @@ -0,0 +1,191 @@ +import hooks +import logging +from rpc.jsonrpc import Dsuccess, Derror +log = logging.getLogger('social.network') + +from util import try_this, Storage +from common import AccountBase, profile, UpdateMixin, FromNetMixin, pref + +class SocialNetwork(UpdateMixin, AccountBase, FromNetMixin): + filters = {} + header_funcs = [] + timer = Null + + def __init__(self, enabled = True, **options): + AccountBase.__init__(self, **options) + UpdateMixin.__init__(self, **options) + FromNetMixin.__init__(self, **options) + + self.enabled = enabled + self._dirty_error = True # The next error is new + + @property + def dirty(self): + return self._dirty + + @property + def display_name(self): + return try_this(lambda: getattr(self, pref('social.display_attr')), self.username) + + def _decryptedpw(self): + return profile.plain_pw(self.password) + + def update_info(self, **info): + force = info.pop('force', None) + self._dirty_error = True + for k, v in info.iteritems(): + setattr(self, k, v) + self.notify() + +# if self.OFFLINE and self.enabled: +# self.update_now() + + # Tell the server. + profile.update_account(self, force = force) + + def get_options(self): + try: + get_opts = super(SocialNetwork, self).get_options + except AttributeError: + opts = {} + else: + opts = get_opts() + #updatefreq is not user settable, so we don't need to store it + opts.pop('updatefreq', None) + return opts + + @property + def icon(self): + from gui import skin + from util import try_this + return try_this(lambda: skin.get('serviceicons.%s' % self.protocol), None) + + def error_link(self): + reason = self.Reasons + + if self.protocol_info().get('needs_password', True): + bplinkref = (_('Edit Account'), lambda *a: profile.account_manager.edit(self, True)) + else: + bplinkref =(_('Retry'), lambda *a: self.Connect()) + + + linkref = { + reason.BAD_PASSWORD : bplinkref, + reason.CONN_FAIL : (_('Retry'), lambda *a: self.Connect()), + reason.OTHER_USER : (_('Reconnect'), lambda *a: self.update_now()), + reason.CONN_LOST : (_('Retry'), lambda *a: self.update_now()), + reason.WILL_RECONNECT : (_('Retry'), lambda *a: self.update_now()), + reason.NONE : None, + } + if self.offline_reason in linkref: + return linkref[self.offline_reason] + else: + log.debug('Couldn\'t find offline reason %r in linkref dictionary. Returning None for error_link', + self.offline_reason) + return None + + @property + def service(self): + raise NotImplementedError + + @property + def protocol(self): + raise NotImplementedError + + def Connect(self, *a, **k): + raise NotImplementedError +# self.change_state(self.Statuses.ONLINE) + + def Disconnect(self, *a, **k): + raise NotImplementedError +# self.change_state(self.Statuses.OFFLINE) +# disconnect = Disconnect + def disconnect(self, *a, **k): + raise NotImplementedError + + def observe_count(self,callback): + return NotImplemented + #self.add_gui_observer(callback, 'count') + + def observe_state(self, callback): + return NotImplemented + #self.add_gui_observer(callback, 'enabled') + + def unobserve_count(self,callback): + return NotImplemented + #self.remove_gui_observer(callback, 'count') + + def unobserve_state(self,callback): + return NotImplemented + #self.remove_gui_observer(callback) + + +import weakref +weak_socialfeeds = weakref.WeakValueDictionary() + +def on_dirty(ctx): + try: + feed = weak_socialfeeds[ctx] + except KeyError: + log.warning('SocialFeed marked dirty but not in weak dictionary: %r', ctx) + else: + feed.set_dirty() + +hooks.register('social.feed.mark_dirty', on_dirty) + +class SocialFeed(object): + ''' + allows plugins to use social.feed.* hooks to inject things into social feeds + ''' + + def __init__(self, id_, feed_context, get_feed_items, render_items, set_dirty=None): + assert hasattr(render_items, '__call__') + assert hasattr(get_feed_items, '__call__') + + self.id = id_ # globally unique, i.e. account_id + name + subtype. Must be hashable + self.context = feed_context # for use by whatever is creating the SocialFeed + self.get_feed_items = get_feed_items + self.render_items = render_items + + + self.iterators = {} + + hooks.notify('social.feed.created', self.id) + + self.set_dirty_cb = set_dirty + + weak_socialfeeds[self.id] = self + + def set_dirty(self): + if self.set_dirty_cb is not None: + self.set_dirty_cb() + else: + log.warning('%r dirty hook called, but has no callback', self) + + def get_iterator(self): + iterator_info = Storage(id=self.id, + context=self.context, + iterator=self.get_feed_items()) + + # allow plugins to wrap/transform the generator + return hooks.reduce('social.feed.iterator', iterator_info).iterator + + def new_ids(self, ids): + hooks.notify('social.feed.updated', self.id, ids) + + def jscall_initialize_feed(self, webview, _id): + self.iterators.pop(webview, None) + + def jscall_next_item(self, webview, id): + try: + it = self.iterators[webview] + except KeyError: + it = self.iterators[webview] = self.get_iterator() + + try: + item = it.next() + except StopIteration: + Derror(id, webview) + else: + Dsuccess(id, webview, html=self.render_items([item], self.context)) + diff --git a/digsby/src/social/socialfeed.py b/digsby/src/social/socialfeed.py new file mode 100644 index 0000000..197265e --- /dev/null +++ b/digsby/src/social/socialfeed.py @@ -0,0 +1,23 @@ +from gui.imwin.styles.adiummsgstyles import AdiumMessageStyle + +class SocialFeedStyle(AdiumMessageStyle): + pass + +def main(): + from tests.testapp import testapp + app = testapp() + + import wx.webview + f = wx.Frame(None, -1, size=(300, 700)) + + from gui.imwin.styles.msgstyles import get_theme + style = SocialFeedStyle(get_theme('Smooth Operator').path) + + w = wx.webview.WebView(f) + w.SetPageSource(style.initialContents('', None, False)) + f.Show() + app.MainLoop() + +if __name__ == '__main__': + main() + diff --git a/digsby/src/stdpaths.py b/digsby/src/stdpaths.py new file mode 100644 index 0000000..1324c78 --- /dev/null +++ b/digsby/src/stdpaths.py @@ -0,0 +1,302 @@ +import config +import sys +from path import path +inited = False + +if config.platform == 'win': + import ctypes + SHGFP_TYPE_DEFAULT = 1 + MAX_PATH = 260 + u_buffer = ctypes.create_unicode_buffer(MAX_PATH) + s_buffer = ctypes.create_string_buffer(MAX_PATH) + SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW + + def GetSpecialFolder(csidl): + SHGetFolderPath(None, csidl, None, SHGFP_TYPE_DEFAULT, ctypes.byref(u_buffer)) + return u_buffer.value + + class SHItemID(ctypes.Structure): + 'http://msdn.microsoft.com/en-us/library/bb759800(VS.85).aspx' + _fields_ = [("cb", ctypes.c_ushort), + ("abID", ctypes.POINTER(ctypes.c_byte))] + + class ItemIDList(ctypes.Structure): + 'http://msdn.microsoft.com/en-us/library/bb773321(VS.85).aspx' + _fields_ = [("mkid", SHItemID)] + + SHGetFolderLocation = ctypes.windll.shell32.SHGetFolderLocation + SHGetPathFromIDList = ctypes.windll.shell32.SHGetPathFromIDList + def GetFolderLocation(csidl): + idl = ItemIDList() + + idl.mkid.cb = ctypes.sizeof(ctypes.c_ushort) + + pidl = ctypes.pointer(idl) + + try: + # http://msdn.microsoft.com/en-us/library/bb762180(VS.85).aspx + if SHGetFolderLocation(0, csidl, 0, 0, ctypes.byref(pidl)): + raise WindowsError(ctypes.GetLastError()) + + # http://msdn.microsoft.com/en-us/library/bb762194(VS.85).aspx + if not SHGetPathFromIDList(pidl, s_buffer): + raise WindowsError(ctypes.GetLastError()) + except WindowsError: + # if a special folder like "My Documents" cannot be resolved, use + # the path returned by SHGetFolderPath with SHGFP_TYPE_DEFAULT, + # which should be writable at least + return GetSpecialFolder(csidl) + + return s_buffer.value + +def init(reinit=False): + global inited + if inited and not reinit: + return + inited = True + + our_paths = {} + our_paths.update(init_wx()) + + sys.is_portable = False + if (not sys.DEV) or sys.opts.allow_portable: + try: + portable_paths = init_portable() + except Exception: + sys.is_portable = False + # can't print a traceback, it would go to the digsby-app.exe.log during normal app usage, + # and that file is not accesssible to users when installed in a UAC environment. + # it's also the normal case (non-portable), so who cares. + #import traceback; traceback.print_exc() + else: + sys.is_portable = True + our_paths.update(portable_paths) + + for k in our_paths: + v = path(our_paths[k]).abspath() + if v.isfile(): + continue + if not v.isdir(): + try: + v.makedirs() + except Exception: + import traceback; traceback.print_exc() + +def __res_dir(): + # XXX: Mostly copied from util.program_dir() + import locale + + if hasattr(sys, 'frozen') and sys.frozen == 'windows_exe': + prog_dir = path(sys.executable.decode(locale.getpreferredencoding())).abspath().dirname() + else: + import digsbypaths + prog_dir = path(digsbypaths.__file__).parent + + return prog_dir / 'res' + +def init_portable(): + import syck + with open(__res_dir() / "portable.yaml") as f: + usb_info = syck.load(f) + + for name in usb_info.keys(): + usb_info[name] = path(usb_info[name]).abspath() + + _set_paths(**usb_info) + + return usb_info + +def GetAppDir(): + return path(sys.executable).parent + +def GetTempDir(): + import os + for p in ('TMPDIR', 'TMP', 'TEMP'): + try: + return os.environ[p] + except KeyError: + pass + return GetFolderLocation(CSIDL.LOCAL_APPDATA) + '\\Temp' + +def init_wx_old(): + import wx + + s = wx.StandardPaths.Get() + + paths = dict(config = s.GetConfigDir(), + data = s.GetDataDir(), + documents = s.GetDocumentsDir(), + executablepath = s.GetExecutablePath(), + localdata = s.GetLocalDataDir(), + + userconfig = s.GetUserConfigDir(), + userdata = s.GetUserDataDir(), + userlocaldata = s.GetUserLocalDataDir(), + + temp = s.GetTempDir()) + + if sys.platform == 'win32': + _winpaths = [ + ('GetUserStartupDir', CSIDL.STARTUP), + ('GetStartupDir', CSIDL.COMMON_STARTUP), + ('GetUserDesktopDir', CSIDL.DESKTOP), + ('GetDesktopDir', CSIDL.COMMON_DESKTOPDIRECTORY) + ] + + for method_name, csidl in _winpaths: + setattr(wx.StandardPaths, method_name, lambda p, id=csidl: p.GetFolderLocation(id)) + + # extended wxStandardPaths folders + paths.update(userstartup = s.GetUserStartupDir(), + startup = s.GetStartupDir(), + userdesktop = s.GetUserDesktopDir(), + desktop = s.GetDesktopDir()) + + _set_paths(**paths) + return paths + +def init_wx(appname='Digsby'): + if config.platform != 'win': + # on windows we don't use wxStandardPaths at all to avoid having to have + # the app created. + return init_wx_old() + + paths = \ + [('GetConfigDir', CSIDL.COMMON_APPDATA, True), + ('GetDataDir', GetAppDir), + ('GetDocumentsDir', CSIDL.PERSONAL, False), + ('GetExecutablePath', lambda: sys.executable), + ('GetLocalDataDir', GetAppDir), + + ('GetUserConfigDir', CSIDL.APPDATA, False), + ('GetUserDataDir', CSIDL.APPDATA, True), + ('GetUserLocalDataDir', CSIDL.LOCAL_APPDATA, True), + ('GetUserLocalConfigDir', CSIDL.LOCAL_APPDATA, False), + ('GetTempDir', GetTempDir) + ] + + from util.primitives.mapping import Storage + s = Storage() + + for p in paths: + name = p[0] + if hasattr(p[1], '__call__'): + s[name] = p[1] + else: + csidl, append_app_name = p[1], p[2] + if append_app_name: + method = lambda id=csidl: GetFolderLocation(id) + '\\' + appname + else: + method = lambda id=csidl: GetFolderLocation(id) + + setattr(s, name, method) + + paths_dict = dict(config = s.GetConfigDir(), + data = s.GetDataDir(), + documents = s.GetDocumentsDir(), + executablepath = s.GetExecutablePath(), + localdata = s.GetLocalDataDir(), + + userconfig = s.GetUserConfigDir(), + userdata = s.GetUserDataDir(), + userlocaldata = s.GetUserLocalDataDir(), + userlocalconfig = s.GetUserLocalConfigDir(), + + temp = s.GetTempDir()) + + _winpaths = [ + ('GetUserStartupDir', CSIDL.STARTUP), + ('GetStartupDir', CSIDL.COMMON_STARTUP), + ('GetUserDesktopDir', CSIDL.DESKTOP), + ('GetDesktopDir', CSIDL.COMMON_DESKTOPDIRECTORY) + ] + + for method_name, csidl in _winpaths: + setattr(s, method_name, lambda id=csidl: GetFolderLocation(id)) + + # extended wxStandardPaths folders + paths_dict.update(userstartup = s.GetUserStartupDir(), + startup = s.GetStartupDir(), + userdesktop = s.GetUserDesktopDir(), + desktop = s.GetDesktopDir()) + + _set_paths(**paths_dict) + return paths_dict + +if sys.platform == 'win32': + + # use these with wxStandardPaths::GetFolderLocation + + class CSIDL(object): + DESKTOP = 0x0000 # + INTERNET = 0x0001 # Internet Explorer (icon on desktop) + PROGRAMS = 0x0002 # Start Menu\Programs + CONTROLS = 0x0003 # My Computer\Control Panel + PRINTERS = 0x0004 # My Computer\Printers + PERSONAL = 0x0005 # My Documents + FAVORITES = 0x0006 # \Favorites + STARTUP = 0x0007 # Start Menu\Programs\Startup + RECENT = 0x0008 # \Recent + SENDTO = 0x0009 # \SendTo + BITBUCKET = 0x000a # \Recycle Bin + STARTMENU = 0x000b # \Start Menu + MYDOCUMENTS = 0x000c # logical "My Documents" desktop icon + MYMUSIC = 0x000d # "My Music" folder + MYVIDEO = 0x000e # "My Videos" folder + DESKTOPDIRECTORY = 0x0010 # \Desktop + DRIVES = 0x0011 # My Computer + NETWORK = 0x0012 # Network Neighborhood (My Network Places) + NETHOOD = 0x0013 # \nethood + FONTS = 0x0014 # windows\fonts + TEMPLATES = 0x0015 + COMMON_STARTMENU = 0x0016 # All Users\Start Menu + COMMON_PROGRAMS = 0x0017 # All Users\Start Menu\Programs + COMMON_STARTUP = 0x0018 # All Users\Startup + COMMON_DESKTOPDIRECTORY = 0x0019 # All Users\Desktop + APPDATA = 0x001a # \Application Data + PRINTHOOD = 0x001b # \PrintHood + + LOCAL_APPDATA = 0x001c # \Local Settings\Applicaiton Data (non roaming) + + ALTSTARTUP = 0x001d # non localized startup + COMMON_ALTSTARTUP = 0x001e # non localized common startup + COMMON_FAVORITES = 0x001f + + INTERNET_CACHE = 0x0020 + COOKIES = 0x0021 + HISTORY = 0x0022 + COMMON_APPDATA = 0x0023 # All Users\Application Data + WINDOWS = 0x0024 # GetWindowsDirectory() + SYSTEM = 0x0025 # GetSystemDirectory() + PROGRAM_FILES = 0x0026 # C:\Program Files + MYPICTURES = 0x0027 # C:\Program Files\My Pictures + + PROFILE = 0x0028 # USERPROFILE + SYSTEMX86 = 0x0029 # x86 system directory on RISC + PROGRAM_FILESX86 = 0x002a # x86 C:\Program Files on RISC + + PROGRAM_FILES_COMMON = 0x002b # C:\Program Files\Common + + PROGRAM_FILES_COMMONX86 = 0x002c # x86 Program Files\Common on RISC + COMMON_TEMPLATES = 0x002d # All Users\Templates + + COMMON_DOCUMENTS = 0x002e # All Users\Documents + COMMON_ADMINTOOLS = 0x002f # All Users\Start Menu\Programs\Administrative Tools + ADMINTOOLS = 0x0030 # \Start Menu\Programs\Administrative Tools + + CONNECTIONS = 0x0031 # Network and Dial-up Connections + COMMON_MUSIC = 0x0035 # All Users\My Music + COMMON_PICTURES = 0x0036 # All Users\My Pictures + COMMON_VIDEO = 0x0037 # All Users\My Video + RESOURCES = 0x0038 # Resource Direcotry + + RESOURCES_LOCALIZED = 0x0039 # Localized Resource Direcotry + + COMMON_OEM_LINKS = 0x003a # Links to All Users OEM specific apps + CDBURN_AREA = 0x003b # USERPROFILE\Local Settings\Application Data\Microsoft\CD Burning + + +def _set_paths(**d): + for k in d.keys(): + v = path(d[k]).abspath() + globals()[k] = v diff --git a/digsby/src/sysident.py b/digsby/src/sysident.py new file mode 100644 index 0000000..cbfe028 --- /dev/null +++ b/digsby/src/sysident.py @@ -0,0 +1,67 @@ +import os +import uuid +import hashlib +from traceback import print_exc +from config import platformName + +def sysident(prepend = '', append = ''): + ''' + Returns a system identifier specific to this machine. + + Value may have null bytes. + ''' + + keymat = None + + try: + if platformName == 'win': + keymat = _get_key_win() + except Exception: + print_exc() + + if keymat is None: + try: + keymat = _get_key_default() + except Exception, e: + print_exc() + # jfc, what's goin on here? + keymat = 'foo' + + return hashlib.sha1(prepend + keymat + append).digest() + +def _get_key_win(): + 'Gets a system identifier from the registry.' + + import _winreg + KEY_WOW64_64KEY = 0x0100 + path = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' + + windows_info = None + try: + try: + windows_info = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, path, 0, _winreg.KEY_READ | KEY_WOW64_64KEY) + except Exception: #KEY_WOW64_64KEY not allowed on win 2000 + windows_info = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, path, 0, _winreg.KEY_READ) + dpid = _winreg.QueryValueEx(windows_info, 'DigitalProductId')[0] + id_ = _winreg.QueryValueEx(windows_info, 'InstallDate')[0] + finally: + if windows_info is not None: + windows_info.Close() + + return ''.join(str(x) for x in (dpid, id_)) + +def _get_key_default(): + 'Uses uuid to get a system identifier.' + + mac_address = uuid.getnode() + + # in accordance with the RFC, the UUID module may return a random + # number if unable to discover the machine's MAC address. this doesn't + # make for a very good key. + if mac_address == uuid.getnode(): + return str(mac_address) + else: + # this value is dependent on the computer's hostname. a weak + import platform + return os.environ.get('processor_identifier', 'OMG WHERE AM I') + ''.join(platform.uname()) + diff --git a/digsby/src/tests/__init__.py b/digsby/src/tests/__init__.py new file mode 100644 index 0000000..5cd5811 --- /dev/null +++ b/digsby/src/tests/__init__.py @@ -0,0 +1 @@ +from tests.digsby_unittest import TestCase, test_main diff --git a/digsby/src/tests/datastore/__init__.py b/digsby/src/tests/datastore/__init__.py new file mode 100644 index 0000000..aa392d8 --- /dev/null +++ b/digsby/src/tests/datastore/__init__.py @@ -0,0 +1,11 @@ +import unittest +_tests = unittest.TestSuite() +from .common import * +_tests.addTests(suite()) +from .v0 import * +_tests.addTests(suite()) + +if __name__ == '__main__': + unittest.TextTestRunner(verbosity=2).run(suite()) + + diff --git a/digsby/src/tests/datastore/common.py b/digsby/src/tests/datastore/common.py new file mode 100644 index 0000000..ee40470 --- /dev/null +++ b/digsby/src/tests/datastore/common.py @@ -0,0 +1,198 @@ +# if __name__ == '__main__': +import sys +import textwrap +import path +import unittest + +import digsbysite + +from datastore.common import DictDatastore, NestedDictDatastore, YAMLDatastore + +class DictDatastoreTests(unittest.TestCase): + def setUp(self): + self.data = { + 'emptystring': '', + 'string': 'here\'s a string', + 'zero': 0, + 'int': 1, + 'none': None, + 'empty tuple': (), + 'tuple': (1, 'foo', None), + 'empty list': [], + 'list': ['a', 'b', 'c'], + 'empty dict': {}, + 'dict': {'foo': 'bar'}, + } + self.datastore = DictDatastore(self.data) + + def tearDown(self): + del self.datastore + del self.data + + def test_keys(self): + self.assertEqual(set(self.datastore.keys()), set(self.data.keys())) + + def test_items(self): + self.assertEqual(sorted(list(self.datastore.items())), sorted(list(self.data.items()))) + + def test_get_success(self): + for key in self.datastore.keys(): + try: + val = self.datastore.get(key) + except Exception: + pass + if isinstance(val, type(self.datastore)): + val = val.data + self.assertEqual(val, self.data.get(key), key) + + def test_get_fail(self): + self.assertRaises(KeyError, lambda: self.datastore.get('bad key')) + + def test_get_default(self): + for k, v in self.datastore.items(): + default = v + val = self.datastore.get('bad key', default = default) + val = getattr(val, 'data', val) + self.assertEqual(default, val) + + def test_set_new(self): + for k, v in self.datastore.items(): + new_k = 'new ' + k + self.datastore.set(new_k, v) + new_v = self.datastore.get(new_k) + if isinstance(new_v, type(self.datastore)): + new_v = new_v.data + self.assertEqual(v, new_v) + + def test_set_existing(self): + new_value = 'previously nonexistent value' + for k in sorted(self.datastore.keys()): + self.datastore.set(k, new_value) + self.assertEqual(self.datastore.get(k), new_value) + + def test_set_empty(self): + for k, v in sorted(self.datastore.items()): + self.datastore.set(k) + self.assertRaises(KeyError, lambda: self.datastore.get(k)) + + def test_clear(self): + for k, v in self.datastore.items(): + self.datastore.clear(k) + self.assertRaises(KeyError, lambda: self.datastore.get(k)) + +class NestedDictDatastoreTests(DictDatastoreTests): + def setUp(self): + self.data = { + 'a': {'b': {'c': 0}, + 'd': {'e': 'f'}, + 'g': 'h', + }, + 'i': 'j', + } + key = lambda x: tuple(x.split('.')) + self.flat_data = dict([(key('a.b.c'), 0), + (key('a.d.e'), 'f'), + (key('a.g'), 'h'), + (key('i'), 'j')]) + + self.datastore = NestedDictDatastore(data = self.data) + + def tearDown(self): + del self.data + del self.flat_data + del self.datastore + + def test_set_new(self): + for k, v in sorted(self.datastore.items(), reverse=True): + new_k0 = (('new ' + k[0]),) + new_k = new_k0 + k[1:] + self.datastore.set(new_k, v) + new_v = self.datastore.get(new_k) + + if isinstance(new_v, type(self.datastore)): + new_v = new_v.data + self.assertEqual(v, new_v) + + def test_items(self): + items = sorted(list(self.datastore.items())) + for k, v in items: + self.assertEqual(type(k), tuple) + self.assertEqual(items, sorted(list(self.flat_data.items()))) + + def test_get_success(self): + for key in self.datastore.keys(): + try: + val = self.datastore.get(key) + except Exception: + pass + if isinstance(val, type(self.datastore)): + val = val.data + self.assertEqual(val, self.flat_data[key], (key, val, self.flat_data[key])) + + for key in self.flat_data.keys(): + if not all(isinstance(x, basestring) for x in key): + continue + string_key = '.'.join(key) + self.assertEqual(self.flat_data[key], self.datastore.get(key)) + self.assertEqual(self.flat_data[key], self.datastore.get(string_key)) + + def test_keys(self): + self.assertEqual(set(self.datastore.keys()), set(self.flat_data.keys())) + + +class YAMLDatastoreTests(NestedDictDatastoreTests): + def create_datastore(self): + filepath = path.path(__file__).parent / 'test.yaml' + with filepath.open('wb') as f: + f.write(textwrap.dedent( + ''' + --- + a: + b: + c: 0 + d: + e: f + g: h + i: j + ''' + )) + return YAMLDatastore(filepath = filepath) + + def setUp(self): + self.datastore = self.create_datastore() + key = lambda x: tuple(x.split('.')) + self.flat_data = dict([(key('a.b.c'), 0), + (key('a.d.e'), 'f'), + (key('a.g'), 'h'), + (key('i'), 'j')]) + + def tearDown(self): + del self.datastore + del self.flat_data + (path.path(__file__).parent / 'test.yaml').remove() + + def test_file_write(self): + new_dstore = self.create_datastore() + self.test_set_new() + self.test_set_existing() + new_items = set(new_dstore.items()) + my_items = set(self.datastore.items()) + self.assertEqual(my_items, new_items) + +def suite(): + s = unittest.TestSuite() + loader = unittest.TestLoader() + tests = map(loader.loadTestsFromTestCase, [ + DictDatastoreTests, + NestedDictDatastoreTests, + YAMLDatastoreTests, + ]) + + s.addTests( + tests + ) + return s + +if __name__ == '__main__': + unittest.TextTestRunner(verbosity=2).run(suite()) + #unittest.main() diff --git a/digsby/src/tests/datastore/v0.py b/digsby/src/tests/datastore/v0.py new file mode 100644 index 0000000..49c9f81 --- /dev/null +++ b/digsby/src/tests/datastore/v0.py @@ -0,0 +1,361 @@ +import path +import unittest +import hashlib +import sysident +from datastore.v0 import LoginInfo +from tests.datastore.common import YAMLDatastoreTests + +data = ''' +--- +last: !python/unicode username +users: + ? !python/unicode username + : + password: !binary | + /3m/lJnajX0= + + autologin: False + save: True + username: !python/unicode username + + ? !python/unicode veryreactive1 + : + username: !python/unicode veryreactive1 + save: False + autologin: False + password: "" + + ~: + status: "" + pos: !python/tuple + - 136 + - 106 + + "": + pos: !python/tuple + - 200 + - 200 + password: "" + autologin: False + save: False + username: "" +''' + +## Old classes for compatibility testing +import syck +import os.path +import stdpaths +import traceback +import cPickle + + +class SplashDiskStorage(object): + info_default = {'': dict(username='', + password='', + save=False, + autologin=False, + pos=(200, 200)), + None: dict()} + last_default = '' + + def get_conf_dir(self): + 'Returns (and makes, if necessary) the user application directory.' + c_dir = stdpaths.userdata + try: + return c_dir + finally: + if not os.path.exists(c_dir): + os.mkdir(c_dir) + + def get_splash_data_path(self): + return os.path.join(self.get_conf_dir(), self.data_file_name % + ('dev' if sys.REVISION == 'dev' else '')) + + def crypt_pws(self, info, codec): + for user in info: + if 'password' in info[user]: + if codec == 'encode': + encrypted_password = self.simple_crypt( + info[user]['password'].encode('utf8'), + keymat=user.encode('utf8') + ) + info[user]['password'] = encrypted_password + elif codec == 'decode': + decrypted_password = self.simple_crypt( + info[user]['password'], + keymat=user.encode('utf8') + ) + info[user]['password'] = decrypted_password.decode('utf8') + else: + raise AssertionError + + def simple_crypt(self, s, k=None, keymat=''): + raise NotImplementedError + + def save_to_disk(self, last, info): + raise NotImplementedError + + def load_from_disk(self): + raise NotImplementedError + + def can_write(self): + + fname = os.path.join(self.get_conf_dir(), '__test__') + test_data = "test" + try: + with open(fname, 'wb') as f: + f.write(test_data) + + with open(fname, 'rb') as f: + if f.read() == test_data: + return True + + return False + except Exception: + return False + finally: + try: + if os.path.exists(fname): + os.remove(fname) + except Exception: + pass + + +class NewSplashDiskStorage(SplashDiskStorage): + data_file_name = 'logininfo%s.yaml' + + def __init__(self): + SplashDiskStorage.__init__(self) + + def get_conf_dir(self): + 'Returns (and makes, if necessary) the user application directory.' + + c_dir = stdpaths.userlocaldata + try: + return c_dir + finally: + if not os.path.exists(c_dir): + os.makedirs(c_dir) + + def simple_crypt(self, s, k=None, keymat=''): + from M2Crypto.RC4 import RC4 + if k is None: + k = self._get_key(keymat) + return RC4(k).update(s) + + def _get_key(self, keymat=''): + keys = getattr(self, '_keys', None) + if keys is None: + keys = self._keys = {} + + key = keys.get(keymat, None) + if key is not None: + return key + + self._keys[keymat] = sysident.sysident(append=keymat) + + return self._keys[keymat] + + def load_from_disk(self): + path = self.get_splash_data_path() + try: + with open(path, 'rb') as f: + all_info = syck.load(f) + + if not isinstance(all_info, dict): + all_info = {} + + info = all_info.get('users', self.info_default) + last = all_info.get('last', self.last_default) + self.crypt_pws(info, 'decode') + except Exception, _e: + traceback.print_exc() + + return last, info + + def save_to_disk(self, last, info): + path = self.get_splash_data_path() + + self.crypt_pws(info, 'encode') + for_yaml = {'users': info, + 'last': last} + try: + with open(path, 'wb') as f: + syck.dump(for_yaml, f) + except Exception: + traceback.print_exc() + self.crypt_pws(info, 'decode') + return True + +## End old classes + + +class _LoginInfoTests(object): + ''' + Base class for tests in common for old "NewSplashDiskStorage" + and LoginInfo + ''' + + _filepath = path.path(__file__).parent / 'test.yaml' + + def create_datastore(self): + raise NotImplementedError + + def setUp(self): + # Part of the key to encrypt passwords is sysident, which is + # (designed to be) machine specific. In order for tests to be + # machine-independent, we're replacing it with something predictable. + def new_sysident(prepend='', append=''): + return hashlib.sha1(prepend + append).digest() + self._old_sysident = sysident.sysident + sysident.sysident = new_sysident + + self.datastore = self.create_datastore() + self.flat_data = dict([ + (('last',), u'username'), + (('users', u'username', 'username'), u'username'), + (('users', u'username', 'save'), True), + (('users', u'username', 'password'), 'password'), + (('users', u'username', 'autologin'), False), + (('users', '', 'username'), ''), + (('users', '', 'save'), False), + (('users', '', 'password'), ''), + (('users', '', 'pos'), (200, 200)), + (('users', '', 'autologin'), False), + (('users', u'veryreactive1', 'username'), u'veryreactive1'), + (('users', u'veryreactive1', 'password'), ''), + (('users', u'veryreactive1', 'save'), False), + (('users', u'veryreactive1', 'autologin'), False), + (('users', None, 'status'), ''), + (('users', 'pos'), (136, 106)), + ]) + + def tearDown(self): + sysident.sysident = self._old_sysident + del self.datastore + self._filepath.remove() + + def test_load_from_disk(self): + last, allinfo = self.datastore.load_from_disk() + self.assertEqual(last, self.flat_data.get(('last',))) + + for key in self.flat_data: + if key[0] == 'users': + key = key[1:] + else: + continue + + val = reduce(lambda a, b: a.get(b), key, allinfo) + self.assertEqual(val, self.flat_data.get(('users',) + key)) + + def test_change_password(self): + other_datastore = self.create_datastore() + _last, allinfo = self.datastore.load_from_disk() + new_password = 'new password' + username = 'veryreactive1' + key = (username, 'password') + allinfo[username]['password'] = new_password + self.datastore.save_to_disk(username, allinfo) + + other_last, other_allinfo = other_datastore.load_from_disk() + last, allinfo = self.datastore.load_from_disk() + + self.assertEqual(last, username) + self.assertEqual(allinfo[username]['password'], new_password) + + self.assertEqual(other_last, username) + self.assertEqual(other_allinfo[username]['password'], new_password) + + def test_add_new_user(self): + other_datastore = self.create_datastore() + _last, allinfo = self.datastore.load_from_disk() + new_username = 'new name' + self.assertEqual(None, allinfo.get(new_username, None)) + self.assertRaises(KeyError, lambda: allinfo[new_username]) + + allinfo[new_username] = {} + allinfo[new_username]['username'] = new_username + allinfo[new_username]['password'] = 'a password' + allinfo[new_username]['save'] = True + allinfo[new_username]['autologin'] = False + + self.datastore.save_to_disk(new_username, allinfo) + + other_last, other_allinfo = other_datastore.load_from_disk() + last, allinfo = self.datastore.load_from_disk() + + self.assertEqual(last, new_username) + self.assertEqual(other_last, new_username) + + for key in ('username', 'password', 'save', 'autologin'): + self.assertEqual(allinfo[new_username][key], + other_allinfo[new_username][key]) + + self.assertEqual(allinfo[new_username]['username'], new_username) + self.assertEqual(allinfo[new_username]['password'], 'a password') + self.assertEqual(allinfo[new_username]['save'], True) + self.assertEqual(allinfo[new_username]['autologin'], False) + + +class LoginInfoTests(_LoginInfoTests, YAMLDatastoreTests): + ''' + All the tests for a YAMLDatastore (with some overrides, see notes + in methods below) and those for NewSplashDiskStorage class. + ''' + def create_datastore(self): + with self._filepath.open('wb') as f: + f.write(data) + + return LoginInfo(filepath=self._filepath) + + def test_clear(self): + # Note: sorting them because LoginInfo requires a 'username' key in + # order to decrypt the value of 'password'. If username gets cleared + # first, password will throw an Invalid Key error + for k, _v in sorted(self.datastore.items()): + self.datastore.clear(k) + self.assertRaises(KeyError, lambda: self.datastore.get(k)) + + def test_file_write(self): + new_dstore = self.create_datastore() + self.test_set_new() + ## This doesn't work because the password doesn't get re-encrypted when + ## the username is changed. see note in LoginInfo. + #self.test_set_existing() + new_items = set(new_dstore.items()) + my_items = set(self.datastore.items()) + self.assertEqual(my_items, new_items) + + +class OldLoginInfoTests(_LoginInfoTests, unittest.TestCase): + ''' + Tests for NewSplashDiskStorage. + ''' + # Apologies for the naming - let this be a lesson to everyone, don't use + # New, Old, etc in class names. + + def create_datastore(self): + with self._filepath.open('wb') as f: + f.write(data) + + datastore = NewSplashDiskStorage() + datastore.get_splash_data_path = lambda: self._filepath + return datastore + + +def suite(): + s = unittest.TestSuite() + loader = unittest.TestLoader() + tests = map(loader.loadTestsFromTestCase, [ + OldLoginInfoTests, + LoginInfoTests, + ]) + + s.addTests( + tests + ) + return s + +if __name__ == '__main__': + unittest.TextTestRunner(verbosity=2).run(suite()) + #unittest.main() diff --git a/digsby/src/tests/debugxmlhandler.py b/digsby/src/tests/debugxmlhandler.py new file mode 100644 index 0000000..d36f313 --- /dev/null +++ b/digsby/src/tests/debugxmlhandler.py @@ -0,0 +1,62 @@ +import libxml2 +class DebugXMLHandler(libxml2.SAXCallback): + """SAX events handler for the python-only stream parser.""" + def __init__(self): + pass + + def cdataBlock(self, data): + print 'cdataBlock: %r' %data + + def characters(self, data): + print 'characters: %r' % data + + def ignorableWhitespace(self, data): + print 'ignorableWhitespace: %r' % data + + def comment(self, content): + print 'comment: %r' % content + + def endDocument(self): + print 'endDocument' + + def endElement(self, tag): + print 'endElement(%s)' % tag + + def error(self, msg): + print '### error: %s' % msg + return True + + def fatalError(self, msg): + print 'fatalError: %s' % msg + + def reference(self, name): + print 'reference: &[%s];' % name + + def startDocument(self): + print 'startDocument' + + def startElement(self, tag, attrs): + print 'startElement(%r, %r)' % (tag, attrs) + + def warning(self, msg): + print '### warning: %s' % msg + return True + + def _stream_start(self,_doc): + """Process stream start.""" + pass + + def stream_start(self,_doc): + """Process stream start.""" + pass + + def _stream_end(self,_doc): + """Process stream end.""" + pass + + def stream_end(self,_doc): + """Process stream end.""" + pass + + def _stanza(self,*a): + pass diff --git a/digsby/src/tests/digsby_unittest.py b/digsby/src/tests/digsby_unittest.py new file mode 100644 index 0000000..f91dd51 --- /dev/null +++ b/digsby/src/tests/digsby_unittest.py @@ -0,0 +1,61 @@ +import wx +import unittest + +try: + _ +except NameError: + import gettext + gettext.install('Digsby') + +if getattr(wx, 'WXPY', False): + wx.WindowClass = wx._Window +else: + wx.WindowClass = wx.Window + +try: + sentinel +except NameError: + import bootstrap + bootstrap.install_sentinel() + +class TestCase(unittest.TestCase): + def setUp(self): + if wx.GetApp() is None: + self._init_once() + + def _init_once(self): + global app + from tests.testapp import testapp + app = testapp() + + + def run(self, result=None): + if result is None: + self.result = self.defaultTestResult() + else: + self.result = result + + return unittest.TestCase.run(self, result) + + def expect(self, val, msg=None): + ''' + Like TestCase.assert_, but doesn't halt the test. + ''' + try: + self.assert_(val, msg) + except: + self.result.addFailure(self, self._exc_info()) + + def expectEqual(self, first, second, msg=None): + try: + self.failUnlessEqual(first, second, msg) + except: + self.result.addFailure(self, self._exc_info()) + + expect_equal = expectEqual + + assert_equal = unittest.TestCase.assertEqual + assert_raises = unittest.TestCase.assertRaises + + +test_main = unittest.main diff --git a/digsby/src/tests/fake/FakeProtocol.py b/digsby/src/tests/fake/FakeProtocol.py new file mode 100644 index 0000000..c9f924a --- /dev/null +++ b/digsby/src/tests/fake/FakeProtocol.py @@ -0,0 +1,114 @@ +import random + +import common +from contacts import Group, Contact +from util.observe import ObservableDict +from util import Timer + +class FakeBuddy(common.buddy): + + _renderer = 'Contact' + + def __init__(self, name, protocol): + self.status = 'unknown' + common.buddy.__init__(self, name, protocol) + + @property #required property + def idle(self): return False + @property + def online(self): return self.status != 'offline' + @property + def mobile(self): return False + @property + def status_message(self): return "FooooooBAAAAAR!!!!" + @property + def away(self): return False + @property + def blocked(self): False + +class FakeConversation(common.Conversation): + ischat = False + def __init__(self, protocol): + common.Conversation.__init__(self, protocol) + + @property + def self_buddy(self): + return self.protocol.self_buddy + + @property + def name(self): + return self.self_buddy.name + + def send_typing_status(self, status): + pass + + #really should be callsback + def _send_message(self, message, success=None, error=None, *args, **kwargs): + if message.startswith('msg'): + try: + how_long = int(message.split()[1]) + t = Timer(how_long, self.protocol.incomming_message, self.self_buddy, u"Here's your message %ds later" % how_long) + t.start() + except Exception: + pass + + def exit(self): + pass + +class FakeProtocol(common.protocol): + + name = 'fake' + NUM_BUDDIES = common.prefprop('fake.num_buddies', default=20) + + def __init__(self, username, password, hub, server=None, **options): + common.protocol.__init__(self, username, password, hub) + + self.root_group = Group('Root', self, 'Root') + self.buddies = ObservableDict() + self.self_buddy = FakeBuddy('self', self) + self.buddies['self'] = self.self_buddy + self.conversations = {} + + def Connect(self, invisible=False): + self.change_state(self.Statuses.ONLINE) + g1 = Group('Foo', self, 'Foo') + g2 = Group('Bar', self, 'Bar') + self.buddies['foobar'] = FakeBuddy('foobar', self) + #g1.append(Contact(self.buddies['foobar'], 'foobar')) + self.root_group.append(g1) + self.root_group.append(g2) + + for i in range(int(self.NUM_BUDDIES)): + g = random.choice((g1, g2)) + buddy = FakeBuddy('FakeBuddy #%d'% (i % 3), self) + buddy.status = random.choice(('away', 'available', 'offline')) + g.append(buddy) + + self.root_group.notify() + + + #needs to be added to Protocol NotImplemented + def set_message(self, message, status, format = None, default_status='away'): + pass + + #needs to be added to Protocol NotImplemented + def set_buddy_icon(self, icondata): + pass + + def Disconnect(self): + self.change_state(self.Statuses.OFFLINE) + + @property + def caps(self): + return [] + + def convo_for(self, contact): + try: + return self.conversations[contact.id] + except KeyError: + c = FakeConversation(self) + self.conversations[contact.id] = c + return c + + def incomming_message(self, buddy, message): + self.conversations.values()[0].received_message(buddy, message) diff --git a/digsby/src/tests/fake/__init__.py b/digsby/src/tests/fake/__init__.py new file mode 100644 index 0000000..641a970 --- /dev/null +++ b/digsby/src/tests/fake/__init__.py @@ -0,0 +1 @@ +from .FakeProtocol import FakeProtocol as protocol \ No newline at end of file diff --git a/digsby/src/tests/fbbenchmark.py b/digsby/src/tests/fbbenchmark.py new file mode 100644 index 0000000..8b1f630 --- /dev/null +++ b/digsby/src/tests/fbbenchmark.py @@ -0,0 +1,33 @@ +from tests.testapp import testapp +from time import clock +import os.path + +def main(): + app = testapp() + + import cPickle + + fbdata_file = os.path.join(os.path.dirname(__file__), r'fbdata.dat') + fbdata = cPickle.loads(open(fbdata_file, 'rb').read()) + + from util import Storage + account = Storage( + protocol = 'fb20', + connection = Storage( + last_stream = fbdata['stream'], + last_alerts = fbdata['alerts'], + last_status = fbdata['status'])) + + + from fb20.fbacct import FBIB + + def doit(): + before = clock() + FBIB(account).get_html(None) + return clock() - before + + print 'first ', doit() + print 'second', doit() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/fullscreen.py b/digsby/src/tests/fullscreen.py new file mode 100644 index 0000000..4b7b004 --- /dev/null +++ b/digsby/src/tests/fullscreen.py @@ -0,0 +1,103 @@ +from gui.native.win.dockconstants import * +from gui.native.docking import APPBARDATA, RECT +from functools import wraps +from gui.toolbox import Monitor +from ctypes import sizeof +import wx +import util +import util.primitives.funcs as funcs + +from logging import getLogger +log = getLogger('fullscreen') + +FULLSCREEN_CB_ID = WM_USER + 203 + +from ctypes import windll, byref +SHAppBarMessage = windll.shell32.SHAppBarMessage + +def AppBarFSCallback(func, cb_id): + @wraps(func) + def wrapper(hWnd, msg, wParam, lParam): + if msg == cb_id and wParam == ABN_FULLSCREENAPP: + func(bool(lParam)) + return wrapper + +def BindFS(win, func, cb_id): + abd = APPBARDATA() + abd.cbSize = sizeof(APPBARDATA) + print sizeof(APPBARDATA) + abd.hWnd = win.Handle + abd.rc = RECT(win.Position.x, win.Position.y, 0, 0) + abd.uCallbackMessage = cb_id + SHAppBarMessage(ABM_NEW, byref(abd)) + + win.BindWin32(cb_id, AppBarFSCallback(func, cb_id)) + +class FullscreenMonitor(object): + def __init__(self): + self.frames = {} + self.values = {} + self.check() + self.timer = util.RepeatTimer(30, self.check) + self.start = self.timer.start + self.stop = self.timer.stop + + self.on_fullscreen = funcs.Delegate() + + def __nonzero__(self): + return sum(self.values.values()) + + def check(self): + ''' + get number of displays + associate current windows with displays + destroy other windows, clear state + create new if necessary + ''' + current = set((n, tuple(m.Geometry)) for n, m in enumerate(Monitor.All())) + last_time = set(self.frames.keys()) + + new = current - last_time + dead = last_time - current + check = last_time & current + + for n, geo in dead: + f = self.frames.pop((n, geo)) + f.Destroy() + self.values.pop((n, geo)) + + for n, geo in new: + self.frames[(n, geo)] = self.new_win(n, geo) + self.values[(n, geo)] = False + + for n, geo in check: + self.fix_win(n, geo) + + def set(self, (n, geo), state): + self.values[(n, geo)] = state + log.info('fullscreen %r, %r', (n, geo), state) + log.info('fullscreen is %s', bool(self)) + log.info('fullscreen %r', self.values) + # notify listeners with True for going fullscreen + self.on_fullscreen(bool(self)) + + def fix_win(self, n, geo): + self.frames[(n, geo)].Position=geo[:2] + + def new_win(self, n, geo): + f = wx.Frame(None, -1, pos=geo[:2], size=(300, 200), name="Fullscreen Detector #%s" % n) + BindFS(f, (lambda state: self.set((n, geo), state)), FULLSCREEN_CB_ID+n) + f.Show() + return f + +def main(): + a = wx.PySimpleApp() + f = FullscreenMonitor() + + def on_fullscreen(val): print 'fullscreen!!!OMG', val + f.on_fullscreen += on_fullscreen + + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/irctest.py b/digsby/src/tests/irctest.py new file mode 100644 index 0000000..77df1b5 --- /dev/null +++ b/digsby/src/tests/irctest.py @@ -0,0 +1,8 @@ + +if __name__ == '__main__': + import irc + irc = irc.client('RIT_pfire', '', None) + irc.Connect() + + + diff --git a/digsby/src/tests/json_serialization_tests.py b/digsby/src/tests/json_serialization_tests.py new file mode 100644 index 0000000..464c8f6 --- /dev/null +++ b/digsby/src/tests/json_serialization_tests.py @@ -0,0 +1,25 @@ +import unittest +from simplejson import dumps, loads +from util.json import pydumps, pyloads + +class JSON_SerializeTestingSuite(unittest.TestCase): + + def test_serialization(self): + values = [5, 'foo', u'bar', (), (1,), (1,2), [], [1], [1,2], + set(), set('abc'), set((1,2,3)), set(((3,), (), '')), + {}, {1:2}, {'a':1}, {'1': 1}, {4.3: ''}, 4.3, {'4.3':5}, + {u'234':54}, {1:u'342'}, {1:'abc'}, {1:2,3:4,5:6}, + {'foo': set((1,2.3,'3', (), (1,2)))}, + frozenset('123'),None, {None:None}, + {True:False},u'__str__foo', + {u'__tuple__':u'foo'}, {u'__tuple__':'foo'}, + {'__tuple__':u'foo'}, {'__tuple__':u'foo'}, + ['__tuple__'],'__True__',u'__None__', + ] + for value in values + [values]: + dataout = pyloads(pydumps(value)) + self.assertEquals(value, dataout) + self.assertEqual(type(value), type(dataout)) + +if __name__ == "__main__": + unittest.main() diff --git a/digsby/src/tests/mock/__init__.py b/digsby/src/tests/mock/__init__.py new file mode 100644 index 0000000..02b7df7 --- /dev/null +++ b/digsby/src/tests/mock/__init__.py @@ -0,0 +1,2 @@ +from tests.mock.mockconversation import * +from tests.mock.mock import * \ No newline at end of file diff --git a/digsby/src/tests/mock/mock.py b/digsby/src/tests/mock/mock.py new file mode 100644 index 0000000..e78249d --- /dev/null +++ b/digsby/src/tests/mock/mock.py @@ -0,0 +1,7 @@ +from util import Storage + +class MockProtocol(Storage): + pass + +class MockContact(Storage): + pass \ No newline at end of file diff --git a/digsby/src/tests/mock/mockbuddy.py b/digsby/src/tests/mock/mockbuddy.py new file mode 100644 index 0000000..c599bd2 --- /dev/null +++ b/digsby/src/tests/mock/mockbuddy.py @@ -0,0 +1,114 @@ +__metaclass__ = type +from util.observe import Observable +from util import Storage +S = Storage +from path import path +import random +from gui import skin +from tests.mock.mockprofiles import MockProfiles + +protos = ['yahoo', 'aim', 'msn', 'jabber'] +status_messages = ''' +working on Digsby +out for lunch +homework +'''.strip().split('\n') + +from common import caps + +statuses = 'away available idle'.split() + +class MockProtocol(object): + def __init__(self, protocol): + self.name = protocol + self.self_buddy = S(name='digsby01') + self.buddies = {'digsby01': self.self_buddy} + + def get_buddy(self, name): + return MockBuddy(name) + + def group_for(self, s): + return 'group' + +class MockBuddy(Observable): + + _renderer = 'Contact' + icon_path = path(r'file:///C:/windows/Blue Lace 16.bmp') + + def __init__(self, name, status = None, protocol = 'aim', capabilities=None): + Observable.__init__(self) + self.remote_alias = self.name = name + + self.mockprofile = getattr(MockProfiles,name,'') + self.buddy = Storage() + self.buddy.name = name + self.buddy.nice_name = name + self.buddy.profile = self.mockprofile + self.icon = skin.get('BuddiesPanel.BuddyIcons.NoIcon') + self.icon_path = self.icon.path + self.icon = self.icon.PIL + self.id = 5 + self.status_message = random.choice(status_messages) + self.sightly_status = self.status_orb = self.status = status if status else random.choice(statuses) + self.buddy.away = self.status=='away' + + self.protocol = MockProtocol(protocol) + self.protocol.icq = random.choice([True, False]) + self.protocol.username = self.protocol.name + + self.mockcaps = capabilities if capabilities else [caps.BLOCKABLE, caps.EMAIL, caps.FILES, caps.IM, caps.PICTURES, caps.SMS] + self.online_time = None + self.buddy.protocol = self.protocol + self.caps = self.mockcaps + + #self.blocked = False + + + @property + def service(self): + return self.protocol.name + + @property + def serviceicon(self): + from gui import skin + return skin.get('serviceicons.%s' % self.service) + + @property + def alias(self): + return self.name + + @property + def idle(self): + return self.status == 'idle' + + @property + def info_key(self): + return self.service + '_' + self.name + + @property + def stripped_msg(self): + from util import strip_html2 + return strip_html2(self.status_message) + + def GetMockProfile(self): + return self.htmlprofile + + def idstr(self): + return u'/'.join([self.protocol.name, + self.protocol.username, + self.name]) + + + @property + def online(self): + return True + + @property + def num_online(self): + return int(self.online) + + def chat(self): + print 'wut?' + +from contacts.Contact import Contact +MockBuddy.__bases__ += (Contact,) diff --git a/digsby/src/tests/mock/mockbuddylist.py b/digsby/src/tests/mock/mockbuddylist.py new file mode 100644 index 0000000..da86d4f --- /dev/null +++ b/digsby/src/tests/mock/mockbuddylist.py @@ -0,0 +1,70 @@ +import util, wx +from tests.mock.mockbuddy import MockBuddy +from tests.mock.mockmetacontact import MockMetaContact +from gui.buddylist import BuddyList +from common import caps +import random + +away,idle,available='away','idle','available' + +class MockBuddyList(BuddyList): + def __init__(self, parent): + from contacts.Group import DGroup + + AIM=('aim',[caps.BLOCKABLE, caps.EMAIL, caps.FILES, caps.IM, caps.PICTURES, caps.SMS]) + MSN=('msn',[caps.BLOCKABLE, caps.EMAIL, caps.FILES, caps.IM]) + JBR=('jabber',[caps.EMAIL, caps.FILES, caps.IM]) + YHO=('yahoo',[caps.BLOCKABLE, caps.EMAIL, caps.FILES, caps.IM, caps.SMS]) + + self.dude=MockMetaContact( + 'MetaDude', + MockBuddy('Dude1',0,*AIM), + MockBuddy('Dude2',0,*YHO), + MockBuddy('Dude3',0,*MSN), + MockBuddy('Dude4',0,*JBR), + ) + + grp = DGroup('coworkers', [], [], [ + MockBuddy('Aaron',0,*JBR), + MockBuddy('Chris',0,*JBR), + MockBuddy('Jeff',0,*AIM), + MockBuddy('Kevin',0,*YHO), + MockBuddy('Mike',0,*MSN), + MockBuddy('Steve',0,*AIM), + self.dude + ] + ) + + + from gui.treelist import expanded_id + BuddyList.__init__(self, parent, expanded = [expanded_id(grp)]) + self.set_root([grp]) + +statuses = 'away available idle'.split() +def OnButton(event): + print 'All Away' + f.MBL.dude[0].status=random.choice(statuses) + f.MBL.dude[1].status=random.choice(statuses) + f.MBL.dude[2].status=random.choice(statuses) + f.MBL.dude[3].status=random.choice(statuses) + + + +if __name__ == '__main__': + import gettext, sys + from gui.skin import skininit + gettext.install('Digsby', './locale', unicode=True) + + app = wx.PySimpleApp() + skininit('../../../res') + f = wx.Frame(None, -1, 'BuddyList test') + f.Sizer=wx.BoxSizer(wx.VERTICAL) + f.MBL=MockBuddyList(f) + f.Sizer.Add(f.MBL,1,wx.EXPAND) + rrb=wx.Button(f,-1,'Randomize') + rrb.Bind(wx.EVT_BUTTON,OnButton) + f.Sizer.Add(rrb,0,wx.EXPAND) + f.Size=(200,400) + f.Show() + f.Bind(wx.EVT_CLOSE, lambda e: app.Exit()) + app.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/mock/mockconversation.py b/digsby/src/tests/mock/mockconversation.py new file mode 100644 index 0000000..71365a8 --- /dev/null +++ b/digsby/src/tests/mock/mockconversation.py @@ -0,0 +1,35 @@ + +from util.observe import Observable, ObservableList, ObservableDict +from tests.mock.mockbuddy import MockBuddy +from util import Storage + +from Queue import Queue + +__metaclass__ = type + + + +class MockConversation(Observable): + def __init__(self): + Observable.__init__(self) + + bud = MockBuddy('fakebuddy') + + self.name = 'fakebuddy' + self.me = MockBuddy('digsby007') + + self.room_list = ObservableList([bud, self.me]) + self.typing_status = ObservableDict() + self.buddy = bud + self.messages = Queue() + self.protocol = Storage(self_buddy = self.me, buddies = {'digsby007': self.me}) + self.ischat = False + + + def send_typing_status(self, *a): + pass + + def _send_message(self, msg): + self.sent_message(msg) + + def exit(self): pass diff --git a/digsby/src/tests/mock/mockmetacontact.py b/digsby/src/tests/mock/mockmetacontact.py new file mode 100644 index 0000000..8271453 --- /dev/null +++ b/digsby/src/tests/mock/mockmetacontact.py @@ -0,0 +1,117 @@ + +class MockMetaContact(list): + + _renderer = 'Contact' + + def __init__(self, name, *contacts): + list.__init__(self,contacts) + + @property + def away(self): + return self.status=='away' + + @property + def alias(self): + return self.name + + @property + def service(self): + return self[0].service + + @property + def idle(self): + return self.status == 'idle' + + def __hash__(self): + return hash(self.name) + + @property + def stripped_msg(self): + from util import strip_html2 + return strip_html2(self.status_message) + + @property + def online(self): + return True + + @property + def num_online(self): + return int(self.online) + @property + def name(self): + for contact in self: + if contact.status=='available': + return contact.name + + for contact in self: + if contact.status=='away': + return contact.name + + return self[0].name + + + @property + def protocol(self): + for contact in self: + if contact.status=='available': + return contact.protocol + + for contact in self: + if contact.status=='away': + return contact.protocol + + return self[0].protocol + + @property + def icon(self): + for contact in self: + if contact.status=='available': + return contact.icon + + for contact in self: + if contact.status=='away': + return contact.icon + + return self[0].icon + @property + def status_message(self): + for contact in self: + if contact.status=='available': + return contact.status_message + + for contact in self: + if contact.status=='away': + return contact.status_message + + return self[0].status_message + + @property + def status_orb(self): + return self.status + + @property + def status(self): + for contact in self: + if contact.status=='available': + return contact.status + + for contact in self: + if contact.status=='away': + return contact.status + + return self[0].status + + @property + def first_online(self): + for contact in self: + if contact.status=='available': + return contact + + for contact in self: + if contact.status=='away': + return contact + + return self[0] + + def chat(self): + print 'wut?' diff --git a/digsby/src/tests/mock/mockprofiles.py b/digsby/src/tests/mock/mockprofiles.py new file mode 100644 index 0000000..2ad81f3 --- /dev/null +++ b/digsby/src/tests/mock/mockprofiles.py @@ -0,0 +1,374 @@ +from util import Storage + +MockProfiles = Storage( + Aaron=""" +
+ + +
+ +  Aaron + +
JID:  brok3nhalo@gmail.com + +
Subscription:  Both + + +
+ +
Resource:  Gaim (5)
Status:  Free for Chat
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss +

Resource:  Psi (10)
Status:  Extended Away
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss
+
+
+
Hide Profile
+ +
+ +

+ + Full Name:  Steve Shapiro
Birthday:  11/20/1982 + +
Phone:  917-757-7555 + +
Email:  shaps776@gmail.com + +
Website:  http://www.digsby.com + +
+ Additional Information: +
Here is my extensive information section which may have a whole paragraph

+ + Home Address: (Map) +
Address Line
125 Tech Park Drive
Rochester, NY 14623

Company:  dotSyntax, LLC
Department:  Software + +
Position:  HCI Guy + +
Role:  Do stuff + +
Website:  http://www.dotsyntax.com + +
+
+ """, + Chris=""" +
+ + +
+ +  Chis + +
JID:  stelminator@gmail.com + +
Subscription:  Both + + +
+ +
Resource:  Gaim (5)
Status:  Free for Chat
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss +

Resource:  Psi (10)
Status:  Extended Away
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss
+
+
+
Hide Profile
+ +
+ +

+ + Full Name:  Steve Shapiro
Birthday:  11/20/1982 + +
Phone:  917-757-7555 + +
Email:  shaps776@gmail.com + +
Website:  http://www.digsby.com + +
+ Additional Information: +
Here is my extensive information section which may have a whole paragraph

+ + Home Address: (Map) +
Address Line
125 Tech Park Drive
Rochester, NY 14623

Company:  dotSyntax, LLC
Department:  Software + +
Position:  HCI Guy + +
Role:  Do stuff + +
Website:  http://www.dotsyntax.com + +
+
+ """, + Jeff=""" +
+ + +
+ +  Jeff + +
JID:  Jeff@aol.com + +
Subscription:  Both + + +
+ +
Resource:  Gaim (5)
Status:  Free for Chat
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss +

Resource:  Psi (10)
Status:  Extended Away
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss
+
+
+
Hide Profile
+ +
+ +

+ + Full Name:  Steve Shapiro
Birthday:  11/20/1982 + +
Phone:  917-757-7555 + +
Email:  shaps776@gmail.com + +
Website:  http://www.digsby.com + +
+ Additional Information: +
Here is my extensive information section which may have a whole paragraph

+ + Home Address: (Map) +
Address Line
125 Tech Park Drive
Rochester, NY 14623

Company:  dotSyntax, LLC
Department:  Software + +
Position:  HCI Guy + +
Role:  Do stuff + +
Website:  http://www.dotsyntax.com + +
+
+ """, + Kevin=""" +
+ + +
+ +  Kevin + +
JID:  Kevin@yahoo.com + +
Subscription:  Both + + +
+ +
Resource:  Gaim (5)
Status:  Free for Chat
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss +

Resource:  Psi (10)
Status:  Extended Away
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss
+
+
+
Hide Profile
+ +
+ +

+ + Full Name:  Steve Shapiro
Birthday:  11/20/1982 + +
Phone:  917-757-7555 + +
Email:  shaps776@gmail.com + +
Website:  http://www.digsby.com + +
+ Additional Information: +
Here is my extensive information section which may have a whole paragraph

+ + Home Address: (Map) +
Address Line
125 Tech Park Drive
Rochester, NY 14623

Company:  dotSyntax, LLC
Department:  Software + +
Position:  HCI Guy + +
Role:  Do stuff + +
Website:  http://www.dotsyntax.com + +
+
+ +

Once upon a midnight + dreary, while my thumbs grew weak and weary
+ My whole library of Zelda games laid out upon the floor
+ Each I conquered, fully finished, from original to "Minish,"
+ + Yet my thirst did not diminish for a kingdom to explore
+ Instantly I wanted more
+
+ With my catalog completed, I began to feel defeated
+ And absurdly missed the heated battles waged throughout the + war
+ RPGs, I felt, were boring, but my friends replied, ignoring,
+ + And at once began assuring that I simply would adore
+ The next game to hit to the store
+
+ So they told me, with great vigor, that this new game would + be bigger
+ Than the last hit, "Chrono Trigger," which they + took months to explore
+ + But the fighting style I hated. To attack, my turn I waited
+ As my enemy invaded, and did knock me on the floor
+ 'Twas entitled, "Evermore"
+
+ And they asked me, "Don't you love it?" But I said + that they could shove it
+ + I was sick and tired of it and its "Gotcha Last" + type war
+ Though they thought that it was uncool, I retreated to the + old school
+ And proceeded to save Hyrule like the good old days of yore
+ There I'll stay, forevermore
+ ( Add + Comment )

+ """, + Mike=""" +
+ + +
+ +  Mike + +
JID:  Mike@hotmail.com + +
Subscription:  Both + + +
+ +
Resource:  Gaim (5)
Status:  Free for Chat
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss +

Resource:  Psi (10)
Status:  Extended Away
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss
+
+
+
Hide Profile
+ +
+ +

+ + Full Name:  Steve Shapiro
Birthday:  11/20/1982 + +
Phone:  917-757-7555 + +
Email:  shaps776@gmail.com + +
Website:  http://www.digsby.com + +
+ Additional Information: +
Here is my extensive information section which may have a whole paragraph

+ + Home Address: (Map) +
Address Line
125 Tech Park Drive
Rochester, NY 14623

Company:  dotSyntax, LLC
Department:  Software + +
Position:  HCI Guy + +
Role:  Do stuff + +
Website:  http://www.dotsyntax.com + +
+
+ """, + Steve=""" +
+ + +
+ +  Steve + +
JID:  shaps776@jabber.org + +
Subscription:  Both + + +
+ +
Resource:  Gaim (5)
Status:  Free for Chat
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss +

Resource:  Psi (10)
Status:  Extended Away
Getting ready for class and then making a GUI for this IM client because it roxorssssssssss
+
+
+
Hide Profile
+ +
+ +

+ + Full Name:  Steve Shapiro
Birthday:  11/20/1982 + +
Phone:  917-757-7555 + +
Email:  shaps776@gmail.com + +
Website:  http://www.digsby.com + +
+ Additional Information: +
Here is my extensive information section which may have a whole paragraph

+ + Home Address: (Map) +
Address Line
125 Tech Park Drive
Rochester, NY 14623

Company:  dotSyntax, LLC
Department:  Software + +
Position:  HCI Guy + +
Role:  Do stuff + +
Website:  http://www.dotsyntax.com + +
+
+ """, + + Dude=""" +

Once upon a midnight + dreary, while my thumbs grew weak and weary
+ My whole library of Zelda games laid out upon the floor
+ Each I conquered, fully finished, from original to "Minish,"
+ + Yet my thirst did not diminish for a kingdom to explore
+ Instantly I wanted more
+
+ With my catalog completed, I began to feel defeated
+ And absurdly missed the heated battles waged throughout the + war
+ RPGs, I felt, were boring, but my friends replied, ignoring,
+ + And at once began assuring that I simply would adore
+ The next game to hit to the store
+
+ So they told me, with great vigor, that this new game would + be bigger
+ Than the last hit, "Chrono Trigger," which they + took months to explore
+ + But the fighting style I hated. To attack, my turn I waited
+ As my enemy invaded, and did knock me on the floor
+ 'Twas entitled, "Evermore"
+
+ And they asked me, "Don't you love it?" But I said + that they could shove it
+ + I was sick and tired of it and its "Gotcha Last" + type war
+ Though they thought that it was uncool, I retreated to the + old school
+ And proceeded to save Hyrule like the good old days of yore
+ There I'll stay, forevermore
+ ( Add + Comment )

+ """ +) \ No newline at end of file diff --git a/digsby/src/tests/network/__init__.py b/digsby/src/tests/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/tests/network/test_jabber.py b/digsby/src/tests/network/test_jabber.py new file mode 100644 index 0000000..234b727 --- /dev/null +++ b/digsby/src/tests/network/test_jabber.py @@ -0,0 +1,50 @@ +import digsbysite +import AsyncoreThread +import logging +from tests.testapp import testapp + + +connection_settings = { + 'jabber' : (('digsby@jabber.org', 'NO PASSWORD FOR YOU!', Null,), + {'hide_os': False, 'block_unknowns': False, + 'autologin': False, 'verify_tls_peer': True, + 'do_tls': True, 'plain': True, 'sasl_md5': True, + 'do_ssl': False, + 'server': ('', 443), 'require_tls': True, + 'sasl_plain': True, 'use_md5': True}), + 'gtalk' : (('digsby03@gmail.com', 'not here either, haha', Null,), + {'hide_os': False, 'block_unknowns': False, + 'autologin': False, 'verify_tls_peer': False, + 'do_tls': False, 'plain': True, 'sasl_md5': True, + 'do_ssl': True, + 'server': ('talk.google.com', 443), 'require_tls': False, + 'sasl_plain': True, 'use_md5': True}), + } + + +def fix_logging(): + logging.Logger.debug_s = logging.Logger.debug + +def main(): + fix_logging() + AsyncoreThread.start() + import wx; a = testapp('../../..') + + a.toggle_crust() + + from jabber import protocol + global jabber + jargs, jkwargs = connection_settings['gtalk'] + jabber = protocol(*jargs, **jkwargs) + jabber.Connect() + + a.MainLoop() + try: + jabber.Disconnect() + except: + pass + + AsyncoreThread.join(timeout = None) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/network/test_nonblocking_ssl.py b/digsby/src/tests/network/test_nonblocking_ssl.py new file mode 100644 index 0000000..1482c0b --- /dev/null +++ b/digsby/src/tests/network/test_nonblocking_ssl.py @@ -0,0 +1,48 @@ +import socket +import ssl +import select +from time import clock + +from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, EISCONN + +def log(msg): + print msg, clock() + +def main(): + s = socket.socket() + s.setblocking(0.0) + + while True: + log('connect') + err = s.connect_ex(('twitter.com', 443)) + if err in (EINPROGRESS, EALREADY, EWOULDBLOCK): + continue + if err in (0, EISCONN): + break + + s = ssl.wrap_socket(s, do_handshake_on_connect=False) + i = 0 + while True: + try: + log('do_handshake') + s.do_handshake() + except ssl.SSLError, err: + if err.args[0] == ssl.SSL_ERROR_WANT_READ: + log('want read') + select.select([s], [], []) + elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: + log('want write') + select.select([], [s], []) + else: + raise + except Exception: + i +=1 + print clock(), i + else: + break + + log('finished') + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/network/testasync.py b/digsby/src/tests/network/testasync.py new file mode 100644 index 0000000..5be920e --- /dev/null +++ b/digsby/src/tests/network/testasync.py @@ -0,0 +1,30 @@ +import AsyncoreThread +import logging +from tests.testapp import testapp + +def fix_logging(): + logging.Logger.debug_s = logging.Logger.debug + +def main(): + fix_logging() + AsyncoreThread.start() + import wx; a = testapp('../../..') + + a.toggle_crust() + + from oscar import protocol + oscar = protocol('digsby01', 'no passwords', user = object(), server = ('login.oscar.aol.com',5190)) + oscar.Connect() + + oscar.send_im('digsby04', u'hello world') + + a.MainLoop() + #AsyncoreThread.join(timeout = None) + +def main(): + import email + msg = 'Server: NS8.0.54.6\r\nLocation: http://www.apple.com/favicon.ico\r\nContent Type: text/html\r\nCache Control: private\r\nConnection: close' + x = email.message_from_string(msg) + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/rss/__init__.py b/digsby/src/tests/rss/__init__.py new file mode 100644 index 0000000..de4926e --- /dev/null +++ b/digsby/src/tests/rss/__init__.py @@ -0,0 +1,148 @@ +from common.accountbase import AccountBase +from util.primitives import Storage +from util.Events import EventMixin +from util.threads.timeout_thread import Timer +from social.network import SocialNetwork +from util.threads.timeout_thread import RepeatTimer +from util.threads.threadpool2 import threaded + +class RssAccount(SocialNetwork, EventMixin): + events = set(['enabled', 'disabled']) | EventMixin.events + service = protocol = 'rss' + + def __init__(self, name, password, *a, **k): + self.location = name + EventMixin.__init__(self) + self.proto = RssProtocol(self) + SocialNetwork.__init__(self, name=name, password=password, *a, **k) + self._dirty = True + self.change_state(self.Statuses.ONLINE) + + def Connect(self, *a, **k): + self.change_state(self.Statuses.ONLINE) + + def Disconnect(self, *a, **k): + self.change_state(self.Statuses.OFFLINE) + disconnect = Disconnect + + def get_enabled(self): + try: + return self._enabled + except AttributeError: + self.enabled = False + return self.enabled + + def set_enabled(self, value): + self._enabled = value + if value: + self.event('enabled') + else: + self.event('disabled') + + @property + def count(self): + return 2 + + @property + def feed(self): + return sorted(list(self.proto.feed), reverse = True) + + def observe_count(self,callback): + self.add_gui_observer(callback, 'count') + + def observe_state(self, callback): + self.add_gui_observer(callback, 'enabled') + + def unobserve_count(self,callback): + self.remove_gui_observer(callback, 'count') + + def unobserve_state(self,callback): + self.remove_gui_observer(callback) + + enabled = property(get_enabled, set_enabled) + +class RssProtocol(object): + + def __init__(self, myacct): + self.myacct = myacct + self.filter = RssFeedFilter(myacct.location) + self.filter.bind('new_data', self.got_data) + self.myacct.bind('enabled', self.start) + self.myacct.bind('disabled', self.stop) + + def start(self): + try: + print 'starting timer' + self.timer.start() + except AttributeError: + self.timer = RepeatTimer(300, self.fetch) + print 'starting timer' + self.start() + self.fetch() + + def stop(self): + try: + print 'stopping timer' + self.timer.stop() + except AttributeError: + pass + + @threaded + def fetch(self): + self.filter.update() + self.myacct._dirty = True + + def got_data(self, items): + self._dirty = True + for item in items: + print 'new rss item', item.title + +#------------------------------------------------- + + @property + def count(self): + return len(self.filter.items) + + @property + def feed(self): + return sorted(self.filter.items) + +class RssItem(Storage): + pass + +class RssFeedFilter(EventMixin): + events = set(['new_data']) | EventMixin.events + + def __init__(self, location): + self.location = location + self.items = [] + EventMixin.__init__(self) + + def update(self): + import feedparser + new_items = feedparser.parse(self.location) + items = [RssItem(item) for item in new_items['items']] + self.items = items + self.event('new_data', items) + +## +# Protocol Meta stuff +## +#protocols.rss = S( +# name = 'RSS', +# name_truncated = 'rss', +# path = 'tests.rss.RssAccount', +# username_desc = _(u'Feed URL'), +# newuser_url = '', +# password_url = '', +# alerts_desc = '', +# form = 'social', +# needs_alerts = False, +# alerts = [], +# defaults = dict(), +# whitelist_opts = set(['location']), +# type = 'social', +# notifications = {}, +# ) + + diff --git a/digsby/src/tests/scripts/open_im_window.py b/digsby/src/tests/scripts/open_im_window.py new file mode 100644 index 0000000..1260043 --- /dev/null +++ b/digsby/src/tests/scripts/open_im_window.py @@ -0,0 +1,16 @@ +# open_im_window.py + +from api import wait +from time import clock + +def main(): + before = clock() + print 'before' + yield wait(1000) + print 'after' + after = clock() + + print after - before + +# python Digsby.py --script=open_im_window.py + diff --git a/digsby/src/tests/skintest.py b/digsby/src/tests/skintest.py new file mode 100644 index 0000000..3b42086 --- /dev/null +++ b/digsby/src/tests/skintest.py @@ -0,0 +1,94 @@ +import wx +import unittest +import util +import struct +import gui +import yaml +from gui.skin import ready_skin + +class SkinTestingSuite(unittest.TestCase): + + def setUp(self): + app = wx.PySimpleApp() + print 'setting up' + gui.skin.skininit('../../res') + assert app.skin + + def testSkinTransform(self): + s = ''' +button: + font: {color: white} + menuicon: dropmenuicon.png + color: green + border: {color: black, width: 0, style: solid} + spacing: [5, 5] + image: + regions: {} + source: buttonblue.png + corners: + side: all + size: [3, 3] + style: stretch + down: + color: dark green + image: + regions: {} + source: buttonmagenta.png + corners: + side: all + size: [3, 3] + style: stretch + border: {color: black, width: 0, style: solid} + font: {color: black} + over: + color: light green + image: + regions: {} + source: buttoncyan.png + style: stretch + corners: + side: all + size: [3, 3] + font: {color: green} + border: {color: black, width: 0, style: solid} + active: + color: dark green + image: + regions: {} + source: buttonmagenta.png + corners: + side: all + size: [3, 3] + style: stretch + over: + color: light green + image: + regions: {} + source: buttoncyan.png + style: stretch + corners: + side: all + size: [3, 3] + font: {color: green} + border: {color: black, width: 0, style: solid} + border: {color: black, width: 0, style: solid} + font: {color: black} + disabled: + color: grey + image: + regions: {} + source: buttongrey.png + style: stretch + corners: + side: all + size: [3, 3] + border: 1px black solid + font: {color: light grey} +''' + s = util.to_storage(yaml.load(s)) + ready_skin(s) + print s + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/digsby/src/tests/sorttest.py b/digsby/src/tests/sorttest.py new file mode 100644 index 0000000..d12bdcd --- /dev/null +++ b/digsby/src/tests/sorttest.py @@ -0,0 +1,80 @@ +from util import * +import wx +from wx.lib.mixins.listctrl import ColumnSorterMixin +from random import randint + +class SortFrame(wx.Frame, ColumnSorterMixin): + def __init__(self, parent=None, id=-1, title="Sort Test"): + wx.Frame.__init__(self, parent, id, title) + + numColumns = 3 + + loadWindowPos(self) + self.Bind(wx.EVT_CLOSE, self.on_close) + + self.list=wx.ListCtrl(self,id, style=wx.LC_REPORT) + ColumnSorterMixin.__init__(self, numColumns) + + cols = ["Name", "Address", "Phone", "Age"] + + firsts = "Adam Laura Sara Danielle Dave Kevin Joe Mike Chris Aaron Steve Mary Sally".split() + lasts = "Clinton Bush Adams Washington Garfield Ford Kennedy Reagan Taft".split() + stNames = "Fake Apple Banana Orange Blueberry Mango".split() + sts = "Street Avenue Drive Bouelevard".split() + + # return a random element from a list + def rnd(lst): return lst[randint(0,len(lst)-1)] + + # generate a bunch of random + self.itemDataMap = {} + for i in range(500): + name = rnd(firsts) + " " + rnd(lasts) + address = str(randint(1,1000)) + " " + rnd(stNames) + " " + rnd(sts) + phone = "%d-%d-%d" % (randint(100,999), randint(100,999), randint(1000,9999)) + age = randint(1,100) + + self.itemDataMap[i] = [name,address,phone,age] + + + itemDataMap = self.itemDataMap + + keys = itemDataMap.keys() + + [self.list.InsertColumn(i, cols[i]) for i in range(len(cols))] + for i in keys: + o = itemDataMap[i] + self.list.InsertStringItem(i, o[0]) + for c in range(len(o)): + self.list.SetStringItem(i,c,str(o[c])) + self.list.SetItemData(i, i) + + self.SetColumnCount(len(cols)) + + def GetListCtrl(self): return self.list + + def on_close(self,e): + saveWindowPos(self) + self.Destroy() + +class SortApp(wx.App): + def OnInit(self): + f = SortFrame() + f.Show(True) + self.SetTopWindow(f) + + return True + +# not used yet... +class Person: + cols = ["name", "address", "phone", "age"] + + def __init__(self, **kwargs): + for k in kwargs: + self.__dict__[k] = kwargs[k] + + +if __name__ == '__main__': + wx.ConfigBase.Set(wx.FileConfig()) + app = SortApp(0) # Create an instance of the application class + app.MainLoop() # Tell it to start processing events + \ No newline at end of file diff --git a/digsby/src/tests/ssitests.py b/digsby/src/tests/ssitests.py new file mode 100644 index 0000000..4e944a7 --- /dev/null +++ b/digsby/src/tests/ssitests.py @@ -0,0 +1,155 @@ +import unittest +from oscar.ssi import item +import oscar +from struct import pack + +class SSITestingSuite(unittest.TestCase): + + def testSSIItemto_bytes(self): + testdata = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 0) + pack("!H", 0) + testSSI = item('hello', 23, 0) + + self.assertEquals(testdata, testSSI.to_bytes()) + + def testSSIItemto_bytes2(self): + testSSI1 = item('hello', 23, 0, 1) + testSSI1.add_item_to_group(15) + testdata = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 6) + \ + pack("!HH", 0xc8, 2) + pack("!H", 15) + self.assertEquals(testdata, testSSI1.to_bytes()) + + def testSSIadditemtogroup1(self): + testSSI1 = item('hello', 23, 0, 1) + testSSI1.add_item_to_group(15) + testSSI1.add_item_to_group(30) + testdata2 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 8) + \ + pack("!HH", 0xc8, 4) + pack("!H", 30) + pack("!H", 15) + self.assertEquals(testdata2, testSSI1.to_bytes()) + + def testSSIadditemtogroup2(self): + #item type 0 means this is a buddy, not a group + testSSI = item('hello', 23, 45, 0) + self.assertRaises(AssertionError, testSSI.add_item_to_group, 15) + + def testSSIadd_item_to_group_with_position_1(self): + testSSI1 = item('hello', 23, 0, 1) + testSSI1.add_item_to_group(15) + testSSI1.add_item_to_group(30) + testdata2 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 8) + \ + pack("!HH", 0xc8, 4) + pack("!H", 30) + pack("!H", 15) + self.assertEquals(testdata2, testSSI1.to_bytes()) + + def testSSIadd_item_to_group_with_position_2(self): + testSSI1 = item('hello', 23, 0, 1) + testSSI1.add_item_to_group(15) + testSSI1.add_item_to_group(30, 1) + testdata2 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 8) + \ + pack("!HH", 0xc8, 4) + pack("!H", 15) + pack("!H", 30) + self.assertEquals(testdata2, testSSI1.to_bytes()) + + def testSSIremoveitemfromgroup1(self): + testSSI1 = item('hello', 23, 0, 1) + testdata2 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 0) + testSSI1.remove_item_from_group(15) + self.assertEquals(testdata2, testSSI1.to_bytes()) + + def testSSImove_item_to_position1(self): + testdata1 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 8) + \ + pack("!HH", 0xc8, 4) + pack("!H", 30) + pack("!H", 15) + testSSI1 = oscar.unpack((('ssi','ssi'),),testdata1)[0] + testSSI1.move_item_to_position(15, 0) + testdata2 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 8) + \ + pack("!HH", 0xc8, 4) + pack("!H", 15) + pack("!H", 30) + + self.assertEquals(testdata2, testSSI1.to_bytes()) + + def testSSI_clone1(self): + testdata1 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 0) + pack("!H", 8) + \ + pack("!HH", 0xc8, 4) + pack("!H", 30) + pack("!H", 15) + testSSI1 = oscar.unpack((('ssi','ssi'),),testdata1)[0] + testSSI2 = testSSI1.clone() + + self.assertNotEquals(testSSI1, testSSI2) + self.assertEquals(testSSI1.to_bytes(), testSSI2.to_bytes()) + + def testSSI_Alias1(self): + testSSI1 = item('hello', 23, 45, 0, {0x131:"Aliasness"}) + testSSI2 = item('hello', 23, 45, 0) + + self.assertEquals(testSSI1.alias, "Aliasness") + self.assertEquals(len(testSSI1.alias), 9) + self.assertEquals(testSSI1.alias, testSSI1.get_alias()) + + self.assertEquals(testSSI1.alias, testSSI1.get_alias()) + self.assertNotEquals(testSSI1.alias, "blah") + self.assertRaises(AttributeError, stupid_assign_alias, testSSI1) + self.assertNotEquals(testSSI1.alias, "blah") + testSSI1.set_alias("blah") + self.assertEquals(testSSI1.alias, "blah") + self.assertEquals(testSSI1.get_alias(), testSSI1.get_alias()) + + self.assertNotEquals(testSSI1.to_bytes(), testSSI2.to_bytes()) + + self.assertTrue(testSSI1.alias) + testSSI1.remove_alias() + self.assertFalse(testSSI1.alias) + + self.assertEquals(testSSI1.to_bytes(), testSSI2.to_bytes()) + + def testSSI_Comment1(self): + testSSI1 = item('hello', 23, 45, 0, {0x13C:"Comment"}) + testSSI2 = item('hello', 23, 45, 0) + + + self.assertEquals(testSSI1.comment, "Comment") + self.assertEquals(len(testSSI1.comment), 7) + self.assertEquals(testSSI1.comment, testSSI1.get_comment()) + + self.assertEquals(testSSI1.comment, testSSI1.get_comment()) + self.assertNotEquals(testSSI1.comment, "blah") + self.assertRaises(AttributeError, stupid_assign_comment, testSSI1) + self.assertNotEquals(testSSI1.comment, "blah") + testSSI1.set_comment("blah") + self.assertEquals(testSSI1.comment, "blah") + self.assertEquals(testSSI1.get_comment(), testSSI1.get_comment()) + + self.assertNotEquals(testSSI1.to_bytes(), testSSI2.to_bytes()) + + self.assertTrue(testSSI1.comment) + testSSI1.remove_comment() + self.assertFalse(testSSI1.comment) + + self.assertEquals(testSSI1.to_bytes(), testSSI2.to_bytes()) + +# def get_item_position(self, idToFind): + def testSSIget_item_position1(self): + testSSI1 = item('hello', 23, 0, 1, {0xc8:[30,15]}) + + self.assertEquals(testSSI1.get_item_position(15),1) + testSSI1.move_item_to_position(15, 0) + testdata2 = pack("!H", 5) + "hello" + \ + pack("!HH", 23, 0) + pack("!H", 1) + pack("!H", 8) + \ + pack("!HH", 0xc8, 4) + pack("!H", 15) + pack("!H", 30) + + self.assertEquals(testdata2, testSSI1.to_bytes()) + + self.assertEquals(testSSI1.get_item_position(15),0) + + +def stupid_assign_alias(ssi): + ssi.alias = "blah" + +def stupid_assign_comment(ssi): + ssi.comment = "blah" + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/digsby/src/tests/testMeta.py b/digsby/src/tests/testMeta.py new file mode 100644 index 0000000..2bc1695 --- /dev/null +++ b/digsby/src/tests/testMeta.py @@ -0,0 +1,44 @@ +from collections import defaultdict +from weakref import WeakValueDictionary +from pprint import pprint + +class Resource(object): + _idhash = WeakValueDictionary() + + def __new__(cls, *a, **k): + print 'id', a[0] + + instance = super(Resource, cls).__new__(cls, *a, **k) + + + + def __init__(self, name, value): + self.name = name + self.value = value + + @staticmethod + def from_id(id, *a, **k): + try: + r = Resource._idhash[id] + except KeyError: + r = Resource._idhash[id] = Resource(*a, **k) + r.id = id + return r + else: + r.__init__(*a, **k) + return r + + def __repr__(self): + return '<%s #%d>' % (self.__class__.__name__, self.id) + + + +if __name__ == '__main__': + one = Resource(5, 'myResource', 15) + two = Resource(10, 'other', 20) + three = Resource(5, 'yet another', 25) + + print one, two, three + print one is three + + print one.test() \ No newline at end of file diff --git a/digsby/src/tests/test_calllimit.py b/digsby/src/tests/test_calllimit.py new file mode 100644 index 0000000..8d3333e --- /dev/null +++ b/digsby/src/tests/test_calllimit.py @@ -0,0 +1,27 @@ +def main(): + from gui.toolbox import calllimit + import wx + + from time import sleep, clock + print 'ready...', clock() + sleep(1.5) + print 'go' + + + a = wx.PySimpleApp() + + class Foo(object): + @calllimit(1) + def bar(self): + print 'foo', clock() + + foo = Foo() + + for x in xrange(100): + foo.bar() + + f = wx.Frame(None) + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/test_comtypes.py b/digsby/src/tests/test_comtypes.py new file mode 100644 index 0000000..8e19eb0 --- /dev/null +++ b/digsby/src/tests/test_comtypes.py @@ -0,0 +1,25 @@ +import wx +from wx.lib import iewin +import ctypes + +class IETest(iewin.IEHtmlWindow): + def DocumentComplete(self, this, pDisp, URL): + print self, this, pDisp, URL + print dir(URL) + print dir(URL.value) + print str(URL) + print URL[0] + + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None) + ie = IETest(f) + f.Show() + + ie.LoadUrl('http://www.google.com') + + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/test_fb_yesno.py b/digsby/src/tests/test_fb_yesno.py new file mode 100644 index 0000000..f3500f9 --- /dev/null +++ b/digsby/src/tests/test_fb_yesno.py @@ -0,0 +1,21 @@ + +def show_dialog(): + from facebook.fbgui import show_achievements_dialog + from facebook.fbacct import UPGRADE_QUESTION + + def success(e): + print 'success', e + show_achievements_dialog(None, 'New Facebook', UPGRADE_QUESTION, success) + +def main(): + from tests.testapp import testapp + app = testapp() + + show_dialog() + + + app.MainLoop() + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/test_gui_observers.py b/digsby/src/tests/test_gui_observers.py new file mode 100644 index 0000000..81f2636 --- /dev/null +++ b/digsby/src/tests/test_gui_observers.py @@ -0,0 +1,47 @@ +import util +import util.observe as observe +from weakref import ref + +import gc +import wx + +prefs = observe.ObservableDict() +prefs['test_attr'] = True + +if False: + def link(mapping, attr, cb): + + try: + obs = mapping._observers + except Exception: + obs = mapping._observers = [] + + obs.append(ref(cb)) +else: + def link(mapping, attr, cb): + return mapping.link(attr, cb) + +def main(): + f = wx.Frame(None) + link(prefs, 'test_attr', lambda val, show=f.Show: show()) + f.Destroy() + + weak_f = ref(f) + del f + #linked.unlink() + #del linked + gc.collect() + + + if weak_f() is not None: + print gc.get_referrers(weak_f()) + from util.gcutil import gctree + gctree(weak_f()) + wx.GetApp().MainLoop() + + assert weak_f() is None + +if __name__ == '__main__': + from tests.testapp import testapp + a=testapp()#wx.PySimpleApp() + main() diff --git a/digsby/src/tests/test_logging.py b/digsby/src/tests/test_logging.py new file mode 100644 index 0000000..397b460 --- /dev/null +++ b/digsby/src/tests/test_logging.py @@ -0,0 +1,71 @@ +''' +multithreaded logging performance test +''' +import sys +import wx +from logging import getLogger; log = getLogger('test_logging') +from threading import Thread +from contextlib import contextmanager + +class LogThread(Thread): + def __init__(self, n): + Thread.__init__(self) + self.n = n + self.profiler = None + + def run(self): + def foo(): + n = self.n + for x in xrange(n): + log.info('test %r %d', self.name, n) + + foo() + + +def main(): + from tests.testapp import testapp + with testapp(release_logging=True, plugins=False): + threads = [] + + for x in xrange(10): + threads.append(LogThread(10000)) + + def foo(): + print sys.LOGFILE_NAME + with timed(logtofile=r'c:\log.txt'): + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + foo() + + import os + os._exit(0) + +@contextmanager +def timed(name='', logtofile=None): + 'Shows the time something takes.' + + from time import time + + before = time() + try: + yield + except: + raise + else: + diff = time() - before + msg = 'took %s secs' % diff + if name: + msg = name + ' ' + msg + print msg + + if logtofile is not None: + open(logtofile, 'a').write('%f\n' % diff) + + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/test_nowplaying.py b/digsby/src/tests/test_nowplaying.py new file mode 100644 index 0000000..9ac841b --- /dev/null +++ b/digsby/src/tests/test_nowplaying.py @@ -0,0 +1,7 @@ +import nowplaying as nowplaying + +def main(): + print nowplaying.currentSong() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/test_observers.py b/digsby/src/tests/test_observers.py new file mode 100644 index 0000000..5f337dc --- /dev/null +++ b/digsby/src/tests/test_observers.py @@ -0,0 +1,23 @@ +import unittest + +class bar(object): + def bar(self): + print 'bar' + + +class AddRemoveTests(unittest.TestCase): + + def test_add_remove(self): + from util.observe import ObservableList + l = ObservableList() + b = bar() + l.add_observer(b.bar) + l.remove_observer(b.bar) + self.failIf(any(l.observers.values()), 'still have observers') + + +def main(): + unittest.main() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/test_tbleak.py b/digsby/src/tests/test_tbleak.py new file mode 100644 index 0000000..8f17b4f --- /dev/null +++ b/digsby/src/tests/test_tbleak.py @@ -0,0 +1,41 @@ +import time +import gc +import types +import sys +import util +import util.threads.threadpool as threadpool + +def is_match(x): + try: + return isinstance(x, types.TracebackType) + except: + return False + +def do_something_good(): + return 3 + 4 + +def do_something_bad(): + id() + +def main(): + threadpool.ThreadPool(15) + + for i in range(50): + util.threaded(do_something_bad)() + + time.sleep(1) + gc.collect() + + for i in range(50): + util.threaded(do_something_good)() + + for i in xrange(50): + util.threaded(sys.exc_clear)() + + time.sleep(1) + gc.collect() + + print len(filter(is_match, gc.get_objects())) + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/test_trellis.py b/digsby/src/tests/test_trellis.py new file mode 100644 index 0000000..8d80b56 --- /dev/null +++ b/digsby/src/tests/test_trellis.py @@ -0,0 +1,185 @@ + +from peak.events import trellis +from hashlib import md5 +import struct +from pprint import pformat, pprint +from operator import attrgetter +from collections import defaultdict + +class Viewer(trellis.Component): + model = trellis.attr(None) + + @trellis.perform + def view_it(self): + if self.model is not None: + pprint( list(self.model)) + + +class Buddy(trellis.Component): + + name = trellis.attr() + status = trellis.attr() + status_msg = trellis.attr() + log_size = trellis.attr() + service = trellis.attr() + + def __repr__(self): + return '' % \ + dict((a,getattr(self, a)) for a in 'name status status_msg log_size service'.split()) + +buddy_num = 0 + +services = ['foo', 'bar', 'quux'] +statuses = ['woot', 'blargh', 'wtf'] +status_messages = ['hi', 'bye', 'not here', 'lunch', 'go away'] + +buddies = {} + +import random + +def make_buddies(num): + global buddy_num + global services + global buddies + ret = [] + for _i in xrange(num): + name = "buddy %2d" % buddy_num + b = Buddy(name = name, log_size = struct.unpack('H', md5(name).digest()[:2])[0], + service = services[buddy_num % len(services)], + status = statuses[(buddy_num -1) % len(statuses)], + status_msg = status_messages[(buddy_num -2) % len(status_messages)], + ) + buddies[buddy_num] = b + ret.append(b) + buddy_num += 1 + return ret + +class NameSorter(trellis.Component): + + input = trellis.make(list) + + @trellis.maintain(optional=True) + def output(self): +# print 'name output running', id(self) + return sorted(self.input, key = lambda x: x.name) + +def merge(left, right, cmp=cmp, key = lambda x: x): + result = [] + i ,j = 0, 0 + while(i < len(left) and j < len(right)): + if cmp(key(left[i]), key(right[j])) <= 0: + result.append(left[i]) + i = i + 1 + else: + result.append(right[j]) + j = j + 1 + result += left[i:] + result += right[j:] + return result + +class Merger(trellis.Component): + input = trellis.make(list) + key = trellis.attr() #attrgetter('name') + + @trellis.maintain + def output(self): + if len(self.input) > 1: + return reduce(lambda *a, **k: merge(*a, **dict(key=self.key)), self.input) + elif len(self.input): + return self.input[0] + else: + return [] + +class Splitter(trellis.Component): + input = trellis.make(list) + basesort = trellis.attr() #NameSorter + spliton = trellis.attr() #attrgetter('status') + + @trellis.maintain + def output(self): +# print "partitions running" + if not self.input: + return {} + ret = dict() + for b in self.input: + stat = self.spliton(b) + if stat not in ret: + ret[stat] = n = self.basesort() + else: + n = ret[stat] + n.input.append(b) +# print len(ret), "partitions" + return ret + +class Splitter1(object): + def __init__(self, in_=None, splitfuncs=()): + self.in_ = in_ or [] + self.funcs = splitfuncs + def output(self): + if not self.funcs: + return self.in_ + f = self.funcs[0] + d = defaultdict(lambda: Splitter1(splitfuncs=self.funcs[1:])) + for x in self.in_: + d[f(x)].in_.append(x) + return d + +class DJoiner(trellis.Component): + input = trellis.make(list) + + @trellis.maintain + def output(self): + d = defaultdict(list) + for splitter in self.input: + o = splitter.output + for key, val in o.iteritems(): + d[key].append(val.output) + for k,v in d.iteritems(): + d[k] = Merger(input=v, key = attrgetter('name')) + return dict(d) + +class Sum(trellis.Component): + sortfunc = lambda self, x: x + input = trellis.make(dict) + @trellis.maintain + def output(self): + return sum([self.input[k].output for k in sorted(self.input.keys(), + key = self.sortfunc)], []) + + +# @trellis.maintain +# def output(self): +# print 'status output running' +# parts = self.partitions +# return sum([parts[k].output for k in sorted(parts.keys())], []) + + +# @trellis.perform +# def set_output(self): +# self._output = self.output + +buds = make_buddies(10) +buds2 = make_buddies(10) +random.shuffle(buds) +random.shuffle(buds2) + +#pprint(buds) + +print '*' * 100 +import time +a = time.clock() +foobar = Sum(input=DJoiner(input=[Splitter(input = buds, + spliton = attrgetter('status'), + basesort = NameSorter)]).output + ) +b = time.clock() +#pprint(foobar.output) +foobar.output +print b - a +a = time.clock() +buddies[3].service='foobarfoo' +b = time.clock() +#pprint(foobar.output) +foobar.output +print b - a + diff --git a/digsby/src/tests/test_trellis2.py b/digsby/src/tests/test_trellis2.py new file mode 100644 index 0000000..4b92edd --- /dev/null +++ b/digsby/src/tests/test_trellis2.py @@ -0,0 +1,239 @@ +from collections import defaultdict +import string + +input1 = ['z', '4', 'B', '8', '1', '3', 'A', 'c', 'w', '5'] +input2 = ['z', 'B', 'A', 'c', 'w', '4', '8', '1', '3', '5'] + +assert set(input1) == set(input2) + +#fs = ((lambda x: 'letters' if x in string.ascii_letters else 'other'), (lambda x: 'low' if x in string.ascii_lowercase else 'other')) +from operator import attrgetter +fs = (lambda x: -x.log_size, attrgetter('status')) + +from util import odict + +class SplitterS(object): + def __init__(self, input, funcs): + self.funcs = funcs + self._partitions = self._old_partitions = None + self.input = input + + def set_input(self, input): + self.new_input = input + self.repartition() + + def repartition(self): + self._old_partitions = self._partitions + self._partitions = self.partitions() + + def partitions(self): + f = self.funcs[0] + if f is None: + return self.input + d = defaultdict(list) + for x in self.input: + d[f(x)].append(x) + return d + + def _output(self): + if not self.funcs: + return self.input + d = {} + old = self.last_output #get our old value + parts = self._partitions #localize + for x in parts: + if x and x in old: #reuse, for caching purposes. + d[x] = old[x] + d[x].input = parts[x] + continue + d[x] = SplitterS(input=parts[x], funcs = self.funcs[1:]) + self.last_output = d + return d + +class Splitter(trellis.Component): + input = trellis.attr() + + def __init__(self, **k): + self.funcs = k.pop('funcs', ()) + trellis.Component.__init__(self, **k) + + @trellis.compute + def partitions(self): + f = self.funcs[0] + if f is None: + return self.input + d = defaultdict(list) + for x in self.input: + d[f(x)].append(x) + return d + + @trellis.maintain(initially={})#(optional=True) + def output(self): + if not self.funcs: + return self.input + d = {} + old = self.output #get our old value + parts = self.partitions #localize + depend on the partitions + for x in parts: + if x in old: #reuse, for caching purposes. + d[x] = old[x] + d[x].input = parts[x] #only need to set the input, trellis does the rest + continue + d[x] = Splitter(input=parts[x], funcs = self.funcs[1:]) + return d + + def __repr__(self): + return "" + +class GroupSplitterS(object): + def __init__(self, input, funcs): + self.groups = input + self.funcs = funcs + self.output = self._output() + + def _output(self): + funcs = self.funcs + return dict((g.name, SplitterS(input=g, funcs=funcs)) for g in self.groups) + +class GroupSplitter(trellis.Component): + input = trellis.attr() + def __init__(self, **k): + self.funcs = k.pop('funcs', ()) + trellis.Component.__init__(self, **k) + + @trellis.maintain(initially={}) + def output(self): + funcs = self.funcs + return dict((g.name, Splitter(input=g, funcs=funcs)) for g in self.input) + + def __repr__(self): + return "" + +class RootGroupSplitterS(object): + def __init__(self, input, funcs): + self.input = input + self.funcs = funcs + self.output = self._output() + + def _output(self): + funcs = self.funcs + return [GroupSplitterS(input=g, funcs=funcs) for g in self.input] + +class RootGroupSplitter(trellis.Component): + input = trellis.attr() + def __init__(self, **k): + self.funcs = k.pop('funcs', ()) + trellis.Component.__init__(self, **k) + + @trellis.maintain + def output(self): + funcs = self.funcs + return [GroupSplitter(input=g, funcs=funcs) for g in self.input] + + def __repr__(self): + return "" + +class accumulator_dict(defaultdict): + def __init__(self): + defaultdict.__init__(self, list) + + def accumulate(self, d): + for k in d: + self[k].append(d[k]) + +class GroupJoiner(trellis.Component): + input = trellis.attr() + + @trellis.maintain + def output(self): + a = accumulator_dict() + for g in self.input: + a.accumulate(g.output) + for k in a: + a[k] = SplitterJoiner(input=a[k]) + return dict(a) + + def __repr__(self): + return "" + +class GroupJoinerS(object): + def __init__(self, input): + self.input = input + self.output = self._output() + + def _output(self): + a = accumulator_dict() + for g in self.input: + a.accumulate(g.output) + for k in a: + a[k] = SplitterJoinerS(input=a[k]) + return dict(a) + + def __repr__(self): + return "" + +#g = RootGroupSplitter(input=p.blist.rootgroups, funcs = fs) +#gj = GroupJoiner(input=g.output) +#d = {} +#for k in gj.output: +# d[k] = dlocal = accumulator_dict() +# list_of_splitters = gj.output[k] +# for splitter in list_of_splitters: +# dlocal.accumulate(splitter.output) + +class SplitterJoiner(trellis.Component): + input = trellis.attr() + + @trellis.maintain + def output(self): + #accumulate in order, then sorting the keys should do the job. + d = accumulator_dict() + #1. base case + if not isinstance(self.input[0].output, dict): + #leaf merge here! + return sum([x.output for x in self.input], []) + #2. recursion + for splitter in self.input: + d.accumulate(splitter.output) + for k in d: + d[k] = SplitterJoiner(input=d[k]) + + #3. output + ret = [] + #sorted(d) would have key/cmp method(s) + for k in sorted(d): + ret.extend(d[k].output) + return ret + + + def __repr__(self): + return "" + +class SplitterJoinerS(object): + def __init__(self, input): + self.input = input + self.output = self._output() + + def _output(self): + #accumulate in order, then sorting the keys should do the job. + d = accumulator_dict() + #1. base case + if not isinstance(self.input[0].output, dict): + #leaf merge here! + return sum([x.output for x in self.input], []) + #2. recursion + for splitter in self.input: + d.accumulate(splitter.output) + for k in d: + d[k] = SplitterJoinerS(input=d[k]) + + #3. output + ret = [] + #sorted(d) would have key/cmp method(s) + for k in sorted(d): + ret.extend(d[k].output) + return ret + + + def __repr__(self): + return "" diff --git a/digsby/src/tests/test_yaml_bug.py b/digsby/src/tests/test_yaml_bug.py new file mode 100644 index 0000000..fd5261a --- /dev/null +++ b/digsby/src/tests/test_yaml_bug.py @@ -0,0 +1,23 @@ +import syck + +# this yaml has duplicate keys +# and makes the parser return inconsistent data + +fail_yaml =''' +one: two +one: three +four: five +four: six +six: seven +six: eight +''' + +def test_syck(): + s = fail_yaml + + + for x in xrange(100): # really make sure :) + assert syck.load(s) == syck.load(s) + +if __name__ == '__main__': + test_syck() diff --git a/digsby/src/tests/testapp.py b/digsby/src/tests/testapp.py new file mode 100644 index 0000000..7a41561 --- /dev/null +++ b/digsby/src/tests/testapp.py @@ -0,0 +1,210 @@ +import sys +import wx + +import ctypes +try: + ctypes.windll.dwmapi +except Exception: + pass +else: + import gui.vista + +# for side effects: +import digsbysite + +import util +import stdpaths +from util.primitives.funcs import Delegate +from util.primitives.mapping import Storage + +def discover_digsby_root(): + ''' + Finds the digsby root directory somewhere at or above the current + working directory. Raises an Exception if it can't be found. + ''' + + import os.path + return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) + +def _isdigsbyroot(p): + print >> sys.stderr, "isdigsbyroot?", p + return (p / 'res' / 'skins' / 'default').isdir() + +class TestApp(wx.App): + def __enter__(self): + pass + def __exit__(self, exc_type, exc_value, tb): + if exc_type is None: + # don't start MainLoop is there's an exception + self.MainLoop() + +wxMSW = 'wxMSW' in wx.PlatformInfo + +def testapp(pypath = None, appname = 'Digsby', skinname = 'default', prefs = None, username = 'megazord', + on_message = lambda message: None, + plugins = True, logging = True): + 'A test application framework for test __main__s.' + + + if wxMSW: preload_comctrls() + + import gettext, os.path + + import options + sys.opts, _args = options.parser.parse_args() + import logextensions + + digsbysite.COLORIZE_EXCEPTIONS = sys.opts.console_color + + # Install gui elements + gettext.install(appname, unicode=True) + + from bootstrap import install_N_ + install_N_() + + # Create the app + app = TestApp() + app.SetAppName(appname) + + # initialize stdpaths + from stdpaths import init + init() + + if wxMSW: + import gui.native.win.winutil as winutil + winutil.disable_callback_filter() + + from gui import skin + from gui.toolbox import setuplogging + if logging: + import logging + setuplogging(level=logging.INFO) + + + # make wxLogError go to stderr, not popup dialogs + wx.Log.SetActiveTarget(wx.LogStderr()) + + app.PreShutdown = Delegate() + + if pypath is None: + pypath = discover_digsby_root() + + sys.path.insert(0, pypath) + + skin.set_resource_paths([ + util.program_dir() / 'res', # Apparently this has to be first? + stdpaths.userdata, + stdpaths.config, + ]) + + if plugins: + from main import init_plugins + app.plugins = init_plugins() + else: + app.plugins = [] + + + skin.skininit(os.path.join(pypath, 'res'), skinname = skinname) + + from util.threads.threadpool import ThreadPool + ThreadPool(5) + + from prefs.prefsdata import flatten + import syck + from util.observe import ObservableDict + + + prefs_path = os.path.join(pypath, 'res', 'defaults.yaml') + + prefs = ObservableDict(prefs) if prefs is not None else ObservableDict() + prefs.update({'appearance.skin': skinname, + 'appearance.variant': None, + 'debug.shell.font': shellfont()}) + import common + common.set_active_prefs(prefs, {}) + + from util.observe import ObservableDict + + sys.modules['digsbyprofile'] = Storage() + import digsbyprofile + from common.notifications import default_notifications + p = digsbyprofile.profile = Storage(name = username, + username = username, + prefs = prefs, + on_message = on_message, + notifications = default_notifications) + + + + f = file(prefs_path) + defaults = Storage(flatten(syck.load(f))) + f.close() + user = ObservableDict(defaults) + user.update(prefs) + + from prefs.prefsdata import localprefs + import prefs + p.defaultprefs = prefs.defaultprefs() + p.localprefs = localprefs() + + import common + common.setfakeprefs(user) + + def toggle_prefs(user=user, defaults=defaults): + import prefs + prefs.edit(user, defaults, None) + + def toggle_crust(app=app): + if not getattr(app, 'crust', None): + import gui.shell + wins = wx.GetTopLevelWindows() + parent = wins[0] if wins else None + app.crust = gui.shell.PyCrustFrame(None) + if parent is not None: + parent.crust = app.crust + app.crust.Bind(wx.EVT_CLOSE, lambda evt: app.Exit()) + app.crust.toggle_shown() + + # keyboard focus goes to shell prompt + if app.crust.IsShown(): + app.crust.crust.SetFocus() + + def on_key(e): + code = e.GetKeyCode() + if code == wx.WXK_F11: + toggle_prefs() + elif code == wx.WXK_F12: + toggle_crust() + elif code == wx.WXK_F5: + from gui import skin + skin.reload() + else: + e.Skip() + + app.Bind(wx.EVT_KEY_DOWN, on_key) + app.toggle_crust = toggle_crust + + from main import initialize_webkit, SetStatusPrompt + initialize_webkit() + app.SetStatusPrompt = SetStatusPrompt + + return app + +if 'wxMSW' in wx.PlatformInfo: + def preload_comctrls(): + # preload comctrls to prevent a TrackMouseEvent crash + # http://trac.wxwidgets.org/ticket/9922 + from ctypes import windll + windll.comctl32.InitCommonControls() + +def shellfont(): + try: + import ctypes + ctypes.windll.dwmapi + except: + font = 'Courier New' + else: + font = 'Consolas' + + return font + diff --git a/digsby/src/tests/testbuddywatcher.py b/digsby/src/tests/testbuddywatcher.py new file mode 100644 index 0000000..8b3b01b --- /dev/null +++ b/digsby/src/tests/testbuddywatcher.py @@ -0,0 +1,33 @@ +from common.buddy_watcher import BuddyWatcher + +def main(): + watcher = BuddyWatcher() + messages = ('out to lunch', 'busy right now', 'free for chat', '<3','oh hai can haz cookie?', 'save me jebus') + states = 'idle away online offline'.split() + + names = 'mario luigi sonic tails batman robin peanutbutter jelly homer marge'.split() + + buddies = [BuddyState() for _ in range(10)] + + for name,buddy in zip(names, buddies): + buddy.name = name + watcher.register(buddy) + + + from time import sleep + import random + + for i in range(200): + buddy = random.choice(buddies) + oldmsg, oldsta = buddy.message, buddy.status + buddy.message = random.choice(messages) + buddy.status = random.choice(states) + print buddy.name, oldsta, '->', buddy.status, '//', repr(oldmsg), '->', repr(buddy.message or ''), + watcher.on_change(buddy) + print + + for buddy in buddies: + watcher.unregister(buddy) + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testcalllimit.py b/digsby/src/tests/testcalllimit.py new file mode 100644 index 0000000..8c86450 --- /dev/null +++ b/digsby/src/tests/testcalllimit.py @@ -0,0 +1,23 @@ +import wx +from time import time +from gui.toolbox import calllimit + +class MyFrame(wx.Frame): + + @calllimit(1) + def foo(self, e): + self.Title = str(time()) + +def test_calllimit(): + f = MyFrame(None) + b = wx.Button(f, -1, 'foo') + b.Bind(wx.EVT_BUTTON, f.foo) + f.Show() + +def main(): + a = wx.PySimpleApp() + test_calllimit() + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testencodings.py b/digsby/src/tests/testencodings.py new file mode 100644 index 0000000..9155067 --- /dev/null +++ b/digsby/src/tests/testencodings.py @@ -0,0 +1,17 @@ +import util.auxencodings +from timeit import Timer + +def main(): + print Timer("assert s.encode('xml').decode('xml') == s", + 'from __main__ import s').timeit(50) + + +s = '''Google
kevinwatters@gmail.com | iGoogle | My Account | Sign out

Google

 
  Advanced Search
  Preferences
  Language Tools


Advertising Programs - Business Solutions - About Google

©2008 Google

+''' + +if __name__ == '__main__': + print len(s) + main() \ No newline at end of file diff --git a/digsby/src/tests/testfavicons.py b/digsby/src/tests/testfavicons.py new file mode 100644 index 0000000..3ba4e49 --- /dev/null +++ b/digsby/src/tests/testfavicons.py @@ -0,0 +1,92 @@ +import wx + +import digsbysite +import netextensions +import common.favicons +from tests.testapp import testapp + + +## +## DISABLE USE OF threaded +## +common.favicons.use_threaded = False + + + + +def main(): + a = testapp() + f = wx.Frame(None, title = 'Favicon Test', size = (300, 600)) + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + common.favicons.clear_cache() + + # refresh when new favicons come in + common.favicons.on_icon.append(lambda domain: f.Refresh(), obj=f) + + domains = [ + 'rebates.riteaid.com', # server doesn't give "reason phrase" for http response + 'message.myspace.com', # goes to myspace.com + 'usaa.com', # ssl + 'www.53.com', # ssl + 'technion.ac.il', # israel, may not resolve + 'ebay.com', + 'facebookmail.com', + 'gmail.com', + 'livejournal.com', + 'google.com', + 'digsby.com', + 'aol.com', + 'yahoo.com', + 'hotmail.com', + 'reddit.com', + 'slashdot.com', + 'meebo.com', + 'clearspring.com', + 'rge.com', + 'amazon.com', + 'apple.com', # BROKEASS MAC HEADERS + 'nvidia.com', + 'uq.edu.au', + 'hotmail.co.uk', + 'autodeskcommunications.com', + 'email.hsbcusa.com', + 'bugs.python.org', + 'outpost.com', # has big red F but it never shows the image... however, dimensions are determined + 'connect.vmware.com', + 'reply1.ebay.com', + 'wellsfargo.com', + #'gmx.net', # has + 'bose.com', # blank looking icon (white square) + 'excelsiorpkg.com', + 'namesdatabase.com', # ssl? + 'fly.spiritairlines.com', # has a weird redirect- url contains port + 'www.dominos.com', #says "You Got...Lost" when going to www.dominos.com/favicon.ico, but firefox definitely has one. + ] + + x = 5 + + def paint(e): + + dc = wx.AutoBufferedPaintDC(f) + dc.SetFont(f.Font) + dc.SetBrush(wx.WHITE_BRUSH) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangleRect(f.ClientRect) + + y = 0 + for domain in domains: + icon = common.favicons.favicon(domain) + dc.DrawText(domain, x + (icon.Width if icon is not None else 16) + 5, y) + if icon is not None: + dc.DrawBitmap(icon, x, y, True) + + y += icon.Height if icon is not None else 16 + + f.Bind(wx.EVT_PAINT, paint) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testframe.py b/digsby/src/tests/testframe.py new file mode 100644 index 0000000..c9f4db0 --- /dev/null +++ b/digsby/src/tests/testframe.py @@ -0,0 +1,67 @@ +import wx + +from util import odict + +class TestFrame(wx.Frame): + def __init__(self,title = "Test - "): + + wx.Frame.__init__(self,None,-1,title) + + self.MenuBar = wx.MenuBar() + + self.menumap = {} + + menus = odict() + menus['System'] = [('&About' , self.OnAbout , 'About this test' ), + ('-' ), + ('&Console', self.OnConsole, 'Digsby Shell' ), + ('&Prefs' , self.OnPrefs , 'Advanced Preferences'), + ('-' ), + ('E&xit' , self.OnExit , 'Terminate Test App' )] + + + + self.AddMenu(menus) + + Bind = self.Bind + Bind(wx.EVT_MENU,self.OnMenu) + Bind(wx.EVT_CLOSE, self.OnClose) + + + def AddMenu(self,menumeta): + for menutitle in menumeta: + wxmenu = wx.Menu() + for item in menumeta[menutitle]: + if item[0] == '-': + wxmenu.AppendSeparator() + continue; + itemid = wx.NewId() + self.menumap[itemid] = item[1] + wxmenu.Append(itemid, item[0], item[2]) + self.MenuBar.Append(wxmenu, menutitle) + + def OnMenu(self,event): + self.menumap[event.Id]() + + def OnAbout(self): + print 'OnAbout' + + def OnConsole(self): + print 'OnConsole' + + def OnPrefs(self): + print 'OnPrefs' + + def OnExit(self): + print 'OnExit' + self.Close() + + def OnClose(self,event): + wx.GetApp().Exit() + + + + + + + diff --git a/digsby/src/tests/testgui/__init__.py b/digsby/src/tests/testgui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/tests/testgui/gfxlist/gfxlist.py b/digsby/src/tests/testgui/gfxlist/gfxlist.py new file mode 100644 index 0000000..d39a0c6 --- /dev/null +++ b/digsby/src/tests/testgui/gfxlist/gfxlist.py @@ -0,0 +1,108 @@ +import wx, math +from random import randint + +if 'wxMac' in wx.PlatformInfo: + PDC = wx.PaintDC +else: + PDC = wx.AutoBufferedPaintDC + +class GFXList(wx.Panel): + + def __init__(self, parent): + wx.Panel.__init__(self, parent, style=wx.FULL_REPAINT_ON_RESIZE) + + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_TIMER,self.OnAnimate) + self.theta = 0 + self.scale = 1 + + self.count=0 + self.FPS=0 + + self.rr=33 + + self.timer=wx.Timer() + self.timer.SetOwner(self,0) + self.timer.Start(self.rr) + + + self.timer2=wx.Timer() + self.timer2.SetOwner(self,1) + self.timer2.Start(1000) +# self.Bind(wx.EVT_IDLE, self.OnAnimate) +# + def OnAnimate(self, e): + if e.Id==0: + self.theta += 0.1 + self.Refresh() + elif e.Id==1: + self.FPS= self.count + self.count=0 + + def OnPaint(self, e): + + self.count+=1 + + dc = PDC(self) + gc = wx.GraphicsContext.Create(dc) + + + gc.Scale(*([self.scale]*2)) + path = gc.CreatePath() + path.AddRectangle(0,0, *self.Rect[2:]) + + path2 = gc.CreatePath() + + for i in xrange(30): + path2.AddRectangle(randint(0,400),randint(0,400),50,50,) + + gc.SetPen(wx.Pen(wx.Colour(0,0,128), 3)) + gc.SetBrush(gc.CreateLinearGradientBrush(0,0,100,100,wx.GREEN,wx.BLUE)) + gc.DrawPath(path) + + gc.SetBrush(gc.CreateLinearGradientBrush(0,0,100,100,wx.RED,wx.BLUE)) + gc.DrawPath(path2) + + for fs in xrange(30, 100, 10): + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + font.SetPointSize(fs) + gc.SetFont(font, wx.Colour(randint(0,255),randint(0,255),randint(0,255))) + gc.DrawRotatedText('hello digsby', 40,80, self.theta if fs%20==0 else -self.theta) + +# font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) +# font.SetPointSize(72) +# gc.SetFont(font, wx.WHITE) +# gc.DrawRotatedText('hello digsby', 40,80, self.theta)#, wx.TRANSPARENT_BRUSH) + font.SetPointSize(15) + gc.SetFont(font, wx.WHITE) + gc.DrawText("FPS: "+str(self.FPS)+"/"+str(1000.0/self.rr),0,0) + gc.DrawText("Time: "+str(wx.GetLocalTime()),0,17) + gc.DrawText("Process id: "+str(wx.GetProcessId()),0,34) + gc.DrawText("OS: "+str(wx.GetOsDescription()),0,51) + + +def main(): + try: import psyco; psyco.full() + except ImportError: pass + + app = wx.PySimpleApp() + + f = wx.Frame(None, -1, 'gfxlist test', size=(250,700)) + f.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + gfx = GFXList(f) + sz.Add(gfx, 1, wx.EXPAND) + + s = wx.Slider(f, -1, 100, 100, 500) + def onslide(e): + gfx.scale = s.Value / 100.0 + + s.Bind(wx.EVT_SLIDER, onslide) + sz.Add(s, 0, wx.EXPAND) + f.Show() + + + app.MainLoop() + + +if __name__ == '__main__': main() diff --git a/digsby/src/tests/testgui/imagetest.py b/digsby/src/tests/testgui/imagetest.py new file mode 100644 index 0000000..ae27fab --- /dev/null +++ b/digsby/src/tests/testgui/imagetest.py @@ -0,0 +1,50 @@ +from time import clock +start_time = clock() + +import gc +import os +import sys +import wx +from os.path import join as pathjoin +from random import shuffle + +imgexts = 'png gif bmp ico jpg jpeg'.split() + +def isimage(filename): + e = filename.endswith + return any(e(ext) for ext in imgexts) + +def images(path): + for name in os.listdir(path): + if isimage(name): + yield pathjoin(path, name) + +def all_images(path): + for root, dirs, files in os.walk(path): + for file in files: + f = pathjoin(root, file) + if isimage(f): + yield f + +def main(): + a = wx.PySimpleApp() + + imgs = list(all_images('res'))[:20] + #imgs = ['res/happynewdigsby.png'] + + print len(imgs), 'images' + #shuffle(imgs) + + excludes = () + + for img in imgs: + if img not in excludes: + print >> sys.stderr, img + wx.Bitmap(img) + + + print clock() - start_time + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/skin/__init__.py b/digsby/src/tests/testgui/skin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/tests/testgui/skin/testskinparse.py b/digsby/src/tests/testgui/skin/testskinparse.py new file mode 100644 index 0000000..f5304e2 --- /dev/null +++ b/digsby/src/tests/testgui/skin/testskinparse.py @@ -0,0 +1,71 @@ +import wx +from gui.skin.skinparse import makeImage +from gui import skin +from gui.skin.skinparse import makeImage, makeFont, makeBrush + +def test_splitimage(): + f = wx.Frame(None, style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE) + + s = makeImage(skin.resourcedir() / 'digsbybig.png')#si4(S(source = 'c:\\digsbybig.png', center = {'extend': 'up down'}, + # x1 = 30, x2 = -30, y1 = 30, y2 = -30)) + + def paint(e): + dc= wx.AutoBufferedPaintDC(f) + dc.Brush = wx.BLACK_BRUSH + dc.DrawRectangleRect(f.ClientRect) + s.Draw(dc, f.ClientRect) + + f.Bind(wx.EVT_PAINT, paint) + f.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + return f + + +def test_skinparsing(): + cols = 'AQUAMARINE, BLACK, BLUE, BLUE VIOLET, BROWN, CADET BLUE, CORAL, CORNFLOWER BLUE, CYAN, DARK GREY, DARK GREEN, DARK OLIVE GREEN, DARK ORCHID, DARK SLATE BLUE, DARK SLATE GREY DARK TURQUOISE, DIM GREY, FIREBRICK, FOREST GREEN, GOLD, GOLDENROD, GREY, GREEN, GREEN YELLOW, INDIAN RED, KHAKI, LIGHT BLUE, LIGHT GREY, LIGHT STEEL BLUE, LIME GREEN, MAGENTA, MAROON, MEDIUM AQUAMARINE, MEDIUM BLUE, MEDIUM FOREST GREEN, MEDIUM GOLDENROD, MEDIUM ORCHID, MEDIUM SEA GREEN, MEDIUM SLATE BLUE, MEDIUM SPRING GREEN, MEDIUM TURQUOISE, MEDIUM VIOLET RED, MIDNIGHT BLUE, NAVY, ORANGE, ORANGE RED, ORCHID, PALE GREEN, PINK, PLUM, PURPLE, RED, SALMON, SEA GREEN, SIENNA, SKY BLUE, SLATE BLUE, SPRING GREEN, STEEL BLUE, TAN, THISTLE, TURQUOISE, VIOLET, VIOLET RED, WHEAT, WHITE, YELLOW, YELLOW GREEN' + cols = filter(lambda s: s.find(' ') == -1, cols.split(', ')) + + import random + random.shuffle(cols) + + f = wx.Frame(None, style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE) + + font = makeFont("comic sans ms 35 bold italic underlined") + popupshadow = 'popup.png 14 14 -18 -18' + #bb = ['white border black', popupshadow, 'actions/bulb.png', 'actions/email.png'] + #bb = popupshadow + bb = 'white border black' + brush = makeBrush(bb)#'vertical red black 40% border dashed 5px') + g = [makeBrush('%s %s' % tuple(cols[c:c+2])) for c in xrange(0, len(cols)-2, 2)] + #g = [makeBrush(c) for c in cols] + + def paint(e): + dc = wx.AutoBufferedPaintDC(f) + dc.Pen = wx.TRANSPARENT_PEN + dc.Brush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)) + + dc.DrawRectangleRect(f.ClientRect) + dc.Font = font + dc.SetTextForeground(wx.WHITE) + + r = f.ClientRect + r.Deflate(17, 17) + brush.Draw(dc, r) + + dc.DrawText('Digskin', 0, 0) + + f.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + f.Bind(wx.EVT_PAINT, paint) + + + f.Sizer = wx.BoxSizer(wx.HORIZONTAL) + f.Sizer.AddStretchSpacer(1) + return f + +def main(): + from tests.testapp import testapp + a = testapp() + test_skinparsing().Show() + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/test_autodc.py b/digsby/src/tests/testgui/test_autodc.py new file mode 100644 index 0000000..fe132da --- /dev/null +++ b/digsby/src/tests/testgui/test_autodc.py @@ -0,0 +1,31 @@ +''' +test AutoBufferedPaintDC +''' +import wx + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None, -1, 'AutoBufferedPaintDC test') + f.BackgroundStyle = wx.BG_STYLE_CUSTOM + + def paint(e): + #dc = wx.PaintDC(f) # 1) this one works + #dc = wx.AutoBufferedPaintDC(f) # 2) this one works also + dc = wx.AutoBufferedPaintDC(f) # 3) this one results in a traceback + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.RED_BRUSH) + dc.DrawRectangle(20, 20, 30, 30) + + gc = wx.GraphicsContext.Create(dc) # XXX the traceback occurs here + gc.SetPen(wx.TRANSPARENT_PEN) + gc.SetBrush(wx.BLUE_BRUSH) + gc.DrawRectangle(40, 40, 30, 30) + + f.Bind(wx.EVT_PAINT, paint) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/test_gdileaks.py b/digsby/src/tests/testgui/test_gdileaks.py new file mode 100644 index 0000000..63ce261 --- /dev/null +++ b/digsby/src/tests/testgui/test_gdileaks.py @@ -0,0 +1,10 @@ +from tests.testapp import testapp + +def test_popupleak(): + from gui.toast import popup + popup().cancel() + +if __name__ == '__main__': + a = testapp() + test_popupleak() + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/test_jumplist.py b/digsby/src/tests/testgui/test_jumplist.py new file mode 100644 index 0000000..b25fe8e --- /dev/null +++ b/digsby/src/tests/testgui/test_jumplist.py @@ -0,0 +1,22 @@ +import wx + +APP_ID = u'TestJumpListApp' + +def set_app_id(): + import ctypes + + try: + SetAppID = ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID + except AttributeError: + return + + SetAppID(APP_ID) + +def main(): + set_app_id() + app = wx.App() + import cgui + assert cgui.SetUpJumpList(APP_ID, [(u'test', u'bc', u're', 4)]) + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/test_longlist.py b/digsby/src/tests/testgui/test_longlist.py new file mode 100644 index 0000000..1c97ae4 --- /dev/null +++ b/digsby/src/tests/testgui/test_longlist.py @@ -0,0 +1,22 @@ +''' +creates a virtual list big enough to induce bug #3107 (black buddylist.) +''' + +import Digsby, wx +from cgui import SkinVList + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None) + + def draw(dc, rect, n): + dc.DrawText(str(n), rect.x, rect.y) + + l = SkinVList(f) + l.SetHeights([20,20,35,18] * 130) + l.SetDrawCallback(draw) + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/test_regioning.py b/digsby/src/tests/testgui/test_regioning.py new file mode 100644 index 0000000..9c9c8ab --- /dev/null +++ b/digsby/src/tests/testgui/test_regioning.py @@ -0,0 +1,26 @@ +import wx +wx.App() +from gui.windowfx import ApplySmokeAndMirrors + +class MyWindow(wx.Window): + def __init__(self, parent): + wx.Window.__init__(self, parent) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def OnPaint(self, e): + dc = wx.PaintDC(self) + + ApplySmokeAndMirrors(self, wx.Bitmap(r'c:\dev\digsby\res\digsbyclaus.png')) + + dc.SetBrush(wx.RED_BRUSH) + dc.DrawRectangle(*self.ClientRect) + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None) + c = MyWindow(f) + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/test_roomlist.py b/digsby/src/tests/testgui/test_roomlist.py new file mode 100644 index 0000000..2d3724c --- /dev/null +++ b/digsby/src/tests/testgui/test_roomlist.py @@ -0,0 +1,53 @@ +import wx +from tests.mock.mockbuddy import MockBuddy +from common import caps +from util.observe import ObservableList + +from gui.imwin.roomlist import RoomListPanel + +def main(): + from tests.testapp import testapp + a = testapp(skinname='Windows 7') + f = wx.Frame(None, -1, 'roomlist') + + + AIM=('aim', [caps.BLOCKABLE, caps.EMAIL, caps.FILES, caps.IM, caps.PICTURES, caps.SMS]) + MSN=('msn', [caps.BLOCKABLE, caps.EMAIL, caps.FILES, caps.IM]) + JBR=('jabber', [caps.EMAIL, caps.FILES, caps.IM]) + YHO=('yahoo', [caps.BLOCKABLE, caps.EMAIL, caps.FILES, caps.IM, caps.SMS]) + + global contacts + contacts = ObservableList([MockBuddy('Aaron', 'away', *JBR), + MockBuddy('Chris', 'available', *JBR), + MockBuddy('Jeff', 'offline', *AIM), + MockBuddy('Kevin', 'away', *YHO), + MockBuddy('Mike', 'available', *MSN), + + MockBuddy('Steve', 'offline', *AIM),]) + + buddies = dict((c.name, c) for c in contacts) + + contacts.extend([ + MockBuddy('Agatha', 'offline', *AIM), + MockBuddy('Abel', 'away', *YHO), + MockBuddy('Adam', 'available', *MSN), + MockBuddy('Amanda', 'offline', *AIM), + MockBuddy('Beatrice', 'offline', *AIM), + MockBuddy('Betty', 'away', *YHO), + MockBuddy('Brian', 'available', *MSN), + MockBuddy('Biff', 'away', *YHO), + MockBuddy('Bart', 'available', *MSN), + ]) + + + rl = RoomListPanel(f, buddies) + rl.RoomList = contacts + + f.SetSize((200,400)) + f.Show() + + a.MainLoop() + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/test_snap.py b/digsby/src/tests/testgui/test_snap.py new file mode 100644 index 0000000..552ad9a --- /dev/null +++ b/digsby/src/tests/testgui/test_snap.py @@ -0,0 +1,12 @@ +import wx +import gui.snap + +def main(): + app = wx.PySimpleApp() + f = wx.Frame(None) + f.Snap = True + f.Show() + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/test_splines_leak.py b/digsby/src/tests/testgui/test_splines_leak.py new file mode 100644 index 0000000..fa439c6 --- /dev/null +++ b/digsby/src/tests/testgui/test_splines_leak.py @@ -0,0 +1,25 @@ +import wx + +def main(): + from tests.testapp import testapp + + with testapp(): + f = wx.Frame(None) + def paint(e): + i = 1 + dc = wx.PaintDC(e.GetEventObject()) +# dc.DrawSplines(((0, 0), (100, 5), (350, 200))) + for _x in xrange(100000): +# i = i + i + dc.DrawSplines(((0, 0), (100, 5), (350, 200))) +# dc.DrawLine(0, 0, 50, 50) + import gc + gc.collect() + + f.Bind(wx.EVT_PAINT, paint) + f.Show() + + +if __name__ == '__main__': + main() + diff --git a/digsby/src/tests/testgui/test_taskbar.py b/digsby/src/tests/testgui/test_taskbar.py new file mode 100644 index 0000000..24476d2 --- /dev/null +++ b/digsby/src/tests/testgui/test_taskbar.py @@ -0,0 +1,104 @@ +import wx +import cgui +import gettext; gettext.install('Digsby') +import gui.toolbox.imagefx + +class MyTabController(cgui.TabController): + def __init__(self): + cgui.TabController.__init__(self) + self.thumb = None + + def GetIconicBitmap(self, tab, width, height): + print tab, width, height + + if self.thumb is None: + self.thumb = wx.Bitmap(r'c:\users\kevin\src\digsby\res\digsbybig.png', wx.BITMAP_TYPE_ANY).Resized((width, height)) + + return self.thumb + + def GetLivePreview(self, tab, rect): + bitmap = wx.EmptyBitmap(rect.width, rect.height) + dc = wx.MemoryDC(bitmap) + dc.Brush = wx.Brush(tab.Window.BackgroundColour) + dc.DrawRectangle(0, 0, rect.width, rect.height) + return bitmap + +def main2(): + #app = wx.PySimpleApp() + from tests.testapp import testapp + app = testapp() + from gui import skin + + f = wx.Frame(None) + + f.Sizer = sizer = wx.BoxSizer(wx.VERTICAL) + + tabs = cgui.TabNotebook(f) + + def maketab(color, name): + p = wx.Panel(f, name=name) + def leftdown(e): + def later(): + from gui.native.toplevel import FlashOnce + win = p if not wx.GetKeyState(wx.WXK_SHIFT) else p.Top + print 'flashing', win + FlashOnce(win) + + + wx.CallLater(2000, later) + e.Skip() + + def rightdown(e): + e.Skip() + print tabs.SetTabActive(p) + + p.Bind(wx.EVT_LEFT_DOWN, leftdown) + p.Bind(wx.EVT_RIGHT_DOWN, rightdown) + p.SetBackgroundColour(color) + sizer.Add(p, 1, wx.EXPAND) + + p.tab = tabs.CreateTab(f, MyTabController()) + + maketab(wx.RED, 'foo') + maketab(wx.BLUE, 'bar') + maketab(wx.GREEN, 'meep') + + icon = skin.get('AppDefaults.UnreadMessageIcon') + success = tabs.SetOverlayIcon(icon) + + print + print '###'*30 + print success + + #print cgui.TaskbarTab() + + f.Show() + app.MainLoop() + +def main(): + from tests.testapp import testapp + app = testapp() + from gui import skin + + icon = skin.get('AppDefaults.UnreadMessageIcon') + + #f = wx.Frame(None) + #f.Show() + + #f.SetFrameIcon(skin.get('appdefaults.taskbaricon')) + #w = wx.Panel(f) + #n = cgui.TabNotebook(f) + #n.CreateTab(w, MyTabController()) + #print '\n\n' + icon = icon.PIL.ResizeCanvas(16, 16).WXB + #print n.SetOverlayIcon(icon) + + import gui.native.win.taskbar as tb + print '*'*80 + print tb.set_overlay_icon(icon) + + app.MainLoop() + +if __name__ == '__main__': + main2() + diff --git a/digsby/src/tests/testgui/test_threadedgui.py b/digsby/src/tests/testgui/test_threadedgui.py new file mode 100644 index 0000000..2fd6840 --- /dev/null +++ b/digsby/src/tests/testgui/test_threadedgui.py @@ -0,0 +1,117 @@ +from threading import Thread, currentThread +from random import choice, randint +from weakref import ref +from time import sleep +from Queue import Queue +import gc + +import wx + + +num_threads = 20 +num_labels = 10 +num_repeats = 500 + + +class _forallobj(object): + def __init__(self, obj): + object.__setattr__(self, 'obj', obj) + + def __setattr__(self, attr, val): + for elem in self.obj: + setattr(elem, attr, val) + +def forall(seq): + return _forallobj(seq) + + +def test_threads(): + f = wx.Frame(None) + + + s = f.Sizer = wx.BoxSizer(wx.VERTICAL) + + labels = [] + for x in xrange(num_labels): + l = wx.StaticText(f, -1) + s.Add(l) + labels.append(l) + + funcs = [] + def foo1(): + while True: + bar = [meep**2 for meep in xrange(randint(1, 10000))] + sleep(randint(1, 200) / 200.0) + #print 'foo1', currentThread().getName() + + def foo2(): + while True: + objects = [] + for x in xrange(20000): + objects.append(object()) + + sleep(randint(1, 200) / 200.0) + if randint(1,5) == 5: + gc.collect() + + q = Queue() + + def foo3(): + while True: + item = q.get() + weakitem = ref(item) + del item + q.task_done() + #print 'foo3', currentThread().getName(), q.qsize() + sleep(.001) + + def on_timer(): + for x in xrange(randint(1,5)): + item = choice((wx.EvtHandler, wx.Rect))() + q.put(item) + + + funcs = [foo1, foo2, foo3] + + def wrapper(func): + def inner(*a, **k): + gc.set_debug(gc.DEBUG_STATS) + return func(*a, **k) + + return inner + + + def make_thread(): + t = Thread(target = wrapper(choice(funcs))) + t.setDaemon(True) + return t + + threads = [make_thread() for x in xrange(num_threads)] + + f.Show() + + @wx.CallAfter + def later(): + for t in threads: + t.start() + + b = wx.Button(f, -1, 'gc') + b.Bind(wx.EVT_BUTTON, lambda e: gc.collect()) + + a = wx.GetApp() + a.timer = wx.PyTimer(on_timer) + a.timer.Start(15, False) + + a.other_timer = wx.PyTimer(wx.StressTest) + a.other_timer.Start(1000, False) + +def main(): + import gc + gc.set_debug(gc.DEBUG_STATS) + + a = wx.PySimpleApp() + test_threads() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testconvopreview.py b/digsby/src/tests/testgui/testconvopreview.py new file mode 100644 index 0000000..c6a4a77 --- /dev/null +++ b/digsby/src/tests/testgui/testconvopreview.py @@ -0,0 +1,28 @@ +import wx + +def main(): + from tests.testapp import testapp + from tests.mock.mockbuddy import MockBuddy + a = testapp() + + from gui.imwin.messagearea import MessageArea + from gui.imwin.styles import get_theme + from common.logger import history_from_files + from gui import skin + + f = wx.Frame(None, title = 'Conversation Preview') + msgarea = MessageArea(f) + + buddy = MockBuddy('digsby01') + + theme = get_theme('MiniBubble2', None) + msgarea.init_content(theme, buddy.alias, buddy, show_history = False) + + msgs = history_from_files([skin.resourcedir() / 'Example Conversation.html']) + msgarea.replay_messages(msgs, buddy) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/testdisplay.py b/digsby/src/tests/testgui/testdisplay.py new file mode 100644 index 0000000..8234d00 --- /dev/null +++ b/digsby/src/tests/testgui/testdisplay.py @@ -0,0 +1,27 @@ +import wx + +def print_display_info(): + count = wx.Display.GetCount() + + print count, 'displays:' + + for i in xrange(count): + display = wx.Display(i) + print ' %d: %r' % (i, display.Geometry) + + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None) + + b = wx.Button(f, -1, 'Get Displays') + b.Bind(wx.EVT_BUTTON, lambda e: print_display_info()) + + f.Sizer = s = wx.BoxSizer(wx.VERTICAL) + s.Add(b) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testdocking.py b/digsby/src/tests/testgui/testdocking.py new file mode 100644 index 0000000..574873f --- /dev/null +++ b/digsby/src/tests/testgui/testdocking.py @@ -0,0 +1,61 @@ +import gettext; gettext.install('Digsby') +import gui.native.win.winhelpers +from gui.native.docking import Docker +import wx, sys + +if __name__ == '__main__': + + from gui.toolbox import setuplogging + from util import trace + + setuplogging() + a = wx.PySimpleApp() + + f2 = wx.Frame(None, title = u'Docking Control', + pos = (400, 600), size = (150,130), + style=wx.DEFAULT_FRAME_STYLE|wx.STAY_ON_TOP) + + f = wx.Frame(f2, -1, u'Docking Test', size = (250, 500)) + + + b = wx.Button(f, -1, 'vars') + + + + f.Sizer = sz = wx.BoxSizer(wx.VERTICAL) + sz.Add(b) + + f.docker = Docker(f) + + def printvars(e): + from pprint import pprint + pprint(vars(f.docker)) + b.Bind(wx.EVT_BUTTON, printvars) + + trace(Docker) + f.Bind(wx.EVT_LEFT_DOWN, lambda e: sys.stdout.write('docked: %s\n' % f.docker.docked)) + f.docker.Enabled = False + + p = wx.Panel(f2) + sz = p.Sizer = wx.BoxSizer(wx.VERTICAL) + + text = wx.StaticText(p, -1, 'Not Docked') + f.docker.OnDock += lambda docked: text.SetLabel('Docked!' if docked else 'Not Docked') + + b = wx.CheckBox(p, -1, '&Docking') + c = wx.CheckBox(p, -1, '&Auto Hiding') + c.Enabled = False + + def on_dock(e): c.Enabled = f.docker.Enabled = e.IsChecked() + def on_autohide(e): f.docker.AutoHide = e.IsChecked() + + b.Bind(wx.EVT_CHECKBOX, on_dock) + c.Bind(wx.EVT_CHECKBOX, on_autohide) + + sz.Add(text, 0, wx.EXPAND | wx.ALL, 5) + sz.Add(b, 0, wx.EXPAND | wx.ALL, 5) + sz.Add(c, 0, wx.EXPAND | wx.ALL, 5) + f2.Show() + + f.Show(True) + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testdrawspeed.py b/digsby/src/tests/testgui/testdrawspeed.py new file mode 100644 index 0000000..addc382 --- /dev/null +++ b/digsby/src/tests/testgui/testdrawspeed.py @@ -0,0 +1,43 @@ +from wx import Color +import wx +from time import clock + +from random import randint + +def randcolor(): + return Color(randint(0,255), randint(0,255), randint(0,255)) + +def paint(e): + start = clock() + f = e.EventObject + dc = wx.PaintDC(f) + + i = 4 + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.WHITE_BRUSH) + dc.DrawRectangleRect(f.ClientRect) + + for y in xrange(100): + for x in xrange(100): + dc.Brush = wx.Brush(randcolor()) + dc.DrawRectangle(x*i, y*i, x+i, y+i) + + print clock() - start + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None, style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE) + + + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + f.Bind(wx.EVT_PAINT, paint) + + #from psyco import bind + #bind(paint) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testeventloop.py b/digsby/src/tests/testgui/testeventloop.py new file mode 100644 index 0000000..7e79375 --- /dev/null +++ b/digsby/src/tests/testgui/testeventloop.py @@ -0,0 +1,30 @@ +if __name__ == '__main__': + + from wx import PyTimer + import wx + + f = None + + a = wx.PySimpleApp() + f2 = wx.Frame(None) + f2.Show() + b = wx.Button(f2, -1, 'modal') + b.Bind(wx.EVT_BUTTON, lambda e: wx.MessageBox('test')) + + def prnt(s): + print s + + def foo(): + global f + if f is None: + f = wx.Frame(None) + f.Bind(wx.EVT_WINDOW_DESTROY, lambda e: prnt('destroyed: %r' % f)) + f.Show() + else: + f.Destroy() + f = None + + t = PyTimer(foo) + t.Start(1000) + + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testexpando.py b/digsby/src/tests/testgui/testexpando.py new file mode 100644 index 0000000..4d9e2ea --- /dev/null +++ b/digsby/src/tests/testgui/testexpando.py @@ -0,0 +1,62 @@ +import wx +from cgui import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED + + + + +a = wx.PySimpleApp() + +def MakeTextFrame(type = "Expando"): + f = wx.Frame(None, -1, title=type) + + if type == "Expando": + tc = ExpandoTextCtrl(f,-1) + else: + tc = wx.TextCtrl(f,-1,style = wx.TE_RICH2 | wx.TE_MULTILINE | wx.TE_NO_VSCROLL) + + tc.ShowScrollbar(wx.VERTICAL, False) + def OnKey(event): + event.Skip() + line = tc.PositionToXY(tc.GetInsertionPoint())[2] + scrollPos = tc.GetScrollPos(wx.VERTICAL) + print line, scrollPos + + def OnLayoutneeded(event): + event.Skip() + f.Fit() + + def OnKeyDownStripNewlineModifiers(event): + if event.KeyCode == wx.WXK_RETURN and event.Modifiers: + + e = wx.KeyEvent(wx.EVT_CHAR) + e.m_keyCode = event.m_keyCode + e.m_rawCode = event.m_rawCode + e.m_rawFlags = event.m_rawFlags + e.m_scanCode = event.m_scanCode + e.m_controlDown = False + e.m_altDown = False + e.m_metaDown = False + e.m_shiftDown = False + + tc.WriteText('\n') + + tc.ProcessEvent(e) + else: + event.Skip() + + tc.Bind(wx.EVT_KEY_DOWN, OnKeyDownStripNewlineModifiers) + + tc.Bind(wx.EVT_KEY_DOWN, OnKey) + + tc.Bind(EVT_ETC_LAYOUT_NEEDED, OnLayoutneeded) + + if type == "Expando": + tc.SetMaxHeight(100) + + f.Show() + f.Layout() + +MakeTextFrame() +MakeTextFrame("wxTextCtrl") + +a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testfiletransferlist.py b/digsby/src/tests/testgui/testfiletransferlist.py new file mode 100644 index 0000000..7eb489f --- /dev/null +++ b/digsby/src/tests/testgui/testfiletransferlist.py @@ -0,0 +1,81 @@ +from gui.filetransfer.filetransferlist import FileTransferPanel +from common.filetransfer import FileTransfer +import wx +from util import autoassign +from path import path + +class MockFileTransfer(FileTransfer): + def __init__(self, **attrs): + FileTransfer.__init__(self) + autoassign(self, attrs) + + @property + def name(self): + return self.filepath.name + + def accept(self, fobj): + self.state = self.states.CONNECTING + self._accept(fobj) + + def decline(self): + self.state = self.states.CANCELLED_BY_YOU + + def cancel(self): + self.state = self.states.CANCELLED_BY_YOU + +if __name__ == '__main__': + from util.observe import ObservableList + from tests.mock.mockbuddy import MockBuddy + from tests.testapp import testapp + + a = testapp(plugins=False) + f = wx.Frame(None, title = 'File Transfers') + f.Bind(wx.EVT_CLOSE, lambda e: a.ExitMainLoop()) + + filexfers = ObservableList([ + MockFileTransfer(buddy = MockBuddy('digsby03'), + filepath = path('c:\\YyyyYgq-v3.1.exe'), + size = 120.6 * 1024 * 1024, + completed = 0, + direction = 'incoming', + ), + + MockFileTransfer(buddy = MockBuddy('dotsyntax1'), + filepath = path('c:\\DL Manager(2).jpg'), + size = 253 * 1024, + completed = 253 * 1024, + direction = 'outgoing') + ]) + + ft = filexfers[0] + ft.state = ft.states.WAITING_FOR_YOU + filexfers[1].state = ft.states.WAITING_FOR_BUDDY + + + def tick(self): + newval = ft.completed + 320 * 1024 + ft._setcompleted(min(ft.size, newval)) + + if ft.size < newval: + ft.state = ft.states.FINISHED + f.Unbind(wx.EVT_TIMER) + + + + f.Bind(wx.EVT_TIMER, tick) + + t = wx.Timer() + t.SetOwner(f) + + + ft._accept = lambda *a, **k: wx.CallLater(2000, lambda: (setattr(ft, 'state', ft.states.TRANSFERRING), + t.Start(200))) + + + ftl = FileTransferPanel(f, filexfers) + + f.Show() + + a.MainLoop() + + diff --git a/digsby/src/tests/testgui/testfontloader.py b/digsby/src/tests/testgui/testfontloader.py new file mode 100644 index 0000000..d525839 --- /dev/null +++ b/digsby/src/tests/testgui/testfontloader.py @@ -0,0 +1,18 @@ + +import config +import sys, os +sys.path += map(os.path.abspath, ['./src', './ext', './ext/' + config.platformName, './lib', './thirdparty']) + +import wx + +def main(): + from tests.testapp import testapp + a = testapp() + + from gui.toolbox.fonts import loadfont + assert loadfont('res/slkscr.ttf') + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testfontsizes.py b/digsby/src/tests/testgui/testfontsizes.py new file mode 100644 index 0000000..665c8bf --- /dev/null +++ b/digsby/src/tests/testgui/testfontsizes.py @@ -0,0 +1,63 @@ +from __future__ import division +import time +import wx +from tests.testapp import testapp + +def main(): + a = testapp('../../..') + f = wx.Frame(None) + + f.fontsize = 12 + + def paint(e): + dc = wx.AutoBufferedPaintDC(f) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.BLACK_BRUSH) + dc.DrawRectangleRect(f.ClientRect) + + font = wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) + font.SetPixelSize(wx.Size(0, -f.fontsize)) + font.SetFaceName('Times New Roman') + dc.TextForeground = wx.WHITE + dc.Font = font + + str = 'test hello world xyz' + x, y = 40, 10 + dc.DrawText(str, x, y) + w, h, desc, externalLeading = dc.GetFullTextExtent(str) + print w, h, desc, externalLeading + + r = wx.Rect(x, y + desc, w, h - desc * 2) + + realHeight = (h-desc*2) + f.SetTitle('%s / %s = %s' % (f.fontsize, realHeight, f.fontsize / realHeight)) + + #dc.SetPen(wx.RED_PEN) + #dc.SetBrush(wx.TRANSPARENT_BRUSH) + #dc.DrawRectangleRect(r) + + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + f.Bind(wx.EVT_PAINT, paint) + + sl = wx.Slider(f, minValue = -10, maxValue = 50, value = f.fontsize) + + def onslide(e): + f.fontsize = sl.Value + f.Refresh() + f.SetTitle(str(sl.Value)) + + + sl.Bind(wx.EVT_SLIDER, onslide) + + + f.Sizer = s = wx.BoxSizer(wx.VERTICAL) + s.AddStretchSpacer(1) + s.Add(sl, 0, wx.EXPAND) + + + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testframeicon.py b/digsby/src/tests/testgui/testframeicon.py new file mode 100644 index 0000000..66b5dc5 --- /dev/null +++ b/digsby/src/tests/testgui/testframeicon.py @@ -0,0 +1,15 @@ +import wx + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None, -1, 'test', style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_NO_TASKBAR) + + b = wx.ArtProvider.GetBitmap(wx.ART_QUESTION) + i = wx.IconFromBitmap(b) + f.SetIcon(i) + f.Show() + + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/testfullscreen.py b/digsby/src/tests/testgui/testfullscreen.py new file mode 100644 index 0000000..518a72a --- /dev/null +++ b/digsby/src/tests/testgui/testfullscreen.py @@ -0,0 +1,40 @@ +import wx +import gui.native.helpers + +from gui.native.win.dockconstants import * +from gui.native.docking import APPBARDATA +from gui.native.win.winhelpers import FullscreenApp + +FULLSCREEN_CB_ID = WM_USER + 203 + +from ctypes import windll, byref +SHAppBarMessage = windll.shell32.SHAppBarMessage + +def getabd(win, cb_id): + abd = APPBARDATA() + abd.hWnd = win.Handle + abd.uCallbackMessage = FULLSCREEN_CB_ID + return abd + +def AppBarCallback(hWnd, msg, wParam, lParam): + cb_id = msg + print locals() + if cb_id == FULLSCREEN_CB_ID and wParam == ABN_FULLSCREENAPP: + print 'FullscreenApp: %r' % FullscreenApp() + print 'fullscreen is', bool(lParam) +# print wParam, lParam + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None) + f.Position = (1800, 400) + f.Show() + + SHAppBarMessage(ABM_NEW, byref(getabd(f, FULLSCREEN_CB_ID))) + f.BindWin32(FULLSCREEN_CB_ID, AppBarCallback) + +# print locals() + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/testimagecaching.py b/digsby/src/tests/testgui/testimagecaching.py new file mode 100644 index 0000000..3fb2656 --- /dev/null +++ b/digsby/src/tests/testgui/testimagecaching.py @@ -0,0 +1,217 @@ +from __future__ import division +import time +import wx +from tests.testapp import testapp +from gui.toolbox import draw_tiny_text + +def main(): + a = testapp('../../..') + f = wx.Frame(None, style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE) + + from gui.skin import get as skinget + icons = skinget('serviceicons') + + def paint(e): + dc = wx.AutoBufferedPaintDC(f) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.BLACK_BRUSH) + dc.DrawRectangleRect(f.ClientRect) + + dc.DrawBitmap(icons.gmail, 0, 0, True) + dc.DrawBitmap(draw_tiny_text(icons.gmail, 'test').WXB, 0, 40, True) + + + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + f.Bind(wx.EVT_PAINT, paint) + f.Show() + + a.MainLoop() + +def main2(): + a = testapp('../../..') + f = wx.Frame(None) + + from gui.skin import get as skinget + icons = skinget('serviceicons') + + services = 'digsby aim icq jabber gtalk yahoo'.split() + + f.imgsize = 0 + + def paint(e): + dc = wx.AutoBufferedPaintDC(f) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.BLACK_BRUSH) + dc.DrawRectangleRect(f.ClientRect) + + drawbitmap = dc.DrawBitmap + + sizes = [(32, 32), (16, 16), (100, 100)] + + diff = f.imgsize + + y = 0 + for size in sizes: + x = 0 + for srv in services: + icon = icons[srv].Resized((size[0] + diff, size[1] + diff)) + drawbitmap(icon, x, y, True) + x += size[0] + diff + y += size[1] + diff + + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + f.Bind(wx.EVT_PAINT, paint) + + sl = wx.Slider(f, minValue = -10, maxValue = 50, value = 0) + + def onslide(e): + f.imgsize = sl.Value + f.Refresh() + f.SetTitle(str(sl.Value)) + + + sl.Bind(wx.EVT_SLIDER, onslide) + + f.Sizer = s = wx.BoxSizer(wx.VERTICAL) + s.AddStretchSpacer(1) + s.Add(sl, 0, wx.EXPAND) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() + + + + +if __name__ == '_dep_': + @lru_cache(30) + def reflected(bmp, heightPercent = None, maxalpha = None): + sz = (bmp.Width, bmp.Height) + flipped = ImageOps.flip(bmp.PIL) + + grad = Image.new('L', sz) + draw = ImageDraw.Draw(grad) + + height = float(sz[1]) + + alpha = flipped.split()[-1] + + if maxalpha is None: + maxalpha = 100 + if heightPercent is None: + heightPercent = 75 + + hh = (heightPercent / 100.00) * height + + # draw the gradient + for yy in xrange(0, hh): + k = max(0, maxalpha - maxalpha * yy/hh) + draw.line((0, yy, sz[0], yy), fill = k) + + flipped.putalpha(ImageMath.eval('convert(min(a,b), "L")', a=alpha, b=grad)) + + return flipped.WXB + + + def drawreflected(dc, bmp, x, y, alpha = True, heightPercent = None, maxalpha = None): + gc = wx.GraphicsContext.Create(dc) + sz = (bmp.Width, bmp.Height) + + gc.DrawBitmap(bmp, x, y, sz[0], sz[1]) + gc.DrawBitmap(reflected(bmp, heightPercent, maxalpha), x, y + sz[1], sz[0], sz[1]) + + wx.DC.DrawReflectedBitmap = drawreflected + +if __name__ == '__main__123': + from time import clock + N = 50000 + app = wx.PySimpleApp() + + def test(f): + t = clock() + for x in xrange(N): + f(bmp, 16) + diff = clock()-t + print f, diff + + bmp = wx.Bitmap('c:\\digsbybig.png') + + test(wxbitmap_in_square) + test(old_wxbitmap_in_square) + + +if __name__ == '__main__': + from tests.testapp import testapp + from gui.textutil import default_font + from math import ceil, sqrt + from path import path + + app = testapp('../../../') + + bitmaps = [] + + + + for f in path('c:\\src\\digsby\\res\\skins\\default\\serviceicons').files('*.png'): + bitmaps.append(wx.Bitmap(f)) + + m = wx.Menu() + item = wx.MenuItem(m) + + item.Bitmap = bitmaps[0] + bitmaps.insert(0, item.Bitmap) + bitmaps.insert(0, item.Bitmap) + bitmaps.insert(0, item.Bitmap) + bitmaps.insert(0, item.Bitmap) + +# from ctypes import cast, POINTER, c_long + +# def refdata(obj, n): +# return cast(int(obj.this), POINTER(c_long))[n] + + from pprint import pprint + pprint([(b.GetRefData(), id(b)) for b in bitmaps]) + + f = wx.Frame(None, style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE) + + def paint(e): + dc = wx.AutoBufferedPaintDC(f) + gc = wx.GraphicsContext.Create(dc) + r = f.ClientRect + x1, y1 = r.TopLeft + x2, y2 = r.BottomRight + + br = gc.CreateLinearGradientBrush(x1, y1, x2, y2, wx.BLACK, wx.WHITE) + gc.SetBrush(br) + gc.DrawRectangle(*r) + dc.TextForeground = wx.WHITE + dc.Font = default_font() + + j = int(ceil(sqrt(len(bitmaps)))) + + i = 0 + for y in xrange(j): + for x in xrange(j): + w, h = r.Width / j, r.Height / j + xx, yy = w * x, h * y + + if len(bitmaps) > i: + dc.DrawBitmap(bitmaps[i].Resized(min((w, h))), xx, yy) + + + dc.DrawText(str(bitmaps[i].GetRefData()), xx, yy) + + i += 1 + + + #dc.DrawBitmap(b.Resized(min(r.Size)).Greyed, 0, 0, True) + #dc.DrawBitmap(ResizeBitmapSquare(b, min(r.Size)), 0, 0, True) + #dc.DrawBitmap(ResizeBitmap(b, *r.Size), 0, 0, True) + #dc.DrawBitmap(b.Resized(r.Size), 0, 0, True) + + f.Bind(wx.EVT_PAINT, paint) + f.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + f.Show() + app.MainLoop() diff --git a/digsby/src/tests/testgui/testkeycatcher.py b/digsby/src/tests/testgui/testkeycatcher.py new file mode 100644 index 0000000..6f7f68f --- /dev/null +++ b/digsby/src/tests/testgui/testkeycatcher.py @@ -0,0 +1,23 @@ +import wx +from gui.uberwidgets.keycatcher import KeyCatcher + +if __name__ == '__main__': + a = wx.PySimpleApp() + f = wx.Dialog(None) + + keycatcher = KeyCatcher(f) + + s = f.Sizer = wx.BoxSizer(wx.HORIZONTAL) + + b = wx.Button(f, -1, '&test') + b2 = wx.Button(f, -1, 't&est') + s.AddMany([b, b2]) + + def msg(a): print a + + b.Bind(wx.EVT_BUTTON, lambda e: msg('test button')) + keycatcher.OnDown('cmd+k', lambda e: msg('ctrl K!!! from accelerator')) + keycatcher.OnDown('cmd+alt+return', lambda e: msg('ctrl alt enter from accelerator')) + + f.Show() + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testmenuleak.py b/digsby/src/tests/testgui/testmenuleak.py new file mode 100644 index 0000000..6ca17cb --- /dev/null +++ b/digsby/src/tests/testgui/testmenuleak.py @@ -0,0 +1,33 @@ +import wx, gc, weakref + +class MenuCleanup(object): + def add(self, menu): + weakref.ref(menu, callback) + self.refs[ref] = menu + + +def main(): + a = wx.PySimpleApp() + + f = wx.Frame(None) + + + def update(): + menus = [a for a in gc.get_objects() if isinstance(a, wx.Menu)] + + + def onmenu(e): + m = wx.Menu() + m.Append(-1, 'test') + f.PopupMenu(m, f.ScreenToClient(wx.GetMousePosition())) + + update() + + + f.Bind(wx.EVT_CONTEXT_MENU, onmenu) + f.Show() + + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/testmenus.py b/digsby/src/tests/testgui/testmenus.py new file mode 100644 index 0000000..7620783 --- /dev/null +++ b/digsby/src/tests/testgui/testmenus.py @@ -0,0 +1,135 @@ +import wx + +if __name__ == '__main__': + from tests.testapp import testapp + from gui.uberwidgets.umenu import UMenuBar, UMenu + from gui import skin + + app = a = testapp(skinname = 'jeffrey') + wx.InitAllImageHandlers() + wx.UpdateUIEvent.SetMode(wx.UPDATE_UI_PROCESS_SPECIFIED) + + f = wx.Frame(None, -1, 'menu test') + + f.Bind(wx.EVT_MENU, lambda e: msg('%s %s %s' % (e, e.EventType, e.Id))) + + f.Bind(wx.EVT_CLOSE, lambda e: app.ExitMainLoop()) + p = wx.Panel(f) + f.CenterOnScreen() + + p.Sizer = wx.BoxSizer(wx.VERTICAL) + + bar = UMenuBar(p, p.Sizer) + bmps = [wx.Bitmap('..\\..\\..\\res\\%s.png' % aa) for aa in ('online', 'away', 'offline')] + + m = UMenu(f) + m.AddItem('&Preferences\tCtrl+P', callback = lambda: msg('prefs'), bitmap = skin.get('serviceicons.aim')) + accounts_item = m.AddItem('&Accounts\tCtrl+A', callback = lambda: msg('show accounts!')) + + m.AddSep() + + sub = UMenu(f) + g = sub.AddItem('one', callback = lambda: msg('one!')) + sub.AddItem('two\tCtrl+T', bitmap = bmps[1]) + three = sub.AddItem('three', bitmap = bmps[2]) + + sub4 = UMenu(f) + sub4.AddItem('foo1') + sub4.AddItem('&foo') + sub4.AddItem('&foo2') + sub4.AddItem('bar') + sub4.AddItem('another &foo') + sub4.AddItem('meep') + sub4.AddItem('fooness') + sub.AddSubMenu(sub4, 'foobarmeep') + + g.SetBitmap(bmps[0]) + + sub2 = UMenu(f); add = sub2.AddCheckItem + add('four') + add('five\tCtrl+F') + add('six') + + sub3 = UMenu(f); add = sub3.AddRadioItem + add('seven') + add('eight\tCtrl+F') + add('nine') + + def msg(msg): + print msg + + m.AddSubMenu(sub, 'Submenu', onshow = lambda: g.SetText('one shown!')) + m.AddSubMenu(sub2, 'Checks', onshow = lambda: msg('submenu 2 onshow')) + m.AddSubMenu(sub3, 'Radios') + + m.AddItem('&Close\tCtrl+W', callback = lambda: f.Close()) + + m2 = UMenu(f, onshow = lambda menu: msg('wut')); add = m2.AddItem + add('&Undo\tCtrl+Z') + add('&Redo\tCtrl+Y') + m2.AddSep() + add('Cu&t\tCtrl+X') + add('&Copy\tCtrl+C') + add('&Paste\tCtrl+V') + + bar.Append(m, '&File') + bar.Append(m2, '&Edit') + + def menu_open(e): + print vars(e) + + def popup(e): + m.PopupMenu() + + + p.Bind(wx.EVT_RIGHT_UP, popup) + #p.Bind(wx.EVT_PAINT, lambda e: wx.PaintDC(p).DrawBitmap(bmps[0], 10, 10, True)) + + button = wx.Button(p, -1, 'toggle skin') + button2 = wx.Button(p, -1, 'events') + + def showevents(e): + from gui.uberwidgets.umenu import menuEventHandler + from pprint import pprint + from util import funcinfo + + for id, cb in menuEventHandler(f).cbs.iteritems(): + print id, funcinfo(cb) + + + button2.Bind(wx.EVT_BUTTON, showevents) + + wut = False + def toggle(e): + global wut + wut = not wut + + mb = wx.GetApp().skin.tree['menubar'] + mb.mode = 'skin' if mb.get('mode', 'skin').lower() == 'native' else 'native' + + from gui.skin.skintree import refresh_wx_tree + refresh_wx_tree() + p.Sizer.Layout() + + button.Bind(wx.EVT_BUTTON, toggle) + + p.Sizer.Add(bar.SizableWindow) + p.Sizer.Add((30, 140), 0) + p.Sizer.Add(button) + p.Sizer.Add(button2) + + f.Show() + + def wutcapture(): + win =wx.Window.GetCapture() + if win: + print 'capture', wx.Window.GetCapture(),'with',wx.Window.GetCapture().menu[0] + print 'focus ', wx.Window.FindFocus() + print + + a.timer = wx.PyTimer(wutcapture) + a.timer.Start(3000, False) + + a.MainLoop() + #from util import profile; profile(a.MainLoop) + #a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testmodality.py b/digsby/src/tests/testgui/testmodality.py new file mode 100644 index 0000000..88b3cd0 --- /dev/null +++ b/digsby/src/tests/testgui/testmodality.py @@ -0,0 +1,29 @@ +import wx + +def main(): + a = wx.PySimpleApp() + f = wx.Frame(None) + b = wx.Button(f, -1, 'showmodal') + f2 = wx.Dialog(f) + + b2 = wx.Button(f2, -1, 'wut') + + def onsubbutton(e): + print 'IsModal ', f2.IsModal() + print 'IsShown ', f2.IsShown() + + + b2.Bind(wx.EVT_BUTTON, onsubbutton) + + def onbutton(e): + print 'onbutton' + print 'showing modal' + print 'result:', f2.ShowModal() + print 'done!' + + b.Bind(wx.EVT_BUTTON, onbutton) + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testpaintbug.py b/digsby/src/tests/testgui/testpaintbug.py new file mode 100644 index 0000000..602248b --- /dev/null +++ b/digsby/src/tests/testgui/testpaintbug.py @@ -0,0 +1,32 @@ +import wx + +def main(): + a = wx.PySimpleApp() + + f = wx.Frame(None) + + + class MyListBox(wx.VListBox): + + def OnDrawItem(self, dc, rect, n): + dc = wx.PaintDC(f) + dc.Brush = wx.WHITE_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + + + dc.DrawRectangle(rect) + dc.SetTextForeground(wx.BLACK) + dc.DrawText('item %d' % n, rect.x + 5, rect.y + 3) + + def OnMeasureItem(self, n): + return 20 + + v = MyListBox(f) + v.SetItemCount(50) + + f.Show() + + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testsnapping.py b/digsby/src/tests/testgui/testsnapping.py new file mode 100644 index 0000000..bb1cb2e --- /dev/null +++ b/digsby/src/tests/testgui/testsnapping.py @@ -0,0 +1,31 @@ +import wx, gui.snap + +def main(): + app = wx.PySimpleApp() + frame = wx.Frame(None, -1, 'Snap Test') + frame.Snap = True + + f = wx.Frame(frame, -1, 'Other Frame', size=(300,400), pos=(700,300)) + f.Show() + + panel = wx.Panel(frame) + panel.Sizer = wx.BoxSizer(wx.VERTICAL) + b = wx.Button(panel, -1, "Don't Snap") + + def push(e): + if frame.Snap: + frame.Snap = False + b.Label = 'Snap' + else: + frame.Snap = True + b.Label = "Don't Snap" + + b.Bind(wx.EVT_BUTTON, push) + + panel.Sizer.Add(b, 0, wx.ALL, 20) + frame.Show() + app.MainLoop() + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/teststress.py b/digsby/src/tests/testgui/teststress.py new file mode 100644 index 0000000..6058f7a --- /dev/null +++ b/digsby/src/tests/testgui/teststress.py @@ -0,0 +1,50 @@ +import random +import wx +from Queue import Queue +from threading import Thread + + +class BgThread(Thread): + def __init__(self): + Thread.__init__(self) + self.q = Queue() + + def run(self): + q = self.q + while True: + item = q.get() + print item + q.task_done() + +def main(): + a = wx.PySimpleApp() + + threads = [] + for x in xrange(50): + bg = BgThread() + bg.setDaemon(True) + bg.start() + threads.append(bg) + + f = wx.Frame(None) + b = wx.Button(f, -1, 'button') + + def on_button(e=None): + random.choice(threads).q.put(wx.EvtHandler()) + + b.Bind(wx.EVT_BUTTON, on_button) + + timers = [] + for x in xrange(20): + t = wx.PyTimer(on_button) + t.Start(5, False) + timers.append(t) + + s=f.Sizer=wx.BoxSizer(wx.HORIZONTAL) + s.Add(b) + f.Show() + + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/testtexthistory.py b/digsby/src/tests/testgui/testtexthistory.py new file mode 100644 index 0000000..703c294 --- /dev/null +++ b/digsby/src/tests/testgui/testtexthistory.py @@ -0,0 +1,64 @@ +if __name__ == '__main__': + import gettext; gettext.install('Digsby') +import wx +from util import to_hex + +from gui.toolbox.texthistory import TextHistory +from gui.toolbox import prnt + +txtFlags = wx.TE_RICH2 | wx.TE_MULTILINE | wx.TE_CHARWRAP | wx.NO_BORDER | wx.WANTS_CHARS | wx.TE_NOHIDESEL + +class TestTextHistory(TextHistory): + def next(self): + TextHistory.next(self) + prnt('index:\t\t',self.index,'\nclip:\t\t',self.clip,'\nhistory:\t',self.history) + + def prev(self): + TextHistory.prev(self) + prnt('index:\t\t',self.index,'\nclip:\t\t',self.clip,'\nhistory:\t',self.history) + +def main(): + from tests.testapp import testapp + a = testapp() + + f = wx.Frame(None, -1, 'history test') + t = wx.TextCtrl(f, -1, style = txtFlags) + t.history = TestTextHistory(t) + st = wx.StaticText(f, -1, '') + + def onenter(e = None): + t.history.commit(t.Value) + t.Clear() + prnt('index:\t\t',t.history.index,'\nclip:\t\t',t.history.clip,'\nhistory:\t',len(t.history.history),t.history.history) + + def onkey(e): + wx.CallAfter(lambda: st.SetLabel(to_hex(t.Value))) + if e.ControlDown() and e.AltDown() and e.KeyCode == wx.WXK_SPACE: + from random import choice, randrange + import string + + for i in xrange(200): + rstr = ''.join(choice(string.letters) for x in xrange(randrange(5,300))) + t.AppendText(rstr) + onenter() + + return + e.Skip() + + t.Bind(wx.EVT_TEXT_ENTER, onenter) + t.Bind(wx.EVT_KEY_DOWN, onkey) + + s = f.Sizer = wx.BoxSizer(wx.VERTICAL) + s.Add(t, 1, wx.EXPAND) + s.Add(st, 1, wx.EXPAND) + + + + + f.Show() + a.MainLoop() + pass + +if __name__ == '__main__': + print 'hello' + main() diff --git a/digsby/src/tests/testgui/testthemecombos.py b/digsby/src/tests/testgui/testthemecombos.py new file mode 100644 index 0000000..bd30142 --- /dev/null +++ b/digsby/src/tests/testgui/testthemecombos.py @@ -0,0 +1,30 @@ +import wx + +def main(): + from tests.testapp import testapp + from tests.mock.mockbuddy import MockBuddy + from path import path + + a = testapp('../../..') + + from gui.imwin.messagearea import MessageArea + from gui.imwin.styles import get_theme + from common.logger import history_from_files + from gui import skin + + f = wx.Frame(None, title = 'Conversation Preview') + msgarea = MessageArea(f) + + buddy = MockBuddy('digsby01') + + theme = get_theme('GoneDark', 'Steel') + msgarea.init_content(theme, buddy.alias, buddy, show_history = False) + + msgs = history_from_files([skin.resourcedir() / 'Example Conversation.html']) + msgarea.replay_messages(msgs, buddy) + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/testtwittergui.py b/digsby/src/tests/testgui/testtwittergui.py new file mode 100644 index 0000000..d8b8153 --- /dev/null +++ b/digsby/src/tests/testgui/testtwittergui.py @@ -0,0 +1,42 @@ +''' +tests Twitter GUI components +''' + +import wx +from social.twitter.twittergui import TwitterStatusDialog, TwitterAccountPanel + +def test_status_dialog(): + import gettext + gettext.install('digsby') + from tests.testapp import testapp + testapp('../../..') + from social.twitter import get_snurl + from util.threads.threadpool import ThreadPool + ThreadPool(2) + a = wx.PySimpleApp() + d = TwitterStatusDialog.ShowSetStatus(None, 'foobar_username', initial_text='hello', tiny_url = get_snurl) + d2 = TwitterStatusDialog.ShowSetStatus(None, 'foobar_username', initial_text='hello', tiny_url = get_snurl) + assert d is d2 + d.Show() + a.MainLoop() + +def test_account_panel(): + import gettext + gettext.install('digsby') + from tests.testapp import testapp + testapp('../../..') + from util.threads.threadpool import ThreadPool + ThreadPool(2) + a = wx.PySimpleApp() + f = wx.Frame(None) + + + p = TwitterAccountPanel(f) + f.SetSize((450, 350)) + f.Layout() + f.Fit() + f.Show() + a.MainLoop() + +if __name__ == '__main__': + test_account_panel() diff --git a/digsby/src/tests/testgui/testuberbar.py b/digsby/src/tests/testgui/testuberbar.py new file mode 100644 index 0000000..1029485 --- /dev/null +++ b/digsby/src/tests/testgui/testuberbar.py @@ -0,0 +1,25 @@ +import wx +from tests.testapp import testapp + +from gui.uberwidgets.UberBar import UberBar +from gui.uberwidgets.UberButton import UberButton + +def main(): + a = testapp() + f = wx.Frame(None) + + bar = UberBar(f, skinkey = 'ButtonBarSkin', overflowmode = True) + + + for x in xrange(5): + title = 'test %d' % x + b = UberButton(bar, -1, title) + bar.Add(b) + + + + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testgui/uberdemos/CapabilitiesBarDemo.py b/digsby/src/tests/testgui/uberdemos/CapabilitiesBarDemo.py new file mode 100644 index 0000000..7f2d3f0 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/CapabilitiesBarDemo.py @@ -0,0 +1,47 @@ +from DemoApp import App +import wx +import gettext +gettext.install('Digsby', './locale', unicode=True) + +from gui.capabilitiesbar import CapabilitiesBar + +class Frame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,title='Simple Menu Test') + self.panel=wx.Panel(self) + + self.Bind(wx.EVT_CLOSE, lambda e: wx.GetApp().ExitMainLoop()) + + self.panel.Sizer=wx.BoxSizer(wx.VERTICAL) + + self.capbar=CapabilitiesBar(self.panel) + + self.panel.Sizer.Add(self.capbar,0,wx.EXPAND) + + b1=wx.Button(self.panel,-1,'Hide Capabilities') + b2=wx.Button(self.panel,-1,'Hide To/From') + b3=wx.Button(self.panel,-1,'Hide Compose') + + b1.Bind(wx.EVT_BUTTON,lambda e: self.capbar.ShowCapabilities(not self.capbar.cbar.IsShown())) + b2.Bind(wx.EVT_BUTTON,lambda e: self.capbar.ShowToFrom(not self.capbar.tfbar.IsShown())) + b3.Bind(wx.EVT_BUTTON, lambda e: self.capbar.ShowComposeButton(not self.capbar.bcompose.IsShown())) + + self.panel.Sizer.Add(b1) + self.panel.Sizer.Add(b2) + self.panel.Sizer.Add(b3) + + self.capbar.bsms.Bind(wx.EVT_BUTTON,self.OnButton) + self.capbar.binfo.Bind(wx.EVT_BUTTON,lambda e: self.capbar.bsms.SendButtonEvent()) + + def OnButton(self,event): + print "button clicked" + + +def Go(): + f=Frame() + f.Show(True) + +if __name__=='__main__': + a = App( Go ) + from util import profile + profile(a.MainLoop) diff --git a/digsby/src/tests/testgui/uberdemos/DemoApp.py b/digsby/src/tests/testgui/uberdemos/DemoApp.py new file mode 100644 index 0000000..ca544d7 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/DemoApp.py @@ -0,0 +1,16 @@ +import wx +from gui.skin import skininit + +class App(wx.App): + def __init__(self, callback): + + self.Go=callback + + wx.App.__init__(self,0) + + def OnInit(self): + + skininit('../../../../res') + self.Go() + + return True \ No newline at end of file diff --git a/digsby/src/tests/testgui/uberdemos/FormattedInputDemo.py b/digsby/src/tests/testgui/uberdemos/FormattedInputDemo.py new file mode 100644 index 0000000..6685404 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/FormattedInputDemo.py @@ -0,0 +1,73 @@ +import wx, wx.html + +from wx.lib.expando import ExpandoTextCtrl +#from gui.uberwidgets.formattedinput import FormattedInput +#from gui.uberwidgets.SizerBar import SizerBar +import gettext +gettext.install('Digsby', unicode=True) +from gui.skin import skininit +from gui.uberwidgets.formattedinput import FormattedInput + +class P(wx.Panel): + def __init__(self,parent): + wx.Panel.__init__(self,parent,-1) + + self.Sizer=wx.BoxSizer(wx.VERTICAL) + + from util import trace + trace(ExpandoTextCtrl) + + #profile = ExpandoTextCtrl(self,style= wx.TE_MULTILINE|wx.TE_CHARWRAP|wx.TE_PROCESS_ENTER|wx.NO_BORDER|wx.WANTS_CHARS|wx.TE_NOHIDESEL|wx.TE_RICH) + profile=FormattedInput(self) + self.Sizer.Add(profile,0) + self.Layout() + + + +class F(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,-1,"Profile Box Test") + + self.Sizer =wx.BoxSizer(wx.VERTICAL) + p=P(self) + self.Sizer.Add(p,1,wx.EXPAND) + + +class A(wx.App): + def OnInit(self): + +# self.Bind(wx.EVT_KEY_DOWN,self.OnKeyDown) + + skininit('../../../res') + f=F() + f.Bind(wx.EVT_CLOSE, lambda e: self.ExitMainLoop()) + + wx.CallAfter(f.Show) +# pref=PreF() +# pref.Show(True) + return True + + + +if __name__=='__main__': + + #a = A( 0 ) + #from util import profile + #profile(a.MainLoop) + + from tests.testapp import testapp + a = testapp('../../../../') + f = F() + +# inp = FormattedInput(f) +# s.Add(inp, 0, wx.EXPAND) + + +# def doit(e): +# inp.Fit() + + #inp.bemote.Bind(wx.EVT_BUTTON, doit) + + f.Show() + a.MainLoop() + diff --git a/digsby/src/tests/testgui/uberdemos/PrefPanelDemo.py b/digsby/src/tests/testgui/uberdemos/PrefPanelDemo.py new file mode 100644 index 0000000..4891b9f --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/PrefPanelDemo.py @@ -0,0 +1,99 @@ +import wx +from gui.uberwidgets.PrefPanel import PrefPanel +from gui.pref.noobcontactlayoutpanel import NoobContactLayoutPanel +class FakeContent(wx.Panel): + def __init__(self, parent, color=wx.RED): + wx.Panel.__init__(self, parent) + + self.brush = wx.Brush(color) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + def OnPaint(self,event): + dc = wx.AutoBufferedPaintDC(self) + rect = wx.RectS(self.Size) + + dc.Brush = self.brush + dc.Pen = wx.TRANSPARENT_PEN + + dc.DrawRectangleRect(rect) + +class Frame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,-1,"A simple test.",pos=wx.Point(50,50)) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + + pp = PrefPanel(self, FakeContent(self,wx.Colour(238,238,238)),' A test of silly proportions ',"A Button",self.AButtonCB) + self.Sizer.Add(pp,1,wx.EXPAND|wx.ALL,5) + + self.SetBackgroundColour(wx.WHITE) + + self.Bind(wx.EVT_CLOSE,lambda e: wx.GetApp().ExitMainLoop()) + + def AButtonCB(self,event): + print "You clicked teh button, Tee-He!!!" + +class Frame2(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,-1,"Hey! It's a combo title.",pos=wx.Point(300,300)) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + + self.pp = pp = PrefPanel(self,[(FakeContent(self,wx.Colour(238,238,238)),'grey' ), + (FakeContent(self,wx.Colour(238,238,255)),'blue' ), + (FakeContent(self,wx.Colour(255,238,238)),'red' ), + (FakeContent(self,wx.Colour(238,255,238)),'green' ), + (FakeContent(self,wx.Colour(255,255,238)),'yellow'), + ]) + self.Sizer.Add(pp,1,wx.EXPAND|wx.ALL,5) + + button = wx.Button(self,-1,'ZOMG Lazers') + self.Sizer.Add(button,0,wx.EXPAND|wx.ALL,3) + button.Bind(wx.EVT_BUTTON, self.OnLazers) + + self.SetBackgroundColour(wx.WHITE) + + self.Bind(wx.EVT_CLOSE,lambda e: wx.GetApp().ExitMainLoop()) + + def OnLazers(self,event): + self.pp.SetContents([(FakeContent(self,wx.Colour(255,255,238)),'yellow'), + (FakeContent(self,wx.Colour(238,255,238)),'green' ), + (FakeContent(self,wx.Colour(255,238,238)),'red' ), + (FakeContent(self,wx.Colour(238,238,255)),'blue' ), + (FakeContent(self,wx.Colour(238,238,238)),'grey' ), + ],True) + +class Frame3(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,-1,"A simple test.",pos=wx.Point(400,550),size=wx.Size(563, 294)) + + self.Sizer = wx.BoxSizer(wx.VERTICAL) + + pp = PrefPanel(self, NoobContactLayoutPanel(self),' A test of noobish proportions ') + self.Sizer.Add(pp,1,wx.EXPAND|wx.ALL,5) + + self.SetBackgroundColour(wx.WHITE) + + self.Bind(wx.EVT_CLOSE,lambda e: wx.GetApp().ExitMainLoop()) + + + +if __name__ == '__main__': + from tests.testapp import testapp + + hit = wx.FindWindowAtPointer + + a = testapp('../../../../') + + f = Frame() + f.Show(True) + + f2 = Frame2() + f2.Show(True) + + f3 = Frame3() + f3.Show(True) + + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/uberdemos/SimpleMenuXTDemo.py b/digsby/src/tests/testgui/uberdemos/SimpleMenuXTDemo.py new file mode 100644 index 0000000..e67efe6 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/SimpleMenuXTDemo.py @@ -0,0 +1,75 @@ +from DemoApp import App +import wx +from gui.uberwidgets.simplemenu import SimpleMenu,SimpleMenuItem +from gui.uberwidgets.UberButton import UberButton + +class Frame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,title='Extended Simple Menu Test') + self.panel=wx.Panel(self) + + self.panel.Sizer=wx.BoxSizer(wx.VERTICAL) + + + menu=SimpleMenu(self, 'simplemenu',width=100) + submenu=SimpleMenu(menu, 'simplemenu',width=100) + submenu2=SimpleMenu(submenu, 'simplemenu',width=100) + + subitems=[ + SimpleMenuItem('Test5'), + SimpleMenuItem(id=-1), + SimpleMenuItem('Test6'), + SimpleMenuItem('Test7',menu=submenu2), + SimpleMenuItem('Test8') + ] + + submenu.SetItems(subitems) + + items=[ + SimpleMenuItem('Test1'), + SimpleMenuItem('Test2'), + SimpleMenuItem("Test3 is a submenu m'kay",menu=submenu), + SimpleMenuItem(id=-1), + SimpleMenuItem('Test4'), + ] + + items3=[ + SimpleMenuItem('Test9'), + SimpleMenuItem('Test10'), + SimpleMenuItem('Test11'), + SimpleMenuItem('Test12'), + SimpleMenuItem('Test13'), + SimpleMenuItem('Test14'), + SimpleMenuItem('Test15'), + SimpleMenuItem('Test16'), + SimpleMenuItem('Test17') + ] + + submenu2.SetItems(items3) + + + menu.SetItems(items) + + skin='button' + size=None#(100,100)# + type='menu'#None#'toggle'# + #menu=None#self.menu# + icon=wx.Bitmap('../../../res/skins/default/statusicons/mobile.png',wx.BITMAP_TYPE_PNG)#wx.Bitmap('../../res/skins/default/tinydigsby.png',wx.BITMAP_TYPE_PNG) + + self.smb1=UberButton(self.panel,wx.NewId(),"SMB",skin,icon=icon,style=wx.HORIZONTAL,size=size,type=type,menu=menu) + + self.panel.Sizer.Add(self.smb1) + + self.Bind(wx.EVT_MENU,self.OnMenu) + + def OnMenu(self,event): + print "Hey, it works?!?" + + +def Go(): + f=Frame() + f.Show(True) + +if __name__=='__main__': + a = App( Go ) + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/uberdemos/SizerBarDemo.py b/digsby/src/tests/testgui/uberdemos/SizerBarDemo.py new file mode 100644 index 0000000..51908eb --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/SizerBarDemo.py @@ -0,0 +1,38 @@ +import wx +from gui.uberwidgets.SizerBar import SizerBar +from gui.uberwidgets.uberbook.SamplePanel import SamplePanel +from DemoApp import App + +class P(wx.Panel): + def __init__(self,parent): + wx.Panel.__init__(self,parent) + + self.Sizer=wx.BoxSizer(wx.HORIZONTAL) + + red=SamplePanel(self,'red') + blue=SamplePanel(self,'blue') + blue.MinSize=(10,10) + green=SamplePanel(self,'green') + self.sizer2=wx.BoxSizer(wx.VERTICAL) + + sb=SizerBar(self,blue,self.sizer2) + self.sizer2.Add(red,1,wx.EXPAND) + self.sizer2.Add(sb,0,wx.EXPAND) + self.sizer2.Add(blue,0,wx.EXPAND) + + self.Sizer.Add(self.sizer2,1,wx.EXPAND) + self.Sizer.Add(green,1,wx.EXPAND) + +class F(wx.Frame): + def __init__(self,parent=None): + wx.Frame.__init__(self,parent) + self.p=P(self) + + +def Go(): + f=F() + f.Show(True) + +if __name__=='__main__': + a = App( Go ) + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/UberBarDemo.py b/digsby/src/tests/testgui/uberdemos/UberBarDemo.py new file mode 100644 index 0000000..f2fb04f --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/UberBarDemo.py @@ -0,0 +1,135 @@ +import wx +from gui.uberwidgets.UberBar import UberBar +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.simplemenu import SimpleMenuItem +from gui.skin import skininit + +from util.primitives.funcs import do + + +class F4(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, wx.NewId(), "UberBar Overflow sampler",(0,0),(600,150)) + + content=wx.BoxSizer(wx.VERTICAL) + + self.skin = 'buttonbar'#None# + + self.ubar=UberBar(self,skinkey=self.skin,overflowmode=True,alignment=wx.ALIGN_LEFT) + self.b1 = UberButton(self.ubar,-1,'Button 1') + self.b2 = UberButton(self.ubar,-1,'Button 2') + self.bi = UberButton(self.ubar,-1,'Button i') + self.b3 = UberButton(self.ubar,-1,'Button 3') + self.b4 = UberButton(self.ubar,-1,'Button 4') + self.ubar.Add(self.b1) + self.ubar.Add(self.b2) + self.ubar.Add(self.bi) + self.ubar.Add(self.b3) + self.ubar.Add(self.b4) + +# self.b1.Show(self.b1,False) + self.bi.Show(self.bi,False) + + self.ubar.AddMenuItem(SimpleMenuItem('Menu Item 1')) + self.ubar.AddMenuItem(SimpleMenuItem('Menu Item 2')) + self.ubar.AddMenuItem(SimpleMenuItem('Menu Item 3')) + + + self.b4=UberButton(self.ubar, -1, icon = wx.Bitmap(resdir / 'skins/default/digsbybig.png',wx.BITMAP_TYPE_PNG)) + self.ubar.AddStatic(self.b4) + + content.Add(self.ubar,0,wx.EXPAND,0) + self.SetSizer(content) + +class F3(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, wx.NewId(), "UberBar sampler",(0,0),(600,150)) + events=[ + #(wx.EVT_BUTTON,self.onButton) + ] + do(self.Bind(event, method) for (event,method) in events) + content=wx.BoxSizer(wx.VERTICAL) + + self.skin = 'buttonbar'#None# + + self.ubar=UberBar(self,skinkey=self.skin,alignment=wx.ALIGN_LEFT) + self.b1=UberButton(self.ubar,-1,'Button 1',icon=wx.Bitmap(resdir / 'skins/default/digsbybig.png',wx.BITMAP_TYPE_PNG)) + self.b2=UberButton(self.ubar,-1,'Button 2',style=wx.VERTICAL,icon=wx.Bitmap(resdir / 'skins/default/digsbybig.png',wx.BITMAP_TYPE_PNG)) + self.b3=UberButton(self.ubar,-1,'Button 3',icon=wx.Bitmap(resdir / 'skins/default/digsbybig.png',wx.BITMAP_TYPE_PNG)) + self.ubar.Add(self.b1) + self.ubar.Add(self.b2) + self.ubar.AddSpacer() + self.ubar.Add(self.b3) + + content.Add(self.ubar,0,wx.EXPAND,0) + self.SetSizer(content) + + def onButton(self,event): + print 'clixxored!!!' + self.ubar.SetAlignment(wx.ALIGN_LEFT if self.ubar.alignment!=wx.ALIGN_LEFT else wx.ALIGN_CENTER) + +class F2(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, wx.NewId(), "UberBar sampler 2",(0,0),(600,150)) + events=[ + #(wx.EVT_BUTTON,self.onButton) + ] + do(self.Bind(event, method) for (event,method) in events) + content=wx.BoxSizer(wx.VERTICAL) + + self.skin = 'buttonbar'#None# + + self.ubar=UberBar(self,skinkey=self.skin,alignment=wx.ALIGN_CENTER) + self.b1=UberButton(self.ubar,-1,'Button 1',icon=wx.Bitmap(resdir / 'skins/default/digsbybig.png',wx.BITMAP_TYPE_PNG)) + self.b2=UberButton(self.ubar,-1,'Button 2',style=wx.VERTICAL,icon=wx.Bitmap(resdir / 'skins/default/digsbybig.png',wx.BITMAP_TYPE_PNG)) + self.b3=UberButton(self.ubar,-1,'Button 3',icon=wx.Bitmap(resdir / 'skins/default/digsbybig.png',wx.BITMAP_TYPE_PNG)) + self.ubar.Add(self.b1) + self.ubar.Add(self.b2) + self.ubar.Add(self.b3) + + content.Add(self.ubar,0,wx.EXPAND,0) + self.SetSizer(content) + + def onButton(self,event): + print 'clixxored!!!' + self.ubar.SetAlignment(wx.ALIGN_LEFT if self.ubar.alignment!=wx.ALIGN_LEFT else wx.ALIGN_CENTER) + +if __name__ == '__main__': + from tests.testapp import testapp + a = testapp() + + from gui.skin import resourcedir + global resdir + resdir = resourcedir() + + hit = wx.FindWindowAtPointer + def onKey(event): + if hasattr(wx.GetActiveWindow(),'menubar'): + menubar=wx.GetActiveWindow().menubar + if menubar.focus != None: + a.captured=True + if menubar.DoNaviCode(event.GetKeyCode()):return + elif event.GetKeyCode()==wx.WXK_ALT: + if not menubar.navimode: menubar.ToggleNaviMode(True) + return + event.Skip() + + def onKeyUp(event): + if not a.captured and hasattr(wx.GetActiveWindow(),'menubar') and event.GetKeyCode()==wx.WXK_ALT and wx.GetActiveWindow().menubar.focus == None: + wx.GetActiveWindow().menubar.AltFocus() + a.captured=False + return + a.captured=False + event.Skip() + + a.Bind(wx.EVT_KEY_DOWN, onKey) + a.Bind(wx.EVT_KEY_UP, onKeyUp) + f2=F2() + f2.Show(True) + f3=F3() + f3.Show(True) + f4=F4() + f4.Show(True) + a.captured=False + + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/UberBookDemo.py b/digsby/src/tests/testgui/uberdemos/UberBookDemo.py new file mode 100644 index 0000000..40c56f2 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/UberBookDemo.py @@ -0,0 +1,88 @@ +import wx + +from util.primitives.funcs import do +from gui import skin + +from gui.uberwidgets.uberbook.UberBook import NoteBook +from gui.uberwidgets.uberbook.tabmanager import TabManager +from gui.uberwidgets.uberbook.dragtimer import WinDragTimer +from gui.uberwidgets.uberbook.OverlayImage import SimpleOverlayImage +from gui.uberwidgets.uberbook.SamplePanel import SamplePanel +from gui.windowfx import fadein + +class F(wx.Frame): + """ + Sample frame + """ + def __init__(self,winman,tabman,pos=wx.DefaultPosition): + wx.Frame.__init__(self, None, wx.NewId(), "UberBook Sampler", pos, (600, 150)) + + events=[(wx.EVT_MOVE, self.OnMove), + (wx.EVT_SIZE, self.OnSize)] + + do(self.Bind(event,method) for (event,method) in events) + + self.skin = 'Tabs' + + self.content = wx.BoxSizer(wx.VERTICAL) + self.winman = winman + self.tabman = tabman + self.notebook = NoteBook(self, self, self.tabman, self.skin) + self.content.Add(self.notebook, 1, wx.EXPAND) + + self.SetSizer(self.content) + + self.focus = False + + self.mergstuff = None + + def OnMove(self,event): + self.mergstuff = wx.CallLater(10,self.notebook.StartWindowDrag) + + def OnSize(self,event): + if self.mergstuff: + self.mergstuff.Stop() + self.mergstuff = None + event.Skip() + + + def Close(self): + self.tabman.books.remove(self.notebook) + wx.Frame.Close(self) + + def AddTabs(self): + colors = ['green', 'blue', 'red', 'yellow', 'MEDIUM FOREST GREEN','purple', 'grey', 'white'] + do(self.notebook.Add(SamplePanel(self.notebook.pagecontainer, color)) + for color in colors) + + def AddTabs2(self): + colors = ['black', 'aquamarine', 'coral', 'orchid'] + do(self.notebook.Add(SamplePanel(self.notebook.pagecontainer, color)) for color in colors) + + + +class WinManTmp(object): + def __init__(self): + self.wdt = WinDragTimer() + self.tabman = TabManager() + self.f = self.NewWindow(self.tabman, (50, 50)) + self.f.AddTabs() + self.f2 = self.NewWindow(self.tabman, (200,200)) + self.f2.AddTabs2() + + + + def NewWindow(self,tabman=None,pos=wx.DefaultPosition): + if pos[1]<0:pos[1]=0 + Fn=F(self,tabman,pos) if tabman else F(self,self.tabman,pos) + Fn.Show(False) + fadein(Fn,'quick') + return Fn + +if __name__ == '__main__': + from tests.testapp import testapp + + a = testapp('../../../../') + a.winman = WinManTmp() + + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/UberButtonDemo.py b/digsby/src/tests/testgui/uberdemos/UberButtonDemo.py new file mode 100644 index 0000000..93c9000 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/UberButtonDemo.py @@ -0,0 +1,130 @@ +import wx +from gui.uberwidgets.UberButton import UberButton +#from gui.uberwidgets.umenu import UMenu + + + +class F(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, wx.NewId(), "Button sampler",(400,100),(400,600),wx.DEFAULT_FRAME_STYLE) + self.panel=P(self) + +class P(wx.Panel): + def __init__(self,parent): + wx.Panel.__init__(self,parent,-1)#,style = wx.CLIP_CHILDREN|wx.CLIP_SIBLINGS + + self.Bind(wx.EVT_BUTTON,self.onButton), + self.Bind(wx.EVT_LEFT_UP,self.OnMouse), + self.Bind(wx.EVT_LEFT_DOWN,self.OnMouse) + + self.skin = 'button' +# print self.skin +# self.menu=UMenu(self) +# self.menu.Append(wx.NewId(),'item 1') +# self.menu.Append(wx.NewId(),'item 2') +# self.menu.Append(wx.NewId(),'item 3') + + content=wx.BoxSizer(wx.VERTICAL) + + size=None#(200,50)# + type=None#'menu'#'toggle'#'toggle'# + menu=None#self.menu# + icon=wx.Bitmap('../../../../res/skins/default/statusicons/mobile.png',wx.BITMAP_TYPE_PNG)#wx.Bitmap('../../res/skins/default/tinydigsby.png',wx.BITMAP_TYPE_PNG) + #label= "button"#"Super wide Button Name of Impending doooooooooooom!!!"#"button"# + skin=self.skin#None# + + self.b1=UberButton(self,wx.NewId(),'&One',skin,icon=icon,style=wx.HORIZONTAL,size=size,type=type,menu=menu) +# self.b1.SetStaticWidth(60) + self.b2=UberButton(self,wx.NewId(),'&Two',icon=icon,style=wx.HORIZONTAL,size=size,type=type,menu=menu) + self.b3=UberButton(self,wx.NewId(),'T&hree',skin,icon=icon,style=wx.VERTICAL,size=size,type=type,menu=menu) + self.b4=UberButton(self,wx.NewId(),'&Four',icon=icon,style=wx.VERTICAL,size=size,type=type,menu=menu) + self.b5=UberButton(self,wx.NewId(),"",skin,icon=icon,size=size,type=type,menu=menu) + self.b6=UberButton(self,wx.NewId(),"",icon=icon,size=size,type=type,menu=menu) + self.b7=UberButton(self,wx.NewId(),'Fi&ve',skin,size=size,type=type,menu=menu) + self.b8=UberButton(self,wx.NewId(),'&Six',size=size,type=type,menu=menu) + self.b9=UberButton(self,wx.NewId(),"",skin,size=size,type=type,menu=menu) + self.b10=UberButton(self,wx.NewId(),"",size=size,type=type,menu=menu) + self.b11 = wx.Button(self, wx.NewId(), 'Native') + self.b12= wx.Button(self, wx.NewId(), ' ') + self.b12.Bind(wx.EVT_BUTTON,self.ToggleAlignment) + + self.b13 = UberButton(self, wx.NewId(), 'active?', self.skin) + self.b13.Bind(wx.EVT_BUTTON,self.ToggleSkin) + + wexp = 0#wx.EXPAND#wx.ALL| + hrat = 0#1# + pad = 0 + + content.Add(self.b1,hrat,wexp,pad) + content.Add(self.b2,hrat,wexp,pad) + content.Add(self.b3,hrat,wexp,pad) + content.Add(self.b4,hrat,wexp,pad) + content.Add(self.b5,hrat,wexp,pad) + content.Add(self.b6,hrat,wexp,pad) + content.Add(self.b7,hrat,wexp,pad) + content.Add(self.b8,hrat,wexp,pad) + content.Add(self.b9,hrat,wexp,pad) + content.Add(self.b10,hrat,wexp,pad) + content.Add(self.b11,hrat,wexp,pad) + content.Add(self.b12,hrat,wexp,pad) + content.Add(self.b13, hrat, wexp, pad) + + + self.SetSizer(content) + + def onButton(self,event): + print event.GetEventObject().Label +# print "..." +# if type(event.EventObject)==wx.Button: +# print event.EventObject.Label +# else: +# print event.EventObject.label +# switch= not self.b1.IsEnabled() +# self.b1.Enable(switch) +# self.b2.Enable(switch) +# self.b3.Enable(switch) +# self.b4.Enable(switch) +# self.b5.Enable(switch) +# self.b6.Enable(switch) +# self.b7.Enable(switch) +# self.b8.Enable(switch) +# self.b9.Enable(switch) +# self.b10.Enable(switch) +# self.b11.Enable(switch) + + def ToggleSkin(self,event = None): + key = None if self.b1.skinkey else 'button' + + print 'toggleskin to',key + self.b1.SetSkinKey(key,1) + self.b3.SetSkinKey(key,1) + self.b5.SetSkinKey(key,1) + self.b7.SetSkinKey(key,1) + self.b9.SetSkinKey(key,1) + + def ToggleAlignment(self,event = None): + print "ToggleAlignment DISABLED!!! It can not be!!!" +# align = wx.VERTICAL if self.b1.Alignment == wx.HORIZONTAL else wx.HORIZONTAL +# self.b1.Alignment = align +# self.b3.Alignment = align +# self.b5.Alignment = align +# self.b7.Alignment = align +# self.b9.Alignment = align +# self.Layout() + + + def OnMouse(self,event): + print "window caught mouse" + + +if __name__ == '__main__': + from tests.testapp import testapp + + hit = wx.FindWindowAtPointer + + a = testapp() + + f=F() + f.Show(True) + + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/UberComboDemo.py b/digsby/src/tests/testgui/uberdemos/UberComboDemo.py new file mode 100644 index 0000000..3ac8240 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/UberComboDemo.py @@ -0,0 +1,94 @@ +import wx +from gui.uberwidgets.UberCombo import UberCombo +from gui import skin as skincore +from gui.textutil import GetFonts +from gui.uberwidgets.simplemenu import SimpleMenuItem + +from util.primitives.funcs import do + +class F(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None,size=(500,55)) + + events=[ + (wx.EVT_COMBOBOX,self.OnSelect), +# (wx.EVT_TEXT,self.OnType), +# (wx.EVT_TEXT_ENTER,self.OnPressEnter) + ] + do(self.Bind(event,method) for (event,method) in events) + + self.skin='combobox' + self.uc=UberCombo(self, skinkey=self.skin,typeable=False,size=(100,20),maxmenuheight=3,minmenuwidth=400,)#selectioncallback=self.defaultmethodtest) + items=[ + SimpleMenuItem([skincore.get("statusicons.typing"),'Test1']), + SimpleMenuItem([skincore.get("statusicons.typing"),skincore.get("statusicons.typing"),'Test2']), + SimpleMenuItem([skincore.get("statusicons.typing"),skincore.get("statusicons.typing"),skincore.get("statusicons.typing"),'Test3 followed by a long line of thext so I can see if truncating worked well or not, maybe?']), + SimpleMenuItem(id=-1), + SimpleMenuItem([skincore.get("statusicons.typing"),skincore.get("statusicons.typing"),skincore.get("statusicons.typing"),'Test4 cause I can']) + ] + self.uc.SetItems(items) + + self.ucf=UberCombo(self, skinkey=self.skin,typeable=False,size=(100,20),maxmenuheight=10,minmenuwidth=400)#font method + self.ucf.SetItems(self.DoFonts()) + + self.uct=UberCombo(self, value='test',skinkey=self.skin,typeable=True,valuecallback=self.ValueTest,size=(100,20)) + self.uct.AppendItem(SimpleMenuItem('Sample Item 1')) + self.uct.AppendItem(SimpleMenuItem('Sample Item 2')) + self.uct.AppendItem(SimpleMenuItem(id=-1)) + self.uct.AppendItem(SimpleMenuItem('Sample Item 3')) + + + sizer=wx.BoxSizer(wx.HORIZONTAL) + self.SetSizer(sizer) + sizer.Add(self.uc,1,wx.EXPAND | wx.ALL, 3) + sizer.Add(self.ucf,1,wx.EXPAND | wx.ALL, 3) + sizer.Add(self.uct,1,wx.EXPAND | wx.ALL, 3) + + self.Fit() + + def DoFonts(self): + fontlist = GetFonts() + fontitems = [] + for font in fontlist: + wxfont = wx.Font(self.Font.GetPointSize(), wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_NORMAL, False, font) + if font[0] != '@': + fontitems.append(SimpleMenuItem([font], font=wxfont)) + + return fontitems + + def defaultmethodtest(self,item): + print item.id + for i in item.content: + if isinstance(i,basestring): + print i + break + + def DifferentMethodTest(self,item): + print 'workage!!' + + def CycleTest(self,button): + print 'yey!' + button.Parent.display.TypeField() + button.Parent.display.txtfld.SetSelection(-1,-1) + + def OnSelect(self,event): + print 'OnSelect:', event.GetInt() + + def OnType(self,event): + print 'OnType' + + def OnPressEnter(self,event): + print 'OnPressEnter' + + def ValueTest(self,value): + print 'valuecallback',value + +if __name__ == '__main__': + from tests.testapp import testapp + hit = wx.FindWindowAtPointer + a = testapp('../../../../') + f=F() + f.Show(True) + del f + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/UberComboXTDemo.py b/digsby/src/tests/testgui/uberdemos/UberComboXTDemo.py new file mode 100644 index 0000000..9c8af97 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/UberComboXTDemo.py @@ -0,0 +1,152 @@ +import wx +from DemoApp import App +from gui.uberwidgets.UberCombo import UberCombo +from gui import skin as skincore +from gui.uberwidgets.simplemenu import SimpleMenu,SimpleMenuItem +from util.primitives.funcs import do +from logging import getLogger; log = getLogger('ComboListEditor') + +class ComboListEditor(UberCombo): + def __init__(self, parent, content_callback, objects = None): + UberCombo.__init__(self, parent, skinkey = 'combobox', typeable = False, + valuecallback = self.OnValue, + selectioncallback = lambda item: self.ChangeValue(item)) + + self.remove_menu = SimpleMenu(self.menu, 'simplemenu',callback = self.OnRemove) + self.content_cb = content_callback + + if objects is not None: + self.SetList(objects) + + def SetList(self, objs): + self.objects = objs + + # create SimpleMenuItems for each domain object + items = [SimpleMenuItem(self.content_cb(obj)) for obj in objs] + self.remove_menu.SetItems(items) + self.SetItems(items) + + self.separator = SimpleMenuItem(id = -1) + self.AppendItem(self.separator) + + self.remove_item = SimpleMenuItem(_('Remove'), menu = self.remove_menu) + + self.AppendItem(SimpleMenuItem(_('Add'), method = lambda: self.EditValue())) + self.AppendItem(self.remove_item) + + + def OnValue(self, value): + if value is None: return + + skip = False + for t in self.GetStringsAndItems(): + if t[0] == value: + skip = True + self.ChangeValue( t[1] ) + break + + if not skip: + item = self.content_cb(value) + self.InsertItem(-3, item) + self.remove_menu.AppendItem(item) + self.ChangeValue(item) + + if not self.remove_item in self: + self.AppendItem(self.remove_item) + self.InsertItem(-2, self.separator) + + def OnRemove(self, item): + i = self.GetItemIndex(item) + log.info('removing %s', self.objects.pop(i)) + + self.RemoveItem(item) + self.remmenu.RemoveItem(item) + + if self.GetValue() == item: + self.ChangeValue(self[0]) + + if self.remove_menu.Count == 0: + self.RemoveItem(self.remove_item) + self.RemoveItem(self.separator) + +class Frame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,title='Add Remove Combo') + self.panel=wx.Panel(self) + + self.panel.Sizer=wx.BoxSizer(wx.VERTICAL) + + self.uc=UberCombo(self.panel, skinkey = 'combobox', + typeable = False, size=(200,20), minmenuwidth=200, + valuecallback = self.ValCB, + selectioncallback = self.SelCB) + + self.remmenu=SimpleMenu(self.uc.menu,'simplemenu',width=200,callback=self.RemCB) + + items=[ + SimpleMenuItem('atc282@samplemail.com'), + SimpleMenuItem('x2ndshadow@samplemail.com'), + SimpleMenuItem('brokenhalo282@samplemail.com'), + SimpleMenuItem('brok3nhalo@samplemail.com') + ] + + self.panel.Sizer.Add(self.uc,0,wx.EXPAND) + + do(self.remmenu.AppendItem(item) for item in items) + + do(self.uc.AppendItem(item) for item in items) + self.sep=SimpleMenuItem(id=-1) + self.uc.AppendItem(self.sep) + + self.remitem=SimpleMenuItem('Remove', menu = self.remmenu) + + self.uc.AppendItem(SimpleMenuItem('Add', method = self.AddCB)) + self.uc.AppendItem(self.remitem) + + def AddCB(self,item): + self.uc.EditValue() + + def RemCB(self,item): + combo = self.uc + + combo.RemoveItem(item) + self.remmenu.RemoveItem(item) + + if combo.GetValue()==item: + combo.ChangeValue(combo[0]) + + if len(self.remmenu.spine.items) == 0: + combo.RemoveItem(self.remitem) + combo.RemoveItem(self.sep) + + def ValCB(self,value): + value = value.lower() + + if value: + thelist = self.uc.GetStringsAndItems() + skip=False + for t in thelist: + if t[0] == value: + skip=True + self.uc.ChangeValue(t[1]) + break + if not skip: + item = SimpleMenuItem(value) + self.uc.InsertItem(-3,item) + self.remmenu.AppendItem(item) + self.uc.ChangeValue(item) + + if not self.remitem in self.uc.menu.spine.items: + self.uc.AppendItem(self.remitem) + self.uc.InsertItem(-2, self.sep) + + def SelCB(self,item): + self.uc.ChangeValue(item) + +def Go(): + f=Frame() + f.Show(True) + +if __name__=='__main__': + a = App( Go ) + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/UberEmotiBoxDemo.py b/digsby/src/tests/testgui/uberdemos/UberEmotiBoxDemo.py new file mode 100644 index 0000000..927ef10 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/UberEmotiBoxDemo.py @@ -0,0 +1,36 @@ +from __future__ import with_statement +import wx + +from gui.uberwidgets.UberEmotiBox import EmotiHandler,UberEmotiBox + +from gui.skin import skininit + +from util.primitives.funcs import do + +class F(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,size=(75,100)) + + events=[ + (wx.EVT_BUTTON,self.OnButton) + ] + do(self.Bind(event,method) for (event,method) in events) + emotihandler=EmotiHandler() + self.cp=UberEmotiBox(self,emotihandler) + + self.b1=wx.Button(self,label='^_^') + + def OnButton(self,event): + #print 'open faces' + self.cp.Display(self.b1.ScreenRect) + +class A(wx.App): + def OnInit(self): + skininit('../../../../res') + f=F() + f.Show(True) + return True + +if __name__=='__main__': + a=A(0) + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/UberProgressBarDemo.py b/digsby/src/tests/testgui/uberdemos/UberProgressBarDemo.py new file mode 100644 index 0000000..acfdd86 --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/UberProgressBarDemo.py @@ -0,0 +1,32 @@ +import wx +from gui.uberwidgets.UberProgressBar import UberProgressBar +from gui import skin as skincore + + + +class F(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, wx.NewId(), "Progress Bar sampler",(0,0),(600,250)) + + self.Bind(wx.EVT_SLIDER, self.on_slide) + self.content = wx.BoxSizer(wx.VERTICAL) + self.g = UberProgressBar(self,wx.NewId(),100,'progressbar',showlabel=True,size=(300,20)) + self.s = wx.Slider(self, -1, 0, 0, 100, (0,0), (300, 50)) + + self.content.Add(self.g,0,wx.ALIGN_CENTER_HORIZONTAL) + self.content.Add(self.s,0,wx.ALIGN_CENTER_HORIZONTAL) + self.SetSizer(self.content) + + def on_slide(self,e): + self.g.SetValue(self.s.GetValue()) + print self.s.GetValue() + + + +if __name__=='__main__': + a = wx.PySimpleApp( 0 ) + skincore.skininit('../../../../res') + f=F() + f.Show(True) + + a.MainLoop() diff --git a/digsby/src/tests/testgui/uberdemos/__init__.py b/digsby/src/tests/testgui/uberdemos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/tests/testgui/uberdemos/simplemenudemo.py b/digsby/src/tests/testgui/uberdemos/simplemenudemo.py new file mode 100644 index 0000000..990e8ec --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/simplemenudemo.py @@ -0,0 +1,45 @@ +from DemoApp import App +import wx +#from gui import skin as skincore +from gui.uberwidgets.simplemenu import SimpleMenu,SimpleMenuItem +from gui.uberwidgets.UberButton import UberButton + +class Frame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self,None,title='Simple Menu Test') + self.panel=wx.Panel(self) + + self.panel.Sizer=wx.BoxSizer(wx.VERTICAL) + + + menu=SimpleMenu(self, skinkey='simplemenu',maxheight=10,width=100) + + + items=[ + SimpleMenuItem('Test1'),#,self.DifferentMethodTest), + SimpleMenuItem('Test2'), + SimpleMenuItem('Test3'), + SimpleMenuItem(id=-1), + SimpleMenuItem('Test4') + ] + + menu.SetItems(items) + + skin='button' + size=None#(100,100)# + type='menu'#None#'toggle'# + #menu=None#self.menu# + icon=None#wx.Bitmap('../../../res/skins/default/statusicons/mobile.png',wx.BITMAP_TYPE_PNG)#wx.Bitmap('../../res/skins/default/tinydigsby.png',wx.BITMAP_TYPE_PNG) + + self.smb1=UberButton(self.panel,wx.NewId(),"SMB",skin,icon=icon,style=wx.HORIZONTAL,size=size,type=type,menu=menu) + + self.panel.Sizer.Add(self.smb1) + + +def Go(): + f=Frame() + f.Show(True) + +if __name__=='__main__': + a = App( Go ) + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/testgui/uberdemos/testPopup.py b/digsby/src/tests/testgui/uberdemos/testPopup.py new file mode 100644 index 0000000..32ffbbd --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/testPopup.py @@ -0,0 +1,219 @@ +''' +An interactive demo for testing popup functionality. +''' + +import wx + +from tests.testapp import testapp +from logging import getLogger; log = getLogger('testpopup') +from gui.controls import Button +from gui import windowfx +from gui.toast import popup, Popup +from config import platformName + +class input_callback(object): + '''Passed to fire() as handlers for "buttons" callbacks. Causes popups to + show input fields after pressing those buttons.''' + + # TODO: document this interface and place an abstract class in toast.py + + close_button = True + spellcheck = True + + def __init__(self, cb): + self.input_cb = cb + self.get_value = lambda *k: 'value' + self.char_limit = 140 + +def main(): + a = testapp(plugins=False) + f = wx.Frame(None, title = 'Popup Test', style = wx.STAY_ON_TOP | wx.DEFAULT_FRAME_STYLE, + size = (400, 300)) + f.CenterOnScreen() + f.Bind(wx.EVT_CLOSE, lambda e: windowfx.fadeout(f)) + f.Sizer = sz = wx.GridSizer(2, 2) + + if platformName == "win": + from gui.native import process + count_gdi_objects = process.count_gdi_objects + def settitle(): + f.Title = 'Popup Test (gdi: %s)' % count_gdi_objects() + settitle() + + title_timer = wx.PyTimer(settitle) + title_timer.Start(1000, False) + + from gui import skin + service_icons = [skin.get('serviceicons.' + srv) for srv in ('aim', 'icq', 'msn', 'yahoo', 'jabber', 'gtalk')] + + for b in 'upperleft upperright lowerleft lowerright'.split(): + button = wx.Button(f, -1, b) + button.Bind(wx.EVT_BUTTON, lambda e, b=b: popup(position = b, + badge = skin.get('serviceicons.facebook', None), + header = 'header abcdefghijklmnopqrstuvwxyz djaflk dajlfk djaklf dsjaklf djakl', + major = 'major', + minor = 'minorfd jaklf jdkla fjkldwads fdjsa flkejw alfkd jsaklf jdsklafjdklsafjkl---wdq------------------fdasfsda------------fdasfdsa----fdasfdas---------------------------------------------------------------jdskla fjklsdaa jfkldwa jfkldsa jfklds ajklfds ajklfds ajklfds ajkl')) + sz.Add(button) + + class Target(object): + def OnSave(self): print 'save' + def OnSaveAs(self): print 'save as' + def OnCancel(self): print 'cancel' + + target = Target() + + b = Button(f, 'with buttons', lambda: popup(position = 'lowerright', + header = 'digsbyDragon', + major = 'wants to send you file "digsby.zip"', + buttons = [('Save', lambda: log.info('SAVE'))], + target = target)) + def input_cb(text, opts): + print 'message:', text, opts + + def timed(): + for x, msg in enumerate('lol roflmao brb afk jk wtf roflcopterz omg rusrs?'.split() * 3): + wx.CallLater(1000 * x+1, lambda msg = msg: popup(header = 'digsby', minor = msg, + input = input_cb, popupid = 'wassup')) + + + + b2 = Button(f, 'with several inputs', timed) + + + class Email(object): + def __init__(self, subject, message): + self.subject = subject + self.message = message + + b3 = Button(f, 'with pages', lambda: popup(popupid = ('a','b'), + update = 'paged', + position = 'lowerright', + header = '${email.subject}', + major = '${email.message}', + pages = 'emails', + emails = [Email('test subject', 'test content'), + Email('test subject 2', 'testcontent2')], + )) + + + email_pages_args = dict(position = 'lowerright', + header = '${email.subject}', + minor = '${email.message}', + pages = 'emails', + emails = [Email('test subject', 'test really long really long really ' * 10), + Email('test subject 2', 'testcontent2')], + onclick = lambda a: log.info('wut')) + + b4 = Button(f, 'with pages', + lambda: popup(**email_pages_args)) + + b5 = Button(f, 'after 2 secs', + lambda: wx.CallLater(2000, lambda: popup(position = 'lowerleft', + header = '${email.subject}', + major = '${email.message}', + pages = 'emails', + emails = [Email('test subject', 'test content'), + Email('test subject 2', 'testcontent2')], + ))) + + def prnt(*a): + for f in a: print f, + print + + def button_func(item): + return [(item.email.subject, lambda *a, **k: prnt(item))] + + b11 = Button(f, 'multiple buttons', + lambda: popup(position = 'lowerleft', + header = '${email.subject}', + major = '${email.message}', + pages = 'emails', + emails = [Email('test subject', 'test content'), + Email('test subject 2', 'testcontent2')], + buttons = button_func + )) + b9 = Button(f, 'new paged', + lambda: popup(update='paged', header='blah', + major='bleeh', minor='bluh', + popupid='facebook.alerts')) + + from itertools import count + count = count() + + def page_adder(position='lowerleft'): + id = count.next() + + if id == 0: + d = dict(major = '${email.message}', minor='') + else: + d = dict(minor ='${email.message}', major ='') + + popup(position = position, + header = '${email.subject}', + pages = 'emails', + email = Email('test subject 2', 'content%d'%id), + #update ='paged', + popupid = 'add', + onclick = lambda a: log.info('rawr %d', id), + max_lines = 20, + **d + ) + + b6 = Button(f, 'add pages', page_adder) + + b6_2 = Button(f, 'add pages (top left)', lambda: page_adder('upperleft')) + + b7 = Button(f, 'show modal dialog', lambda: wx.MessageBox('modal!')) + + def many(): + for x in xrange(3): + popup(header ='wut', major = 'major', minor = 'minor') + + b8 = Button(f, 'show many at once', many) + + def input_and_pages(): + args = dict(email_pages_args) + args.update(input = input_cb) + popup(**args) + + def input_appends(): + popup(input = lambda text, opts: '> ' + text, + header = 'test', + minor = 'minor', + max_lines=5) + + + + b10 = Button(f, 'input + pages', input_and_pages) + + b12 = Button(f, 'input appends', input_appends) + + def input_after_buttons_popup(): + buttons = [('test', input_callback(lambda **k: None))] + popup(header='input after buttons', + buttons = buttons) + + input_after_button = Button(f, 'input after button', input_after_buttons_popup) + + sz.AddMany((b, b2, b3, b4, b5, b6, b6_2, b7, b8, b9, b10, b11, b12)) + sz.AddMany([ + input_after_button, + ]) + + sz.Add(wx.TextCtrl(f)) + + if hasattr(Popup, 'use_alphaborder'): + use_alpha = wx.CheckBox(f, -1, 'use alphaborder') + use_alpha.SetValue(Popup.use_alphaborder) + use_alpha.Bind(wx.EVT_CHECKBOX, lambda e: setattr(Popup, 'use_alphaborder', e.Checked)) + + sz.Add(use_alpha) + + f.Bind(wx.EVT_CLOSE, lambda e: wx.GetApp().ExitMainLoop()) + f.Show() + + a.MainLoop() + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/testgui/uberdemos/translink.py b/digsby/src/tests/testgui/uberdemos/translink.py new file mode 100644 index 0000000..42c961d --- /dev/null +++ b/digsby/src/tests/testgui/uberdemos/translink.py @@ -0,0 +1,64 @@ +import wx +from gui.textutil import CopyFont,default_font +from gui.uberwidgets.pseudosizer import PseudoSizer +from gui.uberwidgets.cleartext import ClearText +from gui.uberwidgets.clearlink import ClearLink +from util.primitives.funcs import Delegate + + +a=wx.PySimpleApp() +f=wx.Frame(None,size=(450,450)) +f.Font=CopyFont(default_font(),pointSize=15) +p=wx.Panel(f) + +p.Sizer=wx.BoxSizer(wx.HORIZONTAL) + +#l=ClearLink(p,-1,'Super link of epic proportions','google.com') +#l.VisitedColour=wx.BLUE +#x=ClearLink(p,-1,'Mega Tokyo','megatokyo.com') +#y=ClearLink(p,-1,'Errant Story','errantstory.com') +#z=ClearLink(p,-1,'VG Cats','vgcats.com') +#t=PseudoSizer() +#t.Add(x) +#t.Add(y) +#t.Add(z) + +#t=ClearText(p,label='Test') +#t.SetPosition((0,50)) + +st= """Dreams come true: Capcom has confirmed that Harvey Birdman: Attorney At Law, their game based on the hilarious Adult Swim cartoon courtroom comedy, will come to Wii as............ well.""" +# +#Capcom also said that all three versions -- Wii, PS2, and PSP -- will hit retail on November 13. If you want motion control with your lawyering, you'll have to pay up, as the Wii version will run you $40 while the Sony versions only cost $30. +# +#Totally worth it.""" + + +b=wx.Bitmap('c:/b.jpg') +def paint(event): + dc=wx.AutoBufferedPaintDC(p) + b2=wx.BitmapFromImage(wx.ImageFromBitmap(b).Scale(*p.Size)) + dc.DrawBitmap(b2,0,0,True) + + p.ChildPaints(dc) + +def OnSize(event): + ct.Wrap(p.Size.width - 4,4) + event.Skip() + +p.Bind(wx.EVT_PAINT,paint) +p.Bind(wx.EVT_SIZE,lambda e: (p.Layout(),p.Refresh())) +p.Bind(wx.EVT_ERASE_BACKGROUND,lambda e: None) +p.Bind(wx.EVT_SIZE,OnSize) +p.ChildPaints = Delegate() +#p.Sizer.Add(wx.ComboBox(p,-1,"test1"),0,0) +ct = ClearText(p, st) +ct.FontColor = wx.GREEN +ct.Font = f.Font + +p.Sizer.Add(ct,0,0) + +#p.Sizer.Add(wx.Button(p,-1,"test"),0,0) + +#l.Position=(10,10) +f.Show(True) +a.MainLoop() diff --git a/digsby/src/tests/testlogger.py b/digsby/src/tests/testlogger.py new file mode 100644 index 0000000..664b774 --- /dev/null +++ b/digsby/src/tests/testlogger.py @@ -0,0 +1,40 @@ +from __future__ import with_statement +from util import Storage as S +from path import path +from calendar import Calendar +from random import randrange, shuffle +from datetime import datetime + +def generate_random(): + 'Generates a years worth of random logs.' + + p = path('c:\\test') + + words = 'foo bar to be or not the and apple orange banana cherry futon proleptic gregorian ordinal'.split() + + cal = Calendar() + + for month in xrange(1, 12): + for day in cal.itermonthdates(2007, month): + + messages = [] + for x in xrange(2, randrange(20, 50)): + shuffle(words) + + messages.append( + S(buddy = S(name = 'digsby0%d' % randrange(1, 3)), + timestamp = random_time_in_day(day), + message = ' '.join(words[:randrange(1, len(words)+1)]))) + + messages.sort(key = lambda mobj: mobj['timestamp']) + + daylog = p / (day.isoformat() + '.html') + + with daylog.open('w') as f: + f.write(html_header % dict(title = 'IM Logs with %s on %s' % ('digsby0%d' % randrange(1, 3), day.isoformat()))) + + for mobj in messages: + f.write(generate_output_html(mobj)) + +def random_time_in_day(day): + return datetime(day.year, day.month, day.day, randrange(0, 24), randrange(0, 60), randrange(0, 60)) diff --git a/digsby/src/tests/testnet.py b/digsby/src/tests/testnet.py new file mode 100644 index 0000000..fdc5d6d --- /dev/null +++ b/digsby/src/tests/testnet.py @@ -0,0 +1,365 @@ +import util.net as net +import time + +def testSpacify(): + tests = [ + ('', ''), + (' ', ' '), + (' ', ' '), + (' ', '   '), + (' ' * 1000, ' ' + ''.join([' '] * 998) + ' '), + ] + + + for i, (input, expected) in enumerate(tests): + result = net.spacify(input) + + if result != expected: + print "%d) FAIL result: %r, expected: %r" % (i, result, expected) + else: + print '%d) OK' % i + + +linkify_test_strings = ( + 'n.com', 'n.com', + 'x.com', 'x.com', + 'http://www.digsby.com', 'http://www.digsby.com', + 'http://digsby.com', 'http://digsby.com', + 'www.digsby.com.', 'www.digsby.com.', + '{digsby.com}', '{digsby.com}', + 'www.digsby.com/woot!!!', 'www.digsby.com/woot!!!', + 'not.a.link', 'not.a.link', + 'gopher://awesome', 'gopher://awesome', + 'http://digsby.com:8080', 'http://digsby.com:8080', + 'http://user:pass@www.digsby.com', 'http://user:pass@www.digsby.com', + '(www.digsby.com/path/to/stuff)', '(www.digsby.com/path/to/stuff)', + 'http://en.wikipedia.org/wiki/Robots_(computer_game)', 'http://en.wikipedia.org/wiki/Robots_(computer_game)', + 'https://www.selectblinds.com/fauxwoodblinds/faux-wood-blinds-detail.aspx?pid=94&c=1', 'https://www.selectblinds.com/fauxwoodblinds/faux-wood-blinds-detail.aspx?pid=94&c=1', + 'http://mini/mike/digsySetup.exe', 'http://mini/mike/digsySetup.exe', + 'http://img.thedailywtf.com/images/200801/Error\'d/Unvisable.jpg', '''http://img.thedailywtf.com/images/200801/Error'd/Unvisable.jpg''', + 'http://www.flickr.com/photos/55972631@N00/2281143312/', 'http://www.flickr.com/photos/55972631@N00/2281143312/', + 'https://edit.europe.yahoo.com/config/replica_agree?.done=http%3a//mail.yahoo.com&.scrumb=JwNajALT0z3', 'https://edit.europe.yahoo.com/config/replica_agree?.done=http%3a//mail.yahoo.com&.scrumb=JwNajALT0z3', + 'sup', 'sup', + 'This is just a test.', 'This is just a test.', + 'False positives.suck', 'False positives.suck', + 'svn://mini/svn/dotSyntax', 'svn://mini/svn/dotSyntax', + 'http://www.google.com/search?q=digsby&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a', 'http://www.google.com/search?q=digsby&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a', + '#', '#', + '.', '.', + '/', '/', + '("http://mini/digsby_setup_r12699.exe")', + '("http://mini/digsby_setup_r12699.exe")', + + 'http://www.weather.com/outlook/events/sports/tenday/75075?from=36hr_topnav_sports', + 'http://www.weather.com/outlook/events/sports/tenday/75075?from=36hr_topnav_sports', + + 'http://aapplemint.blogspot.com/2007/02/ghanian-palm-nut-soup.html', + 'http://aapplemint.blogspot.com/2007/02/ghanian-palm-nut-soup.html', + + '(http://mini/cgi-bin/ticket/2494)', + '(http://mini/cgi-bin/ticket/2494)', + + '3.ly', + '3.ly', + + 'fg.dm,fg', 'fg.dm,fg', + + 'http://translationparty.com/#993473', + 'http://translationparty.com/#993473', + + # from http://twitter.com/Scott_Ian/status/9308822323 + '''Vote for Pearl here:http://www.goldengodsawards.com/vote.html''', + '''Vote for Pearl here:http://www.goldengodsawards.com/vote.html''', + + '''http://site.com/search.php?section=people&ginv=191#c[city]=181&c[country]=4&c[noiphone]=1&c[section]=people&c[sort]=1&offset=10''', + '''http://site.com/search.php?section=people&ginv=191#c[city]=181&c[country]=4&c[noiphone]=1&c[section]=people&c[sort]=1&offset=10''', + + 'http://v.digsby.com/?id=kr7njks1&wid=0kknv56hq1', + 'http://v.digsby.com/?id=kr7njks1&wid=0kknv56hq1', + + 'http://live.xbox.com/en-US/profile/Achievements/ViewAchievementDetails.aspx?tid=%09]%3acn*i7', + 'http://live.xbox.com/en-US/profile/Achievements/ViewAchievementDetails.aspx?tid=%09]%3acn*i7', + + 'http://www.amazon.com/gp/offer-listing/B0009VXBAQ/ref=dp_olp_1?ie=UTF8&qid=1210776182&sr=1-1', + 'http://www.amazon.com/gp/offer-listing/B0009VXBAQ/ref=dp_olp_1?ie=UTF8&qid=1210776182&sr=1-1', + + 'www.\xe2\x98\x83.com'.decode('utf8'), + 'www.\xe2\x98\x83.com'.decode('utf8'), +) + +known_failures = ( + "log.info('yahoo", + "log.info('yahoo", + + 'top left icon worked (http://mini/cgi-bin/ticket/2494)...do you remember', + 'top left icon worked (http://mini/cgi-bin/ticket/2494)...do you remember', + + 'face.com\'s', + 'face.com\'s', + + "google.com's", + '''google.com's''', + +) + +linkify_test_strings = zip(linkify_test_strings[::2], linkify_test_strings[1::2]) + +def testLinkify(linkifyfunc): + + from sys import stdout + success = 0 + for url, result in linkify_test_strings: + myresult = linkifyfunc(url) + + if myresult == result: + sout, out = stdout, stdout.write + out('OK\n') + success += 1 + else: + sout, out = stdout, stdout.write + out('FAIL\n') + + out(' result: %s\n' % myresult) + out(' expected: %s\n\n' % result) + sout.flush() + + print;print '%d/%d correct.' % (success, len(linkify_test_strings)) + +def testProxiesURLs(test_proxies): + ''' + Tests urllib2 and httplib2 with various proxy settings + ''' + + import sys + myproxy = dict() + + class fake: pass + fake_proxy_settings = fake() + fake_proxy_settings.get_proxy_dict = lambda: myproxy + + sys.modules['util.proxy_settings'] = fake_proxy_settings + + import util.net as net + import util.httplib2 as httplib2 + + test_urls = ( + ('http ', 'http://www.google.com'), + ('https', 'https://mail.google.com'), + ) + + import urllib2 + + test_openers = ( + ('urllib2 ', urllib2.urlopen, lambda response: response.code), + ('httplib2', lambda url: httplib2.Http().request(url), lambda response: response[0].status), + ) + + for pname, proxy in test_proxies: + myproxy.clear() + myproxy.update(proxy) + + for uname, url in test_urls: + for oname, opener, get_status in test_openers: + print ('%s with proxy = %r with url=%r' % (oname, pname, uname)), + try: + result = opener(url) + status = get_status(result) + if not net.httpok(status): + raise Exception(status) + except Exception, e: + print '...fail (%r)' % e + #traceback.print_exc() + + else: + print '...success' + + +def testProxiesSockets(test_proxies): + ''' + Tests socksockets with varios proxy settings. + ''' + import netextensions + import logextensions + import digsbysite + + import sys, socket, time + myproxy = dict() + + + class fake: pass + fake_proxy_settings = fake() + fake_proxy_settings.get_proxy_dict = lambda: myproxy + + sys.modules['util.proxy_settings'] = fake_proxy_settings + + import threading + + import util + myip = util.get_ips_s()[0] + TEST_ADDR = (myip, 443) + class TestSocketServer(threading.Thread): + def run(self): + self.__stop = False + + import socket + server = socket.socket() + server.bind(TEST_ADDR) + server.listen(1) + server.settimeout(2) + while not self.__stop: + try: + client, addr = server.accept() + except socket.timeout: + continue + else: + try: + print client.recv(1024) + client.close() + except Exception, e: + print '...fail (%r)' % e + server.close() + + def join(self): + self.__stop = True + threading.Thread.join(self) + + socket_thread = TestSocketServer() + socket_thread.start() + import socks + try: + for pname, proxy in test_proxies: + time.sleep(.1) + myproxy.clear() + myproxy.update(proxy) + + print ('socket with proxy = %r' % pname), + try: + testsck = socks.socksocket() + testsck.setproxy(**net.GetProxyInfo()) + testsck.settimeout(1) + testsck.connect(TEST_ADDR) + testsck.sendall('...success') + testsck.close() + except Exception, e: + print '...fail: (%r)' % e +# traceback.print_exc() + + finally: + socket_thread.join() + + +def testProxiesAsync(test_proxies): + ''' + Tests async sockets with various proxy settings + ''' + + import sys + print >>sys.stderr, "AsyncSocket proxy tests are incomplete and currently non-working" + return + + myproxy = dict() + + class fake: pass + fake_proxy_settings = fake() + fake_proxy_settings.get_proxy_dict = lambda: myproxy + + sys.modules['util.proxy_settings'] = fake_proxy_settings + + import common + + class TestClientSocket(common.socket): + def __init__(self, on_done): + self.when_done = on_done + common.socket.__init__(self) + def handle_close(self): + self.when_done() + self.close() + time.sleep(.1) + + def collect_incoming_data(self, data): + common.socket.collect_incoming_data(self, data) + + def handle_connect(self): + self.socket.sendall('...success\n') + self.handle_close() + + class TestServerHandler(common.socket): + def handle_close(self): + if not getattr(self, '_got_term', None): + print '...fail' + self.close() + time.sleep(.1) + + def found_terminator(self): + self._got_term = True + if self.data: + print self.data.strip() + self.data = '' + self.handle_close() + + class TestServerSocket(common.socket): + def handle_accept(self): + conn, addr = self.accept() + x = TestServerHandler(conn) + x.set_terminator('\n') + + TEST_ADDR = ('192.168.1.101', 443) + + server = TestServerSocket() + server.bind(TEST_ADDR) + server.listen(2) + test_proxies = list(test_proxies) + global client + client = None + + def test_one_proxy(): + global client + if not test_proxies: + print 'done' + client.close() + server.close() + import AsyncoreThread; AsyncoreThread.end_thread() + return + + pname, proxy = test_proxies.pop(0) + myproxy.clear() + myproxy.update(proxy) + + if client is not None: + client.close() + + print ('asyncsocket with proxy = %r' % pname), + client = TestClientSocket(on_done = test_one_proxy) + client.connect(TEST_ADDR) + + test_one_proxy() + time.sleep(.1) + + +def main(): + import sys + sys.modules['__builtin__']._ = lambda s: s + testLinkify(net.linkify) + testSpacify() + test_proxies = ( + ('no proxy ', dict(override='NOPROX', username=None, proxytype=None, addr=None, password=None, port=None)), + #('http squid', dict(username='', proxytype='HTTP', addr='192.168.1.50', override='SETPROX', password='', port='8080')), + ('my http ', dict(username='', proxytype='HTTP', addr='192.168.1.103', loverride='SETPROX', password='', port='8080')), + ('my socks4 ', dict(username='', proxytype='SOCKS4', addr='192.168.1.103', override='SETPROX', password='', port='1080')), + ('my socks5 ', dict(username='', proxytype='SOCKS5', addr='192.168.1.103', override='SETPROX', password='', port='1080')), + ('pw http ', dict(username='digsby', proxytype='HTTP', addr='192.168.1.103', override='SETPROX', password='password', port='8080')), + ('pw socks4 ', dict(username='digsby', proxytype='SOCKS4', addr='192.168.1.103', override='SETPROX', password='password', port='1080')), + ('pw socks5 ', dict(username='digsby', proxytype='SOCKS5', addr='192.168.1.103', override='SETPROX', password='password', port='1080')), + ) + + testProxiesURLs(test_proxies) + testProxiesSockets(test_proxies) + testProxiesAsync(test_proxies) # Doesnt work yet! + +if __name__ == '__main__': + import digsbysite + import traceback +# import logging +# logging.root.setLevel(1) +# logging.basicConfig() + main() diff --git a/digsby/src/tests/testramstats.py b/digsby/src/tests/testramstats.py new file mode 100644 index 0000000..4b5afc2 --- /dev/null +++ b/digsby/src/tests/testramstats.py @@ -0,0 +1,28 @@ +''' + if False and sys.TRACK_RAM_STATS: + def log_ram_stats(**k): + from sysinfo_hack import memory_info + from path import path + from operator import itemgetter + import time + + d = path(r'c:\ramstats') + if not d.isdir(): + d.makedirs() + + ramstats = sorted(sys.ramstats, key=itemgetter(2)) + + stats_file = d / 'ram_%s.csv' % time.time() + with open(stats_file, 'w') as f: + write = f.write + + write('name,time,delta,wss\n') + for entry in ramstats: + write(','.join(str(s) for s in entry) + '\n') + + import gc + gc.enable() + + self.BuddyListShown.append(lambda **k: setattr(self, 'timer', wx.CallLater(2000, log_ram_stats)))#lambda: setattr(self, 'timer', wx.CallLater(2000, log_ram_stats)) + +''' diff --git a/digsby/src/tests/testrijndael.py b/digsby/src/tests/testrijndael.py new file mode 100644 index 0000000..64c2eda --- /dev/null +++ b/digsby/src/tests/testrijndael.py @@ -0,0 +1,60 @@ +from __future__ import division +import digsbysite +import sys +import util +import util.threads.threadpool as threadpool +import util.primitives.bits as bits + +from tlslite.utils.rijndael.py_rijndael import encrypt as py_encrypt, decrypt as py_decrypt, rijndael as py_rijndael +from c_rijndael import encrypt as c_encrypt, decrypt as c_decrypt, rijndael as c_rijndael + +TRIALS = 10000 + +print '0', '-'*46, '50', '-'*46, '100' + +def try_once(i): + key = bits.getrandbytes(16) + bytes = bits.getrandbytes(16) + py_e = py_encrypt(key, bytes) + c_e = c_encrypt(key, bytes) + + assert py_e == c_e + + py_d = py_decrypt(key, py_e) + c_d = c_decrypt(key, c_e) + py_d_c = py_decrypt(key, c_e) + c_d_py = c_decrypt(key, py_e) + + assert py_d_c == bytes + assert py_d == bytes + assert c_d == bytes + assert c_d_py == bytes + + if ((i*100)/TRIALS) == ((i*100)//TRIALS): + sys.stdout.write('|') + +threadpool.ThreadPool(15) + +#print '\nthreaded' +#for i in range(TRIALS): +# util.threaded(try_once)(i) +# +#print 'sequential' +#for i in range(TRIALS): +# try_once(i) + +key = bits.getrandbytes(16) +c_crypter = c_rijndael(key) +py_crypter = py_rijndael(key) + +py_encrypt = lambda _key, x: py_crypter.encrypt(x) +py_decrypt = lambda _key, x: py_crypter.decrypt(x) +c_encrypt = lambda _key, x: c_crypter.encrypt(x) +c_decrypt = lambda _key, x: c_crypter.decrypt(x) + +print '\nsequential stateful' +for i in range(TRIALS): + try_once(i) + +#import time +#time.sleep(TRIALS//1000 * 5) diff --git a/digsby/src/tests/testsafeeval.py b/digsby/src/tests/testsafeeval.py new file mode 100644 index 0000000..7112419 --- /dev/null +++ b/digsby/src/tests/testsafeeval.py @@ -0,0 +1,71 @@ +from util.primitives.safeeval import safe_eval, SafeEvalException, unallowed_ast_nodes +import unittest +import time + +should_raise = [ +'import sys', +'__import__("sys")', +"(lambda: None).func_globals['__builtins__'].quit()", +] + +class TestSafeEval(unittest.TestCase): + def test_should_raise(self): + for expr in should_raise: + self.assertRaises(SafeEvalException, + safe_eval, expr) + + def test_builtin(self): + # attempt to access a unsafe builtin + self.assertRaises(SafeEvalException, + safe_eval, "open('test.txt', 'w')") + + def test_getattr(self): + # attempt to get arround direct attr access + self.assertRaises(SafeEvalException, \ + safe_eval, "getattr(int, '__abs__')") + + def test_func_globals(self): + # attempt to access global enviroment where fun was defined + self.assertRaises(SafeEvalException, \ + safe_eval, "def x(): pass; print x.func_globals") + + def test_lowlevel(self): + # lowlevel tricks to access 'object' + self.assertRaises(SafeEvalException, \ + safe_eval, "().__class__.mro()[1].__subclasses__()") + + def test_timeout_ok(self): + if 'CallFunc' in unallowed_ast_nodes: + return # disabled since function calls are disabled + + # attempt to exectute 'slow' code which finishes within timelimit + def test(): time.sleep(2) + env = {'test':test} + safe_eval("test()", env, timeout_secs = 5) + + def test_timeout_exceed(self): + # attempt to exectute code which never teminates + self.assertRaises(SafeEvalException, \ + safe_eval, "while 1: pass") + + def test_invalid_context(self): + # can't pass an enviroment with modules or builtins + import __builtin__ + env = {'f' : __builtin__.open, 'g' : time} + self.assertRaises(SafeEvalException, \ + safe_eval, "print 1", env) + + def test_callback(self): + if 'CallFunc' in unallowed_ast_nodes: + return # disabled since function calls are disabled + + # modify local variable via callback + self.value = 0 + def test(): self.value = 1 + env = {'test':test} + safe_eval("test()", env) + self.assertEqual(self.value, 1) + +if __name__ == "__main__": + unittest.main() + diff --git a/digsby/src/tests/testservicedialog.py b/digsby/src/tests/testservicedialog.py new file mode 100644 index 0000000..1f48b76 --- /dev/null +++ b/digsby/src/tests/testservicedialog.py @@ -0,0 +1,22 @@ + +if __name__ == '__main__': + import os, sys + import tests.testapp as TA + app = TA.testapp() + os.chdir(sys.path[0]) + import protocols + +def _main(): + import wx + import hooks + import services.service_provider as SP + sps = [p.provider_id for p in wx.GetApp().plugins if p.info.type == 'service_provider'] + msp = SP.get_meta_service_provider('pop') + diag = hooks.first("digsby.services.create", msp) + diag.Show() + diag.Bind(wx.EVT_CLOSE, lambda e: (e.Skip(), app.ExitMainLoop())) + app.SetTopWindow(diag) + app.MainLoop() + +if __name__ == '__main__': + _main() diff --git a/digsby/src/tests/testspellcheck.py b/digsby/src/tests/testspellcheck.py new file mode 100644 index 0000000..a01eda4 --- /dev/null +++ b/digsby/src/tests/testspellcheck.py @@ -0,0 +1,85 @@ +from tests.testframe import TestFrame +import gui.native.helpers +import wx + + + +if __name__ == '__main__': + + from tests.testapp import testapp + app = testapp('../..', skinname = 'silverblue') + +from spelling import SpellCheckTextCtrlMixin + +class SpellCheckTextCtrl(wx.TextCtrl,SpellCheckTextCtrlMixin): + def __init__(self,parent): + + long = ''' +we should just mirror the FTP site. use the sig files to check for up-to-date-ness. + +we should not mess with the directories. we should just be unpacking the files on top of the aspell folder (or users aspell folder). + +we should use the 0index.html file (linked above) to generate the list of choices of files. note that the table entries have links, ascii names, and unicode names, as well as the 2 letter codes that aspell needs to locate them. + +we should use our file transfer window to show the progress, and provide cancel-ability. +''' + + empty = '' + + sample = empty + + + + wx.TextCtrl.__init__(self, parent,-1, style= wx.TE_RICH2 | wx.TE_MULTILINE| wx.TE_CHARWRAP | wx.NO_BORDER | wx.WANTS_CHARS | wx.TE_NOHIDESEL) + SpellCheckTextCtrlMixin.__init__(self) + + + +input = None +class SpellCheckTest(wx.Panel): + def __init__(self,parent = None): + + wx.Panel.__init__(self,parent or TestFrame("Test - Spellcheck")) + + s = self.Sizer = wx.BoxSizer(wx.VERTICAL) + + global input + input = self.input = SpellCheckTextCtrl(self) + +# output = self.output = wx.TextCtrl(self,-1,style= wx.TE_RICH2 | wx.TE_MULTILINE | wx.TE_CHARWRAP | wx.WANTS_CHARS | wx.TE_NOHIDESEL | wx.TE_READONLY) +# output.MinSize = (output.MinSize.width, 60) + s.Add(input , 1, wx.EXPAND) +# s.Add(output, 0, wx.EXPAND) + + self.Layout() + + if isinstance(self.Parent,TestFrame): + wx.CallAfter(self.ConfigureTestFrame,self.Parent) + + + + def ConfigureTestFrame(self, testframe): + + s = testframe.Sizer = wx.BoxSizer(wx.VERTICAL) + + s.Add(self,1,wx.EXPAND) + + testframe.Show(True) + + testframe.CreateStatusBar(2) + testframe.SetStatusWidths([100,-1]) + + + +if __name__ == '__main__': + sct = SpellCheckTest() + app.MainLoop() + + + + + + + + + diff --git a/digsby/src/tests/teststripformatting.py b/digsby/src/tests/teststripformatting.py new file mode 100644 index 0000000..15d399e --- /dev/null +++ b/digsby/src/tests/teststripformatting.py @@ -0,0 +1,62 @@ +from __future__ import with_statement + +if __name__ == '__main__': + import gettext; gettext.install('Digsby') + +from gui.imwin.styles.stripformatting import * + + +def printline(): print '*'*80 + +if __name__ == '__main__2': + + #print remove_style('background: blue;', 'background') + + + s = soupify('test') + + print convert_back(s) + + #convert_back(s) + #print s.prettify() + + +if __name__ == '__main__3': + + with file('formattedhtml.html', 'r') as f: + t = f.read() + + def dorun(t, msg, formatting, colors): + print 'stripping', msg + printline() + print strip(t, formatting = formatting, colors = colors) + + dorun(t, 'everything', formatting = True, colors = True) + dorun(t, 'formatting', formatting = True, colors = False) + dorun(t, 'colors', formatting = False, colors = True) + +if __name__ == '__main__4': + t = 'ok ' + + #print t + #print soupify(t) + import sys + + sys.stdout.flush() + t = strip(t, formatting = True, colors = False, emoticons = False) + + for x in xrange(5): sys.stderr.flush() + + print + print + print t + +if __name__ == '__main__': + text = 'http://www.amazon.com/gp/offer-listing/B0009VXBAQ/ref=dp_olp_1?ie=UTF8&qid=1210776182&sr=1-1' + soup = soupify(text) + print soup +# removed = defaultdict(list) +# print strip_formatting(soup, removed) +# print removed +# print soup + diff --git a/digsby/src/tests/testthreads.py b/digsby/src/tests/testthreads.py new file mode 100644 index 0000000..14286e5 --- /dev/null +++ b/digsby/src/tests/testthreads.py @@ -0,0 +1,28 @@ +import time +from random import randint + +def main(): + + from threading import Thread, currentThread + + def foo(): + for i in xrange(1000): + time.sleep(randint(100,1000) / 1000) + t = currentThread() + t._mycount += 1 + print t.getName(), t._mycount + + threads = [] + + for x in xrange(5): + t = Thread(target = foo) + t._mycount = 0 + threads.append(t) + t.start() + + for t in threads: + t.join() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/testutil/__init__.py b/digsby/src/tests/testutil/__init__.py new file mode 100644 index 0000000..500200e --- /dev/null +++ b/digsby/src/tests/testutil/__init__.py @@ -0,0 +1,2 @@ +from .findleaks import gc_enabled + diff --git a/digsby/src/tests/testutil/findleaks.py b/digsby/src/tests/testutil/findleaks.py new file mode 100644 index 0000000..8522cbb --- /dev/null +++ b/digsby/src/tests/testutil/findleaks.py @@ -0,0 +1,48 @@ +import gc +import sys +import weakref + +def _enable_gc(val): + if val: gc.enable() + else: gc.disable() + +class gc_enabled(object): + ''' + a context manager that temporarily enables or disables garbage collection + ''' + + def __init__(self, enabled=True): + self.enabled = enabled + + def __enter__(self): + self.originally_enabled = gc.isenabled() + _enable_gc(self.enabled) + + def __exit__(self, *a): + _enable_gc(self.originally_enabled) + +def check_collected(func): + ''' + Decorates func, and checks that the return value of func is collected. + ''' + + obj = func() + assert obj is not None, "function given to check_collected must return a value" + + if isinstance(obj, tuple): + # don't use [a for a ...] syntax here, since it leaks a "magic" local + # like _[1] and the last item in the list won't be collected + weakobjs = list(weakref.ref(o) for o in obj) + else: + weakobjs = [weakref.ref(obj)] + + del obj + + gc.collect() + + for weakobj in weakobjs: + if weakobj() is not None: + refs = '\n'.join(' %r' % r for r in gc.get_referrers(weakobj())) + raise AssertionError('In function %r, %s has %d references:\n%s' % + (func.__name__, repr(weakobj()), sys.getrefcount(weakobj()), refs)) + diff --git a/digsby/src/tests/testutil/testfsm.py b/digsby/src/tests/testutil/testfsm.py new file mode 100644 index 0000000..c4c19eb --- /dev/null +++ b/digsby/src/tests/testutil/testfsm.py @@ -0,0 +1,133 @@ +from util.fsm import StateMachine + +if __name__ == "__main__": + machine = StateMachine('status', ["off", "fleft", "left", "bleft", + "fright", "right", "bright"], "left") + + machine.create_trans("left", "fleft", "otherleft") + + print machine.process("otherleft") + +#>>> states = ["off", "fleft", "left", "bleft", "fright", "right", "bright"] +#>>> ops = ["buddy_icon_disabled", "buddy_icon_enabled"] +#>>> ops2 = ["enabled", "disabled", "off", "fleft", "left", "bleft", "fright", "right", "bright"] +#>>> ["status_" + op for op in ops2] +#['status_enabled', 'status_disabled', 'status_off', 'status_fleft', 'status_left', 'status_bleft', 'status_fright', 'status_right', 'status_bright'] +#>>> ops3 = ["status_" + op for op in ops2] +#>>> for state in states: +#... for op in ops + ops3: +#... print "machine.create_transition('%s', '%s', None)" % (state, op) + + import sys + sys.exit(0) + + """ + below is what I believe to be a complete state machine definition for + a status or service icon, neglecting the off states. + so far, my idea for implementation is incomplete, it is lacking + the interaction between state machines necessary for the whole thing to + work. One can see, however, that the number of needed transitions is + much smaller than the possible ones. + """ + + machine = None + to_state = None + + 'buddy_icon_disabled' + 'buddy_icon_left' + 'buddy_icon_right' + + states = ['fleft', 'left', 'bleft_l', 'bright_l', 'bleft_r', 'bright_r', 'right', 'fright'] + + #definition of simple transitions (someone else wants my spot) + + #I'm in the far left, other bumps me + machine.create_trans('fleft', 'left', 'other_fleft') + + #I'm in the left, other bumps me + machine.create_trans('left', 'fleft', 'other_left') + + #buddy icon is on the left + #badge on left + machine.create_trans('bleft_l', 'bright_l', 'other_bleft_l') + #badge on right + machine.create_trans('bright_l', 'bleft_l', 'other_bright_l') + + #buddy icon is on the right + #badge on left + machine.create_trans('bleft_r', 'bright_r', 'other_bleft_r') + #badge on right + machine.create_trans('bright_r', 'bleft_r', 'other_bright_r') + + #I'm in the far right, other bumps me + machine.create_trans('fright', 'right', 'other_fright') + + #I'm in the right, other bumps me + machine.create_trans('right', 'fright', 'other_right') + + #definition of buddy icon translation + #badge on left + machine.create_trans('bleft_l', 'bleft_r', 'buddy_icon_right') + #badge on right + machine.create_trans('bright_l', 'bright_r', 'buddy_icon_right') + #badge on left + machine.create_trans('bleft_r', 'bleft_l', 'buddy_icon_left') + #badge on right + machine.create_trans('bright_r', 'bright_l', 'buddy_icon_left') + + + #these are the hard ones + #buddy icon disabled. where to go + #ok, the definition is easy, the trouble is, you have to tell them in the + #correct order. If you do, the state machines do the heavy lifting for you + #else, you get the wrong result + machine.create_trans('bleft_l', 'left', 'buddy_icon_disabled') + machine.create_trans('bright_l', 'left', 'buddy_icon_disabled') + machine.create_trans('bleft_r', 'right', 'buddy_icon_disabled') + machine.create_trans('bright_r', 'right', 'buddy_icon_disabled') + + #example + states1 = ['off', 'fleft', 'left', 'bleft_l', 'bright_l', + 'bleft_r', 'bright_r', 'right', 'fright'] + states2 = ['off', 'left', 'right'] + manager = StateManager() + status_machine = StateMachine("status", states1, "off") + service_machine = StateMachine("service", states1, "off") + buddy_icon_machine = StateMachine("buddy_icon", states2, "off") + manager.add_machine(status_machine) + manager.add_machine(service_machine) + manager.add_machine(buddy_icon_machine) + + status_machine.create_trans('fleft', 'left', 'service_fleft') + status_machine.create_trans('left', 'fleft', 'service_left') + status_machine.create_trans('bleft_l', 'bright_l', 'service_bleft_l') + status_machine.create_trans('bright_l', 'bleft_l', 'service_bright_l') + status_machine.create_trans('bleft_r', 'bright_r', 'service_bleft_r') + status_machine.create_trans('bright_r', 'bleft_r', 'service_bright_r') + status_machine.create_trans('fright', 'right', 'service_fright') + status_machine.create_trans('right', 'fright', 'service_right') + status_machine.create_trans('bleft_l', 'bleft_r', 'buddy_icon_right') + status_machine.create_trans('bright_l', 'bright_r', 'buddy_icon_right') + status_machine.create_trans('bleft_r', 'bleft_l', 'buddy_icon_left') + status_machine.create_trans('bright_r', 'bright_l', 'buddy_icon_left') + status_machine.create_trans('bleft_l', 'left', 'buddy_icon_off') + status_machine.create_trans('bright_l', 'left', 'buddy_icon_off') + status_machine.create_trans('bleft_r', 'right', 'buddy_icon_off') + status_machine.create_trans('bright_r', 'right', 'buddy_icon_off') + + service_machine.create_trans('fleft', 'left', 'status_fleft') + service_machine.create_trans('left', 'fleft', 'status_left') + service_machine.create_trans('bleft_l', 'bright_l', 'status_bleft_l') + service_machine.create_trans('bright_l', 'bleft_l', 'status_bright_l') + service_machine.create_trans('bleft_r', 'bright_r', 'status_bleft_r') + service_machine.create_trans('bright_r', 'bleft_r', 'status_bright_r') + service_machine.create_trans('fright', 'right', 'status_fright') + service_machine.create_trans('right', 'fright', 'status_right') + service_machine.create_trans('bleft_l', 'bleft_r', 'buddy_icon_right') + service_machine.create_trans('bright_l', 'bright_r', 'buddy_icon_right') + service_machine.create_trans('bleft_r', 'bleft_l', 'buddy_icon_left') + service_machine.create_trans('bright_r', 'bright_l', 'buddy_icon_left') + service_machine.create_trans('bleft_l', 'left', 'buddy_icon_off') + service_machine.create_trans('bright_l', 'left', 'buddy_icon_off') + service_machine.create_trans('bleft_r', 'right', 'buddy_icon_off') + service_machine.create_trans('bright_r', 'right', 'buddy_icon_off') diff --git a/digsby/src/tests/testutil/testhttptools.py b/digsby/src/tests/testutil/testhttptools.py new file mode 100644 index 0000000..5a45246 --- /dev/null +++ b/digsby/src/tests/testutil/testhttptools.py @@ -0,0 +1,26 @@ +import urllib2 +import util +import util.httptools as httptools +import util.threads.threadpool as threadpool +import digsbysite, logextensions, netextensions +import tests.testapp as testapp + +if __name__ == '__main__': + _app = testapp.testapp() + def success(resp): + print 'success:', repr(resp.read()) + + def error(err = None): + print 'error:', None + + + req = urllib2.Request('http://www.google.com') + opener = util.net.build_opener() + + ro = httptools.RequestOpener(util.threaded(opener.open), req) + ro.open(success = success, error = error) + + tp = threadpool.ThreadPool(2) + _app.toggle_crust() + _app.MainLoop() + tp.joinAll() diff --git a/digsby/src/tests/testvars.py b/digsby/src/tests/testvars.py new file mode 100644 index 0000000..20da1ed --- /dev/null +++ b/digsby/src/tests/testvars.py @@ -0,0 +1,48 @@ +class Model1(object): + def __init__( self, root = None, expanded = None): + self.root = root or [] + self.expanded = expanded or [] + self.flattened_list = [] + self.listeners = [] + self.depths = {} + self.filters = [] + self.donotexpand = [] + +class Model2(object): + def __init__( self, root = None, expanded = None): + vars(self).update(root = root or [], + expanded = expanded or [], + flattened_list = [], + listeners = [], + depths = {}, + filters = [], + donotexpand = []) + +class Model3(object): + def __init__( self, root = None, expanded = None): + self.__dict__.update(root = root or [], + expanded = expanded or [], + flattened_list = [], + listeners = [], + depths = {}, + filters = [], + donotexpand = []) + +class Model4(object): + def __init__( self, root = None, expanded = None): + for key, val in [('root', root or []), + ('expanded', expanded or []), + ('flattened_list', []), + ('listeners', []), + ('depths', {}), + ('filters', []), + ('donotexpand', [])]: + setattr(self, key, val) + +if __name__ == '__main__': + from timeit import Timer + + print 'Model1', Timer('Model1()', 'from __main__ import Model1').timeit() + print 'Model2', Timer('Model2()', 'from __main__ import Model2').timeit() + print 'Model3', Timer('Model3()', 'from __main__ import Model3').timeit() + print 'Model4', Timer('Model4()', 'from __main__ import Model4').timeit() diff --git a/digsby/src/tests/testvec.py b/digsby/src/tests/testvec.py new file mode 100644 index 0000000..acfabde --- /dev/null +++ b/digsby/src/tests/testvec.py @@ -0,0 +1,45 @@ +from util import vector + +if __name__ == '__main__': + v = vector + assert v(1,2) + v(2,3) == v(3,5) + assert v(5,4) - v(3,2) == v(2,2) + assert -v(3,3) == v(-3,-3) + assert v(5,0).length == 5 + assert v(0,4).length == 4 + assert (v(3,0) - v(0,4)).length == 5 + assert v(4,0).to(v(0,3)) == 5 + assert v(3,4) * 2 == v(6,8) + assert v(6,6).div(2) == v(3,3) + assert v(0,5).normal == v(0,1) + assert(v(1,1).angle == 45.0) + + vs = [] + + import wx + a = wx.PySimpleApp() + f = wx.Frame(None) + + origin = v(400, 400) + + def paint(e): + dc = wx.PaintDC(f) + dc.Font = default_font() + for v in vs: + dc.DrawLine(*(tuple(origin) + tuple(v))) + dc.DrawText('%s: %s' % ((v-origin), (v-origin).angle), *tuple(v)) + + def click(e): + p = v(e.Position) + vs.append(p) + f.Refresh() + + + f.Bind(wx.EVT_PAINT, paint) + f.Bind(wx.EVT_LEFT_DOWN, click) + + f.Show() + a.MainLoop() + + + diff --git a/digsby/src/tests/twitter/runtwitter.py b/digsby/src/tests/twitter/runtwitter.py new file mode 100644 index 0000000..9ae6d94 --- /dev/null +++ b/digsby/src/tests/twitter/runtwitter.py @@ -0,0 +1,176 @@ +from __future__ import print_function +import wx +import sys + +class MockTwitterAccount(object): + def __init__(self, protocol): + from twitter.twitter import TwitterAccount + self.protocol = TwitterAccount.protocol + self.twitter_protocol = protocol + self.twitter_protocol.account = self + self.username = protocol.username + self._dirty = True + + +# TODO: protocols.declare_adapter_for_type + +twitter = None +def main(): + def on_close(e): + twitter.disconnect() + + import AsyncoreThread + AsyncoreThread.join() + + f.Destroy() + + def droptables(): + if wx.YES == wx.MessageBox( + 'Are you sure you want to drop all tables?', + style = wx.YES_NO, parent = f): + twitter.clear_cache() + + def build_test_frame(): + f = wx.Frame(None, title='Twitter Test') + f.SetSize((500, 700)) + f.Bind(wx.EVT_CLOSE, on_close) + + buttons = [] + def button(title, callback): + b = wx.Button(f, -1, title) + b.Bind(wx.EVT_BUTTON, lambda e: callback()) + buttons.append(b) + + def infobox(): + from gui.toolbox import Monitor + from gui.infobox.infobox import DEFAULT_INFOBOX_WIDTH + from gui.infobox.infoboxapp import init_host, set_hosted_content + + f = wx.Frame(None) + size = (DEFAULT_INFOBOX_WIDTH, Monitor.GetFromWindow(f).ClientArea.height * .75) + f.SetClientSize(size) + + w = wx.webview.WebView(f) + + init_host(w) + set_hosted_content(w, MockTwitterAccount(twitter)) + f.Show() + + def popup(): + twitter.webkitcontroller.evaljs('account.showTimelinePopup();') + + def fake_tweets(): + j('fakeTweets(%d);' % int(fake_tweets_txt.Value)) + twitter.webkitcontroller.webview.GarbageCollect() + + button('Open Window', twitter.open_timeline_window) + button('Update', twitter.update) + button('Infobox', infobox) + button('Drop Tables', droptables) + button('Popup', popup) + button('Fake Tweets', fake_tweets) + + s = f.Sizer = wx.BoxSizer(wx.HORIZONTAL) + + v = wx.BoxSizer(wx.VERTICAL) + v.AddMany(buttons) + + fake_tweets_txt = wx.TextCtrl(f, -1, '1000') + v.Add(fake_tweets_txt) + + s.Add(v, 0, wx.EXPAND) + + v2 = wx.BoxSizer(wx.VERTICAL) + stxt = wx.StaticText(f) + v2.Add(stxt, 0, wx.EXPAND) + + from pprint import pformat + from common.commandline import wkstats + def update_text(): + debug_txt = '\n\n'.join([ + pformat(wkstats()), + j('debugCounts()') + ]) + + stxt.Label = debug_txt + f.Sizer.Layout() + + f._timer = wx.PyTimer(update_text) + f._timer.StartRepeating(1000) + f.SetBackgroundColour(wx.WHITE) + + s.Add((50, 50)) + s.Add(v2, 0, wx.EXPAND) + + return f + + from tests.testapp import testapp + + username, password = 'ninjadigsby', 'no passwords' + if len(sys.argv) > 2: + username, password = sys.argv[1:3] + + app = testapp(skinname='Windows 7', plugins=True) + + global twitter # on console + from twitter.twitter import TwitterProtocol + twitter = TwitterProtocol(username, password) + twitter.events.state_changed += lambda state: print('state changed:', state) + twitter.events.reply += lambda screen_name: print('reply:', screen_name) + + import common.protocolmeta + account_opts = common.protocolmeta.protocols['twitter']['defaults'].copy() + + if '--twitter-offline' in sys.argv: + account_opts['offlineMode'] = True + + #import simplejson + #account_opts['feeds'] = simplejson.loads('[{"type": "timeline", "name": "timeline"}, {"type": "mentions", "name": "mentions"}, {"type": "directs", "name": "directs"}, {"name": "group:1", "popups": false, "ids": [14337372, 9499402, 8517312, 7341872, 32218792, 9853162], "filter": false, "groupName": ".syntax", "type": "group"}, {"name": "search:1", "title": "foramilliondollars", "popups": false, "merge": false, "query": "#foramilliondollars", "save": true, "type": "search"}]') + + twitter.connect(account_opts) + + # mock an accountmanager/social networks list for the global status dialog + from util.observe import ObservableList, Observable + class TwitterAccount(Observable): + service = protocol = 'twitter' + enabled = True + ONLINE = True + def __init__(self, connection): + Observable.__init__(self) + self.display_name = self.name = connection.username + self.connection = connection + self.connection.account = self + + import digsbyprofile + from util import Storage as S + + acctmgr = digsbyprofile.profile.account_manager = S( + socialaccounts = ObservableList([TwitterAccount(twitter)]) + ) + digsbyprofile.profile.socialaccounts = acctmgr.socialaccounts + + global j + j = twitter.webkitcontroller.evaljs + + def _html(): + txt = twitter.webkitcontroller.FeedWindow.PageSource.encode('utf-8') + from path import path + p = path(r'c:\twitter.html') + p.write_bytes(txt) + wx.LaunchDefaultBrowser(p.url()) + + global html + html = _html + + f = build_test_frame() + f.Show() + + if '--drop' in sys.argv: + twitter.clear_cache() + + wx.CallLater(500, twitter.open_timeline_window) + + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/twitter/testemailusername.py b/digsby/src/tests/twitter/testemailusername.py new file mode 100644 index 0000000..b0c8280 --- /dev/null +++ b/digsby/src/tests/twitter/testemailusername.py @@ -0,0 +1,42 @@ +html = ''' + + + + + + + + + + + + +''' + +def main(): + from tests.testapp import testapp + app = testapp() + + from path import path + import os + os.chdir(path(__file__).parent) + + import wx.webview + + + f = wx.Frame(None) + w = wx.webview.WebView(f) + + from gui.browser.webkit import setup_webview_logging + setup_webview_logging(w, 'webview') + + w.SetPageSource(html) + + app.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/unittests/__init__.py b/digsby/src/tests/unittests/__init__.py new file mode 100644 index 0000000..a83d978 --- /dev/null +++ b/digsby/src/tests/unittests/__init__.py @@ -0,0 +1,43 @@ +import wx +import wx.lib.sized_controls as sc +import unittest + +import utiltests +import menutests + +class UnitTestDialog(sc.SizedDialog): + def __init__(self, *args, **kwargs): + sc.SizedDialog.__init__(self, *args, **kwargs) + + self.pane = self.GetContentsPane() + wx.StaticText(self.pane, -1, "Test Runner Console") + + self.outputText = wx.TextCtrl(self.pane, -1, style=wx.TE_MULTILINE) + self.outputText.SetSizerProps(expand=True, proportion=1) + + self.testButton = wx.Button(self.pane, -1, "Run Tests") + + self.Bind(wx.EVT_BUTTON, self.run_tests, self.testButton) + + def run_tests(self, event): + results = unittest.TestResult() + suite = unittest.TestLoader().loadTestsFromTestCase(utiltests.UtilTestingSuite) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(menutests.MenubarTestingSuite)) + + suite.run(results) + + self.outputText.WriteText("%d tests run\n\n" % results.testsRun) + + errors = len(results.errors) + failures = len(results.failures) + + if errors > 0 or failures > 0: + for failure in results.failures: + self.outputText.WriteText("Failure in %r\n%s" % (failure[0], failure[1])) + + for error in results.errors: + self.outputText.WriteText("Error in %r\n%s" % (error[0], error[1])) + + + else: + self.outputText.WriteText("All tests passed!") diff --git a/digsby/src/tests/unittests/discover.py b/digsby/src/tests/unittests/discover.py new file mode 100644 index 0000000..133f037 --- /dev/null +++ b/digsby/src/tests/unittests/discover.py @@ -0,0 +1,269 @@ +# Copyright Michael Foord 2009 +# Licensed under the BSD License +# See: http://pypi.python.org/pypi/discover + +import os +import sys +import optparse +import types +import unittest + +from fnmatch import fnmatch + +if hasattr(types, 'ClassType'): + # for Python 3.0 compatibility + class_types = (types.ClassType, type) +else: + class_types = type + + +__version__ = '0.2.0' + + +class DiscoveringTestLoader(unittest.TestLoader): + """ + This class is responsible for loading tests according to various criteria + and returning them wrapped in a TestSuite + """ + _top_level_dir = None + + def loadTestsFromModule(self, module, use_load_tests=True): + """Return a suite of all tests cases contained in the given module""" + tests = [] + for name in dir(module): + obj = getattr(module, name) + if isinstance(obj, type) and issubclass(obj, unittest.TestCase): + tests.append(self.loadTestsFromTestCase(obj)) + + load_tests = getattr(module, 'load_tests', None) + if use_load_tests and load_tests is not None: + return load_tests(self, tests, None) + return self.suiteClass(tests) + + + def discover(self, start_dir, pattern='test*.py', top_level_dir=None): + """Find and return all test modules from the specified start + directory, recursing into subdirectories to find them. Only test files + that match the pattern will be loaded. (Using shell style pattern + matching.) + + All test modules must be importable from the top level of the project. + If the start directory is not the top level directory then the top + level directory must be specified separately. + + If a test package name (directory with '__init__.py') matches the + pattern then the package will be checked for a 'load_tests' function. If + this exists then it will be called with loader, tests, pattern. + + If load_tests exists then discovery does *not* recurse into the package, + load_tests is responsible for loading all tests in the package. + + The pattern is deliberately not stored as a loader attribute so that + packages can continue discovery themselves. top_level_dir is stored so + load_tests does not need to pass this argument in to loader.discover(). + """ + if top_level_dir is None and self._top_level_dir is not None: + # make top_level_dir optional if called from load_tests in a package + top_level_dir = self._top_level_dir + elif top_level_dir is None: + top_level_dir = start_dir + + top_level_dir = os.path.abspath(os.path.normpath(top_level_dir)) + start_dir = os.path.abspath(os.path.normpath(start_dir)) + + if not top_level_dir in sys.path: + # all test modules must be importable from the top level directory + sys.path.append(top_level_dir) + self._top_level_dir = top_level_dir + + if start_dir != top_level_dir and not os.path.isfile(os.path.join(start_dir, '__init__.py')): + # what about __init__.pyc or pyo (etc) + raise ImportError('Start directory is not importable: %r' % start_dir) + + tests = list(self._find_tests(start_dir, pattern)) + return self.suiteClass(tests) + + + def _get_module_from_path(self, path): + """Load a module from a path relative to the top-level directory + of a project. Used by discovery.""" + path = os.path.splitext(os.path.normpath(path))[0] + + _relpath = os.path.relpath(path, self._top_level_dir) + assert not os.path.isabs(_relpath), "Path must be within the project" + assert not _relpath.startswith('..'), "Path must be within the project" + + name = _relpath.replace(os.path.sep, '.') + __import__(name) + return sys.modules[name] + + def _find_tests(self, start_dir, pattern): + """Used by discovery. Yields test suites it loads.""" + paths = os.listdir(start_dir) + + for path in paths: + full_path = os.path.join(start_dir, path) + # what about __init__.pyc or pyo (etc) + # we would need to avoid loading the same tests multiple times + # from '.py', '.pyc' *and* '.pyo' + if os.path.isfile(full_path) and path.lower().endswith('.py'): + if fnmatch(path, pattern): + # if the test file matches, load it + module = self._get_module_from_path(full_path) + yield self.loadTestsFromModule(module) + elif os.path.isdir(full_path): + if not os.path.isfile(os.path.join(full_path, '__init__.py')): + continue + + load_tests = None + tests = None + if fnmatch(path, pattern): + # only check load_tests if the package directory itself matches the filter + package = self._get_module_from_path(full_path) + load_tests = getattr(package, 'load_tests', None) + tests = self.loadTestsFromModule(package, use_load_tests=False) + + if load_tests is None: + if tests is not None: + # tests loaded from package file + yield tests + # recurse into the package + for test in self._find_tests(full_path, pattern): + yield test + else: + yield load_tests(self, tests, pattern) + +############################################## +# relpath implementation taken from Python 2.7 + +if not hasattr(os.path, 'relpath'): + if os.path is sys.modules.get('ntpath'): + def relpath(path, start=os.path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + start_list = os.path.abspath(start).split(os.path.sep) + path_list = os.path.abspath(path).split(os.path.sep) + if start_list[0].lower() != path_list[0].lower(): + unc_path, rest = os.path.splitunc(path) + unc_start, rest = os.path.splitunc(start) + if bool(unc_path) ^ bool(unc_start): + raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" + % (path, start)) + else: + raise ValueError("path is on drive %s, start on drive %s" + % (path_list[0], start_list[0])) + # Work out how much of the filepath is shared by start and path. + for i in range(min(len(start_list), len(path_list))): + if start_list[i].lower() != path_list[i].lower(): + break + else: + i += 1 + + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return os.path.curdir + return os.path.join(*rel_list) + + else: + # default to posixpath definition + def relpath(path, start=os.path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + start_list = os.path.abspath(start).split(os.path.sep) + path_list = os.path.abspath(path).split(os.path.sep) + + # Work out how much of the filepath is shared by start and path. + i = len(os.path.commonprefix([start_list, path_list])) + + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return os.path.curdir + return os.path.join(*rel_list) + os.path.relpath = relpath + +############################################# + + +USAGE = """\ +Usage: discover.py [options] + +Options: + -v, --verbose Verbose output + -s directory Directory to start discovery ('.' default) + -p pattern Pattern to match test files ('test*.py' default) + -t directory Top level directory of project (default to + start directory) + +For test discovery all test modules must be importable from the top +level directory of the project. +""" + +def _usage_exit(msg=None): + if msg: + print (msg) + print (USAGE) + sys.exit(2) + + +def _do_discovery(argv, verbosity, Loader): + # handle command line args for test discovery + parser = optparse.OptionParser() + parser.add_option('-v', '--verbose', dest='verbose', default=False, + help='Verbose output', action='store_true') + parser.add_option('-s', '--start-directory', dest='start', default='.', + help="Directory to start discovery ('.' default)") + parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', + help="Pattern to match tests ('test*.py' default)") + parser.add_option('-t', '--top-level-directory', dest='top', default=None, + help='Top level directory of project (defaults to start directory)') + + options, args = parser.parse_args(argv) + if len(args) > 3: + _usage_exit() + + for name, value in zip(('start', 'pattern', 'top'), args): + setattr(options, name, value) + + if options.verbose: + verbosity = 2 + + start_dir = options.start + pattern = options.pattern + top_level_dir = options.top + + loader = Loader() + return loader.discover(start_dir, pattern, top_level_dir), verbosity + + +def _run_tests(tests, testRunner, verbosity, exit): + if isinstance(testRunner, class_types): + try: + testRunner = testRunner(verbosity=verbosity) + except TypeError: + # didn't accept the verbosity argument + testRunner = testRunner() + result = testRunner.run(tests) + if exit: + sys.exit(not result.wasSuccessful()) + return result + + +def main(argv=None, testRunner=None, testLoader=None, exit=True, verbosity=1): + if testLoader is None: + testLoader = DiscoveringTestLoader + if testRunner is None: + testRunner = unittest.TextTestRunner + if argv is None: + argv = sys.argv[1:] + + tests, verbosity = _do_discovery(argv, verbosity, testLoader) + return _run_tests(tests, testRunner, verbosity, exit) + + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/unittests/javascript/JsTestDriver-1.2.2.jar b/digsby/src/tests/unittests/javascript/JsTestDriver-1.2.2.jar new file mode 100644 index 0000000..8541aab Binary files /dev/null and b/digsby/src/tests/unittests/javascript/JsTestDriver-1.2.2.jar differ diff --git a/digsby/src/tests/unittests/javascript/README.txt b/digsby/src/tests/unittests/javascript/README.txt new file mode 100644 index 0000000..55bab14 --- /dev/null +++ b/digsby/src/tests/unittests/javascript/README.txt @@ -0,0 +1,2 @@ +to execute Javascript tests, we use js-test-driver: http://code.google.com/p/js-test-driver + diff --git a/digsby/src/tests/unittests/javascript/runjstests.bat b/digsby/src/tests/unittests/javascript/runjstests.bat new file mode 100644 index 0000000..24850b5 --- /dev/null +++ b/digsby/src/tests/unittests/javascript/runjstests.bat @@ -0,0 +1,9 @@ +@setlocal +@echo off + +set BROWSER=%ProgramFiles%\safari\safari.exe +set PORT=4224 +set CONFIG=%~d0%~p0\..\..\..\plugins\twitter\jsTestDriver.conf + +java -jar %~d0%~p0\JsTestDriver-1.2.2.jar --port %PORT% --browser "%BROWSER%" --tests all --testOutput testOutputDir --config "%CONFIG%" + diff --git a/digsby/src/tests/unittests/menutests.py b/digsby/src/tests/unittests/menutests.py new file mode 100644 index 0000000..e6f96d0 --- /dev/null +++ b/digsby/src/tests/unittests/menutests.py @@ -0,0 +1,48 @@ +from tests import TestCase, test_main +import actionIDs +import time +import wx + +from gui.app.mainMenuEvents import fire_action_event + +popupActions = [ + actionIDs.ShowBuddyList, + actionIDs.ShowMenuBar, + actionIDs.SetStatus, + actionIDs.SetStatusCustom, + ] + +# requires Digsby app to be running +class MenubarTestingSuite(TestCase): + + def setUp(self): + TestCase.setUp(self) + wx.GetApp().testing = True + + def tearDown(self): + TestCase.tearDown(self) + wx.GetApp().testing = False + + def testHandlers(self): + # event system requires App mixin; we need to make it more testable + return + + app = wx.GetApp() + for action in dir(actionIDs): + app.event_fired = None + if action.startswith("__"): + continue + + id = getattr(actionIDs, action, -1) + self.assert_(action != -1) + + if isinstance(id, int) and not id == wx.ID_EXIT and not id in popupActions: + print "Firing handler for id=%r" % id + fire_action_event(id) + + app.Yield() + self.assert_(app.event_fired, "No event fired for action %s" % action) + + +if __name__ == "__main__": + test_main() diff --git a/digsby/src/tests/unittests/mocker.py b/digsby/src/tests/unittests/mocker.py new file mode 100644 index 0000000..8d5c891 --- /dev/null +++ b/digsby/src/tests/unittests/mocker.py @@ -0,0 +1,2155 @@ +""" +Mocker + +Graceful platform for test doubles in Python: mocks, stubs, fakes, and dummies. + +Copyright (c) 2007-2010, Gustavo Niemeyer + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +import __builtin__ +import tempfile +import unittest +import inspect +import shutil +import types +import sys +import os +import gc + + +if sys.version_info < (2, 4): + from sets import Set as set # pragma: nocover + + +__all__ = ["Mocker", "Expect", "expect", "IS", "CONTAINS", "IN", "MATCH", + "ANY", "ARGS", "KWARGS", "MockerTestCase"] + + +__author__ = "Gustavo Niemeyer " +__license__ = "BSD" +__version__ = "1.0" + + +ERROR_PREFIX = "[Mocker] " + + +# -------------------------------------------------------------------- +# Exceptions + +class MatchError(AssertionError): + """Raised when an unknown expression is seen in playback mode.""" + + +# -------------------------------------------------------------------- +# Helper for chained-style calling. + +class expect(object): + """This is a simple helper that allows a different call-style. + + With this class one can comfortably do chaining of calls to the + mocker object responsible by the object being handled. For instance:: + + expect(obj.attr).result(3).count(1, 2) + + Is the same as:: + + obj.attr + mocker.result(3) + mocker.count(1, 2) + + """ + + __mocker__ = None + + def __init__(self, mock, attr=None): + self._mock = mock + self._attr = attr + + def __getattr__(self, attr): + return self.__class__(self._mock, attr) + + def __call__(self, *args, **kwargs): + mocker = self.__mocker__ + if not mocker: + mocker = self._mock.__mocker__ + getattr(mocker, self._attr)(*args, **kwargs) + return self + + +def Expect(mocker): + """Create an expect() "function" using the given Mocker instance. + + This helper allows defining an expect() "function" which works even + in trickier cases such as: + + expect = Expect(mymocker) + expect(iter(mock)).generate([1, 2, 3]) + + """ + return type("Expect", (expect,), {"__mocker__": mocker}) + + +# -------------------------------------------------------------------- +# Extensions to Python's unittest. + +class MockerTestCase(unittest.TestCase): + """unittest.TestCase subclass with Mocker support. + + @ivar mocker: The mocker instance. + + This is a convenience only. Mocker may easily be used with the + standard C{unittest.TestCase} class if wanted. + + Test methods have a Mocker instance available on C{self.mocker}. + At the end of each test method, expectations of the mocker will + be verified, and any requested changes made to the environment + will be restored. + + In addition to the integration with Mocker, this class provides + a few additional helper methods. + """ + + def __init__(self, methodName="runTest"): + # So here is the trick: we take the real test method, wrap it on + # a function that do the job we have to do, and insert it in the + # *instance* dictionary, so that getattr() will return our + # replacement rather than the class method. + test_method = getattr(self, methodName, None) + if test_method is not None: + def test_method_wrapper(): + try: + result = test_method() + except: + raise + else: + if (self.mocker.is_recording() and + self.mocker.get_events()): + raise RuntimeError("Mocker must be put in replay " + "mode with self.mocker.replay()") + if (hasattr(result, "addCallback") and + hasattr(result, "addErrback")): + def verify(result): + self.mocker.verify() + return result + result.addCallback(verify) + else: + self.mocker.verify() + self.mocker.restore() + return result + # Copy all attributes from the original method.. + for attr in dir(test_method): + # .. unless they're present in our wrapper already. + if not hasattr(test_method_wrapper, attr) or attr == "__doc__": + setattr(test_method_wrapper, attr, + getattr(test_method, attr)) + setattr(self, methodName, test_method_wrapper) + + # We could overload run() normally, but other well-known testing + # frameworks do it as well, and some of them won't call the super, + # which might mean that cleanup wouldn't happen. With that in mind, + # we make integration easier by using the following trick. + run_method = self.run + def run_wrapper(*args, **kwargs): + try: + return run_method(*args, **kwargs) + finally: + self.__cleanup() + self.run = run_wrapper + + self.mocker = Mocker() + self.expect = Expect(self.mocker) + + self.__cleanup_funcs = [] + self.__cleanup_paths = [] + + super(MockerTestCase, self).__init__(methodName) + + def __call__(self, *args, **kwargs): + # This is necessary for Python 2.3 only, because it didn't use run(), + # which is supported above. + try: + super(MockerTestCase, self).__call__(*args, **kwargs) + finally: + if sys.version_info < (2, 4): + self.__cleanup() + + def __cleanup(self): + for path in self.__cleanup_paths: + if os.path.isfile(path): + os.unlink(path) + elif os.path.isdir(path): + shutil.rmtree(path) + self.mocker.reset() + for func, args, kwargs in self.__cleanup_funcs: + func(*args, **kwargs) + + def addCleanup(self, func, *args, **kwargs): + self.__cleanup_funcs.append((func, args, kwargs)) + + def makeFile(self, content=None, suffix="", prefix="tmp", basename=None, + dirname=None, path=None): + """Create a temporary file and return the path to it. + + @param content: Initial content for the file. + @param suffix: Suffix to be given to the file's basename. + @param prefix: Prefix to be given to the file's basename. + @param basename: Full basename for the file. + @param dirname: Put file inside this directory. + + The file is removed after the test runs. + """ + if path is not None: + self.__cleanup_paths.append(path) + elif basename is not None: + if dirname is None: + dirname = tempfile.mkdtemp() + self.__cleanup_paths.append(dirname) + path = os.path.join(dirname, basename) + else: + fd, path = tempfile.mkstemp(suffix, prefix, dirname) + self.__cleanup_paths.append(path) + os.close(fd) + if content is None: + os.unlink(path) + if content is not None: + file = open(path, "w") + file.write(content) + file.close() + return path + + def makeDir(self, suffix="", prefix="tmp", dirname=None, path=None): + """Create a temporary directory and return the path to it. + + @param suffix: Suffix to be given to the file's basename. + @param prefix: Prefix to be given to the file's basename. + @param dirname: Put directory inside this parent directory. + + The directory is removed after the test runs. + """ + if path is not None: + os.makedirs(path) + else: + path = tempfile.mkdtemp(suffix, prefix, dirname) + self.__cleanup_paths.append(path) + return path + + def failUnlessIs(self, first, second, msg=None): + """Assert that C{first} is the same object as C{second}.""" + if first is not second: + raise self.failureException(msg or "%r is not %r" % (first, second)) + + def failIfIs(self, first, second, msg=None): + """Assert that C{first} is not the same object as C{second}.""" + if first is second: + raise self.failureException(msg or "%r is %r" % (first, second)) + + def failUnlessIn(self, first, second, msg=None): + """Assert that C{first} is contained in C{second}.""" + if first not in second: + raise self.failureException(msg or "%r not in %r" % (first, second)) + + def failUnlessStartsWith(self, first, second, msg=None): + """Assert that C{first} starts with C{second}.""" + if first[:len(second)] != second: + raise self.failureException(msg or "%r doesn't start with %r" % + (first, second)) + + def failIfStartsWith(self, first, second, msg=None): + """Assert that C{first} doesn't start with C{second}.""" + if first[:len(second)] == second: + raise self.failureException(msg or "%r starts with %r" % + (first, second)) + + def failUnlessEndsWith(self, first, second, msg=None): + """Assert that C{first} starts with C{second}.""" + if first[len(first)-len(second):] != second: + raise self.failureException(msg or "%r doesn't end with %r" % + (first, second)) + + def failIfEndsWith(self, first, second, msg=None): + """Assert that C{first} doesn't start with C{second}.""" + if first[len(first)-len(second):] == second: + raise self.failureException(msg or "%r ends with %r" % + (first, second)) + + def failIfIn(self, first, second, msg=None): + """Assert that C{first} is not contained in C{second}.""" + if first in second: + raise self.failureException(msg or "%r in %r" % (first, second)) + + def failUnlessApproximates(self, first, second, tolerance, msg=None): + """Assert that C{first} is near C{second} by at most C{tolerance}.""" + if abs(first - second) > tolerance: + raise self.failureException(msg or "abs(%r - %r) > %r" % + (first, second, tolerance)) + + def failIfApproximates(self, first, second, tolerance, msg=None): + """Assert that C{first} is far from C{second} by at least C{tolerance}. + """ + if abs(first - second) <= tolerance: + raise self.failureException(msg or "abs(%r - %r) <= %r" % + (first, second, tolerance)) + + def failUnlessMethodsMatch(self, first, second): + """Assert that public methods in C{first} are present in C{second}. + + This method asserts that all public methods found in C{first} are also + present in C{second} and accept the same arguments. C{first} may + have its own private methods, though, and may not have all methods + found in C{second}. Note that if a private method in C{first} matches + the name of one in C{second}, their specification is still compared. + + This is useful to verify if a fake or stub class have the same API as + the real class being simulated. + """ + first_methods = dict(inspect.getmembers(first, inspect.ismethod)) + second_methods = dict(inspect.getmembers(second, inspect.ismethod)) + for name, first_method in first_methods.iteritems(): + first_argspec = inspect.getargspec(first_method) + first_formatted = inspect.formatargspec(*first_argspec) + + second_method = second_methods.get(name) + if second_method is None: + if name[:1] == "_": + continue # First may have its own private methods. + raise self.failureException("%s.%s%s not present in %s" % + (first.__name__, name, first_formatted, second.__name__)) + + second_argspec = inspect.getargspec(second_method) + if first_argspec != second_argspec: + second_formatted = inspect.formatargspec(*second_argspec) + raise self.failureException("%s.%s%s != %s.%s%s" % + (first.__name__, name, first_formatted, + second.__name__, name, second_formatted)) + + def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): + """ + Fail unless an exception of class excClass is thrown by callableObj + when invoked with arguments args and keyword arguments kwargs. If a + different type of exception is thrown, it will not be caught, and the + test case will be deemed to have suffered an error, exactly as for an + unexpected exception. It returns the exception instance if it matches + the given exception class. + """ + try: + result = callableObj(*args, **kwargs) + except excClass, e: + return e + else: + excName = excClass + if hasattr(excClass, "__name__"): + excName = excClass.__name__ + raise self.failureException( + "%s not raised (%r returned)" % (excName, result)) + + + assertIs = failUnlessIs + assertIsNot = failIfIs + assertIn = failUnlessIn + assertNotIn = failIfIn + assertStartsWith = failUnlessStartsWith + assertNotStartsWith = failIfStartsWith + assertEndsWith = failUnlessEndsWith + assertNotEndsWith = failIfEndsWith + assertApproximates = failUnlessApproximates + assertNotApproximates = failIfApproximates + assertMethodsMatch = failUnlessMethodsMatch + assertRaises = failUnlessRaises + + # The following are missing in Python < 2.4. + assertTrue = unittest.TestCase.failUnless + assertFalse = unittest.TestCase.failIf + + # The following is provided for compatibility with Twisted's trial. + assertIdentical = assertIs + assertNotIdentical = assertIsNot + failUnlessIdentical = failUnlessIs + failIfIdentical = failIfIs + + +# -------------------------------------------------------------------- +# Mocker. + +class classinstancemethod(object): + + def __init__(self, method): + self.method = method + + def __get__(self, obj, cls=None): + def bound_method(*args, **kwargs): + return self.method(cls, obj, *args, **kwargs) + return bound_method + + +class MockerBase(object): + """Controller of mock objects. + + A mocker instance is used to command recording and replay of + expectations on any number of mock objects. + + Expectations should be expressed for the mock object while in + record mode (the initial one) by using the mock object itself, + and using the mocker (and/or C{expect()} as a helper) to define + additional behavior for each event. For instance:: + + mock = mocker.mock() + mock.hello() + mocker.result("Hi!") + mocker.replay() + assert mock.hello() == "Hi!" + mock.restore() + mock.verify() + + In this short excerpt a mock object is being created, then an + expectation of a call to the C{hello()} method was recorded, and + when called the method should return the value C{10}. Then, the + mocker is put in replay mode, and the expectation is satisfied by + calling the C{hello()} method, which indeed returns 10. Finally, + a call to the L{restore()} method is performed to undo any needed + changes made in the environment, and the L{verify()} method is + called to ensure that all defined expectations were met. + + The same logic can be expressed more elegantly using the + C{with mocker:} statement, as follows:: + + mock = mocker.mock() + mock.hello() + mocker.result("Hi!") + with mocker: + assert mock.hello() == "Hi!" + + Also, the MockerTestCase class, which integrates the mocker on + a unittest.TestCase subclass, may be used to reduce the overhead + of controlling the mocker. A test could be written as follows:: + + class SampleTest(MockerTestCase): + + def test_hello(self): + mock = self.mocker.mock() + mock.hello() + self.mocker.result("Hi!") + self.mocker.replay() + self.assertEquals(mock.hello(), "Hi!") + """ + + _recorders = [] + + # For convenience only. + on = expect + + class __metaclass__(type): + def __init__(self, name, bases, dict): + # Make independent lists on each subclass, inheriting from parent. + self._recorders = list(getattr(self, "_recorders", ())) + + def __init__(self): + self._recorders = self._recorders[:] + self._events = [] + self._recording = True + self._ordering = False + self._last_orderer = None + + def is_recording(self): + """Return True if in recording mode, False if in replay mode. + + Recording is the initial state. + """ + return self._recording + + def replay(self): + """Change to replay mode, where recorded events are reproduced. + + If already in replay mode, the mocker will be restored, with all + expectations reset, and then put again in replay mode. + + An alternative and more comfortable way to replay changes is + using the 'with' statement, as follows:: + + mocker = Mocker() + + with mocker: + + + The 'with' statement will automatically put mocker in replay + mode, and will also verify if all events were correctly reproduced + at the end (using L{verify()}), and also restore any changes done + in the environment (with L{restore()}). + + Also check the MockerTestCase class, which integrates the + unittest.TestCase class with mocker. + """ + if not self._recording: + for event in self._events: + event.restore() + else: + self._recording = False + for event in self._events: + event.replay() + + def restore(self): + """Restore changes in the environment, and return to recording mode. + + This should always be called after the test is complete (succeeding + or not). There are ways to call this method automatically on + completion (e.g. using a C{with mocker:} statement, or using the + L{MockerTestCase} class. + """ + if not self._recording: + self._recording = True + for event in self._events: + event.restore() + + def reset(self): + """Reset the mocker state. + + This will restore environment changes, if currently in replay + mode, and then remove all events previously recorded. + """ + if not self._recording: + self.restore() + self.unorder() + del self._events[:] + + def get_events(self): + """Return all recorded events.""" + return self._events[:] + + def add_event(self, event): + """Add an event. + + This method is used internally by the implementation, and + shouldn't be needed on normal mocker usage. + """ + self._events.append(event) + if self._ordering: + orderer = event.add_task(Orderer(event.path)) + if self._last_orderer: + orderer.add_dependency(self._last_orderer) + self._last_orderer = orderer + return event + + def verify(self): + """Check if all expectations were met, and raise AssertionError if not. + + The exception message will include a nice description of which + expectations were not met, and why. + """ + errors = [] + for event in self._events: + try: + event.verify() + except AssertionError, e: + error = str(e) + if not error: + raise RuntimeError("Empty error message from %r" + % event) + errors.append(error) + if errors: + message = [ERROR_PREFIX + "Unmet expectations:", ""] + for error in errors: + lines = error.splitlines() + message.append("=> " + lines.pop(0)) + message.extend([" " + line for line in lines]) + message.append("") + raise AssertionError(os.linesep.join(message)) + + def mock(self, spec_and_type=None, spec=None, type=None, + name=None, count=True): + """Return a new mock object. + + @param spec_and_type: Handy positional argument which sets both + spec and type. + @param spec: Method calls will be checked for correctness against + the given class. + @param type: If set, the Mock's __class__ attribute will return + the given type. This will make C{isinstance()} calls + on the object work. + @param name: Name for the mock object, used in the representation of + expressions. The name is rarely needed, as it's usually + guessed correctly from the variable name used. + @param count: If set to false, expressions may be executed any number + of times, unless an expectation is explicitly set using + the L{count()} method. By default, expressions are + expected once. + """ + if spec_and_type is not None: + spec = type = spec_and_type + return Mock(self, spec=spec, type=type, name=name, count=count) + + def proxy(self, object, spec=True, type=True, name=None, count=True, + passthrough=True): + """Return a new mock object which proxies to the given object. + + Proxies are useful when only part of the behavior of an object + is to be mocked. Unknown expressions may be passed through to + the real implementation implicitly (if the C{passthrough} argument + is True), or explicitly (using the L{passthrough()} method + on the event). + + @param object: Real object to be proxied, and replaced by the mock + on replay mode. It may also be an "import path", + such as C{"time.time"}, in which case the object + will be the C{time} function from the C{time} module. + @param spec: Method calls will be checked for correctness against + the given object, which may be a class or an instance + where attributes will be looked up. Defaults to the + the C{object} parameter. May be set to None explicitly, + in which case spec checking is disabled. Checks may + also be disabled explicitly on a per-event basis with + the L{nospec()} method. + @param type: If set, the Mock's __class__ attribute will return + the given type. This will make C{isinstance()} calls + on the object work. Defaults to the type of the + C{object} parameter. May be set to None explicitly. + @param name: Name for the mock object, used in the representation of + expressions. The name is rarely needed, as it's usually + guessed correctly from the variable name used. + @param count: If set to false, expressions may be executed any number + of times, unless an expectation is explicitly set using + the L{count()} method. By default, expressions are + expected once. + @param passthrough: If set to False, passthrough of actions on the + proxy to the real object will only happen when + explicitly requested via the L{passthrough()} + method. + """ + if isinstance(object, basestring): + if name is None: + name = object + import_stack = object.split(".") + attr_stack = [] + while import_stack: + module_path = ".".join(import_stack) + try: + __import__(module_path) + except ImportError: + attr_stack.insert(0, import_stack.pop()) + if not import_stack: + raise + continue + else: + object = sys.modules[module_path] + for attr in attr_stack: + object = getattr(object, attr) + break + if isinstance(object, types.UnboundMethodType): + object = object.im_func + if spec is True: + spec = object + if type is True: + type = __builtin__.type(object) + return Mock(self, spec=spec, type=type, object=object, + name=name, count=count, passthrough=passthrough) + + def replace(self, object, spec=True, type=True, name=None, count=True, + passthrough=True): + """Create a proxy, and replace the original object with the mock. + + On replay, the original object will be replaced by the returned + proxy in all dictionaries found in the running interpreter via + the garbage collecting system. This should cover module + namespaces, class namespaces, instance namespaces, and so on. + + @param object: Real object to be proxied, and replaced by the mock + on replay mode. It may also be an "import path", + such as C{"time.time"}, in which case the object + will be the C{time} function from the C{time} module. + @param spec: Method calls will be checked for correctness against + the given object, which may be a class or an instance + where attributes will be looked up. Defaults to the + the C{object} parameter. May be set to None explicitly, + in which case spec checking is disabled. Checks may + also be disabled explicitly on a per-event basis with + the L{nospec()} method. + @param type: If set, the Mock's __class__ attribute will return + the given type. This will make C{isinstance()} calls + on the object work. Defaults to the type of the + C{object} parameter. May be set to None explicitly. + @param name: Name for the mock object, used in the representation of + expressions. The name is rarely needed, as it's usually + guessed correctly from the variable name used. + @param passthrough: If set to False, passthrough of actions on the + proxy to the real object will only happen when + explicitly requested via the L{passthrough()} + method. + """ + mock = self.proxy(object, spec, type, name, count, passthrough) + event = self._get_replay_restore_event() + event.add_task(ProxyReplacer(mock)) + return mock + + def patch(self, object, spec=True): + """Patch an existing object to reproduce recorded events. + + @param object: Class or instance to be patched. + @param spec: Method calls will be checked for correctness against + the given object, which may be a class or an instance + where attributes will be looked up. Defaults to the + the C{object} parameter. May be set to None explicitly, + in which case spec checking is disabled. Checks may + also be disabled explicitly on a per-event basis with + the L{nospec()} method. + + The result of this method is still a mock object, which can be + used like any other mock object to record events. The difference + is that when the mocker is put on replay mode, the *real* object + will be modified to behave according to recorded expectations. + + Patching works in individual instances, and also in classes. + When an instance is patched, recorded events will only be + considered on this specific instance, and other instances should + behave normally. When a class is patched, the reproduction of + events will be considered on any instance of this class once + created (collectively). + + Observe that, unlike with proxies which catch only events done + through the mock object, *all* accesses to recorded expectations + will be considered; even these coming from the object itself + (e.g. C{self.hello()} is considered if this method was patched). + While this is a very powerful feature, and many times the reason + to use patches in the first place, it's important to keep this + behavior in mind. + + Patching of the original object only takes place when the mocker + is put on replay mode, and the patched object will be restored + to its original state once the L{restore()} method is called + (explicitly, or implicitly with alternative conventions, such as + a C{with mocker:} block, or a MockerTestCase class). + """ + if spec is True: + spec = object + patcher = Patcher() + event = self._get_replay_restore_event() + event.add_task(patcher) + mock = Mock(self, object=object, patcher=patcher, + passthrough=True, spec=spec) + patcher.patch_attr(object, '__mocker_mock__', mock) + return mock + + def act(self, path): + """This is called by mock objects whenever something happens to them. + + This method is part of the implementation between the mocker + and mock objects. + """ + if self._recording: + event = self.add_event(Event(path)) + for recorder in self._recorders: + recorder(self, event) + return Mock(self, path) + else: + # First run events that may run, then run unsatisfied events, then + # ones not previously run. We put the index in the ordering tuple + # instead of the actual event because we want a stable sort + # (ordering between 2 events is undefined). + events = self._events + order = [(events[i].satisfied()*2 + events[i].has_run(), i) + for i in range(len(events))] + order.sort() + postponed = None + for weight, i in order: + event = events[i] + if event.matches(path): + if event.may_run(path): + return event.run(path) + elif postponed is None: + postponed = event + if postponed is not None: + return postponed.run(path) + raise MatchError(ERROR_PREFIX + "Unexpected expression: %s" % path) + + def get_recorders(cls, self): + """Return recorders associated with this mocker class or instance. + + This method may be called on mocker instances and also on mocker + classes. See the L{add_recorder()} method for more information. + """ + return (self or cls)._recorders[:] + get_recorders = classinstancemethod(get_recorders) + + def add_recorder(cls, self, recorder): + """Add a recorder to this mocker class or instance. + + @param recorder: Callable accepting C{(mocker, event)} as parameters. + + This is part of the implementation of mocker. + + All registered recorders are called for translating events that + happen during recording into expectations to be met once the state + is switched to replay mode. + + This method may be called on mocker instances and also on mocker + classes. When called on a class, the recorder will be used by + all instances, and also inherited on subclassing. When called on + instances, the recorder is added only to the given instance. + """ + (self or cls)._recorders.append(recorder) + return recorder + add_recorder = classinstancemethod(add_recorder) + + def remove_recorder(cls, self, recorder): + """Remove the given recorder from this mocker class or instance. + + This method may be called on mocker classes and also on mocker + instances. See the L{add_recorder()} method for more information. + """ + (self or cls)._recorders.remove(recorder) + remove_recorder = classinstancemethod(remove_recorder) + + def result(self, value): + """Make the last recorded event return the given value on replay. + + @param value: Object to be returned when the event is replayed. + """ + self.call(lambda *args, **kwargs: value) + + def generate(self, sequence): + """Last recorded event will return a generator with the given sequence. + + @param sequence: Sequence of values to be generated. + """ + def generate(*args, **kwargs): + for value in sequence: + yield value + self.call(generate) + + def throw(self, exception): + """Make the last recorded event raise the given exception on replay. + + @param exception: Class or instance of exception to be raised. + """ + def raise_exception(*args, **kwargs): + raise exception + self.call(raise_exception) + + def call(self, func): + """Make the last recorded event cause the given function to be called. + + @param func: Function to be called. + + The result of the function will be used as the event result. + """ + self._events[-1].add_task(FunctionRunner(func)) + + def count(self, min, max=False): + """Last recorded event must be replayed between min and max times. + + @param min: Minimum number of times that the event must happen. + @param max: Maximum number of times that the event must happen. If + not given, it defaults to the same value of the C{min} + parameter. If set to None, there is no upper limit, and + the expectation is met as long as it happens at least + C{min} times. + """ + event = self._events[-1] + for task in event.get_tasks(): + if isinstance(task, RunCounter): + event.remove_task(task) + event.add_task(RunCounter(min, max)) + + def is_ordering(self): + """Return true if all events are being ordered. + + See the L{order()} method. + """ + return self._ordering + + def unorder(self): + """Disable the ordered mode. + + See the L{order()} method for more information. + """ + self._ordering = False + self._last_orderer = None + + def order(self, *path_holders): + """Create an expectation of order between two or more events. + + @param path_holders: Objects returned as the result of recorded events. + + By default, mocker won't force events to happen precisely in + the order they were recorded. Calling this method will change + this behavior so that events will only match if reproduced in + the correct order. + + There are two ways in which this method may be used. Which one + is used in a given occasion depends only on convenience. + + If no arguments are passed, the mocker will be put in a mode where + all the recorded events following the method call will only be met + if they happen in order. When that's used, the mocker may be put + back in unordered mode by calling the L{unorder()} method, or by + using a 'with' block, like so:: + + with mocker.ordered(): + + + In this case, only expressions in will be ordered, + and the mocker will be back in unordered mode after the 'with' block. + + The second way to use it is by specifying precisely which events + should be ordered. As an example:: + + mock = mocker.mock() + expr1 = mock.hello() + expr2 = mock.world + expr3 = mock.x.y.z + mocker.order(expr1, expr2, expr3) + + This method of ordering only works when the expression returns + another object. + + Also check the L{after()} and L{before()} methods, which are + alternative ways to perform this. + """ + if not path_holders: + self._ordering = True + return OrderedContext(self) + + last_orderer = None + for path_holder in path_holders: + if type(path_holder) is Path: + path = path_holder + else: + path = path_holder.__mocker_path__ + for event in self._events: + if event.path is path: + for task in event.get_tasks(): + if isinstance(task, Orderer): + orderer = task + break + else: + orderer = Orderer(path) + event.add_task(orderer) + if last_orderer: + orderer.add_dependency(last_orderer) + last_orderer = orderer + break + + def after(self, *path_holders): + """Last recorded event must happen after events referred to. + + @param path_holders: Objects returned as the result of recorded events + which should happen before the last recorded event + + As an example, the idiom:: + + expect(mock.x).after(mock.y, mock.z) + + is an alternative way to say:: + + expr_x = mock.x + expr_y = mock.y + expr_z = mock.z + mocker.order(expr_y, expr_x) + mocker.order(expr_z, expr_x) + + See L{order()} for more information. + """ + last_path = self._events[-1].path + for path_holder in path_holders: + self.order(path_holder, last_path) + + def before(self, *path_holders): + """Last recorded event must happen before events referred to. + + @param path_holders: Objects returned as the result of recorded events + which should happen after the last recorded event + + As an example, the idiom:: + + expect(mock.x).before(mock.y, mock.z) + + is an alternative way to say:: + + expr_x = mock.x + expr_y = mock.y + expr_z = mock.z + mocker.order(expr_x, expr_y) + mocker.order(expr_x, expr_z) + + See L{order()} for more information. + """ + last_path = self._events[-1].path + for path_holder in path_holders: + self.order(last_path, path_holder) + + def nospec(self): + """Don't check method specification of real object on last event. + + By default, when using a mock created as the result of a call to + L{proxy()}, L{replace()}, and C{patch()}, or when passing the spec + attribute to the L{mock()} method, method calls on the given object + are checked for correctness against the specification of the real + object (or the explicitly provided spec). + + This method will disable that check specifically for the last + recorded event. + """ + event = self._events[-1] + for task in event.get_tasks(): + if isinstance(task, SpecChecker): + event.remove_task(task) + + def passthrough(self, result_callback=None): + """Make the last recorded event run on the real object once seen. + + @param result_callback: If given, this function will be called with + the result of the *real* method call as the only argument. + + This can only be used on proxies, as returned by the L{proxy()} + and L{replace()} methods, or on mocks representing patched objects, + as returned by the L{patch()} method. + """ + event = self._events[-1] + if event.path.root_object is None: + raise TypeError("Mock object isn't a proxy") + event.add_task(PathExecuter(result_callback)) + + def __enter__(self): + """Enter in a 'with' context. This will run replay().""" + self.replay() + return self + + def __exit__(self, type, value, traceback): + """Exit from a 'with' context. + + This will run restore() at all times, but will only run verify() + if the 'with' block itself hasn't raised an exception. Exceptions + in that block are never swallowed. + """ + self.restore() + if type is None: + self.verify() + return False + + def _get_replay_restore_event(self): + """Return unique L{ReplayRestoreEvent}, creating if needed. + + Some tasks only want to replay/restore. When that's the case, + they shouldn't act on other events during replay. Also, they + can all be put in a single event when that's the case. Thus, + we add a single L{ReplayRestoreEvent} as the first element of + the list. + """ + if not self._events or type(self._events[0]) != ReplayRestoreEvent: + self._events.insert(0, ReplayRestoreEvent()) + return self._events[0] + + +class OrderedContext(object): + + def __init__(self, mocker): + self._mocker = mocker + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + self._mocker.unorder() + + +class Mocker(MockerBase): + __doc__ = MockerBase.__doc__ + +# Decorator to add recorders on the standard Mocker class. +recorder = Mocker.add_recorder + + +# -------------------------------------------------------------------- +# Mock object. + +class Mock(object): + + def __init__(self, mocker, path=None, name=None, spec=None, type=None, + object=None, passthrough=False, patcher=None, count=True): + self.__mocker__ = mocker + self.__mocker_path__ = path or Path(self, object) + self.__mocker_name__ = name + self.__mocker_spec__ = spec + self.__mocker_object__ = object + self.__mocker_passthrough__ = passthrough + self.__mocker_patcher__ = patcher + self.__mocker_replace__ = False + self.__mocker_type__ = type + self.__mocker_count__ = count + + def __mocker_act__(self, kind, args=(), kwargs={}, object=None): + if self.__mocker_name__ is None: + self.__mocker_name__ = find_object_name(self, 2) + action = Action(kind, args, kwargs, self.__mocker_path__) + path = self.__mocker_path__ + action + if object is not None: + path.root_object = object + try: + return self.__mocker__.act(path) + except MatchError, exception: + root_mock = path.root_mock + if (path.root_object is not None and + root_mock.__mocker_passthrough__): + return path.execute(path.root_object) + # Reinstantiate to show raise statement on traceback, and + # also to make the traceback shown shorter. + raise MatchError(str(exception)) + except AssertionError, e: + lines = str(e).splitlines() + message = [ERROR_PREFIX + "Unmet expectation:", ""] + message.append("=> " + lines.pop(0)) + message.extend([" " + line for line in lines]) + message.append("") + raise AssertionError(os.linesep.join(message)) + + def __getattribute__(self, name): + if name.startswith("__mocker_"): + return super(Mock, self).__getattribute__(name) + if name == "__class__": + if self.__mocker__.is_recording() or self.__mocker_type__ is None: + return type(self) + return self.__mocker_type__ + if name == "__length_hint__": + # This is used by Python 2.6+ to optimize the allocation + # of arrays in certain cases. Pretend it doesn't exist. + raise AttributeError("No __length_hint__ here!") + return self.__mocker_act__("getattr", (name,)) + + def __setattr__(self, name, value): + if name.startswith("__mocker_"): + return super(Mock, self).__setattr__(name, value) + return self.__mocker_act__("setattr", (name, value)) + + def __delattr__(self, name): + return self.__mocker_act__("delattr", (name,)) + + def __call__(self, *args, **kwargs): + return self.__mocker_act__("call", args, kwargs) + + def __contains__(self, value): + return self.__mocker_act__("contains", (value,)) + + def __getitem__(self, key): + return self.__mocker_act__("getitem", (key,)) + + def __setitem__(self, key, value): + return self.__mocker_act__("setitem", (key, value)) + + def __delitem__(self, key): + return self.__mocker_act__("delitem", (key,)) + + def __len__(self): + # MatchError is turned on an AttributeError so that list() and + # friends act properly when trying to get length hints on + # something that doesn't offer them. + try: + result = self.__mocker_act__("len") + except MatchError, e: + raise AttributeError(str(e)) + if type(result) is Mock: + return 0 + return result + + def __nonzero__(self): + try: + result = self.__mocker_act__("nonzero") + except MatchError, e: + return True + if type(result) is Mock: + return True + return result + + def __iter__(self): + # XXX On py3k, when next() becomes __next__(), we'll be able + # to return the mock itself because it will be considered + # an iterator (we'll be mocking __next__ as well, which we + # can't now). + result = self.__mocker_act__("iter") + if type(result) is Mock: + return iter([]) + return result + + # When adding a new action kind here, also add support for it on + # Action.execute() and Path.__str__(). + + +def find_object_name(obj, depth=0): + """Try to detect how the object is named on a previous scope.""" + try: + frame = sys._getframe(depth+1) + except: + return None + for name, frame_obj in frame.f_locals.iteritems(): + if frame_obj is obj: + return name + self = frame.f_locals.get("self") + if self is not None: + try: + items = list(self.__dict__.iteritems()) + except: + pass + else: + for name, self_obj in items: + if self_obj is obj: + return name + return None + + +# -------------------------------------------------------------------- +# Action and path. + +class Action(object): + + def __init__(self, kind, args, kwargs, path=None): + self.kind = kind + self.args = args + self.kwargs = kwargs + self.path = path + self._execute_cache = {} + + def __repr__(self): + if self.path is None: + return "Action(%r, %r, %r)" % (self.kind, self.args, self.kwargs) + return "Action(%r, %r, %r, %r)" % \ + (self.kind, self.args, self.kwargs, self.path) + + def __eq__(self, other): + return (self.kind == other.kind and + self.args == other.args and + self.kwargs == other.kwargs) + + def __ne__(self, other): + return not self.__eq__(other) + + def matches(self, other): + return (self.kind == other.kind and + match_params(self.args, self.kwargs, other.args, other.kwargs)) + + def execute(self, object): + # This caching scheme may fail if the object gets deallocated before + # the action, as the id might get reused. It's somewhat easy to fix + # that with a weakref callback. For our uses, though, the object + # should never get deallocated before the action itself, so we'll + # just keep it simple. + if id(object) in self._execute_cache: + return self._execute_cache[id(object)] + execute = getattr(object, "__mocker_execute__", None) + if execute is not None: + result = execute(self, object) + else: + kind = self.kind + if kind == "getattr": + result = getattr(object, self.args[0]) + elif kind == "setattr": + result = setattr(object, self.args[0], self.args[1]) + elif kind == "delattr": + result = delattr(object, self.args[0]) + elif kind == "call": + result = object(*self.args, **self.kwargs) + elif kind == "contains": + result = self.args[0] in object + elif kind == "getitem": + result = object[self.args[0]] + elif kind == "setitem": + result = object[self.args[0]] = self.args[1] + elif kind == "delitem": + del object[self.args[0]] + result = None + elif kind == "len": + result = len(object) + elif kind == "nonzero": + result = bool(object) + elif kind == "iter": + result = iter(object) + else: + raise RuntimeError("Don't know how to execute %r kind." % kind) + self._execute_cache[id(object)] = result + return result + + +class Path(object): + + def __init__(self, root_mock, root_object=None, actions=()): + self.root_mock = root_mock + self.root_object = root_object + self.actions = tuple(actions) + self.__mocker_replace__ = False + + def parent_path(self): + if not self.actions: + return None + return self.actions[-1].path + parent_path = property(parent_path) + + def __add__(self, action): + """Return a new path which includes the given action at the end.""" + return self.__class__(self.root_mock, self.root_object, + self.actions + (action,)) + + def __eq__(self, other): + """Verify if the two paths are equal. + + Two paths are equal if they refer to the same mock object, and + have the actions with equal kind, args and kwargs. + """ + if (self.root_mock is not other.root_mock or + self.root_object is not other.root_object or + len(self.actions) != len(other.actions)): + return False + for action, other_action in zip(self.actions, other.actions): + if action != other_action: + return False + return True + + def matches(self, other): + """Verify if the two paths are equivalent. + + Two paths are equal if they refer to the same mock object, and + have the same actions performed on them. + """ + if (self.root_mock is not other.root_mock or + len(self.actions) != len(other.actions)): + return False + for action, other_action in zip(self.actions, other.actions): + if not action.matches(other_action): + return False + return True + + def execute(self, object): + """Execute all actions sequentially on object, and return result. + """ + for action in self.actions: + object = action.execute(object) + return object + + def __str__(self): + """Transform the path into a nice string such as obj.x.y('z').""" + result = self.root_mock.__mocker_name__ or "" + for action in self.actions: + if action.kind == "getattr": + result = "%s.%s" % (result, action.args[0]) + elif action.kind == "setattr": + result = "%s.%s = %r" % (result, action.args[0], action.args[1]) + elif action.kind == "delattr": + result = "del %s.%s" % (result, action.args[0]) + elif action.kind == "call": + args = [repr(x) for x in action.args] + items = list(action.kwargs.iteritems()) + items.sort() + for pair in items: + args.append("%s=%r" % pair) + result = "%s(%s)" % (result, ", ".join(args)) + elif action.kind == "contains": + result = "%r in %s" % (action.args[0], result) + elif action.kind == "getitem": + result = "%s[%r]" % (result, action.args[0]) + elif action.kind == "setitem": + result = "%s[%r] = %r" % (result, action.args[0], + action.args[1]) + elif action.kind == "delitem": + result = "del %s[%r]" % (result, action.args[0]) + elif action.kind == "len": + result = "len(%s)" % result + elif action.kind == "nonzero": + result = "bool(%s)" % result + elif action.kind == "iter": + result = "iter(%s)" % result + else: + raise RuntimeError("Don't know how to format kind %r" % + action.kind) + return result + + +class SpecialArgument(object): + """Base for special arguments for matching parameters.""" + + def __init__(self, object=None): + self.object = object + + def __repr__(self): + if self.object is None: + return self.__class__.__name__ + else: + return "%s(%r)" % (self.__class__.__name__, self.object) + + def matches(self, other): + return True + + def __eq__(self, other): + return type(other) == type(self) and self.object == other.object + + +class ANY(SpecialArgument): + """Matches any single argument.""" + +ANY = ANY() + + +class ARGS(SpecialArgument): + """Matches zero or more positional arguments.""" + +ARGS = ARGS() + + +class KWARGS(SpecialArgument): + """Matches zero or more keyword arguments.""" + +KWARGS = KWARGS() + + +class IS(SpecialArgument): + + def matches(self, other): + return self.object is other + + def __eq__(self, other): + return type(other) == type(self) and self.object is other.object + + +class CONTAINS(SpecialArgument): + + def matches(self, other): + try: + other.__contains__ + except AttributeError: + try: + iter(other) + except TypeError: + # If an object can't be iterated, and has no __contains__ + # hook, it'd blow up on the test below. We test this in + # advance to prevent catching more errors than we really + # want. + return False + return self.object in other + + +class IN(SpecialArgument): + + def matches(self, other): + return other in self.object + + +class MATCH(SpecialArgument): + + def matches(self, other): + return bool(self.object(other)) + + def __eq__(self, other): + return type(other) == type(self) and self.object is other.object + + +def match_params(args1, kwargs1, args2, kwargs2): + """Match the two sets of parameters, considering special parameters.""" + + has_args = ARGS in args1 + has_kwargs = KWARGS in args1 + + if has_kwargs: + args1 = [arg1 for arg1 in args1 if arg1 is not KWARGS] + elif len(kwargs1) != len(kwargs2): + return False + + if not has_args and len(args1) != len(args2): + return False + + # Either we have the same number of kwargs, or unknown keywords are + # accepted (KWARGS was used), so check just the ones in kwargs1. + for key, arg1 in kwargs1.iteritems(): + if key not in kwargs2: + return False + arg2 = kwargs2[key] + if isinstance(arg1, SpecialArgument): + if not arg1.matches(arg2): + return False + elif arg1 != arg2: + return False + + # Keywords match. Now either we have the same number of + # arguments, or ARGS was used. If ARGS wasn't used, arguments + # must match one-on-one necessarily. + if not has_args: + for arg1, arg2 in zip(args1, args2): + if isinstance(arg1, SpecialArgument): + if not arg1.matches(arg2): + return False + elif arg1 != arg2: + return False + return True + + # Easy choice. Keywords are matching, and anything on args is accepted. + if (ARGS,) == args1: + return True + + # We have something different there. If we don't have positional + # arguments on the original call, it can't match. + if not args2: + # Unless we have just several ARGS (which is bizarre, but..). + for arg1 in args1: + if arg1 is not ARGS: + return False + return True + + # Ok, all bets are lost. We have to actually do the more expensive + # matching. This is an algorithm based on the idea of the Levenshtein + # Distance between two strings, but heavily hacked for this purpose. + args2l = len(args2) + if args1[0] is ARGS: + args1 = args1[1:] + array = [0]*args2l + else: + array = [1]*args2l + for i in range(len(args1)): + last = array[0] + if args1[i] is ARGS: + for j in range(1, args2l): + last, array[j] = array[j], min(array[j-1], array[j], last) + else: + array[0] = i or int(args1[i] != args2[0]) + for j in range(1, args2l): + last, array[j] = array[j], last or int(args1[i] != args2[j]) + if 0 not in array: + return False + if array[-1] != 0: + return False + return True + + +# -------------------------------------------------------------------- +# Event and task base. + +class Event(object): + """Aggregation of tasks that keep track of a recorded action. + + An event represents something that may or may not happen while the + mocked environment is running, such as an attribute access, or a + method call. The event is composed of several tasks that are + orchestrated together to create a composed meaning for the event, + including for which actions it should be run, what happens when it + runs, and what's the expectations about the actions run. + """ + + def __init__(self, path=None): + self.path = path + self._tasks = [] + self._has_run = False + + def add_task(self, task): + """Add a new task to this taks.""" + self._tasks.append(task) + return task + + def remove_task(self, task): + self._tasks.remove(task) + + def get_tasks(self): + return self._tasks[:] + + def matches(self, path): + """Return true if *all* tasks match the given path.""" + for task in self._tasks: + if not task.matches(path): + return False + return bool(self._tasks) + + def has_run(self): + return self._has_run + + def may_run(self, path): + """Verify if any task would certainly raise an error if run. + + This will call the C{may_run()} method on each task and return + false if any of them returns false. + """ + for task in self._tasks: + if not task.may_run(path): + return False + return True + + def run(self, path): + """Run all tasks with the given action. + + @param path: The path of the expression run. + + Running an event means running all of its tasks individually and in + order. An event should only ever be run if all of its tasks claim to + match the given action. + + The result of this method will be the last result of a task + which isn't None, or None if they're all None. + """ + self._has_run = True + result = None + errors = [] + for task in self._tasks: + try: + task_result = task.run(path) + except AssertionError, e: + error = str(e) + if not error: + raise RuntimeError("Empty error message from %r" % task) + errors.append(error) + else: + if task_result is not None: + result = task_result + if errors: + message = [str(self.path)] + if str(path) != message[0]: + message.append("- Run: %s" % path) + for error in errors: + lines = error.splitlines() + message.append("- " + lines.pop(0)) + message.extend([" " + line for line in lines]) + raise AssertionError(os.linesep.join(message)) + return result + + def satisfied(self): + """Return true if all tasks are satisfied. + + Being satisfied means that there are no unmet expectations. + """ + for task in self._tasks: + try: + task.verify() + except AssertionError: + return False + return True + + def verify(self): + """Run verify on all tasks. + + The verify method is supposed to raise an AssertionError if the + task has unmet expectations, with a one-line explanation about + why this item is unmet. This method should be safe to be called + multiple times without side effects. + """ + errors = [] + for task in self._tasks: + try: + task.verify() + except AssertionError, e: + error = str(e) + if not error: + raise RuntimeError("Empty error message from %r" % task) + errors.append(error) + if errors: + message = [str(self.path)] + for error in errors: + lines = error.splitlines() + message.append("- " + lines.pop(0)) + message.extend([" " + line for line in lines]) + raise AssertionError(os.linesep.join(message)) + + def replay(self): + """Put all tasks in replay mode.""" + self._has_run = False + for task in self._tasks: + task.replay() + + def restore(self): + """Restore the state of all tasks.""" + for task in self._tasks: + task.restore() + + +class ReplayRestoreEvent(Event): + """Helper event for tasks which need replay/restore but shouldn't match.""" + + def matches(self, path): + return False + + +class Task(object): + """Element used to track one specific aspect on an event. + + A task is responsible for adding any kind of logic to an event. + Examples of that are counting the number of times the event was + made, verifying parameters if any, and so on. + """ + + def matches(self, path): + """Return true if the task is supposed to be run for the given path. + """ + return True + + def may_run(self, path): + """Return false if running this task would certainly raise an error.""" + return True + + def run(self, path): + """Perform the task item, considering that the given action happened. + """ + + def verify(self): + """Raise AssertionError if expectations for this item are unmet. + + The verify method is supposed to raise an AssertionError if the + task has unmet expectations, with a one-line explanation about + why this item is unmet. This method should be safe to be called + multiple times without side effects. + """ + + def replay(self): + """Put the task in replay mode. + + Any expectations of the task should be reset. + """ + + def restore(self): + """Restore any environmental changes made by the task. + + Verify should continue to work after this is called. + """ + + +# -------------------------------------------------------------------- +# Task implementations. + +class OnRestoreCaller(Task): + """Call a given callback when restoring.""" + + def __init__(self, callback): + self._callback = callback + + def restore(self): + self._callback() + + +class PathMatcher(Task): + """Match the action path against a given path.""" + + def __init__(self, path): + self.path = path + + def matches(self, path): + return self.path.matches(path) + +def path_matcher_recorder(mocker, event): + event.add_task(PathMatcher(event.path)) + +Mocker.add_recorder(path_matcher_recorder) + + +class RunCounter(Task): + """Task which verifies if the number of runs are within given boundaries. + """ + + def __init__(self, min, max=False): + self.min = min + if max is None: + self.max = sys.maxint + elif max is False: + self.max = min + else: + self.max = max + self._runs = 0 + + def replay(self): + self._runs = 0 + + def may_run(self, path): + return self._runs < self.max + + def run(self, path): + self._runs += 1 + if self._runs > self.max: + self.verify() + + def verify(self): + if not self.min <= self._runs <= self.max: + if self._runs < self.min: + raise AssertionError("Performed fewer times than expected.") + raise AssertionError("Performed more times than expected.") + + +class ImplicitRunCounter(RunCounter): + """RunCounter inserted by default on any event. + + This is a way to differentiate explicitly added counters and + implicit ones. + """ + +def run_counter_recorder(mocker, event): + """Any event may be repeated once, unless disabled by default.""" + if event.path.root_mock.__mocker_count__: + event.add_task(ImplicitRunCounter(1)) + +Mocker.add_recorder(run_counter_recorder) + +def run_counter_removal_recorder(mocker, event): + """ + Events created by getattr actions which lead to other events + may be repeated any number of times. For that, we remove implicit + run counters of any getattr actions leading to the current one. + """ + parent_path = event.path.parent_path + for event in mocker.get_events()[::-1]: + if (event.path is parent_path and + event.path.actions[-1].kind == "getattr"): + for task in event.get_tasks(): + if type(task) is ImplicitRunCounter: + event.remove_task(task) + +Mocker.add_recorder(run_counter_removal_recorder) + + +class MockReturner(Task): + """Return a mock based on the action path.""" + + def __init__(self, mocker): + self.mocker = mocker + + def run(self, path): + return Mock(self.mocker, path) + +def mock_returner_recorder(mocker, event): + """Events that lead to other events must return mock objects.""" + parent_path = event.path.parent_path + for event in mocker.get_events(): + if event.path is parent_path: + for task in event.get_tasks(): + if isinstance(task, MockReturner): + break + else: + event.add_task(MockReturner(mocker)) + break + +Mocker.add_recorder(mock_returner_recorder) + + +class FunctionRunner(Task): + """Task that runs a function everything it's run. + + Arguments of the last action in the path are passed to the function, + and the function result is also returned. + """ + + def __init__(self, func): + self._func = func + + def run(self, path): + action = path.actions[-1] + return self._func(*action.args, **action.kwargs) + + +class PathExecuter(Task): + """Task that executes a path in the real object, and returns the result.""" + + def __init__(self, result_callback=None): + self._result_callback = result_callback + + def get_result_callback(self): + return self._result_callback + + def run(self, path): + result = path.execute(path.root_object) + if self._result_callback is not None: + self._result_callback(result) + return result + + +class Orderer(Task): + """Task to establish an order relation between two events. + + An orderer task will only match once all its dependencies have + been run. + """ + + def __init__(self, path): + self.path = path + self._run = False + self._dependencies = [] + + def replay(self): + self._run = False + + def has_run(self): + return self._run + + def may_run(self, path): + for dependency in self._dependencies: + if not dependency.has_run(): + return False + return True + + def run(self, path): + for dependency in self._dependencies: + if not dependency.has_run(): + raise AssertionError("Should be after: %s" % dependency.path) + self._run = True + + def add_dependency(self, orderer): + self._dependencies.append(orderer) + + def get_dependencies(self): + return self._dependencies + + +class SpecChecker(Task): + """Task to check if arguments of the last action conform to a real method. + """ + + def __init__(self, method): + self._method = method + self._unsupported = False + + if method: + try: + self._args, self._varargs, self._varkwargs, self._defaults = \ + inspect.getargspec(method) + except TypeError: + self._unsupported = True + else: + if self._defaults is None: + self._defaults = () + if type(method) is type(self.run): + self._args = self._args[1:] + + def get_method(self): + return self._method + + def _raise(self, message): + spec = inspect.formatargspec(self._args, self._varargs, + self._varkwargs, self._defaults) + raise AssertionError("Specification is %s%s: %s" % + (self._method.__name__, spec, message)) + + def verify(self): + if not self._method: + raise AssertionError("Method not found in real specification") + + def may_run(self, path): + try: + self.run(path) + except AssertionError: + return False + return True + + def run(self, path): + if not self._method: + raise AssertionError("Method not found in real specification") + if self._unsupported: + return # Can't check it. Happens with builtin functions. :-( + action = path.actions[-1] + obtained_len = len(action.args) + obtained_kwargs = action.kwargs.copy() + nodefaults_len = len(self._args) - len(self._defaults) + for i, name in enumerate(self._args): + if i < obtained_len and name in action.kwargs: + self._raise("%r provided twice" % name) + if (i >= obtained_len and i < nodefaults_len and + name not in action.kwargs): + self._raise("%r not provided" % name) + obtained_kwargs.pop(name, None) + if obtained_len > len(self._args) and not self._varargs: + self._raise("too many args provided") + if obtained_kwargs and not self._varkwargs: + self._raise("unknown kwargs: %s" % ", ".join(obtained_kwargs)) + +def spec_checker_recorder(mocker, event): + spec = event.path.root_mock.__mocker_spec__ + if spec: + actions = event.path.actions + if len(actions) == 1: + if actions[0].kind == "call": + method = getattr(spec, "__call__", None) + event.add_task(SpecChecker(method)) + elif len(actions) == 2: + if actions[0].kind == "getattr" and actions[1].kind == "call": + method = getattr(spec, actions[0].args[0], None) + event.add_task(SpecChecker(method)) + +Mocker.add_recorder(spec_checker_recorder) + + +class ProxyReplacer(Task): + """Task which installs and deinstalls proxy mocks. + + This task will replace a real object by a mock in all dictionaries + found in the running interpreter via the garbage collecting system. + """ + + def __init__(self, mock): + self.mock = mock + self.__mocker_replace__ = False + + def replay(self): + global_replace(self.mock.__mocker_object__, self.mock) + + def restore(self): + global_replace(self.mock, self.mock.__mocker_object__) + + +def global_replace(remove, install): + """Replace object 'remove' with object 'install' on all dictionaries.""" + for referrer in gc.get_referrers(remove): + if (type(referrer) is dict and + referrer.get("__mocker_replace__", True)): + for key, value in list(referrer.iteritems()): + if value is remove: + referrer[key] = install + + +class Undefined(object): + + def __repr__(self): + return "Undefined" + +Undefined = Undefined() + + +class Patcher(Task): + + def __init__(self): + super(Patcher, self).__init__() + self._monitored = {} # {kind: {id(object): object}} + self._patched = {} + + def is_monitoring(self, obj, kind): + monitored = self._monitored.get(kind) + if monitored: + if id(obj) in monitored: + return True + cls = type(obj) + if issubclass(cls, type): + cls = obj + bases = set([id(base) for base in cls.__mro__]) + bases.intersection_update(monitored) + return bool(bases) + return False + + def monitor(self, obj, kind): + if kind not in self._monitored: + self._monitored[kind] = {} + self._monitored[kind][id(obj)] = obj + + def patch_attr(self, obj, attr, value): + original = obj.__dict__.get(attr, Undefined) + self._patched[id(obj), attr] = obj, attr, original + setattr(obj, attr, value) + + def get_unpatched_attr(self, obj, attr): + cls = type(obj) + if issubclass(cls, type): + cls = obj + result = Undefined + for mro_cls in cls.__mro__: + key = (id(mro_cls), attr) + if key in self._patched: + result = self._patched[key][2] + if result is not Undefined: + break + elif attr in mro_cls.__dict__: + result = mro_cls.__dict__.get(attr, Undefined) + break + if isinstance(result, object) and hasattr(type(result), "__get__"): + if cls is obj: + obj = None + return result.__get__(obj, cls) + return result + + def _get_kind_attr(self, kind): + if kind == "getattr": + return "__getattribute__" + return "__%s__" % kind + + def replay(self): + for kind in self._monitored: + attr = self._get_kind_attr(kind) + seen = set() + for obj in self._monitored[kind].itervalues(): + cls = type(obj) + if issubclass(cls, type): + cls = obj + if cls not in seen: + seen.add(cls) + unpatched = getattr(cls, attr, Undefined) + self.patch_attr(cls, attr, + PatchedMethod(kind, unpatched, + self.is_monitoring)) + self.patch_attr(cls, "__mocker_execute__", + self.execute) + + def restore(self): + for obj, attr, original in self._patched.itervalues(): + if original is Undefined: + delattr(obj, attr) + else: + setattr(obj, attr, original) + self._patched.clear() + + def execute(self, action, object): + attr = self._get_kind_attr(action.kind) + unpatched = self.get_unpatched_attr(object, attr) + try: + return unpatched(*action.args, **action.kwargs) + except AttributeError: + type, value, traceback = sys.exc_info() + if action.kind == "getattr": + # The normal behavior of Python is to try __getattribute__, + # and if it raises AttributeError, try __getattr__. We've + # tried the unpatched __getattribute__ above, and we'll now + # try __getattr__. + try: + __getattr__ = unpatched("__getattr__") + except AttributeError: + pass + else: + return __getattr__(*action.args, **action.kwargs) + raise type, value, traceback + + +class PatchedMethod(object): + + def __init__(self, kind, unpatched, is_monitoring): + self._kind = kind + self._unpatched = unpatched + self._is_monitoring = is_monitoring + + def __get__(self, obj, cls=None): + object = obj or cls + if not self._is_monitoring(object, self._kind): + return self._unpatched.__get__(obj, cls) + def method(*args, **kwargs): + if self._kind == "getattr" and args[0].startswith("__mocker_"): + return self._unpatched.__get__(obj, cls)(args[0]) + mock = object.__mocker_mock__ + return mock.__mocker_act__(self._kind, args, kwargs, object) + return method + + def __call__(self, obj, *args, **kwargs): + # At least with __getattribute__, Python seems to use *both* the + # descriptor API and also call the class attribute directly. It + # looks like an interpreter bug, or at least an undocumented + # inconsistency. + return self.__get__(obj)(*args, **kwargs) + + +def patcher_recorder(mocker, event): + mock = event.path.root_mock + if mock.__mocker_patcher__ and len(event.path.actions) == 1: + patcher = mock.__mocker_patcher__ + patcher.monitor(mock.__mocker_object__, event.path.actions[0].kind) + +Mocker.add_recorder(patcher_recorder) diff --git a/digsby/src/tests/unittests/runtests.py b/digsby/src/tests/unittests/runtests.py new file mode 100644 index 0000000..c496a4a --- /dev/null +++ b/digsby/src/tests/unittests/runtests.py @@ -0,0 +1,57 @@ +if __name__ == '__main__': + __builtins__._ = lambda s: s + +import sys +import os.path +import unittest + +# the src directory +unittest_basedir = os.path.abspath( + os.path.join(os.path.dirname(__file__), '../..')) + +def plugin_test_dirs(plugins): + '''Given a list of PluginLoader objects, returns a sequence of "test" + directories, if they exist, under each plugin's path.''' + + dirs = [] + for p in plugins: + for dir in ('unittests', 'tests'): + dir = p.path / dir + if dir.isdir(): + dirs.append(dir) + + return dirs + +def get_tests(app): + dirs = [os.path.dirname(__file__)] + plugin_test_dirs(app.plugins) + + from discover import DiscoveringTestLoader + loader = DiscoveringTestLoader() + + all_tests = unittest.TestSuite() + for rootdir in dirs: + tests = loader.discover(rootdir, pattern='*test*.py', top_level_dir=unittest_basedir) + all_tests.addTests(tests) + + return all_tests + +def run_tests(): + from tests.testapp import testapp + app = testapp() + + runner = unittest.TextTestRunner(verbosity = 2) + + all_tests = get_tests(app) + + orig_dir = os.getcwd() + os.chdir(unittest_basedir) + try: + test_result = runner.run(all_tests) + finally: + os.chdir(orig_dir) + + sys.exit(not test_result.wasSuccessful()) + +if __name__ == '__main__': + run_tests() + diff --git a/digsby/src/tests/unittests/test_decorators.py b/digsby/src/tests/unittests/test_decorators.py new file mode 100644 index 0000000..7df2fc0 --- /dev/null +++ b/digsby/src/tests/unittests/test_decorators.py @@ -0,0 +1,288 @@ +from unittest import TestCase, makeSuite, TestSuite +from peak.util.decorators import * +import sys + +def ping(log, value): + + """Class decorator for testing""" + + def pong(klass): + log.append((value,klass)) + return [klass] + + decorate_class(pong) + + +def additional_tests(): + import doctest + return doctest.DocFileSuite( + 'README.txt', + optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE, + ) + + + + + + + + + + + + + + + + + + + + +class DecoratorTests(object): +#class DecoratorTests(TestCase): + + def testAssignAdvice(self): + + log = [] + def track(f,k,v,d): + log.append((f,k,v)) + if k in f.f_locals: + del f.f_locals[k] # simulate old-style advisor + + decorate_assignment(track,frame=sys._getframe()) + test_var = 1 + self.assertEqual(log, [(sys._getframe(),'test_var',1)]) + log = [] + decorate_assignment(track,1) + test2 = 42 + self.assertEqual(log, [(sys._getframe(),'test2',42)]) + + # Try doing double duty, redefining an existing variable... + log = [] + decorate_assignment(track,1) + decorate_assignment(track,1) + + test2 = 42 + self.assertEqual(log, [(sys._getframe(),'test2',42)]*2) + + + def testAs(self): + + def f(): pass + + [decorate(lambda x: [x])] + f1 = f + + self.assertEqual(f1, [f]) + + [decorate(list, lambda x: (x,))] + f1 = f + self.assertEqual(f1, [f]) + + + def test24DecoratorMode(self): + log = [] + def track(f,k,v,d): + log.append((f,k,v)) + return v + + def foo(x): pass + + decorate_assignment(track,1)(foo) + x = 1 + + self.assertEqual(log, [(sys._getframe(),'foo',foo)]) + + def testAlreadyTracing(self): + log = [] + def my_global_tracer(frm,event,arg): + log.append(frm.f_code.co_name) + return my_local_tracer + def my_local_tracer(*args): + return my_local_tracer + + sys.settrace(my_global_tracer) # This is going to break your debugger! + self.testAssignAdvice() + sys.settrace(None) + + # And this part is going to fail if testAssignAdvice() or + # decorate_assignment change much... + self.assertEqual(log, [ + 'testAssignAdvice', + 'decorate_assignment', 'enclosing_frame', '', 'failUnlessEqual', + 'decorate_assignment', 'enclosing_frame', '', 'failUnlessEqual', + 'decorate_assignment', 'enclosing_frame', '', + 'decorate_assignment', 'enclosing_frame', '', 'failUnlessEqual', + ]) + + + + + + + +moduleLevelFrameInfo = frameinfo(sys._getframe()) + +class FrameInfoTest(TestCase): + + classLevelFrameInfo = frameinfo(sys._getframe()) + + def testModuleInfo(self): + kind,module,f_locals,f_globals = moduleLevelFrameInfo + assert kind=="module" + for d in module.__dict__, f_locals, f_globals: + assert d is globals() + + def testClassInfo(self): + kind,module,f_locals,f_globals = self.classLevelFrameInfo + assert kind=="class" + assert f_locals['classLevelFrameInfo'] is self.classLevelFrameInfo + for d in module.__dict__, f_globals: + assert d is globals() + + + def testCallInfo(self): + kind,module,f_locals,f_globals = frameinfo(sys._getframe()) + assert kind=="function call" + assert f_locals is locals() # ??? + for d in module.__dict__, f_globals: + assert d is globals() + + + def testClassExec(self): + d = {'sys':sys, 'frameinfo':frameinfo} + exec "class Foo: info=frameinfo(sys._getframe())" in d + kind,module,f_locals,f_globals = d['Foo'].info + assert kind=="class", kind + + + + + + + + +class ClassDecoratorTests(TestCase): + + def testOrder(self): + log = [] + class Foo: + ping(log, 1) + ping(log, 2) + ping(log, 3) + + # Strip the list nesting + for i in 1,2,3: + assert isinstance(Foo,list) + Foo, = Foo + + assert log == [ + (1, Foo), + (2, [Foo]), + (3, [[Foo]]), + ] + + def testOutside(self): + try: + ping([], 1) + except SyntaxError: + pass + else: + raise AssertionError( + "Should have detected advice outside class body" + ) + + def testDoubleType(self): + if sys.hexversion >= 0x02030000: + return # you can't duplicate bases in 2.3 + class aType(type,type): + ping([],1) + aType, = aType + assert aType.__class__ is type + + + + + def testSingleExplicitMeta(self): + + class M(type): pass + + class C(M): + __metaclass__ = M + ping([],1) + + C, = C + assert C.__class__ is M + + + def testMixedMetas(self): + + class M1(type): pass + class M2(type): pass + + class B1: __metaclass__ = M1 + class B2: __metaclass__ = M2 + + try: + class C(B1,B2): + ping([],1) + except TypeError: + pass + else: + raise AssertionError("Should have gotten incompatibility error") + + class M3(M1,M2): pass + + class C(B1,B2): + __metaclass__ = M3 + ping([],1) + + assert isinstance(C,list) + C, = C + assert isinstance(C,M3) + + + + + def testMetaOfClass(self): + + class metameta(type): + pass + + class meta(type): + __metaclass__ = metameta + + assert metaclass_for_bases((meta,type))==metameta + + + +class ClassyMetaTests(TestCase): + """Test subclass/instance checking of classy for Python 2.6+ ABC mixin""" + + def setUp(self): + class x(classy): pass + class y(x): pass + class cc(type(classy)): pass + self.__dict__.update(locals()) + + def test_subclassing(self): + self.failUnless(issubclass(self.x, classy)) + self.failUnless(issubclass(self.y, self.x)) + self.failIf(issubclass(self.x, self.y)) + self.failIf(issubclass(classy, self.x)) + self.failIf(issubclass(self.x, type(classy))) + + def test_instancing(self): + self.failIf(isinstance(self.x, classy)) + self.failUnless(isinstance(self.x, type(classy))) + self.failIf(isinstance(self.x(), type(classy))) + self.failIf(isinstance(object, type(classy))) + self.failIf(isinstance(self.x(),self.y)) + self.failUnless(isinstance(self.y(),self.x)) + + + + + + diff --git a/digsby/src/tests/unittests/test_digsbyprotocol.py b/digsby/src/tests/unittests/test_digsbyprotocol.py new file mode 100644 index 0000000..3177e72 --- /dev/null +++ b/digsby/src/tests/unittests/test_digsbyprotocol.py @@ -0,0 +1,106 @@ +from tests import TestCase, test_main +from digsby.DigsbyProtocol import conditional_messages + +from pyxmpp.stanza import Stanza +from libxml2 import parseDoc + +class MockAccount(object): + def __init__(self, protocol): + self.protocol = protocol + + +class TestDigsbyProtocol(TestCase): + def test_conditional_messages(self): + ''' + test handling of conditional server messages + ''' + + self.assert_raises(Exception, conditional_messages, None) + + def make_stanza(m): + return Stanza(parseDoc(m).children) + + # options sent to the conditional_messages function + default_options = dict( + revision = 20000, + all_accounts = [MockAccount(protocol = 'aim'), + MockAccount(protocol = 'yahoo')] + ) + + def test_stanza(m, **opts): + msgopts = default_options.copy() + msgopts.update(opts) + return conditional_messages(make_stanza(m), **msgopts) + + # empty conditional + empty = '''\ + + announce to all online users + test message + +''' + + self.assert_(not test_stanza(empty)) + + fail_because_no_aolmail = '''\ + + multiple + test + + aolmail + +''' + + self.assert_(test_stanza(fail_because_no_aolmail)) + + succeed_revision = '''\ + + test + abc + + 15 + 0 + +''' + + self.assert_(not test_stanza(succeed_revision)) + + succeed_revision_2 = '''\ + + test + abc + + 0 + 500000 + +''' + + self.assert_(not test_stanza(succeed_revision_2)) + + + fail_revision = '''\ + + test + abc + + 50000000 + 0 + +''' + + self.assert_(test_stanza(fail_revision)) + + fail_revision_2 = '''\ + + test + abc + + 0 + 20 + +''' + + self.assert_(test_stanza(fail_revision_2)) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_dispatch.py b/digsby/src/tests/unittests/test_dispatch.py new file mode 100644 index 0000000..55486d2 --- /dev/null +++ b/digsby/src/tests/unittests/test_dispatch.py @@ -0,0 +1,44 @@ +from tests import TestCase, test_main +from util import Storage as S +from contacts.dispatch import ContactDispatcher + +class MockProfile(object): + def __init__(self): + self.connected_accounts = [] + self.account_manager = S(connected_accounts = self.connected_accounts) + +class MockBuddy(object): + def __init__(self, name, service): + self.name = name + self.service = service + +B = MockBuddy + +class MockAccount(object): + def __init__(self, username, name): + self.username = username + self.protocol = self.name = name + self.connection = S( + protocol = name, + username = username + ) + +A = MockAccount + +class TestDispatch(TestCase): + def test_dispatch(self): + profile = MockProfile() + dispatch = ContactDispatcher(profile=profile) + + a = A('digsby13', 'aim') + b = B('digsby01', 'aim') + dispatch.add_tofrom('im', b, a) + + profile.connected_accounts[:] = [a] + _b, proto = dispatch.get_from(b) + + self.assertEquals(b, _b) + self.assertEquals(a.connection, proto) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_emoticons.py b/digsby/src/tests/unittests/test_emoticons.py new file mode 100644 index 0000000..f0e1fb9 --- /dev/null +++ b/digsby/src/tests/unittests/test_emoticons.py @@ -0,0 +1,20 @@ +from tests import TestCase, test_main +import gui.imwin.emoticons as emoticons + +class TestEmoticons(TestCase): + def test_quotes(self): + from pprint import pprint + pack = 'Yahoo Messenger' + pprint(emoticons.load_pack(pack).emoticons) + + def success(emot): + self.assertTrue('img src' in emoticons.apply_emoticons(emot, pack)) + + success(':)') + success('>:)') + success(':-"') + success(':-"') + success('>:)') + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_fileutil.py b/digsby/src/tests/unittests/test_fileutil.py new file mode 100644 index 0000000..b6f7184 --- /dev/null +++ b/digsby/src/tests/unittests/test_fileutil.py @@ -0,0 +1,35 @@ +from __future__ import with_statement +from tests import TestCase, test_main +from tempfile import NamedTemporaryFile + +from util.primitives.files import atomic_write, filecontents +from uuid import uuid4 +import os.path + +class TestFileUtil(TestCase): + def test_atomic_write(self): + temp_name = os.path.join('c:\\', 'test_abc_def.txt') + assert not os.path.isfile(temp_name) + + try: + # a fresh file: write to temporary and then rename will be used + with atomic_write(temp_name) as f: + f.write('good') + + assert filecontents(temp_name) == 'good' + + # a second time: ReplaceFile will be used on Windows + with atomic_write(temp_name) as f: + f.write('good 2') + + assert filecontents(temp_name) == 'good 2' + + + # TODO: test error during a write + + finally: + if os.path.isfile(temp_name): + os.remove(temp_name) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_fmtstr.py b/digsby/src/tests/unittests/test_fmtstr.py new file mode 100644 index 0000000..94f1d67 --- /dev/null +++ b/digsby/src/tests/unittests/test_fmtstr.py @@ -0,0 +1,59 @@ +import wx +from tests import TestCase, test_main + +test_rtf = '''{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Times New Roman;}}\r\n{\\colortbl ;\\red0\\green0\\blue0;\\red255\\green255\\blue255;}\r\n{\\*\\generator Msftedit 5.41.21.2509;}\\viewkind4\\uc1\\pard\\cf1\\highlight2\\b\\f0\\fs20 test\~abc def\\par\r\n}\r\n''' + +class TestFmtStr(TestCase): + def test_rtf(self): + from util.primitives.fmtstr import fmtstr + + s = fmtstr(rtf=test_rtf) + + self.assertEqual(s.format_as('rtf'), test_rtf) + self.assertEqual(s.format_as('html'), + 'test abc def') + + def test_plaintext(self): + from util.primitives.fmtstr import fmtstr + + txt = u'this is plaintext with an & ampersand.' + s = fmtstr(plaintext=txt) + + self.assertEqual(txt, s.format_as('plaintext')) + + self.assertEqual(u'this is plaintext with an & ampersand.', + s.format_as('xhtml')) + + def test_append(self): + from util.primitives.fmtstr import fmtstr + a = fmtstr(rtf=test_rtf) + b = a + 'foo' + + self.assertEqual(b.format_as('html'), + 'test abc deffoo') + + rtf2 = '{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Arial;}{\\f1\\fswiss\\fcharset0 Levenim MT;}}\r\n{\\colortbl ;\\red0\\green0\\blue0;\\red255\\green255\\blue255;}\r\n{\\*\\generator Msftedit 5.41.21.2509;}\\viewkind4\\uc1\\pard\\cf1\\highlight2\\f0\\fs16 test small \\b\\fs72 large bold \\i italic\\i0\\f1\\fs48\\par\r\n}\r\n' + f = fmtstr(rtf=rtf2) + msn = f.format_as('msn') + + f2 = f + 'test' + self.assertEquals(msn + 'test', f2.format_as('msn')) + + def test_singleformat(self): + from util.primitives.fmtstr import fmtstr + s = fmtstr.singleformat(u'lgkjsdg df gd fg df d', + {'foregroundcolor': wx.Colour(0, 0, 0, 255), + 'bold': False, + 'family': u'default', + 'face': u'arial', + 'italic': False, + 'backgroundcolor': wx.Colour (255, 255, 255, 255), + 'underline': False, 'size': 11}) + + # test that wxColors get turned into tuples + import simplejson + simplejson.dumps(s.asDict()) + + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_fonts.py b/digsby/src/tests/unittests/test_fonts.py new file mode 100644 index 0000000..f0d9e94 --- /dev/null +++ b/digsby/src/tests/unittests/test_fonts.py @@ -0,0 +1,28 @@ +from tests import TestCase, test_main +import wx + +class TestFonts(TestCase): + def test_font_size(self): + f = wx.Frame(None) + try: + t = wx.TextCtrl(f, style=wx.TE_RICH2) + + # ensure passing a wxFont into and out of wxTextCtrl::Get/SetStyle + # maintains its size + success, style = t.GetStyle(0) + assert success + + point_size = style.Font.PointSize + + t.SetStyle(0, t.LastPosition, style) + + success, style = t.GetStyle(0) + assert success + + self.assertEqual(style.Font.PointSize, point_size) + finally: + f.Destroy() + +if __name__ == '__main__': + test_main() + diff --git a/digsby/src/tests/unittests/test_linkify.py b/digsby/src/tests/unittests/test_linkify.py new file mode 100644 index 0000000..7ab0b01 --- /dev/null +++ b/digsby/src/tests/unittests/test_linkify.py @@ -0,0 +1,16 @@ +from tests import TestCase, test_main +from tests.testnet import linkify_test_strings +import util + +class TestLinkify(TestCase): + def test_linkify(self): + ''' + Test all the linkify strings in src/tests/testnet.py + ''' + + for url, result in linkify_test_strings: + self.expect_equal(util.linkify(url), result) + + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_logger.py b/digsby/src/tests/unittests/test_logger.py new file mode 100644 index 0000000..8a8809e --- /dev/null +++ b/digsby/src/tests/unittests/test_logger.py @@ -0,0 +1,124 @@ +''' +tests the common.logger.Logger class, which is responsible for logging messages +to disk, and for reading them back +''' + +from common.logger import Logger +from common.message import Message +from contextlib import contextmanager +from tests import TestCase, test_main +import os +import shutil +from datetime import datetime + +__all__ = ['TestLogger'] + +test_logdir = os.path.join(os.path.dirname(__file__), 'data', 'logs', 'kevin') + +class MockAccount(object): + def __init__(self, service, username): + self.name = service + self.username = username + +class MockBuddy(object): + def __init__(self, name, protocol): + self.name = name + self.protocol = protocol + self.service = protocol.name + + def increase_log_size(self, bytes): + pass + +class MockProtocol(object): + def __init__(self, name, username): + self.name = name + self.username = username + + def should_log(self, msg): + return True + +class MockConversation(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + +class TestLogger(TestCase): + def setUp(self): + TestCase.setUp(self) + self.logger = Logger(output_dir = test_logdir) + self.protocol = MockProtocol('aim', 'digsby03') + self.account = MockAccount('aim', 'digsby03') + self.self_buddy = MockBuddy('digsby03', self.protocol) + self.buddy = MockBuddy('digsby01', self.protocol) + self.convo = MockConversation(buddy = self.buddy, ischat=False, protocol=self.protocol) + + def test_log_dir_exists(self): + log_path = self.logger.pathfor(self.account, self.buddy) + self.assert_(os.path.isdir(log_path), 'directory does not exist: %r' % log_path) + + def test_read_messages(self): + history = list(self.logger.history_for(self.account, self.buddy)) + self.assert_equal(10, len(history)) + self.assert_(bool(msg) for msg in history) + + @contextmanager + def scratch_logger(self): + SCRATCH = 'scratch' # directory to store temp logs + if os.path.isdir(SCRATCH): + self.assert_(False, 'scratch directory should not exist already') + + os.makedirs(SCRATCH) + self.assert_(os.path.isdir(SCRATCH)) + try: + logger = Logger(output_dir = SCRATCH) + yield logger + finally: + shutil.rmtree(SCRATCH) + + def test_write_read_unicode(self): + with self.scratch_logger() as logger: + unicode_msg = u'\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5' + + # log a message with unicode + msg = Message(buddy = self.buddy, + message = unicode_msg, + type = 'incoming', + conversation = self.convo, + timestamp = datetime(month=12, day=25, year=2000, hour=12)) + + logger.on_message(msg) + + # check that the file is on disk + logfile = os.path.join(logger.OutputDir, r'aim\digsby03\digsby01_aim\2000-12-25.html') + self.assert_(os.path.isfile(logfile), logfile) + + history = logger.history_for(self.account, self.buddy) + + # assert that unicode was encoded and decoded properly + logmsg = list(history)[0] + self.assert_(logmsg.message == unicode_msg) + + def test_html_entities(self): + with self.scratch_logger() as logger: + + message = u'foo bar meep'.encode('xml') + + msg = Message(buddy = self.buddy, + message = message, + type = 'incoming', + conversation = self.convo, + timestamp = datetime(month=12, day=25, year=2000, hour=12)) + + logger.on_message(msg) + + # check that the file is on disk + logfile = os.path.join(logger.OutputDir, r'aim\digsby03\digsby01_aim\2000-12-25.html') + self.assert_(os.path.isfile(logfile), logfile) + print open(logfile,'rb').read() + + history = list(logger.history_for(self.account, self.buddy)) + self.assert_equal(1, len(history)) + self.assert_('<' not in history[0].message) + + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_lxml.py b/digsby/src/tests/unittests/test_lxml.py new file mode 100644 index 0000000..57265a8 --- /dev/null +++ b/digsby/src/tests/unittests/test_lxml.py @@ -0,0 +1,26 @@ +import sys +import lxml.html +import lxml.etree +from tests import TestCase, test_main + +def parse_test_doc(): + parser = lxml.html.HTMLParser(encoding='utf-8') + doc = lxml.html.document_fromstring(''' + + ''', parser = parser) + +class TestLxml(TestCase): + def test_error_log_leak(self): + if not hasattr(sys, 'gettotalrefcount'): + return + + before = sys.gettotalrefcount() + for x in xrange(50): + parse_test_doc() + after = sys.gettotalrefcount() + + self.assert_equal(before, after) + + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_mail.py b/digsby/src/tests/unittests/test_mail.py new file mode 100644 index 0000000..5a6523e --- /dev/null +++ b/digsby/src/tests/unittests/test_mail.py @@ -0,0 +1,10 @@ +from tests import TestCase, test_main +from mail.emailobj import unicode_hdr + +class TestMail(TestCase): + def test_header_decode(self): + header = r'You=?UTF8?Q?=E2=80=99?=re HOT, brian@thebdbulls.com =?UTF8?Q?=E2=80=93?= See if Someone Searched for You' + unicode_hdr(header) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_merge.py b/digsby/src/tests/unittests/test_merge.py new file mode 100644 index 0000000..ab46925 --- /dev/null +++ b/digsby/src/tests/unittests/test_merge.py @@ -0,0 +1,23 @@ +from __future__ import with_statement +from tests import TestCase, test_main + +from util.merge import merge_values, merge + +class TestMerge(TestCase): + def test_merge(self): + d = {1: 2} + merge_values(d, 3, 4); assert d == {1: 2, 3: 4} + + d = {1: 2} + merge_values(d, 1, 3); assert d == {1: 3} + + d, d2 = {1: {2: 3}}, {1: {2: 4}} + res = merge(d, d2) + assert res == {1: {2: 4}}, repr(res) + + d, d2 = {1: {2: 3}}, [(1, {2: 4})] + res = merge(d, d2) + assert res == {1: {2: 4}}, repr(res) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_messagearea.py b/digsby/src/tests/unittests/test_messagearea.py new file mode 100644 index 0000000..f141853 --- /dev/null +++ b/digsby/src/tests/unittests/test_messagearea.py @@ -0,0 +1,28 @@ +from tests import TestCase, test_main +import wx +from gui.imwin.styles import get_theme_safe + +class TestWebKit(TestCase): + def test_unicode_paths(self): + ''' + ensure that the example conversation loads correctly + ''' + + f = wx.Frame(None) + + theme = get_theme_safe('Smooth Operator', None) + from gui.pref.pg_appearance import build_example_message_area + + a = build_example_message_area(f, theme) + html = a.HTML + f.Destroy() + + + from common import profile + username = profile.username + + print 'username is', username + assert username in html, 'username %r was not in HTML:\n%r' % (username, html) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_observable.py b/digsby/src/tests/unittests/test_observable.py new file mode 100644 index 0000000..5005d2b --- /dev/null +++ b/digsby/src/tests/unittests/test_observable.py @@ -0,0 +1,151 @@ +''' +Observable Tests +''' +from __future__ import with_statement + +from tests import TestCase, test_main +from tests.testutil.findleaks import check_collected + +import wx +import util.observe as observe +import weakref, gc + +ObservableBase = observe.ObservableBase +Observable = observe.Observable +base_classes = observe.Observable + +class Car(Observable): pass +class Chevy(Car): pass +class Cav(Chevy): pass + +class TestObserver(object): + def observer(self, source, attr, old, new): + self.attr, self.old, self.new = attr, old, new + + def class_observer(self, obj, attr, old, new): + self.obj, self.attr, self.old, self.new = obj, attr, old, new + + def __call__(self): + attrs = (['obj'] if hasattr(self, 'obj') else []) + 'attr old new'.split() + return tuple(getattr(self, a) for a in attrs) + +class EventHandlerObserver(wx.EvtHandler): + def on_change(self, src, attr, old, new): + pass + +class TestObservableFunctions(TestCase): + + def setUp(self): + self.t = TestObserver() + + def testObserving(self): + a = Observable() + a.add_observer(self.t.observer) + + a.setnotify('foo', 'bar') + self.assertEqual(('foo', None, 'bar'), self.t()) + + a.remove_observer(self.t.observer) + a.setnotify('foo', 'meep') + self.assertEqual(('foo', None, 'bar'), self.t()) + + def testAttrObserving(self): + # Add an observer that's only interested in foo changes + a = Observable() + a.add_observer(self.t.observer, 'foo') + + result = ('foo', None, 'bar') + + a.setnotify('foo', 'bar') + self.assertEqual(result, self.t()) + + a.setnotify('other_attribute', 5) + self.assertEqual(result, self.t()) + + a.remove_observer(self.t.observer, 'foo') + a.setnotify('foo', 'done') + self.assertEqual(result, self.t()) + + def testWeakRefs(self): + 'Make sure observable objects can be garbage collected.' + + # To see if Observable causes memory leaks, first keep a weak reference + # to an Observable object, then add an observer to it + obj = Observable() + obj.add_observer(self.t.observer) + obj_ref = weakref.ref(obj) + self.assertEqual(obj_ref(), obj) + + obj.setnotify('color', 'red') + self.assertEqual(self.t(), ('color', None,'red')) + + # Now delete the object, and force a garbage collect. + del obj + gc.collect() + + # Our weak reference should be none--meaning the object was successfully + # garbage collected. + self.assertEqual(obj_ref(), None) + + def test_check_collected(self): + 'Make sure the @check_collected decorator reports a leak' + + class myobject(object): pass + + # should raise AssertionError + def foo(): + lst = [] + @check_collected + def fail(): + o = myobject() + lst.append(o) + return o + + self.assertRaises(AssertionError, foo) + + # should not raise AssertionError + def bar(): + @check_collected + def succeed(): + return myobject() + + bar() + + def testWeakRefGui(self): + 'Ensure GUI object observers do not leak' + + eh = EventHandlerObserver() + foo = TestObserver() + + @check_collected + def check_evthandler_method(): + o = Observable() + o.add_observer(eh.on_change) + return o + + @check_collected + def check_normal_method(): + o = Observable() + o.add_observer(foo.observer) + return o + + @check_collected + def check_lambda_with_obj(): + o = Observable() + o.add_observer(lambda *a: eh.on_change(*a), obj = eh) + return o + + + def testFreezing(self): + car = Car() + car.add_observer(self.t.observer) + + with car.frozen(): + car.setnotify('fuel', 500) + car.setnotify('miles', 12000) + car.setnotify('another', 'afdafas') + print self.t() + + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_oscar.py b/digsby/src/tests/unittests/test_oscar.py new file mode 100644 index 0000000..dc9c2e9 --- /dev/null +++ b/digsby/src/tests/unittests/test_oscar.py @@ -0,0 +1,10 @@ +from tests import TestCase, test_main +import oscar.rendezvous.chat + +class TestOscar(TestCase): + def test_oscar_chatinvite(self): + data = "\x00\n\x00\x02\x00\x01\x00\x0f\x00\x00\x00\x0c\x00\x00'\x11\x00\x19\x00\x04\x14!aol://2719:10-4-tst\x00\x00" + invite_msg, chatcookie = oscar.rendezvous.chat.unpack_chat_invite(data) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_platformmessages.py b/digsby/src/tests/unittests/test_platformmessages.py new file mode 100644 index 0000000..947a5ec --- /dev/null +++ b/digsby/src/tests/unittests/test_platformmessages.py @@ -0,0 +1,39 @@ +from tests import TestCase, test_main +import wx +import sip +import weakref +from cgui import PlatformMessageBinder, WindowSnapper +import gc + +class TestPlatformMessages(TestCase): + def test_ownership(self): + f = wx.Frame(None) + p = PlatformMessageBinder.ForWindow(f) + assert not sip.ispyowned(p) + f.Destroy(); del f; wx.GetApp().ProcessIdle(); gc.collect() + + def test_forwindow_returns_same(self): + f = wx.Frame(None) + p1 = PlatformMessageBinder.ForWindow(f) + p2 = PlatformMessageBinder.ForWindow(f) + assert p1 is p2 + f.Destroy(); del f; wx.GetApp().ProcessIdle(); gc.collect() + + def test_cycle(self): + f = wx.Frame(None) + f._binder = PlatformMessageBinder.ForWindow(f) + ref = weakref.ref(f._binder) + f.Destroy(); del f; wx.GetApp().ProcessIdle(); gc.collect() + assert ref() is None + + def test_snap(self): + f = wx.Frame(None) + f._snapper = WindowSnapper(f) + snapper = weakref.ref(f._snapper) + f.Destroy(); del f; wx.GetApp().ProcessIdle(); gc.collect() + assert snapper() is None + + +if __name__ == '__main__': + test_main() + diff --git a/digsby/src/tests/unittests/test_plura.py b/digsby/src/tests/unittests/test_plura.py new file mode 100644 index 0000000..7be8262 --- /dev/null +++ b/digsby/src/tests/unittests/test_plura.py @@ -0,0 +1,41 @@ +from tests import TestCase, test_main + +import researchdriver.driver +STARTED = researchdriver.driver.STARTED +ALREADY_RUNNING = researchdriver.driver.ALREADY_RUNNING +Driver = researchdriver.driver.Driver + +class DriverTests(TestCase): + class profile(object): + username = 'digsby' + + def setUp(self): + try: + Driver.stop() + except Exception: + pass + + def tearDown(self): + try: + Driver.stop() + except Exception: + pass + + def testStart(self): + self.failUnlessEqual(Driver.start(self.profile), STARTED, "driver failed to start") + self.failUnless(Driver.running(), "driver failed to start") + + def testAlreadyStarted(self): + self.failUnlessEqual(Driver.start(self.profile), STARTED, "driver failed to start") + self.failUnless(Driver.running(), "driver failed to start") + self.failUnlessEqual(Driver.start(self.profile), ALREADY_RUNNING, "driver did not recognize it was already started") + self.failUnless(Driver.running(), "driver failed to start") + + def testStop(self): + self.failUnlessEqual(Driver.start(self.profile), STARTED, "driver failed to start") + self.failUnless(Driver.running(), "driver failed to start") + Driver.stop() + self.failIf(Driver.running()) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_ppmd7.py b/digsby/src/tests/unittests/test_ppmd7.py new file mode 100644 index 0000000..7b8af2f --- /dev/null +++ b/digsby/src/tests/unittests/test_ppmd7.py @@ -0,0 +1,10 @@ +import util.pyppmd as ppmd +from tests import TestCase, test_main + +class TestPpmd(TestCase): + def test_encode_decode(self): + s = 'foo foo bar foo meep meep meep foo' + self.assertRaises(WindowsError, ppmd.pack, s) + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_pyxmpp.py b/digsby/src/tests/unittests/test_pyxmpp.py new file mode 100644 index 0000000..a7c69cd --- /dev/null +++ b/digsby/src/tests/unittests/test_pyxmpp.py @@ -0,0 +1,22 @@ +from tests import TestCase, test_main +from tests.debugxmlhandler import DebugXMLHandler +from pyxmpp.xmlextra import StreamReader +from jabber.threadstream import ignore_xml_error + +class TestPyxmpp(TestCase): + def test_undefined_namespace(self): + 'Make sure we ignore undefined namespace errors.' + + s = '''''' + + handler = DebugXMLHandler() + + try: + return StreamReader(handler).feed(s) + except Exception, e: + if not ignore_xml_error(e.message): + raise + +if __name__ == '__main__': + test_main() + diff --git a/digsby/src/tests/unittests/test_refs.py b/digsby/src/tests/unittests/test_refs.py new file mode 100644 index 0000000..78dc17f --- /dev/null +++ b/digsby/src/tests/unittests/test_refs.py @@ -0,0 +1,74 @@ +from tests import TestCase, test_main +from weakref import ref + +class myobj(object): + pass + +class TestRefs(TestCase): + def test_weakref_subclass(self): + + root = myobj() + def foo(): + b = myobj() + b_ref = ref(b) + b.root = root + + gui = myobj() + gui.b = b + + class refsub(ref): + __slots__ = ('strong_refs',) + + root.a = refsub(b) + b.gui = refsub(gui) + + root.a.strong_refs = [b.gui] + b.gui.strong_refs = [root.a] + return b_ref + + assert foo()() is None + + def test_weakref_callback(self): + + f = myobj() + ctx = myobj() + + def cb(wr): + ctx.called_with_ref = wr + + w = ref(f, cb) + + del f + assert w() is None + + assert ctx.called_with_ref is w + + def test_unbound_ref_callback(self): + from util.primitives.refs import unbound_ref + + foo = myobj() + ctx = myobj() + + def cb(wr): + ctx.was_called = True + + + theref = unbound_ref(foo, lambda: 42, cb=cb) + del foo + + assert ctx.was_called and theref() is None + + def test_weakref_callback_cycle(self): + '''check the assumption that a cycle involving a weakref callback keeps + the object alive''' + + foo = myobj() + def cb(f=foo): + print 'deleting foo', f + + r = ref(foo, cb) + del foo + assert r() is not None + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_rtfencoder.py b/digsby/src/tests/unittests/test_rtfencoder.py new file mode 100644 index 0000000..2030679 --- /dev/null +++ b/digsby/src/tests/unittests/test_rtfencoder.py @@ -0,0 +1,8 @@ +from tests import TestCase, test_main + +class TestRTFEncoder(TestCase): + def test_xhtml(self): + pass + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_sets.py b/digsby/src/tests/unittests/test_sets.py new file mode 100644 index 0000000..ac7605b --- /dev/null +++ b/digsby/src/tests/unittests/test_sets.py @@ -0,0 +1,760 @@ +"""Lifted from the stdlib test.test_sets module""" + +import unittest, operator, copy, pickle +from peak.events.trellis import Set +from sets import ImmutableSet +empty_set = Set() + +#============================================================================== + +class TestBasicOps(unittest.TestCase): + + def test_repr(self): + if self.repr is not None: + self.assertEqual(`self.set`, self.repr) + + def test_length(self): + self.assertEqual(len(self.set), self.length) + + def test_self_equality(self): + self.assertEqual(self.set, self.set) + + def test_equivalent_equality(self): + self.assertEqual(self.set, self.dup) + + def test_copy(self): + self.assertEqual(self.set.copy(), self.dup) + + def test_self_union(self): + result = self.set | self.set + self.assertEqual(result, self.dup) + + def test_empty_union(self): + result = self.set | empty_set + self.assertEqual(result, self.dup) + + def test_union_empty(self): + result = empty_set | self.set + self.assertEqual(result, self.dup) + + def test_self_intersection(self): + result = self.set & self.set + self.assertEqual(result, self.dup) + + def test_empty_intersection(self): + result = self.set & empty_set + self.assertEqual(result, empty_set) + + def test_intersection_empty(self): + result = empty_set & self.set + self.assertEqual(result, empty_set) + + def test_self_symmetric_difference(self): + result = self.set ^ self.set + self.assertEqual(result, empty_set) + + def checkempty_symmetric_difference(self): + result = self.set ^ empty_set + self.assertEqual(result, self.set) + + def test_self_difference(self): + result = self.set - self.set + self.assertEqual(result, empty_set) + + def test_empty_difference(self): + result = self.set - empty_set + self.assertEqual(result, self.dup) + + def test_empty_difference_rev(self): + result = empty_set - self.set + self.assertEqual(result, empty_set) + + def test_iteration(self): + for v in self.set: + self.assert_(v in self.values) + + def test_pickling(self): + p = pickle.dumps(self.set) + copy = pickle.loads(p) + self.assertEqual(self.set, copy, + "%s != %s" % (self.set, copy)) + +#------------------------------------------------------------------------------ + +class TestBasicOpsEmpty(TestBasicOps): + def setUp(self): + self.case = "empty set" + self.values = [] + self.set = Set(self.values) + self.dup = Set(self.values) + self.length = 0 + self.repr = "Set([])" + +#------------------------------------------------------------------------------ + +class TestBasicOpsSingleton(TestBasicOps): + def setUp(self): + self.case = "unit set (number)" + self.values = [3] + self.set = Set(self.values) + self.dup = Set(self.values) + self.length = 1 + self.repr = "Set([3])" + + def test_in(self): + self.failUnless(3 in self.set) + + def test_not_in(self): + self.failUnless(2 not in self.set) + +#------------------------------------------------------------------------------ + +class TestBasicOpsTuple(TestBasicOps): + def setUp(self): + self.case = "unit set (tuple)" + self.values = [(0, "zero")] + self.set = Set(self.values) + self.dup = Set(self.values) + self.length = 1 + self.repr = "Set([(0, 'zero')])" + + def test_in(self): + self.failUnless((0, "zero") in self.set) + + def test_not_in(self): + self.failUnless(9 not in self.set) + +#------------------------------------------------------------------------------ + +class TestBasicOpsTriple(TestBasicOps): + def setUp(self): + self.case = "triple set" + self.values = [0, "zero", operator.add] + self.set = Set(self.values) + self.dup = Set(self.values) + self.length = 3 + self.repr = None + +#============================================================================== + +def baditer(): + raise TypeError + yield True + +def gooditer(): + yield True + +class TestExceptionPropagation(unittest.TestCase): + """SF 628246: Set constructor should not trap iterator TypeErrors""" + + def test_instanceWithException(self): + self.assertRaises(TypeError, Set, baditer()) + + def test_instancesWithoutException(self): + # All of these iterables should load without exception. + Set([1,2,3]) + Set((1,2,3)) + Set({'one':1, 'two':2, 'three':3}) + Set(xrange(3)) + Set('abc') + Set(gooditer()) + +#============================================================================== + +class _TestSetOfSets(unittest.TestCase): + # XXX we don't support auto-conversion of mutable sets + def test_constructor(self): + return + inner = Set([1]) + outer = Set([inner]) + element = list(outer).pop() + outer.remove(inner) + self.assertEqual(type(element), ImmutableSet) + outer.add(inner) # Rebuild set of sets with .add method + outer.remove(inner) + self.assertEqual(outer, Set()) # Verify that remove worked + outer.discard(inner) # Absence of KeyError indicates working fine + + def test_constructor2(self): + inner = ImmutableSet([1]) + outer = Set([inner]) + element = list(outer).pop() + outer.remove(inner) + self.assertEqual(type(element), ImmutableSet) + outer.add(inner) # Rebuild set of sets with .add method + outer.remove(inner) + self.assertEqual(outer, Set()) # Verify that remove worked + outer.discard(inner) # Absence of KeyError indicates working fine + +#============================================================================== + +class TestBinaryOps(unittest.TestCase): + def setUp(self): + self.set = Set((2, 4, 6)) + + def test_eq(self): # SF bug 643115 + self.assertEqual(self.set, Set({2:1,4:3,6:5})) + + def test_union_subset(self): + result = self.set | Set([2]) + self.assertEqual(result, Set((2, 4, 6))) + + def test_union_superset(self): + result = self.set | Set([2, 4, 6, 8]) + self.assertEqual(result, Set([2, 4, 6, 8])) + + def test_union_overlap(self): + result = self.set | Set([3, 4, 5]) + self.assertEqual(result, Set([2, 3, 4, 5, 6])) + + def test_union_non_overlap(self): + result = self.set | Set([8]) + self.assertEqual(result, Set([2, 4, 6, 8])) + + def test_intersection_subset(self): + result = self.set & Set((2, 4)) + self.assertEqual(result, Set((2, 4))) + + def test_intersection_superset(self): + result = self.set & Set([2, 4, 6, 8]) + self.assertEqual(result, Set([2, 4, 6])) + + def test_intersection_overlap(self): + result = self.set & Set([3, 4, 5]) + self.assertEqual(result, Set([4])) + + def test_intersection_non_overlap(self): + result = self.set & Set([8]) + self.assertEqual(result, empty_set) + + def test_sym_difference_subset(self): + result = self.set ^ Set((2, 4)) + self.assertEqual(result, Set([6])) + + def test_sym_difference_superset(self): + result = self.set ^ Set((2, 4, 6, 8)) + self.assertEqual(result, Set([8])) + + def test_sym_difference_overlap(self): + result = self.set ^ Set((3, 4, 5)) + self.assertEqual(result, Set([2, 3, 5, 6])) + + def test_sym_difference_non_overlap(self): + result = self.set ^ Set([8]) + self.assertEqual(result, Set([2, 4, 6, 8])) + + def test_cmp(self): + a, b = Set('a'), Set('b') + self.assertRaises(TypeError, cmp, a, b) + + # You can view this as a buglet: cmp(a, a) does not raise TypeError, + # because __eq__ is tried before __cmp__, and a.__eq__(a) returns True, + # which Python thinks is good enough to synthesize a cmp() result + # without calling __cmp__. + self.assertEqual(cmp(a, a), 0) + + self.assertRaises(TypeError, cmp, a, 12) + self.assertRaises(TypeError, cmp, "abc", a) + +#============================================================================== + +class TestUpdateOps(unittest.TestCase): + def setUp(self): + self.set = Set((2, 4, 6)) + + def test_union_subset(self): + self.set |= Set([2]) + self.assertEqual(self.set, Set((2, 4, 6))) + + def test_union_superset(self): + self.set |= Set([2, 4, 6, 8]) + self.assertEqual(self.set, Set([2, 4, 6, 8])) + + def test_union_overlap(self): + self.set |= Set([3, 4, 5]) + self.assertEqual(self.set, Set([2, 3, 4, 5, 6])) + + def test_union_non_overlap(self): + self.set |= Set([8]) + self.assertEqual(self.set, Set([2, 4, 6, 8])) + + def test_union_method_call(self): + self.set.union_update(Set([3, 4, 5])) + self.assertEqual(self.set, Set([2, 3, 4, 5, 6])) + + def test_intersection_subset(self): + self.set &= Set((2, 4)) + self.assertEqual(self.set, Set((2, 4))) + + def test_intersection_superset(self): + self.set &= Set([2, 4, 6, 8]) + self.assertEqual(self.set, Set([2, 4, 6])) + + def test_intersection_overlap(self): + self.set &= Set([3, 4, 5]) + self.assertEqual(self.set, Set([4])) + + def test_intersection_non_overlap(self): + self.set &= Set([8]) + self.assertEqual(self.set, empty_set) + + def test_intersection_method_call(self): + self.set.intersection_update(Set([3, 4, 5])) + self.assertEqual(self.set, Set([4])) + + def test_sym_difference_subset(self): + self.set ^= Set((2, 4)) + self.assertEqual(self.set, Set([6])) + + def test_sym_difference_superset(self): + self.set ^= Set((2, 4, 6, 8)) + self.assertEqual(self.set, Set([8])) + + def test_sym_difference_overlap(self): + self.set ^= Set((3, 4, 5)) + self.assertEqual(self.set, Set([2, 3, 5, 6])) + + def test_sym_difference_non_overlap(self): + self.set ^= Set([8]) + self.assertEqual(self.set, Set([2, 4, 6, 8])) + + def test_sym_difference_method_call(self): + self.set.symmetric_difference_update(Set([3, 4, 5])) + self.assertEqual(self.set, Set([2, 3, 5, 6])) + + def test_difference_subset(self): + self.set -= Set((2, 4)) + self.assertEqual(self.set, Set([6])) + + def test_difference_superset(self): + self.set -= Set((2, 4, 6, 8)) + self.assertEqual(self.set, Set([])) + + def test_difference_overlap(self): + self.set -= Set((3, 4, 5)) + self.assertEqual(self.set, Set([2, 6])) + + def test_difference_non_overlap(self): + self.set -= Set([8]) + self.assertEqual(self.set, Set([2, 4, 6])) + + def test_difference_method_call(self): + self.set.difference_update(Set([3, 4, 5])) + self.assertEqual(self.set, Set([2, 6])) + +#============================================================================== + +class TestMutate(unittest.TestCase): + def setUp(self): + self.values = ["a", "b", "c"] + self.set = Set(self.values) + + def test_add_present(self): + self.set.add("c") + self.assertEqual(self.set, Set("abc")) + + def test_add_absent(self): + self.set.add("d") + self.assertEqual(self.set, Set("abcd")) + + def test_add_until_full(self): + tmp = Set() + expected_len = 0 + for v in self.values: + tmp.add(v) + expected_len += 1 + self.assertEqual(len(tmp), expected_len) + self.assertEqual(tmp, self.set) + + def test_remove_present(self): + self.set.remove("b") + self.assertEqual(self.set, Set("ac")) + + def test_remove_absent(self): + try: + self.set.remove("d") + self.fail("Removing missing element should have raised LookupError") + except LookupError: + pass + + def test_remove_until_empty(self): + expected_len = len(self.set) + for v in self.values: + self.set.remove(v) + expected_len -= 1 + self.assertEqual(len(self.set), expected_len) + + def test_discard_present(self): + self.set.discard("c") + self.assertEqual(self.set, Set("ab")) + + def test_discard_absent(self): + self.set.discard("d") + self.assertEqual(self.set, Set("abc")) + + def test_clear(self): + self.set.clear() + self.assertEqual(len(self.set), 0) + + def test_update_empty_tuple(self): + self.set.union_update(()) + self.assertEqual(self.set, Set(self.values)) + + def test_update_unit_tuple_overlap(self): + self.set.union_update(("a",)) + self.assertEqual(self.set, Set(self.values)) + + def test_update_unit_tuple_non_overlap(self): + self.set.union_update(("a", "z")) + self.assertEqual(self.set, Set(self.values + ["z"])) + +#============================================================================== + +class TestSubsets(unittest.TestCase): + + case2method = {"<=": "issubset", + ">=": "issuperset", + } + + reverse = {"==": "==", + "!=": "!=", + "<": ">", + ">": "<", + "<=": ">=", + ">=": "<=", + } + + def test_issubset(self): + if type(self) is TestSubsets: + return + x = self.left + y = self.right + for case in "!=", "==", "<", "<=", ">", ">=": + expected = case in self.cases + # Test the binary infix spelling. + result = eval("x" + case + "y", locals()) + self.assertEqual(result, expected) + # Test the "friendly" method-name spelling, if one exists. + if case in TestSubsets.case2method: + method = getattr(x, TestSubsets.case2method[case]) + result = method(y) + self.assertEqual(result, expected) + + # Now do the same for the operands reversed. + rcase = TestSubsets.reverse[case] + result = eval("y" + rcase + "x", locals()) + self.assertEqual(result, expected) + if rcase in TestSubsets.case2method: + method = getattr(y, TestSubsets.case2method[rcase]) + result = method(x) + self.assertEqual(result, expected) +#------------------------------------------------------------------------------ + +class TestSubsetEqualEmpty(TestSubsets): + left = Set() + right = Set() + name = "both empty" + cases = "==", "<=", ">=" + +#------------------------------------------------------------------------------ + +class TestSubsetEqualNonEmpty(TestSubsets): + left = Set([1, 2]) + right = Set([1, 2]) + name = "equal pair" + cases = "==", "<=", ">=" + +#------------------------------------------------------------------------------ + +class TestSubsetEmptyNonEmpty(TestSubsets): + left = Set() + right = Set([1, 2]) + name = "one empty, one non-empty" + cases = "!=", "<", "<=" + +#------------------------------------------------------------------------------ + +class TestSubsetPartial(TestSubsets): + left = Set([1]) + right = Set([1, 2]) + name = "one a non-empty proper subset of other" + cases = "!=", "<", "<=" + +#------------------------------------------------------------------------------ + +class TestSubsetNonOverlap(TestSubsets): + left = Set([1]) + right = Set([2]) + name = "neither empty, neither contains" + cases = "!=" + +#============================================================================== + +class TestOnlySetsInBinaryOps(unittest.TestCase): + + def test_eq_ne(self): + # Unlike the others, this is testing that == and != *are* allowed. + self.assertEqual(self.other == self.set, False) + self.assertEqual(self.set == self.other, False) + self.assertEqual(self.other != self.set, True) + self.assertEqual(self.set != self.other, True) + + def test_ge_gt_le_lt(self): + self.assertRaises(TypeError, lambda: self.set < self.other) + self.assertRaises(TypeError, lambda: self.set <= self.other) + self.assertRaises(TypeError, lambda: self.set > self.other) + self.assertRaises(TypeError, lambda: self.set >= self.other) + + self.assertRaises(TypeError, lambda: self.other < self.set) + self.assertRaises(TypeError, lambda: self.other <= self.set) + self.assertRaises(TypeError, lambda: self.other > self.set) + self.assertRaises(TypeError, lambda: self.other >= self.set) + + def test_union_update_operator(self): + try: + self.set |= self.other + except TypeError: + pass + else: + self.fail("expected TypeError") + + def test_union_update(self): + if self.otherIsIterable: + self.set.union_update(self.other) + else: + self.assertRaises(TypeError, self.set.union_update, self.other) + + def test_union(self): + self.assertRaises(TypeError, lambda: self.set | self.other) + self.assertRaises(TypeError, lambda: self.other | self.set) + if self.otherIsIterable: + self.set.union(self.other) + else: + self.assertRaises(TypeError, self.set.union, self.other) + + def test_intersection_update_operator(self): + try: + self.set &= self.other + except TypeError: + pass + else: + self.fail("expected TypeError") + + def test_intersection_update(self): + if self.otherIsIterable: + self.set.intersection_update(self.other) + else: + self.assertRaises(TypeError, + self.set.intersection_update, + self.other) + + def test_intersection(self): + self.assertRaises(TypeError, lambda: self.set & self.other) + self.assertRaises(TypeError, lambda: self.other & self.set) + if self.otherIsIterable: + self.set.intersection(self.other) + else: + self.assertRaises(TypeError, self.set.intersection, self.other) + + def test_sym_difference_update_operator(self): + try: + self.set ^= self.other + except TypeError: + pass + else: + self.fail("expected TypeError") + + def test_sym_difference_update(self): + if self.otherIsIterable: + self.set.symmetric_difference_update(self.other) + else: + self.assertRaises(TypeError, + self.set.symmetric_difference_update, + self.other) + + def test_sym_difference(self): + self.assertRaises(TypeError, lambda: self.set ^ self.other) + self.assertRaises(TypeError, lambda: self.other ^ self.set) + if self.otherIsIterable: + self.set.symmetric_difference(self.other) + else: + self.assertRaises(TypeError, self.set.symmetric_difference, self.other) + + def test_difference_update_operator(self): + try: + self.set -= self.other + except TypeError: + pass + else: + self.fail("expected TypeError") + + def test_difference_update(self): + if self.otherIsIterable: + self.set.difference_update(self.other) + else: + self.assertRaises(TypeError, + self.set.difference_update, + self.other) + + def test_difference(self): + self.assertRaises(TypeError, lambda: self.set - self.other) + self.assertRaises(TypeError, lambda: self.other - self.set) + if self.otherIsIterable: + self.set.difference(self.other) + else: + self.assertRaises(TypeError, self.set.difference, self.other) +#------------------------------------------------------------------------------ + +class TestOnlySetsNumeric(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = Set((1, 2, 3)) + self.other = 19 + self.otherIsIterable = False + +#------------------------------------------------------------------------------ + +class TestOnlySetsDict(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = Set((1, 2, 3)) + self.other = {1:2, 3:4} + self.otherIsIterable = True + +#------------------------------------------------------------------------------ + +class TestOnlySetsOperator(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = Set((1, 2, 3)) + self.other = operator.add + self.otherIsIterable = False + +#------------------------------------------------------------------------------ + +class TestOnlySetsTuple(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = Set((1, 2, 3)) + self.other = (2, 4, 6) + self.otherIsIterable = True + +#------------------------------------------------------------------------------ + +class TestOnlySetsString(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = Set((1, 2, 3)) + self.other = 'abc' + self.otherIsIterable = True + +#------------------------------------------------------------------------------ + +class TestOnlySetsGenerator(TestOnlySetsInBinaryOps): + def setUp(self): + def gen(): + for i in xrange(0, 10, 2): + yield i + self.set = Set((1, 2, 3)) + self.other = gen() + self.otherIsIterable = True + +#============================================================================== + +class TestCopying(unittest.TestCase): + + def test_copy(self): + dup = self.set.copy() + dup_list = list(dup); dup_list.sort() + set_list = list(self.set); set_list.sort() + self.assertEqual(len(dup_list), len(set_list)) + for i in range(len(dup_list)): + self.failUnless(dup_list[i] is set_list[i]) + + def test_deep_copy(self): + dup = copy.deepcopy(self.set) + ##print type(dup), `dup` + dup_list = list(dup); dup_list.sort() + set_list = list(self.set); set_list.sort() + self.assertEqual(len(dup_list), len(set_list)) + for i in range(len(dup_list)): + self.assertEqual(dup_list[i], set_list[i]) + +#------------------------------------------------------------------------------ + +class TestCopyingEmpty(TestCopying): + def setUp(self): + self.set = Set() + +#------------------------------------------------------------------------------ + +class TestCopyingSingleton(TestCopying): + def setUp(self): + self.set = Set(["hello"]) + +#------------------------------------------------------------------------------ + +class TestCopyingTriple(TestCopying): + def setUp(self): + self.set = Set(["zero", 0, None]) + +#------------------------------------------------------------------------------ + +class TestCopyingTuple(TestCopying): + def setUp(self): + self.set = Set([(1, 2)]) + +#------------------------------------------------------------------------------ + +class TestCopyingNested(TestCopying): + def setUp(self): + self.set = Set([((1, 2), (3, 4))]) + +#============================================================================== + +class TestIdentities(unittest.TestCase): + def setUp(self): + self.a = Set('abracadabra') + self.b = Set('alacazam') + + def test_binopsVsSubsets(self): + a, b = self.a, self.b + self.assert_(a - b < a) + self.assert_(b - a < b) + self.assert_(a & b < a) + self.assert_(a & b < b) + self.assert_(a | b > a) + self.assert_(a | b > b) + self.assert_(a ^ b < a | b) + + def test_commutativity(self): + a, b = self.a, self.b + self.assertEqual(a&b, b&a) + self.assertEqual(a|b, b|a) + self.assertEqual(a^b, b^a) + if a != b: + self.assertNotEqual(a-b, b-a) + + def test_summations(self): + # check that sums of parts equal the whole + a, b = self.a, self.b + self.assertEqual((a-b)|(a&b)|(b-a), a|b) + self.assertEqual((a&b)|(a^b), a|b) + self.assertEqual(a|(b-a), a|b) + self.assertEqual((a-b)|b, a|b) + self.assertEqual((a-b)|(a&b), a) + self.assertEqual((b-a)|(a&b), b) + self.assertEqual((a-b)|(b-a), a^b) + + def test_exclusion(self): + # check that inverse operations show non-overlap + a, b, zero = self.a, self.b, Set() + self.assertEqual((a-b)&b, zero) + self.assertEqual((b-a)&a, zero) + self.assertEqual((a&b)&(a^b), zero) + +#============================================================================== + + +# abstract bases +del TestBasicOps, TestOnlySetsInBinaryOps, TestCopying + +__all__ = [k for k in globals().keys() if k.startswith('Test')] +__all__.remove('TestSubsets') + + diff --git a/digsby/src/tests/unittests/test_sizers.py b/digsby/src/tests/unittests/test_sizers.py new file mode 100644 index 0000000..a70403d --- /dev/null +++ b/digsby/src/tests/unittests/test_sizers.py @@ -0,0 +1,20 @@ +from tests import TestCase, test_main +import wx + +class TestSizers(TestCase): + def test_adding_to_two_sizers(self): + f = wx.Frame(None) + p = wx.Panel(f) + c = wx.CheckBox(p, -1, 'Test') + + sz = wx.BoxSizer(wx.VERTICAL) + sz.Add(c) + + sz2 = wx.BoxSizer(wx.VERTICAL) + self.assert_raises(Exception, lambda: sz2.Add(c)) + + f.Destroy() + +if __name__ == '__main__': + test_main() + diff --git a/digsby/src/tests/unittests/test_speedups.py b/digsby/src/tests/unittests/test_speedups.py new file mode 100644 index 0000000..9489cd4 --- /dev/null +++ b/digsby/src/tests/unittests/test_speedups.py @@ -0,0 +1,20 @@ +from tests import TestCase, test_main + +class TestSpeedups(TestCase): + def test_simplejson_speedups(self): + import simplejson.encoder + import simplejson.decoder + + assert simplejson.encoder.c_make_encoder is not None + assert simplejson.encoder.c_encode_basestring_ascii is not None + assert simplejson.decoder.c_scanstring is not None + + def test_pyxmpp_speedups(self): + import _xmlextra + + def test_protocols_speedups(self): + import protocols + import _speedups + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_splash.py b/digsby/src/tests/unittests/test_splash.py new file mode 100644 index 0000000..e432982 --- /dev/null +++ b/digsby/src/tests/unittests/test_splash.py @@ -0,0 +1,52 @@ +from tests import TestCase, test_main +import digsbysplash +import config +import os.path +import gui.skin +import wx + +def _create_splash_screen(): + def res(*a): + return os.path.join(gui.skin.resourcedir(), *a) + + import cgui + bitmaps = cgui.LoginWindowBitmaps() + bitmaps.logo = wx.Bitmap(res('digsbybig.png')) + bitmaps.help = wx.Bitmap(res('skins/default/help.png')) + bitmaps.settings = wx.Bitmap(res('AppDefaults', 'gear.png')) + bitmaps.language = wx.Bitmap(res('skins/default/serviceicons/widget_trans.png')) + revision_string = ' ' + return digsbysplash.LoginWindow(None, (0, 0), bitmaps, str(revision_string), True) + + +class TestSplash(TestCase): + def test_validator(self): + 'test the splash screen validator function' + + val, reason = digsbysplash.validate_data(dict(username='digsby')) + self.expect(val) + + val, reason = digsbysplash.validate_data(dict(username='fake@hotmail.com')) + self.expect(not val) + + if config.platform == 'win': + def test_splash_windowclass(self): + '''Ensure that we can construct a splash screen, and that its window class name is + "wxWindowClassNR", since that is what the installer uses to detect if Digsby + is running.''' + + window = _create_splash_screen() + + def GetClassName(window): + import ctypes + count = 200 + class_name = ctypes.create_string_buffer(200) + assert ctypes.windll.user32.GetClassNameA(window.Handle, ctypes.byref(class_name), count) + return class_name.value + + assert GetClassName(window) == 'wxWindowClassNR' + window.Destroy() + +if __name__ == '__main__': + test_main() + diff --git a/digsby/src/tests/unittests/test_stripformatting.py b/digsby/src/tests/unittests/test_stripformatting.py new file mode 100644 index 0000000..94a9f68 --- /dev/null +++ b/digsby/src/tests/unittests/test_stripformatting.py @@ -0,0 +1,124 @@ +from tests import TestCase, test_main + +import lxml.html +from util.htmlutils import to_xhtml, remove_tags, remove_attrs, render_contents, remove_styles, transform_text + + +class TestStripFormatting(TestCase): + 'test HTML transformations' + + def test_html_and_body_tag_removed(self): + 'test AIM-style html junk removal' + + # tuples of (input, expected_output) + fragments = [ + (u'red text', + u'red text'), + + (u'test', u'test'), + (u'bare html', u'bare html'), + ] + + for original, expected in fragments: + transformed = to_xhtml(original) + self.expect_equal(expected, transformed) + + def test_html_unharmed(self): + 'test that some text fragments remain untransformed' + + # these shouldn't change + fragments = [ + 'test', + 'test', + 'test', + ' ', + '', + 'test & test', + ] + + for fragment in fragments: + transformed = to_xhtml(fragment) + self.expect_equal(fragment, transformed) + + def test_remove_tags(self): + 'test removing tags' + + # (tags to remove, input string, expected) + fragments = [ + (('b',), 'test', 'test'), + (('b',), 'test', 'test'), + (('b', 'i'), 'test foo', 'test foo'), + ((), 'test', 'test'), + ] + + for tagnames, fragment, expected in fragments: + tree = string_to_tree(fragment) + transformed = remove_tags(tree, tagnames) + self.expect_equal(expected, tree_to_string(tree)) + + def test_remove_attrs(self): + 'test removing attributes' + + tests = [ + (('bgcolor',), 'red', 'red'), + (('bgcolor',), 'red', 'red'), + (('bgcolor',), 'wut', 'wut'), + ] + + for attrs, inp, expected in tests: + tree = string_to_tree(inp) + remove_attrs(tree, attrs) + transformed = tree_to_string(tree) + self.expect_equal(expected, transformed) + + def test_remove_styles(self): + 'test removing styles' + + # (style to remove, input, expected) + tests = [ + (('background-color',), + 'foo', + 'foo', + ), + (('background-color',), + 'foo', + 'foo', + ), + (('foo',), 'abc', 'abc'), + ] + + for styles, inp, expected in tests: + tree = string_to_tree(inp) + remove_styles(tree, styles) + transformed = tree_to_string(tree) + self.expect_equal(expected, transformed) + + def test_transform_text(self): + 'test transforming text nodes' + + # (transform function, input, expected) + tests = [ + (str.upper, + 'test more test', + 'TEST MORE TEST'), + ((lambda s: s), + 'testtest', + 'testtest',), + (str.upper, '', ''), + ] + + for func, inp, expected in tests: + tree = string_to_tree(inp) + transform_text(tree, func) + self.expect_equal(expected, tree_to_string(tree)) + +def string_to_tree(s): + return lxml.html.fragment_fromstring('' + s + '') + +def tree_to_string(tree): + return render_contents(tree) + + +if __name__ == '__main__': + test_main() + diff --git a/digsby/src/tests/unittests/test_subthread_delete.py b/digsby/src/tests/unittests/test_subthread_delete.py new file mode 100644 index 0000000..c80d10c --- /dev/null +++ b/digsby/src/tests/unittests/test_subthread_delete.py @@ -0,0 +1,49 @@ +import random +from tests import TestCase, test_main +import wx +import gc + +def subthread_delete(): + app = wx.GetApp() + from threading import Thread + + for x in xrange(10): + if x % 100 == 0: print x + + num_timers = 100 + timers = [] + for x in xrange(num_timers): + t = wx.Timer() + timers.append(t) + if x != 0: + t.t = timers[x-1] + + timers[0].t = timers[-1] + for x in xrange(100): + timers[x].t2 = timers[random.randint(0, len(timers)-1)] + + + + def subthread(): + del timers[:] + gc.collect() + assert num_timers == len(app.pendingDelete) + + t = Thread(target=subthread) + t.start() + t.join() + + wx.flush_pending_delete() + assert 0 == len(app.pendingDelete) + +class TestSubthreadDelete(TestCase): + def test_subthread_delete(self): + return subthread_delete() + +if __name__ == '__main__': + from tests.testapp import testapp + with testapp(plugins=False): + subthread_delete() + + #test_main() + diff --git a/digsby/src/tests/unittests/test_tofrom.py b/digsby/src/tests/unittests/test_tofrom.py new file mode 100644 index 0000000..bc6b985 --- /dev/null +++ b/digsby/src/tests/unittests/test_tofrom.py @@ -0,0 +1,111 @@ +from tests import TestCase, test_main +import contacts.buddyliststore as bl +import contacts.tofrom as tofrom + +class Contact(object): + def __init__(self, name, service): + self.name = name + self.service = service + self.protocol = object() + + def __repr__(self): + return '' % (self.name, self.service) + +class Account(object): + def __init__(self, name, service): + self.name = name + self.service = service + self.protocol = service + self.connection = object() + + def __repr__(self): + return '' % (self.name, self.service) + +class ToFromTests(TestCase): + 'Test to/from history' + + def test_validator(self): + 'Test the to/from validator' + + should_pass = [ + bl.default_tofrom(), + {u'im': [[u'aaron@digsby.org', u'digsby', u'kevin@digsby.org', u'digsby']], + u'sms': [], + u'email': []}, + ] + + should_fail = [ + None, + [], + [[]], + {u'im': [[5, u'digsby', u'kevin@digsby.org', u'digsby']], + u'sms': [], + u'email': []}, + ] + + for entry in should_pass: + self.assert_(bl.validate_tofrom(entry)) + + for entry in should_fail: + self.assert_raises(TypeError, bl.validate_tofrom, entry) + + def test_tofrom_history_lookup(self): + ''' + To: First online so the user can choose what service they prefer + From: Last one I used so it remembers my preference (that is where storing to/from is useful) + ''' + + contact = Contact('digsby01', 'aim') + + accounts = [ + Account('digsby03', 'aim') + ] + + # check the failing case + tofrom_table = [] + assert None is tofrom.lookup_tofrom_account(contact, accounts, tofrom_table) + + # Check that when you're connected to two different AIM accounts, + # that lookup_tofrom_account returns the last one you messaged a contact + # with. + foo, bar = Account('foo', 'aim'), Account('bar', 'aim') + accounts = [foo, bar] + + tofrom_table = [('digsby01', 'aim', 'bar', 'aim')] + assert bar is tofrom.lookup_tofrom_account(contact, accounts, tofrom_table) + + tofrom_table = [('digsby01', 'aim', 'foo', 'aim')] + assert foo is tofrom.lookup_tofrom_account(contact, accounts, tofrom_table) + + # Make sure a non-matching from service is ignored. + tofrom_table = [ + ('digsby01', 'aim', 'foo', 'jabber'), + ('digsby01', 'aim', 'foo', 'aim') + ] + assert foo is tofrom.lookup_tofrom_account(contact, accounts, tofrom_table) + + # Make sure an offline from account is ignored. + accounts = [bar] + tofrom_table = [ + ('digsby01', 'aim', 'foo', 'aim'), + ('digsby01', 'aim', 'bar', 'aim'), + ] + assert bar is tofrom.lookup_tofrom_account(contact, accounts, tofrom_table) + + def test_compatible_im_accounts(self): + foo, bar = Account('foo', 'yahoo'), Account('bar', 'msn') + accounts = [foo] + contact = Contact('digsby03', 'msn') + tofrom_table = [] + + # Make sure Yahoo can talk to MSN + assert tofrom.im_service_compatible('msn', 'yahoo') + + # Make sure choose_from understands IM compatibility + self.assertEqual(foo, tofrom.choose_from(contact, accounts, tofrom_table)) + + + +if __name__ == "__main__": + test_main() + diff --git a/digsby/src/tests/unittests/test_trellis.py b/digsby/src/tests/unittests/test_trellis.py new file mode 100644 index 0000000..4b4c550 --- /dev/null +++ b/digsby/src/tests/unittests/test_trellis.py @@ -0,0 +1,1681 @@ +from test_sets import * +from peak import context +from peak.events.activity import EventLoop, TwistedEventLoop, Time, NOT_YET +from peak.events import trellis, stm, collections, activity +from peak.util.decorators import rewrap#, decorate as d +from peak.util.extremes import Max +import unittest, heapq, mocker, types, sys + +try: + import testreactor +except ImportError: + testreactor = None # either twisted or testreactor are missing +try: + import wx +except ImportError: + wx = None + +class EventLoopTestCase(unittest.TestCase): + def setUp(self): + self.state = context.new() + self.state.__enter__() + super(EventLoopTestCase, self).setUp() + self.configure_context() + + def tearDown(self): + super(EventLoopTestCase, self).tearDown() + self.state.__exit__(None, None, None) + + def configure_context(self): + pass + +class TestListener(stm.AbstractListener): + def __repr__(self): return self.name +class TestSubject(stm.AbstractSubject): + def __repr__(self): return self.name +class DummyError(Exception): pass +class UndirtyListener(TestListener): + def dirty(self): + return False + + +try: + set +except NameError: + from sets import Set as set + +if wx and False: + class TestWxEventLoop(EventLoopTestCase): + def configure_context(self): + from peak.events.activity import EventLoop, WXEventLoop + EventLoop <<= WXEventLoop + self.app = wx.PySimpleApp(redirect=False) + self.frame = wx.Frame(None) + + def testSequentialCalls(self): + log = [] + EventLoop.call(log.append, 1) + EventLoop.call(log.append, 2) + EventLoop.call(log.append, 3) + EventLoop.call(log.append, 4) + event = Time[0.00001] + def c(): + if event: + # events aren't + EventLoop.call(EventLoop.stop) + c = trellis.Cell(c) + c.value + + # This will loop indefinitely, if sub-millisecond events aren't + # rounded up to the next millisecond. + EventLoop.run() + self.frame.Destroy() + self.assertEqual(log, [1,2,3,4]) + + # XXX this should test more timing stuff, but the only way to do it + # is with a wx mock, which I haven't time for as yet. + + + + + + +if testreactor: + + class TestReactorEventLoop(EventLoopTestCase, testreactor.ReactorTestCase): + + def configure_context(self): + from peak.events.activity import Time, EventLoop + from twisted.internet import reactor + Time <<= lambda: Time() + Time.time = reactor.getTime + EventLoop <<= TwistedEventLoop + + def testSequentialCalls(self): + log = [] + EventLoop.call(log.append, 1) + EventLoop.call(log.append, 2) + EventLoop.call(log.append, 3) + EventLoop.call(log.append, 4) + + class IdleTimer(trellis.Component): + trellis.attrs( + idle_timeout = 20, + busy = False, + ) + idle_for = trellis.maintain( + lambda self: self.idle_for.begins_with(not self.busy), + initially=NOT_YET + ) + @trellis.maintain # XXX should be perform + def alarm(self): + if self.idle_for[self.idle_timeout] and EventLoop.running: + log.append(5) + EventLoop.stop() + + it = IdleTimer() + EventLoop.run() + self.assertEqual(log, [1,2,3,4,5]) + + + + + +class TestLinks(unittest.TestCase): + + def setUp(self): + self.l1 = TestListener(); self.l1.name = 'l1' + self.l2 = TestListener(); self.l1.name = 'l2' + self.s1 = TestSubject(); self.s1.name = 's1' + self.s2 = TestSubject(); self.s2.name = 's2' + self.lk11 = stm.Link(self.s1, self.l1) + self.lk12 = stm.Link(self.s1, self.l2) + self.lk21 = stm.Link(self.s2, self.l1) + self.lk22 = stm.Link(self.s2, self.l2) + + def verify_subjects(self, items): + for link, nxt, prev in items: + self.failUnless(link.next_subject is nxt) + if isinstance(link,stm.Link): + self.failUnless(link.prev_subject is prev) + + def verify_listeners(self, items): + for link, nxt, prev in items: + self.failUnless(link.next_listener is nxt) + if isinstance(link,stm.Link): + self.failUnless(link.prev_listener is prev) + + def testBreakIterSubjects(self): + it = self.l1.iter_subjects() + self.failUnless(it.next() is self.s2) + self.lk21.unlink() + self.failUnless(it.next() is self.s1) + + def testBreakIterListeners(self): + it = self.s1.iter_listeners() + self.failUnless(it.next() is self.l2) + self.lk11.unlink() + self.failUnless(it.next() is self.l1) + + + + + + + def testLinkSetup(self): + self.verify_subjects([ + (self.l1, self.lk21, None), (self.l2, self.lk22, None), + (self.lk21, self.lk11, None), (self.lk11, None, self.lk21), + (self.lk22, self.lk12, None), (self.lk12, None, self.lk22), + ]) + self.verify_listeners([ + (self.s1, self.lk12, None), (self.s2, self.lk22, None), + (self.lk22, self.lk21, self.s2), (self.lk21, None, self.lk22), + (self.lk12, self.lk11, self.s1), (self.lk11, None, self.lk12), + ]) + + def testUnlinkListenerHeadSubjectTail(self): + self.lk21.unlink() + self.verify_subjects([ + (self.l1, self.lk11, None), (self.lk11, None, None) + ]) + self.verify_listeners([ + (self.s2, self.lk22, None), (self.lk22, None, self.s2) + ]) + + def testUnlinkListenerTailSubjectHead(self): + self.lk12.unlink() + self.verify_subjects([ + (self.l2, self.lk22, None), (self.lk22, None, None), + ]) + self.verify_listeners([ + (self.s1, self.lk11, None), (self.lk11, None, self.s1), + ]) + + + +def a(f): + def g(self): + return self.ctrl.atomically(f, self) + return rewrap(f, g) + + + + + +class TestController(unittest.TestCase): + + def setUp(self): + self.ctrl = stm.Controller() + self.t0 = TestListener(); self.t0.name='t0'; + self.t1 = TestListener(); self.t1.name='t1'; self.t1.layer = 1 + self.t2 = TestListener(); self.t2.name='t2'; self.t2.layer = 2 + self.t3 = UndirtyListener(); self.t3.name='t3' + self.s1 = TestSubject(); self.s2 = TestSubject() + self.s1.name = 's1'; self.s2.name = 's2' + + def tearDown(self): + # Verify correct cleanup in all scenarios + for k,v in dict( + undo=[], managers={}, queues={}, layers=[], reads={}, writes={}, + has_run={}, destinations=None, routes=None, + current_listener=None, readonly=False, in_cleanup=False, + active=False, at_commit=[], to_retry={} + ).items(): + val = getattr(self.ctrl, k) + self.assertEqual(val, v, '%s: %r' % (k,val)) + + def testScheduleSimple(self): + t1 = TestListener(); t1.name='t1' + t2 = TestListener(); t2.name='t2' + self.assertEqual(self.ctrl.layers, []) + self.assertEqual(self.ctrl.queues, {}) + self.ctrl.schedule(t1) + self.ctrl.schedule(t2) + self.assertEqual(self.ctrl.layers, [0]) + self.assertEqual(self.ctrl.queues, {0: {t1:1, t2:1}}) + self.ctrl.cancel(t1) + self.assertEqual(self.ctrl.layers, [0]) + self.assertEqual(self.ctrl.queues, {0: {t2:1}}) + self.ctrl.cancel(t2) + # tearDown will assert that everything has been cleared + + def testThreadLocalController(self): + self.failUnless(isinstance(trellis.ctrl, stm.Controller)) + self.failUnless(isinstance(trellis.ctrl, stm.threading.local)) + + def testHeapingCancel(self): + # verify that cancelling the last listener of a layer keeps + # the 'layers' list in heap order + self.ctrl.schedule(self.t0) + self.ctrl.schedule(self.t2) + self.ctrl.schedule(self.t1) + layers = self.ctrl.layers + self.assertEqual(layers, [0, 2, 1]) + self.ctrl.cancel(self.t0) + self.assertEqual(heapq.heappop(layers), 1) + self.assertEqual(heapq.heappop(layers), 2) + self.assertEqual(self.ctrl.queues, {1: {self.t1:1}, 2: {self.t2:1}}) + self.ctrl.queues.clear() + + def testDoubleAndMissingCancelOrSchedule(self): + self.ctrl.schedule(self.t2) + self.ctrl.cancel(self.t0) + self.ctrl.cancel(self.t2) + self.ctrl.cancel(self.t2) + self.ctrl.schedule(self.t1) + self.assertEqual(self.ctrl.queues, {1: {self.t1:1}}) + self.ctrl.schedule(self.t1) + self.assertEqual(self.ctrl.queues, {1: {self.t1:1}}) + self.ctrl.cancel(self.t1) + + def testScheduleLayerBump(self): + # listener layer must be at least source layer + 1 + self.ctrl.schedule(self.t1) + self.ctrl.schedule(self.t1, 0) + self.assertEqual(self.ctrl.queues, {1: {self.t1:1}}) + self.ctrl.schedule(self.t1, 1) + self.assertEqual(self.ctrl.queues, {2: {self.t1:1}}) + self.assertEqual(self.t1.layer, 2) + self.ctrl.cancel(self.t1) + + @a + def testScheduleRollback(self): + # when running atomically, scheduling is an undo-logged operation + self.ctrl.schedule(self.t1) + self.ctrl.rollback_to(0) + + def testCleanup(self): + self.ctrl.schedule(self.t0) + def raiser(): + # XXX need to actually run one rule, plus start another w/error + raise DummyError + try: + self.ctrl.atomically(self.runAs, self.t0, raiser) + except DummyError: + pass + + def testSubjectsMustBeAtomic(self): + self.assertRaises(AssertionError, self.ctrl.lock, self.s1) + self.assertRaises(AssertionError, self.ctrl.used, self.s1) + self.assertRaises(AssertionError, self.ctrl.changed, self.s1) + + @a + def testLockAcquiresManager(self): + class Dummy: + def __enter__(*args): pass + def __exit__(*args): pass + mgr = self.s1.manager = Dummy() + self.ctrl.lock(self.s1) + self.assertEqual(self.ctrl.managers, {mgr:0}) + self.ctrl.lock(self.s2) + self.assertEqual(self.ctrl.managers, {mgr:0}) + + @a + def testReadWrite(self): + self.ctrl.used(self.s1) + self.ctrl.changed(self.s2) + self.assertEqual(self.ctrl.reads, {}) + self.assertEqual(self.ctrl.writes, {}) + self.ctrl.current_listener = self.t0 + self.ctrl.used(self.s1) + self.ctrl.changed(self.s2) + self.assertEqual(self.ctrl.reads, {self.s1:1}) + self.assertEqual(self.ctrl.writes, {self.s2:self.t0}) + self.ctrl.reads.clear() # these would normally be handled by + self.ctrl.writes.clear() # the run() method's try/finally + self.ctrl.current_listener = None # reset + + @a + def testNoReadDuringCommit(self): + self.ctrl.readonly = True + self.assertRaises(RuntimeError, self.ctrl.changed, self.s1) + self.ctrl.readonly = False # normally reset by ctrl.run_rule() + + @a + def testRecalcOnWrite(self): + stm.Link(self.s1, self.t0) + stm.Link(self.s2, self.t1) + stm.Link(self.s2, self.t0) + self.ctrl.current_listener = self.t1 + self.ctrl.changed(self.s1) + self.ctrl.changed(self.s2) + self.assertEqual(self.ctrl.writes, {self.s1:self.t1, self.s2:self.t1}) + sp = self.ctrl.savepoint(); self.ctrl.has_run[self.t1] = self.t1 + self.ctrl._process_writes(self.t1) + # Only t0 is notified, not t1, since t1 is the listener + self.assertEqual(self.ctrl.queues, {2: {self.t0:1}}) + self.ctrl.rollback_to(sp) + self.ctrl.current_listener = None # reset + + @a + def testDependencyUpdatingAndUndo(self): + stm.Link(self.s1, self.t0) + s3 = TestSubject() + stm.Link(s3, self.t0) + self.assertEqual(list(self.t0.iter_subjects()), [s3, self.s1]) + self.ctrl.current_listener = self.t0 + self.ctrl.used(self.s1) + self.ctrl.used(self.s2) + sp = self.ctrl.savepoint() + self.ctrl._process_reads(self.t0) + self.assertEqual(list(self.t0.iter_subjects()), [self.s2, self.s1]) + self.ctrl.rollback_to(sp) + self.assertEqual(list(self.t0.iter_subjects()), [s3, self.s1]) + self.ctrl.current_listener = None # reset + + + + + def runAs(self, listener, rule): + listener.run = rule + self.ctrl.run_rule(listener) + + @a + def testIsRunningAndHasRan(self): + def rule(): + self.assertEqual(self.ctrl.current_listener, self.t1) + self.assertEqual(self.ctrl.has_run, {self.t1: 0}) + sp = self.ctrl.savepoint() + self.runAs(self.t1, rule) + self.assertEqual(self.ctrl.current_listener, None) + self.assertEqual(self.ctrl.has_run, {self.t1: 0}) + + @a + def testIsRunningButHasNotRan(self): + def rule(): + self.assertEqual(self.ctrl.current_listener, self.t1) + self.assertEqual(self.ctrl.has_run, {}) + sp = self.ctrl.savepoint() + self.t1.run = rule; self.ctrl.initialize(self.t1) # uninit'd rule + self.assertEqual(self.ctrl.current_listener, None) + self.assertEqual(self.ctrl.has_run, {}) + + @a + def testScheduleUndo(self): + sp = self.ctrl.savepoint() + self.ctrl.schedule(self.t2) + self.assertEqual(self.ctrl.queues, {2: {self.t2:1}}) + self.ctrl.rollback_to(sp) + self.assertEqual(self.ctrl.queues, {}) + + def testNestedReadOnly(self): + log = [] + def aRule(): + log.append(trellis.ctrl.readonly); return 1 + c1 = trellis.Cell(aRule) + c2 = trellis.Cell(lambda: c1.value * aRule()) + c3 = trellis.Performer(lambda: c2.value) + self.assertEqual(log, [True, True]) + + @a + def testWriteProcessingInRun(self): + stm.Link(self.s1, self.t0) + stm.Link(self.s2, self.t1) + stm.Link(self.s2, self.t3) + stm.Link(self.s2, self.t0) + def rule(): + self.ctrl.changed(self.s1) + self.ctrl.changed(self.s2) + self.assertEqual(self.ctrl.writes, {self.s1:self.t1, self.s2:self.t1}) + self.runAs(self.t1, rule) + # Only t0 is notified, not t1, since t1 is the listener & t3 is !dirty + self.assertEqual(self.ctrl.writes, {}) + self.assertEqual(self.ctrl.queues, {2: {self.t0:1}}) + self.ctrl.cancel(self.t0) + + @a + def testReadProcessingInRun(self): + stm.Link(self.s1, self.t0) + s3 = TestSubject() + stm.Link(s3, self.t0) + self.assertEqual(list(self.t0.iter_subjects()), [s3, self.s1]) + def rule(): + self.ctrl.used(self.s1) + self.ctrl.used(self.s2) + self.assertEqual(self.ctrl.reads, {self.s1:1, self.s2:1}) + self.runAs(self.t0, rule) + self.assertEqual(self.ctrl.reads, {}) + self.assertEqual(list(self.t0.iter_subjects()), [self.s2, self.s1]) + + @a + def testReadOnlyDuringMax(self): + def rule(): + self.assertEqual(self.ctrl.readonly, True) + self.t0.layer = Max + self.assertEqual(self.ctrl.readonly, False) + self.runAs(self.t0, rule) + self.assertEqual(self.ctrl.readonly, False) + + + + @a + def testRunClearsReadWriteOnError(self): + def rule(): + self.ctrl.used(self.s1) + self.ctrl.changed(self.s2) + self.assertEqual(self.ctrl.reads, {self.s1:1}) + self.assertEqual(self.ctrl.writes, {self.s2:1}) + try: + self.runAs(self.t0, rule) + except DummyError: + pass + else: + raise AssertionError("Error should've propagated") + self.assertEqual(self.ctrl.reads, {}) + self.assertEqual(self.ctrl.writes, {}) + + @a + def testSimpleCycle(self): + stm.Link(self.s1, self.t1) + stm.Link(self.s2, self.t2) + def rule0(): + self.ctrl.used(self.s1) + self.ctrl.changed(self.s1) + def rule1(): + self.ctrl.used(self.s1) + self.ctrl.changed(self.s2) + def rule2(): + self.ctrl.used(self.s2) + self.ctrl.changed(self.s1) + self.runAs(self.t0, rule0) + self.runAs(self.t1, rule1) + self.runAs(self.t2, rule2) + try: + self.ctrl._retry() + except stm.CircularityError, e: + self.assertEqual(e.args[0], {self.t0: set([self.t1]), + self.t1: set([self.t2]), self.t2: set([self.t0, self.t1])}) + self.assertEqual(e.args[1], (self.t1, self.t2, self.t0)) + else: + raise AssertionError("Should've caught a cycle") + + @a + def testSimpleRetry(self): + def rule(): + pass + self.runAs(self.t0, rule) + self.runAs(self.t1, rule) + self.runAs(self.t2, rule) + self.assertEqual(set(self.ctrl.has_run),set([self.t0,self.t1,self.t2])) + self.ctrl.to_retry[self.t1]=1 + self.ctrl._retry() + self.assertEqual(set(self.ctrl.has_run), set([self.t0])) + self.ctrl.to_retry[self.t0]=1 + self.ctrl._retry() + + + @a + def testNestedNoRetry(self): + def rule0(): + self.t1.run=rule1; self.ctrl.initialize(self.t1) + def rule1(): + pass + self.runAs(self.t2, rule1) + self.runAs(self.t0, rule0) + self.ctrl.schedule(self.t1) + self.assertEqual(self.ctrl.to_retry, {}) + self.assertEqual( + set(self.ctrl.has_run), set([self.t0, self.t2]) + ) + self.assertEqual(self.ctrl.queues, {1: {self.t1:1}}) + + + def testRunScheduled(self): + log = [] + self.t1.run = lambda: log.append(True) + def go(): + self.ctrl.schedule(self.t1) + self.ctrl.atomically(go) + self.assertEqual(log, [True]) + + + + def testRollbackReschedules(self): + sp = [] + def rule0(): + self.ctrl.rollback_to(sp[0]) + self.assertEqual(self.ctrl.queues, {0: {self.t0:1}}) + self.ctrl.cancel(self.t0) + self.t0.run = rule0 + def go(): + self.ctrl.schedule(self.t0) + sp.append(self.ctrl.savepoint()) + self.ctrl.atomically(go) + + def testManagerCantCreateLoop(self): + class Mgr: + def __enter__(self): pass + def __exit__(*args): + self.ctrl.schedule(self.t1) + log = [] + def rule1(): + log.append(True) + self.t1.run = rule1 + self.t0.run = lambda:self.ctrl.manage(Mgr()) + self.ctrl.atomically(self.ctrl.schedule, self.t0) + self.assertEqual(log, []) + self.ctrl.atomically(lambda:None) + self.assertEqual(log, [True]) + + @a + def testNotifyOnChange(self): + stm.Link(self.s2, self.t2) + stm.Link(self.s2, self.t3) + self.ctrl.changed(self.s2) + self.ctrl.current_listener = self.t0 + self.ctrl.changed(self.s2) + self.assertEqual(self.ctrl.queues, {2: {self.t2:1}}) + self.ctrl.cancel(self.t2) + self.ctrl.writes.clear() + self.ctrl.current_listener = None # reset + + + + def testCommitCanLoop(self): + log=[] + def go(): + log.append(True) + self.t0.run = go + self.ctrl.atomically(self.ctrl.on_commit, self.ctrl.schedule, self.t0) + self.assertEqual(log,[True]) + + @a + def testNoUndoDuringUndo(self): + def undo(): + self.ctrl.on_undo(redo) + def redo(): + raise AssertionError("Should not be run") + self.ctrl.on_undo(undo) + self.ctrl.rollback_to(0) + + @a + def testReentrantRollbackToMinimumTarget(self): + sp = self.ctrl.savepoint() + # these 2 rollbacks will be ignored, since they target a higher sp. + # note that both are needed for testing, as one is there to potentially + # set a new target, and the other is there to make the offset wrong if + # the rollback stops prematurely. + self.ctrl.on_undo(self.ctrl.rollback_to, sp+100) + self.ctrl.on_undo(self.ctrl.rollback_to, sp+100) + sp2 = self.ctrl.savepoint() + + # ensure that there's no way this test can pass unless rollback_to + # notices re-entrant invocations (because it would overflow the stack) + for i in range(sys.getrecursionlimit()*2): + # request a rollback all the way to 0; this target should be used + # in place of the sp2 target or sp+100 targets, since it will be + # the lowest target encountered during the rollback. + self.ctrl.on_undo(self.ctrl.rollback_to, sp) + + self.ctrl.rollback_to(sp2) # ask to rollback to posn 2 + self.assertEqual(self.ctrl.savepoint(), sp) # but should rollback to 0 + + + + @a + def testNestedRule(self): + def rule1(): + self.assertEqual(set(self.ctrl.has_run), set([self.t0, self.t1])) + self.assertEqual(self.ctrl.current_listener, self.t1) + self.ctrl.used(self.s1) + self.ctrl.changed(self.s2) + self.assertEqual(self.ctrl.reads, {self.s1:1}) + self.assertEqual(self.ctrl.writes, {self.s2:self.t1}) + self.t2.run=rule2; self.ctrl.initialize(self.t2) + self.assertEqual(set(self.ctrl.has_run), set([self.t0, self.t1])) + self.assertEqual(self.ctrl.current_listener, self.t1) + self.assertEqual(self.ctrl.reads, {self.s1:1}) + self.assertEqual(self.ctrl.writes, {self.s2:self.t1, s3:self.t2}) + + def rule2(): + self.assertEqual(set(self.ctrl.has_run), set([self.t0, self.t1])) + self.assertEqual(self.ctrl.current_listener, self.t2) + self.assertEqual(self.ctrl.reads, {}) + self.assertEqual(self.ctrl.writes, {self.s2:self.t1}) + self.ctrl.used(self.s2) + self.ctrl.changed(s3) + + def rule0(): + pass + + s3 = TestSubject(); s3.name = 's3' + self.runAs(self.t0, rule0) + self.runAs(self.t1, rule1) + self.assertEqual( + set(self.ctrl.has_run), + set([self.t1, self.t0]) # t2 was new, so doesn't show + ) + self.assertEqual(list(self.t1.iter_subjects()), [self.s1]) + self.assertEqual(list(self.t2.iter_subjects()), [self.s2]) + self.ctrl.rollback_to(self.ctrl.has_run[self.t1]) # should undo both t1/t2 + + + + + + def testUndoLogSpansMultipleRecalcs(self): + c1 = trellis.Value(False, discrete=True) + c2 = trellis.Cell(lambda: (c1.value, log.append(trellis.savepoint()))) + log = []; c2.value; log = []; c1.value = True + self.failUnless(len(log)==2 and log[1]>log[0], log) + + def testUndoPostCommitCancelsUndoOfCommitSchedule(self): + c1 = trellis.Value(False, discrete=True) + def c2(): + c1.value + log.append(trellis.savepoint()) + if len(log)==2: + raise DummyError + c2 = trellis.Cell(c2) + log = []; c2.value; log = []; + # This will raise a different error if undoing the on-commit stack + # causes an underflow: + self.assertRaises(DummyError, setattr, c1, 'value', True) + + +class TestTime(unittest.TestCase): + + def testIndependentNextEventTime(self): + # Ensure that next_event_time() never returns a *past* time + t = Time() + t.auto_update = False + t20 = t[20] + t40 = t[40] + @trellis.Cell + def check_reached(): + t.reached(t20) + t.reached(t40) + nt = t.next_event_time(True) + self.failIf(nt is not None and nt<=0) + check_reached.value + t.advance(25) + t.advance(15) + + + + +class TestCells(mocker.MockerTestCase): + + ctrl = stm.ctrl + def tearDown(self): + # make sure the old controller is back + trellis.install_controller(self.ctrl) + + def testValueBasics(self): + self.failUnless(issubclass(trellis.Value, trellis.AbstractCell)) + self.failUnless(issubclass(trellis.Value, stm.AbstractSubject)) + v = trellis.Value() + self.assertEqual(v.value, None) + self.assertEqual(v._set_by, trellis._sentinel) + self.assertEqual(v._reset, trellis._sentinel) + v.value = 21 + self.assertEqual(v._set_by, trellis._sentinel) + + @a + def testValueUndo(self): + v = trellis.Value(42) + self.assertEqual(v.value, 42) + sp = self.ctrl.savepoint() + v.value = 43 + self.assertEqual(v.value, 43) + self.ctrl.rollback_to(sp) + self.assertEqual(v.value, 42) + + @a + def testValueUsed(self): + v = trellis.Value(42) + ctrl = self.mocker.replace(self.ctrl) #'peak.events.stm.ctrl') + ctrl.used(v) + self.mocker.replay() + trellis.install_controller(ctrl) + self.assertEqual(v.value, 42) + + def testDiscrete(self): + v = trellis.Value(None, True) + v.value = 42 + self.assertEqual(v.value, None) + + def testValueChanged(self): + v = trellis.Value(42) + old_ctrl, ctrl = self.ctrl, self.mocker.replace(self.ctrl) + ctrl.lock(v) + ctrl.changed(v) + self.mocker.replay() + trellis.install_controller(ctrl) + v.value = 43 + self.assertEqual(v.value, 43) + + def testValueUnchanged(self): + v = trellis.Value(42) + ctrl = self.mocker.replace(self.ctrl) + ctrl.lock(v) + mocker.expect(ctrl.changed(v)).count(0) + self.mocker.replay() + trellis.install_controller(ctrl) + v.value = 42 + self.assertEqual(v.value, 42) + + @a + def testValueSetLock(self): + v = trellis.Value(42) + v.value = 43 + self.assertEqual(v.value, 43) + self.assertEqual(v._set_by, None) + def go(): + v.value = 99 + t = TestListener(); t.name = 't' + t.run = go + self.assertRaises(trellis.InputConflict, self.ctrl.run_rule, t) + self.assertEqual(v.value, 43) + def go(): + v.value = 43 + t = TestListener(); t.name = 't' + t.run = go + self.ctrl.run_rule(t) + self.assertEqual(v.value, 43) + + + + def testReadOnlyCellBasics(self): + log = [] + c = trellis.Cell(lambda:log.append(1)) + self.failUnless(type(c) is trellis.ReadOnlyCell) + c.value + self.assertEqual(log,[1]) + c.value + self.assertEqual(log,[1]) + + def testDiscreteValue(self): + log = [] + v = trellis.Value(False, True) + c = trellis.Cell(lambda: log.append(v.value)) + self.assertEqual(log,[]) + c.value + self.assertEqual(log,[False]) + del log[:] + v.value = True + self.assertEqual(log, [True, False]) + self.assertEqual(v.value, False) + del log[:] + v.value = False + self.assertEqual(log, []) + + def testCellConstructor(self): + self.failUnless(type(trellis.Cell(value=42)) is trellis.Value) + self.failUnless(type(trellis.Cell(lambda:42)) is trellis.ReadOnlyCell) + self.failUnless(type(trellis.Cell(lambda:42, value=42)) is trellis.Cell) + + def testRuleChain(self): + v = trellis.Value(0) + log = [] + c1 = trellis.Cell(lambda:int(v.value/2)) + c2 = trellis.Cell(lambda:log.append(c1.value)) + c2.value + self.assertEqual(log, [0]) + v.value = 1 + self.assertEqual(log, [0]) + v.value = 2 + self.assertEqual(log, [0, 1]) + + def testConstant(self): + for v in (42, [57], "blah"): + c = trellis.Constant(v) + self.assertEqual(c.value, v) + self.assertEqual(c.get_value(), v) + self.failIf(hasattr(c,'set_value')) + self.assertRaises(AttributeError, setattr, c, 'value', v) + self.assertEqual(repr(c), "Constant(%r)" % (v,)) + + def testRuleToConstant(self): + log = [] + def go(): + log.append(1) + return 42 + c = trellis.Cell(go) + self.assertEqual(c.value, 42) + self.assertEqual(log, [1]) + self.failUnless(isinstance(c, trellis.ConstantRule)) + self.assertEqual(repr(c), "Constant(42)") + self.assertEqual(c.value, 42) + self.assertEqual(c.get_value(), 42) + self.assertEqual(c.rule, None) + self.assertEqual(log, [1]) + self.failIf(c.dirty()) + c.__class__ = trellis.ReadOnlyCell # transition must be reversible to undo + self.failIf(isinstance(c, trellis.ConstantRule)) + + def testModifierIsAtomic(self): + log = [] + @trellis.modifier + def do_it(): + self.failUnless(self.ctrl.active) + self.assertEqual(self.ctrl.current_listener, None) + log.append(True) + return log + rv = do_it() + self.failUnless(rv is log) + self.assertEqual(log, [True]) + + + + @a + def testModifierAlreadyAtomic(self): + log = [] + @trellis.modifier + def do_it(): + self.failUnless(self.ctrl.active) + self.assertEqual(self.ctrl.current_listener, None) + log.append(True) + return log + rv = do_it() + self.failUnless(rv is log) + self.assertEqual(log, [True]) + + @a + def testModifierFromCell(self): + v1, v2 = trellis.Value(42), trellis.Value(99) + @trellis.modifier + def do_it(): + v1.value = v1.value * 2 + self.assertEqual(self.ctrl.reads, {v1:1}) + def rule(): + v2.value + do_it() + self.assertEqual(self.ctrl.reads, {v2:1}) + trellis.Cell(rule).value + self.assertEqual(v1.value, 84) + + def testDiscreteToConstant(self): + log = [] + c1 = trellis.ReadOnlyCell(lambda:True, False, True) + c2 = trellis.Cell(lambda:log.append(c1.value)) + c2.value + self.assertEqual(log, [True, False]) + self.failUnless(isinstance(c1, trellis.ConstantRule)) + + + + + + + + def testReadWriteCells(self): + C = trellis.Cell(lambda: (F.value-32) * 5.0/9, -40) + F = trellis.Cell(lambda: (C.value * 9.0)/5 + 32, -40) + self.assertEqual(C.value, -40) + self.assertEqual(F.value, -40) + C.value = 0 + self.assertEqual(C.value, 0) + self.assertEqual(F.value, 32) + + def testSelfDependencyDoesNotIncreaseLayer(self): + c1 = trellis.Value(23) + c2 = trellis.Cell(lambda: c1.value + c2.value, 0) + self.assertEqual(c2.value, 23) + self.assertEqual(c2.layer, 1) + c1.value = 19 + self.assertEqual(c2.value, 42) + self.assertEqual(c2.layer, 1) + + def testSettingOccursForEqualObjects(self): + d1 = {}; d2 = {} + c1 = trellis.Value() + c1.value = d1 + self.failUnless(c1.value is d1) + c1.value = d2 + self.failUnless(c1.value is d2) + + def testRepeat(self): + def counter(): + if counter.value == 10: + return counter.value + trellis.repeat() + return counter.value + 1 + counter = trellis.ReadOnlyCell(counter, 1) + self.assertEqual(counter.value, 10) + + + + + + + + @a + def testPartialRollbackList(self): + c1 = trellis.Cell(value=42) + l = trellis.List() + l.append(1) + self.assertEqual(l.future, [1]) + sp = self.ctrl.savepoint() + self.ctrl.change_attr(self.ctrl, 'current_listener', c1) + l.append(2) + self.assertEqual(l.future, [1, 2]) + self.ctrl.rollback_to(sp) + self.assertEqual(l.future, [1]) + + @a + def testPartialRollbackDict(self): + c1 = trellis.Cell(lambda:None) + d = trellis.Dict() + d[1] = 2 + self.assertEqual(d.added, {1:2}) + sp = self.ctrl.savepoint() + self.ctrl.change_attr(self.ctrl, 'current_listener', c1) + d[2] = 3 + self.assertEqual(d.added, {1:2, 2:3}) + self.ctrl.rollback_to(sp) + self.assertEqual(d.added, {1:2}) + + @a + def testPartialRollbackSet(self): + c1 = trellis.Cell(lambda:None) + s = trellis.Set() + s.add(1) + self.assertEqual(list(s.added), [1]) + sp = self.ctrl.savepoint() + self.ctrl.change_attr(self.ctrl, 'current_listener', c1) + s.add(2) + self.assertEqual(list(s.added), [1, 2]) + self.ctrl.rollback_to(sp) + self.assertEqual(list(s.added), [1]) + + + + def testSetAfterSchedule(self): + def A(): + B.value + C.value + + A = trellis.Cell(A, None) + + @trellis.Cell + def B(): + A.value = C.value + + C = trellis.Value() + + A.value + C.value = 1 + + def run_modifier_and_rule(self, func, rule): + @self.ctrl.atomically + def go(): + self.ctrl.schedule(trellis.Cell(rule)) + func.sp = self.ctrl.savepoint() + trellis.modifier(func)() + + + + + + + + + + + + + + + + + + + + def _testNonterminatingXXX(self): + + class InfiniteLoop(trellis.Component): + trellis.attrs(v1=False, v2=False) + + @trellis.maintain + def a(self): + #print 'A' + if self.v1: + self.v2 + return True + + @trellis.maintain + def b(self): + #print 'B' + if self.v1: + self.a + self.v2 = True + + comp = InfiniteLoop() + comp.v1 = True + + + + + + + + + + + + + + + + + + + + + def _testFalseCircularityXXX(self): + + class FalseCircularity(trellis.Component): + trellis.attrs(v1=False, v2=False) + + @trellis.maintain + def a(self): + if self.v1: + self.v2 + return True + + @trellis.maintain + def b(self): + if self.v1: + self.v2 = True + else: + self.a + + comp = FalseCircularity() + comp.v1 = True + + # there's no actual circularity, but the correct order can't be found + + + + + + + + + + + + + + + + + + + + def testSensorRollback(self): + connector = trellis.Connector( + connect=lambda sensor:None, disconnect=lambda sensor, key: None + ) + + sensor = trellis.Cell(connector) + + class SensorInitUndoTest(trellis.Component): + trellis.attrs(v1=False) + + @trellis.maintain + def a(self): + if self.v1: + return _Comp() + + @trellis.maintain + def b(self): + if self.v1: + self.a + + class _Comp(trellis.Component): + @trellis.maintain + def c(self): + sensor.value + + comp = SensorInitUndoTest() + comp.__cells__['a'].layer = comp.__cells__['b'].layer + 1 + comp.v1 = True + self.failUnless(sensor.next_listener() is comp.a.__cells__['c']) + self.failUnless(sensor.listening is not trellis.NOT_GIVEN) + + + + + + + + + + + + def testSetShouldOverrideInitialCalculatedValue(self): + class C(trellis.Component): + def calc(self): + return 0 + calc = trellis.maintain(calc, optional=True, name='calc') + @trellis.maintain + def getx(self): + self.calc + @trellis.maintain + def set(self): + # This should not conflict with .calc setting itself to 0 + self.calc = 1 + def __init__(self): + self.getx + self.set + c = C() + + def testMakeDuringPerform(self): + class C1(trellis.Component): + x = trellis.attr() + @trellis.maintain + def rule(self): + self.x = 1 + + class C2(trellis.Component): + c1 = trellis.make(C1, name='c1') + @trellis.compute + def calc(self): + return self.c1.x + C2().calc + + def __testMaintainReassign(self): + class C(trellis.Component): + x = trellis.attr() + @trellis.maintain + def rule(self): + self.x = 10 + @trellis.atomically + def test(): + C(x = 1) + + def testFalsePositiveDepCycle(self): + + c1 = trellis.Cell(value=1) + + @trellis.Cell + def c2(): + return c1.value+1 + + @trellis.Cell + def c3(): + return c1.value+c2.value + + self.assertEqual(c3.value, 3) + + @trellis.Cell + def c5(): + c1.value = 27 + + @trellis.atomically + def doit(): + c5.value + for c in c2, c3: + trellis.ctrl.has_run.setdefault(c, 1) + trellis.on_undo(trellis.ctrl.has_run.pop, c) + trellis.ctrl.to_retry.setdefault(c, 1) + trellis.on_undo(trellis.ctrl._unrun, c2, [c3]) + trellis.ctrl._retry() + + + + + + + + + + + + + + +class TestDefaultEventLoop(unittest.TestCase): + + def setUp(self): + self.loop = EventLoop() + self.ctrl = trellis.ctrl + + def testCallAndPoll(self): + log = [] + self.loop.call(log.append, 1) + self.loop.call(log.append, 2) + self.assertEqual(log, []) + self.loop.poll() + self.assertEqual(log, [1]) + self.loop.poll() + self.assertEqual(log, [1, 2]) + self.loop.poll() + self.assertEqual(log, [1, 2]) + + @a + def testLoopIsNonAtomic(self): + self.assertRaises(AssertionError, self.loop.poll) + self.assertRaises(AssertionError, self.loop.flush) + self.assertRaises(AssertionError, self.loop.run) + + def testCallAndFlush(self): + log = [] + self.loop.call(log.append, 1) + self.loop.call(log.append, 2) + self.loop.call(self.loop.call, log.append, 3) + self.assertEqual(log, []) + self.loop.flush() + self.assertEqual(log, [1, 2]) + self.loop.poll() + self.assertEqual(log, [1, 2, 3]) + self.loop.poll() + self.assertEqual(log, [1, 2, 3]) + + + + + + def testUndoOfCall(self): + log = [] + def do(): + self.loop.call(log.append, 1) + sp = self.ctrl.savepoint() + self.loop.call(log.append, 2) + self.ctrl.rollback_to(sp) + self.loop.call(log.append, 3) + self.ctrl.atomically(do) + self.assertEqual(log, []) + self.loop.flush() + self.assertEqual(log, [1, 3]) + + def testScheduleUndo(self): + t = Time() + t.auto_update = False + t20 = t[20] + log = [] + @trellis.Cell + def checktime(): + t.reached(t20) + log.append(t._events[t20._when]) + @trellis.Performer + def err_after_reached(): + if len(t._schedule)>1: + raise DummyError + self.assertRaises(DummyError, checktime.get_value) + self.assertEqual(t._schedule, [t20._when, Max]) + self.assertEqual(dict(t._events), {t20._when:log[0]}) + del checktime + self.failUnless(isinstance(log.pop(), trellis.Sensor)) + self.assertEqual(dict(t._events), {}) + self.assertEqual(log, []) + + def force_rollback(self): + @trellis.Performer + def do_it(): + raise DummyError + + + + def testUpdateUndo(self): + t = Time() + t.auto_update = False + t20 = t[20] + @trellis.Cell + def checktime(): + if t.reached(t20): + self.force_rollback() + checktime.value + self.assertEqual(t._schedule, [t20._when, Max]) + self.assertEqual(list(t._events), [t20._when]) + self.assertRaises(DummyError, t.advance, 20) + self.assertEqual(t._schedule, [t20._when, Max]) + self.assertEqual(list(t._events), [t20._when]) + + + + + + + + + + + + + + + + + + + + + + + + + + + +class TestTasks(unittest.TestCase): + + ctrl = trellis.ctrl + + def testRunAtomicallyInLoop(self): + log = [] + def f(): + self.failUnless(self.ctrl.active) + log.append(1) + yield activity.Pause + self.failUnless(self.ctrl.active) + log.append(2) + t = activity.TaskCell(f) + self.assertEqual(log, []) + t._loop.flush() + self.assertEqual(log, [1]) + t._loop.flush() + self.assertEqual(log, [1, 2]) + + def testDependencyAndCallback(self): + log = [] + v = trellis.Value(42) + v1 = trellis.Value(1) + c1 = trellis.Cell(lambda: v1.value*2) + def f(): + while v.value: + log.append(v.value) + v1.value = v.value + yield activity.Pause + t = activity.TaskCell(f) + check = [] + for j in 42, 57, 99, 106, 23, None: + self.assertEqual(log, check) + v.value = j + if j: check.append(j) + for i in range(5): + t._loop.flush() + if j: self.assertEqual(c1.value, j*2) + self.assertEqual(log, check) + + + def testPauseAndCall(self): + log = [] + class TaskExample(trellis.Component): + trellis.attrs( + start = False, + stop = False + ) + + def wait_for_start(self): + log.append("waiting to start") + while not self.start: + yield activity.Pause + + def wait_for_stop(self): + while not self.stop: + log.append("waiting to stop") + yield activity.Pause + + @activity.task + def demo(self): + yield self.wait_for_start() + log.append("starting") + yield self.wait_for_stop() + log.append("stopped") + + self.assertEqual(log, []) + t = TaskExample() + EventLoop.flush() + self.assertEqual(log, ['waiting to start']) + log.pop() + t.start = True + EventLoop.flush() + self.assertEqual(log, ['starting', 'waiting to stop']) + log.pop() + log.pop() + t.stop = True + EventLoop.flush() + self.assertEqual(log, ['stopped']) + + + + def testValueReturns(self): + log = [] + def f1(): + yield 42 + def f2(): + yield f1(); yield activity.resume() + def f3(): + yield f2(); v = activity.resume() + log.append(v) + + t = activity.TaskCell(f3) + EventLoop.flush() + self.assertEqual(log, [42]) + + log = [] + def f1(): + yield activity.Return(42) + + t = activity.TaskCell(f3) + EventLoop.flush() + self.assertEqual(log, [42]) + + + def testErrorPropagation(self): + log = [] + def f1(): + raise DummyError + def f2(): + try: + yield f1(); activity.resume() + except DummyError: + log.append(True) + else: + pass + + t = activity.TaskCell(f2) + self.assertEqual(log, []) + EventLoop.flush() + self.assertEqual(log, [True]) + + + def testSendAndThrow(self): + log = [] + class SendThrowIter(object): + count = 0 + def next(self): + if self.count==0: + self.count = 1 + def f(): yield 99 + return f() + raise StopIteration + + def send(self, value): + log.append(value) + def f(): raise DummyError; yield None + return f() + + def throw(self, typ, val, tb): + log.append(typ) + log.append(val.__class__) # type(val) is instance in Py<2.5 + log.append(type(tb)) + raise StopIteration + + def fs(): yield SendThrowIter() + t = activity.TaskCell(fs) + self.assertEqual(log, []) + EventLoop.flush() + self.assertEqual(log, [99, DummyError,DummyError, types.TracebackType]) + + + + + + + + + + + + + + + def testResumeOnlyOnceUntilFlushed(self): + log = [] + c1 = trellis.Value(1) + c2 = trellis.Value(2) + def f(): + for i in range(3): + c1.value, c2.value + log.append(i) + yield activity.Pause + + t = activity.TaskCell(f) + self.assertEqual(log, []) + EventLoop.flush() + self.assertEqual(log, [0]) + c1.value = 3 + self.assertEqual(log, [0]) + c2.value = 4 + EventLoop.flush() + self.assertEqual(log, [0, 1]) + + + + + + + + + + + + + + + + + + + + + + + def testNoTodoRollbackIntoTask(self): + class CV(trellis.Component): + v = trellis.attr(False) + s = trellis.make(trellis.Set, name='s') + + @trellis.maintain + def maintain(self): + if self.v: + self.s.add(1) + else: + self.s.discard(1) + + @trellis.perform + def perform(s): + self.assertEqual(s.v, True) + + @activity.TaskCell + def task(): + cv = CV() + cv.v = True + yield activity.Pause + + EventLoop.run() + + + + + + + + + + + + + + + + + + +class SortedSetTestCase(unittest.TestCase): + + def testUnicodeSort(self): + data = trellis.Set([1, 2]) + sorted_set = collections.SortedSet( + data=data, sort_key=unicode, reverse=True + ) + + self.failUnlessEqual(list(sorted_set), [2, 1]) + data.add(0) + self.failUnlessEqual(list(sorted_set), [2, 1, 0]) + + def testStrSort(self): + data = trellis.Set([1, 3, 4]) + sorted_set = collections.SortedSet(data=data, sort_key=str) + + self.failUnlessEqual(list(sorted_set), [1, 3, 4]) + data.add(2) + self.failUnlessEqual(list(sorted_set), [1, 2, 3, 4]) + + def testRemoveLast(self): + data = trellis.Set([1, 2]) + sorted_set = collections.SortedSet(data=data) + data.remove(2) + self.failUnlessEqual(list(sorted_set), [1]) + + def testAddToEnd(self): + data = trellis.Set([1]) + sorted_set = collections.SortedSet(data=data) + data.add(2) + self.failUnlessEqual(list(sorted_set), [1, 2]) + + + + + + + + + + +def additional_tests(): + import doctest, sys + files = [ + 'README.txt', 'STM-Observer.txt', 'Activity.txt', 'Collections.txt', + 'Internals.txt', + ][(sys.version<'2.4')*3:] # All but Internals+Collections use decorator syntax + try: + from sqlalchemy.orm.attributes import ClassManager + except ImportError: + pass + else: + files.insert(0, 'SQLAlchemy.txt') + return doctest.DocFileSuite( + optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE, *files + ) + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/digsby/src/tests/unittests/test_util_primitives.py b/digsby/src/tests/unittests/test_util_primitives.py new file mode 100644 index 0000000..999f7e4 --- /dev/null +++ b/digsby/src/tests/unittests/test_util_primitives.py @@ -0,0 +1,111 @@ +from __future__ import with_statement + +import sys +from tests import TestCase, test_main +from contextlib import contextmanager +from util.primitives.error_handling import try_this, with_traceback +from util.primitives.funcs import isint +from util.primitives.mapping import odict +from util.primitives.strings import preserve_whitespace +import traceback + +def rabbithole(): + rabbithole() + +python_debug_build = hasattr(sys, 'gettotalrefcount') + +class TestUtilPrimitives(TestCase): + def test_try_this(self): + 'Ensure try_this squashes exceptions' + + def raise_it(e): + raise e + + self.assert_equal(5, try_this(lambda: 1/0, 5)) + self.assert_equal('foo', try_this(lambda: raise_it(AssertionError), 'foo')) + + if not python_debug_build: + self.assert_raises(RuntimeError, rabbithole) + self.assert_equal('bar', try_this(rabbithole, 'bar')) + + def test_with_traceback(self): + 'Test with_traceback' + + def test_prints_traceback(func): + def test_print_exc(): + test_print_exc.succeed = True + test_print_exc.succeed = False + + + with replace_function(traceback, 'print_exc', test_print_exc): + with_traceback(func) + + self.assert_(test_print_exc.succeed, '%r did not call traceback.print_exc()' % func) + + if not python_debug_build: + test_prints_traceback(rabbithole) + test_prints_traceback(lambda: 1/0) + + def test_isint(self): + self.assert_(isint('5')) + self.assert_(isint('123')) + self.assert_(isint('-2321')) + self.assert_(not isint('1.5')) + self.assert_(not isint('.')) + self.assert_(not isint('')) + self.assert_(not isint(' ')) + self.assert_(not isint('foo')) + self.assert_(not isint('5 foo')) + self.assert_(not isint('foo 5')) + + def test_odict(self): + d = odict() + d['foo'] = 'bar' + d['meep'] = 'baz' + d[321] = 123 + d[None] = None + + self.assert_equal(['foo', 'meep', 321, None], d.keys()) + self.assert_equal(['bar', 'baz', 123, None], d.values()) + self.assert_equal([('foo', 'bar'), ('meep', 'baz'), (321, 123), (None, None)], + d.items()) + + d.pop(321) + + self.assert_equal(['foo', 'meep', None], d.keys()) + self.assert_equal(['bar', 'baz', None], d.values()) + self.assert_equal([('foo', 'bar'), ('meep', 'baz'), (None, None)], + d.items()) + + self.assert_raises(KeyError, lambda: d.pop(456)) + + def test_preserve_whitespace(self): + cases = [ + ('', ''), + ('test', 'test'), + ('test', 'test'), + (' ', ' '), + (' ', '  '), + (' ', '   '), + ('test abc\ndef', 'test abc
def'), + ] + + for inp, expected in cases: + self.expect_equal(expected, preserve_whitespace(inp)) + +@contextmanager +def replace_function(module, function_name, replfunc): + assert hasattr(replfunc, '__call__') + assert isinstance(function_name, basestring) + + oldfunc = getattr(module, function_name) + try: + setattr(module, function_name, replfunc) + yield + finally: + setattr(module, function_name, oldfunc) + + + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/test_webkit.py b/digsby/src/tests/unittests/test_webkit.py new file mode 100644 index 0000000..bc2984f --- /dev/null +++ b/digsby/src/tests/unittests/test_webkit.py @@ -0,0 +1,52 @@ +from tests import TestCase, test_main +from path import path +import wx.webview + +if False: + class TestWebKit(TestCase): + def test_unicode_paths(self): + ''' + ensure that webkit can load unicode paths from the harddrive + ''' + unicode_name = u'\u0439\u0446\u0443\u043a\u0435\u043d' + html = u'''\ + + + + + + + + + + ''' % (unicode_name, unicode_name) + + f = wx.Frame(None) + try: + w = wx.webview.WebView(f) + thisdir = path(__file__).dirname() + htmlfile = thisdir / 'unicode_test.html' + htmlfile.write_bytes(html.encode('utf-8')) + + w.LoadURL(htmlfile.url()) + + self._did_onload = False + + def onload(e): + e.Skip() + if e.GetState() == wx.webview.WEBVIEW_LOAD_ONLOAD_HANDLED: + self._did_onload = True + + w.Bind(wx.webview.EVT_WEBVIEW_LOAD, onload) + f.Show() + + # <--- hypothetical "block until loaded" here + + assert '149' == w.RunScript('document.getElementById("unicodeImage").clientWidth') + finally: + f.Destroy() + try:htmlfile.remove() + except Exception: pass + +if __name__ == '__main__': + test_main() diff --git a/digsby/src/tests/unittests/utiltests.py b/digsby/src/tests/unittests/utiltests.py new file mode 100644 index 0000000..f4f2074 --- /dev/null +++ b/digsby/src/tests/unittests/utiltests.py @@ -0,0 +1,43 @@ +import unittest +import util +import util.primitives.structures as structures +import struct + +class UtilTestingSuite(unittest.TestCase): + + def testUnpackNames(self): + testdata = struct.pack("!HIB", 1,4000L,3) + "some extraneous data" + magic_hash = structures.unpack_named("!HIBR", "one", "four thousand long", "three", "extra", testdata) + + self.assertEquals(magic_hash['extra'], 'some extraneous data') + + def testStorage(self): + s = util.Storage({ "key": "value" }) + self.assertEquals(s.key, "value") + + def testdocs(self): + import doctest + doctest.testmod(util) + +# def test_named_structs(self): +# network_format = ( +# 'magic', '4s', +# 'version', 'H', +# 'size', 'I', +# 'cookie', 'Q', +# 'type', 'H', +# ) +# +# AIMPacket = util.NamedStruct(network_format) +# +# packet_dict = util.Storage(magic='AIM3', version=4, size=3200, +# cookie=4532432L, type=0x03) +# +# packet_bytes = AIMPacket.pack(packet_dict) +# unpacked_dict, data = AIMPacket.unpack(packet_bytes) +# +# self.assertEqual(packet_dict, unpacked_dict) +# self.assertEqual(packet_dict.cookie, 4532432L) + +if __name__ == "__main__": + unittest.main() diff --git a/digsby/src/tests/webkit/html/candybars.html b/digsby/src/tests/webkit/html/candybars.html new file mode 100644 index 0000000..b61fbd7 --- /dev/null +++ b/digsby/src/tests/webkit/html/candybars.html @@ -0,0 +1,164 @@ + + + + + + + + + + + + + +
+ 5:12 AM Monday, November 26, 2007 5:12 AM ninjadigsby You don't seem to like any fantasy movies, ever see Dragon Heart? 5:13 AM Friend Naw, but Braveheart is kind of fantasy 5:14 AM ninjadigsby Not really, lol 5:15 AM Friend I think so 5:15 AM He fights dragons, Dragons are a fantasy icon man! Lol 5:15 AM Braveheart ftw! 5:15 AM ninjadigsby There's no dragons in Braveheart... 5:16 AM Friend Wait, did I get them confused? 5:16 AM What's the one where he fights dragons? 5:16 AM ninjadigsby Dragon Hart 5:16 AM Friend Mel Gibson? 5:17 AM ninjadigsby I don't think Mel Gibson ever fought any dragons :P
+ + + + + + + \ No newline at end of file diff --git a/digsby/src/tests/webkit/html/lucida/19270.png b/digsby/src/tests/webkit/html/lucida/19270.png new file mode 100644 index 0000000..7637a8a Binary files /dev/null and b/digsby/src/tests/webkit/html/lucida/19270.png differ diff --git a/digsby/src/tests/webkit/html/lucidagrande.html b/digsby/src/tests/webkit/html/lucidagrande.html new file mode 100644 index 0000000..73aa829 --- /dev/null +++ b/digsby/src/tests/webkit/html/lucidagrande.html @@ -0,0 +1,25 @@ + + + + + + +
+

19270

+

A quick brown fox jumps over the lazy dog.

+

A quick brown fox jumps over the lazy dog. (not bold)

+ + + diff --git a/digsby/src/tests/webkit/html/metal.html b/digsby/src/tests/webkit/html/metal.html new file mode 100644 index 0000000..9f6c8ca --- /dev/null +++ b/digsby/src/tests/webkit/html/metal.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + +
+
+
+
+
+ +
started at 04:54 PM
+
+ Nick +
+
+ + + +
+
Monday, November 26, 2007 (5:12 AM)
ninjadigsby 5:12 AM

You don't seem to like any fantasy movies, ever see Dragon Heart?

Friend 5:13 AM

Naw, but Braveheart is kind of fantasy

ninjadigsby 5:14 AM

Not really, lol

Friend 5:15 AM

I think so

5:15 AM

He fights dragons, Dragons are a fantasy icon man! Lol

5:15 AM

Braveheart ftw!

ninjadigsby 5:15 AM

There's no dragons in Braveheart...

Friend 5:16 AM

Wait, did I get them confused?

5:16 AM

What's the one where he fights dragons?

ninjadigsby 5:16 AM

Dragon Hart

Friend 5:16 AM

Mel Gibson?

ninjadigsby 5:17 AM

I don't think Mel Gibson ever fought any dragons :P

+ + + + \ No newline at end of file diff --git a/digsby/src/tests/webkit/html/smooth.html b/digsby/src/tests/webkit/html/smooth.html new file mode 100644 index 0000000..a919439 --- /dev/null +++ b/digsby/src/tests/webkit/html/smooth.html @@ -0,0 +1,161 @@ + + + + + + + + + + + + + +
+
Monday, November 26, 2007
5:12 AM
ninjadigsby
5:12 AM

You don't seem to like any fantasy movies, ever see Dragon Heart?

Friend
5:13 AM

Naw, but Braveheart is kind of fantasy

ninjadigsby
5:14 AM

Not really, lol

Friend
5:15 AM

I think so

15:44

He fights dragons, Dragons are a fantasy icon man! Lol

15:55

Braveheart ftw!

ninjadigsby
5:15 AM

There's no dragons in Braveheart...

Friend
5:16 AM

Wait, did I get them confused?

16:30

What's the one where he fights dragons?

ninjadigsby
5:16 AM

Dragon Hart

Friend
5:16 AM

Mel Gibson?

ninjadigsby
5:17 AM

I don't think Mel Gibson ever fought any dragons :P

+ + + + \ No newline at end of file diff --git a/digsby/src/tests/webkit/html/smoothsep.html b/digsby/src/tests/webkit/html/smoothsep.html new file mode 100644 index 0000000..6c6579e --- /dev/null +++ b/digsby/src/tests/webkit/html/smoothsep.html @@ -0,0 +1,161 @@ + + + + + + + + + + + + + +
+
Monday, November 26, 2007 5:12 AM
[5:12 AM] ninjadigsby [5:12 AM]: You don't seem to like any fantasy movies, ever see Dragon Heart?
[5:13 AM] Friend [5:13 AM]: Naw, but Braveheart is kind of fantasy
[5:14 AM] ninjadigsby [5:14 AM]: Not really, lol
[5:15 AM] Friend [5:15 AM]: I think so
[5:15 AM] Friend [5:15 AM]: He fights dragons, Dragons are a fantasy icon man! Lol
[5:15 AM] Friend [5:15 AM]: Braveheart ftw!
[5:15 AM] ninjadigsby [5:15 AM]: There's no dragons in Braveheart...
[5:16 AM] Friend [5:16 AM]: Wait, did I get them confused?
[5:16 AM] Friend [5:16 AM]: What's the one where he fights dragons?
[5:16 AM] ninjadigsby [5:16 AM]: Dragon Hart
[5:16 AM] Friend [5:16 AM]: Mel Gibson?
[5:17 AM] ninjadigsby [5:17 AM]: I don't think Mel Gibson ever fought any dragons :P
+ + + + \ No newline at end of file diff --git a/digsby/src/tests/webkit/test_infobox_app.py b/digsby/src/tests/webkit/test_infobox_app.py new file mode 100644 index 0000000..084e88e --- /dev/null +++ b/digsby/src/tests/webkit/test_infobox_app.py @@ -0,0 +1,47 @@ +import wx.webview +from tests.testapp import testapp +from path import path + +def main(): + a = testapp() + f = wx.Frame(None) + w = wx.webview.WebView(f) + + def on_js_console_message(e): + print u'JS {e.LineNumber:>4}: {message}'.format( + e=e, message=e.Message.encode('ascii', 'replace')) + + w.Bind(wx.webview.EVT_WEBVIEW_CONSOLE_MESSAGE, on_js_console_message) + + js = path(__file__) / '../../../..' / 'res/html/infobox/infobox.js' + url = js.abspath().url() + + html = '''\ + + + + + + + + foo + baz +

+

+ +''' % url + + w.SetPageSource(html, 'file://') + f.Show() + a.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/webkit/test_offscreen.py b/digsby/src/tests/webkit/test_offscreen.py new file mode 100644 index 0000000..fda2e05 --- /dev/null +++ b/digsby/src/tests/webkit/test_offscreen.py @@ -0,0 +1,104 @@ +import wx.webview +from tests.testapp import testapp +import cgui + +def main(): + app = testapp() + + f = wx.Frame(None) + n = wx.Notebook(f) + + f.Sizer = wx.BoxSizer(wx.HORIZONTAL) + f.Sizer.Add(n, 1, wx.EXPAND) + + taskbar = cgui.TabNotebook(f) + + def page_index(win): + for x in xrange(n.GetPageCount()): + nwin = n.GetPage(x) + if win is nwin: + return x + + return -1 + + class Preview(cgui.TabController): + def OnTabClosed(self, tab): + i = page_index(tab.Window) + if i != -1: + n.DeletePage(i) + + def GetLivePreview(self, tab, rect): + print 'yay', rect + return self.get_preview(tab, rect) + + def GetIconicBitmap(self, tab, width, height): + return self.get_preview(tab, wx.Rect(0, 0, width, height)) + + def get_preview(self, tab, r): + bitmap = wx.EmptyBitmap(r.width, r.height, False) + memdc = wx.MemoryDC(bitmap) + memdc.SetBrush(wx.WHITE_BRUSH) + memdc.SetPen(wx.TRANSPARENT_PEN) + memdc.DrawRectangle(0, 0, *r.Size) + + #memdc.Brush = wx.RED_BRUSH + #memdc.DrawRectangle(50, 50, 100, 100) + + tab.Window.PaintOnDC(memdc, r, r.Size) + memdc.SelectObject(wx.NullBitmap) + + return bitmap + + pages = [] + def addpage(url): + w = wx.webview.WebView(f) + pages.append(w) + w.LoadURL(url) + n.AddPage(w, url) + taskbar.CreateTab(w, Preview()) + + addpage('http://google.com') + addpage('http://digsby.com') + + f.Show() + + if False: + f2 = wx.Frame(None) + f2.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + t = wx.PyTimer(f2.Refresh) + t.StartRepeating(1000) + + def paint(e): + dc = wx.PaintDC(f2) + + r = f2.ClientRect + + bitmap = wx.EmptyBitmap(r.width, r.height, False) + memdc = wx.MemoryDC(bitmap) + memdc.SetBrush(wx.WHITE_BRUSH) + memdc.SetPen(wx.TRANSPARENT_PEN) + memdc.DrawRectangle(0, 0, *r.Size) + + #memdc.Brush = wx.RED_BRUSH + #memdc.DrawRectangle(50, 50, 100, 100) + + pages[1].PaintOnDC(memdc, f2.ClientRect, f2.ClientSize) + memdc.SelectObject(wx.NullBitmap) + + #cgui.Unpremultiply(bitmap) + dc.DrawBitmap(bitmap, 0, 0, False) + + f2.Bind(wx.EVT_PAINT, paint) + f2.Show() + + + def close(e): + app.ExitMainLoop() + for _f in (f, ): + _f.Bind(wx.EVT_CLOSE, close) + + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/webkit/test_webkit_hosted.py b/digsby/src/tests/webkit/test_webkit_hosted.py new file mode 100644 index 0000000..e1009cc --- /dev/null +++ b/digsby/src/tests/webkit/test_webkit_hosted.py @@ -0,0 +1,102 @@ +if __name__ == '__main__': + __builtins__._ = lambda s: s + +import wx.webview +import protocols +import gui.infobox.interfaces as gui_interfaces +from path import path +from util import Storage + +class MockAccount(object): + def get_dirty(self): return True + def set_dirty(self, dirty): pass + _dirty = property(get_dirty, set_dirty) + + protocol = 'mock' + username = 'digsbee' + + def __init__(self): + pass + +returns_empty_str = lambda *a: path('') + +mock_app_context = Storage( + resource= lambda *a: Storage(url=returns_empty_str), + get_res_dir = returns_empty_str) + +content = ''' + +
+ +function fb_comment_button_mousedown() {} + +swapIn(function(){ + $(".comment_button").live("mousedown", fb_comment_button_mousedown); +}); + +swapOut(function(){ + $(".comment_button").die("mousedown", fb_comment_button_mousedown); +}); + +''' + +import gui.infobox.providers as gui_providers +class MockProvider(gui_providers.InfoboxProviderBase): + protocols.advise( + asAdapterForTypes=[MockAccount], + instancesProvide=[gui_interfaces.IInfoboxHTMLProvider]) + + def __init__(self, account): + self.acct = account + + def get_html(self, file, dir): + print 'get_html', file + if file == 'head.tenjin': + return '' + elif file == 'content.tenjin': + return '
content!
' * 500 + + def get_app_context(self, context): + return mock_app_context + +def on_console_message(e): + print u'JS {e.LineNumber:>4}: {message}'.format( + e=e, message=e.Message.encode('ascii', 'replace')) + +def main(): + from tests.testapp import testapp + app = testapp() + f = wx.Frame(None) + w = wx.webview.WebView(f) + w.Bind(wx.webview.EVT_WEBVIEW_CONSOLE_MESSAGE, on_console_message) + + from gui.infobox.infoboxapp import init_host, set_hosted_content + + account = MockAccount() + + init_host(w) + + def do_set_content(): + for x in xrange(100): + set_hosted_content(w, account) + + def on_load(e): + if e.GetState() == wx.webview.WEBVIEW_LOAD_ONLOAD_HANDLED: + pass + w.Bind(wx.webview.EVT_WEBVIEW_LOAD, on_load) + + set_content_button = wx.Button(f, -1, 'set content') + set_content_button.Bind(wx.EVT_BUTTON, lambda e: do_set_content()) + + hsizer = wx.BoxSizer(wx.HORIZONTAL) + hsizer.Add(set_content_button) + + f.Sizer = wx.BoxSizer(wx.VERTICAL) + f.Sizer.AddMany([(hsizer, 0, wx.EXPAND), (w, 1, wx.EXPAND)]) + + f.Show() + + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/webkit/test_webkit_unicode.py b/digsby/src/tests/webkit/test_webkit_unicode.py new file mode 100644 index 0000000..152bf7a --- /dev/null +++ b/digsby/src/tests/webkit/test_webkit_unicode.py @@ -0,0 +1,27 @@ +import logging +logging.Logger.debug_s = logging.Logger.debug + +import wx +from gui.browser.webkit import WebKitWindow + + + +def test_webkit_unicode(): + f = wx.Frame(None) + w = WebKitWindow(f, initialContents = 'test') + + #w.RunScript('document.write("test");') + def foo(): + w.RunScript(u'document.write("

abcd\u1234

");') + + wx.CallLater(500, foo) + + f.Show() + +def main(): + a = wx.PySimpleApp() + test_webkit_unicode() + a.MainLoop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/digsby/src/tests/webkit/testwebkitwindow.py b/digsby/src/tests/webkit/testwebkitwindow.py new file mode 100644 index 0000000..ab18922 --- /dev/null +++ b/digsby/src/tests/webkit/testwebkitwindow.py @@ -0,0 +1,239 @@ +from __future__ import with_statement + + +from traceback import print_exc + + +import wx +import wx.webview as webview +import cgui + +import os.path + +def main(): + from tests.testapp import testapp + app = testapp(plugins=False) + from gui.browser.webkit.webkitwindow import WebKitWindow + + webview.WebView.SetDatabaseDirectory(r'c:\twitter_test') + + f = wx.Frame(None, title='webkit test', size = (950, 600)) + f.BackgroundStyle = wx.BG_STYLE_CUSTOM + s = f.Sizer = wx.BoxSizer(wx.VERTICAL) + + #url = 'file://c:/Users/Kevin/src/digsby/src/social/twitter/web/twitter.html' + #url = 'file://c:/leak.html' + #url = 'file://c:/Users/Kevin/src/digsby/src/social/feed/app.html' + + #filepath = 'd:\\sizetest.html' + #url = 'file://C:/Users/Aaron/Desktop/ib3.html' + #url = 'http://symbolsystem.com/wxwebkit/bigimtest.html' + + url = 'file:///C:/users/kevin/desktop/scrollbars.html' + #url = 'http://it-help.bathspa.ac.uk/iframe_demo.html' + #url='http://google.com' + #url = 'http://webkit.org/blog/138/css-animation/' + + #filepath = r'C:\users\kevin\desktop\scrollbars.html' + #url = '' + #filepath = 'c:\\mydir\\test.html' + + #url = 'http://webkit.org/misc/DatabaseExample.html' + #wp = 'file:///C:/src/Digsby/res/MessageStyles/GoneDarkHacked.AdiumMessageStyle/Contents/Resources/' + #wp = 'file://c:/mydir/' + + + #with open(filepath, 'rb') as html: + #contents = html.read().decode('utf-8') + + #contents = u'\u0647\u064a' + + from optparse import OptionParser + parser = OptionParser() + parser.add_option('-z', '--zoom', dest='zoom', type='int', default=100) + + options, args = parser.parse_args() + + print args + if args: + url = args[0] + + if os.path.isfile(url): + from path import path + url = path(url).url() + + contents = u'''\ + + + + + + + + + +''' + + #w = WebKitWindow(f, contents) + w = WebKitWindow(f, url = url) + w.BlockWebKitMenu = False + w.ExternalLinks = False + + t = wx.TextCtrl(f, -1, locals().get('url', ''), size=(400,-1)) + + def gotourl(e): + w.LoadURL(t.Value) + + t.Bind(wx.EVT_TEXT_ENTER, gotourl) + + set_url_button = wx.Button(f, -1, 'url') + set_contents_button = wx.Button(f, -1, 'contents') + find_button = wx.Button(f, -1, 'find') + copy_button = wx.Button(f, -1, 'Copy') + set_contents_delay_button = wx.Button(f, -1, 'set content 5s') + unicode_button = wx.Button(f, -1, 'add unicode') + +# def foo(e): +# e.Skip() +# if e.GetState() == webview.EVT_WEBVIEW_LOAD: +# print repr(w.HTML) +# print ' '.join('%02x' % ord(c) for c in w.HTML.encode('utf-8')) +# t.SetValue(e.GetURL()) + + def find(e): + #const wxString& string, bool forward, bool caseSensitive, bool wrapSelection, bool startInSelection + w.FindString(t.Value, True, False, True, False) + + # shows the order of EVT_WEBVIEW_LOAD events. + def show(s): + def _show(e): + e.Skip() + + return + print s + if s == 'EVT_WEBVIEW_LOAD': + print webview_evt_constants[e.State] + return _show + + #w.Bind(webview.EVT_WEBVIEW_LOAD, show('EVT_WEBVIEW_LOAD')) + #w.Bind(webview.EVT_WEBVIEW_BEFORE_LOAD, show('EVT_WEBVIEW_BEFORE_LOAD')) + + + def OnBeforeLoad(e): + if e.NavigationType == wx.webview.WEBVIEW_NAV_LINK_CLICKED: + e.Cancel() # don't navigate in webkit + wx.LaunchDefaultBrowser(e.URL) + else: + e.Skip() + + w.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, OnBeforeLoad) + + def setcontents(e): + w.SetHTML(t.Value) + + def copy(e): + w.Copy() + + def setcontentlater(e): + def later(): + try: + w.ReleaseMouse() + except Exception: + print_exc() + + if w.HasCapture(): + w.ReleaseMouse() + w.SetHTML(contents) + + wx.CallLater(2000, later) + + def addunicode(e): + w.RunScript(u'''document.body.innerHTML = '';''') + + set_url_button.Bind(wx.EVT_BUTTON, gotourl) + set_contents_button.Bind(wx.EVT_BUTTON, setcontents) + find_button.Bind(wx.EVT_BUTTON, find) + copy_button.Bind(wx.EVT_BUTTON, copy) + set_contents_delay_button.Bind(wx.EVT_BUTTON, setcontentlater) + unicode_button.Bind(wx.EVT_BUTTON, addunicode) + + def OnConsoleMessage(e): + print e.Message + + zoom = wx.Slider(f, minValue=1, maxValue=400, value=options.zoom) + def on_zoom(e=None): + if e is not None: e.Skip() + w.SetPageZoom(zoom.Value/100.0) + zoom.Bind(wx.EVT_SCROLL_CHANGED, on_zoom) + zoom.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, on_zoom) + + def zoom_later(): + zoom.SetValue(options.zoom) + on_zoom() + + wx.CallLater(500, zoom_later) + + # glass checkbox + + glass_check = wx.CheckBox(f, -1, 'glass') + def on_glass(e): + if not e.IsChecked(): + cgui.glassExtendInto(f, 0, 0, 0, 0) + f.Refresh() + glass_check.Bind(wx.EVT_CHECKBOX, on_glass) + + f.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + def paint(e): + dc = wx.PaintDC(f) + dc.Brush = wx.BLACK_BRUSH + dc.Pen = wx.TRANSPARENT_PEN + dc.DrawRectangleRect(f.ClientRect) + if glass_check.Value: + cgui.glassExtendInto(f, 0, 0, 0, w.Size.height) + + f.Bind(wx.EVT_PAINT, paint) + + w.Bind(webview.EVT_WEBVIEW_CONSOLE_MESSAGE, OnConsoleMessage) + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + button_sizer.AddMany((button, 1, wx.EXPAND) for button in + [ + zoom, + set_url_button, + glass_check, + #set_contents_button, + #find_button, + #copy_button, + #set_contents_delay_button, + #unicode_button, + ]) + + h = wx.BoxSizer(wx.HORIZONTAL) + h.AddMany([(t, 1, wx.EXPAND), (button_sizer, 0, wx.EXPAND)]) + + + + s.Add(h) + s.Add(w, 1, wx.EXPAND) + + def later(): + f.Show() + f.Raise() + print 'ParseMode', w.ParseMode + + wx.CallAfter(later) + app.MainLoop() + +webview_evt_constants = {webview.WEBVIEW_LOAD_STARTED: 'WEBVIEW_LOAD_STARTED', + webview.WEBVIEW_LOAD_NEGOTIATING: 'WEBVIEW_LOAD_NEGOTIATING', + webview.WEBVIEW_LOAD_REDIRECTING: 'WEBVIEW_LOAD_REDIRECTING', + webview.WEBVIEW_LOAD_TRANSFERRING: 'WEBVIEW_LOAD_TRANSFERRING', + webview.WEBVIEW_LOAD_STOPPED: 'WEBVIEW_LOAD_STOPPED', + webview.WEBVIEW_LOAD_FAILED: 'WEBVIEW_LOAD_FAILED', + webview.WEBVIEW_LOAD_DL_COMPLETED: 'WEBVIEW_LOAD_DL_COMPLETED', + webview.WEBVIEW_LOAD_DOC_COMPLETED: 'WEBVIEW_LOAD_DOC_COMPLETED', + webview.WEBVIEW_LOAD_ONLOAD_HANDLED: 'WEBVIEW_LOAD_ONLOAD_HANDLED', + webview.WEBVIEW_LOAD_WINDOW_OBJECT_CLEARED: 'WEBVIEW_LOAD_WINDOW_OBJECT_CLEARED'} + +if __name__ == '__main__': + main() + diff --git a/digsby/src/tests/webkit/transparent.py b/digsby/src/tests/webkit/transparent.py new file mode 100644 index 0000000..48a10e6 --- /dev/null +++ b/digsby/src/tests/webkit/transparent.py @@ -0,0 +1,47 @@ +import Digsby +import wx.webview +import os.path + +thisdir = os.path.dirname(os.path.abspath(__file__)) + +def main(): + from tests.testapp import testapp + app = testapp() + + from cgui import TransparentFrame + + class TransparentWebKit(TransparentFrame): + def __init__(self, parent): + TransparentFrame.__init__(self, parent) + self.webview = wx.webview.WebView(self) + + def GetBitmap(self): + s = self.ClientSize + + img = wx.EmptyImage(*s) + bmp = wx.BitmapFromImage(img) + bmp.UseAlpha() + dc = wx.MemoryDC(bmp) + dc.Clear() + dc.DrawRectangleRect(self.ClientRect) + self.webview.PaintOnDC(dc, False) + dc.SelectObject(wx.NullBitmap) + + return bmp + + f = TransparentWebKit(None) + w = f.webview + + w.SetTransparent(True) + w.SetPageSource(open(os.path.join(thisdir, 'test.html')).read()) + #w.LoadURL('http://webkit.org/blog/138/css-animation/') + + f.Show() + + f.SetRect((200, 200, 400, 400)) + + assert w.IsTransparent() + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/webkit/transparentwebkit.py b/digsby/src/tests/webkit/transparentwebkit.py new file mode 100644 index 0000000..b770c7f --- /dev/null +++ b/digsby/src/tests/webkit/transparentwebkit.py @@ -0,0 +1,81 @@ +import Digsby +import sys +import wx.webview +from tests.testapp import testapp + +wxMSW = sys.platform.startswith("win") +if wxMSW: + from cgui import ApplyAlpha + +html = ''' + + + + + +hello world + + +''' + +def main(): + app = testapp() + style = wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR | wx.FRAME_TOOL_WINDOW + + f = wx.Frame(None, style=style, pos = (400, 300), size = (300, 300)) + p = wx.Panel(f, -1) + p.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + s = wx.BoxSizer(wx.VERTICAL) + w = wx.webview.WebView(p) + + def paint(e): + ''' + i = wx.ImageFromBitmap(w.Bitmap) + b = wx.BitmapFromImage(i) + b.UseAlpha() + ''' + print "Hello..." + bitmap = wx.EmptyBitmap(*w.Size) + dc = wx.MemoryDC(bitmap) + dc.Clear() + #w.PaintOnDC(dc, False) + + dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 0))) + dc.DrawRectangle(0, 0, 400, 400) + #dc.SetBrush(wx.RED_BRUSH) + #dc.DrawRectangle(50, 50, 300, 200) + + if wxMSW: + ApplyAlpha(f, bitmap) + + dc = wx.PaintDC(e.GetEventObject()) + dc.DrawBitmap(bitmap, 0, 0, True) + #wx.ScreenDC().DrawBitmap(bitmap, 0, 0, True) + + if not wxMSW: + p.Bind(wx.EVT_PAINT, paint) + f.Bind(wx.EVT_PAINT, paint) + + s.Add(w, 1, wx.EXPAND) + p.SetSizer(s) + w.SetPageSource(html) + f.SetTransparent(125) + + w.SetTransparent(True) + + #wx.CallLater(2000, lambda: ApplyAlpha(f, w.Bitmap)) + + f.Show() + + f.SetSize((500, 500)) + if wxMSW: + paint(None) + app.MainLoop() + +if __name__ == '__main__': + main() diff --git a/digsby/src/tests/webkit/webframebug.py b/digsby/src/tests/webkit/webframebug.py new file mode 100644 index 0000000..8a8bd92 --- /dev/null +++ b/digsby/src/tests/webkit/webframebug.py @@ -0,0 +1,27 @@ +import wx, wx.webview + +app = wx.PySimpleApp() +f = wx.Frame(None, size = (640, 480)) + +f.Sizer = s = wx.BoxSizer(wx.VERTICAL) + +b = wx.webview.WebView(f) +b.SetPageSource('''\ + + + + + + +

link to http://: Yahoo!

+

link to local file: test.html

+

image stored locally:

+ +''', "file:///c:/") +f.Show() + + +b.RunScript('document.getElementById("wut").style.font.weight = "normal";') + +app.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/webkit/webkitbugs.py b/digsby/src/tests/webkit/webkitbugs.py new file mode 100644 index 0000000..e657e83 --- /dev/null +++ b/digsby/src/tests/webkit/webkitbugs.py @@ -0,0 +1,78 @@ +''' +Tests editor functionality of WebKit through the execCommand Javascript API. +''' +import wx, wx.webview +from functools import partial + + +class WebKitEditor(wx.webview.WebView): + def __init__(self, parent): + wx.webview.WebView.__init__(self, parent) + + def _execCommand(self, *a): + args = ', '.join(jsrepr(arg) for arg in a) + js = 'document.execCommand(%s);' % args + + print js + + return self.RunScript(js) + + def __getattr__(self, attr): + try: + return wx.webview.WebView.__getattr__(self, attr) + except AttributeError: + if attr.startswith('__'): + raise + return partial(self._execCommand, attr) + +def jsrepr(o): + 'Python object -> Javascript equivalent' + + if isinstance(o, bool): + return str(o).lower() # True -> true + elif isinstance(o, unicode): + return repr(o)[1:] # u'string' -> 'string' + else: + return repr(o) + + +def Button(parent, text, callback, **kwargs): + button = wx.Button(parent, -1, text, **kwargs) + button.Bind(wx.EVT_BUTTON, lambda *e: callback()) + return button + + +if __name__ == '__main__': + + # construct gui + app = wx.PySimpleApp() + f = wx.Frame(None, title = 'WebKit Editor Test') + f.Sizer = fs = wx.BoxSizer(wx.VERTICAL) + + editor = WebKitEditor(f) + editor.SetPageSource('') + editor.MakeEditable(True) + + but = lambda label, callback: Button(f, label, callback, style = wx.BU_EXACTFIT) + + editbuttons = [ + ('B', editor.Bold), + ('I', editor.Italic), + ('U', editor.Underline), + ('A+', lambda: editor.FontSizeDelta(True, 1)), + ('A-', lambda: editor.FontSizeDelta(True, -1)), + ('bg', lambda: editor.BackColor(True, wx.GetColourFromUser().GetAsString(wx.C2S_HTML_SYNTAX))), + ('fg', lambda: editor.ForeColor(True, wx.GetColourFromUser().GetAsString(wx.C2S_HTML_SYNTAX))), + ] + + # layout gui + bsizer = wx.BoxSizer(wx.HORIZONTAL) + for label, callback in editbuttons: + bsizer.Add(but(label, callback)) + + fs.Add(bsizer, 0, wx.EXPAND) + fs.Add(editor, 1, wx.EXPAND) + + # run + f.Show() + app.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/webkit/webkittest_noapp.py b/digsby/src/tests/webkit/webkittest_noapp.py new file mode 100644 index 0000000..ea41b97 --- /dev/null +++ b/digsby/src/tests/webkit/webkittest_noapp.py @@ -0,0 +1,16 @@ +import wx, wx.webview + +app = wx.PySimpleApp() +f = wx.Frame(None, size = (640, 480)) +b = wx.webview.WebView(f) +b.LoadURL('http://www.google.com') + +# show an alert box +b.RunScript('alert("test");') + +# modify some CSS +b.RunScript("document.getElementById('mydiv').style.color = '#0000ff';") + +f.Show() +app.MainLoop() + diff --git a/digsby/src/tests/widgettest.py b/digsby/src/tests/widgettest.py new file mode 100644 index 0000000..b177c39 --- /dev/null +++ b/digsby/src/tests/widgettest.py @@ -0,0 +1,249 @@ +import wx +from gui.uberwidgets.UberButton import UberButton +from gui.uberwidgets.UberCombo import UberCombo +from gui.uberwidgets.simplemenu import SimpleMenu,SimpleMenuItem +from gui import skin +from gui.uberwidgets.UberBar import UberBar +from random import randint + +def buttons(parent): + + yahoo = skin.load_bitmap('../../protocols/yahoo.png') + + s = 'button' + b1 = UberButton(parent, -1, 'Skinned', skin = s) + b2 = UberButton(parent, -1, 'Native', skin = None) + b3 = UberButton(parent, -1, 'SkinnedPic', skin = s, icon = yahoo) + b4 = UberButton(parent, -1, 'NativePic', skin = None, icon = yahoo) + + return [b1, b2, b3, b4] + +def combos(parent): + uct = UberCombo(parent, value='test', skinkey = 'combobox', typeable = True, size=(100, 20)) + uct.AppendItem(SimpleMenuItem('Sample Item 1')) + uct.AppendItem(SimpleMenuItem('Sample Item 2')) + uct.AppendItem(SimpleMenuItem(id=-1)) + uct.AppendItem(SimpleMenuItem('Sample Item 3')) + + return [uct] + + +def progressbars(parent, sizer): + from gui.uberwidgets.UberProgressBar import UberProgressBar + + + sizer.Add(wx.StaticText(parent, -1, 'With label, no size:')) + b1 = UberProgressBar(parent, wx.NewId(), 100, 'progressbar', showlabel = True) + sizer.Add(b1, 0, wx.EXPAND) + + sizer.Add(wx.StaticText(parent, -1, 'No label, with size:')) + b2 = UberProgressBar(parent, wx.NewId(), 100, 'progressbar', showlabel = False, size=(300,20)) + sizer.Add(b2, 0, wx.EXPAND) + + bars = [b1, b2] + + class T(wx.Timer): + def Notify(self): + for b in bars: + newval = (b.GetValue() + randint(3, 15)) % 100 + b.SetValue(newval) + + b.t = T() + b.t.Start(200) + + + +if __name__ == '__main__': + + import re + debug_re = re.compile('line (\d+), column (\d+)') + + skinyaml = ''' +Common: +- &DigsGreenV gradient vertical 0xC8FFC8 0x55FF55 + +Button: + Backgrounds: + Normal: *DigsGreenV + Down: gradient vertical 0x007700 0x00CC00 + Active: gradient vertical 0xC8FFEE 0x55FFEE + Hover: gradient vertical 0x00CC00 0x00FF00 + ActiveHover: gradient vertical 0x00CCEE 0x00FFEE + Disabled: gradient vertical 0x7D7D7D 0xEDEDED + Notify: gradient vertical 0xFFFFC8 0xFFFF55 + FontColors: + Normal: '0xFF0000' + Down: '0xFF0000' + Active: '0xFF0000' + Hover: '0xFF0000' + ActiveHover: '0xFF0000' + Disabled: '0xFF0000' + Notify: '0xFF0000' + MenuIcon: dropmenuicon.png + Padding: [ 3 , 3 ] #x,y between all items + +#### SHOULD BE LIKE REGULAR MENU - ONE USED THROUGHOUT +SimpleMenu: + SubmenuIcon: submenuicon.png + SeparatorImage: separator2.png + Padding: 0 # left right between items + Border: 5 + Backgrounds: + Frame: gradient red white blue #0x000000 0xFFFFFF #pic gradient or color + Menu: 0xFFFFFF border red dot #selectedtab.png 6 6 -6 -7 #gradient vertical 0x00CC00 0x00FF00 + #Item: 0xCCCCCC rounded shadow bevel #each item instead of menu + Selection: square_grad.png 6 6 -6 -6 + Font: comic sans ms + Fontcolors: + Normal: '0x00FF00' + Selection: white + +#### ????? +#### USE GENERIC (comboboxskin) or separate for each place +#### SINGLE BUTTON SKIN MODE !!!!! +combobox: + Backgrounds: + Normal: *DigsGreenV + Active: red #white + hover: yellow #not implemented + fontcolors: + normal: black + active: white + Hover: white #not implemented + font: comic sans ms + dropdownbutton: + Icon: dropmenuicon.png + Skin: Button + cyclebutton: + Skin: Button + menu: + Skin: SimpleMenu + padding: 3 + +Menu: + Margins: [ 3 , 3 ] + Border: 2 #frame + Iconsize: 16 + Font: comic sans ms + Backgrounds: + Frame: black #menubgofuglyness.png 38 38 -38 -38 + menu: black + item: #white frame1.png 8 8 -8 -8 #White + #disabled: + Selection: vertical 0xC8FFC8 0x55FF55 + #Gutter: selectedtab.png 6 6 -6 -7 #gradient vertical 0x00CC00 0x00FF00 + FontColors: + Normal: white + Selection: black + Disabled: gray + SeparatorImage: separator2.png + SubmenuIcon: submenuicon.png + CheckedIcon: checked.png + #uncheckedicon: + +#### OVERRIDE'S DEFAULT OPERATING SYSTEM MENUBAR AT THE TOP OF THE BUDDY LIST +MenuBar: + padding: 3 + background: *DigsGreenV + itemskin: Button + +#### OVERRIDE'S DEFAULT OPERATING SYSTEM PROGRESS BAR USED FOR FILE TRANSFERS +#### MERGE INFO FILE TRANSFER DIALOG SKIN +Progressbar: + #Padding: 4 + #Style: Repeat or none + Backgrounds: + Normal: progressbg.png 9 1 -9 -1 + Fill: progressfg.png 9 1 -9 -1 + #font not needed + #fontcolors not needed + #normal + #fill + +''' + + from gui.skin import SkinException + + a = wx.PySimpleApp() + + #set_yaml('../../res/skins/default', skinyaml) + from tests.testapp import testapp + app = testapp('../..', skinname = 'silverblue') + + f = wx.Frame(None, -1, 'UberWidgets', size = (800, 750)) + f.Bind(wx.EVT_CLOSE, lambda e: a.ExitMainLoop()) + + split = wx.SplitterWindow(f, style = wx.SP_LIVE_UPDATE) + + panel = wx.Panel(split) + f.Sizer = wx.BoxSizer(wx.VERTICAL) + #f.Sizer.Add(menubar(f), 0, wx.EXPAND) + f.Sizer.Add(split, 1, wx.EXPAND) + + def box(title): + box = wx.StaticBox(panel, -1, title) + sz = wx.StaticBoxSizer(box, wx.VERTICAL) + return sz + + sz = panel.Sizer = wx.GridSizer(3, 3) + + buttongroup = box('buttons') + for i, b in enumerate(buttons(panel)): buttongroup.Add(b, 0, wx.EXPAND | wx.ALL, 3) + + combogroup = box('combos') + for c in combos(panel): combogroup.Add(c, 0, wx.EXPAND | wx.ALL, 3) + + progressgrp = box('progress') + progressbars(panel, progressgrp) + + sz.AddMany([(buttongroup, 1, wx.EXPAND), + (combogroup, 1, wx.EXPAND), + (progressgrp, 1, wx.EXPAND)]) + + + epanel = wx.Panel(split) + txt = wx.TextCtrl(epanel, -1, skinyaml, style = wx.TE_MULTILINE | wx.TE_DONTWRAP) + save = wx.Button(epanel, -1, '&Save'); save.Enable(False) + undo = wx.Button(epanel, -1, '&Undo'); undo.Enable(False) + + def doundo(e): + txt.SetValue(skinyaml) + updatebuttons() + + def dosave(e): + global skinyaml + skinyaml = txt.GetValue() + + try: set_yaml('../../res/skins/default', skinyaml) + except SkinException, e: + m = debug_re.search(str(e)) + if m: txt.SetInsertionPoint(txt.XYToPosition(*(int(c) for c in reversed(m.groups())))) + raise + + txt.SetModified(False) + updatebuttons() + + + + undo.Bind(wx.EVT_BUTTON, doundo) + save.Bind(wx.EVT_BUTTON, dosave) + + def updatebuttons(e = None): + undo.Enable(txt.IsModified()) + save.Enable(txt.IsModified()) + + txt.Bind(wx.EVT_TEXT, updatebuttons) + epanel.Sizer = s = wx.BoxSizer(wx.VERTICAL) + + buttons = wx.BoxSizer(wx.HORIZONTAL) + buttons.AddStretchSpacer(1) + buttons.Add(undo) + buttons.Add(save, 0, wx.LEFT, 7) + + s.Add(txt, 1, wx.EXPAND) + s.Add(buttons, 0, wx.EXPAND | wx.ALL, 5) + + split.SplitHorizontally(panel, epanel) + + f.Show() + a.MainLoop() \ No newline at end of file diff --git a/digsby/src/tests/yahootests.py b/digsby/src/tests/yahootests.py new file mode 100644 index 0000000..c3c0642 --- /dev/null +++ b/digsby/src/tests/yahootests.py @@ -0,0 +1,24 @@ +from yahoo.YahooSocket import to_ydict, from_ydict, argsep +import unittest, string + +d = {'1': 'penultimatefire','2': 'some ascii'} +d2= { 1: 'penultimatefire', 2: 'some ascii'} + +bytes = '1\xc0\x80penultimatefire\xc0\x802\xc0\x80some ascii\xc0\x80' + +class YahooTestingSuite(unittest.TestCase): + + def testYDictConstruction(self): + + str = string.Template("1${a}penultimatefire${a}2${a}some ascii${a}") + + self.assertEqual(to_ydict(d), to_ydict(d2)) + self.assertEqual(to_ydict(d), str.substitute(a=argsep)) + self.assertEqual(to_ydict(d), bytes) + + def testYDictFromNetwork(self): + self.assertEqual(from_ydict(bytes), [d]) + self.assertEqual(from_ydict(""), {}) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/digsby/src/util/BeautifulSoup.py b/digsby/src/util/BeautifulSoup.py new file mode 100644 index 0000000..9e3c400 --- /dev/null +++ b/digsby/src/util/BeautifulSoup.py @@ -0,0 +1,1852 @@ +"""Beautiful Soup +Elixir and Tonic +"The Screen-Scraper's Friend" +http://www.crummy.com/software/BeautifulSoup/ + +Beautiful Soup parses a (possibly invalid) XML or HTML document into a +tree representation. It provides methods and Pythonic idioms that make +it easy to navigate, search, and modify the tree. + +A well-structured XML/HTML document yields a well-behaved data +structure. An ill-structured XML/HTML document yields a +correspondingly ill-behaved data structure. If your document is only +locally well-structured, you can use this library to find and process +the well-structured part of it. + +Beautiful Soup works with Python 2.2 and up. It has no external +dependencies, but you'll have more success at converting data to UTF-8 +if you also install these three packages: + +* chardet, for auto-detecting character encodings + http://chardet.feedparser.org/ +* cjkcodecs and iconv_codec, which add more encodings to the ones supported + by stock Python. + http://cjkpython.i18n.org/ + +Beautiful Soup defines classes for two main parsing strategies: + + * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific + language that kind of looks like XML. + + * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid + or invalid. This class has web browser-like heuristics for + obtaining a sensible parse tree in the face of common HTML errors. + +Beautiful Soup also defines a class (UnicodeDammit) for autodetecting +the encoding of an HTML or XML document, and converting it to +Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed +Parser. + +For more than you ever wanted to know about Beautiful Soup, see the +documentation: +http://www.crummy.com/software/BeautifulSoup/documentation.html +""" +from __future__ import generators + +__author__ = "Leonard Richardson (crummy.com)" +__contributors__ = ["Sam Ruby (intertwingly.net)", + "the unwitting Mark Pilgrim (diveintomark.org)", + "http://www.crummy.com/software/BeautifulSoup/AUTHORS.html"] +__version__ = "3.0.3" +__copyright__ = "Copyright (c) 2004-2006 Leonard Richardson" +__license__ = "PSF" + +from sgmllib import SGMLParser as _SGMLParser +class SGMLParser(_SGMLParser): + def convert_charref(self, name): + """Convert character reference, may be overridden.""" + + # + # Overridden from the standard library, to ignore characters between + # 0 and 127, instead of between 0 and 255. + # + try: + n = int(name) + except ValueError: + return + if not 0 <= n <= 127: + return + return self.convert_codepoint(n) + +from sgmllib import SGMLParseError +import codecs +import types +import re +import sgmllib +from htmlentitydefs import name2codepoint + +# This RE makes Beautiful Soup able to parse XML with namespaces. +sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') + +# This RE makes Beautiful Soup capable of recognizing numeric character +# references that use hexadecimal. +sgmllib.charref = re.compile('&#(\d+|x[0-9a-fA-F]+);') + +DEFAULT_OUTPUT_ENCODING = "utf-8" + +# First, the classes that represent markup elements. + + +class PageElement: + """Contains the navigational information for some part of the page + (either a tag or a piece of text)""" + + def setup(self, parent=None, previous=None): + """Sets up the initial relations between this element and + other elements.""" + self.parent = parent + self.previous = previous + self.next = None + self.previousSibling = None + self.nextSibling = None + if self.parent and self.parent.contents: + self.previousSibling = self.parent.contents[-1] + self.previousSibling.nextSibling = self + + def replaceWith(self, replaceWith): + oldParent = self.parent + myIndex = self.parent.contents.index(self) + if hasattr(replaceWith, 'parent') and replaceWith.parent == self.parent: + # We're replacing this element with one of its siblings. + index = self.parent.contents.index(replaceWith) + if index and index < myIndex: + # Furthermore, it comes before this element. That + # means that when we extract it, the index of this + # element will change. + myIndex = myIndex - 1 + self.extract() + oldParent.insert(myIndex, replaceWith) + + def extract(self): + """Destructively rips this element out of the tree.""" + if self.parent: + try: + self.parent.contents.remove(self) + except ValueError: + pass + + #Find the two elements that would be next to each other if + #this element (and any children) hadn't been parsed. Connect + #the two. + lastChild = self._lastRecursiveChild() + nextElement = lastChild.next + + if self.previous: + self.previous.next = nextElement + if nextElement: + nextElement.previous = self.previous + self.previous = None + lastChild.next = None + + self.parent = None + if self.previousSibling: + self.previousSibling.nextSibling = self.nextSibling + if self.nextSibling: + self.nextSibling.previousSibling = self.previousSibling + self.previousSibling = self.nextSibling = None + + def _lastRecursiveChild(self): + "Finds the last element beneath this object to be parsed." + lastChild = self + while hasattr(lastChild, 'contents') and lastChild.contents: + lastChild = lastChild.contents[-1] + return lastChild + + def insert(self, position, newChild): + if (isinstance(newChild, basestring) + or isinstance(newChild, unicode)) \ + and not isinstance(newChild, NavigableString): + newChild = NavigableString(newChild) + + position = min(position, len(self.contents)) + if hasattr(newChild, 'parent') and newChild.parent != None: + # We're 'inserting' an element that's already one + # of this object's children. + if newChild.parent == self: + index = self.find(newChild) + if index and index < position: + # Furthermore we're moving it further down the + # list of this object's children. That means that + # when we extract this element, our target index + # will jump down one. + position = position - 1 + newChild.extract() + + newChild.parent = self + previousChild = None + if position == 0: + newChild.previousSibling = None + newChild.previous = self + else: + previousChild = self.contents[position-1] + newChild.previousSibling = previousChild + newChild.previousSibling.nextSibling = newChild + newChild.previous = previousChild._lastRecursiveChild() + if newChild.previous: + newChild.previous.next = newChild + + newChildsLastElement = newChild._lastRecursiveChild() + + if position >= len(self.contents): + newChild.nextSibling = None + + parent = self + parentsNextSibling = None + while not parentsNextSibling: + parentsNextSibling = parent.nextSibling + parent = parent.parent + if not parent: # This is the last element in the document. + break + if parentsNextSibling: + newChildsLastElement.next = parentsNextSibling + else: + newChildsLastElement.next = None + else: + nextChild = self.contents[position] + newChild.nextSibling = nextChild + if newChild.nextSibling: + newChild.nextSibling.previousSibling = newChild + newChildsLastElement.next = nextChild + + if newChildsLastElement.next: + newChildsLastElement.next.previous = newChildsLastElement + self.contents.insert(position, newChild) + + def findNext(self, name=None, attrs={}, text=None, **kwargs): + """Returns the first item that matches the given criteria and + appears after this Tag in the document.""" + return self._findOne(self.findAllNext, name, attrs, text, **kwargs) + + def findAllNext(self, name=None, attrs={}, text=None, limit=None, + **kwargs): + """Returns all items that match the given criteria and appear + before after Tag in the document.""" + return self._findAll(name, attrs, text, limit, self.nextGenerator) + + def findNextSibling(self, name=None, attrs={}, text=None, **kwargs): + """Returns the closest sibling to this Tag that matches the + given criteria and appears after this Tag in the document.""" + return self._findOne(self.findNextSiblings, name, attrs, text, + **kwargs) + + def findNextSiblings(self, name=None, attrs={}, text=None, limit=None, + **kwargs): + """Returns the siblings of this Tag that match the given + criteria and appear after this Tag in the document.""" + return self._findAll(name, attrs, text, limit, + self.nextSiblingGenerator, **kwargs) + fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x + + def findPrevious(self, name=None, attrs={}, text=None, **kwargs): + """Returns the first item that matches the given criteria and + appears before this Tag in the document.""" + return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs) + + def findAllPrevious(self, name=None, attrs={}, text=None, limit=None, + **kwargs): + """Returns all items that match the given criteria and appear + before this Tag in the document.""" + return self._findAll(name, attrs, text, limit, self.previousGenerator, + **kwargs) + fetchPrevious = findAllPrevious # Compatibility with pre-3.x + + def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs): + """Returns the closest sibling to this Tag that matches the + given criteria and appears before this Tag in the document.""" + return self._findOne(self.findPreviousSiblings, name, attrs, text, + **kwargs) + + def findPreviousSiblings(self, name=None, attrs={}, text=None, + limit=None, **kwargs): + """Returns the siblings of this Tag that match the given + criteria and appear before this Tag in the document.""" + return self._findAll(name, attrs, text, limit, + self.previousSiblingGenerator, **kwargs) + fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x + + def findParent(self, name=None, attrs={}, **kwargs): + """Returns the closest parent of this Tag that matches the given + criteria.""" + # NOTE: We can't use _findOne because findParents takes a different + # set of arguments. + r = None + l = self.findParents(name, attrs, 1) + if l: + r = l[0] + return r + + def findParents(self, name=None, attrs={}, limit=None, **kwargs): + """Returns the parents of this Tag that match the given + criteria.""" + + return self._findAll(name, attrs, None, limit, self.parentGenerator, + **kwargs) + fetchParents = findParents # Compatibility with pre-3.x + + #These methods do the real heavy lifting. + + def _findOne(self, method, name, attrs, text, **kwargs): + r = None + l = method(name, attrs, text, 1, **kwargs) + if l: + r = l[0] + return r + + def _findAll(self, name, attrs, text, limit, generator, **kwargs): + "Iterates over a generator looking for things that match." + + if isinstance(name, SoupStrainer): + strainer = name + else: + # Build a SoupStrainer + strainer = SoupStrainer(name, attrs, text, **kwargs) + results = ResultSet(strainer) + g = generator() + while True: + try: + i = g.next() + except StopIteration: + break + if i: + found = strainer.search(i) + if found: + results.append(found) + if limit and len(results) >= limit: + break + return results + + #These Generators can be used to navigate starting from both + #NavigableStrings and Tags. + def nextGenerator(self): + i = self + while i: + i = i.next + yield i + + def nextSiblingGenerator(self): + i = self + while i: + i = i.nextSibling + yield i + + def previousGenerator(self): + i = self + while i: + i = i.previous + yield i + + def previousSiblingGenerator(self): + i = self + while i: + i = i.previousSibling + yield i + + def parentGenerator(self): + i = self + while i: + i = i.parent + yield i + + # Utility methods + def substituteEncoding(self, str, encoding=None): + encoding = encoding or "utf-8" + return str.replace("%SOUP-ENCODING%", encoding) + + def toEncoding(self, s, encoding=None): + """Encodes an object to a string in some encoding, or to Unicode. + .""" + if isinstance(s, unicode): + if encoding: + s = s.encode(encoding) + elif isinstance(s, str): + if encoding: + s = s.encode(encoding) + else: + s = unicode(s) + else: + if encoding: + s = self.toEncoding(str(s), encoding) + else: + s = unicode(s) + return s + +class NavigableString(unicode, PageElement): + + def __getattr__(self, attr): + """text.string gives you text. This is for backwards + compatibility for Navigable*String, but for CData* it lets you + get the string without the CData wrapper.""" + if attr == 'string': + return self + else: + raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr) + + def __unicode__(self): + return __str__(self, None) + + def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): + if encoding: + return self.encode(encoding) + else: + return self + +class CData(NavigableString): + + def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): + return "" % NavigableString.__str__(self, encoding) + +class ProcessingInstruction(NavigableString): + def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): + output = self + if "%SOUP-ENCODING%" in output: + output = self.substituteEncoding(output, encoding) + return "" % self.toEncoding(output, encoding) + +class Comment(NavigableString): + def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): + return "" % NavigableString.__str__(self, encoding) + +class Declaration(NavigableString): + def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): + return "" % NavigableString.__str__(self, encoding) + +class Tag(PageElement): + """Represents a found HTML tag with its attributes and contents.""" + + XML_ENTITIES_TO_CHARS = { 'apos' : "'", + "quot" : '"', + "amp" : "&", + "lt" : "<", + "gt" : ">" + } + # An RE for finding ampersands that aren't the start of of a + # numeric entity. + BARE_AMPERSAND = re.compile("&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)") + + def __init__(self, parser, name, attrs=None, parent=None, + previous=None): + "Basic constructor." + + # We don't actually store the parser object: that lets extracted + # chunks be garbage-collected + self.parserClass = parser.__class__ + self.isSelfClosing = parser.isSelfClosingTag(name) + self.convertHTMLEntities = parser.convertHTMLEntities + self.name = name + if attrs == None: + attrs = [] + self.attrs = attrs + self.contents = [] + self.setup(parent, previous) + self.hidden = False + self.containsSubstitutions = False + + def get(self, key, default=None): + """Returns the value of the 'key' attribute for the tag, or + the value given for 'default' if it doesn't have that + attribute.""" + return self._getAttrMap().get(key, default) + + def has_key(self, key): + return self._getAttrMap().has_key(key) + + def __getitem__(self, key): + """tag[key] returns the value of the 'key' attribute for the tag, + and throws an exception if it's not there.""" + return self._getAttrMap()[key] + + def __iter__(self): + "Iterating over a tag iterates over its contents." + return iter(self.contents) + + def __len__(self): + "The length of a tag is the length of its list of contents." + return len(self.contents) + + def __contains__(self, x): + return x in self.contents + + def __nonzero__(self): + "A tag is non-None even if it has no contents." + return True + + def __setitem__(self, key, value): + """Setting tag[key] sets the value of the 'key' attribute for the + tag.""" + self._getAttrMap() + self.attrMap[key] = value + found = False + for i in range(0, len(self.attrs)): + if self.attrs[i][0] == key: + self.attrs[i] = (key, value) + found = True + if not found: + self.attrs.append((key, value)) + self._getAttrMap()[key] = value + + def __delitem__(self, key): + "Deleting tag[key] deletes all 'key' attributes for the tag." + for item in self.attrs: + if item[0] == key: + self.attrs.remove(item) + #We don't break because bad HTML can define the same + #attribute multiple times. + self._getAttrMap() + if self.attrMap.has_key(key): + del self.attrMap[key] + + def __call__(self, *args, **kwargs): + """Calling a tag like a function is the same as calling its + findAll() method. Eg. tag('a') returns a list of all the A tags + found within this tag.""" + return apply(self.findAll, args, kwargs) + + def __getattr__(self, tag): + #print "Getattr %s.%s" % (self.__class__, tag) + if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3: + return self.find(tag[:-3]) + elif tag.find('__') != 0: + return self.find(tag) + + def __eq__(self, other): + """Returns true iff this tag has the same name, the same attributes, + and the same contents (recursively) as the given tag. + + NOTE: right now this will return false if two tags have the + same attributes in a different order. Should this be fixed?""" + if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other): + return False + for i in range(0, len(self.contents)): + if self.contents[i] != other.contents[i]: + return False + return True + + def __ne__(self, other): + """Returns true iff this tag is not identical to the other tag, + as defined in __eq__.""" + return not self == other + + def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING): + """Renders this tag as a string.""" + return self.__str__(encoding) + + def __unicode__(self): + return self.__str__(None) + + def _convertEntities(self, match): + x = match.group(1) + if x in name2codepoint: + return unichr(name2codepoint[x]) + elif "&" + x + ";" in self.XML_ENTITIES_TO_CHARS: + return '&%s;' % x + else: + return '&%s;' % x + + def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING, + prettyPrint=False, indentLevel=0): + """Returns a string or Unicode representation of this tag and + its contents. To get Unicode, pass None for encoding. + + NOTE: since Python's HTML parser consumes whitespace, this + method is not certain to reproduce the whitespace present in + the original string.""" + + encodedName = self.toEncoding(self.name, encoding) + + attrs = [] + if self.attrs: + for key, val in self.attrs: + fmt = '%s="%s"' + if isString(val): + if self.containsSubstitutions and '%SOUP-ENCODING%' in val: + val = self.substituteEncoding(val, encoding) + + # The attribute value either: + # + # * Contains no embedded double quotes or single quotes. + # No problem: we enclose it in double quotes. + # * Contains embedded single quotes. No problem: + # double quotes work here too. + # * Contains embedded double quotes. No problem: + # we enclose it in single quotes. + # * Embeds both single _and_ double quotes. This + # can't happen naturally, but it can happen if + # you modify an attribute value after parsing + # the document. Now we have a bit of a + # problem. We solve it by enclosing the + # attribute in single quotes, and escaping any + # embedded single quotes to XML entities. + if '"' in val: + # This can't happen naturally, but it can happen + # if you modify an attribute value after parsing. + if "'" in val: + val = val.replace('"', """) + else: + fmt = "%s='%s'" + + # Optionally convert any HTML entities + if self.convertHTMLEntities: + val = re.sub("&(\w+);", self._convertEntities, val) + + # Now we're okay w/r/t quotes. But the attribute + # value might also contain angle brackets, or + # ampersands that aren't part of entities. We need + # to escape those to XML entities too. + val = val.replace("<", "<").replace(">", ">") + val = self.BARE_AMPERSAND.sub("&", val) + + + attrs.append(fmt % (self.toEncoding(key, encoding), + self.toEncoding(val, encoding))) + close = '' + closeTag = '' + if self.isSelfClosing: + close = ' /' + else: + closeTag = '' % encodedName + + indentTag, indentContents = 0, 0 + if prettyPrint: + indentTag = indentLevel + space = (' ' * (indentTag-1)) + indentContents = indentTag + 1 + contents = self.renderContents(encoding, prettyPrint, indentContents) + if self.hidden: + s = contents + else: + s = [] + attributeString = '' + if attrs: + attributeString = ' ' + ' '.join(attrs) + if prettyPrint: + s.append(space) + s.append('<%s%s%s>' % (encodedName, attributeString, close)) + if prettyPrint: + s.append("\n") + s.append(contents) + if prettyPrint and contents and contents[-1] != "\n": + s.append("\n") + if prettyPrint and closeTag: + s.append(space) + s.append(closeTag) + if prettyPrint and closeTag and self.nextSibling: + s.append("\n") + s = ''.join(s) + return s + + def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING): + return self.__str__(encoding, True) + + def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, + prettyPrint=False, indentLevel=0): + """Renders the contents of this tag as a string in the given + encoding. If encoding is None, returns a Unicode string..""" + s=[] + for c in self: + text = None + if isinstance(c, NavigableString): + text = c.__str__(encoding) + elif isinstance(c, Tag): + s.append(c.__str__(encoding, prettyPrint, indentLevel)) + if text and prettyPrint: + text = text.strip() + if text: + if prettyPrint: + s.append(" " * (indentLevel-1)) + s.append(text) + if prettyPrint: + s.append("\n") + return ''.join(s) + + #Soup methods + + def find(self, name=None, attrs={}, recursive=True, text=None, + **kwargs): + """Return only the first child of this Tag matching the given + criteria.""" + r = None + l = self.findAll(name, attrs, recursive, text, 1, **kwargs) + if l: + r = l[0] + return r + findChild = find + + def findAll(self, name=None, attrs={}, recursive=True, text=None, + limit=None, **kwargs): + """Extracts a list of Tag objects that match the given + criteria. You can specify the name of the Tag and any + attributes you want the Tag to have. + + The value of a key-value pair in the 'attrs' map can be a + string, a list of strings, a regular expression object, or a + callable that takes a string and returns whether or not the + string matches for some custom definition of 'matches'. The + same is true of the tag name.""" + generator = self.recursiveChildGenerator + if not recursive: + generator = self.childGenerator + return self._findAll(name, attrs, text, limit, generator, **kwargs) + findChildren = findAll + + # Pre-3.x compatibility methods + first = find + fetch = findAll + + def fetchText(self, text=None, recursive=True, limit=None): + return self.findAll(text=text, recursive=recursive, limit=limit) + + def firstText(self, text=None, recursive=True): + return self.find(text=text, recursive=recursive) + + #Utility methods + + def append(self, tag): + """Appends the given tag to the contents of this tag.""" + self.contents.append(tag) + + #Private methods + + def _getAttrMap(self): + """Initializes a map representation of this tag's attributes, + if not already initialized.""" + if not getattr(self, 'attrMap'): + self.attrMap = {} + for (key, value) in self.attrs: + self.attrMap[key] = value + return self.attrMap + + #Generator methods + def childGenerator(self): + for i in range(0, len(self.contents)): + yield self.contents[i] + raise StopIteration + + def recursiveChildGenerator(self): + stack = [(self, 0)] + while stack: + tag, start = stack.pop() + if isinstance(tag, Tag): + for i in range(start, len(tag.contents)): + a = tag.contents[i] + yield a + if isinstance(a, Tag) and tag.contents: + if i < len(tag.contents) - 1: + stack.append((tag, i+1)) + stack.append((a, 0)) + break + raise StopIteration + +# Next, a couple classes to represent queries and their results. +class SoupStrainer: + """Encapsulates a number of ways of matching a markup element (tag or + text).""" + + def __init__(self, name=None, attrs={}, text=None, **kwargs): + self.name = name + if isString(attrs): + kwargs['class'] = attrs + attrs = None + if kwargs: + if attrs: + attrs = attrs.copy() + attrs.update(kwargs) + else: + attrs = kwargs + self.attrs = attrs + self.text = text + + def __str__(self): + if self.text: + return self.text + else: + return "%s|%s" % (self.name, self.attrs) + + def searchTag(self, markupName=None, markupAttrs={}): + found = None + markup = None + if isinstance(markupName, Tag): + markup = markupName + markupAttrs = markup + callFunctionWithTagData = callable(self.name) \ + and not isinstance(markupName, Tag) + + if (not self.name) \ + or callFunctionWithTagData \ + or (markup and self._matches(markup, self.name)) \ + or (not markup and self._matches(markupName, self.name)): + if callFunctionWithTagData: + match = self.name(markupName, markupAttrs) + else: + match = True + markupAttrMap = None + for attr, matchAgainst in self.attrs.items(): + if not markupAttrMap: + if hasattr(markupAttrs, 'get'): + markupAttrMap = markupAttrs + else: + markupAttrMap = {} + for k,v in markupAttrs: + markupAttrMap[k] = v + attrValue = markupAttrMap.get(attr) + if not self._matches(attrValue, matchAgainst): + match = False + break + if match: + if markup: + found = markup + else: + found = markupName + return found + + def search(self, markup): + #print 'looking for %s in %s' % (self, markup) + found = None + # If given a list of items, scan it for a text element that + # matches. + if isList(markup) and not isinstance(markup, Tag): + for element in markup: + if isinstance(element, NavigableString) \ + and self.search(element): + found = element + break + # If it's a Tag, make sure its name or attributes match. + # Don't bother with Tags if we're searching for text. + elif isinstance(markup, Tag): + if not self.text: + found = self.searchTag(markup) + # If it's text, make sure the text matches. + elif isinstance(markup, NavigableString) or \ + isString(markup): + if self._matches(markup, self.text): + found = markup + else: + raise Exception, "I don't know how to match against a %s" \ + % markup.__class__ + return found + + def _matches(self, markup, matchAgainst): + #print "Matching %s against %s" % (markup, matchAgainst) + result = False + if matchAgainst == True and type(matchAgainst) == types.BooleanType: + result = markup != None + elif callable(matchAgainst): + result = matchAgainst(markup) + else: + #Custom match methods take the tag as an argument, but all + #other ways of matching match the tag name as a string. + if isinstance(markup, Tag): + markup = markup.name + if markup and not isString(markup): + markup = unicode(markup) + #Now we know that chunk is either a string, or None. + if hasattr(matchAgainst, 'match'): + # It's a regexp object. + result = markup and matchAgainst.search(markup) + elif isList(matchAgainst): + result = markup in matchAgainst + elif hasattr(matchAgainst, 'items'): + result = markup.has_key(matchAgainst) + elif matchAgainst and isString(markup): + if isinstance(markup, unicode): + matchAgainst = unicode(matchAgainst) + else: + matchAgainst = str(matchAgainst) + + if not result: + result = matchAgainst == markup + return result + +class ResultSet(list): + """A ResultSet is just a list that keeps track of the SoupStrainer + that created it.""" + def __init__(self, source): + list.__init__([]) + self.source = source + +# Now, some helper functions. + +def isList(l): + """Convenience method that works with all 2.x versions of Python + to determine whether or not something is listlike.""" + return hasattr(l, '__iter__') \ + or (type(l) in (types.ListType, types.TupleType)) + +def isString(s): + """Convenience method that works with all 2.x versions of Python + to determine whether or not something is stringlike.""" + try: + return isinstance(s, unicode) or isinstance(s, basestring) + except NameError: + return isinstance(s, str) + +def buildTagMap(default, *args): + """Turns a list of maps, lists, or scalars into a single map. + Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and + NESTING_RESET_TAGS maps out of lists and partial maps.""" + built = {} + for portion in args: + if hasattr(portion, 'items'): + #It's a map. Merge it. + for k,v in portion.items(): + built[k] = v + elif isList(portion): + #It's a list. Map each item to the default. + for k in portion: + built[k] = default + else: + #It's a scalar. Map it to the default. + built[portion] = default + return built + +# Now, the parser classes. + +class BeautifulStoneSoup(Tag, SGMLParser): + + """This class contains the basic parser and search code. It defines + a parser that knows nothing about tag behavior except for the + following: + + You can't close a tag without closing all the tags it encloses. + That is, "" actually means + "". + + [Another possible explanation is "", but since + this class defines no SELF_CLOSING_TAGS, it will never use that + explanation.] + + This class is useful for parsing XML or made-up markup languages, + or when BeautifulSoup makes an assumption counter to what you were + expecting.""" + + SELF_CLOSING_TAGS = {} + NESTABLE_TAGS = {} + RESET_NESTING_TAGS = {} + QUOTE_TAGS = {} + + MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'), + lambda x: x.group(1) + ' />'), + (re.compile(']*)>'), + lambda x: '') + ] + + ROOT_TAG_NAME = u'[document]' + + HTML_ENTITIES = "html" + XML_ENTITIES = "xml" + ALL_ENTITIES = [HTML_ENTITIES, XML_ENTITIES] + + def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None, + markupMassage=True, smartQuotesTo=XML_ENTITIES, + convertEntities=None, selfClosingTags=None): + """The Soup object is initialized as the 'root tag', and the + provided markup (which can be a string or a file-like object) + is fed into the underlying parser. + + sgmllib will process most bad HTML, and the BeautifulSoup + class has some tricks for dealing with some HTML that kills + sgmllib, but Beautiful Soup can nonetheless choke or lose data + if your data uses self-closing tags or declarations + incorrectly. + + By default, Beautiful Soup uses regexes to sanitize input, + avoiding the vast majority of these problems. If the problems + don't apply to you, pass in False for markupMassage, and + you'll get better performance. + + The default parser massage techniques fix the two most common + instances of invalid HTML that choke sgmllib: + +
(No space between name of closing tag and tag close) + (Extraneous whitespace in declaration) + + You can pass in a custom list of (RE object, replace method) + tuples to get Beautiful Soup to scrub your input the way you + want.""" + + self.parseOnlyThese = parseOnlyThese + self.fromEncoding = fromEncoding + self.smartQuotesTo = smartQuotesTo + + if convertEntities: + # It doesn't make sense to convert encoded characters to + # entities even while you're converting entities to Unicode. + # Just convert it all to Unicode. + self.smartQuotesTo = None + + if isList(convertEntities): + self.convertHTMLEntities = self.HTML_ENTITIES in convertEntities + self.convertXMLEntities = self.XML_ENTITIES in convertEntities + else: + self.convertHTMLEntities = self.HTML_ENTITIES == convertEntities + self.convertXMLEntities = self.XML_ENTITIES == convertEntities + + self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags) + SGMLParser.__init__(self) + + if hasattr(markup, 'read'): # It's a file-type object. + markup = markup.read() + self.markup = markup + self.markupMassage = markupMassage + try: + self._feed() + except StopParsing: + pass + self.markup = None # The markup can now be GCed + + def _feed(self, inDocumentEncoding=None): + # Convert the document to Unicode. + markup = self.markup + if isinstance(markup, unicode): + if not hasattr(self, 'originalEncoding'): + self.originalEncoding = None + else: + dammit = UnicodeDammit\ + (markup, [self.fromEncoding, inDocumentEncoding], + smartQuotesTo=self.smartQuotesTo) + markup = dammit.unicode + self.originalEncoding = dammit.originalEncoding + if markup: + if self.markupMassage: + if not isList(self.markupMassage): + self.markupMassage = self.MARKUP_MASSAGE + for fix, m in self.markupMassage: + markup = fix.sub(m, markup) + self.reset() + + SGMLParser.feed(self, markup or "") + SGMLParser.close(self) + # Close out any unfinished strings and close all the open tags. + self.endData() + while self.currentTag.name != self.ROOT_TAG_NAME: + self.popTag() + + def __getattr__(self, methodName): + """This method routes method call requests to either the SGMLParser + superclass or the Tag superclass, depending on the method name.""" + #print "__getattr__ called on %s.%s" % (self.__class__, methodName) + + if methodName.find('start_') == 0 or methodName.find('end_') == 0 \ + or methodName.find('do_') == 0: + return SGMLParser.__getattr__(self, methodName) + elif methodName.find('__') != 0: + return Tag.__getattr__(self, methodName) + else: + raise AttributeError + + def isSelfClosingTag(self, name): + """Returns true iff the given string is the name of a + self-closing tag according to this parser.""" + return self.SELF_CLOSING_TAGS.has_key(name) \ + or self.instanceSelfClosingTags.has_key(name) + + def reset(self): + Tag.__init__(self, self, self.ROOT_TAG_NAME) + self.hidden = 1 + SGMLParser.reset(self) + self.currentData = [] + self.currentTag = None + self.tagStack = [] + self.quoteStack = [] + self.pushTag(self) + + def popTag(self): + tag = self.tagStack.pop() + # Tags with just one string-owning child get the child as a + # 'string' property, so that soup.tag.string is shorthand for + # soup.tag.contents[0] + if len(self.currentTag.contents) == 1 and \ + isinstance(self.currentTag.contents[0], NavigableString): + self.currentTag.string = self.currentTag.contents[0] + + #print "Pop", tag.name + if self.tagStack: + self.currentTag = self.tagStack[-1] + return self.currentTag + + def pushTag(self, tag): + #print "Push", tag.name + if self.currentTag: + self.currentTag.append(tag) + self.tagStack.append(tag) + self.currentTag = self.tagStack[-1] + + def endData(self, containerClass=NavigableString): + if self.currentData: + currentData = ''.join(self.currentData) + if currentData.endswith('<') and self.convertHTMLEntities: + currentData = currentData[:-1] + '<' + if not currentData.strip(): + if '\n' in currentData: + currentData = '\n' + else: + currentData = ' ' + self.currentData = [] + if self.parseOnlyThese and len(self.tagStack) <= 1 and \ + (not self.parseOnlyThese.text or \ + not self.parseOnlyThese.search(currentData)): + return + o = containerClass(currentData) + o.setup(self.currentTag, self.previous) + if self.previous: + self.previous.next = o + self.previous = o + self.currentTag.contents.append(o) + + + def _popToTag(self, name, inclusivePop=True): + """Pops the tag stack up to and including the most recent + instance of the given tag. If inclusivePop is false, pops the tag + stack up to but *not* including the most recent instqance of + the given tag.""" + #print "Popping to %s" % name + if name == self.ROOT_TAG_NAME: + return + + numPops = 0 + mostRecentTag = None + for i in range(len(self.tagStack)-1, 0, -1): + if name == self.tagStack[i].name: + numPops = len(self.tagStack)-i + break + if not inclusivePop: + numPops = numPops - 1 + + for i in range(0, numPops): + mostRecentTag = self.popTag() + return mostRecentTag + + def _smartPop(self, name): + + """We need to pop up to the previous tag of this type, unless + one of this tag's nesting reset triggers comes between this + tag and the previous tag of this type, OR unless this tag is a + generic nesting trigger and another generic nesting trigger + comes between this tag and the previous tag of this type. + + Examples: +

FooBar

should pop to 'p', not 'b'. +

FooBar

should pop to 'table', not 'p'. +

Foo

Bar

should pop to 'tr', not 'p'. +

FooBar

should pop to 'p', not 'b'. + +

    • *
    • * should pop to 'ul', not the first 'li'. +
  • ** should pop to 'table', not the first 'tr' + tag should + implicitly close the previous tag within the same
    ** should pop to 'tr', not the first 'td' + """ + + nestingResetTriggers = self.NESTABLE_TAGS.get(name) + isNestable = nestingResetTriggers != None + isResetNesting = self.RESET_NESTING_TAGS.has_key(name) + popTo = None + inclusive = True + for i in range(len(self.tagStack)-1, 0, -1): + p = self.tagStack[i] + if (not p or p.name == name) and not isNestable: + #Non-nestable tags get popped to the top or to their + #last occurance. + popTo = name + break + if (nestingResetTriggers != None + and p.name in nestingResetTriggers) \ + or (nestingResetTriggers == None and isResetNesting + and self.RESET_NESTING_TAGS.has_key(p.name)): + + #If we encounter one of the nesting reset triggers + #peculiar to this tag, or we encounter another tag + #that causes nesting to reset, pop up to but not + #including that tag. + popTo = p.name + inclusive = False + break + p = p.parent + if popTo: + self._popToTag(popTo, inclusive) + + def unknown_starttag(self, name, attrs, selfClosing=0): + #print "Start tag %s: %s" % (name, attrs) + if self.quoteStack: + #This is not a real tag. + #print "<%s> is not real!" % name + attrs = ''.join(map(lambda(x, y): ' %s="%s"' % (x, y), attrs)) + self.currentData.append('<%s%s>' % (name, attrs)) + return + self.endData() + + if not self.isSelfClosingTag(name) and not selfClosing: + self._smartPop(name) + + if self.parseOnlyThese and len(self.tagStack) <= 1 \ + and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)): + return + + tag = Tag(self, name, attrs, self.currentTag, self.previous) + if self.previous: + self.previous.next = tag + self.previous = tag + self.pushTag(tag) + if selfClosing or self.isSelfClosingTag(name): + self.popTag() + if name in self.QUOTE_TAGS: + #print "Beginning quote (%s)" % name + self.quoteStack.append(name) + self.literal = 1 + return tag + + def unknown_endtag(self, name): + #print "End tag %s" % name + if self.quoteStack and self.quoteStack[-1] != name: + #This is not a real end tag. + #print " is not real!" % name + self.currentData.append('' % name) + return + self.endData() + self._popToTag(name) + if self.quoteStack and self.quoteStack[-1] == name: + self.quoteStack.pop() + self.literal = (len(self.quoteStack) > 0) + + def handle_data(self, data): + if self.convertHTMLEntities and data: + if data[0] == '&': + data = self.BARE_AMPERSAND.sub("&",data) + else: + data = data.replace('&','&') \ + .replace('<','<') \ + .replace('>','>') + + self.currentData.append(data) + + def _toStringSubclass(self, text, subclass): + """Adds a certain piece of text to the tree as a NavigableString + subclass.""" + self.endData() + self.handle_data(text) + self.endData(subclass) + + def handle_pi(self, text): + """Handle a processing instruction as a ProcessingInstruction + object, possibly one with a %SOUP-ENCODING% slot into which an + encoding will be plugged later.""" + if text[:3] == "xml": + text = "xml version='1.0' encoding='%SOUP-ENCODING%'" + self._toStringSubclass(text, ProcessingInstruction) + + def handle_comment(self, text): + "Handle comments as Comment objects." + self._toStringSubclass(text, Comment) + + def handle_charref(self, ref): + "Handle character references as data." + if ref[0] == 'x': + data = unichr(int(ref[1:],16)) + else: + try: + data = unichr(int(ref)) + except ValueError: + data = unichr(int(ref, 16)) + + if u'\x80' <= data <= u'\x9F': + data = UnicodeDammit.subMSChar(chr(ord(data)), self.smartQuotesTo) + elif not self.convertHTMLEntities and not self.convertXMLEntities: + data = '&#%s;' % ref + + self.handle_data(data) + + def handle_entityref(self, ref): + """Handle entity references as data, possibly converting known + HTML entity references to the corresponding Unicode + characters.""" + replaceWithXMLEntity = self.convertXMLEntities and \ + self.XML_ENTITIES_TO_CHARS.has_key(ref) + if self.convertHTMLEntities or replaceWithXMLEntity: + try: + data = unichr(name2codepoint[ref]) + except KeyError: + if replaceWithXMLEntity: + data = self.XML_ENTITIES_TO_CHARS.get(ref) + else: + data="&%s" % ref + else: + data = '&%s;' % ref + self.handle_data(data) + + def handle_decl(self, data): + "Handle DOCTYPEs and the like as Declaration objects." + self._toStringSubclass(data, Declaration) + + def parse_declaration(self, i): + """Treat a bogus SGML declaration as raw data. Treat a CDATA + declaration as a CData object.""" + j = None + if self.rawdata[i:i+9] == '', i) + if k == -1: + k = len(self.rawdata) + data = self.rawdata[i+9:k] + j = k+3 + self._toStringSubclass(data, CData) + else: + try: + j = SGMLParser.parse_declaration(self, i) + except SGMLParseError: + toHandle = self.rawdata[i:] + self.handle_data(toHandle) + j = i + len(toHandle) + return j + +class BeautifulSoup(BeautifulStoneSoup): + + """This parser knows the following facts about HTML: + + * Some tags have no closing tag and should be interpreted as being + closed as soon as they are encountered. + + * The text inside some tags (ie. 'script') may contain tags which + are not really part of the document and which should be parsed + as text, not tags. If you want to parse the text as tags, you can + always fetch it and parse it explicitly. + + * Tag nesting rules: + + Most tags can't be nested at all. For instance, the occurance of + a

    tag should implicitly close the previous

    tag. + +

    Para1

    Para2 + should be transformed into: +

    Para1

    Para2 + + Some tags can be nested arbitrarily. For instance, the occurance + of a

    tag should _not_ implicitly close the previous +
    tag. + + Alice said:
    Bob said:
    Blah + should NOT be transformed into: + Alice said:
    Bob said:
    Blah + + Some tags can be nested, but the nesting is reset by the + interposition of other tags. For instance, a
    , + but not close a tag in another table. + +
    BlahBlah + should be transformed into: +
    BlahBlah + but, + Blah
    Blah + should NOT be transformed into + Blah
    Blah + + Differing assumptions about tag nesting rules are a major source + of problems with the BeautifulSoup class. If BeautifulSoup is not + treating as nestable a tag your page author treats as nestable, + try ICantBelieveItsBeautifulSoup, MinimalSoup, or + BeautifulStoneSoup before writing your own subclass.""" + + def __init__(self, *args, **kwargs): + if not kwargs.has_key('smartQuotesTo'): + kwargs['smartQuotesTo'] = self.HTML_ENTITIES + BeautifulStoneSoup.__init__(self, *args, **kwargs) + + SELF_CLOSING_TAGS = buildTagMap(None, + ['br' , 'hr', 'input', 'img', 'meta', + 'spacer', 'link', 'frame', 'base']) + + QUOTE_TAGS = {'script': None} + + #According to the HTML standard, each of these inline tags can + #contain another tag of the same type. Furthermore, it's common + #to actually use these tags this way. + NESTABLE_INLINE_TAGS = ['span', 'font', 'q', 'object', 'bdo', 'sub', 'sup', + 'center'] + + #According to the HTML standard, these block tags can contain + #another tag of the same type. Furthermore, it's common + #to actually use these tags this way. + NESTABLE_BLOCK_TAGS = ['blockquote', 'div', 'fieldset', 'ins', 'del'] + + #Lists can contain other lists, but there are restrictions. + NESTABLE_LIST_TAGS = { 'ol' : [], + 'ul' : [], + 'li' : ['ul', 'ol'], + 'dl' : [], + 'dd' : ['dl'], + 'dt' : ['dl'] } + + #Tables can contain other tables, but there are restrictions. + NESTABLE_TABLE_TAGS = {'table' : [], + 'tr' : ['table', 'tbody', 'tfoot', 'thead'], + 'td' : ['tr'], + 'th' : ['tr'], + 'thead' : ['table'], + 'tbody' : ['table'], + 'tfoot' : ['table'], + } + + NON_NESTABLE_BLOCK_TAGS = ['address', 'form', 'p', 'pre'] + + #If one of these tags is encountered, all tags up to the next tag of + #this type are popped. + RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript', + NON_NESTABLE_BLOCK_TAGS, + NESTABLE_LIST_TAGS, + NESTABLE_TABLE_TAGS) + + NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS, + NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) + + # Used to detect the charset in a META tag; see start_meta + CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)") + + def start_meta(self, attrs): + """Beautiful Soup can detect a charset included in a META tag, + try to convert the document to that charset, and re-parse the + document from the beginning.""" + httpEquiv = None + contentType = None + contentTypeIndex = None + tagNeedsEncodingSubstitution = False + + for i in range(0, len(attrs)): + key, value = attrs[i] + key = key.lower() + if key == 'http-equiv': + httpEquiv = value + elif key == 'content': + contentType = value + contentTypeIndex = i + + if httpEquiv and contentType: # It's an interesting meta tag. + match = self.CHARSET_RE.search(contentType) + if match: + if getattr(self, 'declaredHTMLEncoding') or \ + (self.originalEncoding == self.fromEncoding): + # This is our second pass through the document, or + # else an encoding was specified explicitly and it + # worked. Rewrite the meta tag. + newAttr = self.CHARSET_RE.sub\ + (lambda(match):match.group(1) + + "%SOUP-ENCODING%", value) + attrs[contentTypeIndex] = (attrs[contentTypeIndex][0], + newAttr) + tagNeedsEncodingSubstitution = True + else: + # This is our first pass through the document. + # Go through it again with the new information. + newCharset = match.group(3) + if newCharset and newCharset != self.originalEncoding: + self.declaredHTMLEncoding = newCharset + self._feed(self.declaredHTMLEncoding) + raise StopParsing + tag = self.unknown_starttag("meta", attrs) + if tag and tagNeedsEncodingSubstitution: + tag.containsSubstitutions = True + +class StopParsing(Exception): + pass + +class ICantBelieveItsBeautifulSoup(BeautifulSoup): + + """The BeautifulSoup class is oriented towards skipping over + common HTML errors like unclosed tags. However, sometimes it makes + errors of its own. For instance, consider this fragment: + + FooBar + + This is perfectly valid (if bizarre) HTML. However, the + BeautifulSoup class will implicitly close the first b tag when it + encounters the second 'b'. It will think the author wrote + "FooBar", and didn't close the first 'b' tag, because + there's no real-world reason to bold something that's already + bold. When it encounters '' it will close two more 'b' + tags, for a grand total of three tags closed instead of two. This + can throw off the rest of your document structure. The same is + true of a number of other tags, listed below. + + It's much more common for someone to forget to close a 'b' tag + than to actually use nested 'b' tags, and the BeautifulSoup class + handles the common case. This class handles the not-co-common + case: where you can't believe someone wrote what they did, but + it's valid HTML and BeautifulSoup screwed up by assuming it + wouldn't be.""" + + I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \ + ['em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong', + 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b', + 'big'] + + I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ['noscript'] + + NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS, + I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS, + I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS) + +class MinimalSoup(BeautifulSoup): + """The MinimalSoup class is for parsing HTML that contains + pathologically bad markup. It makes no assumptions about tag + nesting, but it does know which tags are self-closing, that + + # * + # etc. + + for redirecter in (self._find_http_redirect, self._find_js_redirect): + redirect = redirecter(resp) + if redirect is not None: + if not redirect.startswith('http'): + if not redirect.startswith('/'): + redirect = '/' + redirect + redirect = self.request.get_type() + '://' + self.request.get_host() + redirect + + parsed = urlparse.urlparse(redirect) + if parsed.path == '': + d = parsed._asdict() + d['path'] = '/' + redirect = urlparse.urlunparse(type(parsed)(**d)) + + log.debug('got redirect: %r', redirect) + return redirect + + return None + + def _find_http_redirect(self, resp): + if resp.code in (301, 302): + return resp.headers.get('Location', None) + + def _find_js_redirect(self, resp): + for redirect_re, url_group_id in self.js_redirect_res: + match = redirect_re.search(resp.content) + if match: + new_url = match.group(url_group_id) + if new_url: + return new_url + + def check_resp_for_errors(self, resp): + # TODO: or maybe leave this for subclasses + return None + + def _check_error(self, err = None, resp = None): + if resp is not None: + self._on_error((err, resp)) + else: + self._on_error(err) + + def _on_error(self, e = None): + self.retries -= 1 + if self.retries: + if self.pause_for_attempts > 0: + Timer(self.pause_for_attempts, self._attempt_open).start() + else: + self._attempt_open() + else: + self.finish('error', e) + + def finish(self, result, *args): + cb, self.callback = self.callback, None + self._sub_request = self.request = self.openfunc = None + getattr(cb, result, lambda * a: None)(*args) + +def dispatcher(what, arg_getter): + def dispatch(self, *args, **req_options): + name = arg_getter(args) + handler = getattr(self, '%s_%s' % (what, name), getattr(self, '%s_default' % what, None)) + + if handler is not None: + return handler(*args, **req_options) + else: + log.error('No default handler for %r', what) + return dispatch + +class WebScraperBase(object): + CookieJarFactory = cookielib.CookieJar # I almost called it "CookieCutter". + HttpOpenerFactory = staticmethod(build_opener) # TODO: use asynchttp here + RequestFactory = staticmethod(urllib2.Request.make_request) + @classmethod + def RequestOpenerFactory(cls, open, req, **kwds): + return RequestOpener(threaded(open), req, **kwds) + domain = None # 'www.hotmail.com' or something like that. + + urls = {} # Convenience for mapping of names to urls so you don't have to implement lots of build_request_* methods. + + def __init__(self): + self._waiting = set() + + self._callbacks = {} + self.init_http() + self._batching = False + self._batchqueue = [] + + def init_http(self): + self._jar = self.CookieJarFactory() + self.http = self.HttpOpenerFactory(urllib2.HTTPCookieProcessor(self._jar)) + + def get_cookie(self, key, default = sentinel, domain = None, path='/'): + if domain is None: + domain = self.domain + + val = default + + try: + with self._jar._cookies_lock: + val = self._jar._cookies[domain][path][key].value + except (AttributeError, KeyError), e: + if val is sentinel: + raise e + else: + return val + else: + return val + + def set_cookie(self, key, value, domain = None, path = '/'): + if domain is None: + domain = self.domain + + with self._jar._cookies_lock: + domain_dict = self._jar._cookies.setdefault(domain, {}) + path_dict = domain_dict.setdefault(path, {}) + + cookie = path_dict.get(key, None) + if cookie is None: + cookie = build_cookie(key, value, domain = domain, path = path) + path_dict[key] = cookie + else: + cookie.value = value + + def set_waiting(self, *things): + self._waiting.update(things) + + def clear_waiting(self, *things): + self._waiting -= set(things) + + if not self._waiting: + self.done_waiting() + + def done_waiting(self): + pass + + @contextlib.contextmanager + def batch(self): + if self._batching: + raise Exception('Can\'t do more than one batch of requests at a time.') + self._batching = True + + try: + yield self + finally: + self._batching = False + + while self._batchqueue: + name, req, req_options = self._batchqueue.pop(0) + self.perform_request(name, req, **req_options) + + @callsback + def request(self, name, callback = None, **req_options): + if name in self._waiting: + log.warning('already waiting for %r', name) + return + + self._callbacks[name] = callback + req = self.build_request(name, **req_options) + + if self._batching: + self.set_waiting(name) + self._batchqueue.append((name, req, req_options)) + return + + self.perform_request(name, req, **req_options) + + def perform_request(self, name, req, **req_options): + self.set_waiting(name) + + if req is None: + return self.error_handler(name, req_options)(Exception("No request created for %r" % name)) + + reqopen = self.RequestOpenerFactory(self.http.open, req, **req_options) + + reqopen.open(success = self.success_handler(name, req_options), + error = self.error_handler(name, req_options)) + + def error_handler(self, name, req_options): + def handler(e = None): + try: + e = self.preprocess_resp(name, e, **req_options) + except Exception, exc: + if not req_options.get('quiet', False): + traceback.print_exc() +# self.error_handler(name, req_options)(exc) +# return + + self.clear_waiting(name) + cb = self._callbacks.pop(name, None) + retval = self.handle_error(name, e, **req_options) + + if cb is not None: + cb.error(e) + + return retval + + return handler + + def success_handler(self, name, req_options): + def handler(resp): + try: + resp = self.preprocess_resp(name, resp, **req_options) + except Exception, exc: + traceback.print_exc() + self.error_handler(name, req_options)(exc) + return + + try: + newresp = self.handle_success(name, resp, **req_options) + except Exception, exc: + traceback.print_exc() + self.error_handler(name, req_options)(exc) + return + + if newresp is not None: + resp = newresp + + cb = self._callbacks.pop(name, None) + if cb is not None: + cb.success(resp) + self.clear_waiting(name) + + return newresp + + return handler + + build_request = dispatcher('build_request', itemgetter0) + handle_error = dispatcher('handle_error', itemgetter0) + preprocess_resp= dispatcher('preprocess_resp', itemgetter0) + handle_success = dispatcher('handle_success', itemgetter0) + + def build_request_default(self, name, **req_options): + link = self.urls[name] + + if callable(link): + link = link() + + return self.RequestFactory(link, **req_options) + + def handle_error_default(self, name, e, **req_options): + log.error("Error requesting %r (options = %r): %r", name, req_options, e) + + def handle_success_default(self, name, resp, **req_options): + if resp.document is not None: + log.debug_s("document body: %r", HTML.tostring(resp.document, pretty_print = True)) + else: + log.info('Got None for lxml doc. code/status= %r', resp.code, resp.msg, str(resp.headers)) + + def preprocess_resp_default(self, name, resp, **req_options): + data = resp.content + # Since this is primarily for use with HTML screen-scraping, we're going + # to use lxml's HTML parser. + + if data: + document = HTML.fromstring(data, base_url = resp.geturl()) + document.make_links_absolute() + resp.document = document + else: + resp.document = None + + return resp + +class AsyncRequestOpener(RequestOpener): + request_cls = CookieJarHTTPMaster.request_cls + def _check_success(self, req, resp): + return super(AsyncRequestOpener, self)._check_success(resp) + def _check_error(self, req, resp=None): + if resp == None: + resp=req + return super(AsyncRequestOpener, self)._check_error(resp) + +class AsyncWebScraper(WebScraperBase): + HttpOpenerFactory = CookieJarHTTPMaster +# RequestFactory = staticmethod(CookieJarHTTPMaster.request_cls.make_request) +# @classmethod + def RequestOpenerFactory(self, open, req, **kwds): + return AsyncRequestOpener(open, req, **kwds) + + def init_http(self): + self._jar = self.CookieJarFactory() + self.http = self.HttpOpenerFactory(jar=self._jar) + + def RequestFactory(self, *a, **k): + headers = dict(getattr(self.http, 'addheaders', {})) + headers.update(k.get('headers', {})) + k['headers'] = headers + ret = self.http.request_cls.make_request(*a, **k) + return ret + +WebScraper = AsyncWebScraper + +if __name__ == '__main__': + pass diff --git a/digsby/src/util/ie.py b/digsby/src/util/ie.py new file mode 100644 index 0000000..8390245 --- /dev/null +++ b/digsby/src/util/ie.py @@ -0,0 +1,389 @@ +import collections +import logging +log = logging.getLogger('iebrowser') +#log.setLevel(logging.NOTSET) + +import pythoncom +import win32com.client as com + +#pythoncom.CoInitialize() + +def COMEvent(f): + def wrapper(self, *a, **k): + ename = f.__name__ + if self.debug: + print 'Calling %s event with args: %r, %r' % (ename, a, k) + cancel = self._OnEvent(ename, *a, **k) + if not cancel: f(self, *a, **k) + return wrapper + +class IEEvents(object): + debug = False + def __init__(self): + self._bound = collections.defaultdict(list) + + def _OnEvent(self, e_name, *e_args, **e_kwargs): + cbs = self._bound.get(e_name, []) + if not cbs: return + + for cb in cbs: + try: + cb((e_args, e_kwargs)) + except StopIteration: + pass + + def _Bind(self, ename, callback): + assert callable(getattr(self, ename)), '%s is not an event of the WebBrowser or WebBrowser2 interface!' %ename + if callback not in self._bound[ename]: + self._bound[ename].append(callback) + + def _UnBind(self, ename, callback): + assert callback in self._bound[ename], '%r not bound to %s' % (callback, ename) + self._bound[ename].remove(callback) + + + ##### START:webbrowser2 events ##### + @COMEvent + def OnUpdatePageStatus(self, pDisp=sentinel, nPage=sentinel, fDone=sentinel): + """Fired when a page is spooled. When it is fired can be changed by a custom template.""" + @COMEvent + def OnFileDownload(self, ActiveDocument=sentinel, Cancel=sentinel): + """Fired to indicate the File Download dialog is opening""" + @COMEvent + def OnDownloadComplete(self): + """Download of page complete.""" + @COMEvent + def OnBeforeNavigate2(self, pDisp=sentinel, URL=sentinel, Flags=sentinel, + TargetFrameName=sentinel, PostData=sentinel, Headers=sentinel, + Cancel=sentinel): + """Fired before navigate occurs in the given WebBrowser (window or frameset element). The processing of this navigation may be modified.""" + @COMEvent + def OnSetSecureLockIcon(self, SecureLockIcon=sentinel): + """Fired to indicate the security level of the current web page contents""" + @COMEvent + def OnProgressChange(self, Progress=sentinel, ProgressMax=sentinel): + """Fired when download progress is updated.""" + @COMEvent + def OnNavigateError(self, pDisp=sentinel, URL=sentinel, Frame=sentinel, + StatusCode=sentinel, Cancel=sentinel): + """Fired when a binding error occurs (window or frameset element).""" + @COMEvent + def OnCommandStateChange(self, Command=sentinel, Enable=sentinel): + """The enabled state of a command changed.""" + @COMEvent + def OnClientToHostWindow(self, CX=sentinel, CY=sentinel): + """Fired to request client sizes be converted to host window sizes""" + @COMEvent + def OnTitleChange(self, Text=sentinel): + """Document title changed.""" + @COMEvent + def OnWindowSetWidth(self, Width=sentinel): + """Fired when the host window should change its width""" + @COMEvent + def OnDocumentComplete(self, pDisp=sentinel, URL=sentinel): + """Fired when the document being navigated to reaches ReadyState_Complete.""" + @COMEvent + def OnMenuBar(self, MenuBar=sentinel): + """Fired when the menubar should be shown/hidden""" + @COMEvent + def OnPrivacyImpactedStateChange(self, bImpacted=sentinel): + """Fired when the global privacy impacted state changes""" + @COMEvent + def OnPropertyChange(self, szProperty=sentinel): + """Fired when the PutProperty method has been called.""" + @COMEvent + def OnToolBar(self, ToolBar=sentinel): + """Fired when the toolbar should be shown/hidden""" + @COMEvent + def OnTheaterMode(self, TheaterMode=sentinel): + """Fired when theater mode should be on/off""" + @COMEvent + def OnWindowSetTop(self, Top=sentinel): + """Fired when the host window should change its Top coordinate""" + @COMEvent + def OnStatusTextChange(self, Text=sentinel): + """Statusbar text changed.""" + @COMEvent + def OnWindowClosing(self, IsChildWindow=sentinel, Cancel=sentinel): + """Fired when the WebBrowser is about to be closed by script""" + @COMEvent + def OnStatusBar(self, StatusBar=sentinel): + """Fired when the statusbar should be shown/hidden""" + @COMEvent + def OnWindowSetResizable(self, Resizable=sentinel): + """Fired when the host window should allow/disallow resizing""" + @COMEvent + def OnNewWindow2(self, ppDisp=sentinel, Cancel=sentinel): + """A new, hidden, non-navigated WebBrowser window is needed.""" + @COMEvent + def OnPrintTemplateTeardown(self, pDisp=sentinel): + """Fired when a print template destroyed.""" + @COMEvent + def OnPrintTemplateInstantiation(self, pDisp=sentinel): + """Fired when a print template is instantiated.""" + @COMEvent + def OnFullScreen(self, FullScreen=sentinel): + """Fired when fullscreen mode should be on/off""" + @COMEvent + def OnQuit(self): + """Fired when application is quiting.""" + @COMEvent + def OnWindowSetLeft(self, Left=sentinel): + """Fired when the host window should change its Left coordinate""" + @COMEvent + def OnWindowSetHeight(self, Height=sentinel): + """Fired when the host window should change its height""" + @COMEvent + def OnNavigateComplete2(self, pDisp=sentinel, URL=sentinel): + """Fired when the document being navigated to becomes visible and enters the + navigation stack.""" + @COMEvent + def OnDownloadBegin(self): + """Download of a page started.""" + @COMEvent + def OnVisible(self, Visible=sentinel): + """Fired when the window should be shown/hidden""" + #### END: webbrowser2 events #### + + #### START: webbrowser events #### + #### Note: events overriden by webbrowser2 interface are commented out #### + @COMEvent + def OnNavigateComplete(self, URL=sentinel): + """Fired when the document being navigated to becomes visible and enters the + navigation stack.""" +# def OnQuit(self, Cancel=sentinel): +# """Fired when application is quiting.""" + @COMEvent + def OnFrameNavigateComplete(self, URL=sentinel): + """Fired when a new hyperlink is being navigated to in a frame.""" +# def OnProgressChange(self, Progress=sentinel, ProgressMax=sentinel): +# """Fired when download progress is updated.""" + @COMEvent + def OnWindowResize(self): + """Fired when window has been sized.""" + @COMEvent + def OnWindowMove(self): + """Fired when window has been moved.""" +# def OnDownloadComplete(self): +# """Download of page complete.""" + @COMEvent + def OnNewWindow(self, URL=sentinel, Flags=sentinel, TargetFrameName=sentinel, + PostData=sentinel + , Headers=sentinel, Processed=sentinel): + """Fired when a new window should be created.""" + @COMEvent + def OnWindowActivate(self): + """Fired when window has been activated.""" +# def OnStatusTextChange(self, Text=sentinel): +# """Statusbar text changed.""" + @COMEvent + def OnFrameBeforeNavigate(self, URL=sentinel, Flags=sentinel, TargetFrameName=sentinel, + PostData=sentinel + , Headers=sentinel, Cancel=sentinel): + """Fired when a new hyperlink is being navigated to in a frame.""" + @COMEvent + def OnFrameNewWindow(self, URL=sentinel, Flags=sentinel, TargetFrameName=sentinel, + PostData=sentinel + , Headers=sentinel, Processed=sentinel): + """Fired when a new window should be created.""" +# def OnCommandStateChange(self, Command=sentinel, Enable=sentinel): +# """The enabled state of a command changed""" +# def OnDownloadBegin(self): +# """Download of a page started.""" +# def OnTitleChange(self, Text=sentinel): +# """Document title changed.""" + @COMEvent + def OnBeforeNavigate(self, URL=sentinel, Flags=sentinel, TargetFrameName=sentinel, + PostData=sentinel + , Headers=sentinel, Cancel=sentinel): + """Fired when a new hyperlink is being navigated to.""" +# def OnPropertyChange(self, Property=sentinel): +# """Fired when the PutProperty method has been called.""" + #### END: webbrowser events #### + +class IEBrowser(object): + def __init__(self, events_cls=IEEvents): + self._ie = com.DispatchWithEvents("InternetExplorer.Application", IEEvents) + self._evt = self._ie._obj_ + if hasattr(self._evt, '_init'): + self._evt._init(self) + + @classmethod + def Barebones(cls, size = None): + ''' + Popups an IE browser without status bar, address bar, or menu bar. + + If given a size, sizes to that size and centers itself on the screen. + ''' + + import wx + ie = IEBrowser() + ie.AddressBar = False + ie.MenuBar = False + ie.StatusBar = False + ie.ToolBar = False + + if size is not None: + ie.Width, ie.Height = size + w, h = wx.Display(0).ClientArea[2:] + ie.Left, ie.Top = w/2 - ie.Width/2, h/2 - ie.Height/2 + + return ie + + def _Bind(self, *a, **k): + self._evt._Bind(*a,**k) + + def _UnBind(self, *a, **k): + self._evt._UnBind(*a,**k) + + def __getattr__(self, attr): + if attr in (self.methods + self.properties): + return getattr(self._ie, attr) + else: + object.__getattribute__(self, attr) + + def __setattr__(self, attr, val): + if attr in (self.properties): + return setattr(self._ie, attr, val) + else: + return object.__setattr__(self, attr, val) + + def __bool__(self): + try: self._ie.Visible = self._ie.Visible + except: return False + else: return True + + def show(self,show=True): + self._ie.Visible = int(show) + + def shown(self): + return bool(self._ie.Visible) + + def hide(self): + self.show(False) + + def RunScript(self, js): + return self._ie.Document.parentWindow.execScript(js, 'JavaScript') + + ##### END:EVENTS ##### + + ##### START:METHODS ##### + methods = ''' + ClientToWindow + ExecWB + GetProperty + GoBack + GoForward + GoHome + GoSearch + Navigate + Navigate2 + PutProperty + QueryStatusWB + Quit + Refresh + Refresh2 + ShowBrowserBar + Stop + '''.split() + ##### END:METHODS ##### + + + ##### START: PROPS ##### + properties = ''' + AddressBar + Application + Busy + Container + Document + FullName + FullScreen + Height + HWND + Left + LocationName + LocationURL + MenuBar + Name + Offline + Parent + Path + ReadyState + RegisterAsBrowser + RegisterAsDropTarget + Resizable + Silent + StatusBar + StatusText + TheaterMode + ToolBar + Top + TopLevelContainer + Type + Visible + Width + '''.split() + ##### END: PROPS ##### + +class JavaScript(str): + _SCRIPT = 'JavaScript' + def __new__(cls, s, ie, return_val=None): + return str.__new__(cls, s) + def __init__(self, s, ie, return_val=None): + self._ie = ie + self._ret = return_val + str.__init__(self, s) + + def __call__(self, *args): + self._ie.Document.parentWindow.execScript(self % args, self._SCRIPT) + if self._ret is not None: + return getattr(self, self._ret) + + def __getattr__(self, attr): + try: + return str.__getattr__(self, attr) + except: + return getattr(self._ie.Document.parentWindow, attr) + +def JavaScriptFactory(ie): + def make_js(s, ret_val=None): + return JavaScript(s, ie, ret_val) + return make_js + +import atexit +# +#def IEGenerator(): +# cur = None +# next = IEBrowser() +# atexit.register(lambda n=next: n.Quit() if not n.Visible else None) +# while True: +# cur, next = next, IEBrowser() +# atexit.register(lambda n=next: n.Quit() if not n.Visible else None) +# yield cur +# print 'exiting IEGenerator' +# +#iegen = IEGenerator() +#iegen.next().Quit() +#def GetIE(): +# global iegen +# try: +# return iegen.next() +# except: +# iegen = IEGenerator() +# return iegen.next() + +def GetIE(): + ie = IEBrowser() + def quit(i=ie): + try: + if not i.Visible: + i.Quit() + except: + pass + atexit.register(quit) + return ie + +if __name__ == '__main__': + ie = IEBrowser() + ie.show(True) + #ie.Navigate2('file://./test.html') \ No newline at end of file diff --git a/digsby/src/util/introspect.py b/digsby/src/util/introspect.py new file mode 100644 index 0000000..a777d6c --- /dev/null +++ b/digsby/src/util/introspect.py @@ -0,0 +1,1112 @@ +from __future__ import with_statement + +import sys, time, threading, string, logging, re, keyword, \ + types, inspect +import primitives +import primitives.funcs +from weakref import ref +from types import GeneratorType +from path import path +from collections import defaultdict +from traceback import print_exc +import warnings +import gc +import operator + +log = logging.getLogger('util.introspect') + +oldvars = vars + +def uncollectable(clz): + ''' + Returns all active objects of the given class. + + Calls gc.collect() first. + ''' + + import gc + gc.collect() + + return [a for a in gc.get_objects() if isinstance(a, clz)] + +class debug_property(object): + ''' + Doesn't squash AttributeError. + ''' + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.__get = fget + self.__set = fset + self.__del = fdel + self.__doc__ = doc + + def __get__(self, inst, type=None): + if inst is None: + return self + if self.__get is None: + raise AttributeError, "unreadable attribute" + try: + return self.__get(inst) + except AttributeError, e: + # don't allow attribute errors to be squashed + print_exc() + raise AssertionError('attribute error during __get__') + + def __set__(self, inst, value): + if self.__set is None: + raise AttributeError, "can't set attribute" + + try: + return self.__set(inst, value) + except AttributeError, e: + print_exc() + raise AssertionError('attribute error during __set__') + + def __delete__(self, inst): + if self.__del is None: + raise AttributeError, "can't delete attribute" + + try: + return self.__del(inst) + except AttributeError, e: + print_exc() + raise AssertionError('attribute error during __del__') + + +def vars(obj=None): + res = {} + + if hasattr(obj, '__dict__'): + return oldvars(obj) + elif hasattr(obj, '__slots__'): + return dict((attr, getattr(obj,attr,sentinel)) for attr in obj.__slots__) + else: + assert not (hasattr(obj, '__slots__') and hasattr(obj, '__dict__')) + if hasattr(obj, 'keys'): + return obj + else: + assert primitives.funcs.isiterable(obj) + return dict((x, sentinel) for x in obj) + +version_23 = sys.version_info < (2,4) +def this_list(): + ''' + From the Python Cookbook + ''' + d = inspect.currentframe(1).f_locals + nestlevel =1 + while '_[%d]' % nestlevel in d: nestlevel +=1 + result = d['_[%d]' %(nestlevel-1)] + + if version_23: return result.__self__ + else: return result + + +def cannotcompile(f): + return f + +def stack_trace(level=1, frame=None): + try: + if frame is None: + f = sys._getframe(level) + else: + f = frame + frames = [] + while f is not None: + c = f.f_code + frames.insert(0,(c.co_filename, c.co_firstlineno, c.co_name)) + f = f.f_back + return frames + finally: + del f, frame + +def print_stack_trace(frame=None): + trace = stack_trace(2,frame) + for frame in trace: + + print ' File "%s", line %d, in %s' % frame + +def print_stack_traces(): + ''' + Prints stack trace of all frames in sys._current_frames. + No promises of threadsafety are made!! + ''' + frames = sys._current_frames().items() + for id,frame in frames: + print 'Frame %d:' % id + print_stack_trace(frame) + +def is_all(seq, my_types = None): + """ + Returns True if all elements in seq are of type my_type. + + If True, also returns the type. + """ + + #TODO: technically, return val for an empty sequence is undefined...what do we do? + if not seq: + try: iter(my_types) + except: t = my_types + else: t = my_types[0] + finally: return True, t + + if type( my_types ) == type: + my_types = [my_types] + + if my_types == None: + my_types = [type(seq[0])] + + all = True + for elem in seq: + if type(elem) not in my_types: + all = False + break + + if all: + return all, my_types[0] + else: + return all, None + +def get_func_name(level=1): + return sys._getframe(level).f_code.co_name + + +def get_func(obj, command, *params): + """ + get_func(obj, command, *params) + Returns a function object named command from obj. If a function is + not found, logs a critical message with the command name and parameters + it was attempted to be called with. + """ + try: + func = getattr(obj, command.lower()) + log.debug('Finding %s.%s%s', + obj.__class__.__name__, + command.lower(), + params) + except AttributeError: + log.critical('%s has no function to handle: %s(%s)', + obj.__class__.__name__, + command.lower(), + ", ".join([repr(x) for x in params])) + func = None + + return func + +def decormethod(decorator): + """ + Returns a method decorator. See observable.py for an example, or + U{http://miscoranda.com/136} for an explanation. + """ + def wrapper(method): + return (lambda self, *args, **kw: + decorator(self, method, *args, **kw)) + return wrapper + +def funcToMethod(func,clas,method_name=None): + """Adds func to class so it is an accessible method; use method_name to specify the name to be used for calling the method. + The new method is accessible to any instance immediately. + + Thanks U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732} (yet again) + """ + func.im_class=clas + func.im_func=func + func.im_self=None + if not method_name: method_name=func.__name__ + clas.__dict__[method_name]=func + +def attach_method(obj, func, name=None): + 'Attaches a function to an object as a method.' + + name = name or func.__name__ + cls = obj.__class__ + cls.temp_foo = func + obj.__setattr__(name, cls.temp_foo) + del cls.temp_foo + +def isgeneratormethod(object): + 'Return true if the object is a method of a generator object.' + + return isinstance(getattr(object, '__self__', None), GeneratorType) + +CO_VARARGS = 0x0004 +CO_VARKEYWORDS = 0x0008 + +def callany(func, *args): + ''' + Calls a callable with the number args it is expecting. + ''' + if not callable(func): raise TypeError, "callany's first argument must be callable" + + from util.callbacks import CallLater, CallLaterDelegate, CallbackSequence + + c = func + while isinstance(c, CallLater): + c = c.cb + + if isinstance(c, CallbackSequence): + c = c.__call__ + + if isinstance(c, CallLaterDelegate): + return [callany(child, *args) for child in c] + + if hasattr(c, 'im_func'): + code = c.im_func.func_code + nargs = code.co_argcount - 1 + codeflags = code.co_flags + elif hasattr(c, 'func_code'): + code = c.func_code + nargs = code.co_argcount + codeflags = code.co_flags + else: + # Last resort: call it simply as func(*args) + code = None + codeflags = 0 + nargs = len(args) + + hasargs = codeflags & CO_VARARGS + haskwargs = codeflags & CO_VARKEYWORDS + + if haskwargs: + args = [] + msg = 'callany given a kwarg function (%r): no arguments will be passed!' % funcinfo(c) + + warnings.warn(msg) + + if getattr(sys, 'DEV', False): + # Keep the warning above, in case this exception is squashed. + raise AssertionError(msg) + + if not hasargs: + args = list(args)[:nargs] + args += [None] * (nargs - len(args)) + return func(*args) + +def pythonize(s, lower=True): + 'Return a valid python variable name' + + if not isinstance(s, basestring): + raise TypeError, 'Only string/unicode types can be pythonized!' + + allowed = string.letters + string.digits + '_' + + s = str(s).strip() + + if s.startswith('__') and s.endswith('__'): + # only special words are allowed to be like __this__ + s = s[2:-2] + + if not s: + return s + + s = ('_' if s[0] in string.digits else '')+ s + s = ('_' if keyword.iskeyword(s) else '') + s + + new_s = '' + for ch in s: + new_s += ch if ch in allowed else '_' + + if lower: + new_s = new_s.lower() + + return new_s + +attached_functions = {} + +def dyn_dispatch(obj, func_name, *args, **kwargs): + func_name = pythonize(str(func_name)) + if not hasattr(obj, func_name): + fn = sys._getframe(1).f_code.co_filename + + d = dict(name=func_name) + d.update(kwargs) + code = str(obj.func_templ % d) + f = open(fn, 'a') + f.write(code) + f.close() + + newcode = '' + code = code.replace('\n ', '\n') + + exec(code) + attach_method(obj, locals()[func_name]) + l = attached_functions.setdefault(obj.__class__, []) + l.append(func_name) + + if func_name in attached_functions.setdefault(obj.__class__, []): + args = [obj] + list(args) + + return getattr(obj, func_name)(*args, **kwargs) + +class CallTemplate(string.Template): + ''' + Like string.Template, but matches unnamed arguments and is callable. + + Useful as a quick replacement for defining short "return a string" + functions. + + >>> nick = CallTemplate('NICK $username $realname') + >>> print nick('digsby05', 'Digsby Dragon') + NICK digsby05 Digsby Dragon + ''' + + def __init__(self, templ): + string.Template.__init__(self,templ) + + # grab placeholder names from the string using the Template regex + self.placeholders = [m[1] for m in + re.findall(self.pattern, self.template)] + + def __call__(self, *args, **kws): + # match placeholders to args, and override with keyword args + return self.substitute(**primitives.dictadd + (zip(self.placeholders, args), kws)) + + + +_profilers_enabled = False +def set_profilers_enabled(val): + global _profilers_enabled + _profilers_enabled = val + +def use_profiler(target, callable): + ''' + Use a disabled profiler to run callable. + + Profile object will be stored in target.profiler and can be .enable()d later. + ''' + target.profiler = EnableDisableProfiler() + + def cb(): + #print 'starting profiler on %s' % threading.currentThread().getName() + global _profilers_enabled + if not _profilers_enabled: + target.profiler.disable() # start disabled to avoid performance cost + callable() + + if sys.platform == 'win32': + from wx import SEHGuard + else: + SEHGuard = lambda c: c() + + def run_with_sehguard(): + return SEHGuard(cb) + + target.profiler.runcall(run_with_sehguard) + +def all_profilers(): + 'Returns a sequence of all the profilers in the system.' + + return dict((thread, thread.profiler) for thread in threading.enumerate() if hasattr(thread, 'profiler')) + +def get_profile_report(profiler): + from pstats import Stats + from cStringIO import StringIO + + io = StringIO() + stats = Stats(profiler, stream = io) + + io.write('\nby cumulative time:\n\n') + stats.sort_stats('cumulative').print_stats(25) + + io.write('\nby number of calls:\n\n') + stats.sort_stats('time').print_stats(25) + + return io.getvalue() + +from cProfile import Profile +Profile.report = get_profile_report + +def profilereport(): + s = [] + for thread, profiler in all_profilers().iteritems(): + s.extend([repr(thread), profiler.report()]) + + return '\n'.join(s) + +class Memoize(object): + __slots__ = ['func', 'cache'] + + def __init__(self, func): + self.func = func + self.cache = {} + + def __repr__(self): + return '' % (funcinfo(self.func), len(self.cache)) + + def __call__(self, *args, **kwargs): + key = (args, tuple(kwargs.items())) + cache = self.cache + + try: + return cache[key] + except KeyError: + return cache.setdefault(key, self.func(*args, **kwargs)) + +memoize = Memoize + +# A read-once property. +memoizedprop = lambda getter: property(memoize(getter)) + +# timing decorator + +def print_timing(num_runs=1): + def wrapper1(func): + def wrapper(*arg, **kwargs): + t1 = time.clock() + for i in range(num_runs): + res = func(*arg, **kwargs) + t2 = time.clock() + print '%s took %0.3fms.' % (func.func_name, (((t2-t1)*1000.)/float(num_runs))) + return res + return wrapper + return wrapper1 + +class i(int): + ''' + Iterable integers. + + >>> for x in i(5): print x, + 0 1 2 3 4 + ''' + def __iter__(self): return iter(xrange(self)) + +def reload_(obj): + ''' + Gets the latest code from disk and swaps it into the object. + + Returns the newly reloaded module. + ''' + # Reload the object's bases class module +# def bases(klas): +# if klas == object: +# return +# for klass in klas.__bases__: +# bases(klass) +# for klass in klas.__bases__: +# reload(sys.modules[klass.__module__]) +# bases(obj.__class__) + for klass in reversed(obj.__class__.__mro__): + if klass not in __builtins__: + reload(sys.modules[klass.__module__]) + return reload2_(obj) + +def reload2_(obj): + ''' + Gets the latest code from disk and swaps it into the object. + + Returns the newly reloaded module. + ''' + # Reload the object's class's module. + m = sys.modules[obj.__class__.__module__] + m = reload(m) + + # Grab the new class object. + cl = getattr(m, obj.__class__.__name__) + + # Replace the object's __class__ attribute with the new class. + obj.__class__ = cl + + return sys.modules[obj.__class__.__module__] + +def bitflags_enabled(map, flags): + bits = [f for f in map.iterkeys() if isinstance(f, int)] + return [map[f] for f in sorted(bits) if f & flags != 0] + +def import_module(modulePath): + aMod = sys.modules.get(modulePath, False) + if aMod is False or not isinstance(aMod, types.ModuleType): + if isinstance(modulePath, unicode): + modulePath = modulePath.encode('filesys') + # The last [''] is very important so that the 'fromlist' is non-empty, thus returning the + # module specified by modulePath, and not the top-level package. (i.e. when importing a.b.c + # with no fromlist, the module 'a' is returned. with a non-empty fromlist, a.b.c is returned. + # See http://docs.python.org/library/functions.html#__import__ + aMod = __import__(modulePath, globals(), locals(), ['']) + sys.modules[modulePath] = aMod + return aMod + +def import_function(fullFuncName): + 'Retrieve a function object from a full dotted-package name.' + + if not isinstance(fullFuncName, basestring): + raise TypeError('import_function needs a string, you gave a %s' % type(fullFuncName)) + + + # Parse out the path, module, and function + lastDot = fullFuncName.rfind(u".") + funcName = fullFuncName[lastDot + 1:] + modPath = fullFuncName[:lastDot] + + aMod = import_module(modPath) + try: + aFunc = getattr(aMod, funcName) + except AttributeError, e: + log.error('%r. Module contents = %r', e, aMod.__dict__) + raise e + + # Assert that the function is a *callable* attribute. + assert callable(aFunc), u"%s is not callable." % fullFuncName + + # Return a reference to the function itself, + # not the results of the function. + return aFunc + +@memoize +def base_classes(clazz): + ''' + Returns the list of classes above a class in the inheritance tree. + + Classes only appear once in the list, even in a diamond inheritance pattern. + + >>> class Foo(object): pass + >>> class Bar(Foo): pass + >>> class Boop(Bar): pass + >>> [c.__name__ for c in base_classes(Boop)] + ['Bar', 'Foo', 'object'] + ''' + classes = [] + for cl in clazz.__bases__: + classes += [cl] + base_classes(cl) + return list(set(classes)) + +# thanks Python Cookbook +def wrapfunc(obj, name, processor, avoid_doublewrap = True): + call = getattr(obj, name) + if avoid_doublewrap and getattr(call, 'processor', None) is processor: + return + + original_callable = getattr(call, 'im_func', call) + def wrappedfunc(*args, **kwargs): + return processor(original_callable, *args, **kwargs) + + wrappedfunc.original = call + wrappedfunc.processor = processor + + wrappedfunc.__name__ = getattr(call, '__name__', name) + if inspect.isclass(obj): + if hasattr(call, 'im_self'): + if call.im_self: + wrappedfunc = classmethod(wrappedfunc) + else: + wrappedfunc = staticmethod(wrappedfunc) + + setattr(obj, name, wrappedfunc) + +def unwrapfunc(obj, name): + setattr(obj, name, getattr(obj, name).original) + +def tracing_processor(original_callable, *args, **kwargs): + r_name = getattr(original_callable, '__name__', '') + r_args = [primitives.try_this((lambda: repr(a)), '<%s at %s>' % (type(a), id(a))) + for a in args] + r_args.extend(['%s-%r' % x for x in kwargs.iteritems()]) + + print '-> %s(%s)' % (r_name, ', '.join(r_args)) + return original_callable(*args, **kwargs) + +def add_tracing(class_object, method_name): + wrapfunc(class_object, method_name, tracing_processor) + +def trace(clz): + 'Adds tracing print statements for entering and exiting all methods of a class.' + + for meth, v in inspect.getmembers(clz, inspect.ismethod): + if not meth.startswith('__'): + add_tracing(clz, meth) + +def typecounts(contains = None, objs=None): + import gc + + if objs is None: + objs = gc.get_objects() + + counts = defaultdict(int) + + for obj in objs: + counts[type(obj).__name__] += 1 + + if contains is not None: + contains = lambda s, ss = contains: s[0].find(ss) != -1 + + return filter(contains, sorted(counts.iteritems(), key = lambda a: a[1], reverse = True)) + + +def funcinfo(func): + """ + Returns a simple readable string describing a function's name and location + in the codebase. + + >>> from util import strip_html + >>> funcinfo(strip_html) + '' + """ + if not hasattr(func, 'func_code'): + return repr(func) + + name = getattr(func, '__name__', getattr(getattr(func, '__class__', None), '__name__', '')) + c = func.func_code + + filename = c.co_filename + if not isinstance(filename, str): + filename = '??' + else: + try: + try: + filepath = path(c.co_filename) + except UnicodeDecodeError: + pass + else: + if filepath.name == '__init__.py': + filename = filepath.parent.name + '/' + filepath.name + else: + filename = filepath.name + except Exception: + print_exc() + + return '<%s (%s:%s)>' % (name, filename, c.co_firstlineno) + +def leakfinder(): + import wx + from pprint import pprint + from util import typecounts + import gc + + f = wx.Frame(None, pos=(30,30), + style = wx.DEFAULT_FRAME_STYLE|wx.FRAME_TOOL_WINDOW | wx.STAY_ON_TOP) + + b = wx.Button(f, -1, 'memory stats') + b2 = wx.Button(f, -1, 'all functions') + b3 = wx.Button(f, -1, 'all unnamed lambdas') + + sz = f.Sizer = wx.BoxSizer(wx.VERTICAL); + sz.AddMany([b,b2,b3]) + + + f.stats = {} + def onstats(e): + new = typecounts() + news = dict(new) + + for cname in news.keys(): + if cname in f.stats: + diff = news[cname] - f.stats[cname][0] + f.stats[cname] = (news[cname], diff) + else: + f.stats[cname] = (news[cname], 0) + + print '****' * 10 + pprint(sorted(f.stats.iteritems(), key = lambda a: a[1])) + + def on2(e, filterName = None): + funcs = [o for o in gc.get_objects() if type(o).__name__ == 'function'] + counts = defaultdict(int) + + for name, f in [(f.__name__, f) for f in funcs]: + if filterName is not None and name != filterName: continue + t = path(f.func_code.co_filename).name, f.func_code.co_firstlineno + counts[t] += 1 + + print '((Filename, Line Number), Count)' + pprint(sorted(list(counts.iteritems()), key= lambda i: i[1])) + + #for name in [f.__name__ for f in funcs]: + # counts[name]+=1 + + #pprint(sorted(list(counts.iteritems()), key= lambda i: i[1])) + + b.Bind(wx.EVT_BUTTON, onstats) + b2.Bind(wx.EVT_BUTTON, on2) + b3.Bind(wx.EVT_BUTTON, lambda e: on2(e, '')) + f.Sizer.Layout() + f.Fit() + f.Show() + + +def counts(seq, groupby): + # accumulate counts + counts = defaultdict(int) + for obj in seq: + counts[groupby(obj)] += 1 + + # return descending [(count, group), ...] + return sorted(((count, val) for val, count in counts.iteritems()), reverse = True) + + +class InstanceTracker(object): + ''' + Mixin to track all instances of a class through a class variable "_instances" + + Provides cls.CallAll(func, *a, **k) as well. + + If you're not using CallAll, beware--the references in _instances are weakref.ref + objects--i.e., to get the real instances: + + filter(None, (r() for r in cls._instances)) + ''' + + def track(self): + # store a weak reference to the instance in the + # class's "_instances" list + try: + _instances = self.__class__._instances + except AttributeError: + self.__class__._instances = [ref(self)] + else: + # Make sure we don't already have a reference to this object. + for wref in _instances: + if wref() is self: + break + else: + # Keep a weak reference in self.__class__._instances + _instances.append(ref(self)) + + @classmethod + def all(cls): + objs = [] + + try: + wrefs = cls._instances + except AttributeError: + # an instance hasn't been __new__ed yet. + return [] + + import wx + for wref in wrefs[:]: + obj = wref() + if obj is not None: + if wx.IsDestroyed(obj): + wrefs.remove(wref) + else: + objs.append(obj) + + return objs + + @classmethod + def CallAll(cls, func, *args, **kwargs): + 'Calls func(obj, *a, **k) on all live instances of this class.' + + import wx + + try: + instances = cls._instances + except AttributeError: + return # no instances were created yet. + + removeList = [] + + for wref in instances: + obj = wref() + if obj is not None and not wx.IsDestroyed(obj): + try: + func(obj, *args, **kwargs) + except TypeError: + print type(obj), repr(obj) + raise + else: + removeList.append(wref) + + for wref in removeList: + try: + instances.remove(wref) + except ValueError: + pass # weakrefs can go away + + +class DeadObjectError(AttributeError): + pass + +class DeadObject(object): + reprStr = "Placeholder for DELETED %s object! Please unhook all callbacks, observers, and event handlers PROPERLY." + attrStr = "Attribute access no longer allowed - This object has signaled that it is no longer valid!" + + def __repr__(self): + if not hasattr(self, "_name"): + self._name = "[unknown]" + return self.reprStr % self._name + + def __getattr__(self, *args): + if not hasattr(self, "_name"): + self._name = "[unknown]" + raise DeadObjectError(self.attrStr % self._name) + + def __nonzero__(self): + return 0 + +def generator_repr(g): + ''' + A nicer __repr__ for generator instances. + ''' + frame = g.gi_frame + code = frame.f_code + return '' % \ + (code.co_name, code.co_filename, frame.f_lineno, id(g)) + +def gc_diagnostics(stream = None): + ''' + prints a detailed GC report to stream (or stdout) about "interesting" + objects in gc.get_objects(), based on things like __len__, + sys.getreferents, etc. + ''' + import gc, sys, linecache + import locale + from operator import itemgetter + from itertools import ifilterfalse, imap + getrefcount = sys.getrefcount + + if stream is None: + stream = sys.stdout + + # Collect garbage first. + linecache.clearcache() + gc.collect() + + def w(s): stream.write(s + '\n') + + filter_objects = ((), '', ) + filter_types = (type,) + objs = [(getrefcount(o), o) for o in gc.get_objects() # gather (refcount, object) if + if not isinstance(o, filter_types) and # not is any of the types above + not any(o is a for a in filter_objects)] # and is not any of the objects above + + itemgetter0 = itemgetter(0) + itemgetter1 = itemgetter(1) + objs.sort(key=itemgetter0) + num_objs = len(objs) + + # Print the total number of objects + w('%d objects' % num_objs) + + ## + # find objects with len() returning obscenely large numbers + ## + N = 600 # threshold + notallowed = (basestring, ) # don't care about long strings... + # ...or module dictionaries + + import __builtin__ + blacklist = set() + oldlen = 0 + modlen = len(sys.modules) + while modlen != oldlen: + blacklist |= set(id(m.__dict__) for m in sys.modules.itervalues() if m) + for m in sys.modules.values(): + if m and hasattr(m, '__docfilter__'): + blacklist.add(id(m.__docfilter__._globals)) + oldlen = modlen + modlen = len(sys.modules) + blacklist.add(id(__builtin__)) + blacklist.add(id(__builtin__.__dict__)) + blacklist.add(id(sys.modules)) # also exclude sys.modules + blacklist.add(id(locale.locale_alias)) + if sys.modules.get('stringprep', None) is not None: + import stringprep + blacklist.add(id(stringprep.b3_exceptions)) + blacklist.add(id(objs)) # and the gc.get_objects() list itself + + def blacklisted(z): + return (isinstance(z, notallowed) or id(z) in blacklist or z is blacklist) + + def blacklisted_1(z): + z = z[-1] + return blacklisted(z) + + def large_sequence(z): + try: + return len(z) > 300 and not blacklisted(z) + except: + pass + + def saferepr(obj): + try: + # use generator_repr if necessary + if hasattr(obj, 'gi_frame'): + try: + return generator_repr(obj) + except Exception, e: + pass + return repr(obj) + except Exception, e: + try: return '<%s>' % type(obj).__name__ + except: return '' + + ## + # Print the most referenced objects + + num_most_reffed = min(int(num_objs * .05), 20) + most_reffed = ifilterfalse(blacklisted_1, reversed(objs)) + + w('\n\n') + w('*** top %d referenced objects' % num_most_reffed) + w('sys.getrefcount(obj), id, repr(obj)[:1000]') + + for nil in xrange(num_most_reffed): + try: + rcount, obj = most_reffed.next() + except StopIteration: + break + + w('%d %d: %s' % (rcount, id(obj), saferepr(obj)[:1000])) +# referrers = [foo for foo in [(getrefcount(o), o) for o in gc.get_referrers(obj) # gather (refcount, object) if +# if not isinstance(o, filter_types) and # not is any of the types above +# not any(o is a for a in filter_objects) and not blacklisted(o) and isinstance(o, dict)] +# if foo[0]>10] +# for ref in referrers: +# w('\t%d %d: %s' % (ref[0], id(ref[1]), saferepr(ref[1])[:1000])) + + + + # + ## + + import util.gcutil as gcutil + def safe_nameof(o): + try: + return gcutil.nameof(o)[:200] + except: + return '[?]' + + ## + # print the first thousand characters of each objects repr + w('\n\n') + w('*** objects with __len__ more than %d' % N) + w('__len__(obj), repr(obj)[:1000]') + + large_objs = sorted([(len(a), id(a), safe_nameof(a), saferepr(a)[:2000]) + for (obj_refcount, a) in objs if large_sequence(a)], + key = itemgetter0, reverse = True) + + for count, _id, nameof, s in large_objs: + if _id != id(objs): + w('count %d id %d: %s %s' % (count, _id, nameof, s)) + # + ## + + ## + # Print "count: 'typename'" for the top 20 most instantiated types + # that aren't builtin types. + w('\n\n') + _typecounts = typecounts(objs = imap(itemgetter1, objs)) + + num_types = 20 + w('*** top %d instantiated types' % num_types) + builtin_names = set(__builtins__.keys()) + tc_iter = ifilterfalse(lambda _x: builtin_names.__contains__(itemgetter0(_x)), _typecounts) + for nil in range(num_types): + try: + tname, tcount = tc_iter.next() + except StopIteration: + break + + w('%d: %r' % (tcount, tname)) + + funcinfos = defaultdict(int) + for refcount, obj in objs: + if callable(obj): + try: + finfo = funcinfo(obj) + except: + continue + funcinfos[finfo] += refcount + + num_infos = min(len(funcinfos), 20) + funcinfos = funcinfos.items() + funcinfos.sort(key=itemgetter1, reverse=True) + w('\n\n') + w('*** %d most referenced callables' % num_infos) + for i in range(num_infos): + finfo, frcount = funcinfos[i] + w('%d: %r' % (frcount, finfo)) + + # + # show all top level WX objects + # + try: + import wx + except ImportError: + pass + else: + w('\n\n*** top level windows') + for tlw in wx.GetTopLevelWindows(): + w(' - '.join((saferepr(tlw), saferepr(tlw.Name)))) + + ## + # Print out anything in gc.garbage + # + + w('\n\n*** gc.garbage') + if not gc.garbage: + w('(none)') + else: + for obj in gc.garbage: + w(saferepr(obj)) + + + ## + # Print out objects with refcounts >>> len(referrers) + # + w('\n\n*** high refcounts w/out referrers') + for refcount, obj in find_high_refcounts(): + w('%d: %s' % (refcount, repr(obj)[:100])) + +HIGH_REFCOUNT = 500 + +def _high_refcounts(getrefcount=sys.getrefcount): + for obj in gc.get_objects(): + refcount = getrefcount(obj) + if refcount > HIGH_REFCOUNT: + yield refcount, obj + +def find_high_refcounts(limit = 10, + # localize hack + key=operator.itemgetter(0), + getrefcount=sys.getrefcount, + get_referrers=gc.get_referrers, + get_objects=gc.get_objects): + ''' + returns a sorted list of [(refcount, object), ...] for objects who + have a higher refcount than number of referrers. + ''' + + blacklist = set( + [id(tuple())], + ) + + l = [] + for refcount, obj in _high_refcounts(): + if id(obj) not in blacklist: + delta = refcount - len(get_referrers(obj)) + if delta: + l.append((delta, obj)) + l.sort(reverse=True, key=key) + del l[limit:] + + return l + + + +import cProfile, _lsprof +class EnableDisableProfiler(cProfile.Profile): + ''' + Subclasses cProfile.Profile to add enable and disable methods. + ''' + + def __init__(self, *a, **k): + self.enabled = False + _lsprof.Profiler.__init__(self, *a, **k) + + def enable(self): + self.enabled = True + return _lsprof.Profiler.enable(self) + + def disable(self): + self.enabled = False + return _lsprof.Profiler.disable(self) + + def print_stats(self, sort=-1): + import pstats + pstats.Stats(self, stream = sys.stderr).strip_dirs().sort_stats(sort).print_stats() + +if __name__ == '__main__': + from pprint import pprint + pprint(typecounts('Graphics')) diff --git a/digsby/src/util/json.py b/digsby/src/util/json.py new file mode 100644 index 0000000..1c3b347 --- /dev/null +++ b/digsby/src/util/json.py @@ -0,0 +1,123 @@ +import simplejson + +# +---------------+-------------------+ +# | JSON | Python | +# +===============+===================+ +# | object | dict | +# +---------------+-------------------+ +# | array | list | +# +---------------+-------------------+ +# | string | unicode, !str | +# +---------------+-------------------+ +# | number (int) | !int, !long | +# +---------------+-------------------+ +# | number (real) | !float | +# +---------------+-------------------+ +# | true | True | +# +---------------+-------------------+ +# | false | False | +# +---------------+-------------------+ +# | null | None | +# +---------------+-------------------+ + +# +---------------------+---------------+ +# | Python | JSON | +# +=====================+===============+ +# | dict | object | +# +---------------------+---------------+ +# | list, !tuple | array | +# +---------------------+---------------+ +# | !str, unicode | string | +# +---------------------+---------------+ +# | !int, !long, !float | number | +# +---------------------+---------------+ +# | True | true | +# +---------------------+---------------+ +# | False | false | +# +---------------------+---------------+ +# | None | null | +# +---------------------+---------------+ +def serialize(thing): + if type(thing) is dict: + return dict((serialize(a), serialize(b)) for a,b in thing.iteritems()) + elif isinstance(thing, str): + return '__str__' + thing + elif isinstance(thing, unicode): + return '__unicode__' + thing + elif isinstance(thing, bool): + if thing: + return '__True__' + else: + return '__False__' + elif isinstance(thing, (int, long)): + return '__int__' + str(thing) + elif isinstance(thing, float): + return '__float__' + repr(thing) + elif isinstance(thing, type(None)): + return '__None__' + elif type(thing) is tuple: + return {'__tuple__' : list(serialize(foo) for foo in thing)} + elif type(thing) is list: + return list(serialize(foo) for foo in thing) + elif type(thing) is set: + return {'__set__' : [serialize(foo) for foo in sorted(thing)]} + elif type(thing) is frozenset: + return {'__frozenset__' : [serialize(foo) for foo in sorted(thing)]} + else: + assert False, (type(thing), thing) + +def unserialize(thing): + if type(thing) in (unicode, str): + if thing.startswith('__str__'): + return str(thing[7:]) + if thing.startswith('__unicode__'): + return unicode(thing[11:]) + if thing.startswith('__int__'): + return int(thing[7:]) + if thing.startswith('__float__'): + return float(thing[9:]) + if thing == '__None__': + return None + if thing == '__True__': + return True + if thing == '__False__': + return False + assert False, 'all incoming unicode should have been prepended' + return thing + if type(thing) is dict: + return dict((unserialize(foo),unserialize(bar)) for foo,bar in thing.iteritems()) + elif type(thing) is set: + return set(unserialize(foo) for foo in thing) + elif type(thing) is frozenset: + return frozenset(unserialize(foo) for foo in thing) + elif type(thing) is tuple: + return tuple(unserialize(foo) for foo in thing) + elif type(thing) is list: + return list(unserialize(foo) for foo in thing) + else: + assert False, type(thing) + +def untupleset(obj): + if '__tuple__' in obj: + assert len(obj) == 1 + return tuple(obj['__tuple__']) + elif '__set__' in obj: + assert len(obj) == 1 + return set(obj['__set__']) + elif '__frozenset__' in obj: + assert len(obj) == 1 + return frozenset(obj['__frozenset__']) + return obj + +def pydumps(obj): + return simplejson.dumps(serialize(obj), sort_keys=True, separators=(',', ':')) + +def pyloads(obj): + return unserialize(simplejson.loads(obj, object_hook=untupleset)) + +__all__ = ['pydumps', 'pyloads'] + +if __name__=='__main__': + #TODO: this needs test cases + pass + diff --git a/digsby/src/util/lego/__init__.py b/digsby/src/util/lego/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/util/lego/blocks.py b/digsby/src/util/lego/blocks.py new file mode 100644 index 0000000..58fdefb --- /dev/null +++ b/digsby/src/util/lego/blocks.py @@ -0,0 +1,9 @@ +import protocols + +class IBindableValue(protocols.Interface): + def bind(func): + pass + def unbind(): + pass + value = property() + diff --git a/digsby/src/util/lego/lattice/__init__.py b/digsby/src/util/lego/lattice/__init__.py new file mode 100644 index 0000000..15694e4 --- /dev/null +++ b/digsby/src/util/lego/lattice/__init__.py @@ -0,0 +1 @@ +import frame diff --git a/digsby/src/util/lego/lattice/blocks/__init__.py b/digsby/src/util/lego/lattice/blocks/__init__.py new file mode 100644 index 0000000..4992317 --- /dev/null +++ b/digsby/src/util/lego/lattice/blocks/__init__.py @@ -0,0 +1,5 @@ +import protocols +from peak.events import trellis + +class IValueListener(protocols.Interface): + value = trellis.attr() diff --git a/digsby/src/util/lego/lattice/frame/__init__.py b/digsby/src/util/lego/lattice/frame/__init__.py new file mode 100644 index 0000000..8ce0d2e --- /dev/null +++ b/digsby/src/util/lego/lattice/frame/__init__.py @@ -0,0 +1,48 @@ +import protocols +from peak.events import trellis +from ...blocks import IBindableValue +from ..blocks import IValueListener + +class BindingValueListener(protocols.Adapter, trellis.Component): + protocols.advise(asAdapterForProtocols=[IBindableValue], + instancesProvide=[IValueListener]) + + def __init__(self, subject): + protocols.Adapter.__init__(self, subject) + trellis.Component.__init__(self) +# self.value_changed() + self.subject.bind(self.value_changed) + + def value_changed(self): + self.value = self.get_value() + + def get_value(self): + return self.subject.value + + value = trellis.make(rule=get_value, writable=True, optional=True, name='value') + +class ObservableChangeBindable(trellis.Component): + def __init__(self, model, *attrs): + self.model = model + self.attrs = attrs + + def bind(self, func): + assert func + self.func = func + self.model.add_gui_observer(self.on_change, *self.attrs) + + def unbind(self): + self.model.remove_gui_observer(self.on_change, *self.attrs) + del self.func + + def on_change(self, *a, **k): + self.func() + +class ObservableAttrBindable(ObservableChangeBindable): + protocols.advise(instancesProvide=[IBindableValue]) + def __init__(self, model, attr): + super(ObservableAttrBindable, self).__init__(model, attr) + self.attr = attr + @property + def value(self): + return getattr(self.model, self.attr, False) diff --git a/digsby/src/util/lrucache.py b/digsby/src/util/lrucache.py new file mode 100644 index 0000000..0bb2aa8 --- /dev/null +++ b/digsby/src/util/lrucache.py @@ -0,0 +1,232 @@ +from __future__ import with_statement + +# thanks ASPN Python Cookbook +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/498245 + +from collections import deque +from threading import RLock +from functools import wraps +from pprint import pformat +from timeit import default_timer + +def lru_cache(maxsize): + '''Decorator applying a least-recently-used cache with the given maximum size. + + Arguments to the cached function must be hashable. + Cache performance statistics stored in f.hits and f.misses. + ''' + + lck = RLock() + def decorating_function(f): + cache = {} # mapping of args to results + queue = deque() # order that keys have been accessed + refcount = {} # number of times each key is in the access queue + + @wraps(f) + def wrapper(*args, **kwargs): + with lck: + # localize variable access (ugly but fast) + _cache=cache; _len=len; _refcount=refcount; _maxsize=maxsize + queue_append=queue.append; queue_popleft = queue.popleft + #print wrapper.hits, '/', wrapper.misses + + # get cache entry or compute if not found + + key = args#+tuple(sorted(kwargs.items())) + + try: + result = _cache[key] + #wrapper.hits += 1 + except KeyError: + result = _cache[key] = f(*args, **kwargs) + #wrapper.misses += 1 + + # record that this key was recently accessed + queue_append(key) + _refcount[key] = _refcount.get(key, 0) + 1 + + # Purge least recently accessed cache contents + while _len(_cache) > _maxsize: + k = queue_popleft() + _refcount[k] -= 1 + if not _refcount[k]: + del _cache[k] + del _refcount[k] + + # Periodically compact the queue by duplicate keys + if _len(queue) > _maxsize * 4: + for i in [None] * _len(queue): + k = queue_popleft() + if _refcount[k] == 1: + queue_append(k) + else: + _refcount[k] -= 1 + assert len(queue) == len(cache) == len(refcount) == sum(refcount.itervalues()) + + return result + + return wrapper + return decorating_function + + + +class MyDoublyLinkedListNode(object): + __slots__ = ['data', 'prev', 'next'] + + def __init__(self, data, prev=None, next=None): + self.data = data + self.prev = prev + self.next = next + + def remove(self): + assert self.next is not None + + if self.prev is not None: + self.prev.next = self.next + + self.next.prev = self.prev + + def append(self, node): + if self.next is not None: + raise NotImplementedError("I don't have use for real insertion") + + self.next = node + node.prev = self + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.data, self.next) + +class LRU(dict): + # thanks http://www.algorithm.co.il/blogs/ + + node_class = MyDoublyLinkedListNode + def __init__(self, limit): + self._limit = limit + + self._init() + + def _init(self): + # Use order initialization + self._ll_head = self._ll_last = self.node_class(None) + self._ll_mapping = {} + self.lock = RLock() + + def __setitem__(self, key, value): + with self.lock: + # if key is last node (most recent), nothing needs to be done + if self._ll_last.data == key: + return dict.__setitem__(self, key, value) + + if key in self: + # remove from llist (in order be appended to the end) + self._ll_mapping[key].remove() + elif len(self) == self._limit: + # remove least recently used item + self.pop_lru_key() + + # append to llist and update mapping + new_node = self.node_class(key) + self._ll_last.append(new_node) + self._ll_last = new_node + self._ll_mapping[key] = new_node + + # Actually set the item + dict.__setitem__(self, key, value) + + assert len(self) == len(self._ll_mapping), (self, self._ll_mapping) + assert len(self) <= self._limit, self + + def pop_lru_key(self): + key = self._ll_head.next.data + try: + dict.__delitem__(self, key) + except KeyError: + pass + + try: + del self._ll_mapping[key] + except KeyError: + pass + + self._ll_head.next.remove() + return key + + def __delitem__(self, key): + raise NotImplementedError("Not necessary for LRU Cache") + + +class MyTimedDoublyLinkedListNode(MyDoublyLinkedListNode): + __slots__ = MyDoublyLinkedListNode.__slots__ + ['created'] + + def __init__(self, data, prev=None, next=None): + super(MyTimedDoublyLinkedListNode, self).__init__(data, prev, next) + self.created = default_timer() + +class ExpiringLRU(LRU): + node_class = MyTimedDoublyLinkedListNode + + def __init__(self, time_limit): + self.time_limit = time_limit + self._init() + + @property + def _limit(self): + return len(self) + + def pop_lru_key(self): + #base case, can't kill the last node + while len(self) > 1: + key = self._ll_head.next.data + try: + val = self[key] + except KeyError: + pass + else: + if self._ll_head.next.created + self.time_limit >= default_timer(): + return + try: + dict.__delitem__(self, key) + except KeyError: + pass + + try: + del self._ll_mapping[key] + except KeyError: + pass + self._ll_head.next.remove() + +if __name__ == "__main__": + c = LRU(3) + print c + + c['a'] = 1 + c['b'] = 2 + c['c'] = 3 + + print c + + c['c'] = 5 + print c + c['a'] = 6 + print c + + print '#'*20 + d = ExpiringLRU(1) + d['a'] = 1 + d['b'] = 2 + print d + import time + print 'sleeping' + time.sleep(1.5) + print d + d['c'] = 3 + d['d'] = 4 + print d + print 'sleeping' + time.sleep(1.5) + print d + d['e'] = 5 + d['c'] = 7 + print d + d['f'] = 6 + print d diff --git a/digsby/src/util/merge.py b/digsby/src/util/merge.py new file mode 100644 index 0000000..d6dd782 --- /dev/null +++ b/digsby/src/util/merge.py @@ -0,0 +1,120 @@ +''' + +For merging mappings recursively. + +>>> d, d2 = {1: {2: 3}}, {1: {2: 4}} +>>> res = merge(d, d2) +>>> assert res == {1: {2: 4}} + +''' + +from operator import isMappingType, isSequenceType +from heapq import heapreplace, heappop + +from .primitives import Storage +S = Storage + +if 'sentinel' not in globals(): + sentinel = object() + +def merge(*mappings, **opts): + ''' + Merges all mappings given as arguments. + + opts can have {'keyeq': predicate} + ''' + + d = {} + + keytransform = opts.get('keytransform', lambda k: k) + + for elem in mappings: + if isMappingType(elem): + # {key: value, key: value, ...} + items = elem.iteritems() + elif isSequenceType(elem): + # [(key, value), (key, value), ...] + if not all(len(s) == 2 for s in elem): + raise TypeError('mapping sequences must be sequences of (key,' + 'value) pairs: %s %s' % (type(elem), repr(elem))) + + items = elem + else: + raise TypeError('all arguments to merge must be mappings: %r' % elem) + + for key, value in items: + merge_values(d, keytransform(key), value, keytransform) + + return d + +def merge_values(mapping, key, value, keytransform = None): + ''' + Exactly like mapping.update({key: value}) except that if both values are + mappings, then they are merged recursively. + ''' + assert isMappingType(mapping) + assert key is not sentinel and value is not sentinel + + if keytransform is None: + keytransform = lambda k: k + + curval = mapping.get(key, sentinel) + if curval is not sentinel and isMappingType(curval) and isMappingType(value): + # key exists in old and new, and both values are mappings--recursively + # merge those mappings + for subkey, subvalue in value.iteritems(): + merge_values(curval, keytransform(subkey), subvalue) + else: + # new value doesn't exist in mapping or both values are not mappings-- + # so overwrite the old value with the new one + mapping[keytransform(key)] = value + + +def lower_if_string(s): + try: + return s.lower() + except AttributeError: + return s + +def merge_keys(d, maptype=Storage): + 'Recursively merges all keys of d (and its sub dictionaries), case insensitively.' + + if not d: return d + return tolower(merge(d, keytransform = lower_if_string), maptype=maptype) + +def tolower(mapping, maptype=Storage): + 'Returns a copy of a mapping tree with all string keys lowercased.' + + return maptype((getattr(k, 'lower', lambda k: k)(), tolower(v, maptype) if isMappingType(v) else v) + for k, v in mapping.iteritems()) + +def imerge(*iterlist, **key): + """Merge a sequence of sorted iterables. + + Returns pairs [value, index] where each value comes from +iterlist[index], and the pairs are sorted + if each of the iterators is sorted. + Hint use groupby(imerge(...), operator.itemgetter(0)) to get +the items one by one. + """ + # thanks http://mail.python.org/pipermail/python-bugs-list/2005-August/029983.html + if key.keys() not in ([], ["key"]): + raise TypeError("Excess keyword arguments for imerge") + + key = key.get("key", lambda x:x) + + # initialize the heap containing (inited, value, index, currentItem, iterator) + # this automatically makes sure all iterators are initialized, then run, and finally emptied + heap = [(False, None, index, None, iter(iterator)) + for index, iterator in enumerate(iterlist)] + + while heap: + inited, item, index, value, iterator = heap[0] + if inited: yield value, index + try: item = iterator.next() + except StopIteration: heappop(heap) + else: heapreplace(heap, (True, key(item), index, item, iterator)) + +def lazy_sorted_merge(*iterables, **k): + return (value for value, index in imerge(*iterables, **k)) + diff --git a/digsby/src/util/mimehelpers.py b/digsby/src/util/mimehelpers.py new file mode 100644 index 0000000..0e40391 --- /dev/null +++ b/digsby/src/util/mimehelpers.py @@ -0,0 +1,76 @@ +def form_builder(parts): + global _mimifiers + form_mime = mimify('multipart', _subtype = 'form-data') + for props in parts: + _name = props.get('name') + _type = props.get('type') + _val = props.get('value') + _kws = props.get('kws', {}) + disp_kws = props.get('disposition', {}) + + mime_part = _mimifiers.get(_type)(_val, **_kws) + mime_part.add_header('Content-Disposition', 'form-data', name = _name, **disp_kws) + + form_mime.attach(mime_part) + + return form_mime + +_mimifiers = {} + +def mimifier(name): + def registerer(f): + global _mimifiers + _mimifiers[name] = f + return f + return registerer + +_cap_names = {'nonmultipart' : 'NonMultipart'} +def mimify(type, **kwargs): + cap_name = _cap_names.get(type, type.capitalize()) + mime_mod = getattr(__import__('email.mime.%s' % type).mime, type) + mime_type = getattr(mime_mod, 'MIME%s' % cap_name) + return mime_type(**kwargs) + +@mimifier('text') +def mime_text(text, encoding = 'utf8'): + return mimify('text', _text = text.encode(encoding), _charset = encoding) + +@mimifier('text-noenc') +def mime_text_noenc(text, encoding = 'ascii'): + mimeobj = mimify('nonmultipart', _maintype = 'text', _subtype = 'plain',) + mimeobj.set_payload(text.encode(encoding)) + mimeobj.set_charset(encoding) + return mimeobj + +@mimifier('application') +def mime_app(data): + return mimify('application', _data = data) + +def subtype_for_image(data): + import imghdr, os.path + if hasattr(data, 'read') or os.path.exists(data): + return imghdr.what(data) + else: + return imghdr.what(None, data) + +@mimifier('image') +def mime_image(data): + subtype = subtype_for_image(data) + return mimify('image', _imagedata = data, _subtype = subtype) + +@mimifier('image-noenc') +def mime_image_noenc(data, filename = None): + import email.encoders as encoders + subtype = subtype_for_image(data) + mimeobj = mimify('image', _imagedata = data, _subtype = subtype, _encoder = encoders.encode_noop) + if filename is not None: + pass + return mimeobj + +@mimifier('data') +def mime_data(data): + import email.encoders as encoders + mimeobj = mimify('nonmultipart', _maintype = '', _subtype = '') + mimeobj._headers[:] = [] + mimeobj.set_payload(data) + return mimeobj diff --git a/digsby/src/util/net.py b/digsby/src/util/net.py new file mode 100644 index 0000000..85612b2 --- /dev/null +++ b/digsby/src/util/net.py @@ -0,0 +1,1518 @@ +from __future__ import with_statement + +import sys, traceback, re, struct, logging, random, StringIO +import calendar, time, rfc822 +import socks, socket, asynchat +import urllib, urllib2, urlparse +import httplib, httplib2 +import cookielib +import simplejson +import itertools + +from httplib import HTTPConnection +from httplib import NotConnected + +import primitives.funcs +import proxy_settings +from Events import EventMixin +from callbacks import callsback + +try: + sentinel +except NameError: + class Sentinel(object): + def __repr__(self): + return "" % (__file__, id(self)) + sentinel = Sentinel() + +log = logging.getLogger('util.net') + +default_chunksize = 1024 * 4 + +def get_ips_s(hostname = ''): + ''' + returns the ip addresses of the given hostname, default is '' (localhost) + @param hostname: hostname to get the ips of + @return ips as list of string + ''' + # gethostbyname_ex returns tuple: (hostname, aliaslist, ipaddr_list) + return socket.gethostbyname_ex(hostname or socket.gethostname())[2] + +def get_ips(hostname = ''): + ''' + returns the ip addresses of the given hostname, default is '' (localhost) + @param hostname: hostname to get the ips of + @return ips as list of int + ''' + return [socket.inet_aton(ip) for ip in get_ips_s(hostname)] + +myips = get_ips + +def myip(): + 'Returns the IP of this machine to the outside world.' + return myips()[0] + +def ip_from_bytes(bytes): + """ + Converts a long int to a dotted XX.XX.XX.XX quad string. + thanks U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66517} + """ + + + return socket.inet_ntoa(bytes) + +class FileChunker(object): + 'asynchat producer to return chunks of a file' + + def __init__(self, fileobj, chunksize = default_chunksize, close_when_done = False, + progress_cb = lambda bytes: None, bytecounter = None): + + self.fileobj = fileobj + self.chunksize = chunksize + self.close_when_done = close_when_done + if bytecounter is None: + bytecounter = fileobj.tell + self.total = bytecounter() + self.progress_cb = progress_cb + self.cancelled = False + + def more(self): + try: + data_read = self.fileobj.read(self.chunksize) + except ValueError: + try: self.fileobj.close() #make sure it's good and dead + except: pass + return '' #same as what happens at end of file + sz = len(data_read) + + if sz == 0 and self.close_when_done: + self.fileobj.close() + + self.total += sz + self.progress_cb(self.total) + + return data_read + + @classmethod + def tofile(cls, sourcefile, outfile, progress_callback = lambda *a: None, + bytecounter = None): + gen = cls.tofile_gen(sourcefile, outfile, progress_callback, bytecounter) + gen.next() + try: + gen.next() + except StopIteration: + pass + + @classmethod + def tofile_gen(cls, sourcefile, outfile, progress_callback = lambda *a: None, + bytecounter = None): + fc = cls(sourcefile, close_when_done = True, bytecounter = bytecounter) + yield fc + chunk = fc.more() + bytes_written = 0 + + # localize for speed + write, tell, more = outfile.write, outfile.tell, fc.more + + while chunk and not fc.cancelled: + write(chunk) + bytes_written += len(chunk) + progress_callback(tell()) + chunk = more() + + outfile.close() + +class NoneFileChunker(FileChunker): + def more(self): + return super(NoneFileChunker, self).more() or None + +def httpjoin(base, path, keepquery=False): + if path.startswith('http'): + # path is already absolute + return path + else: + joined = urlparse.urljoin(base, path) + if not keepquery: + parsed = list(urlparse.urlparse(joined)) + parsed[4] = '' + return urlparse.urlunparse(parsed) + else: + return joined + +class UrlQuery(str): + @classmethod + def form_encoder(cls): + return WebFormData + + @classmethod + def parse(cls, url, parse_query = True, utf8=False): + ''' + Return a mapping from a URL string containing the following keys: + + scheme://netloc/path;parameters?query#fragment + + If parse_query is True, "key=value&done" becomes + {'key':'value', 'done':True} instead. Otherwise query is just a string. + ''' + scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) + + if parse_query: + query = cls.form_encoder().parse(query, utf8=utf8) + + return dict(scheme = scheme, netloc = netloc, path = path, + params = params, query = query, fragment = fragment) + + @classmethod + def unparse(cls, scheme = '', netloc = '', path = '', params = '', query = None, fragment = ''): + if query is None: + query = {} + + if isinstance(query, dict): + query = cls.form_encoder()(query) + + return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) + + def __new__(cls, link, d={}, **kwargs): + ''' + Splat **kwargs (and d) as URL parameters into the given url. + + The url must not already contain parameters (but may end with a + question mark.) + ''' + if not (d or kwargs): + return str.__new__(cls, link) + + if link.endswith('?'): + link = link[:-1] + + if '?' in link: + joiner = '&' if (d or kwargs) else '' + else: + joiner = '?' + + return str.__new__(cls, ''.join([link, joiner, cls.form_encoder()(d = d, **kwargs)])) + +class WebFormData(str): + @classmethod + def encoder(cls): + return urllib.urlencode + + def __new__(cls, d={}, **kwargs): + if d and kwargs: + kwargs.update(d) + else: + kwargs = kwargs or d + base = cls.encoder()(kwargs) + return str.__new__(cls, base) + + @classmethod + def parse(cls, s, utf8=False): + querymap = {} + if not s: + return querymap + encoding = 'utf8url' if utf8 else 'url' + for elem in s.split('&'): + if '=' in elem: + key, value = elem.split('=', 1) + querymap[key] = value.decode(encoding) + else: + querymap[elem] = True + + return querymap + +class WebFormObjectData(WebFormData): + @classmethod + def encoder(cls): + return urlencode_object + +class UrlQueryObject(UrlQuery): + @classmethod + def form_encoder(cls): + return WebFormObjectData + +def urlencode_object(query): + return urllib.urlencode(param(query)) + +def param(a): + s = list() + def add(key, value): + s.append((key, value)) + for prefix in a: + buildParams( prefix, a[prefix], add ); + return s + +def buildParams(prefix, obj, add): + if isinstance(obj, dict) and obj: + for k,v in obj.items(): + buildParams( prefix + "[" + k + "]", v, add ); + elif isinstance(obj, list): + for k, v in enumerate(obj): + if not isinstance(v, (list,dict)): + k = '' + buildParams( prefix + "[" + str(k) + "]", v, add ); + else: + add(prefix, obj) + +def int_to_ip(s, byteorder='<'): + ''' + turns an int (or string as int) into a dotted IP address. + ex: int_to_ip('580916604') --> '124.21.160.34' + + default byteorder is little-endian (for msn) + ''' + return '.'.join(str(ord(c)) for c in struct.pack(byteorder+'I', int(s))) + + +# matches two or more spaces in a row +spacify_pattern = re.compile('( {2,})') + +def spacify_repl(m): + l = len(m.group()) + + if l == 2: + return ' ' # "word joiner" unicode character is ࠌ but we're not using that anymore. + else: + # for more than two spaces use + return ' ' + ''.join([' '] * (l-2)) + ' ' + +def spacify(s): + 'Turns consecutive spaces into a series of   entities.' + + return spacify_pattern.sub(spacify_repl, s) + +# +# a url for matching regexes +# +urlregex = re.compile(r'([A-Za-z][A-Za-z0-9+.-]{1,120}:[A-Za-z0-9/]' + '(([A-Za-z0-9$_.+!*,;/?:@&~=-])|%[A-Fa-f0-9]{2}){1,333}' + "(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*,;/?:@&~=%-']{0,1000}))?)" + '[^\. <]') + + +# +# thanks http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains +# +TLDs = \ +['arpa', 'root', 'aero', 'asia', 'biz', 'com', 'coop', 'edu', 'gov', 'info', 'int', + 'museum', 'name', 'net', 'org', 'pro', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', + 'an', 'ao', 'aq', 'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', + 'be', 'bf', 'bg', 'bh', 'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', + 'bw', 'by', 'bz', 'ca', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', + 'cn', 'co', 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', + 'dz', 'ec', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', + 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', + 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', + 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', + 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', + 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', + 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp', 'mq', 'mr', 'ms', 'mt', + 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf', 'ng', 'ni', 'nl', + 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', + 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', + 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', + 'so', 'sr', 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', + 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', + 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', + 'ws', 'ye', 'yt', 'yu', 'za', 'zm', 'zw' +] + + +domains = '(?:%s)' % '|'.join(TLDs) + +''' + the one true email regex(tm) + + complicated case and simple case: + this_email-user.name+mylabel@bbc.co.uk + name@domain +''' + +email_regex_string = r'(?:([a-zA-Z0-9_][a-zA-Z0-9_\-\.]*)(\+[a-zA-Z0-9_\-\.]+)?@((?:[a-zA-Z0-9\-_]+\.?)*[a-zA-Z]{1,4}))' + +email_regex = re.compile(email_regex_string) +email_wholestring_regex = re.compile('^' + email_regex_string + '$') + +is_email = primitives.funcs.ischeck(lambda s:bool(email_wholestring_regex.match(s))) + +class EmailAddress(tuple): + def __new__(cls, addr, default_domain=sentinel): + + try: + name, label, domain = parse_email(addr) + except: + if default_domain is sentinel: + raise + else: + name, label, domain = parse_email(addr + '@' + default_domain) + + return tuple.__new__(cls, (name, label, domain.lower())) + + @property + def name(self): + return self[0] + + @property + def label(self): + return self[1] + + @property + def domain(self): + return self[2] + + def __str__(self): + if self.label: + return '%s+%s@%s' % self + else: + return '%s@%s' % (self.name, self.domain) + + def __repr__(self): + return '' % (self,) + +def parse_email(s): + match = email_wholestring_regex.match(s) + + if match is None: + raise ValueError('Not a valid email address: %r', s) + + user, lbl, dom = match.groups() + if lbl: + lbl = lbl.strip('+') + else: + lbl = '' + return user, lbl, dom + + +protocols = 'ftp|https?|gopher|msnim|icq|telnet|nntp|aim|file|svn|svn+(?:\w)+' + +# for these TLDs, only a few have ever been allowed to be registered. +single_letter_rule_tlds = frozenset(('net', 'com', 'org')) +allowed_single_letter_domains = frozenset(('i.net', 'q.com', 'q.net', 'x.com', 'x.org', 'z.com')) + +linkify_url_pattern = re.compile( + # thanks textile + r'''(?=[a-zA-Z0-9]) # Must start correctly + ((?: # Match the leading part (proto://hostname, or just hostname) + (?:(?P%s) # protocol + :// # :// + (?: # Optional 'username:password@' + (?P\w+) # username + (?::(?P\w+))? # optional :password + @ # @ + )?)? # + (?P # hostname (sub.example.com). single-letter + (?:[iqxz]|(?:[-\w\x7F-\xFF]+)) # domains are not allowed, except those listed: + (?:\.[\w\x7F-\xFF][-\w\x7F-\xFF]*)*) # http://en.wikipedia.org/wiki/Single-letter_second-level_domains + )? # + (?::(?P\d+))? # Optional port number + (?P + (?: # Rest of the URL, optional + /? # Start with '/' + [^.!,?;:"<>\[\]{}\s\x7F-\xFF]+ # Can't start with these + (?: # + [.!,?;:]+ # One or more of these + [^.!,?;:"<>{}\s\x7F-\xFF]+ # Can't finish with these + #'" # # or ' or " + )*) # + )?) # + ''' % protocols, re.VERBOSE) + + +def isurl(text): + m = linkify_url_pattern.match(text) + if not m: return False + + protocol, host = m.group('protocol'), m.group('hostname') + + if host is not None: + # only allow links without protocols (i.e., www.links.com) + # if the TLD is one of the allowed ones + if protocol is None: + myTLDs = (host.split('.') if '.' in host else [host]) + if len(myTLDs) < 2 or myTLDs[-1] not in TLDs: + return False + + return True + +class LinkAccumulator(object): + def __init__(self, s=None): + self.links = [] + self.spans = [] + + if s is not None: + linkify_url_pattern.sub(self.repl, s) + + def repl(self, m): + url, protocol, after = _url_from_match(m) + if url is None: + return '' + href = ('http://' + url) if protocol is None else url + self.links.append(href) + self.spans.append(m.span()) + return '' + + def __iter__(self): + return itertools.izip(self.links, self.spans) + +def find_links(text): + return LinkAccumulator(text).links + +def _url_from_match(m): + protocol, host = m.group('protocol'), m.group('hostname') + url = m.group() + + # fix urls (surrounded by parens) + after = '' + if url.endswith(')') and '(' not in url: + url = url[:-1] + after = ')' + + if host is not None: + myTLDs = (host.split('.') if '.' in host else [host]) + # only allow links without protocols (i.e., www.links.com) + # if the TLD is one of the allowed ones + if protocol is None: + if len(myTLDs) < 2 or myTLDs[-1] not in TLDs: + return None, None, None + + if len(myTLDs) >= 2: + # don't allow single letter second level domains unless they are in the list + # of allowed ones above + second_level_domain = myTLDs[-2] + top_level_domain = myTLDs[-1] + if (len(second_level_domain) == 1 + and '.'.join((second_level_domain, top_level_domain)) not in allowed_single_letter_domains + and top_level_domain in single_letter_rule_tlds): + # "cancel" the replacement + return None, None, None + + return url, protocol, after + + return None, None, None + +def _dolinkify(text): + def repl(m): + url, protocol, after = _url_from_match(m) + + if url is None: + i, j = m.span() + return text[i:j] + + href = ('http://' + url) if protocol is None else url + + return '%s' % (href, url) + after # TODO: add 'target="_blank"' ? + + #text = email_regex.sub(r'\1', text) + text = linkify_url_pattern.sub(repl, text) + return text + +def linkify(text): + if isinstance(text, unicode): + return _linkify(text.encode('utf-8')).decode('utf-8') + else: + return _linkify(text) + +def _linkify(text): + # Linkify URL and emails. + + # If there is no html, do a simple search and replace. + if not re.search(r'''<.*>''', text): + return _dolinkify(text) + + # Else split the text into an array at <>. + else: + lines = [] + prev_line = '' + for line in re.split('(<.*?>)', text): + if not re.match('<.*?>', line) and not prev_line.startswith(' 0: + print "sending contents of", fileobj + + try: + read = fileobj.read + sendall = self.sock.sendall + + chunk = read(blocksize) + total = 0 + + while chunk: + total += len(chunk) + sendall(chunk) + progress_cb(total - progressDelta) + chunk = read(blocksize) + + except socket.error, v: + if v[0] == 32: # Broken pipe + self.close() + raise + +class SocketEventMixin(EventMixin): + events = EventMixin.events | set(("connected", + "connection_failed", + "socket_error", + "socket_closed", + )) + + def post_connect_error(self, e=None): + self.event("socket_error") + self.post_connect_disconnect() + + def post_connect_expt(self): + self.event("socket_error") + self.post_connect_disconnect() + + def post_connect_disconnect(self): + self.close() + self.event("socket_closed") + + def post_connect_close(self): + self.close() + self.event("socket_closed") + + def reassign(self): + self.handle_expt = self.post_connect_expt + self.handle_error = self.post_connect_error + self.handle_close = self.post_connect_close + self.do_disconnect = self.post_connect_disconnect + +def build_cookie(name, value, + version = 0, + domain = sentinel, + port = sentinel, + path = sentinel, + secure = False, + expires = None, + discard = False, + comment = None, + comment_url = None, + rest = {'httponly' : None}, + rfc2109 = False): + + if domain is sentinel: + domain = None + domain_specified = False + domain_initial_dot = False + else: + domain_specified = True + domain_initial_dot = domain.startswith('.') + + if port is sentinel: + port = None + port_specified = False + else: + port_specified = True + + if path is sentinel: + path = None + path_specified = False + else: + path_specified = True + return cookielib.Cookie(**locals()) + + +def GetSocketType(): + d = GetProxyInfo() + if d: + #socks.setdefaultproxy(**GetProxyInfo()) + return socks.socksocket + else: + return socket.socket + +NONE = 'NONPROX' +SYSDEFAULT = 'SYSPROX' +CUSTOM = "SETPROX" + +def GetProxyInfo(): + ps = proxy_settings + + try: + pd = ps.get_proxy_dict() + except Exception, e: + print >>sys.stderr, 'No proxies because: %r' % e + pd = {} + + get = pd.get + proxytype= get('proxytype') + port = get('port') + + try: + port = int(port) + except: + port = None + + addr = get('addr') + username = get('username') + password = get('password') + override = get('override') + rdns = get('rdns', False) + + try: + override = int(override) + except: + if override not in (SYSDEFAULT, CUSTOM, NONE): + override = SYSDEFAULT + else: + # Hack for old proxy code that only had 2 options + if override: + override = CUSTOM + else: + override = SYSDEFAULT + + if override == NONE: + return {} + elif override == SYSDEFAULT: + px = urllib._getproxies() + if not px: return {} + + url = px.get('http', None) + if url is None: return {} + + url = urlparse.urlparse(url) + + addr = url.hostname or '' + if not addr: return {} + + port = url.port or 80 + username = url.username or username or None + password = url.password or password or None + proxytype = 'http' + +# d = {} +# if all((addr, port)): +# d.update(addr=addr, port=port, proxytype=proxytype) +# +# if all((username, password)): +# d.update(username=username, password=password) + + if all((type, port, addr)): + proxytype=getattr(socks, ('proxy_type_%s'%proxytype).upper(), None) + return dict(addr=addr, port=port, username=username, password=password, proxytype=proxytype, rdns = rdns) + else: + return {} + +def GetProxyInfoHttp2(): + i = GetProxyInfo() + if not i: + return None + return httplib2.ProxyInfo(proxy_type = i['proxytype'], + proxy_host = i['addr'], + proxy_port = i['port'], + proxy_user = i['username'], + proxy_pass = i['password'], + proxy_rdns = i.get('rdns', False), + ) + +def getproxies_digsby(): + ''' + A replacement for urllib's getproxies that returns the digsby app settings. + the return value is a dictionary with key:val as: + 'http' : 'http://user:pass@proxyhost:proxyport' + + the dictionary can be empty, indicating that there are no proxy settings. + ''' + + pinfo = GetProxyInfo() + + proxies = {} + if pinfo.get('username', None) and pinfo.get('password', None): + unpw = '%s:%s@' % (pinfo['username'], pinfo['password']) + else: + unpw = '' + + if pinfo.get('port', None): + port = ':' + str(pinfo['port']) + else: + port = '' + + host = pinfo.get('addr', None) + if not host: + return proxies # empty dict + + all = unpw + host + port + + proxies = urllib.OneProxy() + proxies._proxyServer = all + + if pinfo['proxytype'] != socks.PROXY_TYPE_HTTP: + proxy_url = ('socks%d://' % (4 if pinfo['proxytype'] == socks.PROXY_TYPE_SOCKS4 else 5)) + all + return dict(socks=proxy_url, http=proxy_url, https=proxy_url) + + proxies['https'] = 'http://' + all + proxies['http'] = 'http://' + all + proxies['ftp'] = 'http://' + all + + return proxies + + +class SocksProxyHandler(urllib2.ProxyHandler): + ''' + Handles SOCKS4/5 proxies as well as HTTP proxies. + ''' + handler_order = 100 + def proxy_open(self, req, type): + try: + req._proxied + except AttributeError: + proxyinfo = self.proxies.get(type, '') + proxytype = urllib2._parse_proxy(proxyinfo)[0] + if proxytype is None: + req._proxied = False + return urllib2.ProxyHandler.proxy_open(self, req, type) + else: + req._proxytype = proxytype + req._proxied = True + + if proxytype == 'http' and type != 'https': # Http proxy + return urllib2.ProxyHandler.proxy_open(self, req, type) + else: + return None + else: + # Already proxied. skip it. + return None + + def socks4_open(self, req): + return self.socks_open(req, 4) + + def socks5_open(self, req): + return self.socks_open(req, 5) + + def socks_open(self, req, sockstype): + + orig_url_type, __, __, orighostport = urllib2._parse_proxy(req.get_full_url()) + req.set_proxy(orighostport, orig_url_type) + + endpoint = req.get_host() + if ':' in endpoint: + host, port = endpoint.rsplit(':', 1) + port = int(port) + else: + host, port = endpoint, 80 + req._proxied = True + + return self.parent.open(req) + +try: + import ssl +except ImportError: + pass +else: + class SocksHttpsOpener(urllib2.HTTPSHandler): + handler_order = 101 + def https_open(self, req): + if getattr(req, '_proxied', False) and getattr(req, '_proxytype', None) is not None: + return urllib2.HTTPSHandler.do_open(self, SocksHttpsConnection, req) + else: + return urllib2.HTTPSHandler.https_open(self, req) + + class SocksHttpsConnection(httplib.HTTPSConnection): + _sockettype = socks.socksocket + def connect(self): + "Connect to a host on a given (SSL) port." + + pd = urllib.getproxies().get('https', None) + if pd is None: + sockstype = '' + else: + sockstype, user, password, hostport = urllib2._parse_proxy(pd) + + + assert ':' in hostport # if we don't have a port we're screwed + host, port = hostport.rsplit(':', 1) + port = int(port) + + sock = self._sockettype(socket.AF_INET, socket.SOCK_STREAM) + sock.setproxy(proxytype=getattr(socks, 'PROXY_TYPE_%s' % sockstype.upper()), addr=host, port=port, rdns=True, username=user, password=password) + sock.connect((self.host, self.port)) + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file) + + +class SocksHttpOpener(urllib2.HTTPHandler): + handler_order = 101 + def http_open(self, req): + proxytype = getattr(req, '_proxytype', None) + if getattr(req, '_proxied', False) and proxytype not in ('http', None): + return urllib2.HTTPHandler.do_open(self, SocksConnection, req) + else: + return urllib2.HTTPHandler.http_open(self, req) + + +class SocksConnection(httplib.HTTPConnection): + _sockettype = socks.socksocket + def connect(self): + #- Parse proxies + pd = urllib.getproxies().get('http', None) + if pd is None: + sockstype = '' + else: + sockstype, user, password, hostport = urllib2._parse_proxy(pd) + + if 'socks' not in sockstype: + return httplib.HTTPConnection.connect(self) + + assert ':' in hostport # if we don't have a port we're screwed + host, port = hostport.rsplit(':', 1) + port = int(port) + + + for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): + + af, socktype, proto, canonname, sa = res + try: + self.sock = self._sockettype(af, socktype, proto) + self.sock.setproxy(proxytype=getattr(socks, 'PROXY_TYPE_%s' % sockstype.upper()), addr=host, port=port, rdns=False, username=user, password=password) + #- The rest is the same as superclass + + if self.debuglevel > 0: + print "connect: (%s, %s)" % (self.host, self.port) + self.sock.connect(sa) + except socket.error, msg: + if self.debuglevel > 0: + print 'connect fail:', (self.host, self.port) + if self.sock: + self.sock.close() + self.sock = None + continue + break + if not self.sock: + raise socket.error, msg + +class DigsbyHttpProxyPasswordManager(urllib2.HTTPPasswordMgr): + def find_user_password(self, realm, uri): + pi = GetProxyInfo() + return (pi['username'] or None), (pi['password'] or None) + +if not hasattr(urllib, '_getproxies'): + urllib._getproxies, urllib.getproxies = urllib.getproxies, getproxies_digsby + + urllib2.UnknownHandler.handler_order = sys.maxint # This should be last, no matter what + + + # BaseHandler comes first in the superclass list, but it doesn't take any arguments. + # This causes problems when initializing the class with an argument. + urllib2.ProxyDigestAuthHandler.__bases__ = urllib2.ProxyDigestAuthHandler.__bases__[::-1] + urllib2.ProxyBasicAuthHandler.handler_order = 499 # Make sure it comes before the 'default' error handler + + httplib2.ProxyInfo.get_default_proxy = staticmethod(GetProxyInfoHttp2) + +def GetDefaultHandlers(): + handlers = [SocksProxyHandler, SocksHttpOpener] + httpsopener = globals().get('SocksHttpsOpener', None) + if httpsopener is not None: + handlers.append(httpsopener) + + pwdmgr = DigsbyHttpProxyPasswordManager() + + for auth_handler_type in (urllib2.ProxyBasicAuthHandler, urllib2.ProxyDigestAuthHandler): + handlers.append(auth_handler_type(pwdmgr)) + + return handlers + +def build_opener(*a, **k): + if 'default_classes' not in k: + k['default_classes'] = GetDefaultHandlers() + urllib2.default_opener_classes + + return urllib2.build_opener(*a, **k) + +opener = urllib2.build_opener(*GetDefaultHandlers()) +# for handler in opener.handlers: +# handler._debuglevel = 1 +urllib2.install_opener(opener) + + +_hostprog = re.compile('^//([^/?]*)(.*)$') +def splithost(url): + """splithost('//host[:port]/path') --> 'host[:port]', '/path'.""" + match = _hostprog.match(url) + if match: + groups = match.group(1, 2) + # Check if we're throwing out a slash accidentally. if so, put it back and return. + if groups[0] == '': + return groups[0], '/' + groups[1] + else: + return groups + return None, url + +urllib.splithost = urllib2.splithost = splithost # urllib2 imports it "from" urllib so we have to replace its copy as well + +def _HTTPError__repr(self): + if not hasattr(self, 'content'): + try: + self.content = self.read() + self.close() + except Exception, e: + self._error = e + self.content = "error reading body: %r" % e + self.read = lambda: '' + else: + self._stringio = StringIO.StringIO(self.content) + self.read = self._stringio.read + + etxt = self.content + return '' % (str(getattr(self, 'hdrs', {})), etxt) + +urllib2.HTTPError.__repr__ = _HTTPError__repr + +def httpok(_code): + return getattr(_code, 'status', _code)//100 == 2 + +class SimpleProducer(asynchat.simple_producer): + def more(self): + data = asynchat.simple_producer.more(self) + if data == '': + data = None + + return data + +class CallbackProducerMixin(object): + ''' + Simple mixin for producer classes that calls callback.success() when all data has been read from it (via more()). + + Must be listed before the producer type in the inheritance list, for method resolution to work correctly. + ''' + def __init__(self): + bases = self.__class__.__bases__ + found_self = False + self._siblingClass = None + for base in bases: + if base is CallbackProducerMixin: + found_self = True + else: + if hasattr(base, 'more') and found_self: + self._siblingClass = base + break + + if self._siblingClass is None: + raise AssertionError("This mix-in requires there is a sibling class with a 'more' method. " + "Additionally, CallbackProducerMixin must be *before* that class in the inheritance list " + "(for method resolution reasons).") + + @callsback + def set_callback(self, callback=None): + self._callback = callback + + def more(self): + if not hasattr(self, '_siblingClass'): + result = None + else: + result = self._siblingClass.more(self) + + if result is None: + if getattr(self, '_callback', None) is not None: + self._callback.success() + if getattr(self, '_callback', None) is not None: + del self._callback + return result + +class SimpleCallbackProducer(CallbackProducerMixin, SimpleProducer): + ''' + Subclass of asynchat.simple_producer that calls self._callback.success() when all data has + been exhausted. Set callback after instantiation with set_callback() method. + + SimpleCallbackProducer(data, buffer_size=512) + ''' + def __init__(self, data): + SimpleProducer.__init__(self, data) + CallbackProducerMixin.__init__(self) + +## Add a 'remove' method to asynchat's fifo + +def _fifo_remove(self, val): + ''' + returns True if value was found + removed, false otherwise. + ''' + # self.list is a deque + try: + self.list.remove(val) + except Exception: + return False + else: + return True + +asynchat.fifo.remove = _fifo_remove + +@callsback +def producer_cb(data, callback=None): + ''' + producer(data, success=callable) + + Facade for SimpleCallbackProducer. + ''' + prod = SimpleCallbackProducer(data) + prod.set_callback(callback=callback) + return prod + +class GeneratorProducer(object): + def __init__(self, gen): + self.gen = gen + + def more(self): + if self.gen is None: + return None + + try: + return self.gen.next() + except StopIteration: + self.gen = None + return None + +# from tr.im: +_short_domains = frozenset(( + '2big.at', '2me.tw', '3.ly', 'a.gd', 'a2n.eu', 'abbrr.com', + 'adjix.com', 'arunaurl.com', 'beam.to', 'bit.ly', + 'bitly.com', 'bkite.com', 'blip.fm', 'bloat.me', + 'budurl.com', 'burnurl.com', 'canurl.com', 'chilp.it', + 'cli.gs', 'decenturl.com', 'digg.com', 'digs.by', 'dn.vc', + 'doiop.com', 'durl.us', 'dwarfurl.com', 'easyuri.com', + 'easyurl.net', 'ff.im', 'fon.gs', 'fyiurl.com', 'ginx.com', + 'goo.gl', 'go2.me', 'hex.io', 'hopurl.com', 'hurl.ws', 'icanhaz.com', + 'idek.net', 'is.gd', 'ix.it', 'jijr.com', 'jmp2.net', + 'knol.me', 'krz.ch', 'kurl.us', 'last.fm', 'lin.cr', + 'lnk.in', 'makeitbrief.com', 'memurl.com', 'micurl.com', + 'minu.ws', 'moourl.com', 'myturl.com', 'notlong.com', 'ow.ly', + 'pic.im', 'pikchur.com', 'ping.fm', 'piurl.com', 'poprl.com', + 'qurlyq.com', 'r.im', 'refurl.com', 'rubyurl.com', 'rurl.org', + 'rurl.us', 's7y.us', 'sai.ly', 'sbt.sh', 'shorl.com' + 'short.ie', 'short.to', 'shortna.me', 'shrinkify.com', + 'shw.com', 'si9.org', 'skocz.pl', 'smalur.com', 'sn.im', + 'snipr.com', 'snipurl.com', 'snurl.com', 'spedr.com', + 'starturl.com', 'three.ly', 'timesurl.at', 'tiny.cc', 'tiny.pl', + 'tinyarro.ws', 'tinylink.co.za', 'tinyuri.ca', 'tinyurl.com', + 'tnij.org', 'tr.im', 'turo.us', 'twitclicks.com', 'twitpic.com', + 'twt.fm', 'twurl.cc', 'twurl.nl', 'u.nu', 'ub0.cc', 'uris.jp', + 'urlb.at', 'urlcut.com', 'urlenco.de', 'urlhawk.com', + 'urltea.com', 'vieurl.com', 'w3t.org', 'x.se', 'xaddr.com', + 'xr.com', 'xrl.us', 'yep.it', 'zi.ma', 'zombieurl.com', 'zz.gd')) + +def is_short_url(url, domains = _short_domains): + parsed = urlparse.urlparse(url) + if parsed.netloc in domains: + return True + + return False + +def get_snurl(url): + return get_short_url(url, 'snurl') + +def get_isgd(url): + return get_short_url(url, 'isgd') + +def get_tinyurl(url): + return get_short_url(url, 'tinyurl') + +class UrlShortenerException(Exception): + pass + +from .lrucache import LRU +_short_url_cache = LRU(10) +def cache_shortened_url(url, short_url): + if short_url: + _short_url_cache[short_url] = url + +class UrlShortener(object): + endpoint = None + + def build_request_url(self, url): + return UrlQuery(self.endpoint, d = self.get_args(url.encode('utf-8'))) + + def shorten(self, url): + try: + resp = urllib2.urlopen(self.build_request_url(url)) + except urllib2.HTTPError, e: + resp = e + + short_url = self.process_response(resp) + cache_shortened_url(url, short_url) + return short_url + + def shorten_async(self, url, success, error=None): + def async_success(req, resp): + try: + ret = self.process_response(resp) + except Exception as e: + if error is not None: error(e) + else: + cache_shortened_url(url, ret) + success(ret) + + def async_error(req=None, resp=None): + print req + print resp + if error is not None: + error(None) # TODO: async interface for errors? + + import common.asynchttp as asynchttp + asynchttp.httpopen(self.build_request_url(url), success=async_success, error=async_error) + + def get_args(self, url): + raise NotImplementedError + + def process_response(self, resp): + if resp.code != 200: + body = resp.read() + raise UrlShortenerException(body) + + ret = resp.read() + return ret + +class ResponseIsResultShortener(UrlShortener): + def process_response(self, resp): + ret = UrlShortener.process_response(self, resp) + if not isurl(ret): + raise UrlShortenerException(body) + return ret + +class isgd_shortener(ResponseIsResultShortener): + endpoint = 'http://is.gd/api.php' + def get_args(self, url): + return dict(longurl=url) + +class tinyurl_shortener(ResponseIsResultShortener): + endpoint = 'http://tinyurl.com/api-create.php' + def get_args(self, url): + return dict(url=url) + +class threely_shortener(UrlShortener): + endpoint = 'http://3.ly/' + def get_args(self, url): + return dict(api = 'em5893833', + u = url) + + def process_response(self, resp): + ret = UrlShortener.process_response(self, resp) + if not ret.startswith(self.endpoint): + raise UrlShortenerException(ret) + + return ret + +class snipr_shortener(UrlShortener): + endpoint = 'http://snipr.com/site/snip' + def get_args(self, url): + return dict(r='simple', link=url.encode('url')) + def process_response(self, resp): + ret = UrlShortener.process_response(self, resp) + if not ret.startswith('http'): + raise UrlShortenerException('bad url: %r' % ret, ret) + return ret + +class shortname_shortener(UrlShortener): + endpoint = 'http://shortna.me/hash/' + def get_args(self, url): + return dict(snURL=url, api=0) + + def process_response(self, resp): + ret = UrlShortener.process_response(self, resp) + import lxml.html as HTML + doc = HTML.fromstring(ret) + links = doc.findall('a') + for link in links: + href = link.attrib.get('href') + if href is not None and href.startswith('http://shortna.me/') and href != 'http://shortna.me': + return href + + raise UrlShortenerException('short link not found in %r' % ret, ret) + +# not currently used +class digsby_shortener(UrlShortener): + endpoint = 'https://accounts.digsby.com/api/shorturl' + def get_args(self, url): + import common + import hashlib + username = common.profile.username.encode('utf8') + password = hashlib.sha256(common.profile.password.encode('utf8')).digest() + + return {'user':username, 'pass':password, 'link' : url} + + def process_response(self, httpresp): + ret = UrlShortener.process_response(self, httpresp) + + resp = simplejson.loads(ret) + if resp['shorter']['status'] == 'error': + raise UrlShortenerException(resp['shorter']['errormsg']) + + elif resp['shorter']['status'] == 'ok': + import common + url = resp['shorter']['shortURL'] + to_add = common.pref('urlshorteners.digsby.append_text', type = unicode, default = u'') + return url + to_add + +class bitly_shortener(UrlShortener): + login = 'digsby' + api_key = 'R_1fdb0bb8ce9af01f9939c2ffdf391dc8' + endpoint = 'http://api.bit.ly/shorten' + + def __init__(self, login=None, api_key=None): + if login is not None: + self.login = login + if api_key is not None: + self.api_key = api_key + + def get_args(self, url): + return dict(longUrl=url, version='2.0.1', login=self.login, apiKey=self.api_key) + + def process_response(self, resp): + ret = UrlShortener.process_response(self, resp) + + try: + info = simplejson.loads(ret) + except Exception: + raise UrlShortenerException('expected JSON') + else: + if info['errorCode'] == 0: + return self.extract_shorturl(info) + else: + raise UrlShortenerException(info['errorMessage']) + + def extract_shorturl(self, info): + return info['results'].values()[0]['shortUrl'] + + +class digsby_bitly_shortener(bitly_shortener): + def extract_shorturl(self, info): + return "http://digs.by/" + info['results'].values()[0]['userHash'] + + +# TODO: add bit.ly, cli.gs, tr.im +_shorteners = { + #'snipr' : snipr_shortener, + #'snurl' : snipr_shortener, + #'snipurl' : snipr_shortener, # has a new API we didn't implement yet + 'isgd' : isgd_shortener, + 'tinyurl' : tinyurl_shortener, + 'tiny' : tinyurl_shortener, + 'threely' : threely_shortener, + '3ly' : threely_shortener, + 'shortname': shortname_shortener, + 'digsby' : digsby_bitly_shortener, + } + +def get_short_url(url, provider=None, choices = None): + """ + Gets a shortened url from 'provider' through their api. + + Intended to be used with threaded: + threaded(get_short_url)(url, 'tinyurl', success=func, error=func) + + @param url: The URL to be snipped + @param provider: The shortening service to use. + """ + if choices is None: + choices = list(_shorteners.keys()) + choices = choices[:] + random.shuffle(choices) + + if provider is not None: + choices.append(provider) + else: + import common + choices.append(common.pref("url_shortener.default", type = basestring, default = 'digsby')) + + e = None + while choices: + try: + provider = choices.pop() + shortener = _shorteners.get(provider) + if shortener is None: + raise Exception("UrlShortener provider %r not found", provider) + + return shortener().shorten(url) +# except UrlShortenerException, e: +# log.error('error getting short URL from %r: %r', provider, e) +# raise e + except Exception, e: + log.error('error getting short URL from %r: %r', provider, e) + shortener = provider = None + + if e is None: + e = Exception('No shorteners found!') + # none of them worked + raise e + + +def wget(url, data=None): + ''' + return urllib2.urlopen(url, data).read() + ''' + from contextlib import closing + import urllib2 + with closing(urllib2.urlopen(url, data=data)) as web: + return web.read() + +def long_url_from_cache(shorturl): + try: + return _short_url_cache[shorturl] + except KeyError: + return None + +def unshorten_url(url, cb): + longurl = long_url_from_cache(url) + if url is not None: + return cb(longurl) + + requrl = UrlQuery('http://untiny.me/api/1.0/extract', + url=url, format='json') + + def success(req, resp): + json = resp.read() + unshortened_url = simplejson.loads(json)['org_url'] + cb(unshortened_url) + + def error(req, resp): + pass + + import common.asynchttp as asynchttp + return asynchttp.httpopen(requrl, success=success, error=error) + +def timestamp_to_http_date(ts): + return rfc822.formatdate(timeval=ts) + +def http_date_to_timestamp(date_str): + if date_str is None: + return None + return calendar.timegm(rfc822.parsedate(date_str)) + +def user_agent(): + return 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.15' + +if __name__ == '__main__': + print get_snurl('http://www.google.com') + + diff --git a/digsby/src/util/network/__init__.py b/digsby/src/util/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/digsby/src/util/network/soap.py b/digsby/src/util/network/soap.py new file mode 100644 index 0000000..d916ea1 --- /dev/null +++ b/digsby/src/util/network/soap.py @@ -0,0 +1,403 @@ +import ZSI +import ZSI.schema as schema +import ZSI.fault as fault +import ZSI.auth as auth +import ZSI.client as ZSIClient +import ZSI.wstools.Namespaces as WSNS + +import logging +import urlparse + +import common.asynchttp as asynchttp +import util.callbacks as callbacks + +import traceback +log = logging.getLogger("util.soap") + +SOAP_TYPES = ('text/xml', + 'application/soap+xml', + ) + +MinTime = '0001-01-01T00:00:00.0000000-08:00' + +class Binding(ZSIClient.Binding): + + def __init__(self, url, transport = None, **k): + super(Binding, self).__init__(url, transport = transport, **k) + + def __getattr__(self, attr): + if attr == "_getAttributeNames": + return False + else: + return ZSIClient.Binding.__getattr__(self, attr) + + + def get_default_headers(self): + return {'Content-Type' : 'application/soap+xml; charset="utf-8"', + } + + @callbacks.callsback + def RPC(self, url, opname, obj, callback = None, **kw): + '''Send a request, return the reply. See Send() and Recieve() + docstrings for details. + ''' + self.Send(url, opname, obj, + # Might be a fault, or it could be an HTTP error + error = lambda response: self.Receive(response, callback = callback, **kw), + success = lambda response: self.Receive(response, callback = callback, **kw), + **kw) + + @callbacks.callsback + def Send(self, url, opname, obj, nsdict={}, soapaction=None, wsaction=None, + endPointReference=None, soapheaders=(), callback = None, **kw): + '''Send a message. If url is None, use the value from the + constructor (else error). obj is the object (data) to send. + Data may be described with a requesttypecode keyword, the default + is the class's typecode (if there is one), else Any. + + Try to serialize as a Struct, if this is not possible serialize an Array. If + data is a sequence of built-in python data types, it will be serialized as an + Array, unless requesttypecode is specified. + + arguments: + url -- + opname -- struct wrapper + obj -- python instance + + key word arguments: + nsdict -- + soapaction -- + wsaction -- WS-Address Action, goes in SOAP Header. + endPointReference -- set by calling party, must be an + EndPointReference type instance. + soapheaders -- list of pyobj, typically w/typecode attribute. + serialized in the SOAP:Header. + requesttypecode -- + + ''' + url = url or self.url + endPointReference = endPointReference or self.endPointReference + + # Serialize the object. + d = {} + d.update(self.nsdict) + d.update(nsdict) + + SWC = kw.get('writerclass', self.writerclass or ZSI.SoapWriter) + sw = SWC(nsdict=d, header=True, outputclass=self.writerclass, + encodingStyle=kw.get('encodingStyle'),) + + requesttypecode = kw.get('requesttypecode') + if kw.has_key('_args'): #NamedParamBinding + tc = requesttypecode or ZSI.TC.Any(pname=opname, aslist=False) + sw.serialize(kw['_args'], tc) + elif not requesttypecode: + tc = getattr(obj, 'typecode', None) or ZSI.TC.Any(pname=opname, aslist=False) + try: + if type(obj) in ZSI._seqtypes: + obj = dict(map(lambda i: (i.typecode.pname,i), obj)) + except AttributeError: + # can't do anything but serialize this in a SOAP:Array + tc = ZSI.TC.Any(pname=opname, aslist=True) + else: + tc = ZSI.TC.Any(pname=opname, aslist=False) + + sw.serialize(obj, tc) + else: + sw.serialize(obj, requesttypecode) + + for i in soapheaders: + sw.serialize_header(i) + + # + # Determine the SOAP auth element. SOAP:Header element + if self.auth_style & auth.AUTH.zsibasic: + sw.serialize_header(ZSIClient._AuthHeader(self.auth_user, self.auth_pass), + ZSIClient._AuthHeader.typecode) + + # + # Serialize WS-Address + if self.wsAddressURI is not None: + if self.soapaction and wsaction.strip('\'"') != self.soapaction: + raise WSActionException, 'soapAction(%s) and WS-Action(%s) must match'\ + %(self.soapaction,wsaction) + + self.address = Address(url, self.wsAddressURI) + self.address.setRequest(endPointReference, wsaction) + self.address.serialize(sw) + + # + # WS-Security Signature Handler + if self.sig_handler is not None: + self.sig_handler.sign(sw) + + scheme,netloc,path,nil,nil,nil = urlparse.urlparse(url) + + soapdata = str(sw) + headers = self.get_default_headers() + + sa = soapaction or self.soapaction + if sa: + headers['SOAPAction'] = sa +# if self.auth_style & auth.AUTH.httpbasic: +# headers['Authorization'] = 'Basic ' + _b64_encode(self.auth_user + ':' + self.auth_pass).replace("\012", "") +# elif self.auth_style == auth.AUTH.httpdigest and 'Authorization' not in headers and 'Expect' not in headers: +# pass + + log.debug_s("soap data: %r", soapdata) + req = asynchttp.HTTPRequest.make_request(self.url, + data = soapdata, + headers = headers, + method = 'POST', + adjust_headers = False) + + self.ps = self.data = None + + transport = self.transport or asynchttp + + transport.httpopen(req, + success = lambda req, resp: self.handle_response(req, resp, callback = callback), + error = lambda req, resp: self.handle_error(req, resp, callback=callback)) + + @callbacks.callsback + def handle_response(self, request, response, callback = None): + callback.success(response) + + @callbacks.callsback + def handle_error(self, request, response, callback = None): + callback.error(response) + + + def ReceiveRaw(self, response): + '''Read a server reply, unconverted to any format and return it. + ''' + + # TODO: handle possible HTTP errors and stuff here. + response.body.seek(0) + response.content = response.body.read() + response.body.seek(0) + log.info("SOAP response: %r", response.content) + return response.content + + def IsSOAP(self, response): + mimetype = response.headers.get_content_type() + return mimetype in SOAP_TYPES + + def ReceiveSOAP(self, response, readerclass=None, **kw): + '''Get back a SOAP message. + ''' + + self.ReceiveRaw(response) + + if self.ps: return self.ps + if not self.IsSOAP(response): + raise TypeError( + 'Response is "%s", not in %r' % (response.headers.get_content_type(), SOAP_TYPES)) + if len(response.content) == 0: + raise TypeError('Received empty response') + + self.ps = ZSI.ParsedSoap(response.content, + readerclass=readerclass or self.readerclass, + encodingStyle=kw.get('encodingStyle')) + + if self.sig_handler is not None: + self.sig_handler.verify(self.ps) + + return self.ps + + def IsAFault(self, response): + '''Get a SOAP message, see if it has a fault. + ''' + self.ReceiveSOAP(response) + return self.ps.IsAFault() + + def ReceiveFault(self, response, **kw): + '''Parse incoming message as a fault. Raise TypeError if no + fault found. + ''' + self.ReceiveSOAP(response, **kw) + if not self.ps.IsAFault(): + raise TypeError("Expected SOAP Fault not found") + + return fault.FaultFromFaultMessage(self.ps) + + @callbacks.callsback + def Receive(self, response, callback = None, **kw): + '''Parse message, create Python object. + + KeyWord data: + faults -- list of WSDL operation.fault typecodes + wsaction -- If using WS-Address, must specify Action value we expect to + receive. + ''' +# fault = None +# try: +# fault = self.ReceiveFault(response, **kw) +# except TypeError: +# pass +# else: +# return callback.error(FaultException(msg)) + self.ReceiveSOAP(response, **kw) + pname = self.ps.body_root.namespaceURI, self.ps.body_root.localName + el_type = schema.GTD(*pname) + if el_type is not None: + tc = el_type(pname) + else: + tc = schema.GED(*pname) + + reply = self.ps.Parse(tc) + + if self.ps.body_root.localName == 'Fault': + return callback.error(reply) + + reply_tc = getattr(reply, 'typecode', None) + if reply_tc is None: + log.warning("Unable to parse reply with tc=%r (got %r). Returning empty tc instance.", tc, reply) + reply = tc + + headers_to_parse = [] + for hdr in self.ps.header_elements: + hdr_pname = (hdr.namespaceURI, hdr.localName) + hdr_type = schema.GTD(*hdr_pname) + if hdr_type is not None: + hdr_tc = hdr_type(hdr_pname) + else: + hdr_tc = schema.GED(*hdr_pname) + + headers_to_parse.append(hdr_tc) + + headers = self.ps.ParseHeaderElements(headers_to_parse) + reply.soapheaders = headers + + if self.address is not None: + self.address.checkResponse(self.ps, kw.get('wsaction')) + callback.success(reply) + + def __repr__(self): + return "<%s instance %s>" % (self.__class__.__name__, ZSI._get_idstr(self)) + + +class BindingSOAP(object): + BindingClass = Binding + nsdict = dict(#ps = MSNS.PPCRL.BASE, + #psf = MSNS.PPCRL.FAULT, + wsse = WSNS.OASIS.WSSE, + wsp = WSNS.WSP.POLICY, # should this be WSP200212 ? + wsu = WSNS.OASIS.UTILITY, + wsa = WSNS.WSA.ADDRESS, # should this be WSA200403 ? + wssc = WSNS.BEA.SECCONV, + wst = WSNS.WSTRUST.BASE,# should this be WSTRUST200404 ? + ) + def __init__(self, url, **kw): + kw.setdefault("readerclass", None) + kw.setdefault("writerclass", None) + # no resource properties + bindingClass = kw.get('BindingClass', self.BindingClass) + nsdict = kw.setdefault('nsdict', {}) + nsdict.update(self.nsdict) + self.transport = kw.get('transport', None) + self.binding = bindingClass(url=url, **kw) + if self.transport is None: + raise Exception + if self.binding.transport is None: + raise Exception + # no ws-addressing + +class soapcall(object): + def __init__(self, + soap_module = None, + service_name = None, + name = None, + getHeaders = None, + getLocator = None, + getPort = None, + getMessage = None, + handleHeaders = None, + handleResponse = None): + + self.soap_module = soap_module + self.service_name = service_name + self.name = name + self.message_setup = None + + self._getLocator = getLocator + self._getPort = getPort + self._getMessage = getMessage + self._getHeaders = getHeaders + self._handleHeaders = handleHeaders + self._handleResponse = handleResponse + + def __get__(self, obj, objtype): + @callbacks.callsback + def call(**k): + + soap_module = self.soap_module or obj.Soap + self.service_name = self.service_name or getattr(obj, 'AppName', None) + getLocator = self._getLocator or getattr(obj, 'Locator', getattr(soap_module, '%sLocator' % self.service_name)) + locator = getLocator() + getPort = self._getPort or getattr(obj, 'getPort', None) or getattr(locator, 'get%sPort' % self.name) + + transport = getattr(obj, 'Transport', None) + + port = getPort(url = k.get('PortUrl', None), soapName = self.name, locator = locator, transport = transport, **k) + phn = getattr(obj, 'PreferredHostName', None) + if phn is not None: + parsed_url = urlparse.urlparse(port.binding.url)._asdict() + parsed_url['netloc'] = phn + new_url = urlparse.urlunparse(urlparse.ParseResult(**parsed_url)) + log.debug("using PreferredHostName url %r instead of default %r", new_url, port.binding.url) + port.binding.url = new_url + + callback = k.pop('callback') + getHeaders = self._getHeaders or getattr(obj, '%sHeaders' % self.name, getattr(obj, 'serviceHeaders', lambda *a, **k: ())) + headers = getHeaders(**k) or () + + getMessage = self._getMessage or getattr(soap_module, '%sMessage' % self.name) + message = getMessage() + e = None + try: + do_request = self.message_setup(self=obj, msg=message, **k) + except Exception, e: + do_request = False + + if do_request is False: + _v = 'BadRequest', message, k, e + if e: + traceback.print_exc() + return callback.error(Exception(*_v)) + + def success(resp): + handleHeaders = getattr(obj, self._handleHeaders or 'handle%sHeaders' % self.name, getattr(obj, 'handleHeaders', None)) + handleResponse = getattr(obj, self._handleResponse or '%sSuccess' % self.name, None) + + if handleHeaders is not None: + handleHeaders(client = k.get('client'), headers = resp.soapheaders) + + if handleResponse is not None: + resp = handleResponse(response = resp, **k) + + callback.success(resp) + + try: + getattr(port, self.name)(request = message, soapheaders = headers, + success = success, error = callback.error, + **k) + except Exception, e: + traceback.print_exc() + callback.error(e) + + return do_request + + def CheckAndCall(**k): + obj.CheckAuth(k.get('client'), + success = lambda:call(**k), + error = getattr(k.get('callback'), 'error', lambda *a: None)) + + return CheckAndCall + + def __call__(self, message_setup): + if self.name is None: + self.name = message_setup.func_name + self.message_setup = message_setup + return self + diff --git a/digsby/src/util/observe/__init__.py b/digsby/src/util/observe/__init__.py new file mode 100644 index 0000000..014717f --- /dev/null +++ b/digsby/src/util/observe/__init__.py @@ -0,0 +1,39 @@ +from observable import * +from observabledict import * +from observablelist import * +from observableproperty import * + +def clear_all(): + import gc + + count = 0 + for obj in gc.get_objects(): + try: + observers = getattr(obj, 'observers', None) + clear = getattr(observers, 'clear', None) + if clear is not None: + clear() + count += 1 + except: + pass + + return count + +import logging +def add_observers(obj, argslist): + for func, attr in argslist: + if not hasattr(obj, attr): + logging.critical('%s has no attribute %s to observe!', + obj.__class__.__name__, attr) + else: + obj.add_observer(func, attr) + + +if __name__ == '__main__': + import doctest, unittest + + import observable + + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(observable)) + unittest.TextTestRunner().run(suite) diff --git a/digsby/src/util/observe/observable.py b/digsby/src/util/observe/observable.py new file mode 100644 index 0000000..3d4c775 --- /dev/null +++ b/digsby/src/util/observe/observable.py @@ -0,0 +1,412 @@ +''' +Classes for observing property changes on objects. +''' + +from __future__ import with_statement +from contextlib import contextmanager +from collections import defaultdict +from util.introspect import funcinfo +from util.primitives.refs import better_ref as ref +from weakref import ref as weakref_ref +from peak.util.imports import whenImported +import sys +import traceback +from threading import currentThread + +try: + sentinel +except NameError: + class Sentinel(object): + def __repr__(self): + return "" % (__file__, id(self)) + sentinel = Sentinel() + +import logging +log = logging.getLogger('observable') + + +# with wx present, there will be extra event functionality +# CAS: it never would work properly w/o wx, but now it might not work properly +# if wx isn't used somewhere else first. +_usingWX = False +def _setup_usingWX(_mod): + try: + import wx + globals()['wx'] = wx + from wx import EvtHandler #@UnusedImport + globals()['EvtHandler'] = EvtHandler + from wx import PyDeadObjectError #@UnusedImport + globals()['PyDeadObjectError'] = PyDeadObjectError + except ImportError: + pass + else: + globals()['_usingWX'] = True + +class PyDeadObjectError(object): + #must be before whenImported('wx', _setup_usingWX) + pass +whenImported('wx', _setup_usingWX) + +class mydefaultdict(defaultdict): + pass + +class ObservableBase(object): + ''' + Provides plumbing for the observable pattern. You probably don't want to + subclass this--try Observable instead. + ''' + + observable_attrs = 'observers _observable_frozen class_names _freezecount guimap _flags'.split() + + _observable_frozen = False + _freezecount = False + + def __init__(self): + # attribute -> [callbacks] + s = object.__setattr__ + + s(self, 'observers', mydefaultdict(list)) + s(self, '_flags', {}) + + def add_observer_callnow(self, callback, *attrs, **options): + 'Like add_observer, except invokes "callback" immediately.' + + self.add_observer(callback, *attrs, **options) + + for attr in attrs: + val = getattr(self, attr) + callback(self, attr, val, val) + + def add_observer(self, callback, *attrs, **options): + ''' + Listen for changes on an object. + + Callback should be a callable with the following signature: + + callback(obj, attr, old, new) + + obj is this object (self), attr is the name of what has changed, + old is the old value (None if there was no value), and new is + the new value. + ''' + assert all(isinstance(a, basestring) for a in attrs) + + if isinstance(getattr(callback, 'im_self', None), EvtHandler) \ + or 'gui_target' in options: + + if not hasattr(self, 'guimap'): object.__setattr__(self, 'guimap', {}) + + callback = _wrap_gui_callback(self.guimap, + options.get('gui_target', callback)) + + obj = options.get('obj') + if obj is None: + obj = getattr(callback, 'im_self', None) + + # when a weakref dies, make sure it is removed from our .observers list. + + try: + obs = self.observers + except AttributeError, err: + raise AttributeError('did you forget to call Observable.__init__ in a subclass? ' + str(err)) + + weak_obs = weakref_ref(obs) + def on_weakref_dead(wref): + _obs = weak_obs() + if _obs is not None: + for a in (attrs or [None]): + live = [] + for r in _obs[a]: + if getattr(r, 'object', None) is wref: + r.destroy() + else: + live.append(r) + _obs[a] = live + + _cb = ref(callback, on_weakref_dead, obj = obj) + + for a in (attrs or [None]): + if _cb not in obs[a]: + obs[a].append(_cb) + + def add_gui_observer(self, callback, *attrs, **_kws): + cb = lambda *a, **k: wx.CallAfter(callback, *a, **k) + obj = _kws.get('obj', None) + if obj is None: + obj = callback.im_self + cb._observer_cb = callback + return self.add_observer(cb, *attrs, **dict(obj=obj)) + + + + def notifyall(self, attrs, olds, vals): + if self._observable_frozen: return + + kw = {'doNone': False} + obs = self.observers + notify = self.notify + livecbs = self._live_callbacks + + for (attr, old, new) in zip(attrs, olds, vals): + if old != new: #XXX: temp removed + notify(attr, old, new, **kw) + + for cb, wref in livecbs(None): + try: cb(self, None, None, None) + except Exception, e: + traceback.print_exc() + try: obs[None].remove(wref) + except ValueError: log.info('notifyall: weak reference (%s) already collected', wref) + + def notify(self, attr = None, old = None, val = None, doNone = True): + 'Subclasses call notify to inform observers of changes to the object.' + obs = self.observers + notify = self.notify + livecbs = self._live_callbacks + + if self._observable_frozen or attr == 'observers': return + + checkList = obs.keys() if attr is None else ( [None, attr] if doNone else [attr] ) + + # Notify regular object observers (both attribute specific and not) + for a in checkList: + for cb, wref in livecbs(a): + try: + cb(self, attr, old, val) + except PyDeadObjectError, _deade: + try: + obs[a].remove(wref) + except ValueError: + log.info('weak reference already collected') + except Exception, e: + print >> sys.stderr, "Error notifying %s" % funcinfo(cb) + traceback.print_exc() + + try: + obs[a].remove(wref) # remove the listener + except ValueError: + # the listener was already removed somehow. + log.info('weak reference already collected') + + # Observable property notification + for obsprop in getattr(self, '_oprops', {}).get(attr, []): + notify(obsprop, old, val) + + #TODO: remove_observer will not complain if there are no callbacks to + # remove. perhaps have an optional "strict" flag argument that asserts that + # the number of observers removed is correct? (assuming of course that + # nothing was garbage collected. + def remove_observer(self, callback, *attrs): + try: + callback = self.guimap.pop(callback) + except (AttributeError, KeyError): + pass + + obs = self.observers + + for a in attrs or [None]: + for wref in obs[a]: + ref = wref() + ref = getattr(ref, '_observer_cb', ref) + if ref == callback: # b.bar == b.bar, however: b.bar is not b.bar + try: + obs[a].remove(wref) + except ValueError: + # this is okay: the weak reference may be invalid by naow + # (but log since this should only happen very occasionally and randomly) + log.info('weak reference already collected') + else: + # unbound_ref leaks in some situations; destroy alleviates it + if hasattr(wref, 'destroy'): + wref.destroy() + + remove_gui_observer = remove_observer + + def _live_callbacks(self, attr): + "Returns all callback objects which haven't been garbage collected." + + try: + obs = self.observers + except AttributeError, err: + raise AttributeError('Did you forget to call an Observable init? ' + err) + + live = [] + retval = [] + for wref in obs[attr]: + obj = wref() + if obj is not None: + live.append(wref) + retval.append((obj, wref)) + + # don't keep references to dead weakrefs + obs[attr][:] = live + + return retval + + @contextmanager + def change_group(self): + self.freeze() + try: + yield self + finally: + self.thaw() + + frozen = change_group + + def freeze(self): + if self._freezecount == 0: + assert self._observable_frozen == False + object.__setattr__(self, '_observable_frozen', True) + self._freezecount += 1 + + assert self._freezecount >= 0 + + def thaw(self): + self._freezecount -= 1 + + if self._freezecount == 0: + assert self._observable_frozen != False + self._observable_frozen = False + + self.notify() + + assert self._freezecount >= 0 + + def thaw_silent(self): + self._freezecount -= 1 + + if self._freezecount == 0: + assert self._observable_frozen != False + self._observable_frozen = False + + assert self._freezecount >= 0 + + @contextmanager + def silent(self): + self.freeze() + try: + yield self + finally: + self.thaw_silent() + + @contextmanager + def flagged(self, flags): + '''flags will be passed as the value of attr in any notify calls + in this context and on this thread.''' + t = currentThread() + self._flags[t] = flags + yield + self._flags.pop(t, None) + + def isflagged(self, flags): + t = currentThread() + curflags = self._flags.get(t, sentinel) + return (curflags is not sentinel and curflags == flags) + + +class Observable(ObservableBase): + ''' + Notifies observers of any attribute changes. + + >>> foo = Observable() + >>> def observer(attr, old, new): print '%s: %s -> %s' % (attr, old, new) + >>> foo.add_observer(observer) + >>> foo.bar = 'kelp' + bar: None -> kelp + ''' + _osentinel = Sentinel() + + def setnotify(self, attr, val): + 'Sets an attribute on this object, and notifies any observers.' + + old = getattr(self, attr, None) + object.__setattr__(self, attr, val) + self.notify(attr, old, val) + + def setnotifyif(self, attr, val): + ''' + If val is different than this object's current value for attr, then + the attribute is set and any observers are notified. + ''' + + if getattr(self, attr, self._osentinel) == attr: + return False + else: + self.setnotify(attr, val) + return True + + def link(self, attrname, callback, callnow = True): + """ + Link an attribute's value to a simple callback, which is called with new + values when the attributes' value changes. + """ + options = {} + if _usingWX and isinstance(getattr(callback, 'im_self', None), EvtHandler): + options['gui_target'] = callback + + self.add_observer(lambda src, attr, old, new: callback(new), attrname, + **options) + + if callnow: + callback(getattr(self, attrname)) + +class notifyprop(object): + def __init__(self, name): + self.name = name + self.privname = '_autoprop_' + name + + def __get__(self, obj, objtype = None): + try: + return getattr(obj, self.privname) + except: + import sys, pprint + print >>sys.stderr, pprint.pformat(vars(obj)) + raise + + def __set__(self, obj, val): + n = self.privname + old = getattr(obj, n, None) + object.__setattr__(obj, n, val) + obj.notify(self.name, old, val) + + + +def _wrap_gui_callback(guimap, func): + 'Uses "AddPendingEvent" to call a function later on the GUI thread.' + + try: + return guimap[func] + except KeyError: + pass + + evtH = func.im_self + + def wrap(*a, **kws): + try: + # getting any attribute on a dead WX object raises PyDeadObjectError + getattr(evtH, '__bogusattributeXYZ123__', None) + except PyDeadObjectError: + # if its dead, remove this callback + guimap.pop(func, None) + else: + # otherwise call it on the GUI thread. + wx.CallAfter(func, *a, **kws) + + wrap.im_self = func.im_self + + guimap[func] = wrap + return wrap + +if __name__ == '__main__': + #import doctest + #doctest.testmod(verbose=True) + + class A(Observable): + state = notifyprop('state') + + a = A() + + def foo(self, *a): print a + a >> "state" >> foo + + a.state = 'online' diff --git a/digsby/src/util/observe/observabledict.py b/digsby/src/util/observe/observabledict.py new file mode 100644 index 0000000..0bb784c --- /dev/null +++ b/digsby/src/util/observe/observabledict.py @@ -0,0 +1,242 @@ +from .observable import ObservableBase +from weakref import WeakValueDictionary +from collections import defaultdict +from traceback import print_exc +from util.primitives.refs import better_ref +from peak.util.imports import whenImported + + +# with wx present, there will be extra event functionality +# CAS: it never would work properly w/o wx, but now it might not work properly +# if wx isn't used somewhere else first. +_usingWX = False +def _setup_usingWX(_mod): + try: + import wx + globals()['wx'] = wx + from wx import EvtHandler #@UnusedImport + globals()['EvtHandler'] = EvtHandler + from wx import PyDeadObjectError + globals()['PyDeadObjectError'] = PyDeadObjectError + except ImportError: + pass + else: + globals()['_usingWX'] = True + +class PyDeadObjectError(object): + #must be before whenImported('wx', _setup_usingWX) + pass +whenImported('wx', _setup_usingWX) + +class Linked(object): + __slots__ = ('dict', 'thread', 'attr', 'ref') + def __init__(self, dict, thread, attr, ref): + self.dict = dict + self.thread = thread + self.attr = attr + self.ref = ref + + def unlink(self): + links = self.dict._links[self.thread] + if self.attr in links: + try: + links[self.attr].remove(self.ref) + except ValueError: + pass + +class DictWatcher(object): + def __init__(self, srcdict, child_callback, *attrs): + srcdict.add_observer(self.on_dict_changed) + self.srcdict = srcdict + self.dictcopy = srcdict.copy() + self.child_args = [child_callback] + list(attrs) + + # Initial observations. + for key, child in srcdict.items(): + child.add_observer(*self.child_args) + + def unbind_children(self): + # Initial observations. + for key, child in self.srcdict.items(): + child.remove_observer(*self.child_args) + + self.srcdict.clear() + del self.srcdict + + def on_dict_changed(self, src, attr, old, new): + if src != getattr(self, 'srcdict', None): + raise PyDeadObjectError + + new = set(src.keys()) + old = set(self.dictcopy.keys()) + + for newkey in (new - old): + src[newkey].add_observer(*self.child_args) + + for oldkey in (old - new): + self.dictcopy[oldkey].remove_observer(*self.child_args) + + self.dictcopy = src.copy() + + +class ObservableDict(dict, ObservableBase): + 'Observable dictionaries inform their observers when their values change.' + + _dict_observers = WeakValueDictionary() + + def __init__(self, *args): + ObservableBase.__init__(self) + dict.__init__(self, *args) + + def __setitem__(self, key, val): + """ + Notifies upon setting a dictionary item. + + >>> def observer(k, old, new): print "%s: %s -> %s" % (k, old, new) + >>> ages = observable_dict({'john': 35, 'joe': 24, 'jenny': 63}) + >>> ages.add_observer(observer) + >>> ages['joe'] += 1 + joe: 24 -> 25 + """ + old = self.get(key, None) + retval = dict.__setitem__(self, key, val) + self.notify(key, old, val) + return retval + + def secret_set(self, key, val): + return dict.__setitem__(self, key, val) + + def remove_dict_observer(self, dict_changed, child_changed): + watcher = self._dict_observers.pop((dict_changed, child_changed), None) + if watcher: + watcher.unbind_children() + del watcher + + def link(self, key, callback, callnow = True, obj = None, thread = 'gui'): + """ + Link a key's value to a simple callback, which is called with new + values when the key changes. + """ + assert callnow is True or callnow is False + + try: + links = self._links + except AttributeError: + links = self._setup_links() + + try: + thread_links = links[thread] + except KeyError: + thread_links = links[thread] = defaultdict(list) + + if obj is None: + obj = callback.im_self + + def on_weakref_dead(wref): + live = [] + for r in thread_links[key]: + if getattr(r, 'object', None) is wref: + r.destroy() + else: + live.append(r) + thread_links[key][:] = live + + ref = better_ref(callback, cb=on_weakref_dead, obj = obj) + thread_links[key].append(ref) + + if callnow: + callback(self[key]) + + return Linked(self, thread, key, ref) + + def _setup_links(self): + links = {} + object.__setattr__(self, '_links', links) + assert self._links is links + self.add_observer(self._link_watcher) + + return links + + def _link_watcher(self, src, attr, old, new): + for thread_name, callbacks in self._links.items(): + if attr in callbacks: + def later(cbs=callbacks[attr]): + for cb_ref in list(cbs): + cb = cb_ref() + + if cb is None: + try: + cbs.remove(cb) + except ValueError: + pass + else: + try: + cb(new) + except Exception: + print_exc() + + if thread_name == 'gui': + wx.CallAfter(later) + else: + assert False + + + def clear(self): + """ + Observers are given a list of all the dictionaries keys, all of it's + old values, and a list of Nones with length equal to it's previous + keys. + + >>> def observer(k, old, new): print "%s: %s -> %s" % (k, old, new) + >>> ages = observable_dict({'john': 35, 'joe': 24, 'jenny': 63}) + >>> ages.add_observer(observer) + >>> ages.clear() + ['john', 'joe', 'jenny']: [35, 24, 63] -> [None, None, None] + """ + keys = self.keys() + old_vals = [self[k] for k in keys] + dict.clear(self) + self.notify() + + def update(self, mapping): + keys = mapping.keys() + old_vals = [self.get(k, None) for k in keys] + new_vals = [mapping.get(k) for k in keys] + dict.update(self, mapping) + self.notifyall(keys, old_vals, new_vals) + + def __delitem__(self, key): + old_val = self[key] + retval = dict.__delitem__(self, key) + self.notify(key, old_val, None) + return retval + + def setdefault(self, k, x=None): + "a[k] if k in a, else x (also setting it)" + if k not in self: + retval = dict.setdefault(self, k, x) + self.notify(k, None, x) + return retval + else: + return self[k] + + def pop(self, k, x=None): + "a[k] if k in a, else x (and remove k)" + if k in self: + val = self[k] + dict.pop(self, k, x) + self.notify(k, val, None) + return val + else: + return x + + def popitem(self): + "remove and return an arbitrary (key, value) pair" + k, v = dict.popitem(self) + self.notify(k, v, None) + return k, v + + + + +observable_dict = ObservableDict diff --git a/digsby/src/util/observe/observablelist.py b/digsby/src/util/observe/observablelist.py new file mode 100644 index 0000000..9607d9e --- /dev/null +++ b/digsby/src/util/observe/observablelist.py @@ -0,0 +1,172 @@ +from .observable import ObservableBase +from util.introspect import decormethod +import sys +from logging import getLogger; log = getLogger('observablelist') + +try: + sentinel +except NameError: + class Sentinel(object): + def __repr__(self): + return "" % (__file__, id(self)) + sentinel = Sentinel() + +def list_notify(f): + def wrapper1(self, *args, **kw): + old_list = self[:] + val = f(self, *args,**kw) + self.notify('list', old_list, self) + return val + return wrapper1 + +class ObservableList(list, ObservableBase): + "A list you can register observers on." + + def __init__(self, *args): + ObservableBase.__init__(self) + list.__init__(self, *args) + self._observable_frozen = False + self._freezecount = 0 + + @decormethod + def notify_wrap(self, func, *args, **kw): + """ + Methods decorated with notify_wrap make a copy of the list before the + operation, then notify observers of the change after. The list itself, + the old list, and the new list are sent as arguments. + """ + val = func(self, *args,**kw) + if not self._observable_frozen: + self.notify('list', None, self) + return val + + def add_list_observer(self, list_changed, child_changed, *attrs, **kwargs): + ch = kwargs.get('childfunc', lambda child: child) + + + # Sanity check -- this list must be holding Observable children + if getattr(sys, 'DEV', False): + for child in [ch(c) for c in self]: + if not hasattr(child, 'add_observer'): + raise TypeError("This list has non observable children") + + class ListWatcher(object): + def __init__(self, srclist, list_callback, child_callback, *attrs, **kwargs): + self.srclist = srclist + self.listcopy = set(srclist) + srclist.add_observer(self.on_list_changed, **kwargs) + + self.list_callback = list_callback + self.child_args = [child_callback] + list(attrs) + self.child_kwargs = dict(kwargs) + + # Initial observations. + for child in [ch(c) for c in srclist]: + child.add_observer(*self.child_args, **self.child_kwargs) + + def on_list_changed(self, src, attr, old, new): +# if not hasattr(src, 'srclist'): +# print >> sys.stderr, 'WARNING: observablelist.on_list_change not unlinked' +# return + + assert src is self.srclist + + new = set(src) + old = self.listcopy + + chargs = self.child_args + kwargs = self.child_kwargs + for newchild in (new - old): + ch(newchild).add_observer(*chargs, **kwargs) + + for oldchild in (old - new): + ch(oldchild).remove_observer(*chargs, **kwargs) + + self.listcopy = new + + def disconnect(self): + chargs, kwargs = self.child_args, self.child_kwargs + for child in self.srclist: + child.remove_observer(*chargs) + + self.srclist.remove_observer(self.on_list_changed) + self.srclist.remove_observer(self.list_callback) + self.__dict__.clear() + + self.disconnect = self.disconnect_warning + + def disconnect_warning(self): + log.critical('calling ListWatcher.disconnect more than once') + + # Inform of list structure changes + if list_changed is not None: + self.add_observer(list_changed, **kwargs) + + # Inform of list children changes + # call .disconnect on this object when finished. + return ListWatcher(self, list_changed, child_changed, *attrs, **kwargs) + + def remove_list_observer(self, list_changed, child_changed, *attrs): + if list_changed is not None: + self.remove_observer(list_changed) + + #TODO: uncruft this with some metaclass magic + + @notify_wrap + def __setitem__(self, i, v): return list.__setitem__(self, i, v) + @notify_wrap + def __delitem__ (self,key): return list.__delitem__(self, key) + @notify_wrap + def __setslice__ (self, i, j, seq): return list.__setslice__(self, i, j, seq) + @notify_wrap + def __delslice__(self, i, j): return list.__delslice__(self, i, j) + @notify_wrap + def append(self, elem): return list.append(self, elem) + + @notify_wrap + def pop(self, i = sentinel): + if i is not sentinel: + return list.pop(self, i) + else: + return list.pop(self) + + @notify_wrap + def extend(self, seq): return list.extend(self, seq) + @notify_wrap + def insert(self, i, elem): return list.insert(self, i, elem) + @notify_wrap + def remove(self, elem): return list.remove(self, elem) + @notify_wrap + def reverse(self): return list.reverse(self) + @notify_wrap + def sort(self, *a, **k): return list.sort(self, *a, **k) + @notify_wrap + def __iadd__(self, item): return list.__iadd__(self, item) + +observable_list = ObservableList + +if __name__ == '__main__': + from util.observe.observable import Observable + class Buddy(Observable): + def __init__(self, name, status = 'online'): + Observable.__init__(self) + self.name = name; self.status = status + + def __repr__(self): return '' % (self.name, self.status) + + buddies = ObservableList([Buddy(n) for n in 'Bobby Barbera Betty Ben'.split()]) + + def buddy_list_changed(src, attr, old, new): + print 'The buddylist changed. New list is %r' % new + + def buddy_attr_changed(src, attr, old, new): + print 'Buddy %r changed %s from %s to %s' % (src, attr, old, new) + + buddies.add_list_observer(buddy_list_changed, buddy_attr_changed) + + print 'Original list', buddies + b = buddies[2] + b.setnotify('status', 'away') + del buddies[2] + + b.setnotify('status', 'online') # should not notify diff --git a/digsby/src/util/observe/observableproperty.py b/digsby/src/util/observe/observableproperty.py new file mode 100644 index 0000000..45f7026 --- /dev/null +++ b/digsby/src/util/observe/observableproperty.py @@ -0,0 +1,128 @@ +''' +Properties that notify observers when one of the attributes they depend on +changes. + +todo: + should ObservableProperty just catch attribute accesss? or is explicit better + + +thanks U{http://users.rcn.com/python/download/Descriptor.htm} +''' +from pprint import pprint +from collections import defaultdict + +try: + sentinel +except NameError: + class Sentinel(object): + def __repr__(self): + return "" % (__file__, id(self)) + sentinel = Sentinel() + +class ObservableMeta(type): + 'Hooks properties up to dependents.' + + def __new__(mcl, name, bases, cdict): + # Collect all all observable properties + obsprops = [(k,v) for k,v in cdict.iteritems() + if isinstance(v, ObservableProperty)] + + props = cdict['_oprops'] = defaultdict(list) + + for propname, prop in obsprops: + for hidden_attr in prop.observe: + props[hidden_attr].append(propname) + + return super(ObservableMeta, mcl).__new__(mcl, name, bases, cdict) + +class ObservableProperty(object): + def __init__(self, fget = None, fset = None, fdel = None, doc = None, + observe = sentinel): + if not all(callable(c) for c in filter(None, [fget, fset, fdel])): + raise AssertionError('%s %s %s' % (repr(fget), repr(fset), repr(fdel))) + self.fget, self.fset, self.fdel = fget, fset, fdel + self.__doc__ = doc + + if observe is sentinel: + raise ValueError("'observe' keyword argument is required") + + if isinstance(observe, basestring): + observe = tuple([observe]) + self.observe = observe + + def __get__(self, obj, objtype): + return self.fget(obj) + + def __set__(self, obj, value): + self.fset(obj, value) + + def __delete__(self, obj): + self.fdel(obj) + + def __repr__(self): + return '' % self.observe + +def main(): + from util.observe import Observable + + class foo(Observable): + __metaclass__ = ObservableMeta + + def __init__(self): + Observable.__init__(self) + self._hidden = 'Hello world' + + + def _wikized(self): + return self._hidden.title().replace(' ','') + + def transmutate(self): + self.setnotify('_hidden', 'Observable properties sure are fun') + + wikiword = ObservableProperty(_wikized, observe = ('_hidden')) + + f = foo() + assert hasattr(f, '_oprops') + + def observer(instance, attr, old, new): + print '%s: %s -> %s' % (attr, old, new) + + f.add_observer(observer, 'wikiword') + f.transmutate() + f.setnotify('_hidden','another test') + +if __name__ == '__main__': + main() + + +''' +the pure python equivalent of 'property' + + +class Property(object): + "Emulate PyProperty_Type() in Objects/descrobject.c" + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + if obj is None: + return self + if self.fget is None: + raise AttributeError, "unreadable attribute" + return self.fget(obj) + + def __set__(self, obj, value): + if self.fset is None: + raise AttributeError, "can't set attribute" + self.fset(obj, value) + + def __delete__(self, obj): + if self.fdel is None: + raise AttributeError, "can't delete attribute" + self.fdel(obj) +''' + diff --git a/digsby/src/util/packable.py b/digsby/src/util/packable.py new file mode 100644 index 0000000..69e8687 --- /dev/null +++ b/digsby/src/util/packable.py @@ -0,0 +1,159 @@ +''' +Usage: + +class Packet(Packable): + fmt = ( + 'version', 'H', + 'length', 'I', + 'flags', 'B', + ) + +p = Packet(3,10,12) +assert Packet.unpack(p.pack()) == p + +''' + +from primitives import to_hex +from struct import Struct +from itertools import izip +from logging import getLogger; log = getLogger('packable') + +__all__ = ['Packable'] + +def debugline(fc): + return ' File "%s", line %d, in %s' % \ + (fc.co_filename, fc.co_firstlineno, fc.co_name) + +class PackableMeta(type): + # + # For an example, see the __main__ method of this file. + # + + def __new__(meta, classname, bases, newattrs): + cls = super(PackableMeta, meta).__new__(meta, classname, + bases, newattrs) + + # If there is no format description, don't do any class magic. + if not 'fmt' in newattrs: + return cls + + byteorder = newattrs.pop('byteorder', '!') + fmt = newattrs.pop('fmt') + __slots__ = list(fmt[::2]) + _struct = Struct(byteorder + ''.join(fmt[1::2])) + _invars = newattrs.pop('invars', []) + if not isinstance(_invars, (list, tuple)): _invars = [_invars] + + fmts = ''.join('\t%s\t\t%s\n' % (i,j) + for i,j in izip(fmt[::2], fmt[1::2])) + + cls.__doc__ = \ +'''Constructs a %s, taking sequential arguments in the order they were +specified by the format description (or named keyword arguments!):\n\n%s''' % (classname, fmts) + + def checkinvars(cls, o): + for invar in cls._invars: + if not invar(o): + fc = invar.func_code + raise AssertionError('Invariant failed after unpacking:' + '\n%s' % debugline(fc)) + @classmethod + def unpack(cls, data): + sz = cls._struct.size + o = cls(*cls._struct.unpack(data[:sz])) + + try: + checkinvars(cls, o) + except AssertionError: + log.error('wrong data: %s', to_hex(data)) + raise + return o, data[sz:] + + def pack(self): + checkinvars(self.__class__, self) + attrs = [getattr(self, field) for field in self.__slots__] + return self._struct.pack(*attrs) + + def __eq__(self, other): + for attr in self.__slots__: + if getattr(other, attr, sentinel) != getattr(self, attr): + return False + return True + + __len__ = lambda self: self.size + __iter__ = lambda self: ((s, getattr(self, s)) for s in self.__slots__) + __str__ = pack + copy = lambda self: cls.unpack(self.pack())[0] + size = _struct.size + + localdict = locals() + classattrs = '''__slots__ _struct pack unpack __len__ + _invars __iter__ __str__ __eq__ copy size'''.split() + + for a in classattrs: setattr(cls, a, localdict[a]) + return cls + +class Packable(object): + __metaclass__ = PackableMeta + + def __init__(self, *a, **kw): + i = -1 + for i, d in enumerate(a): setattr(self, self.__slots__[i], d) + for f in self.__slots__[i+1:]: setattr(self, f, 0) + for k in kw: setattr(self, k, kw[k]) + + def __repr__(self): + return '<%s %s>' % (type(self).__name__, ' '.join('%s=%r' % i for i in self)) + +from math import log as _log, floor, ceil + +def num_bits(i): + return floor(_log(i,2))+1 + +def num_bytes(i): + if i in (0,1): return 1 + else: return int(ceil(pow(2, num_bits(num_bits(i)-1))/8)) + +def make_packable(info): + names, values = zip(*sorted(info, key=lambda x: type(x[1]))) + + type_size = { + str: lambda s: len(s), + unicode: lambda u: 2*len(u), + int: lambda i: pow(2, num_bits(num_bits(i)-1))/8, + bool: lambda b: 0, + } + + get_size = lambda x: type_size[type(x)](x) + sizes = [get_size(v) for v in values] + ints = bools_to_ints(filter(lambda x: type(x) is bool, values)) + size_in_bytes = sum(sizes)+len(ints)*4 + + +def bools_to_ints(bools): + nums = [] + num_ints = ceil(len(bools)/32.0) + for i in range(num_ints): + num = 0 + for i in range(len(bools[i*32:(i+1)*32])): + num |= bools[i] * pow(2, i%32) + nums.append(num) + return nums + +def main(): + + class Meep(Packable): + fmt = ('version', '4s', + 'length', 'H', + 'name','3s') + + invars = [lambda o: o.version == 'OFT2'] + + m = Meep('OFT2', 2, 'abc') + print repr(str(Meep.unpack(m.pack())[0])) + print m.__class__.__name__ + print m.__doc__ + print repr(m) + +if __name__ == '__main__': + main() diff --git a/digsby/src/util/perfmon.py b/digsby/src/util/perfmon.py new file mode 100644 index 0000000..8087e58 --- /dev/null +++ b/digsby/src/util/perfmon.py @@ -0,0 +1,332 @@ +''' + +monitor CPU usage + +''' + +from __future__ import division + +import os, sys +from threading import Thread +from ctypes import WinError, byref, c_ulonglong +from time import clock, sleep +from traceback import print_exc +from cStringIO import StringIO + +from datetime import datetime +from common.commandline import where + +from util.threads.bgthread import BackgroundThread + +from logging import getLogger; log = getLogger('perfmon') + +PROCESS_QUERY_INFORMATION = 0x400 +THREAD_QUERY_INFORMATION = 0x0040 + +from ctypes import windll +kernel32 = windll.kernel32 +GetProcessTimes = kernel32.GetProcessTimes +GetThreadTimes = kernel32.GetThreadTimes +OpenProcess = kernel32.OpenProcess +CloseHandle = kernel32.CloseHandle +OpenThread = kernel32.OpenThread + +# number of consecutive high cpu ticks before prompting the user to send information +if getattr(sys, 'DEV', False): + NUM_CONSECUTIVE_HIGHS = 5 +else: + NUM_CONSECUTIVE_HIGHS = 10 + +TICK_FREQUENCY = 5 # number of seconds: interval to measure CPU usage at +PROFILE_TICKS = 5 # number of ticks to profile for before sending diagnostic information +CPU_THRESHOLD = .95 # CPU usage above which is considered "high" + +TICKS_PER_SEC = 1e7 # GetProcessTimes returns 100 nanosecond units + # (see http://msdn2.microsoft.com/en-us/library/ms683223(VS.85).aspx) + +from util.introspect import all_profilers + +def enable_profilers(): + profilers = all_profilers().values() + for profiler in profilers: profiler.enable() + _log_enabled_profilers(profilers) + +def disable_profilers(): + profilers = all_profilers().values() + for profiler in profilers: profiler.disable() + _log_enabled_profilers(profilers) + +def _log_enabled_profilers(profilers): + log.info('%d/%d profilers enabled' % (len([p for p in profilers if p.enabled]), len(profilers))) + +def profilers_enabled(): + return all(p.enabled for p in all_profilers().itervalues()) + +def get_stack_info(): + 'Returns a string showing the current stack for each running thread.' + io = StringIO() + where(duplicates = True, stream = io) + stack_info = io.getvalue() + + return '\n\n'.join([datetime.now().isoformat(), stack_info]) + + +class CPUWatch(object): + # todo: modelling a state machine with dynamic dispatch is ok, but this could + # be clearer. + + def usage(self, user, kernel): + return getattr(self, self.state + '_usage')(user, kernel) + + def watching_usage(self, user, kernel): + self.user = user + self.kernel = kernel + + if user + kernel >= self.threshold: + self.count += 1 + log.info('cpu usage is high (not profiling yet: %s/%s): %s', self.count, NUM_CONSECUTIVE_HIGHS, (user + kernel)) + + if self.count > NUM_CONSECUTIVE_HIGHS: + import wx + wx.CallAfter(self.prompt_for_profiling) + else: + self.count = 0 + + def profiling_usage(self, user, kernel): + # log.info('profiling CPU usage: %s (user: %s, kernel: %s)', user + kernel, user, kernel) + + self.user = user + self.kernel = kernel + + if user + kernel >= self.threshold: + log.info('cpu usage is high: %s' % (user + kernel)) + self.stack_info.append(get_stack_info()) + self.count += 1 + if self.count > PROFILE_TICKS: + self.disable() + self.send_info() + else: + log.info('cpu usage was low again: %s' % (user + kernel)) + log.info('') + self.count = 0 + self.state = 'watching' + + def disabled_usage(self, user, kernel): + pass + + def send_info(self): + log.info('sending diagnostic information...') + + from util.diagnostic import Diagnostic + import wx + + try: + d = Diagnostic(description = 'CPU usage was too high.') + d.prepare_data() + if d.do_no_thread_post(): + return wx.CallAfter(wx.MessageBox, _('A log of the problem has been sent to digsby.com.\n\nThanks for helping!'), + _('Diagnostic Log')) + except Exception: + print_exc() + + wx.CallAfter(wx.MessageBox, _('There was an error when submitting the diagnostic log.')) + + + def prompt_for_profiling(self): + if self.__in: return + self.__in = True + log.info('prompting for profiling info') + + dev = getattr(sys, 'DEV', False) + + if profilers_enabled(): + self.state = 'profiling' + return log.info('profiler is already enabled') + + line1 = _('Digsby appears to be running slowly.') + line2 = _('Do you want to capture diagnostic information and send it to digsby.com?') + msg = u'%s\n\n%s' % (line1, line2) + + import wx + if dev or wx.YES == wx.MessageBox(msg, _('Digsby CPU Usage'), style = wx.YES_NO | wx.ICON_ERROR): + + log.info('enabling profiler') + enable_profilers() + self.count = 0 + self.state = 'profiling' + else: + self.disable() + + self.__in = False + + def disable(self): + self.state = 'disabled' + disable_profilers() + self.cpu_monitor.done = True + + def __init__(self, threshold = CPU_THRESHOLD): + if not (0 < threshold <= 1): + raise ValueError('0 < threshold <= 1') + + self.state = 'watching' + + self.threshold = threshold + self.count = 0 + self.ignore = False + self.cpu_monitor = CPUMonitor(self.usage) + self.cpu_monitor.start() + self.__in = False + + self.stack_info = [] + +class CPUMonitor(BackgroundThread): + def __init__(self, usage_cb, update_freq_secs = TICK_FREQUENCY): + BackgroundThread.__init__(self, name = 'CPUMonitor') + self.setDaemon(True) + + self.done = False + self.update_freq_secs = 5 + self.perfinfo = ProcessPerfInfo() + + assert callable(usage_cb) + self.usage_cb = usage_cb + + def run(self): + self.BeforeRun() + while not self.done: + setattr(self, 'loopcount', getattr(self, 'loopcount', 0) + 1) + self.usage_cb(*self.perfinfo.update()) + sleep(self.update_freq_secs) + self.AfterRun() + +class PerfInfo(object): + __slots__ = ['last_update', + 'handle', + + 'creationTime', # <-- all c_ulonglongs corresponding to FILETIME structs + 'exitTime', + 'kernelTime', + 'userTime', + 'oldKernel', + 'oldUser'] + + def __init__(self): + self.last_update = clock() + + for name in ('creationTime', 'exitTime', 'kernelTime', 'userTime', 'oldKernel', 'oldUser'): + setattr(self, name, c_ulonglong()) + + self.handle = self.get_handle() + self.update() + + def get_handle(self): + raise NotImplementedError + + def update(self): + if not self.times_func(self.handle, + byref(self.creationTime), + byref(self.exitTime), + byref(self.kernelTime), + byref(self.userTime)): + raise WinError() + + now = clock() + diff = now - self.last_update + + userPercent = (self.userTime.value - self.oldUser.value) / TICKS_PER_SEC / diff + kernelPercent = (self.kernelTime.value - self.oldKernel.value) / TICKS_PER_SEC / diff + + self.last_update = now + self.oldUser.value = self.userTime.value + self.oldKernel.value = self.kernelTime.value + + return userPercent, kernelPercent + + def __del__(self): + CloseHandle(self.handle) + + +class ProcessPerfInfo(PerfInfo): + "For measuring a process's CPU time." + + __slots__ = [] + + def __init__(self): + PerfInfo.__init__(self) + + def get_handle(self): + return obtain_process_handle() + + @property + def times_func(self): + return GetProcessTimes + +class ThreadPerfInfo(PerfInfo): + __slots__ = ['thread_id'] + + def __init__(self, thread_id): + self.thread_id = thread_id + PerfInfo.__init__(self) + + def get_handle(self): + return obtain_thread_handle(self.thread_id) + + @property + def times_func(self): + return GetThreadTimes + + +def num_processors(): + # TODO: is there a more reliable way to get this? + return os.environ.get('NUMBER_OF_PROCESSORS', 1) + +def obtain_process_handle(pid = None): + ''' + Gets a process handle for a process ID. + If pid is not given, uses this process's ID. + + Don't forget to CloseHandle it! + ''' + handle = OpenProcess(PROCESS_QUERY_INFORMATION, False, os.getpid() if pid is None else pid) + + if not handle: + raise WinError() + + return handle + +def obtain_thread_handle(thread_id): + 'Thread ID -> Thread Handle.' + + handle = OpenThread(THREAD_QUERY_INFORMATION, False, thread_id) + + if not handle: + raise WinError() + + return handle + +def main(): + import wx + a = wx.PySimpleApp() + f = wx.Frame(None) + b = wx.Button(f, -1, 'info') + + def foo(): + while True: + pass + + t = Thread(target = foo) + + cpumonitor = CPUMonitor() + cpumonitor.start() + + def onbutton(e): + t.start() + + b.Bind(wx.EVT_BUTTON, onbutton) + + f.Show() + a.MainLoop() + + +if __name__ == '__main__': + main() diff --git a/digsby/src/util/plistutil.py b/digsby/src/util/plistutil.py new file mode 100644 index 0000000..25a38ec --- /dev/null +++ b/digsby/src/util/plistutil.py @@ -0,0 +1,52 @@ +''' +Some tools to convert a .plist into python objects. Incomplete. + +TODO: add load(s)/dump(s) functions to match the 'data shelving' interfaces of pickle, simplejson, pyyaml, etc. +''' + +#_type_map = dict(real = (float, 'cdata'), +# integer = (int, 'cdata'), +# true = (lambda x: True, 'cdata'), +# false = (lambda x: False, 'cdata'), +# data = (_from_data, 'cdata'), +# array = (_to_plist, 'children'), +# dict = +# ) + + +def plisttype_to_pytype(plist): + type = plist._name + transformer = globals().get('plist_to_%s' % type, None) + if transformer is not None: + return transformer(plist) + else: + return plist + +def _from_data(cdata): + return cdata.decode('base64') + +def _to_data(data): + return data.encode('base64') + +def plist_to_real(plist): + return float(plist._cdata) + +def plist_to_string(plist): + return unicode(plist._cdata) + +def plist_to_array(plist): + return map(plisttype_to_pytype, plist._children) + +def plist_to_dict(plist): + result = {} + key = None + value = None + for child in plist._children: + if child._name == 'key': + key = child._cdata + else: + value = plisttype_to_pytype(child) + result[key] = value + key = value = None + return result + diff --git a/digsby/src/util/primitives/__init__.py b/digsby/src/util/primitives/__init__.py new file mode 100644 index 0000000..22341db --- /dev/null +++ b/digsby/src/util/primitives/__init__.py @@ -0,0 +1,9 @@ +from error_handling import * +from funcs import * +globals().pop('get', None) +globals().pop('do', None) +globals().pop('Delegate', None) +from functional import * +from mapping import * +from strings import * +from synchronization import * diff --git a/digsby/src/util/primitives/bisect2.py b/digsby/src/util/primitives/bisect2.py new file mode 100644 index 0000000..fc3bc0f --- /dev/null +++ b/digsby/src/util/primitives/bisect2.py @@ -0,0 +1,78 @@ +"""Bisection algorithms.""" + +def insort_right(a, x, cmp, lo=0, hi=None): + """Insert item x in list a, and keep it sorted assuming a is sorted. + + If x is already in a, insert it to the right of the rightmost x. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + + if hi is None: + hi = len(a) + while lo < hi: + mid = (lo+hi)//2 + if cmp(x, a[mid]) < 0: hi = mid + else: lo = mid+1 + a.insert(lo, x) + +insort = insort_right # backward compatibility + +def bisect_right(a, x, cmp, lo=0, hi=None): + """Return the index where to insert item x in list a, assuming a is sorted. + + The return value i is such that all e in a[:i] have e <= x, and all e in + a[i:] have e > x. So if x already appears in the list, a.insert(x) will + insert just after the rightmost x already there. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + + if hi is None: + hi = len(a) + while lo < hi: + mid = (lo+hi)//2 + if cmp(x, a[mid]) < 0: hi = mid + else: lo = mid+1 + return lo + +bisect = bisect_right # backward compatibility + +def insort_left(a, x, cmp, lo=0, hi=None): + """Insert item x in list a, and keep it sorted assuming a is sorted. + + If x is already in a, insert it to the left of the leftmost x. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + + if hi is None: + hi = len(a) + while lo < hi: + mid = (lo+hi)//2 + if cmp(a[mid], x) < 0: lo = mid+1 + else: hi = mid + a.insert(lo, x) + +def bisect_left(a, x, cmp, lo=0, hi=None): + """Return the index where to insert item x in list a, assuming a is sorted. + + The return value i is such that all e in a[:i] have e < x, and all e in + a[i:] have e >= x. So if x already appears in the list, a.insert(x) will + insert just before the leftmost x already there. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + + if hi is None: + hi = len(a) + while lo < hi: + mid = (lo+hi)//2 + if cmp(a[mid], x) < 0: lo = mid+1 + else: hi = mid + return lo + diff --git a/digsby/src/util/primitives/bits.py b/digsby/src/util/primitives/bits.py new file mode 100644 index 0000000..5b1cb28 --- /dev/null +++ b/digsby/src/util/primitives/bits.py @@ -0,0 +1,66 @@ +import mapping +import itertools +import random + +def hex2bin(s): + return ''.join(chr(int(x,16)) for x in s.split()) + +def rand32(): + return random.randint(1, (1 << 32) - 1) + +def getrandbytes(n): + return ''.join(chr(random.getrandbits(8)) for _i in range(n)) + +def rol(i, n, bits=32): + ''' + ROtate Left. + ''' + div, mod = divmod(i << n, (2**bits)-1) + return mod | (div >> bits) + +def ror(i, n, bits=32): + ''' + ROtate Right. + ''' + return ((i % 2**n) << (bits - n)) + (i >> n) + +def bitfields(*names): + ''' + >>> bitfields('zero','one','two','three').three + 8 + ''' + bits = [2**i for i in xrange(len(names))] + return mapping.Storage(itertools.izip(names, bits)) + +class BitFlags(object): + def __init__(self, names): + self._field = bitfields(*names) + self.__dict__.update(dict.fromkeys(names, False)) + + def Pack(self): + return reduce(lambda a, b: a | b, + map(lambda x: getattr(self._field,x)*getattr(self,x), self._field)) + + def Unpack(self, val): + [setattr(self, field, True) for (field, i) in self._field.items() if val&i==i] + +def leftrotate(s, shift, size=32): + max = pow(2, size) + s = (s % max) << shift + wrap, s = divmod(s, max) + return s | wrap + +def utf7_to_int(databuf): + total = i = 0 + more_bytes = True + while more_bytes: + byte = ord(databuf[i]) + more_bytes = bool(byte & 0x80) + total |= (byte & 0x7f) * (1 << (7*i)) + i += 1 + + return total, i + +if __name__ == '__main__': + import doctest + doctest.testmod(verbose=True) diff --git a/digsby/src/util/primitives/error_handling.py b/digsby/src/util/primitives/error_handling.py new file mode 100644 index 0000000..57f86e3 --- /dev/null +++ b/digsby/src/util/primitives/error_handling.py @@ -0,0 +1,116 @@ +from contextlib import contextmanager +import sys +import traceback + +try: + sentinel +except NameError: + sentinel = object() + +def try_this(func, default = sentinel, allow = (NameError,), ignore = ()): + if not callable(func): + raise TypeError('try_this takes a callable as its first argument') + + try: + return func() + except ignore: + assert default is sentinel + except allow: + raise + except Exception: + if default is sentinel: + raise + else: + return default + +def syck_error_message(e, fpath): + msg, line, column = e + + msg = '%s in %s:\n\n' % (msg, fpath) + + from path import path + try: error_line = path(fpath).lines()[line].strip() + except: error_line = '(could not load line)' + + msg += 'line %d, column %d: "%s"' % (line, column, error_line) + return msg + +@contextmanager +def repr_exception(*args, **kwargs): + ''' + Given local variables, prints them out if there's an exception before + re-raising the exception. + ''' + + try: + yield + except: + for item in args: + found = False + for k, v in sys._getframe(2).f_locals.iteritems(): + if v is item: + print >> sys.stderr, k, '-->', repr(item) + found = True + break + + if not found: + print >> sys.stderr, repr(item) + + for k, v in kwargs.iteritems(): + print >> sys.stderr, repr(k), repr(v) + raise + +def with_traceback(func, *args, **kwargs): + ''' + Any exceptions raised during the execution of "func" are are be printed + and execution continues. + ''' + try: + return func(*args, **kwargs) + except Exception: + traceback.print_exc() + +class traceguard(object): + @classmethod + def __enter__(cls): pass + @classmethod + def __exit__(cls, *a): + try: + if filter(None, a): + try: + print >>sys.stderr, "The following exception has been squashed!" + except Exception: + pass + + try: + exc_string = traceback.format_exc(a) + except Exception: + + # if there was an exception using format_exc, see if + # we stored an old one in _old_format_exc + exc_string = '(Could not format exception.)' + + if hasattr(traceback, '_old_format_exc'): + try: + exc_string = traceback._old_format_exc(a) + except Exception: + pass + try: + if isinstance(exc_string, unicode): + try: + exc_string = exc_string.encode('utf-8') + except Exception: + exc_string = "Error encoding this as utf8: %r" % (exc_string,) + print >>sys.stderr, exc_string + except Exception: + try: + print >> sys.stderr, "could not print exception" + except Exception: + pass + except Exception: + #this method cannot be allowed to blow up. + pass + finally: + del a + return True + diff --git a/digsby/src/util/primitives/files.py b/digsby/src/util/primitives/files.py new file mode 100644 index 0000000..3ea3274 --- /dev/null +++ b/digsby/src/util/primitives/files.py @@ -0,0 +1,77 @@ +from __future__ import with_statement +from contextlib import contextmanager +import logging +import os +import time + +log = logging.getLogger('util.primitives.files') + +__all__ = [ + 'filecontents', + 'atomic_write', + 'replace_file', + ] + +def filecontents(filename, mode = 'rb'): + 'Returns the contents of a file.' + with open(filename, mode) as f: return f.read() + +@contextmanager +def atomic_write(filename, mode = 'w'): + ''' + Returns a file object opened with the given mode. After the with statement, + data written to the file object will be written to atomically. + ''' + path, filepart = os.path.split(filename) + tempfile = os.path.join(path, '%s.tmp' % filepart) + + f = open(tempfile, mode) + + # yield the file object to the caller + try: + yield f + except Exception: + f.close() + os.remove(tempfile) + raise + finally: + f.close() + + assert os.path.isfile(tempfile) + + if os.path.isfile(filename): + # If the file already exists, we need to replace it atomically. + replace_file(filename, tempfile) # atomic on win32 + else: + # Otherwise, just rename the temp file to the actual file. + os.rename(tempfile, filename) + +import ctypes +if hasattr(ctypes, 'windll'): + ReplaceFileW = ctypes.windll.kernel32.ReplaceFileW + + def _make_filename_unicode(f): + if isinstance(f, str): + f = f.decode('filesys') + elif not isinstance(f, unicode): + raise TypeError + + return f + + # implement with atomic win32 function ReplaceFile + def replace_file(filename, replacement): + if not ReplaceFileW(_make_filename_unicode(filename), + _make_filename_unicode(replacement), + None, 0, 0, 0): + raise ctypes.WinError() +else: + # implement with os.rename + def replace_file(filename, replacement): + backup = filename + '.backup.' + str(time.time()) + os.rename(filename, backup) + + try: + os.rename(replacement, filename) + except Exception: + os.rename(backup, filename) + raise diff --git a/digsby/src/util/primitives/fmtstr.py b/digsby/src/util/primitives/fmtstr.py new file mode 100644 index 0000000..0afd07d --- /dev/null +++ b/digsby/src/util/primitives/fmtstr.py @@ -0,0 +1,177 @@ +from collections import defaultdict +from util import Storage + +class FormattingException(Exception): pass + +class fmtstr(object): + ''' + Holds formatted text, and converts between different formats. + ''' + + least_lossy = ['rtf', 'singleformat', 'plaintext'] + + @staticmethod + def plaintext(txt): + assert isinstance(txt, basestring) + return fmtstr(plaintext=txt) + + @staticmethod + def singleformat(s, format): + assert isinstance(s, basestring) + return fmtstr(singleformat=dict(text=s, format=format)) + + def __init__(self, **string_values): + assert string_values + + # pop any None values + for k, v in string_values.items(): + if v is None: + string_values.pop(k) + else: + string_values[k] = getattr(self, '_transform_' + k, lambda v: v)(v) + + self.string_values = string_values # {mime-type: value} + + def _transform_singleformat(self, val): + ''' + turn any wxColors in format storages into tuples + ''' + import wx + format = val.get('format') + if format is None: + return + + format_primitive = {} + for k, v in format.items(): + format_primitive[k] = tuple(v) if isinstance(v, wx.Color) else v + + val['format'] = format_primitive + return val + + def __add__(self, text): + rtf = self.string_values.get('rtf') + if rtf is not None: + return fmtstr(rtf=rtf_append(rtf, text)) + + singleformat = self.string_values.get('singleformat') + if singleformat is not None: + return fmtstr.singleformat(singleformat['text'] + text, format=singleformat['format']) + + return fmtstr.plainttext(self.format_as('plaintext') + text) + + @property + def bestFormat(self): + for fmt in self.least_lossy: + if fmt in self.string_values: + return fmt + + return None + + def asDict(self): + serialized_dict = {} + for fmt in self.least_lossy: + try: + val = self.string_values[fmt] + except KeyError: + pass + else: + serialized_dict[fmt] = val + break + + assert serialized_dict + return serialized_dict + + @staticmethod + def fromDict(dict): + return fmtstr(**dict) + + def __repr__(self): + try: + r = self.string_values['plaintext'] + except KeyError: + r = repr(self.string_values)[:40] + + return '' % r + + def format_as(self, type): + try: + return self.string_values[type] + except KeyError: + for encoder in self._encoder_types[type]: + try: + val = encoder(self) + except FormattingException: + val = None + if val is not None: + self.string_values[type] = val # memoize + return val + + raise FormattingException('cannot format as %r' % type) + + _encoder_types = defaultdict(list) + +try: + from cgui import RTFToX +except ImportError: + pass +else: + # register RTF -> types + import cgui + + def register_encoder(output_type, encoder): + fmtstr._encoder_types[output_type].append(encoder) + + def register_rtf_encoder(fmt): + def encoder(fmtstr): + rtf = fmtstr.format_as('rtf') + if rtf is not None: + return RTFToX().Convert(rtf, fmt, 'rtf') + encoder.__name__ = 'rtf_to_%s_encoder' % fmt + + register_encoder(fmt, encoder) + + def register_plaintext_encoder(fmt): + def encoder(fmtstr): + plaintext = fmtstr.format_as('plaintext') + if plaintext is not None: + return RTFToX().Convert(plaintext, fmt, 'plaintext') + encoder.__name__ = 'plaintext_to_%s_encoder' % fmt + + register_encoder(fmt, encoder) + + + def register_singleformat_encoder(fmt): + def encoder(fmtstr): + singleformat = fmtstr.format_as('singleformat') + if singleformat is not None: + from gui.uberwidgets.formattedinput2.fontutil import StorageToStyle + formatstorage = singleformat['format'] + if not isinstance(formatstorage, Storage): + formatstorage = Storage(formatstorage) + textattr = StorageToStyle(formatstorage) + return RTFToX().Convert(singleformat['text'], fmt, 'plaintext', textattr) + encoder.__name__ = 'singleformat_to_%s_encoder' % fmt + + register_encoder(fmt, encoder) + + def singleformat_to_plaintext(fmtstr): + return fmtstr.format_as('singleformat')['text'] + + register_encoder('plaintext', singleformat_to_plaintext) + + for fmt in cgui.RTFToX.EncoderTypes(): + register_rtf_encoder(fmt) + register_singleformat_encoder(fmt) + register_plaintext_encoder(fmt) + + +def rtf_append(rtf, text): + 'Appends plaintext to an RTF string.' + + # This is a hack until RTFToX supports appending text to its formatting tree. + i = rtf.rfind('\\par') + i = rtf.rfind(' ', 0, i) + i = rtf.find('\\',i) + + return ''.join((rtf[:i], text, rtf[i:])) + diff --git a/digsby/src/util/primitives/funcs.py b/digsby/src/util/primitives/funcs.py new file mode 100644 index 0000000..8997a7e --- /dev/null +++ b/digsby/src/util/primitives/funcs.py @@ -0,0 +1,537 @@ +from __future__ import with_statement +from functional import ObjectList +from refs import stupidref, better_ref +from collections import defaultdict +from error_handling import traceguard +import functools +import operator +import pprint +import sys +import traceback + +try: + sentinel +except NameError: + sentinel = object() + +#import logging +#log = logging.getLogger('util.primitives.funcs') + +def get(obj, key, default=sentinel): + try: + return obj[key] + except (IndexError, KeyError): + if default is sentinel: + raise + else: + return default + except TypeError: + try: + return getattr(obj, key) + except AttributeError: + if default is sentinel: + raise + else: + return default + +def ischeck(f): + def checker(v): + try: + r = f(v) + except Exception: + return False + else: + if type(r) is bool: + return r + else: + return True + return checker + +def itercheck(x, exclude=(basestring,)): + try: + iter(x) + except: + return False + else: + if not isinstance(x, exclude): + return True + else: + return False + +isint = ischeck(int) +isiterable = ischeck(itercheck) +isnumber = ischeck(float) + +def make_first(seq, item): + '''make item the first and only copy of item in seq''' + + seq[:] = [item] + [elem for elem in seq if elem != item] + +def do(seq_or_func, seq=None): + ''' + For use with generator expressions or where map would be used. + More memory efficient than a list comprehension, and faster than map. + + Please note that you won't get your results back (if you want them, you shouldn't + replace your list comprehension or map() with this). + + ex. [sock.close() for sock in self.sockets] becomes do(sock.close() for sock in self.sockets) + map(lambda x: x.close(), self.sockets] becomes do(lambda x: x.close(), self.sockets) + ''' + + if seq is None: + for x in seq_or_func: pass + else: + for x in seq: seq_or_func(x) + +def find(seq, item): + ''' + Like list.index(item) but returns -1 instead of raising ValueError if the + item cannot be found. + ''' + + try: + return seq.index(item) + except ValueError: + return -1 + +def groupby(seq, key = lambda x: x): + ''' + Like itertools.groupby, except all similar items throughout the sequence are grouped, + not just consecutive ones. + ''' + + res = defaultdict(list) + + for item in seq: + res[key(item)].append(item) + + for k in res: + yield k, res[k] + +class Delegate(ObjectList): + VETO = object() + +# NOTE: better_ref didn't work: lambdas and inner functions +# lose their references, and so they are garbage collected. +# The code is being left here for reference, warning, and +# example. + +# def __init__(self, sequence=()): +# list.__init__(map(better_ref,sequence)) + + def __init__(self, iterable = [], ignore_exceptions = None, collect_values=False): + list.__init__(self, iterable) + self.__dict__['collect_values'] = collect_values + object.__setattr__(self, 'ignore_exceptions', ignore_exceptions if ignore_exceptions is not None else tuple()) + + def __iadd__(self, f): + if isinstance(f, list): + for thing in f: + self.__iadd__(thing) + else: + assert callable(f) + self.append(f) + return self + + def __isub__(self, f): + #f = better_ref(f) + if not isiterable(f): f = (f,) + for x in f: self.remove(x) + return self + + def __ipow__(self, d): + assert operator.isMappingType(d) + self(**d) + return self + + def __imul__(self, a): + assert operator.isSequenceType(a) + self(*a) + return self + + def __idiv__(self, tup): + a,k = tup + self(*a,**k) + return self + + def __call__(self, *a, **k): + result = [None] + for call in self: + try: + result.append(call(*a, **k)) + except self.ignore_exceptions: + self.remove(call) + except Exception: + traceback.print_exc() + + if result[-1] is self.VETO: + break + + if not self.collect_values: + # Since the likely case is that we are not collecting values for memory usage reasons, + # we chop off the list to avoid wasting memory. + result = result[-1:] + + if self.collect_values: + # remove the None that we started with + result.pop(0) + else: + # unwrap the result + result = result[-1] + + return result + + def call_and_clear(self, *a, **k): + copy = Delegate(self, self.ignore_exceptions, self.collect_values) + del self[:] + result = copy(*a, **k) + return result + + def __repr__(self): + from util import funcinfo + return '<%s: [%s]>' % (type(self).__name__, ', '.join(funcinfo(f) for f in self)) + + def add_unique(self, cb): + if not cb in self: self.append(cb) + + def remove_maybe(self, cb): + if cb in self: self.remove(cb) + +objset = object.__setattr__ + +class PausableDelegate(Delegate): + def __init__(self): + Delegate.__init__(self) + objset(self, 'paused', False) + + def __call__(self, *a, **k): + if self.paused: + self.paused_calls.append(lambda: Delegate.__call__(self, *a, **k)) + return None + else: + return Delegate.__call__(self, *a, **k) + + def pause(self): + if not self.paused: + objset(self, 'paused', True) + objset(self, 'paused_calls', []) + return True + return False + + def unpause(self): + if self.paused: + objset(self, 'paused', False) + if self.paused_calls: + for call in self.paused_calls: + with traceguard: call() + del self.paused_calls[:] + return True + return False + +class WeakDelegate(object): + def __init__(self): + self.cbs = [] + + def append(self, cb, obj = None): + assert callable(cb) + self.cbs.append(better_ref(cb, obj=obj)) + + def __iadd__(self, cb): + assert callable(cb) + self.cbs.append(better_ref(cb)) + return self + + def __isub__(self, cb): + assert callable(cb) + assert cb is not None + + new_cbs = [] + for cbref in self.cbs: + callback = cbref() + if cb is not callback: + new_cbs.append(cb) + + self.cbs = new_cbs + return self + + def __call__(self, *a, **k): + new_cbs = [] + cbs = self.cbs[:] + self.cbs[:] = [] + + for cbref in cbs: + callback = cbref() + if callback is not None: + new_cbs.append(cbref) + try: + callback(*a, **k) + except Exception: + traceback.print_exc() + + self.cbs[:] = new_cbs + +def autoassign(self, locals): + ''' + Automatically assigns local variables to `self`. + Generally used in `__init__` methods, as in: + + def __init__(self, foo, bar, baz=1): autoassign(self, locals()) + ''' + #locals = sys._getframe(1).f_locals + #self = locals['self'] + for (key, value) in locals.iteritems(): + if key == 'self': + continue + setattr(self, key, value) + +def flatten(seq): + """ + Returns a list of the contents of seq with sublists and tuples "exploded". + The resulting list does not contain any sequences, and all inner sequences + are exploded. For example: + + >>> flatten([7,(6,[5,4],3),2,1]) + [7, 6, 5, 4, 3, 2, 1] + """ + lst = [] + for el in seq: + if isinstance(el, (list, tuple)): + lst.extend(flatten(el)) + else: + lst.append(el) + return lst + +def dictargs(**argmap): + 'Magical dictionary splattage.' + + def decorator(func): + @functools.wraps(func) + def newf(self, response): + try: + args = [response[argmap[argname]] + for argname in + func.func_code.co_varnames[:func.func_code.co_argcount] + if argname != 'self'] + + except KeyError: + print >> sys.stderr, \ + "Error matching argument for", func.__name__ + raise + return func(self, *args) + return newf + return decorator + +def dictargcall(func, argdict, argmapping): + ''' + Calls a function, filling in arguments with the associations provided + in argmapping. + ''' + argdict = argdict.copy() + args, kwargs = [], {} + + # grab some information about the function and its arguments + code = func.func_code + argcount = code.co_argcount + argnames = code.co_varnames[:argcount] + defaults = func.func_defaults or () + _takes_args, takes_kwargs = bool(code.co_flags & 4), bool(code.co_flags & 8) + + # skip over 'self' for bound methods + if argnames and argnames[0] == 'self': + argnames = argnames[1:] + argcount -= 1 + + # first, fill required arguments. + for argname in argnames[:argcount - len(defaults)]: + if argname not in argmapping: + raise ValueError('required argument %s is not in mapping\n' + 'given mapping: %s' % (argname, pprint.pformat(argmapping))) + + real_name = argmapping[argname] + if real_name not in argdict: + raise ValueError('required argument "%s" (%s) is not in argument ' + 'dictionary\ngiven arguments: %s' % (argname, real_name, + pprint.pformat(argdict))) + + args.append(argdict[real_name]) + del argdict[real_name] + + # second, fill in args with default values. + default_index = 0 + for argname in argnames[argcount - len(defaults):]: + if argname not in argmapping or argmapping[argname] not in argdict: + args.append(defaults[default_index]) + else: + args.append(argdict[argmapping[argname]]) + del argdict[argmapping[argname]] + + default_index += 1 + + # if the function takes extra keyword arguments, fill them with the rest of + # the values, using nice names if possible. + if takes_kwargs: + for k,v in argdict.iteritems(): + if k in argmapping: kwargs[argmapping[k]] = v + else: kwargs[str(k)] = v + + if not takes_kwargs: return func(*args) + else: return func(*args, **kwargs) + +def funcToMethod(func,clas,method_name=None): + """ + Adds func to class so it is an accessible method; use method_name to specify the name to be used for calling the method. + The new method is accessible to any instance immediately. + + Thanks U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732} (yet again) + """ + func.im_class=clas + func.im_func=func + func.im_self=None + if not method_name: method_name=func.__name__ + clas.__dict__[method_name]=func + +def attach_method(obj, func, name=None): + "Attaches a function to an object as a method." + + name = name or func.__name__ + cls = obj.__class__ + cls.temp_foo = func + obj.__setattr__(name, cls.temp_foo) + del cls.temp_foo + +class InheritableProperty(object): + ''' + >>> class Foo(object): + ... def getter(self): + ... return getattr(self, '_foo', None) or "Foo" + ... def setter(self, value): + ... self._foo = value + ... foo = InheritableProperty('getter', setter) + >>> class Bar(Foo): + ... def getter(self): + ... return "Bar" + >>> f = Foo() + >>> f.foo + 'Foo' + >>> f.foo = "Baz" + >>> f.foo + 'Baz' + >>> b = Bar() + >>> b.foo + 'Bar' + >>> b.foo = "Baz" + >>> b.foo + 'Bar' + >>> b._foo + 'Baz' + ''' + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + if obj is None: + return self + if callable(self.fget): + return self.fget(obj) + if isinstance(self.fget, basestring): + return getattr(obj, self.fget)() + raise AttributeError("unreadable attribute") + + def __set__(self, obj, value): + if callable(self.fset): + return self.fset(obj, value) + if isinstance(self.fset, basestring): + return getattr(obj, self.fset)(value) + raise AttributeError("can't set attribute") + + def __delete__(self, obj): + if callable(self.fdel): + return self.fdel(obj) + if isinstance(self.fdel, basestring): + return getattr(obj, self.fdel)() + raise AttributeError("can't delete attribute") + +iproperty = InheritableProperty + +def gen_sequence(func): + from util.introspect import cannotcompile + @cannotcompile + @functools.wraps(func) + def wrapper(*args, **kwargs): + gen = func(*args, **kwargs) + val = gen.next(); + try: + gen.send(stupidref(gen)) + except StopIteration: + pass + return val + return wrapper + +def removedupes(seq, key = lambda x: x): + ''' + >>> removedupes(['one','two','two','two','three','three']) + ['one', 'two', 'three'] + ''' + #TODO: list(set(seq)).sort(cmp = lambda a, b: cmp(seq.index(a), seq.index(b))) + # does the same thing. Test it for speed diff + s = set() + uniqueseq = [] + for elem in seq: + if key(elem) not in s: + uniqueseq.append(elem) + s.add(key(elem)) + return uniqueseq + +def lispify(*args): + ''' + returns a function which is the composition of the functions in + args + + use at your own sanity's risk + ''' + assert args + try: iter(args[0]) + except: pass + else: args = args[0] + + return reduce(lambda x,y: lambda *a, **k: x(y(*a,**k)), args) + +class CallCounter(object): + def __init__(self, trigger, func, *args, **kwargs): + assert trigger >= 0 + assert callable(func) + self._count = -1 + self._trigger = trigger + self._func = func + self._args = args + self._kwargs = kwargs + + self() + + def __call__(self): + self._count += 1 + if self._count == self._trigger: + self._func(*self._args, **self._kwargs) + + @property + def func_code(self): + return self._func.func_code + +def readonly(attr): + return property(operator.attrgetter(attr)) + +def takemany(n, iterable): + while n > 0: + yield iterable.next() + n -= 1 + +def boolify(s): + return s.lower() in ('yes', 'true', '1') + +if __name__ == '__main__': + import doctest + doctest.testmod(verbose=True) diff --git a/digsby/src/util/primitives/functional.py b/digsby/src/util/primitives/functional.py new file mode 100644 index 0000000..21e5495 --- /dev/null +++ b/digsby/src/util/primitives/functional.py @@ -0,0 +1,180 @@ + +from cStringIO import StringIO + +class chained_files(object): + ''' + Makes a .read able object from several .readable objects. + + >>> from cStringIO import StringIO + >>> chained = chained_files(StringIO(s) for s in 'one two three'.split()) + >>> print chained.read(8) + onetwth + >>> print chained.read(8) + ree + ''' + + def __init__(self, fileobjs): + # make any non readable strings StringIOs + self._bytecount = 0 + self.fileobjs = [] + + for obj in fileobjs: + if not hasattr(obj, 'read') and isinstance(obj, str): + self.fileobjs.append(StringIO(obj)) + else: + self.fileobjs.append(obj) + + if not hasattr(self.fileobjs[-1], 'read'): + raise TypeError('chained_files makes a .read object out of .read objects only. got a %r', obj) + + if hasattr(obj, 'tell'): + self._bytecount += obj.tell() + else: + print '%s object %r has no attribute tell' % (type(obj), obj) + + self.obj = self.fileobjs.pop(0) + + def read(self, blocksize = -1): + if blocksize < 0: + val = ''.join(obj.read() for obj in [self.obj] + self.fileobjs) + self._bytecount += len(val) + return val + else: + chunk = StringIO() + chunkstr = self.obj.read(blocksize) if self.obj is not None else '' + chunksize = len(chunkstr) + chunk.write(chunkstr) + + diff = blocksize - chunksize + while diff and self.obj is not None: + subchunk = self.obj.read(diff) + if not subchunk: + if self.fileobjs: + self.obj = self.fileobjs.pop(0) + else: + self.read = lambda self: '' + self.obj = None + + + chunk.write(subchunk) + chunksize += len(subchunk) + diff = blocksize - chunksize + + val = chunk.getvalue() + self._bytecount += len(val) + return val + + def tell(self): + return self._bytecount + +class AttrChain(object): + __name = None + __target = None + + def __init__(self, name=None, target=None, *_a, **_k): + self.__name = name + self.__target = target + + def __getattr__(self, attr, sep='.'): + if attr == "_getAttributeNames": + return False + return AttrChain((self.__name + sep if self.__name is not None else '') + attr, self.__get_target()) + + def __truediv__(self, other): + return self.__getattr__(other, sep='/') + + __div__ = __truediv__ + + def __call__(self, *a, **k): + return self.__get_target()(self.__name, *a, **k) + + def __get_target(self): + if self.__target is not None: + return self.__target + try: + return super(AttrChain, self).__call__ + except AttributeError: + if getattr(type(self), '__call__') != AttrChain.__call__: + return self.__call__ + raise AttributeError("%s object has no target, no __call__ and no super class with __call__" % type(self).__name__) + + def __repr__(self): + return '' + + +class ObjectList(list): + + def __init__(self, *a, **k): + self.__dict__['strict'] = k.pop('strict', True) + list.__init__(self, *a, **k) + + def __setattr__(self, attr, val): + for o in self: setattr(o, attr, val) + + def __getattr__(self, attr): + try: + return list.__getattr__(self, attr) + except AttributeError: + try: + return self.__dict__[attr] + except KeyError: + + if self.__dict__.get('strict', True): + default = sentinel + else: + default = lambda *a, **k: None + + res = ObjectList(getattr(x, attr, sentinel) for x in self) + + if self.__dict__.get('strict', True) and sentinel in res: + raise AttributeError("Not all objects in %r have attribute %r" % (self, attr)) + + try: + res = FunctionList(res) + except AssertionError: + pass + + return res + + def __repr__(self): + return '<%s: %r>' % (type(self).__name__, list.__repr__(self)) + +class FunctionList(ObjectList): + def __init__(self, *a, **k): + ObjectList.__init__(self, *a, **k) + if not all(callable(x) for x in self): + raise AssertionError + + def __call__(self, *a, **k): + return [f(*a, **k) for f in self] + +def compose(funcs): + ''' + Returns a function which is the composition of all functions in "funcs." + + Each function must take and return one argument. + ''' + + def composed(res): + for f in funcs: + res = f(res) + return res + + return composed + +def chain(*iterables): + ''' + Replacement for itertools.chain, you get back a normal generator, complete with + next, close, send, and throws methods. + ''' + for it in iterables: + for element in it: + yield element + +def main(): + chained = chained_files(StringIO(s) for s in 'one two three'.split()) + print chained.read(8) + print chained.read(8) + +if __name__ == '__main__': + main() diff --git a/digsby/src/util/primitives/mapping.py b/digsby/src/util/primitives/mapping.py new file mode 100644 index 0000000..6a5f43e --- /dev/null +++ b/digsby/src/util/primitives/mapping.py @@ -0,0 +1,685 @@ +import funcs +import functional +import itertools +from UserDict import DictMixin + +try: + sentinel +except NameError: + class Sentinel(object): + def __repr__(self): + return "" % (__file__, id(self)) + sentinel = Sentinel() + +def fmt_to_dict(delim1, delim2): + class dictifier(dict): + def __init__(self, *a, **k): + if a and isinstance(a[0], basestring): + dict.__init__(self, self.parse(a[0]), *a[1:],**k) + else: + dict.__init__(self, *a, **k) + + @classmethod + def parse(cls, s): + pairs = list(entry.strip().split(delim2,1) for entry in s.split(delim1)) + if len(pairs[-1]) == 1 and pairs[-1][0].strip() == '': + pairs = pairs[:-1] + + return pairs + + def __str__(self): + return delim1.join(delim2.join(i) for i in self.items()) + + return dictifier + +def dictreverse(mapping): + """ + >>> dictreverse({1: 2, 3: 4}) + {2: 1, 4: 3} + """ + return dict((value, key) for (key, value) in mapping.iteritems()) + +def dictadd(dict_a, dict_b): + """ + Returns a dictionary consisting of the keys in `a` and `b`. + If they share a key, the value from b is used. + + >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1}) + {1: 0, 2: 1, 3: 1} + """ + result = dict(dict_a) + result.update(dict_b) + return result + +def dictsub(a, b): + ''' + >>> dictsub({3: 'three', 4: 'four', 5: 'five'}, {4: 'foo'}) + {3: 'three', 5: 'five'} + ''' + a = a.copy() + for key in b: a.pop(key, None) + return a + +def dictdiff(defaults, user): + ''' + >>> dictdiff({3: 'three', 4: 'four', 5: 'five'}, {3: 'three', 4: 'foo', 5: 'five'}) + {4: 'foo'} + ''' + diff = {} + for k, v in user.iteritems(): + if k not in defaults or v != defaults[k]: + diff[k] = v + + return diff + +def intdictdiff(start, end): + ''' + >>> intdictdiff({'a': 3, 'b': 4, 'c': 5}, {'a': 2, 'c': 6, 'd': 2}) + {'a': -1, 'c': 1, 'b': -4, 'd': 2} + ''' + keys = set(start.keys()) | set(end.keys()) + out = {} + for key in keys: + startval = start.get(key, 0) + endval = end.get(key, 0) + outval = endval - startval + if outval: + out[key] = outval + + return out + +class DictChain(functional.AttrChain,dict): + def __init__(self,*args,**kwargs): + dict.__init__(self,*args,**kwargs) + functional.AttrChain.__init__(self,'base',self.__getattr2__) + + def __getattr2__(self, key): + keys = key.split('.')[1:] if isinstance(key,basestring) else key + + next = self[keys[0]] + if len(keys)>1 and isinstance(next,DictChain): + return dict.__getattribute__(next,'__getattr2__')(keys[1:]) + else: + if keys: + try: + returner = next + except: + return self['generic'][keys[0]] + if isinstance(returner,DictChain): + return returner['value'] + else: + return returner + else: + return self['value'] + +class Storage(dict): + """ + A Storage object is like a dictionary except `obj.foo` can be used + instead of `obj['foo']`. Create one by doing `storage({'a':1})`. + + Setting attributes is like putting key-value pairs in too! + + (Thanks web.py) + """ + def __getattr__(self, key, ga = dict.__getattribute__, gi = None): + try: + return ga(self, key) + except AttributeError: + try: + if gi is not None: + return gi(self, key) + else: + return self[key] + except KeyError: + msg = repr(key) + if len(self) <= 20: + keys = sorted(self.keys()) + msg += '\n (%d existing keys: ' % len(keys) + str(keys) + ')' + raise AttributeError, msg + + def __setattr__(self, key, value): + self[key] = value + + def copy(self): + return type(self)(self) + +def dictrecurse(newtype): + def recurser(_d, forbidden = ()): + if not hasattr(_d, 'keys'): + from pprint import pformat + raise TypeError('what is?\n%s' % pformat(_d)) + + for k in _d: + if isinstance(_d[k], dict): + _d[k] = recurser(_d[k]) + elif funcs.isiterable(_d[k]) and not isinstance(_d[k], forbidden + (basestring,)): + if isinstance(_d[k], tuple): + t = tuple + else: + t = list + _d[k] = t((recurser(item) if isinstance(item, dict) else item) + for item in _d[k]) + if isinstance(newtype, type): + return _d if type(_d) is newtype else newtype(_d) + else: + return newtype(_d) + + return recurser + +to_storage = dictrecurse(Storage) + +def from_storage(d): + ''' + >>> s = Storage(wut = "boop", meep = Storage(yip = "pow")) + >>> d = from_storage(s) + >>> assert type(d) is dict and type(d['meep']) is dict + >>> print d['meep']['yip'] + pow + ''' + for k, v in d.items(): + if isinstance(v, Storage): + d[k] = from_storage(v) + elif isinstance(v, list): + newlist = [(from_storage(e) if isinstance(e, Storage) else e) for e in d[k]] + d[k] = newlist + + return d if type(d) is dict else dict(d) + +def lookup_table(*a, **d): + """ + Takes a dictionary, makes it a storage object, and stores all the reverse + associations in it. + """ + + d = dict(*a, **d) + d.update(dictreverse(d)) + return to_storage(d) + +class get_change_dict(dict): + def __init__(self, *a, **k): + self.__dict__['_get_change_dict__get_change'] = k.pop('_get_change', None) + super(get_change_dict, self).__init__(*a, **k) + + def __getitem__(self, key): + if getattr(self, '_get_change_dict__get_change', None) is not None: + key = self.__get_change(key) + return super(get_change_dict, self).__getitem__(key) + + def __contains__(self, key): + if self.__get_change is not None: + key = self.__get_change(key) + return super(get_change_dict, self).__contains__(key) + + def pop(self, key, x=None): + 'a.pop(k[, x]) a[k] if k in a, else x (and remove k)' + key = self.__get_change(key) + return super(get_change_dict, self).pop(key, x) + +class set_change_dict(dict): + def __init__(self, *a, **k): + self.__dict__['_set_change_dict__set_change'] = k.pop('_set_change', None) + super(set_change_dict, self).__init__(*a, **k) + + def __setitem__(self, key, val): + if getattr(self, '_set_change_dict__set_change', None) is not None: + key = self.__set_change(key) + return super(set_change_dict, self).__setitem__(key, val) + + def setdefault(self, key, default): + if self.__key_change is not None: + key = self.__key_change(key) + try: + return super(set_change_dict, self).__getitem__(key.lower()) + except KeyError: + super(set_change_dict, self).__setitem__(key.lower(), default) + return default + +class key_change_dict(set_change_dict, get_change_dict): + def __init__(self, *a, **k): + self.__dict__['_key_change_dict__key_change'] = _key_change = k.pop('_key_change', None) + k['_set_change'] = _key_change + k['_get_change'] = _key_change + super(key_change_dict, self).__init__(*a, **k) + +class lower_case_dict(key_change_dict): + def __init__(self, *a, **k): + self.__dict__['_lower_case_dict__key_change'] = k['_key_change'] = lambda key: key.lower() + super(lower_case_dict, self).__init__(*a, **k) + for key in list(self.keys()): + self[key] = super(lower_case_dict, self).pop(key) + + def __delitem__(self, key): + key = self.__key_change(key) + return super(lower_case_dict, self).__delitem__(key) + +class no_case_dict(set_change_dict, get_change_dict): + ''' + >>> class Foo(no_case_dict, Storage): + ... pass + ... + >>> f = Foo({'Foo':'bar'}) + >>> f.foo + 'bar' + >>> f.fOo + 'bar' + >>> f + {'Foo': 'bar'} + ''' + def __init__(self, *a, **k): + self.__dict__['_no_case_dict__mapping'] = {} + k['_set_change'] = self.__set_change + k['_get_change'] = self.__get_change + super(no_case_dict, self).__init__(*a, **k) + self.__dict__['_no_case_dict__inited'] = False + for key in list(self.keys()): + self[key] = self.pop(key) + self.__dict__['_no_case_dict__inited'] = True + + def __set_change(self, key): + self.__mapping[key.lower()] = key + return key + + def __get_change(self, key): + if self.__inited: + key = self.__mapping[key.lower()] + return key + +def stringify_dict(dict): + 'Turn dict keys into strings for use as keyword args' + new = {} + for k,v in dict.items(): + if isinstance(k, basestring): + new[str(k)] = v + else: + new[k] = v + + return new + +class odict(dict): + """ + an ordered dictionary + """ + def __init__(self, d = None): + if d is None: d = {} + + try: + t = d.items() + self._keys = [k for k, _v in t] + dict.__init__(self, t) + except: + one, two, three = itertools.tee(d, 3) + + try: self._keys = [k for k, _v in one] + except: self._keys = [k for k in two] + dict.__init__(self, d if isinstance(d, dict) else three) + + def __delitem__(self, key): + dict.__delitem__(self, key) + self._keys.remove(key) + + def __setitem__(self, key, item): + dict.__setitem__(self, key, item) + # a peculiar sharp edge from copy.deepcopy + # we'll have our set item called without __init__ + + if not hasattr(self, '_keys'): + self._keys = [key,] + if key not in self._keys: + self._keys.append(key) + + __iter__ = property(lambda self: self._keys.__iter__) + + def clear(self): + dict.clear(self) + self._keys = [] + + def pop(self, k, defval = sentinel): + try: + val = dict.pop(self, k) + except KeyError: + if defval is sentinel: raise + else: return defval + else: + self._keys.remove(k) + return val + + def iteritems(self): + for i in self._keys: + try: + yield i, self[i] + except KeyError: + print 'fake keys', self._keys + print 'real keys', self.keys() + raise + + + def items(self): + return list(self.iteritems()) + + def keys(self): + return self._keys[:] #fast? copy + + def iterkeys(self): #iterator tied so that "changed size" etc... + return iter(self._keys) + + def itervalues(self): + for i in self._keys: + yield self[i] + + def values(self): + return list(self.itervalues()) + + def popitem(self): + if len(self._keys) == 0: + raise KeyError('dictionary is empty') + else: + key = self._keys[-1] + val = self[key] + del self[key] + return key, val + + def setdefault(self, key, failobj = None): + ret = dict.setdefault(self, key, failobj) + if key not in self._keys: + self._keys.append(key) + return ret + + def update(self, d): + try: + for key in d.keys(): + if not self.has_key(key): + self._keys.append(key) + except AttributeError: + # might be an iterable of tuples + for k,v in d: + self[k] = v + + return +# if not self.has_key(k): +# self._keys.append(k) +# tmp[k] = v +# d = tmp + + dict.update(self, d) + + def move(self, key, index): + + """ Move the specified to key to *before* the specified index. """ + + try: + cur = self._keys.index(key) + except ValueError: + raise KeyError(key) + self._keys.insert(index, key) + # this may have shifted the position of cur, if it is after index + if cur >= index: cur = cur + 1 + del self._keys[cur] + + def index(self, key): + if not self.has_key(key): + raise KeyError(key) + return self._keys.index(key) + + def get(self, key, default = None): + return dict.get(self, key, default) + + def sort(self, cmp=None, key=None, reverse=False): + return self._keys.sort(cmp=cmp, key=key, reverse=reverse) + + def sort_values(self, cmp=None, key=None, reverse=False): + if key is None: + key = lambda k: k + value_key = lambda k: key(self.get(k)) + self.sort(cmp=cmp, key=value_key, reverse=reverse) + +class OrderedDict(dict, DictMixin): + ''' + Recipe 576693: Ordered Dictionary for Py2.4 + Drop-in substitute for Py2.7's new collections.OrderedDict. The recipe has big-oh performance that matches regular dictionaries (amortized O(1) insertion/deletion/lookup and O(n) iteration/repr/copy/equality_testing). + http://code.activestate.com/recipes/576693/ + ''' + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + key = reversed(self).next() if last else iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + return len(self)==len(other) and \ + all(p==q for p, q in zip(self.items(), other.items())) + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + +class LazySortedDict(odict): + ''' + dictionary which sorts it's keys when the internal _keys attribute is accessed/set + note: this will not automatically sort in every instance + if you want the dictionary to be sorted immediately, access the _keys + such as the following as a one-liner + dict._keys + ''' + + def _get_keys(self): + try: + self._real_keys = sorted(self._real_keys) + except AttributeError: + self._real_keys = [] + return self._real_keys + + def _set_keys(self, keys): + self._real_keys = sorted(keys) + + def copy(self): + return type(self)(self) + + _keys = property(_get_keys, _set_keys) + +class ostorage(odict, Storage): + ''' + >>> ostorage() + {} + >>> s = ostorage() + >>> s.foo = 'bar' + >>> s.bar = 'foo' + >>> s + {'foo': 'bar', 'bar': 'foo'} + ''' + def __getattr__(self, attr): + if attr == '_keys': + return odict.__getattr__(self, attr) + else: + return Storage.__getattr__(self, attr) + + def __setattr__(self, attr, val): + if attr == '_keys': + return odict.__setattr__(self, attr, val) + else: + return Storage.__setattr__(self, attr, val) + +# def __delattr__(self, attr, val): +# if attr == '_keys': +# return odict.__delattr__(self, attr, val) +# else: +# return Storage.__delattr__(self, attr, val) + +class Ostorage(OrderedDict, Storage): + ''' + >>> Ostorage() + Ostorage() + >>> s = Ostorage() + >>> s.foo = 'bar' + >>> s.bar = 'foo' + >>> s + Ostorage([('foo', 'bar'), ('bar', 'foo')]) + >>> s.foo + 'bar' + ''' + def __getattr__(self, attr): + if attr in ('_OrderedDict__map', '_OrderedDict__end'): + return OrderedDict.__getattr__(self, attr) + else: + return Storage.__getattr__(self, attr) + + def __setattr__(self, attr, val): + if attr in ('_OrderedDict__map', '_OrderedDict__end'): + return OrderedDict.__setattr__(self, attr, val) + else: + return Storage.__setattr__(self, attr, val) + +def groupify(seq, keys = None, whitelist = True, mapclass=odict): + ''' + converts sequential flattened dictionaries into dictionaries + example [(1, 'a'), (2, 'b'), (1, 'c'), (2, 'd')] yields + [{1: 'a', 2: 'b'}, {1: 'c', 2: 'd'}] + ''' + retval = [mapclass()] + idx = 0 + for k, v in seq: + if keys and (whitelist ^ (k in keys)): + continue + if k in retval[idx]: + retval.append(mapclass()) + idx += 1 + retval[idx][k] = v + if not retval[0]: + return [] + return retval + +class FilterDict(dict): + def __init__(self, filterfunc, d=None, **kw): + if d is None: d = {} + dict.__init__(self) + + d.update(kw) + dict.__setattr__(self, 'ff', filterfunc) + + for k, v in d.iteritems(): + self.__setitem__(k,v) + + def __getitem__(self, key): + return dict.__getitem__(self, self.ff(key)) + + def __delitem__(self, key): + return dict.__delitem__(self, self.ff(key)) + + def __contains__(self, key): + return dict.__contains__(self, self.ff(key)) + + def __setitem__(self, key, newval): + return dict.__setitem__(self, self.ff(key), newval) + +class LowerDict(FilterDict): + def __init__(self, *a, **k): + def filterfunc(x): + try: x = x.lower() + except: pass + return x + + FilterDict.__init__(self, filterfunc, *a, **k) + +recurse_lower = dictrecurse(LowerDict) + +class LowerStorage(LowerDict, Storage): + def __init__(self, *a, **k): + Storage.__init__(self) + LowerDict.__init__(self, *a, **k) + +recurse_lower_storage = dictrecurse(LowerStorage) + +def odict_from_dictlist(seq): + od = odict() + for subdict in seq: + key = subdict.keys()[0] + od[key] = subdict[key] + + return od + + +if __name__ == '__main__': + import doctest + doctest.testmod(verbose=True) diff --git a/digsby/src/util/primitives/misc.py b/digsby/src/util/primitives/misc.py new file mode 100644 index 0000000..c81a9d3 --- /dev/null +++ b/digsby/src/util/primitives/misc.py @@ -0,0 +1,64 @@ +import subprocess +import calendar +import datetime +import time + +#import logging +#log = logging.getLogger('util.primitives.misc') + +def clamp(number, min_=None, max_=None): + + if None not in (min_, max_) and (min_ > max_): + max_ = min_ + + if min_ is not None: + number = max(min_, number) + + if max_ is not None: + number = min(max_, number) + + return number + +def backtick(cmd, check_retcode = True): + ''' + Returns the standard output of a spawned subprocess. + + Like `perl`. + ''' + proc = subprocess.Popen(cmd.split(' '), stdout = subprocess.PIPE) + proc.wait() + + if check_retcode and proc.returncode != 0: + raise Exception('subprocess returned nonzero: %s' % cmd) + + return proc.stdout.read() + +class ysha(object): + h0 = 0x67452301 + h1 = 0xEFCDAB89 + h2 = 0x98BADCFE + h3 = 0x10325476 + h4 = 0xC3D2E1F0 + +def fromutc(t): + ''' + Takes a UTC datetime and returns a localtime datetime + ''' + return datetime.datetime(*time.localtime(calendar.timegm(t.timetuple()))[:-2]) + +def toutc(t): + ''' + takes a localtime datetime and returns a UTC datetime + ''' + return datetime.datetime(*time.gmtime(time.mktime(t.timetuple()))[:-2]) + +#see TODO in NonBool; sentinel would be used for the default +#try: +# sentinel +#except NameError: +# sentinel = object() + +class NonBool(object): + def __nonzero__(self): + raise TypeError('NonBool cannot be bool()ed, use ==/is CONSTANT') + #TODO: add a value that can be compared. NonBool(5) == 5 diff --git a/digsby/src/util/primitives/refs.py b/digsby/src/util/primitives/refs.py new file mode 100644 index 0000000..5de8582 --- /dev/null +++ b/digsby/src/util/primitives/refs.py @@ -0,0 +1,94 @@ +from weakref import ref, ref as weakref_ref +import new +import traceback + +def better_ref(method, cb = None, obj = None): + if obj is None: + return bound_ref(method, cb) + else: + return unbound_ref(obj, method, cb) + +class ref_base(object): + def maybe_call(self, *a, **k): + try: + o = self() + if o is not None: + return o(*a, **k) + except Exception: + traceback.print_exc() + +class bound_ref(ref_base): + def __init__(self, method, cb = None): + from util.introspect import funcinfo + assert hasattr(method, 'im_self'), 'no im_self: %s' % funcinfo(method) + + self.object = weakref_ref(method.im_self, cb) + self.func = weakref_ref(method.im_func) + self.cls = weakref_ref(method.im_class) + + def __call__(self): + obj = self.object() + if obj is None: return None + + func = self.func() + if func is None: return None + + cls = self.cls() + if cls is None: return None + + return new.instancemethod(func, obj, cls) + +class unbound_ref(ref_base): + def __init__(self, obj, method, cb = None): + self.object = weakref_ref(obj, cb) + self.func = weakref_ref(method) + + try: + unbound_cbs = obj._unbound_cbs + except AttributeError: + obj._unbound_cbs = unbound_cbs = {} + + unbound_cbs[self] = method + + def __call__(self): + try: + obj, func = self.object(), self.func() + except AttributeError: + pass + else: + if obj is not None and func is not None: + return func + + def destroy(self): + obj = self.object() + if obj is not None: + try: + obj._unbound_cbs.pop(self, None) + except AttributeError: + pass + + del self.object + del self.func + + def __repr__(self): + try: + from util import funcinfo + f_info = funcinfo(self.func()) + except Exception, e: + f_info = '(unknown)' + + try: + o_info = self.object() + except Exception, e: + o_info = '(unknown)' + return '' % (f_info, o_info) + +better_ref_types = (bound_ref, unbound_ref) + +class stupidref(ref): + def __getattr__(self, attr): + return getattr(self(), attr) + +if __name__ == '__main__': + import doctest + doctest.testmod(verbose=True) diff --git a/digsby/src/util/primitives/safeeval.py b/digsby/src/util/primitives/safeeval.py new file mode 100644 index 0000000..e7caf63 --- /dev/null +++ b/digsby/src/util/primitives/safeeval.py @@ -0,0 +1,385 @@ +import sys +import inspect, compiler.ast +import thread, time + +DEBUG = False + +# List of all AST node classes in compiler/ast.py. +all_ast_nodes = frozenset(name for (name, obj) in inspect.getmembers(compiler.ast) + if inspect.isclass(obj) and issubclass(obj, compiler.ast.Node)) + +# List of all builtin functions and types (ignoring exception classes). +import __builtin__ +all_builtins = set(dir(__builtin__)) + +def classname(obj): + return obj.__class__.__name__ + +def is_valid_ast_node(name): + return name in all_ast_nodes + +def is_valid_builtin(name): + return name in all_builtins + +def get_node_lineno(node): + return (node.lineno) and node.lineno or 0 + +# Deny evaluation of code if the AST contain any of the following nodes: +unallowed_ast_nodes = frozenset(( +# 'Add', 'And', + 'AssAttr', 'AssList', 'AssName', 'AssTuple', + 'Assert', 'Assign', 'AugAssign', + 'Backquote', +# 'Bitand', 'Bitor', 'Bitxor', + 'Break', + 'CallFunc', + 'Class', +# 'Compare', +# 'Const', + 'Continue', + 'Decorators', +# 'Dict', 'Discard', 'Div', +# 'Ellipsis', +# 'EmptyNode', + 'Exec', +# 'Expression', 'FloorDiv', + 'For', + 'From', + 'Function', +# 'GenExpr', 'GenExprFor', 'GenExprIf', 'GenExprInner', + 'Getattr', + #'Global', + 'If', + 'Import', +# 'Invert', +# 'Keyword', 'Lambda', 'LeftShift', +# 'List', 'ListComp', 'ListCompFor', 'ListCompIf', 'Mod', +# 'Module', +# 'Mul', 'Name', 'Node', 'Not', 'Or', 'Pass', 'Power', + 'Print', 'Printnl', + 'Raise', + 'Return', +# 'RightShift', 'Slice', 'Sliceobj', +# 'Stmt', 'Sub', 'Subscript', + 'TryExcept', 'TryFinally', +# 'Tuple', 'UnaryAdd', 'UnarySub', + 'While', 'Yield' +)) + +# Deny evaluation of code if it tries to access any of the following builtins: +unallowed_builtins = frozenset(( + '__import__', +# 'abs', 'apply', 'basestring', 'bool', 'buffer', +# 'callable', 'chr', + 'classmethod', +# 'cmp', 'coerce', + 'compile', +# 'complex', + 'delattr', +# 'dict', + 'dir', +# 'divmod', 'enumerate', + 'eval', 'execfile', 'file', +# 'filter', 'float', 'frozenset', + 'getattr', 'globals', 'hasattr', +# 'hash', 'hex', 'id', + 'input', +# 'int', + 'intern', + # 'isinstance', 'issubclass', 'iter', +# 'len', 'list', + 'locals', +# 'long', 'map', 'max', 'min', 'object', 'oct', + 'open', +# 'ord', 'pow', 'property', 'range', + 'raw_input', +# 'reduce', + 'reload', +# 'repr', 'reversed', 'round', 'set', + 'setattr', +# 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', +# 'tuple', 'type', 'unichr', 'unicode', + 'vars', +# 'xrange', 'zip' +)) + +for ast_name in unallowed_ast_nodes: + assert(is_valid_ast_node(ast_name)) + +for name in unallowed_builtins: + assert(is_valid_builtin(name)), name + +def is_unallowed_ast_node(kind): + return kind in unallowed_ast_nodes + +def is_unallowed_builtin(name): + return name in unallowed_builtins + +# In addition to these we deny access to all lowlevel attrs (__xxx__). +unallowed_attr = frozenset(( + 'im_class', 'im_func', 'im_self', + 'func_code', 'func_defaults', 'func_globals', 'func_name', + 'tb_frame', 'tb_next', + 'f_back', 'f_builtins', 'f_code', 'f_exc_traceback', + 'f_exc_type', 'f_exc_value', 'f_globals', 'f_locals')) + +def is_unallowed_attr(name): + return (name.startswith('__') and name.endswith('__')) or (name in unallowed_attr) + +class SafeEvalError(object): + """ + Base class for all which occur while walking the AST. + + Attributes: + errmsg = short decription about the nature of the error + lineno = line offset to where error occured in source code + """ + def __init__(self, errmsg, lineno): + self.errmsg, self.lineno = errmsg, lineno + def __str__(self): + return "line %d : %s" % (self.lineno, self.errmsg) + +class SafeEvalASTNodeError(SafeEvalError): + "Expression/statement in AST evaluates to a restricted AST node type." + pass +class SafeEvalBuiltinError(SafeEvalError): + "Expression/statement in tried to access a restricted builtin." + pass +class SafeEvalAttrError(SafeEvalError): + "Expression/statement in tried to access a restricted attribute." + pass + +class SafeEvalVisitor(object): + """ + Data-driven visitor which walks the AST for some code and makes + sure it doesn't contain any expression/statements which are + declared as restricted in 'unallowed_ast_nodes'. We'll also make + sure that there aren't any attempts to access/lookup restricted + builtin declared in 'unallowed_builtins'. By default we also won't + allow access to lowlevel stuff which can be used to dynamically + access non-local envrioments. + + Interface: + walk(ast) = validate AST and return True if AST is 'safe' + + Attributes: + errors = list of SafeEvalError if walk() returned False + + Implementation: + + The visitor will automatically generate methods for all of the + available AST node types and redirect them to self.ok or self.fail + reflecting the configuration in 'unallowed_ast_nodes'. While + walking the AST we simply forward the validating step to each of + node callbacks which take care of reporting errors. + """ + + def __init__(self): + "Initialize visitor by generating callbacks for all AST node types." + self.errors = [] + for ast_name in all_ast_nodes: + # Don't reset any overridden callbacks. + if getattr(self, 'visit' + ast_name, None): continue + if is_unallowed_ast_node(ast_name): + setattr(self, 'visit' + ast_name, self.fail) + else: + setattr(self, 'visit' + ast_name, self.ok) + + def walk(self, ast): + "Validate each node in AST and return True if AST is 'safe'." + self.visit(ast) + return self.errors == [] + + def visit(self, node, *args): + "Recursively validate node and all of its children." + fn = getattr(self, 'visit' + classname(node)) + if DEBUG: self.trace(node) + fn(node, *args) + for child in node.getChildNodes(): + self.visit(child, *args) + + def visitName(self, node, *args): + "Disallow any attempts to access a restricted builtin/attr." + name = node.getChildren()[0] + lineno = get_node_lineno(node) + if is_unallowed_builtin(name): + self.errors.append(SafeEvalBuiltinError( \ + "access to builtin '%s' is denied" % name, lineno)) + elif is_unallowed_attr(name): + self.errors.append(SafeEvalAttrError( \ + "access to attribute '%s' is denied" % name, lineno)) + + def visitGetattr(self, node, *args): + "Disallow any attempts to access a restricted attribute." + name = node.attrname + lineno = get_node_lineno(node) + if is_unallowed_attr(name): + self.errors.append(SafeEvalAttrError( \ + "access to attribute '%s' is denied" % name, lineno)) + + def ok(self, node, *args): + "Default callback for 'harmless' AST nodes." + pass + + def fail(self, node, *args): + "Default callback for unallowed AST nodes." + lineno = get_node_lineno(node) + self.errors.append(SafeEvalASTNodeError( \ + "execution of '%s' statements is denied" % classname(node), + lineno)) + + def trace(self, node): + "Debugging utility for tracing the validation of AST nodes." + print classname(node) + for attr in dir(node): + if attr[:2] != '__': + print ' ' * 4, "%-15.15s" % attr, getattr(node, attr) + +class SafeEvalException(Exception): + "Base class for all safe-eval related errors." + pass + +class SafeEvalCodeException(SafeEvalException): + """ + Exception class for reporting all errors which occured while + validating AST for source code in safe_eval(). + + Attributes: + code = raw source code which failed to validate + errors = list of SafeEvalError + """ + def __init__(self, code, errors): + self.code, self.errors = code, errors + def __str__(self): + return '\n'.join(str(err) for err in self.errors) + +class SafeEvalContextException(SafeEvalException): + """ + Exception class for reporting unallowed objects found in the dict + intended to be used as the local enviroment in safe_eval(). + + Attributes: + keys = list of keys of the unallowed objects + errors = list of strings describing the nature of the error + for each key in 'keys' + """ + def __init__(self, keys, errors): + self.keys, self.errors = keys, errors + def __str__(self): + return '\n'.join(str(err) for err in self.errors) + +class SafeEvalTimeoutException(SafeEvalException): + """ + Exception class for reporting that code evaluation execeeded + the given timelimit. + + Attributes: + timeout = time limit in seconds + """ + def __init__(self, timeout): + self.timeout = timeout + def __str__(self): + return "Timeout limit execeeded (%s secs) during exec" % self.timeout + +def exec_timed(code, context, timeout_secs): + """ + Dynamically execute 'code' using 'context' as the global enviroment. + SafeEvalTimeoutException is raised if execution does not finish within + the given timelimit. + """ + assert(timeout_secs > 0) + + signal_finished = False + + def alarm(secs): + def wait(secs): + for n in xrange(timeout_secs): + time.sleep(1) + if signal_finished: break + else: + thread.interrupt_main() + thread.start_new_thread(wait, (secs,)) + + try: + alarm(timeout_secs) + exec code in context + signal_finished = True + except KeyboardInterrupt: + raise SafeEvalTimeoutException(timeout_secs) + +def safe_eval(code, context = None, timeout_secs = 5): + """ + Validate source code and make sure it contains no unauthorized + expression/statements as configured via 'unallowed_ast_nodes' and + 'unallowed_builtins'. By default this means that code is not + allowed import modules or access dangerous builtins like 'open' or + 'eval'. If code is considered 'safe' it will be executed via + 'exec' using 'context' as the global environment. More details on + how code is executed can be found in the Python Reference Manual + section 6.14 (ignore the remark on '__builtins__'). The 'context' + enviroment is also validated and is not allowed to contain modules + or builtins. The following exception will be raised on errors: + + if 'context' contains unallowed objects = + SafeEvalContextException + + if code is didn't validate and is considered 'unsafe' = + SafeEvalCodeException + + if code did not execute within the given timelimit = + SafeEvalTimeoutException + """ + if context is None: + context = {} + + ctx_errkeys, ctx_errors = [], [] + for key, obj in context.items(): + if inspect.isbuiltin(obj): + ctx_errkeys.append(key) + ctx_errors.append("key '%s' : unallowed builtin %s" % (key, obj)) + if inspect.ismodule(obj): + ctx_errkeys.append(key) + ctx_errors.append("key '%s' : unallowed module %s" % (key, obj)) + + if ctx_errors: + raise SafeEvalContextException(ctx_errkeys, ctx_errors) + + ast = compiler.parse(code) + checker = SafeEvalVisitor() + + if checker.walk(ast): + exec_timed(code, context, timeout_secs) + else: + raise SafeEvalCodeException(code, checker.errors) + + +def pprintAst(ast, indent=' ', stream=sys.stdout): + "Pretty-print an AST to the given output stream." + rec_node(ast, 0, indent, stream.write) + +def rec_node(node, level, indent, write): + "Recurse through a node, pretty-printing it." + pfx = indent * level + if isinstance(node, compiler.ast.Node): + write(pfx) + write(node.__class__.__name__) + write('(') + + if any(isinstance(child, compiler.ast.Node) for child in node.getChildren()): + for i, child in enumerate(node.getChildren()): + if i != 0: + write(',') + write('\n') + rec_node(child, level+1, indent, write) + write('\n') + write(pfx) + else: + # None of the children as nodes, simply join their repr on a single + # line. + write(', '.join(repr(child) for child in node.getChildren())) + + write(')') + + else: + write(pfx) + write(repr(node)) diff --git a/digsby/src/util/primitives/strings.py b/digsby/src/util/primitives/strings.py new file mode 100644 index 0000000..dccc6c1 --- /dev/null +++ b/digsby/src/util/primitives/strings.py @@ -0,0 +1,731 @@ +import mapping +import HTMLParser +import StringIO +import re +import string +import sys +import traceback + +try: + _ +except NameError: + _ = lambda x: x + +def strftime_u(timeobj, fmt): + ''' + strftime_u(datetime timeobj, str fmt) -> unicode + Returns a unicode string of the date. Uses locale encoding to decode. + ''' + + if isinstance(fmt, unicode): + fmt = fmt.encode('utf-8') + return timeobj.strftime(fmt).decode('locale') + +def strfileobj(writefunc): + s = StringIO() + writefunc(s) + return s.getvalue() + +def strincrement(s): return str(int(s)+1) + +def tuple2hex(t): + return ''.join('%02x' % c for c in t[:3]) + +def format_xhtml(s, f): + 'Turns "format storages" and a string into a with css styles.' + + s = s.replace('\n', '
    ') + + font_attrs = filter(lambda e: bool(e), + ['bold' if f.bold else '', + 'italic' if f.italic else'', + ('%spt' % f.size) if f.size is not None else '', + f.face if f.face is not None else '']) + + style_elems = filter(lambda s: bool(s), [ + ('color: #%s' % tuple2hex(f.foregroundcolor)) if f.foregroundcolor is not None else '', + ('background-color: #%s' % tuple2hex(f.backgroundcolor)) if f.backgroundcolor not in (None, (255, 255, 255, 255)) else '', + 'text-decoration: underline' if f.underline else '', + ('font: ' + ' '.join(font_attrs)) if font_attrs else '']) + + if style_elems: + span_style = '; '.join(style_elems) + return '%s' % (span_style, s) + else: + return s + +fontsizes = ( + lambda x: x < 8, + lambda x: x == 8, + lambda x: x in (9, 10), + lambda x: x in (11, 12, 13), + lambda x: x in (14, 15, 16), + lambda x: x in (17, 18, 19), + lambda x: x > 19, +) + +def Point2HTMLSize(n): + for i, f in enumerate (fontsizes): + if f(n): + return i + 1 + + assert False + +# html stripper...make sure to pay her well ;-) + +class StrippingParser(HTMLParser.HTMLParser): + + # These are the HTML tags that we will leave intact + from htmlentitydefs import entitydefs #@UnusedImport + def __init__(self, valid_tags=()): + HTMLParser.HTMLParser.__init__(self) + self.valid_tags = valid_tags + self.result = u"" + self.endTagList = [] + + def handle_data(self, data): + if data: + self.result = self.result + data + + def handle_starttag(self, tag, attrs): + if tag == 'br': + self.result += '\n' + + def handle_charref(self, name): + self.result += unichr(int(name)) + #self.result += "%s&#%s;" % (self.result, name) + + def handle_entityref(self, name): + self.result += self.entitydefs.get(name,'') + + def unknown_starttag(self, tag, attrs): + """ Delete all tags except for legal ones """ + if tag in self.valid_tags: + self.result = self.result + '<' + tag + for k, v in attrs: + if string.lower(k[0:2]) != 'on' and string.lower(v[0:10]) != 'javascript': + self.result = u'%s %s="%s"' % (self.result, k, v) + endTag = '' % tag + self.endTagList.insert(0,endTag) + self.result = self.result + '>' + + def unknown_endtag(self, tag): + if tag in self.valid_tags: + self.result = "%s" % (self.result, tag) + remTag = '' % tag + self.endTagList.remove(remTag) + + def cleanup(self): + """ Append missing closing tags """ + for j in range(len(self.endTagList)): + self.result = self.result + self.endTagList[j] + +parser = StrippingParser() + +def scrape_clean(text): + ''' + Removes HTML markup from a text string. + + @param text The HTML source. + @return The plain text. If the HTML source contains non-ASCII + entities or character references, this is a Unicode string. + ''' + def fixup(m): + text = m.group(0) + if text[:1] == "<": + return "" # ignore tags + if text[:2] == "&#": + try: + if text[:3] == "&#x": + return unichr(int(text[3:-1], 16)) + else: + return unichr(int(text[2:-1])) + except ValueError: + pass + elif text[:1] == "&": + import htmlentitydefs + entity = htmlentitydefs.entitydefs.get(text[1:-1]) + if entity: + if entity[:2] == "&#": + try: + return unichr(int(entity[2:-1])) + except ValueError: + pass + else: + return unicode(entity, "iso-8859-1") + return text # leave as is + return re.sub("(?s)<[^>]*>|&#?\w+;", fixup, text) + +def strip_html(s, valid_tags=()): + "Strip HTML tags from string s, leaving valid_tags." + parser.valid_tags = valid_tags + parser.feed(s) + parser.close() + parser.cleanup() + result = parser.result + parser.result = "" + return result + +def strip_html2(s): + ''' + Strips out HTML with the BeautifulSoup library. + + >>> strip_html2('Some ugly html.') + u'Some ugly html.' + ''' + if not s: return s + + from util.BeautifulSoup import BeautifulSoup + soup = BeautifulSoup(s) + + text_pieces = [] + for pc in soup.recursiveChildGenerator(): + if isinstance(pc, unicode): + text_pieces.append(pc) + elif pc.name == 'br': + text_pieces.append('\n') + + return ''.join(text_pieces) + +def strip_html_and_tags(s, invalid_tags): + ''' + content between "invalid_tags" is removed + ''' + if not s: return s + + from util.BeautifulSoup import BeautifulSoup + soup = BeautifulSoup(s.replace('
    ','\n').replace('
    ','\n').replace('
    ', '\n')) + for tag in invalid_tags: + for result in soup.findAll(name=tag): + result.replaceWith("") + + return ''.join(e for e in soup.recursiveChildGenerator() + if isinstance(e,unicode)) + +def srv_str_to_tuple(address, default_port = None): + if address.find(':') != -1: + host, port = address.split(':') + port = int(port) + else: + + if default_port is None: + raise ValueError('No port found in %r and no default port supplied.', address) + + host = address + port = default_port + + return (host, port) + +def get_between(s, start_str, end_str): + ''' + Returns the portion of s between start_str and end_str, or None + if + ''' + + start_i = s.find(start_str) + end_i = s.find(end_str, start_i + len(start_str)) + + if start_i > end_i or (-1 in (start_i, end_i)): + return None + else: + return s[start_i+len(start_str):end_i] + +def strlist(s): + 'Strips comments from a multiline string, then splits on whitespace.' + + lines = s.split('\n') + for y, line in enumerate(lines): + i = line.find('#') + if i != -1: lines[y] = line[:i] + return '\n'.join(lines).split() + +# matches anything ${between} to group "expr" +_curlymatcher = re.compile(r'\$\{(?P.*?)\}', re.DOTALL) + +def curly(s, frame = 2, source = None): + def evalrepl(match, source = source): + f = sys._getframe(frame) + + if source is None: + source = {} + elif isinstance(source, dict): + source = source + elif hasattr(source, '__dict__'): + source = source.__dict__ + elif hasattr(source, '__slots__'): + source = dict((x, getattr(source, x)) for x in source.__slots__) + else: + raise AssertionError('not sure what to do with this argument: %r' % source) + + locals = mapping.dictadd(f.f_locals, source) + try: + res = eval(match.group('expr'), f.f_globals, locals) + #except NameError: + except Exception, e: + traceback.print_exc() + #print >> sys.stderr, 'Error parsing string: ' + s + #print >> sys.stderr, 'locals:' + #from pprint import pformat + #try: print >> sys.stderr, pformat(locals) + + return '' + + raise e + return u'%s' % (res,) + + return _curlymatcher.sub(evalrepl, s) + +def cprint(s): + print curly(s, frame = 3) + +def replace_newlines(s, replacement=' / ', newlines=(u"\n", u"\r")): + """ + Used by the status message display on the buddy list to replace newline + characters. + """ + # turn all carraige returns to newlines + for newline in newlines[1:]: + s = s.replace(newline, newlines[0]) + + # while there are pairs of newlines, turn them into one + while s.find(newlines[0] * 2) != -1: + s = s.replace( newlines[0] * 2, newlines[0]) + + # replace newlines with the newline_replacement above + return s.strip().replace(newlines[0], replacement) + +SI_PREFIXES = ('','k','M','G','T','P','E','Z','Y') +IEC_PREFIXES = ('','Ki','Mi','Gi','Ti','Pi','Ei','Zi','Yi') + +def nicebytecount(bytes): + ''' + >>> nicebytecount(1023*1024*1024) + '1,023 MB' + >>> nicebytecount(0) + '0 B' + >>> nicebytecount(12.34*1024*1024) + '12.34 MB' + ''' + bytes = float(bytes) + if not bytes: + count = 0 + else: + import math + count = min(int(math.log(bytes, 1024)), len(SI_PREFIXES)-1) + bytes = bytes / (1024 ** count) + return '{bytes:.4n} {prefix}B'.format(bytes=bytes, prefix=SI_PREFIXES[count]) + +class istr(unicode): + def __new__(self, strng): + return unicode.__new__(self, _(strng)) + def __init__(self, strng): + self.real = strng + def __cmp__(self, other): + if type(self) == type(other): + return cmp(self.real, other.real) + else: + return unicode.__cmp__(self, other) + def __eq__(self, other): + return not bool(self.__cmp__(other)) + +def nicetimecount(seconds, max=2, sep=' '): + + seconds = int(seconds) + + if seconds < 0: + return '-' + nicetimecount(abs(seconds), max=max, sep=sep) + + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + years, days = divmod(days, 365) #@UnusedVariable + + i = 0 + res = [] + for thing in 'years days hours minutes seconds'.split(): + if not vars()[thing]: + continue + else: + res.append('%d%s' % (vars()[thing], thing[0])) + i += 1 + + if i == max: + break + else: + if not res: + res = ['0s'] + + return sep.join(res) + +#pascal strings +def unpack_pstr(s): + from struct import unpack + l = unpack('B', s[0])[0] + if l > (len(s) - 1): + raise ValueError(s) + return s[1:1+l], s[1+l:] + +def pack_pstr(s): + from struct import pack + return pack('B', len(s)) + s + +def preserve_newlines(s): + return preserve_whitespace(s, nbsp=False) + +_whitespace_pattern = re.compile(r'(\s{2,})') + +def _whitespace_repl(match): + return len(match.group(1)) * ' ' + +def preserve_whitespace(s, nbsp=True): + ''' + HTML condenses all spaces, tabs, newlines into a single space when rendered. This function + replaces multiple space/tab runs into   entitities and newlines with
    + + Runs of whitespace are replaced with an equal number of   - this means two spaces get + converted to    but a single space is left alone. + ''' + s = s.replace('\r\n', '\n').replace('\n', '
    ') + + if not nbsp: + return s + + return _whitespace_pattern.sub(_whitespace_repl, s) + +class EncodedString(object): + ''' + Attempt at making a new string type that keeps track of how it's encoded. + Hopefully we'll be able to know that '<3' can't go into the IM window but + 'my text' can. + ''' + def __init__(self, s, encodings=None): + if encodings is None: + encodings = () + + self.encodings = tuple(encodings) + self._data = s + + def encode(self, encoding): + ''' + Encode this string's data with encoding, returning a new EncodedString object. + + >>> x = EncodedString(u'<3').encode('xml') + >>> x, type(x) + (, ) + ''' + return EncodedString(self._data.encode(encoding), self.encodings+(encoding,)) + + def decodeAll(self): + ''' + Decode this string down to its data. Just calls .decode() over and over again. + + >>> x = EncodedString(u'%26lt%3B%26gt%3B%26amp%3B', ['xml', 'url']) + >>> unicode(x.decodeAll()) + u'<>&' + ''' + s = self + while s.encodings: + s = s.decode() + return s + + def decode(self): + ''' + Decode a single encoding. Since this object tracks its codecs, specifying a codec + is not allowed. + + >>> x = EncodedString(u'%26lt%3B%26gt%3B%26amp%3B', ['xml', 'url']) + >>> y = x.decode() + >>> z = y.decode() + >>> map(unicode, (x,y,z)) + [u'%26lt%3B%26gt%3B%26amp%3B', u'<>&', u'<>&'] + ''' + encoding = self.encodings[-1] + newencodings = self.encodings[:-1] + return EncodedString(self._data.decode(encoding), newencodings) + + def __getattr__(self, attr): + ''' + Delegate to data's methods. Useful? maybe. + ''' + if attr in ('encodings', '_data', 'encode', 'decode', 'decodeAll'): + return object.__getattribute__(self, attr) + else: + return self._data.__getattribute__(attr) + + def __repr__(self): + return '<%s (%s) encodings=%r>' % (type(self).__name__, repr(self._data), self.encodings) + + def __str__(self): + if type(self._data) is str: + return self._data + else: + raise TypeError('%r cannot be implicitly converted to str') + + def __unicode__(self): + if type(self._data) is unicode: + return self._data + else: + raise TypeError('%r cannot be implicitly converted to unicode') + +estring = EncodedString + +def try_all_encodings(s): + ''' + You'd better have a good reason to use this. + ''' + successes = [] + import encodings + codecs = set(encodings.aliases.aliases.values()) + for c in codecs: + try: + decoded = s.decode(c) + except (Exception,): + continue + else: + if isinstance(decoded, unicode): + try: + recoded = decoded.encode('utf8') + except UnicodeEncodeError: + continue + else: + continue + if isinstance(recoded, str) and isinstance(decoded, unicode): + # we have a winner! + codec = c + successes.append((s,codec,decoded,recoded)) + else: + decoded = recoded = codec = None + + return successes + +def saferepr(obj): + try: + return repr(obj) + except Exception: + try: + return '<%s>' % obj.__class__.__name__ + except Exception: + return '' + +def wireshark_format(data): + out = StringIO() + + safe = (set(string.printable) - set(string.whitespace)) | set((' '),) + + def hex(s): + return ' '.join('%02X' % ord(ch) for ch in s) + def safe_bin(s): + return ''.join((ch if ch in safe else '.') for ch in s) + + def pad(s, l, ch=' '): + return s + (ch * (l - len(s))) + + w = out.write + space = ' ' * 4 + + while data: + chunk1, chunk2, data = data[:8], data[8:16], data[16:] + w(pad(hex(chunk1), 24)); w(space) + w(pad(hex(chunk2), 24)); w(space) + w(pad(safe_bin(chunk1), 8)); w(space) + w(pad(safe_bin(chunk2), 8)); w('\n') + + return out.getvalue() + +#these next two came from introspect +def to_hex(string, spacer=" "): + """ + Converts a string to a series of hexadecimal characters. + + Use the optional spacer argument to define a string that is placed between + each character. + + Example: + + >>> print to_hex("hello!", ".") + 68.65.6C.6C.6F.21 + + >>> print to_hex("boop") + 62 6F 6F 70 + + """ + return spacer.join('%02X' % ord(byte) for byte in string) + +def byte_print(string_, spacer=" "): + import string as strng + output = '' + for i in range(len(string_)/16+1): + line = string_[i*16: i*16 + 16] + pline = ''.join(x if x in strng.printable else '.' for x in line) + pline = pline.replace('\n', ' ') + pline = pline.replace('\r', ' ') + pline = pline.replace('\t', ' ') + pline = pline.replace('\x0b', ' ') + pline = pline.replace('\x0c', ' ') + output += to_hex(line) + ' ' + pline + '\n' + return output + +def string_xor(x, y, adjustx=False, adjusty=False): + assert isinstance(x, bytes) and isinstance(y, bytes) + assert not (adjustx and adjusty) + if adjusty: + x, y, adjustx = y, x, True + if adjustx: + x = ((int(float(len(y))/float(len(x))) + 1)*x)[:len(y)] + assert len(x) == len(y) + return ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(x, y)) + +def dequote(s): + if isinstance(s, basestring): + if (s.startswith('"') and s.endswith('"')) or \ + (s.startswith("'") and s.endswith("'")): + return s[1:-1] + return s + +def indefinite_article(s): + # i18n ... help! + if s.lower()[0] in _('aeiou'): + article = _('an') + else: + article = _('a') + + return _('%(article)s %(s)s') % locals() + + +def abbreviate_dotted(dotted_string, desired_len=-1, + bias_left = 0, + bias_right = 0, + biases = None): + ''' + abbreviates a string like: + alpha.beta.gamma + alpha.b.gamma + a.b.gamma + ab.gamma + ab.g + abg + a + + bias determines which sections get shortened last(+)/first(-) + left 1, right 1 + default.foo.com -> default.f.com + left -1, right -1 + negative.foo.com -> n.foo.c + + if more fine grained control is desired, a sequence can be provided, + of the form: [(position, weight)]. position is 0-based. + with [(2, -5)]: + weighted.foo.com -> weighted.foo.c + when "biases" is provided, left and right will be ignored. + otherwise, they determine the number of sections on each side for which + it is desirable to keep (or get rid of). + + TODO: add an ellipsis mode, for "alpha.beta.g..." + + >>> print abbreviate_dotted("alpha.beta.gamma", 10) + al.b.gamma + >>> print abbreviate_dotted("alpha.beta.gamma", 4) + ab.g + >>> print abbreviate_dotted("alpha.beta.gamma", 8) + a.b.gamm + >>> print abbreviate_dotted("alpha.beta.gamma", 0) + + ''' + + if any((bias_left, bias_right, biases)): + raise NotImplementedError + + remove = len(dotted_string) - desired_len + + split = dotted_string.split('.') + + if len(split) >= desired_len: + return ''.join([s[0] for s in split])[:desired_len] + + lengths = [len(section) for section in split] + while remove and max(lengths) > 1: + notone = [i for i in lengths if i != 1] + val = min(notone) + idx = lengths.index(val) + if val > remove: + newval = val - remove + else: + newval = 1 + lengths[idx] = newval + remove -= val - newval + + return ''.join([s[:i] for s,i in zip(split[:remove+1], lengths[:remove+1])]) + \ + '.' + '.'.join([s[:i] for s,i in zip(split[remove+1:], lengths[remove+1:])]) + + +def merge_xml(a, b, **opts): + import lxml.etree as etree + import lxml.objectify as objectify + import copy + + if isinstance(a, objectify.ObjectifiedElement): + # Objectify elements are not editable. + a = etree.tostring(a) + + if isinstance(a, basestring): + a = etree.fromstring(a) + + if isinstance(b, objectify.ObjectifiedElement): + # Objectify elements are not editable. + b = etree.tostring(b) + + if isinstance(b, basestring): + b = etree.fromstring(b) + + if not isinstance(a, etree._Element): + raise TypeError("Need string or lxml element!", a) + + if not isinstance(b, etree._Element): + raise TypeError("Need string or lxml element!", b) + + if a.tag != b.tag: + raise Exception("Tags don't match, can't merge!", a, b) + + text_replace = opts.get("text", 'replace') == 'replace' + + a.attrib.update(dict(b.attrib)) + if a.text is None or text_replace: + a.text = b.text + elif b.text is not None: + a.text += b.text + + if a.tail is None or text_replace: + a.tail = b.tail + elif b.tail is not None: + a.tail += b.tail + + merged_tags = [] + a_child = b_child = None + for a_child in a.iterchildren(): + for b_child in b.iterfind(a_child.tag): + merged_tags.append(a_child.tag) + merge_xml(a_child, b_child) + + a_child = b_child = None + for b_child in b.iterchildren(): + if b_child.tag in merged_tags: + continue + + matches = 0 + for a_child in a.iterfind(b_child.tag): + matches += 1 + merge_xml(a_child, b_child) + + if matches == 0: + a.append(copy.deepcopy(b_child)) + + return a + +if __name__ == '__main__': + import locale + locale.setlocale(locale.LC_ALL, 'US') + import doctest + doctest.testmod(verbose=True) + + + diff --git a/digsby/src/util/primitives/structures.py b/digsby/src/util/primitives/structures.py new file mode 100644 index 0000000..83d59e9 --- /dev/null +++ b/digsby/src/util/primitives/structures.py @@ -0,0 +1,435 @@ +import mapping +import struct +import types + +#import logging +#log = logging.getLogger('util.primitives.structures') + +class enum(list): + ''' + >>> suits = enum(*'spades hearts diamonds clubs'.split()) + >>> print suits.clubs + 3 + >>> print suits['hearts'] + 1 + ''' + + def __init__(self, *args): + list.__init__(self, args) + + def __getattr__(self, elem): + return self.index(elem) + + def __getitem__(self, i): + if isinstance(i, basestring): + return self.__getattr__(i) + else: + return list.__getitem__(self, i) + +class EnumValue(object): + def __init__(self, name, int, **kwds): + self.str = name + self.int = int + + for k,v in kwds.items(): + setattr(self, k, v) + + def __str__(self): + return self.str + def __int__(self): + return self.int + def __cmp__(self, other): + try: + other_int = int(other) + except: + return 1 + else: + return cmp(int(self), other_int) + def __repr__(self): + return '<%s %s=%d>' % (type(self).__name__, str(self), int(self)) + +class _EnumType(type): + def __new__(self, clsname, bases, vardict): + clsdict = {} + values = [] + ValueType = vardict.get('ValueType', EnumValue) + for name, value in vardict.items(): + if name == 'ValueType' or name.startswith('_') or isinstance(value, types.FunctionType): + clsdict[name] = value + continue + + if isinstance(value, dict): + EVal = ValueType(name, **value) + elif isinstance(value, int): + EVal = ValueType(name, value) + elif isinstance(value, tuple): + EVal = ValueType(name, *value) + + values.append(EVal) + + for val in values: + clsdict[str(val)] = val + + _known = {} + for val in values: + values_dict = dict(vars(val)) + equiv = values_dict.values() + for eq in equiv: + try: + hash(eq) + except TypeError: + continue + _known[eq] = val + + clsdict['_known'] = _known + + return type.__new__(self, clsname, bases, clsdict) + +class _Enum(object): + __metaclass__ = _EnumType + ValueType = EnumValue + + def __call__(self, something): + if isinstance(something, self.ValueType): + return something + if isinstance(something, dict): + something = something.get('int') + + return self._known.get(something, None) + +def Enum(Name, Type = EnumValue, **kws): + enum_dict = dict(vars(_Enum)) + enum_dict.update(ValueType = Type, **kws) + return _EnumType(Name, (_Enum,), enum_dict)() + +def new_packable(fmt, byteorder='!', invars=None): + invars = invars or [] + slots = fmt[::2] + fmtstring = byteorder + ''.join(fmt[1::2]) + + class packable(object): + __slots__, _fmt, invariants = slots, fmtstring, invars + @classmethod + def unpack(cls,data): + o = cls(*struct.unpack(cls._fmt, data)) + assert all(invar(o) for invar in cls.invariants) + return o + def __init__(self, *a, **kw): + i = -1 + for i, d in enumerate(a): setattr(self, self.__slots__[i], d) + for field in self.__slots__[i+1:]: setattr(self, field, 0) + for k in kw: setattr(self, k, kw[k]) + def pack(self): + return struct.pack(self._fmt, *(getattr(self, field) + for field in self.__slots__)) + def __iter__(self): + return ((s, getattr(self, s)) for s in self.__slots__) + def __len__(self): return struct.calcsize(self._fmt) + __str__ = pack + + def __eq__(self, other): + o = () + for slot in self.__slots__: + sval = getattr(self, slot) + oval = getattr(other, slot, o) + if oval is o: return False + if oval != sval: return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def copy(self): + return self.unpack(self.pack()) + + return packable + +def unpack_named(format, *args): + """ + Like struct.unpack, but with names. Name/value pairs are put into a dictionary and + returned. + + Usage: + my_hash = unpack_named( data format, name1, name2, ..., nameN, data ) + + In addition to all the normal pack/unpack keycodes like I, B, and H, you can also + use an uppercase R to indicate the "rest" of the data. Logically, the R can only + appear at the end of the format string. + + Example: + + >>> testdata = struct.pack("!HIB", 1,4000L,3) + "some extraneous data" + >>> magic_hash = unpack_named("!HIBR", "one", "four thousand long", "three", "extra", testdata) + >>> v = magic_hash.values() + >>> v.sort() + >>> print v + [1, 3, 4000, 'some extraneous data'] + """ + data = args[-1] + + # if format has our special R character, make sure it's at end + rest = None + if 'R' in format: + if format.find('R') != len(format) - 1: + raise AssertionError("R character in format string to unpack_named can only appear at the end") + else: + format = format[:-1] # chop off the last character + sz = struct.calcsize(format) + + # slice the "rest" off of the data + rest = data[sz:] + data = data[:sz] + + # unpack using the ever handy struct module + tup = struct.unpack(format, data) + + # give names to our newly unpacked items + magic_hash = {} + for i in xrange(len(tup)): + magic_hash[ args[i] ] = tup[i] + if rest: + magic_hash[ args[i+1] ] = rest + + return mapping.to_storage(magic_hash) + +def remove_from_list(my_list, remove_these): + my_list = my_list[:] + remove_list = [e for e in my_list if e in remove_these] + for e in remove_list: my_list.remove(e) + return my_list + +class oset(set): + def __init__(self, iterable=None): + self.data = [] + + if iterable is None: + iterable = [] + self.update(iterable, init=True) + + def add(self, val): + ''' + >>> a = oset([1,2,3]) + >>> a.add(3) + >>> a + oset([1, 2, 3]) + >>> a = oset([1,2,3]) + >>> a.add(4) + >>> a + oset([1, 2, 3, 4]) + ''' + if val not in self.data: + self.data.append(val) + set.add(self, val) + + def __getitem__(self,n): + ''' + >>> a = oset([8,4,6]) + >>> a[1] + 4 + >>> a[1:] + oset([4, 6]) + ''' + if isinstance(n, slice): + return type(self)(self.data[n]) + return self.data[n] + + def __iter__(self): + return iter(self.data) + + def clear(self): + del self.data[:] + set.clear(self) + + def pop(self): + ret = set.pop(self) + self.data.remove(ret) + return ret + + def remove(self, item): + self.data.remove(item) + set.remove(self, item) + + def discard(self, item): + try: self.remove(item) + except ValueError: pass + except KeyError: pass + + def union(self, other): + if not isinstance(other, oset): + other = oset(other) + return self | other + + def __or__(self, other): + if not isinstance(other, set): + raise ValueError, "other must be a set" + ret = oset(self) + ret.update(other) + return ret + + def intersection(self, other): + if not isinstance(other, oset): + other = oset(other) + return self & other + + def __and__(self, other): + if not isinstance(other, set): + raise ValueError, "other must be a set" + a = oset(self) + b = other + return a - (a - b) + + def difference(self, other): + other = oset(other) + return self - other + + def __sub__(self, other): + if not isinstance(other, set): + raise ValueError, "other must be a set" + first = oset(self) + first -= other + return first + + def symmetric_difference(self, other): + if not isinstance(other, oset): + other = oset(other) + return self ^ other + + def __xor__(self, other): + if not isinstance(other, set): + raise ValueError, "other must be a set" + return (self | other) - (self & other) + + def copy(self): + return oset(self) + + def update(self, other, init=False): + if not isinstance(other, oset) and not init: + other = oset(other) + self.__ior__(other, init=init) + + def __ior__(self, other, init=False): + if not isinstance(other, set) and not init: + raise ValueError, "other must be a set" + for i in other: + self.add(i) + return self + + def intersection_update(self, other): + if not isinstance(other, oset): + other = oset(other) + self &= other + + def __iand__(self, other): + if not isinstance(other, set): + raise ValueError, "other must be a set" + self -= (self & other) + + def difference_update(self, other): + if not isinstance(other, oset): + other = oset(other) + self -= other + + def __isub__(self, other): + if not isinstance(other, set): + raise ValueError, "other must be a set" + for item in other: + self.discard(item) + return self + + def symmetric_difference_update(self, other): + if not isinstance(other, oset): + other = oset(other) + self ^= other + + def __ixor__(self, other): + if not isinstance(other, set): + raise ValueError, "other must be a set" + b = oset(other) + b -= self + self -= other + self |= b + return self + +class roset(oset): + def add(self,val): + if val in self: + self.data.remove(val) + self.data.append(val) + else: + oset.add(self,val) + + def insert(self, idx, item): + if item in self: + self.data.remove(item) + + self.data.insert(idx, item) + set.add(self, item) + +class EmptyQueue(Exception): pass + +class PriorityQueue(object): + ''' + PriorityQueues sort their elements on insertion, using the heapq module. + + Not thread-safe! + + >>> pq = PriorityQueue('last') + >>> pq += ('first', 0) + >>> pq += ('third', 3) + >>> pq += ('second', 2) + >>> while len(pq): print pq.next() + first + second + third + last + >>> len(pq) + 0 + ''' + default_priority = 5 + + def __init__(self, *args): + self.q = [(self.default_priority, arg) for arg in args] + + # Sort elements if we got them + self.key = lambda a: a[0] + self.q.sort(key=self.key) + + def __len__(self): + return len(self.q) + + def count(self, x): + return self.q.count(x) + + def peek(self): + 'Peek at the next element.' + if not self.q: raise EmptyQueue + + __, item = self.q[0] + return item + + def __iadd__(self, elemtuple): + if isinstance(elemtuple, (tuple, list)): + if len(elemtuple) != 2: + raise TypeError('add to the PriorityQueue like += (item, priority) or just += item') + self.append(*elemtuple) + else: + self.append(elemtuple) + return self + + def __nonzero__(self): + return self.q.__len__() + + def append(self, item, priority = default_priority): + self.q.append((priority, item)) + self.q.sort(key=self.key) + + def next(self): + __, item = self.q.pop(0) + return item + + def __repr__(self): + return "" % self.q + +if __name__ == '__main__': + import doctest + doctest.testmod(verbose=True) diff --git a/digsby/src/util/primitives/synchronization.py b/digsby/src/util/primitives/synchronization.py new file mode 100644 index 0000000..681f675 --- /dev/null +++ b/digsby/src/util/primitives/synchronization.py @@ -0,0 +1,113 @@ +from __future__ import with_statement +import itertools +import functools +import logging +import os, sys, traceback +import threading +import time + +log = logging.getLogger('util.primitives.synch') + +def lock(f): + @functools.wraps(f) + def wrapper1(instance, *args, **kw): + if not hasattr(instance, '_lock'): + try: + instance._lock = threading.RLock() + except AttributeError: + raise NotImplementedError, '%s needs a _lock slot' % instance.__class__.__name__ + with instance._lock: + val = f(instance, *args, **kw) + return val + return wrapper1 + +class RepeatCheck(object): + ''' + A callable object that returns True if you call + it with the same object, or the same list of objects. + ''' + + def __init__(self, idfunc = None): + self.ids = sentinel + + if idfunc is None: idfunc = id + self.id = idfunc + + def __call__(self, *x): + if x == tuple(): + # clear + self.ids = sentinel + return + elif len(x) != 1: + raise TypeError('takes one argument') + + try: + newids = [self.id(a) for a in x] + except TypeError: + newids = [self.id(a)] + + changed = newids != self.ids + self.ids = newids + + return changed + +def repeat_guard(func): + 'Useful for observer callbacks to elimanate redunant updates.' + + guard = RepeatCheck() + + def wrapper(src, attr, old, new): + if guard(src): return + return func(src, attr, old, new) + + return wrapper + +class HangingThreadDaemon(threading.Thread): + ''' + Create one, and start() it when you are closing the program. + + If the program is not exiting because of non-daemon Threads + sticking around, it will tell you which ones are still running. + ''' + ids = itertools.count() + def __init__(self, wait = 3, sysexit = False): + threading.Thread.__init__(self, name="HangingThreadDaemon %d" % + self.ids.next()) + self.wait = wait + self.sysexit = sysexit + + # the presence of this thread should not prevent normal program shutdown + self.setDaemon(True) + + def run(self): + time.sleep(self.wait) + + threads = list(threading.enumerate()) + if threads: + print 'Remaining non-daemon threads:' + for thread in threads: + if not thread.isDaemon(): + print ' ', thread + + collect_garbage_and_report() + + if self.sysexit: + try: + import common.commandline as cc + cc.where() + except Exception: + traceback.print_exc() + print >>sys.stderr, 'forcing shutdown...' + os._exit(1) + +def collect_garbage_and_report(): + import gc + garbage_count = gc.collect() + if garbage_count > 0: + log.info("Garbage collected. " + str(garbage_count) + " unreachable objects") + if garbage_count: + log.info("Garbage left (only first 20 listed): %r", gc.garbage[:20]) + +if __name__ == '__main__': + import doctest + doctest.testmod(verbose=True) diff --git a/digsby/src/util/primitives/topo_sort.py b/digsby/src/util/primitives/topo_sort.py new file mode 100644 index 0000000..13ca654 --- /dev/null +++ b/digsby/src/util/primitives/topo_sort.py @@ -0,0 +1,94 @@ +''' +Created on Nov 23, 2010 + +@author: Christopher Stelma + +A topological sort based on finishing time, +which does not have a problem with cycles. +''' +from structures import oset +from util.primitives.mapping import odict, dictreverse +from collections import defaultdict + +def add_node(graph, incidence, node): + """Add a node to the graph if not already exists.""" + graph.setdefault(node, oset()) + incidence.setdefault(node, 0) + +def add_arc(graph, incidence, u, v): + graph[u].add(v) + incidence[v] = incidence.get(v, 0) + 1 + +def create_graph(chains, incidence): + graph = odict() + for chain in chains: + if not len(chain): + continue + add_node(graph, incidence, chain[0]) +# for i in range(len(chain)-1): +# add_node(graph, incidence, chain[i+1]) +# add_arc(graph, incidence, chain[i], chain[i+1]) + for i in range(len(chain)): + add_node(graph, incidence, chain[i]) + for j in range(i, len(chain)): + add_arc(graph, incidence, chain[i], chain[j]) + return graph + +WHITE = 'WHITE' +BLACK = 'BLACK' +GREY = 'GREY' + +def DFS(G, color, pred, disc, fin): + for u in G: + color[u] = WHITE + pred[u] = None + time = 0 + for u in G: + if color[u] == WHITE: + time = DFSvisit(u, G, color, pred, disc, fin, time) + +def DFSvisit(u, G, color, pred, disc, fin, time): + color[u] = GREY + time = time+1 + disc[u] = time + for v in G[u]: + if color[v] == WHITE: + pred[v] = u + time = DFSvisit(v, G, color, pred, disc, fin, time) + color[u] = BLACK + time = time+1 + fin[u] = time + return time + +def topological_sort_chains(chains): + incidence = dict() + G = create_graph(chains, incidence) + assert len(G) == len(incidence) + color = {} + pred = {} + disc = {} + fin = {} + DFS(G, color, pred, disc, fin) + fin2 = dictreverse(fin) + fin3 = [] + for i in sorted(fin2, reverse=True): + fin3.append(fin2[i]) + return fin3 + +if __name__ == '__main__': + incidence = dict() + G = create_graph([ [6, 0, 5], [5, 6, 9, 3, 8, 7, 4, 2], [1, 0], [5, 6, 1, 0, 9, 3, 8, 7, 4, 2]], incidence) + assert len(G) == len(incidence) + color = {} + pred = {} + disc = {} + fin = {} + DFS(G, color, pred, disc, fin) + from mapping import dictreverse + fin2 = dictreverse(fin) + fin3 = [] + for i in sorted(fin2, reverse=True): + fin3.append(fin2[i]) + print fin3 +# print incidence + diff --git a/digsby/src/util/primitives/topological_sort.py b/digsby/src/util/primitives/topological_sort.py new file mode 100644 index 0000000..4b2e581 --- /dev/null +++ b/digsby/src/util/primitives/topological_sort.py @@ -0,0 +1,137 @@ +# Original topological sort code written by Ofer Faigon (www.bitformation.com) and used with permission +def topological_sort(items, partial_order): + """Perform topological sort. + items is a list of items to be sorted. + partial_order is a list of pairs. If pair (a,b) is in it, it means + that item a should appear before item b. + Returns a list of the items in one of the possible orders, or None + if partial_order contains a loop. + """ + + def add_node(graph, node): + """Add a node to the graph if not already exists.""" + if not graph.has_key(node): + graph[node] = [0] # 0 = number of arcs coming into this node. + + def add_arc(graph, fromnode, tonode): + """Add an arc to a graph. Can create multiple arcs. + The end nodes must already exist.""" + graph[fromnode].append(tonode) + # Update the count of incoming arcs in tonode. + graph[tonode][0] = graph[tonode][0] + 1 + + # step 1 - create a directed graph with an arc a->b for each input + # pair (a,b). + # The graph is represented by a dictionary. The dictionary contains + # a pair item:list for each node in the graph. /item/ is the value + # of the node. /list/'s 1st item is the count of incoming arcs, and + # the rest are the destinations of the outgoing arcs. For example: + # {'a':[0,'b','c'], 'b':[1], 'c':[1]} + # represents the graph: c <-- a --> b + # The graph may contain loops and multiple arcs. + # Note that our representation does not contain reference loops to + # cause GC problems even when the represented graph contains loops, + # because we keep the node names rather than references to the nodes. + graph = {} + for v in items: + add_node(graph, v) + for a,b in partial_order: + add_arc(graph, a, b) + + # Step 2 - find all roots (nodes with zero incoming arcs). + roots = [node for (node,nodeinfo) in graph.items() if nodeinfo[0] == 0] + + # step 3 - repeatedly emit a root and remove it from the graph. Removing + # a node may convert some of the node's direct children into roots. + # Whenever that happens, we append the new roots to the list of + # current roots. + sorted = [] + while len(roots) != 0: + # If len(roots) is always 1 when we get here, it means that + # the input describes a complete ordering and there is only + # one possible output. + # When len(roots) > 1, we can choose any root to send to the + # output; this freedom represents the multiple complete orderings + # that satisfy the input restrictions. We arbitrarily take one of + # the roots using pop(). Note that for the algorithm to be efficient, + # this operation must be done in O(1) time. + root = roots.pop() + sorted.append(root) + for child in graph[root][1:]: + graph[child][0] = graph[child][0] - 1 + if graph[child][0] == 0: + roots.append(child) + del graph[root] + if len(graph.items()) != 0: + # There is a loop in the input. + return None + return sorted + +def topological_sort_chains(chains): + """Perform topological sort. + items is a list of items to be sorted. + partial_order is a list of pairs. If pair (a,b) is in it, it means + that item a should appear before item b. + Returns a list of the items in one of the possible orders, or None + if partial_order contains a loop. + """ + + def add_node(graph, node): + """Add a node to the graph if not already exists.""" + if not graph.has_key(node): + graph[node] = [0] # 0 = number of arcs coming into this node. + + def add_arc(graph, fromnode, tonode): + """Add an arc to a graph. Can create multiple arcs. + The end nodes must already exist.""" + graph[fromnode].append(tonode) + # Update the count of incoming arcs in tonode. + graph[tonode][0] = graph[tonode][0] + 1 + + # step 1 - create a directed graph representing the input. + graph = {} + for chain in chains: + if not len(chain): + continue + add_node(graph, chain[0]) + for i in range(len(chain)-1): + add_node(graph, chain[i+1]) + add_arc(graph, chain[i], chain[i+1]) + + from .structures import oset + total = oset() + for chain in chains: + total.update(chain) + + # Step 2 - find all roots (nodes with zero incoming arcs). + roots = oset([node for (node,nodeinfo) in graph.items() if nodeinfo[0] == 0]) + + roots = list(total & roots) +# return roots, graph + # step 3 - repeatedly emit a root and remove it from the graph. Removing + # a node may convert some of the node's direct children into roots. + # Whenever that happens, we append the new roots to the list of + # current roots. + sorted = [] + while len(roots) != 0: + # If len(roots) is always 1 when we get here, it means that + # the input describes a complete ordering and there is only + # one possible output. + # When len(roots) > 1, we can choose any root to send to the + # output; this freedom represents the multiple complete orderings + # that satisfy the input restrictions. We arbitrarily take one of + # the roots using pop(). Note that for the algorithm to be efficient, + # this operation must be done in O(1) time. + root = roots.pop(0) + sorted.append(root) + newroots = [] + for child in graph[root][1:]: + graph[child][0] = graph[child][0] - 1 + if graph[child][0] == 0: + newroots.append(child) + roots = newroots + roots + del graph[root] + if len(graph.items()) != 0: + # There is a loop in the input. + return None + return sorted diff --git a/digsby/src/util/proxy_settings.py b/digsby/src/util/proxy_settings.py new file mode 100644 index 0000000..b98a7ef --- /dev/null +++ b/digsby/src/util/proxy_settings.py @@ -0,0 +1,45 @@ +from .observe import ObservableDict as SavingDictBase +class ProxySavingDict(SavingDictBase): + def __init__(self, save_func, *a, **k): + self.save_func = save_func + SavingDictBase.__init__(self, *a, **k) + + def save(self): + self.save_func(self) + + def __setitem__(self, key, val): + SavingDictBase.__setitem__(self, key, val) + self.save() + + def __delitem__(self, key): + SavingDictBase.__delitem__(self, key) + self.save() + + def clear(self): + SavingDictBase.clear(self) + self.save() + +__proxy_settings = None + +def get_proxy_dict(): + global __proxy_settings + + if __proxy_settings is not None: + return __proxy_settings + + import gui.toolbox.toolbox as tb + ls = tb.local_settings() + + section = 'Proxy Settings' + + if not ls.has_section(section): + ls.add_section(section) + + def save(d): + ls._sections[section] = d.copy() + ls.save() + + __proxy_settings = ProxySavingDict(save, ls.iteritems(section)) + + + return __proxy_settings diff --git a/digsby/src/util/pyppmd.py b/digsby/src/util/pyppmd.py new file mode 100644 index 0000000..a76e78b --- /dev/null +++ b/digsby/src/util/pyppmd.py @@ -0,0 +1,64 @@ +import ctypes +import sys +import struct + +class Ppmd7Error(Exception): pass +class Ppmd7DecodeError(Ppmd7Error): pass +class Ppmd7EncodeError(Ppmd7Error): pass + +MAGICPREFIX = 'ppmd7' + +ppmd = None +def _load_ppmd(): + global ppmd + if ppmd is None: + ppmd = ctypes.cdll.ppmd7 + return ppmd + +def encode(buffer, order=64, memsize=((1<<26)*3)): + ppmd = _load_ppmd() + + outBuffer = ctypes.POINTER(ctypes.c_char)() + outBufferSize = ctypes.c_int() + buffer_size = len(buffer) + ret = ppmd.encode(buffer, buffer_size, order, memsize, ctypes.byref(outBuffer), ctypes.byref(outBufferSize)) + if ret < 0: + raise Ppmd7EncodeError() + val = ctypes.string_at(outBuffer, outBufferSize) + ppmd.kill(ctypes.byref(outBuffer)) + return val + +def pack(buffer): + buffer_size = len(buffer) + val = encode(buffer) + assert buffer_size < sys.maxint + return MAGICPREFIX + struct.pack('!I', buffer_size) + val + +def unpack(buffer): + ppmd = _load_ppmd() + if not buffer.startswith(MAGICPREFIX): + raise Ppmd7DecodeError('invalid header') + buffer = buffer[len(MAGICPREFIX):] + (size,) = struct.unpack('!I', buffer[:4]) + buffer = buffer[4:] + return decode(buffer, size) + +def decode(buffer, expected, order=64, memsize=((1<<26)*3)): + outBuffer = ctypes.POINTER(ctypes.c_char)() + outBufferSize = ctypes.c_int() + expected = ctypes.c_int(expected) + if -1 == ppmd.decode(buffer, len(buffer), order, memsize, expected, ctypes.byref(outBuffer), ctypes.byref(outBufferSize)): + raise Ppmd7DecodeError() + val = ctypes.string_at(outBuffer, outBufferSize) + ppmd.kill(ctypes.byref(outBuffer)) + return val + +if __name__ == '__main__': +# from tests.testapp import testapp +# with testapp(plugins=False): + s = 'test foo foo bar foo foo bar' + enc = pack(s) + dec = unpack(enc) + print s + print dec + diff --git a/digsby/src/util/rtf.py b/digsby/src/util/rtf.py new file mode 100644 index 0000000..49057fb --- /dev/null +++ b/digsby/src/util/rtf.py @@ -0,0 +1,460 @@ +''' +Module for various RTF functions. Extremely limited. + +Originally created for parsing simple RTF documents. +Example: + + {\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}} + {\colortbl ;\red0\green255\blue64;} + \viewkind4\uc1\pard\cf1\b\f0\fs32 this is the body\par + } + + {\rtf1\ansi\ansicpg1251\deff0\deflang1026{\fonttbl{\f0\fnil\fcharset204{\*\fname Arial;}Arial CYR;}} + {\colortbl ;\red0\green64\blue128;} + \viewkind4\uc1\pard\cf1\f0\fs20\'e4\'e0\par + } + +''' +import string + +symbols = { + '\\': '\\', + '~': ' ', # should really be non-breaking space-   in html + 'tab' : '\t', + "'7b" : '{', + "'7d" : '}', + } + +rev_symbols = {} +for k,v in symbols.items(): + rev_symbols[v] = k + +rtf_fcharsets = { + 0 : 'ANSI', + 1 : 'Default', + 2 : 'Symbol', + 3 : 'Invalid', + 77 : 'Mac', + 128 : 'shiftjis', + 130 : 'johab', + 134 : 'GB2312', + 136 : 'Big5', + 161 : 'Greek', + 162 : 'iso-8859-9', # Turkish + 163 : 'cp1258', # Vietnamese + 177 : 'Hebrew', + 178 : 'Arabic', + 179 : 'Arabic Traditional', + 180 : 'Arabic user', + 181 : 'Hebrew user', + 186 : 'Baltic', + 204 : 'Russian', + 222 : 'Thai', + 238 : 'Eastern European', + 254 : 'PC 437', + 255 : 'OEM', +} +def tokenize(string): + ''' + input: + {\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}} + {\colortbl ;\red0\green255\blue64;} + \viewkind4\uc1\pard\cf1\b\f0\fs32 this is the body\par + } + + output: + + ['{', '\\', 'rtf1', '\\', 'ansi', '\\', + 'ansicpg1252', '\\', 'deff0', '\\', + 'deflang1033', '{', '\\', 'fonttbl', + '{', '\\', 'f0', '\\', 'fmodern', '\\', + 'fprq1', '\\', 'fcharset0', ' ', + 'Courier', ' ', 'New;', '}', '}', '\n', + '{', '\\', 'colortbl', ' ', ';', '\\', + 'red0', '\\', 'green255', '\\', 'blue64;', + '}', '\n', '\\', 'viewkind4', '\\', 'uc1', + '\\', 'pard', '\\', 'cf1', '\\', 'b', '\\', + 'f0', '\\', 'fs32', ' ', 'and', ' ', 'another', + '\\', 'par\n', '}'] + ''' + tokens = [] + curr_token = '' + + for c in string: + if c in '\\{} \r\n': + if curr_token: + tokens.append(curr_token) + curr_token = '' + if c == '\n' and tokens[-1] == '\r': + tokens[-1] = c + else: + tokens.append(c) + else: + curr_token += c + + if curr_token: + tokens.append(curr_token) + + + return tokens + +class TypedString(str): + def __repr__(self): + return '<%s %s>' % (type(self).__name__, str.__repr__(self)) + +class TypedList(list): + def __repr__(self): + return '<%s %s>' % (type(self).__name__, list.__repr__(self)) + +class ControlNode(TypedString): + pass +class TextNode(TypedString): + pass +class WhitespaceNode(TypedString): + pass +class Group(TypedList): + pass + +def compress_text(doc): + new = Group() + cur_text = [] + while doc: + node = doc.pop(0) + if type(node) is WhitespaceNode: + if cur_text: + cur_text.append(node) + elif type(node) is TextNode: + cur_text.append(node) + elif type(node) is Group: + if cur_text: + new.append(TextNode(''.join(cur_text))) + new.append(compress_text(node)) + + return new + + +def parse(tokens): + doc = None + + while tokens: + token = tokens.pop(0) + + if token == '{': # start of group + if doc is None: + doc = Group() + else: + tokens.insert(0,'{') + doc.append(parse(tokens)) + elif token == '}': # end of group + return doc + elif token == '\\': # control code or symbol follows + next = tokens.pop(0) + + if len(next) == 1 and next not in (string.ascii_letters + string.digits): + doc.append(TextNode(symbols.get(next, next))) + else: + if next.startswith("'"): + # Hex number for a character + hexchar = next[1:3] + tokens.insert(0, next[3:]) # put the rest back + doc.append(TextNode(chr(int(hexchar, 16)))) + else: + doc.append(ControlNode(token + next)) + + elif token in string.whitespace: # whitespace. end of control code if applicable + last = doc[-1] + if type(last) is WhitespaceNode: + doc[-1] = WhitespaceNode(last + token) + else: + doc.append(WhitespaceNode(token)) + else: + last = doc[-1] + if type(last) is TextNode: + doc[-1] = TextNode(last + token) + else: + doc.append(TextNode(token)) + + doc = compress_text(doc) + + return doc + +def tree_to_plain(tree): + + ''' input: +, + , + , + , + , + , + , + , + , + , + + ]> + ]>, + , + , + , + , + + ]>, + , + , + , + , + , + , + , + , + +]> + + output: + 'this is the body' + ''' + tree = tree[:] + + if not tree: + return '' + + if type(tree[0]) is ControlNode and str(tree[0]) in ('\\colortbl','\\fonttbl'): + return '' + + res = [] + encoding = None + last = None + uni_replace_len = None + while tree: + node = tree.pop(0) + + if type(node) is Group: + res.append(tree_to_plain(node)) + + if type(node) is TextNode: + s = str(node) + if encoding is not None: + s = s.decode(encoding) + res.append(s) + + if type(node) is WhitespaceNode: + s = str(node) + if type(last) in (ControlNode, Group): + s = s[1:] + res.append(s) + + if type(node) is ControlNode: + if str(node) == '\\par': + res.append('\n') + elif str(node).startswith('\\ansicpg'): + try: + codepage = int(str(node)[len('\\ansicpg'):].strip()) + except (ValueError, IndexError), e: + pass + else: + encoding = 'cp%d' % codepage + + elif str(node).startswith('\\u') and str(node)[2] in ('-' + string.digits): + if tree: + put_back = True + replacement_charnode = tree.pop(0) + else: + put_back = False + replacement_charnode = TextNode('') + + if type(replacement_charnode) is not TextNode: + if put_back: + tree.insert(0, replacement_charnode) + replacement_char = ' ' + else: + replacement_char = str(replacement_charnode) + if uni_replace_len is not None: + if len(replacement_char) > uni_replace_len: + replacement_char, rest = replacement_char[uni_replace_len:], replacement_char[:uni_replace_len] + if rest: # should be true, given previous if statements + tree.insert(0, TextNode(rest)) + + try: + val = int(str(node)[2:]) + except ValueError: + val = ord(replacement_char) + else: + val = abs(val) + ((val < 0) * 32767) + + try: + res.append(unichr(val)) + except ValueError: + res.append(replacement_char) + last = node + + final = ''.join(res) + return final + +def rtf_to_plain(s): + return tree_to_plain(parse(tokenize(s))) + +def make_color_table(colors): + table = Group() + table.append(ControlNode('\\colortbl')) + table.append(TextNode(';')) + for color in colors: + r,g,b,a = tuple(color) + table.extend((ControlNode('\\red%d' % r), + ControlNode('\\green%d' % g), + ControlNode('\\blue%d' % b), + TextNode(';'))) + + return table + +def normalize_font_family(family): + family = family.lower() + if family not in set(('nil', 'roman', 'swiss', 'modern', 'script', 'decor', 'tech')): + return 'nil' + + return family + +def make_font_table(fonts): + table = Group() + table.append(ControlNode('\\fonttbl')) + for i, (family, font) in enumerate(fonts): + table.extend((ControlNode('\\f%d' % i), + ControlNode('\\' + normalize_font_family(family)), + TextNode(' ' + font + ';'))) + + return table + +def storage_to_tree(s): + if s.get('backgrouncolor') and s.get('foregroundcolor'): + color_table = make_color_table([s.backgroundcolor, s.foregroundcolor]) + else: + color_table = TextNode('') + + if s.get('family') and s.get('font'): + font_table = make_font_table([(s.family, s.font)]) + else: + font_table = TextNode('') + + top_level = Group([ + ControlNode('\\rtf1'), + ControlNode('\\ansi'), + ControlNode('\\uc1'), + color_table, + font_table, + ]) + + format_group = Group([]) + if font_table: + format_group.append(ControlNode('\\f1')) + if color_table: + format_group.append(ControlNode('\\cb1')) + format_group.append(ControlNode('\\cf2')) + + if s.get('bold'): + format_group.append(ControlNode('\\b')) + if s.get('italic'): + format_group.append(ControlNode('\\i')) + if s.get('underline'): + format_group.append(ControlNode('\\ul')) + if s.get('size'): + format_group.append(ControlNode('\\fs%d' % (s.size * 2))) + + top_level.append(format_group) + + return top_level, format_group.append + +def storage_to_rtf(s, text): + escaped = rtf_escape(text) + doc, add_text = storage_to_tree(s) + add_text(escaped) + return tree_to_rtf(doc) + +''' +{\rtf1\ansi\ansicpg1252\deff0\deflang1033 + {\fonttbl + {\f0\fswiss\fcharset0 Arial;} + {\f1\froman\fprq2\fcharset0 Bodoni;}} + {\\colortbl ;\\red0\\green255\\blue64;} + \viewkind4\uc1\pard\i\f0\fs20 first line\par\b second line\par\ul\i0 third line\par\b0 fourth line\par\ulnone\b bold\par\f1 newfont\ul\b0\f0\par} +''' + +#Storage( +# backgroundcolor = tuple(tc.BackgroundColour), +# foregroundcolor = tuple(tc.ForegroundColour), + +# face = font.FaceName, +# size = font.PointSize, +# underline = font.Underlined, +# bold = font.Weight == BOLD, +# italic = font.Style == ITALIC) + + +def rtf_escape(node): + if isinstance(node, unicode): + s = unicode(node) + try: + s = s.encode('ascii') + except UnicodeEncodeError: + pass + else: + assert isinstance(node, str) + s = str(node) + + return ''.join(rtf_escape_chr(c) for c in s) + +def rtf_escape_chr(c): + if c in rev_symbols: + return '\\' + rev_symbols[c] + elif isinstance(c, unicode): + val = ord(c) + negate, val = divmod(val, 32767) + if negate: + val = -abs(val) + return '\\u%d ?' % val + elif ord(c) > 127 and isinstance(c, str): + return ('\\\'%x' % ord(c)) + else: + return str(c) + +def tree_to_rtf(tree): + res = [] + res.append('{') + for node in tree: + t = type(node) + if t is Group: + res.append(tree_to_rtf(node)) + elif t is TextNode: + res.append(rtf_escape(node)) + else: + res.append(str(node)) + res.append('}') + return ''.join(res) + +def main(): + + for test_string, test_plain in ( + (r'{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fmodern\fprq1\fcharset0 Courier New;}}{\colortbl ;\red0\green255\blue64;}\viewkind4\uc1\pard\cf1\b\f0\fs32 this is the body\par}', 'this is the body\n'), + (r'{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fswiss\fcharset0 Arial;}{\f1\froman\fprq2\fcharset0 Bodoni;}}\viewkind4\uc1\pard\i\f0\fs20 first line\par\b second line\par\ul\i0 third line\par\b0 fourth line\par\ulnone\b bold\par\f1 newfont\ul\b0\f0\par}', ' first line\nsecond line\nthird line\nfourth line\nbold\nnewfont\n'), + ('{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0 Courier New;}}\n{\\colortbl ;\\red0\\green255\\blue64;}\n\\viewkind4\\uc1\\pard\\cf1\\b\\f0\\fs32 newline\\par\nbackslash\\\\ rawr end\\par\n}', 'newline\nbackslash\\ rawr end\n'), + #(r'{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\froman\fprq1\fcharset128 MS PGothic;}{\f1\fswiss\fprq2\fcharset0 Lucida Sans Unicode;}}\viewkind4\uc1\pard\f0\fs20\'93\'fa\'96\'7b\'8c\'ea\f1 / \f0\'82\'c9\'82\'d9\'82\'f1\'82\'b2\f1\par}', u'\u65e5\u672c\u8a9e / \u306b\u307b\u3093\u3054'), + + ): + + parsed = parse(tokenize(test_string)) + plain = tree_to_plain(parsed) + print plain + if not test_plain == plain: + print repr(test_plain) + print repr(plain) + print + if not test_string == tree_to_rtf(parsed): + print repr(test_string) + print repr(tree_to_rtf(parsed)) + print + +if __name__ == '__main__': + print main() diff --git a/digsby/src/util/singletonmixin.py b/digsby/src/util/singletonmixin.py new file mode 100644 index 0000000..2db5edd --- /dev/null +++ b/digsby/src/util/singletonmixin.py @@ -0,0 +1,216 @@ +""" +A Python Singleton mixin class that makes use of some of the ideas +found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit +from it and you have a singleton. No code is required in +subclasses to create singleton behavior -- inheritance from +Singleton is all that is needed. + +Assume S is a class that inherits from Singleton. Useful behaviors +are: + +1) Getting the singleton: + + S.getInstance() + +returns the instance of S. If none exists, it is created. + +2) The usual idiom to construct an instance by calling the class, i.e. + + S() + +is disabled for the sake of clarity. If it were allowed, a programmer +who didn't happen notice the inheritance from Singleton might think he +was creating a new instance. So it is felt that it is better to +make that clearer by requiring the call of a class method that is defined in +Singleton. An attempt to instantiate via S() will restult in an SingletonException +being raised. + +3) If S.__init__(.) requires parameters, include them in the +first call to S.getInstance(.). If subsequent calls have parameters, +a SingletonException is raised. + +4) As an implementation detail, classes that inherit +from Singleton may not have their own __new__ +methods. To make sure this requirement is followed, +an exception is raised if a Singleton subclass includ +es __new__. This happens at subclass instantiation +time (by means of the MetaSingleton metaclass. + +By Gary Robinson, grobinson@transpose.com. No rights reserved -- +placed in the public domain -- which is only reasonable considering +how much it owes to other people's version which are in the +public domain. The idea of using a metaclass came from +a comment on Gary's blog (see +http://www.garyrobinson.net/2004/03/python_singleto.html#comments). +Other improvements came from comments and email from other +people who saw it online. (See the blog post and comments +for further credits.) + +Not guaranteed to be fit for any particular purpose. Use at your +own risk. +""" + +class SingletonException(Exception): + pass + + +class Singleton(object): + def getInstance(cls, *lstArgs): + """ + Call this to instantiate an instance or retrieve the existing instance. + If the singleton requires args to be instantiated, include them the first + time you call getInstance. + """ + if cls._isInstantiated(): + if len(lstArgs) != 0: + raise SingletonException, 'If no supplied args, singleton must already be instantiated, or __init__ must require no args' + else: + if cls._getConstructionArgCountNotCountingSelf() > 0 and len(lstArgs) <= 0: + raise SingletonException, 'If the singleton requires __init__ args, supply them on first instantiation' + instance = cls.__new__(cls) + instance.__init__(*lstArgs) + cls.cInstance = instance + return cls.cInstance + getInstance = classmethod(getInstance) + + def _isInstantiated(cls): + return hasattr(cls, 'cInstance') + _isInstantiated = classmethod(_isInstantiated) + + def _getConstructionArgCountNotCountingSelf(cls): + return cls.__init__.im_func.func_code.co_argcount - 1 + _getConstructionArgCountNotCountingSelf = classmethod(_getConstructionArgCountNotCountingSelf) + + def _forgetClassInstanceReferenceForTesting(cls): + """ + This is designed for convenience in testing -- sometimes you + want to get rid of a singleton during test code to see what + happens when you call getInstance() under a new situation. + + To really delete the object, all external references to it + also need to be deleted. + """ + try: + delattr(cls,'cInstance') + except AttributeError: + # run up the chain of base classes until we find the one that has the instance + # and then delete it there + for baseClass in cls.__bases__: + if issubclass(baseClass, Singleton): + baseClass._forgetClassInstanceReferenceForTesting() + _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting) + + + +if __name__ == '__main__': + import unittest + + class PublicInterfaceTest(unittest.TestCase): + def testReturnsSameObject(self): + """ + Demonstrates normal use -- just call getInstance and it returns a singleton instance + """ + + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + a1 = A.getInstance() + a2 = A.getInstance() + self.assertEquals(id(a1), id(a2)) + + def testInstantiateWithMultiArgConstructor(self): + """ + If the singleton needs args to construct, include them in the first + call to get instances. + """ + + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + b1 = B.getInstance('arg1 value', 'arg2 value') + b2 = B.getInstance() + self.assertEquals(b1.arg1, 'arg1 value') + self.assertEquals(b1.arg2, 'arg2 value') + self.assertEquals(id(b1), id(b2)) + + + def testTryToInstantiateWithoutNeededArgs(self): + + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + self.assertRaises(SingletonException, B.getInstance) + + def testTryToInstantiateWithoutGetInstance(self): + """ + Demonstrates that singletons can ONLY be instantiated through + getInstance, as long as they call Singleton.__init__ during construction. + + If this check is not required, you don't need to call Singleton.__init__(). + """ + + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + self.assertRaises(SingletonException, A) + + def testDontAllowNew(self): + + def instantiatedAnIllegalClass(): + class A(Singleton): + def __init__(self): + super(A, self).__init__() + + def __new__(metaclass, strName, tupBases, dict): + return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict) + + self.assertRaises(SingletonException, instantiatedAnIllegalClass) + + + def testDontAllowArgsAfterConstruction(self): + class B(Singleton): + + def __init__(self, arg1, arg2): + super(B, self).__init__() + self.arg1 = arg1 + self.arg2 = arg2 + + b1 = B.getInstance('arg1 value', 'arg2 value') + self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value') + + def test_forgetClassInstanceReferenceForTesting(self): + class A(Singleton): + def __init__(self): + super(A, self).__init__() + class B(A): + def __init__(self): + super(B, self).__init__() + + # check that changing the class after forgetting the instance produces + # an instance of the new class + a = A.getInstance() + assert a.__class__.__name__ == 'A' + A._forgetClassInstanceReferenceForTesting() + b = B.getInstance() + assert b.__class__.__name__ == 'B' + + # check that invoking the 'forget' on a subclass still deletes the instance + B._forgetClassInstanceReferenceForTesting() + a = A.getInstance() + B._forgetClassInstanceReferenceForTesting() + b = B.getInstance() + assert b.__class__.__name__ == 'B' + + unittest.main() + + diff --git a/digsby/src/util/startup.py b/digsby/src/util/startup.py new file mode 100644 index 0000000..d9193bd --- /dev/null +++ b/digsby/src/util/startup.py @@ -0,0 +1,148 @@ +''' +Add or remove a program from a user's startup sequence. + +>>> import startup +>>> startup.enable('c:\\Program Files\\Digsby\\digsby.exe', 'Digsby', 'extra', 'args') + +Implemented for Windows and Mac OS X. +''' + +import platform, os.path +from logging import getLogger, StreamHandler +log = getLogger('startup') +log.addHandler(StreamHandler()) + +# Darwin, Windows, ... +_myplatform = platform.platform(terse = True).split('-')[0] + +def enable(program_path, nice_name, *args): + 'Causes a program to load on startup.' + + func_name = 'enable_' + _myplatform + program_name = nice_name or app_name(program_path) + + globals()[func_name](program_path, program_name, *args) + log.info('%s will launch on startup', program_name) + +def disable(program_name): + 'Disable a program loading on startup.' + + func_name = 'disable_' + _myplatform + globals()[func_name](program_name) + + log.info('%s removed from startup', program_name) + +def is_enabled(program_name): + return globals()['is_enabled_' + _myplatform]() + + + + +def app_name(program_path): + 'c:\Program Files\Digsby\digsby.exe -> digsby' + + return os.path.split(program_path)[-1].split('.')[0] + +# +# Mac >= 10.4 +# + +def enable_Darwin(program_path, program_name, *args): + username, home = os.environ.get('USER'), os.environ.get('HOME') + folderpath = home + "/Library/LaunchAgents" + plistpath = home + "/Library/LaunchAgents/%(program_name)s.plist" % locals() + + if not os.path.isdir(folderpath): + os.mkdir(folderpath) + if not os.path.isfile(plistpath): + # On 10.4, startup items are just XML files in the right place. + contents = \ +''' + + + + Label + %(program_name)s + OnDemand + + ProgramArguments + + %(program_path)s +''' % locals() + # Add an entry for each additional arg + contents += ''.join('%s' % arg for arg in args) + \ +''' + + RunAtLoad + + ServiceDescription + This plist launches a the specified program at login. + UserName + %(username)s + + +''' % locals() + + f = open(plistpath, 'w') + f.writelines(contents) + f.close() + else: + log.warning('There is already a startup item') + +def disable_Darwin(program_name): + home = os.environ.get("HOME") + plistpath = home + "/Library/LaunchAgents/%(program_name)s.plist" + + if os.path.isfile(plistpath): + os.remove(plistpath) + else: + log.warning('There was no plist file to remove!') + +def is_enabled_Darwin(program_name): + home = os.environ.get("HOME") + path = home + "/Library/LaunchAgents/%s.plist" % program_name + return os.path.isfile(path) + +# +# Windows +# + +def _startup_regkey(): + import _winreg + + return _winreg.OpenKeyEx(_winreg.HKEY_CURRENT_USER, #@UndefinedVariable + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",0, + _winreg.KEY_ALL_ACCESS) #@UndefinedVariable + + +def enable_Windows(program_path, program_name, *args): + import _winreg + + key = _startup_regkey() + regval = program_path + ' ' + ' '.join(args) + _winreg.SetValueEx(key, program_name, 0, _winreg.REG_SZ, regval) + _winreg.CloseKey(key) + +def disable_Windows(program_name): + ''' + This function will delete the value programname from + SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run in the registry + ''' + import _winreg + + key = _startup_regkey() + try: + _winreg.DeleteValue(key, program_name) + except WindowsError: + log.warning('startup.disable could not delete registry key') + _winreg.CloseKey(key) + +def is_enabled_Windows(program_name): + import _winreg + + key = _startup_regkey() + try: + _winreg.QueryValueEx(key, program_name) + return True + except WindowsError: + return False \ No newline at end of file diff --git a/digsby/src/util/threads/__init__.py b/digsby/src/util/threads/__init__.py new file mode 100644 index 0000000..938a6f7 --- /dev/null +++ b/digsby/src/util/threads/__init__.py @@ -0,0 +1,51 @@ +from threadpool2 import * +from timeout_thread import * + +import contextlib +from ..Events import EventMixin +class InvertedSemaphore(EventMixin): + events = EventMixin.events | set(( + 'resource_release', + 'resource_acquire', + )) + + def __init__(self, init = 0, lock = None): + EventMixin.__init__(self) + assert init >= 0 + self._value = init + + if lock is not None: + assert hasattr(lock, '__enter__') and hasattr(lock, '__exit__') + self.lock = lambda:lock + + def acquire(self): + do_event = False + with self.lock(): + self._value += 1 + + if self._value == 1: + do_event = True + + if do_event: + self.event("resource_acquire") + + def release(self): + do_event = False + with self.lock(): + if self._value == 0: + raise ValueError("Can't release when internal counter is at 0") + + self._value -= 1 + + if self._value == 0: + do_event = True + + if do_event: + self.event("resource_release") + + @contextlib.contextmanager + def lock(self): + try: + yield None + finally: + pass diff --git a/digsby/src/util/threads/bgthread.py b/digsby/src/util/threads/bgthread.py new file mode 100644 index 0000000..a15fa60 --- /dev/null +++ b/digsby/src/util/threads/bgthread.py @@ -0,0 +1,158 @@ +from __future__ import with_statement +import util.primitives.funcs as funcs +import threading +from Queue import Queue +from functools import wraps +from traceback import print_exc + +__all__ = ['BackgroundThread', 'add_before_cb', 'add_before_cb'] + +class DelegateThread(threading.Thread): + def __init__(self, group=None, target=None, name=None, + args=(), kwargs=None, verbose=None): + threading.Thread.__init__(self, group, target, name, args, kwargs, + verbose) + + self.BeforeRun = funcs.Delegate() + self.AfterRun = funcs.Delegate() + +class BackgroundThread(DelegateThread): + def __init__(self, group=None, target=None, name=None, + args=(), kwargs=None, verbose=None): + + DelegateThread.__init__(self, group, target, name, args, kwargs, + verbose) + + self.setDaemon(True) + + global _before_run, _after_run + + self.BeforeRun[:] = _before_run + self.AfterRun[:] = _after_run + + +_before_run = [] +_after_run = [] + +def add_before_cb(cb): + assert callable(cb) + global _before_run + _before_run.append(cb) + +def add_after_cb(cb): + assert callable(cb) + global _after_run + _after_run.append(cb) + +from thread import get_ident + +class on_thread(object): + ''' + Used as a decorator: + + @on_thread('foo') + def do_foo_stuff(): + this_executes_on_thread_named_foo() + + And with calling a function directly: + + on_thread('foo').call(my_func, *args, **kwargs) + ''' + + threads = {} + lock = threading.RLock() + + def __init__(self, name, daemon = True): + self.name = name + self.daemon = daemon + + try: + self._id = self.threads[self.name].ident + except KeyError: + self._id = -1 + + @property + def thread(self): + # should already be locked. + + try: + return self._thread + except AttributeError: + with self.lock: + try: + self._thread = self.threads[self.name] + except KeyError: + self._thread = self.threads[self.name] = on_thread_thread(self.name, daemon=self.daemon) + self._thread.start() + self._id = self._thread.ident + + return self._thread + + def _done(self, thread_name): + # called internally by the thread + with self.lock: + self.threads.pop(thread_name) + + @property + def now(self): + "True if the current thread is this on_thread object's thread." + + return self._id == get_ident() + + def call(self, func, *a, **k): + assert hasattr(func, '__call__') + self.thread.queue(func, *a, **k) + + def __call__(self, func): + # So it acts as a decorator. + + @wraps(func) + def wrapper(*a, **k): + self.call(func, *a, **k) + + wrapper.on_thread = self + return wrapper + +try: + from wx import SEHGuard +except ImportError: + SEHGuard = lambda c: c() + +class on_thread_thread(BackgroundThread): + 'yet another consumer thread' + + def __init__(self, name, daemon=True): + BackgroundThread.__init__(self, name=name) + self.setDaemon(daemon) + self.work = Queue() + self.done = False + + def run(self): + self.BeforeRun() + try: + SEHGuard(self._consumer_loop) + finally: + self.AfterRun() + + def _consumer_loop(self): + while not self.done: + setattr(self, 'loopcount', getattr(self, 'loopcount', 0) + 1) + func, args, kwargs = self.work.get() + try: + func(*args, **kwargs) + except Exception: + print_exc() + self.work.task_done() + + on_thread(self.name)._done() + + def queue(self, func, *a, **k): + if __debug__: + import traceback + self.last_stack = traceback.format_stack() + self.work.put((func, a, k)) + + def join(self): + self.done = True + self.work.join() + diff --git a/digsby/src/util/threads/threadpool.py b/digsby/src/util/threads/threadpool.py new file mode 100644 index 0000000..085fafa --- /dev/null +++ b/digsby/src/util/threads/threadpool.py @@ -0,0 +1,385 @@ +"""Easy to use object-oriented thread pool framework. + +A thread pool is an object that maintains a pool of worker threads to perform +time consuming operations in parallel. It assigns jobs to the threads +by putting them in a work request queue, where they are picked up by the +next available thread. This then performs the requested operation in the +background and puts the results in a another queue. + +The thread pool object can then collect the results from all threads from +this queue as soon as they become available or after all threads have +finished their work. It's also possible, to define callbacks to handle +each result as it comes in. + +The basic concept and some code was taken from the book "Python in a Nutshell" +by Alex Martelli, copyright 2003, ISBN 0-596-00188-6, from section 14.5 +"Threaded Program Architecture". I wrapped the main program logic in the +ThreadPool class, added the WorkRequest class and the callback system and +tweaked the code here and there. Kudos also to Florent Aide for the exception +handling mechanism. + +Basic usage: + +>>> pool = TreadPool(poolsize) +>>> requests = makeRequests(some_callable, list_of_args, callback) +>>> [pool.putRequest(req) for req in requests] +>>> pool.wait() + +See the end of the module code for a brief, annotated usage example. + +Website : http://chrisarndt.de/en/software/python/threadpool/ +""" +from __future__ import with_statement +from util.introspect import use_profiler + +__all__ = [ + 'makeRequests', + 'NoResultsPending', + 'NoWorkersAvailable', + 'ThreadPool', + 'WorkRequest', + 'WorkerThread' +] + +__author__ = "Christopher Arndt" +__version__ = "1.2.3" +__revision__ = "$Revision: 1.5 $" +__date__ = "$Date: 2006/06/23 12:32:25 $" +__license__ = 'Python license' + +# standard library modules +import sys +import time +import threading +import Queue +import traceback +import logging +log = logging.getLogger('util.threadpool') + +from util.introspect import callany + +from .bgthread import BackgroundThread + +# exceptions +class NoResultsPending(Exception): + 'All work requests have been processed.' + pass + +class NoWorkersAvailable(Exception): + 'No worker threads available to process remaining requests.' + pass + +# classes +class WorkerThread(BackgroundThread): + """ + Background thread connected to the requests/results queues. + + A worker thread sits in the background and picks up work requests from + one queue and puts the results in another until it is dismissed. + """ + + def __init__(self, threadPool, **kwds): + """Set up thread in daemonic mode and start it immediatedly. + + requestsQueue and resultQueue are instances of Queue.Queue passed + by the ThreadPool class when it creates a new worker thread. + """ + + if 'name' not in kwds: + kwds['name'] = threading._newname('Wkr%d') + + BackgroundThread.__init__(self, **kwds) + self.setDaemon(1) + + self.workRequestQueue = threadPool.requestsQueue + + self._dismissed = threading.Event() + self.request_info = {} + self.start() + + def run(self): + self.BeforeRun() + try: + use_profiler(self, self._run) + finally: + self.AfterRun() + + def _run(self): + 'Repeatedly process the job queue until told to exit.' + + while not self._dismissed.isSet(): + setattr(self, 'loopcount', getattr(self, 'loopcount', 0) + 1) + + # thread blocks here, if queue empty + request = self.workRequestQueue.get() + if self._dismissed.isSet(): + # if told to exit, return the work request we just picked up + self.workRequestQueue.put(request) + break # and exit + + callable = request.callable + + # keep track of which requests we're running + self.request_info = dict(time_start = time.time(), finished = False) + if hasattr(callable, 'func_code'): + try: + code = callable.func_code + self.request_info.update(filename = code.co_filename, + lineno = code.co_firstlineno, + name = code.co_name) + except Exception, e: + traceback.print_exc() + del e + + try: + result = callable(*request.args, **request.kwds) + except Exception, e: + if request.verbose: + print >> sys.stderr, "threadpool: this error is being passed to exception handler (or being ignored):\n" + traceback.print_exc() + + request.exception = True + request.exception_instance = e + result = None + del e + + try: + if request.exception and request.exc_callback: + callany(request.exc_callback, request.exception_instance) + if request.callback and not \ + (request.exception and request.exc_callback): + callany(request.callback, result) + except Exception, e: + traceback.print_exc() + del e + + self.request_info['finished'] = time.time() + + # Make sure tracebacks and locals don't stay around + sys.exc_clear() + del result, request, callable + + def dismiss(self): + """Sets a flag to tell the thread to exit when done with current job. + """ + + self._dismissed.set() + + +class WorkRequest(object): + """A request to execute a callable for putting in the request queue later. + + See the module function makeRequests() for the common case + where you want to build several WorkRequests for the same callable + but with different arguments for each call. + """ + + def __init__(self, callable, args=None, kwds=None, requestID=None, + callback = None, exc_callback = None): + """Create a work request for a callable and attach callbacks. + + A work request consists of the a callable to be executed by a + worker thread, a list of positional arguments, a dictionary + of keyword arguments. + + A callback function can be specified, that is called when the results + of the request are picked up from the result queue. It must accept + two arguments, the request object and the results of the callable, + in that order. If you want to pass additional information to the + callback, just stick it on the request object. + + You can also give a callback for when an exception occurs. It should + also accept two arguments, the work request and a tuple with the + exception details as returned by sys.exc_info(). + + requestID, if given, must be hashable since it is used by the + ThreadPool object to store the results of that work request in a + dictionary. It defaults to the return value of id(self). + """ + + if requestID is None: + self.requestID = id(self) + else: + try: + hash(requestID) + except TypeError: + raise TypeError("requestID must be hashable.") + self.requestID = requestID + self.exception = False + self.callback = callback + self.exc_callback = exc_callback + self.callable = callable + self.args = args or [] + self.kwds = kwds or {} + + def __repr__(self): + try: + return u'<%s callable = %r, callback = %r, exc_callback = %r, args = %r, kwds = %r>' % \ + (type(self).__name__, self.callable, self.callback, self.exc_callback, self.args, self.kwds) + except: + return u'' + + +class ThreadPool(object): + """A thread pool, distributing work requests and collecting results. + + See the module doctring for more information. + """ + requestsQueue = Queue.Queue() + + workers = [] + + def __init__(self, num_workers=0, q_size=0): + """Set up the thread pool and start num_workers worker threads. + + num_workers is the number of worker threads to start initialy. + If q_size > 0 the size of the work request queue is limited and + the thread pool blocks when the queue is full and it tries to put + more work requests in it (see putRequest method). + """ + self.requestsQueue.maxsize = q_size + self.createWorkers(num_workers) + + def createWorkers(self, num_workers): + """Add num_workers worker threads to the pool.""" + + for i in range(num_workers): + self.workers.append(WorkerThread(self)) + + def joinAll(self): + log.info('Dismissing all workers. %r tasks remaining on queue. self.workers = %r', self.requestsQueue.qsize(), self.workers) + for worker in self.workers: + log.info('worker %r running %r', worker, getattr(worker, 'request_info', None)) + worker.dismiss() + log.info('\t%r dismissed', worker) + + # wake up! + from .threadpool2 import threaded + threaded(lambda: None)() + log.info('Joining with all workers. %r tasks remaining on queue.', self.requestsQueue.qsize()) + for worker in self.workers: + worker.join() + log.info('\t%r joined', worker) + + def dismissWorkers(self, num_workers): + """Tell num_workers worker threads to quit after their current task. + """ + + for i in range(min(num_workers, len(self.workers))): + worker = self.workers.pop() + worker.dismiss() + + def putRequest(self, request, block=True, timeout=0): + """Put work request into work queue and save its id for later.""" + + assert isinstance(request, WorkRequest) + + self.requestsQueue.put(request, block, timeout) + + def wait(self): + """Wait for results, blocking until all have arrived.""" + + while 1: + try: + self.poll(True) + except NoResultsPending: + break + +# helper functions +def makeRequests(callable, args_list, callback=None, exc_callback=None): + """Create several work requests for same callable with different arguments. + + Convenience function for creating several work requests for the same + callable where each invocation of the callable receives different values + for its arguments. + + args_list contains the parameters for each invocation of callable. + Each item in 'args_list' should be either a 2-item tuple of the list of + positional arguments and a dictionary of keyword arguments or a single, + non-tuple argument. + + See docstring for WorkRequest for info on callback and exc_callback. + """ + + requests = [] + for item in args_list: + if isinstance(item, tuple): + requests.append( + WorkRequest(callable, item[0], item[1], callback=callback, + exc_callback=exc_callback) + ) + else: + requests.append( + WorkRequest(callable, [item], None, callback=callback, + exc_callback=exc_callback) + ) + return requests + +################ +# USAGE EXAMPLE +################ + +if __name__ == '__main__': + import random + + # the work the threads will have to do (rather trivial in our example) + def do_something(data): + time.sleep(random.randint(1,5)) + result = round(random.random() * data, 5) + # just to show off, we throw an exception once in a while + if result > 3: + raise RuntimeError("Something extraordinary happened!") + return result + + # this will be called each time a result is available + def print_result(request, result): + print "**Result: %s from request #%s" % (result, request.requestID) + + # this will be called when an exception occurs within a thread + def handle_exception(request, exc_info): + print "Exception occured in request #%s: %s" % \ + (request.requestID, exc_info[1]) + + # assemble the arguments for each job to a list... + data = [random.randint(1,10) for i in range(20)] + # ... and build a WorkRequest object for each item in data + requests = makeRequests(do_something, data, print_result, handle_exception) + + # or the other form of args_lists accepted by makeRequests: ((,), {}) + data = [((random.randint(1,10),), {}) for i in range(20)] + requests.extend( + makeRequests(do_something, data, print_result, handle_exception) + ) + + # we create a pool of 3 worker threads + main = ThreadPool(3) + + # then we put the work requests in the queue... + for req in requests: + main.putRequest(req) + print "Work request #%s added." % req.requestID + # or shorter: + # [main.putRequest(req) for req in requests] + + # ...and wait for the results to arrive in the result queue + # by using ThreadPool.wait(). This would block until results for + # all work requests have arrived: + # main.wait() + + # instead we can poll for results while doing something else: + i = 0 + while 1: + try: + main.poll() + print "Main thread working..." + time.sleep(0.5) + if i == 10: + print "Adding 3 more worker threads..." + main.createWorkers(3) + i += 1 + except KeyboardInterrupt: + print "Interrupted!" + break + except NoResultsPending: + print "All results collected." + break diff --git a/digsby/src/util/threads/threadpool2.py b/digsby/src/util/threads/threadpool2.py new file mode 100644 index 0000000..2beed8b --- /dev/null +++ b/digsby/src/util/threads/threadpool2.py @@ -0,0 +1,73 @@ +from .threadpool import ThreadPool, WorkRequest +from functools import wraps +from util.callbacks import callsback +from traceback import print_exc +from threading import Lock + +from logging import getLogger; log = getLogger('threadpool2') + +__all__ = ['threaded', 'threaded_exclusive'] + +def threaded(func): + @wraps(func) + @callsback + def wrapper(*a, **kws): + callback = kws.pop('callback') + requestID = kws.pop('requestID', None) + req = WorkRequest(func, args=a, kwds=kws, requestID=requestID, + callback=callback.success, + exc_callback=callback.error) + req.verbose = wrapper.verbose + + ThreadPool().putRequest(req) + + wrapper.verbose = True + return wrapper + +def threaded_exclusive(func): + ''' + Ensures that "func" is only running on one threadpool thread at a time. + + If you call "func" while it's running 5 times, it will run once more + after it is finished -- there is not a 1-1 correspondence between + the number of calls and the number of runs. + ''' + assert hasattr(func, '__call__') + + func._exclusive_count = 0 + running_lock = Lock() + count_lock = Lock() + + @wraps(func) + def wrapper(*a, **k): + count_lock.acquire(True) + if not running_lock.acquire(False): + # another thread is running -- increment the count so that + # we know we need to run again + func._exclusive_count += 1 + count_lock.release() + else: + try: + old_count = func._exclusive_count + count_lock.release() + + try: + func(*a, **k) + except Exception: + print_exc() + + # compare old_count with the count now. if it's different, + # execute the function again + count_lock.acquire(True) + if old_count != func._exclusive_count: + count_lock.release() + + # thunk to threaded again to avoid any stack limits + threaded(wrapper)(*a, **k) + else: + count_lock.release() + finally: + running_lock.release() + + return threaded(wrapper) + diff --git a/digsby/src/util/threads/timeout_thread.py b/digsby/src/util/threads/timeout_thread.py new file mode 100644 index 0000000..76ce671 --- /dev/null +++ b/digsby/src/util/threads/timeout_thread.py @@ -0,0 +1,523 @@ +''' +Timers which run on a background thread, and execute your callback function on that background thread. + +Timer intervals are in seconds! +''' + +from __future__ import with_statement +from itertools import count +from operator import attrgetter +from threading import Condition +from peak.util.imports import whenImported +import sys +from util import default_timer +from logging import getLogger; log = getLogger('timeout_thread') +from traceback import print_exc +from os.path import split as pathsplit +from functools import wraps + +TR = None +CV = Condition() + +from heapq import heappush, heapify, heappop + +from .bgthread import BackgroundThread + +__all__ = ['TimeOut', 'Timer', 'call_later', 'ResetTimer', 'RepeatTimer', 'delayed_call'] + +class TimeRouter(BackgroundThread): + ''' + A thread to run Timers and occasional repetetive actions. + This implements a single thread for all of them. + ''' + + ids = count() + + def __init__(self, cv): + BackgroundThread.__init__(self, name = 'TimeRouter') + + self.count = self.ids.next() + self.done = False + self.cv = cv + + self.timeouts = [] + + def add(self, timeout): + ''' + Adds a TimeOut object to the internal queue + + @param timeout: an instance of TimeOut + @type timeout: TimeOut + ''' + with self.cv: + timeout.compute_timeout() + if timeout not in self.timeouts: + heappush(self.timeouts, timeout) + + self.cv.notifyAll() + + def stop(self): + with self.cv: + self.done = True + del TR.timeouts[:] + TimeRouter.add = Null + TimeOut.start = Null + self.cv.notifyAll() + + join = stop # WARNING: DOES NOT ACTUALLY JOIN + + def resort(self): + ''' + Cause the heap to resort itself. + ''' + with self.cv: + heapify(self.timeouts) + self.cv.notifyAll() + + + def run(self): + from util.introspect import use_profiler + self.BeforeRun() + try: + res = use_profiler(self, self._run) + finally: + self.AfterRun() + return res + + def _run(self): + with self.cv: + while not self.done: + setattr(self, 'loopcount', getattr(self, 'loopcount', 0) + 1) + timeouts = self.timeouts + if not timeouts: #this is so we can start the thread and + #then add stuff to it + self.cv.wait() + if self.done: break + + if len(timeouts) > 1000: + log.warning('um thats a lot of timeouts: %d', len(timeouts)) + + for x in timeouts: x.compute_timeout() + + heapify(timeouts) # timeouts are maintained in a sorted order in a heap + while timeouts and timeouts[0].finished(): + setattr(self, 'loopcount', getattr(self, 'loopcount', 0) + 1) + heappop(timeouts) + + if not timeouts: + t = None + break + + t = timeouts[0].compute_timeout() + + while t <= 0: + setattr(self, 'loopcount', getattr(self, 'loopcount', 0) + 1) + front = heappop(timeouts) + + if not front.finished(): + self.cv.release() + try: + front.process() + except Exception, e: + front.finished = lambda *a, **k: True + print_exc() + log.log(100, "caught exception in process, FIX this NOW!: %r, %r", front, e) + del e + finally: + self.cv.acquire() + if self.done: break + + if not front.finished(): + front.compute_timeout() + heappush(timeouts, front) + + if not timeouts: + t = None + break + + t = timeouts[0].compute_timeout() + if self.done: break + if t is None: + break + + self.cv.wait(t + t/100) + if self.done: break + + # clear any tracebacks + sys.exc_clear() + + #we're done, so take us out of action, + #and then release the lock + global TR + log.info('TimeRouter %r id(0x%x) is done', TR, id(TR)) + TR = None + +def join(): + 'Ends the timeout thread.' + + global TR, CV + with CV: + if TR is not None: + TR.join() + +class TimeOut(object): + ''' + The base class that is used by TimeRouter. + in a nutshell: if TimeRouter is a queue, everything in the queue should be + a TimeOut + ''' + + def __init__(self): + global CV + self._cv = CV + self._started = False + self._finished = False + + def __hash__(self): + return hash(id(self)) + + def start(self): + global TR + with self._cv: + if TR is None: + TR = TimeRouter(self._cv) + TR.start() + self._started = True + self._finished = False + TR.add(self) + self._cv.notifyAll() + + def stop(self): + with self._cv: + self._finished = True + self._cv.notifyAll() + if getattr(self, '_verbose', True): + log.debug("%r done.", self) + + def compute_timeout(self): + ''' + Returns the amount of time from now after which process should be called + + Implementing classes must set self._last_computed to this value before + the method returns + ''' + raise NotImplementedError + + last_computed = property(attrgetter('_last_computed')) + + def started(self): + return self._started + + def finished(self): + return self._finished + + def process(self): + ''' + The method where things happen + + Implementing classes which are done after this call should + call self.stop() before this method exits + ''' + raise NotImplementedError + + def __cmp__(self, other): + ret = self.last_computed - other.last_computed + if ret > 0: ret = 1 + elif ret < 0: ret = -1 + else: ret = cmp(hash(self), hash(other)) + return ret + +class Timer(TimeOut): + def __init__(self, interval, function, *args, **kwargs): + assert callable(function) + int(interval) + self._interval = interval + self._func = function + self._args = args + self._kwargs = kwargs + + self._called_from = self._getcaller() + TimeOut.__init__(self) + + def _getcaller(self): + ''' + Grab the name, filename, and line number of the function that created + this Timer. + ''' + + f = sys._getframe(2) + caller_name = f.f_code.co_name + filename = pathsplit(f.f_code.co_filename)[-1] + linenumber = f.f_code.co_firstlineno + self.called_from = '%s:%s:%s' % (filename, caller_name, linenumber) + + + def __repr__(self): + from util import funcinfo + return '<%s (from %s), callable is %s>' % (self.__class__.__name__, self.called_from, funcinfo(self._func)) + + def start(self): + self.done_at = default_timer() + self._interval + TimeOut.start(self) + + def compute_timeout(self): + self._last_computed = self.done_at - default_timer() + return self._last_computed + + def cancel(self): + self.stop() + + def process(self): + self.stop() + self._func(*self._args, **self._kwargs) + + def isAlive(self): + return self.started() and not self.finished() + + def stop(self): + with self._cv: + self.done_at = default_timer() + self.compute_timeout() + TimeOut.stop(self) + + @property + def remaining(self): + return self.done_at - default_timer() + + +def call_later(interval, function, *a, **k): + t = Timer(interval, function, *a, **k) + t.start() + return t + + +class ResetTimer(Timer): + ''' + A timer that can be reset + ''' + def __init__(self, *a, **k): + Timer.__init__(self, *a, **k) + self.waiting = False + + def compute_timeout(self): + if self.waiting: + self._last_computed = default_timer() + 5 + return self._last_computed + else: + return Timer.compute_timeout(self) + + def process(self): + self._func(*self._args, **self._kwargs) + self.waiting = True + + def temp_override(self, new_time): + with self._cv: + self.done_at = default_timer() + new_time + self._cv.notifyAll() + + def reset(self, new_time = None): + with self._cv: + if new_time is not None: + self._interval = new_time + + self.waiting = False + self.done_at = default_timer() + self._interval + + if self.finished(): + self.start() + else: + self._cv.notifyAll() +# else: +# global TR, CV +# with CV: +# if TR is None: +# self.start() +# else: +# TR.resort() + +class RepeatTimer(Timer): + + def __init__(self, *a, **k): + Timer.__init__(self, *a, **k) + self.paused = None + + #def __repr__(self): + #if hasattr(self, 'done_at'): + # return '' % self.compute_timeout() + #else: + # return '' % id(self) + + def compute_timeout(self): + if self.paused is not None: + self._last_computed = self.paused + return self._last_computed + else: + self._last_computed = self.done_at - default_timer() + return self._last_computed + + def pause(self): + 'pause the countdown' + with self._cv: + self.paused = self.compute_timeout() or .01 + + def unpause(self): + 'resume the countdown' + with self._cv: + assert self.paused is not None, 'must be paused to unpause' + self.done_at = self.paused + default_timer() + self.paused = None + + def temp_override(self, new_time): + 'set the time remaining to new_time' + with self._cv: + self.done_at = default_timer() + new_time + self._cv.notifyAll() + + def temp_reset(self, new_time): + 'set the time remaining to new_time, start/unpause the timer if stopped/paused' + with self._cv: + self.paused = None + self.done_at = default_timer() + new_time + + if not self.isAlive(): + TimeOut.start(self) + else: + self._cv.notifyAll() + + def process(self): + self._func(*self._args, **self._kwargs) + self.done_at = default_timer() + self._interval + + def reset(self, new_time = None): + ''' + reset, timer will go off in new_time or current interval + starts the timer if stopped/paused + ''' + with self._cv: + if new_time is not None: + self._interval = new_time + + self.paused = None + self.done_at = default_timer() + self._interval + + if self.finished(): + self.start() + else: + self._cv.notifyAll() +# else: +# global TR, CV +# with CV: +# if TR is None: +# self.start() +# else: +# TR.resort() + def stop(self): + ''' + turns the timer off + ''' + with self._cv: + self.paused = None + Timer.stop(self) + +def delayed_call(func, seconds, wxonly = False): + ''' + Function wrapper to make function invocation only happen after so many seconds. + + Recalling the function will set the timer back to the original "seconds" value. + ''' + + assert callable(func) + + def ontimer(*a, **k): + func._rtimer.stop() + func(*a, **k) + + @wraps(func) + def wrapper(*a, **k): + try: + timer = func._rtimer + except AttributeError: + # timer is being created for the first time. + if wxonly: + import wx + timer = func._rtimer = wx.PyTimer(lambda: ontimer(*a, **k)) + timer.start = timer.reset = lambda s=seconds*1000, timer=timer: timer.Start(s) + timer.stop = timer.Stop + else: + timer = func._rtimer = ResetTimer(seconds, lambda: ontimer(*a, **k)) + + timer.start() + else: + # timer already exists. + timer.reset() + + if wxonly: + timer.notify = lambda: ontimer(*a, **k) + + return wrapper + +def TimeRouterCallLater(*a, **k): + t = Timer(0, *a, **k) + t._verbose = False + t.start() + +def wakeup(): + TimeRouterCallLater(lambda: None) + +def _register_call_later(callbacks): + callbacks.register_call_later('TimeRouter', TimeRouterCallLater) +whenImported('util.callbacks', _register_call_later) + +if __name__ == "__main__": + from util import CallCounter + class WaitNSeconds(TimeOut): + + def __init__(self, seconds, name): + TimeOut.__init__(self) + self._finished = False + self.seconds = seconds + self.name = name + self.totaltime = 0 + self.cc = CallCounter(4, self.stop) + self.done_at = default_timer() + seconds + + def compute_timeout(self): + self._last_computed = self.done_at - default_timer() + return self._last_computed + + def process(self): + x = default_timer() - self.done_at + self.seconds + self.totaltime += x + print "%s done, waited %f, total:%f" % (self.name, x, self.totaltime) + self.done_at = default_timer() + self.seconds + self.cc() + + + + one = WaitNSeconds(1, "one") + two = WaitNSeconds(3, "two") + three = WaitNSeconds(3, "three") + two.done_at = three.done_at + one.start() + two.start() + three.start() + TR.join() + + def cb(): + print 'the time is now:',default_timer() + _5sec = ResetTimer(5, cb) + _2sec = ResetTimer(2, cb) + _5sec.start() + _2sec.start() + from time import sleep + for i in range(5): + sleep(2) + _2sec.reset() + print 'reset 2' + + _5sec.reset() + print 'reset 5' + sleep(6) + _5sec.stop() + _2sec.stop() + TR.join() diff --git a/digsby/src/util/urlhandler.py b/digsby/src/util/urlhandler.py new file mode 100644 index 0000000..98cbb9f --- /dev/null +++ b/digsby/src/util/urlhandler.py @@ -0,0 +1,32 @@ +import wx +from logging import getLogger; log = getLogger('urlhandler'); info = log.info +if 'wxMSW' in wx.PlatformInfo: + + + def register_protocol(protocol): + + key = _winreg.OpenKeyEx(_winreg.HKEY_CURRENT_USER, #@UndefinedVariable + "HKEY_CLASSES_ROOT\\%s" % protocol,0, + _winreg.KEY_ALL_ACCESS) #@UndefinedVariable + + # an example of how to do this... + ''' +[HKEY_CLASSES_ROOT\news] +@="URL:news Protocol" +"URL Protocol"="" +"EditFlags"=hex:02,00,00,00 + +[HKEY_CLASSES_ROOT\news\DefaultIcon] +@="\"C:\\Xnews\\Xnews.exe\"" + +[HKEY_CLASSES_ROOT\news\shell] + +[HKEY_CLASSES_ROOT\news\shell\open] + +[HKEY_CLASSES_ROOT\news\shell\open\command] +@="\"C:\\Xnews\\Xnews.exe\" /url=\"%1\"" +''' + + +else: + log.warning('No URL handling implementation for this platform.') \ No newline at end of file diff --git a/digsby/src/util/urllib2_file.py b/digsby/src/util/urllib2_file.py new file mode 100644 index 0000000..d7da7e0 --- /dev/null +++ b/digsby/src/util/urllib2_file.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python +#### +# Version: 0.2.0 +# - UTF-8 filenames are now allowed (Eli Golovinsky)
    +# - File object is no more mandatory, Object only needs to have seek() read() attributes (Eli Golovinsky)
    +# +# Version: 0.1.0 +# - upload is now done with chunks (Adam Ambrose) +# +# Version: older +# THANKS TO: +# bug fix: kosh @T aesaeion.com +# HTTPS support : Ryan Grow +# Copyright (C) 2004,2005,2006 Fabien SEISEN +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# you can contact me at: +# http://fabien.seisen.org/python/ +# +# Also modified by Adam Ambrose (aambrose @T pacbell.net) to write data in +# chunks (hardcoded to CHUNK_SIZE for now), so the entire contents of the file +# don't need to be kept in memory. + +""" +enable to upload files using multipart/form-data + +idea from: +upload files in python: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 + +timeoutsocket.py: overriding Python socket API: + http://www.timo-tasi.org/python/timeoutsocket.py + http://mail.python.org/pipermail/python-announce-list/2001-December/001095.html + +import urllib2_files +import urllib2 +u = urllib2.urlopen('http://site.com/path' [, data]) + +data can be a mapping object or a sequence of two-elements tuples +(like in original urllib2.urlopen()) +varname still need to be a string and +value can be string of a file object +eg: + ((varname, value), + (varname2, value), + ) + or + { name: value, + name2: value2 + } + +""" + +from urllib2 import URLError +from urllib import splittype +from urllib import splithost +from httplib import NotConnected +from urllib import addinfourl + +import os +import socket +import sys +import stat +import mimetypes +import mimetools +import httplib +import urllib +import urllib2 + +CHUNK_SIZE = 65536 + +def get_content_type(filename): + return mimetypes.guess_type(filename)[0] or 'application/octet-stream' + +# if sock is None, juste return the estimate size +def send_data(v_vars, v_files, boundary, sendfunc=None): + l = 0 + for (k, v) in v_vars: + buffer='' + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"\r\n' % k + buffer += '\r\n' + buffer += v + '\r\n' + if sendfunc: + sendfunc(buffer) + l += len(buffer) + for (k, v) in v_files: + fd = v + if hasattr(fd, 'len'): + file_size = fd.len + else: + file_size = os.fstat(fd.fileno())[stat.ST_SIZE] + if isinstance(k, tuple): + k, name = k + else: + name = fd.name.split('/')[-1] + if isinstance(name, unicode): + name = name.encode('UTF-8') + buffer='' + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' \ + % (k, name) + buffer += 'Content-Type: %s\r\n' % get_content_type(name) + buffer += 'Content-Length: %s\r\n' % file_size + buffer += '\r\n' + + l += len(buffer) + if sendfunc: + sendfunc(buffer) + if hasattr(fd, 'seek'): + fd.seek(0) + while True: + chunk = fd.read(CHUNK_SIZE) + if not chunk: break + sendfunc(chunk) + + l += file_size + buffer='\r\n' + buffer += '--%s--\r\n' % boundary + buffer += '\r\n' + if sendfunc: + sendfunc(buffer) + l += len(buffer) + return l + +def do_request_(self, request): + host = request.get_host() + if not host: + raise URLError('no host given') + + data = request.get_data() + v_files=[] + v_vars=[] + if request.has_data() and not isinstance(data, str): #POST + if hasattr(data, 'items'): + data = data.items() + else: + try: + if len(data) and not isinstance(data[0], tuple): + raise TypeError + except TypeError: + _ty, _va, tb = sys.exc_info() + try: + raise TypeError, "not a valid non-string sequence or mapping object: %r" % type(data), tb + finally: + del tb + for (k, v) in data: + if hasattr(v, 'read'): + v_files.append((k, v)) + else: + v_vars.append( (k, v) ) + boundary = mimetools.choose_boundary() + request.boundary = boundary + request.v_files = v_files + request.v_vars = v_vars + # no file ? convert to string + if len(v_vars) > 0 and len(v_files) == 0: + request.data = data = urllib.urlencode(v_vars) + v_files[:]=[] + v_vars[:]=[] + + if request.has_data(): + if not 'Content-type' in request.headers: + if len(v_files) > 0: + l = send_data(v_vars, v_files, boundary) + request.add_unredirected_header('Content-Type', + 'multipart/form-data; boundary=%s' % boundary) + request.add_unredirected_header('Content-length', str(l)) + else: + request.add_unredirected_header('Content-type', + 'application/x-www-form-urlencoded') + if not 'Content-length' in request.headers: + request.add_unredirected_header('Content-length', '%d' % len(data)) + + _scheme, sel = splittype(request.get_selector()) + sel_host, _sel_path = splithost(sel) + if not request.has_header('Host'): + request.add_unredirected_header('Host', sel_host or host) + for name, value in self.parent.addheaders: + name = name.capitalize() + if not request.has_header(name): + request.add_unredirected_header(name, value) + + return request + +urllib2.AbstractHTTPHandler.do_request_ = do_request_ +old_open = urllib2.AbstractHTTPHandler.do_open + +def do_open(self, http_class, req): + req = do_request_(self, req) + host = req.get_host() + if not host: + raise URLError('no host given') + + h = http_class(host) # will parse host:port + h.set_debuglevel(self._debuglevel) + + headers = dict(req.headers) + headers.update(req.unredirected_hdrs) + headers["Connection"] = "close" + headers = dict( + (name.title(), val) for name, val in headers.items()) + + if req.has_data() and not isinstance(req.data, str): + reqdata = req + else: + reqdata = req.data + + try: + h.request(req.get_method(), req.get_selector(), reqdata, headers) + r = h.getresponse() + except socket.error, err: # XXX what error? + raise URLError(err) + + r.recv = r.read + fp = socket._fileobject(r, close=True) + + resp = addinfourl(fp, r.msg, req.get_full_url()) + resp.code = r.status + resp.msg = r.reason + return resp + +urllib2.AbstractHTTPHandler.do_open = do_open + +def _send_request(self, method, url, body, headers): + # honour explicitly requested Host: and Accept-Encoding headers + header_names = dict.fromkeys([k.lower() for k in headers]) + skips = {} + if 'host' in header_names: + skips['skip_host'] = 1 + if 'accept-encoding' in header_names: + skips['skip_accept_encoding'] = 1 + + self.putrequest(method, url, **skips) + + if isinstance(body, str): + if body and ('content-length' not in header_names): + self.putheader('Content-Length', str(len(body))) + for hdr, value in headers.iteritems(): + self.putheader(hdr, value) + self.endheaders() + + if body: + self.send(body) + +httplib.HTTPConnection._send_request = _send_request + +def send(self, str): + """Send `str' to the server.""" + if self.sock is None: + if self.auto_open: + self.connect() + else: + raise NotConnected() + + # send the data to the server. if we get a broken pipe, then close + # the socket. we want to reconnect when somebody tries to send again. + # + # NOTE: we DO propagate the error, though, because we cannot simply + # ignore the error... the caller will know if they can retry. + if self.debuglevel > 0: + print "send:", repr(str) + try: + if hasattr(str, 'boundary'): + boundary = str.boundary + v_files = str.v_files + v_vars = str.v_vars + send_data(v_vars, v_files, boundary, self.sock.sendall) + else: + self.sock.sendall(str) + except socket.error, v: + if v[0] == 32: # Broken pipe + self.close() + raise + + +httplib.HTTPConnection.send = send + +# mainly a copy of HTTPHandler from urllib2 +#class newHTTPHandler(urllib2.BaseHandler): +# def http_open(self, req): +# return self.do_open(httplib.HTTP, req) +# +# def do_open(self, http_class, req): +# data = req.get_data() +# v_files=[] +# v_vars=[] +# # mapping object (dict) +# if isinstance(data, unicode): +# data = data.encode('UTF-8') +# if req.has_data() and not isinstance(data, str): +# if hasattr(data, 'items'): +# data = data.items() +# else: +# try: +# if len(data) and not isinstance(data[0], tuple): +# raise TypeError +# except TypeError: +# ty, va, tb = sys.exc_info() +# raise TypeError, "not a valid non-string sequence or mapping object", tb +# +# for (k, v) in data: +# if hasattr(v, 'read'): +# v_files.append((k, v)) +# else: +# v_vars.append( (k, v) ) +# # no file ? convert to string +# if len(v_vars) > 0 and len(v_files) == 0: +# data = urllib.urlencode(v_vars) +# v_files=[] +# v_vars=[] +# host = req.get_host() +# if not host: +# raise urllib2.URLError('no host given') +# +# h = http_class(host) # will parse host:port +# if req.has_data(): +# print 'doing post' +# h.putrequest('POST', req.get_selector()) +# if not 'Content-type' in req.headers: +# if len(v_files) > 0: +# boundary = mimetools.choose_boundary() +# l = send_data(v_vars, v_files, boundary) +# h.putheader('Content-Type', +# 'multipart/form-data; boundary=%s' % boundary) +# h.putheader('Content-length', str(l)) +# else: +# h.putheader('Content-type', +# 'application/x-www-form-urlencoded') +# if not 'Content-length' in req.headers: +# h.putheader('Content-length', '%d' % len(data)) +# else: +# print 'doing get ', req.get_selector() +# h.putrequest('GET', req.get_selector()) +# +# scheme, sel = urllib.splittype(req.get_selector()) +# sel_host, sel_path = urllib.splithost(sel) +# h.putheader('Host', sel_host or host) +# for name, value in self.parent.addheaders: +# name = name.capitalize() +# if name not in req.headers: +# h.putheader(name, value) +# for k, v in req.headers.items(): +# h.putheader(k, v) +# # httplib will attempt to connect() here. be prepared +# # to convert a socket error to a URLError. +# try: +# h.endheaders() +# except socket.error, err: +# raise urllib2.URLError(err) +# +# if req.has_data(): +# if len(v_files) >0: +# l = send_data(v_vars, v_files, boundary, h) +# elif len(v_vars) > 0: +# # if data is passed as dict ... +# data = urllib.urlencode(v_vars) +# h.send(data) +# else: +# # "normal" urllib2.urlopen() +# h.send(data) +# +# code, msg, hdrs = h.getreply() +# fp = h.getfile() +# if code == 200: +# resp = urllib.addinfourl(fp, hdrs, req.get_full_url()) +# resp.code = code +# resp.msg = msg +# return resp +# else: +# return self.parent.error('http', req, fp, code, msg, hdrs) +# +#urllib2._old_HTTPHandler = urllib2.HTTPHandler +#urllib2.HTTPHandler = newHTTPHandler +# +#class newHTTPSHandler(newHTTPHandler): +# def https_open(self, req): +# return self.do_open(httplib.HTTPS, req) +# +#urllib2.HTTPSHandler = newHTTPSHandler +# +#if __name__ == '__main__': +# import getopt +# import urllib2 +# import urllib2_file +# import string +# import sys +# +# def usage(progname): +# print """ +#SYNTAX: %s -u url -f file [-v] +#""" % progname +# +# try: +# opts, args = getopt.getopt(sys.argv[1:], 'hvu:f:') +# except getopt.GetoptError, errmsg: +# print "ERROR:", errmsg +# sys.exit(1) +# +# v_url = '' +# v_verbose = 0 +# v_file = '' +# +# for name, value in opts: +# if name in ('-h',): +# usage(sys.argv[0]) +# sys.exit(0) +# elif name in ('-v',): +# v_verbose += 1 +# elif name in ('-u',): +# v_url = value +# elif name in ('-f',): +# v_file = value +# else: +# print "invalid argument:", name +# sys.exit(2) +# +# error = 0 +# if v_url == '': +# print "need -u" +# error += 1 +# if v_file == '': +# print "need -f" +# error += 1 +# +# if error > 0: +# sys.exit(3) +# +# fd = open(v_file, 'r') +# data = { +# 'filename' : fd, +# } +# # u = urllib2.urlopen(v_url, data) +# req = urllib2.Request(v_url, data, {}) +# try: +# u = urllib2.urlopen(req) +# except urllib2.HTTPError, errobj: +# print "HTTPError:", errobj.code +# +# else: +# buf = u.read() +# print "OK" diff --git a/digsby/src/util/urlprotocol.py b/digsby/src/util/urlprotocol.py new file mode 100644 index 0000000..373c1c0 --- /dev/null +++ b/digsby/src/util/urlprotocol.py @@ -0,0 +1,146 @@ +''' +URL protocol handling. + +MyProtocol = { + name = 'DigsbyPlugin' + protocol = 'digsbyplugin' # digsbyplugin://path/to/plugin + icon = 'c:\\someicon.ico' + command = 'c:\\Program Files\\Digsby\\Digsby.exe' +} + +>> import urlprotocol +>> urlprotocol.register(MyProtocol) +>> assert urlprotocol.isRegistered(MyProtocol) +>> urlprotocol.unregister(MyProtocol) +''' + +__all__ = 'isRegistered register unregister'.split() + +import platform +from traceback import print_exc +from warnings import warn +platform = platform.system().lower() +platform = {'microsoft': 'windows', + '4nt': 'windows', + 'darwin': 'darwin', + 'linux': 'linux', + }.get(platform, 'windows') + +def attrget(obj, attr): + try: return obj[attr] + except TypeError: return obj.name + +def platform_call(name, *a, **k): + funcname = name + '_' + platform + + try: + func = globals()[funcname] + except KeyError: + warn('platform %s not implemented (%s)' % (platform, funcname)) + else: + return func(*a, **k) + +def isRegistered(urlprotocol, system = True): + ''' + Returns if the URL protocol handler specified in urlprotocol is currently + registered. + ''' + + return platform_call('isDefault', urlprotocol, system = system) + +def get(protocol_name, system = True): + return platform_call('get', protocol_name, system = system) + +def register(urlprotocol, system = True): + + return platform_call('register', urlprotocol, system = system) + +def unregister(protocol_or_name, system = True): + name = protocol_or_name if isinstance(protocol_or_name, basestring) \ + else getattr(protocol_or_name, 'name', protocol_or_name['name']) + + return platform_call('unregister', name, system = system) + +## +## Windows +## + +def _init_windows(): + global reg + import _winreg as reg + +def get_windows(protocol_name, system = True): + from auxencodings import fuzzydecode + + key = reg.OpenKey(basekey(system), '%s\\shell\\open\\command' % protocol_name) + val = reg.EnumValue(key, 0)[1] + + # if what comes back is not already unicode, we have to guess :( + if isinstance(val, str): + val = fuzzydecode(reg.EnumValue(key, 0)[1], 'utf-16-be') + + return val + +def urlkey(protocolname, system = False, write = False): + try: + if system: + return reg.OpenKey(reg.HKEY_CLASSES_ROOT, '%s' % protocolname, + 0, reg.KEY_ALL_ACCESS if write else reg.KEY_READ) + else: + return reg.OpenKey(reg.HKEY_CURRENT_USER, 'Software\\Classes\\%s' % protocolname, + 0, reg.KEY_ALL_ACCESS if write else reg.KEY_READ) + except reg.error: + return None + +def basekey(system = False): + if system: + return reg.OpenKey(reg.HKEY_CLASSES_ROOT, '') + else: + return reg.OpenKey(reg.HKEY_CURRENT_USER, 'Software\\Classes') + +def keyToObj((obj, objtype)): + if objtype in (reg.REG_NONE): + return None + elif objtype in (reg.REG_SZ, reg.REG_EXPAND_SZ, reg.REG_RESOURCE_LIST, reg.REG_LINK, reg.REG_BINARY, reg.REG_DWORD, reg.REG_DWORD_LITTLE_ENDIAN, reg.REG_DWORD_BIG_ENDIAN, reg.REG_MULTI_SZ): + return obj + raise NotImplementedError, "Registry type 0x%08X not supported" % (objtype,) + +def register_microsoft(urlprotocol, system = True): + protocol = attrget(urlprotocol, 'protocol') + key = urlkey(protocol, system) + + if not key: + reg.CreateKey(basekey(system), 'Software\\Classes\\%s' % protocol) + + + +def isRegistered_microsoft(urlprotocol, system = True): + name = attrget(urlprotocol, 'protocol') + key = urlkey(name, system) + + # If there's no key at all, return False. + if not key: + return False + + +## +## Mac +## + +## TODO: Determine best way to load default mail program from URL +def _init_darwin(): + pass + +def _init_linux(): + pass + + #print keyToObj(reg.QueryValueEx(key, '')) + + +try: + platform_call('_init') +except Exception: + print_exc() + +if __name__ == '__main__': + print get('mailto') diff --git a/digsby/src/util/vec.py b/digsby/src/util/vec.py new file mode 100644 index 0000000..39c9dee --- /dev/null +++ b/digsby/src/util/vec.py @@ -0,0 +1,56 @@ +'A vector with n components.' + +from __future__ import division +from itertools import izip +import math + +class vector(list): + def __init__(self, *a): + if len(a) == 1: list.__init__(self, *a) + else: list.__init__(self, iter(a)) + + def __getslice__(self, i, j): return vector(list.__getslice__(i,j)) + + def __add__(self, v): return vector(x+y for x,y in izip(self, v)) + def __neg__(self): return vector(-x for x in self) + def __sub__(self, v): return vector(x-y for x,y in izip(self, v)) + def __mul__(self, o): + try: iter(o) + except: return vector(x*o for x in self) + else: return vector(x*y for x,y in izip(o)) + + def div(self, o): + try: iter(o) + except: return vector(x/o for x in self) + else: return vector(x*y for x,y in izip(o)) + + def __repr__(self): return 'vec' + repr(tuple(self)) + + @classmethod + def distance(cls, v1, v2): + return sum((y-x)**2 for x,y in izip(v1, v2)) ** 0.5 + + def to(self, other): + return vector.distance(self, other) + + @property + def length(self): + return vector.distance(self, (0,)*len(self)) + + @property + def normal(self): + try: + return self.div(self.length) + except ZeroDivisionError: + return vector((0,)*len(self)) + + @staticmethod + def zero(n = 2): + return vector((0,) * n) + + @property + def angle(self): + try: + return abs(math.atan(self[1] / self[0]) * 180.0 / math.pi) + except ZeroDivisionError: + return 90 diff --git a/digsby/src/util/xml_tag.py b/digsby/src/util/xml_tag.py new file mode 100644 index 0000000..b22eadd --- /dev/null +++ b/digsby/src/util/xml_tag.py @@ -0,0 +1,648 @@ +import urllib2 +import traceback +from xml.sax import make_parser +from xml.sax.handler import ContentHandler, DTDHandler, EntityResolver, feature_namespaces, \ + feature_external_ges, feature_external_pes +from xml.sax.saxutils import escape, unescape +from logging import getLogger +log = getLogger('xml_tag') + +__all__ = ['tag_parse', 'tag', 'post_xml', 'plist'] + + +ATTR_BLACKLIST = ('bogu5_123_aTTri8ute', 'mro', + 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', + 'func_globals', 'func_name') + +class LooseNSDict(dict): + def __contains__(self, thing): + return True + def __getitem__(self, thing): + try: + return dict.__getitem__(self, thing) + except KeyError: + dict.__setitem__(self, thing, 'unknown') + return 'unknown' + +def tag_parse(s, ns=None): + if ns is None: + ns = {} + elif not ns: + # don't care about namespaces + ns = LooseNSDict() + + h = TagHandler() + + if type(s) is unicode: + s = s.encode('utf-8') + + + try: + parser = make_parser() + + features=((feature_namespaces, 0), (feature_external_ges, 0), (feature_external_pes, 0)) + + for key, value in features: + parser.setFeature(key, value) + + parser.setContentHandler(h) + parser.feed(s, True) + except Exception: + traceback.print_exc() + log.error('bad xml: %r', s) + log.error('tag handler obj: %r', h) + log.error('handler root: %r', h.root) + raise + + h.root._source = s + assert h.root is not None + return h.root + +class TagHandler(ContentHandler, DTDHandler, EntityResolver): + def __init__(self): + ContentHandler.__init__(self) + #DTDHandler.__init__(self) # DTDHandler has no __init__ (?) + #EntityResolver.__init__(self) # neither does EntityResolver + self.data = '' + self.stack = [] + self.root = None + self.ns_dict = {} + + def characters(self, data): + #if data.strip(): + self.stack[-1](data) + + def startElement(self, name, attrs): + # take stupid mapping type object and + # make it a map, while taking stupid + # unicode strings and making them strings + + kwargs = {} + + for k,v in attrs.items(): + if ':' in k: + xmlns, ns = k.split(':', 1) + if xmlns == 'xmlns': + ns_uri = v + self.ns_dict[ns] = ns_uri + else: + assert xmlns in self.ns_dict, (xmlns, self.ns_dict) + kwargs[str(k)] = unicode(v) + else: + kwargs[str(k)] = unicode(v) + + if ':' in name: + ns, name = name.rsplit(':', 1) + name = (ns, name) + + t = tag(name, _ns_dict=self.ns_dict, **kwargs) + + if self.stack: + self.stack[-1](t) + else: + self.root = t + + self.stack.append(t) + + def endElement(self, name): + if ':' in name: + ns, name = name.rsplit(':', 1) + + + assert(name == self.stack[-1]._name), (name, self.stack[-1]._name) + + self.stack.pop() + + def startElementNS(self, name, attrs): + import warnings + warnings.warn("Namespaced elements and attributes are not officially supported!") + +# def ignorableWhitespace(self, whitespace): +# self.stack[-1](whitespace) + +class tag(object): + _pretty_sep = ' ' * 2 +# __ns_dict = {} +# @staticmethod +# def _set_ns_dict(new_ns): +# tag.__ns_dict = new_ns + + def __init__(self, _name, _cdata=u'', _ns_dict=None, **attrs): + + # _nd_dict of False will make tag not care about namespaces and return 'unknown' for the ones + # with no value present + + if isinstance(_name, basestring) and _name.strip().startswith('<'): + # Someone gave us xml, parse it and set our state + self.__dict__ = tag_parse(_name, _ns_dict).__dict__ + self._source = _name + return + else: + self._source = None + + if _ns_dict is None: + _ns_dict = {} + + if 'xml' not in _ns_dict: + _ns_dict['xml'] = 'http://www.w3.org' + + self.__ns_dict = _ns_dict + + self._cdata = unicode(_cdata) + if type(_cdata) is bool: + self._cdata = self._cdata.lower() + + self._children = [] + self._attrs = {} + self._attrns = {} + + + for k in attrs: + if ':' in k: + ns, attrname = k.rsplit(':',1) + + if ns == 'xmlns': + if attrname in self.__ns_dict: + assert self.__ns_dict[attrname] == k, (k, attrs[k],self.__ns_dict) + else: + self.__ns_dict[attrname] = attrs[k] + else: + self._attrns[ns] = self.__ns_dict[ns] + + self._attrs = attrs + + if isinstance(_name, basestring): + self._name = _name + self._ns = (None, None) + + if 'xmlns' in self._attrs: + self._ns = (True, self._attrs.pop('xmlns')) + + else: + assert len(_name) == 2 + ns, self._name = _name + if isinstance(ns, basestring): + assert ns in self.__ns_dict, (_name, self.__ns_dict) + else: + assert len(ns) == 2 + ns, ns_uri = ns + self.__ns_dict[ns] = ns_uri + + self._ns = (ns, self.__ns_dict[ns]) + + + def _find(self, *a, **k): + ''' + Returns a list of all tags that match the given criteria. + + _name : string, matches the name of the tag if provided. + _f : callable predicate. is given one argument (the tag) and if bool(result) is true, the tag matches. + **kwds: matches keywords against attributes of the tag. + ''' + results = [] + if self._match(*a, **k): + results.append(self) + + for res in (x._find(*a, **k) for x in self): + results.extend(res) + return results + + def _findOne(self, *a, **k): + ''' + Searches for a tag that matches the given criteria, returning the first. + + _name : string, matches the name of the tag if provided. + _f : callable predicate. is given one argument (the tag) and if bool(result) is true, the tag matches. + **kwds: matches keywords against attributes of the tag. + ''' + if self._match(*a, **k): + return self + + for x in self: + res = x._findOne(*a, **k) + if res is not None: + return res + + return None + + def _match(self, _name='', _f=lambda t:True, **kwds): + ''' + Attempt to match this tag against the provided criteria. + + _name : string, matches the name of the tag if provided. + _f : callable predicate. is given one argument (the tag) and if bool(result) is true, the tag matches. + **kwds: matches keywords against attributes of the tag. + ''' + if not self._name.startswith(_name): + return False + + if not _f(self): + return False + + for k,v in kwds.iteritems(): + if k not in self._attrs: + return False + if self._attrs[k] != v: + return False + + return True + + def _write_to(self, xml_handler): + xml_handler.startElement(self._name, self._attrs) + if self._children: + #children + [child.write_to(xml_handler) for child in self._children] + if self._cdata: + #characters + xml_handler.characters(self._cdata) + xml_handler.endElement(self._name) + + def _add_child(self, *args, **kwargs): + if '_ns_dict' not in kwargs: + kwargs['_ns_dict'] = self.__ns_dict + t = args[0] if isinstance(args[0], tag) else tag(*args, **kwargs) + self._children.append(t) + return self + + def _add_attrs(self, **kwargs): + self._attrs.update(kwargs) + return self + + def _add_cdata(self, data): + ''' + >>> t = tag('name') + >>> t._add_cdata('dot') + >>> str(t) + 'dot' + >>> t._add_cdata('Syntax') + >>> str(t) + 'dotSyntax' + >>> print t._to_xml() + + dotSyntax + + ''' + + if type(data) is bool: + data = unicode(data).lower() + + if not isinstance(data, basestring): + data = unicode(data) + + if type(data) is str: + data = unicode(data, 'utf-8') + + self._cdata += data + return self + + def __call__(self, *args): + for arg in args: + if isinstance(arg, tag): + self._add_child(arg) + elif isinstance(arg, (tuple, list, set)): + self._add_child(tag(*arg, **dict(_ns_dict=self.__ns_dict))) + else: + try: + self._add_cdata(arg) + except: + raise ValueError, 'Couldn\'t add child from %r' % arg + return self + + def __getattr__(self, attr): + if attr.startswith('_') or attr in ATTR_BLACKLIST: + return object.__getattribute__(self, attr) + + children = [child for child in self._children if child._name == attr] + if len(children) == 1: + children = children[0] + elif len(children) == 0: + children = tag(attr, _ns_dict = self.__ns_dict) + self._add_child(children) + + return children + + def __getitem__(self, index): + if isinstance(index, basestring): + # pretend to be a dictionary with attr:val pairs + return self._attrs[index] + + # pretend to be a list containing children + return self._children[index] + + def __setattr__(self, attr, val): + if attr.startswith('_'): + return object.__setattr__(self, attr, val) + + child = getattr(self, attr) + if isinstance(child, tag): + if hasattr(val, '_to_xml') and val is not child: + self._children[self._children.index(child)] = val + elif val is not child: + child._cdata = '' + child(val) + else: + #print 'val:',val + self._add_child(tag(attr, _ns_dict = self.__ns_dict)(val)) + + def __setitem__(self, index, val): + if isinstance(index, basestring): + # pretend to be a dictionary + self._attrs[index] = val + else: + # pretend to be a list + self._children[index] = val + + def __iadd__(self, other): + return self(other) + + def __iter__(self): + return iter(self._children) + + def __len__(self): + return len(self._children) + + def __contains__(self, name): + if isinstance(name, tag): + name = name._name + + return name in [c._name for c in self._children] + + def __hasattr__(self, attr): + if attr.startswith('_'): + return object.__hasattr__(self, attr) + return attr in self._attrs + + def _to_xml(self, depth=0, pretty=True, self_closing=True, return_list=False): + ''' + Not guaranteed to be valid xml...but pretty damn good so far! + ''' + ns, ns_uri = self._ns + + if ns is not True: + name = u'%s:%s' % (ns, self._name) if ns else self._name + else: + name = self._name + + indent = (u'\n' + self._pretty_sep*depth) * pretty + s = [indent, '<'] + + s.append(name) + + for k in self._attrs: + s.extend([' ', k, '="', unicode(self._attrs[k]), '"']) + + if depth == 0: + for ch_ns, ch_ns_uri in self._child_ns(): + if ch_ns is True: + s.extend([' xmlns="', ch_ns_uri, '"']) + else: + s.extend([' xmlns:', ch_ns, '="', ch_ns_uri,'"']) + + if not self._cdata and not self._children and self_closing: + s.append(' />') + return s if return_list else ''.join(s) + s.append('>') + cleancdata = escape(self._cdata.strip()) + if cleancdata: + s += [indent, self._pretty_sep*pretty,cleancdata] + + for child in self._children: + s.extend(child._to_xml(depth+1, pretty, self_closing, return_list=True)) + + s.extend([indent, '' ]) + + if return_list: + return s + else: + return u''.join(s) + + def _child_ns(self): + ns_s = [] + for ch in self._children: + ns_s.extend(ch._child_ns()) + +# for attr in self._attrs: +# if ':' in attr: +# ns, name = attr.rsplit(':',1) +# ns_s.insert(0, (ns,self._attrns[ns])) + for ns in self._attrns.items(): + ns_s.insert(0, ns) + +# if 'xmlns' in self._attrs: +# ns_s.insert(0, (True,self._attrs['xmlns'])) + + + ns_s.insert(0,self._ns) + # return all non-empty + return set(tup for tup in ns_s if all(tup)) + + def __repr__(self): + return '<%s object %s at %s>' % (self.__class__.__name__, + self._name, id(self)) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + return self._cdata + + def __int__(self): + return int(self._cdata) + + def __nonzero__(self): + if self._attrs and (self._attrs.get('xsi:nil',False) != 'true'): + return True + elif self._attrs: + # xsi:nil must be present and true, so return False + return False + + if self._children: + return True + + if self._cdata: + return True + + return False + + def _copy(self): + new = tag((self._ns, self._name), self._cdata, _ns_dict = self.__ns_dict, **self._attrs) + for child in self._children: + new._add_child(child._copy()) + return new + + def _recurse_ns(self, ns): + ''' + Set a namespace on this tag and all children. + + so to generate + + + + + you just do: + + >>> t = tag('parent') + >>> t._add_child(tag('ch1')) + >>> t._add_child(tag('ch2')) + >>> t._recurse_ns(('ds', "http://www.dotsyntax.com")) + >>> print t._to_xml() + + + + + ''' + if isinstance(ns, basestring): + assert ns in self.__ns_dict + ns = (ns, self.__ns_dict[ns]) + else: + assert len(ns) == 2 + self.__ns_dict[ns[0]] = ns[1] + + self._ns = ns + for ch in self._children: + ch._recurse_ns(ns) + +import util +@util.threaded +def post_xml(url, data = None, xmldecl = True, verbose=False, **headers): + ''' + Use HTTP POST to send XML to a URL. + + Optionally specify headers for urllib2 to use in the HTTP request. + Returns a tag object. (The XML response from the server). + ''' + +# class Request(urllib2.Request): +# def add_header(self, key, val): +# self.headers[key] = val + + if isinstance(data, basestring): + try: + tag_parse(data) + except: + raise + + if isinstance(data, tag): + data = data._to_xml(pretty = False).strip().encode('utf-8') + + if xmldecl and data and not data.startswith('" + data + + if type(data) is unicode: + data = data.encode('utf-8') + + #data = unicode(data,'utf-8') + headers.setdefault('Content-type','text/xml; charset=utf-8') + + req = urllib2.Request(url, data) + req.headers = headers # avoid Title-Casing of headers + + if verbose: + print '*'*80 + print req.headers + print data + print '*'*80 + + e = None + + try: + httpresp = urllib2.urlopen( req ) + except urllib2.HTTPError, e: + print 'Error with request', e + if e.fp is not None: + httpresp = e.fp + else: raise + + document = httpresp.read() + tag_result = tag_parse( document ) + + if (not tag_result) and e is not None: + raise e + + if verbose or 'Fault' in tag_result: + print httpresp.headers + print document + print '*'*80 + + if 'Fault' in tag_result: + raise SOAPException(tag_result) + + return tag_result + +plist_types = {'string': unicode, + 'integer': int, + 'true': lambda *a: True, + 'false': lambda *a: False} + +class SOAPException(Exception): + def __init__(self, t): + self.t = t + self.fault = fault = t.Body.Fault or t.Fault + faultstring = str(self.fault.faultstring) + + if fault.detail: + errorstring = str(fault.detail.errorstring) + Exception.__init__(self, faultstring, errorstring) + else: + Exception.__init__(self, faultstring) + + +def plist(xml): + 'Apple PLIST xml -> ordered dict' + + from util.primitives import odict + + d = odict() + + for child in tag(xml).dict: + contents = unicode(child) + + if child._name.lower() == 'key': + key = contents + else: + d[key] = plist_types[child._name](contents) + + return d + +def main(): + root = tag((('digsby','http://www.digsby.com'),'Root'),'some cdata', type='tag') + + root['font'] = 'Arial' + root.Header.Security += 'test','testdata' + print repr(root.Header.Security._children) + root.Header.AuthInfo += ('digsby','Password'), 'pword' + root.Header.Security += (('ps','longurl'),'HostingApp'), 'CLSID' + print repr(root.Header.Security._children) + print root.Header.Security._to_xml() + print '-'*80 + print root.Header.Security.HostingApp._to_xml() + print '-'*80 + root.Header.Security += ('ps','HostingApp'), 'CLSID2' + print root.Header.Security.HostingApp + print '-'*80 + root.Header.Security += ('ps','BinaryVersion'), 2 + root.Header.Security.TupledArg = 'whee' + print root.Header.Security._to_xml() + print '-'*80 + print root.Body._to_xml(self_closing=False) + print '-'*80 + print 'iter:' + for ch in root: + print ch._name + print '-'*80 + print 'original xml:' + print root._to_xml() + print '-'*80 + print 'parsed tag xml:' + t = tag_parse(root._to_xml()) + print t._to_xml() + print '-'*80 + print 'Parsed XML is equal to original XML?',root._to_xml() == t._to_xml() + xml = tag(root._to_xml()) + print 'Constructed tag from xml. Constructed XML is equal to original?', xml._to_xml() == root._to_xml() + + xml = u"siempre co\xf1o" + print repr(tag_parse(xml.encode('utf-8'))._to_xml()) + + +if __name__ == '__main__': main() diff --git a/digsby/src/win32events.py b/digsby/src/win32events.py new file mode 100644 index 0000000..644e6ef --- /dev/null +++ b/digsby/src/win32events.py @@ -0,0 +1,14 @@ + +def bindwin32(win, msg, callback): + if not hasattr(win, '_win32binder'): + from cgui import PlatformMessageBinder + win._win32binder = PlatformMessageBinder.ForWindow(win) + + win._win32binder.Bind(msg, callback) + +def unbindwin32(win, msg, callback): + if not hasattr(win, '_win32binder'): + return + + win._win32binder.Unbind(msg, callback) + diff --git a/digsby/src/yahoo/YahooConversation.py b/digsby/src/yahoo/YahooConversation.py new file mode 100644 index 0000000..c0bcb72 --- /dev/null +++ b/digsby/src/yahoo/YahooConversation.py @@ -0,0 +1,246 @@ +''' +Objects representing interactions with other Yahoo buddies. +''' + +from common.Conversation import Conversation +from util import dictadd, callsback +from .yahoobuddy import YahooBuddy +from .yahoolookup import ykeys +from . import yahooformat +from logging import getLogger; log = getLogger('yahoo.convo') + +class YahooConvoBase(Conversation): + 'Base class of all Yahoo conversations, providing buddy management.' + + def __init__(self, protocol): + Conversation.__init__(self, protocol) + self.__dict__.update( + buddies = protocol.buddies, + self_buddy = protocol.self_buddy) + + + def buddy_join(self, buddy): + self.typing_status[buddy] = None + if buddy not in self.room_list: + self.room_list.append(buddy) + super(YahooConvoBase, self).buddy_join(buddy) + + def buddy_leave(self, buddy): + if buddy in self.room_list: + self.room_list.remove(buddy) + del self.typing_status[buddy] + + super(YahooConvoBase, self).buddy_leave(buddy) + + + def incoming_message(self, buddy, message, timestamp = None, content_type = 'text/yahoortf'): + if not isinstance(buddy, YahooBuddy): + raise TypeError() + + # Convert formatting to HTML. + if content_type == 'text/html': + message = yahooformat.tohtml(message) + message = yahooformat.fix_font_size(message) + if content_type == 'text/yahoortf': + message = yahooformat.tohtml(message.encode('xml')) + content_type = 'text/html' + + # convert newlines + message = newlines_to_brs(message) + + self.typing_status[buddy] = None + + if timestamp is None: + kws = {} + else: + kws = dict(timestamp=timestamp) + kws['content_type'] = content_type + if self.received_message(buddy, message, **kws): + Conversation.incoming_message(self) + + @property + def myname(self): + "Returns the self buddy's name." + + return self.self_buddy.name + + def send_typing_status(self, status=None): + pass + +# +# instant messages +# + +class YahooConvo(YahooConvoBase): + 'A one-to-one instant message conversation.' + + ischat = False + + def __init__(self, parent_protocol, buddy): + YahooConvoBase.__init__(self,parent_protocol) + + #self.buddy = buddy + self.buddy_join(parent_protocol.self_buddy) + self.buddy_join(buddy) + self.name = buddy.alias + + # These values will always be added to any ydicts going out. + self.to_from = {'1': self.myname, + '5': buddy.name } + + @callsback + def _send_message(self, message, auto = False, callback=None, **options): + ''' + Sends an instant message to the buddy this conversation is chatting + with. + ''' + network_msg = message.format_as('yahoo') + network_msg = network_msg.encode('utf-8') + + d = { + ykeys['message']: network_msg, + ykeys['msg_encoding']: '1', #1==utf-8, which we just encoded it as + } + + import YahooProtocol + servicekey = YahooProtocol.PROTOCOL_CODES.get(self.buddy.service, None) + if servicekey is not None: #if this follows the rest of the api, this is the right thing to do + d[ykeys['buddy_service']] = servicekey + + log.info('Sending message to %r. buddy_service=%r', self.buddy, self.buddy.service=='msn') + + try: + self.protocol.send('message', 'offline', dictadd(self.to_from, d)) + except Exception, e: + callback.error(e) + else: + callback.success() + + #self.sent_message(message.encode('utf-8').encode('xml').decode('utf-8').replace('\n', '
    '), format) + + @property + def self_buddy(self): + return self.protocol.self_buddy + + def send_typing_status(self, status=None): + ''' + Sends a typing status packet. + + Sends the typing state if status is "typing." + ''' + typing_now = status == 'typing' + + log.debug('sending %styping', ('not ' if not typing_now else '')) + + self.protocol.send('notify','typing', [ + 'typing_status', 'TYPING', + 'frombuddy', self.self_buddy.name, + 'message', '', + 'flag', '1' if typing_now else '0', + 'to', self.buddy.name]) + + def exit(self): + self.protocol.exit_conversation(self) + Conversation.exit(self) + + def __repr__(self): + return '<%s with %s from %s>' % (self.__class__.__name__, self.buddy, self.protocol.username) + +# +# conferences +# + +class YahooConf(YahooConvoBase): + 'A Yahoo conference is a multi-user chat.' + + ischat = True + + def __init__(self, parent_protocol, name): + YahooConvoBase.__init__(self, parent_protocol) + self.name = name + + def _bind_reconnect(self): + pass # do not reconnect to Yahoo conferences. + + @property + def chat_room_name(self): + return self.name + + @callsback + def invite(self, buddy, message=None, callback=None): + bname = getattr(buddy, 'name', buddy) + + self.protocol.invite_to_conference(self.name, bname, message, callback=callback) + + @callsback + def _send_message(self, message, callback=None): + 'Sends an instant message to this conference.' + + # create list of users to send to + users = [] + [users.extend([ykeys['conf_entering'], buddy.name]) + for buddy in self.room_list if buddy != self.self_buddy] + + if len(self.room_list) == 1 and self.room_list[0] == self.protocol.self_buddy: + # Yahoo whines when you send a message to a conference + # in which you are the only participant. + log.info('You are the only person in this chatroom.') + else: + self.protocol.send('confmsg', 'available', [ + 'frombuddy', self.self_buddy.name, + 'conf_name', self.name, + 'message', message.format_as('yahoo').encode('utf-8'), + ] + users) + + callback.success() + + def exit(self): + buddies_to_notify_of_exit = [] + for buddy in self.other_buddies: + buddies_to_notify_of_exit.extend([ + 'conf_from', buddy.name]) + + self.protocol.send('conflogoff', 'available', [ + 'frombuddy', self.self_buddy.name, + 'conf_name', self.name, + ] + buddies_to_notify_of_exit) + + Conversation.exit(self) + + def __repr__(self): + return '' % (self.name, len(self.room_list)) + +# +# chats +# + +class YahooChat(YahooConvoBase): + 'A public Yahoo chatroom.' + + def __init__(self, parent_protocol, name="Yahoo Chat", topic=""): + YahooConvoBase.__init__(self,parent_protocol) + self.name = name # the room name + self.topic = topic + + def _send_message(self, message): + log.info_s('send_message for %r: %s', self, message) + self.protocol.send('comment', 'available', dictadd(dict( + frombuddy = self.myname, + room_name = self.name, + chat_message = message.format_as('yahoo')), + {'124':'1'}) + ) + + + + def exit(self): + log.info('YahooChat exit: sending chatlogout_available') + self.protocol.send('chatlogout', 'available', frombuddy = self.myname) + Conversation.exit(self) + + def __repr__(self): + return '= len(LOAD_BALANCERS): + self.continue_connect() + + def stage2_lb_success(self, server): + self.loadbalance_http_success_count += 1 + self.loadbalanced_http_servers.append(server) + s = HTTPEndpoint(server) + heapq.heappush(self.endpoints, s) + if len(self.loadbalanced_http_servers) == 1: + self.continue_connect() + + def stage2_lb_error(self, error=None): + self.loadbalance_http_error_count += 1 + if self.loadbalance_http_error_count >= len(HTTP_LOAD_BALANCERS): + self.continue_connect() + + def connect_stage3(self): + '''remaining yahoo servers, remaining ports for initial set''' + for server in self.loadbalanced_servers: + for port in S3_PORTS: + s = ServerEndpoint(server, port) + heapq.heappush(self.endpoints, s) + self.continue_connect() + + def connect_stage4(self): + '''remaining yahoo servers, remaining ports for initial set''' + for port in ALL_PORTS: + s = ServerEndpoint('scs.msg.yahoo.com', port) + heapq.heappush(self.endpoints, s) + self.continue_connect() + + def _reconnect(self): + if not self.endpoints: + return self.continue_connect() + self.change_state(self.Statuses.CONNECTING) + self.socket = heapq.heappop(self.endpoints).make_socket(self) + self.keepalive_timer.start() + self.ping_timer.start() + + def fix_unknown_statuses(self): + keys = self.buddies.keys() + for k in keys: + if self.buddies[k].status == 'unknown': + self.buddies[k].status = 'offline' + for k in keys: + self.buddies[k].notify() + + + @action(lambda self: True if self.state != self.Statuses.OFFLINE else None) + def Disconnect(self): + self.set_disconnected() + + def set_disconnected(self, reason = None): + if self.state in (self.Statuses.CONNECTING, + self.Statuses.AUTHENTICATING) and \ + reason in (self.Reasons.CONN_FAIL, + self.Reasons.CONN_LOST): + if not self.endpoints: + if self.connection_stage == 5: + self.set_offline(reason) + elif self.use_http_only and (self.loadbalance_http_error_count + self.loadbalance_http_success_count) >= len(HTTP_LOAD_BALANCERS): + self.set_offline(reason) + else: + self.set_offline(reason) + self.disconnect() + + def disconnect(self): + try: + self.socket.close() + except Exception, e: + traceback.print_exc() + #TODO: find better alternative here + # also...what are the implications of disconnecting while waiting for a + # return on some data inside a generator? + pass + + self._clear_timers() + + for peer in self.peertopeers.values(): + try: + peer.Disconnect() + except Exception: + traceback.print_exc() + common.protocol.Disconnect(self) + if self.state in (self.Statuses.CONNECTING, + self.Statuses.AUTHENTICATING): + self._reconnect() + + def _clear_timers(self): + # timers might be deleted by send_keepalive at any time + kt = getattr(self, 'keepalive_timer', None) + if kt is not None: + kt.stop() + + pt = getattr(self, 'ping_timer', None) + if pt is not None: + pt.stop() + + + # + # + + def group_for(self, contact): + group, name = contact.id + return group.name + + # + # Buddy Icons + # + + def set_buddy_icon(self, imgdata): + 'Upload a buddy icon.' + + from .yfiletransfer import set_buddy_icon + h = hash(imgdata) + self.picture_checksum = h + self.self_buddy.cache_icon(imgdata, h) + set_buddy_icon(self, imgdata) + + def picture_upload_brb(self, to, filename, url, expires): + log.info('bicon upload success (%s), expires %s', url, expires) + + icon_flags = dict(show = '2') + selfname = self.self_buddy.name + self.picture_url = url + + self.send('picture_checksum', 'available', [ + 'frombuddy', selfname, + '212', '1', + 'checksum', self.picture_checksum,]) +# self.send('avatar_update', 'available', ['conf_from', selfname, +# 'avatar_flag', '2']) +# self.send('picture_update', 'available', [ +# 'icon_flag', icon_flags['show'], +# 'to', selfname, +# 'frombuddy', selfname]) + + def _open_url_with_cookies(self, url, method='GET'): + opener = urllib2.build_opener() + opener.addheaders.append(('Cookie', self.cookie_str)) + request = urllib2.Request(url) + request.get_method = lambda: method + resp = opener.open(request) + return resp + + def _request_icon(self, buddy): + url = 'http://rest-img.msg.yahoo.com/v1/displayImage/custom/yahoo/%s?src=orion&chksum' + url = url % buddy.name + log.info('downloading buddy icon for %r from %r', buddy, url) + data = self._open_url_with_cookies(url).read() + buddy.cache_icon(data, buddy.icon_hash) + + def get_buddy_icon(self, buddy): + "Ask the server for a buddy's icon. Takes a buddy tuple, object, or name." + + if isinstance(buddy, basestring): + buddy = self.buddies[buddy] + + if _buddy_name(buddy) != self.self_buddy.name: + self._request_icon(buddy) + + def picture_brb(self, buddy, flag, to, url = None, checksum = None): + 'Server response for a buddy icon.' + if flag == '2': + info('picture_brb for %s', buddy) + info(' flag: %s', flag) + info(' url: %r', url) + info(' checksum: %r', checksum) + + + elif flag == '1': + self.send('picture', 'available', [ + 'frombuddy', to, + 'to', buddy, + 'flag', '2', + 'url', self.picture_url, + 'checksum', self.picture_checksum, + ]) + else: + log.warning('unknown flag "%s" in picture_brb', flag) + + def picture_checksum_brb(self, buddy, checksum, url=None): + "Server is sending you a buddy's icon checksum." + + log.info('picture_checksum_brb gave checksum for %r: %r', buddy, checksum) + buddy = self.buddies[buddy] + buddy.setnotifyif('icon_hash', checksum) + log.info('icon_hash: %r', buddy.icon_hash) + log.info('_cached_hash: %r', buddy._cached_hash) + + def picture_update_brb(self, buddy, to, icon_flag): + 'Server is informing you of an icon "enabled" change.' + + log.info("%s's buddy icon_flag: %s", buddy, icon_flag) + + selfname = self.self_buddy.name + + if buddy == selfname and to == selfname: + # server is informing us that our icon state has been successfully modified... + # it is our job (the client's) to inform friends on our buddylist. + self.send('picture_checksum', 'available', [ + 'checksum', hash(self.self_buddy.icon), + '212', '1', + 'frombuddy', selfname]) + + online_buddies = [b for b in self.buddies.values() if b.online and b is not self.self_buddy] + log.info('sending picture_checksum updates to %d buddies', len(online_buddies)) + for b in online_buddies: + self.send('picture_update', 'available', + frombuddy = selfname, to = b.name, icon_flag = icon_flag) + + if icon_flag == '0': # zero means don't show icon + self.buddies[buddy].setnotifyif('icon_disabled', True) + elif icon_flag == '2': + self.buddies[buddy].setnotifyif('icon_disabled', False) + + def avatar_update_brb(self, buddy, avatar_flag, checksum=None): + buddy = self.buddies[buddy] + + if avatar_flag == '0': + buddy.icon_disabled = True + elif avatar_flag == '2': + buddy.icon_disabled = False + buddy.notify() + + def get_profile(self, name): + print "Yahoo: get_profile " + name + + # + # Chat + # + + def send_chat_req(self, buddy, room_name, message = None): + buddy = _buddy_name(buddy) + myname = self.self_buddy.name + + self.send('confinvite', 'available', + frombuddy = myname, + confbuddy = myname, + conf_name = room_name, + conf_invite_message = message or 'Join My Conference...', + conf_tobuddy = buddy) + + def chatjoin_brb_raw(self, ydict_iter): + "Received when you join a chatroom." + + room = None + for k, v in ydict_iter: + # room name + if k == ykeys.room_name: + if not room: + if v not in self.chats: + info('creating YahooChat %s', v) + self.chats[v] = room = YahooChat(self, name = v) + cb = self.callbacks.pop('chatjoin.%s' % v) + cb.success(room) + else: + room = self.chats[v] + # Topic + elif k == ykeys.topic: + info('setting topic %s', v) + room.topic = v + # A buddy. + elif k == ykeys.chat_buddy: + if room: + room.buddy_join( self.buddies[v] ) + else: + log.warning( 'got a buddy with no room: %s', v ) + + + def comment_brb(self, room_name, chat_buddy, chat_message): + if room_name in self.chats: + self.chats[room_name].incoming_message(self.buddies[chat_buddy], + yahooformat.tohtml(chat_message)) + else: + info('unhandled chat message for room %s...%s: %s', + room_name, chat_buddy, chat_message) + + + def chatexit_brb(self, room_name, chat_buddy): + "Received when someone leaves a chatroom." + + if room_name not in self.chats: + log.error('Got a CHATEXIT for %s in room %s', chat_buddy, room_name) + log.error('chats: ' + repr(self.chats)) + return + + info('chatexit_brb for %s in %s, calling buddy_leave', + chat_buddy, room_name) + self.chats[room_name].buddy_leave(self.buddies[chat_buddy]) + + @callsback + def join_chat_room(self, room_name, callback = None): + if room_name is None: + room_name = 'DigsbyChat%s' % random.randint(0, sys.maxint) + + myname = self.self_buddy.name + + query = {'109': myname, + '1':myname, + '6':'abcde', + '98':'us', + '135':'ym8.0.0.505'} + self.send('chatonline', 'available', query) + self.send('chatjoin', 'available',{ + 'frombuddy':myname, + 'room_name': room_name, + '62':'2' + }) + + self.callbacks['chatjoin.%s' % room_name] = callback + + + def join_in_chat(self, buddy): + 'Finds and joins a chat room a buddy is in.' + me = self.self_buddy.name + + # Send a chat "online" before requesting the room. + self.socket.chatlogon('chatgoto', 'available', dict( + chat_buddy = _buddy_name(buddy), + frombuddy = me, + chat_search = '2', + )) + + def chatgoto_cancel(self): + ''' + Server error message for "Join in Chat" when the buddy you are joining + isn't in a chat room. + ''' + self.hub.on_error(YahooError('User is not in a chat room.')) + + def set_message(self, message, status='away', url = '', format = None): + 'Sets message [and/or status].' + + info('setting status: status=%r url=%r', status, url) + log.info_s(' message: %r', message) + + avail = status.lower() in ('available', 'online') + + self.status = status + self._status_message = u'' + + if message.lower() in ('available', '') and avail: + # setting available + out = ('status', str(statuses['available'])) + elif status == 'idle': + # setting idle + self._status_message = message + out = ('status', str(statuses['idle']), + 'custom_message', message.encode('utf-8')) + elif not message: + # away with no message + if status in nice_statuses: + out = ('status', str(nice_statuses[status])) + else: + out = ('status', str(statuses['steppedout']) ) + else: + # custom message + self._status_message = message + out = ( 'status', str( statuses['custom'] ), + 'away', '0' if avail else '1', + 'custom_message', message.encode('utf-8')) + + self.send('yahoo6_status_update', 'available', out) + + # + # sms + # + + @callsback + def send_sms(self, sms_number, message, callback=None): + 'Sends an outgoing SMS message.' + + yahoohttp.send_sms(self, sms_number, message, callback=callback) + + def sms_message_brb(self, buddy, message, sms_carrier = 'unknown carrier'): + 'An incoming SMS message.' + + buddy = self.buddies[buddy] + + self.convo_for(buddy).received_message(buddy, message, sms = True) + + # + # Buddy list management + # + + def addbuddy_brb(self, contact, group, error='0', buddy_service=None, pending_auth=None): + "Server ack for adding buddies." + name = contact + group = getattr(group, 'name', group) + + if error == '2': + log.info('ignoring "%s" in group "%s" error 2 (already in group?).', name, group) + elif error != '0': + self.hub.on_error(YahooError("There was an error in adding " + "buddy %s." % name)) + else: + # create the group if it doesn't exist + if group in self.groups: + group = self.groups[group] + else: + log.info('group %s did not exist, creating', group) + grp = Group(group, self, None) + grp.id = grp + self.groups[group] = grp + group = grp + + buddy = self.buddies[name] + buddy._service = PROTOCOL_CODES.get(buddy_service, 'yahoo') #is it msn/LCS/sametime? + log.info('adding "%s" to "%s"', name, group) + group.append( YahooContact(buddy, group) ) + if buddy.status == 'unknown': + buddy.setnotify('status', 'offline') + # Mark this buddy as "pending subscription" + #TODO: across app runs, this won't stay. + buddy.setnotifyif('pending_auth', True) + self.root_group.notify() + + @callsback + def remove_buddy(self, contact_id, callback = None): + "Removes a buddy from the indicated group." + + if isinstance(contact_id, basestring): + contact = self.get_contact(contact_id) + if not contact: + callback.error() + return log.warning('cannot find contact %s', contact_id) + contact_id = contact + + group = contact_id.group + buddyname = _buddy_name(contact_id) + buddy = contact_id + + self.callbacks['removebuddy.%s' % buddyname] = callback + d = dict(frombuddy = self.self_buddy.name, + contact= buddyname, + group= group.name) + + protocode = PROTOCOL_CODES.get(buddy._service, None) + if protocode is not None: #is it msn/LCS/sametime? + d.update(buddy_service=protocode) + + self.send('rembuddy','available', **d) + + def contact_for_name(self, name, root = None): + if root is None: + root = self.root_group + + for c in root: + if isinstance(c, Group): + contact, group = self.contact_for_name(name, c) + if contact: + return contact, group + elif isinstance(c, YahooContact) and c.name == name: + return c, root + + return None, None + + def get_contact(self, buddyname): + 'Returns a contact object for a buddy name.' + + contact, group = self.contact_for_name(buddyname) + return contact + + def rembuddy_brb(self, contact, group, error='0'): + 'Server ack for buddy removal.' + + name = contact + contact, g_ = self.contact_for_name(contact, self.groups[group]) + + cb = self.callbacks.pop('removebuddy.%s' % contact.name) + log.info('removing "%s" from "%s"', name, group) + if error == '0': + if contact is None: + return log.warning('server sent rembuddy_brb but there is no ' + 'contact %s', name) + self.groups[group].remove( contact ) + self.root_group.notify() + cb.success() + else: + cb.error() + self.hub.on_error(YahooError("Could not remove buddy " + name)) + + def send_buddylist(self, buddy, buddygroup = None): + buddies = [buddy for group in buddygroup or self.groups.itervalues() + for buddy in group] + + #TODO: finish send_buddylist + + self.send('send_buddylist', 'available', [ + 'frombuddy', self.self_buddy.name, + 'flag', '0', + ]) + + def handle_blocklists(self, ignore_list, appear_offline_list): + ''' + ignore_list - a list of buddies you are permanently blocking + + appear_offline_list - buddies for whom stealth_perm is True, i.e., they + are on your buddylist but they can't see you + ''' + buds = self.buddies + + if ignore_list: + for buddy in ignore_list.split(','): + buds[buddy].setnotifyif('blocked', True) + + if appear_offline_list: + for buddy in appear_offline_list.split(','): + buds[buddy].setnotifyif('stealth_perm', True) + + def list_notinoffice(self, buddy_list = None, ignore_list = None, appear_offline_list = None): + self.handle_blocklists(ignore_list, appear_offline_list) + + if not buddy_list: + return + + self.incoming_list(buddy_list) + + def list_available(self, + buddy_list = None, + ignore_list = None, + appear_offline_list = None, + identity_list = '', + login_cookie = None, **kw): + '''The final packet in receiving a buddy list. + + List comes in as Group:Buddy1,Buddy2\nGroup2:Buddy3''' + + if self.state != self.Statuses.LOADING_CONTACT_LIST: + self.change_state(self.Statuses.LOADING_CONTACT_LIST) + + self.handle_blocklists(ignore_list, appear_offline_list) + + self.identities = [i.lower() for i in identity_list.split(',')] + + if login_cookie: + # server sends logon cookies we use later for file transfers + k = login_cookie[0] + + # For Y, T, grab after the letter/tab, and until the first semicolon + if k in ('Y', 'T'): + add_cookie(self.cookies, login_cookie) + + if len(self.cookies) >= 2: + self.sync_addressbook() + self.root_group.notify() + + if buddy_list: + self.incoming_list(buddy_list) + + def init_buddylist(self): + #Buddylist PDA (push down automata) + self.grp = None + self.ct = None + self.modes = [] + self.entries = [] + self.group_dict = {} + self.contact_dict = {} + + def list15_available_raw(self, buddy_list, is_available=True): + '''YMSG15 incomming buddy list''' + root = self.root_group + for k,v in ((ykeys.get(k, k), v) for k, v in buddy_list): +# if k in LIST_COMMANDS: +# v = BLIST_ENTRIES[v] + log.debug((k, v)) + if k == BEGIN_MODE: + self.modes.insert(0, v) + elif k == END_MODE: + m = self.modes.pop(0) + assert m == v + elif k == BEGIN_ENTRY: + self.entries.insert(0, v) + if v == GROUP: + assert self.modes[0] == GROUP + self.group_dict = {} + elif v == CONTACT: + self.contact_dict = {} + elif v == BLOCK_ENTRY: + self.contact_dict = {} + elif k == END_ENTRY: + m = self.entries.pop(0) + assert m == v + if v == GROUP: + pass + elif v == CONTACT: + contact_dict = self.contact_dict + if 'buddy_service' in self.contact_dict: + service_type = PROTOCOL_CODES.get(self.contact_dict['buddy_service'], 'yahoo') + self.ct.buddy._service = service_type + else: + self.ct.buddy._service = 'yahoo' + if 'pending_auth' in self.contact_dict: + self.ct.buddy.pending_auth = True + if 'stealth' in self.contact_dict: + self.ct.buddy.stealth_perm = True + elif v == BLOCK_ENTRY: + pass + elif k == 'group': + assert self.modes[0] == GROUP + assert self.entries[0] == GROUP + self.grp = Group(v, self, None) + self.grp.id = self.grp + self.groups[v] = self.grp + root.append(self.grp) + elif k == 'contact': + if self.modes[0] == BLOCK_ENTRY: + assert self.entries[0] == BLOCK_ENTRY + self.buddies[v].blocked = True + else: + assert self.modes[0] == CONTACT + assert self.entries[0] == CONTACT + self.ct = YahooContact(self.buddies[v], self.grp) +# print self.ct, self.ct.status + self.grp.append(self.ct) + else: + if self.modes[0] == GROUP: + self.group_dict[k] = v + elif self.modes[0] == CONTACT: + self.contact_dict[k] = v + elif self.modes[0] == BLOCK_ENTRY: + self.contact_dict[k] = v + if is_available and self.state != self.Statuses.ONLINE: + log.info('setting online in list15_available_raw') + self.change_state(self.Statuses.ONLINE) + root.notify() + + def list15_steppedout_raw(self, buddy_list): + self.list15_available_raw(buddy_list, False) + + def incoming_list(self, buddy_list): + + log.info('incoming_list') + + root = self.root_group + + groups = buddy_list.split('\n') + for group_str in groups: + # grab the group name + group_split = group_str.split(':') + group_name = group_split.pop(0) + if not group_name: + continue + + # grab names + if group_split: names = group_split[0].split(',') + else: names = [] + + # create the group + if group_name in self.groups: + grp = self.groups[group_name] + else: + grp = Group(group_name, self, None) + grp.id = grp + self.groups[group_name] = grp + + log.info('group "%s": %s', group_name, names) + + # add a Contact with a YahooBuddy for each name + grp.extend([YahooContact(self.buddies[name], grp) + for name in names]) + root.append(grp) + root.notify() + + def listallow_notathome(self, buddy, to, message="", buddy_service=None): + ''' + Server is asking if you'd like to allow a buddy to add you to his or + her list. + ''' + if to.lower() not in self.identities: + return log.warning("got an auth request for %r, but that name is not in this account's identities: %r." + " Ignoring.", to, self.identities) + bud = self.buddies[buddy] + bud._service = PROTOCOL_CODES.get(buddy_service, 'yahoo') + self.hub.authorize_buddy(self, bud, message, username_added=to) + + def listallow_brb(self, buddy, flag, message=None): + 'Server ack for buddy authorize.' + + if flag == '1': + log.info('%s has authorized you to add them to your list', buddy) + self.buddies[buddy].setnotifyif('pending_auth', False) + + elif flag == '2': + msg = u'Buddy %s has chosen not to allow you as a friend.' % buddy + if message: + msg += '\n\n"%s"' % message + + #TODO: this really isn't an error. + self.hub.on_error(YahooError(msg)) + + @callsback + def move_buddy(self, buddy, new_group, from_group=None, new_position=0, callback = None): + ''' + If new_group is none, then you are moving the buddy within the same + group. + ''' + + info('moving %s from %s to %s', buddy, from_group, new_group) + + # yahoo doesn't support ordering within a group. + if new_group is None: + return log.warning('no new_group passed to move_buddy') + + # find the parent + if isinstance(buddy, basestring): + contact, parent_group = self.contact_for_name( buddy ) + else: + # Contact's ID are ( group, buddyname ) + parent_group, buddy = buddy.id + + + if not parent_group: + return log.error("Couldn't find a parent group for %s", buddy) + + new_group = getattr(new_group, 'name', new_group) + + self.callbacks['movebuddy.%s' % buddy] = callback + pkt = ['frombuddy', self.self_buddy.name, + BEGIN_MODE, MOVE, + BEGIN_ENTRY, MOVE, + 'contact', buddy] + + protocode = PROTOCOL_CODES.get(self.buddies[buddy]._service, None) + if protocode is not None: #is it msn/LCS/sametime? + pkt.extend(['buddy_service', protocode]) + pkt.extend(['fromgroup', parent_group.name, + 'togroup', new_group, + END_ENTRY, MOVE, + END_MODE, MOVE,]) + self.send('movebuddy','available', pkt) + + def movebuddy_brb(self, contact, fromgroup, togroup, error='0'): + 'Server ack for moving a buddy between groups.' + name, err = contact, lambda s: self.hub.on_error(YahooError(s)) + + info('movebuddy ACK') + + found_contact = None # search for the right Contact object + for c in self.groups[fromgroup]: + if name == c.name: + found_contact = c + break + + if found_contact is None: + return err('Could not move buddy.') + + cb = self.callbacks.pop('movebuddy.%s' % found_contact.name) + if error != '0': + cb.error() + err('Server indicated an error in moving buddy: %s' % error) + if fromgroup not in self.groups: + cb.error() + return err('%s is not in the list of groups' % fromgroup) + + # Actually do the moving. + self.groups[fromgroup].remove(found_contact) + + if not togroup in self.groups: + log.info('group %s did not exist, creating', togroup) + grp = Group(togroup, self, None) + grp.id = grp + self.groups[togroup] = grp + + self.groups[togroup].append(found_contact) + + # Update the Contact's id + found_contact.id = (self.groups[togroup], found_contact.name) + found_contact.group = self.groups[togroup] + + cb.success(found_contact.id) + self.root_group.notify() + + def remove_group_case_insensitive(self, group_name): + ''' + Removes any groups that match group_name, case insentive. + ''' + + group_name = group_name.lower() + + for name, group in self.groups.items(): + if name.lower() == group_name: + self.remove_group(group) + + @callsback + def remove_group(self, group, callback): + group_name = group.name if hasattr(group, 'name') else str(group) + + log.info('Removing %r.', group) + + oldsuccess = callback.success + def on_done(): + log.info('deleting group %r', group_name) + groupobj = self.groups.pop(group_name, None) + self.root_group.remove(groupobj) + oldsuccess() + + callback.success = on_done + + groupobj = self.groups[group_name] + if len(groupobj) > 0: + ms = [] + for contact in groupobj: + @callsback + def do_remove(contact=contact, callback = None): + log.info('Removing %r.', contact) + self.remove_buddy(contact, callback = callback) + + ms.append(do_remove) + + from util.callbacks import do_cb_na + do_cb_na(ms, callback = callback) #I know, there are no callback args, but we don't want them + else: + on_done() + + def authorize_buddy(self, buddy, allow, username_added): + assert username_added + buddy_name = str(buddy) + bud = self.buddies[buddy_name] + #HAX: yahoo should become aware of it's multiple screen names + l = ['frombuddy', username_added, + 'to', buddy_name] + protocode = PROTOCOL_CODES.get(bud._service, None) + if protocode is not None: #is it msn/LCS/sametime? + l.extend(['buddy_service',protocode]) + if allow: l.extend(['flag', '1', 334, '0',]) + else: l.extend(['flag', '2', 334, '0', 'message', 'decline']) + + self.send('listallow', 'available', l) + + @callsback + def add_group(self, group_name, callback = None): + '''Add an empty group. This happens immediately, since there is no + corresponding server operation--groups seem to only be defined by the + buddies in them.''' + + grp = self.groups[group_name] = Group(group_name, self, None) + grp.id = grp + + callback.success(grp) + self.root_group.append(grp) + + @callsback + def rename_group(self, group_obj, new_name, callback = None): + 'Renames a group.' + + if hasattr(group_obj, 'name'): group = group_obj.name + elif isinstance(group_obj, basestring): group = group_obj + else: raise TypeError('give a group name or object: %s %r' % (type(group_obj), group_obj)) + + self.callbacks['grouprename.%s' % new_name] = callback + + self.send('grouprename','available', + frombuddy = self.self_buddy.name, + group = group, + new_group = new_name.encode('utf-8'), + ) + + def grouprename_brb(self, new_group, group = None, error = '0'): + 'Server ack for group rename.' + grps = self.groups + + cb = self.callbacks.pop('grouprename.%s' % group, None) + + if error == '1': + if cb is not None: cb.error() + else: + grps[group] = grps[new_group] + grps[new_group].name = group + del grps[new_group] + if cb is not None: cb.success() + self.root_group.notify() + + def add_contact(self, buddy_name, group, intro_text = None, service = None): + assert service is not None + protocode = PROTOCOL_CODES.get(service, 'yahoo') + + if protocode == 'yahoo' and '@yahoo' in buddy_name: + buddy_name = buddy_name.split('@', 1)[0] + + if buddy_name in self.buddies: + buddy = self.buddies[buddy_name] + if buddy.blocked: + self.unblock_buddy(buddy, + success = lambda: self.add_contact(buddy_name, + group, intro_text=intro_text, service=service)) + return + + if intro_text is None: + intro_text = _("May I add you to my contact list?") + + group = getattr(group, 'name', group) + if not isinstance(group, basestring): + raise TypeError('group must be a Group or a string group name: %s' % type(group)) + + pkt = ['message', intro_text, + 'group', group, + 'frombuddy', self.self_buddy.name, + BEGIN_MODE, CONTACT, + BEGIN_ENTRY, CONTACT, + 'contact', buddy_name, + ] + + if protocode != 'yahoo': #is it msn/LCS/sametime? + pkt.extend(['buddy_service', protocode]) + pkt.extend([END_ENTRY, CONTACT, + END_MODE, CONTACT]) + + self.send('addbuddy', 'available', pkt) + + + add_buddy = add_contact # compatibility + + def set_invisible(self, invisible = True): + 'Become or come back from being invisible.' + + log.info('%r: set_invisible(%s)', self, invisible) + + self.send('invisible', 'available', + flag = '2' if invisible else '1') + + if invisible: + # going invisible clears all stealth sessions + for buddy in self.buddies.values(): + if not buddy.stealth_session: + buddy.setnotifyif('stealth_session', True) + + # + # IGNORECONTACT (133) - adds a buddy to your "block list" + # + + @callsback + def block_buddy(self, buddy, callback = None): + 'Block a buddy. (This adds the buddy to your "ignore" list.)' + + self.set_blocked(buddy, True, callback = callback) + + @callsback + def unblock_buddy(self, buddy, callback = None): + 'Unblock a buddy. (This removes a buddy from your "ignore" list.)' + + self.set_blocked(buddy, False, callback = callback) + + @callsback + def set_blocked(self, buddy, blocked = True, callback = None): + 'Adds or removes a buddy from the blocklist.' + + buddy = _buddy_name(buddy) + self.callbacks['ignorecontact.%s' % buddy] = callback + + def send_block(buddy = buddy, blocked = blocked): + self.send('ignorecontact', 'available', + frombuddy = self.self_buddy.name, + contact = buddy, + flag = '1' if blocked else '2') + + for group in self.groups.values(): + for gbud in group: + if gbud.name == buddy: + log.info('removing buddy %s', buddy) + return self.remove_buddy(buddy, success = send_block) + + send_block() + + def ignorecontact_brb(self, away_buddy, flag, error): + 'Server is responding to a block buddy.' + + cb = self.callbacks.pop('ignorecontact.%s' % away_buddy, None) + + if error == '0': + self.buddies[away_buddy].setnotifyif('blocked', flag == '1') + if cb: cb.success() + else: + log.warning('error blocking buddy %s (err code %s)', away_buddy, error) + if cb: cb.error() + + + # + # STEALTH_PERM (185) - appearing permanantly invisible to buddies while they + # are still on your buddy list + # + + @callsback + def set_stealth_perm(self, buddy, set_blocked = True, callback = None): + 'Set permanent stealth settings for the indicated buddy.' + + log.info('set_stealth_perm %s set_blocked = %s', buddy, set_blocked) + + buddy = _buddy_name(buddy) + self.callbacks['block_buddy.%s' % buddy] = callback + + self.send('stealth_perm', 'available', [ + 'frombuddy', self.self_buddy.name, + 'block' , '1' if set_blocked else '2', + 'flag' , '2', + BEGIN_MODE , CONTACT, + BEGIN_ENTRY, CONTACT, + 'contact' , buddy, + END_ENTRY , CONTACT, + END_MODE , CONTACT]) + + def stealth_perm_brb(self, contact, block, flag, error='0'): + 'Server ack for blocking/unblocking a buddy.' + + cb = self.callbacks.pop('block_buddy.%s' % contact, None) + + if error != '0': + if cb is not None: cb.error() + return self.hub.on_error(YahooError('There was an error blocking ' + contact)) + + if block == '1': blocked = True + elif block == '2': blocked = False + else: + if cb is not None: cb.error() + return log.warning('unknown block flag in stealth_perm_brb packet: %r', block) + + self.buddies[contact].setnotifyif('stealth_perm', blocked) + if cb is not None: cb.success() + + # + # STEALTH_SESSION (186) - appearing online to specific buddies while invisible + # + + @callsback + def set_stealth_session(self, buddy, appear_online = True, callback = None): + ''' + Modifies buddy specific visibilty setting. Use to appear online to someone + while invisible. + ''' + buddy = _buddy_name(buddy) + self.callbacks['appear_online.%s' % buddy] = callback + + self.send('stealth_session', 'available', [ + 'frombuddy', self.self_buddy.name, + 'block' , '2' if appear_online else '1', + 'flag' , '1', + BEGIN_MODE , CONTACT, + BEGIN_ENTRY, CONTACT, + 'contact', buddy, + END_ENTRY , CONTACT, + END_MODE , CONTACT]) + + def stealth_session_brb(self, contact, block, flag, error = '0'): + 'Server ack for appearing online to a specific buddy while invisible.' + + cb = self.callbacks.pop('appear_online.%s' % contact, None) + + if error != '0': + if cb is not None: cb.error() + return self.hub.on_error(YahooError(_('There was an error modifying stealth settings for {name}.').format(name=contact))) + + if block == '2': seesme = True + elif block == '1': seesme = False + else: + if cb is not None: cb.error() + return log.warning('unknown block flag in stealth_session_brb: %r', block) + + log.info('stealth_session_brb for %s: %s (%s)', contact, block, seesme) + + self.buddies[contact].setnotifyif('stealth_session', seesme) + if cb is not None: cb.success() + + def set_remote_alias(self, buddy, alias): + buddy = get_bname(buddy) + + if not buddy in self.buddies: + return self.hub.on_error(YahooError(buddy + ' is not on your ' + 'contact list')) + + url = UrlQuery('http://address.yahoo.com/us/?', d={'.intl':'us'}, + v='XM', prog='ymsgr', sync='1', tags='long', + noclear='1') + + # Assemble XML necessary to change the address book entry + data = tag('ab', cc=1, k=self.self_buddy.name)( + tag('ct', pr=0, yi=buddy, c=1, id=11, nn=alias) + ) + + info('sending:') + info(data._to_xml()) + + def set_remote_done(res): + if res: + self.buddies[buddy].update_cinfo(res) + else: + self.hub.on_error(YahooError("There was an error in editing " + "contact details.")) + # POST to the address.yahoo.com address + post_xml(url, data, **{ + 'Cookie' : self.cookie_str, + 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 5.5)', 'success' :set_remote_done}) + + def get_remotes(self): + url = UrlQuery('http://address.yahoo.com/', d={'.intl':'us'}, + v='XM', prog='ymsgr', tags='short') + post_xml(url, data=tag(''), **{ + 'Cookie' : self.cookie_str, + 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 5.5)', 'success':self.finish_get_remotes}) + + def finish_get_remotes(self, res): + cts = res.ct +# print repr(res._to_xml()) + for ct in cts: + if 'yi' in ct._attrs: + self.buddies[str(ct['yi'])].update_cinfo_raw(ct) + + # + # File Transfer + # + + def send_file(self, buddy, fileinfo): + # Since pure p2p is not implemented: + + from .peerfiletransfer import YahooOutgoingPeerFileXfer + xfer = YahooOutgoingPeerFileXfer(self, buddy, fileinfo) + xfer.send_offer() + return xfer + + def filetransfer_brb(self, to, url): + 'A buddy is trying to send you a file.' + + # ask the hub if it's okay to download this file. + # hub will call accept_file with the URL if okay. + transfer = YahooHTTPIncoming(self, self.buddies[to], url) + self.hub.on_file_request( self, transfer ) + + def filetransfer_available(self, **opts): + print opts + + def accept_file(self, filexfer, fileobj): + 'Accept a file transfer.' + + if isinstance(filexfer, basestring): + # Receive a file from the Yahoo! file transfer server + url = filexfer + info('Accepting a file from the Yahoo file xfer server via a HTTP GET') + result = urlopen(url) + + # Write the HTTP result out to the open file, and close it. + fileobj.write( result.read() ) + fileobj.close() + else: + # Must be a peer to peer file transfer. + if not isinstance(filexfer, YahooPeerFileTransfer): + raise TypeError('filexfer must be a YahooPeerFileTransfer object') + info('Accepting a p2p file transfer') + filexfer.accept() + + def decline_file(self, filexfer): + 'Decline a file transfer.' + filexfer.decline() + + def filetransfer_notatdesk(self): + 'Server ack for a file upload.' + + print 'Got ack for file upload!' + + def peertopeer_brb(self, base64ip, buddy, typing_status, **k): + from struct import pack, unpack + ip = tuple(reversed(unpack("B"*4, pack('!I', int(base64ip.decode('base64')))))) + print "ip:", ip + print "buddy", buddy + print "status", typing_status + from pprint import pprint + pprint(k) + ip = '.'.join(str(x) for x in ip) + port = 5101 + if buddy in self.peertopeers: + return + self.peertopeers[buddy] = YahooP2P(self, buddy, k['to'], (ip, port), self.socket.session_id) + + def peerrequest_brb(self, buddy, p2pcookie, filename = None, longcookie=None, **k): + '220: P2P file transfer request. Asks the hub whether or not to accept.' + mode = k.get('acceptxfer', sentinel) + if mode == '1': + # k.pop('to') + if filename is None: # Is this a filetransfer cancel? yes. + try: + self.file_transfers.pop((buddy, p2pcookie)).cancel(me=False) + except AttributeError: + info("could not cancel file transfer with %r", buddy) + return info('%r cancelled a file transfer', buddy) + + filexfer = YahooPeerFileTransfer(self, self.buddies[buddy], + filename = filename, + p2pcookie = p2pcookie, + longcookie = longcookie, + # to = buddy, + # frombuddy = self.self_buddy.name, + buddyname=buddy, **k) + self.file_transfers[(buddy, p2pcookie)] = filexfer + self.hub.on_file_request(self, filexfer) + elif mode == '3': + #someone accepted our request + if (buddy, p2pcookie) in self.file_transfers: + self.file_transfers[(buddy, p2pcookie)].do_setup(buddy, p2pcookie, filename, longcookie, **k) + elif mode == '4': + #decline + try: + self.file_transfers.pop((buddy, p2pcookie)).cancel(me=False) + except AttributeError: + info("could not cancel file transfer with %r", buddy) + + def peersetup_brb(self, buddy, filename, peer_path, p2pcookie, peerip=None, transfertype=None): + try: + ft = self.file_transfers[(buddy, p2pcookie)] + ft.connect(buddy, filename, peer_path, p2pcookie, peerip, transfertype) + except KeyError: + pass + + def peersetup_cancel(self, buddy, p2pcookie, **kws): + ft = self.file_transfers.pop((buddy, p2pcookie), None) + if ft is not None: + ft.cancel(me=False) + + def peerinit_brb(self, buddy, p2pcookie, filename, transfertype, peer_path, **k): + try: + ft = self.file_transfers[(buddy, p2pcookie)] + ft.go(buddy, p2pcookie, filename, transfertype, peer_path, **k) + except KeyError: + pass + + def peerinit_cancel(self, buddy, p2pcookie, **k): + ft = self.file_transfers.pop((buddy, p2pcookie), None) + if ft is not None: + ft.cancel(me=False) + + #VIDEO chat? + + def skinname_available_raw(self, ydict_iter): + pass + #print list(ydict_iter) + + # + # IM + # + + def chat_with(self, buddy): + 'Ask to chat with a buddy.' + self.hub.on_conversation(self.convo_for(buddy)) + + def _parse_incoming_message(self, ydict_iter): + all_messages = [] + ydict_iter = list(ydict_iter) + print 'i', ydict_iter + messages = groupify(ydict_iter) + for d in messages: + keep = True + d = yiter_to_dict(d.iteritems()) + if 'message' not in d: + continue + if 'buddy' not in d: + if 'frombuddy' in d: + d['buddy'] = d['frombuddy'] + else: + continue + + buddy = self.buddies[d['buddy']] + + timestamp = None + unix_time = d.get('unix_time') + if unix_time is not None: + with traceguard: + timestamp = datetime.utcfromtimestamp(int(unix_time)) + if 'msgid' in d: + keep = ack_message(self, d, 'buddy') + if keep: + all_messages.append((buddy, d['message'], timestamp)) + return all_messages + + def incoming_message_raw(self, ydict_iter): + + message_info = self._parse_incoming_message(ydict_iter) + for (buddy, message, timestamp) in message_info: + self.convo_for(buddy).incoming_message(buddy, message, timestamp = timestamp, content_type = 'text/html') + + message_brb_raw = message_notinoffice_raw = message_offline_raw = incoming_message_raw + +# def message_brb_raw(self, ydict_iter): +# message_info = self._parse_incoming_message(ydict_iter) +# for (buddy, message, timestamp) in message_info: +# self.convo_for(buddy).incoming_message(buddy, message, timestamp = timestamp) + + def audible_brb(self, buddy, audible_message): + ''' + A buddy has sent you an audible, a small flash animation with a + message from a character. + ''' + + # Just display the text message: + buddy = self.buddies[buddy] + self.convo_for(buddy).incoming_message(buddy, audible_message) + + def notify_brb(self, buddy, typing_status, flag): + 'Typing notifications.' + from common import pref + + if typing_status == 'TYPING': + # buddy's typing status has changed + if flag == '1': state = 'typing' + else: state = None + + buddy = self.buddies[buddy] + + # For some reason, the server still sends you typing + # notifications for buddies on your ignore list. + if not buddy.blocked: + if buddy in self.conversations: + self.conversations[buddy].typing_status[buddy] = state + elif pref('messaging.psychic', False): + c = self.convo_for(buddy) + self.hub.on_conversation(c) + c.tingle() + + @threaded + def sync_addressbook(self): + times = Storage(f=0, i=0, sy=0, sm=0, c=0, a=0) + + log.info('syncing yahoo! addressbook') + ab_url = ('http://insider.msg.yahoo.com/ycontent/?' + #'filter=%(f)s&' + #'imv=%(i)s&' + #'system=%(sy)s&' + #'sms=%(sm)s&' + #'chatcat=%(c)s&' + 'ab2=%(a)s&' + 'intl=us&os=win') % times + + req = Request(ab_url, headers = {'Cookie': self.cookie_str}) + + try: + response = urlopen(req) + except Exception: + traceback.print_exc() + return + + xml = response.read() + log.debug(xml) + content = tag(xml) + + for record in content.addressbook.record: + name = record['userid'] + log.debug('updating address book info for %s', name) + buddy = self.buddies[name] + buddy.update_cinfo_raw(record) + + # + # conferences (multi user private chats) + # + + def _create_conference(self, name): + if name not in self.conferences: + info('creating YahooConf %r', name) + self.conferences[name] = conf = YahooConf(self, name = name) + else: + conf = self.conferences[name] + + return conf + + def confaddinvite_brb_raw(self, ydict_iter): + 'You are invited to a conference.' + + log.info('confaddinvite_brb_raw') + + conf, confbuddy, confname = None, None, None + conf_entering = None + invite_msg = '' + tobuddies = [] + + from functools import partial + + conf_entering_buddies = [] + for k, v in ydict_iter: + if k == ykeys['conf_name']: + conf = self._create_conference(v) + confname = v + elif k == ykeys['conf_entering']: + conf_entering_buddies.append(v) + elif k == ykeys['conf_buddy']: + confbuddy = v + elif k == ykeys['conf_invite_message']: + invite_msg = v + elif k == ykeys['conf_tobuddy']: + tobuddies.append(v) + + for b in conf_entering_buddies: + conf.buddy_join(self.buddies[b]) + + self.callbacks['confinvite.' + confname].success(conf) + conf.buddy_join(self.buddies[confbuddy]) + + from common import profile + if confbuddy.lower() == self.self_buddy.name.lower(): + return profile.on_entered_chat(conf) + + def invite_answer(accept, confname=confname, confbuddy=confbuddy): + other_buddies = [] + if confbuddy not in other_buddies: + tobuddies.insert(0, confbuddy) + + for b in conf_entering_buddies: + #other_buddies.extend(['conf_entering', b]) + other_buddies.extend(['conf_from', b]) + + for bud in tobuddies: + other_buddies.extend(['conf_from', bud]) + + self.send('conflogon' if accept else 'confdecline', 'available', + ['frombuddy', self.self_buddy.name, + 'conf_name', confname] + + other_buddies) + + if accept: + self.conflogon_brb(confname, confbuddy) + self.conflogon_brb(confname, self.self_buddy.name) + + profile.on_entered_chat(conf) + else: + self.conferences.pop(confname, None) + + self.hub.on_invite(self, confbuddy, confname, + message = invite_msg, + on_yes = partial(invite_answer, accept = True), + on_no = partial(invite_answer, accept = False)) + + confinvite_brb_raw = confaddinvite_brb_raw + confaddinvite_11_raw = confaddinvite_brb_raw + + def confmsg_brb(self, conf_name, conf_from, message): + 'Incoming conference message.' + + buddy = self.buddies[conf_from] + if conf_name in self.conferences: + self.conferences[conf_name].incoming_message(buddy, message) + else: + log.info_s('conference(%s) - %s: %s', conf_name, conf_from, message) + + def confmsg_cancel(self, error_message): + msg = 'There was an error in sending a conference message:\n\n%s' \ + % error_message + self.hub.on_error(YahooError(msg)) + + def conflogon_brb(self, conf_name, conf_entering): + 'Incoming conference logon.' + + info('%s entering %s' % (conf_entering, conf_name)) + conf = self._create_conference(conf_name) + conf.buddy_join(self.buddies[conf_entering]) + if self.self_buddy not in conf: + conf.buddy_join(self.self_buddy) + + + def conflogoff_brb(self, conf_name, conf_leaving): + 'Buddy has left conversation.' + + if conf_name in self.conferences: + self.conferences[conf_name].buddy_leave(self.buddies[conf_leaving]) + + if conf_leaving == self.self_buddy.name: + del self.conferences[conf_name] + else: + log.warning('got conf logoff for %s from room %s, but rooms are %r', + conf_leaving, conf_name, self.conferences.keys()) + + @callsback + def make_chat_and_invite(self, buddies_to_invite, convo=None, room_name=None, server=None, notify=False, callback=None): + if not buddies_to_invite: + return common.protocol.make_chat_and_invite(self, buddies_to_invite, convo=convo, + room_name=room_name, server=server, notify=notify, callback=callback) + + buddynames = [b.name for b in buddies_to_invite] + self.invite_to_conference(self._gen_room_name(), buddynames, callback=callback) + + @callsback + def invite_to_conference(self, roomname, buddyname, message=None, callback=None): + 'Invite performed after a "conference logon"' + + myname = self.self_buddy.name + + log.info('inviting %r to %r', buddyname, roomname) + + buddies = [buddyname] if isinstance(buddyname, basestring) else buddyname + buddies_args = [] + for bud in buddies: + buddies_args.extend(['conf_tobuddy', bud]) + + self.callbacks['confinvite.' + roomname] = callback + self.send('confinvite', 'available', [ + 'frombuddy', myname, + 'conf_buddy', myname, + 'conf_name', roomname, + 'conf_invite_message', unicode(message or 'Join my conference...').encode('utf8'), + 'msg_encoding', '1'] + + buddies_args + + ['flag', '256']) + + def _gen_room_name(self): + return '%s-%d' % (self.self_buddy.name, random.randint(1, sys.maxint)) + + @callsback + def join_chat(self, room_name = None, convo = None, server = None, notify_profile=True, callback = None): + ''' + Starts a conference. + ''' + + def success(conf): + callback.success(conf) + if notify_profile: + from common import profile + profile.on_entered_chat(conf) + + self.socket.conflogon(room_name or self._gen_room_name(), success) + + # + # Yahoo! 360 + # + + def yahoo360_available(self, to, yahoo360xml): + yahoo360.handle(self, yahoo360xml) + + def yahoo360update_available(self, to, mingle_xml): + yahoo360.handle(self, mingle_xml) + + def yahoo360update_brb(self, mingle_xml, unix_time = None, ): + yahoo360.handle(self, mingle_xml) + + # + # Status Updates + # + + def logoff_brb(self, contact): + "A buddy has logged off." + self.buddies[contact].signoff() + + def ping_brb(self, **response): + info("PING: %r", response) + + def lastlogin_brb(self, unix_time): + 'Received at the end of the login process.' + + info('lastlogin: %r', unix_time) + + def newcontact_brb(self, **response): + print response + + def newmail_brb(self, count): + self.hub.on_mail(self, count) + + + def logoff_cancel(self): + 'Server is informing you that this username is logged on in another location.' + + self.set_disconnected(self.Reasons.OTHER_USER) + + def set_profile(self, msg, profile=None): + log.warning('TODO: set yahoo profile to %r', msg) + + def update_buddy(self, contact, status = None, custom_message = '', away = '0', + idle_seconds = None, + idle_duration_privacy = None, login = False, + **k): + + buddy = self.buddies[contact] + + set = lambda k, v: setattr(buddy, k, v) + status = int(status) if status is not None else status + idle = None + + checksum = k.get('checksum', None) + if checksum is not None: + log.warning('login checksum for %r: %r', contact, checksum) + set('_login_icon_hash', checksum) + set('icon_hash', checksum) + + # available + if status == 0: + set('status', 'available') + set('status_message', u'') + + # custom message + elif status == 99 or custom_message: + set('status', 'away' if away != '0' else 'available') + set('status_message', custom_message) + + # "normal" busy states like out to lunch + elif status in nice_statuses: + nice_status = nice_statuses[status] + set('status', 'away') + set('status_message', nice_status) + + # idle + elif status == 999: + if not idle_seconds: + idle_seconds = 0 + + if login: + idle = int(time() - int(idle_seconds)) + else: + idle = int(time()) + + set('status', 'idle') + + elif status == -1: + set('status', 'offline') + + else: + log.warning('unknown status %s', status) + + set('idle', idle) + buddy.notify() + + awaylogin_brb = \ + yahoo6_status_update_brb = \ + update_buddy + + + def logon_available_raw(self, ydict_iter): + y = list(ydict_iter) + + idxs = [i for i, (k, v) in enumerate(y) if k == ykeys['contact']] + + for x in xrange(len(idxs)): + # split up the packet by contact + i = idxs[x] + try: j = idxs[x+1] + except IndexError: j = len(y) + + opts = yiter_to_dict(y[i:j]) + opts['login'] = True + self.update_buddy(**opts) + + if self.state != self.Statuses.ONLINE: + log.info('setting online in logon_available_raw') + self.change_state(self.Statuses.ONLINE) + self.get_remotes() + + awaylogin_steppedout_raw = \ + logon_brb_raw = \ + logon_available_raw + + def awaylogin_available_raw(self, ydict_iter): + self.logon_available_raw(ydict_iter) + self.fix_unknown_statuses() + + def authresp_cancel(self, **k): + if self.state == self.Statuses.ONLINE: + self.set_disconnected(self.Reasons.CONN_FAIL) + else: + error = k.get('error', None) + if error == '1011': + self.set_disconnected(self.Reasons.CONN_FAIL) + else: + self.set_disconnected(self.Reasons.BAD_PASSWORD) + + def send(self, command, status, ydict = None, **kw): + 'Sends a Yahoo dictionary over the network.' + + # to_ydict can either take a mapping or a FLAT list of pairs + + if ydict is None: + packet = kw + elif not hasattr(ydict, 'items'): + # maintain order + packet = ydict + for k, v in kw.iteritems(): + packet.extend([k, v]) + else: + packet = ydict + packet.update(kw) + + self.socket.ysend(commands[command], statuses[status], data = packet) + + def yahoo_packet(self, command, status, *a, **kw): + if a: + assert not kw + ydict = a + else: ydict = kw + + return self.socket.make_ypacket(commands[command], statuses[status], data=ydict) + + def yahoo_packet_v(self, version, command, status, *a, **kw): + assert isinstance(version, int) + + if a: + assert not kw + ydict = a + else: ydict = kw + + return self.socket.make_ypacket(commands[command], statuses[status], version, + data=ydict) + + def yahoo_packet_v_bin(self, version, command, status, *a, **kw): + assert isinstance(version, int) + + if a: + assert not kw + ydict = a + else: ydict = kw + ys = YahooSocketBase(self) + ys.session_id = self.socket.session_id + return ys.make_ypacket(commands[command], statuses[status], version, + data=ydict) + + def convo_for(self, buddy): + print self.conversations.keys() + + service = None + if not isinstance(buddy, basestring): + service = buddy.service + buddy = buddy.name + + if not isinstance(buddy, YahooBuddy): + assert isinstance(buddy, basestring) + buddy = self.buddies[buddy] + + # This is a hack to change a buddy's service to the one that was asked for via the + # buddy argument. We have a problem in that self.buddies keys by name, but really + # you could have an MSN buddy and a Yahoo buddy with the same name. + if service is not None: + buddy._service = service + + if buddy in self.conversations: + convo = self.conversations[buddy] + else: + convo = YahooConvo(self, buddy) + self.conversations[buddy] = convo + return convo + + def exit_conversation(self, convo): + '''Conversations call this with themselves as the only argument when + they are exiting.''' + + for k, v in self.conversations.iteritems(): + if v is convo: + return self.conversations.pop(k) + + def __repr__(self): + return '' % self.username + + @property + def cookie_str(self): + ck = 'T=%s; path=/; domain=.yahoo.com; Y=%s; path=/; domain=.yahoo.com' % (self.cookies['T'], self.cookies['Y']) + return ck + + + def set_idle(self, since=0): + if since: + self._old_status = self.status + self.set_message(self._status_message, 'idle') + else: + self.set_message(self._status_message, self._old_status) + self._old_status = 'idle' + + @action() + def go_mobile(self): + pass + + @action() + def send_contact_details(self): + pass + + @action() + def send_messenger_list(self): + pass + + @action() + def my_account_info(self): + import wx + wx.LaunchDefaultBrowser('http://msg.edit.yahoo.com/config/eval_profile') + # needs token for autologin + # self.hub.launch_url('...') + + @action() + def my_web_profile(self): + import wx + wx.LaunchDefaultBrowser('http://msg.edit.yahoo.com/config/edit_identity') + pass # needs token + # self.hub.launch_url('...') + + def allow_message(self, buddy, mobj): + ''' + Returns True if messages from this buddy are allowed, False otherwise. + ''' + super = common.protocol.allow_message(self, buddy, mobj) + if super in (True, False): + return super + + if buddy.blocked: + return False + elif self.block_unknowns: + for gname in self.groups: + if buddy.name.lower() in (b.name.lower() for b in self.groups[gname]): + return True + else: + return False + else: + return True + diff --git a/digsby/src/yahoo/YahooSocket.py b/digsby/src/yahoo/YahooSocket.py new file mode 100644 index 0000000..2bb01de --- /dev/null +++ b/digsby/src/yahoo/YahooSocket.py @@ -0,0 +1,294 @@ +''' +YahooSocket.py + +For YMSG internals. + +Pulls and pushes Yahoo packets from and to the network, using asynchat. +''' +from __future__ import with_statement +from logging import getLogger +from struct import pack +from util import to_storage, dictargcall, traceguard +from util.primitives.structures import unpack_named +from .yahooutil import YahooLoginError, format_packet, to_ydict, from_ydict, \ + from_ydict_iter, header_pack, header_size, header_desc +import common +import sys +import traceback +from . import yahoolookup as ylookup + +DEFAULT_YMSG_VERSION = 17 + +log, loginlog = getLogger('yahoo'), getLogger('yahoo.login') + +packets_log = getLogger('yahoo.packets') + +class YahooConnectionBase(object): + def __init__(self, yahoo): + self.yahoo = yahoo # Reference to YahooProtocol object + self.session_id = 0 + packets_log.debug('initializing active_gens, self.id: %d', id(self)) + self.active_gens = {} + + def handle_packet(self, header, data): + 'Handle an incoming packet.' + + self.session_id = header.session_id + + # The first possibility is that a generator is waiting on a packet + # with the incoming command/status pair. If so, pass the packet off to + # it. + packets_log.debug('header %r', (header.command, header.status)) + packets_log.debug('active_gens %r', self.active_gens) + if (header.command, header.status) in self.active_gens: + gen = self.active_gens.pop((header.command, header.status)) + self.async_proc(gen, header, data) + return + + # Otherwise: dynamic dispatch on the command/status.... + command_str = ylookup.commands.get(header.command, '') + status_str = ylookup.statuses.get(header.status, '') + target = self.yahoo + + + # 1) is there a "raw" function which just wants an iterable? + fnname = '%s_%s' % (command_str, status_str) + + with traceguard: + packet_str = format_packet(data, sensitive = not getattr(sys, 'DEV', True)) + packets_log.debug("<== %s\n%s", fnname, packet_str) + + raw_fn = '%s_raw' % fnname + + if hasattr(target, raw_fn): + try: + return getattr(target, raw_fn)(self.from_ydict_iter(data)) + except Exception: + traceback.print_exc() + finally: + return + + # No. Dictarg call to match yahoo dictionary keys -> argument names, + # and call the respective function. + + if command_str and status_str: + fn = "%s_%s" % (command_str, status_str) + if hasattr(target, fn): + func = getattr(target, fn) + + try: + return dictargcall(func, self.from_ydict(data), ylookup.ykeys) + except Exception: + # Print out the exception + traceback.print_exc() + + # and the function we WOULD have called. + print >> sys.stderr, ' File "%s", line %d, in %s' % \ + (func.func_code.co_filename, + func.func_code.co_firstlineno, func.func_name) + + return + else: + log.warning('no fn, %s', fn) + + # Unhandled packets get their contents dumped to the console. + unhandled_text = ''.join([ + 'unhandled packet: ', + command_str if command_str else str(header.command), + '_', + status_str if status_str else str(header.status), + '\n', + '\n'.join('%s: %s' % (k, v) for k, v in self.from_ydict_iter(data)) + ]) + + log.warning( "%s:::%r", unhandled_text, data) + + def async_proc(self, gen, header=None, data=None): + 'Processes one generator command.' + + try: + # process a next iteration + args = (header, data) if header else None + + # Send an incoming packet, if there is one, to the generator, + # picking up where it left off. + cmd, wait_on, packet = gen.send(args) + + # The generator yields a command: + while cmd == 'send': + self.push(packet) # Send a packet over the network + cmd, wait_on, packet = gen.next() # And get the next command + + if cmd == 'wait': + # On a wait, queue the generator and it's waiting condition. + packets_log.debug('queueing: %r %r', wait_on, gen) + self.active_gens[wait_on] = gen + packets_log.debug('self.active_gens: %r', self.active_gens) + + # wait_on will be a command/status pair + assert isinstance(wait_on, tuple) + + except StopIteration: + # If the generator has stopped, it should definitely not be waiting + # on a packet. + assert gen not in self.active_gens.values() + except YahooLoginError: + self.yahoo.set_disconnected(self.yahoo.Reasons.BAD_PASSWORD) + + def gsend(self, command, status, data={}, **kwargs): + """Generator send. Returns a command to async_proc to send the indicated + packet.""" + + # set a default version flag to 13 + if 'v' not in kwargs: v = DEFAULT_YMSG_VERSION + else: v = kwargs['v'] + + # lookup codes for command, status + if isinstance(command, str): command = ylookup.commands[command] + if isinstance(status, str): status = ylookup.statuses[status] + + # convert dictionary to a yahoo network dictionary + assert isinstance(data, (dict, list)) + + packet = self.make_ypacket(command, status, v, data) + return ('send', None, packet) + + def gwait(self, command, status, err_fn): + "Signals async_proc to wait for a specified packet type." + + command, status = ylookup.commands[command], ylookup.statuses[status] + return ( 'wait', (command, status), err_fn ) + + def make_ypacket(self, command, status, version=DEFAULT_YMSG_VERSION, data={}): + ''' + Construct a Yahoo packet out of integers for command and status, + an optional version number, and byte data. + ''' + + if not isinstance(command, (int, long)): + raise TypeError("command is", command, "but should be an int!") + if not isinstance(status, (int, long)): + raise TypeError("status is", status, "but should be an int!") + + return self.ypacket_pack(command, status, version, data) + + def ysend(self, command, status, version=DEFAULT_YMSG_VERSION, data={}): + assert isinstance(version, int) + if getattr(sys, 'DEV', False): + with traceguard: + if isinstance(data, list): + group = lambda t, n: zip(*[t[i::n] for i in range(n)]) + data_str = '' + for x, y in group(data, 2): + data_str += ' %r: %r\n' % (x, y) + else: + from pprint import pformat + data_str = pformat(data) + packets_log.debug("--> %s_%s\n%s", + ylookup.commands.get(command), + ylookup.statuses.get(status), + data_str) + packet = self.make_ypacket(command, status, version, data) + self.push(packet) + +class YahooSocketBase(YahooConnectionBase): + @staticmethod + def to_ydict(data): + return to_ydict(data) + + @staticmethod + def from_ydict(data): + return from_ydict(data) + + @staticmethod + def from_ydict_iter(data): + return from_ydict_iter(data) + + def ypacket_pack(self, command, status, version, data): + data = self.to_ydict(data) + #localize the values for debugging purposes + vars = header_pack, "YMSG", version, 0, len(data), command, status, self.session_id + return pack(*vars) + data + + +class YahooSocket(YahooSocketBase, common.socket): + def __init__(self, yahoo, server): + common.socket.__init__(self) + YahooSocketBase.__init__(self, yahoo) + + assert isinstance(server, tuple) and len(server) == 2 + self.server = server + # socket is either receiving a header or receiving the rest of the data + self.set_terminator(header_size) + self.getting_header = True + self.data, self.byte_count = [], 0 + + def __str__(self): + return "YahooSocket(%s:%d, %d bytes in)" % \ + (self.server[0], self.server[1], self.byte_count) + + def __repr__(self): + return '<%s(%s:%d) - sId: %s, bytes in: %d>' % \ + (self.__class__.__name__, self.server[0], self.server[1], + self.session_id, self.byte_count) + + # + # async_chat handlers + # + # These functions occur as respones to network events. + # + + def handle_connect(self): + raise NotImplementedError + + def handle_close(self): + raise NotImplementedError + + def collect_incoming_data(self, data): + self.data.append(data) + self.byte_count += len(data) + + def handle_error(self, *a, **k): + traceback.print_exc() + raise NotImplementedError + + def handle_expt(self): + raise NotImplementedError + + def found_terminator(self): + ''' + Invoked by asynchat when either the end of a packet header has + been reached, or when the end of packet data has been reached. + ''' + datastr = ''.join(self.data) + + # Flip between receive header/data... + # either the entire header has just arrived + if self.getting_header: + self.getting_header = False + + self.header = to_storage(unpack_named( + *(header_desc + tuple([datastr])))) + if self.header.ymsg != 'YMSG' or self.header.size < 0: + return log.warning('invalid packet') + + if self.header.size > 0: + # Tell asynchat to read _size_ more bytes of data. + self.set_terminator(self.header.size) + else: + # In this case, there is no data--handle the packet now. + self.getting_header = True + self.set_terminator(header_size) + self.handle_packet(self.header, datastr) + self.data = [] + + # or else we just got the rest of the packet + else: + self.getting_header = True + self.set_terminator(header_size) + self.handle_packet(self.header, datastr[header_size:]) + self.data = [] + + def push(self, pkt): + #print 'sending', repr(pkt) + super(YahooSocket, self).push(pkt) diff --git a/digsby/src/yahoo/__init__.py b/digsby/src/yahoo/__init__.py new file mode 100644 index 0000000..4fb635c --- /dev/null +++ b/digsby/src/yahoo/__init__.py @@ -0,0 +1,10 @@ +""" + +Yahoo Protocol + +""" + +import yahoologinsocket +from YahooSocket import YahooSocket as socket +from YahooProtocol import YahooProtocol as protocol +from yahoobuddy import YahooBuddy as buddy \ No newline at end of file diff --git a/digsby/src/yahoo/endpoints.py b/digsby/src/yahoo/endpoints.py new file mode 100644 index 0000000..4944900 --- /dev/null +++ b/digsby/src/yahoo/endpoints.py @@ -0,0 +1,49 @@ +from .yahooHTTPConnection import YahooHTTPConnection +from .yahoologinsocket import YahooLoginSocket + +ALL_PORTS = (5050, 80, 25, 23, 20) + +S1_PORTS = (5050, 80) +S3_PORTS = (25, 23, 20) + +class ServerEndpoint(object): + def __init__(self, server_address, port): + self.address = server_address + self.port = port + + def __repr__(self): + return '' % (self.address, self.port) + + def __cmp__(self, other): + if type(other) == HTTPEndpoint: + if self.port in S3_PORTS: + return 1 + return -1 + if self.port not in ALL_PORTS: + if other.port in ALL_PORTS: + return -1 + return 0 + if other.port not in ALL_PORTS: + if self.port in ALL_PORTS: + return 1 + return 0 + if other.port in S1_PORTS and self.port in S1_PORTS: + return S1_PORTS.index(self.port) - S1_PORTS.index(other.port) + if other.port in S1_PORTS and self.port in S3_PORTS: + return 1 + if other.port in S3_PORTS and self.port in S1_PORTS: + return -1 + if other.port in S3_PORTS and self.port in S3_PORTS: + return S3_PORTS.index(self.port) - S3_PORTS.index(other.port) + assert False + + def make_socket(self, y): + return YahooLoginSocket(y, (self.address, self.port)) + +class HTTPEndpoint(object): + def __init__(self, server_address): + self.address = server_address + def __repr__(self): + return '' % (self.address,) + def make_socket(self, y): + return YahooHTTPConnection(y, self.address) diff --git a/digsby/src/yahoo/login.py b/digsby/src/yahoo/login.py new file mode 100644 index 0000000..336aa55 --- /dev/null +++ b/digsby/src/yahoo/login.py @@ -0,0 +1,122 @@ +import base64 +import cookielib +import logging +import traceback +import types +import urllib2 +import util.net + +log = logging.getLogger('yahoo.login') + +class YahooAuthException(Exception): + error_type = "Unknown" + error_msg = None + known_codes = {} + def __init__(self, resp_str): + Exception.__init__(self, resp_str) + try: + code = self.code = resp_str.split()[0] + except Exception: + self.code = None + else: + self.error_msg = _error_messages.get(code) + if code in self.known_codes: + self.__class__ = self.known_codes[code] + +_error_types = [ #these are NOT translatable + ('1212', 'Bad Password'), + ('1213', 'Security Lock'), + ('1221', 'Account Not Set Up'), + ('1235', 'Bad Username'), + ('1236', 'Rate Limit'), + ] + +_error_messages = { #these ARE translatable + '1212': _('Bad Password'), + '1213': _('There is a security lock on your account. Log in to http://my.yahoo.com and try again.'), + '1221': _('Account Not Set Up'), + '1235': _('Bad Username'), + '1236': _('Rate Limit'), + } + +for code, error_type in _error_types: + name = ''.join(['Yahoo'] + error_type.split() + ['Exception']) + klass = types.ClassType(name, (YahooAuthException,), dict(error_type = error_type)) + globals()[name] = YahooAuthException.known_codes[code] = klass + del name, klass, code, error_type #namespace pollution + +def yahoo_v15_auth(challenge, password, username, jar=None): + jar = jar if jar is not None else cookielib.CookieJar() + http_opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar), *util.net.GetDefaultHandlers()) + from time import time + now = int(time()) + url_password = util.net.UrlQuery("https://login.yahoo.com/config/pwtoken_get", + src='ymsgr', + ts=str(now), + login=username, + passwd=password, + chal=challenge) + resp = http_opener.open(url_password) + resp = resp.read() + log.info('Yahoo response starts: %r', resp[:100]) + + if not resp.startswith('0'): + raise YahooAuthException(resp) + + token = resp.split('\r\n')[1].split('=')[1] + url2 = util.net.UrlQuery("https://login.yahoo.com/config/pwtoken_login", + src='ymsgr', + ts=str(now), + token=token) + resp2 = http_opener.open(url2) + resp2 = resp2.read() + log.info('Yahoo response2 starts: %r', resp2[:100]) + #got '100' here from the sbcglobal response above + lines = resp2.split('\r\n') + + crumb = lines[1].split('=')[1] + y = lines[2] + t = lines[3] + y = y[2:] + t = t[2:] + return crumb, y, t, jar + +def yahoo64(buffer): + 'Wacky yahoo base 64 encoder.' + return base64.b64encode(buffer, '._').replace('=', '-') + +LOAD_BALANCERS = ['http://vcs2.msg.yahoo.com/capacity', #2 had a lower ping + 'http://vcs1.msg.yahoo.com/capacity'] + +HTTP_LOAD_BALANCERS = ['http://httpvcs2.msg.yahoo.com/capacity', + 'http://httpvcs1.msg.yahoo.com/capacity'] + +def get_load_balance_info(server): + data = util.net.wget(server) + site = data.split() + pairs = [line.split('=') for line in site] + d = dict(pairs) + return d['CS_IP_ADDRESS'] + +def async_get_load_balance_info(server, callback=None): + from common import asynchttp + def success(req, resp): + try: + data = resp.read() + site = data.splitlines() + pairs = [line.strip().split('=') for line in site] + d = dict((x.strip(), y.strip()) for x,y in pairs) + callback.success(d['CS_IP_ADDRESS']) + except Exception, e: + traceback.print_exc() + callback.error(e) + def error(*a): + callback.error(None) + def timeout(*a): + callback.timeout(None) + + asynchttp.httpopen(server, + success = success, + error = error, + timeout=timeout) + diff --git a/digsby/src/yahoo/peerfiletransfer.py b/digsby/src/yahoo/peerfiletransfer.py new file mode 100644 index 0000000..51b197f --- /dev/null +++ b/digsby/src/yahoo/peerfiletransfer.py @@ -0,0 +1,255 @@ +from common.filetransfer import IncomingFileTransfer +from util.primitives import Storage +from httplib import HTTPConnection +from urllib import quote +from util.net import FileChunker +from path import path +from urlparse import urlparse +from util.threads.threadpool2 import threaded +from common.filetransfer import OutgoingFileTransfer +from random import choice +from traceback import print_exc +from .yfiletransfer import OutgoingYHTTPXfer +import dns +import logging + +log = logging.getLogger('yahoo.peerft'); info = log.info; error = log.error + + +class YahooPeerFileTransfer(IncomingFileTransfer): + def __init__(self, yahoo, buddy, **kws): + IncomingFileTransfer.__init__(self) + self.yahoo = yahoo + self.buddy = buddy + self.name = kws['filename'] + self.packet = Storage(**kws) + self.numfiles = 1 + self.size = int(kws['filesize']) + + self.on_get_buddy(self.buddy) + + @property + def protocol(self): + return self.yahoo + + def decline(self): + self.yahoo.send('peerrequest', 'available', + ['frombuddy', self.packet.to, + 'to', self.packet.buddyname, + 'p2pcookie', self.packet.p2pcookie, + 'acceptxfer', '4']) + self.state = self.states.CANCELLED_BY_YOU + IncomingFileTransfer.decline(self) + + def accept(self, openfile): + self.packet.acceptxfer = '3' + self.openfile = openfile + self.yahoo.send('peerrequest', 'available', [ + 'frombuddy', self.packet.to, + 'to', self.packet.buddyname, + 'p2pcookie', self.packet['p2pcookie'], + 'acceptxfer', '3', + ]) + self.state = self.states.CONNECTING + + @threaded + def connect(self, buddy, filename, peer_path, p2pcookie, peerip, transfertype): + myname = self.yahoo.self_buddy.name + if transfertype == '1': + u = urlparse(peerip) + url = u.path + '?' + u.query + peerip = u.netloc + else: + url = '/relay?token=%s&sender=%s&recver=%s' % (quote(peer_path), myname, buddy) + # connect to them + if peerip: + info( 'Got a relay IP; connecting...' ) + + info( 'HTTP HEAD: %s', peerip) + relayconn = HTTPConnection(peerip) + headers = {'User-Agent':'Mozilla/4.0 (compatible; MSIE 5.5)', + 'Accept':'*/*', + 'Cache-Control':'no-cache', + 'Cookie': self.yahoo.cookie_str } + + relayconn.request('HEAD', url, headers = headers) + + # Check for OK + response = relayconn.getresponse() + status = response.status + if status != 200: + return log.error('ERROR: HEAD request returned a status of %d', status) + print "resp1 in" + print response.read() + print "resp1 out" + + # Now make a GET to the same host/url + relayconn.request('GET', url, headers = headers) + + if transfertype != '1': + self.yahoo.send('peerinit', 'available', + frombuddy = myname, + to = buddy, + p2pcookie = p2pcookie, + filename = filename, + transfertype = '3', + peer_path = peer_path + ) + + response = relayconn.getresponse() + status = response.status + if status != 200: + return log.error('ERROR: GET req returned %d status', status) + + response.info = lambda: dict(response.getheaders()) + + headers = response.info() + + bytecounter = lambda: 0 + + if self.state != self.states.CANCELLED_BY_YOU: + self.state = self.states.TRANSFERRING + self.filepath = path(self.openfile.name) + gen = FileChunker.tofile_gen(response, self.openfile, self.progress, + bytecounter=bytecounter) + self.chunker = gen.next() + try: + gen.next() + except StopIteration: + pass + if self.bytes_read != self.size: + self.state = self.states.CONN_FAIL_XFER + + if self.state != self.states.CANCELLED_BY_YOU: + self._ondone() + + + else: # not peerip + log.warning('No peer IP, buddy will connect to me. NOT IMPLEMENTED') + + def progress(self, bytes_read): + self._setcompleted(bytes_read) + self.bytes_read = bytes_read + + def cancel(self, me=True, state=None): + if me: + self.yahoo.send('peerinit', 'cancel', + ['frombuddy', self.packet.to, + 'to', self.packet.buddyname, + 'p2pcookie', self.packet.p2pcookie, + 'error', '-1']) + + if state is None: + state = self.states.CANCELLED_BY_YOU + self.state = state + else: + self.state = self.states.CANCELLED_BY_BUDDY + try: + self.chunker.cancelled = True + except Exception: + print_exc() + self._ondone() + + def _ondone(self): + try: + self.yahoo.file_transfers.pop((self.packet.buddyname, self.packet.p2pcookie)) + except Exception: + log.warning('_ondone pop failed') + try: + IncomingFileTransfer._ondone(self) + except Exception: + log.warning('_ondone call failed') + +class YahooOutgoingPeerFileXfer(OutgoingYHTTPXfer): + +# conn_host = 'relay. + + def __init__(self, protocol, buddy = None, fileinfo = None): + OutgoingYHTTPXfer.__init__(self, protocol, buddy=buddy, fileinfo=fileinfo, initiate=False) + self.yahoo = protocol + + def send_offer(self): + log.critical('send_offer') + import string + letters = string.ascii_letters + string.digits + cookie = [] + for _i in xrange(22): + cookie.append(choice(letters)) + self.cookie = cookie = ''.join(cookie) + '$$' + + self.yahoo.file_transfers[(self.buddy.name, self.cookie)] = self + + self.yahoo.send( + 'peerrequest', 'available', + ['frombuddy', self.yahoo.self_buddy.name, + 'to', self.buddy.name, + 'p2pcookie', self.cookie, + 'acceptxfer', '1', + '266', '1', + 'begin_mode', '268', + 'begin_entry', '268', + 'filename', self.name, + 'filesize', str(self.size), + 'end_entry', '268', + 'end_mode', '268']) + self.state = self.states.WAITING_FOR_BUDDY + + def do_setup(self, buddy, p2pcookie, filename = None, peer_path=None, **k): + log.critical('do_setup') + self.state = self.states.CONNECTING + assert p2pcookie == self.cookie + @threaded + def get_ip(): + for ip in dns.resolver.query('relay.msg.yahoo.com', 'A'): + return str(ip) + raise Exception, 'no ip for relay transfer' + + def send_later(ip): + self.host = ip + self.conn_host = ip + self.yahoo.send( + 'peersetup', 'available', + ['frombuddy', self.yahoo.self_buddy.name, + 'to', self.buddy.name, + 'p2pcookie', self.cookie, + 'filename', self.name, + 'transfertype', '3', + 'peerip', ip]) + + get_ip(success = send_later) + + def go(self, buddy, p2pcookie, filename, transfertype, peer_path, **k): + log.critical('go') + peer_path = quote(peer_path) + self.http_path = '/relay?token=' + peer_path + '&sender=' + self.yahoo.self_buddy.name + '&recver=' + buddy + self.state = self.states.TRANSFERRING + try: + filesize = self.filepath.size + fileobj = file(self.filepath, 'rb') + except Exception, e: + print_exc() + self.state = self.states.CONN_FAIL + self.on_error() + else: + self._post_file('', self.yahoo.cookie_str, fileobj = fileobj, filesize = filesize) + + def cancel(self, me=True): + self.cancelled = True + if me: + self.yahoo.send( + 'peersetup', 'cancel', + ['frombuddy', self.yahoo.self_buddy.name, + 'to', self.buddy.name, + 'p2pcookie', self.cookie, + '66', '-1']) + self.state = self.states.CANCELLED_BY_YOU + else: + self.state = self.states.CANCELLED_BY_BUDDY + try: + self.conn.close() + del self.conn + except: + pass + + + diff --git a/digsby/src/yahoo/yahoo360.py b/digsby/src/yahoo/yahoo360.py new file mode 100644 index 0000000..05a938e --- /dev/null +++ b/digsby/src/yahoo/yahoo360.py @@ -0,0 +1,150 @@ +''' +Yahoo! 360 updates. + +Incoming XML from the Yahoo server is parsed and stored in buddy.y360updates. +''' + +from util import Storage +from datetime import datetime +from util import soupify, odict +from logging import getLogger; log = getLogger('yahoo360'); info = log.info +from util.BeautifulSoup import BeautifulStoneSoup + +FEED_TEXT = u'New RSS Feed' + +def handle(yahoo, y360xml, updates_cb = None): + 'Handle incoming Yahoo! 360 XML, updating buddy states as necessary.' + log.info(y360xml) + + s = BeautifulStoneSoup(y360xml, + convertEntities=BeautifulStoneSoup.ALL_ENTITIES, + fromEncoding = 'utf-8') + if not s or not s.mingle: return + G = globals() + + updates = [] + for buddy in s.mingle.vitality.findAll('buddy'): + vitals = buddy.findAll('vital') + + buddyobj = yahoo.buddies[buddy['id']] + + try: + buddyobj.setnotifyif('y360hash', buddy['hash']) + except KeyError: + buddyobj.setnotifyif('y360hash', buddy['u'][len('http://360.yahoo.com/profile-'):]) + + + for vital in vitals: + vitaltype = 'vital_' + vital['p'][:-1] + print 'vitaltype', vitaltype + if vitaltype in G: + url, vid = vital.get('u', profile_link(buddy['hash'])), int(vital['id']) + cdata = vital.renderContents(None) + content = (u' - %s' % cdata) if cdata.strip() else '' + + # Remove any old RSS Feed entries if we got a new one + if vitaltype == 'vital_feeds': + old_updates = buddyobj.y360updates + if old_updates: + for (update, tstamp) in list(old_updates): + if update[1].startswith(FEED_TEXT): + old_updates.remove((update, tstamp)) + + + updates.append( (G[vitaltype](url, vid, content), # "profile" formatted + int(vital['t'])) ) # timestamp + else: + log.warning('unhandled 360 vital %s', vitaltype) + + if updates: + if updates_cb: return updates_cb(updates) + + log.info('received %d yahoo 360 updates for %s', len(updates), buddy['id']) + + + ups = list(buddyobj.y360updates) + ups.extend([update for update in updates if update not in ups]) + + buddyobj.setnotifyif('y360updates', ups) + +def profile_link(hash): + return 'http://360.yahoo.com/profile-' + hash + +def vital_blog(url, vid, content): + return url, u'New Blog Post' + content + +def vital_feeds(url, vid, content): + return url, FEED_TEXT + content + +def vital_profile(url, vid, content): + if vid == 2: + text = u'New Blast' + content + else: + text = u'Updated Profile' + content + + return url, text + +def vital_lists(url, vid, content): + return url, u'New Lists' + content + +if __name__ == '__main__': + s = ''' + + + + + + +''' + + from pprint import pprint + handle(None, s, lambda u: pprint(u)) + +''' +some samples for reference: + +a blog update + + + + Entry for January 17, 2007 24321321: another one!!! + + + + +a blast change (with url) + + + + this is my blast message + + + +another blast change (with url) + + + + another blast + + + +a profile update + + + + + + + + + +a list edit + + + + + + + + +''' diff --git a/digsby/src/yahoo/yahooHTTPConnection.py b/digsby/src/yahoo/yahooHTTPConnection.py new file mode 100644 index 0000000..62f88ee --- /dev/null +++ b/digsby/src/yahoo/yahooHTTPConnection.py @@ -0,0 +1,365 @@ +from .YahooSocket import YahooConnectionBase +from .yahoologinsocket import YahooLoginBase +from .yahoolookup import ykeys +from common import callsback +from hashlib import sha1 +from itertools import izip +from logging import getLogger +from .yahooutil import from_utf8 +from util.xml_tag import tag +from common.asynchttp.cookiejartypes import CookieJarHTTPMaster +from urllib2 import HTTPError +from httplib import HTTPResponse as libHTTPResponse +from common.asynchttp.httptypes import HTTPResponse as asyncHTTPResponse +RESPONSE_TYPES = (libHTTPResponse, asyncHTTPResponse) +import cookielib +import re +import string + +log = getLogger('yahoohttp') + +argsep = '^$' + +ASCII_LETTERS = '@' + ''.join(sorted(string.ascii_letters))[:0x1f] + +caret_split_re = re.compile('((?:\^.)|!(?:\^.))') + +even_caret = r'(?:(?:\^{2})*)' +single_caret_dollar = r'(?:\^\$)' + +odd_caret_dollar_re = re.compile(even_caret + single_caret_dollar) + +parser_re = re.compile('(' + even_caret + (single_caret_dollar + '?') + ')') + +def decode_caret(s): + ss = caret_split_re.split(s) + ret = [] + for s2 in ss: + if s2 == '^^': + s2 = '^' + elif s2 == '^@': + s2 = '\0' + elif len(s2) == 2 and s2[0] == '^': + ascii_pos = ASCII_LETTERS.find(s2[1]) + if ascii_pos >= 0: # != -1 + s2 = chr(ascii_pos) + ret.append(s2) + return ''.join(ret) + +#TODO: use StringIO +def encode_caret(s): + ret = [] + for char in s: + if char == '^': + ret.append('^^') + continue + if 0 <= ord(char) < len(ASCII_LETTERS): # < 0x20 (1 + 0x1f) + ret.append('^' + ASCII_LETTERS[ord(char)]) + else: + ret.append(char) + return ''.join(ret) + +class YahooHTTPConnection(YahooConnectionBase, YahooLoginBase): + def __init__(self, yahoo, server): + YahooConnectionBase.__init__(self, yahoo) + YahooLoginBase.__init__(self, yahoo) + self.server = server + self.init_vars() + self.handle_connect() + + def init_vars(self): + self.ClientCounter = 0 + + #I kid you not, an 8 digit integer string representation encoded in hex + # from the last 8 digits of the string representation of the lower word + # of GetSystemTimeAsFileTime. Random seems just as good, since that value + # in units of 100 nanoseconds, 8 digits will cycle every 10 seconds + #http://forum.sharpdressedcodes.com/index.php?showtopic=589 + #wtfinterns? + from random import randint + self.secret = '%08d' % randint(0, int('9'*8)) + self.Secret = (self.secret).encode('hex') + self.jar = cookielib.CookieJar() + + self.client_http_opener = CookieJarHTTPMaster(jar=self.jar) + self.server_http_opener = CookieJarHTTPMaster(jar=self.jar) + + def close(self): + self._close_server() + #if we don't inform the server that we're leaving, we won't appear offline until + # it decides we have timed-out. + if self.client_http_opener is not None: + self._push_cb(self.yahoo.yahoo_packet('logoff', 'available'), + success = self._close_client, + error = self._close_client) + + def _close_server(self, *a): + self.server_http_opener, sho = None, self.server_http_opener + + if sho is not None: + sho.close_all() + + def _close_client(self, *a): + self.client_http_opener, cho = None, self.client_http_opener + + if cho is not None: + cho.close_all() + + @classmethod + def to_ydict(cls, d): + if not d: + return '' + + def to_ydict_entry(k, v): + try: n = int(k) + except ValueError: + try: + n = ykeys[k] + except: + log.warning('to_ydict got a dict with a non number key: %s', k) + return '' + + try: + v = str(v) + except UnicodeEncodeError: + v = unicode(v).encode('utf-8') + + return ''.join([str(n), argsep, encode_caret(v), argsep]) + + # find some way to iterate + if hasattr(d, 'iteritems'): item_iterator = d.iteritems() + elif isinstance(d, (list, tuple)): item_iterator = izip(d[::2],d[1::2]) + + return ''.join(to_ydict_entry(k,v) for k,v in item_iterator) + + @classmethod + def from_ydict(cls, data): + d = {} + for k, v in cls.from_ydict_iter(data): + if k in d: + log.warning('duplicate %s: %s', k, v) + #raise AssertionError() + d[k] = v + return d + + @classmethod + def from_ydict_iter(cls, data): + rval = None + retval = [] + for val in parser_re.split(data): + if odd_caret_dollar_re.match(val) is not None: + if rval is None: + retval.append(decode_caret(val[:-2])) + else: + retval.append(decode_caret(rval+val[:-2])) + rval = None + else: + if rval is None: + rval = val + else: + rval += val + + keys, values = retval[::2], retval[1::2] + + # see note in yahooutil:from_ydict_iter + values = [from_utf8(v) for v in values] + + return izip(keys, values) + + def ypacket_pack(self, command, status, version, data): + data = self.to_ydict(data) + return Ymsg(command, status, version, data, vendor_id='0', session=self) + + def push(self, packet, count=0): + self._push_cb(packet, + success = self.recieve_response, + error = lambda *a: self.handle_client_stream_error(packet=packet, count=count, *a)) + + @callsback + def _push_cb(self, packet, callback=None): + http, to_send = self.wrap_packet(packet) + self._client_send(http, to_send, callback=callback) + + def wrap_packet(self, packet): + is_auth = getattr(packet, '_yahoo_auth', False) + http = 'http' + if is_auth: + http = 'https' + assert self.ClientCounter == 0 + to_send = SessionWrapper(packet, self) + else: + to_send = SessionWrapper(packet, self) + self.ClientCounter += 1 + return http, to_send + + @callsback + def _client_send(self, http, to_send, callback=None): + if self.client_http_opener is None: + callback.error(Exception("Not connected")) + if self.yahoo: + self.yahoo.set_disconnected(self.yahoo.Reasons.CONN_LOST) + return + + log.debug_s('yahoo sending\n%r', str(to_send)) + self.client_http_opener.open(http + '://' + self.server + '/', str(to_send), + callback=callback) + #if request already in progress, wait, let responses go first? keep order separate until actually sending. + + def server_update(self): + to_send = SessionWrapper(None, self, server=True) + self.ClientCounter += 1 + log.debug_s('yahoo sending\n%r', str(to_send)) + self.server_http_opener.open('http://' + self.server + '/', str(to_send), + success = self.server_response, + error = self.handle_server_stream_error) + #wait for list15 "online" to start this chain. + #SessionWrapper(None, self, server=True), send on server connection. + #wait for response, send again. + + def server_response(self, req, resp=None): + try: + self.recieve_response(req, resp) + except Exception: + pass + self.server_update() + + def handle_server_stream_error(self, req, resp=None, *a, **k): + log.error('handle_server_stream_error %r, %r, %r, %r', req, resp, a, k) + if isinstance(resp, RESPONSE_TYPES): + return self.server_response(req, resp) + if isinstance(req, HTTPError) or isinstance(resp, HTTPError): + return self.server_response(req, resp) + import traceback;traceback.print_exc() + self.close() + if self.yahoo: + self.yahoo.set_disconnected(self.yahoo.Reasons.CONN_LOST) + + def handle_client_stream_error(self, req, resp=None, packet=None, count=0, *a, **k): + log.error('handle_client_stream_error %r, %r, %r, %r, %r, %r', req, resp, packet, count, a, k) + if resp is None: #urllib compatibility + resp = req + del req + #it's not going to be a 200 if we're in the error handler, no need to check that here. + + if isinstance(resp, RESPONSE_TYPES) or isinstance(resp, HTTPError): + if count < 3: + return self.push(packet, count + 1) + + self.close() + if self.yahoo: + self.yahoo.set_disconnected(self.yahoo.Reasons.CONN_LOST) + + def handle_connect(self): + return self.async_proc(self.logon()) + + def send_auth_req(self, user): + cmd, wait, pkt = super(YahooHTTPConnection, self).send_auth_req(user) + pkt._yahoo_auth = True + return cmd, wait, pkt + + def recieve_response(self, req, resp=None): + if resp is None: #urllib compatibility + resp = req + del req + #should probably all be treated the same, who knows what can come back from where, nor should we care. + data = resp.read() + + log.debug_s('yahoo recieved\n%r', data) + + Session = tag(data) + #deal with sequence numbers + if Session['Payload'] == 'yes': + ymsg = Session.Ymsg + if not isinstance(ymsg, list): + ymsg = [ymsg] + for pkt in ymsg: + self.handle_packet(pkt, Session) + try: + self.ServerSeqno = Session['ServerSeqno'] + except KeyError: + pass + + def handle_packet(self, pkt, session): + from util import Storage + header = Storage() + header.command = int(pkt['Command']) + header.status = int(pkt['Status']) + account_id, session_id = session['SessionId'].split('-', 1) + self.account_id = account_id + header.session_id = self.session_id = int(session_id) + data = pkt._cdata.strip().encode('utf-8') + YahooConnectionBase.handle_packet(self, header, data) + if header.command == 241 and header.status == 0: + self.server_update() + +class Ymsg(object): + def __init__(self, command, status, version, data='', vendor_id='0', session=None): + self.Command = command + self.Status = status + self.Version = version + self.data = data + self.VendorId = vendor_id + self.SessionId = getattr(session, 'session_id', '0') + + def _as_tag(self): + t = tag('Ymsg') + t['Command'] = self.Command + t['Status'] = self.Status + t['Version'] = self.Version + t['VendorId'] = self.VendorId + t['SessionId'] = self.SessionId + t._cdata = self.data + return t + + def __str__(self): + t = self._as_tag() + #should all be utf-8 already, but we don't want to accidently go back to unicode via ascii + l = t._to_xml(pretty=False, self_closing=False, return_list=True) + return ''.join((s if isinstance(s, bytes) else s.encode('utf-8')) for s in l) + +class SessionWrapper(object): + def __init__(self, payload=None, session=None, server=False): + assert payload is None or isinstance(payload, Ymsg) + self.payload = payload + self.server = server + self.session = session + self.cc = self.session.ClientCounter + + @property + def Payload(self): + return "yes" if self.payload else "no" + + @property + def SessionId(self): + return '%s-%s' % (self.session.account_id, self.session.session_id) + + @property + def Channel(self): + return "ServerPost" if self.server else "ClientPost" + + @property + def ClientHash(self): + return sha1(str(self.cc) + self.SessionId + self.session.secret + self.Channel).hexdigest().upper() + + def __str__(self): + t = tag('Session') + t['Payload'] = self.Payload + t['Channel'] = self.Channel + session_id = getattr(self.session, 'session_id', None) + if session_id: + t['ClientHash'] = self.ClientHash + t['SessionId'] = self.SessionId + else: + t['Secret'] = self.session.Secret + t['ClientCounter'] = self.cc + t['ClientSeqno'] = self.cc + if self.server: + server_seqno = getattr(self.session, 'ServerSeqno', None) + if server_seqno: + t['ServerSeqno'] = server_seqno + if self.payload: + t.Ymsg = self.payload._as_tag() + #should all be utf-8 already, but we don't want to accidently go back to unicode via ascii + l = t._to_xml(pretty=False, self_closing=False, return_list=True) + return ''.join((s if isinstance(s, bytes) else s.encode('utf-8')) for s in l) + diff --git a/digsby/src/yahoo/yahooP2Pprotocol.py b/digsby/src/yahoo/yahooP2Pprotocol.py new file mode 100644 index 0000000..e842e7d --- /dev/null +++ b/digsby/src/yahoo/yahooP2Pprotocol.py @@ -0,0 +1,132 @@ +from util.primitives.mapping import groupify +from .yahooP2Psocket import YahooP2PSocket +from .yahoolookup import commands, statuses +from .yahooutil import yiter_to_dict +from util.lrucache import ExpiringLRU + +#MESSAGES_TO_REMEMBER = 30 +REMEMBER_MESSAGES_FOR = 5 * 60 #seconds + +ACK_LIST = '430' + +def ack_message(yahoo_connection, d, buddy_key): + ''' + sends a message ack + ''' + ret = True + lru = getattr(yahoo_connection, '_msg_acks', None) + if lru is None: + lru = yahoo_connection._msg_acks = ExpiringLRU(REMEMBER_MESSAGES_FOR) + ack_message_key = get_ack_message_key(d, buddy_key) + if ack_message_key is not None: + if ack_message_key in lru: + c1 = dict(lru[ack_message_key]) + c2 = dict(d) + s1 = c1.pop('send_attempt', None) + s2 = c2.pop('send_attempt', None) + if (s1 is not s2 is not None) and s1 != s2: + if c1['msgid'] == c2['msgid'] and c1.get('message') == c2.get('message'): + ret = False + if not ret: + #refresh the RU in LRU + lru[ack_message_key] = lru[ack_message_key] + else: + lru[ack_message_key] = d + else: + lru[ack_message_key] = d + dout = type(d)() + dout['frombuddy'] = d['to'] + dout['to'] = d[buddy_key] + for k in ['msgid', 'send_attempt']: + if k in d: + if k == 'msgid': + dout['begin_mode'] = ACK_LIST + dout['msgid_ack'] = d['msgid'] + dout['end_mode'] = ACK_LIST + dout.pop('message', None) + else: + dout[k] = d[k] +# d[252] = ('a'*22).encode('b64') + yahoo_connection.send('msg_ack', 'available', ydict=dout) + return ret + +def get_ack_message_key(d, buddy_key): + if 'msgid' in d: + return (d[buddy_key], d['to'], buddy_key, d['msgid']) + + +class YahooP2P(object): + def __init__(self, yahoo, buddy, me, remote_client, session_id): + self.yahoo = yahoo + self.buddy = buddy + self.me = me + self.rc = remote_client + + self.socket = YahooP2PSocket(self, self.rc, session_id) + self.socket.connect(self.rc) + + def incoming_message_raw(self, ydict_iter): + return getattr(self.yahoo, 'incoming_message_raw')(ydict_iter) + def message_notinoffice_raw(self, ydict_iter): + return getattr(self.yahoo, 'message_notinoffice_raw')(ydict_iter) + def message_brb_raw(self, ydict_iter): + return getattr(self.yahoo, 'message_brb_raw')(ydict_iter) + + def message_offline_raw(self, ydict_iter): + out = list(ydict_iter) + i = list(out) + print 'i', i + messages = groupify(i) + for d in messages: + d = yiter_to_dict(d.iteritems()) + if 'message' not in d: + continue + if 'frombuddy' not in d: + continue + if 'msgid' not in d: + d['buddy'] = d['frombuddy'] + d.pop('frombuddy') + for k in d.keys(): + if k not in ['message', 'buddy', 'to']: + d.pop(k) +# d[252] = ('a'*22).encode('b64') + self.send('message', 'brb', ydict=d) + continue + else: #'msgid' in d + ack_message(self, d, 'frombuddy') + return getattr(self.yahoo, 'message_offline_raw')(iter(out)) + + def notify_typing(self, typing_status, flag): + self.yahoo.notify_brb(self.buddy, typing_status, flag) + + def on_close(self): + foo = self.yahoo.peertopeers.pop(self.buddy) + assert foo is self + + def on_connect(self): + self.send('p2pfilexfer', 'available', buddy=self.me, to=self.buddy, + flag='1', typing_status='PEERTOPEER', **{'2':'1'}) + + def p2pfilexfer_available(self, buddy, to, flag, typing_status, **k): + if flag == '5': + self.send('p2pfilexfer', 'available', buddy=to, to=buddy, flag='6', + typing_status='PEERTOPEER') + elif flag == '7': + self.send('p2pfilexfer', 'available', buddy=to, to=buddy, flag='7', + typing_status='PEERTOPEER') + + def send(self, command, status, ydict={}, **kw): + 'Sends a Yahoo dictionary over the network.' + if isinstance(ydict, dict): + ydict.update(kw) + else: + [ydict.extend([k, v]) for k, v in kw.iteritems()] + + self.socket.ysend(commands[command],statuses[status], + data=ydict) + + def Disconnect(self): + self.socket.close() + foo = self.yahoo.peertopeers.pop(self.buddy) + assert foo is self + diff --git a/digsby/src/yahoo/yahooP2Psocket.py b/digsby/src/yahoo/yahooP2Psocket.py new file mode 100644 index 0000000..dc8fb6e --- /dev/null +++ b/digsby/src/yahoo/yahooP2Psocket.py @@ -0,0 +1,32 @@ +from logging import getLogger +from .YahooSocket import YahooSocket +import common +log = getLogger('YahooP2PSocket') + +class YahooP2PSocket(YahooSocket): + def __init__(self, yahoo, server, session_id): + YahooSocket.__init__(self, yahoo, server) + self.session_id = session_id + + def handle_connect(self): + log.info("Yahoo Socket connected to %s:%d", *self.server) + self.yahoo.on_connect() + + def handle_close(self): + log.critical('handle_close') + self.yahoo.on_close() + self.close() + self.yahoo = None + + def handle_error(self, *a, **k): + import traceback;traceback.print_exc() + log.error('handle_error') + self.yahoo.on_close() + self.yahoo = None + common.socket.handle_error(self, *a, **k) + + def handle_expt(self): + log.error("handle_expt: out-of-band data") + self.yahoo.on_close() + self.close() + self.yahoo = None diff --git a/digsby/src/yahoo/yahoobuddy.py b/digsby/src/yahoo/yahoobuddy.py new file mode 100644 index 0000000..058671b --- /dev/null +++ b/digsby/src/yahoo/yahoobuddy.py @@ -0,0 +1,345 @@ +'Yahoo buddylist objects.' + +from logging import getLogger; log = getLogger('yahoo.buddy'); info = log.info +from common.actions import action +from contacts import Contact +from util.observe import ObservableProperty as oproperty +from operator import attrgetter +from util.cacheable import cproperty +from util import odict, is_email +from util.primitives.funcs import do +from util.callbacks import callsback +from time import time +import common +from . import yahooprofile + +__all__ = ['YahooBuddy, YahooContact'] +objget = object.__getattribute__ + +class YahooBuddy(common.buddy): + 'A Yahoo! Messenger buddy.' + + __slots__ = ['location', + 'status_message', + 'pending_auth', + 'blocked', # appear permanently offline to this contact + 'stealth_perm', + 'stealth_session', + 'idle', + 'contact_info', + '_service'] + + def __init__(self, name, protocol): + # Yahoo buddy names are case insensitive. + self._nice_name = name + name = name.lower() + + self.status = 'unknown' + self.status_message = '' + self.idle = None + self._service = 'yahoo' + common.buddy.__init__(self, name, protocol) + self.icon_hash = None #force yahoo buddies to find out what the current checksum is + + do(setattr(self, slot, None) for slot in YahooBuddy.__slots__) + self._service = 'yahoo' + self.contact_info = {} + + self.stealth_perm = False + self.stealth_session = True + + nice_name = property(lambda self: self._nice_name) + + y360hash = cproperty(None) + y360updates = cproperty([]) + + @property + def service(self): + return self._service + + def set_remote_alias(self, alias): + return #this definitely doesn't work right yet. + self.protocol.set_remote_alias(self, alias) + + def get_remote_alias(self): + info = self.contact_info + + if info: + nick = info._attrs.get('nname', info._attrs.get('nn', None)) + # Return a nickname if there is one. + if nick: + return nick + else: + # Otherwise return their name, if it is specified. + name = u' '.join([info._attrs.get('fn', ''), info._attrs.get('ln', '')]) + if name.strip(): return name + + def update_cinfo(self, ab): + if ab['request-status'] == 'OK': + log.info('address book result: OK') + + from pprint import pprint + pprint(ab._to_xml()) + + log.info('replacing contact info') + if ab.ct: + self.contact_info = ab.ct + else: + log.warning("address book didn't have a CT tag!") + + self.notify() + else: + log.warning('error address book result: %s', ab['rs']) + + def update_cinfo_raw(self, ct): + log.debug('replacing contact info raw for %r', self) + self.contact_info = ct + self.notify() + + remote_alias = property(get_remote_alias, set_remote_alias) + + profile = cproperty(odict) + + @property + def pretty_profile(self): + #none = u'No Profile' + if self.service != 'yahoo': + return None + + if self.y360hash: + p = odict(self.profile) + + now = time() + filteredupdates = [] + + for update_and_time in self.y360updates: + try: update, tstamp = update_and_time + except: continue + if now - tstamp <= 60 * 60 * 24: + filteredupdates.append((update, tstamp)) + + self.y360updates = filteredupdates + + ups = [u for u, t in self.y360updates] + withnewlines = [] + if ups: + for i, u in enumerate(ups): + withnewlines.append(u) + if i != len(ups) - 1: withnewlines.append('\n') + else: + withnewlines.append(_('No Updates')) + + from . import yahoo360 + profilelink = (yahoo360.profile_link(self.y360hash), _('View Profile')) + p[_('Yahoo! 360:')] = ['(', profilelink, ')','\n'] + list(reversed(withnewlines)) + + # Place Yahoo! 360 at the beginning. + p.move(_('Yahoo! 360:'), 0) + + else: + p=self.profile + + URL = ''.join([u'http://profiles.yahoo.com/', self.name]) + + if p is not None: + p[_(u'Directory URL:')] = ['\n',(URL,URL)] + + return self.reorder_profile(p) or None + + def reorder_profile(self,p): + orderedkeys=[ + _('Yahoo! 360:'), + _('Real Name:'), + _('Nickname:'), + _('Location:'), + _('Age:'), + _('Sex:'), + _('Marital Status:'), + _('Occupation:'), + _('Email:'), + _('Home Page:'), + _('Hobbies:'), + _('Latest News:'), + _('Favorite Quote:'), + _('Links:'), + _('Member Since '), + _('Last Update: '), + _('Directory URL:') + ] + toc=[key for key in orderedkeys if key in p.keys() if p[key] and filter(None,p[key])] +# print p.keys(),'\n',toc + p._keys=toc + + if _('Yahoo! 360:') in p.keys() and filter(None,p[_('Yahoo! 360:')]): + i=p.keys().index(_('Directory URL:')) + p['sep360']=4 + p.move('sep360',1) + + for key in p.keys(): + i=p.index(key) +# print key,(p[key].replace('\n','(newline)\n').encode('ascii','ignore') if isinstance(p[key],basestring) else p[key]) + if key in [_('Hobbies:'),_('Latest News:'),_('Favorite Quote:'),_('Links:')] and p[key] and filter(None,p[key]): + if isinstance(p[key],list) and p[key][0]!='\n': + p[key]=p[key].insert(0,'\n') + elif isinstance(p[key],basestring) and p[key][0]!='\n': + p[key]=''.join(['\n',p[key]]) + + + if i!=0 and not isinstance(p[p.keys()[i-1]],int): +# print 'Key: ',key,' index: ',i, ' value: ',p[key] +# if i>0: print 'PrevKey: ',p.keys()[i-1],' value: ',p[p.keys()[i-1]] + sepkey='sepb4'+key + p[sepkey]=4 + p.move(sepkey,i) + + if key in [_('Member Since '),_('Last Update: '),_('Directory URL:')]: + i=p.keys().index(key) + if i!=0 and not isinstance(p[p.keys()[i-1]],int): + sepkey='sepb4'+key + p[sepkey]=4 + p.move(sepkey,i) + break + + i=p.keys().index(_('Directory URL:')) + p['sepb4URL']=4 + p.move('sepb4URL',i) + + return p + def request_info(self): + if self.service == 'yahoo': + yahooprofile.get(self.name, success = lambda p: setattr(self, 'profile', p)) + else: + self.profile = odict() + + @property + def _status(self): + return self.status_orb + + def get_online(self): + return self.status not in ('offline', 'unknown') + + online = oproperty(get_online, observe= ['status', 'pending_auth']) + + @property + def away(self): + return self.status == 'away' + + @property + def mobile(self): + return self.status_message and self.status_message.startswith("I'm mobile") + + def get_alias(self): + return self.remote_alias or self.name + + def set_alias(self, new_alias): + self.modify_addressbook(nname = new_alias) + + + def modify_addressbook(self, **attrs): + "Sets attributes in this contact's serverside addressbook entry." + + from urllib2 import urlopen + + if not hasattr(self, 'dbid'): + return log.warning('no dbid in %r', self) + + url = 'http://insider.msg.yahoo.com/ycontent/?addab2=0&' + + # Add some mystery params, and my database Id. + url += 'ee=1&ow=1&id=%s&' % self.dbid + + # Add params for each keyword argument. + url += '&'.join('%s=%s' % (k,v) for k,v in attrs.iteritems()) + url += '&fname=test' + url += '&yid=' + self.name + + info('setting addressbook entry: %s', url) + print urlopen(url).read() + + + def signoff(self): + 'Signs off this buddy.' + + self.status_message = '' + self.status = 'offline' + #self.setnotifyif('status_message', '') + #self.setnotifyif('status', 'offline') + self.notify() + + # + # block / unblock - IGNORECONTACT + # + @callsback + def block(self, setblocked = True, callback = None): + func = self.protocol.block_buddy if setblocked else self.protocol.unblock_buddy + return func(self, callback = callback) + + @callsback + def unblock(self, callback = None): + self.protocol.unblock_buddy(self, callback = callback) + + def set_stealth_session(self, session): + # False means appear visible to the buddy while you are invisible + + # clear any permanent block if there is one + if self.stealth_perm: + self.protocol.set_stealth_perm(self, False) + + self.protocol.set_stealth_session(self, session) + + def set_stealth_perm(self, perm): + self.protocol.set_stealth_perm(self, perm) + + def __repr__(self): + return u'' % (self.name, id(self)) + + def __str__(self): + return self.name +# +# +# + +class YahooContact(Contact): + 'A Yahoo buddy entry on a buddy list.' + inherited_actions = [common.buddy] + + def __init__(self, buddy, group): + Contact.__init__(self, buddy, (group, buddy.name)) + self.group = group + + + @action(needs = \ + lambda self: ((unicode, 'Username to invite', self.name), + (unicode, 'Invitation Message', 'Join me in chat!'), + (unicode, 'Chat room'))) + def invite_to_chat(self, usernames, message, roomname = None): + self.protocol.invite_to_conference(usernames, roomname, message) + + def __repr__(self): + return '' % (self.buddy.name, self.group.name) + + @action() + def remove(self): + self.protocol.remove_buddy(self) + + @action(lambda self: self.online) + def join_in_chat(self): + self.protocol.join_in_chat(self) + + def __hash__(self): + return hash('%s_%s_%s' % (id(self.protocol), self.name, self.id[0].name)) + + def __cmp__(self, other): + if type(self) is not type(other): + return -1 + if self is other: + return 0 + else: + return cmp((self.buddy.name, self.group.name), (other.buddy.name, other.group.name)) + + @action(Contact._block_pred) + def block(self, *a,**k): + return Contact.block(self, *a, **k) + @action(Contact._unblock_pred) + def unblock(self, *a,**k): + return Contact.unblock(self, *a, **k) diff --git a/digsby/src/yahoo/yahooformat.py b/digsby/src/yahoo/yahooformat.py new file mode 100644 index 0000000..19a70b2 --- /dev/null +++ b/digsby/src/yahoo/yahooformat.py @@ -0,0 +1,227 @@ +''' + +Parses and creates Yahoo formatting codes. + + tohtml - returns valid XHTML from a Yahoo message with formatting codes + format - returns a string with formatting codes from a message and a "format storage" + +''' +import traceback +import re +from util import flatten +import util.primitives.bits as bits + +color = lambda hex: ('font color="#' + hex + '"', 'font') + +codes = { + 1: 'b', + 2: 'i', + 4: 'u', + + 'l': '', # ignore link markers +} + +color_codes = { + 30: '000000', + 31: '0000FF', + 32: '008080', + 33: '808080', + 34: '008000', + 35: 'FF0080', + 36: '800080', + 37: 'FF8000', + 38: 'FF0000', + 39: '808000', +} + +rereplace = [ + # + ('<font (.+?)>', ''), + ('</font>', ''), + + # + ('<s>', ''), + ('</s>', ''), + + # ignore alt and fade tags + ('<alt (?:.+?)>', ''), + ('</alt>', ''), + ('<fade (?:.+?)>', ''), + ('</fade>', ''), + +] + + +def hexstr_to_tuple(hexstr): + return tuple([ord(a) for a in bits.hex2bin(' '.join(''.join(x) for x in zip(hexstr[::2], hexstr[1::2])))]) + +color_lookup = dict((hexstr_to_tuple(v), str(k)) for k, v in color_codes.iteritems()) + +format_codes = { + # + # codes following \x1b bytes in the formatting stream + # + # code: (start tag, end tag) + # + '1': ('b', 'b'), + '2': ('i', 'i'), + '4': ('u', 'u'), + '#': ((lambda hex: 'font color="#%s"' % hex), 'font'), + 'face': ((lambda face: 'font face="%s"' % face), 'font'), + 'size': ((lambda size: 'font style="font-size: %spt"' % size), 'font'), +} + +format_lookup = {'b': '1', + 'i': '2', + 'u': '4'} + +format_codes.update(dict((str(k), color(v)) for k, v in color_codes.iteritems())) + +def flatten(s): + for a in s: + for e in a: yield e + +def codepair(code, tag): + if isinstance(tag, tuple): + start, end = tag + else: + start = end = tag + + return [('\033[%sm' % code, ('<%s>' % start) if start else ''), + ('\033[x%sm' % code, ('' % end) if end else '')] + +color_re = re.compile('\033\\[#([^m]+)m') + +codes.update(('' + str(code), ('font color="#%s"' % color, 'font')) for code, color in color_codes.iteritems()) + +replace = list(flatten(codepair(code, tag) for code, tag in codes.iteritems())) + +rereplace = [(re.compile(txt, re.IGNORECASE), repl) for txt, repl in rereplace] + +FADE_ALT_RE = r'<(?:(?:FADE)|(?:ALT))\s+(?:(?:#[a-f0-9]{6}),?\s*)+>' + +BIT_DEFENDER_RE = r'' + +STUPID_COMBINED_RE = r'((?:%s)|(?:%s))' % (FADE_ALT_RE, BIT_DEFENDER_RE) + +tags_re = re.compile(r'''(\s]+))?)+\s*|\s*)/?>)''', re.DOTALL | re.IGNORECASE) +entity_re = re.compile(r'''&(?:(?:(?:#(?:(?:x[a-f0-9]+)|(?:[0-9]+)))|[a-z]+));''', re.IGNORECASE) +stupid_tags_re = re.compile(STUPID_COMBINED_RE, re.DOTALL | re.MULTILINE | re.IGNORECASE) +stupid_tags = frozenset(('alt', 'fade')) +allowed_tags = frozenset(('b', 'i', 'u', 's', 'font')) + +def is_ascii(s): + ''' + sanity check only. + ''' + return all(((32 <= ord(c) <= 126) or (ord(c) in (0x9, 0xA, 0xD))) for c in s) + +def tohtml(s): + #s = s.encode('xml') + + # normal replacements + for txt, repl in replace: + s = s.replace(txt, repl) + + # regex replacements + for pattern, repl in rereplace: + match = pattern.search(s) + while match: + i, j = match.span() + splat = [a.decode('xml') for a in match.groups()] + + if repl == '': + for k, e in enumerate(splat[:]): + splat[k] = fix_font_size(e) + + s = s[:i] + repl % tuple(splat) + s[j:] + match = pattern.search(s) + + # custom colors + match = color_re.search(s) + while match: + i, j = match.span() + s = s[:i] + '' % match.group(1) + s[j:] + match = color_re.search(s) + + # close tags as necessary + exploded = tags_re.split(s) + ret = [] + for chunk in exploded: + if tags_re.match(chunk): + if any(any(chunk.lower().startswith(s % tag) for s in ('<%s ', '<%s>', '')) for tag in allowed_tags): + ret.append(chunk) + continue + elif any(any(chunk.lower().startswith(s % tag) for s in ('')) for tag in stupid_tags): + continue + elif stupid_tags_re.match(chunk): + exp2 = filter((lambda arg: not stupid_tags_re.match(arg)), stupid_tags_re.split(chunk)) + for chunk2 in exp2: + ret.append(chunk2.encode('xml')) + continue + ret.append(chunk.encode('xml')) + s = ''.join(ret) + + #lxml turns everything into ascii w/ escaped characters. + + return s + +def fix_font_size(e): + # TODO: Get rid of the regex matching here. it affects non-markup text. + # turn '' into '' + return re.sub(r'size=["\']([^"\']+)["\']', lambda match: 'style="font-size: %spt"' % match.group(1), e) + +def color2code(color): + 'returns the yahoo format code for color, where color is (r, g, b)' + + return ''.join([ + '\x1b[', + color_lookup.get(color, '#' + ''.join('%02x' % c for c in color[:3])), + 'm' + ]) + +def format(format, string): + before, after = '' % format, '' + + try: + foregroundcolor = format.get('foregroundcolor') + except KeyError: + pass + else: + before += color2code(foregroundcolor) + + for a in ('bold', 'italic', 'underline'): + if format.get(a, False): + before += '\x1b[' + format_lookup[a[0]] + 'm' + + return before + string + after + + +def main(): + tests = ''' + +\x1b[30mhey, who\'s this? +normal +\033[1mbold\033[x1m +\033[1mbold\033[x1mnotbold +\033[1mbold\033[x1m\033[2mitalic\033[1mbolditalic\033[4mall\033[x1m\033[x2mjustunderline + +\033[38mred +\033[#ff3737moffre +test +test +\033[#FF0000mhello\033[#000000m +\033[1m\033[4m\033[2m\033[#000000m\033[lmhttp://www.digsby.com\033[xlm\033[x2m\033[x4m\033[x1m + +'''.strip().split('\n') + + #print decode(tests[0]) + + for t in tests: + print repr(t) + print repr(tohtml(t)) + print + + +if __name__ == '__main__': + main() diff --git a/digsby/src/yahoo/yahoohttp.py b/digsby/src/yahoo/yahoohttp.py new file mode 100644 index 0000000..a0e5c9e --- /dev/null +++ b/digsby/src/yahoo/yahoohttp.py @@ -0,0 +1,193 @@ +from logging import getLogger +from traceback import print_exc +from util.callbacks import callsback +from util.xml_tag import tag, post_xml +import urllib2 +import httplib + +log = getLogger('yahoohttp') + +def y_webrequest(url, data, cookies): + headers = {'Cookie': cookies['Y'] + '; ' + cookies['T'], + 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5)', + 'Cache-Control': 'no-cache'} + + req = urllib2.Request(url, data, headers) + response = urllib2.urlopen(req) + return response.read() + +# holds already-looked up mobile carriers +_carriers = {} + +@callsback +def get_carrier(yahoo, sms_number, callback = None): + ''' + Uses a Yahoo webservice to find a carrier string (like 'pcsms.us.version') for + a mobile phone number. + + Requires Yahoo web cookies (in the yahoo.cookie_str property). + + These carrier strings can be used to send SMS messages through the Yahoo servers + (see the send_sms function below). + ''' + + # has this number's carrier already been looked up? + global _carriers + if sms_number in _carriers: + return callback.success(_carriers[sms_number]) + + # options for the request + version = '8.1.0.209' + intl = 'us' + + # build request XML + validate = tag('validate', intl = intl, version = version, qos = '0') + validate.mobile_no['msisdn'] = sms_number + validate._cdata = '\n' + + # setup callbacks + def on_response(validate): + log.info('HTTP response to get_carrier:\n%s', validate._to_xml()) + + status = unicode(validate.mobile_no.status) + + log.info(' tag contents: %s', status) + + if status == 'Valid': + # got a valid carrier string; memoize it and return to the success callback. + carrier = str(validate.mobile_no.carrier) + _carriers[sms_number] = carrier + log.info('carrier for %s: %s', sms_number, carrier) + return callback.success(carrier) + + elif status == 'Unknown': + log.critical('unknown carrier for %s', sms_number) + return callback.error() + + else: + log.critical('unknown XML returned from mobile carrier lookup service') + return callback.error() + + def on_error(validate): + log.critical('could not connect to mobile carrier lookup web service') + return callback.error() + + # setup HTTP POST + url = 'http://validate.msg.yahoo.com/mobileno?intl=%s&version=%s' % (intl, version) + + headers = {'Cookie': yahoo.cookie_str, + 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5)', + 'Cache-Control': 'no-cache'} + + log.info('POSTing SMS carrier request to %s', url) + xmlstr = validate._to_xml(self_closing = False, pretty = False) + log.info(xmlstr) + + # POST + post_xml(url, xmlstr, success = on_response, error = on_error, **headers) + ''' + POST /mobileno?intl=us&version=8.1.0.209 HTTP/1.1 + Cookie: T=z=Vr0OGBVxJPGB9u7bOG1linxMjI2BjUyNjA2NU81NzA-&a=QAE&sk=DAAGhaQDlIYJTS&d=c2wBTlRVeEFUSTFNVGN4TWpneU1EYy0BYQFRQUUBenoBVnIwT0dCZ1dBAXRpcAFCV0JvaUE-; path=/; domain=.yahoo.com; Y=v=1&n=50s9nph28dhu3&l=386i1oqs/o&p=m2g0e3d012000000&r=g6&lg=us&intl=us&np=1; path=/; domain=.yahoo.com ;B=a6d0ooh2qsp2r&b=3&s=ko + User-Agent: Mozilla/4.0 (compatible; MSIE 5.5) + Host: validate.msg.yahoo.com + Content-Length: 105 + Cache-Control: no-cache + + + + ''' + + success = \ + ''' + HTTP/1.1 200 OK + Date: Fri, 04 May 2007 15:09:26 GMT + P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV" + Content-Length: 140 + Connection: close + Content-Type: text/html + + + + Valid + pcsms.us.verizon + + + ''' + + error = \ + ''' + HTTP/1.1 200 OK + Date: Fri, 04 May 2007 15:12:19 GMT + P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV" + Content-Length: 117 + Connection: close + Content-Type: text/html + + + + Unknown + + + + ''' + +@callsback +def send_sms(yahoo, sms_number, message, callback = None): + try: + if isinstance(message, unicode): + message = message.encode('utf-8') + + sms_number = format_smsnumber(sms_number) + + def on_carrier(carrier): + log.info('sending Yahoo SMS packet to %s on carrier %s', sms_number, carrier) + + me = yahoo.self_buddy + + yahoo.send('sms_message', 'available', [ + 'frombuddy', me.name, + 'sms_alias', me.remote_alias or me.name, + 'to', sms_number, + 'sms_carrier', carrier, + 'message', message]) + + get_carrier(yahoo, sms_number, success = on_carrier, error = callback.error) + + + except: + print_exc() + + callback.success() + +def format_smsnumber(sms): + 'Yahoo carrier lookup/sms send requires a 1 in front of US numbers.' + + #TODO: international rules? + + if len(sms) == 10: + return '1' + sms + + return sms + + +if __name__ == '__main__': + + cookies = {'Y':'v=1&n=3lutd2l220eoo&l=a4l8dm0jj4hisqqv/o&p=m2l0e8v002000000&r=fr&lg=us&intl=us; path=/; domain=.yahoo.com', + 'T':'z=/V2OGB/bLPGB9ZlmrY27n5q&a=YAE&sk=DAAcD2qurh0ujr&d=YQFZQUUBb2sBWlcwLQF0aXABQldCb2lBAXp6AS9WMk9HQmdXQQ--; path=/; domain=.yahoo.com'} + + headers = { + 'Cookie': '; '.join([cookies['Y'], cookies['T']]), + 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5)', + 'Cache-Control': 'no-cache', + } + + data = '' + + conn = httplib.HTTPConnection("validate.msg.yahoo.com",80) + conn.set_debuglevel( 10000 ) + conn.request('POST','/mobileno?intl=us&version=8.1.0.209', data, headers) + + resp = conn.getresponse() + print resp.status, resp.reason + if resp.status == 200: + print resp.read() diff --git a/digsby/src/yahoo/yahoologinbase.py b/digsby/src/yahoo/yahoologinbase.py new file mode 100644 index 0000000..def8b4e --- /dev/null +++ b/digsby/src/yahoo/yahoologinbase.py @@ -0,0 +1,169 @@ +from .YahooSocket import DEFAULT_YMSG_VERSION +from .login import yahoo64, YahooRateLimitException, \ + YahooAuthException +from hashlib import md5 +from logging import getLogger +from common.asynchttp.cookiejartypes import CookieJarHTTPMaster +from .yahoolookup import ykeys +import urllib2 +import cookielib +import common +import util.net +log = getLogger('YahooLogin.base') + +class YahooLoginBase(object): + def __init__(self, yahoo): + pass + + def logon(self): + 'Login generator.' + + y = self.yahoo + def fail(exc = None): + y.set_disconnected(y.Reasons.CONN_FAIL) + + user, password = y.username, y.password + log.info('logging in %s', user) + + y.change_state(y.Statuses.AUTHENTICATING) + + yield self.send_auth_req(user) + print 'waiting for auth' + (_hdr, data) = (yield self.gwait('auth', 'brb', self.logon_err)) + print 'got auth_brb' + # got a auth challenge back + auth_challenge = self.from_ydict(data) + + if not '94' in auth_challenge: + log.warning('auth challenge packet did not have challenge str (94): %r', auth_challenge) + fail() + return + + challenge_str = auth_challenge['94'] + common.callsback(self.yahoo_15_weblogin)(user, password, challenge_str, error=fail) + + def yahoo_15_weblogin(self, user, password, challenge_str, callback=None): + ''' + Version 15 login, using an SSL connection to an HTTP server. + ''' + y = self.yahoo + jar = getattr(y, 'jar', None) + def yahoo_v15_auth_success(crumb, yc, t, jar): + y.cookies.update(Y = yc.split()[0][:-1], + T = t.split()[0][:-1]) + y.jar = jar + + crumbchallengehash = yahoo64(md5(crumb + challenge_str).digest()) + + log.info('logging on with initial status %s', y.initial_status) + common.netcall(lambda: y.send('authresp', y.initial_status, [ + 1, user, + 0, user, + 'ycookie', yc, + 'tcookie', t, + 'crumbchallengehash', crumbchallengehash, + 'mystery_login_num', '8388543',#str(0x3fffbf),#'4194239', + 'identity', user, + 'locale', 'us', + 'version_str', '10.0.0.1270'])) + def yahoo_v15_auth_error(e): + if isinstance(e, YahooRateLimitException): + return y.set_disconnected(y.Reasons.RATE_LIMIT) + if isinstance(e, YahooAuthException): + y._auth_error_msg = getattr(e, 'error_msg', None) or getattr(e, 'error_type', None) + return y.set_disconnected(y.Reasons.BAD_PASSWORD) + callback.error(e) + common.callsback(self.yahoo_v15_auth)(challenge_str, password, user, jar=jar, + success=yahoo_v15_auth_success, + error = yahoo_v15_auth_error) + + def yahoo_v15_auth(self, challenge, password, username, jar=None, callback=None): + jar = jar if jar is not None else cookielib.CookieJar() + http_opener = CookieJarHTTPMaster(jar = jar) + + from time import time + now = int(time()) + + def bar(req, resp): + resp = resp.read() + log.info('Yahoo response starts: %r', resp[:100]) + if not resp.startswith('0'): + return callback.error(YahooAuthException(resp)) + token = resp.split('\r\n')[1].split('=')[1] + url2 = util.net.UrlQuery("https://login.yahoo.com/config/pwtoken_login", + src='ymsgr', + ts=str(now), + token=token) + http_opener.open(url2, + success = lambda req, resp: self.cookie_crumbs_success(resp, jar, callback=callback), + error = callback.error) + + url_password = util.net.UrlQuery("https://login.yahoo.com/config/pwtoken_get", + src='ymsgr', + ts=str(now), + login=username, + passwd=password, + chal=challenge) + http_opener.open(url_password, + success=bar, + error = callback.error) + + def cookie_crumbs_success(self, resp2, jar, callback=None): + try: + resp2 = resp2.read() + log.info('Yahoo response2 starts: %r', resp2[:100]) + #got '100' here from the sbcglobal response above + lines = resp2.split('\r\n') + + crumb = lines[1].split('=')[1] + y = lines[2] + t = lines[3] + y = y[2:] + t = t[2:] + except Exception: + callback.error() + else: + callback.success(crumb, y, t, jar) + + def send_auth_req(self, user): + return self.gsend('auth', 'custom', {1:user}, v=DEFAULT_YMSG_VERSION) + + def logon_err(self): + return NotImplemented + + def generic_error(self): + y = self.yahoo + y.set_disconnected(y.Reasons.CONN_FAIL) + + def chatlogon(self, command, status, data): + def gen(command, status, data): + me = self.yahoo.self_buddy.name + + yield self.gsend('chatonline', 'available', + {'1': me, '109': me, '6': 'abcde'}) + yield self.gwait('chatonline', 'brb', self.generic_error) + yield self.gsend(command, status, data) + self.async_proc(gen(command, status, data)) + + def _unwrap_gwait(self, res): + '''given the result of yielding to a packet generator, returns a ydict''' + + _hdr, data = res + return self.from_ydict(data) + + def conflogon(self, roomname, callback): + def gen(): + me = self.yahoo.self_buddy.name + + yield self.gsend('conflogon', 'available', + {'1': me, '3': me, '57': roomname}) + + ydict = self._unwrap_gwait((yield self.gwait('conflogon', 'brb', self.generic_error))) + actual_roomname = ydict[ykeys.conf_name] + + conf = self.yahoo._create_conference(actual_roomname) + conf.buddy_join(self.yahoo.self_buddy) + callback(conf) + + self.async_proc(gen()) + diff --git a/digsby/src/yahoo/yahoologinsocket.py b/digsby/src/yahoo/yahoologinsocket.py new file mode 100644 index 0000000..e768980 --- /dev/null +++ b/digsby/src/yahoo/yahoologinsocket.py @@ -0,0 +1,54 @@ +from .YahooSocket import YahooSocket +from .yahoologinbase import YahooLoginBase +from logging import getLogger +log = getLogger('YahooLoginSocket') + +class YahooLoginSocket(YahooLoginBase, YahooSocket): + def __init__(self, yahoo, server): + YahooSocket.__init__(self, yahoo, server) + YahooLoginBase.__init__(self, yahoo) + + log.info( "connecting to %s with user: %s", self.server, + self.yahoo.username) + + def onfail(exc = None): + self.yahoo.set_disconnected(self.yahoo.Reasons.CONN_FAIL) + self.close() + + self.connect(self.server, error = onfail) + + if not self.readable(): + onfail() + + def logon_err(self): + y = self.yahoo + y.set_disconnected(y.Reasons.CONN_FAIL) + self.close() + + def handle_connect(self): + log.info("Yahoo Socket connected to %s:%d", *self.server) + + self.async_proc(self.logon()) + + def handle_close(self): + log.critical('handle_close') + if self.yahoo.offline_reason == self.yahoo.Reasons.NONE: + rsn = self.yahoo.Reasons.CONN_LOST + else: + rsn = self.yahoo.offline_reason + self.yahoo.set_disconnected(rsn) + self.close() + self.yahoo = None + + def handle_error(self, *a, **k): + import traceback;traceback.print_exc() + log.error('handle_error') + if self.yahoo: + self.yahoo.set_disconnected(self.yahoo.Reasons.CONN_LOST) + super(YahooSocket, self).handle_error(*a, **k) #skip over YahooSocket.handle_error, it raises NotImplementedError + + def handle_expt(self): + log.error("handle_expt: out-of-band data") + self.yahoo.set_disconnected(self.yahoo.Reasons.CONN_LOST) + self.close() + self.yahoo = None diff --git a/digsby/src/yahoo/yahoolookup.py b/digsby/src/yahoo/yahoolookup.py new file mode 100644 index 0000000..4017667 --- /dev/null +++ b/digsby/src/yahoo/yahoolookup.py @@ -0,0 +1,247 @@ +''' +Human readable names for Yahoo packet values and dictionary keys. + +Incoming packets with Yahoo dictionaries get mapped to function calls with +named arguments via the values in these dictionaries. + +i.e., command_status(namedarg1, namedarg2) +''' + +from __future__ import with_statement +from util import lookup_table + +# The eleventh and twelfth bytes of a YMSG packet header form this network order +# short: +commands = lookup_table( + logon = 1, + logoff = 2, + isaway = 3, + isback = 4, + idle = 5, + message = 6, + idact = 7, + iddeact = 8, + mailstat = 9, + userstat = 10, + newmail = 11, + chatinvite = 12, + calendar = 13, + newpersonalmail = 14, + newcontact = 15, + addident = 16, + addignore = 17, + ping = 18, + gotgrouprename = 19, # < 1, 36(old) + sysmessage = 20, + skinname = 21, + passthrough2 = 22, + confinvite = 24, + conflogon = 25, + confdecline = 26, + conflogoff = 27, + confaddinvite = 28, + confmsg = 29, + chatlogon = 30, + chatlogoff = 31, + chatmsg = 32, + gamelogon = 40, + gamelogoff = 41, + gamemsg = 42, + filetransfer = 70, + voicechat = 74, + notify = 75, + verify = 76, + p2pfilexfer = 77, + peertopeer = 79, + webcam = 80, + authresp = 84, + list = 85, + auth = 87, + addbuddy = 131, + rembuddy = 132, + ignorecontact = 133, # > 1, 7, 13 < 1, 66, 13 + rejectcontact = 134, + grouprename = 137, + keepalive = 138, + chatonline = 150, + chatgoto = 151, + chatjoin = 152, + chatleave = 153, + chatexit = 155, + chataddinvite = 157, + chatlogout = 160, + chatping = 161, + comment = 168, + stealth_perm = 185, + stealth_session = 186, + avatar = 188, + picture_checksum = 189, + picture = 190, + picture_update = 193, + picture_upload = 194, + invisible = 197, + yahoo6_status_update = 198, + avatar_update = 199, + audible = 208, + send_buddylist = 211, + # send_checksum = 212, # ???? + listallow = 214, + + peerrequest = 220, + peersetup = 221, + peerinit = 222, + + yahoo360 = 225, + yahoo360update = 226, + movebuddy = 231, + awaylogin = 240, + list15 = 241, + msg_ack = 251, + weblogin = 550, + sms_message = 746, + sms_login = 748, + + +) + +# Four byte integer following the command. +statuses = lookup_table( + available= 0, + brb = 1, + busy = 2, + notathome = 3, + notatdesk = 4, + notinoffice = 5, + onphone = 6, + onvacation = 7, + outtolunch = 8, + steppedout = 9, + invisible = 12, + typing = 22, + custom = 99, + idle = 999, + weblogin = 1515563605, + offline = 1515563606, + cancel = -1, +) + +# +# Meanings for Yahoo! dictionary keys. +# +# These were not in Ethereal--their meanings are guessed from context alone, +# so they come with the disclaimer that some of them may be totally wrong. +# +# YahooSocket uses these to make named argument calls into YahooProtocol. +# +ykeys = { + 0: 'away_buddy', + 1: 'frombuddy', + 2: 'identity', + 3: 'conf_from', + 4: 'buddy', + 5: 'to', + 6: 'chatflag', + 7: 'contact', +# 8: ? don't know. appear in logon_brb as 3 + 9: 'count', + 10: 'status', + 11: 'session_id', + 12: 'base64ip', + 13: 'flag', + 14: 'message', + 15: 'unix_time', + 16: 'error_message', + 19: 'custom_message', + 20: 'url', + 27: 'filename', + 28: 'filesize', + 29: 'filedata', + 31: 'block', +# 32: 'unknown file xfer flag', + 38: 'expires', + 47: 'away', + 49: 'typing_status', + 50: 'conf_buddy', + 51: 'conf_invite_buddy', + 52: 'conf_tobuddy', + 53: 'conf_entering', + 56: 'conf_leaving', + 57: 'conf_name', + 58: 'conf_invite_message', + 59: 'login_cookie', #bcookie? + 62: 'chat_search', +# 63: , +# 64: , + 65: 'group', + 66: 'error', + 67: 'new_group', + 68: 'sms_carrier', + 69: 'sms_alias', + 87: 'buddy_list', + 88: 'ignore_list', + 89: 'identity_list', + 97: 'msg_encoding', #1=utf-8, omit for ascii? thanks jYMSG API + 98: 'locale', #? saw as 'us' in yahoo http packet + 104: 'room_name', + 105: 'topic', + 109: 'chat_buddy', + 117: 'chat_message', + 135: 'version_str', + 137: 'idle_seconds', + 138: 'idle_duration_privacy', +# 140: 'unknown file xfer flag' + + 184: 'opentalk', + 185: 'appear_offline_list', +# 187: something related to away messages? + 192: 'checksum', +# 198: , ? seen in "awaylogin_available" packet + 203: 'mingle_xml', + 206: 'icon_flag', + 213: 'avatar_flag', + 216: 'firstname', + 222: 'acceptxfer', + 223: 'pending_auth', + 224: 'fromgroup', + 231: 'audible_message', + 233: 'cookie', + 234: 'conf_cookie', + 241: 'buddy_service', +# 242: , something to do with msn buddies? + 244: 'mystery_login_num', + 249: 'transfertype', + 250: 'peerip', + 251: 'peer_path', + + 254: 'lastname', + 257: 'yahoo360xml', + 264: 'togroup', + + 265: 'p2pcookie', + 267: 'longcookie', + + 277: 'ycookie', + 278: 'tcookie', + +# 283: , ? seen in "awaylogin_brb" packet + + 300: 'begin_entry', + 301: 'end_entry', + 302: 'begin_mode', + 303: 'end_mode', + + 307: 'crumbchallengehash', + 317: 'stealth', + +# 334: + + 429: 'msgid', + 430: 'msgid_ack', +# 440: , ? seen in "awaylogin_available" packet + 450: 'send_attempt', #0-based + +# 10093: 'unknown file xfer flag', +} + +# stringify the numbers and add the dictionary's reverse +ykeys = lookup_table(dict((str(k), v) for k,v in ykeys.iteritems())) diff --git a/digsby/src/yahoo/yahooprofile.py b/digsby/src/yahoo/yahooprofile.py new file mode 100644 index 0000000..db8ed55 --- /dev/null +++ b/digsby/src/yahoo/yahooprofile.py @@ -0,0 +1,89 @@ +''' + +Webscraping for Yahoo! Member profiles. + +''' +from __future__ import with_statement +from util import threaded, soupify, odict, scrape_clean +from util.primitives.funcs import do +from util.BeautifulSoup import BeautifulStoneSoup +from itertools import izip +from logging import getLogger; log = getLogger('yahooprofile') +from urllib2 import urlopen + +profile_url = 'http://profiles.yahoo.com/%s?warn=1' + +@threaded +def get(yahooid): + data = urlopen(profile_url % yahooid).read().decode('utf-8') + return scrape_profile(data) + +def scrape_profile(s): + # fixup HTML + s = s.replace(u' ', u' ').replace(u'Hobbies:', u'Hobbies:') + soup = BeautifulStoneSoup(s, convertEntities=BeautifulStoneSoup.ALL_ENTITIES, + fromEncoding = 'utf-8') + profile = odict() + + # grab info + for section in ('basics', 'more'): + div = soup('div', id='ypfl-' + section)[0].dl + if div is not None: + info = [elem.renderContents(None) for elem in div if elem != u'\n'] + profile.update(dictfrompairs(info)) + + # grab links + links = dictfrompairs([e.renderContents(None).replace('','').replace('','') + for e in soup('dl', attrs = {'class':'mylinks'})[0].contents if e != u'\n']) + + # construct [list of] tuples for links + if 'Home Page:' in links: links['Home Page:'] = anchor2tuple(links['Home Page:']) + linktuples = [anchor2tuple(v) for k, v in sorted(links.items()) + if k.startswith('Cool Link')] + + # insert newlines between the link tuples. + finallinks = [] + for i, tuple in enumerate(linktuples): + finallinks.append(tuple) + if i != len(linktuples) - 1: finallinks.append(u'\n') + links['Links:'] = finallinks + + do(links.pop(k) for k in links.keys() if k.startswith('Cool Link')) + + profile.update(links) + + # pull "member since" and "last update" + for p in soup.findAll('p', attrs = {'class':'footnote'}): + c = p.renderContents(None) + for hdr in ('Member Since ', 'Last Update: '): + if c.startswith(hdr): + profile[hdr] = c[len(hdr):] + + # remove empty entries + for k, v in dict(profile).iteritems(): + if isinstance(v, basestring): + dict.__setitem__(profile, k, + None if v.strip() in ('', 'No Answer') else scrape_clean(v)) + + profile.pop('Yahoo! ID:', None) + + return profile + +def dictfrompairs(info): + return dict(izip(info[::2], info[1::2])) # make into a dict by zipping pairs + +def anchor2tuple(s): + ''' + Our profile box takes tuples for links. + + Returns (u"http://www.google.com", u"http://www.google.com") for + http://www.google.com. + ''' + + if not s: return None + a = soupify(s).a + return (a['href'], a.renderContents()) if a else None + + + + diff --git a/digsby/src/yahoo/yahoosip.py b/digsby/src/yahoo/yahoosip.py new file mode 100644 index 0000000..c2922cd --- /dev/null +++ b/digsby/src/yahoo/yahoosip.py @@ -0,0 +1,59 @@ +import uuid +import socket + +server_ip_port = '98.138.26.129:443' + +def send(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + host, port = server_ip_port.split(':') + port = int(port) + print 'connecting to', (host, port) + s.connect((host, port)) + packet = register('digsby01') + print 'sending packet' + s.sendall(packet) + print 'receiving data' + print s.recv(50) + s.close() + +def register(yahoo_username, branch=None, call_id=None): + if branch is None: + branch = uuid.uuid4() + if call_id is None: + call_id = uuid.uuid4() + random_tag = uuid.uuid4().hex[:8] + + local_port_ip='127.0.0.1:5061' + my_contact_ip='127.0.0.1:443' + + contact_id = '' % (yahoo_username, my_contact_ip) + to_id = '' % (yahoo_username, server_ip_port) + from_id = ';tag=%s' % (yahoo_username, server_ip_port, random_tag) + call_id = 'f3e9ee72-6270-42d8-a058-a6c3f34cfce2' # generate + + lines = [ + ('Via', 'SIP/2.0/TCP %s;branch=%s' % (local_port_ip, branch)), + ('Max-Forwards', '70'), + ('Contact', contact_id), + ('To', to_id), + ('From', from_id), + ('Call-ID', call_id), + ('CSeq', '1 REGISTER'), + ('Expires', '3600'), + ('User-Agent', 'Yahoo Voice,2.0'), + ('Content-Length', '0'), + ('Y-User-Agent', 'intl=us; os-version=w-2-6-1; internet-connection=lan; cpu-speed=2653; pstn-call-enable=true; appid=10.0.0.1270'), + ] + + + verb = 'REGISTER' + action = '%s sip:%s;transport=tcp SIP/2.0' % (verb, server_ip_port) + + header_lines = ['%s: %s' % (k, v) for k, v in lines] + return '\r\n'.join([action] + header_lines) + '\r\n' + +def main(): + send() + +if __name__ == '__main__': + main() diff --git a/digsby/src/yahoo/yahooutil.py b/digsby/src/yahoo/yahooutil.py new file mode 100644 index 0000000..3202307 --- /dev/null +++ b/digsby/src/yahoo/yahooutil.py @@ -0,0 +1,258 @@ +''' +Yahoo utilities. +''' + +from __future__ import with_statement +import re +from urllib import unquote_plus +from itertools import izip +from struct import pack, calcsize +from httplib import HTTPConnection +from .yahoolookup import ykeys, commands +from logging import getLogger; log = getLogger('yahoo'); info = log.info +from util import odict, threaded +from pprint import pformat +from common.exceptions import LoginError + +class YahooLoginError(LoginError): + def __init__(self, protocol, message): + if not isinstance(message, basestring): + raise TypeError('message must be a string') + + self.protocol = protocol + Exception.__init__(self, message) + + +_filematch = re.compile(r'.*/(.+)\?\w+') + + +def filename_from_url(url): + ''' + Returns a normally formatted filename from a file transfer URL. + + >> filename_from_url("http://fs.yahoo.com/name/.tmp/filename.txt?adfadfa'") + filename.txt + ''' + + return unquote_plus(_filematch.search(url).group(1)) + +# standard ymsg argument seperator that separates items in a Yahoo dictionary +# on the network: 0xc0 followed by 0x80 +argsep = pack( 'BB', 0xc0, 0x80 ) + +def to_ydict(d, argsep=argsep): + ''' + Makes a yahoo dictionary ready to be sent out on the network. + + Takes either a mapping object (specifically, one with an iteritems + attribute), or a even-length sequence object of key-value pairs. + ''' + if not d: + return '' + + def to_ydict_entry(k, v): + try: n = int(k) + except ValueError: + try: + n = ykeys[k] + except: + log.warning('to_ydict got a dict with a non number key: %s', k) + return '' + + if isinstance(v, bytes): + pass + elif isinstance(v, (int,long)): + v = str(v) + else: + if not isinstance(v, unicode): + import warnings + warnings.warn("yahoo got argument %r of type %r, not unicode" % (v, type(v))) + v = unicode(v) + v = v.encode('utf-8') + + return ''.join([str(n), argsep, v, argsep]) + + # find some way to iterate + if hasattr(d, 'iteritems'): item_iterator = d.iteritems() + elif isinstance(d, (list, tuple)): item_iterator = izip(d[::2],d[1::2]) + + return ''.join(to_ydict_entry(k,v) for k,v in item_iterator) + +def from_ydict_iter(data, argsep=argsep): + 'Returns an iterable of key-value pairs from a yahoo dictionary.' + + if not data: return iter([]) + data = data.split(argsep) + keys, values = data[::2], data[1::2] + + # utf8 is the defined wire protocol for values in these dictionaries. + # convert them to unicode before they get to the upper layers + values = [from_utf8(v) for v in values] + + return izip(keys, values) + +def from_utf8(s): + if isinstance(s, str): s = s.decode('fuzzy utf8') + return s + +def format_packet(data, maxlen = 500, sensitive = False): + if sensitive: + items = [] + append = items.append + for k, v in from_ydict_iter(data): + k = ykeys.get(k, k) + if k == 'message': + v = '' + append((k, v)) + else: + items = [(ykeys.get(k, k), v) + for k, v in from_ydict_iter(data)] + + return pformat(items) + +def yiter_to_dict(yiter): + d = odict() + + for k, v in yiter: + try: k = ykeys[k] + except KeyError: pass + d[k] = v + + return d + +def from_ydict(data, argsep=argsep): + '''Turns a special contact yahoo dictionary into [a] Python dictionar[y/ies]. + + If duplicate keys are found, all subsequent keys get turned into seperate + dicts.''' + + + d = {} + + for k, v in from_ydict_iter(data, argsep=argsep): + if k in d: + log.warning('duplicate %s: %s', k, v) + #raise AssertionError() + + d[k] = v + + return d + +# the layout of a Yahoo packet header has the following binary layout: +header_pack = '!4sHHHHiI' + +# the binary entries above correspond to the following names: +header_desc = (header_pack, + 'ymsg', # 4 + 'version', # 2 + 'zero', # 2 + 'size', # 2 + 'command','status', # 2, 4 + 'session_id') # 4 +header_size = calcsize(header_pack) + +#TODO: use Packable in the above + +def header_tostr(hdr): + "Prints out a nice string representation of a YMSG header." + try: + sv = commands[hdr.command] + except KeyError: + log.error("No command string for %s", hdr.command) + sv = str(hdr.command) + + try: + st = commands[hdr.status] + except KeyError: + log.error("No status string for %s", hdr.status) + st = str(hdr.status) + + return 'YMSG packet( srv:%s, st:%s, id:%d v:%d, sz:%d )' % \ + (sv, st, hdr.session_id, hdr.version, hdr.size) + +@threaded +def yahoo_http_post(ydata, cookies, progress_cb = lambda x: None): + conn = HTTPConnection('filetransfer.msg.yahoo.com') + + # Hack httplib to send HTTP/1.0 as the version + conn._http_vsn_str = 'HTTP/1.0' + conn._http_vsn = 10 + + #conn.set_debuglevel(3) + url = 'http://filetransfer.msg.yahoo.com:80/notifyft' + conn.putrequest('POST', url, skip_host = True, skip_accept_encoding=True) + + conn.putheader ('Content-length', str(len(ydata))) + conn.putheader ('Host', 'filetransfer.msg.yahoo.com:80') + conn.putheader ('Cookie', cookies) + conn.endheaders() + + log.info('putting %d bytes of data...', len(ydata)) + + for x in xrange(0, len(ydata), 512): + conn.send(ydata) + progress_cb(x) + + progress_cb(len(ydata)) + + # Check for OK + response = conn.getresponse() + respdata, status = response.read(), response.status + + log.info('response data %d bytes, status code %s', len(respdata), status) + + conn.close() + + if status != 200: + log.error('ERROR: POST returned a status of %d', status) + return False + + info('HTTP POST response status %d', status) + return True + +class Cookie(str): + def __init__(self, s): + str.__init__(s) + info('Cookie string %s', s) + self.params = odict() + + for pair in s.split('&'): + key, val = pair.split('=') + self.params[key] = val + + def val(self): + return '&'.join('%s=%s' % (k,v) for k,v in self.params.items()) + +def add_cookie(jar, cookiestr): + assert isinstance(jar, dict) + + spl = cookiestr.find('\t') + assert spl != -1 + + key, value = cookiestr[:spl], cookiestr[spl + 1:] + + value = value.split('; ')[0] + log.info('adding cookie %s: %s', key, value) + jar[key] = value + +def y_webrequest(url, data, cookies): + headers = {'Cookie': cookies['Y'] + '; ' + cookies['T'], + 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5)', + 'Cache-Control': 'no-cache'} + + import urllib2 + req = urllib2.Request(url, data, headers) + response = urllib2.urlopen(req) + return response.read() + + + +if __name__ == '__main__': + + cookies = {'Y':'v=1&n=3lutd2l220eoo&l=a4l8dm0jj4hisqqv/o&p=m2l0e8v002000000&r=fr&lg=us&intl=us', + 'T':'z=3H2OGB3NLPGBQXPLeKgW24v&a=YAE&sk=DAAHHqpEue2zZY&d=YQFZQUUBb2sBWlcwLQF0aXABQldCb2lBAXp6ATNIMk9HQmdXQQ--'} + + data = '' + + print y_webrequest('http://validate.msg.yahoo.com/mobileno?intl=us&version=8.1.0.209', + data, cookies) diff --git a/digsby/src/yahoo/yfiletransfer.py b/digsby/src/yahoo/yfiletransfer.py new file mode 100644 index 0000000..9c39001 --- /dev/null +++ b/digsby/src/yahoo/yfiletransfer.py @@ -0,0 +1,233 @@ +from __future__ import with_statement +from httplib import IncompleteRead +from .YahooSocket import DEFAULT_YMSG_VERSION + +FILE_XFER_URL = 'http://filetransfer.msg.yahoo.com:80/notifyft' + +PROXY_FILESIZE_LIMIT = 2 ** 20 # 10 MB + +import common +from traceback import print_exc +from util import threaded, chained_files +from .yahooutil import filename_from_url +import struct +from StringIO import StringIO +from util.net import HTTPConnProgress + + +from logging import getLogger +log = getLogger('yahoo.ft'); info = log.info; error = log.error + +in_cls = common.IncomingHTTPFileTransfer +out_cls = common.filetransfer.OutgoingFileTransfer + +# first eight bytes of PNGs (the only thing Yahoo's icon sever will accept) +PNG_HEADER = struct.pack('8B', 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A) + +class YahooHTTPIncoming(in_cls): + 'Incoming HTTP file transfer.' + + def __init__(self, protocol, buddy, url): + + # Use filename_from_url to grab filename + in_cls.__init__(self, protocol, buddy, filename_from_url(url), url) + +def set_buddy_icon(yahoo, image_data): + log.info('set_buddy_icon %r', yahoo) + YahooIconSender.send_buddy_icon(yahoo, image_data) + + +class OutgoingYHTTPXfer(out_cls): + + direction = 'outgoing' + http_path = '/notifyft' + host = 'filetransfer.msg.yahoo.com:80' + conn_host = 'filetransfer.msg.yahoo.com' + + def __init__(self, protocol, buddy = None, fileinfo = None, initiate = True): + out_cls.__init__(self) + + if fileinfo is not None: + self.filepath = fpath = fileinfo.files[0] + self.name = fpath.name + self.size = fpath.size + + self.buddy = buddy + self.protocol = protocol + self.cancelled = False + + + + if hasattr(fileinfo, 'obj'): fileinfo.obj.close() + if initiate: + self.initiate_transfer() + + def initiate_transfer(self, message = ''): + filesize = self.filepath.size + + # Yahoo's proxy server has a filesize limit. + if filesize > PROXY_FILESIZE_LIMIT: + self.state = self.states.PROXY_XFER_FILESIZE_ERROR + return + + # squeeze the following YDict into an HTTP post to Yahoo's file + # transfer server + protocol = self.protocol + ydata = protocol.yahoo_packet_v_bin(DEFAULT_YMSG_VERSION, + 'filetransfer', 'available', + 'away_buddy', protocol.self_buddy.name, + 'to', self.buddy.name, + 'message', message, + 'filename', self.name, + 'filesize', str(filesize), + 'filedata', '') + + sublen = struct.unpack('!H', ydata[8:10])[0] - (8 + len(str(filesize))) + 14 + ydata = ydata[:8] + struct.pack('!H', sublen-1) + ydata[10:-2] + + self.state = self.states.TRANSFERRING + + try: + fileobj = file(self.filepath, 'rb') + except: + print_exc() + self.state = self.states.CONN_FAIL + self.on_error() + else: + self._post_file(ydata, protocol.cookie_str, fileobj = fileobj, filesize = filesize) + + def cancel(self): + try: + self.conn.close() + del self.conn + except: + pass + + self.cancelled = True + self.state = self.states.CANCELLED_BY_YOU + + + @threaded + def _post_file(self, ydata, cookies, fileobj = None, filesize = None): + try: + self.conn = conn = HTTPConnProgress(self.conn_host) + except Exception, e: + print_exc() + if not self.cancelled: + self.state = self.states.CONN_FAIL + self.on_error() + return False + + # Hack httplib to send HTTP/1.0 as the version +# conn._http_vsn_str = 'HTTP/1.0' +# conn._http_vsn = 10 + + #conn.set_debuglevel(3) + conn.putrequest('POST', self.http_path, skip_host = True, skip_accept_encoding=True) +# conn.putheader ('Referer', 'foo') + conn.putheader ('Cookie', cookies) +# conn.putheader ('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5)') + conn.putheader ('Host', self.host) + conn.putheader ('Content-Length', str(len(ydata) + filesize)) + conn.putheader ('Cache-Control', 'no-cache') + + conn.endheaders() + + diff = len(ydata) - filesize + + try: + # use "chained_files" function to send file without reading it into a str + if ydata != '': + conn.send_file_cb(chained_files([ydata, fileobj]), + self._setcompleted, + progressDelta = len(ydata)) # report bytes MINUS the length of the YMSG header + else: + conn.send_file_cb(fileobj, self._setcompleted) + except: + if not self.cancelled: + print_exc() + self.state = self.states.CONN_FAIL + try: conn.close() + except: pass + finally: + try: fileobj.close() + except: pass + + if self.state != self.states.TRANSFERRING: + return + + + # we're done--make sure the progress bar is at 100% + self._setcompleted(filesize) + + # Check for OK + try: + response = conn.getresponse() + respdata, status = response.read(), response.status + + log.info('response data %d bytes, status code %s', len(respdata), status) + log.info('response data %s', respdata) + + if status != 200: + self.state = self.states.CONN_FAIL_XFER + log.error('ERROR: POST returned a status of %d', status) + return False + + info('HTTP POST response status %d', status) + except IncompleteRead: + if ydata != '': + raise + finally: + conn.close() + + self.state = self.states.FINISHED + self._ondone() + return True + + +class YahooIconSender(OutgoingYHTTPXfer): + def __repr__(self): + return '' + + @classmethod + def send_buddy_icon(cls, protocol, image_data): + + # yahoo's server accepts only PNGs--make the icon one if it isn't already. + from PIL import Image + i = Image.open(StringIO(image_data)) + if (image_data[:8] != PNG_HEADER) or (i.size != (96, 96)): + converted = StringIO() + i2 = i.Resized(96) + i2.save(converted, 'PNG') + image_data = converted.getvalue() + + xfer = YahooIconSender(protocol, initiate = False) + + selfname = protocol.self_buddy.name + filesize = len(image_data) + + ydata = protocol.yahoo_packet_v_bin(DEFAULT_YMSG_VERSION, + 'picture_upload', 'available', + 'frombuddy', selfname, + 'expires', struct.pack('6s', '604800'), + 'away_buddy', selfname, + 'filesize', str(filesize), + 'filename', 'myicon.png', + 'message', ' ', + 'filedata', '') + + sublen = struct.unpack('!H', ydata[8:10])[0] - (8 + len(str(filesize))) + 14 + ydata = ydata[:8] + struct.pack('!H', sublen-1) + ydata[10:-2] + + xfer.state = xfer.states.TRANSFERRING + + log.info('sending %d bytes of image data', filesize) + xfer._post_file(ydata, protocol.cookie_str, fileobj = StringIO(image_data), filesize = filesize) + + + def _ondone(self): + self.setnotifyif('state', self.states.FINISHED) + self._done = True + + def on_error(self): + log.critical('error sending icon') diff --git a/digsby/tagtrunk.py b/digsby/tagtrunk.py new file mode 100644 index 0000000..1b555c5 --- /dev/null +++ b/digsby/tagtrunk.py @@ -0,0 +1,54 @@ +''' +copies digsby TRUNK to tags named after the time +''' + +digsbyroot = 'http://mini/svn/dotSyntax/digsby' + +import os +import sys +from datetime import datetime + + +def shell(cmd): + print cmd + + proc = os.popen(cmd) + res = proc.read() + print res + retCode = proc.close() + if retCode is not None: + raise Exception('subprocess %r failed with error %s' % (cmd, retCode)) + + return res + +def timestamp(): + 'iso formatted utcnow without secs and microsecs' + return datetime.utcnow().isoformat('_')[:-10] + +def dotag(m='Tagging for release'): + t = timestamp() + message = '%s at %s' % (m, t) + shell('svn copy %s/trunk %s/tags/%s -m "%s"' % (digsbyroot, digsbyroot, t, message)) + +def dobranch(m='Branching for release'): + t = timestamp() + message = '%s at %s' % (m, t) + + from package import _get_svn_rev + rev = _get_svn_rev(['%s/trunk' % digsbyroot]) + branchname = 'maint-%s' % rev + shell('svn copy %s/trunk %s/branches/%s -m "%s"' % (digsbyroot, digsbyroot, branchname, message)) + +if __name__ == '__main__': + operation = sys.argv[1] + if operation == 'branch': + do = dobranch + elif operation == 'tag': + do = dotag + else: + raise Exception("Unknown operation %r" % operation) + + if len(sys.argv) > 2: + do(sys.argv[1]) + else: + do() diff --git a/digsby/tests.bat b/digsby/tests.bat new file mode 100644 index 0000000..f1211d9 --- /dev/null +++ b/digsby/tests.bat @@ -0,0 +1,7 @@ +@echo off +setlocal + +echo Running digsby unit tests in src/tests/unittests + +set PYTHONPATH=.;.\src;.\lib;.\platlib\win;.\ext\win;.\src\plugins +build\msw\dpython\python.exe src\tests\unittests\runtests.py diff --git a/digsby/windist.bat b/digsby/windist.bat new file mode 100644 index 0000000..4652190 --- /dev/null +++ b/digsby/windist.bat @@ -0,0 +1,27 @@ +rem +rem builds a Digsby windows installer +rem + +set DIGSBY_INSTALLER_DIR=build\msw\DigsbyInstaller +set DIGSBY_LAUNCHER_DIR=c:\dev\digsby-launcher\bin + +rem update all sources +svn up + +pushd %DIGSBY_INSTALLER_DIR% +svn up +popd + +pushd %DIGSBY_LAUNCHER_DIR% +svn up +popd + +rem make the distribution and installer +call dpy makedist.py -p package.py %1 %2 %3 %4 %5 %6 + +rem copy installers over to mini +copy dist\digsby_setup*.exe \\192.168.1.100\mike /Y + +rem pushd build\msw +rem call ss.bat +rem popd diff --git a/dist/.project b/dist/.project new file mode 100644 index 0000000..0d29547 --- /dev/null +++ b/dist/.project @@ -0,0 +1,18 @@ + + + digsby-dist + + + digsby + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/dist/.pydevproject b/dist/.pydevproject new file mode 100644 index 0000000..09be905 --- /dev/null +++ b/dist/.pydevproject @@ -0,0 +1,13 @@ + + + + + +/digsby-dist/src +/digsby-dist/lib + +python 2.6 +digsby-venv + + + diff --git a/dist/deploy.yaml b/dist/deploy.yaml new file mode 100644 index 0000000..cbaa62f --- /dev/null +++ b/dist/deploy.yaml @@ -0,0 +1,153 @@ +- clean: + ## Clean all artifact directories created. + - digsby: + paths: + - &build !path [&dist !path [&root !path './', 'dist'], build] + - &freeze !path [*dist, freeze] + - &prep !path [*dist, prep] + - &output !path [*dist, output] + - &install_output !path [*dist, install] +- checkout: + ## Update the working copy + - git: + source: *root + remote: origin + branch: master +# - compile: + ## TODO: Compile any binaries we need + # - msvc: {} + ## TODO: send debug symbols to our symbol store + # - sendsymbols: {} +- test: + ## TODO: Check all unittests pass + # - unittest: {} + ## TODO: Check buildbot status + # - buildbot: {} + ## Sanitycheck to make sure all required components are present and ready to go. + ## XXX: currently this is performing double-duty and getting build information + ## related to MSVC DLLs + - sanitycheck: + path: *root + launcher_exe: !path [*root, digsby, ext, msw, DigsbyLauncher.exe] +- freeze: + ## Py2Exe options. + - py2exe: + source: &digsby_src !path [*root, digsby] + dest: *freeze + distutils_verbosity: 0 + distutils_options: + options: + py2exe: + optimize: 2 + compressed: 0 + retain_times: 1 + skip_archive: 1 + verbose: 0 + dist_dir: *freeze + dll_excludes: [ + 'MSVCP90.dll' + ] + includes: [ + 'plugins', + 'distutils', + 'pkg_resources' + ] + excludes: [ + 'sre', + 'Tkconstants', + 'Tkinter', + 'tcl', + '_imagingtk', + 'PIL._imagingtk', + 'ImageTk', + 'PIL.ImageTk', + 'FixTk', + 'platlib.mac', + 'aifc', + 'BaseHTTPServer', + 'bdb', + 'chunk', + 'colorsys', + 'doctest', + 'dummy_thread', + 'dummy_threading', + 'formatter', + 'getpass', + 'gopherlib', + 'imghdr', + 'macpath', + 'macurl2path', + 'multifile', + 'os2emxpath', + 'pdb', + 'py_compile', + 'pydoc', + 'repr', + 'sndhdr', + 'sre', + 'symtable', + 'tty', + 'webbrowser', + 'distutils.tests' + ] + windows: + - script: !path [*digsby_src, "Digsby.py"] + dest_base: '%%NAME%%-app' + icon_resources: + - !!python/tuple [1, !path [*digsby_src, res, "digsby.ico"]] + version: '%%DQVERSION%%' + product_version: '%%VERSION%%' + comments: '%%COMMENTS%%' + company_name: '%%COMPANY%%' + copyright: '%%COPYRIGHT%%' + name: '%%NAME%%' + description: '%%DESCRIPTION%%' + other_resources: [] + zipfile: '' + script_args: ['py2exe'] + distutils_vals: + OUTFILEPREFIX: "" + RELEASE: 0 + NAME: 'digsby' + DESCRIPTION: 'Digsby' + COMMENTS: 'digsby IM - A communications dashboard' + COPYRIGHT: 'Copyright (C) 2012 Tagged, Inc' + URL: 'http://github.com/tagged/digsby' + SUPPORTURL: 'http://github.com/tagged/digsby/wiki' + DQVERSION: '1.0.0.0' +- prepare: + ## Prompt to make sure the builder has updated the release_notes.html file + # - release_notes: {} + ## Ensures that 'badfiles' are removed in the build output, and rearranges + ## the build output to match our desired install directory layout. this also + ## generates the 'manifest' for our updater. + - digsby: + source: *freeze + dest: *prep +- verify: + ## Currently just ensures no source is distributed (based on blacklist of file + ## extensions in helpers.py) + - digsby: + path: *prep +- package: + ## Create an NSIS installer with our content. + ## NOTE: all of these options are funneled to NSIS via PyInfo.nsh at build time. + - nsis: + DIGSBY_INSTALLER_DIR: &installer_source !path [*root, installer] + PRODUCT_WEB_SITE: "http://github.com/tagged/digsby" + PLURA_LEARNMORE_URL: "https://github.com/tagged/digsby/wiki/CPU-Usage" + DOCUMENTATION_URL: "http://github.com/tagged/digsby/wiki" + FORUM_SUPPORT_URL: "http://github.com/tagged/digsby/issues" + source: *prep + dest: *install_output + script: !path [*installer_source, 'DigsbyInstall.nsi'] + path_nsis: !path [*installer_source, nsis, 'makensis.exe'] + instver: 7 + use_no_adware_image: yes + foldername: 'install' + honest: yes +- prepare: + ## Moves the prepared files into the ultimate 'output' location. + - move_files: + source: *prep + dest_dir: *output diff --git a/dist/lib/path.py b/dist/lib/path.py new file mode 100644 index 0000000..6ecf3c9 --- /dev/null +++ b/dist/lib/path.py @@ -0,0 +1,1005 @@ +""" path.py - An object representing a path to a file or directory. + +Example: + +from path import path +d = path('/home/guido/bin') +for f in d.files('*.py'): + f.chmod(0755) + +This module requires Python 2.2 or later. + + +URL: http://www.jorendorff.com/articles/python/path +Author: Jason Orendorff (and others - see the url!) +Date: 9 Mar 2007 +""" + + +# TODO +# - Tree-walking functions don't avoid symlink loops. Matt Harrison +# sent me a patch for this. +# - Bug in write_text(). It doesn't support Universal newline mode. +# - Better error message in listdir() when self isn't a +# directory. (On Windows, the error message really sucks.) +# - Make sure everything has a good docstring. +# - Add methods for regex find and replace. +# - guess_content_type() method? +# - Perhaps support arguments to touch(). + +from __future__ import generators + +import sys, warnings, os, fnmatch, glob, shutil, codecs, hashlib +from os.path import join as pathjoin + +__version__ = '2.2' +__all__ = ['path'] + +# Platform-specific support for path.owner +if os.name == 'nt': + try: + import win32security + except ImportError: + win32security = None +else: + try: + import pwd + except ImportError: + pwd = None + +# Pre-2.3 support. Are unicode filenames supported? +_base = str +_getcwd = os.getcwd +try: + if os.path.supports_unicode_filenames: + _base = unicode + _getcwd = os.getcwdu + _filesystem_encoding = sys.getfilesystemencoding() +except AttributeError: + pass + +# Pre-2.3 workaround for booleans +try: + True, False +except NameError: + True, False = 1, 0 + +# Pre-2.3 workaround for basestring. +try: + basestring +except NameError: + basestring = (str, unicode) + +# Universal newline support +_textmode = 'r' +if hasattr(file, 'newlines'): + _textmode = 'U' + + +class TreeWalkWarning(Warning): + pass + +class path(_base): + """ Represents a filesystem path. + + For documentation on individual methods, consult their + counterparts in os.path. + """ + + # --- Special Python methods. + + def __repr__(self): + return 'path(%s)' % _base.__repr__(self) + + # Adding a path and a string yields a path. + def __add__(self, more): + try: + resultStr = _base.__add__(self, more) + except TypeError: #Python bug + resultStr = NotImplemented + if resultStr is NotImplemented: + return resultStr + return self.__class__(resultStr) + + def __radd__(self, other): + if isinstance(other, basestring): + return self.__class__(other.__add__(self)) + else: + return NotImplemented + + # The / operator joins paths. + def __div__(self, rel): + """ fp.__div__(rel) == fp / rel == fp.joinpath(rel) + + Join two path components, adding a separator character if + needed. + """ + return self.__class__(pathjoin(self, rel)) + + # Make the / operator work even when true division is enabled. + __truediv__ = __div__ + + def getcwd(cls): + """ Return the current working directory as a path object. """ + return cls(_getcwd()) + getcwd = classmethod(getcwd) + + + # --- Operations on path strings. + + isabs = os.path.isabs + def url(self): return self.__class__('file:///' + self.abspath().replace('\\', '/')) + def abspath(self): return self.__class__(os.path.abspath(self)) + def normcase(self): return self.__class__(os.path.normcase(self)) + def normpath(self): return self.__class__(os.path.normpath(self)) + def realpath(self): return self.__class__(os.path.realpath(self)) + def expanduser(self): return self.__class__(os.path.expanduser(self)) + def expandvars(self): return self.__class__(os.path.expandvars(self)) + def dirname(self): return self.__class__(os.path.dirname(self)) + basename = os.path.basename + + def expand(self): + """ Clean up a filename by calling expandvars(), + expanduser(), and normpath() on it. + + This is commonly everything needed to clean up a filename + read from a configuration file, for example. + """ + return self.expandvars().expanduser().normpath().normcase() + + def _get_namebase(self): + base, ext = os.path.splitext(self.name) + return base + + def _get_ext(self): + f, ext = os.path.splitext(_base(self)) + return ext + + def _get_drive(self): + drive, r = os.path.splitdrive(self) + return self.__class__(drive) + + parent = property( + dirname, None, None, + """ This path's parent directory, as a new path object. + + For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib') + """) + + name = property( + basename, None, None, + """ The name of this file or directory without the full path. + + For example, path('/usr/local/lib/libpython.so').name == 'libpython.so' + """) + + namebase = property( + _get_namebase, None, None, + """ The same as path.name, but with one file extension stripped off. + + For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz', + but path('/home/guido/python.tar.gz').namebase == 'python.tar' + """) + + ext = property( + _get_ext, None, None, + """ The file extension, for example '.py'. """) + + drive = property( + _get_drive, None, None, + """ The drive specifier, for example 'C:'. + This is always empty on systems that don't use drive specifiers. + """) + + def splitpath(self): + """ p.splitpath() -> Return (p.parent, p.name). """ + parent, child = os.path.split(self) + return self.__class__(parent), child + + def splitdrive(self): + """ p.splitdrive() -> Return (p.drive, ). + + Split the drive specifier from this path. If there is + no drive specifier, p.drive is empty, so the return value + is simply (path(''), p). This is always the case on Unix. + """ + drive, rel = os.path.splitdrive(self) + return self.__class__(drive), rel + + def splitext(self): + """ p.splitext() -> Return (p.stripext(), p.ext). + + Split the filename extension from this path and return + the two parts. Either part may be empty. + + The extension is everything from '.' to the end of the + last path segment. This has the property that if + (a, b) == p.splitext(), then a + b == p. + """ + filename, ext = os.path.splitext(self) + return self.__class__(filename), ext + + def stripext(self): + """ p.stripext() -> Remove one file extension from the path. + + For example, path('/home/guido/python.tar.gz').stripext() + returns path('/home/guido/python.tar'). + """ + return self.splitext()[0] + + if hasattr(os.path, 'splitunc'): + def splitunc(self): + unc, rest = os.path.splitunc(self) + return self.__class__(unc), rest + + def _get_uncshare(self): + unc, r = os.path.splitunc(self) + return self.__class__(unc) + + uncshare = property( + _get_uncshare, None, None, + """ The UNC mount point for this path. + This is empty for paths on local drives. """) + + def joinpath(self, *args): + """ Join two or more path components, adding a separator + character (os.sep) if needed. Returns a new path + object. + """ + return self.__class__(pathjoin(self, *args)) + + def splitall(self): + r""" Return a list of the path components in this path. + + The first item in the list will be a path. Its value will be + either os.curdir, os.pardir, empty, or the root directory of + this path (for example, '/' or 'C:\\'). The other items in + the list will be strings. + + path.path.joinpath(*result) will yield the original path. + """ + parts = [] + loc = self + while loc != os.curdir and loc != os.pardir: + prev = loc + loc, child = prev.splitpath() + if loc == prev: + break + parts.append(child) + parts.append(loc) + parts.reverse() + return parts + + def relpath(self): + """ Return this path as a relative path, + based from the current working directory. + """ + cwd = self.__class__(os.getcwd()) + return cwd.relpathto(self) + + def relpathto(self, dest): + """ Return a relative path from self to dest. + + If there is no relative path from self to dest, for example if + they reside on different drives in Windows, then this returns + dest.abspath(). + """ + origin = self.abspath() + dest = self.__class__(dest).abspath() + + orig_list = origin.normcase().splitall() + # Don't normcase dest! We want to preserve the case. + dest_list = dest.splitall() + + if orig_list[0] != os.path.normcase(dest_list[0]): + # Can't get here from there. + return dest + + # Find the location where the two paths start to differ. + i = 0 + for start_seg, dest_seg in zip(orig_list, dest_list): + if start_seg != os.path.normcase(dest_seg): + break + i += 1 + + # Now i is the point where the two paths diverge. + # Need a certain number of "os.pardir"s to work up + # from the origin to the point of divergence. + segments = [os.pardir] * (len(orig_list) - i) + # Need to add the diverging part of dest_list. + segments += dest_list[i:] + if len(segments) == 0: + # If they happen to be identical, use os.curdir. + relpath = os.curdir + else: + relpath = pathjoin(*segments) + return self.__class__(relpath) + + # --- Listing, searching, walking, and matching + + def listdir(self, pattern=None): + """ D.listdir() -> List of items in this directory. + + Use D.files() or D.dirs() instead if you want a listing + of just files or just subdirectories. + + The elements of the list are path objects. + + With the optional 'pattern' argument, this only lists + items whose names match the given pattern. + """ + try: + names = os.listdir(self) + except Exception: + return [] + if pattern is not None: + names = fnmatch.filter(names, pattern) + return [self / child for child in names] + + def dirs(self, pattern=None): + """ D.dirs() -> List of this directory's subdirectories. + + The elements of the list are path objects. + This does not walk recursively into subdirectories + (but see path.walkdirs). + + With the optional 'pattern' argument, this only lists + directories whose names match the given pattern. For + example, d.dirs('build-*'). + """ + return [p for p in self.listdir(pattern) if p.isdir()] + + def files(self, pattern=None): + """ D.files() -> List of the files in this directory. + + The elements of the list are path objects. + This does not walk into subdirectories (see path.walkfiles). + + With the optional 'pattern' argument, this only lists files + whose names match the given pattern. For example, + d.files('*.pyc'). + """ + + return [p for p in self.listdir(pattern) if p.isfile()] + + def walk(self, pattern=None, errors='strict'): + """ D.walk() -> iterator over files and subdirs, recursively. + + The iterator yields path objects naming each child item of + this directory and its descendants. This requires that + D.isdir(). + + This performs a depth-first traversal of the directory tree. + Each directory is returned just before all its children. + + The errors= keyword argument controls behavior when an + error occurs. The default is 'strict', which causes an + exception. The other allowed values are 'warn', which + reports the error via warnings.warn(), and 'ignore'. + """ + if errors not in ('strict', 'warn', 'ignore'): + raise ValueError("invalid errors parameter") + + try: + childList = self.listdir() + except Exception: + if errors == 'ignore': + return + elif errors == 'warn': + warnings.warn( + "Unable to list directory '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + return + else: + raise + + for child in childList: + if pattern is None or child.fnmatch(pattern): + yield child + try: + isdir = child.isdir() + except Exception: + if errors == 'ignore': + isdir = False + elif errors == 'warn': + warnings.warn( + "Unable to access '%s': %s" + % (child, sys.exc_info()[1]), + TreeWalkWarning) + isdir = False + else: + raise + + if isdir: + for item in child.walk(pattern, errors): + yield item + + def walkdirs(self, pattern=None, errors='strict', top_down=True): + """ D.walkdirs() -> iterator over subdirs, recursively. + + With the optional 'pattern' argument, this yields only + directories whose names match the given pattern. For + example, mydir.walkdirs('*test') yields only directories + with names ending in 'test'. + + The errors= keyword argument controls behavior when an + error occurs. The default is 'strict', which causes an + exception. The other allowed values are 'warn', which + reports the error via warnings.warn(), and 'ignore'. + """ + if errors not in ('strict', 'warn', 'ignore'): + raise ValueError("invalid errors parameter") + + try: + dirs = self.dirs() + except Exception: + if errors == 'ignore': + return + elif errors == 'warn': + warnings.warn( + "Unable to list directory '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + return + else: + raise + + for child in dirs: + if top_down: + if pattern is None or child.fnmatch(pattern): + yield child + for subsubdir in child.walkdirs(pattern, errors): + yield subsubdir + + if not top_down: + if pattern is None or child.fnmatch(pattern): + yield child + + def walkfiles(self, pattern=None, errors='strict'): + """ D.walkfiles() -> iterator over files in D, recursively. + + The optional argument, pattern, limits the results to files + with names that match the pattern. For example, + mydir.walkfiles('*.tmp') yields only files with the .tmp + extension. + """ + if errors not in ('strict', 'warn', 'ignore'): + raise ValueError("invalid errors parameter") + + try: + childList = self.listdir() + except Exception: + if errors == 'ignore': + return + elif errors == 'warn': + warnings.warn( + "Unable to list directory '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + return + else: + raise + + for child in childList: + try: + isfile = child.isfile() + isdir = not isfile and child.isdir() + except: + if errors == 'ignore': + continue + elif errors == 'warn': + warnings.warn( + "Unable to access '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + continue + else: + raise + + if isfile: + if pattern is None or child.fnmatch(pattern): + yield child + elif isdir: + for f in child.walkfiles(pattern, errors): + yield f + + def fnmatch(self, pattern): + """ Return True if self.name matches the given pattern. + + pattern - A filename pattern with wildcards, + for example '*.py'. + """ + return fnmatch.fnmatch(self.name, pattern) + + def glob(self, pattern): + """ Return a list of path objects that match the pattern. + + pattern - a path relative to this directory, with wildcards. + + For example, path('/users').glob('*/bin/*') returns a list + of all the files users have in their bin directories. + """ + cls = self.__class__ + return [cls(s) for s in glob.glob(_base(self / pattern))] + + + # --- Reading or writing an entire file at once. + + def open(self, mode='r'): + """ Open this file. Return a file object. """ + return file(self, mode) + + def openfolder(self): + 'Opens an explorer window with this file selected.' + + if os.name == 'nt' and (self.isfile() or self.isdir()): + # Tell explorer to highlight the file in its folder. + from subprocess import Popen + args = ['explorer', '/select,', '%s' % self.abspath().encode(sys.getfilesystemencoding())] + Popen(args, shell = True) + else: + os.startfile(self.parent) + + def bytes(self): + """ Open this file, read all bytes, return them as a string. """ + f = self.open('rb') + try: + return f.read() + finally: + f.close() + + def write_bytes(self, bytes, append=False): + """ Open this file and write the given bytes to it. + + Default behavior is to overwrite any existing file. + Call p.write_bytes(bytes, append=True) to append instead. + """ + if append: + mode = 'ab' + else: + mode = 'wb' + f = self.open(mode) + try: + f.write(bytes) + finally: + f.close() + + def text(self, encoding=None, errors='strict'): + r""" Open this file, read it in, return the content as a string. + + This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r' + are automatically translated to '\n'. + + Optional arguments: + + encoding - The Unicode encoding (or character set) of + the file. If present, the content of the file is + decoded and returned as a unicode object; otherwise + it is returned as an 8-bit str. + errors - How to handle Unicode errors; see help(str.decode) + for the options. Default is 'strict'. + """ + if encoding is None: + # 8-bit + f = self.open(_textmode) + try: + return f.read() + finally: + f.close() + else: + # Unicode + f = codecs.open(self, 'r', encoding, errors) + # (Note - Can't use 'U' mode here, since codecs.open + # doesn't support 'U' mode, even in Python 2.3.) + try: + t = f.read() + finally: + f.close() + return (t.replace(u'\r\n', u'\n') + .replace(u'\r\x85', u'\n') + .replace(u'\r', u'\n') + .replace(u'\x85', u'\n') + .replace(u'\u2028', u'\n')) + + def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False): + r""" Write the given text to this file. + + The default behavior is to overwrite any existing file; + to append instead, use the 'append=True' keyword argument. + + There are two differences between path.write_text() and + path.write_bytes(): newline handling and Unicode handling. + See below. + + Parameters: + + - text - str/unicode - The text to be written. + + - encoding - str - The Unicode encoding that will be used. + This is ignored if 'text' isn't a Unicode string. + + - errors - str - How to handle Unicode encoding errors. + Default is 'strict'. See help(unicode.encode) for the + options. This is ignored if 'text' isn't a Unicode + string. + + - linesep - keyword argument - str/unicode - The sequence of + characters to be used to mark end-of-line. The default is + os.linesep. You can also specify None; this means to + leave all newlines as they are in 'text'. + + - append - keyword argument - bool - Specifies what to do if + the file already exists (True: append to the end of it; + False: overwrite it.) The default is False. + + + --- Newline handling. + + write_text() converts all standard end-of-line sequences + ('\n', '\r', and '\r\n') to your platform's default end-of-line + sequence (see os.linesep; on Windows, for example, the + end-of-line marker is '\r\n'). + + If you don't like your platform's default, you can override it + using the 'linesep=' keyword argument. If you specifically want + write_text() to preserve the newlines as-is, use 'linesep=None'. + + This applies to Unicode text the same as to 8-bit text, except + there are three additional standard Unicode end-of-line sequences: + u'\x85', u'\r\x85', and u'\u2028'. + + (This is slightly different from when you open a file for + writing with fopen(filename, "w") in C or file(filename, 'w') + in Python.) + + + --- Unicode + + If 'text' isn't Unicode, then apart from newline handling, the + bytes are written verbatim to the file. The 'encoding' and + 'errors' arguments are not used and must be omitted. + + If 'text' is Unicode, it is first converted to bytes using the + specified 'encoding' (or the default encoding if 'encoding' + isn't specified). The 'errors' argument applies only to this + conversion. + + """ + if isinstance(text, unicode): + if linesep is not None: + # Convert all standard end-of-line sequences to + # ordinary newline characters. + text = (text.replace(u'\r\n', u'\n') + .replace(u'\r\x85', u'\n') + .replace(u'\r', u'\n') + .replace(u'\x85', u'\n') + .replace(u'\u2028', u'\n')) + text = text.replace(u'\n', linesep) + if encoding is None: + encoding = sys.getdefaultencoding() + bytes = text.encode(encoding, errors) + else: + # It is an error to specify an encoding if 'text' is + # an 8-bit string. + assert encoding is None + + if linesep is not None: + text = (text.replace('\r\n', '\n') + .replace('\r', '\n')) + bytes = text.replace('\n', linesep) + + self.write_bytes(bytes, append) + + def lines(self, encoding=None, errors='strict', retain=True): + r""" Open this file, read all lines, return them in a list. + + Optional arguments: + encoding - The Unicode encoding (or character set) of + the file. The default is None, meaning the content + of the file is read as 8-bit characters and returned + as a list of (non-Unicode) str objects. + errors - How to handle Unicode errors; see help(str.decode) + for the options. Default is 'strict' + retain - If true, retain newline characters; but all newline + character combinations ('\r', '\n', '\r\n') are + translated to '\n'. If false, newline characters are + stripped off. Default is True. + + This uses 'U' mode in Python 2.3 and later. + """ + if encoding is None and retain: + f = self.open(_textmode) + try: + return f.readlines() + finally: + f.close() + else: + return self.text(encoding, errors).splitlines(retain) + + def write_lines(self, lines, encoding=None, errors='strict', + linesep=os.linesep, append=False): + r""" Write the given lines of text to this file. + + By default this overwrites any existing file at this path. + + This puts a platform-specific newline sequence on every line. + See 'linesep' below. + + lines - A list of strings. + + encoding - A Unicode encoding to use. This applies only if + 'lines' contains any Unicode strings. + + errors - How to handle errors in Unicode encoding. This + also applies only to Unicode strings. + + linesep - The desired line-ending. This line-ending is + applied to every line. If a line already has any + standard line ending ('\r', '\n', '\r\n', u'\x85', + u'\r\x85', u'\u2028'), that will be stripped off and + this will be used instead. The default is os.linesep, + which is platform-dependent ('\r\n' on Windows, '\n' on + Unix, etc.) Specify None to write the lines as-is, + like file.writelines(). + + Use the keyword argument append=True to append lines to the + file. The default is to overwrite the file. Warning: + When you use this with Unicode data, if the encoding of the + existing data in the file is different from the encoding + you specify with the encoding= parameter, the result is + mixed-encoding data, which can really confuse someone trying + to read the file later. + """ + if append: + mode = 'ab' + else: + mode = 'wb' + f = self.open(mode) + try: + for line in lines: + isUnicode = isinstance(line, unicode) + if linesep is not None: + # Strip off any existing line-end and add the + # specified linesep string. + if isUnicode: + if line[-2:] in (u'\r\n', u'\x0d\x85'): + line = line[:-2] + elif line[-1:] in (u'\r', u'\n', + u'\x85', u'\u2028'): + line = line[:-1] + else: + if line[-2:] == '\r\n': + line = line[:-2] + elif line[-1:] in ('\r', '\n'): + line = line[:-1] + line += linesep + if isUnicode: + if encoding is None: + encoding = sys.getdefaultencoding() + line = line.encode(encoding, errors) + f.write(line) + finally: + f.close() + + def read_md5(self): + """ Calculate the md5 hash for this file. + + This reads through the entire file. + """ + f = self.open('rb') + try: + m = hashlib.md5() + while True: + d = f.read(8192) + if not d: + break + m.update(d) + finally: + f.close() + return m.digest() + + # --- Methods for querying the filesystem. + + def exists(self): + import warnings + warnings.warn("path.exists is deprecated. Use pth.isfile() or pth.isdir()") + return os.path.exists(self) + + isdir = os.path.isdir + isfile = os.path.isfile + islink = os.path.islink + ismount = os.path.ismount + + if hasattr(os.path, 'samefile'): + samefile = os.path.samefile + + getatime = os.path.getatime + atime = property( + getatime, None, None, + """ Last access time of the file. """) + + getmtime = os.path.getmtime + mtime = property( + getmtime, None, None, + """ Last-modified time of the file. """) + + if hasattr(os.path, 'getctime'): + getctime = os.path.getctime + ctime = property( + getctime, None, None, + """ Creation time of the file. """) + + getsize = os.path.getsize + size = property( + getsize, None, None, + """ Size of the file, in bytes. """) + + if hasattr(os, 'access'): + def access(self, mode): + """ Return true if current user has access to this path. + + mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK + """ + return os.access(self, mode) + + def stat(self): + """ Perform a stat() system call on this path. """ + return os.stat(self) + + def lstat(self): + """ Like path.stat(), but do not follow symbolic links. """ + return os.lstat(self) + + def get_owner(self): + r""" Return the name of the owner of this file or directory. + + This follows symbolic links. + + On Windows, this returns a name of the form ur'DOMAIN\User Name'. + On Windows, a group can own a file or directory. + """ + if os.name == 'nt': + if win32security is None: + raise Exception("path.owner requires win32all to be installed") + desc = win32security.GetFileSecurity( + self, win32security.OWNER_SECURITY_INFORMATION) + sid = desc.GetSecurityDescriptorOwner() + account, domain, typecode = win32security.LookupAccountSid(None, sid) + return domain + u'\\' + account + else: + if pwd is None: + raise NotImplementedError("path.owner is not implemented on this platform.") + st = self.stat() + return pwd.getpwuid(st.st_uid).pw_name + + owner = property( + get_owner, None, None, + """ Name of the owner of this file or directory. """) + + if hasattr(os, 'statvfs'): + def statvfs(self): + """ Perform a statvfs() system call on this path. """ + return os.statvfs(self) + + if hasattr(os, 'pathconf'): + def pathconf(self, name): + return os.pathconf(self, name) + + + # --- Modifying operations on files and directories + + def utime(self, times): + """ Set the access and modified times of this file. """ + os.utime(self, times) + + def chmod(self, mode): + os.chmod(self, mode) + + if hasattr(os, 'chown'): + def chown(self, uid, gid): + os.chown(self, uid, gid) + + def rename(self, new): + os.rename(self, new) + + def renames(self, new): + os.renames(self, new) + + + # --- Create/delete operations on directories + + def mkdir(self, mode=0777): + os.mkdir(self, mode) + + def makedirs(self, mode=0777): + os.makedirs(self, mode) + + def rmdir(self): + os.rmdir(self) + + def removedirs(self): + os.removedirs(self) + + + # --- Modifying operations on files + + def touch(self): + """ Set the access/modified times of this file to the current time. + Create the file if it does not exist. + """ + fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0666) + os.close(fd) + os.utime(self, None) + + def remove(self): + os.remove(self) + + def unlink(self): + os.unlink(self) + + + # --- Links + + if hasattr(os, 'link'): + def link(self, newpath): + """ Create a hard link at 'newpath', pointing to this file. """ + os.link(self, newpath) + + if hasattr(os, 'symlink'): + def symlink(self, newlink): + """ Create a symbolic link at 'newlink', pointing here. """ + os.symlink(self, newlink) + + if hasattr(os, 'readlink'): + def readlink(self): + """ Return the path to which this symbolic link points. + + The result may be an absolute or a relative path. + """ + return self.__class__(os.readlink(self)) + + def readlinkabs(self): + """ Return the path to which this symbolic link points. + + The result is always an absolute path. + """ + p = self.readlink() + if p.isabs(): + return p + else: + return (self.parent / p).abspath() + + + # --- High-level functions from shutil + + copyfile = shutil.copyfile + copymode = shutil.copymode + copystat = shutil.copystat + copy = shutil.copy + copy2 = shutil.copy2 + copytree = shutil.copytree + if hasattr(shutil, 'move'): + move = shutil.move + rmtree = shutil.rmtree + + + # --- Special stuff from os + + if hasattr(os, 'chroot'): + def chroot(self): + os.chroot(self) + + if hasattr(os, 'startfile'): + def startfile(self): + os.startfile(self) + + if _base is unicode: + def __str__(self): + return self.encode(_filesystem_encoding) + + def __new__(self, s): + if isinstance(s, str): + s = s.decode(_filesystem_encoding) + return unicode.__new__(self, s) + diff --git a/dist/lib/yaml/__init__.py b/dist/lib/yaml/__init__.py new file mode 100644 index 0000000..f977f46 --- /dev/null +++ b/dist/lib/yaml/__init__.py @@ -0,0 +1,315 @@ + +from error import * + +from tokens import * +from events import * +from nodes import * + +from loader import * +from dumper import * + +__version__ = '3.10' + +try: + from cyaml import * + __with_libyaml__ = True +except ImportError: + __with_libyaml__ = False + +def scan(stream, Loader=Loader): + """ + Scan a YAML stream and produce scanning tokens. + """ + loader = Loader(stream) + try: + while loader.check_token(): + yield loader.get_token() + finally: + loader.dispose() + +def parse(stream, Loader=Loader): + """ + Parse a YAML stream and produce parsing events. + """ + loader = Loader(stream) + try: + while loader.check_event(): + yield loader.get_event() + finally: + loader.dispose() + +def compose(stream, Loader=Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding representation tree. + """ + loader = Loader(stream) + try: + return loader.get_single_node() + finally: + loader.dispose() + +def compose_all(stream, Loader=Loader): + """ + Parse all YAML documents in a stream + and produce corresponding representation trees. + """ + loader = Loader(stream) + try: + while loader.check_node(): + yield loader.get_node() + finally: + loader.dispose() + +def load(stream, Loader=Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + """ + loader = Loader(stream) + try: + return loader.get_single_data() + finally: + loader.dispose() + +def load_all(stream, Loader=Loader): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + """ + loader = Loader(stream) + try: + while loader.check_data(): + yield loader.get_data() + finally: + loader.dispose() + +def safe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + Resolve only basic YAML tags. + """ + return load(stream, SafeLoader) + +def safe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + Resolve only basic YAML tags. + """ + return load_all(stream, SafeLoader) + +def emit(events, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + """ + Emit YAML parsing events into a stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + from StringIO import StringIO + stream = StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + try: + for event in events: + dumper.emit(event) + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize_all(nodes, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding='utf-8', explicit_start=None, explicit_end=None, + version=None, tags=None): + """ + Serialize a sequence of representation trees into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + from StringIO import StringIO + else: + from cStringIO import StringIO + stream = StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end) + try: + dumper.open() + for node in nodes: + dumper.serialize(node) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize(node, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a representation tree into a YAML stream. + If stream is None, return the produced string instead. + """ + return serialize_all([node], stream, Dumper=Dumper, **kwds) + +def dump_all(documents, stream=None, Dumper=Dumper, + default_style=None, default_flow_style=None, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding='utf-8', explicit_start=None, explicit_end=None, + version=None, tags=None): + """ + Serialize a sequence of Python objects into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + from StringIO import StringIO + else: + from cStringIO import StringIO + stream = StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, default_style=default_style, + default_flow_style=default_flow_style, + canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end) + try: + dumper.open() + for data in documents: + dumper.represent(data) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def dump(data, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a Python object into a YAML stream. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=Dumper, **kwds) + +def safe_dump_all(documents, stream=None, **kwds): + """ + Serialize a sequence of Python objects into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all(documents, stream, Dumper=SafeDumper, **kwds) + +def safe_dump(data, stream=None, **kwds): + """ + Serialize a Python object into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=SafeDumper, **kwds) + +def add_implicit_resolver(tag, regexp, first=None, + Loader=Loader, Dumper=Dumper): + """ + Add an implicit scalar detector. + If an implicit scalar value matches the given regexp, + the corresponding tag is assigned to the scalar. + first is a sequence of possible initial characters or None. + """ + Loader.add_implicit_resolver(tag, regexp, first) + Dumper.add_implicit_resolver(tag, regexp, first) + +def add_path_resolver(tag, path, kind=None, Loader=Loader, Dumper=Dumper): + """ + Add a path based resolver for the given tag. + A path is a list of keys that forms a path + to a node in the representation tree. + Keys can be string values, integers, or None. + """ + Loader.add_path_resolver(tag, path, kind) + Dumper.add_path_resolver(tag, path, kind) + +def add_constructor(tag, constructor, Loader=Loader): + """ + Add a constructor for the given tag. + Constructor is a function that accepts a Loader instance + and a node object and produces the corresponding Python object. + """ + Loader.add_constructor(tag, constructor) + +def add_multi_constructor(tag_prefix, multi_constructor, Loader=Loader): + """ + Add a multi-constructor for the given tag prefix. + Multi-constructor is called for a node if its tag starts with tag_prefix. + Multi-constructor accepts a Loader instance, a tag suffix, + and a node object and produces the corresponding Python object. + """ + Loader.add_multi_constructor(tag_prefix, multi_constructor) + +def add_representer(data_type, representer, Dumper=Dumper): + """ + Add a representer for the given type. + Representer is a function accepting a Dumper instance + and an instance of the given data type + and producing the corresponding representation node. + """ + Dumper.add_representer(data_type, representer) + +def add_multi_representer(data_type, multi_representer, Dumper=Dumper): + """ + Add a representer for the given type. + Multi-representer is a function accepting a Dumper instance + and an instance of the given data type or subtype + and producing the corresponding representation node. + """ + Dumper.add_multi_representer(data_type, multi_representer) + +class YAMLObjectMetaclass(type): + """ + The metaclass for YAMLObject. + """ + def __init__(cls, name, bases, kwds): + super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) + if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: + cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) + cls.yaml_dumper.add_representer(cls, cls.to_yaml) + +class YAMLObject(object): + """ + An object that can dump itself to a YAML stream + and load itself from a YAML stream. + """ + + __metaclass__ = YAMLObjectMetaclass + __slots__ = () # no direct instantiation, so allow immutable subclasses + + yaml_loader = Loader + yaml_dumper = Dumper + + yaml_tag = None + yaml_flow_style = None + + def from_yaml(cls, loader, node): + """ + Convert a representation node to a Python object. + """ + return loader.construct_yaml_object(node, cls) + from_yaml = classmethod(from_yaml) + + def to_yaml(cls, dumper, data): + """ + Convert a Python object to a representation node. + """ + return dumper.represent_yaml_object(cls.yaml_tag, data, cls, + flow_style=cls.yaml_flow_style) + to_yaml = classmethod(to_yaml) + diff --git a/dist/lib/yaml/composer.py b/dist/lib/yaml/composer.py new file mode 100644 index 0000000..06e5ac7 --- /dev/null +++ b/dist/lib/yaml/composer.py @@ -0,0 +1,139 @@ + +__all__ = ['Composer', 'ComposerError'] + +from error import MarkedYAMLError +from events import * +from nodes import * + +class ComposerError(MarkedYAMLError): + pass + +class Composer(object): + + def __init__(self): + self.anchors = {} + + def check_node(self): + # Drop the STREAM-START event. + if self.check_event(StreamStartEvent): + self.get_event() + + # If there are more documents available? + return not self.check_event(StreamEndEvent) + + def get_node(self): + # Get the root node of the next document. + if not self.check_event(StreamEndEvent): + return self.compose_document() + + def get_single_node(self): + # Drop the STREAM-START event. + self.get_event() + + # Compose a document if the stream is not empty. + document = None + if not self.check_event(StreamEndEvent): + document = self.compose_document() + + # Ensure that the stream contains no more documents. + if not self.check_event(StreamEndEvent): + event = self.get_event() + raise ComposerError("expected a single document in the stream", + document.start_mark, "but found another document", + event.start_mark) + + # Drop the STREAM-END event. + self.get_event() + + return document + + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + + # Compose the root node. + node = self.compose_node(None, None) + + # Drop the DOCUMENT-END event. + self.get_event() + + self.anchors = {} + return node + + def compose_node(self, parent, index): + if self.check_event(AliasEvent): + event = self.get_event() + anchor = event.anchor + if anchor not in self.anchors: + raise ComposerError(None, None, "found undefined alias %r" + % anchor.encode('utf-8'), event.start_mark) + return self.anchors[anchor] + event = self.peek_event() + anchor = event.anchor + if anchor is not None: + if anchor in self.anchors: + raise ComposerError("found duplicate anchor %r; first occurence" + % anchor.encode('utf-8'), self.anchors[anchor].start_mark, + "second occurence", event.start_mark) + self.descend_resolver(parent, index) + if self.check_event(ScalarEvent): + node = self.compose_scalar_node(anchor) + elif self.check_event(SequenceStartEvent): + node = self.compose_sequence_node(anchor) + elif self.check_event(MappingStartEvent): + node = self.compose_mapping_node(anchor) + self.ascend_resolver() + return node + + def compose_scalar_node(self, anchor): + event = self.get_event() + tag = event.tag + if tag is None or tag == u'!': + tag = self.resolve(ScalarNode, event.value, event.implicit) + node = ScalarNode(tag, event.value, + event.start_mark, event.end_mark, style=event.style) + if anchor is not None: + self.anchors[anchor] = node + return node + + def compose_sequence_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == u'!': + tag = self.resolve(SequenceNode, None, start_event.implicit) + node = SequenceNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + index = 0 + while not self.check_event(SequenceEndEvent): + node.value.append(self.compose_node(node, index)) + index += 1 + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + + def compose_mapping_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == u'!': + tag = self.resolve(MappingNode, None, start_event.implicit) + node = MappingNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + while not self.check_event(MappingEndEvent): + #key_event = self.peek_event() + item_key = self.compose_node(node, None) + #if item_key in node.value: + # raise ComposerError("while composing a mapping", start_event.start_mark, + # "found duplicate key", key_event.start_mark) + item_value = self.compose_node(node, item_key) + #node.value[item_key] = item_value + node.value.append((item_key, item_value)) + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + diff --git a/dist/lib/yaml/constructor.py b/dist/lib/yaml/constructor.py new file mode 100644 index 0000000..635faac --- /dev/null +++ b/dist/lib/yaml/constructor.py @@ -0,0 +1,675 @@ + +__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor', + 'ConstructorError'] + +from error import * +from nodes import * + +import datetime + +import binascii, re, sys, types + +class ConstructorError(MarkedYAMLError): + pass + +class BaseConstructor(object): + + yaml_constructors = {} + yaml_multi_constructors = {} + + def __init__(self): + self.constructed_objects = {} + self.recursive_objects = {} + self.state_generators = [] + self.deep_construct = False + + def check_data(self): + # If there are more documents available? + return self.check_node() + + def get_data(self): + # Construct and return the next document. + if self.check_node(): + return self.construct_document(self.get_node()) + + def get_single_data(self): + # Ensure that the stream contains a single document and construct it. + node = self.get_single_node() + if node is not None: + return self.construct_document(node) + return None + + def construct_document(self, node): + data = self.construct_object(node) + while self.state_generators: + state_generators = self.state_generators + self.state_generators = [] + for generator in state_generators: + for dummy in generator: + pass + self.constructed_objects = {} + self.recursive_objects = {} + self.deep_construct = False + return data + + def construct_object(self, node, deep=False): + if node in self.constructed_objects: + return self.constructed_objects[node] + if deep: + old_deep = self.deep_construct + self.deep_construct = True + if node in self.recursive_objects: + raise ConstructorError(None, None, + "found unconstructable recursive node", node.start_mark) + self.recursive_objects[node] = None + constructor = None + tag_suffix = None + if node.tag in self.yaml_constructors: + constructor = self.yaml_constructors[node.tag] + else: + for tag_prefix in self.yaml_multi_constructors: + if node.tag.startswith(tag_prefix): + tag_suffix = node.tag[len(tag_prefix):] + constructor = self.yaml_multi_constructors[tag_prefix] + break + else: + if None in self.yaml_multi_constructors: + tag_suffix = node.tag + constructor = self.yaml_multi_constructors[None] + elif None in self.yaml_constructors: + constructor = self.yaml_constructors[None] + elif isinstance(node, ScalarNode): + constructor = self.__class__.construct_scalar + elif isinstance(node, SequenceNode): + constructor = self.__class__.construct_sequence + elif isinstance(node, MappingNode): + constructor = self.__class__.construct_mapping + if tag_suffix is None: + data = constructor(self, node) + else: + data = constructor(self, tag_suffix, node) + if isinstance(data, types.GeneratorType): + generator = data + data = generator.next() + if self.deep_construct: + for dummy in generator: + pass + else: + self.state_generators.append(generator) + self.constructed_objects[node] = data + del self.recursive_objects[node] + if deep: + self.deep_construct = old_deep + return data + + def construct_scalar(self, node): + if not isinstance(node, ScalarNode): + raise ConstructorError(None, None, + "expected a scalar node, but found %s" % node.id, + node.start_mark) + return node.value + + def construct_sequence(self, node, deep=False): + if not isinstance(node, SequenceNode): + raise ConstructorError(None, None, + "expected a sequence node, but found %s" % node.id, + node.start_mark) + return [self.construct_object(child, deep=deep) + for child in node.value] + + def construct_mapping(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + mapping = {} + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + try: + hash(key) + except TypeError, exc: + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unacceptable key (%s)" % exc, key_node.start_mark) + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + + def construct_pairs(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + pairs = [] + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + value = self.construct_object(value_node, deep=deep) + pairs.append((key, value)) + return pairs + + def add_constructor(cls, tag, constructor): + if not 'yaml_constructors' in cls.__dict__: + cls.yaml_constructors = cls.yaml_constructors.copy() + cls.yaml_constructors[tag] = constructor + add_constructor = classmethod(add_constructor) + + def add_multi_constructor(cls, tag_prefix, multi_constructor): + if not 'yaml_multi_constructors' in cls.__dict__: + cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() + cls.yaml_multi_constructors[tag_prefix] = multi_constructor + add_multi_constructor = classmethod(add_multi_constructor) + +class SafeConstructor(BaseConstructor): + + def construct_scalar(self, node): + if isinstance(node, MappingNode): + for key_node, value_node in node.value: + if key_node.tag == u'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return BaseConstructor.construct_scalar(self, node) + + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == u'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError("while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark) + elif key_node.tag == u'tag:yaml.org,2002:value': + key_node.tag = u'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value + + def construct_mapping(self, node, deep=False): + if isinstance(node, MappingNode): + self.flatten_mapping(node) + return BaseConstructor.construct_mapping(self, node, deep=deep) + + def construct_yaml_null(self, node): + self.construct_scalar(node) + return None + + bool_values = { + u'yes': True, + u'no': False, + u'true': True, + u'false': False, + u'on': True, + u'off': False, + } + + def construct_yaml_bool(self, node): + value = self.construct_scalar(node) + return self.bool_values[value.lower()] + + def construct_yaml_int(self, node): + value = str(self.construct_scalar(node)) + value = value.replace('_', '') + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + elif value.startswith('0b'): + return sign*int(value[2:], 2) + elif value.startswith('0x'): + return sign*int(value[2:], 16) + elif value[0] == '0': + return sign*int(value, 8) + elif ':' in value: + digits = [int(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value) + + inf_value = 1e300 + while inf_value != inf_value*inf_value: + inf_value *= inf_value + nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). + + def construct_yaml_float(self, node): + value = str(self.construct_scalar(node)) + value = value.replace('_', '').lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '.inf': + return sign*self.inf_value + elif value == '.nan': + return self.nan_value + elif ':' in value: + digits = [float(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0.0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*float(value) + + def construct_yaml_binary(self, node): + value = self.construct_scalar(node) + try: + return str(value).decode('base64') + except (binascii.Error, UnicodeEncodeError), exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + timestamp_regexp = re.compile( + ur'''^(?P[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)?$''', re.X) + + def construct_yaml_timestamp(self, node): + value = self.construct_scalar(node) + match = self.timestamp_regexp.match(node.value) + values = match.groupdict() + year = int(values['year']) + month = int(values['month']) + day = int(values['day']) + if not values['hour']: + return datetime.date(year, month, day) + hour = int(values['hour']) + minute = int(values['minute']) + second = int(values['second']) + fraction = 0 + if values['fraction']: + fraction = values['fraction'][:6] + while len(fraction) < 6: + fraction += '0' + fraction = int(fraction) + delta = None + if values['tz_sign']: + tz_hour = int(values['tz_hour']) + tz_minute = int(values['tz_minute'] or 0) + delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) + if values['tz_sign'] == '-': + delta = -delta + data = datetime.datetime(year, month, day, hour, minute, second, fraction) + if delta: + data -= delta + return data + + def construct_yaml_omap(self, node): + # Note: we do not check for duplicate keys, because it's too + # CPU-expensive. + omap = [] + yield omap + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + omap.append((key, value)) + + def construct_yaml_pairs(self, node): + # Note: the same code as `construct_yaml_omap`. + pairs = [] + yield pairs + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + pairs.append((key, value)) + + def construct_yaml_set(self, node): + data = set() + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_str(self, node): + value = self.construct_scalar(node) + try: + return value.encode('ascii') + except UnicodeEncodeError: + return value + + def construct_yaml_seq(self, node): + data = [] + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = {} + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_object(self, node, cls): + data = cls.__new__(cls) + yield data + if hasattr(data, '__setstate__'): + state = self.construct_mapping(node, deep=True) + data.__setstate__(state) + else: + state = self.construct_mapping(node) + data.__dict__.update(state) + + def construct_undefined(self, node): + raise ConstructorError(None, None, + "could not determine a constructor for the tag %r" % node.tag.encode('utf-8'), + node.start_mark) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:null', + SafeConstructor.construct_yaml_null) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:bool', + SafeConstructor.construct_yaml_bool) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:int', + SafeConstructor.construct_yaml_int) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:float', + SafeConstructor.construct_yaml_float) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:binary', + SafeConstructor.construct_yaml_binary) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:timestamp', + SafeConstructor.construct_yaml_timestamp) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:omap', + SafeConstructor.construct_yaml_omap) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:pairs', + SafeConstructor.construct_yaml_pairs) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:set', + SafeConstructor.construct_yaml_set) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:str', + SafeConstructor.construct_yaml_str) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:seq', + SafeConstructor.construct_yaml_seq) + +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:map', + SafeConstructor.construct_yaml_map) + +SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + +class Constructor(SafeConstructor): + + def construct_python_str(self, node): + return self.construct_scalar(node).encode('utf-8') + + def construct_python_unicode(self, node): + return self.construct_scalar(node) + + def construct_python_long(self, node): + return long(self.construct_yaml_int(node)) + + def construct_python_complex(self, node): + return complex(self.construct_scalar(node)) + + def construct_python_tuple(self, node): + return tuple(self.construct_sequence(node)) + + def find_python_module(self, name, mark): + if not name: + raise ConstructorError("while constructing a Python module", mark, + "expected non-empty name appended to the tag", mark) + try: + __import__(name) + except ImportError, exc: + raise ConstructorError("while constructing a Python module", mark, + "cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark) + return sys.modules[name] + + def find_python_name(self, name, mark): + if not name: + raise ConstructorError("while constructing a Python object", mark, + "expected non-empty name appended to the tag", mark) + if u'.' in name: + module_name, object_name = name.rsplit('.', 1) + else: + module_name = '__builtin__' + object_name = name + try: + __import__(module_name) + except ImportError, exc: + raise ConstructorError("while constructing a Python object", mark, + "cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark) + module = sys.modules[module_name] + if not hasattr(module, object_name): + raise ConstructorError("while constructing a Python object", mark, + "cannot find %r in the module %r" % (object_name.encode('utf-8'), + module.__name__), mark) + return getattr(module, object_name) + + def construct_python_name(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python name", node.start_mark, + "expected the empty value, but found %r" % value.encode('utf-8'), + node.start_mark) + return self.find_python_name(suffix, node.start_mark) + + def construct_python_module(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python module", node.start_mark, + "expected the empty value, but found %r" % value.encode('utf-8'), + node.start_mark) + return self.find_python_module(suffix, node.start_mark) + + class classobj: pass + + def make_python_instance(self, suffix, node, + args=None, kwds=None, newobj=False): + if not args: + args = [] + if not kwds: + kwds = {} + cls = self.find_python_name(suffix, node.start_mark) + if newobj and isinstance(cls, type(self.classobj)) \ + and not args and not kwds: + instance = self.classobj() + instance.__class__ = cls + return instance + elif newobj and isinstance(cls, type): + return cls.__new__(cls, *args, **kwds) + else: + return cls(*args, **kwds) + + def set_python_instance_state(self, instance, state): + if hasattr(instance, '__setstate__'): + instance.__setstate__(state) + else: + slotstate = {} + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + if hasattr(instance, '__dict__'): + instance.__dict__.update(state) + elif state: + slotstate.update(state) + for key, value in slotstate.items(): + setattr(object, key, value) + + def construct_python_object(self, suffix, node): + # Format: + # !!python/object:module.name { ... state ... } + instance = self.make_python_instance(suffix, node, newobj=True) + yield instance + deep = hasattr(instance, '__setstate__') + state = self.construct_mapping(node, deep=deep) + self.set_python_instance_state(instance, state) + + def construct_python_object_apply(self, suffix, node, newobj=False): + # Format: + # !!python/object/apply # (or !!python/object/new) + # args: [ ... arguments ... ] + # kwds: { ... keywords ... } + # state: ... state ... + # listitems: [ ... listitems ... ] + # dictitems: { ... dictitems ... } + # or short format: + # !!python/object/apply [ ... arguments ... ] + # The difference between !!python/object/apply and !!python/object/new + # is how an object is created, check make_python_instance for details. + if isinstance(node, SequenceNode): + args = self.construct_sequence(node, deep=True) + kwds = {} + state = {} + listitems = [] + dictitems = {} + else: + value = self.construct_mapping(node, deep=True) + args = value.get('args', []) + kwds = value.get('kwds', {}) + state = value.get('state', {}) + listitems = value.get('listitems', []) + dictitems = value.get('dictitems', {}) + instance = self.make_python_instance(suffix, node, args, kwds, newobj) + if state: + self.set_python_instance_state(instance, state) + if listitems: + instance.extend(listitems) + if dictitems: + for key in dictitems: + instance[key] = dictitems[key] + return instance + + def construct_python_object_new(self, suffix, node): + return self.construct_python_object_apply(suffix, node, newobj=True) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/none', + Constructor.construct_yaml_null) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/bool', + Constructor.construct_yaml_bool) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/str', + Constructor.construct_python_str) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/unicode', + Constructor.construct_python_unicode) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/int', + Constructor.construct_yaml_int) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/long', + Constructor.construct_python_long) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/float', + Constructor.construct_yaml_float) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/complex', + Constructor.construct_python_complex) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/list', + Constructor.construct_yaml_seq) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/tuple', + Constructor.construct_python_tuple) + +Constructor.add_constructor( + u'tag:yaml.org,2002:python/dict', + Constructor.construct_yaml_map) + +Constructor.add_multi_constructor( + u'tag:yaml.org,2002:python/name:', + Constructor.construct_python_name) + +Constructor.add_multi_constructor( + u'tag:yaml.org,2002:python/module:', + Constructor.construct_python_module) + +Constructor.add_multi_constructor( + u'tag:yaml.org,2002:python/object:', + Constructor.construct_python_object) + +Constructor.add_multi_constructor( + u'tag:yaml.org,2002:python/object/apply:', + Constructor.construct_python_object_apply) + +Constructor.add_multi_constructor( + u'tag:yaml.org,2002:python/object/new:', + Constructor.construct_python_object_new) + diff --git a/dist/lib/yaml/cyaml.py b/dist/lib/yaml/cyaml.py new file mode 100644 index 0000000..68dcd75 --- /dev/null +++ b/dist/lib/yaml/cyaml.py @@ -0,0 +1,85 @@ + +__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader', + 'CBaseDumper', 'CSafeDumper', 'CDumper'] + +from _yaml import CParser, CEmitter + +from constructor import * + +from serializer import * +from representer import * + +from resolver import * + +class CBaseLoader(CParser, BaseConstructor, BaseResolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class CSafeLoader(CParser, SafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class CLoader(CParser, Constructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + Constructor.__init__(self) + Resolver.__init__(self) + +class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=None, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style) + Resolver.__init__(self) + +class CSafeDumper(CEmitter, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=None, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style) + Resolver.__init__(self) + +class CDumper(CEmitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=None, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style) + Resolver.__init__(self) + diff --git a/dist/lib/yaml/dumper.py b/dist/lib/yaml/dumper.py new file mode 100644 index 0000000..f811d2c --- /dev/null +++ b/dist/lib/yaml/dumper.py @@ -0,0 +1,62 @@ + +__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] + +from emitter import * +from serializer import * +from representer import * +from resolver import * + +class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=None, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style) + Resolver.__init__(self) + +class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=None, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style) + Resolver.__init__(self) + +class Dumper(Emitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=None, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style) + Resolver.__init__(self) + diff --git a/dist/lib/yaml/emitter.py b/dist/lib/yaml/emitter.py new file mode 100644 index 0000000..e5bcdcc --- /dev/null +++ b/dist/lib/yaml/emitter.py @@ -0,0 +1,1140 @@ + +# Emitter expects events obeying the following grammar: +# stream ::= STREAM-START document* STREAM-END +# document ::= DOCUMENT-START node DOCUMENT-END +# node ::= SCALAR | sequence | mapping +# sequence ::= SEQUENCE-START node* SEQUENCE-END +# mapping ::= MAPPING-START (node node)* MAPPING-END + +__all__ = ['Emitter', 'EmitterError'] + +from error import YAMLError +from events import * + +class EmitterError(YAMLError): + pass + +class ScalarAnalysis(object): + def __init__(self, scalar, empty, multiline, + allow_flow_plain, allow_block_plain, + allow_single_quoted, allow_double_quoted, + allow_block): + self.scalar = scalar + self.empty = empty + self.multiline = multiline + self.allow_flow_plain = allow_flow_plain + self.allow_block_plain = allow_block_plain + self.allow_single_quoted = allow_single_quoted + self.allow_double_quoted = allow_double_quoted + self.allow_block = allow_block + +class Emitter(object): + + DEFAULT_TAG_PREFIXES = { + u'!' : u'!', + u'tag:yaml.org,2002:' : u'!!', + } + + def __init__(self, stream, canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + + # The stream should have the methods `write` and possibly `flush`. + self.stream = stream + + # Encoding can be overriden by STREAM-START. + self.encoding = None + + # Emitter is a state machine with a stack of states to handle nested + # structures. + self.states = [] + self.state = self.expect_stream_start + + # Current event and the event queue. + self.events = [] + self.event = None + + # The current indentation level and the stack of previous indents. + self.indents = [] + self.indent = None + + # Flow level. + self.flow_level = 0 + + # Contexts. + self.root_context = False + self.sequence_context = False + self.mapping_context = False + self.simple_key_context = False + + # Characteristics of the last emitted character: + # - current position. + # - is it a whitespace? + # - is it an indention character + # (indentation space, '-', '?', or ':')? + self.line = 0 + self.column = 0 + self.whitespace = True + self.indention = True + + # Whether the document requires an explicit document indicator + self.open_ended = False + + # Formatting details. + self.canonical = canonical + self.allow_unicode = allow_unicode + self.best_indent = 2 + if indent and 1 < indent < 10: + self.best_indent = indent + self.best_width = 80 + if width and width > self.best_indent*2: + self.best_width = width + self.best_line_break = u'\n' + if line_break in [u'\r', u'\n', u'\r\n']: + self.best_line_break = line_break + + # Tag prefixes. + self.tag_prefixes = None + + # Prepared anchor and tag. + self.prepared_anchor = None + self.prepared_tag = None + + # Scalar analysis and style. + self.analysis = None + self.style = None + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def emit(self, event): + self.events.append(event) + while not self.need_more_events(): + self.event = self.events.pop(0) + self.state() + self.event = None + + # In some cases, we wait for a few next events before emitting. + + def need_more_events(self): + if not self.events: + return True + event = self.events[0] + if isinstance(event, DocumentStartEvent): + return self.need_events(1) + elif isinstance(event, SequenceStartEvent): + return self.need_events(2) + elif isinstance(event, MappingStartEvent): + return self.need_events(3) + else: + return False + + def need_events(self, count): + level = 0 + for event in self.events[1:]: + if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): + level += 1 + elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): + level -= 1 + elif isinstance(event, StreamEndEvent): + level = -1 + if level < 0: + return False + return (len(self.events) < count+1) + + def increase_indent(self, flow=False, indentless=False): + self.indents.append(self.indent) + if self.indent is None: + if flow: + self.indent = self.best_indent + else: + self.indent = 0 + elif not indentless: + self.indent += self.best_indent + + # States. + + # Stream handlers. + + def expect_stream_start(self): + if isinstance(self.event, StreamStartEvent): + if self.event.encoding and not getattr(self.stream, 'encoding', None): + self.encoding = self.event.encoding + self.write_stream_start() + self.state = self.expect_first_document_start + else: + raise EmitterError("expected StreamStartEvent, but got %s" + % self.event) + + def expect_nothing(self): + raise EmitterError("expected nothing, but got %s" % self.event) + + # Document handlers. + + def expect_first_document_start(self): + return self.expect_document_start(first=True) + + def expect_document_start(self, first=False): + if isinstance(self.event, DocumentStartEvent): + if (self.event.version or self.event.tags) and self.open_ended: + self.write_indicator(u'...', True) + self.write_indent() + if self.event.version: + version_text = self.prepare_version(self.event.version) + self.write_version_directive(version_text) + self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() + if self.event.tags: + handles = self.event.tags.keys() + handles.sort() + for handle in handles: + prefix = self.event.tags[handle] + self.tag_prefixes[prefix] = handle + handle_text = self.prepare_tag_handle(handle) + prefix_text = self.prepare_tag_prefix(prefix) + self.write_tag_directive(handle_text, prefix_text) + implicit = (first and not self.event.explicit and not self.canonical + and not self.event.version and not self.event.tags + and not self.check_empty_document()) + if not implicit: + self.write_indent() + self.write_indicator(u'---', True) + if self.canonical: + self.write_indent() + self.state = self.expect_document_root + elif isinstance(self.event, StreamEndEvent): + if self.open_ended: + self.write_indicator(u'...', True) + self.write_indent() + self.write_stream_end() + self.state = self.expect_nothing + else: + raise EmitterError("expected DocumentStartEvent, but got %s" + % self.event) + + def expect_document_end(self): + if isinstance(self.event, DocumentEndEvent): + self.write_indent() + if self.event.explicit: + self.write_indicator(u'...', True) + self.write_indent() + self.flush_stream() + self.state = self.expect_document_start + else: + raise EmitterError("expected DocumentEndEvent, but got %s" + % self.event) + + def expect_document_root(self): + self.states.append(self.expect_document_end) + self.expect_node(root=True) + + # Node handlers. + + def expect_node(self, root=False, sequence=False, mapping=False, + simple_key=False): + self.root_context = root + self.sequence_context = sequence + self.mapping_context = mapping + self.simple_key_context = simple_key + if isinstance(self.event, AliasEvent): + self.expect_alias() + elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): + self.process_anchor(u'&') + self.process_tag() + if isinstance(self.event, ScalarEvent): + self.expect_scalar() + elif isinstance(self.event, SequenceStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_sequence(): + self.expect_flow_sequence() + else: + self.expect_block_sequence() + elif isinstance(self.event, MappingStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_mapping(): + self.expect_flow_mapping() + else: + self.expect_block_mapping() + else: + raise EmitterError("expected NodeEvent, but got %s" % self.event) + + def expect_alias(self): + if self.event.anchor is None: + raise EmitterError("anchor is not specified for alias") + self.process_anchor(u'*') + self.state = self.states.pop() + + def expect_scalar(self): + self.increase_indent(flow=True) + self.process_scalar() + self.indent = self.indents.pop() + self.state = self.states.pop() + + # Flow sequence handlers. + + def expect_flow_sequence(self): + self.write_indicator(u'[', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_sequence_item + + def expect_first_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator(u']', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + def expect_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(u',', False) + self.write_indent() + self.write_indicator(u']', False) + self.state = self.states.pop() + else: + self.write_indicator(u',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + # Flow mapping handlers. + + def expect_flow_mapping(self): + self.write_indicator(u'{', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_mapping_key + + def expect_first_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator(u'}', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator(u'?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(u',', False) + self.write_indent() + self.write_indicator(u'}', False) + self.state = self.states.pop() + else: + self.write_indicator(u',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator(u'?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_simple_value(self): + self.write_indicator(u':', False) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + def expect_flow_mapping_value(self): + if self.canonical or self.column > self.best_width: + self.write_indent() + self.write_indicator(u':', True) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + # Block sequence handlers. + + def expect_block_sequence(self): + indentless = (self.mapping_context and not self.indention) + self.increase_indent(flow=False, indentless=indentless) + self.state = self.expect_first_block_sequence_item + + def expect_first_block_sequence_item(self): + return self.expect_block_sequence_item(first=True) + + def expect_block_sequence_item(self, first=False): + if not first and isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + self.write_indicator(u'-', True, indention=True) + self.states.append(self.expect_block_sequence_item) + self.expect_node(sequence=True) + + # Block mapping handlers. + + def expect_block_mapping(self): + self.increase_indent(flow=False) + self.state = self.expect_first_block_mapping_key + + def expect_first_block_mapping_key(self): + return self.expect_block_mapping_key(first=True) + + def expect_block_mapping_key(self, first=False): + if not first and isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + if self.check_simple_key(): + self.states.append(self.expect_block_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator(u'?', True, indention=True) + self.states.append(self.expect_block_mapping_value) + self.expect_node(mapping=True) + + def expect_block_mapping_simple_value(self): + self.write_indicator(u':', False) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + def expect_block_mapping_value(self): + self.write_indent() + self.write_indicator(u':', True, indention=True) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + # Checkers. + + def check_empty_sequence(self): + return (isinstance(self.event, SequenceStartEvent) and self.events + and isinstance(self.events[0], SequenceEndEvent)) + + def check_empty_mapping(self): + return (isinstance(self.event, MappingStartEvent) and self.events + and isinstance(self.events[0], MappingEndEvent)) + + def check_empty_document(self): + if not isinstance(self.event, DocumentStartEvent) or not self.events: + return False + event = self.events[0] + return (isinstance(event, ScalarEvent) and event.anchor is None + and event.tag is None and event.implicit and event.value == u'') + + def check_simple_key(self): + length = 0 + if isinstance(self.event, NodeEvent) and self.event.anchor is not None: + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + length += len(self.prepared_anchor) + if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ + and self.event.tag is not None: + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(self.event.tag) + length += len(self.prepared_tag) + if isinstance(self.event, ScalarEvent): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + length += len(self.analysis.scalar) + return (length < 128 and (isinstance(self.event, AliasEvent) + or (isinstance(self.event, ScalarEvent) + and not self.analysis.empty and not self.analysis.multiline) + or self.check_empty_sequence() or self.check_empty_mapping())) + + # Anchor, Tag, and Scalar processors. + + def process_anchor(self, indicator): + if self.event.anchor is None: + self.prepared_anchor = None + return + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + if self.prepared_anchor: + self.write_indicator(indicator+self.prepared_anchor, True) + self.prepared_anchor = None + + def process_tag(self): + tag = self.event.tag + if isinstance(self.event, ScalarEvent): + if self.style is None: + self.style = self.choose_scalar_style() + if ((not self.canonical or tag is None) and + ((self.style == '' and self.event.implicit[0]) + or (self.style != '' and self.event.implicit[1]))): + self.prepared_tag = None + return + if self.event.implicit[0] and tag is None: + tag = u'!' + self.prepared_tag = None + else: + if (not self.canonical or tag is None) and self.event.implicit: + self.prepared_tag = None + return + if tag is None: + raise EmitterError("tag is not specified") + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(tag) + if self.prepared_tag: + self.write_indicator(self.prepared_tag, True) + self.prepared_tag = None + + def choose_scalar_style(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.event.style == '"' or self.canonical: + return '"' + if not self.event.style and self.event.implicit[0]: + if (not (self.simple_key_context and + (self.analysis.empty or self.analysis.multiline)) + and (self.flow_level and self.analysis.allow_flow_plain + or (not self.flow_level and self.analysis.allow_block_plain))): + return '' + if self.event.style and self.event.style in '|>': + if (not self.flow_level and not self.simple_key_context + and self.analysis.allow_block): + return self.event.style + if not self.event.style or self.event.style == '\'': + if (self.analysis.allow_single_quoted and + not (self.simple_key_context and self.analysis.multiline)): + return '\'' + return '"' + + def process_scalar(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.style is None: + self.style = self.choose_scalar_style() + split = (not self.simple_key_context) + #if self.analysis.multiline and split \ + # and (not self.style or self.style in '\'\"'): + # self.write_indent() + if self.style == '"': + self.write_double_quoted(self.analysis.scalar, split) + elif self.style == '\'': + self.write_single_quoted(self.analysis.scalar, split) + elif self.style == '>': + self.write_folded(self.analysis.scalar) + elif self.style == '|': + self.write_literal(self.analysis.scalar) + else: + self.write_plain(self.analysis.scalar, split) + self.analysis = None + self.style = None + + # Analyzers. + + def prepare_version(self, version): + major, minor = version + if major != 1: + raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) + return u'%d.%d' % (major, minor) + + def prepare_tag_handle(self, handle): + if not handle: + raise EmitterError("tag handle must not be empty") + if handle[0] != u'!' or handle[-1] != u'!': + raise EmitterError("tag handle must start and end with '!': %r" + % (handle.encode('utf-8'))) + for ch in handle[1:-1]: + if not (u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-_'): + raise EmitterError("invalid character %r in the tag handle: %r" + % (ch.encode('utf-8'), handle.encode('utf-8'))) + return handle + + def prepare_tag_prefix(self, prefix): + if not prefix: + raise EmitterError("tag prefix must not be empty") + chunks = [] + start = end = 0 + if prefix[0] == u'!': + end = 1 + while end < len(prefix): + ch = prefix[end] + if u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-;/?!:@&=+$,_.~*\'()[]': + end += 1 + else: + if start < end: + chunks.append(prefix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append(u'%%%02X' % ord(ch)) + if start < end: + chunks.append(prefix[start:end]) + return u''.join(chunks) + + def prepare_tag(self, tag): + if not tag: + raise EmitterError("tag must not be empty") + if tag == u'!': + return tag + handle = None + suffix = tag + prefixes = self.tag_prefixes.keys() + prefixes.sort() + for prefix in prefixes: + if tag.startswith(prefix) \ + and (prefix == u'!' or len(prefix) < len(tag)): + handle = self.tag_prefixes[prefix] + suffix = tag[len(prefix):] + chunks = [] + start = end = 0 + while end < len(suffix): + ch = suffix[end] + if u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-;/?:@&=+$,_.~*\'()[]' \ + or (ch == u'!' and handle != u'!'): + end += 1 + else: + if start < end: + chunks.append(suffix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append(u'%%%02X' % ord(ch)) + if start < end: + chunks.append(suffix[start:end]) + suffix_text = u''.join(chunks) + if handle: + return u'%s%s' % (handle, suffix_text) + else: + return u'!<%s>' % suffix_text + + def prepare_anchor(self, anchor): + if not anchor: + raise EmitterError("anchor must not be empty") + for ch in anchor: + if not (u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-_'): + raise EmitterError("invalid character %r in the anchor: %r" + % (ch.encode('utf-8'), anchor.encode('utf-8'))) + return anchor + + def analyze_scalar(self, scalar): + + # Empty scalar is a special case. + if not scalar: + return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, + allow_flow_plain=False, allow_block_plain=True, + allow_single_quoted=True, allow_double_quoted=True, + allow_block=False) + + # Indicators and special characters. + block_indicators = False + flow_indicators = False + line_breaks = False + special_characters = False + + # Important whitespace combinations. + leading_space = False + leading_break = False + trailing_space = False + trailing_break = False + break_space = False + space_break = False + + # Check document indicators. + if scalar.startswith(u'---') or scalar.startswith(u'...'): + block_indicators = True + flow_indicators = True + + # First character or preceded by a whitespace. + preceeded_by_whitespace = True + + # Last character or followed by a whitespace. + followed_by_whitespace = (len(scalar) == 1 or + scalar[1] in u'\0 \t\r\n\x85\u2028\u2029') + + # The previous character is a space. + previous_space = False + + # The previous character is a break. + previous_break = False + + index = 0 + while index < len(scalar): + ch = scalar[index] + + # Check for indicators. + if index == 0: + # Leading indicators are special characters. + if ch in u'#,[]{}&*!|>\'\"%@`': + flow_indicators = True + block_indicators = True + if ch in u'?:': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == u'-' and followed_by_whitespace: + flow_indicators = True + block_indicators = True + else: + # Some indicators cannot appear within a scalar as well. + if ch in u',?[]{}': + flow_indicators = True + if ch == u':': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == u'#' and preceeded_by_whitespace: + flow_indicators = True + block_indicators = True + + # Check for line breaks, special, and unicode characters. + if ch in u'\n\x85\u2028\u2029': + line_breaks = True + if not (ch == u'\n' or u'\x20' <= ch <= u'\x7E'): + if (ch == u'\x85' or u'\xA0' <= ch <= u'\uD7FF' + or u'\uE000' <= ch <= u'\uFFFD') and ch != u'\uFEFF': + unicode_characters = True + if not self.allow_unicode: + special_characters = True + else: + special_characters = True + + # Detect important whitespace combinations. + if ch == u' ': + if index == 0: + leading_space = True + if index == len(scalar)-1: + trailing_space = True + if previous_break: + break_space = True + previous_space = True + previous_break = False + elif ch in u'\n\x85\u2028\u2029': + if index == 0: + leading_break = True + if index == len(scalar)-1: + trailing_break = True + if previous_space: + space_break = True + previous_space = False + previous_break = True + else: + previous_space = False + previous_break = False + + # Prepare for the next character. + index += 1 + preceeded_by_whitespace = (ch in u'\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = (index+1 >= len(scalar) or + scalar[index+1] in u'\0 \t\r\n\x85\u2028\u2029') + + # Let's decide what styles are allowed. + allow_flow_plain = True + allow_block_plain = True + allow_single_quoted = True + allow_double_quoted = True + allow_block = True + + # Leading and trailing whitespaces are bad for plain scalars. + if (leading_space or leading_break + or trailing_space or trailing_break): + allow_flow_plain = allow_block_plain = False + + # We do not permit trailing spaces for block scalars. + if trailing_space: + allow_block = False + + # Spaces at the beginning of a new line are only acceptable for block + # scalars. + if break_space: + allow_flow_plain = allow_block_plain = allow_single_quoted = False + + # Spaces followed by breaks, as well as special character are only + # allowed for double quoted scalars. + if space_break or special_characters: + allow_flow_plain = allow_block_plain = \ + allow_single_quoted = allow_block = False + + # Although the plain scalar writer supports breaks, we never emit + # multiline plain scalars. + if line_breaks: + allow_flow_plain = allow_block_plain = False + + # Flow indicators are forbidden for flow plain scalars. + if flow_indicators: + allow_flow_plain = False + + # Block indicators are forbidden for block plain scalars. + if block_indicators: + allow_block_plain = False + + return ScalarAnalysis(scalar=scalar, + empty=False, multiline=line_breaks, + allow_flow_plain=allow_flow_plain, + allow_block_plain=allow_block_plain, + allow_single_quoted=allow_single_quoted, + allow_double_quoted=allow_double_quoted, + allow_block=allow_block) + + # Writers. + + def flush_stream(self): + if hasattr(self.stream, 'flush'): + self.stream.flush() + + def write_stream_start(self): + # Write BOM if needed. + if self.encoding and self.encoding.startswith('utf-16'): + self.stream.write(u'\uFEFF'.encode(self.encoding)) + + def write_stream_end(self): + self.flush_stream() + + def write_indicator(self, indicator, need_whitespace, + whitespace=False, indention=False): + if self.whitespace or not need_whitespace: + data = indicator + else: + data = u' '+indicator + self.whitespace = whitespace + self.indention = self.indention and indention + self.column += len(data) + self.open_ended = False + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_indent(self): + indent = self.indent or 0 + if not self.indention or self.column > indent \ + or (self.column == indent and not self.whitespace): + self.write_line_break() + if self.column < indent: + self.whitespace = True + data = u' '*(indent-self.column) + self.column = indent + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_line_break(self, data=None): + if data is None: + data = self.best_line_break + self.whitespace = True + self.indention = True + self.line += 1 + self.column = 0 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_version_directive(self, version_text): + data = u'%%YAML %s' % version_text + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + def write_tag_directive(self, handle_text, prefix_text): + data = u'%%TAG %s %s' % (handle_text, prefix_text) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + # Scalar streams. + + def write_single_quoted(self, text, split=True): + self.write_indicator(u'\'', True) + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch is None or ch != u' ': + if start+1 == end and self.column > self.best_width and split \ + and start != 0 and end != len(text): + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch is None or ch not in u'\n\x85\u2028\u2029': + if text[start] == u'\n': + self.write_line_break() + for br in text[start:end]: + if br == u'\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + start = end + else: + if ch is None or ch in u' \n\x85\u2028\u2029' or ch == u'\'': + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch == u'\'': + data = u'\'\'' + self.column += 2 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + 1 + if ch is not None: + spaces = (ch == u' ') + breaks = (ch in u'\n\x85\u2028\u2029') + end += 1 + self.write_indicator(u'\'', False) + + ESCAPE_REPLACEMENTS = { + u'\0': u'0', + u'\x07': u'a', + u'\x08': u'b', + u'\x09': u't', + u'\x0A': u'n', + u'\x0B': u'v', + u'\x0C': u'f', + u'\x0D': u'r', + u'\x1B': u'e', + u'\"': u'\"', + u'\\': u'\\', + u'\x85': u'N', + u'\xA0': u'_', + u'\u2028': u'L', + u'\u2029': u'P', + } + + def write_double_quoted(self, text, split=True): + self.write_indicator(u'"', True) + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if ch is None or ch in u'"\\\x85\u2028\u2029\uFEFF' \ + or not (u'\x20' <= ch <= u'\x7E' + or (self.allow_unicode + and (u'\xA0' <= ch <= u'\uD7FF' + or u'\uE000' <= ch <= u'\uFFFD'))): + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + if ch in self.ESCAPE_REPLACEMENTS: + data = u'\\'+self.ESCAPE_REPLACEMENTS[ch] + elif ch <= u'\xFF': + data = u'\\x%02X' % ord(ch) + elif ch <= u'\uFFFF': + data = u'\\u%04X' % ord(ch) + else: + data = u'\\U%08X' % ord(ch) + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end+1 + if 0 < end < len(text)-1 and (ch == u' ' or start >= end) \ + and self.column+(end-start) > self.best_width and split: + data = text[start:end]+u'\\' + if start < end: + start = end + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_indent() + self.whitespace = False + self.indention = False + if text[start] == u' ': + data = u'\\' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + end += 1 + self.write_indicator(u'"', False) + + def determine_block_hints(self, text): + hints = u'' + if text: + if text[0] in u' \n\x85\u2028\u2029': + hints += unicode(self.best_indent) + if text[-1] not in u'\n\x85\u2028\u2029': + hints += u'-' + elif len(text) == 1 or text[-2] in u'\n\x85\u2028\u2029': + hints += u'+' + return hints + + def write_folded(self, text): + hints = self.determine_block_hints(text) + self.write_indicator(u'>'+hints, True) + if hints[-1:] == u'+': + self.open_ended = True + self.write_line_break() + leading_space = True + spaces = False + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in u'\n\x85\u2028\u2029': + if not leading_space and ch is not None and ch != u' ' \ + and text[start] == u'\n': + self.write_line_break() + leading_space = (ch == u' ') + for br in text[start:end]: + if br == u'\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + elif spaces: + if ch != u' ': + if start+1 == end and self.column > self.best_width: + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + else: + if ch is None or ch in u' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in u'\n\x85\u2028\u2029') + spaces = (ch == u' ') + end += 1 + + def write_literal(self, text): + hints = self.determine_block_hints(text) + self.write_indicator(u'|'+hints, True) + if hints[-1:] == u'+': + self.open_ended = True + self.write_line_break() + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in u'\n\x85\u2028\u2029': + for br in text[start:end]: + if br == u'\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + else: + if ch is None or ch in u'\n\x85\u2028\u2029': + data = text[start:end] + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in u'\n\x85\u2028\u2029') + end += 1 + + def write_plain(self, text, split=True): + if self.root_context: + self.open_ended = True + if not text: + return + if not self.whitespace: + data = u' ' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.whitespace = False + self.indention = False + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch != u' ': + if start+1 == end and self.column > self.best_width and split: + self.write_indent() + self.whitespace = False + self.indention = False + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch not in u'\n\x85\u2028\u2029': + if text[start] == u'\n': + self.write_line_break() + for br in text[start:end]: + if br == u'\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + self.whitespace = False + self.indention = False + start = end + else: + if ch is None or ch in u' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + spaces = (ch == u' ') + breaks = (ch in u'\n\x85\u2028\u2029') + end += 1 + diff --git a/dist/lib/yaml/error.py b/dist/lib/yaml/error.py new file mode 100644 index 0000000..577686d --- /dev/null +++ b/dist/lib/yaml/error.py @@ -0,0 +1,75 @@ + +__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError'] + +class Mark(object): + + def __init__(self, name, index, line, column, buffer, pointer): + self.name = name + self.index = index + self.line = line + self.column = column + self.buffer = buffer + self.pointer = pointer + + def get_snippet(self, indent=4, max_length=75): + if self.buffer is None: + return None + head = '' + start = self.pointer + while start > 0 and self.buffer[start-1] not in u'\0\r\n\x85\u2028\u2029': + start -= 1 + if self.pointer-start > max_length/2-1: + head = ' ... ' + start += 5 + break + tail = '' + end = self.pointer + while end < len(self.buffer) and self.buffer[end] not in u'\0\r\n\x85\u2028\u2029': + end += 1 + if end-self.pointer > max_length/2-1: + tail = ' ... ' + end -= 5 + break + snippet = self.buffer[start:end].encode('utf-8') + return ' '*indent + head + snippet + tail + '\n' \ + + ' '*(indent+self.pointer-start+len(head)) + '^' + + def __str__(self): + snippet = self.get_snippet() + where = " in \"%s\", line %d, column %d" \ + % (self.name, self.line+1, self.column+1) + if snippet is not None: + where += ":\n"+snippet + return where + +class YAMLError(Exception): + pass + +class MarkedYAMLError(YAMLError): + + def __init__(self, context=None, context_mark=None, + problem=None, problem_mark=None, note=None): + self.context = context + self.context_mark = context_mark + self.problem = problem + self.problem_mark = problem_mark + self.note = note + + def __str__(self): + lines = [] + if self.context is not None: + lines.append(self.context) + if self.context_mark is not None \ + and (self.problem is None or self.problem_mark is None + or self.context_mark.name != self.problem_mark.name + or self.context_mark.line != self.problem_mark.line + or self.context_mark.column != self.problem_mark.column): + lines.append(str(self.context_mark)) + if self.problem is not None: + lines.append(self.problem) + if self.problem_mark is not None: + lines.append(str(self.problem_mark)) + if self.note is not None: + lines.append(self.note) + return '\n'.join(lines) + diff --git a/dist/lib/yaml/events.py b/dist/lib/yaml/events.py new file mode 100644 index 0000000..f79ad38 --- /dev/null +++ b/dist/lib/yaml/events.py @@ -0,0 +1,86 @@ + +# Abstract classes. + +class Event(object): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] + if hasattr(self, key)] + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +class NodeEvent(Event): + def __init__(self, anchor, start_mark=None, end_mark=None): + self.anchor = anchor + self.start_mark = start_mark + self.end_mark = end_mark + +class CollectionStartEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, + flow_style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class CollectionEndEvent(Event): + pass + +# Implementations. + +class StreamStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndEvent(Event): + pass + +class DocumentStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None, version=None, tags=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + self.version = version + self.tags = tags + +class DocumentEndEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + +class AliasEvent(NodeEvent): + pass + +class ScalarEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, value, + start_mark=None, end_mark=None, style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class SequenceStartEvent(CollectionStartEvent): + pass + +class SequenceEndEvent(CollectionEndEvent): + pass + +class MappingStartEvent(CollectionStartEvent): + pass + +class MappingEndEvent(CollectionEndEvent): + pass + diff --git a/dist/lib/yaml/loader.py b/dist/lib/yaml/loader.py new file mode 100644 index 0000000..293ff46 --- /dev/null +++ b/dist/lib/yaml/loader.py @@ -0,0 +1,40 @@ + +__all__ = ['BaseLoader', 'SafeLoader', 'Loader'] + +from reader import * +from scanner import * +from parser import * +from composer import * +from constructor import * +from resolver import * + +class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) + diff --git a/dist/lib/yaml/nodes.py b/dist/lib/yaml/nodes.py new file mode 100644 index 0000000..c4f070c --- /dev/null +++ b/dist/lib/yaml/nodes.py @@ -0,0 +1,49 @@ + +class Node(object): + def __init__(self, tag, value, start_mark, end_mark): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + value = self.value + #if isinstance(value, list): + # if len(value) == 0: + # value = '' + # elif len(value) == 1: + # value = '<1 item>' + # else: + # value = '<%d items>' % len(value) + #else: + # if len(value) > 75: + # value = repr(value[:70]+u' ... ') + # else: + # value = repr(value) + value = repr(value) + return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) + +class ScalarNode(Node): + id = 'scalar' + def __init__(self, tag, value, + start_mark=None, end_mark=None, style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class CollectionNode(Node): + def __init__(self, tag, value, + start_mark=None, end_mark=None, flow_style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class SequenceNode(CollectionNode): + id = 'sequence' + +class MappingNode(CollectionNode): + id = 'mapping' + diff --git a/dist/lib/yaml/parser.py b/dist/lib/yaml/parser.py new file mode 100644 index 0000000..f9e3057 --- /dev/null +++ b/dist/lib/yaml/parser.py @@ -0,0 +1,589 @@ + +# The following YAML grammar is LL(1) and is parsed by a recursive descent +# parser. +# +# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +# implicit_document ::= block_node DOCUMENT-END* +# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +# block_node_or_indentless_sequence ::= +# ALIAS +# | properties (block_content | indentless_block_sequence)? +# | block_content +# | indentless_block_sequence +# block_node ::= ALIAS +# | properties block_content? +# | block_content +# flow_node ::= ALIAS +# | properties flow_content? +# | flow_content +# properties ::= TAG ANCHOR? | ANCHOR TAG? +# block_content ::= block_collection | flow_collection | SCALAR +# flow_content ::= flow_collection | SCALAR +# block_collection ::= block_sequence | block_mapping +# flow_collection ::= flow_sequence | flow_mapping +# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +# indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +# block_mapping ::= BLOCK-MAPPING_START +# ((KEY block_node_or_indentless_sequence?)? +# (VALUE block_node_or_indentless_sequence?)?)* +# BLOCK-END +# flow_sequence ::= FLOW-SEQUENCE-START +# (flow_sequence_entry FLOW-ENTRY)* +# flow_sequence_entry? +# FLOW-SEQUENCE-END +# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# flow_mapping ::= FLOW-MAPPING-START +# (flow_mapping_entry FLOW-ENTRY)* +# flow_mapping_entry? +# FLOW-MAPPING-END +# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# +# FIRST sets: +# +# stream: { STREAM-START } +# explicit_document: { DIRECTIVE DOCUMENT-START } +# implicit_document: FIRST(block_node) +# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_sequence: { BLOCK-SEQUENCE-START } +# block_mapping: { BLOCK-MAPPING-START } +# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY } +# indentless_sequence: { ENTRY } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_sequence: { FLOW-SEQUENCE-START } +# flow_mapping: { FLOW-MAPPING-START } +# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } +# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } + +__all__ = ['Parser', 'ParserError'] + +from error import MarkedYAMLError +from tokens import * +from events import * +from scanner import * + +class ParserError(MarkedYAMLError): + pass + +class Parser(object): + # Since writing a recursive-descendant parser is a straightforward task, we + # do not give many comments here. + + DEFAULT_TAGS = { + u'!': u'!', + u'!!': u'tag:yaml.org,2002:', + } + + def __init__(self): + self.current_event = None + self.yaml_version = None + self.tag_handles = {} + self.states = [] + self.marks = [] + self.state = self.parse_stream_start + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def check_event(self, *choices): + # Check the type of the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + if self.current_event is not None: + if not choices: + return True + for choice in choices: + if isinstance(self.current_event, choice): + return True + return False + + def peek_event(self): + # Get the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + return self.current_event + + def get_event(self): + # Get the next event and proceed further. + if self.current_event is None: + if self.state: + self.current_event = self.state() + value = self.current_event + self.current_event = None + return value + + # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + # implicit_document ::= block_node DOCUMENT-END* + # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + + def parse_stream_start(self): + + # Parse the stream start. + token = self.get_token() + event = StreamStartEvent(token.start_mark, token.end_mark, + encoding=token.encoding) + + # Prepare the next state. + self.state = self.parse_implicit_document_start + + return event + + def parse_implicit_document_start(self): + + # Parse an implicit document. + if not self.check_token(DirectiveToken, DocumentStartToken, + StreamEndToken): + self.tag_handles = self.DEFAULT_TAGS + token = self.peek_token() + start_mark = end_mark = token.start_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=False) + + # Prepare the next state. + self.states.append(self.parse_document_end) + self.state = self.parse_block_node + + return event + + else: + return self.parse_document_start() + + def parse_document_start(self): + + # Parse any extra document end indicators. + while self.check_token(DocumentEndToken): + self.get_token() + + # Parse an explicit document. + if not self.check_token(StreamEndToken): + token = self.peek_token() + start_mark = token.start_mark + version, tags = self.process_directives() + if not self.check_token(DocumentStartToken): + raise ParserError(None, None, + "expected '', but found %r" + % self.peek_token().id, + self.peek_token().start_mark) + token = self.get_token() + end_mark = token.end_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=True, version=version, tags=tags) + self.states.append(self.parse_document_end) + self.state = self.parse_document_content + else: + # Parse the end of the stream. + token = self.get_token() + event = StreamEndEvent(token.start_mark, token.end_mark) + assert not self.states + assert not self.marks + self.state = None + return event + + def parse_document_end(self): + + # Parse the document end. + token = self.peek_token() + start_mark = end_mark = token.start_mark + explicit = False + if self.check_token(DocumentEndToken): + token = self.get_token() + end_mark = token.end_mark + explicit = True + event = DocumentEndEvent(start_mark, end_mark, + explicit=explicit) + + # Prepare the next state. + self.state = self.parse_document_start + + return event + + def parse_document_content(self): + if self.check_token(DirectiveToken, + DocumentStartToken, DocumentEndToken, StreamEndToken): + event = self.process_empty_scalar(self.peek_token().start_mark) + self.state = self.states.pop() + return event + else: + return self.parse_block_node() + + def process_directives(self): + self.yaml_version = None + self.tag_handles = {} + while self.check_token(DirectiveToken): + token = self.get_token() + if token.name == u'YAML': + if self.yaml_version is not None: + raise ParserError(None, None, + "found duplicate YAML directive", token.start_mark) + major, minor = token.value + if major != 1: + raise ParserError(None, None, + "found incompatible YAML document (version 1.* is required)", + token.start_mark) + self.yaml_version = token.value + elif token.name == u'TAG': + handle, prefix = token.value + if handle in self.tag_handles: + raise ParserError(None, None, + "duplicate tag handle %r" % handle.encode('utf-8'), + token.start_mark) + self.tag_handles[handle] = prefix + if self.tag_handles: + value = self.yaml_version, self.tag_handles.copy() + else: + value = self.yaml_version, None + for key in self.DEFAULT_TAGS: + if key not in self.tag_handles: + self.tag_handles[key] = self.DEFAULT_TAGS[key] + return value + + # block_node_or_indentless_sequence ::= ALIAS + # | properties (block_content | indentless_block_sequence)? + # | block_content + # | indentless_block_sequence + # block_node ::= ALIAS + # | properties block_content? + # | block_content + # flow_node ::= ALIAS + # | properties flow_content? + # | flow_content + # properties ::= TAG ANCHOR? | ANCHOR TAG? + # block_content ::= block_collection | flow_collection | SCALAR + # flow_content ::= flow_collection | SCALAR + # block_collection ::= block_sequence | block_mapping + # flow_collection ::= flow_sequence | flow_mapping + + def parse_block_node(self): + return self.parse_node(block=True) + + def parse_flow_node(self): + return self.parse_node() + + def parse_block_node_or_indentless_sequence(self): + return self.parse_node(block=True, indentless_sequence=True) + + def parse_node(self, block=False, indentless_sequence=False): + if self.check_token(AliasToken): + token = self.get_token() + event = AliasEvent(token.value, token.start_mark, token.end_mark) + self.state = self.states.pop() + else: + anchor = None + tag = None + start_mark = end_mark = tag_mark = None + if self.check_token(AnchorToken): + token = self.get_token() + start_mark = token.start_mark + end_mark = token.end_mark + anchor = token.value + if self.check_token(TagToken): + token = self.get_token() + tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + elif self.check_token(TagToken): + token = self.get_token() + start_mark = tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + if self.check_token(AnchorToken): + token = self.get_token() + end_mark = token.end_mark + anchor = token.value + if tag is not None: + handle, suffix = tag + if handle is not None: + if handle not in self.tag_handles: + raise ParserError("while parsing a node", start_mark, + "found undefined tag handle %r" % handle.encode('utf-8'), + tag_mark) + tag = self.tag_handles[handle]+suffix + else: + tag = suffix + #if tag == u'!': + # raise ParserError("while parsing a node", start_mark, + # "found non-specific tag '!'", tag_mark, + # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.") + if start_mark is None: + start_mark = end_mark = self.peek_token().start_mark + event = None + implicit = (tag is None or tag == u'!') + if indentless_sequence and self.check_token(BlockEntryToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark) + self.state = self.parse_indentless_sequence_entry + else: + if self.check_token(ScalarToken): + token = self.get_token() + end_mark = token.end_mark + if (token.plain and tag is None) or tag == u'!': + implicit = (True, False) + elif tag is None: + implicit = (False, True) + else: + implicit = (False, False) + event = ScalarEvent(anchor, tag, implicit, token.value, + start_mark, end_mark, style=token.style) + self.state = self.states.pop() + elif self.check_token(FlowSequenceStartToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_sequence_first_entry + elif self.check_token(FlowMappingStartToken): + end_mark = self.peek_token().end_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_mapping_first_key + elif block and self.check_token(BlockSequenceStartToken): + end_mark = self.peek_token().start_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_sequence_first_entry + elif block and self.check_token(BlockMappingStartToken): + end_mark = self.peek_token().start_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_mapping_first_key + elif anchor is not None or tag is not None: + # Empty scalars are allowed even if a tag or an anchor is + # specified. + event = ScalarEvent(anchor, tag, (implicit, False), u'', + start_mark, end_mark) + self.state = self.states.pop() + else: + if block: + node = 'block' + else: + node = 'flow' + token = self.peek_token() + raise ParserError("while parsing a %s node" % node, start_mark, + "expected the node content, but found %r" % token.id, + token.start_mark) + return event + + # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + + def parse_block_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_sequence_entry() + + def parse_block_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, BlockEndToken): + self.states.append(self.parse_block_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_block_sequence_entry + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block collection", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + # indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + + def parse_indentless_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, + KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_indentless_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_indentless_sequence_entry + return self.process_empty_scalar(token.end_mark) + token = self.peek_token() + event = SequenceEndEvent(token.start_mark, token.start_mark) + self.state = self.states.pop() + return event + + # block_mapping ::= BLOCK-MAPPING_START + # ((KEY block_node_or_indentless_sequence?)? + # (VALUE block_node_or_indentless_sequence?)?)* + # BLOCK-END + + def parse_block_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_mapping_key() + + def parse_block_mapping_key(self): + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_value) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_value + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block mapping", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_block_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_key) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_block_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + # flow_sequence ::= FLOW-SEQUENCE-START + # (flow_sequence_entry FLOW-ENTRY)* + # flow_sequence_entry? + # FLOW-SEQUENCE-END + # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + # + # Note that while production rules for both flow_sequence_entry and + # flow_mapping_entry are equal, their interpretations are different. + # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?` + # generate an inline mapping (set syntax). + + def parse_flow_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_sequence_entry(first=True) + + def parse_flow_sequence_entry(self, first=False): + if not self.check_token(FlowSequenceEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow sequence", self.marks[-1], + "expected ',' or ']', but got %r" % token.id, token.start_mark) + + if self.check_token(KeyToken): + token = self.peek_token() + event = MappingStartEvent(None, None, True, + token.start_mark, token.end_mark, + flow_style=True) + self.state = self.parse_flow_sequence_entry_mapping_key + return event + elif not self.check_token(FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry) + return self.parse_flow_node() + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_sequence_entry_mapping_key(self): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_value + return self.process_empty_scalar(token.end_mark) + + def parse_flow_sequence_entry_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_end) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_end + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_sequence_entry_mapping_end + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_sequence_entry_mapping_end(self): + self.state = self.parse_flow_sequence_entry + token = self.peek_token() + return MappingEndEvent(token.start_mark, token.start_mark) + + # flow_mapping ::= FLOW-MAPPING-START + # (flow_mapping_entry FLOW-ENTRY)* + # flow_mapping_entry? + # FLOW-MAPPING-END + # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + + def parse_flow_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_mapping_key(first=True) + + def parse_flow_mapping_key(self, first=False): + if not self.check_token(FlowMappingEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow mapping", self.marks[-1], + "expected ',' or '}', but got %r" % token.id, token.start_mark) + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_value + return self.process_empty_scalar(token.end_mark) + elif not self.check_token(FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_empty_value) + return self.parse_flow_node() + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_key) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_mapping_empty_value(self): + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(self.peek_token().start_mark) + + def process_empty_scalar(self, mark): + return ScalarEvent(None, None, (True, False), u'', mark, mark) + diff --git a/dist/lib/yaml/reader.py b/dist/lib/yaml/reader.py new file mode 100644 index 0000000..3249e6b --- /dev/null +++ b/dist/lib/yaml/reader.py @@ -0,0 +1,190 @@ +# This module contains abstractions for the input stream. You don't have to +# looks further, there are no pretty code. +# +# We define two classes here. +# +# Mark(source, line, column) +# It's just a record and its only use is producing nice error messages. +# Parser does not use it for any other purposes. +# +# Reader(source, data) +# Reader determines the encoding of `data` and converts it to unicode. +# Reader provides the following methods and attributes: +# reader.peek(length=1) - return the next `length` characters +# reader.forward(length=1) - move the current position to `length` characters. +# reader.index - the number of the current character. +# reader.line, stream.column - the line and the column of the current character. + +__all__ = ['Reader', 'ReaderError'] + +from error import YAMLError, Mark + +import codecs, re + +class ReaderError(YAMLError): + + def __init__(self, name, position, character, encoding, reason): + self.name = name + self.character = character + self.position = position + self.encoding = encoding + self.reason = reason + + def __str__(self): + if isinstance(self.character, str): + return "'%s' codec can't decode byte #x%02x: %s\n" \ + " in \"%s\", position %d" \ + % (self.encoding, ord(self.character), self.reason, + self.name, self.position) + else: + return "unacceptable character #x%04x: %s\n" \ + " in \"%s\", position %d" \ + % (self.character, self.reason, + self.name, self.position) + +class Reader(object): + # Reader: + # - determines the data encoding and converts it to unicode, + # - checks if characters are in allowed range, + # - adds '\0' to the end. + + # Reader accepts + # - a `str` object, + # - a `unicode` object, + # - a file-like object with its `read` method returning `str`, + # - a file-like object with its `read` method returning `unicode`. + + # Yeah, it's ugly and slow. + + def __init__(self, stream): + self.name = None + self.stream = None + self.stream_pointer = 0 + self.eof = True + self.buffer = u'' + self.pointer = 0 + self.raw_buffer = None + self.raw_decode = None + self.encoding = None + self.index = 0 + self.line = 0 + self.column = 0 + if isinstance(stream, unicode): + self.name = "" + self.check_printable(stream) + self.buffer = stream+u'\0' + elif isinstance(stream, str): + self.name = "" + self.raw_buffer = stream + self.determine_encoding() + else: + self.stream = stream + self.name = getattr(stream, 'name', "") + self.eof = False + self.raw_buffer = '' + self.determine_encoding() + + def peek(self, index=0): + try: + return self.buffer[self.pointer+index] + except IndexError: + self.update(index+1) + return self.buffer[self.pointer+index] + + def prefix(self, length=1): + if self.pointer+length >= len(self.buffer): + self.update(length) + return self.buffer[self.pointer:self.pointer+length] + + def forward(self, length=1): + if self.pointer+length+1 >= len(self.buffer): + self.update(length+1) + while length: + ch = self.buffer[self.pointer] + self.pointer += 1 + self.index += 1 + if ch in u'\n\x85\u2028\u2029' \ + or (ch == u'\r' and self.buffer[self.pointer] != u'\n'): + self.line += 1 + self.column = 0 + elif ch != u'\uFEFF': + self.column += 1 + length -= 1 + + def get_mark(self): + if self.stream is None: + return Mark(self.name, self.index, self.line, self.column, + self.buffer, self.pointer) + else: + return Mark(self.name, self.index, self.line, self.column, + None, None) + + def determine_encoding(self): + while not self.eof and len(self.raw_buffer) < 2: + self.update_raw() + if not isinstance(self.raw_buffer, unicode): + if self.raw_buffer.startswith(codecs.BOM_UTF16_LE): + self.raw_decode = codecs.utf_16_le_decode + self.encoding = 'utf-16-le' + elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE): + self.raw_decode = codecs.utf_16_be_decode + self.encoding = 'utf-16-be' + else: + self.raw_decode = codecs.utf_8_decode + self.encoding = 'utf-8' + self.update(1) + + NON_PRINTABLE = re.compile(u'[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD]') + def check_printable(self, data): + match = self.NON_PRINTABLE.search(data) + if match: + character = match.group() + position = self.index+(len(self.buffer)-self.pointer)+match.start() + raise ReaderError(self.name, position, ord(character), + 'unicode', "special characters are not allowed") + + def update(self, length): + if self.raw_buffer is None: + return + self.buffer = self.buffer[self.pointer:] + self.pointer = 0 + while len(self.buffer) < length: + if not self.eof: + self.update_raw() + if self.raw_decode is not None: + try: + data, converted = self.raw_decode(self.raw_buffer, + 'strict', self.eof) + except UnicodeDecodeError, exc: + character = exc.object[exc.start] + if self.stream is not None: + position = self.stream_pointer-len(self.raw_buffer)+exc.start + else: + position = exc.start + raise ReaderError(self.name, position, character, + exc.encoding, exc.reason) + else: + data = self.raw_buffer + converted = len(data) + self.check_printable(data) + self.buffer += data + self.raw_buffer = self.raw_buffer[converted:] + if self.eof: + self.buffer += u'\0' + self.raw_buffer = None + break + + def update_raw(self, size=1024): + data = self.stream.read(size) + if data: + self.raw_buffer += data + self.stream_pointer += len(data) + else: + self.eof = True + +#try: +# import psyco +# psyco.bind(Reader) +#except ImportError: +# pass + diff --git a/dist/lib/yaml/representer.py b/dist/lib/yaml/representer.py new file mode 100644 index 0000000..5f4fc70 --- /dev/null +++ b/dist/lib/yaml/representer.py @@ -0,0 +1,484 @@ + +__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', + 'RepresenterError'] + +from error import * +from nodes import * + +import datetime + +import sys, copy_reg, types + +class RepresenterError(YAMLError): + pass + +class BaseRepresenter(object): + + yaml_representers = {} + yaml_multi_representers = {} + + def __init__(self, default_style=None, default_flow_style=None): + self.default_style = default_style + self.default_flow_style = default_flow_style + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent(self, data): + node = self.represent_data(data) + self.serialize(node) + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def get_classobj_bases(self, cls): + bases = [cls] + for base in cls.__bases__: + bases.extend(self.get_classobj_bases(base)) + return bases + + def represent_data(self, data): + if self.ignore_aliases(data): + self.alias_key = None + else: + self.alias_key = id(data) + if self.alias_key is not None: + if self.alias_key in self.represented_objects: + node = self.represented_objects[self.alias_key] + #if node is None: + # raise RepresenterError("recursive objects are not allowed: %r" % data) + return node + #self.represented_objects[alias_key] = None + self.object_keeper.append(data) + data_types = type(data).__mro__ + if type(data) is types.InstanceType: + data_types = self.get_classobj_bases(data.__class__)+list(data_types) + if data_types[0] in self.yaml_representers: + node = self.yaml_representers[data_types[0]](self, data) + else: + for data_type in data_types: + if data_type in self.yaml_multi_representers: + node = self.yaml_multi_representers[data_type](self, data) + break + else: + if None in self.yaml_multi_representers: + node = self.yaml_multi_representers[None](self, data) + elif None in self.yaml_representers: + node = self.yaml_representers[None](self, data) + else: + node = ScalarNode(None, unicode(data)) + #if alias_key is not None: + # self.represented_objects[alias_key] = node + return node + + def add_representer(cls, data_type, representer): + if not 'yaml_representers' in cls.__dict__: + cls.yaml_representers = cls.yaml_representers.copy() + cls.yaml_representers[data_type] = representer + add_representer = classmethod(add_representer) + + def add_multi_representer(cls, data_type, representer): + if not 'yaml_multi_representers' in cls.__dict__: + cls.yaml_multi_representers = cls.yaml_multi_representers.copy() + cls.yaml_multi_representers[data_type] = representer + add_multi_representer = classmethod(add_multi_representer) + + def represent_scalar(self, tag, value, style=None): + if style is None: + style = self.default_style + node = ScalarNode(tag, value, style=style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + return node + + def represent_sequence(self, tag, sequence, flow_style=None): + value = [] + node = SequenceNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + for item in sequence: + node_item = self.represent_data(item) + if not (isinstance(node_item, ScalarNode) and not node_item.style): + best_style = False + value.append(node_item) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def represent_mapping(self, tag, mapping, flow_style=None): + value = [] + node = MappingNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + if hasattr(mapping, 'items'): + mapping = mapping.items() + mapping.sort() + for item_key, item_value in mapping: + node_key = self.represent_data(item_key) + node_value = self.represent_data(item_value) + if not (isinstance(node_key, ScalarNode) and not node_key.style): + best_style = False + if not (isinstance(node_value, ScalarNode) and not node_value.style): + best_style = False + value.append((node_key, node_value)) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def ignore_aliases(self, data): + return False + +class SafeRepresenter(BaseRepresenter): + + def ignore_aliases(self, data): + if data in [None, ()]: + return True + if isinstance(data, (str, unicode, bool, int, float)): + return True + + def represent_none(self, data): + return self.represent_scalar(u'tag:yaml.org,2002:null', + u'null') + + def represent_str(self, data): + tag = None + style = None + try: + data = unicode(data, 'ascii') + tag = u'tag:yaml.org,2002:str' + except UnicodeDecodeError: + try: + data = unicode(data, 'utf-8') + tag = u'tag:yaml.org,2002:str' + except UnicodeDecodeError: + data = data.encode('base64') + tag = u'tag:yaml.org,2002:binary' + style = '|' + return self.represent_scalar(tag, data, style=style) + + def represent_unicode(self, data): + return self.represent_scalar(u'tag:yaml.org,2002:str', data) + + def represent_bool(self, data): + if data: + value = u'true' + else: + value = u'false' + return self.represent_scalar(u'tag:yaml.org,2002:bool', value) + + def represent_int(self, data): + return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data)) + + def represent_long(self, data): + return self.represent_scalar(u'tag:yaml.org,2002:int', unicode(data)) + + inf_value = 1e300 + while repr(inf_value) != repr(inf_value*inf_value): + inf_value *= inf_value + + def represent_float(self, data): + if data != data or (data == 0.0 and data == 1.0): + value = u'.nan' + elif data == self.inf_value: + value = u'.inf' + elif data == -self.inf_value: + value = u'-.inf' + else: + value = unicode(repr(data)).lower() + # Note that in some cases `repr(data)` represents a float number + # without the decimal parts. For instance: + # >>> repr(1e17) + # '1e17' + # Unfortunately, this is not a valid float representation according + # to the definition of the `!!float` tag. We fix this by adding + # '.0' before the 'e' symbol. + if u'.' not in value and u'e' in value: + value = value.replace(u'e', u'.0e', 1) + return self.represent_scalar(u'tag:yaml.org,2002:float', value) + + def represent_list(self, data): + #pairs = (len(data) > 0 and isinstance(data, list)) + #if pairs: + # for item in data: + # if not isinstance(item, tuple) or len(item) != 2: + # pairs = False + # break + #if not pairs: + return self.represent_sequence(u'tag:yaml.org,2002:seq', data) + #value = [] + #for item_key, item_value in data: + # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', + # [(item_key, item_value)])) + #return SequenceNode(u'tag:yaml.org,2002:pairs', value) + + def represent_dict(self, data): + return self.represent_mapping(u'tag:yaml.org,2002:map', data) + + def represent_set(self, data): + value = {} + for key in data: + value[key] = None + return self.represent_mapping(u'tag:yaml.org,2002:set', value) + + def represent_date(self, data): + value = unicode(data.isoformat()) + return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value) + + def represent_datetime(self, data): + value = unicode(data.isoformat(' ')) + return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value) + + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) + + def represent_undefined(self, data): + raise RepresenterError("cannot represent an object: %s" % data) + +SafeRepresenter.add_representer(type(None), + SafeRepresenter.represent_none) + +SafeRepresenter.add_representer(str, + SafeRepresenter.represent_str) + +SafeRepresenter.add_representer(unicode, + SafeRepresenter.represent_unicode) + +SafeRepresenter.add_representer(bool, + SafeRepresenter.represent_bool) + +SafeRepresenter.add_representer(int, + SafeRepresenter.represent_int) + +SafeRepresenter.add_representer(long, + SafeRepresenter.represent_long) + +SafeRepresenter.add_representer(float, + SafeRepresenter.represent_float) + +SafeRepresenter.add_representer(list, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(tuple, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(dict, + SafeRepresenter.represent_dict) + +SafeRepresenter.add_representer(set, + SafeRepresenter.represent_set) + +SafeRepresenter.add_representer(datetime.date, + SafeRepresenter.represent_date) + +SafeRepresenter.add_representer(datetime.datetime, + SafeRepresenter.represent_datetime) + +SafeRepresenter.add_representer(None, + SafeRepresenter.represent_undefined) + +class Representer(SafeRepresenter): + + def represent_str(self, data): + tag = None + style = None + try: + data = unicode(data, 'ascii') + tag = u'tag:yaml.org,2002:str' + except UnicodeDecodeError: + try: + data = unicode(data, 'utf-8') + tag = u'tag:yaml.org,2002:python/str' + except UnicodeDecodeError: + data = data.encode('base64') + tag = u'tag:yaml.org,2002:binary' + style = '|' + return self.represent_scalar(tag, data, style=style) + + def represent_unicode(self, data): + tag = None + try: + data.encode('ascii') + tag = u'tag:yaml.org,2002:python/unicode' + except UnicodeEncodeError: + tag = u'tag:yaml.org,2002:str' + return self.represent_scalar(tag, data) + + def represent_long(self, data): + tag = u'tag:yaml.org,2002:int' + if int(data) is not data: + tag = u'tag:yaml.org,2002:python/long' + return self.represent_scalar(tag, unicode(data)) + + def represent_complex(self, data): + if data.imag == 0.0: + data = u'%r' % data.real + elif data.real == 0.0: + data = u'%rj' % data.imag + elif data.imag > 0: + data = u'%r+%rj' % (data.real, data.imag) + else: + data = u'%r%rj' % (data.real, data.imag) + return self.represent_scalar(u'tag:yaml.org,2002:python/complex', data) + + def represent_tuple(self, data): + return self.represent_sequence(u'tag:yaml.org,2002:python/tuple', data) + + def represent_name(self, data): + name = u'%s.%s' % (data.__module__, data.__name__) + return self.represent_scalar(u'tag:yaml.org,2002:python/name:'+name, u'') + + def represent_module(self, data): + return self.represent_scalar( + u'tag:yaml.org,2002:python/module:'+data.__name__, u'') + + def represent_instance(self, data): + # For instances of classic classes, we use __getinitargs__ and + # __getstate__ to serialize the data. + + # If data.__getinitargs__ exists, the object must be reconstructed by + # calling cls(**args), where args is a tuple returned by + # __getinitargs__. Otherwise, the cls.__init__ method should never be + # called and the class instance is created by instantiating a trivial + # class and assigning to the instance's __class__ variable. + + # If data.__getstate__ exists, it returns the state of the object. + # Otherwise, the state of the object is data.__dict__. + + # We produce either a !!python/object or !!python/object/new node. + # If data.__getinitargs__ does not exist and state is a dictionary, we + # produce a !!python/object node . Otherwise we produce a + # !!python/object/new node. + + cls = data.__class__ + class_name = u'%s.%s' % (cls.__module__, cls.__name__) + args = None + state = None + if hasattr(data, '__getinitargs__'): + args = list(data.__getinitargs__()) + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__ + if args is None and isinstance(state, dict): + return self.represent_mapping( + u'tag:yaml.org,2002:python/object:'+class_name, state) + if isinstance(state, dict) and not state: + return self.represent_sequence( + u'tag:yaml.org,2002:python/object/new:'+class_name, args) + value = {} + if args: + value['args'] = args + value['state'] = state + return self.represent_mapping( + u'tag:yaml.org,2002:python/object/new:'+class_name, value) + + def represent_object(self, data): + # We use __reduce__ API to save the data. data.__reduce__ returns + # a tuple of length 2-5: + # (function, args, state, listitems, dictitems) + + # For reconstructing, we calls function(*args), then set its state, + # listitems, and dictitems if they are not None. + + # A special case is when function.__name__ == '__newobj__'. In this + # case we create the object with args[0].__new__(*args). + + # Another special case is when __reduce__ returns a string - we don't + # support it. + + # We produce a !!python/object, !!python/object/new or + # !!python/object/apply node. + + cls = type(data) + if cls in copy_reg.dispatch_table: + reduce = copy_reg.dispatch_table[cls](data) + elif hasattr(data, '__reduce_ex__'): + reduce = data.__reduce_ex__(2) + elif hasattr(data, '__reduce__'): + reduce = data.__reduce__() + else: + raise RepresenterError("cannot represent object: %r" % data) + reduce = (list(reduce)+[None]*5)[:5] + function, args, state, listitems, dictitems = reduce + args = list(args) + if state is None: + state = {} + if listitems is not None: + listitems = list(listitems) + if dictitems is not None: + dictitems = dict(dictitems) + if function.__name__ == '__newobj__': + function = args[0] + args = args[1:] + tag = u'tag:yaml.org,2002:python/object/new:' + newobj = True + else: + tag = u'tag:yaml.org,2002:python/object/apply:' + newobj = False + function_name = u'%s.%s' % (function.__module__, function.__name__) + if not args and not listitems and not dictitems \ + and isinstance(state, dict) and newobj: + return self.represent_mapping( + u'tag:yaml.org,2002:python/object:'+function_name, state) + if not listitems and not dictitems \ + and isinstance(state, dict) and not state: + return self.represent_sequence(tag+function_name, args) + value = {} + if args: + value['args'] = args + if state or not isinstance(state, dict): + value['state'] = state + if listitems: + value['listitems'] = listitems + if dictitems: + value['dictitems'] = dictitems + return self.represent_mapping(tag+function_name, value) + +Representer.add_representer(str, + Representer.represent_str) + +Representer.add_representer(unicode, + Representer.represent_unicode) + +Representer.add_representer(long, + Representer.represent_long) + +Representer.add_representer(complex, + Representer.represent_complex) + +Representer.add_representer(tuple, + Representer.represent_tuple) + +Representer.add_representer(type, + Representer.represent_name) + +Representer.add_representer(types.ClassType, + Representer.represent_name) + +Representer.add_representer(types.FunctionType, + Representer.represent_name) + +Representer.add_representer(types.BuiltinFunctionType, + Representer.represent_name) + +Representer.add_representer(types.ModuleType, + Representer.represent_module) + +Representer.add_multi_representer(types.InstanceType, + Representer.represent_instance) + +Representer.add_multi_representer(object, + Representer.represent_object) + diff --git a/dist/lib/yaml/resolver.py b/dist/lib/yaml/resolver.py new file mode 100644 index 0000000..6b5ab87 --- /dev/null +++ b/dist/lib/yaml/resolver.py @@ -0,0 +1,224 @@ + +__all__ = ['BaseResolver', 'Resolver'] + +from error import * +from nodes import * + +import re + +class ResolverError(YAMLError): + pass + +class BaseResolver(object): + + DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str' + DEFAULT_SEQUENCE_TAG = u'tag:yaml.org,2002:seq' + DEFAULT_MAPPING_TAG = u'tag:yaml.org,2002:map' + + yaml_implicit_resolvers = {} + yaml_path_resolvers = {} + + def __init__(self): + self.resolver_exact_paths = [] + self.resolver_prefix_paths = [] + + def add_implicit_resolver(cls, tag, regexp, first): + if not 'yaml_implicit_resolvers' in cls.__dict__: + cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy() + if first is None: + first = [None] + for ch in first: + cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) + add_implicit_resolver = classmethod(add_implicit_resolver) + + def add_path_resolver(cls, tag, path, kind=None): + # Note: `add_path_resolver` is experimental. The API could be changed. + # `new_path` is a pattern that is matched against the path from the + # root to the node that is being considered. `node_path` elements are + # tuples `(node_check, index_check)`. `node_check` is a node class: + # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None` + # matches any kind of a node. `index_check` could be `None`, a boolean + # value, a string value, or a number. `None` and `False` match against + # any _value_ of sequence and mapping nodes. `True` matches against + # any _key_ of a mapping node. A string `index_check` matches against + # a mapping value that corresponds to a scalar key which content is + # equal to the `index_check` value. An integer `index_check` matches + # against a sequence value with the index equal to `index_check`. + if not 'yaml_path_resolvers' in cls.__dict__: + cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy() + new_path = [] + for element in path: + if isinstance(element, (list, tuple)): + if len(element) == 2: + node_check, index_check = element + elif len(element) == 1: + node_check = element[0] + index_check = True + else: + raise ResolverError("Invalid path element: %s" % element) + else: + node_check = None + index_check = element + if node_check is str: + node_check = ScalarNode + elif node_check is list: + node_check = SequenceNode + elif node_check is dict: + node_check = MappingNode + elif node_check not in [ScalarNode, SequenceNode, MappingNode] \ + and not isinstance(node_check, basestring) \ + and node_check is not None: + raise ResolverError("Invalid node checker: %s" % node_check) + if not isinstance(index_check, (basestring, int)) \ + and index_check is not None: + raise ResolverError("Invalid index checker: %s" % index_check) + new_path.append((node_check, index_check)) + if kind is str: + kind = ScalarNode + elif kind is list: + kind = SequenceNode + elif kind is dict: + kind = MappingNode + elif kind not in [ScalarNode, SequenceNode, MappingNode] \ + and kind is not None: + raise ResolverError("Invalid node kind: %s" % kind) + cls.yaml_path_resolvers[tuple(new_path), kind] = tag + add_path_resolver = classmethod(add_path_resolver) + + def descend_resolver(self, current_node, current_index): + if not self.yaml_path_resolvers: + return + exact_paths = {} + prefix_paths = [] + if current_node: + depth = len(self.resolver_prefix_paths) + for path, kind in self.resolver_prefix_paths[-1]: + if self.check_resolver_prefix(depth, path, kind, + current_node, current_index): + if len(path) > depth: + prefix_paths.append((path, kind)) + else: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + for path, kind in self.yaml_path_resolvers: + if not path: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + prefix_paths.append((path, kind)) + self.resolver_exact_paths.append(exact_paths) + self.resolver_prefix_paths.append(prefix_paths) + + def ascend_resolver(self): + if not self.yaml_path_resolvers: + return + self.resolver_exact_paths.pop() + self.resolver_prefix_paths.pop() + + def check_resolver_prefix(self, depth, path, kind, + current_node, current_index): + node_check, index_check = path[depth-1] + if isinstance(node_check, basestring): + if current_node.tag != node_check: + return + elif node_check is not None: + if not isinstance(current_node, node_check): + return + if index_check is True and current_index is not None: + return + if (index_check is False or index_check is None) \ + and current_index is None: + return + if isinstance(index_check, basestring): + if not (isinstance(current_index, ScalarNode) + and index_check == current_index.value): + return + elif isinstance(index_check, int) and not isinstance(index_check, bool): + if index_check != current_index: + return + return True + + def resolve(self, kind, value, implicit): + if kind is ScalarNode and implicit[0]: + if value == u'': + resolvers = self.yaml_implicit_resolvers.get(u'', []) + else: + resolvers = self.yaml_implicit_resolvers.get(value[0], []) + resolvers += self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers: + if regexp.match(value): + return tag + implicit = implicit[1] + if self.yaml_path_resolvers: + exact_paths = self.resolver_exact_paths[-1] + if kind in exact_paths: + return exact_paths[kind] + if None in exact_paths: + return exact_paths[None] + if kind is ScalarNode: + return self.DEFAULT_SCALAR_TAG + elif kind is SequenceNode: + return self.DEFAULT_SEQUENCE_TAG + elif kind is MappingNode: + return self.DEFAULT_MAPPING_TAG + +class Resolver(BaseResolver): + pass + +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:bool', + re.compile(ur'''^(?:yes|Yes|YES|no|No|NO + |true|True|TRUE|false|False|FALSE + |on|On|ON|off|Off|OFF)$''', re.X), + list(u'yYnNtTfFoO')) + +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:float', + re.compile(ur'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + |\.[0-9_]+(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN))$''', re.X), + list(u'-+0123456789.')) + +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:int', + re.compile(ur'''^(?:[-+]?0b[0-1_]+ + |[-+]?0[0-7_]+ + |[-+]?(?:0|[1-9][0-9_]*) + |[-+]?0x[0-9a-fA-F_]+ + |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), + list(u'-+0123456789')) + +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:merge', + re.compile(ur'^(?:<<)$'), + [u'<']) + +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:null', + re.compile(ur'''^(?: ~ + |null|Null|NULL + | )$''', re.X), + [u'~', u'n', u'N', u'']) + +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:timestamp', + re.compile(ur'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? + (?:[Tt]|[ \t]+)[0-9][0-9]? + :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? + (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), + list(u'0123456789')) + +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:value', + re.compile(ur'^(?:=)$'), + [u'=']) + +# The following resolver is only for documentation purposes. It cannot work +# because plain scalars cannot start with '!', '&', or '*'. +Resolver.add_implicit_resolver( + u'tag:yaml.org,2002:yaml', + re.compile(ur'^(?:!|&|\*)$'), + list(u'!&*')) + diff --git a/dist/lib/yaml/scanner.py b/dist/lib/yaml/scanner.py new file mode 100644 index 0000000..5228fad --- /dev/null +++ b/dist/lib/yaml/scanner.py @@ -0,0 +1,1457 @@ + +# Scanner produces tokens of the following types: +# STREAM-START +# STREAM-END +# DIRECTIVE(name, value) +# DOCUMENT-START +# DOCUMENT-END +# BLOCK-SEQUENCE-START +# BLOCK-MAPPING-START +# BLOCK-END +# FLOW-SEQUENCE-START +# FLOW-MAPPING-START +# FLOW-SEQUENCE-END +# FLOW-MAPPING-END +# BLOCK-ENTRY +# FLOW-ENTRY +# KEY +# VALUE +# ALIAS(value) +# ANCHOR(value) +# TAG(value) +# SCALAR(value, plain, style) +# +# Read comments in the Scanner code for more details. +# + +__all__ = ['Scanner', 'ScannerError'] + +from error import MarkedYAMLError +from tokens import * + +class ScannerError(MarkedYAMLError): + pass + +class SimpleKey(object): + # See below simple keys treatment. + + def __init__(self, token_number, required, index, line, column, mark): + self.token_number = token_number + self.required = required + self.index = index + self.line = line + self.column = column + self.mark = mark + +class Scanner(object): + + def __init__(self): + """Initialize the scanner.""" + # It is assumed that Scanner and Reader will have a common descendant. + # Reader do the dirty work of checking for BOM and converting the + # input data to Unicode. It also adds NUL to the end. + # + # Reader supports the following methods + # self.peek(i=0) # peek the next i-th character + # self.prefix(l=1) # peek the next l characters + # self.forward(l=1) # read the next l characters and move the pointer. + + # Had we reached the end of the stream? + self.done = False + + # The number of unclosed '{' and '['. `flow_level == 0` means block + # context. + self.flow_level = 0 + + # List of processed tokens that are not yet emitted. + self.tokens = [] + + # Add the STREAM-START token. + self.fetch_stream_start() + + # Number of tokens that were emitted through the `get_token` method. + self.tokens_taken = 0 + + # The current indentation level. + self.indent = -1 + + # Past indentation levels. + self.indents = [] + + # Variables related to simple keys treatment. + + # A simple key is a key that is not denoted by the '?' indicator. + # Example of simple keys: + # --- + # block simple key: value + # ? not a simple key: + # : { flow simple key: value } + # We emit the KEY token before all keys, so when we find a potential + # simple key, we try to locate the corresponding ':' indicator. + # Simple keys should be limited to a single line and 1024 characters. + + # Can a simple key start at the current position? A simple key may + # start: + # - at the beginning of the line, not counting indentation spaces + # (in block context), + # - after '{', '[', ',' (in the flow context), + # - after '?', ':', '-' (in the block context). + # In the block context, this flag also signifies if a block collection + # may start at the current position. + self.allow_simple_key = True + + # Keep track of possible simple keys. This is a dictionary. The key + # is `flow_level`; there can be no more that one possible simple key + # for each level. The value is a SimpleKey record: + # (token_number, required, index, line, column, mark) + # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow), + # '[', or '{' tokens. + self.possible_simple_keys = {} + + # Public methods. + + def check_token(self, *choices): + # Check if the next token is one of the given types. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + if not choices: + return True + for choice in choices: + if isinstance(self.tokens[0], choice): + return True + return False + + def peek_token(self): + # Return the next token, but do not delete if from the queue. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + return self.tokens[0] + + def get_token(self): + # Return the next token. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + self.tokens_taken += 1 + return self.tokens.pop(0) + + # Private methods. + + def need_more_tokens(self): + if self.done: + return False + if not self.tokens: + return True + # The current token may be a potential simple key, so we + # need to look further. + self.stale_possible_simple_keys() + if self.next_possible_simple_key() == self.tokens_taken: + return True + + def fetch_more_tokens(self): + + # Eat whitespaces and comments until we reach the next token. + self.scan_to_next_token() + + # Remove obsolete possible simple keys. + self.stale_possible_simple_keys() + + # Compare the current indentation and column. It may add some tokens + # and decrease the current indentation level. + self.unwind_indent(self.column) + + # Peek the next character. + ch = self.peek() + + # Is it the end of stream? + if ch == u'\0': + return self.fetch_stream_end() + + # Is it a directive? + if ch == u'%' and self.check_directive(): + return self.fetch_directive() + + # Is it the document start? + if ch == u'-' and self.check_document_start(): + return self.fetch_document_start() + + # Is it the document end? + if ch == u'.' and self.check_document_end(): + return self.fetch_document_end() + + # TODO: support for BOM within a stream. + #if ch == u'\uFEFF': + # return self.fetch_bom() <-- issue BOMToken + + # Note: the order of the following checks is NOT significant. + + # Is it the flow sequence start indicator? + if ch == u'[': + return self.fetch_flow_sequence_start() + + # Is it the flow mapping start indicator? + if ch == u'{': + return self.fetch_flow_mapping_start() + + # Is it the flow sequence end indicator? + if ch == u']': + return self.fetch_flow_sequence_end() + + # Is it the flow mapping end indicator? + if ch == u'}': + return self.fetch_flow_mapping_end() + + # Is it the flow entry indicator? + if ch == u',': + return self.fetch_flow_entry() + + # Is it the block entry indicator? + if ch == u'-' and self.check_block_entry(): + return self.fetch_block_entry() + + # Is it the key indicator? + if ch == u'?' and self.check_key(): + return self.fetch_key() + + # Is it the value indicator? + if ch == u':' and self.check_value(): + return self.fetch_value() + + # Is it an alias? + if ch == u'*': + return self.fetch_alias() + + # Is it an anchor? + if ch == u'&': + return self.fetch_anchor() + + # Is it a tag? + if ch == u'!': + return self.fetch_tag() + + # Is it a literal scalar? + if ch == u'|' and not self.flow_level: + return self.fetch_literal() + + # Is it a folded scalar? + if ch == u'>' and not self.flow_level: + return self.fetch_folded() + + # Is it a single quoted scalar? + if ch == u'\'': + return self.fetch_single() + + # Is it a double quoted scalar? + if ch == u'\"': + return self.fetch_double() + + # It must be a plain scalar then. + if self.check_plain(): + return self.fetch_plain() + + # No? It's an error. Let's produce a nice error message. + raise ScannerError("while scanning for the next token", None, + "found character %r that cannot start any token" + % ch.encode('utf-8'), self.get_mark()) + + # Simple keys treatment. + + def next_possible_simple_key(self): + # Return the number of the nearest possible simple key. Actually we + # don't need to loop through the whole dictionary. We may replace it + # with the following code: + # if not self.possible_simple_keys: + # return None + # return self.possible_simple_keys[ + # min(self.possible_simple_keys.keys())].token_number + min_token_number = None + for level in self.possible_simple_keys: + key = self.possible_simple_keys[level] + if min_token_number is None or key.token_number < min_token_number: + min_token_number = key.token_number + return min_token_number + + def stale_possible_simple_keys(self): + # Remove entries that are no longer possible simple keys. According to + # the YAML specification, simple keys + # - should be limited to a single line, + # - should be no longer than 1024 characters. + # Disabling this procedure will allow simple keys of any length and + # height (may cause problems if indentation is broken though). + for level in self.possible_simple_keys.keys(): + key = self.possible_simple_keys[level] + if key.line != self.line \ + or self.index-key.index > 1024: + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not found expected ':'", self.get_mark()) + del self.possible_simple_keys[level] + + def save_possible_simple_key(self): + # The next token may start a simple key. We check if it's possible + # and save its position. This function is called for + # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'. + + # Check if a simple key is required at the current position. + required = not self.flow_level and self.indent == self.column + + # A simple key is required only if it is the first token in the current + # line. Therefore it is always allowed. + assert self.allow_simple_key or not required + + # The next token might be a simple key. Let's save it's number and + # position. + if self.allow_simple_key: + self.remove_possible_simple_key() + token_number = self.tokens_taken+len(self.tokens) + key = SimpleKey(token_number, required, + self.index, self.line, self.column, self.get_mark()) + self.possible_simple_keys[self.flow_level] = key + + def remove_possible_simple_key(self): + # Remove the saved possible key position at the current flow level. + if self.flow_level in self.possible_simple_keys: + key = self.possible_simple_keys[self.flow_level] + + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not found expected ':'", self.get_mark()) + + del self.possible_simple_keys[self.flow_level] + + # Indentation functions. + + def unwind_indent(self, column): + + ## In flow context, tokens should respect indentation. + ## Actually the condition should be `self.indent >= column` according to + ## the spec. But this condition will prohibit intuitively correct + ## constructions such as + ## key : { + ## } + #if self.flow_level and self.indent > column: + # raise ScannerError(None, None, + # "invalid intendation or unclosed '[' or '{'", + # self.get_mark()) + + # In the flow context, indentation is ignored. We make the scanner less + # restrictive then specification requires. + if self.flow_level: + return + + # In block context, we may need to issue the BLOCK-END tokens. + while self.indent > column: + mark = self.get_mark() + self.indent = self.indents.pop() + self.tokens.append(BlockEndToken(mark, mark)) + + def add_indent(self, column): + # Check if we need to increase indentation. + if self.indent < column: + self.indents.append(self.indent) + self.indent = column + return True + return False + + # Fetchers. + + def fetch_stream_start(self): + # We always add STREAM-START as the first token and STREAM-END as the + # last token. + + # Read the token. + mark = self.get_mark() + + # Add STREAM-START. + self.tokens.append(StreamStartToken(mark, mark, + encoding=self.encoding)) + + + def fetch_stream_end(self): + + # Set the current intendation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + self.possible_simple_keys = {} + + # Read the token. + mark = self.get_mark() + + # Add STREAM-END. + self.tokens.append(StreamEndToken(mark, mark)) + + # The steam is finished. + self.done = True + + def fetch_directive(self): + + # Set the current intendation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Scan and add DIRECTIVE. + self.tokens.append(self.scan_directive()) + + def fetch_document_start(self): + self.fetch_document_indicator(DocumentStartToken) + + def fetch_document_end(self): + self.fetch_document_indicator(DocumentEndToken) + + def fetch_document_indicator(self, TokenClass): + + # Set the current intendation to -1. + self.unwind_indent(-1) + + # Reset simple keys. Note that there could not be a block collection + # after '---'. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Add DOCUMENT-START or DOCUMENT-END. + start_mark = self.get_mark() + self.forward(3) + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_start(self): + self.fetch_flow_collection_start(FlowSequenceStartToken) + + def fetch_flow_mapping_start(self): + self.fetch_flow_collection_start(FlowMappingStartToken) + + def fetch_flow_collection_start(self, TokenClass): + + # '[' and '{' may start a simple key. + self.save_possible_simple_key() + + # Increase the flow level. + self.flow_level += 1 + + # Simple keys are allowed after '[' and '{'. + self.allow_simple_key = True + + # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_end(self): + self.fetch_flow_collection_end(FlowSequenceEndToken) + + def fetch_flow_mapping_end(self): + self.fetch_flow_collection_end(FlowMappingEndToken) + + def fetch_flow_collection_end(self, TokenClass): + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Decrease the flow level. + self.flow_level -= 1 + + # No simple keys after ']' or '}'. + self.allow_simple_key = False + + # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_entry(self): + + # Simple keys are allowed after ','. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add FLOW-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(FlowEntryToken(start_mark, end_mark)) + + def fetch_block_entry(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a new entry? + if not self.allow_simple_key: + raise ScannerError(None, None, + "sequence entries are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-SEQUENCE-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockSequenceStartToken(mark, mark)) + + # It's an error for the block entry to occur in the flow context, + # but we let the parser detect this. + else: + pass + + # Simple keys are allowed after '-'. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add BLOCK-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(BlockEntryToken(start_mark, end_mark)) + + def fetch_key(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a key (not nessesary a simple)? + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping keys are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-MAPPING-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after '?' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add KEY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(KeyToken(start_mark, end_mark)) + + def fetch_value(self): + + # Do we determine a simple key? + if self.flow_level in self.possible_simple_keys: + + # Add KEY. + key = self.possible_simple_keys[self.flow_level] + del self.possible_simple_keys[self.flow_level] + self.tokens.insert(key.token_number-self.tokens_taken, + KeyToken(key.mark, key.mark)) + + # If this key starts a new block mapping, we need to add + # BLOCK-MAPPING-START. + if not self.flow_level: + if self.add_indent(key.column): + self.tokens.insert(key.token_number-self.tokens_taken, + BlockMappingStartToken(key.mark, key.mark)) + + # There cannot be two simple keys one after another. + self.allow_simple_key = False + + # It must be a part of a complex key. + else: + + # Block context needs additional checks. + # (Do we really need them? They will be catched by the parser + # anyway.) + if not self.flow_level: + + # We are allowed to start a complex value if and only if + # we can start a simple key. + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping values are not allowed here", + self.get_mark()) + + # If this value starts a new block mapping, we need to add + # BLOCK-MAPPING-START. It will be detected as an error later by + # the parser. + if not self.flow_level: + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after ':' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add VALUE. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(ValueToken(start_mark, end_mark)) + + def fetch_alias(self): + + # ALIAS could be a simple key. + self.save_possible_simple_key() + + # No simple keys after ALIAS. + self.allow_simple_key = False + + # Scan and add ALIAS. + self.tokens.append(self.scan_anchor(AliasToken)) + + def fetch_anchor(self): + + # ANCHOR could start a simple key. + self.save_possible_simple_key() + + # No simple keys after ANCHOR. + self.allow_simple_key = False + + # Scan and add ANCHOR. + self.tokens.append(self.scan_anchor(AnchorToken)) + + def fetch_tag(self): + + # TAG could start a simple key. + self.save_possible_simple_key() + + # No simple keys after TAG. + self.allow_simple_key = False + + # Scan and add TAG. + self.tokens.append(self.scan_tag()) + + def fetch_literal(self): + self.fetch_block_scalar(style='|') + + def fetch_folded(self): + self.fetch_block_scalar(style='>') + + def fetch_block_scalar(self, style): + + # A simple key may follow a block scalar. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Scan and add SCALAR. + self.tokens.append(self.scan_block_scalar(style)) + + def fetch_single(self): + self.fetch_flow_scalar(style='\'') + + def fetch_double(self): + self.fetch_flow_scalar(style='"') + + def fetch_flow_scalar(self, style): + + # A flow scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after flow scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. + self.tokens.append(self.scan_flow_scalar(style)) + + def fetch_plain(self): + + # A plain scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after plain scalars. But note that `scan_plain` will + # change this flag if the scan is finished at the beginning of the + # line. + self.allow_simple_key = False + + # Scan and add SCALAR. May change `allow_simple_key`. + self.tokens.append(self.scan_plain()) + + # Checkers. + + def check_directive(self): + + # DIRECTIVE: ^ '%' ... + # The '%' indicator is already checked. + if self.column == 0: + return True + + def check_document_start(self): + + # DOCUMENT-START: ^ '---' (' '|'\n') + if self.column == 0: + if self.prefix(3) == u'---' \ + and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029': + return True + + def check_document_end(self): + + # DOCUMENT-END: ^ '...' (' '|'\n') + if self.column == 0: + if self.prefix(3) == u'...' \ + and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029': + return True + + def check_block_entry(self): + + # BLOCK-ENTRY: '-' (' '|'\n') + return self.peek(1) in u'\0 \t\r\n\x85\u2028\u2029' + + def check_key(self): + + # KEY(flow context): '?' + if self.flow_level: + return True + + # KEY(block context): '?' (' '|'\n') + else: + return self.peek(1) in u'\0 \t\r\n\x85\u2028\u2029' + + def check_value(self): + + # VALUE(flow context): ':' + if self.flow_level: + return True + + # VALUE(block context): ':' (' '|'\n') + else: + return self.peek(1) in u'\0 \t\r\n\x85\u2028\u2029' + + def check_plain(self): + + # A plain scalar may start with any non-space character except: + # '-', '?', ':', ',', '[', ']', '{', '}', + # '#', '&', '*', '!', '|', '>', '\'', '\"', + # '%', '@', '`'. + # + # It may also start with + # '-', '?', ':' + # if it is followed by a non-space character. + # + # Note that we limit the last rule to the block context (except the + # '-' character) because we want the flow context to be space + # independent. + ch = self.peek() + return ch not in u'\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ + or (self.peek(1) not in u'\0 \t\r\n\x85\u2028\u2029' + and (ch == u'-' or (not self.flow_level and ch in u'?:'))) + + # Scanners. + + def scan_to_next_token(self): + # We ignore spaces, line breaks and comments. + # If we find a line break in the block context, we set the flag + # `allow_simple_key` on. + # The byte order mark is stripped if it's the first character in the + # stream. We do not yet support BOM inside the stream as the + # specification requires. Any such mark will be considered as a part + # of the document. + # + # TODO: We need to make tab handling rules more sane. A good rule is + # Tabs cannot precede tokens + # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END, + # KEY(block), VALUE(block), BLOCK-ENTRY + # So the checking code is + # if : + # self.allow_simple_keys = False + # We also need to add the check for `allow_simple_keys == True` to + # `unwind_indent` before issuing BLOCK-END. + # Scanners for block, flow, and plain scalars need to be modified. + + if self.index == 0 and self.peek() == u'\uFEFF': + self.forward() + found = False + while not found: + while self.peek() == u' ': + self.forward() + if self.peek() == u'#': + while self.peek() not in u'\0\r\n\x85\u2028\u2029': + self.forward() + if self.scan_line_break(): + if not self.flow_level: + self.allow_simple_key = True + else: + found = True + + def scan_directive(self): + # See the specification for details. + start_mark = self.get_mark() + self.forward() + name = self.scan_directive_name(start_mark) + value = None + if name == u'YAML': + value = self.scan_yaml_directive_value(start_mark) + end_mark = self.get_mark() + elif name == u'TAG': + value = self.scan_tag_directive_value(start_mark) + end_mark = self.get_mark() + else: + end_mark = self.get_mark() + while self.peek() not in u'\0\r\n\x85\u2028\u2029': + self.forward() + self.scan_directive_ignored_line(start_mark) + return DirectiveToken(name, value, start_mark, end_mark) + + def scan_directive_name(self, start_mark): + # See the specification for details. + length = 0 + ch = self.peek(length) + while u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch.encode('utf-8'), self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in u'\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch.encode('utf-8'), self.get_mark()) + return value + + def scan_yaml_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == u' ': + self.forward() + major = self.scan_yaml_directive_number(start_mark) + if self.peek() != '.': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or '.', but found %r" + % self.peek().encode('utf-8'), + self.get_mark()) + self.forward() + minor = self.scan_yaml_directive_number(start_mark) + if self.peek() not in u'\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or ' ', but found %r" + % self.peek().encode('utf-8'), + self.get_mark()) + return (major, minor) + + def scan_yaml_directive_number(self, start_mark): + # See the specification for details. + ch = self.peek() + if not (u'0' <= ch <= u'9'): + raise ScannerError("while scanning a directive", start_mark, + "expected a digit, but found %r" % ch.encode('utf-8'), + self.get_mark()) + length = 0 + while u'0' <= self.peek(length) <= u'9': + length += 1 + value = int(self.prefix(length)) + self.forward(length) + return value + + def scan_tag_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == u' ': + self.forward() + handle = self.scan_tag_directive_handle(start_mark) + while self.peek() == u' ': + self.forward() + prefix = self.scan_tag_directive_prefix(start_mark) + return (handle, prefix) + + def scan_tag_directive_handle(self, start_mark): + # See the specification for details. + value = self.scan_tag_handle('directive', start_mark) + ch = self.peek() + if ch != u' ': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch.encode('utf-8'), + self.get_mark()) + return value + + def scan_tag_directive_prefix(self, start_mark): + # See the specification for details. + value = self.scan_tag_uri('directive', start_mark) + ch = self.peek() + if ch not in u'\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch.encode('utf-8'), + self.get_mark()) + return value + + def scan_directive_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == u' ': + self.forward() + if self.peek() == u'#': + while self.peek() not in u'\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in u'\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a comment or a line break, but found %r" + % ch.encode('utf-8'), self.get_mark()) + self.scan_line_break() + + def scan_anchor(self, TokenClass): + # The specification does not restrict characters for anchors and + # aliases. This may lead to problems, for instance, the document: + # [ *alias, value ] + # can be interpteted in two ways, as + # [ "value" ] + # and + # [ *alias , "value" ] + # Therefore we restrict aliases to numbers and ASCII letters. + start_mark = self.get_mark() + indicator = self.peek() + if indicator == u'*': + name = 'alias' + else: + name = 'anchor' + self.forward() + length = 0 + ch = self.peek(length) + while u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch.encode('utf-8'), self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in u'\0 \t\r\n\x85\u2028\u2029?:,]}%@`': + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch.encode('utf-8'), self.get_mark()) + end_mark = self.get_mark() + return TokenClass(value, start_mark, end_mark) + + def scan_tag(self): + # See the specification for details. + start_mark = self.get_mark() + ch = self.peek(1) + if ch == u'<': + handle = None + self.forward(2) + suffix = self.scan_tag_uri('tag', start_mark) + if self.peek() != u'>': + raise ScannerError("while parsing a tag", start_mark, + "expected '>', but found %r" % self.peek().encode('utf-8'), + self.get_mark()) + self.forward() + elif ch in u'\0 \t\r\n\x85\u2028\u2029': + handle = None + suffix = u'!' + self.forward() + else: + length = 1 + use_handle = False + while ch not in u'\0 \r\n\x85\u2028\u2029': + if ch == u'!': + use_handle = True + break + length += 1 + ch = self.peek(length) + handle = u'!' + if use_handle: + handle = self.scan_tag_handle('tag', start_mark) + else: + handle = u'!' + self.forward() + suffix = self.scan_tag_uri('tag', start_mark) + ch = self.peek() + if ch not in u'\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a tag", start_mark, + "expected ' ', but found %r" % ch.encode('utf-8'), + self.get_mark()) + value = (handle, suffix) + end_mark = self.get_mark() + return TagToken(value, start_mark, end_mark) + + def scan_block_scalar(self, style): + # See the specification for details. + + if style == '>': + folded = True + else: + folded = False + + chunks = [] + start_mark = self.get_mark() + + # Scan the header. + self.forward() + chomping, increment = self.scan_block_scalar_indicators(start_mark) + self.scan_block_scalar_ignored_line(start_mark) + + # Determine the indentation level and go to the first non-empty line. + min_indent = self.indent+1 + if min_indent < 1: + min_indent = 1 + if increment is None: + breaks, max_indent, end_mark = self.scan_block_scalar_indentation() + indent = max(min_indent, max_indent) + else: + indent = min_indent+increment-1 + breaks, end_mark = self.scan_block_scalar_breaks(indent) + line_break = u'' + + # Scan the inner part of the block scalar. + while self.column == indent and self.peek() != u'\0': + chunks.extend(breaks) + leading_non_space = self.peek() not in u' \t' + length = 0 + while self.peek(length) not in u'\0\r\n\x85\u2028\u2029': + length += 1 + chunks.append(self.prefix(length)) + self.forward(length) + line_break = self.scan_line_break() + breaks, end_mark = self.scan_block_scalar_breaks(indent) + if self.column == indent and self.peek() != u'\0': + + # Unfortunately, folding rules are ambiguous. + # + # This is the folding according to the specification: + + if folded and line_break == u'\n' \ + and leading_non_space and self.peek() not in u' \t': + if not breaks: + chunks.append(u' ') + else: + chunks.append(line_break) + + # This is Clark Evans's interpretation (also in the spec + # examples): + # + #if folded and line_break == u'\n': + # if not breaks: + # if self.peek() not in ' \t': + # chunks.append(u' ') + # else: + # chunks.append(line_break) + #else: + # chunks.append(line_break) + else: + break + + # Chomp the tail. + if chomping is not False: + chunks.append(line_break) + if chomping is True: + chunks.extend(breaks) + + # We are done. + return ScalarToken(u''.join(chunks), False, start_mark, end_mark, + style) + + def scan_block_scalar_indicators(self, start_mark): + # See the specification for details. + chomping = None + increment = None + ch = self.peek() + if ch in u'+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch in u'0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + elif ch in u'0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + ch = self.peek() + if ch in u'+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch not in u'\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected chomping or indentation indicators, but found %r" + % ch.encode('utf-8'), self.get_mark()) + return chomping, increment + + def scan_block_scalar_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == u' ': + self.forward() + if self.peek() == u'#': + while self.peek() not in u'\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in u'\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected a comment or a line break, but found %r" + % ch.encode('utf-8'), self.get_mark()) + self.scan_line_break() + + def scan_block_scalar_indentation(self): + # See the specification for details. + chunks = [] + max_indent = 0 + end_mark = self.get_mark() + while self.peek() in u' \r\n\x85\u2028\u2029': + if self.peek() != u' ': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + else: + self.forward() + if self.column > max_indent: + max_indent = self.column + return chunks, max_indent, end_mark + + def scan_block_scalar_breaks(self, indent): + # See the specification for details. + chunks = [] + end_mark = self.get_mark() + while self.column < indent and self.peek() == u' ': + self.forward() + while self.peek() in u'\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + while self.column < indent and self.peek() == u' ': + self.forward() + return chunks, end_mark + + def scan_flow_scalar(self, style): + # See the specification for details. + # Note that we loose indentation rules for quoted scalars. Quoted + # scalars don't need to adhere indentation because " and ' clearly + # mark the beginning and the end of them. Therefore we are less + # restrictive then the specification requires. We only need to check + # that document separators are not included in scalars. + if style == '"': + double = True + else: + double = False + chunks = [] + start_mark = self.get_mark() + quote = self.peek() + self.forward() + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + while self.peek() != quote: + chunks.extend(self.scan_flow_scalar_spaces(double, start_mark)) + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + self.forward() + end_mark = self.get_mark() + return ScalarToken(u''.join(chunks), False, start_mark, end_mark, + style) + + ESCAPE_REPLACEMENTS = { + u'0': u'\0', + u'a': u'\x07', + u'b': u'\x08', + u't': u'\x09', + u'\t': u'\x09', + u'n': u'\x0A', + u'v': u'\x0B', + u'f': u'\x0C', + u'r': u'\x0D', + u'e': u'\x1B', + u' ': u'\x20', + u'\"': u'\"', + u'\\': u'\\', + u'N': u'\x85', + u'_': u'\xA0', + u'L': u'\u2028', + u'P': u'\u2029', + } + + ESCAPE_CODES = { + u'x': 2, + u'u': 4, + u'U': 8, + } + + def scan_flow_scalar_non_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + length = 0 + while self.peek(length) not in u'\'\"\\\0 \t\r\n\x85\u2028\u2029': + length += 1 + if length: + chunks.append(self.prefix(length)) + self.forward(length) + ch = self.peek() + if not double and ch == u'\'' and self.peek(1) == u'\'': + chunks.append(u'\'') + self.forward(2) + elif (double and ch == u'\'') or (not double and ch in u'\"\\'): + chunks.append(ch) + self.forward() + elif double and ch == u'\\': + self.forward() + ch = self.peek() + if ch in self.ESCAPE_REPLACEMENTS: + chunks.append(self.ESCAPE_REPLACEMENTS[ch]) + self.forward() + elif ch in self.ESCAPE_CODES: + length = self.ESCAPE_CODES[ch] + self.forward() + for k in range(length): + if self.peek(k) not in u'0123456789ABCDEFabcdef': + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "expected escape sequence of %d hexdecimal numbers, but found %r" % + (length, self.peek(k).encode('utf-8')), self.get_mark()) + code = int(self.prefix(length), 16) + chunks.append(unichr(code)) + self.forward(length) + elif ch in u'\r\n\x85\u2028\u2029': + self.scan_line_break() + chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) + else: + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "found unknown escape character %r" % ch.encode('utf-8'), self.get_mark()) + else: + return chunks + + def scan_flow_scalar_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + length = 0 + while self.peek(length) in u' \t': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch == u'\0': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected end of stream", self.get_mark()) + elif ch in u'\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + breaks = self.scan_flow_scalar_breaks(double, start_mark) + if line_break != u'\n': + chunks.append(line_break) + elif not breaks: + chunks.append(u' ') + chunks.extend(breaks) + else: + chunks.append(whitespaces) + return chunks + + def scan_flow_scalar_breaks(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + # Instead of checking indentation, we check for document + # separators. + prefix = self.prefix(3) + if (prefix == u'---' or prefix == u'...') \ + and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected document separator", self.get_mark()) + while self.peek() in u' \t': + self.forward() + if self.peek() in u'\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + else: + return chunks + + def scan_plain(self): + # See the specification for details. + # We add an additional restriction for the flow context: + # plain scalars in the flow context cannot contain ',', ':' and '?'. + # We also keep track of the `allow_simple_key` flag here. + # Indentation rules are loosed for the flow context. + chunks = [] + start_mark = self.get_mark() + end_mark = start_mark + indent = self.indent+1 + # We allow zero indentation for scalars, but then we need to check for + # document separators at the beginning of the line. + #if indent == 0: + # indent = 1 + spaces = [] + while True: + length = 0 + if self.peek() == u'#': + break + while True: + ch = self.peek(length) + if ch in u'\0 \t\r\n\x85\u2028\u2029' \ + or (not self.flow_level and ch == u':' and + self.peek(length+1) in u'\0 \t\r\n\x85\u2028\u2029') \ + or (self.flow_level and ch in u',:?[]{}'): + break + length += 1 + # It's not clear what we should do with ':' in the flow context. + if (self.flow_level and ch == u':' + and self.peek(length+1) not in u'\0 \t\r\n\x85\u2028\u2029,[]{}'): + self.forward(length) + raise ScannerError("while scanning a plain scalar", start_mark, + "found unexpected ':'", self.get_mark(), + "Please check http://pyyaml.org/wiki/YAMLColonInFlowContext for details.") + if length == 0: + break + self.allow_simple_key = False + chunks.extend(spaces) + chunks.append(self.prefix(length)) + self.forward(length) + end_mark = self.get_mark() + spaces = self.scan_plain_spaces(indent, start_mark) + if not spaces or self.peek() == u'#' \ + or (not self.flow_level and self.column < indent): + break + return ScalarToken(u''.join(chunks), True, start_mark, end_mark) + + def scan_plain_spaces(self, indent, start_mark): + # See the specification for details. + # The specification is really confusing about tabs in plain scalars. + # We just forbid them completely. Do not use tabs in YAML! + chunks = [] + length = 0 + while self.peek(length) in u' ': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch in u'\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + self.allow_simple_key = True + prefix = self.prefix(3) + if (prefix == u'---' or prefix == u'...') \ + and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029': + return + breaks = [] + while self.peek() in u' \r\n\x85\u2028\u2029': + if self.peek() == ' ': + self.forward() + else: + breaks.append(self.scan_line_break()) + prefix = self.prefix(3) + if (prefix == u'---' or prefix == u'...') \ + and self.peek(3) in u'\0 \t\r\n\x85\u2028\u2029': + return + if line_break != u'\n': + chunks.append(line_break) + elif not breaks: + chunks.append(u' ') + chunks.extend(breaks) + elif whitespaces: + chunks.append(whitespaces) + return chunks + + def scan_tag_handle(self, name, start_mark): + # See the specification for details. + # For some strange reasons, the specification does not allow '_' in + # tag handles. I have allowed it anyway. + ch = self.peek() + if ch != u'!': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch.encode('utf-8'), + self.get_mark()) + length = 1 + ch = self.peek(length) + if ch != u' ': + while u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-_': + length += 1 + ch = self.peek(length) + if ch != u'!': + self.forward(length) + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch.encode('utf-8'), + self.get_mark()) + length += 1 + value = self.prefix(length) + self.forward(length) + return value + + def scan_tag_uri(self, name, start_mark): + # See the specification for details. + # Note: we do not check if URI is well-formed. + chunks = [] + length = 0 + ch = self.peek(length) + while u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' \ + or ch in u'-;/?:@&=+$,_.!~*\'()[]%': + if ch == u'%': + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + chunks.append(self.scan_uri_escapes(name, start_mark)) + else: + length += 1 + ch = self.peek(length) + if length: + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + if not chunks: + raise ScannerError("while parsing a %s" % name, start_mark, + "expected URI, but found %r" % ch.encode('utf-8'), + self.get_mark()) + return u''.join(chunks) + + def scan_uri_escapes(self, name, start_mark): + # See the specification for details. + bytes = [] + mark = self.get_mark() + while self.peek() == u'%': + self.forward() + for k in range(2): + if self.peek(k) not in u'0123456789ABCDEFabcdef': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected URI escape sequence of 2 hexdecimal numbers, but found %r" % + (self.peek(k).encode('utf-8')), self.get_mark()) + bytes.append(chr(int(self.prefix(2), 16))) + self.forward(2) + try: + value = unicode(''.join(bytes), 'utf-8') + except UnicodeDecodeError, exc: + raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark) + return value + + def scan_line_break(self): + # Transforms: + # '\r\n' : '\n' + # '\r' : '\n' + # '\n' : '\n' + # '\x85' : '\n' + # '\u2028' : '\u2028' + # '\u2029 : '\u2029' + # default : '' + ch = self.peek() + if ch in u'\r\n\x85': + if self.prefix(2) == u'\r\n': + self.forward(2) + else: + self.forward() + return u'\n' + elif ch in u'\u2028\u2029': + self.forward() + return ch + return u'' + +#try: +# import psyco +# psyco.bind(Scanner) +#except ImportError: +# pass + diff --git a/dist/lib/yaml/serializer.py b/dist/lib/yaml/serializer.py new file mode 100644 index 0000000..0bf1e96 --- /dev/null +++ b/dist/lib/yaml/serializer.py @@ -0,0 +1,111 @@ + +__all__ = ['Serializer', 'SerializerError'] + +from error import YAMLError +from events import * +from nodes import * + +class SerializerError(YAMLError): + pass + +class Serializer(object): + + ANCHOR_TEMPLATE = u'id%03d' + + def __init__(self, encoding=None, + explicit_start=None, explicit_end=None, version=None, tags=None): + self.use_encoding = encoding + self.use_explicit_start = explicit_start + self.use_explicit_end = explicit_end + self.use_version = version + self.use_tags = tags + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + self.closed = None + + def open(self): + if self.closed is None: + self.emit(StreamStartEvent(encoding=self.use_encoding)) + self.closed = False + elif self.closed: + raise SerializerError("serializer is closed") + else: + raise SerializerError("serializer is already opened") + + def close(self): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif not self.closed: + self.emit(StreamEndEvent()) + self.closed = True + + #def __del__(self): + # self.close() + + def serialize(self, node): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif self.closed: + raise SerializerError("serializer is closed") + self.emit(DocumentStartEvent(explicit=self.use_explicit_start, + version=self.use_version, tags=self.use_tags)) + self.anchor_node(node) + self.serialize_node(node, None, None) + self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + + def anchor_node(self, node): + if node in self.anchors: + if self.anchors[node] is None: + self.anchors[node] = self.generate_anchor(node) + else: + self.anchors[node] = None + if isinstance(node, SequenceNode): + for item in node.value: + self.anchor_node(item) + elif isinstance(node, MappingNode): + for key, value in node.value: + self.anchor_node(key) + self.anchor_node(value) + + def generate_anchor(self, node): + self.last_anchor_id += 1 + return self.ANCHOR_TEMPLATE % self.last_anchor_id + + def serialize_node(self, node, parent, index): + alias = self.anchors[node] + if node in self.serialized_nodes: + self.emit(AliasEvent(alias)) + else: + self.serialized_nodes[node] = True + self.descend_resolver(parent, index) + if isinstance(node, ScalarNode): + detected_tag = self.resolve(ScalarNode, node.value, (True, False)) + default_tag = self.resolve(ScalarNode, node.value, (False, True)) + implicit = (node.tag == detected_tag), (node.tag == default_tag) + self.emit(ScalarEvent(alias, node.tag, implicit, node.value, + style=node.style)) + elif isinstance(node, SequenceNode): + implicit = (node.tag + == self.resolve(SequenceNode, node.value, True)) + self.emit(SequenceStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + index = 0 + for item in node.value: + self.serialize_node(item, node, index) + index += 1 + self.emit(SequenceEndEvent()) + elif isinstance(node, MappingNode): + implicit = (node.tag + == self.resolve(MappingNode, node.value, True)) + self.emit(MappingStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + for key, value in node.value: + self.serialize_node(key, node, None) + self.serialize_node(value, node, key) + self.emit(MappingEndEvent()) + self.ascend_resolver() + diff --git a/dist/lib/yaml/tokens.py b/dist/lib/yaml/tokens.py new file mode 100644 index 0000000..4d0b48a --- /dev/null +++ b/dist/lib/yaml/tokens.py @@ -0,0 +1,104 @@ + +class Token(object): + def __init__(self, start_mark, end_mark): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in self.__dict__ + if not key.endswith('_mark')] + attributes.sort() + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +#class BOMToken(Token): +# id = '' + +class DirectiveToken(Token): + id = '' + def __init__(self, name, value, start_mark, end_mark): + self.name = name + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class DocumentStartToken(Token): + id = '' + +class DocumentEndToken(Token): + id = '' + +class StreamStartToken(Token): + id = '' + def __init__(self, start_mark=None, end_mark=None, + encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndToken(Token): + id = '' + +class BlockSequenceStartToken(Token): + id = '' + +class BlockMappingStartToken(Token): + id = '' + +class BlockEndToken(Token): + id = '' + +class FlowSequenceStartToken(Token): + id = '[' + +class FlowMappingStartToken(Token): + id = '{' + +class FlowSequenceEndToken(Token): + id = ']' + +class FlowMappingEndToken(Token): + id = '}' + +class KeyToken(Token): + id = '?' + +class ValueToken(Token): + id = ':' + +class BlockEntryToken(Token): + id = '-' + +class FlowEntryToken(Token): + id = ',' + +class AliasToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class AnchorToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class TagToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class ScalarToken(Token): + id = '' + def __init__(self, value, plain, start_mark, end_mark, style=None): + self.value = value + self.plain = plain + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + diff --git a/dist/src/config.py b/dist/src/config.py new file mode 100644 index 0000000..4882783 --- /dev/null +++ b/dist/src/config.py @@ -0,0 +1,20 @@ +''' +Not sure if this is the best name, but we need a place for getting the platform dir +that won't indirectly import cgui, as we need this to determine where to look for it. +''' + +import sys + +if sys.platform.startswith("win"): + platformName = "win" +elif sys.platform.startswith("darwin"): + platformName = "mac" +else: + platformName = "gtk" + +newMenubar = 0 +nativeIMWindow = (platformName == "mac") + + +platform = platformName # phase out camelCase + diff --git a/dist/src/deploy/__init__.py b/dist/src/deploy/__init__.py new file mode 100644 index 0000000..392e264 --- /dev/null +++ b/dist/src/deploy/__init__.py @@ -0,0 +1 @@ +from phases import * \ No newline at end of file diff --git a/dist/src/deploy/phases.py b/dist/src/deploy/phases.py new file mode 100644 index 0000000..58cf33b --- /dev/null +++ b/dist/src/deploy/phases.py @@ -0,0 +1,96 @@ +''' +Created on Apr 9, 2012 + +@author: "Michael Dougherty " +''' +import logging + +__all__ = ['phase', 'Clean', 'Checkout', 'Compile', 'Test', 'Freeze', 'Prepare', 'Verify', 'Package', 'Upload', 'Deploy'] + +class Strategy(type): + _strategies = {} + def __new__(cls, name, bases, dct): + new_cls = type.__new__(cls, name, bases, dct) + strat = getattr(new_cls, 'strategy') + if strat is not None: + phase = getattr(new_cls, 'phase') + Strategy._strategies.setdefault(phase, {})[strat] = new_cls + return new_cls + +def phase(phase, strategy, target, **options): + return Strategy._strategies[phase][strategy](target, **options) + +class DeploymentPhase(object): + __metaclass__ = Strategy + strategy = None + + def __init__(self, target, **options): + self.target = target + self.options = options + for key, val in options.items(): + setattr(self, key, val) + + def pre(self): + target_pre = getattr(self.target, 'pre_%s' % self.phase, None) + if target_pre is not None: + # Allow target to configure this for our phase + target_pre(self) + if self.strategy is not None: + target_pre_strat = getattr(self.target, 'pre_%s_%s' % (self.phase, self.strategy), None) + if target_pre_strat is not None: + # Allow target to configure this for our phase and strategy + target_pre_strat(self) + + print('*** starting %s (%s) ***' % (self.phase, self.strategy)) + + def do(self): + pass + + def post(self, *exc_info): + target_post = getattr(self.target, 'post_%s' % self.phase, None) + if target_post is not None: + # Allow target to configure this for our phase + target_post(self) + if self.strategy is not None: + target_post_strat = getattr(self.target, 'post_%s_%s' % (self.phase, self.strategy), None) + if target_post_strat is not None: + # Allow target to configure this for our phase and strategy + target_post_strat(self) + + def __enter__(self): + self.pre() + return self + + def __exit__(self, *exc_info): + if any(exc_info): + import traceback; traceback.print_exception(*exc_info) + return self.post() + +class Clean(DeploymentPhase): + phase = 'clean' +class Checkout(DeploymentPhase): + phase = 'checkout' +class Compile(DeploymentPhase): + phase = 'compile' +class Test(DeploymentPhase): + phase = 'test' +class Freeze(DeploymentPhase): + phase = 'freeze' +class Prepare(DeploymentPhase): + phase = 'prepare' +class Verify(DeploymentPhase): + phase = 'verify' +class Package(DeploymentPhase): + phase = 'package' +class Upload(DeploymentPhase): + phase = 'upload' +class Deploy(DeploymentPhase): + phase = 'deploy' + +if __name__ == '__main__': + class FakeUploader(Upload): + strategy = 'fake' + + print Strategy._strategies + print phase('upload', 'fake') + diff --git a/dist/src/digsby_dist.py b/dist/src/digsby_dist.py new file mode 100644 index 0000000..34687b3 --- /dev/null +++ b/dist/src/digsby_dist.py @@ -0,0 +1,646 @@ +''' +Created on Apr 9, 2012 + +@author: "Michael Dougherty " +''' +import os +import yaml +import sys +import uuid +import time +import email +import optparse +import distutils +import config +import path +import deploy +import helpers +import shutil + +DEBUG = hasattr(sys, 'gettotalrefcount') +SVN = os.environ.get('SVN_EXE', 'svn') + +class DigsbyClean(deploy.Clean): + strategy = 'digsby' + def do(self): + helpers.clean(self.paths) + +class SVNCheckout(deploy.Checkout): + strategy = 'svn' + revision = None + def do(self): + # TODO: make sure repo is clean + helpers.run(SVN, 'status', self.source) + helpers.run(SVN, 'update', self.source) + + svnoutput = helpers.run(SVN, 'info', self.source, verbose = False) + svninfo = dict(line.split(': ', 1) for line in filter(bool, svnoutput.splitlines())) + self.revision = int(svninfo['Last Changed Rev']) + + if self.options.get('dest'): + helpers.run(SVN, 'export', '-r', self.revision, self.source, self.dest) + + +class GitCheckout(deploy.Checkout): + strategy = 'git' + revision = None + + def do(self): + with helpers.cd(self.source): + branch = getattr(self, 'branch', 'master') + remote = getattr(self, 'remote', 'origin') + + git_status = helpers.run('git', 'status', '--porcelain', verbose = False).strip() + helpers.run('git', 'checkout', branch) + if git_status: + helpers.run('git', 'reset', '--hard', branch) + helpers.run('git', 'fetch', remote, branch) + helpers.run('git', 'merge', '--ff-only', '%s/%s' % (remote, branch)) + helpers.run('git', 'submodule', 'init') + helpers.run('git', 'submodule', 'update') + + self.revision = helpers.run('git', 'rev-parse', '--short', 'HEAD', verbose = False).strip() + + if self.options.get('dest'): + raise Exception("Haven't figured out the proper set of `git archive` commands yet") + + helpers.run('git', 'archive', '-o', self.options.get('dest') / ('%s.zip' % self.revision), branch) + with helpers.cd(self.options.get('dest')): + # TODO: this doesn't work. + helpers.run('unzip', 'archive.zip') + + +class MSVC(deploy.Compile): + strategy = 'msvc' + +class PyUnitTest(deploy.Test): + strategy = 'pyunittest' + def pre(self): + super(PyUnitTest, self).pre() + self._old_pypath = os.environ['PYTHONPATH'] + os.environ['PYTHONPATH'] = self.options.get('pythonpath', '') + + def do(self): + ## not valid in 2.6 + #helpers.run(sys.executable, '-m', 'unittest', 'discover', '-s', self.dest / 'digsby' / 'src' / 'tests') + helpers.run(sys.executable, self.dest / 'digsby' / 'src' / 'tests' / 'unittests' / 'runtests.py') + + def post(self): + os.environ['PYTHONPATH'] = self._old_pypath + super(PyUnitTest, self).post() + +class SanityTest(deploy.Test): + strategy = 'sanitycheck' + def do(self): + self.sanitycheck_win() + + def sanitycheck_win(self): + self.needed_dlls = [] + + assert os.path.isdir(self.path / 'installer') + assert os.path.isdir(self.launcher_exe.parent) + assert os.path.isfile(self.launcher_exe) + + self.msvc_devenv = path.path(os.environ.get('VS90COMNTOOLS', r'C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools')).abspath() + assert self.msvc_devenv.isdir() + + if not DEBUG: + self.redist_dirname = 'Microsoft.VC90.CRT' + self.msvc_crt = self.msvc_devenv / '..' / '..' / 'VC' / 'redist' / 'x86' / self.redist_dirname + self.debug_postfix = '' + else: + self.redist_dirname = 'Microsoft.VC90.DebugCRT' + self.msvc_crt = self.msvc_devenv / '..' / '..' / 'VC' / 'Debug_NonRedist' / 'x86' / self.redist_dirname + self.debug_postfix = 'd' + + self.needed_dlls.extend(['msvcp90%s.dll' % self.debug_postfix, + 'msvcr90%s.dll' % self.debug_postfix]) + + for f in self.needed_dlls: + f = self.msvc_crt / f + assert f.isfile() + +class CheckReleaseNotes(deploy.Prepare): + strategy = 'release_notes' + def do(self): + from buildutil.promptlib import prompt + if not prompt("Type 'blag' if you updated the blog post in res/release_notes.html", str).lower() == 'blag': + raise Exception('Update the blog post link in res/release_notes.html') + +class SignBinary(deploy.Prepare): + strategy = 'sign_binary' + + def do(self): + from buildutil.signing import Authenticode + Authenticode(self.path) + +class SignBinaryInstaller(deploy.Prepare): + strategy = 'sign_binary_installer' + +class DigsbyUpdaterPrep(deploy.Prepare): + strategy = 'digsby' + + def do(self): + "Do any finalizations before the installer is made" + # You'll want to remove unneeded files, set permissions etc + badfiles = [self.source / 'devmode.pyo', + self.source / 'package.pyo', + ] + + for badfile in badfiles: + badfile = badfile.expand() + if badfile.isfile(): + badfile.remove() + + if not self.dest.isdir(): + self.dest.makedirs() + if (self.source / 'lib').isdir(): + (self.source / 'lib').rename(self.dest / 'lib') + + if not (self.dest / 'lib').isdir(): + (self.dest / 'lib').makedirs() + + for fname in self.source.listdir(): + if fname.name != 'lib': + try: + fname.rename(self.dest / 'lib' / fname.name) + except: + print fname + raise + + self.target.launcher_exe.copy2(self.dest / 'digsby.exe') + self.target.copyresources(self.dest) + + print '*** generating manifest ***' + import __builtin__ + __builtins__._ = __builtin__._ = __builtins__.N_ = __builtin__.N_ = lambda s:s + + from plugins.digsby_updater.file_integrity import generate_manifest + man = generate_manifest(self.dest) + + with open(self.dest / 'manifest', 'wb') as f: + f.write(man) + + print '*** manifest is in %s ***' % (self.dest /'manifest') + +class DigsbyUpdaterVerify(deploy.Verify): + strategy = 'digsby' + + def do(self): + helpers.check_no_sources(self.path) + +class NSISInstaller(deploy.Package): + strategy = 'nsis' + tag = None + def installer_name_for_settings(self, nsis_opts): + build_id = nsis_opts.get('build_identifier') + instver = nsis_opts.get('INSTVER', 0) + if instver: + return 'digsby_setup_r%s_%s.exe' % (build_id, instver) + else: + return 'digsby_setup_r%s.exe' % (build_id) + + def pre(self): + super(NSISInstaller, self).pre() + from buildutil.promptlib import prompt + + self.tag = prompt('(optional) Please enter a tag for the installer:', str, None) + + if self.tag: + tag_path = self.source / 'tag.yaml' + tag_path.open('wb').write('tag: %s' % self.tag) + + nsis_defaults = dict(DISTDIR = str(self.source), + SVNREV = str(self.build_identifier)) + + nsis_options = {} + nsis_options.update(nsis_defaults) + nsis_options.update(self.options) + self.outfile = self.dest / self.installer_name_for_settings(nsis_options) + if not self.outfile.parent.isdir(): + self.outfile.parent.makedirs() + + with (self.script.parent / 'PyInfo.nsh').open('w') as f: + outfile_str = '"%s"' % self.outfile + f.write('OutFile %s\n' % outfile_str) + + for k, v in nsis_options.items(): + if v is None: + s = '!define %s\n' % (k,) + else: + s = '!define %s "%s"\n' % (k,v) + + f.write(s) + + def do(self): + with helpers.cd(self.script.parent): + helpers.run(self.path_nsis, '/V4', self.script) + + def post(self): + (self.script.parent / 'PyInfo.nsh').remove() + super(NSISInstaller, self).post() + +class MoveFiles(deploy.Prepare): + strategy = 'move_files' + def do(self): + if not self.dest_dir.isdir(): + self.dest_dir.makedirs() + self.source.rename(self.dest_dir / self.build_identifier) + +class YamlDeploy(deploy.Deploy): + strategy = 'yaml' + +default_blacklist = ['makedist', 'bot', + 'build.updatedeps', 'linecount', 'video', + 'gui.webcam', + 'common.MultiImage', 'common.shaped', + 'gui.gfxlist.gfxlist', 'gui.si4demo', + 'gui._splitimage4', 'gui.splitimage4', + 'util.ie', 'util.shared.testshared', + 'gui._ctextutil', 'gui.ctextutil', + 'gui.browser.iewindow', 'psyco', + 'thumbs.db', 'Thumbs.db', + 'gui.bugreporter', 'pylint', 'logilab', + 'tagtrunk', 'pyflakes', + 'python_twitter', 'spelling.dicts', + 'package', 'makedist', 'setup', 'tagtrunk', + 'jsTestDriver.conf', + + #'build.mac.build-deps', + lambda x: 'pg_dev' in x, + lambda x: '.svn' in x, + lambda x: '.git' in x, + lambda x: x.startswith('gui.native.'), + lambda x: 'devmode' in x, + lambda x: 'PPCRL' in x, + lambda x: 'uberdemos' in x, + lambda x: x.endswith('_setup'), + lambda x: 'tests' in x.lower(), + lambda x: 'otherskins' in x.lower(), + lambda x: x.endswith('.pdb'), + lambda x: 'rupture' in x, + lambda x: x.startswith('build'), + lambda x: 'SOAPpy' in x, + lambda x: 'fake' in x, + lambda x: 'fb20' in x, + lambda x: 'ms20' in x, + lambda x: 'devplugins' in x, + lambda x: 'BuddyList' in x, + lambda x: x.startswith('i18n'), + lambda x: x.startswith('src.jangle'), + lambda x: x.startswith('src.RTFToX'), + lambda x: x.startswith('lib.'), + lambda x: x.startswith('src.'), + lambda x: x.startswith('platlib.'), + ] + +windows_res_blacklist = [ + lambda x: '.svn' in x, + lambda x: '.git' in x, + lambda x: os.path.isdir(x) and x.startswith('mac'), + lambda x: os.path.isdir(x) and x == 'defaultmac', + lambda x: 'res\\skins\\native' in x, + ] + +class DigsbyDeployTarget(object): + disallowed_extensions = ['.py', '.pyc', '.cpp', '.c', '.h', '.erl', '.hrl', '.php', '.cs', '.pl', '.gitignore'] + verbose = False + has_updated_syspath = False + version = None + def __init__(self, location): + self.location = location + self.update_sys_path() + + def get_options(self, phase, strategy, options): + phase_updater = getattr(self, 'get_options_' + phase, None) + if phase_updater is not None: + options = phase_updater(options) + strategy_updater = getattr(self, 'get_options_%s_%s' % (phase, strategy), None) + if strategy_updater is not None: + options = strategy_updater(options) + return options + + def get_options_freeze_py2exe(self, options): + options['pythonpath'] = self.get_pythonpath() + import plat.win.manifest as manifest + manifest_resource = (24, 1, manifest.manifest) + for windows_options in options['distutils_options']['windows']: + if manifest_resource not in windows_options['other_resources']: + windows_options['other_resources'].append(manifest_resource) + + options['distutils_vals']['GUID'] = str(uuid.uuid3(uuid.NAMESPACE_DNS, options['distutils_vals']['URL'])) + options['distutils_vals']['VERSION'] = self.build_identifier() + options['distutils_vals']['BUILDDATETIME'] = email.Utils.formatdate(time.time(), True) + + options['distutils_options']['options']['py2exe']['includes'] = self.getallmodulenames() + + options['distutils_options'] = eval(helpers.dosubs(options['distutils_vals'], + repr(options['distutils_options'])), + {}, + dict(path = path.path)) + options['product_version'] = self.build_identifier() + return options + + def get_options_upload(self, options): + options['build_identifier'] = self.build_identifier() + return options + + def get_options_package_nsis(self, options): + #options['tag'] = ?? + options['build_identifier'] = self.build_identifier() + return options + + def get_options_prepare_sign_binary_installer(self, options): + options['path'] = self.install_package + return options + + def get_options_prepare_move_files(self, options): + options['build_identifier'] = self.build_identifier() + return options + + def get_options_deploy_updater(self, options): + options['build_identifier'] = self.build_identifier() + return options + + def get_options_deploy_installer(self, options): + options['build_identifier'] = self.build_identifier() + options['release_tags'] = self.tags + options['install_package'] = self.install_package + return options + + def post_deploy_updater(self, updater_deployer): + self.tags = updater_deployer.release_tags + + def post_package_nsis(self, nsis_builder): + self.install_package = nsis_builder.outfile + + def post_test_sanitycheck(self, sanitychecker): + # checker has needed DLL paths + self.msvc_crt = sanitychecker.msvc_crt + self.needed_dlls = sanitychecker.needed_dlls + self.redist_dirname = sanitychecker.redist_dirname + + def post_checkout_svn(self, svn_checkout): + self.version = svn_checkout.revision + + def post_checkout_git(self, git_checkout): + self.version = git_checkout.revision + + def build_identifier(self): + # return your version string + return str(self.version) + + def update_sys_path(self): + if self.has_updated_syspath: + return + + self.has_updated_syspath = True + for pth in ('.', 'src', 'lib', 'res', 'ext', 'build', + path.path('ext') / config.platformName, + path.path('platlib') / config.platformName): + sys.path.append((self.location / 'digsby' / pth).abspath()) + + sys.path.append(self.get_platlib_dir()) + + def get_platlib_dir(self, debug=DEBUG): + if debug is None: + debug = hasattr(sys, 'gettotalrefcount') + + return (self.location / 'platlib' / ('platlib_%s32_26%s' % (config.platformName, '_d' if debug else ''))).abspath() + + def get_pythonpath(self): + return os.pathsep.join(sys.path) + + def getallmodulenames_fromdir(self, root, prefix='', blacklist = default_blacklist): + pyfiles = [] + + # directories that will be on PYTHONPATH when running the program + pathdirs = ['src', '', 'ext',] + + for curdir, dirs, files in os.walk(root): + # TODO: this should eventually be unnecessary + if '.git' in root: + continue + if '.svn' in root: + continue + if '.git' in dirs: + dirs.remove('.git') + if '.svn' in dirs: + dirs.remove('.svn') + + for file in files: + if file.endswith('.py') or file.endswith('.pyc') or file.endswith('.pyd'): + pyfiles.append(path.path(curdir[len(root)+1:]) / file.rsplit('.')[0]) + + if prefix and not prefix.endswith('.'): + prefix = prefix + '.' + + modules = [] + for f in pyfiles: + f = f.replace('\\','.') + if f.endswith('.__init__'): + f = f[:-len('.__init__')] + + parts = f.split('.') + for pathdir in pathdirs: + if parts[0] == pathdir: + parts = parts[1:] + break + + f = '.'.join(parts) + + if not f: continue + + if self.blacklisted(f, blacklist): + print 'skipping %r because it\'s blacklisted' % f + continue + + if (prefix+f) not in modules: + modules.append(prefix+f) + + return modules + + def getallmodulenames(self): + import dns + import email + import ZSI + import PIL + import lxml + import pkg_resources + import peak + f = self.getallmodulenames_fromdir + + modules = ( + f(self.location / 'digsby') + + f(os.path.dirname(dns.__file__), 'dns') + + f(os.path.dirname(email.__file__), 'email') + + f(os.path.dirname(ZSI.__file__), 'ZSI') + + f(os.path.dirname(PIL.__file__), 'PIL') + + f(os.path.dirname(lxml.__file__), 'lxml') + + f(os.path.dirname(peak.__file__), 'peak') + ) + self.os_specific_modules() + + return modules + + def os_specific_modules(self): + return getattr(self, 'os_specific_modules_%s' % config.platform, lambda: [])() + + def os_specific_modules_win(self): + import gui.native.win as guiwin + f = self.getallmodulenames_fromdir + + return ( + f(os.path.dirname(guiwin.__file__), 'gui.native.win', []) + + f(os.path.abspath('./ext/win'), '') + + f(os.path.abspath('./platlib/win'), '') + ) + + def blacklisted(self, file, blacklist): + for test in blacklist: + if not callable(test): + filename = test + comparator = lambda a, b = filename: a == b + else: + comparator = test + if comparator(file): + return True + + return False + + def copyresources(self, prep): + "Copy other files to the destination directory" + + print '*** copying resources ***' + + ext = self.location / 'digsby' / 'ext' + lib = prep / 'lib' + + def strays(): + yield (ext / 'msw' / 'Digsby Updater.exe', lib) + yield (ext / 'msw' / 'Digsby PreUpdater.exe', lib) + # yield (self.repo.source_root / 'lib' /'digsby_updater.exe', lib) + yield (self.location / 'digsby' / 'lib' / 'digsby.dummy', lib) + + bad_exts = frozenset(self.disallowed_extensions) + plugins_dir = self.location / 'digsby' / 'src' / 'plugins' + + for plugin_info in plugins_dir.walk(): + if plugin_info.isfile() and \ + not self.blacklisted(plugin_info, default_blacklist) and \ + not plugin_info.ext.lower() in bad_exts: + target = lib / 'plugins' / plugins_dir.relpathto(plugin_info).parent + yield plugin_info, target + + for fname, dest in strays(): + fdst = (dest / fname.name) + + if not fdst.parent.isdir(): + fdst.parent.makedirs() + + fname.copy2(fdst) + if self.verbose: + print fdst + + def copy_stray_dir(start, destdir, ignore=[]): + pth = self.location / 'digsby' / start + destdir = path.path(destdir) + dst = destdir.abspath() / start + #print pth, '->', dst + _myblacklist = windows_res_blacklist+ignore + for file in pth.walk(): + if self.blacklisted(file, _myblacklist): + print 'skipping', file, 'because it\'s blacklisted' + continue + + rel = pth.relpathto(file) + fdst = dst / rel + if file.isdir() and not fdst.isdir(): + os.makedirs(fdst) + else: + if not fdst.parent.isdir(): + os.makedirs(fdst.parent) + shutil.copy2(file, fdst) + + # comtypes IE support needs a aux. type library file + import wx.lib.iewin # <-- its in this file's directory + + ole_tlb = path.path(wx.lib.iewin.__file__).parent / 'myole4ax.tlb' + ole_dest = lib / 'wx' / 'lib' + if self.verbose: + print 'copying %r to %r' % (ole_tlb, ole_dest) + if not ole_dest.isdir(): + ole_dest.makedirs() + ole_tlb.copy2(ole_dest) + + self.copy_certifi_resources(prep) + self.copy_msvc_binaries(prep) + + copy_stray_dir('lib/aspell', prep) + copy_stray_dir('res', prep) + + def copy_certifi_resources(self, prep): + # certifi has a .pem file, which is a list of CA certificates for browser-style authentication. + import certifi + certifi_dest = prep / 'lib' / 'certifi' + if not certifi_dest.isdir(): + certifi_dest.makedirs() + path.path(certifi.where()).copy2(certifi_dest) + + def copy_msvc_binaries(self, prep): + 'Copies MSVC redistributable files.' + + # Copy the two needed MSVC DLLs into Digsby/lib + for f in self.needed_dlls: + redist_src = self.msvc_crt / f + redist_dest = prep / 'lib' / f + redist_src.copy2(redist_dest) + + # Write a private assembly manifest into ./lib pointing at the DLLs + # in the same directory. + import plat.win.manifest as manifest + + with (prep / 'lib' / (self.redist_dirname + '.manifest')).open('w') as f: + f.write(manifest.msvc9_private_assembly % + ' '.join('' % dll for dll in self.needed_dlls)) + + @property + def launcher_exe(self): + return self.location / 'digsby' / 'ext' / 'msw' / 'DigsbyLauncher.exe' + +def do_deploy(repo, deploy_file, target = None): + repo = path.path(repo) + if target is None: + target = DigsbyDeployTarget(repo) + + def path_constructor(loader, node): + if node.id == 'sequence': + return repo.joinpath(*(loader.construct_sequence(node))).abspath() + elif node.id == 'scalar': + return (repo / loader.construct_scalar(node)).abspath() + + import digsby_phases + yaml.add_constructor('!path', path_constructor) + phases = yaml.load(open(deploy_file)) + + for phase in phases: + assert(len(phase) == 1) + phase_name, phase_parts = phase.items()[0] + for strat in phase_parts: + ((strategy_name, options),) = strat.items() + options = target.get_options(phase_name, strategy_name, options) + with deploy.phase(phase_name, strategy_name, target, **options) as phase: + phase.do() + + print('*** done ***') + + +def main(args): + opts, args = _option_parser.parse_args(args) + do_deploy(**vars(opts)) + +_option_parser = optparse.OptionParser(prog = 'makedist') +_option_parser.add_option('-r', '--repo', type = 'string', dest = 'repo') +_option_parser.add_option('-d', '--deploy', type = 'string', dest = 'deploy_file', default = 'deploy.yaml') + +if __name__ == '__main__': + main(sys.argv) diff --git a/dist/src/digsby_phases/__init__.py b/dist/src/digsby_phases/__init__.py new file mode 100644 index 0000000..67f3f0a --- /dev/null +++ b/dist/src/digsby_phases/__init__.py @@ -0,0 +1,3 @@ +from digsby_py2exe import Py2EXE +from digsby_s3upload import S3 +from digsby_deploy_yaml import DigsbyUpdaterDeploy \ No newline at end of file diff --git a/dist/src/digsby_phases/digsby_deploy_yaml.py b/dist/src/digsby_phases/digsby_deploy_yaml.py new file mode 100644 index 0000000..d63baed --- /dev/null +++ b/dist/src/digsby_phases/digsby_deploy_yaml.py @@ -0,0 +1,97 @@ +''' +Created on Apr 11, 2012 + +@author: "Michael Dougherty " +''' +import os +import urllib2 +import deploy +import random +import yaml +import config + +from digsby_s3upload import S3Uploader + +class DigsbyUpdaterDeploy(deploy.Deploy, S3Uploader): + strategy = 'updater' + def pre(self): + super(DigsbyUpdaterDeploy, self).pre() + for key, val in self.s3options.items(): + setattr(self, key, val) + + def do(self): + import util.net as net + from buildutil.promptlib import prompt + + updateyaml_data = None + abs_source = net.httpjoin(self.server, self.source) + while updateyaml_data is None: + try: + print 'Fetching', abs_source + res = urllib2.urlopen(abs_source) + except Exception, e: + tryagain = prompt("Error opening %r (e = %r). Try again?" % (abs_source, e), + bool, True) + if not tryagain: + break + else: + updateyaml_data = res.read() + + if updateyaml_data is None: + print 'Could not retrieve current update.yaml. This release has not been activated, you\'ll have to upload the update.yaml file yourself.' + return + + info = yaml.load(updateyaml_data) + currentplatname = config.platformName + + manifest_ext = '' + if currentplatname == 'mac': + manifest_ext = '.mac' + + new_manifest_url = net.httpjoin(self.server, self.build_identifier + '/' + 'manifest' + manifest_ext) + done = False + + while not done: + platname = prompt("Enter platform", str, currentplatname) + platdict = info.setdefault(platname, {}) + if platdict: + release_names = prompt('Enter releases to update', list, default = ['alpha']) + else: + release_names = [prompt('No releases present for platform %r. Enter new release name.' % platname, + str, default = 'release')] + + done = prompt('Release types %r for platform %r will be set to %r. Is this ok?' % (release_names, platname, new_manifest_url), + bool, False) + if not done: + if prompt('What next?', options = ['make changes', 'cancel'], default = 'make changes') == 'cancel': + return + + self.release_tags = release_names + for name in release_names: + platdict[name] = new_manifest_url + + new_yaml = yaml.dump(info) + try: + with open(self.source, 'wb') as f: + f.write(new_yaml) + + party_strings = ['party', 'soiree', 'extravaganza', + 'get-together', 'festival', 'celebration', + 'gala', 'shindig', 'hoe-down', 'fiesta', + 'mitzvah'] + + party = random.choice(party_strings) + should_upload = prompt('To upload %r to S3 and make this release (%r) live' % + (self.source, self.build_identifier), + 'confirm', party) + if not should_upload: + print 'Cancelling automated release. You\'ll have to upload update.yaml yourself.' + return + + self.upload_file_to_s3(self.source, compress = False) + finally: + if os.path.exists(self.source): + try: + os.remove(self.source) + except Exception: + pass diff --git a/dist/src/digsby_phases/digsby_py2exe.py b/dist/src/digsby_phases/digsby_py2exe.py new file mode 100644 index 0000000..af960b2 --- /dev/null +++ b/dist/src/digsby_phases/digsby_py2exe.py @@ -0,0 +1,123 @@ +''' +Created on Apr 10, 2012 + +@author: "Michael Dougherty " +''' +import os +import sys +import stat +import deploy +import distutils + +import py2exe.build_exe as build_exe +import py2exe.mf as mf + +build_exe.EXCLUDED_DLLS = \ + map(str.lower, build_exe.EXCLUDED_DLLS + \ + ('msvcp90d.dll', 'msvcr90d.dll', + 'usp10.dll', 'wldap32.dll', + 'WININET.dll', 'urlmon.dll', + 'Normaliz.dll', 'iertutil.dll', + 'API-MS-Win-Core-LocalRegistry-L1-1-0.dll'.lower(), + )) + +# We need to use a builder that doesnt put all the files into a single zip, +# and also doesn't choke on modules it can't find. + + +class mf_fixed(mf.ModuleFinder, object): + def import_hook(self, name, caller = None, fromlist = None, level = -1): + try: + super(mf_fixed, self).import_hook(name, caller, fromlist, level) + except ImportError: + self.missing_modules.add(name) + raise + else: + self.found_modules.add(name) + + +class py2exeSafe(build_exe.py2exe): + boolean_options = build_exe.py2exe.boolean_options + ['retain_times'] + _module_finder = None + + def initialize_options(self): + build_exe.py2exe.initialize_options(self) + self.retain_times = 0 + + def make_lib_archive(self, zip_filename, base_dir, files=None, verbose=0, dry_run=0): + if files is None: + files = [] + # Don't really produce an archive, just copy the files. + from distutils.dir_util import copy_tree + copy_tree(base_dir, os.path.dirname(zip_filename), + verbose=verbose, dry_run=dry_run) + return '.' + + def create_modulefinder(self): + from modulefinder import ReplacePackage + ReplacePackage('_xmlplus', 'xml') + py2exeSafe._module_finder = mf_fixed(excludes = self.excludes) + return py2exeSafe._module_finder + + def create_loader(self, item): + ## + ## Copied from build_exe.py2exe to add `if self.retain_times:` block + ## + + # Hm, how to avoid needless recreation of this file? + pathname = os.path.join(self.temp_dir, "%s.py" % item.__name__) + if self.bundle_files > 2: # don't bundle pyds and dlls + # all dlls are copied into the same directory, so modify + # names to include the package name to avoid name + # conflicts and tuck it away for future reference + fname = item.__name__ + os.path.splitext(item.__file__)[1] + item.__pydfile__ = fname + else: + fname = os.path.basename(item.__file__) + + # and what about dry_run? + if self.verbose: + print "creating python loader for extension '%s' (%s -> %s)" % (item.__name__, item.__file__, fname) + + source = build_exe.LOADER % fname + if not self.dry_run: + open(pathname, "w").write(source) + if self.retain_times: # Restore the times. + st = os.stat(item.__file__) + os.utime(pathname, (st[stat.ST_ATIME], st[stat.ST_MTIME])) + else: + return None + from modulefinder import Module + return Module(item.__name__, pathname) + + +class Py2EXE(deploy.Freeze): + strategy = 'py2exe' + + def pre(self): + super(Py2EXE, self).pre() + sys.setrecursionlimit(5000) + # Needed for byte-compiling to succeed + os.environ['PYTHONPATH'] = self.options.get('pythonpath') + + mf_fixed.missing_modules = set() + mf_fixed.found_modules = set() + + self.distutils_options.update({'cmdclass': {'py2exe' : py2exeSafe}}) + + with (self.source / 'srcrev.py').open('w') as f: + f.write('REVISION = %r' % self.options.get('product_version')) + + def do(self): + #import distutils.log + #distutils.log.set_verbosity(self.options.get('distutils_verbosity', 2)) + distutils.core.setup(**self.distutils_options) + + def post(self): + real_missing = (mf_fixed.missing_modules - mf_fixed.found_modules) - set(py2exeSafe._module_finder.modules.keys()) + if (real_missing): + print ('missing %d modules' % len(real_missing)) + for x in sorted(real_missing): + print '\t', x + + (self.source / 'srcrev.py').remove() diff --git a/dist/src/digsby_phases/digsby_s3upload.py b/dist/src/digsby_phases/digsby_s3upload.py new file mode 100644 index 0000000..2e89c6d --- /dev/null +++ b/dist/src/digsby_phases/digsby_s3upload.py @@ -0,0 +1,73 @@ +import sys +import deploy +import helpers + +class S3Uploader(object): + def upload_file_to_s3(self, f, compress = True, mimetypes = False): + return self.upload_to_s3(f, compress, False, mimetypes = mimetypes) + + def upload_dir_to_s3(self, directory, compress = True, mimetypes = False): + return self.upload_to_s3(directory, compress, True, mimetypes = mimetypes) + + def upload_to_s3(self, what, compress, recursive, mimetypes = False): + args = [] + if getattr(self, 'dry_run', False): + args.append('--dry-run') + if recursive: + args.append('--recursive') + if compress: + args.append('--compress-all') + args.extend('-c %s' % ext for ext in ('png', 'jpg', 'zip')) + + if mimetypes: + args.append('--mimetypes') + + helpers.run(sys.executable, self.script, what, + "--key", self.access, + '--secret', self.secret, + '--bucket', self.bucket, + '--public-read', + '--time', + '--verbose', + *args + ) + +class S3(deploy.Upload, S3Uploader): + strategy = 's3' + def do(self): + with helpers.cd(self.path): + self.upload_dir_to_s3(self.build_identifier) + + print '*** done uploading ***' + +class InstallerDeploy(deploy.Deploy, S3Uploader): + strategy = 'installer' + cancel = False + + def pre(self): + super(InstallerDeploy, self).pre() + from buildutil.promptlib import prompt + + plain_installer = self.install_package + + if not (self.path / 'install').isdir(): + (self.path / 'install').makedirs() + + for name in self.release_tags: + if name == 'release': + plain_installer.copyfile(self.path / 'install' / 'digsby_setup.exe' % name) + else: + plain_installer.copyfile(self.path / 'install' / ('digsby_setup_%s.exe' % name)) + + plain_installer.copyfile(self.path / 'install' / plain_installer.name) + + if not prompt("Upload installers?", bool, True): + self.cancel = True + + def do(self): + if self.cancel: + return + + with helpers.cd(self.path): + self.upload_dir_to_s3('install', compress=False, mimetypes=True) + diff --git a/dist/src/helpers.py b/dist/src/helpers.py new file mode 100644 index 0000000..fbafadd --- /dev/null +++ b/dist/src/helpers.py @@ -0,0 +1,143 @@ +from path import path +import os +import re +import logging +log = logging.getLogger('helpers') +import subprocess +from contextlib import contextmanager + +def run(*args, **kwargs): + """Execute the command. + + The path is searched""" + verbose = kwargs.get("verbose", True) + args = map(str, args) + if verbose: + log.debug('run: args = %r', args) + + if not verbose: + popen_kwargs = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + else: + popen_kwargs = {} + + p = subprocess.Popen(args, **popen_kwargs) + + stdout, stderr = p.communicate() + + if verbose: + log.debug('run: stdout = %r', stdout) + log.debug('run: stderr = %r', stderr) + log.debug('run: status = %r', p.returncode) + + if p.returncode: + if not verbose: + logging.debug('run: args = %r', args) + logging.debug('run: status = %r', p.returncode) + raise Exception("The command failed") + + return stdout + +def clean(dirs): + """Remove temporary directories created by various packaging tools""" + for d in dirs: + if d.isdir(): + d.rmtree() + +def dosubs(subs, infile, outfile=None): + """Performs substitutions on a template file or string + + @param infile: filename to read or string + @param outfile: filename to write result to or None if infile is a string + @type subs: dict + @param subs: the substitutions to make + """ + if outfile is None: + stuff=infile + else: + stuff=open(infile, "rt").read() + + for k in subs: + stuff=re.sub("%%"+k+"%%", "%s" % (subs[k],), stuff) + + if outfile: + open(outfile, "w").write(stuff) + else: + return stuff + +@contextmanager +def timed(name=''): + 'Shows the time something takes.' + + from time import time + + before = time() + try: + yield + finally: + msg = 'took %s secs' % (time() - before) + if name: + msg = name + ' ' + msg + logging.info(msg) + +@contextmanager +def cd(*pth): + ''' + chdirs to path, always restoring the cwd + + >>> with cd('mydir'): + >>> do_stuff() + ''' + original_cwd = os.getcwd() + try: + new_cwd = path.joinpath(*pth) + #inform('cd %s' % os.path.abspath(new_cwd)) + os.chdir(new_cwd) + yield + finally: + #inform('cd %s' % os.path.abspath(original_cwd)) + os.chdir(original_cwd) + +class Storage(dict): + """ + A Storage object is like a dictionary except `obj.foo` can be used + instead of `obj['foo']`. Create one by doing `storage({'a':1})`. + + Setting attributes is like putting key-value pairs in too! + + (Thanks web.py) + """ + def __getattr__(self, key, ga = dict.__getattribute__, gi = None): + try: + return ga(self, key) + except AttributeError: + try: + if gi is not None: + return gi(self, key) + else: + return self[key] + except KeyError: + msg = repr(key) + if len(self) <= 20: + keys = sorted(self.keys()) + msg += '\n (%d existing keys: ' % len(keys) + str(keys) + ')' + raise AttributeError, msg + + def __setattr__(self, key, value): + self[key] = value + + def copy(self): + return type(self)(self) + + +disallowed_extensions = [e.lower() for e in + ['.py', '.pyc', '.cpp', '.c', '.h', '.erl', '.hrl', '.php', '.cs', '.pl', '.gitignore']] + +def check_no_sources(distdir): + 'Make sure there are no source files in distdir.' + + + for root, dirs, files in os.walk(distdir): + for file in files: + for ext in disallowed_extensions: + if file.lower().endswith(ext): + raise AssertionError('found a %s file in %s: %s' % (ext, distdir, os.path.join(root, file))) diff --git a/dist/src/plat/__init__.py b/dist/src/plat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dist/src/plat/win/__init__.py b/dist/src/plat/win/__init__.py new file mode 100644 index 0000000..44cb6a3 --- /dev/null +++ b/dist/src/plat/win/__init__.py @@ -0,0 +1 @@ +from build import builder diff --git a/dist/src/plat/win/build.py b/dist/src/plat/win/build.py new file mode 100644 index 0000000..324fc0f --- /dev/null +++ b/dist/src/plat/win/build.py @@ -0,0 +1,100 @@ +import distutils +from path import path +from helpers import clean, dosubs +from . import manifest + + +def builder(pkg, vals): + 'Do all the steps necessary to make a Windows installer.' + import py2exe + defaults = { + 'options': {'py2exe': {'optimize': 2, + 'compressed': 1, + 'dist_dir': pkg.repo.dist_prep_root}}, + 'windows': [], + 'console': [], + 'script_args': ('py2exe', ), + 'verbose': False, + } + + opts = pkg.getpy2exeoptions(defaults) + + # add in manifests if not already specified + w = [] + for i in opts['windows']: + # insert manifest at begining - user version will override if supplied + i['other_resources'] = [(manifest.RT_MANIFEST, 1, manifest.WINXP_MANIFEST)] + i.get('other_resources', []) + + # set other values if not provided + for k, v in (('icon_resources', [(1, pkg.repo.source_root / 'res' / 'digsby.ico')]), + ('product_version', '%%VERSION%%'), + ('version', '%%DQVERSION%%'), + ('comments', '%%COMMENTS%%'), + ('company_name', "%%URL%%"), + ('copyright', "%%COPYRIGHT%%"), + ('name', "%%NAME%%"), + ('description', "%%DESCRIPTION%%")): + if k not in i: + i[k] = v + w.append(i) + + opts['windows'] = w + + # fixup vals for any that are missing + if 'RELEASE' not in vals: + vals['RELEASE'] = 0 + if 'OUTFILE' not in vals: + if vals['RELEASE']: + rstr = '-r' + repr(vals['RELEASE']) + "-" + else: + rstr = "" + vals['OUTFILE'] = "%s%s-%s-%ssetup.exe" % (vals['OUTFILEPREFIX'], vals['NAME'].lower(), vals['VERSION'], rstr) + + opts = eval(dosubs(vals, repr(opts))) + pkg.pre_setup(opts) + # run py2exe + distutils.core.setup(**opts) + + pkg.post_setup() + # copy resources + + distdir = opts['options']['py2exe']['dist_dir'] + pkg.copyresources(distdir) + pkg.finalize(distdir) + pkg.verify_output(distdir) + #check_no_sources(distdir) + + # make the innosetup package + vals['GUID'] = vals['GUID'].replace("{", "{{") # needed for innosetup + # must not supply .exe on end to innosetup + while vals['OUTFILE'].endswith(".exe"): + vals['OUTFILE']=vals['OUTFILE'][:-4] + + if 'INSTALLER' in vals: + for installer_options in vals['INSTALLER']: + inst = installer.make(pkg, pkg.repo.installer_root, installer_options) + inst.prep() + inst.do() + inst.post() + + else: + print '*** no installer key present, skipping installer build ***' + # TODO: move files from 'prep' to folder with revision id + if pkg.upload_files: + dir_to_upload = pkg.repo.dist_output_root + if hasattr(pkg, 'prep_upload'): + dir_to_upload = pkg.prep_upload() + + for uploader_options in vals['UPLOADER']: + upl = uploader.make(pkg, dir_to_upload, uploader_options) + upl.prep() + upl.do() + upl.post() + + if hasattr(pkg, 'post_upload'): + pkg.post_upload() + + else: + print '*** upload disabled ***' + + print '*** windowsbuild finished ***' diff --git a/dist/src/plat/win/manifest.py b/dist/src/plat/win/manifest.py new file mode 100644 index 0000000..a1dffc3 --- /dev/null +++ b/dist/src/plat/win/manifest.py @@ -0,0 +1,114 @@ +import sys + +RT_MANIFEST = 24 +DEBUG = hasattr(sys, 'gettotalrefcount') +if DEBUG: + WINXP_MANIFEST = '''\ + + + + + WA59/AbhIDaZgmB1cN9MFlDi2g4= Rxofhw9S95owIW2MDmCg2zStQgs= iOxEMZK3fufMDQFuKGZ4VdAYzWY= +''' +else: + WINXP_MANIFEST = ''' + + + + Program + + + + + + + ''' + + + +if DEBUG: + manifest = '''\ + + + + + WA59/AbhIDaZgmB1cN9MFlDi2g4= Rxofhw9S95owIW2MDmCg2zStQgs= iOxEMZK3fufMDQFuKGZ4VdAYzWY= + + + + + + + +''' + + msvc9_private_assembly = '''\ + + + + + %s +''' +else: + manifest = ''' + ''' + manifest += ''' + + + + + ''' + manifest+=''' + + + + + ''' + + msvc9_private_assembly = '''\ + + + + + + %s + ''' + diff --git a/dist/src/plat/win/nsis_installer.py b/dist/src/plat/win/nsis_installer.py new file mode 100644 index 0000000..a1c76ba --- /dev/null +++ b/dist/src/plat/win/nsis_installer.py @@ -0,0 +1,86 @@ +''' +Created on Apr 9, 2012 + +@author: "Michael Dougherty " +''' +import path +import helpers + +import installer +class NSISInstallerBuilder(installer.InstallerBuilder): + def __init__(self, pkg, prep, source, options): + self.package = pkg + self.content_dir = prep + self.installer_script = path.path(source) # self.repo.installer_root / 'DigsbyInstall.nsi' + self.options = options + ''' + options = dict( + foldername = 'install', + honest = True, + use_no_adware_image = True, + instver = 7, + offers = dict(), + ), + ''' + + self.nsis_exe = pkg.repo.installer_root / 'NSIS' / 'makensis.exe' + self.content_dir = pkg.repo.dist_prep_root + self.output_dir = pkg.repo.dist_output_root / 'install' + + def prep(self): + + print '*** prepping for NSIS installer creation ***' + tag = self.options.get('tag', None) + if tag is not None: + tagfile = path.path(self.content_dir) / 'tag.yaml' + open(tagfile, 'wb').write('tag: %s' % tag) + + nsis_defaults = dict(DISTDIR = self.content_dir, + SVNREV = self.package.get_source_rev(), + DIGSBY_INSTALLER_DIR = self.package.repo.installer_root) + + nsis_options = {} + nsis_options.update(nsis_defaults) + nsis_options.update(self.options) + self.all_options = nsis_options + + with (path.path(self.package.repo.installer_root) / 'PyInfo.nsh').open('w') as f: + outfile_str = '"%s"' % (self.output_dir / installer_name_for_settings(nsis_options, nsis_options['SVNREV'])) + f.write('OutFile %s\n' % outfile_str) + + for k, v in nsis_options.items(): + if v is None: + s = '!define %s\n' % (k,) + else: + s = '!define %s "%s"\n' % (k,v) + + f.write(s) + + if not self.output_dir.isdir(): + self.output_dir.mkdir() + + def do(self): + with helpers.cd(self.installer_script.parent): + print '*** running makensis with options %r ***' % (self.options,) + helpers.run(self.nsis_exe, '/V4', self.installer_script) + + def post(self): + tagfile = path.path(self.content_dir) / 'tag.yaml' + if tagfile.isfile(): + tagfile.remove() + + installer_path = self.output_dir / installer_name_for_settings(self.all_options, self.all_options['SVNREV']) + + # Sign installer + self.package.sign_binary(installer_path) + + installer_path.copyfile(installer_path.parent / 'digsby_setup.exe') + +def installer_name_for_settings(nsis_opts, svnrev): + instver = nsis_opts.get('INSTVER', 0) + if instver: + return 'digsby_setup_r%d_%d.exe' % (svnrev, instver) + else: + return 'digsby_setup_r%d.exe' % (svnrev) + +installer.InstallerBuilder._builders['nsis'] = NSISInstallerBuilder diff --git a/installer/DigsbyIni.nsh b/installer/DigsbyIni.nsh new file mode 100644 index 0000000..a70a9dc --- /dev/null +++ b/installer/DigsbyIni.nsh @@ -0,0 +1,143 @@ +;------------------------- +;-- Gui +;------------------------- + +Function WriteIni + ; Writes the INI files for gui, to be read later. + WriteINIStr "$PLUGINSDIR\register.ini" "Settings" "NumFields" "11" + ;WriteINIStr "$PLUGINSDIR\register.ini" "Settings" "TimeOut" "500" + + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Type" "RadioButton" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Text" "Existing Digsby user" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Left" "5" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Right" "120" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Top" "3" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Bottom" "16" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "State" "0" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Flags" "GROUP" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 1" "Notify" "ONCLICK" + + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Type" "RadioButton" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Text" "New Digsby user" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "State" "1" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Left" "5" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Right" "120" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Top" "18" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Bottom" "30" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Flags" "NOTIFY" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 2" "Notify" "ONCLICK" + +;; Invite Code +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Type" "Text" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "State" "" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Left" "104" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Right" "228" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Top" "39" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Bottom" "51" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Flags" "" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Notify" "ONTEXTCHANGE" + +;; Username + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Type" "Text" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "State" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Left" "104" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Right" "228" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Top" "55" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Bottom" "67" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Flags" "GROUP" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 3" "Notify" "ONTEXTCHANGE" + +;; Email + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "Type" "Text" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "State" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "Left" "104" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "Right" "228" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "Top" "71" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "Bottom" "83" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "Flags" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 4" "Notify" "ONTEXTCHANGE" + +;; PW 1 + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "Type" "Password" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "State" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "Left" "104" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "Right" "228" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "Top" "87" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "Bottom" "99" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "Flags" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 5" "Notify" "ONTEXTCHANGE" + +;; PW 2 + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "Type" "Password" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "State" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "Left" "104" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "Right" "228" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "Top" "103" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "Bottom" "114" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "Flags" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 6" "Notify" "ONTEXTCHANGE" + + WriteINIStr "$PLUGINSDIR\register.ini" "Field 7" "Type" "Label" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 7" "Text" "Digsby Username:" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 7" "Left" "44" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 7" "Right" "102" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 7" "Top" "56" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 7" "Bottom" "68" + + WriteINIStr "$PLUGINSDIR\register.ini" "Field 8" "Type" "Label" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 8" "Text" "Your Email Address:" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 8" "Left" "38" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 8" "Right" "102" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 8" "Top" "72" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 8" "Bottom" "80" + + WriteINIStr "$PLUGINSDIR\register.ini" "Field 9" "Type" "Label" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 9" "Text" "Create Password:" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 9" "Left" "44" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 9" "Right" "103" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 9" "Top" "88" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 9" "Bottom" "96" + + WriteINIStr "$PLUGINSDIR\register.ini" "Field 10" "Type" "Label" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 10" "Text" "Verify Password:" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 10" "Left" "48" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 10" "Right" "103" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 10" "Top" "104" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 10" "Bottom" "112" + +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Type" "CheckBox" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Text" "I am at least 13 years of age" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "State" "0" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Flags" "" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Top" "115" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Left" "104" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Right" "224" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Bottom" "127" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 12" "Notify" "ONCLICK" + + WriteINIStr "$PLUGINSDIR\register.ini" "Field 11" "Type" "Label" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 11" "Text" "" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 11" "Left" "80" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 11" "Right" "224" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 11" "Top" "130" + WriteINIStr "$PLUGINSDIR\register.ini" "Field 11" "Bottom" "142" + +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 13" "Type" "Label" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 13" "Text" "Invite Code:" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 13" "Left" "62" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 13" "Right" "102" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 13" "Top" "40" +; WriteINIStr "$PLUGINSDIR\register.ini" "Field 13" "Bottom" "48" + + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "connection" "Could not connect to Digsby server." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "user_regex" "Invalid username. Please only use letters and numbers." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "email_regex" "That does not appear to be a valid email address." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "pw_length" "Your password must be between 5 and 32 characters in length." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "age_req" "We're sorry, but you must be at least 13 years old to use this software." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "username_or_email_taken" "That username or email has already been registered." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "server_error" "There was a server error. Please select $\"Existing user$\" and create an account after the installation." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "not_approved" "The invite code you entered is either invalid or expired." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "tospp_req" "You must agree to the Terms of Service and Privacy Policy." + WriteINIStr "$PLUGINSDIR\errorcodes.ini" "msgs" "outofdate" "This installer is out of date. You should get the latest from http://www.digsby.com" + +FunctionEnd \ No newline at end of file diff --git a/installer/DigsbyInstall.nsi b/installer/DigsbyInstall.nsi new file mode 100644 index 0000000..59cbb42 --- /dev/null +++ b/installer/DigsbyInstall.nsi @@ -0,0 +1,770 @@ +SetCompressor /SOLID /FINAL lzma + +!addincludedir "NSIS\Include" +!addplugindir "NSIS\Plugins" + +!include "PyInfo.nsh" + +!ifdef USE_OPENCANDY + RequestExecutionLevel admin +!else + RequestExecutionLevel user +!endif + +# HM NIS Edit Wizard helper defines +!define HTTPS "https" +!define PRODUCT_NAME "Digsby" +!define PRODUCT_VERSION "1.0" +!define PRODUCT_PUBLISHER "Tagged, Inc" +!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" +!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "${PRODUCT_UNINST_KEY}" +!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "InstallPath" + +BrandingText "Digsby r${SVNREV}" +InstallButtonText "&Next >" +AutoCloseWindow true + +# MUI 1.67 compatible ------ +!include "System.nsh" +!include "LogicLib.nsh" +!include "DetailPrint.nsh" +!include "WinMessages.nsh" +!include "TextFunc.nsh" +!include "OpenLinkInBrowser.nsh" +!include "digsbyutil.nsh" + +!define MULTIUSER_MUI +!define MULTIUSER_EXECUTIONLEVEL Highest +!define MULTIUSER_INSTALLMODE_INSTDIR_ALLUSERS ${PRODUCT_NAME} +!define MULTIUSER_INSTALLMODE_INSTDIR_CURRENT "${PRODUCT_NAME}\App" +!include "MultiUser.nsh" + +!define FLAG_SILENT 1 # Installer run in silent mode? +!define FLAG_SETHOME 2 # did the user set their home page? +!define FLAG_SET_DIGSBYSEARCH 4 # Did they set the digsby search provider +!define FLAG_AMAZON 8 # did the user select the amazon/ebay search options + +!define FLAG_OFFER_ASK 1 +!define FLAG_OFFER_ACCEPT 2 +!define FLAG_OFFER_SUCCESS 4 + +!define VARNAME_FLAG "f" # flags mentioned above +!define VARNAME_PAGE "p" # what page number we finish on +!define VARNAME_INSTVER "v" # the version of the installer +!define VARNAME_DIGSVER "rev" # the SVN revision of digsby +!define VARNAME_EXITPAGE "e" +!define VARNAME_OFFERS_SHOWN "os" +!define VARNAME_OFFER_RESULTS "or" + +!insertmacro LineRead + +# MUI Settings +!define DIGSINST "${DIGSBY_INSTALLER_DIR}" +!define DIGSRES "${DIGSINST}\res" + +#!include "Offer-OpenCandy.nsh" +#!include "Offer-Bing.nsh" +#!include "Offer-Bccthis.nsh" +#!include "Offer-Xobni.nsh" +#!include "Offer-AskToolbar.nsh" +#!include "Offer-Crawler.nsh" +#!include "Offer-Inbox.nsh" +#!include "Offer-Babylon.nsh" +#!include "Offer-Uniblue.nsh" +#!include "Offer-Aro.nsh" + +!include "Page1-Welcome.nsh" +!include "Page5-Finish.nsh" + +!ifndef INSTVER + !define INSTVER_REPLACE 1 + !define INSTVER "" +!else + !define INSTVER_REPLACE "" +!endif + +!define SILENT_VERSION_NUMBER ${INSTVER} # this is the version the installer sends IfSilent + +${REDEF} MUI_FINISHPAGE_V_OFFSET -12 + +!define MUI_ICON "${DIGSRES}\digsby.ico" +!define MUI_UNICON "${DIGSRES}\digsby.ico" + +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_RIGHT + +!define MUI_HEADERIMAGE_BITMAP "${DIGSRES}\new_right_header.bmp" +!define MUI_HEADERIMAGE_UNBITMAP "${DIGSRES}\new_right_header.bmp" + +!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${DIGSRES}\wizard-un.bmp" + +!define DIGSBY_FINISH_NEXT_TEXT "Launch" +!define DIGSBY_FINISH_CANCEL_DISABLED +!define DIGSBY_FINISH_BACK_DISABLED + +!define DIGSBY_FINISH_LAUNCHCHECK_TEXT "&Launch Digsby when my computer starts" +!define DIGSBY_FINISH_PLURALINK_TEXT "?" +!define DIGSBY_FINISH_PLURACHECK_TEXT "Allow Digsby to use idle CPU time for grid computing [ ]" +!define DIGSBY_FINISH_SEARCHCHECK_TEXT "Make Google Powered Digsby Search my &search engine" +!define DIGSBY_FINISH_GOOGLEHOMECHECK_TEXT "Make Google Powered Digsby Search my &home page" +!define DIGSBY_FINISH_PLURALINK_ONCLICK PluraLearnMoreLink +!define DIGSBY_FINISH_LAUNCHCHECK_FUNC AddStartupShortcut +!define DIGSBY_FINISH_PLURACHECK_FUNC EnablePlura +!define DIGSBY_FINISH_SEARCHCHECK_FUNC AddGoogleSearchEngine +!define DIGSBY_FINISH_GOOGLEHOMECHECK_FUNC AddGoogleHomePage + +!define DIGSBY_FINISH_LAUNCH_STARTCHECKED +!define DIGSBY_FINISH_PLURA_STARTCHECKED +!define DIGSBY_FINISH_SEARCH_STARTCHECKED +!define DIGSBY_FINISH_GOOGLE_STARTCHECKED + +!ifndef DIGSBY_REGISTRATION_IN_INSTALLER + !define MUI_BUTTONTEXT_FINISH "Launch" +!endif + +Var FinishFlags +Var LastSeenpage +Var NumPagesVisited +Var RegisteredAccount +Var HomePageSet +Var SearchPageSet +Var IsPortable + +Var PostUrl + +!define PLURA_ENABLED_FLAG "--set-plura-enabled" +!define PLURA_DISABLED_FLAG "--set-plura-disabled" +Var PluraCommandString + +!define DIGSBY_METRIC_VARS_DEFINED + +!insertmacro DIGSBY_WELCOME_INIT +!insertmacro DIGSBY_FINISH_INIT + +${REDEF} _UN_ "" + +!include "DigsbyIni.nsh" +!include "DigsbyRegister.nsh" + +!define FLAG_PLURAPAGE_LEARN 8 +Function EnablePlura + StrCpy $PluraCommandString ${PLURA_ENABLED_FLAG} +FunctionEnd + +Function PluraLearnMoreLink + IntOp $PluraPage.Results $PluraPage.Results | ${FLAG_PLURAPAGE_LEARN} + ${OpenLinkNewWindow} "${PLURA_LEARNMORE_URL}" +FunctionEnd + +Function .onInit + StrCpy $LastSeenPage "" + !insertmacro MULTIUSER_INIT + !insertmacro DIGSBY_WELCOME_VARS_INIT + !insertmacro DIGSBY_FINISH_VARS_INIT + IntOp $FinishFlags 0 + 0 + IntOp $NumPagesVisited 0 + 0 + IntOp $RegisteredAccount 0 + 0 + IntOp $HomePageSet 0 + 0 + IntOp $SearchPageSet 0 + 0 + + !ifdef PAGE_NAME_XOBNI + Call XobniPage.InitVars + !endif + + !ifdef PAGE_NAME_ASK + Call AskPage.InitVars + !endif + + !ifdef PAGE_NAME_BING + Call BingPage.InitVars + !endif + + !ifdef PAGE_NAME_BCCTHIS + Call BccthisPage.InitVars + !endif + + !ifdef PAGE_NAME_BABYLON + Call BabylonPage.InitVars + !endif + + !ifdef PAGE_NAME_UNIBLUE + Call UnibluePage.InitVars + !endif + + !ifdef PAGE_NAME_ARO + Call AroPage.InitVars + !endif + + !ifdef PAGE_NAME_CRAWLER + Call CrawlerPage.InitVars + !endif + + !ifdef PAGE_NAME_INBOX + Call InboxPage.InitVars + !endif + + !ifdef PAGE_NAME_CAUSES + Call CausesPage.InitVars + !endif + + !ifdef PAGE_NAME_PLURA + Call PluraPage.InitVars + !endif + + !ifdef PAGE_NAME_OPENCANDY + Call OpencandyPage.InitVars + !endif + + StrCpy $PluraCommandString ${PLURA_DISABLED_FLAG} + StrCpy $user_status "None" + StrCpy $IsPortable "False" + + InitPluginsDir + GetTempFileName $0 + Rename $0 "$PLUGINSDIR\register.ini" + + GetTempFileName $0 + Rename $0 "$PLUGINSDIR\errorcodes.ini" + + IfSilent 0 next + Call EnablePlura + IntOp $FinishFlags $FinishFlags | ${FLAG_SILENT} + + next: + Call WriteIni # From DigsbyIni +FunctionEnd + +!macro POST_METRICS + !ifndef SKIP_METRICS_POST + !ifdef INSTALLER_PROGRESS_URL + StrCpy $PostUrl "${INSTALLER_PROGRESS_URL}?" + StrCpy $PostUrl "$PostUrl${VARNAME_PAGE}=$NumPagesVisited" + StrCpy $PostUrl "$PostUrl&${VARNAME_DIGSVER}=${SVNREV}" + IfSilent 0 notsilent + StrCpy $PostUrl "$PostUrl&${VARNAME_INSTVER}=${SILENT_VERSION_NUMBER}" + Goto nextvar + notsilent: + StrCpy $PostUrl "$PostUrl&${VARNAME_INSTVER}=${INSTVER}${INSTVER_REPLACE}" + nextvar: + + + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=digsby" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$FinishFlags" + + StrCpy $PostUrl "$PostUrl&${VARNAME_EXITPAGE}=$LastSeenPage" + + !ifdef PAGE_NAME_CAUSES + ${If} $CausesPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_CAUSES}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$CausesPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_PLURA + ${If} $PluraPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_PLURA}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$PluraPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_BING + ${If} $BingPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_BING}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$BingPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_BCCTHIS + ${If} $BccthisPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_BCCTHIS}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$BccthisPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_BABYLON + ${If} $BabylonPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_BABYLON}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$BabylonPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_UNIBLUE + ${If} $UnibluePage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_UNIBLUE}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$UnibluePage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_ARO + ${If} $AroPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_ARO}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$AroPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_ASK + ${If} $AskPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_ASK}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$AskPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_XOBNI}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$XobniPage.Results" + ${EndIf} + !endif + + !ifdef PAGE_NAME_CRAWLER + ${If} $CrawlerPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_CRAWLER}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$CrawlerPage.Results" + ${EndIf} + !endif + + !ifdef PAGE_NAME_INBOX + ${If} $InboxPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_INBOX}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$InboxPage.Results" + ${EndIf} + !endif + !ifdef PAGE_NAME_OPENCANDY + ${If} $OpencandyPage.Visited == "True" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFERS_SHOWN}[]=${PAGE_NAME_OPENCANDY}" + StrCpy $PostUrl "$PostUrl&${VARNAME_OFFER_RESULTS}[]=$OpencandyPage.Results" + ${EndIf} + !endif + + #MessageBox MB_OK "$PostUrl" + + GetTempFileName $R7 + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "$PostUrl" \ + $R7 + !endif + !endif +!macroend + +Function .onInstSuccess + !ifdef PAGE_NAME_OPENCANDY + !insertmacro OpenCandyOnInstSuccess + !endif + + !ifdef VCREDIST + Delete "$INSTDIR\vcredist.exe" + !endif + + ${If} $IsPortable == "False" + Call AddQuicklaunchShortcut + Call AddDesktopShortcut + ${Else} + Call AddPortableRootShortcut + ${EndIf} + + !ifndef BRAND + !ifdef INSTALL_THANKS_URL + ${OpenLinkNewWindow} "${INSTALL_THANKS_URL}" + !endif + !endif + + IfSilent next + Goto common + next: + Call AddStartupShortcut + !insertmacro POST_METRICS + + common: + + !ifndef DIGSBY_REGISTRATION_IN_INSTALLER + Call StartDigsby + !else + IfSilent 0 end + Call StartDigsby + !endif + end: +FunctionEnd + +Function .onInstFailed + !ifdef PAGE_NAME_OPENCANDY + !insertmacro OpenCandyOnInstFailed + !endif +FunctionEnd + +Function onUserAbort + !ifdef PAGE_NAME_OPENCANDY + IntOp $OpencandyPage.UserAborted 1 + 0 + !endif +FunctionEnd + +Function .onGUIEnd + #MessageBox MB_OK "PageTitle: $OCPageTitle$\r$\nOCPageDesc: $OCPageDesc" + + ## Set flags for stats reporting + ${If} $HomePageSet != 0 + IntOp $FinishFlags $FinishFlags | ${FLAG_SETHOME} + ${EndIf} + + ${If} $SearchPageSet != 0 + IntOp $FinishFlags $FinishFlags | ${FLAG_SET_DIGSBYSEARCH} + ${EndIf} + + IfSilent 0 +2 + IntOp $FinishFlags $FinishFlags | ${FLAG_SILENT} + ### + + !ifdef PAGE_NAME_OPENCANDY + !insertmacro OpenCandyOnGuiEnd + IntCmp $OpencandyPage.UserAborted 0 ocdone + !insertmacro OpenCandyOnInstFailed + ocdone: + !endif + + !insertmacro POST_METRICS +FunctionEnd + +!macro DIGSBY_PAGE_REGISTER + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + Page custom DigsbyPageRegister_enter DigsbyPageRegister_leave "" # Both are from DigsbyRegister.nsh + + !verbose pop + +!macroend + +!insertmacro DIGSBY_PAGE_WELCOME +!insertmacro MULTIUSER_PAGE_INSTALLMODE +!ifdef PAGE_NAME_XOBNI + !insertmacro DIGSBY_PAGE_XOBNI +!endif +!ifdef PAGE_NAME_BCCTHIS + !insertmacro DIGSBY_PAGE_BCCTHIS +!endif +!ifdef PAGE_NAME_CRAWLER + !insertmacro DIGSBY_PAGE_CRAWLER +!endif +!ifdef PAGE_NAME_INBOX + !insertmacro DIGSBY_PAGE_INBOX +!endif +!ifdef PAGE_NAME_BING + !insertmacro DIGSBY_PAGE_BING +!endif +!ifdef PAGE_NAME_BABYLON + !insertmacro DIGSBY_PAGE_BABYLON +!endif +!ifdef PAGE_NAME_OPENCANDY + !insertmacro DIGSBY_PAGE_OPENCANDY +!endif +!ifdef PAGE_NAME_ASK + !insertmacro DIGSBY_PAGE_ASK_TOOLBAR +!endif +!ifdef PAGE_NAME_UNIBLUE + !insertmacro DIGSBY_PAGE_UNIBLUE +!endif +!ifdef PAGE_NAME_ARO + !insertmacro DIGSBY_PAGE_ARO +!endif + +!ifdef DIGSBY_REGISTRATION_IN_INSTALLER + Page custom DigsbyPageRegister_enter DigsbyPageRegister_leave "" +!endif + +!insertmacro MUI_PAGE_INSTFILES +#!insertmacro MUI_PAGE_FINISH +!insertmacro DIGSBY_PAGE_FINISH + +!insertmacro MUI_UNPAGE_WELCOME +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" + +# Reserve files +#!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + +# MUI end ------ + +Name "${PRODUCT_NAME}" #${PRODUCT_VERSION} +InstallDir "$PROGRAMFILES\${PRODUCT_NAME}" + +!macro CleanInstDir + IfFileExists "$INSTDIR\manifest" 0 +2 + Delete "$INSTDIR\manifest" + IfFileExists "$INSTDIR\${PRODUCT_NAME}.exe" 0 +2 + Delete "$INSTDIR\${PRODUCT_NAME}.exe" + IfFileExists "$INSTDIR\python25.dll" 0 +2 + Delete "$INSTDIR\python25.dll" + IfFileExists "$INSTDIR\msvcr90.dll" 0 +2 + Delete "$INSTDIR\msvcr90.dll" + IfFileExists "$INSTDIR\msvcp90.dll" 0 +2 + Delete "$INSTDIR\msvcp90.dll" + IfFileExists "$INSTDIR\Microsoft.VC90.CRT.manifest" 0 +2 + Delete "$INSTDIR\Microsoft.VC90.CRT.manifest" + IfFileExists "$INSTDIR\msvcr71.dll" 0 +2 + Delete "$INSTDIR\msvcr71.dll" + + # delete the updatetag file so that a fresh install brings you back to release + IfFileExists "$LOCALAPPDATA\${PRODUCT_NAME}\tag.yaml" 0 +2 + Delete "$LOCALAPPDATA\${PRODUCT_NAME}\tag.yaml" + + RMDir /R "$INSTDIR\res" + RMDir /R "$INSTDIR\logs" + RMDir /R "$INSTDIR\lib" + RMDir /R "$INSTDIR\temp" +!macroend + +Section "Install" + StrCpy $LastSeenPage "install" + + Push "wxWindowClassNR" + Push "Buddy List" + Call FindWindowClose + + Push "wxWindowClassNR" + Push "${PRODUCT_NAME} Login" + Call FindWindowClose + + !insertmacro CleanInstDir + + #System::Call "advapi32::GetUserName(t .r0, *i ${NSIS_MAX_STRLEN} r1) i.r2" + RMDir /R "$%LocalAppData%\VirtualStore\Program Files\${PRODUCT_NAME}" + ClearErrors + + CreateDirectory "$INSTDIR" + ClearErrors + + !ifdef BRAND + FileOpen $0 "$INSTDIR\brand.yaml" w + FileWrite $0 "brand: ${BRAND}$\r$\n" + FileClose $0 + !endif + + !ifdef TAG + FileOpen $0 "$INSTDIR\tag.yaml" w + FileWrite $0 "tag: ${TAG}$\r$\n" + FileClose $0 + !endif + + SetOutPath "$INSTDIR" + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}\support" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Support\Documentation.lnk" "${DOCUMENTATION_URL}" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Support\Forums.lnk" "${FORUM_SUPPORT_URL}" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Support\Uninstall.lnk" "$INSTDIR\uninstall.exe" + + ${If} $IsPortable == "False" + WriteUninstaller "$INSTDIR\uninstall.exe" + WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}" + WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_NAME}.exe" + WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninstall.exe" +!ifdef INSTALL_THANKS_URL + WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "HelpLink" "${INSTALL_THANKS_URL}" +!endif + WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" + WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "InstallPath" "$INSTDIR" + WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "Application" "$INSTDIR\${PRODUCT_NAME}.exe" + + CreateDirectory "$SMPROGRAMS\Digsby" + CreateDirectory "$SMPROGRAMS\Digsby\support" + CreateShortCut "$SMPROGRAMS\Digsby\Digsby.lnk" "$INSTDIR\Digsby.exe" + CreateShortCut "$SMPROGRAMS\Digsby\Support\Documentation.lnk" "${DOCUMENTATION_URL}" + CreateShortCut "$SMPROGRAMS\Digsby\Support\Forums.lnk" "${FORUM_SUPPORT_URL}" + CreateShortCut "$SMPROGRAMS\Digsby\Support\Uninstall.lnk" "$INSTDIR\uninstall.exe" + ${EndIf} + + !ifdef VCREDIST_VERSION + File "/oname=$INSTDIR\vcredist.exe" "${DIGSINST}\${VCREDIST_VERSION}\vcredist.exe" + !endif + + !ifdef VCREDIST + DetailPrint "Installing required system components..." + ExecWait "vcredist.exe /Q" + !endif + + File /r "${DISTDIR}\*" + + ${If} $IsPortable == "True" + CreateDirectory "$INSTDIR\res" + File "/oname=$INSTDIR\res\portable.yaml" "${DIGSRES}\portable.yaml" + ${EndIf} + + !ifdef PAGE_NAME_CAUSES + ${If} $CausesPage.Install == "True" + Call CausesPage.PerformInstall + ${EndIf} + !endif + + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Install == "True" + Call XobniPage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_CRAWLER + ${If} $CrawlerPage.Install == "True" + Call CrawlerPage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_INBOX + ${If} $InboxPage.Install == "True" + Call InboxPage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_BING + ${If} $BingPage.Install == "True" + Call BingPage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_BCCTHIS + ${If} $BccthisPage.Install == "True" + Call BccthisPage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_BABYLON + ${If} $BabylonPage.Install == "True" + Call BabylonPage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_UNIBLUE + ${If} $UnibluePage.Install == "True" + Call UnibluePage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_ARO + ${If} $AroPage.Install == "True" + Call AroPage.PerformInstall + ${EndIf} + !endif + !ifdef PAGE_NAME_ASK + ${If} $AskPage.Install == "True" + Call AskPage.PerformInstall + ${EndIf} + !endif + + !ifdef PAGE_NAME_PLURA + ${If} $PluraPage.Install == "True" + # Actually there's nothing to do for this, + # the finish page takes care of calling the right functions + ${EndIf} + !endif + + !ifdef PAGE_NAME_OPENCANDY + # This variable doesn't get set, Opencandy manages its own installation + #${If} $OpencandyPage.Install == "True" + Call OpencandyPage.PerformInstall + #${EndIf} + !endif + +SectionEnd + +${REDEF} _UN_ "un." + +!macro RemoveShortcuts + RMDir /R "$SMPROGRAMS\${PRODUCT_NAME}\support" + RMDir /R "$SMPROGRAMS\${PRODUCT_NAME}" + + IfFileExists "$DESKTOP\${PRODUCT_NAME}.lnk" 0 +2 + Delete "$DESKTOP\${PRODUCT_NAME}.lnk" + + IfFileExists "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" 0 +2 + Delete "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" + + IfFileExists "$SMPROGRAMS\Startup\${PRODUCT_NAME}.lnk" 0 +2 + Delete "$SMPROGRAMS\Startup\${PRODUCT_NAME}.lnk" +!macroend + +Section "Uninstall" + Push "wxWindowClassNR" + Push "Buddy List" + Call un.FindWindowClose + + Push "#32770" + Push "${PRODUCT_NAME} Login" + Call un.FindWindowClose + + !insertmacro CleanInstDir + + SetShellVarContext all + !insertmacro RemoveShortcuts + + SetShellVarContext current + !insertmacro RemoveShortcuts + +!ifdef WHY_UNINSTALL + # open the 'why did you uninstall' page in a new browser window + ${OpenLinkNewWindow} "${WHY_UNINSTALL}" +!endif + + IfErrors end + DeleteRegKey HKLM "${PRODUCT_UNINST_KEY}" + IfErrors end + Delete "$INSTDIR\uninstall.exe" + RMDir "$INSTDIR" + + end: +SectionEnd + +#Function un.onUninstSuccess +# HideWindow +# MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer." +#FunctionEnd + +Function un.onInit +# MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove ;$(^Name) and all of its components?" IDYES +2 +# Abort + !insertmacro MULTIUSER_UNINIT +FunctionEnd + +!macro FindWindowClose_definition + Exch $0 + Exch + Exch $1 + Push $2 + Push $3 + find: + FindWindow $2 $1 $0 + IntCmp $2 0 nowindow + MessageBox MB_OKCANCEL|MB_ICONSTOP "An instance of ${PRODUCT_NAME} is running. Please close it and press OK to continue." IDOK find IDCANCEL abortinst + Goto find + abortinst: + Abort "Please close ${PRODUCT_NAME} to continue this operation." + nowindow: + Pop $3 + Pop $2 + Pop $1 + Pop $0 +!macroend + +Function un.FindWindowClose + !insertmacro FindWindowClose_definition +FunctionEnd + +Function FindWindowClose + !insertmacro FindWindowClose_definition +FunctionEnd + +Function FinishPage_ShowChecks + FindWindow $R0 "#32770" "" $HWNDPARENT # Parent window + + LockWindow on + + GetDlgItem $R1 $R0 1209 + ShowWindow $R1 ${SW_HIDE} + + GetDlgItem $R1 $R0 1203 + ShowWindow $R1 ${SW_SHOW} + + GetDlgItem $R1 $R0 1204 + ShowWindow $R1 ${SW_SHOW} + + GetDlgItem $R1 $R0 1205 + ShowWindow $R1 ${SW_SHOW} + + GetDlgItem $R1 $R0 1206 + ShowWindow $R1 ${SW_SHOW} + + GetDlgItem $R1 $R0 1208 + ShowWindow $R1 ${SW_SHOW} + + LockWindow off + +FunctionEnd diff --git a/installer/DigsbyRegister.nsh b/installer/DigsbyRegister.nsh new file mode 100644 index 0000000..a9f024d --- /dev/null +++ b/installer/DigsbyRegister.nsh @@ -0,0 +1,536 @@ +!include "FileFunc.nsh" +!include "TextFunc.nsh" +!include "LogicLib.nsh" +!include "System.nsh" + +!include "DigsbySearch.nsh" +!insertmacro _FFX_RewriteSearchPlugin "ebay.xml" "ChangeEbaySearchEngineFFX" +!insertmacro _FFX_RewriteSearchPlugin "amazondotcom.xml" "ChangeAmazonSearchEngineFFX" + +!insertmacro Locate +!insertmacro LineRead + +!define NAME "${PRODUCT_NAME}" + +Var /GLOBAL user_status +# Var /GLOBAL invite #3 +Var /GLOBAL user # 4 +Var /GLOBAL email # 5 +Var /GLOBAL pass # 6 +SpaceTexts none +!define TEXT_IO_TITLE "Create ${PRODUCT_NAME} Account" +!define TEXT_IO_SUBTITLE " Synchronize preferences between sessions and locations" + +LangString RegisterTitle ${LANG_ENGLISH} "${TEXT_IO_TITLE}" +LangString RegisterSubtitle ${LANG_ENGLISH} "${TEXT_IO_SUBTITLE}" + +!define DIGSBY_SEARCH_UUID_IE "{3326ab56-742e-5603-906f-290517220122}" + +!macro RegisterValue FieldNum + ReadINIStr $R9 "$PLUGINSDIR\register.ini" "Field ${FieldNum}" "State" +!macroend + +!define AMAZON_SEARCH_RESOURCE "http://www.amazon.com/gp/search" +!define EBAY_SEARCH_RESOURCE "http://rover.ebay.com/rover/1/711-53200-19255-0/1" + +#ReserveFile "vcredist_x86.exe" + +Function Validate + push $R9 # gonna be using this one a lot + push $R8 + +#-- Check all fields for content. 3,4,5,6,7 are the fields to check + ${For} $R8 3 6 + !insertmacro RegisterValue $R8 + ${If} $R9 == "" + Goto empty_fields + ${EndIf} + ${Next} + +#-- Invite code must be present. For sanity's sake disallow >40 characters +# !insertmacro RegisterValue 3 +# StrLen $R8 $R9 +# ${If} $R8 < 1 +# ${OrIf} $R8 > 40 + # Too long/short +# Goto range_invite +# ${EndIf} + +#-- Check Passwords + # (5==6), length >= password requirement + !insertmacro RegisterValue 5 # 3,4,5,6,11 + StrCpy $R8 $R9 + !insertmacro RegisterValue 6 # + ${If} $R9 S!= $R8 + Goto incorrect_pw + ${EndIf} + StrLen $R8 $R9 + ${If} $R8 < 5 + ${OrIf} $R8 > 32 # 5= username requirement + !insertmacro RegisterValue 3 + StrLen $R8 $R9 + ${If} $R8 < 3 + ${OrIf} $R8 > 18 # 3$\r$\n' + FileWrite $R0 '$\r$\n' + FileWrite $R0 ' Amazon.com$\r$\n' + FileWrite $R0 ' Amazon.com Search$\r$\n' + FileWrite $R0 ' UTF-8$\r$\n' + FileWrite $R0 ' http://digsby.com/digsbysearch.xml$\r$\n' + FileWrite $R0 ' 7$\r$\n' + FileWrite $R0 ' data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsQAAALEAGtI711AAAAB3RJTUUH0wESEi0MqTATXwAAAjVJREFUeJyFUlGLElEU/mbVHd2aaaRgWGyJgmXRINiXfJCeRgaiLEiKgYXoRQrqRejNH7D1uNBDPvbWtGxvS64IG2IIQQhxScpYMpt1l1qdptVVZ+z2oM6qu9KBC4dzv/Od73z3AmPROmjeVlWVKopCRVGkHMdRURSpoig0lUrRcfxI6LoelWV5GwCddOLx+PEklmVej0QiI80Oh4OyLHuE5Fgl/aJ9gsEgzefzm4SQzVgs9n8VqqqO7EwIsUGEkEscx9kEsizbd85BEo3eenzzRkRstTsfAVwRBOH+EP/DSb4x4wVN0wq5XE7MZDKz5XIZlUoFtVoNu7u7NkaWZaTTaWZEQV8qDYfDKBaLkwZOVkAI8UuS9GkwiWVZNBr7sLZeo1V6hb/GFrxGwW6s84twzYbgGBRM0/yZzWZtQCKRQGhuD80PT0DbdUzxF9DmA2jzAbiNIjztHUzvvT+UIoqi7TLHcVTX9QeWZVLLMikh5Nzwf2h9USlNgtIk6NSAoNlsYjgXBOG50+liAGCe3/72ayOGP28f9fZ2ewEAv89GYRMEAgGboNvtYjBtf0PB9BsZJz8/Q7dR7d3Xeia75+/0XsGyTEqNrzC/p9HVSzCr7w4N+7GGOr+IE6GnOH3+KgCgo2XhAeCak+DU16PUWL0Mr1EYfdO+027/PZxaWIKJmY4kSaX1lysXnat+HARXMOM5wzA0iSP/etDILixhp9aGz+djAEDTtLt8aflFt1GFcG2NAYB/rN8dqx12fbIAAAAASUVORK5CYII=$\r$\n' + FileWrite $R0 ' $\r$\n' + FileWrite $R0 ' http://www.amazon.com/$\r$\n' + FileWrite $R0 '$\r$\n' + FileClose $R0 + Push "" +FunctionEnd + +Function ChangeEbaySearchEngineFFX + FileOpen $R0 '$R9' w + FileWrite $R0 '$\r$\n' + FileWrite $R0 '$\r$\n' + FileWrite $R0 ' eBay$\r$\n' + FileWrite $R0 ' eBay - Online actions$\r$\n' + FileWrite $R0 ' UTF-8$\r$\n' + FileWrite $R0 ' http://digsby.com/digsbysearch.xml$\r$\n' + FileWrite $R0 ' 7$\r$\n' + FileWrite $R0 ' data:image/x-icon;base64,R0lGODlhEAAQAMQAAAAAAMz/zMwAADMAzOfn1sxmAP///5kAZpnM////AACZM/777zPMAP+ZAP8AAP9mmf/MzMwAZjNmAADMM/+ZM/9mM//MMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAQUAP8ALAAAAAAQABAAAAVPoCGOZGmeaKqiQDkMYqAoBqMELpxUlVTfBohjeHjBLJLZZCF0GASOAWJQSFAUE1FTwIUNKoYKTQslGCLSb6MBFD2G3Zdo0k4tVvi8fl8KAQA7$\r$\n' + FileWrite $R0 ' $\r$\n' + FileWrite $R0 ' http://search.ebay.com/$\r$\n' + FileWrite $R0 '$\r$\n' + FileClose $R0 + Push "" +FunctionEnd + +Function WriteFFXSearchDefaults + FileOpen $R0 $R9 a + FileSeek $R0 0 END + FileWrite $R0 'user_pref("browser.search.defaultenginename", "Google Powered Digsby Search");' + FileWrite $R0 'user_pref("keyword.URL", "http://searchbox.digsby.com/search?sourceid=navclient&gfns=1&q=");' + FileClose $R0 + Push "" +FunctionEnd + +Function WriteDigsbySearchPlugin + FileOpen $R0 '$R9\digsby.xml' w + FileWrite $R0 '$\r$\n' + FileWrite $R0 '$\r$\n' + FileWrite $R0 ' Google Powered Digsby Search$\r$\n' + FileWrite $R0 ' Google Powered Digsby Search$\r$\n' + FileWrite $R0 ' UTF-8$\r$\n' + FileWrite $R0 ' http://digsby.com/digsbysearch.xml$\r$\n' + FileWrite $R0 ' 7$\r$\n' + FileWrite $R0 ' data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAaRJREFUeNpiVIg5JRURw0A0YAHio943kYV%2B%2Ff33%2BdvvX7%2F%2FMjEx8nKycrGzwKXOiPKzICvdeezLhCV3jp15%2Bfv%2FX0YGhv8MDDxMX2qKTIw0RK10eYD6QYqATvoPBkt3f5K0W9Ew4fjTFz%2F%2Bw8Dm3W8UPeZxqFa%2BevsFyD0twgfVsOfkRxHrtfV9u5BVQ8Crd98%2FffkGYQM1QJ20%2FfSPv79eNxQGYfpSVJADmcvEAHbr7oOX2dj%2FERNKIA2%2F%2F%2Fz%2FxfCDhYVoDUDw5P6vf9%2B5iY0HVmZGQWm%2BN3fff%2Fn2k4eLHS739x%2FDiRs%2Ff%2F%2F5x8HO%2FOHzN3djfqgNjIwMgc6qzLx%2Fpy47j2zY%2Feff06tXhOUucgxeun33AUZGpHh4%2Bvo7t8EyIJqz%2FhpasD59%2B5dNrqdnznZIsEL9ICXCsWuBCwvTv%2FymS5PWPP32ExEALz%2F%2BB5r848cPCJcRaMP9xaYQzofPPzfuvrnj0Jst%2B5%2F8%2Bc4sLPeDkYlRgJc93VPE18NIXkYUmJYQSQMZ%2FP3379uPH7%2F%2F%2FEETBzqJ0WqLGvFpe2LCC4AAAwAyjg7ENzDDWAAAAABJRU5ErkJggg%3D%3D$\r$\n' + FileWrite $R0 ' $\r$\n' + FileWrite $R0 ' $\r$\n' + FileWrite $R0 ' http://searchbox.digsby.com$\r$\n' + FileWrite $R0 '$\r$\n' + FileClose $R0 + Push "" +FunctionEnd + +Function WriteFFXHomepageDigsbyCheck + FileOpen $R0 '$APPDATA\${PRODUCT_NAME}\dohomepage' w + FileWrite $R0 'yes' + FileClose $R0 +FunctionEnd + +Function WriteFFXSearchDigsbyCheck + FileOpen $R0 '$APPDATA\${PRODUCT_NAME}\dosearch' w + FileWrite $R0 'yes' + FileClose $R0 +FunctionEnd + +Function WriteFFXHomepagePrefs + FileOpen $R0 $R9 a + FileSeek $R0 0 END + FileWrite $R0 'user_pref("browser.startup.homepage", "http://search.digsby.com");' + FileClose $R0 + Push "" +FunctionEnd + +Function StartDigsby + SetOutPath $INSTDIR + + !ifdef DIGSBY_REGISTRATION_IN_INSTALLER + IfSilent reg noreg + !else + Goto reg + !endif + + reg: + Exec '"$INSTDIR\${PRODUCT_NAME}.exe" --register $PluraCommandString' + Goto end + noreg: + Exec '"$INSTDIR\${PRODUCT_NAME}.exe" $PluraCommandString' + Goto end + + end: +FunctionEnd \ No newline at end of file diff --git a/installer/DigsbySearch.nsh b/installer/DigsbySearch.nsh new file mode 100644 index 0000000..2eac768 --- /dev/null +++ b/installer/DigsbySearch.nsh @@ -0,0 +1,140 @@ +!include "FileFunc.nsh" +!include "StrFunc.nsh" + +!insertmacro Locate +!insertmacro GetParent +!insertmacro GetFileName +${StrLoc} + +!macro _FFX_RewriteSearchPlugin WhichOne FunctionToCall + Function _OnFindUserFolder_${WhichOne} + Push "" # Return val to Locate + #MessageBox MB_OK "Found userfolder for $R7: $R9$R1" + IfFileExists $R9$R1 0 end + # $R1 is still the suffix of the "$APPDATA" folder from the function "WriteSearchPluginToFFXProfileFolders". + ClearErrors + ${Locate} "$R9$R1\Mozilla\Firefox\Profiles" "/M=* /L=D /G=0" "_OnFindFFXProfile_${WhichOne}" + end: + ClearErrors + FunctionEnd + + Function FFX_WriteSearch_${WhichOne} + ClearErrors + IfFileExists "$R9\searchplugins" do_write_${WhichOne} + CreateDirectory "$R9\searchplugins" + ClearErrors + + do_write_${WhichOne}: + Push $R9 + Push "$R9\searchplugins\${WhichOne}" + Pop $R9 + #MessageBox MB_OK "Calling ${FunctionToCall} for this file:$\n$R9" + Call ${FunctionToCall} + ClearErrors + Exch + Pop $R9 + FunctionEnd + + Function _OnFindFFXProfile_${WhichOne} + ClearErrors + #MessageBox MB_OK "Found FFX Profile $R9" + #Push "" # Return val to Locate + Call FFX_WriteSearch_${WhichOne} + ClearErrors + FunctionEnd + + Function WriteSearchPluginToFFXProfileFolders_${WhichOne} + Push $R1 + Push $R2 + + !define prefix_len $0 + !define suffix_len $1 + !define profile_prefix $2 + !define appdata_suffix $3 + !define profile_len $4 + !define appdata_len $5 + + !define PROFILE $PROFILE + !define APPDATA $APPDATA + + StrLen ${profile_len} ${PROFILE} + StrCpy ${appdata_suffix} ${APPDATA} "" ${profile_len} + StrLen ${suffix_len} ${appdata_suffix} + IntOp ${prefix_len} ${appdata_len} - ${suffix_len} + StrCpy ${profile_prefix} ${APPDATA} ${prefix_len} + + Push ${appdata_suffix} + Pop $R1 + + # $R1 is now the suffix of APPDATA that does not include PROFILE. + # e.g. profile is "c:\Users\Mike" and AppData is "C:\users\Mike\AppData\Roaming" + # $R1 is "\AppData\Roaming" + # ${profile_prefix} is the first part, so we can check if it's actually a prefix of PROFILE + ${GetParent} "${PROFILE}" $R2 + StrCmp ${profile_prefix} ${PROFILE} 0 unknown_profile + #MessageBox MB_OK "Looking for folders like $R2\*$R1." + ${Locate} $R2 "/M=* /L=D /G=0" "_OnFindUserFolder_${WhichOne}" + #MessageBox MB_OK "Done looking for user folders for ${WhichOne}." + + !undef prefix_len + !undef suffix_len + !undef profile_prefix + !undef appdata_suffix + !undef appdata_len + !undef profile_len + !undef PROFILE + !undef APPDATA + + unknown_profile: + + Pop $R2 + Pop $R1 + FunctionEnd + + Function _FFX_RewriteSearchPlugin_impl_${WhichOne} + ClearErrors + !insertmacro DeleteFromMozillaSearchPlugins ${WhichOne} + IfErrors rewritedone_${WhichOne} + Call WriteSearchPluginToFFXProfileFolders_${WhichOne} + + rewritedone_${WhichOne}: + ClearErrors + + FunctionEnd +!macroend + +!macro FFX_RewriteSearchPlugin Filename + Call _FFX_RewriteSearchPlugin_impl_${Filename} +!macroend + +!macro DeleteFromMozillaSearchPlugins WhatFile + # ClearErrors + ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Mozilla\Mozilla Firefox" CurrentVersion + ${StrLoc} $2 $0 "a" ">" + StrCmp $2 "" 0 done_${WhatFile} + ${StrLoc} $2 $0 "A" ">" + StrCmp $2 "" 0 done_${WhatFile} + ${StrLoc} $2 $0 "b" ">" + StrCmp $2 "" 0 done_${WhatFile} + ${StrLoc} $2 $0 "B" ">" + StrCmp $2 "" 0 done_${WhatFile} + ${StrLoc} $2 $0 "c" ">" + StrCmp $2 "" 0 done_${WhatFile} + ${StrLoc} $2 $0 "C" ">" + StrCmp $2 "" 0 done_${WhatFile} + + ReadRegStr $1 HKEY_LOCAL_MACHINE "SOFTWARE\Mozilla\Mozilla Firefox\$0\Main" "Install Directory" + + IfFileExists "$1\searchplugins\${WhatFile}" 0 notfound_${WhatFile} + #MessageBox MB_OK "Deleting this: $1\searchplugins\${WhatFile}" + ClearErrors + Delete "$1\searchplugins\${WhatFile}" + Goto done_${WhatFile} + + notfound_${WhatFile}: + SetErrors + #MessageBox MB_OK "Not found: $1\searchplugins\${WhatFile}" + + done_${WhatFile}: +!macroend + diff --git a/installer/License.txt b/installer/License.txt new file mode 100644 index 0000000..920f371 --- /dev/null +++ b/installer/License.txt @@ -0,0 +1,1268 @@ +THIS IS A LEGAL AGREEMENT between "you," the end user of Digsby(R) brand software, and dotSyntax, LLC ("dotSyntax"). + +Use of the software you are about to download or install indicates your acceptance of these terms. You also agree to accept these terms by so indicating at the appropriate screen, prior to the download or installation process. As used in this Agreement, the capitalized term "Software" means the Digsby instant messaging software, the Digsby service, the Digsby websites, the Digsby Widget, and all other software, features, tools, websites, widgets, and services offered by dotSyntax and its business divisions together with any and all enhancements, upgrades, and updates that may be provided to you in the future by dotSyntax. IF YOU DO NOT AGREE TO THESE TERMS AND CONDITIONS, YOU SHOULD SO INDICATE AT THE APPROPRIATE SCREEN AND PROMPTLY DISCONTINUE THE INSTALLATION AND DOWNLOAD PROCESS. + +1. OWNERSHIP. + +The Software and any accompanying documentation are owned by dotSyntax and ownership of the Software shall at all times remain with dotSyntax. Copies are provided to you only to allow you to exercise your rights under this Agreement. This Agreement does not constitute a sale of the Software or any accompanying documentation, or any portion thereof. Without limiting the generality of the foregoing, you do not receive any rights to any patents, copyrights, trade secrets, trademarks or other intellectual property rights relating to or in the Software or any accompanying documentation. All rights not expressly granted to you under this Agreement are reserved by dotSyntax. + +2. HOW WE MAY MODIFY THIS CONTRACT. + +We may change this contract at any time. You must review this contract on a regular basis. You can find the most recent version of the contract at http://www.digsby.com/tos.php. The changed contract is in effect right away. If you do not agree to changes in the contract, then you must stop using the Software. If you do not stop using the Software, then your use of the Software will continue under the changed contract. + +3. HOW YOU MAY ACCESS AND USE THE SOFTWARE. + +You must be at least 13 years of age to use the Software. If dotSyntax discovers through reliable means that you are younger than 13, dotSyntax will cancel your account. + +We provide the Software for your personal use. You may use the Software while you are at work, but you may not use the Software to conduct business without a separate written contract with dotSyntax. + +You are responsible for all activity associated with your Software account and password. You must notify dotSyntax right away of any use of your account that you did not authorize or any breach in security known to you that relates to the Software. + +Do not provide your Software account and password to third parties. You may not authorize any third party to access and/or use the Software on your behalf. You may also not use any automated process or service to access and/or use the Software such as a BOT, a spider or periodic caching of information stored by dotSyntax. You may not use any software or services with the Software or authorized third-party software which modifies or reroutes, or attempts to modify or reroute, the Software. You may also not use any software or hardware that reduces the number of users directly accessing or using the Software (sometimes called 'multiplexing' or 'pooling' software or hardware). + +You may only use the Software or authorized third-party software to sign into and use the Software. + + +4. WHAT YOU MAY NOT DO WITH THE SOFTWARE. + +The privacy, safety and security of our Software and the users of our Software are very important to us. You may not use the Software in any way that could harm the Software, other Software users, dotSyntax or our affiliates. Some examples of harmful activity that we do not permit include: + +You will not, and will not permit others to: (i) reverse engineer, decompile, disassemble, derive the source code of, modify, or create derivative works from the Software; or (ii) use, copy, modify, alter, or transfer, electronically or otherwise, the Software or any of the accompanying documentation except as expressly permitted in this Agreement; or (iii) redistribute, sell, rent, lease, sublicense, or otherwise transfer rights to the Software whether in a stand-alone configuration or as incorporated with other software code written by any party except as expressly permitted in this Agreement. + +You will not use the Software to engage in or allow others to engage in any illegal activity. + +You will not engage in use of the Software that will interfere with or damage the operation of the services of third parties by overburdening/disabling network resources through automated queries, excessive usage or similar conduct. + +You will not use the Software to engage in any activity that will violate the rights of third parties, including, without limitation, through the use, public display, public performance, reproduction, distribution, or modification of communications or materials that infringe copyrights, trademarks, publicity rights, privacy rights, other proprietary rights, or rights against defamation of third parties. + +You will not transfer the Software or utilize the Software in combination with third party software authored by you or others to create an integrated software program which you transfer to unrelated third parties. + +5. UPGRADES, UPDATES AND ENHANCEMENTS + +All upgrades, updates or enhancements of the Software shall be deemed to be part of the Software and will be subject to this Agreement. The Software may communicate with dotSyntax's servers to check for available updates to the Software, including bug fixes, patches, enhanced functions, missing plug-ins and new versions (collectively, "Updates"). During this process, the Software sends dotSyntax a request for the latest version information. By installing the Software, you hereby agree to automatically request and receive Updates from dotSyntax's servers. + +6. DISCLAIMER OF WARRANTY + +THE SOFTWARE IS PROVIDED ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES THAT IT IS FREE OF DEFECTS, VIRUS FREE, ABLE TO OPERATE ON AN UNINTERRUPTED BASIS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE AND AGREEMENT. NO USE OF THE SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +7. LIMITATION OF LIABILITY + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL DOTSYNTAX BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED. IN ANY CASE, DOTSYNTAX'S COLLECTIVE LIABILITY UNDER ANY PROVISION OF THIS LICENSE SHALL NOT EXCEED IN THE AGGREGATE THE SUM OF THE FEES (IF ANY) YOU PAID FOR THIS LICENSE. + +8. CHANGES TO THE SOFTWARE; ADDITIONAL LIABILITY LIMITATION. + +WE MAY CHANGE THE SOFTWARE OR DELETE FEATURES AT ANY TIME AND FOR ANY REASON. WITHOUT LIMITING THE GENERAL NATURE OF SECTIONS 6 AND 7, DOTSYNTAX IS NOT RESPONSIBLE OR LIABLE FOR (1) ANY CONTENT, INCLUDING WITHOUT LIMITATION, ANY INFRINGING, INACCURATE, OBSCENE, INDECENT, THREATENING, OFFENSIVE, DEFAMATORY, TORTIOUS, OR ILLEGAL CONTENT, OR (2) ANY THIRD PARTY CONDUCT, TRANSMISSIONS OR DATA. IN ADDITION, WITHOUT LIMITING THE GENERALITY OF SECTIONS 6 AND 7, DOTSYNTAX IS NOT RESPONSIBLE OR LIABLE FOR (1) ANY VIRUSES OR OTHER DISABLING FEATURES THAT AFFECT YOUR ACCESS TO OR USE OF THE SOFTWARE, (2) ANY INCOMPATIBILITY BETWEEN THE SOFTWARE AND OTHER WEB SITES, SERVICES, SOFTWARE AND HARDWARE, (3) ANY DELAYS OR FAILURES YOU MAY EXPERIENCE IN INITIATING, CONDUCTING OR COMPLETING ANY TRANSMISSIONS OR TRANSACTIONS IN CONNECTION WITH THE SOFTWARE IN AN ACCURATE OR TIMELY MANNER, OR (4) ANY DAMAGES OR COSTS OF ANY TYPE ARISING OUT OF OR IN ANY WAY CONNECTED WITH YOUR USE OF ANY SERVICES AVAILABLE FROM THIRD PARTIES THROUGH LINKS. THE LIMITATIONS, EXCLUSIONS AND DISCLAIMERS OF THIS CONTRACT APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, AND ARE NOT INTENDED TO DEPRIVE YOU OF ANY MANDATORY PROTECTIONS PROVIDED TO YOU UNDER APPLICABLE LAW. + +9. LEGENDS AND NOTICES + +You agree that you will not remove or alter any trademark, logo, copyright or other proprietary notices, legends, symbols or labels in the Software or any accompanying documentation. + +10. TERMINATION OF SOFTWARE AND SERVICE. + +This Agreement is effective upon your acceptance as provided herein and payment of the applicable license fees (if any), and will remain in force until terminated. You may terminate the licenses granted in this Agreement at any time by destroying the Software and any accompanying documentation, together with any and all copies thereof. The licenses granted in this Agreement will terminate automatically if you breach any of its terms or conditions or any of the terms or conditions of any other agreement between you and dotSyntax. Upon termination, you shall immediately destroy the original and all copies of the Software and any accompanying documentation, or return them to dotSyntax. + +11. SOFTWARE SUGGESTIONS + +dotSyntax welcomes suggestions for enhancing the Software and any accompanying documentation that may result in computer programs, reports, presentations, documents, ideas or inventions relating or useful to dotSyntax's business. You acknowledge that all title, ownership rights, and intellectual property rights concerning such suggestions shall become the exclusive property of dotSyntax and may be used for its business purposes in its sole discretion without any payment or accounting to you. + +12. EXPORT CONTROL + +The Software may contain encryption and is subject to United States export control laws and regulations and may be subject to export or import regulations in other countries, including controls on encryption products. You agree that you will not export, re-export or transfer the Software in violation of any applicable laws or regulations of the United States or the country where you legally obtained it. You are responsible for obtaining any licenses to export, re-export, transfer or import the Software. + +In addition to the above, the Software may not be used by, or exported or re-exported to: (i) any U.S. or EU sanctioned or embargoed country, or to nationals or residents of such countries; or (ii) to any person, entity or organization or other party identified on the U.S. Department of Commerce's Table of Denial Orders or the U.S. Department of Treasury's lists of Specially Designated Nationals and Blocked Persons, as published and revised from time to time; (iii) to any party engaged in nuclear, chemical/biological weapons or missile proliferation activities, unless authorized by U.S. and local (as required) law or regulations. + +12. COPYRIGHT AND TRADEMARK INFORMATION + +COPYRIGHT NOTICE: Copyright 2007 - 2009 dotSyntax, LLC. All Rights Reserved. +"Digsby" and the digsby egg shaped logo are registered trademarks of dotSyntax, LLC. All other trademarks, trade names service marks, service names are the property of their respective holders, including but not limited to, the following: +* "AIM" and the running man logo are registered trademarks of America Online, Inc. +* "Google Talk" is a registered trademark of Google Inc. +* "ICQ" is a registered mark of ICQ, Inc. +* "JABBER" is a registered trademark of the Jabber Software Foundation +* "MSN" and the butterfly logo are registered trademarks of Microsoft Corporation. +* "Yahoo!" and "Yahoo! Messenger" are registered trademarks of Yahoo, Inc. +* "GMail" is a registered trademark of Google Inc. +* "Facebook" is a registered trademark of Facebook Inc. +* "MySpace" is a registered trademark MySpace Inc. +* "LinkedIn" is a registered trademark of LinkedIn Inc. +Digsby and dotSyntax are in no way affiliated with or endorsed by America Online, Inc., Google Inc., ICQ, Inc., Jabber Software Foundation, Microsoft Corporation, Yahoo, Inc., Facebook Inc., MySpace Inc., LinkedIn, Inc. + +All "Conversation Themes", sound sets, and emoticon sets are used with the consent of their original creators and/or license owners. + +14. MISCELLANEOUS + +This Agreement constitutes the entire agreement between the parties concerning the Software, and may be amended only by a writing signed by both parties. This Agreement shall be governed by the laws of the State of New York, excluding its conflict of law provisions. All disputes relating to this Agreement are subject to the exclusive jurisdiction of the courts of New York and you expressly consent to the exercise of personal jurisdiction in the courts of New York in connection with any such dispute. This Agreement shall not be governed by the United Nations Convention on Contracts for the International Sale of Goods. If any provision in this Agreement should be held illegal or unenforceable by a court of competent jurisdiction, such provision shall be modified to the extent necessary to render it enforceable without losing its intent, or severed from this Agreement if no such modification is possible, and other provisions of this Agreement shall remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, shall not waive such term or condition or any subsequent breach thereof. + +15. USAGE OF COMPUTER RESOURCES. + +You agree to permit the Software to use the processing power of your computer when it is idle to run downloaded algorithms (mathematical equations) and code within a process. You understand that when the Software uses your computer, it likewise uses your CPU, bandwidth, and electrical power. The Software will use your computer to solve distributed computing problems, such as but not limited to, accelerating medical research projects, analyzing the stock market, searching the web, and finding the largest known prime number. This functionality is completely optional and you may disable it at any time. + +16. DIGSBY DONATES +dotSyntax offers a browser plugin called Digsby Donates. Installation and usage of the plugin Software indicates your acceptance of the terms of service found at http://www.digsby.com/freecause.php + +17. PERFORMANCE AND USAGE DATA. + +dotSyntax may automatically upload performance and usage data for evaluating the Software. Such data will not personally identify you. + +18. CUSTOMER SUPPORT. + +Please consult the "help" section of the Software for how we may provide you with customer support ("Support"). Unless you have entered into a separate written support contract with dotSyntax for the Software, however, we may terminate any Support we provide at any time. + +Authorized third-party software that uses the Software is not supported by dotSyntax. You should contact the provider of such software for support, if any. + +19. PRIVACY POLICY + +The Privacy Policy available at http://www.digsby.com/privacy.php explains dotSyntax's information practices that apply to your Registration Information and other information about you and your use of Digsby. You consent to the transfer of this information to and within the United States or other countries for processing and storage by us. Additionally, you agree that Digsby may use your username and password to authenticate you on the Software. + +The following file has a listing of all the licenses included within the Product + +============================================================= +The following license applies to: + Python - Copyright (C) 1990-2007 - Python Software Foundation + BeautifulSoup - Copyright (C) 2006 - Leonard Richardson, et al + ThreadPool - Copyright (C) 2006 - Christopher Arndt + +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.2 2.1.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2.1 2.2 2002 PSF yes + 2.2.2 2.2.1 2002 PSF yes + 2.2.3 2.2.2 2003 PSF yes + 2.3 2.2.2 2002-2003 PSF yes + 2.3.1 2.3 2002-2003 PSF yes + 2.3.2 2.3.1 2002-2003 PSF yes + 2.3.3 2.3.2 2002-2003 PSF yes + 2.3.4 2.3.3 2004 PSF yes + 2.3.5 2.3.4 2005 PSF yes + 2.4 2.3 2004 PSF yes + 2.4.1 2.4 2005 PSF yes + 2.4.2 2.4.1 2005 PSF yes + 2.4.3 2.4.2 2006 PSF yes + 2.4.4 2.4.3 2006 PSF yes + 2.5 2.4 2006 PSF yes + 2.5.1 2.5 2007 PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python +alone or in any derivative version, provided, however, that PSF's +License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative +version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +============================================================= + wxWindows Library Licence, Version 3.1 + ====================================== + + Copyright (c) 1998-2005 Julian Smart, Robert Roebling et al + + Everyone is permitted to copy and distribute verbatim copies + of this licence document, but changing it is not allowed. + + WXWINDOWS LIBRARY LICENCE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public Licence as published by + the Free Software Foundation; either version 2 of the Licence, or (at + your option) any later version. + + This library 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 Library + General Public Licence for more details. + + You should have received a copy of the GNU Library General Public Licence + along with this software, usually in a file named COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA. + + EXCEPTION NOTICE + + 1. As a special exception, the copyright holders of this library give + permission for additional uses of the text contained in this release of + the library as licenced under the wxWindows Library Licence, applying + either version 3.1 of the Licence, or (at your option) any later version of + the Licence as published by the copyright holders of version + 3.1 of the Licence document. + + 2. The exception is that you may use, copy, link, modify and distribute + under your own terms, binary object code versions of works based + on the Library. + + 3. If you copy code from files distributed under the terms of the GNU + General Public Licence or the GNU Library General Public Licence into a + copy of this library, as this licence permits, the exception does not + apply to the code that you add in this way. To avoid misleading anyone as + to the status of such modified files, you must delete this exception + notice from such code and/or adjust the licensing conditions notice + accordingly. + + 4. If you write modifications of your own for this library, it is your + choice whether to permit this exception to apply to your modifications. + If you do not wish that, you must delete the exception notice from such + code and/or adjust the licensing conditions notice accordingly. + +============================================================= + +============================================================= +WebKit + + Copyright (C) 2005 Apple Computer, Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +============================================================= +Copyright (C) 2001-2003 Nominum, Inc. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose with or without fee is hereby granted, +provided that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +============================================================= +The Python Imaging Library (PIL) is + + Copyright 1997-2006 by Secret Labs AB + Copyright 1995-2006 by Fredrik Lundh + +By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions: + +Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. + +SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +============================================================= +Copyright (c) 2003 why the lucky stiff + +This software is subject to either of two licenses (BSD or D&R), which you can choose +from in your use of the code. The terms for each of these licenses is listed below: + +BSD License +=========== + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +============================================================= +========================== + +Except where otherwise noted in the source code (trio files, hash.c and list.c) +covered by a similar licence but with different Copyright notices: + + Copyright (C) 1998-2002 Daniel Veillard. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- +NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Daniel Veillard shall not +be used in advertising or otherwise to promote the sale, use or other deal- +ings in this Software without prior written authorization from him. +========================== +trio.c, trio.h: +Copyright (C) 1998 Bjorn Reese and Daniel Stenberg. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +triop.h: +Copyright (C) 2000 Bjorn Reese and Daniel Stenberg. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +hash.c: +Copyright (C) 2000 Bjorn Reese and Daniel Veillard. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +list.c: +Copyright (C) 2000 Gary Pennington and Daniel Veillard. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +triodef.h, trionan.c, trionan.h: +Copyright (C) 2001 Bjorn Reese + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +triostr.c, triostr.h: +Copyright (C) 2001 Bjorn Reese and Daniel Stenberg. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +============================================================= + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + +============================================================= + Copyright (c) 2000 - 2006 Thomas Heller + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +============================================================= + + urllib2_file Copyright (C) 2004,2005,2006 Fabien SEISEN + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + you can contact me at: + http://fabien.seisen.org/python/ + + Also modified by Adam Ambrose (aambrose @T pacbell.net) to write data in + chunks (hardcoded to CHUNK_SIZE for now), so the entire contents of the file + don't need to be kept in memory. + +============================================================= +SocksiPy +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. + +============================================================= +Aspell (project lead is Kevin Atkinson) is covered under the Lesser General Public License (LGPL). + +============================================================= +PyXMPP (project lead is Jacek Konieczny) is covered under the Lesser General Public License (LGPL). + +============================================================= + python-twitter + Copyright 2007 Google Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the 'License'); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an 'AS IS' BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +============================================================= + simplejson + Copyright (c) 2006 Bob Ippolito + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +============================================================= +Rijndael cipher from the BSD project + * rijndael-alg-fst.h + * + * @version 3.0 (December 2000) + * + * Optimised ANSI C code for the Rijndael cipher (now AES) + * + * @author Vincent Rijmen + * @author Antoon Bosselaers + * @author Paulo Barreto + * + * This code is hereby placed in the public domain. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +============================================================= +M2Crypto +Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. + +Portions copyright (c) 2004-2006 Open Source Applications Foundation. +All rights reserved. + +Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. +All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation. + +THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============================================================= +Riceballs Emoticons + +Copyright (c) 2007-2009 David Lanham (http://www.dlanham.com) + +Used with permission from copyright owner. + diff --git a/installer/License_Privacy.txt b/installer/License_Privacy.txt new file mode 100644 index 0000000..d954ebd --- /dev/null +++ b/installer/License_Privacy.txt @@ -0,0 +1,161 @@ +THIS IS A LEGAL AGREEMENT between "you," the end user of Digsby(R) brand software, and dotSyntax, LLC ("dotSyntax"). + +Use of the software you are about to download or install indicates your acceptance of these terms. You also agree to accept these terms by so indicating at the appropriate screen, prior to the download or installation process. As used in this Agreement, the capitalized term "Software" means the Digsby instant messaging software, the Digsby service, the Digsby websites, the Digsby Widget, and all other software, features, tools, websites, widgets, and services offered by dotSyntax and its business divisions together with any and all enhancements, upgrades, and updates that may be provided to you in the future by dotSyntax. IF YOU DO NOT AGREE TO THESE TERMS AND CONDITIONS, YOU SHOULD SO INDICATE AT THE APPROPRIATE SCREEN AND PROMPTLY DISCONTINUE THE INSTALLATION AND DOWNLOAD PROCESS. + +1. OWNERSHIP. + +The Software and any accompanying documentation are owned by dotSyntax and ownership of the Software shall at all times remain with dotSyntax. Copies are provided to you only to allow you to exercise your rights under this Agreement. This Agreement does not constitute a sale of the Software or any accompanying documentation, or any portion thereof. Without limiting the generality of the foregoing, you do not receive any rights to any patents, copyrights, trade secrets, trademarks or other intellectual property rights relating to or in the Software or any accompanying documentation. All rights not expressly granted to you under this Agreement are reserved by dotSyntax. + +2. HOW WE MAY MODIFY THIS CONTRACT. + +We may change this contract at any time. You must review this contract on a regular basis. You can find the most recent version of the contract at http://www.digsby.com/tos.php. The changed contract is in effect right away. If you do not agree to changes in the contract, then you must stop using the Software. If you do not stop using the Software, then your use of the Software will continue under the changed contract. + +3. HOW YOU MAY ACCESS AND USE THE SOFTWARE. + +You must be at least 13 years of age to use the Software. If dotSyntax discovers through reliable means that you are younger than 13, dotSyntax will cancel your account. + +We provide the Software for your personal use. You may use the Software while you are at work, but you may not use the Software to conduct business without a separate written contract with dotSyntax. + +You are responsible for all activity associated with your Software account and password. You must notify dotSyntax right away of any use of your account that you did not authorize or any breach in security known to you that relates to the Software. + +Do not provide your Software account and password to third parties. You may not authorize any third party to access and/or use the Software on your behalf. You may also not use any automated process or service to access and/or use the Software such as a BOT, a spider or periodic caching of information stored by dotSyntax. You may not use any software or services with the Software or authorized third-party software which modifies or reroutes, or attempts to modify or reroute, the Software. You may also not use any software or hardware that reduces the number of users directly accessing or using the Software (sometimes called 'multiplexing' or 'pooling' software or hardware). + +You may only use the Software or authorized third-party software to sign into and use the Software. + + +4. WHAT YOU MAY NOT DO WITH THE SOFTWARE. + +The privacy, safety and security of our Software and the users of our Software are very important to us. You may not use the Software in any way that could harm the Software, other Software users, dotSyntax or our affiliates. Some examples of harmful activity that we do not permit include: + +You will not, and will not permit others to: (i) reverse engineer, decompile, disassemble, derive the source code of, modify, or create derivative works from the Software; or (ii) use, copy, modify, alter, or transfer, electronically or otherwise, the Software or any of the accompanying documentation except as expressly permitted in this Agreement; or (iii) redistribute, sell, rent, lease, sublicense, or otherwise transfer rights to the Software whether in a stand-alone configuration or as incorporated with other software code written by any party except as expressly permitted in this Agreement. + +You will not use the Software to engage in or allow others to engage in any illegal activity. + +You will not engage in use of the Software that will interfere with or damage the operation of the services of third parties by overburdening/disabling network resources through automated queries, excessive usage or similar conduct. + +You will not use the Software to engage in any activity that will violate the rights of third parties, including, without limitation, through the use, public display, public performance, reproduction, distribution, or modification of communications or materials that infringe copyrights, trademarks, publicity rights, privacy rights, other proprietary rights, or rights against defamation of third parties. + +You will not transfer the Software or utilize the Software in combination with third party software authored by you or others to create an integrated software program which you transfer to unrelated third parties. + +5. UPGRADES, UPDATES AND ENHANCEMENTS + +All upgrades, updates or enhancements of the Software shall be deemed to be part of the Software and will be subject to this Agreement. The Software may communicate with dotSyntax's servers to check for available updates to the Software, including bug fixes, patches, enhanced functions, missing plug-ins and new versions (collectively, "Updates"). During this process, the Software sends dotSyntax a request for the latest version information. By installing the Software, you hereby agree to automatically request and receive Updates from dotSyntax's servers. + +6. DISCLAIMER OF WARRANTY + +THE SOFTWARE IS PROVIDED ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES THAT IT IS FREE OF DEFECTS, VIRUS FREE, ABLE TO OPERATE ON AN UNINTERRUPTED BASIS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE AND AGREEMENT. NO USE OF THE SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +7. LIMITATION OF LIABILITY + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL DOTSYNTAX BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED. IN ANY CASE, DOTSYNTAX'S COLLECTIVE LIABILITY UNDER ANY PROVISION OF THIS LICENSE SHALL NOT EXCEED IN THE AGGREGATE THE SUM OF THE FEES (IF ANY) YOU PAID FOR THIS LICENSE. + +8. CHANGES TO THE SOFTWARE; ADDITIONAL LIABILITY LIMITATION. + +WE MAY CHANGE THE SOFTWARE OR DELETE FEATURES AT ANY TIME AND FOR ANY REASON. WITHOUT LIMITING THE GENERAL NATURE OF SECTIONS 6 AND 7, DOTSYNTAX IS NOT RESPONSIBLE OR LIABLE FOR (1) ANY CONTENT, INCLUDING WITHOUT LIMITATION, ANY INFRINGING, INACCURATE, OBSCENE, INDECENT, THREATENING, OFFENSIVE, DEFAMATORY, TORTIOUS, OR ILLEGAL CONTENT, OR (2) ANY THIRD PARTY CONDUCT, TRANSMISSIONS OR DATA. IN ADDITION, WITHOUT LIMITING THE GENERALITY OF SECTIONS 6 AND 7, DOTSYNTAX IS NOT RESPONSIBLE OR LIABLE FOR (1) ANY VIRUSES OR OTHER DISABLING FEATURES THAT AFFECT YOUR ACCESS TO OR USE OF THE SOFTWARE, (2) ANY INCOMPATIBILITY BETWEEN THE SOFTWARE AND OTHER WEB SITES, SERVICES, SOFTWARE AND HARDWARE, (3) ANY DELAYS OR FAILURES YOU MAY EXPERIENCE IN INITIATING, CONDUCTING OR COMPLETING ANY TRANSMISSIONS OR TRANSACTIONS IN CONNECTION WITH THE SOFTWARE IN AN ACCURATE OR TIMELY MANNER, OR (4) ANY DAMAGES OR COSTS OF ANY TYPE ARISING OUT OF OR IN ANY WAY CONNECTED WITH YOUR USE OF ANY SERVICES AVAILABLE FROM THIRD PARTIES THROUGH LINKS. THE LIMITATIONS, EXCLUSIONS AND DISCLAIMERS OF THIS CONTRACT APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, AND ARE NOT INTENDED TO DEPRIVE YOU OF ANY MANDATORY PROTECTIONS PROVIDED TO YOU UNDER APPLICABLE LAW. + +9. LEGENDS AND NOTICES + +You agree that you will not remove or alter any trademark, logo, copyright or other proprietary notices, legends, symbols or labels in the Software or any accompanying documentation. + +10. TERMINATION OF SOFTWARE AND SERVICE. + +This Agreement is effective upon your acceptance as provided herein and payment of the applicable license fees (if any), and will remain in force until terminated. You may terminate the licenses granted in this Agreement at any time by destroying the Software and any accompanying documentation, together with any and all copies thereof. The licenses granted in this Agreement will terminate automatically if you breach any of its terms or conditions or any of the terms or conditions of any other agreement between you and dotSyntax. Upon termination, you shall immediately destroy the original and all copies of the Software and any accompanying documentation, or return them to dotSyntax. + +11. SOFTWARE SUGGESTIONS + +dotSyntax welcomes suggestions for enhancing the Software and any accompanying documentation that may result in computer programs, reports, presentations, documents, ideas or inventions relating or useful to dotSyntax's business. You acknowledge that all title, ownership rights, and intellectual property rights concerning such suggestions shall become the exclusive property of dotSyntax and may be used for its business purposes in its sole discretion without any payment or accounting to you. + +12. EXPORT CONTROL + +The Software may contain encryption and is subject to United States export control laws and regulations and may be subject to export or import regulations in other countries, including controls on encryption products. You agree that you will not export, re-export or transfer the Software in violation of any applicable laws or regulations of the United States or the country where you legally obtained it. You are responsible for obtaining any licenses to export, re-export, transfer or import the Software. + +In addition to the above, the Software may not be used by, or exported or re-exported to: (i) any U.S. or EU sanctioned or embargoed country, or to nationals or residents of such countries; or (ii) to any person, entity or organization or other party identified on the U.S. Department of Commerce's Table of Denial Orders or the U.S. Department of Treasury's lists of Specially Designated Nationals and Blocked Persons, as published and revised from time to time; (iii) to any party engaged in nuclear, chemical/biological weapons or missile proliferation activities, unless authorized by U.S. and local (as required) law or regulations. + +12. COPYRIGHT AND TRADEMARK INFORMATION + +COPYRIGHT NOTICE: Copyright 2007 - 2009 dotSyntax, LLC. All Rights Reserved. +"Digsby" and the digsby egg shaped logo are registered trademarks of dotSyntax, LLC. All other trademarks, trade names service marks, service names are the property of their respective holders, including but not limited to, the following: +* "AIM" and the running man logo are registered trademarks of America Online, Inc. +* "Google Talk" is a registered trademark of Google Inc. +* "ICQ" is a registered mark of ICQ, Inc. +* "JABBER" is a registered trademark of the Jabber Software Foundation +* "MSN" and the butterfly logo are registered trademarks of Microsoft Corporation. +* "Yahoo!" and "Yahoo! Messenger" are registered trademarks of Yahoo, Inc. +* "GMail" is a registered trademark of Google Inc. +* "Facebook" is a registered trademark of Facebook Inc. +* "MySpace" is a registered trademark MySpace Inc. +* "LinkedIn" is a registered trademark of LinkedIn Inc. +Digsby and dotSyntax are in no way affiliated with or endorsed by America Online, Inc., Google Inc., ICQ, Inc., Jabber Software Foundation, Microsoft Corporation, Yahoo, Inc., Facebook Inc., MySpace Inc., LinkedIn, Inc. + +All "Conversation Themes", sound sets, and emoticon sets are used with the consent of their original creators and/or license owners. + +14. MISCELLANEOUS + +This Agreement constitutes the entire agreement between the parties concerning the Software, and may be amended only by a writing signed by both parties. This Agreement shall be governed by the laws of the State of New York, excluding its conflict of law provisions. All disputes relating to this Agreement are subject to the exclusive jurisdiction of the courts of New York and you expressly consent to the exercise of personal jurisdiction in the courts of New York in connection with any such dispute. This Agreement shall not be governed by the United Nations Convention on Contracts for the International Sale of Goods. If any provision in this Agreement should be held illegal or unenforceable by a court of competent jurisdiction, such provision shall be modified to the extent necessary to render it enforceable without losing its intent, or severed from this Agreement if no such modification is possible, and other provisions of this Agreement shall remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, shall not waive such term or condition or any subsequent breach thereof. + +15. USAGE OF COMPUTER RESOURCES. + +You agree to permit the Software to use the processing power of your computer when it is idle to run downloaded algorithms (mathematical equations) and code within a process. You understand that when the Software uses your computer, it likewise uses your CPU, bandwidth, and electrical power. The Software will use your computer to solve distributed computing problems, such as but not limited to, accelerating medical research projects, analyzing the stock market, searching the web, and finding the largest known prime number. This functionality is completely optional and you may disable it at any time. + +16. DIGSBY DONATES +dotSyntax offers a browser plugin called Digsby Donates. Installation and usage of the plugin Software indicates your acceptance of the terms of service found at http://www.digsby.com/freecause.php + +17. PERFORMANCE AND USAGE DATA. + +dotSyntax may automatically upload performance and usage data for evaluating the Software. Such data will not personally identify you. + +18. CUSTOMER SUPPORT. + +Please consult the "help" section of the Software for how we may provide you with customer support ("Support"). Unless you have entered into a separate written support contract with dotSyntax for the Software, however, we may terminate any Support we provide at any time. + +Authorized third-party software that uses the Software is not supported by dotSyntax. You should contact the provider of such software for support, if any. + +19. PRIVACY POLICY + +The Privacy Policy available at http://www.digsby.com/privacy.php explains dotSyntax's information practices that apply to your Registration Information and other information about you and your use of Digsby. You consent to the transfer of this information to and within the United States or other countries for processing and storage by us. Additionally, you agree that Digsby may use your username and password to authenticate you on the Software. + +PRIVACY POLICY +Last Updated: May 20, 2008 + +This privacy policy governs your use of the DIGSBY software, the DIGSBY service, the DIGSBY websites, the DIGSBY Widget, and all other software, features, tools, websites, widgets, and services offered by or through DIGSBY ("DIGSBY Products") from DOTSYNTAX LLC and its business divisions. + +When you register with DIGSBY, and use DIGSBY Products, you provide DIGSBY and DOTSYNTAX LLC with personally identifiable information (your "DIGSBY information"). This Policy explains the information practices that apply to your DIGSBY information. This Policy does not apply to information collected by third party Web sites linked to or otherwise accessible from offerings presented through DIGSBY Products and DOTSYNTAX LLC. The information collected or received by third party Web sites is subject to their own privacy policies. + +Collection of Your DIGSBY Information +Your DIGSBY information consists of personally identifiable information collected or received about you when you register to use DIGSBY or log in to DIGSBY as a registered user and interact with DIGSBY Products. Depending on how you use DIGSBY Products, your DIGSBY information may include registration-related information (such as e-mail address); information about your visits to DIGSBY Web sites and pages; information about contacts on your Buddy List (such as alias, SMS numbers, and email addresses); information about the features or offerings from DIGSBY that you use, and how frequently you use them; customer service information about you as a DIGSBY user; and other information specifically related to your use of a particular DIGSBY feature or offering. + +In order to access your third party Instant Messaging, Email, and Social Network accounts, you must enter your applicable user name(s) and password(s) in to DIGSBY. This information is stored on our servers and is encrypted in a manner that would prevent anyone, including DOTSYNTAX LLC employees from accessing your passwords. We are 100% transparent about our security procedures and you can read about the exact steps we take to protect your data at http://security.digsby.com. + +Your DIGSBY information may also include certain technical information gathered by DIGSBY and DOTSYNTAX LLC when you use DIGSBY Products. Some of the technical information that may be collected or received includes: the type of browser you are using (e.g., Firefox, Internet Explorer), the type of operating system you are using (e.g., Windows XP or Mac OS), CPU type (e.g. Pentium), your manner of connecting to the Internet (e.g, connection speed through narrowband or broadband access); Internet protocol address; other information about your geographic location; and the domain name of your Internet service provider. + +How Your DIGSBY Information Is Used +Your DIGSBY information is used to operate and improve the features, offerings and content presented to you by DIGSBY and DOTSYNTAX LLC; to personalize the content provided to you; to fulfill your requests for products, programs, and services; to communicate with you and respond to your inquiries; to conduct research about your use of DIGSBY Products; and to help offer you other products, programs, or services that may be of interest. + +Your DIGSBY information will not be shared with third parties except in circumstances in which you have consented to the sharing of your DIGSBY information. Your IM conversations are not sent to DIGSBY servers or stored at DOTSYNTAX LLC owned facilities. However, certain features such as "Chat Logs" result in Instant Messaging history being stored on the computer you use to access DIGSBY. DOTSYNTAX LLC does not read your private online communications when you use any of the communication tools offered as DIGSBY Products. If, however, you use these tools to disclose information about yourself publicly (for example, in chat rooms or online message boards made available by DIGSBY), other online users may obtain access to any information you provide. + +Your DIGSBY information may be accessed and disclosed in response to legal process (for example, a court order, search warrant or subpoena), or in other circumstances in which DOTSYNTAX LLC has a good faith belief that DIGSBY is being used for unlawful purposes. DOTSYNTAX LLC may also access or disclose your DIGSBY information when necessary to protect the rights or property of DIGSBY or DOTSYNTAX LLC, or in special cases such as a threat to your safety or that of others. + +DOTSYNTAX LLC may use agents and contractors in order to help operate DIGSBY Products. If such agents and contractors have access to personally identifiable information, they are required to protect this information in a manner that is consistent with this Privacy Policy by, for example, not using the information for any purpose other than to carry out the services they are performing for DOTSYNTAX LLC. + +In the event that ownership of DIGSBY was to change as a result of a merger or acquisition by another company, your DIGSBY information may be transferred. DOTSYNTAX LLC will provide you notice prior to any such transfer of your DIGSBY information. + +Cookies +DOTSYNTAX LLC may use cookies on the Websites it operates. Cookies are small text files that are placed on your computer's hard drive by our servers to identify your computer to our servers. Except for your IP Address, no personally identifiable information stored in the DIGSBY cookies will be shared with outside companies for any purpose without your permission. + +Our Commitment to Security +DOTSYNTAX LLC has established safeguards to help prevent unauthorized access to or misuse of your DIGSBY information, but cannot guarantee that your personally identifiable information will never be disclosed in a manner inconsistent with this Privacy Policy (for example, in the event of unauthorized acts by third parties that violate applicable law or the policies of DIGSBY or DOTSYNTAX LLC. To protect your privacy and security, DOTSYNTAX LLC uses passwords to help verify your identity before granting access or making corrections to any of your DIGSBY information. These passwords are encrypted in a way which prevents them from being accessed by anyone including all DOTSYNTAX LLC employees. If you forget or misplace your DIGSBY password, an email will be sent to the email address on record with instructions on how to set a new password. You will be required to re-enter all of your Instant Messaging, Email, and Social Network account passwords when resetting your DIGSBY password. + +Special Note for Parents +DIGSBY is intended for a general audience, and children under the age of thirteen are not permitted to register with DIGSBY. + +How to Contact Us +If you have any questions or concerns about the DIGSBY Privacy Policy or its implementation, you may contact us by sending an email to privacy[at]digsby[dot]com or by sending mail to the following address: + +dotSyntax, LLC +125 Tech Park Drive +Rochester, NY 14623 + +Changes to this Privacy Policy and Additional Information +The DIGSBY Privacy Policy may be updated from time to time, and so you should review this Policy periodically. + +DIGSBY may in the future offer other special features and services, and if these features and services are not described in this Policy, you will be provided with additional privacy-related information in this agreement. \ No newline at end of file diff --git a/installer/Licenses/Aspell.txt b/installer/Licenses/Aspell.txt new file mode 100644 index 0000000..e68775a --- /dev/null +++ b/installer/Licenses/Aspell.txt @@ -0,0 +1 @@ +Aspell (project lead is Kevin Atkinson) is covered under the Lesser Gnu Public License (LGPL). \ No newline at end of file diff --git a/installer/Licenses/DNSPython.txt b/installer/Licenses/DNSPython.txt new file mode 100644 index 0000000..e8cc025 --- /dev/null +++ b/installer/Licenses/DNSPython.txt @@ -0,0 +1,14 @@ +Copyright (C) 2001-2003 Nominum, Inc. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose with or without fee is hereby granted, +provided that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/installer/Licenses/DigsbyAlpha.txt b/installer/Licenses/DigsbyAlpha.txt new file mode 100644 index 0000000..53fd1e1 --- /dev/null +++ b/installer/Licenses/DigsbyAlpha.txt @@ -0,0 +1,94 @@ +This Alpha Test Agreement ("Agreement") is made and effective from the 21st day of December, 2007, until the 1st day of February, 2008 by and between dotSyntax, LLC ("Developer") and Alpha Tester ("Recipient"). + +Developer is the owner of a prototype product (the "Product") which it desires to have tested by a prospective user in what is commonly referred to as "Alpha Test".The Product contains valuable, confidential, trade secret information owned by Developer.The Recipient desires to test and evaluate the Product. + +NOW, THEREFORE, in consideration of the promises set forth herein, the parties hereto agree as follows: + + 1. Arrangement. + + Developer agrees to provide to Recipient the Product, and Recipient accepts the Product, subject to the terms of this Agreement. Recipient agrees to test and evaluate the Product as provided herein, and report to Developer with respect to the usefulness and functionality of Product. + + 2. Non-Disclosure. + + A. Recipient acknowledges and agrees that in providing the Product, Developer may disclose to Recipient certain confidential, proprietary trade secret information of Developer (the "Confidential Information"). Confidential Information may include, but is not limited to, the Product, computer programs, flowcharts, diagrams, manuals, schematics, development tools, specifications, design documents, marketing information, screen shots, or any descriptions of the product. During this Agreement, Recipient agrees that it will not, without the express prior written consent of Developer, disclose any Confidential Information or any part thereof to any third party, except to the extent that such Confidential Information a) is or becomes generally available to the public through no fault of Recipient; or b) is rightfully received by Recipient from a third party without limitation as to its use. At the termination of this Agreement, Recipient may publicly disclose any Confidential Information learned about the Product. + + B. Recipient also agrees that it shall not duplicate, translate, modify, copy, printout, disassemble, decompile or otherwise tamper with the Product or any firmware, circuit board or software provided therewith. + + C. Recipient also agrees that it shall not provide any third party with screen shots, descriptions, demonstrations, or other information about the Product. + + 3. License. + + Recipient acknowledges that Recipients shall have only a limited, non-exclusive, nontransferable license to use the Product for a period to be concluded by notification by Developer. Recipient acknowledges and agrees that it will not use the Product for any purpose that is illegal. Because the Product is a "Alpha Test" version only and is not error or bug free, Recipient agrees that it will use the Product carefully and will not use it in any way which might result in any loss of its or any third party's property or information. + + 4. Termination. + + Recipient may terminate this Agreement at any time prior to expiration of the Alpha Test by deleting the Product including all Confidential Information and copies thereof. Developer may terminate this Agreement upon notice to Recipient, subject to Recipient's obligation to return the Product, Confidential Information and all copies thereof. The obligations of Recipient in Section 2 above shall survive the termination of this Agreement. If not earlier terminated, this Agreement shall terminate automatically upon the end of the period set forth in this Agreement. + 5. Developer's Warranties. + + Developer represents and warrants that it has the requisite right and legal authority to grant the license and provide the Product and the Confidential Information as contemplated by this Agreement. DEVELOPER MAKES NO OTHER WARRANTY, EXPRESS OR IMPLIED, WITH RESPECT TO THE PRODUCT OR ANY OTHER CONFIDENTIAL INFORMATION AND ALL OTHER WARRANTIES, WHETHER EXPRESS OR IMPLIED, ARE HEREBY DISCLAIMED, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. DEVELOPER'S SOLE LIABILITY FOR BREACH OF THE REPRESENTATION AND WARRANTY ABOVE, AND RECIPIENT'S SOLE REMEDY, SHALL BE THAT DEVELOPER SHALL INDEMNIFY AND HOLD RECIPIENT HARMLESS FROM AND AGAINST ANY LOSS, SUIT, DAMAGE, CLAIM OR DEFENSE ARISING OUT OF BREACH OF THE REPRESENTATION AND WARRANTY, INCLUDING REASONABLE ATTORNEYS' FEES. + + 7. Governing Law. + + This Agreement is to be governed by, construed and enforced according to the laws of the State of New York. + + 8. No Assignment. + + Recipient may not assign this Agreement without the prior written consent of Developer. This Agreement shall be binding upon and inured to the benefit of the parties and their respective administrators, successors and assigns. + + 9. Headings. + + Headings used in this Agreement are provided for convenience only and shall not be used to construe meaning or intent. + + 10. Final Agreement. + + This Agreement terminates and supersedes all prior understandings or agreements on the subject matter hereof. This Agreement may be modified only by a further writing that is duly executed by both parties. + + 11. Arbitration. + + The parties agree that they will use their best efforts to amicably resolve any dispute arising out of or relating to this Agreement. Any controversy, claim or dispute that cannot be so resolved shall be settled by final binding arbitration in accordance with the rules of the American Arbitration Association, and judgment upon the award rendered by the arbitrator or arbitrators may be entered in any court having jurisdiction thereof. Any such arbitration shall be conducted in the city where the Developer's headquarters are located, or such other place as may be mutually agreed upon by the parties. Within fifteen (15) days after the commencement of the arbitration, each party shall select one person to act as arbitrator, and the two arbitrators so selected shall select a third arbitrator within ten (10) days of their appointment. Each party shall bear its own costs and expenses and an equal share of the arbitrators expenses and administrative fees of arbitration. + +Privacy Policy +Last Updated: December 21, 2007 + +This privacy policy governs your use of the DIGSBY software, the DIGSBY service, the DIGSBY websites, the DIGSBY Widget, and all other software, features, tools, websites, widgets, and services offered by or through DIGSBY ("DIGSBY Products") from DOTSNYTAX LLC and its business divisions. + +When you register with DIGSBY, and use DIGSBY Products, you provide DIGSBY and DOTSYNTAX LLC with personally identifiable information (your "DIGSBY information"). This Policy explains the information practices that apply to your DIGSBY information. This Policy does not apply to information collected by third party Web sites linked to or otherwise accessible from offerings presented through DIGSBY Products and DOTSYNTAX LLC. The information collected or received by third party Web sites is subject to their own privacy policies. + +Collection of Your DIGSBY Information +Your DIGSBY information consists of personally identifiable information collected or received about you when you register to use DIGSBY or log in to DIGSBY as a registered user and interact with DIGSBY Products. Depending on how you use DIGSBY Products, your DIGSBY information may include registration-related information (such as e-mail address); information about your visits to DIGSBY Web sites and pages; information about contacts on your Buddy List (such as alias, SMS numbers, and email addresses); information about the features or offerings from DIGSBY that you use, and how frequently you use them; customer service information about you as a DIGSBY user; and other information specifically related to your use of a particular DIGSBY feature or offering. + +In order to access your third party Instant Messaging, Email, and Social Network accounts, you must enter your applicable user name(s) and password(s) on DIGSBY. This information is stored on our servers and is encrypted in a manor that would prevent anyone, including DOTSYNTAX LLC employees from accessing your passwords. + +Your DIGSBY information may also include certain technical information gathered by DIGSBY and DOTSYNTAX LLC when you use DIGSBY Products. Some of the technical information that may be collected or received includes: the type of browser you are using (e.g., Firefox, Internet Explorer), the type of operating system you are using (e.g., Windows XP or Mac OS), CPU type (e.g. Pentium), your manner of connecting to the Internet (e.g, connection speed through narrowband or broadband access); Internet protocol address; other information about your geographic location; and the domain name of your Internet service provider. + +How Your DIGSBY Information Is Used +Your DIGSBY information is used to operate and improve the features, offerings and content presented to you by DIGSBY and DOTSYNTAX LLC; to personalize the content provided to you; to fulfill your requests for products, programs, and services; to communicate with you and respond to your inquiries; to conduct research about your use of DIGSBY Products; and to help offer you other products, programs, or services that may be of interest. + +Your DIGSBY information will not be shared with third parties except in circumstances in which you have consented to the sharing of your DIGSBY information. Your IM conversations are not sent to DIGSBY servers or stored at DOTSYNTAX LLC owned facilities. However, certain features such as "Chat Logs" result in Instant Messaging history being stored on the computer you use to access DIGSBY. DOTSNYTAX LLC does not read your private online communications when you use any of the communication tools offered as DIGSBY Products. If, however, you use these tools to disclose information about yourself publicly (for example, in chat rooms or online message boards made available by DIGSBY), other online users may obtain access to any information you provide. + +Your DIGSBY information may be accessed and disclosed in response to legal process (for example, a court order, search warrant or subpoena), or in other circumstances in which DOTSYNTAX LLC has a good faith belief that DIGSBY is being used for unlawful purposes. DOTSYNTAX LLC may also access or disclose your DIGSBY information when necessary to protect the rights or property of DIGSBY or DOTSYNTAX LLC, or in special cases such as a threat to your safety or that of others. + +DOTSYNTAX LLC may use agents and contractors in order to help operate DIGSBY Products. If such agents and contractors have access to personally identifiable information, they are required to protect this information in a manner that is consistent with this Privacy Policy by, for example, not using the information for any purpose other than to carry out the services they are performing for DOTSYNTAX, LLC. + +In the event that ownership of DIGSBY was to change as a result of a merger or acquisition by another company, your DIGSBY information may be transferred. DOTSYNTAX LLC will provide you notice prior to any such transfer of your DIGSBY information. + +Cookies +DOTSNYTAX may use cookies on the Websites it operates. Cookies are small text files that are placed on your computer's hard drive by our servers to identify your computer to our servers. Except for your IP Address, no personally identifiable information stored in the DIGSBY cookies will be shared with outside companies for any purpose without your permission. + +Our Commitment to Security +DOTSYNTAX LLC has established safeguards to help prevent unauthorized access to or misuse of your DIGSBY information, but cannot guarantee that your personally identifiable information will never be disclosed in a manner inconsistent with this Privacy Policy (for example, in the event of unauthorized acts by third parties that violate applicable law or the policies of DIGSBY or DOTSYNTAX LLC. To protect your privacy and security, DOTSYNTAX LLC uses passwords to help verify your identity before granting access or making corrections to any of your DIGSBY information. These passwords are encrypted in a way that they may not be retrieved by DOTSYNTAX LLC employees. If you forget or misplace your DIGSBY password, an email will be sent to the email address on record with instructions on how to set a new password. You will be required to re-enter all of your Instant Messaging, Email, and Social Network account passwords when resetting your DIGSBY password. + +Special Note for Parents +DIGSBY is intended for a general audience, and children under the age of thirteen are not permitted to register with DIGSBY. + +How to Contact Us +If you have any questions or concerns about the DIGSBY Privacy Policy or its implementation, you may contact us by sending an email to privacy[at]digsby[dot]com or by sending mail to the following address: + +dotSyntax, LLC +125 Tech Park Drive +Rochester, NY 14623 + +Changes to this Privacy Policy and Additional Information +The DIGSBY Privacy Policy may be updated from time to time, and so you should review this Policy periodically. + +DIGSBY may in the future offer other special features and services, and if these features and services are not described in this Policy, you will be provided with additional privacy-related information in this agreement. diff --git a/installer/Licenses/DigsbyTOS.txt b/installer/Licenses/DigsbyTOS.txt new file mode 100644 index 0000000..15def1e --- /dev/null +++ b/installer/Licenses/DigsbyTOS.txt @@ -0,0 +1,107 @@ +THIS IS A LEGAL AGREEMENT between "you," the end user of Digsby(R) brand software, and dotSyntax, LLC ("dotSyntax"). + +Use of the software you are about to download or install indicates your acceptance of these terms. You also agree to accept these terms by so indicating at the appropriate screen, prior to the download or installation process. As used in this Agreement, the capitalized term "Software" means the Digsby instant messaging software, the Digsby service, the Digsby websites, the Digsby Widget, and all other software, features, tools, websites, widgets, and services offered by dotSyntax and its business divisions together with any and all enhancements, upgrades, and updates that may be provided to you in the future by dotSyntax. IF YOU DO NOT AGREE TO THESE TERMS AND CONDITIONS, YOU SHOULD SO INDICATE AT THE APPROPRIATE SCREEN AND PROMPTLY DISCONTINUE THE INSTALLATION AND DOWNLOAD PROCESS. + +1. OWNERSHIP. + +The Software and any accompanying documentation are owned by dotSyntax and ownership of the Software shall at all times remain with dotSyntax. Copies are provided to you only to allow you to exercise your rights under this Agreement. This Agreement does not constitute a sale of the Software or any accompanying documentation, or any portion thereof. Without limiting the generality of the foregoing, you do not receive any rights to any patents, copyrights, trade secrets, trademarks or other intellectual property rights relating to or in the Software or any accompanying documentation. All rights not expressly granted to you under this Agreement are reserved by dotSyntax. + +2. HOW WE MAY MODIFY THIS CONTRACT. + +We may change this contract at any time. You must review this contract on a regular basis. You can find the most recent version of the contract at http://www.digsby.com/tos.php. The changed contract is in effect right away. If you do not agree to changes in the contract, then you must stop using the Software. If you do not stop using the Software, then your use of the Software will continue under the changed contract. + +3. HOW YOU MAY ACCESS AND USE THE SOFTWARE. + +You must be at least 13 years of age to use the Software. If dotSyntax discovers through reliable means that you are younger than 13, dotSyntax will cancel your account. + +We provide the Software for your personal use. You may use the Software while you are at work, but you may not use the Software to conduct business without a separate written contract with dotSyntax. + +You are responsible for all activity associated with your Software account and password. You must notify dotSyntax right away of any use of your account that you did not authorize or any breach in security known to you that relates to the Software. + +Do not provide your Software account and password to third parties. You may not authorize any third party to access and/or use the Software on your behalf. You may also not use any automated process or service to access and/or use the Software such as a BOT, a spider or periodic caching of information stored by dotSyntax. You may not use any software or services with the Software or authorized third-party software which modifies or reroutes, or attempts to modify or reroute, the Software. You may also not use any software or hardware that reduces the number of users directly accessing or using the Software (sometimes called 'multiplexing' or 'pooling' software or hardware). + +You may only use the Software or authorized third-party software to sign into and use the Software. + + +4. WHAT YOU MAY NOT DO WITH THE SOFTWARE. + +The privacy, safety and security of our Software and the users of our Software are very important to us. You may not use the Software in any way that could harm the Software, other Software users, dotSyntax or our affiliates. Some examples of harmful activity that we do not permit include: + +You will not, and will not permit others to: (i) reverse engineer, decompile, disassemble, derive the source code of, modify, or create derivative works from the Software; or (ii) use, copy, modify, alter, or transfer, electronically or otherwise, the Software or any of the accompanying documentation except as expressly permitted in this Agreement; or (iii) redistribute, sell, rent, lease, sublicense, or otherwise transfer rights to the Software whether in a stand-alone configuration or as incorporated with other software code written by any party except as expressly permitted in this Agreement. + +You will not use the Software to engage in or allow others to engage in any illegal activity. + +You will not engage in use of the Software that will interfere with or damage the operation of the services of third parties by overburdening/disabling network resources through automated queries, excessive usage or similar conduct. + +You will not use the Software to engage in any activity that will violate the rights of third parties, including, without limitation, through the use, public display, public performance, reproduction, distribution, or modification of communications or materials that infringe copyrights, trademarks, publicity rights, privacy rights, other proprietary rights, or rights against defamation of third parties. + +You will not transfer the Software or utilize the Software in combination with third party software authored by you or others to create an integrated software program which you transfer to unrelated third parties. + +5. UPGRADES, UPDATES AND ENHANCEMENTS + +All upgrades, updates or enhancements of the Software shall be deemed to be part of the Software and will be subject to this Agreement. The Software may communicate with dotSyntax's servers to check for available updates to the Software, including bug fixes, patches, enhanced functions, missing plug-ins and new versions (collectively, "Updates"). During this process, the Software sends dotSyntax a request for the latest version information. By installing the Software, you hereby agree to automatically request and receive Updates from dotSyntax's servers. + +6. DISCLAIMER OF WARRANTY + +THE SOFTWARE IS PROVIDED ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES THAT IT IS FREE OF DEFECTS, VIRUS FREE, ABLE TO OPERATE ON AN UNINTERRUPTED BASIS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE AND AGREEMENT. NO USE OF THE SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +7. LIMITATION OF LIABILITY + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL DOTSYNTAX BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED. IN ANY CASE, DOTSYNTAXS COLLECTIVE LIABILITY UNDER ANY PROVISION OF THIS LICENSE SHALL NOT EXCEED IN THE AGGREGATE THE SUM OF THE FEES (IF ANY) YOU PAID FOR THIS LICENSE. + +8. CHANGES TO THE SOFTWARE; ADDITIONAL LIABILITY LIMITATION. + +WE MAY CHANGE THE SOFTWARE OR DELETE FEATURES AT ANY TIME AND FOR ANY REASON. WITHOUT LIMITING THE GENERAL NATURE OF SECTIONS 6 AND 7, DOTSYNTAX IS NOT RESPONSIBLE OR LIABLE FOR (1) ANY CONTENT, INCLUDING WITHOUT LIMITATION, ANY INFRINGING, INACCURATE, OBSCENE, INDECENT, THREATENING, OFFENSIVE, DEFAMATORY, TORTIOUS, OR ILLEGAL CONTENT, OR (2) ANY THIRD PARTY CONDUCT, TRANSMISSIONS OR DATA. IN ADDITION, WITHOUT LIMITING THE GENERALITY OF SECTIONS 6 AND 7, DOTSYNTAX IS NOT RESPONSIBLE OR LIABLE FOR (1) ANY VIRUSES OR OTHER DISABLING FEATURES THAT AFFECT YOUR ACCESS TO OR USE OF THE SOFTWARE, (2) ANY INCOMPATIBILITY BETWEEN THE SOFTWARE AND OTHER WEB SITES, SERVICES, SOFTWARE AND HARDWARE, (3) ANY DELAYS OR FAILURES YOU MAY EXPERIENCE IN INITIATING, CONDUCTING OR COMPLETING ANY TRANSMISSIONS OR TRANSACTIONS IN CONNECTION WITH THE SOFTWARE IN AN ACCURATE OR TIMELY MANNER, OR (4) ANY DAMAGES OR COSTS OF ANY TYPE ARISING OUT OF OR IN ANY WAY CONNECTED WITH YOUR USE OF ANY SERVICES AVAILABLE FROM THIRD PARTIES THROUGH LINKS. THE LIMITATIONS, EXCLUSIONS AND DISCLAIMERS OF THIS CONTRACT APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, AND ARE NOT INTENDED TO DEPRIVE YOU OF ANY MANDATORY PROTECTIONS PROVIDED TO YOU UNDER APPLICABLE LAW. + +9. LEGENDS AND NOTICES + +You agree that you will not remove or alter any trademark, logo, copyright or other proprietary notices, legends, symbols or labels in the Software or any accompanying documentation. + +10. TERMINATION OF SOFTWARE AND SERVICE. + +This Agreement is effective upon your acceptance as provided herein and payment of the applicable license fees (if any), and will remain in force until terminated. You may terminate the licenses granted in this Agreement at any time by destroying the Software and any accompanying documentation, together with any and all copies thereof. The licenses granted in this Agreement will terminate automatically if you breach any of its terms or conditions or any of the terms or conditions of any other agreement between you and dotSyntax. Upon termination, you shall immediately destroy the original and all copies of the Software and any accompanying documentation, or return them to dotSyntax. + +11. SOFTWARE SUGGESTIONS + +dotSyntax welcomes suggestions for enhancing the Software and any accompanying documentation that may result in computer programs, reports, presentations, documents, ideas or inventions relating or useful to dotSyntax's business. You acknowledge that all title, ownership rights, and intellectual property rights concerning such suggestions shall become the exclusive property of dotSyntax and may be used for its business purposes in its sole discretion without any payment or accounting to you. + +12. EXPORT CONTROL + +The Software may contain encryption and is subject to United States export control laws and regulations and may be subject to export or import regulations in other countries, including controls on encryption products. You agree that you will not export, re-export or transfer the Software in violation of any applicable laws or regulations of the United States or the country where you legally obtained it. You are responsible for obtaining any licenses to export, re-export, transfer or import the Software. + +In addition to the above, the Software may not be used by, or exported or re-exported to: (i) any U.S. or EU sanctioned or embargoed country, or to nationals or residents of such countries; or (ii) to any person, entity or organization or other party identified on the U.S. Department of Commerces Table of Denial Orders or the U.S. Department of Treasurys lists of Specially Designated Nationals and Blocked Persons, as published and revised from time to time; (iii) to any party engaged in nuclear, chemical/biological weapons or missile proliferation activities, unless authorized by U.S. and local (as required) law or regulations. + +12. COPYRIGHT AND TRADEMARK INFORMATION + +COPYRIGHT NOTICE: Copyright 2007 2008 dotSyntax, LLC. All Rights Reserved. +"Digsby" and the digsby egg shaped logo are registered trademarks of dotSyntax, LLC. All other trademarks, trade names service marks, service names are the property of their respective holders, including but not limited to, the following: +* "AIM" and the running man logo are registered trademarks of America Online, Inc. +* "Google Talk" is a registered trademark of Google Inc. +* "ICQ" is a registered mark of ICQ, Inc. +* "JABBER" is a registered trademark of the Jabber Software Foundation +* "MSN" and the butterfly logo are registered trademarks of Microsoft Corporation. +* "Yahoo!" and "Yahoo! Messenger" are registered trademarks of Yahoo, Inc. +* "GMail" is a registered trademark of Google Inc. +* "Facebook" is a registered trademark of Facebook Inc. +* "MySpace" is a registered trademark MySpace Inc. +Digsby and dotSyntax are in no way affiliated with or endorsed by America Online, Inc., Google Inc., ICQ, Inc., Jabber Software Foundation, Microsoft Corporation, Yahoo, Inc., Facebook Inc., or MySpace Inc. + +All "Conversation Themes", sound sets, and emoticon sets are used with the consent of their original creators and/or license owners. + +14. MISCELLANEOUS + +This Agreement constitutes the entire agreement between the parties concerning the Software, and may be amended only by a writing signed by both parties. This Agreement shall be governed by the laws of the State of New York, excluding its conflict of law provisions. All disputes relating to this Agreement are subject to the exclusive jurisdiction of the courts of New York and you expressly consent to the exercise of personal jurisdiction in the courts of New York in connection with any such dispute. This Agreement shall not be governed by the United Nations Convention on Contracts for the International Sale of Goods. If any provision in this Agreement should be held illegal or unenforceable by a court of competent jurisdiction, such provision shall be modified to the extent necessary to render it enforceable without losing its intent, or severed from this Agreement if no such modification is possible, and other provisions of this Agreement shall remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, shall not waive such term or condition or any subsequent breach thereof. + +15. PERFORMANCE AND USAGE DATA. + +dotSyntax may automatically upload performance and usage data for evaluating the Software. Such data will not personally identify you. + +16. CUSTOMER SUPPORT. + +Please consult the "help" section of the Software for how we may provide you with customer support ("Support"). Unless you have entered into a separate written support contract with dotSyntax for the Software, however, we may terminate any Support we provide at any time. + +Authorized third-party software that uses the Software is not supported by dotSyntax. You should contact the provider of such software for support, if any. + +17. PRIVACY POLICY + +The Privacy Policy available at http://www.digsby.com/privacy.php explains dotSyntaxs information practices that apply to your Registration Information and other information about you and your use of Digsby. You consent to the transfer of this information to and within the United States or other countries for processing and storage by us. Additionally, you agree that Digsby may use your username and password to authenticate you on the Software. \ No newline at end of file diff --git a/installer/Licenses/LGPL.txt b/installer/Licenses/LGPL.txt new file mode 100644 index 0000000..e043525 --- /dev/null +++ b/installer/Licenses/LGPL.txt @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/installer/Licenses/M2Crypto.txt b/installer/Licenses/M2Crypto.txt new file mode 100644 index 0000000..86550c2 --- /dev/null +++ b/installer/Licenses/M2Crypto.txt @@ -0,0 +1,25 @@ +M2Crypto --------------------- +Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. + +Portions copyright (c) 2004-2006 Open Source Applications Foundation. +All rights reserved. + +Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. +All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation. + +THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/installer/Licenses/PIL.txt b/installer/Licenses/PIL.txt new file mode 100644 index 0000000..f4710e7 --- /dev/null +++ b/installer/Licenses/PIL.txt @@ -0,0 +1,10 @@ +The Python Imaging Library (PIL) is + + Copyright 1997-2006 by Secret Labs AB + Copyright 1995-2006 by Fredrik Lundh + +By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions: + +Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. + +SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/installer/Licenses/PyDES.txt b/installer/Licenses/PyDES.txt new file mode 100644 index 0000000..b68a38f --- /dev/null +++ b/installer/Licenses/PyDES.txt @@ -0,0 +1 @@ +Thanks to Todd Whiteman (twhitema@gmail.com) for pyDES, which he has kindly placed in the public domain. \ No newline at end of file diff --git a/installer/Licenses/PyXMPP.txt b/installer/Licenses/PyXMPP.txt new file mode 100644 index 0000000..8f15bf9 --- /dev/null +++ b/installer/Licenses/PyXMPP.txt @@ -0,0 +1 @@ +PyXMPP (project lead is Jacek Konieczny) is covered under the Lesser Gnu Public License (LGPL). \ No newline at end of file diff --git a/installer/Licenses/Python.txt b/installer/Licenses/Python.txt new file mode 100644 index 0000000..ff54ac5 --- /dev/null +++ b/installer/Licenses/Python.txt @@ -0,0 +1,276 @@ +The following license applies to: + Python - Copyright (C) 1990-2007 - Python Software Foundation + BeautifulSoup - Copyright (C) 2006 - Leonard Richardson, et al + ThreadPool - Copyright (C) 2006 - Christopher Arndt + +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.2 2.1.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2.1 2.2 2002 PSF yes + 2.2.2 2.2.1 2002 PSF yes + 2.2.3 2.2.2 2003 PSF yes + 2.3 2.2.2 2002-2003 PSF yes + 2.3.1 2.3 2002-2003 PSF yes + 2.3.2 2.3.1 2002-2003 PSF yes + 2.3.3 2.3.2 2002-2003 PSF yes + 2.3.4 2.3.3 2004 PSF yes + 2.3.5 2.3.4 2005 PSF yes + 2.4 2.3 2004 PSF yes + 2.4.1 2.4 2005 PSF yes + 2.4.2 2.4.1 2005 PSF yes + 2.4.3 2.4.2 2006 PSF yes + 2.4.4 2.4.3 2006 PSF yes + 2.5 2.4 2006 PSF yes + 2.5.1 2.5 2007 PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python +alone or in any derivative version, provided, however, that PSF's +License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative +version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/installer/Licenses/Riceballs-Emoticons.txt b/installer/Licenses/Riceballs-Emoticons.txt new file mode 100644 index 0000000..50d910a --- /dev/null +++ b/installer/Licenses/Riceballs-Emoticons.txt @@ -0,0 +1,5 @@ +Riceballs Emoticons + +Copyright (c) 2007-2009 David Lanham (http://www.dlanham.com) + +Used with permission from copyright owner. \ No newline at end of file diff --git a/installer/Licenses/Rijndael.txt b/installer/Licenses/Rijndael.txt new file mode 100644 index 0000000..7ab57c1 --- /dev/null +++ b/installer/Licenses/Rijndael.txt @@ -0,0 +1,24 @@ +Rijndael cipher from the BSD project + * rijndael-alg-fst.h + * + * @version 3.0 (December 2000) + * + * Optimised ANSI C code for the Rijndael cipher (now AES) + * + * @author Vincent Rijmen + * @author Antoon Bosselaers + * @author Paulo Barreto + * + * This code is hereby placed in the public domain. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/installer/Licenses/SocksiPy.txt b/installer/Licenses/SocksiPy.txt new file mode 100644 index 0000000..fc33078 --- /dev/null +++ b/installer/Licenses/SocksiPy.txt @@ -0,0 +1,22 @@ +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. diff --git a/installer/Licenses/Syck.txt b/installer/Licenses/Syck.txt new file mode 100644 index 0000000..0a30054 --- /dev/null +++ b/installer/Licenses/Syck.txt @@ -0,0 +1,25 @@ +Copyright (c) 2003 why the lucky stiff + +This software is subject to either of two licenses (BSD or D&R), which you can choose +from in your use of the code. The terms for each of these licenses is listed below: + +BSD License +=========== + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/installer/Licenses/TLSLite.txt b/installer/Licenses/TLSLite.txt new file mode 100644 index 0000000..12e3d25 --- /dev/null +++ b/installer/Licenses/TLSLite.txt @@ -0,0 +1 @@ +Thanks to Trevor Perrin (trevp@trevp.net) for TLSLite, which he has kindly placed in the public domain. \ No newline at end of file diff --git a/installer/Licenses/WebKit.txt b/installer/Licenses/WebKit.txt new file mode 100644 index 0000000..672604e --- /dev/null +++ b/installer/Licenses/WebKit.txt @@ -0,0 +1,26 @@ + + Copyright (C) 2005 Apple Computer, Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/installer/Licenses/ZSI.txt b/installer/Licenses/ZSI.txt new file mode 100644 index 0000000..98320f9 --- /dev/null +++ b/installer/Licenses/ZSI.txt @@ -0,0 +1,13 @@ +COPYRIGHT + +Copyright 2001, Zolera Systems, Inc. +All Rights Reserved. + +Copyright 2002-2003, Rich Salz. +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. \ No newline at end of file diff --git a/installer/Licenses/comtypes.txt b/installer/Licenses/comtypes.txt new file mode 100644 index 0000000..9b55352 --- /dev/null +++ b/installer/Licenses/comtypes.txt @@ -0,0 +1,20 @@ + Copyright (c) 2000 - 2006 Thomas Heller + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/installer/Licenses/libxml2.txt b/installer/Licenses/libxml2.txt new file mode 100644 index 0000000..1ca4af6 --- /dev/null +++ b/installer/Licenses/libxml2.txt @@ -0,0 +1,99 @@ +========================== + +Except where otherwise noted in the source code (trio files, hash.c and list.c) +covered by a similar licence but with different Copyright notices: + + Copyright (C) 1998-2002 Daniel Veillard. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- +NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Daniel Veillard shall not +be used in advertising or otherwise to promote the sale, use or other deal- +ings in this Software without prior written authorization from him. +========================== +trio.c, trio.h: +Copyright (C) 1998 Bjorn Reese and Daniel Stenberg. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +triop.h: +Copyright (C) 2000 Bjorn Reese and Daniel Stenberg. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +hash.c: +Copyright (C) 2000 Bjorn Reese and Daniel Veillard. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +list.c: +Copyright (C) 2000 Gary Pennington and Daniel Veillard. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +triodef.h, trionan.c, trionan.h: +Copyright (C) 2001 Bjorn Reese + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. +========================== +triostr.c, triostr.h: +Copyright (C) 2001 Bjorn Reese and Daniel Stenberg. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND +CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. \ No newline at end of file diff --git a/installer/Licenses/python-twitter.txt b/installer/Licenses/python-twitter.txt new file mode 100644 index 0000000..58ce2fb --- /dev/null +++ b/installer/Licenses/python-twitter.txt @@ -0,0 +1,13 @@ + Copyright 2007 Google Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the 'License'); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an 'AS IS' BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/installer/Licenses/simplejson.txt b/installer/Licenses/simplejson.txt new file mode 100644 index 0000000..ad95f29 --- /dev/null +++ b/installer/Licenses/simplejson.txt @@ -0,0 +1,19 @@ +Copyright (c) 2006 Bob Ippolito + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/installer/Licenses/urllib2_file.txt b/installer/Licenses/urllib2_file.txt new file mode 100644 index 0000000..1e977d8 --- /dev/null +++ b/installer/Licenses/urllib2_file.txt @@ -0,0 +1,23 @@ + + urllib2_file Copyright (C) 2004,2005,2006 Fabien SEISEN + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + you can contact me at: + http://fabien.seisen.org/python/ + + Also modified by Adam Ambrose (aambrose @T pacbell.net) to write data in + chunks (hardcoded to CHUNK_SIZE for now), so the entire contents of the file + don't need to be kept in memory. diff --git a/installer/Licenses/wxWidgets.txt b/installer/Licenses/wxWidgets.txt new file mode 100644 index 0000000..9495fad --- /dev/null +++ b/installer/Licenses/wxWidgets.txt @@ -0,0 +1,53 @@ + wxWindows Library Licence, Version 3.1 + ====================================== + + Copyright (c) 1998-2005 Julian Smart, Robert Roebling et al + + Everyone is permitted to copy and distribute verbatim copies + of this licence document, but changing it is not allowed. + + WXWINDOWS LIBRARY LICENCE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public Licence as published by + the Free Software Foundation; either version 2 of the Licence, or (at + your option) any later version. + + This library 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 Library + General Public Licence for more details. + + You should have received a copy of the GNU Library General Public Licence + along with this software, usually in a file named COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA. + + EXCEPTION NOTICE + + 1. As a special exception, the copyright holders of this library give + permission for additional uses of the text contained in this release of + the library as licenced under the wxWindows Library Licence, applying + either version 3.1 of the Licence, or (at your option) any later version of + the Licence as published by the copyright holders of version + 3.1 of the Licence document. + + 2. The exception is that you may use, copy, link, modify and distribute + under your own terms, binary object code versions of works based + on the Library. + + 3. If you copy code from files distributed under the terms of the GNU + General Public Licence or the GNU Library General Public Licence into a + copy of this library, as this licence permits, the exception does not + apply to the code that you add in this way. To avoid misleading anyone as + to the status of such modified files, you must delete this exception + notice from such code and/or adjust the licensing conditions notice + accordingly. + + 4. If you write modifications of your own for this library, it is your + choice whether to permit this exception to apply to your modifications. + If you do not wish that, you must delete the exception notice from such + code and/or adjust the licensing conditions notice accordingly. + + diff --git a/installer/MultiUser.nsh b/installer/MultiUser.nsh new file mode 100644 index 0000000..922083a --- /dev/null +++ b/installer/MultiUser.nsh @@ -0,0 +1,623 @@ +/* + +MultiUser.nsh + +Installer configuration for multi-user Windows environments + +Copyright 2008 Joost Verburg + +*/ + +!ifndef MULTIUSER_INCLUDED +!define MULTIUSER_INCLUDED +!define PAGE_NAME_MULTIUSER "dir" +!verbose push +!verbose 3 + +;Standard NSIS header files + +!ifdef MULTIUSER_MUI + !include MUI2.nsh +!endif +!include nsDialogs.nsh +!include LogicLib.nsh +!include WinVer.nsh +!include FileFunc.nsh +!include StrFunc.nsh + +;Variables + +Var MultiUser.Privileges +Var MultiUser.InstallMode +Var MultiUser.Visited + +;Command line installation mode setting + +!ifdef MULTIUSER_INSTALLMODE_COMMANDLINE + !insertmacro GetParameters + !ifndef MULTIUSER_NOUNINSTALL + !insertmacro un.GetParameters + !endif + !include StrFunc.nsh + !ifndef StrStr_INCLUDED + ${StrStr} + !endif + !ifndef MULTIUSER_NOUNINSTALL + !ifndef UnStrStr_INCLUDED + ${UnStrStr} + !endif + !endif + + Var MultiUser.Parameters + Var MultiUser.Result +!endif + +;Installation folder stored in registry + +!ifdef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY & MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME + Var MultiUser.InstDir +!endif + +!ifdef MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY & MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME + Var MultiUser.DefaultKeyValue +!endif + +;Windows Vista UAC setting + +!if "${MULTIUSER_EXECUTIONLEVEL}" == Admin + RequestExecutionLevel admin + !define MULTIUSER_EXECUTIONLEVEL_ALLUSERS +!else if "${MULTIUSER_EXECUTIONLEVEL}" == Power + RequestExecutionLevel admin + !define MULTIUSER_EXECUTIONLEVEL_ALLUSERS +!else if "${MULTIUSER_EXECUTIONLEVEL}" == Highest + RequestExecutionLevel highest + !define MULTIUSER_EXECUTIONLEVEL_ALLUSERS +!else + RequestExecutionLevel user +!endif + +/* + +Install modes + +*/ + +!macro MULTIUSER_INSTALLMODE_ALLUSERS UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + + ;Install mode initialization - per-machine + + ${ifnot} ${IsNT} + ${orif} $MultiUser.Privileges == "Admin" + ${orif} $MultiUser.Privileges == "Power" + + StrCpy $MultiUser.InstallMode AllUsers + + SetShellVarContext all + + !if "${UNINSTALLER_PREFIX}" != UN + ;Set default installation location for installer + !ifdef MULTIUSER_INSTALLMODE_INSTDIR_ALLUSERS + StrCpy $INSTDIR "$PROGRAMFILES\${MULTIUSER_INSTALLMODE_INSTDIR_ALLUSERS}" + !endif + !endif + + !ifdef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY & MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME + + ReadRegStr $MultiUser.InstDir HKLM "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" + + ${if} $MultiUser.InstDir != "" + StrCpy $INSTDIR $MultiUser.InstDir + ${endif} + + !endif + + !ifdef MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION + Call "${MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION}" + !endif + + ${endif} + +!macroend + +!macro MULTIUSER_INSTALLMODE_CURRENTUSER UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + + ;Install mode initialization - per-user + + ${if} ${IsNT} + + StrCpy $MultiUser.InstallMode CurrentUser + + SetShellVarContext current + + !if "${UNINSTALLER_PREFIX}" != UN + ;Set default installation location for installer + !ifdef MULTIUSER_INSTALLMODE_INSTDIR_CURRENT | MULTIUSER_INSTALLMODE_INSTDIR_ALLUSERS + ${if} ${AtLeastWin2000} + StrCpy $INSTDIR "$LOCALAPPDATA\${MULTIUSER_INSTALLMODE_INSTDIR_CURRENT}" + ${else} + StrCpy $INSTDIR "$PROGRAMFILES\${MULTIUSER_INSTALLMODE_INSTDIR_ALLUSERS}" + ${endif} + !endif + !endif + + !ifdef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY & MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME + + ReadRegStr $MultiUser.InstDir HKCU "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" + + ${if} $MultiUser.InstDir != "" + StrCpy $INSTDIR $MultiUser.InstDir + ${endif} + + !endif + + !ifdef MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION + Call "${MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION}" + !endif + + ${endif} + +!macroend +!macro MULTIUSER_INSTALLMODE_PORTABLE UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + StrCpy $MultiUser.InstallMode Portable + SetShellVarContext current +!macroend + +Function MultiUser.InstallMode.AllUsers + !insertmacro MULTIUSER_INSTALLMODE_ALLUSERS "" "" +FunctionEnd + +Function MultiUser.InstallMode.CurrentUser + !insertmacro MULTIUSER_INSTALLMODE_CURRENTUSER "" "" +FunctionEnd + +Function MultiUser.InstallMode.Portable + !insertmacro MULTIUSER_INSTALLMODE_PORTABLE "" "" +FunctionEnd + +!ifndef MULTIUSER_NOUNINSTALL + +Function un.MultiUser.InstallMode.AllUsers + !insertmacro MULTIUSER_INSTALLMODE_ALLUSERS UN .un +FunctionEnd + +Function un.MultiUser.InstallMode.CurrentUser + !insertmacro MULTIUSER_INSTALLMODE_CURRENTUSER UN .un +FunctionEnd + +Function un.MultiUser.InstallMode.Portable + !insertmacro MULTIUSER_INSTALLMODE_PORTABLE UN .un +FunctionEnd + +!endif + +/* + +Installer/uninstaller initialization + +*/ + +!macro MULTIUSER_INIT_QUIT UNINSTALLER_FUNCPREFIX + + !ifdef MULTIUSER_INIT_${UNINSTALLER_FUNCPREFIX}FUNCTIONQUIT + Call "${MULTIUSER_INIT_${UNINSTALLER_FUNCPREFIX}FUCTIONQUIT} + !else + Quit + !endif + +!macroend + +!macro MULTIUSER_INIT_TEXTS + + !ifndef MULTIUSER_INIT_TEXT_ADMINREQUIRED + !define MULTIUSER_INIT_TEXT_ADMINREQUIRED "$(^Caption) requires administrator priviledges." + !endif + + !ifndef MULTIUSER_INIT_TEXT_POWERREQUIRED + !define MULTIUSER_INIT_TEXT_POWERREQUIRED "$(^Caption) requires at least Power User priviledges." + !endif + + !ifndef MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE + !define MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE "Your user account does not have sufficient privileges to install $(^Name) for all users of this computer." + !endif + +!macroend + +!macro MULTIUSER_INIT_CHECKS UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + + ;Installer initialization - check privileges and set install mode + + !insertmacro MULTIUSER_INIT_TEXTS + + UserInfo::GetAccountType + Pop $MultiUser.Privileges + + ${if} ${IsNT} + + ;Check privileges + + !if "${MULTIUSER_EXECUTIONLEVEL}" == Admin + + ${if} $MultiUser.Privileges != "Admin" + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_ADMINREQUIRED}" + !insertmacro MULTIUSER_INIT_QUIT "${UNINSTALLER_FUNCPREFIX}" + ${endif} + + !else if "${MULTIUSER_EXECUTIONLEVEL}" == Power + + ${if} $MultiUser.Privileges != "Power" + ${andif} $MultiUser.Privileges != "Admin" + ${if} ${AtMostWinXP} + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_POWERREQUIRED}" + ${else} + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_ADMINREQUIRED}" + ${endif} + !insertmacro MULTIUSER_INIT_QUIT "${UNINSTALLER_FUNCPREFIX}" + ${endif} + + !endif + + !ifdef MULTIUSER_EXECUTIONLEVEL_ALLUSERS + + ;Default to per-machine installation if possible + + ${if} $MultiUser.Privileges == "Admin" + ${orif} $MultiUser.Privileges == "Power" + !ifndef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + !else + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !endif + + !ifdef MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY & MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME + + ;Set installation mode to setting from a previous installation + + !ifndef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER + ReadRegStr $MultiUser.DefaultKeyValue HKLM "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue == "" + ReadRegStr $MultiUser.DefaultKeyValue HKCU "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue != "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + ${endif} + !else + ReadRegStr $MultiUser.DefaultKeyValue HKCU "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue == "" + ReadRegStr $MultiUser.DefaultKeyValue HKLM "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue != "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${endif} + ${endif} + !endif + + !endif + + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + + !else + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + + !endif + + !ifdef MULTIUSER_INSTALLMODE_COMMANDLINE + + ;Check for install mode setting on command line + + ${${UNINSTALLER_FUNCPREFIX}GetParameters} $MultiUser.Parameters + + ${${UNINSTALLER_PREFIX}StrStr} $MultiUser.Result $MultiUser.Parameters "/CurrentUser" + + ${if} $MultiUser.Result != "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + + ${${UNINSTALLER_PREFIX}StrStr} $MultiUser.Result $MultiUser.Parameters "/AllUsers" + + ${if} $MultiUser.Result != "" + ${if} $MultiUser.Privileges == "Admin" + ${orif} $MultiUser.Privileges == "Power" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE}" + ${endif} + ${endif} + + !endif + + ${else} + + ;Not running Windows NT, per-user installation not supported + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + + ${endif} + +!macroend + +!macro MULTIUSER_INIT + !verbose push + !verbose 3 + + !insertmacro MULTIUSER_INIT_CHECKS "" "" + + !verbose pop +!macroend + +!ifndef MULTIUSER_NOUNINSTALL + +!macro MULTIUSER_UNINIT + !verbose push + !verbose 3 + + !insertmacro MULTIUSER_INIT_CHECKS Un un. + + !verbose pop +!macroend + +!endif + +/* + +Modern UI 2 page + +*/ + +!ifdef MULTIUSER_MUI + +!macro MULTIUSER_INSTALLMODEPAGE_INTERFACE + + !ifndef MULTIUSER_INSTALLMODEPAGE_INTERFACE + !define MULTIUSER_INSTALLMODEPAGE_INTERFACE + Var MultiUser.InstallModePage + + Var MultiUser.InstallModePage.Text + + Var MultiUser.InstallModePage.AllUsers + Var MultiUser.InstallModePage.CurrentUser + Var MultiUser.InstallModePage.Portable + Var MultiUser.InstallModePage.DirectorySelect + Var MultiUser.InstallModePage.DirBrowseButton + + Var MultiUser.InstallModePage.ReturnValue + Var MultiUser.DirectoryResult + !endif + +!macroend + +!macro MULTIUSER_PAGEDECLARATION_INSTALLMODE + + !insertmacro MUI_SET MULTIUSER_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLMODEPAGE + !insertmacro MULTIUSER_INSTALLMODEPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_TOP "$(MULTIUSER_INNERTEXT_INSTALLMODE_TOP)" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_ALLUSERS "All Users" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_SUBTEXT_ALLUSERS 'You are the computer administrator and can install to the $\"Program Files$\" folder' + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_CURRENTUSER "Single User" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_SUBTEXT_CURRENTUSER "You have a limited account and can't install to the $\"Program Files$\" folder" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_PORTABLE "Portable" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_SUBTEXT_PORTABLE "Install Digsby to a USB drive or other portable device" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_DIRSELECT "$(MULTIUSER_INNERTEXT_INSTALLMODE_DIRSELECT)" + + PageEx custom + + PageCallbacks MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MULTIUSER_FUNCTION_INSTALLMODEPAGE MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} + + !undef MULTIUSER_INSTALLMODEPAGE_TEXT_TOP + !undef MULTIUSER_INSTALLMODEPAGE_TEXT_ALLUSERS + !undef MULTIUSER_INSTALLMODEPAGE_TEXT_CURRENTUSER + !undef MULTIUSER_INSTALLMODEPAGE_TEXT_PORTABLE + !undef MULTIUSER_INSTALLMODEPAGE_SUBTEXT_ALLUSERS + !undef MULTIUSER_INSTALLMODEPAGE_SUBTEXT_CURRENTUSER + !undef MULTIUSER_INSTALLMODEPAGE_SUBTEXT_PORTABLE + +!macroend + +!macro MULTIUSER_PAGE_INSTALLMODE + + ;Modern UI page for install mode + + !verbose push + !verbose 3 + + !ifndef MULTIUSER_EXECUTIONLEVEL_ALLUSERS + !error "A mixed-mode installation requires MULTIUSER_EXECUTIONLEVEL to be set to Admin, Power or Highest." + !endif + + !insertmacro MUI_PAGE_INIT + !insertmacro MULTIUSER_PAGEDECLARATION_INSTALLMODE + + !verbose pop + +!macroend + +!macro SyncInstDir + ${NSD_GetText} $MultiUser.InstallModePage.DirectorySelect $MultiUser.DirectoryResult + ${If} $MultiUser.DirectoryResult != "" + StrCpy $INSTDIR $MultiUser.DirectoryResult + ${NSD_SetText} $MultiUser.InstallModePage.DirectorySelect $INSTDIR + ${EndIf} + #MessageBox MB_OK "SyncEnd: $MultiUser.DirectoryResult" +!macroend + +!macro SetPathForRadio + ${NSD_GetState} $MultiUser.InstallModePage.CurrentUser $1 + ${NSD_GetState} $MultiUser.InstallModePage.AllUsers $2 + ${if} $1 == ${BST_CHECKED} # current user is checked + ExpandEnvStrings $3 "$LOCALAPPDATA\${MULTIUSER_INSTALLMODE_INSTDIR_CURRENT}" + ${elseif} $2 == ${BST_CHECKED} # all users is checked + ExpandEnvStrings $3 "$PROGRAMFILES\${MULTIUSER_INSTALLMODE_INSTDIR_ALLUSERS}" + ${else} # portable is checked + ExpandEnvStrings $3 "C:\${MULTIUSER_INSTALLMODE_INSTDIR_CURRENT}" + ${endif} + ${NSD_SetText} $MultiUser.InstallModePage.DirectorySelect $3 + !insertmacro SyncInstDir + +!macroend + +!macro MULTIUSER_FUNCTION_INSTALLMODEPAGE PRE LEAVE + + ;Page functions of Modern UI page + + Function nsDialogsPageRadioClicked + StrCpy $IsPortable "False" + Pop $1 # clear the clicked window off the stack + SendMessage $MultiUser.InstallModePage.AllUsers ${BM_GETCHECK} 0 0 $1 + SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_GETCHECK} 0 0 $2 + + ${if} $1 = ${BST_CHECKED} + StrCpy $MultiUser.InstallModePage.ReturnValue "all" + ${elseif} $2 = ${BST_CHECKED} + StrCpy $MultiUser.InstallModePage.ReturnValue "current" + ${else} + StrCpy $MultiUser.InstallModePage.ReturnValue "portable" + ${endif} + + !insertmacro SetPathForRadio + FunctionEnd + + Function nsDialogsBrowseButtonClicked + Pop $1 # clear the clicked window off the stack + + ${NSD_GetText} $MultiUser.InstallModePage.DirectorySelect $1 + nsDialogs::SelectFolderDialog /NOUNLOAD "Select Folder" $1 + Pop $1 + StrLen $2 $1 + ${If} $2 = 3 + StrCpy $1 $1 -1 + ${EndIf} + ${if} $1 != "" + ${andif} $1 != "error" + ${if} $MultiUser.InstallModePage.ReturnValue == "all" + ${NSD_SetText} $MultiUser.InstallModePage.DirectorySelect "$1\${MULTIUSER_INSTALLMODE_INSTDIR_ALLUSERS}" + ${else} + ${NSD_SetText} $MultiUser.InstallModePage.DirectorySelect "$1\${MULTIUSER_INSTALLMODE_INSTDIR_CURRENT}" + ${endif} + !insertmacro SyncInstDir + ${endif} + + FunctionEnd + + Function nsDialogsPortableRadioClicked + ${NSD_SetText} $MultiUser.InstallModepage.DirectorySelect "C:\" + Call nsDialogsPageRadioClicked + Push $1 + Call nsDialogsBrowseButtonClicked + StrCpy $IsPortable "True" + FunctionEnd + + Function nsDialogsDirSelectTextChanged + Pop $1 + #!insertmacro SyncInstDir + FunctionEnd + + Function "${PRE}" + + ${ifnot} ${IsNT} + Abort + ${endif} + + ${If} $MultiUser.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $MultiUser.Visited "True" + ${EndIf} + + StrCpy $LastSeenPage ${PAGE_NAME_MULTIUSER} + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MULTIUSER_TEXT_INSTALLMODE_TITLE) $(MULTIUSER_TEXT_INSTALLMODE_SUBTITLE) + + nsDialogs::Create /NOUNLOAD 1018 + Pop $MultiUser.InstallModePage + + ${NSD_CreateLabel} 0u 0u 300u 20u "${MULTIUSER_INSTALLMODEPAGE_TEXT_TOP}" + Pop $MultiUser.InstallModePage.Text + + ${NSD_CreateRadioButton} 20u 27u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_TEXT_ALLUSERS}" + Pop $MultiUser.InstallModePage.AllUsers + #SendMessage $MultiUser.InstallModePage.AllUsers ${TB_SETSTYLE} ${SW_BOLD} + ${NSD_OnClick} $MultiUser.InstallModePage.AllUsers nsDialogsPageRadioClicked + + ${NSD_CreateLabel} 32u 37u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_SUBTEXT_ALLUSERS}" + Pop $1 + + ${NSD_CreateRadioButton} 20u 51u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_TEXT_CURRENTUSER}" + Pop $MultiUser.InstallModePage.CurrentUser + # Set bold + ${NSD_OnClick} $MultiUser.InstallModePage.CurrentUser nsDialogsPageRadioClicked + + ${NSD_CreateLabel} 32u 61u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_SUBTEXT_CURRENTUSER}" + Pop $1 + + ${NSD_CreateRadioButton} 20u 77u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_TEXT_PORTABLE}" + Pop $MultiUser.InstallModePage.Portable + + ${NSD_OnClick} $MultiUser.InstallModePage.Portable nsDialogsPortableRadioClicked + + ${NSD_CreateLabel} 32u 87u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_SUBTEXT_PORTABLE}" + + ${NSD_CreateGroupBox} 10u 110u 290u 30u "Destination Folder" + Pop $1 + + ${NSD_CreateDirRequest} 20u 122u 200u 12u "${MULTIUSER_INSTALLMODEPAGE_TEXT_DIRSELECT}" + Pop $MultiUser.InstallModePage.DirectorySelect + ${NSD_OnChange} $MultiUser.InstallModePage.DirectorySelect nsDialogsDirSelectTextChanged + + ${NSD_CreateBrowseButton} 230u 120u 56u 16u "Browse..." + Pop $MultiUser.InstallModePage.DirBrowseButton + ${NSD_OnClick} $MultiUser.InstallModePage.DirBrowseButton nsDialogsBrowseButtonClicked + + ${if} $MultiUser.InstallMode == "AllUsers" + SendMessage $MultiUser.InstallModePage.AllUsers ${BM_SETCHECK} ${BST_CHECKED} 0 + ${else} + SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_SETCHECK} ${BST_CHECKED} 0 + ${endif} + + ${if} $MultiUser.Privileges != "Power" + ${andif} $MultiUser.Privileges != "Admin" + SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_SETCHECK} ${BST_CHECKED} 0 + EnableWindow $MultiUser.InstallModePage.AllUsers 0 + ${endif} + + !insertmacro SetPathForRadio + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + nsDialogs::Show + + FunctionEnd + + Function "${LEAVE}" + SendMessage $MultiUser.InstallModePage.AllUsers ${BM_GETCHECK} 0 0 $1 + SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_GETCHECK} 0 0 $2 + + ${if} $1 = ${BST_CHECKED} + StrCpy $MultiUser.InstallModePage.ReturnValue "all" + Call MultiUser.InstallMode.AllUsers + ${elseif} $2 = ${BST_CHECKED} + StrCpy $MultiUser.InstallModePage.ReturnValue "current" + Call MultiUser.InstallMode.CurrentUser + ${else} + StrCpy $MultiUser.InstallModePage.ReturnValue "portable" + Call MultiUser.InstallMode.Portable + ${endif} + + !insertmacro SyncInstDir + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + FunctionEnd + +!macroend + +!endif + +!verbose pop +!endif diff --git a/installer/NSIS/Bin/GenPat.exe b/installer/NSIS/Bin/GenPat.exe new file mode 100644 index 0000000..63b7946 Binary files /dev/null and b/installer/NSIS/Bin/GenPat.exe differ diff --git a/installer/NSIS/Bin/LibraryLocal.exe b/installer/NSIS/Bin/LibraryLocal.exe new file mode 100644 index 0000000..7c6f4bd Binary files /dev/null and b/installer/NSIS/Bin/LibraryLocal.exe differ diff --git a/installer/NSIS/Bin/MakeLangId.exe b/installer/NSIS/Bin/MakeLangId.exe new file mode 100644 index 0000000..f65d2d3 Binary files /dev/null and b/installer/NSIS/Bin/MakeLangId.exe differ diff --git a/installer/NSIS/Bin/RegTool.bin b/installer/NSIS/Bin/RegTool.bin new file mode 100644 index 0000000..e738c68 Binary files /dev/null and b/installer/NSIS/Bin/RegTool.bin differ diff --git a/installer/NSIS/Bin/zip2exe.exe b/installer/NSIS/Bin/zip2exe.exe new file mode 100644 index 0000000..3d36c49 Binary files /dev/null and b/installer/NSIS/Bin/zip2exe.exe differ diff --git a/installer/NSIS/COPYING b/installer/NSIS/COPYING new file mode 100644 index 0000000..4cb7626 --- /dev/null +++ b/installer/NSIS/COPYING @@ -0,0 +1,144 @@ +COPYRIGHT +--------- + +Copyright (C) 1995-2009 Contributors + +More detailed copyright information can be found in the individual source code files. + +APPLICABLE LICENSES +------------------- + +* All NSIS source code, plug-ins, documentation, examples, header files and graphics, with the exception of the compression modules and where otherwise noted, are licensed under the zlib/libpng license. + +* The zlib compression module for NSIS is licensed under the zlib/libpng license. + +* The bzip2 compression module for NSIS is licensed under the bzip2 license. + +* The LZMA compression module for NSIS is licensed under the Common Public License version 1.0. + +ZLIB/LIBPNG LICENSE +------------------- + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +BZIP2 LICENSE +------------- + +This program, "bzip2" and associated library "libbzip2", are copyright (C) 1996-2000 Julian R Seward. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + + 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + + 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Julian Seward, Cambridge, UK. + +jseward@acm.org + +COMMON PUBLIC LICENSE VERSION 1.0 +--------------------------------- + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. + + c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within the Program. + +Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. + +SPECIAL EXCEPTION FOR LZMA COMPRESSION MODULE +--------------------------------------------- + +Igor Pavlov and Amir Szekely, the authors of the LZMA compression module for NSIS, expressly permit you to statically or dynamically link your code (or bind by name) to the files from the LZMA compression module for NSIS without subjecting your linked code to the terms of the Common Public license version 1.0. Any modifications or additions to files from the LZMA compression module for NSIS, however, are subject to the terms of the Common Public License version 1.0. diff --git a/installer/NSIS/Contrib/Graphics/Checks/big.bmp b/installer/NSIS/Contrib/Graphics/Checks/big.bmp new file mode 100644 index 0000000..d6db077 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/big.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/classic-cross.bmp b/installer/NSIS/Contrib/Graphics/Checks/classic-cross.bmp new file mode 100644 index 0000000..a4d37a1 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/classic-cross.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/classic.bmp b/installer/NSIS/Contrib/Graphics/Checks/classic.bmp new file mode 100644 index 0000000..83e3cf5 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/classic.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/colorful.bmp b/installer/NSIS/Contrib/Graphics/Checks/colorful.bmp new file mode 100644 index 0000000..7713942 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/colorful.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/grey-cross.bmp b/installer/NSIS/Contrib/Graphics/Checks/grey-cross.bmp new file mode 100644 index 0000000..b28b59b Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/grey-cross.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/grey.bmp b/installer/NSIS/Contrib/Graphics/Checks/grey.bmp new file mode 100644 index 0000000..b374432 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/grey.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/modern.bmp b/installer/NSIS/Contrib/Graphics/Checks/modern.bmp new file mode 100644 index 0000000..62468de Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/modern.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/red-round.bmp b/installer/NSIS/Contrib/Graphics/Checks/red-round.bmp new file mode 100644 index 0000000..31d3c02 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/red-round.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/red.bmp b/installer/NSIS/Contrib/Graphics/Checks/red.bmp new file mode 100644 index 0000000..e14e6b4 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/red.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/simple-round.bmp b/installer/NSIS/Contrib/Graphics/Checks/simple-round.bmp new file mode 100644 index 0000000..6950232 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/simple-round.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/simple-round2.bmp b/installer/NSIS/Contrib/Graphics/Checks/simple-round2.bmp new file mode 100644 index 0000000..ee1ec84 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/simple-round2.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Checks/simple.bmp b/installer/NSIS/Contrib/Graphics/Checks/simple.bmp new file mode 100644 index 0000000..c687a1d Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Checks/simple.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/nsis-r.bmp b/installer/NSIS/Contrib/Graphics/Header/nsis-r.bmp new file mode 100644 index 0000000..eb3650f Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/nsis-r.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/nsis.bmp b/installer/NSIS/Contrib/Graphics/Header/nsis.bmp new file mode 100644 index 0000000..cbb5231 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange-nsis.bmp b/installer/NSIS/Contrib/Graphics/Header/orange-nsis.bmp new file mode 100644 index 0000000..b4a0cf9 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange-nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange-r-nsis.bmp b/installer/NSIS/Contrib/Graphics/Header/orange-r-nsis.bmp new file mode 100644 index 0000000..2da34f1 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange-r-nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange-r.bmp b/installer/NSIS/Contrib/Graphics/Header/orange-r.bmp new file mode 100644 index 0000000..c74fbdd Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange-r.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-nsis.bmp b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-nsis.bmp new file mode 100644 index 0000000..635596b Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-r-nsis.bmp b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-r-nsis.bmp new file mode 100644 index 0000000..5f215d7 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-r-nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-r.bmp b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-r.bmp new file mode 100644 index 0000000..1672afa Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall-r.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange-uninstall.bmp b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall.bmp new file mode 100644 index 0000000..97be674 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange-uninstall.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/orange.bmp b/installer/NSIS/Contrib/Graphics/Header/orange.bmp new file mode 100644 index 0000000..4ac1413 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/orange.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Header/win.bmp b/installer/NSIS/Contrib/Graphics/Header/win.bmp new file mode 100644 index 0000000..6612357 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Header/win.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/arrow-install.ico b/installer/NSIS/Contrib/Graphics/Icons/arrow-install.ico new file mode 100644 index 0000000..0441d5c Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/arrow-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/arrow-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/arrow-uninstall.ico new file mode 100644 index 0000000..f3e7bfe Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/arrow-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/arrow2-install.ico b/installer/NSIS/Contrib/Graphics/Icons/arrow2-install.ico new file mode 100644 index 0000000..e047f7d Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/arrow2-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/arrow2-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/arrow2-uninstall.ico new file mode 100644 index 0000000..fa6064f Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/arrow2-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/box-install.ico b/installer/NSIS/Contrib/Graphics/Icons/box-install.ico new file mode 100644 index 0000000..fd6c7c1 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/box-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/box-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/box-uninstall.ico new file mode 100644 index 0000000..bc27541 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/box-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/classic-install.ico b/installer/NSIS/Contrib/Graphics/Icons/classic-install.ico new file mode 100644 index 0000000..5afcc62 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/classic-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/classic-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/classic-uninstall.ico new file mode 100644 index 0000000..0953290 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/classic-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/llama-blue.ico b/installer/NSIS/Contrib/Graphics/Icons/llama-blue.ico new file mode 100644 index 0000000..08288b6 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/llama-blue.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/llama-grey.ico b/installer/NSIS/Contrib/Graphics/Icons/llama-grey.ico new file mode 100644 index 0000000..4749479 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/llama-grey.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-install-blue-full.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-install-blue-full.ico new file mode 100644 index 0000000..8f1c512 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-install-blue-full.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-install-blue.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-install-blue.ico new file mode 100644 index 0000000..fecdc27 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-install-blue.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-install-colorful.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-install-colorful.ico new file mode 100644 index 0000000..2908f58 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-install-colorful.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-install-full.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-install-full.ico new file mode 100644 index 0000000..3aa83e9 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-install-full.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-install.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-install.ico new file mode 100644 index 0000000..f8fbd5f Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-blue-full.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-blue-full.ico new file mode 100644 index 0000000..cd92279 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-blue-full.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-blue.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-blue.ico new file mode 100644 index 0000000..77031b5 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-blue.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-colorful.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-colorful.ico new file mode 100644 index 0000000..461035c Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-colorful.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-full.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-full.ico new file mode 100644 index 0000000..a134f58 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall-full.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall.ico new file mode 100644 index 0000000..6c7410c Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/modern-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/nsis1-install.ico b/installer/NSIS/Contrib/Graphics/Icons/nsis1-install.ico new file mode 100644 index 0000000..e180449 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/nsis1-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/nsis1-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/nsis1-uninstall.ico new file mode 100644 index 0000000..a37774c Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/nsis1-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/orange-install-nsis.ico b/installer/NSIS/Contrib/Graphics/Icons/orange-install-nsis.ico new file mode 100644 index 0000000..ef3975f Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/orange-install-nsis.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/orange-install.ico b/installer/NSIS/Contrib/Graphics/Icons/orange-install.ico new file mode 100644 index 0000000..1db75f8 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/orange-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/orange-uninstall-nsis.ico b/installer/NSIS/Contrib/Graphics/Icons/orange-uninstall-nsis.ico new file mode 100644 index 0000000..431eb2e Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/orange-uninstall-nsis.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/orange-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/orange-uninstall.ico new file mode 100644 index 0000000..59c79f3 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/orange-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/pixel-install.ico b/installer/NSIS/Contrib/Graphics/Icons/pixel-install.ico new file mode 100644 index 0000000..f2106d6 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/pixel-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/pixel-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/pixel-uninstall.ico new file mode 100644 index 0000000..2003b2d Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/pixel-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/win-install.ico b/installer/NSIS/Contrib/Graphics/Icons/win-install.ico new file mode 100644 index 0000000..a5eb774 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/win-install.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Icons/win-uninstall.ico b/installer/NSIS/Contrib/Graphics/Icons/win-uninstall.ico new file mode 100644 index 0000000..9329176 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Icons/win-uninstall.ico differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/arrow.bmp b/installer/NSIS/Contrib/Graphics/Wizard/arrow.bmp new file mode 100644 index 0000000..9f7426b Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/arrow.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/llama.bmp b/installer/NSIS/Contrib/Graphics/Wizard/llama.bmp new file mode 100644 index 0000000..1e1d942 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/llama.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/nsis.bmp b/installer/NSIS/Contrib/Graphics/Wizard/nsis.bmp new file mode 100644 index 0000000..dcc3809 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/nullsoft.bmp b/installer/NSIS/Contrib/Graphics/Wizard/nullsoft.bmp new file mode 100644 index 0000000..d4145d4 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/nullsoft.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/orange-nsis.bmp b/installer/NSIS/Contrib/Graphics/Wizard/orange-nsis.bmp new file mode 100644 index 0000000..ec46bd8 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/orange-nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/orange-uninstall-nsis.bmp b/installer/NSIS/Contrib/Graphics/Wizard/orange-uninstall-nsis.bmp new file mode 100644 index 0000000..661e702 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/orange-uninstall-nsis.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/orange-uninstall.bmp b/installer/NSIS/Contrib/Graphics/Wizard/orange-uninstall.bmp new file mode 100644 index 0000000..097d094 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/orange-uninstall.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/orange.bmp b/installer/NSIS/Contrib/Graphics/Wizard/orange.bmp new file mode 100644 index 0000000..196a5b7 Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/orange.bmp differ diff --git a/installer/NSIS/Contrib/Graphics/Wizard/win.bmp b/installer/NSIS/Contrib/Graphics/Wizard/win.bmp new file mode 100644 index 0000000..5524eef Binary files /dev/null and b/installer/NSIS/Contrib/Graphics/Wizard/win.bmp differ diff --git a/installer/NSIS/Contrib/Inetc/!inetc-custom.dll b/installer/NSIS/Contrib/Inetc/!inetc-custom.dll new file mode 100644 index 0000000..058c858 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/!inetc-custom.dll differ diff --git a/installer/NSIS/Contrib/Inetc/!inetc-orig.dll b/installer/NSIS/Contrib/Inetc/!inetc-orig.dll new file mode 100644 index 0000000..6461c7b Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/!inetc-orig.dll differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/BuildLog.htm b/installer/NSIS/Contrib/Inetc/Debug/BuildLog.htm new file mode 100644 index 0000000..83c63bd Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/BuildLog.htm differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/RCa01972 b/installer/NSIS/Contrib/Inetc/Debug/RCa01972 new file mode 100644 index 0000000..fd3ddf3 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/RCa01972 differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.embed.manifest b/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.embed.manifest new file mode 100644 index 0000000..cca7079 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.embed.manifest @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.embed.manifest.res b/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.embed.manifest.res new file mode 100644 index 0000000..4530151 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.embed.manifest.res differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.intermediate.manifest b/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.intermediate.manifest new file mode 100644 index 0000000..e47a6b3 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/Debug/inetc.dll.intermediate.manifest @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/installer/NSIS/Contrib/Inetc/Debug/inetc.exp b/installer/NSIS/Contrib/Inetc/Debug/inetc.exp new file mode 100644 index 0000000..785587a Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/inetc.exp differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/inetc.lib b/installer/NSIS/Contrib/Inetc/Debug/inetc.lib new file mode 100644 index 0000000..0f46939 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/inetc.lib differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/inetc.obj b/installer/NSIS/Contrib/Inetc/Debug/inetc.obj new file mode 100644 index 0000000..3543f95 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/inetc.obj differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/inetc.res b/installer/NSIS/Contrib/Inetc/Debug/inetc.res new file mode 100644 index 0000000..99c25b3 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/inetc.res differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/mt.dep b/installer/NSIS/Contrib/Inetc/Debug/mt.dep new file mode 100644 index 0000000..bd5b602 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/Debug/mt.dep @@ -0,0 +1 @@ +Manifest resource last updated at 16:44:45.34 on Wed 12/05/2007 diff --git a/installer/NSIS/Contrib/Inetc/Debug/vc80.idb b/installer/NSIS/Contrib/Inetc/Debug/vc80.idb new file mode 100644 index 0000000..997f250 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/vc80.idb differ diff --git a/installer/NSIS/Contrib/Inetc/Debug/vc80.pdb b/installer/NSIS/Contrib/Inetc/Debug/vc80.pdb new file mode 100644 index 0000000..3e31309 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/Debug/vc80.pdb differ diff --git a/installer/NSIS/Contrib/Inetc/Example.nsi b/installer/NSIS/Contrib/Inetc/Example.nsi new file mode 100644 index 0000000..cc55241 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/Example.nsi @@ -0,0 +1,52 @@ + +;-------------------------------- +; General Attributes + +Name "InetClient Test" +OutFile "inetc.exe" +;SilentInstall silent + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install-colorful.ico" + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + +;SetFont 14 +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + SetDetailsView hide + +; two files download, popup mode + inetc::get /caption "2003-2004 reports" /popup "" "http://ineum.narod.ru/spr_2003.htm" "$EXEDIR\spr3.htm" "http://ineum.narod.ru/spr_2004.htm" "$EXEDIR\spr4.htm" /end + Pop $0 # return value = exit code, "OK" means OK + +; single file, NSISdl-style embedded progress bar with specific cancel button text + inetc::get /caption "2005 report" /canceltext "interrupt!" "http://ineum.narod.ru/spr_2005.htm" "$EXEDIR\spr5.htm" /end + Pop $1 # return value = exit code, "OK" means OK + +; banner with 2 text lines and disabled Cancel button + inetc::get /caption "2006 report" /banner "Banner mode with /nocancel option setten$\nSecond Line" /nocancel "http://ineum.narod.ru/spr_2006.htm" "$EXEDIR\spr6.htm" /end + Pop $2 # return value = exit code, "OK" means OK + + MessageBox MB_OK "Download Status: $0, $1, $2" +SectionEnd + + +;-------------------------------- +;Installer Functions + +Function .onInit + +; plug-in auto-recognizes 'no parent dlg' in onInit and works accordingly +; inetc::head /RESUME "Network error. Retry?" "http://ineum.narod.ru/spr_2003.htm" "$EXEDIR\spr3.txt" +; Pop $4 + +FunctionEnd \ No newline at end of file diff --git a/installer/NSIS/Contrib/Inetc/Readme.htm b/installer/NSIS/Contrib/Inetc/Readme.htm new file mode 100644 index 0000000..9e81fd8 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/Readme.htm @@ -0,0 +1,131 @@ +
    +

    Contents

    +
      +
    • 1 Links +
    • 2 Description +
    • 3 Command line +
        +
      • 3.1 get DLL Function +
      • 3.2 post DLL Function +
      • 3.3 head DLL Function +
      • 3.4 put DLL Function +
      +
    • 4 Examples +
    • 5 Credits +
    +
    + +

    Links

    +Download: http://nsis.sourceforge.net/Inetc_plug-in + +

    Description

    +Internet client plug-in for files download and upload. Based on the InetLoad plug-in. +Network implementation uses MS WinInet API, supports http/https and ftp protocols. +Plugin has better proxy support compare to NSISdl plug-in. Command line may include +few URL/File pairs to be transfered. If server or proxy login/password are not setten in the script, +displays IE-style authentication dialog (except silent mode). Plug-in supports 3 +"transfer in progress" display modes: +
      +
    • old NSISdl style - additional embedded progress bar and text on the INSTFILES page; +
    • POPUP dialog mode with detailed info; +
    • BANNER mode with simple popup window. +
    +Plug-in recognizes Installer's Silent mode and this case hides any output (this feature +requires NSIS 2.03 or later). Program implements simple re-get functionality - host +reconnect and download from current position after short pause. While program depends on IE settings, +it changes current IE mode to online. NSISdl code fragment was used for progress bar displaying +in the "old style" mode. For ftp use "host/path" for file location relative to user's home dir and +"host//path" for absolute path. + +

    Command line

    + +Plug-in DLL functions (entry points): get, post, head, put + +

    get DLL Function

    + +inetc::get [/PROXY IP:PORT] [/USERNAME PROXY_LOGIN /PASSWORD PROXY_PASSWD] + [/NOPROXY] [/NOCANCEL] [/TIMEOUT INT_MS] [/SILENT] [/CAPTION TEXT] + [/RESUME RETRY_QUESTION] [/POPUP HOST_ALIAS | /BANNER TEXT] [/CANCELTEXT CANCEL_TEXT] + [/USER_AGENT USER_AGENT_TEXT] [/HEADER HEADER_TEXT] [/TRANSLATE LANG_PARAMS] + URL1 local_file1 [URL2 local_file2 [...]] [/END] +

    This call returns "OK" string if successful, error description string if failed (see included InetLoad.cpp file for a full set of status strings). Usage and result processing samples are included to the package. +

    /PROXY - +Overwrites current proxy settings, not required in most cases. IE settings will be used by default. +

    /USERNAME - +Proxy username (http only). +

    /PASSWORD - +Proxy password (http only). For server (http/ftp) authentication it is possible to use URL encoded name and password, for example http://username:password@nsis.sourceforge.net. +

    /NOPROXY - +Disables proxy settings for this connection (if any) +

    /NOCANCEL - +Prevents download from being interrupted by user (locks Esc, Alt-F4, Cancel handling, removes sysmenu) +

    /TIMEOUT - +Sets INTERNET_OPTION_CONNECT_TIMEOUT, milliseconds, default - IE current parameter value. +

    /SILENT - +Key hides plug-in' output (both popup dialog and embedded progress bar). Not required if 'SilentInstall silent' mode was defined in script (NSIS 2.03 or later). +

    /RESUME - +On the permanent connection/transfer error instead of exit first displays message box with "resume download" question. Useful for dial-up connections and big files - allows user to restore connection and resume download. Default is "Your internet connection seems to have dropped out!\nPlease reconnect and click Retry to resume downloading...". +

    /CAPTION - +Defines caption text for /BANNER mode, caption prefix (left of '-') for /POPUP mode and caption for RESUME MessageBox. Default is "InetLoad plug-in" if not set or "". +

    /POPUP - +This mode displays detailed download dialog instead of embedded progress bar. Also useful in .onInit function (i.e. not in Section). If HOST_ALIAS is not "", text will replace URL in the dialog - this allows to hide real URL (including password). +

    /BANNER - +Displays simple popup dialog (MSI Banner mode) and sets dialog TEXT (up to 3 lines using $\n). +

    /CANCELTEXT - +Text for the Cancel button in the NSISdl mode. Default is NSIS dialog Cancel button text (current lang). +

    /USERAGENT - +UserAgent http request header value. Default is "NSIS_Inetc (Mozilla)". +

    /HEADER - +Adds or replaces http request header. Common HEADER_TEXT format is "header: value". +

    /END - +Allows to limit plug-in stack reading (optional, required if you stores other vars in the stack). +

    /TRANSLATE - +Allows translating plug-in text in the POPUP or NSISdl modes. 8 parameters both cases.
    + +NSISdl mode parameters:
    + /TRANSLATE downloading connecting second minute hour plural progress remaining
    +With default values:
    + "Downloading %s" "Connecting ..." second minute hour s "%dkB (%d%%) of %dkB @ %d.%01dkB/s" "(%d %s%s remaining)"
    + +POPUP mode parameters:
    + /TRANSLATE url downloading connecting file_name received file_size remaining_time total_time
    +With default values:
    + URL Downloading Connecting "File Name" Received "File Size" "Remaining Time" "Total Time"
    + +

    post DLL Function

    + +inetc::post TEXT2POST [/PROXY IP:PORT] [/NOPROXY] [/NOCANCEL] + [/USERNAME PROXY_LOGIN /PASSWORD PROXY_PASSWD] [/TIMEOUT INT_MS] [/SILENT] + [/CAPTION TEXT] [/POPUP | /BANNER TEXT] [/CANCELTEXT CANCEL_TEXT] + [/USER_AGENT USER_AGENT_TEXT] [/TRANSLATE LANG_PARAMS] + URL1 local_file1 [URL2 local_file2 [...]] [/END] +

    Sets POST http mode and defines text string to be used in the POST (http only). Disables auto re-get. No char replaces used (%20 and others). + +

    head DLL Function

    + +The same as get, but requests http headers only. Writes raw headers to file. + +

    put DLL Function

    + +inetc::put [/PROXY IP:PORT] [/USERNAME PROXY_LOGIN /PASSWORD PROXY_PASSWD] [/NOPROXY] + [/NOCANCEL] [/TIMEOUT INT_MS] [/SILENT] [/CAPTION TEXT] [/POPUP | /BANNER TEXT] + [/CANCELTEXT CANCEL_TEXT] [/USER_AGENT USER_AGENT_TEXT] + [/TRANSLATE LANG_PARAMS] URL1 local_file1 [URL2 local_file2 [...]] [/END] +

    Return value and parameters (if applicable) are the same as for previous entry point. + +

    Examples

    +
      inetc::get "http://dl.zvuki.ru/6306/mp3/12.mp3" "$EXEDIR\12.mp3" \
    +     "ftp://dl.zvuki.ru/6306/mp3/11.mp3" "$EXEDIR\11.mp3"
    +  Pop $0
    +  inetc::put /BANNER "Cameron Diaz upload in progress..." \
    +    "http://www.dreamgirlswallpaper.co.uk/fiveyearsonline/wallpaper/Cameron_Diaz/camerond09big.JPG" \
    +    "$EXEDIR\cd.jpg"
    +  Pop $0
    +  StrCmp $0 "OK" dlok
    +  MessageBox MB_OK|MB_ICONEXCLAMATION "http upload Error, click OK to abort installation" /SD IDOK
    +  Abort
    +dlok:
    +  ...
    + +

    Credits

    +Many thanks to Backland who offered a simple way to fix NSISdl mode crashes and added 'center parent' function. diff --git a/installer/NSIS/Contrib/Inetc/auth_dlg.nsi b/installer/NSIS/Contrib/Inetc/auth_dlg.nsi new file mode 100644 index 0000000..b322f42 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/auth_dlg.nsi @@ -0,0 +1,31 @@ + +;-------------------------------- +; General Attributes + +Name "Inetc http auth Test" +OutFile "auth_dlg.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install-colorful.ico" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + +; Displays IE auth dialog. +; Both server and proxy auth. +; Please test this with your own link. + + inetc::get "http://www.cnt.ru/personal" "$EXEDIR\auth.html" + Pop $0 # return value = exit code, "OK" if OK + MessageBox MB_OK "Download Status: $0" + +SectionEnd diff --git a/installer/NSIS/Contrib/Inetc/ftp_auth.nsi b/installer/NSIS/Contrib/Inetc/ftp_auth.nsi new file mode 100644 index 0000000..ecbfa8d --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/ftp_auth.nsi @@ -0,0 +1,31 @@ + +;-------------------------------- +; General Attributes + +Name "Inetc ftp authentication Test" +OutFile "ftp_auth.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install-colorful.ico" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + +; use your own URL and login@pwd. Password hidden from user with /popup "ALIAS" + + inetc::get /caption "service pack download" /popup "ftp://p401/" "ftp://takhir:pwd@p401/hard/arch_disk/tar.gz/W2Ksp3.exe" "$EXEDIR\sp3.exe" +; inetc::put /caption "service pack download" /popup "" "ftp://takhir:pwd@p401/hard/arch_disk/tar.gz/W2Ksp3.bu.exe" "$EXEDIR\sp3.exe" + Pop $0 # return value = exit code, "OK" if OK + MessageBox MB_OK "Download Status: $0" + +SectionEnd + diff --git a/installer/NSIS/Contrib/Inetc/head.nsi b/installer/NSIS/Contrib/Inetc/head.nsi new file mode 100644 index 0000000..2ad7ff7 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/head.nsi @@ -0,0 +1,29 @@ + +;-------------------------------- +; General Attributes + +Name "Inetc Head Test" +OutFile "head.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + DetailPrint "New version check out (internet connection)" + inetc::head /silent "http://ineum.narod.ru/spr_2006.htm" "$EXEDIR\head.txt" + Pop $0 # return value = exit code, "OK" if OK + MessageBox MB_OK "Download Status: $0" + +SectionEnd + + diff --git a/installer/NSIS/Contrib/Inetc/headers.nsi b/installer/NSIS/Contrib/Inetc/headers.nsi new file mode 100644 index 0000000..15b0a69 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/headers.nsi @@ -0,0 +1,31 @@ + +;-------------------------------- +; General Attributes + +Name "Headers Test" +OutFile "headers.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + + inetc::get /useragent "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1)" /header "SOAPAction: urn:anonOutInOpe" "http://localhost/headers.php" "$EXEDIR\headers.html" + Pop $0 + + MessageBox MB_OK "Download Status: $0" + +SectionEnd + + diff --git a/installer/NSIS/Contrib/Inetc/headers.php b/installer/NSIS/Contrib/Inetc/headers.php new file mode 100644 index 0000000..e33b0d8 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/headers.php @@ -0,0 +1,7 @@ + $value) { + echo "$header: $value
    \n"; +} +?> \ No newline at end of file diff --git a/installer/NSIS/Contrib/Inetc/https.nsi b/installer/NSIS/Contrib/Inetc/https.nsi new file mode 100644 index 0000000..ccf7c58 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/https.nsi @@ -0,0 +1,26 @@ + +;-------------------------------- +; General Attributes + +Name "Inetc https Test" +OutFile "https.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + inetc::get /POPUP "" /CAPTION "bending_property_demo.zip" "https://secure.codeproject.com/cs/miscctrl/bending_property/bending_property_src.zip" "$EXEDIR\bending_property_src.zip" + Pop $0 # return value = exit code, "OK" if OK + MessageBox MB_OK "Download Status: $0" + +SectionEnd diff --git a/installer/NSIS/Contrib/Inetc/inetc.cpp b/installer/NSIS/Contrib/Inetc/inetc.cpp new file mode 100644 index 0000000..9a44f99 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/inetc.cpp @@ -0,0 +1,1358 @@ +/******************************************************* +* FILE NAME: inetc.cpp +* +* Copyright 2004 - Present NSIS +* +* PURPOSE: +* ftp/http file download plug-in +* on the base of MS Inet API +* todo: status write mutex (?) +* 4 GB limit (http support?) +* +* CHANGE HISTORY +* +* Author Date Modifications +* Takhir Bedertdinov +* Nov 11, 2004 Original +* Dec 17, 2004 Embedded edition - +* NSISdl GUI style as default +* (nsisdl.cpp code was partly used) +* Dec 17, 2004 MSI Banner style +* Feb 20, 2005 Resume download +* feature for big files and bad connections +* Mar 05, 2005 Proxy authentication +* and /POPUP caption prefix option +* Mar 25, 2005 Connect timeout option +* and FTP switched to passive mode +* Apr 18, 2005 Crack URL buffer size +* bug fixed (256->string_size) +* HTTP POST added +* Jun 06, 2005 IDOK on "Enter" key locked +* POST HTTP header added +* Jun 22, 2005 non-interaptable mode /nocancel +* and direct connect /noproxy +* Jun 29, 2005 post.php written and tested +* Jul 05, 2005 60 sec delay on WinInet detach problem +* solved (not fine, but works including +* installer exit and system reboot) +* Jul 08, 2005 'set foreground' finally removed +* Jul 26, 2005 POPUP translate option +* Aug 23, 2005 https service type in InternetConnect +* and "ignore certificate" flags +* Sep 30, 2005 https with bad certificate from old OS; +* Forbidden handling +* Dec 23, 2005 'put' entry point, new names, 12003 +* ftp error handling (on ftp write permission) +* 405 http error (method not allowed) +* Mar 12, 2006 Internal authorization via InternetErrorDlg() +* and Unauthorized (401) handling. +* Jun 10, 2006 Caption text option for Resume download +* MessageBox +* Jun 24, 2006 HEAD method, silent mode clean up +* Sep 05, 2006 Center dialog code from Backland +* Sep 07, 2006 NSISdl crash fix /Backland idea/ +* Sep 08, 2006 post as dll entry point. +* Sep 21, 2006 parent dlg progr.bar style and font, +* nocancel via ws_sysmenu +* Sep 22, 2006 current lang IDCANCEL text, /canceltext +* and /useragent options +* Sep 24, 2006 .onInit improvements and defaults +* Nov 11, 2006 FTP path creation, root|current dir mgmt +* Jan 01, 2007 Global char[] cleanup, GetLastError() to +* status string on ERR_DIALOG, few MSVCRT replaces +* Jan 13, 2007 /HEADER option added +* Jan 28, 2007 _open -> CreateFile and related +* Feb 18, 2007 Speed calculating improved (pauses), +* /popup text parameter to hide URL +* Jun 07, 2007 Local file truncation added for download +* (CREATE_ALWAYS) +* Jun 11, 2007 FTP download permitted even if server rejects +* SIZE request (ProFTPD). +* Aug 11, 2007 Backland' fix for progress bar redraw/style +* issue in NSISdl display mode. +*******************************************************/ + + +#define _WIN32_WINNT 0x0500 + +#include +#include +#include +#include "..\exdll\exdll.h" +#include "resource.h" + +#include + +// IE 4 safety and VS 6 compatibility +typedef BOOL (__stdcall *FTP_CMD)(HINTERNET,BOOL,DWORD,LPCSTR,DWORD,HINTERNET *); +FTP_CMD myFtpCommand; + +#define PLUGIN_NAME "Inetc plug-in" +#define INETC_USERAGENT "NSIS_Inetc (Mozilla)" +#define PB_RANGE 400 +#define PAUSE1_SEC 2 // transfer error indication time, for reget only +#define PAUSE2_SEC 3 // paused state time, increase this if need (60?) +#define PAUSE3_SEC 1 // pause after resume button pressed +#define NOT_AVAILABLE 0xffffffff +#define POST_HEADER "Content-Type: application/x-www-form-urlencoded" +#define INTERNAL_OK 0xFFEE +#define PROGRESS_MS 1000 + +enum STATUS_CODES { + ST_OK = 0, + ST_CONNECTING, + ST_DOWNLOAD, + ST_CANCELLED, + ST_URLOPEN, + ST_PAUSE, + ERR_TERMINATED, + ERR_DIALOG, + ERR_INETOPEN, + ERR_URLOPEN, + ERR_TRANSFER, + ERR_FILEOPEN, + ERR_FILEWRITE, + ERR_FILEREAD, + ERR_REGET, + ERR_CONNECT, + ERR_OPENREQUEST, + ERR_SENDREQUEST, + ERR_CRACKURL, + ERR_NOTFOUND, + ERR_THREAD, + ERR_PROXY, + ERR_FORBIDDEN, + ERR_NOTALLOWED, + ERR_REQUEST, + ERR_SERVER, + ERR_AUTH, + ERR_CREATEDIR, + ERR_PATH +}; + + +static char szStatus[][32] = { + "OK", "Connecting", "Downloading", "Cancelled", "Connecting", //"Opening URL", + "Reconnect Pause", "Terminated", "Dialog Error", "Open Internet Error", + "Open URL Error", "Transfer Error", "File Open Error", "File Write Error", "File Read Error", + "Reget Error", "Connection Error", "OpenRequest Error", "SendRequest Error", + "URL Parts Error", "File Not Found (404)", "CreateThread Error", "Proxy Error (407)", + "Access Forbidden (403)", "Not Allowed (405)", "Request Error", "Server Error", + "Unauthorized (401)", "FtpCreateDir failed (550)", "Error FTP path (550)"}; + +HINSTANCE g_hInstance; +char fn[MAX_PATH]="", + *url = NULL, + *szPost = NULL, + *szProxy = NULL, + *szHeader = NULL, + szCancel[64]="", + szBanner[256]="", + szAlias[256]="", + szCaption[128]="", + szUsername[128]="", + szPassword[128]="", + szUserAgent[128]="", + szResume[256] = "Your internet connection seems to be not permitted or dropped out!\nPlease reconnect and click Retry to resume installation."; + +int status; +DWORD cnt = 0, + fs = 0, + timeout = 0; +DWORD startTime, transfStart, openType; +bool silent, popup, resume, nocancel, noproxy; + +HWND childwnd; +HWND hDlg; +bool fput = false, fhead = false; + + +/***************************************************** + * FUNCTION NAME: sf(HWND) + * PURPOSE: + * moves HWND to top and activates it + * SPECIAL CONSIDERATIONS: + * commented because annoying + *****************************************************/ +/* +void sf(HWND hw) +{ + DWORD ctid = GetCurrentThreadId(); + DWORD ftid = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + AttachThreadInput(ftid, ctid, TRUE); + SetForegroundWindow(hw); + AttachThreadInput(ftid, ctid, FALSE); +} +*/ + +static char szUrl[64] = ""; +static char szDownloading[64] = "Downloading %s"; +static char szConnecting[64] = "Connecting ..."; +static char szSecond[64] = "second"; +static char szMinute[32] = "minute"; +static char szHour[32] = "hour"; +static char szPlural[32] = "s"; +static char szProgress[128] = "%dkB (%d%%) of %dkB @ %d.%01dkB/s"; +static char szRemaining[64] = " (%d %s%s remaining)"; + + + +/***************************************************** + * FUNCTION NAME: fileTransfer() + * PURPOSE: + * http/ftp file transfer itself + * for any protocol and both directions I guess + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +void fileTransfer(HANDLE localFile, + HINTERNET hFile) +{ + byte data_buf[1024*8]; + byte *dw; + DWORD rslt = 0; + DWORD bytesDone; + + status = ST_DOWNLOAD; + while(status == ST_DOWNLOAD) + { + if(fput) + { + if(!ReadFile(localFile, data_buf, rslt = sizeof(data_buf), &bytesDone, NULL)) + { + status = ERR_FILEREAD; + break; + } + if(bytesDone == 0) // EOF reached + { + status = ST_OK; + break; + } + while(bytesDone > 0) + { + dw = data_buf; + if(!InternetWriteFile(hFile, dw, bytesDone, &rslt) || rslt == 0) + { + status = ERR_TRANSFER; + break; + } + dw += rslt; + cnt += rslt; + bytesDone -= rslt; + } + } + else + { + if(!InternetReadFile(hFile, data_buf, sizeof(data_buf), &rslt)) + { + status = ERR_TRANSFER; + break; + } + if(rslt == 0) // EOF reached + { + status = ST_OK; + break; + } + if(!WriteFile(localFile, data_buf, rslt, &bytesDone, NULL) || + rslt != bytesDone) + { + status = ERR_FILEWRITE; + break; + } + cnt += rslt; + } + } +} + +/***************************************************** + * FUNCTION NAME: mySendRequest() + * PURPOSE: + * HttpSendRequestEx() sends headers only - for PUT + * We can use InetWriteFile for POST headers I guess + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +int mySendRequest(HINTERNET hFile) +{ + INTERNET_BUFFERS BufferIn = {0}; + if(fput) + { + BufferIn.dwStructSize = sizeof( INTERNET_BUFFERS ); + BufferIn.dwBufferTotal = fs; + return HttpSendRequestEx( hFile, &BufferIn, NULL, HSR_INITIATE, 0); + } + return HttpSendRequest(hFile, NULL, 0, szPost, szPost ? lstrlen(szPost) : 0); +} + +/***************************************************** + * FUNCTION NAME: queryStatus() + * PURPOSE: + * http status code comes before download (get) and + * after upload (put), so this is called from 2 places + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +void queryStatus(HINTERNET hFile) +{ + char buf[256] = ""; + DWORD rslt; + if(HttpQueryInfo(hFile, HTTP_QUERY_STATUS_CODE, + buf, &(rslt = sizeof(buf)), NULL)) + { + buf[3] = 0; + if(lstrcmp(buf, "401") == 0) + status = ERR_AUTH; + else if(lstrcmp(buf, "403") == 0) + status = ERR_FORBIDDEN; + else if(lstrcmp(buf, "404") == 0) + status = ERR_NOTFOUND; + else if(lstrcmp(buf, "407") == 0) + status = ERR_PROXY; + else if(lstrcmp(buf, "405") == 0) + status = ERR_NOTALLOWED; + else if(*buf == '4') + { + status = ERR_REQUEST; + wsprintf(szStatus[status] + lstrlen(szStatus[status]), " (%s)", buf); + } + else if(*buf == '5') + { + status = ERR_SERVER; + wsprintf(szStatus[status] + lstrlen(szStatus[status]), " (%s)", buf); + } + } +} + +/***************************************************** + * FUNCTION NAME: openInetFile() + * PURPOSE: + * file open, size request, re-get lseek + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +HINTERNET openInetFile(HINTERNET hConn, + INTERNET_SCHEME nScheme, + char *path) +{ + char buf[256] = "", *movp; + HINTERNET hFile; + DWORD rslt, err, gle; + bool req_sent_ok = false; + status = ST_URLOPEN; + if(nScheme == INTERNET_SCHEME_FTP) + { +/* reads connection / auth responce info and cleares buffer this way */ + InternetGetLastResponseInfo(&err, buf, &(rslt = sizeof(buf))); + if(cnt == 0) + { + if(!fput) // we know local file size already + { +/* too clever myFtpCommand returnes false on the valid "550 Not found/Not permitted" server answer, + to read answer I had to ignory returned false (!= 999999) :-( + GetLastError also possible, but MSDN description of codes is very limited */ + wsprintf(buf, "SIZE %s", path + 1); + if(myFtpCommand != NULL && + myFtpCommand(hConn, false, FTP_TRANSFER_TYPE_ASCII, buf, 0, &hFile) != 9999 && + memset(buf, 0, sizeof(buf)) != NULL && + InternetGetLastResponseInfo(&err, buf, &(rslt = sizeof(buf)))) + { + if(strstr(buf, "213 ")) + { + fs = strtol(strchr(buf, ' ') + 1, NULL, 0); + } +/* stupied ProFTPD + else if(strstr(buf, "550 ")) + { + status = ERR_SIZE_NOT_PERMITTED; + return NULL; + } +*/ + } + if(fs == 0) + { + fs = NOT_AVAILABLE; + } + + } + } + else + { + wsprintf(buf, "REST %d", cnt); + if(myFtpCommand == NULL || + !myFtpCommand(hConn, false, FTP_TRANSFER_TYPE_BINARY, buf, 0, &hFile) || + memset(buf, 0, sizeof(buf)) == NULL || + !InternetGetLastResponseInfo(&err, buf, &(rslt = sizeof(buf))) || + (strstr(buf, "350") == NULL && strstr(buf, "110") == NULL)) + { + status = ERR_REGET; + return NULL; + } + } + if((hFile = FtpOpenFile(hConn, path + 1, fput ? GENERIC_WRITE : GENERIC_READ, + FTP_TRANSFER_TYPE_BINARY|INTERNET_FLAG_RELOAD,0)) == NULL) + { + gle = GetLastError(); + *buf = 0; + InternetGetLastResponseInfo(&err, buf, &(rslt = sizeof(buf))); +// wrong path - dir may not exist + if(fput && strstr(buf, "550") != NULL) + { + + movp = path + 1; + if(*movp == '/') movp++; // don't need to creat root + while(strchr(movp, '/')) + { + *strchr(movp,'/') = 0; + FtpCreateDirectory(hConn, path + 1); + InternetGetLastResponseInfo(&err, buf, &(rslt = sizeof(buf))); + *(movp + lstrlen(movp)) = '/'; + movp = strchr(movp, '/') + 1; + } + if(status != ERR_CREATEDIR && + (hFile = FtpOpenFile(hConn, path + 1, GENERIC_WRITE, + FTP_TRANSFER_TYPE_BINARY|INTERNET_FLAG_RELOAD,0)) == NULL) + { + status = ERR_PATH; + if(InternetGetLastResponseInfo(&err, buf, &(rslt = sizeof(buf)))) + lstrcpyn(szStatus[status], buf, 31); + } + } +// firewall related error, let's give user time to disable it + else if(gle == 12003) + { + status = ERR_SERVER; + if(*buf) + lstrcpyn(szStatus[status], buf, 31); + } +// timeout (firewall or dropped connection problem) + else if(gle == 12002) + { + if(!silent) + resume = true; + status = ERR_URLOPEN; + } + } + } + else + { + if((hFile = HttpOpenRequest(hConn, fput ? "PUT" : (fhead ? "HEAD" : (szPost ? "POST" : NULL)), + path, NULL, NULL, NULL, + INTERNET_FLAG_RELOAD | INTERNET_FLAG_KEEP_CONNECTION | + (nScheme == INTERNET_SCHEME_HTTPS ? INTERNET_FLAG_SECURE | + INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS : 0), 0)) != NULL) + { + + if(*szUsername != 0) + InternetSetOption(hFile, INTERNET_OPTION_PROXY_USERNAME, szUsername, lstrlen(szUsername) + 1); + if(*szPassword != 0) + InternetSetOption(hFile, INTERNET_OPTION_PROXY_PASSWORD, szPassword, sizeof(szPassword)); + if(szPost != NULL) + HttpAddRequestHeaders(hFile, POST_HEADER, lstrlen(POST_HEADER), + HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); + if(szHeader != NULL) + HttpAddRequestHeaders(hFile, szHeader, lstrlen(szHeader), + HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); + if(fput) + { + wsprintf(buf, "Content-Type: octet-stream\nContent-Length: %d", fs); + /*MessageBox(childwnd, buf, "", 0);*/ + HttpAddRequestHeaders(hFile, buf, lstrlen(buf), + HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); + } +// on Win98 security flags may be setten after first (failed) Send only. XP works without this. + if(nScheme == INTERNET_SCHEME_HTTPS) + { + if(!mySendRequest(hFile)) + { + InternetQueryOption (hFile, INTERNET_OPTION_SECURITY_FLAGS, + (LPVOID)&rslt, &(err = sizeof(rslt))); + rslt |= SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_REVOCATION; + InternetSetOption (hFile, INTERNET_OPTION_SECURITY_FLAGS, + &rslt, sizeof(rslt) ); + } + else req_sent_ok = true; + } +// Request answer may be after optional second Send +resend: + if(req_sent_ok || mySendRequest(hFile)) + { + if(!fput) + { + if(cnt == 0) + { + queryStatus(hFile); + if(HttpQueryInfo(hFile, HTTP_QUERY_CONTENT_LENGTH, buf, + &(rslt = sizeof(buf)), NULL)) + fs = strtoul(buf, NULL, 0); + else fs = NOT_AVAILABLE; + } + else + { + if(!InternetSetFilePointer(hFile, cnt, NULL, FILE_BEGIN, 0)) + { + status = ERR_REGET; + } + } + } + } + else + { + status = ERR_SENDREQUEST; + } + if(!silent && (status == ERR_PROXY || status == ERR_AUTH)) + { + rslt = InternetErrorDlg(hDlg, hFile, ERROR_SUCCESS, + FLAGS_ERROR_UI_FILTER_FOR_ERRORS | + FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | + FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, + NULL); + if (rslt != ERROR_CANCELLED) + { + status = ST_URLOPEN; + req_sent_ok = false; + goto resend; + } + } + } + else status = ERR_OPENREQUEST; + } + if(status != ST_URLOPEN && hFile != NULL) + { + InternetCloseHandle(hFile); + hFile = NULL; + } + return hFile; +} + +/***************************************************** + * FUNCTION NAME: inetTransfer() + * PURPOSE: + * http/ftp file transfer + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +DWORD __stdcall inetTransfer(void *hw) +{ + HINTERNET hSes, hConn, hFile; + HINSTANCE hInstance = NULL; + HANDLE localFile = NULL; + HWND hDlg = (HWND)hw; + DWORD lastCnt, rslt; + char hdr[2048]; + char *host = (char*)GlobalAlloc(GPTR, g_stringsize), + *path = (char*)GlobalAlloc(GPTR, g_stringsize), + *user = (char*)GlobalAlloc(GPTR, g_stringsize), + *passwd = (char*)GlobalAlloc(GPTR, g_stringsize), + *params = (char*)GlobalAlloc(GPTR, g_stringsize); + + URL_COMPONENTS uc = {sizeof(URL_COMPONENTS), NULL, 0, + (INTERNET_SCHEME)0, host, g_stringsize, 0 , user, g_stringsize, + passwd, g_stringsize, path, g_stringsize, params, g_stringsize}; + + if((hSes = InternetOpen(*szUserAgent == 0 ? INETC_USERAGENT : szUserAgent, openType, szProxy, NULL, 0)) != NULL) + { + if(InternetQueryOption(hSes, INTERNET_OPTION_CONNECTED_STATE, &(rslt=0), + &(lastCnt=sizeof(DWORD))) && + (rslt & INTERNET_STATE_DISCONNECTED_BY_USER)) + { + INTERNET_CONNECTED_INFO ci = {INTERNET_STATE_CONNECTED, 0}; + InternetSetOption(hSes, + INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci)); + } + if(timeout > 0) + lastCnt = InternetSetOption(hSes, INTERNET_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout)); +// 60 sec WinInet.dll detach delay on socket time_wait fix +// if(hInstance = GetModuleHandle("wininet.dll")) + if(hInstance = LoadLibrary("wininet.dll")) + myFtpCommand = (FTP_CMD)GetProcAddress(hInstance, "FtpCommandA"); + while(!popstring(url) && lstrcmpi(url, "/end") != 0) + { +// sf(hDlg); + if(popstring(fn) != 0 || lstrcmpi(url, "/end") == 0) break; + status = ST_CONNECTING; + cnt = fs = *host = *user = *passwd = *path = *params = 0; + PostMessage(hDlg, WM_TIMER, 1, 0); // show url & fn, do it sync + if((localFile = CreateFile(fn, fput ? GENERIC_READ : GENERIC_WRITE, FILE_SHARE_READ, + NULL, fput ? OPEN_EXISTING : CREATE_ALWAYS, 0, NULL)) != NULL) + { + uc.dwHostNameLength = uc.dwUserNameLength = uc.dwPasswordLength = + uc.dwUrlPathLength = uc.dwExtraInfoLength = g_stringsize; + if(fput) + { + GetFileSize(localFile, &fs); + } + if(InternetCrackUrl(url, 0, ICU_ESCAPE , &uc)) + { + lstrcat(path, params); + transfStart = GetTickCount(); + do + { +// re-PUT to already deleted tmp file on http server is not possible. +// the same with POST - must re-send data to server. for 'resume' loop + if((fput && uc.nScheme != INTERNET_SCHEME_FTP) || szPost) + { + cnt = 0; + SetFilePointer(localFile, 0, NULL, SEEK_SET); + } + status = ST_CONNECTING; + lastCnt = cnt; + if((hConn = InternetConnect(hSes, host, uc.nPort, + lstrlen(user) > 0 ? user : NULL, + lstrlen(passwd) > 0 ? passwd : NULL, + uc.nScheme == INTERNET_SCHEME_FTP ? INTERNET_SERVICE_FTP : INTERNET_SERVICE_HTTP, + uc.nScheme == INTERNET_SCHEME_FTP ? INTERNET_FLAG_PASSIVE : 0, 0)) != NULL) + { + if((hFile = openInetFile(hConn, uc.nScheme, path)) != NULL) + { + if(fhead) + {// repeating calls clears headers.. + HttpQueryInfo(hFile, HTTP_QUERY_RAW_HEADERS_CRLF, hdr, &(rslt=2048), NULL); + WriteFile(localFile, hdr, rslt, &lastCnt, NULL); + status = ST_OK; + } + else + { + fileTransfer(localFile, hFile); + if(fput && uc.nScheme != INTERNET_SCHEME_FTP) + { + HttpEndRequest(hFile, NULL, 0, 0); + queryStatus(hFile); + } + } + InternetCloseHandle(hFile); + } + InternetCloseHandle(hConn); + } + else + { + rslt = GetLastError(); + if((rslt == 12003 || rslt == 12002) && !silent) + resume = true; + status = ERR_CONNECT; + } +// Sleep(2000); + } while(((!fput || uc.nScheme == INTERNET_SCHEME_FTP) && + cnt > lastCnt && + status == ERR_TRANSFER && + SleepEx(PAUSE1_SEC * 1000, false) == 0 && + (status = ST_PAUSE) != ST_OK && + SleepEx(PAUSE2_SEC * 1000, false) == 0) + || (resume && + status != ST_OK && + status != ST_CANCELLED && + status != ERR_NOTFOUND && + ShowWindow(hDlg, SW_HIDE) != -1 && +// MessageBox(hDlg, szResume, szCaption, MB_RETRYCANCEL|MB_ICONWARNING) == IDRETRY && + MessageBox(GetParent(hDlg), szResume, szCaption, MB_RETRYCANCEL|MB_ICONWARNING) == IDRETRY && + (status = ST_PAUSE) != ST_OK && + ShowWindow(hDlg, silent ? SW_HIDE : SW_SHOW) == false && + SleepEx(PAUSE3_SEC * 1000, false) == 0)); + } + else status = ERR_CRACKURL; + CloseHandle(localFile); + if(!fput && status != ST_OK) + { + DeleteFile(fn); + break; + } + } + else status = ERR_FILEOPEN; + } + InternetCloseHandle(hSes); + } + else status = ERR_INETOPEN; + GlobalFree(host); + GlobalFree(path); + GlobalFree(user); + GlobalFree(passwd); + GlobalFree(params); + if(IsWindow(hDlg)) + PostMessage(hDlg, WM_COMMAND, MAKELONG(IDOK, INTERNAL_OK), 0); + return status; +} + +/***************************************************** + * FUNCTION NAME: fsFormat() + * PURPOSE: + * formats DWORD (max 4 GB) file size for dialog, big MB + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +void fsFormat(DWORD bfs, + char *b) +{ + if(bfs == NOT_AVAILABLE) + lstrcpy(b, "???"); + else if(bfs == 0) + lstrcpy(b, "0"); + else if(bfs < 10 * 1024) + wsprintf(b, "%u bytes", bfs); + else if(bfs < 10 * 1024 * 1024) + wsprintf(b, "%u kB", bfs / 1024); + else wsprintf(b, "%u MB", (bfs / 1024 / 1024)); +} + + +/***************************************************** + * FUNCTION NAME: progress_callback + * PURPOSE: + * old-style progress bar text updates + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ + +void progress_callback(void) +{ + char buf[1024] = "", b[1024] = ""; + int time_sofar = max(1, (GetTickCount() - transfStart) / 1000); + int bps = cnt / time_sofar; + int remain = (cnt > 0 && fs != NOT_AVAILABLE) ? (MulDiv(time_sofar, fs, cnt) - time_sofar) : 0; + char *rtext=szSecond; + if(remain < 0) remain = 0; + if (remain >= 60) + { + remain/=60; + rtext=szMinute; + if (remain >= 60) + { + remain/=60; + rtext=szHour; + } + } + wsprintf(buf, + szProgress, + cnt/1024, + fs > 0 && fs != NOT_AVAILABLE ? MulDiv(100, cnt, fs) : 0, + fs != NOT_AVAILABLE ? fs/1024 : 0, + bps/1024,((bps*10)/1024)%10 + ); + if (remain) wsprintf(buf+lstrlen(buf), + szRemaining, + remain, + rtext, + remain==1?"":szPlural + ); + SetDlgItemText(hDlg, IDC_STATIC1, (cnt == 0 || status == ST_CONNECTING) ? szConnecting : buf); + SendMessage(GetDlgItem(hDlg, IDC_PROGRESS1), PBM_SETPOS, fs > 0 && fs != NOT_AVAILABLE ? + MulDiv(cnt, PB_RANGE, fs) : 0, 0); + wsprintf(buf, + szDownloading, + strchr(fn, '\\') ? strrchr(fn, '\\') + 1 : fn + ); + HWND hwndS = GetDlgItem(childwnd, 1006); + if(!silent && hwndS != NULL && IsWindow(hwndS)) + { + GetWindowText(hwndS, b, sizeof(b)); + if(lstrcmp(b, buf) != 0) + SetWindowText(hwndS, buf); + } +} + +/***************************************************** + * FUNCTION NAME: onTimer() + * PURPOSE: + * updates text fields every second + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +void onTimer(HWND hDlg) +{ + char b[128]; + DWORD ct = (GetTickCount() - transfStart) / 1000, + tt = (GetTickCount() - startTime) / 1000; +// dialog window caption + wsprintf(b, "%s - %s", szCaption, szStatus[status]); + if(fs > 0 && fs != NOT_AVAILABLE && status == ST_DOWNLOAD) + { + wsprintf(b + lstrlen(b), " %d%%", MulDiv(100, cnt, fs)); + } + if(*szBanner == 0) SetWindowText(hDlg, b); +// current file and url + SetDlgItemText(hDlg, IDC_STATIC1, *szAlias ? szAlias : url); + SetDlgItemText(hDlg, IDC_STATIC2, /*strchr(fn, '\\') ? strrchr(fn, '\\') + 1 : */fn); +// bytes done and rate + if(cnt > 0) + { + fsFormat(cnt, b); + if(ct > 1 && status == ST_DOWNLOAD) + { + lstrcat(b, " ( "); + fsFormat(cnt / ct, b + lstrlen(b)); + lstrcat(b, "/sec )"); + } + } + else *b = 0; + SetDlgItemText(hDlg, IDC_STATIC3, b); +// total download time + wsprintf(b, "%d:%02d:%02d", tt / 3600, (tt / 60) % 60, tt % 60); + SetDlgItemText(hDlg, IDC_STATIC6, b); +// file size, time remaining, progress bar + if(fs == NOT_AVAILABLE) + { + SetWindowText(GetDlgItem(hDlg, IDC_STATIC5), "Not Available"); + SetWindowText(GetDlgItem(hDlg, IDC_STATIC4), "Unknown"); +// ShowWindow(GetDlgItem(hDlg, IDC_PROGRESS1), SW_HIDE); + SendDlgItemMessage(hDlg, IDC_PROGRESS1, PBM_SETPOS, 0, 0); + } + else if(fs > 0) + { + fsFormat(fs, b); + SetDlgItemText(hDlg, IDC_STATIC5, b); + ShowWindow(GetDlgItem(hDlg, IDC_PROGRESS1), SW_NORMAL); + SendDlgItemMessage(hDlg, IDC_PROGRESS1, PBM_SETPOS, MulDiv(cnt, PB_RANGE, fs), 0); + if(cnt > 5000) + { + ct = MulDiv(fs - cnt, ct, cnt); + wsprintf(b, "%d:%02d:%02d", ct / 3600, (ct / 60) % 60, ct % 60); + } + else *b = 0; + SetWindowText(GetDlgItem(hDlg, IDC_STATIC4), b); + } + else + { + SetDlgItemText(hDlg, IDC_STATIC5, ""); + SetDlgItemText(hDlg, IDC_STATIC4, ""); + SendDlgItemMessage(hDlg, IDC_PROGRESS1, PBM_SETPOS, 0, 0); + } +} + +/***************************************************** + * FUNCTION NAME: timeRemaining() + * PURPOSE: + * Returns a string of the remaining time for the + * download. + * + *****************************************************/ +extern "C" +void __declspec(dllexport) timeRemaining(HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop, + extra_parameters *extra + ) +{ + char b[128]; + DWORD ct = (GetTickCount() - transfStart) / 1000; + + EXDLL_INIT(); + + wsprintf(b, "%d, %d, %d", fs, cnt, ct); + pushstring(b); + if((fs != NOT_AVAILABLE) && (fs > 0) && (cnt > 5000)) + { + ct = MulDiv(fs - cnt, ct, cnt); + wsprintf(b, "%d:%02d:%02d", ct / 3600, (ct / 60) % 60, ct % 60); + pushstring(b); + + } else pushstring("Unknown"); +} + +extern "C" +void __declspec(dllexport) cancel(HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop, + extra_parameters *extra + ) +{ + status = ST_CANCELLED; +} + +/***************************************************** + * FUNCTION NAME: centerDlg() + * PURPOSE: + * centers dlg on NSIS parent + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +void centerDlg(HWND hDlg) +{ + HWND hwndParent = GetParent(hDlg); + RECT nsisRect, dlgRect, waRect; + int dlgX, dlgY, dlgWidth, dlgHeight; + + if(hwndParent == NULL || silent) + return; + if(popup) + GetWindowRect(hwndParent, &nsisRect); + else GetClientRect(hwndParent, &nsisRect); + GetWindowRect(hDlg, &dlgRect); + + dlgWidth = dlgRect.right - dlgRect.left; + dlgHeight = dlgRect.bottom - dlgRect.top; + dlgX = (nsisRect.left + nsisRect.right - dlgWidth) / 2; + dlgY = (nsisRect.top + nsisRect.bottom - dlgHeight) / 2; + + if(popup) + { + SystemParametersInfo(SPI_GETWORKAREA, 0, &waRect, 0); + if(dlgX > waRect.right - dlgWidth) + dlgX = waRect.right - dlgWidth; + if(dlgX < waRect.left) dlgX = waRect.left; + if(dlgY > waRect.bottom - dlgHeight) + dlgY = waRect.bottom - dlgHeight; + if(dlgY < waRect.top) dlgY = waRect.top; + } + else dlgY += 20; + + SetWindowPos(hDlg, HWND_TOP, dlgX, dlgY, 0, 0, SWP_NOSIZE); +} + +/***************************************************** + * FUNCTION NAME: onInitDlg() + * PURPOSE: + * dlg init + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +void onInitDlg(HWND hDlg) +{ + HFONT hFont; + HWND hPrbNew; + HWND hPrbOld; + HWND hCan = GetDlgItem(hDlg, IDCANCEL); +// char s[32]; + + if(childwnd) + { + hPrbNew = GetDlgItem(hDlg, IDC_PROGRESS1); + hPrbOld = GetDlgItem(childwnd, 0x3ec); + +// Backland' fix for progress bar redraw/style issue. +// Original bar may be hidden because of interfernce with other plug-ins. + LONG prbStyle = WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + if(hPrbOld != NULL/* && GetClassName(hPrbOld, s, sizeof(s)) > 0 && lstrcmpi(s, "msctl_progress32") == 0*/) + { + prbStyle |= GetWindowLong(hPrbOld, GWL_STYLE); + } + SetWindowLong(hPrbNew, GWL_STYLE, prbStyle); + + if(!popup) + { + if((hFont = (HFONT)SendMessage(childwnd, WM_GETFONT, 0, 0)) != NULL) + { + SendDlgItemMessage(hDlg, IDC_STATIC1, WM_SETFONT, (WPARAM)hFont, 0); + SendDlgItemMessage(hDlg, IDCANCEL, WM_SETFONT, (WPARAM)hFont, 0); + } + if(*szCancel == 0) + GetWindowText(GetDlgItem(GetParent(childwnd), IDCANCEL), szCancel, sizeof(szCancel)); + SetWindowText(hCan, szCancel); + SetWindowPos(hPrbNew, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + } + } + + if(nocancel) + { + if(hCan != NULL) + ShowWindow(hCan, SW_HIDE); + if(popup) + SetWindowLong(hDlg, GWL_STYLE, GetWindowLong(hDlg, GWL_STYLE) ^ WS_SYSMENU); + } + SendDlgItemMessage(hDlg, IDC_PROGRESS1, PBM_SETRANGE, + 0, MAKELPARAM(0, PB_RANGE)); + if(*szBanner != 0) + { + SendDlgItemMessage(hDlg, IDC_STATIC13, STM_SETICON, + (WPARAM)LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(103)), 0); + SetDlgItemText(hDlg, IDC_STATIC12, szBanner); + if(*szCaption != 0) SetWindowText(hDlg, szCaption); + } + SetTimer(hDlg, 1, 1000, NULL); + if(*szUrl != 0) + { + SetDlgItemText(hDlg, IDC_STATIC20, szUrl); + SetDlgItemText(hDlg, IDC_STATIC21, szDownloading); + SetDlgItemText(hDlg, IDC_STATIC22, szConnecting); + SetDlgItemText(hDlg, IDC_STATIC23, szProgress); + SetDlgItemText(hDlg, IDC_STATIC24, szSecond); + SetDlgItemText(hDlg, IDC_STATIC25, szRemaining); + } +} + +/***************************************************** + * FUNCTION NAME: dlgProc() + * PURPOSE: + * dlg message handling procedure + * SPECIAL CONSIDERATIONS: + * todo: better dialog design + *****************************************************/ +BOOL WINAPI dlgProc(HWND hDlg, + UINT message, + WPARAM wParam, + LPARAM lParam ) { + switch(message) { + case WM_INITDIALOG: + onInitDlg(hDlg); + centerDlg(hDlg); + break; + case WM_PAINT: +/* child dialog redraw problem. return false is important */ + RedrawWindow(GetDlgItem(hDlg, IDC_STATIC1), NULL, NULL, RDW_INVALIDATE); + RedrawWindow(GetDlgItem(hDlg, IDCANCEL), NULL, NULL, RDW_INVALIDATE); + RedrawWindow(GetDlgItem(hDlg, IDC_PROGRESS1), NULL, NULL, RDW_INVALIDATE); + UpdateWindow(GetDlgItem(hDlg, IDC_STATIC1)); + UpdateWindow(GetDlgItem(hDlg, IDCANCEL)); + UpdateWindow(GetDlgItem(hDlg, IDC_PROGRESS1)); + return false; + case WM_TIMER: + if(!silent && IsWindow(hDlg)) + { +// long connection period and paused state updates + if(status != ST_DOWNLOAD && GetTickCount() - transfStart > PROGRESS_MS) + transfStart += PROGRESS_MS; + if(popup) onTimer(hDlg); + else progress_callback(); + RedrawWindow(GetDlgItem(hDlg, IDC_STATIC1), NULL, NULL, RDW_INVALIDATE); + RedrawWindow(GetDlgItem(hDlg, IDCANCEL), NULL, NULL, RDW_INVALIDATE); + RedrawWindow(GetDlgItem(hDlg, IDC_PROGRESS1), NULL, NULL, RDW_INVALIDATE); + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDCANCEL: + if(nocancel) break; + status = ST_CANCELLED; + case IDOK: + if(status != ST_CANCELLED && HIWORD(wParam) != INTERNAL_OK) break; +// otherwise in the silent mode next banner windows may go to background +// if(silent) sf(hDlg); +// Sleep(3000); + KillTimer(hDlg, 1); + DestroyWindow(hDlg); + break; + } + default: return false; + } + return true; +} + + /***************************************************** + * FUNCTION NAME: get() + * PURPOSE: + * http/https/ftp file download entry point + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +extern "C" +void __declspec(dllexport) get(HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop, + extra_parameters *extra + ) +{ + HANDLE hThread; + DWORD dwThreadId, dwStyle = 0; + MSG msg; + + EXDLL_INIT(); + if(szPost) + popstring(szPost); + +// for /nounload plug-un calls - global vars clean up + silent = popup = resume = nocancel = noproxy = false; + myFtpCommand = NULL; + openType = INTERNET_OPEN_TYPE_PRECONFIG; + status = ST_CONNECTING; + *szBanner = *szCaption = *szUsername = *szPassword = *szCancel = *szUserAgent = *szAlias = 0; + + url = (char*)GlobalAlloc(GPTR, string_size); +// global silent option + if(extra->exec_flags->silent != 0) + silent = true; +// we must take this from stack, or push url back + while(!popstring(url) && *url == '/') + { + if(lstrcmpi(url, "/silent") == 0) + silent = true; + else if(lstrcmpi(url, "/caption") == 0) + popstring(szCaption); + else if(lstrcmpi(url, "/username") == 0) + popstring(szUsername); + else if(lstrcmpi(url, "/password") == 0) + popstring(szPassword); + else if(lstrcmpi(url, "/nocancel") == 0) + nocancel = true; + else if(lstrcmpi(url, "/noproxy") == 0) + openType = INTERNET_OPEN_TYPE_DIRECT; + else if(lstrcmpi(url, "/popup") == 0) + { + popup = true; + popstring(szAlias); + } + else if(lstrcmpi(url, "/resume") == 0) + { + popstring(url); + if(lstrlen(url) > 0) + lstrcpy(szResume, url); + resume = true; + } + else if(lstrcmpi(url, "/translate") == 0) + { + if(popup) + { + popstring(szUrl); + popstring(szStatus[ST_DOWNLOAD]); // Downloading + popstring(szStatus[ST_CONNECTING]); // Connecting + lstrcpy(szStatus[ST_URLOPEN], szStatus[ST_CONNECTING]); + popstring(szDownloading);// file name + popstring(szConnecting);// received + popstring(szProgress);// file size + popstring(szSecond);// remaining time + popstring(szRemaining);// total time + } + else + { + popstring(szDownloading); + popstring(szConnecting); + popstring(szSecond); + popstring(szMinute); + popstring(szHour); + popstring(szPlural); + popstring(szProgress); + popstring(szRemaining); + } + } + else if(lstrcmpi(url, "/banner") == 0) + { + popup = true; + popstring(szBanner); + } + else if(lstrcmpi(url, "/canceltext") == 0) + { + popstring(szCancel); + } + else if(lstrcmpi(url, "/useragent") == 0) + { + popstring(szUserAgent); + } + else if(lstrcmpi(url, "/proxy") == 0) + { + szProxy = (char*)GlobalAlloc(GPTR, string_size); + popstring(szProxy); + openType = INTERNET_OPEN_TYPE_PROXY; + } + else if(lstrcmpi(url, "/timeout") == 0) + { + popstring(url); + timeout = strtol(url, NULL, 10); + } + else if(lstrcmpi(url, "/header") == 0) + { + szHeader = (char*)GlobalAlloc(GPTR, string_size); + popstring(szHeader); + } + } + if(*szCaption == 0) lstrcpy(szCaption, PLUGIN_NAME); + pushstring(url); +// may be silent for plug-in, but not so for installer itself - let's try to define 'progress text' + if(hwndParent != NULL && + (childwnd = FindWindowEx(hwndParent, NULL, "#32770", NULL)) != NULL && + !silent) + SetDlgItemText(childwnd, 1006, szCaption); + else InitCommonControls(); // or NSIS do this before .onInit? +// cannot embed child dialog to non-existing parent. Using 'silent' to hide it + if(childwnd == NULL && !popup) silent = true; +// let's use hidden popup dlg in the silent mode - works both on .onInit and Page + if(silent) { resume = false; popup = true; } +// google says WS_CLIPSIBLINGS helps to redraw... not in my tests... + if(!popup) + { + unsigned int wstyle = GetWindowLong(childwnd, GWL_STYLE); + wstyle |= WS_CLIPSIBLINGS; + SetWindowLong(childwnd, GWL_STYLE, wstyle); + } + startTime = GetTickCount(); + if((hDlg = CreateDialog(g_hInstance, + MAKEINTRESOURCE(*szBanner ? IDD_DIALOG2 : (popup ? IDD_DIALOG1 : IDD_DIALOG3)), + (popup ? hwndParent : childwnd), dlgProc)) != NULL) + { + + if((hThread = CreateThread(NULL, 0, inetTransfer, (LPVOID)hDlg, 0, + &dwThreadId)) != NULL) + { + HWND hDetailed = GetDlgItem(childwnd, 0x403); + if(!silent) + { + ShowWindow(hDlg, SW_NORMAL); + if(childwnd && !popup) + { + dwStyle = GetWindowLong(hDetailed, GWL_STYLE); + EnableWindow(hDetailed, false); + } + } + + while(IsWindow(hDlg) && + GetMessage(&msg, NULL, 0, 0) > 0) + { + if(!IsDialogMessage(hDlg, &msg) && + !IsDialogMessage(hwndParent, &msg) && + !TranslateMessage(&msg)) + DispatchMessage(&msg); + } + + if(WaitForSingleObject(hThread, 3000) == WAIT_TIMEOUT) + { + TerminateThread(hThread, 1); + status = ERR_TERMINATED; + } + CloseHandle(hThread); + if(!silent && childwnd) + { + SetDlgItemText(childwnd, 1006, ""); + if(!popup) + SetWindowLong(hDetailed, GWL_STYLE, dwStyle); +// RedrawWindow(childwnd, NULL, NULL, RDW_INVALIDATE|RDW_ERASE); + } + } + else + { + status = ERR_THREAD; + DestroyWindow(hDlg); + } + } + else { + status = ERR_DIALOG; + wsprintf(szStatus[status] + lstrlen(szStatus[status]), " (Err=%d)", GetLastError()); + } + if(status != ST_OK) + { + while(!popstring(url) && lstrcmpi(url, "/end") != 0) + { /* nothing MessageBox(NULL, url, "", 0);*/ } + } + GlobalFree(url); + if(szProxy) GlobalFree(szProxy); + if(szPost) GlobalFree(szPost); + if(szHeader) GlobalFree(szHeader); + + url = szProxy = szPost = szHeader = NULL; + fput = fhead = false; + + pushstring(szStatus[status]); +} + +/***************************************************** + * FUNCTION NAME: put() + * PURPOSE: + * http/ftp file upload entry point + * SPECIAL CONSIDERATIONS: + * re-put not works with http, but ftp REST - may be. + *****************************************************/ +extern "C" +void __declspec(dllexport) put(HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop, + extra_parameters *extra + ) +{ + fput = true; + lstrcpy(szDownloading, "Uploading %s"); + lstrcpy(szStatus[2], "Uploading"); + get(hwndParent, string_size, variables, stacktop, extra); +} + +/***************************************************** + * FUNCTION NAME: post() + * PURPOSE: + * http post entry point + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +extern "C" +void __declspec(dllexport) post(HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop, + extra_parameters *extra + ) +{ + szPost = (char*)GlobalAlloc(GPTR, string_size); + get(hwndParent, string_size, variables, stacktop, extra); +} + +/***************************************************** + * FUNCTION NAME: head() + * PURPOSE: + * http/ftp file upload entry point + * SPECIAL CONSIDERATIONS: + * re-put not works with http, but ftp REST - may be. + *****************************************************/ +extern "C" +void __declspec(dllexport) head(HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop, + extra_parameters *extra + ) +{ + fhead = true; + get(hwndParent, string_size, variables, stacktop, extra); +} + +/***************************************************** + * FUNCTION NAME: DllMain() + * PURPOSE: + * Dll main (initialization) entry point + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +BOOL WINAPI DllMain(HANDLE hInst, + ULONG ul_reason_for_call, + LPVOID lpReserved) +{ + g_hInstance=(HINSTANCE)hInst; + return TRUE; +} + /*wsprintf(buf, "GetLastError=%d", GetLastError()); + MessageBox(childwnd, buf, "", 1);*/ + +char *__urlencode(char *str_to_encode) { + + int lent = strlen(str_to_encode); + char *tmp = (char*)calloc(lent*10, sizeof(char)); + int i = 0; + int x = 0; + char c = 0; + char hex[3]; + +/* http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4029/#more */ + + for (; i 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123)) + tmp[x++] = c; + else { + sprintf(hex, "%02X", c); + tmp[x++] = '%'; + tmp[x++] = hex[0]; + tmp[x++] = hex[1]; + } + } + + tmp[x++] = 0; + + tmp = (char *) realloc(tmp, x);//sizeof(char)*strlen(tmp)); + return tmp; +} + + +extern "C" +void __declspec(dllexport) urlencode(HWND hwndParent, + int string_size, + char *variables, + stack_t **stacktop, + extra_parameters *extra + ) +{ + EXDLL_INIT(); + + char *thestring = (char*)GlobalAlloc(GPTR, g_stringsize); + popstring(thestring); + //MessageBox(hwndParent, thestring, "got the string", 1); + char* res = __urlencode(thestring); + //MessageBox(hwndParent, res, "got result", 1); + pushstring(res); +} diff --git a/installer/NSIS/Contrib/Inetc/inetc.dll b/installer/NSIS/Contrib/Inetc/inetc.dll new file mode 100644 index 0000000..b8bc5f2 Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/inetc.dll differ diff --git a/installer/NSIS/Contrib/Inetc/inetc.dsp b/installer/NSIS/Contrib/Inetc/inetc.dsp new file mode 100644 index 0000000..c424139 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/inetc.dsp @@ -0,0 +1,117 @@ +# Microsoft Developer Studio Project File - Name="inetc" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=inetc - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "inetc.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "inetc.mak" CFG="inetc - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "inetc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "inetc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "inetc - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "debug" +# PROP Intermediate_Dir "debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "inetc_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O1 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "inetc_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 msvcrt.lib kernel32.lib user32.lib gdi32.lib wininet.lib comctl32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /nodefaultlib /out:"..\..\plugins\inetc.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "inetc - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "inetc_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "inetc_EXPORTS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib wininet.lib comctl32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"..\..\plugins\inetc.dll" /pdbtype:sept +# SUBTRACT LINK32 /incremental:no /debug + +!ENDIF + +# Begin Target + +# Name "inetc - Win32 Release" +# Name "inetc - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\inetc.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE="..\Graphics\Icons\classic-install.ico" +# End Source File +# Begin Source File + +SOURCE=.\inetc.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/installer/NSIS/Contrib/Inetc/inetc.dsw b/installer/NSIS/Contrib/Inetc/inetc.dsw new file mode 100644 index 0000000..98a25c5 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/inetc.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "ftpc"=".\inetc.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/installer/NSIS/Contrib/Inetc/inetc.ncb b/installer/NSIS/Contrib/Inetc/inetc.ncb new file mode 100644 index 0000000..e76c61e Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/inetc.ncb differ diff --git a/installer/NSIS/Contrib/Inetc/inetc.rc b/installer/NSIS/Contrib/Inetc/inetc.rc new file mode 100644 index 0000000..56a8438 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/inetc.rc @@ -0,0 +1,170 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Russian resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) +#ifdef _WIN32 +LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT +#pragma code_page(1251) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Russian resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOGEX 0, 0, 286, 71 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "InetLoad plug-in" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "",IDC_STATIC1,50,4,230,12,SS_CENTERIMAGE, + WS_EX_STATICEDGE + LTEXT "",IDC_STATIC2,50,18,230,12,SS_CENTERIMAGE, + WS_EX_STATICEDGE + CTEXT "",IDC_STATIC3,50,32,102,12,SS_CENTERIMAGE, + WS_EX_STATICEDGE + CTEXT "",IDC_STATIC4,220,32,60,12,SS_CENTERIMAGE, + WS_EX_STATICEDGE + CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",NOT + WS_VISIBLE,5,62,275,7 + CTEXT "",IDC_STATIC5,50,46,102,12,SS_CENTERIMAGE, + WS_EX_STATICEDGE + CTEXT "",IDC_STATIC6,220,46,60,12,SS_CENTERIMAGE, + WS_EX_STATICEDGE + CONTROL "URL",IDC_STATIC20,"Static",SS_LEFTNOWORDWRAP | WS_GROUP, + 5,6,44,10 + CONTROL "File name",IDC_STATIC21,"Static",SS_LEFTNOWORDWRAP | + WS_GROUP,5,20,44,10 + CONTROL "Transfered",IDC_STATIC22,"Static",SS_LEFTNOWORDWRAP | + WS_GROUP,5,34,44,10 + CONTROL "File size",IDC_STATIC23,"Static",SS_LEFTNOWORDWRAP | + WS_GROUP,5,48,44,10 + CONTROL "Remaining time",IDC_STATIC24,"Static",SS_LEFTNOWORDWRAP | + WS_GROUP,164,34,55,10 + CONTROL "Total time",IDC_STATIC25,"Static",SS_LEFTNOWORDWRAP | + WS_GROUP,164,48,55,10 +END + +IDD_DIALOG2 DIALOG DISCARDABLE 0, 0, 226, 62 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "InetClient plug-in" +FONT 8, "MS Sans Serif" +BEGIN + ICON 103,IDC_STATIC13,4,4,20,20 + LTEXT "Please wait",IDC_STATIC12,35,6,184,28 + CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",NOT + WS_VISIBLE,12,40,201,11 +END + +IDD_DIALOG3 DIALOG DISCARDABLE 0, 0, 266, 62 +STYLE DS_CONTROL | WS_CHILD | WS_VISIBLE +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",0x0,0,23, + 266,11 + CTEXT "",IDC_STATIC1,0,8,266,11 + PUSHBUTTON "Cancel",IDCANCEL,166,41,80,16 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 279 + TOPMARGIN, 7 + BOTTOMMARGIN, 64 + END + + IDD_DIALOG2, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 219 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END + + IDD_DIALOG3, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 259 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/installer/NSIS/Contrib/Inetc/inetc.sln b/installer/NSIS/Contrib/Inetc/inetc.sln new file mode 100644 index 0000000..ea7b8c4 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/inetc.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inetc", "inetc.vcproj", "{1DBA274F-7935-43F0-804D-436E69DE932E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1DBA274F-7935-43F0-804D-436E69DE932E}.Debug|Win32.ActiveCfg = Debug|Win32 + {1DBA274F-7935-43F0-804D-436E69DE932E}.Debug|Win32.Build.0 = Debug|Win32 + {1DBA274F-7935-43F0-804D-436E69DE932E}.Release|Win32.ActiveCfg = Release|Win32 + {1DBA274F-7935-43F0-804D-436E69DE932E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/installer/NSIS/Contrib/Inetc/inetc.suo b/installer/NSIS/Contrib/Inetc/inetc.suo new file mode 100644 index 0000000..6c2c2cc Binary files /dev/null and b/installer/NSIS/Contrib/Inetc/inetc.suo differ diff --git a/installer/NSIS/Contrib/Inetc/inetc.vcproj b/installer/NSIS/Contrib/Inetc/inetc.vcproj new file mode 100644 index 0000000..a4fcfa2 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/inetc.vcproj @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/NSIS/Contrib/Inetc/post.nsi b/installer/NSIS/Contrib/Inetc/post.nsi new file mode 100644 index 0000000..34a68aa --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/post.nsi @@ -0,0 +1,30 @@ + +;-------------------------------- +; General Attributes + +Name "Inetc Post Test" +OutFile "post.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + +; this is my LAN sample, use your own URL for tests. Sample post.php included + + inetc::post "login=ami&passwd=333" "http://p320/post.php?lg=iam&pw=44" "$EXEDIR\post_reply.htm" + Pop $0 # return value = exit code, "OK" if OK + MessageBox MB_OK "Download Status: $0" + +SectionEnd + + diff --git a/installer/NSIS/Contrib/Inetc/post.php b/installer/NSIS/Contrib/Inetc/post.php new file mode 100644 index 0000000..f6a8949 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/post.php @@ -0,0 +1,13 @@ + + + + +"; +echo "post.passwd=".$_POST['passwd']."
    "; +echo "get.lg=".$_GET['lg']."
    "; +echo "get.pw=".$_GET['pw']."
    "; +?> + + + diff --git a/installer/NSIS/Contrib/Inetc/post_form.html b/installer/NSIS/Contrib/Inetc/post_form.html new file mode 100644 index 0000000..96afa2d --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/post_form.html @@ -0,0 +1,18 @@ + + +Registration form for post.php test + + +This form sends POST request to server. It was interesting to compare server echo
    +reply (by included post.php or post.cgi) for this form and InetLoad plug-in - in my
    +tests server did not see any difference between them :)
    +
    + +
    +
    + + + + + + diff --git a/installer/NSIS/Contrib/Inetc/put.nsi b/installer/NSIS/Contrib/Inetc/put.nsi new file mode 100644 index 0000000..db2993b --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/put.nsi @@ -0,0 +1,30 @@ + +;-------------------------------- +; General Attributes + +Name "Inetc Test" +OutFile "put.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install-colorful.ico" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + +; this is my LAN sample, use your own URL for tests. Login/pwd hidden from user. Sample put.php (for http request) included + + inetc::put /POPUP "http://p320/" /CAPTION "my local http upload" "http://takhir:pwd@p320/m2.jpg" "$EXEDIR\m2.jpg" +; inetc::put /POPUP "ftp://p320/" /CAPTION "my local ftp upload" "ftp://takhir:pwd@p320/m2.bu.jpg" "$EXEDIR\m2.jpg" + Pop $0 # return value = exit code, "OK" if OK + MessageBox MB_OK "Upload Status: $0" + +SectionEnd diff --git a/installer/NSIS/Contrib/Inetc/put.php b/installer/NSIS/Contrib/Inetc/put.php new file mode 100644 index 0000000..7bb2bf5 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/put.php @@ -0,0 +1,19 @@ + diff --git a/installer/NSIS/Contrib/Inetc/recursive.nsi b/installer/NSIS/Contrib/Inetc/recursive.nsi new file mode 100644 index 0000000..52e0326 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/recursive.nsi @@ -0,0 +1,64 @@ +Name "Inetc Recursive Dir Upload Test" +OutFile "recursive.exe" + +!include "MUI.nsh" +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_LANGUAGE "English" +!include "FileFunc.nsh" +!insertmacro GetFileAttributes + +var url +var path + +Function dirul + + Push $0 ; search handle + Push $1 ; file name + Push $2 ; attributes + + FindFirst $0 $1 "$path\*" +loop: + StrCmp $1 "" done + ${GetFileAttributes} "$path\$1" DIRECTORY $2 + IntCmp $2 1 isdir +retry: + Inetc::put $url/$1 "$path\$1" /end + Pop $2 + DetailPrint "$2 $path\$1" + StrCmp $2 "OK" cont + MessageBox MB_YESNO "$path\$1 file upload failed. Retry?" IDYES retry + Abort "terminated by user" + Goto cont +isdir: + StrCmp $1 . cont + StrCmp $1 .. cont + Push $path + Push $url + StrCpy $path "$path\$1" + StrCpy $url "$url/$1" + Call dirul + Pop $url + Pop $path +cont: + FindNext $0 $1 + Goto loop +done: + FindClose $0 + + Pop $2 + Pop $1 + Pop $0 + +FunctionEnd + + +Section "Dummy Section" SecDummy + + SetDetailsView hide + StrCpy $path "$EXEDIR" +; put is dir in the user's ftp home, use //put for root-relative path + StrCpy $url ftp://takhir:pwd@localhost/put + Call dirul + SetDetailsView show + +SectionEnd diff --git a/installer/NSIS/Contrib/Inetc/redirect.nsi b/installer/NSIS/Contrib/Inetc/redirect.nsi new file mode 100644 index 0000000..cd29434 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/redirect.nsi @@ -0,0 +1,30 @@ + +;-------------------------------- +; General Attributes + +Name "Redirect Test" +OutFile "redirect.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_LANGUAGE "English" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + SetDetailsView hide + + inetc::get "http://localhost/redirect.php" "$EXEDIR\redirect.htm" /end + Pop $1 + + MessageBox MB_OK "Download Status: $1" + +SectionEnd + diff --git a/installer/NSIS/Contrib/Inetc/redirect.php b/installer/NSIS/Contrib/Inetc/redirect.php new file mode 100644 index 0000000..afe4552 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/redirect.php @@ -0,0 +1,6 @@ + diff --git a/installer/NSIS/Contrib/Inetc/resource.h b/installer/NSIS/Contrib/Inetc/resource.h new file mode 100644 index 0000000..d7fc835 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/resource.h @@ -0,0 +1,47 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by inetc.rc +// +#define IDC_SLOGIN 8 +#define IDC_PROGRESS 10 +#define IDC_SUBTEXT 11 +#define IDC_SPWD 11 +#define IDC_ICON1 12 +#define IDD_DIALOG1 101 +#define IDI_ICON1 102 +#define IDI_ICON2 103 +#define IDD_AUTH 104 +#define IDI_ICON3 105 +#define IDI_ICON4 106 +#define IDI_ICON5 107 +#define IDD_DIALOG2 108 +#define IDI_ICON6 109 +#define IDD_DIALOG3 110 +#define IDC_STATIC1 1001 +#define IDC_STATIC2 1002 +#define IDC_STATIC3 1003 +#define IDC_STATIC4 1004 +#define IDC_PROGRESS1 1005 +#define IDC_STATIC5 1006 +#define IDC_STATIC6 1007 +#define IDC_STATIC12 1008 +#define IDC_STATIC13 1009 +#define IDC_STATIC20 1009 +#define IDC_STATIC21 1010 +#define IDC_STATIC22 1011 +#define IDC_STATIC23 1012 +#define IDC_STATIC24 1013 +#define IDC_STATIC25 1014 +#define IDC_ELOGIN 1015 +#define IDC_EPWD 1016 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 111 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1018 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/installer/NSIS/Contrib/Inetc/translate.nsi b/installer/NSIS/Contrib/Inetc/translate.nsi new file mode 100644 index 0000000..053a815 --- /dev/null +++ b/installer/NSIS/Contrib/Inetc/translate.nsi @@ -0,0 +1,32 @@ + +;-------------------------------- +; General Attributes + +Name "Inetc Translate Test" +OutFile "Translate.exe" + + +;-------------------------------- +;Interface Settings + + !include "MUI.nsh" + !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install-colorful.ico" + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + !insertmacro MUI_LANGUAGE "Russian" + + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + +; This is russian variant. See Readme.txt for a list of parameters. +; Use LangStrings as TRANSLATE parameters for multilang options + + inetc::load /POPUP "" /CAPTION " " /TRANSLATE "URL" "" " " " " "" "" "" "http://ineum.narod.ru/g06s.htm" "$EXEDIR\g06s.htm" + Pop $0 # return value = exit code, "OK" if OK + MessageBox MB_OK "Download Status: $0" + +SectionEnd diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/Button.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/Button.h new file mode 100644 index 0000000..c2d64ce --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/Controls/Button.h @@ -0,0 +1,6 @@ +int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM lpData) { + if (uMsg == BFFM_INITIALIZED && lpData) + mySendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)lpData); + + return 0; +} diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/CheckBox.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/CheckBox.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/ComboBox.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/ComboBox.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/DateTime.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/DateTime.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/GroupBox.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/GroupBox.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/Image.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/Image.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/IpAddress.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/IpAddress.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/Label.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/Label.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/Link.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/Link.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/ListBox.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/ListBox.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/ListView.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/ListView.h new file mode 100644 index 0000000..775b299 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/Controls/ListView.h @@ -0,0 +1,105 @@ +int iItemPlaceholder = -1; + +int WINAPI FIELD_LISTVIEW_IOLI_CountItems(LPSTR str, int nFlags) +{ + //nFlags: + //0 = Items. + //1 = SubItems. + + int nItems = 0; + int nItemsTemp = nItems; + int bSubItem = FALSE; + + int nResult = 0; + + char *pszEnd; + pszEnd = str; + + while (*pszEnd) + { + if (*pszEnd == '\x01') + { + if(!bSubItem && nFlags == 0) + ++nItems; + if(bSubItem && nFlags == 1) + ++nItemsTemp; + } + else if (*pszEnd == '\x02') + bSubItem = TRUE; + else if (*pszEnd == '\x03') + { + bSubItem = FALSE; + + if(nFlags == 1) + { + ++nItemsTemp; + + if(nItemsTemp > nItems) + nItems = nItemsTemp; + nItemsTemp = 0; + } + } + + pszEnd = CharNext(pszEnd); + } + + return nItems; +} + +int WINAPI FIELD_LISTVIEW_IOLI_CountSubItems(LPSTR str, LPSTR pszHeaderItems) +{ + int nPart = 0; + + int nItems = 0; + int nItemsTmp = 0; + int nItemsTemp = nItems; + int bSubItem = FALSE; + + char *pszEnd; + pszEnd = str; + + while(nPart < 2) + { + if(nPart == 0 && str) + pszEnd = str; + if(nPart == 1 && pszHeaderItems) + pszEnd = pszHeaderItems; + + while (*pszEnd) + { + if (*pszEnd == '\x01') + { + if(!bSubItem && nPart != 0) + ++nItemsTmp; + if(bSubItem && nPart == 0) + ++nItemsTemp; + } + else if (*pszEnd == '\x02') + bSubItem = TRUE; + else if (*pszEnd == '\x03') + { + bSubItem = FALSE; + + if(nPart == 0) + { + ++nItemsTemp; + + if(nItemsTemp > nItemsTmp) + nItemsTmp = nItemsTemp; + nItemsTemp = 0; + } + } + + pszEnd = CharNext(pszEnd); + } + + if(nItemsTmp > nItems) + nItems = nItemsTmp; + + nItemsTmp = 0; + + nPart++; + } + + return nItems; +} diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/MonthCalendar.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/MonthCalendar.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/ProgressBar.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/ProgressBar.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/RadioButton.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/RadioButton.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/RichText.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/RichText.h new file mode 100644 index 0000000..10540ae --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/Controls/RichText.h @@ -0,0 +1,28 @@ +#define _RICHEDIT_VER 0x0200 +#include +#undef _RICHEDIT_VER + +int nRichTextVersion; +static DWORD dwRead; + +DWORD CALLBACK FIELD_RICHTEXT_StreamIn(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) +{ + strncpy((char*)pbBuff,(char*)dwCookie+dwRead,cb); + *pcb=strlen((char*)pbBuff); + dwRead+=*pcb; + return 0; +} + +DWORD CALLBACK FIELD_RICHTEXT_StreamOut(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) +{ + if(dwRead+1 > (UINT)g_nBufferSize) + return 1; + + if(dwRead+cb+1 <= (UINT)g_nBufferSize) + strcpy((char*)dwCookie+dwRead,(char*)pbBuff); + else + strncpy((char*)dwCookie+dwRead,(char*)pbBuff, (UINT)g_nBufferSize - dwRead+1); + *pcb=strlen((char*)dwCookie); + dwRead+=*pcb; + return 0; +} diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/StatusBar.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/StatusBar.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/Text.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/Text.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/ToolBar.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/ToolBar.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/TrackBar.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/TrackBar.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/TreeView.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/TreeView.h new file mode 100644 index 0000000..29e4021 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/Controls/TreeView.h @@ -0,0 +1,380 @@ +//================================================================ +// TreeView Special Flags +//================================================================ + +// TREEVIEW_UNCHECKED = Unselected *Items* +// TREEVIEW_CHECKED = Selected *Items* +// TREEVIEW_READONLY = Read Only *Items* +// TREEVIEW_NOCHECKBOX = No CheckBox *All Items* +// TREEVIEW_BOLD = Bold *All* +// TREEVIEW_EXPANDED = Expand *Parent Items* +// TREEVIEW_PART_UNCHECK = Swap between 3rd state to 1st *Parent Items* (Reserved) +// TREEVIEW_PART_CHECK = Swap between 3rd state to 2st *Parent Items* (Reserved) +// TREEVIEW_PART_FIXED = Stay on 3rd state *Parent Items* (Reserved) +// TREEVIEW_AUTOCHECK = Automatic Check (only for function FIELD_TREEVIEW_Check) + +#define TREEVIEW_UNCHECKED 0x00000000 +#define TREEVIEW_CHECKED 0x00000001 +#define TREEVIEW_READONLY 0x00000002 +#define TREEVIEW_NOCHECKBOX 0x00000004 +#define TREEVIEW_BOLD 0x00000008 +#define TREEVIEW_EXPANDED 0x00000010 +#define TREEVIEW_PART_UNCHECK 0x00000020 +#define TREEVIEW_PART_CHECK 0x00000040 +#define TREEVIEW_PART_FIXED 0x00000080 +#define TREEVIEW_AUTOCHECK 0xFFFFFFFF + +void WINAPI FIELD_TREEVIEW_Check(HWND hCtrl, HTREEITEM hItem, int nCheck, int nParam, bool bCalledFromParent) +{ + +// Step 1: Check/uncheck itself. +//================================================================ + + // Get Item Information + //-------------------------------------------------------------- + TVITEM tvItem; + tvItem.mask = TVIF_STATE | TVIF_PARAM | TVIF_CHILDREN; + tvItem.stateMask = TVIS_STATEIMAGEMASK; + tvItem.hItem = hItem; + TreeView_GetItem(hCtrl, &tvItem); + + // "3rd state" checkboxes + //-------------------------------------------------------------- + if(tvItem.lParam & TREEVIEW_PART_FIXED) + { + if((tvItem.state >> 12) >= 3 && !(tvItem.lParam & TREEVIEW_CHECKED)) // If item state is leaving the 3rd state + { + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_CHECKED; + tvItem.lParam |= TREEVIEW_CHECKED; + } + else + { + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_UNCHECKED; + tvItem.lParam &= ~TREEVIEW_CHECKED; + } + tvItem.state = INDEXTOSTATEIMAGEMASK(3 - 1); // SPECIAL: Stays there + } + // "3rd state <-> checked" checkboxes + //-------------------------------------------------------------- + else if(tvItem.lParam & TREEVIEW_PART_CHECK) + { + if((tvItem.state >> 12) >= 3) // If item state is leaving the 3rd state + { + tvItem.state = INDEXTOSTATEIMAGEMASK((tvItem.state >> 12) - 2); // SPECIAL: Goes to checked state + tvItem.lParam |= TREEVIEW_CHECKED; + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_CHECKED; + } + else + { + tvItem.lParam &= ~TREEVIEW_CHECKED; + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_UNCHECKED; + } + } + // "3rd state <-> unchecked" checkboxes + //-------------------------------------------------------------- + else if (tvItem.lParam & TREEVIEW_PART_UNCHECK) + { + if((tvItem.state >> 12) == 1) // If item state is leaving the unchecked state + { + tvItem.state = INDEXTOSTATEIMAGEMASK((tvItem.state >> 12) + 1); // SPECIAL: Goes to 3rd state + tvItem.lParam |= TREEVIEW_CHECKED; + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_CHECKED; + } + else if ((tvItem.state >> 12) >= 3) // If item state is leaving the 3rd state + { + tvItem.state = INDEXTOSTATEIMAGEMASK((tvItem.state >> 12) - 3); // SPECIAL: Goes to unchecked state + tvItem.lParam &= ~TREEVIEW_CHECKED; + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_UNCHECKED; + } + } + // "Read only" checkboxes + //-------------------------------------------------------------- + else if (tvItem.lParam & TREEVIEW_READONLY) + { + if(!bCalledFromParent) tvItem.state = INDEXTOSTATEIMAGEMASK((tvItem.state >> 12) - 1); // SPECIAL: Stays the same + } + // "Normal" checkboxes (Leaving from Checked state) + //-------------------------------------------------------------- + else if (nParam == 0xFFFFFFFF) + { + if ((tvItem.state >> 12) >= 3) + { + tvItem.state = INDEXTOSTATEIMAGEMASK((tvItem.state >> 12) - 2); // SPECIAL: Goes to checked state + tvItem.lParam |= TREEVIEW_CHECKED; + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_CHECKED; + } + else + if ((tvItem.state >> 12) == 2) + { + tvItem.state = INDEXTOSTATEIMAGEMASK((tvItem.state >> 12) - 2); // SPECIAL: Goes to unchecked state + tvItem.lParam &= ~TREEVIEW_CHECKED; + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_UNCHECKED; + } + else + { + tvItem.lParam |= TREEVIEW_CHECKED; + if(nCheck == TREEVIEW_AUTOCHECK) nCheck = TREEVIEW_CHECKED; + goto Step2; + } + } + // Checkbox creation (used when creating the control) + //-------------------------------------------------------------- + else + { + // No checkboxes (TREEVIEW_NOCHECKBOX) + //------------------------------------ + if(nParam & TREEVIEW_NOCHECKBOX) + tvItem.state = INDEXTOSTATEIMAGEMASK(0); + + // Read-only checkboxes (TREEVIEW_READONLY) + //----------------------------------------- + else + if(nParam & TREEVIEW_READONLY) + { + if(nParam & TREEVIEW_CHECKED) + // Read-only checked checkboxes (TREEVIEW_READONLY | TREEVIEW_CHECKED) + tvItem.state = INDEXTOSTATEIMAGEMASK(5); + else + // Read-only unchecked checkboxes (TREEVIEW_READONLY) + tvItem.state = INDEXTOSTATEIMAGEMASK(4); + } + else + // Checked checkboxes (TREEVIEW_CHECKED) + //----------------------------------------- + if(nParam & TREEVIEW_CHECKED) + tvItem.state = INDEXTOSTATEIMAGEMASK(2); + + // Bold items (TREEVIEW_BOLD) + //--------------------------- + if(nParam & TREEVIEW_BOLD) + { + tvItem.state |= TVIS_BOLD; + tvItem.stateMask |= TVIS_BOLD; + } + // Expanded items (TREEVIEW_EXPANDED) + //----------------------------------- + if(nParam & TREEVIEW_EXPANDED) + { + tvItem.state |= TVIS_EXPANDED; + tvItem.stateMask |= TVIS_EXPANDED; + } + + tvItem.lParam = nParam; + nCheck = nParam; + } + + TreeView_SetItem(hCtrl, &tvItem); + +Step2: + +// Step 2: Detect and check/uncheck all children items of "hItem". +//================================================================ + if(nParam == 0xFFFFFFFF) + { + TVITEM tvChildItem; + HTREEITEM hChildItem = TreeView_GetChild(hCtrl, hItem); + while(hChildItem) + { + tvChildItem.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_PARAM; + tvChildItem.stateMask = TVIS_STATEIMAGEMASK; + tvChildItem.hItem = hChildItem; + TreeView_GetItem(hCtrl, &tvChildItem); + + tvChildItem.mask = TVIF_STATE; + tvChildItem.stateMask = TVIS_STATEIMAGEMASK; + + if(tvChildItem.cChildren == 1) + { + FIELD_TREEVIEW_Check(hCtrl, hChildItem, nCheck, 0xFFFFFFFF, TRUE); + } + + if(!(tvChildItem.lParam & TREEVIEW_NOCHECKBOX)) + { + if(tvChildItem.lParam & TREEVIEW_PART_FIXED) + { + tvChildItem.state = INDEXTOSTATEIMAGEMASK(3); + TreeView_SetItem(hCtrl, &tvChildItem); + } + else if(tvChildItem.lParam & TREEVIEW_PART_CHECK) + { + if(nCheck == TREEVIEW_CHECKED) + { + tvChildItem.state = INDEXTOSTATEIMAGEMASK(2); + tvChildItem.lParam |= TREEVIEW_CHECKED; + } + else + { + tvChildItem.state = INDEXTOSTATEIMAGEMASK(3); + tvChildItem.lParam &= ~TREEVIEW_CHECKED; + } + TreeView_SetItem(hCtrl, &tvChildItem); + } + else if(tvChildItem.lParam & TREEVIEW_PART_UNCHECK) + { + if(nCheck == TREEVIEW_CHECKED) + { + tvChildItem.state = INDEXTOSTATEIMAGEMASK(3); + tvChildItem.lParam |= TREEVIEW_CHECKED; + } + else + { + tvChildItem.state = INDEXTOSTATEIMAGEMASK(1); + tvChildItem.lParam &= ~TREEVIEW_CHECKED; + } + TreeView_SetItem(hCtrl, &tvChildItem); + } + else if(!(tvChildItem.lParam & TREEVIEW_READONLY)) + { + if(nCheck == TREEVIEW_CHECKED) + { + tvChildItem.state = INDEXTOSTATEIMAGEMASK(2); + tvChildItem.lParam |= TREEVIEW_CHECKED; + } + else + { + tvChildItem.state = INDEXTOSTATEIMAGEMASK(1); + tvChildItem.lParam &= ~TREEVIEW_CHECKED; + } + TreeView_SetItem(hCtrl, &tvChildItem); + } + } + + hChildItem = TreeView_GetNextSibling(hCtrl, hChildItem); + } + } + +// Step 3: Detect and check/uncheck parent items. +//================================================================ + if(!bCalledFromParent) + { + HTREEITEM hParentItem = TreeView_GetParent(hCtrl, hItem); + HTREEITEM hBackupParentItem = hParentItem; + while(hParentItem) + { + TVITEM tvParentItem; + tvParentItem.mask = TVIF_STATE | TVIF_PARAM; + tvParentItem.stateMask = TVIS_STATEIMAGEMASK; + tvParentItem.hItem = hParentItem; + TreeView_GetItem(hCtrl, &tvParentItem); + + if(tvParentItem.lParam & TREEVIEW_NOCHECKBOX) + { + hParentItem = TreeView_GetParent(hCtrl, hParentItem); + continue; + } + else + { + HTREEITEM hSiblingItem = TreeView_GetChild(hCtrl, hBackupParentItem); + bool bFirstSibling = TRUE; + + while(hSiblingItem != NULL) + { + TVITEM tvSiblingItem; + tvSiblingItem.hItem = hSiblingItem; + tvSiblingItem.mask = TVIF_STATE | TVIF_PARAM; + tvSiblingItem.stateMask = TVIS_STATEIMAGEMASK; + TreeView_GetItem(hCtrl, &tvSiblingItem); + + if(!(tvSiblingItem.lParam & TREEVIEW_NOCHECKBOX)) + { + if(nParam == 0xFFFFFFFF && hItem == hSiblingItem) + tvSiblingItem.state = INDEXTOSTATEIMAGEMASK((tvSiblingItem.state >> 12) + 1); + + if(bFirstSibling == TRUE) + { + tvParentItem.state = tvSiblingItem.state; + tvParentItem.lParam = tvSiblingItem.lParam; + bFirstSibling = FALSE; + } + else + { + if (((tvParentItem.lParam & TREEVIEW_PART_UNCHECK) && (tvSiblingItem.lParam & TREEVIEW_PART_CHECK)) || + ((tvParentItem.lParam & TREEVIEW_PART_CHECK) && (tvSiblingItem.lParam & TREEVIEW_PART_UNCHECK))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + tvParentItem.lParam &= ~TREEVIEW_PART_UNCHECK; + tvParentItem.lParam &= ~TREEVIEW_PART_CHECK; + tvParentItem.lParam |= TREEVIEW_PART_FIXED; + } + else + if (tvSiblingItem.lParam & TREEVIEW_PART_UNCHECK) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + tvParentItem.lParam |= TREEVIEW_PART_UNCHECK; + } + else + if (tvSiblingItem.lParam & TREEVIEW_PART_CHECK) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + tvParentItem.lParam |= TREEVIEW_PART_CHECK; + } + else + if (tvSiblingItem.lParam & TREEVIEW_PART_FIXED) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + tvParentItem.lParam |= TREEVIEW_PART_FIXED; + } + else + if (((tvParentItem.state >> 12) == 3) && ((tvSiblingItem.state >> 12) == 1)) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + } + else + if((((tvParentItem.state >> 12) == 3) && ((tvSiblingItem.state >> 12) == 2)) || + (((tvParentItem.state >> 12) == 2) && ((tvSiblingItem.state >> 12) == 3))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + } + else + if((((tvParentItem.state >> 12) == 1) && ((tvSiblingItem.state >> 12) == 2)) || + (((tvParentItem.state >> 12) == 2) && ((tvSiblingItem.state >> 12) == 1))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + } + else + if((((tvParentItem.state >> 12) == 4) && ((tvSiblingItem.state >> 12) == 1)) || + (((tvParentItem.state >> 12) == 1) && ((tvSiblingItem.state >> 12) == 4))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(1); + tvParentItem.lParam = TREEVIEW_PART_UNCHECK; + } + else + if((((tvParentItem.state >> 12) == 4) && ((tvSiblingItem.state >> 12) == 2)) || + (((tvParentItem.state >> 12) == 2) && ((tvSiblingItem.state >> 12) == 4))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + tvParentItem.lParam = TREEVIEW_PART_UNCHECK; + } + else + if((((tvParentItem.state >> 12) == 5) && ((tvSiblingItem.state >> 12) == 4)) || + (((tvParentItem.state >> 12) == 4) && ((tvSiblingItem.state >> 12) == 5))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + } + else + if((((tvParentItem.state >> 12) == 5) && ((tvSiblingItem.state >> 12) == 1)) || + (((tvParentItem.state >> 12) == 1) && ((tvSiblingItem.state >> 12) == 5))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(3); + tvParentItem.lParam = TREEVIEW_PART_CHECK; + } + else + if((((tvParentItem.state >> 12) == 5) && ((tvSiblingItem.state >> 12) == 2)) || + (((tvParentItem.state >> 12) == 2) && ((tvSiblingItem.state >> 12) == 5))) + { + tvParentItem.state = INDEXTOSTATEIMAGEMASK(2); + tvParentItem.lParam = TREEVIEW_PART_CHECK; + } + } + } + + TreeView_SetItem(hCtrl, &tvParentItem); + hSiblingItem = TreeView_GetNextSibling(hCtrl, hSiblingItem); + } + } + hBackupParentItem = hParentItem = TreeView_GetParent(hCtrl, hParentItem); + } + } + + return; +} diff --git a/installer/NSIS/Contrib/InstallOptionsEx/Controls/UpDown.h b/installer/NSIS/Contrib/InstallOptionsEx/Controls/UpDown.h new file mode 100644 index 0000000..e69de29 diff --git a/installer/NSIS/Contrib/InstallOptionsEx/InstallerOptions.cpp b/installer/NSIS/Contrib/InstallOptionsEx/InstallerOptions.cpp new file mode 100644 index 0000000..1bb7148 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/InstallerOptions.cpp @@ -0,0 +1,5551 @@ +/* +o----------------------------------------------------------------o +|InstallOptionsEx 2.4.5 beta 2 | +|Based under InstallOptions 2.47 (CVS version 1.120) | +(----------------------------------------------------------------) +| Main source code. / A plug-in for NSIS 2 | +| ----------------------------| +| By deguix (see copyright notes on readme) | +| And by SuperPat since 2.4.5 beta 1 | +o----------------------------------------------------------------o +*/ + +#include "InstallerOptions.h" + +//================================================================ +// DLLMain +//================================================================ +extern "C" BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + m_hInstance=(HINSTANCE) hInst; + return TRUE; +} + +//================================================================ +// External Functions +//================================================================ + +// TODO: Add another call for changing item properties. +// TODO: Add another call for adding external controls. + +// TODO: Expand functions to only show a specific main window. + +// show Function +//================================================================ +extern "C" void __declspec(dllexport) show(HWND hwndParent, int string_size, + char *variables, stack_t **stacktop) +{ + EXDLL_INIT(); + if (!initCalled) { + pushstring("error"); + return; + } + initCalled--; + showCfgDlg(); +} + +// dialog Function +//================================================================ +extern "C" void __declspec(dllexport) dialog(HWND hwndParent, int string_size, + char *variables, stack_t **stacktop) +{ + hMainWindow=hwndParent; + EXDLL_INIT(); + if (initCalled) { + pushstring("error"); + return; + } + if (createCfgDlg()) + return; + popstring(NULL); + showCfgDlg(); +} + +// initDialog Function +//================================================================ +extern "C" void __declspec(dllexport) initDialog(HWND hwndParent, int string_size, + char *variables, stack_t **stacktop) +{ + hMainWindow=hwndParent; + EXDLL_INIT(); + if (initCalled) { + pushstring("error"); + return; + } + if (createCfgDlg()) + return; + initCalled++; +} + + + +extern "C" void __declspec(dllexport) setFocus(HWND hwndParent, int string_size, + char *variables, stack_t **stacktop) +{ + EXDLL_INIT(); + + // Get the HWND of the control + char szHwCtrl[10]; + popstring(szHwCtrl); + + // Convert the string into an HWND + HWND hwnd = 0; +#ifdef USE_SECURE_FUNCTIONS + sscanf_s(szHwCtrl,"%d",&hwnd); +#else + sscanf(szHwCtrl,"%d",&hwnd); +#endif + + // Change Focus + mySetFocus(hwnd); +} + + + + +//================================================================ +// Post-Call Function Implementations +//================================================================ + + + + + +//---------------------------------------------------------------- +// Pre function part +//================================================================ + + +int WINAPI createCfgDlg() +{ + + // TODO: Make NSIS controls be handled by the plug-in as an option + +// Initialize Variables +//================================================================ + g_is_back=0; + g_is_cancel=0; + g_is_timeout=0; + +// Detect If A Main Window Exists +//================================================================ + HWND mainwnd = hMainWindow; + if (!mainwnd) + { + popstring(NULL); + pushstring("error finding mainwnd"); + return 1; // cannot be used in silent mode unfortunately. + } + +// Detect If Settings Were Loaded +//================================================================ + if (!g_stacktop || !*g_stacktop || !(pszFilename = (*g_stacktop)->text) || !pszFilename[0] || !ReadSettings()) + { + popstring(NULL); + pushstring("error finding config"); + return 1; + } + +// Detect If Child Window Exists +//================================================================ + HWND childwnd=GetDlgItem(mainwnd,nRectId); + if (!childwnd) + { + popstring(NULL); + pushstring("error finding childwnd"); + return 1; + } + +// NSIS Main Buttons Configurations +//================================================================ + hCancelButton = GetDlgItem(mainwnd,IDCANCEL); + hNextButton = GetDlgItem(mainwnd,IDOK); + hBackButton = GetDlgItem(mainwnd,3); + + mySetWindowText(hCancelButton,pszCancelButtonText); + mySetWindowText(hNextButton,pszNextButtonText); + mySetWindowText(hBackButton,pszBackButtonText); + + if(bNextShow!=-1) + old_next_visible=ShowWindow(hNextButton,bNextShow?SW_SHOWNA:SW_HIDE); + else + { + old_next_visible=ShowWindow(hNextButton,SW_SHOWNA); + ShowWindow(hNextButton,old_next_visible?SW_SHOWNA:SW_HIDE); + } + + if(bBackShow!=-1) + old_back_visible=ShowWindow(hBackButton,bBackShow?SW_SHOWNA:SW_HIDE); + else + { + old_back_visible=ShowWindow(hBackButton,SW_SHOWNA); + ShowWindow(hBackButton,old_back_visible?SW_SHOWNA:SW_HIDE); + } + + if(bCancelShow==-1) + old_cancel_visible=ShowWindow(hCancelButton,bCancelShow?SW_SHOWNA:SW_HIDE); + else + { + old_cancel_visible=ShowWindow(hCancelButton,SW_SHOWNA); + ShowWindow(hCancelButton,old_cancel_visible?SW_SHOWNA:SW_HIDE); + } + + old_next_enabled = IsWindowEnabled(hNextButton); + old_back_enabled = IsWindowEnabled(hBackButton); + old_cancel_enabled = IsWindowEnabled(hCancelButton); + + if (bNextEnabled!=-1) + EnableWindow(hNextButton,bNextEnabled); + else + EnableWindow(hNextButton,old_next_enabled); + + if (bNextShow!=-1) + ShowWindow(hNextButton,bNextShow?SW_SHOWNA:SW_HIDE); + else + ShowWindow(hNextButton,old_next_visible?SW_SHOWNA:SW_HIDE); + + + if (bBackEnabled!=-1) + EnableWindow(hBackButton,bBackEnabled); + else + EnableWindow(hBackButton,old_back_enabled); + + if (bBackShow!=-1) + ShowWindow(hBackButton,bBackShow?SW_SHOWNA:SW_HIDE); + else + ShowWindow(hBackButton,old_back_visible?SW_SHOWNA:SW_HIDE); + + + if (bCancelEnabled!=-1) + { + EnableWindow(hCancelButton,bCancelEnabled); + if (bCancelEnabled) + EnableMenuItem(GetSystemMenu(mainwnd, FALSE), SC_CLOSE, MF_BYCOMMAND | MF_ENABLED); + else + EnableMenuItem(GetSystemMenu(mainwnd, FALSE), SC_CLOSE, MF_BYCOMMAND | MF_GRAYED); + } + else + EnableWindow(hCancelButton,old_cancel_enabled); + + if (bCancelShow!=-1) + ShowWindow(hCancelButton,bCancelShow?SW_SHOWNA:SW_HIDE); + else + ShowWindow(hCancelButton,old_cancel_visible?SW_SHOWNA:SW_HIDE); + +// Create Window +//================================================================ + HFONT hFont = (HFONT)mySendMessage(mainwnd, WM_GETFONT, 0, 0); + + // Prevent WM_COMMANDs from being processed while we are building + g_done = 1; + + // TODO: Use RegisterClassEx to create true GUI windows (not dialogs - CreateDialog - like now) + // http://www.functionx.com/win32/Lesson05.htm + + // TODO: Make loops for each main window/dialog and each control to create. + + int mainWndWidth, mainWndHeight; + hConfigWindow=CreateDialog(m_hInstance,MAKEINTRESOURCE(IDD_DIALOG1),mainwnd,DialogWindowProc); + if (!hConfigWindow) + { + popstring(NULL); + pushstring("error creating dialog"); + return 1; + } + + RECT dialog_r; + GetWindowRect(childwnd,&dialog_r); + MapWindowPoints(0, mainwnd, (LPPOINT) &dialog_r, 2); + mainWndWidth = dialog_r.right - dialog_r.left; + mainWndHeight = dialog_r.bottom - dialog_r.top; + SetWindowPos( + hConfigWindow, + 0, + dialog_r.left, + dialog_r.top, + mainWndWidth, + mainWndHeight, + SWP_NOZORDER|SWP_NOACTIVATE + ); + // Sets the font of IO window to be the same as the main window + mySendMessage(hConfigWindow, WM_SETFONT, (WPARAM)hFont, TRUE); + + BOOL fFocused = FALSE; + BOOL fFocusedByFlag = FALSE; + +// Identify Styles For Each Control Type +//================================================================ + + for (int nIdx = 0; nIdx < nNumFields; nIdx++) { + IOExControlStorage *pField = pFields + nIdx; + + static struct { + char* pszClass; + DWORD dwStyle; + DWORD dwRTLStyle; + DWORD dwExStyle; + DWORD dwRTLExStyle; + } ClassTable[] = { + { "STATIC", // FIELD_HLINE + DEFAULT_STYLES | SS_ETCHEDHORZ | SS_SUNKEN, + DEFAULT_STYLES | SS_ETCHEDHORZ | SS_SUNKEN, + WS_EX_TRANSPARENT, + WS_EX_TRANSPARENT | RTL_EX_STYLES }, + { "STATIC", // FIELD_VLINE + DEFAULT_STYLES | SS_ETCHEDVERT | SS_SUNKEN, + DEFAULT_STYLES | SS_ETCHEDVERT | SS_SUNKEN, + WS_EX_TRANSPARENT, + WS_EX_TRANSPARENT | RTL_EX_STYLES }, + { "STATIC", // FIELD_LABEL + DEFAULT_STYLES | SS_OWNERDRAW | SS_NOTIFY, + DEFAULT_STYLES | SS_OWNERDRAW | SS_NOTIFY, + WS_EX_TRANSPARENT, + WS_EX_TRANSPARENT | RTL_EX_STYLES }, + { "BUTTON", // FIELD_GROUPBOX + DEFAULT_STYLES | BS_GROUPBOX, + DEFAULT_STYLES | BS_GROUPBOX | BS_RIGHT, + WS_EX_TRANSPARENT, + WS_EX_TRANSPARENT | RTL_EX_STYLES }, + { "IMAGE", // FIELD_IMAGE // Representation for both "Static" and "Animation" controls. + DEFAULT_STYLES, + DEFAULT_STYLES, + 0, + RTL_EX_STYLES }, + { PROGRESS_CLASS, // FIELD_PROGRESSBAR + DEFAULT_STYLES, + DEFAULT_STYLES, + 0, + RTL_EX_STYLES }, + { "BUTTON", // FIELD_LINK + DEFAULT_STYLES | WS_TABSTOP | BS_OWNERDRAW | BS_NOTIFY, + DEFAULT_STYLES | WS_TABSTOP | BS_OWNERDRAW | BS_RIGHT | BS_NOTIFY, + 0, + RTL_EX_STYLES }, + { "BUTTON", // FIELD_BUTTON + DEFAULT_STYLES | WS_TABSTOP | BS_MULTILINE | BS_NOTIFY, + DEFAULT_STYLES | WS_TABSTOP | BS_MULTILINE | BS_NOTIFY, + 0, + RTL_EX_STYLES }, + { UPDOWN_CLASS, // FIELD_UPDOWN + DEFAULT_STYLES | WS_TABSTOP | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_SETBUDDYINT | UDS_ALIGNRIGHT, + DEFAULT_STYLES | WS_TABSTOP | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_SETBUDDYINT | UDS_ALIGNLEFT, + 0, + RTL_EX_STYLES }, + { "BUTTON", // FIELD_CHECKBOX + DEFAULT_STYLES | WS_TABSTOP | BS_TEXT | BS_VCENTER | BS_AUTOCHECKBOX | BS_MULTILINE | BS_NOTIFY, + DEFAULT_STYLES | WS_TABSTOP | BS_TEXT | BS_VCENTER | BS_AUTOCHECKBOX | BS_MULTILINE | BS_RIGHT | BS_LEFTTEXT | BS_NOTIFY, + 0, + RTL_EX_STYLES }, + { "BUTTON", // FIELD_RADIOBUTTON + DEFAULT_STYLES | WS_TABSTOP | BS_TEXT | BS_VCENTER | BS_AUTORADIOBUTTON | BS_MULTILINE | BS_NOTIFY, + DEFAULT_STYLES | WS_TABSTOP | BS_TEXT | BS_VCENTER | BS_AUTORADIOBUTTON | BS_MULTILINE | BS_RIGHT | BS_LEFTTEXT | BS_NOTIFY, + 0, + RTL_EX_STYLES }, + { "EDIT", // FIELD_TEXT + DEFAULT_STYLES | WS_TABSTOP | ES_AUTOHSCROLL, + DEFAULT_STYLES | WS_TABSTOP | ES_AUTOHSCROLL | ES_RIGHT, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | RTL_EX_STYLES | WS_EX_LEFTSCROLLBAR }, + { WC_IPADDRESS, // FIELD_IPADDRESS + WS_CHILD | WS_TABSTOP | WS_VISIBLE, + WS_CHILD | WS_TABSTOP | WS_VISIBLE, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | RTL_EX_STYLES }, + { RICHEDIT_CLASS, // FIELD_RICHTEXT // Representation for the actual class name (depends on version) + DEFAULT_STYLES | WS_TABSTOP | ES_AUTOHSCROLL, + DEFAULT_STYLES | WS_TABSTOP | ES_AUTOHSCROLL | ES_RIGHT, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | RTL_EX_STYLES | WS_EX_LEFTSCROLLBAR }, + { "COMBOBOX", // FIELD_COMBOBOX + DEFAULT_STYLES | WS_TABSTOP | WS_VSCROLL | WS_CLIPCHILDREN | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | CBS_AUTOHSCROLL, + DEFAULT_STYLES | WS_TABSTOP | WS_VSCROLL | WS_CLIPCHILDREN | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | CBS_AUTOHSCROLL, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_RIGHT | RTL_EX_STYLES | WS_EX_LEFTSCROLLBAR }, + { DATETIMEPICK_CLASS, // FIELD_DATETIME + DEFAULT_STYLES | WS_TABSTOP, + DEFAULT_STYLES | WS_TABSTOP | DTS_RIGHTALIGN, + 0, + RTL_EX_STYLES | WS_EX_LEFTSCROLLBAR }, + { "LISTBOX", // FIELD_LISTBOX + DEFAULT_STYLES | WS_TABSTOP | WS_VSCROLL | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | LBS_NOTIFY, + DEFAULT_STYLES | WS_TABSTOP | WS_VSCROLL | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | LBS_NOTIFY, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE, + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_RIGHT | RTL_EX_STYLES | WS_EX_LEFTSCROLLBAR }, + { WC_LISTVIEW, // FIELD_LISTVIEW + DEFAULT_STYLES | WS_TABSTOP | LVS_SHOWSELALWAYS | LVS_SINGLESEL, + DEFAULT_STYLES | WS_TABSTOP | LVS_SHOWSELALWAYS | LVS_SINGLESEL, + WS_EX_CLIENTEDGE, + WS_EX_CLIENTEDGE | RTL_EX_STYLES | WS_EX_LEFTSCROLLBAR }, + { WC_TREEVIEW, // FIELD_TREEVIEW + DEFAULT_STYLES | WS_TABSTOP | TVS_DISABLEDRAGDROP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS, + DEFAULT_STYLES | WS_TABSTOP | TVS_DISABLEDRAGDROP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS, + WS_EX_CLIENTEDGE, + WS_EX_CLIENTEDGE | RTL_EX_STYLES | WS_EX_LEFTSCROLLBAR }, + { TRACKBAR_CLASS, // FIELD_TRACKBAR + DEFAULT_STYLES | WS_TABSTOP | TBS_AUTOTICKS, + DEFAULT_STYLES | WS_TABSTOP | TBS_AUTOTICKS, + 0, + RTL_EX_STYLES }, + { MONTHCAL_CLASS, // FIELD_MONTHCALENDAR + DEFAULT_STYLES | WS_TABSTOP, + DEFAULT_STYLES | WS_TABSTOP, + 0, + RTL_EX_STYLES } + }; + + if (pField->nType < 1 || pField->nType > (int)(sizeof(ClassTable) / sizeof(ClassTable[0]))) + continue; + + DWORD dwStyle, dwExStyle; + if (bRTL) { + dwStyle = ClassTable[pField->nType - 1].dwRTLStyle; + dwExStyle = ClassTable[pField->nType - 1].dwRTLExStyle; + } + else { + dwStyle = ClassTable[pField->nType - 1].dwStyle; + dwExStyle = ClassTable[pField->nType - 1].dwExStyle; + } + + // Required because we want to change this for FIELD_IMAGE image types. + LPSTR pszClass = (LPSTR)MALLOC(64); + strcpy(pszClass, ClassTable[pField->nType - 1].pszClass); + +// Convert From User Defined Units +//================================================================ + + pField->RectPx = pField->RectUDU; + // MapDialogRect uses the font used when a dialog is created, and ignores + // any subsequent WM_SETFONT messages (like we used above); so use the main + // NSIS window for the conversion, instead of this one. + if(!bMUnit) + MapDialogRect(mainwnd, &pField->RectPx); + +// Implement support for negative coordinates +//================================================================ + + if (pField->RectUDU.left < 0) + pField->RectPx.left += mainWndWidth; + if (pField->RectUDU.right < 0) + pField->RectPx.right += mainWndWidth; + if (pField->RectUDU.top < 0) + pField->RectPx.top += mainWndHeight; + if (pField->RectUDU.bottom < 0) + pField->RectPx.bottom += mainWndHeight; + +// Implement support for RTL +//================================================================ + + if (bRTL) { + int right = pField->RectPx.right; + pField->RectPx.right = mainWndWidth - pField->RectPx.left; + pField->RectPx.left = mainWndWidth - right; + } + +// Initialize Controls Before Showing Them Up +//================================================================ + switch(pField->nType) + { + case FIELD_IMAGE: + // Integrated control for icons, cursors, bitmaps and videos w/o sound. + { + // Videos cannot be handled here. Only one message suffices for this, + // and this message has to have a control already created to work. + + int nHeight = pField->RectPx.bottom - pField->RectPx.top; + int nWidth = pField->RectPx.right - pField->RectPx.left; + + // Step 1: Load from file or from resource + //-------------------------------------------------------- + LPSTR pFileExtension = PathFindExtension(pField->pszText); + int nImageType; + + // Handle icons first from executables and dlls + if(PathFileExists(pField->pszText) && (stricmp(pFileExtension, ".exe") == 0 || stricmp(pFileExtension, ".dll") == 0)) + { + nImageType = IMAGE_ICON; + + if(myatoi(pField->pszState) < 0) + pField->pszState = "0"; + + HICON hIcon = NULL; + + ExtractIconEx( + pField->pszText, + (UINT) myatoi(pField->pszState), + nWidth >= 32 && nHeight >= 32 ? &hIcon : NULL, + (nWidth >= 16 && nWidth < 32) && (nHeight >= 16 && nHeight < 32) ? &hIcon : NULL, 1); + pField->hImage = (HBITMAP)hIcon; + + if(pField->nFlags & FLAG_RESIZETOFIT) + pField->hImage = (HBITMAP)CopyImage(pField->hImage, nImageType, nWidth, nHeight, 0); + } + else if(PathFileExists(pField->pszText) && (stricmp(pFileExtension, ".ico") == 0 || stricmp(pFileExtension, ".cur") == 0 || stricmp(pFileExtension, ".ani") == 0 || stricmp(pFileExtension, ".bmp") == 0)) + { + if(stricmp(pFileExtension, ".ico") == 0) + nImageType = IMAGE_ICON; + else if(stricmp(pFileExtension, ".cur") == 0 || stricmp(pFileExtension, ".ani") == 0) + nImageType = IMAGE_CURSOR; + else if(stricmp(pFileExtension, ".bmp") == 0) + nImageType = IMAGE_BITMAP; + + pField->hImage = (HBITMAP)LoadImage(m_hInstance, pField->pszText, nImageType, (pField->nFlags & FLAG_RESIZETOFIT) ? nWidth : 0, (pField->nFlags & FLAG_RESIZETOFIT) ? nHeight : 0, LR_LOADFROMFILE); + } + else if(PathFileExists(pField->pszText) && stricmp(pFileExtension, ".avi") == 0) + pField->nDataType = IMAGE_TYPE_ANIMATION; + else if(PathFileExists(pField->pszText) && ( + //Raster Images (OLE) + stricmp(pFileExtension, ".gif") == 0 || + stricmp(pFileExtension, ".jpg") == 0 || + stricmp(pFileExtension, ".jpeg") == 0)) + //Vector Images (OLE not supported yet) + //stricmp(pFileExtension, ".wmf") == 0 + { + if(stricmp(pFileExtension, ".gif") == 0 || + stricmp(pFileExtension, ".jpg") == 0 || + stricmp(pFileExtension, ".jpeg") == 0) + pField->nDataType = IMAGE_TYPE_OLE; + + //if(pField->nDataType == IMAGE_TYPE_OLE) + //{ + HANDLE hFile = CreateFile(pField->pszText, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + break; + + DWORD nFileSize = GetFileSize(hFile, 0); + HGLOBAL hFileGlobal = GlobalAlloc(GPTR, nFileSize); + + if (!hFileGlobal) + { + CloseHandle(hFile); + break; + } + + LPVOID lpFileLocked = GlobalLock(hFileGlobal); + if (!lpFileLocked) + { + CloseHandle(hFile); + GlobalFree(hFileGlobal); + break; + } + + ReadFile(hFile, lpFileLocked, nFileSize, &nFileSize, 0); + + GlobalUnlock(hFileGlobal); + CloseHandle(hFile); + + LPSTREAM lpStream; + if (CreateStreamOnHGlobal(hFileGlobal, FALSE, &lpStream) != S_OK || !lpStream) + { + GlobalFree(hFileGlobal); + break; + } + + if (OleLoadPicture(lpStream, 0, FALSE, IID_IPicture, (void **)&pField->nImageInterface) != S_OK) + pField->nImageInterface = NULL; + + lpStream->Release(); + GlobalFree(hFileGlobal); + + if (!pField->nImageInterface) + break; + + pField->nImageInterface->get_Handle((OLE_HANDLE *)&pField->hImage); + + if (pField->hImage) + pField->hImage = (HBITMAP)CopyImage(pField->hImage, IMAGE_BITMAP, (pField->nFlags & FLAG_RESIZETOFIT) ? nWidth : 0, (pField->nFlags & FLAG_RESIZETOFIT) ? nHeight : 0, LR_COPYRETURNORG); + + pField->nImageInterface->Release(); + //} + } + else + { + struct TrioTableEntry { + int nDataType; + char *pszName; + int nValue; + }; + + // Icon Flags + //------------------------------- + // These below are resource numbers. Needs to use MAKEINTRESOURCE later. + static TrioTableEntry IconTable[] = + { + // Icon Flags + { IMAGE_ICON, "APPLICATION", 32512 }, + { IMAGE_ICON, "EXCLAMATION", 32515 }, + { IMAGE_ICON, "INFORMATION", 32516 }, + { IMAGE_ICON, "QUESTION", 32514 }, + { IMAGE_ICON, "STOP", 32513 }, + { IMAGE_ICON, "WINLOGO", 32517 }, + + // Cursor Flags + { IMAGE_CURSOR, "APPSTARTING", 32650 }, + { IMAGE_CURSOR, "ARROW", 32512 }, + { IMAGE_CURSOR, "CROSS", 32515 }, + { IMAGE_CURSOR, "HAND", 32649 }, + { IMAGE_CURSOR, "HELP", 32651 }, + { IMAGE_CURSOR, "IBEAM", 32513 }, + { IMAGE_CURSOR, "NO", 32648 }, + { IMAGE_CURSOR, "SIZEALL", 32646 }, + { IMAGE_CURSOR, "SIZENESW", 32643 }, + { IMAGE_CURSOR, "SIZENS", 32645 }, + { IMAGE_CURSOR, "SIZENWSE", 32642 }, + { IMAGE_CURSOR, "SIZEWE", 32644 }, + { IMAGE_CURSOR, "UPARROW", 32516 }, + { IMAGE_CURSOR, "WAIT", 32514 }, + + // Bitmap Flags + { IMAGE_BITMAP, "BTNCORNERS", 32758 }, + { IMAGE_BITMAP, "BTSIZE", 32761 }, + { IMAGE_BITMAP, "CHECK", 32760 }, + { IMAGE_BITMAP, "CHECKBOXES", 32759 }, + { IMAGE_BITMAP, "CLOSE", 32754 }, + { IMAGE_BITMAP, "COMBO", 32738 }, + { IMAGE_BITMAP, "DNARROW", 32752 }, + { IMAGE_BITMAP, "DNARROWD", 32742 }, + { IMAGE_BITMAP, "DNARROWI", 32736 }, + { IMAGE_BITMAP, "LFARROW", 32750 }, + { IMAGE_BITMAP, "LFARROWD", 32740 }, + { IMAGE_BITMAP, "LFARROWI", 32734 }, + { IMAGE_BITMAP, "MNARROW", 32739 }, + { IMAGE_BITMAP, "REDUCE", 32749 }, + { IMAGE_BITMAP, "REDUCED", 32746 }, + { IMAGE_BITMAP, "RESTORE", 32747 }, + { IMAGE_BITMAP, "RESTORED", 32744 }, + { IMAGE_BITMAP, "RGARROW", 32751 }, + { IMAGE_BITMAP, "RGARROWD", 32741 }, + { IMAGE_BITMAP, "RGARROWI", 32735 }, + { IMAGE_BITMAP, "SIZE", 32766 }, + { IMAGE_BITMAP, "UPARROW", 32753 }, + { IMAGE_BITMAP, "UPARROWD", 32743 }, + { IMAGE_BITMAP, "UPARROWI", 32737 }, + { IMAGE_BITMAP, "ZOOM", 32748 }, + { IMAGE_BITMAP, "ZOOMD", 32745 }, + + { IMAGE_ICON, NULL, 0 } //NSIS application icon. + }; + + WORD nIcon = 103; + nImageType = IMAGE_ICON; + HINSTANCE hInstance = NULL; + + //LookupToken adapted to TrioTableEntry. + for (int i = 0; IconTable[i].pszName; i++) + if (!stricmp(pField->pszState, IconTable[i].pszName)) + { + nImageType = IconTable[i].nDataType; + nIcon = IconTable[i].nValue; + } + + pField->hImage = (HBITMAP)LoadImage((nIcon == 103) ? GetModuleHandle(0) : NULL, MAKEINTRESOURCE(nIcon), nImageType, 0, 0, LR_SHARED); + + if(pField->nFlags & FLAG_RESIZETOFIT) + pField->hImage = (HBITMAP)CopyImage(pField->hImage, nImageType, nWidth, nHeight, LR_COPYFROMRESOURCE); + } + + if(nImageType == IMAGE_BITMAP) + pField->nDataType = IMAGE_TYPE_BITMAP; + else if(nImageType == IMAGE_ICON) + pField->nDataType = IMAGE_TYPE_ICON; + else if(nImageType == IMAGE_CURSOR) + pField->nDataType = IMAGE_TYPE_CURSOR; + //IMAGE_TYPE_ANIMATION and IMAGE_TYPE_OLE were already set at this point. + + // Step 2: Transform into specific internal controls + //-------------------------------------------------------- + switch(pField->nDataType) + { + case IMAGE_TYPE_BITMAP: + case IMAGE_TYPE_OLE: + case IMAGE_TYPE_GDIPLUS: + { + pszClass = "STATIC"; + dwStyle |= SS_BITMAP | SS_NOTIFY; + break; + } + case IMAGE_TYPE_ICON: + case IMAGE_TYPE_CURSOR: + { + pszClass = "STATIC"; + dwStyle |= SS_ICON | SS_NOTIFY; + break; + } + case IMAGE_TYPE_ANIMATION: + { + pszClass = ANIMATE_CLASS; + dwStyle |= ACS_TIMER | ACS_CENTER; + break; + } + } + break; + } + case FIELD_RICHTEXT: + { + // Load the dll for the RichText in the memory + //-------------------------------------------------------- + if(!LoadLibrary("riched20.dll")) //Version 2 + { + LoadLibrary("riched32.dll"); //Version 1 + + pszClass = "RichEdit"; + } + } + } + +// Assign To Each Control Type Optional Flags +//================================================================ + char *title = pField->pszText; + + switch (pField->nType) { + case FIELD_IMAGE: + title = NULL; // otherwise it is treated as the name of a resource + if(pField->nDataType == IMAGE_TYPE_ANIMATION) + if (pField->nFlags & FLAG_TRANSPARENT) + dwStyle |= ACS_TRANSPARENT; + break; + case FIELD_LABEL: + if(!(pField->nNotify & NOTIFY_CONTROL_ONCLICK) && !(pField->nNotify & NOTIFY_CONTROL_ONDBLCLICK)) + dwStyle &= ~SS_NOTIFY; + + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + dwStyle &= ~SS_OWNERDRAW; + break; + case FIELD_CHECKBOX: + if (pField->nFlags & FLAG_READONLY) + { + dwStyle &= ~BS_AUTOCHECKBOX; + if (pField->nFlags & FLAG_3STATE) + dwStyle |= BS_3STATE; + else + dwStyle |= BS_CHECKBOX; + } + else + { + if (pField->nFlags & FLAG_3STATE) + { + dwStyle &= ~BS_AUTOCHECKBOX; + dwStyle |= BS_AUTO3STATE; + } + } + break; + case FIELD_RADIOBUTTON: + if (pField->nFlags & FLAG_READONLY) + { + dwStyle &= ~BS_AUTORADIOBUTTON; + dwStyle |= BS_RADIOBUTTON; + } + break; + case FIELD_TEXT: + title = pField->pszState; + case FIELD_RICHTEXT: + // Microsoft says that password style cannot be used with + // multiline style, but in use, these can be used together. + // (Thank you Microsoft.) + if (pField->nFlags & FLAG_PASSWORD) + dwStyle |= ES_PASSWORD; + if (pField->nFlags & FLAG_ONLYNUMBERS) + dwStyle |= ES_NUMBER; + if (pField->nFlags & FLAG_WANTRETURN) + dwStyle |= ES_WANTRETURN; + if (pField->nFlags & FLAG_READONLY) + dwStyle |= ES_READONLY; + if (pField->nFlags & FLAG_VSCROLL) + dwStyle |= WS_VSCROLL; + if (pField->nFlags & FLAG_MULTILINE) + { + dwStyle |= ES_MULTILINE | ES_AUTOVSCROLL; + + // Enable word-wrap unless we have a horizontal scroll bar + // or it has been explicitly disallowed + if (!(pField->nFlags & (FLAG_HSCROLL | FLAG_NOWORDWRAP))) + dwStyle &= ~ES_AUTOHSCROLL; + + atoIO(pField->pszState); + + // If multiline-readonly then hold the text back until after the + // initial focus has been set. This is so the text is not initially + // selected - useful for License Page look-a-likes. + if (pField->nFlags & FLAG_READONLY) + title = NULL; + } + break; + case FIELD_COMBOBOX: + dwStyle |= (pField->nFlags & FLAG_DROPLIST) ? CBS_DROPDOWNLIST : CBS_DROPDOWN; + if (pField->nFlags & FLAG_VSCROLL) + dwStyle |= CBS_DISABLENOSCROLL; + title = pField->pszState; + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + dwStyle &= ~CBS_OWNERDRAWFIXED; + break; + case FIELD_LISTBOX: + if (pField->nFlags & FLAG_MULTISELECT) + dwStyle |= LBS_MULTIPLESEL; + if (pField->nFlags & FLAG_EXTENDEDSELECT) + dwStyle |= LBS_EXTENDEDSEL; + if (pField->nFlags & FLAG_VSCROLL) + dwStyle |= LBS_DISABLENOSCROLL; + if (pField->pszText) + if(myatoi(pField->pszText) > 0) + dwStyle |= LBS_MULTICOLUMN; + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + dwStyle &= ~LBS_OWNERDRAWFIXED; + break; + case FIELD_TREEVIEW: + if (pField->nFlags & FLAG_CHECKBOXES && FileExists(pField->pszStateImageList)) + dwStyle |= TVS_CHECKBOXES; + else + pField->nFlags &= ~FLAG_CHECKBOXES; + if (pField->nFlags & FLAG_EDITLABELS) + dwStyle |= TVS_EDITLABELS; + break; + case FIELD_LISTVIEW: + if (pField->nFlags & FLAG_EDITLABELS) + dwStyle |= LVS_EDITLABELS; + if (pField->nFlags & FLAG_ICON_VIEW) + dwStyle |= LVS_ICON; + if (pField->nFlags & FLAG_LIST_VIEW) + dwStyle |= LVS_LIST; + if (pField->nFlags & FLAG_SMALLICON_VIEW) + dwStyle |= LVS_SMALLICON; + if (pField->nFlags & FLAG_REPORT_VIEW) + dwStyle |= LVS_REPORT; + if (pField->nFlags & FLAG_MULTISELECT) + dwStyle &= ~LVS_SINGLESEL; + break; + case FIELD_BUTTON: + if (pField->nFlags & FLAG_BITMAP) + dwStyle |= BS_BITMAP; + if (pField->nFlags & FLAG_ICON) + dwStyle |= BS_ICON; + break; + case FIELD_PROGRESSBAR: + if (pField->nFlags & FLAG_SMOOTH) + dwStyle |= PBS_SMOOTH; + if (pField->nFlags & FLAG_VSCROLL) + dwStyle |= PBS_VERTICAL; + break; + case FIELD_TRACKBAR: + if (pField->nFlags & FLAG_VSCROLL) + dwStyle |= TBS_VERT; + if (pField->nFlags & FLAG_NO_TICKS) + dwStyle |= TBS_NOTICKS; + break; + case FIELD_DATETIME: + if (pField->nFlags & FLAG_UPDOWN) + dwStyle |= DTS_TIMEFORMAT; + break; + case FIELD_MONTHCALENDAR: + if (pField->nFlags & FLAG_NOTODAY) + dwStyle |= MCS_NOTODAY | MCS_NOTODAYCIRCLE; + if (pField->nFlags & FLAG_WEEKNUMBERS) + dwStyle |= MCS_WEEKNUMBERS; + break; + case FIELD_UPDOWN: + if (pField->nFlags & FLAG_HSCROLL) + dwStyle |= UDS_HORZ; + if (pField->nFlags & FLAG_WRAP) + dwStyle |= UDS_WRAP; + break; + } + if (pField->nFlags & FLAG_GROUP) dwStyle |= WS_GROUP; + if (pField->nFlags & FLAG_HSCROLL && (pField->nType == FIELD_TEXT || pField->nType == FIELD_RICHTEXT)) dwStyle |= WS_HSCROLL; + if (pField->nFlags & FLAG_VSCROLL && (pField->nType == FIELD_TEXT || pField->nType == FIELD_LISTBOX || pField->nType == FIELD_COMBOBOX)) dwStyle |= WS_VSCROLL; + if (pField->nFlags & FLAG_DISABLED) dwStyle |= WS_DISABLED; + if (pField->nFlags & FLAG_NOTABSTOP) dwStyle &= ~WS_TABSTOP; + +// Assign To Each Control Type An Optional Align Flag +//================================================================ + + switch (pField->nType) { + case FIELD_UPDOWN: + if (pField->nAlign == ALIGN_LEFT) + { + dwStyle &= ~UDS_ALIGNRIGHT; + dwStyle |= UDS_ALIGNLEFT; + } + else + if (pField->nAlign == ALIGN_RIGHT) + { + dwStyle &= ~UDS_ALIGNLEFT; + dwStyle |= UDS_ALIGNRIGHT; + } + break; + case FIELD_CHECKBOX: + if (pField->nAlign == ALIGN_LEFT) + dwStyle &= ~BS_LEFTTEXT; + else + if (pField->nAlign == ALIGN_RIGHT) + dwStyle |= BS_LEFTTEXT; + break; + case FIELD_DATETIME: + if (pField->nAlign == ALIGN_LEFT) + dwStyle &= ~DTS_RIGHTALIGN; + else + if (pField->nAlign == ALIGN_RIGHT) + dwStyle |= DTS_RIGHTALIGN; + break; + case FIELD_TRACKBAR: + if (pField->nFlags & FLAG_VSCROLL) + { + if (pField->nAlign == ALIGN_LEFT) + dwStyle |= TBS_LEFT; + else + if (pField->nAlign == ALIGN_CENTER) + dwStyle |= TBS_BOTH; + else + if (pField->nAlign == ALIGN_RIGHT) + dwStyle |= TBS_RIGHT; + } + else + { + if (pField->nVAlign == VALIGN_TOP) + dwStyle |= TBS_TOP; + else + if (pField->nVAlign == VALIGN_CENTER) + dwStyle |= TBS_BOTH; + else + if (pField->nVAlign == VALIGN_BOTTOM) + dwStyle |= TBS_BOTTOM; + } + break; + } + + switch (pField->nType) { + case FIELD_LABEL: + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + { + if (pField->nTxtAlign == ALIGN_TEXT_LEFT) + dwStyle |= SS_LEFT; + else + if (pField->nTxtAlign == ALIGN_TEXT_CENTER) + dwStyle |= SS_CENTER; + else + if (pField->nTxtAlign == ALIGN_TEXT_RIGHT) + dwStyle |= SS_RIGHT; + } + case FIELD_BUTTON: + if (pField->nTxtVAlign == VALIGN_TEXT_TOP) + dwStyle |= BS_TOP; + else + if (pField->nTxtVAlign == VALIGN_TEXT_CENTER) + dwStyle |= BS_VCENTER; + else + if (pField->nTxtVAlign == VALIGN_TEXT_BOTTOM) + dwStyle |= BS_BOTTOM; + case FIELD_CHECKBOX: + case FIELD_RADIOBUTTON: + case FIELD_GROUPBOX: + if (pField->nTxtAlign == ALIGN_TEXT_LEFT) + { + dwStyle &= ~BS_CENTER; + dwStyle &= ~BS_RIGHT; + dwStyle |= BS_LEFT; + } + else + if (pField->nTxtAlign == ALIGN_TEXT_CENTER) + { + dwStyle &= ~BS_LEFT; + dwStyle &= ~BS_RIGHT; + dwStyle |= BS_CENTER; + } + else + if (pField->nTxtAlign == ALIGN_TEXT_RIGHT) + { + dwStyle &= ~BS_LEFT; + dwStyle &= ~BS_CENTER; + dwStyle |= BS_RIGHT; + } + break; + case FIELD_TEXT: + if (pField->nTxtAlign == ALIGN_LEFT) + { + dwStyle &= ~ES_CENTER; + dwStyle &= ~ES_RIGHT; + dwStyle |= ES_LEFT; + } + else + if (pField->nTxtAlign == ALIGN_CENTER) + { + dwStyle &= ~ES_LEFT; + dwStyle &= ~ES_RIGHT; + dwStyle |= ES_CENTER; + } + else + if (pField->nTxtAlign == ALIGN_RIGHT) + { + dwStyle &= ~ES_LEFT; + dwStyle &= ~ES_CENTER; + dwStyle |= ES_RIGHT; + } + break; + } + +// Create Control +//================================================================ + HWND hwCtrl = pField->hwnd = CreateWindowEx( + dwExStyle, + pszClass, + title, + dwStyle, + pField->RectPx.left, + pField->RectPx.top, + pField->RectPx.right - pField->RectPx.left, + pField->RectPx.bottom - pField->RectPx.top, + hConfigWindow, + (HMENU)pField->nControlID, + m_hInstance, + NULL + ); + + FREE(pszClass); + +// Create ToolTip +//================================================================ + if(pField->pszToolTipText) { + + if(!bRTL) + dwExStyle = WS_EX_TOPMOST; + else + dwExStyle = WS_EX_TOPMOST | RTL_EX_STYLES; + + pField->hwToolTip = CreateWindowEx( + dwExStyle, + TOOLTIPS_CLASS, + NULL, + pField->nToolTipFlags, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + pField->hwnd, + NULL, + m_hInstance, + NULL + ); + + SetWindowPos(pField->hwToolTip, + HWND_TOPMOST, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + TOOLINFO ti; + + ti.cbSize = sizeof(TOOLINFO); + if(!bRTL) + ti.uFlags = TTF_TRANSPARENT | TTF_SUBCLASS | TTF_IDISHWND; + else + ti.uFlags = TTF_TRANSPARENT | TTF_SUBCLASS | TTF_IDISHWND | TTF_RTLREADING; + ti.uId = (int)pField->hwnd; + ti.lpszText = pField->pszToolTipText; + + mySendMessage(pField->hwToolTip, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti); + + mySendMessage(pField->hwToolTip, TTM_SETMAXTIPWIDTH, 0, (LPARAM) (INT) pField->nToolTipMaxWidth); + + if(pField->crToolTipTxtColor != 0xFFFFFFFF) + mySendMessage(pField->hwToolTip,TTM_SETTIPTEXTCOLOR, (WPARAM) (COLORREF) pField->crToolTipTxtColor, 0); + if(pField->crToolTipBgColor != 0xFFFFFFFF) + mySendMessage(pField->hwToolTip,TTM_SETTIPBKCOLOR, (WPARAM) (COLORREF) pField->crToolTipBgColor, 0); + + if(pField->nToolTipFlags & TTS_BALLOON) + mySendMessage(pField->hwToolTip, TTM_SETTITLE, pField->nToolTipIcon, (LPARAM) (LPCTSTR) pField->pszToolTipTitle); + } + + { + char szField[64]; + char szHwnd[64]; + wsprintf(szField, "Field %d", pField->nField); + wsprintf(szHwnd, "%d", hwCtrl); + WritePrivateProfileString(szField, "HWND", szHwnd, pszFilename); + } + +// Set Configurations For Each Control Type +//================================================================ + if (hwCtrl) { + + // Font + //-------------------------------------------------------------- + // Sets the font of IO window to be the same as the main window by default + mySendMessage(hwCtrl, WM_SETFONT, (WPARAM)hFont, TRUE); + + // Set new font based on control settings + { + LOGFONT lf; + HDC hDC = GetDC(hwCtrl); + HFONT OldFont; + + // CreateFont now + + GetObject(hFont, sizeof(lf), (LPVOID)&lf); + + if(pField->pszFontName) + strncpy(lf.lfFaceName,pField->pszFontName, 32); + if(pField->nFontHeight) + lf.lfHeight = -MulDiv(pField->nFontHeight, GetDeviceCaps(hDC, LOGPIXELSY), 72); + if(pField->nFontWidth) + lf.lfWidth = pField->nFontWidth; + if(pField->nFontBold) + lf.lfWeight = 700; + if(pField->nFontItalic) + lf.lfItalic = TRUE; + if(pField->nFontUnderline) + lf.lfUnderline = TRUE; + if(pField->nFontStrikeOut) + lf.lfStrikeOut = TRUE; + + OldFont = (HFONT)SelectObject(hDC, CreateFontIndirect(&lf)); + + mySendMessage(hwCtrl, WM_SETFONT, (WPARAM) CreateFontIndirect(&lf), TRUE); + + DeleteObject(SelectObject(hDC, OldFont)); + ReleaseDC(hwCtrl, hDC); + } + + // Configurations for each control type + //-------------------------------------------------------------- + + // make sure we created the window, then set additional attributes + switch (pField->nType) { + case FIELD_IMAGE: + { + switch(pField->nDataType) + { + case IMAGE_TYPE_BITMAP: + case IMAGE_TYPE_OLE: + case IMAGE_TYPE_GDIPLUS: + { + if (pField->nFlags & FLAG_TRANSPARENT) + { + // based on AdvSplash's SetTransparentRegion + BITMAP bm; + HBITMAP hBitmap = (HBITMAP) pField->hImage; + + if (GetObject(hBitmap, sizeof(bm), &bm)) + { + HDC dc; + int x, y; + HRGN region, cutrgn; + BITMAPINFO bmi; + int size = bm.bmWidth * bm.bmHeight * sizeof(int); + int *bmp = (int *) MALLOC(size); + if (bmp) + { + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biHeight = bm.bmHeight; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = bm.bmWidth; + bmi.bmiHeader.biClrUsed = 0; + bmi.bmiHeader.biClrImportant = 0; + + dc = CreateCompatibleDC(NULL); + SelectObject(dc, hBitmap); + + x = GetDIBits(dc, hBitmap, 0, bm.bmHeight, bmp, &bmi, DIB_RGB_COLORS); + + region = CreateRectRgn(0, 0, bm.bmWidth, bm.bmHeight); + + int keycolor = *bmp & 0xFFFFFF; + + // Search for transparent pixels + for (y = bm.bmHeight - 1; y >= 0; y--) { + for (x = 0; x < bm.bmWidth;) { + if ((*bmp & 0xFFFFFF) == keycolor) { + int j = x; + while ((x < bm.bmWidth) && ((*bmp & 0xFFFFFF) == keycolor)) { + bmp++, x++; + } + + // Cut transparent pixels from the original region + cutrgn = CreateRectRgn(j, y, x, y + 1); + CombineRgn(region, region, cutrgn, RGN_XOR); + DeleteObject(cutrgn); + } else { + bmp++, x++; + } + } + } + + // Set resulting region. + SetWindowRgn(hwCtrl, region, TRUE); + DeleteObject(region); + DeleteObject(dc); + FREE(bmp); + } + } + } + + mySendMessage(hwCtrl,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)pField->hImage); + + // Center the image in the requested space. + // Cannot use SS_CENTERIMAGE because it behaves differently on XP to + // everything else. (Thank you Microsoft.) + RECT bmp_rect; + GetClientRect(hwCtrl, &bmp_rect); + bmp_rect.left = (pField->RectPx.left + pField->RectPx.right - bmp_rect.right) / 2; + bmp_rect.top = (pField->RectPx.top + pField->RectPx.bottom - bmp_rect.bottom) / 2; + SetWindowPos(hwCtrl, NULL, bmp_rect.left, bmp_rect.top, 0, 0,SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); + break; + } + case IMAGE_TYPE_ICON: + { + mySendMessage(hwCtrl,STM_SETIMAGE,IMAGE_ICON,(LPARAM)pField->hImage); + break; + } + case IMAGE_TYPE_CURSOR: + { + mySendMessage(hwCtrl,STM_SETIMAGE,IMAGE_CURSOR,(LPARAM)pField->hImage); + break; + } + case IMAGE_TYPE_ANIMATION: + { + if (pField->pszText) { + mySendMessage(hwCtrl, ACM_OPEN, 0, (LPARAM) pField->pszText); + if(lstrcmp(pField->pszState,"")==0) + mySendMessage(hwCtrl, ACM_PLAY, -1, MAKELONG(0, -1)); + else + { + UINT nTimes = myatoi(pField->pszState); + + if (nTimes != 0) { + if((pField->nMaxLength == NULL)&&(pField->nMinLength == NULL)) + mySendMessage(hwCtrl, ACM_PLAY, nTimes, MAKELONG(0, -1)); + else if((pField->nMaxLength != NULL)&&(pField->nMinLength == NULL)&&(0 <= pField->nMaxLength)) + mySendMessage(hwCtrl, ACM_PLAY, nTimes, MAKELONG(0, pField->nMaxLength)); + else if((pField->nMaxLength == NULL)&&(pField->nMinLength != NULL)&&(pField->nMinLength <= 65536)) + mySendMessage(hwCtrl, ACM_PLAY, nTimes, MAKELONG(pField->nMinLength, -1)); + else if((pField->nMaxLength != NULL)&&(pField->nMinLength != NULL)&&(pField->nMinLength <= pField->nMaxLength)) + mySendMessage(hwCtrl, ACM_PLAY, nTimes, MAKELONG(pField->nMinLength, pField->nMaxLength)); + else + pField->nMinLength = pField->nMaxLength = NULL; + } + } + } + break; + } + } + break; + } + case FIELD_TEXT: + mySendMessage(hwCtrl, EM_LIMITTEXT, (WPARAM)pField->nMaxLength, (LPARAM)0); + + if(pField->nFlags & FLAG_PASSWORD) + { + if(pField->pszText) + { + TCHAR* pszChar = (TCHAR*)MALLOC(1+1); + strncpy(pszChar, pField->pszText, 1+1); + mySendMessage(hwCtrl, EM_SETPASSWORDCHAR, (WPARAM) *pszChar, 0); + } + else + mySendMessage(hwCtrl, EM_SETPASSWORDCHAR, (WPARAM) '*', 0); + } + break; + + case FIELD_UPDOWN: + { + if((pField->nMaxLength == NULL)&&(pField->nMinLength == NULL)) + mySendMessage(hwCtrl, UDM_SETRANGE32, -0x7FFFFFFE, 0x7FFFFFFE); + else if((pField->nMaxLength != NULL)&&(pField->nMinLength == NULL)&&(-2147483646 <= pField->nMaxLength)) + mySendMessage(hwCtrl, UDM_SETRANGE32, -0x7FFFFFFE, pField->nMaxLength); + else if((pField->nMaxLength == NULL)&&(pField->nMinLength != NULL)&&(pField->nMinLength <= 2147483646)) + mySendMessage(hwCtrl, UDM_SETRANGE32, pField->nMinLength,0x7FFFFFFE); + else if((pField->nMaxLength != NULL)&&(pField->nMinLength != NULL)&&(pField->nMinLength <= pField->nMaxLength)) + mySendMessage(hwCtrl, UDM_SETRANGE32, pField->nMinLength, pField->nMaxLength); + else + pField->nMinLength = pField->nMaxLength = NULL; + + if (pField->pszState) + mySendMessage(hwCtrl, UDM_SETPOS32, 0, myatoi(pField->pszState)); + break; + } + case FIELD_CHECKBOX: + case FIELD_RADIOBUTTON: + switch (pField->pszState[0]) + { + case '1': + { + mySendMessage(hwCtrl, BM_SETCHECK, (WPARAM)BST_CHECKED, 0); + break; + } + case '2': + { + if(pField->nFlags & FLAG_3STATE) + mySendMessage(hwCtrl, BM_SETCHECK, (WPARAM)BST_INDETERMINATE, 0); + break; + } + } + break; + + case FIELD_COMBOBOX: + case FIELD_LISTBOX: + // if this is a listbox or combobox, we need to add the list items. + if (pField->pszListItems) + { + UINT nAddMsg, nFindMsg, nSetSelMsg; + + switch(pField->nType) + { + case FIELD_COMBOBOX: + { + nAddMsg = CB_ADDSTRING; + nFindMsg = CB_FINDSTRINGEXACT; + nSetSelMsg = CB_SETCURSEL; + + // Using the same limit for text controls because + // nobody will write 32KB of text in a single line. + mySendMessage(hwCtrl, CB_LIMITTEXT, 32766, 0); + + break; + } + case FIELD_LISTBOX: + { + nAddMsg = LB_ADDSTRING; + nFindMsg = LB_FINDSTRINGEXACT; + nSetSelMsg = LB_SETCURSEL; + + if(pField->pszText && myatoi(pField->pszText) > 0) + mySendMessage(hwCtrl, LB_SETCOLUMNWIDTH, (WPARAM) myatoi(pField->pszText), 0); + } + } + + int nResult = 0; + + LPSTR pszStart, pszEnd; + pszStart = pszEnd = pField->pszListItems; + + while(nResult = IOLI_NextItem(pField->pszListItems, &pszStart, &pszEnd, 1)) + { + if((nResult == 5 && lstrcmp(pszStart,"") == 0) || nResult == 6) + break; + + mySendMessage(hwCtrl, nAddMsg, 0, (LPARAM) pszStart); + + IOLI_RestoreItemStructure(pField->pszListItems, &pszStart, &pszEnd, nResult); + } + + if(pField->pszState) + { + if(pField->nType == FIELD_LISTBOX && pField->nFlags & (FLAG_MULTISELECT|FLAG_EXTENDEDSELECT)) + { + mySendMessage(hwCtrl, LB_SETSEL, FALSE, (LPARAM)-1); + + pszStart = pszEnd = pField->pszState; + + while(nResult = IOLI_NextItem(pField->pszState, &pszStart, &pszEnd, 1)) + { + if((nResult == 5 && lstrcmp(pszStart,"") == 0) || nResult == 6) + break; + + int nItem = mySendMessage(hwCtrl, LB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)pszStart); + if (nItem != LB_ERR) + mySendMessage(hwCtrl, LB_SETSEL, TRUE, nItem); + + IOLI_RestoreItemStructure(pField->pszState, &pszStart, &pszEnd, nResult); + } + } + else + { + int nItem = mySendMessage(hwCtrl, nFindMsg, (WPARAM)-1, (LPARAM)pField->pszState); + + if (nItem != CB_ERR) // CB_ERR == LB_ERR == -1 + mySendMessage(hwCtrl, nSetSelMsg, nItem, 0); + else + if(pField->nType == FIELD_COMBOBOX && !(pField->nFlags & FLAG_DROPLIST)) + mySendMessage(hwCtrl, WM_SETTEXT, 0, (LPARAM)pField->pszState); + } + } + } + break; + + case FIELD_TREEVIEW: + { + // "ListItems" has to exist in order to continue + if (pField->pszListItems && lstrlen(pField->pszListItems)) + { + + // Step 1: Implement image list feature. + //---------------------------------------------------------------- + if(pField->pszStateImageList && pField->nFlags & FLAG_CHECKBOXES) + { + if(FileExists(pField->pszStateImageList)) + { + // Set the image list to the TreeView control + HBITMAP hBitmap = (HBITMAP)LoadImage(0, pField->pszStateImageList, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + pField->hStateImageList = (HIMAGELIST)ImageList_Create(16, 16, ILC_COLOR32|ILC_MASK, 6, 0); + ImageList_AddMasked(pField->hStateImageList,hBitmap,RGB(255,0,255)); + TreeView_SetImageList(hwCtrl, pField->hStateImageList, TVSIL_STATE); + + DeleteObject((HBITMAP)hBitmap); + } + } + + // Step 2: Include items to TreeView. + //---------------------------------------------------------------- + HTREEITEM hItem = NULL; + HTREEITEM hParentItem = NULL; + + int nResult = 0; + + LPSTR pszStart, pszEnd; + + pszStart = pszEnd = pField->pszListItems; + while(nResult = IOLI_NextItem(pField->pszListItems, &pszStart, &pszEnd, 0)) + { + if((nResult == 5 && lstrlen(pszStart)==0 )||nResult == 6) + break; + + // When two sublevel are closed, it return an empty string... Then we go to the next item + if(lstrlen(pszStart)==0) + { + if(nResult == 2) hParentItem = hItem; + if(nResult == 3) hParentItem = TreeView_GetParent(hwCtrl, hParentItem); + + IOLI_RestoreItemStructure(pField->pszListItems, &pszStart, &pszEnd, nResult); + continue; + } + + // Provide information about the item + TVITEM tvi; + + tvi.mask = TVIF_TEXT | TVIF_CHILDREN; + tvi.pszText = pszStart; + tvi.cchTextMax = lstrlen(pszStart); + tvi.cChildren = nResult == 2 ? 1 : 0; + + // Insert the item to the TreeView control + TVINSERTSTRUCT tvins; + tvins.hParent = hParentItem; + tvins.item = tvi; + + tvins.hInsertAfter = TVI_LAST; + + tvi.hItem = hItem = TreeView_InsertItem(hwCtrl, &tvins); + TreeView_SetItem(hwCtrl, &tvi); + if(nResult == 2) hParentItem = hItem; + if(nResult == 3) hParentItem = TreeView_GetParent(hwCtrl, hParentItem); + + IOLI_RestoreItemStructure(pField->pszListItems, &pszStart, &pszEnd, nResult); + } + + hParentItem = NULL; + + // Step 3: Set the state of each item. + //---------------------------------------------------------------- + if(pField->pszState) + { + int nPrevResult = 0; + hItem = TreeView_GetRoot(hwCtrl); + + pszStart = pszEnd = pField->pszState; + + while(nResult = IOLI_NextItem(pField->pszState, &pszStart, &pszEnd, 0)) + { + if((nResult == 5 && lstrcmp(pszStart,"")==0)||nResult == 6) + break; + + if(pField->pszStateImageList && pField->nFlags & FLAG_CHECKBOXES) + { + if(*pszStart) + FIELD_TREEVIEW_Check(hwCtrl, hItem, TREEVIEW_AUTOCHECK, myatoi(pszStart), FALSE); + + if(nResult == 2) + hItem = TreeView_GetChild(hwCtrl, hItem); + if(nResult == 1) + hItem = TreeView_GetNextSibling(hwCtrl, hItem); + if(nResult == 3) + { + hItem = TreeView_GetParent(hwCtrl, hItem); + if(TreeView_GetNextSibling(hwCtrl, hItem) != NULL) + hItem = TreeView_GetNextSibling(hwCtrl, hItem); + } + } + else + { + LPSTR pszText = (LPSTR)MALLOC(260+1); + + for(;;) + { + TVITEM tvi; + tvi.mask = TVIF_TEXT | TVIF_CHILDREN; + tvi.pszText = pszText; + tvi.cchTextMax = 260; + tvi.hItem = hItem; + TreeView_GetItem(hwCtrl, &tvi); + + pszText = tvi.pszText; + + if(!hItem) goto AfterState; + + if(lstrcmp(pszText, pszStart) == 0) + { + if(nResult == 2) + { + hItem = TreeView_GetChild(hwCtrl, hItem); + break; + } + else + { + TreeView_SelectItem(hwCtrl, hItem); + goto AfterState; + } + } + hItem = TreeView_GetNextSibling(hwCtrl, hItem); + } + + FREE(pszText); + } + + nPrevResult = nResult; + + IOLI_RestoreItemStructure(pField->pszState, &pszStart, &pszEnd, nResult); + } + AfterState:; + } + + if(pField->pszStateImageList && pField->nFlags & FLAG_CHECKBOXES) + SetWindowLong(hwCtrl, GWL_STYLE ,dwStyle | TVS_CHECKBOXES); + } + + TreeView_SetTextColor(hwCtrl, pField->crTxtColor); + TreeView_SetBkColor(hwCtrl, pField->crBgColor); + + TreeView_SetItemHeight(hwCtrl, pField->nListItemsHeight); + + break; + } + + case FIELD_LISTVIEW: + { + if (pField->nFlags & FLAG_CHECKBOXES && FileExists(pField->pszStateImageList)) + ListView_SetExtendedListViewStyleEx(hwCtrl, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES); + else + pField->nFlags &= ~FLAG_CHECKBOXES; + + // "ListItems" has to exist in order to continue + if (pField->pszListItems) + { + // Step 1: Implement image list feature. + //---------------------------------------------------------------- + + // Step 1.1: Detect number of items. + int nItems = FIELD_LISTVIEW_IOLI_CountItems(pField->pszListItems, 0); + + // Step 1.2: State image list. + if(pField->nFlags & FLAG_CHECKBOXES) + { + if(FileExists(pField->pszStateImageList)) + { + // Set the image list to the ListView control + HBITMAP hBitmap = (HBITMAP)LoadImage(0, pField->pszStateImageList, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + HIMAGELIST hImageList = (HIMAGELIST)ImageList_Create(16, 16, ILC_COLOR32|ILC_MASK, 6, 0); + ImageList_AddMasked(hImageList,hBitmap,RGB(255,0,255)); + ListView_SetImageList(hwCtrl, hImageList, LVSIL_STATE); + + DeleteObject((HBITMAP)hBitmap); + } + } + + // Step 1.3: Small items image list. + if(pField->pszSmallImageList) + { + if(FileExists(pField->pszSmallImageList)) + { + // Set the image list to the ListView control + HBITMAP hBitmap = (HBITMAP)LoadImage(0, pField->pszSmallImageList, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + HIMAGELIST hImageList = (HIMAGELIST)ImageList_Create(16, 16, ILC_COLOR32|ILC_MASK, nItems, 0); + ImageList_AddMasked(hImageList,hBitmap,RGB(255,0,255)); + ListView_SetImageList(hwCtrl, hImageList, LVSIL_SMALL); + + DeleteObject((HBITMAP)hBitmap); + } + } + + // Step 1.4: Large items image list. + if(pField->pszLargeImageList) + { + if(FileExists(pField->pszLargeImageList)) + { + // Set the image list to the ListView control + HBITMAP hBitmap = (HBITMAP)LoadImage(0, pField->pszLargeImageList, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + HIMAGELIST hImageList = (HIMAGELIST)ImageList_Create(16, 16, ILC_COLOR32|ILC_MASK, nItems, 0); + ImageList_AddMasked(hImageList,hBitmap,RGB(255,0,255)); + ListView_SetImageList(hwCtrl, hImageList, LVSIL_NORMAL); + + DeleteObject((HBITMAP)hBitmap); + } + } + + // Step 2: Create columns. + //---------------------------------------------------------------- + + // Step 2.1: Detect number of colums to add. + int nColumns = FIELD_LISTVIEW_IOLI_CountSubItems(pField->pszListItems, pField->pszHeaderItems) + 1; // Plus an item column. + + // Step 2.2: Insert a fake column. + LVCOLUMN lvc; + + int nSubItem = 0; + + lvc.mask = LVCF_WIDTH; + lvc.cx = 0; + + ListView_InsertColumn(hwCtrl, 0, &lvc); + + // Step 2.3: Create custom columns. + LPSTR pszHeaderStart, pszHeaderEnd; + pszHeaderStart = pszHeaderEnd = pField->pszHeaderItems; + + LPSTR pszHeaderItemsWidthStart, pszHeaderItemsWidthEnd; + pszHeaderItemsWidthStart = pszHeaderItemsWidthEnd = pField->pszHeaderItemsWidth; + + LPSTR pszHeaderItemsAlignStart, pszHeaderItemsAlignEnd; + pszHeaderItemsAlignStart = pszHeaderItemsAlignEnd = pField->pszHeaderItemsAlign; + + int nHeaderResult = 0; + int nHeaderItemsWidthResult = 0; + int nHeaderItemsAlignResult = 0; + + // pField->pszHeaderItems has a trailing pipe + if(pField->pszHeaderItems) + { + pszHeaderStart = pszHeaderEnd = pField->pszHeaderItems; + + while((nHeaderResult = IOLI_NextItem(pField->pszHeaderItems, &pszHeaderStart, &pszHeaderEnd, 1)) && nSubItem < nColumns) + { + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + + lvc.cx = 100; + if(pField->pszHeaderItemsWidth) + { + nHeaderItemsWidthResult = IOLI_NextItem(pField->pszHeaderItemsWidth, &pszHeaderItemsWidthStart, &pszHeaderItemsWidthEnd, 1); + + if (*pszHeaderItemsWidthStart) + lvc.cx = myatoi(pszHeaderItemsWidthStart); + + IOLI_RestoreItemStructure(pField->pszHeaderItemsWidth, &pszHeaderItemsWidthStart, &pszHeaderItemsWidthEnd, nHeaderItemsWidthResult); + } + + lvc.fmt = (bRTL ? LVCFMT_RIGHT : LVCFMT_LEFT); + // pField->pszHeaderItemsAlign has a trailing pipe + if(pField->pszHeaderItemsAlign) + { + nHeaderItemsAlignResult = IOLI_NextItem(pField->pszHeaderItemsAlign, &pszHeaderItemsAlignStart, &pszHeaderItemsAlignEnd, 1); + + if (*pszHeaderItemsAlignStart) + { + if(lstrcmp(pszHeaderItemsAlignStart,"LEFT")==0) + lvc.fmt = (bRTL ? LVCFMT_RIGHT : LVCFMT_LEFT); + else if(lstrcmp(pszHeaderItemsAlignStart,"CENTER")==0) + lvc.fmt = LVCFMT_CENTER; + else if(lstrcmp(pszHeaderItemsAlignStart,"RIGHT")==0) + lvc.fmt = (bRTL ? LVCFMT_LEFT : LVCFMT_RIGHT); + } + + IOLI_RestoreItemStructure(pField->pszHeaderItemsAlign, &pszHeaderItemsAlignStart, &pszHeaderItemsAlignEnd, nHeaderItemsAlignResult); + } + + lvc.pszText = pszHeaderStart; + lvc.iSubItem = nSubItem; + nSubItem++; + + ListView_InsertColumn(hwCtrl, nSubItem, &lvc); + + IOLI_RestoreItemStructure(pField->pszHeaderItems, &pszHeaderStart, &pszHeaderEnd, nHeaderResult); + } + } + + ListView_DeleteColumn(hwCtrl, 0); + + // Step 3: Include items to ListView. + //---------------------------------------------------------------- + + int nResult = 0; + + if(pField->pszListItems) + { + LPSTR pszStart, pszEnd; + pszStart = pszEnd = pField->pszListItems; + + int nResult = 0; + + int nLevel = 0; + int nItem = -1; + nSubItem = 0; + + while(nResult = IOLI_NextItem(pField->pszListItems, &pszStart, &pszEnd, 0)) + { + if((nResult == 5 && lstrcmp(pszStart,"")==0)||nResult == 6) + break; + + nItem++; + // Provide information about the item + LVITEM lvi; + + lvi.mask = LVIF_TEXT | LVIF_IMAGE; + lvi.pszText = pszStart; + lvi.cchTextMax = strlen(pszStart); + lvi.iItem = nItem; + + if(nLevel <= 0) + { + lvi.mask |= LVIF_STATE; + lvi.iImage = nItem; + lvi.state = 0; + lvi.stateMask = 0; + + lvi.iSubItem = 0; + // Insert the item to the ListView control + ListView_InsertItem(hwCtrl, &lvi); + } + else + { + nItem--; + nSubItem++; + + lvi.iItem = nItem; + lvi.iSubItem = nSubItem; + // Set the information to the respective item + ListView_SetItem(hwCtrl, &lvi); + } + + switch (nResult) + { + case 2: {nLevel++; break;} + case 1: break; + case 3: {nLevel--; nSubItem = 0; break;} + } + + IOLI_RestoreItemStructure(pField->pszListItems, &pszStart, &pszEnd, nResult); + } + } + + // Step 4: Select items. + //---------------------------------------------------------------- + + if(pField->pszState) + { + int nItem = -1; + + // Search the "State" string given by the user for ListView items + + LPSTR pszStart, pszEnd; + pszStart = pszEnd = pField->pszState; + while(nResult = IOLI_NextItem(pField->pszState, &pszStart, &pszEnd, 1)) + { + if((nResult == 5 && lstrcmp(pszStart,"")==0)||nResult == 6) + break; + + if(pField->nFlags & FLAG_CHECKBOXES) + { + nItem++; + LVITEM lvi; + + lvi.mask = LVIF_STATE; + lvi.iItem = nItem; + lvi.iSubItem = 0; + + lvi.state = INDEXTOSTATEIMAGEMASK(2); + lvi.stateMask = LVIS_STATEIMAGEMASK; + + int nState = myatoi(pszStart); + int iState = INDEXTOSTATEIMAGEMASK(2); + + // No checkboxes (TREEVIEW_NOCHECKBOX) + //------------------------------------ + if(nState & TREEVIEW_NOCHECKBOX) + iState = INDEXTOSTATEIMAGEMASK(0); + + // Read-only checkboxes (TREEVIEW_READONLY) + //----------------------------------------- + else + if(nState & TREEVIEW_READONLY) + { + if(nState & TREEVIEW_CHECKED) + // Read-only checked checkboxes (TREEVIEW_READONLY | TREEVIEW_CHECKED) + iState = INDEXTOSTATEIMAGEMASK(6); + else + // Read-only unchecked checkboxes (TREEVIEW_READONLY) + iState = INDEXTOSTATEIMAGEMASK(5); + } + else + // Checked checkboxes (TREEVIEW_CHECKED) + //----------------------------------------- + if(nState & TREEVIEW_CHECKED) + iState = INDEXTOSTATEIMAGEMASK(3); + + ListView_SetItemState(hwCtrl, nItem, iState, LVIS_STATEIMAGEMASK); + } + else + { + LPSTR pszText = (LPSTR)MALLOC(260+1); + while(true) + { + nItem++; + // Provide information about the item + ListView_GetItemText(hwCtrl, nItem, 0, pszText, 260); + if(lstrcmp(pszStart, pszText)==0) + { + ListView_SetItemState(hwCtrl, nItem, LVIS_SELECTED, LVIS_SELECTED); + break; + } + if(ListView_GetNextItem(hwCtrl, nItem, 0) == -1) + break; + } + FREE(pszText); + } + + IOLI_RestoreItemStructure(pField->pszState, &pszStart, &pszEnd, nResult); + } + } + } + + ListView_SetTextColor(hwCtrl, pField->crTxtColor); + ListView_SetBkColor(hwCtrl, pField->crBgColor); + ListView_SetTextBkColor(hwCtrl, pField->crBgColor); + break; + } + + case FIELD_BUTTON: + { + if(FileExists(pField->pszText)) + { + if (pField->nFlags & FLAG_BITMAP) { + HANDLE hImageButton = LoadImage(m_hInstance,pField->pszText,IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + mySendMessage(hwCtrl, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hImageButton); + } + else if (pField->nFlags & FLAG_ICON) { + HANDLE hImageButton = LoadImage(m_hInstance,pField->pszText,IMAGE_ICON, 0, 0, LR_LOADFROMFILE); + mySendMessage(hwCtrl, BM_SETIMAGE, IMAGE_ICON, (LPARAM) hImageButton); + } + } + break; + } + + case FIELD_PROGRESSBAR: + { + if(pField->nMaxLength) + mySendMessage(hwCtrl, PBM_SETRANGE32, 0, pField->nMaxLength); + else + mySendMessage(hwCtrl, PBM_SETRANGE32, 0, 100); + + int nState = myatoi(pField->pszState); + + mySendMessage(hwCtrl, PBM_SETPOS, nState, NULL); + + // Set Indicator and Background colors + + mySendMessage(hwCtrl, PBM_SETBARCOLOR, 0, pField->crTxtColor); + if(pField->crBgColor != 0xFFFFFFFF) + mySendMessage(hwCtrl, PBM_SETBKCOLOR, 0, pField->crBgColor); + + break; + } + case FIELD_TRACKBAR: + { + if(pField->nMaxLength) + mySendMessage(hwCtrl, TBM_SETRANGEMAX, TRUE, pField->nMaxLength - 1); + else + mySendMessage(hwCtrl, TBM_SETRANGEMAX, TRUE, 100 - 1); + + mySendMessage(hwCtrl, TBM_SETTICFREQ, 1, NULL); + mySendMessage(hwCtrl, TBM_SETPOS, TRUE, myatoi(pField->pszState)); + break; + } + case FIELD_IPADDRESS: + { + int nResult = strlen(pField->pszState); + + pField->pszState[nResult] = '.'; + pField->pszState[nResult + 1] = '\0'; + + char *pszStart, *pszEnd, *pszList; + pszStart = pszEnd = pszList = STRDUP(pField->pszState); + + int iItem = 0; + BYTE b0 = 0; + BYTE b1 = 0; + BYTE b2 = 0; + BYTE b3 = 0; + + while (*pszEnd) { + if (*pszEnd == '.') { + *pszEnd = '\0'; + iItem++; + if (*pszStart) { + switch(iItem) + { + case 1: + b0 = myatoi(pszStart); + break; + case 2: + b1 = myatoi(pszStart); + break; + case 3: + b2 = myatoi(pszStart); + break; + case 4: + b3 = myatoi(pszStart); + break; + default: + break; + } + } + pszStart = ++pszEnd; + } + else + pszEnd = CharNext(pszEnd); + } + + LPARAM nIP = MAKEIPADDRESS(b0,b1,b2,b3); + + mySendMessage(hwCtrl, IPM_SETADDRESS, NULL, nIP); + + break; + } + case FIELD_DATETIME: + { + SYSTEMTIME SystemTime; + + if(pField->pszState != NULL) { + + int nResult = strlen(pField->pszState); + + pField->pszState[nResult] = ' '; + pField->pszState[nResult + 1] = '\0'; + + char *pszStart, *pszEnd, *pszList; + pszStart = pszEnd = pszList = STRDUP(pField->pszState); + + int iItem = 0; + + while (*pszEnd) { + if ((*pszEnd == '/')||(*pszEnd == '\\')||(*pszEnd == '-')||(*pszEnd == ':')||(*pszEnd == ' ')) { + *pszEnd = '\0'; + iItem++; + if (*pszStart) { + switch(iItem) + { + case 1: + SystemTime.wDay = myatoi(pszStart); //Day + break; + case 2: + SystemTime.wMonth = myatoi(pszStart); //Month + break; + case 3: + SystemTime.wYear = myatoi(pszStart); //Year + break; + case 4: + SystemTime.wHour = myatoi(pszStart); //Hour + break; + case 5: + SystemTime.wMinute = myatoi(pszStart); //Minute + break; + case 6: + SystemTime.wSecond = myatoi(pszStart); //Second + break; + case 7: + SystemTime.wDayOfWeek = myatoi(pszStart); //DayOfWeek + break; + default: + break; + } + } + pszStart = ++pszEnd; + } + else + pszEnd = CharNext(pszEnd); + } + } + else + { + GetSystemTime(&SystemTime); + } + mySendMessage(hwCtrl, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM) (LPSYSTEMTIME) &SystemTime); + + if(pField->pszText) + mySendMessage(hwCtrl, DTM_SETFORMAT, 0, (LPARAM) (LPCTSTR) pField->pszText); + + // Set colors + mySendMessage(hwCtrl, DTM_SETMCCOLOR, MCSC_TEXT, pField->crTxtColor); + mySendMessage(hwCtrl, DTM_SETMCCOLOR, MCSC_MONTHBK, pField->crBgColor); + mySendMessage(hwCtrl, DTM_SETMCCOLOR, MCSC_TITLETEXT, pField->crSelTxtColor); + mySendMessage(hwCtrl, DTM_SETMCCOLOR, MCSC_TITLEBK, pField->crSelBgColor); + mySendMessage(hwCtrl, DTM_SETMCCOLOR, MCSC_BACKGROUND, pField->crMonthOutColor); + mySendMessage(hwCtrl, DTM_SETMCCOLOR, MCSC_TRAILINGTEXT, pField->crMonthTrailingTxtColor); + + break; + } + case FIELD_MONTHCALENDAR: + { + SYSTEMTIME SystemTime; + + if(pField->pszState != NULL) { + + int nResult = strlen(pField->pszState); + + pField->pszState[nResult] = ' '; + pField->pszState[nResult + 1] = '\0'; + + char *pszStart, *pszEnd, *pszList; + pszStart = pszEnd = pszList = STRDUP(pField->pszState); + + int iItem = 0; + + while (*pszEnd) { + if ((*pszEnd == '/')||(*pszEnd == '\\')||(*pszEnd == '-')||(*pszEnd == ' ')) { + *pszEnd = '\0'; + iItem++; + if (*pszStart) { + switch(iItem) + { + case 1: + SystemTime.wDay = myatoi(pszStart); //Day + break; + case 2: + SystemTime.wMonth = myatoi(pszStart); //Month + break; + case 3: + SystemTime.wYear = myatoi(pszStart); //Year + break; + default: + break; + } + } + pszStart = ++pszEnd; + } + else + pszEnd = CharNext(pszEnd); + } + mySendMessage(hwCtrl, MCM_SETCURSEL, 0, (LPARAM) (LPSYSTEMTIME) &SystemTime); + } + else + { + GetSystemTime(&SystemTime); + mySendMessage(hwCtrl, MCM_SETCURSEL, 0, (LPARAM) (LPSYSTEMTIME) &SystemTime); + } + + // Set colors + mySendMessage(hwCtrl, MCM_SETCOLOR, MCSC_TEXT, pField->crTxtColor); + mySendMessage(hwCtrl, MCM_SETCOLOR, MCSC_MONTHBK, pField->crBgColor); + mySendMessage(hwCtrl, MCM_SETCOLOR, MCSC_TITLETEXT, pField->crSelTxtColor); + mySendMessage(hwCtrl, MCM_SETCOLOR, MCSC_TITLEBK, pField->crSelBgColor); + mySendMessage(hwCtrl, MCM_SETCOLOR, MCSC_BACKGROUND, pField->crMonthOutColor); + mySendMessage(hwCtrl, MCM_SETCOLOR, MCSC_TRAILINGTEXT, pField->crMonthTrailingTxtColor); + + break; + } + case FIELD_RICHTEXT: + { + // Step 1: Set settings + mySendMessage(hwCtrl,EM_AUTOURLDETECT,TRUE,0); + + LPARAM nEvents = ENM_LINK|ENM_KEYEVENTS; + if(pField->nNotify & NOTIFY_CONTROL_ONTEXTCHANGE) + nEvents |= ENM_CHANGE; + if(pField->nNotify & NOTIFY_CONTROL_ONTEXTUPDATE) //Ignored if RichEdit version => 2 + nEvents |= ENM_UPDATE; + if(pField->nNotify & NOTIFY_CONTROL_ONTEXTSELCHANGE) + nEvents |= ENM_SELCHANGE; + + mySendMessage(hwCtrl,EM_SETEVENTMASK,0,nEvents); + if(pField->nMaxLength) + mySendMessage(hwCtrl,EM_EXLIMITTEXT,0,(LPARAM)pField->nMaxLength); + else + mySendMessage(hwCtrl,EM_EXLIMITTEXT,0,0xFFFFFFFE); + + if(pField->nFlags & FLAG_PASSWORD) + { + if(pField->pszText) + { + TCHAR* pszChar = (TCHAR*)MALLOC(1+1); + strncpy(pszChar, pField->pszText, 1+1); + mySendMessage(hwCtrl, EM_SETPASSWORDCHAR, (WPARAM) *pszChar, 0); + } + else + mySendMessage(hwCtrl, EM_SETPASSWORDCHAR, (WPARAM) '*', 0); + } + + // Step 2: Open the user file + // escape the path name: \n => \\n but not \ to \\ + // I can't use IOtoa because "c:\\fichier.rtf" don't work on windows 9x + pField->pszState = IOtoaFolder(pField->pszState); + + EDITSTREAM editStream; +#ifdef USE_SECURE_FUNCTIONS + FILE * hFile; + if ( fopen_s(&hFile, pField->pszState, "rb") != 0) +#else + FILE * hFile = fopen(pField->pszState, "rb"); + if(!(hFile)) +#endif + { + //TODO: Add error handling + break; + } + fseek(hFile,0,SEEK_END); + UINT nDataLen=ftell(hFile); + if (nDataLen == -1) + { + //TODO: Add error handling + break; + } + rewind(hFile); + char *pszData = (char*)MALLOC(nDataLen+1); + + if (fread(pszData,1,nDataLen,hFile) != nDataLen) { + //TODO: Add error handling + fclose(hFile); + FREE(pszData); + break; + } + + pszData[nDataLen]=0; + if (!strncmp(pszData,"{\\rtf",sizeof("{\\rtf")-1)) + pField->nDataType = SF_RTF; + else + pField->nDataType = SF_TEXT; + fclose(hFile); + + dwRead=0; + editStream.pfnCallback = FIELD_RICHTEXT_StreamIn; + editStream.dwCookie = (DWORD)pszData; + mySendMessage(hwCtrl,EM_STREAMIN,(WPARAM)pField->nDataType,(LPARAM)&editStream); + + FREE(pszData); + break; + } + } + + pField->nParentIdx = SetWindowLong(hwCtrl, GWL_WNDPROC, (long)ControlWindowProc); + + // Set initial focus to the first appropriate field ( with FOCUS flag) + if (!fFocusedByFlag && (dwStyle & (WS_TABSTOP | WS_DISABLED)) == WS_TABSTOP && pField->nType >= FIELD_SETFOCUS) { + if (pField->nFlags & FLAG_FOCUS) { + fFocusedByFlag = TRUE; + } + if (fFocusedByFlag) { + fFocused = TRUE; + mySetFocus(hwCtrl); + } else if (!fFocused) { + // If multiline-readonly don't initially select RichText controls. + // useful for License Page look-a-likes. + if (! (pField->nType == FIELD_RICHTEXT && (pField->nFlags & (FLAG_MULTILINE | FLAG_READONLY)) == (FLAG_MULTILINE | FLAG_READONLY)) ) { + fFocused = TRUE; + mySetFocus(hwCtrl); + } + } + } + + // If multiline-readonly then hold the text back until after the + // initial focus has been set. This is so the text is not initially + // selected - useful for License Page look-a-likes. + if (pField->nType == FIELD_TEXT && (pField->nFlags & (FLAG_MULTILINE | FLAG_READONLY)) == (FLAG_MULTILINE | FLAG_READONLY)) + mySetWindowText(hwCtrl, pField->pszState); + } + } + +// Special Control Configurations +//================================================================ + for (int nIdx2 = 0; nIdx2 < nNumFields; nIdx2++) + { + IOExControlStorage *pField = pFields + nIdx2; + if(pField->nType == FIELD_UPDOWN && pField->nRefFields) { + HWND hBuddy = GetDlgItem(hConfigWindow,1200+pField->nRefFields-1); + mySendMessage(pField->hwnd, UDM_SETBUDDY, (WPARAM) (HWND) hBuddy, 0); + myGetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), pField->pszState); + mySendMessage(pField->hwnd, UDM_SETPOS32, 0, myatoi(pField->pszState)); + } + } + +// Set focus to the first control (depending on controls order) +//================================================================ + if (!fFocused) + mySetFocus(hNextButton); + +// "Title" ("Settings") Implementation +//================================================================ + mySetWindowText(mainwnd,pszTitle); + +// Pop filename and push the hConfigWindow handle to NSIS Stack +//================================================================ + *g_stacktop = (*g_stacktop)->next; + + static char tmp[32]; + wsprintf(tmp,"%d",hConfigWindow); + pushstring(tmp); + +// Return to original function +//================================================================ + return 0; +} + + + + + + + + + + + + + + +///---------------------------------------------------------------- +// Show function part +//================================================================ +void WINAPI showCfgDlg() +{ +// Step 1: Show Page +//================================================================ + + // Let the plugin handle dialog messages for now on + //-------------------------------------------------------------- + lpWndProcOld = (void *) SetWindowLong(hMainWindow,DWL_DLGPROC,(long)MainWindowProc); + + // Enable keyboard and mouse input and show back window + //-------------------------------------------------------------- + mySendMessage(hMainWindow, WM_NOTIFY_CUSTOM_READY, (WPARAM)hConfigWindow, 0); + ShowWindow(hConfigWindow, SW_SHOWNA); + + // Provide a way to leave the "while" below + //-------------------------------------------------------------- + g_done = 0; + + // Notification System Implementation + //-------------------------------------------------------------- + g_aNotifyQueue = (NotifyQueue*)MALLOC(sizeof(NotifyQueue)*g_nNotifyQueueAmountMax); + + g_aNotifyQueueTemp = (NotifyQueue*)MALLOC(sizeof(NotifyQueue)); + g_aNotifyQueueTemp->iNotifyId = NOTIFY_NONE; + g_aNotifyQueueTemp->iField = 0; + g_aNotifyQueueTemp->bNotifyType = NOTIFY_TYPE_PAGE; + + // Set the page timer (TimeOut Implementation) + //-------------------------------------------------------------- + if(nTimeOutTemp != 0) + { + g_timer_cur_time = GetTickCount(); + SetTimer(hConfigWindow,g_timer_id,nTimeOutTemp,NULL); + } + + // Handle hConfigWindow messages (code execution pauses here) + //-------------------------------------------------------------- + while (!g_done) { + GetMessage(&msg, NULL, 0, 0); + + if (!IsDialogMessage(hConfigWindow,&msg) && !IsDialogMessage(hMainWindow,&msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + //KillTimer(hConfigWindow, g_notification_timer_id); + +// Step 2: Destroy Page +//================================================================ + + // Notification System Implementation + //-------------------------------------------------------------- + + // Free buffers (g_aNotifyQueue) + FREE(g_aNotifyQueue); + FREE(g_aNotifyQueueTemp); + + // we don't save settings on cancel since that means your installer will likely + // quit soon, which means the ini might get flushed late and cause crap. :) anwyay. + if (!g_is_cancel) SaveSettings(); + + // Let NSIS handle dialog messages for now on + //-------------------------------------------------------------- + SetWindowLong(hMainWindow,DWL_DLGPROC,(long)lpWndProcOld); + + // Destroy the window (w/ all the controls inside) + //-------------------------------------------------------------- + DestroyWindow(hConfigWindow); + + // Show installer buttons as they were before using plugin + //-------------------------------------------------------------- + if (bNextShow!=-1) ShowWindow(hNextButton,old_next_visible?SW_SHOWNA:SW_HIDE); + if (bBackShow!=-1) ShowWindow(hBackButton,old_back_visible?SW_SHOWNA:SW_HIDE); + if (bCancelShow!=-1) ShowWindow(hCancelButton,old_cancel_visible?SW_SHOWNA:SW_HIDE); + + if (bNextEnabled!=-1) EnableWindow(hNextButton,old_next_enabled); + if (bBackEnabled!=-1) EnableWindow(hBackButton,old_back_enabled); + if (bCancelEnabled!=-1) EnableWindow(hCancelButton,old_cancel_enabled); + + // Free buffers (global) and handles + //-------------------------------------------------------------- + FREE(pszTitle); + FREE(pszCancelButtonText); + FREE(pszNextButtonText); + FREE(pszBackButtonText); + + // Kill the page timer (TimeOut Implementation) + //-------------------------------------------------------------- + if(g_timer_id != 0) + { + KillTimer(hConfigWindow, g_timer_id); + g_timer_id = 0; + nTimeOut = nTimeOutTemp = 0; + } + + // Free buffers (pField) and handles + //-------------------------------------------------------------- + int i = nNumFields; + while (i--) { + IOExControlStorage *pField = pFields + i; + + int j = FIELD_BUFFERS; + while (j--) + FREE(((char **) pField)[j]); + + if (pField->nType == FIELD_IMAGE) { + switch(pField->nDataType) + { + case IMAGE_TYPE_BITMAP: + case IMAGE_TYPE_OLE: + case IMAGE_TYPE_GDIPLUS: + { + DeleteObject(pField->hImage); + break; + } + case IMAGE_TYPE_ICON: + { + DestroyIcon((HICON)pField->hImage); + break; + } + case IMAGE_TYPE_CURSOR: + { + DestroyCursor((HCURSOR)pField->hImage); + break; + } + case IMAGE_TYPE_ANIMATION: + { + Animate_Close((HWND)pField->hImage); + break; + } + } + } + if (pField->nType == FIELD_TREEVIEW && pField->pszStateImageList) { + // ListView controls automatically destroy image lists. + ImageList_Destroy((HIMAGELIST)pField->hStateImageList); + } + } + FREE(pFields); + + // Push the page direction to NSIS Stack + //-------------------------------------------------------------- + pushstring(g_is_cancel?"cancel":g_is_back?"back":"success"); +} + + + + + + + +BOOL CALLBACK MainWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ +// Notification System Base Implementation (Depends on Conditions) +//================================================================ + BOOL bRes; + BOOL g_timer_notify = false; + + if (message == WM_NOTIFY_OUTER_NEXT && wParam == 1) + { + // Don't call leave function if fields aren't valid + if (!g_aNotifyQueue[0].iField && !ValidateFields()) + return 0; + // Get the settings ready for the leave function verification + SaveSettings(); + + // Make default notification as "ONNEXT" so to confirm that "State" + // and "Notify" INI value names were written under "Settings" INI + // section when the page itself notifies (not controls). + if (g_aNotifyQueue[0].bNotifyType == NOTIFY_TYPE_PAGE) + { + g_aNotifyQueue[0].iField = 0; + if(g_timer_activated) + g_aNotifyQueue[0].iNotifyId = NOTIFY_PAGE_ONTIMEOUT; + else + g_aNotifyQueue[0].iNotifyId = NOTIFY_PAGE_ONNEXT; + + WritePrivateProfileString("Settings", "State", "0", pszFilename); + WritePrivateProfileString("Settings", "Notify", LookupTokenName(PageNotifyTable, g_aNotifyQueue[0].iNotifyId), pszFilename); + } + + // Timer: Allow SetTimer to reset the timer again. + if(nTimeOut >= USER_TIMER_MINIMUM) + g_timer_notify = true; + + // Reset the record of activated control and notification + RemoveNotifyQueueItem(); + } + +// Call lpWndProcOld (-> DialogProc NSIS function - this also calls the leave function if necessary) +//================================================================ + bRes = CallWindowProc((long (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,long))lpWndProcOld,hwnd,message,wParam,lParam); + +// Start To Get Out Of This Page (Depends on Conditions) +//================================================================ + if (message == WM_NOTIFY_OUTER_NEXT && !bRes) + { + WritePrivateProfileString("Settings", "State", "0", pszFilename); + // if leave function didn't abort (bRes != 0 in that case) + if (wParam == (WPARAM)-1) + { + WritePrivateProfileString("Settings", "Notify", LookupTokenName(PageNotifyTable, NOTIFY_PAGE_ONBACK), pszFilename); + g_is_back++; + } + else if (wParam == NOTIFY_BYE_BYE) + { + WritePrivateProfileString("Settings", "Notify", LookupTokenName(PageNotifyTable, NOTIFY_PAGE_ONCANCEL), pszFilename); + g_is_cancel++; + } + else + { + if (g_timer_activated) + WritePrivateProfileString("Settings", "Notify", LookupTokenName(PageNotifyTable, NOTIFY_PAGE_ONTIMEOUT), pszFilename); + else + WritePrivateProfileString("Settings", "Notify", LookupTokenName(PageNotifyTable, NOTIFY_PAGE_ONNEXT), pszFilename); + } + + g_done++; + PostMessage(hConfigWindow,WM_CLOSE,0,0); + } + else if (message == WM_NOTIFY_OUTER_NEXT && bRes && g_timer_notify) + { + // Set the page timer if notified (TimeOut Implementation) + //-------------------------------------------------------------- + g_timer_cur_time = GetTickCount(); + SetTimer(hConfigWindow,g_timer_id,nTimeOut,NULL); + nTimeOut = nTimeOutTemp; + } + + g_timer_activated = false; + return bRes; +} + + + + + + + + + + + + + + +//================================================================ +// Dialog Configuration Proc +//================================================================ +BOOL CALLBACK DialogWindowProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ +// Handle Advanced Notification Messages +//================================================================ + + switch (uMsg) + { + case WM_TIMER: + { + if (wParam == g_timer_id) + { + KillTimer(hConfigWindow,g_timer_id); + g_timer_activated = true; + mySendMessage(hMainWindow, WM_NOTIFY_OUTER_NEXT, 1, 0); + } + } + case WM_COMMAND: + { + UINT nIdx = LOWORD((DWORD)wParam); + UINT nNotifyCode = HIWORD((DWORD)wParam); + HWND hCtrl = (HWND) lParam; + + if (nIdx < 0) + break; + IOExControlStorage *pField = pFields + nIdx; + + NotifyProc(hwndDlg, nIdx, hCtrl, nNotifyCode); + break; + } + case WM_NOTIFY: + { + LPNMHDR hdr = (LPNMHDR)lParam; + + UINT nIdx = hdr->idFrom; + UINT nNotifyCode = hdr->code; + HWND hCtrl = GetDlgItem(hwndDlg, nIdx); + + if (nIdx < 0) + break; + IOExControlStorage *pField = pFields + FindControlIdx(nIdx); + + if(pField->nType == FIELD_TREEVIEW) + { + switch(nNotifyCode) + { + case NM_CLICK: + { + LPNMHDR hdr = (LPNMHDR)lParam; + TVHITTESTINFO ht; + GetCursorPos(&ht.pt); + + ScreenToClient(hCtrl, &ht.pt); + + HTREEITEM hItem = TreeView_HitTest(hCtrl, &ht); + if(!(ht.flags & TVHT_ONITEMSTATEICON)) break; + FIELD_TREEVIEW_Check(hCtrl, hItem, TREEVIEW_AUTOCHECK, 0xFFFFFFFF, FALSE); + } + case TVN_ITEMEXPANDED: + { + LPNMTREEVIEW pnmtv = (LPNMTREEVIEW)lParam; + if (pnmtv->action == TVE_EXPAND) + pnmtv->itemNew.lParam |= TREEVIEW_EXPANDED; + else + pnmtv->itemNew.lParam &= ~TREEVIEW_EXPANDED; + TreeView_SetItem(hCtrl, &pnmtv->itemNew); + } + case TVN_BEGINLABELEDIT: + { + pField->hEditControl = TreeView_GetEditControl(hCtrl); + return 0; + } + case TVN_ENDLABELEDIT: + { + NMTVDISPINFO* nmtvdi = (NMTVDISPINFO*)lParam; + + if((TCHAR)msg.wParam != VK_ESCAPE && nmtvdi->item.mask & LVIF_TEXT) + { + TVITEM tvItem; + tvItem.mask = TVIF_TEXT; + tvItem.pszText = nmtvdi->item.pszText; + tvItem.hItem = nmtvdi->item.hItem; + TreeView_SetItem(hCtrl, &tvItem); + + return 1; + } + else + return 0; + } + case TVN_KEYDOWN: + { + LPNMTVKEYDOWN nmtvkd = (LPNMTVKEYDOWN)lParam; + + if(pField->nFlags & FLAG_EDITLABELS && nmtvkd->wVKey == VK_F2) + { + HTREEITEM iSelectedItem = TreeView_GetNextItem(hCtrl, -1, TVGN_CARET); + + if(!iSelectedItem) + break; + + TreeView_EditLabel(hCtrl, iSelectedItem); + break; + } + + if(pField->nFlags & FLAG_CHECKBOXES && nmtvkd->wVKey == VK_SPACE) + { + HTREEITEM hItem = TreeView_GetSelection(hCtrl); + FIELD_TREEVIEW_Check(hCtrl, hItem, TREEVIEW_AUTOCHECK, 0xFFFFFFFF, FALSE); + } + return 0; + } + } + } + + else if(pField->nType == FIELD_LISTVIEW) + { + switch(nNotifyCode) + { + case NM_CLICK: + case NM_DBLCLK: + { + LPNMHDR hdr = (LPNMHDR)lParam; + LVHITTESTINFO ht; + GetCursorPos(&ht.pt); + + ScreenToClient(hCtrl, &ht.pt); + + int iItem = ListView_HitTest(hCtrl, &ht); + if(!(ht.flags & LVHT_ONITEMSTATEICON)) break; + + // Get Item Information + //-------------------------------------------------------------- + int iState = ListView_GetItemState(hCtrl, iItem, LVIS_STATEIMAGEMASK) >> 12; + + // "Normal" checkboxes (Leaving from Checked state) + //-------------------------------------------------------------- + if(iState == 3) + iState-= 2; + else if(iState >= 4 || iState == 1 || iState <= -1) + iState--; + + ListView_SetItemState(hCtrl, iItem, INDEXTOSTATEIMAGEMASK(iState), LVIS_STATEIMAGEMASK); + } + case LVN_BEGINLABELEDIT: + { + pField->hEditControl = ListView_GetEditControl(hCtrl); + return 0; + } + case LVN_ENDLABELEDIT: + { + NMLVDISPINFO* nmlvdi = (NMLVDISPINFO*)lParam; + + if((TCHAR)msg.wParam != VK_ESCAPE && nmlvdi->item.mask & LVIF_TEXT) + { + ListView_SetItemText(hCtrl, nmlvdi->item.iItem, 0, nmlvdi->item.pszText); + return 1; + } + else + return 0; + } + case LVN_KEYDOWN: + { + LPNMLVKEYDOWN nmlvkd = (LPNMLVKEYDOWN)lParam; + + if(pField->nFlags & FLAG_EDITLABELS && nmlvkd->wVKey == VK_F2) + { + int iSelectedItem = ListView_GetNextItem(hCtrl, -1, LVNI_FOCUSED); + + if(iSelectedItem == -1) + break; + + ListView_EditLabel(hCtrl, iSelectedItem); + break; + } + + if(pField->nFlags & FLAG_CHECKBOXES && nmlvkd->wVKey == VK_SPACE) + { + int iSelectedItem = -1; + + while(true) + { + iSelectedItem = ListView_GetNextItem(hCtrl, iSelectedItem, LVNI_SELECTED); + if(iSelectedItem == -1) + break; + + int iSelectionState = ListView_GetItemState(hCtrl, iSelectedItem, LVIS_STATEIMAGEMASK|LVNI_SELECTED|LVNI_FOCUSED); + int iState = iSelectionState >> 12; + + // "Normal" checkboxes (Leaving from Checked state) + //-------------------------------------------------------------- + if(iState == 3) + { + if(iSelectionState & LVNI_SELECTED && iSelectionState & LVNI_FOCUSED) + iState -= 2; + else + iState--; + } + else if(iState == 2 && !(iSelectionState & LVNI_SELECTED && iSelectionState & LVNI_FOCUSED)) + iState += 1; + else if(iState == 0 && (iSelectionState & LVNI_SELECTED && iSelectionState & LVNI_FOCUSED)) + { + iState; + } + else if((iState >= 4 || iState <= 1) && (iSelectionState & LVNI_SELECTED && iSelectionState & LVNI_FOCUSED)) + { + iState--; + } + + ListView_SetItemState(hCtrl, iSelectedItem, INDEXTOSTATEIMAGEMASK(iState), LVIS_STATEIMAGEMASK); + } + } + return 0; + } + } + } + + else if(pField->nType == FIELD_RICHTEXT) + { + switch(nNotifyCode) + { + case EN_LINK: + { + ENLINK * enlink = (ENLINK *)lParam; + LPSTR ps_tmpbuf = (LPSTR)MALLOC(g_nBufferSize); + + if(pField->nNotify == NOTIFY_CONTROL_ONCLICK) + { + if (enlink->msg==WM_LBUTTONDOWN) { + TEXTRANGE tr = { + { + enlink->chrg.cpMin, + enlink->chrg.cpMax, + }, + ps_tmpbuf + }; + + if (tr.chrg.cpMax-tr.chrg.cpMin < g_nBufferSize) { + SendMessage(hCtrl,EM_GETTEXTRANGE,0,(LPARAM)&tr); + SetCursor(LoadCursor(0,IDC_WAIT)); + ShellExecute(hConfigWindow,"open",tr.lpstrText,NULL,NULL,SW_SHOWNORMAL); + SetCursor(LoadCursor(0,IDC_ARROW)); + } + + // START: Parts of NotifyProc. + g_aNotifyQueueTemp->iField = nIdx + 1; + g_aNotifyQueueTemp->bNotifyType = NOTIFY_TYPE_CONTROL; + + AddNotifyQueueItem(g_aNotifyQueueTemp); + + // Kill the page timer if notified (TimeOut Implementation) + //================================================================ + if(g_timer_id != 0) + { + nTimeOut = nTimeOutTemp - (GetTickCount() - g_timer_cur_time); + if (nTimeOut < USER_TIMER_MINIMUM) + nTimeOut = 0; + KillTimer(hConfigWindow, g_timer_id); + } + + // Simulate "Next" Click So NSIS Can Call Its PageAfter Function + //================================================================ + mySendMessage(hMainWindow, WM_NOTIFY_OUTER_NEXT, 1, 0); + // END: Part of NotifyProc. + } + + return 0; + } + } + case EN_MSGFILTER: + { + MSGFILTER * msgfilter = (MSGFILTER *)lParam; + if (msgfilter->msg==WM_KEYDOWN) + { + if ((!IsWindowEnabled(hdr->hwndFrom) || pField->nFlags & FLAG_READONLY) && msgfilter->wParam==VK_RETURN) + SendMessage(hMainWindow, WM_COMMAND, IDOK, 0); + if (msgfilter->wParam==VK_ESCAPE) + SendMessage(hMainWindow, WM_CLOSE, 0, 0); + return 1; + } + } + } + } + + NotifyProc(hwndDlg, nIdx, hCtrl, nNotifyCode); + break; + } + + case WM_HSCROLL: + case WM_VSCROLL: + { + int nTempIdx = FindControlIdx(GetDlgCtrlID((HWND)lParam)); + // Ignore if the dialog is in the process of being created + if (g_done || nTempIdx < 0) + break; + IOExControlStorage *pField = pFields + nTempIdx; + + if(pField->nType == FIELD_TRACKBAR) { + UINT nIdx = GetDlgCtrlID((HWND)lParam); + UINT nNotifyCode = LOWORD(wParam); + HWND hCtrl = (HWND)lParam; + NotifyProc(hwndDlg, nIdx, hCtrl, nNotifyCode); + } + return 0; + } + + case WM_MEASUREITEM: + { + LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT) lParam; + + int nIdx = FindControlIdx(lpmis->CtlID); + + if (nIdx < 0) + break; + IOExControlStorage *pField = pFields + nIdx; + + switch(pField->nType) + + case FIELD_LISTBOX: + case FIELD_COMBOBOX: + + // Set the height of the items + lpmis->itemHeight = pField->nListItemsHeight; + + return TRUE; + } + + case WM_DRAWITEM: + { + DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam; + int nIdx = FindControlIdx(lpdis->CtlID); + + if (nIdx < 0) + break; + IOExControlStorage *pField = pFields + nIdx; + + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP && pField->nType != FIELD_LINK) + return 0; //Return false so the default handling is used + + // Set text and background colors now + + if(pField->nType == FIELD_LABEL || pField->nType == FIELD_LINK || pField->nType == FIELD_BUTTON) { + + // We need lpdis->rcItem later + RECT rc = lpdis->rcItem; + + ++rc.left; + --rc.right; + + if(pField->nType == FIELD_LINK) + { + ++rc.left; + --rc.right; + } + + // Move rect to right if in RTL mode + if (bRTL) + { + rc.left += lpdis->rcItem.right - rc.right; + rc.right += lpdis->rcItem.right - rc.right; + } + + if (lpdis->itemAction & ODA_DRAWENTIRE) + { + COLORREF crBgColor; + COLORREF crTxtColor; + COLORREF crTxtShwColor; + + if(IsWindowEnabled(lpdis->hwndItem)) { + if (pField->crBgColor == 0xFFFFFFFF && GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) + crBgColor = GetBkColor(lpdis->hDC); + else + crBgColor = pField->crBgColor; + + if (pField->crTxtColor == 0xFFFFFFFF) + { + if (GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) + crTxtColor = GetTextColor(lpdis->hDC); + else + crTxtColor = RGB(0,0,255); + } + else + { + crTxtColor = pField->crTxtColor; + } + + if (pField->crTxtShwColor == 0xFFFFFFFF && GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) + crTxtShwColor = GetBkColor(lpdis->hDC); + else + crTxtShwColor = pField->crTxtShwColor; + } else { + if (pField->crDisBgColor == 0xFFFFFFFF && GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) + crBgColor = GetBkColor(lpdis->hDC); + else + crBgColor = pField->crDisBgColor; + + if (pField->crDisTxtColor == 0xFFFFFFFF) + { + if(GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) + crTxtColor = GetSysColor(COLOR_GRAYTEXT); + else + crTxtColor = RGB(0,0,100); + } + else + crTxtColor = pField->crDisTxtColor; + + if (pField->crDisTxtShwColor == 0xFFFFFFFF) + { + if(GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) + crTxtColor = GetSysColor(COLOR_WINDOW); + else + crTxtColor = RGB(255, 255, 255); + } + else + crTxtColor = pField->crDisTxtShwColor; + } + + // Draw Background on the whole control + if(crBgColor != 0xFFFFFFFF && GetBkMode(lpdis->hDC) != TRANSPARENT) { + + HBRUSH hBrush = CreateSolidBrush(crBgColor); + + HGDIOBJ hOldSelObj = SelectObject(lpdis->hDC, hBrush); + if(GetDeviceCaps(lpdis->hDC, RASTERCAPS) & RC_BITBLT) { + int rcWidth = lpdis->rcItem.right - lpdis->rcItem.left; + int rcHeight = lpdis->rcItem.bottom - lpdis->rcItem.top; + PatBlt(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, rcWidth, rcHeight, PATCOPY); + } + SelectObject(lpdis->hDC, hOldSelObj); + } + + int clrBackgroundMode = SetBkMode(lpdis->hDC, TRANSPARENT); + + if(crTxtShwColor != 0xFFFFFFFF && GetBkMode(lpdis->hDC) != TRANSPARENT) + { + // Draw Shadow Text + ++rc.left; + ++rc.right; + ++rc.top; + ++rc.bottom; + SetTextColor(lpdis->hDC, crTxtShwColor); + + DrawText(lpdis->hDC, pField->pszText, -1, &rc, DT_EXPANDTABS | DT_WORDBREAK | (pField->nTxtAlign == ALIGN_TEXT_LEFT ? DT_LEFT : 0) | (pField->nTxtAlign == ALIGN_TEXT_CENTER ? DT_CENTER : 0) | (pField->nTxtAlign == ALIGN_TEXT_RIGHT ? DT_RIGHT : 0)); + + // Draw Normal Text + --rc.left; + --rc.right; + --rc.top; + --rc.bottom; + } + + // Set Text Color + SetTextColor(lpdis->hDC, crTxtColor); + + DrawText(lpdis->hDC, pField->pszText, -1, &rc, DT_EXPANDTABS | DT_WORDBREAK | (pField->nTxtAlign == ALIGN_TEXT_LEFT ? DT_LEFT : 0) | (pField->nTxtAlign == ALIGN_TEXT_CENTER ? DT_CENTER : 0) | (pField->nTxtAlign == ALIGN_TEXT_RIGHT ? DT_RIGHT : 0)); + + SetBkMode(lpdis->hDC, clrBackgroundMode); + } + + if(pField->nType == FIELD_LINK) { + // Draw the focus rect if needed + if (lpdis->itemState & ODS_FOCUS) + { + // NB: when not in DRAWENTIRE mode, this will actually toggle the focus + // rectangle since it's drawn in a XOR way + DrawFocusRect(lpdis->hDC, &lpdis->rcItem); + } + } + pField->RectPx = lpdis->rcItem; + + if(pField->nType == FIELD_LINK && pField->nFlags & FLAG_CUSTOMDRAW_TEMP) { + return mySendMessage(hMainWindow, uMsg, wParam, lParam); + } + } + else + if(pField->nType == FIELD_LISTBOX || pField->nType == FIELD_COMBOBOX) { + + // If there are no list box items, skip this drawing part + + if (lpdis->itemID == -1) + break; + + // We need lpdis->rcItem later + RECT rc = lpdis->rcItem; + + switch (lpdis->itemAction) + { + case ODA_SELECT: + case ODA_DRAWENTIRE: + { + + COLORREF clrBackground; + COLORREF clrForeground; + + // Set the item selected background color + + if(lpdis->itemState & ODS_SELECTED) + { + if(lpdis->itemState & ODS_DISABLED) + { + if (!GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) { + clrForeground = SetTextColor(lpdis->hDC, pField->crDisSelTxtColor); + if(pField->crBgColor != 0xFFFFFFFF) + clrBackground = SetBkColor(lpdis->hDC, pField->crDisSelBgColor); + } + } + else + { + if (!GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) { + clrForeground = SetTextColor(lpdis->hDC, pField->crSelTxtColor); + if(pField->crBgColor != 0xFFFFFFFF) + clrBackground = SetBkColor(lpdis->hDC, pField->crSelBgColor); + } + } + } + else + { + if(lpdis->itemState & ODS_DISABLED) + { + if (!GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) { + clrForeground = SetTextColor(lpdis->hDC, pField->crDisTxtColor); + if(pField->crBgColor != 0xFFFFFFFF) + clrBackground = SetBkColor(lpdis->hDC, pField->crDisBgColor); + } + } + else + { + if (!GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) { + clrForeground = SetTextColor(lpdis->hDC, pField->crTxtColor); + if(pField->crBgColor != 0xFFFFFFFF) + clrBackground = SetBkColor(lpdis->hDC, pField->crBgColor); + } + } + } + + // Get text from the item specified by lpdis->itemID + LPTSTR pszItemText = (char*)MALLOC(g_nBufferSize); + + if(pField->nType == FIELD_COMBOBOX) + SendMessage(lpdis->hwndItem, CB_GETLBTEXT, lpdis->itemID, (LPARAM) pszItemText); + else + SendMessage(lpdis->hwndItem, LB_GETTEXT, lpdis->itemID, (LPARAM) pszItemText); + + if (!GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) { + + COLORREF crBgColor; + COLORREF crTxtColor; + COLORREF crTxtShwColor; + + if(lpdis->itemState & ODS_SELECTED) { + if(lpdis->itemState & ODS_DISABLED) { + crBgColor = pField->crDisSelBgColor; + crTxtColor = pField->crDisSelTxtColor; + crTxtShwColor = pField->crDisSelTxtShwColor; + } else { + crBgColor = pField->crSelBgColor; + crTxtColor = pField->crSelTxtColor; + crTxtShwColor = pField->crSelTxtShwColor; + } + } else { + if(lpdis->itemState & ODS_DISABLED) { + crBgColor = pField->crDisBgColor; + crTxtColor = pField->crDisTxtColor; + crTxtShwColor = pField->crDisTxtShwColor; + } else { + crBgColor = pField->crBgColor; + crTxtColor = pField->crTxtColor; + crTxtShwColor = pField->crTxtShwColor; + } + } + + // Draw Background on the whole control + if(crBgColor != 0xFFFFFFFF) { + + HBRUSH hBrush = CreateSolidBrush(crBgColor); + + HGDIOBJ hOldSelObj = SelectObject(lpdis->hDC, hBrush); + if(GetDeviceCaps(lpdis->hDC, RASTERCAPS) & RC_BITBLT) { + int rcWidth = lpdis->rcItem.right - lpdis->rcItem.left; + int rcHeight = lpdis->rcItem.bottom - lpdis->rcItem.top; + PatBlt(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, rcWidth, rcHeight, PATCOPY); + } + SelectObject(lpdis->hDC, hOldSelObj); + } + + int clrBackgroundMode = SetBkMode(lpdis->hDC, TRANSPARENT); + + // Make some more room so the focus rect won't cut letters off + rc.left += 2; + rc.right -= 2; + + if(crTxtShwColor != 0xFFFFFFFF) { + + // Draw Shadow Text + ++rc.left; + ++rc.right; + ++rc.top; + ++rc.bottom; + SetTextColor(lpdis->hDC, crTxtShwColor); + + DrawText(lpdis->hDC, pszItemText, -1, &rc, DT_EXPANDTABS | (pField->nTxtAlign == ALIGN_TEXT_LEFT ? DT_LEFT : 0) | (pField->nTxtAlign == ALIGN_TEXT_CENTER ? DT_CENTER : 0) | (pField->nTxtAlign == ALIGN_TEXT_RIGHT ? DT_RIGHT : 0)); + + // Draw Normal Text + --rc.left; + --rc.right; + --rc.top; + --rc.bottom; + } + + // Set Text Color + SetTextColor(lpdis->hDC, crTxtColor); + + DrawText(lpdis->hDC, pszItemText, -1, &rc, DT_EXPANDTABS | (pField->nTxtAlign == ALIGN_TEXT_LEFT ? DT_LEFT : 0) | (pField->nTxtAlign == ALIGN_TEXT_CENTER ? DT_CENTER : 0) | (pField->nTxtAlign == ALIGN_TEXT_RIGHT ? DT_RIGHT : 0)); + + SetBkMode(lpdis->hDC, clrBackgroundMode); + + if(pField->nType == FIELD_LISTBOX) { + // Draw the focus rect if needed + if (lpdis->itemAction & ODA_FOCUS) + { + // Return to the default rect size + rc.left -= 2; + rc.right += 2; + // NB: when not in DRAWENTIRE mode, this will actually toggle the focus + // rectangle since it's drawn in a XOR way + DrawFocusRect(lpdis->hDC, &rc); + } + } + + if(pField->nType == FIELD_COMBOBOX) { + // Draw the focus rect if needed + if (lpdis->itemState & ODS_SELECTED) + { + // Return to the default rect size + rc.left -= 2; + rc.right += 2; + // NB: when not in DRAWENTIRE mode, this will actually toggle the focus + // rectangle since it's drawn in a XOR way + DrawFocusRect(lpdis->hDC, &rc); + } + } + } + } + } + } + return 0; + } + case WM_CTLCOLORDLG: + { + // let the NSIS window handle colors, it knows best + return mySendMessage(hMainWindow, uMsg, wParam, lParam); + } + case WM_CTLCOLORSTATIC: + case WM_CTLCOLOREDIT: + case WM_CTLCOLORBTN: + case WM_CTLCOLORLISTBOX: + { + int nIdx = FindControlIdx(GetDlgCtrlID((HWND)lParam)); + + if (nIdx < 0) + break; + IOExControlStorage *pField = pFields + nIdx; + + if(pField->nType == FIELD_TREEVIEW || (pField->nType == FIELD_IMAGE && pField->nDataType & IMAGE_TYPE_ANIMATION)) break; + + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP || ( + pField->nType == FIELD_CHECKBOX || + pField->nType == FIELD_RADIOBUTTON || + pField->nType == FIELD_BUTTON || + pField->nType == FIELD_UPDOWN)) + // let the NSIS window handle colors, it knows best + return mySendMessage(hMainWindow, uMsg, wParam, lParam); + + // Set text and background colors now + + if(pField->nType == FIELD_TEXT) + { + if(IsWindowEnabled((HWND)lParam) && !(GetWindowLong((HWND)lParam, GWL_STYLE) & ES_READONLY)) + { + SetTextColor((HDC)wParam, pField->crTxtColor); + if(pField->crBgColor != 0xFFFFFFFF) { + SetBkColor((HDC)wParam, pField->crBgColor); + + LOGBRUSH lb; + + lb.lbStyle = BS_SOLID; + lb.lbColor = pField->crBgColor; + + return (UINT)CreateBrushIndirect(&lb); + } + return 0; + } + else if(GetWindowLong((HWND)lParam, GWL_STYLE) & ES_READONLY) + { + SetTextColor((HDC)wParam, pField->crReadOnlyTxtColor); + if(pField->crReadOnlyBgColor != 0xFFFFFFFF) { + SetBkColor((HDC)wParam, pField->crReadOnlyBgColor); + + LOGBRUSH lb; + + lb.lbStyle = BS_SOLID; + lb.lbColor = pField->crReadOnlyBgColor; + + return (UINT)CreateBrushIndirect(&lb); + } + return 0; + } + } + else + { + if(pField->crTxtColor != 0xFFFFFFFF) + SetTextColor((HDC)wParam, pField->crTxtColor); + if(pField->crBgColor != 0xFFFFFFFF) + SetBkColor((HDC)wParam, pField->crBgColor); + } + + UpdateWindow(pField->hwnd); + break; + } + } + return 0; +} + +int WINAPI ControlWindowProc(HWND hWin, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + int iIdx = FindControlIdx(GetDlgCtrlID(hWin)); + if (iIdx < 0) + return 0; + IOExControlStorage *pField = pFields + iIdx; + + switch(uMsg) + { + case WM_KEYDOWN: + { + //BUG: Caused by Windows bug: the "End" key used for the navigation + //on ListView controls when the items have no icons and the view is LVS_ICON, + //it selects the first item on the last row of items, which differs from + //the usage everywhere else. + + //SOLUTION: Workaround below. This handles the "End" key in a + //similar way than as done on the rest of ListView view modes, + //except that this sends a lot of messages, and has lots of if's. + + switch(pField->nType) + { + case FIELD_LISTVIEW: + { + if( + (pField->nFlags & FLAG_ICON_VIEW && !pField->pszLargeImageList) && + ( + ( + (wParam == VK_SHIFT || HIWORD(GetKeyState(VK_SHIFT))) && + ( + (wParam != VK_LEFT && !HIWORD(GetKeyState(VK_LEFT))) && + (wParam != VK_RIGHT && !HIWORD(GetKeyState(VK_RIGHT))) && + (wParam != VK_UP && !HIWORD(GetKeyState(VK_UP))) && + (wParam != VK_DOWN && !HIWORD(GetKeyState(VK_DOWN))) && + (wParam != VK_HOME && !HIWORD(GetKeyState(VK_HOME))) && + (wParam != VK_END && !HIWORD(GetKeyState(VK_END))) && + (wParam != VK_PRIOR && !HIWORD(GetKeyState(VK_PRIOR))) && + (wParam != VK_NEXT && !HIWORD(GetKeyState(VK_NEXT))) + ) + || + (wParam != VK_SHIFT && !HIWORD(GetKeyState(VK_SHIFT)) && + ( + (wParam == VK_LEFT || HIWORD(GetKeyState(VK_LEFT))) || + (wParam == VK_RIGHT || HIWORD(GetKeyState(VK_RIGHT))) || + (wParam == VK_UP || HIWORD(GetKeyState(VK_UP))) || + (wParam == VK_DOWN || HIWORD(GetKeyState(VK_DOWN))) || + (wParam == VK_HOME || HIWORD(GetKeyState(VK_HOME))) || + (wParam == VK_END || HIWORD(GetKeyState(VK_END))) || + (wParam == VK_PRIOR || HIWORD(GetKeyState(VK_PRIOR))) || + (wParam == VK_NEXT || HIWORD(GetKeyState(VK_NEXT))) + ) + ) + ) + && + ListView_GetItemState(hWin, ListView_GetNextItem(hWin, -1, LVNI_FOCUSED), LVIS_SELECTED) & LVIS_SELECTED + ) + ) + { + iItemPlaceholder = ListView_GetNextItem(hWin, -1, LVNI_FOCUSED); + } + + if(wParam == VK_END || HIWORD(GetKeyState(VK_END))) + { + if(HIWORD(GetKeyState(VK_SHIFT))) + { + int iItem = 0; + + while(iItem < iItemPlaceholder && iItem != -1) + { + if(ListView_GetItemState(hWin, iItem, LVIS_SELECTED) & LVIS_SELECTED) + ListView_SetItemState(hWin, iItem, 0, LVIS_FOCUSED|LVIS_SELECTED); + iItem = ListView_GetNextItem(hWin, iItem, LVNI_ALL); + } + + while(iItem >= iItemPlaceholder) + { + if(ListView_GetNextItem(hWin, iItem, LVNI_ALL) == -1) + { + if(ListView_GetItemState(hWin, iItem, LVIS_FOCUSED) | ~LVIS_FOCUSED) + ListView_SetItemState(hWin, iItem, LVIS_FOCUSED, LVIS_FOCUSED); + + if(ListView_GetItemState(hWin, iItem, LVIS_SELECTED) | ~LVIS_SELECTED) + ListView_SetItemState(hWin, iItem, LVIS_SELECTED, LVIS_SELECTED); + + POINT ptlvItem; + ListView_GetItemPosition(hWin, iItem, &ptlvItem); + ListView_Scroll(hWin, 0, ptlvItem.y); + break; + } + + if(ListView_GetItemState(hWin, iItem, LVIS_SELECTED) | ~LVIS_SELECTED) + ListView_SetItemState(hWin, iItem, LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED); + + iItem = ListView_GetNextItem(hWin, iItem, LVNI_ALL); + } + } + else + { + int iItem = -1; + + while(true) + { + iItem = ListView_GetNextItem(hWin, iItem, LVNI_SELECTED); + if(iItem == -1) + break; + + ListView_SetItemState(hWin, iItem, 0, LVIS_FOCUSED|LVIS_SELECTED); + } + + iItem = -1; + + while(ListView_GetNextItem(hWin, iItem, 0) != -1) + iItem = ListView_GetNextItem(hWin, iItem, LVNI_ALL); + + ListView_SetItemState(hWin, iItem, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED); + POINT ptlvItem; + ListView_GetItemPosition(hWin, iItem, &ptlvItem); + ListView_Scroll(hWin, 0, ptlvItem.y); + } + return 0; + } + break; + } + } + break; + } + case WM_GETDLGCODE: + { + if(pField->nType == FIELD_LINK) + // Pretend we are a normal button/default button as appropriate + return DLGC_BUTTON | ((pField->nFlags & FLAG_DRAW_TEMP) ? DLGC_DEFPUSHBUTTON : DLGC_UNDEFPUSHBUTTON); + break; + } + case BM_SETSTYLE: + { + if(pField->nType == FIELD_LINK) + { + // Detect when we are becoming the default button but don't lose the owner-draw style + + // Microsoft implicitly says that BS_TYPEMASK was introduced + // on Win 2000. But really (maybe), it was introduced w/ Win 95. + // (Thank you Microsoft.) + if ((wParam & BS_TYPEMASK) == BS_DEFPUSHBUTTON) + pField->nFlags |= FLAG_DRAW_TEMP; // Hijack this flag to indicate default button status + else + pField->nFlags &= ~FLAG_DRAW_TEMP; + wParam = (wParam & ~BS_TYPEMASK) | BS_OWNERDRAW; + } + break; + } + case WM_SETCURSOR: + { + if((HWND)wParam == hWin && + ((pField->nType != FIELD_LINK && pField->nType != FIELD_RICHTEXT && pField->nNotify & NOTIFY_CONTROL_ONCLICK) || + pField->nType == FIELD_LINK) && + LOWORD(lParam) == HTCLIENT) + { + HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(pField->nNotifyCursor)); + if (hCur) + { + SetCursor(hCur); + return 1; // halt further processing + } + } + break; + } + case EM_SETPASSWORDCHAR: + { + if(pField->nType == FIELD_TEXT) + { + TCHAR* pszChar = (TCHAR*)wParam; + if(pField->nFlags == FLAG_PASSWORD) + { + if(lstrcmp(pszChar,"")==0) + pszChar = "*"; + } + else + { + pszChar = ""; + } + return 0; + } + break; + } + case EM_SETREADONLY: + { + if(wParam) + mySendMessage(pField->hwnd,EM_SETBKGNDCOLOR,0,pField->crReadOnlyBgColor); + else + mySendMessage(pField->hwnd,EM_SETBKGNDCOLOR,0,pField->crBgColor); + } + } + return CallWindowProc((WNDPROC)pField->nParentIdx, hWin, uMsg, wParam, lParam); +} + +LRESULT WINAPI NotifyProc(HWND hWnd, UINT id, HWND hwndCtl, UINT codeNotify) { + +// Initialize Variables +//================================================================ + char szBrowsePath[MAX_PATH]; + +// Initialize pField +//================================================================ + int nIdx = FindControlIdx(id); + // Ignore if the dialog is in the process of being created + if (g_done || nIdx < 0) + return 0; + IOExControlStorage *pField = pFields + nIdx; + +// Detect Notifications +//================================================================ + bool isNotified = false; + bool dontSimulateNext = false; + + switch(pField->nType) + { + case FIELD_RICHTEXT: + { + Notification(NOTIFY_CONTROL_ONTEXTSELCHANGE, EN_SELCHANGE); + } + case FIELD_TEXT: + { + Notification(NOTIFY_CONTROL_ONTEXTTRUNCATE, EN_MAXTEXT); + Notification(NOTIFY_CONTROL_ONTEXTVSCROLL, EN_VSCROLL); + Notification(NOTIFY_CONTROL_ONTEXTCHANGE, EN_CHANGE); + Notification(NOTIFY_CONTROL_ONTEXTUPDATE, EN_UPDATE); + Notification(NOTIFY_CONTROL_ONSETFOCUS, EN_SETFOCUS); + Notification(NOTIFY_CONTROL_ONKILLFOCUS, EN_KILLFOCUS); + break; + } + + case FIELD_IPADDRESS: + { + Notification(NOTIFY_CONTROL_ONTEXTCHANGE, EN_CHANGE); + Notification(NOTIFY_CONTROL_ONSELCHANGE, IPN_FIELDCHANGED); + Notification(NOTIFY_CONTROL_ONSETFOCUS, EN_SETFOCUS); + Notification(NOTIFY_CONTROL_ONKILLFOCUS, EN_KILLFOCUS); + break; + } + + case FIELD_COMBOBOX: + { + Notification(NOTIFY_CONTROL_ONTEXTCHANGE, CBN_EDITCHANGE); + Notification(NOTIFY_CONTROL_ONTEXTUPDATE, CBN_EDITUPDATE); + Notification(NOTIFY_CONTROL_ONLISTOPEN, CBN_DROPDOWN); + Notification(NOTIFY_CONTROL_ONLISTCLOSE, CBN_CLOSEUP); + } + case FIELD_LISTBOX: + { + Notification(NOTIFY_CONTROL_ONSELCHANGE, CBN_SELCHANGE); + Notification(NOTIFY_CONTROL_ONDBLCLICK, CBN_DBLCLK); + Notification(NOTIFY_CONTROL_ONSETFOCUS, CBN_SETFOCUS); + Notification(NOTIFY_CONTROL_ONKILLFOCUS, CBN_KILLFOCUS); + break; + } + + case FIELD_DATETIME: + { + Notification(NOTIFY_CONTROL_ONSELCHANGE, DTN_DATETIMECHANGE); + Notification(NOTIFY_CONTROL_ONLISTOPEN, DTN_DROPDOWN); + Notification(NOTIFY_CONTROL_ONLISTCLOSE, NOTIFY_CONTROL_ONLISTCLOSE); + Notification(NOTIFY_CONTROL_ONSETFOCUS, NM_SETFOCUS); + Notification(NOTIFY_CONTROL_ONKILLFOCUS, NM_KILLFOCUS); + break; + } + + case FIELD_MONTHCALENDAR: + { + Notification(NOTIFY_CONTROL_ONSELCHANGE, MCN_SELCHANGE); + break; + } + + case FIELD_UPDOWN: + { + Notification(NOTIFY_CONTROL_ONSELCHANGE, UDN_DELTAPOS); + break; + } + + case FIELD_TRACKBAR: + { + Notification(NOTIFY_CONTROL_ONSELCHANGE, TB_BOTTOM || + codeNotify == TB_LINEDOWN || + codeNotify == TB_LINEUP || + codeNotify == TB_PAGEDOWN || + codeNotify == TB_PAGEUP || + codeNotify == TB_THUMBTRACK || + codeNotify == TB_TOP); + break; + } + + case FIELD_LABEL: + { + Notification(NOTIFY_CONTROL_ONCLICK, STN_CLICKED); + Notification(NOTIFY_CONTROL_ONDBLCLICK, STN_DBLCLK); + break; + } + + case FIELD_LINK: + case FIELD_BUTTON: + { + if (codeNotify == BN_CLICKED || codeNotify == BN_DBLCLK) + { + // If button or link with OPEN_FILEREQUEST, SAVE_FILEREQUEST, DIRREQUEST... flags + if ( (pField->nFlags & FLAG_OPEN_FILEREQUEST) || (pField->nFlags & FLAG_SAVE_FILEREQUEST) || (pField->nFlags & FLAG_DIRREQUEST) || (pField->nFlags & FLAG_COLORREQUEST) || (pField->nFlags & FLAG_FONTREQUEST) ) + { + dontSimulateNext = true; + isNotified = true; + } + // If button or link with a not empty state link + else if (strlen(pField->pszState) > 0) + { + dontSimulateNext = true; + isNotified = true; + } + } + } + case FIELD_CHECKBOX: + case FIELD_RADIOBUTTON: + { + Notification(NOTIFY_CONTROL_ONCLICK, BN_CLICKED); + Notification(NOTIFY_CONTROL_ONDBLCLICK, BN_DBLCLK); + + break; + } + + case FIELD_TREEVIEW: + { + Notification(NOTIFY_CONTROL_ONSELCHANGE, TVN_SELCHANGED); + Notification(NOTIFY_CONTROL_ONCLICK, NM_CLICK); + Notification(NOTIFY_CONTROL_ONDBLCLICK, NM_DBLCLK); + Notification(NOTIFY_CONTROL_ONRCLICK, NM_RCLICK); + Notification(NOTIFY_CONTROL_ONSETFOCUS, NM_SETFOCUS); + Notification(NOTIFY_CONTROL_ONKILLFOCUS, NM_KILLFOCUS); + break; + } + + case FIELD_LISTVIEW: + { + Notification(NOTIFY_CONTROL_ONSELCHANGE, LVN_ITEMCHANGED); + Notification(NOTIFY_CONTROL_ONCLICK, NM_CLICK); + Notification(NOTIFY_CONTROL_ONDBLCLICK, NM_DBLCLK); + Notification(NOTIFY_CONTROL_ONRCLICK, NM_RCLICK); + Notification(NOTIFY_CONTROL_ONRDBLCLICK, NM_RDBLCLK); + Notification(NOTIFY_CONTROL_ONSETFOCUS, NM_SETFOCUS); + Notification(NOTIFY_CONTROL_ONKILLFOCUS, NM_KILLFOCUS); + break; + } + + case FIELD_IMAGE: + { + if(pField->nDataType == IMAGE_TYPE_ANIMATION) + { + Notification(NOTIFY_CONTROL_ONSTART, ACN_START); + Notification(NOTIFY_CONTROL_ONSTOP, ACN_STOP); + } + else + { + Notification(NOTIFY_CONTROL_ONCLICK, STN_CLICKED); + Notification(NOTIFY_CONTROL_ONDBLCLICK, STN_DBLCLK); + } + break; + } + default: + return 0; + } + + if (isNotified == false) + { + return 0; + } + + g_aNotifyQueueTemp->iField = nIdx + 1; + g_aNotifyQueueTemp->bNotifyType = NOTIFY_TYPE_CONTROL; + + AddNotifyQueueItem(g_aNotifyQueueTemp); + + +// Associate Notifications With Actions (By Control Types) +//================================================================ + + switch (pField->nType) + { + + // Link, Button + //-------------------------------------------------------------- + case FIELD_LINK: + case FIELD_BUTTON: + { + // State ShellExecute Implementation + //---------------------------------- + + // Allow the state to be empty - this might be useful in conjunction + // with the NOTIFY flag + if (lstrcmp(pField->pszState,"")!=0 && !(pField->nFlags & FLAG_OPEN_FILEREQUEST) && !(pField->nFlags & FLAG_SAVE_FILEREQUEST) && !(pField->nFlags & FLAG_DIRREQUEST) && !(pField->nFlags & FLAG_COLORREQUEST) && !(pField->nFlags & FLAG_FONTREQUEST)) + { + ShellExecute(hMainWindow, NULL, pField->pszState, NULL, NULL, SW_SHOWDEFAULT); + } + + // FileRequest Dialog Implementation + //---------------------------------- + else if(pField->nFlags & FLAG_OPEN_FILEREQUEST || pField->nFlags & FLAG_SAVE_FILEREQUEST) { + OPENFILENAME ofn={0,}; + + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hConfigWindow; + ofn.lpstrFilter = pField->pszFilter; + ofn.lpstrFile = szBrowsePath; + ofn.nMaxFile = sizeof(szBrowsePath); + + if(pField->nFlags & FLAG_WARN_IF_EXIST) + ofn.Flags |= OFN_OVERWRITEPROMPT; + if(pField->nFlags & FLAG_FILE_HIDEREADONLY) + ofn.Flags |= OFN_HIDEREADONLY; + if(pField->nFlags & FLAG_PATH_MUST_EXIST) + ofn.Flags |= OFN_PATHMUSTEXIST; + if(pField->nFlags & FLAG_FILE_MUST_EXIST) + ofn.Flags |= OFN_FILEMUSTEXIST; + if(pField->nFlags & FLAG_PROMPT_CREATE) + ofn.Flags |= OFN_CREATEPROMPT; + if(pField->nFlags & FLAG_FILE_EXPLORER) + ofn.Flags |= OFN_EXPLORER; + if(pField->nFlags & FLAG_MULTISELECT) { + ofn.Flags |= OFN_ALLOWMULTISELECT; + ofn.Flags |= OFN_EXPLORER; + } + + if(pField->nRefFields) + GetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), szBrowsePath, sizeof(szBrowsePath)); + else + strcpy(szBrowsePath, pField->pszState); + + tryagain: + GetCurrentDirectory(BUFFER_SIZE, szResult); // save working dir + if ((pField->nFlags & FLAG_SAVE_FILEREQUEST) ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn)) { + lstrcpyn(pField->pszState,szBrowsePath,g_nBufferSize); + if(pField->nRefFields) + mySetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), szBrowsePath); + + SetCurrentDirectory(szResult); // restore working dir + // OFN_NOCHANGEDIR doesn't always work (see MSDN) + break; + } + else if (szBrowsePath[0] && CommDlgExtendedError() == FNERR_INVALIDFILENAME) { + szBrowsePath[0] = '\0'; + goto tryagain; + } + break; + } + + // DirRequest Dialog Implementation + //--------------------------------- + else if(pField->nFlags & FLAG_DIRREQUEST) { + + if(codeNotify == EN_CHANGE) + break; + BROWSEINFO bi; + + LPSTR szDisplayNameFolder = (LPSTR)MALLOC(MAX_PATH); + + bi.hwndOwner = hConfigWindow; + bi.pidlRoot = NULL; + bi.pszDisplayName = szDisplayNameFolder; + atoIO(pField->pszListItems); + bi.lpszTitle = pField->pszListItems; +#ifndef BIF_NEWDIALOGSTYLE +#define BIF_NEWDIALOGSTYLE 0x0040 +#endif + bi.ulFlags = BIF_STATUSTEXT | BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; + bi.lpfn = BrowseCallbackProc; + bi.lParam = (LPARAM)szBrowsePath; + bi.iImage = 0; + + if(pField->nRefFields) + GetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), szBrowsePath, sizeof(szBrowsePath)); + else + strcpy(szBrowsePath, pField->pszState); + + lstrcpyn(szDisplayNameFolder, pField->pszState, g_nBufferSize > MAX_PATH ? MAX_PATH : g_nBufferSize); + + if (pField->pszRoot) { + LPSHELLFOLDER sf; + ULONG eaten; + LPITEMIDLIST root; + int ccRoot = (strlen(pField->pszRoot) * 2) + 2; + LPWSTR pwszRoot = (LPWSTR) MALLOC(ccRoot); + MultiByteToWideChar(CP_ACP, 0, pField->pszRoot, -1, pwszRoot, ccRoot); + SHGetDesktopFolder(&sf); + sf->ParseDisplayName(hConfigWindow, NULL, pwszRoot, &eaten, &root, NULL); + bi.pidlRoot = root; + sf->Release(); + FREE(pwszRoot); + } + + LPITEMIDLIST pResult = SHBrowseForFolder(&bi); + if (pResult) + { + if (SHGetPathFromIDList(pResult, szBrowsePath)) + { + lstrcpyn(pField->pszState,szBrowsePath,g_nBufferSize); + if(pField->nRefFields) + mySetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), szBrowsePath); + } + } + + FREE(szDisplayNameFolder); + + break; + } + + // ColorRequest Dialog Implementation + //--------------------------------- + else if (pField->nFlags & FLAG_COLORREQUEST) + { + CHOOSECOLOR cc; + + cc.lStructSize = sizeof(cc); + cc.hwndOwner = pField->hwnd; + cc.Flags = CC_RGBINIT; + + int i = 0; + COLORREF acrCustClr[16]; + + if(pField->pszListItems) + { + int nResult = 0; + + LPSTR pszStart, pszEnd; + pszStart = pszEnd = pField->pszListItems; + + while(nResult = IOLI_NextItem(pField->pszListItems, &pszStart, &pszEnd, 0)) + { + if((nResult == 5 && lstrcmp(pszStart,"") == 0) || nResult == 6) + break; + + if(lstrcmp(pszStart,"")!=0) + acrCustClr[i] = (COLORREF)myatoi(pszStart); + else + acrCustClr[i] = (COLORREF)0xFFFFFF; + + i++; + + IOLI_RestoreItemStructure(pField->pszListItems, &pszStart, &pszEnd, nResult); + } + } + + for(;i < 16;i++) + acrCustClr[i] = RGB(255,255,255); + + cc.lpCustColors = (LPDWORD)acrCustClr; + + if(pField->nRefFields) + myGetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), pField->pszState); + + cc.rgbResult = (COLORREF)myatoi(pField->pszState); + + if(ChooseColor(&cc)) + { + mycrxtoa(pField->pszState, (int)cc.rgbResult); + + if(pField->nRefFields) + mySetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), pField->pszState); + + LPSTR pszColorTemp = (LPSTR)MALLOC(sizeof(COLORREF)+1); + LPSTR pszResultTemp = (LPSTR)MALLOC(g_nBufferSize); + LPSTR pszResultTemp2 = (LPSTR)MALLOC(g_nBufferSize); + for(i = 0;i < 16;i++) + { + mycrxtoa(pszColorTemp, acrCustClr[i]); + if(i==15) + wsprintf(pszResultTemp2,"%s%s",pszResultTemp,pszColorTemp); + else + wsprintf(pszResultTemp2,"%s%s\x01",pszResultTemp,pszColorTemp); + strcpy(pszResultTemp, pszResultTemp2); + } + + lstrcpy(pField->pszListItems, pszResultTemp); + + FREE(pszColorTemp); + FREE(pszResultTemp); + FREE(pszResultTemp2); + } + break; + } + + // FontRequest Dialog Implementation + //--------------------------------- + else if (pField->nFlags & FLAG_FONTREQUEST) + { + CHOOSEFONT cf; + LOGFONT lf; + LPSTR pszFontRequestList = (LPSTR)MALLOC(96); + + GetObject((HFONT)mySendMessage(hConfigWindow, WM_GETFONT, 0, 0), sizeof(lf), (LPVOID)&lf); + + if(pField->nRefFields) + { + GetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), pField->pszState, 96); + pField->pszState[lstrlen(pField->pszState)-1] = '\0'; + LItoIOLI(pField->pszState, 96, 2, pField->nType); + } + + lstrcpyn(pszFontRequestList, pField->pszState, 96); + + if(pszFontRequestList) + { + int i = 0; + + int nResult = 0; + + LPSTR pszStart, pszEnd; + pszStart = pszEnd = pszFontRequestList; + + while(nResult = IOLI_NextItem(pszFontRequestList, &pszStart, &pszEnd, 1)) + { + if((nResult == 5 && lstrcmp(pszStart,"") == 0) || nResult == 6) + break; + + switch(i) + { + case 0: + { + if(pszStart) + strncpy(lf.lfFaceName,pszStart, 32); + break; + } + case 1: + { + if(pszStart) + { + HDC hDC = GetDC(pField->hwnd); + lf.lfHeight = -MulDiv(myatoi(pszStart), GetDeviceCaps(hDC, LOGPIXELSY), 72); + ReleaseDC(pField->hwnd, hDC); + } + break; + } + case 2: + { + if(myatoi(pszStart)) + lf.lfWeight = 700; + break; + } + case 3: + { + if(myatoi(pszStart)) + lf.lfItalic = TRUE; + break; + } + case 4: + { + if(myatoi(pszStart)) + lf.lfUnderline = TRUE; + break; + } + case 5: + { + if(myatoi(pszStart)) + lf.lfStrikeOut = TRUE; + break; + } + case 6: + { + cf.rgbColors = (COLORREF)myatoi(pszStart); + break; + } + default: + { + break; + } + } + ++i; + + IOLI_RestoreItemStructure(pszFontRequestList, &pszStart, &pszEnd, nResult); + } + } + + cf.lStructSize = sizeof(cf); + cf.hwndOwner = pField->hwnd; + cf.Flags = CF_SCREENFONTS | CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_NOSCRIPTSEL; + cf.lpLogFont = &lf; + + if(ChooseFont(&cf)) + { + lstrcpy(pszFontRequestList, ""); + LPSTR pszStrTemp = (LPSTR)MALLOC(32); + for(int i = 0;i < 7;i++) + { + switch(i) + { + case 0: + { + strcpy(pszStrTemp,lf.lfFaceName); + break; + } + case 1: + { + HDC hDC = GetDC(pField->hwnd); + myitoa(pszStrTemp, -MulDiv(lf.lfHeight, 72, GetDeviceCaps(hDC, LOGPIXELSY))); + ReleaseDC(pField->hwnd, hDC); + break; + } + case 2: + { + if(lf.lfWeight == 700) + myitoa(pszStrTemp,1); + else + myitoa(pszStrTemp,0); + break; + } + case 3: + { + if(lf.lfItalic) + myitoa(pszStrTemp,1); + else + myitoa(pszStrTemp,0); + break; + } + case 4: + { + if(lf.lfUnderline) + myitoa(pszStrTemp,1); + else + myitoa(pszStrTemp,0); + break; + } + case 5: + { + if(lf.lfStrikeOut) + myitoa(pszStrTemp,1); + else + myitoa(pszStrTemp,0); + break; + } + case 6: + { + mycrxtoa(pszStrTemp, cf.rgbColors); + break; + } + default: + break; + } + + if(i==6) + lstrcat(pszFontRequestList, pszStrTemp); + else + { + lstrcat(pszFontRequestList, pszStrTemp); + lstrcat(pszFontRequestList, "\x01"); + } + } + + lstrcpyn(pField->pszState,pszFontRequestList, 96); + + if(pField->nRefFields) + { + pszFontRequestList = IOLItoLI(pszFontRequestList, 96, pField->nType); + SetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), pszFontRequestList); + } + FREE(pszStrTemp); + } + //else + //if(!pField->nRefFields) + // pField->pszState[lstrlen(pField->pszState)-1] = '\0'; + FREE(pszFontRequestList); + } + break; + } + } + +// Kill the page timer if notified (TimeOut Implementation) +//================================================================ + if(g_timer_id != 0) + { + nTimeOut = nTimeOutTemp - (GetTickCount() - g_timer_cur_time); + if (nTimeOut < USER_TIMER_MINIMUM) + nTimeOut = 0; + KillTimer(hConfigWindow, g_timer_id); + } + +// Simulate "Next" Click So NSIS Can Call Its PageAfter Function +//================================================================ + if (dontSimulateNext != true) + mySendMessage(hMainWindow, WM_NOTIFY_OUTER_NEXT, 1, 0); + + return 0; +} + + + + + + + + + + + + + + +int WINAPI ReadSettings() { + +// Initial Variables Definitions +//================================================================ + static char szField[25]; + int nIdx, nCtrlIdx; + +// Get Window Settings +//================================================================ + pszAppName = "Settings"; + + // Assign default values for buffer sizes: + g_nBufferSize = BUFFER_SIZE; + g_nNotifyQueueAmountMax = NOTIFY_QUEUE_AMOUNT_MAX; + + // BufferSize + //-------------------------------------------------------------- + LPSTR pszTemp = (LPSTR)MALLOC(16); + + GetPrivateProfileString(pszAppName, "BufferSize", "", pszTemp, 16, pszFilename); + g_nBufferSize = myatoi(pszTemp); + FREE(pszTemp); + + if(g_nBufferSize < 1) + g_nBufferSize = BUFFER_SIZE; + + // Use this for normal "szResult" uses + szResult = (char*)MALLOC(g_nBufferSize); + + // NotifyFlagsMax + //-------------------------------------------------------------- + g_nNotifyQueueAmountMax = myGetProfileInt("NotifyFlagsMax", NOTIFY_QUEUE_AMOUNT_MAX); + + if(g_nNotifyQueueAmountMax <= 0) + g_nNotifyQueueAmountMax = NOTIFY_QUEUE_AMOUNT_MAX; + + // Title + //-------------------------------------------------------------- + pszTitle = myGetProfileStringDup("Title"); + + // CancelButtonText + //-------------------------------------------------------------- + pszCancelButtonText = myGetProfileStringDup("CancelButtonText"); + + // NextButtonText + //-------------------------------------------------------------- + pszNextButtonText = myGetProfileStringDup("NextButtonText"); + + // BackButtonText + //-------------------------------------------------------------- + pszBackButtonText = myGetProfileStringDup("BackButtonText"); + + // Rect + //-------------------------------------------------------------- + nRectId = myGetProfileInt("Rect", DEFAULT_RECT); + + // NextEnabled + //-------------------------------------------------------------- + bNextEnabled = myGetProfileInt("NextEnabled", -1); + + // NextShow + //-------------------------------------------------------------- + bNextShow = myGetProfileInt("NextShow", -1); + + // BackEnabled + //-------------------------------------------------------------- + bBackEnabled = myGetProfileInt("BackEnabled", -1); + + // BackShow + //-------------------------------------------------------------- + bBackShow = myGetProfileInt("BackShow", -1); + + // CancelEnabled + //-------------------------------------------------------------- + bCancelEnabled = myGetProfileInt("CancelEnabled", -1); + + // CancelShow + //-------------------------------------------------------------- + bCancelShow = myGetProfileInt("CancelShow", -1); + + // RTL + //-------------------------------------------------------------- + bRTL = myGetProfileInt("RTL", 0); + + // MUnit + //-------------------------------------------------------------- + bMUnit = myGetProfileInt("MUnit", 0); + + // TimeOut + //-------------------------------------------------------------- + nTimeOut = myGetProfileInt("TimeOut", 0); + if (nTimeOut < USER_TIMER_MINIMUM || nTimeOut > USER_TIMER_MAXIMUM) + nTimeOut = 0; + nTimeOutTemp = nTimeOut; + + // NumFields + //-------------------------------------------------------------- + nNumFields = myGetProfileInt("NumFields", 0); + if(nNumFields <= 0) + { + nNumFields = 0; + while (nNumFields > -1) { + wsprintf(szField, "Field %d", nNumFields + 1); + pszAppName = szField; + if(myGetProfileString("Type")) + ++nNumFields; + else + break; + } + } + + if (nNumFields > 0) + // the structure is small enough that this won't waste much memory. + // if the structure gets much larger, we should switch to a linked list. + pFields = (IOExControlStorage *)MALLOC(sizeof(IOExControlStorage)*nNumFields); + +// Detect Field Settings +//================================================================ + for (nIdx = 0, nCtrlIdx = 0; nCtrlIdx < nNumFields; nCtrlIdx++, nIdx++) { + + // Type Flags + //----------- + static TableEntry TypeTable[] = { + { "Label", FIELD_LABEL }, + { "GroupBox", FIELD_GROUPBOX }, + { "Image", FIELD_IMAGE }, + { "Icon", FIELD_IMAGE }, // For compatibility w/ IO + { "Bitmap", FIELD_IMAGE }, // For compatibility w/ IO + { "Animation", FIELD_IMAGE }, // For compatibility w/ IO + { "ProgressBar", FIELD_PROGRESSBAR }, + { "Link", FIELD_LINK }, + { "CheckBox", FIELD_CHECKBOX }, + { "RadioButton", FIELD_RADIOBUTTON }, + { "Button", FIELD_BUTTON }, + { "UpDown", FIELD_UPDOWN }, + { "Text", FIELD_TEXT }, + { "Edit", FIELD_TEXT }, // Same as TEXT + { "Password", FIELD_TEXT }, + { "IPAddress", FIELD_IPADDRESS }, + { "RichText", FIELD_RICHTEXT }, + { "RichEdit", FIELD_RICHTEXT }, // Same as RICHTEXT + { "DropList", FIELD_COMBOBOX }, + { "ComboBox", FIELD_COMBOBOX }, + { "DateTime", FIELD_DATETIME }, + { "ListBox", FIELD_LISTBOX }, + { "ListView", FIELD_LISTVIEW }, + { "TreeView", FIELD_TREEVIEW }, + { "TrackBar", FIELD_TRACKBAR }, + { "MonthCalendar", FIELD_MONTHCALENDAR }, + { "HLINE", FIELD_HLINE }, + { "VLINE", FIELD_VLINE }, + { NULL, 0 } + }; + + // Control Flags + //-------------- + static TableEntry FlagTable[] = { + // All + { "DISABLED", FLAG_DISABLED }, + { "GROUP", FLAG_GROUP }, + { "NOTABSTOP", FLAG_NOTABSTOP }, + + // Image + { "RESIZETOFIT", FLAG_RESIZETOFIT }, + { "TRANSPARENT", FLAG_TRANSPARENT }, + + // ProgressBar + { "SMOOTH", FLAG_SMOOTH }, + { "VSCROLL", FLAG_VSCROLL }, + + // CheckBox/RadioButton + { "READONLY", FLAG_READONLY }, + { "3STATE", FLAG_3STATE }, // Except CheckBox + + // Button + { "OPEN_FILEREQUEST", FLAG_OPEN_FILEREQUEST }, + { "SAVE_FILEREQUEST", FLAG_SAVE_FILEREQUEST }, + { "REQ_SAVE", FLAG_SAVE_FILEREQUEST }, + { "DIRREQUEST", FLAG_DIRREQUEST }, + { "COLORREQUEST", FLAG_COLORREQUEST }, + { "FONTREQUEST", FLAG_FONTREQUEST }, + + { "FILE_MUST_EXIST", FLAG_FILE_MUST_EXIST }, + { "FILE_EXPLORER", FLAG_FILE_EXPLORER }, + { "FILE_HIDEREADONLY", FLAG_FILE_HIDEREADONLY }, + { "WARN_IF_EXIST", FLAG_WARN_IF_EXIST }, + { "PATH_MUST_EXIST", FLAG_PATH_MUST_EXIST }, + { "PROMPT_CREATE", FLAG_PROMPT_CREATE }, + + { "BITMAP", FLAG_BITMAP }, + { "ICON", FLAG_ICON }, + + // UpDown + { "HSCROLL", FLAG_HSCROLL }, + { "WRAP", FLAG_WRAP }, + + // Text/Password/RichText + { "ONLY_NUMBERS", FLAG_ONLYNUMBERS }, + { "MULTILINE", FLAG_MULTILINE }, + { "WANTRETURN", FLAG_WANTRETURN }, + { "NOWORDWRAP", FLAG_NOWORDWRAP }, +// { "HSCROLL", FLAG_HSCROLL }, +// { "VSCROLL", FLAG_VSCROLL }, +// { "READONLY", FLAG_READONLY }, + { "PASSWORD", FLAG_PASSWORD }, //Except Password + { "FOCUS", FLAG_FOCUS }, + + // DropList/ComboBox +// { "VSCROLL", FLAG_VSCROLL }, + { "DROPLIST", FLAG_DROPLIST }, //Except DropList + + // DateTime + { "UPDOWN", FLAG_UPDOWN }, + + // ListBox + { "MULTISELECT", FLAG_MULTISELECT }, + { "EXTENDEDSELECT", FLAG_EXTENDEDSELECT }, + { "EXTENDEDSELCT", FLAG_EXTENDEDSELECT }, + +// { "VSCROLL", FLAG_VSCROLL }, + + // ListView/TreeView + { "CHECKBOXES", FLAG_CHECKBOXES }, + { "EDITLABELS", FLAG_EDITLABELS }, +// { "MULTISELECT", FLAG_MULTISELECT }, + + { "LIST_VIEW", FLAG_LIST_VIEW }, + { "ICON_VIEW", FLAG_ICON_VIEW }, + { "SMALLICON_VIEW", FLAG_SMALLICON_VIEW }, + { "REPORT_VIEW", FLAG_REPORT_VIEW }, + + // TrackBar + { "NO_TICKS", FLAG_NO_TICKS }, +// { "VSCROLL", FLAG_VSCROLL }, + + // MonthCalendar + { "NOTODAY", FLAG_NOTODAY }, + { "WEEKNUMBERS", FLAG_WEEKNUMBERS }, + + // Null + { NULL, 0 } + }; + + // Control Alignation Flags + //------------------------- + static TableEntry AlignTable[] = { + { "LEFT", ALIGN_LEFT }, + { "CENTER", ALIGN_CENTER }, + { "RIGHT", ALIGN_RIGHT }, + { NULL, 0 } + }; + + static TableEntry VAlignTable[] = { + { "TOP", VALIGN_TOP }, + { "CENTER", VALIGN_CENTER }, + { "BOTTOM", VALIGN_BOTTOM }, + { NULL, 0 } + }; + + // Text Alignation Flags + //------------------------- + static TableEntry TxtAlignTable[] = { + { "LEFT", ALIGN_TEXT_LEFT }, + { "CENTER", ALIGN_TEXT_CENTER }, + { "RIGHT", ALIGN_TEXT_RIGHT }, + { "JUSTIFY", ALIGN_TEXT_JUSTIFY }, + { NULL, 0 } + }; + + static TableEntry TxtVAlignTable[] = { + { "TOP", VALIGN_TEXT_TOP }, + { "CENTER", VALIGN_TEXT_CENTER }, + { "BOTTOM", VALIGN_TEXT_BOTTOM }, + { "JUSTIFY", VALIGN_TEXT_JUSTIFY }, + { NULL, 0 } + }; + + // ToolTip Flags + //-------------- + static TableEntry ToolTipFlagTable[] = { + { "NOALWAYSTIP", TTS_ALWAYSTIP }, + { "BALLOON", TTS_BALLOON }, + { "NOANIMATE", TTS_NOANIMATE }, + { "NOFADE", TTS_NOFADE }, + { "NOPREFIX", TTS_NOPREFIX }, + { NULL, 0 } + }; + + // ToolTip Icon Flags + //------------------- + static TableEntry ToolTipIconTable[] = { + { "INFORMATION", 1 }, + { "EXCLAMATION", 2 }, + { "STOP", 3 }, + { NULL, 0 } + }; + + // Notification Flag Cursor Flags + //------------------------------- + // These below are resource numbers. Needs to use MAKEINTRESOURCE later. + static TableEntry NotifyCursorTable[] = { + { "APPSTARTING", 32650 }, + { "ARROW", 32512 }, + { "CROSS", 32515 }, + { "HAND", 32649 }, + { "HELP", 32651 }, + { "IBEAM", 32513 }, + { "NO", 32648 }, + { "SIZEALL", 32646 }, + { "SIZENESW", 32643 }, + { "SIZENS", 32645 }, + { "SIZENWSE", 32642 }, + { "SIZEWE", 32644 }, + { "UPARROW", 32516 }, + { "WAIT", 32514 }, + { NULL, 0 } + }; + + // Initialize pField + //-------------------------------------------------------------- + IOExControlStorage *pField = pFields + nIdx; + + pField->nControlID = 1200 + nIdx; + + // Initialize Field Settings + //-------------------------------------------------------------- + pField->nField = nCtrlIdx + 1; + wsprintf(szField, "Field %d", nCtrlIdx + 1); + pszAppName = szField; + + // Type + //-------------------------------------------------------------- + myGetProfileString("TYPE"); + pField->nType = LookupToken(TypeTable, szResult); + + if (pField->nType == FIELD_INVALID) + continue; + + // Flags + //-------------------------------------------------------------- + // This transforms the control type name to a flag, if the + // flag exists. + pField->nFlags = LookupToken(FlagTable, szResult); + + myGetProfileString("Flags"); + pField->nFlags |= LookupTokens(FlagTable, szResult); + + // Remove FLAG_CUSTOMDRAW_TEMP flag only when the user + // specifies any of the color commands. + switch(pField->nType) + { + case FIELD_LABEL: + case FIELD_LINK: + case FIELD_TEXT: + case FIELD_LISTBOX: + case FIELD_COMBOBOX: + case FIELD_GROUPBOX: + { + pField->nFlags |= FLAG_CUSTOMDRAW_TEMP; + break; + } + } + + // Notify + //-------------------------------------------------------------- + myGetProfileString("Notify"); + pField->nNotify |= LookupTokens(ControlNotifyTable, szResult); + + // NotifyCursor + //-------------------------------------------------------------- + myGetProfileString("NotifyCursor"); + pField->nNotifyCursor = LookupToken(NotifyCursorTable, szResult); + if(!pField->nNotifyCursor || pField->nNotifyCursor == 0) + if(pField->nType == FIELD_LINK) + pField->nNotifyCursor = 32649; //HAND + else + pField->nNotifyCursor = 32512; //ARROW + + // Align + //-------------------------------------------------------------- + myGetProfileString("Align"); + pField->nAlign = LookupToken(AlignTable, szResult); + if(bRTL) + { + if(pField->nAlign == ALIGN_LEFT) + pField->nAlign = ALIGN_RIGHT; + else + if(pField->nAlign == ALIGN_RIGHT) + pField->nAlign = ALIGN_LEFT; + } + + // VAlign + //-------------------------------------------------------------- + myGetProfileString("VAlign"); + pField->nVAlign = LookupToken(VAlignTable, szResult); + + // TxtAlign + //-------------------------------------------------------------- + myGetProfileString("TxtAlign"); + pField->nTxtAlign = LookupToken(TxtAlignTable, szResult); + if(bRTL) + { + if(pField->nTxtAlign == ALIGN_TEXT_LEFT) + pField->nTxtAlign = ALIGN_TEXT_RIGHT; + else + if(pField->nTxtAlign == ALIGN_TEXT_RIGHT) + pField->nTxtAlign = ALIGN_TEXT_LEFT; + } + + // TxtVAlign + //-------------------------------------------------------------- + myGetProfileString("TxtVAlign"); + pField->nTxtVAlign = LookupToken(TxtVAlignTable, szResult); + + // RefFields + //-------------------------------------------------------------- + pField->nRefFields = myGetProfileInt("RefFields", 0); + + // State + //-------------------------------------------------------------- + // pszState must not be NULL! + if (pField->nType == FIELD_TREEVIEW || pField->nType == FIELD_LISTVIEW || (pField->nType == FIELD_BUTTON && pField->nFlags & FLAG_FONTREQUEST && !pField->nRefFields)) + { + pField->pszState = myGetProfileListItemsDup("State", 0, pField->nType); + } + else + { + myGetProfileString("State"); + if(!szResult && (pField->nType == FIELD_BUTTON || pField->nType == FIELD_LINK) && pField->nFlags & FLAG_COLORREQUEST) + myitoa(szResult, GetSysColor(COLOR_WINDOW)); + pField->pszState = strdup(szResult); + } + + // ListItems + //-------------------------------------------------------------- + { + if ((pField->nType == FIELD_BUTTON || pField->nType == FIELD_LINK) && pField->nFlags & FLAG_DIRREQUEST) + pField->pszListItems = myGetProfileStringDup("ListItems"); + else + { + pField->pszListItems = myGetProfileListItemsDup("ListItems", 0, pField->nType); + } + } + + // Text + //-------------------------------------------------------------- + // Label Text - convert newline + pField->pszText = myGetProfileStringDup("TEXT"); + if (pField->nType == FIELD_LABEL || pField->nType == FIELD_LINK) + atoIO(pField->pszText); + + // Root + //-------------------------------------------------------------- + // Dir request - root folder + pField->pszRoot = myGetProfileStringDup("ROOT"); + + // ValidateText + //-------------------------------------------------------------- + // ValidateText - convert newline + pField->pszValidateText = myGetProfileStringDup("ValidateText"); + atoIO(pField->pszValidateText); + + // Filter + //-------------------------------------------------------------- + pField->pszFilter = myGetProfileFilterItemsDup("Filter"); + + // HeaderItems + //-------------------------------------------------------------- + pField->pszHeaderItems = myGetProfileListItemsDup("HeaderItems", 2, pField->nType); + + // HeaderItemsWidth + //-------------------------------------------------------------- + pField->pszHeaderItemsWidth = myGetProfileListItemsDup("HeaderItemsWidth", 2, pField->nType); + + // HeaderItemsAlign + //-------------------------------------------------------------- + pField->pszHeaderItemsAlign = myGetProfileListItemsDup("HeaderItemsAlign", 2, pField->nType); + + // StateImageList + //-------------------------------------------------------------- + pField->pszStateImageList = myGetProfileStringDup("StateImageList"); + + // SmallImageList + //-------------------------------------------------------------- + pField->pszSmallImageList = myGetProfileStringDup("SmallImageList"); + + // LargeImageList + //-------------------------------------------------------------- + pField->pszLargeImageList = myGetProfileStringDup("LargeImageList"); + + // Left + //-------------------------------------------------------------- + pField->RectUDU.left = myGetProfileInt("Left", 0); + + // Top + //-------------------------------------------------------------- + pField->RectUDU.top = myGetProfileInt("Top", 0); + + // Width + //-------------------------------------------------------------- + if(myGetProfileInt("Width", 0)) + pField->RectUDU.right = myGetProfileInt("Width", 0) + pField->RectUDU.left; + + // Height + //-------------------------------------------------------------- + if(myGetProfileInt("Height", 0)) + pField->RectUDU.bottom = myGetProfileInt("Height", 0) + pField->RectUDU.top; + + // Right + //-------------------------------------------------------------- + pField->RectUDU.right = myGetProfileInt("Right", 0); + + // Bottom + //-------------------------------------------------------------- + pField->RectUDU.bottom = myGetProfileInt("Bottom", 0); + + // MinLen + //-------------------------------------------------------------- + pField->nMinLength = myGetProfileInt("MinLen", 0); + + // MaxLen + //-------------------------------------------------------------- + pField->nMaxLength = myGetProfileInt("MaxLen", 0); + + // FontName + //-------------------------------------------------------------- + if(myGetProfileString("FontName")) + pField->pszFontName = myGetProfileStringDup("FontName"); + + // FontHeight + //-------------------------------------------------------------- + pField->nFontHeight = myGetProfileInt("FontHeight", 0); + + // FontWidth + //-------------------------------------------------------------- + pField->nFontWidth = myGetProfileInt("FontWidth", 0); + + // FontBold + //-------------------------------------------------------------- + if(myGetProfileInt("FontBold", 0)) + pField->nFontBold = TRUE; + else + pField->nFontBold = FALSE; + + // FontItalic + //-------------------------------------------------------------- + if(myGetProfileInt("FontItalic", 0)) + pField->nFontItalic = TRUE; + else + pField->nFontItalic = FALSE; + + // FontUnderline + //-------------------------------------------------------------- + if(myGetProfileInt("FontUnderline", 0)) + pField->nFontUnderline = TRUE; + else + pField->nFontUnderline = FALSE; + + // FontStrikeOut + //-------------------------------------------------------------- + if(myGetProfileInt("FontStrikeOut", 0)) + pField->nFontStrikeOut = TRUE; + else + pField->nFontStrikeOut = FALSE; + + // ListItemsHeight + //-------------------------------------------------------------- + pField->nListItemsHeight = myGetProfileInt("ListItemsHeight", 15); + + // TxtColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("TxtColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LABEL: + case FIELD_TEXT: + case FIELD_LISTBOX: + case FIELD_COMBOBOX: + case FIELD_TREEVIEW: + case FIELD_LISTVIEW: + pField->crTxtColor = GetSysColor(COLOR_WINDOWTEXT); + break; + case FIELD_MONTHCALENDAR: + case FIELD_DATETIME: + pField->crTxtColor = GetSysColor(COLOR_INFOTEXT); + break; +//Retain 0xFFFFFFFF for later: +// case FIELD_LINK: +// pField->crTxtColor = RGB(0,0,255); +// break; + case FIELD_PROGRESSBAR: + pField->crTxtColor = GetSysColor(COLOR_MENUHILIGHT); + break; + default: + pField->crTxtColor = myGetProfileInt("TxtColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crTxtColor = myGetProfileInt("TxtColor", 0xFFFFFFFF); + } + + // BgColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("BgColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_TEXT: + case FIELD_RICHTEXT: + case FIELD_LISTBOX: + case FIELD_COMBOBOX: + case FIELD_TREEVIEW: + case FIELD_LISTVIEW: + pField->crBgColor = GetSysColor(COLOR_WINDOW); + break; + case FIELD_DATETIME: + case FIELD_MONTHCALENDAR: + pField->crBgColor = GetSysColor(COLOR_INFOBK); + break; + default: + pField->crBgColor = myGetProfileInt("BgColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crBgColor = myGetProfileInt("BgColor", 0xFFFFFFFF); + } + + // ReadOnlyTxtColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("ReadOnlyTxtColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_TEXT: + pField->crReadOnlyTxtColor = GetSysColor(COLOR_WINDOWTEXT); + break; + default: + pField->crReadOnlyTxtColor = myGetProfileInt("ReadOnlyTxtColor", 0xFFFFFFFF); + break; + } + else + pField->crReadOnlyTxtColor = myGetProfileInt("ReadOnlyTxtColor", 0xFFFFFFFF); + + // ReadOnlyBgColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("ReadOnlyBgColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_TEXT: + case FIELD_RICHTEXT: + pField->crReadOnlyBgColor = GetSysColor(COLOR_3DFACE); + break; + default: + pField->crReadOnlyBgColor = myGetProfileInt("ReadOnlyBgColor", 0xFFFFFFFF); + break; + } + else + pField->crReadOnlyBgColor = myGetProfileInt("ReadOnlyBgColor", 0xFFFFFFFF); + + // SelTxtColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("SelTxtColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LISTBOX: + case FIELD_COMBOBOX: + case FIELD_MONTHCALENDAR: + case FIELD_DATETIME: + pField->crSelTxtColor = GetSysColor(COLOR_HIGHLIGHTTEXT); + break; + default: + pField->crSelTxtColor = myGetProfileInt("SelTxtColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crSelTxtColor = myGetProfileInt("SelTxtColor", 0xFFFFFFFF); + } + + // SelBgColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("SelBgColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LISTBOX: + case FIELD_COMBOBOX: + case FIELD_MONTHCALENDAR: + case FIELD_DATETIME: + pField->crSelBgColor = GetSysColor(COLOR_MENUHILIGHT); + break; + default: + pField->crSelBgColor = myGetProfileInt("SelBgColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crSelBgColor = myGetProfileInt("SelBgColor", 0xFFFFFFFF); + } + + // DisTxtColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("DisTxtColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LABEL: + case FIELD_LISTBOX: + case FIELD_COMBOBOX: + pField->crDisTxtColor = GetSysColor(COLOR_GRAYTEXT); + break; +//Retain 0xFFFFFFFF for later: +// case FIELD_LINK: +// pField->crDisTxtColor = RGB(0,0,100); +// break; + default: + pField->crDisTxtColor = myGetProfileInt("DisTxtColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crDisTxtColor = myGetProfileInt("DisTxtColor", 0xFFFFFFFF); + } + + // DisBgColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("DisBgColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LISTBOX: + pField->crDisBgColor = GetSysColor(COLOR_WINDOW); + break; + default: + pField->crDisBgColor = myGetProfileInt("DisBgColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crDisBgColor = myGetProfileInt("DisBgColor", 0xFFFFFFFF); + } + + // DisSelTxtColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("DisSelTxtColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LISTBOX: + pField->crDisSelTxtColor = RGB(255, 255, 255); + break; + default: + pField->crDisSelTxtColor = myGetProfileInt("DisSelTxtColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crDisSelTxtColor = myGetProfileInt("DisSelTxtColor", 0xFFFFFFFF); + } + + // DisSelBgColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("DisSelBgColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LISTBOX: + pField->crDisSelBgColor = RGB(170, 170, 170); + break; + default: + pField->crDisSelBgColor = myGetProfileInt("DisSelBgColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crDisSelBgColor = myGetProfileInt("DisSelBgColor", 0xFFFFFFFF); + } + + // TxtShwColor + //-------------------------------------------------------------- + pField->crTxtShwColor = (COLORREF)myGetProfileInt("TxtShwColor", 0xFFFFFFFF); + if (pField->crTxtShwColor != 0xFFFFFFFF) + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + + + // SelTxtShwColor + //-------------------------------------------------------------- + pField->crSelTxtShwColor = (COLORREF)myGetProfileInt("SelTxtShwColor", 0xFFFFFFFF); + if (pField->crSelTxtShwColor != 0xFFFFFFFF) + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + + // DisTxtShwColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("DisTxtShwColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_LABEL: + pField->crDisTxtShwColor = GetSysColor(COLOR_WINDOW); + break; +//Retain 0xFFFFFFFF for later: +// case FIELD_LINK: +// pField->crDisTxtShwColor = RGB(255, 255, 255); +// break; + default: + pField->crDisTxtShwColor = myGetProfileInt("DisTxtShwColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crDisTxtShwColor = myGetProfileInt("DisTxtShwColor", 0xFFFFFFFF); + } + + // DisSelTxtShwColor + //-------------------------------------------------------------- + pField->crDisSelTxtShwColor = (COLORREF)myGetProfileInt("DisSelTxtShwColor", 0xFFFFFFFF); + if (pField->crDisSelTxtShwColor != 0xFFFFFFFF) + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + + // MonthOutColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("MonthOutColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_MONTHCALENDAR: + pField->crMonthOutColor = GetSysColor(COLOR_WINDOW); + break; + default: + pField->crMonthOutColor = (COLORREF)myGetProfileInt("MonthOutColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crMonthOutColor = (COLORREF)myGetProfileInt("MonthOutColor", 0xFFFFFFFF); + } + + // MonthTrailingTxtColor + //-------------------------------------------------------------- + if((COLORREF)myGetProfileInt("MonthTrailingTxtColor", 0xFFFFFFFF) == 0xFFFFFFFF) + switch(pField->nType) { + case FIELD_MONTHCALENDAR: + pField->crMonthTrailingTxtColor = GetSysColor(COLOR_GRAYTEXT); + break; + default: + pField->crMonthTrailingTxtColor = (COLORREF)myGetProfileInt("MonthTrailingTxtColor", 0xFFFFFFFF); + break; + } + else + { + if (pField->nFlags & FLAG_CUSTOMDRAW_TEMP) + pField->nFlags &= ~FLAG_CUSTOMDRAW_TEMP; + pField->crMonthTrailingTxtColor = (COLORREF)myGetProfileInt("MonthTrailingTxtColor", 0xFFFFFFFF); + } + + // ToolTipText + //-------------------------------------------------------------- + pField->pszToolTipText = myGetProfileStringDup("ToolTipText"); + + // "ToolTipText" has to exist in order to have a ToolTip + if(pField->pszToolTipText) { + + // ToolTipFlags + //-------------------------------------------------------------- + pField->nToolTipFlags = LookupToken(ToolTipFlagTable, szResult); + myGetProfileString("ToolTipFlags"); + pField->nToolTipFlags |= LookupTokens(ToolTipFlagTable, szResult); + + if(pField->nToolTipFlags & TTS_BALLOON) { + // ToolTipIcon + //-------------------------------------------------------------- + myGetProfileString("ToolTipIcon"); + pField->nToolTipIcon = LookupToken(ToolTipIconTable, szResult); + + // ToolTipTitle + //-------------------------------------------------------------- + pField->pszToolTipTitle = myGetProfileStringDup("ToolTipTitle"); + } + // ToolTipTxtColor + //-------------------------------------------------------------- + pField->crToolTipTxtColor = (COLORREF)myGetProfileInt("ToolTipTxtColor", GetSysColor(COLOR_INFOTEXT)); + + // ToolTipBgColor + //-------------------------------------------------------------- + pField->crToolTipBgColor = (COLORREF)myGetProfileInt("ToolTipBgColor", GetSysColor(COLOR_INFOBK)); + + // ToolTipMaxWidth + //-------------------------------------------------------------- + pField->nToolTipMaxWidth = myGetProfileInt("ToolTipMaxWidth", 300); + } + } + + return nNumFields; +} + + + + + + + + + + + +bool WINAPI SaveSettings(void) { + +// Initialize Variables +//================================================================ + static char szField[25]; + int nBufLen = g_nBufferSize; + char *pszBuffer = (char*)MALLOC(nBufLen); + if (!pszBuffer) return false; + + int nIdx; + int CurrField; + +// Save Settings For Each Field Existant +//================================================================ + for (nIdx = 0, CurrField = 1; nIdx < nNumFields; nIdx++, CurrField++) { + + // Define pField + //-------------------------------------------------------------- + IOExControlStorage *pField = pFields + nIdx; + HWND hwnd = pField->hwnd; + + // Save Settings (By Control Type) + //-------------------------------------------------------------- + switch (pField->nType) { + + // Invalid + //-------- + default: + continue; + + // CheckBox, RadioButton + //---------------------- + case FIELD_CHECKBOX: + case FIELD_RADIOBUTTON: + { + myltoa(pszBuffer, mySendMessage(hwnd, BM_GETCHECK, 0, 0)); + break; + } + + // ListBox + //-------- + case FIELD_LISTBOX: + { + // Ok, this one requires a bit of work. + // First, we allocate a buffer long enough to hold every item. + // Then, we loop through every item and if it's selected we add it to our buffer. + // If there is already an item in the list, then we prepend a | character before the new item. + // We could simplify for single-select boxes, but using one piece of code saves some space. + int nLength = strlen(pField->pszListItems) + 10; + if (nLength > nBufLen) { + FREE(pszBuffer); + nBufLen = nLength; + pszBuffer = (char*)MALLOC(nBufLen); + if (!pszBuffer) return false; + } + char *pszItem = (char*)MALLOC(nBufLen); + if (!pszItem) return false; + + *pszBuffer = '\0'; + int nNumItems = mySendMessage(hwnd, LB_GETCOUNT, 0, 0); + for (int nIdx2 = 0; nIdx2 < nNumItems; nIdx2++) { + if (mySendMessage(hwnd, LB_GETSEL, nIdx2, 0) > 0) { + if (*pszBuffer) lstrcat(pszBuffer, "|"); + mySendMessage(hwnd, LB_GETTEXT, (WPARAM)nIdx2, (LPARAM)pszItem); + lstrcat(pszBuffer, pszItem); + } + } + + FREE(pszItem); + break; + } + + // Text, ComboBox + //--------------- + case FIELD_TEXT: + case FIELD_COMBOBOX: + { + //we should only add the final '\"' after IOtoa is called. + GetWindowText(hwnd, pszBuffer, nBufLen); + + //Re-allocate, truncate, and add '\"' to the string. + char* pszBuf2 = (char*)MALLOC(nBufLen+2); + + *pszBuf2='\"'; + + if (pField->nType == FIELD_TEXT && (pField->nFlags & FLAG_MULTILINE)) + strncpy(pszBuf2+1, IOtoa(pszBuffer), nBufLen); + else + strcpy(pszBuf2+1, pszBuffer); + + FREE(pszBuffer); + + int nLength = strlen(pszBuf2); + pszBuf2[nLength]='\"'; + pszBuf2[nLength+1]='\0'; + + pszBuffer=pszBuf2; + break; + } + + // RichText + //--------- + case FIELD_RICHTEXT: + { + // Only if not read only + if ( (pField->nFlags & FLAG_READONLY) == 0) + { + strcpy(pszBuffer, pField->pszState); + + // Step 1: Open the user file + EDITSTREAM editStream; + char *pszData = (char*)MALLOC(g_nBufferSize); + if (!pszData) + { + //TODO: Add error handling + break; + } + + dwRead=0; + + editStream.pfnCallback = FIELD_RICHTEXT_StreamOut; + editStream.dwCookie = (DWORD)pszData; + mySendMessage(hwnd,EM_STREAMOUT,(WPARAM)pField->nDataType,(LPARAM)&editStream); + +#ifdef USE_SECURE_FUNCTIONS + FILE * hFile; + if ( fopen_s(&hFile, pField->pszState, "wb") != 0) +#else + FILE * hFile = fopen(pField->pszState, "wb"); + if(!(hFile)) +#endif + { + //TODO: Add error handling + FREE(pszData); + break; + } + + UINT nDataLen = (UINT)strlen(pszData); + fwrite(pszData,1,nDataLen,hFile); + fclose(hFile); + FREE(pszData); + } + break; + } + + // TreeView + //--------- + case FIELD_TREEVIEW: + { + LPSTR pszBuf2 = (LPSTR)MALLOC(260*2+1); + LPSTR pszBuf3 = (LPSTR)MALLOC(260*2+1); + + if(pField->nFlags & FLAG_CHECKBOXES) + { + bool bFinishing = FALSE; + TVITEM tvItem; + HTREEITEM hItem = TreeView_GetRoot(hwnd); + while(hItem) + { + tvItem.mask = TVIF_PARAM; + tvItem.hItem = hItem; + TreeView_GetItem(hwnd, &tvItem); + + if(!bFinishing) + { + myitoa(pszBuf2, tvItem.lParam); + + if(TreeView_GetChild(hwnd, hItem)) + lstrcat(pszBuf2, "{"); + else if(TreeView_GetNextSibling(hwnd, hItem)) + lstrcat(pszBuf2, "|"); + else if(TreeView_GetParent(hwnd, hItem)) + lstrcat(pszBuf2, "}"); + } + else + { + if(TreeView_GetParent(hwnd, hItem)) + strcpy(pszBuf2,"}"); + else + strcpy(pszBuf2,""); + } + + lstrcat(pszBuffer, pszBuf2); + + if(TreeView_GetChild(hwnd, hItem) && bFinishing == FALSE) + hItem = TreeView_GetChild(hwnd, hItem); + else if(TreeView_GetNextSibling(hwnd, hItem) && bFinishing == FALSE) + hItem = TreeView_GetNextSibling(hwnd, hItem); + else if(TreeView_GetParent(hwnd, hItem)) + { + bFinishing = FALSE; + hItem = TreeView_GetParent(hwnd, hItem); + if(TreeView_GetNextSibling(hwnd, hItem)) + hItem = TreeView_GetNextSibling(hwnd, hItem); + else + { + bFinishing = TRUE; + } + } + else + { + bFinishing = FALSE; + break; + } + } + } + // No CHECKBOXES Flag + else + { + HTREEITEM hItem = TreeView_GetSelection(hwnd); + int i = 0; + + TVITEM tvi; + tvi.mask = TVIF_TEXT; + tvi.pszText = pszBuf2; + tvi.cchTextMax = 260; + + while(hItem) + { + ++i; + tvi.hItem = hItem; + + TreeView_GetItem(hwnd, &tvi); + + pszBuf2 = tvi.pszText; + + pszBuf2 = IOLItoLI(pszBuf2, 260*2+1, pField->nType); + strcpy(pszBuf3, pszBuf2); + + if(i!=1) + { + lstrcat(pszBuf3, "{"); + lstrcat(pszBuf3, pszBuffer); + lstrcat(pszBuf3, "}"); + } + + strcpy(pszBuffer, pszBuf3); + + hItem = TreeView_GetParent(hwnd, hItem); + } + } + + // Return "ListItems" + + if(pField->nFlags & FLAG_EDITLABELS) + { + strcpy(pszBuf3, ""); + strcpy(pszBuf2, ""); + strcpy(pField->pszListItems, ""); + bool bFinishing = FALSE; + TVITEM tvItem; + HTREEITEM hItem = TreeView_GetRoot(hwnd); + + while(hItem) + { + tvItem.mask = TVIF_TEXT; + tvItem.hItem = hItem; + tvItem.pszText = pszBuf2; + tvItem.cchTextMax = 260; + TreeView_GetItem(hwnd, &tvItem); + + pszBuf3 = tvItem.pszText; + + pszBuf2 = IOLItoLI(pszBuf2, 260*2+1, pField->nType); + strcpy(pszBuf3, pszBuf2); + + if(!bFinishing) + { + if(TreeView_GetChild(hwnd, hItem)) + lstrcat(pszBuf3, "{"); + else if(TreeView_GetNextSibling(hwnd, hItem)) + lstrcat(pszBuf3, "|"); + else if(TreeView_GetParent(hwnd, hItem)) + lstrcat(pszBuf3, "}"); + } + else + { + if(TreeView_GetParent(hwnd, hItem)) + strcpy(pszBuf3,"}"); + else + strcpy(pszBuf3,""); + } + + lstrcat(pField->pszListItems, pszBuf2); + + if(TreeView_GetChild(hwnd, hItem) && bFinishing == FALSE) + hItem = TreeView_GetChild(hwnd, hItem); + else if(TreeView_GetNextSibling(hwnd, hItem) && bFinishing == FALSE) + hItem = TreeView_GetNextSibling(hwnd, hItem); + else if(TreeView_GetParent(hwnd, hItem)) + { + bFinishing = FALSE; + hItem = TreeView_GetParent(hwnd, hItem); + if(TreeView_GetNextSibling(hwnd, hItem)) + hItem = TreeView_GetNextSibling(hwnd, hItem); + else + { + bFinishing = TRUE; + } + } + else + { + bFinishing = FALSE; + break; + } + } + + wsprintf(szField, "Field %d", CurrField); + WritePrivateProfileString(szField, "ListItems", pField->pszListItems, pszFilename); + } + FREE(pszBuf3); + FREE(pszBuf2); + + break; + } + + // ListView + //--------- + case FIELD_LISTVIEW: + { + LPSTR pszBuf2 = (LPSTR)MALLOC(260*2+1); + LPSTR pszBuf3 = (LPSTR)MALLOC(260*2+1); + + // Step 1: Detect number of subitems per item to retrieve. + int nColumns = FIELD_LISTVIEW_IOLI_CountSubItems(pField->pszListItems, pField->pszHeaderItems) + 1; // Plus an item column. + + if(pField->nFlags & FLAG_CHECKBOXES) + { + int iItem = -1; + bool bFinishing = FALSE; + + // Step 2: Create output data. + while(true) + { + iItem++; + // It's the same as: + // iItem = ListView_GetNextItem(hwnd, iItem, 0); + + // Step 2.1: Return "State" + //----------------------- + int iState = (ListView_GetItemState(hwnd, iItem, LVIS_STATEIMAGEMASK)>>12); + + myitoa(pszBuf2, TREEVIEW_UNCHECKED); + + // No checkboxes (TREEVIEW_NOCHECKBOX) + //------------------------------------ + if(iState == 0) + myitoa(pszBuf2, TREEVIEW_NOCHECKBOX); + + // Read-only checkboxes (TREEVIEW_READONLY) + //----------------------------------------- + else + if(iState == 6) + myitoa(pszBuf2, TREEVIEW_READONLY | TREEVIEW_CHECKED); + else + if(iState == 5) + myitoa(pszBuf2, TREEVIEW_READONLY | TREEVIEW_UNCHECKED); + // Checked checkboxes (TREEVIEW_CHECKED) + //----------------------------------------- + else + if(iState == 3) + myitoa(pszBuf2, TREEVIEW_CHECKED); + else + if(iState == 2) + myitoa(pszBuf2, TREEVIEW_UNCHECKED); + + if(ListView_GetNextItem(hwnd, iItem, 0)!=-1) + lstrcat(pszBuf2, "|"); + + lstrcat(pszBuffer, pszBuf2); + + if(ListView_GetNextItem(hwnd, iItem, 0)==-1) + break; + } + } + else + { + int iSelectedItem = -1; + int i = 0; + + while((iSelectedItem = ListView_GetNextItem(hwnd, iSelectedItem, LVNI_ALL | LVNI_SELECTED)) > -1) + { + int nSelected = ListView_GetItemState(hwnd, iSelectedItem, LVIS_SELECTED); + if (nSelected == LVIS_SELECTED) + { + ListView_GetItemText(hwnd, iSelectedItem, 0, pszBuf2, 260); + pszBuf2 = IOLItoLI(pszBuf2, 260*2+1, pField->nType); + + if(i!=0) + lstrcat(pszBuffer, "|"); + lstrcat(pszBuffer, pszBuf2); + } + i++; + } + } + + // Return "ListItems" + + if(pField->nFlags & FLAG_EDITLABELS) + { + strcpy(pszBuf2,""); + strcpy(pszBuf3,""); + strcpy(pField->pszListItems, ""); + + int iItem = -1; + while(true) + { + ++iItem; + ListView_GetItemText(hwnd, iItem, 0, pszBuf2, 260); + + pszBuf2 = IOLItoLI(pszBuf2, 260*2+1, pField->nType); + + lstrcat(pField->pszListItems, pszBuf2); + + int iSubItem = 1; + int nStWritten = 0; + if(nColumns-1 > 0) + { + strcpy(pszBuf2, ""); + strcpy(pszBuf3, "{"); + + while(true) + { + ListView_GetItemText(hwnd, iItem, iSubItem, pszBuf2, 260) + pszBuf2 = IOLItoLI(pszBuf2, 260*2+1, pField->nType); + + if(lstrcmp(pszBuf2,"")==0) + { + if(iSubItem > 1) + lstrcat(pszBuf3, "|"); + } + else + { + nStWritten = 1; + lstrcat(pField->pszListItems, pszBuf3); + if(iSubItem > 1) + lstrcat(pField->pszListItems, "|"); + lstrcat(pField->pszListItems, pszBuf2); + strcpy(pszBuf3, ""); + } + + ++iSubItem; + if(iSubItem > nColumns) + { + strcpy(pszBuf2, ""); + strcpy(pszBuf3, ""); + if(nStWritten) + lstrcat(pField->pszListItems, "}"); + break; + } + } + } + if(ListView_GetNextItem(hwnd, iItem, 0)!=-1 && !nStWritten) + lstrcat(pField->pszListItems, "|"); + + if(ListView_GetNextItem(hwnd, iItem, 0)==-1) + break; + } + wsprintf(szField, "Field %d", CurrField); + WritePrivateProfileString(szField, "ListItems", pField->pszListItems, pszFilename); + } + + FREE(pszBuf2); + FREE(pszBuf3); + + break; + } + + // ProgressBar + //------------ + case FIELD_PROGRESSBAR: + { + int nProgressState = mySendMessage(hwnd, PBM_GETPOS, 0, 0); + myitoa(pszBuffer, nProgressState); + break; + } + + // TrackBar + //--------- + case FIELD_TRACKBAR: + { + int nTrackState = mySendMessage(hwnd, TBM_GETPOS, 0, 0); + myitoa(pszBuffer, nTrackState); + break; + } + + // IPAddress + //---------- + case FIELD_IPADDRESS: + { + DWORD nIPAddressState; + + mySendMessage(hwnd, IPM_GETADDRESS, 0, (LPARAM) &nIPAddressState); + BYTE nField0 = (BYTE) FIRST_IPADDRESS(nIPAddressState); + BYTE nField1 = (BYTE) SECOND_IPADDRESS(nIPAddressState); + BYTE nField2 = (BYTE) THIRD_IPADDRESS(nIPAddressState); + BYTE nField3 = (BYTE) FOURTH_IPADDRESS(nIPAddressState); + + wsprintf(pszBuffer, "%d.%d.%d.%d", nField0, nField1, nField2, nField3); + break; + } + + // DateTime + //--------- + case FIELD_DATETIME: + { + SYSTEMTIME lpSysTime; + + mySendMessage(hwnd, DTM_GETSYSTEMTIME, 0, (LPARAM) &lpSysTime); + const SYSTEMTIME lpSysTime2 = lpSysTime; + + char* pszDate = (char*)MALLOC(10+1); + char* pszDayOfWeek = (char*)MALLOC(3+1); + char* pszTime = (char*)MALLOC(8+1); + + GetDateFormat(MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),SORT_DEFAULT), NULL, &lpSysTime2, "dd/MM/yyyy", pszDate, 11); + GetDateFormat(MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),SORT_DEFAULT), NULL, &lpSysTime2, "ddd", pszDayOfWeek, 4); + GetTimeFormat(MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),SORT_DEFAULT), NULL, &lpSysTime2, "HH:mm:ss", pszTime, 9); + + int nDayOfWeek = 6; + + if(pszDayOfWeek == "Sun") + nDayOfWeek = 0; + else if(pszDayOfWeek == "Mon") + nDayOfWeek = 1; + else if(pszDayOfWeek == "Tue") + nDayOfWeek = 2; + else if(pszDayOfWeek == "Wed") + nDayOfWeek = 3; + else if(pszDayOfWeek == "Thu") + nDayOfWeek = 4; + else if(pszDayOfWeek == "Fri") + nDayOfWeek = 5; + else if(pszDayOfWeek == "Sat") + nDayOfWeek = 6; + + wsprintf(pszBuffer, "%hs %hs %d", pszDate, pszTime, nDayOfWeek); + + FREE(pszDate); + FREE(pszDayOfWeek); + FREE(pszTime); + break; + } + + // MonthCalendar + //-------------- + case FIELD_MONTHCALENDAR: + { + SYSTEMTIME lpSysTime; + mySendMessage(hwnd, MCM_GETCURSEL, 0, (LPARAM) &lpSysTime); + const SYSTEMTIME lpSysTime2 = lpSysTime; + GetDateFormat(LOCALE_USER_DEFAULT, NULL,&lpSysTime2, "dd/MM/yyyy", pszBuffer, 11); + break; + } + + // UpDown + //------- + case FIELD_UPDOWN: + { + if(!pField->nRefFields) + { + int nUpDownState = mySendMessage(hwnd, UDM_GETPOS32, 0, 0); + wsprintf(pszBuffer, "%d", nUpDownState); + } + else + continue; + break; + } + + // Link, Button + //------------- + case FIELD_LINK: + case FIELD_BUTTON: + { + // Copy the State of the refFields in the its State + if (lstrcmp(pField->pszState,"")==0 && pField->nRefFields != 0) + { + GetWindowText(GetDlgItem(hConfigWindow,1200+pField->nRefFields-1), pszBuffer, g_nBufferSize); + } + else + if(lstrcmp(pField->pszState,"")!=0 && (pField->nFlags & FLAG_OPEN_FILEREQUEST || pField->nFlags & FLAG_SAVE_FILEREQUEST || pField->nFlags & FLAG_DIRREQUEST)) + { + lstrcpyn(pszBuffer, (LPSTR)pField->pszState, g_nBufferSize); + } + else + if(lstrcmp(pField->pszState,"")!=0 && pField->nFlags & FLAG_FONTREQUEST) + { + lstrcpyn(pszBuffer, (LPSTR)pField->pszState, g_nBufferSize); + pszBuffer = IOLItoLI(pszBuffer, lstrlen(pszBuffer)+1, pField->nType); + } + else + if(pField->nFlags & FLAG_COLORREQUEST) + { + lstrcpyn(pszBuffer, (LPSTR)pField->pszListItems, g_nBufferSize); + pszBuffer = IOLItoLI(pszBuffer, lstrlen(pszBuffer)+1, pField->nType); + wsprintf(szField, "Field %d", CurrField); + WritePrivateProfileString(szField, "ListItems", pszBuffer, pszFilename); + + lstrcpyn(pszBuffer, (LPSTR)pField->pszState, g_nBufferSize); + } + else + continue; + break; + } + } + // Write Back To INI File The Control State + //-------------------------------------------------------------- + wsprintf(szField, "Field %d", CurrField); + WritePrivateProfileString(szField, "State", pszBuffer, pszFilename); + } + +// Write To INI File What Field and Notification Were Activated +//================================================================ + if(g_aNotifyQueue[0].iField > 0) + { + myitoa(pszBuffer, g_aNotifyQueue[0].iField); + WritePrivateProfileString("Settings", "State", pszBuffer, pszFilename); + } + if(g_aNotifyQueue[0].iNotifyId != NOTIFY_NONE && g_aNotifyQueue[0].bNotifyType == NOTIFY_TYPE_CONTROL) + WritePrivateProfileString("Settings", "Notify", LookupTokenName(ControlNotifyTable, g_aNotifyQueue[0].iNotifyId), pszFilename); + + FREE(pszBuffer); + return true; +} + + + + + + + + + + +//Other Functions +bool INLINE ValidateFields() { + int nIdx; + int nLength; + +// "MaxLen" and "MinLen" Implementations (Only For Text) +//================================================================ + // In the unlikely event we can't allocate memory, go ahead and return true so we can get out of here. + // May cause problems for the install script, but no memory is problems for us. + for (nIdx = 0; nIdx < nNumFields; nIdx++) { + IOExControlStorage *pField = pFields + nIdx; + + // Only check controls that are after FIELD_CHECKLEN + //-------------------------------------------------------------- + // this if statement prevents a stupid bug where a min/max length is assigned to a label control + // where the user obviously has no way of changing what is displayed. (can you say, "infinite loop"?) + if (pField->nType >= FIELD_CHECKLEN) { + nLength = mySendMessage(pField->hwnd, WM_GETTEXTLENGTH, 0, 0); + + if (((pField->nMaxLength > 0) && (nLength > pField->nMaxLength)) || + ((pField->nMinLength > 0) && (nLength < pField->nMinLength))) { + + // "ValidateText" Implementation + if (pField->pszValidateText) { + char szTitle[1024]; + GetWindowText(hMainWindow, szTitle, sizeof(szTitle)); + MessageBox(hConfigWindow, pField->pszValidateText, szTitle, MB_OK|MB_ICONWARNING); + } + mySetFocus(pField->hwnd); + return false; + } + } + } + return true; +} diff --git a/installer/NSIS/Contrib/InstallOptionsEx/InstallerOptions.h b/installer/NSIS/Contrib/InstallOptionsEx/InstallerOptions.h new file mode 100644 index 0000000..43b2c58 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/InstallerOptions.h @@ -0,0 +1,1691 @@ +// NOTES: +// - pFilenameStackEntry was removed. Not necessary. + +//================================================================ +// Header Implementation +//================================================================ + +// File Inclusions +//================================================================ +#include +#include +#include +#include +#include +#include +#include +#include +#include "resource.h" +#include "shellapi.h" + +#define popstring dontuseme +#include "exdll.h" +#undef popstring + +// Defines Based on File Inclusions +//================================================================ +#ifndef IDC_HAND + #define IDC_HAND MAKEINTRESOURCE(32649) +#endif + +#ifndef BS_TYPEMASK + #define BS_TYPEMASK 0x0000000FL +#endif + +#ifndef USER_TIMER_MAXIMUM + #define USER_TIMER_MAXIMUM 0x7FFFFFFF +#endif +#ifndef USER_TIMER_MINIMUM + #define USER_TIMER_MINIMUM 0x0000000A +#endif + +#ifndef COLOR_MENUHILIGHT + #define COLOR_MENUHILIGHT 29 +#endif + +// Compiler Options +//================================================================ + +// Force INLINE code +//---------------------------------------------------------------- +// Use for functions only called from one place to possibly reduce some code +// size. Allows the source code to remain readable by leaving the function +// intact. +#ifdef _MSC_VER + #define INLINE __forceinline +#else + #define INLINE inline +#endif + +//================================================================ +// Classes Definitions +//================================================================ + +// NotifyQueue (Allows notification messages to have queue) +//================================================================ +struct NotifyQueue { + int iNotifyId; + bool bNotifyType; // 0 = page, 1 = control. Page notifications are hardcodedly handled by the plug-in. + int iField; +}; + +// TableEntry (Table used to convert strings to numbers) +//================================================================ +struct TableEntry { + char *pszName; + int nValue; +}; + +// IOExWindowStorage (Storage structure for window information) +//================================================================ +// all allocated buffers must be first in the struct +// when adding more allocated buffers to IOExControlStorage, don't forget to change this define +#define IOEX_WINDOW_BUFFERS 4 +struct IOExWindowStorage { + LPSTR pszTitle; + LPSTR pszState; //Window state. + LPSTR pszNotify; //Set which window notifications to use. + LPSTR pszNotifyResult; //INI Files change: Notify->NotifyResult. + //Idea: Maybe I should add another "Notify" key name so that it could + //be used for selecting which page notification should be accepted, and + //then use the NOTIFY_TIMEOUT (for example) so that the "nTimeOut" be activated. + int nRect; + int bRTL; + int iMUnit; + int nTimeOut; + + int iDlgResource; //If used, then pszRect is used. Default is 105. + + RECT RectUDU; + RECT RectPx; +}; + +// IOExControlStorage (Storage structure for control information) +//================================================================ +// all allocated buffers must be first in the struct +// when adding more allocated buffers to IOExControlStorage, don't forget to change this define +#define FIELD_BUFFERS 15 +struct IOExControlStorage { + char *pszText; + char *pszState; + char *pszRoot; + + char *pszListItems; + char *pszFilter; + + char *pszValidateText; + + TCHAR *pszFontName; + + char *pszToolTipText; + char *pszToolTipTitle; + + char *pszHeaderItems; + char *pszHeaderItemsWidth; + char *pszHeaderItemsAlign; + + char *pszStateImageList; + char *pszSmallImageList; + char *pszLargeImageList; + + int nRefFields; + + int nMinLength; + int nMaxLength; + + int nFontHeight; + int nFontWidth; + bool nFontBold; + bool nFontItalic; + bool nFontUnderline; + bool nFontStrikeOut; + + int nListItemsHeight; + + COLORREF crTxtColor; + COLORREF crBgColor; + COLORREF crSelTxtColor; + COLORREF crSelBgColor; + + COLORREF crReadOnlyTxtColor; + COLORREF crReadOnlyBgColor; + + COLORREF crDisTxtColor; + COLORREF crDisBgColor; + COLORREF crDisSelTxtColor; + COLORREF crDisSelBgColor; + + COLORREF crTxtShwColor; + COLORREF crSelTxtShwColor; + + COLORREF crDisTxtShwColor; + COLORREF crDisSelTxtShwColor; + + COLORREF crMonthOutColor; + COLORREF crMonthTrailingTxtColor; + + int nType; + RECT RectUDU; + RECT RectPx; + + int nFlags; + int nNotify; + int nNotifyCursor; + int nAlign; + int nVAlign; + int nTxtAlign; + int nTxtVAlign; + + HWND hwnd; + UINT nControlID; + + HWND hwToolTip; + int nToolTipFlags; + int nToolTipIcon; + COLORREF crToolTipTxtColor; + COLORREF crToolTipBgColor; + int nToolTipMaxWidth; + + int nParentIdx; // this is used to store original windowproc. + int nDataType; // used by FIELD_RICHTEXT and FIELD_IMAGE. + LPPICTURE nImageInterface; // used by FIELD_IMAGE controls for gif, jpeg and wmf files. + HANDLE hImage; // this is used by image field to save the handle to the image. + HIMAGELIST hStateImageList; // handle to state image list. + //HIMAGELIST hSmallImageList; + //HIMAGELIST hLargeImageList; + HWND hEditControl; // handle to a temporary edit control for controls which use FLAG_EDITLABELS flag. + + int nField; + + UINT nWindowID; //Connects with the the IOExWindowStorage structure. +}; + +//TODO: Remove 9 key names to disable, enable or change buttons text. Reason: This is +// located outside of the InstallOptionsEx window, so it shouldn't touch it. +// For compatibility purposes, a specific function should be created that does this. + +//TODO: Add Tooltip control feature custom positioning of the tooltip itself in the dialog. +//TODO: Make Tooltip commands separate from the control individual and make them individual controls. + +//TODO: Add flags for Image control to determine image file types. + +//================================================================ +// Variables/Defines Definitions +//================================================================ + +// Plug-in +//================================================================ + +// General +//---------------------------------------------------------------- +HINSTANCE m_hInstance = NULL; + +int initCalled; //If the page initialization was called before + +// Message Loop +//---------------------------------------------------------------- + +// Misc. +MSG msg; //message previous than the current one +int g_done; // specifies if the message loop should end + +// Notification Flags Queue +#define NOTIFY_QUEUE_AMOUNT_MAX 2 +#define NOTIFY_TYPE_PAGE 0 +#define NOTIFY_TYPE_CONTROL 1 +int g_nNotifyQueueAmountMax; +NotifyQueue *g_aNotifyQueue = NULL; // MaxItems = NOTIFY_QUEUE_AMOUNT_MAX +NotifyQueue *g_aNotifyQueueTemp = NULL; // MaxItems = 1 + +// Determine direction of page change +int g_is_cancel,g_is_back,g_is_timeout; + +// Config file +//================================================================ +char *pszFilename = NULL; // File name +char *szResult; // Key Name (Temporary) +char *pszAppName; // Section Name (Temporary) + +// Dialog +//================================================================ + +// NSIS Window +//---------------------------------------------------------------- + +// Properties + +// / Window handles +HWND hMainWindow = NULL; +HWND hConfigWindow = NULL; //Dialog Window + +// / Main window button handles +HWND hCancelButton = NULL; +HWND hNextButton = NULL; +HWND hBackButton = NULL; + +// Settings + +// / Button texts +char *pszCancelButtonText = NULL; // CancelButtonText +char *pszNextButtonText = NULL; // NextButtonText +char *pszBackButtonText = NULL; // BackButtonText + +// / Enable buttons? +int bNextEnabled = FALSE; // NextEnabled +int bBackEnabled = FALSE; // BackEnabled +int bCancelEnabled = FALSE; // CancelEnabled + +// / Show buttons? +int bNextShow = FALSE; // NextShow +int bBackShow = FALSE; // BackShow +int bCancelShow = FALSE; // CancelShow + +// Temporary + +// Were buttons on previous page shown? +int old_cancel_visible; +int old_back_visible; +int old_next_visible; + +// Were buttons on previous page enabled? +int old_cancel_enabled; +int old_back_enabled; +int old_next_enabled; + +// Previous page callback +static void *lpWndProcOld; + +// IOEx Window Related +//---------------------------------------------------------------- + +// Controls Storage +IOExControlStorage *pFields = NULL; + +// Temporary Variables + +// / Control Field +char *pszTitle = NULL; //temp variable - probably not needed w/ the new classes + +// / Timer +int nTimeOutTemp; +UINT_PTR g_timer_id = 14; // This can be any number except 0. +DWORD g_timer_cur_time; +bool g_timer_activated; + +// Variable Buffer Size +// - Should be > MAX_PATH to prevent excessive growing. +// - Includes last '\0' character. +// - Default supports most of controls needs. +#define BUFFER_SIZE 8192 // 8KB + +// Default Rectangle Number +#define DEFAULT_RECT 1018 + +// Default Control Styles +#define DEFAULT_STYLES (WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS) +#define RTL_EX_STYLES (WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR) + +// Page Settings +int g_nBufferSize; // BufferSize +int bMUnit = 0; // MUnit +int bRTL = 0; // RTL +int nRectId = 0; // RectId +int nNumFields = 0; // NumFields (not sure if to continue compatibility) +int nTimeOut; // TimeOut + +// Control Settings + +// / Type Flags + +//================================================================ +// Field Types Definitions +//================================================================ +// NB - the order of this list is important - see below + +#define FIELD_INVALID (0) +#define FIELD_HLINE (1) +#define FIELD_VLINE (2) +#define FIELD_LABEL (3) +#define FIELD_GROUPBOX (4) +#define FIELD_IMAGE (5) +#define FIELD_PROGRESSBAR (6) +#define FIELD_LINK (7) +#define FIELD_BUTTON (8) +#define FIELD_UPDOWN (9) +#define FIELD_CHECKBOX (10) +#define FIELD_RADIOBUTTON (11) +#define FIELD_TEXT (12) +#define FIELD_IPADDRESS (13) +#define FIELD_RICHTEXT (14) +#define FIELD_COMBOBOX (15) +#define FIELD_DATETIME (16) +#define FIELD_LISTBOX (17) +#define FIELD_LISTVIEW (18) +#define FIELD_TREEVIEW (19) +#define FIELD_TRACKBAR (20) +#define FIELD_MONTHCALENDAR (21) + +#define FIELD_SETFOCUS FIELD_CHECKBOX // First field that qualifies for having the initial keyboard focus +#define FIELD_CHECKLEN FIELD_TEXT // First field to have length of state value checked against MinLen/MaxLen + +// / / Control Types Table + + static TableEntry TypeTable[] = { + { "Label", FIELD_LABEL }, + { "GroupBox", FIELD_GROUPBOX }, + { "Image", FIELD_IMAGE }, + { "Icon", FIELD_IMAGE }, // For compatibility w/ IO + { "Bitmap", FIELD_IMAGE }, // For compatibility w/ IO + { "Animation", FIELD_IMAGE }, // For compatibility w/ IO + { "ProgressBar", FIELD_PROGRESSBAR }, + { "Link", FIELD_LINK }, + { "CheckBox", FIELD_CHECKBOX }, + { "RadioButton", FIELD_RADIOBUTTON }, + { "Button", FIELD_BUTTON }, + { "UpDown", FIELD_UPDOWN }, + { "Text", FIELD_TEXT }, + { "Edit", FIELD_TEXT }, // Same as TEXT + { "Password", FIELD_TEXT }, + { "IPAddress", FIELD_IPADDRESS }, + { "RichText", FIELD_RICHTEXT }, + { "RichEdit", FIELD_RICHTEXT }, // Same as RICHTEXT + { "DropList", FIELD_COMBOBOX }, + { "ComboBox", FIELD_COMBOBOX }, + { "DateTime", FIELD_DATETIME }, + { "ListBox", FIELD_LISTBOX }, + { "ListView", FIELD_LISTVIEW }, + { "TreeView", FIELD_TREEVIEW }, + { "TrackBar", FIELD_TRACKBAR }, + { "MonthCalendar", FIELD_MONTHCALENDAR }, + { "HLINE", FIELD_HLINE }, + { "VLINE", FIELD_VLINE }, + { NULL, 0 } + }; + +// / Flags + +// / / All controls +#define FLAG_DISABLED 0x00000001 +#define FLAG_GROUP 0x00000002 +#define FLAG_NOTABSTOP 0x00000004 +#define FLAG_HSCROLL 0x00000008 +#define FLAG_VSCROLL 0x00000010 +#define FLAG_MULTISELECT 0x00000020 +#define FLAG_READONLY 0x00000040 +// / / Image controls +#define FLAG_RESIZETOFIT 0x00000080 +#define FLAG_TRANSPARENT 0x00000100 +// / / ProgressBar controls +#define FLAG_SMOOTH 0x00000080 +// FLAG_VSCROLL 0x00000010 +// / / CheckBox/RadioButton controls +// FLAG_READONLY 0x00000040 +#define FLAG_3STATE 0x00000080 // CheckBox +// / / Button controls +#define FLAG_OPEN_FILEREQUEST 0x00000100 +#define FLAG_SAVE_FILEREQUEST 0x00000200 +#define FLAG_DIRREQUEST 0x00000400 +#define FLAG_COLORREQUEST 0x00000800 +#define FLAG_FONTREQUEST 0x00001000 +#define FLAG_FILE_MUST_EXIST 0x00002000 +#define FLAG_FILE_EXPLORER 0x00004000 +#define FLAG_FILE_HIDEREADONLY 0x00008000 +#define FLAG_WARN_IF_EXIST 0x00010000 +#define FLAG_PATH_MUST_EXIST 0x00020000 +#define FLAG_PROMPT_CREATE 0x00040000 +#define FLAG_BITMAP 0x00080000 +#define FLAG_ICON 0x00100000 +// FLAG_MULTISELECT 0x00000020 +// / / UpDown controls +// FLAG_HSCROLL 0x00000008 +#define FLAG_WRAP 0x00000080 +// / / Text/Password/RichText controls +#define FLAG_ONLYNUMBERS 0x00000080 +#define FLAG_MULTILINE 0x00000100 +#define FLAG_WANTRETURN 0x00000200 +#define FLAG_NOWORDWRAP 0x00000400 +#define FLAG_PASSWORD 0x00000800 // Text/RichText +// / / DropList/ComboBox controls +// FLAG_VSCROLL 0x00000010 +#define FLAG_DROPLIST 0x00000080 // ComboBox +// / / DateTime controls +#define FLAG_UPDOWN 0x00000080 +// / / ListBox controls +// FLAG_MULTISELECT 0x00000020 +#define FLAG_EXTENDEDSELECT 0x00000080 +// FLAG_VSCROLL 0x00000010 +// / / TreeView/ListView controls +#define FLAG_CHECKBOXES 0x00000080 +#define FLAG_EDITLABELS 0x00000100 +#define FLAG_ICON_VIEW 0x00000200 // ListView +#define FLAG_LIST_VIEW 0x00000400 // ListView +#define FLAG_REPORT_VIEW 0x00000800 // ListView +#define FLAG_SMALLICON_VIEW 0x00001000 // ListView +// / / TrackBar controls +#define FLAG_NO_TICKS 0x00000080 +// FLAG_VSCROLL 0x00000010 +// / / MonthCalendar controls +#define FLAG_NOTODAY 0x00000080 +#define FLAG_WEEKNUMBERS 0x00000100 + +#define FLAG_FOCUS 0x10000000 // Controls that can receive focus +#define FLAG_DRAW_TEMP 0x40000000 // Temporary flag for + // drawing process +#define FLAG_CUSTOMDRAW_TEMP 0x80000000 // Temporary flag for + // custom drawing process + +// / - Control Flags Table + static TableEntry FlagTable[] = { + // All controls + { "DISABLED", FLAG_DISABLED }, + { "GROUP", FLAG_GROUP }, + { "NOTABSTOP", FLAG_NOTABSTOP }, + + // Image controls + { "RESIZETOFIT", FLAG_RESIZETOFIT }, + { "TRANSPARENT", FLAG_TRANSPARENT }, + + // ProgressBar controls + { "SMOOTH", FLAG_SMOOTH }, + { "VSCROLL", FLAG_VSCROLL }, + + // CheckBox/RadioButton controls + { "READONLY", FLAG_READONLY }, + { "3STATE", FLAG_3STATE }, // Except CheckBox + + // Button controls + { "OPEN_FILEREQUEST", FLAG_OPEN_FILEREQUEST }, + { "SAVE_FILEREQUEST", FLAG_SAVE_FILEREQUEST }, + { "REQ_SAVE", FLAG_SAVE_FILEREQUEST }, + { "DIRREQUEST", FLAG_DIRREQUEST }, + { "COLORREQUEST", FLAG_COLORREQUEST }, + { "FONTREQUEST", FLAG_FONTREQUEST }, + { "FILE_MUST_EXIST", FLAG_FILE_MUST_EXIST }, + { "FILE_EXPLORER", FLAG_FILE_EXPLORER }, + { "FILE_HIDEREADONLY", FLAG_FILE_HIDEREADONLY }, + { "WARN_IF_EXIST", FLAG_WARN_IF_EXIST }, + { "PATH_MUST_EXIST", FLAG_PATH_MUST_EXIST }, + { "PROMPT_CREATE", FLAG_PROMPT_CREATE }, + { "BITMAP", FLAG_BITMAP }, + { "ICON", FLAG_ICON }, + + // UpDown controls + { "HSCROLL", FLAG_HSCROLL }, + { "WRAP", FLAG_WRAP }, + + // Text/Password/RichText controls + { "ONLY_NUMBERS", FLAG_ONLYNUMBERS }, + { "MULTILINE", FLAG_MULTILINE }, + { "WANTRETURN", FLAG_WANTRETURN }, + { "NOWORDWRAP", FLAG_NOWORDWRAP }, +// { "HSCROLL", FLAG_HSCROLL }, +// { "VSCROLL", FLAG_VSCROLL }, +// { "READONLY", FLAG_READONLY }, + { "PASSWORD", FLAG_PASSWORD }, //Except Password + + // DropList/ComboBox controls +// { "VSCROLL", FLAG_VSCROLL }, + { "DROPLIST", FLAG_DROPLIST }, //Except DropList + + // DateTime controls + { "UPDOWN", FLAG_UPDOWN }, + + // ListBox controls + { "MULTISELECT", FLAG_MULTISELECT }, + { "EXTENDEDSELECT", FLAG_EXTENDEDSELECT }, + { "EXTENDEDSELCT", FLAG_EXTENDEDSELECT }, +// { "VSCROLL", FLAG_VSCROLL }, + + // ListView/TreeView controls + { "CHECKBOXES", FLAG_CHECKBOXES }, + { "EDITLABELS", FLAG_EDITLABELS }, +// { "MULTISELECT", FLAG_MULTISELECT }, + { "LIST_VIEW", FLAG_LIST_VIEW }, + { "ICON_VIEW", FLAG_ICON_VIEW }, + { "SMALLICON_VIEW", FLAG_SMALLICON_VIEW }, + { "REPORT_VIEW", FLAG_REPORT_VIEW }, + + // TrackBar controls + { "NO_TICKS", FLAG_NO_TICKS }, +// { "VSCROLL", FLAG_VSCROLL }, + + // MonthCalendar controls + { "NOTODAY", FLAG_NOTODAY }, + { "WEEKNUMBERS", FLAG_WEEKNUMBERS }, + + // Null + { NULL, 0 } + }; + +// / Notification Flags + +#define NOTIFY_NONE 0x00000000 + +// / / Control Notification Flags + +// / / / Kill and set focus +#define NOTIFY_CONTROL_ONSETFOCUS 0x00000001 +#define NOTIFY_CONTROL_ONKILLFOCUS 0x00000002 +// / / / Open or close drop lists +#define NOTIFY_CONTROL_ONLISTOPEN 0x00000004 +#define NOTIFY_CONTROL_ONLISTCLOSE 0x00000008 +// / / / Change a control selection +#define NOTIFY_CONTROL_ONSELCHANGE 0x00000010 +#define NOTIFY_CONTROL_ONTEXTCHANGE 0x00000020 +#define NOTIFY_CONTROL_ONTEXTUPDATE 0x00000040 +#define NOTIFY_CONTROL_ONTEXTSELCHANGE 0x00000080 +// / / / Mouse clicks +#define NOTIFY_CONTROL_ONCLICK 0x00000100 +#define NOTIFY_CONTROL_ONDBLCLICK 0x00000200 +#define NOTIFY_CONTROL_ONRCLICK 0x00000400 +#define NOTIFY_CONTROL_ONRDBLCLICK 0x00000800 +// / / / Text being truncated +#define NOTIFY_CONTROL_ONTEXTTRUNCATE 0x00001000 +// / / / Using text scrollbar +#define NOTIFY_CONTROL_ONTEXTVSCROLL 0x00002000 +// / / / Image control of animation type start/end +#define NOTIFY_CONTROL_ONSTART 0x00004000 +#define NOTIFY_CONTROL_ONSTOP 0x00008000 + +// / / - Control Notification Flags Table + static TableEntry ControlNotifyTable[] = { + { "ONSETFOCUS", NOTIFY_CONTROL_ONSETFOCUS }, + { "ONKILLFOCUS", NOTIFY_CONTROL_ONKILLFOCUS }, + { "ONLISTOPEN", NOTIFY_CONTROL_ONLISTOPEN }, + { "ONLISTCLOSE", NOTIFY_CONTROL_ONLISTCLOSE }, + { "ONSELCHANGE", NOTIFY_CONTROL_ONSELCHANGE }, + { "ONTEXTCHANGE", NOTIFY_CONTROL_ONTEXTCHANGE }, + { "ONTEXTUPDATE", NOTIFY_CONTROL_ONTEXTUPDATE }, + { "ONTEXTSELCHANGE",NOTIFY_CONTROL_ONTEXTSELCHANGE}, + { "ONTEXTTRUNCATE", NOTIFY_CONTROL_ONTEXTTRUNCATE }, + { "ONCLICK", NOTIFY_CONTROL_ONCLICK }, + { "ONDBLCLICK", NOTIFY_CONTROL_ONDBLCLICK }, + { "ONRCLICK", NOTIFY_CONTROL_ONRCLICK }, + { "ONRDBLCLICK", NOTIFY_CONTROL_ONRDBLCLICK }, + { "ONTEXTVSCROLL", NOTIFY_CONTROL_ONTEXTVSCROLL }, + { "ONSTART", NOTIFY_CONTROL_ONSTART }, + { "ONSTOP", NOTIFY_CONTROL_ONSTOP }, + { NULL, 0 } + }; + +// / / Page Notification Flags + +// / / / Page clicks +#define NOTIFY_PAGE_ONNEXT 0x00000001 +#define NOTIFY_PAGE_ONBACK 0x00000002 +#define NOTIFY_PAGE_ONCANCEL 0x00000004 +// / / / Timeout feature +#define NOTIFY_PAGE_ONTIMEOUT 0x00000008 + +// / / - Page Notification Flags Table + static TableEntry PageNotifyTable[] = { + { "ONNEXT", NOTIFY_PAGE_ONNEXT }, + { "ONBACK", NOTIFY_PAGE_ONBACK }, + { "ONCANCEL", NOTIFY_PAGE_ONCANCEL }, + { "ONTIMEOUT", NOTIFY_PAGE_ONTIMEOUT }, + { NULL, 0 } + }; + +// / Alignment Flags + +// / / Horizontal +#define ALIGN_LEFT 0x00000001 +#define ALIGN_CENTER 0x00000002 +#define ALIGN_RIGHT 0x00000004 + +// / / Vertical +#define VALIGN_TOP 0x00000001 +#define VALIGN_CENTER 0x00000002 +#define VALIGN_BOTTOM 0x00000004 + +// / Text Alignment Flags + +// / / Horizontal +#define ALIGN_TEXT_LEFT 0x00000001 +#define ALIGN_TEXT_CENTER 0x00000002 +#define ALIGN_TEXT_RIGHT 0x00000004 +#define ALIGN_TEXT_JUSTIFY 0x00000008 +// / / Vertical +#define VALIGN_TEXT_TOP 0x00000001 +#define VALIGN_TEXT_CENTER 0x00000002 +#define VALIGN_TEXT_BOTTOM 0x00000004 +#define VALIGN_TEXT_JUSTIFY 0x00000008 + +// / Image Type Flags + +#define IMAGE_TYPE_BITMAP 0 +#define IMAGE_TYPE_ICON 1 +#define IMAGE_TYPE_CURSOR 2 +#define IMAGE_TYPE_ANIMATION 3 +#define IMAGE_TYPE_OLE 4 +#define IMAGE_TYPE_GDIPLUS 5 + +//================================================================ +// Function Definitions +//================================================================ + +// Memory Functions (w/ Implementation) +//================================================================ +void *WINAPI MALLOC(int len) { return (void*)GlobalAlloc(GPTR,len); } +void WINAPI FREE(void *d) { if (d) GlobalFree((HGLOBAL)d); } + +// String Functions/Macros +//================================================================ + +// WARNING: lstrcpy(str, "") != ZeroMemory. lstrcpy assigns '\0' to +// first character only. It also ignores extra '\0' +// characters. + +#define strcpy(x,y) lstrcpy(x,y) +#define strncpy(x,y,z) lstrcpyn(x,y,z) +#define strlen(x) lstrlen(x) +#define strdup(x) STRDUP(x) +#define stricmp(x,y) lstrcmpi(x,y) +//#define abs(x) ((x) < 0 ? -(x) : (x)) + +char *WINAPI STRDUP(const char *c) +{ + char *t=(char*)MALLOC(strlen(c)+1); + return strcpy(t,c); +} + +// Turn a pair of chars into a word +// Turn four chars into a dword +// NOTE: These codes are only when comparing WORD's and DWORD's. +#ifdef __BIG_ENDIAN__ // Not very likely, but, still... + #define CHAR2_TO_WORD(a,b) (((WORD)(b))|((a)<<8)) + #define CHAR4_TO_DWORD(a,b,c,d) (((DWORD)CHAR2_TO_WORD(c,d))|(CHAR2_TO_WORD(a,b)<<16)) +#else + #define CHAR2_TO_WORD(a,b) (((WORD)(a))|((b)<<8)) + #define CHAR4_TO_DWORD(a,b,c,d) (((DWORD)CHAR2_TO_WORD(a,b))|(CHAR2_TO_WORD(c,d)<<16)) +#endif + +void WINAPI popstring(char *str); + +// Integer Functions +//================================================================ +void WINAPI myitoa(char *s, int d); +void WINAPI mycrxtoa(char *s, int x); +void WINAPI myltoa(char *s, long l); +int WINAPI myatoi(char *s); + +// Debug Functions +//================================================================ +int WINAPI myMessageBox(char lpText); +int WINAPI myMessageBoxI(int i); + +// Configuration File (.ini File) Functions +//================================================================ +// TODO: Needs new header specially for configuration file reading +// for greater support to configuration files for other +// Operating Systems. + +DWORD WINAPI myGetProfileString(LPCTSTR lpKeyName); +char * WINAPI myGetProfileStringDup(LPCTSTR lpKeyName); +int WINAPI myGetProfileInt(LPCTSTR lpKeyName, INT nDefault); +char * WINAPI myGetProfileListItemsDup(LPCTSTR lpKeyName, int nStrFlags); +char * WINAPI myGetProfileFilterItemsDup(LPCTSTR lpKeyName); + +char* WINAPI IOtoa(char *str); +char* WINAPI IOtoaFolder(char *str); +void WINAPI atoIO(char *str); + +// Input Functions +//================================================================ +// Convert a string to internal types +void WINAPI ConvertVariables(char *str, int nMaxChars); +void WINAPI LItoIOLI(LPSTR str, int nMaxChars, int nStrFlags, int iControlType); +void WINAPI LItoFI(LPSTR str, int nMaxChars); +char* WINAPI IOLItoLI(LPSTR str, int nMaxChars, int iControlType); +int WINAPI IOLI_NextItem(LPSTR str, LPSTR *strstart, LPSTR *strend, int nStrFlags); +void WINAPI IOLI_RestoreItemStructure(LPSTR str, LPSTR *strstart, LPSTR *strend, int nFlags); + +// Convert a string to respective integer +int WINAPI LookupToken(TableEntry*, char*); +int WINAPI LookupTokens(TableEntry*, char*); +char* WINAPI LookupTokenName(TableEntry*, int); + +// Find the index of a control +int WINAPI FindControlIdx(UINT id); + +// Window Functions +//================================================================ +LRESULT WINAPI mySendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +void WINAPI mySetFocus(HWND hWnd); +void WINAPI mySetWindowText(HWND hWnd, LPCTSTR pszText); +void WINAPI myGetWindowText(HWND hWnd, LPTSTR pszText); + +bool ConvertDlgRect(HWND hWnd, RECT* pRect, int iFrom, int iTo); + +// Notification Queue Functions +//================================================================ +int WINAPI AddNotifyQueueItem(NotifyQueue * nqItem); +int WINAPI RemoveNotifyQueueItem(); + +#define Notification(ID, code) \ + if ( (pField->nNotify & ID) == ID && codeNotify == code ) \ + { \ + g_aNotifyQueueTemp->iNotifyId = ID; \ + isNotified = true; \ + dontSimulateNext = false; \ + } + +// Other Functions +//================================================================ +bool WINAPI FileExists(LPSTR pszFile); // Alternative - needs testing to see performance. + +//================================================================ +// Function Implementations +//================================================================ + +// String Functions/Macros +//================================================================ +void WINAPI popstring(char *str) +{ + if (g_stacktop && *g_stacktop) + { + stack_t *th = *g_stacktop; + *g_stacktop = th->next; + if (str) + strcpy(str, th->text); + FREE(th); + } +} + +// Integer Functions +//================================================================ +void WINAPI myitoa(char *s, int d) +{ + wsprintf(s,"%d",d); +} + +void WINAPI mycrxtoa(char *s, int x) +{ + wsprintf(s,"%#06x",x); +} + +void WINAPI myltoa(char *s, long l) +{ + wsprintf(s,"%ld",l); +} + +int WINAPI myatoi(char *s) +{ + unsigned int v=0; + int sign=1; // sign of positive + char m=10; // base of 0 + char t='9'; // cap top of numbers at 9 + + if (*s == '-') + { + s++; //skip over - + sign=-1; // sign flip + } + + if (*s == '0') + { + s++; // skip over 0 + if (s[0] >= '0' && s[0] <= '7') + { + m=8; // base of 8 + t='7'; // cap top at 7 + } + if ((s[0] & ~0x20) == 'X') + { + m=16; // base of 16 + s++; // advance over 'x' + } + } + + for (;;) + { + int c=*s++; + if (c >= '0' && c <= t) c-='0'; + else if (m==16 && (c & ~0x20) >= 'A' && (c & ~0x20) <= 'F') c = (c & 7) + 9; + else break; + v*=m; + v+=c; + } + return ((int)v)*sign; +} + +// Debug Functions +//================================================================ +int WINAPI myMessageBox(LPCSTR lpText) +{ + return MessageBox(0,lpText,0,MB_OK); +} + +int WINAPI myMessageBoxI(int i) +{ + LPSTR lpText = (LPSTR)MALLOC(g_nBufferSize); + myitoa(lpText, i); + int nMsgBoxResult = MessageBox(0,lpText,0,MB_OK); + FREE(lpText); + return nMsgBoxResult; +} + +// Configuration File (.ini) Functions +//================================================================ +DWORD WINAPI myGetProfileString(LPCTSTR lpKeyName) +{ + *szResult = '\0'; + int nSize = GetPrivateProfileString(pszAppName, lpKeyName, "", szResult, g_nBufferSize, pszFilename); + if(nSize) + ConvertVariables(szResult, g_nBufferSize); + return nSize; +} + +char * WINAPI myGetProfileStringDup(LPCTSTR lpKeyName) +{ + int nSize = myGetProfileString(lpKeyName); + + if (nSize) + return strdup(szResult); + else + return NULL; +} + +int WINAPI myGetProfileInt(LPCTSTR lpKeyName, INT nDefault) +{ + LPSTR str; + int num; + + str = myGetProfileStringDup(lpKeyName); + + if(str) + num = myatoi(str); + else + num = nDefault; + + FREE(str); + return num; +} + +char * WINAPI myGetProfileListItemsDup(LPCTSTR lpKeyName, int nStrFlags, int iControlType) +{ + int size; + LPSTR str = (LPSTR)MALLOC(g_nBufferSize); // + end list char + + size = myGetProfileString(lpKeyName); + + if(size) + { + LItoIOLI(szResult, g_nBufferSize, nStrFlags, iControlType); + + size = lstrlen(szResult)+1; + + lstrcpy(str, szResult); + + str[size] = '\1'; + str[size+1] = '\0'; + } + + return str; +} + +char * WINAPI myGetProfileFilterItemsDup(LPCTSTR lpKeyName) +{ + int size; + LPSTR str = (LPSTR)MALLOC(g_nBufferSize); // + end list char + + size = myGetProfileString(lpKeyName); + + if(size) + { + size = lstrlen(szResult)+1; + + lstrcpy(str, szResult); + LItoFI(str, g_nBufferSize); + } + + return str; +} + + + +void WINAPI atoIO(char *str) { + + if (!str) + return; + + char *p1, *p2, *p3; + + p1 = p2 = str; + + while (*p1) + { + switch (*(LPWORD)p1) + { + case CHAR2_TO_WORD('\\', 't'): + *p2 = '\t'; + p1 += 2; + p2++; + break; + case CHAR2_TO_WORD('\\', 'n'): + *p2 = '\n'; + p1 += 2; + p2++; + break; + case CHAR2_TO_WORD('\\', 'r'): + *p2 = '\r'; + p1 += 2; + p2++; + break; + case CHAR2_TO_WORD('\\', '\\'): + *p2 = '\\'; + p1 += 2; + p2++; + break; + default: + p3 = CharNext(p1); + while (p1 < p3) + *p2++ = *p1++; + break; + } + } + + *p2 = 0; +} + +char* WINAPI IOtoa(char *str) +{ + if (!str) + return str; + + char *newstr = (char*)MALLOC(strlen(str)*2+1); + char *p1, *p2; + + for (p1 = str, p2 = newstr; *p1; p1 = CharNext(p1), p2 = CharNext(p2)) + { + switch (*p1) { + case '\t': + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 't'); + p2++; + break; + case '\n': + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 'n'); + p2++; + break; + case '\r': + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 'r'); + p2++; + break; + case '\\': + *p2++ = '\\'; + default: + strncpy(p2, p1, CharNext(p1) - p1 + 1); + break; + } + } + *p2 = 0; + return newstr; +} + +char* WINAPI IOtoaFolder(char *str) +{ + if (!str) + return str; + + char *newstr = (char*)MALLOC(strlen(str)*2+1); + char *p1, *p2; + + for (p1 = str, p2 = newstr; *p1; p1 = CharNext(p1), p2 = CharNext(p2)) + { + switch (*p1) { + case '\t': + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 't'); + p2++; + break; + case '\n': + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 'n'); + p2++; + break; + case '\r': + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 'r'); + p2++; + break; + default: + strncpy(p2, p1, CharNext(p1) - p1 + 1); + break; + } + } + *p2 = 0; + return newstr; +} + + +// Input Functions +//================================================================ +void WINAPI ConvertVariables(char *str, int nMaxChars) { + + if (!str) + return; + + char *p1, *p2, *p3, *p4, *p5; + + p1 = p2 = str; + p4 = p5 = (char*)MALLOC(nMaxChars); + + while (*p1 && lstrlen(p2) <= nMaxChars) + { + if(*(LPBYTE)p1 == '$') + { + p1++; + + if(*(LPBYTE)p1 == '$') + { + p1++; + + *p2 = '$'; + p2++; + } + else if(*(LPBYTE)p1 >= '0' && *(LPBYTE)p1 <= '9') + { + p1++; + + if(!(p4 = getuservariable(myatoi(p1)))) + p4 = ""; + + while (*p4 && lstrlen(p2) <= nMaxChars) + { + p5 = CharNext(p4); + while (p4 < p5 && lstrlen(p2) <= nMaxChars) + { + *p2++ = *p4++; + } + } + } + else if(*(LPBYTE)p1 == 'R') + { + p1++; + + if(*(LPBYTE)p1 >= '0' && *(LPBYTE)p1 <= '9') + { + p1++; + + if(!(p4 = getuservariable(myatoi(p1)+ 10))) + p4 = ""; + + while (*p4 && lstrlen(p2) <= nMaxChars) + { + p5 = CharNext(p4); + while (p4 < p5 && lstrlen(p2) <= nMaxChars) + { + *p2++ = *p4++; + } + } + } + else + { + p1 -= 2; + + p3 = CharNext(p1); + while (p1 < p3 && lstrlen(p2) <= nMaxChars) + *p2++ = *p1++; + } + } + else + { + p1--; + + p3 = CharNext(p1); + while (p1 < p3 && lstrlen(p2) <= nMaxChars) + *p2++ = *p1++; + } + } + else + { + p3 = CharNext(p1); + while (p1 < p3 && lstrlen(p2) <= nMaxChars) + *p2++ = *p1++; + } + } + + *p2 = 0; + + FREE(p4); +} + +// This function converts a list items (not necessarily from the user) +// and converts characters to be used internally. It converts: +// '|' -> '\x01' +// '{' -> '\x02' +// '}' -> '\x03' +// '\\|' -> '|' +// '\\{' -> '{' +// '\\}' -> '}' + +// This is done because it becomes easier to manipulate the search +// for strings into a list of items. + +// A list of items could be somewhat called an array, except that +// it doesn't hold the exact location of the items, but gives +// enough reference to where the item starts or ends. + +void WINAPI LItoIOLI(char *str, int nMaxChars, int nStrFlags, int iControlType) +{ + //nStrFlags + // 0 = Accept '|', '{' and '}' characters. + // 1 = Except '|'. + // 2 = Except '{' and '}'. + + if (!str) + return; + + char *p1, *p2, *p3; + + p1 = p2 = str; + + while (*p1 && lstrlen(p2) <= nMaxChars) + { + if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', '\\')) + { + *p2 = '\\'; + p1 += 2; + p2++; + } + else if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', 'r') && (iControlType == FIELD_COMBOBOX || iControlType == FIELD_LISTBOX)) + { + *p2 = '\r'; + p1 += 2; + p2++; + } + else if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', 'n') && (iControlType == FIELD_COMBOBOX || iControlType == FIELD_LISTBOX)) + { + *p2 = '\n'; + p1 += 2; + p2++; + } + else if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', 't') && (iControlType == FIELD_COMBOBOX || iControlType == FIELD_LISTBOX)) + { + *p2 = '\t'; + p1 += 2; + p2++; + } + else if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', '|')) + { + *p2 = '|'; + p1 += 2; + p2++; + } + else if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', '{')) + { + *p2 = '{'; + p1 += 2; + p2++; + } + else if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', '}')) + { + *p2 = '}'; + p1 += 2; + p2++; + } + else if (*(LPBYTE)p1 == '|' && nStrFlags != 1) + { + *p2 = '\x01'; + p1++; + p2++; + } + else if (*(LPBYTE)p1 == '{' && nStrFlags != 2) + { + *p2 = '\x02'; + p1++; + p2++; + } + else if (*(LPBYTE)p1 == '}' && nStrFlags != 2) + { + *p2 = '\x03'; + p1++; + p2++; + } + else + { + p3 = CharNext(p1); + while (p1 < p3) + *p2++ = *p1++; + } + } + + *p2 = 0; +} + +// This function is specially used by the "Filter" value name +// to convert a list items (not necessarily from the user) to +// the supported syntax for the filter of directories. It converts: +// '|' -> '\0' +// '\\|' -> '|' +// the ending '\0' -> '\0\0' + +void WINAPI LItoFI(char *str, int nMaxChars) +{ + if (!str) + return; + + char *p1, *p2, *p3; + + p1 = p2 = str; + + while (*p1 && lstrlen(p2) <= nMaxChars) + { + if (*(LPWORD)p1 == CHAR2_TO_WORD('\\', '|')) + { + *p2 = '|'; + p1 += 2; + p2++; + } + else if (*(LPBYTE)p1 == '\0') + { + *p2 = '\0'; + p1++; + } + else if (*(LPBYTE)p1 == '|') + { + *p2 = '\0'; + p1++; + p2++; + } + else + { + p3 = CharNext(p1); + while (p1 < p3) + *p2++ = *p1++; + } + } + + *p2 = 0; +} + +// This function converts a list items used internally to one +// to be returned to the user. It converts: +// '|' -> '\\|' +// '{' -> '\\{' +// '}' -> '\\}' +// '\x01' -> '|' +// '\x02' -> '{' +// '\x03' -> '}' + +// This is done because the user needs to get the string back to +// be reused again on the next call to the plug-in. + +char* WINAPI IOLItoLI(char *str, int nMaxChars, int iControlType) +{ + if (!str) + return str; + + char *newstr = (char*)MALLOC(lstrlen(str)*2+1); + char *p1, *p2; + + for (p1 = str, p2 = newstr; *p1; p1 = CharNext(p1), p2 = CharNext(p2)) + { + if (*(LPBYTE)p1 == '\\') + { + *(LPWORD)p2 = CHAR2_TO_WORD('\\', '\\'); + p2++; + } + else if (*(LPBYTE)p1 == '\r' && (iControlType == FIELD_LISTBOX || iControlType == FIELD_COMBOBOX)) + { + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 'r'); + p2++; + } + else if (*(LPBYTE)p1 == '\n' && (iControlType == FIELD_LISTBOX || iControlType == FIELD_COMBOBOX)) + { + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 'n'); + p2++; + } + else if (*(LPBYTE)p1 == '\t' && (iControlType == FIELD_LISTBOX || iControlType == FIELD_COMBOBOX)) + { + *(LPWORD)p2 = CHAR2_TO_WORD('\\', 't'); + p2++; + } + else if (*(LPBYTE)p1 == '|') + { + *(LPWORD)p2 = CHAR2_TO_WORD('\\', '|'); + p2++; + } + else if (*(LPBYTE)p1 == '{') + { + *(LPWORD)p2 = CHAR2_TO_WORD('\\', '{'); + p2++; + } + else if (*(LPBYTE)p1 == '}') + { + *(LPWORD)p2 = CHAR2_TO_WORD('\\', '}'); + p2++; + } + else if (*(LPBYTE)p1 == '\x01') + { + *(LPBYTE)p2 = '|'; + p2++; + } + else if (*(LPBYTE)p1 == '\x02') + { + *(LPBYTE)p2 = '{'; + p2++; + } + else if (*(LPBYTE)p1 == '\x03') + { + *(LPBYTE)p2 = '}'; + p2++; + } + else + { + strncpy(p2, p1, CharNext(p1) - p1 + 1); + } + } + *p2 = 0; + return newstr; +} + +int WINAPI IOLI_NextItem(LPSTR str, char **strstart, char **strend, int nStrFlags) +{ + int nFlags = 0; + // nFlags + // 0 = The string is in format "ListItems". It's not an item. + // 1 = It's a normal item. + // 2 = Item ending w/ '\x02' - '{'. + // 3 = Item ending w/ '\x03' - '}'. + // 4 = End of string, but there is string. + // 5 = End of string + item starting w/ '\x03' - '}'. + // 6 = End of string. + + // nStrFlags + // 0 = Accept '|', '{' and '}' characters. + // 1 = Accept only '|'. + // 2 = Accept only '{' and '}'. + + if (!str) + return nFlags; + + //strend = current item in the list + + if(*(LPBYTE)*strend == '\x03') + nFlags = 5; + else + nFlags = 4; + + if(*(LPBYTE)*strend == '\0') + nFlags = 6; + + while (*(LPBYTE)*strend) + { + if(*(LPBYTE)*strend == '\x01') + { + if(nStrFlags == 2) + *(LPBYTE)*strend = '|'; + else + { + *(LPBYTE)*strend = '\0'; + return 1; + } + } + + if(*(LPBYTE)*strend == '\x02') + { + if(nStrFlags == 1) + *(LPBYTE)*strend = '{'; + else + { + *(LPBYTE)*strend = '\0'; + return 2; + } + } + + if(*(LPBYTE)*strend == '\x03') + { + if(nStrFlags == 1) + *(LPBYTE)*strend = '}'; + else + { + *(LPBYTE)*strend = '\0'; + return 3; + } + } + + *strend = CharNext(*strend); + } + + return nFlags; +} + +void WINAPI IOLI_RestoreItemStructure(LPSTR str, LPSTR *strstart, LPSTR *strend, int nFlags) +{ + // nFlags + // 0 = The string is in format "ListItems". It's not an item. + // 1 = It's a normal item. + // 2 = Item ending w/ '\x02' - '{'. + // 3 = Item ending w/ '\x03' - '}'. + // 4 = End of string. + + switch(nFlags) + { + case 1: + { + **strend = '\x01'; + *strstart = ++*strend; + break; + } + case 2: + { + **strend = '\x02'; + *strstart = ++*strend; + break; + } + case 3: + { + **strend = '\x03'; + *strstart = ++*strend; + break; + } + } + + return; +} + + +int WINAPI LookupToken(TableEntry* psTable_, char* pszToken_) +{ + for (int i = 0; psTable_[i].pszName; i++) + if (!stricmp(pszToken_, psTable_[i].pszName)) + return psTable_[i].nValue; + return 0; +} + +char* WINAPI LookupTokenName(TableEntry* psTable_, int nValue) +{ + for (int i = 0; psTable_[i].nValue; i++) + if (nValue == psTable_[i].nValue) + return psTable_[i].pszName; + return 0; +} + +int WINAPI LookupTokens(TableEntry* psTable_, char* pszTokens_) +{ + int n = 0; + char *pszStart = pszTokens_; + char *pszEnd = pszTokens_; + for (;;) { + char c = *pszEnd; + if (c == '|' || c == '\0') { + *pszEnd = '\0'; + n |= LookupToken(psTable_, pszStart); + *pszEnd = c; + if (!c) + break; + pszStart = ++pszEnd; + } + else + pszEnd = CharNext(pszEnd); + } + return n; +} + +int WINAPI FindControlIdx(UINT id) +{ + for (int nIdx = 0; nIdx < nNumFields; nIdx++) + if (id == pFields[nIdx].nControlID) + return nIdx; + return -1; +} + +// Window Functions +//================================================================ +LRESULT WINAPI mySendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + return SendMessage(hWnd, Msg, wParam, lParam); +} + +void WINAPI mySetFocus(HWND hWnd) +{ + mySendMessage(hMainWindow, WM_NEXTDLGCTL, (WPARAM)hWnd, TRUE); +} + +void WINAPI mySetWindowText(HWND hWnd, LPCTSTR pszText) +{ + if (pszText) + SetWindowText(hWnd, pszText); +} + +void WINAPI myGetWindowText(HWND hWnd, LPTSTR pszText) +{ + GetWindowText(hWnd, pszText, GetWindowTextLength(hWnd)+1); +} + +bool ConvertDlgRect(HWND hWnd, RECT* pRect, int iFrom, int iTo) +{ + // iFrom and iTo: + // 0x0 = Pixels + // 0x1 = Dialog units + + if(iFrom == 0x0 && iTo == 0x1) + { + RECT pTempRect = {0, 0, 4, 8}; + + if(!MapDialogRect(hWnd, &pTempRect)) + return 0; + int baseUnitY = pTempRect.bottom; + int baseUnitX = pTempRect.right; + + pRect->left = (pRect->left * baseUnitX) / 4; + pRect->right = (pRect->right * baseUnitX) / 4; + + pRect->top = (pRect->top * baseUnitX) / 8; + pRect->bottom = (pRect->bottom * baseUnitX) / 8; + } + else + if(iFrom == 0x1 && iTo == 0x0) + { + if(!MapDialogRect(hWnd, pRect)) + return 0; + } + + return 1; +} + +//================================================================ +// Notify Queue Functions Definitions +//================================================================ +int WINAPI AddNotifyQueueItem(NotifyQueue * nqItem) +{ + int id = NOTIFY_QUEUE_AMOUNT_MAX - 1; + + while(g_aNotifyQueue[id].iNotifyId == NOTIFY_NONE && id >= 0) + { + --id; + } + + ++id; + + if(id > NOTIFY_QUEUE_AMOUNT_MAX - 1) + return -1; + + g_aNotifyQueue[id].iNotifyId = nqItem[0].iNotifyId; + g_aNotifyQueue[id].iField = nqItem[0].iField; + g_aNotifyQueue[id].bNotifyType = nqItem[0].bNotifyType; + + nqItem[0].iNotifyId = NOTIFY_NONE; + nqItem[0].iField = 0; + nqItem[0].bNotifyType = NOTIFY_TYPE_PAGE; + + return 0; +} + +int WINAPI RemoveNotifyQueueItem() +{ + int id = 0; + + while(++id && id < NOTIFY_QUEUE_AMOUNT_MAX) + { + g_aNotifyQueue[id-1].iNotifyId = g_aNotifyQueue[id].iNotifyId; + g_aNotifyQueue[id-1].iField = g_aNotifyQueue[id].iField; + g_aNotifyQueue[id-1].bNotifyType = g_aNotifyQueue[id].bNotifyType; + + g_aNotifyQueue[id].iNotifyId = NOTIFY_NONE; + g_aNotifyQueue[id].iField = 0; + g_aNotifyQueue[id].bNotifyType = NOTIFY_TYPE_PAGE; + } + return 0; +} + +// Other Functions +//================================================================ + +// Alternative for "PathFileExists" from shlwapi.h. +bool WINAPI FileExists(LPSTR pszFile) +{ + HANDLE h; + static WIN32_FIND_DATA fd; + // Avoid a "There is no disk in the drive" error box on empty removable drives + SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); + h = FindFirstFile(pszFile,&fd); + SetErrorMode(0); + if (h != INVALID_HANDLE_VALUE) + return TRUE; + return FALSE; +} + +//================================================================ +// Post-Call Functions (see InstallOptions.cpp) +//================================================================ + +//Pre function part +int WINAPI createCfgDlg(); + +//Show function part +void WINAPI showCfgDlg(); + +BOOL CALLBACK MainWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); +BOOL CALLBACK DialogWindowProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +int WINAPI ControlWindowProc(HWND hWin, UINT uMsg, WPARAM wParam, LPARAM lParam); +LRESULT WINAPI NotifyProc(HWND hWnd, UINT id, HWND hwndCtl, UINT codeNotify); + +//Reading and writing operations +int WINAPI ReadSettings(); +bool WINAPI SaveSettings(void); + +//Other main functions +bool INLINE ValidateFields(); + +//================================================================ +// Include Control Implementations +//================================================================ + +#include "Controls\Label.h" // FIELD_LABEL +#include "Controls\GroupBox.h" // FIELD_GROUPBOX +#include "Controls\Image.h" // FIELD_IMAGE +#include "Controls\ProgressBar.h" // FIELD_PROGRESSBAR +#include "Controls\Link.h" // FIELD_LINK +#include "Controls\Button.h" // FIELD_BUTTON +#include "Controls\UpDown.h" // FIELD_UPDOWN +#include "Controls\CheckBox.h" // FIELD_CHECKBOX +#include "Controls\RadioButton.h" // FIELD_RADIOBUTTON +#include "Controls\Text.h" // FIELD_TEXT +#include "Controls\IPAddress.h" // FIELD_IPADDRESS +#include "Controls\RichText.h" // FIELD_RICHTEXT +#include "Controls\ComboBox.h" // FIELD_COMBOBOX +#include "Controls\DateTime.h" // FIELD_DATETIME +#include "Controls\ListBox.h" // FIELD_LISTBOX +#include "Controls\ListView.h" // FIELD_LISTVIEW +#include "Controls\TreeView.h" // FIELD_TREEVIEW +#include "Controls\TrackBar.h" // FIELD_TRACKBAR +#include "Controls\MonthCalendar.h" // FIELD_MONTHCALENDAR \ No newline at end of file diff --git a/installer/NSIS/Contrib/InstallOptionsEx/exdll.h b/installer/NSIS/Contrib/InstallOptionsEx/exdll.h new file mode 100644 index 0000000..f16cf04 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/exdll.h @@ -0,0 +1,127 @@ +#ifndef _EXDLL_H_ +#define _EXDLL_H_ + +#include + +#if defined(__GNUC__) +#define UNUSED __attribute__((unused)) +#else +#define UNUSED +#endif + +// only include this file from one place in your DLL. +// (it is all static, if you use it in two places it will fail) + +#define EXDLL_INIT() { \ + g_stringsize=string_size; \ + g_stacktop=stacktop; \ + g_variables=variables; } + +// For page showing plug-ins +#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8) +#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd) +#define NOTIFY_BYE_BYE 'x' + +typedef struct _stack_t { + struct _stack_t *next; + char text[1]; // this should be the length of string_size +} stack_t; + + +static unsigned int g_stringsize; +static stack_t **g_stacktop; +static char *g_variables; + +static int __stdcall popstring(char *str) UNUSED; // 0 on success, 1 on empty stack +static void __stdcall pushstring(const char *str) UNUSED; +static char * __stdcall getuservariable(const int varnum) UNUSED; +static void __stdcall setuservariable(const int varnum, const char *var) UNUSED; + +enum +{ +INST_0, // $0 +INST_1, // $1 +INST_2, // $2 +INST_3, // $3 +INST_4, // $4 +INST_5, // $5 +INST_6, // $6 +INST_7, // $7 +INST_8, // $8 +INST_9, // $9 +INST_R0, // $R0 +INST_R1, // $R1 +INST_R2, // $R2 +INST_R3, // $R3 +INST_R4, // $R4 +INST_R5, // $R5 +INST_R6, // $R6 +INST_R7, // $R7 +INST_R8, // $R8 +INST_R9, // $R9 +INST_CMDLINE, // $CMDLINE +INST_INSTDIR, // $INSTDIR +INST_OUTDIR, // $OUTDIR +INST_EXEDIR, // $EXEDIR +INST_LANG, // $LANGUAGE +__INST_LAST +}; + +typedef struct { + int autoclose; + int all_user_var; + int exec_error; + int abort; + int exec_reboot; + int reboot_called; + int XXX_cur_insttype; // deprecated + int XXX_insttype_changed; // deprecated + int silent; + int instdir_error; + int rtl; + int errlvl; +} exec_flags_type; + +typedef struct { + exec_flags_type *exec_flags; + int (__stdcall *ExecuteCodeSegment)(int, HWND); + void (__stdcall *validate_filename)(char *); +} extra_parameters; + +// utility functions (not required but often useful) +static int __stdcall popstring(char *str) +{ + stack_t *th; + if (!g_stacktop || !*g_stacktop) return 1; + th=(*g_stacktop); + lstrcpy(str,th->text); + *g_stacktop = th->next; + GlobalFree((HGLOBAL)th); + return 0; +} + +static void __stdcall pushstring(const char *str) +{ + stack_t *th; + if (!g_stacktop) return; + th=(stack_t*)GlobalAlloc(GPTR,sizeof(stack_t)+g_stringsize); + lstrcpyn(th->text,str,g_stringsize); + th->next=*g_stacktop; + *g_stacktop=th; +} + +static char * __stdcall getuservariable(const int varnum) +{ + if (varnum < 0 || varnum >= __INST_LAST) return NULL; + return g_variables+varnum*g_stringsize; +} + +static void __stdcall setuservariable(const int varnum, const char *var) +{ + if (var != NULL && varnum >= 0 && varnum < __INST_LAST) + lstrcpy(g_variables + varnum*g_stringsize, var); +} + + + +#endif//_EXDLL_H_ diff --git a/installer/NSIS/Contrib/InstallOptionsEx/io.dsp b/installer/NSIS/Contrib/InstallOptionsEx/io.dsp new file mode 100644 index 0000000..1c74387 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/io.dsp @@ -0,0 +1,224 @@ +# Microsoft Developer Studio Project File - Name="io" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=io - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "io.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "io.mak" CFG="io - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "io - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "io - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "io - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTOPTDLL_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /O1 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTOPTDLL_EXPORTS" /D "WIN32_LEAN_AND_MEAN" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib shlwapi.lib msvcrt.lib /nologo /entry:"DllMain" /dll /map /machine:I386 /nodefaultlib /out:"../../../NSIS/Plugins/InstallOptionsEx.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "io - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTOPTDLL_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTOPTDLL_EXPORTS" /YX /FD /GZ /c +# SUBTRACT CPP /Fr +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib shlwapi.lib /nologo /dll /debug /machine:I386 /out:"../../../NSIS/Plugins/InstallOptionsEx.dll" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "io - Win32 Release" +# Name "io - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\InstallerOptions.cpp + +!IF "$(CFG)" == "io - Win32 Release" + +!ELSEIF "$(CFG)" == "io - Win32 Debug" + +# ADD CPP /W3 /Od + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Group "Controls" + +# PROP Default_Filter "*.h" +# Begin Source File + +SOURCE=.\Controls\Button.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\CheckBox.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\ComboBox.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\DateTime.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\GroupBox.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\Image.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\IpAddress.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\Label.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\Link.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\ListBox.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\ListView.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\MonthCalendar.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\ProgressBar.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\RadioButton.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\RichText.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\StatusBar.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\Text.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\ToolBar.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\TrackBar.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\TreeView.h +# End Source File +# Begin Source File + +SOURCE=.\Controls\UpDown.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\exdll.h +# End Source File +# Begin Source File + +SOURCE=.\InstallerOptions.h +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\ioptdll.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/installer/NSIS/Contrib/InstallOptionsEx/io.dsw b/installer/NSIS/Contrib/InstallOptionsEx/io.dsw new file mode 100644 index 0000000..6400ecc --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/io.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "io"=.\io.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/installer/NSIS/Contrib/InstallOptionsEx/io.sln b/installer/NSIS/Contrib/InstallOptionsEx/io.sln new file mode 100644 index 0000000..1585cf2 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/io.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "io", "io.vcproj", "{80973FE4-5C0E-48F7-97EC-F6FEE6A12B31}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80973FE4-5C0E-48F7-97EC-F6FEE6A12B31}.Debug|Win32.ActiveCfg = Debug|Win32 + {80973FE4-5C0E-48F7-97EC-F6FEE6A12B31}.Debug|Win32.Build.0 = Debug|Win32 + {80973FE4-5C0E-48F7-97EC-F6FEE6A12B31}.Release|Win32.ActiveCfg = Release|Win32 + {80973FE4-5C0E-48F7-97EC-F6FEE6A12B31}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/installer/NSIS/Contrib/InstallOptionsEx/io.vcproj b/installer/NSIS/Contrib/InstallOptionsEx/io.vcproj new file mode 100644 index 0000000..a6c156d --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/io.vcproj @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/NSIS/Contrib/InstallOptionsEx/ioptdll.rc b/installer/NSIS/Contrib/InstallOptionsEx/ioptdll.rc new file mode 100644 index 0000000..27290a2 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/ioptdll.rc @@ -0,0 +1,94 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 57, 41 +STYLE DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 50 + TOPMARGIN, 7 + BOTTOMMARGIN, 34 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/installer/NSIS/Contrib/InstallOptionsEx/resource.h b/installer/NSIS/Contrib/InstallOptionsEx/resource.h new file mode 100644 index 0000000..843a653 --- /dev/null +++ b/installer/NSIS/Contrib/InstallOptionsEx/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ioptdll.rc +// +#define IDD_DIALOG1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/installer/NSIS/Contrib/Language files/Afrikaans.nlf b/installer/NSIS/Contrib/Language files/Afrikaans.nlf new file mode 100644 index 0000000..3c45431 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Afrikaans.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1078 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Friedel Wolff +# ^Branding +Nullsoft Installeerstelsel %s +# ^SetupCaption +$(^Name) Installasie +# ^UninstallCaption +$(^Name) Verwydering +# ^LicenseSubCaption +: Lisensie-ooreenkoms +# ^ComponentsSubCaption +: Installasiekeuses +# ^DirSubCaption +: Installasiegids +# ^InstallingSubCaption +: Installeer tans +# ^CompletedSubCaption +: Voltooid +# ^UnComponentsSubCaption +: Verwyderingkeuses +# ^UnDirSubCaption +: Verwyderinggids +# ^ConfirmSubCaption +: Bevestiging +# ^UninstallingSubCaption +: Verwyder tans +# ^UnCompletedSubCaption +: Voltooid +# ^BackBtn +< V&orige +# ^NextBtn +&Volgende > +# ^AgreeBtn +&Regso +# ^AcceptBtn +Ek &aanvaar die ooreenkoms +# ^DontAcceptBtn +Ek aan vaar &nie die ooreenkoms nie +# ^InstallBtn +&Installeer +# ^UninstallBtn +&Verwyder +# ^CancelBtn +Kanselleer +# ^CloseBtn +&Sluit af +# ^BrowseBtn +&Blaai... +# ^ShowDetailsBtn +&Wys detail +# ^ClickNext +Klik op Volgende om verder te gaan. +# ^ClickInstall +Klik op Installeer om die installasie te begin. +# ^ClickUninstall +Klik op Verwyder om die verwydering te begin. +# ^Name +Naam +# ^Completed +Voltooid +# ^LicenseText +Lees die lisensieooreenkoms voordat u $(^NameDA) installeer. Klik op Regso as u die ooreenkoms aanvaar. +# ^LicenseTextCB +Lees die lisensieooreenkoms voordat u $(^NameDA) installeer. Merk die blokkie hieronder as u die ooreenkoms aanvaar. $_CLICK +# ^LicenseTextRB +Lees die lisensieooreenkoms voordat u $(^NameDA) installeer. Kies die eerste keuse hieronder as u die ooreenkoms aanvaar. $_CLICK +# ^UnLicenseText +Lees die lisensieooreenkoms voordat u $(^NameDA) verwyder. Klik op Regso als u die ooreenkoms aanvaar. +# ^UnLicenseTextCB +Lees die lisensieooreenkoms voordat u $(^NameDA) verwyder. Merk die blokkie hieronder as u die ooreenkoms aanvaar. $_CLICK +# ^UnLicenseTextRB +Lees die lisensieooreenkoms voordat u $(^NameDA) verwyder. KIes die eerste keuse hieronder as u die ooreenkoms aanvaar. $_CLICK +# ^Custom +Aangepast +# ^ComponentsText +Kies die komponente wat u wil installeer en deselekteer di wat u nie wil installeer nie. $_CLICK +# ^ComponentsSubText1 +Kies die installasietipe: +# ^ComponentsSubText2_NoInstTypes +Kies die komponente wat genstalleer moet word: +# ^ComponentsSubText2 +Of kies die komponente wat genstalleer moet word: +# ^UnComponentsText +Kies die komponente wat u wil verwyder en deselekteer di wat u nie wil verwyder nie. $_CLICK +# ^UnComponentsSubText1 +Kies die verwyderingstipe: +# ^UnComponentsSubText2_NoInstTypes +Kies die komponente wat verwyder moet word: +# ^UnComponentsSubText2 +Of kies die komponente wat verwyder moet word: +# ^DirText +$(^NameDA) sal in die volgende gids genstalleer word. Om elders te installeer, klik op Blaai en kies 'n ander een. $_CLICK +# ^DirSubText +Installasiegids +# ^DirBrowseText +Kies die gids om $(^NameDA) in te installeer: +# ^UnDirText +$(^NameDA) gaan uit die volgende gids verwyder word. Om van elders af te verwyder, klik op Blaai en kies 'n ander gids. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Kies die gids om $(^NameDA) uit te verwyder: +# ^SpaceAvailable +"Beskikbare spasie: " +# ^SpaceRequired +"Vereiste spasie: " +# ^UninstallingText +$(^NameDA) sal uit die volgende gids verwyder word. $_CLICK +# ^UninstallingSubText +Verwydering uit: +# ^FileError +Fout met skryf na ler: \r\n\r\n$0\r\n\r\nKlik Staak om de installasie te stop,\r\nProbeer weer om weer te probeer of\r\nIgnoreer om di ler oor te slaan. +# ^FileError_NoIgnore +Fout met skryf na ler: \r\n\r\n$0\r\n\r\nKlik Probeer weer om op nuut te probeer, of \r\nKanselleer om die installasie te stop. +# ^CantWrite +"Kon nie skyf nie: " +# ^CopyFailed +Kopiring het misluk +# ^CopyTo +"Kopieer na " +# ^Registering +"Registreer tans: " +# ^Unregistering +"Deregistreer tans: " +# ^SymbolNotFound +"Kon nie simbool vind nie: " +# ^CouldNotLoad +"Kon nie laai nie: " +# ^CreateFolder +"Skep gids: " +# ^CreateShortcut +"Maak kortpad: " +# ^CreatedUninstaller +"Verwyderingsprogram gemaak: " +# ^Delete +"Verwyder ler: " +# ^DeleteOnReboot +"Verwyder na herbegin van rekenaar: " +# ^ErrorCreatingShortcut +"Fout met maak van kortpad: " +# ^ErrorCreating +"Fout met skep: " +# ^ErrorDecompressing +Fout met uitpak van data! Korrupte installasieler? +# ^ErrorRegistering +Fout met registrasie van DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Voer uit: " +# ^Extract +"Pak uit: " +# ^ErrorWriting +"Uitpak: fout met skryf na ler " +# ^InvalidOpcode +Installasieprogram korrup: ongeldige opcode +# ^NoOLE +"Geen OLE vir: " +# ^OutputFolder +"Afvoergids: " +# ^RemoveFolder +"Verwyder gids: " +# ^RenameOnReboot +"Hernoem na herbegin van rekenaar: " +# ^Rename +"Hernoem: " +# ^Skipped +"Oorgeslaan: " +# ^CopyDetails +Kopieer detail na knipbord +# ^LogInstall +Boekstaaf die installasieproses +# ^Byte +G +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Afrikaans.nsh b/installer/NSIS/Contrib/Language files/Afrikaans.nsh new file mode 100644 index 0000000..18059c0 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Afrikaans.nsh @@ -0,0 +1,121 @@ +;Language: Afrikaans (1078) +;By Friedel Wolff + +!insertmacro LANGFILE "Afrikaans" "Afrikaans" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Welkom by die $(^NameDA) Installasieslimmerd" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Hierdie slimmerd lei mens deur die installasie van $(^NameDA).$\r$\n$\r$\nDit word aanbeveel dat u alle ander programme afsluit voor die begin van die installasie. Dit maak dit moontlik om die relevante stelsellers op te dateer sonder om die rekenaar te herlaai.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Welkom by die $(^NameDA) Verwyderingslimmerd" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Hierdie slimmerd lei mens deur die verwydering van $(^NameDA).$\r$\n$\r$\nVoor die verwydering begin word, maak seker dat $(^NameDA) nie loop nie.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lisensie-ooreenkoms" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Lees die lisensie-ooreenkoms voordat u $(^NameDA) installeer." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Klik op Regso om verder te gaan as u die ooreenkoms aanvaar. U moet die ooreenkoms aanvaar om $(^NameDA) te installeer." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Merk die blokkie hier onder as u die ooreenkoms aanvaar. U moet die ooreenkoms aanvaar om $(^NameDA) te installeer. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Kies die eerste keuse hieronder as u die ooreenkoms aanvaar. U moet die ooreenkoms aanvaar om $(^NameDA) te installeer. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lisensie-ooreenkoms" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Lees die lisensie-ooreenkoms voordat u $(^NameDA) verwyder." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Klik op Regso om verder te gaan as u die ooreenkoms aanvaar. U moet die ooreenkoms aanvaar om $(^NameDA) te verwyder." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Merk die kiesblokkie hieronder as u die ooreenkoms aanvaar. U moet die ooreenkoms aanvaar om $(^NameDA) te verwyder." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Kies die eerste keuse hieronder as u die ooreenkoms aanvaar. U moet die ooreenkoms aanvaar om $(^NameDA) te verwyder." +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Druk op Page Down om die res van die ooreenkoms te sien." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Kies komponente" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Kies watter komponente van $(^NameDA) genstalleer moet word." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Kies komponente" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Kies watter komponente van $(^NameDA) verwyder moet word." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beskrywing" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beweeg die muis oor 'n komponent om sy beskrywing te sien." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beweeg die muis oor 'n komponent om sy beskrywing te sien." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Kies installasieplek" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Kies die gids waarin u $(^NameDA) wil installeer." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Kies verwyderinggids" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Kies die gids waaruit u $(^NameDA) wil verwyder." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installeer tans" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Wag asb. terwyl $(^NameDA) genstalleer word." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installasie voltooid" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Die installasie is suksesvol voltooi." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installasie gestaak" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Die installasie is nie suksesvol voltooi nie." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Verwyder tans" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Wag asb. terwyl $(^NameDA) van u rekenaar verwyder word." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Verwydering voltooi" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Verwydering is suksesvol voltooi." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Verwydering gestaak" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Verwydering is nie suksesvol voltooi nie." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Voltooi van die $(^NameDA) Installasieslimmerd" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) is genstalleer op uw rekenaar.$\r$\n$\r$\nKlik op Voltooi om hierdie slimmerd af te sluit." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Die rekenaar moet oorbegin word om die installasie van $(^NameDA) te voltooi. Wil u nou oorbegin?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Voltooi van die $(^NameDA) Verwyderingslimmerd" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) is van u rekenaar verwyder.$\r$\n$\r$\nKlik op Voltooi om hierdie slimmerd af te sluit." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Die rekenaar moet oorbegin word om die verwydering van $(^NameDA) te voltooi. Wil u nou oorbegin?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Begin nou oor" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ek wil later self oorbegin" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Laat loop $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Wys Leesmy-ler" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Voltooi" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Kies gids in Begin-kieslys" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Kies 'n gids in die Begin-kieslys vir $(^NameDA) se kortpaaie." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Kies die gids in die Begin-kieslys waarin die program se kortpaaie geskep moet word. U kan ook 'n nuwe naam gee om 'n nuwe gids te skep." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Moenie kortpaaie maak nie" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Verwyder $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Verwyder $(^NameDA) van u rekenaar." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Wil u definitief die installasie van $(^Name) afsluit?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Wil u definitief die verwydering van $(^Name) afsluit?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Albanian.nlf b/installer/NSIS/Contrib/Language files/Albanian.nlf new file mode 100644 index 0000000..79028f6 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Albanian.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1052 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Prkthimi nga Besnik Bleta, besnik@programeshqip.org +# ^Branding +Sistemi Nullsoft pr Instalime %s +# ^SetupCaption +Rregullimi i $(^Name) +# ^UninstallCaption +instalimi i $(^Name) +# ^LicenseSubCaption +: Marrveshje Licence +# ^ComponentsSubCaption +: Mundsi Instalimi +# ^DirSubCaption +: Dosje Instalimi +# ^InstallingSubCaption +: Po instalohet +# ^CompletedSubCaption +: U plotsua +# ^UnComponentsSubCaption +: Mundsi instalimi +# ^UnDirSubCaption +: Dosje instalimi +# ^ConfirmSubCaption +: Ripohim +# ^UninstallingSubCaption +: Po instalohet +# ^UnCompletedSubCaption +: U plotsua +# ^BackBtn +< &Mbrapsht +# ^NextBtn +&Tjetri > +# ^AgreeBtn +&Pajtohem +# ^AcceptBtn +&I pranoj kushtet e Marrveshjes s Licenss +# ^DontAcceptBtn +&Nuk i pranoj kushtet e Marrveshjes s Licenss +# ^InstallBtn +&Instaloje +# ^UninstallBtn +&instaloje +# ^CancelBtn +Anuloje +# ^CloseBtn +&Mbylle +# ^BrowseBtn +Sh&fletoni... +# ^ShowDetailsBtn +Shfaq &hollsi +# ^ClickNext +Klikoni Tjetri pr t vazhduar. +# ^ClickInstall +Pr t filluar instalimin klikoni Instaloje. +# ^ClickUninstall +Pr t filluar instalimin klikoni instaloje. +# ^Name +Emr +# ^Completed +U plotsua +# ^LicenseText +Ju lutem, para instalimit t $(^NameDA), shqyrtoni marrveshjen e licencs. Nse i pranoni tr kushtet e marrveshjes, klikoni Pajtohem. +# ^LicenseTextCB +Ju lutem, para instalimit t $(^NameDA), shqyrtoni marrveshjen e licenss. Nse i pranoni tr kushtet e marrveshjes, klikoni kutizn m posht. $_CLICK +# ^LicenseTextRB +Ju lutem, para instalimit t $(^NameDA), shqyrtoni marrveshjen e licenss. Nse i pranoni tr kushtet e marrveshjes, przgjidhni mundsin e par m posht. $_CLICK +# ^UnLicenseText +Ju lutem, para instalimit t $(^NameDA), shqyrtoni marrveshjen e licenss. Nse i pranoni tr kushtet e marrveshjes, klikoni Pajtohem. +# ^UnLicenseTextCB +Ju lutem, para instalimit t $(^NameDA), shqyrtoni marrveshjen e licenss. Nse i pranoni tr kushtet e marrveshjes, klikoni kutizn m posht. $_CLICK +# ^UnLicenseTextRB +Ju lutem, para instalimit t $(^NameDA), shqyrtoni marrveshjen e licenss. Nse i pranoni tr kushtet e marrveshjes, przgjidhni mundsin e par m posht. $_CLICK +# ^Custom +Vetjake +# ^ComponentsText +U vini shenj prbrsve q doni t instalohen dhe hiquani shenjn prbrsvet q nuk doni t instalohen. $_CLICK +# ^ComponentsSubText1 +Przgjidhni llojin e instalimit: +# ^ComponentsSubText2_NoInstTypes +Przgjidhni prbrsit pr instalim: +# ^ComponentsSubText2 +Ose, przgjidhni prbrsit e mundshm q doni t instalohen: +# ^UnComponentsText +U vini shenj prbrsve q doni t instalohen dhe hiquni shenjn prbrsve q nuk doni t instalohen. $_CLICK +# ^UnComponentsSubText1 +Przgjidhni llojin e instalimit: +# ^UnComponentsSubText2_NoInstTypes +Przgjidhni prbrsit pr instalim: +# ^UnComponentsSubText2 +Ose, przgjidhni prbrsit e mundshm q doni t instalohen: +# ^DirText +Rregullimi do ta instaloj $(^NameDA) n dosjen vijuese. Pr instalim n nj dosje tjetr, klikoni Shfletoni dhe przgjidhni nj tjetr dosje. $_CLICK +# ^DirSubText +Dosje Vendmbrritje +# ^DirBrowseText +Przgjidhni dosjen ku t instalohet $(^NameDA): +# ^UnDirText +Rregullimi do t instaloj $(^NameDA) prej dosjes vijuese. Pr instalim prej nj dosjeje tjetr, klikoni Shfletoni dhe przgjidhni nj tjetr dosje. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Przgjidhni dosjen prej nga ku t instalohet $(^NameDA): +# ^SpaceAvailable +"Hapsir e mundshme: " +# ^SpaceRequired +"Hapsir e nevojshme: " +# ^UninstallingText +$(^NameDA) do t instalohet prej dosjes vijuese. $_CLICK +# ^UninstallingSubText +Po instalohet prej: +# ^FileError +Gabim n hapje kartele pr shkrim: \r\n\r\n$0\r\n\r\nKlikoni Ndrprite pr t ndalur instalimin,\r\nRiprovo pr t provuar srish, ose\r\nShprfille pr t sanashkaluar kt kartel. +# ^FileError_NoIgnore +Gabim n hapje kartele pr shkrim: \r\n\r\n$0\r\n\r\nKlikoni Riprovo pr t provuar srish, ose\r\nAnulo pr t ndalur instalimin. +# ^CantWrite +"S'shkruaj dot: " +# ^CopyFailed +Kopjimi dshtoi +# ^CopyTo +"Kopjo tek " +# ^Registering +"Regjistrim: " +# ^Unregistering +"regjistrim: " +# ^SymbolNotFound +"S'u gjet dot simbol: " +# ^CouldNotLoad +"S'u ngarkua dot: " +# ^CreateFolder +"Krijo dosje: " +# ^CreateShortcut +"Krijo shkurtore: " +# ^CreatedUninstaller +"Krijo instalues: " +# ^Delete +"Fshi kartel: " +# ^DeleteOnReboot +"Fshi gjat rinisjes: " +# ^ErrorCreatingShortcut +"Gabim n krijim shkurtoresh: " +# ^ErrorCreating +"Gabim n krijimin e: " +# ^ErrorDecompressing +Gabim n ngjeshje t dhnash! Instalues i dmtuar? +# ^ErrorRegistering +Gabim n regjistrim DLL-je +# ^ExecShell +"ExecShell: " +# ^Exec +"Ekzekuto: " +# ^Extract +"Prfto: " +# ^ErrorWriting +"Prftim: gabim n shkrim te kartela " +# ^InvalidOpcode +Instalues i dmtuar: opcode i pavlefshm +# ^NoOLE +"Pa OLE pr: " +# ^OutputFolder +"Dosje prfundimesh: " +# ^RemoveFolder +"Hiq dosjen: " +# ^RenameOnReboot +"Riemrtoje gjat rinisjes: " +# ^Rename +"Riemrtoje: " +# ^Skipped +"U anashkalua: " +# ^CopyDetails +Kopjo Hollsira Te Clipboard +# ^LogInstall +Regjistro procesin e instalimit +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Albanian.nsh b/installer/NSIS/Contrib/Language files/Albanian.nsh new file mode 100644 index 0000000..154761f --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Albanian.nsh @@ -0,0 +1,129 @@ +;Language: Albanian (1052) +;Translation Besnik Bleta, besnik@programeshqip.org + +!insertmacro LANGFILE "Albanian" "Albanian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Mirsevini te Rregullimi i $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Ky do t'ju udhheq gjat instalimit t $(^NameDA).$\r$\n$\r$\nKshillohet q t mbyllni tr zbatimet e tjera para se t nisni Rregullimin. Kjo bn t mundur prditsimin e kartelave t rndsishme t sistemit pa u dashur t riniset kompjuteri juaj.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Mirsevini te instalimi i $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Ky do t'ju udhheq gjat instalimit t $(^NameDA).$\r$\n$\r$\nPara nisjes s instalimit, sigurohuni q $(^NameDA) nuk sht duke xhiruar.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Marrveshje Licence" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Ju lutem shqyrtoni kushtet e licencs prpara se t instaloni $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Nse i pranoni kushtet e marrveshjes, klikoni Pajtohem pr t vazhduar. Duhet ta pranoni marrveshjen pr t instaluar $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Nse pranoni kushtet e marrveshjes, klikoni kutizn m posht. Duhet t pranoni marrveshjen pr t instaluar $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Nse pranoni kushtet e marrveshjes, przgjidhni m posht mundsin e par. Duhet t pranoni marrveshjen pr t instaluar $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Marrveshje Licence" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Ju lutem shqyrtoni kushtet e licencs prpara instalimit t $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Nse i pranoni kushtet e marrveshjes, klikoni Pajtohem pr t vazhduar. Duhet t pranoni marrveshjen pr t instaluar $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Nse pranoni kushtet e marrveshjes, klikoni kutizn m posht. Duhet t pranoni marrveshjen pr t instaluar $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Nse pranoni kushtet e marrveshjes, przgjidhni mundsin e par m posht. Duhet t pranoni marrveshjen pr t instaluar $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Shtypni Page Down pr t par pjesn e mbetur t marrveshjes." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Przgjidhni Prbrs" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Zgjidhni cilat an t $(^NameDA) doni t instalohen." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Zgjidhni Prbrsa" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Zgjidhni cilat an t $(^NameDA) doni t instalohen." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Prshkrim" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Pr t par prshkrimin e nj prbrsi, vendosni miun prsipr tij." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Pr t par prshkrimin e nj prbrsi, vendosni miun prsipr tij." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Zgjidhni Vend Instalimi" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Zgjidhni dosjen tek e cila t instalohet $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Zgjidhni Vend instalimi" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Zgjidhni dosjen prej s cils t instalohet $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Po instalohet" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Ju lutem prisni ndrkoh q $(^NameDA) instalohet." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalim i Plotsuar" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Rregullimi u plotsua me sukses." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalimi u Ndrpre" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Rregullimi nuk u plotsua me sukses." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "instalim" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Ju lutem prisni ndrsa $(^NameDA) instalohet." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "instalim i Plot" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "instalimi u plotsua me sukses." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "instalimi u Ndrpre" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "instalimi nuk plotsua me sukses." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Po plotsoj Rregullimin e $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) u instalua n kompjuterin tuaj.$\r$\n$\r$\nPr mbylljen e procesit, klikoni Prfundoje." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Q t mund t plotsohet instalimi i $(^NameDA) kompjuteri juaj duhet t riniset. Doni ta rinisni tani?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Po plotsoj instalimin e $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) u instalua prej kompjuterit tuaj.$\r$\n$\r$\nPr mbylljen e procesit, klikoni Prfundoje." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Kompjuteri juaj duhet t riniset q t mund t plotsohet instalimi i $(^NameDA). Doni ta rinisni tani?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Rinise tani" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Dua ta rinis dorazi m von" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Nis $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Shfaq Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Prfundoje" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Zgjidhni Dosje Menuje Start" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Zgjidhni nj dosje Menuje Start pr shkurtore $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Przgjidhni dosjen e Menus Start n t ciln do t donit t krijonit shkurtoret pr programin. Mundeni edhe t jepni nj emr pr t krijuar nj dosje t re." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Mos krijo shkurtore" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "instalo $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Hiqeni $(^NameDA) prej kompjuterit tuaj." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Jeni i sigurt q doni t dilni nga Rregullimi i $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Jeni i sigurt q doni t dilni nga instalimi i $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Zgjidhni Prdoruesa" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Zgjidhni pr cilt prdoruesa doni t instalohet $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Przgjidhni n doni t instalohet $(^NameDA) vetm pr veten tuaj apo pr tr prdoruesit e ktij kompjuteri. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Instaloje pr kdo n kt kompjuter" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Instaloje vetm pr mua" +!endif diff --git a/installer/NSIS/Contrib/Language files/Arabic.nlf b/installer/NSIS/Contrib/Language files/Arabic.nlf new file mode 100644 index 0000000..ebb0e28 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Arabic.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1025 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1256 +# RTL - anything else than RTL means LTR +RTL +# Translation by asdfuae@msn.com, updated by Rami Kattan +# ^Branding + %s +# ^SetupCaption + $(^Name) +# ^UninstallCaption + $(^Name) +# ^LicenseSubCaption + : +# ^ComponentsSubCaption + : +# ^DirSubCaption + : +# ^InstallingSubCaption + : +# ^CompletedSubCaption + : +# ^UnComponentsSubCaption + : +# ^UnDirSubCaption + : +# ^ConfirmSubCaption + : +# ^UninstallingSubCaption + : +# ^UnCompletedSubCaption + : +# ^BackBtn +< & +# ^NextBtn +& > +# ^AgreeBtn +& +# ^AcceptBtn +& +# ^DontAcceptBtn +& +# ^InstallBtn +& +# ^UninstallBtn +& +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +&... +# ^ShowDetailsBtn +& +# ^ClickNext + . +# ^ClickInstall + . +# ^ClickUninstall + . +# ^Name + +# ^Completed + +# ^LicenseText + $(^NameDA). ɡ . +# ^LicenseTextCB + $(^NameDA). ɡ . $_CLICK. +# ^LicenseTextRB + $(^NameDA). ɡ . $_CLICK +# ^UnLicenseText + $(^NameDA). ɡ . +# ^UnLicenseTextCB + $(^NameDA). ɡ . $_CLICK +# ^UnLicenseTextRB + $(^NameDA). ɡ . $_CLICK +# ^Custom + +# ^ComponentsText + . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 + : +# ^UnComponentsText + . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 + : +# ^DirText + $(^NameDA) . ѡ . $_CLICK +# ^DirSubText + +# ^DirBrowseText + $(^NameDA) : +# ^UnDirText + $(^NameDA) . ѡ . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + $(^NameDA) : +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText + $(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\t\"$0\"\r\n ȡ\r\n ݡ\r\n +# ^FileError_NoIgnore + : \r\n\t\"$0\"\r\n ݡ \r\n +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +": " +# ^Unregistering +" : " +# ^SymbolNotFound +" :" +# ^CouldNotLoad +" :" +# ^CreateFolder +" " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" :" +# ^ErrorDecompressing + ! ݿ +# ^ErrorRegistering + DLL +# ^ExecShell +" ExecShell:" +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + : +# ^NoOLE +" OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +" : " +# ^Skipped +": " +# ^CopyDetails + +# ^LogInstall + +# ^Byte + +# ^Kilo + +# ^Mega + +# ^Giga + \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Arabic.nsh b/installer/NSIS/Contrib/Language files/Arabic.nsh new file mode 100644 index 0000000..abb1441 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Arabic.nsh @@ -0,0 +1,122 @@ +;Language: Arabic (1025) +;Translation by asdfuae@msn.com +;updated by Rami Kattan + +!insertmacro LANGFILE "Arabic" "Arabic" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA) " + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n ɡ $(^NameDA) .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " . $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " . $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " ɡ . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " ɡ . $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " ɡ . $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " ɡ . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " " +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " " + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " " + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT " $(^NameDA) $\r$\n$\r$\n ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " $(^NameDA). " +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " $(^NameDA). " +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " " + ${LangFileString} MUI_TEXT_FINISH_RUN "& $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "& " + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " $(^Name)" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name)" +!endif diff --git a/installer/NSIS/Contrib/Language files/Basque.nlf b/installer/NSIS/Contrib/Language files/Basque.nlf new file mode 100644 index 0000000..d4230c5 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Basque.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1069 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Iaki San Vicente +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) -ren Instalazioa +# ^UninstallCaption +$(^Name) -ren Ezabaketa +# ^LicenseSubCaption +: Lizentzia hitzarmen agiria +# ^ComponentsSubCaption +: Instalazio aukerak +# ^DirSubCaption +: Instalazio karpeta +# ^InstallingSubCaption +: Instalatzen +# ^CompletedSubCaption +: Instalazioa burututa +# ^UnComponentsSubCaption +: Ezabaketa aukerak +# ^UnDirSubCaption +: Ezabaketa direktorioa +# ^ConfirmSubCaption +: Berretsi ezabaketa +# ^UninstallingSubCaption +: Ezabatzen +# ^UnCompletedSubCaption +: Ezabaketa burututa +# ^BackBtn +< &Atzera +# ^NextBtn +&Aurrera > +# ^AgreeBtn +Onartu +# ^AcceptBtn +Lizentzia hitzarmenaren baldintzak onartzen ditut. +# ^DontAcceptBtn +Ez ditut lizentzia hitzarmenaren baldintzak onartzen. +# ^InstallBtn +&Instalatu +# ^UninstallBtn +&Ezabatu +# ^CancelBtn +Ezeztatu +# ^CloseBtn +&Itxi +# ^BrowseBtn +&Arakatu... +# ^ShowDetailsBtn +Ikusi &zehaztasunak +# ^ClickNext +Sakatu Aurrera jarraitzeko. +# ^ClickInstall +Sakatu Instalatu instalazioarekin hasteko. +# ^ClickUninstall +Sakatu Ezabatu ezabaketarekin hasteko. +# ^Name +Izena +# ^Completed +Osatuta +# ^LicenseText +Mesedez, aztertu lizentzia hitzarmena $(^NameDA) instalatu aurretik. Baldintza guztiak onartzen badituzu, sakatu Onartu. +# ^LicenseTextCB +Mesedez, aztertu lizentzia hitzarmena $(^NameDA) instalatu aurretik. Baldintza guztiak onartzen badituzu, nabarmendu azpiko laukitxoa. $_CLICK +# ^LicenseTextRB +Mesedez, aztertu lizentzia hitzarmena $(^NameDA) instalatu aurretik. Baldintza guztiak onartzen badituzu, hautatu azpian lehen aukera. $_CLICK +# ^UnLicenseText +Mesedez, aztertu lizentzia hitzarmena $(^NameDA) ezabatu aurretik. Baldintza guztiak onartzen badituzu, sakatu Onartu. +# ^UnLicenseTextCB +Mesedez, aztertu lizentzia hitzarmena $(^NameDA) ezabatu aurretik. Baldintza guztiak onartzen badituzu, nabarmendu azpiko laukitxoa. $_CLICK. +# ^UnLicenseTextRB +Mesedez, aztertu lizentzia hitzarmena $(^NameDA) ezabatu aurretik. Baldintza guztiak onartzen badituzu, hautatu azpian lehen aukera. $_CLICK +# ^Custom +Norberaren nahien arabera +# ^ComponentsText +Nabarmendu instalatu nahi diren osagaiak, eta utzi zuri instalatu nahi ez direnak. $_CLICK +# ^ComponentsSubText1 +Hautatu instalazio mota: +# ^ComponentsSubText2_NoInstTypes +Hautatu instalatu beharreko osagaiak: +# ^ComponentsSubText2 +Edo hautatu instalatu beharreko aukerazko osagaiak: +# ^UnComponentsText +Nabarmendu ezabatu nahi diren osagaiak, eta utzi zuri ezabatu nahi ez direnak. $_CLICK +# ^UnComponentsSubText1 +Hautatu ezabaketa mota: +# ^UnComponentsSubText2_NoInstTypes +Hautatu ezabatu beharreko osagaiak: +# ^UnComponentsSubText2 +Edo hautatu ezabatu beharreko aukerazko osagaiak: +# ^DirText +Instalazio programak $(^NameDA) honako karpetan instalatuko du. Beste karpeta batean instalatzeko, sakatu Arakatu eta aukeratu beste bat. $_CLICK +# ^DirSubText +Helburu karpeta +# ^DirBrowseText +Aukeratu $(^NameDA) instalatuko den karpeta: +# ^UnDirText +Instalazio programak $(^NameDA) honako karpetatik ezabatuko du. Beste karpeta batetik ezabatzeko, sakatu Arakatu eta aukeratu beste bat. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Aukeratu $(^NameDA) zein karpetatik ezabatuko den: +# ^SpaceAvailable +Leku erabilgarria: +# ^SpaceRequired +Behar den lekua: +# ^UninstallingText +$(^NameDA) ondorengo karpetan instalatuko da. $_CLICK +# ^UninstallingSubTex +Ezabatzen honako karpetatik: +# ^FileError +Hutsegitea idazteko fitxategia irekitzean: \r\n\t"$0"\r\nSakatu Irten instalazioa ,\r\nsaiatu berriz fitxategi hau berriz idazten saiatzeko, u\r\njarraitu fitxategi hau alde batera utzita aurrera egiteko +# ^FileError_NoIgnore +Hutsegitea idazteko fitxategia irekitzean: \r\n\t"$0"\r\nsaiatu berriz fitxategi hau berriz idazten saiatzeko ,\r\nSakatu Irten instalazioa +# ^CantWrite +"Ezin izan da idatzi: " +# ^CopyFailed +Kopiatzeak hutsegin du +# ^CopyTo +"Kopiatu hona " +# ^Registering +"Erregistratzen: " +# ^Unregistering +"Erregistroa ezabatzen: " +# ^SymbolNotFound +"Ikurra ezin izan da aurkitu: " +# ^CouldNotLoad +"Ezin izan da kargatu: " +# ^CreateFolder +"Sortu karpeta: " +# ^CreateShortcut +"Sortu lasterbidea: " +# ^CreatedUninstaller +"Sortu ezabatzailea: " +# ^Delete +"Ezabatu fitxategia: " +# ^DeleteOnReboot +"Ezabatu berrabiarazitakoan: " +# ^ErrorCreatingShortcut +"Hutsegitea lasterbidea sortzerakoan: " +# ^ErrorCreating +"Hutsegitea sortzerakoan: " +# ^ErrorDecompressing +Hutsegitea datuak deskomprimatzean! Instalatzailea okerra? +# ^ErrorRegistering +Hutsegitea DLL erregistratzerakoan +# ^ExecShell +"Exekutatu agindua: " +# ^Exec +"Exekutatu: " +# ^Extract +"Kanporatu: " +# ^ErrorWriting +"Kanporaketa: hutsegitea fitxategira idazterakoan " +# ^InvalidOpcode +Instalatzailea okerra: ekintza kodea ez da baliozkoa +# ^NoOLE +"OLE-rik ez honentzako: " +# ^OutputFolder +"Irteera karpeta: " +# ^RemoveFolder +"Ezabatu karpeta: " +# ^RenameOnReboot +"Berrizendatu berrabiarazitakoan: " +# ^Rename +"Berrizendatu: " +# ^Skipped +"Alde batera utzitakoa: " +# ^CopyDetails +Kopiatu xehetasunak arbelera +# ^LogInstall +Instalazio prozesuaren erregistroa gorde +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Basque.nsh b/installer/NSIS/Contrib/Language files/Basque.nsh new file mode 100644 index 0000000..6f41779 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Basque.nsh @@ -0,0 +1,121 @@ +;Language: Basque (1069) +;By Iaki San Vicente + +!insertmacro LANGFILE "Basque" "Euskera" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Ongi etorri $(^NameDA) -ren instalazio programara" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Programa honek $(^NameDA) zure ordenagailuan instalatuko du.$\r$\n$\r$\nAholkatzen da instalazioarekin hasi aurretik beste aplikazio guztiak ixtea. Honek sistemarekin erlazionatuta dauden fitxategien eguneratzea ahalbidetuko du, ordenagailua berrabiarazi beharrik izan gabe.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Ongi etorri $(^NameDA) -ren ezabaketa programara" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Laguntzaile honek $(^NameDA)-ren ezabaketa prozesuan zehar gidatuko zaitu.$\r$\n$\r$\nEzabaketa hasi aurretik, ziurtatu $(^NameDA) martxan ez dagoela .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lizentzia hitzarmena" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Mesedez aztertu lizentziaren baldintzak $(^NameDA) instalatu aurretik." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Baldintzak onartzen badituzu, sakatu Onartu aurrera egiteko. Hitzarmena onartzea ezinbestekoa da $(^NameDA) instalatzeko." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Baldintzak onartzen badituzu, nabarmendu azpiko laukitxoa. Hitzarmena onartzea ezinbestekoa da $(^NameDA) instalatzeko. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Baldintzak onartzen badituzu, hautatu azpian lehen aukera. Hitzarmena onartzea ezinbestekoa da $(^NameDA) instalatzeko. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lizentzia hitzarmena" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Mesedez aztertu lizentziaren baldintzak $(^NameDA) ezabatu aurretik." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Baldintzak onartzen badituzu, sakatu Onartu aurrera egiteko. Hitzarmena onartzea ezinbestekoa da $(^NameDA) ezabatzeko." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Baldintzak onartzen badituzu, nabarmendu azpiko laukitxoa. Hitzarmena onartzea ezinbestekoa da $(^NameDA) ezabatzeko. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Baldintzak onartzen badituzu, hautatu azpian lehen aukera. Hitzarmena onartzea ezinbestekoa da $(^NameDA) ezabatzeko. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Sakatu Av Pg hitzarmenaren gainontzeko atalak ikusteko." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Osagaien hautatzea" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Hautatu $(^NameDA)-ren zein ezaugarri instalatu nahi duzun." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Osagaien hautatzea" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Hautatu $(^NameDA)-ren zein ezaugarri ezabatu nahi duzun." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Azalpena" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Jarri sagua osagai baten gainean dagokion azalpena ikusteko." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Jarri sagua osagai baten gainean dagokion azalpena ikusteko." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Hautatu instalazioaren lekua" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Aukeratu $(^NameDA) instalatzeko karpeta." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Aukeratu ezabatuko den karpeta" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Aukeratu $(^NameDA) zein karpetatik ezabatuko den." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalatzen" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Mesedez itxoin $(^NameDA) instalatzen den bitartean." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalazioa burututa" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Instalazioa zuzen burutu da." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalazioa ezeztatua" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Instalazioa ez da zuzen burutu." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Ezabatzen" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Mesedez itxoin $(^NameDA) ezabatzen den bitartean." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Ezabatzea burututa" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Ezabatzea zuzen burutu da." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Ezabatzea ezeztatuta" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Ezabatzea ez da zuzen burutu." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "$(^NameDA)-ren instalazio laguntzailea osatzen" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) zure sisteman instalatu da.$\r$\n$\r$\nSakatu Amaitu laguntzaile hau ixteko." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Zure sistema berrabiarazi behar duzu $(^NameDA)-ren instalazioa osatzeko. Orain Berrabiarazi nahi duzu?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "$(^NameDA)-ren ezabaketa laguntzailea osatzen" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) zure sistematik ezabatu da.$\r$\n$\r$\nSakatu Amaitu laguntzaile hau ixteko." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Zure ordenagailuak berrabiarazia izan behar du $(^NameDA)-ren ezabaketa osatzeko. Orain Berrabiarazi nahi duzu?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Berrabiarazi orain" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Neuk berrabiarazi geroago" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Exekutatu $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Ikusi Readme.txt" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Amaitu" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Aukeratu Hasiera Menuko karpeta" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Aukeratu Hasiera Menuko karpeta bat $(^NameDA)-ren lasterbideentzako." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Aukeratu Hasiera Menuko karpeta bat, non programaren lasterbideak instalatu nahi dituzun. Karpeta berri bat sortzeko izen bat ere adierazi dezakezu." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ez sortu lasterbiderik" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Ezabatu $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA) zure sistematik ezabatzen du." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Ziur zaude $(^Name)-ren instalaziotik irten nahi duzula?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Ziur zaude $(^Name)-ren ezabaketa laguntzailetik irten nahi duzula?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Belarusian.nlf b/installer/NSIS/Contrib/Language files/Belarusian.nlf new file mode 100644 index 0000000..8babe2c --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Belarusian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1059 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1251 +# RTL - anything else than RTL means LTR +- +# Translation by Sitnikov Vjacheslav [ glory_man@tut.by ] +# ^Branding +Nullsoft Install System %s +# ^SetupCaption + $(^Name) +# ^UninstallCaption + $(^Name) +# ^LicenseSubCaption +: ˳ +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< & +# ^NextBtn +& > +# ^AgreeBtn +& +# ^AcceptBtn + & ˳ +# ^DontAcceptBtn + & ˳ +# ^InstallBtn +& +# ^UninstallBtn +& +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +& ... +# ^ShowDetailsBtn +&... +# ^ClickNext + "", . +# ^ClickInstall + "" . +# ^ClickUninstall + "" . +# ^Name + +# ^Completed + +# ^LicenseText + , ˳ $(^NameDA). ˳ , "". +# ^LicenseTextCB + , ˳ $(^NameDA). ˳ , . $_CLICK +# ^LicenseTextRB + , ˳ $(^NameDA). ˳ , i. $_CLICK +# ^UnLicenseText + , ˳ $(^NameDA). ˳ , ii "". +# ^UnLicenseTextCB + , ˳ $(^NameDA). ˳ , . $_CLICK +# ^UnLicenseTextRB + , ˳ $(^NameDA). ˳ , i. $_CLICK +# ^Custom + +# ^ComponentsText + , i i. $_CLICK +# ^ComponentsSubText1 + i: +# ^ComponentsSubText2_NoInstTypes + , i i: +# ^ComponentsSubText2 + , i i : +# ^UnComponentsText + , i i, i ii , , i . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 + : +# ^DirText + $(^NameDA) . i i , ii "" i . $_CLICK +# ^DirSubText + i +# ^DirBrowseText + i $(^NameDA): +# ^UnDirText + i $(^NameDA) . i , ii "" i . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + , i $(^NameDA): +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText + i $(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + i: \r\n\r\n$0\r\n\r\nii "i", i ;\r\n"", i ;\r\n"", i . +# ^FileError_NoIgnore + i: \r\n\r\n$0\r\n\r\nii "", i ;\r\n"", i . +# ^CantWrite +" i: " +# ^CopyFailed +i +# ^CopyTo +"i " +# ^Registering +"i: " +# ^Unregistering +" ii: " +# ^SymbolNotFound +" i: " +# ^CouldNotLoad +" i: " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" i : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + . . +# ^ErrorRegistering + (DLL) +# ^ExecShell +" : " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": i " +# ^InvalidOpcode + : i +# ^NoOLE +" OLE : " +# ^OutputFolder +" i: " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" i : " +# ^Rename +": " +# ^Skipped +": " +# ^CopyDetails +i i +# ^LogInstall +i i +# byte + +# kilo + +# mega + +# giga + \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Belarusian.nsh b/installer/NSIS/Contrib/Language files/Belarusian.nsh new file mode 100644 index 0000000..b31e696 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Belarusian.nsh @@ -0,0 +1,121 @@ +;Language: Belarusian (1059) +;Translated by Sitnikov Vjacheslav [ glory_man@tut.by ] + +!insertmacro LANGFILE "Belarusian" "Byelorussian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " i $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n i , . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " i $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n , $(^NameDA) .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "ii " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " , ˳ i $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "i ii , $\"$\". ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "i ii , . . $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "i ˳ , . . $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "˳ " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " , ˳ $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " ˳ , $\"$\". . $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " ˳ , . . $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " ˳ , . . $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " i $\"PageUp$\" i $\"PageDown$\" ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " , 븢" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA), ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA), ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " , ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " , ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " , $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE " " + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE ", , $(^NameDA) ..." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE ", , $(^NameDA) ..." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n $\"$\" ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " $(^NameDA), . ֳ ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n $\"$\" ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " $(^NameDA), . ֳ ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW ", " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER ", " + ${LangFileString} MUI_TEXT_FINISH_RUN "& $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "& " + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " $\"$\"" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " $\"$\" ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " $\"$\", . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Bosnian.nlf b/installer/NSIS/Contrib/Language files/Bosnian.nlf new file mode 100644 index 0000000..be2ba62 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Bosnian.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +5146 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by Salih CAVKIC, cavkic@skynet.be +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Instalacija +# ^UninstallCaption +$(^Name) Uklanjanje +# ^LicenseSubCaption +: Licencno pravo koritenja +# ^ComponentsSubCaption +: Opcije instalacije +# ^DirSubCaption +: Izbor mape za instalaciju +# ^InstallingSubCaption +: Instaliranje +# ^CompletedSubCaption +: Zavreno +# ^UnComponentsSubCaption +: Opcije uklanjanja +# ^UnDirSubCaption +: Mapa uklanjanja +# ^ConfirmSubCaption +: Potvrda +# ^UninstallingSubCaption +: Uklanjanje +# ^UnCompletedSubCaption +: Zavreno uklanjanje +# ^BackBtn +< &Nazad +# ^NextBtn +&Dalje > +# ^AgreeBtn +&Prihvatam +# ^AcceptBtn +&Prihvatam uvjete licencnog ugovora +# ^DontAcceptBtn +&Ne prihvatam uvjete licencnog ugovora +# ^InstallBtn +&Instaliraj +# ^UninstallBtn +&Ukloni +# ^CancelBtn +Odustani +# ^CloseBtn +&Zatvori +# ^BrowseBtn +&Pregledaj... +# ^ShowDetailsBtn +Prikai &detalje +# ^ClickNext +Pritisnite dugme 'Dalje' za nastavak. +# ^ClickInstall +Pritisnite dugme 'Instaliraj' za poetak instalacije. +# ^ClickUninstall +Pritisnite dugme 'Ukloni' za poetak uklanjanja. +# ^Name +Ime +# ^Completed +Zavreno +# ^LicenseText +Molim proitajte licencu prije instaliranja programa $(^NameDA). Ukoliko prihvatate sve uvjete ugovora, odaberite 'Prihvatam'. +# ^LicenseTextCB +Molim proitajte licencu prije instaliranja programa $(^NameDA). Ukoliko prihvatate sve uvjete ugovora, oznaite donji kvadrati. $_CLICK +# ^LicenseTextRB +Molim proitajte licencu prije instaliranja programa $(^NameDA). Ukoliko prihvatate sve uvjete ugovora, odaberite prvu donju opciju. $_CLICK +# ^UnLicenseText +Molim proitajte licencu prije uklanjanja programa $(^NameDA). Ukoliko prihvatate sve uvjete ugovora, odaberite 'Prihvatam'. +# ^UnLicenseTextCB +Molim proitajte licencu prije uklanjanja programa $(^NameDA). Ako prihvatate sve uvjete ugovora, obiljeite donji kvadrati. $_CLICK +# ^UnLicenseTextRB +Molim proitajte licencu prije uklanjanja programa $(^NameDA). Ukoliko prihvatate sve uvjete ugovora, odaberite prvu opciju ispod. $_CLICK +# ^Custom +Podeavanje +# ^ComponentsText +Oznaite komponente koje elite instalirati. Instaliraju se samo oznaene komponente. Uklonite oznaku sa onih koje ne elite instalirati. $_CLICK +# ^ComponentsSubText1 +Izaberite tip instalacije: +# ^ComponentsSubText2_NoInstTypes +Odaberite komponente za instalaciju: +# ^ComponentsSubText2 +Ili po izboru oznaite komponente koje elite instalirati: +# ^UnComponentsText +Oznaite komponente koje elite ukloniti. Uklonite oznaku sa onih koje ne elite ukloniti. $_CLICK +# ^UnComponentsSubText1 +Izaberite tip uklanjanja: +# ^UnComponentsSubText2_NoInstTypes +Izaberite komponente za uklanjanje: +# ^UnComponentsSubText2 +Ili po izboru odaberite komponente koje elite da uklonite: +# ^DirText +Program $(^NameDA) e biti instaliran u sljedeu mapu. Za instalaciju na neku drugu mapu odaberite 'Pregledaj...' i odaberite drugu mapu. $_CLICK +# ^DirSubText +Odredina mapa +# ^DirBrowseText +Izaberite mapu u koju elite instalirati program $(^NameDA): +# ^UnDirText +Program $(^NameDA) e biti uklonjen iz navedene mape. Za uklanjanje iz druge mape odaberite 'Pregledaj...' i oznaite drugu mapu. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Izaberite mapu iz koje ete program $(^NameDA) ukloniti: +# ^SpaceAvailable +"Slobodno prostora na disku: " +# ^SpaceRequired +"Potrebno prostora na disku: " +# ^UninstallingText +Program $(^NameDA) e biti uklonjen iz sljedee mape. $_CLICK +# ^UninstallingSubText +Uklanjanje iz: +# ^FileError +Greka prilikom otvaranja datoteke za upisivanje: \r\n\t"$0"\r\n\"Odustani\" za prekid instalacije,\r\n\"Ponovi\" za ponovni pokuaj upisivanja, ili\r\n\"Ignorii\" za zanemarenje te datoteke +# ^FileError_NoIgnore +Greka prilikom otvaranja datoteke za upisivanje: \r\n\t"$0"\r\n\"Ponovi\" za ponovni pokuaj zapisivanja, ili\r\n\"Odustani\" za prekid instalacije +# ^CantWrite +"Nemogue upisati: " +# ^CopyFailed +Greka prilikom kopiranja +# ^CopyTo +"Kopiraj u " +# ^Registering +"Prijava: " +# ^Unregistering +"Odjava: " +# ^SymbolNotFound +"Nemogue nai simbol: " +# ^CouldNotLoad +"Nemogue uitavanje: " +# ^CreateFolder +"Napravi mapu: " +# ^CreateShortcut +"Napravi preicu: " +# ^CreatedUninstaller +"Program za uklanjanje: " +# ^Delete +"Obrii datoteku: " +# ^DeleteOnReboot +"Obrii prilikom ponovnog pokretanja: " +# ^ErrorCreatingShortcut +"Greka prilikom kreiranja preica: " +# ^ErrorCreating +"Greka prilikom kreiranja: " +# ^ErrorDecompressing +Greka prilikom otpakivanja podataka! Oteen instalacijski program? +# ^ErrorRegistering +Greka prilikom prijavljivanja DLLa +# ^ExecShell +"ExecShell: " +# ^Exec +"Izvri: " +# ^Extract +"Otpakuj: " +# ^ErrorWriting +"Otpakivanje: greka upisivanja u datoteku " +# ^InvalidOpcode +Oteena instalacijska datoteka: neispravna opkoda +# ^NoOLE +"Nema OLE za: " +# ^OutputFolder +"Izlazna mapa: " +# ^RemoveFolder +"Obrii mapu: " +# ^RenameOnReboot +"Preimenuj prilikom ponovnog startovanja: " +# ^Rename +"Preimenuj: " +# ^Skipped +"Preskoeno: " +# ^CopyDetails +Kopiraj detalje na Klembord +# ^LogInstall +Logiraj zapisnik procesa instalacije +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G + diff --git a/installer/NSIS/Contrib/Language files/Bosnian.nsh b/installer/NSIS/Contrib/Language files/Bosnian.nsh new file mode 100644 index 0000000..0d80c46 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Bosnian.nsh @@ -0,0 +1,121 @@ +;Language: Bosnian (5146) +;By Salih avki, cavkic@skynet.be + +!insertmacro LANGFILE "Bosnian" "Bosanski" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Dobrodoli u program za instalaciju $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Ovaj program e instalirati $(^NameDA) na Va sistem. $\r$\n$\r$\nPreporuujemo da neizostavno zatvorite sve druge otvorene programe prije nego to definitivno zaponete sa instaliranjem. To e omoguiti bolju nadogradnju odreenih sistemskih datoteka bez potrebe da Va raunar ponovo startujete. Instaliranje programa moete prekinuti pritiskom na dugme 'Odustani'.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Dobrodoli u postupak uklanjanja programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Ovaj e Vas vodi provesti kroz postupak uklanjanja programa $(^NameDA).$\r$\n$\r$\nPrije samog poetka, molim zatvorite program $(^NameDA) ukoliko je sluajno otvoren.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licencni ugovor" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Molim proitajte licencni ugovor $(^NameDA) prije instalacije programa." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Ako prihvatate uslove licence, odaberite 'Prihvatam' za nastavak. Morate prihvatiti licencu za instalaciju programa $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ako prihvatate uslove licence, oznaite donji kvadrati. Morate prihvatiti licencu za instalaciju programa $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ako prihvatate uslove licence, odaberite prvu donju opciju. Morate prihvatiti licencu za instalaciju programa $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licencni ugovor o pravu koritenja" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Molim proitajte licencu prije uklanjanja programa $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Ako prihvatate uslove licence, odaberite 'Prihvatam' za nastavak. Morate prihvatiti licencu za uklanjanje programa $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ako prihvatate uslove licence, oznaite donji kvadrati. Morate prihvatiti licencu za uklanjanje programa $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ako prihvatate uslove licence, odaberite prvu donju opciju. Morate prihvatiti licencu za uklanjanje programa $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Pritisnite 'Page Down' na tastaturi za ostatak licence." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Izbor komponenti za instalaciju" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Izaberite komponente programa $(^NameDA) koje elite instalirati." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Izbor komponenti za uklanjanje" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Izaberite komponente programa $(^NameDA) koje elite ukloniti." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Opis" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Postavite kursor od mia iznad komponente da biste vidjeli njezin opis." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Postavite kursor od mia iznad komponente da biste vidjeli njezin opis." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Odaberite odredite za instalaciju" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Odaberite mapu u koju elite instalirati program $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Izaberite polazite za uklanjanje" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Selektirajte mapu iz koje elite ukloniti program $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instaliranje" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Molim priekajte na zavretak instalacije programa $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Kraj instalacije" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Instalacija je u potpunosti uspjeno zavrila." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalacija je prekinuta" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Instalacija nije zavrila uspjeno." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Uklanjanje" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Molim Vas priekajte da vodi zavri uklanjanje $(^NameDA) programa." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Uklanjanje je zavreno" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Uklanjanje je u potpunosti zavrilo uspjeno." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Uklanjanje je prekinuto" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Uklanjanje nije zavrilo uspjeno." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Dovravanje instalacije programa $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Program $(^NameDA) je instaliran na Vae raunar.$\r$\n$\r$\nPritisnite dugme 'Kraj' za zavretak." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Raunar treba ponovno startovati za dovravanje instalacije programa $(^NameDA). elite li to uiniti sada?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Zavretak uklanjanja programa $(^NameDA) sa Vaeg sistema." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Program $(^NameDA) je uklonjen sa Vaeg raunara.$\r$\n$\r$\nPritisnite dugme 'Kraj' za zatvaranje ovog prozora." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Va raunar trebate ponovno startovati da dovrite uklanjanje programa $(^NameDA). elite li da odmah sad ponovo startujete raunar?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Startuj raunar odmah sad" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ponovno u pokrenuti raunar kasnije" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Pokreni program $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Prikai datoteku &Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Kraj" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Izbor mape u Start meniju" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Izaberite ime za programsku mapu unutar Start menija." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Izaberite jednu mapu u Start meniju u kojoj elite da se kreiraju preice programa. Moete takoer unijeti ime za novu mapu ili selektirati ve postojeu." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nemojte praviti preice" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Uklanjanje programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Program $(^NameDA) e biti uklonjen sa Vaeg raunara." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Jeste li sigurni da elite prekinuti instalaciju programa $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Jeste li sigurni da elite prekinuti uklanjanje $(^Name) programa?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Breton.nlf b/installer/NSIS/Contrib/Language files/Breton.nlf new file mode 100644 index 0000000..fcfaf33 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Breton.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1150 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by KAD-Korvigello an Drouizig (drouizig@drouizig.org) +# ^Branding +Reizhiad stalia Nullsoft %s +# ^SetupCaption +Stalia ha keflunia $(^Name) +# ^UninstallCaption +Distalia $(^Name) +# ^LicenseSubCaption +: Aotre arvera +# ^ComponentsSubCaption +: Dibabo stalia +# ^DirSubCaption +: Kavlec'h stalia +# ^InstallingSubCaption +: O stalia ar restro +# ^CompletedSubCaption +: Echu eo +# ^UnComponentsSubCaption +: Dibabo distalia +# ^UnDirSubCaption +: Kavlec'h distalia +# ^ConfirmSubCaption +: Kadarna +# ^UninstallingSubCaption +: O tistalia ar restro +# ^UnCompletedSubCaption +: Echu eo +# ^BackBtn +< &Kent +# ^NextBtn +&War-lerc'h > +# ^AgreeBtn +&A-du emaon +# ^AcceptBtn +&Degemer holl dermo al lavaz emglev +# ^DontAcceptBtn +&Chom hep degemer termo al lavaz emglev +# ^InstallBtn +&Stalia +# ^UninstallBtn +&Distalia +# ^CancelBtn +Nulla +# ^CloseBtn +&Serri +# ^BrowseBtn +F&urchal... +# ^ShowDetailsBtn +Muioc'h a &ditouro +# ^ClickNext +Klikit war War-lerc'h evit mont war-raok. +# ^ClickInstall +Klikit war Stalia evit kregi gant ar staliadur. +# ^ClickUninstall +Klikit war Distalia evit kregi gant an distaliadur. +# ^Name +Anv +# ^Completed +Echu eo +# ^LicenseText +Bezit aketus en ur lenn an aotre arvera a-raok stalia $(^NameDA) mar plij. Mar degemerit pep term eus an aotre, klikit war A-du emaon . +# ^LicenseTextCB +Bezit aketus en ur lenn an aotre arvera a-raok stalia $(^NameDA) mar plij. Mar degemerit pep term eus an aotre, klikit war al log a-zindan. $_CLICK +# ^LicenseTextRB +Bezit aketus en ur lenn an aotre arvera a-raok stalia $(^NameDA) mar plij. Mar degemerit pep term eus an aotre, dizuzit an dibab kenta a-zindan. $_CLICK +# ^UnLicenseText +Bezit aketus en ur lenn an aotre arvera a-raok distalia $(^NameDA) mar plij. Mar degemerit pep term eus an aotre, klikit war A-du emaon . +# ^UnLicenseTextCB +Bezit aketus en ur lenn an aotre arvera a-raok distalia $(^NameDA) mar plij. Mar degemerit pep term eus an aotre, klikit war al log a-zindan. $_CLICK +# ^UnLicenseTextRB +Bezit aketus en ur lenn an aotre arvera a-raok distalia $(^NameDA) mar plij. Mar degemerit pep term eus an aotre, diuzit an dibab kenta a-zindan. $_CLICK +# ^Custom +Diouzh ho kiz +# ^ComponentsText +Dibabit an elfenno a fell deoc'h stalia pe diziuzit an elfenno a fell deoc'h leuskel a-gostez. $_CLICK +# ^ComponentsSubText1 +Dibabit pe seurt stalia a vo : +# ^ComponentsSubText2_NoInstTypes +Dibabit an elfenno da stalia : +# ^ComponentsSubText2 +Pe dibabit an elfenno diret a fell deoc'h stalia : +# ^UnComponentsText +Dibabit an elfenno a fell deoc'h distalia pe diziuzit an elfenno a fell deoc'h mirout. $_CLICK +# ^UnComponentsSubText1 +Dibabit peseurt distalia a vo : +# ^UnComponentsSubText2_NoInstTypes +Dibabit an elfenno da zistalia : +# ^UnComponentsSubText2 +Pe dibabit an elfenno diret a fell deoc'h distalia : +# ^DirText +Staliet e vo $(^NameDA) gant ar goulev-ma er c'havlec'h da-heul. Mar fell deoc'h dibab ur c'havlec'h all, klikit war Furchal ha dibabit ur c'havlec'h all. $_CLICK +# ^DirSubText +Kavlec'h bukenn +# ^DirBrowseText +Dibabit ar c'havlec'h e vo diazezet $(^NameDA) enna : +# ^UnDirText +Distaliet e vo $(^NameDA) gant ar goulev-ma adalek ar c'havlec'h da heul. Ma fell deoc'h distalia adalek ur c'havlec'h all, klikit war Furchal ha diuzit ur c'havlec'h all. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Diuzit ar c'havlec'h evit distalia $(^NameDA) adalek : +# ^SpaceAvailable +"Egor kantenn vak : " +# ^SpaceRequired +"Egor kantenn rekis : " +# ^UninstallingText +Distaliet e vo $(^NameDA) adalek ar c'havelec'h da-heul. $_CLICK +# ^UninstallingSubText +Distalia adalek : +# ^FileError +Fazi en ur zigeri ur restr evit skriva : \r\n\r\n$0\r\n\r\nKlikit war Paouez evit paouez gant ar stalia,\r\n Adober evit esea en-dro, pe\r\n Tremen evit leuskel a-gostez ar restr-ma. +# ^FileError_NoIgnore +Fazi en ur zigeri ur restr a-benn skriva : \r\n\r\n$0\r\n\r\nKlikit war Adober evit esaea en-dro, pe\r\nwar Nulla evit paouez gant ar stalia. +# ^CantWrite +"N'haller ket skriva : " +# ^CopyFailed +Kopia faziet +# ^CopyTo +"Kopia da " +# ^Registering +"Oc'h enskriva : " +# ^Unregistering +"O tienskriva : " +# ^SymbolNotFound +"N'haller ket kavout ur simbolenn : " +# ^CouldNotLoad +"N'haller ket karga : " +# ^CreateFolder +"Kroui kavlec'h : " +# ^CreateShortcut +"Kroui berradenn : " +# ^CreatedUninstaller +"Skoazeller distalia krouet : " +# ^Delete +"Dilemel restr : " +# ^DeleteOnReboot +"Dilemel en ur adloc'ha : " +# ^ErrorCreatingShortcut +"Fazi en ur groui berradenn : " +# ^ErrorCreating +"Fazi en ur groui : " +# ^ErrorDecompressing +Fazi en ur ziwaska stlenn ! Skoazeller stalia gwastet ? +# ^ErrorRegistering +Fazi en ur enskriva an DLL +# ^ExecShell +"ExecShell : " +# ^Exec +"Lasa : " +# ^Extract +"Eztenna : " +# ^ErrorWriting +"Eztenna : fazi en ur skriva restr " +# ^InvalidOpcode +Skoazeller stalia gwastet : opcode direizh +# ^NoOLE +"OLE ebet evit : " +# ^OutputFolder +"Kavlec'h ec'hank : " +# ^RemoveFolder +"Dilemel ar c'havlec'h : " +# ^RenameOnReboot +"Adenvel pa vez adloc'het : " +# ^Rename +"Adenvel : " +# ^Skipped +"Laosket a-gostez: " +# ^CopyDetails +Kopia ar munudo er golver +# ^LogInstall +Tresa an argerzh stalia +# ^Byte +E +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Breton.nsh b/installer/NSIS/Contrib/Language files/Breton.nsh new file mode 100644 index 0000000..50067d2 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Breton.nsh @@ -0,0 +1,121 @@ +;Language: Breton (1150) +;By KAD-Korvigello An Drouizig + +!insertmacro LANGFILE "Breton" "Brezhoneg" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Degemer mat e skoazeller stalia $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Sturiet e viot gant ar skoazeller-ma evit stalia $(^NameDA).$\r$\n$\r$\nGwelloc'h eo serri pep arload oberiant er reizhiad a-raok mont pelloc'h gant ar skoazeller-ma. Evel-se e c'heller nevesaat ar restro reizhiad hep rankout adloc'ha hoc'h urzhiataer.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Degemer mat er skoazeller distalia $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Sturiet e viot gant ar skoazeller-ma a-benn distalia $(^NameDA).$\r$\n$\r$\nEn em asurit n'eo ket laset $(^NameDA) a-raok mont pelloc'h gant an distalia.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lavaz emglev" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Bezit aketus en ur lenn pep term eus al lavaz a-raok stalia $(^NameDA), mar plij." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Mar degemerit pep term eus al lavaz, klikit war War-lerc'h . Ret eo deoc'h degemer al lavaz evit stalia $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Mar degemerit pep term eus al lavaz, klikit war al log a-zindan. Ret eo deoc'h degemer al lavaz a-benn stalia $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Mar degemerit pep term eus al lavaz, diuzit an dibab kenta a-zindan. Ret eo deoc'h degemer al lavaz a-benn stalia $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lavaz emglev" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Trugarez da lenn al lavaz a-raok distalia $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Mar degemerit pep term eus al lavaz, klikit war A-du emaon evit kenderc'hel. Ret eo deoc'h degemer al lavaz evit distalia $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Mar degemerit pep term eus al lavaz, klikit war al log a-zindan. Ret eo deoc'h degemer al lavaz evit distalia $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Mar degemerit pep term eus al lavaz, dizuit an dibab kenta a-zindan. Ret eo deoc'h degemer al lavaz evit distalia $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Pouezit war Pajenn a-raok evit lenn ar pajenno eus al lavaz da-heul." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Dibab elfenno" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Dibabit pe elfenn(o) $(^NameDA) a fell deoc'h stalia." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Dibabit elfenno" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Dibabit pe elfenn(o) $(^NameDA) a fell deoc'h distalia." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Deskrivadenn" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Lakait ho logodenn a-zioc'h an elfenn evit gwelout he deskrivadenn." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Lakait ho logodenn a-zioc'h an elfenn evit gwelout he deskrivadenn." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Dibabit al lec'hiadur stalia" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Dibabit ar c'havlec'h ma vo lakaet $(^NameDA) enna." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Dibabit al lec'hiadur distalia" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Dibabit ar c'havlec'h e vo dilamet $(^NameDA) diouta." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "O stalia" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Gortozit mar plij, ema $(^NameDA) o veza staliet." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Echu eo gant ar stalia" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Kaset eo bet da benn mat ar stalia." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Stalia paouezet" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "N'eo ket bet kaset da benn mat ar stalia." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "O tistalia" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Gortozit mar plij, ema $(^NameDA) o veza distaliet." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Echu eo gant an distalia" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Gant berzh eo bet kaset da benn an distalia." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Distalia paouezet" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "N'eo ket bet kaset da benn mat an distalia." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Oc'h echui stalia $(^NameDA) gant ar skoazeller" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Staliet eo bet $(^NameDA) war hoc'h urzhiataer.$\r$\n$\r$\nKlikit war Echui evit serri ar skoazeller-ma." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Ret eo hoc'h urzhiataer beza adloc'het evit ma vez kaset da benn stalia $(^NameDA). Ha fellout a ra deoc'h adloc'ha diouzhtu ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Oc'h echui distalia $(^NameDA) gant ar skoazeller" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Distaliet eo bet $(^NameDA) diouzh hoc'h urzhiataer.$\r$\n$\r$\nKlikit war Echui evit serri ar skoazeller-ma." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Ret eo hoc'h urzhiataer beza adloc'het evit ma vez kaset da benn distalia $(^NameDA). Ha fellout a ra deoc'h adloc'ha diouzhtu ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Adloc'ha diouzhtu" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Fellout a ra din adloc'ha diwezatoc'h dre zorn" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Lasa $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Diskouez ar restr Malennit" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Echui" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Diskouez kavlec'h al Laser loc'ha" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Dibabit ur c'havlec'h Laser loc'ha evit berradenno $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Diuzit ar c'havlec'h Laser loc'ha e vo savet enna berradenno ar goulevio. Gallout a rit ingal rei un anv evit sevel ur c'havlec'h nevez." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Chom hep sevel berradenno" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Distalia $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Dilemel $(^NameDA) adalek hoc'h urzhiataer." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Ha sur oc'h e fell deoc'h kuitaat stalia $(^Name) ?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Ha sur oc'h e fell deoc'h kuitaat distalia $(^Name) ?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Bulgarian.nlf b/installer/NSIS/Contrib/Language files/Bulgarian.nlf new file mode 100644 index 0000000..a99c3bd --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Bulgarian.nlf @@ -0,0 +1,194 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1026 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1251 +# RTL - anything else than RTL means LTR +- +# Translation by Asparouh Kalyandjiev +# Updated v2 to v6 by Plamen Penkov +# Updated by (DumpeR) +# +# ^Branding +Nullsoft Install System %s +# ^SetupCaption + $(^Name) +# ^UninstallCaption + $(^Name) +#^LicenseSubCaption +: +#^ComponentsSubCaption +: +# ^DirSubCaption +: +#^InstallingSubCaption +: +#^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +#^UninstallingSubCaption +: +#^UnCompletedSubCaption +: +# ^BackBtn +< & +#^NextBtn +& > +# ^AgreeBtn +& +# ^AcceptBtn +& . +# ^DontAcceptBtn +& . +# ^InstallBtn +& +#^UninstallBtn +& +# ^CancelBtn +& +#^CloseBtn +& +#^BrowseBtn +&... +#^ShowDetailsBtn +& +# ^ClickNext + "", . +# ^ClickInstall + "", . +# ^ClickUninstall + "", . +# ^Name + +#^Completed + +# ^LicenseText + $(^NameDA). , "". +# ^LicenseTextCB + $(^NameDA). , -. $_CLICK +# ^LicenseTextRB + $(^NameDA). , -. $_CLICK +# ^UnLicenseText + $(^NameDA). , "". +# ^UnLicenseTextCB + $(^NameDA). , -. $_CLICK +# ^UnLicenseTextRB + $(^NameDA). , -. $_CLICK +# ^Custom + +#^ComponentsText + , . $_CLICK +#^ComponentsSubText1 + : +#^ComponentsSubText2_NoInstTypes + : +#^ComponentsSubText2 + , : +# ^UnComponentsText + , , . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 +, : +# ^DirText + $(^NameDA) . , "" . $_CLICK +# ^DirSubText + +# ^DirBrowseText + , $(^NameDA): +# ^UnDirText + $(^NameDA) . , "" . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + $(^NameDA): +# ^SpaceAvailable +" : " +#^SpaceRequired +" : " +# ^UninstallingText +$(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\t"$0"\r\n "", , "", "", +# ^FileError_NoIgnore + : \r\n\t"$0"\r\n "", "", . +# ^CantWrite +" : " +#^CopyFailed + +# ^CopyTo +" " +# ^Registering +" : " +# ^Unregistering +" : " +#^SymbolNotFound +" : " +# ^CouldNotLoad +" : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +#^CreatedUninstaller +" : " +# ^Delete +" : " +#^DeleteOnReboot +" : " +#^ErrorCreatingShortcut +" : " +#^ErrorCreating +" : " +# ^ErrorDecompressing + ! . +# ^ErrorRegistering + DLL +#^ExecShell +"ExecShell: " +#^Exec +" : " +#^Extract +" : " +#^ErrorWriting +" : " +#^InvalidOpcode + : +# ^NoOLE +" OLE : " +#^OutputFolder +" : " +#^RemoveFolder +" : " +#^RenameOnReboot +" : " +# ^Rename +" : " +#^Skipped +" : " +# ^CopyDetails + +# ^LogInstall + +# ^Byte + +# ^Kilo + +# ^Mega + +# ^Giga + \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Bulgarian.nsh b/installer/NSIS/Contrib/Language files/Bulgarian.nsh new file mode 100644 index 0000000..d1763ab --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Bulgarian.nsh @@ -0,0 +1,124 @@ +;Language: Bulgarian (1026) +;Translated by Asparouh Kalyandjiev [acnapyx@sbline.net] +;Review and update from v1.63 to v1.68 by Plamen Penkov [plamen71@hotmail.com] +;Updated by (DumpeR) [dumper@data.bg] +; + +!insertmacro LANGFILE "Bulgarian" "Bulgarian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA)!" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n , . , .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA)!" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n , $(^NameDA) .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " , $\"$\", . , $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , -. , $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , -. , $(^NameDA) $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " , $\"$\" . , $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , -. , $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , -. , $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " $\"Page Down$\", ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " , ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " , ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " , $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " , $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE ", $(^NameDA)..." + ${LangFileString} MUI_TEXT_FINISH_TITLE " ." + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " ." + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE ", $(^NameDA)..." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n $\"$\", ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " , $(^NameDA). ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n $\"$\" ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " , $(^NameDA). ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW ", " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER ", -" + ${LangFileString} MUI_TEXT_FINISH_RUN " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME " $\"ReadMe$\"" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " $\"$\"" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " $\"$\" ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " $\"$\", . , ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " , $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " , $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Catalan.nlf b/installer/NSIS/Contrib/Language files/Catalan.nlf new file mode 100644 index 0000000..1c41751 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Catalan.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +1027 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by falanko +# Corrections by Toni Hermoso Pulido +# ^Branding +Sistema d'installaci de Nullsoft %s +# ^SetupCaption +Installaci de l'aplicaci $(^Name) +# ^UninstallCaption +Desinstallaci de l'aplicaci $(^Name) +# ^LicenseSubCaption +: Acord de llicncia +# ^ComponentsSubCaption +: Opcions d'installaci +# ^DirSubCaption +: Carpeta d'installaci +# ^InstallingSubCaption +: S'est installant +# ^CompletedSubCaption +: S'ha acabat +# ^UnComponentsSubCaption +: Opcions de desinstallaci +# ^UnDirSubCaption +: Carpeta de desinstallaci +# ^ConfirmSubCaption +: Confirmaci +# ^UninstallingSubCaption +: S'est desinstallant +# ^UnCompletedSubCaption +: No s'ha acabat +# ^BackBtn +< En&rere +# ^NextBtn +En&davant > +# ^AgreeBtn +Hi estic d'a&cord +# ^AcceptBtn +&Accepto els termes de l'acord de llicncia +# ^DontAcceptBtn +&No accepto els termes de l'acord de llicncia +# ^InstallBtn +&Installa +# ^UninstallBtn +&Desinstalla +# ^CancelBtn +&Cancella +# ^CloseBtn +&Tanca +# ^BrowseBtn +&Navega... +# ^ShowDetailsBtn +&Mostra els detalls +# ^ClickNext +Feu clic a Endavant per a continuar. +# ^ClickInstall +Feu clic a Installa per a iniciar la installaci. +# ^ClickUninstall +Feu clic a Desinstalla per a iniciar la desinstallaci. +# ^Name +Nom +# ^Completed +S'ha acabat +# ^LicenseText +Reviseu l'acord de llicncia abans d'installar l'aplicaci $(^NameDA). Si accepteu tots els termes de l'acord, feu clic a Hi estic d'acord. +# ^LicenseTextCB +Reviseu l'acord de llicncia abans d'installar l'aplicaci $(^NameDA). Si accepteu tots els termes de l'acord, activeu la casella de sota. $_CLICK +# ^LicesnseTextRB +Reviseu l'acord de llicncia abans d'installar l'aplicaci $(^NameDA). Si accepteu tots els termes de l'acord, seleccioneu la primera opci de sota. $_CLICK +# ^UnLicenseText +Reviseu l'acord de llicncia abans de desinstallar l'aplicaci $(^NameDA). Si accepteu tots els termes de l'acord, feu clic a Hi estic d'acord. +# ^UnLicenseTextCB +Reviseu l'acord de llicncia abans de desinstallar l'aplicaci $(^NameDA). Si accepteu tots els termes de l'acord, activeu la la casella de sota. $_CLICK +# ^UnLicesnseTextRB +Reviseu l'acord de llicncia abans de desinstallar l'aplicaci $(^NameDA). Si accepteu tots els termes de l'acord, seleccioneu la primera opci de sota. $_CLICK +# ^Custom +Personalitzada +# ^ComponentsText +Activeu els components que voleu installar i desactiveu els que no. $_CLICK +# ^ComponentsSubText1 +Seleccioneu el tipus d'installaci: +# ^ComponentsSubText2_NoInstTypes +Seleccioneu els components per installar: +# ^ComponentsSubText2 +O b, seleccioneu els components opcionals que desitgssiu installar: +# ^UnComponentsText +Activeu els components que voleu desinstallar i desactiveu els que no. $_CLICK +# ^UnComponentsSubText1 +Seleccioneu el tipus de desinstallaci: +# ^UnComponentsSubText2_NoInstTypes +Seleccioneu els components per desinstallar: +# ^UnComponentsSubText2 +O b, seleccioneu els components opcionals per desinstallar: +# ^DirText +El programa d'installaci installar l'aplicaci $(^NameDA) en la segent carpeta. Per a installar-lo en una carpeta diferent, feu clic a Navega i seleccioneu-ne una altra. $_CLICK +# ^DirSubText +Carpeta de destinaci +# ^DirBrowseText +Seleccioneu la carpeta on s'installar l'aplicaci $(^NameDA): +# ^UnDirText +El programa d'installaci desinstallar l'aplicaci $(^NameDA) de la segent carpeta. Per a desinstallar-lo d'una carpeta diferent, feu clic a Navega i seleccioneu-ne una altra. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Seleccioneu la carpeta des d'on es desinstallar l'aplicaci $(^NameDA): +# ^SpaceAvailable +"Espai lliure: " +# ^SpaceRequired +"Espai necessari: " +# ^UninstallingText +Es desinstallar l'aplicaci $(^NameDA) de la segent carpeta. $_CLICK +# ^UninstallingSubTex +S'est desinstallant de: +# ^FileError +S'ha produt un error en obrir el fitxer en mode d'escriptura: \r\n\t"$0"\r\nFeu clic a Abandona per a aturar la installaci,\r\nReintenta per a tornar-ho a provar, o\r\Ignora per a ometre aquest fitxer. +# ^FileError_NoIgnore +S'ha produt un error en obrir el fitxer en mode d'escriptura: \r\n\t"$0"\r\nFeu clic a Reintenta per a tornar-ho a provar, o\r\Cancella per a aturar la installaci. +# ^CantWrite +"No s'ha pogut escriure: " +# ^CopyFailed +Ha fallat la cpia +# ^CopyTo +"Copia a " +# ^Registering +"S'esta registrant:" +# ^Unregistering +"S'est suprimint el registre: " +# ^SymbolNotFound +"No s'ha trobat el smbol: " +# ^CouldNotLoad +"No s'ha pogut carregar: " +# ^CreateFolder +"Crea la carpeta: " +# ^CreateShortcut +"Crea la drecera: " +# ^CreatedUninstaller +"S'ha creat el desinstallador: " +# ^Delete +"S'ha suprimit el fitxer: " +# ^DeleteOnReboot +"Suprimeix en reiniciar: " +# ^ErrorCreatingShortcut +"S'ha produt un error en crear la drecera: " +# ^ErrorCreating +S'ha produt un error en crear: +# ^ErrorDecompressing +S'ha produt un error en descomprimir les dades! L'installador est corrupte? +# ^ErrorRegistering +S'ha produt un error en registrar una DLL +# ^ExecShell +"Executa l'ordre: " +# ^Exec +"Executa:" +# ^Extract +"Extreu: " +# ^ErrorWriting +"Extreu: s'ha produt un error en escriure el fitxer " +# ^InvalidOpcode +L'installador est corrupte: el codi d'operaci no s vlid +# ^NoOLE +"No hi ha OLE per a: " +# ^OutputFolder +"Carpeta de sortida: " +# ^RemoveFolder +"Suprimeix la carpeta: " +# ^RenameOnReboot +"Reanomena en reiniciar: " +# ^Rename +"Reanomena: " +# ^Skipped +"S'ha oms: " +# ^CopyDetails +Copia els detalls al porta-retalls +# ^LogInstall +Registra el procs d'installaci +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Catalan.nsh b/installer/NSIS/Contrib/Language files/Catalan.nsh new file mode 100644 index 0000000..08579ea --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Catalan.nsh @@ -0,0 +1,121 @@ +;Language: Catalan (1027) +;By falanko, corrections by Toni Hermoso Pulido + +!insertmacro LANGFILE "Catalan" "Catal" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Benvinguts a l'auxiliar d'installaci de l'aplicaci $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Aquest auxiliar us guiar durant el procs d'installaci de l'aplicaci $(^NameDA).$\r$\n$\r$\nEs recomana tancar la resta d'aplicacions abans de comenar la installaci. Aix permetr al programa d'instalaci actualitzar fitxers del sistema rellevants sense haver de reiniciar l'ordinador.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Benvinguts a l'auxiliar de desinstallaci de l'aplicaci $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Aquest auxiliar us guiar a travs de la desinstallaci de l'aplicaci $(^NameDA).$\r$\n\rAbans de comenar la desinstallaci, assegureu-vos que l'aplicaci $(^NameDA) no s'est executant.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Acord de Llicncia" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Reviseu els termes de la llicncia abans d'installar l'aplicaci $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Si accepteu tots els termes de l'acord, premeu Hi estic d'acord per a continuar. Heu d'acceptar l'acord per a poder installar l'aplicaci $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si accepteu tots els termes de l'acord, activeu la casella de sota. Heu d'acceptar l'acord per poder installar l'aplicaci $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si accepteu tots els termes de l'acord, seleccioneu la primera opci de sota. Heu d'acceptar l'acord per a poder installar $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Acord de llicncia" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Reviseu els termes de la llicncia abans de desinstallar l'aplicaci $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Si accepteu tots els termes de l'acord, premeu Hi estic d'Acord per a continuar. Heu d'acceptar l'acord per a poder desinstallar l'aplicaci $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si accepteu tots els termes de l'acord, activeu la casella de sota. Heu d'acceptar l'acord per a poder desinstallar l'aplicaci $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si accepteu tots els termes de l'acord, seleccioneu la primera opci de sota. Heu d'acceptar l'acord per a poder desinstallar l'aplicaci $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Premeu AvPg per a veure la resta de l'acord." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Selecci de components" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Seleccioneu quines caracterstiques de l'aplicaci $(^NameDA) desitgeu installar." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Selecci de components" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Seleccioneu quines caracterstiques de l'aplicaci $(^NameDA) desitgeu desinstallar." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descripci" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Situeu el ratol damunt d'un component per a veure'n la descripci." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Situeu el ratol damunt d'un component per a veure'n la descripci." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Trieu una ubicaci d'installaci" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Trieu la carpeta on installar-hi l'aplicaci $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Trieu la ubicaci de desinstallaci" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Trieu la carpeta d'on desinstallar l'aplicaci $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "S'est installant" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Si us plau, espereu mentre l'aplicaci $(^NameDA) s'installa." + ${LangFileString} MUI_TEXT_FINISH_TITLE "S'ha acabat la installaci" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "La installaci ha acabat correctament." + ${LangFileString} MUI_TEXT_ABORT_TITLE "S'ha abandonat la installaci" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "La installaci no ha acabat correctament." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "S'est desinstallant" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Si us plau, espereu mentre l'aplicaci $(^NameDA) es desinstalla." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "S'ha acabat la desinstallaci" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "La desinstallaci s'ha realitzat correctament." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "S'ha abandonat la desinstallaci" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "La desinstallaci no ha acabat correctament." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "S'est acabant l'auxiliar d'installaci de l'aplicaci $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "L'aplicaci $(^NameDA) s'ha installat a l'ordinador.$\r$\n$\r$\nFeu clic a Finalitza per a tancar aquest auxiliar." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Cal reiniciar l'ordinador perqu pugui acabar-se la installaci de l'aplicaci $(^NameDA). Voleu reiniciar-lo ara?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "S'est acabant l'auxiliar de desinstallaci de l'aplicaci $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "L'aplicaci $(^NameDA) s'ha desinstallat de l'ordinador.$\r$\n$\r$\nFeu clic a Finalitza per a tancar aquest auxiliar." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Cal reiniciar l'ordinador perqu pugui acabar-se la desinstallaci de l'aplicaci $(^NameDA). Voleu reiniciar-lo ara?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reinicia ara" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Reinicia ms tard manualment" + ${LangFileString} MUI_TEXT_FINISH_RUN "Executa l'aplicaci $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Mostra el Llegeix-me" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Finalitza" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Tria la carpeta del men Inicia" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Tria una carpeta del men Inicia per a les dreceres de l'aplicaci $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Seleccioneu la carpeta del Men Inicia en la que hi vulgueu crear les dreceres del programa. Podeu introduir-hi un altre nom si voleu crear una carpeta nova." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "No cres les dreceres" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Desinstalla l'aplicaci $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Suprimeix l'aplicaci $(^NameDA) de l'ordinador." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Esteu segur que voleu sortir del programa d'installaci de l'aplicaci $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Esteu segur que voleu sortir del programa de desinstallaci de l'aplicaci $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Croatian.nlf b/installer/NSIS/Contrib/Language files/Croatian.nlf new file mode 100644 index 0000000..b70ca36 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Croatian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1050 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by Igor Ostriz +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Instalacija +# ^UninstallCaption +$(^Name) Uklanjanje +# ^LicenseSubCaption +: Licenenni uvjeti +# ^ComponentsSubCaption +: Instalacijske opcije +# ^DirSubCaption +: Instalacijska mapa +# ^InstallingSubCaption +: Instaliranje +# ^CompletedSubCaption +: Zavreno +# ^UnComponentsSubCaption +: Opcije uklanjanja +# ^UnDirSubCaption +: Mapa uklanjanja +# ^ConfirmSubCaption +: Potvrda +# ^UninstallingSubCaption +: Uklanjanje +# ^UnCompletedSubCaption +: Zavreno +# ^BackBtn +< &Nazad +# ^NextBtn +&Dalje > +# ^AgreeBtn +&Prihvaam +# ^AcceptBtn +&Prihvaam uvjete licennog ugovora +# ^DontAcceptBtn +&Ne prihvaam uvjete licennog ugovora +# ^InstallBtn +&Instaliraj +# ^UninstallBtn +&Ukloni +# ^CancelBtn +Odustani +# ^CloseBtn +&Zatvori +# ^BrowseBtn +&Pregledaj... +# ^ShowDetailsBtn +Prikai &detalje +# ^ClickNext +Za nastavak odaberite 'Dalje'. +# ^ClickInstall +Za poetak instalacije odaberite 'Instaliraj'. +# ^ClickUninstall +Za poetak uklanjanja odaberite 'Ukloni'. +# ^Name +Ime +# ^Completed +Zavreno +# ^LicenseText +Molim proitajte licencu prije instalacije programa $(^NameDA). Ukoliko prihvaate sve uvjete ugovora, odaberite 'Prihvaam'. +# ^LicenseTextCB +Molim proitajte licencu prije instalacije programa $(^NameDA). Ukoliko prihvaate sve uvjete ugovora, oznaite donji kvadrati. $_CLICK +# ^LicenseTextRB +Molim proitajte licencu prije instalacije programa $(^NameDA). Ukoliko prihvaate sve uvjete ugovora, odaberite prvu donju opciju. $_CLICK +# ^UnLicenseText +Molim proitajte licencu prije uklanjanja programa $(^NameDA). Ukoliko prihvaate sve uvjete ugovora, odaberite 'Prihvaam'. +# ^UnLicenseTextCB +Molim proitajte licencu prije uklanjanja programa $(^NameDA). Ukoliko prihvaate sve uvjete ugovora, oznaite donji kvadrati. $_CLICK +# ^UnLicenseTextRB +Molim proitajte licencu prije uklanjanja programa $(^NameDA). Ukoliko prihvaate sve uvjete ugovora, odaberite prvu donju opciju. $_CLICK +# ^Custom +Posebna +# ^ComponentsText +Oznaite komponente koje elite instalirati i uklonite oznaku s onih koje ne elite instalirati. $_CLICK +# ^ComponentsSubText1 +Izaberite tip instalacije: +# ^ComponentsSubText2_NoInstTypes +Odaberite komponente za instalaciju: +# ^ComponentsSubText2 +Ili po izboru oznaite komponente koje elite instalirati: +# ^UnComponentsText +Oznaite komponente koje elite ukloniti i uklonite oznaku s onih koje ne elite ukloniti. $_CLICK +# ^UnComponentsSubText1 +Izaberite tip uklanjanja: +# ^UnComponentsSubText2_NoInstTypes +Odaberite komponente za uklanjanje: +# ^UnComponentsSubText2 +Ili po izboru oznaite komponente koje elite ukloniti: +# ^DirText +Program $(^NameDA) e biti instaliran u slijedeu mapu. Za instalaciju na drugo odredite odaberite 'Pregledaj' i oznaite drugu mapu. $_CLICK +# ^DirSubText +Odredina mapa +# ^DirBrowseText +Odaberite mapu u koju elite instalirati program $(^NameDA): +# ^UnDirText +Program $(^NameDA) e biti uklonjen iz slijedee mape. Za uklanjanje s drugog mjesta odaberite 'Pregledaj' i oznaite drugu mapu. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Mapa iz koje e program $(^NameDA) biti uklonjen: +# ^SpaceAvailable +"Slobodno prostora na disku: " +# ^SpaceRequired +"Potrebno prostora na disku: " +# ^UninstallingText +Program $(^NameDA) e biti uklonjen iz slijedee mape. $_CLICK +# ^UninstallingSubText +Uklanjam iz: +# ^FileError +Greka prilikom otvaranja datoteke za zapisivanje: \r\n\t"$0"\r\n\"Abort\" za prekid instalacije,\r\n\"Retry\" za ponovni pokuaj zapisivanja, ili\r\n\"Ignore\" za zanemarenje te datoteke +# ^FileError_NoIgnore +Greka prilikom otvaranja datoteke za zapisivanje: \r\n\t"$0"\r\n\"Retry\" za ponovni pokuaj zapisivanja, ili\r\n\"Cancel\" za prekid instalacije +# ^CantWrite +"Ne mogu zapisati: " +# ^CopyFailed +Greka prilikom kopiranja +# ^CopyTo +"Kopiraj u " +# ^Registering +"Prijava: " +# ^Unregistering +"Odjava: " +# ^SymbolNotFound +"Ne mogu nai simbol: " +# ^CouldNotLoad +"Ne mogu uitati: " +# ^CreateFolder +"Stvori mapu: " +# ^CreateShortcut +"Stvori preac: " +# ^CreatedUninstaller +"Program za uklanjanje: " +# ^Delete +"Obrii datoteku: " +# ^DeleteOnReboot +"Obrii prilikom ponovnog pokretanja: " +# ^ErrorCreatingShortcut +"Greka prilikom stvaranja preaca: " +# ^ErrorCreating +"Greka prilikom stvaranja: " +# ^ErrorDecompressing +Greka dekompresije podataka! Oteena instalacijska datoteka? +# ^ErrorRegistering +Greka prilikom prijavljivanja DLLa +# ^ExecShell +"ExecShell: " +# ^Exec +"Izvri: " +# ^Extract +"Otpakiraj: " +# ^ErrorWriting +"Otpakiranje: greka zapisivanja u datoteku " +# ^InvalidOpcode +Oteena instalacijska datoteka: neispravni opkod +# ^NoOLE +"Nema OLE za: " +# ^OutputFolder +"Izlazna mapa: " +# ^RemoveFolder +"Obrii mapu: " +# ^RenameOnReboot +"Preimenuj prilikom ponovnog pokretanja: " +# ^Rename +"Preimenuj: " +# ^Skipped +"Preskoeno: " +# ^CopyDetails +Kopiraj detalje u Clipboard +# ^LogInstall +Logiraj instalacijski proces +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Croatian.nsh b/installer/NSIS/Contrib/Language files/Croatian.nsh new file mode 100644 index 0000000..9570056 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Croatian.nsh @@ -0,0 +1,121 @@ +;Language: Croatian (1050) +;By Igor Ostriz + +!insertmacro LANGFILE "Croatian" "Hrvatski" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Dobrodoli u instalaciju programa $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Instalacija programa $(^NameDA) na Vae raunalo sastoji se od nekoliko jednostavnih koraka kroz koje e Vas provesti ovaj arobnjak.$\r$\n$\r$\nPreporuamo zatvaranje svih ostalih aplikacija prije samog poetka instalacije. To e omoguiti nadogradnju nekih sistemskih datoteka bez potrebe za ponovnim pokretanjem Vaeg raunala. U svakom trenutku instalaciju moete prekinuti pritiskom na 'Odustani'.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Dobrodoli u postupak uklanjanja programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Ovaj e Vas arobnjak provesti kroz postupak uklanjanja programa $(^NameDA).$\r$\n$\r$\nPrije samog poetka, molim zatvorite program $(^NameDA) ukoliko je sluajno otvoren.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licenni ugovor" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Molim proitajte licencu prije instalacije programa $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Ukoliko prihvaate uvjete licence, odaberite 'Prihvaam' za nastavak. Morate prihvatiti licencu za instalaciju programa $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ukoliko prihvaate uvjete licence, oznaite donji kvadrati. Morate prihvatiti licencu za instalaciju programa $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ukoliko prihvaate uvjete licence, odaberite prvu donju opciju. Morate prihvatiti licencu za instalaciju programa $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licenni ugovor" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Molim proitajte licencu prije uklanjanja programa $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Ukoliko prihvaate uvjete licence, odaberite 'Prihvaam' za nastavak. Morate prihvatiti licencu za uklanjanje programa $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ukoliko prihvaate uvjete licence, oznaite donji kvadrati. Morate prihvatiti licencu za uklanjanje programa $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ukoliko prihvaate uvjete licence, odaberite prvu donju opciju. Morate prihvatiti licencu za uklanjanje programa $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "'Page Down' za ostatak licence." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Izbor komponenti" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Odaberite komponente programa $(^NameDA) koje elite instalirati." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Izbor komponenti" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Odaberite koje komponente programa $(^NameDA) elite ukloniti." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Opis" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Postavite pokaziva iznad komponente za njezin opis." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Postavite pokaziva iznad komponente za njezin opis." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Odaberite odredite za instalaciju" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Odaberite mapu u koju elite instalirati program $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Odaberite polazite za uklanjanje" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Odaberite mapu iz koje elite ukloniti program $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instaliranje" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Molim priekajte na zavretak instalacije programa $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Kraj instalacije" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Instalacija je u potpunosti zavrila uspjeno." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalacija je prekinuta" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Instalacija nije zavrila uspjeno." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Uklanjanje" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Molim priekajte na zavretak uklanjanja programa $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Uklanjanje zavreno" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Uklanjanje je u potpunosti zavrilo uspjeno." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Uklanjanje je prekinuto" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Uklanjanje nije zavrilo uspjeno." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Dovrenje instalacije programa $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Program $(^NameDA) je instaliran na Vae raunalo.$\r$\n$\r$\nOdaberite 'Kraj' za zavretak." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Vae raunalo treba ponovno pokrenuti za dovrenje instalacije programa $(^NameDA). elite li to uiniti sada?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Zavretak uklanjanja programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Program $(^NameDA) je uklonjen s Vaeg raunala.$\r$\n$\r$\nOdaberite 'Kraj' za zatvaranje ovog arobnjaka." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Vae raunalo treba ponovno pokrenuti za dovrenje postupka uklanjanja programa $(^NameDA). elite li to uiniti sada?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Ponovno pokreni raunalo sada" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ponovno u pokrenuti raunalo kasnije" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Pokreni program $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Prikai &Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Kraj" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Izbor mape u Start meniju" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Odaberite ime za programsku mapu unutar Start menija." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Program e pripadati odabranoj programskoj mapi u Start meniju. Moete odrediti novo ime za mapu ili odabrati ve postojeu." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nemoj napraviti preace" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Uklanjanje programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Program $(^NameDA) e biti uklonjen s Vaeg raunala." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Jeste li sigurni da elite prekinuti instalaciju programa $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Jeste li sigurni da elite prekinuti uklanjanje programa $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Czech.nlf b/installer/NSIS/Contrib/Language files/Czech.nlf new file mode 100644 index 0000000..534d4fe --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Czech.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1029 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by SELiCE (ls@selice.cz - http://ls.selice.cz) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Instalovat $(^Name) +# ^UninstallCaption +Odinstalovat $(^Name) +# ^LicenseSubCaption +: Licenn ujednn +# ^ComponentsSubCaption +: Monosti instalace +# ^DirSubCaption +: Umstn instalace +# ^InstallingSubCaption +: Instaluje se +# ^CompletedSubCaption +: Dokoneno +# ^UnComponentsSubCaption +: Monosti odinstalace +# ^UnDirSubCaption +: Odinstalovat adres +# ^ConfirmSubCaption +: Potvrzen +# ^UninstallingSubCaption +: Odinstalovn +# ^UnCompletedSubCaption +: Dokoneno +# ^BackBtn +< &Zpt +# ^NextBtn +&Dal > +# ^AgreeBtn +Souhl&asm +# ^AcceptBtn +Souhl&asm s podmnkami Licennho ujednn +# ^DontAcceptBtn +&Nesouhlasm s podmnkami Licennho ujednn +# ^InstallBtn +&Instalovat +# ^UninstallBtn +&Odinstalovat +# ^CancelBtn +Storno +# ^CloseBtn +&Zavt +# ^BrowseBtn +P&rochzet... +# ^ShowDetailsBtn +Zobrazit &detaily +# ^ClickNext +Pro pokraovn kliknte na 'Dal'. +# ^ClickInstall +Kliknte na 'Instalovat' pro zahjen instalace. +# ^ClickUninstall +Kliknte na 'Odinstalovat' pro zahjen odinstalace. +# ^Name +Nzev +# ^Completed +Dokoneno +# ^LicenseText +Ped instalac $(^NameDA) si prosm pette licenn ujednn. Jestlie souhlaste se vema podmnkama ujednn, kliknte 'Souhlasm'. +# ^LicenseTextCB +Ped instalac $(^NameDA) si prosm pette licenn ujednn. Jestlie souhlaste se vema podmnkama ujednn, zakrtnte polko dole. $_CLICK +# ^LicenseTextRB +Ped instalac $(^NameDA) si prosm pette licenn ujednn. Jestlie souhlaste se vema podmnkama ujednn, zakrtnte ne prvn monost. $_CLICK +# ^UnLicenseText +Ped odinstalovn $(^NameDA) si prosm pette licenn ujednn. Jestlie souhlaste se vema podmnkama ujednn, kliknte 'Souhlasm'. +# ^UnLicenseTextCB +Ped odinstalovn $(^NameDA) si prosm pette licenn ujednn. Jestlie souhlaste se vema podmnkama ujednn, zakrtnte polko dole. $_CLICK +# ^UnLicenseTextRB +Ped odinstalovn $(^NameDA) si prosm pette licenn ujednn. Jestlie souhlaste se vema podmnkama ujednn, zakrtnte ne prvn monost. $_CLICK +# ^Custom +Vlastn +# ^ComponentsText +Zvolte sousti, kter chcete nainstalovat a nezatrhnte sousti, kter instalovat nechcete. $_CLICK +# ^ComponentsSubText1 +Zvolte zpsob instalace: +# ^ComponentsSubText2_NoInstTypes +Zvolte sousti k instalaci: +# ^ComponentsSubText2 +Nebo zvolte voliteln sousti, kter chcete nainstalovat: +# ^UnComponentsText +Zatrhnte sousti, kter chcete odinstalovat a nezatrhnte sousti, kter odinstalovat nechcete. $_CLICK +# ^UnComponentsSubText1 +Zvolte zpsob odinstalace: +# ^UnComponentsSubText2_NoInstTypes +Zvolte sousti pro odinstalaci: +# ^UnComponentsSubText2 +Nebo zvolte jednotliv sousti, kter chcete odinstalovat: +# ^DirText +Setup nyn nainstaluje program $(^NameDA) do nsledujc sloky. Pro instalaci do jin sloky zvolte 'Prochzet' a vyberte jinou sloku. $_CLICK +# ^DirSubText +Clov sloka +# ^DirBrowseText +Zvolte sloku kam instalovat program $(^NameDA): +# ^UnDirText +Setup nyn odinstaluje program $(^NameDA) z nsledujc sloky. Pro odinstalaci z jin sloky zvolte 'Prochzet' a vyberte jinou sloku. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Zvolte adres pro odinstalaci $(^NameDA) z: +# ^SpaceAvailable +"Voln msto: " +# ^SpaceRequired +"Potebn msto: " +# ^UninstallingText +Tento prvodce z Vaeho potae odinstaluje $(^NameDA). $_CLICK +# ^UninstallingSubText +Odinstalovat z: +# ^FileError +Nelze otevt soubor pro zpis: \r\n\t"$0"\r\nStisknte 'Peruit' pro ukonen instalace\r\nnebo 'Znovu' pro dal pokus nebo\r\n'Ignorovat' pro peskoen souboru +# ^FileError_NoIgnore +Nelze otevt soubor pro zpis: \r\n\t"$0"\r\nStisknte 'Znovu' pro dal pokus nebo\r\n*Storno* pro ukonen instalace +# ^CantWrite +"Nelze zapsat: " +# ^CopyFailed +Koprovn selhalo +# ^CopyTo +"Zkoprovat do " +# ^Registering +"Registrovn: " +# ^Unregistering +"Odregistrovn: " +# ^SymbolNotFound +"Nelze najt znak: " +# ^CouldNotLoad +"Nelze nast: " +# ^CreateFolder +"Vytvoen adres: " +# ^CreateShortcut +"Vytvoen zstupce: " +# ^CreatedUninstaller +"Vytvoen odinstaltor: " +# ^Delete +"Smazat soubor: " +# ^DeleteOnReboot +"Smazat po restartu: " +# ^ErrorCreatingShortcut +"Chyba pi vytven zstupce: " +# ^ErrorCreating +"Chyba pi vytven: " +# ^ErrorDecompressing +Chyba pi rozbalovn dat! Pokozen instaltor? +# ^ErrorRegistering +Chyba pi registrovn DLL +# ^ExecShell +"Spustit shell: " +# ^Exec +"Spustit: " +# ^Extract +"Rozbalit: " +# ^ErrorWriting +"Rozbalit: chyba pi zpisu do souboru " +# ^InvalidOpcode +Instaltor je pokozen: nesprvn kontroln kd +# ^NoOLE +"Nedostupn OLE pro: " +# ^OutputFolder +"Vstupn sloka: " +# ^RemoveFolder +"Odstranit sloku: " +# ^RenameOnReboot +"Pejmenovat pi restartu: " +# ^Rename +"Pejmenovno: " +# ^Skipped +"Peskoeno: " +# ^CopyDetails +Zkoprovat podrobnosti do schrnky +# ^LogInstall +Zaznamenat prbh instalace +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Czech.nsh b/installer/NSIS/Contrib/Language files/Czech.nsh new file mode 100644 index 0000000..c36836f --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Czech.nsh @@ -0,0 +1,122 @@ +;Language: Czech (1029) +;By SELiCE (ls@selice.cz - http://ls.selice.cz) +;Corrected by Ondej Vani - http://www.vanis.cz/ondra + +!insertmacro LANGFILE "Czech" "Cesky" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Vtejte v prvodci instalace programu $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Tento prvodce Vs provede instalac $(^NameDA).$\r$\n$\r$\nPed zatkem instalace je doporueno zavt vechny ostatn aplikace. Toto umon aktualizovat dleit systmov soubory bez restartovn Vaeho potae.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Vtejte v $(^NameDA) odinstalanm prvodci" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Tento prvodce Vs provede odinstalac $(^NameDA).$\r$\n$\r$\nPed zatkem odinstalace, se pesvdte, e $(^NameDA) nen sputn.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licenn ujednn" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Ped instalac programu $(^NameDA) si prosm prostudujte licenn podmnky." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Jestlie souhlaste se vemi podmnkami ujednn, zvolte 'Souhlasm' pro pokraovn. Pro instalaci programu $(^NameDA) je nutn souhlasit s licennm ujednnm." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jestlie souhlaste se vemi podmnkami ujednn, zakrtnte ne uvedenou volbu. Pro instalaci programu $(^NameDA) je nutn souhlasit s licennm ujednnm. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jestlie souhlaste se vemi podmnkami ujednn, zvolte prvn z monost uvedench ne. Pro instalaci programu $(^NameDA) je nutn souhlasit s licennm ujednnm. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licenn ujednn" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Ped odinstalovnm programu $(^NameDA) si prosm prostudujte licenn podmnky." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Jestlie souhlaste se vemi podmnkami ujednn, zvolte 'Souhlasm' pro pokraovn. Pro odinstalovn programu $(^NameDA) je nutn souhlasit s licennm ujednnm." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jestlie souhlaste se vemi podmnkami ujednn, zakrtnte ne uvedenou volbu. Pro odinstalovn programu $(^NameDA) je nutn souhlasit s licennm ujednnm. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jestlie souhlaste se vemi podmnkami ujednn, zvolte prvn z ne uvedench monost. Pro odinstalovn programu $(^NameDA) je nutn souhlasit s licennm ujednnm. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Stisknutm klvesy Page Down posunete text licennho ujednn." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Volba soust" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Zvolte sousti programu $(^NameDA), kter chcete nainstalovat." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Volba soust" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Zvolte sousti programu $(^NameDA), kter chcete odinstalovat." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Popis" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Pi pohybu my nad instaltorem programu se zobraz jej popis." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Pi pohybu my nad instaltorem programu se zobraz jej popis." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Zvolte umstn instalace" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Zvolte sloku, do kter bude program $(^NameDA) nainstalovn." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Zvolte umstn odinstalace" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Zvolte sloku, ze kter bude program $(^NameDA) odinstalovn." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalace" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Vykejte, prosm, na dokonen instalace programu $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalace dokonena" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Instalace probhla v podku." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalace peruena" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Instalace nebyla dokonena." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Odinstalace" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Vykejte, prosm, na dokonen odinstalace programu $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Odinstalace dokonena" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Odinstalace probhla v podku." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Odinstalace peruena" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Odinstalace nebyla dokonena." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Dokonen prvodce programu $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Program $(^NameDA) byl nainstalovn na V pota.$\r$\n$\r$\nKliknte 'Dokonit' pro ukonen prvodce." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Pro dokonen instalace programu $(^NameDA) je nutno restartovat pota. Chcete restatovat nyn?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Dokonuji odinstalanho prvodce $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) byl odinstalovn z Vaeho potae.$\r$\n$\r$\nKliknte na 'Dokonit' pro ukonen tohoto prvodce." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Pro dokonen odinstalace $(^NameDA) mus bt V pota restartovn. Chcete restartovat nyn?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Restartovat nyn" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Restartovat run pozdji" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Spustit program $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Zobrazit ti-mne" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Dokonit" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Zvolte sloku v Nabdce Start" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Zvolte sloku v Nabdce Start pro zstupce programu $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Zvolte sloku v Nabdce Start, ve kter chcete vytvoit zstupce programu. Mete tak zadat nov jmno pro vytvoen nov sloky." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nevytvet zstupce" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Odinstalovat program $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Odebrat program $(^NameDA) z Vaeho potae." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Opravdu chcete ukonit instalaci programu $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Skuten chcete ukonit odinstalaci $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Danish.nlf b/installer/NSIS/Contrib/Language files/Danish.nlf new file mode 100644 index 0000000..10e0f54 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Danish.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1030 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Claus Futtrup +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Installation +# ^UninstallCaption +$(^Name) Afinstallation +# ^LicenseSubCaption +: Licensaftale +# ^ComponentsSubCaption +: Installationsmuligheder +# ^DirSubCaption +: Installationsmappe +# ^InstallingSubCaption +: Installerer +# ^CompletedSubCaption +: Gennemfrt +# ^UnComponentsSubCaption +: Afinstallationsmuligheder +# ^UnDirSubCaption +: Afinstallationsmappe +# ^ConfirmSubCaption +: Bekrft +# ^UninstallingSubCaption +: Afinstallerer +# ^UnCompletedSubCaption +: Gennemfrt +# ^BackBtn +< &Tilbage +# ^NextBtn +&Nste > +# ^AgreeBtn +Jeg &accepterer +# ^AcceptBtn +Jeg &accepterer vilkrene i licensaftalen +# ^DontAcceptBtn +Jeg &accepterer ikke vilkrene i licensaftalen +# ^InstallBtn +&Installer +# ^UninstallBtn +&Afinstaller +# ^CancelBtn +Afbryd +# ^CloseBtn +&Luk +# ^BrowseBtn +G&ennemse... +# ^ShowDetailsBtn +Vis &detaljer +# ^ClickNext +Tryk Nste for at fortstte. +# ^ClickInstall +Tryk Installer for at starte installationen. +# ^ClickUninstall +Tryk Afinstaller for at starte afinstallationen. +# ^Name +Navn +# ^Completed +Gennemfrt +# ^LicenseText +Ls venligst licensaftalen fr installationen af $(^NameDA). Hvis du accepterer alle betingelser i aftalen, skal du trykke 'Jeg accepterer'. +# ^LicenseTextCB +Ls venligst licensaftalen fr installationen af $(^NameDA). Hvis du accepterer alle betingelser i aftalen, skal du markere afkrydsningsfeltet nedenfor. $_CLICK +# ^LicenseTextRB +Ls venligst licensaftalen fr installationen af $(^NameDA). Hvis du accepterer alle betingelser i aftalen, skal du vlge den frste mulighed nedenfor. $_CLICK +# ^UnLicenseText +Ls venligst licensaftalen fr afinstallationen af $(^NameDA). Hvis du accepterer alle betingelser i aftalen, skal du trykke 'Jeg accepterer' +# ^UnLicenseTextCB +Ls venligst licensaftalen fr afinstallationen af $(^NameDA). Hvis du accepterer alle betingelser i aftalen, skal du markere afkrydsningsfeltet nedenfor. $_CLICK +# ^UnLicenseTextRB +Ls venligst licensaftalen fr afinstallationen af $(^NameDA). Hvis du accepterer alle betingelser i aftalen, skal du vlge den frste mulighed nedenfor $_CLICK +# ^Custom +Brugerdefineret +# ^ComponentsText +Marker de komponenter du vil installere, og fjern markeringen af de komponenter du ikke vil installere. $_CLICK +# ^ComponentsSubText1 +Vlg installationstype: +# ^ComponentsSubText2_NoInstTypes +Vlg de komponenter der skal installeres: +# ^ComponentsSubText2 +Eller vlg de tillgskomponenter komponenter du nsker at installere: +# ^UnComponentsText +Marker de komponenter du vil afinstallere, og fjern markeringen af de komponenter du ikke vil afinstallere. $_CLICK +# ^UnComponentsSubText1 +Vlg afinstallationstype: +# ^UnComponentsSubText2_NoInstTypes +Vlg de komponenter der skal afinstalleres: +# ^UnComponentsSubText2 +Eller vlg de tillgskomponenter du nsker at afinstallere: +# ^DirText +Installationsguiden vil installere $(^NameDA) i flgende mappe. For at installere i en anden mappe, tryk Gennemse og vlg en anden mappe. $_CLICK +# ^DirSubText +Destinationsmappe +# ^DirBrowseText +Vlg den mappe $(^NameDA) skal installeres i: +# ^UnDirText +Installationsguiden vil afinstallere $(^NameDA) fra flgende mappe. For at afinstallere fra en anden mappe, tryk Gennemse og vlg en anden mappe. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Vlg den mappe hvorfra $(^NameDA) skal afinstalleres: +# ^SpaceAvailable +"Ledig plads: " +# ^SpaceRequired +"Ndvendig plads: " +# ^UninstallingText +$(^NameDA) vil blive afinstalleret fra flgende mappe. $_CLICK +# ^UninstallingSubText +Afinstallerer fra: +# ^FileError +Fejl ved skrivning af fil: \r\n\t"$0"\r\nTryk Afbryd for at afbryde installationen,\r\nPrv Igen for at prve at skrive til filen, eller\r\nIgnorer for at springe over denne fil +# ^FileError_NoIgnore +Fejl ved bning af fil: \r\n\t"$0"\r\nTryk Prv Igen for at prve at skrive til filen, eller\r\nAfbryd for at afbryde installationen +# ^CantWrite +"Kan ikke skrive: " +# ^CopyFailed +Kopiering mislykkedes +# ^CopyTo +"Kopier til " +# ^Registering +"Registrerer: " +# ^Unregistering +"Afregisterer: " +# ^SymbolNotFound +"Kunne ikke finde symbol: " +# ^CouldNotLoad +"Kunne ikke hente: " +# ^CreateFolder +"Opret mappe: " +# ^CreateShortcut +"Opret genvej: " +# ^CreatedUninstaller +"Afinstallationsguide oprettet: " +# ^Delete +"Slet fil: " +# ^DeleteOnReboot +"Slet ved genstart: " +# ^ErrorCreatingShortcut +"Fejl ved oprettelse af genvej: " +# ^ErrorCreating +"Fejl ved oprettelse: " +# ^ErrorDecompressing +Fejl ved udpakning af data! Installationsguiden skadet? +# ^ErrorRegistering +Fejl ved registrering af DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Kr: " +# ^Extract +"Udpak: " +# ^ErrorWriting +"Udpak: Fejl ved skrivning til fil " +# ^InvalidOpcode +Installationsguide i stykker: Ugyldig opcode +# ^NoOLE +"Ingen OLE for: " +# ^OutputFolder +"Outputmappe: " +# ^RemoveFolder +"Slet mappe: " +# ^RenameOnReboot +"Omdb ved genstart: " +# ^Rename +"Omdb: " +# ^Skipped +"Sprunget over: " +# ^CopyDetails +Kopier detaljer til udklipsholderen +# ^LogInstall +Log installationsproces +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Danish.nsh b/installer/NSIS/Contrib/Language files/Danish.nsh new file mode 100644 index 0000000..7c25943 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Danish.nsh @@ -0,0 +1,121 @@ +;Language: Danish (1030) +;By Claus Futtrup + +!insertmacro LANGFILE "Danish" "Dansk" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Velkommen til installationsguiden for $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Denne guide vil installere $(^NameDA) p din computer.$\r$\n$\r$\nDet anbefales, at du lukker alle krende programmer inden start af installationsguiden. Dette vil tillade guiden at opdatere de ndvendige systemfiler uden at skulle genstarte computeren.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Velkommen til $(^NameDA) afinstallationsguiden" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Denne afinstallationsguide vil hjlpe dig gennem afinstallationen af $(^NameDA).$\r$\n$\r$\nFr start af afinstallationen skal du vre sikker p at $(^NameDA) ikke krer.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licensaftale" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Ls venligst licensaftalen fr du installerer $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Tryk 'Jeg accepterer' hvis du nsker at accepterer alle vilkrene i aftalen og forstte. Du skal acceptere vilkrene for at installere $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Hvis du accepterer alle vilkrene i aftalen, skal du markere afkrydsningsfeltet nedenfor. Du skal acceptere vilkrene for at installere $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Hvis du accepterer alle vilkrene i aftalen, skal du vlge den frste mulighed nedenfor. Du skal acceptere vilkrene for at installere $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licensaftale" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Ls venligst licensvilkrene fr afinstalleringen af $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Hvis du accepterer vilkrene for aftalen, skal du trykke 'Jeg accepterer' for at fortstte. Du skal acceptere aftalen for at afinstallere $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Hvis du accepterer vilkrene for aftalen, skal du markere afkrydsningsfeltet nedenfor. Du skal acceptere aftalen for at afinstallere $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Hvis du accepterer vilkrene for aftalen, skal du vlge den frste mulighed nedenfor. Du skal acceptere aftalen for at afinstallere $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Tryk Page Down for at se resten af aftalen." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Vlg komponenter" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Vlg hvilke features af $(^NameDA) du vil installere." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Vlg komponenter" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Vlg hvilke features af $(^NameDA) du vil afinstallere." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beskrivelse" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Placer musemarkren over en komponent for at se beskrivelsen af komponenten." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Placer musemarkren over en komponent for at se beskrivelsen af komponenten." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Vlg installationsmappe" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Vlg hvilken mappe du vil installere $(^NameDA) i." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Vlg afinstallationsmappe" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Vlg den mappe hvorfra du vil afinstallere $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installerer" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Vent venligst mens $(^NameDA) bliver installeret." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installation gennemfrt" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Installationsguiden blev gennemfrt med succes." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installation afbrudt" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Installationsguiden blev ikke gennemfrt." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Afinstallerer" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Vent venligst mens $(^NameDA) bliver afinstalleret." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Afinstallationen er frdig" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Afinstallationen blev afsluttet med succes." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Afinstallationen er blevet afbrudt" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Afinstallationen blev ikke genmmenfrt." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Afslutter $(^NameDA) installationsguiden" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) er blevet installeret p din computer.$\r$\n$\r$\nTryk 'Afslut' for at lukke installationsguiden." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Din computer skal genstartes fr installationen af $(^NameDA) er afsluttet. Vil du genstarte nu?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Afslutter $(^NameDA) afinstallationsguiden" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) er blevet afinstalleret fra din computer.$\r$\n$\r$\nTryk 'Afslut' for at lukke denne guide." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Din computer skal genstartes for at gennemfre afinstallationen af $(^NameDA). Vil du genstarte nu?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Genstart nu" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Jeg genstarter selv p et andet tidspunkt" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Start $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Vis vigtig information" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Afslut" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Vlg Start Menu mappe" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Vlg en Start Menu mappe til programmets genveje." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Vlg Start Menu mappen hvor du vil lave programmets genveje. Du kan ogs skrive et navn for at oprette en ny mappe." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Opret ikke genveje" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Afinstaller $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Slet $(^NameDA) fra din computer." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Er du sikker p, at du vil afslutte $(^Name) installationen?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Er du sikker p at du vil afbryde $(^Name) afinstallationen?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Dutch.nlf b/installer/NSIS/Contrib/Language files/Dutch.nlf new file mode 100644 index 0000000..a39a3d7 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Dutch.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1043 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Joost Verburg & Hendri Adireans, fixes by Milan Bast +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name)-installatie +# ^UninstallCaption +$(^Name)-denstallatie +# ^LicenseSubCaption +: Licentieovereenkomst +# ^ComponentsSubCaption +: Installatieopties +# ^DirSubCaption +: Installatiemap +# ^InstallingSubCaption +: Bezig met installeren +# ^CompletedSubCaption +: Voltooid +# ^UnComponentsSubCaption +: Verwijderingsopties +# ^UnDirSubCaption +: Te verwijderen map +# ^ConfirmSubCaption +: Bevestiging +# ^UninstallingSubCaption +: Bezig met verwijderen +# ^UnCompletedSubCaption +: Voltooid +# ^BackBtn +< V&orige +# ^NextBtn +&Volgende > +# ^AgreeBtn +&Akkoord +# ^AcceptBtn +Ik &accepteer de overeenkomst +# ^DontAcceptBtn +Ik accepteer de overeenkomst &niet +# ^InstallBtn +&Installeren +# ^UninstallBtn +&Verwijderen +# ^CancelBtn +Annuleren +# ^CloseBtn +&Afsluiten +# ^BrowseBtn +&Bladeren... +# ^ShowDetailsBtn +&Details tonen +# ^ClickNext +Klik op Volgende om verder te gaan. +# ^ClickInstall +Klik op Installeren om de installatie te beginnen. +# ^ClickUninstall +Klik op Verwijderen om de denstallatie te beginnen. +# ^Name +Naam +# ^Completed +Voltooid +# ^LicenseText +Lees de licentieovereenkomst voordat u $(^NameDA) installeert. Klik op Akkoord als u de overeenkomst accepteert. +# ^LicenseTextCB +Lees de licentieovereenkomst voordat u $(^NameDA) installeert. Schakel het selectievakje hieronder in als u de overeenkomst accepteert. $_CLICK +# ^LicenseTextRB +Lees de licentieovereenkomst voordat u $(^NameDA) installeert. Selecteer de eerste optie hieronder als u de overeenkomst accepteert. $_CLICK +# ^UnLicenseText +Lees de licentieovereenkomst voordat u $(^NameDA) verwijdert. Klik op Akkoord als u de overeenkomst accepteert. +# ^UnLicenseTextCB +Lees de licentieovereenkomst voordat u $(^NameDA) verwijdert. Schakel het selectievakje hieronder in als u de overeenkomst accepteert. $_CLICK +# ^UnLicenseTextRB +Lees de licentieovereenkomst voordat u $(^NameDA) verwijdert. Selecteer de eerste optie hieronder als u de overeenkomst accepteert. $_CLICK +# ^Custom +Aangepast +# ^ComponentsText +Selecteer de onderdelen die u wilt installeren en deselecteer welke u niet wilt installeren. $_CLICK +# ^ComponentsSubText1 +Selecteer het installatietype: +# ^ComponentsSubText2_NoInstTypes +Selecteer de onderdelen die moeten worden genstalleerd: +# ^ComponentsSubText2 +Of selecteer de optionele onderdelen die moeten worden genstalleerd: +# ^UnComponentsText +Selecteer de onderdelen die u wilt verwijderen en deselecteer welke u niet wilt verwijderen. $_CLICK +# ^UnComponentsSubText1 +Selecteer het type verwijdering: +# ^UnComponentsSubText2_NoInstTypes +Selecteer de onderdelen die moeten worden verwijderd: +# ^UnComponentsSubText2 +Of selecteer de optionele onderdelen die moeten worden verwijderd: +# ^DirText +Setup zal $(^NameDA) in de volgende map installeren. Klik op Bladeren als u $(^NameDA) in een andere map wilt installeren en selecteer deze. $_CLICK +# ^DirSubText +Installatiemap +# ^DirBrowseText +Selecteer de map om $(^NameDA) in te installeren: +# ^UnDirText +Setup zal $(^NameDA) uit de volgende map verwijderen. Klik op Bladeren als u $(^NameDA) uit een andere map wilt verwijderen en selecteer deze. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Selecteer de map om $(^NameDA) uit te verwijderen: +# ^SpaceAvailable +"Beschikbare ruimte: " +# ^SpaceRequired +"Vereiste ruimte: " +# ^UninstallingText +$(^NameDA) zal uit de volgende map worden verwijderd. $_CLICK +# ^UninstallingSubText +Verwijderen uit: +# ^FileError +Fout bij het schrijven naar bestand: \r\n\r\n$0\r\n\r\nKlik op Afbreken om de installatie te stoppen,\r\nOpnieuw om het opnieuw te proberen of\r\nNegeren om dit bestand over te slaan. +# ^FileError_NoIgnore +Fout bij het schrijven naar bestand: \r\n\r\n$0\r\n\r\nKlik op Opnieuw om het opnieuw te proberen of \r\nAnnuleren om de installatie te stoppen. +# ^CantWrite +"Kon niet schrijven: " +# ^CopyFailed +Kopiren mislukt +# ^CopyTo +"Kopiren naar " +# ^Registering +"Registreren: " +# ^Unregistering +"Deregistreren: " +# ^SymbolNotFound +"Kon symbool niet vinden: " +# ^CouldNotLoad +"Kon niet laden: " +# ^CreateFolder +"Map maken: " +# ^CreateShortcut +"Snelkoppeling maken: " +# ^CreatedUninstaller +"Denstallatieprogramma gemaakt: " +# ^Delete +"Bestand verwijderen: " +# ^DeleteOnReboot +"Verwijderen na opnieuw opstarten: " +# ^ErrorCreatingShortcut +"Fout bij maken snelkoppeling: " +# ^ErrorCreating +"Fout bij maken: " +# ^ErrorDecompressing +Fout bij uitpakken van gegevens! Gegevens beschadigd? +# ^ErrorRegistering +Fout bij registreren DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Uitvoeren: " +# ^Extract +"Uitpakken: " +# ^ErrorWriting +"Uitpakken: fout bij schrijven naar bestand " +# ^InvalidOpcode +Installatieprogramma beschadigd: ongeldige opcode +# ^NoOLE +"Geen OLE voor: " +# ^OutputFolder +"Uitvoermap: " +# ^RemoveFolder +"Map verwijderen: " +# ^RenameOnReboot +"Hernoemen na opnieuw opstarten: " +# ^Rename +"Hernoemen: " +# ^Skipped +"Overgeslagen: " +# ^CopyDetails +Details kopiren naar klembord +# ^LogInstall +Gegevens over installatie bewaren +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Dutch.nsh b/installer/NSIS/Contrib/Language files/Dutch.nsh new file mode 100644 index 0000000..d62b7cb --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Dutch.nsh @@ -0,0 +1,129 @@ +;Language: Dutch (1043) +;By Joost Verburg + +!insertmacro LANGFILE "Dutch" "Nederlands" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Welkom bij de $(^NameDA)-installatiewizard" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Deze wizard zal $(^NameDA) op uw systeem installeren.$\r$\n$\r$\nHet wordt aanbevolen alle overige toepassingen af te sluiten alvorens de installatie te starten. Dit maakt het mogelijk relevante systeembestanden bij te werken zonder uw systeem opnieuw op te moeten starten.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Welkom bij de $(^NameDA)-denstallatiewizard" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Deze wizard zal $(^NameDA) van uw syteem verwijderen.$\r$\n$\r$\nControleer voordat u begint met verwijderen of $(^NameDA) is afgesloten.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licentieovereenkomst" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Lees de licentieovereenkomst voordat u $(^NameDA) installeert." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Klik op Akkoord om verder te gaan als u de overeenkomst accepteert. U moet de overeenkomst accepteren om $(^NameDA) te installeren." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Schakel het selectievakje hieronder in als u de overeenkomst accepteert. U moet de overeenkomst accepteren om $(^NameDA) te installeren." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Selecteer de eerste optie hieronder als u de overeenkomst accepteert. U moet de overeenkomst accepteren om $(^NameDA) te installeren." +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licentieovereenkomst" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Lees de licentieovereenkomst voordat u $(^NameDA) verwijdert." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Klik op Akkoord op verder te gaan als u de overeenkomst accepteert. U moet de overeenkomst accepteren om $(^NameDA) te verwijderen." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Schakel het selectievakje hieronder in als u de overeenkomst accepteert. U moet de overeenkomst accepteren om $(^NameDA) te verwijderen." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Selecteer de eerste optie hieronder als u de overeenkomst accepteert. U moet de overeenkomst accepteren om $(^NameDA) te verwijderen." +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Druk op Page Down om de rest van de overeenkomst te zien." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Onderdelen kiezen" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Kies de onderdelen die u wilt installeren." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Onderdelen kiezen" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Kies de onderdelen die u wilt verwijderen." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beschrijving" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beweeg uw muis over een onderdeel om de beschrijving te zien." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beweeg uw muis over een onderdeel om de beschrijving te zien." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Installatielocatie kiezen" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Kies de map waarin u $(^NameDA) wilt installeren." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Locatie kiezen" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Kies de map waaruit u $(^NameDA) wilt verwijderen." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Bezig met installeren" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Een ogenblik geduld terwijl $(^NameDA) wordt genstalleerd." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installatie voltooid" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "De installatie is succesvol voltooid." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installatie afgebroken" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "De installatie is niet voltooid." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Bezig met verwijderen" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Een ogenblik geduld terwijl $(^NameDA) van uw systeem wordt verwijderd." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Verwijderen gereed" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "$(^NameDA) is van uw systeem verwijderd." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Verwijderen afgebroken" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "$(^NameDA) is niet volledig van uw systeem verwijderd." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Voltooien van de $(^NameDA)-installatiewizard" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) is genstalleerd op uw systeem.$\r$\n$\r$\nKlik op Voltooien om deze wizard te sluiten." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Uw systeem moet opnieuw worden opgestart om de installatie van $(^NameDA) te voltooien. Wilt u nu herstarten?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Voltooien van de $(^NameDA)-denstallatiewizard" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) is van uw systeem verwijderd.$\r$\n$\r$\nKlik op Voltooien om deze wizard te sluiten." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Uw systeem moet opnieuw worden opgestart om het verwijderen van $(^NameDA) te voltooien. Wilt u nu herstarten?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Nu herstarten" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ik wil later handmatig herstarten" + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) &starten" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Leesmij weergeven" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Voltooien" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Startmenumap kiezen" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Kies een map in het menu Start voor de snelkoppelingen van $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Kies een map in het menu Start waarin de snelkoppelingen moeten worden aangemaakt. U kunt ook een naam invoeren om een nieuwe map te maken." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Geen snelkoppelingen aanmaken" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "$(^NameDA) verwijderen" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA) van uw systeem verwijderen." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Weet u zeker dat u de $(^Name)-installatie wilt afsluiten?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Weet u zeker dat u de $(^Name)-denstallatie wilt afsluiten?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Gebruikers kiezen" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Kies voor welke gebruikers u $(^NameDA) wilt installeren." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Selecteer of u $(^NameDA) alleen voor uzelf of voor alle gebruikers van deze computer wilt installeren. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Installeer voor alle gebruikers van deze computer" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Installeer alleen voor mijzelf" +!endif diff --git a/installer/NSIS/Contrib/Language files/English.nlf b/installer/NSIS/Contrib/Language files/English.nlf new file mode 100644 index 0000000..d325f03 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/English.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1033 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +- +# RTL - anything else than RTL means LTR +- +# Translation by ..... (any credits should go here) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Setup +# ^UninstallCaption +$(^Name) Uninstall +# ^LicenseSubCaption +: License Agreement +# ^ComponentsSubCaption +: Installation Options +# ^DirSubCaption +: Installation Folder +# ^InstallingSubCaption +: Installing +# ^CompletedSubCaption +: Completed +# ^UnComponentsSubCaption +: Uninstallation Options +# ^UnDirSubCaption +: Uninstallation Folder +# ^ConfirmSubCaption +: Confirmation +# ^UninstallingSubCaption +: Uninstalling +# ^UnCompletedSubCaption +: Completed +# ^BackBtn +< &Back +# ^NextBtn +&Next > +# ^AgreeBtn +I &Agree +# ^AcceptBtn +I &accept the terms of the License Agreement +# ^DontAcceptBtn +I &do not accept the terms of the License Agreement +# ^InstallBtn +&Install +# ^UninstallBtn +&Uninstall +# ^CancelBtn +Cancel +# ^CloseBtn +&Close +# ^BrowseBtn +B&rowse... +# ^ShowDetailsBtn +Show &details +# ^ClickNext +Click Next to continue. +# ^ClickInstall +Click Install to start the installation. +# ^ClickUninstall +Click Uninstall to start the uninstallation. +# ^Name +Name +# ^Completed +Completed +# ^LicenseText +Please review the license agreement before installing $(^NameDA). If you accept all terms of the agreement, click I Agree. +# ^LicenseTextCB +Please review the license agreement before installing $(^NameDA). If you accept all terms of the agreement, click the check box below. $_CLICK +# ^LicenseTextRB +Please review the license agreement before installing $(^NameDA). If you accept all terms of the agreement, select the first option below. $_CLICK +# ^UnLicenseText +Please review the license agreement before uninstalling $(^NameDA). If you accept all terms of the agreement, click I Agree. +# ^UnLicenseTextCB +Please review the license agreement before uninstalling $(^NameDA). If you accept all terms of the agreement, click the check box below. $_CLICK +# ^UnLicenseTextRB +Please review the license agreement before uninstalling $(^NameDA). If you accept all terms of the agreement, select the first option below. $_CLICK +# ^Custom +Custom +# ^ComponentsText +Check the components you want to install and uncheck the components you don't want to install. $_CLICK +# ^ComponentsSubText1 +Select the type of install: +# ^ComponentsSubText2_NoInstTypes +Select components to install: +# ^ComponentsSubText2 +Or, select the optional components you wish to install: +# ^UnComponentsText +Check the components you want to uninstall and uncheck the components you don't want to uninstall. $_CLICK +# ^UnComponentsSubText1 +Select the type of uninstall: +# ^UnComponentsSubText2_NoInstTypes +Select components to uninstall: +# ^UnComponentsSubText2 +Or, select the optional components you wish to uninstall: +# ^DirText +Setup will install $(^NameDA) in the following folder. To install in a different folder, click Browse and select another folder. $_CLICK +# ^DirSubText +Destination Folder +# ^DirBrowseText +Select the folder to install $(^NameDA) in: +# ^UnDirText +Setup will uninstall $(^NameDA) from the following folder. To uninstall from a different folder, click Browse and select another folder. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Select the folder to uninstall $(^NameDA) from: +# ^SpaceAvailable +"Space available: " +# ^SpaceRequired +"Space required: " +# ^UninstallingText +$(^NameDA) will be uninstalled from the following folder. $_CLICK +# ^UninstallingSubText +Uninstalling from: +# ^FileError +Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Abort to stop the installation,\r\nRetry to try again, or\r\nIgnore to skip this file. +# ^FileError_NoIgnore +Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Retry to try again, or\r\nCancel to stop the installation. +# ^CantWrite +"Can't write: " +# ^CopyFailed +Copy failed +# ^CopyTo +"Copy to " +# ^Registering +"Registering: " +# ^Unregistering +"Unregistering: " +# ^SymbolNotFound +"Could not find symbol: " +# ^CouldNotLoad +"Could not load: " +# ^CreateFolder +"Create folder: " +# ^CreateShortcut +"Create shortcut: " +# ^CreatedUninstaller +"Created uninstaller: " +# ^Delete +"Delete file: " +# ^DeleteOnReboot +"Delete on reboot: " +# ^ErrorCreatingShortcut +"Error creating shortcut: " +# ^ErrorCreating +"Error creating: " +# ^ErrorDecompressing +Error decompressing data! Corrupted installer? +# ^ErrorRegistering +Error registering DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Execute: " +# ^Extract +"Extract: " +# ^ErrorWriting +"Extract: error writing to file " +# ^InvalidOpcode +Installer corrupted: invalid opcode +# ^NoOLE +"No OLE for: " +# ^OutputFolder +"Output folder: " +# ^RemoveFolder +"Remove folder: " +# ^RenameOnReboot +"Rename on reboot: " +# ^Rename +"Rename: " +# ^Skipped +"Skipped: " +# ^CopyDetails +Copy Details To Clipboard +# ^LogInstall +Log install process +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/English.nsh b/installer/NSIS/Contrib/Language files/English.nsh new file mode 100644 index 0000000..3f606a1 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/English.nsh @@ -0,0 +1,129 @@ +;Language: English (1033) +;By Joost Verburg + +!insertmacro LANGFILE "English" "English" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Welcome to the $(^NameDA) Setup Wizard" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "This wizard will guide you through the installation of $(^NameDA).$\r$\n$\r$\nIt is recommended that you close all other applications before starting Setup. This will make it possible to update relevant system files without having to reboot your computer.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Welcome to the $(^NameDA) Uninstall Wizard" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "This wizard will guide you through the uninstallation of $(^NameDA).$\r$\n$\r$\nBefore starting the uninstallation, make sure $(^NameDA) is not running.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "License Agreement" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Please review the license terms before installing $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "If you accept the terms of the agreement, click I Agree to continue. You must accept the agreement to install $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "If you accept the terms of the agreement, click the check box below. You must accept the agreement to install $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "If you accept the terms of the agreement, select the first option below. You must accept the agreement to install $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "License Agreement" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Please review the license terms before uninstalling $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "If you accept the terms of the agreement, click I Agree to continue. You must accept the agreement to uninstall $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "If you accept the terms of the agreement, click the check box below. You must accept the agreement to uninstall $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "If you accept the terms of the agreement, select the first option below. You must accept the agreement to uninstall $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Press Page Down to see the rest of the agreement." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Choose Components" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Choose which features of $(^NameDA) you want to install." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Choose Components" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Choose which features of $(^NameDA) you want to uninstall." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Description" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Position your mouse over a component to see its description." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Position your mouse over a component to see its description." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Choose Install Location" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Choose the folder in which to install $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Choose Uninstall Location" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Choose the folder from which to uninstall $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installing" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Please wait while $(^NameDA) is being installed." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installation Complete" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Setup was completed successfully." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installation Aborted" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Setup was not completed successfully." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Uninstalling" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Please wait while $(^NameDA) is being uninstalled." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Uninstallation Complete" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Uninstall was completed successfully." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Uninstallation Aborted" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Uninstall was not completed successfully." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Completing the $(^NameDA) Setup Wizard" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) has been installed on your computer.$\r$\n$\r$\nClick Finish to close this wizard." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Your computer must be restarted in order to complete the installation of $(^NameDA). Do you want to reboot now?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Completing the $(^NameDA) Uninstall Wizard" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) has been uninstalled from your computer.$\r$\n$\r$\nClick Finish to close this wizard." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Your computer must be restarted in order to complete the uninstallation of $(^NameDA). Do you want to reboot now?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reboot now" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "I want to manually reboot later" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Run $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Show Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Finish" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Choose Start Menu Folder" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Choose a Start Menu folder for the $(^NameDA) shortcuts." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Select the Start Menu folder in which you would like to create the program's shortcuts. You can also enter a name to create a new folder." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Do not create shortcuts" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Uninstall $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Remove $(^NameDA) from your computer." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Are you sure you want to quit $(^Name) Setup?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Are you sure you want to quit $(^Name) Uninstall?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Choose Users" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Choose for which users you want to install $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Select whether you want to install $(^NameDA) for yourself only or for all users of this computer. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Install for anyone using this computer" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Install just for me" +!endif diff --git a/installer/NSIS/Contrib/Language files/Esperanto.nlf b/installer/NSIS/Contrib/Language files/Esperanto.nlf new file mode 100644 index 0000000..c1b1303 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Esperanto.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID (none exists for Kurdish at the moment) +9998 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +- +# RTL - anything else than RTL means LTR +- +# Translation v4.0.3 by Felipe Castro +# ^Branding +Instalada Sistemo de Nullsoft %s +# ^SetupCaption +Instalado de $(^Name) +# ^UninstallCaption +Malinstalado de $(^Name) +# ^LicenseSubCaption +: Permes-Kontrakto +# ^ComponentsSubCaption +: Instaladaj Opcioj +# ^DirSubCaption +: Instalada Dosierujo +# ^InstallingSubCaption +: Oni Instalas Dosierojn +# ^CompletedSubCaption +: Kompletite +# ^UnComponentsSubCaption +: Malinstaladaj Opcioj +# ^UnDirSubCaption +: Malinstalada Dosierujo +# ^ConfirmSubCaption +: Konfirmo +# ^UninstallingSubCaption +: Oni malinstalas +# ^UnCompletedSubCaption +: Kompletite +# ^BackBtn +< &Antauxe +# ^NextBtn +&Sekve > +# ^AgreeBtn +&Akceptite +# ^AcceptBtn +Mi &akceptas la kondicxojn de la Permes-Kontrakto +# ^DontAcceptBtn +Mi &ne akceptas la kondicxojn de la Permes-Kontrakto +# ^InstallBtn +&Instali +# ^UninstallBtn +&Malinstali +# ^CancelBtn +Nuligi +# ^CloseBtn +&Fermi +# ^BrowseBtn +&Sercxi... +# ^ShowDetailsBtn +Vidi &Detalojn +# ^ClickNext +Musklaku en 'Sekve' por dauxrigi. +# ^ClickInstall +Musklaku en 'Instali' por ekigi la instaladon. +# ^ClickUninstall +Musklaku en 'Malinstali' por ekigi la malinstaladon. +# ^Name +Nomo +# ^Completed +Kompletite +# ^LicenseText +Bonvole revidu la permes-akordon antaux ol instali $(^NameDA). Se vi konsentas kun cxiuj kondicxoj de la permeso, musklaku en 'Akceptite'. +# ^LicenseTextCB +Bonvole revidu la permes-akordon antaux ol instali $(^NameDA). Se vi konsentas kun cxiuj kondicxoj de la permeso, musklaku en la suba elekt-skatolo. $_CLICK +# ^LicenseTextRB +Bonvole revidu la permes-akordon antaux ol instali $(^NameDA). Se vi konsentas kun cxiuj kondicxoj de la permeso, elektu la unuan opcion sube. $_CLICK +# ^UnLicenseText +Bonvole revidu la permes-akordon antaux ol malinstali $(^NameDA). Se vi konsentas kun cxiuj kondicxoj de la permeso, musklaku en 'Akceptite'. +# ^UnLicenseTextCB +Bonvole revidu la permes-akordon antaux ol malinstali $(^NameDA). Se vi konsentas kun cxiuj kondicxoj de la permeso, musklaku en la suba elekt-skatolo. $_CLICK +# ^UnLicenseTextRB +Bonvole revidu la permes-akordon antaux ol malinstali $(^NameDA). Se vi konsentas kun cxiuj kondicxoj de la permeso, elektu la unuan opcion sube. $_CLICK +# ^Custom +Personigite +# ^ComponentsText +Marku la konsisterojn, kiujn vi deziras instali kaj malmarku tiujn, kiujn vi ne deziras instali. $_CLICK +# ^ComponentsSubText1 +Elektu la tipon de instalado: +# ^ComponentsSubText2_NoInstTypes +Elektu la konsisterojn por instali: +# ^ComponentsSubText2 +Aux, elektu la nedevigajn konsisterojn, kiujn vi deziras instali: +# ^UnComponentsText +Marku la konsisterojn, kiujn vi volas malinstali aux male. $_CLICK +# ^UnComponentsSubText1 +Elektu la tipon de malinstalado: +# ^UnComponentsSubText2_NoInstTypes +Elektu la konsisterojn por malinstali: +# ^UnComponentsSubText2 +Aux, elektu la nedevigajn konsisterojn, kiujn vi deziras malinstali: +# ^DirText +$(^NameDA) estos instalita en la jena dosierujo. Por instali en alia dosierujo, musklaku en 'Sercxi...' kaj elektu gxin. $_CLICK +# ^DirSubText +Celota Dosierujo +# ^DirBrowseText +Elektu dosierujon por instali $(^NameDA): +# ^UnDirText +$(^NameDA) estos malinstalita el la jena dosierujo. Por malinstali en alia dosierujo, musklaku en 'Sercxi...' kaj elektu gxin. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Elektu dosierujon el kie $(^NameDA) estos malinstalita: +# ^SpaceAvailable +"Disponebla spaco: " +# ^SpaceRequired +"Postulata spaco: " +# ^UninstallingText +$(^NameDA) estos malinstalita el la jena dosierujo. $_CLICK +# ^UninstallingSubText +Malinstalado el: +# ^FileError +Eraro dum malfermo de dosiero por skribi: \r\n\t"$0"\r\nMusklaku en Cxesigi por finigi la instaladon,\r\Ripeti por provi refoje skribi sur la dosiero, aux\r\nPreteratenti por preteratenti tiun cxi dosieron. +# ^FileError_NoIgnore +Eraro dum malfermo de dosierujo por skribi: \r\n\t"$0"\r\nMusklaku en Ripeti por provi refoje skribi sur la dosiero, aux\r\nNuligi por cxesigi la instaladon. +# ^CantWrite +"Ne eblis skribi: " +# ^CopyFailed +Malsukceso dum kopio +# ^CopyTo +"Kopii al " +# ^Registering +"Oni registras: " +# ^Unregistering +"Oni malregistras: " +# ^SymbolNotFound +"Ne trovita simbolo: " +# ^CouldNotLoad +"Ne eblis sxargi: " +# ^CreateFolder +"Oni kreas subdosierujon: " +# ^CreateShortcut +"Oni kreas lancxilon: " +# ^CreatedUninstaller +"Oni kreas malinstalilon: " +# ^Delete +"Oni forigas dosieron: " +# ^DeleteOnReboot +"Forigi je restarto: " +# ^ErrorCreatingShortcut +"Eraro dum kreo de lancxilo: " +# ^ErrorCreating +"Eraro dum kreo: " +# ^ErrorDecompressing +Eraro dum malkompaktigo de datumaro! Cxu misrompita instalilo? +# ^ErrorRegistering +Eraru dum registro de DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Lancxi: " +# ^Extract +"Eltiri: " +# ^ErrorWriting +"Eltirado: eraro dum skribo de dosiero " +# ^InvalidOpcode +Misrompita instalilo: malvalida operaci-kodo +# ^NoOLE +"Sen OLE por: " +# ^OutputFolder +"Celota dosierujo: " +# ^RemoveFolder +"Oni forigas la dosierujon: " +# ^RenameOnReboot +"Renomigi je restarto: " +# ^Rename +"Oni renomigas: " +# ^Skipped +"Preterpasita: " +# ^CopyDetails +Kopii detalojn al la tondejo +# ^LogInstall +Registri instalad-procezo +# ^Byte +B +# ^Kilo +k +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Esperanto.nsh b/installer/NSIS/Contrib/Language files/Esperanto.nsh new file mode 100644 index 0000000..8cabd25 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Esperanto.nsh @@ -0,0 +1,129 @@ +;Language: Esperanto (0) +;By Felipe Castro + +!insertmacro LANGFILE "Esperanto" "Esperanto" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Bonvenon al la Gvidilo por Instalado de $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Tiu cxi gvidilo helpos vin dum la instalado de $(^NameDA).$\r$\n$\r$\nOni rekomendas fermi cxiujn aliajn aplikajxojn antaux ol ekigi la Instaladon. Tio cxi ebligos al la Instalilo gxisdatigi la koncernajn dosierojn de la sistemo sen bezono restartigi la komputilon.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Bonvenon al la Gvidilo por Malinstalado de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Tiu cxi gvidilo helpos vin dum la malinstalado de $(^NameDA).$\r$\n$\r$\nAntaux ol ekigi la malinstalado, certigxu ke $(^NameDA) ne estas plenumata nun.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Permes-Kontrakto" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Bonvole, kontrolu la kondicxojn de la permesilo antaux ol instali la programon $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Se vi akceptas la kondicxojn, musklaku en 'Akceptite' por dauxrigi. Vi devos akcepti la kontrakton por instali la programon $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se vi akceptas la permes-kondicxojn, musklaku la suban elekt-skatolon. Vi devos akcepti la kontrakton por instali la programon $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se vi akceptas la permes-kondicxojn, elektu la unuan opcion sube. Vi devas akcepti la kontrakton por instali la programon $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Permes-Kontrakto" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Bonvole, kontrolu la kondicxojn de la permesilo antaux ol malinstali la programon $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Se vi akceptas la kondicxojn, musklaku en 'Akceptite' por dauxrigi. Vi devos akcepti la kontrakton por malinstali la programon $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se vi akceptas la permes-kondicxojn, musklaku la suban elekt-skatolon. Vi devos akcepti la kontrakton por malinstali la programon $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se vi akceptas la permes-kondicxojn, elektu la unuan opcion sube. Vi devas akcepti la kontrakton por malinstali la programon $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Premu 'Page Down' por rigardi la reston de la permeso." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Elekto de Konsisteroj" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Elektu kiujn funkciojn de $(^NameDA) vi deziras instali." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Elekto de Konsisteroj" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Elektu kiujn funkciojn de $(^NameDA) vi deziras malinstali." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Priskribo" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Poziciu la muson sur konsistero por rigardi ties priskribon." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Poziciu la muson sur konsistero por rigardi ties priskribon." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Elekto de la Instalada Loko" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Elektu la dosierujon en kiun vi deziras instali la programon $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Elekto de la Malinstalada Loko" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Elektu la dosierujon el kiu vi deziras malinstali la programon $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Oni instalas" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Bonvole, atendu dum $(^NameDA) estas instalata." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalado Plenumite" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "La instalado sukcese plenumigxis." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalado Cxesigite" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "La instalado ne plenumigxis sukcese." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Oni malinstalas" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Bonvole, atendu dum $(^NameDA) estas malinstalata." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Malinstalado Plenumite" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "La malinstalado sukcese plenumigxis." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Malinstalado Cxesigxite" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "La malinstalado ne plenumigxis sukcese." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Oni finigas la Gvidilon por Instalado de $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) estas instalita en via komputilo.$\r$\n$\r$\nMusklaku en Finigi por fermi tiun cxi gvidilon." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Via komputilo devas esti restartigita por kompletigi la instaladon de $(^NameDA). Cxu restartigi nun?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Oni finigas la Gvidilon por Malinstalado de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) estis forigita el via komputilo.$\r$\n$\r$\nMusklaku en Finigi por fermi tiun cxi gvidilon." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Via komputilo devas esti restartigita por kompletigi la malinstaladon de $(^NameDA). Cxu restartigi nun?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Restartigi Nun" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Mi volas restartigi permane poste" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Lancxi $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Montri Legumin" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Finigi" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Elektu Dosierujon de la Ek-Menuo" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Elektu dosierujon de la Ek-Menuo por la lancxiloj de la programo." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Elektu dosierujon de la Ek-Menuo en kiu vi deziras krei la lancxilojn de la programo. Vi povas ankaux tajpi nomon por krei novan ujon." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ne krei lancxilojn" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Malinstali $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Forigi $(^NameDA) el via komputilo." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Cxu vi certe deziras nuligi la instaladon de $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Cxu vi certe deziras nuligi la malinstaladon de $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Elekti Uzantojn" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Elekti por kiuj uzantoj vi deziras instali $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Elektu cxu vi volas instali $(^NameDA) por vi mem aux por cxiuj uzantoj de tiu cxi komputilo. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Instali por iu ajn uzanto de tiu cxi komputilo" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Instali nur por mi" +!endif diff --git a/installer/NSIS/Contrib/Language files/Estonian.nlf b/installer/NSIS/Contrib/Language files/Estonian.nlf new file mode 100644 index 0000000..0b80a01 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Estonian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1061 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1257 +# RTL - anything else than RTL means LTR +- +# Translation by izzo (izzo@hot.ee) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Paigaldamine +# ^UninstallCaption +$(^Name) Eemaldamine +# ^LicenseSubCaption +: Litsentsileping +# ^ComponentsSubCaption +: Paigaldusvalikud +# ^DirSubCaption +: Paigalduskaust +# ^InstallingSubCaption +: Paigaldan +# ^CompletedSubCaption +: Valmis +# ^UnComponentsSubCaption +: Eemaldusvalikud +# ^UnDirSubCaption +: Eemalduskaust +# ^ConfirmSubCaption +: Kinnitus +# ^UninstallingSubCaption +: Eemaldan +# ^UnCompletedSubCaption +: Valmis +# ^BackBtn +< Tagasi +# ^NextBtn +Edasi > +# ^AgreeBtn +Nustun +# ^AcceptBtn +Nustun litsentsilepingu tingimustega +# ^DontAcceptBtn +Ei nustu litsentsilepingu tingimustega +# ^InstallBtn +Paigalda +# ^UninstallBtn +Eemalda +# ^CancelBtn +Loobu +# ^CloseBtn +Sule +# ^BrowseBtn +Sirvi... +# ^ShowDetailsBtn +Detailid +# ^ClickNext +Jtkamiseks vajuta Edasi. +# ^ClickInstall +Paigaldamise alustamiseks vajuta Paigalda. +# ^ClickUninstall +Eemaldamise alustamiseks vajuta Eemalda. +# ^Name +Nimi +# ^Completed +Valmis +# ^LicenseText +Enne $(^NameDA) paigaldamist vaata palun litsentsileping le. Kui sa nustud kigi lepingu tingimustega, vajuta Nustun. +# ^LicenseTextCB +Enne $(^NameDA) paigaldamist vaata palun litsentsileping le. Kui sa nustud kigi lepingu tingimustega, vali allolev mrkeruut. $_CLICK +# ^LicenseTextRB +Enne $(^NameDA) paigaldamist vaata palun litsentsileping le. Kui sa nustud kigi lepingu tingimustega, mrgi allpool esimene valik. $_CLICK +# ^UnLicenseText +Enne $(^NameDA) eemaldamist vaata palun litsentsileping le. Kui sa nustud kigi lepingu tingimustega, vajuta Nustun. +# ^UnLicenseTextCB +Enne $(^NameDA) eemaldamist vaata palun litsentsileping le. Kui sa nustud kigi lepingu tingimustega, vali allolev mrkeruut. $_CLICK +# ^UnLicenseTextRB +Enne $(^NameDA) eemaldamist vaata palun litsentsileping le. Kui sa nustud kigi lepingu tingimustega, mrgi allpool esimene valik. $_CLICK +# ^Custom +Kohandatud +# ^ComponentsText +Mrgista komponendid mida soovid paigaldada ja eemalda mrgistus neilt, mida ei soovi paigaldada. $_CLICK +# ^ComponentsSubText1 +Vali paigalduse tp: +# ^ComponentsSubText2_NoInstTypes +Vali paigaldatavad komponendid: +# ^ComponentsSubText2 +vi vali lisakomponendid mida soovid paigaldada: +# ^UnComponentsText +Mrgista komponendid mida soovid eemaldada ja eemalda mrgistus neilt, mida ei soovi eemaldada. $_CLICK +# ^UnComponentsSubText1 +Vali eemalduse tp: +# ^UnComponentsSubText2_NoInstTypes +Vali eemaldatavad komponendid: +# ^UnComponentsSubText2 +vi vali lisakomponendid mida soovid eemaldada: +# ^DirText +$(^NameDA) paigaldatakse jrgmisse kausta. Et mujale paigaldada, vajuta sirvi ja vali teine kaust. $_CLICK +# ^DirSubText +Sihtkaust +# ^DirBrowseText +Vali kaust kuhu $(^NameDA) paigaldada: +# ^UnDirText +$(^NameDA) eemaldatakse jrgmisest kaustast. Et mujalt eemaldada, vajuta sirvi ja vali teine kaust. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Vali kaust kust $(^NameDA) eemaldada: +# ^SpaceAvailable +"Vaba ruum: " +# ^SpaceRequired +"Vajalik ruum: " +# ^UninstallingText +$(^NameDA) eemaldatakse jrgmisest kaustast. $_CLICK +# ^UninstallingSubText +Eemaldan sealt: +# ^FileError +Trge faili avamisel kirjutamiseks: \r\n\t"$0"\r\nPaigalduse katkestamiseks vajuta Katkesta,\r\nvajuta rita uuesti, et faili kirjutamist uuesti proovida, vi\r\nIgnoreeri, et see fail vahele jtta. +# ^FileError_NoIgnore +Trge faili avamisel kirjutamiseks: \r\n\t"$0"\r\nVajuta rita uuesti, et faili kirjutamist uuesti proovida, vi\r\nLoobu, et paigaldamine katkestada +# ^CantWrite +"Ei saa kirjutada: " +# ^CopyFailed +Kopeerimine ebannestus +# ^CopyTo +"Kopeeri sinna " +# ^Registering +"Registreerin: " +# ^Unregistering +"Deregistreerin: " +# ^SymbolNotFound +"Ei leidnud smbolit: " +# ^CouldNotLoad +"Ei saanud laadida: " +# ^CreateFolder +"Loo kaust: " +# ^CreateShortcut +"Loo otsetee: " +# ^CreatedUninstaller +"Loodud eemaldaja: " +# ^Delete +"Kustuta fail: " +# ^DeleteOnReboot +"Kustuta taaskivitamisel: " +# ^ErrorCreatingShortcut +"Trge otsetee loomisel: " +# ^ErrorCreating +"Trge loomisel: " +# ^ErrorDecompressing +Trge andmete lahtipakkimisel! Vigane paigaldaja? +# ^ErrorRegistering +Trge DLL-i registreerimisel +# ^ExecShell +"ExecShell: " +# ^Exec +"Kivita: " +# ^Extract +"Paki lahti: " +# ^ErrorWriting +"Paki lahti: viga faili kirjutamisel " +# ^InvalidOpcode +Paigaldaja klbmatu: vigane opkood +# ^NoOLE +"No OLE for: " +# ^OutputFolder +"Vljastatav kaust: " +# ^RemoveFolder +"Eemalda kaust: " +# ^RenameOnReboot +"Taaskivitusel nimeta mber: " +# ^Rename +"Nimeta mber: " +# ^Skipped +"Vahele jetud: " +# ^CopyDetails +Kopeeri detailid likelauale +# ^LogInstall +Logi paigaldusprotsess +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Estonian.nsh b/installer/NSIS/Contrib/Language files/Estonian.nsh new file mode 100644 index 0000000..883836c --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Estonian.nsh @@ -0,0 +1,121 @@ +;Language: Estonian (1061) +;Translated by johnny izzo (izzo@hot.ee) + +!insertmacro LANGFILE "Estonian" "Eesti keel" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "$(^NameDA) paigaldamine!" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "See abiline aitab paigaldada $(^NameDA).$\r$\n$\r$\nEnne paigaldamise alustamist on soovitatav kik teised programmid sulgeda, see vimaldab teatud ssteemifaile uuendada ilma arvutit taaskivitamata.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "$(^NameDA) eemaldamine!" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "See abiline aitab eemaldada $(^NameDA).$\r$\n$\r$\nEnne eemaldamist vaata, et $(^NameDA) oleks suletud.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Litsentsileping" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Enne $(^NameDA) paigaldamist vaata palun litsentsileping le." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Kui sa oled lepingu tingimustega nus, vali jtkamiseks Nustun. $(^NameDA) paigaldamiseks pead sa lepinguga nustuma." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Kui nustud lepingu tingimustega, vali allolev mrkeruut. $(^NameDA) paigaldamiseks pead lepinguga nustuma. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Kui nustud lepingu tingimustega, mrgi allpool esimene valik. $(^NameDA) paigaldamiseks pead lepinguga nustuma. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Litsentsileping" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Enne $(^NameDA) eemaldamist vaata palun litsentsileping le." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Kui sa oled lepingu tingimustega nus, vali jtkamiseks Nustun. $(^NameDA) eemaldamiseks pead sa lepinguga nustuma." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Kui nustud lepingu tingimustega, vali allolev mrkeruut. $(^NameDA) eemaldamiseks pead lepinguga nustuma. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Kui nustud lepingu tingimustega, mrgi allpool esimene valik. $(^NameDA) eemaldamiseks pead lepinguga nustuma. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Vajuta Page Down, et nha lejnud teksti." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Vali komponendid" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Vali millised $(^NameDA) osad sa soovid paigaldada." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Vali komponendid" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Vali millised $(^NameDA) osad sa soovid eemaldada." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Kirjeldus" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Nihuta hiir komponendile, et nha selle kirjeldust." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Nihuta hiir komponendile, et nha selle kirjeldust." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Vali asukoht" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Vali kaust kuhu paigaldada $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Vali asukoht" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Vali kaust kust $(^NameDA) eemaldada." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Paigaldan..." + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Palun oota kuni $(^NameDA) on paigaldatud." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Programm paigaldatud" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Paigaldus edukalt sooritatud." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Paigaldus katkestatud" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Paigaldamine ebannestus." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Eemaldan..." + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Palun oota kuni $(^NameDA) on eemaldatud." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Eemaldamine lpetatud" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Eemaldamine edukalt lpule viidud." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Eemaldamine katkestatud" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Eemaldamine ebanestus." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "$(^NameDA) paigalduse lpule viimine." + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) on sinu arvutisse paigaldatud.$\r$\n$\r$\nAbilise sulgemiseks vajuta Lpeta." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "$(^NameDA) tielikuks paigaldamiseks tuleb arvuti taaskivitada. Kas soovid arvuti kohe taaskivitada ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "$(^NameDA) eemaldamise lpule viimine." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) on sinu arvutist eemaldatud.$\r$\n$\r$\nAbilise sulgemiseks vajuta Lpeta." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "$(^NameDA) tielikuks eemaldamiseks tuleb arvuti taaskivitada. Kas soovid arvuti kohe taaskivitada ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Taaskivita kohe" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Taaskivitan hiljem ksitsi" + ${LangFileString} MUI_TEXT_FINISH_RUN "Kivita $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Kuva Loemind" + ${LangFileString} MUI_BUTTONTEXT_FINISH "Lpeta" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Vali Start-men kaust" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Vali $(^NameDA) otseteede jaoks Start-men kaust." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Vali Start-men kaust, kuhu soovid paigutada programmi otseteed. Vid ka sisestada nime, et luua uus kaust." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "ra loo otseteid" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Eemalda $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Eemalda $(^NameDA) oma arvutist." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Oled sa kindel et soovid $(^Name) paigaldamise katkestada?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Oled sa kindel et soovid $(^Name) eemaldamise katkestada?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Farsi.nlf b/installer/NSIS/Contrib/Language files/Farsi.nlf new file mode 100644 index 0000000..13a22c4 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Farsi.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1065 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1256 +# RTL - anything else than RTL means LTR +RTL +# Translation By FzerorubigD - FzerorubigD@gmail.com - Thanx to all people help me in forum.persiantools.com, Elnaz Sarbar +# ^Branding + %s +# ^SetupCaption + $(^Name) +# ^UninstallCaption + $(^Name) +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +& +# ^NextBtn +& +# ^AgreeBtn +& +# ^AcceptBtn + & +# ^DontAcceptBtn + & +# ^InstallBtn +& +# ^UninstallBtn +& +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +&... +# ^ShowDetailsBtn + +# ^ClickNext + Ϙ . +# ^ClickInstall + Ϙ . +# ^ClickUninstall + Ϙ . +# ^Name + +# ^Completed + +# ^LicenseText + $(^NameDA) . ǐ Ϙ . +# ^LicenseTextCB + $(^NameDA) . ǐ . $_CLICK +# ^LicenseTextRB + $(^NameDA) . ǐ . $_CLICK +# ^UnLicenseText + $(^NameDA) . ǐ Ϙ . +# ^UnLicenseTextCB + $(^NameDA) . ǐ . $_CLICK +# ^UnLicenseTextRB + $(^NameDA) . ǐ . $_CLICK +# ^Custom + +# ^ComponentsText + ԝ ԝ . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes +ԝ : +# ^ComponentsSubText2 +ǡ ԝ : +# ^UnComponentsText + ԝ ԝ . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes +ԝ : +# ^UnComponentsSubText2 +ǡ ԝ : +# ^DirText + ȡ $(^NameDA) . Ϙ . $_CLICK +# ^DirSubText + +# ^DirBrowseText + $(^NameDA): +# ^UnDirText + ȡ $(^NameDA) . Ϙ . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + $(^NameDA): +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText +(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\r\n$0\r\n\n Abort \r\n Retry \r\n ݝ Ignore . +# ^FileError_NoIgnore + : \r\n\r\n$0\r\n\n Retry\r\n Cancel . +# ^CantWrite +" : " +# ^CopyFailed + . +# ^CopyTo +" : " +# ^Registering +" : " +# ^Unregistering +" : " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +"ѐ : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +"ݝ : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ! ȝ ʿ +# ^ErrorRegistering + DLL +# ^ExecShell +" : " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode +ȝ : . +# ^NoOLE +"OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +" : " +# ^Skipped +" : " +# ^CopyDetails + 큝 +# ^LogInstall + +# ^Byte + +# ^Kilo + +# ^Mega + +# ^Giga + diff --git a/installer/NSIS/Contrib/Language files/Farsi.nsh b/installer/NSIS/Contrib/Language files/Farsi.nsh new file mode 100644 index 0000000..4c1ddf2 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Farsi.nsh @@ -0,0 +1,121 @@ +;Language: Farsi (1065) +;By FzerorubigD - FzerorubigD@gmail.com - Thanx to all people help me in forum.persiantools.com + +!insertmacro LANGFILE "Farsi" "Farsi" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA) ." + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA) ." + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n $(^NameDA) .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "ǐ . $(^NameDA) ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "ǐ . $(^NameDA) . $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "ǐ . $(^NameDA) . $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "ǐ . $(^NameDA) ." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "ǐ . $(^NameDA) . $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "ǐ . $(^NameDA) . $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " Page Down ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE " " + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " ." + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE " " + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " " +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA) " + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " $(^NameDA) . Ͽ" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA) " + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " $(^NameDA) . Ͽ" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " ." + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " ." + ${LangFileString} MUI_TEXT_FINISH_RUN "& $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "& " + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " $(^NameDA) Ȑ ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " $(^Name) Ͽ" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name) Ͽ" +!endif diff --git a/installer/NSIS/Contrib/Language files/Finnish.nlf b/installer/NSIS/Contrib/Language files/Finnish.nlf new file mode 100644 index 0000000..d6e558b --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Finnish.nlf @@ -0,0 +1,192 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1035 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Eclipser (Jonne Lehtinen) +# Corrections by the Mozilla.fi crew +# ^Branding +Nullsoftin asennusjrjestelm %s +# ^SetupCaption +$(^Name) Asennus +# ^UninstallCaption +$(^Name) Poisto +# ^LicenseSubCaption +: Lisenssisopimus +# ^ComponentsSubCaption +: Asennusvaihtoehdot +# ^DirSubCaption +: Asennuskansio +# ^InstallingSubCaption +: Asennetaan +# ^CompletedSubCaption +: Valmis +# ^UnComponentsSubCaption +: Poistovaihtoehdot +# ^UnDirSubCaption +: Poistokansio +# ^ConfirmSubCaption +: Varmistus +# ^UninstallingSubCaption +: Poistetaan +# ^UnCompletedSubCaption +: Valmis +# ^BackBtn +< &Takaisin +# ^NextBtn +&Seuraava > +# ^AgreeBtn +&Hyvksyn +# ^AcceptBtn +Hyvksyn lisenssisopimuksen ehdot +# ^DontAcceptBtn +En hyvksy sopimuksen ehtoja +# ^InstallBtn +&Asenna +# ^UninstallBtn +&Poista +# ^CancelBtn +Peruuta +# ^CloseBtn +&Sulje +# ^BrowseBtn +S&elaa... +# ^ShowDetailsBtn +&Nyt tiedot +# ^ClickNext +Valitse Seuraava jatkaaksesi. +# ^ClickInstall +Valitse Asenna aloittaaksesi asennuksen. +# ^ClickUninstall +Valitse Poista poistaaksesi asennuksen. +# ^Name +Nimi +# ^Completed +Valmis +# ^LicenseText +Lue lisenssisopimus ennen asentamista. Jos hyvksyt sopimuksen kaikki ehdot, valitse Hyvksyn. +# ^LicenseTextCB +Lue lisenssisopimus ennen asentamista. Jos hyvksyt sopimuksen kaikki ehdot, laita rasti ruutuun. $_CLICK +# ^LicenseTextRB +Lue lisenssisopimus ennen asentamista. Jos hyvksyt sopimuksen kaikki ehdot, valitse ensimminen vaihtoehto alapuolelta. $_CLICK +# ^UnLicenseText +Lue lisenssisopimus ennen poistamista. Jos hyvksyt sopimuksen kaikki ehdot, valitse Hyvksyn. +# ^UnLicenseTextCB +Lue lisenssisopimus ennen poistamista. Jos hyvksyt sopimuksen kaikki ehdot, laita rasti ruutuun. $_CLICK +# ^UnLicenseTextRB +Lue lisenssisopimus ennen poistamista. Jos hyvksyt sopimuksen kaikki ehdot, valitse ensimminen vaihtoehto alapuolelta. $_CLICK +# ^Custom +Oma +# ^ComponentsText +Valitse komponentit, jotka haluat asentaa, ja poista valinta komponenteista, joita et halua asentaa. $_CLICK +# ^ComponentsSubText1 +Valitse asennustyyppi: +# ^ComponentsSubText2_NoInstTypes +Valitse asennettavat komponentit: +# ^ComponentsSubText2 +Tai, valitse valinnaiset komponentit, jotka haluat asentaa: +# ^UnComponentsText +Valitse komponentit, jotka haluat poistaa, ja poista valinta komponenteista, joita et haluat poistaa. $_CLICK +# ^UnComponentsSubText1 +Valitse poistotyyppi: +# ^UnComponentsSubText2_NoInstTypes +Valitse poistettavat komponentit: +# ^UnComponentsSubText2 +Tai, valitse valinnaiset komponentit, jotka haluat poistaa +# ^DirText +Asennus asentaa ohjelman $(^NameDA) seuraavaan kansioon. Jos haluat asentaa sen johonkin muuhun kansioon, valitse Selaa, ja valitse toinen kansio. $_CLICK +# ^DirSubText +Kohdekansio +# ^DirBrowseText +Valitse kansio, johon haluat asentaa ohjelman $(^NameDA): +# ^UnDirText +Asennus poistaa ohjelman $(^NameDA) seuraavasta kansiosta. Jos haluat poistaa sen jostakin muusta kansiosta, valitse Selaa, ja valitse toinen kansio. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Valitse kansio, josta haluat poistaa ohjelman $(^NameDA): +# ^SpaceAvailable +"Tilaa vapaana: " +# ^SpaceRequired +"Tarvittava tila: " +# ^UninstallingText +Tm ohjelma poistaa ohjelman $(^NameDA) tietokoneelta. $_CLICK +# ^UninstallingSubText +Poistetaan kansiosta: +# ^FileError +Tiedostoon ei voitu kirjoittaa: \r\n\t"$0"\r\nLopeta asennus valitsemalla Hylk,\r\nyrit uudelleen valitsemalla Uudelleen, tai\r\nohita tiedosto valitsemalla Ohita +# ^FileError_NoIgnore +Tiedostoon ei voitu kirjoittaa: \r\n\t"$0"\r\nYrit uudelleen valitsemalla Uudelleen, tai\r\nlopeta asennus valitsemalla Hylk +# ^CantWrite +"Ei voi kirjoittaa: " +# ^CopyFailed +Kopiointi eponnistui +# ^CopyTo +"Kopioidaan kohteeseen " +# ^Registering +"Rekisteridn: " +# ^Unregistering +"Poistetaan rekisterinti: " +# ^SymbolNotFound +"Symbolia ei lytynyt: " +# ^CouldNotLoad +"Ei voitu ladata: " +# ^CreateFolder +"Luo kansio: " +# ^CreateShortcut +"Luo pikakuvake: " +# ^CreatedUninstaller +"Poisto-ohjelma luotiin: " +# ^Delete +"Poista: " +# ^DeleteOnReboot +"Poista kynnistyksen yhteydess: " +# ^ErrorCreatingShortcut +"Virhe luotaessa pikakuvaketta: " +# ^ErrorCreating +"Virhe luotaessa: " +# ^ErrorDecompressing +Pakettia ei voitu purkaa. Korruptoitunut asennusohjelma? +# ^ErrorRegistering +Virhe rekisteridess DLL-tiedostoa +# ^ExecShell +"ExecShell: " +# ^Exec +"Suorita: " +# ^Extract +"Pura: " +# ^ErrorWriting +"Pura: tiedostoon ei voitu kirjoittaa " +# ^InvalidOpcode +Asennuspaketti on vioittunut: virheellinen opcode +# ^NoOLE +"Ei OLEa: " +# ^OutputFolder +"Kansio: " +# ^RemoveFolder +"Poista kansio: " +# ^RenameOnReboot +"Muuta nimi uudelleenkynnistyksen yhteydess: " +# ^Rename +"Muuta nimi: " +# ^Skipped +"Ohitettiin: " +# ^CopyDetails +Kopioi tiedot leikepydlle +# ^LogInstall +Tallenna asennusloki +# ^Byte +t +# ^Kilo +k +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Finnish.nsh b/installer/NSIS/Contrib/Language files/Finnish.nsh new file mode 100644 index 0000000..1cfc4a9 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Finnish.nsh @@ -0,0 +1,123 @@ +;Compatible with Modern UI 1.86 +;Language: Finnish (1035) +;By Eclipser (Jonne Lehtinen) +;Updated by Puuhis (puuhis@puuhis.net) + +!insertmacro LANGFILE "Finnish" "Suomi" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Tervetuloa ohjelman $(^NameDA) asennukseen" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Tm avustaja ohjaa sinut ohjelman $(^NameDA) asennuksen lpi.$\r$\n$\r$\nOn suositeltavaa sulkea kaikki muut ohjelmat ennen asennuksen aloittamista, jotta asennus voisi pivitt tiettyj jrjestelmtiedostoja kynnistmtt konetta uudelleen.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Tervetuloa $(^NameDA) -ohjelmiston poisto-ohjelmaan" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Tm velho auttaa sinut lpi $(^NameDA) -ohjelmiston poistamisen.$\r$\n$\r$\nEnnen poisto-ohjelman aloitusta, varmista ettei $(^NameDA) ole kynniss.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lisenssisopimus" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Lue lisenssiehdot tarkasti ennen ohjelman $(^NameDA) asentamista." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Jos hyvksyt ehdot, valitse Hyvksyn jatkaaksesi. Sinun pit hyvksy ehdot asentaaksesi ohjelman $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jos hyvksyt ehdot, laita rasti alla olevaan ruutuun. Sinun pit hyvksy ehdot asentaaksesi ohjelman $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jos hyvksyt ehdot, valitse ensimminen vaihtoehto alapuolelta. Sinun pit hyvksy ehdot asentaaksesi ohjelman $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lisenssisopimus" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Lue huolellisesti lisenssiehdot ennen $(^NameDA) -ohjelmiston poistoa." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Jos hyvksyt snnt ja ehdot, paina Hyvksyn -nappia jatkaakseni. Sinun tytyy hyvksy ehdot poistaaksesi $(^NameDA) -ohjelmiston." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jos hyvksyt ehdot, klikkaa valintaruutua alhaalla. Sinun tytyy hyvksy ehdot poistaaksesi $(^NameDA) -ohjelmiston. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jos hyvksyt ehdot, valitse ensimminen vaihtoehto alhaalta. Sinun tytyy hyvksy ehdot poistaaksesi $(^NameDA) -ohjelmiston. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Paina Page Down nhdksesi loput sopimuksesta." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Valitse komponentit" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Valitse toiminnot, jotka haluat asentaa ohjelmaan $(^NameDA)." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Valitse komponentit" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Valitse $(^NameDA) toiminnot, jotka haluat poistaa." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Selitys" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Siirr hiiri komponentin nimen plle saadaksesi sen selityksen." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Siirr hiiri komponentin nimen plle saadaksesi sen selityksen." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Valitse asennuskohde" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Valitse hakemisto, johon haluat asentaa ohjelman $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Valitse paikka mist poistetaan" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Valitse kansio mist $(^NameDA) poistetaan." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Asennetaan" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Odota... $(^NameDA) asennetaan..." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Asennus valmis" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Asennus valmistui onnistuneesti." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Asennus keskeytettiin" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Asennus ei onnistunut." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Poistetaan" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Odota... Ohjelmaa $(^NameDA) poistetaan." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Poisto valmis" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Ohjelma poistettiin onnistuneesti." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Poisto lopetettu" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Ohjelmaa poisto eponnistuneesti." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Viimeistelln ohjelman $(^NameDA) asennusta" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) on asennettu koneellesi.$\r$\n$\r$\nValitse Valmis sulkeaksesi avustajan." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Tietokoneesi pit kynnist uudelleen jotta ohjelman $(^NameDA) asennus saataisiin valmiiksi. Haluatko kynnist koneen uudelleen nyt?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Viimeistelln $(^NameDA) -ohjelmiston poistamista" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) on poistettu koneeltasi.$\r$\n$\r$\nPaina Lopeta -nappia sulkeaksesi tmn velhon." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Jotta $(^NameDA) -ohjelmiston poistaminen olisi valmis, tulee tietokone kynnist uudelleen. Haluatko uudelleenkynnist nyt?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Kynnist uudelleen nyt" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Kynnistn koneen myhemmin uudelleen" + ${LangFileString} MUI_TEXT_FINISH_RUN "Kynnist $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Nyt LueMinut" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Valmis" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Valitse Kynnist-valikon hakemisto" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Valitse Kynnist-valikon hakemisto ohjelman pikakuvakkeille." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Valitse Kynnist-valikon hakemisto, johon haluaisit luoda ohjelman pikakuvakkeet. Voit mys kirjoittaa uuden nimen." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "l luo pikakuvakkeita" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Poista $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Poista $(^NameDA) tietokoneestasi." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Haluatko varmasti lopettaa $(^Name) Asennuksen?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Oletko varma ett haluat poistua $(^Name) poisto-ohjelmasta?" +!endif diff --git a/installer/NSIS/Contrib/Language files/French.nlf b/installer/NSIS/Contrib/Language files/French.nlf new file mode 100644 index 0000000..1a3b822 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/French.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +1036 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by the French NSIS team - http://www.winampfr.com/nsis. +# Updated to v6 by Jerome Charaoui (lavamind@inetflex.com) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Installation de $(^Name) +# ^UninstallCaption +Dsinstallation de $(^Name) +# ^LicenseSubCaption +: Licence +# ^ComponentsSubCaption +: Options d'installation +# ^DirSubCaption +: Dossier d'installation +# ^InstallingSubCaption +: Installation des fichiers +# ^CompletedSubCaption +: Termin +# ^UnComponentsSubCaption +: Options de dsinstallation +# ^UnDirSubCaption +: Dossier de dsinstallation +# ^ConfirmSubCaption +: Confirmation +# ^UninstallingSubCaption +: Dsinstallation des fichiers +# ^UnCompletedSubCaption +: Termin +# ^BackBtn +< &Prcdent +# ^NextBtn +&Suivant > +# ^AgreeBtn +J'a&ccepte +# ^AcceptBtn +J'a&ccepte les termes de la licence +# ^DontAcceptBtn +Je &n'accepte pas les termes de la licence +# ^InstallBtn +&Installer +# ^UninstallBtn +&Dsinstaller +# ^CancelBtn +Annuler +# ^CloseBtn +&Fermer +# ^BrowseBtn +P&arcourir... +# ^ShowDetailsBtn +P&lus d'infos +# ^ClickNext +Cliquez sur Suivant pour continuer. +# ^ClickInstall +Cliquez sur Installer pour dmarrer l'installation. +# ^ClickUninstall +Cliquez sur Dsinstaller pour dmarrer la dsinstallation. +# ^Name +Nom +# ^Completed +Termin +# ^LicenseText +Veuillez examiner le contrat de licence avant d'installer $(^NameDA). Si vous acceptez tous les termes du contrat, cliquez sur J'accepte. +# ^LicenseTextCB +Veuillez examiner le contrat de licence avant d'installer $(^NameDA). Si vous acceptez tous les termes du contrat, cochez la bote de contrle ci-dessous. $_CLICK +# ^LicesnseTextRB +Veuillez examiner le contrat de licence avant d'installer $(^NameDA). Si vous acceptez tous les termes du contrat, slectionnez la premire option ci-dessous. $_CLICK +# ^UnLicenseText +Veuillez examiner le contrat de licence avant de dsinstaller $(^NameDA). Si vous acceptez tous les termes du contrat, cliquez sur J'accepte. +# ^UnLicenseTextCB +Veuillez examiner le contrat de licence avant de dsinstaller $(^NameDA). Si vous acceptez tous les termes du contrat, cochez la bote de contrle ci-dessous. $_CLICK +# ^UnLicesnseTextRB +Veuillez examiner le contrat de licence avant de dsinstaller $(^NameDA). Si vous acceptez tous les termes du contrat, slectionnez la premire option ci-dessous. $_CLICK +# ^Custom +Personnalise +# ^ComponentsText +Cochez les composants que vous dsirez installer et dcochez ceux que vous ne dsirez pas installer. $_CLICK +# ^ComponentsSubText1 +Type d'installation : +# ^ComponentsSubText2_NoInstTypes +Slectionnez les composants installer : +# ^ComponentsSubText2 +Ou, slectionnez les composants optionnels que vous voulez installer : +# ^UnComponentsText +Cochez les composants que vous dsirez dsinstaller et dcochez ceux que vous ne dsirez pas dsinstaller. $_CLICK +# ^UnComponentsSubText1 +Slectionnez le type de dsinstallation : +# ^UnComponentsSubText2_NoInstTypes +Slectionnez les composants dsinstaller : +# ^UnComponentsSubText2 +Ou, slectionnez les composants optionnels que vous voulez dsinstaller : +# ^DirText +Ceci installera $(^NameDA) dans le dossier suivant. Pour installer dans un autre dossier, cliquez sur Parcourir et choisissez un autre dossier. $_CLICK +# ^DirSubText +Dossier d'installation +# ^DirBrowseText +Slectionnez le dossier d'installation pour $(^NameDA) : +# ^UnDirText +Ceci dsinstallera $(^NameDA) du dossier suivant. Pour dsinstaller d'un autre dossier, cliquez sur Parcourir et choisissez un autre dossier. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Slectionnez le dossier de dsinstallation pour $(^NameDA) : +# ^SpaceAvailable +"Espace disponible : " +# ^SpaceRequired +"Espace requis : " +# ^UninstallingText +Ce programme dsinstallera $(^NameDA) de votre ordinateur. $_CLICK +# ^UninstallingSubText +Dsinstallation partir de : +# ^FileError +Erreur lors de l'ouverture du fichier en criture : \r\n\t"$0"\r\nAppuyez sur Abandonner pour annuler l'installation,\r\nRessayer pour ressayer l'criture du fichier, ou\r\nIgnorer pour passer ce fichier +# ^FileError_NoIgnore +Erreur lors de l'ouverture du fichier en criture : \r\n\t"$0"\r\nAppuyez sur Ressayez pour re-crire le fichier, ou\r\nAnnuler pour abandonner l'installation +# ^CantWrite +"Impossible d'crire : " +# ^CopyFailed +chec de la copie +# ^CopyTo +"Copier vers " +# ^Registering +"Enregistrement : " +# ^Unregistering +"Suppression de l'enregistrement : " +# ^SymbolNotFound +"Impossible de trouver un symbole : " +# ^CouldNotLoad +"Impossible de charger : " +# ^CreateFolder +"Cration du dossier : " +# ^CreateShortcut +"Cration du raccourci : " +# ^CreatedUninstaller +"Cration de la dsinstallation : " +# ^Delete +"Suppression : " +# ^DeleteOnReboot +"Suppression au redmarrage : " +# ^ErrorCreatingShortcut +"Erreur lors de la cration du raccourci : " +# ^ErrorCreating +"Erreur de la cration : " +# ^ErrorDecompressing +Erreur lors de la dcompression des donnes ! Installation corrompue ? +# ^ErrorRegistering +Erreur lors de l'enregistrement de la DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Excution : " +# ^Extract +"Extraction : " +# ^ErrorWriting +"Extraction : erreur d'criture du fichier " +# ^InvalidOpcode +Installation corrompue : opcode incorrect +# ^NoOLE +"Pas de OLE pour : " +# ^OutputFolder +"Destination : " +# ^RemoveFolder +"Suppression du dossier : " +# ^RenameOnReboot +"Renommer au redmarrage : " +# ^Rename +"Renommer : " +# ^Skipped +"Pass : " +# ^CopyDetails +Copier les Dtails dans le Presse-papier +# ^LogInstall +Enregistrer le droulement de l'installation +# ^Byte +o +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/French.nsh b/installer/NSIS/Contrib/Language files/French.nsh new file mode 100644 index 0000000..1275d72 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/French.nsh @@ -0,0 +1,129 @@ +;Language: French (1036) +;By Sbastien Delahaye + +!insertmacro LANGFILE "French" "French" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Bienvenue dans le programme d'installation de $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Vous tes sur le point d'installer $(^NameDA) sur votre ordinateur.$\r$\n$\r$\nAvant de dmarrer l'installation, il est recommand de fermer toutes les autres applications. Cela permettra la mise jour de certains fichiers systme sans redmarrer votre ordinateur.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Bienvenue dans le programme de dsinstallation de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Vous tes sur le point de dsinstaller $(^NameDA) de votre ordinateur.$\r$\n$\r$\nAvant d'amorcer la dsinstallation, assurez-vous que $(^NameDA) ne soit pas en cours d'excution.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licence utilisateur" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Veuillez examiner les termes de la licence avant d'installer $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Si vous acceptez les conditions de la licence utilisateur, cliquez sur J'accepte pour continuer. Vous devez accepter la licence utilisateur afin d'installer $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si vous acceptez les conditions de la licence utilisateur, cochez la case ci-dessous. Vous devez accepter la licence utilisateur afin d'installer $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si vous acceptez les conditions de la licence utilisateur, slectionnez le premier choix ci-dessous. Vous devez accepter la licence utilisateur afin d'installer $(^NameDA)." +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licence utilisateur" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Veuillez examiner les conditions de la licence avant de dsinstaller $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Si vous acceptez les conditions de la licence utilisateur, cliquez sur J'accepte pour continuer. Vous devez accepter la licence utilisateur afin de dsinstaller $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si vous acceptez les conditions de la licence utilisateur, cochez la case ci-dessous. Vous devez accepter la licence utilisateur afin de dsintaller $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si vous acceptez les conditions de la licence utilisateur, slectionnez le premier choix ci-dessous. Vous devez accepter la licence utilisateur afin de dsinstaller $(^NameDA)." +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Appuyez sur Page Suivante pour lire le reste de la licence utilisateur." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Choisissez les composants" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Choisissez les composants de $(^NameDA) que vous souhaitez installer." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Choisissez les composants" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Cochez les composants de $(^NameDA) que vous souhaitez dsinstaller." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Description" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Passez le curseur de votre souris sur un composant pour en voir la description." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Passez le curseur de votre souris sur un composant pour en voir la description." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Choisissez le dossier d'installation" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Choisissez le dossier dans lequel installer $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Choisissez le dossier de dsinstallation" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Choisissez le dossier partir duquel vous voulez dsinstaller $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installation en cours" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Veuillez patienter pendant que $(^NameDA) est en train d'tre install." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installation termine" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "L'installation s'est termine avec succs." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installation interrompue" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "L'installation n'a pas t termine." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Dsinstallation en cours" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Veuillez patienter pendant que $(^NameDA) est en train d'tre supprim de votre ordinateur." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Dsinstallation termine" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "La dsinstallation s'est termine avec succs." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Dsinstallation interrompue" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "La dsinstallation n'a pas t termine." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Fin de l'installation de $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) a t install sur votre ordinateur.$\r$\n$\r$\nCliquez sur Fermer pour quitter le programme d'installation." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Votre ordinateur doit tre redmarr afin de complter l'installation de $(^NameDA). Souhaitez-vous redmarrer maintenant ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Fin de la dsinstallation de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) a t supprim de votre ordinateur.$\r$\n$\r$\nCliquez sur Fermer pour quitter le programme d'installation." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Votre ordinateur doit tre redmarr pour terminer la dsinstallation de $(^NameDA). Souhaitez-vous redmarrer maintenant ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Redmarrer maintenant" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Je souhaite redmarrer moi-mme plus tard" + ${LangFileString} MUI_TEXT_FINISH_RUN "Lancer $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Afficher le fichier Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Fermer" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Choisissez un dossier dans le menu Dmarrer" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Choisissez un dossier dans le menu Dmarrer pour les raccourcis de l'application." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Choisissez le dossier du menu Dmarrer dans lequel vous voulez placer les raccourcis du programme. Vous pouvez galement entrer un nouveau nom pour crer un nouveau dossier." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ne pas crer de raccourcis" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Dsinstaller $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Supprimer $(^NameDA) de votre ordinateur." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "tes-vous sr de vouloir quitter l'installation de $(^Name) ?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "tes-vous sr de vouloir quitter la dsinstallation de $(^Name) ?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Choix des utilisateurs" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Slection des utilisateurs dsirant utiliser $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Choix entre installer $(^NameDA) seulement pour vous-mme ou bien pour tous les utilisateurs du systme. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Installer pour tous les utilisateurs" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Installer seulement pour moi" +!endif diff --git a/installer/NSIS/Contrib/Language files/Galician.nlf b/installer/NSIS/Contrib/Language files/Galician.nlf new file mode 100644 index 0000000..f1e405e --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Galician.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1110 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation v 1.0.0 by Ramon Flores +# ^Branding +Sistema de Instalacin Nullsoft %s +# ^SetupCaption +Instalacin de $(^Name) +# ^UninstallCaption +Desinstalacin de $(^Name) +# ^LicenseSubCaption +: Contrato de licenza +# ^ComponentsSubCaption +: Opcins de instalacin +# ^DirSubCaption +: Diretria de instalacin +# ^InstallingSubCaption +: Instalando ficheiros +# ^CompletedSubCaption +: Concludo +# ^UnComponentsSubCaption +: Opcins de desinstalacin +# ^UnDirSubCaption +: Cartafol de desinstalacin +# ^ConfirmSubCaption +: Confirmacin +# ^UninstallingSubCaption +: Desinstalando +# ^UnCompletedSubCaption +: Concludo +# ^BackBtn +< &Anterior +# ^NextBtn +&Seguinte > +# ^AgreeBtn +&Aceito +# ^AcceptBtn +Eu &aceito os termos do Contrato de licenza +# ^DontAcceptBtn +Eu &non aceito os termos do Contrato de licenza +# ^InstallBtn +&Instalar +# ^UninstallBtn +&Desinstalar +# ^CancelBtn +Cancelar +# ^CloseBtn +&Fechar +# ^BrowseBtn +&Procurar... +# ^ShowDetailsBtn +Ver &Detalles +# ^ClickNext +Clique en 'Seguinte' para continuar. +# ^ClickInstall +Clique en 'Instalar' para iniciar a instalacin. +# ^ClickUninstall +Clique en 'Desinstalar' para iniciar a desinstalacin. +# ^Name +Nome +# ^Completed +Concludo +# ^LicenseText +Por favor revexa o acordo de licenza antes de instalar $(^NameDA). Se concordar con todos os termos da licenza, clique em 'Aceito'. +# ^LicenseTextCB +Por favor reveja o acordo de licenza antes de instalar $(^NameDA). Se concordar con todos os termos da licenza, clique na caixa de seleccin abaixo. $_CLICK +# ^LicenseTextRB +Por favor revexa o acordo de licenza antes de instalar $(^NameDA). Se concordar con todos os termos da licenza, escolla a primeira opcin abaixo. $_CLICK +# ^UnLicenseText +Por favor revexa o acordo de licenza antes de desinstalar $(^NameDA). Se concordar con todos os termos da licenza, clique em 'Aceito'. +# ^UnLicenseTextCB +Por favor reveja o acordo de licenza antes de desinstalar $(^NameDA). Se concordar con todos os termos da licenza, clique na caixa de seleccin abaixo. $_CLICK +# ^UnLicenseTextRB +Por favor revexa o acordo de licenza antes de desinstalar $(^NameDA). Se concordar con todos os termos da licenza, escolla a primeira opcin abaixo. $_CLICK +# ^Custom +Personalizado +# ^ComponentsText +Marque os componentes que desexa instalar e desmarque os componentes que non desexa instalar. $_CLICK +# ^ComponentsSubText1 +Escolla o tipo de instalacin: +# ^ComponentsSubText2_NoInstTypes +Escolla os componentes para instalar: +# ^ComponentsSubText2 +Ou, escolla os componentes opcionais que desexa instalar: +# ^UnComponentsText +Marque os componentes que queira desinstalar e vice versa. $_CLICK +# ^UnComponentsSubText1 +Escolla o tipo de desinstalacin: +# ^UnComponentsSubText2_NoInstTypes +Escolla os componentes para desinstalar: +# ^UnComponentsSubText2 +Ou, escolla os componentes opcionais que queira desinstalar: +# ^DirText +O $(^NameDA) ser instalado na seguinte directria. Para instalar nunha directria diferente, clique en 'Procurar...' e escolla outra directria. $_CLICK +# ^DirSubText +Directria de destino +# ^DirBrowseText +Escolla unha directria para instalar o $(^NameDA): +# ^UnDirText +O $(^NameDA) ser desinstalado da seguinte directria. Para desinstalar dunha pasta diferente, clique en 'Procurar...' e escolla outra directria. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Escolla a directria de onde vai ser desinstalado o $(^NameDA): +# ^SpaceAvailable +"Espazo disponbel: " +# ^SpaceRequired +"Espazo necesrio: " +# ^UninstallingText +$(^NameDA) vai ser desinstalado da seguinte directria. $_CLICK +# ^UninstallingSubText +Desinstalando de: +# ^FileError +Erro ao abrir ficheiro para escrita: \r\n\t"$0"\r\nClique en Abortar para abortar a instalacin,\r\nRepetir para tentar novamente a escrita do ficheiro, ou\r\nIgnorar para ignorar este ficheiro. +# ^FileError_NoIgnore +Erro ao abrir ficheiro para escrita: \r\n\t"$0"\r\nClique en Repetir para tentar novamente a gravacin do ficheiro, ou\r\nCancelar para abortar a instalacin. +# ^CantWrite +"Non foi posbel escreber: " +# ^CopyFailed +Falla ao copiar +# ^CopyTo +"Copiar para " +# ^Registering +"Rexistando: " +# ^Unregistering +"Desrexistando: " +# ^SymbolNotFound +"Smbolo non achado: " +# ^CouldNotLoad +"Non foi posbel carregar: " +# ^CreateFolder +"Criando diretria: " +# ^CreateShortcut +"Criando atallo: " +# ^CreatedUninstaller +"Criando desinstalador: " +# ^Delete +"Eliminando ficheiro: " +# ^DeleteOnReboot +"Eliminar ao reiniciar: " +# ^ErrorCreatingShortcut +"Erro ao criar atallo: " +# ^ErrorCreating +"Erro ao criar: " +# ^ErrorDecompressing +Erro ao descomprimir dados! Instalador corrompido? +# ^ErrorRegistering +Erro ao rexistar DLL +# ^ExecShell +"Executando polo Shell: " +# ^Exec +"Executando: " +# ^Extract +"Extraindo: " +# ^ErrorWriting +"Extraindo: erro ao escreber ficheiro " +# ^InvalidOpcode +Instalador corrompido: cdigo de operacin invlido +# ^NoOLE +"Sen OLE para: " +# ^OutputFolder +"Cartafol de destino: " +# ^RemoveFolder +"Removendo cartafol: " +# ^RenameOnReboot +"Renomear ao reiniciar: " +# ^Rename +"Renomeando: " +# ^Skipped +"Ignorado: " +# ^CopyDetails +Copiar detalles para a rea de transfrencia +# ^LogInstall +Rexistar proceso de instalacin +# ^Byte +B +# kilo +K +# mega +M +# giga +G diff --git a/installer/NSIS/Contrib/Language files/Galician.nsh b/installer/NSIS/Contrib/Language files/Galician.nsh new file mode 100644 index 0000000..772c986 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Galician.nsh @@ -0,0 +1,121 @@ +;Language: Galician (1110) +;Ramon Flores + +!insertmacro LANGFILE "Galician" "Galego" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Benvindo ao Asistente de Instalacin do $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Este asistente vai axud-lo durante a instalacin do $(^NameDA).$\r$\n$\r$\nRecomenda-se fechar todas as outras aplicacins antes de iniciar a instalacin. Isto posibilita actualizar os ficheiros do sistema relevantes sen ter que reiniciar o computador.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Benvindo ao Asistente de desinstalacin do $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Este asistente vai axud-lo durante a desinstalacin do $(^NameDA).$\r$\n$\r$\nAntes de iniciar a desinstalacin, certifique-se de que o $(^NameDA) non est a executar-se.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Contrato de licenza" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Por favor, verifique os termos da licenza antes de instalar o $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Se aceitar os termos da licenza, clique en 'Aceito' para continuar. Cumpre aceitar o contrato para instalar o $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se aceitar os termos da licenza, clique na caixa de seleccin abaixo. Cumpre aceitar o contrato para instalar o $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se aceitar os termos da licenza, seleccione a primeira opcin abaixo. Cumpre aceitar o contrato para instalar o $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Contrato de licenza" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Por favor, verifique os termos da licenza antes de desinstalar o $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Se aceitar os termos da licenza, clique en 'Aceito' para continuar. Cumpre aceitar o contrato para desinstalar o $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se aceitar os termos da licenza, clique na caixa de seleccin abaixo. Cumpre aceitar o contrato para desinstalar o $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se aceitar os termos da licenza, seleccione a primeira opcin abaixo. Cumpre aceitar o contrato para desinstalar o $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Prema Page Down para ver o restante da licenza." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Escolla de componentes" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Escolla que caractersticas do $(^NameDA) que desexa instalar." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Escoller componentes" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Escolla que caractersticas do $(^NameDA) desexa desinstalar." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descricin" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posicione o rato sobre un componente para ver a sua descricin." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posicione o rato sobre un componente para ver a sua descricin." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Escolla do local da instalacin" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Escolla a directria na cal desexa instalar o $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Escolla o Local de desinstalacin" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Escolla a directria de onde pretende desinstalar o $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalando" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Por favor, agarde entanto o $(^NameDA) est sendo instalado." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalacin completa" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "A instalacin concluiu con suceso." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalacin Abortada" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "A instalacin concluiu sen suceso." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Desinstalando" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Por favor, agarde entanto se desinstala o $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Desinstalacin completa" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "A desinstalacin concluiu con suceso." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Desinstalacin abortada" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "A desinstalacin non concluiu con suceso" +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Concluindo o Asistente de instalacin do $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Instalou-se o $(^NameDA) no seu computador.$\r$\n$\r$\nClique en Rematar para fechar este asistente." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Cumpre reiniciar o seu computador para conclur a instalacin do $(^NameDA). Desexa reiniciar agora?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Conclundo o asistente de desinstalacin do $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Eliminou-se $(^NameDA) do seu computador.$\r$\n$\r$\nClique em Rematar para fechar este asistente." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Cumpre reiniciar o seu computador para conclur a desinstalacin do $(^NameDA). Desexa reinici-lo agora?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reiniciar agora" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Prefiro reinicia-lo manualmente despois" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Executar $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Mostrar Leame" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Rematar" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Escolla un cartafol do Menu Iniciar" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Escolla un cartafol do Menu Iniciar para os atallos do programa." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Seleccione o cartafol do Menu Iniciar no que desexa criar os atallos do programa. Tamn posbel dixitar un nome para criar un novo cartafol. " + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Non criar atallos" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Desinstalar $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Eliminar o $(^NameDA) do seu computador." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Realmente desexa cancelar a instalacin do $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Realmente desexa cancelar a desinstalacin do $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/German.nlf b/installer/NSIS/Contrib/Language files/German.nlf new file mode 100644 index 0000000..0b2464d --- /dev/null +++ b/installer/NSIS/Contrib/Language files/German.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1031 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by L.King, changes by R. Bisswanger, Tim Kosse +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Installation +# ^UninstallCaption +$(^Name) Deinstallation +# ^LicenseSubCaption +: Lizenzabkommen +# ^ComponentsSubCaption +: Installations-Optionen +# ^DirSubCaption +: Zielverzeichnis +# ^InstallingSubCaption +: Installiere +# ^CompletedSubCaption +: Fertig +# ^UnComponentsSubCaption +: Deinstallations-Optionen +# ^UnDirSubCaption +: Quellverzeichnis +# ^ConfirmSubCaption +: Besttigung +# ^UninstallingSubCaption +: Entferne +# ^UnCompletedSubCaption +: Fertig +# ^BackBtn +< &Zurck +# ^NextBtn +&Weiter > +# ^AgreeBtn +&Annehmen +# ^AcceptBtn +Ich &akzeptiere das Lizenzabkommen. +# ^DontAcceptBtn +Ich &lehne das Lizenzabkommen ab. +# ^InstallBtn +&Installieren +# ^UninstallBtn +&Deinstallieren +# ^CancelBtn +Abbrechen +# ^CloseBtn +&Beenden +# ^BrowseBtn +&Durchsuchen... +# ^ShowDetailsBtn +&Details anzeigen +# ^ClickNext +Klicken Sie auf Weiter, um fortzufahren. +# ^ClickInstall +Klicken Sie auf Installieren, um die Installation zu starten. +# ^ClickUninstall +Klicken Sie auf Deinstallieren, um die Deinstallation zu starten. +# ^Name +Name +# ^Completed +Fertig +# ^LicenseText +Bitte lesen Sie das Lizenzabkommen, bevor Sie $(^NameDA) installieren. Wenn Sie alle Bedingungen des Abkommens akzeptieren, klicken Sie auf Annehmen. +# ^LicenseTextCB +Bitte lesen Sie das Lizenzabkommen, bevor Sie $(^NameDA) installieren. Wenn Sie alle Bedingungen des Abkommens akzeptieren, aktivieren Sie das Kontrollkstchen. $_CLICK +# ^LicenseTextRB +Bitte lesen Sie das Lizenzabkommen, bevor Sie $(^NameDA) installieren. Wenn Sie alle Bedingungen des Abkommens akzeptieren, whlen Sie die entsprechende Option. $_CLICK +# ^UnLicenseText +Bitte lesen Sie das Lizenzabkommen, bevor Sie $(^NameDA) entfernen. Wenn Sie alle Bedingungen des Abkommens akzeptieren, klicken Sie auf Annehmen. +# ^UnLicenseTextCB +Bitte lesen Sie das Lizenzabkommen, bevor Sie $(^NameDA) entfernen. Wenn Sie alle Bedingungen des Abkommens akzeptieren, aktivieren Sie das Kontrollkstchen. $_CLICK +# ^UnLicenseTextRB +Bitte lesen Sie das Lizenzabkommen, bevor Sie $(^NameDA) entfernen. Wenn Sie alle Bedingungen des Abkommens akzeptieren, whlen Sie die entsprechende Option. $_CLICK +# ^Custom +Benutzerdefiniert +# ^ComponentsText +Whlen Sie die Komponenten aus, die Sie installieren mchten und whlen Sie diejenigen ab, die Sie nicht installieren wollen. $_CLICK +# ^ComponentsSubText1 +Installations-Typ bestimmen: +# ^ComponentsSubText2_NoInstTypes +Whlen Sie die Komponenten aus, die Sie installieren mchten: +# ^ComponentsSubText2 +oder whlen Sie zustzliche Komponenten aus, die Sie installieren mchten: +# ^UnComponentsText +Whlen Sie die Komponenten aus, die Sie entfernen mchten und whlen Sie diejenigen ab, die Sie nicht entfernen wollen. $_CLICK +# ^UnComponentsSubText1 +Deinstallations-Typ bestimmen: +# ^UnComponentsSubText2_NoInstTypes +Whlen Sie die Komponenten aus, die Sie entfernen mchten: +# ^UnComponentsSubText2 +oder whlen Sie zustzliche Komponenten aus, die Sie entfernen mchten: +# ^DirText +$(^NameDA) wird in das unten angegebene Verzeichnis installiert. Falls Sie in ein anderes Verzeichnis installieren mchten, klicken Sie auf Durchsuchen und whlen Sie ein anderes Verzeichnis aus. $_CLICK +# ^DirSubText +Zielverzeichnis +# ^DirBrowseText +Whlen Sie das Verzeichnis aus, in das Sie $(^NameDA) installieren mchten: +# ^UnDirText +$(^NameDA) wird aus dem unten angegebenen Verzeichnis entfernt. Falls sich $(^NameDA) in einem anderen Verzeichnis befindet, klicken Sie auf Durchsuchen und whlen Sie das richtige Verzeichnis aus. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Whlen Sie das Verzeichnis aus, in dem sich $(^NameDA) befindet: +# ^SpaceAvailable +"Verfgbarer Speicher: " +# ^SpaceRequired +"Bentigter Speicher: " +# ^UninstallingText +$(^NameDA) wird aus dem unten angegebenen Verzeichnis entfernt. $_CLICK +# ^UninstallingSubText +Entferne aus: +# ^FileError +Fehler beim berschreiben der Datei: \r\n\t"$0"\r\nKlicken Sie auf Abbrechen, um abzubrechen,\r\nauf Wiederholen, um den Schreibvorgang erneut zu versuchen\r\noder auf Ignorieren, um diese Datei zu berspringen. +# ^FileError_NoIgnore +Fehler beim berschreiben der Datei: \r\n\t"$0"\r\nKlicken Sie auf Wiederholen, um den Schreibvorgang erneut zu versuchen\r\noder auf Abbrechen, um die Installation zu beenden. +# ^CantWrite +"Fehler beim Schreiben: " +# ^CopyFailed +Kopieren fehlgeschlagen +# ^CopyTo +"Kopiere nach " +# ^Registering +"Registriere: " +# ^Unregistering +"Deregistriere: " +# ^SymbolNotFound +"Symbol ist nicht vorhanden: " +# ^CouldNotLoad +"Fehler beim Laden von " +# ^CreateFolder +"Erstelle Verzeichnis: " +# ^CreateShortcut +"Erstelle Verknpfung: " +# ^CreatedUninstaller +"Erstelle Deinstallations-Programm: " +# ^Delete +"Lsche Datei: " +# ^DeleteOnReboot +"Lsche Datei nach Neustart: " +# ^ErrorCreatingShortcut +"Fehler beim Erstellen der Verknpfung: " +# ^ErrorCreating +"Fehler beim Erstellen: " +# ^ErrorDecompressing +Fehler beim Dekomprimieren. Beschdigtes Installations-Programm? +# ^ErrorRegistering +Fehler beim Registrieren der DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Starte: " +# ^Extract +"Dekomprimiere: " +# ^ErrorWriting +"Dekomprimierung: Fehler beim Schreiben der Datei " +# ^InvalidOpcode +Beschdigtes Installations-Programm: ungltiger Befehlscode +# ^NoOLE +"Kein OLE fr: " +# ^OutputFolder +"Zielverzeichnis: " +# ^RemoveFolder +"Entferne Verzeichnis: " +# ^RenameOnReboot +"Umbenennen nach Neustart: " +# ^Rename +"Umbenennen: " +# ^Skipped +"bersprungen: " +# ^CopyDetails +Details in die Zwischenablage kopieren +# ^LogInstall +Installationsverlauf protokollieren +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/German.nsh b/installer/NSIS/Contrib/Language files/German.nsh new file mode 100644 index 0000000..a7a39c7 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/German.nsh @@ -0,0 +1,129 @@ +;Language: German (1031) +;By L.King, changes by K. Windszus & R. Bisswanger & M. Simmack & D. Wei + +!insertmacro LANGFILE "German" "Deutsch" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Willkommen beim Installations-$\r$\nAssistenten fr $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Dieser Assistent wird Sie durch die Installation von $(^NameDA) begleiten.$\r$\n$\r$\nEs wird empfohlen, vor der Installation alle anderen Programme zu schlieen, damit bestimmte Systemdateien ohne Neustart ersetzt werden knnen.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Willkommen beim Deinstallations-$\r$\nAssistenten fr $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Dieser Assistent wird Sie durch die Deinstallation von $(^NameDA) begleiten.$\r$\n$\r$\nBitte beenden Sie $(^NameDA), bevor Sie mit der Deinstallation fortfahren.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lizenzabkommen" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Bitte lesen Sie die Lizenzbedingungen durch, bevor Sie mit der Installation fortfahren." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Falls Sie alle Bedingungen des Abkommens akzeptieren, klicken Sie auf Annehmen. Sie mssen die Lizenzvereinbarungen anerkennen, um $(^NameDA) installieren zu knnen." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Falls Sie alle Bedingungen des Abkommens akzeptieren, aktivieren Sie das Kstchen. Sie mssen die Lizenzvereinbarungen anerkennen, um $(^NameDA) installieren zu knnen. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Falls Sie alle Bedingungen des Abkommens akzeptieren, whlen Sie unten die entsprechende Option. Sie mssen die Lizenzvereinbarungen anerkennen, um $(^NameDA) installieren zu knnen. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lizenzabkommen" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Bitte lesen Sie die Lizenzbedingungen durch, bevor Sie mit der Deinstallation von $(^NameDA) fortfahren." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Falls Sie alle Bedingungen des Abkommens akzeptieren, klicken Sie auf Annehmen. Sie mssen die Lizenzvereinbarungen anerkennen, um $(^NameDA) deinstallieren zu knnen." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Falls Sie alle Bedingungen des Abkommens akzeptieren, aktivieren Sie das Kstchen. Sie mssen die Lizenzvereinbarungen anerkennen, um $(^NameDA) deinstallieren zu knnen. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Falls Sie alle Bedingungen des Abkommens akzeptieren, whlen Sie unten die entsprechende Option. Sie mssen die Lizenzvereinbarungen anerkennen, um $(^NameDA) deinstallieren zu knnen. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Drcken Sie die Bild-nach-unten Taste, um den Rest des Abkommens zu sehen." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Komponenten auswhlen" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Whlen Sie die Komponenten aus, die Sie installieren mchten." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Komponenten auswhlen" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Whlen Sie die Komponenten aus, die Sie entfernen mchten." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beschreibung" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Bewegen Sie den Mauszeiger ber eine Komponente, um ihre Beschreibung zu sehen." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Bewegen Sie den Mauszeiger ber eine Komponente, um ihre Beschreibung zu sehen." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Zielverzeichnis auswhlen" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Whlen Sie das Verzeichnis aus, in das $(^NameDA) installiert werden soll." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Verzeichnis fr Deinstallation auswhlen" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Whlen Sie das Verzeichnis aus, aus dem $(^NameDA) entfernt werden soll." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installiere..." + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Bitte warten Sie, whrend $(^NameDA) installiert wird." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Die Installation ist vollstndig" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Die Installation wurde erfolgreich abgeschlossen." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Abbruch der Installation" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Die Installation wurde nicht vollstndig abgeschlossen." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Deinstalliere..." + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Bitte warten Sie, whrend $(^NameDA) entfernt wird." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Die Deinstallation ist vollstndig" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Die Deinstallation wurde erfolgreich abgeschlossen." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Abbruch der Deinstallation" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Die Deinstallation wurde nicht vollstndig abgeschlossen." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Die Installation von $(^NameDA) wird abgeschlossen" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) wurde auf Ihrem Computer installiert.$\r$\n$\r$\nKlicken Sie auf Fertig stellen, um den Installations-Assistenten zu schlieen." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Windows muss neu gestartet werden, um die Installation von $(^NameDA) zu vervollstndigen. Mchten Sie Windows jetzt neu starten?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Die Deinstallation von $(^NameDA) wird abgeschlossen" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) ist von Ihrem Computer entfernt worden.$\r$\n$\r$\nKlicken Sie auf Fertig stellen, um den Assistenten zu schlieen." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Windows muss neu gestartet werden, um die Deinstallation von $(^NameDA) zu vervollstndigen. Mchten Sie Windows jetzt neu starten?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Jetzt neu starten" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Windows spter selbst neu starten" + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) ausfhren" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Liesmich anzeigen" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Fertig stellen" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Startmen-Ordner bestimmen" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Bestimmen Sie einen Startmen-Ordner fr die Programmverknpfungen." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Whlen Sie den Startmen-Ordner fr die Programmverknpfungen aus. Falls Sie einen neuen Ordner erstellen mchten, geben Sie dessen Namen ein." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Keine Verknpfungen erstellen" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Deinstallation von $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA) wird von Ihrem Computer entfernt." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Sind Sie sicher, dass Sie die Installation von $(^Name) abbrechen wollen?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Sind Sie sicher, dass Sie die Deinstallation von $(^Name) abbrechen mchten?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Benutzer auswhlen" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Whlen Sie die Benutzer aus, fr die Sie $(^NameDA) installieren wollen." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Whlen Sie aus, ob Sie $(^NameDA) nur fr den eigenen Gebrauch oder fr die Nutzung durch alle Benutzer dieses Systems installieren mchten. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Installation fr alle Benutzer dieses Computers" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Installation nur fr mich" +!endif diff --git a/installer/NSIS/Contrib/Language files/Greek.nlf b/installer/NSIS/Contrib/Language files/Greek.nlf new file mode 100644 index 0000000..0caed22 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Greek.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1032 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1253 +# RTL - anything else than RTL means LTR +- +# Translation by Makidis N. Michael - http://dias.aueb.gr/~p3010094/ +# ^Branding +Nullsoft Install System %s +# ^SetupCaption + '$(^Name)' +# ^UninstallCaption + '$(^Name)' +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< & +# ^NextBtn +& > +# ^AgreeBtn +& +# ^AcceptBtn +& +# ^DontAcceptBtn +& +# ^InstallBtn +& +# ^UninstallBtn +&. +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +&... +# ^ShowDetailsBtn +& +# ^ClickNext + . +# ^ClickInstall + . +# ^ClickUninstall + . +# ^Name + +# ^Completed + +# ^LicenseText + '$(^NameDA)'. , . +# ^LicenseTextCB + '$(^NameDA)'. , . $_CLICK +# ^LicenseTextRB + '$(^NameDA)'. , . $_CLICK +# ^UnLicenseText + '$(^NameDA)'. , . +# ^UnLicenseTextCB + '$(^NameDA)'. , . $_CLICK +# ^UnLicenseTextRB + '$(^NameDA)'. , . $_CLICK +# ^Custom + +# ^ComponentsText + . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 +, : +# ^UnComponentsText + . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 +, : +# ^DirText + '$(^NameDA)' . , . $_CLICK +# ^DirSubText + +# ^DirBrowseText + '$(^NameDA)': +# ^UnDirText + '$(^NameDA)' . , . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + '$(^NameDA)': +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText + '$(^NameDA)' . $_CLICK +# ^UninstallingSubText +. : +# ^FileError + : \r\n\t"$0"\r\n ,\r\n , \r\n . +# ^FileError_NoIgnore + : \r\n\t"$0"\r\n , \r\n . +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +": " +# ^Unregistering +" : " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +" : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ! ; +# ^ErrorRegistering + DLL +# ^ExecShell +" (ExecShell): " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + : - opcode +# ^NoOLE +" OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +": " +# ^Skipped +": " +# ^CopyDetails + +# ^LogInstall + +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Greek.nsh b/installer/NSIS/Contrib/Language files/Greek.nsh new file mode 100644 index 0000000..4020eba --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Greek.nsh @@ -0,0 +1,121 @@ +;Language: Greek (1032) +;By Makidis N. Michael - http://dias.aueb.gr/~p3010094/ + +!insertmacro LANGFILE "Greek" "Greek" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " '$(^NameDA)'" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " '$(^NameDA)'.$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " . '$(^NameDA)'" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " '$(^NameDA)'.$\r$\n$\r$\n , '$(^NameDA)' .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " '$(^NameDA)'." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " , . '$(^NameDA)'." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . '$(^NameDA)'. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . '$(^NameDA)'. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " '$(^NameDA)'." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " , . '$(^NameDA)'." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . '$(^NameDA)'. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . '$(^NameDA)'. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " Page Down ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " '$(^NameDA)' ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " '$(^NameDA)' ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " '$(^NameDA)'." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " '$(^NameDA)'." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE " " + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE " '$(^NameDA)' ." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE " " + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE " '$(^NameDA)' ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " '$(^NameDA)'" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT " '$(^NameDA)' .$\r$\n$\r$\n ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " '$(^NameDA)'. ;" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " '$(^NameDA)'" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT " '$(^NameDA)' .$\r$\n$\r$\n ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " '$(^NameDA)'. ;" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " " + ${LangFileString} MUI_TEXT_FINISH_RUN "& '$(^NameDA)'" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME " & Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " '$(^NameDA)'." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " '$(^NameDA)'" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " '$(^NameDA)' ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " '$(^Name)';" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " '$(^Name)';" +!endif diff --git a/installer/NSIS/Contrib/Language files/Hebrew.nlf b/installer/NSIS/Contrib/Language files/Hebrew.nlf new file mode 100644 index 0000000..7acde6f --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Hebrew.nlf @@ -0,0 +1,190 @@ +# Hebrew NSIS language file +NLF v6 +# Language ID +1037 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1255 +# RTL - anything else than RTL means LTR +RTL +# Translation by Amir Szekely (aka KiCHiK), fixed by Yaron Shahrabani +# ^Branding +Nullsoft Install System %s +# ^SetupCaption + $(^Name) +# ^UninstallCaption + $(^Name) +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< & +# ^NextBtn +& > +# ^AgreeBtn + & +# ^AcceptBtn + & +# ^DontAcceptBtn + & +# ^InstallBtn +& +# ^UninstallBtn +& +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +&... +# ^ShowDetailsBtn +& +# ^ClickNext + . +# ^ClickInstall + . +# ^ClickUninstall + . +# ^Name + +# ^Completed + +# ^LicenseText + $(^NameDA). , ' '. +# ^LicenseTextCB + $(^NameDA). , . $_CLICK +# ^LicenseTextRB + $(^NameDA). , . $_CLICK +# ^UnLicenseText + $(^NameDA). , ' '. +# ^UnLicenseTextCB + $(^NameDA). , . $_CLICK +# ^UnLicenseTextRB + $(^NameDA). , . $_CLICK +# ^Custom + +# ^ComponentsText + . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 +, : +# ^UnComponentsText + . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 +, : +# ^DirText + $(^NameDA) . , '' . $_CLICK +# ^DirSubText + +# ^DirBrowseText + $(^NameDA): +# ^UnDirText + $(^NameDA) . , '' . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + $(^NameDA): +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText + $(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + :\r\n\t"$0"\r\n ,\r\n , \r\n +# ^FileError_NoIgnore + :\r\n\t"$0"\r\n , \r\n +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo + - +# ^Registering +": " +# ^Unregistering +" : " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +" : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ! ? +# ^ErrorRegistering + DLL +# ^ExecShell +" -: " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + ! +# ^NoOLE +" OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +" : " +# ^Skipped +": " +# ^CopyDetails + +# ^LogInstall + +# ^Byte +" +# ^Kilo +" " +# ^Mega +" " +# ^Giga +" " \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Hebrew.nsh b/installer/NSIS/Contrib/Language files/Hebrew.nsh new file mode 100644 index 0000000..53a7a1c --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Hebrew.nsh @@ -0,0 +1,129 @@ +;Language: Hebrew (1037) +;By Yaron Shahrabani + +!insertmacro LANGFILE "Hebrew" "Hebrew" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " , ' ' . $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " , ' ' . $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " Page Down." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE " -$(^NameDA) ." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE " -$(^NameDA) ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " $(^NameDA). ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " $(^NameDA). ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " " + ${LangFileString} MUI_TEXT_FINISH_RUN "& $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "& ' '" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE " " + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE " $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP " $(^NameDA) . $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS " " + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER " " +!endif diff --git a/installer/NSIS/Contrib/Language files/Hungarian.nlf b/installer/NSIS/Contrib/Language files/Hungarian.nlf new file mode 100644 index 0000000..4a411d2 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Hungarian.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +1038 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by Soft-Trans Bt. (V2) +# Translation by Orfanik Kft. (V3-V6) +# ^Branding +Nullsoft Teleptrendszer %s +# ^SetupCaption +$(^Name) Telept +# ^UninstallCaption +$(^Name) Eltvolt +# ^LicenseSubCaption +: Licencszerzds +# ^ComponentsSubCaption +: Teleptsi lehetsgek +# ^DirSubCaption +: Clmappa +# ^InstallingSubCaption +: Fjlok teleptse +# ^CompletedSubCaption +: Ksz +# ^UnComponentsSubCaption +: Eltvoltsi lehetsgek +# ^UnDirSubCaption +: Eltvolts mappja +# ^ConfirmSubCaption +: Megersts +# ^UninstallingSubCaption +: Fjlok eltvoltsa +# ^UnCompletedSubCaption +: Ksz +# ^BackBtn +< &Vissza +# ^NextBtn +&Tovbb > +# ^AgreeBtn +&Elfogadom +# ^AcceptBtn +&Elfogadom a Licencszerzds feltteleit +# ^DontAcceptBtn +&Nem fogadom el a Licencszerzds feltteleit +# ^InstallBtn +&Telepts +# ^UninstallBtn +&Eltvolts +# ^CancelBtn +&Mgse +# ^CloseBtn +&Bezrs +# ^BrowseBtn +&Tallzs... +# ^ShowDetailsBtn +&Rszletek +# ^ClickNext +Kattintson a Tovbb-ra a folytatshoz. +# ^ClickInstall +Kattintson a Teleptsre a teleptshez. +# ^ClickUninstall +Kattintson az Eltvoltsra az eltvoltshoz. +# ^Name +Nv +# ^Completed +Ksz +# ^LicenseText +A(z) $(^NameDA) teleptse eltt tekintse t a szerzds feltteleit. Ha elfogadja a szerzds valamennyi felttelt, az Elfogadom gombbal folytathatja. +# ^LicenseTextCB +A(z) $(^NameDA) teleptse eltt tekintse t a szerzds feltteleit. Ha elfogadja a szerzds valamennyi felttelt, jellje be a Jellngyzeten. $_CLICK +# ^LicenseTextRB +A(z) $(^NameDA) teleptse eltt tekintse t a szerzds feltteleit. Ha elfogadja a szerzds valamennyi felttelt, vlassza az els lehetsget. $_CLICK +# ^UnLicenseText +A(z) $(^NameDA) eltvoltsa eltt tekintse t a szerzds feltteleit. Ha elfogadja a szerzds valamennyi felttelt, az Elfogadom gombbal folytathatja. +# ^UnLicenseTextCB +A(z) $(^NameDA) eltvoltsa eltt tekintse t a szerzds feltteleit. Ha elfogadja a szerzds valamennyi felttelt, jellje be a Jellngyzeten. $_CLICK +# ^UnLicenseTextRB +A(z) $(^NameDA) eltvoltsa eltt tekintse t a szerzds feltteleit. Ha elfogadja a szerzds valamennyi felttelt, vlassza az els lehetsget. $_CLICK +# ^Custom +Egyni +# ^ComponentsText +Jellje be azokat az sszetevket amelyeket telepteni kvn s trlje a jellst a nem kvnt sszetevknl. $_CLICK +# ^ComponentsSubText1 +Vlassza ki a telepts tpust: +# ^ComponentsSubText2_NoInstTypes +Vlassza ki a teleptend sszetevket: +# ^ComponentsSubText2 +vagy, jellje ki a vlaszthat sszetevk kzl a telepteni kvnta(ka)t: +# ^UnComponentsText +Jellje be azokat az sszetevket amelyeket el kvn tvoltani s trlje a jellst az eltvoltani nem kvnt sszetevknl. $_CLICK +# ^UnComponentsSubText1 +Vlassza ki az Eltvolts tpust: +# ^UnComponentsSubText2_NoInstTypes +Vlassza ki az eltvoltand sszetevket: +# ^UnComponentsSubText2 +vagy, jellje ki a vlaszthat sszetevk kzl az eltvoltani kvnta(ka)t: +# ^DirText +A $(^NameDA) a kvetkez mappba kerl. Msik mappa vlasztshoz kattintson a Tallzs gombra. $_CLICK +# ^DirSubText +Telepts helye +# ^DirBrowseText +A(z) $(^NameDA) teleptsi helynek kivlasztsa: +# ^UnDirText +A(z) $(^NameDA) eltvoltsa a kvetkez mappbl. Msik mappa vlasztshoz kattintson a Tallzs gombra. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Vlassza ki, hogy a $(^NameDA) melyik mappbl kerljn eltvoltsra: +# ^SpaceAvailable +"Szabad terlet: " +# ^SpaceRequired +"Helyigny: " +# ^UninstallingText +A(z) $(^NameDA) eltvoltsa kvetkezik a szmtgprl. $_CLICK +# ^UninstallingSubText +Eltvolts helye: +# ^FileError +Hiba trtnt a fjl rsra trtn megnyitsakor: \r\n\t"$0"\r\nA Mgse gomb megnyomsval megszakthatja a teleptst,\r\naz Ismt gombbal megismtelheti a fjl rst,\r\na Kihagys gombbal kihagyhatja ezt a fjlt. +# ^FileError_NoIgnore +Hiba trtnt a fjl rsra trtn megnyitsakor: \r\n\t"$0"\r\nAz jra gomb megnyomsval megismtelheti a mveletet, vagy \r\na Mgse gombbal megszakthatja a teleptst. +# ^CantWrite +"Nem rhat: " +# ^CopyFailed +A msols megszakadt +# ^CopyTo +"Msols ide: " +# ^Registering +"Bejegyzs: " +# ^Unregistering +"Eltvolts: " +# ^SymbolNotFound +"A kvetkez szimblum nem tallhat: " +# ^CouldNotLoad +"Nem tlthet be: " +# ^CreateFolder +"Mappa ltrehozs: " +# ^CreateShortcut +"Parancsikon ltrehozsa: " +# ^CreatedUninstaller +"Ltrehozott eltvolt: " +# ^Delete +"Trlt fjl: " +# ^DeleteOnReboot +"Rendszerindtskor trlend: " +# ^ErrorCreatingShortcut +"Hiba a parancsikon ltrehozsakor: " +# ^ErrorCreating +"Hiba a ltrehozskor: " +# ^ErrorDecompressing +Hiba az adatok kibontsakor! Megsrlt a Telept? +# ^ErrorRegistering +Hiba a DLL regisztrlsakor +# ^ExecShell +"Vgrehajts a hozzrendelseken keresztl: " +# ^Exec +"Vgrehajts: " +# ^Extract +"Kibonts: " +# ^ErrorWriting +"Kibonts: Hiba a fjl rsakor " +# ^InvalidOpcode +Srlt a telept: hibs utasts +# ^NoOLE +"Nincs OLE: " +# ^OutputFolder +"Kimeneti mappa: " +# ^RemoveFolder +"Mappa eltvoltsa: " +# ^RenameOnReboot +"tnevezs rendszerindtskor: " +# ^Rename +"tnevezs: " +# ^Skipped +"Kihagyott: " +# ^CopyDetails +Adatok vglapra msolsa +# ^LogInstall +Telept ellenrzlista +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Hungarian.nsh b/installer/NSIS/Contrib/Language files/Hungarian.nsh new file mode 100644 index 0000000..a852a80 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Hungarian.nsh @@ -0,0 +1,122 @@ +;Language: Hungarian (1038) +;Translation by Jozsef Tamas Herczeg ( - 1.61-ig), +; Lajos Molnar (Orfanik) ( 1.62 - tl) + +!insertmacro LANGFILE "Hungarian" "Magyar" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "dvzli a(z) $(^NameDA) Telept Varzsl" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "A(z) $(^NameDA) teleptse kvetkezik a szmtgpre.$\r$\n$\r$\nJavasoljuk, hogy indts eltt zrja be a fut alkalmazsokat. gy a telept a rendszer jraindtsa nlkl tudja frissteni a szksges rendszerfjlokat.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "dvzli a(z) $(^NameDA) Eltvolt Varzsl" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Ez a varzsl segti a(z) $(^NameDA) eltvoltsban.$\r$\n$\r$\nMieltt elkezdi az eltviltst gyzdjn meg arrl, hogy a(z) $(^NameDA) nem fut.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licencszerzds" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "A(z) $(^NameDA) teleptse eltt tekintse t a szerzds feltteleit." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Ha elfogadja a szerzds valamennyi felttelt, az Elfogadom gombbal folytathatja. El kell fogadnia a(z) $(^NameDA) teleptshez." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Amennyiben elfogadja a feltteleket, jellje be a jellnnyzeten. A(z) $(^NameDA) teleptshez el kell fogadnia a feltteleket. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Amennyiben elfogadja a feltteleket, vlassza az els opcit. A(z) $(^NameDA) teleptshez el kell fogadnia a feltteleket. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licencszerzds" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "A(z) $(^NameDA) eltvoltsa eltt tekintse t a szerzds feltteleit." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Ha elfogadja a szerzds valamennyi felttelt, az Elfogadom gombbal folytathatja. El kell fogadnia a(z) $(^NameDA) eltvoltshoz." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Amennyiben elfogadja a feltteleket, jellje be a jellnnyzeten. A(z) $(^NameDA) eltvoltshoz el kell fogadnia a feltteleket. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Amennyiben elfogadja a feltteleket, vlassza az els opcit. A(z) $(^NameDA) eltvoltshoz el kell fogadnia a feltteleket. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "A PageDown gombbal olvashatja el a szerzds folytatst." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "sszetevk kivlasztsa" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Vlassza ki, hogy a(z) $(^NameDA) mely funkciit kvnja telepteni." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "sszetevk kivlasztsa" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Vlassza ki, hogy a(z) $(^NameDA) mely funkciit kvnja eltvoltani." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Lers" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Vigye r az egeret az sszetevre, hogy megtekinthesse a lerst." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Vigye r az egeret az sszetevre, hogy megtekinthesse a lerst." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Teleptsi hely kivlasztsa" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Vlassza ki a(z) $(^NameDA) teleptsnek mappjt." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Teleptsi hely kivlasztsa" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Vlassza ki a(z) $(^NameDA) teleptsnek mappjt." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Teleptsi folyamat" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Kis trelmet a(z) $(^NameDA) teleptsig." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Telepts befejezdtt" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "A telepts sikeresen befejezdtt." + ${LangFileString} MUI_TEXT_ABORT_TITLE "A telepts megszakadt" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "A telepts sikertelen volt." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Eltvoltsi folyamat" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Kis trelmet a(z) $(^NameDA) eltvoltsig." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Az eltvolts befejezdtt" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Az eltvolts sikeresen befejezdtt." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Az eltvolts megszakadt" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Az eltvolts sikertelen volt." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "A(z) $(^NameDA) teleptse megtrtnt." + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "A(z) $(^NameDA) teleptse megtrtnt.$\r$\n$\r$\nA Befejezs gomb megnyomsval zrja be a varzslt." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "A(z) $(^NameDA) teleptsnek befejezshez jra kell indtani a rendszert. Most akarja jraindtani?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "A(z) $(^NameDA) eltvolts varzsljnak befejezse." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "A(z) $(^NameDA) eltvoltsa sikeresen befejezdtt.$\r$\n$\r$\nA Finish-re kattintva bezrul ez a varzsl." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "A szmtgpet jra kell indtani, hogy a(z) $(^NameDA) eltvoltsa teljes legyen. Akarja most jraindtani a rendszert?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Most indtom jra" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ksbb fogom jraindtani" + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) futtatsa" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "OlvassEl fjl megjelentse" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Befejezs" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Start men mappa kijellse" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Start men mappa kijellse a program parancsikonjaihoz." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Jelljn ki egy mappt a Start menben, melybe a program parancsikonjait fogja elhelyezni. Berhatja j mappa nevt is." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nincs parancsikon elhelyezs" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "A(z) $(^NameDA) Eltvoltsa." + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "A(z) $(^NameDA) eltvoltsa kvetkezik a szmtgprl." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Biztos, hogy ki akar lpni a(z) $(^Name) Teleptbl?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Biztos, hogy ki akar lpni a(z) $(^Name) Eltvoltbl?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Icelandic.nlf b/installer/NSIS/Contrib/Language files/Icelandic.nlf new file mode 100644 index 0000000..6375697 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Icelandic.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +15 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Gretar Orri Kristinsson +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Uppsetning +# ^UninstallCaption +$(^Name) Fjarlging +# ^LicenseSubCaption +: Notandaleyfissamningur +# ^ComponentsSubCaption +: Uppsetningarvalmguleikar +# ^DirSubCaption +: Uppsetningarskrarsafn +# ^InstallingSubCaption +: Set upp +# ^CompletedSubCaption +: Loki +# ^UnComponentsSubCaption +: Fjarlgingarvalmguleikar +# ^UnDirSubCaption +: Fjarlgingarskrarsafn +# ^ConfirmSubCaption +: Stafesting +# ^UninstallingSubCaption +: Fjarlgi +# ^UnCompletedSubCaption +: Loki +# ^BackBtn +< &Til baka +# ^NextBtn +&fram > +# ^AgreeBtn +g &Samykki +# ^AcceptBtn +g &samykki skilmla leyfissamningsins +# ^DontAcceptBtn +g samykki &ekki skilmla leyfissamningsins +# ^InstallBtn +&Setja upp +# ^UninstallBtn +&Fjarlgja +# ^CancelBtn +Htta vi +# ^CloseBtn +&Loka +# ^BrowseBtn +&Vafra... +# ^ShowDetailsBtn +Sna &upplsingar +# ^ClickNext +Smelltu 'fram' til a halda fram. +# ^ClickInstall +Smelltu 'Setja upp' til ess a hefja uppsetninguna. +# ^ClickUninstall +Smelltu 'Fjarlgja' til a hefja fjarlgingar ferli. +# ^Name +Nafn +# ^Completed +Loki +# ^LicenseText +Vinsamlegast skoau notandaleyfissamninginn vel ur en uppsetning $(^NameDA) hefst. Ef samykkir skilmla samningsins, smelltu 'g samykki'. +# ^LicenseTextCB +Vinsamlegast skoau notandaleyfissamninginn vel ur en uppsetning $(^NameDA) hefst. Ef samykkir skilmla samningsins, hakau kassann hr a nean. $_CLICK +# ^LicenseTextRB +Vinsamlegast skoau notandaleyfissamninginn vel ur en uppsetning $(^NameDA) hefst. Ef samykkir skilmla samningsins, veldu fyrsta valmguleikann hr a nean. $_CLICK +# ^UnLicenseText +Vinsamlegast skoau notandaleyfissamninginn vel ur en uppsetning $(^NameDA) hefst. Ef samykkir skilmla samningsins, smelltu 'g samykki'. +# ^UnLicenseTextCB +Vinsamlegast skoau notandaleyfissamninginn vel ur en uppsetning $(^NameDA) hefst. Ef samykkir skilmla samningsins, hakau kassann hr a nean. $_CLICK +# ^UnLicenseTextRB +Vinsamlegast skoau notandaleyfissamninginn vel ur en uppsetning $(^NameDA) hefst. Ef samykkir skilmla samningsins, veldu fyrsta valmguleikann hr a nean. $_CLICK +# ^Custom +Sjlfval +# ^ComponentsText +Hakau vi hluti sem vilt setja upp og taktu haki af eim hlutum sem vilt ekki setja upp. $_CLICK +# ^ComponentsSubText1 +Veldu tegund uppsetningar: +# ^ComponentsSubText2_NoInstTypes +Veldu hluti sem a setja upp: +# ^ComponentsSubText2 +Ea, veldu valfrjlsa hluti a setja upp: +# ^UnComponentsText +Hakau vi hluti sem vilt fjarlgja og taktu haki af eim hlutum sem vilt ekki fjarlgja. $_CLICK +# ^UnComponentsSubText1 +Veldu tegund fjarlgingar: +# ^UnComponentsSubText2_NoInstTypes +Veldu hluti sem a fjarlgja: +# ^UnComponentsSubText2 +Ea, veldu valfrjlsa hluti sem a fjarlgja: +# ^DirText +Uppsetningin mun setja $(^NameDA) upp eftirfarandi skrarsafn. Til a setja forriti upp anna skrarsafn, smelltu 'Vafra...' og veldu anna skrarsafn. $_CLICK +# ^DirSubText +Uppsetningarskrarsafn +# ^DirBrowseText +Veldu a skrarsafn sem vilt setja $(^NameDA) upp : +# ^UnDirText +Uppsetningin mun fjarlgja $(^NameDA) r eftirfarandi skrarsafni. Til a fjarlgja forriti r ru skrarsafni, smelltu 'Vafra...' og veldu anna skrarsafn. $_CLICK +# ^UnDirSubText +"Fjarlgingarskrarsafn" +# ^UnDirBrowseText +Veldu a skrarsafn sem vilt fjarlgja $(^NameDA) r: +# ^SpaceAvailable +"Tiltkt rmi: " +# ^SpaceRequired +"Nausynlegt rmi: " +# ^UninstallingText +$(^NameDA) verur fjarlgt r eftirfarandi skrarsafni. $_CLICK +# ^UninstallingSubText +Fjarlgi r: +# ^FileError +Villa vi a skrifa skr: \r\n\r\n$0\r\n\r\nSmelltu 'Htta vi' til a stoppa uppsetninguna,\r\n'Reyna aftur' til a gera ara tilraun, ea\r\n'Hunsa' til sleppa essari skr. +# ^FileError_NoIgnore +Villa vi a skrifa skr: \r\n\r\n$0\r\n\r\nSmelltu 'Reyna aftur' til a gera ara tilraun, ea\r\n'Htta vi' til a stoppa uppsetninguna. +# ^CantWrite +"Get ei skrifa: " +# ^CopyFailed +Afritun mistkst +# ^CopyTo +"Afrita til " +# ^Registering +"Skrsetja: " +# ^Unregistering +"Afskr: " +# ^SymbolNotFound +"Fann ekki tkn: " +# ^CouldNotLoad +"Gat ekki hlai inn: " +# ^CreateFolder +"Ba til skrarsafn: " +# ^CreateShortcut +"Ba til fltilei: " +# ^CreatedUninstaller +"Bj til fjarlgingarhjlp: " +# ^Delete +"Eya skr: " +# ^DeleteOnReboot +"Eya vi endurrsingu: " +# ^ErrorCreatingShortcut +"Villa vi ger fltileiar: " +# ^ErrorCreating +"Villa vi ger: " +# ^ErrorDecompressing +Villa vi afjppun gagna! Bilu uppsetningarhjlp? +# ^ErrorRegistering +Villa vi skrsetningu DLL +# ^ExecShell +"Keyrslugluggi: " +# ^Exec +"Keyra: " +# ^Extract +"Fra t: " +# ^ErrorWriting +"Fra t: villa vi a skrifa skr " +# ^InvalidOpcode +Uppsetningarhjlp bilu: rangur striki +# ^NoOLE +"Engin OLE fyrir: " +# ^OutputFolder +"tskrarsafn: " +# ^RemoveFolder +"Fjarlgja skrarsafn: " +# ^RenameOnReboot +"Endurskra vi endurrsingu: " +# ^Rename +"Endurskra: " +# ^Skipped +"Sleppt: " +# ^CopyDetails +Afrita upplsingar til skrifbrettis +# ^LogInstall +Skr uppsetningarferli +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Icelandic.nsh b/installer/NSIS/Contrib/Language files/Icelandic.nsh new file mode 100644 index 0000000..97bb5b1 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Icelandic.nsh @@ -0,0 +1,121 @@ +;Language: Icelandic (15) +;By Gretar Orri Kristinsson + +!insertmacro LANGFILE "Icelandic" "Icelandic" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Velkominn til $(^NameDA) uppsetningarhjlparinnar" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "essi hjlp mun leia ig gegnum uppsetninguna $(^NameDA).$\r$\n$\r$\nMlt er me v a lokir llum rum forritum ur en uppsetningin hefst. etta mun gera uppsetningarforritinu kleyft a uppfra kerfiskrr n ess a endurrsa tlvuna.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Velkomin(n) til $(^NameDA) fjarlgingarhjlparinnar" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "essi hjlp mun leia ig gegnum fjarlginguna $(^NameDA).$\r$\n$\r$\nur en fjarlging hefst skal ganga r skugga um a $(^NameDA) s ekki opi.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Notandaleyfissamningur" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Vinsamlegast skoau Notandaleyfissamninginn vel ur en uppsetning $(^NameDA) hefst." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Ef samykkir skilmla samningsins, smelltu 'g samykki' til a halda fram. verur a samykkja samninginn til ess a setja upp $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ef samykkir skilmla samningsins, hakau kassann hr a nean. verur a samykkja samninginn til ess a setja upp $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ef samykkir skilmla samningsins, veldu fyrsta valmguleikann hr a nean. verur a samykkja samninginn til ess a setja upp $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Leyfissamningur" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Vinsamlegast skoau leyfissamninginn vel ur en fjarlging $(^NameDA) hefst." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Ef samykkir skilmla samningsins, smelltu 'g samykki' til a halda fram. verur a samykkja samninginn til ess a fjarlgja $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ef samykkir skilmla samningsins, hakau kassann hr a nean. verur a samykkja samninginn til ess a fjarlgja $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ef samykkir skilmla samningsins, veldu fyrsta valmguleikann hr a nean. verur a samykkja samninginn til ess a fjarlgja $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Smelltu 'PageDown' takkann lyklaborinu til a sj afganginn af samningnum." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Velja hluti" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Veldu hvaa $(^NameDA) hluti vilt setja upp." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Velja hluti" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Velja hvaa $(^NameDA) hluti vilt fjarlgja." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Lsing" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Fru msina yfir hlut til a f lsinguna honum." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Fru msina yfir hlut til a f lsinguna honum." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Veldu uppsetningarskarsafn" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Veldu a skrarsafn sem vilt setja $(^NameDA) upp ." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Velja fjarlgingarskarsafn" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Veldu a skrarsafn sem vilt fjarlgja $(^NameDA) r." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Set upp" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Vinsamlegast dokau vi mean $(^NameDA) er sett upp." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Uppsetningu loki" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Uppsetning tkst." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Htt vi uppsetningu" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Uppsetningu lauk ekki sem skildi." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Fjarlgi" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Vinsamlegast dokau vi mean $(^NameDA) er fjarlgt." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Fjarlgingu loki" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Fjarlging tkst." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Htt vi fjarlgingu" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Fjarlgingu lauk ekki sem skildi." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Ljka $(^NameDA) uppsetningarhjlpinni" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) er n upp sett tlvunni inni.$\r$\n$\r$\nSmelltu 'Ljka' til a loka essari hjlp." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Til a ljka uppsetningunni $(^NameDA) verur a endurrsa tlvuna. Viltu endurrsa nna?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Ljka $(^NameDA) fjarlgingarhjlpinni" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) hefur n veri fjarlgt r tlvunni.$\r$\n$\r$\nSmelltu 'Ljka' til a loka essari hjlp." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Til a ljka fjarlgingunni $(^NameDA) verur a endurrsa tlvuna. Viltu endurrsa nna?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Endurrsa nna" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "g vil endurrsa seinna" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Keyra $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Skoa LestuMig" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Ljka" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Velja skrarsafn 'Start' valmyndar" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Veldu skrarsafn $(^NameDA) fltileia fyrir 'Start' valmyndina." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Veldu skrarsafn fltileia forritsins fyrir 'Start' valmyndina. getur einnig bi til ntt skrarsafn me v a setja inn ntt nafn." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ekki ba til fltileiir 'Start' valmyndinni" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Fjarlgja $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Fjarlgja $(^NameDA) r tlvunni." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Ertu viss um a viljir loka $(^Name) uppsetningarhjlpinni?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Ertu viss um a viljir loka $(^Name) fjarlgingarhjlpinni?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Indonesian.nlf b/installer/NSIS/Contrib/Language files/Indonesian.nlf new file mode 100644 index 0000000..9d52bd1 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Indonesian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1057 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +- +# RTL - anything else than RTL means LTR +- +# Translation ariel825010106@yahoo.com modified and completed by was.uthm@gmail.com in April 2009 +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Instalasi Program $(^Name) +# ^UninstallCaption +Penghapusan Program $(^Name) +# ^LicenseSubCaption +: Perihal Lisensi +# ^ComponentsSubCaption +: Pilihan Instalasi +# ^DirSubCaption +: Lokasi Instalasi +# ^InstallingSubCaption +: Proses Instalasi +# ^CompletedSubCaption +: Selesai +# ^UnComponentsSubCaption +: Pilihan Penghapusan +# ^UnDirSubCaption +: Berkas Lokasi yang dihapus +# ^ConfirmSubCaption +: Konfirmasi +# ^UninstallingSubCaption +: Proses Penghapusan +# ^UnCompletedSubCaption +: Selesai +# ^BackBtn +< &Mundur +# ^NextBtn +&Lanjut > +# ^AgreeBtn +Saya &Setuju +# ^AcceptBtn +Saya s&etuju dengan Perihal Lisensi +# ^DontAcceptBtn +Saya &tidak setuju dengan Perihal Lisensi +# ^InstallBtn +&Instal +# ^UninstallBtn +&Hapus +# ^CancelBtn +Batalkan +# ^CloseBtn +&Tutup +# ^BrowseBtn +Ca&ri... +# ^ShowDetailsBtn +Lihat &perincian +# ^ClickNext +Tekan tombol Lanjut untuk melanjutkan. +# ^ClickInstall +Tekan tombol Instal untuk memulai instalasi. +# ^ClickUninstall +Tekan tombol Hapus untuk memulai penghapusan. +# ^Name +Nama +# ^Completed +Selesai +# ^LicenseText +Silahkan membaca lisensi berikut sebelum memulai instalasi $(^NameDA). Jika anda menyetujui dan menerima semua pernyataan, tekan tombol Saya Setuju. +# ^LicenseTextCB +Silahkan membaca lisensi berikut sebelum memulai instalasi $(^NameDA). Jika anda menyetujui dan menerima semua pernyataan, beri tanda centang. $_CLICK +# ^LicenseTextRB +Silahkan membaca lisensi berikut sebelum memulai instalasi $(^NameDA). Jika anda menyetujui dan menerima semua pernyataan, pilihlah salah satu item dibawah ini. $_CLICK +# ^UnLicenseText +Silahkan membaca lisensi berikut sebelum mulai menghapus $(^NameDA). Jika anda menyetujui dan menerima semua pernyataan, tekan tombol Saya Setuju. +# ^UnLicenseTextCB +Silahkan membaca lisensi berikut sebelum mulai menghapus $(^NameDA). Jika anda menyetujui dan menerima semua pernyataan, beri tanda centang. $_CLICK +# ^UnLicenseTextRB +Silahkan membaca lisensi berikut sebelum mulai menghapus $(^NameDA). Jika anda menyetujui dan menerima semua pernyataan, pilihlah salah satu item dibawah ini. $_CLICK +# ^Custom +Tentukan Sendiri +# ^ComponentsText +Beri tanda centang pada komponen yang akan di instal and hilangkan tanda centang pada komponen yang tidak perlu di instal. $_CLICK +# ^ComponentsSubText1 +Pilih tipe instalasi: +# ^ComponentsSubText2_NoInstTypes +Pilih komponen-komponen yang akan di instal: +# ^ComponentsSubText2 +Atau, pilih komponen tambahan yang akan di instal: +# ^UnComponentsText +Beri tanda centang pada komponen yang akan dihapus and hilangkan tanda centang pada komponen yang tidak ingin dihapus. $_CLICK +# ^UnComponentsSubText1 +Pilih tipe penghapusan: +# ^UnComponentsSubText2_NoInstTypes +Pilih komponen-komponen yang ingin dihapus: +# ^UnComponentsSubText2 +Atau, pilih komponen tambahan yang ingin dihapus: +# ^DirText +Program $(^NameDA) akan di instal pada lokasi berikut. Untuk memilih lokasi, tekan tombol Cari kemudian pilih lokasi yang anda kehendaki. $_CLICK +# ^DirSubText +Lokasi instalasi +# ^DirBrowseText +Pilih lokasi instalasi program $(^NameDA): +# ^UnDirText +Proses penghapusan program $(^NameDA) dari lokasi instalasi berikut. Untuk memilih lokasi lainnya, tekan tombol Cari kemudian pilih lokasi yang anda kehendaki. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Pilih lokasi instalasi program $(^NameDA) yang akan dihapus: +# ^SpaceAvailable +"Ruang yang tersedia: " +# ^SpaceRequired +"Ruang yang dibutuhkan: " +# ^UninstallingText +$(^NameDA) akan dihapus dari lokasi berikut. $_CLICK +# ^UninstallingSubText +Proses penghapusan dari: +# ^FileError +Tidak dapat membuka berkas untuk menulis: \r\n\t"$0"\r\nTekan tombol Abort untuk membatalkan instalasi,\r\nRetry untuk mencoba lagi, atau\r\nIgnore untuk melewati file ini. +# ^FileError_NoIgnore +Tidak dapat membuka berkas untuk menulis: \r\n\t"$0"\r\nTekan tombol Retry untuk mencoba lagi, atau\r\nCancel untuk membatalkan instalasi. +# ^CantWrite +"Tidak bisa menulis pada berkas: " +# ^CopyFailed +Gagal menyalin berkas +# ^CopyTo +"Menyalin ke " +# ^Registering +"Memasukkan dalam daftar: " +# ^Unregistering +"Menghapus dari daftar: " +# ^SymbolNotFound +"Tidak dapat menemukan simbol: " +# ^CouldNotLoad +"Tidak dapat memuat: " +# ^CreateFolder +"Membuat tempat menyimpan berkas: " +# ^CreateShortcut +"Membuat shortcut: " +# ^CreatedUninstaller +"Program penghapusan yang dibuat: " +# ^Delete +"Menghapus berkas: " +# ^DeleteOnReboot +"Akan dihapus saat reboot: " +# ^ErrorCreatingShortcut +"Tidak dapat membuat shortcut: " +# ^ErrorCreating +"Ada kesalahan saat membuat: " +# ^ErrorDecompressing +Ada kesalahan saat membuka data! Program Instalasi tidak lengkap? +# ^ErrorRegistering +Ada kesalahan ketika mendaftarkan modul DLL +# ^ExecShell +"Perintah: " +# ^Exec +"Menjalankan: " +# ^Extract +"Proses ekstraksi berkas: " +# ^ErrorWriting +"Ekstraksi: ada kesalahan saat menulis ke berkas " +# ^InvalidOpcode +Program instalasi rusak: kode program tidak lengkap +# ^NoOLE +"OLE tidak ditemukan: " +# ^OutputFolder +"Lokasi tujuan: " +# ^RemoveFolder +"Menghapus lokasi penyimpanan: " +# ^RenameOnReboot +"Memberi nama baru saat reboot: " +# ^Rename +"Memberi nama baru: " +# ^Skipped +"Dilewati: " +# ^CopyDetails +Salin perincian ke Clipboard +# ^LogInstall +Catat proses instalasi +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Indonesian.nsh b/installer/NSIS/Contrib/Language files/Indonesian.nsh new file mode 100644 index 0000000..a785a9c --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Indonesian.nsh @@ -0,0 +1,129 @@ +;Language: Indonesian (1057) +;By Ariel825010106@yahoo.com modified by was.uthm@gmail.com in April 2009 + +!insertmacro LANGFILE "Indonesian" "Indonesian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Selamat datang di program instalasi $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Program ini akan membantu anda dalam proses instalasi $(^NameDA).$\r$\n$\r$\nAnda sangat disarankan untuk menutup program lainnya sebelum memulai proses instalasi. Hal ini diperlukan agar berkas yang terkait dapat diperbarui tanpa harus booting ulang komputer anda.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Selamat datang di program penghapusan $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Program ini akan membantu anda pada proses penghapusan $(^NameDA).$\r$\n$\r$\nSebelum memulai proses penghapusan, pastikan dulu $(^NameDA) tidak sedang digunakan.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Perihal Lisensi" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Silahkan membaca perihal lisensi sebelum memulai proses instalasi $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Jika anda setuju dan menerima semua pernyataan, tekan tombol Saya Setuju untuk melanjutkan. Anda harus setuju untuk memulai instalasi $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jika anda setuju dan menerima semua pernyatan, beri tanda centang. Anda harus setuju untuk memulai instalasi $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jika anda setuju dan menerima semua pernyataan, pilihlah salah satu item dibawah ini. Anda harus setuju untuk memulai instalasi $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Perihal Lisensi" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Silahkan membaca lisensi berikut sebelum melakukan penghapusan $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Jika anda setuju dan menerima semua pernyataan, tekan tombol Saya Setuju untuk melanjutkan. Anda harus setuju untuk memulai proses penghapusan $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jika anda setuju dan menerima semua pernyataan, beri tanda centang. Anda harus setuju untuk memulai proses penghapusan $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jika anda setuju dan menerima semua pernyataan, pilihlah salah satu item dibawah ini. Anda harus setuju untuk memulai proses penghapusan $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Tekan tombol Page Down untuk melihat pernyataan berikutnya." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Pilih Komponen" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Pilih komponen fitur tambahan dari $(^NameDA) yang ingin di instal." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Pilih Komponen" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Pilih komponen fitur tambahan dari $(^NameDA) yang ingin dihapus." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Deskripsi" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Tunjuk ke salah satu komponen untuk melihat deskripsi tentang komponen itu." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Tunjuk ke salah satu komponen untuk melihat deskripsi tentang komponen itu." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Pilih Lokasi Instalasi" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Pilih lokasi untuk instalasi program $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Pilih Lokasi berkas yang akan dihapus" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Pilih lokasi instalasi program $(^NameDA) yang akan dihapus." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Proses instalasi " + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Mohon tunggu sejenak, instalasi program $(^NameDA) sedang berlangsung." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalasi Selesai" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Seluruh proses instalasi sudah paripurna." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalasi Dibatalkan" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Proses instalasi tidak selesai dengan sempurna." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Proses penghapusan" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Mohon tunggu sejenak, penghapusan program $(^NameDA) sedang berlangsung." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Proses Penghapusan Selesai" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Seluruh proses penghapusan sudah paripurna." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Penghapusan Dibatalkan" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Proses penghapusa tidak selesai dengan sempurna." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Menutup Instalasi Program $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) sudah di instal di komputer anda.$\r$\n$\r$\nTekan tombol Selesai untuk menutup program." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Komputer anda memerlukan booting ulang untuk menyempurnakan proses instalasi $(^NameDA). Apakah anda akan melakukan booting ulang sekarang juga?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Menutup program penghapusan $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) sudah dihapus dari komputer anda.$\r$\n$\r$\nTekan tombol Selesai untuk menutup." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Komputer anda memerlukan booting untuk menyempurnakan proses penghapusan $(^NameDA). Apakah anda akan melakukan booting ulang sekarang juga?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Booting ulang sekarang" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Booting ulang nanti" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Jalankan $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Buka berkas Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Selesai" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Pilih lokasi dari Menu Start" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Pilih lokasi dari Menu Start untuk meletakkan shortcut $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Pilih lokasi dari Menu Start untuk meletakkan shortcut program ini. Anda bisa juga membuat lokasi baru dengan cara menulis nama lokasi yang dikehendaki." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Tidak perlu membuat shortcut" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Penghapusan $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Menghapus $(^NameDA) dari komputer anda." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Apakah anda yakin ingin menghentikan proses instalasi $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Apakah anda yakin ingin menghentikan proses penghapusan $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Pilihan Pemakai" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Pilihlah pemakai komputer yang akan menggunakan program $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Apakah anda akan melakukan instalasi $(^NameDA) untuk anda sendiri atau untuk semua pemakai komputer ini. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Instalasi untuk semua pemakai komputer ini" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Instalasi hanya untuk saya sendiri" +!endif diff --git a/installer/NSIS/Contrib/Language files/Irish.nlf b/installer/NSIS/Contrib/Language files/Irish.nlf new file mode 100644 index 0000000..999b278 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Irish.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +2108 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Kevin P. Scannell < scannell at slu dot edu > +# ^Branding +Cras Suitela Nullsoft %s +# ^SetupCaption +Socr $(^Name) +# ^UninstallCaption +Dshuiteil $(^Name) +# ^LicenseSubCaption +: Comhaont um Cheadnas +# ^ComponentsSubCaption +: Roghanna Suitela +# ^DirSubCaption +: Fillten Suitela +# ^InstallingSubCaption +: Suiteil +# ^CompletedSubCaption +: Crochnaithe +# ^UnComponentsSubCaption +: Roghanna Dshuitela +# ^UnDirSubCaption +: Fillten Dshuitela +# ^ConfirmSubCaption +: Deimhni +# ^UninstallingSubCaption +: Dshuiteil +# ^UnCompletedSubCaption +: Crochnaithe +# ^BackBtn +< Ar Ai&s +# ^NextBtn +Ar &Aghaidh > +# ^AgreeBtn +Gl&acaim Leis +# ^AcceptBtn +Tim toilteanach &glacadh le coinnollacha an Chomhaont um Cheadnas +# ^DontAcceptBtn +Nlim &toilteanach glacadh le coinnollacha an Chomhaont um Cheadnas +# ^InstallBtn +&Suiteil +# ^UninstallBtn +&Dshuiteil +# ^CancelBtn +Cealaigh +# ^CloseBtn +&Dn +# ^BrowseBtn +B&rabhsil... +# ^ShowDetailsBtn +Taispein &sonra +# ^ClickNext +Cliceil "Ar Aghaidh" chun leanint ar aghaidh. +# ^ClickInstall +Cliceil "Suiteil" chun tos. +# ^ClickUninstall +Cliceil "Dshuiteil" chun tos. +# ^Name +Ainm +# ^Completed +Crochnaithe +# ^LicenseText +Dan inichadh ar an gComhaont um Cheadnas sula suitelann t $(^NameDA). M ghlacann t le gach coinnoll den chomhaont, cliceil "Glacaim Leis". +# ^LicenseTextCB +Dan inichadh ar an gComhaont um Cheadnas sula suitelann t $(^NameDA). M ghlacann t le gach coinnoll den chomhaont, cliceil an ticbhosca thos. $_CLICK +# ^LicenseTextRB +Dan inichadh ar an gComhaont um Cheadnas sula suitelann t $(^NameDA). M ghlacann t le gach coinnoll den chomhaont, roghnaigh an chad rogha thos. $_CLICK +# ^UnLicenseText +Dan inichadh ar an gComhaont um Cheadnas sula ndshuitelann t $(^NameDA). M ghlacann t le gach coinnoll den chomhaont, cliceil "Glacaim Leis". +# ^UnLicenseTextCB +Dan inichadh ar an gComhaont um Cheadnas sula ndshuitelann t $(^NameDA). M ghlacann t le gach coinnoll den chomhaont, cliceil an ticbhosca thos. $_CLICK +# ^UnLicenseTextRB +Dan inichadh ar an gComhaont um Cheadnas sula ndshuitelann t $(^NameDA). M ghlacann t le gach coinnoll den chomhaont, roghnaigh an chad rogha thos. $_CLICK +# ^Custom +Saincheaptha +# ^ComponentsText +Roghnaigh na comhphirteanna is mian leat a shuiteil, agus droghnaigh na comhphirteanna nach mian leat a shuiteil. $_CLICK +# ^ComponentsSubText1 +Roghnaigh cinel na suitela: +# ^ComponentsSubText2_NoInstTypes +Roghnaigh na comhphirteanna is mian leat a shuiteil: +# ^ComponentsSubText2 +N, roghnaigh na comhphirteanna roghnacha is mian leat a shuiteil: +# ^UnComponentsText +Roghnaigh na comhphirteanna is mian leat a dhshuiteil, agus droghnaigh na comhphirteanna nach mian leat a dhshuiteil. $_CLICK +# ^UnComponentsSubText1 +Roghnaigh cinel na dshuitela: +# ^UnComponentsSubText2_NoInstTypes +Roghnaigh comhphirteanna le dshuiteil: +# ^UnComponentsSubText2 +N, roghnaigh na comhphirteanna roghnacha is mian leat a dhshuiteil: +# ^DirText +Cuirfidh an Suitela $(^NameDA) san fhillten seo a leanas. Ms mian leat suiteil i bhfillten difriil, cliceil "Brabhsil" agus roghnaigh fillten eile. $_CLICK +# ^DirSubText +Sprioc-Fhillten +# ^DirBrowseText +Roghnaigh an fillten inar mian leat $(^NameDA) a shuiteil: +# ^UnDirText +Bainfidh an Suitela $(^NameDA) amach as an bhfillten seo a leanas. Ms mian leat a dhshuiteil fhillten difriil, cliceil "Brabhsil" agus roghnaigh fillten eile. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Roghnaigh an fillten ar mian leat $(^NameDA) a dhshuiteil as: +# ^SpaceAvailable +"Sps le fil: " +# ^SpaceRequired +"Sps de dhth: " +# ^UninstallingText +Dshuitelfar $(^NameDA) n fhillten seo a leanas. $_CLICK +# ^UninstallingSubText + dhshuiteil : +# ^FileError +Earrid agus comhad scrobh: \r\n\r\n$0\r\n\r\nCliceil "Abort" chun an tsuiteil a stopadh,\r\n"Retry" chun iarracht eile a dhanamh, n\r\n"Ignore" chun neamhaird a dhanamh den chomhad seo. +# ^FileError_NoIgnore +Earrid agus comhad scrobh: \r\n\r\n$0\r\n\r\nCliceil "Retry" chun iarracht eile a dhanamh, n\r\n"Cancel" chun an tsuiteil a stopadh. +# ^CantWrite +"N fidir scrobh: " +# ^CopyFailed +Theip ar an gcipeil +# ^CopyTo +"Cipeil go " +# ^Registering +"Clr: " +# ^Unregistering +"Dchlr: " +# ^SymbolNotFound +"Norbh fhidir siombail a aimsi: " +# ^CouldNotLoad +"Norbh fhidir lucht: " +# ^CreateFolder +"Cruthaigh fillten: " +# ^CreateShortcut +"Cruthaigh aicearra: " +# ^CreatedUninstaller +"Cruthaodh dshuitela: " +# ^Delete +"Scrios comhad: " +# ^DeleteOnReboot +"Scrios ag am atosaithe: " +# ^ErrorCreatingShortcut +"Earrid agus aicearra chruth: " +# ^ErrorCreating +"Earrid le linn cruthaithe: " +# ^ErrorDecompressing +Earrid agus sonra ndchomhbhr! Suitela truaillithe? +# ^ErrorRegistering +Earrid agus DLL chlr +# ^ExecShell +"Blaosc: " +# ^Exec +"Rith: " +# ^Extract +"Bain Amach: " +# ^ErrorWriting +"Extract: earrid le linn scrofa " +# ^InvalidOpcode +Dshuitela truaillithe: cd neamhbhail oibrochta +# ^NoOLE +"Gan OLE le haghaidh: " +# ^OutputFolder +"Fillten aschurtha: " +# ^RemoveFolder +"Bain fillten: " +# ^RenameOnReboot +"Athainmnigh ag am atosaithe: " +# ^Rename +"Athainmnigh: " +# ^Skipped +"Neamhaird danta de: " +# ^CopyDetails +Cipeil Sonra go dt an Ghearrthaisce +# ^LogInstall +Logil an priseas suitela +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Irish.nsh b/installer/NSIS/Contrib/Language files/Irish.nsh new file mode 100644 index 0000000..8f7c5cd --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Irish.nsh @@ -0,0 +1,121 @@ +;Language: Irish (2108) +;By Kevin P. Scannell < scannell at slu dot edu > + +!insertmacro LANGFILE "Irish" "Irish" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Filte go dt Draoi Suitela $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Danfaidh an draoi seo treor duit trd an suiteil de $(^NameDA).$\r$\n$\r$\nMoltar duit gach feidhmchlr eile a dhnadh sula dtosaonn t an Suitela. Cinnteoidh s seo gur fidir na comhaid oirinacha a nuashonr gan do romhaire a atos.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Filte go dt Draoi Dshuitela $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Danfaidh an draoi seo treor duit trd an dshuiteil de $(^NameDA).$\r$\n$\r$\nB cinnte nach bhfuil $(^NameDA) ag rith sula dtosaonn t an dshuiteil.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Comhaont um Cheadnas" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Dan inichadh ar choinnollacha an cheadnais sula suitelann t $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "M ghlacann t le coinnollacha an chomhaontaithe, cliceil $\"Glacaim Leis$\" chun leanint ar aghaidh. Caithfidh t glacadh leis an gcomhaont chun $(^NameDA) a shuiteil." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "M ghlacann t le coinnollacha an chomhaontaithe, cliceil an ticbhosca thos. Caithfidh t glacadh leis an gcomhaont chun $(^NameDA) a shuiteil. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "M ghlacann t le coinnollacha an chomhaontaithe, roghnaigh an chad rogha thos. Caithfidh t glacadh leis an gcomhaont chun $(^NameDA) a dhshuiteil. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Comhaont um Cheadnas" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Dan inichadh ar choinnollacha an cheadnais sula ndshuitelann t $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "M ghlacann t le coinnollacha an chomhaontaithe, cliceil $\"Glacaim Leis$\" chun leanint ar aghaidh. Caithfidh t glacadh leis an gcomhaont chun $(^NameDA) a dhshuiteil." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "M ghlacann t le coinnollacha an chomhaontaithe, cliceil an ticbhosca thos. Caithfidh t glacadh leis an gcomhaont chun $(^NameDA) a dhshuiteil. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "M ghlacann t le coinnollacha an chomhaontaithe, roghnaigh an chad rogha thos. Caithfidh t glacadh leis an gcomhaont chun $(^NameDA) a dhshuiteil. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Brigh $\"Page Down$\" chun an chuid eile den cheadnas a lamh." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Roghnaigh Comhphirteanna" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Roghnaigh na gnithe $(^NameDA) ba mhaith leat suiteil." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Roghnaigh Comhphirteanna" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Roghnaigh na gnithe $(^NameDA) ba mhaith leat dshuiteil." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Cur Sos" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Cuir do luch os cionn comhphirte chun cur sos a fheiceil." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Cuir do luch os cionn comhphirte chun cur sos a fheiceil." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Roghnaigh Suomh na Suitela" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Roghnaigh an fillten inar mian leat $(^NameDA) a shuiteil." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Roghnaigh Suomh na Dshuitela" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Roghnaigh an fillten ar mian leat $(^NameDA) a dhshuiteil as." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE " Shuiteil" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Fan go fill; $(^NameDA) shuiteil." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Suiteil Crochnaithe" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "D'irigh leis an tsuiteil." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Suiteil Tobscortha" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Nor irigh leis an tsuiteil." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE " Dhshuiteil" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Fan go fill; $(^NameDA) dhshuiteil." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Dshuiteil Crochnaithe" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "D'irigh leis an dshuiteil." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Dshuiteil Tobscortha" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Nor irigh leis an dshuiteil." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Draoi Suitela $(^NameDA) Chrochn" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Suiteladh $(^NameDA) ar do romhaire.$\r$\n$\r$\nCliceil $\"Crochnaigh$\" chun an draoi seo a dhnadh." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "N mr duit do romhaire a atos chun suiteil $(^NameDA) a chur i gcrch. Ar mhaith leat atos anois?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Draoi Dshuitela $(^NameDA) Chrochn" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Dshuiteladh $(^NameDA) do romhaire.$\r$\n$\r$\nCliceil $\"Crochnaigh$\" chun an draoi seo a dhnadh." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "N mr duit do romhaire a atos chun dshuiteil $(^NameDA) a chur i gcrch. Ar mhaith leat atos anois?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Atosaigh anois" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Atosidh m de limh nos dana" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Rith $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Tai&spein comhad README" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Crochnaigh" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Roghnaigh Fillten sa Roghchlr Tosaigh" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Roghnaigh fillten sa Roghchlr Tosaigh a gcuirfear aicearra $(^NameDA) ann." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Roghnaigh an fillten sa Roghchlr Tosaigh inar mian leat aicearra an chlir a chruth. Is fidir freisin fillten nua a chruth tr ainm nua a iontril." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "N cruthaigh aicearra" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Dshuiteil $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Bain $(^NameDA) do romhaire." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "An bhfuil t cinnte gur mian leat Suitela $(^Name) a scor?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "An bhfuil t cinnte gur mian leat Dshuitela $(^Name) a scor?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Italian.nlf b/installer/NSIS/Contrib/Language files/Italian.nlf new file mode 100644 index 0000000..7b48362 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Italian.nlf @@ -0,0 +1,192 @@ +# Header, don't edit +NLF v6 +# Language ID +1040 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation orginally started by Orfanik - http://www.orfanik.hu +# Updated v2 to v6 by Alessandro Staltari < staltari (a) geocities.com > +# corrected by < mdalco@gmail.com > +# ^Branding +Sistema di installazione Nullsoft %s +# ^SetupCaption +Installazione di $(^Name) +# ^UninstallCaption +Disinstallazione di $(^Name) +# ^LicenseSubCaption +: Licenza d'uso +# ^ComponentsSubCaption +: Opzioni di installazione +# ^DirSubCaption +: Cartella di installazione +# ^InstallingSubCaption +: Installazione dei file +# ^CompletedSubCaption +: Completata +# ^UnComponentsSubCaption +: Opzioni di disinstallazione +# ^UnDirSubCaption +: Cartella di disinstallazione +# ^ConfirmSubCaption +: Conferma +# ^UninstallingSubCaption +: Rimozione dei file +# ^UnCompletedSubCaption +: Completata +# ^BackBtn +< &Indietro +# ^NextBtn +&Avanti > +# ^AgreeBtn +&Accetto +# ^AcceptBtn +&Accetto le condizioni della licenza +# ^DontAcceptBtn +&Non accetto le condizioni della licenza +# ^InstallBtn +Ins&talla +# ^UninstallBtn +&Disinstalla +# ^CancelBtn +Annulla +# ^CloseBtn +&Fine +# ^BrowseBtn +S&foglia... +# ^ShowDetailsBtn +Mostra &dettagli +# ^ClickNext +Per proseguire, scegliere Avanti. +# ^ClickInstall +Per avviare l'installazione, selezionare Installa. +# ^ClickUninstall +Per avviare la disinstallazione, selezionare Disinstalla. +# ^Name +Nome +# ^Completed +Completata +# ^LicenseText +Leggere la licenza prima di procedere con l'installazione di $(^NameDA). Se si accettano le condizioni di licenza, selezionare Accetto. +# ^LicenseTextCB +Leggere la licenza prima di procedere con l'installazione di $(^NameDA). Se si accettano pienamente le condizioni di licenza, selezionare la casella sottostante. $_CLICK +# ^LicesnseTextRB +Leggere la licenza prima di procedere con l'installazione di $(^NameDA). Se si accettano pienamente le condizioni di licenza, selezionare la prima delle opzioni sottoindicate. $_CLICK +# ^UnLicenseText +Leggere la licenza prima di procedere con la disinstallazione di $(^NameDA). Se si accettano pienamente le condizioni di licenza, selezionare Accetto. $_CLICK +# ^UnLicenseTextCB +Leggere la licenza prima di procedere con la disinstallazione di $(^NameDA). Se si accettano pienamente le condizioni di licenza, selezionare la casella sottostante. $_CLICK +# ^UnLicesnseTextRB +Leggere la licenza prima di procedere con la disinstallazione di $(^NameDA). Se si accettano pienamente le condizioni di licenza, selezionare la prima delle opzioni sottoindicate. $_CLICK +# ^Custom +Personalizzata +# ^ComponentsText +Selezionare i componenti che si desidera installare. +# ^ComponentsSubText1 +Selezionare il tipo d'installazione: +# ^ComponentsSubText2_NoInstTypes +Selezionare i componenti da installare: +# ^ComponentsSubText2 +Oppure, selezionare i componenti opzionali che si desidera installare: +# ^UnComponentsText +Selezionare i componenti che si desidera disinstallare. +# ^UnComponentsSubText1 +Selezionare il tipo di disinstallazione: +# ^UnComponentsSubText2_NoInstTypes +Selezionare i componenti da disinstallare: +# ^UnComponentsSubText2 +Oppure, selezionare i componenti opzionali che si desidera disinstallare : +# ^DirText +Questa procedura installer $(^NameDA) nella cartella seguente. Per installare in una cartella diversa, selezionare Sfoglia e sceglierne un'altra. $_CLICK +# ^DirSubText +Cartella di destinazione +# ^DirBrowseText +Selezionare la cartella dove installare $(^NameDA): +# ^UnDirText +Questa procedura disinstaller $(^NameDA) nella cartella seguente. Per disinstallare da una cartella diversa, selezionare Sfoglia e sceglierene un'altra. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Selezionare la cartella dalla quale disinstallare $(^NameDA): +# ^SpaceAvailable +"Spazio disponibile: " +# ^SpaceRequired +"Spazio richiesto: " +# ^UninstallingText +$(^NameDA) verr disinstallato dalla cartella seguente. $_CLICK +# ^UninstallingSubText +Rimozione da: +# ^FileError +Errore nell'apertura del file per la scrittura: \r\n\t"$0"\r\nSeleziona Termina per interrompere l'installazione,\r\nsu Riprova per ritentare, oppure\r\nsu Ignora per saltare questo file. +# ^FileError_NoIgnore +Errore nell'apertura del file per la scrittura: \r\n\t"$0"\r\nSeleziona Riprova per ritentare, oppure\r\nsu Termina per interrompere l'installazione +# ^CantWrite +"Impossibile scrivere: " +# ^CopyFailed +Copia fallita +# ^CopyTo +"Copia in " +# ^Registering +"Registrazione in corso: " +# ^Unregistering +"Deregistrazione in corso: " +# ^SymbolNotFound +"Impossibile trovare il simbolo: " +# ^CouldNotLoad +"Impossibile caricare: " +# ^CreateFolder +"Crea cartella: " +# ^CreateShortcut +"Crea collegamento: " +# ^CreatedUninstaller +"Creato il programma di disinstallazione: " +# ^Delete +"Elimina file: " +# ^DeleteOnReboot +"Elimina al riavvio: " +# ^ErrorCreatingShortcut +"Errore nella creazione del collegamento: " +# ^ErrorCreating +"Errore nella creazione: " +# ^ErrorDecompressing +Errore nella decompressione dei dati! Programma di installazione corrotto? +# ^ErrorRegistering +Errore nella registrazione della DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Esecuzione: " +# ^Extract +"Estrazione: " +# ^ErrorWriting +"Estrazione: errore nella scrittura sul file " +# ^InvalidOpcode +Programma di installazione corrotto: opcode non valido +# ^NoOLE +"Nessuna OLE per: " +# ^OutputFolder +"Cartella di destinazione: " +# ^RemoveFolder +"Rimuovi cartella: " +# ^RenameOnReboot +"Rinomina al riavvio: " +# ^Rename +Rinomina +# ^Skipped +"Saltato: " +# ^CopyDetails +Copia i dettagli negli appunti +# ^LogInstall +Log del processo di installazione +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Italian.nsh b/installer/NSIS/Contrib/Language files/Italian.nsh new file mode 100644 index 0000000..8f07847 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Italian.nsh @@ -0,0 +1,131 @@ +;Language: Italian (1040) +;By SANFACE Software v1.67 accents +;Review and update from v1.65 to v1.67 by Alessandro Staltari < staltari (a) geocities.com > +;Review and update from v1.67 to v1.68 by Lorenzo Bevilacqua < meow811@libero.it > + +!insertmacro LANGFILE "Italian" "Italiano" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Benvenuti nel programma di installazione di $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Questo programma installer $(^NameDA) nel vostro computer.$\r$\n$\r$\nSi raccomanda di chiudere tutte le altre applicazioni prima di iniziare l'installazione. Questo permetter al programma di installazione di aggiornare i file di sistema senza dover riavviare il computer.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Benvenuti nella procedura guidata di disinstallazione di $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Questa procedura vi guider nella disinstallazione di $(^NameDA).$\r$\n$\r$\nPrima di iniziare la disinstallazione, assicuratevi che $(^Name) non sia in esecuzione.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licenza d'uso" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Prego leggere le condizioni della licenza d'uso prima di installare $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Se si accettano i termini della licenza d'uso scegliere Accetto per continuare. necessario accettare i termini della licenza d'uso per installare $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se si accettano i termini della licenza d'uso, selezionare la casella sottostante. necessario accettare i termini della licenza d'uso per installare $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se si accettano i termini della licenza d'uso, selezionare la prima opzione sottostante. necessario accettare i termini della licenza d'uso per installare $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licenza d'uso" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Prego leggere le condizioni della licenza d'uso prima di disinstallare $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Se si accettano i termini della licenza d'uso scegliere Accetto per continuare. necessario accettare i termini della licenza d'uso per disinstallare $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se si accettano i termini della licenza d'uso, selezionare la casella sottostante. necessario accettare i termini della licenza d'uso per disinstallare $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se si accettano i termini della licenza d'uso, selezionare la prima opzione sottostante. necessario accettare i termini della licenza d'uso per disinstallare $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Premere Page Down per vedere il resto della licenza d'uso." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Selezione dei componenti" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Selezionare i componenti di $(^NameDA) che si desidera installare." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Selezione componenti" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Scegliere i componenti di $(^NameDA) che si desidera disinstallare." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descrizione" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posizionare il puntatore del mouse sul componente per vederne la descrizione." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posizionare il puntatore del mouse sul componente per vederne la descrizione." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Scelta della cartella di installazione" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Scegliere la cartella nella quale installare $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Scelta della cartella da cui disinstallare" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Scegliere la cartella dalla quale disinstallare $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installazione in corso" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Prego attendere mentre $(^NameDA) viene installato." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installazione completata" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "L'installazione stata completata con successo." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installazione interrotta" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "L'installazione non stata completata correttamente." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Disinstallazione in corso" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Prego attendere mentre $(^NameDA) viene disinstallato." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Disinstallazione completata" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "La disinstallazione stata completata con successo." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Disinstallazione interrotta" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "La disintallazione non stata completata correttamente." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Completamento dell'installazione di $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) stato installato sul vostro computer.$\r$\n$\r$\nScegliere Fine per chiudere il programma di installazione." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Il computer deve essere riavviato per completare l'installazione di $(^NameDA). Vuoi riavviarlo ora?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Completamento della disinstallazione di $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) stato disinstallato dal computer.$\r$\n$\r$\nSelezionare Fine per terminare questa procedura." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Il computer deve essere riavviato per completare l'installazione di $(^NameDA). Vuoi riavviarlo ora?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Riavvia adesso" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Preferisco riavviarlo manualmente pi tardi" + ${LangFileString} MUI_TEXT_FINISH_RUN "Esegui $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Mostra il file Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Fine" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Scelta della cartella del menu Start" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Scegliere una cartella del menu Start per i collegamenti del programma." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Scegliere la cartella del menu Start in cui verranno creati i collegamenti del programma. possibile inserire un nome per creare una nuova cartella." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Non creare i collegamenti al programma." +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Disinstalla $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Rimuove $(^NameDA) dal computer." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Sei sicuro di voler interrompere l'installazione di $(^Name) ?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Sei sicuro di voler interrompere la disinstallazione di $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Scelta degli Utenti" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Scegliete per quali utenti volete installare $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Scegliete se volete installare $(^NameDA) solo per voi o per tutti gli utenti di questo sistema. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Installazione per tutti gli utenti" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Installazione personale" +!endif diff --git a/installer/NSIS/Contrib/Language files/Japanese.nlf b/installer/NSIS/Contrib/Language files/Japanese.nlf new file mode 100644 index 0000000..3045126 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Japanese.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1041 +# Font and size - dash (-) means default +lr oSVbN +9 +# Codepage - dash (-) means ANSI code page +932 +# RTL - anything else than RTL means LTR +- +# Translation by Dnanako, updated by Takahiro Yoshimura +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) ZbgAbv +# ^UninstallCaption +$(^Name) ACXg[ +# ^LicenseSubCaption +FCZX_ +# ^ComponentsSubCaption +FCXg[ IvV +# ^DirSubCaption +FCXg[ tH_ +# ^InstallingSubCaption +FCXg[ +# ^CompletedSubCaption +F +# ^UnComponentsSubCaption +: ACXg[ IvV +# ^UnDirSubCaption +: ACXg[ tH_ +# ^ComfirmSubCaption +FmF +# ^UninstallingSubCaption +FACXg[ +# ^UnCompletedSubCaption +F +# ^BackBtn +< ߂(&B) +# ^NextBtn +(&N) > +# ^AgreeBtn +ӂ(&A) +# ^AcceptBtn +̃CZX_񏑂ɓӂ܂(&A) +# ^DontAcceptBtn +̃CZX_񏑂ɂ͓ӂł܂(&D) +# ^InstallBtn +CXg[ +# ^UninstallBtn +ݲݽİ(&U) +# ^CancelBtn +LZ +# ^CloseBtn +‚(&C) +# ^BrowseBtn +Q(&R)... +# ^ShowDetailsBtn +ڍׂ\(&D) +# ^ClickNext +ɂ [] NbNĉB +# ^ClickInstall +CXg[n߂ɂ [CXg[] NbNĉB +# ^ClickUninstall +ACXg[n߂ɂ [ݲݽİ] NbNĉB +# ^Name +AvP[V +# ^Completed + +# ^LicenseText +$(^NameDA)CXg[OɁACZX_񏑂mFĉB_񏑂̑SĂ̏ɓӂȂ΁A[ӂ] {^NbNĉB +# ^LicenseTextCB +$(^NameDA)CXg[OɁACZX_񏑂mFĉB_񏑂̑SĂ̏ɓӂȂ΁Ã`FbN{bNXNbNĉB $_CLICK +# ^LicenseTextRB +$(^NameDA)CXg[OɁACZX_񏑂mFĉB_񏑂̑SĂ̏ɓӂȂ΁Aɕ\ĂIvV̂Aŏ̂̂IʼnB $_CLICK +# ^UnLicenseText +$(^NameDA)ACXg[OɁACZX_񏑂mFĉB_񏑂̑SĂ̏ɓӂȂ΁A[ӂ] {^NbNĉB +# ^UnLicenseTextCB +$(^NameDA)ACXg[OɁACZX_񏑂mFĉB_񏑂̑SĂ̏ɓӂȂ΁Ã`FbN{bNXNbNĉB $_CLICK +# ^UnLicenseTextRB +$(^NameDA)ACXg[OɁACZX_񏑂mFĉB_񏑂̑SĂ̏ɓӂȂ΁Aɕ\ĂIvV̂Aŏ̂̂IʼnB $_CLICK +# ^Custom +JX^ +# ^ComponentsText +CXg[R|[lgɃ`FbNtĉBsvȂ̂ɂ‚ẮA`FbNOĉB $_CLICK +# ^ComponentsSubText1 +CXg[ ^CvIF +# ^ComponentsSubText2_NoInstTypes +CXg[ R|[lgIF +# ^ComponentsSubText2 +܂́ACXg[ IvV R|[lgIF +# ^UnComponentsText +ACXg[R|[lgɃ`FbNtĉBłȂ̂ɂ‚ẮA`FbNOĉB $_CLICK +# ^UnComponentsSubText1 +ACXg[ ^CvIF +# ^UnComponentsSubText2_NoInstTypes +ACXg[ R|[lgIF +# ^UnComponentsSubText2 +܂́AACXg[ IvV R|[lgIF +# ^DirText +$(^NameDA)ȉ̃tH_ɃCXg[܂BقȂtH_ɃCXg[ɂ́A[Q] āAʂ̃tH_IĂB $_CLICK +# ^DirSubText +CXg[ tH_ +# ^DirBrowseText +$(^NameDA)CXg[tH_IĂF +# ^UnDirText +$(^NameDA)ȉ̃tH_ACXg[܂BقȂtH_ACXg[ɂ́A[Q] āAʂ̃tH_IĂB $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +$(^NameDA)ACXg[tH_IĂF +# ^SpaceAvailable +p”\ȃfBXNXy[XF +# ^SpaceRequired +KvȃfBXNXy[XF +# ^UninstallingText +$(^NameDA)́Aȉ̃tH_ACXg[܂B $_CLICK +# ^UninstallingSubText +ACXg[F +# ^FileError +t@C̍쐬G[F\r\n\t"$0"\r\nCXg[𒆎~ɂ͒~,\r\nĂт̃t@C̍쐬݂ɂ͍Ďs, ܂\r\ñt@CXLbvđɂ͖Ă +# ^FileError_NoIgnore +t@C̍쐬G[: \r\n\t"$0"\r\nĂт̃t@C̍쐬݂ɂ͍Ďs, ܂\r\nCXg[𒆎~ɂ̓LZĉ +# ^CantWrite +쐬ł܂F +# ^CopyFailed +Rs[͎s܂ +# ^CopyTo +Rs[܂ +# ^Registering +o^: +# ^Unregistering +o^: +# ^SymbolNotFound +V{‚邱Ƃł܂F +# ^CouldNotLoad +[h邱Ƃł܂F +# ^CreateFolder +tH_̍쐬F +# ^CreateShortcut +V[gJbg̍쐬F +# ^CreatedUninstaller +ACXg[̍쐬F +# ^Delete +t@C̍폜F +# ^DeleteOnReboot +u[gɍ폜F +# ^ErrorCreatingShortcut +V[gJbg̍쐬G[F +# ^ErrorCreating +쐬G[F +# ^ErrorDecompressing +f[^̒oG[\r\n\r\nCXg[jĂ܂B +# ^ErrorRegistering +DLL̓o^G[ +# ^ExecShell +gq̊֘Ats: +# ^Execute +sF +# ^Extract +oF +# ^ErrorWriting +oFt@C쐬G[ +# ^InvalidOpcode +CXg[̕sFopcode +# ^NoOLE +OLE܂F +# ^OutputFolder +o͐tH_F +# ^RemoveFolder +tH_̍폜F +# ^RenameOnReboot +u[gɖO̕ύXF +# ^Rename +O̕ύXF +# ^Skipped +XLbvF +# ^CopyDetails +Nbv{[h֏ڍׂRs[ +# ^LogInstall +CXg[vZXOwL^ +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Japanese.nsh b/installer/NSIS/Contrib/Language files/Japanese.nsh new file mode 100644 index 0000000..c42e0ab --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Japanese.nsh @@ -0,0 +1,122 @@ +;Language: Japanese (1041) +;By Dnanako +;Translation updated by Takahiro Yoshimura + +!insertmacro LANGFILE "Japanese" "Japanese" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "$(^NameDA) ZbgAbv EBU[hւ悤" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "̃EBU[h́A$(^NameDA)̃CXg[KChĂ܂B$\r$\n$\r$\nZbgAbvJnOɁÂׂẴAvP[VI邱Ƃ𐄏܂BɂăZbgAbvRs[^ċNɁAVXe t@CXV邱Ƃo悤ɂȂ܂B$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "$(^NameDA) ACXg[ EBU[hւ悤" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "̃EBU[h́A$(^NameDA)̃ACXg[KChĂ܂B$\r$\n$\r$\nACXg[JnOɁA$(^NameDA)NĂȂƂmFĉB$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "CZX_" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "$(^NameDA)CXg[OɁACZXmFĂB" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "_񏑂ׂ̂Ă̏ɓӂȂ΁A[ӂ] IŃCXg[𑱂ĂB$(^NameDA) CXg[ɂ́A_񏑂ɓӂKv܂B" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "_񏑂ׂ̂Ă̏ɓӂȂ΁Ã`FbN{bNXNbNĂB$(^NameDA) CXg[ɂ́A_񏑂ɓӂKv܂B $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "_񏑂ׂ̂Ă̏ɓӂȂ΁Aɕ\ĂIvV̂Aŏ̂̂IʼnB$(^NameDA) CXg[ɂ́A_񏑂ɓӂKv܂B $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "CZX_" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "$(^NameDA)ACXg[OɁACZXmFĂB" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "_񏑂ׂ̂Ă̏ɓӂȂ΁A[ӂ] IŃACXg[𑱂ĂB$(^NameDA) ACXg[ɂ́A_񏑂ɓӂKv܂B" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "_񏑂ׂ̂Ă̏ɓӂȂ΁Ã`FbN{bNXNbNĂB$(^NameDA) ACXg[ɂ́A_񏑂ɓӂKv܂B $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "_񏑂ׂ̂Ă̏ɓӂȂ΁Aɕ\ĂIvV̂Aŏ̂̂IʼnB$(^NameDA) ACXg[ɂ́A_񏑂ɓӂKv܂B $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "[Page Down]Č_񏑂ׂĂǂ݂B" +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "R|[lgIłB" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "$(^NameDA)̃CXg[ IvVIłB" +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "R|[lgIłB" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "$(^NameDA)̃ACXg[ IvVIłB" +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "R|[lg̏Ƀ}EX J[\ړƁAɐ\܂B" + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "R|[lg̏Ƀ}EX J[\ړƁAɐ\܂B" + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "CXg[IłB" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "$(^NameDA)CXg[tH_IłB" +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "ACXg[IłB" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "$(^NameDA)ACXg[tH_IłB" +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "CXg[" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "$(^NameDA)CXg[Ă܂B΂炭҂B" + ${LangFileString} MUI_TEXT_FINISH_TITLE "CXg[̊" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "CXg[ɐ܂B" + ${LangFileString} MUI_TEXT_ABORT_TITLE "CXg[̒~" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "ZbgAbv͐Ɋ܂łB" +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "ACXg[" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "$(^NameDA)ACXg[Ă܂B΂炭҂B" + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "ACXg[̊" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "ACXg[ɐ܂B" + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "ACXg[̒~" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "ACXg[͐Ɋ܂łB" +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "$(^NameDA) ZbgAbv EBU[h͊܂B" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA)́ÃRs[^ɃCXg[܂B$\r$\n$\r$\nEBU[h‚ɂ [] ĂB" + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "$(^NameDA) ̃CXg[ɂ́ÃRs[^ċNKv܂BċN܂H" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "$(^NameDA) ACXg[ EBU[h͊܂B" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA)́ÃRs[^ACXg[܂B$\r$\n$\r$\nEBU[h‚ɂ [] ĂB" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "$(^NameDA) ̃ACXg[ɂ́ÃRs[^ċNKv܂BċN܂H" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "ċN" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ŏ蓮ōċN" + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA)s(&R)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Readme \(&S)" + ${LangFileString} MUI_BUTTONTEXT_FINISH "(&F)" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "X^[gj[ tH_IłB" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "$(^NameDA)̃V[gJbg쐬X^[gj[ tH_IʼnB" + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "̃vÕV[gJbg쐬X^[gj[ tH_IĂB܂A쐬VtH_ɖO‚邱Ƃł܂B" + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "V[gJbg쐬Ȃ" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "$(^NameDA)̃ACXg[" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA)̃Rs[^폜܂B" +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "$(^Name) ZbgAbv𒆎~܂H" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "$(^Name) ACXg[𒆎~܂H" +!endif diff --git a/installer/NSIS/Contrib/Language files/Korean.nlf b/installer/NSIS/Contrib/Language files/Korean.nlf new file mode 100644 index 0000000..b9d5a0f --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Korean.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1042 +# Font and size - dash (-) means default + +9 +# Codepage - dash (-) means ANSI code page +949 +# RTL - anything else than RTL means LTR +- +# Translation by dTomoyo dtomoyo@empal.com ( ~V2.0 BETA2 ) / By hardkoder@gmail.com (V2.0 BETA3 ~ ) +# ^Branding +μƮ ġ ý %s +# ^SetupCaption +$(^Name) ġ +# ^UninstallCaption +$(^Name) +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: ġ ɼ +# ^DirSubCaption +: +# ^InstallingSubCaption +: ġ +# ^CompletedSubCaption +: ġ Ϸ +# ^UnComponentsSubCaption +: ɼ +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: Ȯ +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: Ϸ +# ^BackBtn +< ڷ +# ^NextBtn + > +# ^AgreeBtn + +# ^AcceptBtn + ׿ մϴ. +# ^DontAcceptBtn + ʽϴ. +# ^InstallBtn +ġ +# ^UninstallBtn + +# ^CancelBtn + +# ^CloseBtn + +# ^BrowseBtn +ãƺ... +# ^ShowDetailsBtn +ڼ +# ^ClickNext +Ͻ÷ '' ư ּ. +# ^ClickInstall +ġ Ͻ÷ 'ġ' ư ּ. +# ^ClickUninstall +'' ư Ű ۵˴ϴ. +# ^Name +̸ +# ^Completed +Ϸ +# ^LicenseText +$(^NameDA)() ġϱ 캸ñ ٶϴ. 뿡 ϼ̴ٸ '' ּ. +# ^LicenseTextCB +$(^NameDA)() ġϱ 캸ñ ٶϴ. 뿡 ϼ̴ٸ Ʒ üũ ּ. $_CLICK +# ^LicesnseTextRB +$(^NameDA)() ġϱ 캸ñ ٶϴ. 뿡 ϼ̴ٸ Ʒ ɼ ּ. $_CLICK +# ^UnLicenseText +$(^NameDA)() ϱ 캸ñ ٶϴ. 뿡 ϼ̴ٸ '' ּ. +# ^UnLicenseTextCB +$(^NameDA)() ϱ 캸ñ ٶϴ. 뿡 ϼ̴ٸ Ʒ üũ ּ. $_CLICK +# ^UnLicesnseTextRB +$(^NameDA)() ϱ 캸ñ ٶϴ. 뿡 ϼ̴ٸ Ʒ ɼ ּ. $_CLICK +# ^Custom + +# ^ComponentsText +ġ Ͻô Ҹ Ͽ ֽñ ٶϴ. $_CLICK +# ^ComponentsSubText1 +ġ : +# ^ComponentsSubText2_NoInstTypes +ġϷ : +# ^ComponentsSubText2 + : +# ^UnComponentsText +Ÿ ϴ Ҹ üũ ֽñ ٶϴ. $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes +Ϸ : +# ^UnComponentsSubText2 +Ϸ : +# ^DirText +$(^NameDA)() ġ Դϴ. \r\nٸ ġϰ ø 'ãƺ' ư ٸ ּ. $_CLICK +# ^DirSubText +ġ +# ^DirBrowseText +$(^NameDA)() ġմϴ: +# ^UnDirText +$(^NameDA)() Դϴ. \r\nٸ ϰ ø 'ãƺ' ư ٸ ּ. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +$(^NameDA)() մϴ: +# ^SpaceAvailable +" ũ : " +# ^SpaceRequired +"ʿ ũ : " +# ^UninstallingText +ýۿ $(^NameDA)() Դϴ. $_CLICK +# ^UninstallingText + : +# ^FileError + ϴ.: \r\n\t"$0"\r\n'ߴ' ġ ϰų,\r'ٽ õ' ٽ õ ų,\r'' dz ټ. +# ^FileError_NoIgnore + ϴ.: \r\n\t"$0"\r\n'ٽ õ' ٽ õ ų,\r'' ġ ϼ. +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +": " +# ^Unregistering +" : " +# ^SymbolNotFound +"ɺ ã : " +# ^CouldNotLoad +"ҷ : " +# ^CreateFolder +" : " +# ^CreateShortcut +"ٷ : " +# ^CreatedUninstaller +"ν緯 : " +# ^Delete +" : " +# ^DeleteOnReboot +"ý : " +# ^ErrorCreatingShortcut +"ٷ : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ߻! ġ ջǾϴ. +# ^ErrorRegistering +DLL +# ^ExecShell +" : " +# ^Exec +": " +# ^Extract +" : " +# ^ErrorWriting +" : ϴ ߻ " +# ^InvalidOpcode +ν緯 ջ: ߸ ڵ +# ^NoOLE +"OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +"ý ̸ : " +# ^Rename +"̸ : " +# ^Skipped +"dzʶ: " +# ^CopyDetails +ڼ Ŭ +# ^LogInstall +ġ α ۼ +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Korean.nsh b/installer/NSIS/Contrib/Language files/Korean.nsh new file mode 100644 index 0000000..71113be --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Korean.nsh @@ -0,0 +1,121 @@ +;Language: Korean (1042) +;By linak linak@korea.com ( ~ V2.0 BETA3 ) By kippler@gmail.com(www.kipple.pe.kr) ( V2.0 BETA3 ~ ) (last update:2007/09/05) + +!insertmacro LANGFILE "Korean" "Korean" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "$(^NameDA) ġ մϴ." + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " α׷ ǻͿ $(^NameDA)() ġ Դϴ.$\r$\n$\r$\nġ ϱ α׷ Ͽ ֽñ ٶϴ. ̴ ʰ ý ְ ݴϴ.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "$(^NameDA) Ÿ մϴ." + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " α׷ ǻͿ $(^NameDA)() Դϴ.$\r$\n$\r$\nŸ ϱ $(^NameDA)() Ͽ ֽñ ٶϴ.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "$(^NameDA)() ġϽñ 캸ñ ٶϴ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "뿡 ϼ̴ٸ '' ּ. $(^NameDA)() ġϱ ؼ ݵ 뿡 ϼž մϴ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "뿡 ϼ̴ٸ Ʒ ּ. $(^NameDA)() ġϱ ؼ ݵ 뿡 ϼž մϴ. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "뿡 ϼ̴ٸ ù ° ּ. $(^NameDA)() ġϱ ؼ ݵ 뿡 ϼž մϴ. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "$(^NameDA)() Ͻñ 캸ñ ٶϴ." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "뿡 ϼ̴ٸ '' ּ. $(^NameDA)() ϱ ؼ ݵ 뿡 ϼž մϴ." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "뿡 ϼ̴ٸ Ʒ ּ. $(^NameDA)() ϱ ؼ ݵ 뿡 ϼž մϴ. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "뿡 ϼ̴ٸ ù ° ּ. $(^NameDA)() ϱ ؼ ݵ 뿡 ϼž մϴ. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " κ ÷ [Page Down] Ű ּ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "ġϰ ϴ $(^NameDA) Ҹ ּ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "ϰ ϴ $(^NameDA) Ҹ ּ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE " " + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " κп 콺 ÷." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " κп 콺 ÷." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "ġ ġ " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "$(^NameDA)() ġ ּ." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " ġ " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "$(^NameDA)() ּ." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "ġ" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "$(^NameDA)() ġϴ ٷ ּ." + ${LangFileString} MUI_TEXT_FINISH_TITLE "ġ Ϸ" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "ġ ϷǾϴ." + ${LangFileString} MUI_TEXT_ABORT_TITLE "ġ " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "α׷ ġ ҵǾϴ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "$(^NameDA)() ϴ ٷ ֽñ ٶϴ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " ħ" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "α׷ Ͽϴ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "α׷ " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "α׷ Ű ҵǾϴ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "$(^NameDA) ġ Ϸ" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) ġ ϷǾϴ. ġ α׷ ġ 'ħ' ư ּ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "$(^NameDA) ġ Ϸϱ ؼ ý ٽ ؾ մϴ. Ͻðڽϱ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " Ϸ" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) Ű Ϸ Ǿϴ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "$(^NameDA) Ÿ Ϸϱ ؼ ý ٽ ؾ մϴ. Ͻðڽϱ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " ϰڽϴ." + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "߿ ϰڽϴ." + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) ϱ(&R)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Readme (&S)" + ${LangFileString} MUI_BUTTONTEXT_FINISH "ħ" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " ޴ " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "α׷ ٷ ޴ ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "α׷ ٷ ޴ ϼ. ο Ϸ ̸ Էϼ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "ٷ ʰڽϴ." +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "$(^NameDA) " + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA) ϱ" +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "$(^Name) ġ Ͻðڽϱ?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "$(^Name) Ÿ Ͻðڽϱ?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Kurdish.nlf b/installer/NSIS/Contrib/Language files/Kurdish.nlf new file mode 100644 index 0000000..8716a1d --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Kurdish.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID (none exists for Kurdish at this time) +9999 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +- +# RTL - anything else than RTL means LTR +- +# Translation by Rzan Tovjn(chagy) (retovjin@hotmail.com.com) +# ^Branding +Nullsoft Pergala Sazkirin %s +# ^SetupCaption +$(^Name) Sazkirin +# ^UninstallCaption +$(^Name) Rakirin +# ^LicenseSubCaption +: Peymana Lsans +# ^ComponentsSubCaption +: Vebijrkn Sazkirin +# ^DirSubCaption +: Peldanka Sazkirin +# ^InstallingSubCaption +: T Sazkirin +# ^CompletedSubCaption +: Qediya +# ^UnComponentsSubCaption +: Vebijrkn Rakirin +# ^UnDirSubCaption +: Peldanka Rakirin +# ^ConfirmSubCaption +: Erkirin +# ^UninstallingSubCaption +: T Rakirin +# ^UnCompletedSubCaption +: Qediya +# ^BackBtn +< &Vegere +# ^NextBtn +&Bidomne > +# ^AgreeBtn +&Ez Dipejirnim +# ^AcceptBtn +ertn Peyman &Dipejirnim +# ^DontAcceptBtn +ertn Peyman Napejirnim +# ^InstallBtn +&Saz Bike +# ^UninstallBtn +&Rake +# ^CancelBtn +Betal +# ^CloseBtn +&Bigire +# ^BrowseBtn +&avlgern... +# ^ShowDetailsBtn +Hragahiyan &Nan Bide +# ^ClickNext +Ji bo berdewam 'Bidomne'y bitikne. +# ^ClickInstall +Ji bo destpka sazkirin 'Saz Bike'y bitikne. +# ^ClickUninstall +Ji bo destpka rakirin 'Rake' bitikne. +# ^Name +nav +# ^Completed +Qediya +# ^LicenseText +Ji kerema xwe re ber tu bernameya $(^NameDA) saz bik, avek li peymana lsans bigerne. Heke tu hem ertn peyman dipejirn, 'Ez Dipejirnim' bitikne. +# ^LicenseTextCB +Ji kerema xwe re ber tu bernameya $(^NameDA) saz bik, avek li peymana lsans bigerne. Heke tu hem ertan dipejirn, zeviya erkirin ya jrn dagire. $_CLICK +# ^LicenseTextRB +Ji kerema xwe re ber tu bernameya $(^NameDA) saz bik avek li peymana lsans bigerne. Heke tu hem ertn peyman dipejirn, zeviya vebijrk ya jrn dagire. $_CLICK +# ^UnLicenseText +Ji kerema xwe re ber tu bernameya $(^NameDA) rak, avek li peymana lsans bigerne. Heke tu hem ertn peyman dipejirn, 'Ez Dipejirnim' bitikn. +# ^UnLicenseTextCB +Ji kerema xwe re ber tu bernameya $(^NameDA) ji pergala xwe rak, avek li peymana lsans bigerne. Heke tu hem ertn peyman dipejirn, zeviya jrn a erkirin dagire. $_CLICK +# ^UnLicenseTextRB +Ji kerema xwe re ber tu bernameya $(^NameDA) ji pergala xwe rak, avek li peymana lsans bigerne. Heke tu hem ertn peyman dipejirn, zeviya vebijrk ya jrn dagire. $_CLICK +# ^Custom +Taybet +# ^ComponentsText +Ben tu dixwaz saz bik hilbijre niqirn 'check' ben tu naxwaz werin sazkirin rake. $_CLICK +# ^ComponentsSubText1 +Away sazkirin hilbijre: +# ^ComponentsSubText2_NoInstTypes +Ben d werin sazkirin hilbijre: +# ^ComponentsSubText2 +an j, ben ben tu dixwaz werin sazkirin hilbijre: +# ^UnComponentsText +Ben tu dixwaz rak hilbijre, an j niqira 'check'a ber ben tu daxwaz were rakirin, rake. $_CLICK +# ^UnComponentsSubText1 +Away rakirin hilbijre: +# ^UnComponentsSubText2_NoInstTypes +Ben d werin rakirin hilbijre: +# ^UnComponentsSubText2 +an j ben tu dixwaz werin rakirin hilbijre: +# ^DirText +$(^NameDA) d ji aliy sazkirin ve li peldanka jrn were sazkirin. Ji bo tu li peldankeke din saz bik 'avlgern' bitikne peldankeke din hilbijre. $_CLICK +# ^DirSubText +Peldanka Armanckir +# ^DirBrowseText +Peldanka tu dixwaz bernameya $(^NameDA) l were sazkirin hilbijre: +# ^UnDirText +$(^NameDA) d ji aliy sazkirin ve ji peldanka jrn were rakirin. Ji bo tu ji peldankeke cuda rak 'avlgern' bitikne peldankeke din hilbijre. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Peldanka tu dixwaz bernameya $(^NameDA) j were rakirin hilbijre: +# ^SpaceAvailable +"Herma vala ku dikare were bikarann: " +# ^SpaceRequired +"Herma vala ya pwist: " +# ^UninstallingText +Bernameya $(^NameDA) d ji peldanka jrn were rakirin. $_CLICK +# ^UninstallingSubText +t rakirin: +# ^FileError +Dosya ji bo nivsandin veneb: \r\n\t"$0"\r\nJi bo destjberdana sazkirin abort' bitikne,\r\nji bo ceribandina ji n ve retry' , an j\r\nji bo tu dosiy tune bihesibn berdewam bik ignore'y bitikne +# ^FileError_NoIgnore +Dosya ji bo nivsandin vebeneb: \r\n\t"$0"\r\nJi bo nivsandina ji n ve retry'y, an j\r\nJi bo destjberdana sazkirin abort' hilbijre +# ^CantWrite +"Nehate Nivsandin: " +# ^CopyFailed +ewtiya Jibergirtin +# ^CopyTo +"Ji Ber Bigire " +# ^Registering +"T Tomarkirin: " +# ^Unregistering +"Tomar T Jbirin: " +# ^SymbolNotFound +"Dawr Nehate Dtin: " +# ^CouldNotLoad +"Nehate Barkirin: " +# ^CreateFolder +"Peldank ke: " +# ^CreateShortcut +"Kineriy ke: " +# ^CreatedUninstaller +"Srbaz Rakirin Hate kirin: " +# ^Delete +"Dosyay J Bibe: " +# ^DeleteOnReboot +"Dema ji n ve dest p kir dosiy j bibe: " +# ^ErrorCreatingShortcut +"Dema kirina kineriy ewt derket: " +# ^ErrorCreating +"ewtiya kirin: " +# ^ErrorDecompressing +Di dema vekirina daneyan de ewt derket! Sazkirina ewt? +# ^ErrorRegistering +ewtiya tomariya DLL +# ^ExecShell +"Qalik Xebat: " +# ^Exec +"Bixebitne: " +# ^Extract +"Veke: " +# ^ErrorWriting +"Veke: Dema li dosiy hate nivsn ewtiyek derket " +# ^InvalidOpcode +Sazkirina Xirabe: koda nerast pkann +# ^NoOLE +"OLE nehate dtin: " +# ^OutputFolder +"Peldanka derketin: " +# ^RemoveFolder +"Peldank j bibe: " +# ^RenameOnReboot +"Dema ji n hate destpkirin ji n ve bi nav bike: " +# ^Rename +"Nav Biguhere: " +# ^Skipped +"Hate gavkirin: " +# ^CopyDetails +Hragahiyan li Pano'y binivse +# ^LogInstall +Pkanna sazkirin li lnska rew binivse +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Kurdish.nsh b/installer/NSIS/Contrib/Language files/Kurdish.nsh new file mode 100644 index 0000000..591736f --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Kurdish.nsh @@ -0,0 +1,122 @@ +;Language: Kurdish +;By Rzan Tovjn +;Updated by Erdal Ronah (erdal.ronahi@gmail.com) + +!insertmacro LANGFILE "Kurdish" "Kurd" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "$(^NameDA) Tu bi xr hat srbaziya sazkirin" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Ev srbaz d di dema sazkirina $(^NameDA) de rberiya te bike.$\r$\n$\r$\nBer tu dest bi sazkirin bik, em pniyar dikin tu hem bernameyn vekir bigir. Bi v reng bey tu komputera ji n ve vek d hinek dosiyn pergal bpirsgirk werin sazkirin.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Tu bi xr hat srbaziya rakirina bernameya $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Ev srbaz ji bo rakirina bernameya $(^NameDA) d alkariya te bike.$\r$\n$\r$\nBer tu dest bi rakirina bernamey bik, bernameyn vekir hemyan bigire. Bi v reng d re tu mecbr namn ku komputera xwe bigir ji n ve veki.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Peymana Lsans" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Ji kerema xwe re ber tu bernameya $(^NameDA) saz bik, peymana lsans bixwne." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Heke tu ertn peyman dipejirn, 'Ez Dipejirnim' bitikne. Ji bo sazkirina bernameya $(^NameDA) div tu ertn peyman bipejirn." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Heke tu ertn peyman dipejirn, zeviya pitrastkirin ya jrn dagire. Ji bo tu bikar bernameya $(^NameDA) saz bik div tu ertn peyman bipejirn. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Heke tu ertn peyman dipejirn, bikojka erkirin ya jrn bitikne. Ji bo sazkirina bernameya $(^NameDA) div tu ertn peyman bipejirn. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Peymana Lsans" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Ber tu bernameya $(^NameDA) ji pergala xwe rak peyman bixwne." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Heke tu ertn peyman dipejirn, 'Dipejirnim' bitikne. Ji bo rakirina bernameya $(^NameDA) div tu ertn peyman bipejirn." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Heke tu ertn peyman dipejirn, zeviya erkirin ya jrn dagire. Ji bo tu bernameya $(^NameDA) ji pergala xwe rak div tu peyman bipejirn. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Heke tu ertn peyman dipejirn, bikojka erkirin ya jrn hilbijre. Ji bo tu bernameya $(^NameDA) ji pergala xwe rak div tu ertn peyman bipejirn. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Ji bo dmahka peyman bikojka 'page down' bitikne." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Hilbijartina pareyan" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Ji bo sazkirina $(^NameDA) pareyn tu dixwaz hilbijre." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Hilbijartina Pareyan" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Pareya bernameya $(^NameDA) ku tu dixwaz rak hilbijre." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Dazann" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Ji bo tu der bar pareyan de agahiyan bistn nanek bibe ser pareyek." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Ji bo tu der bar pareyan de agahiyan bistn nanek bibe ser pareyek." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Hilbijartina peldanka armanckir" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Peldanka tu dixwaz bernameya $(^NameDA) t de were sazkirin hilbijre." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Hilbijartina Peldanka D Were Rakirin" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Peldanka bernameya $(^NameDA) ku tudixwaz rak hilbijre." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "T sazkirin" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Ji kerema xwe re heta sazkirina $(^NameDA) biqede raweste." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Sazkirin Qediya" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Sazkirin bi serkeftin qediya." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Sazkirin hate betalkirin" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Sazkirin be tevah qediya." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "T rakirin" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Heta bernameya $(^NameDA) ji pergala te were rakirin raweste." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Rakirina Bernamey Biqedne" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Rakirina bernamey bi serkeftin pk hat." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Rakirina bernamey hate betalkirin" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Rakirina bernamey neqediya." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Srbaziya sazkirina $(^NameDA) diqede." + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) li komputera te hate barkirin.$\r$\n$\r$\n'Biqedne'y bitikne sazkirin bi daw bne." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Ji bo bidawkirina sazkirina $(^NameDA) div tu komputer ji n ve vek.Tu dixwaz komputer ji n ve vek?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Srbaziya Rakirina Bernameya $(^NameDA) T Temamkirin" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Bernameya $(^NameDA) ji pergale hate rakirin.$\r$\n$\r$\nJi bo girtina srbaz 'biqedne'y bitikne." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Ji bo rakirina bernameya $(^NameDA) biqede div tu komputera xwe ji n ve vek. Tu dixwaz niha komputera te were girtin ji n ve dest p bike?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Ji n ve veke" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ez pa ji n ve vekim." + ${LangFileString} MUI_TEXT_FINISH_RUN "Bernameya $(^NameDA) bixebitne" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Dosiya min bixwne/readme &nan bide" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Biqedne" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Hilbijartina Peldanka Peka Destpk" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Peldanka peka destpk ya ku d kineriya $(^NameDA) t de were bikarann hilbijre." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Peldanka peka destpk ya ku d kineriya bernamey t de were bicihkirin hilbijre. Tu dikar bi navek n peldankeke n ava bik." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "By kirina kineriy bidomne" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Bernameya $(^NameDA) Rake" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Rakirina bernameya $(^NameDA) ji pergala te." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Tu bawer ku dixwaz ji sazkirina $(^Name) derkev?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Tu bawer ku dixwaz dest ji rakirina bernameya $(^Name) berd?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Latvian.nlf b/installer/NSIS/Contrib/Language files/Latvian.nlf new file mode 100644 index 0000000..280a028 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Latvian.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +1062 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1257 +# RTL - anything else than RTL means LTR +- +# Translation by Valdis Griis (valmiera-9@inbox.lv) +# Corrections by Kristaps Meelis / x-f (x-f 'AT' inbox.lv) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +'$(^Name)' Uzstdana +# ^UninstallCaption +'$(^Name)' Atinstalana +# ^LicenseSubCaption +: Licences lgums +# ^ComponentsSubCaption +: Uzstdanas opcijas +# ^DirSubCaption +: Uzstdanas mape +# ^InstallingSubCaption +: Notiek uzstdana +# ^CompletedSubCaption +: Uzstdana pabeigta. +# ^UnComponentsSubCaption +: Atinstalanas opcijas +# ^UnDirSubCaption +: Atinstalanas mape +# ^ConfirmSubCaption +: Apstiprinana +# ^UninstallingSubCaption +: Notiek atinstalana +# ^UnCompletedSubCaption +: Atinstalana pabeigta +# ^BackBtn +< &Atpaka +# ^NextBtn +&Tlk > +# ^AgreeBtn +Es &piekrtu +# ^AcceptBtn +Es &piekrtu licences lguma noteikumiem +# ^DontAcceptBtn +Es &nepiekrtu licences lguma noteikumiem +# ^InstallBtn +&Uzstdt +# ^UninstallBtn +&Atinstalt +# ^CancelBtn +Atcelt +# ^CloseBtn +Ai&zvrt +# ^BrowseBtn +P&rlkot... +# ^ShowDetailsBtn +Pardt &detaas +# ^ClickNext +Spiediet 'Tlk', lai turpintu. +# ^ClickInstall +Spiediet 'Uzstdt', lai sktu uzstdanas procesu. +# ^ClickUninstall +Spiediet 'Atinstalt', lai sktu atinstalanas procesu. +# ^Name +Vrds +# ^Completed +Uzstdana pabeigta +# ^LicenseText +Ldzu izlasiet licences lgumu pirms '$(^NameDA)' uzstdanas. Ja piekrtat licences lguma noteikumiem, tad spiediet 'Es piekrtu'. +# ^LicenseTextCB +Ldzu izlasiet licences lgumu pirms '$(^NameDA)' uzstdanas. Ja piekrtat licences lguma noteikumiem, tad atzmjiet izvles rtiu. $_CLICK +# ^LicenseTextRB +Ldzu izlasiet licences lgumu pirms '$(^NameDA)' uzstdanas. Ja piekrtat licences lguma noteikumiem, tad izvlieties pirmo zemkesoo opciju. $_CLICK +# ^UnLicenseText +Ldzu izlasiet licences lgumu pirms '$(^NameDA)' atinstalanas. Ja piekrtat licences lguma noteikumiem, tad spiediet 'Es piekrtu'. +# ^UnLicenseTextCB +Ldzu izlasiet licences lgumu pirms '$(^NameDA)' atinstalanas. Ja piekrtat licences lguma noteikumiem, tad atzmjiet izvles rtiu. $_CLICK +# ^UnLicenseTextRB +Ldzu izlasiet licences lgumu pirms '$(^NameDA)' atinstalanas. Ja piekrtat licences lguma noteikumiem, tad izvlieties zemkesoo opciju. $_CLICK +# ^Custom +Pielgots +# ^ComponentsText +Izvlieties, kurus komponentus vlaties uzstdt un neiezmjiet tos, kurus nevlaties uzstdt. $_CLICK +# ^ComponentsSubText1 +Izvlieties uzstdanas veidu: +# ^ComponentsSubText2_NoInstTypes +Izvlieties uzstdmos komponentus: +# ^ComponentsSubText2 +Vai ar izvlieties tikai nepiecieamos komponentus, kurus vlaties uzstdt: +# ^UnComponentsText +Izvlieties, kurus komponentus atinstalt un neiezmjiet tos, kurus nevlaties atinstalt. $_CLICK +# ^UnComponentsSubText1 +Izvlieties atinstalanas veidu: +# ^UnComponentsSubText2_NoInstTypes +Izvlieties atinstaljamos komponentus: +# ^UnComponentsSubText2 +Vai ar izvlieties tikai nepiecieamos komponentus, kurus vlaties atinstalt: +# ^DirText +'$(^NameDA)' tiks uzstdta aj map. Lai to uzstdtu cit map, nospiediet 'Prlkot' un izvlieties citu mapi. $_CLICK +# ^DirSubText +Uzstdanas mape +# ^DirBrowseText +Izvlieties mapi, kur uzstdt '$(^NameDA)': +# ^UnDirText +'$(^NameDA)' tiks atinstalta no s mapes. Lai to atinstaltu no citas mapes, nospiediet 'Prlkot' un izvlieties citu mapi. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Izvlieties mapi, no kuras atinstalt '$(^NameDA)': +# ^SpaceAvailable +"Pieejamais diska apjoms: " +# ^SpaceRequired +"Nepiecieamais diska apjoms: " +# ^UninstallingText +'$(^NameDA)' tiks atinstalta no s mapes. $_CLICK +# ^UninstallingSubText +Atinstalana no: +# ^FileError +Kda atverot failu rakstanai: \r\n\t"$0"\r\nNospiediet 'Atcelt', lai atceltu uzstdanas procesu,\r\n'Mint vlreiz', lai atkrtoti mintu rakstt fail vai\r\n'Ignort', lai izlaistu faila uzstdanu +# ^FileError_NoIgnore +Kda atverot failu rakstanai: \r\n\t"$0"\r\nNospiediet 'Atcelt', lai prtrauktu uzstdanas procesu +# ^CantWrite +"Nevar ierakstt: " +# ^CopyFailed +Kopana neizdevs +# ^CopyTo +"Kop uz " +# ^Registering +"Reistr: " +# ^Unregistering +"Atreistr: " +# ^SymbolNotFound +"Simbols nav atrasts: " +# ^CouldNotLoad +"Nav iespjams ieldt: " +# ^CreateFolder +"Izveido mapi: " +# ^CreateShortcut +"Izveido sasni: " +# ^CreatedUninstaller +"Izveidots atinstaltjs: " +# ^Delete +"Dz failu: " +# ^DeleteOnReboot +"Dzst pc prstartanas: " +# ^ErrorCreatingShortcut +"Kda veidojot sasni: " +# ^ErrorCreating +"Kda veidojot: " +# ^ErrorDecompressing +Kda atkompresjot datus! Bojta instalcija? +# ^ErrorRegistering +Kda reistrjot DLL failu +# ^ExecShell +"Izpilda aul: " +# ^Exec +"Izpilda: " +# ^Extract +"Atspie: " +# ^ErrorWriting +"Atspieana: kda rakstot fail " +# ^InvalidOpcode +Instalcija bojta: nedergs CRC kods +# ^NoOLE +"Nav OLE priek: " +# ^OutputFolder +"Izvades mape: " +# ^RemoveFolder +"Dz mapi: " +# ^RenameOnReboot +"Prsaukt pc prstartanas: " +# ^Rename +"Prsaukt: " +# ^Skipped +"Izlaists: " +# ^CopyDetails +Iekopt detaas starpliktuv +# ^LogInstall +Ierakstt urnla fail uzstdanas procesu +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Latvian.nsh b/installer/NSIS/Contrib/Language files/Latvian.nsh new file mode 100644 index 0000000..32b634f --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Latvian.nsh @@ -0,0 +1,122 @@ +;Language: Latvieu [Latvian] - (1062) +;By Valdis Griis +;Corrections by Kristaps Meelis / x-f (x-f 'AT' inbox.lv) + +!insertmacro LANGFILE "Latvian" "Latvieu" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Esiet sveicinti '$(^NameDA)' uzstdanas vedn" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "is uzstdanas vednis jums paldzs veikt '$(^NameDA)' uzstdanu.$\r$\n$\r$\noti ieteicams aizvrt citas programmas pirms s programmas uzstdanas veikanas. Tas aus atjaunot svargus sistmas failus bez datora prstartanas.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Esiet sveicinti '$(^NameDA)' atinstalanas vedn" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "is vednis jums paldzs veikt '$(^NameDA)' atinstalanu.$\r$\n$\r$\nPirms skt atinstalanas procesu, prliecinieties, vai '$(^NameDA)' palaik nedarbojas.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licences lgums" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Ldzu izlasiet licences lgumu pirms '$(^NameDA)' uzstdanas." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Ja piekrtat licences lguma noteikumiem, spiediet 'Piekrtu', lai turpintu uzstdanu. Jums ir jpiekrt licences noteikumiem, lai uzstdtu '$(^NameDA)'." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ja piekrtat licences lguma noteikumiem, tad atzmjiet izvles rtiu. Jums ir jpiekrt licences noteikumiem, lai uzstdtu '$(^NameDA)'. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ja piekrtat licences lguma noteikumiem, tad izvlieties pirmo zemkesoo opciju. Jums ir jpiekrt licences noteikumiem, lai uzstdtu '$(^NameDA)'. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licences lgums" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Ldzu izlasiet licences lgumu pirms '$(^NameDA)' atinstalanas." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Ja piekrtat licences noteikumiem, spiediet 'Piekrtu', lai turpintu. Jums ir jpiekrt licences noteikumiem, lai atinstaltu '$(^NameDA)'." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ja piekrtat licences lguma noteikumiem, tad iezmjiet izvles rtiu. Jums ir jpiekrt licences noteikumiem, lai atinstaltu '$(^NameDA)'. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ja piekrtat licences lguma noteikumiem, tad izvlieties pirmo zemkesoo opciju. Jums ir jpiekrt licences noteikumiem, lai atinstaltu '$(^NameDA)'. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Spiediet 'Page Down', lai aplkotu visu lgumu." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Izvlieties komponentus" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Izvlieties nepiecieams '$(^NameDA)' sastvdaas, kuras uzstdt." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Izvlieties komponentus" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Izvlieties nepiecieams '$(^NameDA)' sastvdaas, kuras atinstalt." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Apraksts" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Novietojiet peles kursoru uz komponenta, lai tiktu pardts t apraksts." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Novietojiet peles kursoru uz komponenta, lai tiktu pardts t apraksts." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Izvlieties uzstdanas mapi" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Izvlieties mapi, kur uzstdt '$(^NameDA)'." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Izvlieties atinstalanas mapi" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Izvlieties mapi, no kuras notiks '$(^NameDA)' atinstalana." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Notiek uzstdana" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Ldzu uzgaidiet, kamr notiek '$(^NameDA)' uzstdana." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Uzstdana pabeigta" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Uzstdana noritja veiksmgi." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Uzstdana atcelta" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Uzstdana nenoritja veiksmgi." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Notiek atinstalana" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Ldzu uzgaidiet, kamr '$(^NameDA)' tiek atinstalta." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Atinstalana pabeigta" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Atinstalana noritja veiksmgi." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Atinstalana atcelta" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Atinstalana nenoritja veiksmgi." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Tiek pabeigta '$(^NameDA)' uzstdana" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "'$(^NameDA)' tika veiksmgi uzstdta jsu dator.$\r$\n$\r$\nNospiediet 'Pabeigt', lai aizvrtu vedni." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Jsu datoru ir nepiecieams prstartt, lai pabeigtu '$(^NameDA)' uzstdanu. Vai vlaties prstartt datoru tlt?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Tiek pabeigta '$(^NameDA)' atinstalcija" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "'$(^NameDA)' tika veiksmgi izdzsta no jsu datora.$\r$\n$\r$\nNospiediet 'Pabeigt', lai aizvrtu vedni." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Jsu datoru nepiecieams prstartt, lai pabeigtu '$(^NameDA)' atinstalanu. Vai vlaties prstartt datoru tlt?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Prstartt tlt" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Es vlos prstartt pats vlk" + ${LangFileString} MUI_TEXT_FINISH_RUN "P&alaist '$(^NameDA)'" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Pa&rdt LasiMani failu" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Pabeigt" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Izvlieties 'Start Menu' folderi" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Izvlieties 'Start Menu' mapi '$(^NameDA)' sasnm." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Izvlieties 'Start Menu' mapi, kur tiks izveidotas programmas sasnes. Varat ar pats izveidot jaunu mapi." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Neveidot sasnes" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "'$(^NameDA)' atinstalana" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Dzst '$(^NameDA)' no jsu datora." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Vai tiem vlaties prtraukt '$(^Name)' uzstdanu?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Vai tiem vlaties prtraukt '$(^Name)' atinstalanu?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Lithuanian.nlf b/installer/NSIS/Contrib/Language files/Lithuanian.nlf new file mode 100644 index 0000000..50fff1b --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Lithuanian.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +1063 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1257 +# RTL - anything else than RTL means LTR +- +# Translation by Vytautas Krivickas (Vytautas) +# Updated by Danielius Scepanskis (Daan daniel@takas.lt) 2004.03.24 +# ^Branding +Nullsoft instaliavimo sistema %s +# ^SetupCaption +$(^Name) diegimas +# ^UninstallCaption +$(^Name) alinti +# ^LicenseSubCaption +: Naudojimo sutartis +# ^ComponentsSubCaption +: diegimo nustatymai +# ^DirSubCaption +: diegimo katalogas +# ^InstallingSubCaption +: diegiama +# ^CompletedSubCaption +: Baigta +# ^UnComponentsSubCaption +: Itrinimo nustatymai +# ^UnDirSubCaption +: Itrinimo katalogas +# ^ConfirmSubCaption +: Patvirtinimas +# ^UninstallingSubCaption +: Panaikinama +# ^UnCompletedSubCaption +: Baigta +# ^BackBtn +< &Atgal +# ^NextBtn +&Toliau > +# ^AgreeBtn +A &sutinku +# ^AcceptBtn +A &sutinku su naudojimo sutarties slygomis +# ^DontAcceptBtn +A &nesutinku su naudojimo sutarties slygomis +# ^InstallBtn +&diegti +# ^UninstallBtn +&Panaikinti +# ^CancelBtn +Nutraukti +# ^CloseBtn +&Udaryti +# ^BrowseBtn +P&asirinkti... +# ^ShowDetailsBtn +Parodyti &detales +# ^ClickNext +Paspauskite toliau +# ^ClickInstall +Paspauskite diegti +# ^ClickUninstall +Paspauskite itrinti +# ^Name +Vardas +# ^Completed +Baigta +# ^LicenseText +Praome perskaityti sutart prie diegdami $(^NameDA). Jei js sutinkate su nurodytomis slygomis, spauskite Sutinku. +# ^LicenseTextCB +Praome perskaityti sutart prie diegdami $(^NameDA). Jei js sutinkate su nurodytomis slygomis, padkite varnel tam skirtame laukelyje. $_CLICK +# ^LicenseTextRB +Praome perskaityti sutart prie diegdami $(^NameDA). Jei js sutinkate su nurodytomis slygomis, pasirinkite pirm pasirinkim esant emiau. $_CLICK +# ^UnLicenseText +Praome perskaityti sutart prie itrinant $(^NameDA). Jei js sutinkate su nurodytomis slygomis, spauskite Sutinku. +# ^UnLicenseTextCB +Praome perskaityti sutart prie itrinant $(^NameDA). Jei js sutinkate su nurodytomis slygomis, padkite varnel tam skirtame laukelyje. $_CLICK +# ^UnLicenseTextRB +Praome perskaityti sutart prie itrinant $(^NameDA). Jei js sutinkate su nurodytomis slygomis, pasirinkite pirm pasirinkim esant emiau. $_CLICK +# ^Custom +Kitoks +# ^ComponentsText +Padkite varneles laukeliuose komponent kuriuos norite diegti ir nuimkite nuo kuri nenorite diegti. $_CLICK +# ^ComponentsSubText1 +Pasirinkite diegimo bd: +# ^ComponentsSubText2_NoInstTypes +Pasirinkite komponentus, kuriuos diegti: +# ^ComponentsSubText2 +Arba, pasirinkite neprivalomus komponentus, kuriuos js norite diegti: +# ^UnComponentsText +Padkite varneles laukeliuose komponent kuriuos norite paalinti ir nuimkite nuo kuri nenorite paalinti. $_CLICK +# ^UnComponentsSubText1 +Pasirinkite alinimo bd: +# ^UnComponentsSubText2_NoInstTypes +Pasirinkite komponentus, kuriuos alinti: +# ^UnComponentsSubText2 +Arba, pasirinkite neprivalomus komponentus, kuriuos js norite paalinti: +# ^DirText +diegimas dabar diegs $(^NameDA) iame kataloge. Jeigu norite pakeisti katalog, paspauskite Pasirinkti. $_CLICK +# ^DirSubText +diegimo katalogas +# ^DirBrowseText +Pasirinkite katalog, kur diegti $(^NameDA): +# ^UnDirText +diegimas dabar paalins $(^NameDA) i io katalogo. Jeigu norite pakeisti katalog paspauskite Pasirinkti. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Pasirinkite katalog i kurio paalinti $(^NameDA): +# ^SpaceAvailable +Yra vietos: +# ^SpaceRequired +Reikia vietos: +# ^UninstallingText +$(^NameDA) dabar bus paalintas i io katalogo. $_CLICK +# ^UninstallingSubText +Trinama i: +# ^FileError +Klaida atidarant fail raymui: \r\n\t"$0"\r\nPaspauskite Nutraukti, jei norite nutraukti diegim,\r\nPakartoti, jei norite pabandyti dar kart rayti fail, ar\r\nIgnoruoti, jei norite praleisti fail +# ^FileError_NoIgnore +Klaida atidarant fail raymui: \r\n\t"$0"\r\nPaspauskite Pakartoti, jei norite pabandyti dar kart rayti fail, ar\r\nNutraukti, jei norite nutraukti diegim. +# ^CantWrite +"Negalima rayti: " +# ^CopyFailed +Kopijavimas nepavyko +# ^CopyTo +Kopijuoti +# ^Registering +"Uregistruojama: " +# ^Unregistering +"Iregistruojama: " +# ^SymbolNotFound +Nerastas simbolis: +# ^CouldNotLoad +Negaliu krauti: +# ^CreateFolder +Sukurti katalog: +# ^CreateShortcut +Sukurti nuorod: +# ^CreatedUninstaller +Sukurti panaikinimo program: +# ^Delete +Itrinti fail: +# ^DeleteOnReboot +"Itrinti perkraunant: " +# ^ErrorCreatingShortcut +"Klaida kuriant nuorod: " +# ^ErrorCreating +"Klaida kuriant: " +# ^ErrorDecompressing +Klaida iskleidiant duomenis! Sugadintas diegimo failas? +# ^ErrorRegistering +Klaida uregistruojant DLL +# ^ExecShell +"VykdytiShell: " +# ^Exec +"Vykdyti: " +# ^Extract +"Iskleisti: " +# ^ErrorWriting +Iskleisti: klaida raant fail +# ^InvalidOpcode +diegimo failas sugadintas: neteisingas opkodas +# ^NoOLE +"Nra OLE dl: " +# ^OutputFolder +"Paskirties katalogas: " +# ^RemoveFolder +"Panaikinti katalog: " +# ^RenameOnReboot +"Pervardinti perkraunant: " +# ^Rename +"Pervardinti: " +# ^Skipped +"Praleista: " +# ^CopyDetails +Kopijuoti detales atmint +# ^LogInstall +rayti diegimo detales +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Lithuanian.nsh b/installer/NSIS/Contrib/Language files/Lithuanian.nsh new file mode 100644 index 0000000..f35d84a --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Lithuanian.nsh @@ -0,0 +1,121 @@ +;Language: Lithuanian (1063) +;By Vytautas Krivickas (Vytautas). Updated by Danielius Scepanskis (Daan daniel@takas.lt) 2004.01.09 + +!insertmacro LANGFILE "Lithuanian" "Lietuviu" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Sveiki atvyk $(^NameDA) diegimo program." + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "i programa jums pads lengvai diegti $(^NameDA).$\r$\n$\r$\nRekomenduojama ijungti visas programas, prie pradedant diegim. Tai leis atnaujinti sistemos failus neperkraunat kompiuterio.$\r$\n$\r$\n" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Sveiki atvyk $(^NameDA) paalinimo program." + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "i programa jums pads lengvai itrinti $(^NameDA).$\r$\n$\r$\nPrie pradedant pasitikrinkite kad $(^NameDA) yra ijungta.$\r$\n$\r$\n" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Naudojimo sutartis" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Praome perskaityti sutart prie diegdami $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Jei js sutinkate su nurodytomis slygomis, spauskite Sutinku. Js privalote sutikti, jei norite diegti $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jei js sutinkate su nurodytomis slygomis, padkite varnel tam skirtame laukelyje. Js privalote sutikti, jei norite diegti $(^NameDA). " + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jei js sutinkate su nurodytomis slygomis, pasirinkite pirm pasirinkim esant emiau. Js privalote sutikti, jei norite diegti $(^NameDA). " +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Naudojimo sutartis" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Praome perskaityti sutart prie $(^NameDA) paalinim." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Jei js sutinkate su nurodytomis slygomis, spauskite Sutinku. Js privalote sutikti, jei norite itrinti $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "s, padkite varnel tam skirtame laukelyje. Js privalote sutikti, jei norite itrinti $(^NameDA). " + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jei js sutinkate su nurodytomis slygomis, pasirinkite pirm pasirinkim esant emiau. Js privalote sutikti, jei norite itrinti $(^NameDA)." +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Paspauskite Page Down ir perskaitykite vis sutart." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Pasirinkite" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Pasirinkite kokias $(^NameDA) galimybes js norite diegti." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Pasirinkite" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Pasirinkite kokias $(^NameDA) galimybes js norite paalinti." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Paaikinimas" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Uveskite pels ymekl ant komponento ir pamatysite jo apraym." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Uveskite pels ymekl ant komponento ir pamatysite jo apraym." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Pasirinkite diegimo viet" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Pasirinkite katalog kri diegsite $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Pasirinkite itrinimo viet" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Pasirinkite katalog i kurio itrinsite $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Diegiama" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Praome palaukti, kol $(^NameDA) bus diegtas." + ${LangFileString} MUI_TEXT_FINISH_TITLE "diegimas baigtas" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "diegimas baigtas sekmingai." + ${LangFileString} MUI_TEXT_ABORT_TITLE "diegimas nutrauktas" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "diegimas nebuvo baigtas sekmingai." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "alinama" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Praome palaukti, kol $(^NameDA) bus paalinta." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Programos paalinimas baigtas" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Itrynimas baigtas sekmingai." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Itrynimas nutrauktas" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Itrynimas nebuvo baigtas sekmingai." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Baigiu $(^NameDA) diegimo proces" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) buvo diegtas js kompiuter.$\r$\n$\r$\nPaspauskite Baigti." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Js kompiuteris turi bti perkrautas, kad bt baigtas $(^NameDA) diegimas. Ar js norite perkrauti dabar?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Baigiu $(^NameDA) paalinimo program." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) buvo itrinta i js kompiuterio.$\r$\n$\r$\nPaspauskite Baigti." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Js kompiuteris turi bti perkrautas, kad bt baigtas $(^NameDA) paalinimas. Ar js norite perkrauti dabar?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Perkrauti dabar" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "A noriu perkrauti veliau pats" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Leisti $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Parodyti dokumentacij" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Baigti" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Pasirinkite Start Menu katalog" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Pasirinkite Start Menu katalog, kuriame bus sukurtos programos nuorodos." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Pasirinkite Start Menu katalog, kuriame bus sukurtos programos nuorodos. Js taip pat galite sukurti nauj katalog." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nekurti nuorod" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Panaikinti $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Itrinti $(^NameDA) i js kompiuterio." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Ar js tikrai norite ijungti $(^Name) diegimo program?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Ar js tikrai norite ijungti $(^Name) paalinimo program?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Luxembourgish.nlf b/installer/NSIS/Contrib/Language files/Luxembourgish.nlf new file mode 100644 index 0000000..4638d78 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Luxembourgish.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +4103 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Jo Hoeser +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Installatioun +# ^UninstallCaption +$(^Name) Desinstallatioun +# ^LicenseSubCaption +: Lizenzofkommes +# ^ComponentsSubCaption +: Installatiouns-Optiounen +# ^DirSubCaption +: Zielverzeechnis +# ^InstallingSubCaption +: Installieren +# ^CompletedSubCaption +: Frdeg +# ^UnComponentsSubCaption +: Desinstallatiuons-Optiounen +# ^UnDirSubCaption +: Quellverzeechnes +# ^ConfirmSubCaption +: Besttegung +# ^UninstallingSubCaption +: Lschen +# ^UnCompletedSubCaption +: Frdeg +# ^BackBtn +< &Zrck +# ^NextBtn +&Weider > +# ^AgreeBtn +&Unhuelen +# ^AcceptBtn +Ech &huelen d'Lizenzofkommes un. +# ^DontAcceptBtn +Ech &lehnen d'Lizenzofkommes of. +# ^InstallBtn +&Installieren +# ^UninstallBtn +&Desinstallieren +# ^CancelBtn +Ofbriechen +# ^CloseBtn +&Zou maan +# ^BrowseBtn +&Duerchsichen... +# ^ShowDetailsBtn +&Details uweisen +# ^ClickNext +Klick op weider fir weiderzefueren +# ^ClickInstall +Klick op Installieren, fir d'Installatioun unzefnken. +# ^ClickUninstall +Klick op Desinstallieren, fir d'Desinstallatioun unzefnken. +# ^Name +Numm +# ^Completed +Frdeg +# ^LicenseText +W.e.g. d'Lizenzofkommes liesen, ierts de $(^NameDA) installiers. Wanns de all Bedengungen vum Ofkommes akzeptiers, klick op Unhuelen. +# ^LicenseTextCB +W.e.g. d'Lizenzofkommes liesen, ierts de $(^NameDA) installiers. Wanns de all Bedengungen vum Ofkommes akzeptiers, aktivier d'Kontrollkeschtchen. $_CLICK +# ^LicenseTextRB +W.e.g. d'Lizenzofkommes liesen, ierts de $(^NameDA) installiers. Wanns de all Bedengungen vum Ofkommes akzeptiers, wiel d'entsprichend Optioun. $_CLICK +# ^UnLicenseText +W.e.g. d'Lizenzofkommes liesen, ierts de $(^NameDA) desinstalliers. Wanns de all Bedengungen vum Ofkommes akzeptiers, klick op Unhuelen. +# ^UnLicenseTextCB +W.e.g. d'Lizenzofkommes liesen, ierts de $(^NameDA) desinstalliers. Wanns de all Bedengungen vum Ofkommes akzeptiers, aktivier d'Kontrollkeschtchen. $_CLICK +# ^UnLicenseTextRB +W.e.g. d'Lizenzoofkommes liesen, ierts de $(^NameDA) desinstalliers. Wanns de all Bedengungen vum Oofkommes akzeptiers, wiel d'entspriechend Optioun. $_CLICK +# ^Custom +Benutzerdefiniert +# ^ComponentsText +Wiel d'Komponenten aus, dis de wlls installieren an wiel dijineg of, dis de net installieren wlls. $_CLICK +# ^ComponentsSubText1 +Installatiouns-Typ bestmmen: +# ^ComponentsSubText2_NoInstTypes +Wiel d'Komponenten aus, dis de installieren wlls: +# ^ComponentsSubText2 +oder wiel zoustzlech Komponenten aus dis de installieren wlls: +# ^UnComponentsText +Wiel d'Komponenten aus dis de desinstallieren wlls an wiel dijineg of, dis de net desinstallieren wlls. $_CLICK +# ^UnComponentsSubText1 +Deinstallatiouns-Typ bestmmen: +# ^UnComponentsSubText2_NoInstTypes +Wiel d'Komponenten aus, dis de desinstallieren wlls: +# ^UnComponentsSubText2 +oder wiel zustzlech Komponenten aus, dis de desinstallieren wlls: +# ^DirText +$(^NameDA) gtt an den Dossier installiert deen fierginn gouf. Wanns de et an een aneren Dossier installieren wlls, klick op Duechsichen an wiel een aneren Dossier aus. $_CLICK +# ^DirSubText +Zielverzeechnes +# ^DirBrowseText +Wiel en Dossier aus wuers de $(^NameDA) installieren wlls: +# ^UnDirText +$(^NameDA) gtt an deem Dossier desinstalliert, deen uginn gouf. Wann $(^NameDA) an engem aneren Dossier ass, klick op Duechsichen an wiel een aneren Dossier aus. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Wiel den Dossier aus wou $(^NameDA) dran installiert ass: +# ^SpaceAvailable +"Verfgbaren Spicher: " +# ^SpaceRequired +"Gebrauchten Spicher: " +# ^UninstallingText +$(^NameDA) gtt aus dem ausgewielten Dossier desinstalliert. $_CLICK +# ^UninstallingSubText +Desinstallieren aus: +# ^FileError +Fehler beim Iwwerschreiwen vun der Datei: \r\n\t"$0"\r\nKlick op ofbriechen fir den Setup ze verloossen,\r\nop Widderhuelen fir den Setup nach eng Kier duechzefieren\r\n oder op Ignorieren fir des Datei ze iwwersprengen an weiderzefueren. +# ^FileError_NoIgnore +Fehler beim Iwwerschreiwen vun der Datei: \r\n\t"$0"\r\nKlick op Widderhuelen fir den Setup nach eng Kier duechzefieren,\r\noder op ofbriechen fir den Setup ze verloossen. +# ^CantWrite +"Fehler beim Schreiwen: " +# ^CopyFailed +Kopieren fehlgeschloen +# ^CopyTo +"Kopiere an " +# ^Registering +"Registrieren: " +# ^Unregistering +"Deregistrieren: " +# ^SymbolNotFound +"Symbol ass net do: " +# ^CouldNotLoad +"Fehler beim Lueden vun: " +# ^CreateFolder +"Maan Dossier: " +# ^CreateShortcut +"Maan Oofkierzung: " +# ^CreatedUninstaller +"Man Desinstallatiouns-Programm: " +# ^Delete +"Lschen Datei: " +# ^DeleteOnReboot +"Lschen Datei no engem Neistart: " +# ^ErrorCreatingShortcut +"Fehler beim man vun enger Oofkierzung: " +# ^ErrorCreating +"Fehler beim maan: " +# ^ErrorDecompressing +Fehler beim Dekomprimieren. Installations-Programm beschiedegt? +# ^ErrorRegistering +Fehler beim Registrieren vun der DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Starten: " +# ^Extract +"Dekomprimieren: " +# ^ErrorWriting +"Dekomprimierung: Fehler beim Schreiwen vun der Datei " +# ^InvalidOpcode +Installations-Programm Beschiedegt: net zoulssegen Befehlscode +# ^NoOLE +"Keen OLE fier: " +# ^OutputFolder +"Zieldossier: " +# ^RemoveFolder +"Lschen Dossier: " +# ^RenameOnReboot +"Gett no Neistart embenannt: " +# ^Rename +"Embenennen: " +# ^Skipped +"Iwwersprongen: " +# ^CopyDetails +Detailler an d'Zwschenooflag kopieren +# ^LogInstall +Installatioun protokollieren +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Luxembourgish.nsh b/installer/NSIS/Contrib/Language files/Luxembourgish.nsh new file mode 100644 index 0000000..d52568a --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Luxembourgish.nsh @@ -0,0 +1,121 @@ +;Language: Luxembourgish (1031) +;By Snowloard, changes by Philo + +!insertmacro LANGFILE "Luxembourgish" "Ltzebuergesch" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Wllkomm beim Installatiouns-$\r$\nAssistent vun $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Dsen Assistent wrt dech duech d'Installatioun vun $(^NameDA) begleeden.$\r$\n$\r$\nEt gtt ugeroden alleguer d'Programmer di am Moment lafen zouzemaan, datt bestmmt Systemdateien ouni Neistart ersat knne ginn.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Wllkomm am Desinstallatiouns-$\r$\n\Assistent fir $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Desen Assistent begleet dech duech d'Desinstallatioun vun $(^NameDA).$\r$\n$\r$\nW.e.g. maach $(^NameDA) zu, ierts de mat der Desinstallatioun ufnks.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lizenzofkommes" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "W.e.g. d'Lizenzoofkommes liesen, ierts de mat der Installatioun weiderfiers." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Wanns de alleguer d'Bedengungen vum Ofkommes akzeptiers, klick op Unhuelen. Du muss alleguer d'Fuerderungen unerkennen, fir $(^NameDA) installieren ze knnen." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Wanns de alleguer d'Bedengungen vum Ofkommes akzeptiers, aktivier d'Kschtchen. Du muss alleguer d'Fuerderungen unerkennen, fir $(^NameDA) installieren ze knnen. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Wanns de alleguer d'Bedengungen vum Ofkommes akzeptiers, wiel nnen di entspriechend ntwert aus. Du muss alleguer d'Fuerderungen unerkennen, fir $(^NameDA) installieren ze knnen. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lizenzofkommes" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "W.e.g. lies d'Lizenzofkommes duech ierts de mat der Desinstallatioun vun $(^NameDA) weiderfiers." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Wanns de d'Fuerderungen vum Ofkommes akzeptiers, klick op unhuelen. Du muss d'Ofkommes akzeptieren, fir $(^NameDA) knnen ze desinstallieren." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Wanns de d'Fuerderungen vum Ofkommes akzeptiers, aktivier d'Kschtchen. Du muss d'Ofkommes akzeptieren, fir $(^NameDA) knnen ze desinstallieren. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Wanns de d'Fuerderungen vum Ofkommes akzeptiers, wiel nnen di entspriechend Optioun. Du muss d'Oofkommes akzeptieren, fir $(^NameDA) kennen ze desinstallieren. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Drck d'PageDown-Tast fir den Rescht vum Ofkommes ze liesen." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Komponenten auswielen" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Wiel d'Komponenten aus, dis de wlls installieren." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Komponenten auswielen" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Wiel eng Komponent aus, dis de desinstallieren wlls." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beschreiwung" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Hal den Mausfeil iwwer eng Komponent, fir d'Beschreiwung dervun ze gesinn." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Hal den Mausfeil iwwer eng Komponent, fir d'Beschreiwung dervun ze gesinn." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Zielverzeechnes auswielen" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Wiel den Dossier aus, an deen $(^NameDA) installiert soll ginn." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Dossier fir d'Desinstallatioun wielen" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Wiel den Dossier aus, aus dem $(^NameDA) desinstalliert soll ginn." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installieren..." + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Waard w.e.g whrend deem $(^NameDA) installiert gtt." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installatioun frdeg" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "D'Installatioun ass feelerfri oofgeschloss ginn." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installatioun ofgebrach" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "D'Installatioun ass net komplett ofgeschloss ginn." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Desinstallieren..." + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "W.e.g. waard, whrend deems $(^NameDA) desinstalliert gtt." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Desinstallatioun ofgeschloss" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "D'Desinstallatioun ass erfollegrich ofgeschloss ginn." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Desinstallatioun oofbriechen" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Desinstallatioun ass net erfollegrich ofgeschloss ginn." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "D'Installatioun vun $(^NameDA) gtt ofgeschloss." + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) ass um Computer installiert ginn.$\r$\n$\r$\nKlick op frdeg maan, fir den Installatiouns-Assistent zou ze maan.." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Den Windows muss nei gestart ginn, fir d'Installatioun vun $(^NameDA) ofzeschlissen. Wlls de Windows lo ni starten?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Desinstallatioun vun $(^NameDA) gtt ofgeschloss" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) ass vum Computer desinstalliert ginn.$\r$\n$\r$\nKlick op Ofschlissen fir den Assistent zou ze maan." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Windows muss ni gestart gin, fir d'Desinstallatioun vun $(^NameDA) ze vervollstnnegen. Wlls de Windows lo ni starten?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Lo ni starten" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Spider manuell ni starten" + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) op maan" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Liesmech op maan" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Frdeg man" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Startmen-Dossier bestmmen" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Bestmm een Startman-Dossier an deen d'Programmofkierzungen kommen." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Bestmm een Startman-Dossier an deen d'Programmofkierzungen kommen. Wanns de een nien Dossier man wells, gff deem sin zuknftegen Numm an." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Keng Ofkierzungen man" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Desinstallatioun vun $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA) gett vum Computer desinstalliert." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Bass de scher, dass de d'Installatioun vun $(^Name) ofbriechen wlls?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Bass de scher, dass de d'Desinstallatioun vun $(^Name) ofbriechen wlls?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Macedonian.nlf b/installer/NSIS/Contrib/Language files/Macedonian.nlf new file mode 100644 index 0000000..818ab8e --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Macedonian.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1071 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1251 +# RTL - anything else than RTL means LTR +- +# Translation by Sasko Zdravkin [wingman2083@yahoo.com] +# ^Branding +Nullsoft Install System %s +# ^SetupCaption + $(^Name) +# ^UninstallCaption + $(^Name) +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< & +# ^NextBtn +& > +# ^AgreeBtn +& +# ^AcceptBtn +& +# ^DontAcceptBtn +& +# ^InstallBtn +& +# ^UninstallBtn +& +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +&... +# ^ShowDetailsBtn +& +# ^ClickNext + '' . +# ^ClickInstall + '' . +# ^ClickUninstall + '' . +# ^Name + +# ^Completed + +# ^LicenseText + $(^NameDA). , ''. +# ^LicenseTextCB + $(^NameDA). , Check box- . $_CLICK +# ^LicenseTextRB + $(^NameDA). , . $_CLICK +# ^UnLicenseText + $(^NameDA). , ''. +# ^UnLicenseTextCB + $(^NameDA). , Check box- . $_CLICK +# ^UnLicenseTextRB + $(^NameDA). , . $_CLICK +# ^Custom + +# ^ComponentsText + . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 +, : +# ^UnComponentsText + . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 +, : +# ^DirText + $(^NameDA) . , '' . $_CLICK +# ^DirSubText + +# ^DirBrowseText + $(^NameDA): +# ^UnDirText + $(^NameDA) . , '' . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + $(^NameDA): +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText +$(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\t"$0"\r\n '' ,\r\n'' , \r\n'' +# ^FileError_NoIgnore + : \r\n\t"$0"\r\n '' , \r\n'' +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +": " +# ^Unregistering +": " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +" : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ! ? +# ^ErrorRegistering + DLL +# ^ExecShell +"ExecShell: " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + : +# ^NoOLE +" OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +": " +# ^Skipped +": " +# ^CopyDetails + Clipboard- +# ^LogInstall + +# ^Byte + +# ^Kilo + +# ^Mega + +# ^Giga + \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Macedonian.nsh b/installer/NSIS/Contrib/Language files/Macedonian.nsh new file mode 100644 index 0000000..5bfd36e --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Macedonian.nsh @@ -0,0 +1,121 @@ +;Language: Macedonian (1071) +;By Sasko Zdravkin [wingman2083@yahoo.com] + +!insertmacro LANGFILE "Macedonian" "Macedonian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n $(^NameDA) .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " , '' . $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , check box- . $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " , '' . $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , check box- . $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " 'Page Down' ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n '' ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " $(^NameDA). ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n '' ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " $(^NameDA). ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " " + ${LangFileString} MUI_TEXT_FINISH_RUN "& $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "& ' '" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Malay.nlf b/installer/NSIS/Contrib/Language files/Malay.nlf new file mode 100644 index 0000000..48fc968 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Malay.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1086 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +- +# RTL - anything else than RTL means LTR +- +# Translation muhammadazwa@yahoo.com +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Setup $(^Name) +# ^UninstallCaption +Uninstall $(^Name) +# ^LicenseSubCaption +: Perlesenan +# ^ComponentsSubCaption +: Pilihan kemasukan +# ^DirSubCaption +: Folder kemasukan +# ^InstallingSubCaption +: Memasang +# ^CompletedSubCaption +: Selesai +# ^UnComponentsSubCaption +: Pilihan membuang +# ^UnDirSubCaption +: Folder Uninstal +# ^ConfirmSubCaption +: Kepastian +# ^UninstallingSubCaption +: Membuang +# ^UnCompletedSubCaption +: Tidak Selesai +# ^BackBtn +< &Ke Belakang +# ^NextBtn +&Ke Depan > +# ^AgreeBtn +Saya &setuju +# ^AcceptBtn +Saya s&etuju dengan Perlesenan +# ^DontAcceptBtn +Saya &tidak setuju dengan Perlesenan +# ^InstallBtn +&Masukkan +# ^UninstallBtn +&Buang +# ^CancelBtn +Batal +# ^CloseBtn +&Tutup +# ^BrowseBtn +S&elusur... +# ^ShowDetailsBtn +Buka &lagi +# ^ClickNext +Klik Ke Depan untuk teruskan. +# ^ClickInstall +Klik Masukkan untuk kemasukkan. +# ^ClickUninstall +Klik Uninstall untuk membuang. +# ^Name +Nama +# ^Completed +Selesai +# ^LicenseText +Sila baca lesen sebelum memasukkan $(^NameDA). Jika anda terima perlesenan, klik Saya setuju. +# ^LicenseTextCB +Sila baca lesen sebelum memasukkan $(^NameDA). Jika terima, beri tanda dicheckbox. $_CLICK +# ^LicenseTextRB +Sila baca lesen sebelum sebelum membuang $(^NameDA). Jika anda terima perlesenan, pilihlah salah satu item dibawah ini. $_CLICK +# ^UnLicenseText +Sila baca lesen sebelum sebelum membuang $(^NameDA). Jika anda terima perlesenan, klik Saya setuju. +# ^UnLicenseTextCB +Sila baca lesen sebelum memasukkan $(^NameDA). Jika terima, beri tanda dicheckbox. $_CLICK +# ^UnLicenseTextRB +Sila baca lesen sebelum sebelum membuang $(^NameDA).Jika anda terima perlesenan, pilihlah salah satu item dibawah ini. $_CLICK +# ^Custom +Custom +# ^ComponentsText +Beri tanda dicheckbox pada komponen yang ingin dimasukkan and hilangkan tanda pada komponen yang tidak perlu dimasukkan. $_CLICK +# ^ComponentsSubText1 +Pilih kemasukan: +# ^ComponentsSubText2_NoInstTypes +Pilih komponen-komponen untuk dimasukkan: +# ^ComponentsSubText2 +Atau, pilih komponen berikut untuk dimasukkan: +# ^UnComponentsText +Beri tanda dicheckbox pada komponen yang ingin dimasukkan and hilangkan tanda pada komponen yang tidak perlu dimasukkan. $_CLICK +# ^UnComponentsSubText1 +Pilih tipe un-kemasukan: +# ^UnComponentsSubText2_NoInstTypes +Pilih komponen-komponen untuk di buang: +# ^UnComponentsSubText2 +Atau, pilih komponen berikut untuk di buang: +# ^DirText +Setup akan memasukkan $(^NameDA) pada folder berikut. Untuk memilih folder lainnya, klik Selusur dan pilih folder pilihan anda. $_CLICK +# ^DirSubText +Folder tujuan +# ^DirBrowseText +Pilih folder untuk memasukkan $(^NameDA): +# ^UnDirText +Setup akan membuang $(^NameDA) dari folder berikut. Untuk memilih folder lainnya, klik Selusur dan pilih folder pilihan anda. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Pilih folder untuk dibuang $(^NameDA): +# ^SpaceAvailable +"Ruang cakera keras yang ada: " +# ^SpaceRequired +"Ruang cakera keras yang diperlukan: " +# ^UninstallingText +$(^NameDA) akan buang dari folder berikut. $_CLICK +# ^UninstallingSubText +Membuang: +# ^FileError +Tidak dapat menulis pada fail: \r\n\t"$0"\r\nKlik abort untuk membatalkan kemasukan,\r\nretry untuk cuba lagi, atau\r\nignore untuk abaikan fail ini. +# ^FileError_NoIgnore +Tidak dapat menulis pada fail: \r\n\t"$0"\r\nKlik retry untuk cuba lagi, atau\r\ncancel untuk batalkan kemasukan +# ^CantWrite +"Gagal menulis pada: " +# ^CopyFailed +Gagal menyalin +# ^CopyTo +"Menyalin ke " +# ^Registering +"Mendaftarkan modul: " +# ^Unregistering +"Melepaskan modul: " +# ^SymbolNotFound +"Symbol tidak jumpa : " +# ^CouldNotLoad +"Tidak dapat membuka: " +# ^CreateFolder +"Membuat folder: " +# ^CreateShortcut +"Membuat pintasan: " +# ^CreatedUninstaller +"Membuat program unistall: " +# ^Delete +"Memadam fail: " +# ^DeleteOnReboot +"Akan dipadam ketika reboot: " +# ^ErrorCreatingShortcut +"Tidak dapat membuat pintasan: " +# ^ErrorCreating +"Ralat penciptaan: " +# ^ErrorDecompressing +Ralat ketika membuka data! Program Installer rosak +# ^ErrorRegistering +Ralat mendaftarkan modul DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Menjalankan: " +# ^Extract +"Mengekstrak: " +# ^ErrorWriting +"Ekstrak: ralat ketika menulis ke fail " +# ^InvalidOpcode +Installer rosak: opcode tidak lengkap +# ^NoOLE +"OLE tidak ditemukan: " +# ^OutputFolder +"Folder tujuan: " +# ^RemoveFolder +"Menghapuskan folder: " +# ^RenameOnReboot +"Menamakan semula pada reboot: " +# ^Rename +"Menamakan semula: " +# ^Skipped +"Diabaikan: " +# ^CopyDetails +Salin terperinci ke clipboard +# ^LogInstall +Catat proses kemasukan +# ^Byte +Bait +# ^Kilo +Kilo +# ^Mega +Mega +# ^Giga +Giga \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Malay.nsh b/installer/NSIS/Contrib/Language files/Malay.nsh new file mode 100644 index 0000000..da5bf61 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Malay.nsh @@ -0,0 +1,121 @@ +;Language: Malay (1086) +;By muhammadazwa@yahoo.com + +!insertmacro LANGFILE "Malay" "Malay" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Assalamualaikum, Selamat datang ke $(^NameDA) Setup Wizard" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Setup Wizard akan membantu anda untuk memasukkan $(^NameDA).$\r$\n$\r$\nSila tutup program aplikasi yang lain sebelum Setup ini dimulakan. Ini supaya tiada proses reboot komputer diperlukan.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Selamat datang ke $(^NameDA) Uninstall Wizard" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Uninstall Wizard akan membantu anda pada proses membuang $(^NameDA).$\r$\n$\r$\nSebelum membuang, pastikan dulu $(^NameDA) dimatikan.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Perlesenan" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Sila baca teks lesen berikut sebelum memasukkan $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Jika anda bersetuju, klik Saya setuju untuk teruskan. Anda mesti setuju untuk sebelum aplikasi dapat dimasukkan $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jika anda bersetuju dengan syarat-syarat lesen, sila tanda dicheckbox. Anda mesti setuju sebelum memasukkan $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jika anda terima semua yang ada di lesen, pilihlah salah satu item dibawah ini. Anda mesti setuju sebelum memasukkan $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Tentang Lesen" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Sila baca teks lesen sebelum membuang $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Jika anda menerima lesen, klik Saya setuju untuk teruskan. Anda mesti setuju untuk dapat membuang $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jika anda menerima semua yang ada di lesen, beri tanda dicheckbox. Anda mesti setuju untuk dapat membuang $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jika anda menerima semua yang ada di lesen, pilihlah salah satu item dibawah ini. Anda mesti setuju untuk dapat membuang $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Tekan Page Down untuk melihat teks selebihnya." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Pilih Komponen" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Pilih fungsi-fungsi dari $(^NameDA) yang ingin dimasukkan." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Pilih Komponen" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Pilih fungsi-fungsi $(^NameDA) yang ingin dibuang." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Penerangan" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Alihkan tetikus ke komponen untuk mengetahui penerangannya." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Alihkan tetikus ke komponen untuk mengetahui penerangannya." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Pilih Lokasi Kemasukan" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Pilih folder untuk memasukkan $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Pilih Lokasi Uninstall" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Pilih folder untuk meng-uninstall $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Pemasangan" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Sila tunggu ketika $(^NameDA) sedang dimasukkan." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Proses Selesai" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Setup sudah selesai." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Proses Dibatalkan" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Setup terbatal." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Uninstall" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Sila tunggu ketika $(^NameDA) sedang di-buang." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Proses Uninstall Selesai" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Uninstall sudah selesai." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Proses Uninstall Dibatalkan" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Uninstall belum selesai secara sempurna." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Menyelesaikan $(^NameDA) Setup Wizard" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) sudah dimasukkan di komputer anda.$\r$\n$\r$\nKlik Selesai untuk menutup Setup Wizard." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Komputer anda harus direboot untuk menyelesaikan proses memasukkan $(^NameDA). Apakah anda hendak reboot sekarang juga?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Menyelesaikan $(^NameDA) Uninstall Wizard" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) sudah dibuang dari komputer anda.$\r$\n$\r$\nKlik Selesai untuk menutup Setup Wizard." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Komputer anda harus di reboot untuk menyelesaikan proses membuang $(^NameDA). Reboot sekarang?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reboot sekarang" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Reboot nanti" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Jalankan $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Buka fail Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Selesai" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Pilih Folder Start Menu" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Pilih folder Start Menu untuk meletakkan pintasan $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Pilih folder Start Menu untuk perletakkan pintasan aplikasi ini. Boleh cipta nama folder anda sendiri." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Tidak perlu pintasan" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Buang $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Padam $(^NameDA) dari komputer anda." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Adakan anda yakin ingin membatalkan Setup $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Adakan anda yakin ingin membatalkan proses buang $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Mongolian.nlf b/installer/NSIS/Contrib/Language files/Mongolian.nlf new file mode 100644 index 0000000..c49c2ae --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Mongolian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1104 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1251 +# RTL - anything else than RTL means LTR +- +# Translation by Bayarsaikhan Enkhtaivan. ebayaraa@gmail.com +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) +# ^UninstallCaption +$(^Name) +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< & +# ^NextBtn +&> +# ^AgreeBtn +&Ǻ뺺 +# ^AcceptBtn + Ǻ 뿿 & +# ^DontAcceptBtn + Ǻ 뿿 & +# ^InstallBtn +& +# ^UninstallBtn +& +# ^CancelBtn + +# ^CloseBtn +&Xaax +# ^BrowseBtn +&... +# ^ShowDetailsBtn +& +# ^ClickNext + . +# ^ClickInstall + . +# ^ClickUninstall + . +# ^Name + +# ^Completed + +# ^LicenseText +$(^NameDA)- Ǻ . 뿿 , Ǻ뺺- . +# ^LicenseTextCB +$(^NameDA)- Ǻ . 뿿 , . $_CLICK +# ^LicenseTextRB +$(^NameDA)- Ǻ . 뿿 , . $_CLICK +# ^UnLicenseText +$(^NameDA)- Ǻ . 뿿 , Ǻ뺺- . +# ^UnLicenseTextCB +$(^NameDA)- Ǻ . 뿿 , . $_CLICK +# ^UnLicenseTextRB +$(^NameDA)- Ǻ . 뿿 , . $_CLICK +# ^Custom + +# ^ComponentsText + , ῿ . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 +, 뿿 : +# ^UnComponentsText + , ῿ . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + 뿿 l: +# ^UnComponentsSubText2 +, 뿿 : +# ^DirText +$(^NameDA) . պ . $_CLICK +# ^DirSubText + +# ^DirBrowseText +$(^NameDA)- : +# ^UnDirText +$(^NameDA)- . պ . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +$(^NameDA)- : +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText +$(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\t"$0"\r\n ,\r\n ,\r\n +# ^FileError_NoIgnore + : \r\n\t"$0"\r\n , \r\n +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +" : " +# ^Unregistering +" : " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +": " +# ^CreateFolder +" : " +# ^CreateShortcut +" shortcut: " +# ^CreatedUninstaller +" uninstaller: " +# ^Delete +" : " +# ^DeleteOnReboot +". : " +# ^ErrorCreatingShortcut +"Shortcut : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ! ? +# ^ErrorRegistering +DLL 㿿 +# ^ExecShell +" (ExecShell): " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + : +# ^NoOLE +"OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +". : " +# ^Rename +" : " +# ^Skipped +": " +# ^CopyDetails + Clipboard +# ^LogInstall + +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Mongolian.nsh b/installer/NSIS/Contrib/Language files/Mongolian.nsh new file mode 100644 index 0000000..01662e5 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Mongolian.nsh @@ -0,0 +1,121 @@ +;Language: Mongolian (1104) +;By Bayarsaikhan Enkhtaivan + +!insertmacro LANGFILE "Mongolian" "Mongolian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "$(^NameDA) " + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "$(^NameDA) " + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\n $(^NameDA) .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "$(^NameDA)- 뿿 ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " , Ǻ뺺 뿿 . $(^NameDA)- ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , Ǻ 뿿 . $(^NameDA)- . $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , 뿿 . $(^NameDA)- . $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "$(^NameDA) ." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " , Ǻ뺺 뿿 . $(^NameDA)- ." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , Ǻ 뿿 . $(^NameDA)- . $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , 뿿 . $(^NameDA)- . $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Page Down ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "$(^NameDA)- ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "$(^NameDA)- ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "$(^NameDA) ." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "$(^NameDA)- ." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE " " + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "$(^NameDA)- ." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE " " + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "$(^NameDA) - ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "$(^NameDA) " + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\nҺ ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "$(^NameDA)- . ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "$(^NameDA) " + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) .$\r$\n$\r$\nҺ ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "$(^NameDA) . . ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "." + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " . ." + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) " + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Readme " + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Һ" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Start " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Start $(^NameDA) shortcut- ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Start shortcut . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Do not create shortcuts" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "$(^NameDA)-- " + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA) - ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "$(^Name) - ?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "$(^Name) ?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Norwegian.nlf b/installer/NSIS/Contrib/Language files/Norwegian.nlf new file mode 100644 index 0000000..1370395 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Norwegian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1044 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Jonas Lindstrm (jonasc_88@hotmail.com). Reviewed and fixed by Jan Ivar Beddari +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) installasjon +# ^UninstallCaption +$(^Name) avinstallasjon +# ^LicenseSubCaption +: Lisensavtale +# ^ComponentsSubCaption +: Installasjonsvalg +# ^DirSubCaption +: Installasjonsmappe +# ^InstallingSubCaption +: Installerer +# ^CompletedSubCaption +: Ferdig +# ^UnComponentsSubCaption +: Avinstallasjonsvalg +# ^UnDirSubCaption +: Avinstallasjonsmappe +# ^ConfirmSubCaption +: Bekreft +# ^UninstallingSubCaption +: Avinstallerer +# ^UnCompletedSubCaption +: Ferdig +# ^BackBtn +< &Tilbake +# ^NextBtn +&Neste > +# ^AgreeBtn +&Godta +# ^AcceptBtn +Jeg &godtar vilkrene i lisensavtalen +# ^DontAcceptBtn +Jeg godtar &ikke vilkrene i lisensavtalen +# ^InstallBtn +&Installer +# ^UninstallBtn +&Avinstaller +# ^CancelBtn +Avbryt +# ^CloseBtn +&Lukk +# ^BrowseBtn +Bla &gjennom... +# ^ShowDetailsBtn +Vis &detaljer +# ^ClickNext +Trykk Neste for fortsette. +# ^ClickInstall +Trykk Installer for starte installasjonen. +# ^ClickUninstall +Trykk Avinstaller for starte avinstallasjonen. +# ^Name +Navn +# ^Completed +Ferdig +# ^LicenseText +Vennligst les gjennom lisensavtalen fr du installerer $(^Name). Hvis du godtar vilkrene i avtalen, trykk p Godta. +# ^LicenseTextCB +Vennligst les gjennom lisensavtalen fr du installerer $(^Name). Hvis du godtar vilkrene i avtalen, merk av under. $_CLICK +# ^LicenseTextRB +Vennligst les gjennom lisensavtalen fr du installerer $(^Name). Hvis du godtar vilkrene i avtalen, velg det frste alternativet. $_CLICK +# ^UnLicenseText +Vennligst les gjennom lisensavtalen fr du avinstallerer $(^Name). Hvis du godtar vilkrene i avtalen, trykk p Godta. +# ^UnLicenseTextCB +Vennligst les gjennom lisensavtalen fr du avinstallerer $(^Name). Hvis du godtar vilkrene i avtalen, merk av under. $_CLICK +# ^UnLicenseTextRB +Vennligst les gjennom lisensavtalen fr du avinstallerer $(^Name). Hvis du godtar vilkrene i avtalen, velg det frste alternativet. $_CLICK +# ^Custom +Egendefinert +# ^ComponentsText +Merk komponentene du vil installere og fjern merkingen for de du ikke vil installere. $_CLICK +# ^ComponentsSubText1 +Velg hvilken mte du vil installere p: +# ^ComponentsSubText2_NoInstTypes +Merk komponenter du vil installere: +# ^ComponentsSubText2 +Eller merk de valgfrie komponentene du nsker installere: +# ^UnComponentsText +Merk komponentene du vil avinstallere og fjern merkingen for de du vil beholde. $_CLICK +# ^UnComponentsSubText1 +Velg hvilken mte du vil avinstallere p: +# ^UnComponentsSubText2_NoInstTypes +Merk komponenter du vil avinstallere: +# ^UnComponentsSubText2 +Eller merk de valgfrie komponentene du nsker avinstallere: +# ^DirText +$(^Name) vil bli installert i flgende mappe. For velge en annen mappe, trykk Bla gjennom. $_CLICK +# ^DirSubText +Mlmappe +# ^DirBrowseText +Velg mappe du vil installere $(^Name) i: +# ^UnDirText +$(^Name) i flgende mappe vil bli avinstallert. For velge en annen mappe, trykk Bla gjennom. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Velg mappe du vil avinstallere $(^Name) fra: +# ^SpaceAvailable +"Ledig plass: " +# ^SpaceRequired +"Ndvendig plass: " +# ^UninstallingText +Denne veiviseren vil avinstallere $(^Name) fra din datamaskin. $_CLICK +# ^UninstallingSubText +Avinstallerer fra: +# ^FileError +Feil under pning av fil for skriving: \r\n\t\"$0\"\r\nTrykk Avbryt for avbryte installasjonen,\r\nPrv igjen for prve igjen, eller\r\nIgnorer for hoppe over denne filen +# ^FileError_NoIgnore +Feil under pning av fil for skriving: \r\n\t\"$0\"\r\nTrykk Prv igjen for prve igjen, or\r\neller Avbryt for avbryte installasjonen +# ^CantWrite +"Kan ikke skrive: " +# ^CopyFailed +Kopiering mislyktes +# ^CopyTo +"Kopier til " +# ^Registering +"Registrerer: " +# ^Unregistering +""Avregistrerer: " +# ^SymbolNotFound +"Kunne ikke finne symbol: " +# ^CouldNotLoad +"Kunne ikke laste: " +# ^CreateFolder +"Lag mappe: " +# ^CreateShortcut +"Lag snarvei: " +# ^CreatedUninstaller +"Avinstallasjon laget: " +# ^Delete +"Slett fil: " +# ^DeleteOnReboot +"Slett ved omstart: " +# ^ErrorCreatingShortcut +"Feil under opprettelse av snarvei: " +# ^ErrorCreating +"Feil under opprettelse av: " +# ^ErrorDecompressing +Feil under utpakking av data! Installasjonsprogrammet kan vre skadet. +# ^ErrorRegistering +Feil under registrering av DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Kjre: " +# ^Extract +"Pakk ut: " +# ^ErrorWriting +"Pakk ut: Feil under skriving til fil " +# ^InvalidOpcode +Installasjonsprogrammet er skadet: ukjent kode +# ^NoOLE +"Ingen OLE for: " +# ^OutputFolder +"Ut-mappe: " +# ^RemoveFolder +"Fjern mappe: " +# ^RenameOnReboot +"Gi nytt navn ved omstart: " +# ^Rename +"Gi nytt navn: " +# ^Skipped +"Hoppet over: " +# ^CopyDetails +Kopier detaljer til utklippstavlen +# ^LogInstall +Loggfr installasjonsprosessen +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Norwegian.nsh b/installer/NSIS/Contrib/Language files/Norwegian.nsh new file mode 100644 index 0000000..c7c9d8b --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Norwegian.nsh @@ -0,0 +1,121 @@ +;Language: Norwegian (2068) +;By Jonas Lindsrm (jonasc_88@hotmail.com) Reviewed and fixed by Jan Ivar Beddari, d0der at online.no + +!insertmacro LANGFILE "Norwegian" "Norwegian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Velkommen til veiviseren for installasjon av $(^NameDA) " + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Denne veiviseren vil lede deg gjennom installasjonen av $(^NameDA).$\r$\n$\r$\nDet anbefales at du avslutter alle andre programmer fr du fortsetter. Dette vil la installasjonsprogrammet forandre p systemfiler uten at du m starte datamaskinen p nytt.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Velkommen til veiviseren for avinstallasjon av $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Denne veiviseren vil lede deg gjennom avinstallasjonen av $(^NameDA).$\r$\n$\r$\nFr du fortsetter m du forsikre deg om at $(^NameDA) ikke kjrer.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lisensavtale" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Vennligst les gjennom lisensavtalen fr du starter installasjonen av $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Hvis du godtar lisensavtalen trykk Godta for fortsette. Du m godta lisensavtalen for installere $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Hvis du godtar lisensavtalen, kryss av p merket under. Du m godta lisensavtalen for installere $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Hvis du godtar lisensavtalen, velg det frste alternativet ovenfor. Du m godta lisensavtalen for installere $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lisensavtale" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Vennligst les gjennom lisensavtalen fr du avinstallerer $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Hvis du godtar lisensavtalen trykk Godta for fortsette. Du m godta lisensavtalen for avintallere $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Hvis du godtar lisensavtalen, kryss av p merket under. Du m godta lisensavtalen for avinstallere $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Hvis du godtar lisensavtalen, velg det frste alternativet ovenfor. Du m godta lisensavtalen for avinstallere $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Trykk Page Down knappen for se resten av lisensavtalen." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Velg komponenter" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Velg hvilke deler av $(^NameDA) du nsker installere." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Velg komponenter" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Velg hvilke deler av $(^NameDA) du nsker avinstallere." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beskrivelse" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beveg musen over komponentene for se beskrivelsen." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beveg musen over komponentene for se beskrivelsen." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Velg installasjonsmappe" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Velg hvilken mappe du vil installere $(^NameDA) i." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Velg mappe for avinstallasjon" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Velg mappen du vil avinstallere $(^NameDA) fra." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installasjonen pgr" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Vennligst vent mens $(^NameDA) blir installert." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installasjonen er ferdig" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Installasjonen ble fullfrt uten feil." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installasjonen er avbrutt" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Installasjonen ble ikke fullfrt riktig." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Avinstallasjon pgr" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Vennligst vent mens $(^NameDA) blir avinstallert." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Avinstallasjon ferdig" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Avinstallasjonen ble utfrt uten feil." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Avinstallasjon avbrutt" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Avinstallasjonen ble ikke utfrt riktig." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Avslutter $(^NameDA) installasjonsveiviser" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) er klart til bruk p din datamskin.$\r$\n$\r$\nTrykk Ferdig for avslutte installasjonsprogrammet." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Du m starte datamaskinen p nytt for fullfre installasjonen av $(^NameDA). Vil du starte datamaskinen p nytt n?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Fullfrer avinstallasjonen av $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) har blitt avinstallert fra din datamaskin.$\r$\n$\r$\nTrykk p ferdig for avslutte denne veiviseren." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Datamaskinen m starte p nytt for fullfre avinstallasjonen av $(^NameDA). Vil du starte datamaskinen p nytt n?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Ja. Start datamaskinen p nytt n" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Nei. Jeg vil starte datamaskinen p nytt senere" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Kjr $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Vis Readme filen" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Ferdig" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Velg plassering p startmenyen" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Velg hvilken mappe snarveiene til $(^NameDA) skal ligge i." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Velg mappe for snarveiene til programmet. Du kan ogs skrive inn et nytt navn for lage en ny mappe." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ikke lag snarveier" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Avinstaller $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Fjern $(^NameDA) fra din datamaskin." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Er du sikker p at du vil avslutte installasjonen av $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Er du sikker p at du vil avbryte avinstallasjonen av $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/NorwegianNynorsk.nlf b/installer/NSIS/Contrib/Language files/NorwegianNynorsk.nlf new file mode 100644 index 0000000..ae6a41d --- /dev/null +++ b/installer/NSIS/Contrib/Language files/NorwegianNynorsk.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +2068 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Vebjrn Sture, vsture gmail com +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) installasjon +# ^UninstallCaption +$(^Name) avinstallasjon +# ^LicenseSubCaption +: Lisensavtale +# ^ComponentsSubCaption +: Installasjonsval +# ^DirSubCaption +: Installasjonsmappe +# ^InstallingSubCaption +: Installerer +# ^CompletedSubCaption +: Ferdig +# ^UnComponentsSubCaption +: Avinstallasjonsval +# ^UnDirSubCaption +: Avinstallasjonsmappe +# ^ConfirmSubCaption +: Stadfest +# ^UninstallingSubCaption +: Avinstallerer +# ^UnCompletedSubCaption +: Ferdig +# ^BackBtn +< &Attende +# ^NextBtn +&Neste > +# ^AgreeBtn +&Godta +# ^AcceptBtn +Eg &godtek vilkra i lisensavtalen +# ^DontAcceptBtn +Eg godtek &ikkje vilkra i lisensavtalen +# ^InstallBtn +&Installer +# ^UninstallBtn +&Avinstaller +# ^CancelBtn +Avbryt +# ^CloseBtn +&Lat att +# ^BrowseBtn +Bla &gjennom ... +# ^ShowDetailsBtn +Syn &detaljar +# ^ClickNext +Trykk Neste for halda fram. +# ^ClickInstall +Trykk Installer for starta installasjonen. +# ^ClickUninstall +Trykk Avinstaller for starta avinstallasjonen. +# ^Name +Namn +# ^Completed +Ferdig +# ^LicenseText +Ver grei og les gjennom lisensavtalen fr du installerer $(^NameDA). Dersom du godtek vilkra i avtalen, trykk p Godta. +# ^LicenseTextCB +Ver grei og les gjennom lisensavtalen fr du installerer $(^NameDA). Dersom du godtek vilkra i avtalen, merk av under. $_CLICK +# ^LicenseTextRB +Ver grei og les gjennom lisensavtalen fr du installerer $(^NameDA). Dersom du godtek vilkra i avtalen, vel det fyrste alternativet. $_CLICK +# ^UnLicenseText +Ver grei og les gjennom lisensavtalen fr du avinstallerer $(^NameDA). Dersom du godtek vilkra i avtalen, trykk p Godta. +# ^UnLicenseTextCB +Ver grei og les gjennom lisensavtalen fr du avinstallerer $(^NameDA). Dersom du godtek vilkra i avtalen, merk av under. $_CLICK +# ^UnLicenseTextRB +Ver grei og les gjennom lisensavtalen fr du avinstallerer $(^NameDA). Dersom du godtek vilkra i avtalen, vel det fyrste alternativet. $_CLICK +# ^Custom +Eigendefinert +# ^ComponentsText +Merk komponentane du vil installera og fjern merkinga for dei du ikkje vil installera. $_CLICK +# ^ComponentsSubText1 +Vel kva mte du vil installera p: +# ^ComponentsSubText2_NoInstTypes +Merk komponentar du vil installera: +# ^ComponentsSubText2 +Eller merk dei valfrie komponentane du ynskjer installera: +# ^UnComponentsText +Merk komponentane du vil avinstallera og fjern merkinga for dei du vil ta vare p. $_CLICK +# ^UnComponentsSubText1 +Vel kva mte du vil avinstallera p: +# ^UnComponentsSubText2_NoInstTypes +Merk komponentar du vil avinstallera: +# ^UnComponentsSubText2 +Eller merk dei valfrie komponentane du ynskjer avinstallera: +# ^DirText +$(^NameDA) vil verta installert i fylgjande mappe. For velja ei anna mappe, trykk Bla gjennom. $_CLICK +# ^DirSubText +Mlmappe +# ^DirBrowseText +Vel mappe du vil installera $(^NameDA) i: +# ^UnDirText +$(^NameDA) i fylgjande mappe vil verta avinstallert. For velja ei anna mappe, trykk Bla gjennom. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Vel mappe du vil avinstallera $(^NameDA) fr: +# ^SpaceAvailable +"Ledig plass: " +# ^SpaceRequired +"Naudsynt plass: " +# ^UninstallingText +Denne vegvisaren vil avinstallera $(^NameDA) fr din datamaskin. $_CLICK +# ^UninstallingSubText +Avinstallerer fr: +# ^FileError +Feil under opning av fil for skriving: \r\n\t\"$0\"\r\nTrykk Avbryt for avbryta installasjonen,\r\nPrv igjen for prva igjen, eller\r\nIgnorer for hoppa over denne fila +# ^FileError_NoIgnore +Feil under opning av fil for skriving: \r\n\t\"$0\"\r\nTrykk Prv igjen for prva igjen, or\r\neller Avbryt for avbryta installasjonen +# ^CantWrite +"Kan ikkje skriva: " +# ^CopyFailed +Kopiering mislukka +# ^CopyTo +"Kopier til " +# ^Registering +"Registrerer: " +# ^Unregistering +""Avregistrerer: " +# ^SymbolNotFound +"Kunne ikkje finna symbol: " +# ^CouldNotLoad +"Kunne ikkje lasta: " +# ^CreateFolder +"Lag mappe: " +# ^CreateShortcut +"Lag snarveg: " +# ^CreatedUninstaller +"Avinstallasjon laga: " +# ^Delete +"Slett fil: " +# ^DeleteOnReboot +"Slett ved omstart: " +# ^ErrorCreatingShortcut +"Feil under oppretting av snarveg: " +# ^ErrorCreating +"Feil under oppretting av: " +# ^ErrorDecompressing +Feil under utpakking av data! Installasjonsprogrammet kan vera skadd. +# ^ErrorRegistering +Feil under registrering av DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Kyra: " +# ^Extract +"Pakk ut: " +# ^ErrorWriting +"Pakk ut: Feil under skriving til fil " +# ^InvalidOpcode +Installasjonsprogrammet er skadd: ukjend kode +# ^NoOLE +"Ingen OLE for: " +# ^OutputFolder +"Ut-mappe: " +# ^RemoveFolder +"Fjern mappe: " +# ^RenameOnReboot +"Gje nytt namn ved omstart: " +# ^Rename +"Gje nytt namn: " +# ^Skipped +"Hoppa over: " +# ^CopyDetails +Kopier detaljar til utklyppstavla +# ^LogInstall +Loggfr installasjonsprosessen +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G + diff --git a/installer/NSIS/Contrib/Language files/NorwegianNynorsk.nsh b/installer/NSIS/Contrib/Language files/NorwegianNynorsk.nsh new file mode 100644 index 0000000..4da2cd9 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/NorwegianNynorsk.nsh @@ -0,0 +1,121 @@ +;Language: Norwegian nynorsk (2068) +;By Vebjoern Sture and Hvard Mork (www.firefox.no) + +!insertmacro LANGFILE "NorwegianNynorsk" "Norwegian nynorsk" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Velkommen til $(^NameDA) innstallasjonsvegvisar" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Denne vegvisaren vil leie deg gjennom installeringa av $(^NameDA).$\n$\nDet er tilrdd at du avsluttar alle andre program fr du held fram. Dette vil la installeringsprogrammet oppdatera systemfiler utan at du m starta datamaskinen p nytt.$\n$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Velkommen til avinstallering av $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Denne vegvisaren vil leie deg gjennom avinstalleringen av $(^NameDA).$\n$\nFr du fortsetter m du forsikre deg om at $(^NameDA) ikkje er opent.$\n$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lisensavtale" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Les gjennom lisensavtalen fr du startar installeringa av $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Trykk p Godta dersom du godtar betingelsane i avtala. Du m godta avtala for installere $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Trykk p avkryssingsboksen nedanfor nedanfor dersom du godtar betingelsane i avtala. Du m godta avtala for installere $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Vel det frste alternativet nedanfor dersom du godtek vilkra i avtala. Du m godta avtala for installera $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lisensavtale" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Les gjennom lisensavtalen fr du startar avinstalleringa av $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Trykk p Godta dersom du godtar betingelsane i avtala. Du m godta avtala for avinstallera $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Trykk p avkryssingsboksen nedanfor nedanfor dersom du godtar betingelsane i avtala. Du m godta avtala for avinstallera $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Vel det frste alternativet nedanfor dersom du godtar betingelsane i avtala. Du m godta avtala for avinstallera $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Trykk Page Down-knappen for sj resten av lisensavtala." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Vel komponentar" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Vel kva delar av $(^NameDA) du ynskjer installera." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Vel funksjonar" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Vel kva for funksjonar du vil avinstallera i $(^NameDA)." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beskriving" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beveg musa over komponentene for sj beskrivinga." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Beveg musa over komponentene for sj beskrivinga." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Vel installasjonsmappe" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Vel kva mappe du vil installera $(^NameDA) i." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Vel avinstalleringplassering" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Vel mappa du vil avinstallere $(^NameDA) fr." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installerer" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Vent mens $(^NameDA) blir installert." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installeringa er fullfrt" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Installeringa vart fullfrt." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installeringa vart avbroten" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Installeringa vart ikkje fullfrt." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Avinstallerer" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Vent medan $(^NameDA) vert avinstallert." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Avinstallering ferdig" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Avinstallering ble utfrt uten feil." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Avinstallering broten" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Avinstallering ble ikkje utfrt riktig." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Installering fullfrt" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) er installert og klar til bruk.$\n$\nTrykk p Fullfr for avslutte installeringa." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Du m starta datamaskinen p nytt for fullfra installeringa av $(^NameDA). Vil du starta p nytt no?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Fullfrer avinstalleringa av $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) er no avinstallert fr datamaskina di.$\n$\nTrykk p Fullfr for avslutta denne vegvisaren." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Datamaskinen m starta p nytt for fullfra avinstalleringa av $(^NameDA). Vil du starta datamaskina p nytt no?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Start p nytt no" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Eg vil starta p nytt seinare" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Kyr $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Syn lesmeg" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Fullfr" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Vel mappe p startmenyen" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Vel kva mappe snarvegane til $(^NameDA) skal liggja i." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Vel mappa du vil oppretta snarvegane til programmet i. Du kan g skriva inn eit nytt namn for laga ei ny mappe." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ikkje opprett snarvegar" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Avinstaller $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Slett $(^NameDA) fr datamaskinen." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Er du viss p at du vil avslutta installeringa av $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Er du viss p at du vil avbryta avinstalleringa av $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Polish.nlf b/installer/NSIS/Contrib/Language files/Polish.nlf new file mode 100644 index 0000000..f680d89 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Polish.nlf @@ -0,0 +1,195 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1045 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by Piotr Murawski & Rafa Lampe +# Updated by cube and SYSTEMsoft Group +# Corrections by Marek Stepien - http://www.aviary.pl/ +# Updated by Pawe Porwisz, http://www.pepesoft.tox.pl +# Corrected by Mateusz Gola (aka Prozac) - http://www.videopedia.pl/avirecomp +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Instalator programu $(^Name) +# ^UninstallCaption +Deinstalator programu $(^Name) +# ^LicenseSubCaption +: Umowa licencyjna +# ^ComponentsSubCaption +: Opcje instalacji +# ^DirSubCaption +: Folder instalacyjny +# ^InstallingSubCaption +: Instalowanie plikw +# ^CompletedSubCaption +: Zakoczono +# ^UnComponentsSubCaption +: Opcje deinstalacji +# ^UnDirSubCaption +: Folder deinstalacyjny +# ^ConfirmSubCaption +: Potwierdzenie +# ^UnDirSubCaption +: Deinstalowanie plikw +# ^UnCompletedSubCaption +: Zakoczono +# ^BackBtn +< &Wstecz +# ^NextBtn +&Dalej > +# ^AgreeBtn +&Zgadzam si +# ^AcceptBtn +&Akceptuj warunki umowy licencyjnej +# ^DontAcceptBtn +&Nie akceptuj warunkw umowy licencyjnej +# ^InstallBtn +&Zainstaluj +# ^UninstallBtn +&Odinstaluj +# ^CancelBtn +Anuluj +# ^CloseBtn +&Zamknij +# ^BrowseBtn +&Przegldaj... +# ^ShowDetailsBtn +Poka &szczegy +# ^ClickNext +Kliknij Dalej, aby kontynuowa. +# ^ClickInstall +Kliknij Zainstaluj, aby rozpocz instalacj. +# ^ClickUninstall +Kliknij Odinstaluj, aby rozpocz deinstalacj. +# ^Name +Nazwa +# ^Completed +Zakoczono +# ^LicenseText +Przed zainstalowaniem $(^NameDA) przeczytaj umow licencyjn. Jeli akceptujesz wszystkie warunki umowy, kliknij Zgadzam si. +# ^LicenseTextCB +Przed zainstalowaniem $(^NameDA) przeczytaj umow licencyjn. Jeli akceptujesz wszystkie warunki umowy, kliknij pole wyboru poniej. $_CLICK. +# ^LicenseTextRB +Przed zainstalowaniem $(^NameDA) przeczytaj umow licencyjn. Jeli akceptujesz wszystkie warunki umowy, wybierz pierwsz opcj poniej. $_CLICK. +# ^UnLicenseText +Przed odinstalowaniem $(^NameDA) przeczytaj umow licencyjn. Jeli akceptujesz wszystkie warunki umowy, kliknij Zgadzam si. +# ^UnLicenseTextCB +Przed odinstalowaniem $(^NameDA) przeczytaj umow licencyjn. Jeli akceptujesz wszystkie warunki umowy, kliknij pole wyboru poniej. $_CLICK. +# ^UnLicenseTextRB +Przed odinstalowaniem $(^NameDA) przeczytaj licencj. Jeli akceptujesz wszystkie warunki umowy, wybierz pierwsz opcj poniej. $_CLICK. +# ^Custom +Uytkownika +# ^ComponentsText +Zaznacz komponenty, ktre chcesz zainstalowa i odznacz te, ktrych nie chcesz instalowa. $_CLICK +# ^ComponentsSubText1 +Wybierz typ instalacji: +# ^ComponentsSubText2_NoInstTypes +Wybierz komponenty do zainstalowania: +# ^ComponentsSubText2 +Albo wybierz opcjonalne komponenty, ktre chcesz zainstalowa: +# ^UnComponentsText +Zaznacz komponenty, ktre chcesz odinstalowa i odznacz te, ktre nie zostan odinstalowane. $_CLICK +# ^UnComponentsSubText1 +Wybierz typ deinstalacji: +# ^UnComponentsSubText2_NoInstTypes +Wybierz komponenty do odinstalowania: +# ^UnComponentsSubText2 +Albo wybierz opcjonalne komponenty, ktre chcesz odinstalowa: +# ^DirText +Instalator zainstaluje program $(^NameDA) w nastpujcym folderze. Aby zainstalowa w innym folderze, kliknij Przegldaj i wybierz folder. $_CLICK +# ^DirSubText +Folder docelowy +# ^DirBrowseText +Wybierz folder instalacyjny $(^NameDA): +# ^UnDirText +Deinstalator usunie $(^NameDA) z nastpujcego folderu. Aby odinstalowa z innego folderu, kliknij Przegldaj i wybierz folder. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Wybierz folder, z ktrego zostanie odinstalowany program $(^NameDA): +# ^SpaceAvailable +"Dostpne miejsce: " +# ^SpaceRequired +"Wymagane miejsce: " +# ^UninstallingText +Ten kreator odinstaluje $(^NameDA) z Twojego komputera. $_CLICK +# ^UninstallingSubText +Deinstalacja z: +# ^FileError +Bd otwarcia pliku do zapisu: \r\n\r\n$0\r\n\r\nWybierz Anuluj, aby przerwa instalacj,\r\nPonw, aby ponowi zapis do pliku lub\r\nIgnoruj, aby pomin ten plik. +# ^FileError_NoIgnore +Bd otwarcia pliku do zapisu: \r\n\r\n$0\r\n\r\nWybierz Ponw, aby ponowi zapis do pliku lub\r\nAnuluj, aby przerwa instalacj. +# ^CantWrite +"Nie mona zapisa: " +# ^CopyFailed +Bd kopiowania +# ^CopyTo +"Kopiuj do " +# ^Registering +"Rejestrowanie: " +# ^Unregistering +"Odrejestrowywanie: " +# ^SymbolNotFound +"Nie mona odnale symbolu: " +# ^CouldNotLoad +"Nie mona wczyta: " +#^CreateFolder +"Utwrz folder: " +# ^CreateShortcut +"Utwrz skrt: " +# ^CreatedUninstaller +"Utworzono deinstalator: " +# ^Delete +"Usu plik: " +# ^DeleteOnReboot +"Usu przy ponownym uruchomieniu: " +# ^ErrorCreatingShortcut +"Bd tworzenia skrtu: " +# ^ErrorCreating +"Bd tworzenia: " +# ^ErrorDecompressing +Bd rozpakowywania danych! Uszkodzony instalator? +# ^ErrorRegistering +Bd rejestracji pliku DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Uruchom: " +# ^Extract +"Rozpakuj: " +# ^ErrorWriting +"Rozpakuj: bd zapisu do pliku " +# ^InvalidOpcode +Instalator uszkodzony: nieprawidowy kod operacji +# ^NoOLE +"Brak OLE dla: " +# ^OutputFolder +"Folder wyjciowy: " +# ^RemoveFolder +"Usu folder: " +# ^RenameOnReboot +"Zmie nazw przy ponownym uruchomieniu: " +# ^Rename +"Zmie nazw: " +# ^Skipped +"Pominite: " +# ^CopyDetails +Kopiuj szczegy do schowka +# ^LogInstall +Rejestruj przebieg instalacji +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Polish.nsh b/installer/NSIS/Contrib/Language files/Polish.nsh new file mode 100644 index 0000000..24da76a --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Polish.nsh @@ -0,0 +1,132 @@ +;Language: Polish (1045) +;By Piotr Murawski & Rafa Lampe +;Updated by cube and SYSTEMsoft Group +;Updated by Pawe Porwisz, http://www.pepesoft.tox.pl +;Corrected by Mateusz Gola (aka Prozac) - http://www.videopedia.pl/avirecomp + +!insertmacro LANGFILE "Polish" "Polski" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Witamy w kreatorze instalacji programu $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Kreator ten pomoe Ci zainstalowa program $(^NameDA).$\r$\n$\r$\nZalecane jest zamknicie wszystkich uruchomionych programw przed rozpoczciem instalacji. Pozwoli to na uaktualnienie niezbdnych plikw systemowych bez koniecznoci ponownego uruchamiania komputera.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Witamy w kreatorze deinstalacji $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Kreator poprowadzi Ci przez proces deinstalacji $(^NameDA).$\r$\n$\r$\nPrzed rozpoczciem deinstalacji programu, upewnij si, czy $(^NameDA) NIE jest wanie uruchomiony.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Umowa licencyjna" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Przed instalacj programu $(^NameDA) zapoznaj si z warunkami licencji." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Jeeli akceptujesz warunki umowy, wybierz Zgadzam si, aby kontynuowa. Musisz zaakceptowa warunki umowy, aby zainstalowa $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jeeli akceptujesz warunki umowy, zaznacz pole wyboru poniej, aby kontynuowa. Musisz zaakceptowa warunki umowy, aby zainstalowa $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jeeli akceptujesz warunki umowy, wybierz pierwsz opcj poniej, aby kontynuowa. Musisz zaakceptowa warunki umowy, aby zainstalowa $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Umowa licencyjna" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Przed deinstalacj programu $(^NameDA) zapoznaj si z warunkami licencji." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Jeeli akceptujesz warunki umowy, wybierz Zgadzam si, aby kontynuowa. Musisz zaakceptowa warunki umowy, aby odinstalowa $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Jeeli akceptujesz warunki umowy, zaznacz pole wyboru poniej, aby kontynuowa. Musisz zaakceptowa warunki umowy, aby odinstalowa $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Jeeli akceptujesz warunki umowy, wybierz pierwsz opcj poniej, aby kontynuowa. Musisz zaakceptowa warunki umowy, aby odinstalowa $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Nacinij klawisz Page Down, aby zobaczy reszt umowy." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Wybierz komponenty" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Wybierz komponenty programu $(^NameDA), ktre chcesz zainstalowa." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Wybierz komponenty" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Wybierz, ktre elementy $(^NameDA) chcesz odinstalowa." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Opis" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Przesu kursor myszy nad komponent, aby zobaczy jego opis." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Przesu kursor myszy nad komponent, aby zobaczy jego opis." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Wybierz lokalizacj dla instalacji" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Wybierz folder, w ktrym ma by zainstalowany $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Wybr miejsca deinstalacji" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Wybierz folder, z ktrego chcesz odinstalowa $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalacja" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Prosz czeka, podczas gdy $(^NameDA) jest instalowany." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Zakoczono instalacj" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Instalacja zakoczona pomylnie." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalacja przerwana" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Instalacja nie zostaa zakoczona pomylnie." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Deinstalacja" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Prosz czeka, $(^NameDA) jest odinstalowywany." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Zakoczono odinstalowanie" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Odinstalowanie zakoczone pomylnie." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Deinstalacja przerwana" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Deinstalacja nie zostaa zakoczona pomylnie." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Koczenie pracy kreatora instalacji $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) zosta pomylnie zainstalowany na Twoim komputerze.$\r$\n$\r$\nKliknij Zakocz, aby zakoczy dziaanie kreatora." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Twj komputer musi zosta ponownie uruchomiony, aby zakoczy instalacj programu $(^NameDA). Czy chcesz zrobi to teraz?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Koczenie pracy kreatora deinstalacji $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) zosta odinstalowany z Twojego komputera.$\r$\n$\r$\nKliknij Zakocz, aby zakoczy dziaanie kreatora." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Twj komputer musi zosta ponownie uruchomiony w celu zakoczenia deinstalacji programu $(^NameDA). Czy chcesz zrobi to teraz?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Uruchom ponownie teraz" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Sam uruchomi ponownie komputer pniej" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Uruchom program $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Poka plik ReadMe" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Zakocz" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Wybierz folder w menu Start" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Wybierz folder menu Start, w ktrym zostan umieszczone skrty do programu" + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Wybierz folder w menu Start, w ktrym chciaby umieci skrty do programu. Moesz take utworzy nowy folder wpisujc jego nazw." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nie twrz skrtw" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Odinstaluj $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Usu $(^NameDA) z Twojego komputera." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Czy na pewno chcesz zakoczy dziaanie instalatora $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Czy na pewno chcesz przerwa proces deinstalacji $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Wybierz uytkownikw" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Wybierz, dla ktrych uytkownikw chcesz zainstalowa program $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Wybierz, czy chcesz zainstalowa program $(^NameDA) tylko dla siebie, czy dla wszystkich uytkownikw tego komputera. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Zainstaluj dla wszystkich uytkownikw tego komputera" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Zainstaluj tylko dla mnie" +!endif diff --git a/installer/NSIS/Contrib/Language files/Portuguese.nlf b/installer/NSIS/Contrib/Language files/Portuguese.nlf new file mode 100644 index 0000000..8ccf71c --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Portuguese.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +2070 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation v4.0.3 by DragonSoull with help from Dre` - Updated by Ramon +# ^Branding +Sistema de Instalao Nullsoft %s +# ^SetupCaption +Instalao de $(^Name) +# ^UninstallCaption +Desinstalao de $(^Name) +# ^LicenseSubCaption +: Contrato de Licena +# ^ComponentsSubCaption +: Opes de instalao +# ^DirSubCaption +: Diretrio de instalao +# ^InstallingSubCaption +: Instalando Ficheiros +# ^CompletedSubCaption +: Concludo +# ^UnComponentsSubCaption +: Opes de Desinstalao +# ^UnDirSubCaption +: Pasta de Desinstalao +# ^ConfirmSubCaption +: Confirmao +# ^UninstallingSubCaption +: Desinstalando +# ^UnCompletedSubCaption +: Concludo +# ^BackBtn +< &Anterior +# ^NextBtn +&Seguinte > +# ^AgreeBtn +&Aceito +# ^AcceptBtn +Eu &aceito os termos do Contrato de Licena +# ^DontAcceptBtn +Eu &no aceito os termos do Contrato de Licena +# ^InstallBtn +&Instalar +# ^UninstallBtn +&Desinstalar +# ^CancelBtn +Cancelar +# ^CloseBtn +&Fechar +# ^BrowseBtn +&Procurar... +# ^ShowDetailsBtn +Ver &Detalhes +# ^ClickNext +Clique em 'Seguinte' para continuar. +# ^ClickInstall +Clique em 'Instalar' para iniciar a instalao. +# ^ClickUninstall +Clique em 'Desinstalar' para iniciar a desinstalao. +# ^Name +Nome +# ^Completed +Concludo +# ^LicenseText +Por favor reveja o acordo de licensa antes de instalar $(^NameDA). Se concorda com todos os termos da licensa, clique em 'Aceito'. +# ^LicenseTextCB +Por favor reveja o acordo de licensa antes de instalar $(^NameDA). Se concorda com todos os termos da licensa, clique na caixa de seleo abaixo. $_CLICK +# ^LicenseTextRB +Por favor reveja o acordo de licensa antes de instalar $(^NameDA). Se concorda com todos os termos da licensa, escolha a primeira opo abaixo. $_CLICK +# ^UnLicenseText +Por favor reveja o acordo de licensa antes de desinstalar $(^NameDA). Se concorda com todos os termos da licensa, clique em 'Aceito'. +# ^UnLicenseTextCB +Por favor reveja o acordo de licensa antes de desinstalar $(^NameDA). Se concorda com todos os termos da licensa, clique na caixa de seleo abaixo. $_CLICK +# ^UnLicenseTextRB +Por favor reveja o acordo de licensa antes de desinstalar $(^NameDA). Se concorda com todos os termos da licensa, escolha a primeira opo abaixo. $_CLICK +# ^Custom +Personalizado +# ^ComponentsText +Marque os componentes que deseja instalar e desmarque os componentes que no deseja instalar. $_CLICK +# ^ComponentsSubText1 +Escolha o tipo de instalao: +# ^ComponentsSubText2_NoInstTypes +Escolha os componentes para instalar: +# ^ComponentsSubText2 +Ou, escolha os componentes opcionais que deseja instalar: +# ^UnComponentsText +Marque os componentes que queira desinstalar e vice versa. $_CLICK +# ^UnComponentsSubText1 +Escolha o tipo de desinstalao: +# ^UnComponentsSubText2_NoInstTypes +Escolha os componentes para desinstalar: +# ^UnComponentsSubText2 +Ou, escolha os componentes opcionais que queira desinstalar: +# ^DirText +O $(^NameDA) ser instalado na seguinte pasta. Para instalar numa pasta diferente, clique em 'Procurar...' e escolha outra pasta. $_CLICK +# ^DirSubText +Pasta de Destino +# ^DirBrowseText +Escolha uma pasta para instalar o $(^NameDA): +# ^UnDirText +O $(^NameDA) ser desinstalado da seguinte pasta. Para desinstalar de uma pasta diferente, clique em 'Procurar...' e escolha outra pasta. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Escolha uma pasta de onde ser desinstalado o $(^NameDA): +# ^SpaceAvailable +"Espao disponvel: " +# ^SpaceRequired +"Espao necessrio: " +# ^UninstallingText +$(^NameDA) ser desinstalado da seguinte pasta. $_CLICK +# ^UninstallingSubText +Desinstalando de: +# ^FileError +Erro ao abrir ficheiro para escrita: \r\n\t"$0"\r\nClique em Abortar para abortar a instalao,\r\nRepetir para tentar novamente a escrita do ficheiro, ou\r\nIgnorar para ignorar este ficheiro. +# ^FileError_NoIgnore +Erro ao abrir ficheiro para escrita: \r\n\t"$0"\r\nClique em Repetir para tentar novamente a gravao do ficheiro, ou\r\nCancelar para abortar a instalao. +# ^CantWrite +"No foi possvel escrever: " +# ^CopyFailed +Falha ao copiar +# ^CopyTo +"Copiar para " +# ^Registering +"Registando: " +# ^Unregistering +"Desregistando: " +# ^SymbolNotFound +"Smbolo no encontrado: " +# ^CouldNotLoad +"No foi possvel carregar: " +# ^CreateFolder +"Criando diretrio: " +# ^CreateShortcut +"Criando atalho: " +# ^CreatedUninstaller +"Criando desinstalador: " +# ^Delete +"Apagando ficheiro: " +# ^DeleteOnReboot +"Apagar ao reiniciar: " +# ^ErrorCreatingShortcut +"Erro ao criar atalho: " +# ^ErrorCreating +"Erro ao criar: " +# ^ErrorDecompressing +Erro ao descomprimir dados! Instalador corrompido? +# ^ErrorRegistering +Erro ao registar DLL +# ^ExecShell +"Executando pelo Shell: " +# ^Exec +"Executando: " +# ^Extract +"Extraindo: " +# ^ErrorWriting +"Extraindo: erro ao escrever ficheiro " +# ^InvalidOpcode +Instalador corrompido: cdigo de operao invlido +# ^NoOLE +"Sem OLE para: " +# ^OutputFolder +"Pasta de destino: " +# ^RemoveFolder +"Removendo pasta: " +# ^RenameOnReboot +"Renomear ao reiniciar: " +# ^Rename +"Renomeando: " +# ^Skipped +"Ignorado: " +# ^CopyDetails +Copiar detalhes para a rea de Transfrencia +# ^LogInstall +Registar processo de instalao +# ^Byte +B +# kilo +K +# mega +M +# giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Portuguese.nsh b/installer/NSIS/Contrib/Language files/Portuguese.nsh new file mode 100644 index 0000000..408e0d0 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Portuguese.nsh @@ -0,0 +1,121 @@ +;Language: Portuguese (2070) +;By Ramon + +!insertmacro LANGFILE "Portuguese" "Portugus" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Bem vindo ao Assistente de Instalao do $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Este assistente ajud-lo- durante a instalao do $(^NameDA).$\r$\n$\r$\n recomendado que feche todas as outras aplicaes antes de iniciar a Instalao. Isto permitir que o Instalador actualize ficheiros relacionados com o sistema sem necessidade de reiniciar o computador.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Bem vindo ao Assistente de desinstalao do $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Este assistente ajud-lo- durante a desinstalao do $(^NameDA).$\r$\n$\r$\nAntes de iniciar a desinstalao, certifique-se de que o $(^NameDA) no est em execuo.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Contrato de Licena" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Por favor, verifique os termos da licena antes de instalar o $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Se aceitar os termos da licena, clique em 'Aceito' para continuar. Dever aceitar o contrato para instalar o $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se aceitar os termos da licena, clique na caixa de seleo abaixo. Dever aceitar o contrato para instalar o $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se aceitar os termos da licena, selecione a primeira opo abaixo. Voc deve aceitar o contrato para instalar o $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Contrato de Licena" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Por favor, verifique os termos da licena antes de desinstalar o $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Se aceitar os termos da licena, clique em 'Aceito' para continuar. Dever aceitar o contrato para desinstalar o $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se aceitar os termos da licena, clique na caixa de seleo abaixo. Dever aceitar o contrato para desinstalar o $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se aceitar os termos da licena, selecione a primeira opo abaixo. Voc deve aceitar o contrato para desinstalar o $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Tecle Page Down para ver o restante da licena." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Escolha de Componentes" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Escolha quais as caractersticas do $(^NameDA) que deseja instalar." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Escolher Componentes" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Escolha quais as caractersticas do $(^NameDA) que deseja desinstalar." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descrio" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posicione o rato sobre um componente para ver a sua descrio." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posicione o rato sobre um componente para ver a sua descrio." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Escolha do Local da Instalao" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Escolha a pasta na qual deseja instalar o $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Escolha o Local de desinstalao" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Escolha a pasta de onde pretende desinstalar o $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalando" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Por favor, aguarde enquanto o $(^NameDA) est sendo instalado." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalao Completa" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "A instalao foi concluda com sucesso." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalao Abortada" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "A instalao no foi concluda com sucesso." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Desinstalando" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Por favor, aguarde enquanto o $(^NameDA) est sendo desinstalado." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Desinstalao Completa" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "A desinstalao foi concluda com sucesso." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Desinstalao Abortada" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "A desinstalao no foi concluda com sucesso" +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Concluindo o Assistente de Instalao do $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) foi instalado no seu computador.$\r$\n$\r$\nClique em Terminar para fechar este assistente." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "O seu computador deve ser reiniciado para conclur a instalao do $(^NameDA). Deseja reiniciar agora?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Conclundo o assistente de desisntalao do $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) foi removido do seu computador.$\r$\n$\r$\nClique em Terminar para fechar este assistente." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "O seu computador deve ser reiniciado para conclur a desinstalao do $(^NameDA). Deseja reiniciar agora?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reiniciar Agora" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Eu quero reiniciar manualmente depois" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Executar $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Mostrar Leiame" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Terminar" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Escolha uma Pasta do Menu Iniciar" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Escolha uma pasta do Menu Iniciar para os atalhos do programa." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Selecione uma pasta do Menu Iniciar em que deseja criar os atalhos do programa. Voc pode tambm digitar um nome para criar uma nova pasta. " + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "No criar atalhos" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Desinstalar $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Remover o $(^NameDA) do seu computador." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Deseja realmente cancelar a instalao do $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Deseja realmente cancelar a desinstalao do $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/PortugueseBR.nlf b/installer/NSIS/Contrib/Language files/PortugueseBR.nlf new file mode 100644 index 0000000..810bf80 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/PortugueseBR.nlf @@ -0,0 +1,192 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1046 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Diego Marcos +# Corrections by Felipe +# ^Branding +Sistema de Instalao Nullsoft %s +# ^SetupCaption +Instalao do $(^Name) +# ^UninstallCaption +Desinstalao do $(^Name) +# ^LicenseSubCaption +: Acordo da Licena +# ^ComponentsSubCaption +: Opes da Instalao +# ^DirSubCaption +: Pasta da Instalao +# ^InstallingSubCaption +: Instalando +# ^CompletedSubCaption +: Completado +# ^UnComponentsSubCaption +: Opes da Desinstalao +# ^UnDirSubCaption +: Pasta da Desinstalao +# ^ConfirmSubCaption +: Confirmao +# ^UninstallingSubCaption +: Desinstalando +# ^UnCompletedSubCaption +: Completado +# ^BackBtn +< &Voltar +# ^NextBtn +&Prximo > +# ^AgreeBtn +Eu &Concordo +# ^AcceptBtn +Eu &aceito os termos no Acordo da Licena +# ^DontAcceptBtn +Eu &no aceito os termos no Acordo da Licena +# ^InstallBtn +&Instalar +# ^UninstallBtn +&Desinstalar +# ^CancelBtn +Cancelar +# ^CloseBtn +&Fechar +# ^BrowseBtn +P&rocurar... +# ^ShowDetailsBtn +Mostrar &detalhes +# ^ClickNext +Clique em Prximo para continuar. +# ^ClickInstall +Clique em Instalar para iniciar a instalao. +# ^ClickUninstall +Clique em Desinstalar para iniciar a desinstalao. +# ^Name +Nome +# ^Completed +Completado +# ^LicenseText +Por favor reveja o acordo da licena antes de instalar o $(^NameDA). Se voc aceita todos os termos do acordo, clique em Eu Concordo. +# ^LicenseTextCB +Por favor reveja o acordo da licena antes de instalar o $(^NameDA). Se voc aceita todos os termos do acordo, clique na caixa de seleo abaixo. $_CLICK +# ^LicenseTextRB +Por favor reveja o acordo da licena antes de instalar o $(^NameDA). Se voc aceita todos os termos do acordo, selecione a primeira opo abaixo. $_CLICK +# ^UnLicenseText +Por favor reveja o acordo da licena antes de desinstalar o $(^NameDA). Se voc aceita todos os termos do acordo, clique em Eu Concordo. +# ^UnLicenseTextCB +Por favor reveja o acordo da licena antes de desinstalar o $(^NameDA). Se voc aceita todos os termos do acordo, clique na caixa de seleo abaixo. $_CLICK +# ^UnLicenseTextRB +Por favor reveja o acordo da licena antes de desinstalar o $(^NameDA). Se voc aceita todos os termos do acordo, selecione a primeira opo abaixo. $_CLICK +# ^Custom +Personalizado +# ^ComponentsText +Marque os componentes que voc quer instalar e desmarque os componentes que voc no quer instalar. $_CLICK +# ^ComponentsSubText1 +Selecione o tipo de instalao: +# ^ComponentsSubText2_NoInstTypes +Selecione os componentes a instalar: +# ^ComponentsSubText2 +Ou, selecione os componentes opcionais que voc deseja instalar: +# ^UnComponentsText +Marque os componentes que voc quer desinstalar e desmarque os componentes que voc no quer desinstalar. $_CLICK +# ^UnComponentsSubText1 +Selecione o tipo de desinstalao: +# ^UnComponentsSubText2_NoInstTypes +Selecione os componentes a desinstalar: +# ^UnComponentsSubText2 +Ou, selecione os componentes opcionais que voc deseja desinstalar: +# ^DirText +O Instalador instalar o $(^NameDA) na seguinte pasta. Para instalar em uma pasta diferente, clique em Procurar e selecione outra pasta. $_CLICK +# ^DirSubText +Pasta Destino +# ^DirBrowseText +Selecione a pasta para instalar o $(^NameDA): +# ^UnDirText +O Instalador desinstalar o $(^NameDA) da seguinte pasta. Para desinstalar de uma pasta diferente, clique em Procurar e selecione outra pasta. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Selecione a pasta de onde desinstalar o $(^NameDA): +# ^SpaceAvailable +"Espao disponvel: " +# ^SpaceRequired +"Espao requerido: " +# ^UninstallingText +O $(^NameDA) ser desinstalado da seguinte pasta. $_CLICK +# ^UninstallingSubText +Desinstalando de: +# ^FileError +Erro ao abrir o arquivo para a gravao: \r\n\r\n$0\r\n\r\nClique em Abortar para parar a instalao,\r\nRepetir para tentar de novo, ou\r\nIgnorar para pular este arquivo. +# ^FileError_NoIgnore +Erro ao abrir o arquivo para a gravao: \r\n\r\n$0\r\n\r\nClique em Repetir para tentar de novo, ou\r\nCancelar para parar a instalao. +# ^CantWrite +"No pode escrever: " +# ^CopyFailed +Falhou em copiar +# ^CopyTo +"Copiar para " +# ^Registering +"Registrando: " +# ^Unregistering +"Desfazendo o registro: " +# ^SymbolNotFound +"No pde achar o smbolo: " +# ^CouldNotLoad +"No pde carregar: " +# ^CreateFolder +"Criar pasta: " +# ^CreateShortcut +"Criar atalho: " +# ^CreatedUninstaller +"Desinstalador criado: " +# ^Delete +"Apagar o arquivo: " +# ^DeleteOnReboot +"Apagar ao reiniciar: " +# ^ErrorCreatingShortcut +"Erro ao criar o atalho: " +# ^ErrorCreating +"Erro ao criar: " +# ^ErrorDecompressing +Erro ao descompactar os dados! Instalador corrompido? +# ^ErrorRegistering +Erro ao registar a DLL +# ^ExecShell +"Executar pelo Shell: " +# ^Exec +"Executar: " +# ^Extract +"Extrair: " +# ^ErrorWriting +"Extrair: erro ao gravar o arquivo " +# ^InvalidOpcode +Instalador corrompido: opcode invlido +# ^NoOLE +"Sem OLE para: " +# ^OutputFolder +"Pasta de sada: " +# ^RemoveFolder +"Remover a pasta: " +# ^RenameOnReboot +"Renomear ao reiniciar: " +# ^Rename +"Renomear: " +# ^Skipped +"Ignorado: " +# ^CopyDetails +Copiar os Detalhes para a rea de Transferncia +# ^LogInstall +Pr no Log o processo de instalao +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/PortugueseBR.nsh b/installer/NSIS/Contrib/Language files/PortugueseBR.nsh new file mode 100644 index 0000000..eeb626e --- /dev/null +++ b/installer/NSIS/Contrib/Language files/PortugueseBR.nsh @@ -0,0 +1,129 @@ +;Language: Brazilian Portuguese (1046) +;By Felipe + +!insertmacro LANGFILE "PortugueseBR" "Portugus Brasileiro" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Bem-vindo ao Assistente de Instalao do $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Este assistente guiar voc atravs da instalao do $(^NameDA).$\r$\n$\r$\n recomendado que voc feche todos os outros aplicativos antes de iniciar o Instalador. Isto tornar possvel atualizar os arquivos de sistema relevantes sem ter que reiniciar seu computador.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Bem-vindo ao Assistente de Desinstalao do $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Este assistente guiar voc atravs da desinstalao do $(^NameDA).$\r$\n$\r$\nAntes de iniciar a desinstalao, tenha certeza de que o $(^NameDA) no est em execuo.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Acordo da licena" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Por favor, reveja os termos da licena antes de instalar o $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Se voc aceita os termos do acordo, clique em Eu Concordo para continuar. Voc deve aceitar o acordo para instalar o $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se voc aceita os termos do acordo, clique na caixa de seleo abaixo. Voc deve aceitar o acordo para instalar o $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se voc aceita os termos do acordo, selecione a primeira opo abaixo. Voc deve aceitar o acordo para instalar o $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Acordo da licena" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Por favor, reveja os termos da licena antes de desinstalar o $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Se voc aceita os termos do acordo, clique em Eu Concordo para continuar. Voc deve aceitar o acordo para desinstalar o $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Se voc aceita os termos do acordo, clique na caixa de seleo abaixo. Voc deve aceitar o acordo para desinstalar o $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Se voc aceita os termos do acordo, selecione a primeira opo abaixo. Voc deve aceitar o acordo para desinstalar o $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Pressione Page Down para ver o resto do acordo." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Escolher Componentes" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Escolha quais funes do $(^NameDA) voc quer instalar." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Escolher Componentes" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Escolha quais funes do $(^NameDA) voc quer desinstalar." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descrio" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posicione seu mouse sobre um componente para ver sua descrio." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Posicione seu mouse sobre um componente para ver sua descrio." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Escolher o Local da Instalao" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Escolha a pasta na qual instalar o $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Escolher o Local da Desinstalao" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Escolha a pasta da qual desinstalar o $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalando" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Por favor espere enquanto o $(^NameDA) est sendo instalado." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalao Completa" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "O Instalador completou com sucesso." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalao Abortada" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "O Instalador no completou com sucesso." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Desinstalando" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Por favor espere enquanto o $(^NameDA) est sendo desinstalado." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Desinstalao Completa" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "A desinstalao foi completada com sucesso." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Desinstalao Abortada" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "A desinstalao no foi completada com sucesso." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Completando o Assistente de Instalao do $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "O $(^NameDA) foi instalado no seu computador.$\r$\n$\r$\nClique em Terminar para fechar este assistente." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Seu computador deve ser reiniciado para completar a instalao do $(^NameDA). Voc quer reiniciar agora?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Completando o Assistente de Desinstalao do $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) foi desinstalado do seu computador.$\r$\n$\r$\nClique em Terminar para fechar este assistente." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Seu computador tem que ser reiniciado para completar a desinstalao do $(^NameDA). Voc quer reiniciar agora?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reiniciar agora" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Eu quero reiniciar manualmente depois" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Executar $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Mostrar o Readme" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Terminar" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Escolher a Pasta do Menu Iniciar" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Escolher uma pasta do Menu Iniciar para os atalhos do $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Selecione a pasta do Menu Iniciar na qual voc gostaria de criar os atalhos do programa. Voc pode tambm inserir um nome para criar uma nova pasta." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "No criar atalhos" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Desinstalar o $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Remover o $(^NameDA) do seu computador." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Voc tem certeza de que quer sair do Instalador do $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Voc tem certeza de que quer sair da Desinstalao do $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Escolher Usurios" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Escolher para quais usurios voc quer instalar o $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Selecione se voc quer instalar o $(^NameDA) para si mesmo ou para todos os usurios deste computador. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Instalar para qualquer um usando este computador" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Instalar apenas para mim" +!endif diff --git a/installer/NSIS/Contrib/Language files/Romanian.nlf b/installer/NSIS/Contrib/Language files/Romanian.nlf new file mode 100644 index 0000000..74ad4b1 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Romanian.nlf @@ -0,0 +1,202 @@ +# Header, don't edit +NLF v6 +# Language ID +1048 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by Cristian Pirvu (pcristip@yahoo.com) v6 +# Revision by Sorin Sbarnea (sorin@intersol.ro) v5.1 +# and Sorin Sbarnea INTERSOL SRL (sorin@intersol.ro) v4 +# New revision by George Radu (georadu@hotmail.com) +# New revision by Iulian Dogariu (iulian@jayomega.net) +# - Use Romanian letters +# - Use imperative forms on buttons +# - Replace some neologisms +# New revision by Vlad Rusu (vlad@bitattack.ro) +# - "Rasfoiete" replaced with "Alege" - more appropiate +# - "Elimin" related terms replaced with more appropiate "Dezinstaleaz" +# - Fixed: Wrong translation in ^FileError and ^FileError_NoIgnore -> no translation +# needed Abort/Retry/Ignore, as these are not translated into local language, OS related +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Instalare $(^Name) +# ^UninstallCaption +Dezinstalare $(^Name) +# ^LicenseSubCaption +: Contract de licen +# ^ComponentsSubCaption +: Opiuni instalare +# ^DirSubCaption +: Directorul destinaie +# ^InstallingSubCaption +: n curs de instalare +# ^CompletedSubCaption +: Instalare terminat +# ^UnComponentsSubCaption +: Opiuni dezinstalare +# ^UnDirSubCaption +: Directorul de dezinstalare +# ^ConfirmSubCaption +: Confirm +# ^UninstallingSubCaption +: n curs de dezinstalare +# ^UnCompletedSubCaption +: Termin +# ^BackBtn +< na&poi +# ^NextBtn +na&inte > +# ^AgreeBtn +&De acord +# ^AcceptBtn +&Accept termenii contractului de licen +# ^DontAcceptBtn +Nu accept termenii contractului de licen +# ^InstallBtn +&Instaleaz +# ^UninstallBtn +&Dezinstaleaz +# ^CancelBtn +&Renun +# ^CloseBtn +n&chide +# ^BrowseBtn +&Alege... +# ^ShowDetailsBtn +Arat &detalii +# ^ClickNext +Apsai nainte pentru a continua. +# ^ClickInstall +Apsai Instaleaz pentru a ncepe instalarea. +# ^ClickUninstall +Apsai Dezinstaleaz pentru a ncepe dezinstalarea. +# ^Name +Nume +# ^Completed +Terminat +# ^LicenseText +Citii cu atenie contractul de licen nainte de a instala $(^NameDA). Dac acceptai termenii contractului de licen, apsai butonul De acord. +# ^LicenseTextCB +Citii cu atenie contractul de licen nainte de a instala $(^NameDA). Dac acceptai termenii contractului de licen, bifai csua de mai jos. $_CLICK +# ^LicenseTextRB +Citii cu atenie contractul de licen nainte de a instala $(^NameDA). Dac acceptai termenii contractului de licen, selectai prima opiune de mai jos. $_CLICK +# ^UnLicenseText +Citii cu atenie contractul de licen nainte de a dezinstala $(^NameDA). Dac acceptai termenii contractului de licen, apsai butonul De acord. +# ^UnLicenseTextCB +Citii cu atenie contractul de licen nainte de a dezinstala $(^NameDA). Dac acceptai termenii contractului de licen, bifai csua de mai jos. $_CLICK +# ^UnLicenseTextRB +Citii cu atenie contractul de licen nainte de a dezinstala $(^NameDA). Dac acceptai termenii contractului de licen, selectai prima opiune de mai jos. $_CLICK +# ^Custom +Personalizat +# ^ComponentsText +Alegei componentele pe care dorii s le instalai. $_CLICK +# ^ComponentsSubText1 +Alegei tipul instalrii: +# ^ComponentsSubText2_NoInstTypes +Alegei componentele ce urmeaz a fi instalate: +# ^ComponentsSubText2 +Sau, alegei componentele opionale pe care dorii s le instalai: +# ^UnComponentsText +Alegei componentele pe care dorii s le dezinstalai. $_CLICK +# ^UnComponentsSubText1 +Alegei tipul de dezinstalare: +# ^UnComponentsSubText2_NoInstTypes +Alegei componentele ce urmeaz a fi dezinstalate: +# ^UnComponentsSubText2 +Sau, alegei componentele opionale pe care dorii s le dezinstalai: +# ^DirText +$(^NameDA) se va instala n urmtorul director. Pentru a alege alt destinaie, apsai Alege i alegei alt director. $_CLICK +# ^DirSubText +Director destinaie +# ^DirBrowseText +Alegei directorul n care dorii s instalai $(^NameDA): +# ^UnDirText +$(^NameDA) se va dezinstala din urmtorul director. Pentru a dezinstala din alt director, apsai Alege i alegei alt director. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Alegei directorul de dezinstalare al $(^NameDA): +# ^SpaceAvailable +"Spaiu disponibil: " +# ^SpaceRequired +"Spaiu necesar: " +# ^UninstallingText +Aceast aplicaie va dezinstala $(^NameDA) din computerul Dv. $_CLICK +# ^UninstallingSubText +Dezinstalare din: +# ^FileError +Eroare la scrierea fiierului: \r\n\t"$0"\r\nApsai Abort pentru oprirea instalrii,\r\nRetry pentru a mai ncerca o dat scrierea fiierului, \r\nIgnore pentru a trece peste acest fiier. +# ^FileError_NoIgnore +Eroare la scrierea fiierului: \r\n\t"$0"\r\nApsai Retry pentru a mai ncerca o dat, sau\r\nAbort pentru oprirea instalrii. +# ^CantWrite +"Nu am putut scrie: " +# ^CopyFailed +Copierea a euat +# ^CopyTo +"Copiere n " +# ^Registering +"Se nregistreaz: " +# ^Unregistering +"Se deznregistreaz din registru: " +# ^SymbolNotFound +"Simbolul nu a fost gsit: " +# ^CouldNotLoad +"Nu am putut ncrca: " +# ^CreateFolder +"Creare director: " +# ^CreateShortcut +"Creare comand rapid: " +# ^CreatedUninstaller +"S-a creat aplicaia de dezinstalare: " +# ^Delete +"tergere fiier: " +# ^DeleteOnReboot +"tergere la repornire: " +# ^ErrorCreatingShortcut +"Eroare la crearea comenzii rapide: " +# ^ErrorCreating +"Eroare la creare: " +# ^ErrorDecompressing +Eroare la dezarhivarea datelor! Aplicatia de instalare este defect? +# ^ErrorRegistering +Eroare la nregistrarea DLL-ului +# ^ExecShell +"ExecShell: " +# ^Exec +"Executare: " +# ^Extract +"Extragere: " +# ^ErrorWriting +"Extragere: eroare la scriere n fiier " +# ^InvalidOpcode +Aplicaie de instalare defect: opcode incorect +# ^NoOLE +"Nu exist OLE pentru: " +# ^OutputFolder +"Directorul destinaie: " +# ^RemoveFolder +"tergere destinaie: " +# ^RenameOnReboot +"Redenumire la repornirea computerului: " +# ^Rename +"Redenumire: " +# ^Skipped +"Srite: " +# ^CopyDetails +Copiere detalii n clipboard +# ^LogInstall +Jurnal proces instalare +# ^Byte +O +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Romanian.nsh b/installer/NSIS/Contrib/Language files/Romanian.nsh new file mode 100644 index 0000000..7a38bb3 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Romanian.nsh @@ -0,0 +1,130 @@ +;Language: Romanian (1048) +;Translated by Cristian Pirvu (pcristip@yahoo.com) +;Updates by Sorin Sbarnea - INTERSOL SRL (sbarneasorin@intersol.ro) - ROBO Design (www.robodesign.ro) +;New revision by George Radu (georadu@hotmail.com) http://mediatae.3x.ro +;New revision by Vlad Rusu (vlad@bitattack.ro) +; - Use Romanian letters +; - ".. produsului" removed as unnecessary +; - "Elimin" related terms replaced with more appropiate "Dezinstaleaz" +; - Misc language tweaks +!insertmacro LANGFILE "Romanian" "Romana" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Bine ai venit la instalarea $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Aceast aplicaie va instala $(^NameDA).$\r$\n$\r$\nEste recomandat s nchidei toate aplicaiile nainte de nceperea procesului de instalare. Acest lucru v poate asigura un proces de instalare fr erori sau situaii neprevzute.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Bine ai venit la dezinstalarea $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Aceast aplicaie va dezinstala $(^NameDA).$\r$\n$\r$\nEste recomandat s nchidei toate aplicaiile nainte de nceperea procesului de dezinstalare. Acest lucru v poate asigura un proces de dezinstalare fr erori sau situaii neprevzute.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_WELCOMEPAGE | MUI_UNWELCOMEPAGE + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Terminare" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Contract de licen" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Citii cu atenie termenii contractului de licen nainte de a instala $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Dac acceptai termenii contractului de licen, apsati De Acord. Pentru a instala $(^NameDA) trebuie s acceptai termenii din contractul de licen." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Dac acceptai termenii contractului de licen, bifai csua de mai jos. Pentru a instala $(^NameDA) trebuie s acceptai termenii din contractul de licen. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Dac acceptai termenii contractului de licen, selectai prima opiune de mai jos. Pentru a instala $(^NameDA) trebuie s acceptai termenii din contractul de licen. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Contract de licen" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Citii cu atenie termenii contractului de licen nainte de a dezinstala $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Dac acceptai termenii contractului de licen, apsati De Acord. Pentru a dezinstala $(^NameDA) trebuie s acceptai termenii din contractul de licen." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Dac acceptai termenii contractului de licen, bifai csua de mai jos. Pentru a dezinstala $(^NameDA) trebuie s acceptai termenii din contractul de licen. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Dac acceptai termenii contractului de licen, selectai prima opiune de mai jos. Pentru a dezinstala $(^NameDA) trebuie s acceptai termenii din contractul de licen. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Apsai Page Down pentru a vizualiza restul contractului de licen." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Selectare componente" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Selectai componentele $(^NameDA) pe care dorii s le instalai." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Selectare componente" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Selectai componentele $(^NameDA) pe care dorii s le dezinstalai." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descriere" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Aezai mouse-ul deasupra fiecrei componente pentru a vizualiza descrierea acesteia." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Aezai mouse-ul deasupra fiecrei componente pentru a vizualiza descrierea acesteia." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Selectare director destinaie" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Selectai directorul n care dorii s instalai $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Selectare director de dezinstalat" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Selectai directorul din care dorii s dezinstalai $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "n curs de instalare" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "V rugm s ateptai, $(^NameDA) se instaleaz." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalare terminat" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Instalarea s-a terminat cu succes." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalare anulat" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Instalarea a fost anulat de utilizator." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "n curs de dezinstalare" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "V rugm s ateptai, $(^NameDA) se dezinstaleaz." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Dezinstalare terminat" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Dezinstalarea s-a terminat cu succes." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Dezinstalare anulat" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Dezinstalarea fost anulat de utilizator." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Terminare instalare $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) a fost instalat.$\r$\n$\r$\nApsai Terminare pentru a ncheia instalarea." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Trebuie s repornii calculatorul pentru a termina instalarea. Dorii s-l repornii acum?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Terminare dezinstalare $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) a fost dezinstalat.$\r$\n$\r$\nApsai Terminare pentru a ncheia dezinstalarea." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Trebuie s repornii calculatorul pentru a termina dezinstalarea. Dorii s-l repornii acum?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Repornete acum" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Repornesc eu mai trziu" + ${LangFileString} MUI_TEXT_FINISH_RUN "Executare $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Afiare fiier readme (citete-m)." +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Selectare grup Meniul Start" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Selectai un grup in Meniul Start pentru a crea comenzi rapide pentru produs." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Selectai grupul din Meniul Start n care vor fi create comenzi rapide pentru produs. Putei de asemenea s creai un grup nou." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nu doresc comenzi rapide" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Dezinstalare $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Dezinstalare $(^NameDA) din calculatorul dumneavoastr." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Suntei sigur() c dorii s anulai instalarea $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Suntei sigur() c dorii s anulai dezinstalarea $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Russian.nlf b/installer/NSIS/Contrib/Language files/Russian.nlf new file mode 100644 index 0000000..2064bd1 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Russian.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Language ID +1049 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1251 +# RTL - anything else than RTL means LTR +- +# Translation by Timon [ timon@front.ru ] + 20030919 +# Translation updated by Dmitry Yerokhin [erodim@mail.ru] (050424) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption + $(^Name) +# ^UninstallCaption + $(^Name) +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< & +# ^NextBtn +& > +# ^AgreeBtn +& +# ^AcceptBtn + & +# ^DontAcceptBtn + & +# ^InstallBtn +& +# ^UninstallBtn +& +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +& ... +# ^ShowDetailsBtn +&... +# ^ClickNext + '' . +# ^ClickInstall + '', . +# ^ClickUninstall + '', . +# ^Name + +# ^Completed + +# ^LicenseText + $(^NameDA) . , ''. +# ^LicenseTextCB + $(^NameDA) . , . $_CLICK +# ^LicenseTextRB + $(^NameDA) . , . $_CLICK +# ^UnLicenseText + $(^NameDA) . , ''. +# ^UnLicenseTextCB + $(^NameDA) . , . $_CLICK +# ^UnLicenseTextRB + $(^NameDA) . , . $_CLICK +# ^Custom + +# ^ComponentsText + , . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 + : +# ^UnComponentsText + , . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 + : +# ^DirText + $(^NameDA) . , '' . $_CLICK +# ^DirSubText + +# ^DirBrowseText + $(^NameDA): +# ^UnDirText + $(^NameDA) . , '' . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + , $(^NameDA): +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText + $(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\t"$0"\r\n'': ;\r\n"": ;\r\n"": . +# ^FileError_NoIgnore + : \r\n\t"$0"\r\n'': ;\r\n'': . +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +": " +# ^Unregistering +"-: " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +" : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ! , . +# ^ErrorRegistering + (DLL) +# ^ExecShell +" : " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + : +# ^NoOLE +" OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +": " +# ^Skipped +": " +# ^CopyDetails + +# ^LogInstall + +# byte + +# kilo + +# mega + +# giga + \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Russian.nsh b/installer/NSIS/Contrib/Language files/Russian.nsh new file mode 100644 index 0000000..e5dde06 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Russian.nsh @@ -0,0 +1,121 @@ +;Language: Russian (1049) +;Translation updated by Dmitry Yerokhin [erodim@mail.ru] (050424) + +!insertmacro LANGFILE "Russian" "Russian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n , $(^NameDA) .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " , $\"$\". , ." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . , . $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . , . $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " , $\"$\". . $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . . $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . . $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " $\"PageUp$\" $\"PageDown$\"." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA), ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA), ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " , ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " , ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " , $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE " " + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE ", $(^NameDA)..." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE ", $(^NameDA)..." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n $\"$\" ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " $(^NameDA) . ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n $\"$\" ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " $(^NameDA) . ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW ", " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER ", " + ${LangFileString} MUI_TEXT_FINISH_RUN "& $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "& ReadMe" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " $\"$\"" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " $\"$\" ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " $\"$\", . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Serbian.nlf b/installer/NSIS/Contrib/Language files/Serbian.nlf new file mode 100644 index 0000000..8b68ebd --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Serbian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +3098 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1251 +# RTL - anything else than RTL means LTR +- +# Translation by +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) +# ^UninstallCaption +$(^Name) +# ^LicenseSubCaption +: +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< +# ^NextBtn + > +# ^AgreeBtn + +# ^AcceptBtn + +# ^DontAcceptBtn + +# ^InstallBtn + +# ^UninstallBtn + +# ^CancelBtn + +# ^CloseBtn + +# ^BrowseBtn +... +# ^ShowDetailsBtn + +# ^ClickNext + . +# ^ClickInstall + ༓ . +# ^ClickUninstall + ༓ . +# ^Name + +# ^Completed + +# ^LicenseText + $(^NameDA). , . +# ^LicenseTextCB + $(^NameDA). , . $_CLICK +# ^LicenseTextRB + $(^NameDA). , . $_CLICK +# ^UnLicenseText + $(^NameDA). , . +# ^UnLicenseTextCB + $(^NameDA). , . $_CLICK +# ^UnLicenseTextRB + $(^NameDA). , . $_CLICK +# ^Custom + +# ^ComponentsText + . . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 +, : +# ^UnComponentsText + . . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 +, : +# ^DirText + $(^NameDA) . ... . $_CLICK +# ^DirSubText + +# ^DirBrowseText + $(^NameDA): +# ^UnDirText + $(^NameDA) . ... . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + $(^NameDA): +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText + $(^NameDA) . $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\t"$0"\r\n ,\r\n , \r\n . +# ^FileError_NoIgnore + : \r\n\t"$0"\r\n , \r\n . +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +": " +# ^Unregistering +": " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +" : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + ! ? +# ^ErrorRegistering + +# ^ExecShell +" : " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + : +# ^NoOLE +" OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +": " +# ^Skipped +": " +# ^CopyDetails + +# ^LogInstall + +# ^Byte +B +# ^Kilo +k +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Serbian.nsh b/installer/NSIS/Contrib/Language files/Serbian.nsh new file mode 100644 index 0000000..de10879 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Serbian.nsh @@ -0,0 +1,121 @@ +;Language: Serbian (3098) +;Translation by + +!insertmacro LANGFILE "Serbian" "Serbian Cyrillic" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n , $(^NameDA) . $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE " " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " , . $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE " " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " , . $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " Page Down ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " . ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " . ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO " ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " a" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE " $(^NameDA) ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n ༓ ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " $(^NameDA) . ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT " $(^NameDA) .$\r$\n$\r$\n ༓ ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " $(^NameDA) . ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " " + ${LangFileString} MUI_TEXT_FINISH_RUN " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME " " + ${LangFileString} MUI_BUTTONTEXT_FINISH "" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " . ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/SerbianLatin.nlf b/installer/NSIS/Contrib/Language files/SerbianLatin.nlf new file mode 100644 index 0000000..534381a --- /dev/null +++ b/installer/NSIS/Contrib/Language files/SerbianLatin.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +2074 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by Sran Obuina +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Instalacija +# ^UninstallCaption +$(^Name) Deinstalacija +# ^LicenseSubCaption +: Dogovor o pravu korienja +# ^ComponentsSubCaption +: Opcije instalacije +# ^DirSubCaption +: Izbor foldera za instalaciju +# ^InstallingSubCaption +: Instalacija +# ^CompletedSubCaption +: Zavrena instalacija +# ^UnComponentsSubCaption +: Opcije deinstalacije +# ^UnDirSubCaption +: Izbor foldera za deinstalaciju +# ^ConfirmSubCaption +: Potvrivanje +# ^UninstallingSubCaption +: Deinstalacija +# ^UnCompletedSubCaption +: Zavrena deinstalacija +# ^BackBtn +< Nazad +# ^NextBtn +Napred > +# ^AgreeBtn +Prihvatam +# ^AcceptBtn +Prihvatam uslove dogovora o pravu korienja +# ^DontAcceptBtn +Ne prihvatam uslove dogovora o pravu korienja +# ^InstallBtn +Instaliraj +# ^UninstallBtn +Deinstaliraj +# ^CancelBtn +Odustani +# ^CloseBtn +Zatvori +# ^BrowseBtn +Izbor... +# ^ShowDetailsBtn +Detalji +# ^ClickNext +Pritisnite dugme Napred za nastavak. +# ^ClickInstall +Pritisnite dugme Instaliraj za poetak instalacije. +# ^ClickUninstall +Pritisnite dugme Deinstaliraj za poetak deinstalacije. +# ^Name +Ime +# ^Completed +Zavreno +# ^LicenseText +Palivo proitajte dogovor o pravu korienja pre instalacije programa $(^NameDA). Ako prihvatate sve uslove dogovora, pritisnite dugme Prihvatam. +# ^LicenseTextCB +Palivo proitajte dogovor o pravu korienja pre instalacije programa $(^NameDA). Ako prihvatate sve uslove dogovora, obeleite kvadrati ispod. $_CLICK +# ^LicenseTextRB +Palivo proitajte dogovor o pravu korienja pre instalacije programa $(^NameDA). Ako prihvatate sve uslove dogovora, izaberite prvu opciju ispod. $_CLICK +# ^UnLicenseText +Palivo proitajte dogovor o pravu korienja pre deinstalacije programa $(^NameDA). Ako prihvatate sve uslove dogovora, pritisnite dugme Prihvatam. +# ^UnLicenseTextCB +Palivo proitajte dogovor o pravu korienja pre deinstalacije programa $(^NameDA). Ako prihvatate sve uslove dogovora, obeleite kvadrati ispod. $_CLICK +# ^UnLicenseTextRB +Palivo proitajte dogovor o pravu korienja pre deinstalacije programa $(^NameDA). Ako prihvatate sve uslove dogovora, izaberite prvu opciju ispod. $_CLICK +# ^Custom +Prilagoavanje +# ^ComponentsText +Izaberite komponente za instalaciju. Instaliraju se samo oznaene komponente. $_CLICK +# ^ComponentsSubText1 +Izaberite tip instalacije: +# ^ComponentsSubText2_NoInstTypes +Izaberite komponente za instalaciju: +# ^ComponentsSubText2 +Ili, izaberite opcione komponente koje elite da instalirate: +# ^UnComponentsText +Izaberite komponente za deinstalaciju. Deinstaliraju se samo oznaene komponente. $_CLICK +# ^UnComponentsSubText1 +Izaberite tip deinstalacije: +# ^UnComponentsSubText2_NoInstTypes +Izaberite komponente za deinstalaciju: +# ^UnComponentsSubText2 +Ili, izaberite opcione komponente koje elite da deinstalirate: +# ^DirText +Program $(^NameDA) e biti instaliran u navedeni folder. Za instalaciju u drugi folder pritisnite dugme Izbor... i izaberite folder. $_CLICK +# ^DirSubText +Folder +# ^DirBrowseText +Izaberite folder u koji ete instalirati program $(^NameDA): +# ^UnDirText +Program $(^NameDA) e biti deinstaliran iz navedenog foldera. Za deinstalaciju iz drugog foldera pritisnite dugme Izbor... i izaberite folder. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Izaberite folder iz koga ete deinstalirati program $(^NameDA): +# ^SpaceAvailable +"Slobodan prostor: " +# ^SpaceRequired +"Potreban prostor: " +# ^UninstallingText +Program $(^NameDA) e biti deinstaliran iz navedenog foldera. $_CLICK +# ^UninstallingSubText +Deinstalacija iz: +# ^FileError +Greka pri otvaranju fajla za pisanje: \r\n\t"$0"\r\nPritisnite dugme Odustani za prekid instalacije,\r\nPonovi za ponovni pokuaj pisanja u fajl, ili\r\nIgnorii za preskakanje ovog fajla. +# ^FileError_NoIgnore +Greka pri otvaranju fajla za pisanje: \r\n\t"$0"\r\nPritisnite dugme Ponovi za ponovni pokuaj pisanja u fajl, ili\r\nOdustani za prekid instaliranja. +# ^CantWrite +"Nemogue pisanje: " +# ^CopyFailed +Neuspeno kopiranje +# ^CopyTo +"Kopiranje u " +# ^Registering +"Registrovanje: " +# ^Unregistering +"Deregistrovanje: " +# ^SymbolNotFound +"Simbol nije naen: " +# ^CouldNotLoad +"Nemogue uitavanje: " +# ^CreateFolder +"Kreiranje foldera: " +# ^CreateShortcut +"Kreiranje preice: " +# ^CreatedUninstaller +"Kreiranje deinstalera: " +# ^Delete +"Brisanje fajla: " +# ^DeleteOnReboot +"Brisanje pri restartu: " +# ^ErrorCreatingShortcut +"Greka pri kreiranju preice: " +# ^ErrorCreating +"Greka pri kreiranju: " +# ^ErrorDecompressing +Greka pri otpakivanju podataka! Oteen instalacioni program? +# ^ErrorRegistering +Greka pri registrovanju biblioteke +# ^ExecShell +"Izvravanje u okruenju: " +# ^Exec +"Izvravanje: " +# ^Extract +"Otpakivanje: " +# ^ErrorWriting +"Otpakivanje: greka pri upisu u fajl " +# ^InvalidOpcode +Oteen instalacioni program: neispravna komanda +# ^NoOLE +"Nema OLE podrke za: " +# ^OutputFolder +"Izlazni folder: " +# ^RemoveFolder +"Brisanje foldera: " +# ^RenameOnReboot +"Preimenovanje pri restartu: " +# ^Rename +"Preimenovan: " +# ^Skipped +"Preskoen: " +# ^CopyDetails +Kopiraj detalje u klipbord +# ^LogInstall +Vodi zapisnik o procesu instalacije +# ^Byte +B +# ^Kilo +k +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/SerbianLatin.nsh b/installer/NSIS/Contrib/Language files/SerbianLatin.nsh new file mode 100644 index 0000000..ddc24c3 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/SerbianLatin.nsh @@ -0,0 +1,121 @@ +;Language: Serbian Latin (2074) +;Translation by Sran Obuina + +!insertmacro LANGFILE "SerbianLatin" "Serbian Latin" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Dobrodoli u vodi za instalaciju programa $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Biete voeni kroz proces instalacije programa $(^NameDA).$\r$\n$\r$\nPreporuljivo je da iskljuite sve druge programe pre poetka instalacije. Ovo moe omoguiti auriranje sistemskih fajlova bez potrebe za ponovnim pokretanjem raunara.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Dobrodoli u deinstalaciju programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Biete voeni kroz proces deinstalacije programa $(^NameDA).$\r$\n$\r$\nPre poetka deinstalacije, uverite se da je program $(^NameDA) iskljuen. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Dogovor o pravu korienja" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Paljivo proitajte dogovor o pravu korienja pre instalacije programa $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Ako prihvatate sve uslove dogovora, pritisnite dugme Prihvatam za nastavak. Morate prihvatiti dogovor da biste instalirali program $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ako prihvatate sve uslove dogovora, obeleite kvadrati ispod. Morate prihvatiti dogovor da biste instalirali program $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ako prihvatate sve uslove dogovora, izaberite prvu opciju ispod. Morate prihvatiti dogovor da biste instalirali program $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Dogovor o pravu korienja" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Palivo proitajte dogovor o pravu korienja pre deinstalacije programa $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Ako prihvatate sve uslove dogovora, pritisnite dugme Prihvatam za nastavak. Morate prihvatiti dogovor da biste deinstalirali program $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ako prihvatate sve uslove dogovora, obeleite kvadrati ispod. Morate prihvatiti dogovor da biste deinstalirali program $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ako prihvatate sve uslove dogovora, izaberite prvu opciju ispod. Morate prihvatiti dogovor da biste deinstalirali program $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Pritisnite Page Down da biste videli ostatak dogovora." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Izbor komponenti za instalaciju" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Izaberite komponente za instalaciju. Instaliraju se samo oznaene komponente." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Izbor komponenti za deinstalaciju" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Izaberite komponente za deinstalaciju. Deinstaliraju se samo oznaene komponente." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Opis" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Preite kursorom mia preko imena komponente da biste videli njen opis." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Preite kursorom mia preko imena komponente da biste videli njen opis." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Izbor foldera za instalaciju" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Izaberite folder u koji ete instalirati program $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Izbor foldera za deinstalaciju" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Izaberite folder iz koga ete deinstalirati program $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalacija" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Saekajte dok se program $(^NameDA) instalira." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Zavrena instalacija" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Instalacija je uspeno zavrena." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Prekinuta instalacija" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Instalacija je prekinuta i nije uspeno zavrena." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Deinstalacija" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Saekajte dok se program $(^NameDA) deinstalira." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Zavrena deinstalacija" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Deinstalacija je uspeno zavrena." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Prekinuta deinstalacija" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Deinstalacija je prekinuta i nije uspeno zavrena." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Zavrena instalacija programa $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Program $(^NameDA) je instaliran na raunar.$\r$\n$\r$\nPritisnite dugme Kraj za zatvaranje ovog prozora." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Raunar mora biti ponovo pokrenut da bi se proces instalacije programa $(^NameDA) uspeno zavrio. elite li to odmah da uradite?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Zavrena deinstalacija programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Program $(^NameDA) je deinstaliran sa raunara.$\r$\n$\r$\nPritisnite dugme Kraj za zatvaranje ovog prozora." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Raunar mora biti ponovo pokrenut da bi se proces deinstalacije programa $(^NameDA) uspeno zavrio. elite li to da uradite odmah?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Odmah ponovo pokreni raunar" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Bez ponovnog pokretanja" + ${LangFileString} MUI_TEXT_FINISH_RUN "Pokreni program $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "Prikai ProitajMe fajl" + ${LangFileString} MUI_BUTTONTEXT_FINISH "Kraj" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Izbor foldera u Start meniju" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Izaberite folder u Start meniju u kome ete kreirati preice." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Izaberite folder u Start meniju u kome elite da budu kreirane preice programa. Moete upisati i ime za kreiranje novog foldera." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Bez kreiranja preica" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Deinstalacija programa $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Deinstalacija programa $(^NameDA) sa raunara." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Sigurno elite da prekinete instalaciju programa $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Sigurno elite da prekinete deinstalaciju programa $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/SimpChinese.nlf b/installer/NSIS/Contrib/Language files/SimpChinese.nlf new file mode 100644 index 0000000..a545049 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/SimpChinese.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +2052 +# Font and size - dash (-) means default С + +9 +# Codepage - dash (-) means ANSI code page ANSI ҳ +936 +# RTL - anything else than RTL means LTR д +- +# Translator: Kii Ali ;Revision date: 2004-12-15 +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) װ +# ^UninstallCaption +$(^Name) ж +# ^LicenseSubCaption +: ֤Э +# ^ComponentsSubCaption +: װѡ +# ^DirSubCaption +: װļ +# ^InstallingSubCaption +: ڰװ +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: жѡ +# ^UnDirSubCaption +: жļ +# ^ConfirmSubCaption +: ȷ +# ^UninstallingSubCaption +: ж +# ^UnCompletedSubCaption +: +# ^BackBtn +< һ(&P) +# ^NextBtn +һ(&N) > +# ^AgreeBtn +ҽ(&I) +# ^AcceptBtn +ҽܡ֤Э顱е(&A) +# ^DontAcceptBtn +Ҳܡ֤Э顱е(&N) +# ^InstallBtn +װ(&I) +# ^UninstallBtn +ж(&U) +# ^CancelBtn +ȡ(&C) +# ^CloseBtn +ر(&L) +# ^BrowseBtn +(&B)... +# ^ShowDetailsBtn +ʾϸ(&D) +# ^ClickNext + [һ(N)] +# ^ClickInstall + [װ(I)] ʼװ̡ +# ^ClickUninstall + [ж(U)] ʼװ̡ +# ^Name + +# ^Completed + +# ^LicenseText +ڰװ $(^NameDA) ֮ǰ֤Э顣Э [ҽ(I)] +# ^LicenseTextCB +ڰװ $(^NameDA) ֮ǰ֤Э顣Э·Ĺѡ $_CLICK +# ^LicenseTextRB +ڰװ $(^NameDA) ֮ǰ֤Э顣Эѡ·ĵһѡ $_CLICK +# ^UnLicenseText +ж $(^NameDA) ֮ǰ֤Э顣Э [ҽ(I)] +# ^UnLicenseTextCB +ж $(^NameDA) ֮ǰ֤Э顣Э·Ĺѡ $_CLICK +# ^UnLicenseTextRB +ж $(^NameDA) ֮ǰ֤Э顣Эѡ·ĵһѡ $_CLICK +# ^Custom +Զ +# ^ComponentsText +ѡҪװѡ㲻ϣװ $_CLICK +# ^ComponentsSubText1 +ѡװ: +# ^ComponentsSubText2_NoInstTypes +ѡװ: +# ^ComponentsSubText2 +ߣԶѡ밲װ: +# ^UnComponentsText +ѡҪжصѡ㲻ϣжص $_CLICK +# ^UnComponentsSubText1 +ѡжص: +# ^UnComponentsSubText2_NoInstTypes +ѡҪжص: +# ^UnComponentsSubText2 +ǣѡҪжصĿѡ: +# ^DirText +Setup װ $(^NameDA) ļСҪװͬļУ [(B)] ѡļС $_CLICK +# ^DirSubText +Ŀļ +# ^DirBrowseText +ѡҪװ $(^NameDA) ļλ: +# ^UnDirText +Setup ж $(^NameDA) ļСҪжصͬļУ [(B)] ѡļС $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +ѡҪж $(^NameDA) ļλ: +# ^SpaceAvailable +"ÿռ: " +# ^SpaceRequired +"ռ: " +# ^UninstallingText +򵼽ļж $(^NameDA) $_CLICK +# ^UninstallingSubText +жĿ¼: +# ^FileError +ܴҪдļ: \r\n\t"$0"\r\n [Abort] װ\r\n [Retry] ³дļ\r\n [Ignore] ļ +# ^FileError_NoIgnore +ܴҪдļ: \r\n\t"$0"\r\n [Retry] ³дļ\r\n [Cancel] ȡװ +# ^CantWrite +"޷д: " +# ^CopyFailed +"ʧ " +# ^CopyTo +"Ƶ: " +# ^Registering +"ע: " +# ^Unregistering +"ڽע: " +# ^SymbolNotFound +"޷ҵ: " +# ^CouldNotLoad +"޷: " +# ^CreateFolder +"ļ: " +# ^CreateShortcut +"ݷʽ: " +# ^CreatedUninstaller +"жس: " +# ^Delete +"ɾļ: " +# ^DeleteOnReboot +"ɾ: " +# ^ErrorCreatingShortcut +"ڴݷʽʱ: " +# ^ErrorCreating +"ڴʱ: " +# ^ErrorDecompressing +"ڽѹݷ𻵵İװ" +# ^ErrorRegistering +"ע DLL ʱ" +# ^ExecShell +"ⲿ: " +# ^Exec +": " +# ^Extract +"ȡ: " +# ^ErrorWriting +"ȡ: ޷дļ " +# ^InvalidOpcode +"װ: ЧIJ " +# ^NoOLE +"û OLE : " +# ^OutputFolder +"Ŀ¼: " +# ^RemoveFolder +"ƳĿ¼: " +# ^RenameOnReboot +": " +# ^Rename +": " +# ^Skipped +": " +# ^CopyDetails +"ϸڵ " +# ^LogInstall +"־װ" +# byte +B +# kilo +K +# mega +M +# giga +G diff --git a/installer/NSIS/Contrib/Language files/SimpChinese.nsh b/installer/NSIS/Contrib/Language files/SimpChinese.nsh new file mode 100644 index 0000000..a283c55 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/SimpChinese.nsh @@ -0,0 +1,123 @@ +;Language: 'Chinese (Simplified)' (2052) +;Translator: Kii Ali +;Revision date: 2004-12-15 +;Verified by: QFox + +!insertmacro LANGFILE "SimpChinese" "Chinese (Simplified)" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "ӭʹá$(^NameDA)װ" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "򵼽ָɡ$(^NameDA)İװ̡$\r$\n$\r$\nڿʼװ֮ǰȹرӦó⽫װ򡱸ָϵͳļҪļ$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "ӭʹá$(^NameDA)ж" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "򵼽ȫָ㡰$(^NameDA)жؽ̡$\r$\n$\r$\nڿʼж֮ǰȷϡ$(^NameDA)δеС$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "֤Э" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "ڰװ$(^NameDA)֮ǰĶȨЭ顣" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Эе [ҽ(I)] װѡ [ȡ(C)] װ򽫻رաЭܰװ$(^NameDA)" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Эе·Ĺѡ򡣱ҪЭܰװ $(^NameDA)$_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Эеѡ·һѡҪЭܰװ $(^NameDA)$_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "֤Э" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "жء$(^NameDA)֮ǰȨ" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Эе [ҽ(I)] жءѡ [ȡ(C)] װ򽫻رաҪЭжء$(^NameDA)" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Эе·Ĺѡ򡣱ҪЭж $(^NameDA)$_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Эеѡ·һѡҪЭж $(^NameDA)$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " [PgDn] ĶȨЭ顱ಿ֡" +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "ѡ" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "ѡҪװ$(^NameDA)Щܡ" +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "ѡ" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "ѡ$(^NameDA)ҪжصĹܡ" +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "ƶָ뵽֮ϣɼ" + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "ƶָ뵽֮ϣɼ" + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "ѡװλ" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "ѡ$(^NameDA)İװļС" +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "ѡжλ" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "ѡ$(^NameDA)ҪжصļС" +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "ڰװ" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "$(^NameDA)ڰװȺ..." + ${LangFileString} MUI_TEXT_FINISH_TITLE "װ" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "װѳɹɡ" + ${LangFileString} MUI_TEXT_ABORT_TITLE "װֹ" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "װûгɹ" +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "ж" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "$(^NameDA)жأȺ..." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "ж" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "жѳɹɡ" + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "жֹ" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "жسδɹɡ" +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "ɡ$(^NameDA)װ" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA)Ѱװϵͳ$\r$\n [(F)] رմ򵼡" + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "ϵͳҪԱɡ$(^NameDA)İװҪ" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "ɡ$(^NameDA)ж" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA)Ѵļжء$\r$\n$\r$\n [] ر򵼡" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "ҪԱɡ$(^NameDA)жءҪ" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "ǣ(&Y)" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Ժ(&N)" + ${LangFileString} MUI_TEXT_FINISH_RUN " $(^NameDA)(&R)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "ʾļ(&M)" + ${LangFileString} MUI_BUTTONTEXT_FINISH "(&F)" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "ѡ񡰿ʼ˵ļ" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "ѡ񡰿ʼ˵ļУڳĿݷʽ" + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "ѡ񡰿ʼ˵ļУԱ㴴ĿݷʽҲƣļС" + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ҫݷʽ(&N)" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "ж $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "ļжء$(^NameDA)" +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "ȷʵҪ˳$(^Name)װ" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "ȷʵҪ˳$(^Name)ж" +!endif diff --git a/installer/NSIS/Contrib/Language files/Slovak.nlf b/installer/NSIS/Contrib/Language files/Slovak.nlf new file mode 100644 index 0000000..6c3a1ff --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Slovak.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1051 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +#Translated by: Kypec (peter.dzugas@mahe.sk); edited by: Marin Hikank (podnety@mojepreklady.net), Ivan Masr , 2008. +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Intalcia programu $(^Name) +# ^UninstallCaption +Odintalovanie programu $(^Name) +# ^LicenseSubCaption +: Licenn zmluva +# ^ComponentsSubCaption +: Monosti intalcie +# ^DirSubCaption +: Intalan prieinok +# ^InstallingSubCaption +: Prebieha intalcia +# ^CompletedSubCaption +: Hotovo +# ^UnComponentsSubCaption +: Monosti odintalovania +# ^UnDirSubCaption +: Prieinok s informciami pre odintalovanie +# ^ConfirmSubCaption +: Potvrdenie +# ^UninstallingSubCaption +: Prebieha odintalcia +# ^UnCompletedSubCaption +: Hotovo +# ^BackBtn +< &Sp +# ^NextBtn +&alej > +# ^AgreeBtn +&Shlasm +# ^AcceptBtn +&Shlasm s podmienkami licennej zmluvy +# ^DontAcceptBtn +N&eshlasm s podmienkami licennej zmluvy +# ^InstallBtn +&Naintalova +# ^UninstallBtn +&Odintalova +# ^CancelBtn +Zrui +# ^CloseBtn +&Zatvori +# ^BrowseBtn +&Prehadva... +# ^ShowDetailsBtn +&Podrobnosti +# ^ClickNext +V intalcii pokraujte kliknutm na tlaidlo alej. +# ^ClickInstall +Pre spustenie intalcie kliknite na tlaidlo Naintalova. +# ^ClickUninstall +Pre spustenie procesu odintalovania kliknite na tlaidlo Odintalova. +# ^Name +Nzov +# ^Completed +Hotovo +# ^LicenseText +Pred intalciou programu si prosm dkladne pretajte licenn zmluvu $(^NameDA). Ak shlaste so vetkmi jej podmienkami, kliknite na tlaidlo Shlasm. +# ^LicenseTextCB +Pred intalciou programu si prosm dkladne pretajte licenn zmluvu $(^NameDA). Ak shlaste so vetkmi jej podmienkami, zakrtnite nasledujce polko. $_CLICK +# ^LicenseTextRB +Pred intalciou programu si prosm dkladne pretajte licenn zmluvu $(^NameDA). Ak shlaste so vetkmi jej podmienkami, oznate prv z nasledujcich monost. $_CLICK +# ^UnLicenseText +Pred odintalovanm programu si prosm dkladne pretajte licenn zmluvu $(^NameDA). Ak shlaste so vetkmi jej podmienkami, kliknite na tlaidlo Shlasm. +# ^UnLicenseTextCB +Pred odintalovanm programu si prosm dkladne pretajte licenn zmluvu $(^NameDA). Ak shlaste so vetkmi jej podmienkami, zakrtnite nasledujce polko. $_CLICK +# ^UnLicenseTextRB +Pred odintalovanm programu si prosm dkladne pretajte licenn zmluvu $(^NameDA). Ak shlaste so vetkmi jej podmienkami, oznate prv z nasledujcich monost. $_CLICK +# ^Custom +Voliten +# ^ComponentsText +Oznate sasti programu, ktor chcete naintalova a odznate tie, ktor naintalova nechcete. $_CLICK +# ^ComponentsSubText1 +Vyberte si typ intalcie: +# ^ComponentsSubText2_NoInstTypes +Vyberte si tie sasti programu, ktor chcete naintalova: +# ^ComponentsSubText2 +Alebo oznate voliten doplnky, ktor chcete naintalova: +# ^UnComponentsText +Oznate sasti programu, ktor chcete odintalova a odznate tie, ktor chcete ponecha naintalovan. $_CLICK +# ^UnComponentsSubText1 +Zvote typ deintalcie: +# ^UnComponentsSubText2_NoInstTypes +Vyberte sasti, ktor chcete odintalova: +# ^UnComponentsSubText2 +Alebo oznate voliten sasti, ktor chcete odintalova: +# ^DirText +$(^NameDA) bude naintalovan do nasledujceho prieinka. Intalova do inho prieinka mete po kliknut na tlaidlo Prehadva a vybran inho prieinka. $_CLICK +# ^DirSubText +Cieov prieinok +# ^DirBrowseText +Zvote prieinok, do ktorho sa naintaluje program $(^NameDA): +# ^UnDirText +Intaltor odintaluje program $(^NameDA) z nasledovnho prieinka. Ak ho chcete odintalova z inho prieinka, kliknite na tlaidlo Prehadva a vyberte in prieinok. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Zvote prieinok, z ktorho sa odintaluje program $(^NameDA): +# ^SpaceAvailable +"Von miesto na disku: " +# ^SpaceRequired +"Potrebn miesto na disku: " +# ^UninstallingText +Program $(^NameDA) sa odintaluje z nasledovnho prieinka. $_CLICK +# ^UninstallingSubText +Prebieha odintalovanie z: +# ^FileError +Chyba pri otvran sboru na zpis: \r\n\r\n$0\r\n\r\n. Ak chcete intalciu ukoni, kliknite na tlaidlo Ukoni,\r\ ak chcete zpis sboru zopakova, kliknite na tlaidlo Opakova alebo kliknite na tlaidlo \r\nIgnorova, ak chcete intalciu tohto sboru vynecha. +# ^FileError_NoIgnore +Chyba pri otvran sboru na zpis: \r\n\r\n$0\r\n\r\n. Ak chcete zopakova zpis sboru, kliknite na tlaidlo Opakova, alebo kliknite na tlaidlo \r\nZrui, v prpade, e chcete intalciu ukoni. +# ^CantWrite +"Nemono zapsa sbor: " +# ^CopyFailed +Koprovanie zlyhalo. +# ^CopyTo +"Koprova do " +# ^Registering +"Registruje sa: " +# ^Unregistering +"Vymazva sa z registra: " +# ^SymbolNotFound +"Nemono njs symbol: " +# ^CouldNotLoad +"Nemono nata: " +# ^CreateFolder +"Vytvoren prieinok: " +# ^CreateShortcut +"Vytvoren odkaz: " +# ^CreatedUninstaller +"Program pre odintalovanie: " +# ^Delete +"Vymazan sbor: " +# ^DeleteOnReboot +"Vymaza po retartovan systmu: " +# ^ErrorCreatingShortcut +"Chyba pri vytvran odkazu: " +# ^ErrorCreating +"Chyba pri vytvran: " +# ^ErrorDecompressing +Chyba pri dekomprimovan dt! Intaltor je pravdepodobne pokoden... +# ^ErrorRegistering +Chyba pri registrcii sasti +# ^ExecShell +"Vykona prkaz: " +# ^Exec +"Spusti: " +# ^Extract +"Extrahuje sa: " +# ^ErrorWriting +"Chyba pri zpise do sboru " +# ^InvalidOpcode +Intaltor je pravdepodobne pokoden, pretoe obsahuje neplatn operan kd. +# ^NoOLE +"iadny zpis OLE pre: " +# ^OutputFolder +"Vstupn prieinok: " +# ^RemoveFolder +"Odstrni prieinok: " +# ^RenameOnReboot +"Premenova po retartovan systmu: " +# ^Rename +"Premenova: " +# ^Skipped +"Vynechan: " +# ^CopyDetails +Skoprova podrobnosti do schrnky +# ^LogInstall +Zaznamena priebeh intalcie +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Slovak.nsh b/installer/NSIS/Contrib/Language files/Slovak.nsh new file mode 100644 index 0000000..60c462a --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Slovak.nsh @@ -0,0 +1,133 @@ +;Language: Slovak (1051) +;Translated by: +; Kypec (peter.dzugas@mahe.sk) +;edited by: +; Marin Hikank (podnety@mojepreklady.net) +; Ivan Masr , 2008. + +!insertmacro LANGFILE "Slovak" "Slovensky" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Vitajte v sprievodcovi intalciou programu $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Tento sprievodca vs prevedie intalciou $(^NameDA).$\r$\n$\r$\nPred zaiatkom intalcie sa odpora ukoni vetky ostatn programy. Tm umonte aktualizovanie systmovch sborov bez potreby retartovania vho potaa.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Vitajte v sprievodcovi odintalovanm programu $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Tento sprievodca vs prevedie procesom odintalovania programu $(^NameDA).$\r$\n$\r$\nPred spustenm procesu odintalovania sa uistite, e program $(^NameDA) nie je prve aktvny.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licenn zmluva" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Pred intalciou $(^NameDA) si prosm pretudujte licenn podmienky." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Ak shlaste s podmienkami zmluvy, kliknite na tlaidlo Shlasm a mete pokraova v intalcii. Ak chcete v intalcii pokraova, muste odshlasi podmienky licennej zmluvy $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ak shlaste s podmienkami zmluvy, zakrtnite niie uveden polko. Ak chcete v intalcii pokraova, muste odshlasi podmienky licennej zmluvy $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ak shlaste s podmienkami zmluvy, oznate prv z niie uvedench monost. Ak chcete v intalcii pokraova, muste odshlasi podmienky licennej zmluvy $(^NameDA)." +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licenn zmluva" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Pred odintalovanm programu $(^NameDA) si prosm pretajte licenn podmienky." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Ak shlaste s podmienkami zmluvy, zvote Shlasm. Licenn zmluvu muste odshlasi, ak chcete v odintalovan programu $(^NameDA) pokraova." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Ak shlaste s podmienkami zmluvy, zakrtnite niie uveden polko. Licenn zmluvu muste odshlasi, ak chcete pokraova v odintalovan programu $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Ak shlaste s podmienkami licennej zmluvy, oznate prv z niie uvedench monost. Licenn zmluvu muste odshlasi, ak chcete pokraova v odintalovan programu $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Stlaenm klvesu Page Down posuniete text licennej zmluvy." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Voba sast programu" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Zvote si tie sasti programu $(^NameDA), ktor chcete naintalova." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Voba sast" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Zvote sasti programu $(^NameDA), ktor chcete odintalova." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Popis" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Pri prejden kurzorom myi nad nzvom sasti sa zobraz jej popis." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Pri prejden kurzorom myi nad nzvom sasti sa zobraz jej popis." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Voba umiestnenia programu" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Vyberte si prieinok, do ktorho chcete naintalova program $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Umiestenie programu pre odintalovanie" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Vyberte si prieinok, z ktorho chcete odintalova program $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Intalcia" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Pokajte prosm, km prebehne intalcia programu $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Ukonenie intalcie" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Intalcia bola dokonen spene." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Preruenie intalcie" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Intalciu sa nepodarilo dokoni." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Odintalovanie" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "akajte prosm, km prebehne odintalovanie programu $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Ukonenie odintalovania" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Odintalovanie bolo spene dokonen." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Preruenie odintalovania" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Odintalovanie sa neukonilo spene." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Dokonenie intalcie programu $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Program $(^NameDA) bol naintalovan do vho potaa.$\r$\nKliknite na tlaidlo Dokoni a tento sprievodca sa ukon." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Pre pln dokonenie intalcie programu $(^NameDA) je potrebn retartova v pota. Chcete ho retartova ihne?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Dokonenie sprievodcu odintalovanm" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Program $(^NameDA) bol odintalovan z vho potaa.$\r$\n$\r$\nKliknite na tlaidlo Dokoni a tento sprievodca sa ukon." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Pre pln dokonenie odintalovania programu $(^NameDA) je nutn retartova v pota. Chcete ho retartova ihne?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Retartova teraz" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Retartova neskr (manulne)" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Spusti program $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Zobrazi sbor s informciami" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Dokoni" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Voba umiestnenia v ponuke tart" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Vyberte si prieinok v ponuke tart, kam sa umiestnia odkazy na program $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Vyberte si prieinok v ponuke tart, v ktorom chcete vytvori odkazy na program. Takisto mete napsa nzov novho prieinka." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Nevytvra odkazy" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Odintalovanie programu $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Odstrnenie programu $(^NameDA) z vho potaa." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Naozaj chcete ukoni intalciu programu $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Naozaj chcete ukoni proces odintalovania programu $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Vybra pouvateov" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Vyberte pre ktorch pouvateov chcete naintalova $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Vyberte, i chcete naintalova program $(^NameDA) iba pre seba alebo pre vetkch pouvateov tohto potaa. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Naintalova pre vetkch pouvateov tohto potaa" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Naintalova iba pre ma" +!endif \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Slovenian.nlf b/installer/NSIS/Contrib/Language files/Slovenian.nlf new file mode 100644 index 0000000..3a37197 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Slovenian.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1060 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1250 +# RTL - anything else than RTL means LTR +- +# Translation by Janez Dolinar, edited by Martin Srebotnjak - Lugos.si +# ^Branding +Namestitveni sistem Nullsoft %s +# ^SetupCaption +Namestitev $(^Name) +# ^UninstallCaption +Odstranitev $(^Name) +# ^LicenseSubCaption +: Licenna pogodba +# ^ComponentsSubCaption +: Monosti namestitve +# ^DirSubCaption +: Mapa namestitve +# ^InstallingSubCaption +: Nameanje poteka +# ^CompletedSubCaption +: Dokonano +# ^UnComponentsSubCaption +: Monosti odstranitve +# ^UnDirSubCaption +: Mapa odstranitve +# ^ConfirmSubCaption +: Potrditev +# ^UninstallingSubCaption +: Odstranjevanje poteka +# ^UnCompletedSubCaption +: Dokonano +# ^BackBtn +< &Nazaj +# ^NextBtn +N&aprej > +# ^AgreeBtn +Se &strinjam +# ^AcceptBtn +&Sprejmem pogoje licenne pogodbe +# ^DontAcceptBtn +&Ne sprejmem pogojev licenne pogodbe +# ^InstallBtn +&Namesti +# ^UninstallBtn +&Odstrani +# ^CancelBtn +Preklii +# ^CloseBtn +&Zapri +# ^BrowseBtn +Prebrsk&aj ... +# ^ShowDetailsBtn +&Podrobnosti +# ^ClickNext +Kliknite Naprej za nadaljevanje. +# ^ClickInstall +Kliknite Namesti za zaetek namestitve. +# ^ClickUninstall +Kliknite Odstrani za odstranitev. +# ^NameIme +Ime +# ^Completed +Dokonano +# ^LicenseText +Prosimo, da pred namestitvijo $(^NameDA) pregledate licenno pogodbo. e se z njo strinjate, pritisnite Se strinjam. +# ^LicenseTextCB +Prosimo, da pred namestitvijo $(^NameDA) pregledate licenno pogodbo. e sprejmete vse natete pogoje, potrdite spodnje polje. $_CLICK +# ^LicenseTextRB +Prosimo, da pred namestitvijo $(^NameDA) pregledate licenno pogodbo. e sprejmete vse natete pogoje, izberite prvo spodaj podano monost. $_CLICK +# ^UnLicenseText +Prosimo, da pred odstranitvijo $(^NameDA) pregledate licenno pogodbo. e se z njo strinjate, pritisnite Se strinjam. +# ^UnLicenseTextCB +Prosimo, da pred odstranitvijo $(^NameDA) pregledate licenno pogodbo. e sprejmete vse natete pogoje, potrdite spodnje polje. $_CLICK +# ^UnLicenseTextRB +Prosimo, da pred odstranitvijo $(^NameDA) pregledate licenno pogodbo. e sprejmete vse natete pogoje, izberite prvo spodaj podano monost. $_CLICK +# ^Custom +Po meri ... +# ^ComponentsText +Oznaite komponente, ki jih elite namestiti, in pustite neoznaene tiste, katerih ne elite namestiti. $_CLICK +# ^ComponentsSubText1 +Izberite vrsto namestitve: +# ^ComponentsSubText2_NoInstTypes +Izberite komponente namestitve: +# ^ComponentsSubText2 +Ali pa izberite komponente, ki jih elite namestiti: +# ^UnComponentsText +Oznaite komponente, ki jih elite odstraniti, in pustite neoznaene tiste, ki jih ne elite odstraniti. $_CLICK +# ^UnComponentsSubText1 +Izberite vrsto odstranitve: +# ^UnComponentsSubText2_NoInstTypes +Izberite komponente za odstranitev: +# ^UnComponentsSubText2 +Ali pa izberite komponente namestitve, ki jih elite odstraniti: +# ^DirText +$(^NameDA) boste namestili v sledeo mapo. Za izbiro druge mape kliknite tipko Prebrskaj in izberite drugo mapo. $_CLICK +# ^DirSubText +Ciljna mapa +# ^DirBrowseText +Izberite mapo, kamor elite namestiti $(^NameDA): +# ^UnDirText +Odstranili boste $(^NameDA) iz sledee mape. Za izbiro druge mape kliknite tipko Prebrskaj in izberite drugo mapo. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Izberite mapo, od koder elite odstraniti $(^NameDA): +# ^SpaceAvailable +"Prostor na disku: " +# ^SpaceRequired +"Potreben prostor: " +# ^UninstallingText +$(^NameDA) bo odstranjen iz naslednje mape. $_CLICK +# ^UninstallingSubText +Odstranjevanje iz: +# ^FileError +Napaka pri odpiranju datoteke za pisanje: \r\n\r\n$0\r\n\r\nPritisnite Prekini za prekinitev namestitve,\r\nPonovi za ponoven poskus ali\r\nPrezri za izpust te datoteke. +# ^FileError_NoIgnore +Napaka pri odpiranju datoteke za pisanje: \r\n\r\n$0\r\n\r\nPritisnite Ponovi za ponoven poskus pisanja ali\r\Preklii za prekinitev namestitve. +# ^CantWrite +"Ni mogoe pisati: " +# ^CopyFailed +Kopiranje neuspeno +# ^CopyTo +"Kopiranje v " +# ^Registering +"Registracija: " +# ^Unregistering +"Preklic registracije: " +# ^SymbolNotFound +"Ni mogoe najti simbola: " +# ^CouldNotLoad +"Ni mogoe naloiti: " +# ^CreateFolder +"Ustvarjanje mape: " +# ^CreateShortcut +"Ustvarjanje blinjice: " +# ^CreatedUninstaller +"Ustvarjena odstranitev: " +# ^Delete +"Brisanje datoteke: " +# ^DeleteOnReboot +"Brisanje ob ponovnem zagonu: " +# ^ErrorCreatingShortcut +"Napaka ustvarjanja blinjice: " +# ^ErrorCreating +"Napaka ustvarjanja: " +# ^ErrorDecompressing +Napaka pri razirjanju podatkov! Je namestitvena datoteka okvarjena? +# ^ErrorRegistering +Napaka registracije DLL +# ^ExecShell +"Izvajanje v lupini: " +# ^Exec +"Izvajanje: " +# ^Extract +"Razirjanje: " +# ^ErrorWriting +"Razirjanje: napaka pri pisanju v datoteko " +# ^InvalidOpcode +Namestitev neveljavna: napaen ukaz +# ^NoOLE +"Neobstojei OLE za: " +# ^OutputFolder +"Ciljna mapa: " +# ^RemoveFolder +"Odstranjevanje mape: " +# ^RenameOnReboot +"Preimenovanje ob zagonu: " +# ^Rename +"Preimenovanje: " +# ^Skipped +"Izpueno: " +# ^CopyDetails +Kopiraj podrobnosti v odloie +# ^LogInstall +Shrani potek namestitve +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Slovenian.nsh b/installer/NSIS/Contrib/Language files/Slovenian.nsh new file mode 100644 index 0000000..5c247bf --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Slovenian.nsh @@ -0,0 +1,129 @@ +;Language: Slovenian (1060) +;By Janez Dolinar, edited by Martin Srebotnjak - Lugos.si + +!insertmacro LANGFILE "Slovenian" "Slovenski jezik" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Dobrodoli v arovniku namestitve $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Ta arovnik vas vodi skozi namestitev programa $(^NameDA).$\r$\n$\r$\nPred namestitvijo je priporoeno zapreti vsa ostala okna in programe. S tem omogoite nemoteno namestitev programa in potrebnih sistemskih datotek brez ponovnega zagona raunalnika.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Dobrodoli v arovniku za odstranitev $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Ta arovnik vas bo vodil skozi odstranitev $(^NameDA).$\r$\n$\r$\nPreden prinete z odstranitvijo, se prepriajte, da program $(^NameDA) ni zagnan.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licenna pogodba" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Prosimo, da si ogledate pogoje licenne pogodbe pred namestitvijo $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "e se strinjate s pogoji, pritisnite Se strinjam. Da bi lahko namestili $(^NameDA), se morate s pogodbo strinjati." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "e se strinjate z licennimi pogoji pogodbe, spodaj izberite ustrezno okence. Za namestitev $(^NameDA) se morate strinjati s pogoji pogodbe. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "e se strinjate z licennimi pogoji pogodbe, spodaj izberite prvo monost. Za namestitev $(^NameDA) se morate strinjati s pogoji pogodbe. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licenna pogodba" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Prosimo, da pred odstranitvijo $(^NameDA) pregledate pogoje licenne pogodbe." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "e se strinjate s pogoji licenne pogodbe, izberite Se strinjam. Za odstranitev $(^NameDA) se morate strinjati s pogoji." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "e se strinjate s pogoji licenne pogodbe, kliknite na okence spodaj. Za odstranitev $(^NameDA) se morate strinjati s pogoji. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "e se strinjate s pogoji licenne pogodbe, spodaj izberite prvo podano monost. Za odstranitev $(^NameDA) se morate strinjati s pogoji. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Za preostali del pogodbe pritisnite tipko 'Page Down'." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Izbor komponent" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Izberite, katere komponente izdelka $(^NameDA) elite namestiti." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Izbor komponent" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Izberite komponente $(^NameDA), ki jih elite odstraniti." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Opis" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Povlecite miko nad komponento, da vidite njen opis." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Povlecite miko nad komponento, da vidite njen opis." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Izberite pot namestive" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Izberite mapo, v katero elite namestiti $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Izbor mape" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Izberite mapo, iz katere elite odstraniti $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Nameanje poteka" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Prosimo, poakajte, $(^NameDA) se namea." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Dokonana namestitev" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Namestitev je uspeno zakljuena." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Prekinjena namestitev" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Namestitev ni bila uspeno zakljuena." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Odstranjevanje poteka" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Prosimo, poakajte, dokler se program $(^NameDA) odstranjuje." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Odstranitev konana" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Odstranitev je uspeno konana." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Odstranitev prekinjena" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Odstranitev ni bila konana uspeno." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Zakljuevanje namestitve $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Program $(^NameDA) je bil nameen na va raunalnik.$\r$\n$\r$\nPritisnite Dokonaj za zaprtje arovnika." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Za dokonanje namestitve $(^NameDA) morate ponovno zagnati raunalnik. elite zdaj ponovno zagnati raunalnik?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "arovnik za odstranitev $(^NameDA) se zakljuuje" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Program $(^NameDA) je odstranjen z vaega raunalnika.$\r$\n$\r$\nKliknite Dokonaj, da zaprete arovnika." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Da bi se namestitev $(^NameDA) dokonala, morate ponovno zagnati raunalnik. elite zdaj znova zagnati raunalnik?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Ponovni zagon" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Raunalnik elim znova zagnati kasneje" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Zaeni $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Pokai BeriMe" + ${LangFileString} MUI_BUTTONTEXT_FINISH "Do&konaj" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Izberite mapo menija Start" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Izberite mapo menija Start za blinjice do $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Izberite mapo menija Start, kjer elite ustvariti blinjico do programa. e vpiete novo ime, boste ustvarili istoimensko mapo." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ne ustvari blinjic" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Odstranitev $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Odstrani $(^NameDA) z vaega raunalnika." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Ste prepriani, da elite prekiniti namestitev $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Ste prepriani, da elite zapustiti odstranitev $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Izberite uporabnike" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Izberite uporabnike, za katere elite namestiti $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Izberite, ali elite namestiti $(^NameDA) le zase ali za vse uporabnike tega raunalnika. $(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Namesti za vse uporabnike tega raunalnika" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Namesti le zame" +!endif diff --git a/installer/NSIS/Contrib/Language files/Spanish.nlf b/installer/NSIS/Contrib/Language files/Spanish.nlf new file mode 100644 index 0000000..4324eae --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Spanish.nlf @@ -0,0 +1,191 @@ +# Header, don't edit ;Espaol - Espaa (Alfabetizacin Tradicional) +NLF v6 +# Language ID +1034 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by MoNKi & Joel +# Review and minor corrections Darwin Rodrigo Toledo Cceres (niwrad777@gmail.com) www.winamp-es.com +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Instalacin de $(^Name) +# ^UninstallCaption +Desinstalacin de $(^Name) +# ^LicenseSubCaption +: Acuerdo de Licencia +# ^ComponentsSubCaption +: Opciones de Instalacin +# ^DirSubCaption +: Directorio de Instalacin +# ^InstallingSubCaption +: Instalando +# ^CompletedSubCaption +: Completado +# ^UnComponentsSubCaption +: Opciones de Desinstalacin +# ^UnDirSubCaption +: Directorio de Desinstalacin +# ^ConfirmSubCaption +: Confirmacin +# ^UninstallingSubCaption +: Desinstalando +# ^UnCompletedSubCaption +: Completado +# ^BackBtn +< &Atrs +# ^NextBtn +&Siguiente > +# ^AgreeBtn +A&cepto +# ^AcceptBtn +A&cepto los trminos de la licencia +# ^DontAcceptBtn +&No acepto los trminos de la licencia +# ^InstallBtn +&Instalar +# ^UninstallBtn +&Desinstalar +# ^CancelBtn +Cancelar +# ^CloseBtn +&Cerrar +# ^BrowseBtn +&Examinar... +# ^ShowDetailsBtn +Ver &detalles +# ^ClickNext +Presione Siguiente para continuar. +# ^ClickInstall +Presione Instalar para comenzar la instalacin. +# ^ClickUninstall +Presione Desinstalar para comenzar la desinstalacin. +# ^Name +Nombre +# ^Completed +Completado +# ^LicenseText +Por favor, revise el acuerdo de licencia antes de instalar $(^NameDA). Si acepta todos los trminos del acuerdo, presione Acepto. +# ^LicenseTextCB +Por favor, revise el acuerdo de licencia antes de instalar $(^NameDA). Si acepta todos los trminos del acuerdo, marque abajo la casilla. $_CLICK +# ^LicenseTextRB +Por favor, revise el acuerdo de licencia antes de instalar $(^NameDA). Si acepta todos los trminos del acuerdo, seleccione abajo la primera opcin. $_CLICK +# ^UnLicenseText +Por favor, revise el acuerdo de licencia antes de desinstalar $(^NameDA). Si acepta todos los trminos del acuerdo, presione Acepto. +# ^UnLicenseTextCB +Por favor, revise el acuerdo de licencia antes de desinstalar $(^NameDA). Si acepta todos los trminos del acuerdo, marque abajo la casilla. $_CLICK. +# ^UnLicenseTextRB +Por favor, revise el acuerdo de licencia antes de desinstalar $(^NameDA). Si acepta todos los trminos del acuerdo, seleccione abajo la primera opcin. $_CLICK +# ^Custom +Personalizada +# ^ComponentsText +Marque los componentes que desee instalar y desmarque los componentes que no desee instalar. $_CLICK +# ^ComponentsSubText1 +Tipos de instalacin: +# ^ComponentsSubText2_NoInstTypes +Seleccione los componentes a instalar: +# ^ComponentsSubText2 +O seleccione los componentes opcionales que desee instalar: +# ^UnComponentsText +Marque los componentes que desee desinstalar y desmarque los componentes que no desee desinstalar. $_CLICK +# ^UnComponentsSubText1 +Tipos de desinstalacin: +# ^UnComponentsSubText2_NoInstTypes +Seleccione los componentes a desinstalar: +# ^UnComponentsSubText2 +O seleccione los componentes opcionales que desee desinstalar: +# ^DirText +El programa de instalacin instalar $(^NameDA) en el siguiente directorio. Para instalar en un directorio diferente, presione Examinar y seleccione otro directorio. $_CLICK +# ^DirSubText +Directorio de Destino +# ^DirBrowseText +Seleccione el directorio en el que instalar $(^NameDA): +# ^UnDirText +El programa de instalacin desinstalar $(^NameDA) del siguiente directorio. Para desinstalar de un directorio diferente, presione Examinar y seleccione otro directorio. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Seleccione el directorio desde el cual desinstalar $(^NameDA): +# ^SpaceAvailable +Espacio disponible: +# ^SpaceRequired +Espacio requerido: +# ^UninstallingText +$(^NameDA) ser desinstalado del siguiente directorio. $_CLICK +# ^UninstallingSubText +Desinstalando desde: +# ^FileError +Error abriendo archivo para escritura: \r\n\t"$0"\r\nPresione abortar para anular la instalacin,\r\nreintentar para volver a intentar escribir el archivo, u\r\nomitir para ignorar este archivo +# ^FileError_NoIgnore +Error abriendo archivo para escritura: \r\n\t"$0"\r\nPresione reintentar para volver a intentar escribir el archivo, o\r\ncancelar para anular la instalacin +# ^CantWrite +"No pudo escribirse: " +# ^CopyFailed +Fall la copia +# ^CopyTo +"Copiar a " +# ^Registering +"Registrando: " +# ^Unregistering +"Eliminando registro: " +# ^SymbolNotFound +"No pudo encontrarse smbolo: " +# ^CouldNotLoad +"No pudo cargarse: " +# ^CreateFolder +"Crear directorio: " +# ^CreateShortcut +"Crear acceso directo: " +# ^CreatedUninstaller +"Crear desinstalador: " +# ^Delete +"Borrar archivo: " +# ^DeleteOnReboot +"Borrar al reinicio: " +# ^ErrorCreatingShortcut +"Error creando acceso directo: " +# ^ErrorCreating +"Error creando: " +# ^ErrorDecompressing +Error descomprimiendo datos! Instalador corrupto? +# ^ErrorRegistering +Error registrando DLL +# ^ExecShell +"Ejecutar comando: " +# ^Exec +"Ejecutar: " +# ^Extract +"Extraer: " +# ^ErrorWriting +"Extraer: error escribiendo al archivo " +# ^InvalidOpcode +Instalador corrupto: cdigo de operacin no vlido +# ^NoOLE +"Sin OLE para: " +# ^OutputFolder +"Directorio de salida: " +# ^RemoveFolder +"Eliminar directorio: " +# ^RenameOnReboot +"Renombrar al reinicio: " +# ^Rename +"Renombrar: " +# ^Skipped +"Omitido: " +# ^CopyDetails +Copiar Detalles al Portapapeles +# ^LogInstall +Registrar proceso de instalacin +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Spanish.nsh b/installer/NSIS/Contrib/Language files/Spanish.nsh new file mode 100644 index 0000000..00f3c0a --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Spanish.nsh @@ -0,0 +1,130 @@ +;Language: Spanish (1034) +;By MoNKi & Joel +;Updates & Review Darwin Rodrigo Toledo Cceres - www.winamp-es.com - niwrad777@gmail.com + +!insertmacro LANGFILE "Spanish" "Espaol" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Bienvenido al Asistente de Instalacin de $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Este programa instalar $(^NameDA) en su ordenador.$\r$\n$\r$\nSe recomienda que cierre todas las dems aplicaciones antes de iniciar la instalacin. Esto har posible actualizar archivos relacionados con el sistema sin tener que reiniciar su ordenador.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Bienvenido al Asistente de Desinstalacin de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Este asistente le guiar durante la desinstalacin de $(^NameDA).$\r$\n$\r$\nAntes de comenzar la desinstalacin, asegrese de que $(^NameDA) no se est ejecutando.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Acuerdo de licencia" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Por favor revise los trminos de la licencia antes de instalar $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Si acepta todos los trminos del acuerdo, seleccione Acepto para continuar. Debe aceptar el acuerdo para instalar $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si acepta los trminos del acuerdo, marque abajo la casilla. Debe aceptar los trminos para instalar $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si acepta los trminos del acuerdo, seleccione abajo la primera opcin. Debe aceptar los trminos para instalar $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Acuerdo de licencia" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Por favor revise los trminos de la licencia antes de desinstalar $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Si acepta todos los trminos del acuerdo, seleccione Acepto para continuar. Debe aceptar el acuerdo para desinstalar $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si acepta los trminos del acuerdo, marque abajo la casilla. Debe aceptar los trminos para desinstalar $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si acepta los trminos del acuerdo, seleccione abajo la primera opcin. Debe aceptar los trminos para desinstalar $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Presione Avanzar Pgina para ver el resto del acuerdo." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Seleccin de componentes" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Seleccione qu caractersticas de $(^NameDA) desea instalar." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Seleccin de componentes" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Seleccione qu caractersticas de $(^NameDA) desea desinstalar." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descripcin" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Site el ratn encima de un componente para ver su descripcin." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Site el ratn encima de un componente para ver su descripcin." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Elegir lugar de instalacin" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Elija el directorio para instalar $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Elegir lugar de desinstalacin" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Elija el directorio desde el cual se desinstalar $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalando" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Por favor espere mientras $(^NameDA) se instala." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalacin Completada" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "La instalacin se ha completado correctamente." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalacin Anulada" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "La instalacin no se complet correctamente." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Desinstalando" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Por favor espere mientras $(^NameDA) se desinstala." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Desinstalacin Completada" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "La desinstalacin se ha completado correctamente." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Desinstalacin Anulada" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "La desinstalacin no se complet correctamente." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Completando el Asistente de Instalacin de $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) ha sido instalado en su sistema.$\r$\n$\r$\nPresione Terminar para cerrar este asistente." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Su sistema debe ser reiniciado para que pueda completarse la instalacin de $(^NameDA). Desea reiniciar ahora?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Completando el Asistente de Desinstalacin de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) ha sido desinstalado de su sistema.$\r$\n$\r$\nPresione Terminar para cerrar este asistente." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Su ordenador debe ser reiniciado para completar la desinstalacin de $(^NameDA). Desea reiniciar ahora?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reiniciar ahora" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Deseo reiniciar manualmente ms tarde" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Ejecutar $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Ver Lame" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Terminar" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Elegir Carpeta del Men Inicio" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Elija una Carpeta del Men Inicio para los accesos directos de $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Seleccione una carpeta del Men Inicio en la que quiera crear los accesos directos del programa. Tambin puede introducir un nombre para crear una nueva carpeta." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "No crear accesos directos" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Desinstalar $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Elimina $(^NameDA) de su sistema." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Est seguro de que desea salir de la instalacin de $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Est seguro de que desea salir de la desinstalacin de $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Elegir Usuarios" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Elija los usuarios para los cuales Ud. desea instalar $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Seleccione si desea instalar $(^NameDA) slo para Ud. o para todos los usuarios de este Ordenador.$(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Instacin para cualquier usuario de este ordenador" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Instalacin solo para m" +!endif diff --git a/installer/NSIS/Contrib/Language files/SpanishInternational.nlf b/installer/NSIS/Contrib/Language files/SpanishInternational.nlf new file mode 100644 index 0000000..827dd48 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/SpanishInternational.nlf @@ -0,0 +1,191 @@ +# Header, don't edit ;Espaol (Alfabetizacin Internacional) +NLF v6 +# Language ID +3082 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Darwin Rodrigo Toledo Cceres - www.winamp-es.com - nwrad777@gmail.com +# Base traslation by MoNKi & Joel +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Instalacin de $(^Name) +# ^UninstallCaption +Desinstalacin de $(^Name) +# ^LicenseSubCaption +: Acuerdo de Licencia +# ^ComponentsSubCaption +: Opciones de Instalacin +# ^DirSubCaption +: Carpeta de Instalacin +# ^InstallingSubCaption +: Instalando +# ^CompletedSubCaption +: Finalizado +# ^UnComponentsSubCaption +: Opciones de Desinstalacin +# ^UnDirSubCaption +: Carpeta de Desinstalacin +# ^ConfirmSubCaption +: Confirmacin +# ^UninstallingSubCaption +: Desinstalando +# ^UnCompletedSubCaption +: Finalizado +# ^BackBtn +< &Atrs +# ^NextBtn +&Siguiente > +# ^AgreeBtn +&Acepto +# ^AcceptBtn +&Acepto las condiciones del Acuerdo de Licencia +# ^DontAcceptBtn +No &acepto las condiciones del Acuerdo de Licencia +# ^InstallBtn +&Instalar +# ^UninstallBtn +&Desinstalar +# ^CancelBtn +Cancelar +# ^CloseBtn +&Cerrar +# ^BrowseBtn +&Examinar... +# ^ShowDetailsBtn +Mostrar &detalles +# ^ClickNext +Presione Siguiente para continuar. +# ^ClickInstall +Presione Instalar para iniciar la instalacin. +# ^ClickUninstall +Presione Desinstalar para iniciar la desinstalacin. +# ^Name +Nombre +# ^Completed +Finalizado +# ^LicenseText +Por favor, revise el acuerdo de licencia antes de instalar $(^NameDA). Si usted acepta todas las condiciones del acuerdo, presione Acepto. +# ^LicenseTextCB +Por favor, revise el acuerdo de licencia antes de instalar $(^NameDA). Si usted acepta todas las condiciones del acuerdo, marque abajo la casilla. $_CLICK +# ^LicenseTextRB +Por favor, revise el acuerdo de licencia antes de instalar $(^NameDA). Si usted acepta todas las condiciones del acuerdo, seleccione abajo la primera opcin. $_CLICK +# ^UnLicenseText +Por favor, revise el acuerdo de licencia antes de desinstalar $(^NameDA). Si usted acepta todas las condiciones del acuerdo, presione Acepto. +# ^UnLicenseTextCB +Por favor, revise el acuerdo de licencia antes de desinstalar $(^NameDA). Si usted acepta todas las condiciones del acuerdo, marque abajo la casilla. $_CLICK +# ^UnLicenseTextRB +Por favor, revise el acuerdo de licencia antes de desinstalar $(^NameDA). Si usted acepta todas las condiciones del acuerdo, seleccione abajo la primera opcin. $_CLICK +# ^Custom +Personalizada +# ^ComponentsText +Marque los componentes que desee instalar y desmarque los componentes que no desee instalar. $_CLICK +# ^ComponentsSubText1 +Seleccione el tipo de instalacin: +# ^ComponentsSubText2_NoInstTypes +Seleccione los componentes a instalar: +# ^ComponentsSubText2 +O seleccione los componentes opcionales que desee instalar: +# ^UnComponentsText +Marque los componentes que desee desinstalar y desmarque los componentes que no desee desinstalar. $_CLICK +# ^UnComponentsSubText1 +Seleccione el tipo de desinstalacin: +# ^UnComponentsSubText2_NoInstTypes +Seleccione los componentes a desinstalar: +# ^UnComponentsSubText2 +O seleccione los componentes opcionales que desee desinstalar: +# ^DirText +El programa de instalacin instalar $(^NameDA) en la siguiente carpeta. Para instalar en una carpeta diferente, presione Examinar y seleccione otra carpeta. $_CLICK +# ^DirSubText +Carpeta de Destino +# ^DirBrowseText +Seleccione la carpeta en la que instalar $(^NameDA): +# ^UnDirText +El programa de instalacin desinstalar $(^NameDA) de la siguiente carpeta. Para desinstalar de una carpeta diferente, presione Examinar y seleccione otra carpeta. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Seleccione la carpeta desde la que desinstalar $(^NameDA): +# ^SpaceAvailable +Espacio disponible: +# ^SpaceRequired +Espacio requerido: +# ^UninstallingText +$(^NameDA) ser desinstalado de la siguiente carpeta. $_CLICK +# ^UninstallingSubText +Desinstalando desde: +# ^FileError +Error abriendo archivo para escribir: \r\n\r\n$0\r\n\r\nPresione Abortar para detener la instalacin,\r\nReintentar para probar otra vez, o\r\nOmitir para ignorar este archivo. +# ^FileError_NoIgnore +Error abriendo archivo para escribir: \r\n\r\n$0\r\n\r\nPresione Reintentar para probar otra vez, o\r\nCancelar para detener la instalacin. +# ^CantWrite +"No pudo escribirse: " +# ^CopyFailed +Copia fallida +# ^CopyTo +"Copiar a " +# ^Registering +"Registrando: " +# ^Unregistering +"Eliminando registro: " +# ^SymbolNotFound +"No se encontr simbolo: " +# ^CouldNotLoad +"No pudo cargarse: " +# ^CreateFolder +"Crear carpeta: " +# ^CreateShortcut +"Crear acceso directo: " +# ^CreatedUninstaller +"Crear desinstalador: " +# ^Delete +"Borrar archivo: " +# ^DeleteOnReboot +"Borrar al reinicio: " +# ^ErrorCreatingShortcut +"Error creando acceso directo: " +# ^ErrorCreating +"Error creando: " +# ^ErrorDecompressing +Error descomprimiendo datos! Instalador corrupto? +# ^ErrorRegistering +Error registrando DLL +# ^ExecShell +"Ejecutar comando: " +# ^Exec +"Ejecutar: " +# ^Extract +"Extraer: " +# ^ErrorWriting +"Extraer: error escribiendo al archivo " +# ^InvalidOpcode +Instalador corrupto: cdigo de operacin no vlido +# ^NoOLE +"Sin OLE para: " +# ^OutputFolder +"Carpeta de salida: " +# ^RemoveFolder +"Eliminar carpeta: " +# ^RenameOnReboot +"Renombrar al reinicio: " +# ^Rename +"Renombrar: " +# ^Skipped +"Omitido: " +# ^CopyDetails +Copiar Detalles al Portapapeles +# ^LogInstall +Registrar proceso de instalacin +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/SpanishInternational.nsh b/installer/NSIS/Contrib/Language files/SpanishInternational.nsh new file mode 100644 index 0000000..65aa917 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/SpanishInternational.nsh @@ -0,0 +1,130 @@ +;Language: Spanish International (3082) +;By Darwin Rodrigo Toledo Cceres - www.winamp-es.com - niwrad777@gmail.com +;Base by Monki y Joel + +!insertmacro LANGFILE "SpanishInternational" "Espaol (Alfabetizacin Internacional)" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Bienvenido al Asistente de Instalacin de $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Este asistente le guiar a travs de la instalacin de $(^NameDA).$\r$\n$\r$\nSe recomienda que cierre todas la dems aplicaciones antes de iniciar la instalacin. Esto har posible actualizar archivos de sistema sin tener que reiniciar su computadora.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Bienvenido al Asistente de Desinstalacin de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Este asistente le guiar durante la desinstalacin de $(^NameDA).$\r$\n$\r$\nAntes de iniciar la desinstalacin, asegrese de que $(^NameDA) no se est ejecutando.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Acuerdo de licencia" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Por favor revise el acuerdo de licencia antes de instalar $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Si acepta todas las condiciones del acuerdo, seleccione Acepto para continuar. Debe aceptar el acuerdo para instalar $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si acepta las condiciones del acuerdo, marque abajo la casilla. Debe aceptar las condiciones para instalar $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si acepta las condiciones del acuerdo, seleccione abajo la primera opcin. Debe aceptar las condiciones para instalar $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Acuerdo de licencia" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Por favor revise el acuerdo de licencia antes de desinstalar $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Si acepta todas las condiciones del acuerdo, seleccione Acepto para continuar. Debe aceptar el acuerdo para desinstalar $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Si acepta los trminos del acuerdo, marque abajo la casilla. Debe aceptar los trminos para desinstalar $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Si acepta los trminos del acuerdo, seleccione abajo la primera opcin. Debe aceptar los trminos para desinstalar $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Presione Avanzar Pgina para ver el resto del acuerdo." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Seleccin de componentes" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Seleccione qu caractersticas de $(^NameDA) desea instalar." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Seleccin de componentes" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Seleccione qu caractersticas de $(^NameDA) desea desinstalar." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Descripcin" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Site el ratn encima de un componente para ver su descripcin." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Site el ratn encima de un componente para ver su descripcin." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Elegir lugar de instalacin" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Elija la carpeta para instalar $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Elegir lugar de desinstalacin" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Elija la carpeta desde la cual desinstalar $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Instalando" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Por favor espere mientras $(^NameDA) se instala." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Instalacin Finalizada" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "La instalacin se ha finalizado correctamente." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Instalacin Abortada" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "La instalacin no se termin correctamente." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Desinstalando" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Por favor espere mientras $(^NameDA) se desinstala." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Desinstalacin Finalizada" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "La desinstalacin se ha finalizado correctamente." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Desinstalacin Abortada" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "La desinstalacin no se termin correctamente." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Finalizando el Asistente de Instalacin de $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) ha sido instalado en su sistema.$\r$\n$\r$\nPresione Terminar para cerrar este asistente." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Su sistema debe ser reiniciado para poder finalizar la instalacin de $(^NameDA). Desea reiniciar ahora?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Completando el Asistente de Desinstalacin de $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) ha sido desinstalado de su sistema.$\r$\n$\r$\nPresione Terminar para cerrar este asistente." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Su computadora debe ser reiniciada para finalizar la desinstalacin de $(^NameDA). Desea reiniciar ahora?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Reiniciar ahora" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Deseo reiniciar manualmente ms tarde" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Ejecutar $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Mostrar Lame" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Terminar" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Elegir Carpeta del Men Inicio" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Elija una Carpeta del Men Inicio para los accesos directos de $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Seleccione una carpeta del Men Inicio en la que quiera crear los accesos directos del programa. Tambin puede introducir un nombre para crear una nueva carpeta." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "No crear accesos directos" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Desinstalar $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Elimina $(^NameDA) de su sistema." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Est seguro de que desea salir de la instalacin de $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Est seguro de que desea salir de la desinstalacin de $(^Name)?" +!endif + +!ifdef MULTIUSER_INSTALLMODEPAGE + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_TITLE "Elegir Usuarios" + ${LangFileString} MULTIUSER_TEXT_INSTALLMODE_SUBTITLE "Elija los usuarios para los cuales Ud. desea instalar $(^NameDA)." + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_TOP "Elija una opcin si desea instalar $(^NameDA) para slo para Ud., o para todos los usuarios de esta computadora.$(^ClickNext)" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Instacin para cualquier usuario de esta computadora" + ${LangFileString} MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Instalacin solo para m" +!endif diff --git a/installer/NSIS/Contrib/Language files/Swedish.nlf b/installer/NSIS/Contrib/Language files/Swedish.nlf new file mode 100644 index 0000000..d1f2f33 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Swedish.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1053 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Magnus Bonnevier (magnus.bonnevier@telia.com) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Installation +# ^UninstallCaption +$(^Name) Avinstallation +# ^LicenseSubCaption +: Licensavtal +# ^ComponentsSubCaption +: Installationsval +# ^DirSubCaption +: Installationskatalog +# ^InstallingSubCaption +: Installerar +# ^CompletedSubCaption +: Slutfrd +# ^UnComponentsSubCaption +: Avinstallationsval +# ^UnDirSubCaption +: Avinstallationskatalog +# ^ConfirmSubCaption +: Bekrftelse +# ^UninstallingSubCaption +: Avinstallerar +# ^UnCompletedSubCaption +: Slutfrd +# ^BackBtn +< &Tillbaka +# ^NextBtn +&Nsta > +# ^AgreeBtn +Jag &Godknner +# ^AcceptBtn +Jag &Godknner villkoren i licensavtalet +# ^DontAcceptBtn +Jag &Godknner inte villkoren i licensavtalet +# ^InstallBtn +&Installera +# ^UninstallBtn +&Avinstallera +# ^CancelBtn +Avbryt +# ^CloseBtn +&Stng +# ^BrowseBtn +B&lddra... +# ^ShowDetailsBtn +Visa &detaljer +# ^ClickNext +Klicka p Nsta fr att fortstta. +# ^ClickInstall +Klicka p Installera fr att starta installationen. +# ^ClickUninstall +Klicka p Avinstallera fr att starta avinstallationen. +# ^Name +Namn +# ^Completed +Slutfrd +# ^LicenseText +Var vnlig ls igenom licensvillkoren innan du installerar $(^NameDA). Om du accepterar villkoren i avtalet, klicka Jag Godknner. +# ^LicenseTextCB +Var vnlig ls igenom licensvillkoren innan du installerar $(^NameDA). Om du accepterar villkoren i avtalet, klicka i checkrutan nedan. $_CLICK +# ^LicenseTextRB +Var vnlig ls igenom licensvillkoren innan du installerar $(^NameDA). Om du accepterar villkoren i avtalet, vlj det frsta alternativet nedan. $_CLICK +# ^UnLicenseText +Var vnlig ls igenom licensvillkoren innan du avinstallerar $(^NameDA). Om du accepterar villkoren i avtalet, klicka Jag Godknner. +# ^UnLicenseTextCB +Var vnlig ls igenom licensvillkoren innan du avinstallerar $(^NameDA). Om du accepterar villkoren i avtalet, klicka i checkrutan nedan. $_CLICK +# ^UnLicenseTextRB +Var vnlig ls igenom licensvillkoren innan du avinstallerar $(^NameDA). Om du accepterar villkoren i avtalet, vlj det frsta alternativet nedan. $_CLICK +# ^Custom +Valfri +# ^ComponentsText +Markera de komponenter du vill installera och avmarkera de komponenter du inte vill installera. $_CLICK +# ^ComponentsSubText1 +Vlj typ av installation: +# ^ComponentsSubText2_NoInstTypes +Vlj komponenter att installera: +# ^ComponentsSubText2 +Eller, vlj de alternativa komponenter du nskar installera: +# ^UnComponentsText +Markera de komponenter du vill avinstallera och avmarkera de komponenter du inte vill avinstallera. $_CLICK +# ^UnComponentsSubText1 +Vlj typ av avinstallation: +# ^UnComponentsSubText2_NoInstTypes +Vlj komponenter att avinstallera: +# ^UnComponentsSubText2 +Eller, vlj de alternativa komponenter du nskar avinstallera: +# ^DirText +Guiden kommer att installera $(^NameDA) i fljande katalog. Fr att installera i en annan katalog, klicka Blddra och vlj en alternativ katalog. $_CLICK +# ^DirSubText +Mlkatalog +# ^DirBrowseText +Vlj katalog att installera $(^NameDA) i: +# ^UnDirText +Installationsguiden kommer att avinstallera $(^NameDA) frn fljande katalog. Fr att avinstallera frn en annan katalog, klicka Blddra och vlj en annan katalog. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Vlj katalog att avinstallera $(^NameDA) frn: +# ^SpaceAvailable +"Utrymme tillgngligt: " +# ^SpaceRequired +"Utrymme som behvs: " +# ^UninstallingText +$(^NameDA) kommer att avinstalleras frn fljande katalog. $_CLICK +# ^UninstallingSubText +Avinstallerar frn: +# ^FileError +Fel vid ppning av fil fr skrivning: \r\n\t"$0"\r\nKlicka p avbryt fr att avbryta installationen,\r\nfrsk igen fr att frska skriva till filen igen, eller\r\nIgnorera fr att skippa denna fil +# ^FileError_NoIgnore +Fel vid ppning av fil fr skrivning: \r\n\t"$0"\r\nKlicka p frsk igen fr att skriva till filen igen, eller\r\navbryt fr att avbryta installationen +# ^CantWrite +"Kan inte skriva: " +# ^CopyFailed +Kopiering misslyckades +# ^CopyTo +"Kopiera till " +# ^Registering +"Registrerar: " +# ^Unregistering +"Avregistrerar: " +# ^SymbolNotFound +"Kunde inte hitta symbol: " +# ^CouldNotLoad +"Kunde inte ladda: " +# ^CreateFolder +"Skapa katalog: " +# ^CreateShortcut +"Skapa genvg: " +# ^CreatedUninstaller +"Skapade avinstallationsprogram: " +# ^Delete +"Radera fil: " +# ^DeleteOnReboot +"Radera vid omstart: " +# ^ErrorCreatingShortcut +"Fel vid skapande av genvg: " +# ^ErrorCreating +"Fel vid skapande: " +# ^ErrorDecompressing +Fel vid uppackning av data! Skadat installationspaket? +# ^ErrorRegistering +Fel vid registrering av DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Kr: " +# ^Extract +"Extrahera: " +# ^ErrorWriting +"Extrahera: fel vid skrivning till fil " +# ^InvalidOpcode +Installationspaket skadat: ogiltig opcode +# ^NoOLE +"Ingen OLE fr: " +# ^OutputFolder +"Mlkatalog: " +# ^RemoveFolder +"Ta bort katalog: " +# ^RenameOnReboot +"Dp om vid omstart: " +# ^Rename +"Dp om: " +# ^Skipped +"Ignorerad: " +# ^CopyDetails +Kopiera detaljinformation till klippbordet +# ^LogInstall +Logga installationsfrfarandet +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Swedish.nsh b/installer/NSIS/Contrib/Language files/Swedish.nsh new file mode 100644 index 0000000..e096609 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Swedish.nsh @@ -0,0 +1,122 @@ +;Compatible with Modern UI 1.72 +;Language: Swedish (1053) +;By Magnus Bonnevier (magnus.bonnevier@telia.com), updated by Rickard Angbratt (r.angbratt@home.se), updated by Ulf Axelsson (ulf.axelsson@gmail.com) + +!insertmacro LANGFILE "Swedish" "Svenska" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Vlkommen till installationsguiden fr $(^NameDA)." + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Denna guide tar dig igenom installationen av $(^NameDA).$\r$\n$\r$\nDet rekommenderas att du avslutar alla andra program innan du fortstter installationen. Detta tillter att installationen uppdaterar ndvndiga systemfiler utan att behva starta om din dator.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Vlkommen till avinstallationsguiden fr $(^NameDA)." + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Denna guide tar dig igenom avinstallationen av $(^NameDA).$\r$\n$\r$\nInnan du startar avinstallationen, frskra dig om att $(^NameDA) inte krs.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Licensavtal" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Var vnlig ls igenom licensvillkoren innan du installerar $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Om du accepterar villkoren i avtalet, klicka Jag Godknner fr att fortstta. Du mste acceptera avtalet fr att installera $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Om du accepterar villkoren i avtalet, klicka i checkrutan nedan. Du mste acceptera avtalet fr att installera $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Om du accepterar villkoren i avtalet, vlj det frsta alternativet nedan. Du mste acceptera avtalet fr att installera $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Licensavtal" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Var vnlig ls igenom licensvillkoren innan du avinstallerar $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Om du accepterar villkoren i avtalet, klicka Jag Godknner fr att fortstta. Du mste acceptera avtalet fr att avinstallera $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Om du accepterar villkoren i avtalet, klicka i checkrutan nedan. Du mste acceptera avtalet fr att avinstallera $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Om du accepterar villkoren i avtalet, vlj det frsta alternativet nedan. Du mste acceptera avtalet fr att avinstallera $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Tryck Page Down fr att se resten av licensavtalet." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Vlj komponenter" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Vlj vilka alternativ av $(^NameDA) som du vill installera." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Vlj komponenter" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Vlj vilka alternativ av $(^NameDA) som du vill avinstallera." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Beskrivning" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Hll muspekaren ver ett alternativ fr att se dess beskrivning." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Hll muspekaren ver ett alternativ fr att se dess beskrivning." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Vlj installationsvg" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Vlj katalog att installera $(^NameDA) i." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Vlj avinstallationsvg" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Vlj katalog att avinstallera $(^NameDA) frn." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Installerar" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Var vnlig vnta medan $(^NameDA) installeras." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Installationen r klar" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Guiden avslutades korrekt." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Installationen avbrts" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Guiden genomfrdes inte korrekt." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Avinstallerar" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Var vnlig vnta medan $(^NameDA) avinstalleras." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Avinstallationen genomfrd" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Avinstallationen genomfrdes korrekt." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Avinstallationen avbruten" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Avinstallationen genomfrdes inte korrekt." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Avslutar installationsguiden fr $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) har installerats p din dator.$\r$\n$\r$\nKlicka p Slutfr fr att avsluta guiden." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Din dator mste startas om fr att fullborda installationen av $(^NameDA). Vill du starta om nu?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Avslutar avinstallationsguiden fr $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) komponenter har avinstallerats frn din dator.$\r$\n$\r$\nKlicka p Slutfr fr att avsluta guiden." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Din dator mste startas om fr att fullborda avinstallationen av $(^NameDA). Vill du starta om nu?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Starta om nu" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Jag vill starta om sjlv senare" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Kr $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Visa Readme-filen" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Slutfr" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Vlj Startmenykatalog" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Vlj en Startmenykatalog fr programmets genvgar." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Vlj startmenykatalog i vilken du vill skapa programmets genvgar. Du kan ange ett eget namn fr att skapa en ny katalog." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Skapa ej genvgar" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Avinstallera $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Ta bort $(^NameDA) frn din dator." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "r du sker p att du vill avbryta installationen av $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "r du sker p att du vill avbryta avinstallationen av $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Thai.nlf b/installer/NSIS/Contrib/Language files/Thai.nlf new file mode 100644 index 0000000..d47409a --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Thai.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1054 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +874 +# RTL - anything else than RTL means LTR +- +# Translation by SoKoOLz, TuW@nNu (asdfuae) +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) Դ +# ^UninstallCaption +$(^Name) ¡ԡõԴ +# ^LicenseSubCaption +: ͵ŧͧԢԷ +# ^ComponentsSubCaption +: ͡õԴ +# ^DirSubCaption +: Դ +# ^InstallingSubCaption +: ѧԴ +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: ͡¡ԡõԴ +# ^UnDirSubCaption +: ¡ԡõԴ +# ^ConfirmSubCaption +: ׹ѹ +# ^UninstallingSubCaption +: ѧ¡ԡõԴ +# ^UnCompletedSubCaption +: +# ^BackBtn +< &Ѻ +# ^NextBtn +& > +# ^AgreeBtn +&ŧ +# ^AcceptBtn +&ŧѺ͵ҧǢԢԷ +# ^DontAcceptBtn +&Ѻ͵ҧǢԢԷ +# ^InstallBtn +&Դ +# ^UninstallBtn +&¡ԡõԴ +# ^CancelBtn +¡ԡ +# ^CloseBtn +&Դ +# ^BrowseBtn +&¡... +# ^ShowDetailsBtn +&´ +# ^ClickNext + кѵѵ +# ^ClickInstall + Դ ͷӡõԴ +# ^ClickUninstall + ¡ԡõԴ ¡ԡõԴ +# ^Name + +# ^Completed + +# ^LicenseText +ôҹǹͧѺԢԷ͹سзӡõԴ $(^NameDA). ҤسѺ͵ŧ㹷ءҹ, ѹѺ +# ^LicenseTextCB +ôҹǹͧѺԢԷ͹سзӡõԴ $(^NameDA). ҤسѺ͵ŧ㹷ءҹ, ͧ͡ҹҧ. $_CLICK +# ^LicenseTextRB +ôҹǹͧѺԢԷ͹سзӡõԴ $(^NameDA). ҤسѺ͵ŧ㹷ءҹ, ͡͡áҧҧ. $_CLICK +# ^UnLicenseText +ôҹǹͧѺԢԷ͹سзӡ¡ԡԴ $(^NameDA). ҤسѺ͵ŧ㹷ءҹ, ѹѺ +# ^UnLicenseTextCB +ôҹǹͧѺԢԷ͹سзӡ¡ԡԴ $(^NameDA). ҤسѺ͵ŧ㹷ءҹ, ͧ͡ҹҧ. $_CLICK +# ^UnLicenseTextRB +ôҹǹͧѺԢԷ͹سзӡ¡ԡԴ $(^NameDA). ҤسѺ͵ŧ㹷ءҹ, ͡͡áҧҧ. $_CLICK +# ^Custom +˹ͧ +# ^ComponentsText +͡觷سͧõԴ͡觷سͧõԴ $_CLICK +# ^ComponentsSubText1 +͡ԸաáõԴ: +# ^ComponentsSubText2_NoInstTypes +͡觷سͧõԴ: +# ^ComponentsSubText2 +, ͡͡سͧõԴ: +# ^UnComponentsText +͡͡سͧè¡ԡõԴ͡觷سͧè¡ԡõԴ $_CLICK +# ^UnComponentsSubText1 +͡Ըա¡ԡõԴ: +# ^UnComponentsSubText2_NoInstTypes +ͧ͡͡è¡ԡõԴ: +# ^UnComponentsSubText2 +, ͡ҡ͡سͧè¡ԡõԴ: +# ^DirText +ǵԴ駨зӡõԴ $(^NameDA) ŧѧ仹, ҵͧõԴŧ, ¡͡ $_CLICK +# ^DirSubText +ͧõԴ +# ^DirBrowseText +ͧ͡õԴ $(^NameDA) : +# ^UnDirText +ǵԴ駨зӡ¡ԡõԴ $(^NameDA) ҡѧ仹, ҵͧ¡ԡõԴ駨ҡ, ¡ ͡ $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +ͧ͡¡ԡõԴ $(^NameDA) ҡ: +# ^SpaceAvailable +"վ鹷: " +# ^SpaceRequired +"ͧþ鹷: " +# ^UninstallingText +$(^NameDA) ж١¡ԡõԴ駨ҡ仹. $_CLICK +# ^UninstallingSubText +¡ԡõԴ駨ҡ: +# ^FileError +öԴѺ¹: \r\n\r\n$0\r\n\r\n ¡ԡ شõԴ,\r\nͧա ͧա, \r\nԡ ͢. +# ^FileError_NoIgnore +öԴѺ¹: \r\n\r\n$0\r\n\r\n ͧա ͧա, \r\n¡ԡشõԴ +# ^CantWrite +"ö¹: " +# ^CopyFailed +Ѵ͡ԴҴ +# ^CopyTo +"Ѵ͡ѧ " +# ^Registering +"ѧŧ¹: " +# ^Unregistering +"¡ԡŧ¹: " +# ^SymbolNotFound +"öѭѡɳ: " +# ^CouldNotLoad +"öŴ: " +# ^CreateFolder +"ҧ: " +# ^CreateShortcut +"ҧ쵤ѷ: " +# ^CreatedUninstaller +"ҧ¡ԡõԴ: " +# ^Delete +"ź: " +# ^DeleteOnReboot +"ź͹պٷ: " +# ^ErrorCreatingShortcut +"ջѭҧ쵤ѷ: " +# ^ErrorCreating +"ջѭ㹡ҧ: " +# ^ErrorDecompressing +ջѭ㹡ä¢! ԴͼԴҴҡǵԴ? +# ^ErrorRegistering +ջѭ㹡ŧ¹ DLL +# ^ExecShell +"ѹ: " +# ^Exec +"ѹ: " +# ^Extract +"ᵡ: " +# ^ErrorWriting +"ᵡ: Դѭ㹡¹" +# ^InvalidOpcode +ǵԴջѭ: opcode ԴҴ +# ^NoOLE +" OLE Ѻ: " +# ^OutputFolder +"յԴ: " +# ^RemoveFolder +"ź: " +# ^RenameOnReboot +"¹͵͹պٷ: " +# ^Rename +"¹: " +# ^Skipped +": " +# ^CopyDetails +Ѵ͡´ŧԻ +# ^LogInstall +ѹ֡õԴ +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Thai.nsh b/installer/NSIS/Contrib/Language files/Thai.nsh new file mode 100644 index 0000000..d6d16fb --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Thai.nsh @@ -0,0 +1,121 @@ +;Language: Thai (1054) +;By SoKoOLz, TuW@nNu (asdfuae) + +!insertmacro LANGFILE "Thai" "Thai" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Թյ͹ѺõԴ $(^NameDA) " + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "ǵԴѵѵԨйӤسõԴ駢ͧ $(^NameDA).$\r$\n$\r$\nҢйԴ͹Դ, 繡ѻഷ¢¤س繵ͧӡպٷͧس$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Թյ͹Ѻ¡ԡõԴѵѵԢͧ $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "ǵԴѵѵԹйӤس¡ԡõԴ駢ͧ $(^NameDA).$\r$\n$\r$\nè¡ԡõԴ駹, ôǨͺ $(^NameDA) $\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "͵ŧͧԢԷ" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "ôҹǹԢԷǢ͵ҧա駡͹سзӡõԴ $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "ҤسѺ͵ŧͧԢԷ, ѹѺ ͷӵ, سͧѺ㹢͵ŧԢԷͷзӡõԴ $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "ҤسѺ͵ŧͧԢԷ, ͡㹡ͧҧҧ سͧѺ㹢͵ŧԢԷͷзӡõԴ $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "ҤسѺ͵ŧͧԢԷ, ͡͡áҹҧ سͧѺ㹢͵ŧԢԷͷзӡõԴ $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "͵ŧͧԢԷ" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "سҹ͵ŧҹԢԷ͹Դ $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "ҤسѺ㹢͵ŧ سҡ ѹѺ Фسеͧŧ͹¡ԡԴ $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "ҤسѺ͵ŧͧԢԷ, ͡㹡ͧҧҧ سͧѺ㹢͵ŧԢԷͷзӡõԴ $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "ҤسѺ͵ŧͧԢԷ, ͡͡áҹҧ سͧѺ㹢͵ŧԢԷͷзӡõԴ $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP " Page Down ҹ͵ŧ" +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "͡ǹСͺ" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "͡觷سͧҹҡ $(^NameDA) سͧõԴ" +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "͡ǹСͺ" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "͡觷سͧҹҡ $(^NameDA) سͧ¡ԡõԴ" +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "´" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Ѻͧس˹ǹСͺʹ´" + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Ѻͧس˹ǹСͺʹ´" + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "ͧ͡õԴ" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "ͧ͡õԴ $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "ͧ͡¡ԡõԴ" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "͡سͧ¡ԡõԴ駢ͧ $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "ѧԴ" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "ô㹢з $(^NameDA) ѧ١Դ" + ${LangFileString} MUI_TEXT_FINISH_TITLE "õԴ" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "õԴó" + ${LangFileString} MUI_TEXT_ABORT_TITLE "õԴ駶١¡ԡ" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "õԴó" +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "ѧ¡ԡõԴ" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "ô㹢з $(^NameDA) ѧ١¡ԡõԴ." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "¡ԡõԴ" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "¡ԡõԴó" + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "¡ԡõԴ駶١¡ԡ" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "¡ԡõԴ" +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "õԴѵѵԢͧ $(^NameDA) ѧ" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) ١Դŧͧͧس$\r$\n$\r$\n ͻԴǵԴѵѵ" + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "ͧͧس繵ͧʵ÷͡õԴ駢ͧ $(^NameDA) º, سͧè պٷ ǹ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "¡ԡõԴѵѵԢͧ $(^NameDA) ѧó" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) ١¡ԡ͡ҡͧͧس $\r$\n$\r$\n ͻԴ˹Ҩ͵Դѵѵ" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "ͧͧس繵ͧʵ㹡÷зӡ¡ԡõԴ駢ͧ $(^NameDA) , سͧèպٷǹ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "պٷ ǹ" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "ѹͧ պٷµͧ ѧ" + ${LangFileString} MUI_TEXT_FINISH_RUN "&ѹ $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&ʴ´" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "͡ Start Menu" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "͡ Start Menu ҧ쵤ѷͧ $(^NameDA). " + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "͡ Start Menu سͧèҧ쵤ѷͧ, سѧö˹ҧա" + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "ͧҧ 쵤ѷ" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "¡ԡõԴ $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "¡ԡõԴ $(^NameDA) ҡͧͧس" +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "سҤسͧè͡ҡõԴ駢ͧ $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "سҤسͧ͡ҡ¡ԡõԴ駢ͧ $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/TradChinese.nlf b/installer/NSIS/Contrib/Language files/TradChinese.nlf new file mode 100644 index 0000000..a335319 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/TradChinese.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1028 +# Font and size - dash (-) means default rWٻPjp +sө +9 +# Codepage - dash (-) means ANSI code page ANSI rX +950 +# RTL - anything else than RTL means LTR ѥkܥѼg +- +# Translator: Kii Ali ;Revision date: 2004-12-14 +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) w +# ^UninstallCaption +$(^Name) Ѱw +# ^LicenseSubCaption +: vij +# ^ComponentsSubCaption +: w˿ﶵ +# ^DirSubCaption +: w˸Ƨ +# ^InstallingSubCaption +: bw +# ^CompletedSubCaption +: w +# ^UnComponentsSubCaption +: Ѱw˿ﶵ +# ^UnDirSubCaption +: Ѱw˸Ƨ +# ^ConfirmSubCaption +: T{ +# ^UninstallingSubCaption +: bѰw +# ^UnCompletedSubCaption +: +# ^BackBtn +< W@B(&P) +# ^NextBtn +U@B(&N) > +# ^AgreeBtn +ڱ(&I) +# ^AcceptBtn +ڱuvijv(&A) +# ^DontAcceptBtn +ڤuvijv(&N) +# ^InstallBtn +w(&I) +# ^UninstallBtn +(&U) +# ^CancelBtn +(&C) +# ^CloseBtn +(&L) +# ^BrowseBtn +s(&B)... +# ^ShowDetailsBtn +ܲӸ`(&D) +# ^ClickNext +@U [U@B(N)] ~C +# ^ClickInstall +@U [w(I)] }lw˶i{C +# ^ClickUninstall +@U [Ѱw(U)] }lѰw˶i{C +# ^Name +W +# ^Completed +w +# ^LicenseText +bw $(^NameDA) e˾\vijCpGAijҦڡA@U [ڦPN(I)] C +# ^LicenseTextCB +bw $(^NameDA) e˾\vijCpGAijҦڡA@UU誺ĿءC $_CLICK +# ^LicenseTextRB +bw $(^NameDA) e˾\vijCpGAijҦڡAܤU誺Ĥ@ӿﶵC $_CLICK +# ^UnLicenseText +bѰw $(^NameDA) e˾\vijCpGAijҦڡA@U [ڦPN(I)] C +# ^UnLicenseTextCB +bѰw $(^NameDA) e˾\vijCpGAijҦڡA@UU誺ĿءC $_CLICK +# ^UnLicenseTextRB +bѰw $(^NameDA) e˾\vijCpGAijҦڡAܤU誺Ĥ@ӿﶵC $_CLICK +# ^Custom +ۭq +# ^ComponentsText +ĿAQnw˪AøѰĿAƱw˪C $_CLICK +# ^ComponentsSubText1 +w˪: +# ^ComponentsSubText2_NoInstTypes +w˪: +# ^ComponentsSubText2 +Ϊ̡AۭqQw˪: +# ^UnComponentsText +ĿAQnѰw˪AøѰĿAƱѰw˪C $_CLICK +# ^UnComponentsSubText1 +ܸѰw˪: +# ^UnComponentsSubText2_NoInstTypes +ܭnѰw˪: +# ^UnComponentsSubText2 +άOAܷQnѰw˪iﶵ: +# ^DirText +Setup Nw $(^NameDA) bUCƧCnw˨줣PƧA@U [s(B)] ÿܨLƧC $_CLICK +# ^DirSubText +ؼиƧ +# ^DirBrowseText +ܭnw $(^NameDA) Ƨm: +# ^UnDirText +Setup NѰw $(^NameDA) bUCƧCnѰw˨줣PƧA@U [s(B)] ÿܨLƧC $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +ܭnѰw $(^NameDA) Ƨm: +# ^SpaceAvailable +"iΪŶ: " +# ^SpaceRequired +"һݪŶ: " +# ^UninstallingText +oӺFNqAqѰw $(^NameDA) C $_CLICK +# ^UninstallingSubText +Ѱw˥ؿ: +# ^FileError +Lk}ҭngJɮ: \r\n\t"$0"\r\n@U [Abort] wˡA\r\n [Retry] sռgJɮסA\r\n [Ignore] oɮסC +# ^FileError_NoIgnore +Lk}ҭngJɮ: \r\n\t"$0"\r\n@U [Retry] sռgJɮסA\r\n [Cancel] wˡC +# ^CantWrite +"LkgJ: " +# ^CopyFailed +"ƻs " +# ^CopyTo +"ƻs: " +# ^Registering +"bU: " +# ^Unregistering +"bѰU: " +# ^SymbolNotFound +"LkŸ: " +# ^CouldNotLoad +"LkJ: " +# ^CreateFolder +"إ߸Ƨ: " +# ^CreateShortcut +"إ߱|: " +# ^CreatedUninstaller +"إ߸Ѱw˵{: " +# ^Delete +"Rɮ: " +# ^DeleteOnReboot +"sҰʫR: " +# ^ErrorCreatingShortcut +"bإ߱|ɵoͿ~: " +# ^ErrorCreating +"bإ߮ɵoͿ~: " +# ^ErrorDecompressing +"bYƵoͿ~Iwlaw˵{H" +# ^ErrorRegistering +"bU DLL ɵoͿ~" +# ^ExecShell +"~{: " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": LkgJɮ " +# ^InvalidOpcode +"w˷l: LĪ@~NX " +# ^NoOLE +"S OLE Ω: " +# ^OutputFolder +"Xؿ: " +# ^RemoveFolder +"ؿ: " +# ^RenameOnReboot +"sҰʫ᭫sRW: " +# ^Rename +"sRW: " +# ^Skipped +"wL: " +# ^CopyDetails +"ƻsӸ`ŶKï " +# ^LogInstall +"xw˶i{" +# byte +B +# kilo +K +# mega +M +# giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/TradChinese.nsh b/installer/NSIS/Contrib/Language files/TradChinese.nsh new file mode 100644 index 0000000..0e26ba3 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/TradChinese.nsh @@ -0,0 +1,122 @@ +;Language: 'Chinese (Traditional)' (1028) +;Translator: Kii Ali +;Revision date: 2004-12-15 + +!insertmacro LANGFILE "TradChinese" "Chinese (Traditional)" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "wϥ $(^NameDA) w˺F" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "oӺFNާA $(^NameDA) w˶i{C$\r$\n$\r$\nb}lwˤeAijLҦε{CoN\\uw˵{vswtɮסAӤݭnsҰʧAqC$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "wϥ $(^NameDA) Ѱw˺F" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "oӺFN{ާA $(^NameDA) Ѱw˶i{C$\r$\n$\r$\nb}lѰwˤeAT{ $(^NameDA) åC$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "vij" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "bw $(^NameDA) eA˾\vڡC" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "pGAijڡA@U [ڦPN(I)] ~wˡCpGA [(C)] Aw˵{N|Cnij~w $(^NameDA) C" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "pGAijڡA@UU誺ĿءCnij~w $(^NameDA)C$_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "pGAijڡAܤUĤ@ӿﶵCnij~w $(^NameDA)C$_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "vij" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "bѰw $(^NameDA) eA˾\vڡC" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "pGAijڡA@U [ڦPN(I)] ~ѰwˡCpGA [(C)] Aw˵{N|Cnij~Ѱw $(^NameDA) C" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "pGAijڡA@UU誺ĿءCnij~Ѱw $(^NameDA)C$_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "pGAijڡAܤUĤ@ӿﶵCnij~Ѱw $(^NameDA)C$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "˾\ijlAЫ [PgDn] UʭC" +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "ܤ" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "ܧAQnw $(^NameDA) ǥ\C" +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA) AQnѰw˪\C" +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "yz" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "ʧAƹШ줸󤧤WAKi쥦yzC" + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "ʧAƹШ줸󤧤WAKi쥦yzC" + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "w˦m" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE " $(^NameDA) nw˪ƧC" +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Ѱw˦m" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " $(^NameDA) nѰw˪ƧC" +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "bw" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "$(^NameDA) bwˡAеԡC" + ${LangFileString} MUI_TEXT_FINISH_TITLE "w˧" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "w˵{w\a槹C" + ${LangFileString} MUI_TEXT_ABORT_TITLE "wˤv" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "w˵{å\a槹C" +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "bѰw" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "$(^NameDA) bѰwˡAеԡC" + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Ѱwˤw" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Ѱw˵{w\a槹C" + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Ѱwˤw" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Ѱw˵{å\a槹C" +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "b $(^NameDA) w˺F" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) ww˦bAtΡC$\r$\n@U [(F)] FC" + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "AtλݭnsҰʡAHK $(^NameDA) wˡC{bnsҰʶܡH" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "b $(^NameDA) Ѱw˺F" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) wqAqѰwˡC$\r$\n$\r$\n@U [] oӺFC" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "qݭnsҰʡAHK $(^NameDA) ѰwˡC{bQnsҰʶܡH" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "OA{bsҰ(&Y)" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "_AڵyAۦ歫sҰ(&N)" + ${LangFileString} MUI_TEXT_FINISH_RUN " $(^NameDA)(&R)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "ܡuŪɮסv(&M)" + ${LangFileString} MUI_BUTTONTEXT_FINISH "(&F)" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "ܡu}l\vƧ" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "ܡu}l\vƧAΩ{|C" + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "ܡu}l\vƧAHKإߵ{|CA]iHJW١Aإ߷sƧC" + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "nإ߱|(&N)" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Ѱw $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "qAqѰw $(^NameDA) C" +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "ATwn} $(^Name) w˵{H" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "ATwn} $(^Name) Ѱw˶ܡH" +!endif diff --git a/installer/NSIS/Contrib/Language files/Turkish.nlf b/installer/NSIS/Contrib/Language files/Turkish.nlf new file mode 100644 index 0000000..964ef20 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Turkish.nlf @@ -0,0 +1,192 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1055 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1254 +# RTL - anything else than RTL means LTR +- +# Translation by ?atay Dilsiz(chagy) (amigos_cagi@hotmail.com) +# Corrections by Mozilla Trkiye Yerelletirme Topluluu tarafndan evrilmitir. http://mozilla.org.tr +# ^Branding +Nullsoft Kurulum Sistemi %s +# ^SetupCaption +$(^Name) Kurulumu +# ^UninstallCaption +$(^Name) Kaldrma +# ^LicenseSubCaption +: Lisans Szlemesi +# ^ComponentsSubCaption +: Kurulum Seenekleri +# ^DirSubCaption +: Kurulum Dizini +# ^InstallingSubCaption +: Kuruluyor +# ^CompletedSubCaption +: Tamamland +# ^UnComponentsSubCaption +: Kaldrma Seenekleri +# ^UnDirSubCaption +: Kaldrlacak Dizin +# ^ConfirmSubCaption +: Onay +# ^UninstallingSubCaption +: Kaldrlyor +# ^UnCompletedSubCaption +: Tamamland +# ^BackBtn +< &Geri +# ^NextBtn +&leri > +# ^AgreeBtn +&Kabul Ediyorum +# ^AcceptBtn +Lisans Szlemesi'nin koullarn &kabul ediyorum +# ^DontAcceptBtn +Lisans Szlemesi'nin koullarn kabul et&miyorum +# ^InstallBtn +&Kur +# ^UninstallBtn +&Kaldr +# ^CancelBtn +Vazge +# ^CloseBtn +&Kapat +# ^BrowseBtn +&Gzat... +# ^ShowDetailsBtn +&Ayrntlar gster +# ^ClickNext +Devam etmek iin leri dmesine basn. +# ^ClickInstall +Kurulumu balatmak iin Kur dmesine basn. +# ^ClickUninstall +Kaldrmay balatmak iin Kaldr dmesine basn. +# ^Name +Ad +# ^Completed +Tamamland +# ^LicenseText +Ltfen $(^NameDA) uygulamasn kurmadan nce lisans szlemesini gzden geirin. Szlemedeki btn koullar kabul ediyorsanz Kabul Ediyorum dmesine basn. +# ^LicenseTextCB +Ltfen $(^NameDA) uygulamasn kurmadan nce lisans szlemesini gzden geirin. Szlemedeki btn koullar kabul ediyorsanz aadaki kutuya iaret koyun. $_CLICK +# ^LicenseTextRB +Ltfen $(^NameDA) uygulamasn kurmadan nce lisans szlemesini gzden geirin. Szlemedeki btn koullar kabul ediyorsanz aadaki ilk seenei sein. $_CLICK +# ^UnLicenseText +Ltfen $(^NameDA) uygulamasn kaldrmadan nce lisans szlemesini gzden geirin. Szlemedeki btn koullar kabul ediyorsanz Kabul Ediyorum dmesine basn. +# ^UnLicenseTextCB +Ltfen $(^NameDA) uygulamasn kaldrmadan nce lisans szlemesini gzden geirin. Szlemedeki btn koullar kabul ediyorsanz aadaki kutuya iaret koyun. $_CLICK +# ^UnLicenseTextRB +Ltfen $(^NameDA) uygulamasn kaldrmadan nce lisans szlemesini gzden geirin. Szlemedeki btn koullar kabul ediyorsanz aadaki ilk seenei sein. $_CLICK +# ^Custom +zel +# ^ComponentsText +Kurmak istediiniz bileenleri iaretleyip kurmak istemediklerinizi iaretlemeden brakn. $_CLICK +# ^ComponentsSubText1 +Kurulum trn sein: +# ^ComponentsSubText2_NoInstTypes +Kurulacak bileenleri sein: +# ^ComponentsSubText2 +ya da istee bal olarak kurmak istediiniz bileenleri sein: +# ^UnComponentsText +Kaldrmak istediiniz bileenleri iaretleyip kaldrmak istemediklerinizi iaretlemeden brakn. $_CLICK +# ^UnComponentsSubText1 +Kaldrma trn sein: +# ^UnComponentsSubText2_NoInstTypes +Kaldrlacak bileenleri sein: +# ^UnComponentsSubText2 +ya da istee bal olarak kaldrmak istediiniz bileenleri sein: +# ^DirText +$(^NameDA) aadaki dizinde kurulacak. Farkl bir dizinde kurmak iin Gzat dmesine basp baka bir dizin sein. $_CLICK +# ^DirSubText +Hedef Dizin +# ^DirBrowseText +$(^NameDA) uygulamasnn kurulaca dizini sein: +# ^UnDirText +$(^NameDA) aadaki dizinden kaldrlacak. Farkl bir dizinden kaldrmak iin Gzat dmesine basp baka bir dizin sein. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +$(^NameDA) uygulamasnn kaldrlaca dizini sein: +# ^SpaceAvailable +"Kullanlabilir bo alan: " +# ^SpaceRequired +"Gereken bo alan: " +# ^UninstallingText +$(^NameDA) aadaki dizinden kaldrlacak. $_CLICK +# ^UninstallingSubText +Kaldrlan yer: +# ^FileError +Dosya yazmak iin alrken hata meydana geldi: \r\n\r\n$0\r\n\r\nKurulumu durdurmak iin Dur dmesine,\r\nyeniden denemek iin Yeniden Dene dmesine,\r\nbu dosyay atlamak iin Yoksay dmesine basn. +# ^FileError_NoIgnore +Dosya yazmak iin alrken hata meydana geldi: \r\n\r\n$0\r\n\r\nYeniden denemek iin Yeniden Dene dmesine,\r\nkurulumu durdurmak iin Vazge dmesine basn. +# ^CantWrite +"Yazlamad: " +# ^CopyFailed +Kopyalama baarsz oldu +# ^CopyTo +"Kayt: " +# ^Registering +"Kaydediliyor: " +# ^Unregistering +"Kayt siliniyor: " +# ^SymbolNotFound +"Simge bulunamad: " +# ^CouldNotLoad +"Yklenemedi: " +# ^CreateFolder +"Dizin olutur: " +# ^CreateShortcut +"Ksayol olutur: " +# ^CreatedUninstaller +"Kaldrma uygulamas olutur: " +# ^Delete +"Dosya sil: " +# ^DeleteOnReboot +"Alta sil: " +# ^ErrorCreatingShortcut +"Ksayol oluturulurken hata meydana geldi: " +# ^ErrorCreating +"Oluturma hatas: " +# ^ErrorDecompressing +Veriyi aarken hata meydana geldi! Acaba kurulum uygulamas m bozuk? +# ^ErrorRegistering +DLL kaydedilirken hata meydana geldi +# ^ExecShell +"ExecShell: " +# ^Exec +"altr: " +# ^Extract +"A: " +# ^ErrorWriting +"Ama: Dosyaya yazarken hata meydana geldi " +# ^InvalidOpcode +Kurulum bozuk: Geersiz kod +# ^NoOLE +"OLE yok: " +# ^OutputFolder +"kt dizini: " +# ^RemoveFolder +"Dizini sil: " +# ^RenameOnReboot +"Alta adn deitir: " +# ^Rename +"Ad deitir: " +# ^Skipped +"Atland: " +# ^CopyDetails +Ayrntlar panoya kopyala +# ^LogInstall +Kurulum srecinin kaydn tut +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Turkish.nsh b/installer/NSIS/Contrib/Language files/Turkish.nsh new file mode 100644 index 0000000..8387508 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Turkish.nsh @@ -0,0 +1,122 @@ +;Language: Turkish (1055) +;By agatay Dilsiz(Chagy) +;Updated by Fatih BOY (fatih_boy@yahoo.com) + +!insertmacro LANGFILE "Turkish" "Trke" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "$(^NameDA) Kurulum sihirbazna ho geldiniz" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Bu sihirbaz size $(^NameDA) kurulumu boyunca rehberlik edecektir.$\r$\n$\r$\nKurulumu balatmadan nce alan dier programlari kapatmanz neririz. Bylece bilgisayarnz yeniden balatmadan baz sistem dosyalar sorunsuz kurulabilir.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "$(^NameDA) Programn Kaldrma Sihirbazna Ho Geldiniz" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Bu sihirbaz size $(^NameDA) programnn kadrlm boyunca rehberlik edecektir.$\r$\n$\r$\nKaldrm ilemeni balatmadan nce alan dier programlari kapatmanz neririz. Bylece bilgisayarnz yeniden balatmadan baz sistem dosyalar sorunsuz kaldrlabilir.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lisans Szlemesi" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Ltfen $(^NameDA) programn kurmadan nce szlemeyi okuyunuz." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Szleme koullarn kabul ediyorsanz, 'Kabul Ediyorum'a basnz. $(^NameDA) programn kurmak iin szleme koullarn kabul etmelisiniz." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Szleme koullarn kabul ediyorsanz, aadaki onay kutusunu doldurunuz. $(^NameDA) programn kurmak iin szleme koullarn kabul etmelisiniz. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Szleme koullarn kabul ediyorsanz, asagidaki onay dmesini seiniz. $(^NameDA) programn kurmak iin szleme koullarn kabul etmelisiniz. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lisans Szlemesi" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Ltfen $(^NameDA) programn sisteminizden kaldrmadan nce szlemeyi okuyunuz." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Szleme koullarn kabul ediyorsanz, 'Kabul Ediyorum'a basnz. $(^NameDA) programn sisteminizden kaldrmak iin szleme koullarn kabul etmelisiniz." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Szleme koullarn kabul ediyorsanz, aadaki onay kutusunu doldurunuz. $(^NameDA) programn sisteminizden kaldrmak iin szleme koullarn kabul etmelisiniz. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Szleme koullarn kabul ediyorsanz, asagidaki onay dmesini seiniz. $(^NameDA) programn sisteminizden kaldrmak iin szleme koullarn kabul etmelisiniz. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Szlemenin geri kalann okumak iin 'page down' tuuna basabilirsiniz." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Bileen seimi" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Ltfen $(^NameDA) iin kurmak istediginiz bileenleri seiniz." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Bileen eimi" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Ltfen kaldrmak istediiniz $(^NameDA) program bileenini seiniz." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Aklama" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Bileenlerin aklamalarn grmek iin imleci bileen zerine gtrn." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Bileenlerin aklamalarn grmek iin imleci bileen zerine gtrn." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Hedef dizini seimi" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "$(^NameDA) programn kurmak istediiniz dizini einiz." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Kaldrlcak Dizin Seimi" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "$(^NameDA) programn kaldrmak istediginiz dizini seiniz." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Kuruluyor" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Ltfen $(^NameDA) kurulurken bekleyiniz." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Kurulum Tamamland" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Kurulum baaryla tamamland." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Kurulum ptal Edildi" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Kurulum tam olarak tamamlanmad." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Kaldrlyor" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Litfen $(^NameDA) program sisteminizden kaldrlrken bekleyiniz." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Kaldrma lemi Tamamlandr" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Kaldrma ilemi baaryla tamamland." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Kaldrma lemi ptal Edildi" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Kaldrma lemi tamamlanamad." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "$(^NameDA) Kurulum sihirbaz tamamlanyor." + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) bilgisayariniza yklendi.$\r$\n$\r$\nLtfen 'Bitir'e basarak kurulumu sonlandrn." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "$(^NameDA) kurulumunun tamamlanmas iin bilgisayarnz yeniden balatmanz gerekiyor.Bilgisayarnz yeniden balatmak istiyor musunuz?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "$(^NameDA) Program Kaldrma Sihirbaz Tamamlanyor" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) program sisteminizden kaldrld.$\r$\n$\r$\nSihirbaz kapatmak iin 'bitir'e basnz." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "$(^NameDA) programn kaldrma ileminin tamamlanmas iin bilgisayarnzn yeniden balatlmas gerekiyor. Bilgisayarnzn imdi yeniden balatlmasn ister misiniz?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Yeniden balat" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Bilgisayarm daha sonra balatacam." + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) programn altr" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "beni oku/readme dosyasn &gster" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Bitir" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Balat Mens Klasr Seimi" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "$(^NameDA) ksayollarnn konulacag balat mens klasrn seiniz." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Programn ksayollarnn konulaca balat mens klasrn seiniz. Farkl bir isim girerek yeni bir klasr yaratabilirsiniz." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Ksayollar oluturmadan devam et" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "$(^NameDA) Programn Kaldr" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA) programn sisteminizden kaldrma." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "$(^Name) kurulumundan kmak istediinize emin misiniz?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "$(^Name) Programi Kaldrma ileminden kmak istediinize emin misiniz?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Ukrainian.nlf b/installer/NSIS/Contrib/Language files/Ukrainian.nlf new file mode 100644 index 0000000..5a6b4a9 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Ukrainian.nlf @@ -0,0 +1,192 @@ +# Header, don't edit +NLF v6 +# Language ID +1058 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1251 +# RTL - anything else than RTL means LTR +- +# Translation by Yuri Holubow, Nash-Soft.com +# Corrections by Dmitriy Kononchuk [http://gri3ly.kiev.ua] +# New corrections by Osidach Vitaly +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +$(^Name) +# ^UninstallCaption +$(^Name) +# ^LicenseSubCaption +: ˳ +# ^ComponentsSubCaption +: +# ^DirSubCaption +: +# ^InstallingSubCaption +: +# ^CompletedSubCaption +: +# ^UnComponentsSubCaption +: +# ^UnDirSubCaption +: +# ^ConfirmSubCaption +: ϳ +# ^UninstallingSubCaption +: +# ^UnCompletedSubCaption +: +# ^BackBtn +< & +# ^NextBtn +& > +# ^AgreeBtn +& +# ^AcceptBtn + & ˳ +# ^DontAcceptBtn + & ˳ +# ^InstallBtn +& +# ^UninstallBtn + +# ^CancelBtn + +# ^CloseBtn +& +# ^BrowseBtn +&... +# ^ShowDetailsBtn + +# ^ClickNext + +# ^ClickInstall + +# ^ClickUninstall + +# ^Name +' +# ^Completed + +# ^LicenseText + $(^NameDA). , . +# ^LicenseTextCB + $(^NameDA). , . $_CLICK +# ^LicenseTextRB + $(^NameDA). , . $_CLICK +# ^UnLicenseText + $(^NameDA). , . +# ^UnLicenseTextCB + $(^NameDA). , . $_CLICK +# ^UnLicenseTextRB + $(^NameDA). , . $_CLICK +# ^Custom + +# ^ComponentsText + . $_CLICK +# ^ComponentsSubText1 + : +# ^ComponentsSubText2_NoInstTypes + : +# ^ComponentsSubText2 +, , : +# ^UnComponentsText + . ³ , . $_CLICK +# ^UnComponentsSubText1 + : +# ^UnComponentsSubText2_NoInstTypes + : +# ^UnComponentsSubText2 + : +# ^DirText + $(^NameDA) . , , . $_CLICK +# ^DirSubText + +# ^DirBrowseText + $(^NameDA): +# ^UnDirText + $(^NameDA) . , , . $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText + , $(^NameDA): +# ^SpaceAvailable +" : " +# ^SpaceRequired +" : " +# ^UninstallingText + $(^NameDA) '. $_CLICK +# ^UninstallingSubText + : +# ^FileError + : \r\n\t"$0"\r\n , ,\r\n , , \r\n , +# ^FileError_NoIgnore + : \r\n\t"$0"\r\n , , \r\n , +# ^CantWrite +" : " +# ^CopyFailed + +# ^CopyTo +" " +# ^Registering +": " +# ^Unregistering +" : " +# ^SymbolNotFound +" : " +# ^CouldNotLoad +" : " +# ^CreateFolder +" : " +# ^CreateShortcut +" : " +# ^CreatedUninstaller +" : " +# ^Delete +" : " +# ^DeleteOnReboot +" : " +# ^ErrorCreatingShortcut +" : " +# ^ErrorCreating +" : " +# ^ErrorDecompressing + . . +# ^ErrorRegistering + (DLL) +# ^ExecShell +" : " +# ^Exec +": " +# ^Extract +": " +# ^ErrorWriting +": " +# ^InvalidOpcode + : +# ^NoOLE +" OLE : " +# ^OutputFolder +" : " +# ^RemoveFolder +" : " +# ^RenameOnReboot +" : " +# ^Rename +": " +# ^Skipped +": " +# ^CopyDetails + +# ^LogInstall + +# byte + +# kilo + +# mega + +# giga + diff --git a/installer/NSIS/Contrib/Language files/Ukrainian.nsh b/installer/NSIS/Contrib/Language files/Ukrainian.nsh new file mode 100644 index 0000000..bbf108f --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Ukrainian.nsh @@ -0,0 +1,122 @@ +;Language: Ukrainian (1058) +;By Yuri Holubow, http://www.Nash-Soft.com +;Correct by Osidach Vitaly (Vit_Os2) + +!insertmacro LANGFILE "Ukrainian" "Ukrainian" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT " $(^NameDA) '.$\r$\n$\r$\n . .$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT " $(^NameDA).$\r$\n$\r$\n , , $(^NameDA).$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "˳ " + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "- $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM " i , i . i $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "˳ " + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE " $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM " i , i . i $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX " , . $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS " , , . $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "i PageDown ." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE " $(^NameDA) i ." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE " " + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE " $(^NameDA) ." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "ii , ." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "ii , ." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "i " + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "i $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE " " + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE " , $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE " " + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "- , $(^NameDA)." + ${LangFileString} MUI_TEXT_FINISH_TITLE " " + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE " i ." + ${LangFileString} MUI_TEXT_ABORT_TITLE " " + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE " i ." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "- , $(^NameDA)." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE " " + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE " ." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE " " + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE " ." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA) '.$\r$\n$\r$\n ʳ ." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT " , $(^NameDA) ' . ?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) '.$\r$\n$\r$\n , ." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT " , $(^NameDA) ' . ?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW " " + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER " " + ${LangFileString} MUI_TEXT_FINISH_RUN "& $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "& " + ${LangFileString} MUI_BUTTONTEXT_FINISH "&ʳ" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE " " + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE " ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP " . ' ." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX " " +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE " $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE " $(^NameDA) '." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING " i, $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING " $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Uzbek.nlf b/installer/NSIS/Contrib/Language files/Uzbek.nlf new file mode 100644 index 0000000..a64ad97 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Uzbek.nlf @@ -0,0 +1,190 @@ +# Header, don't edit +NLF v6 +# Language ID +1091 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +1252 +# RTL - anything else than RTL means LTR +- +# Translation by Emil Garipov [emil.garipov@gmail.com] +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +O'rnatish $(^Name) +# ^UninstallCaption +O'chirish $(^Name) +# ^LicenseSubCaption +: Lisenzion kelishuv +# ^ComponentsSubCaption +: O'rnatish parametrlari +# ^DirSubCaption +: O'rnatish papkasi +# ^InstallingSubCaption +: Fayllarni kopiya qilish +# ^CompletedSubCaption +: Operatsiya yakunlandi +# ^UnComponentsSubCaption +: O'chirish parametrlari +# ^UnDirSubCaption +: O'chirsh papkasi +# ^ConfirmSubCaption +: Tasdiqlash +# ^UninstallingSubCaption +: Fayllarni o'chirish +# ^UnCompletedSubCaption +: Operatsiya yakunlandi +# ^BackBtn +< &Orqaga +# ^NextBtn +&Oldinga > +# ^AgreeBtn +&Qabul qilaman +# ^AcceptBtn +Men &kelishuv shartlarini qabul qilaman +# ^DontAcceptBtn +Men &kelishuv shartlarini qabul qilmayman +# ^InstallBtn +&O'rnatish +# ^UninstallBtn +&O'chirish +# ^CancelBtn +Bekor qilish +# ^CloseBtn +&Yopish +# ^BrowseBtn +&Ko'rish ... +# ^ShowDetailsBtn +&... +# ^ClickNext +Davom etish uchun 'Oldinga'tugmachasini bosing. +# ^ClickInstall +Dasturni o'rnatish uchun'O'rnatish' tugmachasini bosing. +# ^ClickUninstall +Dasturni o'chirish uchun 'O'chirsh' tugmachasini bosing. +# ^Name +Ism +# ^Completed +Tayor +# ^LicenseText +$(^NameDA)ni o'rnatishdan oldin lisenzion kelishuv bilan tanishib oling. Kelishuv shartlarini qabul qilsangiz 'Qabul qilaman' tugmachasini bosing. +# ^LicenseTextCB +$(^NameDA)ni o'rnatishdan oldin lisenzion kelishuv bilan tanishib oling. Kelishuv shartlarini qabul qilsangiz bayroqchani joylashtiring. $_CLICK +# ^LicenseTextRB +$(^NameDA)ni o'rnatishdan oldin lisenzion kelishuv bilan tanishib oling. Kelishuv shartlarini qabul qilsangiz quyida taklif etilganlardan birinchi variantni tanlang. $_CLICK +# ^UnLicenseText +$(^NameDA)ni o'rnatishdan oldin lisenzion kelishuv bilan tanishib oling. Kelishuv shartlarini qabul qilsangiz 'Qabul qilaman' tugmachasini bosing. +# ^UnLicenseTextCB +$(^NameDA)ni o'rnatishdan oldin lisenzion kelishuv bilan tanishib oling. Kelishuv shartlarini qabul qilsangiz bayroqchani joylashtiring. $_CLICK +# ^UnLicenseTextRB +$(^NameDA)ni o'rnatishdan oldin lisenzion kelishuv bilan tanishib oling. Kelishuv shartlarini qabul qilsangiz quyida taklif etilganlardan birinchi variantni tanlang. $_CLICK +# ^Custom +Tanlash bo'icha +# ^ComponentsText +O'rnatish ucun dastur komponentlarini tanlang. $_CLICK +# ^ComponentsSubText1 +O'rnatish jarayonini tanlang: +# ^ComponentsSubText2_NoInstTypes +O'rnatish uchun dastur komponentlarini tanlang: +# ^ComponentsSubText2 +Yoki o'rnatish uchun qushimcha komponentlarini tanlang: +# ^UnComponentsText +O'chirish uchun dastur komponentlarini tanlang. $_CLICK +# ^UnComponentsSubText1 +O'chirish jarayonini tanlang: +# ^UnComponentsSubText2_NoInstTypes +O'chirish uchun dastur komponentlarini tanlang: +# ^UnComponentsSubText2 +Yoki o'chirish uchun qushimcha komponentlarini tanlang: +# ^DirText +Dastur $(^NameDA)ni ko'rsatilgan papkaga o'rnatadi. Boshqa papkaga o'rnatish uchun, 'Ko'rish'tugmachasini bosing va uni ko'rsatib bering. $_CLICK +# ^DirSubText +O'rnatish papkasi +# ^DirBrowseText +O'rnatish papkasini ko'rsating $(^NameDA): +# ^UnDirText +Dastur $(^NameDA)ni ko'rsatilgan papkadan o'chiradi. Boshqa papkaga o'rnatish uchun, 'Ko'rish'tugmachasini bosing va uni ko'rsatib bering. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +$(^NameDA)ni o'chirish uchun papkani ko'rsating: +# ^SpaceAvailable +"Diskda joriy qilingan: " +# ^SpaceRequired +"Diskda talab qilinadigan: " +# ^UninstallingText +$(^NameDA) dasturi kompyuterizdan uchiriladi. $_CLICK +# ^UninstallingSubText +O'chirilish: +# ^FileError +Yozish uchun faylni ochish imkoniyati yuq: \r\n\t"$0"\r\n'Tuxtashish': O'rnatishni tuxtatish;\r\n"Takrorlash":yana bir o'rinib ko'rish;\r\n"Taylab ketish": shu xarakatni taylab ketish. +# ^FileError_NoIgnore +Yozish uchun faylni ochish imkoniyati yuq: \r\n\t"$0"\r\n'Takrorlash': yana bir o'rinib ko'rish;\r\n'Bekor qilish': o'rnatish protsessini bekor qilish. +# ^CantWrite +"Yozish uchun imkoniyat yuq: " +# ^CopyFailed +Kopiya qilganda xato bor +# ^CopyTo +"Kopiya qilish " +# ^Registering +"Ro'yxatga olish: " +# ^Unregistering +"Ro'xatdan chiqish: " +# ^SymbolNotFound +"Simvolni topish imkoniyati yuq: " +# ^CouldNotLoad +"Zagruzka qilish imkoniyati yuq: " +# ^CreateFolder +"Papkani yaratish: " +# ^CreateShortcut +"Belgini yaratish: " +# ^CreatedUninstaller +"O'chirish dasturini yaratish: " +# ^Delete +"Faylni o'chirish: " +# ^DeleteOnReboot +"Kompyuter qayta yuklash jaraonida o'chirish: " +# ^ErrorCreatingShortcut +"Belgini yaratish jarayonida xato: " +# ^ErrorCreating +"Yaratish xatosi: " +# ^ErrorDecompressing +Ma'lumotlarni asilga qaytarish xatosi! Distributiv ziyonlangan bulishi mumkin. +# ^ErrorRegistering +Kutubxonani ro'xatga olish imkoniyati yuq (DLL) +# ^ExecShell +"Qoplang'ich komandasini bajarish: " +# ^Exec +"Bajarish: " +# ^Extract +"Ichidan olish: " +# ^ErrorWriting +"Ichidan olish: fayl yozish xatosi " +# ^InvalidOpcode +Distributiv ziyonlangan: ruxsatlanmangan kod +# ^NoOLE +"Quydagilarga OLE yuq: " +# ^OutputFolder +"Papkani o'rnatish: " +# ^RemoveFolder +"Papkani o'chirish: " +# ^RenameOnReboot +"Kompyuter qayta yuklanish jarayonida ismni qaita quyish: " +# ^Rename +"Ismni qayta quyish: " +# ^Skipped +"O'tkazib yuborish: " +# ^CopyDetails +Bufer obmenaga ma'lumotlarni kopiya qilish +# ^LogInstall +O'rnatish xisobotini chiqorish +# byte + +# kilo + +# mega + +# giga + \ No newline at end of file diff --git a/installer/NSIS/Contrib/Language files/Uzbek.nsh b/installer/NSIS/Contrib/Language files/Uzbek.nsh new file mode 100644 index 0000000..9c55b61 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Uzbek.nsh @@ -0,0 +1,121 @@ +;Language: Uzbek (1091) +;Translation updated by Emil Garipov [emil.garipov@gmail.com] + +!insertmacro LANGFILE "Uzbek" "Uzbek" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Sizni o'rnatish dastur tabriklaydi $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Bu dastur sizning komputeringizga $(^NameDA) dasturni o'rnatadi.$\r$\n$\r$\nO'rnatishdan oldin ishlayotgan barcha ilovalarni yopish tavsiya etiladi. Bu o'rnatuvchi dasturga kompyuterni qayta yuklamasdan sistemali fayllarni yangilash imkonini beradi.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Sizni $(^NameDA)ni o'chirish dasturi tabriklaydi" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Bu dastur $(^NameDA)ni sizning kompyuteringizdan o'chiradi.$\r$\n$\r$\nO'chirishdan oldin $(^NameDA) dasturni ishlamayotganligini aniqlang.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Lisenzion kelishuv" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "$(^NameDA) dasturini o'rnatishdan oldin lisenzion kelishuv bilan tanishib chiking." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Agar kelishuv shartlariga rozi bo'lsangiz $\"Qabul kilaman$\" tugmasini bosing.Dasturni o'rnatish uchun,kelishuv shartlarini qabul qilish kerak." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Agar siz kelishuv shartlarini qabul kilsangiz,bayroqchani joylashtiring. Dasturni o'rnatish uchun kelisuv shartlarini qabul qilish kerak. $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Kelishuv shartlarini qabul qilsangiz quida taklif etilganlardan birinchi variantni tanlang. Dasturni o'rnatish uchun kelisuv shartlarini qabul qilish kerak. $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Lisenzion kelishuv" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "$(^NameDA)ni o'chirishdan oldin lesinzion kelishuv bilan tanishing." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Agar siz kelishuv shartlariniqabul qilsangiz $\"Qabul qilaman$\" tugmasini bosing. O'chirish uchun kelishuv shartlarini qabul qilishingiz kerak. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Agar shartlarni qabul qilsangiz, bayroqchani o'rnating.O'chirish uchun kelishuv shartlarini qabul qilishingiz kerak. $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Kelishuv shartlarini qabul qilsangiz, taklif etilganlardan birinchi variantni tanlang.O'chirish uchun kelishuv shartlarini qabul qilishingiz kerak. $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Matn bo'icha silgish uchun $\"PageUp$\" va $\"PageDown$\" tugmasidan foydalaning." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "O'rnatilayotgan dastur komponentlari" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "$(^NameDA) dasturning o'zingizga kerak bo'lgan komponentasini tanlang." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Dastur komponentlari" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "$(^NameDA)ning o'chirish kerak bo'lgan komponentlarini tanlang." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Tasvir" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Sichqonchaning kursorini komponent tasvirini o'qish uchun ustiga quying." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Sichqonchaning kursorini komponent tasvirini o'qish uchun ustiga quying." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "O'rnatish papkasini tanlash" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "$(^NameDA)ni o'rnatish uchun papka tanlang." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "O'chiriladigan papkani tanlash" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "$(^NameDA) o'chiriladigan papkasini ko'rsating." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Fayllarni ko'chirish" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Biror kuting, $(^NameDA) fayllari ko'chirilmoqda..." + ${LangFileString} MUI_TEXT_FINISH_TITLE "O'rnatish jarayoni tugadi" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "O'rnatish jarayoni muvaffaqiyat bilan tugadi." + ${LangFileString} MUI_TEXT_ABORT_TITLE "O'rnatish jarayoni uzildi" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "O'rnatish jarayoni tugamadi." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "O'chirish" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Biror kutib turing, $(^NameDA) fayllarini o'chirish bajarilmoqda..." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "O'chirish tuganlandi" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Dasturni o'chirish muvaffaqiyatli yakunlandi." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "O'chirish jarayoni uzildi" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "O'chirish to'la bajarilmadi." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "$(^NameDA)ni o'rnatuvci dasturi o'z ishini tugatmoqda" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "$(^NameDA)ni o'rnatish bajarildi.$\r$\n$\r$\nO'rnatuvchi dasturdan chiqish uchun $\"Tayor$\" tugmasini bosing." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "$(^NameDA) dasturini o'rnatish jarayonini tugatish uchun Kompyuterni qayta yuklash kerak.Shu ishni bajarishni xoziroq istaysizmi?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "$(^NameDA)ni o'chirish dasturi o'z ishini tugatdi." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "$(^NameDA) dasturi kompyuteringizdan o'chirildi.$\r$\n$\r$\nO'chirish dasturidan chiqish uchun $\"Tayor$\"tugmasini bosing." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "$(^NameDA) dasturini o'chirishni tugatish uchun kompyuterni qayta yuklash kerak.shu ishni xozir bajarasizmi?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Ha, kompyuter hozir qayta yuklansin" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Yo'q, bu ishni keyinroq bajaraman" + ${LangFileString} MUI_TEXT_FINISH_RUN "$(^NameDA) &Ishga tushirilsin" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Readme fayli ko'rsatilsin" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Tayor" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Papka $\"$\" menyusida" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Dastur belgilarini joylashtirish uchun $\"$\" menyusidan papka tanlang." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "$\"$\" menyusidan dastur belgilari joylashadigan papka tanlang. Siz papkaning boshqa ismini kiritishingiz mumkin" + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Belgilar yaratilmasin" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "$(^NameDA)ni o'chirish" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "$(^NameDA)ni kompyuterdan o'chirish." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Haqiqatdan ham siz $(^Name)ni o'rnatishni bekor qilmoqchimisiz?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "$(^Name)ni o'chirish jarayonini bekor qilmoqchisizmi?" +!endif diff --git a/installer/NSIS/Contrib/Language files/Welsh.nlf b/installer/NSIS/Contrib/Language files/Welsh.nlf new file mode 100644 index 0000000..85520e2 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Welsh.nlf @@ -0,0 +1,191 @@ +# Header, don't edit +NLF v6 +# Start editing here +# Language ID +1160 +# Font and size - dash (-) means default +- +- +# Codepage - dash (-) means ANSI code page +- +# RTL - anything else than RTL means LTR +- +# Translation by Rhoslyn Prys, Meddal.com +# ^Branding +Nullsoft Install System %s +# ^SetupCaption +Rhaglen Osod $(^Name) +# ^UninstallCaption +Rhaglen Dadosod $(^Name) +# ^LicenseSubCaption +: Cytundeb Trwyddedu +# ^ComponentsSubCaption +: Dewisiadau Gosod +# ^DirSubCaption +: Ffolder Gosod +# ^InstallingSubCaption +: Gosod +# ^CompletedSubCaption +: Cwblhawyd +# ^UnComponentsSubCaption +: Dewisiadau Dadosod +# ^UnDirSubCaption +: Ffolder Dadosod +# ^ConfirmSubCaption +: Cadarnhad +# ^UninstallingSubCaption +: Dadosod +# ^UnCompletedSubCaption +: Cwblhawyd +# ^BackBtn +< &Nl +# ^NextBtn +&Nesaf > +# ^AgreeBtn +&Cytuno +# ^AcceptBtn +Rwy'n &derbyn Amodau'r Drwydded +# ^DontAcceptBtn +Rwy'n &gwrthod Amodau'r Drwydded +# ^InstallBtn +&Gosod +# ^UninstallBtn +&Dadosod +# ^CancelBtn +Diddymu +# ^CloseBtn +C&au +# ^BrowseBtn +&Pori... +# ^ShowDetailsBtn +&Dangos manylion +# ^ClickNext +Cliciwch Nesaf i barhau. +# ^ClickInstall +Cliciwch Gosod i gychwyn gosod. +# ^ClickUninstall +Cliciwch Dadosod i gychwyn dadosod. +# ^Name +Enw +# ^Completed +Cwblhawyd +# ^LicenseText +Darllenwch y cytundeb trwyddedu cyn gosod $(^NameDA). Os ydych yn derbyn holl amodau'r cytundeb, cliciwch Cytuno. +# ^LicenseTextCB +Darllenwch y cytundeb trwyddedu cyn gosod $(^NameDA). Os ydych yn derbyn holl amodau'r cytundeb, cliciwch y blwch ticio isod. $_CLICK +# ^LicenseTextRB +Darllenwch y cytundeb trwyddedu cyn gosod $(^NameDA). Os ydych yn derbyn holl amodau'r cytundeb, ticiwch y dewis cyntaf isod. $_CLICK +# ^UnLicenseText +Darllenwch y cytundeb trwyddedu cyn dadosod $(^NameDA). Os ydych yn derbyn holl amodau'r cytundeb, cliciwch Cytuno. +# ^UnLicenseTextCB +Darllenwch y cytundeb trwyddedu cyn dadosod $(^NameDA). Os ydych yn derbyn holl amodau'r cytundeb, cliciwch y blwch ticio isod. $_CLICK +# ^UnLicenseTextRB +Darllenwch y cytundeb trwyddedu cyn dadosod $(^NameDA). Os ydych yn derbyn holl amodau'r cytundeb, ticiwch y dewis cyntaf isod. $_CLICK +# ^Custom +Addasu +# ^ComponentsText +Ticiwch y cydrannau rydych am eu gosod a dad-dicio'r cydrannau nad ydych am eu gosod. $_CLICK +# ^ComponentsSubText1 +Dewis y math o osod: +# ^ComponentsSubText2_NoInstTypes +Dewis cydrannau i'w gosod: +# ^ComponentsSubText2 +Neu, ddewis y cydrannau ychwanegol i'w gosod: +# ^UnComponentsText +Ticiwch y cydrannau rydych am eu dadosod a dad-dicio'r cydrannau nad ydych am eu dadosod. $_CLICK +# ^UnComponentsSubText1 +Dewis y math o ddadosod: +# ^UnComponentsSubText2_NoInstTypes +Dewis cydrannau i'w dadosod: +# ^UnComponentsSubText2 +Neu, ddewis y cydrannau ychwanegol i'w dadosod: +# ^DirText +Bydd y Rhaglen Osod yn gosod $(^NameDA) yn y ffolder canlynol. I'w osod mewn ffolder gwahanol, cliciwch Pori a dewis ffolder arall. $_CLICK +# ^DirSubText +Ffolder Cyrchfan +# ^DirBrowseText +Dewis y ffolder i osod $(^NameDA) ynddo: +# ^UnDirText +Bydd y Rhegen Osod yn dadosod $(^NameDA) o'r ffolder canlynol. I ddadosod o ffolder gwahanol, cliciwch Pori a dewis ffolder arall. $_CLICK +# ^UnDirSubText +"" +# ^UnDirBrowseText +Dewis ffolder i ddadosod $(^NameDA) ohono: +# ^SpaceAvailable +"Lle ar gael: " +# ^SpaceRequired +"Lle angenrheidiol: " +# ^UninstallingText +Bydd $(^NameDA) yn cael ei ddadosod o'r ffolder canlynol. $_CLICK +# ^UninstallingSubText +Dadosod o: +# ^FileError +Gwall agor ffeil i'w hysgrifennu: \r\n\r\n$0\r\n\r\nCliciwch Atal i atal y gosod,\r\nEto i geisio eto, neu\r\nAnwybyddu i hepgor y ffeil. +# ^FileError_NoIgnore +Gwall agor ffeil i'w hysgrifennu: \r\n\r\n$0\r\n\r\nCliciwch Eto i geisio eto, neu\r\nDiddymu i atal y gosod. +# ^CantWrite +"Methu ysgrifennu: " +# ^CopyFailed +Methu Copo +# ^CopyTo +"Copo i " +# ^Registering +"Cofrestru: " +# ^Unregistering +"Dadgofrestru: " +# ^SymbolNotFound +"Methu canfod symbol: " +# ^CouldNotLoad +"Methu llwytho: " +# ^CreateFolder +"Creu ffolder: " +# ^CreateShortcut +"Creu llwybr byr: " +# ^CreatedUninstaller +"Creu dadosodwr: " +# ^Delete +"Dileu ffeil: " +# ^DeleteOnReboot +"Dileu wrth ailgychwyn: " +# ^ErrorCreatingShortcut +"Gwall wrth greu llwybr byr: " +# ^ErrorCreating +"Gwall wrth greu: " +# ^ErrorDecompressing +Gwall wrth ddatgywasgu data! Gosodwr llwgr? +# ^ErrorRegistering +Gwall cofrestru DLL +# ^ExecShell +"ExecShell: " +# ^Exec +"Gweithredu: " +# ^Extract +"Echdynnu: " +# ^ErrorWriting +"Echdynnu: gwall ysgrifennu i ffeil " +# ^InvalidOpcode +Gosodwr llwgr: opcode annilys +# ^NoOLE +"Dim OLE ar gyfer: " +# ^OutputFolder +"Ffolder allbwn: " +# ^RemoveFolder +"Tynnu ffolder: " +# ^RenameOnReboot +"Ailenwi wrth ailgychwyn: " +# ^Rename +"Ailenwi: " +# ^Skipped +"Hepgor: " +# ^CopyDetails +Copo Manylion i'r Clipfwrdd +# ^LogInstall +Cofnodi'r brosed gosod +# ^Byte +B +# ^Kilo +K +# ^Mega +M +# ^Giga +G diff --git a/installer/NSIS/Contrib/Language files/Welsh.nsh b/installer/NSIS/Contrib/Language files/Welsh.nsh new file mode 100644 index 0000000..158f1b9 --- /dev/null +++ b/installer/NSIS/Contrib/Language files/Welsh.nsh @@ -0,0 +1,121 @@ +;Language: Welsh (1106) +;By Rhoslyn Prys, Meddal.com + +!insertmacro LANGFILE "Welsh" "Welsh" + +!ifdef MUI_WELCOMEPAGE + ${LangFileString} MUI_TEXT_WELCOME_INFO_TITLE "Croeso i Ddewin Gosod $(^NameDA)" + ${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Bydd y dewin yn eich arwain drwy osodiad $(^NameDA).$\r$\n$\r$\nCaewch pob rhaglen cyn cychwyn y rhaglen osod. Bydd hyn yn ei gwneud yn bosibl i ddiweddaru'r ffeiliau system berthnasol heb fod angen ailgychwyn eich cyfrifiadur.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_UNWELCOMEPAGE + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TITLE "Croeso i Ddewin Dadosod $(^NameDA)" + ${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Bydd y dewin yn eich arwain drwy ddadosod $(^NameDA).$\r$\n$\r$\nCyn cychwyn dadosod, gwnewch yn siwr nad yw $(^NameDA) yn rhedeg.$\r$\n$\r$\n$_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE + ${LangFileString} MUI_TEXT_LICENSE_TITLE "Cytundeb Trwyddedu" + ${LangFileString} MUI_TEXT_LICENSE_SUBTITLE "Darllenwch amodau'r drwydded cyn gosod $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM "Os ydych yn derbyn amodau'r cytundeb, cliciwch Cytuno i barhau. Mae'n rhaid i chi dderbyn amodau'r cytundeb er mwyn gosod $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX "Os ydych yn derbyn amodau'r cytundeb, cliciwch y blwch ticio isod. Mae'n rhaid i chi dderbyn amodau'r cytundeb er mwyn gosod $(^NameDA). $_CLICK" + ${LangFileString} MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Os ydych yn derbyn amodau'r cytundeb, cliciwch y dewis cyntaf isod. Mae'n rhaid i chi dderbyn amodau'r cytundeb er mwyn gosod $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_UNLICENSEPAGE + ${LangFileString} MUI_UNTEXT_LICENSE_TITLE "Cytundeb Trwyddedu" + ${LangFileString} MUI_UNTEXT_LICENSE_SUBTITLE "Darllenwch amodau'r drwydded cyn dadosod $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM "Os ydych yn derbyn amodau'r cytundeb, cliciwch Cytuno i barhau. Mae'n rhaid i chi dderbyn amodau'r cytundeb er mwyn dadosod $(^NameDA)." + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX "Os ydych yn derbyn amodau'r cytundeb, cliciwch y blwch ticio isod. Mae'n rhaid i chi dderbyn amodau'r cytundeb er mwyn dadosod $(^NameDA). $_CLICK" + ${LangFileString} MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS "Os ydych yn derbyn amodau'r cytundeb, cliciwch y dewis cyntaf isod. Mae'n rhaid i chi dderbyn amodau'r cytundeb er mwyn dadosod $(^NameDA). $_CLICK" +!endif + +!ifdef MUI_LICENSEPAGE | MUI_UNLICENSEPAGE + ${LangFileString} MUI_INNERTEXT_LICENSE_TOP "Pwyswch Page Down i ddarllen gweddill y cytundeb." +!endif + +!ifdef MUI_COMPONENTSPAGE + ${LangFileString} MUI_TEXT_COMPONENTS_TITLE "Dewis Cydrannau" + ${LangFileString} MUI_TEXT_COMPONENTS_SUBTITLE "Dewis pa nodweddion o $(^NameDA) rydych am eu gosod." +!endif + +!ifdef MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_UNTEXT_COMPONENTS_TITLE "Dewis Cydrannau" + ${LangFileString} MUI_UNTEXT_COMPONENTS_SUBTITLE "Dewis pa nodweddion o $(^NameDA) i'w dadoso." +!endif + +!ifdef MUI_COMPONENTSPAGE | MUI_UNCOMPONENTSPAGE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE "Disgrifiad" + !ifndef NSIS_CONFIG_COMPONENTPAGE_ALTERNATIVE + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Gosod eich llygoden dros gydran i weld ei ddisgrifiad." + !else + ${LangFileString} MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO "Gosod eich llygoden dros gydran i weld ei ddisgrifiad." + !endif +!endif + +!ifdef MUI_DIRECTORYPAGE + ${LangFileString} MUI_TEXT_DIRECTORY_TITLE "Dewis Lleoliad Gosod" + ${LangFileString} MUI_TEXT_DIRECTORY_SUBTITLE "Dewis y ffolder i osod $(^NameDA)." +!endif + +!ifdef MUI_UNDIRECTORYPAGE + ${LangFileString} MUI_UNTEXT_DIRECTORY_TITLE "Dewis Lleoliad Dadosod" + ${LangFileString} MUI_UNTEXT_DIRECTORY_SUBTITLE "Dewis y ffolder i ddadosod $(^NameDA)." +!endif + +!ifdef MUI_INSTFILESPAGE + ${LangFileString} MUI_TEXT_INSTALLING_TITLE "Gosod" + ${LangFileString} MUI_TEXT_INSTALLING_SUBTITLE "Arhoswch tra fo $(^NameDA) yn cael ei osod." + ${LangFileString} MUI_TEXT_FINISH_TITLE "Cwblhawyd y Gosod" + ${LangFileString} MUI_TEXT_FINISH_SUBTITLE "Mae'r Gosod wedi ei gwblhau'n llwyddiannus." + ${LangFileString} MUI_TEXT_ABORT_TITLE "Ataliwyd y Gosod" + ${LangFileString} MUI_TEXT_ABORT_SUBTITLE "Methwyd chwblhau'r gosod yn llwyddiannus." +!endif + +!ifdef MUI_UNINSTFILESPAGE + ${LangFileString} MUI_UNTEXT_UNINSTALLING_TITLE "Dadosod" + ${LangFileString} MUI_UNTEXT_UNINSTALLING_SUBTITLE "Arhoswch tra bo $(^NameDA) yn cael ei ddadosod." + ${LangFileString} MUI_UNTEXT_FINISH_TITLE "Cwblhawyd y Dadosod" + ${LangFileString} MUI_UNTEXT_FINISH_SUBTITLE "Mae'r Dadosod wedi ei gwblhau'n llwyddiannus." + ${LangFileString} MUI_UNTEXT_ABORT_TITLE "Ataliwyd y Dadosod" + ${LangFileString} MUI_UNTEXT_ABORT_SUBTITLE "Methwyd chwblhau'r dadosod yn llwyddiannus." +!endif + +!ifdef MUI_FINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_INFO_TITLE "Cwblhau Dewin Gosod $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_INFO_TEXT "Mae $(^NameDA) wedi cael ei osod ar eich cyfrifiadur.$\r$\n$\r$\nCliciwch Gorffen i gau'r dewin." + ${LangFileString} MUI_TEXT_FINISH_INFO_REBOOT "Rhaid ailgychwyn eich cyfrifiadur i gwblhau gosod $(^NameDA). Hoffech chi ailgychwyn?" +!endif + +!ifdef MUI_UNFINISHPAGE + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TITLE "Cwblhau Dewin Dadosod $(^NameDA)" + ${LangFileString} MUI_UNTEXT_FINISH_INFO_TEXT "Mae $(^NameDA) wedi ei ddadosod oddi ar eich cyfrifiadur.$\r$\n$\r$\nCliciwch Gorffen i gau'r dewin." + ${LangFileString} MUI_UNTEXT_FINISH_INFO_REBOOT "Rhaid ailgychwyn eich cyfrifiadur i gwblhau gosod $(^NameDA). Hoffech chi ailgychwyn?" +!endif + +!ifdef MUI_FINISHPAGE | MUI_UNFINISHPAGE + ${LangFileString} MUI_TEXT_FINISH_REBOOTNOW "Ailgychwyn" + ${LangFileString} MUI_TEXT_FINISH_REBOOTLATER "Rwyf am ailgychwyn yn hwyrach" + ${LangFileString} MUI_TEXT_FINISH_RUN "&Rhedeg $(^NameDA)" + ${LangFileString} MUI_TEXT_FINISH_SHOWREADME "&Dangos Darllenfi" + ${LangFileString} MUI_BUTTONTEXT_FINISH "&Gorffen" +!endif + +!ifdef MUI_STARTMENUPAGE + ${LangFileString} MUI_TEXT_STARTMENU_TITLE "Dewis Ffolder Dewislen Cychwyn" + ${LangFileString} MUI_TEXT_STARTMENU_SUBTITLE "Dewis ffolder Dewislen Cychwyn ar gyfer llwybrau byr $(^NameDA)." + ${LangFileString} MUI_INNERTEXT_STARTMENU_TOP "Dewis ffolder Dewislen Cychwyn i greu llwybrau byr y rhaglen. Gallwch roi enw i greu ffolder newydd." + ${LangFileString} MUI_INNERTEXT_STARTMENU_CHECKBOX "Peidio creu llwybrau byr" +!endif + +!ifdef MUI_UNCONFIRMPAGE + ${LangFileString} MUI_UNTEXT_CONFIRM_TITLE "Dadosod $(^NameDA)" + ${LangFileString} MUI_UNTEXT_CONFIRM_SUBTITLE "Tynnu $(^NameDA) oddiar eich cyfrifiadur." +!endif + +!ifdef MUI_ABORTWARNING + ${LangFileString} MUI_TEXT_ABORTWARNING "Ydych chi'n si?r eich bod am adael Rhaglen Osod $(^Name)?" +!endif + +!ifdef MUI_UNABORTWARNING + ${LangFileString} MUI_UNTEXT_ABORTWARNING "Ydych chi'n siwr eich bod am adael Rhaglen Dadosod $(^Name)?" +!endif diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0000_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0000_in.jpg new file mode 100644 index 0000000..1bb213c Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0000_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0001_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0001_un.jpg new file mode 100644 index 0000000..3f17a28 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0001_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0002_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0002_in.jpg new file mode 100644 index 0000000..5120b4c Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0002_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0003_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0003_un.jpg new file mode 100644 index 0000000..65c5181 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0003_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0004_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0004_in.jpg new file mode 100644 index 0000000..428f4ce Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0004_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0005_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0005_un.jpg new file mode 100644 index 0000000..eb1e7a1 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0005_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0006_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0006_in.jpg new file mode 100644 index 0000000..54fc0c8 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0006_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0007_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0007_un.jpg new file mode 100644 index 0000000..00b4ee4 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0007_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0008_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0008_in.jpg new file mode 100644 index 0000000..883d172 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0008_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0009_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0009_un.jpg new file mode 100644 index 0000000..2169df2 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0009_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0010_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0010_in.jpg new file mode 100644 index 0000000..0841fd8 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0010_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0011_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0011_un.jpg new file mode 100644 index 0000000..db7a4b4 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0011_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0012_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0012_in.jpg new file mode 100644 index 0000000..02e86cc Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0012_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0013_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0013_un.jpg new file mode 100644 index 0000000..1d4354b Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0013_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0014_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0014_in.jpg new file mode 100644 index 0000000..eb5c88c Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0014_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0015_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0015_un.jpg new file mode 100644 index 0000000..9a59543 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0015_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0016_in.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0016_in.jpg new file mode 100644 index 0000000..2e1fd52 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0016_in.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0017_un.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0017_un.jpg new file mode 100644 index 0000000..7446cf3 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/icon_0017_un.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image1.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image1.jpg new file mode 100644 index 0000000..3fde926 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image1.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image2.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image2.jpg new file mode 100644 index 0000000..c31b52a Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image2.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image3.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image3.jpg new file mode 100644 index 0000000..1020a5f Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image3.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image4.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image4.jpg new file mode 100644 index 0000000..a1980e1 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/image4.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/web-header.jpg b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/web-header.jpg new file mode 100644 index 0000000..23f1ff1 Binary files /dev/null and b/installer/NSIS/Contrib/MUI Orange Vista Theme/Docs/web-header.jpg differ diff --git a/installer/NSIS/Contrib/MUI Orange Vista Theme/Readme.htm b/installer/NSIS/Contrib/MUI Orange Vista Theme/Readme.htm new file mode 100644 index 0000000..9e7cfff --- /dev/null +++ b/installer/NSIS/Contrib/MUI Orange Vista Theme/Readme.htm @@ -0,0 +1,179 @@ + + + + +Orange Vista NSIS Modern UI Theme + + + + +
    +
    +

    Orange Vista NSIS Modern UI Theme

    +
    +
    + +

    +

    Description

    +

    The "Orange Vista Modern UI Theme" is a professional collection of icons and bitmaps with a Windows Vista style for NullSoft Scriptable Install System (NSIS) and his Modern UI.

    +

    The icons have the new 256x256 icon format and adapts perfectly with the look and feel of Windows Vista.

    +

    The theme is a evolution of my old "Orange" Modern UI Theme. You can see and download it here

    +

    It has 9 variants, each one with different icons:

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Each theme consist of two icons (un/install), two "wizard" bitmaps (un/install), and four "header" bitmaps (un/install, left/right).

    + +

    Welcome/Finish Bitmaps

    +

    + +

    Headers

    +

    + +

    Icons

    +

    +

    + +

    Usage

    +

    Here's a simple copy-n-paste example on how to use the Orange Vista theme.
    + The code below uses the "CD-Clean" variant and a right-sided header.

    +
    ; Sets the theme path
    +	
    +	!define OMUI_THEME_PATH "${NSISDIR}\Contrib\MUI Orange Vista Theme\CD-Clean"
    +  
    +; MUI Settings / Icons
    +
    +; In the moment of writing this, NSIS don't support well Vista icons with PNG compression.
    +; We provide both, compressed and uncompressed (-nopng) icons.
    +
    +	!define MUI_ICON "${OMUI_THEME_PATH\installer-nopng.ico"
    +	!define MUI_UNICON "${OMUI_THEME_PATH}\uninstaller-nopng.ico"
    + 
    +; MUI Settings / Header
    +	!define MUI_HEADERIMAGE
    +	!define MUI_HEADERIMAGE_RIGHT
    +	!define MUI_HEADERIMAGE_BITMAP "${OMUI_THEME_PATH}\header-r.bmp"
    +	!define MUI_HEADERIMAGE_UNBITMAP "${OMUI_THEME_PATH\header-r-un.bmp"
    + 
    +; MUI Settings / Wizard		
    +	!define MUI_WELCOMEFINISHPAGE_BITMAP "${OMUI_THEME_PATH\wizard.bmp"
    +	!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${OMUI_THEME_PATH\wizard-un.bmp"
    + +

    License

    +

    The "Orange Vista" Modern UI Theme is licensed under a "Attribution-NoDerivs 3.0 Unported" Creative Commons License. You can use it freely on your instalers without ask me.

    +

    +Creative Commons License + +
    +This + +work is licensed under a +Creative +Commons Attribution-No Derivative Works 3.0 License.

    +

    +

    About the author

    +

    Manuel Lozano (me) is a spanish graphic designer and programmer born in 1981 specialized in web design.

    +

    You can see my blog at http://www.monki.es

    +
    +
    + + diff --git a/installer/NSIS/Contrib/Modern UI 2/Deprecated.nsh b/installer/NSIS/Contrib/Modern UI 2/Deprecated.nsh new file mode 100644 index 0000000..754fd4d --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Deprecated.nsh @@ -0,0 +1,72 @@ +/* + +NSIS Modern User Interface +Deprecated code - display warnings + +*/ + +;-------------------------------- +;InstallOptions + +!define INSTALLOPTIONS_ERROR "MUI_INSTALLOPTIONS_* macros are no longer a part of MUI2. Include InstallOptions.nsh and use INSTALLOPTIONS_* macros instead. It is also recommended to upgrade to nsDialogs." + +!macro MUI_INSTALLOPTIONS_EXTRACT FILE + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_EXTRACT_AS FILE FILENAME + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_DISPLAY FILE + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_DISPLAY_RETURN FILE + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_INITDIALOG FILE + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_SHOW + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_SHOW_RETURN + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_READ VAR FILE SECTION KEY + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_INSTALLOPTIONS_WRITE FILE SECTION KEY VALUE + + !error "${INSTALLOPTIONS_ERROR}" + +!macroend + +!macro MUI_RESERVEFILE_INSTALLOPTIONS + + !define MUI_DOLLAR "$" + !error "MUI_RESERVEFILE_INSTALLOPTIONS is no longer supported as InstallOptions is no longer used by MUI2. Instead, use ReserveFile '${MUI_DOLLAR}{NSISDIR}\Plugins\InstallOptions.dll'. It is also recommended to upgrade to nsDialogs." + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Interface.nsh b/installer/NSIS/Contrib/Modern UI 2/Interface.nsh new file mode 100644 index 0000000..e7f89d6 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Interface.nsh @@ -0,0 +1,304 @@ +/* + +NSIS Modern User Interface +Interface code for all pages + +*/ + +;-------------------------------- +;Variables + +Var mui.Header.Text +Var mui.Header.Text.Font +Var mui.Header.SubText +Var mui.Header.Background +Var mui.Header.Image + +Var mui.Branding.Text +Var mui.Branding.Background + +Var mui.Line.Standard +Var mui.Line.FullWindow + +Var mui.Button.Next +Var mui.Button.Cancel +Var mui.Button.Back + + +;-------------------------------- +;General interface settings + +!macro MUI_INTERFACE + + !ifndef MUI_INTERFACE + + !define MUI_INTERFACE + + ;These values are set after the interface settings in the script, + ;so the script itself can override all values. + + ;Default interface settings in nsisconf.nsh + !ifdef MUI_INSERT_NSISCONF + !insertmacro MUI_NSISCONF + !endif + + ;Default interface settings + !insertmacro MUI_DEFAULT MUI_UI "${NSISDIR}\Contrib\UIs\modern.exe" + !insertmacro MUI_DEFAULT MUI_UI_HEADERIMAGE "${NSISDIR}\Contrib\UIs\modern_headerbmp.exe" + !insertmacro MUI_DEFAULT MUI_UI_HEADERIMAGE_RIGHT "${NSISDIR}\Contrib\UIs\modern_headerbmpr.exe" + !insertmacro MUI_DEFAULT MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" + !insertmacro MUI_DEFAULT MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" + !insertmacro MUI_DEFAULT MUI_BGCOLOR "FFFFFF" + + ;Default header images + !ifdef MUI_HEADERIMAGE + + !insertmacro MUI_DEFAULT MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp" + + !ifndef MUI_HEADERIMAGE_UNBITMAP + !define MUI_HEADERIMAGE_UNBITMAP "${MUI_HEADERIMAGE_BITMAP}" + !ifdef MUI_HEADERIMAGE_BITMAP_NOSTRETCH + !insertmacro MUI_SET MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH "" + !endif + !endif + + !ifdef MUI_HEADERIMAGE_BITMAP_RTL + !ifndef MUI_HEADERIMAGE_UNBITMAP_RTL + !define MUI_HEADERIMAGE_UNBITMAP_RTL "${MUI_HEADERIMAGE_BITMAP_RTL}" + !ifdef MUI_HEADERIMAGE_BITMAP_RTL_NOSTRETCH + !insertmacro MUI_SET MUI_HEADERIMAGE_UNBITMAP_RTL_NOSTRETCH "" + !endif + !endif + !endif + + !endif + + ;Default texts + !insertmacro MUI_DEFAULT MUI_ABORTWARNING_TEXT "$(MUI_TEXT_ABORTWARNING)" + !insertmacro MUI_DEFAULT MUI_UNABORTWARNING_TEXT "$(MUI_UNTEXT_ABORTWARNING)" + + ;Apply settings + + XPStyle On ;XP style setting in manifest resource + + ;Dialog resources + ChangeUI all "${MUI_UI}" + !ifdef MUI_HEADERIMAGE + !ifndef MUI_HEADERIMAGE_RIGHT + ChangeUI IDD_INST "${MUI_UI_HEADERIMAGE}" + !else + ChangeUI IDD_INST "${MUI_UI_HEADERIMAGE_RIGHT}" + !endif + !endif + + ;Icons + Icon "${MUI_ICON}" + UninstallIcon "${MUI_UNICON}" + + !endif + +!macroend + + +;-------------------------------- +;Abort warning message box + +!macro MUI_ABORTWARNING + + !ifdef MUI_ABORTWARNING_CANCEL_DEFAULT + MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 "${MUI_ABORTWARNING_TEXT}" IDYES mui.Quit + !else + MessageBox MB_YESNO|MB_ICONEXCLAMATION "${MUI_ABORTWARNING_TEXT}" IDYES mui.Quit + !endif + + Abort + mui.Quit: + +!macroend + +!macro MUI_UNABORTWARNING + + !ifdef MUI_UNABORTWARNING_CANCEL_DEFAULT + MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 "${MUI_UNABORTWARNING_TEXT}" IDYES mui.Quit + !else + MessageBox MB_YESNO|MB_ICONEXCLAMATION "${MUI_UNABORTWARNING_TEXT}" IDYES mui.Quit + !endif + + Abort + mui.Quit: + +!macroend + + +;-------------------------------- +;Initialization of GUI + +!macro MUI_HEADERIMAGE_INIT UNINSTALLER + + ;Load and display header image + + !ifdef MUI_HEADERIMAGE + + InitPluginsDir + + !ifdef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL + + ${if} $(^RTL) == 1 + + File "/oname=$PLUGINSDIR\modern-header.bmp" "${MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL}" + + !ifndef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL_NOSTRETCH + SetBrandingImage /IMGID=1046 /RESIZETOFIT "$PLUGINSDIR\modern-header.bmp" + !else + SetBrandingImage /IMGID=1046 "$PLUGINSDIR\modern-header.bmp" + !endif + + ${else} + + !endif + + File "/oname=$PLUGINSDIR\modern-header.bmp" "${MUI_HEADERIMAGE_${UNINSTALLER}BITMAP}" + + !ifndef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_NOSTRETCH + SetBrandingImage /IMGID=1046 /RESIZETOFIT "$PLUGINSDIR\modern-header.bmp" + !else + SetBrandingImage /IMGID=1046 "$PLUGINSDIR\modern-header.bmp" + !endif + + !ifdef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL + + ${endif} + + !endif + + !endif + +!macroend + +!macro MUI_GUIINIT_OUTERDIALOG UNINSTALLER + + ;Initialize outer dialog (fonts & colors) + + ;Header + GetDlgItem $mui.Header.Text $HWNDPARENT 1037 + CreateFont $mui.Header.Text.Font "$(^Font)" "$(^FontSize)" "700" + SendMessage $mui.Header.Text ${WM_SETFONT} $mui.Header.Text.Font 0 + + GetDlgItem $mui.Header.SubText $HWNDPARENT 1038 + + !ifndef MUI_HEADER_TRANSPARENT_TEXT + SetCtlColors $mui.Header.Text "" "${MUI_BGCOLOR}" + SetCtlColors $mui.Header.SubText "" "${MUI_BGCOLOR}" + !else + SetCtlColors $mui.Header.Text "" "transparent" + SetCtlColors $mui.Header.SubText "" "transparent" + !endif + + ;Header image + !insertmacro MUI_HEADERIMAGE_INIT "${UNINSTALLER}" + + ;Header background + GetDlgItem $mui.Header.Background $HWNDPARENT 1034 + SetCtlColors $mui.Header.Background "" "${MUI_BGCOLOR}" + + ;Header image background + GetDlgItem $mui.Header.Image $HWNDPARENT 1039 + SetCtlColors $mui.Header.Image "" "${MUI_BGCOLOR}" + + ;Branding text + GetDlgItem $mui.Branding.Background $HWNDPARENT 1028 + SetCtlColors $mui.Branding.Background /BRANDING + GetDlgItem $mui.Branding.Text $HWNDPARENT 1256 + SetCtlColors $mui.Branding.Text /BRANDING + SendMessage $mui.Branding.Text ${WM_SETTEXT} 0 "STR:$(^Branding) " + + ;Lines + GetDlgItem $mui.Line.Standard $HWNDPARENT 1035 + GetDlgItem $mui.Line.FullWindow $HWNDPARENT 1045 + + ;Buttons + GetDlgItem $mui.Button.Next $HWNDPARENT 1 + GetDlgItem $mui.Button.Cancel $HWNDPARENT 2 + GetDlgItem $mui.Button.Back $HWNDPARENT 3 + +!macroend + + +;-------------------------------- +;Interface functions + +!macro MUI_FUNCTION_GUIINIT + + Function .onGUIInit + + !insertmacro MUI_GUIINIT_OUTERDIALOG "" + + !ifdef MUI_PAGE_FUNCTION_GUIINIT + Call "${MUI_PAGE_FUNCTION_GUIINIT}" + !endif + + !ifdef MUI_CUSTOMFUNCTION_GUIINIT + Call "${MUI_CUSTOMFUNCTION_GUIINIT}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_UNFUNCTION_GUIINIT + + Function un.onGUIInit + + !insertmacro MUI_GUIINIT_OUTERDIALOG UN + + !ifdef MUI_UNPAGE_FUNCTION_GUIINIT + Call "${MUI_UNPAGE_FUNCTION_GUIINIT}" + !endif + + !ifdef MUI_CUSTOMFUNCTION_UNGUIINIT + Call "${MUI_CUSTOMFUNCTION_UNGUIINIT}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_ABORTWARNING + + Function .onUserAbort + + !ifdef MUI_PAGE_FUNCTION_ABORTWARNING + Call ${MUI_PAGE_FUNCTION_ABORTWARNING} + !endif + + !ifdef MUI_ABORTWARNING + !insertmacro MUI_ABORTWARNING + !endif + + !ifdef MUI_CUSTOMFUNCTION_ABORT + Call "${MUI_CUSTOMFUNCTION_ABORT}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_UNABORTWARNING + + Function un.onUserAbort + + !ifdef MUI_UNPAGE_FUNCTION_ABORTWARNING + Call ${MUI_UNPAGE_FUNCTION_ABORTWARNING} + !endif + + !ifdef MUI_UNABORTWARNING + !insertmacro MUI_UNABORTWARNING + !endif + + !ifdef MUI_CUSTOMFUNCTION_UNABORT + Call "${MUI_CUSTOMFUNCTION_UNABORT}" + !endif + + FunctionEnd + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Localization.nsh b/installer/NSIS/Contrib/Modern UI 2/Localization.nsh new file mode 100644 index 0000000..e40ff54 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Localization.nsh @@ -0,0 +1,192 @@ +/* + +NSIS Modern User Interface +Localization + +*/ + +;-------------------------------- +;Variables + +!macro MUI_LANGDLL_VARIABLES + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + !ifndef MUI_LANGDLL_REGISTRY_VARAIBLES + !define MUI_LANGDLL_REGISTRY_VARAIBLES + + ;/GLOBAL because the macros are included in a function + Var /GLOBAL mui.LangDLL.RegistryLanguage + + !endif + !endif + +!macroend + + +;-------------------------------- +;Include langauge files + +!macro MUI_LANGUAGE LANGUAGE + + ;Include a language + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_INSERT + + LoadLanguageFile "${NSISDIR}\Contrib\Language files\${LANGUAGE}.nlf" + + ;Include language file + !insertmacro LANGFILE_INCLUDE_WITHDEFAULT "${NSISDIR}\Contrib\Language files\${LANGUAGE}.nsh" "${NSISDIR}\Contrib\Language files\English.nsh" + + ;Add language to list of languages for selection dialog + !ifndef MUI_LANGDLL_LANGUAGES + !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' " + !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' " + !else + !ifdef MUI_LANGDLL_LANGUAGES_TEMP + !undef MUI_LANGDLL_LANGUAGES_TEMP + !endif + !define MUI_LANGDLL_LANGUAGES_TEMP "${MUI_LANGDLL_LANGUAGES}" + !undef MUI_LANGDLL_LANGUAGES + + !ifdef MUI_LANGDLL_LANGUAGES_CP_TEMP + !undef MUI_LANGDLL_LANGUAGES_CP_TEMP + !endif + !define MUI_LANGDLL_LANGUAGES_CP_TEMP "${MUI_LANGDLL_LANGUAGES_CP}" + !undef MUI_LANGDLL_LANGUAGES_CP + + !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' ${MUI_LANGDLL_LANGUAGES_TEMP}" + !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' ${MUI_LANGDLL_LANGUAGES_CP_TEMP}" + !endif + + !verbose pop + +!macroend + + +;-------------------------------- +;Language selection + +!macro MUI_LANGDLL_DISPLAY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_LANGDLL_VARIABLES + + !insertmacro MUI_DEFAULT MUI_LANGDLL_WINDOWTITLE "Installer Language" + !insertmacro MUI_DEFAULT MUI_LANGDLL_INFO "Please select a language." + + !ifdef MUI_LANGDLL_REGISTRY_VARAIBLES + + ReadRegStr $mui.LangDLL.RegistryLanguage "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" + + ${if} $mui.LangDLL.RegistryLanguage != "" + ;Set default langauge to registry language + StrCpy $LANGUAGE $mui.LangDLL.RegistryLanguage + ${endif} + + !endif + + !ifdef NSIS_CONFIG_SILENT_SUPPORT + ${unless} ${Silent} + !endif + + !ifndef MUI_LANGDLL_ALWAYSSHOW + !ifdef MUI_LANGDLL_REGISTRY_VARAIBLES + ${if} $mui.LangDLL.RegistryLanguage == "" + !endif + !endif + + ;Show langauge selection dialog + !ifdef MUI_LANGDLL_ALLLANGUAGES + LangDLL::LangDialog "${MUI_LANGDLL_WINDOWTITLE}" "${MUI_LANGDLL_INFO}" A ${MUI_LANGDLL_LANGUAGES} "" + !else + LangDLL::LangDialog "${MUI_LANGDLL_WINDOWTITLE}" "${MUI_LANGDLL_INFO}" AC ${MUI_LANGDLL_LANGUAGES_CP} "" + !endif + + Pop $LANGUAGE + ${if} $LANGUAGE == "cancel" + Abort + ${endif} + + !ifndef MUI_LANGDLL_ALWAYSSHOW + !ifdef MUI_LANGDLL_REGISTRY_VARAIBLES + ${endif} + !endif + !endif + + + !ifdef NSIS_CONFIG_SILENT_SUPPORT + ${endif} + !endif + + !verbose pop + +!macroend + +!macro MUI_LANGDLL_SAVELANGUAGE + + ;Save language in registry + + !ifndef MUI_PAGE_UNINSTALLER + + IfAbort mui.langdllsavelanguage_abort + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + WriteRegStr "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" $LANGUAGE + !endif + + mui.langdllsavelanguage_abort: + + !endif + +!macroend + +!macro MUI_UNGETLANGUAGE + + ;Get language from registry in uninstaller + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_LANGDLL_VARIABLES + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + ReadRegStr $mui.LangDLL.RegistryLanguage "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" + + ${if} $mui.LangDLL.RegistryLanguage = "" + + !endif + + !insertmacro MUI_LANGDLL_DISPLAY + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + ${else} + StrCpy $LANGUAGE $mui.LangDLL.RegistryLanguage + ${endif} + + !endif + + !verbose pop + +!macroend + + +;-------------------------------- +;Rerserve LangDLL file + +!macro MUI_RESERVEFILE_LANGDLL + + !verbose push + !verbose ${MUI_VERBOSE} + + ReserveFile "${NSISDIR}\Plugins\LangDLL.dll" + + !verbose pop + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/MUI2.nsh b/installer/NSIS/Contrib/Modern UI 2/MUI2.nsh new file mode 100644 index 0000000..3e95815 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/MUI2.nsh @@ -0,0 +1,122 @@ +/* + +NSIS Modern User Interface - Version 2.0 +Copyright 2002-2009 Joost Verburg + +*/ + +!echo "NSIS Modern User Interface version 2.0 - Copyright 2002-2009 Joost Verburg" + +;-------------------------------- + +!ifndef MUI_INCLUDED +!define MUI_INCLUDED + +!define MUI_SYSVERSION "2.0" + +!verbose push + +!ifndef MUI_VERBOSE + !define MUI_VERBOSE 3 +!endif + +!verbose ${MUI_VERBOSE} + +!addincludedir "${NSISDIR}\Contrib\Modern UI 2" + +;-------------------------------- +;Header files required by MUI + +!include WinMessages.nsh +!include LogicLib.nsh +!include nsDialogs.nsh +!include LangFile.nsh + + +;-------------------------------- +;Macros for compile-time defines + +!macro MUI_DEFAULT SYMBOL CONTENT + + ;Define symbol if not yet defined + ;For setting default values + + !ifndef "${SYMBOL}" + !define "${SYMBOL}" "${CONTENT}" + !endif + +!macroend + +!macro MUI_SET SYMBOL CONTENT + + ;Define symbol and undefine if neccesary + + !insertmacro MUI_UNSET "${SYMBOL}" + !define "${SYMBOL}" "${CONTENT}" + +!macroend + +!macro MUI_UNSET SYMBOL + + ;Undefine symbol if defined + + !ifdef "${SYMBOL}" + !undef "${SYMBOL}" + !endif + +!macroend + + +;-------------------------------- +;MUI interface + +!include "Deprecated.nsh" +!include "Interface.nsh" +!include "Localization.nsh" +!include "Pages.nsh" + + +;-------------------------------- +;Pages + +!include "Pages\Components.nsh" +!include "Pages\Directory.nsh" +!include "Pages\Finish.nsh" +!include "Pages\InstallFiles.nsh" +!include "Pages\License.nsh" +!include "Pages\StartMenu.nsh" +!include "Pages\UninstallConfirm.nsh" +!include "Pages\Welcome.nsh" + + +;-------------------------------- +;Insert MUI code in script + +!macro MUI_INSERT + + !ifndef MUI_INSERT + !define MUI_INSERT + + ;This macro is included when the first language file is included, + ;after the pages. + + ;Interface settings + !insertmacro MUI_INTERFACE + + ;Interface functions - Installer + !insertmacro MUI_FUNCTION_GUIINIT + !insertmacro MUI_FUNCTION_ABORTWARNING + + ;Interface functions - Uninstaller + !ifdef MUI_UNINSTALLER + !insertmacro MUI_UNFUNCTION_GUIINIT + !insertmacro MUI_FUNCTION_UNABORTWARNING + !endif + + !endif + +!macroend + +!endif + +!verbose pop diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages.nsh new file mode 100644 index 0000000..cf0cb51 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages.nsh @@ -0,0 +1,160 @@ +/* + +NSIS Modern User Interface +Support code for all pages + +*/ + +;-------------------------------- +;Page initialization + +!macro MUI_PAGE_INIT + + ;Include interface settings in neccesary + !insertmacro MUI_INTERFACE + + ;Define settings for installer page + !insertmacro MUI_UNSET MUI_PAGE_UNINSTALLER + !insertmacro MUI_UNSET MUI_PAGE_UNINSTALLER_PREFIX + !insertmacro MUI_UNSET MUI_PAGE_UNINSTALLER_FUNCPREFIX + + !insertmacro MUI_SET MUI_PAGE_UNINSTALLER_PREFIX "" + !insertmacro MUI_SET MUI_PAGE_UNINSTALLER_FUNCPREFIX "" + + ;Generate unique ID + !insertmacro MUI_UNSET MUI_UNIQUEID + !define MUI_UNIQUEID ${__LINE__} + +!macroend + +!macro MUI_UNPAGE_INIT + + ;Include interface settings + !insertmacro MUI_INTERFACE + + ;Define prefixes for uninstaller page + !insertmacro MUI_SET MUI_UNINSTALLER "" + + !insertmacro MUI_SET MUI_PAGE_UNINSTALLER "" + !insertmacro MUI_SET MUI_PAGE_UNINSTALLER_PREFIX "UN" + !insertmacro MUI_SET MUI_PAGE_UNINSTALLER_FUNCPREFIX "un." + + ;Generate unique ID + !insertmacro MUI_UNSET MUI_UNIQUEID + !define MUI_UNIQUEID ${__LINE__} + +!macroend + + +;-------------------------------- +;Header text for standard MUI page + +!macro MUI_HEADER_TEXT_PAGE TEXT SUBTEXT + + !ifdef MUI_PAGE_HEADER_TEXT & MUI_PAGE_HEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_PAGE_HEADER_TEXT}" "${MUI_PAGE_HEADER_SUBTEXT}" + !else ifdef MUI_PAGE_HEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_PAGE_HEADER_TEXT}" "${SUBTEXT}" + !else ifdef MUI_PAGE_HEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${TEXT}" "${MUI_PAGE_HEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "${TEXT}" "${SUBTEXT}" + !endif + + !insertmacro MUI_UNSET MUI_PAGE_HEADER_TEXT + !insertmacro MUI_UNSET MUI_PAGE_HEADER_SUBTEXT + +!macroend + + +;-------------------------------- +;Header text for custom page + +!macro MUI_HEADER_TEXT TEXT SUBTEXT ;Called from script + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + LockWindow on + !endif + + SendMessage $mui.Header.Text ${WM_SETTEXT} 0 "STR:${TEXT}" + SendMessage $mui.Header.SubText ${WM_SETTEXT} 0 "STR:${SUBTEXT}" + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + LockWindow off + !endif + + !verbose pop + +!macroend + + +;-------------------------------- +;Custom page functions + +!macro MUI_PAGE_FUNCTION_CUSTOM TYPE + + !ifdef MUI_PAGE_CUSTOMFUNCTION_${TYPE} + Call "${MUI_PAGE_CUSTOMFUNCTION_${TYPE}}" + !undef MUI_PAGE_CUSTOMFUNCTION_${TYPE} + !endif + +!macroend + + +;-------------------------------- +;Support for full window pages (like welcome/finish page) + +!macro MUI_PAGE_FUNCTION_FULLWINDOW + + !ifndef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_FULLWINDOW + !define MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_FULLWINDOW + + Function ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}muiPageLoadFullWindow + + LockWindow on + + ;The branding text needs to be hidden because the full windows page + ;overlaps with it. + ShowWindow $mui.Branding.Background ${SW_HIDE} + ShowWindow $mui.Branding.Text ${SW_HIDE} + + ;The texts need to be hidden because otherwise they may show through + ;the page above when the Alt key is pressed. + ShowWindow $mui.Header.Text ${SW_HIDE} + ShowWindow $mui.Header.SubText ${SW_HIDE} + ShowWindow $mui.Header.Image ${SW_HIDE} + + ;Show line below full width of page + ShowWindow $mui.Line.Standard ${SW_HIDE} + ShowWindow $mui.Line.FullWindow ${SW_NORMAL} + + LockWindow off + + FunctionEnd + + Function ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}muiPageUnloadFullWindow + + ;Set everything back to normal again + + LockWindow on + + ShowWindow $mui.Branding.Background ${SW_NORMAL} + ShowWindow $mui.Branding.Text ${SW_NORMAL} + + ShowWindow $mui.Header.Text ${SW_NORMAL} + ShowWindow $mui.Header.SubText ${SW_NORMAL} + ShowWindow $mui.Header.Image ${SW_NORMAL} + + ShowWindow $mui.Line.Standard ${SW_NORMAL} + ShowWindow $mui.Line.FullWindow ${SW_HIDE} + + LockWindow off + + FunctionEnd + + !endif + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/Components.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/Components.nsh new file mode 100644 index 0000000..71387cc --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/Components.nsh @@ -0,0 +1,242 @@ +/* + +NSIS Modern User Interface +Components page + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_COMPONENTSPAGE_INTERFACE + + !ifndef MUI_COMPONENTSPAGE_INTERFACE + !define MUI_COMPONENTSPAGE_INTERFACE + Var mui.ComponentsPage + + Var mui.ComponentsPage.Text + Var mui.ComponentsPage.InstTypesText + Var mui.ComponentsPage.ComponentsText + + Var mui.ComponentsPage.InstTypes + Var mui.ComponentsPage.Components + + Var mui.ComponentsPage.DescriptionTitle + Var mui.ComponentsPage.DescriptionText.Info + Var mui.ComponentsPage.DescriptionText + + Var mui.ComponentsPage.SpaceRequired + + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_CHECKBITMAP "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" + + !insertmacro MUI_DEFAULT MUI_UI_COMPONENTSPAGE_SMALLDESC "${NSISDIR}\Contrib\UIs\modern_smalldesc.exe" + !insertmacro MUI_DEFAULT MUI_UI_COMPONENTSPAGE_NODESC "${NSISDIR}\Contrib\UIs\modern_nodesc.exe" + + ;Apply settings + + !ifdef MUI_COMPONENTSPAGE_SMALLDESC + ChangeUI IDD_SELCOM "${MUI_UI_COMPONENTSPAGE_SMALLDESC}" + !else ifdef MUI_COMPONENTSPAGE_NODESC + ChangeUI IDD_SELCOM "${MUI_UI_COMPONENTSPAGE_NODESC}" + !endif + + CheckBitmap "${MUI_COMPONENTSPAGE_CHECKBITMAP}" + + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_COMPONENTS + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}COMPONENTSPAGE "" + !insertmacro MUI_COMPONENTSPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_COMPLIST "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_INSTTYPE "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE "$(MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE)" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO "$(MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO)" + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}components + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsLeave_${MUI_UNIQUEID} + + Caption " " + + ComponentText "${MUI_COMPONENTSPAGE_TEXT_TOP}" "${MUI_COMPONENTSPAGE_TEXT_INSTTYPE}" "${MUI_COMPONENTSPAGE_TEXT_COMPLIST}" + + PageExEnd + + !insertmacro MUI_FUNCTION_COMPONENTSPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsLeave_${MUI_UNIQUEID} + + !undef MUI_COMPONENTSPAGE_TEXT_TOP + !undef MUI_COMPONENTSPAGE_TEXT_COMPLIST + !undef MUI_COMPONENTSPAGE_TEXT_INSTTYPE + !insertmacro MUI_UNSET MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE + !insertmacro MUI_UNSET MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO + +!macroend + +!macro MUI_PAGE_COMPONENTS + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_PAGEDECLARATION_COMPONENTS + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_COMPONENTS + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + !insertmacro MUI_PAGEDECLARATION_COMPONENTS + + !verbose pop + +!macroend + + +;-------------------------------- +;Page functions + +!macro MUI_FUNCTION_COMPONENTSPAGE PRE SHOW LEAVE + + Function "${PRE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_COMPONENTS_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_COMPONENTS_SUBTITLE) + FunctionEnd + + Function "${SHOW}" + + ;Get control hanldes + FindWindow $mui.ComponentsPage "#32770" "" $HWNDPARENT + GetDlgItem $mui.ComponentsPage.Text $mui.ComponentsPage 1006 + GetDlgItem $mui.ComponentsPage.InstTypesText $mui.ComponentsPage 1021 + GetDlgItem $mui.ComponentsPage.ComponentsText $mui.ComponentsPage 1022 + GetDlgItem $mui.ComponentsPage.InstTypes $mui.ComponentsPage 1017 + GetDlgItem $mui.ComponentsPage.Components $mui.ComponentsPage 1032 + GetDlgItem $mui.ComponentsPage.DescriptionTitle $mui.ComponentsPage 1042 + GetDlgItem $mui.ComponentsPage.DescriptionText $mui.ComponentsPage 1043 + GetDlgItem $mui.ComponentsPage.SpaceRequired $mui.ComponentsPage 1023 + + ;Default text in description textbox + SendMessage $mui.ComponentsPage.DescriptionTitle ${WM_SETTEXT} 0 "STR:${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE}" + EnableWindow $mui.ComponentsPage.DescriptionText 0 + SendMessage $mui.ComponentsPage.DescriptionText ${WM_SETTEXT} 0 "STR:${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO}" + + StrCpy $mui.ComponentsPage.DescriptionText.Info "${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO}" ;Text for current components page + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + + +;-------------------------------- +;Script functions for components descriptions + +!macro MUI_DESCRIPTION_BEGIN + + ${if} $0 == -1 + ;No mouse hover over component in list + SendMessage $mui.ComponentsPage.DescriptionText ${WM_SETTEXT} 0 "STR:" + EnableWindow $mui.ComponentsPage.DescriptionText 0 + SendMessage $mui.ComponentsPage.DescriptionText ${WM_SETTEXT} 0 "STR:$mui.ComponentsPage.DescriptionText.Info" + +!macroend + +!macro MUI_DESCRIPTION_TEXT VAR TEXT + + !verbose push + !verbose ${MUI_VERBOSE} + + ${elseif} $0 == ${VAR} + SendMessage $mui.ComponentsPage.DescriptionText ${WM_SETTEXT} 0 "STR:" + EnableWindow $mui.ComponentsPage.DescriptionText 1 + SendMessage $mui.ComponentsPage.DescriptionText ${WM_SETTEXT} 0 "STR:${TEXT}" + + !verbose pop + +!macroend + +!macro MUI_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + ${endif} + + !verbose pop + +!macroend + +!macro MUI_FUNCTION_DESCRIPTION_BEGIN + + !verbose push + !verbose ${MUI_VERBOSE} + + Function .onMouseOverSection + !insertmacro MUI_DESCRIPTION_BEGIN + + !verbose pop + +!macroend + +!macro MUI_FUNCTION_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DESCRIPTION_END + !ifdef MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION + Call "${MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION}" + !endif + FunctionEnd + + !verbose pop + +!macroend + +!macro MUI_UNFUNCTION_DESCRIPTION_BEGIN + + !verbose push + !verbose ${MUI_VERBOSE} + + Function un.onMouseOverSection + !insertmacro MUI_DESCRIPTION_BEGIN + + !verbose pop + +!macroend + +!macro MUI_UNFUNCTION_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DESCRIPTION_END + !ifdef MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION + Call "${MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION}" + !endif + FunctionEnd + + !verbose pop + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/Directory.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/Directory.nsh new file mode 100644 index 0000000..2e05653 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/Directory.nsh @@ -0,0 +1,125 @@ +/* + +NSIS Modern User Interface +Directory page + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_DIRECTORYPAGE_INTERFACE + + !ifndef MUI_DIRECTORYPAGE_INTERFACE + !define MUI_DIRECTORYPAGE_INTERFACE + Var mui.DirectoryPage + + Var mui.DirectoryPage.Text + + Var mui.DirectoryPage.DirectoryBox + Var mui.DirectoryPage.Directory + Var mui.DirectoryPage.BrowseButton + + Var mui.DirectoryPage.SpaceRequired + Var mui.DirectoryPage.SpaceAvailable + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_DIRECTORY + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}DIRECTORYPAGE "" + !insertmacro MUI_DIRECTORYPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MUI_DIRECTORYPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_DIRECTORYPAGE_TEXT_DESTINATION "" + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}directory + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryLeave_${MUI_UNIQUEID} + + Caption " " + + DirText "${MUI_DIRECTORYPAGE_TEXT_TOP}" "${MUI_DIRECTORYPAGE_TEXT_DESTINATION}" + + !ifdef MUI_DIRECTORYPAGE_VARIABLE + DirVar "${MUI_DIRECTORYPAGE_VARIABLE}" + !endif + + !ifdef MUI_DIRECTORYPAGE_VERIFYONLEAVE + DirVerify leave + !endif + + PageExEnd + + !insertmacro MUI_FUNCTION_DIRECTORYPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryLeave_${MUI_UNIQUEID} + + !undef MUI_DIRECTORYPAGE_TEXT_TOP + !undef MUI_DIRECTORYPAGE_TEXT_DESTINATION + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_VARIABLE + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_VERIFYONLEAVE + +!macroend + +!macro MUI_PAGE_DIRECTORY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_PAGEDECLARATION_DIRECTORY + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_DIRECTORY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + !insertmacro MUI_PAGEDECLARATION_DIRECTORY + + !verbose pop + +!macroend + + +;-------------------------------- +;Page functions + +!macro MUI_FUNCTION_DIRECTORYPAGE PRE SHOW LEAVE + + Function "${PRE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_SUBTITLE) + FunctionEnd + + Function "${SHOW}" + + ;Get control handles + FindWindow $mui.DirectoryPage "#32770" "" $HWNDPARENT + GetDlgItem $mui.DirectoryPage.Text $mui.DirectoryPage 1006 + GetDlgItem $mui.DirectoryPage.DirectoryBox $mui.DirectoryPage 1020 + GetDlgItem $mui.DirectoryPage.Directory $mui.DirectoryPage 1019 + GetDlgItem $mui.DirectoryPage.BrowseButton $mui.DirectoryPage 1001 + GetDlgItem $mui.DirectoryPage.SpaceRequired $mui.DirectoryPage 1023 + GetDlgItem $mui.DirectoryPage.SpaceAvailable $mui.DirectoryPage 1024 + + !ifdef MUI_DIRECTORYPAGE_BGCOLOR + SetCtlColors $mui.DirectoryPage.Directory "" "${MUI_DIRECTORYPAGE_BGCOLOR}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + FunctionEnd + + Function "${LEAVE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + FunctionEnd + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/Finish.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/Finish.nsh new file mode 100644 index 0000000..355d9ad --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/Finish.nsh @@ -0,0 +1,491 @@ + /* + +NSIS Modern User Interface +Finish page (implemented using nsDialogs) + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_FINISHPAGE_INTERFACE + + !ifndef MUI_FINISHPAGE_INTERFACE + !define MUI_FINISHPAGE_INTERFACE + Var mui.FinishPage + + Var mui.FinishPage.Image + Var mui.FinishPage.Image.Bitmap + + Var mui.FinishPage.Title + Var mui.FinishPage.Title.Font + + Var mui.FinishPage.Text + !endif + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + !ifndef MUI_FINISHPAGE_RETURNVALUE_VARIABLES + !define MUI_FINISHPAGE_RETURNVALUE_VARIABLES + Var mui.FinishPage.ReturnValue + !endif + !else ifdef MUI_FINISHPAGE_RUN | MUI_FINISHPAGE_SHOWREADME + !ifndef MUI_FINISHPAGE_RETURNVALUE_VARIABLES + !define MUI_FINISHPAGE_RETURNVALUE_VARIABLES + Var mui.FinishPage.ReturnValue + !endif + !endif + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + !ifndef MUI_FINISHPAGE_CANCEL_ENABLED_VARIABLES + !define MUI_FINISHPAGE_CANCEL_ENABLED_VARIABLES + Var mui.FinishPage.DisableAbortWarning + !endif + !endif + + !ifdef MUI_FINISHPAGE_RUN + !ifndef MUI_FINISHPAGE_RUN_VARIABLES + !define MUI_FINISHPAGE_RUN_VARIABLES + Var mui.FinishPage.Run + !endif + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + !ifndef MUI_FINISHPAGE_SHOREADME_VARAIBLES + !define MUI_FINISHPAGE_SHOREADME_VARAIBLES + Var mui.FinishPage.ShowReadme + !endif + !endif + + !ifdef MUI_FINISHPAGE_LINK + !ifndef MUI_FINISHPAGE_LINK_VARIABLES + !define MUI_FINISHPAGE_LINK_VARIABLES + Var mui.FinishPage.Link + !endif + !endif + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + !ifndef MUI_FINISHPAGE_REBOOT_VARIABLES + !define MUI_FINISHPAGE_REBOOT_VARIABLES + Var mui.FinishPage.RebootNow + Var mui.FinishPage.RebootLater + !endif + !endif + + !insertmacro MUI_DEFAULT MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" + +!macroend + + +;-------------------------------- +;Interface initialization + +!macro MUI_FINISHPAGE_GUIINIT + + !ifndef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_GUINIT + !define MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_GUINIT + + Function ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.GUIInit + + InitPluginsDir + File "/oname=$PLUGINSDIR\modern-wizard.bmp" "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_BITMAP}" + + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_GUIINIT + Call "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_GUIINIT}" + !endif + + !ifndef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}FINISHPAGE_NOAUTOCLOSE + SetAutoClose true + !endif + + FunctionEnd + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_GUIINIT ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.GUIInit + + !endif + +!macroend + + +;-------------------------------- +;Abort warning + +!macro MUI_FINISHPAGE_ABORTWARNING + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + + !ifndef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}FINISHPAGE_ABORTWARNING + !define MUI_${MUI_PAGE_UNINSTALLER_PREFIX}FINISHPAGE_ABORTWARNING + + Function ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.AbortWarning + + ${if} $mui.FinishPage.DisableAbortWarning == "1" + Quit + ${endif} + + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_ABORTWARNING + Call ${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_ABORTWARNING} + !endif + + FunctionEnd + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_ABORTWARNING ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.AbortWarning + + !endif + + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_FINISH + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}FINISHPAGE "" + !insertmacro MUI_FINISHPAGE_INTERFACE + + !insertmacro MUI_FINISHPAGE_GUIINIT + !insertmacro MUI_FINISHPAGE_ABORTWARNING + + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_TITLE "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_TITLE)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_TEXT)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_BUTTON "$(MUI_BUTTONTEXT_FINISH)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_TEXT_REBOOT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_REBOOT)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_TEXT_REBOOTNOW "$(MUI_TEXT_FINISH_REBOOTNOW)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_TEXT_REBOOTLATER "$(MUI_TEXT_FINISH_REBOOTLATER)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_RUN_TEXT "$(MUI_TEXT_FINISH_RUN)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_SHOWREADME_TEXT "$(MUI_TEXT_FINISH_SHOWREADME)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_LINK_COLOR "000080" + + !insertmacro MUI_PAGE_FUNCTION_FULLWINDOW + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.Pre_${MUI_UNIQUEID} \ + ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.Leave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_FINISHPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.Pre_${MUI_UNIQUEID} \ + ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.Leave_${MUI_UNIQUEID} \ + ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPage.Link_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_FINISHPAGE_TITLE + !insertmacro MUI_UNSET MUI_FINISHPAGE_TITLE_3LINES + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_LARGE + !insertmacro MUI_UNSET MUI_FINISHPAGE_BUTTON + !insertmacro MUI_UNSET MUI_FINISHPAGE_CANCEL_ENABLED + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOTNOW + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOTLATER + !insertmacro MUI_UNSET MUI_FINISHPAGE_REBOOTLATER_DEFAULT + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_PARAMETERS + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK_LOCATION + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK_COLOR + !insertmacro MUI_UNSET MUI_FINISHPAGE_NOREBOOTSUPPORT + + !insertmacro MUI_UNSET MUI_FINISHPAGE_ABORTWARNINGCHECK + !insertmacro MUI_UNSET MUI_FINISHPAGE_CURFIELD_TOP + !insertmacro MUI_UNSET MUI_FINISHPAGE_CURFIELD_BOTTOM + +!macroend + +!macro MUI_PAGE_FINISH + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_PAGEDECLARATION_FINISH + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_FINISH + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + !insertmacro MUI_PAGEDECLARATION_FINISH + + !verbose pop + +!macroend + + +;-------------------------------- +;Page functions + +!macro MUI_FUNCTION_FINISHPAGE PRE LEAVE LINK + + !ifdef MUI_FINISHPAGE_LINK + + Function "${LINK}" + + ExecShell open "${MUI_FINISHPAGE_LINK_LOCATION}" + + FunctionEnd + + !endif + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + ;Set text on Next button + SendMessage $mui.Button.Next ${WM_SETTEXT} 0 "STR:${MUI_FINISHPAGE_BUTTON}" + + ;Enable cancel button if set in script + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + EnableWindow $mui.Button.Cancel 1 + !endif + + ;Create dialog + nsDialogs::Create 1044 + Pop $mui.FinishPage + nsDialogs::SetRTL $(^RTL) + SetCtlColors $mui.FinishPage "" "${MUI_BGCOLOR}" + + ;Image control + ${NSD_CreateBitmap} 0u 0u 109u 193u "" + Pop $mui.FinishPage.Image + !ifndef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_BITMAP_NOSTRETCH + ${NSD_SetStretchedImage} $mui.FinishPage.Image $PLUGINSDIR\modern-wizard.bmp $mui.FinishPage.Image.Bitmap + !else + ${NSD_SetImage} $mui.FinishPage.Image $PLUGINSDIR\modern-wizard.bmp $mui.FinishPage.Image.Bitmap + !endif + + ;Positiong of controls + + ;Title + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !define MUI_FINISHPAGE_TITLE_HEIGHT 28 + !else + !define MUI_FINISHPAGE_TITLE_HEIGHT 38 + !endif + + ;Text + ;17 = 10 (top margin) + 7 (distance between texts) + !define /math MUI_FINISHPAGE_TEXT_TOP 17 + ${MUI_FINISHPAGE_TITLE_HEIGHT} + + ;Height if space required for radio buttons or check boxes + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_TEXT_HEIGHT_BUTTONS 40 + !else + !define MUI_FINISHPAGE_TEXT_HEIGHT_BUTTONS 60 + !endif + + !define /math MUI_FINISHPAGE_TEXT_BOTTOM_BUTTONS ${MUI_FINISHPAGE_TEXT_TOP} + ${MUI_FINISHPAGE_TEXT_HEIGHT_BUTTONS} + + ;Positioning of radio buttons to ask for a reboot + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + !define /math MUI_FINISHPAGE_REBOOTNOW_TOP ${MUI_FINISHPAGE_TEXT_BOTTOM_BUTTONS} + 5 ;Distance between text and options + ;25 = 10 (height of first radio button) + 15 (distance between buttons) + !define /math MUI_FINISHPAGE_REBOOTLATER_TOP ${MUI_FINISHPAGE_REBOOTNOW_TOP} + 25 + !endif + + ;Positioning of checkboxes + !ifdef MUI_FINISHPAGE_RUN + !define /math MUI_FINISHPAGE_RUN_TOP ${MUI_FINISHPAGE_TEXT_BOTTOM_BUTTONS} + 5 ;Distance between text and options + !endif + !ifdef MUI_FINISHPAGE_SHOWREADME + !ifdef MUI_FINISHPAGE_RUN + ;25 = 10 (height of run checkbox) + 10 (distance between checkboxes) + !define /math MUI_FINISHPAGE_SHOWREADME_TOP ${MUI_FINISHPAGE_RUN_TOP} + 20 + !else + !define /math MUI_FINISHPAGE_SHOWREADME_TOP ${MUI_FINISHPAGE_TEXT_BOTTOM_BUTTONS} + 5 ;Distance between text and options + !endif + !endif + + !ifndef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME + ;Height if full space is available for text and link + !ifndef MUI_FINISHPAGE_LINK + !define MUI_FINISHPAGE_TEXT_HEIGHT 130 + !else + !define MUI_FINISHPAGE_TEXT_HEIGHT 120 + !endif + !endif + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + ${if} ${RebootFlag} + + ;Title text + ${NSD_CreateLabel} 120u 10u 195u ${MUI_FINISHPAGE_TITLE_HEIGHT}u "${MUI_FINISHPAGE_TITLE}" + Pop $mui.FinishPage.Title + SetCtlColors $mui.FinishPage.Title "" "${MUI_BGCOLOR}" + CreateFont $mui.FinishPage.Title.Font "$(^Font)" "12" "700" + SendMessage $mui.FinishPage.Title ${WM_SETFONT} $mui.FinishPage.Title.Font 0 + + ;Finish text + ${NSD_CreateLabel} 120u 45u 195u ${MUI_FINISHPAGE_TEXT_HEIGHT_BUTTONS}u "${MUI_FINISHPAGE_TEXT_REBOOT}" + Pop $mui.FinishPage.Text + SetCtlColors $mui.FinishPage.Text "" "${MUI_BGCOLOR}" + + ;Radio buttons for reboot page + ${NSD_CreateRadioButton} 120u ${MUI_FINISHPAGE_REBOOTNOW_TOP}u 195u 10u "${MUI_FINISHPAGE_TEXT_REBOOTNOW}" + Pop $mui.FinishPage.RebootNow + SetCtlColors $mui.FinishPage.RebootNow "" "${MUI_BGCOLOR}" + ${NSD_CreateRadioButton} 120u ${MUI_FINISHPAGE_REBOOTLATER_TOP}u 195u 10u "${MUI_FINISHPAGE_TEXT_REBOOTLATER}" + Pop $mui.FinishPage.RebootLater + SetCtlColors $mui.FinishPage.RebootLater "" "${MUI_BGCOLOR}" + !ifndef MUI_FINISHPAGE_REBOOTLATER_DEFAULT + SendMessage $mui.FinishPage.RebootNow ${BM_SETCHECK} ${BST_CHECKED} 0 + !else + SendMessage $mui.FinishPage.RebootLater ${BM_SETCHECK} ${BST_CHECKED} 0 + !endif + ${NSD_SetFocus} $mui.FinishPage.RebootNow + + ${else} + + !endif + + ;Title text + ${NSD_CreateLabel} 120u 10u 195u ${MUI_FINISHPAGE_TITLE_HEIGHT}u "${MUI_FINISHPAGE_TITLE}" + Pop $mui.FinishPage.Title + SetCtlColors $mui.FinishPage.Title "" "${MUI_BGCOLOR}" + CreateFont $mui.FinishPage.Title.Font "$(^Font)" "12" "700" + SendMessage $mui.FinishPage.Title ${WM_SETFONT} $mui.FinishPage.Title.Font 0 + + ;Finish text + !ifndef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME + ${NSD_CreateLabel} 120u ${MUI_FINISHPAGE_TEXT_TOP}u 195u ${MUI_FINISHPAGE_TEXT_HEIGHT}u "${MUI_FINISHPAGE_TEXT}" + !else + ${NSD_CreateLabel} 120u ${MUI_FINISHPAGE_TEXT_TOP}u 195u ${MUI_FINISHPAGE_TEXT_HEIGHT_BUTTONS}u "${MUI_FINISHPAGE_TEXT}" + !endif + Pop $mui.FinishPage.Text + SetCtlColors $mui.FinishPage.Text "" "${MUI_BGCOLOR}" + + ;Checkboxes + !ifdef MUI_FINISHPAGE_RUN + ${NSD_CreateCheckbox} 120u ${MUI_FINISHPAGE_RUN_TOP}u 195u 10u "${MUI_FINISHPAGE_RUN_TEXT}" + Pop $mui.FinishPage.Run + SetCtlColors $mui.FinishPage.Run "" "${MUI_BGCOLOR}" + !ifndef MUI_FINISHPAGE_RUN_NOTCHECKED + SendMessage $mui.FinishPage.Run ${BM_SETCHECK} ${BST_CHECKED} 0 + !endif + ${NSD_SetFocus} $mui.FinishPage.Run + !endif + !ifdef MUI_FINISHPAGE_SHOWREADME + ${NSD_CreateCheckbox} 120u ${MUI_FINISHPAGE_SHOWREADME_TOP}u 195u 10u "${MUI_FINISHPAGE_SHOWREADME_TEXT}" + Pop $mui.FinishPage.ShowReadme + SetCtlColors $mui.FinishPage.ShowReadme "" "${MUI_BGCOLOR}" + !ifndef MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + SendMessage $mui.FinishPage.ShowReadme ${BM_SETCHECK} ${BST_CHECKED} 0 + !endif + !ifndef MUI_FINISHPAGE_RUN + ${NSD_SetFocus} $mui.FinishPage.ShowReadme + !endif + !endif + + ;Link + !ifdef MUI_FINISHPAGE_LINK + ${NSD_CreateLink} 120u 175u 195u 10u "${MUI_FINISHPAGE_LINK}" + Pop $mui.FinishPage.Link + SetCtlColors $mui.FinishPage.Link "${MUI_FINISHPAGE_LINK_COLOR}" "${MUI_BGCOLOR}" + ${NSD_OnClick} $mui.FinishPage.Link "${LINK}" + !endif + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + ${endif} + !endif + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + StrCpy $mui.FinishPage.DisableAbortWarning "1" + !endif + + ;Show page + Call ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}muiPageLoadFullWindow + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + nsDialogs::Show + Call ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}muiPageUnloadFullWindow + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + StrCpy $mui.FinishPage.DisableAbortWarning "" + !endif + + ;Delete image from memory + ${NSD_FreeImage} $mui.FinishPage.Image.Bitmap + + !insertmacro MUI_UNSET MUI_FINISHPAGE_TITLE_HEIGHT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_TOP + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_HEIGHT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_HEIGHT_BUTTONS + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_BOTTOM_BUTTONS + !insertmacro MUI_UNSET MUI_FINISHPAGE_REBOOTNOW_TOP + !insertmacro MUI_UNSET MUI_FINISHPAGE_REBOOTLATER_TOP + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_TOP + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_TOP + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + ;Check whether the user has chosen to reboot the computer + ${if} ${RebootFlag} + SendMessage $mui.FinishPage.RebootNow ${BM_GETCHECK} 0 0 $mui.FinishPage.ReturnValue + ${if} $mui.FinishPage.ReturnValue = ${BST_CHECKED} + Reboot + ${else} + Return + ${endif} + ${endif} + + !endif + + ;Run functions depending on checkbox state + + !ifdef MUI_FINISHPAGE_RUN + + SendMessage $mui.FinishPage.Run ${BM_GETCHECK} 0 0 $mui.FinishPage.ReturnValue + + ${if} $mui.FinishPage.ReturnValue = ${BST_CHECKED} + !ifndef MUI_FINISHPAGE_RUN_FUNCTION + !ifndef MUI_FINISHPAGE_RUN_PARAMETERS + Exec "$\"${MUI_FINISHPAGE_RUN}$\"" + !else + Exec "$\"${MUI_FINISHPAGE_RUN}$\" ${MUI_FINISHPAGE_RUN_PARAMETERS}" + !endif + !else + Call "${MUI_FINISHPAGE_RUN_FUNCTION}" + !endif + ${endif} + + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + + SendMessage $mui.FinishPage.ShowReadme ${BM_GETCHECK} 0 0 $mui.FinishPage.ReturnValue + + ${if} $mui.FinishPage.ReturnValue = ${BST_CHECKED} + !ifndef MUI_FINISHPAGE_SHOWREADME_FUNCTION + ExecShell open "${MUI_FINISHPAGE_SHOWREADME}" + !else + Call "${MUI_FINISHPAGE_SHOWREADME_FUNCTION}" + !endif + ${endif} + + !endif + + FunctionEnd + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/InstallFiles.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/InstallFiles.nsh new file mode 100644 index 0000000..868ae17 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/InstallFiles.nsh @@ -0,0 +1,149 @@ +/* + +NSIS Modern User Interface +InstallFiles page + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_INSTFILESPAGE_INTERFACE + + !ifndef MUI_INSTFILESYPAGE_INTERFACE + !define MUI_INSTFILESYPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MUI_INSTFILESPAGE_COLORS "/windows" + !insertmacro MUI_DEFAULT MUI_INSTFILESPAGE_PROGRESSBAR "smooth" + + Var mui.InstFilesPage + + Var mui.InstFilesPage.Text + Var mui.InstFilesPage.ProgressBar + Var mui.InstFilesPage.ShowLogButton + Var mui.InstFilesPage.Log + + ;Apply settings + InstallColors ${MUI_INSTFILESPAGE_COLORS} + InstProgressFlags ${MUI_INSTFILESPAGE_PROGRESSBAR} + SubCaption 4 " " + UninstallSubCaption 2 " " + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_INSTFILES + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INSTFILESPAGE "" + !insertmacro MUI_INSTFILESPAGE_INTERFACE + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}instfiles + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_INSTFILESPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_FINISHHEADER_TEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_ABORTWARNING_TEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_ABORTWARNING_SUBTEXT + +!macroend + +!macro MUI_PAGE_INSTFILES + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_PAGEDECLARATION_INSTFILES + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_INSTFILES + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + !insertmacro MUI_PAGEDECLARATION_INSTFILES + + !verbose pop + +!macroend + + +;-------------------------------- +;Page functions + +!macro MUI_FUNCTION_INSTFILESPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLING_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLING_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + ;Get controls handles + FindWindow $mui.InstFilesPage "#32770" "" $HWNDPARENT + GetDlgItem $mui.InstFilesPage.Text $mui.InstFilesPage 1006 + GetDlgItem $mui.InstFilesPage.ProgressBar $mui.InstFilesPage 1004 + GetDlgItem $mui.InstFilesPage.ShowLogButton $mui.InstFilesPage 1027 + GetDlgItem $mui.InstFilesPage.Log $mui.InstFilesPage 1016 + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + ;Set text on completed page header + + IfAbort mui.endheader_abort + + !ifdef MUI_INSTFILESPAGE_FINISHHEADER_TEXT & MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_FINISHHEADER_TEXT}" "${MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT}" + !else ifdef MUI_INSTFILESPAGE_FINISHHEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_FINISHHEADER_TEXT}" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_SUBTITLE)" + !else ifdef MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_TITLE)" "${MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_TITLE)" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_SUBTITLE)" + !endif + + Goto mui.endheader_done + + mui.endheader_abort: + + !ifdef MUI_INSTFILESPAGE_ABORTHEADER_TEXT & MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_ABORTHEADER_TEXT}" "${MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT}" + !else ifdef MUI_INSTFILESPAGE_ABORTHEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_ABORTHEADER_TEXT}" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_SUBTITLE)" + !else ifdef MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_TITLE)" "${MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_TITLE)" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_SUBTITLE)" + !endif + + mui.endheader_done: + + !insertmacro MUI_LANGDLL_SAVELANGUAGE + + FunctionEnd + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/License.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/License.nsh new file mode 100644 index 0000000..2bcbee5 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/License.nsh @@ -0,0 +1,145 @@ +/* + +NSIS Modern User Interface +License page + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_LICENSEPAGE_INTERFACE + + !ifndef MUI_LICENSEPAGE_INTERFACE + !define MUI_LICENSEPAGE_INTERFACE + Var mui.LicensePage + + Var mui.Licensepage.TopText + Var mui.Licensepage.Text + Var mui.Licensepage.LicenseText + + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_BGCOLOR "/windows" + + ;Apply settings + LicenseBkColor "${MUI_LICENSEPAGE_BGCOLOR}" + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_LICENSE LICENSEDATA + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}LICENSEPAGE "" + !insertmacro MUI_LICENSEPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_TEXT_TOP "$(MUI_INNERTEXT_LICENSE_TOP)" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_BUTTON "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_CHECKBOX_TEXT "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE "" + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}license + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicensePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseLeave_${MUI_UNIQUEID} + + Caption " " + + LicenseData "${LICENSEDATA}" + + !ifndef MUI_LICENSEPAGE_TEXT_BOTTOM + !ifndef MUI_LICENSEPAGE_CHECKBOX & MUI_LICENSEPAGE_RADIOBUTTONS + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM)" "${MUI_LICENSEPAGE_BUTTON}" + !else ifdef MUI_LICENSEPAGE_CHECKBOX + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM_CHECKBOX)" "${MUI_LICENSEPAGE_BUTTON}" + !else + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS)" "${MUI_LICENSEPAGE_BUTTON}" + !endif + !else + LicenseText "${MUI_LICENSEPAGE_TEXT_BOTTOM}" "${MUI_LICENSEPAGE_BUTTON}" + !endif + + !ifdef MUI_LICENSEPAGE_CHECKBOX + LicenseForceSelection checkbox "${MUI_LICENSEPAGE_CHECKBOX_TEXT}" + !else ifdef MUI_LICENSEPAGE_RADIOBUTTONS + LicenseForceSelection radiobuttons "${MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT}" "${MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE}" + !endif + + PageExEnd + + !insertmacro MUI_FUNCTION_LICENSEPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicensePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_LICENSEPAGE_TEXT_TOP + !insertmacro MUI_UNSET MUI_LICENSEPAGE_TEXT_BOTTOM + !insertmacro MUI_UNSET MUI_LICENSEPAGE_BUTTON + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT + !insertmacro MUI_UNSET MUI_LICENSEPAGE_RADIOBUTTONS + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT_ACCEPT + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT_DECLINE + + !verbose pop + +!macroend + +!macro MUI_PAGE_LICENSE LICENSEDATA + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_PAGEDECLARATION_LICENSE "${LICENSEDATA}" + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_LICENSE LICENSEDATA + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + !insertmacro MUI_PAGEDECLARATION_LICENSE "${LICENSEDATA}" + + !verbose pop + +!macroend + + +;-------------------------------- +;Page functions + +!macro MUI_FUNCTION_LICENSEPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_LICENSE_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_LICENSE_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + ;Get conrol handles + FindWindow $mui.LicensePage "#32770" "" $HWNDPARENT + GetDlgItem $mui.LicensePage.TopText $mui.LicensePage 1040 + GetDlgItem $mui.LicensePage.Text $mui.LicensePage 1006 + GetDlgItem $mui.LicensePage.LicenseText $mui.LicensePage 1000 + + ;Top text + SendMessage $mui.LicensePage.TopText ${WM_SETTEXT} 0 "STR:${MUI_LICENSEPAGE_TEXT_TOP}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/StartMenu.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/StartMenu.nsh new file mode 100644 index 0000000..dd660ac --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/StartMenu.nsh @@ -0,0 +1,235 @@ +/* + +NSIS Modern User Interface +Start Menu folder page + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_STARTMENUPAGE_INTERFACE + + !ifndef MUI_STARTMENUPAGE_INTERFACE + !define MUI_STARTMENUPAGE_INTERFACE + Var mui.StartMenuPage + Var mui.StartMenuPage.Location + Var mui.StartMenuPage.FolderList + + Var mui.StartMenuPage.Temp + !endif + + !ifdef MUI_STARTMENUPAGE_REGISTRY_ROOT & MUI_STARTMENUPAGE_REGISTRY_KEY & MUI_STARTMENUPAGE_REGISTRY_VALUENAME + !ifndef MUI_STARTMENUPAGE_REGISTRY_VARIABLES + !define MUI_STARTMENUPAGE_REGISTRY_VARIABLES + Var mui.StartMenuPage.RegistryLocation + !endif + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_STARTMENU ID VAR + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}STARTMENUPAGE "" + !insertmacro MUI_STARTMENUPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_DEFAULTFOLDER "$(^Name)" + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_TEXT_TOP "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_STARTMENU_TOP)" + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_TEXT_CHECKBOX "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_STARTMENU_CHECKBOX)" + + !define MUI_STARTMENUPAGE_VARIABLE "${VAR}" + !define "MUI_STARTMENUPAGE_${ID}_VARIABLE" "${MUI_STARTMENUPAGE_VARIABLE}" + !define "MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !ifdef MUI_STARTMENUPAGE_REGISTRY_ROOT + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT" "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" + !endif + !ifdef MUI_STARTMENUPAGE_REGISTRY_KEY + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY" "${MUI_STARTMENUPAGE_REGISTRY_KEY}" + !endif + !ifdef MUI_STARTMENUPAGE_REGISTRY_VALUENAME + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME" "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}" + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_STARTMENUPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuLeave_${MUI_UNIQUEID} + + !undef MUI_STARTMENUPAGE_VARIABLE + !undef MUI_STARTMENUPAGE_TEXT_TOP + !undef MUI_STARTMENUPAGE_TEXT_CHECKBOX + !undef MUI_STARTMENUPAGE_DEFAULTFOLDER + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_NODISABLE + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_ROOT + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_KEY + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_VALUENAME + +!macroend + +!macro MUI_PAGE_STARTMENU ID VAR + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_PAGEDECLARATION_STARTMENU "${ID}" "${VAR}" + + !verbose pop + +!macroend + +;-------------------------------- +;Page functions + +!macro MUI_FUNCTION_STARTMENUPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + !ifdef MUI_STARTMENUPAGE_REGISTRY_ROOT & MUI_STARTMENUPAGE_REGISTRY_KEY & MUI_STARTMENUPAGE_REGISTRY_VALUENAME + + ;Get Start Menu location from registry + + ${if} "${MUI_STARTMENUPAGE_VARIABLE}" == "" + + ReadRegStr $mui.StartMenuPage.RegistryLocation "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}" + ${if} $mui.StartMenuPage.RegistryLocation != "" + StrCpy "${MUI_STARTMENUPAGE_VARIABLE}" $mui.StartMenuPage.RegistryLocation + ${endif} + + ClearErrors + + ${endif} + + !endif + + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_TEXT_STARTMENU_TITLE) $(MUI_TEXT_STARTMENU_SUBTITLE) + + ${if} $(^RTL) == "0" + !ifndef MUI_STARTMENUPAGE_NODISABLE + StartMenu::Init /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" /checknoshortcuts "${MUI_STARTMENUPAGE_TEXT_CHECKBOX}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !else + StartMenu::Init /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !endif + ${else} + !ifndef MUI_STARTMENUPAGE_NODISABLE + StartMenu::Init /rtl /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" /checknoshortcuts "${MUI_STARTMENUPAGE_TEXT_CHECKBOX}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !else + StartMenu::Init /rtl /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !endif + ${endif} + + Pop $mui.StartMenuPage + + ;Get control handles + GetDlgItem $mui.StartMenuPage.Location $mui.StartMenuPage 1002 + GetDlgItem $mui.StartMenuPage.FolderList $mui.StartMenuPage 1004 + + !ifdef MUI_STARTMENUPAGE_BGCOLOR + SetCtlColors $mui.StartMenuPage.Location "" "${MUI_STARTMENUPAGE_BGCOLOR}" + SetCtlColors $mui.StartMenuMenu.FolderList "" "${MUI_STARTMENUPAGE_BGCOLOR}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + StartMenu::Show + + Pop $mui.StartMenuPage.Temp + ${if} $mui.StartMenuPage.Temp == "success" + Pop "${MUI_STARTMENUPAGE_VARIABLE}" + ${endif} + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + + +;-------------------------------- +;Script macros to get Start Menu folder + +!macro MUI_STARTMENU_GETFOLDER ID VAR + + !verbose push + !verbose ${MUI_VERBOSE} + + ;Get Start Menu folder from registry + ;Can be called from the script in the uninstaller + + !ifdef MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT & MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY & MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME + + ReadRegStr $mui.StartMenuPage.RegistryLocation "${MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME}" + + ${if} $mui.StartMenuPage.RegistryLocation != "" + StrCpy "${VAR}" $mui.StartMenuPage.RegistryLocation + ${else} + StrCpy "${VAR}" "${MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER}" + ${endif} + + !else + + StrCpy "${VAR}" "${MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER}" + + !endif + + !verbose pop + +!macroend + +!macro MUI_STARTMENU_WRITE_BEGIN ID + + ;The code in the script to write the shortcuts should be put between the + ;MUI_STARTMENU_WRITE_BEGIN and MUI_STARTMENU_WRITE_END macros + + !verbose push + !verbose ${MUI_VERBOSE} + + !define MUI_STARTMENUPAGE_CURRENT_ID "${ID}" + + StrCpy $mui.StartMenuPage.Temp "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" 1 + + ;If the folder start with >, the user has chosen not to create a shortcut + ${if} $mui.StartMenuPage.Temp != ">" + + ${if} "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" == "" + ;Get folder from registry if the variable doesn't contain anything + !insertmacro MUI_STARTMENU_GETFOLDER "${MUI_STARTMENUPAGE_CURRENT_ID}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" + ${endif} + + !verbose pop + +!macroend + +!macro MUI_STARTMENU_WRITE_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_ROOT & MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_KEY & MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_VALUENAME + ;Write folder to registry + WriteRegStr "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_VALUENAME}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" + !endif + + ${endif} + + !undef MUI_STARTMENUPAGE_CURRENT_ID + + !verbose pop + +!macroend + diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/UninstallConfirm.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/UninstallConfirm.nsh new file mode 100644 index 0000000..a9323ee --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/UninstallConfirm.nsh @@ -0,0 +1,96 @@ +/* + +NSIS Modern User Interface +Uninstall confirmation page + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_UNCONFIRMPAGE_INTERFACE + + !ifndef MUI_UNCONFIRMPAGE_INTERFACE + !define MUI_UNCONFIRMPAGE_INTERFACE + Var mui.UnConfirmPage + + Var mui.UnConfirmPage.Text + Var mui.UnConfirmPage.DirectoryText + Var mui.UnConfirmPage.Directory + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_CONFIRM + + !insertmacro MUI_SET MUI_UNCONFIRMPAGE "" + !insertmacro MUI_UNCONFIRMPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_LOCATION "" + + PageEx un.uninstConfirm + + PageCallbacks un.mui.ConfirmPre_${MUI_UNIQUEID} un.mui.ConfirmShow_${MUI_UNIQUEID} un.mui.ConfirmLeave_${MUI_UNIQUEID} + + Caption " " + + UninstallText "${MUI_UNCONFIRMPAGE_TEXT_TOP}" "${MUI_UNCONFIRMPAGE_TEXT_LOCATION}" + + PageExEnd + + !insertmacro MUI_UNFUNCTION_CONFIRMPAGE un.mui.ConfirmPre_${MUI_UNIQUEID} un.mui.ConfirmShow_${MUI_UNIQUEID} un.mui.ConfirmLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_UNCONFIRMPAGE_TEXT_TOP + !insertmacro MUI_UNSET MUI_UNCONFIRMPAGE_TEXT_LOCATION + +!macroend + +!macro MUI_UNPAGE_CONFIRM + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + !insertmacro MUI_PAGEDECLARATION_CONFIRM + + !verbose pop + +!macroend + + +;-------------------------------- +;Page functions + +!macro MUI_UNFUNCTION_CONFIRMPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_UNTEXT_CONFIRM_TITLE) $(MUI_UNTEXT_CONFIRM_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + ;Get controls handles + FindWindow $mui.UnConfirmPage "#32770" "" $HWNDPARENT + GetDlgItem $mui.UnConfirmPage.Text $mui.UnConfirmPage 1006 + GetDlgItem $mui.UnConfirmPage.DirectoryText $mui.UnConfirmPage 1029 + GetDlgItem $mui.UnConfirmPage.Directory $mui.UnConfirmPage 1000 + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI 2/Pages/Welcome.nsh b/installer/NSIS/Contrib/Modern UI 2/Pages/Welcome.nsh new file mode 100644 index 0000000..5e7ebdd --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI 2/Pages/Welcome.nsh @@ -0,0 +1,180 @@ +/* + +NSIS Modern User Interface +Welcome page (implemented using nsDialogs) + +*/ + +;-------------------------------- +;Page interface settings and variables + +!macro MUI_WELCOMEPAGE_INTERFACE + + !ifndef MUI_WELCOMEPAGE_INTERFACE + !define MUI_WELCOMEPAGE_INTERFACE + Var mui.WelcomePage + + Var mui.WelcomePage.Image + Var mui.WelcomePage.Image.Bitmap + + Var mui.WelcomePage.Title + Var mui.WelcomePage.Title.Font + + Var mui.WelcomePage.Text + !endif + + !insertmacro MUI_DEFAULT MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" + +!macroend + + +;-------------------------------- +;Interface initialization + +!macro MUI_WELCOMEPAGE_GUIINIT + + !ifndef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEWELCOMEPAGE_GUINIT + !define MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEWELCOMEPAGE_GUINIT + + Function ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePage.GUIInit + + InitPluginsDir + File "/oname=$PLUGINSDIR\modern-wizard.bmp" "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_BITMAP}" + + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_GUIINIT + Call "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_GUIINIT}" + !endif + + FunctionEnd + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FUNCTION_GUIINIT ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePage.GUIInit + + !endif + +!macroend + + +;-------------------------------- +;Page declaration + +!macro MUI_PAGEDECLARATION_WELCOME + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEPAGE "" + !insertmacro MUI_WELCOMEPAGE_INTERFACE + + !insertmacro MUI_WELCOMEPAGE_GUIINIT + + !insertmacro MUI_DEFAULT MUI_WELCOMEPAGE_TITLE "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_WELCOME_INFO_TITLE)" + !insertmacro MUI_DEFAULT MUI_WELCOMEPAGE_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_WELCOME_INFO_TEXT)" + + !insertmacro MUI_PAGE_FUNCTION_FULLWINDOW + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomeLeave_${MUI_UNIQUEID} + + PageExEnd + + !insertmacro MUI_FUNCTION_WELCOMEPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomeLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TITLE + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TITLE_3LINES + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TEXT + +!macroend + +!macro MUI_PAGE_WELCOME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_PAGEDECLARATION_WELCOME + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_WELCOME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + !insertmacro MUI_PAGEDECLARATION_WELCOME + + !verbose pop + +!macroend + + +;-------------------------------- +;Page functions + +!macro MUI_FUNCTION_WELCOMEPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + ;Create dialog + nsDialogs::Create 1044 + Pop $mui.WelcomePage + nsDialogs::SetRTL $(^RTL) + SetCtlColors $mui.WelcomePage "" "${MUI_BGCOLOR}" + + ;Image control + ${NSD_CreateBitmap} 0u 0u 109u 193u "" + Pop $mui.WelcomePage.Image + !ifndef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEFINISHPAGE_BITMAP_NOSTRETCH + ${NSD_SetStretchedImage} $mui.WelcomePage.Image $PLUGINSDIR\modern-wizard.bmp $mui.WelcomePage.Image.Bitmap + !else + ${NSD_SetImage} $mui.WelcomePage.Image $PLUGINSDIR\modern-wizard.bmp $mui.WelcomePage.Image.Bitmap + !endif + + ;Positiong of controls + + ;Title + !ifndef MUI_WELCOMEPAGE_TITLE_3LINES + !define MUI_WELCOMEPAGE_TITLE_HEIGHT 28 + !else + !define MUI_WELCOMEPAGE_TITLE_HEIGHT 38 + !endif + + ;Text + ;17 = 10 (top margin) + 7 (distance between texts) + !define /math MUI_WELCOMEPAGE_TEXT_TOP 17 + ${MUI_WELCOMEPAGE_TITLE_HEIGHT} + + ;Title + ${NSD_CreateLabel} 120u 10u 195u ${MUI_WELCOMEPAGE_TITLE_HEIGHT}u "${MUI_WELCOMEPAGE_TITLE}" + Pop $mui.WelcomePage.Title + SetCtlColors $mui.WelcomePage.Title "" "${MUI_BGCOLOR}" + CreateFont $mui.WelcomePage.Title.Font "$(^Font)" "12" "700" + SendMessage $mui.WelcomePage.Title ${WM_SETFONT} $mui.WelcomePage.Title.Font 0 + + ;Welcome text + ${NSD_CreateLabel} 120u ${MUI_WELCOMEPAGE_TEXT_TOP}u 195u 130u "${MUI_WELCOMEPAGE_TEXT}" + Pop $mui.WelcomePage.Text + SetCtlColors $mui.WelcomePage.Text "" "${MUI_BGCOLOR}" + + ;Show page + Call ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}muiPageLoadFullWindow + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + nsDialogs::Show + Call ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}muiPageUnloadFullWindow + + ;Delete image from memory + ${NSD_FreeImage} $mui.WelcomePage.Image.Bitmap + + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TITLE_HEIGHT + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TEXT_TOP + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend diff --git a/installer/NSIS/Contrib/Modern UI/System.nsh b/installer/NSIS/Contrib/Modern UI/System.nsh new file mode 100644 index 0000000..1fef858 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI/System.nsh @@ -0,0 +1,2495 @@ +/* + +NSIS Modern User Interface - Version 1.8 +Copyright 2002-2007 Joost Verburg + +*/ + +!echo "NSIS Modern User Interface version 1.8 - 2002-2007 Joost Verburg" + +;-------------------------------- + +!ifndef MUI_INCLUDED +!define MUI_INCLUDED + +!define MUI_SYSVERSION "1.8" + +!verbose push + +!ifndef MUI_VERBOSE + !define MUI_VERBOSE 3 +!endif + +!verbose ${MUI_VERBOSE} + +;-------------------------------- +;HEADER FILES, DECLARATIONS + +!include InstallOptions.nsh +!include LangFile.nsh +!include WinMessages.nsh + +!define LANGFILE_DEFAULT "${NSISDIR}\Contrib\Language files\English.nsh" + +Var MUI_TEMP1 +Var MUI_TEMP2 + +;-------------------------------- +;INSERT CODE + +!macro MUI_INSERT + + !ifndef MUI_INSERT + !define MUI_INSERT + + !ifdef MUI_PRODUCT | MUI_VERSION + !warning "The MUI_PRODUCT and MUI_VERSION defines have been removed. Use a normal Name command now." + !endif + + !insertmacro MUI_INTERFACE + + !insertmacro MUI_FUNCTION_GUIINIT + !insertmacro MUI_FUNCTION_ABORTWARNING + + !ifdef MUI_IOCONVERT_USED + !insertmacro INSTALLOPTIONS_FUNCTION_WRITE_CONVERT + !endif + + !ifdef MUI_UNINSTALLER + !insertmacro MUI_UNFUNCTION_GUIINIT + !insertmacro MUI_FUNCTION_UNABORTWARNING + + !ifdef MUI_UNIOCONVERT_USED + !insertmacro INSTALLOPTIONS_UNFUNCTION_WRITE_CONVERT + !endif + !endif + + !endif + +!macroend + +;-------------------------------- +;GENERAL + +!macro MUI_DEFAULT SYMBOL CONTENT + + !ifndef "${SYMBOL}" + !define "${SYMBOL}" "${CONTENT}" + !endif + +!macroend + +!macro MUI_DEFAULT_IOCONVERT SYMBOL CONTENT + + !ifndef "${SYMBOL}" + !define "${SYMBOL}" "${CONTENT}" + !insertmacro MUI_SET "${SYMBOL}_DEFAULTSET" + !insertmacro MUI_SET "MUI_${MUI_PAGE_UNINSTALLER_PREFIX}IOCONVERT_USED" + !else + !insertmacro MUI_UNSET "${SYMBOL}_DEFAULTSET" + !endif + +!macroend + +!macro MUI_SET SYMBOL + + !ifndef "${SYMBOL}" + !define "${SYMBOL}" + !endif + +!macroend + +!macro MUI_UNSET SYMBOL + + !ifdef "${SYMBOL}" + !undef "${SYMBOL}" + !endif + +!macroend + +;-------------------------------- +;INTERFACE - COMPILE TIME SETTINGS + +!macro MUI_INTERFACE + + !ifndef MUI_INTERFACE + !define MUI_INTERFACE + + !ifdef MUI_INSERT_NSISCONF + !insertmacro MUI_NSISCONF + !endif + + !insertmacro MUI_DEFAULT MUI_UI "${NSISDIR}\Contrib\UIs\modern.exe" + !insertmacro MUI_DEFAULT MUI_UI_HEADERIMAGE "${NSISDIR}\Contrib\UIs\modern_headerbmp.exe" + !insertmacro MUI_DEFAULT MUI_UI_HEADERIMAGE_RIGHT "${NSISDIR}\Contrib\UIs\modern_headerbmpr.exe" + !insertmacro MUI_DEFAULT MUI_UI_COMPONENTSPAGE_SMALLDESC "${NSISDIR}\Contrib\UIs\modern_smalldesc.exe" + !insertmacro MUI_DEFAULT MUI_UI_COMPONENTSPAGE_NODESC "${NSISDIR}\Contrib\UIs\modern_nodesc.exe" + !insertmacro MUI_DEFAULT MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" + !insertmacro MUI_DEFAULT MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_CHECKBITMAP "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_BGCOLOR "/windows" + !insertmacro MUI_DEFAULT MUI_INSTFILESPAGE_COLORS "/windows" + !insertmacro MUI_DEFAULT MUI_INSTFILESPAGE_PROGRESSBAR "smooth" + !insertmacro MUI_DEFAULT MUI_BGCOLOR "FFFFFF" + !insertmacro MUI_DEFAULT MUI_WELCOMEFINISHPAGE_INI "${NSISDIR}\Contrib\Modern UI\ioSpecial.ini" + !insertmacro MUI_DEFAULT MUI_UNWELCOMEFINISHPAGE_INI "${NSISDIR}\Contrib\Modern UI\ioSpecial.ini" + !insertmacro MUI_DEFAULT MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" + !insertmacro MUI_DEFAULT MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" + + !ifdef MUI_HEADERIMAGE + + !insertmacro MUI_DEFAULT MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp" + + !ifndef MUI_HEADERIMAGE_UNBITMAP + !define MUI_HEADERIMAGE_UNBITMAP "${MUI_HEADERIMAGE_BITMAP}" + !ifdef MUI_HEADERIMAGE_BITMAP_NOSTRETCH + !insertmacro MUI_SET MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH + !endif + !endif + + !ifdef MUI_HEADERIMAGE_BITMAP_RTL + !ifndef MUI_HEADERIMAGE_UNBITMAP_RTL + !define MUI_HEADERIMAGE_UNBITMAP_RTL "${MUI_HEADERIMAGE_BITMAP_RTL}" + !ifdef MUI_HEADERIMAGE_BITMAP_RTL_NOSTRETCH + !insertmacro MUI_SET MUI_HEADERIMAGE_UNBITMAP_RTL_NOSTRETCH + !endif + !endif + !endif + + !endif + + XPStyle On + + ChangeUI all "${MUI_UI}" + !ifdef MUI_HEADERIMAGE + !ifndef MUI_HEADERIMAGE_RIGHT + ChangeUI IDD_INST "${MUI_UI_HEADERIMAGE}" + !else + ChangeUI IDD_INST "${MUI_UI_HEADERIMAGE_RIGHT}" + !endif + !endif + !ifdef MUI_COMPONENTSPAGE_SMALLDESC + ChangeUI IDD_SELCOM "${MUI_UI_COMPONENTSPAGE_SMALLDESC}" + !else ifdef MUI_COMPONENTSPAGE_NODESC + ChangeUI IDD_SELCOM "${MUI_UI_COMPONENTSPAGE_NODESC}" + !endif + + Icon "${MUI_ICON}" + UninstallIcon "${MUI_UNICON}" + + CheckBitmap "${MUI_COMPONENTSPAGE_CHECKBITMAP}" + LicenseBkColor "${MUI_LICENSEPAGE_BGCOLOR}" + InstallColors ${MUI_INSTFILESPAGE_COLORS} + InstProgressFlags ${MUI_INSTFILESPAGE_PROGRESSBAR} + + SubCaption 4 " " + UninstallSubCaption 2 " " + + !insertmacro MUI_DEFAULT MUI_ABORTWARNING_TEXT "$(MUI_TEXT_ABORTWARNING)" + !insertmacro MUI_DEFAULT MUI_UNABORTWARNING_TEXT "$(MUI_UNTEXT_ABORTWARNING)" + + !endif + +!macroend + +;-------------------------------- +;INTERFACE - RUN-TIME + +!macro MUI_INNERDIALOG_TEXT CONTROL TEXT + + !verbose push + !verbose ${MUI_VERBOSE} + + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 ${CONTROL} + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:${TEXT}" + + !verbose pop + +!macroend + +!macro MUI_HEADER_TEXT_INTERNAL ID TEXT + + GetDlgItem $MUI_TEMP1 $HWNDPARENT "${ID}" + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + !endif + + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:${TEXT}" + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + ShowWindow $MUI_TEMP1 ${SW_SHOWNA} + + !endif + +!macroend + +!macro MUI_HEADER_TEXT TEXT SUBTEXT + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + LockWindow on + + !endif + + !insertmacro MUI_HEADER_TEXT_INTERNAL 1037 "${TEXT}" + !insertmacro MUI_HEADER_TEXT_INTERNAL 1038 "${SUBTEXT}" + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + LockWindow off + + !endif + + !verbose pop + +!macroend + +!macro MUI_HEADER_TEXT_PAGE TEXT SUBTEXT + + !ifdef MUI_PAGE_HEADER_TEXT & MUI_PAGE_HEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_PAGE_HEADER_TEXT}" "${MUI_PAGE_HEADER_SUBTEXT}" + !else ifdef MUI_PAGE_HEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_PAGE_HEADER_TEXT}" "${SUBTEXT}" + !else ifdef MUI_PAGE_HEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${TEXT}" "${MUI_PAGE_HEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "${TEXT}" "${SUBTEXT}" + !endif + + !insertmacro MUI_UNSET MUI_PAGE_HEADER_TEXT + !insertmacro MUI_UNSET MUI_PAGE_HEADER_SUBTEXT + +!macroend + +!macro MUI_DESCRIPTION_BEGIN + + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 1043 + + StrCmp $0 -1 0 mui.description_begin_done + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:" + EnableWindow $MUI_TEMP1 0 + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:$MUI_TEXT" + Goto mui.description_done + mui.description_begin_done: + +!macroend + +!macro MUI_DESCRIPTION_TEXT VAR TEXT + + !verbose push + !verbose ${MUI_VERBOSE} + + StrCmp $0 ${VAR} 0 mui.description_${VAR}_done + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:" + EnableWindow $MUI_TEMP1 1 + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:${TEXT}" + Goto mui.description_done + mui.description_${VAR}_done: + + !verbose pop + +!macroend + +!macro MUI_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + mui.description_done: + + !verbose pop + +!macroend + +!macro MUI_ENDHEADER + + IfAbort mui.endheader_abort + + !ifdef MUI_INSTFILESPAGE_FINISHHEADER_TEXT & MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_FINISHHEADER_TEXT}" "${MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT}" + !else ifdef MUI_INSTFILESPAGE_FINISHHEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_FINISHHEADER_TEXT}" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_SUBTITLE)" + !else ifdef MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_TITLE)" "${MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_TITLE)" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_SUBTITLE)" + !endif + + Goto mui.endheader_done + + mui.endheader_abort: + + !ifdef MUI_INSTFILESPAGE_ABORTHEADER_TEXT & MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_ABORTHEADER_TEXT}" "${MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT}" + !else ifdef MUI_INSTFILESPAGE_ABORTHEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_ABORTHEADER_TEXT}" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_SUBTITLE)" + !else ifdef MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_TITLE)" "${MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_TITLE)" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_SUBTITLE)" + !endif + + mui.endheader_done: + +!macroend + +!macro MUI_ABORTWARNING + + !ifdef MUI_FINISHPAGE_ABORTWARNINGCHECK + StrCmp $MUI_NOABORTWARNING "1" mui.quit + !endif + + !ifdef MUI_ABORTWARNING_CANCEL_DEFAULT + MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 "${MUI_ABORTWARNING_TEXT}" IDYES mui.quit + !else + MessageBox MB_YESNO|MB_ICONEXCLAMATION "${MUI_ABORTWARNING_TEXT}" IDYES mui.quit + !endif + + Abort + mui.quit: + +!macroend + +!macro MUI_UNABORTWARNING + + !ifdef MUI_UNABORTWARNING_CANCEL_DEFAULT + MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 "${MUI_UNABORTWARNING_TEXT}" IDYES mui.quit + !else + MessageBox MB_YESNO|MB_ICONEXCLAMATION "${MUI_UNABORTWARNING_TEXT}" IDYES mui.quit + !endif + + Abort + mui.quit: + +!macroend + +!macro MUI_GUIINIT + + !insertmacro MUI_WELCOMEFINISHPAGE_INIT "" + !insertmacro MUI_HEADERIMAGE_INIT "" + + !insertmacro MUI_GUIINIT_BASIC + +!macroend + +!macro MUI_UNGUIINIT + + !insertmacro MUI_WELCOMEFINISHPAGE_INIT "UN" + !insertmacro MUI_HEADERIMAGE_INIT "UN" + + !insertmacro MUI_GUIINIT_BASIC + + !ifdef MUI_UNFINISHPAGE + !ifndef MUI_UNFINISHPAGE_NOAUTOCLOSE + SetAutoClose true + !endif + !endif + +!macroend + +!macro MUI_GUIINIT_BASIC + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + CreateFont $MUI_TEMP2 "$(^Font)" "$(^FontSize)" "700" + SendMessage $MUI_TEMP1 ${WM_SETFONT} $MUI_TEMP2 0 + + !ifndef MUI_HEADER_TRANSPARENT_TEXT + + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + !else + + SetCtlColors $MUI_TEMP1 "" "transparent" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + SetCtlColors $MUI_TEMP1 "" "transparent" + + !endif + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1034 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + SetCtlColors $MUI_TEMP1 /BRANDING + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + SetCtlColors $MUI_TEMP1 /BRANDING + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:$(^Branding) " + +!macroend + +!macro MUI_WELCOMEFINISHPAGE_INIT UNINSTALLER + + !ifdef MUI_${UNINSTALLER}WELCOMEPAGE | MUI_${UNINSTALLER}FINISHPAGE + + !insertmacro INSTALLOPTIONS_EXTRACT_AS "${MUI_${UNINSTALLER}WELCOMEFINISHPAGE_INI}" "ioSpecial.ini" + File "/oname=$PLUGINSDIR\modern-wizard.bmp" "${MUI_${UNINSTALLER}WELCOMEFINISHPAGE_BITMAP}" + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 1" "Text" "$PLUGINSDIR\modern-wizard.bmp" + + !ifdef MUI_${UNINSTALLER}WELCOMEFINISHPAGE_BITMAP_NOSTRETCH + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 1" "Flags" "" + !endif + + !endif + +!macroend + +!macro MUI_HEADERIMAGE_INIT UNINSTALLER + + !ifdef MUI_HEADERIMAGE + + InitPluginsDir + + !ifdef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL + + StrCmp $(^RTL) 0 mui.headerimageinit_nortl + + File "/oname=$PLUGINSDIR\modern-header.bmp" "${MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL}" + + !ifndef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL_NOSTRETCH + SetBrandingImage /IMGID=1046 /RESIZETOFIT "$PLUGINSDIR\modern-header.bmp" + !else + SetBrandingImage /IMGID=1046 "$PLUGINSDIR\modern-header.bmp" + !endif + + Goto mui.headerimageinit_done + + mui.headerimageinit_nortl: + + !endif + + File "/oname=$PLUGINSDIR\modern-header.bmp" "${MUI_HEADERIMAGE_${UNINSTALLER}BITMAP}" + + !ifndef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_NOSTRETCH + SetBrandingImage /IMGID=1046 /RESIZETOFIT "$PLUGINSDIR\modern-header.bmp" + !else + SetBrandingImage /IMGID=1046 "$PLUGINSDIR\modern-header.bmp" + !endif + + !ifdef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL + + mui.headerimageinit_done: + + !endif + + !endif + +!macroend + +;-------------------------------- +;INTERFACE - FUNCTIONS + +!macro MUI_FUNCTION_GUIINIT + + Function .onGUIInit + + !insertmacro MUI_GUIINIT + + !ifdef MUI_CUSTOMFUNCTION_GUIINIT + Call "${MUI_CUSTOMFUNCTION_GUIINIT}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_DESCRIPTION_BEGIN + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifndef MUI_VAR_TEXT + Var MUI_TEXT + !define MUI_VAR_TEXT + !endif + + Function .onMouseOverSection + !insertmacro MUI_DESCRIPTION_BEGIN + + !verbose pop + +!macroend + +!macro MUI_FUNCTION_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DESCRIPTION_END + !ifdef MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION + Call "${MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION}" + !endif + FunctionEnd + + !verbose pop + +!macroend + +!macro MUI_UNFUNCTION_DESCRIPTION_BEGIN + + !verbose push + !verbose ${MUI_VERBOSE} + + Function un.onMouseOverSection + !insertmacro MUI_DESCRIPTION_BEGIN + + !verbose pop + +!macroend + +!macro MUI_UNFUNCTION_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DESCRIPTION_END + !ifdef MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION + Call "${MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION}" + !endif + FunctionEnd + + !verbose pop + +!macroend + +!macro MUI_FUNCTION_ABORTWARNING + + Function .onUserAbort + !ifdef MUI_ABORTWARNING + !insertmacro MUI_ABORTWARNING + !endif + !ifdef MUI_CUSTOMFUNCTION_ABORT + Call "${MUI_CUSTOMFUNCTION_ABORT}" + !endif + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_UNABORTWARNING + + Function un.onUserAbort + !ifdef MUI_UNABORTWARNING + !insertmacro MUI_UNABORTWARNING + !endif + !ifdef MUI_CUSTOMFUNCTION_UNABORT + Call "${MUI_CUSTOMFUNCTION_UNABORT}" + !endif + FunctionEnd + +!macroend + +!macro MUI_UNFUNCTION_GUIINIT + + Function un.onGUIInit + + !insertmacro MUI_UNGUIINIT + + !ifdef MUI_CUSTOMFUNCTION_UNGUIINIT + Call "${MUI_CUSTOMFUNCTION_UNGUIINIT}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTIONS_DESCRIPTION_BEGIN + + ;1.65 compatibility + + !warning "Modern UI macro name has changed. Please change MUI_FUNCTIONS_DESCRIPTION_BEGIN to MUI_FUNCTION_DESCRIPTION_BEGIN." + + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + +!macroend + +!macro MUI_FUNCTIONS_DESCRIPTION_END + + ;1.65 compatibility + + !warning "Modern UI macro name has changed. Please change MUI_FUNCTIONS_DESCRIPTION_END to MUI_FUNCTION_DESCRIPTION_END." + + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +!macroend + +;-------------------------------- +;START MENU FOLDER + +!macro MUI_STARTMENU_GETFOLDER ID VAR + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT & MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY & MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME + + ReadRegStr $MUI_TEMP1 "${MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" +3 + StrCpy "${VAR}" $MUI_TEMP1 + Goto +2 + + StrCpy "${VAR}" "${MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER}" + + !else + + StrCpy "${VAR}" "${MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER}" + + !endif + + !verbose pop + +!macroend + +!macro MUI_STARTMENU_WRITE_BEGIN ID + + !verbose push + !verbose ${MUI_VERBOSE} + + !define MUI_STARTMENUPAGE_CURRENT_ID "${ID}" + + StrCpy $MUI_TEMP1 "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" 1 + StrCmp $MUI_TEMP1 ">" mui.startmenu_write_${MUI_STARTMENUPAGE_CURRENT_ID}_done + + StrCmp "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" "" 0 mui.startmenu_writebegin_${MUI_STARTMENUPAGE_CURRENT_ID}_notempty + + !insertmacro MUI_STARTMENU_GETFOLDER "${MUI_STARTMENUPAGE_CURRENT_ID}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" + + mui.startmenu_writebegin_${MUI_STARTMENUPAGE_CURRENT_ID}_notempty: + + !verbose pop + +!macroend + +!macro MUI_STARTMENU_WRITE_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_ROOT & MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_KEY & MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_VALUENAME + WriteRegStr "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_VALUENAME}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" + !endif + + mui.startmenu_write_${MUI_STARTMENUPAGE_CURRENT_ID}_done: + + !undef MUI_STARTMENUPAGE_CURRENT_ID + + !verbose pop + +!macroend + +;-------------------------------- +;PAGES + +!macro MUI_PAGE_INIT + + !insertmacro MUI_INTERFACE + + !insertmacro MUI_DEFAULT MUI_PAGE_UNINSTALLER_PREFIX "" + !insertmacro MUI_DEFAULT MUI_PAGE_UNINSTALLER_FUNCPREFIX "" + + !insertmacro MUI_UNSET MUI_UNIQUEID + + !define MUI_UNIQUEID ${__LINE__} + +!macroend + +!macro MUI_UNPAGE_INIT + + !ifndef MUI_UNINSTALLER + !define MUI_UNINSTALLER + !endif + + !define MUI_PAGE_UNINSTALLER + + !insertmacro MUI_UNSET MUI_PAGE_UNINSTALLER_PREFIX + !insertmacro MUI_UNSET MUI_PAGE_UNINSTALLER_FUNCPREFIX + + !define MUI_PAGE_UNINSTALLER_PREFIX "UN" + !define MUI_PAGE_UNINSTALLER_FUNCPREFIX "un." + +!macroend + +!macro MUI_UNPAGE_END + + !undef MUI_PAGE_UNINSTALLER + !undef MUI_PAGE_UNINSTALLER_PREFIX + !undef MUI_PAGE_UNINSTALLER_FUNCPREFIX + +!macroend + +!macro MUI_PAGE_WELCOME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEPAGE + + !insertmacro MUI_DEFAULT_IOCONVERT MUI_WELCOMEPAGE_TITLE "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_WELCOME_INFO_TITLE)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_WELCOMEPAGE_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_WELCOME_INFO_TEXT)" + + !ifndef MUI_VAR_HWND + Var MUI_HWND + !define MUI_VAR_HWND + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomeLeave_${MUI_UNIQUEID} + + PageExEnd + + !insertmacro MUI_FUNCTION_WELCOMEPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomeLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TITLE + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TITLE_3LINES + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TEXT + + !verbose pop + +!macroend + +!macro MUI_PAGE_LICENSE LICENSEDATA + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}LICENSEPAGE + + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_TEXT_TOP "$(MUI_INNERTEXT_LICENSE_TOP)" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_BUTTON "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_CHECKBOX_TEXT "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE "" + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}license + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicensePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseLeave_${MUI_UNIQUEID} + + Caption " " + + LicenseData "${LICENSEDATA}" + + !ifndef MUI_LICENSEPAGE_TEXT_BOTTOM + !ifndef MUI_LICENSEPAGE_CHECKBOX & MUI_LICENSEPAGE_RADIOBUTTONS + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM)" "${MUI_LICENSEPAGE_BUTTON}" + !else ifdef MUI_LICENSEPAGE_CHECKBOX + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM_CHECKBOX)" "${MUI_LICENSEPAGE_BUTTON}" + !else + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS)" "${MUI_LICENSEPAGE_BUTTON}" + !endif + !else + LicenseText "${MUI_LICENSEPAGE_TEXT_BOTTOM}" "${MUI_LICENSEPAGE_BUTTON}" + !endif + + !ifdef MUI_LICENSEPAGE_CHECKBOX + LicenseForceSelection checkbox "${MUI_LICENSEPAGE_CHECKBOX_TEXT}" + !else ifdef MUI_LICENSEPAGE_RADIOBUTTONS + LicenseForceSelection radiobuttons "${MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT}" "${MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE}" + !endif + + PageExEnd + + !insertmacro MUI_FUNCTION_LICENSEPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicensePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_LICENSEPAGE_TEXT_TOP + !insertmacro MUI_UNSET MUI_LICENSEPAGE_TEXT_BOTTOM + !insertmacro MUI_UNSET MUI_LICENSEPAGE_BUTTON + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT + !insertmacro MUI_UNSET MUI_LICENSEPAGE_RADIOBUTTONS + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT_ACCEPT + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT_DECLINE + + !verbose pop + +!macroend + +!macro MUI_PAGE_COMPONENTS + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}COMPONENTSPAGE + + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_COMPLIST "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_INSTTYPE "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE "$(MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE)" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO "$(MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO)" + + !ifndef MUI_VAR_TEXT + Var MUI_TEXT + !define MUI_VAR_TEXT + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}components + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsLeave_${MUI_UNIQUEID} + + Caption " " + + ComponentText "${MUI_COMPONENTSPAGE_TEXT_TOP}" "${MUI_COMPONENTSPAGE_TEXT_INSTTYPE}" "${MUI_COMPONENTSPAGE_TEXT_COMPLIST}" + + PageExEnd + + !insertmacro MUI_FUNCTION_COMPONENTSPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsLeave_${MUI_UNIQUEID} + + !undef MUI_COMPONENTSPAGE_TEXT_TOP + !undef MUI_COMPONENTSPAGE_TEXT_COMPLIST + !undef MUI_COMPONENTSPAGE_TEXT_INSTTYPE + !insertmacro MUI_UNSET MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE + !insertmacro MUI_UNSET MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO + + !verbose pop + +!macroend + +!macro MUI_PAGE_DIRECTORY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}DIRECTORYPAGE + + !insertmacro MUI_DEFAULT MUI_DIRECTORYPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_DIRECTORYPAGE_TEXT_DESTINATION "" + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}directory + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryLeave_${MUI_UNIQUEID} + + Caption " " + + DirText "${MUI_DIRECTORYPAGE_TEXT_TOP}" "${MUI_DIRECTORYPAGE_TEXT_DESTINATION}" + + !ifdef MUI_DIRECTORYPAGE_VARIABLE + DirVar "${MUI_DIRECTORYPAGE_VARIABLE}" + !endif + + !ifdef MUI_DIRECTORYPAGE_VERIFYONLEAVE + DirVerify leave + !endif + + PageExEnd + + !insertmacro MUI_FUNCTION_DIRECTORYPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryLeave_${MUI_UNIQUEID} + + !undef MUI_DIRECTORYPAGE_TEXT_TOP + !undef MUI_DIRECTORYPAGE_TEXT_DESTINATION + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_BGCOLOR + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_VARIABLE + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_VERIFYONLEAVE + + !verbose pop + +!macroend + +!macro MUI_PAGE_STARTMENU ID VAR + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}STARTMENUPAGE + + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_DEFAULTFOLDER "$(^Name)" + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_TEXT_TOP "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_STARTMENU_TOP)" + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_TEXT_CHECKBOX "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_STARTMENU_CHECKBOX)" + + !define MUI_STARTMENUPAGE_VARIABLE "${VAR}" + !define "MUI_STARTMENUPAGE_${ID}_VARIABLE" "${MUI_STARTMENUPAGE_VARIABLE}" + !define "MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !ifdef MUI_STARTMENUPAGE_REGISTRY_ROOT + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT" "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" + !endif + !ifdef MUI_STARTMENUPAGE_REGISTRY_KEY + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY" "${MUI_STARTMENUPAGE_REGISTRY_KEY}" + !endif + !ifdef MUI_STARTMENUPAGE_REGISTRY_VALUENAME + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME" "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}" + !endif + + !ifndef MUI_VAR_HWND + Var MUI_HWND + !define MUI_VAR_HWND + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_STARTMENUPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuLeave_${MUI_UNIQUEID} + + !undef MUI_STARTMENUPAGE_VARIABLE + !undef MUI_STARTMENUPAGE_TEXT_TOP + !undef MUI_STARTMENUPAGE_TEXT_CHECKBOX + !undef MUI_STARTMENUPAGE_DEFAULTFOLDER + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_NODISABLE + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_ROOT + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_KEY + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_VALUENAME + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_BGCOLOR + + !verbose pop + +!macroend + +!macro MUI_PAGE_INSTFILES + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INSTFILESPAGE + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}instfiles + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_INSTFILESPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_FINISHHEADER_TEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_ABORTWARNING_TEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_ABORTWARNING_SUBTEXT + + !verbose pop + +!macroend + +!macro MUI_PAGE_FINISH + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}FINISHPAGE + + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TITLE "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_TITLE)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_TEXT)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_BUTTON "$(MUI_BUTTONTEXT_FINISH)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT_REBOOT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_REBOOT)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT_REBOOTNOW "$(MUI_TEXT_FINISH_REBOOTNOW)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT_REBOOTLATER "$(MUI_TEXT_FINISH_REBOOTLATER)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_RUN_TEXT "$(MUI_TEXT_FINISH_RUN)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_SHOWREADME_TEXT "$(MUI_TEXT_FINISH_SHOWREADME)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_CHECKBOX3_TEXT "$(MUI_TEXT_FINISH_CHECKBOX3)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_CHECKBOX4_TEXT "$(MUI_TEXT_FINISH_CHECKBOX4)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_CHECKBOX5_TEXT "$(MUI_TEXT_FINISH_CHECKBOX5)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_LINK_COLOR "000080" + + !ifndef MUI_VAR_HWND + Var MUI_HWND + !define MUI_VAR_HWND + !endif + + !ifndef MUI_PAGE_UNINSTALLER + !ifndef MUI_FINISHPAGE_NOAUTOCLOSE + AutoCloseWindow true + !endif + !endif + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + !ifndef MUI_VAR_NOABORTWARNING + !define MUI_VAR_NOABORTWARNING + Var MUI_NOABORTWARNING + !endif + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_FINISHPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_FINISHPAGE_TITLE + !insertmacro MUI_UNSET MUI_FINISHPAGE_TITLE_3LINES + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_LARGE + !insertmacro MUI_UNSET MUI_FINISHPAGE_BUTTON + !insertmacro MUI_UNSET MUI_FINISHPAGE_CANCEL_ENABLED + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOTNOW + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOTLATER + !insertmacro MUI_UNSET MUI_FINISHPAGE_REBOOTLATER_DEFAULT + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_PARAMETERS + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3 + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4 + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5 + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK_LOCATION + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK_COLOR + !insertmacro MUI_UNSET MUI_FINISHPAGE_NOREBOOTSUPPORT + + !insertmacro MUI_UNSET MUI_FINISHPAGE_CURFIELD_TOP + !insertmacro MUI_UNSET MUI_FINISHPAGE_CURFIELD_BOTTOM + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_WELCOME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_WELCOME + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_CONFIRM + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifndef MUI_UNINSTALLER + !define MUI_UNINSTALLER + !endif + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_UNCONFIRMPAGE + + !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_LOCATION "" + + PageEx un.uninstConfirm + + PageCallbacks un.mui.ConfirmPre_${MUI_UNIQUEID} un.mui.ConfirmShow_${MUI_UNIQUEID} un.mui.ConfirmLeave_${MUI_UNIQUEID} + + Caption " " + + UninstallText "${MUI_UNCONFIRMPAGE_TEXT_TOP}" "${MUI_UNCONFIRMPAGE_TEXT_LOCATION}" + + PageExEnd + + !insertmacro MUI_UNFUNCTION_CONFIRMPAGE un.mui.ConfirmPre_${MUI_UNIQUEID} un.mui.ConfirmShow_${MUI_UNIQUEID} un.mui.ConfirmLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_UNCONFIRMPAGE_TEXT_TOP + !insertmacro MUI_UNSET MUI_UNCONFIRMPAGE_TEXT_LOCATION + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_LICENSE LICENSEDATA + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_LICENSE "${LICENSEDATA}" + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_COMPONENTS + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_COMPONENTS + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_DIRECTORY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_DIRECTORY + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_INSTFILES + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_FINISH + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +;-------------------------------- +;PAGE FUNCTIONS + +!macro MUI_PAGE_FUNCTION_CUSTOM TYPE + + !ifdef MUI_PAGE_CUSTOMFUNCTION_${TYPE} + Call "${MUI_PAGE_CUSTOMFUNCTION_${TYPE}}" + !undef MUI_PAGE_CUSTOMFUNCTION_${TYPE} + !endif + +!macroend + +!macro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + !ifdef MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT + Call "${MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT}" + !undef MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT + !endif + +!macroend + +!macro MUI_FUNCTION_WELCOMEPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "NumFields" "3" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "NextButtonText" "" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "CancelEnabled" "" + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 2" "Text" MUI_WELCOMEPAGE_TITLE + + !ifndef MUI_WELCOMEPAGE_TITLE_3LINES + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "38" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Top" "45" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "48" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Top" "55" + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "185" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 3" "Text" MUI_WELCOMEPAGE_TEXT + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + LockWindow off + + !insertmacro INSTALLOPTIONS_INITDIALOG "ioSpecial.ini" + Pop $MUI_HWND + SetCtlColors $MUI_HWND "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1201 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + CreateFont $MUI_TEMP2 "$(^Font)" "12" "700" + SendMessage $MUI_TEMP1 ${WM_SETFONT} $MUI_TEMP2 0 + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1202 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + !insertmacro INSTALLOPTIONS_SHOW + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + LockWindow off + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_LICENSEPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_LICENSE_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_LICENSE_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_INNERDIALOG_TEXT 1040 "${MUI_LICENSEPAGE_TEXT_TOP}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_COMPONENTSPAGE PRE SHOW LEAVE + + Function "${PRE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_COMPONENTS_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_COMPONENTS_SUBTITLE) + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_INNERDIALOG_TEXT 1042 "${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE}" + + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 1043 + EnableWindow $MUI_TEMP1 0 + + !insertmacro MUI_INNERDIALOG_TEXT 1043 "${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO}" + StrCpy $MUI_TEXT "${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_DIRECTORYPAGE PRE SHOW LEAVE + + Function "${PRE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_SUBTITLE) + FunctionEnd + + Function "${SHOW}" + !ifdef MUI_DIRECTORYPAGE_BGCOLOR + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 1019 + SetCtlColors $MUI_TEMP1 "" "${MUI_DIRECTORYPAGE_BGCOLOR}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + FunctionEnd + + Function "${LEAVE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_STARTMENUPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + !ifdef MUI_STARTMENUPAGE_REGISTRY_ROOT & MUI_STARTMENUPAGE_REGISTRY_KEY & MUI_STARTMENUPAGE_REGISTRY_VALUENAME + + StrCmp "${MUI_STARTMENUPAGE_VARIABLE}" "" 0 +4 + + ReadRegStr $MUI_TEMP1 "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" +2 + StrCpy "${MUI_STARTMENUPAGE_VARIABLE}" $MUI_TEMP1 + + !endif + + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_TEXT_STARTMENU_TITLE) $(MUI_TEXT_STARTMENU_SUBTITLE) + + StrCmp $(^RTL) 0 mui.startmenu_nortl + !ifndef MUI_STARTMENUPAGE_NODISABLE + StartMenu::Init /NOUNLOAD /rtl /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" /checknoshortcuts "${MUI_STARTMENUPAGE_TEXT_CHECKBOX}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !else + StartMenu::Init /NOUNLOAD /rtl /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !endif + Goto mui.startmenu_initdone + mui.startmenu_nortl: + !ifndef MUI_STARTMENUPAGE_NODISABLE + StartMenu::Init /NOUNLOAD /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" /checknoshortcuts "${MUI_STARTMENUPAGE_TEXT_CHECKBOX}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !else + StartMenu::Init /NOUNLOAD /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !endif + mui.startmenu_initdone: + + Pop $MUI_HWND + + !ifdef MUI_STARTMENUPAGE_BGCOLOR + GetDlgItem $MUI_TEMP1 $MUI_HWND 1002 + SetCtlColors $MUI_TEMP1 "" "${MUI_STARTMENUPAGE_BGCOLOR}" + GetDlgItem $MUI_TEMP1 $MUI_HWND 1004 + SetCtlColors $MUI_TEMP1 "" "${MUI_STARTMENUPAGE_BGCOLOR}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + StartMenu::Show + + Pop $MUI_TEMP1 + StrCmp $MUI_TEMP1 "success" 0 +2 + Pop "${MUI_STARTMENUPAGE_VARIABLE}" + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_INSTFILESPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLING_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLING_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + !insertmacro MUI_ENDHEADER + !insertmacro MUI_LANGDLL_SAVELANGUAGE + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_FINISHPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Settings" "NextButtonText" MUI_FINISHPAGE_BUTTON + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "CancelEnabled" "1" + !endif + + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "38" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Top" "45" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "48" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Top" "55" + !endif + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 2" "Text" MUI_FINISHPAGE_TITLE + + !ifdef MUI_FINISHPAGE_RUN | MUI_FINISHPAGE_SHOWREADME + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "85" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "115" + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "95" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "125" + !endif + !endif + !else + !ifndef MUI_FINISHPAGE_LINK + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "185" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "175" + !endif + !endif + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + IfRebootFlag 0 mui.finish_noreboot_init + + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "85" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "115" + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "95" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "125" + !endif + !endif + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 3" "Text" MUI_FINISHPAGE_TEXT_REBOOT + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "5" + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Type" "RadioButton" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 4" "Text" MUI_FINISHPAGE_TEXT_REBOOTNOW + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Right" "321" + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "90" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "100" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "130" + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "100" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "110" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "130" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "140" + !endif + !endif + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Type" "RadioButton" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 5" "Text" MUI_FINISHPAGE_TEXT_REBOOTLATER + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Right" "321" + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Top" "110" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Bottom" "120" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Top" "110" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Bottom" "120" + !endif + !ifdef MUI_FINISHPAGE_REBOOTLATER_DEFAULT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "State" "1" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "State" "1" + !endif + + Goto mui.finish_load + + mui.finish_noreboot_init: + + !endif + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 3" "Text" MUI_FINISHPAGE_TEXT + + !ifdef MUI_FINISHPAGE_RUN + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 4" "Text" MUI_FINISHPAGE_RUN_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Right" "315" + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "150" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "160" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "170" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "180" + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "150" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "160" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "180" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "190" + !endif + !endif + !ifndef MUI_FINISHPAGE_RUN_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "State" "1" + !endif + + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 4 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 140 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 150 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 160 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 170 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 140 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 150 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 170 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 180 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 5 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 160 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 170 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 180 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 190 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 170 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 180 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 190 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 200 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_SHOWREADME_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX3 + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 5 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 150 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 160 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 170 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 180 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 150 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 160 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 180 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 190 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 6 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 170 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 180 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 190 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 200 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 180 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 190 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 200 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 210 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_CHECKBOX3_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX4 + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 6 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 160 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 170 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 180 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 190 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 160 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 170 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 190 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 200 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 7 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 180 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 190 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 200 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 210 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 190 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 200 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 210 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 220 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_CHECKBOX4_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + + !ifdef MUI_FINISHPAGE_CHECKBOX5 + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 7 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 90 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 100 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 110 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 120 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 90 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 100 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 120 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 130 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 8 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 110 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 120 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 130 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 140 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define MUI_FINISHPAGE_CURFIELD_TOP 120 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 130 + !else + !define MUI_FINISHPAGE_CURFIELD_TOP 140 + !define MUI_FINISHPAGE_CURFIELD_BOTTOM 150 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_CHECKBOX5_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_CHECKBOX5_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_LINK + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 + !define MUI_FINISHPAGE_CURFIELD_NO 9 + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME + !define MUI_FINISHPAGE_CURFIELD_NO 6 + !else ifdef MUI_FINISHPAGE_RUN | MUI_FINISHPAGE_SHOWREADME + !define MUI_FINISHPAGE_CURFIELD_NO 5 + !else + !define MUI_FINISHPAGE_CURFIELD_NO 4 + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "Link" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_LINK + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" "175" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" "185" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" MUI_FINISHPAGE_LINK_LOCATION + + !endif + + !ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_LINK & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "9" + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "8" + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "5" + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_LINK + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "5" + !else ifdef MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_LINK + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "5" + !else ifdef MUI_FINISHPAGE_RUN | MUI_FINISHPAGE_SHOWREADME | MUI_FINISHPAGE_LINK + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "4" + !endif + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + mui.finish_load: + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + LockWindow off + + !insertmacro INSTALLOPTIONS_INITDIALOG "ioSpecial.ini" + Pop $MUI_HWND + SetCtlColors $MUI_HWND "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1201 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + CreateFont $MUI_TEMP2 "$(^Font)" "12" "700" + SendMessage $MUI_TEMP1 ${WM_SETFONT} $MUI_TEMP2 0 + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1202 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + IfRebootFlag 0 mui.finish_noreboot_show + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + Goto mui.finish_show + + mui.finish_noreboot_show: + + !endif + + !ifdef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX3 + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1205 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX4 + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1205 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1206 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX5 + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1206 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1207 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !endif + + !ifdef MUI_FINISHPAGE_LINK + !ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME + GetDlgItem $MUI_TEMP1 $MUI_HWND 1205 + !else ifdef MUI_FINISHPAGE_RUN | MUI_FINISHPAGE_SHOWREADME + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + !endif + SetCtlColors $MUI_TEMP1 "${MUI_FINISHPAGE_LINK_COLOR}" "${MUI_BGCOLOR}" + !endif + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + mui.finish_show: + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + StrCpy $MUI_NOABORTWARNING "1" + !endif + + !insertmacro INSTALLOPTIONS_SHOW + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + StrCpy $MUI_NOABORTWARNING "" + !endif + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + LockWindow off + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + IfRebootFlag "" mui.finish_noreboot_end + + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 4" "State" + + StrCmp $MUI_TEMP1 "1" 0 +2 + Reboot + + Return + + mui.finish_noreboot_end: + + !endif + + !ifdef MUI_FINISHPAGE_RUN + + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 4" "State" + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_norun + !ifndef MUI_FINISHPAGE_RUN_FUNCTION + !ifndef MUI_FINISHPAGE_RUN_PARAMETERS + StrCpy $MUI_TEMP1 "$\"${MUI_FINISHPAGE_RUN}$\"" + !else + StrCpy $MUI_TEMP1 "$\"${MUI_FINISHPAGE_RUN}$\" ${MUI_FINISHPAGE_RUN_PARAMETERS}" + !endif + Exec "$MUI_TEMP1" + !else + Call "${MUI_FINISHPAGE_RUN_FUNCTION}" + !endif + + mui.finish_norun: + + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 4" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 5" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_noshowreadme + !ifndef MUI_FINISHPAGE_SHOWREADME_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_SHOWREADME}" + !else + Call "${MUI_FINISHPAGE_SHOWREADME_FUNCTION}" + !endif + + mui.finish_noshowreadme: + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX3 + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 5" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 6" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_nocheckbox3 + !ifndef MUI_FINISHPAGE_CHECKBOX3_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_CHECKBOX3}" + !else + Call "${MUI_FINISHPAGE_CHECKBOX3_FUNCTION}" + !endif + + mui.finish_nocheckbox3: + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX4 + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 6" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 7" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_nocheckbox4 + !ifndef MUI_FINISHPAGE_CHECKBOX4_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_CHECKBOX4}" + !else + Call "${MUI_FINISHPAGE_CHECKBOX4_FUNCTION}" + !endif + + mui.finish_nocheckbox4: + + !endif + + + !ifdef MUI_FINISHPAGE_CHECKBOX5 + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 7" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 8" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_nocheckbox5 + !ifndef MUI_FINISHPAGE_CHECKBOX5_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_CHECKBOX5}" + !else + Call "${MUI_FINISHPAGE_CHECKBOX5_FUNCTION}" + !endif + + mui.finish_nocheckbox5: + + !endif + + + FunctionEnd + +!macroend + +!macro MUI_UNFUNCTION_CONFIRMPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_UNTEXT_CONFIRM_TITLE) $(MUI_UNTEXT_CONFIRM_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +;-------------------------------- +;INSTALL OPTIONS (CUSTOM PAGES) + +!macro MUI_INSTALLOPTIONS_EXTRACT FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_EXTRACT "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_EXTRACT_AS FILE FILENAME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_EXTRACT_AS "${FILE}" "${FILENAME}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_DISPLAY FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_DISPLAY "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_DISPLAY_RETURN FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_DISPLAY_RETURN "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_INITDIALOG FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_INITDIALOG "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_SHOW + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_SHOW + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_SHOW_RETURN + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_SHOW_RETURN + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_READ VAR FILE SECTION KEY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_READ "${VAR}" "${FILE}" "${SECTION}" "${KEY}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_WRITE FILE SECTION KEY VALUE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_WRITE "${FILE}" "${SECTION}" "${KEY}" "${VALUE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT FILE SECTION KEY SYMBOL + + ;Converts default strings from language files to InstallOptions format + ;Only for use inside MUI + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifndef "${SYMBOL}_DEFAULTSET" + !insertmacro INSTALLOPTIONS_WRITE "${FILE}" "${SECTION}" "${KEY}" "${${SYMBOL}}" + !else + Push "${${SYMBOL}}" + Call ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}Nsis2Io + Pop $MUI_TEMP1 + !insertmacro INSTALLOPTIONS_WRITE "${FILE}" "${SECTION}" "${KEY}" $MUI_TEMP1 + !endif + + !verbose pop + +!macroend + +;-------------------------------- +;RESERVE FILES + +!macro MUI_RESERVEFILE_INSTALLOPTIONS + + !verbose push + !verbose ${MUI_VERBOSE} + + ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" + + !verbose pop + +!macroend + +!macro MUI_RESERVEFILE_LANGDLL + + !verbose push + !verbose ${MUI_VERBOSE} + + ReserveFile "${NSISDIR}\Plugins\LangDLL.dll" + + !verbose pop + +!macroend + +;-------------------------------- +;LANGUAGES + +!macro MUI_LANGUAGE LANGUAGE + + ;Include a language + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_INSERT + + LoadLanguageFile "${NSISDIR}\Contrib\Language files\${LANGUAGE}.nlf" + !insertmacro LANGFILE_INCLUDE "${NSISDIR}\Contrib\Language files\${LANGUAGE}.nsh" + + !ifndef MUI_LANGDLL_LANGUAGES + !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' " + !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' " + !else + !ifdef MUI_LANGDLL_LANGUAGES_TEMP + !undef MUI_LANGDLL_LANGUAGES_TEMP + !endif + !define MUI_LANGDLL_LANGUAGES_TEMP "${MUI_LANGDLL_LANGUAGES}" + !undef MUI_LANGDLL_LANGUAGES + + !ifdef MUI_LANGDLL_LANGUAGES_CP_TEMP + !undef MUI_LANGDLL_LANGUAGES_CP_TEMP + !endif + !define MUI_LANGDLL_LANGUAGES_CP_TEMP "${MUI_LANGDLL_LANGUAGES_CP}" + !undef MUI_LANGDLL_LANGUAGES_CP + + !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' ${MUI_LANGDLL_LANGUAGES_TEMP}" + !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' ${MUI_LANGDLL_LANGUAGES_CP_TEMP}" + !endif + + !verbose pop + +!macroend + +;-------------------------------- +;LANGUAGE SELECTION DIALOG + +!macro MUI_LANGDLL_DISPLAY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DEFAULT MUI_LANGDLL_WINDOWTITLE "Installer Language" + !insertmacro MUI_DEFAULT MUI_LANGDLL_INFO "Please select a language." + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + ReadRegStr $MUI_TEMP1 "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" mui.langdll_show + StrCpy $LANGUAGE $MUI_TEMP1 + !ifndef MUI_LANGDLL_ALWAYSSHOW + Goto mui.langdll_done + !endif + mui.langdll_show: + + !endif + + !ifdef NSIS_CONFIG_SILENT_SUPPORT + IfSilent mui.langdll_done + !endif + + !ifdef MUI_LANGDLL_ALLLANGUAGES + LangDLL::LangDialog "${MUI_LANGDLL_WINDOWTITLE}" "${MUI_LANGDLL_INFO}" A ${MUI_LANGDLL_LANGUAGES} "" + !else + LangDLL::LangDialog "${MUI_LANGDLL_WINDOWTITLE}" "${MUI_LANGDLL_INFO}" AC ${MUI_LANGDLL_LANGUAGES_CP} "" + !endif + + Pop $LANGUAGE + StrCmp $LANGUAGE "cancel" 0 +2 + Abort + + !ifdef NSIS_CONFIG_SILENT_SUPPORT + mui.langdll_done: + !else ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + mui.langdll_done: + !endif + + !verbose pop + +!macroend + +!macro MUI_LANGDLL_SAVELANGUAGE + + !ifndef MUI_PAGE_UNINSTALLER + + IfAbort mui.langdllsavelanguage_abort + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + WriteRegStr "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" $LANGUAGE + !endif + + mui.langdllsavelanguage_abort: + + !endif + +!macroend + +!macro MUI_UNGETLANGUAGE + + !verbose pop + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + ReadRegStr $MUI_TEMP1 "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" 0 mui.ungetlanguage_setlang + + !endif + + !insertmacro MUI_LANGDLL_DISPLAY + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + Goto mui.ungetlanguage_done + + mui.ungetlanguage_setlang: + StrCpy $LANGUAGE $MUI_TEMP1 + + mui.ungetlanguage_done: + + !endif + + !verbose pop + +!macroend + +;-------------------------------- +;END + +!endif + +!verbose pop diff --git a/installer/NSIS/Contrib/Modern UI/ioSpecial.ini b/installer/NSIS/Contrib/Modern UI/ioSpecial.ini new file mode 100644 index 0000000..55dc5a6 --- /dev/null +++ b/installer/NSIS/Contrib/Modern UI/ioSpecial.ini @@ -0,0 +1,19 @@ +[Settings] +Rect=1044 +NumFields=3 +[Field 1] +Type=bitmap +Left=0 +Right=109 +Top=0 +Bottom=193 +Flags=RESIZETOFIT +[Field 2] +Type=label +Left=120 +Right=315 +Top=10 +[Field 3] +Type=label +Left=120 +Right=315 \ No newline at end of file diff --git a/installer/NSIS/Contrib/UIs/default.exe b/installer/NSIS/Contrib/UIs/default.exe new file mode 100644 index 0000000..9699192 Binary files /dev/null and b/installer/NSIS/Contrib/UIs/default.exe differ diff --git a/installer/NSIS/Contrib/UIs/modern.exe b/installer/NSIS/Contrib/UIs/modern.exe new file mode 100644 index 0000000..61fbc9e Binary files /dev/null and b/installer/NSIS/Contrib/UIs/modern.exe differ diff --git a/installer/NSIS/Contrib/UIs/modern_headerbmp.exe b/installer/NSIS/Contrib/UIs/modern_headerbmp.exe new file mode 100644 index 0000000..f82a711 Binary files /dev/null and b/installer/NSIS/Contrib/UIs/modern_headerbmp.exe differ diff --git a/installer/NSIS/Contrib/UIs/modern_headerbmpr.exe b/installer/NSIS/Contrib/UIs/modern_headerbmpr.exe new file mode 100644 index 0000000..c4dc8e7 Binary files /dev/null and b/installer/NSIS/Contrib/UIs/modern_headerbmpr.exe differ diff --git a/installer/NSIS/Contrib/UIs/modern_nodesc.exe b/installer/NSIS/Contrib/UIs/modern_nodesc.exe new file mode 100644 index 0000000..9aa310d Binary files /dev/null and b/installer/NSIS/Contrib/UIs/modern_nodesc.exe differ diff --git a/installer/NSIS/Contrib/UIs/modern_smalldesc.exe b/installer/NSIS/Contrib/UIs/modern_smalldesc.exe new file mode 100644 index 0000000..58908c1 Binary files /dev/null and b/installer/NSIS/Contrib/UIs/modern_smalldesc.exe differ diff --git a/installer/NSIS/Contrib/UIs/sdbarker_tiny.exe b/installer/NSIS/Contrib/UIs/sdbarker_tiny.exe new file mode 100644 index 0000000..301383b Binary files /dev/null and b/installer/NSIS/Contrib/UIs/sdbarker_tiny.exe differ diff --git a/installer/NSIS/Contrib/zip2exe/!Modern-orig.nsh b/installer/NSIS/Contrib/zip2exe/!Modern-orig.nsh new file mode 100644 index 0000000..b015906 --- /dev/null +++ b/installer/NSIS/Contrib/zip2exe/!Modern-orig.nsh @@ -0,0 +1,8 @@ +;Change this file to customize zip2exe generated installers with a modern interface + +!include "MUI.nsh" + +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" \ No newline at end of file diff --git a/installer/NSIS/Contrib/zip2exe/Base.nsh b/installer/NSIS/Contrib/zip2exe/Base.nsh new file mode 100644 index 0000000..7739d20 --- /dev/null +++ b/installer/NSIS/Contrib/zip2exe/Base.nsh @@ -0,0 +1,87 @@ +;Change this file to customize zip2exe generated installers + +Name "${ZIP2EXE_NAME}" +OutFile "${ZIP2EXE_OUTFILE}" + +AllowRootDirInstall true + + +!ifdef ZIP2EXE_COMPRESSOR_SOLID + !define SETCOMPRESSOR_SWITCH /SOLID +!else + !define SETCOMPRESSOR_SWITCH +!endif + +!ifdef ZIP2EXE_COMPRESSOR_ZLIB + SetCompressor ${SETCOMPRESSOR_SWITCH} zlib +!else ifdef ZIP2EXE_COMPRESSOR_BZIP2 + SetCompressor ${SETCOMPRESSOR_SWITCH} bzip2 +!else ifdef ZIP2EXE_COMPRESSOR_LZMA + SetCompressor ${SETCOMPRESSOR_SWITCH} lzma +!endif + +!ifdef ZIP2EXE_INSTALLDIR + + InstallDir "${ZIP2EXE_INSTALLDIR}" + + Function zip2exe.SetOutPath + SetOutPath "$INSTDIR" + FunctionEnd + +!else ifdef ZIP2EXE_INSTALLDIR_WINAMP + + InstallDir "$PROGRAMFILES\Winamp" + InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Winamp" "UninstallString" + + Function .onVerifyInstDir + IfFileExists $INSTDIR\winamp.exe WinampInstalled + Abort + WinampInstalled: + FunctionEnd + + !ifdef ZIP2EXE_INSTALLDIR_WINAMPMODE + + Var ZIP2EXE_TEMP1 + Var ZIP2EXE_TEMP2 + + Function zip2exe.SetOutPath + !ifdef ZIP2EXE_INSTALLDIR_SKINS + StrCpy $ZIP2EXE_TEMP1 "$INSTDIR\Skins" + !else + StrCpy $ZIP2EXE_TEMP1 "$INSTDIR\Plugins" + !endif + ReadINIStr $ZIP2EXE_TEMP2 "$INSTDIR\winamp.ini" "Winamp" "${ZIP2EXE_INSTALLDIR_WINAMPMODE}" + StrCmp $ZIP2EXE_TEMP2 "" End + IfFileExists $ZIP2EXE_TEMP2 0 End + StrCpy $ZIP2EXE_TEMP1 $ZIP2EXE_TEMP2 + End: + SetOutPath $ZIP2EXE_TEMP1 + FunctionEnd + + !else + + Function zip2exe.SetOutPath + !ifdef ZIP2EXE_INSTALLDIR_PLUGINS + SetOutPath "$INSTDIR\Plugins" + !else + SetOutPath "$INSTDIR" + !endif + FunctionEnd + + !endif + +!endif + +!macro SECTION_BEGIN + + Section "" + + Call zip2exe.SetOutPath + +!macroend + +!macro SECTION_END + + SectionEnd + +!macroend \ No newline at end of file diff --git a/installer/NSIS/Contrib/zip2exe/Classic.nsh b/installer/NSIS/Contrib/zip2exe/Classic.nsh new file mode 100644 index 0000000..de86c49 --- /dev/null +++ b/installer/NSIS/Contrib/zip2exe/Classic.nsh @@ -0,0 +1,4 @@ +;Change this file to customize zip2exe generated installers with a classic interface + +Page directory +Page instfiles \ No newline at end of file diff --git a/installer/NSIS/Contrib/zip2exe/Modern.nsh b/installer/NSIS/Contrib/zip2exe/Modern.nsh new file mode 100644 index 0000000..317f88e --- /dev/null +++ b/installer/NSIS/Contrib/zip2exe/Modern.nsh @@ -0,0 +1,73 @@ +;Change this file to customize zip2exe generated installers with a modern interface + +!include "MUI.nsh" +!include "LogicLib.nsh" +!include "WinMessages.nsh" + +!define DIGSINST "c:\workspace\installer" +!define DIGSRES "${DIGSINST}\res" + +!define MUI_ICON "${DIGSRES}\digsby.ico" +!define MUI_UNICON "${DIGSRES}\digsby.ico" + +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_RIGHT + +!define MUI_HEADERIMAGE_BITMAP "${DIGSRES}\new_right_header.bmp" +!define MUI_HEADERIMAGE_UNBITMAP "${DIGSRES}\new_right_header.bmp" + +!define MUI_WELCOMEFINISHPAGE_BITMAP "${DIGSRES}\wizard.bmp" +!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${DIGSRES}\wizard-un.bmp" + +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_TEXT "Add shortcut to &Desktop" +!define MUI_FINISHPAGE_RUN_FUNCTION AddDesktopShortcut + +!define MUI_FINISHPAGE_SHOWREADME +!define MUI_FINISHPAGE_SHOWREADME_TEXT "Add shortcut to &QuickLaunch" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION AddQuicklaunchShortcut + +!define MUI_FINISHPAGE_CHECKBOX3 +!define MUI_FINISHPAGE_CHECKBOX3_TEXT "&Launch digsby when my computer starts" +!define MUI_FINISHPAGE_CHECKBOX3_FUNCTION AddStartupShortcut + +!define MUI_FINISHPAGE_CHECKBOX4 +!define MUI_FINISHPAGE_CHECKBOX4_TEXT "&Make Google Search my home page" +!define MUI_FINISHPAGE_CHECKBOX4_FUNCTION AddGoogleHomePage + +!define MUI_FINISHPAGE_CHECKBOX5 +!define MUI_FINISHPAGE_CHECKBOX5_TEXT "&Run digsby now" +!define MUI_FINISHPAGE_CHECKBOX5_FUNCTION StartDigsby + +!include "${DIGSINST}\DigsbyIni.nsh" +!include "${DIGSINST}\DigsbyRegister.nsh" + +Function .onInit + + StrCpy $user_status "None" + + InitPluginsDir + GetTempFileName $0 + Rename $0 "$PLUGINSDIR\register.ini" + + GetTempFileName $0 + Rename $0 "$PLUGINSDIR\errorcodes.ini" + + Call WriteIni ; From DigsbyIni + + +FunctionEnd + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "${DIGSINST}\license.txt" +!insertmacro MUI_PAGE_DIRECTORY +Page custom DigsbyPageRegister_enter DigsbyPageRegister_leave "" +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_WELCOME +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" + diff --git a/installer/NSIS/Docs/AdvSplash/advsplash.txt b/installer/NSIS/Docs/AdvSplash/advsplash.txt new file mode 100644 index 0000000..92f333f --- /dev/null +++ b/installer/NSIS/Docs/AdvSplash/advsplash.txt @@ -0,0 +1,53 @@ +AdvSplash.dll - small (5.5k), simple plugin that lets you throw +up a splash screen in NSIS installers with cool fading effects (win2k/xp) +and transparency. + +To use: + +Create a .BMP file of your splash screen. +(optional) Create a .WAV file to play while your splash screen shows. + +Add the following lines to your .NSI file: + +Function .onInit + SetOutPath $TEMP + File /oname=spltmp.bmp "my_splash.bmp" + +; optional +; File /oname=spltmp.wav "my_splashshit.wav" + + advsplash::show 1000 600 400 -1 $TEMP\spltmp + + Pop $0 ; $0 has '1' if the user closed the splash screen early, + ; '0' if everything closed normally, and '-1' if some error occurred. + + Delete $TEMP\spltmp.bmp +; Delete $TEMP\spltmp.wav +FunctionEnd + +Calling format + advsplash::show Delay FadeIn FadeOut KeyColor FileName + +Delay - length to show the screen for (in milliseconds) +FadeIn - length to show the fadein scene (in ms) (not included in Delay) +FadeOut - length to show the fadeout scene (in ms) (not included in Delay) +KeyColor - color used for transparency, could be any RGB value + (for ex. R=255 G=100 B=16 -> KeyColor=0xFF6410), + use KeyColor=-1 if there is no transparent color at your image. +FileName - splash bitmap filename (without the .bmp). The BMP file used will be + this parameter.bmp, and the wave file used (if present) will be this + parameter.wav. + +(If you already have an .onInit function, put that in it) + +Note 1: fadein/fadeout supported only on win2k/winxp systems, all other systems +will show simple splash screen with Delay = Delay + FadeIn + FadeOut. Also, I've +noted my winXP uses no transparent color at 16 bpp, so at bpps lower than 32 +for images with transparent color no fading effect will occur. + +Note 2: the return value of splash is 1 if the user closed the splash +screen early (pop it from the stack) + +-Justin +Converted to a plugin DLL by Amir Szekely (kichik) +Fading and transparency by Nik Medved (brainsucker) \ No newline at end of file diff --git a/installer/NSIS/Docs/Banner/Readme.txt b/installer/NSIS/Docs/Banner/Readme.txt new file mode 100644 index 0000000..b114e5e --- /dev/null +++ b/installer/NSIS/Docs/Banner/Readme.txt @@ -0,0 +1,47 @@ +BANNER PLUG-IN +-------------- + +The Banner plug-in shows a banner with customizable text. It uses the IDD_VERIFY dialog of the UI. + +There are three functions - show, getWindow and destroy. + +Usage +----- + +Banner::show "Text to show" + +[optional] Banner::getWindow + +Banner::destroy + +See Example.nsi for an example. + +Modern UI +--------- + +The Modern UI has two labels on the IDD_VERIFY dialog. To change all the texts, use: + +Banner::show /set 76 "Text 1 (replaces Please wait while Setup is loading...)" "Normal text" + +Custom UI +--------- + +If you have more labels on your IDD_VERIFY dialog, you can use multiple /set parameters to change the texts. + +Example: + +Banner::show /set 76 "bah #1" /set 54 "bah #2" "Normal text" + +The second parameter for /set is the ID of the control. + +Some More Tricks +---------------- + +If you use /set to set the main string (IDC_STR, 1030) you can specify a different string for the window's caption and for the main string. + +If you use an empty string as the main string (Banner::show "") the banner window will not show on the taskbar. + +Credits +------- + +A joint effort of brainsucker and kichik in honor of the messages dropped during the battle \ No newline at end of file diff --git a/installer/NSIS/Docs/BgImage/BgImage.txt b/installer/NSIS/Docs/BgImage/BgImage.txt new file mode 100644 index 0000000..7018570 --- /dev/null +++ b/installer/NSIS/Docs/BgImage/BgImage.txt @@ -0,0 +1,92 @@ +BgImage.DLL - NSIS extension DLL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Displays an image or a gradient with user defined texts and/or images behind the NSIS window. +Can also play WAVs. + +See Example.nsi for a usage example. + +Usage +~~~~~ + +1) Call SetBg to set the background +2) Call AddText, or AddImage to add texts and images +3) Call Redraw to update the background window +4) Optional - repeat steps 2-3 to add more images + -or- + call Clear and repeat steps 1-3 for a completely new background. +5) Call Destroy when the background is no longer required (.onGUIEnd for example) + +Notes +~~~~~ + +* This plugin requires NSIS 2.42 and above. + +* Do not call SetBg (which creates the window) from a section or a function called by a section. + BgImge must be run from the GUI thread as the installation thread is not built to handle GUI. + +Available functions +~~~~~~~~~~~~~~ + +SetBg [/FILLSCREEN|/TILED] path_to_bitmap +SetBg /GRADIENT R G B R G B + Sets the background and creates the window if necessary + Use /FILLSCREEN to make the image fill the screen + Use /TILED to set a tiled background + Use /GRADIENT to set a gradient background + + If SetReturn on was called returns "success" on the stack + or an error string if there was an error + + Do not use in .onInit! + +AddImage [/TRANSPARENT R G B] path_to_bitmap X Y + Adds an image to the background window at (X,Y) + X and Y can be negative to specify distance from right/bottom + Use /TRANSPARENT to make BgImage draw the image transparently + Define the transparent color using R G B + + If SetReturn on was called returns "success" on the stack + or an error string if there was an error + +AddText text font_handle R G B X Y X Y + Adds text to the background window + Use NSIS's CreateFont to create a font and pass it as font_handle + Use R G B to set the text color + The first X Y is for the top left corner of the text box + The second X Y is for the bottom right corner of the text box + X and Y can be negative to specify distance from right/bottoms + + If SetReturn on was called returns "success" on the stack + or an error string if there was an error + +Clear + Clears all of the current background, images and texts + +Destroy + Destroys the current background window + Destroy calls Clear automatically + +Sound [/WAIT|/LOOP] path_to_wav +Sound /STOP + Plays a wave file + Use /WAIT to wait for the sound to finish playing + Use /LOOP to loop the sound + Use Sound /STOP to stop the loop + +SetReturn on|off + Enable return values from SetBg, AddImage and AddText + Default value is off because all of the possible errors + are either things you should handle when debugging your script + such as "can't load bitmap" or errors you can do nothing about + such as "memory allocation error" + +Credits +~~~~~~~ + +Coded by Amir Szekely, aka KiCHiK + +Ximon Eighteen, aka Sunjammer - Fixed window title bar issues +iceman_k - Text idea and original implementation +Lajos Molnar, aka orfanik - Tile idea and original implementation +Jason Reis - Coding help \ No newline at end of file diff --git a/installer/NSIS/Docs/Dialer/Dialer.txt b/installer/NSIS/Docs/Dialer/Dialer.txt new file mode 100644 index 0000000..7e067d7 --- /dev/null +++ b/installer/NSIS/Docs/Dialer/Dialer.txt @@ -0,0 +1,121 @@ +DIALER PLUGIN +------------- + +Written by Amir Szekely aka KiCHiK +Readme by Joost Verburg + +The Dialer plugin for NSIS provides five functions related to internet connections. + +To download files from the internet, use the NSISdl plugin. + +USAGE +----- + +Example of usage: + +ClearErrors ;Clear the error flag +Dialer::FunctionName ;Call Dialer function +IfErrors "" +3 ;Check for errors + MessageBox MB_OK "Function not available" + Quit +Pop $R0 ;Get the return value from the stack +MessageBox MB_OK $R0 ;Display the return value + +EXAMPLE FUNCTION +---------------- + +; ConnectInternet (uses Dialer plugin) +; Written by Joost Verburg +; +; This function attempts to make a connection to the internet if there is no +; connection available. If you are not sure that a system using the installer +; has an active internet connection, call this function before downloading +; files with NSISdl. +; +; The function requires Internet Explorer 3, but asks to connect manually if +; IE3 is not installed. + +Function ConnectInternet + + Push $R0 + + ClearErrors + Dialer::AttemptConnect + IfErrors noie3 + + Pop $R0 + StrCmp $R0 "online" connected + MessageBox MB_OK|MB_ICONSTOP "Cannot connect to the internet." + Quit ;Remove to make error not fatal + + noie3: + + ; IE3 not installed + MessageBox MB_OK|MB_ICONINFORMATION "Please connect to the internet now." + + connected: + + Pop $R0 + +FunctionEnd + +FUNCTIONS +--------- + +If a function is not available on the system, the error flag will be set. + +* AttemptConnect + + Attempts to make a connection to the Internet if the system is not connected. + + online - already connected / connection successful + offline - connection failed + + Requires Internet Explorer 3 or later + +* AutodialOnline + + Causes the modem to automatically dial the default Internet connection if the system + is not connected to the internet. If the system is not set up to automatically + connect, it will prompt the user. + + Return values: + + online - already connected / connection successful + offline - connection failed + + Requires Internet Explorer 4 or later + +* AutodialUnattended + + Causes the modem to automatically dial the default Internet connection if the system + is not connected to the internet. The user will not be prompted. + + Return values: + + online - already connected / connection successful + offline - connection failed + + Requires Internet Explorer 4 or later + +* AutodialHangup + + Disconnects an automatic dial-up connection. + + Return values: + + success - disconnection successful + failure - disconnection failed + + Requires Internet Explorer 4 or later + +* GetConnectedState + + Checks whether the system is connected to the internet. + + Return values: + + online - system is online + offline - system is offline + + Requires Internet Explorer 4 or later \ No newline at end of file diff --git a/installer/NSIS/Docs/InstallOptions/Changelog.txt b/installer/NSIS/Docs/InstallOptions/Changelog.txt new file mode 100644 index 0000000..27e6952 --- /dev/null +++ b/installer/NSIS/Docs/InstallOptions/Changelog.txt @@ -0,0 +1,144 @@ +DLL version 2.47 (4/27/2007) +* Line breaks support in Link control +* Added HLine and VLine controls + +DLL version 2.46 (3/31/2007) +* Use installer's name for message boxes + +DLL version 2.45 (1/23/2007) +* Added FOCUS flag for setting focus to a control other than the first + +DLL version 2.44 (10/11/2005) +* Added HWND and HWND2 entries to the INI file to avoid messy calculations of the correct control id + +DLL version 2.43 (10/4/2005) +* Fixed alteration of the working directory by FileRequest +* Added WS_EX_LEFTSCROLLBAR in RTL mode + +DLL version 2.42 (1/21/2005) +* Added TRANSPARENT flag for BITMAP fields (funded by Chris Morgan) + +DLL version 2.41 (8/5/2004) +* Bitmaps are now automatically centered +* Fixed a bug which prevented enabling the next button from the leave function of InstallOptions pages +* Fixed a rare freeze + +DLL version 2.4 (1/4/2004) +* Initial focus is set in "initDialog" making it possible to override it from NSIS prior to calling "show" +* When initial focus is to a Text field InstallOptions now follows standard Windows behaviour by having the text selected +* Label and other static fields no longer have State= written to the INI file when leaving the dialog +* NOTIFY flag can now be used with Link fields (State should be omitted in this case) +* Likewise, State can now be used with Button fields (behaves the same as with Link fields) +* NOTIFY flag can also now be used with ListBox and DropList fields to have NSIS notified when the selection changes +* Meaning of RIGHT flag is now reversed in right-to-left language mode +* HSCROLL and VSCROLL flags are no longer restricted to Text fields +* Various Link field fixes + +DLL version 2.3 (12/4/2003) +* Added new control type "Button" +* Added new flag "NOTIFY" +* Added new flag "NOWORDWRAP" for multi-line text boxes +* Reduced size down to 12K +* Better RTL support + +DLL version 2.2 (6/10/2003) +* Added New control type LINK +* \r\n converts to newline in Multiline edit box +* Support for multiline edit box +* Better tab order in DirRequest and FileRequest +* Added READONLY option to text box +* Minor fixes + +DLL version 2.1 (3/15/2003) +* \r\n converts to newline in both label Text and ValidateText +* New browse dialog style (modern) +* Word wrapping for check boxes and radio buttons +* No ugly border for edit fields under XP +* Scroll bar for list boxes +* Works with SetStaticBkColor +* DISABLED dir and file request fields now disable the browse button too +* No more STATE value for labels +* Minor fixes + +DLL version 2.0 (1/4/2003) +* Supports custom font and DPI settings (by Joost Verburg) +* INI files should contain dialog units now, no pixels (by Joost Verburg) +* RESIZETOFIT flag for Bitmap control (by Amir Szekely) +* New documentation (by Joost Verburg) +* New GROUP/NOTABSTOP/DISABLED flags + +DLL version 1.7 beta (11/2/2002) +* Added initDialog and show DLL functions + +DLL version 1.6 beta (9/30/2002) +* CancelConfirmIcon becomes CancelConfirmFlags and can now take the other common MessageBox flags + +DLL version 1.5 beta (9/26/2002) +* Made close [x] button behave like Cancel (thanks brainsucker) + +DLL version 1.4 beta (9/4/2002) +* Added Icon and Bitmap controls (by Amir Szekely) + +DLL version 1.3 beta (8/15/2002) +* Added CancelShow (by ORTIM) +* Added pixel transformation for widgets (by ORTIM) + +DLL version 1.2 beta (7/31/2002) +* Added CancelEnabled (by ORTIM) +* Added CancelConfirmCaption and CancelConfirmIcon (by Amir Szekely) + +DLL version 1.1 beta (7/22/2002) +* Font is now taken from the main NSIS window (by Amir Szekely) + +DLL version 1.0 beta (12/16/2001) +* Moved to DLL, no longer need parentwnd ini writing +* Tons of changes - no longer fully compatible (see source for a big list) +* removed support for silent installers (it seems the old version would bring up it's own dialog) + +version 1.4 (11/18/2001) +* Added Listbox controls. +* Added MULTISELECT flag. +* Made the HWND list for the parent window controls dynamically allocated. This prevents a crash if NSIS ever gets more than 150 controls on it's main window. +* The TEXT property of DirRequest control can be used to specify an initial directory. The current directory is automatically selected when clicking the browse button of the DirRequest control. +* Added ROOT property to DirRequest which can be used to set the root directory (mostly due to felfert) +* Edit controls will now auto scroll (thanks felfert) +* Fixed a problem where the window wouldn't draw properly on some systems (thanks felfert) + +version 1.3 (11/03/2001) +* Got rid of the call to RedrawWindow() because it's no longer needed with the WS_CLIPCHILDREN flag for NSIS. +* Removed a few hardcoded limits of buffer sizes +* Added Checkbox and RadioButton controls +* Added RIGHT and CHECKED flags + +version 1.2.2 (10/30/2001) +* Additional size reductions. Further reduced the size down to 8k. +* The text parameter to a combobox can now be used to specify the initial value +* Changed from InvalidateRect() to RedrawWindow() to force a redraw after a browse dialog +* On startup, set the flags of the NSIS window to include WS_CLIPCHILDREN. Otherwise, our controls don't get drawn right. + +version 1.2.1 (10/28/2001) +* Bug fix. ControlID for the caption and the OK button were reused by the first two controls. (Thanks Schultz) + +version 1.2j (10/28/2001) +* 8.5kb from 44kb. heh. (by Justin Frankel) + +version 1.2 (10/28/2001) +* Still 44k +* Added the "FileRequest" and "DirRequest" control types (thanks Schultz) +* Added "MinLen", "MaxLen", and "ValidateText" properties to fields +* Added "Flags" as a way to specify additional parameters for controls +* Few more changes to the documentation +* Cleaned the code in a few places...still trying to make it smaller + +version 1.1 (10/27/2001) +* Added the "Title" option (thanks Alex) +* Moved the OK button so it is in the same location as the buttons on the main NSIS window (thanks Alex) +* Pressing "ENTER" will now automatically select the OK button (thanks Alex) +* Slightly improved the documentation + +version 1.01 (10/25/2001) +* Fixed the SetFocus loop so it exits after the first control like it was supposed to +* Added the license to the documentation + +version 1.0 (10/25/2001) +* Barely qualifies as a distribution diff --git a/installer/NSIS/Docs/InstallOptions/Readme.html b/installer/NSIS/Docs/InstallOptions/Readme.html new file mode 100644 index 0000000..5d7e696 --- /dev/null +++ b/installer/NSIS/Docs/InstallOptions/Readme.html @@ -0,0 +1,907 @@ + + + + InstallOptions 2 + + + + + + +
    +

    + InstallOptions 2

    +
    +

    + The InstallOptions plug-in is deprecated. For new scripts, it is recommended to + use the new nsDialogs plug-in instead.

    +
    +
    +

    + Introduction

    +
    +

    + InstallOptions is an NSIS plugin which allows you to create custom pages for NSIS + installers, to prompt the user for extra information.

    +

    + The dialogs created by InstallOptions are based on INI files which define the controls + on the dialog and their properties. These INI files can be modified from the script + to adjust the dialogs on runtime.

    +

    + The format of INI files is described in a + Wikipedia article.

    +
    +

    + INI file structure

    +
    +

    + The INI file has one required section. This section includes the number of controls + to be created as well as general window attributes. The INI file also includes a + variable number of Field sections which are used to create the controls to be displayed.

    +

    + The required section is named "Settings". It can contain the + following values:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + NumFields + (required) + The number of control elements to be displayed on the dialog window.
    + Title + (optional) + If specified, gives the text to set the titlebar to. Otherwise, the titlebar text + is not changed.
    + CancelEnabled + (optional) + If specified, overrides NSIS settings and enables or disables the cancel button. + If set to 1, the cancel button will be enabled. If set to 0, the cancel button will + be disabled.
    + CancelShow + (optional) + If specified, overrides NSIS settings and shows or hides the cancel button If set + to 1, the cancel button will be shown. If set to 0, the cancel button will be hidden.
    + BackEnabled + (optional) + If specified, overrides NSIS settings and enables or disables the back button. If + set to 1, the back button will be enabled. If set to 0, the back button will be + disabled.
    + CancelButtonText + (optional) + Overrides the text for the cancel button. If not specified, the cancel button text + will not be changed.
    + NextButtonText + (optional) + Overrides the text for the next button. If not specified, the next button text will + not be changed.
    + BackButtonText + (optional) + Overrides the text for the back button. If not specified, the back button text will + not be changed.
    + Rect + (optional) + Overrides the default rect ID to run over. This will make IO resize itself according + to a different rect than NSIS's dialogs rect.
    + RTL + (optional) + If 1 is specified the dialog will be mirrored and all texts will be aligned to the + right. The INSTALLOPTIONS_EXTRACT macros automatically set this field to the right + value for the current installer language as given by the NSIS string $(^RTL).
    + State + (output) + This is not something you have to supply yourself but is set by InstallOptions, + before calling your custom page validation function, to the field number of the + custom Button control (or other control having the Notify flag) the user pressed, + if any.
    +

    + Each field section has the heading "Field #" where # must be sequential + numbers from 1 to NumFields. Each Field section can contain the following values:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Type + (required) + Type of control to be created. Valid values are "Label", "Text", + "Password", "Combobox", "DropList", + "Listbox", "CheckBox", "RadioButton", + "FileRequest", "DirRequest" "Icon", + "Bitmap", "GroupBox", "HLine", + "VLine", "Link" or "Button".
    +
    + A "Label" is used to display static text. (i.e. a caption for + a textbox)
    + A "Text" and "Password" accept text input + from the user. "Password" masks the input with * characters.
    + A "Combobox" allows the user to type text not in the popup list, + a "Droplist" only allows selection of items in the list.
    + A "Listbox" shows multiple items and can optionally allow the + user to select more than one item.
    + A "CheckBox" control displays a check box with label.
    + A "RadioButton" control displays a radio button with label.
    + A "FileRequest" control displays a textbox and a browse button. + Clicking the browse button will display a file requester where the user can browse + for a file.
    + A "DirRequest" control displays a textbox and a browse button. + Clicking the browse button will display a directory requester where the user can + browse for a directory.
    + An "Icon" control displays an icon. Use no Text to use the installer + icon.
    + A "Bitmap" control displays a bitmap.
    + A "GroupBox" control displays a frame to group controls.
    + A "HLine" control displays a horizontal line to separate controls.
    + A "VLine" control displays a vertical line to separate controls.
    + A "Link" control displays a static hot text. When the user clicks + the control the contents of State (e.g. http://...) will be executed + using ShellExecute. Alternatively State can be omitted and the + NOTIFY flag used to have your NSIS script called. See the "NOTIFY" + flag below for more information.
    + A "Button" control displays a push button that can be used in + the same way as the "Link" control above.
    + Text + (optional) + Specifies the caption of a label, checkbox, or radio button control. For DirRequest + control this specifies the title of the browse dialog. For icon and bitmaps control + this specifies the path to the image.
    +
    + Note: For labels, \r\n will be converted to a newline. To use a + back-slash in your text you have to escape it using another back-slash - \\. Described + below are NSIS functions for converting text to/from this + format.
    + State + (optional) + Specifies the state of the control. This is updated when the user closes the window, + so you can read from it from NSIS. For edit texts and dir and file request boxes, + this is the string that is specified. For radio button and check boxes, this can + be '0' or '1' (for unchecked or checked). For list boxes, combo boxes and drop lists + this is the selected items separated by pipes ('|'). For Links and Buttons this + can specify something to be executed or opened (using ShellExecute).
    +
    + Note: For Text fields with the MULTILINE flag, \r\n will be converted + to a newline. To use a back-slash in your text you have to escape it using another + back-slash - \\. Described below are NSIS functions for + converting text to/from this format.
    + ListItems + (optional) + A list of items to display in a combobox, droplist, or listbox.
    + This is a single line of text with each item separated by a pipe character '|'
    + MaxLen + (optional) + Causes validation on the selected control to limit the maximum length of text.
    + If the user specifies more text than this, a message box will appear when they click + "OK" and the dialog will not be dismissed.
    + You should not use this on a "combobox" since the user can not + control what is selected.
    + This should be set to a maximum of 260 for "FileRequest" and + "DirRequest" controls.
    + Ignored on "Label" controls.
    + MinLen + (optional) + Causes validation on the selected control to force the user to enter a minimum amount + of text.
    + If the user specifies less text than this, a message box will appear when they click + "OK" and the dialog will not be dismissed.
    + Unlike MaxLen, this is useful for "Combobox" controls. By setting + this to a value of "1" the program will force the user to select an item.
    + Ignored on "Label" controls.
    + ValidateText + (optional) + If the field fails the test for "MinLen" or "MaxLen", + a messagebox will be displayed with this text.
    +
    + Note: \r\n will be converted to a newline, two back-slashes will + be converted to one - \\. Described below are NSIS functions + for converting text to/from this format.
    + Left
    + Right
    + Top
    + Bottom
    + (required) + The position on the dialog where this control appears. All sizes should be set in + dialog units. To get the right dimensions for your controls, design your dialog + using a resource editor and copy the dimensions to the INI file.
    +
    + Note: You can specify negative coordinates to specify the distance + from the right or bottom edge.
    +
    + Note (2): For combobox or droplist, the "bottom" + value is not used in the same way.
    + In this case, the bottom value is the maximum size of the window when the pop-up + list is being displayed. All other times, the combobox is automatically sized to + be one element tall. If you have trouble where you can not see the combobox drop-down, + then check the bottom value and ensure it is large enough. A rough guide for the + height required is the number of items in the list multiplied by 8, plus 20.
    +
    + Note (3): FileRequest and DirRequest controls will allocate 15 + dialog units to the browse button. Make this control wide enough the contents of + the textbox can be seen.
    + Filter + (optional) + Specifies the filter to be used in the "FileRequest" control.
    + This is constructed by putting pairs of entries together, each item separated by + a | character.
    + The first value in each pair is the text to display for the filter.
    + The second value is the pattern to use to match files.
    + For example, you might specify:
    + Filter=Text Files|*.txt|Programs|*.exe;*.com|All Files|*.*
    + If not specified, then the filter defaults to All Files|*.*
    +
    + Note: you should not put any extra spaces around the | characters.
    + Root + (optional) + Used by DirRequest controls to specify the root directory of the + search. By default, this allows the user to browse any directory on the computer. + This will limit the search to a particular directory on the system.
    + Flags + (optional) + This specifies additional flags for the display of different controls. Each value + should be separated by a | character, and you should be careful not to put any spaces + around the | character.
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Value + Meaning
    + REQ_SAVE + This causes "FileRequest" controls to display a Save As dialog. + If not specified, an Open dialog is used.
    + FILE_MUST_EXIST + Used by "FileRequest" to determine if the selected file must + exist.
    + This only applies if an "Open" dialog is being displayed.
    + This currently does not force the file to exist other than through the browse button.
    + FILE_EXPLORER + Used by "FileRequest", enables new file request look (recommended)
    + FILE_HIDEREADONLY + Used by "FileRequest", hides "open read only" checkbox + in open dialog.
    + WARN_IF_EXIST + Used by "FileRequest" to display a warning message if the selected + file already exists.
    + The warning message is only displayed for files selected with the browse button.
    + PATH_MUST_EXIST + Used by "FileRequest" to force the path to exist. Prevents the + user from typing a non-existent path into the browse dialog window.
    + This only validates path's selected with the browse button.
    + PROMPT_CREATE + Used by "FileRequest" to display a warning if the selected file + does not exist. However, it still allows the user to select the file.
    + This only displays the warning for files selected with the browse button.
    + Doesn't work along with REQ_SAVE.
    + RIGHT + Used by "Checkbox" and "Radiobutton" controls + to specify you want the checkbox to the right of the text instead of the left as + is the default.
    + MULTISELECT + Used by "Listbox" controls. Turns string selection on or off + each time the user clicks or double-clicks a string in the list box. The user can + select any number of strings. If this flag and EXTENDEDSELCT are not specified, + only one item can be selected from the list.
    + EXTENDEDSELCT + Used by "Listbox" controls. Allows multiple items to be selected + by using the SHIFT key and the mouse or special key combinations. If this flag and + MULTISELECT are not specified, only one item can be selected from the list.
    + RESIZETOFIT + This causes "Bitmap" controls to resize the image to the size + of the control. Also useful to support custom DPI settings. Without this, the image + will be centered within the specified area.
    + TRANSPARENT + Used by "Bitmap" controls. Hides every pixel with the same color + as of the top left pixel. This allows to see-through to controls behind it. This + flag doesn't work well with a combination of the RESIZETOFIT flag and bitmaps with + more than 256 colors.
    + GROUP + Add this flag to the first control of a group of controls to group them. Grouping + controls allows you to create multiple groups of radio button and makes keyboard + navigation using arrow keys easier.
    + FOCUS + Sets focus on the specified control, instead of the first focusable control. If + more than one field is specified with this flag, only the first one will receive + focus.
    + NOTABSTOP + Do not stop on the control when the user pressed the Tab key. Add NOTABSTOP to all + controls of a group except the first one to allow navigation between groups with + the Tab key.
    + DISABLED + Causes a control to be disabled.
    + ONLY_NUMBERS + Used by "Text" controls. Forces the user to enter only numbers + into the edit box.
    + MULTILINE + Used by "Text" controls. Causes the control to accept multiple-lines.
    + WANTRETURN + Used by "Text" controls with multiple-line. Specifies that a + carriage return be inserted when the user presses the ENTER key while entering text + into the text box.
    + NOWORDWRAP + Used by "Text" controls with multiple-line. Disables the word-wrap + that occurs when long lines are entered. Long lines instead scroll off to the side. + Specifying the HSCROLL flag also has this effect.
    + HSCROLL + Show a horizontal scrollbar. When used by "Text" controls with + multiple-lines this also disables word-wrap.
    + VSCROLL + Show a vertical scrollbar.
    + READONLY + Used by "Text" controls. Prevents the user from entering or editing + text in the edit control, but allow the user to select and copy the text.
    + NOTIFY + Used by "Button", "Link", "CheckBox", + "RadioButton", "ListBox" and "DropList" + controls. Causes InstallOptions to call your NSIS custom page validation/leave function + whenever the control's selection changes. Your validation/leave function can read + the "State" value from the "Settings" section + to determine which control caused the notification, if any, and perform some appropriate + action followed by an Abort instruction (to tell NSIS to return to the page). The + Examples\InstallOptions folder contains an example script showing how this might + be used.
    +
    + TxtColor + (optional) + Used by Link controls to specify the foreground color of the text. + Format: 0xBBGGRR (hexadecimal).
    + HWND
    + HWND2
    + (output) + After initDialog returns, this will contain the HWND of the control created by this + field. It can be used instead of FindWindow and GetDlgItem. HWND2 contains the HWND + of an additional control, such as the browse button.
    +
    +

    + Header file

    +
    +

    + The InstallOptions header files provides macros and functions to easily create custom + dialogs. You can include it on the top of your script as follows: +

    +!include InstallOptions.nsh
    +
    +
    +

    + Creating dialogs

    +
    +

    + Extracting the INI file

    +
    +

    + First, you have to extract your InstallOptions INI files in the .onInit function + (or un.onInit for the uninstaller) using the INSTALLOPTIONS_EXTRACT macro. The files + will be extracted to a temporary folder (the NSIS plug-ins folder) that is automatically + created.

    +
    +Function .onInit
    +  !insertmacro INSTALLOPTIONS_EXTRACT "ioFile.ini"
    +FunctionEnd
    +
    +

    + If the INI file is located in another directory, use INSTALLOPTIONS_EXTRACT_AS. + The second parameter is the filename in the temporary folder, which is the filename + that should be used as input for the other macros.

    +
    +Function .onInit
    +  !insertmacro INSTALLOPTIONS_EXTRACT_AS "..\ioFile.ini" "ioFile.ini"
    +FunctionEnd
    +
    +
    +

    + Displaying the dialog

    +
    +

    + You can call InstallOptions in a page function defined with the Page or UninstPage + command. Check the NSIS documentation (Scripting Reference -> Pages) for information + about the page system.

    +
    +Page custom CustomPageFunction
    +

    + To display the dialog, use the INSTALLOPTIONS_DISPLAY macro:

    +Function CustomPageFunction ;Function name defined with Page command
    +  !insertmacro INSTALLOPTIONS_DISPLAY "ioFile.ini"
    +FunctionEnd
    +
    +
    +
    +

    + User input

    +
    +

    + To get the input of the user, read the State value of a Field using the INSTALLOPTIONS_READ + macro:

    +
    +!insertmacro INSTALLOPTIONS_READ $VAR "ioFile.ini" "Field #" "Name"
    +
    +
    +

    + Writing to INI files

    +
    +

    + The INSTALLOPTIONS_WRITE macro allows you to write values to the INI file to change + texts or control settings on runtime: +

    +!insertmacro INSTALLOPTIONS_WRITE "ioFile.ini" "Field #" "Name" "Value"
    +
    +
    +

    + Escaped values

    +
    +

    + Some InstallOptions values are escaped (in a similar manner to "C" strings) + to allow characters to be used that are not normally valid in INI file values. The + affected values are:

    +
      +
    • The ValidateText field
    • +
    • The Text value of Label fields
    • +
    • The State value of Text fields that have the MULTILINE flag
    • +
    +

    + The escape character is the back-slash character ("\") and the available + escape sequences are:

    + + + + + + + + + + + + + + + + + +
    + "\\" + Back-slash
    + "\r" + Carriage return (ASCII 13)
    + "\n" + Line feed (ASCII 10)
    + "\t" + Tab (ASCII 9)
    +

    + The INSTALLOPTIONS_READ_CONVERT and INSTALLOPTIONS_WRITE_CONVERT macros automatically + convert these characters in installer code. In uninstaller code, use INSTALLOPTIONS_READ_UNCONVERT + and INSTALLOPTIONS_WRITE_UNCONVERT.

    +

    + To use these macros in your script, the conversion functions need to be included:

    +
    +;For INSTALLOPTIONS_READ_CONVERT
    +  !insertmacro INSTALLOPTIONS_FUNCTION_READ_CONVERT
    +;For INSTALLOPTIONS_WRITE_CONVERT
    +  !insertmacro INSTALLOPTIONS_FUNCTION_WRITE_CONVERT
    +;For INSTALLOPTIONS_READ_UNCONVERT
    +  !insertmacro INSTALLOPTIONS_UNFUNCTION_READ_CONVERT
    +;For INSTALLOPTIONS_WRITE_UNCONVERT
    +  !insertmacro INSTALLOPTIONS_UNFUNCTION_WRITE_CONVERT
    +
    +
    +

    + Input validation

    +
    +

    + To validate the user input (for example, to check whether the user has filled in + a textbox) use the leave function of the Page command and Abort when the validation + has failed:

    +
    +Function ValidateCustom
    +
    +  !insertmacro INSTALLOPTIONS_READ $R0 "test.ini" "Field 1" "State"
    +  StrCmp $R0 "" 0 +3
    +    MessageBox MB_ICONEXCLAMATION|MB_OK "Please enter your name."
    +    Abort
    +
    +FunctionEnd
    +
    +
    +

    + Return value

    +
    + After a dialog is created (using display or show), a return value is available:

    +
      +
    • success - The user has pressed the Next button
    • +
    • back - The user has pressed the Back button
    • +
    • cancel - The user has pressed the Cancel button
    • +
    • error - An error has occurred, the dialog cannot be displayed.
    • +
    +

    + You only have to check this value if you need something really special, such as + doing something when the user pressed the Back button.

    +

    + If you need the return value, use the INSTALLOPTIONS_DISPLAY_RETURN or INSTALLOPTIONS_SHOW_RETURN + macro. The return value will be added to the stack, so you can use the Pop command + to get it.

    +
    +

    + Reserve files

    +
    +

    + When using solid compression, it's important that files which are being extracted + in user interface functions are located before other files in the data block. Otherwise + there may be a delay before a page can be displayed.

    +

    + To ensure that this is the case, add ReserveFile commands for InstallOptions and + the INI files before all sections and functions:

    +
    +ReserveFile "test.ini"
    +ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll"
    +
    +
    +

    + Fonts and colors

    +
    +

    + To customize fonts or colors on InstallOptions dialogs, the INSTALLOPTIONS_INITDIALOG + and INSTALLOPTIONS_SHOW macro can be used.

    +

    + INSTALLOPTIONS_INITDIALOG creates the dialog in memory, but does not show it. After + inserting this macro, you can set the fonts and colors, and then insert INSTALLOPTIONS_SHOW + to show the dialog.

    +

    + The INSTALLOPTIONS_INITDIALOG macro also pushes the HWND of the custom dialog to + the stack. Control HWND's are available for each control in the HWND entry of the + corresponding field in the INI file.

    +

    + Example of using a custom font:

    +
    +Var HWND
    +Var DLGITEM
    +Var FONT
    +
    +Function FunctionName ;FunctionName defined with Page command
    +
    +  !insertmacro INSTALLOPTIONS_INITDIALOG "ioFile.ini"
    +  Pop $HWND ;HWND of dialog
    +    
    +  !insertmacro INSTALLOPTIONS_READ $DLGITEM "ioFile.ini" "Field 1" "HWND"
    +    
    +  ;$DLGITEM contains the HWND of the first field
    +  CreateFont $FONT "Tahoma" 10 700 
    +  SendMessage $DLGITEM ${WM_SETFONT} $FONT 0
    +        
    +  !insertmacro INSTALLOPTIONS_SHOW
    +
    +FunctionEnd
    +
    +
    +

    + Credits

    +
    +

    + Original version by Michael Bishop
    + DLL version by Nullsoft, Inc.
    + DLL version 2 by Amir Szekely, ORTIM, Joost Verburg
    + New documentation by Joost Verburg

    +
    +

    + License

    +
    +
    +Original version Copyright © 2001 Michael Bishop
    +DLL version 1 Copyright © 2001-2002 Nullsoft, Inc., ORTIM
    +DLL version 2 Copyright © 2003-2009 Amir Szekely, Joost Verburg, Dave Laundon
    +
    +This software is provided 'as-is', without any express or implied
    +warranty. In no event will the authors be held liable for any damages
    +arising from the use of this software.
    +
    +Permission is granted to anyone to use this software for any purpose,
    +including commercial applications, and to alter it and redistribute
    +it freely, subject to the following restrictions:
    +
    +1. The origin of this software must not be misrepresented;
    +   you must not claim that you wrote the original software.
    +   If you use this software in a product, an acknowledgment in the
    +   product documentation would be appreciated but is not required.
    +2. Altered versions must be plainly marked as such,
    +   and must not be misrepresented as being the original software.
    +3. This notice may not be removed or altered from any distribution.
    +
    +
    +
    + + diff --git a/installer/NSIS/Docs/Math/Math.txt b/installer/NSIS/Docs/Math/Math.txt new file mode 100644 index 0000000..e304b34 --- /dev/null +++ b/installer/NSIS/Docs/Math/Math.txt @@ -0,0 +1,197 @@ +Math::Script NSIS plugin. + +C-like style scripting (operators at least). +Tip1: plugin watches the case of the letters. +Tip2: plugin makes almost no error checks. So YOU should check your script +twice before run :) + +New HOW TO USE: run the MathTest.Exe, and try yourself. After spending +some minutes your should be able to write your script by yourself. +To include it to your NSIS script just insert that: + Math::Script "YourScript1" + Math::Script "YourScript2" + Math::Script "YourScriptFinal" + +How to use it? Simple: + Strcpy $0 "Brainsucker" + Math::Script "a = 'Math'; B = 'Script'; r0 += ' wants to use ' + a + '::' + b +'!'" + DetailPrint "$0" +That string will fill r0 with some shit. + +Here are some other samples: + 10! (factorial, r0 will contain '10! = 362880'): + r0 = '10! = ' + (1*2*3*4*5*6*7*8*9) + the same: + a = b = 1; #{++a <= 10, b = b*a}; r0 = (a-1) + '! = ' + b + Some floating point: + Strcpy $R0 "1e1" + Math::Script "pi = 3.14159; R1 = 2*pi*R0; r0 = 'Length of circle with radius ' + R0 + ' is equal to ' + R1 + '.'" + Detailprint "$0" + +Ok. Variables. +NSIS: r0-r9 -> $0-$9. R0-R9 -> $R0-$R9. +Also CL ($CMDLINE), ID ($INSTDIR), OD ($OUTDIR), LG ($LANG), ED ($EXEDIR). +User definable: name starting from character, up to 28 letters long. + +Stacks. Two stacks are supported: NSIS stack and plugin's own stack. I see no +reasons for using plugin stack, but if you will, remember - the plugin stores +variables used at function to that stack before function execution, and restores +after execution. Even less I recommend you to use NSIS stack. You should use it +only for input/output. +How to use? It's variable styled. Plugins stack is associated with S variable, +and NSIS stack associated with NS variable. To push to stack just do "S=0" or +"NS=0", to pop from stack "a=S" or "b=NS". Combined operations supported too: +"S += 1.5" will increment value at the top of stack by 1.5. + +Supported types: int (in fact that is __int64), float (double in fact), +string. +Int: just numbers, may include sign. +Float: -123.456, 123.456e-78, 123e-45 +String: something in quotes ("", '', ``). + +There is also an array type. It is actually a reference type, so if b is array +and you will perform "a=b", the a and b will reference a single array. +To create a copy of array, use ca func: dest = ca(source). Btw - you couldn't +control dimensions of arrays - they are autosized. +To declare array: +a = {}; +To declare array and initialize some items with values: +{"Hello!", "Use", "mixed types", 1.01e23, "like that" ,1234}; +To access array: +a[index] = "Cool"; + +Also [] operation could be used to strings. str[x] gives you a single char with +index x (zero-based) as new string. str[-x] - the same, but x counts from the +string end (so the last char is -1). str[x,y] gives you characters in range x-y +(inclusive), both x and y could be <0 - in this case they counted from the end +of the string. + +The function could be useful - is conversion of arrays to strings and back. +Example: +a = a("Hello"); str = s(a); +After running such script array a will contain 6 integers (chars and last zero +- end of string), and str will contain your string back. + +Operators (some binary, some unary): +>>= <<= -= += /= *= |= &= ^= %= -- ++ >> << && || <= =< >= => != == += + - * / % < > & | ^ ~ ! +Only some are applicable to float (logic & arithmetic) and string (+ and logic) +of course. +Additional case: reference/de-reference operators (& and *). & will +give you the reference to argument which should be a variable (NSIS, user, array +item, stack), and * will convert it back to original variable. For example +(a=&b; *a=10) will set b to 10. Expression (*&a) is equal to simple (a). + +Script is set of expressions (mathematical in general) delimited with ';'. +Processing is mathematically right (2+2*2 will give 6), operations are performed +in a C like order (precedence). + +Flow control: + if-then-else like: #[if-expression, then-expr, else-expr] + example: + #[a==0, b=1; c=2, b *= (--c); c/=10] + C eq: + if (a==0) { b=1; c=2;} else { b*=(c++);c-=10; } + while (expr) do; like #{expr, do} + example: + #{(c<1.1e25)&&(b < 10), b++; c*=1.23} + C eq: + while ((c<1.1e25)&&(b<10)) { b++; c*=1.23; } + +WATCH OUT! Comma (,) separates if-expr, then-expr, and else-expr from each +other. All sub-expressions separated by (;) are the part of one expression, +and the result of the last one of these sub-exprs gives you the result of +expression. + +All the shit (like variables and functions) will be saved between calls. + +Functions: + type conversions: + l(string) returns the length of string or array argument + s(source) converts source to string type + i(source) converts source to int type + f(source) converts source to float type + c(source) if source is string, returns int value of first + char, if source is int, returns string which consists + of a single char (source) (+0 terminator). + a(source) converts source to array (only string supported) + ff(float, format) converts float to string, with format + options. + options = precision + flags. + Precision shows how many digits after decimal point + will be shown. Flags: + 16 (or 0x10) - No Exponential View + (number will be shown as 123.123) + 32 (or 0x20) - Only exponential view + (number will be shown as 123.12e123) + 64 (or 0x40) - use 'E' character instead of 'e' + By default the plugin decides itself how to show your + number. + + math (description of all these functions is available at MSDN, use the + second given name for search): + sin(x), sin Sine of argument + cos(x), cos Cosine of argument + cel(x), ceil Ceil of argument (no fract. part) + csh(x), cosh Hyperbolic Cosine of Argument + exp(x), exp Exponential + abs(x), abs Absolute value (warning: float) + flr(x), floor Floor of argument (no fract. part) + asn(x), asin ArcSine of argument + acs(x), acos ArcCosine of argument + atn(x), atan ArcTangent of argument + ln(x), log Exponential Logarithm + log(x), log10 Decimal logarithm + snh(x), sinh Hyperbolic Sine of Argument + sqt(x), sqrt Square root of argument + tan(x), tan Tangent of argument + tnh(x), tanh Hyperbolic tangent of argument + + functions taking two arguments + at2(x, y) atan2 Arctangent of the value (y/x) + pow(x, y) pow power, x^y + fmd(x, y) fmod floating point remainder + fex(x, o) frexp Gets the mantissa (result = r) + and exponent (o) of floating-point + number (x): x = r*(2^o) + mdf(x, o) modf Splits a floating-point value into + fractional and integer parts. + +User-defined functions. +It's very simple. Example: + test(a,b) (a+b); +After that test(1,2) will give you 3. + test2(a,b) (a=a+b; b *= a); +The result of function is always the result of last expression. +As said before it better not to use stack (S) in between function calls. +It will be better to develop variable-safe functions, i.e. functions which will +not corrupt variables. For this you should either push/pop them to stack, or +declare as additional arguments, which will never be used. Example: + test3(a,b,c) (c=10; #{--c > 0, a=sqrt(a*b)}; a) +No matter how many arguments will be passed to function, the values of all three +vars (a,b,c) will be saved. +Such variable-safe functions could be recursive: + Math::Script 'rec(a) (#[a > 0, rec(a-1), 0]+a);' + Math::Script 'R1 = rec(10)' +will set R1 to right result 55. +Sometimes functions will need to return more than one value, in this case you +could declare argument as referent (b at example): + test4(a, &b) (*b = a*a; a*a*a) +In this case test4 will return a^3, and if we will call it like that test4(a,c), +it will place a^2 to c. BUT! Note: you should use de-referencer (*) with variable, +at example *b. CAUTION: never use the same variable as function internal reference +variable and external argument variable (for example test4(a,b)). It will surely +fail. Also: if you declared argument as reference - you should never supply +a constant expression to it. It could be either array item (array[1]), NSIS +register R0, any of the user variables (beside the variable with the same name:), +but never the constant. + +Another may-be-useful possibility is to redeclare the function (the usual +declaration at the time when function already defined will simply call that +function). For such task you could use "#name", like "func()(1); #func()(2);". +But beware, function declaration occurs at time of parsing, so it's not possible +to perform flow controlled declaration. +SUCH IS NOT POSSIBLE: "#[a<0, #func()(1), #func()(2)]" +IT WILL SIMPLY DEFINE #func as (2), as the latest variant. + +(c) Nik Medved (brainsucker) \ No newline at end of file diff --git a/installer/NSIS/Docs/Modern UI 2/License.txt b/installer/NSIS/Docs/Modern UI 2/License.txt new file mode 100644 index 0000000..d3a22b9 --- /dev/null +++ b/installer/NSIS/Docs/Modern UI 2/License.txt @@ -0,0 +1,10 @@ +Copyright 2002-2009 Joost Verburg + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any distribution. \ No newline at end of file diff --git a/installer/NSIS/Docs/Modern UI 2/Readme.html b/installer/NSIS/Docs/Modern UI 2/Readme.html new file mode 100644 index 0000000..c4b241c --- /dev/null +++ b/installer/NSIS/Docs/Modern UI 2/Readme.html @@ -0,0 +1,1157 @@ + + + + NSIS Modern User Interface - Documentation + + + + + + + + + + + +
    + +

    + [Expand all]   [Collapse + all]

    +
    +

    + Introduction

    +
    +

    + The Modern UI provides a user interface for NSIS installers with a modern wizard + style, similar to the wizards of recent Windows versions. It is based on the basic + user interface that is provided by the NSIS compiler itself and extends it with + more interface features and pages.

    +

    + All standard NSIS pages (such as the pages to select components and the installation + folder) are supported as well as a number of additional pages. The welcome page + allows you to provide an introduction to the installation process, while the finish + page provides a way to let the user decide what steps should be performed after + the setup wizard is closed (for example, whether the application should be started + immediately). A finish page can also be used to ask for a system restart is necessary.

    +

    + Open/Close section + Screenshots

    +
    +

    +

    +

    +

    +
    +
    +

    + Modern UI 2.0

    +
    +

    + This new version makes it easier to customize pages, because the same method can + be used to  change standard NSIS pages as well as additional pages provided + by the Modern UI. It is now also possible for other NSIS plug-ins to add new pages + to the Modern UI. You can expect to see examples of this soon.

    +

    + The welcome and finish page are no longer implemented using InstallOptions. Instead, + the new nsDialogs plug-in is used. nsDialogs allows you to create custom pages or + customize existing pages directly from the script.

    +

    + To upgrade a Modern UI 1.8 script, you should:

    +
      +
    • Insert the MUI2.nsh header file instead of MUI.nsh.
    • +
    • The macros for InstallOptions have been moved to a separate header file unrelated + to the Modern UI. If you are still using InstallOptions for custom pages, insert + InstallOptions.nsh and use the INSTALLOPTIONS_* macros instead of the MUI_INSTALLOPTIONS_* + macros. The macros themselves have remained the same.
    • +
    • Rewrite customization code for the Modern UI 1.8 welcome and finish pages in which + the InstallOptions INI file is used. nsDialogs commands should be used instead.
    • +
    • Use the standard NSIS method to escape special characters in all texts. For example, + $\r$\n creates newline.
    • +
    +
    +

    + Script header

    +
    +

    + The settings for the Modern UI should be inserted in the header of the script file. + It's important to follow the same order as the items below. For example, + interface settings should be defined before you insert pages, because the pages + depend on the interface configuration. It may be useful to look at the + example scripts too see how this is done in actual script files.

    +

    + Parameters are given in this format: required (option1 | option2) + [optional]

    +
    +

    + 1. Header file

    +
    +

    + First of all, add this line to the top of script to include the Modern UI:

    +
    +!include MUI2.nsh
    +
    +
    +

    + 2. Interface configuration

    +
    +

    + Then, you may want to use interface settings to change the look and feel of the + installer. These settings apply to all pages.

    + The interface settings provided by the NSIS compiler itself (such as LicenseText, + Icon, CheckBitmap, InstallColors) should not be used in Modern UI scripts. The Modern + UI provides equalivent or extended versions of these settings.

    + Examples:

    +
    +!define MUI_COMPONENTSPAGE_SMALLDESC ;No value
    +!define MUI_UI "myUI.exe" ;Value
    +!define MUI_INSTFILESPAGE_COLORS "FFFFFF 000000" ;Two colors
    +
    +

    + Open/Close section + Interface settings

    +
    +

    + Open/Close section + Page header

    +
    +

    + MUI_ICON icon_file
    + The icon for the installer.
    + Default: ${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico

    +

    + MUI_UNICON icon_file
    + The icon for the uninstaller.
    + Default: ${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico

    +

    + MUI_HEADERIMAGE
    + Display an image on the header of the page.

    +
    +

    + MUI_HEADERIMAGE_BITMAP bmp_file
    + Bitmap image to display on the header of installers pages (recommended size: 150x57 + pixels).
    + Default: ${NSISDIR}\Contrib\Graphics\Header\nsis.bmp

    +
    +

    + MUI_HEADERIMAGE_BITMAP_NOSTRETCH
    + Do not stretch the installer header bitmap to fit the size of the field. Use this + option only if you have an image that does not use the whole space. If you have + a full size bitmap that fits exactly, you should not use this option because the + size of the field will be different if the user has a custom DPI setting.

    +

    + MUI_HEADERIMAGE_BITMAP_RTL bmp_file
    + Bitmap image to display on the header of installers pages when using a RTL language + (recommended size: 150x57 pixels).
    + Default: Non-RTL bitmap

    +
    +

    + MUI_HEADERIMAGE_BITMAP_RTL_NOSTRETCH
    + Do not stretch the installer header bitmap when using a RTL language to fit the + size of the field. Use this option only if you have an image that does not use the + whole space. If you have a full size bitmap that fits exactly, you should not use + this option because the size of the field will be different if the user has a custom + DPI setting.

    +
    +
    +

    + MUI_HEADERIMAGE_UNBITMAP bmp_file
    + Bitmap image to display on the header of uninstaller pages (recommended size: 150x57 + pixels).
    + Default: Installer header bitmap

    +
    +

    + MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH
    + Do not stretch the uninstaller header bitmap to fit the size of the field. Use this + option only if you have an image that does not use the whole space. If you have + a full size bitmap that fits exactly, you should not use this option because the + size of the field will be different if the user has a custom DPI setting.

    +

    + MUI_HEADERIMAGE_UNBITMAP_RTL bmp_file
    + Bitmap image to display on the header of uninstallers pages when using a RTL language + (recommended size: 150x57 pixels).
    + Default: Installer RTL header bitmap

    +
    +

    + MUI_HEADERIMAGE_UNBITMAP_RTL_NOSTRETCH
    + Do not stretch the uninstaller header bitmap when using a RTL language to fit the + size of the field. Use this option only if you have an image that does not use the + whole space. If you have a full size bitmap that fits exactly, you should not use + this option because the size of the field will be different if the user has a custom + DPI setting.

    +
    +
    +

    + MUI_HEADERIMAGE_RIGHT
    + Display the header image on the right side instead of the left side (when using + a RTL language it will be displayed on the left side instead of the right side).

    +
    +

    + MUI_BGCOLOR (color: RRGGBBR hexadecimal)
    + Background color for the header, the Welcome page and the Finish page.
    + Default: FFFFFF

    +

    + MUI_HEADER_TRANSPARENT_TEXT
    + Set a transparent background for the header's label controls. Useful for custom + user interfaces that set a bigger header image.

    +
    +

    + Open/Close section + Interface resources

    +
    +

    + MUI_UI ui_file
    + The interface file with the dialog resources. Change this if you have made your + own customized UI.
    + Default: ${NSISDIR}\Contrib\UIs\modern.exe

    +

    + MUI_UI_HEADERIMAGE ui_file
    + The interface files with the dialog resource IDD_INST that contains a bitmap control + and space for the header bitmap.
    + Default: ${NSISDIR}\Contrib\UIs\modern_headerbmp.exe

    +

    + MUI_UI_HEADERIMAGE_RIGHT ui_file
    + The interface files with the dialog resource IDD_INST that contains a bitmap control + and space for the header bitmap on the right side.
    + Default: ${NSISDIR}\Contrib\UIs\modern_headerbmpr.exe

    +

    + MUI_UI_COMPONENTSPAGE_SMALLDESC ui_file
    + The interface files with a customized dialog resource IDD_SELCOM with a small description + area.
    + Default: ${NSISDIR}\Contrib\UIs\modern_smalldesc.exe

    +

    + MUI_UI_COMPONENTSPAGE_NODESC ui_file
    + The interface files with a customized dialog resource IDD_SELCOM without a description + area.
    + Default: ${NSISDIR}\Contrib\UIs\modern_nodesc.exe

    +
    +

    + Open/Close section + Installer welcome/finish page

    +
    +

    + MUI_WELCOMEFINISHPAGE_BITMAP bmp_file
    + Bitmap for the Welcome page and the Finish page (recommended size: 164x314 pixels).
    + Default: ${NSISDIR}\Contrib\Graphics\Wizard\win.bmp

    +
    +

    + MUI_WELCOMEFINISHPAGE_BITMAP_NOSTRETCH
    + Do not stretch the bitmap for the Welcome and Finish page to fit the size of the + field. Use this option only if you have an image that does not use the whole space. + If you have a full size bitmap that fits exactly, you should not use this option + because the size of the field will be different if the user has a custom DPI setting.

    +
    +
    +

    + Open/Close section + Uninstaller welcome/finish page

    +
    +

    + MUI_UNWELCOMEFINISHPAGE_BITMAP bmp_file
    + Bitmap for the Welcome page and the Finish page (recommended size: 164x314 pixels).
    + Default: ${NSISDIR}\Contrib\Graphics\Wizard\win.bmp

    +
    +

    + MUI_UNWELCOMEFINISHPAGE_BITMAP_NOSTRETCH
    + Do not stretch the bitmap for the Welcome and Finish page to fit the size of the + field. Use this option only if you have an image that does not use the whole space. + If you have a full size bitmap that fits exactly, you should not use this option + because the size of the field will be different if the user has a custom DPI setting.

    +
    +
    +

    + Open/Close section + License page

    +
    +

    + MUI_LICENSEPAGE_BGCOLOR (/windows | /grey | + (color: RRGGBB hexadecimal))
    + The background color for the license textbox. Use /windows for the Windows text + background color (usually white). Use the /grey for the window background color + (usually grey).
    + Default: /windows

    +
    +

    + Open/Close section + Components page

    +
    +

    + MUI_COMPONENTSPAGE_CHECKBITMAP bitmap_file
    + The bitmap with images for the checks of the component select treeview.
    + Default: ${NSISDIR}\Contrib\Graphics\Checks\modern.bmp

    +

    + MUI_COMPONENTSPAGE_SMALLDESC
    + A small description area on the bottom of the page. Use this layout if you have + a lot of sections and don't need large descriptions.

    +

    + MUI_COMPONENTSPAGE_NODESC
    + No description area.

    +
    +

    + Open/Close section + Directory page

    +
    +

    + MUI_DIRECTORYPAGE_BGCOLOR (color: RRGGBB hexadecimal)
    + The background color for the directory textbox.

    +
    +

    + Open/Close section + Start Menu folder page

    +
    +

    + MUI_STARTMENUPAGE_BGCOLOR (color: RRGGBB hexadecimal)
    + The background color for the startmenu directory list and textbox.

    +
    +

    + Open/Close section + Installation page

    +
    +

    + MUI_INSTFILESPAGE_COLORS (/windows | "(foreground + color: RRGGBB hexadecimal) (background color: RRGGBB hexadecimal)")
    + The colors of the details screen. Use /windows for the default Windows colors.
    + Default: /windows

    +

    + MUI_INSTFILESPAGE_PROGRESSBAR ("" + | colored | smooth)
    + The style of the progress bar. Colored makes it use the MUI_INSTALLCOLORS.
    + Default: smooth

    +
    +

    + Open/Close section + Installer finish page

    +
    +

    + MUI_FINISHPAGE_NOAUTOCLOSE
    + Do not automatically jump to the finish page, to allow the user to check the install + log.

    +
    +

    + Open/Close section + Uninstaller finish page

    +
    +

    + MUI_UNFINISHPAGE_NOAUTOCLOSE
    + Do not automatically jump to the finish page, to allow the user to check the uninstall + log.

    +
    +

    + Open/Close section + Abort warning

    +
    +

    + MUI_ABORTWARNING
    + Show a message box with a warning when the user wants to close the installer.

    +
    +

    + MUI_ABORTWARNING_TEXT text
    + Text to display on the abort warning message box.

    +

    + MUI_ABORTWARNING_CANCEL_DEFAULT
    + Set the Cancel button as the default button on the message box.

    +
    +
    +

    + Open/Close section + Uninstaller abort warning

    +
    +

    + MUI_UNABORTWARNING
    + Show a message box with a warning when the user wants to close the uninstaller.

    +
    +

    + MUI_UNABORTWARNING_TEXT text
    + Text to display on the abort warning message box.

    +

    + MUI_UNABORTWARNING_CANCEL_DEFAULT
    + Set the Cancel button as the default button on the message box.

    +
    +
    +
    +
    +

    + 3. Pages

    +
    +

    + Insert the following macros to set the pages you want to use. The pages will appear + in the order in which you insert them in the script. You can also insert custom + Page commands between the macros to add custom pages.

    +

    + You can add multiple pages of certain types (for example, if you want the user to + specify multiple folders).

    +

    + Examples:

    +
    +!insertmacro MUI_PAGE_LICENSE "License.rtf"
    +!insertmacro MUI_PAGE_COMPONENTS
    +
    +Var StartMenuFolder
    +!insertmacro MUI_PAGE_STARTMENU "Application" $StartMenuFolder
    +
    +

    + You will need the page ID for the Start Menu folder page when using the Start Menu + folder macros. The folder will be stored in the specified variable.

    +

    + Installer pages
    + MUI_PAGE_WELCOME
    + MUI_PAGE_LICENSE textfile
    + MUI_PAGE_COMPONENTS
    + MUI_PAGE_DIRECTORY
    + MUI_PAGE_STARTMENU pageid variable
    + MUI_PAGE_INSTFILES
    + MUI_PAGE_FINISH

    +

    + Uninstaller pages
    + MUI_UNPAGE_WELCOME
    + MUI_UNPAGE_CONFIRM
    + MUI_UNPAGE_LICENSE textfile
    + MUI_UNPAGE_COMPONENTS
    + MUI_UNPAGE_DIRECTORY
    + MUI_UNPAGE_INSTFILES
    + MUI_UNPAGE_FINISH

    +

    + Open/Close section + Page settings

    +
    +

    + Page settings apply to a single page and should be set before inserting a page macro. + The same settings can be used for installer and uninstaller pages. You have to repeat + the setting if you want it to apply to multiple pages. Example:

    +
    +;Add a directory page to let the user specify a plug-ins folder
    +;Store the folder in $PluginsFolder
    +
    +Var PLUGINS_FOLDER
    +!define MUI_DIRECTORYPAGE_VARIABLE $PluginsFolder
    +!insertmacro MUI_PAGE_DIRECTORY
    +
    +

    +

    + All standard texts in the user interface are loaded from language files, which are + available for all languages supported by NSIS. So you only need to define these + texts if you want to change the default.

    +

    + If the parameter is a text that should be different for each language, define a + language string using LangString and use $(LangStringName) as value. For a license + text in multiple languages, LicenseLangString can be used. Refer the NSIS Users + Manual for more information about installers with multiple languages.

    +

    + In all text settings, the doublequote character (") should be escaped in the + following form: $\"

    +

    + Open/Close section + General page settings

    +
    +

    + MUI_PAGE_HEADER_TEXT text
    + Text to display on the header of the page.

    +

    + MUI_PAGE_HEADER_SUBTEXT text
    + Subtext to display on the header of the page.

    +
    +

    + Open/Close section + Welcome page settings

    +
    +

    + MUI_WELCOMEPAGE_TITLE title
    + Title to display on the top of the page.

    +

    + MUI_WELCOMEPAGE_TITLE_3LINES
    + Extra space for the title area.

    +

    + MUI_WELCOMEPAGE_TEXT text
    + Text to display on the page.

    +
    +

    + Open/Close section + License page settings

    +
    +

    + MUI_LICENSEPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_LICENSEPAGE_TEXT_BOTTOM text
    + Text to display on the bottom of the page.

    +

    + MUI_LICENSEPAGE_BUTTON button_text
    + Text to display on the 'I Agree' button.

    +

    + MUI_LICENSEPAGE_CHECKBOX
    + Display a checkbox the user has to check to agree with the license terms.

    +
    +

    + MUI_LICENSEPAGE_CHECKBOX_TEXT text
    + Text to display next to the checkbox to agree with the license terms.

    +
    +

    + MUI_LICENSEPAGE_RADIOBUTTONS
    + Display two radio buttons to allow the user to choose between accepting the license + terms or not.

    +
    +

    + MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT text
    + Text to display next to the checkbox to accept the license terms.

    +

    + MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE text
    + Text to display next to the checkbox to decline the license terms.

    +
    +
    +

    + Open/Close section + Components page settings

    +
    +

    + MUI_COMPONENTSPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_COMPONENTSPAGE_TEXT_COMPLIST text
    + Text to display on next to the components list.

    +

    + MUI_COMPONENTSPAGE_TEXT_INSTTYPE text
    + Text to display on next to the installation type combo box.

    +

    + MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE text
    + Text to display on the of the top of the description box.

    +

    + MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO text
    + Text to display inside the description box when no section is selected.

    +
    +

    + Open/Close section + Directory page settings

    +
    +

    + MUI_DIRECTORYPAGE_TEXT_TOP text
    + Text to display on top of the page.

    +

    + MUI_DIRECTORYPAGE_TEXT_DESTINATION text
    + Text to display on the destination folder frame.

    +

    + MUI_DIRECTORYPAGE_VARIABLE variable
    + Variable in which to store the selected folder.
    + Default: $INSTDIR

    +

    + MUI_DIRECTORYPAGE_VERIFYONLEAVE
    + Does not disable the Next button when a folder is invalid but allows you to use + GetInstDirError in the leave function to handle an invalid folder.

    +
    +

    + Open/Close section + Start Menu folder page settings

    +
    +

    + MUI_STARTMENUPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_STARTMENUPAGE_TEXT_CHECKBOX text
    + Text to display next to the checkbox to disable the Start Menu folder creation.

    +

    + MUI_STARTMENUPAGE_DEFAULTFOLDER folder
    + The default Start Menu Folder.

    +

    + MUI_STARTMENUPAGE_NODISABLE
    + Do not display the checkbox to disable the creation of Start Menu shortcuts.

    +

    + MUI_STARTMENUPAGE_REGISTRY_ROOT root
    + MUI_STARTMENUPAGE_REGISTRY_KEY key
    + MUI_STARTMENUPAGE_REGISTRY_VALUENAME value_name
    + The registry key to store the Start Menu folder. The page will use it to remember + the users preference. You should also use for the uninstaller to remove the Start + Menu folders. Don't forget to remove this key during uninstallation.

    +

    + For the uninstaller, use the MUI_STARTMENU_GETFOLDER macro to get the Start Menu + folder:

    +
    +!insertmacro MUI_STARTMENU_GETFOLDER page_id $R0
    +  Delete "$SMPROGRAMS\$R0\Your Shortcut.lnk"
    +
    +
    +

    + Open/Close section + Installation page settings

    +
    +

    + MUI_INSTFILESPAGE_FINISHHEADER_TEXT text
    + Text to display on the header of the installation page when the installation has + been completed (won't be displayed when using a Finish page without MUI_(UN)FINISHPAGE_NOAUTOCLOSE).

    +

    + MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT text
    + Subtext to display on the header of the installation page when the installation + has been completed (won't be displayed when using a Finish page without MUI_(UN)FINISHPAGE_NOAUTOCLOSE).

    +

    + MUI_INSTFILESPAGE_ABORTHEADER_TEXT text
    + Text to display on the header of the installation page when the installation has + been aborted.

    +

    + MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT text
    + Subext to display on the header of the installation page when the installation has + been aborted.

    +
    +

    + Open/Close section + Finish page settings

    +
    +

    + MUI_FINISHPAGE_TITLE title
    + Title to display on the top of the page.

    +

    + MUI_FINISHPAGE_TITLE_3LINES
    + Extra space for the title area.

    +

    + MUI_FINISHPAGE_TEXT text
    + Text to display on the page.

    +

    + MUI_FINISHPAGE_TEXT_LARGE
    + Extra space for the text area (if using checkboxes).

    +

    + MUI_FINISHPAGE_BUTTON text
    + Text to display on the Finish button.

    +

    + MUI_FINISHPAGE_CANCEL_ENABLED
    + Enable the Cancel button so the user can skip any options displayed on the finish + page.

    +

    + MUI_FINISHPAGE_TEXT_REBOOT text
    + Text to display on the finish page when asking for a system reboot.

    +

    + MUI_FINISHPAGE_TEXT_REBOOTNOW text
    + Text to display next to the 'Reboot now' option button.

    +

    + MUI_FINISHPAGE_TEXT_REBOOTLATER text
    + Text to display next to the 'Reboot later' option button.

    +

    + MUI_FINISHPAGE_REBOOTLATER_DEFAULT
    + Sets the 'Reboot later' option as the default option.

    +

    + MUI_FINISHPAGE_RUN exe_file
    + Application which the user can select to run using a checkbox. You don't need to + put quotes around the filename when it contains spaces.

    +
    +

    + MUI_FINISHPAGE_RUN_TEXT text
    + Texts to display next to the 'Run program' checkbox.

    +

    + MUI_FINISHPAGE_RUN_PARAMETERS parameters
    + Parameters for the application to run. Don't forget to escape double quotes in the + value (use $\").

    +

    + MUI_FINISHPAGE_RUN_NOTCHECKED
    + Do not check the 'Run program' checkbox by default

    +

    + MUI_FINISHPAGE_RUN_FUNCTION function
    + Call a function instead of executing an application (define MUI_FINISHPAGE_RUN without + parameters). You can use the function to execute multiple applications or you can + change the checkbox name and use it for other things.

    +
    +

    + MUI_FINISHPAGE_SHOWREADME file/url
    + File or website which the user can select to view using a checkbox. You don't need + to put quotes around the filename when it contains spaces.

    +
    +

    + MUI_FINISHPAGE_SHOWREADME_TEXT text
    + Texts to display next to the 'Show Readme' checkbox.

    +

    + MUI_FINISHPAGE_SHOWREADME_NOTCHECKED
    + Do not check the 'Show Readme' checkbox by default

    +

    + MUI_FINISHPAGE_SHOWREADME_FUNCTION function
    + Call a function instead of showing a file (define MUI_FINISHPAGE_SHOWREADME without + parameters). You can use the function to show multiple files or you can change the + checkbox name and use it for other things.

    +
    +

    + MUI_FINISHPAGE_LINK link_text
    + Text for a link on the which the user can click to view a website or file.

    +
    +

    + MUI_FINISHPAGE_LINK_LOCATION file/url
    + Website or file which the user can select to view using the link. You don't need + to put quotes around the filename when it contains spaces.

    +

    + MUI_FINISHPAGE_LINK_COLOR (color: RRGGBB hexadecimal)
    + Text color for the link on the Finish page.
    + Default: 000080

    +
    +

    + MUI_FINISHPAGE_NOREBOOTSUPPORT
    + Disables support for the page that allows the user to reboot the system. Define + this option to save some space if you are not using the /REBOOTOK flag or SetRebootFlag.

    +
    +

    + Open/Close section + Uninstall confirm page settings

    +
    +

    + MUI_UNCONFIRMPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_UNCONFIRMPAGE_TEXT_LOCATION text
    + Text to display next to the uninstall location text box.

    +
    +
    +
    +

    + 4. Language files

    +
    +

    + Insert the Modern UI language files for the languages to want to include.

    +
    +!insertmacro MUI_LANGUAGE "English"
    +
    +

    + The standard NSIS language files are loaded automatically, there is no need to use + LoadLanguageFile.

    +
    +

    + 5. Reserve files

    +
    +

    + If you are using solid compression, files that are required before the actual installation + should be stored first in the data block, because this will make your installer + start faster. Include reserve file commands for such files before your sections + and functions:

    +
    +ReserveFile MyPlugin.dll
    +!insertmacro MUI_RESERVEFILE_LANGDLL ;Language selection dialog
    +...
    +
    +
    +

    + Script code for pages

    +
    +

    + Some pages allow you to show additional information or can be used to get user input. + Here you can find the script code to use these features.

    +

    + Components page descriptions

    +
    +

    + The Modern UI components page has a text box in which a description can be shown + when the user hovers the mouse over a component. If you don't want to use these + descriptions, insert the MUI_COMPONENTSPAGE_NODESC interface setting.

    +

    + To set a description for a section, an additional parameter needs to be added to + Section commmand with a unique identifier for the section. This name can later be + used to set the description for this section.

    +
    +Section "Section Name 1" Section1
    +   ...
    +SectionEnd
    +
    +

    + After the sections, use these macros to set the descriptions:

    +
    +LangString DESC_Section1 ${LANG_ENGLISH} "Description of section 1."
    +LangString DESC_Section2 ${LANG_ENGLISH} "Description of section 2."
    +
    +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
    +  !insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
    +  !insertmacro MUI_DESCRIPTION_TEXT ${Section2} $(DESC_Section2)
    +!insertmacro MUI_FUNCTION_DESCRIPTION_END
    +
    +

    + For the uninstaller, use the MUI_UNFUNCTION_DESCRIPTION_BEGIN and MUI_UNFUNCTION_DESCRIPTION_END + macros.

    +
    +

    + Start Menu folder

    +
    +

    + Put the code to write the shortcuts (using CreateShortcut) between the MUI_STARTMENU_WRITE_BEGIN + and MUI_STARTMENU_WRITE_END macros:

    +
    +!insertmacro MUI_STARTMENU_WRITE_BEGIN pageid
    +  ...create shortcuts...
    +!insertmacro MUI_STARTMENU_WRITE_END
    +
    +

    + The page ID should be the ID of the page on which the user has selected the folder + for the shortcuts you want to write.

    +

    + The variable which contains the folder and the page ID are set as parameters of + the page macro.

    +
    +
    +

    + Language selection dialog

    +
    +

    + If you want the installer to display a language selection dialog (see the the MultiLanguage.nsi example), + insert the MUI_LANGDLL_DISPLAY macro in the .onInit function:

    +
    +Function .onInit
    +
    +  !insertmacro MUI_LANGDLL_DISPLAY
    +
    +FunctionEnd
    +
    +

    + This macro can also be used in the un.onInit function.

    +

    + Open/Close section + Settings for registry storage of selected language

    +
    +

    + To remember the user's preference, you can define a registry key. These defines + should be set before inserting the installation page macro.

    +

    + MUI_LANGDLL_REGISTRY_ROOT root
    + MUI_LANGDLL_REGISTRY_KEY key
    + MUI_LANGDLL_REGISTRY_VALUENAME value_name
    + The registry key to store the language. The users preference will be remembered. + You can also use it for the uninstaller to display the right language. Don't forget + to remove this key in the uninstaller.

    +

    + For the uninstaller, insert the MUI_UNGETLANGUAGE macro in un.onInit to get the + stored language preference:

    +
    +Function un.onInit
    +
    +  !insertmacro MUI_UNGETLANGUAGE
    +
    +FunctionEnd
    +
    +
    +

    + Open/Close section + Interface settings for selection dialog

    +
    +

    + To customize the language selection dialog interface, use these defines before inserting + the MUI_LANGDLL_DISPLAY macro.

    +

    + MUI_LANGDLL_WINDOWTITLE text
    + The window title of the language selection dialog.

    +

    + MUI_LANGDLL_INFO text
    + The text to display on the language selection dialog.

    +

    + MUI_LANGDLL_ALWAYSSHOW
    + Always show the language selection dialog, even if a language has been stored in + the registry. The language stored in the registry will be selected by default.

    +

    + MUI_LANGDLL_ALLLANGUAGES
    + Always show all available languages and don't filter according to their codepage.

    +
    +
    +

    + Custom pages

    +
    +

    + If you want add your custom pages to your installer, you can insert your own page + commands between the page macros.

    +
    +!insertmacro MUI_PAGE_WELCOME
    +Page custom FunctionName ;Custom page
    +!insertmacro MUI_PAGE_COMPONENTS
    + 
    +;Uninstaller
    +!insertmacro MUI_UNPAGE_CONFIRM
    +UninstPage custom un.FunctionName ;Custom page
    +!insertmacro MUI_UNPAGE_INSTFILES
    +
    +

    + Use the MUI_HEADER_TEXT macro to set the text on the page header in a page function:

    +
    +LangString PAGE_TITLE ${LANG_ENGLISH} "Title"
    +LangString PAGE_SUBTITLE ${LANG_ENGLISH} "Subtitle"
    +
    +Function CustomPageFunction
    +  !insermacro MUI_HEADER_TEXT $(PAGE_TITLE) $(PAGE_SUBTITLE)
    +  ...
    +FuctionEnd
    +
    +
    +

    + Custom functions

    +
    +

    + Interface functions provided by NSIS, like the .onGUIInit function and the page + functions are automatically included by the Modern UI and filled with code to support + new interface features. If you want to add additional code to these function, create + a function with the custom script code in the script use the Modern UI functions + call them.

    +

    + Example:

    +
    +!define MUI_CUSTOMFUNCTION_GUIINIT myGuiInit
    +
    +Function myGUIInit
    +  ...
    +FunctionEnd
    +

    + Modern UI pages can also customized using custom functions.

    +

    + Open/Close section + General Custom Functions

    +
    +

    + These defines should be set before inserting the language macros.

    +

    + MUI_CUSTOMFUNCTION_GUIINIT function
    + MUI_CUSTOMFUNCTION_UNGUIINIT function
    + MUI_CUSTOMFUNCTION_ABORT function
    + MUI_CUSTOMFUNCTION_UNABORT function
    + MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION function
    + MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION function

    +

    + Mouse over functions are only available when the description macros + (MUI_FUNCTION_DESCRIPTION_BEGIN) are used. When component page descriptions are not + used, regular .onMouseOverSection and un.onMouseOverSection must be used.

    +
    +

    + Open/Close section + Page Custom Functions

    +
    +

    + These defines should be set before inserting a page macro.

    +

    + MUI_PAGE_CUSTOMFUNCTION_PRE function
    + MUI_PAGE_CUSTOMFUNCTION_SHOW function
    + MUI_PAGE_CUSTOMFUNCTION_LEAVE function
    +

    +

    + The pre function is called first and allows you to initalize variables or decide + whether the page should be skipped. Then, the show function is called, which can + be used to customize the interface. Finally, the user input can be validated in the + leave function. The NSIS Users Manual provides more information about these functions.

    +

    + In the show function, the window handles of all controls on the page can be retrieved + from a Modern UI variable. A list of the variables names is not yet available. For + now, refer to the source files of the Modern UI 2.0. The variable declarations can + be found in the first lines of the header file for a certain page.

    +
    +
    +

    + Example scripts

    +
    +

    + Basic: Basic.nsi
    + Welcome/Finish page: WelcomeFinish.nsi
    + Multiple languages: MultiLanguage.nsi
    + Header image: HeaderBitmap.nsi
    + Start Menu Folder page: StartMenu.nsi

    +
    +

    + Credits

    +
    +

    + Written by Joost Verburg.
    + Icons designed by Nikos Adamamas, aka adni18.
    + Thanks to Amir Szekely, aka KiCHiK, for his work on NSIS to make this possible.

    +
    +

    + License

    +
    +

    + The zlib/libpng license applies to the Modern UI.

    +

    + Open/Close section + License Terms

    +
    +
    +Copyright © 2002-2009 Joost Verburg
    +
    +This software is provided 'as-is', without any express or implied
    +warranty. In no event will the authors be held liable for any damages
    +arising from the use of this software.
    +
    +Permission is granted to anyone to use this software for any purpose,
    +including commercial applications, and to alter it and redistribute
    +it freely, subject to the following restrictions:
    +
    +1. The origin of this software must not be misrepresented; 
    +   you must not claim that you wrote the original software.
    +   If you use this software in a product, an acknowledgment in the
    +   product documentation would be appreciated but is not required.
    +2. Altered versions must be plainly marked as such,
    +   and must not be misrepresented as being the original software.
    +3. This notice may not be removed or altered from any distribution.
    +
    +
    +
    +
    +
    + + diff --git a/installer/NSIS/Docs/Modern UI 2/images/closed.gif b/installer/NSIS/Docs/Modern UI 2/images/closed.gif new file mode 100644 index 0000000..b45054e Binary files /dev/null and b/installer/NSIS/Docs/Modern UI 2/images/closed.gif differ diff --git a/installer/NSIS/Docs/Modern UI 2/images/header.gif b/installer/NSIS/Docs/Modern UI 2/images/header.gif new file mode 100644 index 0000000..f8810d3 Binary files /dev/null and b/installer/NSIS/Docs/Modern UI 2/images/header.gif differ diff --git a/installer/NSIS/Docs/Modern UI 2/images/open.gif b/installer/NSIS/Docs/Modern UI 2/images/open.gif new file mode 100644 index 0000000..9fff60e Binary files /dev/null and b/installer/NSIS/Docs/Modern UI 2/images/open.gif differ diff --git a/installer/NSIS/Docs/Modern UI 2/images/screen1.png b/installer/NSIS/Docs/Modern UI 2/images/screen1.png new file mode 100644 index 0000000..0e25c0d Binary files /dev/null and b/installer/NSIS/Docs/Modern UI 2/images/screen1.png differ diff --git a/installer/NSIS/Docs/Modern UI 2/images/screen2.png b/installer/NSIS/Docs/Modern UI 2/images/screen2.png new file mode 100644 index 0000000..4fccd41 Binary files /dev/null and b/installer/NSIS/Docs/Modern UI 2/images/screen2.png differ diff --git a/installer/NSIS/Docs/Modern UI/Changelog.txt b/installer/NSIS/Docs/Modern UI/Changelog.txt new file mode 100644 index 0000000..673615c --- /dev/null +++ b/installer/NSIS/Docs/Modern UI/Changelog.txt @@ -0,0 +1,263 @@ +NSIS Modern User Interface +Version History + +1.8 - August 9, 2007 +* Uses the new language files +* Updated documentation +* Although MUI_DIRECTORYPAGE_BGCOLOR and MUI_STARTMENUPAGE_BGCOLOR are documented as interface + setttings that apply to every directory page or Start Menu folder page, they were actually + implemented as page specific settings. They have been changed to interface settings. +* MUI_LANGDLL_DISPLAY now also reads a previously saved language from the registry if the + installation is silent. +* InstallOptions macros have been moved to a separate header file (InstallOptions.nsh). + The MUI_INSTALLOPTIONS_* macros are still provided for backwards compatibility and insert the + equalivent INSTALLOPTIONS_* macros. +* Added MUI_CUSTOMFUNCTION_MOUSEOVERSECTION + +1.78 - June 8, 2007 +* Added MUI_STARTMENUPAGE_BGCOLOR +* Added MUI_DIRECTORYPAGE_BGCOLOR +* Added MUI_LANGDLL_ALLLANGUAGES + +1.77 - April 27, 2007 +* Added MUI_FINISHPAGE_CANCEL_ENABLED +* Added MUI_FINISHPAGE_REBOOTLATER_DEFAULT +* Block unsupported languages in the language selection dialog +* Cancel button no longer enabled by default on the finish page +* Reduced flicker caused by MUI_HEADER_TRANSPARENT_TEXT + +1.76 - September 23, 2006 +* Added MUI_ABORTWARNING_CANCEL_DEFAULT + +1.75 - April 1, 2006 +* Added show function for the start menu page +* Added MUI_HEADER_TRANSPARENT_TEXT for transparent header texts + +1.74 - September 4, 2005 +* Fixed compile error when checkboxes are used on multiple finish page pages + +1.73 - August 6, 2005 +* The checkboxes to run an application or show a Readme file can now also be used on an uninstaller + finish pages or multiple finish pages + +1.72 - November 27, 2004 +* Fixed state of Finish page Cancel button when both an installer and uninstaller page is included + +1.71 - October 14, 2004 +* The selected language is only stored in the registry when installation was successful + +1.70 - February 6, 2004 +* Improved documentation +* New Init custom function for Welcome and Finish page + +1.69 - January 7, 2004 +* All uninstaller pages work without installer pages +* Fixed top text on uninstaller license page + +1.68 - November 24, 2003 +* New settings for extra space for title and text on Welcome and Finish page. +* Improved handling of verbose settings. Define MUI_VERBOSE the set the Modern UI verbose level (1-4). +* Language file string for uninstaller reboot information +* Setting for folder validation in leave function +* Fixed finish page text settings for multiple pages + +1.67 - November 18, 2003 +* Support for uninstaller Welcome pages and Finish pages +* Improved and changed text settings +* ID for Start Menu Folder pages, easier to use multiple pages +* Renamed a few settings +* Default header image +* Support for uninstaller abort warning +* Setting for 3 line text on Welcome and Finish page + (NOTE: New settings have been introduced in version 1.68) +* Langauge file backwards compatibility: English for missing strings +* Support for different uninstaller header image +* Language selection dialog not displayed if installer is silent +* Cancel button on Finish page when there are options +* Full RTL support + +1.66 - October 7, 2003 +* New system for page settings and custom pages +* Support for uninstaller components page +* Support for multiple pages of the same type +* New position for interface settings +* Changed macro and setting names +* Updated langauge system, new language files and settings +* Removed MUI_BRANDINGTEXT. You can use the standard BrandingText instruction. +* Removed MUI_PRODUCT and MUI VERSION. You can use the standard Name instruction. + +1.65 - July 16, 2003 +* New page configuration system, no different system for installers with custom pages +* Default windows color for the license text background +* Example script updates (new format, user variables) +* Improved registry storage for Start Menu folder +* ReserveFile macro for StartMenu plug-in +* Option to always show the language selection dialog (even if a language has been stored in the registry) +* Checkboxes on Finish page can be used to call a function +* Support for custom leave functions for Start Menu Folder, Welcome and Finish pages +* Support for a link on the Finish page +* New macro to get Start Menu folder in uninstaller +* Options to disable bitmap stretching +* Components page description box info text: always displayed + when mouse is outside box, disabled style + +1.64 - April 27, 2003 +* Support for license page with checkbox or radiobuttons to let the user accept the agreement or not +* Macros for finish headers don't have to be inserted anymore +* Language preference stored when installation has completed, no problems anymore when the users selects the wrong language +* Header text for aborted installation +* New macros: get language for uninstaller, delete shortcuts +* Language specific fonts +* Welcome/Finish page INI files can be modified in pre functions +* More texts can be customized + +1.63 - March 9, 2003 +* Support for a bitmap in the wizard header +* New defines to change the components page interface +* MUI_SYSTEM inserted automatically +* Single macro for language selection dialog +* Removed page description in window title +* Easier to customize resource files +* New system for custom functions +* Start Menu folder registry key automatically written +* New InstallOptions macros that do not remove the return value from the stack +* Support for custom pages before the finish page +* Renamed Start Menu page defines +* 'Do not create shortcuts' checkbox can be removed +* 'MS Shell Dlg' font for header title +* RTL support +* Documentation updates +* Minor fixes + +1.62 - February 2, 2003 +* New language strings for Finish page +* Possibility to let a Modern UI Function call your own function +* No problems anymore when using both 'Run program' and 'Show Readme' on the Finish page +* Default state of checkboxes on the finish page can be changed +* Welcome / Finish page compatible with custom DPI settings +* Converted Install Options INI files to use dialog units +* More ReserveFile macros + (NOTE: Some of these macros have been removed in later versions) +* Background color can be changed with a define +* Support for multilingual branding texts +* Start Menu / Finish page window titles also work when using custom page commands +* Language files should be inserted after inserting the MUI_SYSTEM macro + (NOTE: The MUI_SYSTEM macro has been removed in version 1.63) +* Define MUI_MANUALVERBOSE if you don't want the Modern UI to change the verbose settings during compilation + (NOTE: This setting has been changed in version 1.68) + +1.61 - December 5, 2002 +* modern3.exe UI without description area + (NOTE: This setting has been changed in version 1.63) +* Added define to show uninstall confirm page +* Added language string for finish page title and continue to uninstall +* Define for parameters for the application to run on the finish page +* Minor fixes + +1.6 - November 18, 2002 +* Welcome / Finish page +* Automatic ask for reboot on finish page +* Create no shortcut option on the Start Menu Folder selection page +* Customizing GUIInit functions easier +* Minor font / UI changes + +1.5 - November 11, 2002 +* New language file format +* Language strings can be changed in the script without editing languagefiles +* Start Menu Folder selection page +* 'Click Next to continue' and 'Click Install to start the installation' texts automatically change to the page order +* Install Options macros updated. MUI_INSTALLOPTIONS_DISPLAY is the standard macro now. + Use MUI_INSTALLOPTIONS_INITDIALOG and MUI_INSTALLOPTIONS_SHOW if you want to customize dialog controls. +* No more writing window titles & abort warnings to Install Options INI Files +* Compatible with updated paging system +* Renamed macros and defines +* Minor fixes + +1.4 - November 4, 2002 +* Uses new NSIS Page command +* Macro System updates (smaller) +* Macro System a lot easier +* Modern UI Language Files load NLF language files +* Renamed macros and defines + +1.3 - October 27, 2002 +* Easier macro system for basic scripts +* New MultiLanguage system using Modern UI Language Files +* New directory structure (header/language files in Contrib\Modern UI) +* Small bugfixes & typo corrections +* SetPage function should be set using defines +* Different NextPage/PrevPage/FinishHeader macros for install/uninstall + (NOTE: These macros have been removed in version 1.4) +* Interface settings can be definend (for example, MUI_ICON), no parameters for MUI_INTERFACE anymore +* New Install Options macros to read/write IO INI file value + +1.21 - September 30, 2002 +* Temp vars set in Modern UI header +* Currentpage & Install Options vars should be set using + parameters of the MUI_INTERFACE and * MUI_INSTALLOPTIONS macros + (NOTE: The MUI_INTERFACE macro has been removed in version 1.3) +* MultiLanguage.nsi uses the new language strings + +1.2 - September 22, 2002 + (NOTE: All macros mentioned here have been removed in version 1.4) + +* Lots of macro system updates & fixes +* InstallOptions support in macro system +* Added Modern UI + InstallOptions example (InstallOptions.nsi) +* MUI_NEXTPAGE_OUTER integrated in MUI_NEXTPAGE +* No hard-coded function names anymore (you should give + MUI_PREVPAGE a parameter with the set page function name + (for example, MUI_PREVPAGE SetPage) +* Examples use ReserveFile for faster startup + +1.19 - Semtember 19, 2002 +* Renamed some macros +* Custom code can be used between page start/stop macros + (NOTE: These macros have been removed in version 1.4) + +1.18 - Semtember 13, 2002 +* Uses the new Sendmessage string option + +1.17 - Semtember 10, 2002 +* Win9x font weight bug fixed (font of title in white rect) + +1.16 - Semtember 6, 2002 +* Change text 'Scroll down' on license page to 'Press Page Down', + because the RichEdit control has focus by default now + +1.15 - Semtember 4, 2002 +* Multilanguage example: changed LangDialog to LangDLL::LangDialog (using the DLL name is now required) + (NOTE: A new macro for the language selection dialog has been introduced in version 1.63) + +1.14 - Semtember 3, 2002 +* Small grammar fix (thanks eccles) +* UI files updated by Justin for better RichEdit usage + +1.13 - Semtember 2, 2002 +* Added 16 color icons + +1.12 - August 30, 2002 +* Verifying installer & Unpacking data dialog has no titlebar anymore + +1.11 - August 29, 2002 +* Finish header for uninstaller can also be set using MUI_FINISHHEADER + (NOTE: The MUI_FINISHHEADER macro has been removed in version 1.64) + +1.1 - August 29, 2002 + (NOTE: All settings mentioned here do not exist anymore) + +* Header file with macros, it's now very easy to use the UI in your scripts +* Added the modern2.exe UI, with an other location of the Description frame, for installers with a lot of subsections (thanks rainwater) +* Updated example scripts +* Added Multilanguage.nsi example (Multilanguage & LangDLL) +* Fixed background color issue with some custom XP themes +* Removed WS_VISIBLE from black rect for inner dialog (fixes display issues) +* Changed size of description area +* Example script: Added instructions for the user on the Description frame +* Auto sizing branding text +* Used modern.bmp for the checks (thanks rainwater) +* Using the new NSIS version, descriptions work using the keyboard and you can give descriptions to subsections +* Correct font size using High-DPI fonts + +1.0 - August 26, 2002 +* Initial release \ No newline at end of file diff --git a/installer/NSIS/Docs/Modern UI/License.txt b/installer/NSIS/Docs/Modern UI/License.txt new file mode 100644 index 0000000..9621cbb --- /dev/null +++ b/installer/NSIS/Docs/Modern UI/License.txt @@ -0,0 +1,10 @@ +Copyright 2002-2009 Joost Verburg + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any distribution. diff --git a/installer/NSIS/Docs/Modern UI/Readme.html b/installer/NSIS/Docs/Modern UI/Readme.html new file mode 100644 index 0000000..4e42e8a --- /dev/null +++ b/installer/NSIS/Docs/Modern UI/Readme.html @@ -0,0 +1,1154 @@ + + + + NSIS Modern User Interface - Documentation + + + + + + + + + + + +
    + +

    + [Expand all]   [Collapse + all]

    +
    +

    + Introduction

    +
    +

    + The Modern UI provides a user interface for NSIS installers with a modern wizard + style, similar to the wizards of recent Windows versions. It is based on the basic + user interface that is provided by the NSIS compiler itself and extends it with + more interface features and pages.

    +

    + All standard NSIS pages (such as the pages to select components and the installation + folder) are supported as well as a number of additional pages. The welcome page + allows you to provide an introduction to the installation process, while the finish + page provides a way to let the user decide what steps should be performed after + the setup wizard is closed (for example, whether the application should be started + immediately). A finish page can also be used to ask for a system restart is necessary.

    +

    + Open/Close section + Screenshots

    +
    +

    +

    +

    +

    +
    +
    +

    + Script header

    +
    +

    + The settings for the Modern UI should be inserted in the header of the script file. + It's important to follow the same order as the items below. For example, + interface settings should be defined before you insert pages, because the pages + depend on the interface configuration. It may be useful to look at the + example scripts too see how this is done in actual script files.

    +

    + Parameters are given in this format: required (option1 | option2) + [optional]

    +

    + 1. Header file

    +
    +

    + First of all, add this line to the top of script to include the Modern UI:

    +
    +!include "MUI.nsh"
    +
    +
    +

    + 2. Interface configuration

    +
    +

    + Then, you may want to use interface settings to change the look and feel of the + installer. These settings apply to all pages.

    + The interface settings provided by the NSIS compiler itself (such as LicenseText, + Icon, CheckBitmap, InstallColors) should not be used in Modern UI scripts. The Modern + UI provides equalivent or extended versions of these settings.

    + Examples:

    +
    +!define MUI_COMPONENTSPAGE_SMALLDESC ;No value
    +!define MUI_UI "myUI.exe" ;Value
    +!define MUI_INSTFILESPAGE_COLORS "FFFFFF 000000" ;Two colors
    +
    +

    + Open/Close section + Interface settings

    +
    +

    + Open/Close section + Page header

    +
    +

    + MUI_ICON icon_file
    + The icon for the installer.
    + Default: ${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico

    +

    + MUI_UNICON icon_file
    + The icon for the uninstaller.
    + Default: ${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico

    +

    + MUI_HEADERIMAGE
    + Display an image on the header of the page.

    +
    +

    + MUI_HEADERIMAGE_BITMAP bmp_file
    + Bitmap image to display on the header of installers pages (recommended size: 150x57 + pixels).
    + Default: ${NSISDIR}\Contrib\Graphics\Header\nsis.bmp

    +
    +

    + MUI_HEADERIMAGE_BITMAP_NOSTRETCH
    + Do not stretch the installer header bitmap to fit the size of the field. Use this + option only if you have an image that does not use the whole space. If you have + a full size bitmap that fits exactly, you should not use this option because the + size of the field will be different if the user has a custom DPI setting.

    +

    + MUI_HEADERIMAGE_BITMAP_RTL bmp_file
    + Bitmap image to display on the header of installers pages when using a RTL language + (recommended size: 150x57 pixels).
    + Default: Non-RTL bitmap

    +
    +

    + MUI_HEADERIMAGE_BITMAP_RTL_NOSTRETCH
    + Do not stretch the installer header bitmap when using a RTL language to fit the + size of the field. Use this option only if you have an image that does not use the + whole space. If you have a full size bitmap that fits exactly, you should not use + this option because the size of the field will be different if the user has a custom + DPI setting.

    +
    +
    +

    + MUI_HEADERIMAGE_UNBITMAP bmp_file
    + Bitmap image to display on the header of uninstaller pages (recommended size: 150x57 + pixels).
    + Default: Installer header bitmap

    +
    +

    + MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH
    + Do not stretch the uninstaller header bitmap to fit the size of the field. Use this + option only if you have an image that does not use the whole space. If you have + a full size bitmap that fits exactly, you should not use this option because the + size of the field will be different if the user has a custom DPI setting.

    +

    + MUI_HEADERIMAGE_UNBITMAP_RTL bmp_file
    + Bitmap image to display on the header of uninstallers pages when using a RTL language + (recommended size: 150x57 pixels).
    + Default: Installer RTL header bitmap

    +
    +

    + MUI_HEADERIMAGE_UNBITMAP_RTL_NOSTRETCH
    + Do not stretch the uninstaller header bitmap when using a RTL language to fit the + size of the field. Use this option only if you have an image that does not use the + whole space. If you have a full size bitmap that fits exactly, you should not use + this option because the size of the field will be different if the user has a custom + DPI setting.

    +
    +
    +

    + MUI_HEADERIMAGE_RIGHT
    + Display the header image on the right side instead of the left side (when using + a RTL language it will be displayed on the left side instead of the right side).

    +
    +

    + MUI_BGCOLOR (color: RRGGBBR hexadecimal)
    + Background color for the header, the Welcome page and the Finish page.
    + Default: FFFFFF

    +

    + MUI_HEADER_TRANSPARENT_TEXT
    + Set a transparent background for the header's label controls. Useful for custom + user interfaces that set a bigger header image.

    +
    +

    + Open/Close section + Interface resources

    +
    +

    + MUI_UI ui_file
    + The interface file with the dialog resources. Change this if you have made your + own customized UI.
    + Default: ${NSISDIR}\Contrib\UIs\modern.exe

    +

    + MUI_UI_HEADERIMAGE ui_file
    + The interface files with the dialog resource IDD_INST that contains a bitmap control + and space for the header bitmap.
    + Default: ${NSISDIR}\Contrib\UIs\modern_headerbmp.exe

    +

    + MUI_UI_HEADERIMAGE_RIGHT ui_file
    + The interface files with the dialog resource IDD_INST that contains a bitmap control + and space for the header bitmap on the right side.
    + Default: ${NSISDIR}\Contrib\UIs\modern_headerbmpr.exe

    +

    + MUI_UI_COMPONENTSPAGE_SMALLDESC ui_file
    + The interface files with a customized dialog resource IDD_SELCOM with a small description + area.
    + Default: ${NSISDIR}\Contrib\UIs\modern_smalldesc.exe

    +

    + MUI_UI_COMPONENTSPAGE_NODESC ui_file
    + The interface files with a customized dialog resource IDD_SELCOM without a description + area.
    + Default: ${NSISDIR}\Contrib\UIs\modern_nodesc.exe

    +
    +

    + Open/Close section + Installer welcome/finish page

    +
    +

    + MUI_WELCOMEFINISHPAGE_BITMAP bmp_file
    + Bitmap for the Welcome page and the Finish page (recommended size: 164x314 pixels).
    + Default: ${NSISDIR}\Contrib\Graphics\Wizard\win.bmp

    +
    +

    + MUI_WELCOMEFINISHPAGE_BITMAP_NOSTRETCH
    + Do not stretch the bitmap for the Welcome and Finish page to fit the size of the + field. Use this option only if you have an image that does not use the whole space. + If you have a full size bitmap that fits exactly, you should not use this option + because the size of the field will be different if the user has a custom DPI setting.

    +
    +

    + MUI_WELCOMEFINISHPAGE_INI ini_file
    + InstallOptions INI file for the Welcome page and the Finish page.
    + Default: ${NSISDIR}\Contrib\Modern UI\ioSpecial.ini

    +
    +

    + Open/Close section + Uninstaller welcome/finish page

    +
    +

    + MUI_UNWELCOMEFINISHPAGE_BITMAP bmp_file
    + Bitmap for the Welcome page and the Finish page (recommended size: 164x314 pixels).
    + Default: ${NSISDIR}\Contrib\Graphics\Wizard\win.bmp

    +
    +

    + MUI_UNWELCOMEFINISHPAGE_BITMAP_NOSTRETCH
    + Do not stretch the bitmap for the Welcome and Finish page to fit the size of the + field. Use this option only if you have an image that does not use the whole space. + If you have a full size bitmap that fits exactly, you should not use this option + because the size of the field will be different if the user has a custom DPI setting.

    +
    +

    + MUI_UNWELCOMEFINISHPAGE_INI ini_file
    + InstallOptions INI file for the uninstaller Welcome page and the Finish page.
    + Default: ${NSISDIR}\Contrib\Modern UI\ioSpecial.ini

    +
    +

    + Open/Close section + License page

    +
    +

    + MUI_LICENSEPAGE_BGCOLOR (/windows | /grey | + (color: RRGGBB hexadecimal))
    + The background color for the license textbox. Use /windows for the Windows text + background color (usually white). Use the /grey for the window background color + (usually grey).
    + Default: /windows

    +
    +

    + Open/Close section + Components page

    +
    +

    + MUI_COMPONENTSPAGE_CHECKBITMAP bitmap_file
    + The bitmap with images for the checks of the component select treeview.
    + Default: ${NSISDIR}\Contrib\Graphics\Checks\modern.bmp

    +

    + MUI_COMPONENTSPAGE_SMALLDESC
    + A small description area on the bottom of the page. Use this layout if you have + a lot of sections and don't need large descriptions.

    +

    + MUI_COMPONENTSPAGE_NODESC
    + No description area.

    +
    +

    + Open/Close section + Directory page

    +
    +

    + MUI_DIRECTORYPAGE_BGCOLOR (color: RRGGBB hexadecimal)
    + The background color for the directory textbox.

    +
    +

    + Open/Close section + Start Menu folder page

    +
    +

    + MUI_STARTMENUPAGE_BGCOLOR (color: RRGGBB hexadecimal)
    + The background color for the startmenu directory list and textbox.

    +
    +

    + Open/Close section + Installation page

    +
    +

    + MUI_INSTFILESPAGE_COLORS (/windows | "(foreground + color: RRGGBB hexadecimal) (background color: RRGGBB hexadecimal)")
    + The colors of the details screen. Use /windows for the default Windows colors.
    + Default: /windows

    +

    + MUI_INSTFILESPAGE_PROGRESSBAR ("" + | colored | smooth)
    + The style of the progress bar. Colored makes it use the MUI_INSTALLCOLORS.
    + Default: smooth

    +
    +

    + Open/Close section + Installer finish page

    +
    +

    + MUI_FINISHPAGE_NOAUTOCLOSE
    + Do not automatically jump to the finish page, to allow the user to check the install + log.

    +
    +

    + Open/Close section + Uninstaller finish page

    +
    +

    + MUI_UNFINISHPAGE_NOAUTOCLOSE
    + Do not automatically jump to the finish page, to allow the user to check the uninstall + log.

    +
    +

    + Open/Close section + Abort warning

    +
    +

    + MUI_ABORTWARNING
    + Show a message box with a warning when the user wants to close the installer.

    +
    +

    + MUI_ABORTWARNING_TEXT text
    + Text to display on the abort warning message box.

    +

    + MUI_ABORTWARNING_CANCEL_DEFAULT
    + Set the Cancel button as the default button on the message box.

    +
    +
    +

    + Open/Close section + Uninstaller abort warning

    +
    +

    + MUI_UNABORTWARNING
    + Show a message box with a warning when the user wants to close the uninstaller.

    +
    +

    + MUI_UNABORTWARNING_TEXT text
    + Text to display on the abort warning message box.

    +

    + MUI_UNABORTWARNING_CANCEL_DEFAULT
    + Set the Cancel button as the default button on the message box.

    +
    +
    +
    +
    +

    + 3. Pages

    +
    +

    + Insert the following macros to set the pages you want to use. The pages will appear + in the order in which you insert them in the script. You can also insert custom + Page commands between the macros to add custom pages.

    +

    + You can add multiple pages of certain types (for example, if you want the user to + specify multiple folders).

    +

    + Examples:

    +
    +!insertmacro MUI_PAGE_LICENSE "License.rtf"
    +!insertmacro MUI_PAGE_COMPONENTS
    +
    +Var StartMenuFolder
    +!insertmacro MUI_PAGE_STARTMENU "Application" $StartMenuFolder
    +
    +

    + You will need the page ID for the Start Menu folder page when using the Start Menu + folder macros. The folder will be stored in the specified variable.

    +

    + Installer pages
    + MUI_PAGE_WELCOME
    + MUI_PAGE_LICENSE textfile
    + MUI_PAGE_COMPONENTS
    + MUI_PAGE_DIRECTORY
    + MUI_PAGE_STARTMENU pageid variable
    + MUI_PAGE_INSTFILES
    + MUI_PAGE_FINISH

    +

    + Uninstaller pages
    + MUI_UNPAGE_WELCOME
    + MUI_UNPAGE_CONFIRM
    + MUI_UNPAGE_LICENSE textfile
    + MUI_UNPAGE_COMPONENTS
    + MUI_UNPAGE_DIRECTORY
    + MUI_UNPAGE_INSTFILES
    + MUI_UNPAGE_FINISH

    +

    + Open/Close section + Page settings

    +
    +

    + Page settings apply to a single page and should be set before inserting a page macro. + The same settings can be used for installer and uninstaller pages. You have to repeat + the setting if you want it to apply to multiple pages. Example:

    +
    +;Add a directory page to let the user specify a plug-ins folder
    +;Store the folder in $PluginsFolder
    +
    +Var PLUGINS_FOLDER
    +!define MUI_DIRECTORYPAGE_VARIABLE $PluginsFolder
    +!insertmacro MUI_PAGE_DIRECTORY
    +
    +

    +

    + All standard texts in the user interface are loaded from language files, which are + available for all languages supported by NSIS. So you only need to define these + texts if you want to change the default.

    +

    + If the parameter is a text that should be different for each language, define a + language string using LangString and use $(LangStringName) as value. For a license + text in multiple languages, LicenseLangString can be used. Refer the NSIS Users + Manual for more information about installers with multiple languages.

    +

    + In all text settings, the doublequote character (") should be escaped in the + following form: $\"

    +

    + Open/Close section + General page settings

    +
    +

    + MUI_PAGE_HEADER_TEXT text
    + Text to display on the header of the page.

    +

    + MUI_PAGE_HEADER_SUBTEXT text
    + Subtext to display on the header of the page.

    +
    +

    + Open/Close section + Welcome page settings

    +
    +

    + To add a newline to any of these texts, use \r\n instead of $\r$\n.

    +

    + MUI_WELCOMEPAGE_TITLE title
    + Title to display on the top of the page.

    +

    + MUI_WELCOMEPAGE_TITLE_3LINES
    + Extra space for the title area.

    +

    + MUI_WELCOMEPAGE_TEXT text
    + Text to display on the page.

    +
    +

    + Open/Close section + License page settings

    +
    +

    + MUI_LICENSEPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_LICENSEPAGE_TEXT_BOTTOM text
    + Text to display on the bottom of the page.

    +

    + MUI_LICENSEPAGE_BUTTON button_text
    + Text to display on the 'I Agree' button.

    +

    + MUI_LICENSEPAGE_CHECKBOX
    + Display a checkbox the user has to check to agree with the license terms.

    +
    +

    + MUI_LICENSEPAGE_CHECKBOX_TEXT text
    + Text to display next to the checkbox to agree with the license terms.

    +
    +

    + MUI_LICENSEPAGE_RADIOBUTTONS
    + Display two radio buttons to allow the user to choose between accepting the license + terms or not.

    +
    +

    + MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT text
    + Text to display next to the checkbox to accept the license terms.

    +

    + MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE text
    + Text to display next to the checkbox to decline the license terms.

    +
    +
    +

    + Open/Close section + Components page settings

    +
    +

    + MUI_COMPONENTSPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_COMPONENTSPAGE_TEXT_COMPLIST text
    + Text to display on next to the components list.

    +

    + MUI_COMPONENTSPAGE_TEXT_INSTTYPE text
    + Text to display on next to the installation type combo box.

    +

    + MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE text
    + Text to display on the of the top of the description box.

    +

    + MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO text
    + Text to display inside the description box when no section is selected.

    +
    +

    + Open/Close section + Directory page settings

    +
    +

    + MUI_DIRECTORYPAGE_TEXT_TOP text
    + Text to display on top of the page.

    +

    + MUI_DIRECTORYPAGE_TEXT_DESTINATION text
    + Text to display on the destination folder frame.

    +

    + MUI_DIRECTORYPAGE_VARIABLE variable
    + Variable in which to store the selected folder.
    + Default: $INSTDIR

    +

    + MUI_DIRECTORYPAGE_VERIFYONLEAVE
    + Does not disable the Next button when a folder is invalid but allows you to use + GetInstDirError in the leave function to handle an invalid folder.

    +
    +

    + Open/Close section + Start Menu folder page settings

    +
    +

    + MUI_STARTMENUPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_STARTMENUPAGE_TEXT_CHECKBOX text
    + Text to display next to the checkbox to disable the Start Menu folder creation.

    +

    + MUI_STARTMENUPAGE_DEFAULTFOLDER folder
    + The default Start Menu Folder.

    +

    + MUI_STARTMENUPAGE_NODISABLE
    + Do not display the checkbox to disable the creation of Start Menu shortcuts.

    +

    + MUI_STARTMENUPAGE_REGISTRY_ROOT root
    + MUI_STARTMENUPAGE_REGISTRY_KEY key
    + MUI_STARTMENUPAGE_REGISTRY_VALUENAME value_name
    + The registry key to store the Start Menu folder. The page will use it to remember + the users preference. You should also use for the uninstaller to remove the Start + Menu folders. Don't forget to remove this key during uninstallation.

    +

    + For the uninstaller, use the MUI_STARTMENU_GETFOLDER macro to get the Start Menu + folder:

    +
    +!insertmacro MUI_STARTMENU_GETFOLDER page_id $R0
    +  Delete "$SMPROGRAMS\$R0\Your Shortcut.lnk"
    +
    +
    +

    + Open/Close section + Installation page settings

    +
    +

    + MUI_INSTFILESPAGE_FINISHHEADER_TEXT text
    + Text to display on the header of the installation page when the installation has + been completed (won't be displayed when using a Finish page without MUI_(UN)FINISHPAGE_NOAUTOCLOSE).

    +

    + MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT text
    + Subtext to display on the header of the installation page when the installation + has been completed (won't be displayed when using a Finish page without MUI_(UN)FINISHPAGE_NOAUTOCLOSE).

    +

    + MUI_INSTFILESPAGE_ABORTHEADER_TEXT text
    + Text to display on the header of the installation page when the installation has + been aborted.

    +

    + MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT text
    + Subext to display on the header of the installation page when the installation has + been aborted.

    +
    +

    + Open/Close section + Finish page settings

    +
    +

    + To add a newline to any of these texts, use \r\n instead of $\r$\n.

    +

    + MUI_FINISHPAGE_TITLE title
    + Title to display on the top of the page.

    +

    + MUI_FINISHPAGE_TITLE_3LINES
    + Extra space for the title area.

    +

    + MUI_FINISHPAGE_TEXT text
    + Text to display on the page.

    +

    + MUI_FINISHPAGE_TEXT_LARGE
    + Extra space for the text area (if using checkboxes).

    +

    + MUI_FINISHPAGE_BUTTON text
    + Text to display on the Finish button.

    +

    + MUI_FINISHPAGE_CANCEL_ENABLED
    + Enable the Cancel button so the user can skip any options displayed on the finish + page.

    +

    + MUI_FINISHPAGE_TEXT_REBOOT text
    + Text to display on the finish page when asking for a system reboot.

    +

    + MUI_FINISHPAGE_TEXT_REBOOTNOW text
    + Text to display next to the 'Reboot now' option button.

    +

    + MUI_FINISHPAGE_TEXT_REBOOTLATER text
    + Text to display next to the 'Reboot later' option button.

    +

    + MUI_FINISHPAGE_REBOOTLATER_DEFAULT
    + Sets the 'Reboot later' option as the default option.

    +

    + MUI_FINISHPAGE_RUN exe_file
    + Application which the user can select to run using a checkbox. You don't need to + put quotes around the filename when it contains spaces.

    +
    +

    + MUI_FINISHPAGE_RUN_TEXT text
    + Texts to display next to the 'Run program' checkbox.

    +

    + MUI_FINISHPAGE_RUN_PARAMETERS parameters
    + Parameters for the application to run. Don't forget to escape double quotes in the + value (use $\").

    +

    + MUI_FINISHPAGE_RUN_NOTCHECKED
    + Do not check the 'Run program' checkbox by default

    +

    + MUI_FINISHPAGE_RUN_FUNCTION function
    + Call a function instead of executing an application (define MUI_FINISHPAGE_RUN without + parameters). You can use the function to execute multiple applications or you can + change the checkbox name and use it for other things.

    +
    +

    + MUI_FINISHPAGE_SHOWREADME file/url
    + File or website which the user can select to view using a checkbox. You don't need + to put quotes around the filename when it contains spaces.

    +
    +

    + MUI_FINISHPAGE_SHOWREADME_TEXT text
    + Texts to display next to the 'Show Readme' checkbox.

    +

    + MUI_FINISHPAGE_SHOWREADME_NOTCHECKED
    + Do not check the 'Show Readme' checkbox by default

    +

    + MUI_FINISHPAGE_SHOWREADME_FUNCTION function
    + Call a function instead of showing a file (define MUI_FINISHPAGE_SHOWREADME without + parameters). You can use the function to show multiple files or you can change the + checkbox name and use it for other things.

    +
    +

    + MUI_FINISHPAGE_LINK link_text
    + Text for a link on the which the user can click to view a website or file.

    +
    +

    + MUI_FINISHPAGE_LINK_LOCATION file/url
    + Website or file which the user can select to view using the link. You don't need + to put quotes around the filename when it contains spaces.

    +

    + MUI_FINISHPAGE_LINK_COLOR (color: RRGGBB hexadecimal)
    + Text color for the link on the Finish page.
    + Default: 000080

    +
    +

    + MUI_FINISHPAGE_NOREBOOTSUPPORT
    + Disables support for the page that allows the user to reboot the system. Define + this option to save some space if you are not using the /REBOOTOK flag or SetRebootFlag.

    +
    +

    + Open/Close section + Uninstall confirm page settings

    +
    +

    + MUI_UNCONFIRMPAGE_TEXT_TOP text
    + Text to display on the top of the page.

    +

    + MUI_UNCONFIRMPAGE_TEXT_LOCATION text
    + Text to display next to the uninstall location text box.

    +
    +
    +
    +

    + 4. Language files

    +
    +

    + Insert the Modern UI language files for the languages to want to include.

    +
    +!insertmacro MUI_LANGUAGE "English"
    +
    +

    + The standard NSIS language files are loaded automatically, there is no need to use + LoadLanguageFile.

    +
    +

    + 5. Reserve files

    +
    +

    + If you are using solid compression, files that are required before the actual installation + should be stored first in the data block, because this will make your installer + start faster. Include reserve file commands for such files before your sections + and functions:

    +
    +ReserveFile "ioFile.ini" ;Your own InstallOptions INI files
    +!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS ;InstallOptions plug-in
    +!insertmacro MUI_RESERVEFILE_LANGDLL ;Language selection dialog
    +
    +
    +
    +

    + Script code for pages

    +
    +

    + Some pages allow you to show additional information or can be used to get user input. + Here you can find the script code to use these features.

    +

    + Components page descriptions

    +
    +

    + The Modern UI components page has a text box in which a description can be shown + when the user hovers the mouse over a component. If you don't want to use these + descriptions, insert the MUI_COMPONENTSPAGE_NODESC interface setting.

    +

    + To set a description for a section, an additional parameter needs to be added to + Section commmand with a unique identifier for the section. This name can later be + used to set the description for this section.

    +
    +Section "Section Name 1" Section1
    +   ...
    +SectionEnd
    +
    +

    + After the sections, use these macros to set the descriptions:

    +
    +LangString DESC_Section1 ${LANG_ENGLISH} "Description of section 1."
    +LangString DESC_Section2 ${LANG_ENGLISH} "Description of section 2."
    +
    +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
    +  !insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
    +  !insertmacro MUI_DESCRIPTION_TEXT ${Section2} $(DESC_Section2)
    +!insertmacro MUI_FUNCTION_DESCRIPTION_END
    +
    +

    + For the uninstaller, use the MUI_UNFUNCTION_DESCRIPTION_BEGIN and MUI_UNFUNCTION_DESCRIPTION_END + macros.

    +
    +

    + Start Menu folder

    +
    +

    + Put the code to write the shortcuts (using CreateShortcut) between the MUI_STARTMENU_WRITE_BEGIN + and MUI_STARTMENU_WRITE_END macros:

    +
    +!insertmacro MUI_STARTMENU_WRITE_BEGIN pageid
    +  ...create shortcuts...
    +!insertmacro MUI_STARTMENU_WRITE_END
    +
    +

    + The page ID should be the ID of the page on which the user has selected the folder + for the shortcuts you want to write.

    +

    + The variable which contains the folder and the page ID are set as parameters of + the page macro.

    +
    +
    +

    + Language selection dialog

    +
    +

    + If you want the installer to display a language selection dialog (see the the MultiLanguage.nsi example), + insert the MUI_LANGDLL_DISPLAY macro in the .onInit function:

    +
    +Function .onInit
    +
    +  !insertmacro MUI_LANGDLL_DISPLAY
    +
    +FunctionEnd
    +
    +

    + This macro can also be used in the un.onInit function.

    +

    + Open/Close section + Settings for registry storage of selected language

    +
    +

    + To remember the user's preference, you can define a registry key. These defines + should be set before inserting the installation page macro.

    +

    + MUI_LANGDLL_REGISTRY_ROOT root
    + MUI_LANGDLL_REGISTRY_KEY key
    + MUI_LANGDLL_REGISTRY_VALUENAME value_name
    + The registry key to store the language. The users preference will be remembered. + You can also use it for the uninstaller to display the right language. Don't forget + to remove this key in the uninstaller.

    +

    + For the uninstaller, insert the MUI_UNGETLANGUAGE macro in un.onInit to get the + stored language preference:

    +
    +Function un.onInit
    +
    +  !insertmacro MUI_UNGETLANGUAGE
    +
    +FunctionEnd
    +
    +
    +

    + Open/Close section + Interface settings for selection dialog

    +
    +

    + To customize the language selection dialog interface, use these defines before inserting + the MUI_LANGDLL_DISPLAY macro.

    +

    + MUI_LANGDLL_WINDOWTITLE text
    + The window title of the language selection dialog.

    +

    + MUI_LANGDLL_INFO text
    + The text to display on the language selection dialog.

    +

    + MUI_LANGDLL_ALWAYSSHOW
    + Always show the language selection dialog, even if a language has been stored in + the registry. The language stored in the registry will be selected by default.

    +

    + MUI_LANGDLL_ALLLANGUAGES
    + Always show all available languages and don't filter according to their codepage.

    +
    +
    +

    + Custom pages

    +
    +

    + If you want add your custom pages to your installer, you can insert your own page + commands between the page macros. The InstallOptions + documentation provides information about creating custom pages using InstallOptions.

    +
    +!insertmacro MUI_PAGE_WELCOME
    +Page custom FunctionName ;Custom page
    +!insertmacro MUI_PAGE_COMPONENTS
    + 
    +;Uninstaller
    +!insertmacro MUI_UNPAGE_CONFIRM
    +UninstPage custom un.FunctionName ;Custom page
    +!insertmacro MUI_UNPAGE_INSTFILES
    +
    +

    + Use the MUI_HEADER_TEXT macro to set the text on the page header in a page function:

    +
    +LangString PAGE_TITLE ${LANG_ENGLISH} "Title"
    +LangString PAGE_SUBTITLE ${LANG_ENGLISH} "Subtitle"
    +
    +Function CustomPageFunction
    +  !insertmacro MUI_HEADER_TEXT $(PAGE_TITLE) $(PAGE_SUBTITLE)
    +  !insertmacro MUI_INSTALLOPTIONS_DISPLAY "ioFile.ini"
    +FuctionEnd
    +
    +
    +

    + Custom functions

    +
    +

    + Interface functions provided by NSIS, like the .onGUIInit function and the page + functions are automatically included by the Modern UI and filled with code to support + new interface features. If you want to add additional code to these function, create + a function with the custom script code in the script use the Modern UI functions + call them.

    +

    + Example:

    +
    +!define MUI_CUSTOMFUNCTION_GUIINIT myGuiInit
    +
    +Function myGUIInit
    +  ...your own code...
    +FunctionEnd
    +
    +

    + Open/Close section + General Custom Functions

    +
    +

    + These defines should be set before inserting the language macros.

    +

    + MUI_CUSTOMFUNCTION_GUIINIT function
    + MUI_CUSTOMFUNCTION_UNGUIINIT function
    + MUI_CUSTOMFUNCTION_ABORT function
    + MUI_CUSTOMFUNCTION_UNABORT function
    + MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION function
    + MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION function

    +

    + Mouse over functions are only available when the description macros + (MUI_FUNCTION_DESCRIPTION_BEGIN) are used. When component page descriptions are not + used, regular .onMouseOverSection and un.onMouseOverSection must be used.

    +
    +

    + Open/Close section + Page Custom Functions

    +
    +

    + These defines should be set before inserting a page macro.

    +

    + MUI_PAGE_CUSTOMFUNCTION_PRE function
    + MUI_PAGE_CUSTOMFUNCTION_SHOW function
    + MUI_PAGE_CUSTOMFUNCTION_LEAVE function

    +

    + Notes:

    +
      +
    • In the Pre function of the Welcome page and the Finish page, you can write to the + InstallOptions INI file of the page (ioSpecial.ini)
    • +
    • In the Show function of the Welcome, Finish and StartMenu pages, $MUI_HWND contains + the HWND of the inner dialog
    • +
    +
    +

    + Open/Close section + Welcome/Finish Page Custom Functions

    +
    +

    + This define should be inserted before a single Welcome or Finish page.

    +

    + MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT function

    +

    + This Init function is called before the InstallOptions INI file for the page is + written, so you can use it to initialize any variables used in the page settings.

    +
    +
    +

    + Example scripts

    +
    +

    + Basic: Basic.nsi
    + Welcome/Finish page: WelcomeFinish.nsi
    + Multiple languages: MultiLanguage.nsi
    + Header image: HeaderBitmap.nsi
    + Custom pages: InstallOptions.nsi
    + Start Menu Folder page: StartMenu.nsi

    +
    +

    + Credits

    +
    +

    + Written by Joost Verburg.
    + Icons designed by Nikos Adamamas, aka adni18.
    + Thanks to Amir Szekely, aka KiCHiK, for his work on NSIS to make this possible.

    +
    +

    + License

    +
    +

    + The zlib/libpng license applies to the Modern UI.

    +

    + Open/Close section + License Terms

    +
    +
    +Copyright © 2002-2009 Joost Verburg
    +
    +This software is provided 'as-is', without any express or implied
    +warranty. In no event will the authors be held liable for any damages
    +arising from the use of this software.
    +
    +Permission is granted to anyone to use this software for any purpose,
    +including commercial applications, and to alter it and redistribute
    +it freely, subject to the following restrictions:
    +
    +1. The origin of this software must not be misrepresented; 
    +   you must not claim that you wrote the original software.
    +   If you use this software in a product, an acknowledgment in the
    +   product documentation would be appreciated but is not required.
    +2. Altered versions must be plainly marked as such,
    +   and must not be misrepresented as being the original software.
    +3. This notice may not be removed or altered from any distribution.
    +
    +
    +
    +
    +
    + + diff --git a/installer/NSIS/Docs/Modern UI/images/closed.gif b/installer/NSIS/Docs/Modern UI/images/closed.gif new file mode 100644 index 0000000..b45054e Binary files /dev/null and b/installer/NSIS/Docs/Modern UI/images/closed.gif differ diff --git a/installer/NSIS/Docs/Modern UI/images/header.gif b/installer/NSIS/Docs/Modern UI/images/header.gif new file mode 100644 index 0000000..f8810d3 Binary files /dev/null and b/installer/NSIS/Docs/Modern UI/images/header.gif differ diff --git a/installer/NSIS/Docs/Modern UI/images/open.gif b/installer/NSIS/Docs/Modern UI/images/open.gif new file mode 100644 index 0000000..9fff60e Binary files /dev/null and b/installer/NSIS/Docs/Modern UI/images/open.gif differ diff --git a/installer/NSIS/Docs/Modern UI/images/screen1.png b/installer/NSIS/Docs/Modern UI/images/screen1.png new file mode 100644 index 0000000..0e25c0d Binary files /dev/null and b/installer/NSIS/Docs/Modern UI/images/screen1.png differ diff --git a/installer/NSIS/Docs/Modern UI/images/screen2.png b/installer/NSIS/Docs/Modern UI/images/screen2.png new file mode 100644 index 0000000..4fccd41 Binary files /dev/null and b/installer/NSIS/Docs/Modern UI/images/screen2.png differ diff --git a/installer/NSIS/Docs/MultiUser/Readme.html b/installer/NSIS/Docs/MultiUser/Readme.html new file mode 100644 index 0000000..493ca30 --- /dev/null +++ b/installer/NSIS/Docs/MultiUser/Readme.html @@ -0,0 +1,404 @@ + + + + Multi-User Header File (MultiUser.nsh) + + + + +

    + Multi-User Header File (MultiUser.nsh)

    +

    + Installer configuration for multi-user Windows environments

    +

    + Table of Contents

    + +

    + Introduction

    +

    + Modern Windows versions support multiple users accounts on a single computer, each + with different privileges. For security reasons, the privileges of applications + can also be limited. For an installer, the execution level and installation + mode are important. The execution level determines the privileges of the + installer application. For example, to install hardware drivers, administrator privileges + are required. Applications can also be installed for a single user or for all users + on a computer, which is determined by the installation mode. Installation for all + users requires a higher execution level as compared with a single user setup. The + MultiUser.nsh header files provides the features to automatically handle all these + aspects related to user accounts and installer privileges.

    +

    + Note that all settings need to be set before including the MultiUser.nsh header + file.

    +

    + Initialization and Execution Level 

    +

    + Before the MultiUser.nsh file is included, the MULTIUSER_EXECUTIONLEVEL define should + be set to one of the following values depending on the execution level that is required:

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Value + + Description + + Typical application +
    + Admin + + Administrator privileges are required + + Access data of all users accounts +
    + Power + + Power User privileges are required
    + (Power Users no longer exist in Windows Vista. For Vista this is equivalent to Admin) +
    + Installation for all users (writing to "Program Files" or HKLM registry + keys), driver installation +
    + Highest + + Request the highest possible execution level for the current user + + Mixed-mode installer that can both be installed per-machine or per-user +
    + Standard + + No special rights required + + Installation for current user only +
    +

    + Insert the MULTIUSER_INIT and MULTIUSER_UNINT macros in the .onInit and un.onInit + function to verify these privileges. If no uninstaller is created in the script, + set MULTIUSER_NOUNINSTALL.

    +
    +
    !define MULTIUSER_EXECUTIONLEVEL Highest
    +;!define MULTIUSER_NOUNINSTALL ;Uncomment if no uninstaller is created
    +!include MultiUser.nsh
    +
    +...
    +
    +Function .onInit
    +  !insertmacro MULTIUSER_INIT
    +FunctionEnd
    +
    +Function un.onInit
    +  !insertmacro MULTIUSER_UNINIT
    +FunctionEnd
    +
    +

    + Whether the required privileges can be obtained depends on the user that starts + the installer:

    +
      +
    • Windows NT 4/2000/XP/2003 give the installer the same privileges as the user itself. + If the privileges of the user are not sufficient (e.g. Admin level is required is + set but the user has no administrator rights), the macros will display an error + message and quit the installer. If is however possible to manually run the installer + with an administrator account.
    • +
    • Windows Vista restricts the privileges of all applications by default. Depending + on requested execution level, MultiUser.nsh will set the RequestExecutionLevel flag + to request privileges. The user will be asked for confirmation and (if necessary) + for an administrator password.
    • +
    • Windows 95/98/98 do not set any restrictions on users or applications. Administrator + rights are always available.
    • +
    +

    + It is recommended to insert these initialization macros before macros that require + user intervention. For example, it does not make sense to ask a user for an installer + language if the installer will quit afterwards because the user account does not + have the required privileges. After the macros are inserted, the variable $MultiUser.Privileges + will contain the current execution level (Admin, Power, User or Guest).

    +

    + The following additional settings are available to customize the initialization:

    + + + + + + + + + + + + + + + + + + + + +
    + Setting + Description +
    + MULTIUSER_INIT_TEXT_ADMINREQUIRED + + Error message to be displayed when administrator rights are required but not available. +
    + MULTIUSER_INIT_TEXT_POWERREQUIRED + + Error message to be displayed when Power User rights are required but not available. +
    + MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE + + Error message to be displayed when administrator or Power User rights are required + because of an installation mode setting on the command line (see below) but are + not available. +
    + MULTIUSER_INIT_FUNCTIONQUIT
    + MULTIUSER_INIT_UNFUNCTIONQUIT +
    + A custom function to be called when the installer is closed due to insufficient + privileges. +
    +

    + Installation Mode

    +

    + As mentioned before, applications can both be installed for a single users or for + all users on a computer. Applications for all users are typically installed in the + Program Files folder and appear in the Start Menu of every user. On the contrary, + applications for a single user are usually installed in the local Application Data + folder and only a appear in the Start Menu of the user who installed the application.

    +

    + By default, MultiUser.nsh will set the installation mode for a per-machine installation + if Administrator or Power User rights are available (this is always the case if + the execution level is set to Admin or Power, if Highest is set it depends on the + user account). For the Standard execution level the installation will always be + for a single user. On Windows 95/98/Me installation for a single user is not possible.

    +

    + The following settings are available to change the default installation mode: + + + + + + + + + + + + + + +
    + Setting + + Description +
    + MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER + + Set default to a per-user installation, even if the rights for a per-machine installation + are available. +
    + MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME + + Non-empty registry key that is created during the installation in either HKCU or + HKLM. The default installation mode will automatically be set to the previously + selected mode depending on the location of the key. +
    +

    + After initialization, the variable $MultiUser.InstallMode will contain the current + installation mode (AllUsers or CurrentUser). +

    +

    + Mixed-Mode Installation

    +

    + For the Admin and Power levels, both a per-machine as well as a per-user installation + is possible. If the Highest level is set and the user is an Administrator or Power + User, both options are also available.

    +

    + Usually it's a good thing to give the user to choice between these options. For + users of the Modern UI version 2, a page is provided that asks the user for the + installation mode. To use this page, define MULTIUSER_MUI before including User.nsh. + Then, the MULTIUSER_PAGE_INSTALLMODE macro can be used just like a normal Modern + UI page (this page will automatically be skipped when running Windows 95/98/Me):

    +
    !define MULTIUSER_EXECUTIONLEVEL Highest
    +!define MULTIUSER_MUI
    +!define MULTIUSER_INSTALLMODE_COMMANDLINE
    +!include MultiUser.nsh
    +!include MUI2.nsh
    +
    +!insertmacro MULTIUSER_PAGE_INSTALLMODE
    +!insertmacro MUI_PAGE_DIRECTORY
    +!insertmacro MUI_PAGE_INSTFILES 
    +
    +!insertmacro MUI_LANGUAGE English
    +
    +...
    +
    +Function .onInit
    +  !insertmacro MULTIUSER_INIT
    +FunctionEnd
    +
    +Function un.onInit
    +  !insertmacro MULTIUSER_UNINIT
    +FunctionEnd
    +
    +

    + The MULTIUSER_INSTALLMODE_COMMANDLINE setting that also appears in this example + enables the installation mode to be set using the /AllUsers or /CurrentUser command + line parameters. This is especially useful for silent setup.

    +

    + The following settings can be used to customize the texts on the page (in addition + to the general Modern UI page settings):

    + + + + + + + + + + + + + + + + + +
    + Setting + + Description +
    + MULTIUSER_INSTALLMODEPAGE_TEXT_TOP + + Text to display on the top of the page. +
    + MULTIUSER_INSTALLMODEPAGE_TEXT_ALLUSERS + + Text to display on the combo button for a per-machine installation. +
    + MULTIUSER_INSTALLMODEPAGE_TEXT_CURRENTUSER + + Text to display on the combo button for a per-user installation. +
    +

    + Installation Mode Initalization

    +

    + The SetShellVarContext flag (which determines the folders for e.g. shortcuts, like + $DESKTOP) is automatically set depending on the installation mode. In addition, + the following settings can be used to perform additional actions when the installation + mode is initialized:

    + + + + + + + + + + + + + + + + + +
    + Setting + + Description +
    + MULTIUSER_INSTALLMODE_INSTDIR + + Name of the folder in which to install the application, without a path. This folder + will be located in Program Files for a per-machine installation and in the local + Application Data folder for a per-user installation (if supported). +
    + MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME + + Registry key from which to obtain a previously stored installation folder. It will + be retrieved from HKCU for per-user and HKLM for per-machine. +
    + MULTIUSER_INSTALLMODE_FUNCTION
    + MULTIUSER_INSTALLMODE_UNFUNCTION +
    + A custom fuction to be called during the initialization of the installation mode + to set additional installer settings that depend on the mode +
    +

    + To set the installation mode manually, call one of these four functions:

    + + + + + + + + + + + + + + + + + + +
    + Function name + + Installation mode +
    + MultiUser.InstallMode.AllUsers + + Installer: Per-machine installation +
    + MultiUser.InstallMode.CurrentUser + + Installer: Per-user installation +
    + un.MultiUser.InstallMode.AllUsers + Uninstaller: Per-machine installation +
    + un.MultiUser.InstallMode.CurrentUser + Uninstaller: Per-user installation +
    + + diff --git a/installer/NSIS/Docs/NSISdl/License.txt b/installer/NSIS/Docs/NSISdl/License.txt new file mode 100644 index 0000000..642304f --- /dev/null +++ b/installer/NSIS/Docs/NSISdl/License.txt @@ -0,0 +1,18 @@ +NSISdl 1.1 - HTTP downloading plugin for NSIS +Copyright (C) 2001-2002 Yaroslav Faybishenko & Justin Frankel + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/installer/NSIS/Docs/NSISdl/ReadMe.txt b/installer/NSIS/Docs/NSISdl/ReadMe.txt new file mode 100644 index 0000000..db98b43 --- /dev/null +++ b/installer/NSIS/Docs/NSISdl/ReadMe.txt @@ -0,0 +1,91 @@ +NSISdl 1.3 - HTTP downloading plugin for NSIS +--------------------------------------------- + +Copyright (C) 2001-2002 Yaroslav Faybishenko & Justin Frankel + +This plugin can be used from NSIS to download files via http. + +To connect to the internet, use the Dialer plugin. + +USAGE +----- + +NSISdl::download http://www.domain.com/file localfile.exe + +You can also pass /TIMEOUT to set the timeout in milliseconds: + +NSISdl::download /TIMEOUT=30000 http://www.domain.com/file localfile.exe + +The return value is pushed to the stack: + + "cancel" if cancelled + "success" if success + otherwise, an error string describing the error + +If you don't want the progress window to appear, use NSISdl::download_quiet. + +Example of usage: + +NSISdl::download http://www.domain.com/file localfile.exe +Pop $R0 ;Get the return value + StrCmp $R0 "success" +3 + MessageBox MB_OK "Download failed: $R0" + Quit + +For another example, see waplugin.nsi in the examples directory. + +PROXIES +------- + +NSISdl supports only basic configurations of proxies. It doesn't support +proxies which require authentication, automatic configuration script, etc. +NSISdl reads the proxy configuration from Internet Explorer's registry key +under HKLM\Software\Microsoft\Windows\CurrentVersion\Internet Settings. It +reads and parses ProxyEnable and ProxyServer. + +If you don't want NSISdl to use Internet Explorer's settings, use the +/NOIEPROXY flag. /NOIEPROXY should be used after /TRANSLATE and +/TIMEOUT. For example: + +If you want to specify a proxy on your own, use the /PROXY flag. + +NSISdl::download /NOIEPROXY http://www.domain.com/file localfile.exe +NSISdl::download /TIMEOUT=30000 /NOIEPROXY http://www.domain.com/file localfile.exe +NSISdl::download /PROXY proxy.whatever.com http://www.domain.com/file localfile.exe +NSISdl::download /PROXY proxy.whatever.com:8080 http://www.domain.com/file localfile.exe + +TRANSLATE +--------- + +To translate NSISdl add the following values to the call line: + +/TRANSLATE2 downloading connecting second minute hour seconds minutes hours progress + +Default values are: + + downloading - "Downloading %s" + connecting - "Connecting ..." + second - " (1 second remaining)" + minute - " (1 minute remaining)" + hour - " (1 hour remaining)" + seconds - " (%u seconds remaining)" + minutes - " (%u minutes remaining)" + hours - " (%u hours remaining)" + progress - "%skB (%d%%) of %skB @ %u.%01ukB/s" + +The old /TRANSLATE method still works for backward compatibility. + +/TRANSLATE downloading connecting second minute hour plural progress remianing + +Default values are: + + downloading - "Downloading %s" + connecting - "Connecting ..." + second - "second" + minute - "minute" + hour - "hour" + plural - "s" + progress - "%dkB (%d%%) of %ukB @ %d.%01dkB/s" + remaining - " (%d %s%s remaining)" + +/TRANSLATE and /TRANSLATE2 must come before /TIMEOUT. diff --git a/installer/NSIS/Docs/Splash/splash.txt b/installer/NSIS/Docs/Splash/splash.txt new file mode 100644 index 0000000..e69e073 --- /dev/null +++ b/installer/NSIS/Docs/Splash/splash.txt @@ -0,0 +1,41 @@ +Splash.dll - small (4k), simple plugin that lets you throw +up a splash screen in NSIS installers. + +--- UPDATED in 2.0b0 - will break old scripts --- + +To use: + +Create a .BMP file of your splash screen. +(optional) Create a .WAV file to play while your splash screen shows. + +Add the following lines to your .NSI file: + +Function .onInit + SetOutPath $TEMP + File /oname=spltmp.bmp "my_splash.bmp" + +; optional +; File /oname=spltmp.wav "my_splashshit.wav" + + splash::show 1000 $TEMP\spltmp + + Pop $0 ; $0 has '1' if the user closed the splash screen early, + ; '0' if everything closed normally, and '-1' if some error occurred. + + Delete $TEMP\spltmp.bmp +; Delete $TEMP\spltmp.wav +FunctionEnd + +Note that the first parameter to splash.exe is the length to show the +screen for (in milliseconds), and the second is the splash bitmap filename (without +the .bmp). The BMP file used will be this parameter.bmp, and the wave file used +(if present) will be this parameter.wav. + +(If you already have an .onInit function, put that in it) + +Note: the return value of splash is 1 if the user closed the splash +screen early (pop it from the stack) + +-Justin + +Converted to a plugin DLL by Amir Szekely (kichik) diff --git a/installer/NSIS/Docs/StartMenu/Readme.txt b/installer/NSIS/Docs/StartMenu/Readme.txt new file mode 100644 index 0000000..7432998 --- /dev/null +++ b/installer/NSIS/Docs/StartMenu/Readme.txt @@ -0,0 +1,47 @@ +StartMenu.dll shows a custom page that lets the user select a start menu program +folder to put shortcuts in. + +To show the dialog use the Select function. This function has one required parameter +which is the program group default name, and some more optional switches: + /autoadd - automatically adds the program name to the selected folder + /noicon - doesn't show the icon in the top left corner + /text [please select...] - sets the top text to something else than + "Select the Start Menu folder in which..." + /lastused [folder] - sets the edit box to a specific value folder. + Use this to make this plug-in remember the last + folder selected by the user + /checknoshortcuts text - Shows a check box with the text "text". If + the user checks this box, the return value + will have > as its first character and you + should not create the program group. + /rtl - sets the direction of every control on the selection dialog + to RTL. This means every text shown on the page will be + justified to the right. + +The order of the switches doesn't matter but the required parameter must come after +all of them. Every switch after the required parameter will be ignored and left +on the stack. + +The function pushes "success", "cancel" or an error to the stack. If there was no +error and the user didn't press on cancel it will push the selected folder name +after "success". If the user checked the no shortcuts checkbox the result will be +prefixed with '>'. The function does not push the full path but only the selected +sub-folder. It's up to you to decide if to put it in the current user or all +users start menu. + +To set properties of the controls on the page, such as colors and fonts use Init +and Show instead of Select. Init will push the HWND of the page on the stack, +or an error string. For example: + +StartMenu::Init "Test" +Pop $0 +IntCmp $0 0 failed +GetDlgItem $0 $0 1003 +SetCtlColors $0 "" FF0000 +StartMenu::Show +# continue as with Select here +failed: + +Look at Example.nsi for a full example (without Init and Select). + +Created by Amir Szekely (aka KiCHiK) \ No newline at end of file diff --git a/installer/NSIS/Docs/StrFunc/StrFunc.txt b/installer/NSIS/Docs/StrFunc/StrFunc.txt new file mode 100644 index 0000000..400f383 --- /dev/null +++ b/installer/NSIS/Docs/StrFunc/StrFunc.txt @@ -0,0 +1,707 @@ +String Functions Header File Readme +----------------------------------- + +String Functions Header File contains a set of good string manipulation +functions in a much simpler way to include and call in NSIS scripts. + +How to use +---------- + + Basic Syntax + ------------ + + Parameters are specified in this format: + required (required) (option1 | option2) [optional or add. options] + [option1 | option2] + + The stars in command titles (*****) are the function usefulness in my + opinion. The more starts, the more useful it is. 5 stars (*****) is the + most useful. + + Any time when is mentioned "Default is" means that you can use the value + mentioned or keep it blank, the result is the same. + + If you want a certain value (e.g. a text) to be language-specific, set a + language string (using LangString) and define $(STRINGNAME) as value. + + If you want to add ` to a string, you should always escape it using $\` + because the header file macro functions use ` to separate parameters. + + 1. Include Header file + ---------------------- + + !include "StrFunc.nsh" + + StrFunc.nsh has to be inside Include directory, so you don't have to + specify a path. + + You have to put this command before any command used in this header file. + + 2. Defines + ---------- + + This header file contains defines that automate the life of some who + fear a lot of changes sometimes imposed in this header file, or who have + applications that put it to work at maximum capacity. Before you try + these, take a look at the information below: + + - Every item on a define value is separated by a "|", and every subitem + (items in an item) is separated by " ". + + - Use ${StrTok} $var "${DefineName}" "|" "$counter" "0" to get every + item inside the define. For subitems, use ${StrTok} $var2 "$var" " " + "$counter2" "0" after getting the value for a desired item. + + - ${StrFunc_List} is automatically made by the header file. The rest + is manually added to the header. + + 2.1 Defines List: + ----------------- + + StrFunc_List - Lists all function names currently available on StrFunc + header file. + + *_List - Lists all parameter names currently available for "*" + function. (* = function name - i.e. StrTok_List). + + *_TypeList - Lists the types of all parameters on "*" function. + (* = function name - i.e. StrTok_List). Possible types + for each parameter: + + - Output - Needs a variable to output a function result. + + - Text - Needs text or number to be input. + + - Mixed - Needs text, number or option to be inputed. + Each subitem following the "Mixed" word is an + option. The first option is ever the default + one. Two following spaces " " means that + that subitem is empty. + + - Option - Needs an option to be inputed. Each subitem + following the "Option" word is an option. + The first option is ever the default one. Two + following spaces " " means that that subitem + is empty. + + 3. Commands + ----------- + + Some commands have special specifications to work. Consult command's + documentation on "3.3 Commands" section. + + 3.1 How To Use Commands In Install Sections and Functions + --------------------------------------------------------- + + Every command used in install sections and functions have to be called + first before and out of any sections and functions, and without + parameters. + + Example: + -------- + + ${StrStr} + + 3.2 How To Use Commands In Uninstall Sections and Functions + ----------------------------------------------------------- + + Commands with Uninstall Sections and Functions support have "Un" before + the words inside curly brackets "{}". + + Example: + -------- + + ${UnStrStr} + + A complete example with both Install and Uninstall Commands: + ------------------------------------------------------------ + + + !include "StrFunc.nsh" + + ${StrStr} # Supportable for Install Sections and Functions + + ${UnStrStr} # Supportable for Uninstall Sections and Functions + + Section + + ${StrStr} $0 "OK! Now what?" "wh" + + SectionEnd + + Section Uninstall + + ${UnStrStr} $0 "OK! Now what?" "wh" + + SectionEnd + + 3.3 Commands + ------------ + + ========================================================================= + ** ${StrCase} + ------------------------------------------------------------------------- + ResultVar String Type(|L|U|T|S|<>) + ========================================================================= + Converts "String" to "Type" Case. Uses LogicLib. + + Parameters: + + ResultVar + Destination where result is returned. + + String + String to convert to "Type" case. + + Type + Type of string case to convert to: + + - "" = Original Case (same as "String") + - L = Lower Case (this is just an example. a very simple one.) + - U = Upper Case (THIS IS JUST AN EXAMPLE. A VERY SIMPLE ONE.) + - T = Title Case (This Is Just An Example. A Very Simple One.) + - S = Sentence Case (This is just an example. A very simple one.) + - <> = Switch Case (This is just an example. A very simple one.) + + Default value is "" (Original Case). + + Result Value -> ResultVar: + + "String" in "Type" case. + + Example: + + ${StrCase} $0 '"Voc" is "You" in English.' "U" + [__(_)__()___()__()__(____)_] + + $0 = '"VOC" IS "YOU" IN ENGLISH.' + + ========================================================================= + * ${StrClb} + ------------------------------------------------------------------------- + ResultVar String Action(|>|<|<>) + ========================================================================= + Makes an action with the clipboard depending on value of parameter + "Action". Uses LogicLib. + + Parameters: + + String + If "Action" = ">" or "<>" - String to put on the clipboard. + + Action + Can be one of the following values: + + - "" = Cleans the clipboard. + - ">" = Set string to clipboard. + - "<" = Get string from clipboard. + - "<>" = Swap string with clipboard's. + + Result Value -> ResultVar: + + If "Action" = "<" or "<>" - String found on the clipboard. + + ========================================================================= + *** ${StrIOToNSIS} + ------------------------------------------------------------------------- + ResultVar String + ========================================================================= + Convert "String" from Install Options plugin to be supported by NSIS. + Escape, back-slash, carriage return, line feed and tab characters are + converted. + + Parameters: + + ResultVar + Destination where result is returned. + + String + String to convert to be supportable for NSIS. + + Result Value -> ResultVar: + + "String" supportable for NSIS. + + Example: + + ${StrIOToNSIS} $0 "\r\n\t\\This is just an example\\" + [()()()()_______________________()] + + $0 = "$\r$\n$\t\This is just an example\" + + ========================================================================= + * ${StrLoc} + ------------------------------------------------------------------------- + ResultVar String StrToSearchFor CounterDirection(>|<) + ========================================================================= + Searches for "StrToSearchFor" in "String" and returns its location, + according to "CounterDirection". + + Parameters: + + ResultVar + Destination where result is returned. + + String + String where to search "StrToSearchFor". + + StrToSearchFor + String to search in "String". + + CounterDirection(>|<) + Direction where the counter increases to. Default is ">". + (> = increases from left to right, < = increases from right to left) + + Result Value -> ResultVar: + + Where "StrToSearchFor" is, according to "OffsetDirection". + + Example: + + ${StrLoc} $0 "This is just an example" "just" "<" + (__)<<<<<<<<<<< + + $0 = "11" + + ========================================================================= + *** ${StrNSISToIO} + ------------------------------------------------------------------------- + ResultVar String + ========================================================================= + Converts "String" from NSIS to be supported by Install Options plugin. + Escape, back-slash, carriage return, line feed and tab characters are + converted. + + Parameters: + + ResultVar + Destination where result is returned. + + String + String to convert to be supportable for Install Options plugin. + + Result Value -> ResultVar: + + "String" supportable for Install Options plugin. + + Example: + + ${StrNSISToIO} $0 "$\r$\n$\t\This is just an example\" + [(_)(_)(_)^_______________________^] + + $0 = "\r\n\t\\This is just an example\\" + + ========================================================================= + ***** ${StrRep} + ------------------------------------------------------------------------- + ResultVar String StrToReplace ReplacementString + ========================================================================= + Searches for all "StrToReplace" in "String" replacing those with + "ReplacementString". + + Parameters: + + ResultVar + Destination where result is returned. + + String + String where to search "StrToReplace". + + StrToReplaceFor + String to search in "String". + + StringToBeReplacedWith + String to replace "StringToReplace" when it is found in "String". + + Result Value -> ResultVar: + + "String" with all occurrences of "StringToReplace" replaced with + "ReplacementString". + + Example: + + ${StrRep} $0 "This is just an example" "an" "one" + [____________()_______] + + $0 = "This is just one example" + + ========================================================================= + *** ${StrSort} + ------------------------------------------------------------------------- + ResultVar String LeftStr CenterStr RightStr IncludeLeftStr(1|0) + IncludeCenterStr(1|0) IncludeRightStr(1|0) + ========================================================================= + Searches for "CenterStr" in "String", and returns only the value + between "LeftStr" and "RightStr", including or not the "CenterStr" using + "IncludeCenterStr" and/or the "LeftStr" using "IncludeLeftStr" and + "RightStr" using "IncludeRightStr". + + Parameters: + + ResultVar + Destination where result is returned. + + String + String where to search "CenterStr". + + LeftStr + The first occurrence of "LeftStr" on the left of "CenterStr". + If it is an empty value, or was not found, will return + everything on the left of "CenterStr". + + CenterStr + String to search in "String". + + RightStr + The first occurrence of "RightStr" on the right of "CenterStr". + If it is an empty value, or was not found, will return + everything on the right of "CenterStr". + + IncludeLeftStr(1|0) + Include or not the "LeftStr" in the result value. Default is 1 + (True). (1 = True, 0 = False) + + IncludeCenterStr(1|0) + Include or not the "CenterStr" in the result value. Default is 1 + (True). (1 = True, 0 = False) + + IncludeRightStr(1|0) + Include or not the "RightStr" in the result value. Default is 1 + (True). (1 = True, 0 = False) + + Result Value -> ResultVar: + + String between "LeftStr" and "RightStr" of a found "CenterStr" + including or not the "LeftStr" and "RightStr" if + "IncludeLeftRightStr" is 1 and/or the "CenterStr" if + "IncludeCenterStr" is 1. + + Example: + + ${StrSort} $0 "This is just an example" " just" "" "ple" "0" "0" "0" + [_______(___)_______]( ) + C R + + $0 = "This is an exam" + + ========================================================================= + ***** ${StrStr} + ------------------------------------------------------------------------- + ResultVar String StrToSearchFor + ========================================================================= + Searches for "StrToSearchFor" in "String". + + Parameters: + + ResultVar + Destination where result is returned. + + String + String where to search "StrToSearchFor". + + StrToSearchFor + String to search in "String". + + Result Value -> ResultVar: + + "StrToSearchFor" + the string after where "StrToSearchFor" was found in + "String". + + Example: + + ${StrStr} $0 "This is just an example" "just" + >>>>>>>>>{_)____________] + + $0 = "just an example" + + ========================================================================= + ***** ${StrStrAdv} + ------------------------------------------------------------------------- + ResultVar String StrToSearchFor SearchDirection(>|<) + ResultStrDirection(>|<) DisplayStrToSearch(1|0) Loops CaseSensitive(0|1) + ========================================================================= + Searches for "StrToSearchFor" in "String" in the direction specified by + "SearchDirection" and looping "Loops" times. + + Parameters: + + ResultVar + Destination where result is returned. + + String + String where to search "StrToSearchFor". + + StrToSearchFor + String to search in "String". + + SearchDirection (>|<) + Where do you want to direct the search. Default is ">" (to right). + (< = To left, > = To right) + + ResultStrDirection (>|<) + Where the result string will be based on in relation of + "StrToSearchFor" + position. Default is ">" (to right). (< = To left, > = To right) + + DisplayStrToSearch (1|0) + Display "StrToSearchFor" in the result. Default is "1" (True). + (1 = True, 0 = False) + + Loops + Number of times the code will search "StrToSearchFor" in "String" not + including the original execution. Default is "0" (1 code execution). + + CaseSensitive(0|1) + If "1" the search will be case-sensitive (differentiates between cases). + If "0" it is case-insensitive (does not differentiate between cases). + Default is "0" (Case-Insensitive). + + + Result Value -> ResultVar: + + "StrToSearchFor" if "DisplayStrToSearch" is 1 + the result string after + or before "StrToSearchFor", depending on "ResultStrDirection". + + Result with Errors: + + When "StrToSearchFor" was not found, will return an empty string. + + When you put nothing in "StrToSearchFor", will return "String" and set + error flag. + + When you put nothing in "String", will return an empty string and set + error flag. + + Example: + + ${StrStrAdv} $0 "This IS really just an example" "IS " ">" ">" "0" "0" "1" + >>>>>( )[____________________] + + + $0 = "really just an example" + + ========================================================================= + **** ${StrTok} + ------------------------------------------------------------------------- + ResultVar String Separators ResultPart[L] SkipEmptyParts(1|0) + ========================================================================= + Returns the part "ResultPart" between two "Separators" inside + "String". + + Parameters: + + ResultVar + Destination where result is returned. + + String + String where to search for "Separators". + + Separators + Characters to find on "String". + + ResultPart[L] + The part want to be found on "StrToTokenize" between two "Separators". + Can be any number, starting at 0, and "L" that is the last part. + Default is L (Last part). + + SkipEmptyParts(1|0) + Skips empty string parts between two "Separators". Default is 1 (True). + (1 = True, 0 = False) + + Result Value -> ResultVar: + + "String" part number "Part" between two "Separators". + + Examples: + + 1) ${StrTok} $0 "This is, or is not, just an example" " ," "4" "1" + ( ) () () () [_] ( ) () ( ) + 0 1 2 3 4 5 6 7 + $0 = "not" + + 2) ${StrTok} $0 "This is, or is not, just an example" " ," "4" "0" + ( ) () ^() [] ( ) ^( ) () ( ) + 0 1 23 4 5 67 8 9 + $0 = "is" + + ========================================================================= + * ${StrTrimNewLines} + ------------------------------------------------------------------------- + ResultVar String + ========================================================================= + Deletes unnecessary new lines at end of "String". + + Parameters: + + ResultVar + Destination where result is returned. + + String + String where to search unnecessary new lines at end of "String". + + Result Value -> ResultVar: + + "String" with unnecessary end new lines removed. + + Example: + + ${StrTrimNewLines} $0 "$\r$\nThis is just an example$\r$\n$\r$\n" + [_____________________________(_)(_)(_)(_)] + + $0 = "$\r$\nThis is just an example" + +Functions included and not included +-------------------------------------------------- + +11 functions have been included + They are not available on Archive + They are on LogicLib format + +15 functions have not been included + 12 were not included because of better functions + 6 were not included because of AdvStrTok (called here as StrTok) + First String Part Function + Save on Variables Function + Sort Strings (1, 2 and 3) Functions + StrTok Function + 2 were not included because of StrCase + StrLower Function + StrUpper Function + 2 were not included because of StrClb + StrClbSet Function + StrClbGet Function + 1 was not included because of NSISToIO and IOToNSIS + Convert / to // in Paths Function + 1 was not included because of original String Replace Function (called + here as StrRep) + Another String Replace Function + 2 were not included because they aren't useful anymore + Slash <-> Backslash Converter Function + Trim Function + 1 was not included because of bugs + Number to String Converter Function + +Version History +--------------- + +1.09 - 10/22/2004 + +- Fixed stack problems involving: StrCase, StrRep, StrSort, StrTok. +- Fixed StrClb: When "Action" = "<>", handle was wrongly outputed as + text. +- Fixed StrSort, StrStrAdv documentation examples. +- Fixed StrIOToNSIS, StrLoc, StrNSISToIO, StrRep, StrStr: sometimes + didn't find "StrToSearch" at all. + +1.08 - 10/12/2004 + +- Converted all the functions to LogicLib. +- StrSort: Totally remade and it can break old scripts. See + documentation for details. +- StrTok: "ResultPart" has to start from 0 and it can break old scripts. + See documentation for details. +- Added defines: StrFunc_List, *_List and *_TypeList. +- Fixed StrStrAdv: Variables $R0-$R3 couldn't be used on scripts before + calling. +- StrRep: Cut down some variables. +- Arranged correctly the order of StrSort on the documentation. + +1.07 - 09/21/2004 + +- Removed ${UnStrFunc} command. Now you can just include uninstall + functions commands like ${UnStrStr} to be supported by uninstall functions + and sections. +- Added case-sensitive comparation option for StrStrAdv. +- StrCase now uses System.dll which makes case conversions effective with +all latin letters (i.e. ). +- Added switch case and original case for StrCase. +- StrClbSet and StrClbGet removed, added StrClb. +- Made compact the most usual operations inside the header file. File size +reduced. + +1.06 - 03/26/2004 + +- StrNumToStr removed due to complex number handling on some languages. +- Fixed the bug where the old string was attached to string returned by + StrCase when $R5 variable was used. + +1.05 - 03/17/2004 + +- Fixed a bug with StrCase, Title Case wasn't working as should be. +- Fixed a bug with StrStrAdv, previous fix created another bug, string not + returned correctly when using backwards search with "DisplayStrToSearch" as + "0". + +1.04 - 03/07/2004 + +- Added new StrCase, removed StrLower and StrUpper. +- Organized by name commands inside header and readme files. + +1.03 - 02/12/2004 + +- Added commands support for uninstall sections and functions. +- Fixed variables switch in "StrLoc" and "StrTok" after using these. + +1.02 - 02/07/2004 + +- Fixed StrLoc. +- Fixed Documentation about StrLoc. "Direction" is really "OffsetDirection". +- Added my new AdvStrSort, and removed the old one. + +1.01 - 02/05/2004 + +- Fixed Documentation about StrSort and StrTok. +- Fixed StrTok default value for the string part. Now it's "L". +- Fixed StrStrAdv fixed wrong search when had a combination of same + substrings one after another in a string. +- Fixed StrLoc: when a string isn't found, don't return any value at all. + +1.00 - 02/01/2004 + +- Added documentation. +- Renamed header file to "StrFunc.nsh". +- Added 1 function, StrLoc. +- Modified StrStrAdv, removed some lines. +- Fixed StrTok, 2 simple numbers made it loop everytime. +- Fixed some small issues on the header file. + +0.02 - 01/24/2004 + +- Completed StrFunc.nsh file. Need some tests and the readme. + +0.01 - 01/22/2004 + +- First version to test ideas... + +Credits +------- + + Made by Diego Pedroso (aka deguix). + +Functions Credits +----------------- + +- All functions are made by Diego Pedroso on LogicLib format. They + are based on functions by Amir Szekely, Dave Laundon, Hendri + Adriaens, Nik Medved, Joost Verburg, Stuart Welch, Ximon Eighteen, + "bigmac666" and "bluenet". "bluenet"'s version of StrIOToNSIS and + StrNSISToIO on LogicLib format were included. + +License +------- + +This header file is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this header file. + +Permission is granted to anyone to use this header file for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions: + +1. The origin of this header file must not be misrepresented; + you must not claim that you wrote the original header file. + If you use this header file in a product, an acknowledgment in the + product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, + and must not be misrepresented as being the original header file. +3. This notice may not be removed or altered from any distribution. \ No newline at end of file diff --git a/installer/NSIS/Docs/System/System.html b/installer/NSIS/Docs/System/System.html new file mode 100644 index 0000000..7898b4c --- /dev/null +++ b/installer/NSIS/Docs/System/System.html @@ -0,0 +1,786 @@ + + + +NSIS System Plug-in + + + +

    NSIS System Plug-in

    +

    © brainsucker (Nik Medved), 2002

    + +

    Table of Contents

    + + + +

    Introduction

    + +

    The System plug-in gives developers the ability to call any exported function from any DLL. For example, you can use it to call GetLogicalDriveStrings to get a list of available drives on the user's computer.

    + +

    The System plug-in also allows the developer to allocate, free and copy memory; interact with COM objects and perform mathematical operations on 64-bit integers.

    + +

    Programming knowledge is highly recommended for good understanding of the System plug-in.

    + +

    The most useful System plug-in functions (Call, Get and Debug) are not available when compiling with GCC. To work around this, either download a MSVC-compiled version or write your own plugin that calls the functions you need.

    + +

    Usage Examples From The Wiki

    + + + +

    Available Functions

    + +

    Memory Related Functions

    + +
    +
      + +
    • +Alloc SIZE +
      +

      Allocates SIZE bytes and returns a memory address on the stack.

      +

      Usage Example

      +
      +System::Alloc 64
      +Pop $0
      +DetailPrint "64 bytes allocated at $0"
      +System::Free $0
      +
      +
      +
    • + +
    • +Copy [/SIZE] DESTINATION SOURCE +
      +

      Copies SIZE bytes from SOURCE to DESTINATION. If SIZE is not specified, SOURCE's size will queried using GlobalSize. This means that if you don't allocate SOURCE using System::Alloc, System::Call or GlobalAlloc, you must specify SIZE. If DESTINATION is zero it will be allocated and its address will be pushed on the stack.

      +

      Usage example

      +
      +# allocate a buffer and put 'test string' and an int in it
      +System::Call "*(&t1024 'test string', i 5) i .s"
      +Pop $0
      +# copy to an automatically created buffer
      +System::Copy 0 $0
      +Pop $1
      +# get string and int in $1 buffer
      +System::Call "*$1(&t1024 .r2, i .r3)"
      +# free buffer
      +System::Free $1
      +# print result
      +DetailPrint $2
      +DetailPrint $3
      +# copy to our own buffer
      +System::Alloc 1028
      +Pop $1
      +System::Copy $1 $0
      +# get string and int in $1 buffer
      +System::Call "*$1(&t1024 .r2, i .r3)"
      +# free
      +System::Free $0
      +System::Free $1
      +# print result
      +DetailPrint $2
      +DetailPrint $3
      +
      +
      + +
    • + +
    • +Free ADDRESS +
      +

      Frees ADDRESS.

      +

      Usage Example

      +
      +System::Alloc 64
      +Pop $0
      +DetailPrint "64 bytes allocated at $0"
      +System::Free $0
      +
      +
      +
    • + +
    • +Store "OPERATION [OPERATION [OPERATION ...]]" +
      +

      Performs stack operations. An operation can be pushing or popping a single register from the NSIS stack or pushing or popping all of the registers ($0-$9 and $R0-$R9) from System's private stack. Operations can be separated by any character.

      +

      Available Operations

      +
        +
      • To push $#, use p#, where # is a digit from 0 to 9.
      • +
      • To pop $#, use r#, where # is a digit from 0 to 9.
      • +
      • To push $R#, use P#, where # is a digit from 0 to 9.
      • +
      • To pop $R#, use R#, where # is a digit from 0 to 9.
      • +
      • To push $0-$9 and $R0-$R9 to System's private stack, use s or S.
      • +
      • To pop $0-$9 and $R0-$R9 from System's private stack, use l or L.
      • +
      +

      Usage Examples

      +
      +StrCpy $0 "test"
      +System::Store "p0"
      +Pop $1
      +DetailPrint "$0 = $1"
      +
      +
      +StrCpy $2 "test"
      +System::Store "p2 R2"
      +DetailPrint "$2 = $R2"
      +
      +
      +StrCpy $3 "test"
      +System::Store "s"
      +StrCpy $3 "another test"
      +System::Store "l"
      +DetailPrint $3
      +
      +
      +System::Store "r4" "test"
      +DetailPrint $4
      +
      +
      +
    • +
    + +
    + +

    Calling Functions

    + +
    + +
      +
    • Call PROC [( PARAMS ) [RETURN [? OPTIONS]]]
    • +
    • Get PROC [( PARAMS ) [RETURN [? OPTIONS]]] +
      +

      Call and get both share a common syntax. As the names suggest, Call calls and Get gets. What does it call or get? It depends on PROC's value.

      + +

      PARAMS is a list of parameters and what do to with them. You can pass data in the parameters and you can also get data from them. The parameters list is separated by commas. Each parameter is combined of three values: type, source and destination. Type can be an integer, a string, etc. Source, which is the source of the parameter value, can be a NSIS register ($0, $1, $INSTDIR), the NSIS stack, a concrete value (5, "test", etc.) or nothing (null). Destination, which is the destination of the parameter value after the call returns, can be a NSIS register, the NSIS stack or nothing which means no output is required. Either one of source or destination can also be a dot (`.') if it is not needed.

      + +

      RETURN is like a single parameter definition, but source is only used when creating callback functions. Normally source is a dot.

      + +

      OPTIONS is a list of options which control the way System plug-in behaves. Each option can be turned off by prefixing with an exclamation mark. For example: ?!e.

      + +

      PARAMS, RETURN and OPTIONS can be repeated many times in one Get/Call line. When repeating, a lot can be omitted, and only what you wish to change can be used. Type, source and/or destination can be omitted for each parameter, even the return value. Options can be added or removed. This allows you to define function prototypes and save on some typing. The last two examples show this.

      + +

      PROC can also be repeated but must be prefixed with a hash sign (`#').

      + +

      Possible PROC Values and Meanings

      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ValueMeaningExample
      DLL::FUNCFUNC exported from DLLuser32::MessageBox
      ::ADDRFunction located at ADDRsee below
      *ADDRStructure located at ADDRsee below
      *New structuresee below
      IPTR->IDXMember indexed IDX from
      interface pointed by IPTR
      see below
      <nothing>New callback functionsee below
      PROCPROC returned by Getsee below
      +
      + +

      Available Parameter Types

      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeMeaning
      vvoid (generally for return)
      iint (includes char, byte, short, handles, pointers and so on)
      llarge integer, int64
      mANSI text, string. (FYI: 'm' for multibyte string or 'w' flipped over.)
      ttext, string (pointer to first character). Like TCHAR*, it is a Unicode string in Unicode NSIS.
      wWCHAR text, Unicode string
      gGUID
      kcallback
      &vNN bytes padding (structures only)
      &iNinteger of N bytes (structures only)
      &lstructure size (structures only)
      &tNN bytes of text (structures only)
      &wNN bytes of Unicode text (structures only)
      &gNN bytes of GUID (structures only)
      +

      Additionally, each type can be prefixed with an asterisk to denote a pointer. When using an asterisk, the System plug-in still expects the value of the parameter, rather than the pointer's address. To pass a direct address, use `i' with no asterisk. A usage example is available. Alloc returns addresses and its return value should therefore be used with `i', without an asterisk.

      +
      + +

      Available Sources and Destinations

      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TypeMeaning
      .ignored
      numberconcrete hex, decimal or octal integer value. several integers can be or'ed using the pipe symbol (`|')
      'string'
      "string"
      `string`
      concrete string value
      r0 through r9$0 through $9 respectively
      r10 through r19
      R0 through R9
      $R0 through $R9 respectively
      c$CMDLINE
      d$INSTDIR
      o$OUTDIR
      e$EXEDIR
      a$LANGUAGE
      sNSIS stack
      nnull for source, no output required for destination
      +
      + +

      Callbacks

      + +
      +

      Callback functions are simply functions which are passed to a function and called back by it. They are frequently used to pass a possibly large set of data item by item. For example, EnumChildWindows uses a callback function. As NSIS functions are not quite regular functions, the System plug-in provides its own mechanism to support callback functions. It allows you to create callback functions and notifies you each time a callback function was called.

      + +

      Creation of callback functions is done using Get and the callback creation syntax. As you will not call the callbacks yourself, the source of the parameters should be omitted using a dot. When the callback is called, the destination of the parameters will be filled with the values passed on to the callback. The value the callback will return is set by the source of the return "parameter". The destination of the return "parameter" should always be set as that's where System will notify you the callback was called.

      + +
      System::Get "(i .r0, i .r1) iss"
      + +

      To pass a callback to a function, use the k type.

      + +
      System::Get "(i .r0, i .r1) isR0"
      +Pop $0
      +System::Call "dll::UseCallback(k r0)"
      + +

      Each time the callback is called, the string callback#, where # is the number of the callback, will be placed in the destination of the return "parameter". The number of the first callback created is 1, the second's is 2, the third's is 3 and so on. As System is single threaded, a callback can only be called while calling another function. For example, EnumChildWindows's callback can only be called when EnumChildWindows is being called. You should therefore check for callback# after each function call that might call your callback.

      + +
      System::Get "(i .r0, i .r1) isR0"
      +Pop $0
      +System::Call "dll::UseCallback(k r0)"
      +StrCmp $R0 "callback1" 0 +2
      +DetailPrint "UseCallback passed ($0, $1) to the callback"
      +
      + +

      After you've processed the callback call, you should use Call, passing it the value returned by Get - the callback. This tells System to return from the callback. Destination of the return "parameter" must be cleared prior to calling a function, to avoid false detection of a callback call. If you've specified a source for the return "parameter" when the callback was created, you should fill that source with the appropriate return value. Callbacks are not automatically freed, don't forget to free it after you've finished using it.

      + +
      System::Get "(i .r0, i .r1) isR0"
      +Pop $0
      +System::Call "dll::UseCallback(k r0)"
      +loop:
      +	StrCmp $R0 "callback1" 0 done
      +	DetailPrint "UseCallback passed ($0, $1) to the callback"
      +	Push 1 # return value of the callback
      +	StrCpy $R0 "" # clear $R0 in case there are no more callback calls
      +	System::Call $0 # tell system to return from the callback
      +	Goto loop
      +done:
      +System::Free $0
      +
      + +

      A complete working example is available in the usage examples section.

      + +
      + +

      Notes

      + +
      +
        +
      • To find out the index of a member in a COM interface, you need to search for the definition of this COM interface in the header files that come with Visual C/C++ or the Platform SDK. Remember the index is zero based.
      • +
      • If a function can't be found, an `A' will be appended to its name and it will be looked up again. This is done because a lot of Windows API functions have two versions, one for ANSI strings and one for Unicode strings. The ANSI version of the function is marked with `A' and the Unicode version is marked with `W'. For example: lstrcpyA and lstrcpyW.
      • +
      +
      + +

      Available Options

      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      OptionMeaning
      ccdecl calling convention (the stack restored by caller). By default stdcall calling convention is used (the stack restored by callee).
      rAlways return (for GET means you should pop result and proc, for CALL means you should pop result (at least)). By default result is returned for errors only (for GET you will pop either error result or right proc, and for CALL you will get either your return or result at defined return place).
      nNo redefine. Whenever this proc will be used it will never be redefined either by GET or CALL. This options is never inherited to children.
      sUse general Stack. Whenever the first callback defined the system starts using the temporary stacks for function calls.
      eCall GetLastError() after procedure end and push result on stack.
      uUnload DLL after call (using FreeLibrary, so you'll be able to delete it for example).
      +
      + +

      Usage Examples

      + +
      +System::Call "user32::MessageBox(i $HWNDPARENT, t 'NSIS System Plug-in', t 'Test', i 0)"
      +
      +
      +System::Call "kernel32::GetModuleHandle(t 'user32.dll') i .s"
      +System::Call "kernel32::GetProcAddress(i s, t 'MessageBoxA') i .r0"
      +System::Call "::$0(i $HWNDPARENT, t 'GetProcAddress test', t 'NSIS System Plug-in', i 0)"
      +
      +
      +System::Get "user32::MessageBox(i $HWNDPARENT, t 'This is a default text', t 'Default', i 0)"
      +Pop $0
      +System::Call "$0"
      +
      +
      +System::Get "user32::MessageBox(i $HWNDPARENT, t 'This is a default text', \
      +	t 'Default', i 0x1|0x10)"
      +Pop $0
      +System::Call "$0(, 'This is a System::Get test', 'NSIS System Plug-in',)"
      +
      +
      +System::Call "advapi32::GetUserName(t .r0, *i ${NSIS_MAX_STRLEN} r1) i.r2"
      +DetailPrint "User name - $0"
      +DetailPrint "String length - $1"
      +DetailPrint "Return value - $2"
      +
      +
      +System::Alloc 4
      +Pop $0
      +System::Call "*$0(i 5)"
      +System::Call "*$0(i .r1)"
      +DetailPrint $1
      +
      +
      +System::Call "*(i 5) i .r0"
      +System::Call "*$0(i .r1)"
      +DetailPrint $1
      +
      +
      +# defines
      +!define CLSCTX_INPROC_SERVER 1
      +!define CLSID_ActiveDesktop {75048700-EF1F-11D0-9888-006097DEACF9}
      +!define IID_IActiveDesktop {F490EB00-1240-11D1-9888-006097DEACF9}
      +# create IActiveDesktop interface
      +System::Call "ole32::CoCreateInstance( \
      +	g '${CLSID_ActiveDesktop}', i 0, \
      +	i ${CLSCTX_INPROC_SERVER}, \
      +	g '${IID_IActiveDesktop}', *i .r0) i.r1"
      +StrCmp $1 0 0 end
      +# call IActiveDesktop->GetWallpaper
      +System::Call "$0->4(w .r2, i ${NSIS_MAX_STRLEN}, i 0)"
      +# call IActiveDesktop->Release
      +System::Call "$0->2()"
      +# print result
      +DetailPrint $2
      +end:
      +
      +
      +InitPluginsDir
      +SetOutPath $PLUGINSDIR
      +File MyDLL.dll
      +System::Call "MyDLL::MyFunc(i 5) ? u"
      +Delete $PLUGINSDIR\MyDLL.dll
      +
      +
      +System::Get "(i.r1, i) iss"
      +Pop $R0
      +System::Call "user32::EnumChildWindows(i $HWNDPARENT, k R0, i) i.s"
      +loop:
      +	Pop $0
      +	StrCmp $0 "callback1" 0 done
      +	System::Call "user32::GetWindowText(ir1,t.r2,i${NSIS_MAX_STRLEN})"
      +	System::Call "user32::GetClassName(ir1,t.r3,i${NSIS_MAX_STRLEN})"
      +	IntFmt $1 "0x%X" $1
      +	DetailPrint "$1 - [$3] $2"
      +	Push 1 # callback's return value
      +	System::Call "$R0"
      +	Goto loop
      +done:
      +System::Free $R0
      +
      +
      +!define MB "user32::MessageBox(i$HWNDPARENT,t,t'NSIS System Plug-in',i0)"
      +System::Call "${MB}(,'my message',,)"
      +System::Call "${MB}(,'another message',,) i.r0"
      +MessageBox MB_OK "last call returned $0"
      +
      +
      +System::Call "user32::SendMessage(i $HWNDPARENT, t 'test', t 'test', i 0) i.s ? \
      +	e (,t'test replacement',,) i.r0 ? !e #user32::MessageBox"
      +DetailPrint $0
      +ClearErrors
      +Pop $0
      +IfErrors good
      +MessageBox MB_OK "this message box will never be reached"
      +good:
      +
      +
      +
    • +
    + +
    + +

    64-bit Functions

    + +
    + +
      + +
    • +Int64Op ARG1 OP [ARG2] +
      +

      Performs OP on ARG1 and optionally ARG2 and returns the result on the stack. Both ARG1 and ARG2 are 64-bit integers. This means they can range between -2^63 and 2^63 - 1.

      +

      Available Operations

      +
        +
      • Addition -- +
      • +
      • Subtraction -- -
      • +
      • Multiplication -- *
      • +
      • Division -- /
      • +
      • Modulo -- %
      • +
      • Shift right -- >>
      • +
      • Shift left -- <<
      • +
      • Bitwise or -- |
      • +
      • Bitwise and -- &
      • +
      • Bitwise xor -- ^
      • +
      • Logical or -- ||
      • +
      • Logical and -- &&
      • +
      • Less than -- <
      • +
      • Equals -- =
      • +
      • Greater than -- >
      • +
      • Bitwise not (one argument) -- ~
      • +
      • Logical not (one argument) -- !
      • +
      + +

      Usage Examples

      + +
      +System::Int64Op 5 + 5
      +Pop $0
      +DetailPrint "5 + 5 = $0" # 10
      +
      +
      +System::Int64Op 64 - 25
      +Pop $0
      +DetailPrint "64 - 25 = $0" # 39
      +
      +
      +System::Int64Op 526355 * 1565487
      +Pop $0
      +DetailPrint "526355 * 1565487 = $0" # 824001909885
      +
      +
      +System::Int64Op 5498449498849818 / 3
      +Pop $0
      +DetailPrint "5498449498849818 / 3 = $0" # 1832816499616606
      +
      +
      +System::Int64Op 0x89498A198E4566C % 157
      +Pop $0
      +DetailPrint "0x89498A198E4566C % 157 = $0" # 118
      +
      +
      +System::Int64Op 1 << 62
      +Pop $0
      +DetailPrint "1 << 62 = $0" # 4611686018427387904
      +
      +
      +System::Int64Op 0x4000000000000000 >> 62
      +Pop $0
      +DetailPrint "0x4000000000000000 >> 62 = $0" # 1
      +
      +
      +System::Int64Op 0xF0F0F0F | 0xF0F0FFF
      +Pop $0
      +# IntFmt is 32-bit, this is just for the example
      +IntFmt $0 "0x%X" $0
      +DetailPrint "0xF0F0F0F | 0xF0F0FFF = $0" # 0xF0F0FFF
      +
      +
      +System::Int64Op 0x12345678 & 0xF0F0F0F0
      +Pop $0
      +# IntFmt is 32-bit, this is just for the example
      +IntFmt $0 "0x%X" $0
      +DetailPrint "0x12345678 & 0xF0F0F0F0 = $0" # 0x10305070
      +
      +
      +System::Int64Op 1 ^ 0
      +Pop $0
      +DetailPrint "1 ^ 0 = $0" # 1
      +
      +
      +System::Int64Op 1 || 0
      +Pop $0
      +DetailPrint "1 || 0 = $0" # 1
      +
      +
      +System::Int64Op 1 && 0
      +Pop $0
      +DetailPrint "1 && 0 = $0" # 0
      +
      +
      +System::Int64Op 9302157012375 < 570197509190760
      +Pop $0
      +DetailPrint "9302157012375 < 570197509190760 = $0" # 1
      +
      +
      +System::Int64Op 5168 > 89873
      +Pop $0
      +DetailPrint "5168 > 89873 = $0" # 0
      +
      +
      +System::Int64Op 189189 = 189189
      +Pop $0
      +DetailPrint "189189 = 189189 = $0" # 1
      +
      +
      +System::Int64Op 156545668489 ~
      +Pop $0
      +DetailPrint "1 ~ = $0" # -156545668490
      +
      +
      +System::Int64Op 1 !
      +Pop $0
      +DetailPrint "1 ! = $0" # 0
      +
      +
      +
    • + +
    + +
    + +

    FAQ

    + +
    + +
      + +
    • +Q: How can I pass structs to functions? +
      +

      A: First of all, you must allocate the struct. This can be done in two ways. You can either use Alloc or Call with the special struct allocation syntax. Next, if you need to pass data in the struct, you must fill it with data. Then you call the function with a pointer to the struct. Finally, if you want to read data from the struct which might have been written by the called function, you must use Call with the struct handling syntax. After all is done, it's important to remember to free the struct.

      + +

      Allocation

      + +

      To allocate the struct using Alloc, you must know the size of the struct in bytes. Therefore, it would normally be easier to use Call. In this case it's easy to see the required size is 16 bytes, but other cases might not be that trivial. In both cases, the struct address will be located on the top of the stack and should be retrieved using Pop.

      + +
      +System::Alloc 16
      +
      + +
      +System::Call "*(i, i, i, t)i.s"
      +
      + +
      + +

      Setting Data

      + +

      Setting data can be done using Call. It can be done in the allocation stage, or in another stage using the struct handling syntax.

      + +
      +System::Call "*(i 5, i 2, i 513, t 'test')i.s"
      +
      + +
      +# assuming the struct's memory address is kept in $0
      +System::Call "*$0(i 5, i 2, i 513, t 'test')"
      +
      + +
      + +

      Passing to the Function

      + +

      As all allocation methods return an address, the type of the passed data should be an integer, an address in memory.

      + +
      +# assuming the struct's memory address is kept in $0
      +System::Call "dll::func(i r0)"
      +
      + +
      + +

      Reading Data

      + +

      Reading data from the struct can be done using the same syntax as setting it. The only difference is that the destination part of the parameter will be set and the source part will be omitted using a dot.

      + +
      +# assuming the struct's memory address is kept in $0
      +System::Call "*$0(i .r0, i .r1, i .r2, t .r3)"
      +DetailPrint "first int = $0"
      +DetailPrint "second int = $1"
      +DetailPrint "third int = $2"
      +DetailPrint "string = $3"
      +
      + +
      + +

      Freeing Memory

      + +

      Memory is freed using Free.

      + +
      +# assuming the struct's memory address is kept in $0
      +System::Free $0
      +
      + +
      + +

      A Complete Example

      + +
      +# allocate
      +System::Alloc 32
      +Pop $1
      +# call
      +System::Call "Kernel32::GlobalMemoryStatus(i r1)"
      +# get
      +System::Call "*$1(i.r2, i.r3, i.r4, i.r5, i.r6, i.r7, i.r8, i.r9)"
      +# free
      +System::Free $1
      +# print
      +DetailPrint "Structure size: $2 bytes"
      +DetailPrint "Memory load: $3%"
      +DetailPrint "Total physical memory: $4 bytes"
      +DetailPrint "Free physical memory: $5 bytes"
      +DetailPrint "Total page file: $6 bytes"
      +DetailPrint "Free page file: $7 bytes"
      +DetailPrint "Total virtual: $8 bytes"
      +DetailPrint "Free virtual: $9 bytes"
      +
      + +
      +
    • + +
    + +
    + + + diff --git a/installer/NSIS/Docs/System/WhatsNew.txt b/installer/NSIS/Docs/System/WhatsNew.txt new file mode 100644 index 0000000..959c734 --- /dev/null +++ b/installer/NSIS/Docs/System/WhatsNew.txt @@ -0,0 +1,46 @@ +release 2. +1. Syntax, with inline input +2. Int64 full support (conversion/operations/comparison) +3. Structures support +4. Callbacks support, including multilevel callbacks +5. Some useful routines (Alloc, Free, Copy) +6. CDecl and StdCall calling conventions + +release 3, 22 march 2003. +1. Custom Message Boxes (with icons etc) -> bug in case of GetModuleHandle and +call to FreeLibrary (sysfunc.nsh) +2. GetFileSysTime -> No SystemTimeToTzSpecificLocalTime at win9x bug, +changed to use FileTimeToLocalFileTime (sysfunc.nsh) +3. Incorrect automatic structure size (&l) bug, value actually never filled +into the structure (strange -> winxp takes no care of the structure size +members, such as cbSize, and win98 does...) (system.c) +4. Changed Secondary Stack Allocation behavior - now we just leave 65kb of the +stack NSIS give to us to the NSIS itself, and use the other part as the stack +for our calls. (system.c) +5. Secondary Stack Optimization - in case of no more pending callback procs - +"free" the secondary stack pointer. (system.c) +6. PlaySound("", 0, 0) plays the default windows sound at win9x, so changed to +PlaySound(NULL, 0, 0) for effective sound stopping after splash (sysfunc.nsh). + +release 4, 3 september 2003. +1. Division by zero fatal error at Int64Op killed. +2. bool type removed (use int instead). +3. GUID (g) and LPWSTR (w) types added. +4. Memory cleanup after using t (string), g (guid) and w (unicode string) added. +5. Automatic A-letter at proc name discovery. +6. COM optimized: new proc specification "x->y", where x is interface ptr, and +y is vtable member index. For such procs Interface pointer passed as first arg +automatically. + +release 5, 11 september 2003. +1. u flag - unload dll after procedure call. +2. some changes to asm to turn on Whole Program Optimization. +3. Dll shrunk by 1 kb. + +bug-fix-release, 4.06.2004 +1. System::Copy /SIZE fixed (Kichik). +2. System::Copy with destination auto-allocation now pushes destination +address on stack. +3. Callbacks fixed (Kichik's kick is awesome). +4. Bug with proc call parts redefinition, # for example (pointed by Kichik). +5. Bug with memory protection during callback processing (Kichik). \ No newline at end of file diff --git a/installer/NSIS/Docs/VPatch/Readme.html b/installer/NSIS/Docs/VPatch/Readme.html new file mode 100644 index 0000000..a70509f --- /dev/null +++ b/installer/NSIS/Docs/VPatch/Readme.html @@ -0,0 +1,334 @@ + + + +VPatch 3 + + + + + + + + +
    + +

    VPatch 3.1

    +
    +

    Introduction

    +
    +

    VPatch allows to create a patch file to update previous versions + of your software. The GenPat utility generates the patch file. The + plug-in can use the patch to update a file. Using a patch, you can + reduce the download size of your updates, because only the differences + between the files are included in the patch file.

    +
    +

    How to use

    +
    +

    Generate the patch file

    +
    +

    Make sure you have the source file (original version) and the target + file (version to update to). For example, DATA.DTA (currently on user + system) and DATA_20.DTA (version 2.0 of this data file). Now call + the command line tool GenPat.exe:

    +
    +GENPAT oldfile.txt newfile.txt patch.pat
    +
    +

    Now, the patch will be generated, this will take some time.

    +

    Using the /B=(BlockSize) parameter of the GenPat utility (put it + after the filenames), you can use a different block size. A smaller + block size may result in a smaller patch, but the generation will + take more time (the default blocksize is 64).

    +

    If you have trouble using this command-line utility, you can download + a GUI (graphical user interface) for VPatch from its own website: + http://www.tibed.net/vpatch.

    +
    +

    Update the file during installation

    +
    +

    Use the VPatch plug-in to update a file using a patch file:

    +
    +vpatch::vpatchfile "patch.pat" "oldfile.txt" "temporary_newfile.txt"
    +
    +

    The result of the patch operating will be added to the stack and + can be one of the following texts:

    +
      +
    • OK
    • +
    • OK, new version already installed
    • +
    • An error occurred while patching
    • +
    • Patch data is invalid or corrupt
    • +
    • No suitable patches were found
    • +
    +

    Check example.nsi for an example. You + should check whether the stack string starts with "OK" + because then the patch has succeeded and you can rename "temporary_newfile.txt" + to "oldfile.txt" to replace the original, if you want.

    +
    +

    Multiple patches in one file

    +
    +

    GenPat appends a patch to the file you specified. If there is already + a patch for the same original file, with the same CRC/MD5, in the patch file, + the patch will be replaced. For example, if you want to be able to upgrade + version 1 and 2 to version 3, you can put a 1 > 3 and 2 > 3 patch in + one file.

    +

    You can also put patches for different files in one patch file, for + example, a patch from file A version 1 to file A version 2 and a patch + from file B version 1 to file B version 2. Just call the plug-in multiple + times with the same patch file. It will automatically select the right + patch (based on the file CRC).

    +
    +

    Patch generator (GenPat) exit codes

    +
    +

    In version 3 the following exit codes (known as error levels in + the DOS period) can be returned by GenPat. GenPat will return an + exit code based on success of the patch generation. Here is a list + of the possible exit codes:

    + + + + + + + + + + + + + + + + + + + + + +
    Exit codeDescription
    0Success
    1Arguments missing
    2Other error
    3Source file already has a patch in specified patch file (ERROR), + use /R switch to override
    +

    These exit codes can be useful when you generate patch files through + a NSIS script.

    +
    +
    +
    +
    +

    Source code

    +
    +

    Source code is available in the original package and in the SVN repository of NSIS.

    +

    NSIS plug-in (C++)

    +
    +

    The source of the NSIS plug-in that applies patches can be found + in the Source\Plugin folder.

    +
    +

    Patch Generator (C++)

    +
    +

    The most interesting part of VPatch, the actual patch generation + algorithm, can be found in Source\GenPat\PatchGenerator.cpp. The + header of that file contains a brief explanation of the algorithm + as well.

    +
    +

    User interface (Delphi)

    +
    +

    A user interface is included as well, which you will have to build + yourself because the GUI executable was too large to include. Besides + Borland Delphi 6 or higher (you can use the freely available Personal + edition), you will also need to install the VirtualTreeView component by Mike Lischke.

    +
    +
    +

    Version history

    +
    +
      +
    • 3.1 +
        +
      • GenPat now compiles on POSIX platforms (MinGW/GCC), Visual + C++ 6 and Borland C++.
      • +
      • More test cases to verify functionality of GenPat.
      • +
      +
    • +
    • 3.0 +
        +
      • Final: Updates to the GUI, installer
      • +
      • RC8: GenPat will now flag replacement of a patch (e.g. + the source file has the same contents as a previous patch inside + a patch file) as an error. You can specifically allow it using + the /R switch. Added license to source files.
      • +
      • RC7: Fixed critical bug in GenPat with multiple patches + in a single file. Fixed serious bug in stand-alone EXE runtime: + process kept on running forever. Included case testing through + a Python script to test common usage (and prevent bugs like + the one in GenPat in the future).
      • +
      • RC6: Upgraded to non-beta compiler. Added /A switch + to change block match limit and /O to deactivate the limit. + Updated GUI to support the /O switch.
      • +
      • RC4a to RC5a: input block size is now checked for power + of 2 and fixed if incorrect. When patch file does not yet exist, + no longer forgets to create the header. No longer tries to allocate + memory when there are no chunks. Fixed memory leaks.
      • +
      • Target file date is now preserved inside a patch and restored + on the user system.
      • +
      • MD5 checksums are now used instead of CRC32 checksums, unless + existing patches in a file already are in CRC32 mode.
      • +
      • The patch generator, GenPat, has been completely rewritten + in C++. It no longer needs to keep the entire files in memory, + instead memory usage is a certain percentage of the source file + size. The percentage is based on the block size, larger block + sizes will reduce memory usage.
      • +
      • All runtimes now share a common codebase, perform proper error + checking and don't leave behind files if the input file was + already up to date.
      • +
      • Bug Fix: The patch generator algorithm no longer reduces to + a quadratic runtime if there are many blocks with the same content + in the files to patch.
      • +
      • Bug Fix: The documentation of the command-line utilities was + incorrect and no warnings would be given by the runtimes, causing + the patch not to work (this does not apply to NSIS patches).
      • +
      +
    • +
    • 2.1 +
        +
      • Added argument checking and error handling to GenPat. Now + returns exit codes as well to indicate success/failure (and + the reason for failure). Only GenPat has changed in this version + compared to 2.0 final.
      • +
      • Bug Fix: GenPat no longer gives an Access Violation when attempting + to patch a file smaller than 64 bytes into a file larger than + 64 bytes.
      • +
      +
    • +
    • 2.0 final +
        +
      • Cleaned up source code for the patch generator, which is now + included (this code is written in Borland Delphi 6 and compiles + with the freely available Personal edition).
      • +
      +
    • +
    • 2.0 beta 2 +
        +
      • All new algorithm used in the patch generator: much faster + (up to 90%) while using smaller block sizes (higher compression)
      • +
      • Created a NSIS 2 plugin
      • +
      • Works with small files
      • +
      • Replaces existing patch in file if original file CRC is identical
      • +
      +
    • +
    +
    +

    Credits

    +
    +

    Written by Koen van de Sande
    + C plug-in initially by Edgewize, updated by Koen van de Sande
    + New documentation and example by Joost Verburg and Koen van de Sande

    +
    +

    License

    +
    +
    +Copyright (C) 2001-2005 Koen van de Sande / Van de Sande Productions
    +
    +This software is provided 'as-is', without any express or implied
    +warranty. In no event will the authors be held liable for any damages
    +arising from the use of this software.
    +
    +Permission is granted to anyone to use this software for any purpose,
    +including commercial applications, and to alter it and redistribute
    +it freely, subject to the following restrictions:
    +
    +1. The origin of this software must not be misrepresented;
    +   you must not claim that you wrote the original software.
    +   If you use this software in a product, an acknowledgment in the
    +   product documentation would be appreciated but is not required.
    +2. Altered versions must be plainly marked as such,
    +   and must not be misrepresented as being the original software.
    +3. This notice may not be removed or altered from any distribution.
    +
    +
    +
    +
    + + diff --git a/installer/NSIS/Docs/makensisw/License.txt b/installer/NSIS/Docs/makensisw/License.txt new file mode 100644 index 0000000..ccb71be --- /dev/null +++ b/installer/NSIS/Docs/makensisw/License.txt @@ -0,0 +1,17 @@ +Copyright (c) 2002 Robert Rainwater + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. diff --git a/installer/NSIS/Docs/makensisw/Readme.txt b/installer/NSIS/Docs/makensisw/Readme.txt new file mode 100644 index 0000000..f28b162 --- /dev/null +++ b/installer/NSIS/Docs/makensisw/Readme.txt @@ -0,0 +1,214 @@ +---------------------------------------------------- +MakeNSISW - MakeNSIS Windows Wrapper +---------------------------------------------------- + + +About MakeNSISW +--------------- +MakeNSISW is a wrapper for the MakeNSIS that is distributed with +NSIS (http://www.nullsoft.com/free/nsis/). MakeNSISW allows you +to compile NSIS scripts using a Windows GUI interface. To install +MakeNSISW, compile the source using Visual C++ or Mingw. + + +Requirements +------------ +MakeNSISW requires NSIS be installed on your system. The default +directory for this installation is $PROGRAMFILES\NSIS\Contrib\MakeNSISW. + + +Usage: +------ +If you installed the Shell Extensions option during the installation, then +all that is required is that you choose 'Compile NSI' from the right- +click menu on a NSIS script. This will invoke MakeNSISW. + +The format of the parameters when calling MakeNSISW from the commandline is: + makensisw [options] [script.nsi | - [...]] + +For the options, please see the MakeNSIS documentation. + + +Shortcut Keys +------------- +Ctrl+A: Select All text +Ctrl+B: Open Script Folder +Ctrl+C: Copy selected text +Ctrl+D: Opens the Define Symbols dialog +Ctrl+E: Edits the script +Ctrl+F: Find text +Ctrl+L: Load a script +Ctrl+R: Recompiles the script +Ctrl+T: Tests the installer +Ctrl+W: Clear Log Window +Alt+X: Exits the application +F1: View Documentation + + +Version History +--------------- +0.1 + - Initial Release + +0.2 + - Added ability to save output and copy output + +0.3 + - Added option to recompile script (F2 or File|Recompile) + - Added Help Menu + - Return code is now always set + - Added Accelerator key support for Exit and Recompile + - No longer uses NSIS's version string + - Made clearer status message in title bar + - Disabled menu/accelerator functions during compile + +0.4 + - Fixed Copy Selected bug + +0.5 + - Minor Makefile changes (mingw) + - Moved strings into global strings to make editing easier + - Added Clear Log Command under Edit menu + - Recompile no longer clears the log window (use F5) + - Close is now the default button when you hit enter + - added VC++ project, updated resources to work with VC++ + - rearranged directory structure + - makefiles now target ../../makensisw.exe + - removed makensisw home link in help menu (hope this is ok, + doesn't really seem needed to me) + - made display use a fixed width font (Some people may not like + this, but I do) + - added 'test' button (peeks output for 'Output' line) + - made it so that the log shows the most recent 32k. + - made it so that the log always clears on a recompile. + - compiled with VC++ so no longer needs msvcrt.dll + - made the compiler name be a full path (for more flexibility) + +0.6 + - print correct usage if unable to execute compiler + - removed mingw warnings + - set title/branding before errors + - some docs changes + - Added Edit|Edit Script function + +0.7 + - Edit Script should now work for output>32k + - Added resize support (thanks to felfert) + - Added window position saving (thanks to felfert) + - Disable some items when exec of makensis failed + +0.8 + - Added window size constraints (thanks to bcheck) + - Cleaned up the resource file + +0.9 + - Removed global strings (moved into #defines) + - Some GUI changes + - No longer focused Close button (its default anyways) + - Fixed resize bug on minimize/restore (thanks to felfert) + - Made window placement stored in HKLM instead of HKCU, cause + I hate things that get littered in HKCU. + +1.0 + - Fixed bug with large output causing crash + +1.1 + - Crash may actually be fixed + +1.2 + - XP visual style support + +1.3 + - Added Documentation menu item + - Fix GUI problem with About dialog + +1.4 + - Edit Script command will now work with or without file associations + - Added default filename for save dialog + - Use standard fonts + - Documentation menuitem caused recompile + +1.5 + - Fixed Copy All function + +1.6 + - Reduced size from 44k to 12k (kichik) + - Editbox not limited to 32k (now using richedit control) + - Made the log window font-size smaller. + +1.7 + - Added check for warnings + - Added sound for sucessfull compilations + - Update home page and documentation menu items to Sourceforge page + +1.8 + - Contents of log window are now streamed in + - Empty log window check (to prevent random crashes) + +1.9 + - Text always scrolls to bottom (kichik) + - Updated link to new docs + - Makensisw now takes the same parameters as makensis.exe + - Fixed some random crashes + - Drag and Drop Support into the Makensisw window + - Updated icon to more sexy one + - Added Load Script option on File menu + - Added Search Dialog (Ctrl+F) (kichik) + - Added Select All (Ctrl+A), Copy (Ctrl+C), Exit (Alt+X) keys + - Branding text now reflects NSIS version + - Added some simple tool tips + - Added Context Menu in log window + - Added resize gripper + - Ctrl+L loads a script + - Added Clear Log (Ctrl+W) + - Browse Script (Ctrl+B) launches explorer in script directory + - Check for Update command + - Added link to the NSIS Forum under Help menu + - Bunch of other stuff not worth mentioning + - Define Symbols menu (Ctrl+D) + +2.0 + - Improved user interface + - Define Symbols is available even if a script is not loaded + - Defined Symbols are saved on exit and reloaded on start + - Added NSIS Update menu + - Added toolbar for commonly used menus + - Made the Toolbar style flat + - Added option for compile & run + - Added compressor setting option + - Added support for lzma compression + - Added named Symbols sets. + +2.1 + - Added "Cancel compilation" menu item + +2.2 +- Settings saved in HKCU instead of HKLM +- Added menu accelerators to MRU list + +2.3 +- Escape button closes MakeNSISw + +2.3.1 +- Fixed broken command line parameter handling + +Copyright Information +--------------------- +Copyright (c) 2002 Robert Rainwater +Contributors: Justin Frankel, Fritz Elfert, Amir Szekely, Sunil Kamath, Joost Verburg + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. diff --git a/installer/NSIS/Docs/nsDialogs/Readme.html b/installer/NSIS/Docs/nsDialogs/Readme.html new file mode 100644 index 0000000..5aeda7b --- /dev/null +++ b/installer/NSIS/Docs/nsDialogs/Readme.html @@ -0,0 +1,924 @@ + + + +nsDialogs + + + + + +

    NSIS nsDialogs Plug-in

    +

    Next generation of user interface design

    + +

    Table of Contents

    + + + +

    Introduction

    + +

    nsDialogs allows creation of custom pages in the installer. On top of the built-in pages, nsDialogs can create pages with any type of controls in any order and arrangement. It can create everything from a simple page with one label to form which reacts to user's actions. Modern UI 2, for example, uses nsDialogs to create the welcome and finish pages.

    + +

    nsDialogs is a new NSIS plug-in, introduced in version 2.29 as a replacement for InstallOptions. nsDialogs doesn't use INI files, so it's way faster than InstallOptions. Integration with the script is tighter and more natural - creating controls is done using plug-in functions and notification is done by directly calling a function in the script. Unlike InstallOptions, there isn't a predefined set of available control type and by providing a lower level access to Windows API, every type of control can be created and pages can be more customizable.

    + +

    The same thing that makes nsDialogs more flexible can also make it more complicated for users with no knowledge of Win32 API. This is solved by creating a library of predefined functions, defined in script, that allow creation and handling of controls. This way, novices get easy access to the flexibility, while advanced users still get access to the core functionality by modifying the library or simply avoid using it.

    + +

    Step-by-Step Tutorial

    + +

    Basic Script

    + +

    Before using nsDialogs, lets first create a basic script as our skeleton.

    + +
    Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Page instfiles
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Custom Page

    + +

    Next, we'll add a custom page where we can use nsDialogs. nsDialogs cannot be used in sections or any other function but a custom page's function.

    + +
    Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Page custom nsDialogsPage
    +Page instfiles
    +
    +Function nsDialogsPage
    +FunctionEnd
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Creating Page

    + +

    Now that the foundations are laid, it's time to use nsDialogs. The first call must always be to nsDialogs::Create. It will create a new dialog in the page and return its HWND on the stack. The result must be popped from the stack to prevent stack corruption. If the result is error, the dialog couldn't be created.

    + +

    nsDialogs::Create accepts one parameter. It has a very specific function, but to keep things simple for this tutorial, it must always be 1018.

    + +

    HWND is a number that uniquely identifies the dialog and can be used with SendMessage, SetCtlColors and Win32 API.

    + +
    !include LogicLib.nsh
    +
    +Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Var Dialog
    +
    +Page custom nsDialogsPage
    +Page instfiles
    +
    +Function nsDialogsPage
    +
    +	nsDialogs::Create 1018
    +	Pop $Dialog
    +
    +	${If} $Dialog == error
    +		Abort
    +	${EndIf}
    +
    +FunctionEnd
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Showing Page

    + +

    Now that the page is created, it's time to show it. This is done with nsDialogs::Show. This function will not return until the user clicks Next, Back or Cancel.

    + +
    !include LogicLib.nsh
    +
    +Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Var Dialog
    +
    +Page custom nsDialogsPage
    +Page instfiles
    +
    +Function nsDialogsPage
    +	nsDialogs::Create 1018
    +	Pop $Dialog
    +
    +	${If} $Dialog == error
    +		Abort
    +	${EndIf}
    +
    +	nsDialogs::Show
    +
    +FunctionEnd
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Adding Controls

    + +

    Compiling the last script and running it results in an empty page which is not very useful. So now we'll add some controls to it to. To do so, we'll use ${NSD_Create*} macros from nsDialogs.nsh. Each of those macros takes 5 parameters - x, y, width, height and text. Each macro also returns one value on the stack, which is the new control's HWND. Like the dialogs HWND, it must be popped from the stack and saved.

    + +

    Each of the measurements that the macros take can use one of three unit types - pixels, dialog units or percentage of the dialog's size. It can also be negative to indicate it should be measured from the end. To use dialog units, the measurement must be suffixed with the letter u. To use percentage, the measurement must be suffixed with the percentage sign - %. Any other suffix, or no suffix, means pixels.

    + +

    Dialog units allow creation of dialogs that scale well when different fonts or DPI is used. Its size in pixels is determined on runtime based on the font and the DPI. For example, standard pages in the classic NSIS user interface are 266 dialog units wide and 130 dialog units high. Pages in Modern UI are 300 dialogs units wide and 140 dialog units high. In different resolutions, using different fonts or DPI settings, the dialogs will always have the same size in dialog units, but different size in pixels.

    + +
    !include nsDialogs.nsh
    +!include LogicLib.nsh
    +
    +Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Var Dialog
    +Var Label
    +Var Text
    +
    +Page custom nsDialogsPage
    +Page instfiles
    +
    +Function nsDialogsPage
    +
    +	nsDialogs::Create 1018
    +	Pop $Dialog
    +
    +	${If} $Dialog == error
    +		Abort
    +	${EndIf}
    +
    +	${NSD_CreateLabel} 0 0 100% 12u "Hello, welcome to nsDialogs!"
    +	Pop $Label
    +
    +	${NSD_CreateText} 0 13u 100% -13u "Type something here..."
    +	Pop $Text
    +
    +	nsDialogs::Show
    +
    +FunctionEnd
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Available control types that can be created with ${NSD_Create*} are:

    + +
      +
    • HLine
    • +
    • VLine
    • +
    • Label
    • +
    • Icon
    • +
    • Bitmap
    • +
    • BrowseButton
    • +
    • Link
    • +
    • Button
    • +
    • GroupBox
    • +
    • CheckBox
    • +
    • RadioButton
    • +
    • Text
    • +
    • Password
    • +
    • Number
    • +
    • FileRequest
    • +
    • DirRequest
    • +
    • ComboBox
    • +
    • DropList
    • +
    • ListBox
    • +
    • ProgressBar
    • +
    + +

    Control State

    + +

    Now that we have some controls that the user can interact with, it's time to see what the user actually does with them. For that, we'll first add a leave callback function to our page. In that function, we'll query the state of the text control we've created and display it to the user. To do so, we'll use the ${NSD_GetText} macro. Use the ${NSD_GetState} macro for RadioButton and CheckBox controls.

    + +

    Note that not all controls support ${NSD_GetText} and some require special handling with specific messages defined in WinMessages.nsh. For example, the ListBox control requires usage of LB_GETCURSEL and LB_GETTEXT. With time, the library of macros in nsDialogs.nsh will fill with more and more macros that'll handle more cases like this.

    + +
    !include nsDialogs.nsh
    +!include LogicLib.nsh
    +
    +Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Var Dialog
    +Var Label
    +Var Text
    +
    +Page custom nsDialogsPage nsDialogsPageLeave
    +Page instfiles
    +
    +Function nsDialogsPage
    +
    +	nsDialogs::Create 1018
    +	Pop $Dialog
    +
    +	${If} $Dialog == error
    +		Abort
    +	${EndIf}
    +
    +	${NSD_CreateLabel} 0 0 100% 12u "Hello, welcome to nsDialogs!"
    +	Pop $Label
    +
    +	${NSD_CreateText} 0 13u 100% -13u "Type something here..."
    +	Pop $Text
    +
    +	nsDialogs::Show
    +
    +FunctionEnd
    +
    +Function nsDialogsPageLeave
    +
    +	${NSD_GetText} $Text $0
    +	MessageBox MB_OK "You typed:$\n$\n$0"
    +
    +FunctionEnd
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Real-time Notification

    + +

    One of the more exciting new features of nsDialogs is callback function notification of changes to the dialog. nsDialogs can call a function defined in a script in response to a user action such as changing of a text field or click of a button. To make nsDialogs notify us of events, we'll use ${NSD_OnClick} and ${NSD_OnChange}. Not every control supports both of the events. For example, there is nothing to notify about label changes, only clicks.

    + +

    When the callback function is called, the control's HWND will be waiting on the stack and must be popped to prevent stack corruption. In this simple example, this is not so useful. But in case of a bigger script where several controls are associated with the same callback function, the HWND can shed some light on which control originated the event.

    + +

    The new example will respond to the user type hello in the text box.

    + +
    !include nsDialogs.nsh
    +!include LogicLib.nsh
    +
    +Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Var Dialog
    +Var Label
    +Var Text
    +
    +Page custom nsDialogsPage nsDialogsPageLeave
    +Page instfiles
    +
    +Function nsDialogsPage
    +
    +	nsDialogs::Create 1018
    +	Pop $Dialog
    +
    +	${If} $Dialog == error
    +		Abort
    +	${EndIf}
    +
    +	${NSD_CreateLabel} 0 0 100% 12u "Hello, welcome to nsDialogs!"
    +	Pop $Label
    +
    +	${NSD_CreateText} 0 13u 100% -13u "Type something here..."
    +	Pop $Text
    +	${NSD_OnChange} $Text nsDialogsPageTextChange
    +
    +	nsDialogs::Show
    +
    +FunctionEnd
    +
    +Function nsDialogsPageLeave
    +
    +	${NSD_GetText} $Text $0
    +	MessageBox MB_OK "You typed:$\n$\n$0"
    +
    +FunctionEnd
    +
    +Function nsDialogsPageTextChange
    +
    +	Pop $1 # $1 == $ Text
    +
    +	${NSD_GetText} $Text $0
    +
    +	${If} $0 == "hello"
    +
    +		MessageBox MB_OK "right back at ya!"
    +
    +	${EndIf}
    +
    +FunctionEnd
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Memory

    + +

    So far we have a page that has some basic input controls. But what happens when the user goes to the next page and comes back? With the current code, the user's input will not be remembered. To remember, we'll use the already present leave callback function to store the user's choice in variables and pass these values when creating the controls the next time. For a better example, we'll also add a checkbox to the page and use ${NSD_GetState} and ${NSD_SetState} to get and set its state.

    + +

    For clarity, we'll remove some of the notifications from the previous step.

    + +
    !include nsDialogs.nsh
    +!include LogicLib.nsh
    +
    +Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Var Dialog
    +Var Label
    +Var Text
    +Var Text_State
    +Var Checkbox
    +Var Checkbox_State
    +
    +Page custom nsDialogsPage nsDialogsPageLeave
    +Page license
    +Page instfiles
    +
    +Function .onInit
    +
    +	StrCpy $Text_State "Type something here..."
    +
    +FunctionEnd
    +
    +Function nsDialogsPage
    +
    +	nsDialogs::Create 1018
    +	Pop $Dialog
    +
    +	${If} $Dialog == error
    +		Abort
    +	${EndIf}
    +
    +	${NSD_CreateLabel} 0 0 100% 12u "Hello, welcome to nsDialogs!"
    +	Pop $Label
    +
    +	${NSD_CreateText} 0 13u 100% 12u $Text_State
    +	Pop $Text
    +
    +	${NSD_CreateCheckbox} 0 30u 100% 10u "&Something"
    +	Pop $Checkbox
    +
    +	${If} $Checkbox_State == ${BST_CHECKED}
    +		${NSD_Check} $Checkbox
    +	${EndIf}
    +
    +	# alternative for the above ${If}:
    +	#${NSD_SetState} $Checkbox_State
    +
    +	nsDialogs::Show
    +
    +FunctionEnd
    +
    +Function nsDialogsPageLeave
    +
    +	${NSD_GetText} $Text $Text_State
    +	${NSD_GetState} $Checkbox $Checkbox_State
    +
    +FunctionEnd
    +
    +Section
    +
    +	DetailPrint "hello world"
    +
    +SectionEnd
    + +

    Function Reference

    + +

    Create

    + +

    nsDialogs::Create rect

    + +

    Creates a new dialog. rect specific the identifier of the control whose location will be mimiced. This should usually be 1018, which is control mimiced for creation of built-in pages. The Modern UI also has control 1040 for the welcome and the finish page.

    + +

    Returns the new dialog's HWND on the stack or error.

    + +

    CreateControl

    + +

    nsDialogs::CreateControl class style extended_style x y width height text

    + +

    Create a new control in the current dialog. A dialog must exist for this to work, so nsDialogs::Create must be called prior to this function.

    + +

    Returns the new dialog's HWND on the stack or error.

    + +

    Show

    + +

    nsDialogs::Show

    + +

    Displays the page. Call this once finished with nsDialogs::Create, nsDialogs::CreateControl and the rest.

    + +

    Returns nothing.

    + +

    SelectFileDialog

    + +

    nsDialogs::SelectFileDialog mode initial_selection filter

    + +

    Displays a file selection dialog to the user. If mode is set to save, displays a file save dialog. If mode is set to open, displays a file open dialog. filter is a list of available file filters separated by pipes. If an empty string is passed, the default is used - All Files|*.*.

    + +

    initial_selection can be used to provide the user with a default file to look for and/or a default folder to look in. If initial_selection is empty no default filename will be provided for the user and the dialog will start in the current working directory. If initial_selection specifies just a filename, for example "test.exe", the dialog will be set up to look for a file called test.exe in the current working directory. If initial_selection specifies just a directory, for example "C:\Program Files", the dialog starts in the provided directory with no file name provided. If initial_selection specifies a directory and a filename, for example "C:\Windows\System32\calc.exe", the dialog will be set up to look for a file called calc.exe in the directory C:\Windows\System32.

    + +

    Returns the selected file on the stack or an empty string if the user canceled the operation.

    + +

    SelectFolderDialog

    + +

    nsDialogs::SelectFolderDialog title initial_selection

    + +

    Displays a directory selection dialog to the user.

    + +

    Returns the selected directory on the stack or "error" in case the user canceled the operation or an error occured.

    + +

    SetRTL

    + +

    nsDialogs::SetRTL rtl_setting

    + +

    Sets right-to-left mode on or off. If rtl_setting is 0, it's set to off. If rtl_setting is 1, it's set to on. This function must be called before any calls to nsDialogs::CreateControl.

    + +

    Returns nothing.

    + +

    GetUserData

    + +

    nsDialogs::GetUserData control_HWND

    + +

    Returns user data associated with the control on the stack. Use nsDialogs::SetUserData to set this data.

    + +

    SetUserData

    + +

    nsDialogs::SetUserData control_HWND data

    + +

    Associates data with the control. Use nsDialogs::GetUserData to get this data.

    + +

    Returns nothing.

    + +

    OnBack

    + +

    nsDialogs::OnBack function_address

    + +

    Sets the callback function for the Back button. This function will be called when the user clicks the back button. Call Abort in this function to prevent the user from going back to the last page.

    + +

    Use GetFunctionAddress to get the address of the desired callback function.

    + +

    Returns nothing.

    + +

    OnChange

    + +

    nsDialogs::OnChange control_HWND function_address

    + +

    Sets a change notification callback function for the given control. Whenever the control changes, the function will be called and the control's HWND will be waiting on its stack.

    + +

    Use GetFunctionAddress to get the address of the desired callback function.

    + +

    Returns nothing.

    + +

    OnClick

    + +

    nsDialogs::OnClick control_HWND function_address

    + +

    Sets a click notification callback function for the given control. Whenever the control is clicked, the function will be called and the control's HWND will be waiting on its stack.

    + +

    Use GetFunctionAddress to get the address of the desired callback function.

    + +

    Returns nothing.

    + +

    OnNotify

    + +

    nsDialogs::OnNotify control_HWND function_address

    + +

    Sets a notification callback function for the given control. Whenever the control receives the WM_NOTIFY message, the function will be called and the control's HWND, notification code and a pointer to the MNHDR structure will be waiting on its stack.

    + +

    Use GetFunctionAddress to get the address of the desired callback function.

    + +

    Returns nothing.

    + +

    CreateTimer

    + +

    nsDialogs::CreateTimer function_address timer_interval

    + +

    Sets a timer that'd call the callback function for the given control every in a constant interval. Interval times are specified in milliseconds.

    + +

    Use GetFunctionAddress to get the address of the desired callback function.

    + +

    Returns nothing.

    + +

    KillTimer

    + +

    nsDialogs::KillTimer function_address

    + +

    Kills a previously set timer.

    + +

    Use GetFunctionAddress to get the address of the desired callback function.

    + +

    Returns nothing.

    + +

    Macro Reference

    + +

    nsDialogs.nsh contains a lot of macros that can make nsDialogs usage a lot easier. Below is a description of each of those macros including purpose, syntax, input and output.

    + +

    NSD_Create*

    + +

    ${NSD_Create*} x y width height text

    + +

    Create a new control in the current dialog. A dialog must exist for this to work, so nsDialogs::Create must be called prior to this function.

    + +

    Available variants:

    + +
      +
    • ${NSD_CreateHLine}
    • +
    • ${NSD_CreateVLine}
    • +
    • ${NSD_CreateLabel}
    • +
    • ${NSD_CreateIcon}
    • +
    • ${NSD_CreateBitmap}
    • +
    • ${NSD_CreateBrowseButton}
    • +
    • ${NSD_CreateLink}
    • +
    • ${NSD_CreateButton}
    • +
    • ${NSD_CreateGroupBox}
    • +
    • ${NSD_CreateCheckBox}
    • +
    • ${NSD_CreateRadioButton}
    • +
    • ${NSD_CreateText}
    • +
    • ${NSD_CreatePassword}
    • +
    • ${NSD_CreateNumber}
    • +
    • ${NSD_CreateFileRequest}
    • +
    • ${NSD_CreateDirRequest}
    • +
    • ${NSD_CreateComboBox}
    • +
    • ${NSD_CreateDropList}
    • +
    • ${NSD_CreateListBox}
    • +
    • ${NSD_CreateProgressBar}
    • +
    + +

    Returns the new dialog's HWND on the stack or error.

    + +

    NSD_OnBack

    + +

    ${NSD_OnBack} function_address

    + +

    See OnBack for more details.

    + +

    + +

    NSD_OnChange

    + +

    ${NSD_OnChange} control_HWND function_address

    + +

    See OnChange for more details.

    + +

    See Real-time Notification for usage example.

    + +

    NSD_OnClick

    + +

    ${NSD_OnClick} control_HWND function_address

    + +

    See OnClick for more details.

    + +

    NSD_OnNotify

    + +

    ${NSD_OnNotify} control_HWND function_address

    + +

    See OnNotify for more details.

    + +

    NSD_CreateTimer

    + +

    ${NSD_CreateTimer} function_address timer_interval

    + +

    See CreateTimer for more details.

    + +

    NSD_KillTimer

    + +

    ${NSD_KillTimer} function_address

    + +

    See KillTimer for more details.

    + +

    NSD_AddStyle

    + +

    ${NSD_AddStyle} control_HWND style

    + +

    Adds one or more window style to a control. Multiple styles should be separated with pipes `|'.

    + +

    See MSDN for style description.

    + +

    NSD_AddExStyle

    + +

    ${NSD_AddExStyle} control_HWND style

    + +

    Adds one or more extended window style to a control. Multiple styles should be separated with pipes `|'.

    + +

    See MSDN for style description.

    + +

    NSD_GetText

    + +

    ${NSD_GetText} control_HWND output_variable

    + +

    Retrieves the text of a control and stores it into output_variable. Especially useful for textual controls.

    + +

    See Control State for usage example.

    + +

    NSD_SetText

    + +

    ${NSD_SetText} control_HWND text

    + +

    Sets the text of a control.

    + +

    NSD_SetTextLimit

    + +

    ${NSD_SetTextLimit} control_HWND limit

    + +

    Sets input size limit for a text control.

    + +

    NSD_GetState

    + +

    ${NSD_GetState} control_HWND output_variable

    + +

    Retrieves the state of a check box or a radio button control. Possible outputs are ${BST_CHECKED} and ${BST_UNCHECKED}.

    + +

    See Memory for usage example.

    + +

    NSD_SetState

    + +

    ${NSD_SetState} control_HWND state

    + +

    Sets the state of a check box or a radio button control. Possible values for state are ${BST_CHECKED} and ${BST_UNCHECKED}.

    + +

    See Memory for usage example.

    + +

    NSD_Check

    + +

    ${NSD_Check} control_HWND

    + +

    Checks a check box or a radio button control. Same as calling ${NSD_SetState} with ${BST_CHECKED}.

    + +

    NSD_Uncheck

    + +

    ${NSD_Uncheck} control_HWND

    + +

    Unchecks a check box or a radio button control. Same as calling ${NSD_SetState} with ${BST_UNCHECKED}.

    + +

    See Memory for usage example.

    + +

    NSD_CB_AddString

    + +

    ${NSD_CB_AddString} combo_HWND string

    + +

    Adds a string to a combo box.

    + +

    NSD_CB_SelectString

    + +

    ${NSD_CB_SelectString} combo_HWND string

    + +

    Selects a string in a combo box.

    + +

    NSD_LB_AddString

    + +

    ${NSD_LB_AddString} listbox_HWND string

    + +

    Adds a string to a list box.

    + +

    NSD_LB_DelString

    + +

    ${NSD_LB_DelString} listbox_HWND string

    + +

    Deletes a string from a list box.

    + +

    NSD_LB_Clear

    + +

    ${NSD_LB_Clear} listbox_HWND

    + +

    Deletes all strings from a list box.

    + +

    NSD_LB_GetCount

    + +

    ${NSD_LB_GetCount} listbox_HWND output_variable

    + +

    Retrieves the number of strings from a list box.

    + +

    NSD_LB_SelectString

    + +

    ${NSD_LB_SelectString} listbox_HWND string

    + +

    Selects a string in a list box.

    + +

    NSD_LB_GetSelection

    + +

    ${NSD_LB_GetSelection} listbox_HWND output_variable

    + +

    Retrieves the selected stringed from a list box. Returns an empty string if no string is selected.

    + +

    NSD_SetFocus

    + +

    ${NSD_SetFocus} control_HWND

    + +

    Sets focus to a control.

    + +

    NSD_SetImage

    + +

    ${NSD_SetImage} control_HWND image_path output_variable

    + +

    Loads a bitmap from image_path and displays it on control_HWND created by ${NSD_CreateBitmap}. The image handle is stored in output_variable and should be freed using ${NSD_FreeImage} once no longer necessary.

    + +

    The image must be extracted to the user's computer prior to calling this macro. A good place to extract images is $PLUGINSDIR.

    + +
    !include nsDialogs.nsh
    +
    +Name nsDialogs
    +OutFile nsDialogs.exe
    +
    +XPStyle on
    +
    +Page custom nsDialogsImage
    +Page instfiles
    +
    +Var Dialog
    +Var Image
    +Var ImageHandle
    +
    +Function .onInit
    +
    +	InitPluginsDir
    +	File /oname=$PLUGINSDIR\image.bmp "${NSISDIR}\Contrib\Graphics\Header\nsis-r.bmp"
    +
    +FunctionEnd
    +
    +Function nsDialogsImage
    +
    +	nsDialogs::Create 1018
    +	Pop $Dialog
    +
    +	${If} $Dialog == error
    +		Abort
    +	${EndIf}
    +
    +	${NSD_CreateBitmap} 0 0 100% 100% ""
    +	Pop $Image
    +	${NSD_SetImage} $Image $PLUGINSDIR\image.bmp $ImageHandle
    +
    +	nsDialogs::Show
    +
    +	${NSD_FreeImage} $ImageHandle
    +
    +FunctionEnd
    +
    +Section
    +SectionEnd
    + +

    NSD_SetStretchedImage

    + +

    ${NSD_SetStretchedImage} control_HWND image_path output_variable

    + +

    Loads and displays a bitmap just like ${NSD_SetImage}, but stretched the image to fit the control.

    + +

    NSD_SetIcon

    + +

    ${NSD_SetIcon} control_HWND image_path output_variable

    + +

    Same as ${NSD_SetImage}, but used for loading and setting an icon in a control created by ${NSD_CreateIcon}. The image handle is stored in output_variable and should be freed using ${NSD_FreeIcon} once no longer necessary.

    + +

    NSD_SetIconFromInstaller

    + +

    ${NSD_SetIconFromInstaller} control_HWND output_variable

    + +

    Loads the icon used in the isntaller and displays it on control_HWND created by ${NSD_CreateIcon}. The image handle is stored in output_variable and should be freed using ${NSD_FreeIcon} once no longer necessary.

    + +

    NSD_ClearImage

    + +

    ${NSD_ClearImage} control_HWND

    + +

    Clears an image from a control.

    + +

    NSD_ClearIcon

    + +

    ${NSD_ClearIcon} control_HWND

    + +

    Clears an icon from a control.

    + +

    NSD_FreeImage

    + +

    ${NSD_FreeImage} image_handle

    + +

    Frees an image handle previously loaded with ${NSD_SetImage} or ${NSD_SetStretchedImage}.

    + +

    NSD_FreeIcon

    + +

    ${NSD_FreeIcon} icon_handle

    + +

    Frees an icon handle previously loaded with ${NSD_SetIcon} or ${NSD_SetIconFromInstaller}.

    + + +

    FAQ

    + +
    + +
      + +
    • +Q: Can nsDialogs handle InstallOptions INI files? +
      +

      A: nsDialogs.nsh contains a function called CreateDialogFromINI that can create nsDialogs' dialog from an INI file. It can handle every type of control InstallOptions supports, but doesn't handle the flags or notification yet. Examples\nsDialogs\InstallOptions.nsi shows a usage example of this function.

      In the future there'll also be a function that creates the script itself. +

      +
    • + +
    + +
    + + + diff --git a/installer/NSIS/Docs/nsExec/nsExec.txt b/installer/NSIS/Docs/nsExec/nsExec.txt new file mode 100644 index 0000000..3fcb5cf --- /dev/null +++ b/installer/NSIS/Docs/nsExec/nsExec.txt @@ -0,0 +1,49 @@ +nsExec +------ +nsExec will execute command-line based programs and capture the output +without opening a dos box. + + +Usage +----- +nsExec::Exec [/OEM] [/TIMEOUT=x] path + +-or- + +nsExec::ExecToLog [/OEM] [/TIMEOUT=x] path + +-or- + +nsExec::ExecToStack [/OEM] [/TIMEOUT=x] path + +All functions are the same except ExecToLog will print the output +to the log window and ExecToStack will push up to ${NSIS_MAX_STRLEN} +characters of output onto the stack after the return value. + +Use the /OEM switch to convert the output text from OEM to ANSI. + +The timeout value is optional. The timeout is the time in +milliseconds nsExec will wait for output. If output from the +process is received, the timeout value is reset and it will +again wait for more output using the timeout value. See Return +Value for how to check if there was a timeout. + +To ensure that command are executed without problems on all windows versions, +is recommended to use the following syntax: + + nsExec::ExecToStack [OPTIONS] '"PATH" param1 param2 paramN' + +This way the application path may contain non 8.3 paths (with spaces) + +Return Value +------------ +If nsExec is unable to execute the process, it will return "error" +on the top of the stack, if the process timed out it will return +"timeout", else it will return the return code from the +executed process. + + +Copyright Info +-------------- +Copyright (c) 2002 Robert Rainwater +Thanks to Justin Frankel and Amir Szekely \ No newline at end of file diff --git a/installer/NSIS/Examples/AdvSplash/Example.nsi b/installer/NSIS/Examples/AdvSplash/Example.nsi new file mode 100644 index 0000000..751c836 --- /dev/null +++ b/installer/NSIS/Examples/AdvSplash/Example.nsi @@ -0,0 +1,35 @@ +Name "AdvSplash.dll test" + +OutFile "AdvSplash Test.exe" + +XPStyle on + +Function .onInit + # the plugins dir is automatically deleted when the installer exits + InitPluginsDir + File /oname=$PLUGINSDIR\splash.bmp "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp" + #optional + #File /oname=$PLUGINSDIR\splash.wav "C:\myprog\sound.wav" + + MessageBox MB_OK "Fading" + + advsplash::show 1000 600 400 -1 $PLUGINSDIR\splash + + Pop $0 ; $0 has '1' if the user closed the splash screen early, + ; '0' if everything closed normally, and '-1' if some error occurred. + + MessageBox MB_OK "Transparency" + File /oname=$PLUGINSDIR\splash.bmp "${NSISDIR}\Contrib\Graphics\Wizard\orange-uninstall.bmp" + advsplash::show 2000 0 0 0x1856B1 $PLUGINSDIR\splash + Pop $0 + + MessageBox MB_OK "Transparency/Fading" + File /oname=$PLUGINSDIR\splash.bmp "${NSISDIR}\Contrib\Graphics\Wizard\llama.bmp" + advsplash::show 1000 600 400 0x04025C $PLUGINSDIR\splash + Pop $0 + + Delete $PLUGINSDIR\splash.bmp +FunctionEnd + +Section +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/Banner/Example.nsi b/installer/NSIS/Examples/Banner/Example.nsi new file mode 100644 index 0000000..2a98e87 --- /dev/null +++ b/installer/NSIS/Examples/Banner/Example.nsi @@ -0,0 +1,38 @@ +# Look at Readme.txt for usage alongside with the Modern UI + +!include "WinMessages.nsh" + +Name "Banner.dll test" + +OutFile "Banner Test.exe" + +ShowInstDetails show + +Function .onInit + Banner::show "Calculating important stuff..." + + Banner::getWindow + Pop $1 + + again: + IntOp $0 $0 + 1 + Sleep 1 + StrCmp $0 100 0 again + + GetDlgItem $2 $1 1030 + SendMessage $2 ${WM_SETTEXT} 0 "STR:Calculating more important stuff..." + + again2: + IntOp $0 $0 + 1 + Sleep 1 + StrCmp $0 200 0 again2 + + Banner::destroy +FunctionEnd + +Section + DetailPrint "Using previous calculations to quickly calculate 1*2000..." + Sleep 1000 + DetailPrint "Eureka! It's $0!!!" + DetailPrint "" +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/BgImage/Example.nsi b/installer/NSIS/Examples/BgImage/Example.nsi new file mode 100644 index 0000000..14154dd --- /dev/null +++ b/installer/NSIS/Examples/BgImage/Example.nsi @@ -0,0 +1,100 @@ +Name "BgImage.dll test" + +OutFile "BgImage Test.exe" + +XPStyle on + +!define DEBUG +!macro GetReturnValue +!ifdef DEBUG + Pop $R9 + StrCmp $R9 success +2 + DetailPrint "Error: $R9" +!endif +!macroend + +Function .onGUIInit + # the plugins dir is automatically deleted when the installer exits + InitPluginsDir + # lets extract some bitmaps... + File /oname=$PLUGINSDIR\1.bmp "${NSISDIR}\Contrib\Graphics\Wizard\llama.bmp" + File /oname=$PLUGINSDIR\2.bmp "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" + +!ifdef DEBUG + # turn return values on if in debug mode + BgImage::SetReturn on +!endif + + # set the initial background for images to be drawn on + # we will use a gradient from drak green to dark red + BgImage::SetBg /GRADIENT 0 0x80 0 0x80 0 0 + !insertmacro GetReturnValue + # add an image @ (150,0) + BgImage::AddImage $PLUGINSDIR\2.bmp 150 0 + !insertmacro GetReturnValue + # add the same image only transparent (magenta wiped) @ (150,16) + BgImage::AddImage /TRANSPARENT 255 0 255 $PLUGINSDIR\2.bmp 150 16 + !insertmacro GetReturnValue + # create the font for the following text + CreateFont $R0 "Comic Sans MS" 50 700 + # add a blue shadow for the text + BgImage::AddText "Testing 1... 2... 3..." $R0 0 0 255 48 48 798 198 + !insertmacro GetReturnValue + # add a green shadow for the text + BgImage::AddText "Testing 1... 2... 3..." $R0 0 255 0 52 52 802 202 + !insertmacro GetReturnValue + # add the text + BgImage::AddText "Testing 1... 2... 3..." $R0 255 0 0 50 50 800 200 + !insertmacro GetReturnValue + # show our creation to the world! + BgImage::Redraw + # Refresh doesn't return any value + +FunctionEnd + +ShowInstDetails show + +Section + # play some sounds + FindFirst $0 $1 $WINDIR\Media\*.wav + StrCmp $0 "" skipSound + moreSounds: + StrCmp $1 "" noMoreSounds + BgImage::Sound /WAIT $WINDIR\Media\$1 + # Sound doesn't return any value either + MessageBox MB_YESNO "Another sound?" IDNO noMoreSounds + FindNext $0 $1 + Goto moreSounds + + noMoreSounds: + FindClose $0 + skipSound: + + # change the background image to Mike, tiled + BgImage::SetBg /TILED $PLUGINSDIR\1.bmp + !insertmacro GetReturnValue + # we have to redraw to reflect the changes + BgImage::Redraw + + MessageBox MB_OK "Mike the llama" + + # clear everything + BgImage::Clear + # Clear doesn't return any value + # set another gradient + BgImage::SetBg /GRADIENT 0xFF 0xFA 0xBA 0xAA 0xA5 0x65 + !insertmacro GetReturnValue + # add some text + BgImage::AddText "A Desert for Mike" $R0 0 0 0 50 50 800 150 + !insertmacro GetReturnValue + # add mike as an image + BgImage::AddImage $PLUGINSDIR\1.bmp 50 150 + !insertmacro GetReturnValue + # again, we have to call redraw to reflect changes + BgImage::Redraw +SectionEnd + +Function .onGUIEnd + BgImage::Destroy + # Destroy doesn't return any value +FunctionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/FileFunc.ini b/installer/NSIS/Examples/FileFunc.ini new file mode 100644 index 0000000..5b227f4 --- /dev/null +++ b/installer/NSIS/Examples/FileFunc.ini @@ -0,0 +1,91 @@ +[Settings] +NumFields=11 +NextButtonText=&Enter + +[Field 1] +Type=Droplist +Flags=NOTIFY +State=" 1. Locate" +ListItems=| 1. Locate| 2. GetSize (file)| (directory)| (no size, no subdir)| 3. DriveSpace| 4. GetDrives (by type)| (all by letter)| 5. GetTime (local time)| (file time)| 6. GetFileAttributes| 7. GetFileVersion| 8. GetExeName| 9. GetExePath|10. GetParameters|11. GetOptions|12. GetRoot|13. GetParent|14. GetFileName|15. GetBaseName|16. GetFileExt|17. BannerTrimPath|18. DirState|19. RefreshShellIcons +Left=44 +Right=190 +Top=1 +Bottom=210 + +[Field 2] +Type=FileRequest +Left=44 +Right=-10 +Top=22 +Bottom=33 + +[Field 3] +Type=DirRequest +Left=44 +Right=-10 +Top=22 +Bottom=33 + +[Field 4] +Type=Text +Left=44 +Right=-10 +Top=36 +Bottom=49 + +[Field 5] +Type=Text +State=LocateCallback +Left=44 +Right=232 +Top=53 +Bottom=66 + +[Field 6] +Type=Button +Text=view +Flags=NOTIFY +Left=236 +Right=255 +Top=54 +Bottom=65 + +[Field 7] +Type=Text +Flags=MULTILINE|VSCROLL|HSCROLL|READONLY +Left=44 +Right=-10 +Top=73 +Bottom=128 + +[Field 8] +Type=Label +Text=Path +Left=10 +Right=43 +Top=24 +Bottom=36 + +[Field 9] +Type=Label +Text=Options +Left=10 +Right=43 +Top=40 +Bottom=52 + +[Field 10] +Type=Label +Text=Function +Left=10 +Right=44 +Top=56 +Bottom=67 + +[Field 11] +Type=Label +Text=Result: +Left=12 +Right=42 +Top=94 +Bottom=102 diff --git a/installer/NSIS/Examples/FileFunc.nsi b/installer/NSIS/Examples/FileFunc.nsi new file mode 100644 index 0000000..f109ec7 --- /dev/null +++ b/installer/NSIS/Examples/FileFunc.nsi @@ -0,0 +1,732 @@ +;_____________________________________________________________________________ +; +; File Functions +;_____________________________________________________________________________ +; +; 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + +Name "File Functions" +OutFile "FileFunc.exe" +Caption "$(^Name)" +XPStyle on +RequestExecutionLevel user + +!include "WinMessages.nsh" +!include "FileFunc.nsh" + +Var INI +Var HWND +Var STATE +Var FUNCTION +Var LOCATE1 +Var LOCATE2 +Var GETSIZE1 +Var GETSIZE2 +Var GETSIZE3 +Var GETSIZE4 +Var GETSIZE5 +Var GETSIZE6 +Var DRIVESPACE1 +Var DRIVESPACE2 +Var GETDRIVES1 +Var GETTIME1 +Var GETTIME2 +Var GETFILEATTRIBUTES1 +Var GETFILEATTRIBUTES2 +Var GETFILEVERSION1 +Var GETOPTIONS1 +Var GETOPTIONS2 +Var GETROOT1 +Var GETPARENT1 +Var GETFILENAME1 +Var GETBASENAME1 +Var GETFILEEXT1 +Var BANNERTRIMPATH1 +Var BANNERTRIMPATH2 +Var DIRSTATE1 + +Page Custom ShowCustom LeaveCustom + +Function ShowCustom + InstallOptions::initDialog "$INI" + Pop $hwnd + GetDlgItem $1 $HWND 1201 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1202 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1206 + EnableWindow $1 0 + SendMessage $1 ${WM_ENABLE} 1 0 + StrCpy $LOCATE1 $DOCUMENTS + StrCpy $LOCATE2 '/L=FD /M=*.* /S=0B /G=1 /B=0' + StrCpy $GETSIZE1 '$WINDIR' + StrCpy $GETSIZE2 '/M=Explorer.exe /S=0K /G=0' + StrCpy $GETSIZE3 '$PROGRAMFILES\Common Files' + StrCpy $GETSIZE4 '/S=0M' + StrCpy $GETSIZE5 '$WINDIR' + StrCpy $GETSIZE6 '/G=0' + StrCpy $DRIVESPACE1 'C:\' + StrCpy $DRIVESPACE2 '/D=F /S=M' + StrCpy $GETDRIVES1 'FDD+CDROM' + StrCpy $GETTIME1 '$WINDIR\Explorer.exe' + StrCpy $GETTIME2 'C' + StrCpy $GETFILEATTRIBUTES1 'C:\IO.SYS' + StrCpy $GETFILEATTRIBUTES2 'ALL' + StrCpy $GETFILEVERSION1 '$WINDIR\Explorer.exe' + StrCpy $GETOPTIONS1 '/SILENT=yes /INSTDIR="$PROGRAMFILES\Common Files"' + StrCpy $GETOPTIONS2 '/INSTDIR=' + StrCpy $GETROOT1 'C:\path\file.dll' + StrCpy $GETPARENT1 'C:\path\file.dll' + StrCpy $GETFILENAME1 'C:\path\file.dll' + StrCpy $GETBASENAME1 'C:\path\file.dll' + StrCpy $GETFILEEXT1 'C:\path\file.dll' + StrCpy $BANNERTRIMPATH1 'C:\Server\Documents\Terminal\license.htm' + StrCpy $BANNERTRIMPATH2 '34A' + StrCpy $DIRSTATE1 '$TEMP' + + GetDlgItem $1 $HWND 1203 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$LOCATE1" + GetDlgItem $1 $HWND 1205 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$LOCATE2" + InstallOptions::show + Pop $0 +FunctionEnd + +Function LeaveCustom + ReadINIStr $STATE $INI "Field 1" "State" + ReadINIStr $R1 $INI "Field 2" "State" + ReadINIStr $R2 $INI "Field 3" "State" + ReadINIStr $R3 $INI "Field 4" "State" + ReadINIStr $R4 $INI "Field 5" "State" + ReadINIStr $0 $INI "Settings" "State" + StrCmp $0 6 view + StrCmp $0 0 Enter + goto main + + view: + StrCpy $0 '$$' + StrCpy $1 'n' + StrCpy $2 'r' + StrCmp $R4 "LocateCallback" 0 +3 + StrCpy $R0 `Function LocateCallback$\r$\n MessageBox MB_OKCANCEL '$0$$R9 "path\name"=[$$R9]$0\$1$0$$R8 "path" =[$$R8]$0\$1$0$$R7 "name" =[$$R7]$0\$1$0$$R6 "size" =[$$R6]' IDOK +2$\r$\n StrCpy $$R0 StopLocate$\r$\n$\r$\n Push $$R0$\r$\nFunctionEnd` + goto send + StrCmp $R4 "GetDrivesCallback" 0 error + StrCpy $R0 `Function GetDrivesCallback$\r$\n MessageBox MB_OKCANCEL '$0$$9 "drive letter"=[$$9]$0\$1$0$$8 "drive type" =[$$8]' IDOK +2$\r$\n StrCpy $$R0 StopGetDrives$\r$\n StrCpy $$R5 '$$R5$$9 [$$8 Drive]$$\$2$$\$1'$\r$\n$\r$\n Push $$R0$\r$\nFunctionEnd` + goto send + + main: + StrCmp $FUNCTION '' DefaultSend + StrCmp $FUNCTION Locate 0 +4 + StrCpy $LOCATE1 $R2 + StrCpy $LOCATE2 $R3 + goto DefaultSend + StrCmp $FUNCTION GetSize1 0 +4 + StrCpy $GETSIZE1 $R2 + StrCpy $GETSIZE2 $R3 + goto DefaultSend + StrCmp $FUNCTION GetSize2 0 +4 + StrCpy $GETSIZE3 $R2 + StrCpy $GETSIZE4 $R3 + goto DefaultSend + StrCmp $FUNCTION GetSize3 0 +4 + StrCpy $GETSIZE5 $R2 + StrCpy $GETSIZE6 $R3 + goto DefaultSend + StrCmp $FUNCTION DriveSpace 0 +4 + StrCpy $DRIVESPACE1 $R1 + StrCpy $DRIVESPACE2 $R3 + goto DefaultSend + StrCmp $FUNCTION GetDrives 0 +3 + StrCpy $GETDRIVES1 $R1 + goto DefaultSend + StrCmp $FUNCTION GetTime 0 +4 + StrCpy $GETTIME1 $R1 + StrCpy $GETTIME2 $R3 + goto DefaultSend + StrCmp $FUNCTION GetFileAttributes 0 +4 + StrCpy $GETFILEATTRIBUTES1 $R1 + StrCpy $GETFILEATTRIBUTES2 $R3 + goto DefaultSend + StrCmp $FUNCTION GetFileVersion 0 +3 + StrCpy $GETFILEVERSION1 $R1 + goto DefaultSend + StrCmp $FUNCTION GetOptions 0 +4 + StrCpy $GETOPTIONS1 $R1 + StrCpy $GETOPTIONS2 $R3 + goto DefaultSend + StrCmp $FUNCTION GetRoot 0 +3 + StrCpy $GETROOT1 $R1 + goto DefaultSend + StrCmp $FUNCTION GetParent 0 +3 + StrCpy $GETPARENT1 $R1 + goto DefaultSend + StrCmp $FUNCTION GetFileName 0 +3 + StrCpy $GETFILENAME1 $R1 + goto DefaultSend + StrCmp $FUNCTION GetBaseName 0 +3 + StrCpy $GETBASENAME1 $R1 + goto DefaultSend + StrCmp $FUNCTION GetFileExt 0 +3 + StrCpy $GETFILEEXT1 $R1 + goto DefaultSend + StrCmp $FUNCTION BannerTrimPath 0 +4 + StrCpy $BANNERTRIMPATH1 $R1 + StrCpy $BANNERTRIMPATH2 $R3 + goto DefaultSend + StrCmp $FUNCTION DirState 0 +2 + StrCpy $DIRSTATE1 $R2 + + DefaultSend: + GetDlgItem $1 $HWND 1201 + EnableWindow $1 1 + ShowWindow $1 0 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1202 + EnableWindow $1 1 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1203 + EnableWindow $1 1 + ShowWindow $1 0 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1204 + EnableWindow $1 1 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1205 + EnableWindow $1 1 + GetDlgItem $1 $HWND 1206 + ShowWindow $1 0 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1207 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + + ReadINIStr $0 $INI "Field 1" "State" + StrCmp $0 " 1. Locate" 0 GetSize1Send + StrCpy $FUNCTION Locate + GetDlgItem $1 $HWND 1203 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$LOCATE1" + GetDlgItem $1 $HWND 1204 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$LOCATE2" + GetDlgItem $1 $HWND 1206 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:LocateCallback" + GetDlgItem $1 $HWND 1207 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Path" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Options" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Function" + abort + + GetSize1Send: + StrCmp $0 " 2. GetSize (file)" 0 GetSize2Send + StrCpy $FUNCTION 'GetSize1' + GetDlgItem $1 $HWND 1203 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETSIZE1" + GetDlgItem $1 $HWND 1204 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETSIZE2" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:File" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Options" + Abort + + GetSize2Send: + StrCmp $0 " (directory)" 0 GetSize3Send + StrCpy $FUNCTION 'GetSize2' + GetDlgItem $1 $HWND 1203 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETSIZE3" + GetDlgItem $1 $HWND 1204 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETSIZE4" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Directory" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Options" + Abort + + GetSize3Send: + StrCmp $0 " (no size, no subdir)" 0 DriveSpaceSend + StrCpy $FUNCTION 'GetSize3' + GetDlgItem $1 $HWND 1203 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETSIZE5" + GetDlgItem $1 $HWND 1204 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETSIZE6" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Directory" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Options" + Abort + + DriveSpaceSend: + StrCmp $0 " 3. DriveSpace" 0 GetDrivesSend + StrCpy $FUNCTION DriveSpace + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$DRIVESPACE1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$DRIVESPACE2" + GetDlgItem $1 $HWND 1206 + ShowWindow $1 0 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1207 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Drive" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Options" + abort + + GetDrivesSend: + StrCmp $0 " 4. GetDrives (by type)" 0 GetDrives2Send + StrCpy $FUNCTION GetDrives + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETDRIVES1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1206 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:GetDrivesCallback" + GetDlgItem $1 $HWND 1207 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Function" + abort + + GetDrives2Send: + StrCmp $0 " (all by letter)" 0 GetTime1Send + StrCpy $FUNCTION '' + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + EnableWindow $1 0 + SendMessage $1 ${WM_ENABLE} 1 0 + SendMessage $1 ${WM_SETTEXT} 1 "STR:ALL" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1206 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:GetDrivesCallback" + GetDlgItem $1 $HWND 1207 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Function" + abort + + GetTime1Send: + StrCmp $0 " 5. GetTime (local time)" 0 GetTime2Send + StrCpy $FUNCTION '' + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + EnableWindow $1 0 + SendMessage $1 ${WM_ENABLE} 1 0 + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + EnableWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:L" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + Abort + + GetTime2Send: + StrCmp $0 " (file time)" 0 GetFileAttributesSend + StrCpy $FUNCTION GetTime + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETTIME1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETTIME2" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:File" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + Abort + + GetFileAttributesSend: + StrCmp $0 " 6. GetFileAttributes" 0 GetFileVersionSend + StrCpy $FUNCTION GetFileAttributes + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETFILEATTRIBUTES1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETFILEATTRIBUTES2" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Path" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Attrib" + Abort + + GetFileVersionSend: + StrCmp $0 " 7. GetFileVersion" 0 GetCmdSend + StrCpy $FUNCTION GetFileVersion + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETFILEVERSION1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:File" + Abort + + GetCmdSend: + StrCmp $0 " 8. GetExeName" +3 + StrCmp $0 " 9. GetExePath" +2 + StrCmp $0 "10. GetParameters" 0 GetOptionsSend + StrCpy $FUNCTION '' + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + Abort + + GetOptionsSend: + StrCmp $0 "11. GetOptions" 0 GetRootSend + StrCpy $FUNCTION GetOptions + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETOPTIONS1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETOPTIONS2" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Parameters" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + Abort + + GetRootSend: + StrCmp $0 "12. GetRoot" 0 GetParentSend + StrCpy $FUNCTION GetRoot + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETROOT1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:FullPath" + Abort + + GetParentSend: + StrCmp $0 "13. GetParent" 0 GetFileNameSend + StrCpy $FUNCTION GetParent + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETPARENT1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:PathString" + Abort + + GetFileNameSend: + StrCmp $0 "14. GetFileName" 0 GetBaseNameSend + StrCpy $FUNCTION GetFileName + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETFILENAME1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:PathString" + Abort + + GetBaseNameSend: + StrCmp $0 "15. GetBaseName" 0 GetFileExtSend + StrCpy $FUNCTION GetBaseName + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETBASENAME1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:FileString" + Abort + + GetFileExtSend: + StrCmp $0 "16. GetFileExt" 0 BannerTrimPathSend + StrCpy $FUNCTION GetFileExt + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$GETFILEEXT1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:FileString" + Abort + + BannerTrimPathSend: + StrCmp $0 "17. BannerTrimPath" 0 DirStateSend + StrCpy $FUNCTION BannerTrimPath + GetDlgItem $1 $HWND 1201 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$BANNERTRIMPATH1" + GetDlgItem $1 $HWND 1202 + ShowWindow $1 1 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$BANNERTRIMPATH2" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:PathString" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + Abort + + DirStateSend: + StrCmp $0 "18. DirState" 0 RefreshShellIconsSend + StrCpy $FUNCTION DirState + GetDlgItem $1 $HWND 1203 + ShowWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$DIRSTATE1" + GetDlgItem $1 $HWND 1204 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Directory" + Abort + + RefreshShellIconsSend: + StrCmp $0 "19. RefreshShellIcons" 0 Abort + StrCpy $FUNCTION '' + GetDlgItem $1 $HWND 1205 + ShowWindow $1 0 + + Abort: + Abort + +;=Enter= + Enter: + StrCpy $R0 '' + StrCpy $R5 '' + + StrCmp $STATE " 1. Locate" Locate + StrCmp $STATE " 2. GetSize (file)" GetSize + StrCmp $STATE " (directory)" GetSize + StrCmp $STATE " (no size, no subdir)" GetSize + StrCmp $STATE " 3. DriveSpace" DriveSpace + StrCmp $STATE " 4. GetDrives (by type)" GetDrives + StrCmp $STATE " (all by letter)" GetDrives + StrCmp $STATE " 5. GetTime (local time)" GetTime + StrCmp $STATE " (file time)" GetTime + StrCmp $STATE " 6. GetFileAttributes" GetFileAttributes + StrCmp $STATE " 7. GetFileVersion" GetFileVersion + StrCmp $STATE " 8. GetExeName" GetExeName + StrCmp $STATE " 9. GetExePath" GetExePath + StrCmp $STATE "10. GetParameters" GetParameters + StrCmp $STATE "11. GetOptions" GetOptions + StrCmp $STATE "12. GetRoot" GetRoot + StrCmp $STATE "13. GetParent" GetParent + StrCmp $STATE "14. GetFileName" GetFileName + StrCmp $STATE "15. GetBaseName" GetBaseName + StrCmp $STATE "16. GetFileExt" GetFileExt + StrCmp $STATE "17. BannerTrimPath" BannerTrimPath + StrCmp $STATE "18. DirState" DirState + StrCmp $STATE "19. RefreshShellIcons" RefreshShellIcons + Abort + + Locate: + ${Locate} "$R2" "$R3" "LocateCallback" + IfErrors error + StrCmp $R0 StopLocate 0 +3 + StrCpy $R0 'stopped' + goto send + StrCpy $R0 'done' + goto send + + GetSize: + ${GetSize} "$R2" "$R3" $0 $1 $2 + IfErrors error + StrCpy $R0 "Size=$0$\r$\nFiles=$1$\r$\nFolders=$2" + goto send + + DriveSpace: + ${DriveSpace} "$R1" "$R3" $0 + IfErrors error + StrCpy $R0 "$0" + goto send + + GetDrives: + ${GetDrives} "$R1" "GetDrivesCallback" + StrCmp $R0 StopGetDrives 0 +3 + StrCpy $R0 '$R5stopped' + goto send + StrCpy $R0 '$R5done' + goto send + + GetTime: + ${GetTime} "$R1" "$R3" $0 $1 $2 $3 $4 $5 $6 + IfErrors error + StrCpy $R0 'Date=$0/$1/$2 ($3)$\r$\nTime=$4:$5:$6' + goto send + + GetFileAttributes: + ${GetFileAttributes} "$R1" "$R3" $0 + IfErrors error + StrCpy $R0 '$0' + goto send + + GetFileVersion: + ${GetFileVersion} "$R1" $0 + IfErrors error + StrCpy $R0 '$0' + goto send + + GetExeName: + ${GetExeName} $0 + StrCpy $R0 '$0' + goto send + + GetExePath: + ${GetExePath} $0 + StrCpy $R0 '$0' + goto send + + GetParameters: + ${GetParameters} $0 + StrCpy $R0 '$0' + StrCmp $R0 '' 0 send + StrCpy $R0 'no parameters' + goto send + + GetOptions: + ${GetOptions} "$R1" "$R3" $0 + IfErrors error + StrCpy $R0 '$0' + goto send + + GetRoot: + ${GetRoot} "$R1" $0 + StrCpy $R0 '$0' + goto send + + GetParent: + ${GetParent} "$R1" $0 + StrCpy $R0 '$0' + goto send + + GetFileName: + ${GetFileName} "$R1" $0 + StrCpy $R0 '$0' + goto send + + GetBaseName: + ${GetBaseName} "$R1" $0 + StrCpy $R0 '$0' + goto send + + GetFileExt: + ${GetFileExt} "$R1" $0 + StrCpy $R0 '$0' + goto send + + BannerTrimPath: + ${BannerTrimPath} "$R1" "$R3" $0 + StrCpy $R0 '$0' + goto send + + DirState: + ${DirState} "$R2" $0 + StrCpy $R0 '$0' + goto send + + RefreshShellIcons: + ${RefreshShellIcons} + StrCpy $R0 'done' + goto send + + error: + StrCpy $R0 'error' + + send: + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$R0" + + abort +FunctionEnd + +Function LocateCallback + MessageBox MB_OKCANCEL '$$R9 "path\name"=[$R9]$\n$$R8 "path" =[$R8]$\n$$R7 "name" =[$R7]$\n$$R6 "size" =[$R6]' IDOK +2 + StrCpy $R0 StopLocate + + Push $R0 +FunctionEnd + +Function GetDrivesCallback + MessageBox MB_OKCANCEL '$$9 "drive letter"=[$9]$\n$$8 "drive type" =[$8]' IDOK +2 + StrCpy $R0 StopGetDrives + StrCpy $R5 '$R5$9 [$8 Drive]$\r$\n' + + Push $R0 +FunctionEnd + +Function .onInit + InitPluginsDir + GetTempFileName $INI $PLUGINSDIR + File /oname=$INI "FileFunc.ini" +FunctionEnd + +Page instfiles + +Section "Empty" +SectionEnd diff --git a/installer/NSIS/Examples/FileFuncTest.nsi b/installer/NSIS/Examples/FileFuncTest.nsi new file mode 100644 index 0000000..0a2db35 --- /dev/null +++ b/installer/NSIS/Examples/FileFuncTest.nsi @@ -0,0 +1,572 @@ +;_____________________________________________________________________________ +; +; File Functions Test +;_____________________________________________________________________________ +; +; 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + +Name "File Functions Test" +OutFile "FileFuncTest.exe" +Caption "$(^Name)" +ShowInstDetails show +XPStyle on +RequestExecutionLevel user + +Var FUNCTION +Var OUT1 +Var OUT2 +Var OUT3 +Var OUT4 +Var OUT5 +Var OUT6 +Var OUT7 + +!include "FileFunc.nsh" +!include "LogicLib.nsh" + +;############### INSTALL ############### + +!define StackVerificationStart `!insertmacro StackVerificationStart` +!macro StackVerificationStart _FUNCTION + StrCpy $FUNCTION ${_FUNCTION} + Call StackVerificationStart +!macroend + +!define StackVerificationEnd `!insertmacro StackVerificationEnd` +!macro StackVerificationEnd + Call StackVerificationEnd +!macroend + +Function StackVerificationStart + StrCpy $0 !0 + StrCpy $1 !1 + StrCpy $2 !2 + StrCpy $3 !3 + StrCpy $4 !4 + StrCpy $5 !5 + StrCpy $6 !6 + StrCpy $7 !7 + StrCpy $8 !8 + StrCpy $9 !9 + StrCpy $R0 !R0 + StrCpy $R1 !R1 + StrCpy $R2 !R2 + StrCpy $R3 !R3 + StrCpy $R4 !R4 + StrCpy $R5 !R5 + StrCpy $R6 !R6 + StrCpy $R7 !R7 + StrCpy $R8 !R8 + StrCpy $R9 !R9 +FunctionEnd + +Function StackVerificationEnd + IfErrors +3 + DetailPrint 'PASSED $FUNCTION no errors' + goto +2 + DetailPrint 'FAILED $FUNCTION error' + + StrCmp $0 '!0' 0 error + StrCmp $1 '!1' 0 error + StrCmp $2 '!2' 0 error + StrCmp $3 '!3' 0 error + StrCmp $4 '!4' 0 error + StrCmp $5 '!5' 0 error + StrCmp $6 '!6' 0 error + StrCmp $7 '!7' 0 error + StrCmp $8 '!8' 0 error + StrCmp $9 '!9' 0 error + StrCmp $R0 '!R0' 0 error + StrCmp $R1 '!R1' 0 error + StrCmp $R2 '!R2' 0 error + StrCmp $R3 '!R3' 0 error + StrCmp $R4 '!R4' 0 error + StrCmp $R5 '!R5' 0 error + StrCmp $R6 '!R6' 0 error + StrCmp $R7 '!R7' 0 error + StrCmp $R8 '!R8' 0 error + StrCmp $R9 '!R9' 0 error + DetailPrint 'PASSED $FUNCTION stack' + goto end + + error: + DetailPrint 'FAILED $FUNCTION stack' +; MessageBox MB_OKCANCEL '$$0={$0}$\n$$1={$1}$\n$$2={$2}$\n$$3={$3}$\n$$4={$4}$\n$$5={$5}$\n$$6={$6}$\n$$7={$7}$\n$$8={$8}$\n$$9={$9}$\n$$R0={$R0}$\n$$R1={$R1}$\n$$R2={$R2}$\n$$R3={$R3}$\n$$R4={$R4}$\n$$R5={$R5}$\n$$R6={$R6}$\n$$R7={$R7}$\n$$R8={$R8}$\n$$R9={$R9}' IDOK +2 +; quit + + end: +FunctionEnd + + + +Section Locate + ${StackVerificationStart} Locate + + ${Locate} '$DOCUMENTS' '/L=FD /M=*.* /S=0B /G=0' 'LocateCallback' + + ${StackVerificationEnd} +SectionEnd + +Function LocateCallback +; MessageBox MB_YESNO '$$0={$0}$\n$$1={$1}$\n$$2={$2}$\n$$3={$3}$\n$$4={$4}$\n$$5={$5}$\n$$6={$6}$\n$$7={$7}$\n$$8={$8}$\n$$9={$9}$\n$$R0={$R0}$\n$$R1={$R1}$\n$$R2={$R2}$\n$$R3={$R3}$\n$$R4={$R4}$\n$$R5={$R5}$\n$$R6={$R6}$\n$$R7={$R7}$\n$$R8={$R8}$\n$$R9={$R9}$\n$\nContinue?' IDYES +2 +; StrCpy $0 StopLocate + + Push $0 +FunctionEnd + + +Section GetSize + ${StackVerificationStart} GetSize + + ${GetSize} '$WINDIR' '/M=Explorer.exe /S=0K /G=0' $OUT1 $OUT2 $OUT3 + + ${StackVerificationEnd} +SectionEnd + + +Section DriveSpace + ${StackVerificationStart} DriveSpace + + ${DriveSpace} 'C:\' '/D=F /S=M' $OUT1 + + ${StackVerificationEnd} +SectionEnd + + +Section GetDrives + ${StackVerificationStart} GetDrives + + ${GetDrives} 'FDD+CDROM' 'GetDrivesCallback' + + ${StackVerificationEnd} +SectionEnd + +Function GetDrivesCallback +; MessageBox MB_YESNO '$$0={$0}$\n$$1={$1}$\n$$2={$2}$\n$$3={$3}$\n$$4={$4}$\n$$5={$5}$\n$$6={$6}$\n$$7={$7}$\n$$8={$8}$\n$$9={$9}$\n$$R0={$R0}$\n$$R1={$R1}$\n$$R2={$R2}$\n$$R3={$R3}$\n$$R4={$R4}$\n$$R5={$R5}$\n$$R6={$R6}$\n$$R7={$R7}$\n$$R8={$R8}$\n$$R9={$R9}$\n$\nContinue?' IDYES +2 +; StrCpy $0 StopGetDrives + + Push $0 +FunctionEnd + + +Section GetTime + ${StackVerificationStart} GetTime + + ${GetTime} '' 'L' $OUT1 $OUT2 $OUT3 $OUT4 $OUT5 $OUT6 $OUT7 + + ${StackVerificationEnd} +SectionEnd + + +Section GetFileAttributes + ${StackVerificationStart} GetFileAttributes + + ${GetFileAttributes} '$WINDIR\explorer.exe' 'ALL' $OUT1 + + ${StackVerificationEnd} +SectionEnd + + +Section GetFileVersion + ${StackVerificationStart} GetFileVersion + + ${GetFileVersion} '$WINDIR\explorer.exe' $OUT1 + + ${StackVerificationEnd} +SectionEnd + + +Section GetExeName + ${StackVerificationStart} GetExeName + + ${GetExeName} $OUT1 + + ${StackVerificationEnd} +SectionEnd + + +Section GetExePath + ${StackVerificationStart} GetExePath + + ${GetExePath} $OUT1 + + ${StackVerificationEnd} +SectionEnd + + +Section GetParameters + ${StackVerificationStart} GetParameters + + # basic stuff + + StrCpy $CMDLINE '"$PROGRAMFILES\Something\Hello.exe"' + ${GetParameters} $OUT1 + StrCpy $CMDLINE '"$PROGRAMFILES\Something\Hello.exe" test' + ${GetParameters} $OUT2 + StrCpy $CMDLINE '"$PROGRAMFILES\Something\Hello.exe" "test"' + ${GetParameters} $OUT3 + StrCpy $CMDLINE 'C:\Hello.exe' + ${GetParameters} $OUT4 + StrCpy $CMDLINE 'C:\Hello.exe test' + ${GetParameters} $OUT5 + StrCpy $CMDLINE 'C:\Hello.exe "test"' + ${GetParameters} $OUT6 + StrCpy $CMDLINE 'C:\Hello.exe test test ' + ${GetParameters} $OUT7 + + ${If} $OUT1 != "" + ${OrIf} $OUT2 != "test" + ${OrIf} $OUT3 != '"test"' + ${OrIf} $OUT4 != "" + ${OrIf} $OUT5 != "test" + ${OrIf} $OUT6 != '"test"' + ${OrIf} $OUT7 != 'test test' + SetErrors + ${EndIf} + + # some corner cases + + StrCpy $CMDLINE '' + ${GetParameters} $OUT1 + StrCpy $CMDLINE '"' + ${GetParameters} $OUT2 + StrCpy $CMDLINE '""' + ${GetParameters} $OUT3 + StrCpy $CMDLINE '"" test' + ${GetParameters} $OUT4 + StrCpy $CMDLINE ' test' + ${GetParameters} $OUT5 + StrCpy $CMDLINE ' test' # left over bug(?) from old GetParameters + # it starts looking for ' ' from the third char + ${GetParameters} $OUT6 + StrCpy $CMDLINE ' ' + ${GetParameters} $OUT7 + + ${If} $OUT1 != "" + ${OrIf} $OUT2 != "" + ${OrIf} $OUT3 != "" + ${OrIf} $OUT4 != "" + ${OrIf} $OUT5 != "" + ${OrIf} $OUT6 != "" + ${OrIf} $OUT7 != "" + SetErrors + ${EndIf} + + ${StackVerificationEnd} +SectionEnd + + +Section GetOptions + ${StackVerificationStart} GetOptions + + ${GetOptions} '/INSTDIR=C:\Program Files\Common Files /SILENT=yes' '/INSTDIR=' $OUT1 + StrCmp $OUT1 'C:\Program Files\Common Files' 0 error + + ${GetOptions} '-TMP=temp.tmp -INSTDIR="C:/Program Files/Common Files" -SILENT=yes' '-INSTDIR=' $OUT1 + StrCmp $OUT1 'C:/Program Files/Common Files' 0 error + + ${GetOptions} "/INSTDIR='C:/Program Files/Common Files' /SILENT=yes" '/INSTDIR=' $OUT1 + StrCmp $OUT1 'C:/Program Files/Common Files' 0 error + + StrCpy $OUT1 '/INSTDIR=`C:/Program Files/Common Files` /SILENT=yes' + ${GetOptions} '$OUT1' '/INSTDIR=' $OUT1 + StrCmp $OUT1 'C:/Program Files/Common Files' 0 error + + ${GetOptions} '/SILENT=yes /INSTDIR=C:\Program Files\Common Files' '/INSTDIR=' $OUT1 + StrCmp $OUT1 'C:\Program Files\Common Files' 0 error + + ${GetOptions} "/INSTDIR=common directory: 'C:\Program Files\Common Files' /SILENT=yes" '/INSTDIR=' $OUT1 + StrCmp $OUT1 "common directory: 'C:\Program Files\Common Files'" 0 error + + ${GetOptions} '/INSTDIR=WxxxW /SILENT=yes' '/INSTDIR=' $OUT1 + StrCmp $OUT1 'WxxxW' 0 error + + ${GetOptions} "/Prm='/D=True' /D=1" '/D=' $OUT1 + StrCmp $OUT1 "1" 0 error + + ${GetOptions} "/D=1 /Prm='/D=True'" '/Prm=' $OUT1 + StrCmp $OUT1 "/D=True" 0 error + + ${GetOptions} `/D=1 /Prm='/D="True" /S="/Temp"'` '/Prm=' $OUT1 + StrCmp $OUT1 '/D="True" /S="/Temp"' 0 error + + ${GetOptions} `/INSTDIR='"C:/Program Files/Common Files"' /SILENT=yes` '/INSTDIR=' $OUT1 + StrCmp $OUT1 '"C:/Program Files/Common Files"' 0 error + + ${GetOptions} `/INSTDIR='"C:/Program Files/Common Files"' /SILENT=yes` '/INSTDIR*=' $OUT1 + IfErrors 0 error + StrCmp $OUT1 '' 0 error + + ${GetOptions} `/INSTDIR="C:/Program Files/Common Files" /SILENT=yes` '' $OUT1 + IfErrors 0 error + StrCmp $OUT1 '' 0 error + + ${GetOptionsS} '/INSTDIR=C:\Program Files\Common Files /SILENT' '/SILENT' $OUT1 + IfErrors error + StrCmp $OUT1 '' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section GetOptionsS + ${StackVerificationStart} GetOptionsS + + ${GetOptionsS} '/INSTDIR=C:\Program Files\Common Files /SILENT=yes' '/INSTDIR=' $OUT1 + IfErrors error + StrCmp $OUT1 'C:\Program Files\Common Files' 0 error + + ${GetOptionsS} '/INSTDIR=C:\Program Files\Common Files /SILENT=yes' '/Instdir=' $OUT1 + IfErrors 0 error + StrCmp $OUT1 '' 0 error + + ${GetOptionsS} '/INSTDIR=C:\Program Files\Common Files /SILENT' '/SILENT' $OUT1 + IfErrors error + StrCmp $OUT1 '' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section GetRoot + ${StackVerificationStart} GetRoot + + ${GetRoot} 'C:\Program Files\NSIS' $OUT1 + StrCmp $OUT1 'C:' 0 error + + ${GetRoot} '\\SuperPimp\NSIS\Source\exehead\Ui.c' $OUT1 + StrCmp $OUT1 '\\SuperPimp\NSIS' 0 error + + ${GetRoot} '\\Program Files\NSIS' $OUT1 + StrCmp $OUT1 '\\Program Files\NSIS' 0 error + + ${GetRoot} '\\Program Files\NSIS\' $OUT1 + StrCmp $OUT1 '\\Program Files\NSIS' 0 error + + ${GetRoot} '\\Program Files\NSIS\Source\exehead\Ui.c' $OUT1 + StrCmp $OUT1 '\\Program Files\NSIS' 0 error + + ${GetRoot} '\Program Files\NSIS' $OUT1 + StrCmp $OUT1 '' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section GetParent + ${StackVerificationStart} GetParent + + ${GetParent} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + StrCmp $OUT1 'C:\Program Files\Winamp' 0 error + + ${GetParent} 'C:\Program Files\Winamp\plugins' $OUT1 + StrCmp $OUT1 'C:\Program Files\Winamp' 0 error + + ${GetParent} 'C:\Program Files\Winamp\plugins\' $OUT1 + StrCmp $OUT1 'C:\Program Files\Winamp' 0 error + + ${GetParent} 'C:\' $OUT1 + StrCmp $OUT1 '' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section GetFileName + ${StackVerificationStart} GetFileName + + ${GetFileName} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + StrCmp $OUT1 'uninstwa.exe' 0 error + + ${GetFileName} 'uninstwa.exe' $OUT1 + StrCmp $OUT1 'uninstwa.exe' 0 error + + ${GetFileName} 'C:\Program Files\Winamp\plugins' $OUT1 + StrCmp $OUT1 'plugins' 0 error + + ${GetFileName} 'C:\Program Files\Winamp\plugins\' $OUT1 + StrCmp $OUT1 'plugins' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section GetBaseName + ${StackVerificationStart} GetBaseName + + ${GetBaseName} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + StrCmp $OUT1 'uninstwa' 0 error + + ${GetBaseName} 'uninstwa.exe' $OUT1 + StrCmp $OUT1 'uninstwa' 0 error + + ${GetBaseName} 'C:\Program Files\Winamp\plugins' $OUT1 + StrCmp $OUT1 'plugins' 0 error + + ${GetBaseName} 'C:\Program Files\Winamp\plugins\' $OUT1 + StrCmp $OUT1 '' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section GetFileExt + ${StackVerificationStart} GetFileExt + + ${GetFileExt} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + StrCmp $OUT1 'exe' 0 error + + ${GetFileExt} 'uninstwa.exe' $OUT1 + StrCmp $OUT1 'exe' 0 error + + ${GetFileExt} 'C:\Program Files\Winamp\plugins' $OUT1 + StrCmp $OUT1 '' 0 error + + ${GetFileExt} 'C:\Program Files\Winamp\plugins\' $OUT1 + StrCmp $OUT1 '' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section BannerTrimPath + ${StackVerificationStart} BannerTrimPath + + ${BannerTrimPath} 'C:\Server\Documents\Terminal\license.htm' '35A' $OUT1 + StrCmp $OUT1 'C:\Server\...\Terminal\license.htm' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '3A' $OUT1 + StrCmp $OUT1 '' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '4A' $OUT1 + StrCmp $OUT1 'C...' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '11A' $OUT1 + StrCmp $OUT1 'C:\12\...' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '13A' $OUT1 + StrCmp $OUT1 'C:\12\...\789' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '14A' $OUT1 + StrCmp $OUT1 'C:\12\3456\789' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '14A' $OUT1 + StrCmp $OUT1 'C:\12\3456\789' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '11B' $OUT1 + StrCmp $OUT1 'C:\12\...' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '5B' $OUT1 + StrCmp $OUT1 'C:...' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '5B' $OUT1 + StrCmp $OUT1 'C:...' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '11C' $OUT1 + StrCmp $OUT1 'C:\12\34...' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '9D' $OUT1 + StrCmp $OUT1 'C:\12\...' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '10D' $OUT1 + StrCmp $OUT1 'C:\...\789' 0 error + + ${BannerTrimPath} 'C:\12\3456\789' '11D' $OUT1 + StrCmp $OUT1 'C:\1...\789' 0 error + + ${BannerTrimPath} '123456789' '5D' $OUT1 + StrCmp $OUT1 '12...' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section DirState + ${StackVerificationStart} DirState + + ${DirState} '$TEMP' $OUT1 + + ${StackVerificationEnd} +SectionEnd + + +Section RefreshShellIcons + ${StackVerificationStart} RefreshShellIcons + + ${RefreshShellIcons} + + ${StackVerificationEnd} +SectionEnd + + +Section WriteUninstaller + goto +2 + WriteUninstaller '$EXEDIR\un.FileFuncTest.exe' +SectionEnd + + + +;############### UNINSTALL ############### + +Section un.Uninstall + ${Locate} '$DOCUMENTS' '/L=FD /M=*.* /S=0B /G=0' 'un.LocateCallback' + ${GetSize} '$WINDIR' '/M=Explorer.exe /S=0K /G=0' $OUT1 $OUT2 $OUT3 + ${DriveSpace} 'C:\' '/D=F /S=M' $OUT1 + ${GetDrives} 'FDD+CDROM' 'un.GetDrivesCallback' + ${GetTime} '' 'L' $OUT1 $OUT2 $OUT3 $OUT4 $OUT5 $OUT6 $OUT7 + ${GetFileAttributes} '$WINDIR\explorer.exe' 'ALL' $OUT1 + ${GetFileVersion} '$WINDIR\explorer.exe' $OUT1 + ${GetExeName} $OUT1 + ${GetExePath} $OUT1 + ${GetParameters} $OUT1 + ${GetOptions} '/INSTDIR=C:\Program Files\Common Files /SILENT=yes' '/INSTDIR=' $OUT1 + ${GetOptionsS} '/INSTDIR=C:\Program Files\Common Files /SILENT=yes' '/INSTDIR=' $OUT1 + ${GetRoot} 'C:\Program Files\NSIS' $OUT1 + ${GetParent} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + ${GetFileName} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + ${GetBaseName} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + ${GetFileExt} 'C:\Program Files\Winamp\uninstwa.exe' $OUT1 + ${BannerTrimPath} 'C:\Server\Documents\Terminal\license.htm' '35A' $OUT1 + ${DirState} '$TEMP' $OUT1 + ${RefreshShellIcons} +SectionEnd + +Function un.LocateCallback + Push $0 +FunctionEnd + +Function un.GetDrivesCallback + Push $0 +FunctionEnd diff --git a/installer/NSIS/Examples/InstallOptions/test.ini b/installer/NSIS/Examples/InstallOptions/test.ini new file mode 100644 index 0000000..7b94875 --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/test.ini @@ -0,0 +1,76 @@ +[Settings] +NumFields=8 + +[Field 1] +Type=GroupBox +Left=0 +Right=-1 +Top=0 +Bottom=-5 +Text=" This is a group box... " + +[Field 2] +Type=checkbox +Text=Install support for X +Left=10 +Right=-10 +Top=17 +Bottom=25 +State=0 +Flags=GROUP + +[Field 3] +Type=checkbox +Text=Install support for Y +Left=10 +Right=-10 +Top=30 +Bottom=38 +State=1 +Flags=NOTABSTOP + +[Field 4] +Type=checkbox +Text=Install support for Z +Left=10 +Right=-10 +Top=43 +Bottom=51 +State=0 +Flags=NOTABSTOP + +[Field 5] +Type=FileRequest +State=C:\poop.poop +Left=10 +Right=-10 +Top=56 +Bottom=68 +Filter=Poop Files|*.poop|All files|*.* +Flags=GROUP|FILE_MUST_EXIST|FILE_EXPLORER|FILE_HIDEREADONLY + +[Field 6] +Type=DirRequest +Left=10 +Right=-10 +Top=73 +Bottom=85 +Text=Select a directory... +State=C:\Program Files\NSIS + +[Field 7] +Type=Label +Left=10 +Right=-10 +Top=90 +Bottom=98 +Text=This is a label... + +[Field 8] +Type=Text +Left=10 +Right=-10 +Top=98 +Bottom=120 +State="Multiline\r\nedit..." +Flags=MULTILINE|VSCROLL|WANTRETURN \ No newline at end of file diff --git a/installer/NSIS/Examples/InstallOptions/test.nsi b/installer/NSIS/Examples/InstallOptions/test.nsi new file mode 100644 index 0000000..53afe9b --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/test.nsi @@ -0,0 +1,84 @@ +;InstallOptions Test Script +;Written by Joost Verburg +;-------------------------- + +!define TEMP1 $R0 ;Temp variable + +;The name of the installer +Name "InstallOptions Test" + +;The file to write +OutFile "Test.exe" + +; Show install details +ShowInstDetails show + +;Things that need to be extracted on startup (keep these lines before any File command!) +;Only useful for BZIP2 compression +;Use ReserveFile for your own InstallOptions INI files too! + +ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" +ReserveFile "test.ini" + +;Order of pages +Page custom SetCustom ValidateCustom ": Testing InstallOptions" ;Custom page. InstallOptions gets called in SetCustom. +Page instfiles + +Section "Components" + + ;Get Install Options dialog user input + + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 2" "State" + DetailPrint "Install X=${TEMP1}" + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 3" "State" + DetailPrint "Install Y=${TEMP1}" + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 4" "State" + DetailPrint "Install Z=${TEMP1}" + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 5" "State" + DetailPrint "File=${TEMP1}" + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 6" "State" + DetailPrint "Dir=${TEMP1}" + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 8" "State" + DetailPrint "Info=${TEMP1}" + +SectionEnd + +Function .onInit + + ;Extract InstallOptions files + ;$PLUGINSDIR will automatically be removed when the installer closes + + InitPluginsDir + File /oname=$PLUGINSDIR\test.ini "test.ini" + +FunctionEnd + +Function SetCustom + + ;Display the InstallOptions dialog + + Push ${TEMP1} + + InstallOptions::dialog "$PLUGINSDIR\test.ini" + Pop ${TEMP1} + + Pop ${TEMP1} + +FunctionEnd + +Function ValidateCustom + + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 2" "State" + StrCmp ${TEMP1} 1 done + + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 3" "State" + StrCmp ${TEMP1} 1 done + + ReadINIStr ${TEMP1} "$PLUGINSDIR\test.ini" "Field 4" "State" + StrCmp ${TEMP1} 1 done + MessageBox MB_ICONEXCLAMATION|MB_OK "You must select at least one install option!" + Abort + + done: + +FunctionEnd diff --git a/installer/NSIS/Examples/InstallOptions/testimgs.ini b/installer/NSIS/Examples/InstallOptions/testimgs.ini new file mode 100644 index 0000000..381a986 --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/testimgs.ini @@ -0,0 +1,65 @@ +[Settings] +NumFields=8 + +[Field 1] +Type=GroupBox +Left=0 +Right=-1 +Top=0 +Bottom=-5 +Text=" Images " + +[Field 2] +Type=Bitmap +Left=10 +Right=-10 +Top=10 +Bottom=30 +Flags=TRANSPARENT + +[Field 3] +Type=Bitmap +Left=10 +Right=-10 +Top=35 +Bottom=45 +Flags=TRANSPARENT + +[Field 4] +Type=Bitmap +Left=10 +Right=-10 +Top=50 +Bottom=70 +Flags=RESIZETOFIT|TRANSPARENT + +[Field 5] +Type=Bitmap +Left=10 +Right=-10 +Top=75 +Bottom=95 +Flags=RESIZETOFIT|TRANSPARENT + +[Field 6] +Type=Icon +Left=10 +Right=40 +Top=100 +Bottom=120 + +[Field 7] +Type=Icon +Left=50 +Right=80 +Top=100 +Bottom=120 + +[Field 8] +Type=Label +Left=10 +Right=-10 +Top=10 +Bottom=-10 +Text=ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ + diff --git a/installer/NSIS/Examples/InstallOptions/testimgs.nsi b/installer/NSIS/Examples/InstallOptions/testimgs.nsi new file mode 100644 index 0000000..1a832e9 --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/testimgs.nsi @@ -0,0 +1,59 @@ +;InstallOptions Test Script +;Written by Joost Verburg +;-------------------------- + +;The name of the installer +Name "InstallOptions Test" + +;The file to write +OutFile "Test.exe" + +; Show install details +ShowInstDetails show + +;Things that need to be extracted on startup (keep these lines before any File command!) +;Only useful for BZIP2 compression +;Use ReserveFile for your own InstallOptions INI files too! + +ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" +ReserveFile "testimgs.ini" +ReserveFile "${NSISDIR}\Contrib\Graphics\Checks\colorful.bmp" +ReserveFile "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" +ReserveFile "${NSISDIR}\Contrib\Graphics\Icons\pixel-install.ico" + +;Order of pages +Page custom SetCustom "" ": Testing InstallOptions" ;Custom page. InstallOptions gets called in SetCustom. +Page instfiles + +Section +SectionEnd + +Function .onInit + + ;Extract InstallOptions files + ;$PLUGINSDIR will automatically be removed when the installer closes + + InitPluginsDir + File /oname=$PLUGINSDIR\testimgs.ini "testimgs.ini" + File /oname=$PLUGINSDIR\image.bmp "${NSISDIR}\Contrib\Graphics\Checks\colorful.bmp" + File /oname=$PLUGINSDIR\image2.bmp "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" + File /oname=$PLUGINSDIR\icon.ico "${NSISDIR}\Contrib\Graphics\Icons\pixel-install.ico" + + ;Write image paths to the INI file + + WriteINIStr $PLUGINSDIR\testimgs.ini "Field 2" "Text" $PLUGINSDIR\image.bmp + WriteINIStr $PLUGINSDIR\testimgs.ini "Field 3" "Text" $PLUGINSDIR\image2.bmp + WriteINIStr $PLUGINSDIR\testimgs.ini "Field 4" "Text" $PLUGINSDIR\image.bmp + WriteINIStr $PLUGINSDIR\testimgs.ini "Field 5" "Text" $PLUGINSDIR\image2.bmp + WriteINIStr $PLUGINSDIR\testimgs.ini "Field 6" "Text" $PLUGINSDIR\icon.ico + ;No Text for Field 7 so it'll show the installer's icon + +FunctionEnd + +Function SetCustom + + ;Display the InstallOptions dialog + InstallOptions::dialog "$PLUGINSDIR\testimgs.ini" + Pop $0 + +FunctionEnd diff --git a/installer/NSIS/Examples/InstallOptions/testlink.ini b/installer/NSIS/Examples/InstallOptions/testlink.ini new file mode 100644 index 0000000..592ae93 --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/testlink.ini @@ -0,0 +1,44 @@ +[Settings] +NumFields=5 + +[Field 1] +Type=Label +Left=10 +Right=-40 +Top=10 +Bottom=18 +Text=This custom page demonstrates the "Link" control + +[Field 2] +Type=Link +Left=20 +Right=-40 +Top=40 +Bottom=50 +Text=* Run notepad + +[Field 3] +Type=Link +Left=20 +Right=-40 +Top=55 +Bottom=65 +State=mailto:someone@anywhere.com +Text=* Send E-mail + +[Field 4] +Type=Link +Left=20 +Right=-40 +Top=70 +Bottom=80 +State=http://nsis.sourceforge.net/ +Text=* Homepage http://nsis.sourceforge.net/ + +[Field 5] +Type=Text +Left=20 +Right=-40 +Top=85 +Bottom=98 +State=Just to test proper interaction with the other fields diff --git a/installer/NSIS/Examples/InstallOptions/testlink.nsi b/installer/NSIS/Examples/InstallOptions/testlink.nsi new file mode 100644 index 0000000..8b73fc9 --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/testlink.nsi @@ -0,0 +1,58 @@ +;InstallOptions Test Script +;Written by Ramon +;This script demonstrates the power of the new control "LINK" +;that allows you to execute files, send mails, open wepsites, etc. +;-------------------------- + +!define TEMP1 $R0 ;Temp variable + +;The name of the installer +Name "InstallOptions Test Link" + +;The file to write +OutFile "TestLink.exe" + +; Show install details +ShowInstDetails show + +;Things that need to be extracted on startup (keep these lines before any File command!) +;Only useful for BZIP2 compression +;Use ReserveFile for your own InstallOptions INI files too! + +ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" +ReserveFile "testlink.ini" + +;Order of pages +Page custom SetCustom +Page instfiles + +Section "Components" + + ;Get Install Options dialog user input + +SectionEnd + +Function .onInit + + ;Extract InstallOptions files + ;$PLUGINSDIR will automatically be removed when the installer closes + + InitPluginsDir + File /oname=$PLUGINSDIR\test.ini "testlink.ini" + WriteIniStr $PLUGINSDIR\test.ini "Field 2" "State" "$WINDIR\Notepad.exe" + +FunctionEnd + +Function SetCustom + + ;Display the InstallOptions dialog + + Push ${TEMP1} + + InstallOptions::dialog "$PLUGINSDIR\test.ini" + Pop ${TEMP1} + + Pop ${TEMP1} + +FunctionEnd + diff --git a/installer/NSIS/Examples/InstallOptions/testnotify.ini b/installer/NSIS/Examples/InstallOptions/testnotify.ini new file mode 100644 index 0000000..f86fc8e --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/testnotify.ini @@ -0,0 +1,105 @@ +[Settings] +NumFields=11 + +[Field 1] +Type=Groupbox +Text="This is a group box..." +Left=0 +Right=-1 +Top=0 +Bottom=-4 + +[Field 2] +Type=Checkbox +Text=Install support for X +Flags=NOTIFY +State=1 +Left=10 +Right=100 +Top=17 +Bottom=25 + +[Field 3] +Type=Checkbox +Text=Install support for Y +State=0 +Left=10 +Right=100 +Top=30 +Bottom=38 + +[Field 4] +Type=Checkbox +Text=Install support for Z +Flags=RIGHT +State=0 +Left=10 +Right=100 +Top=43 +Bottom=51 + +[Field 5] +Type=FileRequest +Flags=GROUP|FILE_MUST_EXIST|FILE_EXPLORER|FILE_HIDEREADONLY +State=C:\poop.poop +Filter=Poop Files|*.poop|All files|*.* +Left=10 +Right=-10 +Top=56 +Bottom=69 + +[Field 6] +Type=DirRequest +Text=Select a directory... +State=C:\Program Files\NSIS +Left=10 +Right=-10 +Top=74 +Bottom=87 + +[Field 7] +Type=Label +Text=This is a label... +Left=10 +Right=-10 +Top=89 +Bottom=97 + +[Field 8] +Type=Text +Flags=MULTILINE|VSCROLL|WANTRETURN|NOWORDWRAP +State="Multiline\r\nedit..." +Left=10 +Right=-10 +Top=97 +Bottom=118 +MinLen=1 +ValidateText=Please enter some text before proceeding. + +[Field 9] +Type=Button +Flags=NOTIFY +Text=&Clear +Left=-60 +Right=-10 +Top=19 +Bottom=33 + +[Field 10] +Type=Button +Text=&Email +State=mailto:someone@anywhere.com +Left=-60 +Right=-10 +Top=35 +Bottom=49 + +[Field 11] +Type=DROPLIST +ListItems=Show|Hide +State=Show +Flags=NOTIFY +Left=120 +Right=-80 +Top=20 +Bottom=56 diff --git a/installer/NSIS/Examples/InstallOptions/testnotify.nsi b/installer/NSIS/Examples/InstallOptions/testnotify.nsi new file mode 100644 index 0000000..a8ab734 --- /dev/null +++ b/installer/NSIS/Examples/InstallOptions/testnotify.nsi @@ -0,0 +1,133 @@ +; InstallOptions script demonstrating custom buttons +;---------------------------------------------------- + +!include WinMessages.nsh + +; The name of the installer +Name "InstallOptions Test" + +; The file to write +OutFile "TestNotify.exe" + +; Show install details +ShowInstDetails show + +; Called before anything else as installer initialises +Function .onInit + + ; Extract InstallOptions files + ; $PLUGINSDIR will automatically be removed when the installer closes + InitPluginsDir + File /oname=$PLUGINSDIR\test.ini "testnotify.ini" + +FunctionEnd + +; Our custom page +Page custom ShowCustom LeaveCustom ": Testing InstallOptions" + +Function ShowCustom + + ; Initialise the dialog but don't show it yet + MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Test the right-to-left version?" IDNO +2 + WriteINIStr "$PLUGINSDIR\test.ini" "Settings" "RTL" "1" + InstallOptions::initDialog "$PLUGINSDIR\test.ini" + ; In this mode InstallOptions returns the window handle so we can use it + Pop $0 + ; Now show the dialog and wait for it to finish + InstallOptions::show + ; Finally fetch the InstallOptions status value (we don't care what it is though) + Pop $0 + +FunctionEnd + +Function LeaveCustom + + ; At this point the user has either pressed Next or one of our custom buttons + ; We find out which by reading from the INI file + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Settings" "State" + StrCmp $0 0 validate ; Next button? + StrCmp $0 2 supportx ; "Install support for X"? + StrCmp $0 9 clearbtn ; "Clear" button? + StrCmp $0 11 droplist ; "Show|Hide" drop-list? + Abort ; Return to the page + +supportx: + ; Make the FileRequest field depend on the first checkbox + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 2" "State" + ReadINIStr $1 "$PLUGINSDIR\test.ini" "Field 5" "HWND" + EnableWindow $1 $0 + ReadINIStr $1 "$PLUGINSDIR\test.ini" "Field 5" "HWND2" + EnableWindow $1 $0 + ; Add the disabled flag too so when we return to this page it's disabled again + StrCmp $0 0 0 +3 + + WriteINIStr "$PLUGINSDIR\test.ini" "Field 5" "Flags" "GROUP|FILE_MUST_EXIST|FILE_EXPLORER|FILE_HIDEREADONLY|DISABLED" + Goto +2 + + WriteINIStr "$PLUGINSDIR\test.ini" "Field 5" "Flags" "GROUP|FILE_MUST_EXIST|FILE_EXPLORER|FILE_HIDEREADONLY" + Abort ; Return to the page + +clearbtn: + ; Clear all text fields + ReadINIStr $1 "$PLUGINSDIR\test.ini" "Field 5" "HWND" + SendMessage $1 ${WM_SETTEXT} 0 "STR:" + ReadINIStr $1 "$PLUGINSDIR\test.ini" "Field 6" "HWND" + SendMessage $1 ${WM_SETTEXT} 0 "STR:" + ReadINIStr $1 "$PLUGINSDIR\test.ini" "Field 8" "HWND" + SendMessage $1 ${WM_SETTEXT} 0 "STR:" + Abort ; Return to the page + +droplist: + ; Make the DirRequest field depend on the droplist + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 11" "State" + StrCmp $0 "Show" +3 + StrCpy $0 0 + Goto +2 + StrCpy $0 1 + ReadINIStr $1 "$PLUGINSDIR\test.ini" "Field 6" "HWND" + EnableWindow $1 $0 + ReadINIStr $1 "$PLUGINSDIR\test.ini" "Field 6" "HWND2" + EnableWindow $1 $0 + ; Add the disabled flag too so when we return to this page it's disabled again + StrCmp $0 0 0 +3 + + WriteINIStr "$PLUGINSDIR\test.ini" "Field 6" "Flags" "DISABLED" + Goto +2 + + WriteINIStr "$PLUGINSDIR\test.ini" "Field 6" "Flags" "" + Abort ; Return to the page + +validate: + ; At this point we know the Next button was pressed, so perform any validation + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 2" "State" + StrCmp $0 1 done + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 3" "State" + StrCmp $0 1 done + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 4" "State" + StrCmp $0 1 done + MessageBox MB_ICONEXCLAMATION|MB_OK "You must select at least one install option!" + Abort +done: + +FunctionEnd + +; Installation page +Page instfiles + +Section + + ;Get Install Options dialog user input + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 2" "State" + DetailPrint "Install X=$0" + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 3" "State" + DetailPrint "Install Y=$0" + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 4" "State" + DetailPrint "Install Z=$0" + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 5" "State" + DetailPrint "File=$0" + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 6" "State" + DetailPrint "Dir=$0" + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 8" "State" + DetailPrint "Info=$0" + +SectionEnd diff --git a/installer/NSIS/Examples/Library.nsi b/installer/NSIS/Examples/Library.nsi new file mode 100644 index 0000000..9dc8d74 --- /dev/null +++ b/installer/NSIS/Examples/Library.nsi @@ -0,0 +1,129 @@ +# This example tests the compile time aspect of the Library macros +# more than the runtime aspect. It is more of a syntax example, +# rather than a usage example. + +!include "Library.nsh" + +Name "Library Test" +OutFile "Library Test.exe" + +InstallDir "$TEMP\Library Test" + +Page directory +Page instfiles + +XPStyle on + +RequestExecutionLevel user + +!define TestDLL '"${NSISDIR}\Plugins\LangDLL.dll"' +!define TestEXE '"${NSISDIR}\Contrib\UIs\default.exe"' + +Section + +!insertmacro InstallLib DLL NOTSHARED REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib DLL NOTSHARED NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib DLL NOTSHARED REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib DLL NOTSHARED NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib REGDLL NOTSHARED REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLL NOTSHARED NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLL NOTSHARED REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLL NOTSHARED NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib TLB NOTSHARED REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib TLB NOTSHARED NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib TLB NOTSHARED REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib TLB NOTSHARED NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib REGDLLTLB NOTSHARED REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLLTLB NOTSHARED NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLLTLB NOTSHARED REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLLTLB NOTSHARED NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib DLL $0 REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib DLL $0 NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib DLL $0 REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib DLL $0 NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib REGDLL $0 REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLL $0 NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLL $0 REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLL $0 NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib TLB $0 REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib TLB $0 NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib TLB $0 REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib TLB $0 NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib REGDLLTLB $0 REBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLLTLB $0 NOREBOOT_PROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLLTLB $0 REBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR +!insertmacro InstallLib REGDLLTLB $0 NOREBOOT_NOTPROTECTED ${TestDLL} $INSTDIR\test.dll $INSTDIR + +!insertmacro InstallLib REGEXE $0 REBOOT_PROTECTED ${TestEXE} $INSTDIR\test.exe $INSTDIR +!insertmacro InstallLib REGEXE $0 NOREBOOT_PROTECTED ${TestEXE} $INSTDIR\test.exe $INSTDIR +!insertmacro InstallLib REGEXE $0 REBOOT_NOTPROTECTED ${TestEXE} $INSTDIR\test.exe $INSTDIR +!insertmacro InstallLib REGEXE $0 NOREBOOT_NOTPROTECTED ${TestEXE} $INSTDIR\test.exe $INSTDIR + +WriteUninstaller $INSTDIR\uninstall.exe + +SectionEnd + +Section uninstall + +!insertmacro UninstallLib DLL NOTSHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib DLL NOTSHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib DLL NOTSHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib DLL NOTSHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib REGDLL NOTSHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL NOTSHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL NOTSHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL NOTSHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL NOTSHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib TLB NOTSHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib TLB NOTSHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib TLB NOTSHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib TLB NOTSHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib TLB NOTSHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib REGDLLTLB NOTSHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB NOTSHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB NOTSHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB NOTSHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB NOTSHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib DLL SHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib DLL SHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib DLL SHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib DLL SHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib DLL SHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib REGDLL SHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL SHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL SHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL SHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLL SHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib TLB SHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib TLB SHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib TLB SHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib TLB SHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib TLB SHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib REGDLLTLB SHARED NOREMOVE $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB SHARED REBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB SHARED NOREBOOT_PROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB SHARED REBOOT_NOTPROTECTED $INSTDIR\test.dll +!insertmacro UninstallLib REGDLLTLB SHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.dll + +!insertmacro UninstallLib REGEXE SHARED NOREMOVE $INSTDIR\test.exe +!insertmacro UninstallLib REGEXE SHARED REBOOT_PROTECTED $INSTDIR\test.exe +!insertmacro UninstallLib REGEXE SHARED NOREBOOT_PROTECTED $INSTDIR\test.exe +!insertmacro UninstallLib REGEXE SHARED REBOOT_NOTPROTECTED $INSTDIR\test.exe +!insertmacro UninstallLib REGEXE SHARED NOREBOOT_NOTPROTECTED $INSTDIR\test.exe + +SectionEnd diff --git a/installer/NSIS/Examples/LogicLib.nsi b/installer/NSIS/Examples/LogicLib.nsi new file mode 100644 index 0000000..ce7574b --- /dev/null +++ b/installer/NSIS/Examples/LogicLib.nsi @@ -0,0 +1,619 @@ +!verbose 2 + +Name "NSIS LogicLib Example" +OutFile "LogicLib.exe" +ShowInstDetails show +RequestExecutionLevel user + +!include "LogicLib.nsh" + +;!undef LOGICLIB_VERBOSITY +;!define LOGICLIB_VERBOSITY 4 ; For debugging - watch what logiclib does with your code! + +Page components "" "" ComponentsLeave +Page instfiles + +Section /o "Run tests" TESTS + + ; kinds of if other than "value1 comparison value2" + ClearErrors + FindFirst $R1 $R2 "$PROGRAMFILES\*" + ${Unless} ${Errors} + ${Do} + ${Select} $R2 + ${Case2} "." ".." + ; Do nothing + ${CaseElse} + DetailPrint "Found $PROGRAMFILES\$R2" + ${EndSelect} + FindNext $R1 $R2 + ${LoopUntil} ${Errors} + FindClose $R1 + ${EndUnless} + + ${If} ${FileExists} "${__FILE__}" + DetailPrint 'Source file "${__FILE__}" still exists' + ${Else} + DetailPrint 'Source file "${__FILE__}" has gone' + ${EndIf} + + ; if..endif + StrCpy $R1 1 + StrCpy $R2 "" + ${If} $R1 = 1 + StrCpy $R2 $R2A + ${EndIf} + ${If} $R1 = 2 + StrCpy $R2 $R2B + ${EndIf} + ${If} $R1 < 2 + StrCpy $R2 $R2C + ${EndIf} + ${If} $R1 < -2 + StrCpy $R2 $R2D + ${EndIf} + ${If} $R1 > 2 + StrCpy $R2 $R2E + ${EndIf} + ${If} $R1 > -2 + StrCpy $R2 $R2F + ${EndIf} + ${If} $R1 <> 1 + StrCpy $R2 $R2G + ${EndIf} + ${If} $R1 <> 2 + StrCpy $R2 $R2H + ${EndIf} + ${If} $R1 >= 2 + StrCpy $R2 $R2I + ${EndIf} + ${If} $R1 >= -2 + StrCpy $R2 $R2J + ${EndIf} + ${If} $R1 <= 2 + StrCpy $R2 $R2K + ${EndIf} + ${If} $R1 <= -2 + StrCpy $R2 $R2L + ${EndIf} + ${If} $R2 == "ACFHJK" + DetailPrint "PASSED If..EndIf test" + ${Else} + DetailPrint "FAILED If..EndIf test" + ${EndIf} + + ; if..elseif..else..endif + StrCpy $R1 A + StrCpy $R2 "" + ${If} $R1 == A + StrCpy $R2 $R2A + ${ElseIf} $R1 == B + StrCpy $R2 $R2B + ${ElseUnless} $R1 != C + StrCpy $R2 $R2C + ${Else} + StrCpy $R2 $R2D + ${EndIf} + ${If} $R1 == D + StrCpy $R2 $R2D + ${ElseIf} $R1 == A + StrCpy $R2 $R2A + ${ElseUnless} $R1 != B + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2C + ${EndIf} + ${If} $R1 == C + StrCpy $R2 $R2C + ${ElseIf} $R1 == D + StrCpy $R2 $R2D + ${ElseUnless} $R1 != A + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} $R1 == B + StrCpy $R2 $R2B + ${ElseIf} $R1 == C + StrCpy $R2 $R2C + ${ElseUnless} $R1 != D + StrCpy $R2 $R2D + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} $R2 == "$R1$R1$R1$R1" + DetailPrint "PASSED If..ElseIf..Else..EndIf test" + ${Else} + DetailPrint "FAILED If..ElseIf..Else..EndIf test" + ${EndIf} + + ; if..andif..orif..endif + StrCpy $R2 "" + ${If} 1 = 1 + ${AndIf} 2 = 2 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 1 + ${AndIf} 2 = 3 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 2 + ${AndIf} 2 = 2 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 2 + ${AndIf} 2 = 3 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + + ${If} 1 = 1 + ${OrIf} 2 = 2 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 1 + ${OrIf} 2 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 2 + ${OrIf} 2 = 2 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 2 + ${OrIf} 2 = 3 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + + ${If} 1 = 1 + ${AndIf} 2 = 2 + ${OrIf} 3 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 1 + ${AndIf} 2 = 3 + ${OrIf} 3 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 2 + ${AndIf} 2 = 2 + ${OrIf} 3 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 2 + ${AndIf} 2 = 3 + ${OrIf} 3 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 1 + ${AndIf} 2 = 2 + ${OrIf} 3 = 4 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 1 + ${AndIf} 2 = 3 + ${OrIf} 3 = 4 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 2 + ${AndIf} 2 = 2 + ${OrIf} 3 = 4 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 2 + ${AndIf} 2 = 3 + ${OrIf} 3 = 4 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + + ${If} 1 = 1 + ${OrIf} 2 = 2 + ${AndIf} 3 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 1 + ${OrIf} 2 = 3 + ${AndIf} 3 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 2 + ${OrIf} 2 = 2 + ${AndIf} 3 = 3 + StrCpy $R2 $R2A + ${Else} + StrCpy $R2 $R2B + ${EndIf} + ${If} 1 = 2 + ${OrIf} 2 = 3 + ${AndIf} 3 = 3 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 1 + ${OrIf} 2 = 2 + ${AndIf} 3 = 4 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 1 + ${OrIf} 2 = 3 + ${AndIf} 3 = 4 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 2 + ${OrIf} 2 = 2 + ${AndIf} 3 = 4 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + ${If} 1 = 2 + ${OrIf} 2 = 3 + ${AndIf} 3 = 4 + StrCpy $R2 $R2B + ${Else} + StrCpy $R2 $R2A + ${EndIf} + + ${If} $R2 == "AAAAAAAAAAAAAAAAAAAAAAAA" + DetailPrint "PASSED If..AndIf..OrIf..Else..EndIf test" + ${Else} + DetailPrint "FAILED If..AndIf..OrIf..Else..EndIf test" + ${EndIf} + + ; ifthen..|..| + StrCpy $R1 1 + StrCpy $R2 "" + ${IfThen} $R1 = 1 ${|} StrCpy $R2 $R2A ${|} + ${IfThen} $R1 = 2 ${|} StrCpy $R2 $R2B ${|} + ${IfNotThen} $R1 = 1 ${|} StrCpy $R2 $R2C ${|} + ${IfNotThen} $R1 = 2 ${|} StrCpy $R2 $R2D ${|} + ${If} $R2 == "AD" + DetailPrint "PASSED IfThen test" + ${Else} + DetailPrint "FAILED IfThen test" + ${EndIf} + + ; ifcmd..||..| and if/unless cmd + StrCpy $R2 "" + ${IfCmd} MessageBox MB_YESNO "Please click Yes" IDYES ${||} StrCpy $R2 $R2A ${|} + ${Unless} ${Cmd} `MessageBox MB_YESNO|MB_DEFBUTTON2 "Please click No" IDYES` + StrCpy $R2 $R2B + ${EndUnless} + ${If} $R2 == "AB" + DetailPrint "PASSED IfCmd/If Cmd test" + ${Else} + DetailPrint "FAILED IfCmd/If Cmd test" + ${EndIf} + + ; select..case..case2..case3..case4..case5..caseelse..endselect + StrCpy $R1 1 + StrCpy $R2 "" + ${Select} $R1 + ${Case} "1" + StrCpy $R2 $R2A + ${Case} "2" + StrCpy $R2 $R2B + ${Case2} "3" "4" + StrCpy $R2 $R2C + ${CaseElse} + StrCpy $R2 $R2D + ${EndSelect} + ${Select} $R1 + ${Case} "2" + StrCpy $R2 $R2A + ${Case} "3" + StrCpy $R2 $R2B + ${Case2} "4" "5" + StrCpy $R2 $R2C + ${CaseElse} + StrCpy $R2 $R2D + ${EndSelect} + ${Select} $R1 + ${Case} "3" + StrCpy $R2 $R2A + ${Case} "4" + StrCpy $R2 $R2B + ${Case2} "5" "1" + StrCpy $R2 $R2C + ${CaseElse} + StrCpy $R2 $R2D + ${EndSelect} + ${Select} $R1 + ${Case} "4" + StrCpy $R2 $R2A + ${Case} "5" + StrCpy $R2 $R2B + ${Case2} "1" "2" + StrCpy $R2 $R2C + ${CaseElse} + StrCpy $R2 $R2D + ${EndSelect} + ${If} $R2 == "ADCC" + DetailPrint "PASSED Select..Case*..EndSelect test" + ${Else} + DetailPrint "FAILED Select..Case*..EndSelect test" + ${EndIf} + + ; switch..case..caseelse..endswitch + StrCpy $R2 "" + ${For} $R1 1 10 + ${Switch} $R1 + ${Case} 3 + StrCpy $R2 $R2A + ${Case} 4 + StrCpy $R2 $R2B + ${Break} + ${Case} 5 + StrCpy $R2 $R2C + ${EndSwitch} + ${Switch} $R1 + ${Case} 1 + StrCpy $R2 $R2D + ${Default} + StrCpy $R2 $R2E + ${Break} + ${Case} 2 + StrCpy $R2 $R2F + ${EndSwitch} + ${Switch} $R1 + ${Case} 6 + ${Case} 7 + StrCpy $R2 $R2G + ${If} $R1 = 6 + ${Case} 8 + StrCpy $R2 $R2H + ${Switch} $R1 + ${Case} 6 + StrCpy $R2 $R2I + ${Break} + ${Case} 8 + StrCpy $R2 $R2J + ${EndSwitch} + ${EndIf} + StrCpy $R2 $R2K + ${Break} + ${Default} + StrCpy $R2 $R2L + ${Case} 9 + StrCpy $R2 $R2M + ${EndSwitch} + ${Next} + ${If} $R2 == "DELMFLMABELMBELMCELMEGHIKEGKEHJKEMELM" + DetailPrint "PASSED Switch..Case*..EndSwitch test" + ${Else} + DetailPrint "FAILED Switch..Case*..EndSwitch test" + ${EndIf} + + ; for[each]..exitfor..next + StrCpy $R2 "" + ${For} $R1 1 5 + StrCpy $R2 $R2$R1 + ${Next} + ${ForEach} $R1 10 1 - 1 + StrCpy $R2 $R2$R1 + ${Next} + ${For} $R1 1 0 + StrCpy $R2 $R2$R1 + ${Next} + ${If} $R2 == "1234510987654321" + DetailPrint "PASSED For[Each]..Next test" + ${Else} + DetailPrint "FAILED For[Each]..Next test" + ${EndIf} + + ; do..loop + StrCpy $R1 0 + Call DoLoop + ${If} $R1 == 5 + DetailPrint "PASSED Do..Loop test" + ${Else} + DetailPrint "FAILED Do..Loop test" + ${EndIf} + + ; do..exitdo..loop + StrCpy $R1 0 + StrCpy $R2 "" + ${Do} + StrCpy $R2 $R2$R1 + IntOp $R1 $R1 + 1 + ${If} $R1 > 10 + ${ExitDo} + ${EndIf} + ${Loop} + ${If} $R2 == "012345678910" + DetailPrint "PASSED Do..ExitDo..Loop test" + ${Else} + DetailPrint "FAILED Do..ExitDo..Loop test" + ${EndIf} + + ; do..exitdo..loopuntil + StrCpy $R1 0 + StrCpy $R2 "" + ${Do} + StrCpy $R2 $R2$R1 + IntOp $R1 $R1 + 1 + ${LoopUntil} $R1 >= 5 + ${If} $R2 == "01234" + DetailPrint "PASSED Do..ExitDo..LoopUntil test" + ${Else} + DetailPrint "FAILED Do..ExitDo..LoopUntil test" + ${EndIf} + + ; dountil..exitdo..loop + StrCpy $R1 0 + StrCpy $R2 "" + ${DoUntil} $R1 >= 5 + StrCpy $R2 $R2$R1 + IntOp $R1 $R1 + 1 + ${Loop} + ${If} $R2 == "01234" + DetailPrint "PASSED DoUntil..ExitDo..Loop test" + ${Else} + DetailPrint "FAILED DoUntil..ExitDo..Loop test" + ${EndIf} + + ; nested do test + StrCpy $R1 0 + StrCpy $R2 0 + StrCpy $R3 "" + ${Do} + StrCpy $R3 $R3$R1$R2 + IntOp $R1 $R1 + 1 + ${If} $R1 > 5 + ${ExitDo} + ${EndIf} + StrCpy $R2 0 + ${Do} + StrCpy $R3 $R3$R1$R2 + IntOp $R2 $R2 + 1 + ${If} $R2 >= 5 + ${ExitDo} + ${EndIf} + ${Loop} + ${Loop} + ${If} $R3 == "00101112131415202122232425303132333435404142434445505152535455" + DetailPrint "PASSED nested Do test" + ${Else} + DetailPrint "FAILED nested Do test" + ${EndIf} + + ; while..exitwhile..endwhile (exact replica of dowhile..enddo} + StrCpy $R1 0 + StrCpy $R2 "" + ${While} $R1 < 5 + StrCpy $R2 $R2$R1 + IntOp $R1 $R1 + 1 + ${EndWhile} + ${If} $R2 == "01234" + DetailPrint "PASSED While..ExitWhile..EndWhile test" + ${Else} + DetailPrint "FAILED While..ExitWhile..EndWhile test" + ${EndIf} + + ; Unsigned integer tests + StrCpy $R2 "" + ${If} -1 < 1 + StrCpy $R2 $R2A + ${EndIf} + ${If} -1 U< 1 + StrCpy $R2 $R2B + ${EndIf} + ${If} 0xFFFFFFFF > 1 + StrCpy $R2 $R2C + ${EndIf} + ${If} 0xFFFFFFFF U> 1 + StrCpy $R2 $R2D + ${EndIf} + ${If} $R2 == "AD" + DetailPrint "PASSED unsigned integer test" + ${Else} + DetailPrint "FAILED unsigned integer test" + ${EndIf} + + ; 64-bit integer tests (uses System.dll) + StrCpy $R2 "" + ${If} 0x100000000 L= 4294967296 + StrCpy $R2 $R2A + ${EndIf} + ${If} 0x100000000 L< 0x200000000 + StrCpy $R2 $R2B + ${EndIf} + ${If} 0x500000000 L>= 0x500000000 + StrCpy $R2 $R2C + ${EndIf} + ${If} $R2 == "ABC" + DetailPrint "PASSED 64-bit integer test" + ${Else} + DetailPrint "FAILED 64-bit integer test" + ${EndIf} + + ; Extra string tests (uses System.dll) + StrCpy $R2 "" + ${If} "A" S< "B" + StrCpy $R2 $R2A + ${EndIf} + ${If} "b" S> "A" + StrCpy $R2 $R2B + ${EndIf} + ${If} "a" S<= "B" + StrCpy $R2 $R2C + ${EndIf} + ${If} "B" S< "B" + StrCpy $R2 $R2D + ${EndIf} + ${If} "A" S== "A" + StrCpy $R2 $R2E + ${EndIf} + ${If} "A" S== "a" + StrCpy $R2 $R2F + ${EndIf} + ${If} "A" S!= "a" + StrCpy $R2 $R2G + ${EndIf} + ${If} $R2 == "ABCEG" + DetailPrint "PASSED extra string test" + ${Else} + DetailPrint "FAILED extra string test" + ${EndIf} + +SectionEnd + +Function ComponentsLeave + ; Section flags tests (requires sections.nsh be included) + ${Unless} ${SectionIsSelected} ${TESTS} + MessageBox MB_OK "Please select the component" + Abort + ${EndIf} +FunctionEnd + +Function DoLoop + + ${Do} + IntOp $R1 $R1 + 1 + ${If} $R1 == 5 + Return + ${EndIf} + ${Loop} + +FunctionEnd + +!verbose 3 diff --git a/installer/NSIS/Examples/Math/math.nsi b/installer/NSIS/Examples/Math/math.nsi new file mode 100644 index 0000000..a32ab3c --- /dev/null +++ b/installer/NSIS/Examples/Math/math.nsi @@ -0,0 +1,33 @@ +; This is just an example of Math plugin +; +; (c) brainsucker, 2002 +; (r) BSForce + +Name "Math Plugin Example" +OutFile "math.exe" +ShowInstDetails show +XPStyle on + +Section "ThisNameIsIgnoredSoWhyBother?" + Math::Script 'SaR(s,fa,ra, i,f,r,e,p) (i=0;#{i=0, (NS=s[p+4,]; NS=#[p>0,s[,p-1],'']), (NS='';NS=s)])" + + Math::Script "a = 'Hello \r\n World \r\n!!!'; a = SaR(a,{'\r','\n'},{'$\r','$\n'}); R0 = a" + Math::Script "NS = '$\"In quotes$\"'; TQ(); R1=NS; R3=P(s(R1),'qu')" + Math::Script "NS = 'No quotes'; TQ(); R2=NS" + Math::Script "NS='123\r\n456\r\n789'; DL(); R4=NS; DL(); R5=NS; DL(); R6=NS; R7=NS" + + + DetailPrint "'$R0'" + DetailPrint "'$R1'" + DetailPrint "'$R2'" + DetailPrint "'$R3'" + DetailPrint "'$R4'" + DetailPrint "'$R5'" + DetailPrint "'$R6'" + DetailPrint "'$R7'" +SectionEnd + +; eof diff --git a/installer/NSIS/Examples/Math/mathtest.ini b/installer/NSIS/Examples/Math/mathtest.ini new file mode 100644 index 0000000..f18075b --- /dev/null +++ b/installer/NSIS/Examples/Math/mathtest.ini @@ -0,0 +1,101 @@ +[Settings] +NumFields=10 +NextButtonText=Execute +CancelButtonText=Quit +BackButtonText=Readme + +[Field 1] +Type=label +Text=Enter your script here: +Left=0 +Right=-1 +Top=0 +Bottom=8 + +[Field 2] +Type=text +Left=0 +Right=-1 +Top=9 +Bottom=55 +flags=MULTILINE|WANTRETURN|HSCROLL +State="" + +[Field 3] +Type=text +Left=53 +Right=175 +Top=56 +Bottom=140 +flags=MULTILINE|READONLY +State="" + +[Field 4] +Type=text +Left=175 +Right=-1 +Top=56 +Bottom=140 +flags=MULTILINE|READONLY +State="" + +[Field 5] +Type=RadioButton +Left=0 +Right=-1 +Top=70 +Bottom=80 +flags=GROUP +Text="Your script" +State=1 + +[Field 6] +Type=RadioButton +Left=0 +Right=-1 +Top=80 +Bottom=90 +flags= +Text="Sample 1" +State=0 + +[Field 7] +Type=RadioButton +Left=0 +Right=-1 +Top=90 +Bottom=100 +flags= +Text="Sample 2" +State=0 + +[Field 8] +Type=RadioButton +Left=0 +Right=-1 +Top=100 +Bottom=110 +flags= +Text="Sample 3" +State=0 + +[Field 9] +Type=RadioButton +Left=0 +Right=-1 +Top=110 +Bottom=120 +flags= +Text="Sample 4" +State=0 + +[Field 10] +Type=RadioButton +Left=0 +Right=-1 +Top=120 +Bottom=130 +flags= +Text="Sample 5" +State=0 + diff --git a/installer/NSIS/Examples/Math/mathtest.nsi b/installer/NSIS/Examples/Math/mathtest.nsi new file mode 100644 index 0000000..191fa8b --- /dev/null +++ b/installer/NSIS/Examples/Math/mathtest.nsi @@ -0,0 +1,171 @@ +;NSIS Modern User Interface version 1.65 +;InstallOptions Example Script +;Written by Joost Verburg + + !define MUI_BUTTONTEXT_NEXT "Execute" + +;--------------------- +;Include Modern UI + + !include "MUI.nsh" + +;-------------------------------- +;Product Info + +Name "Math::Script Test" + +;-------------------------------- +;Configuration + + ;General + OutFile "MathTest.exe" + +;-------------------------------- +;Variables + + Var TEMP1 + Var TEMP2 + Var TEMP3 + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_LICENSE "mathtest.txt" + Page custom ScriptPageEnter + Page instfiles + +;-------------------------------- +;Modern UI Configuration + +; !define MUI_ABORTWARNING + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Reserve Files + + ;Things that need to be extracted on first (keep these lines before any File command!) + ;Only for BZIP2 compression + + ReserveFile "MathTest.ini" + !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + +;-------------------------------- +;Installer Functions + +LangString SCRIPTSAMPLE0 ${LANG_ENGLISH} "r0 = 'Hello'; r1 = 'Math::Script'\r\nr0 += ' from the ' + r1 + '!'; r1=''" +LangString SCRIPTSAMPLE1 ${LANG_ENGLISH} "a =0; b=1.0\r\n#{a++ < 100, b *= a}\r\nr0 = a; R0 = b; R1 = ff(b, 15)\r\nr1 = (a-1) + '! = ' + b" +LangString SCRIPTSAMPLE2 ${LANG_ENGLISH} 'pi=3.14159; \r\nangle = pi/4;\r\ntext = "x = " + ff(angle,16+3) \r\nr0 = text += ", sin x = " + sin(angle)' +LangString SCRIPTSAMPLE3 ${LANG_ENGLISH} "v1 = 123.456; v2 = 123456789.1011\r\nr0 = v1; r1 = v2\r\nr2 = ff(v1, 3); r3 = ff(v2, 3); r4 = ff(v1, 3+16); r5 = ff(v2, 3+16)\r\nr6 = ff(v1, 3+32); r7 = ff(v2, 3+32); r8 = ff(v1, 3+32+64); r9 = ff(v2, 3+32+64)\r\n" +LangString SCRIPTSAMPLE4 ${LANG_ENGLISH} "a = 10000; b = 0; #{--a > 0, b+= a}; r0 = a; r1 = b\r\nz = 1.55; r2 = #[z > 1.5, 'Its greater', 'Its lower']\r\nz = 1.45; r3 = #[z > 1.5, 'Its greater', 'Its lower']" +LangString SCRIPTSAMPLE5 ${LANG_ENGLISH} 'r0 = "123a123"\r\nr1 = r0; \r\nr2 = s(r0); r3 = f(r0); r4 = i(r0); r5 = l(r0)' + +Function .onInit + + ;Extract InstallOptions INI files + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "MathTest.ini" + + Strcpy "$TEMP1" "$(SCRIPTSAMPLE0)" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 2" "State" $TEMP1 + +FunctionEnd + +LangString TEXT_IO_TITLE ${LANG_ENGLISH} "MathTest Script Page" +LangString TEXT_IO_SUBTITLE ${LANG_ENGLISH} "Try your scripting capapibilites or test one of sample scripts" + + +Function DumpVariables + Strcpy "$TEMP1" "$$0='$0'\r\n$$1='$1'\r\n$$2='$2'\r\n$$3='$3'\r\n$$4='$4'\r\n$$5='$5'\r\n$$6='$6'\r\n$$7='$7'\r\n$$8='$8'\r\n$$9='$9'" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 3" "State" $TEMP1 + Strcpy "$TEMP1" "$$R0='$R0'\r\n$$R1='$R1'\r\n$$R2='$R2'\r\n$$R3='$R3'\r\n$$R4='$R4'\r\n$$R5='$R5'\r\n$$R6='$R6'\r\n$$R7='$R7'\r\n$$R8='$R8'\r\n$$R9='$R9'" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 4" "State" $TEMP1 +FunctionEnd + +Function ClearVariables + Math::Script "r0=r1=r2=r3=r4=r5=r6=r7=r8=r9=R0=R1=R2=R3=R4=R5=R6=R7=R8=R9=''" +FunctionEnd + +Function GetLine + push $TEMP1 + Math::Script "mtsDL()" + pop $TEMP2 + pop $TEMP1 +FunctionEnd + +Function ExecuteScript + !insertmacro MUI_INSTALLOPTIONS_READ $TEMP1 "MathTest.ini" "Field 2" "State" + + Math::Script "mtsTQ(s) (s = s(NS); #[s[0]=='$\"',s=s[1,]]; #[s[-1]=='$\"',s=s[,-2]]; NS = s)" + Math::Script "mtsP(s,e, p,i) (p=-1;i=0; #{(i=0, (NS=s[p+4,]; NS=#[p>0,s[,p-1],'']), (NS='';NS=s)])" + + push $TEMP1 + ; remove "" + Math::Script "mtsTQ()" + pop $TEMP1 + + ; script at $TEMP1 +Go: + StrLen $TEMP3 $TEMP1 + IntCmp $TEMP3 0 End + ; get single line to $TEMP2 + Call GetLine +; MessageBox MB_OK "'$TEMP2' '$TEMP1'" + Math::Script "$TEMP2" + goto Go +End: + Math::Script "" +FunctionEnd + +Function ScriptPageEnter + + !insertmacro MUI_HEADER_TEXT "$(TEXT_IO_TITLE)" "$(TEXT_IO_SUBTITLE)" + +Again: + Call ClearVariables + Call ExecuteScript + Call DumpVariables + + !insertmacro MUI_INSTALLOPTIONS_DISPLAY_RETURN "mathtest.ini" + pop $TEMP3 + + !insertmacro MUI_INSTALLOPTIONS_READ $TEMP1 "MathTest.ini" "Field 5" "State" + IntCmp $TEMP1 1 Test + + Strcpy "$TEMP2" "$(SCRIPTSAMPLE1)" + !insertmacro MUI_INSTALLOPTIONS_READ $TEMP1 "MathTest.ini" "Field 6" "State" + IntCmp $TEMP1 1 Write + + Strcpy "$TEMP2" "$(SCRIPTSAMPLE2)" + !insertmacro MUI_INSTALLOPTIONS_READ $TEMP1 "MathTest.ini" "Field 7" "State" + IntCmp $TEMP1 1 Write + + Strcpy "$TEMP2" "$(SCRIPTSAMPLE3)" + !insertmacro MUI_INSTALLOPTIONS_READ $TEMP1 "MathTest.ini" "Field 8" "State" + IntCmp $TEMP1 1 Write + + Strcpy "$TEMP2" "$(SCRIPTSAMPLE4)" + !insertmacro MUI_INSTALLOPTIONS_READ $TEMP1 "MathTest.ini" "Field 9" "State" + IntCmp $TEMP1 1 Write + + Strcpy "$TEMP2" "$(SCRIPTSAMPLE5)" + +Write: + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 2" "State" "$TEMP2" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 5" "State" "1" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 6" "State" "0" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 7" "State" "0" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 8" "State" "0" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 9" "State" "0" + !insertmacro MUI_INSTALLOPTIONS_WRITE "MathTest.ini" "Field 10" "State" "0" + +Test: + Strcmp $TEMP3 "success" Again + +FunctionEnd + +Section "Dummy Section" SecDummy +SectionEnd diff --git a/installer/NSIS/Examples/Math/mathtest.txt b/installer/NSIS/Examples/Math/mathtest.txt new file mode 100644 index 0000000..f911e66 --- /dev/null +++ b/installer/NSIS/Examples/Math/mathtest.txt @@ -0,0 +1,7 @@ +Math Tester. + +This demo allows you to test your Math::Script expressions without need to compile anything. Just enter your expressions into multiline editbox (every single line is a separate call to Math::Script) or select one of sample expressions and press Execute. + +Every call to Math::Script can accept up to 1kb of script, but this demo is limited to the summ of 1 kb at all lines. And... watch your scripts. No.... Watch your errors at scripts! + +(c) Brainsucker, 2003. diff --git a/installer/NSIS/Examples/Memento.nsi b/installer/NSIS/Examples/Memento.nsi new file mode 100644 index 0000000..531bc57 --- /dev/null +++ b/installer/NSIS/Examples/Memento.nsi @@ -0,0 +1,79 @@ +!include LogicLib.nsh +!include Memento.nsh + +Name Memento +OutFile Memento.exe + +XPStyle on + +ShowInstDetails show + +Page components +Page instfiles + +RequestExecutionLevel user + +# settings + +!define MEMENTO_REGISTRY_ROOT HKCU +!define MEMENTO_REGISTRY_KEY "Software\NSIS\Memento Test" + +# restore + +Function .onInit + + ${If} ${Cmd} `MessageBox MB_YESNO "Would you like to load an example state?" IDYES` + + DeleteRegKey HKCU "Software\NSIS\Memento Test" + + WriteRegStr HKCU "Software\NSIS\Memento Test" MementoSectionUsed "" + WriteRegDWORD HKCU "Software\NSIS\Memento Test" MementoSection_sec_horse 1 + WriteRegDWORD HKCU "Software\NSIS\Memento Test" MementoSection_sec_chicken 1 + WriteRegDWORD HKCU "Software\NSIS\Memento Test" MementoSection_sec_donkey 0 + WriteRegDWORD HKCU "Software\NSIS\Memento Test" MementoSection_sec_croc 0 + + ${EndIf} + + ${MementoSectionRestore} + +FunctionEnd + +# sections + +${MementoSection} horse sec_horse +${MementoSectionEnd} + +${MementoSection} donkey sec_donkey +${MementoSectionEnd} + +${MementoSection} chicken sec_chicken +${MementoSectionEnd} + +SectionGroup /e group + + SectionGroup /e group + + ${MementoSection} croc sec_croc + ${MementoSectionEnd} + + ${MementoSection} cow sec_cow + ${MementoSectionEnd} + + SectionGroupEnd + +SectionGroupEnd + +${MementoUnselectedSection} dinosaur sec_dinosaur +${MementoSectionEnd} + +# done... + +${MementoSectionDone} + +# save + +Function .onInstSuccess + + ${MementoSectionSave} + +FunctionEnd diff --git a/installer/NSIS/Examples/Modern UI/Basic.nsi b/installer/NSIS/Examples/Modern UI/Basic.nsi new file mode 100644 index 0000000..d0fc7a2 --- /dev/null +++ b/installer/NSIS/Examples/Modern UI/Basic.nsi @@ -0,0 +1,88 @@ +;NSIS Modern User Interface +;Basic Example Script +;Written by Joost Verburg + +;-------------------------------- +;Include Modern UI + + !include "MUI2.nsh" + +;-------------------------------- +;General + + ;Name and file + Name "Modern UI Test" + OutFile "Basic.exe" + + ;Default installation folder + InstallDir "$LOCALAPPDATA\Modern UI Test" + + ;Get installation folder from registry if available + InstallDirRegKey HKCU "Software\Modern UI Test" "" + + ;Request application privileges for Windows Vista + RequestExecutionLevel user + +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" + !insertmacro MUI_PAGE_COMPONENTS + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + SetOutPath "$INSTDIR" + + ;ADD YOUR OWN FILES HERE... + + ;Store installation folder + WriteRegStr HKCU "Software\Modern UI Test" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + +SectionEnd + +;-------------------------------- +;Descriptions + + ;Language strings + LangString DESC_SecDummy ${LANG_ENGLISH} "A test section." + + ;Assign language strings to sections + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + ;ADD YOUR OWN FILES HERE... + + Delete "$INSTDIR\Uninstall.exe" + + RMDir "$INSTDIR" + + DeleteRegKey /ifempty HKCU "Software\Modern UI Test" + +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/Modern UI/HeaderBitmap.nsi b/installer/NSIS/Examples/Modern UI/HeaderBitmap.nsi new file mode 100644 index 0000000..c31cdee --- /dev/null +++ b/installer/NSIS/Examples/Modern UI/HeaderBitmap.nsi @@ -0,0 +1,90 @@ +;NSIS Modern User Interface +;Header Bitmap Example Script +;Written by Joost Verburg + +;-------------------------------- +;Include Modern UI + + !include "MUI2.nsh" + +;-------------------------------- +;General + + ;Name and file + Name "Modern UI Test" + OutFile "HeaderBitmap.exe" + + ;Default installation folder + InstallDir "$LOCALAPPDATA\Modern UI Test" + + ;Get installation folder from registry if available + InstallDirRegKey HKCU "Software\Modern UI Test" "" + + ;Request application privileges for Windows Vista + RequestExecutionLevel user + +;-------------------------------- +;Interface Configuration + + !define MUI_HEADERIMAGE + !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp" ; optional + !define MUI_ABORTWARNING + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" + !insertmacro MUI_PAGE_COMPONENTS + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + SetOutPath "$INSTDIR" + + ;ADD YOUR OWN FILES HERE... + + ;Store installation folder + WriteRegStr HKCU "Software\Modern UI Test" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + +SectionEnd + +;-------------------------------- +;Descriptions + + ;Language strings + LangString DESC_SecDummy ${LANG_ENGLISH} "A test section." + + ;Assign language strings to sections + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + ;ADD YOUR OWN FILES HERE... + + Delete "$INSTDIR\Uninstall.exe" + + RMDir "$INSTDIR" + + DeleteRegKey /ifempty HKCU "Software\Modern UI Test" + +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/Modern UI/MultiLanguage.nsi b/installer/NSIS/Examples/Modern UI/MultiLanguage.nsi new file mode 100644 index 0000000..43569bb --- /dev/null +++ b/installer/NSIS/Examples/Modern UI/MultiLanguage.nsi @@ -0,0 +1,178 @@ +;NSIS Modern User Interface +;Multilingual Example Script +;Written by Joost Verburg + +;-------------------------------- +;Include Modern UI + + !include "MUI2.nsh" + +;-------------------------------- +;General + + ;Name and file + Name "Modern UI Test" + OutFile "MultiLanguage.exe" + + ;Default installation folder + InstallDir "$LOCALAPPDATA\Modern UI Test" + + ;Get installation folder from registry if available + InstallDirRegKey HKCU "Software\Modern UI Test" "" + + ;Request application privileges for Windows Vista + RequestExecutionLevel user + +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING + +;-------------------------------- +;Language Selection Dialog Settings + + ;Remember the installer language + !define MUI_LANGDLL_REGISTRY_ROOT "HKCU" + !define MUI_LANGDLL_REGISTRY_KEY "Software\Modern UI Test" + !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" + !insertmacro MUI_PAGE_COMPONENTS + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" ;first language is the default language + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "SpanishInternational" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "NorwegianNynorsk" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "SerbianLatin" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_LANGUAGE "Mongolian" + !insertmacro MUI_LANGUAGE "Luxembourgish" + !insertmacro MUI_LANGUAGE "Albanian" + !insertmacro MUI_LANGUAGE "Breton" + !insertmacro MUI_LANGUAGE "Belarusian" + !insertmacro MUI_LANGUAGE "Icelandic" + !insertmacro MUI_LANGUAGE "Malay" + !insertmacro MUI_LANGUAGE "Bosnian" + !insertmacro MUI_LANGUAGE "Kurdish" + !insertmacro MUI_LANGUAGE "Irish" + !insertmacro MUI_LANGUAGE "Uzbek" + !insertmacro MUI_LANGUAGE "Galician" + !insertmacro MUI_LANGUAGE "Afrikaans" + !insertmacro MUI_LANGUAGE "Catalan" + !insertmacro MUI_LANGUAGE "Esperanto" + +;-------------------------------- +;Reserve Files + + ;If you are using solid compression, files that are required before + ;the actual installation should be stored first in the data block, + ;because this will make your installer start faster. + + !insertmacro MUI_RESERVEFILE_LANGDLL + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + SetOutPath "$INSTDIR" + + ;ADD YOUR OWN FILES HERE... + + ;Store installation folder + WriteRegStr HKCU "Software\Modern UI Test" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + +SectionEnd + +;-------------------------------- +;Installer Functions + +Function .onInit + + !insertmacro MUI_LANGDLL_DISPLAY + +FunctionEnd + +;-------------------------------- +;Descriptions + + ;USE A LANGUAGE STRING IF YOU WANT YOUR DESCRIPTIONS TO BE LANGAUGE SPECIFIC + + ;Assign descriptions to sections + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} "A test section." + !insertmacro MUI_FUNCTION_DESCRIPTION_END + + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + ;ADD YOUR OWN FILES HERE... + + Delete "$INSTDIR\Uninstall.exe" + + RMDir "$INSTDIR" + + DeleteRegKey /ifempty HKCU "Software\Modern UI Test" + +SectionEnd + +;-------------------------------- +;Uninstaller Functions + +Function un.onInit + + !insertmacro MUI_UNGETLANGUAGE + +FunctionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/Modern UI/StartMenu.nsi b/installer/NSIS/Examples/Modern UI/StartMenu.nsi new file mode 100644 index 0000000..760f8ac --- /dev/null +++ b/installer/NSIS/Examples/Modern UI/StartMenu.nsi @@ -0,0 +1,114 @@ +;NSIS Modern User Interface +;Start Menu Folder Selection Example Script +;Written by Joost Verburg + +;-------------------------------- +;Include Modern UI + + !include "MUI2.nsh" + +;-------------------------------- +;General + + ;Name and file + Name "Modern UI Test" + OutFile "StartMenu.exe" + + ;Default installation folder + InstallDir "$LOCALAPPDATA\Modern UI Test" + + ;Get installation folder from registry if available + InstallDirRegKey HKCU "Software\Modern UI Test" "" + + ;Request application privileges for Windows Vista + RequestExecutionLevel user + +;-------------------------------- +;Variables + + Var StartMenuFolder + +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" + !insertmacro MUI_PAGE_COMPONENTS + !insertmacro MUI_PAGE_DIRECTORY + + ;Start Menu Folder Page Configuration + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Modern UI Test" + !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + + !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder + + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + SetOutPath "$INSTDIR" + + ;ADD YOUR OWN FILES HERE... + + ;Store installation folder + WriteRegStr HKCU "Software\Modern UI Test" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + + ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$StartMenuFolder" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe" + + !insertmacro MUI_STARTMENU_WRITE_END + +SectionEnd + +;-------------------------------- +;Descriptions + + ;Language strings + LangString DESC_SecDummy ${LANG_ENGLISH} "A test section." + + ;Assign language strings to sections + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + ;ADD YOUR OWN FILES HERE... + + Delete "$INSTDIR\Uninstall.exe" + + RMDir "$INSTDIR" + + !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder + + Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" + RMDir "$SMPROGRAMS\$StartMenuFolder" + + DeleteRegKey /ifempty HKCU "Software\Modern UI Test" + +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/Modern UI/WelcomeFinish.nsi b/installer/NSIS/Examples/Modern UI/WelcomeFinish.nsi new file mode 100644 index 0000000..2121ea1 --- /dev/null +++ b/installer/NSIS/Examples/Modern UI/WelcomeFinish.nsi @@ -0,0 +1,92 @@ +;NSIS Modern User Interface +;Welcome/Finish Page Example Script +;Written by Joost Verburg + +;-------------------------------- +;Include Modern UI + + !include "MUI2.nsh" + +;-------------------------------- +;General + + ;Name and file + Name "Modern UI Test" + OutFile "WelcomeFinish.exe" + + ;Default installation folder + InstallDir "$LOCALAPPDATA\Modern UI Test" + + ;Get installation folder from registry if available + InstallDirRegKey HKCU "Software\Modern UI Test" "" + + ;Request application privileges for Windows Vista + RequestExecutionLevel user + +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" + !insertmacro MUI_PAGE_COMPONENTS + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_WELCOME + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + !insertmacro MUI_UNPAGE_FINISH + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Installer Sections + +Section "Dummy Section" SecDummy + + SetOutPath "$INSTDIR" + + ;ADD YOUR OWN FILES HERE... + + ;Store installation folder + WriteRegStr HKCU "Software\Modern UI Test" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + +SectionEnd + +;-------------------------------- +;Descriptions + + ;Language strings + LangString DESC_SecDummy ${LANG_ENGLISH} "A test section." + + ;Assign language strings to sections + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + ;ADD YOUR OWN FILES HERE... + + Delete "$INSTDIR\Uninstall.exe" + + RMDir "$INSTDIR" + + DeleteRegKey /ifempty HKCU "Software\Modern UI Test" + +SectionEnd diff --git a/installer/NSIS/Examples/Plugin/exdll-vs2008.sln b/installer/NSIS/Examples/Plugin/exdll-vs2008.sln new file mode 100644 index 0000000..7bd7046 --- /dev/null +++ b/installer/NSIS/Examples/Plugin/exdll-vs2008.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exdll", "exdll.vcproj", "{5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Debug|Win32.ActiveCfg = Debug|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Debug|Win32.Build.0 = Debug|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Release|Win32.ActiveCfg = Release|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/installer/NSIS/Examples/Plugin/exdll-vs2008.vcproj b/installer/NSIS/Examples/Plugin/exdll-vs2008.vcproj new file mode 100644 index 0000000..0091add --- /dev/null +++ b/installer/NSIS/Examples/Plugin/exdll-vs2008.vcproj @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/NSIS/Examples/Plugin/exdll.c b/installer/NSIS/Examples/Plugin/exdll.c new file mode 100644 index 0000000..6e5c1ac --- /dev/null +++ b/installer/NSIS/Examples/Plugin/exdll.c @@ -0,0 +1,38 @@ +#include +#include // nsis plugin + +HINSTANCE g_hInstance; + +HWND g_hwndParent; + +void __declspec(dllexport) myFunction(HWND hwndParent, int string_size, + char *variables, stack_t **stacktop, + extra_parameters *extra) +{ + g_hwndParent=hwndParent; + + EXDLL_INIT(); + + + // note if you want parameters from the stack, pop them off in order. + // i.e. if you are called via exdll::myFunction file.dat poop.dat + // calling popstring() the first time would give you file.dat, + // and the second time would give you poop.dat. + // you should empty the stack of your parameters, and ONLY your + // parameters. + + // do your stuff here + { + char buf[1024]; + wsprintf(buf,"$0=%s\n",getuservariable(INST_0)); + MessageBox(g_hwndParent,buf,0,MB_OK); + } +} + + + +BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + g_hInstance=hInst; + return TRUE; +} diff --git a/installer/NSIS/Examples/Plugin/exdll.dpr b/installer/NSIS/Examples/Plugin/exdll.dpr new file mode 100644 index 0000000..ec70b10 --- /dev/null +++ b/installer/NSIS/Examples/Plugin/exdll.dpr @@ -0,0 +1,118 @@ +{ + NSIS ExDLL example + (C) 2001 - Peter Windridge + + Fixed and formatted by Brett Dever + http://editor.nfscheats.com/ + + Tested in Delphi 7.0 +} + +library exdll; + +uses Windows; + +type + VarConstants = ( + INST_0, + INST_1, // $1 + INST_2, // $2 + INST_3, // $3 + INST_4, // $4 + INST_5, // $5 + INST_6, // $6 + INST_7, // $7 + INST_8, // $8 + INST_9, // $9 + INST_R0, // $R0 + INST_R1, // $R1 + INST_R2, // $R2 + INST_R3, // $R3 + INST_R4, // $R4 + INST_R5, // $R5 + INST_R6, // $R6 + INST_R7, // $R7 + INST_R8, // $R8 + INST_R9, // $R9 + INST_CMDLINE, // $CMDLINE + INST_INSTDIR, // $INSTDIR + INST_OUTDIR, // $OUTDIR + INST_EXEDIR, // $EXEDIR + INST_LANG, // $LANGUAGE + __INST_LAST + ); + TVariableList = INST_0..__INST_LAST; + pstack_t = ^stack_t; + stack_t = record + next: pstack_t; + text: PChar; + end; + +var + g_stringsize: integer; + g_stacktop: ^pstack_t; + g_variables: PChar; + g_hwndParent: HWND; + +function PopString(): string; +var + th: pstack_t; +begin + if integer(g_stacktop^) <> 0 then begin + th := g_stacktop^; + Result := PChar(@th.text); + g_stacktop^ := th.next; + GlobalFree(HGLOBAL(th)); + end; +end; + +procedure PushString(const str: string=''); +var + th: pstack_t; +begin + if integer(g_stacktop) <> 0 then begin + th := pstack_t(GlobalAlloc(GPTR, SizeOf(stack_t) + g_stringsize)); + lstrcpyn(@th.text, PChar(str), g_stringsize); + th.next := g_stacktop^; + g_stacktop^ := th; + end; +end; + +function GetUserVariable(const varnum: TVariableList): string; +begin + if (integer(varnum) >= 0) and (integer(varnum) < integer(__INST_LAST)) then + Result := g_variables + integer(varnum) * g_stringsize + else + Result := ''; +end; + +procedure SetUserVariable(const varnum: TVariableList; const value: string); +begin + if (value <> '') and (integer(varnum) >= 0) and (integer(varnum) < integer(__INST_LAST)) then + lstrcpy(g_variables + integer(varnum) * g_stringsize, PChar(value)) +end; + +procedure NSISDialog(const text, caption: string; const buttons: integer); +begin + MessageBox(g_hwndParent, PChar(text), PChar(caption), buttons); +end; + +procedure ex_dll(const hwndParent: HWND; const string_size: integer; const variables: PChar; const stacktop: pointer); cdecl; +begin + // setup global variables + g_stringsize := string_size; + g_hwndParent := hwndParent; + g_stacktop := stacktop; + g_variables := variables; + // end global variable setup + + NSISDialog(GetUserVariable(INST_0), 'The value of $0', MB_OK); + NSISDialog(PopString, 'pop', MB_OK); + PushString('Hello, this is a push'); + SetUserVariable(INST_0, 'This is user var $0'); +end; + +exports ex_dll; + +begin +end. diff --git a/installer/NSIS/Examples/Plugin/exdll.dsp b/installer/NSIS/Examples/Plugin/exdll.dsp new file mode 100644 index 0000000..c9e31ca --- /dev/null +++ b/installer/NSIS/Examples/Plugin/exdll.dsp @@ -0,0 +1,112 @@ +# Microsoft Developer Studio Project File - Name="exdll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=exdll - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "exdll.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "exdll.mak" CFG="exdll - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "exdll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "exdll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "exdll - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O1 /I "." /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /D NSISCALL=__stdcall /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib nsis\pluginapi.lib /nologo /entry:"DllMain" /dll /machine:I386 /nodefaultlib /out:"../../Plugins/exdll.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "exdll - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "." /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXDLL_EXPORTS" /D NSISCALL=__stdcall /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib nsis\pluginapi.lib /nologo /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "exdll - Win32 Release" +# Name "exdll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\exdll.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\nsis\pluginapi.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/installer/NSIS/Examples/Plugin/exdll.dsw b/installer/NSIS/Examples/Plugin/exdll.dsw new file mode 100644 index 0000000..f40ce32 --- /dev/null +++ b/installer/NSIS/Examples/Plugin/exdll.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "exdll"=.\exdll.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/installer/NSIS/Examples/Plugin/exdll_with_unit.dpr b/installer/NSIS/Examples/Plugin/exdll_with_unit.dpr new file mode 100644 index 0000000..ad206af --- /dev/null +++ b/installer/NSIS/Examples/Plugin/exdll_with_unit.dpr @@ -0,0 +1,31 @@ +{ + NSIS ExDLL2 example + Original is ExDLL + (C) 2001 - Peter Windridge + + Changed with delphi unit nsis.pas + by bernhard mayer + + Tested in Delphi 7.0 +} + +library exdll; + +uses + nsis, windows; + +procedure ex_dll(const hwndParent: HWND; const string_size: integer; const variables: PChar; const stacktop: pointer); cdecl; +begin + // set up global variables + Init(hwndParent, string_size, variables, stacktop); + + NSISDialog(GetUserVariable(INST_0), 'The value of $0', MB_OK); + NSISDialog(PopString, 'pop', MB_OK); + PushString('Hello, this is a push'); + SetUserVariable(INST_0, 'This is user var $0'); +end; + +exports ex_dll; + +begin +end. diff --git a/installer/NSIS/Examples/Plugin/extdll.inc b/installer/NSIS/Examples/Plugin/extdll.inc new file mode 100644 index 0000000..e3b7476 --- /dev/null +++ b/installer/NSIS/Examples/Plugin/extdll.inc @@ -0,0 +1,145 @@ +;################################################################ +; ExtDLL header for MASM32 +; +; Author: Ramon +; +; Obs: This header must be included after windows.inc and kernel32.inc +; because it need the prototypes for lstrcpy, lstrcpyn, +; GlobalAlloc and GlobalFree +; +;################################################################ +stack_t struct + next dd ? + text dd ? ; 1 DUP(?) ; this should be the length of string_size +stack_t ends + +.const +; For page showing plug-ins +WM_NOTIFY_OUTER_NEXT equ (WM_USER+0x8) +WM_NOTIFY_CUSTOM_READY equ (WM_USER+0xd) +NOTIFY_BYE_BYE equ 'x' + +INST_0 EQU 0 ; $0 +INST_1 EQU 1 ; $1 +INST_2 EQU 2 ; $2 +INST_3 EQU 3 ; $3 +INST_4 EQU 4 ; $4 +INST_5 EQU 5 ; $5 +INST_6 EQU 6 ; $6 +INST_7 EQU 7 ; $7 +INST_8 EQU 8 ; $8 +INST_9 EQU 9 ; $9 +INST_R0 EQU 10 ; $R0 +INST_R1 EQU 11 ; $R1 +INST_R2 EQU 12 ; $R2 +INST_R3 EQU 13 ; $R3 +INST_R4 EQU 14 ; $R4 +INST_R5 EQU 15 ; $R5 +INST_R6 EQU 16 ; $R6 +INST_R7 EQU 17 ; $R7 +INST_R8 EQU 18 ; $R8 +INST_R9 EQU 19 ; $R9 +INST_CMDLINE EQU 20 ; $CMDLINE +INST_INSTDIR EQU 21 ; $INSTDIR +INST_OUTDIR EQU 22 ; $OUTDIR +INST_EXEDIR EQU 23 ; $EXEDIR +INST_LANG EQU 24 ; $LANGUAGE +__INST_LAST EQU 25 + +.data? +g_stringsize dd ? +g_stacktop dd ? +g_variables dd ? + +m2m MACRO M1, M2 + push M2 + pop M1 +ENDM + +EXDLL_INIT MACRO + m2m g_stringsize, string_size + m2m g_stacktop, stacktop + m2m g_variables, variables +ENDM + +.code + +; utility functions (not required but often useful) +popstring proc uses edi pStr:DWORD + + LOCAL th:DWORD + + mov edi, g_stacktop + cmp edi, 0 + jz STACK_ERR + mov edi, [edi] + cmp edi, 0 + jz STACK_ERR + + ASSUME edi:PTR stack_t + invoke lstrcpy, pStr, ADDR [edi].text + mov th , edi + mov edi, [edi].next + mov eax, g_stacktop + mov [eax], edi + invoke GlobalFree, th + ASSUME edi:PTR NOTHING + mov eax, 0 + ret + +STACK_ERR: + mov eax, 1 + ret + +popstring endp + +pushstring proc uses edi pStr:DWORD + + cmp g_stacktop, 0 + jz STACK_ERR + + mov eax, sizeof stack_t + add eax, g_stringsize + invoke GlobalAlloc, GPTR, eax + + mov edi, eax + assume edi:PTR stack_t + + invoke lstrcpyn, ADDR [edi].text, pStr, g_stringsize + mov eax, g_stacktop + push DWORD PTR[eax] + mov [eax], edi + pop eax + ;lea edi, [edi].next ; Not needed [edi].next == edi + mov DWORD PTR[edi], eax + ASSUME edi:PTR NOTHING + +STACK_ERR: + ret + +pushstring endp + +getuservariable proc varnum:DWORD + + .if varnum < 0 || varnum >= __INST_LAST + xor eax, eax + .else + mov eax, varnum + imul eax, g_stringsize + add eax, g_variables + .endif + ret + +getuservariable endp + +setuservariable proc varnum:DWORD, var:DWORD + + .if (var != NULL && varnum >= 0 && varnum < __INST_LAST) + mov eax, varnum + imul eax, g_stringsize + add eax, g_variables + invoke lstrcpy, eax, var + .endif + ret + +setuservariable endp diff --git a/installer/NSIS/Examples/Plugin/nsis.pas b/installer/NSIS/Examples/Plugin/nsis.pas new file mode 100644 index 0000000..561a3ad --- /dev/null +++ b/installer/NSIS/Examples/Plugin/nsis.pas @@ -0,0 +1,201 @@ +{ + Original Code from + (C) 2001 - Peter Windridge + + Code in seperate unit and some changes + 2003 by Bernhard Mayer + + Fixed and formatted by Brett Dever + http://editor.nfscheats.com/ + + simply include this unit in your plugin project and export + functions as needed +} + +unit nsis; + +interface + +uses + windows, CommCtrl, SysUtils; + +type + VarConstants = ( + INST_0, // $0 + INST_1, // $1 + INST_2, // $2 + INST_3, // $3 + INST_4, // $4 + INST_5, // $5 + INST_6, // $6 + INST_7, // $7 + INST_8, // $8 + INST_9, // $9 + INST_R0, // $R0 + INST_R1, // $R1 + INST_R2, // $R2 + INST_R3, // $R3 + INST_R4, // $R4 + INST_R5, // $R5 + INST_R6, // $R6 + INST_R7, // $R7 + INST_R8, // $R8 + INST_R9, // $R9 + INST_CMDLINE, // $CMDLINE + INST_INSTDIR, // $INSTDIR + INST_OUTDIR, // $OUTDIR + INST_EXEDIR, // $EXEDIR + INST_LANG, // $LANGUAGE + __INST_LAST + ); + TVariableList = INST_0..__INST_LAST; + + TExecuteCodeSegment = function (const funct_id: Integer; const parent: HWND): Integer; stdcall; + Tvalidate_filename = procedure (const filename: PChar); cdecl; + TRegisterPluginCallback = function (const unknow: Integer; const uknown2: Integer): Integer; cdecl; + + pexec_flags_t = ^exec_flags_t; + exec_flags_t = record + autoclose: Integer; + all_user_var: Integer; + exec_error: Integer; + abort: Integer; + exec_reboot: Integer; + reboot_called: Integer; + XXX_cur_insttype: Integer; + plugin_api_version: Integer; + silent: Integer; + instdir_error: Integer; + rtl: Integer; + errlvl: Integer; + alter_reg_view: Integer; + status_update: Integer; + end; + + pextrap_t = ^extrap_t; + extrap_t = record + exec_flags: Pointer; // exec_flags_t; + exec_code_segment: Pointer; // TFarProc; + validate_filename: Pointer; // Tvalidate_filename; + RegisterPluginCallback: Pointer; //TRegisterPluginCallback; + end; + + pstack_t = ^stack_t; + stack_t = record + next: pstack_t; + text: PChar; + end; + +var + g_stringsize: integer; + g_stacktop: ^pstack_t; + g_variables: PChar; + g_hwndParent: HWND; + g_hwndList: HWND; + g_hwndLogList: HWND; + + g_extraparameters: pextrap_t; + func : TExecuteCodeSegment; + extrap : extrap_t; + +procedure Init(const hwndParent: HWND; const string_size: integer; const variables: PChar; const stacktop: pointer; const extraparameters: pointer = nil); + +function LogMessage(Msg : String): BOOL; +function Call(NSIS_func : String) : Integer; +function PopString(): string; +procedure PushString(const str: string=''); +function GetUserVariable(const varnum: TVariableList): string; +procedure SetUserVariable(const varnum: TVariableList; const value: string); +procedure NSISDialog(const text, caption: string; const buttons: integer); + +implementation + +procedure Init(const hwndParent: HWND; const string_size: integer; const variables: PChar; const stacktop: pointer; const extraparameters: pointer = nil); +begin + g_stringsize := string_size; + g_hwndParent := hwndParent; + g_stacktop := stacktop; + g_variables := variables; + g_hwndList := 0; + g_hwndList := FindWindowEx(FindWindowEx(g_hwndParent, 0, '#32770', nil), 0,'SysListView32', nil); + g_extraparameters := extraparameters; + extrap := g_extraparameters^; +end; + +function Call(NSIS_func : String) : Integer; +var + NSISFun: Integer; //The ID of nsis function +begin + Result := 0; + NSISFun := StrToIntDef(NSIS_func, 0); + if (NSISFun <> 0) and (g_extraparameters <> nil) then + begin + @func := extrap.exec_code_segment; + NSISFun := NSISFun - 1; + Result := func(NSISFun, g_hwndParent); + end; +end; + +function LogMessage(Msg : String): BOOL; +var + ItemCount : Integer; + item: TLVItem; +begin + Result := FAlse; + if g_hwndList = 0 then exit; + FillChar( item, sizeof(item), 0 ); + ItemCount := SendMessage(g_hwndList, LVM_GETITEMCOUNT, 0, 0); + item.iItem := ItemCount; + item.mask := LVIF_TEXT; + item.pszText := PAnsiChar(Msg); + ListView_InsertItem(g_hwndList, item ); + ListView_EnsureVisible(g_hwndList, ItemCount, TRUE); +end; + +function PopString(): string; +var + th: pstack_t; +begin + if integer(g_stacktop^) <> 0 then begin + th := g_stacktop^; + Result := PChar(@th.text); + g_stacktop^ := th.next; + GlobalFree(HGLOBAL(th)); + end; +end; + +procedure PushString(const str: string=''); +var + th: pstack_t; +begin + if integer(g_stacktop) <> 0 then begin + th := pstack_t(GlobalAlloc(GPTR, SizeOf(stack_t) + g_stringsize)); + lstrcpyn(@th.text, PChar(str), g_stringsize); + th.next := g_stacktop^; + g_stacktop^ := th; + end; +end; + +function GetUserVariable(const varnum: TVariableList): string; +begin + if (integer(varnum) >= 0) and (integer(varnum) < integer(__INST_LAST)) then + Result := g_variables + integer(varnum) * g_stringsize + else + Result := ''; +end; + +procedure SetUserVariable(const varnum: TVariableList; const value: string); +begin + if (value <> '') and (integer(varnum) >= 0) and (integer(varnum) < integer(__INST_LAST)) then + lstrcpy(g_variables + integer(varnum) * g_stringsize, PChar(value)) +end; + +procedure NSISDialog(const text, caption: string; const buttons: integer); +begin + MessageBox(g_hwndParent, PChar(text), PChar(caption), buttons); +end; + +begin + +end. + diff --git a/installer/NSIS/Examples/Plugin/nsis/api.h b/installer/NSIS/Examples/Plugin/nsis/api.h new file mode 100644 index 0000000..e22c72c --- /dev/null +++ b/installer/NSIS/Examples/Plugin/nsis/api.h @@ -0,0 +1,83 @@ +/* + * apih + * + * This file is a part of NSIS. + * + * Copyright (C) 1999-2009 Nullsoft and Contributors + * + * Licensed under the zlib/libpng license (the "License"); + * you may not use this file except in compliance with the License. + * + * Licence details can be found in the file COPYING. + * + * This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef _NSIS_EXEHEAD_API_H_ +#define _NSIS_EXEHEAD_API_H_ + +// Starting with NSIS 2.42, you can check the version of the plugin API in exec_flags->plugin_api_version +// The format is 0xXXXXYYYY where X is the major version and Y is the minor version (MAKELONG(y,x)) +// When doing version checks, always remember to use >=, ex: if (pX->exec_flags->plugin_api_version >= NSISPIAPIVER_1_0) {} + +#define NSISPIAPIVER_1_0 0x00010000 +#define NSISPIAPIVER_CURR NSISPIAPIVER_1_0 + +// NSIS Plug-In Callback Messages +enum NSPIM +{ + NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup + NSPIM_GUIUNLOAD, // Called after .onGUIEnd +}; + +// Prototype for callbacks registered with extra_parameters->RegisterPluginCallback() +// Return NULL for unknown messages +// Should always be __cdecl for future expansion possibilities +typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM); + +// extra_parameters data structures containing other interesting stuff +// but the stack, variables and HWND passed on to plug-ins. +typedef struct +{ + int autoclose; + int all_user_var; + int exec_error; + int abort; + int exec_reboot; // NSIS_SUPPORT_REBOOT + int reboot_called; // NSIS_SUPPORT_REBOOT + int XXX_cur_insttype; // depreacted + int plugin_api_version; // see NSISPIAPIVER_CURR + // used to be XXX_insttype_changed + int silent; // NSIS_CONFIG_SILENT_SUPPORT + int instdir_error; + int rtl; + int errlvl; + int alter_reg_view; + int status_update; +} exec_flags_t; + +#ifndef NSISCALL +# define NSISCALL __stdcall +#endif + +typedef struct { + exec_flags_t *exec_flags; + int (NSISCALL *ExecuteCodeSegment)(int, HWND); + void (NSISCALL *validate_filename)(char *); + int (NSISCALL *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already registered and < 0 on errors +} extra_parameters; + +// Definitions for page showing plug-ins +// See Ui.c to understand better how they're used + +// sent to the outer window to tell it to go to the next inner window +#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8) + +// custom pages should send this message to let NSIS know they're ready +#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd) + +// sent as wParam with WM_NOTIFY_OUTER_NEXT when user cancels - heed its warning +#define NOTIFY_BYE_BYE 'x' + +#endif /* _PLUGIN_H_ */ diff --git a/installer/NSIS/Examples/Plugin/nsis/pluginapi.h b/installer/NSIS/Examples/Plugin/nsis/pluginapi.h new file mode 100644 index 0000000..a632026 --- /dev/null +++ b/installer/NSIS/Examples/Plugin/nsis/pluginapi.h @@ -0,0 +1,74 @@ +#ifndef ___NSIS_PLUGIN__H___ +#define ___NSIS_PLUGIN__H___ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "api.h" + +#ifndef NSISCALL +# define NSISCALL __stdcall +#endif + +#define EXDLL_INIT() { \ + g_stringsize=string_size; \ + g_stacktop=stacktop; \ + g_variables=variables; } + +typedef struct _stack_t { + struct _stack_t *next; + char text[1]; // this should be the length of string_size +} stack_t; + +enum +{ +INST_0, // $0 +INST_1, // $1 +INST_2, // $2 +INST_3, // $3 +INST_4, // $4 +INST_5, // $5 +INST_6, // $6 +INST_7, // $7 +INST_8, // $8 +INST_9, // $9 +INST_R0, // $R0 +INST_R1, // $R1 +INST_R2, // $R2 +INST_R3, // $R3 +INST_R4, // $R4 +INST_R5, // $R5 +INST_R6, // $R6 +INST_R7, // $R7 +INST_R8, // $R8 +INST_R9, // $R9 +INST_CMDLINE, // $CMDLINE +INST_INSTDIR, // $INSTDIR +INST_OUTDIR, // $OUTDIR +INST_EXEDIR, // $EXEDIR +INST_LANG, // $LANGUAGE +__INST_LAST +}; + +extern unsigned int g_stringsize; +extern stack_t **g_stacktop; +extern char *g_variables; + +int NSISCALL popstring(char *str); // 0 on success, 1 on empty stack +int NSISCALL popstringn(char *str, int maxlen); // with length limit, pass 0 for g_stringsize +int NSISCALL popint(); // pops an integer +int NSISCALL popint_or(); // with support for or'ing (2|4|8) +int NSISCALL myatoi(const char *s); // converts a string to an integer +unsigned NSISCALL myatou(const char *s); // converts a string to an unsigned integer, decimal only +int NSISCALL myatoi_or(const char *s); // with support for or'ing (2|4|8) +void NSISCALL pushstring(const char *str); +void NSISCALL pushint(int value); +char * NSISCALL getuservariable(const int varnum); +void NSISCALL setuservariable(const int varnum, const char *var); + +#ifdef __cplusplus +} +#endif + +#endif//!___NSIS_PLUGIN__H___ diff --git a/installer/NSIS/Examples/Plugin/nsis/pluginapi.lib b/installer/NSIS/Examples/Plugin/nsis/pluginapi.lib new file mode 100644 index 0000000..ea64b80 Binary files /dev/null and b/installer/NSIS/Examples/Plugin/nsis/pluginapi.lib differ diff --git a/installer/NSIS/Examples/Splash/Example.nsi b/installer/NSIS/Examples/Splash/Example.nsi new file mode 100644 index 0000000..2cc62b5 --- /dev/null +++ b/installer/NSIS/Examples/Splash/Example.nsi @@ -0,0 +1,21 @@ +Name "Splash.dll test" + +OutFile "Splash Test.exe" + +XPStyle on + +Function .onInit + # the plugins dir is automatically deleted when the installer exits + InitPluginsDir + File /oname=$PLUGINSDIR\splash.bmp "${NSISDIR}\Contrib\Graphics\Wizard\orange-nsis.bmp" + #optional + #File /oname=$PLUGINSDIR\splash.wav "C:\myprog\sound.wav" + + splash::show 1000 $PLUGINSDIR\splash + + Pop $0 ; $0 has '1' if the user closed the splash screen early, + ; '0' if everything closed normally, and '-1' if some error occurred. +FunctionEnd + +Section +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/StartMenu/Example.nsi b/installer/NSIS/Examples/StartMenu/Example.nsi new file mode 100644 index 0000000..9a9a82e --- /dev/null +++ b/installer/NSIS/Examples/StartMenu/Example.nsi @@ -0,0 +1,50 @@ +Name "StartMenu.dll test" + +OutFile "StartMenu Test.exe" + +XPStyle on + +Page directory +DirText "This installer will create some shortcuts to MakeNSIS in the start menu.$\nFor this it needs NSIS's path." \ + "Please specify the path in which you have installed NSIS:" +InstallDir "${NSISDIR}" +Function .onVerifyInstDir + IfFileExists $INSTDIR\makensis.exe +2 + Abort +FunctionEnd + +Page custom StartMenuGroupSelect "" ": Start Menu Folder" +Function StartMenuGroupSelect + Push $R1 + + StartMenu::Select /checknoshortcuts "Don't create a start menu folder" /autoadd /lastused $R0 "StartMenu.dll test" + Pop $R1 + + StrCmp $R1 "success" success + StrCmp $R1 "cancel" done + ; error + MessageBox MB_OK $R1 + StrCpy $R0 "StartMenu.dll test" # use default + Return + success: + Pop $R0 + + done: + Pop $R1 +FunctionEnd + +Page instfiles +Section + # this part is only necessary if you used /checknoshortcuts + StrCpy $R1 $R0 1 + StrCmp $R1 ">" skip + + CreateDirectory $SMPROGRAMS\$R0 + CreateShortCut $SMPROGRAMS\$R0\MakeNSISw.lnk $INSTDIR\makensisw.exe + + SetShellVarContext All + CreateDirectory $SMPROGRAMS\$R0 + CreateShortCut "$SMPROGRAMS\$R0\All users MakeNSISw.lnk" $INSTDIR\makensisw.exe + + skip: +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/StrFunc.nsi b/installer/NSIS/Examples/StrFunc.nsi new file mode 100644 index 0000000..f0df353 --- /dev/null +++ b/installer/NSIS/Examples/StrFunc.nsi @@ -0,0 +1,635 @@ +Name "NSIS StrFunc Example" +OutFile "StrFunc.exe" +ShowInstDetails show +ShowUninstDetails show +XPStyle on +RequestExecutionLevel user + +!include "StrFunc.nsh" + +# Declare used functions +${StrCase} +${StrClb} +${StrIOToNSIS} +${StrLoc} +${StrNSISToIO} +${StrRep} +${StrStr} +${StrStrAdv} +${StrTok} +${StrTrimNewLines} +${StrSort} + +${UnStrCase} +${UnStrClb} +${UnStrIOToNSIS} +${UnStrLoc} +${UnStrNSISToIO} +${UnStrRep} +${UnStrStr} +${UnStrStrAdv} +${UnStrTok} +${UnStrTrimNewLines} +${UnStrSort} + +!macro StackVerificationStart + StrCpy $0 S0 + StrCpy $1 S1 + StrCpy $2 S2 + StrCpy $3 S3 + StrCpy $4 S4 + StrCpy $5 S5 + StrCpy $6 S6 + StrCpy $7 S7 + StrCpy $8 S8 + StrCpy $9 S9 + StrCpy $R0 SR0 + StrCpy $R1 SR1 + StrCpy $R2 SR2 + StrCpy $R3 SR3 + StrCpy $R4 SR4 + StrCpy $R5 SR5 + StrCpy $R6 SR6 + StrCpy $R7 SR7 + StrCpy $R8 SR8 + StrCpy $R9 SR9 +!macroend + +!macro StackVerificationEnd + ClearErrors + ${If} $1 != "S1" + ${OrIf} $2 != "S2" + ${OrIf} $3 != "S3" + ${OrIf} $4 != "S4" + ${OrIf} $5 != "S5" + ${OrIf} $6 != "S6" + ${OrIf} $7 != "S7" + ${OrIf} $8 != "S8" + ${OrIf} $9 != "S9" + ${OrIf} $R0 != "SR0" + ${OrIf} $R1 != "SR1" + ${OrIf} $R2 != "SR2" + ${OrIf} $R3 != "SR3" + ${OrIf} $R4 != "SR4" + ${OrIf} $R5 != "SR5" + ${OrIf} $R6 != "SR6" + ${OrIf} $R7 != "SR7" + ${OrIf} $R8 != "SR8" + ${OrIf} $R9 != "SR9" + SetErrors + ${EndIf} +!macroend + +Section + + # Test case conversion + !insertmacro StackVerificationStart + ${StrCase} $0 "This is just an example. A very simple one." "" + StrCmp $0 "This is just an example. A very simple one." 0 strcaseerror + ${StrCase} $0 "THIS IS JUST AN EXAMPLE. A VERY SIMPLE ONE." "S" + StrCmp $0 "This is just an example. A very simple one." 0 strcaseerror + ${StrCase} $0 "This is just an example. A very simple one." "L" + StrCmp $0 "this is just an example. a very simple one." 0 strcaseerror + ${StrCase} $0 "This is just an example. A very simple one." "U" + StrCmp $0 "THIS IS JUST AN EXAMPLE. A VERY SIMPLE ONE." 0 strcaseerror + ${StrCase} $0 "This is just an example. A very simple one." "T" + StrCmp $0 "This Is Just An Example. A Very Simple One." 0 strcaseerror + ${StrCase} $0 "This is just an example. A very simple one." "<>" + StrCmp $0 "tHIS IS JUST AN EXAMPLE. a VERY SIMPLE ONE." 0 strcaseerror + ${StrCase} $0 "123456789!@#%^&*()-_=+[]{};:,./<>?" "S" + StrCmp $0 "123456789!@#%^&*()-_=+[]{};:,./<>?" 0 strcaseerror + ${StrCase} $0 "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#%^&*()abcdefghijklmnopqrstuvwxyz-_=+[]{};:,./<>?" "<>" + StrCmp $0 "123456789abcdefghijklmnopqrstuvwxyz!@#%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ-_=+[]{};:,./<>?" 0 strcaseerror + ${StrCase} $0 "what about taking a shower tomorrow? it's late to do so now! try to sleep now. Good Night!" "S" + StrCmp $0 "What about taking a shower tomorrow? It's late to do so now! Try to sleep now. Good night!" 0 strcaseerror + !insertmacro StackVerificationEnd + IfErrors strcaseerror + + DetailPrint "PASSED StrCase test" + Goto +2 +strcaseerror: + DetailPrint "FAILED StrCase test" + + # Test clipboard function + !insertmacro StackVerificationStart + ${StrClb} $0 "StrFunc clipboard test" ">" + StrCmp $0 "" 0 strclberror + ${StrClb} $0 "StrFunc clipboard test #2" "<>" + StrCmp $0 "StrFunc clipboard test" 0 strclberror + ${StrClb} $0 "" "<" + StrCmp $0 "StrFunc clipboard test #2" 0 strclberror + ${StrClb} $0 "" "" + StrCmp $0 "" 0 strclberror + !insertmacro StackVerificationEnd + IfErrors strclberror + + DetailPrint "PASSED StrClb test" + Goto +2 +strclberror: + DetailPrint "FAILED StrClb test" + + # Test IO functions + !insertmacro StackVerificationStart + !macro testio str + ${StrNSISToIO} $0 "${str}" + ${StrIOToNSIS} $0 $0 + StrCmp $0 "${str}" 0 ioerror + !macroend + !insertmacro testio "$\rtest$\n" + !insertmacro testio "test$\n" + !insertmacro testio "$\rtest" + !insertmacro testio "test" + !insertmacro testio "$\r\$\t$\n" + !insertmacro testio "$\r \ $\t $\n $$" + !insertmacro testio "" + !insertmacro testio " " + !insertmacro StackVerificationEnd + IfErrors ioerror + + DetailPrint "PASSED StrNSISToIO/StrIOToNSIS test" + Goto +2 +ioerror: + DetailPrint "FAILED StrNSISToIO/StrIOToNSIS test" + + # Test string search functions + !insertmacro StackVerificationStart + ${StrLoc} $0 "This is just an example" "just" "<" + StrCmp $0 "11" 0 strlocerror + ${StrLoc} $0 a abc < + StrCmp $0 "" 0 strlocerror + ${StrLoc} $0 a abc > + StrCmp $0 "" 0 strlocerror + ${StrLoc} $0 abc a > + StrCmp $0 "0" 0 strlocerror + ${StrLoc} $0 abc b > + StrCmp $0 "1" 0 strlocerror + ${StrLoc} $0 abc c > + StrCmp $0 "2" 0 strlocerror + ${StrLoc} $0 abc a < + StrCmp $0 "2" 0 strlocerror + ${StrLoc} $0 abc b < + StrCmp $0 "1" 0 strlocerror + ${StrLoc} $0 abc c < + StrCmp $0 "0" 0 strlocerror + ${StrLoc} $0 abc d < + StrCmp $0 "" 0 strlocerror + !insertmacro StackVerificationEnd + IfErrors strlocerror + + DetailPrint "PASSED StrLoc test" + Goto +2 +strlocerror: + DetailPrint "FAILED StrLoc test" + + # Test string replacement + !insertmacro StackVerificationStart + ${StrRep} $0 "This is just an example" "an" "one" + StrCmp $0 "This is just one example" 0 strreperror + ${StrRep} $0 "test... test... 1 2 3..." "test" "testing" + StrCmp $0 "testing... testing... 1 2 3..." 0 strreperror + ${StrRep} $0 "" "test" "testing" + StrCmp $0 "" 0 strreperror + ${StrRep} $0 "test" "test" "testing" + StrCmp $0 "testing" 0 strreperror + ${StrRep} $0 "test" "test" "" + StrCmp $0 "" 0 strreperror + ${StrRep} $0 "test" "" "abc" + StrCmp $0 "test" 0 strreperror + ${StrRep} $0 "test" "" "" + StrCmp $0 "test" 0 strreperror + !insertmacro StackVerificationEnd + IfErrors strreperror + + DetailPrint "PASSED StrRep test" + Goto +2 +strreperror: + DetailPrint "FAILED StrRep test" + + # Test sorting + !insertmacro StackVerificationStart + ${StrSort} $0 "This is just an example" "" " just" "ple" "0" "0" "0" + StrCmp $0 "This is an exam" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "j" " " "0" "" "0" + StrCmp $0 "just" 0 strsorterror + ${StrSort} $0 "This is just an example" "" "j" "" "0" "1" "0" + StrCmp $0 "This is just an example" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "us" "" "0" "1" "0" + StrCmp $0 "just an example" 0 strsorterror + ${StrSort} $0 "This is just an example" "" "u" " " "0" "1" "0" + StrCmp $0 "This is just" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "just" " " "0" "1" "0" + StrCmp $0 "just" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "t" " " "0" "1" "0" + StrCmp $0 "This" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "le" " " "0" "1" "0" + StrCmp $0 "example" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "le" " " "1" "0" "0" + StrCmp $0 " examp" 0 strsorterror + ${StrSort} $0 "an error has occurred" " " "e" " " "0" "1" "0" + StrCmp $0 "error" 0 strsorterror + ${StrSort} $0 "" " " "something" " " "0" "1" "0" + StrCmp $0 "" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "j" " " "" "" "" + StrCmp $0 " just " 0 strsorterror + ${StrSort} $0 "This is just an example" " " "j" " " "1" "0" "1" + StrCmp $0 " ust " 0 strsorterror + ${StrSort} $0 "This is just an example" "" "j" "" "0" "0" "1" + StrCmp $0 "This is ust an example" 0 strsorterror + ${StrSort} $0 "This is just an example" " " "us" "" "1" "0" "0" + StrCmp $0 " jt an example" 0 strsorterror + ${StrSort} $0 "This is just an example" "" "u" " " "0" "0" "1" + StrCmp $0 "This is jst " 0 strsorterror + ${StrSort} $0 "This is just an example" " " "just" " " "1" "0" "1" + StrCmp $0 " " 0 strsorterror + ${StrSort} $0 "an error has occurred" " " "e" "h" "1" "0" "0" + StrCmp $0 " rror " 0 strsorterror + ${StrSort} $0 "" " " "something" " " "1" "0" "1" + StrCmp $0 "" 0 strsorterror + !insertmacro StackVerificationEnd + IfErrors strsorterror + + DetailPrint "PASSED StrSort test" + Goto +2 +strsorterror: + DetailPrint "FAILED StrSort test" + + !insertmacro StackVerificationStart + ${StrStr} $0 "abcefghijklmnopqrstuvwxyz" "g" + StrCmp $0 "ghijklmnopqrstuvwxyz" 0 strstrerror + ${StrStr} $0 "abcefghijklmnopqrstuvwxyz" "ga" + StrCmp $0 "" 0 strstrerror + ${StrStr} $0 "abcefghijklmnopqrstuvwxyz" "" + StrCmp $0 "abcefghijklmnopqrstuvwxyz" 0 strstrerror + ${StrStr} $0 "a" "abcefghijklmnopqrstuvwxyz" + StrCmp $0 "" 0 strstrerror + !insertmacro StackVerificationEnd + IfErrors strstrerror + + DetailPrint "PASSED StrStr test" + Goto +2 +strstrerror: + DetailPrint "FAILED StrStr test" + + !insertmacro StackVerificationStart + ${StrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "0" "0" + StrCmp $0 "abcabcabc" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "1" "0" + StrCmp $0 "abcabc" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "2" "0" + StrCmp $0 "abc" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "3" "0" + StrCmp $0 "" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "abc" ">" "<" "1" "1" "0" + StrCmp $0 "abcabc" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "abc" ">" "<" "0" "1" "0" + StrCmp $0 "abc" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "abc" "<" "<" "1" "0" "0" + StrCmp $0 "abcabcabc" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "abc" "<" "<" "0" "0" "0" + StrCmp $0 "abcabc" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "abc" "<" ">" "0" "0" "0" + StrCmp $0 "" 0 strstradverror + ${StrStrAdv} $0 "abcabcabc" "abc" "<" ">" "0" "1" "0" + StrCmp $0 "abc" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "0" "1" + StrCmp $0 "abcabc" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "1" "1" + StrCmp $0 "abc" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "2" "1" + StrCmp $0 "" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "3" "1" + StrCmp $0 "" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "abc" ">" "<" "1" "1" "1" + StrCmp $0 "ABCabcabc" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "abc" ">" "<" "0" "1" "1" + StrCmp $0 "ABCabc" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "abc" "<" "<" "1" "0" "1" + StrCmp $0 "ABCabcabc" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "abc" "<" "<" "0" "0" "1" + StrCmp $0 "ABCabc" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "abc" "<" ">" "0" "0" "1" + StrCmp $0 "" 0 strstradverror + ${StrStrAdv} $0 "ABCabcabc" "abc" "<" ">" "0" "1" "1" + StrCmp $0 "abc" 0 strstradverror + !insertmacro StackVerificationEnd + IfErrors strstradverror + + DetailPrint "PASSED StrStrAdv test" + Goto +2 +strstradverror: + DetailPrint "FAILED StrStrAdv test" + + # Test tokenizer + !insertmacro StackVerificationStart + ${StrTok} $0 "This is, or is not, just an example" " ," "4" "1" + StrCmp $0 "not" 0 strtokerror + ${StrTok} $0 "This is, or is not, just an example" " ," "4" "0" + StrCmp $0 "is" 0 strtokerror + ${StrTok} $0 "This is, or is not, just an example" " ," "152" "0" + StrCmp $0 "" 0 strtokerror + ${StrTok} $0 "This is, or is not, just an example" " ," "" "0" + StrCmp $0 "example" 0 strtokerror + ${StrTok} $0 "This is, or is not, just an example" " ," "L" "0" + StrCmp $0 "example" 0 strtokerror + ${StrTok} $0 "This is, or is not, just an example" " ," "0" "0" + StrCmp $0 "This" 0 strtokerror + !insertmacro StackVerificationEnd + IfErrors strtokerror + + DetailPrint "PASSED StrTok test" + Goto +2 +strtokerror: + DetailPrint "FAILED StrTok test" + + # Test trim new lines + !insertmacro StackVerificationStart + ${StrTrimNewLines} $0 "$\r$\ntest$\r$\ntest$\r$\n" + StrCmp $0 "$\r$\ntest$\r$\ntest" 0 strtrimnewlineserror + !insertmacro StackVerificationEnd + IfErrors strtrimnewlineserror + + DetailPrint "PASSED StrTrimNewLines test" + Goto +2 +strtrimnewlineserror: + DetailPrint "FAILED StrTrimNewLines test" + + WriteUninstaller $EXEDIR\UnStrFunc.exe + + Exec $EXEDIR\UnStrFunc.exe + +SectionEnd + +Section Uninstall + + # Test case conversion + !insertmacro StackVerificationStart + ${UnStrCase} $0 "This is just an example. A very simple one." "" + StrCmp $0 "This is just an example. A very simple one." 0 strcaseerror + ${UnStrCase} $0 "THIS IS JUST AN EXAMPLE. A VERY SIMPLE ONE." "S" + StrCmp $0 "This is just an example. A very simple one." 0 strcaseerror + ${UnStrCase} $0 "This is just an example. A very simple one." "L" + StrCmp $0 "this is just an example. a very simple one." 0 strcaseerror + ${UnStrCase} $0 "This is just an example. A very simple one." "U" + StrCmp $0 "THIS IS JUST AN EXAMPLE. A VERY SIMPLE ONE." 0 strcaseerror + ${UnStrCase} $0 "This is just an example. A very simple one." "T" + StrCmp $0 "This Is Just An Example. A Very Simple One." 0 strcaseerror + ${UnStrCase} $0 "This is just an example. A very simple one." "<>" + StrCmp $0 "tHIS IS JUST AN EXAMPLE. a VERY SIMPLE ONE." 0 strcaseerror + ${UnStrCase} $0 "123456789!@#%^&*()-_=+[]{};:,./<>?" "S" + StrCmp $0 "123456789!@#%^&*()-_=+[]{};:,./<>?" 0 strcaseerror + ${UnStrCase} $0 "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#%^&*()abcdefghijklmnopqrstuvwxyz-_=+[]{};:,./<>?" "<>" + StrCmp $0 "123456789abcdefghijklmnopqrstuvwxyz!@#%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ-_=+[]{};:,./<>?" 0 strcaseerror + ${UnStrCase} $0 "what about taking a shower tomorrow? it's late to do so now! try to sleep now. Good Night!" "S" + StrCmp $0 "What about taking a shower tomorrow? It's late to do so now! Try to sleep now. Good night!" 0 strcaseerror + !insertmacro StackVerificationEnd + IfErrors strcaseerror + + DetailPrint "PASSED StrCase test" + Goto +2 +strcaseerror: + DetailPrint "FAILED StrCase test" + + # Test clipboard function + !insertmacro StackVerificationStart + ${UnStrClb} $0 "StrFunc clipboard test" ">" + StrCmp $0 "" 0 strclberror + ${UnStrClb} $0 "StrFunc clipboard test #2" "<>" + StrCmp $0 "StrFunc clipboard test" 0 strclberror + ${UnStrClb} $0 "" "<" + StrCmp $0 "StrFunc clipboard test #2" 0 strclberror + ${UnStrClb} $0 "" "" + StrCmp $0 "" 0 strclberror + !insertmacro StackVerificationEnd + IfErrors strclberror + + DetailPrint "PASSED StrClb test" + Goto +2 +strclberror: + DetailPrint "FAILED StrClb test" + + # Test IO functions + !insertmacro StackVerificationStart + !macro untestio str + ${UnStrNSISToIO} $0 "${str}" + ${UnStrIOToNSIS} $0 $0 + StrCmp $0 "${str}" 0 ioerror + !macroend + !insertmacro untestio "$\rtest$\n" + !insertmacro untestio "test$\n" + !insertmacro untestio "$\rtest" + !insertmacro untestio "test" + !insertmacro untestio "$\r\$\t$\n" + !insertmacro untestio "$\r \ $\t $\n $$" + !insertmacro untestio "" + !insertmacro untestio " " + !insertmacro StackVerificationEnd + IfErrors ioerror + + DetailPrint "PASSED StrNSISToIO/StrIOToNSIS test" + Goto +2 +ioerror: + DetailPrint "FAILED StrNSISToIO/StrIOToNSIS test" + + # Test string search functions + !insertmacro StackVerificationStart + ${UnStrLoc} $0 "This is just an example" "just" "<" + StrCmp $0 "11" 0 strlocerror + ${UnStrLoc} $0 a abc < + StrCmp $0 "" 0 strlocerror + ${UnStrLoc} $0 a abc > + StrCmp $0 "" 0 strlocerror + ${UnStrLoc} $0 abc a > + StrCmp $0 "0" 0 strlocerror + ${UnStrLoc} $0 abc b > + StrCmp $0 "1" 0 strlocerror + ${UnStrLoc} $0 abc c > + StrCmp $0 "2" 0 strlocerror + ${UnStrLoc} $0 abc a < + StrCmp $0 "2" 0 strlocerror + ${UnStrLoc} $0 abc b < + StrCmp $0 "1" 0 strlocerror + ${UnStrLoc} $0 abc c < + StrCmp $0 "0" 0 strlocerror + ${UnStrLoc} $0 abc d < + StrCmp $0 "" 0 strlocerror + !insertmacro StackVerificationEnd + IfErrors strlocerror + + DetailPrint "PASSED StrLoc test" + Goto +2 +strlocerror: + DetailPrint "FAILED StrLoc test" + + # Test string replacement + !insertmacro StackVerificationStart + ${UnStrRep} $0 "This is just an example" "an" "one" + StrCmp $0 "This is just one example" 0 strreperror + ${UnStrRep} $0 "test... test... 1 2 3..." "test" "testing" + StrCmp $0 "testing... testing... 1 2 3..." 0 strreperror + ${UnStrRep} $0 "" "test" "testing" + StrCmp $0 "" 0 strreperror + ${UnStrRep} $0 "test" "test" "testing" + StrCmp $0 "testing" 0 strreperror + ${UnStrRep} $0 "test" "test" "" + StrCmp $0 "" 0 strreperror + ${UnStrRep} $0 "test" "" "abc" + StrCmp $0 "test" 0 strreperror + ${UnStrRep} $0 "test" "" "" + StrCmp $0 "test" 0 strreperror + !insertmacro StackVerificationEnd + IfErrors strreperror + + DetailPrint "PASSED StrRep test" + Goto +2 +strreperror: + DetailPrint "FAILED StrRep test" + + # Test sorting + !insertmacro StackVerificationStart + ${UnStrSort} $0 "This is just an example" "" " just" "ple" "0" "0" "0" + StrCmp $0 "This is an exam" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "j" " " "0" "" "0" + StrCmp $0 "just" 0 strsorterror + ${UnStrSort} $0 "This is just an example" "" "j" "" "0" "1" "0" + StrCmp $0 "This is just an example" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "us" "" "0" "1" "0" + StrCmp $0 "just an example" 0 strsorterror + ${UnStrSort} $0 "This is just an example" "" "u" " " "0" "1" "0" + StrCmp $0 "This is just" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "just" " " "0" "1" "0" + StrCmp $0 "just" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "t" " " "0" "1" "0" + StrCmp $0 "This" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "le" " " "0" "1" "0" + StrCmp $0 "example" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "le" " " "1" "0" "0" + StrCmp $0 " examp" 0 strsorterror + ${UnStrSort} $0 "an error has occurred" " " "e" " " "0" "1" "0" + StrCmp $0 "error" 0 strsorterror + ${UnStrSort} $0 "" " " "something" " " "0" "1" "0" + StrCmp $0 "" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "j" " " "" "" "" + StrCmp $0 " just " 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "j" " " "1" "0" "1" + StrCmp $0 " ust " 0 strsorterror + ${UnStrSort} $0 "This is just an example" "" "j" "" "0" "0" "1" + StrCmp $0 "This is ust an example" 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "us" "" "1" "0" "0" + StrCmp $0 " jt an example" 0 strsorterror + ${UnStrSort} $0 "This is just an example" "" "u" " " "0" "0" "1" + StrCmp $0 "This is jst " 0 strsorterror + ${UnStrSort} $0 "This is just an example" " " "just" " " "1" "0" "1" + StrCmp $0 " " 0 strsorterror + ${UnStrSort} $0 "an error has occurred" " " "e" "h" "1" "0" "0" + StrCmp $0 " rror " 0 strsorterror + ${UnStrSort} $0 "" " " "something" " " "1" "0" "1" + StrCmp $0 "" 0 strsorterror + !insertmacro StackVerificationEnd + IfErrors strsorterror + + DetailPrint "PASSED StrSort test" + Goto +2 +strsorterror: + DetailPrint "FAILED StrSort test" + + !insertmacro StackVerificationStart + ${UnStrStr} $0 "abcefghijklmnopqrstuvwxyz" "g" + StrCmp $0 "ghijklmnopqrstuvwxyz" 0 strstrerror + ${UnStrStr} $0 "abcefghijklmnopqrstuvwxyz" "ga" + StrCmp $0 "" 0 strstrerror + ${UnStrStr} $0 "abcefghijklmnopqrstuvwxyz" "" + StrCmp $0 "abcefghijklmnopqrstuvwxyz" 0 strstrerror + ${UnStrStr} $0 "a" "abcefghijklmnopqrstuvwxyz" + StrCmp $0 "" 0 strstrerror + !insertmacro StackVerificationEnd + IfErrors strstrerror + + DetailPrint "PASSED StrStr test" + Goto +2 +strstrerror: + DetailPrint "FAILED StrStr test" + + !insertmacro StackVerificationStart + ${UnStrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "0" "0" + StrCmp $0 "abcabcabc" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "1" "0" + StrCmp $0 "abcabc" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "2" "0" + StrCmp $0 "abc" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "a" ">" ">" "1" "3" "0" + StrCmp $0 "" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "abc" ">" "<" "1" "1" "0" + StrCmp $0 "abcabc" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "abc" ">" "<" "0" "1" "0" + StrCmp $0 "abc" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "abc" "<" "<" "1" "0" "0" + StrCmp $0 "abcabcabc" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "abc" "<" "<" "0" "0" "0" + StrCmp $0 "abcabc" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "abc" "<" ">" "0" "0" "0" + StrCmp $0 "" 0 strstradverror + ${UnStrStrAdv} $0 "abcabcabc" "abc" "<" ">" "0" "1" "0" + StrCmp $0 "abc" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "0" "1" + StrCmp $0 "abcabc" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "1" "1" + StrCmp $0 "abc" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "2" "1" + StrCmp $0 "" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "a" ">" ">" "1" "3" "1" + StrCmp $0 "" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "abc" ">" "<" "1" "1" "1" + StrCmp $0 "ABCabcabc" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "abc" ">" "<" "0" "1" "1" + StrCmp $0 "ABCabc" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "abc" "<" "<" "1" "0" "1" + StrCmp $0 "ABCabcabc" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "abc" "<" "<" "0" "0" "1" + StrCmp $0 "ABCabc" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "abc" "<" ">" "0" "0" "1" + StrCmp $0 "" 0 strstradverror + ${UnStrStrAdv} $0 "ABCabcabc" "abc" "<" ">" "0" "1" "1" + StrCmp $0 "abc" 0 strstradverror + !insertmacro StackVerificationEnd + IfErrors strstradverror + + DetailPrint "PASSED StrStrAdv test" + Goto +2 +strstradverror: + DetailPrint "FAILED StrStrAdv test" + + # Test tokenizer + !insertmacro StackVerificationStart + ${UnStrTok} $0 "This is, or is not, just an example" " ," "4" "1" + StrCmp $0 "not" 0 strtokerror + ${UnStrTok} $0 "This is, or is not, just an example" " ," "4" "0" + StrCmp $0 "is" 0 strtokerror + ${UnStrTok} $0 "This is, or is not, just an example" " ," "152" "0" + StrCmp $0 "" 0 strtokerror + ${UnStrTok} $0 "This is, or is not, just an example" " ," "" "0" + StrCmp $0 "example" 0 strtokerror + ${UnStrTok} $0 "This is, or is not, just an example" " ," "L" "0" + StrCmp $0 "example" 0 strtokerror + ${UnStrTok} $0 "This is, or is not, just an example" " ," "0" "0" + StrCmp $0 "This" 0 strtokerror + !insertmacro StackVerificationEnd + IfErrors strtokerror + + DetailPrint "PASSED StrTok test" + Goto +2 +strtokerror: + DetailPrint "FAILED StrTok test" + + # Test trim new lines + !insertmacro StackVerificationStart + ${UnStrTrimNewLines} $0 "$\r$\ntest$\r$\ntest$\r$\n" + StrCmp $0 "$\r$\ntest$\r$\ntest" 0 strtrimnewlineserror + !insertmacro StackVerificationEnd + IfErrors strtrimnewlineserror + + DetailPrint "PASSED StrTrimNewLines test" + Goto +2 +strtrimnewlineserror: + DetailPrint "FAILED StrTrimNewLines test" + +SectionEnd diff --git a/installer/NSIS/Examples/System/Resource.dll b/installer/NSIS/Examples/System/Resource.dll new file mode 100644 index 0000000..6551f8c Binary files /dev/null and b/installer/NSIS/Examples/System/Resource.dll differ diff --git a/installer/NSIS/Examples/System/SysFunc.nsh b/installer/NSIS/Examples/System/SysFunc.nsh new file mode 100644 index 0000000..57c2016 --- /dev/null +++ b/installer/NSIS/Examples/System/SysFunc.nsh @@ -0,0 +1,398 @@ +; Some useful functions based on System plugin +; +; (c) brainsucker, 2002 +; (r) BSForce + +; Check for double includes +!ifndef SysFunc.NSH.Included +!define SysFunc.NSH.Included + +!include "${NSISDIR}\Examples\System\System.nsh" + +!verbose 3 ; For WinMessages especially + !include "WinMessages.nsh" +!verbose 4 + +; ================= GetInstallerExeName implementation ================= + +; Adopted Get Parameter function -> now it gets full installer.exe path +; input - nothing, output -> full path at the top of the stack +Function GetInstallerExeName + Push $R0 + Push $R1 + Push $R2 + StrCpy $R0 $CMDLINE 1 + StrCpy $R1 '"' + StrCpy $R2 1 + StrCmp $R0 '"' loop + StrCpy $R1 ' ' ; we're scanning for a space instead of a quote + loop: + StrCpy $R0 $CMDLINE 1 $R2 + StrCmp $R0 $R1 loop2 + StrCmp $R0 "" loop2 + IntOp $R2 $R2 + 1 + Goto loop + loop2: + + ; Ok, have we found last exename character or string end? + StrCmp $R0 "" "" +2 + IntOp $R2 $R2 - 1 ; last exename char + StrCmp $R1 ' ' +3 ; was first character the '"', or something other? + StrCpy $R1 1 ; it was quote + Goto +2 + StrCpy $R1 0 + IntOp $R2 $R2 - $R1 + StrCpy $R0 $CMDLINE $R2 $R1 + + SearchPath $R0 $R0 ; expand file name to full path + + Pop $R2 + Pop $R1 + Exch $R0 +FunctionEnd + +; ================= systemGetFileSysTime implementation ================= + +!macro smGetFileSysTime FILENAME + Push ${FILENAME} + Call systemGetFileSysTime + Pop $R0 +!macroend + +; ----------------------------------------------------------------- +; systemGetFileSysTime (params on stack): +; FILENAME - name of file to get file time +; returns to stack (SYSTEMTIME struct addr) +; ----------------------------------------------------------------- + +; uses original method from NSIS +Function systemGetFileSysTime + System::Store "s r1" + + StrCpy $R0 0 + + ; create WIN32_FIND_DATA struct + System::Call '*${stWIN32_FIND_DATA} .r2' + + ; Find file info + System::Call '${sysFindFirstFile}(r1, r2) .r3' + + ; ok? + IntCmp $3 ${INVALID_HANDLE_VALUE} sgfst_exit + + ; close file search + System::Call '${sysFindClose}(r3)' + + ; Create systemtime struct for local time + System::Call '*${stSYSTEMTIME} .R0' + + ; Get File time + System::Call '*$2${stWIN32_FIND_DATA} (,,, .r3)' + + ; Convert file time (UTC) to local file time + System::Call '${sysFileTimeToLocalFileTime}(r3, .r1)' + + ; Convert file time to system time + System::Call '${sysFileTimeToSystemTime}(r1, R0)' + +sgfst_exit: + ; free used memory for WIN32_FIND_DATA struct + System::Free $2 + + System::Store "P0 l" +FunctionEnd + +; ================= systemMessageBox implementation ================= + +; return to $R0 +!macro smMessageBox MODULE MSG CAPTION STYLE ICON + Push "${ICON}" + Push "${STYLE}" + Push "${CAPTION}" + Push "${MSG}" + Push "${MODULE}" + Call systemMessageBox + Pop $R0 +!macroend + +; ----------------------------------------------------------------- +; systemMessageBox (params on stack): +; Module: either handle ("i HANDLE", HANDLE could be 0) or "modulename" +; Msg: text of message +; Caption: caption of message box window +; Style: style, buttons etc +; Icon: either icon handle ("i HANDLE") or resource name +; returns to stack +; ----------------------------------------------------------------- +Function systemMessageBox + System::Store "s r2r3r4r5r6" + + ; may be Module is module handle? + StrCpy $1 $2 + IntCmp $1 0 0 smbnext smbnext + + ; Get module handle + System::Call '${sysGetModuleHandle}($2) .r1' + IntCmp $1 0 loadlib libnotloaded libnotloaded + +loadlib: + ; Load module and get handle + System::Call '${sysLoadLibrary}($2) .r1' + IntCmp $1 0 0 smbnext smbnext + +libnotloaded: + ; Indicate that LoadLibrary wasn't used + StrCpy $2 1 + +smbnext: + ; Create MSGBOXPARAMS structure + System::Call '*${stMSGBOXPARAMS}(, $HWNDPARENT, r1, r3, r4, "$5|${MB_USERICON}", $6, _) .r0' + ; call MessageBoxIndirect + System::Call '${sysMessageBoxIndirect}(r0) .R0' + ; free MSGBOXPARAMS structure + + System::Free $0 + + ; have we used load library at start? + IntCmp $2 0 0 smbskipfree smbskipfree + ; No, then free the module + System::Call '${sysFreeLibrary}(r1)' + +smbskipfree: + System::Store "P0 l" +FunctionEnd + +; ================= systemSplash implementation ================= + +; returns to $R0 +!macro smSystemSplash DELAY FILE + Push ${FILE} + Push ${DELAY} + call systemSplash + Pop $R0 +!macroend + +; ----------------------------------------------------------------- +; systemSplash (params on stack): +; Delay - time in ms to show the splash +; File - bitmap (& audio) file name (without extension) +; returns to stack +; ----------------------------------------------------------------- + +Function _systemSplashWndCB + ; Callback receives 4 values + System::Store "s r2r5r7r9" + + ; Message branching + IntCmp $5 ${WM_CLOSE} m_Close + IntCmp $5 ${WM_TIMER} m_Timer + IntCmp $5 ${WM_LBUTTONDOWN} m_Lbtn + IntCmp $5 ${WM_CREATE} m_Create + IntCmp $5 ${WM_PAINT} m_Paint + goto default + +m_Create: + ; Create structures + System::Call "*${stRECT} (_) .R8" + System::Call "*${stBITMAP} (_, &l0 .R7) .R9" + + ; Get bitmap info + System::Call "${sysGetObject} (r6, R7, R9)" + + ; Get desktop info + System::Call "${sysSystemParametersInfo} (${SPI_GETWORKAREA}, 0, R8, 0)" + + ; Style (callbacked) + System::Call "${sysSetWindowLong} (r2, ${GWL_STYLE}, 0) .s" + !insertmacro SINGLE_CALLBACK 5 $R7 1 _systemSplashWndCB + + ; Calculate and set window pos + + ; Get bmWidth(R2) and bmHeight(R3) + System::Call "*$R9${stBITMAP} (,.R2,.R3)" + ; Get left(R4), top(R5), right(R6), bottom(R7) + System::Call "*$R8${stRECT} (.R4,.R5,.R6,.R7)" + + ; Left pos + IntOp $R0 $R6 - $R4 + IntOp $R0 $R0 - $R2 + IntOp $R0 $R0 / 2 + IntOp $R0 $R0 + $R4 + + ; Top pos + IntOp $R1 $R7 - $R5 + IntOp $R1 $R1 - $R3 + IntOp $R1 $R1 / 2 + IntOp $R1 $R1 + $R5 + + System::Call "${sysSetWindowPos} (r2, 0, R0, R1, R2, R3, ${SWP_NOZORDER}) .s" + !insertmacro SINGLE_CALLBACK 6 $R7 1 _systemSplashWndCB + + ; Show window + System::Call "${sysShowWindow} (r2, ${SW_SHOW}) .s" + !insertmacro SINGLE_CALLBACK 7 $R7 1 _systemSplashWndCB + + ; Set Timer + System::Call "${sysSetTimer} (r2, 1, r8,)" + + ; Free used memory + System::Free $R8 + System::Free $R9 + + StrCpy $R0 0 + goto exit + +m_Paint: + ; Create structures + System::Call "*${stRECT} (_) .R8" + System::Call "*${stPAINTSTRUCT} (_) .R9" + + ; Begin Paint + System::Call "${sysBeginPaint} (r2, R9) .R7" + + ; CreateCompatibleDC + System::Call "${sysCreateCompatibleDC} (R7) .R6" + + ; GetClientRect + System::Call "${sysGetClientRect} (r2, R8)" + + ; Select new bitmap + System::Call "${sysSelectObject} (R6, r6) .R5" + + ; Get left(R0), top(R1), right(R2), bottom(R3) + System::Call "*$R8${stRECT} (.R0,.R1,.R2,.R3)" + + ; width=right-left + IntOp $R2 $R2 - $R0 + ; height=bottom-top + IntOp $R3 $R3 - $R1 + + System::Call "${sysBitBlt} (R7, R0, R1, R2, R3, R6, 0, 0, ${SRCCOPY})" + + ; Select old bitmap + System::Call "${sysSelectObject} (R6, R5)" + + ; Delete compatible DC + System::Call "${sysDeleteDC} (R6)" + + ; End Paint + System::Call "${sysEndPaint} (r2, R9)" + + ; Free used memory + System::Free $R8 + System::Free $R9 + + StrCpy $R0 0 + goto exit + +m_Timer: +m_Lbtn: + StrCpy $4 0 + IntCmp $5 ${WM_TIMER} destroy + StrCpy $4 1 + +destroy: + System::Call "${sysDestroyWindow} (r2) .s" + !insertmacro SINGLE_CALLBACK 12 $R4 1 _systemSplashWndCB + +default: + ; Default + System::Call "${sysDefWindowProc} (r2, r5, r7, r9) .s" + !insertmacro SINGLE_CALLBACK 14 $R0 1 _systemSplashWndCB + goto exit + +m_Close: + StrCpy $R0 0 + goto exit + +exit: + ; Restore + System::Store "p4P0 l R0r4" + + ; Return from callback + System::Call "$3" $R0 +FunctionEnd + +Function systemSplash + + ; Save registers and get input + System::Store "s r8r9" + + ; Get module instance + System::Call "${sysGetModuleHandle} (i) .r7" + + ; Get arrow cursor + System::Call "${sysLoadCursor} (0, i ${IDC_ARROW}) .R9" + + ; Get callback + System::Get "${sysWNDPROC}" + Pop $3 + + ; Create window class + System::Call "*${stWNDCLASS} (0,r3,0,0,r7,0,R9,0,i 0,'_sp') .R9" + + ; Register window class + System::Call "${sysRegisterClass} (R9) .R9" + IntCmp $R9 0 errorexit ; Class registered ok? + + ; Load Image (LR_CREATEDIBSECTION|LR_LOADFROMFILE = 0x2010) + System::Call '${sysLoadImage} (, s, ${IMAGE_BITMAP}, 0, 0, ${LR_CREATEDIBSECTION}|${LR_LOADFROMFILE}) .r6' "$9.bmp" + IntCmp $6 0 errorexit ; Image loaded ok? + + ; Start the sound (SND_ASYNC|SND_FILENAME|SND_NODEFAULT = 0x20003) + System::Call "${sysPlaySound} (s,,${SND_ASYNC}|${SND_FILENAME}|${SND_NODEFAULT})" "$9.wav" + + ; Create window + System::Call "${sysCreateWindowEx} (${WS_EX_TOOLWINDOW}, s, s,,,,,, $HWNDPARENT,,r7,) .s" "_sp" "_sp" + !insertmacro SINGLE_CALLBACK 1 $5 1 _systemSplashWndCB + + ; Create MSG struct + System::Call "*${stMSG} (_) i.R9" + + ; ------------------------- +repeat: + ; Check for window + System::Call "${sysIsWindow} (r5) .s" + !insertmacro SINGLE_CALLBACK 2 $R8 1 _systemSplashWndCB + IntCmp $R8 0 finish + + ; Get message + System::Call "${sysGetMessage} (R9, r5,_) .s" + !insertmacro SINGLE_CALLBACK 3 $R8 1 _systemSplashWndCB + IntCmp $R8 0 finish + + ; Dispatch message + System::Call "${sysDispatchMessage} (R9) .s" + !insertmacro SINGLE_CALLBACK 4 $R8 1 _systemSplashWndCB + + ; Repeat dispatch cycle + goto repeat + ; ------------------------- + +finish: + ; Stop the sound + System::Call "${sysPlaySound} (i 0, i 0, i 0)" + + ; Delete bitmap object + System::Call "${sysDeleteObject} (r6)" + + ; Delete the callback queue + System::Free $3 + + ; Dialog return + StrCpy $R0 $4 + goto exit + +; Exit in case of error +errorexit: + StrCpy $R0 -1 + goto exit + +exit: + ; Restore register and put output + System::Store "P0 l" +FunctionEnd + +!verbose 4 + +!endif \ No newline at end of file diff --git a/installer/NSIS/Examples/System/System.nsh b/installer/NSIS/Examples/System/System.nsh new file mode 100644 index 0000000..b550d19 --- /dev/null +++ b/installer/NSIS/Examples/System/System.nsh @@ -0,0 +1,472 @@ +; Some useful functions, structures, constants +; +; (c) brainsucker, 2002 +; (r) BSForce + +; Check for double includes +!ifndef System.NSH.Included +!define System.NSH.Included + +!verbose 3 + +; ------------- Functions -------------- + +; LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +!define sysWNDPROC "(i.s, i.s, i.s, i.s) iss" + +; LRESULT DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +!define sysDefWindowProc "user32::DefWindowProcA(i, i, i, i) i" + +!define sysMessageBox "user32::MessageBoxA(i, t, t, i) i" + +!define sysMessageBeep "user32::MessageBeep(i) i" + +!define sysMessageBoxIndirect 'user32::MessageBoxIndirectA(i) i' + +; HMODULE GetModuleHandle(LPCTSTR lpModuleName); +!define sysGetModuleHandle "kernel32::GetModuleHandleA(t) i" + +; HMODULE LoadLibrary(LPCTSTR lpFileName); +!define sysLoadLibrary "kernel32::LoadLibraryA(t) i" + +; BOOL FreeLibrary(HMODULE hModule); +!define sysFreeLibrary "kernel32::FreeLibrary(i) i" + +; HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName); +!define sysLoadCursor "user32::LoadCursorA(i, t) i" + +; ATOM RegisterClass(CONST WNDCLASS *lpWndClass); +!define sysRegisterClass "user32::RegisterClassA(i) i" + +; HANDLE LoadImage(HINSTANCE hinst, LPCTSTR lpszName, UINT uType, +; int cxDesired, int cyDesired, UINT fuLoad); +!define sysLoadImage "user32::LoadImageA(i, t, i, i, i, i) i" + +; BOOL PlaySound(LPCSTR pszSound, HMODULE hmod, DWORD fdwSound); +!define sysPlaySound "winmm.dll::PlaySoundA(t, i, i) i" + +; HWND CreateWindowEx(DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, +; DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, +; HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); +!define sysCreateWindowEx "user32::CreateWindowExA(i, t, t, i, i, i, i, i, i, i, i, i) i" + +; BOOL IsWindow(HWND hWnd); +!define sysIsWindow "user32::IsWindow(i) i" + +; LONG SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong); +!define sysSetWindowLong "user32::SetWindowLongA(i, i, i) i" + +; BOOL SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); +!define sysSetWindowPos "user32::SetWindowPos(i, i, i, i, i, i, i) i" + +; BOOL ShowWindow(HWND hWnd, int nCmdShow); +!define sysShowWindow "user32::ShowWindow(i, i) i" + +; BOOL DestroyWindow(HWND hWnd); +!define sysDestroyWindow "user32::DestroyWindow(i) i" + +; BOOL GetClientRect(HWND hWnd, LPRECT lpRect); +!define sysGetClientRect "user32::GetClientRect(i, i) i" + +; BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax); +!define sysGetMessage "user32::GetMessageA(i, i, i, i) i" + +; LRESULT DispatchMessage(CONST MSG *lpmsg); +!define sysDispatchMessage "user32::DispatchMessageA(i) i" + +; BOOL DeleteObject(HGDIOBJ hObject); +!define sysDeleteObject "gdi32::DeleteObject(i) i" + +; int GetObject(HGDIOBJ hgdiobj, int cbBuffer, LPVOID lpvObject); +!define sysGetObject "gdi32::GetObjectA(i, i, i) i" + +; HGDIOBJ SelectObject(HDC hdc, HGDIOBJ hgdiobj); +!define sysSelectObject "gdi32::SelectObject(i, i) i" + +; HDC CreateCompatibleDC(HDC hdc); +!define sysCreateCompatibleDC "gdi32::CreateCompatibleDC(i) i" + +; BOOL DeleteDC(HDC hdc); +!define sysDeleteDC "gdi32::DeleteDC(i) i" + +; BOOL BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, +; HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop); +!define sysBitBlt "gdi32::BitBlt(i, i, i, i, i, i, i, i, i) i" + +; proposed by abgandar +; int AddFontResource(LPCTSTR lpszFilename); +!define sysAddFontResource "gdi32::AddFontResourceA(t) i" + +; HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint); +!define sysBeginPaint "user32::BeginPaint(i, i) i" + +; BOOL EndPaint(HWND hWnd, CONST PAINTSTRUCT *lpPaint); +!define sysEndPaint "user32::EndPaint(i, i) i" + +; BOOL SystemParametersInfo(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni); +!define sysSystemParametersInfo "user32::SystemParametersInfoA(i, i, i, i) i" + +; UINT_PTR SetTimer(HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc); +!define sysSetTimer "user32::SetTimer(i, i, i, k) i" + +!define sysGetLogicalDriveStrings 'kernel32::GetLogicalDriveStringsA(i, i) i' + +!define sysGetDiskFreeSpaceEx 'kernel32::GetDiskFreeSpaceExA(t, *l, *l, *l) i' + +; UINT GetDriveType(LPCTSTR lpRootPathName); +!define sysGetDriveType 'kernel32::GetDriveTypeA(t) i' + +; HANDLE FindFirstFile(LPCTSTR lpFileName,LPWIN32_FIND_DATA lpFindFileData); +!define sysFindFirstFile 'kernel32::FindFirstFileA(t, i) i' + +; BOOL FindClose(HANDLE hFindFile); +!define sysFindClose 'kernel32::FindClose(i) i' + +; BOOL FileTimeToSystemTime(CONST FILETIME *lpFileTime, +; LPSYSTEMTIME lpSystemTime); +!define sysFileTimeToSystemTime 'kernel32::FileTimeToSystemTime(*l, i) i' + +; BOOL FileTimeToLocalFileTime( +; CONST FILETIME *lpFileTime, +; LPFILETIME lpLocalFileTime); +!define sysFileTimeToLocalFileTime 'kernel32::FileTimeToLocalFileTime(*l, *l) i' + +; BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone, +; LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime); +!define sysSystemTimeToTzSpecificLocalTime 'kernel32::SystemTimeToTzSpecificLocalTime(i, i, i) i' + +!define syslstrlen 'kernel32::lstrlenA(t) i' + +; int wsprintf(LPTSTR lpOut, LPCTSTR lpFmt, ...); +!define syswsprintf "user32::wsprintfA(t, t) i ? c" + +; ------------- Structures -------------- + +; typedef struct _WNDCLASS { +; UINT style; +; WNDPROC lpfnWndProc; +; int cbClsExtra; +; int cbWndExtra; +; HINSTANCE hInstance; +; HICON hIcon; +; HCURSOR hCursor; +; HBRUSH hbrBackground; +; LPCTSTR lpszMenuName; +; LPCTSTR lpszClassName; +; } WNDCLASS, *PWNDCLASS; +!define stWNDCLASS "(i, k, i, i, i, i, i, i, t, t) i" + +; typedef struct tagMSG { +; HWND hwnd; +; UINT message; +; WPARAM wParam; +; LPARAM lParam; +; DWORD time; +; POINT pt; -> will be presented as two separate px and py +; } MSG, *PMSG; +!define stMSG "(i, i, i, i, i, i, i) i" + +; typedef struct tagBITMAP { +; LONG bmType; +; LONG bmWidth; +; LONG bmHeight; +; LONG bmWidthBytes; +; WORD bmPlanes; +; WORD bmBitsPixel; +; LPVOID bmBits; +; } BITMAP, *PBITMAP; +!define stBITMAP "(i, i, i, i, i, i, i) i" + +; typedef struct _RECT { +; LONG left; +; LONG top; +; LONG right; +; LONG bottom; +; } RECT, *PRECT; +!define stRECT "(i, i, i, i) i" + +; typedef struct tagPAINTSTRUCT { +; HDC hdc; +; BOOL fErase; +; RECT rcPaint; (rcl, rct, rcr, rcb) +; BOOL fRestore; +; BOOL fIncUpdate; +; BYTE rgbReserved[32]; +; } PAINTSTRUCT, *PPAINTSTRUCT; +!define stPAINTSTRUCT "(i, i, i, i, i, i, i, i, &v32) i" + +; typedef struct { +; UINT cbSize; +; HWND hwndOwner; +; HINSTANCE hInstance; +; LPCTSTR lpszText; +; LPCTSTR lpszCaption; +; DWORD dwStyle; +; LPCTSTR lpszIcon; +; DWORD_PTR dwContextHelpId; +; MSGBOXCALLBACK lpfnMsgBoxCallback; +; DWORD dwLanguageId; +; } MSGBOXPARAMS, *PMSGBOXPARAMS; +!define stMSGBOXPARAMS '(&l4, i, i, t, t, i, t, i, k, i) i' + +; typedef struct _SYSTEMTIME { +; WORD wYear; +; WORD wMonth; +; WORD wDayOfWeek; +; WORD wDay; +; WORD wHour; +; WORD wMinute; +; WORD wSecond; +; WORD wMilliseconds; +; } SYSTEMTIME, *PSYSTEMTIME; +!define stSYSTEMTIME '(&i2, &i2, &i2, &i2, &i2, &i2, &i2, &i2) i' + +; Maximal windows path +!define MAX_PATH 260 + +; typedef struct _WIN32_FIND_DATA { +; DWORD dwFileAttributes; +; FILETIME ftCreationTime; +; FILETIME ftLastAccessTime; +; FILETIME ftLastWriteTime; +; DWORD nFileSizeHigh; +; DWORD nFileSizeLow; +; DWORD dwReserved0; +; DWORD dwReserved1; +; TCHAR cFileName[ MAX_PATH ]; +; TCHAR cAlternateFileName[ 14 ]; +; } WIN32_FIND_DATA, *PWIN32_FIND_DATA; +!define stWIN32_FIND_DATA '(i, l, l, l, i, i, i, i, &t${MAX_PATH}, &t14) i' + +; ------------- Constants -------------- + +; == Other == +!define INVALID_HANDLE_VALUE -1 + +; == Cursors == + +!define IDC_ARROW 32512 +!define IDC_IBEAM 32513 +!define IDC_WAIT 32514 +!define IDC_CROSS 32515 +!define IDC_UPARROW 32516 +!define IDC_SIZE 32640 +!define IDC_ICON 32641 +!define IDC_SIZENWSE 32642 +!define IDC_SIZENESW 32643 +!define IDC_SIZEWE 32644 +!define IDC_SIZENS 32645 +!define IDC_SIZEALL 32646 +!define IDC_NO 32648 +!define IDC_HAND 32649 +!define IDC_APPSTARTING 32650 +!define IDC_HELP 32651 + +; == Images == + +!define IMAGE_BITMAP 0 +!define IMAGE_ICON 1 +!define IMAGE_CURSOR 2 +!define IMAGE_ENHMETAFILE 3 + +!define LR_DEFAULTCOLOR 0x0000 +!define LR_MONOCHROME 0x0001 +!define LR_COLOR 0x0002 +!define LR_COPYRETURNORG 0x0004 +!define LR_COPYDELETEORG 0x0008 +!define LR_LOADFROMFILE 0x0010 +!define LR_LOADTRANSPARENT 0x0020 +!define LR_DEFAULTSIZE 0x0040 +!define LR_VGACOLOR 0x0080 +!define LR_LOADMAP3DCOLORS 0x1000 +!define LR_CREATEDIBSECTION 0x2000 +!define LR_COPYFROMRESOURCE 0x4000 +!define LR_SHARED 0x8000 + +; == Sounds == + +!define SND_SYNC 0x0000 +!define SND_ASYNC 0x0001 +!define SND_NODEFAULT 0x0002 +!define SND_MEMORY 0x0004 +!define SND_LOOP 0x0008 +!define SND_NOSTOP 0x0010 + +!define SND_NOWAIT 0x00002000 +!define SND_ALIAS 0x00010000 +!define SND_ALIAS_ID 0x00110000 +!define SND_FILENAME 0x00020000 +!define SND_RESOURCE 0x00040004 +!define SND_PURGE 0x0040 +!define SND_APPLICATION 0x0080 + +; == Windows == + +!define WS_OVERLAPPED 0x00000000 +!define WS_POPUP 0x80000000 +!define WS_CHILD 0x40000000 +!define WS_MINIMIZE 0x20000000 +!define WS_VISIBLE 0x10000000 +!define WS_DISABLED 0x08000000 +!define WS_CLIPSIBLINGS 0x04000000 +!define WS_CLIPCHILDREN 0x02000000 +!define WS_MAXIMIZE 0x01000000 +!define WS_CAPTION 0x00C00000 +!define WS_BORDER 0x00800000 +!define WS_DLGFRAME 0x00400000 +!define WS_VSCROLL 0x00200000 +!define WS_HSCROLL 0x00100000 +!define WS_SYSMENU 0x00080000 +!define WS_THICKFRAME 0x00040000 +!define WS_GROUP 0x00020000 +!define WS_TABSTOP 0x00010000 +!define WS_MINIMIZEBOX 0x00020000 +!define WS_MAXIMIZEBOX 0x00010000 +!define WS_TILED ${WS_OVERLAPPED} +!define WS_ICONIC ${WS_MINIMIZE} +!define WS_SIZEBOX ${WS_THICKFRAME} +!define WS_OVERLAPPEDWINDOW 0x00CF0000 +!define WS_TILEDWINDOW ${WS_OVERLAPPEDWINDOW} +!define WS_POPUPWINDOW 0x80880000 +!define WS_CHILDWINDOW ${WS_CHILD} +!define WS_EX_DLGMODALFRAME 0x00000001 +!define WS_EX_NOPARENTNOTIFY 0x00000004 +!define WS_EX_TOPMOST 0x00000008 +!define WS_EX_ACCEPTFILES 0x00000010 +!define WS_EX_TRANSPARENT 0x00000020 +!define WS_EX_MDICHILD 0x00000040 +!define WS_EX_TOOLWINDOW 0x00000080 +!define WS_EX_WINDOWEDGE 0x00000100 +!define WS_EX_CLIENTEDGE 0x00000200 +!define WS_EX_CONTEXTHELP 0x00000400 +!define WS_EX_RIGHT 0x00001000 +!define WS_EX_LEFT 0x00000000 +!define WS_EX_RTLREADING 0x00002000 +!define WS_EX_LTRREADING 0x00000000 +!define WS_EX_LEFTSCROLLBAR 0x00004000 +!define WS_EX_RIGHTSCROLLBAR 0x00000000 +!define WS_EX_CONTROLPARENT 0x00010000 +!define WS_EX_STATICEDGE 0x00020000 +!define WS_EX_APPWINDOW 0x00040000 +!define WS_EX_OVERLAPPEDWINDOW 0x00000300 +!define WS_EX_PALETTEWINDOW 0x00000188 +!define WS_EX_LAYERED 0x00080000 +!define WS_EX_NOINHERITLAYOUT 0x00100000 +!define WS_EX_LAYOUTRTL 0x00400000 +!define WS_EX_COMPOSITED 0x02000000 +!define WS_EX_NOACTIVATE 0x08000000 + + +; == System Parameters Info == + +!define SPI_GETWORKAREA 0x0030 + +; == Window Long Offsets == + +!define GWL_WNDPROC -4 +!define GWL_HINSTANCE -6 +!define GWL_HWNDPARENT -8 +!define GWL_STYLE -16 +!define GWL_EXSTYLE -20 +!define GWL_USERDATA -21 +!define GWL_ID -12 + +; == Window swap == + +!define SWP_NOSIZE 0x0001 +!define SWP_NOMOVE 0x0002 +!define SWP_NOZORDER 0x0004 +!define SWP_NOREDRAW 0x0008 +!define SWP_NOACTIVATE 0x0010 +!define SWP_FRAMECHANGED 0x0020 +!define SWP_SHOWWINDOW 0x0040 +!define SWP_HIDEWINDOW 0x0080 +!define SWP_NOCOPYBITS 0x0100 +!define SWP_NOOWNERZORDER 0x0200 +!define SWP_NOSENDCHANGING 0x0400 + +!define SWP_DRAWFRAME ${SWP_FRAMECHANGED} +!define SWP_NOREPOSITION ${SWP_NOOWNERZORDER} +!define SWP_DEFERERASE 0x2000 +!define SWP_ASYNCWINDOWPOS 0x4000 + +; == Bit Copy == + +!define SRCCOPY 0x00CC0020 +!define SRCPAINT 0x00EE0086 +!define SRCAND 0x008800C6 +!define SRCINVERT 0x00660046 +!define SRCERASE 0x00440328 +!define NOTSRCCOPY 0x00330008 +!define NOTSRCERASE 0x001100A6 +!define MERGECOPY 0x00C000CA +!define MERGEPAINT 0x00BB0226 +!define PATCOPY 0x00F00021 +!define PATPAINT 0x00FB0A09 +!define PATINVERT 0x005A0049 +!define DSTINVERT 0x00550009 +!define BLACKNESS 0x00000042 +!define WHITENESS 0x00FF0062 + +; == Message Box == + +!define MB_OK 0x00000000 +!define MB_OKCANCEL 0x00000001 +!define MB_ABORTRETRYIGNORE 0x00000002 +!define MB_YESNOCANCEL 0x00000003 +!define MB_YESNO 0x00000004 +!define MB_RETRYCANCEL 0x00000005 +!define MB_CANCELTRYCONTINUE 0x00000006 +!define MB_ICONHAND 0x00000010 +!define MB_ICONQUESTION 0x00000020 +!define MB_ICONEXCLAMATION 0x00000030 +!define MB_ICONASTERISK 0x00000040 +!define MB_USERICON 0x00000080 +!define MB_ICONWARNING ${MB_ICONEXCLAMATION} +!define MB_ICONERROR ${MB_ICONHAND} + +!define MB_ICONINFORMATION ${MB_ICONASTERISK} +!define MB_ICONSTOP ${MB_ICONHAND} + +!define MB_DEFBUTTON1 0x00000000 +!define MB_DEFBUTTON2 0x00000100 +!define MB_DEFBUTTON3 0x00000200 +!define MB_DEFBUTTON4 0x00000300 + +!define MB_APPLMODAL 0x00000000 +!define MB_SYSTEMMODAL 0x00001000 +!define MB_TASKMODAL 0x00002000 +!define MB_HELP 0x00004000 + +!define MB_NOFOCUS 0x00008000 +!define MB_SETFOREGROUND 0x00010000 +!define MB_DEFAULT_DESKTOP_ONLY 0x00020000 + +!define MB_TOPMOST 0x00040000 +!define MB_RIGHT 0x00080000 +!define MB_RTLREADING 0x00100000 + +; == Drive type constants == + +!define DRIVE_UNKNOWN 0 +!define DRIVE_NO_ROOT_DIR 1 +!define DRIVE_REMOVABLE 2 +!define DRIVE_FIXED 3 +!define DRIVE_REMOTE 4 +!define DRIVE_CDROM 5 +!define DRIVE_RAMDISK 6 + +; == Callbacks == + +!macro SINGLE_CALLBACK CHKN RES INDEX FUNC +CheckCB_${CHKN}: + Pop ${RES} + StrCmp ${RES} "callback${INDEX}" 0 ExitCB_${CHKN} + Call ${FUNC} + Goto CheckCB_${CHKN} +ExitCB_${CHKN}: +!macroend + +!verbose 4 + +!endif \ No newline at end of file diff --git a/installer/NSIS/Examples/System/System.nsi b/installer/NSIS/Examples/System/System.nsi new file mode 100644 index 0000000..66e08a2 --- /dev/null +++ b/installer/NSIS/Examples/System/System.nsi @@ -0,0 +1,136 @@ +; This is just an example of System Plugin +; +; (c) brainsucker, 2002 +; (r) BSForce + +Name "System Plugin Example" +OutFile "System.exe" + +!include "SysFunc.nsh" + +Section "ThisNameIsIgnoredSoWhyBother?" + SetOutPath $TEMP + + ; ----- Sample 1 ----- Message box with custom icon ----- + + ; there are no default beeps for custom message boxes, use sysMessageBeep + ; in case you need it (see next message box example) + !insertmacro smMessageBox "i 0" "Message box with custom icon!" "System Example 1a" ${MB_OK} "i 103" + ; i 0 - installer exe as module + ; i 103 - icon ID + + ; The same example but using icon from resource.dll. + ; You could use this dll for storing your resources, just replace FAR icon + ; with something you really need. + File "Resource.dll" + System::Call '${sysMessageBeep} (${MB_ICONHAND})' ; custom beep + !insertmacro smMessageBox "`$TEMP\resource.dll`" "Message box with custom icon from resource.dll!" "System Example 1b" ${MB_OKCANCEL} "i 103" + Delete $TEMP\resource.dll + + ; ----- Sample 2 ----- Fixed disks size/space ----- + + StrCpy $7 ' Disk, Size, Free, Free for user:$\n$\n' + + ; Memory for paths + System::Alloc 1024 + Pop $1 + ; Get drives + System::Call '${sysGetLogicalDriveStrings}(1024, r1)' +enumok: + ; One more drive? + System::Call '${syslstrlen}(i r1) .r2' + IntCmp $2 0 enumex + + ; Is it DRIVE_FIXED? + System::Call '${sysGetDriveType} (i r1) .r3' + StrCmp $3 ${DRIVE_FIXED} 0 enumnext + + ; Drive space + System::Call '${sysGetDiskFreeSpaceEx}(i r1, .r3, .r4, .r5)' + + ; Pretty KBs will be saved on stack + System::Int64Op $3 / 1048576 + System::Int64Op $5 / 1048576 + System::Int64Op $4 / 1048576 + + ; Get pretty drive path string + System::Call '*$1(&t1024 .r6)' + System::Call '${syswsprintf} (.r7, "%s%20s %20s mb %20s mb %20s mb$\n", tr7, tr6, ts, ts, ts)' + +enumnext: + ; Next drive path + IntOp $1 $1 + $2 + IntOp $1 $1 + 1 + goto enumok +enumex: ; End of drives or user cancel + ; Free memory for paths + System::Free $1 + + ; Message box + System::Call '${sysMessageBox}($HWNDPARENT, s, "System Example 2", ${MB_OKCANCEL})' "$7" + + ; ----- Sample 3 ----- Direct proc defenition ----- + + ; Direct specification demo + System::Call 'user32::MessageBoxA(i $HWNDPARENT, t "Just direct MessageBoxA specification demo ;)", t "System Example 3", i ${MB_OK}) i.s' + Pop $0 + + ; ----- Sample 4 ----- Int64, mixed definition demo ----- + + ; Long int demo + StrCpy $2 "12312312" + StrCpy $3 "12345678903" + System::Int64Op $2 "*" $3 + Pop $4 + + ; Cdecl demo (uses 3 defenitions (simple example)) + System::Call "${syswsprintf}(.R1, s,,, t, ir0) .R0 (,,tr2,tr3,$4_)" "Int64 ops and strange defenition demo, %s x %s == %s, and previous msgbox result = %d" + MessageBox MB_OKCANCEL "Cool: '$R1'" + + ; ----- Sample 5 ----- Small structure example ----- + + ; Create & Fill structure + System::Call "*(i 123123123, &t10 'Hello', &i1 0x123dd, &i2 0xffeeddccaa) i.s" + Pop $1 + ; Read data from structure + System::Call "*$1(i .r2, &t10 .r3, &i1 .r4, &i2 .r5, &l0 .r6)" + ; Show data and delete structure + MessageBox MB_OK "Structure example: $\nint == $2 $\nstring == $3 $\nbyte == $4 $\nshort == $5 $\nsize == $6" + System::Free $1 + + ; ----- Sample 6 ----- systemGetFileSysTime demo ----- + Call GetInstallerExeName + pop $0 + + !insertmacro smGetFileSysTime $0 + System::Call '*$R0${stSYSTEMTIME}(.r1, .r2, .r3, .r4, .r5, .r6, .r7, .r8)' + + MessageBox MB_OK "GetFileSysTime example: file '$0', year $1, month $2, dow $3, day $4, hour $5, min $6, sec $7, ms $8" + + ; free memory from SYSTEMTIME + System::Free $R0 + + ; ----- Sample 7 ----- systemSplash -> Callbacks demonstration ----- + + ; Logo + File /oname=spltmp.bmp "${NSISDIR}\Contrib\Graphics\Header\orange-nsis.bmp" +; File /oname=spltmp.wav "d:\Windows\Media\tada.wav" + + ; I. systemSplash variant + !insertmacro smSystemSplash 2000 "$TEMP\spltmp" + + ; II. Splash Plugin variant +; splash::show 2000 $TEMP\spltmp +; Pop $R0 ; $R0 has '1' if the user closed the splash screen early, + + ; remove logo + Delete $TEMP\spltmp.bmp +; Delete $TEMP\spltmp.wav + + ; Display splash result + pop $0 + MessageBox MB_OK "Splash (callbacks) demo result $R0" + +SectionEnd + +; eof diff --git a/installer/NSIS/Examples/TextFunc.ini b/installer/NSIS/Examples/TextFunc.ini new file mode 100644 index 0000000..5b9a7bc --- /dev/null +++ b/installer/NSIS/Examples/TextFunc.ini @@ -0,0 +1,130 @@ +[Settings] +NumFields=15 +NextButtonText=&Enter + +[Field 1] +Type=Droplist +Flags=NOTIFY +State=1. LineFind +ListItems=1. LineFind|2. LineRead|3. FileReadFromEnd|4. LineSum|5. FileJoin|6. TextCompare|7. ConfigRead|8. ConfigWrite|9. FileRecode +Left=44 +Right=139 +Top=9 +Bottom=100 + +[Field 2] +Type=FileRequest +Left=44 +Right=-17 +Top=30 +Bottom=41 + +[Field 3] +Type=FileRequest +Left=44 +Right=-17 +Top=46 +Bottom=57 + +[Field 4] +Type=FileRequest +State=3:-1 +Left=44 +Right=-17 +Top=62 +Bottom=75 + +[Field 5] +Type=Droplist +Flags=NOTIFY +State=Example1 (delete first two symbols) +ListItems=Example1 (delete first two symbols)|Example2 (show changed lines)|Example3 (delete lines)|Example4 (insert lines)|Example5 (replace in file - WordFunc.nsh required)|Example6 (line string to cut or delete)|Example7 (read lines) +Left=44 +Right=-36 +Top=81 +Bottom=155 + +[Field 6] +Type=Droplist +Flags=NOTIFY +State=Example1 (Different or Equal) +ListItems=Example1 (Different or Equal)|Example2 (Compare line-by-line - Different)|Example3 (Compare line-by-line - Equal)|Example4 (Compare all lines - Different)|Example5 (Compare all lines - Equal) +Left=44 +Right=-36 +Top=81 +Bottom=140 + +[Field 7] +Type=Droplist +State=FileReadFromEndCallback +ListItems=FileReadFromEndCallback +Left=44 +Right=-36 +Top=81 +Bottom=92 + +[Field 8] +Type=Text +Flags=READONLY +Left=9 +Right=-36 +Top=108 +Bottom=120 + +[Field 9] +Type=Button +Text=Edit +Flags=NOTIFY +Left=234 +Right=256 +Top=81 +Bottom=92 + +[Field 10] +Type=Button +Text=Log +Flags=NOTIFY|DISABLED +Left=234 +Right=256 +Top=108 +Bottom=120 + +[Field 11] +Type=Label +Text=InputFile +Left=10 +Right=43 +Top=32 +Bottom=44 + +[Field 12] +Type=Label +Text=OutputFile +Left=10 +Right=43 +Top=48 +Bottom=60 + +[Field 13] +Type=Label +Text=Range +Left=10 +Right=44 +Top=65 +Bottom=75 + +[Field 14] +Type=Label +Text=Function +Left=10 +Right=43 +Top=81 +Bottom=92 + +[Field 15] +Type=Label +Text=Result: +Left=10 +Right=229 +Top=97 +Bottom=105 diff --git a/installer/NSIS/Examples/TextFunc.nsi b/installer/NSIS/Examples/TextFunc.nsi new file mode 100644 index 0000000..e05b7f7 --- /dev/null +++ b/installer/NSIS/Examples/TextFunc.nsi @@ -0,0 +1,834 @@ +;_____________________________________________________________________________ +; +; Text Functions +;_____________________________________________________________________________ +; +; 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + +Name "Text Functions" +OutFile "TextFunc.exe" +Caption "$(^Name)" +XPStyle on +RequestExecutionLevel user + +!include "WinMessages.nsh" +!include "TextFunc.nsh" + +Var HWND +Var INI +Var LOG +Var PROJECT +Var CALLBACK +Var VIEW +Var FUNCTION +Var LINEFIND1 +Var LINEFIND2 +Var LINEFIND3 +Var LINEREAD1 +Var LINEREAD2 +Var FILEREADFROMEND1 +Var LINESUM1 +Var FILEJOIN1 +Var FILEJOIN2 +Var FILEJOIN3 +Var TEXTCOMPARE1 +Var TEXTCOMPARE2 +Var TEXTCOMPARE3 +Var CONFIGREAD1 +Var CONFIGREAD2 +Var CONFIGWRITE1 +Var CONFIGWRITE2 +Var CONFIGWRITE3 +Var FILERECODE1 +Var FILERECODE2 + +Page Custom ShowCustom LeaveCustom + +Function ShowCustom + InstallOptions::initDialog "$INI" + Pop $hwnd + GetDlgItem $0 $HWND 1206 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1208 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1209 + ShowWindow $0 0 + StrCpy $FUNCTION LineFind + StrCpy $LINEREAD2 10 + StrCpy $TEXTCOMPARE3 FastDiff + StrCpy $CONFIGREAD1 "$WINDIR\system.ini" + StrCpy $CONFIGREAD2 "shell=" + StrCpy $FILERECODE2 CharToOem + InstallOptions::show + Pop $0 +FunctionEnd + +Function LeaveCustom + ReadINIStr $0 $INI "Settings" "State" + ReadINIStr $R0 $INI "Field 1" "State" + ReadINIStr $R1 $INI "Field 2" "State" + ReadINIStr $R2 $INI "Field 3" "State" + ReadINIStr $R3 $INI "Field 4" "State" + ReadINIStr $R4 $INI "Field 5" "State" + ReadINIStr $R5 $INI "Field 6" "State" + StrCpy $R4 $R4 8 + StrCpy $R5 $R5 8 + StrCpy $6 0 + StrCpy $7 '$${' + StrCpy $8 'r' + StrCpy $9 'n' + + StrCmp $0 10 Log + StrCmp $0 9 ViewOrEdit + StrCmp $0 0 Enter + goto MainSend + + Log: + Exec 'notepad.exe $LOG' + Abort + + ViewOrEdit: + StrCmp $FUNCTION FileReadFromEnd 0 Edit + StrCmp $VIEW '' 0 ViewFileReadFromEndCallback + GetTempFileName $VIEW $PLUGINSDIR + StrCpy $7 '$$' + FileOpen $0 $VIEW w + FileWrite $0 `Function FileReadFromEndCallback$\r$\n` + FileWrite $0 ` MessageBox MB_OKCANCEL '$7$$9 "Line"=[$$9]$7\$9$7$$8 "#"=[$$8]$7\$9$7$$7 "-#"=[$$7]' IDOK +2$\r$\n` + FileWrite $0 ` StrCpy $$R0 StopFileReadFromEnd$\r$\n$\r$\n` + FileWrite $0 ` Push $$R0$\r$\n` + FileWrite $0 `FunctionEnd$\r$\n` + FileClose $0 + StrCpy $7 '$${' + SetFileAttributes $VIEW READONLY + ViewFileReadFromEndCallback: + Exec 'notepad.exe $VIEW' + Abort + + Edit: + StrCmp $CALLBACK '' +5 + StrCmp $6$R6 '0$R0$R4$R5' showproject + StrCmp $R6 '$R0$R4$R5' +3 + Delete $CALLBACK + StrCpy $CALLBACK '' + StrCpy $R6 '$R0$R4$R5' + + #Project# + StrCmp $6$R0 "01. LineFind" 0 +5 + IfFileExists $CALLBACK +2 + GetTempFileName $CALLBACK $PLUGINSDIR + FileOpen $0 $CALLBACK w + goto function + IfFileExists $PROJECT +2 + GetTempFileName $PROJECT $PLUGINSDIR + FileOpen $0 $PROJECT w + + #Name# + FileWrite $0 'Name "$FUNCTION"$\r$\n' + FileWrite $0 'OutFile "$PROJECT.exe"$\r$\n$\r$\n' + + #!include# + StrCmp $R0$R4 '1. LineFindExample5' 0 TextFuncInclude + IfFileExists '$EXEDIR\WordFunc.nsh' 0 +3 + FileWrite $0 '!include "$EXEDIR\WordFunc.nsh"$\r$\n' + goto +2 + FileWrite $0 '!include "WordFunc.nsh"$\r$\n' + FileWrite $0 '!insertmacro WordFind$\r$\n' + FileWrite $0 '!insertmacro WordFindS$\r$\n' + FileWrite $0 '!insertmacro WordFind2X$\r$\n' + FileWrite $0 '!insertmacro WordFind2XS$\r$\n' + FileWrite $0 '!insertmacro WordFind3X$\r$\n' + FileWrite $0 '!insertmacro WordFind3XS$\r$\n' + FileWrite $0 '!insertmacro WordReplace$\r$\n' + FileWrite $0 '!insertmacro WordReplaceS$\r$\n' + FileWrite $0 '!insertmacro WordAdd$\r$\n' + FileWrite $0 '!insertmacro WordAddS$\r$\n' + FileWrite $0 '!insertmacro WordInsert$\r$\n' + FileWrite $0 '!insertmacro WordInsertS$\r$\n' + FileWrite $0 '!insertmacro StrFilter$\r$\n' + FileWrite $0 '!insertmacro StrFilterS$\r$\n' + TextFuncInclude: + IfFileExists '$EXEDIR\TextFunc.nsh' 0 +3 + FileWrite $0 '!include "$EXEDIR\TextFunc.nsh"$\r$\n' + goto +2 + FileWrite $0 '!include "TextFunc.nsh"$\r$\n' + FileWrite $0 '!insertmacro $FUNCTION$\r$\n' + StrCmp $FUNCTION TextCompare +2 + FileWrite $0 '!insertmacro TrimNewLines$\r$\n' + + #Section# + FileWrite $0 '$\r$\nSection -empty$\r$\n' + FileWrite $0 'SectionEnd$\r$\n$\r$\n' + + #Function .onInit# + FileWrite $0 'Function .onInit$\r$\n' + StrCmp $R0$R5 "6. TextCompareExample1" 0 TextCompareExample235 + FileWrite $0 ' StrCpy $$R0 ""$\r$\n' + FileWrite $0 ' $7TextCompare} "$R1" "$R2" "$R3" "$R5"$\r$\n' + FileWrite $0 ' IfErrors error$\r$\n' + FileWrite $0 ' StrCmp $$R0 NotEqual 0 +2$\r$\n' + FileWrite $0 ' MessageBox MB_OK " Files differ" IDOK +2$\r$\n' + FileWrite $0 ' MessageBox MB_OK " Files identical"$\r$\n' + FileWrite $0 ' goto end$\r$\n$\r$\n' + goto endoninit + TextCompareExample235: + StrCmp $R0$R5 "6. TextCompareExample2" +3 + StrCmp $R0$R5 "6. TextCompareExample3" +2 + StrCmp $R0$R5 "6. TextCompareExample5" 0 TextCompareExample4 + FileWrite $0 ' StrCpy $$R0 "$R1"$\r$\n' + FileWrite $0 ' StrCpy $$R1 "$R2"$\r$\n$\r$\n' + FileWrite $0 ' GetTempFileName $$R2$\r$\n' + FileWrite $0 ' FileOpen $$R3 $$R2 w$\r$\n' + FileWrite $0 ' FileWrite $$R3 "$$R0 | $$R1$$\$8$$\$9"$\r$\n' + FileWrite $0 ' $7TextCompare} "$$R0" "$$R1" "$R3" "$R5"$\r$\n' + FileWrite $0 ' IfErrors error$\r$\n' + FileWrite $0 ' Exec "notepad.exe $$R2"$\r$\n' + FileWrite $0 ' goto end$\r$\n$\r$\n' + goto endoninit + TextCompareExample4: + StrCmp $R0$R5 "6. TextCompareExample4" 0 LineFindExample123456 + FileWrite $0 ' StrCpy $$R0 "$R1"$\r$\n' + FileWrite $0 ' StrCpy $$R1 "$R2"$\r$\n$\r$\n' + FileWrite $0 ' GetTempFileName $$R2$\r$\n' + FileWrite $0 ' FileOpen $$R3 $$R2 w$\r$\n' + FileWrite $0 ' FileWrite $$R3 "$$R0 | $$R1$$\$8$$\$9"$\r$\n' + FileWrite $0 ' $7TextCompare} "$$R0" "$$R1" "$R3" "$R5"$\r$\n' + FileWrite $0 ' IfErrors error$\r$\n' + FileWrite $0 ' FileWrite $$R3 "$$\$8$$\$9$$R1 | $$R0$$\$8$$\$9"$\r$\n' + FileWrite $0 ' $7TextCompare} "$$R1" "$$R0" "$R3" "$R5"$\r$\n' + FileWrite $0 ' FileClose $$R3$\r$\n' + FileWrite $0 ' IfErrors error$\r$\n' + FileWrite $0 ' Exec "notepad.exe $$R2"$\r$\n$\r$\n' + FileWrite $0 ' goto end$\r$\n$\r$\n' + goto endoninit + LineFindExample123456: + FileWrite $0 ' $7$FUNCTION} "$R1" "$R2" "$R3" "$R4"$\r$\n' + FileWrite $0 ' IfErrors error$\r$\n' + FileWrite $0 ' MessageBox MB_YESNO " Open output file?" IDNO end$\r$\n' + FileWrite $0 ' StrCmp "$R2" "" 0 +3$\r$\n' + FileWrite $0 ` Exec 'notepad.exe "$R1"'$\r$\n` + FileWrite $0 ' goto end$\r$\n' + FileWrite $0 ' SearchPath $$R2 "$R2"$\r$\n' + FileWrite $0 ` Exec 'notepad.exe "$$R2"'$\r$\n` + FileWrite $0 ' goto end$\r$\n$\r$\n' + endoninit: + FileWrite $0 ' error:$\r$\n' + FileWrite $0 ' MessageBox MB_OK "Error"$\r$\n$\r$\n' + FileWrite $0 ' end:$\r$\n' + FileWrite $0 ' Quit$\r$\n' + FileWrite $0 'FunctionEnd$\r$\n$\r$\n' + #FunctionEnd# + + + #Function CallBack# + StrCmp $CALLBACK '' 0 close + function: + StrCmp $R0 '1. LineFind' 0 +8 + FileWrite $0 'Function $R4$\r$\n' + StrCmp $R4 "Example1" Example1LF + StrCmp $R4 "Example2" Example2LF + StrCmp $R4 "Example3" Example3LF + StrCmp $R4 "Example4" Example4LF + StrCmp $R4 "Example5" Example5LF + StrCmp $R4 "Example6" Example6LF + + FileWrite $0 'Function $R5$\r$\n' + StrCmp $R5 "Example1" Example1TC + StrCmp $R5 "Example2" Example2TC + StrCmp $R5 "Example3" Example3TC + StrCmp $R5 "Example4" Example4TC + StrCmp $R5 "Example5" Example3TC + + Example1LF: + FileWrite $0 " $7TrimNewLines} '$$R9' $$R9$\r$\n" + FileWrite $0 " StrCpy $$R9 $$R9 '' 2 ;delete first two symbols$\r$\n" + FileWrite $0 " StrCpy $$R9 '$$R9$$\$8$$\$9'$\r$\n$\r$\n" + goto endwrite + Example2LF: + FileWrite $0 " $7TrimNewLines} '$$R9' $$R9$\r$\n" + FileWrite $0 " StrCpy $$R9 '$$R9 ~Changed line ($$R8)~$$\$8$$\$9'$\r$\n$\r$\n" + goto endwrite + Example3LF: + FileWrite $0 " StrCpy $$0 SkipWrite$\r$\n$\r$\n" + goto endwrite + Example4LF: + FileWrite $0 " FileWrite $$R4 '---First Line---$$\$8$$\$9'$\r$\n" + FileWrite $0 " FileWrite $$R4 '---Second Line ...---$$\$8$$\$9'$\r$\n$\r$\n" + goto endwrite + Example5LF: + FileWrite $0 " ; You can use:$\r$\n" + FileWrite $0 " ; $7WordFind}|$7WordFindS}|$7WordFind2X}|$7WordFind2XS}|$\r$\n" + FileWrite $0 " ; $7WordFind3X}|$7WordFind3XS}|$7WordReplace}|$7WordReplaceS}|$\r$\n" + FileWrite $0 " ; $7WordAdd}|$7WordAddS}|$7WordInsert}|$7WordInsertS}|$\r$\n" + FileWrite $0 " ; $7StrFilter}|$7StrFilterS}$\r$\n$\r$\n" + FileWrite $0 " $7WordReplace} '$$R9' ' ' '_' '+*' $$R9$\r$\n$\r$\n" + goto endwrite + Example6LF: + FileWrite $0 ' ;(Cut lines from a line to another line (also including that line))$\r$\n' + FileWrite $0 ' StrCmp $$R0 finish stop$\r$\n' + FileWrite $0 ' StrCmp $$R0 start finish$\r$\n' + FileWrite $0 ' StrCmp $$R9 "Start Line$$\$8$$\$9" 0 skip$\r$\n' + FileWrite $0 ' StrCpy $$R0 start$\r$\n' + FileWrite $0 ' StrCpy $$R1 $$R9$\r$\n' + FileWrite $0 ' goto code$\r$\n' + FileWrite $0 ' finish:$\r$\n' + FileWrite $0 ' StrCmp $$R9 "Finish Line$$\$8$$\$9" 0 code$\r$\n' + FileWrite $0 ' StrCpy $$R0 finish$\r$\n' + FileWrite $0 ' StrCpy $$R2 $$R8$\r$\n' + FileWrite $0 ' goto code$\r$\n' + FileWrite $0 ' skip:$\r$\n' + FileWrite $0 ' StrCpy $$0 SkipWrite$\r$\n' + FileWrite $0 ' goto output$\r$\n' + FileWrite $0 ' stop:$\r$\n' + FileWrite $0 ' StrCpy $$0 StopLineFind$\r$\n' + FileWrite $0 ' goto output$\r$\n$\r$\n' + FileWrite $0 ' ;;(Delete lines from a line to another line (also including that line))$\r$\n' + FileWrite $0 ' ; StrCmp $$R0 finish code$\r$\n' + FileWrite $0 ' ; StrCmp $$R0 start finish$\r$\n' + FileWrite $0 ' ; StrCmp $$R9 "Start Line$$\$8$$\$9" 0 code$\r$\n' + FileWrite $0 ' ; StrCpy $$R0 start$\r$\n' + FileWrite $0 ' ; StrCpy $$R1 $$R8$\r$\n' + FileWrite $0 ' ; goto skip$\r$\n' + FileWrite $0 ' ; finish:$\r$\n' + FileWrite $0 ' ; StrCmp $$R9 "Finish Line$$\$8$$\$9" 0 skip$\r$\n' + FileWrite $0 ' ; StrCpy $$R0 finish$\r$\n' + FileWrite $0 ' ; StrCpy $$R2 $$R8$\r$\n' + FileWrite $0 ' ; skip:$\r$\n' + FileWrite $0 ' ; StrCpy $$0 SkipWrite$\r$\n' + FileWrite $0 ' ; goto output$\r$\n$\r$\n' + FileWrite $0 ' code:$\r$\n' + FileWrite $0 ' ;...$\r$\n$\r$\n' + FileWrite $0 ' output:$\r$\n' + goto endwrite + Example1TC: + FileWrite $0 " StrCpy $$R0 NotEqual$\r$\n" + FileWrite $0 " StrCpy $$0 StopTextCompare$\r$\n$\r$\n" + goto endwrite + Example2TC: + FileWrite $0 " FileWrite $$R3 '$$8=$$9'$\r$\n" + FileWrite $0 " FileWrite $$R3 '$$6=$$7$$\$8$$\$9'$\r$\n$\r$\n" + goto endwrite + Example3TC: + FileWrite $0 " FileWrite $$R3 '$$8|$$6=$$9'$\r$\n$\r$\n" + goto endwrite + Example4TC: + FileWrite $0 " FileWrite $$R3 '$$8=$$9'$\r$\n$\r$\n" + goto endwrite + endwrite: + FileWrite $0 ' Push $$0$\r$\n' + FileWrite $0 'FunctionEnd$\r$\n' + close: + FileClose $0 + goto $6 + #FunctionEnd# + + showproject: + StrCmp $R0 '1. LineFind' 0 +3 + ExecWait 'notepad.exe $CALLBACK' + goto +4 + SetFileAttributes $PROJECT READONLY + ExecWait 'notepad.exe $PROJECT' + SetFileAttributes $PROJECT NORMAL + Abort + + MainSend: + GetDlgItem $0 $HWND 1210 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 1 + EnableWindow $0 1 + GetDlgItem $0 $HWND 1204 + ShowWindow $0 1 + EnableWindow $0 1 + GetDlgItem $0 $HWND 1205 + EnableWindow $0 1 + GetDlgItem $0 $HWND 1206 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1207 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1208 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1209 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1211 + EnableWindow $0 1 + + StrCmp $FUNCTION LineFind 0 +5 + StrCpy $LINEFIND1 $R1 + StrCpy $LINEFIND2 $R2 + StrCpy $LINEFIND3 $R3 + goto LineFindSend + StrCmp $FUNCTION LineRead 0 +4 + StrCpy $LINEREAD1 $R1 + StrCpy $LINEREAD2 $R2 + goto LineFindSend + StrCmp $FUNCTION FileReadFromEnd 0 +3 + StrCpy $FILEREADFROMEND1 $R1 + goto LineFindSend + StrCmp $FUNCTION LineSum 0 +3 + StrCpy $LINESUM1 $R1 + goto LineFindSend + StrCmp $FUNCTION FileJoin 0 +5 + StrCpy $FILEJOIN1 $R1 + StrCpy $FILEJOIN2 $R2 + StrCpy $FILEJOIN3 $R3 + goto LineFindSend + StrCmp $FUNCTION TextCompare 0 +5 + StrCpy $TEXTCOMPARE1 $R1 + StrCpy $TEXTCOMPARE2 $R2 + StrCpy $TEXTCOMPARE3 $R3 + goto LineFindSend + StrCmp $FUNCTION ConfigRead 0 +4 + StrCpy $CONFIGREAD1 $R1 + StrCpy $CONFIGREAD2 $R2 + goto LineFindSend + StrCmp $FUNCTION ConfigWrite 0 +5 + StrCpy $CONFIGWRITE1 $R1 + StrCpy $CONFIGWRITE2 $R2 + StrCpy $CONFIGWRITE3 $R3 + goto LineFindSend + StrCmp $FUNCTION FileRecode 0 +3 + StrCpy $FILERECODE1 $R1 + StrCpy $FILERECODE2 $R2 + + LineFindSend: + StrCmp $R0 "1. LineFind" 0 LineReadSend + StrCmp $FUNCTION LineFind 0 LineFindSend2 + StrCmp $R4 "Example1" 0 +3 + StrCpy $LINEFIND3 "3:-1" + goto LineFindSend2 + StrCmp $R4 "Example2" 0 +3 + StrCpy $LINEFIND3 "{5:12 15 -6:-5 -1}" + goto LineFindSend2 + StrCmp $R4 "Example3" 0 +3 + StrCpy $LINEFIND3 "2:3 10:-5 -3:-2" + goto LineFindSend2 + StrCmp $R4 "Example4" 0 +3 + StrCpy $LINEFIND3 "10" + goto LineFindSend2 + StrCmp $R4 "Example5" 0 +3 + StrCpy $LINEFIND3 "1:-1" + goto LineFindSend2 + StrCmp $R4 "Example6" 0 +3 + StrCpy $LINEFIND3 "" + goto LineFindSend2 + StrCmp $R4 "Example7" 0 +2 + StrCpy $LINEFIND3 "1:-1" + + LineFindSend2: + StrCpy $FUNCTION LineFind + StrCmp $LINEFIND2 '/NUL' 0 +2 + StrCpy $LINEFIND2 '' + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$LINEFIND1" + GetDlgItem $0 $HWND 1203 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$LINEFIND2" + GetDlgItem $0 $HWND 1205 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$LINEFIND3" + GetDlgItem $0 $HWND 1207 + ShowWindow $0 1 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Edit" + GetDlgItem $0 $HWND 1212 + ShowWindow $0 1 + StrCmp $LOG '' +2 + EnableWindow $0 1 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:OutputFile" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Range" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Function" + + StrCmp $R4 "Example7" 0 +9 + GetDlgItem $0 $HWND 1203 + EnableWindow $0 0 + SendMessage $0 ${WM_ENABLE} 1 0 + SendMessage $0 ${WM_SETTEXT} 1 "STR:/NUL" + GetDlgItem $0 $HWND 1204 + EnableWindow $0 0 + GetDlgItem $0 $HWND 1211 + EnableWindow $0 0 + abort + + + LineReadSend: + StrCmp $R0 "2. LineRead" 0 FileReadFromEndSend + StrCpy $FUNCTION LineRead + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$LINEREAD1" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$LINEREAD2" + GetDlgItem $0 $HWND 1204 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1212 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Line #" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + Abort + + FileReadFromEndSend: + StrCmp $R0 "3. FileReadFromEnd" 0 LineSumSend + StrCpy $FUNCTION FileReadFromEnd + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$FILEREADFROMEND1" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1204 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1209 + ShowWindow $0 1 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:View" + GetDlgItem $0 $HWND 1212 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Function" + Abort + + LineSumSend: + StrCmp $R0 "4. LineSum" 0 FileJoinSend + StrCpy $FUNCTION LineSum + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$LINESUM1" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1204 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1212 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + Abort + + FileJoinSend: + StrCmp $R0 "5. FileJoin" 0 TextCompareSend + StrCpy $FUNCTION FileJoin + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$FILEJOIN1" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$FILEJOIN2" + GetDlgItem $0 $HWND 1204 + ShowWindow $0 1 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 1 + EnableWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$FILEJOIN3" + GetDlgItem $0 $HWND 1206 + ShowWindow $0 1 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1212 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile1" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile2" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:OutputFile" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + Abort + + TextCompareSend: + StrCmp $R0 "6. TextCompare" 0 ConfigReadSend + StrCmp $FUNCTION TextCompare 0 TextCompareSend2 + StrCmp $R5 "Example1" 0 +3 + StrCpy $TEXTCOMPARE3 "FastDiff" + goto TextCompareSend2 + StrCmp $R5 "Example2" 0 +3 + StrCpy $TEXTCOMPARE3 "FastDiff" + goto TextCompareSend2 + StrCmp $R5 "Example3" 0 +3 + StrCpy $TEXTCOMPARE3 "FastEqual" + goto TextCompareSend2 + StrCmp $R5 "Example4" 0 +3 + StrCpy $TEXTCOMPARE3 "SlowDiff" + goto TextCompareSend2 + StrCmp $R5 "Example5" 0 +2 + StrCpy $TEXTCOMPARE3 "SlowEqual" + + TextCompareSend2: + StrCpy $FUNCTION TextCompare + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$TEXTCOMPARE1" + GetDlgItem $0 $HWND 1203 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$TEXTCOMPARE2" + GetDlgItem $0 $HWND 1204 + ShowWindow $0 1 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 1 + EnableWindow $0 0 + SendMessage $0 ${WM_ENABLE} 1 0 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$TEXTCOMPARE3" + GetDlgItem $0 $HWND 1208 + ShowWindow $0 1 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:View" + GetDlgItem $0 $HWND 1212 + ShowWindow $0 1 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:TextFile1" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:TextFile2" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Option" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Function" + abort + + ConfigReadSend: + StrCmp $R0 "7. ConfigRead" 0 ConfigWriteSend + StrCpy $FUNCTION ConfigRead + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$CONFIGREAD1" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$CONFIGREAD2" + GetDlgItem $0 $HWND 1204 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1212 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Entry" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + Abort + + ConfigWriteSend: + StrCmp $R0 "8. ConfigWrite" 0 FileRecodeSend + StrCpy $FUNCTION ConfigWrite + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$CONFIGWRITE1" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$CONFIGWRITE2" + GetDlgItem $0 $HWND 1204 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$CONFIGWRITE3" + GetDlgItem $0 $HWND 1211 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1212 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Entry" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Value" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + Abort + + FileRecodeSend: + StrCmp $R0 "9. FileRecode" 0 Abort + StrCpy $FUNCTION FileRecode + GetDlgItem $0 $HWND 1201 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$FILERECODE1" + GetDlgItem $0 $HWND 1203 + ShowWindow $0 1 + SendMessage $0 ${WM_SETTEXT} 1 "STR:$FILERECODE2" + GetDlgItem $0 $HWND 1204 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1205 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1211 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1212 + ShowWindow $0 0 + GetDlgItem $0 $HWND 1213 + SendMessage $0 ${WM_SETTEXT} 1 "STR:InputFile" + GetDlgItem $0 $HWND 1214 + SendMessage $0 ${WM_SETTEXT} 1 "STR:Format" + GetDlgItem $0 $HWND 1215 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $0 $HWND 1216 + SendMessage $0 ${WM_SETTEXT} 1 "STR:" + Abort + +;=Enter= + Enter: + StrCmp $R1 '' 0 +3 + StrCpy $0 'Choose InputFile' + goto send + IfFileExists $R1 +3 + StrCpy $0 'InputFile is not exist' + goto send + + StrCmp $R0 "1. LineFind" LineFindRead + StrCmp $R0 "2. LineRead" LineRead + StrCmp $R0 "3. FileReadFromEnd" FileReadFromEnd + StrCmp $R0 "4. LineSum" LineSum + StrCmp $R0 "5. FileJoin" FileJoin + StrCmp $R0 "6. TextCompare" LineFind-TextCompare + StrCmp $R0 "7. ConfigRead" ConfigRead + StrCmp $R0 "8. ConfigWrite" ConfigWrite + StrCmp $R0 "9. FileRecode" FileRecode + Abort + + LineFindRead: + StrCmp $R4 "Example7" 0 LineFind-TextCompare + ${LineFind} '$R1' '/NUL' '$R3' LineFindCallback + IfErrors error + StrCmp $R0 StopLineFind 0 done + StrCpy $0 'stopped' + goto send + + LineFind-TextCompare: + GetLabelAddress $6 LineFindBack + goto Edit + LineFindBack: + FileClose $0 + StrCmp $R0 "6. TextCompare" Compile + StrCmp $CALLBACK '' Compile + ${FileJoin} "$PROJECT" "$CALLBACK" "" + + Compile: + StrCmp $LOG '' 0 +4 + GetTempFileName $LOG $PLUGINSDIR + GetDlgItem $0 $HWND 1212 + EnableWindow $0 1 + ReadRegStr $0 HKLM "SOFTWARE\NSIS" "" + IfErrors 0 +2 + StrCpy $0 "${NSISDIR}" + nsExec::Exec '"$0\makensis.exe" /O$LOG $PROJECT' + Pop $0 + StrCmp $0 0 0 +6 + ExecWait '$PROJECT.exe' $0 + Delete $PROJECT + Delete $PROJECT.exe + StrCpy $PROJECT '' + goto done + MessageBox MB_YESNO|MB_ICONEXCLAMATION "Compile error. Open log?" IDNO +2 + Exec 'notepad.exe $LOG' + StrCpy $0 "Compile Error" + goto send + + LineRead: + ${LineRead} "$R1" "$R2" $0 + IfErrors error send + + FileReadFromEnd: + ${FileReadFromEnd} "$R1" "FileReadFromEndCallback" + IfErrors error + StrCmp $R0 StopFileReadFromEnd 0 done + StrCpy $0 'stopped' + goto send + + LineSum: + ${LineSum} "$R1" $0 + IfErrors error send + + FileJoin: + ${FileJoin} "$R1" "$R2" "$R3" + IfErrors error + MessageBox MB_YESNO " Open output file?" IDNO done + StrCmp $R3 '' 0 +3 + Exec '"notepad.exe" "$R1"' + goto done + Exec '"notepad.exe" "$R3"' + goto done + + ConfigRead: + ${ConfigRead} "$R1" "$R2" $0 + IfErrors error send + + ConfigWrite: + ${ConfigWrite} "$R1" "$R2" "$R3" $0 + IfErrors error + MessageBox MB_YESNO " Open output file?" IDNO send + Exec '"notepad.exe" "$R1"' + goto send + + FileRecode: + ${FileRecode} "$R1" "$R2" + IfErrors error + MessageBox MB_YESNO " Open output file?" IDNO done + Exec '"notepad.exe" "$R1"' + goto done + + error: + StrCpy $0 'error' + goto send + + done: + StrCpy $0 'Done' + + send: + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$0" + + abort: + Abort +FunctionEnd + +Function LineFindCallback + MessageBox MB_OKCANCEL '$$R9 "Line"=[$R9]$\n$$R8 "#"=[$R8]$\n$$R7 "-#"=[$R7]$\n$$R6 "Range"=[$R6]$\n$$R5 "Read"=[$R5]$\n$$R4 "Write"=[$R4]' IDOK +2 + StrCpy $R0 StopLineFind + + Push $R0 +FunctionEnd + +Function FileReadFromEndCallback + MessageBox MB_OKCANCEL '$$9 "Line"=[$9]$\n$$8 "#"=[$8]$\n$$7 "-#"=[$7]' IDOK +2 + StrCpy $R0 StopFileReadFromEnd + + Push $R0 +FunctionEnd + +Function .onInit + InitPluginsDir + GetTempFileName $INI $PLUGINSDIR + File /oname=$INI "TextFunc.ini" +FunctionEnd + +Page instfiles + +Section -Empty +SectionEnd diff --git a/installer/NSIS/Examples/TextFuncTest.nsi b/installer/NSIS/Examples/TextFuncTest.nsi new file mode 100644 index 0000000..359cdec --- /dev/null +++ b/installer/NSIS/Examples/TextFuncTest.nsi @@ -0,0 +1,414 @@ +;_____________________________________________________________________________ +; +; Text Functions Test +;_____________________________________________________________________________ +; +; 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + +Name "Text Functions Test" +OutFile "TextFuncTest.exe" +Caption "$(^Name)" +ShowInstDetails show +XPStyle on +RequestExecutionLevel user + +Var FUNCTION +Var TEMPFILE1 +Var TEMPFILE2 +Var TEMPFILE3 +Var HANDLE +Var OUT + +!include "TextFunc.nsh" + +;############### INSTALL ############### + +!define StackVerificationStart `!insertmacro StackVerificationStart` +!macro StackVerificationStart _FUNCTION + StrCpy $FUNCTION ${_FUNCTION} + Call StackVerificationStart +!macroend + +!define StackVerificationEnd `!insertmacro StackVerificationEnd` +!macro StackVerificationEnd + Call StackVerificationEnd +!macroend + +Function StackVerificationStart + StrCpy $0 !0 + StrCpy $1 !1 + StrCpy $2 !2 + StrCpy $3 !3 + StrCpy $4 !4 + StrCpy $5 !5 + StrCpy $6 !6 + StrCpy $7 !7 + StrCpy $8 !8 + StrCpy $9 !9 + StrCpy $R0 !R0 + StrCpy $R1 !R1 + StrCpy $R2 !R2 + StrCpy $R3 !R3 + StrCpy $R4 !R4 + StrCpy $R5 !R5 + StrCpy $R6 !R6 + StrCpy $R7 !R7 + StrCpy $R8 !R8 + StrCpy $R9 !R9 +FunctionEnd + +Function StackVerificationEnd + IfErrors +3 + DetailPrint 'PASSED $FUNCTION no errors' + goto +2 + DetailPrint 'FAILED $FUNCTION error' + + StrCmp $0 '!0' 0 error + StrCmp $1 '!1' 0 error + StrCmp $2 '!2' 0 error + StrCmp $3 '!3' 0 error + StrCmp $4 '!4' 0 error + StrCmp $5 '!5' 0 error + StrCmp $6 '!6' 0 error + StrCmp $7 '!7' 0 error + StrCmp $8 '!8' 0 error + StrCmp $9 '!9' 0 error + StrCmp $R0 '!R0' 0 error + StrCmp $R1 '!R1' 0 error + StrCmp $R2 '!R2' 0 error + StrCmp $R3 '!R3' 0 error + StrCmp $R4 '!R4' 0 error + StrCmp $R5 '!R5' 0 error + StrCmp $R6 '!R6' 0 error + StrCmp $R7 '!R7' 0 error + StrCmp $R8 '!R8' 0 error + StrCmp $R9 '!R9' 0 error + DetailPrint 'PASSED $FUNCTION stack' + goto end + + error: + DetailPrint 'FAILED $FUNCTION stack' +; MessageBox MB_OKCANCEL '$$0={$0}$\n$$1={$1}$\n$$2={$2}$\n$$3={$3}$\n$$4={$4}$\n$$5={$5}$\n$$6={$6}$\n$$7={$7}$\n$$8={$8}$\n$$9={$9}$\n$$R0={$R0}$\n$$R1={$R1}$\n$$R2={$R2}$\n$$R3={$R3}$\n$$R4={$R4}$\n$$R5={$R5}$\n$$R6={$R6}$\n$$R7={$R7}$\n$$R8={$R8}$\n$$R9={$R9}' IDOK +2 +; quit + + end: +FunctionEnd + + + +Section CreateTestFile + GetTempFileName $TEMPFILE1 + FileOpen $HANDLE $TEMPFILE1 w + FileWrite $HANDLE '1A=a$\r$\n' + FileWrite $HANDLE '2B=b$\r$\n' + FileWrite $HANDLE '3C=c$\r$\n' + FileWrite $HANDLE '4D=d$\r$\n' + FileWrite $HANDLE '5E=e$\r$\n' + FileClose $HANDLE + GetTempFileName $TEMPFILE2 + GetTempFileName $TEMPFILE3 +SectionEnd + + +Section LineFind + ${StackVerificationStart} LineFind + + ${LineFind} '$TEMPFILE1' '/NUL' '1:-4 3 -1' 'LineFindCallback1' + IfErrors error + StrCmp $OUT '|1:2|-5|1|1A=a$\r$\n|1:2|-4|2|2B=b$\r$\n|3:3|-3|3|3C=c$\r$\n' 0 error + + StrCpy $OUT '' + SetDetailsPrint none + ${LineFind} '$TEMPFILE1' '$TEMPFILE2' '1:-1' 'LineFindCallback2' + SetDetailsPrint both + IfErrors error + StrCmp $OUT '|1:-1||1|1A=a$\r$\n|1:-1||2|4D=d$\r$\n|1:-1||3|3C=c$\r$\n|1:-1||4|2B=B$\r$\n|1:-1||5|5E=e$\r$\n' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + +Function LineFindCallback1 + StrCpy $OUT '$OUT|$R6|$R7|$R8|$R9' + StrCmp $R8 3 0 +2 + StrCpy $0 StopLineFind + + Push $0 +FunctionEnd + +Function LineFindCallback2 + StrCmp $R8 2 0 +2 + StrCpy $R9 '4D=d$\r$\n' + StrCmp $R8 4 0 +2 + StrCpy $R9 '2B=B$\r$\n' + + StrCpy $OUT '$OUT|$R6|$R7|$R8|$R9' + + Push $0 +FunctionEnd + + +Section LineRead + ${StackVerificationStart} LineRead + + ${LineRead} '$TEMPFILE1' '-1' $OUT + IfErrors error + StrCmp $OUT '5E=e$\r$\n' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section FileReadFromEnd + ${StackVerificationStart} FileReadFromEnd + + StrCpy $OUT '' + ${FileReadFromEnd} '$TEMPFILE1' 'FileReadFromEndCallback' + IfErrors error + StrCmp $OUT '|-1|5|5E=e$\r$\n|-2|4|4D=d$\r$\n|-3|3|3C=c$\r$\n|-4|2|2B=b$\r$\n' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + +Function FileReadFromEndCallback + StrCpy $OUT '$OUT|$7|$8|$9' + StrCmp $8 2 0 +2 + StrCpy $0 StopFileReadFromEnd + + Push $0 +FunctionEnd + + +Section LineSum + ${StackVerificationStart} LineSum + + ${LineSum} '$TEMPFILE1' $OUT + IfErrors error + StrCmp $OUT '5' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section FileJoin + ${StackVerificationStart} FileJoin + + SetDetailsPrint none + ${FileJoin} '$TEMPFILE1' '$TEMPFILE2' '$TEMPFILE3' + SetDetailsPrint both + + ${StackVerificationEnd} +SectionEnd + + +Section TextCompare + ${StackVerificationStart} TextCompare + + StrCpy $OUT '' + ${TextCompare} '$TEMPFILE1' '$TEMPFILE2' 'FastDiff' 'TextCompareCallback' + StrCmp $OUT '|2|4D=d$\r$\n|2|2B=b$\r$\n|4|2B=B$\r$\n|4|4D=d$\r$\n' 0 error + + StrCpy $OUT '' + ${TextCompare} '$TEMPFILE1' '$TEMPFILE2' 'FastEqual' 'TextCompareCallback' + StrCmp $OUT '|1|1A=a$\r$\n|1|1A=a$\r$\n|3|3C=c$\r$\n|3|3C=c$\r$\n|5|5E=e$\r$\n|5|5E=e$\r$\n' 0 error + + StrCpy $OUT '' + ${TextCompare} '$TEMPFILE1' '$TEMPFILE2' 'SlowDiff' 'TextCompareCallback' + StrCmp $OUT '' 0 error + + StrCpy $OUT '' + ${TextCompare} '$TEMPFILE1' '$TEMPFILE2' 'SlowEqual' 'TextCompareCallback' + StrCmp $OUT '|1|1A=a$\r$\n|1|1A=a$\r$\n|4|2B=B$\r$\n|2|2B=b$\r$\n|3|3C=c$\r$\n|3|3C=c$\r$\n|2|4D=d$\r$\n|4|4D=d$\r$\n|5|5E=e$\r$\n|5|5E=e$\r$\n' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + +Section TextCompareS + ${StackVerificationStart} TextCompareS + + StrCpy $OUT '' + ${TextCompareS} '$TEMPFILE1' '$TEMPFILE2' 'SlowDiff' 'TextCompareCallback' + StrCmp $OUT '|||2|2B=b$\r$\n' 0 error + + StrCpy $OUT '' + ${TextCompareS} '$TEMPFILE1' '$TEMPFILE2' 'SlowEqual' 'TextCompareCallback' + StrCmp $OUT '|1|1A=a$\r$\n|1|1A=a$\r$\n|3|3C=c$\r$\n|3|3C=c$\r$\n|2|4D=d$\r$\n|4|4D=d$\r$\n|5|5E=e$\r$\n|5|5E=e$\r$\n' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + +Function TextCompareCallback + StrCpy $OUT '$OUT|$6|$7|$8|$9' + + Push $0 +FunctionEnd + + +Section ConfigRead + ${StackVerificationStart} ConfigRead + + ${ConfigRead} '$TEMPFILE1' '3c=' $OUT + StrCmp $OUT 'c' 0 error + + ${ConfigRead} '$TEMPFILE1' '6F=' $OUT + StrCmp $OUT '' 0 error + + ${ConfigRead} '$TEMPFILE1' 'FF=' $OUT + IfErrors 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section ConfigReadS + ${StackVerificationStart} ConfigReadS + + ${ConfigReadS} '$TEMPFILE1' '3C=' $OUT + StrCmp $OUT 'c' 0 error + + ${ConfigReadS} '$TEMPFILE1' '3c=' $OUT + IfErrors 0 error + StrCmp $OUT '' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section ConfigWrite + ${StackVerificationStart} ConfigWrite + + ${ConfigWrite} '$TEMPFILE1' '5E=' 'e**' $OUT + StrCmp $OUT 'CHANGED' 0 error + + ${ConfigWrite} '$TEMPFILE1' '2B=' '' $OUT + StrCmp $OUT 'DELETED' 0 error + + ${ConfigWrite} '$TEMPFILE1' '3c=' 'c' $OUT + StrCmp $OUT 'SAME' 0 error + + ${ConfigWrite} '$TEMPFILE1' '6F=' '*' $OUT + StrCmp $OUT 'ADDED' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section ConfigWriteS + ${StackVerificationStart} ConfigWriteS + + ${ConfigWriteS} '$TEMPFILE1' '5e=' 'e**' $OUT + StrCmp $OUT 'ADDED' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section FileRecode + ${StackVerificationStart} FileRecode + + ${FileRecode} '$TEMPFILE1' 'CharToOem' + + ${StackVerificationEnd} +SectionEnd + + +Section TrimNewLines + ${StackVerificationStart} TrimNewLines + + ${TrimNewLines} 'Text Line$\r$\n' $OUT + StrCmp $OUT 'Text Line' 0 error + + ${TrimNewLines} 'Text Line' $OUT + StrCmp $OUT 'Text Line' 0 error + + ${TrimNewLines} 'Text Line$\n' $OUT + StrCmp $OUT 'Text Line' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WriteUninstaller + SetDetailsPrint none + Delete $TEMPFILE1 + Delete $TEMPFILE2 + Delete $TEMPFILE3 + SetDetailsPrint both + goto +2 + WriteUninstaller '$EXEDIR\un.TextFuncTest.exe' +SectionEnd + + + +;############### UNINSTALL ############### + +Section un.Uninstall + ${LineFind} '$TEMPFILE1' '/NUL' '1:-1' 'un.LineFindCallback' + ${LineRead} '$TEMPFILE1' '-1' $OUT + ${FileReadFromEnd} '$TEMPFILE1' 'un.FileReadFromEndCallback' + ${LineSum} '$TEMPFILE1' $OUT + ${FileJoin} '$TEMPFILE1' '$TEMPFILE2' '$TEMPFILE3' + ${TextCompare} '$TEMPFILE1' '$TEMPFILE2' 'FastDiff' 'un.TextCompareCallback' + ${TextCompareS} '$TEMPFILE1' '$TEMPFILE2' 'FastDiff' 'un.TextCompareCallback' + ${ConfigRead} '$TEMPFILE1' '3c=' $OUT + ${ConfigReadS} '$TEMPFILE1' '3c=' $OUT + ${ConfigWrite} '$TEMPFILE1' '5E=' 'e**' $OUT + ${ConfigWriteS} '$TEMPFILE1' '5E=' 'e**' $OUT + ${FileRecode} '$TEMPFILE1' 'CharToOem' + ${TrimNewLines} 'Text Line$\r$\n' $OUT +SectionEnd + +Function un.LineFindCallback + Push $0 +FunctionEnd + +Function un.FileReadFromEndCallback + Push $0 +FunctionEnd + +Function un.TextCompareCallback + Push $0 +FunctionEnd diff --git a/installer/NSIS/Examples/UserInfo/UserInfo.nsi b/installer/NSIS/Examples/UserInfo/UserInfo.nsi new file mode 100644 index 0000000..e05a161 --- /dev/null +++ b/installer/NSIS/Examples/UserInfo/UserInfo.nsi @@ -0,0 +1,44 @@ +Name "UserInfo.dll test" +OutFile UserInfo.exe + +!define REALMSG "$\nOriginal non-restricted account type: $2" + +Section + ClearErrors + UserInfo::GetName + IfErrors Win9x + Pop $0 + UserInfo::GetAccountType + Pop $1 + # GetOriginalAccountType will check the tokens of the original user of the + # current thread/process. If the user tokens were elevated or limited for + # this process, GetOriginalAccountType will return the non-restricted + # account type. + # On Vista with UAC, for example, this is not the same value when running + # with `RequestExecutionLevel user`. GetOriginalAccountType will return + # "admin" while GetAccountType will return "user". + UserInfo::GetOriginalAccountType + Pop $2 + StrCmp $1 "Admin" 0 +3 + MessageBox MB_OK 'User "$0" is in the Administrators group${REALMSG}' + Goto done + StrCmp $1 "Power" 0 +3 + MessageBox MB_OK 'User "$0" is in the Power Users group${REALMSG}' + Goto done + StrCmp $1 "User" 0 +3 + MessageBox MB_OK 'User "$0" is just a regular user${REALMSG}' + Goto done + StrCmp $1 "Guest" 0 +3 + MessageBox MB_OK 'User "$0" is a guest${REALMSG}' + Goto done + MessageBox MB_OK "Unknown error" + Goto done + + Win9x: + # This one means you don't need to care about admin or + # not admin because Windows 9x doesn't either + MessageBox MB_OK "Error! This DLL can't run under Windows 9x!" + + done: +SectionEnd + diff --git a/installer/NSIS/Examples/UserVars.nsi b/installer/NSIS/Examples/UserVars.nsi new file mode 100644 index 0000000..784b52b --- /dev/null +++ b/installer/NSIS/Examples/UserVars.nsi @@ -0,0 +1,69 @@ +; UserVars.nsi +; +; This script shows you how to declare and user variables. + +;-------------------------------- + + Name "User Variables Text" + OutFile "UserVars.exe" + + InstallDir "$PROGRAMFILES\User Variables Test" + + RequestExecutionLevel admin + +;-------------------------------- + + ;Pages + Page directory + Page instfiles + + UninstPage uninstConfirm + UninstPage instfiles + +;-------------------------------- +; Declaration of user variables (Var command), allowed charaters for variables names : [a-z][A-Z][0-9] and '_' + + Var "Name" + Var "Serial" + Var "Info" + +;-------------------------------- +; Installer + +Section "Dummy Section" SecDummy + + StrCpy $0 "Admin" + StrCpy "$Name" $0 + StrCpy "$Serial" "12345" + MessageBox MB_OK "User Name: $Name $\n$\nSerial Number: $Serial" + + CreateDirectory $INSTDIR + WriteUninstaller "$INSTDIR\Uninst.exe" + +SectionEnd + +Section "Another Section" + + Var /GLOBAL "AnotherVar" + + StrCpy $AnotherVar "test" + +SectionEnd + +;-------------------------------- +; Uninstaller + +Section "Uninstall" + + StrCpy $Info "User variables test uninstalled successfully." + Delete "$INSTDIR\Uninst.exe" + RmDir $INSTDIR + +SectionEnd + +Function un.OnUninstSuccess + + HideWindow + MessageBox MB_OK "$Info" + +FunctionEnd diff --git a/installer/NSIS/Examples/VPatch/example.nsi b/installer/NSIS/Examples/VPatch/example.nsi new file mode 100644 index 0000000..081420e --- /dev/null +++ b/installer/NSIS/Examples/VPatch/example.nsi @@ -0,0 +1,62 @@ +;VPatch example +;Written by Joost Verburg + +;-------------------------------- + +; The name of the installer +Name "VPatch Test" + +; The file to write +OutFile "vpatchtest.exe" + +; The default installation directory +InstallDir "$PROGRAMFILES\VPatch Test" + +; The text to prompt the user to enter a directory +DirText "Choose a folder in which to install the VPatch Test!" + +; Show details +ShowInstDetails show + +;-------------------------------- +; The normal way to use VPatch +;-------------------------------- +!include "VPatchLib.nsh" + +Section "Update file" + ; Set output path to the installation directory + SetOutPath $INSTDIR + + ; Extract the old file under name 'updatefile.txt' + File /oname=updatefile.txt oldfile.txt + + ; Update the file - it will be replaced with the new version + DetailPrint "Updating updatefile.txt using patch..." + !insertmacro VPatchFile "patch.pat" "$INSTDIR\updatefile.txt" "$INSTDIR\temporaryfile.txt" + +SectionEnd + +;------------------------------- +; The hard way to use VPatch +;------------------------------- +Section "New version in separate file" + + ; Set output path to the installation directory + SetOutPath $INSTDIR + + ; Extract the old file + File oldfile.txt + + ; Extract the patch to the plug-ins folder (temporary) + InitPluginsDir + File /oname=$PLUGINSDIR\patch.pat patch.pat + + ; Update the old file to the new file using the patch + DetailPrint "Updating oldfile.txt using patch to newfile.txt..." + vpatch::vpatchfile "$PLUGINSDIR\patch.pat" "$INSTDIR\oldfile.txt" "$INSTDIR\newfile.txt" + + ; Show result + Pop $R0 + DetailPrint "Result: $R0" + +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/VPatch/newfile.txt b/installer/NSIS/Examples/VPatch/newfile.txt new file mode 100644 index 0000000..2369a70 --- /dev/null +++ b/installer/NSIS/Examples/VPatch/newfile.txt @@ -0,0 +1,6 @@ +*** THIS IS A TEST FILE FOR THE VPATCH EXAMPLE *** +*** COMPILE EXAMPLE.NSI TO TEST *** + +newfile - vpatch + +67890 - GHIJKL \ No newline at end of file diff --git a/installer/NSIS/Examples/VPatch/oldfile.txt b/installer/NSIS/Examples/VPatch/oldfile.txt new file mode 100644 index 0000000..a378cf3 --- /dev/null +++ b/installer/NSIS/Examples/VPatch/oldfile.txt @@ -0,0 +1,6 @@ +*** THIS IS A TEST FILE FOR THE VPATCH EXAMPLE *** +*** COMPILE EXAMPLE.NSI TO TEST *** + +oldfile - vpatch + +12345 - ABCDEF \ No newline at end of file diff --git a/installer/NSIS/Examples/VPatch/patch.pat b/installer/NSIS/Examples/VPatch/patch.pat new file mode 100644 index 0000000..256dfac Binary files /dev/null and b/installer/NSIS/Examples/VPatch/patch.pat differ diff --git a/installer/NSIS/Examples/VersionInfo.nsi b/installer/NSIS/Examples/VersionInfo.nsi new file mode 100644 index 0000000..1ff449d --- /dev/null +++ b/installer/NSIS/Examples/VersionInfo.nsi @@ -0,0 +1,29 @@ +; VersionInfo.nsi +; +; This script shows you how to add version information to an installer. +; Windows shows this information on the Version tab of the File properties. + +;-------------------------------- + +Name "Version Info" + +OutFile "VersionInfo.exe" + +LoadLanguageFile "${NSISDIR}\Contrib\Language files\English.nlf" +;-------------------------------- +;Version Information + + VIProductVersion "1.2.3.4" + VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "Test Application" + VIAddVersionKey /LANG=${LANG_ENGLISH} "Comments" "A test comment" + VIAddVersionKey /LANG=${LANG_ENGLISH} "CompanyName" "Fake company" + VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalTrademarks" "Test Application is a trademark of Fake company" + VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "Copyright Fake company" + VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "Test Application" + VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "1.2.3" + +;-------------------------------- + +Section "" + +SectionEnd diff --git a/installer/NSIS/Examples/WordFunc.ini b/installer/NSIS/Examples/WordFunc.ini new file mode 100644 index 0000000..38c748d --- /dev/null +++ b/installer/NSIS/Examples/WordFunc.ini @@ -0,0 +1,107 @@ +[Settings] +NumFields=13 +NextButtonText=&Enter + +[Field 1] +Type=Droplist +Flags=NOTIFY +State=1. WordFind (Find word by number) +ListItems=1. WordFind (Find word by number)| (Delimiter exclude)| (Sum of words)| (Sum of delimiters)| (Find word number)| ( }} )| ( {} )| ( *} )|2. WordFind2X|3. WordReplace (Replace)| (Delete)| (Multiple-replace)|4. WordAdd (Add)| (Delete) |5. WordInsert|6. StrFilter (UpperCase)| (LowerCase)| (Filter)|7. VersionCompare|8. VersionConvert +Left=44 +Right=190 +Top=10 +Bottom=191 + +[Field 2] +Type=Text +State=C:\io.sys|C:\logo.sys|C:\Program Files|C:\WINDOWS +Left=44 +Right=-10 +Top=30 +Bottom=41 + +[Field 3] +Type=Text +State=|C:\ +Left=44 +Right=-10 +Top=46 +Bottom=59 + +[Field 4] +Type=Text +Flags=DISABLED +Left=44 +Right=-10 +Top=62 +Bottom=75 + +[Field 5] +Type=Text +State=-4 +Left=44 +Right=-10 +Top=80 +Bottom=92 + +[Field 6] +Type=Text +Left=10 +Right=-30 +Top=108 +Bottom=120 + +[Field 7] +Type=Text +Left=-22 +Right=-10 +Top=108 +Bottom=120 + +[Field 8] +Type=Label +Text=String +Left=10 +Right=43 +Top=32 +Bottom=44 + +[Field 9] +Type=Label +Text=Delimiter +Left=10 +Right=43 +Top=48 +Bottom=60 + +[Field 10] +Type=Label +Left=10 +Right=44 +Top=65 +Bottom=76 + +[Field 11] +Type=Label +Text=Word # +Left=10 +Right=43 +Top=81 +Bottom=94 + +[Field 12] +Type=Label +Text=Result (Word): +Left=10 +Right=236 +Top=97 +Bottom=110 + +[Field 13] +Type=Label +Text=EL +Left=-21 +Right=-10 +Top=97 +Bottom=110 + diff --git a/installer/NSIS/Examples/WordFunc.nsi b/installer/NSIS/Examples/WordFunc.nsi new file mode 100644 index 0000000..a0e9f99 --- /dev/null +++ b/installer/NSIS/Examples/WordFunc.nsi @@ -0,0 +1,535 @@ +;_____________________________________________________________________________ +; +; Word Functions +;_____________________________________________________________________________ +; +; 2005 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + +Name "Word Functions" +OutFile "WordFunc.exe" +Caption "$(^Name)" +XPStyle on +RequestExecutionLevel user + +Var INI +Var HWND +Var STATE + +!include "WinMessages.nsh" +!include "WordFunc.nsh" + +Page Custom ShowCustom LeaveCustom + +Function ShowCustom + InstallOptions::initDialog "$INI" + Pop $hwnd + InstallOptions::show + Pop $0 +FunctionEnd + +Function LeaveCustom + ReadINIStr $0 $INI "Settings" "State" + StrCmp $0 0 Enter + + GetDlgItem $1 $HWND 1202 + EnableWindow $1 1 + GetDlgItem $1 $HWND 1203 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1204 + ShowWindow $1 1 + GetDlgItem $1 $HWND 1206 + EnableWindow $1 1 + GetDlgItem $1 $HWND 1205 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1206 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + + ReadINIStr $0 $INI "Field 1" "State" + StrCmp $0 "1. WordFind (Find word by number)" 0 WordFind2Send + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|C:\" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:-4" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Word):" + goto WordFindSend + + WordFind2Send: + StrCmp $0 " (Delimiter exclude)" 0 WordFind3Send + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|C:\" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E-2{" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Before{ or }after delimiter):" + goto WordFindSend + + WordFind3Send: + StrCmp $0 " (Sum of words)" 0 WordFind4Send + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|C:\" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:#" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Sum of words):" + goto WordFindSend + + WordFind4Send: + StrCmp $0 " (Sum of delimiters)" 0 WordFind5Send + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E*" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Option" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Sum of delimiters):" + goto WordFindSend + + WordFind5Send: + StrCmp $0 " (Find word number)" 0 WordFind6Send + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|C:\" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:/Program Files" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:/Word" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Word #):" + goto WordFindSend + + WordFind6Send: + StrCmp $0 " ( }} )" 0 WordFind7Send + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|C:\" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E+2}}" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Before{{ or }}after word):" + goto WordFindSend + + WordFind7Send: + StrCmp $0 " ( {} )" 0 WordFind8Send + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|C:\" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:+2{}" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Without word):" + goto WordFindSend + + WordFind8Send: + StrCmp $0 " ( *} )" 0 WordFind2XSend + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|C:\" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E+2*}" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Before{* or *}after word with word):" + goto WordFindSend + + WordFind2XSend: + StrCmp $0 "2. WordFind2X" 0 WordReplace1Send + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:[C:\io.sys];[C:\logo.sys];[C:\WINDOWS]" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:[C:\" + GetDlgItem $1 $HWND 1203 + EnableWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:];" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E+2" + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:String" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Delimiter1" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Delimiter2" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (Word):" + abort + + WordReplace1Send: + StrCmp $0 "3. WordReplace (Replace)" 0 WordReplace2Send + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:C:\io.sys|C:\logo.sys|C:\WINDOWS" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:SYS" + GetDlgItem $1 $HWND 1203 + SendMessage $1 ${WM_SETTEXT} 1 "STR:bmp" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:+2" + goto WordReplaceSend + + WordReplace2Send: + StrCmp $0 " (Delete)" 0 WordReplace3Send + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:C:\io.sys|C:\logo.sys|C:\WINDOWS" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:SYS" + GetDlgItem $1 $HWND 1203 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E+" + goto WordReplaceSend + + WordReplace3Send: + StrCmp $0 " (Multiple-replace)" 0 WordAdd1Send + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:C:\io.sys||||||C:\logo.sys|||C:\WINDOWS" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|" + GetDlgItem $1 $HWND 1203 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:+1*" + goto WordReplaceSend + + WordAdd1Send: + StrCmp $0 "4. WordAdd (Add)" 0 WordAdd2Send + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:+C:\WINDOWS|C:\config.sys|C:\IO.SYS" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (String1 + String2):" + goto WordAddSend + + WordAdd2Send: + StrCmp $0 " (Delete) " 0 WordInsertSend + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E-C:\WINDOWS|C:\config.sys|C:\IO.SYS" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (String1 - String2):" + goto WordAddSend + + WordInsertSend: + StrCmp $0 "5. WordInsert" 0 StrFilter1Send + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:C:\io.sys|C:\WINDOWS" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|" + GetDlgItem $1 $HWND 1203 + EnableWindow $1 1 + SendMessage $1 ${WM_SETTEXT} 1 "STR:C:\logo.sys" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:E+2" + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:String" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Delimiter" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result:" + abort + + StrFilter1Send: + StrCmp $0 "6. StrFilter (UpperCase)" 0 StrFilter2Send + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:123abc 456DEF 7890|%#" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:+" + GetDlgItem $1 $HWND 1203 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (String in uppercase):" + goto StrFilterSend + + StrFilter2Send: + StrCmp $0 " (LowerCase)" 0 StrFilter3Send + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:123abc 456DEF 7890|%#" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:-" + GetDlgItem $1 $HWND 1203 + SendMessage $1 ${WM_SETTEXT} 1 "STR:ef" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (String in lowercase except EF):" + goto StrFilterSend + + StrFilter3Send: + StrCmp $0 " (Filter)" 0 VersionCompareSend + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:123abc 456DEF 7890|%#" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:+12" + GetDlgItem $1 $HWND 1203 + SendMessage $1 ${WM_SETTEXT} 1 "STR:b" + GetDlgItem $1 $HWND 1204 + SendMessage $1 ${WM_SETTEXT} 1 "STR:def" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (String Digits + Letters + b - def):" + goto StrFilterSend + + VersionCompareSend: + StrCmp $0 "7. VersionCompare" 0 VersionConvertSend + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:1.1.1.9" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:1.1.1.01" + GetDlgItem $1 $HWND 1203 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1204 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1206 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Version1" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Version2" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (0-equal 1-newer 2-older):" + abort + + VersionConvertSend: + StrCmp $0 "8. VersionConvert" 0 Abort + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:9.0c" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1203 + ShowWindow $1 0 + GetDlgItem $1 $HWND 1204 + ShowWindow $1 0 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1206 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Version" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:CharList" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result (numerical version format):" + abort + + Abort: + Abort + + WordFindSend: + GetDlgItem $1 $HWND 1203 + EnableWindow $1 0 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:C:\io.sys|C:\logo.sys|C:\Program Files|C:\WINDOWS" + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:String" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Delimiter" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + Abort + + WordReplaceSend: + GetDlgItem $1 $HWND 1203 + EnableWindow $1 1 + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:String" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Replace it" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR: with" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Word #" + GetDlgItem $1 $HWND 1211 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Result:" + Abort + + WordAddSend: + GetDlgItem $1 $HWND 1203 + EnableWindow $1 0 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1201 + SendMessage $1 ${WM_SETTEXT} 1 "STR:C:\io.sys|C:\logo.sys|C:\WINDOWS" + GetDlgItem $1 $HWND 1202 + SendMessage $1 ${WM_SETTEXT} 1 "STR:|" + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:String1" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Delimiter" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:String2" + Abort + + StrFilterSend: + GetDlgItem $1 $HWND 1203 + EnableWindow $1 1 + GetDlgItem $1 $HWND 1206 + EnableWindow $1 0 + GetDlgItem $1 $HWND 1207 + SendMessage $1 ${WM_SETTEXT} 1 "STR:String" + GetDlgItem $1 $HWND 1208 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Filter" + GetDlgItem $1 $HWND 1209 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Include" + GetDlgItem $1 $HWND 1210 + SendMessage $1 ${WM_SETTEXT} 1 "STR:Exclude" + Abort + +;=Enter= + Enter: + StrCpy $0 '' + ReadINIStr $STATE $INI "Field 1" "State" + ReadINIStr $R1 $INI "Field 2" "State" + ReadINIStr $R2 $INI "Field 3" "State" + ReadINIStr $R3 $INI "Field 4" "State" + ReadINIStr $R4 $INI "Field 5" "State" + + StrCmp $STATE "1. WordFind (Find word by number)" WordFind + StrCmp $STATE " (Delimiter exclude)" WordFind + StrCmp $STATE " (Find in string)" WordFind + StrCmp $STATE " (Sum of words)" WordFind + StrCmp $STATE " (Sum of delimiters)" WordFind + StrCmp $STATE " (Find word number)" WordFind + StrCmp $STATE " ( }} )" WordFind + StrCmp $STATE " ( {} )" WordFind + StrCmp $STATE " ( *} )" WordFind + StrCmp $STATE "2. WordFind2X" WordFind2X + StrCmp $STATE "3. WordReplace (Replace)" WordReplace + StrCmp $STATE " (Delete)" WordReplace + StrCmp $STATE " (Multiple-replace)" WordReplace + StrCmp $STATE "4. WordAdd (Add)" WordAdd + StrCmp $STATE " (Delete) " WordAdd + StrCmp $STATE "5. WordInsert" WordInsert + StrCmp $STATE "6. StrFilter (UpperCase)" StrFilter + StrCmp $STATE " (LowerCase)" StrFilter + StrCmp $STATE " (Filter)" StrFilter + StrCmp $STATE "7. VersionCompare" VersionCompare + StrCmp $STATE "8. VersionConvert" VersionConvert + Abort + + WordFind: + ${WordFind} "$R1" "$R2" "$R4" $R0 + IfErrors 0 Send + StrCpy $0 $R0 + StrCmp $R0 3 0 +3 + StrCpy $3 '"+1" "-1" "+1}" "+1{" "#" "/word"' + goto error3 + StrCmp $R0 2 0 error1 + StrCpy $R4 $R4 '' 1 + StrCpy $1 $R4 1 + StrCmp $1 / 0 error2 + StrCpy $R4 $R4 '' 1 + StrCpy $R0 '"$R4" no such word.' + goto Send + + WordFind2X: + ${WordFind2X} "$R1" "$R2" "$R3" "$R4" $R0 + IfErrors 0 Send + StrCpy $0 $R0 + StrCmp $R0 3 0 +3 + StrCpy $3 '"+1" "-1"' + goto error3 + StrCmp $R0 2 +3 + StrCpy $R0 '"$R2...$R3" no words found.' + goto Send + StrCpy $R4 $R4 '' 1 + StrCpy $1 $R4 1 + StrCmp $1 / 0 +2 + StrCpy $R4 $R4 '' 1 + StrCpy $R0 '"$R4" no such word.' + goto Send + + WordReplace: + ${WordReplace} "$R1" "$R2" "$R3" "$R4" $R0 + IfErrors 0 Send + StrCpy $0 $R0 + StrCmp $R0 3 0 +3 + StrCpy $3 '"+1" "+1*" "+" "+*" "{}"' + goto error3 + StrCmp $R0 2 0 error1 + StrCpy $R4 $R4 '' 1 + goto error2 + + WordAdd: + ${WordAdd} "$R1" "$R2" "$R4" $R0 + IfErrors 0 Send + StrCpy $0 $R0 + StrCmp $R0 3 0 error1empty + StrCpy $3 '"+text" "-text"' + goto error3 + + WordInsert: + ${WordInsert} "$R1" "$R2" "$R3" "$R4" $R0 + IfErrors 0 Send + StrCpy $0 $R0 + StrCmp $R0 3 0 +3 + StrCpy $3 '"+1" "-1"' + goto error3 + StrCmp $R0 2 0 error1empty + StrCpy $R4 $R4 '' 1 + goto error2 + + StrFilter: + ${StrFilter} "$R1" "$R2" "$R3" "$R4" $R0 + IfErrors 0 Send + StrCpy $R0 'Syntax error' + goto Send + + VersionCompare: + ${VersionCompare} "$R1" "$R2" $R0 + goto Send + + VersionConvert: + ${VersionConvert} "$R1" "$R2" $R0 + goto Send + + error3: + StrCpy $R0 '"$R4" syntax error ($3)' + goto Send + error2: + StrCpy $R0 '"$R4" no such word number' + goto Send + error1empty: + StrCpy $R0 '"$R2" delimiter is empty' + goto Send + error1: + StrCpy $R0 '"$R2" delimiter not found in string' + goto Send + + Send: + GetDlgItem $1 $HWND 1205 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$R0" + GetDlgItem $1 $HWND 1206 + SendMessage $1 ${WM_SETTEXT} 1 "STR:$0" + abort +FunctionEnd + +Function .onInit + InitPluginsDir + GetTempFileName $INI $PLUGINSDIR + File /oname=$INI "WordFunc.ini" +FunctionEnd + +Page instfiles + +Section "Empty" +SectionEnd diff --git a/installer/NSIS/Examples/WordFuncTest.nsi b/installer/NSIS/Examples/WordFuncTest.nsi new file mode 100644 index 0000000..e9d1160 --- /dev/null +++ b/installer/NSIS/Examples/WordFuncTest.nsi @@ -0,0 +1,610 @@ +;_____________________________________________________________________________ +; +; Word Functions Test +;_____________________________________________________________________________ +; +; 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + +Name "Word Functions Test" +OutFile "WordFuncTest.exe" +Caption "$(^Name)" +ShowInstDetails show +XPStyle on +RequestExecutionLevel user + +Var FUNCTION +Var OUT + +!include "WordFunc.nsh" + +;############### INSTALL ############### + +!define StackVerificationStart `!insertmacro StackVerificationStart` +!macro StackVerificationStart _FUNCTION + StrCpy $FUNCTION ${_FUNCTION} + Call StackVerificationStart +!macroend + +!define StackVerificationEnd `!insertmacro StackVerificationEnd` +!macro StackVerificationEnd + Call StackVerificationEnd +!macroend + +Function StackVerificationStart + StrCpy $0 !0 + StrCpy $1 !1 + StrCpy $2 !2 + StrCpy $3 !3 + StrCpy $4 !4 + StrCpy $5 !5 + StrCpy $6 !6 + StrCpy $7 !7 + StrCpy $8 !8 + StrCpy $9 !9 + StrCpy $R0 !R0 + StrCpy $R1 !R1 + StrCpy $R2 !R2 + StrCpy $R3 !R3 + StrCpy $R4 !R4 + StrCpy $R5 !R5 + StrCpy $R6 !R6 + StrCpy $R7 !R7 + StrCpy $R8 !R8 + StrCpy $R9 !R9 +FunctionEnd + +Function StackVerificationEnd + IfErrors +3 + DetailPrint 'PASSED $FUNCTION no errors' + goto +2 + DetailPrint 'FAILED $FUNCTION error' + + StrCmp $0 '!0' 0 error + StrCmp $1 '!1' 0 error + StrCmp $2 '!2' 0 error + StrCmp $3 '!3' 0 error + StrCmp $4 '!4' 0 error + StrCmp $5 '!5' 0 error + StrCmp $6 '!6' 0 error + StrCmp $7 '!7' 0 error + StrCmp $8 '!8' 0 error + StrCmp $9 '!9' 0 error + StrCmp $R0 '!R0' 0 error + StrCmp $R1 '!R1' 0 error + StrCmp $R2 '!R2' 0 error + StrCmp $R3 '!R3' 0 error + StrCmp $R4 '!R4' 0 error + StrCmp $R5 '!R5' 0 error + StrCmp $R6 '!R6' 0 error + StrCmp $R7 '!R7' 0 error + StrCmp $R8 '!R8' 0 error + StrCmp $R9 '!R9' 0 error + DetailPrint 'PASSED $FUNCTION stack' + goto end + + error: + DetailPrint 'FAILED $FUNCTION stack' +; MessageBox MB_OKCANCEL '$$0={$0}$\n$$1={$1}$\n$$2={$2}$\n$$3={$3}$\n$$4={$4}$\n$$5={$5}$\n$$6={$6}$\n$$7={$7}$\n$$8={$8}$\n$$9={$9}$\n$$R0={$R0}$\n$$R1={$R1}$\n$$R2={$R2}$\n$$R3={$R3}$\n$$R4={$R4}$\n$$R5={$R5}$\n$$R6={$R6}$\n$$R7={$R7}$\n$$R8={$R8}$\n$$R9={$R9}' IDOK +2 +; quit + + end: +FunctionEnd + + + +Section WordFind + ${StackVerificationStart} WordFind + + ${WordFind} '||io.sys|||Program Files|||WINDOWS' '||' '-02' $OUT + StrCmp $OUT '|Program Files' 0 error + + ${WordFind} '||io.sys||||Program Files||||WINDOWS' '||' '-2' $OUT + StrCmp $OUT 'Program Files' 0 error + + ${WordFind} 'C:\io.sys|||logo.sys|||WINDOWS' '||' '-2}' $OUT + StrCmp $OUT '|logo.sys|||WINDOWS' 0 error + + ${WordFind} 'C:\io.sys|||logo.sys|||WINDOWS' '||' '#' $OUT + StrCmp $OUT '3' 0 error + + ${WordFind} 'C:\io.sys|||logo.sys|||WINDOWS' '||' '*' $OUT + StrCmp $OUT '2' 0 error + + ${WordFind} 'C:\io.sys|||Program Files|||WINDOWS' '||' '/|Program Files' $OUT + StrCmp $OUT '2' 0 error + + ${WordFind} 'C:\io.sys|||logo.sys|||WINDOWS' '||' '+2}}' $OUT + StrCmp $OUT '|||WINDOWS' 0 error + + ${WordFind} 'C:\io.sys|||logo.sys|||WINDOWS' '||' '+2{}' $OUT + StrCmp $OUT 'C:\io.sys|||WINDOWS' 0 error + + ${WordFind} 'C:\io.sys|||logo.sys|||WINDOWS' '||' '+2*}' $OUT + StrCmp $OUT '|logo.sys|||WINDOWS' 0 error + + ${WordFind} 'C:\\Program Files\\NSIS\\NSIS.chm' '\' '-2{*' $OUT + StrCmp $OUT 'C:\\Program Files\\NSIS' 0 error + + ${WordFind} 'C:\io.sys|||Program Files|||WINDOWS|||' '||' '-1' $OUT + StrCmp $OUT '|' 0 error + + ${WordFind} '||C:\io.sys|||logo.sys|||WINDOWS||' '||' '-1}' $OUT + StrCmp $OUT '' 0 error + + ${WordFind} '||C:\io.sys|||logo.sys|||WINDOWS||' '||' '+1{' $OUT + StrCmp $OUT '' 0 error + + ${WordFind} 'C:\io.sys|||logo.sys' '_' 'E+1' $OUT + IfErrors 0 error + StrCmp $OUT 1 0 error + + ${WordFind} 'C:\io.sys|||logo.sys|||' '\' 'E+3' $OUT + IfErrors 0 error + StrCmp $OUT 2 0 error + + ${WordFind} 'C:\io.sys|||logo.sys' '\' 'E1' $OUT + IfErrors 0 error + StrCmp $OUT 3 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordFindS + ${StackVerificationStart} WordFindS + + ${WordFindS} 'C:\io.sys|||Program Files|||WINDOWS' '||' '/|PROGRAM FILES' $OUT + StrCmp $OUT 'C:\io.sys|||Program Files|||WINDOWS' 0 error + + ${WordFindS} 'C:\io.sys|||Program Files|||WINDOWS' '||' '/|Program Files' $OUT + StrCmp $OUT '2' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordFind2X + ${StackVerificationStart} WordFind2X + + ${WordFind2X} '[C:\io.sys];[C:\logo.sys];[C:\WINDOWS]' '[C:\' '];' '+2' $OUT + StrCmp $OUT 'logo.sys' 0 error + + ${WordFind2X} 'C:\WINDOWS C:\io.sys C:\logo.sys' '\' '.' '-1' $OUT + StrCmp $OUT 'logo' 0 error + + ${WordFind2X} 'C:\WINDOWS C:\io.sys C:\logo.sys' '\' '.' '-1{{' $OUT + StrCmp $OUT 'C:\WINDOWS C:\io.sys C:' 0 error + + ${WordFind2X} 'C:\WINDOWS C:\io.sys C:\logo.sys' '\' '.' '-1{}' $OUT + StrCmp $OUT 'C:\WINDOWS C:\io.sys C:sys' 0 error + + ${WordFind2X} 'C:\WINDOWS C:\io.sys C:\logo.sys' '\' '.' '-1{*' $OUT + StrCmp $OUT 'C:\WINDOWS C:\io.sys C:\logo.' 0 error + + ${WordFind2X} 'C:\WINDOWS C:\io.sys C:\logo.sys' '\' '.' '/logo' $OUT + StrCmp $OUT '2' 0 error + + ${WordFind2X} '||a||b||c' '||' '||' 'E+1' $OUT + StrCmp $OUT 'a' 0 error + + ${WordFind2X} '[io.sys];[C:\logo.sys]' '\' '];' 'E+1' $OUT + IfErrors 0 error + StrCmp $OUT 1 0 error + + ${WordFind2X} '[io.sys];[C:\logo.sys]' '[' '];' 'E+2' $OUT + IfErrors 0 error + StrCmp $OUT 2 0 error + + ${WordFind2X} '[io.sys];[C:\logo.sys]' '\' '];' 'E2' $OUT + IfErrors 0 error + StrCmp $OUT 3 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordFind2XS + ${StackVerificationStart} WordFind2XS + + ${WordFind2XS} 'C:\WINDOWS C:\io.sys C:\logo.sys' '\' '.' '/LOGO' $OUT + StrCmp $OUT 'C:\WINDOWS C:\io.sys C:\logo.sys' 0 error + + ${WordFind2XS} 'C:\WINDOWS C:\io.sys C:\logo.sys' '\' '.' '/logo' $OUT + StrCmp $OUT '2' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordFind3X + ${StackVerificationStart} WordFind3X + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '+1' $OUT + StrCmp $OUT '1.AAB' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '-1' $OUT + StrCmp $OUT '2.BAA' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '-1{{' $OUT + StrCmp $OUT '[1.AAB];' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '-1{}' $OUT + StrCmp $OUT '[1.AAB];[3.BBB];' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '-1{*' $OUT + StrCmp $OUT '[1.AAB];[2.BAA];' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '/2.BAA' $OUT + StrCmp $OUT '2' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'XX' '];' 'E+1' $OUT + IfErrors 0 error + StrCmp $OUT '1' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' 'E+3' $OUT + IfErrors 0 error + StrCmp $OUT '2' 0 error + + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' 'E3' $OUT + IfErrors 0 error + StrCmp $OUT '3' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordFind3XS + ${StackVerificationStart} WordFind3XS + + ${WordFind3XS} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '/2.baa' $OUT + StrCmp $OUT '[1.AAB];[2.BAA];[3.BBB];' 0 error + + ${WordFind3XS} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '/2.BAA' $OUT + StrCmp $OUT '2' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordReplace + ${StackVerificationStart} WordReplace + + ${WordReplace} 'C:\io.sys C:\logo.sys C:\WINDOWS' 'SYS' 'bmp' '+2' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.bmp C:\WINDOWS' 0 error + + ${WordReplace} 'C:\io.sys C:\logo.sys C:\WINDOWS' 'SYS' '' '+' $OUT + StrCmp $OUT 'C:\io. C:\logo. C:\WINDOWS' 0 error + + ${WordReplace} 'C:\io.sys C:\logo.sys C:\WINDOWS' 'C:\io.sys' '' '+' $OUT + StrCmp $OUT ' C:\logo.sys C:\WINDOWS' 0 error + + ${WordReplace} 'C:\io.sys C:\logo.sys C:\WINDOWS' ' ' ' ' '+1*' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.sys C:\WINDOWS' 0 error + + ${WordReplace} 'C:\io.sys C:\logo.sysSYSsys C:\WINDOWS' 'sys' 'bmp' '+*' $OUT + StrCmp $OUT 'C:\io.bmp C:\logo.bmp C:\WINDOWS' 0 error + + ${WordReplace} 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 'sys' '|' '{' $OUT + StrCmp $OUT '||C:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 0 error + + ${WordReplace} 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 'sys' '|' '}' $OUT + StrCmp $OUT 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWS|||' 0 error + + ${WordReplace} 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 'sys' '|' '{}' $OUT + StrCmp $OUT '||C:\io.sys C:\logo.sys C:\WINDOWS|||' 0 error + + ${WordReplace} 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 'sys' '|' '{*' $OUT + StrCmp $OUT '|C:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 0 error + + ${WordReplace} 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 'sys' '|' '}*' $OUT + StrCmp $OUT 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWS|' 0 error + + ${WordReplace} 'SYSsysC:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 'sys' '|' '{}*' $OUT + StrCmp $OUT '|C:\io.sys C:\logo.sys C:\WINDOWS|' 0 error + + ${WordReplace} 'sysSYSsysC:\io.sys C:\logo.sys C:\WINDOWSsysSYSsys' 'sys' '|' '{}*' $OUT + StrCmp $OUT '|C:\io.sys C:\logo.sys C:\WINDOWS|' 0 error + + ${WordReplace} 'C:\io.sys C:\logo.sys' '#sys' '|sys|' 'E+1' $OUT + IfErrors 0 error + StrCmp $OUT '1' 0 error + + ${WordReplace} 'C:\io.sys C:\logo.sys' '.sys' '|sys|' 'E+3' $OUT + IfErrors 0 error + StrCmp $OUT '2' 0 error + + ${WordReplace} 'C:\io.sys C:\logo.sys' '.sys' '|sys|' 'E3' $OUT + IfErrors 0 error + StrCmp $OUT '3' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordReplaceS + ${StackVerificationStart} WordReplaceS + + ${WordReplaceS} 'C:\io.sys C:\logo.sys C:\WINDOWS' 'SYS' 'bmp' '+2' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.sys C:\WINDOWS' 0 error + + ${WordReplaceS} 'C:\io.sys C:\logo.sys C:\WINDOWS' 'sys' 'bmp' '+2' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.bmp C:\WINDOWS' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordAdd + ${StackVerificationStart} WordAdd + + ${WordAdd} 'C:\io.sys C:\WINDOWS' ' ' '+C:\WINDOWS C:\config.sys' $OUT + StrCmp $OUT 'C:\io.sys C:\WINDOWS C:\config.sys' 0 error + + ${WordAdd} 'C:\io.sys C:\logo.sys C:\WINDOWS' ' ' '-C:\WINDOWS C:\config.sys C:\IO.SYS' $OUT + StrCmp $OUT 'C:\logo.sys' 0 error + + ${WordAdd} 'C:\io.sys' ' ' '+C:\WINDOWS C:\config.sys C:\IO.SYS' $OUT + StrCmp $OUT 'C:\io.sys C:\WINDOWS C:\config.sys' 0 error + + ${WordAdd} 'C:\io.sys C:\logo.sys C:\WINDOWS' ' ' '-C:\WINDOWS' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.sys' 0 error + + ${WordAdd} 'C:\io.sys C:\logo.sys' ' ' '+C:\logo.sys' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.sys' 0 error + + ${WordAdd} 'C:\io.sys C:\logo.sys' ' ' 'E-' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.sys' 0 error + IfErrors error + + ${WordAdd} 'C:\io.sys C:\logo.sys' '' 'E-C:\logo.sys' $OUT + IfErrors 0 error + StrCmp $OUT '1' 0 error + + ${WordAdd} 'C:\io.sys C:\logo.sys' '' 'EC:\logo.sys' $OUT + IfErrors 0 error + StrCmp $OUT '3' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordAddS + ${StackVerificationStart} WordAddS + + ${WordAddS} 'C:\io.sys C:\WINDOWS' ' ' '+C:\windows C:\config.sys' $OUT + StrCmp $OUT 'C:\io.sys C:\WINDOWS C:\windows C:\config.sys' 0 error + + ${WordAddS} 'C:\io.sys C:\WINDOWS' ' ' '+C:\WINDOWS C:\config.sys' $OUT + StrCmp $OUT 'C:\io.sys C:\WINDOWS C:\config.sys' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordInsert + ${StackVerificationStart} WordInsert + + ${WordInsert} 'C:\io.sys C:\WINDOWS' ' ' 'C:\logo.sys' '-2' $OUT + StrCmp $OUT 'C:\io.sys C:\logo.sys C:\WINDOWS' 0 error + + ${WordInsert} 'C:\io.sys' ' ' 'C:\WINDOWS' '+2' $OUT + StrCmp $OUT 'C:\io.sys C:\WINDOWS' 0 error + + ${WordInsert} '' ' ' 'C:\WINDOWS' '+1' $OUT + StrCmp $OUT 'C:\WINDOWS ' 0 error + + ${WordInsert} 'C:\io.sys C:\logo.sys' '' 'C:\logo.sys' 'E+1' $OUT + IfErrors 0 error + StrCmp $OUT '1' 0 error + + ${WordInsert} 'C:\io.sys C:\logo.sys' ' ' 'C:\logo.sys' 'E+4' $OUT + IfErrors 0 error + StrCmp $OUT '2' 0 error + + ${WordInsert} 'C:\io.sys C:\logo.sys' '' 'C:\logo.sys' 'E1' $OUT + IfErrors 0 error + StrCmp $OUT '3' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WordInsertS + ${StackVerificationStart} WordInsertS + + ${WordInsertS} 'C:\io.sys x C:\logo.sys' ' X ' 'C:\NTLDR' '+2' $OUT + StrCmp $OUT 'C:\io.sys x C:\logo.sys X C:\NTLDR' 0 error + + ${WordInsertS} 'C:\io.sys x C:\logo.sys' ' x ' 'C:\NTLDR' '+2' $OUT + StrCmp $OUT 'C:\io.sys x C:\NTLDR x C:\logo.sys' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section StrFilter + ${StackVerificationStart} StrFilter + + ${StrFilter} '123abc 456DEF 7890|%#' '+' '' '' $OUT + IfErrors error + StrCmp $OUT '123ABC 456DEF 7890|%#' 0 error + + ${StrFilter} '123abc 456DEF 7890|%#' '-' 'ef' '' $OUT + IfErrors error + StrCmp $OUT '123abc 456dEF 7890|%#' 0 error + + ${StrFilter} '123abc 456DEF 7890|%#' '2' '|%' '' $OUT + IfErrors error + StrCmp $OUT 'abcDEF|%' 0 error + + ${StrFilter} '123abc 456DEF 7890|%#' '13' 'af' '4590' $OUT + IfErrors error + StrCmp $OUT '123a 6F 78|%#' 0 error + + ${StrFilter} '123abc 456DEF 7890|%#' '+12' 'b' 'def' $OUT + IfErrors error + StrCmp $OUT '123AbC4567890' 0 error + + ${StrFilter} '123abc 456DEF 7890|%#' '+12' 'b' 'def' $OUT + IfErrors error + StrCmp $OUT '123AbC4567890' 0 error + + ${StrFilter} '123abc 456DEF 7890|%#' '123' 'b' 'def' $OUT + IfErrors 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section StrFilterS + ${StackVerificationStart} StrFilterS + + ${StrFilterS} '123abc 456DEF 7890|%#' '13' 'af' '4590' $OUT + IfErrors error + StrCmp $OUT '123a 6 78|%#' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section VersionCompare + ${StackVerificationStart} VersionCompare + + ${VersionCompare} '1.1.1.9' '1.1.1.01' $OUT + StrCmp $OUT '1' 0 error + + ${VersionCompare} '1.1.1.1' '1.1.1.10' $OUT + StrCmp $OUT '2' 0 error + + ${VersionCompare} '91.1.1.1' '101.1.1.9' $OUT + StrCmp $OUT '2' 0 error + + ${VersionCompare} '1.1.1.1' '1.1.1.1' $OUT + StrCmp $OUT '0' 0 error + + ${VersionCompare} '1.1.1.9' '1.1.1.10' $OUT + StrCmp $OUT '2' 0 error + + ${VersionCompare} '1.1.1.0' '1.1.1' $OUT + StrCmp $OUT '0' 0 error + + ${VersionCompare} '1.1.0.0' '1.1' $OUT + StrCmp $OUT '0' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section VersionConvert + ${StackVerificationStart} VersionConvert + + ${VersionConvert} '9.0a' '' $OUT + StrCmp $OUT '9.0.01' 0 error + + ${VersionConvert} '9.0c' '' $OUT + StrCmp $OUT '9.0.03' 0 error + + ${VersionConvert} '0.15c-9m' '' $OUT + StrCmp $OUT '0.15.03.9.13' 0 error + + ${VersionConvert} '0.15c+' 'abcdefghijklmnopqrstuvwxyz+' $OUT + StrCmp $OUT '0.15.0327' 0 error + + ${VersionConvert} '0.0xa12.x.ax|.|.|x|a|.3|a.4.||5.|' '' $OUT + StrCmp $OUT '0.0.2401.12.24.0124.24.01.3.01.4.5' 0 error + + goto +2 + error: + SetErrors + + ${StackVerificationEnd} +SectionEnd + + +Section WriteUninstaller + goto +2 + WriteUninstaller '$EXEDIR\un.WordFuncTest.exe' +SectionEnd + + + +;############### UNINSTALL ############### + +Section un.Uninstall + ${WordFind} 'C:\io.sys C:\Program Files C:\WINDOWS' ' C:\' '-02' $OUT + ${WordFindS} 'C:\io.sys C:\Program Files C:\WINDOWS' ' C:\' '-02' $OUT + ${WordFind2X} '[C:\io.sys];[C:\logo.sys];[C:\WINDOWS]' '[C:\' '];' '+2' $OUT + ${WordFind2XS} '[C:\io.sys];[C:\logo.sys];[C:\WINDOWS]' '[C:\' '];' '+2' $OUT + ${WordFind3X} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '+1' $OUT + ${WordFind3XS} '[1.AAB];[2.BAA];[3.BBB];' '[' 'AA' '];' '+1' $OUT + ${WordReplace} 'C:\io.sys C:\logo.sys C:\WINDOWS' 'SYS' 'bmp' '+2' $OUT + ${WordReplaceS} 'C:\io.sys C:\logo.sys C:\WINDOWS' 'SYS' 'bmp' '+2' $OUT + ${WordAdd} 'C:\io.sys C:\WINDOWS' ' ' '+C:\WINDOWS C:\config.sys' $OUT + ${WordAddS} 'C:\io.sys C:\WINDOWS' ' ' '+C:\WINDOWS C:\config.sys' $OUT + ${WordInsert} 'C:\io.sys C:\WINDOWS' ' ' 'C:\logo.sys' '-2' $OUT + ${WordInsertS} 'C:\io.sys C:\WINDOWS' ' ' 'C:\logo.sys' '-2' $OUT + ${StrFilter} '123abc 456DEF 7890|%#' '+' '' '' $OUT + ${StrFilterS} '123abc 456DEF 7890|%#' '+' '' '' $OUT + ${VersionCompare} '1.1.1.9' '1.1.1.01' $OUT + ${VersionConvert} '9.0a' '' $OUT +SectionEnd diff --git a/installer/NSIS/Examples/bigtest.nsi b/installer/NSIS/Examples/bigtest.nsi new file mode 100644 index 0000000..62f5211 --- /dev/null +++ b/installer/NSIS/Examples/bigtest.nsi @@ -0,0 +1,308 @@ +; bigtest.nsi +; +; This script attempts to test most of the functionality of the NSIS exehead. + +;-------------------------------- + +!ifdef HAVE_UPX +!packhdr tmp.dat "upx\upx -9 tmp.dat" +!endif + +!ifdef NOCOMPRESS +SetCompress off +!endif + +;-------------------------------- + +Name "BigNSISTest" +Caption "NSIS Big Test" +Icon "${NSISDIR}\Contrib\Graphics\Icons\nsis1-install.ico" +OutFile "bigtest.exe" + +SetDateSave on +SetDatablockOptimize on +CRCCheck on +SilentInstall normal +BGGradient 000000 800000 FFFFFF +InstallColors FF8080 000030 +XPStyle on + +InstallDir "$PROGRAMFILES\NSISTest\BigNSISTest" +InstallDirRegKey HKLM "Software\NSISTest\BigNSISTest" "Install_Dir" + +CheckBitmap "${NSISDIR}\Contrib\Graphics\Checks\classic-cross.bmp" + +LicenseText "A test text, make sure it's all there" +LicenseData "bigtest.nsi" + +RequestExecutionLevel admin + +;-------------------------------- + +Page license +Page components +Page directory +Page instfiles + +UninstPage uninstConfirm +UninstPage instfiles + +;-------------------------------- + +!ifndef NOINSTTYPES ; only if not defined + InstType "Most" + InstType "Full" + InstType "More" + InstType "Base" + ;InstType /NOCUSTOM + ;InstType /COMPONENTSONLYONCUSTOM +!endif + +AutoCloseWindow false +ShowInstDetails show + +;-------------------------------- + +Section "" ; empty string makes it hidden, so would starting with - + + ; write reg info + StrCpy $1 "POOOOOOOOOOOP" + DetailPrint "I like to be able to see what is going on (debug) $1" + WriteRegStr HKLM SOFTWARE\NSISTest\BigNSISTest "Install_Dir" "$INSTDIR" + + ; write uninstall strings + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BigNSISTest" "DisplayName" "BigNSISTest (remove only)" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BigNSISTest" "UninstallString" '"$INSTDIR\bt-uninst.exe"' + + SetOutPath $INSTDIR + File /a "silent.nsi" + CreateDirectory "$INSTDIR\MyProjectFamily\MyProject" ; 2 recursively create a directory for fun. + WriteUninstaller "bt-uninst.exe" + + Nop ; for fun + +SectionEnd + +Section "TempTest" + +SectionIn 1 2 3 + Start: MessageBox MB_OK "Start:" + + MessageBox MB_YESNO "Goto MyLabel" IDYES MyLabel + + MessageBox MB_OK "Right before MyLabel:" + + MyLabel: MessageBox MB_OK "MyLabel:" + + MessageBox MB_OK "Right after MyLabel:" + + MessageBox MB_YESNO "Goto Start:?" IDYES Start + +SectionEnd + +SectionGroup /e SectionGroup1 + +Section "Test Registry/INI functions" + +SectionIn 1 4 3 + + WriteRegStr HKLM SOFTWARE\NSISTest\BigNSISTest "StrTest_INSTDIR" "$INSTDIR" + WriteRegDword HKLM SOFTWARE\NSISTest\BigNSISTest "DwordTest_0xDEADBEEF" 0xdeadbeef + WriteRegDword HKLM SOFTWARE\NSISTest\BigNSISTest "DwordTest_123456" 123456 + WriteRegDword HKLM SOFTWARE\NSISTest\BigNSISTest "DwordTest_0123" 0123 + WriteRegBin HKLM SOFTWARE\NSISTest\BigNSISTest "BinTest_deadbeef01f00dbeef" "DEADBEEF01F00DBEEF" + StrCpy $8 "$SYSDIR\IniTest" + WriteINIStr "$INSTDIR\test.ini" "MySection" "Value1" $8 + WriteINIStr "$INSTDIR\test.ini" "MySectionIni" "Value1" $8 + WriteINIStr "$INSTDIR\test.ini" "MySectionIni" "Value2" $8 + WriteINIStr "$INSTDIR\test.ini" "IniOn" "Value1" $8 + + Call MyFunctionTest + + DeleteINIStr "$INSTDIR\test.ini" "IniOn" "Value1" + DeleteINISec "$INSTDIR\test.ini" "MySectionIni" + + ReadINIStr $1 "$INSTDIR\test.ini" "MySectionIni" "Value1" + StrCmp $1 "" INIDelSuccess + MessageBox MB_OK "DeleteINISec failed" + INIDelSuccess: + + ClearErrors + ReadRegStr $1 HKCR "software\microsoft" xyz_cc_does_not_exist + IfErrors 0 NoError + MessageBox MB_OK "could not read from HKCR\software\microsoft\xyz_cc_does_not_exist" + Goto ErrorYay + NoError: + MessageBox MB_OK "read '$1' from HKCR\software\microsoft\xyz_cc_does_not_exist" + ErrorYay: + +SectionEnd + +Section "Test CreateShortCut" + + SectionIn 1 2 3 + + Call CSCTest + +SectionEnd + +SectionGroup Group2 + +Section "Test Branching" + + BeginTestSection: + SectionIn 1 2 3 + + SetOutPath $INSTDIR + + IfFileExists "$INSTDIR\LogicLib.nsi" 0 BranchTest69 + + MessageBox MB_YESNO|MB_ICONQUESTION "Would you like to overwrite $INSTDIR\LogicLib.nsi?" IDNO NoOverwrite ; skipped if file doesn't exist + + BranchTest69: + + SetOverwrite ifnewer ; NOT AN INSTRUCTION, NOT COUNTED IN SKIPPINGS + + NoOverwrite: + + File "LogicLib.nsi" ; skipped if answered no + SetOverwrite try ; NOT AN INSTRUCTION, NOT COUNTED IN SKIPPINGS + + MessageBox MB_YESNO|MB_ICONQUESTION "Would you like to skip the rest of this section?" IDYES EndTestBranch + MessageBox MB_YESNO|MB_ICONQUESTION "Would you like to go back to the beginning of this section?" IDYES BeginTestSection + MessageBox MB_YESNO|MB_ICONQUESTION "Would you like to hide the installer and wait five seconds?" IDNO NoHide + + HideWindow + Sleep 5000 + BringToFront + + NoHide: + + MessageBox MB_YESNO|MB_ICONQUESTION "Would you like to call the function 5 times?" IDNO NoRecurse + + StrCpy $1 "x" + + LoopTest: + + Call myfunc + StrCpy $1 "x$1" + StrCmp $1 "xxxxxx" 0 LoopTest + + NoRecurse: + + EndTestBranch: + +SectionEnd + +SectionGroupEnd + +Section "Test CopyFiles" + + SectionIn 1 2 3 + + SetOutPath $INSTDIR\cpdest + CopyFiles "$WINDIR\*.ini" "$INSTDIR\cpdest" 0 + +SectionEnd + +SectionGroupEnd + +Section "Test Exec functions" TESTIDX + + SectionIn 1 2 3 + + SearchPath $1 notepad.exe + + MessageBox MB_OK "notepad.exe=$1" + Exec '"$1"' + ExecShell "open" '"$INSTDIR"' + Sleep 500 + BringToFront + +SectionEnd + +Section "Test ActiveX control registration" + + SectionIn 2 + + UnRegDLL "$SYSDIR\spin32.ocx" + Sleep 1000 + RegDLL "$SYSDIR\spin32.ocx" + Sleep 1000 + +SectionEnd + +;-------------------------------- + +Function "CSCTest" + + CreateDirectory "$SMPROGRAMS\Big NSIS Test" + SetOutPath $INSTDIR ; for working directory + CreateShortCut "$SMPROGRAMS\Big NSIS Test\Uninstall BIG NSIS Test.lnk" "$INSTDIR\bt-uninst.exe" ; use defaults for parameters, icon, etc. + ; this one will use notepad's icon, start it minimized, and give it a hotkey (of Ctrl+Shift+Q) + CreateShortCut "$SMPROGRAMS\Big NSIS Test\silent.nsi.lnk" "$INSTDIR\silent.nsi" "" "$WINDIR\notepad.exe" 0 SW_SHOWMINIMIZED CONTROL|SHIFT|Q + CreateShortCut "$SMPROGRAMS\Big NSIS Test\TheDir.lnk" "$INSTDIR\" "" "" 0 SW_SHOWMAXIMIZED CONTROL|SHIFT|Z + +FunctionEnd + +Function myfunc + + StrCpy $2 "MyTestVar=$1" + MessageBox MB_OK "myfunc: $2" + +FunctionEnd + +Function MyFunctionTest + + ReadINIStr $1 "$INSTDIR\test.ini" "MySectionIni" "Value1" + StrCmp $1 $8 NoFailedMsg + MessageBox MB_OK "WriteINIStr failed" + + NoFailedMsg: + +FunctionEnd + +Function .onSelChange + + SectionGetText ${TESTIDX} $0 + StrCmp $0 "" e + SectionSetText ${TESTIDX} "" + Goto e2 +e: + SectionSetText ${TESTIDX} "TextInSection" +e2: + +FunctionEnd + +;-------------------------------- + +; Uninstaller + +UninstallText "This will uninstall example2. Hit next to continue." +UninstallIcon "${NSISDIR}\Contrib\Graphics\Icons\nsis1-uninstall.ico" + +Section "Uninstall" + + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BigNSISTest" + DeleteRegKey HKLM "SOFTWARE\NSISTest\BigNSISTest" + Delete "$INSTDIR\silent.nsi" + Delete "$INSTDIR\LogicLib.nsi" + Delete "$INSTDIR\bt-uninst.exe" + Delete "$INSTDIR\test.ini" + Delete "$SMPROGRAMS\Big NSIS Test\*.*" + RMDir "$SMPROGRAMS\BiG NSIS Test" + + MessageBox MB_YESNO|MB_ICONQUESTION "Would you like to remove the directory $INSTDIR\cpdest?" IDNO NoDelete + Delete "$INSTDIR\cpdest\*.*" + RMDir "$INSTDIR\cpdest" ; skipped if no + NoDelete: + + RMDir "$INSTDIR\MyProjectFamily\MyProject" + RMDir "$INSTDIR\MyProjectFamily" + RMDir "$INSTDIR" + + IfFileExists "$INSTDIR" 0 NoErrorMsg + MessageBox MB_OK "Note: $INSTDIR could not be removed!" IDOK 0 ; skipped if file doesn't exist + NoErrorMsg: + +SectionEnd diff --git a/installer/NSIS/Examples/example1.nsi b/installer/NSIS/Examples/example1.nsi new file mode 100644 index 0000000..fd549c0 --- /dev/null +++ b/installer/NSIS/Examples/example1.nsi @@ -0,0 +1,40 @@ +; example1.nsi +; +; This script is perhaps one of the simplest NSIs you can make. All of the +; optional settings are left to their default settings. The installer simply +; prompts the user asking them where to install, and drops a copy of example1.nsi +; there. + +;-------------------------------- + +; The name of the installer +Name "Example1" + +; The file to write +OutFile "example1.exe" + +; The default installation directory +InstallDir $DESKTOP\Example1 + +; Request application privileges for Windows Vista +RequestExecutionLevel user + +;-------------------------------- + +; Pages + +Page directory +Page instfiles + +;-------------------------------- + +; The stuff to install +Section "" ;No components page, name is not important + + ; Set output path to the installation directory. + SetOutPath $INSTDIR + + ; Put file there + File example1.nsi + +SectionEnd ; end the section diff --git a/installer/NSIS/Examples/example2.nsi b/installer/NSIS/Examples/example2.nsi new file mode 100644 index 0000000..6798db1 --- /dev/null +++ b/installer/NSIS/Examples/example2.nsi @@ -0,0 +1,92 @@ +; example2.nsi +; +; This script is based on example1.nsi, but it remember the directory, +; has uninstall support and (optionally) installs start menu shortcuts. +; +; It will install example2.nsi into a directory that the user selects, + +;-------------------------------- + +; The name of the installer +Name "Example2" + +; The file to write +OutFile "example2.exe" + +; The default installation directory +InstallDir $PROGRAMFILES\Example2 + +; Registry key to check for directory (so if you install again, it will +; overwrite the old one automatically) +InstallDirRegKey HKLM "Software\NSIS_Example2" "Install_Dir" + +; Request application privileges for Windows Vista +RequestExecutionLevel admin + +;-------------------------------- + +; Pages + +Page components +Page directory +Page instfiles + +UninstPage uninstConfirm +UninstPage instfiles + +;-------------------------------- + +; The stuff to install +Section "Example2 (required)" + + SectionIn RO + + ; Set output path to the installation directory. + SetOutPath $INSTDIR + + ; Put file there + File "example2.nsi" + + ; Write the installation path into the registry + WriteRegStr HKLM SOFTWARE\NSIS_Example2 "Install_Dir" "$INSTDIR" + + ; Write the uninstall keys for Windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Example2" "DisplayName" "NSIS Example2" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Example2" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Example2" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Example2" "NoRepair" 1 + WriteUninstaller "uninstall.exe" + +SectionEnd + +; Optional section (can be disabled by the user) +Section "Start Menu Shortcuts" + + CreateDirectory "$SMPROGRAMS\Example2" + CreateShortCut "$SMPROGRAMS\Example2\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 + CreateShortCut "$SMPROGRAMS\Example2\Example2 (MakeNSISW).lnk" "$INSTDIR\example2.nsi" "" "$INSTDIR\example2.nsi" 0 + +SectionEnd + +;-------------------------------- + +; Uninstaller + +Section "Uninstall" + + ; Remove registry keys + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Example2" + DeleteRegKey HKLM SOFTWARE\NSIS_Example2 + + ; Remove files and uninstaller + Delete $INSTDIR\example2.nsi + Delete $INSTDIR\uninstall.exe + + ; Remove shortcuts, if any + Delete "$SMPROGRAMS\Example2\*.*" + + ; Remove directories used + RMDir "$SMPROGRAMS\Example2" + RMDir "$INSTDIR" + +SectionEnd diff --git a/installer/NSIS/Examples/gfx.nsi b/installer/NSIS/Examples/gfx.nsi new file mode 100644 index 0000000..7150eaf --- /dev/null +++ b/installer/NSIS/Examples/gfx.nsi @@ -0,0 +1,123 @@ +; gfx.nsi +; +; This script shows some examples of using all of the new +; graphic related additions introduced in NSIS 2 +; +; Written by Amir Szkeley 22nd July 2002 + +;-------------------------------- + +!macro BIMAGE IMAGE PARMS + Push $0 + GetTempFileName $0 + File /oname=$0 "${IMAGE}" + SetBrandingImage ${PARMS} $0 + Delete $0 + Pop $0 +!macroend + +;-------------------------------- + +Name "Graphical effects" + +OutFile "gfx.exe" + +; Adds an XP manifest to the installer +XPStyle on + +; Add branding image to the installer (an image placeholder on the side). +; It is not enough to just add the placeholder, we must set the image too... +; We will later set the image in every pre-page function. +; We can also set just one persistent image in .onGUIInit +AddBrandingImage left 100 + +; Sets the font of the installer +SetFont "Comic Sans MS" 8 + +; Just to make it three pages... +SubCaption 0 ": Yet another page..." +SubCaption 2 ": Yet another page..." +LicenseText "License page" +LicenseData "gfx.nsi" +DirText "Lets make a third page!" + +; Install dir +InstallDir "${NSISDIR}\Examples" + +; Request application privileges for Windows Vista +RequestExecutionLevel user + +;-------------------------------- + +; Pages +Page license licenseImage +Page custom customPage +Page directory dirImage +Page instfiles instImage + +;-------------------------------- + +Section "" + ; You can also use the BI_NEXT macro here... + MessageBox MB_YESNO "We can change the branding image from within a section too!$\nDo you want me to change it?" IDNO done + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Wizard\nsis.bmp" "" + done: + WriteUninstaller uninst.exe +SectionEnd + +;-------------------------------- + +Function licenseImage + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp" /RESIZETOFIT + MessageBox MB_YESNO 'Would you like to skip the license page?' IDNO no + Abort + no: +FunctionEnd + +Function customPage + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" /RESIZETOFIT + MessageBox MB_OK 'This is a nice custom "page" with yet another image :P' + #insert install options/start menu/ here +FunctionEnd + +Function dirImage + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Header\win.bmp" /RESIZETOFIT +FunctionEnd + +Function instImage + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Wizard\llama.bmp" /RESIZETOFIT +FunctionEnd + +;-------------------------------- + +; Uninstall pages + +UninstPage uninstConfirm un.uninstImage +UninstPage custom un.customPage +UninstPage instfiles un.instImage + +Function un.uninstImage + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" /RESIZETOFIT +FunctionEnd + +Function un.customPage + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Header\win.bmp" /RESIZETOFIT + MessageBox MB_OK 'This is a nice uninstaller custom "page" with yet another image :P' + #insert install options/start menu/ here +FunctionEnd + +Function un.instImage + !insertmacro BIMAGE "${NSISDIR}\Contrib\Graphics\Wizard\llama.bmp" /RESIZETOFIT +FunctionEnd + +;-------------------------------- + +; Uninstaller + +; Another page for uninstaller +UninstallText "Another page..." + +Section uninstall + MessageBox MB_OK "Bla" +SectionEnd + diff --git a/installer/NSIS/Examples/languages.nsi b/installer/NSIS/Examples/languages.nsi new file mode 100644 index 0000000..fdf15d1 --- /dev/null +++ b/installer/NSIS/Examples/languages.nsi @@ -0,0 +1,179 @@ +; languages.nsi +; +; This is an example of a multilingual installer +; The user can select the language on startup + +;-------------------------------- + +OutFile languages.exe + +XPStyle on + +RequestExecutionLevel user + +;-------------------------------- + +Page license +Page components +Page instfiles + +;-------------------------------- + +; First is default +LoadLanguageFile "${NSISDIR}\Contrib\Language files\English.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\Dutch.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\French.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\German.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\Korean.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\Russian.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\Spanish.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\Swedish.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\TradChinese.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\SimpChinese.nlf" +LoadLanguageFile "${NSISDIR}\Contrib\Language files\Slovak.nlf" + +; License data +; Not exactly translated, but it shows what's needed +LicenseLangString myLicenseData ${LANG_ENGLISH} "bigtest.nsi" +LicenseLangString myLicenseData ${LANG_DUTCH} "waplugin.nsi" +LicenseLangString myLicenseData ${LANG_FRENCH} "example1.nsi" +LicenseLangString myLicenseData ${LANG_GERMAN} "example2.nsi" +LicenseLangString myLicenseData ${LANG_KOREAN} "gfx.nsi" +LicenseLangString myLicenseData ${LANG_RUSSIAN} "languages.nsi" +LicenseLangString myLicenseData ${LANG_SPANISH} "LogicLib.nsi" +LicenseLangString myLicenseData ${LANG_SWEDISH} "makensis.nsi" +LicenseLangString myLicenseData ${LANG_TRADCHINESE} "one-section.nsi" +LicenseLangString myLicenseData ${LANG_SIMPCHINESE} "primes.nsi" +LicenseLangString myLicenseData ${LANG_SLOVAK} "silent.nsi" + +LicenseData $(myLicenseData) + +; Set name using the normal interface (Name command) +LangString Name ${LANG_ENGLISH} "English" +LangString Name ${LANG_DUTCH} "Dutch" +LangString Name ${LANG_FRENCH} "French" +LangString Name ${LANG_GERMAN} "German" +LangString Name ${LANG_KOREAN} "Korean" +LangString Name ${LANG_RUSSIAN} "Russian" +LangString Name ${LANG_SPANISH} "Spanish" +LangString Name ${LANG_SWEDISH} "Swedish" +LangString Name ${LANG_TRADCHINESE} "Traditional Chinese" +LangString Name ${LANG_SIMPCHINESE} "Simplified Chinese" +LangString Name ${LANG_SLOVAK} "Slovak" + +Name $(Name) + +; Directly change the inner lang strings (Same as ComponentText) +LangString ^ComponentsText ${LANG_ENGLISH} "English component page" +LangString ^ComponentsText ${LANG_DUTCH} "Dutch component page" +LangString ^ComponentsText ${LANG_FRENCH} "French component page" +LangString ^ComponentsText ${LANG_GERMAN} "German component page" +LangString ^ComponentsText ${LANG_KOREAN} "Korean component page" +LangString ^ComponentsText ${LANG_RUSSIAN} "Russian component page" +LangString ^ComponentsText ${LANG_SPANISH} "Spanish component page" +LangString ^ComponentsText ${LANG_SWEDISH} "Swedish component page" +LangString ^ComponentsText ${LANG_TRADCHINESE} "Traditional Chinese component page" +LangString ^ComponentsText ${LANG_SIMPCHINESE} "Simplified Chinese component page" +LangString ^ComponentsText ${LANG_SLOVAK} "Slovak component page" + +; Set one text for all languages (simply don't use a LangString) +CompletedText "Languages example completed" + +; A LangString for the section name +LangString Sec1Name ${LANG_ENGLISH} "English section #1" +LangString Sec1Name ${LANG_DUTCH} "Dutch section #1" +LangString Sec1Name ${LANG_FRENCH} "French section #1" +LangString Sec1Name ${LANG_GERMAN} "German section #1" +LangString Sec1Name ${LANG_KOREAN} "Korean section #1" +LangString Sec1Name ${LANG_RUSSIAN} "Russian section #1" +LangString Sec1Name ${LANG_SPANISH} "Spanish section #1" +LangString Sec1Name ${LANG_SWEDISH} "Swedish section #1" +LangString Sec1Name ${LANG_TRADCHINESE} "Trandional Chinese section #1" +LangString Sec1Name ${LANG_SIMPCHINESE} "Simplified Chinese section #1" +LangString Sec1Name ${LANG_SLOVAK} "Slovak section #1" + +; A multilingual message +LangString Message ${LANG_ENGLISH} "English message" +LangString Message ${LANG_DUTCH} "Dutch message" +LangString Message ${LANG_FRENCH} "French message" +LangString Message ${LANG_GERMAN} "German message" +LangString Message ${LANG_KOREAN} "Korean message" +LangString Message ${LANG_RUSSIAN} "Russian message" +LangString Message ${LANG_SPANISH} "Spanish message" +LangString Message ${LANG_SWEDISH} "Swedish message" +LangString Message ${LANG_TRADCHINESE} "Trandional Chinese message" +LangString Message ${LANG_SIMPCHINESE} "Simplified Chinese message" +LangString Message ${LANG_SLOVAK} "Slovak message" + +;-------------------------------- + +;Section names set by Language strings +;It works with ! too +Section !$(Sec1Name) sec1 + MessageBox MB_OK $(Message) +SectionEnd + +; The old, slow, wasteful way +; Look at this section and see why LangString is so much easier +Section "Section number two" + StrCmp $LANGUAGE ${LANG_ENGLISH} 0 +2 + MessageBox MB_OK "Installing English stuff" + StrCmp $LANGUAGE ${LANG_DUTCH} 0 +2 + MessageBox MB_OK "Installing Dutch stuff" + StrCmp $LANGUAGE ${LANG_FRENCH} 0 +2 + MessageBox MB_OK "Installing French stuff" + StrCmp $LANGUAGE ${LANG_GERMAN} 0 +2 + MessageBox MB_OK "Installing German stuff" + StrCmp $LANGUAGE ${LANG_KOREAN} 0 +2 + MessageBox MB_OK "Installing Korean stuff" + StrCmp $LANGUAGE ${LANG_RUSSIAN} 0 +2 + MessageBox MB_OK "Installing Russian stuff" + StrCmp $LANGUAGE ${LANG_SPANISH} 0 +2 + MessageBox MB_OK "Installing Spanish stuff" + StrCmp $LANGUAGE ${LANG_SWEDISH} 0 +2 + MessageBox MB_OK "Installing Swedish stuff" + StrCmp $LANGUAGE ${LANG_TRADCHINESE} 0 +2 + MessageBox MB_OK "Installing Traditional Chinese stuff" + StrCmp $LANGUAGE ${LANG_SIMPCHINESE} 0 +2 + MessageBox MB_OK "Installing Simplified Chinese stuff" + StrCmp $LANGUAGE ${LANG_SLOVAK} 0 +2 + MessageBox MB_OK "Installing Slovak stuff" +SectionEnd + +;-------------------------------- + +Function .onInit + + ;Language selection dialog + + Push "" + Push ${LANG_ENGLISH} + Push English + Push ${LANG_DUTCH} + Push Dutch + Push ${LANG_FRENCH} + Push French + Push ${LANG_GERMAN} + Push German + Push ${LANG_KOREAN} + Push Korean + Push ${LANG_RUSSIAN} + Push Russian + Push ${LANG_SPANISH} + Push Spanish + Push ${LANG_SWEDISH} + Push Swedish + Push ${LANG_TRADCHINESE} + Push "Traditional Chinese" + Push ${LANG_SIMPCHINESE} + Push "Simplified Chinese" + Push ${LANG_SLOVAK} + Push Slovak + Push A ; A means auto count languages + ; for the auto count to work the first empty push (Push "") must remain + LangDLL::LangDialog "Installer Language" "Please select the language of the installer" + + Pop $LANGUAGE + StrCmp $LANGUAGE "cancel" 0 +2 + Abort +FunctionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/makensis.nsi b/installer/NSIS/Examples/makensis.nsi new file mode 100644 index 0000000..f07a73e --- /dev/null +++ b/installer/NSIS/Examples/makensis.nsi @@ -0,0 +1,1051 @@ +;NSIS Setup Script +;-------------------------------- + +!ifndef VERSION + !define VERSION 'anonymous-build' +!endif + +;-------------------------------- +;Configuration + +!ifdef OUTFILE + OutFile "${OUTFILE}" +!else + OutFile ..\nsis-${VERSION}-setup.exe +!endif + +SetCompressor /SOLID lzma + +InstType "Full" +InstType "Lite" +InstType "Minimal" + +InstallDir $PROGRAMFILES\NSIS +InstallDirRegKey HKLM Software\NSIS "" + +RequestExecutionLevel admin + +;-------------------------------- +;Header Files + +!include "MUI2.nsh" +!include "Sections.nsh" +!include "LogicLib.nsh" +!include "Memento.nsh" +!include "WordFunc.nsh" + +;-------------------------------- +;Functions + +!ifdef VER_MAJOR & VER_MINOR & VER_REVISION & VER_BUILD + + !insertmacro VersionCompare + +!endif + +;-------------------------------- +;Definitions + +!define SHCNE_ASSOCCHANGED 0x8000000 +!define SHCNF_IDLIST 0 + +;-------------------------------- +;Configuration + +;Names +Name "NSIS" +Caption "NSIS ${VERSION} Setup" + +;Memento Settings +!define MEMENTO_REGISTRY_ROOT HKLM +!define MEMENTO_REGISTRY_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" + +;Interface Settings +!define MUI_ABORTWARNING + +!define MUI_HEADERIMAGE +!define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\nsis.bmp" + +!define MUI_COMPONENTSPAGE_SMALLDESC + +;Pages +!define MUI_WELCOMEPAGE_TITLE "Welcome to the NSIS ${VERSION} Setup Wizard" +!define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of NSIS (Nullsoft Scriptable Install System) ${VERSION}, the next generation of the Windows installer and uninstaller system that doesn't suck and isn't huge.$\r$\n$\r$\nNSIS 2 includes a new Modern User Interface, LZMA compression, support for multiple languages and an easy plug-in system.$\r$\n$\r$\n$_CLICK" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "..\COPYING" +!ifdef VER_MAJOR & VER_MINOR & VER_REVISION & VER_BUILD +Page custom PageReinstall PageLeaveReinstall +!endif +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES + +!define MUI_FINISHPAGE_LINK "Visit the NSIS site for the latest news, FAQs and support" +!define MUI_FINISHPAGE_LINK_LOCATION "http://nsis.sf.net/" + +!define MUI_FINISHPAGE_RUN "$INSTDIR\NSIS.exe" +!define MUI_FINISHPAGE_NOREBOOTSUPPORT + +!define MUI_FINISHPAGE_SHOWREADME +!define MUI_FINISHPAGE_SHOWREADME_TEXT "Show release notes" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION ShowReleaseNotes + +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + +!insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Installer Sections + +${MementoSection} "NSIS Core Files (required)" SecCore + + SetDetailsPrint textonly + DetailPrint "Installing NSIS Core Files..." + SetDetailsPrint listonly + + SectionIn 1 2 3 RO + SetOutPath $INSTDIR + RMDir /r $SMPROGRAMS\NSIS + + SetOverwrite on + File ..\makensis.exe + File ..\makensisw.exe + File ..\COPYING + File ..\NSIS.chm + File ..\NSIS.exe + File /nonfatal ..\NSIS.exe.manifest + + IfFileExists $INSTDIR\nsisconf.nsi "" +2 + Rename $INSTDIR\nsisconf.nsi $INSTDIR\nsisconf.nsh + SetOverwrite off + File ..\nsisconf.nsh + SetOverwrite on + + SetOutPath $INSTDIR\Stubs + File ..\Stubs\bzip2 + File ..\Stubs\bzip2_solid + File ..\Stubs\lzma + File ..\Stubs\lzma_solid + File ..\Stubs\zlib + File ..\Stubs\zlib_solid + File ..\Stubs\uninst + + SetOutPath $INSTDIR\Include + File ..\Include\WinMessages.nsh + File ..\Include\Sections.nsh + File ..\Include\Library.nsh + File ..\Include\UpgradeDLL.nsh + File ..\Include\LogicLib.nsh + File ..\Include\StrFunc.nsh + File ..\Include\Colors.nsh + File ..\Include\FileFunc.nsh + File ..\Include\TextFunc.nsh + File ..\Include\WordFunc.nsh + File ..\Include\WinVer.nsh + File ..\Include\x64.nsh + File ..\Include\Memento.nsh + File ..\Include\LangFile.nsh + File ..\Include\InstallOptions.nsh + File ..\Include\MultiUser.nsh + File ..\Include\VB6RunTime.nsh + File ..\Include\Util.nsh + File ..\Include\WinCore.nsh + + SetOutPath $INSTDIR\Include\Win + File ..\Include\Win\WinDef.nsh + File ..\Include\Win\WinError.nsh + File ..\Include\Win\WinNT.nsh + File ..\Include\Win\WinUser.nsh + + SetOutPath $INSTDIR\Docs\StrFunc + File ..\Docs\StrFunc\StrFunc.txt + + SetOutPath $INSTDIR\Docs\MultiUser + File ..\Docs\MultiUser\Readme.html + + SetOutPath $INSTDIR\Docs\makensisw + File ..\Docs\makensisw\*.txt + + SetOutPath $INSTDIR\Menu + File ..\Menu\*.html + SetOutPath $INSTDIR\Menu\images + File ..\Menu\images\header.gif + File ..\Menu\images\line.gif + File ..\Menu\images\site.gif + + Delete $INSTDIR\makensis.htm + Delete $INSTDIR\Docs\*.html + Delete $INSTDIR\Docs\style.css + RMDir $INSTDIR\Docs + + SetOutPath $INSTDIR\Bin + File ..\Bin\LibraryLocal.exe + File ..\Bin\RegTool.bin + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\TypeLib.dll + + ReadRegStr $R0 HKCR ".nsi" "" + StrCmp $R0 "NSISFile" 0 +2 + DeleteRegKey HKCR "NSISFile" + + WriteRegStr HKCR ".nsi" "" "NSIS.Script" + WriteRegStr HKCR "NSIS.Script" "" "NSIS Script File" + WriteRegStr HKCR "NSIS.Script\DefaultIcon" "" "$INSTDIR\makensisw.exe,1" + ReadRegStr $R0 HKCR "NSIS.Script\shell\open\command" "" + StrCmp $R0 "" 0 no_nsiopen + WriteRegStr HKCR "NSIS.Script\shell" "" "open" + WriteRegStr HKCR "NSIS.Script\shell\open\command" "" 'notepad.exe "%1"' + no_nsiopen: + WriteRegStr HKCR "NSIS.Script\shell\compile" "" "Compile NSIS Script" + WriteRegStr HKCR "NSIS.Script\shell\compile\command" "" '"$INSTDIR\makensisw.exe" "%1"' + WriteRegStr HKCR "NSIS.Script\shell\compile-compressor" "" "Compile NSIS Script (Choose Compressor)" + WriteRegStr HKCR "NSIS.Script\shell\compile-compressor\command" "" '"$INSTDIR\makensisw.exe" /ChooseCompressor "%1"' + + ReadRegStr $R0 HKCR ".nsh" "" + StrCmp $R0 "NSHFile" 0 +2 + DeleteRegKey HKCR "NSHFile" + + WriteRegStr HKCR ".nsh" "" "NSIS.Header" + WriteRegStr HKCR "NSIS.Header" "" "NSIS Header File" + WriteRegStr HKCR "NSIS.Header\DefaultIcon" "" "$INSTDIR\makensisw.exe,1" + ReadRegStr $R0 HKCR "NSIS.Header\shell\open\command" "" + StrCmp $R0 "" 0 no_nshopen + WriteRegStr HKCR "NSIS.Header\shell" "" "open" + WriteRegStr HKCR "NSIS.Header\shell\open\command" "" 'notepad.exe "%1"' + no_nshopen: + + System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' + +${MementoSectionEnd} + +${MementoSection} "Script Examples" SecExample + + SetDetailsPrint textonly + DetailPrint "Installing Script Examples..." + SetDetailsPrint listonly + + SectionIn 1 2 + SetOutPath $INSTDIR\Examples + File ..\Examples\makensis.nsi + File ..\Examples\example1.nsi + File ..\Examples\example2.nsi + File ..\Examples\viewhtml.nsi + File ..\Examples\waplugin.nsi + File ..\Examples\bigtest.nsi + File ..\Examples\primes.nsi + File ..\Examples\rtest.nsi + File ..\Examples\gfx.nsi + File ..\Examples\one-section.nsi + File ..\Examples\languages.nsi + File ..\Examples\Library.nsi + File ..\Examples\VersionInfo.nsi + File ..\Examples\UserVars.nsi + File ..\Examples\LogicLib.nsi + File ..\Examples\silent.nsi + File ..\Examples\StrFunc.nsi + File ..\Examples\FileFunc.nsi + File ..\Examples\FileFunc.ini + File ..\Examples\FileFuncTest.nsi + File ..\Examples\TextFunc.nsi + File ..\Examples\TextFunc.ini + File ..\Examples\TextFuncTest.nsi + File ..\Examples\WordFunc.nsi + File ..\Examples\WordFunc.ini + File ..\Examples\WordFuncTest.nsi + File ..\Examples\Memento.nsi + + SetOutPath $INSTDIR\Examples\Plugin + File ..\Examples\Plugin\exdll.c + File ..\Examples\Plugin\exdll.dpr + File ..\Examples\Plugin\exdll.dsp + File ..\Examples\Plugin\exdll.dsw + File ..\Examples\Plugin\exdll_with_unit.dpr + File ..\Examples\Plugin\exdll-vs2008.sln + File ..\Examples\Plugin\exdll-vs2008.vcproj + File ..\Examples\Plugin\extdll.inc + File ..\Examples\Plugin\nsis.pas + + SetOutPath $INSTDIR\Examples\Plugin\nsis + File ..\Examples\Plugin\nsis\pluginapi.h + File ..\Examples\Plugin\nsis\pluginapi.lib + File ..\Examples\Plugin\nsis\api.h + +${MementoSectionEnd} + +!ifndef NO_STARTMENUSHORTCUTS +${MementoSection} "Start Menu and Desktop Shortcuts" SecShortcuts + + SetDetailsPrint textonly + DetailPrint "Installing Start Menu and Desktop Shortcuts..." + SetDetailsPrint listonly + +!else +${MementoSection} "Desktop Shortcut" SecShortcuts + + SetDetailsPrint textonly + DetailPrint "Installing Desktop Shortcut..." + SetDetailsPrint listonly + +!endif + SectionIn 1 2 + SetOutPath $INSTDIR +!ifndef NO_STARTMENUSHORTCUTS + CreateShortCut "$SMPROGRAMS\NSIS.lnk" "$INSTDIR\NSIS.exe" +!endif + + CreateShortCut "$DESKTOP\NSIS.lnk" "$INSTDIR\NSIS.exe" + +${MementoSectionEnd} + +SectionGroup "User Interfaces" SecInterfaces + +${MementoSection} "Modern User Interface" SecInterfacesModernUI + + SetDetailsPrint textonly + DetailPrint "Installing User Interfaces | Modern User Interface..." + SetDetailsPrint listonly + + SectionIn 1 2 + + SetOutPath "$INSTDIR\Examples\Modern UI" + File "..\Examples\Modern UI\Basic.nsi" + File "..\Examples\Modern UI\HeaderBitmap.nsi" + File "..\Examples\Modern UI\MultiLanguage.nsi" + File "..\Examples\Modern UI\StartMenu.nsi" + File "..\Examples\Modern UI\WelcomeFinish.nsi" + + SetOutPath "$INSTDIR\Contrib\Modern UI" + File "..\Contrib\Modern UI\System.nsh" + File "..\Contrib\Modern UI\ioSpecial.ini" + + SetOutPath "$INSTDIR\Docs\Modern UI" + File "..\Docs\Modern UI\Readme.html" + File "..\Docs\Modern UI\Changelog.txt" + File "..\Docs\Modern UI\License.txt" + + SetOutPath "$INSTDIR\Docs\Modern UI\images" + File "..\Docs\Modern UI\images\header.gif" + File "..\Docs\Modern UI\images\screen1.png" + File "..\Docs\Modern UI\images\screen2.png" + File "..\Docs\Modern UI\images\open.gif" + File "..\Docs\Modern UI\images\closed.gif" + + SetOutPath $INSTDIR\Contrib\UIs + File "..\Contrib\UIs\modern.exe" + File "..\Contrib\UIs\modern_headerbmp.exe" + File "..\Contrib\UIs\modern_headerbmpr.exe" + File "..\Contrib\UIs\modern_nodesc.exe" + File "..\Contrib\UIs\modern_smalldesc.exe" + + SetOutPath $INSTDIR\Include + File "..\Include\MUI.nsh" + + SetOutPath "$INSTDIR\Contrib\Modern UI 2" + File "..\Contrib\Modern UI 2\Deprecated.nsh" + File "..\Contrib\Modern UI 2\Interface.nsh" + File "..\Contrib\Modern UI 2\Localization.nsh" + File "..\Contrib\Modern UI 2\MUI2.nsh" + File "..\Contrib\Modern UI 2\Pages.nsh" + + SetOutPath "$INSTDIR\Contrib\Modern UI 2\Pages" + File "..\Contrib\Modern UI 2\Pages\Components.nsh" + File "..\Contrib\Modern UI 2\Pages\Directory.nsh" + File "..\Contrib\Modern UI 2\Pages\Finish.nsh" + File "..\Contrib\Modern UI 2\Pages\InstallFiles.nsh" + File "..\Contrib\Modern UI 2\Pages\License.nsh" + File "..\Contrib\Modern UI 2\Pages\StartMenu.nsh" + File "..\Contrib\Modern UI 2\Pages\UninstallConfirm.nsh" + File "..\Contrib\Modern UI 2\Pages\Welcome.nsh" + + SetOutPath "$INSTDIR\Docs\Modern UI 2" + File "..\Docs\Modern UI 2\Readme.html" + File "..\Docs\Modern UI 2\License.txt" + + SetOutPath "$INSTDIR\Docs\Modern UI 2\images" + File "..\Docs\Modern UI 2\images\header.gif" + File "..\Docs\Modern UI 2\images\screen1.png" + File "..\Docs\Modern UI 2\images\screen2.png" + File "..\Docs\Modern UI 2\images\open.gif" + File "..\Docs\Modern UI 2\images\closed.gif" + + SetOutPath $INSTDIR\Include + File "..\Include\MUI2.nsh" + +${MementoSectionEnd} + +${MementoSection} "Default User Interface" SecInterfacesDefaultUI + + SetDetailsPrint textonly + DetailPrint "Installing User Interfaces | Default User Interface..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath "$INSTDIR\Contrib\UIs" + File "..\Contrib\UIs\default.exe" + +${MementoSectionEnd} + +${MementoSection} "Tiny User Interface" SecInterfacesTinyUI + + SetDetailsPrint textonly + DetailPrint "Installing User Interfaces | Tiny User Interface..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath "$INSTDIR\Contrib\UIs" + File "..\Contrib\UIs\sdbarker_tiny.exe" + +${MementoSectionEnd} + +SectionGroupEnd + +${MementoSection} "Graphics" SecGraphics + + SetDetailsPrint textonly + DetailPrint "Installing Graphics..." + SetDetailsPrint listonly + + SectionIn 1 + + Delete $INSTDIR\Contrib\Icons\*.ico + Delete $INSTDIR\Contrib\Icons\*.bmp + RMDir $INSTDIR\Contrib\Icons + SetOutPath $INSTDIR\Contrib\Graphics + File /r "..\Contrib\Graphics\*.ico" + File /r "..\Contrib\Graphics\*.bmp" +${MementoSectionEnd} + +${MementoSection} "Language Files" SecLangFiles + + SetDetailsPrint textonly + DetailPrint "Installing Language Files..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath "$INSTDIR\Contrib\Language files" + File "..\Contrib\Language files\*.nlf" + + SetOutPath $INSTDIR\Bin + File ..\Bin\MakeLangID.exe + + !insertmacro SectionFlagIsSet ${SecInterfacesModernUI} ${SF_SELECTED} mui nomui + mui: + SetOutPath "$INSTDIR\Contrib\Language files" + File "..\Contrib\Language files\*.nsh" + nomui: + +${MementoSectionEnd} + +SectionGroup "Tools" SecTools + +${MementoSection} "Zip2Exe" SecToolsZ2E + + SetDetailsPrint textonly + DetailPrint "Installing Tools | Zip2Exe..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Bin + File ..\Bin\zip2exe.exe + SetOutPath $INSTDIR\Contrib\zip2exe + File ..\Contrib\zip2exe\Base.nsh + File ..\Contrib\zip2exe\Modern.nsh + File ..\Contrib\zip2exe\Classic.nsh + +${MementoSectionEnd} + +SectionGroupEnd + +SectionGroup "Plug-ins" SecPluginsPlugins + +${MementoSection} "Banner" SecPluginsBanner + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | Banner..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\Banner.dll + SetOutPath $INSTDIR\Docs\Banner + File ..\Docs\Banner\Readme.txt + SetOutPath $INSTDIR\Examples\Banner + File ..\Examples\Banner\Example.nsi +${MementoSectionEnd} + +${MementoSection} "Language DLL" SecPluginsLangDLL + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | Language DLL..." + SetDetailsPrint listonly + + SectionIn 1 + SetOutPath $INSTDIR\Plugins + File ..\Plugins\LangDLL.dll +${MementoSectionEnd} + +${MementoSection} "nsExec" SecPluginsnsExec + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | nsExec..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\nsExec.dll + SetOutPath $INSTDIR\Docs\nsExec + File ..\Docs\nsExec\nsExec.txt + SetOutPath $INSTDIR\Examples\nsExec + File ..\Examples\nsExec\test.nsi +${MementoSectionEnd} + +${MementoSection} "Splash" SecPluginsSplash + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | Splash..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\splash.dll + SetOutPath $INSTDIR\Docs\Splash + File ..\Docs\Splash\splash.txt + SetOutPath $INSTDIR\Examples\Splash + File ..\Examples\Splash\Example.nsi +${MementoSectionEnd} + +${MementoSection} "AdvSplash" SecPluginsSplashT + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | AdvSplash..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\advsplash.dll + SetOutPath $INSTDIR\Docs\AdvSplash + File ..\Docs\AdvSplash\advsplash.txt + SetOutPath $INSTDIR\Examples\AdvSplash + File ..\Examples\AdvSplash\Example.nsi +${MementoSectionEnd} + +${MementoSection} "BgImage" SecPluginsBgImage + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | BgImage..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\BgImage.dll + SetOutPath $INSTDIR\Docs\BgImage + File ..\Docs\BgImage\BgImage.txt + SetOutPath $INSTDIR\Examples\BgImage + File ..\Examples\BgImage\Example.nsi +${MementoSectionEnd} + +${MementoSection} "InstallOptions" SecPluginsIO + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | InstallOptions..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\InstallOptions.dll + SetOutPath $INSTDIR\Docs\InstallOptions + File ..\Docs\InstallOptions\Readme.html + File ..\Docs\InstallOptions\Changelog.txt + SetOutPath $INSTDIR\Examples\InstallOptions + File ..\Examples\InstallOptions\test.ini + File ..\Examples\InstallOptions\test.nsi + File ..\Examples\InstallOptions\testimgs.ini + File ..\Examples\InstallOptions\testimgs.nsi + File ..\Examples\InstallOptions\testlink.ini + File ..\Examples\InstallOptions\testlink.nsi + File ..\Examples\InstallOptions\testnotify.ini + File ..\Examples\InstallOptions\testnotify.nsi +${MementoSectionEnd} + +${MementoSection} "nsDialogs" SecPluginsDialogs + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | nsDialogs..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\nsDialogs.dll + SetOutPath $INSTDIR\Examples\nsDialogs + File ..\Examples\nsDialogs\example.nsi + File ..\Examples\nsDialogs\InstallOptions.nsi + File ..\Examples\nsDialogs\timer.nsi + File ..\Examples\nsDialogs\welcome.nsi + SetOutPath $INSTDIR\Include + File ..\Include\nsDialogs.nsh + SetOutPath $INSTDIR\Docs\nsDialogs + File ..\Docs\nsDialogs\Readme.html +${MementoSectionEnd} + +${MementoSection} "Math" SecPluginsMath + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | Math..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\Math.dll + SetOutPath $INSTDIR\Docs\Math + File ..\Docs\Math\Math.txt + SetOutPath $INSTDIR\Examples\Math + File ..\Examples\Math\math.nsi + File ..\Examples\Math\mathtest.txt + File ..\Examples\Math\mathtest.nsi + File ..\Examples\Math\mathtest.ini + +${MementoSectionEnd} + +${MementoSection} "NSISdl" SecPluginsNSISDL + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | NSISdl..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\nsisdl.dll + SetOutPath $INSTDIR\Docs\NSISdl + File ..\Docs\NSISdl\ReadMe.txt + File ..\Docs\NSISdl\License.txt +${MementoSectionEnd} + +${MementoSection} "System" SecPluginsSystem + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | System..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\System.dll + SetOutPath $INSTDIR\Docs\System + File ..\Docs\System\System.html + File ..\Docs\System\WhatsNew.txt + SetOutPath $INSTDIR\Examples\System + File ..\Examples\System\Resource.dll + File ..\Examples\System\SysFunc.nsh + File ..\Examples\System\System.nsh + File ..\Examples\System\System.nsi +${MementoSectionEnd} + +${MementoSection} "StartMenu" SecPluginsStartMenu + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | StartMenu..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\StartMenu.dll + SetOutPath $INSTDIR\Docs\StartMenu + File ..\Docs\StartMenu\Readme.txt + SetOutPath $INSTDIR\Examples\StartMenu + File ..\Examples\StartMenu\Example.nsi +${MementoSectionEnd} + +${MementoSection} "UserInfo" SecPluginsUserInfo + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | UserInfo..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\UserInfo.dll + SetOutPath $INSTDIR\Examples\UserInfo + File ..\Examples\UserInfo\UserInfo.nsi +${MementoSectionEnd} + +${MementoSection} "Dialer" SecPluginsDialer + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | Dialer..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\Dialer.dll + SetOutPath $INSTDIR\Docs\Dialer + File ..\Docs\Dialer\Dialer.txt +${MementoSectionEnd} + +${MementoSection} "VPatch" SecPluginsVPatch + + SetDetailsPrint textonly + DetailPrint "Installing Plug-ins | VPatch..." + SetDetailsPrint listonly + + SectionIn 1 + + SetOutPath $INSTDIR\Plugins + File ..\Plugins\VPatch.dll + SetOutPath $INSTDIR\Examples\VPatch + File ..\Examples\VPatch\example.nsi + File ..\Examples\VPatch\oldfile.txt + File ..\Examples\VPatch\newfile.txt + File ..\Examples\VPatch\patch.pat + SetOutPath $INSTDIR\Docs\VPatch + File ..\Docs\VPatch\Readme.html + SetOutPath $INSTDIR\Bin + File ..\Bin\GenPat.exe + SetOutPath $INSTDIR\Include + File ..\Include\VPatchLib.nsh +${MementoSectionEnd} + +${MementoSectionDone} + +SectionGroupEnd + +Section -post + + ; When Modern UI is installed: + ; * Always install the English language file + ; * Always install default icons / bitmaps + + !insertmacro SectionFlagIsSet ${SecInterfacesModernUI} ${SF_SELECTED} mui nomui + + mui: + + SetDetailsPrint textonly + DetailPrint "Configuring Modern UI..." + SetDetailsPrint listonly + + !insertmacro SectionFlagIsSet ${SecLangFiles} ${SF_SELECTED} langfiles nolangfiles + + nolangfiles: + + SetOutPath "$INSTDIR\Contrib\Language files" + File "..\Contrib\Language files\English.nlf" + SetOutPath "$INSTDIR\Contrib\Language files" + File "..\Contrib\Language files\English.nsh" + + langfiles: + + !insertmacro SectionFlagIsSet ${SecGraphics} ${SF_SELECTED} graphics nographics + + nographics: + + SetOutPath $INSTDIR\Contrib\Graphics + SetOutPath $INSTDIR\Contrib\Graphics\Checks + File "..\Contrib\Graphics\Checks\modern.bmp" + SetOutPath $INSTDIR\Contrib\Graphics\Icons + File "..\Contrib\Graphics\Icons\modern-install.ico" + File "..\Contrib\Graphics\Icons\modern-uninstall.ico" + SetOutPath $INSTDIR\Contrib\Graphics\Header + File "..\Contrib\Graphics\Header\nsis.bmp" + SetOutPath $INSTDIR\Contrib\Graphics\Wizard + File "..\Contrib\Graphics\Wizard\win.bmp" + + graphics: + + nomui: + + SetDetailsPrint textonly + DetailPrint "Creating Registry Keys..." + SetDetailsPrint listonly + + SetOutPath $INSTDIR + + WriteRegStr HKLM "Software\NSIS" "" $INSTDIR +!ifdef VER_MAJOR & VER_MINOR & VER_REVISION & VER_BUILD + WriteRegDword HKLM "Software\NSIS" "VersionMajor" "${VER_MAJOR}" + WriteRegDword HKLM "Software\NSIS" "VersionMinor" "${VER_MINOR}" + WriteRegDword HKLM "Software\NSIS" "VersionRevision" "${VER_REVISION}" + WriteRegDword HKLM "Software\NSIS" "VersionBuild" "${VER_BUILD}" +!endif + + WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "UninstallString" '"$INSTDIR\uninst-nsis.exe"' + WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "DisplayName" "Nullsoft Install System" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "DisplayIcon" "$INSTDIR\NSIS.exe,0" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "DisplayVersion" "${VERSION}" +!ifdef VER_MAJOR & VER_MINOR & VER_REVISION & VER_BUILD + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "VersionMajor" "${VER_MAJOR}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "VersionMinor" "${VER_MINOR}.${VER_REVISION}" +!endif + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "URLInfoAbout" "http://nsis.sourceforge.net/" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "HelpLink" "http://nsis.sourceforge.net/Support" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "NoModify" "1" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "NoRepair" "1" + + WriteUninstaller $INSTDIR\uninst-nsis.exe + + ${MementoSectionSave} + + SetDetailsPrint both + +SectionEnd + +;-------------------------------- +;Descriptions + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecCore} "The core files required to use NSIS (compiler etc.)" + !insertmacro MUI_DESCRIPTION_TEXT ${SecExample} "Example installation scripts that show you how to use NSIS" + !insertmacro MUI_DESCRIPTION_TEXT ${SecShortcuts} "Adds icons to your start menu and your desktop for easy access" + !insertmacro MUI_DESCRIPTION_TEXT ${SecInterfaces} "User interface designs that can be used to change the installer look and feel" + !insertmacro MUI_DESCRIPTION_TEXT ${SecInterfacesModernUI} "A modern user interface like the wizards of recent Windows versions" + !insertmacro MUI_DESCRIPTION_TEXT ${SecInterfacesDefaultUI} "The default NSIS user interface which you can customize to make your own UI" + !insertmacro MUI_DESCRIPTION_TEXT ${SecInterfacesTinyUI} "A tiny version of the default user interface" + !insertmacro MUI_DESCRIPTION_TEXT ${SecTools} "Tools that help you with NSIS development" + !insertmacro MUI_DESCRIPTION_TEXT ${SecToolsZ2E} "A utility that converts a ZIP file to a NSIS installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecGraphics} "Icons, checkbox images and other graphics" + !insertmacro MUI_DESCRIPTION_TEXT ${SecLangFiles} "Language files used to support multiple languages in an installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsPlugins} "Useful plugins that extend NSIS's functionality" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsBanner} "Plugin that lets you show a banner before installation starts" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsLangDLL} "Plugin that lets you add a language select dialog to your installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsnsExec} "Plugin that executes console programs and prints its output in the NSIS log window or hides it" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsSplash} "Splash screen add-on that lets you add a splash screen to an installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsSplashT} "Splash screen add-on with transparency support that lets you add a splash screen to an installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsSystem} "Plugin that lets you call Win32 API or external DLLs" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsMath} "Plugin that lets you evaluate complicated mathematical expressions" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsDialer} "Plugin that provides internet connection functions" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsIO} "Plugin that lets you add custom pages to an installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsDialogs} "Plugin that lets you add custom pages to an installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsStartMenu} "Plugin that lets the user select the start menu folder" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsBgImage} "Plugin that lets you show a persistent background image plugin and play sounds" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsUserInfo} "Plugin that that gives you the user name and the user account type" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsNSISDL} "Plugin that lets you create a web based installer" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPluginsVPatch} "Plugin that lets you create patches to upgrade older files" +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Installer Functions + +Function .onInit + + ${MementoSectionRestore} + +FunctionEnd + +!ifdef VER_MAJOR & VER_MINOR & VER_REVISION & VER_BUILD + +Var ReinstallPageCheck + +Function PageReinstall + + ReadRegStr $R0 HKLM "Software\NSIS" "" + + ${If} $R0 == "" + Abort + ${EndIf} + + ReadRegDWORD $R0 HKLM "Software\NSIS" "VersionMajor" + ReadRegDWORD $R1 HKLM "Software\NSIS" "VersionMinor" + ReadRegDWORD $R2 HKLM "Software\NSIS" "VersionRevision" + ReadRegDWORD $R3 HKLM "Software\NSIS" "VersionBuild" + StrCpy $R0 $R0.$R1.$R2.$R3 + + ${VersionCompare} ${VER_MAJOR}.${VER_MINOR}.${VER_REVISION}.${VER_BUILD} $R0 $R0 + ${If} $R0 == 0 + StrCpy $R1 "NSIS ${VERSION} is already installed. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Add/Reinstall components" + StrCpy $R3 "Uninstall NSIS" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose the maintenance option to perform." + StrCpy $R0 "2" + ${ElseIf} $R0 == 1 + StrCpy $R1 "An older version of NSIS is installed on your system. It's recommended that you uninstall the current version before installing. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Uninstall before installing" + StrCpy $R3 "Do not uninstall" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install NSIS." + StrCpy $R0 "1" + ${ElseIf} $R0 == 2 + StrCpy $R1 "A newer version of NSIS is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue." + StrCpy $R2 "Uninstall before installing" + StrCpy $R3 "Do not uninstall" + !insertmacro MUI_HEADER_TEXT "Already Installed" "Choose how you want to install NSIS." + StrCpy $R0 "1" + ${Else} + Abort + ${EndIf} + + nsDialogs::Create 1018 + Pop $R4 + + ${NSD_CreateLabel} 0 0 100% 24u $R1 + Pop $R1 + + ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2 + Pop $R2 + ${NSD_OnClick} $R2 PageReinstallUpdateSelection + + ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 + Pop $R3 + ${NSD_OnClick} $R3 PageReinstallUpdateSelection + + ${If} $ReinstallPageCheck != 2 + SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${Else} + SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${EndIf} + + ${NSD_SetFocus} $R2 + + nsDialogs::Show + +FunctionEnd + +Function PageReinstallUpdateSelection + + Pop $R1 + + ${NSD_GetState} $R2 $R1 + + ${If} $R1 == ${BST_CHECKED} + StrCpy $ReinstallPageCheck 1 + ${Else} + StrCpy $ReinstallPageCheck 2 + ${EndIf} + +FunctionEnd + +Function PageLeaveReinstall + + ${NSD_GetState} $R2 $R1 + + StrCmp $R0 "1" 0 +2 + StrCmp $R1 "1" reinst_uninstall reinst_done + + StrCmp $R0 "2" 0 +3 + StrCmp $R1 "1" reinst_done reinst_uninstall + + reinst_uninstall: + ReadRegStr $R1 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" "UninstallString" + + ;Run uninstaller + HideWindow + + ClearErrors + ExecWait '$R1 _?=$INSTDIR' + + IfErrors no_remove_uninstaller + IfFileExists "$INSTDIR\makensis.exe" no_remove_uninstaller + + Delete $R1 + RMDir $INSTDIR + + no_remove_uninstaller: + + StrCmp $R0 "2" 0 +2 + Quit + + BringToFront + + reinst_done: + +FunctionEnd + +!endif # VER_MAJOR & VER_MINOR & VER_REVISION & VER_BUILD + +Function ShowReleaseNotes + ${If} ${FileExists} $WINDIR\hh.exe + StrCpy $0 $WINDIR\hh.exe + Exec '"$0" mk:@MSITStore:$INSTDIR\NSIS.chm::/SectionF.1.html' + ${Else} + SearchPath $0 hh.exe + ${If} ${FileExists} $0 + Exec '"$0" mk:@MSITStore:$INSTDIR\NSIS.chm::/SectionF.1.html' + ${Else} + ExecShell "open" "http://nsis.sourceforge.net/Docs/AppendixF.html#F.1" + ${EndIf} + ${EndIf} +FunctionEnd + +;-------------------------------- +;Uninstaller Section + +Section Uninstall + + SetDetailsPrint textonly + DetailPrint "Uninstalling NSI Development Shell Extensions..." + SetDetailsPrint listonly + + IfFileExists $INSTDIR\makensis.exe nsis_installed + MessageBox MB_YESNO "It does not appear that NSIS is installed in the directory '$INSTDIR'.$\r$\nContinue anyway (not recommended)?" IDYES nsis_installed + Abort "Uninstall aborted by user" + nsis_installed: + + SetDetailsPrint textonly + DetailPrint "Deleting Registry Keys..." + SetDetailsPrint listonly + + ReadRegStr $R0 HKCR ".nsi" "" + StrCmp $R0 "NSIS.Script" 0 +2 + DeleteRegKey HKCR ".nsi" + + ReadRegStr $R0 HKCR ".nsh" "" + StrCmp $R0 "NSIS.Header" 0 +2 + DeleteRegKey HKCR ".nsh" + + DeleteRegKey HKCR "NSIS.Script" + DeleteRegKey HKCR "NSIS.Header" + + System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' + + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSIS" + DeleteRegKey HKLM "Software\NSIS" + + SetDetailsPrint textonly + DetailPrint "Deleting Files..." + SetDetailsPrint listonly + + Delete $SMPROGRAMS\NSIS.lnk + Delete $DESKTOP\NSIS.lnk + Delete $INSTDIR\makensis.exe + Delete $INSTDIR\makensisw.exe + Delete $INSTDIR\NSIS.exe + Delete $INSTDIR\license.txt + Delete $INSTDIR\COPYING + Delete $INSTDIR\uninst-nsis.exe + Delete $INSTDIR\nsisconf.nsi + Delete $INSTDIR\nsisconf.nsh + Delete $INSTDIR\NSIS.chm + RMDir /r $INSTDIR\Bin + RMDir /r $INSTDIR\Contrib + RMDir /r $INSTDIR\Docs + RMDir /r $INSTDIR\Examples + RMDir /r $INSTDIR\Include + RMDir /r $INSTDIR\Menu + RMDir /r $INSTDIR\Plugins + RMDir /r $INSTDIR\Stubs + RMDir $INSTDIR + + SetDetailsPrint both + +SectionEnd diff --git a/installer/NSIS/Examples/nsDialogs/InstallOptions.nsi b/installer/NSIS/Examples/nsDialogs/InstallOptions.nsi new file mode 100644 index 0000000..9fe173c --- /dev/null +++ b/installer/NSIS/Examples/nsDialogs/InstallOptions.nsi @@ -0,0 +1,47 @@ +!include LogicLib.nsh +!include WinMessages.nsh + +Name "nsDialogs IO" +OutFile "nsDialogs IO.exe" + +Page custom nsDialogsIO UpdateINIState +Page instfiles + +XPStyle on + +ShowInstDetails show + +!include nsDialogs.nsh +!insertmacro NSD_FUNCTION_INIFILE + +Function nsDialogsIO + + InitPluginsDir + File /oname=$PLUGINSDIR\io.ini "${NSISDIR}\Examples\InstallOptions\test.ini" + + ${If} ${Cmd} `MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Test the right-to-left version?" IDYES` + WriteINIStr $PLUGINSDIR\io.ini Settings RTL 1 + ${EndIf} + + StrCpy $0 $PLUGINSDIR\io.ini + + Call CreateDialogFromINI + +FunctionEnd + +Section + + ReadINIStr $0 "$PLUGINSDIR\io.ini" "Field 2" "State" + DetailPrint "Install X=$0" + ReadINIStr $0 "$PLUGINSDIR\io.ini" "Field 3" "State" + DetailPrint "Install Y=$0" + ReadINIStr $0 "$PLUGINSDIR\io.ini" "Field 4" "State" + DetailPrint "Install Z=$0" + ReadINIStr $0 "$PLUGINSDIR\io.ini" "Field 5" "State" + DetailPrint "File=$0" + ReadINIStr $0 "$PLUGINSDIR\io.ini" "Field 6" "State" + DetailPrint "Dir=$0" + ReadINIStr $0 "$PLUGINSDIR\io.ini" "Field 8" "State" + DetailPrint "Info=$0" + +SectionEnd diff --git a/installer/NSIS/Examples/nsDialogs/example.nsi b/installer/NSIS/Examples/nsDialogs/example.nsi new file mode 100644 index 0000000..729918a --- /dev/null +++ b/installer/NSIS/Examples/nsDialogs/example.nsi @@ -0,0 +1,82 @@ +!include nsDialogs.nsh +!include LogicLib.nsh + +Name "nsDialogs Example" +OutFile "nsDialogs Example.exe" + +XPStyle on + +Page license +Page custom nsDialogsPage + +Var BUTTON +Var EDIT +Var CHECKBOX + +Function nsDialogsPage + + nsDialogs::Create 1018 + Pop $0 + + GetFunctionAddress $0 OnBack + nsDialogs::OnBack $0 + + ${NSD_CreateButton} 0 0 100% 12u Test + Pop $BUTTON + GetFunctionAddress $0 OnClick + nsDialogs::OnClick $BUTTON $0 + + ${NSD_CreateText} 0 35 100% 12u hello + Pop $EDIT + GetFunctionAddress $0 OnChange + nsDialogs::OnChange $EDIT $0 + + ${NSD_CreateCheckbox} 0 -50 100% 8u Test + Pop $CHECKBOX + GetFunctionAddress $0 OnCheckbox + nsDialogs::OnClick $CHECKBOX $0 + + ${NSD_CreateLabel} 0 40u 75% 40u "* Type `hello there` above.$\n* Click the button.$\n* Check the checkbox.$\n* Hit the Back button." + Pop $0 + + nsDialogs::Show + +FunctionEnd + +Function OnClick + + Pop $0 # HWND + + MessageBox MB_OK clicky + +FunctionEnd + +Function OnChange + + Pop $0 # HWND + + System::Call user32::GetWindowText(i$EDIT,t.r0,i${NSIS_MAX_STRLEN}) + + ${If} $0 == "hello there" + MessageBox MB_OK "right back at ya" + ${EndIf} + +FunctionEnd + +Function OnBack + + MessageBox MB_YESNO "are you sure?" IDYES +2 + Abort + +FunctionEnd + +Function OnCheckbox + + Pop $0 # HWND + + MessageBox MB_OK "checkbox clicked" + +FunctionEnd + +Section +SectionEnd diff --git a/installer/NSIS/Examples/nsDialogs/timer.nsi b/installer/NSIS/Examples/nsDialogs/timer.nsi new file mode 100644 index 0000000..cb6449f --- /dev/null +++ b/installer/NSIS/Examples/nsDialogs/timer.nsi @@ -0,0 +1,102 @@ +!include LogicLib.nsh +!include nsDialogs.nsh + +Name "nsDialogs Timer Example" +OutFile "nsDialogs Timer Example.exe" +XPStyle on + +Var DIALOG +Var TEXT +Var PROGBAR +Var PROGBAR2 +Var PROGBAR3 +Var BUTTON +Var BUTTON2 + +Page custom nsDialogsPage + +Function OnTimer + + SendMessage $PROGBAR ${PBM_GETPOS} 0 0 $1 + ${If} $1 = 100 + SendMessage $PROGBAR ${PBM_SETPOS} 0 0 + ${Else} + SendMessage $PROGBAR ${PBM_DELTAPOS} 10 0 + ${EndIf} + +FunctionEnd + +Function OnTimer2 + + SendMessage $PROGBAR2 ${PBM_GETPOS} 0 0 $1 + ${If} $1 = 100 + SendMessage $PROGBAR2 ${PBM_SETPOS} 0 0 + ${Else} + SendMessage $PROGBAR2 ${PBM_DELTAPOS} 5 0 + ${EndIf} + +FunctionEnd + +Function OnTimer3 + + SendMessage $PROGBAR3 ${PBM_GETPOS} 0 0 $1 + ${If} $1 >= 100 + ${NSD_KillTimer} OnTimer3 + MessageBox MB_OK "Timer 3 killed" + ${Else} + SendMessage $PROGBAR3 ${PBM_DELTAPOS} 2 0 + ${EndIf} + +FunctionEnd + +Function OnClick + + Pop $0 + + ${NSD_KillTimer} OnTimer + +FunctionEnd + +Function OnClick2 + + Pop $0 + + ${NSD_KillTimer} OnTimer2 + +FunctionEnd + +Function nsDialogsPage + + nsDialogs::Create 1018 + Pop $DIALOG + + ${NSD_CreateLabel} 0u 0u 100% 9u "nsDialogs timer example" + Pop $TEXT + + ${NSD_CreateProgressBar} 0u 10u 100% 12u "" + Pop $PROGBAR + + ${NSD_CreateButton} 0u 25u 100u 14u "Kill Timer 1" + Pop $BUTTON + ${NSD_OnClick} $BUTTON OnClick + + ${NSD_CreateProgressBar} 0u 52u 100% 12u "" + Pop $PROGBAR2 + + ${NSD_CreateButton} 0u 67u 100u 14u "Kill Timer 2" + Pop $BUTTON2 + ${NSD_OnClick} $BUTTON2 OnClick2 + + ${NSD_CreateProgressBar} 0u 114u 100% 12u "" + Pop $PROGBAR3 + + ${NSD_CreateTimer} OnTimer 1000 + ${NSD_CreateTimer} OnTimer2 100 + ${NSD_CreateTimer} OnTimer3 200 + + nsDialogs::Show + +FunctionEnd + +Section +SectionEnd diff --git a/installer/NSIS/Examples/nsDialogs/welcome.nsi b/installer/NSIS/Examples/nsDialogs/welcome.nsi new file mode 100644 index 0000000..7f0c255 --- /dev/null +++ b/installer/NSIS/Examples/nsDialogs/welcome.nsi @@ -0,0 +1,320 @@ +!include MUI.nsh +!include LogicLib.nsh +!include WinMessages.nsh +!include FileFunc.nsh + +Name "nsDialogs Welcome" +OutFile "nsDialogs Welcome.exe" + +Page custom nsDialogsWelcome +Page custom nsDialogsDirectory +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +!define WS_EX_CLIENTEDGE 0x00000200 + +!define WS_CHILD 0x40000000 +!define WS_VISIBLE 0x10000000 +!define WS_DISABLED 0x08000000 +!define WS_CLIPSIBLINGS 0x04000000 +!define WS_MAXIMIZE 0x01000000 +!define WS_VSCROLL 0x00200000 +!define WS_HSCROLL 0x00100000 +!define WS_GROUP 0x00020000 +!define WS_TABSTOP 0x00010000 + +!define ES_LEFT 0x00000000 +!define ES_CENTER 0x00000001 +!define ES_RIGHT 0x00000002 +!define ES_MULTILINE 0x00000004 +!define ES_UPPERCASE 0x00000008 +!define ES_LOWERCASE 0x00000010 +!define ES_PASSWORD 0x00000020 +!define ES_AUTOVSCROLL 0x00000040 +!define ES_AUTOHSCROLL 0x00000080 +!define ES_NOHIDESEL 0x00000100 +!define ES_OEMCONVERT 0x00000400 +!define ES_READONLY 0x00000800 +!define ES_WANTRETURN 0x00001000 +!define ES_NUMBER 0x00002000 + +!define SS_LEFT 0x00000000 +!define SS_CENTER 0x00000001 +!define SS_RIGHT 0x00000002 +!define SS_ICON 0x00000003 +!define SS_BLACKRECT 0x00000004 +!define SS_GRAYRECT 0x00000005 +!define SS_WHITERECT 0x00000006 +!define SS_BLACKFRAME 0x00000007 +!define SS_GRAYFRAME 0x00000008 +!define SS_WHITEFRAME 0x00000009 +!define SS_USERITEM 0x0000000A +!define SS_SIMPLE 0x0000000B +!define SS_LEFTNOWORDWRAP 0x0000000C +!define SS_OWNERDRAW 0x0000000D +!define SS_BITMAP 0x0000000E +!define SS_ENHMETAFILE 0x0000000F +!define SS_ETCHEDHORZ 0x00000010 +!define SS_ETCHEDVERT 0x00000011 +!define SS_ETCHEDFRAME 0x00000012 +!define SS_TYPEMASK 0x0000001F +!define SS_REALSIZECONTROL 0x00000040 +!define SS_NOPREFIX 0x00000080 +!define SS_NOTIFY 0x00000100 +!define SS_CENTERIMAGE 0x00000200 +!define SS_RIGHTJUST 0x00000400 +!define SS_REALSIZEIMAGE 0x00000800 +!define SS_SUNKEN 0x00001000 +!define SS_EDITCONTROL 0x00002000 +!define SS_ENDELLIPSIS 0x00004000 +!define SS_PATHELLIPSIS 0x00008000 +!define SS_WORDELLIPSIS 0x0000C000 +!define SS_ELLIPSISMASK 0x0000C000 + +!define BS_PUSHBUTTON 0x00000000 +!define BS_DEFPUSHBUTTON 0x00000001 +!define BS_CHECKBOX 0x00000002 +!define BS_AUTOCHECKBOX 0x00000003 +!define BS_RADIOBUTTON 0x00000004 +!define BS_3STATE 0x00000005 +!define BS_AUTO3STATE 0x00000006 +!define BS_GROUPBOX 0x00000007 +!define BS_USERBUTTON 0x00000008 +!define BS_AUTORADIOBUTTON 0x00000009 +!define BS_PUSHBOX 0x0000000A +!define BS_OWNERDRAW 0x0000000B +!define BS_TYPEMASK 0x0000000F +!define BS_LEFTTEXT 0x00000020 +!define BS_TEXT 0x00000000 +!define BS_ICON 0x00000040 +!define BS_BITMAP 0x00000080 +!define BS_LEFT 0x00000100 +!define BS_RIGHT 0x00000200 +!define BS_CENTER 0x00000300 +!define BS_TOP 0x00000400 +!define BS_BOTTOM 0x00000800 +!define BS_VCENTER 0x00000C00 +!define BS_PUSHLIKE 0x00001000 +!define BS_MULTILINE 0x00002000 +!define BS_NOTIFY 0x00004000 +!define BS_FLAT 0x00008000 +!define BS_RIGHTBUTTON ${BS_LEFTTEXT} + +!define LR_DEFAULTCOLOR 0x0000 +!define LR_MONOCHROME 0x0001 +!define LR_COLOR 0x0002 +!define LR_COPYRETURNORG 0x0004 +!define LR_COPYDELETEORG 0x0008 +!define LR_LOADFROMFILE 0x0010 +!define LR_LOADTRANSPARENT 0x0020 +!define LR_DEFAULTSIZE 0x0040 +!define LR_VGACOLOR 0x0080 +!define LR_LOADMAP3DCOLORS 0x1000 +!define LR_CREATEDIBSECTION 0x2000 +!define LR_COPYFROMRESOURCE 0x4000 +!define LR_SHARED 0x8000 + +!define IMAGE_BITMAP 0 +!define IMAGE_ICON 1 +!define IMAGE_CURSOR 2 +!define IMAGE_ENHMETAFILE 3 + +Var DIALOG +Var HEADLINE +Var TEXT +Var IMAGECTL +Var IMAGE +Var DIRECTORY +Var FREESPACE + +Var HEADLINE_FONT + +Function .onInit + + CreateFont $HEADLINE_FONT "$(^Font)" "14" "700" + + InitPluginsDir + File /oname=$PLUGINSDIR\welcome.bmp "${NSISDIR}\Contrib\Graphics\Wizard\orange-nsis.bmp" + +FunctionEnd + +Function HideControls + + LockWindow on + GetDlgItem $0 $HWNDPARENT 1028 + ShowWindow $0 ${SW_HIDE} + + GetDlgItem $0 $HWNDPARENT 1256 + ShowWindow $0 ${SW_HIDE} + + GetDlgItem $0 $HWNDPARENT 1035 + ShowWindow $0 ${SW_HIDE} + + GetDlgItem $0 $HWNDPARENT 1037 + ShowWindow $0 ${SW_HIDE} + + GetDlgItem $0 $HWNDPARENT 1038 + ShowWindow $0 ${SW_HIDE} + + GetDlgItem $0 $HWNDPARENT 1039 + ShowWindow $0 ${SW_HIDE} + + GetDlgItem $0 $HWNDPARENT 1045 + ShowWindow $0 ${SW_NORMAL} + LockWindow off + +FunctionEnd + +Function ShowControls + + LockWindow on + GetDlgItem $0 $HWNDPARENT 1028 + ShowWindow $0 ${SW_NORMAL} + + GetDlgItem $0 $HWNDPARENT 1256 + ShowWindow $0 ${SW_NORMAL} + + GetDlgItem $0 $HWNDPARENT 1035 + ShowWindow $0 ${SW_NORMAL} + + GetDlgItem $0 $HWNDPARENT 1037 + ShowWindow $0 ${SW_NORMAL} + + GetDlgItem $0 $HWNDPARENT 1038 + ShowWindow $0 ${SW_NORMAL} + + GetDlgItem $0 $HWNDPARENT 1039 + ShowWindow $0 ${SW_NORMAL} + + GetDlgItem $0 $HWNDPARENT 1045 + ShowWindow $0 ${SW_HIDE} + LockWindow off + +FunctionEnd + +Function nsDialogsWelcome + + nsDialogs::Create 1044 + Pop $DIALOG + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS}|${SS_BITMAP} 0 0 0 109u 193u "" + Pop $IMAGECTL + + StrCpy $0 $PLUGINSDIR\welcome.bmp + System::Call 'user32::LoadImage(i 0, t r0, i ${IMAGE_BITMAP}, i 0, i 0, i ${LR_LOADFROMFILE}) i.s' + Pop $IMAGE + + SendMessage $IMAGECTL ${STM_SETIMAGE} ${IMAGE_BITMAP} $IMAGE + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS} 0 120u 10u -130u 20u "Welcome to nsDialogs!" + Pop $HEADLINE + + SendMessage $HEADLINE ${WM_SETFONT} $HEADLINE_FONT 0 + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS} 0 120u 32u -130u -32u "nsDialogs is the next generation of user interfaces in NSIS. It gives the developer full control over custom pages. Some of the features include control text containing variables, callbacks directly into script functions and creation of any type of control. Create boring old edit boxes or load some external library and create custom controls with no need of creating your own plug-in.$\r$\n$\r$\nUnlike InstallOptions, nsDialogs doesn't use INI files to communicate with the script. By interacting directly with the script, nsDialogs can perform much faster without the need of costly, old and inefficient INI operations. Direct interaction also allows direct calls to functions defined in the script and removes the need of conversion functions like Io2Nsis.$\r$\n$\r$\nHit the Next button to see how it all fits into a mock directory page." + Pop $TEXT + + SetCtlColors $DIALOG "" 0xffffff + SetCtlColors $HEADLINE "" 0xffffff + SetCtlColors $TEXT "" 0xffffff + + Call HideControls + + nsDialogs::Show + + Call ShowControls + + System::Call gdi32::DeleteObject(i$IMAGE) + +FunctionEnd + +!define SHACF_FILESYSTEM 1 + +Function nsDialogsDirectory + + !insertmacro MUI_HEADER_TEXT "Choose Install Location" "Choose the folder in which to install $(^NameDA)." + + GetDlgItem $0 $HWNDPARENT 1 + EnableWindow $0 0 + + nsDialogs::Create 1018 + Pop $DIALOG + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS}|${SS_CENTER} 0 0 0 100% 30 "Directory page" + Pop $HEADLINE + + SendMessage $HEADLINE ${WM_SETFONT} $HEADLINE_FONT 0 + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS} 0 0 30 100% 40 "Select the installation directory of NSIS to continue. $_CLICK" + Pop $TEXT + + nsDialogs::CreateControl EDIT ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS}|${ES_AUTOHSCROLL}|${WS_TABSTOP} ${WS_EX_CLIENTEDGE} 0 75 100% 12u "" + Pop $DIRECTORY + + SendMessage $HWNDPARENT ${WM_NEXTDLGCTL} $DIRECTORY 1 + + GetFunctionAddress $0 DirChange + nsDialogs::OnChange $DIRECTORY $0 + + System::Call shlwapi::SHAutoComplete(i$DIRECTORY,i${SHACF_FILESYSTEM}) + + nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS} 0 0 -10u 100% 10u "" + Pop $FREESPACE + + Call UpdateFreeSpace + + nsDialogs::Show + +FunctionEnd + +Function UpdateFreeSpace + + ${GetRoot} $INSTDIR $0 + StrCpy $1 " bytes" + + System::Call kernel32::GetDiskFreeSpaceEx(tr0,*l,*l,*l.r0) + + ${If} $0 > 1024 + ${OrIf} $0 < 0 + System::Int64Op $0 / 1024 + Pop $0 + StrCpy $1 "kb" + ${If} $0 > 1024 + ${OrIf} $0 < 0 + System::Int64Op $0 / 1024 + Pop $0 + StrCpy $1 "mb" + ${If} $0 > 1024 + ${OrIf} $0 < 0 + System::Int64Op $0 / 1024 + Pop $0 + StrCpy $1 "gb" + ${EndIf} + ${EndIf} + ${EndIf} + + SendMessage $FREESPACE ${WM_SETTEXT} 0 "STR:Free space: $0$1" + +FunctionEnd + +Function DirChange + + Pop $0 # dir hwnd + + GetDlgItem $0 $HWNDPARENT 1 + + System::Call user32::GetWindowText(i$DIRECTORY,t.d,i${NSIS_MAX_STRLEN}) + + ${If} ${FileExists} $INSTDIR\makensis.exe + EnableWindow $0 1 + ${Else} + EnableWindow $0 0 + ${EndIf} + + Call UpdateFreeSpace + +FunctionEnd + +Section +SectionEnd diff --git a/installer/NSIS/Examples/nsExec/test.nsi b/installer/NSIS/Examples/nsExec/test.nsi new file mode 100644 index 0000000..5a0c931 --- /dev/null +++ b/installer/NSIS/Examples/nsExec/test.nsi @@ -0,0 +1,31 @@ +Name "nsExec Test" + +OutFile "nsExec Test.exe" + +ShowInstDetails show + +Section "Silent MakeNSIS" + nsExec::Exec '"${NSISDIR}\makensis.exe"' + Pop $0 # return value/error/timeout + DetailPrint "" + DetailPrint " Return value: $0" + DetailPrint "" +SectionEnd + +Section "MakeNSIS commands help" + nsExec::ExecToLog '"${NSISDIR}\makensis.exe" /CMDHELP' + Pop $0 # return value/error/timeout + DetailPrint "" + DetailPrint " Return value: $0" + DetailPrint "" +SectionEnd + +Section "Output to variable" + nsExec::ExecToStack '"${NSISDIR}\makensis.exe" /VERSION' + Pop $0 # return value/error/timeout + Pop $1 # printed text, up to ${NSIS_MAX_STRLEN} + DetailPrint '"${NSISDIR}\makensis.exe" /VERSION printed: $1' + DetailPrint "" + DetailPrint " Return value: $0" + DetailPrint "" +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/one-section.nsi b/installer/NSIS/Examples/one-section.nsi new file mode 100644 index 0000000..033ea0a --- /dev/null +++ b/installer/NSIS/Examples/one-section.nsi @@ -0,0 +1,79 @@ +; one-section.nsi +; +; This example demonstrates how to control section selection. +; It allows only one of the sections of a group to be selected. + +;-------------------------------- + +; Section define/macro header file +; See this header file for more info + +!include "Sections.nsh" + +;-------------------------------- + +Name "One Section" +OutFile "one-section.exe" +RequestExecutionLevel user + +;-------------------------------- + +; Pages + +Page components + +;-------------------------------- + +; Sections + +Section !Required + SectionIn RO +SectionEnd + +Section "Group 1 - Option 1" g1o1 +SectionEnd + +Section /o "Group 1 - Option 2" g1o2 +SectionEnd + +Section /o "Group 1 - Option 3" g1o3 +SectionEnd + +Section "Group 2 - Option 1" g2o1 +SectionEnd + +Section /o "Group 2 - Option 2" g2o2 +SectionEnd + +Section /o "Group 2 - Option 3" g2o3 +SectionEnd + +;-------------------------------- + +; Functions + +; $1 stores the status of group 1 +; $2 stores the status of group 2 + +Function .onInit + + StrCpy $1 ${g1o1} ; Group 1 - Option 1 is selected by default + StrCpy $2 ${g2o1} ; Group 2 - Option 1 is selected by default + +FunctionEnd + +Function .onSelChange + + !insertmacro StartRadioButtons $1 + !insertmacro RadioButton ${g1o1} + !insertmacro RadioButton ${g1o2} + !insertmacro RadioButton ${g1o3} + !insertmacro EndRadioButtons + + !insertmacro StartRadioButtons $2 + !insertmacro RadioButton ${g2o1} + !insertmacro RadioButton ${g2o2} + !insertmacro RadioButton ${g2o3} + !insertmacro EndRadioButtons + +FunctionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/primes.nsi b/installer/NSIS/Examples/primes.nsi new file mode 100644 index 0000000..08a2062 --- /dev/null +++ b/installer/NSIS/Examples/primes.nsi @@ -0,0 +1,70 @@ +; primes.nsi +; +; This is an example of the possibities of the NSIS Script language. +; It calculates prime numbers. + +;-------------------------------- + +Name "primes" +AllowRootDirInstall true +OutFile "primes.exe" +Caption "Prime number generator" +ShowInstDetails show +AllowRootDirInstall true +InstallDir "$EXEDIR" +RequestExecutionLevel user + +DirText "Select a directory to write primes.txt. $_CLICK" + +;-------------------------------- + +;Pages + +Page directory +Page instfiles + +;-------------------------------- + +Section "" + SetOutPath $INSTDIR + Call DoPrimes +SectionEnd + +;-------------------------------- + +Function DoPrimes + +; we put this in here so it doesn't update the progress bar (faster) + +!define PPOS $0 ; position in prime searching +!define PDIV $1 ; divisor +!define PMOD $2 ; the result of the modulus +!define PCNT $3 ; count of how many we've printed + FileOpen $9 $INSTDIR\primes.txt w + + DetailPrint "2 is prime!" + FileWrite $9 "2 is prime!$\r$\n" + DetailPrint "3 is prime!" + FileWrite $9 "3 is prime!$\r$\n" + Strcpy ${PPOS} 3 + Strcpy ${PCNT} 2 +outerloop: + StrCpy ${PDIV} 3 + innerloop: + IntOp ${PMOD} ${PPOS} % ${PDIV} + IntCmp ${PMOD} 0 notprime + IntOp ${PDIV} ${PDIV} + 2 + IntCmp ${PDIV} ${PPOS} 0 innerloop 0 + DetailPrint "${PPOS} is prime!" + FileWrite $9 "${PPOS} is prime!$\r$\n" + IntOp ${PCNT} ${PCNT} + 1 + IntCmp ${PCNT} 100 0 innerloop + StrCpy ${PCNT} 0 + MessageBox MB_YESNO "Process more?" IDNO stop + notprime: + IntOp ${PPOS} ${PPOS} + 2 + Goto outerloop + stop: + FileClose $9 + +FunctionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/rtest.nsi b/installer/NSIS/Examples/rtest.nsi new file mode 100644 index 0000000..e64cd72 --- /dev/null +++ b/installer/NSIS/Examples/rtest.nsi @@ -0,0 +1,93 @@ +; rtest.nsi +; +; This script tests some advanced NSIS functions. + +;-------------------------------- + +Name "rtest" +OutFile "rtest.exe" + +ComponentText "Select tests!" +ShowInstDetails show + +RequestExecutionLevel user + +;-------------------------------- + +Section "Test 1" + + StrCpy $R0 "a" + + GetFunctionAddress $R1 test1 + Call $R1 + + StrCmp $R0 "a182345678" success + + DetailPrint "Test 1 failed (output: $R0)" + Goto end + + success: + DetailPrint "Test 1 succeded (output: $R0)" + + end: + +SectionEnd + +Function test1 + + GetLabelAddress $9 skip + + IntOp $9 $9 - 1 + StrCpy $R0 $R01 + + Call $9 + + StrCpy $R0 $R02 + StrCpy $R0 $R03 + StrCpy $R0 $R04 + StrCpy $R0 $R05 + StrCpy $R0 $R06 + StrCpy $R0 $R07 + StrCpy $R0 $R08 + + skip: + +FunctionEnd + +;-------------------------------- + +Section "Test 2" + + StrCpy $R0 "0" + StrCpy $R1 "11" + + Call test2 + + StrCmp $R1 "11,10,9,8,7,6,5,4,3,2,1" success + + DetailPrint "Test 2 failed (output: $R1)" + Goto end + + success: + DetailPrint "Test 2 succeded (output: $R1)" + + end: + +SectionEnd + +Function test2 + + IntOp $R0 $R0 + 1 + IntCmp $R0 10 done + + Push $R0 + + GetFunctionAddress $R2 test2 + Call $R2 + + Pop $R0 + + done: + StrCpy $R1 "$R1,$R0" + +FunctionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/silent.nsi b/installer/NSIS/Examples/silent.nsi new file mode 100644 index 0000000..d0492ce --- /dev/null +++ b/installer/NSIS/Examples/silent.nsi @@ -0,0 +1,63 @@ +# This example shows how to handle silent installers. +# In short, you need IfSilent and the /SD switch for MessageBox to make your installer +# really silent when the /S switch is used. + +Name "Silent" +OutFile "silent.exe" +RequestExecutionLevel user + +# uncomment the following line to make the installer silent by default. +; SilentInstall silent + +Function .onInit + # `/SD IDYES' tells MessageBox to automatically choose IDYES if the installer is silent + # in this case, the installer can only be silent if the user used the /S switch or if + # you've uncommented line number 5 + MessageBox MB_YESNO|MB_ICONQUESTION "Would you like the installer to be silent from now on?" \ + /SD IDYES IDNO no IDYES yes + + # SetSilent can only be used in .onInit and doesn't work well along with `SetSilent silent' + + yes: + SetSilent silent + Goto done + no: + SetSilent normal + done: +FunctionEnd + +Section + IfSilent 0 +2 + MessageBox MB_OK|MB_ICONINFORMATION 'This is a "silent" installer' + + # there is no need to use IfSilent for this one because the /SD switch takes care of that + MessageBox MB_OK|MB_ICONINFORMATION "This is not a silent installer" /SD IDOK + + # when `SetOverwrite on' (which is the default) is used, the installer will show a message + # if it can't open a file for writing. On silent installers, the ignore option will be + # automatically selected. if `AllowSkipFiles off' (default is on) was used, there is no + # ignore option and the cancel option will be automatically selected. + + # on is default + ; AllowSkipFiles on + + # lock file + FileOpen $0 $TEMP\silentOverwrite w + # try to extract - will fail + File /oname=$TEMP\silentOverwrite silent.nsi + # unlcok + FileClose $0 + + # this will always show on silent installers because ignore is the option automatically + # selected when a file can't be opened for writing on a silent installer + MessageBox MB_OK|MB_ICONINFORMATION "This message box always shows if the installer is silent" + + AllowSkipFiles off + + # lock file + FileOpen $0 $TEMP\silentOverwrite w + # try to extract - will fail + File /oname=$TEMP\silentOverwrite silent.nsi + # unlcok + FileClose $0 +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/viewhtml.nsi b/installer/NSIS/Examples/viewhtml.nsi new file mode 100644 index 0000000..a3dc042 --- /dev/null +++ b/installer/NSIS/Examples/viewhtml.nsi @@ -0,0 +1,53 @@ +; viewhtml.nsi +; +; This script creates a silent installer which extracts one (or more) HTML +; files to a temporary directory, opens Internet Explorer to view the file(s), +; and when Internet Explorer has quit, deletes the file(s). + +;-------------------------------- + +; The name of the installer (not really used in a silent install) +Name "ViewHTML" + +; Set to silent mode +SilentInstall silent + +; The file to write +OutFile "viewhtml.exe" + +; Request application privileges for Windows Vista +RequestExecutionLevel user + +;-------------------------------- + +; The stuff to install +Section "" + + ; Get a temporary filename (in the Windows Temp directory) + GetTempFileName $R0 + + ; Extract file + ; Lets skip this one, it's not built to be showin in IE + ; File /oname=$R0 "..\Menu\compiler.html" + ; and write our own! :) + FileOpen $0 $R0 "w" + FileWrite $0 "

    HTML page for viewhtml.nsi

    " + FileClose $0 + + ; View file + ExecWait '"$PROGRAMFILES\Internet Explorer\iexplore.exe" "$R0"' + + ; Note: another way of doing this would be to use ExecShell, but then you + ; really couldn't get away with deleting the files. Here is the ExecShell + ; line that you would want to use: + ; + ; ExecShell "open" '"$R0"' + ; + ; The advantage of this way is that it would use the default browser to + ; open the HTML. + ; + + ; Delete the files (on reboot if file is in use) + Delete /REBOOTOK $R0 + +SectionEnd \ No newline at end of file diff --git a/installer/NSIS/Examples/waplugin.nsi b/installer/NSIS/Examples/waplugin.nsi new file mode 100644 index 0000000..1c0c292 --- /dev/null +++ b/installer/NSIS/Examples/waplugin.nsi @@ -0,0 +1,205 @@ +; waplugin.nsi +; +; This script will generate an installer that installs a Winamp 2 plug-in. +; +; This installer will automatically alert the user that installation was +; successful, and ask them whether or not they would like to make the +; plug-in the default and run Winamp. + +;-------------------------------- + +; Uncomment the next line to enable auto Winamp download +; !define WINAMP_AUTOINSTALL + +; The name of the installer +Name "TinyVis Plug-in" + +; The file to write +OutFile "waplugin.exe" + +; The default installation directory +InstallDir $PROGRAMFILES\Winamp + +; detect winamp path from uninstall string if available +InstallDirRegKey HKLM \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\Winamp" \ + "UninstallString" + +; The text to prompt the user to enter a directory +DirText "Please select your Winamp path below (you will be able to proceed when Winamp is detected):" +# currently doesn't work - DirShow hide + +; automatically close the installer when done. +AutoCloseWindow true + +; hide the "show details" box +ShowInstDetails nevershow + +; Request application privileges for Windows Vista +RequestExecutionLevel admin + +;-------------------------------- + +;Pages + +Page directory +Page instfiles + +;-------------------------------- + +; The stuff to install + +Section "" + +!ifdef WINAMP_AUTOINSTALL + Call MakeSureIGotWinamp +!endif + + Call QueryWinampVisPath + SetOutPath $1 + + ; File to extract + #File "C:\program files\winamp\plugins\vis_nsfs.dll" + File /oname=vis_nsfs.dll "${NSISDIR}\Plugins\TypeLib.dll" # dummy plug-in + + ; prompt user, and if they select no, go to NoWinamp + MessageBox MB_YESNO|MB_ICONQUESTION \ + "The plug-in was installed. Would you like to run Winamp now with TinyVis as the default plug-in?" \ + IDNO NoWinamp + WriteINIStr "$INSTDIR\Winamp.ini" "Winamp" "visplugin_name" "vis_nsfs.dll" + WriteINIStr "$INSTDIR\Winamp.ini" "Winamp" "visplugin_num" "0" + Exec '"$INSTDIR\Winamp.exe"' + NoWinamp: + +SectionEnd + +;-------------------------------- + +Function .onVerifyInstDir + +!ifndef WINAMP_AUTOINSTALL + + ;Check for Winamp installation + + IfFileExists $INSTDIR\Winamp.exe Good + Abort + Good: + +!endif ; WINAMP_AUTOINSTALL + +FunctionEnd + +Function QueryWinampVisPath ; sets $1 with vis path + + StrCpy $1 $INSTDIR\Plugins + ; use DSPDir instead of VISDir to get DSP plugins directory + ReadINIStr $9 $INSTDIR\winamp.ini Winamp VisDir + StrCmp $9 "" End + IfFileExists $9 0 End + StrCpy $1 $9 ; update dir + End: + +FunctionEnd + +!ifdef WINAMP_AUTOINSTALL + +Function GetWinampInstPath + + Push $0 + Push $1 + Push $2 + ReadRegStr $0 HKLM \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\Winamp" \ + "UninstallString" + StrCmp $0 "" fin + + StrCpy $1 $0 1 0 ; get firstchar + StrCmp $1 '"' "" getparent + ; if first char is ", let's remove "'s first. + StrCpy $0 $0 "" 1 + StrCpy $1 0 + rqloop: + StrCpy $2 $0 1 $1 + StrCmp $2 '"' rqdone + StrCmp $2 "" rqdone + IntOp $1 $1 + 1 + Goto rqloop + rqdone: + StrCpy $0 $0 $1 + getparent: + ; the uninstall string goes to an EXE, let's get the directory. + StrCpy $1 -1 + gploop: + StrCpy $2 $0 1 $1 + StrCmp $2 "" gpexit + StrCmp $2 "\" gpexit + IntOp $1 $1 - 1 + Goto gploop + gpexit: + StrCpy $0 $0 $1 + + StrCmp $0 "" fin + IfFileExists $0\winamp.exe fin + StrCpy $0 "" + fin: + Pop $2 + Pop $1 + Exch $0 + +FunctionEnd + +Function MakeSureIGotWinamp + + Call GetWinampInstPath + + Pop $0 + StrCmp $0 "" getwinamp + Return + + getwinamp: + + Call ConnectInternet ;Make an internet connection (if no connection available) + + StrCpy $2 "$TEMP\Winamp Installer.exe" + NSISdl::download http://download.nullsoft.com/winamp/client/winamp281_lite.exe $2 + Pop $0 + StrCmp $0 success success + SetDetailsView show + DetailPrint "download failed: $0" + Abort + success: + ExecWait '"$2" /S' + Delete $2 + Call GetWinampInstPath + Pop $0 + StrCmp $0 "" skip + StrCpy $INSTDIR $0 + skip: + +FunctionEnd + +Function ConnectInternet + + Push $R0 + + ClearErrors + Dialer::AttemptConnect + IfErrors noie3 + + Pop $R0 + StrCmp $R0 "online" connected + MessageBox MB_OK|MB_ICONSTOP "Cannot connect to the internet." + Quit + + noie3: + + ; IE3 not installed + MessageBox MB_OK|MB_ICONINFORMATION "Please connect to the internet now." + + connected: + + Pop $R0 + +FunctionEnd + +!endif ; WINAMP_AUTOINSTALL \ No newline at end of file diff --git a/installer/NSIS/Include/Colors.nsh b/installer/NSIS/Include/Colors.nsh new file mode 100644 index 0000000..64ca3a1 --- /dev/null +++ b/installer/NSIS/Include/Colors.nsh @@ -0,0 +1,75 @@ +!ifndef COLORS_NSH +!define COLORS_NSH + +!verbose push +!verbose 3 + +# Squad +# Rob Segal +# Joel +# Yathosho + + +# Predefined HTML Hex colors +!define WHITE "FFFFFF" +!define BLACK "000000" +!define YELLOW "FFFF00" +!define RED "FF0000" +!define GREEN "00FF00" +!define BLUE "0000FF" +!define MAGENTA "FF00FF" +!define CYAN "00FFFF" + +# Function to convert red , green and blue integer values to HTML Hex format +!define RGB '!insertmacro rgb2hex' + +# Function to convert red, green and blue integer values to Hexadecimal (0xRRGGBB) format +!define HEX '!insertmacro rgb2hex2' + +# Function to get the r value from a RGB number +!define GetRvalue '!insertmacro redvalue' + +# Function to get the g value from a RGB number +!define GetGvalue '!insertmacro greenvalue' + +# Function to get the b value from a RGB number +!define GetBvalue '!insertmacro bluevalue' + +# Function to get the r value from a Hex number +!define GetRvalueX '!insertmacro bluevalue' + +# Function to get the g value from a Hex number +!define GetGvalueX '!insertmacro greenvalue' + +# Function to get the r value from a HEX number +!define GetBvalueX '!insertmacro redvalue' + +!macro rgb2hex output R G B +IntFmt "${output}" "%02X" "${R}" +IntFmt "${output}" "${output}%02X" "${G}" +IntFmt "${output}" "${output}%02X" "${B}" +!macroend + +!macro rgb2hex2 output R G B +IntFmt "${output}" "%02X" "${B}" +IntFmt "${output}" "${output}%02X" "${G}" +IntFmt "${output}" "${output}%02X" "${R}" +!macroend + +!macro redvalue output hexval +StrCpy ${output} ${hexval} 2 0 +IntFmt "${output}" "%02i" "0x${output}" +!macroend + +!macro greenvalue output hexval +StrCpy ${output} ${hexval} 2 2 +IntFmt "${output}" "%02i" "0x${output}" +!macroend + +!macro bluevalue output hexval +StrCpy ${output} ${hexval} 2 4 +IntFmt "${output}" "%02i" "0x${output}" +!macroend + +!verbose pop +!endif \ No newline at end of file diff --git a/installer/NSIS/Include/DetailPrint.nsh b/installer/NSIS/Include/DetailPrint.nsh new file mode 100644 index 0000000..f5a4426 --- /dev/null +++ b/installer/NSIS/Include/DetailPrint.nsh @@ -0,0 +1,28 @@ +!ifndef OpenDetailPrint + !define OpenDetailPrint `!insertmacro _OpenDetailPrint` +!endif + +!ifndef DetailPrint + !define DetailPrint `!insertmacro _DetailPrint` +!endif + +!ifndef CloseDetailPrint + !define CloseDetailPrint `!insertmacro _CloseDetailPrint` +!endif + +!macro _OpenDetailPrint _FILE + !ifndef DETAIL_PRINT_FILE + !define DETAIL_PRINT_FILE + Var /GLOBAL DETAIL_PRINT_FILE + !endif + StrCpy $DETAIL_PRINT_FILE ${_FILE} + FileOpen $DETAIL_PRINT_FILE `${_FILE}` `w` +!macroend + +!macro _DetailPrint _STRING + FileWrite $DETAIL_PRINT_FILE `${_STRING}$\r$\n` +!macroend + +!macro _CloseDetailPrint + FileClose $DETAIL_PRINT_FILE +!macroend diff --git a/installer/NSIS/Include/FileFunc.nsh b/installer/NSIS/Include/FileFunc.nsh new file mode 100644 index 0000000..87195b8 --- /dev/null +++ b/installer/NSIS/Include/FileFunc.nsh @@ -0,0 +1,2017 @@ +/* +_____________________________________________________________________________ + + File Functions Header v3.4 +_____________________________________________________________________________ + + 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + + See documentation for more information about the following functions. + + Usage in script: + 1. !include "FileFunc.nsh" + 2. [Section|Function] + ${FileFunction} "Param1" "Param2" "..." $var + [SectionEnd|FunctionEnd] + + + FileFunction=[Locate|GetSize|DriveSpace|GetDrives|GetTime|GetFileAttributes| + GetFileVersion|GetExeName|GetExePath|GetParameters|GetOptions| + GetOptionsS|GetRoot|GetParent|GetFileName|GetBaseName|GetFileExt| + BannerTrimPath|DirState|RefreshShellIcons] + +_____________________________________________________________________________ + + Thanks to: +_____________________________________________________________________________ + +GetSize + KiCHiK (Function "FindFiles") +DriveSpace + sunjammer (Function "CheckSpaceFree") +GetDrives + deguix (Based on his idea of Function "DetectDrives") +GetTime + Takhir (Script "StatTest") and deguix (Function "FileModifiedDate") +GetFileVersion + KiCHiK (Based on his example for command "GetDLLVersion") +GetParameters + sunjammer (Based on his Function "GetParameters") +GetRoot + KiCHiK (Based on his Function "GetRoot") +GetParent + sunjammer (Based on his Function "GetParent") +GetFileName + KiCHiK (Based on his Function "GetFileName") +GetBaseName + comperio (Based on his idea of Function "GetBaseName") +GetFileExt + opher (author) +RefreshShellIcons + jerome tremblay (author) +*/ + + +;_____________________________________________________________________________ +; +; Macros +;_____________________________________________________________________________ +; +; Change log window verbosity (default: 3=no script) +; +; Example: +; !include "FileFunc.nsh" +; !insertmacro Locate +; ${FILEFUNC_VERBOSE} 4 # all verbosity +; !insertmacro VersionCompare +; ${FILEFUNC_VERBOSE} 3 # no script + +!ifndef FILEFUNC_INCLUDED +!define FILEFUNC_INCLUDED + +!include Util.nsh + +!verbose push +!verbose 3 +!ifndef _FILEFUNC_VERBOSE + !define _FILEFUNC_VERBOSE 3 +!endif +!verbose ${_FILEFUNC_VERBOSE} +!define FILEFUNC_VERBOSE `!insertmacro FILEFUNC_VERBOSE` +!verbose pop + +!macro FILEFUNC_VERBOSE _VERBOSE + !verbose push + !verbose 3 + !undef _FILEFUNC_VERBOSE + !define _FILEFUNC_VERBOSE ${_VERBOSE} + !verbose pop +!macroend + +!macro LocateCall _PATH _OPTIONS _FUNC + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push $0 + Push `${_PATH}` + Push `${_OPTIONS}` + GetFunctionAddress $0 `${_FUNC}` + Push `$0` + ${CallArtificialFunction} Locate_ + Pop $0 + !verbose pop +!macroend + +!macro GetSizeCall _PATH _OPTIONS _RESULT1 _RESULT2 _RESULT3 + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PATH}` + Push `${_OPTIONS}` + ${CallArtificialFunction} GetSize_ + Pop ${_RESULT1} + Pop ${_RESULT2} + Pop ${_RESULT3} + !verbose pop +!macroend + +!macro DriveSpaceCall _DRIVE _OPTIONS _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_DRIVE}` + Push `${_OPTIONS}` + ${CallArtificialFunction} DriveSpace_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetDrivesCall _DRV _FUNC + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push $0 + Push `${_DRV}` + GetFunctionAddress $0 `${_FUNC}` + Push `$0` + ${CallArtificialFunction} GetDrives_ + Pop $0 + !verbose pop +!macroend + +!macro GetTimeCall _FILE _OPTION _RESULT1 _RESULT2 _RESULT3 _RESULT4 _RESULT5 _RESULT6 _RESULT7 + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_FILE}` + Push `${_OPTION}` + ${CallArtificialFunction} GetTime_ + Pop ${_RESULT1} + Pop ${_RESULT2} + Pop ${_RESULT3} + Pop ${_RESULT4} + Pop ${_RESULT5} + Pop ${_RESULT6} + Pop ${_RESULT7} + !verbose pop +!macroend + +!macro GetFileAttributesCall _PATH _ATTR _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PATH}` + Push `${_ATTR}` + ${CallArtificialFunction} GetFileAttributes_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetFileVersionCall _FILE _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_FILE}` + ${CallArtificialFunction} GetFileVersion_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetExeNameCall _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + ${CallArtificialFunction} GetExeName_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetExePathCall _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + ${CallArtificialFunction} GetExePath_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetParametersCall _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + ${CallArtificialFunction} GetParameters_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetOptionsCall _PARAMETERS _OPTION _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PARAMETERS}` + Push `${_OPTION}` + ${CallArtificialFunction} GetOptions_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetOptionsSCall _PARAMETERS _OPTION _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PARAMETERS}` + Push `${_OPTION}` + ${CallArtificialFunction} GetOptionsS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetRootCall _FULLPATH _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_FULLPATH}` + ${CallArtificialFunction} GetRoot_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetParentCall _PATHSTRING _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PATHSTRING}` + ${CallArtificialFunction} GetParent_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetFileNameCall _PATHSTRING _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PATHSTRING}` + ${CallArtificialFunction} GetFileName_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetBaseNameCall _FILESTRING _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_FILESTRING}` + ${CallArtificialFunction} GetBaseName_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro GetFileExtCall _FILESTRING _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_FILESTRING}` + ${CallArtificialFunction} GetFileExt_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro BannerTrimPathCall _PATH _LENGHT _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PATH}` + Push `${_LENGHT}` + ${CallArtificialFunction} BannerTrimPath_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro DirStateCall _PATH _RESULT + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + Push `${_PATH}` + ${CallArtificialFunction} DirState_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro RefreshShellIconsCall + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + ${CallArtificialFunction} RefreshShellIcons_ + !verbose pop +!macroend + +!define Locate `!insertmacro LocateCall` +!define un.Locate `!insertmacro LocateCall` + +!macro Locate +!macroend + +!macro un.Locate +!macroend + +!macro Locate_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R6 + Push $R7 + Push $R8 + Push $R9 + ClearErrors + + StrCpy $3 '' + StrCpy $4 '' + StrCpy $5 '' + StrCpy $6 '' + StrCpy $7 '' + StrCpy $8 0 + StrCpy $R7 '' + + StrCpy $R9 $0 1 -1 + StrCmp $R9 '\' 0 +3 + StrCpy $0 $0 -1 + goto -3 + IfFileExists '$0\*.*' 0 FileFunc_Locate_error + + FileFunc_Locate_option: + StrCpy $R9 $1 1 + StrCpy $1 $1 '' 1 + StrCmp $R9 ' ' -2 + StrCmp $R9 '' FileFunc_Locate_sizeset + StrCmp $R9 '/' 0 -4 + StrCpy $9 -1 + IntOp $9 $9 + 1 + StrCpy $R9 $1 1 $9 + StrCmp $R9 '' +2 + StrCmp $R9 '/' 0 -3 + StrCpy $R8 $1 $9 + StrCpy $R8 $R8 '' 2 + StrCpy $R9 $R8 '' -1 + StrCmp $R9 ' ' 0 +3 + StrCpy $R8 $R8 -1 + goto -3 + StrCpy $R9 $1 2 + StrCpy $1 $1 '' $9 + + StrCmp $R9 'L=' 0 FileFunc_Locate_mask + StrCpy $3 $R8 + StrCmp $3 '' +6 + StrCmp $3 'FD' +5 + StrCmp $3 'F' +4 + StrCmp $3 'D' +3 + StrCmp $3 'DE' +2 + StrCmp $3 'FDE' 0 FileFunc_Locate_error + goto FileFunc_Locate_option + + FileFunc_Locate_mask: + StrCmp $R9 'M=' 0 FileFunc_Locate_size + StrCpy $4 $R8 + goto FileFunc_Locate_option + + FileFunc_Locate_size: + StrCmp $R9 'S=' 0 FileFunc_Locate_gotosubdir + StrCpy $6 $R8 + goto FileFunc_Locate_option + + FileFunc_Locate_gotosubdir: + StrCmp $R9 'G=' 0 FileFunc_Locate_banner + StrCpy $7 $R8 + StrCmp $7 '' +3 + StrCmp $7 '1' +2 + StrCmp $7 '0' 0 FileFunc_Locate_error + goto FileFunc_Locate_option + + FileFunc_Locate_banner: + StrCmp $R9 'B=' 0 FileFunc_Locate_error + StrCpy $R7 $R8 + StrCmp $R7 '' +3 + StrCmp $R7 '1' +2 + StrCmp $R7 '0' 0 FileFunc_Locate_error + goto FileFunc_Locate_option + + FileFunc_Locate_sizeset: + StrCmp $6 '' FileFunc_Locate_default + StrCpy $9 0 + StrCpy $R9 $6 1 $9 + StrCmp $R9 '' +4 + StrCmp $R9 ':' +3 + IntOp $9 $9 + 1 + goto -4 + StrCpy $5 $6 $9 + IntOp $9 $9 + 1 + StrCpy $1 $6 1 -1 + StrCpy $6 $6 -1 $9 + StrCmp $5 '' +2 + IntOp $5 $5 + 0 + StrCmp $6 '' +2 + IntOp $6 $6 + 0 + + StrCmp $1 'B' 0 +3 + StrCpy $1 1 + goto FileFunc_Locate_default + StrCmp $1 'K' 0 +3 + StrCpy $1 1024 + goto FileFunc_Locate_default + StrCmp $1 'M' 0 +3 + StrCpy $1 1048576 + goto FileFunc_Locate_default + StrCmp $1 'G' 0 FileFunc_Locate_error + StrCpy $1 1073741824 + + FileFunc_Locate_default: + StrCmp $3 '' 0 +2 + StrCpy $3 'FD' + StrCmp $4 '' 0 +2 + StrCpy $4 '*.*' + StrCmp $7 '' 0 +2 + StrCpy $7 '1' + StrCmp $R7 '' 0 +2 + StrCpy $R7 '0' + StrCpy $7 'G$7B$R7' + + StrCpy $8 1 + Push $0 + SetDetailsPrint textonly + + FileFunc_Locate_nextdir: + IntOp $8 $8 - 1 + Pop $R8 + + StrCpy $9 $7 2 2 + StrCmp $9 'B0' +3 + GetLabelAddress $9 FileFunc_Locate_findfirst + goto call + DetailPrint 'Search in: $R8' + + FileFunc_Locate_findfirst: + FindFirst $0 $R7 '$R8\$4' + IfErrors FileFunc_Locate_subdir + StrCmp $R7 '.' 0 FileFunc_Locate_dir + FindNext $0 $R7 + StrCmp $R7 '..' 0 FileFunc_Locate_dir + FindNext $0 $R7 + IfErrors 0 FileFunc_Locate_dir + FindClose $0 + goto FileFunc_Locate_subdir + + FileFunc_Locate_dir: + IfFileExists '$R8\$R7\*.*' 0 FileFunc_Locate_file + StrCpy $R6 '' + StrCmp $3 'DE' +4 + StrCmp $3 'FDE' +3 + StrCmp $3 'FD' FileFunc_Locate_precall + StrCmp $3 'F' FileFunc_Locate_findnext FileFunc_Locate_precall + FindFirst $9 $R9 '$R8\$R7\*.*' + StrCmp $R9 '.' 0 +4 + FindNext $9 $R9 + StrCmp $R9 '..' 0 +2 + FindNext $9 $R9 + FindClose $9 + IfErrors FileFunc_Locate_precall FileFunc_Locate_findnext + + FileFunc_Locate_file: + StrCmp $3 'FDE' +3 + StrCmp $3 'FD' +2 + StrCmp $3 'F' 0 FileFunc_Locate_findnext + StrCpy $R6 0 + StrCmp $5$6 '' FileFunc_Locate_precall + FileOpen $9 '$R8\$R7' r + IfErrors +3 + FileSeek $9 0 END $R6 + FileClose $9 + System::Int64Op $R6 / $1 + Pop $R6 + StrCmp $5 '' +2 + IntCmp $R6 $5 0 FileFunc_Locate_findnext + StrCmp $6 '' +2 + IntCmp $R6 $6 0 0 FileFunc_Locate_findnext + + FileFunc_Locate_precall: + StrCpy $9 0 + StrCpy $R9 '$R8\$R7' + + call: + Push $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R7 + Push $R8 + StrCmp $9 0 +4 + StrCpy $R6 '' + StrCpy $R7 '' + StrCpy $R9 '' + Call $2 + Pop $R9 + Pop $R8 + Pop $R7 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + IfErrors 0 +3 + FindClose $0 + goto FileFunc_Locate_error + StrCmp $R9 'StopLocate' 0 +3 + FindClose $0 + goto FileFunc_Locate_clearstack + goto $9 + + FileFunc_Locate_findnext: + FindNext $0 $R7 + IfErrors 0 FileFunc_Locate_dir + FindClose $0 + + FileFunc_Locate_subdir: + StrCpy $9 $7 2 + StrCmp $9 'G0' FileFunc_Locate_end + FindFirst $0 $R7 '$R8\*.*' + StrCmp $R7 '.' 0 FileFunc_Locate_pushdir + FindNext $0 $R7 + StrCmp $R7 '..' 0 FileFunc_Locate_pushdir + FindNext $0 $R7 + IfErrors 0 FileFunc_Locate_pushdir + FindClose $0 + StrCmp $8 0 FileFunc_Locate_end FileFunc_Locate_nextdir + + FileFunc_Locate_pushdir: + IfFileExists '$R8\$R7\*.*' 0 +3 + Push '$R8\$R7' + IntOp $8 $8 + 1 + FindNext $0 $R7 + IfErrors 0 FileFunc_Locate_pushdir + FindClose $0 + StrCmp $8 0 FileFunc_Locate_end FileFunc_Locate_nextdir + + FileFunc_Locate_error: + SetErrors + + FileFunc_Locate_clearstack: + StrCmp $8 0 FileFunc_Locate_end + IntOp $8 $8 - 1 + Pop $R8 + goto FileFunc_Locate_clearstack + + FileFunc_Locate_end: + SetDetailsPrint both + Pop $R9 + Pop $R8 + Pop $R7 + Pop $R6 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + !verbose pop +!macroend + +!define GetSize `!insertmacro GetSizeCall` +!define un.GetSize `!insertmacro GetSizeCall` + +!macro GetSize +!macroend + +!macro un.GetSize +!macroend + +!macro GetSize_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R3 + Push $R4 + Push $R5 + Push $R6 + Push $R7 + Push $R8 + Push $R9 + ClearErrors + + StrCpy $R9 $0 1 -1 + StrCmp $R9 '\' 0 +3 + StrCpy $0 $0 -1 + goto -3 + IfFileExists '$0\*.*' 0 FileFunc_GetSize_error + + StrCpy $3 '' + StrCpy $4 '' + StrCpy $5 '' + StrCpy $6 '' + StrCpy $8 0 + StrCpy $R3 '' + StrCpy $R4 '' + StrCpy $R5 '' + + FileFunc_GetSize_option: + StrCpy $R9 $1 1 + StrCpy $1 $1 '' 1 + StrCmp $R9 ' ' -2 + StrCmp $R9 '' FileFunc_GetSize_sizeset + StrCmp $R9 '/' 0 -4 + + StrCpy $9 -1 + IntOp $9 $9 + 1 + StrCpy $R9 $1 1 $9 + StrCmp $R9 '' +2 + StrCmp $R9 '/' 0 -3 + StrCpy $8 $1 $9 + StrCpy $8 $8 '' 2 + StrCpy $R9 $8 '' -1 + StrCmp $R9 ' ' 0 +3 + StrCpy $8 $8 -1 + goto -3 + StrCpy $R9 $1 2 + StrCpy $1 $1 '' $9 + + StrCmp $R9 'M=' 0 FileFunc_GetSize_size + StrCpy $4 $8 + goto FileFunc_GetSize_option + + FileFunc_GetSize_size: + StrCmp $R9 'S=' 0 FileFunc_GetSize_gotosubdir + StrCpy $6 $8 + goto FileFunc_GetSize_option + + FileFunc_GetSize_gotosubdir: + StrCmp $R9 'G=' 0 FileFunc_GetSize_error + StrCpy $7 $8 + StrCmp $7 '' +3 + StrCmp $7 '1' +2 + StrCmp $7 '0' 0 FileFunc_GetSize_error + goto FileFunc_GetSize_option + + FileFunc_GetSize_sizeset: + StrCmp $6 '' FileFunc_GetSize_default + StrCpy $9 0 + StrCpy $R9 $6 1 $9 + StrCmp $R9 '' +4 + StrCmp $R9 ':' +3 + IntOp $9 $9 + 1 + goto -4 + StrCpy $5 $6 $9 + IntOp $9 $9 + 1 + StrCpy $1 $6 1 -1 + StrCpy $6 $6 -1 $9 + StrCmp $5 '' +2 + IntOp $5 $5 + 0 + StrCmp $6 '' +2 + IntOp $6 $6 + 0 + + StrCmp $1 'B' 0 +4 + StrCpy $1 1 + StrCpy $2 bytes + goto FileFunc_GetSize_default + StrCmp $1 'K' 0 +4 + StrCpy $1 1024 + StrCpy $2 Kb + goto FileFunc_GetSize_default + StrCmp $1 'M' 0 +4 + StrCpy $1 1048576 + StrCpy $2 Mb + goto FileFunc_GetSize_default + StrCmp $1 'G' 0 FileFunc_GetSize_error + StrCpy $1 1073741824 + StrCpy $2 Gb + + FileFunc_GetSize_default: + StrCmp $4 '' 0 +2 + StrCpy $4 '*.*' + StrCmp $7 '' 0 +2 + StrCpy $7 '1' + + StrCpy $8 1 + Push $0 + SetDetailsPrint textonly + + FileFunc_GetSize_nextdir: + IntOp $8 $8 - 1 + Pop $R8 + FindFirst $0 $R7 '$R8\$4' + IfErrors FileFunc_GetSize_show + StrCmp $R7 '.' 0 FileFunc_GetSize_dir + FindNext $0 $R7 + StrCmp $R7 '..' 0 FileFunc_GetSize_dir + FindNext $0 $R7 + IfErrors 0 FileFunc_GetSize_dir + FindClose $0 + goto FileFunc_GetSize_show + + FileFunc_GetSize_dir: + IfFileExists '$R8\$R7\*.*' 0 FileFunc_GetSize_file + IntOp $R5 $R5 + 1 + goto FileFunc_GetSize_findnext + + FileFunc_GetSize_file: + StrCpy $R6 0 + StrCmp $5$6 '' 0 +3 + IntOp $R4 $R4 + 1 + goto FileFunc_GetSize_findnext + FileOpen $9 '$R8\$R7' r + IfErrors +3 + FileSeek $9 0 END $R6 + FileClose $9 + StrCmp $5 '' +2 + IntCmp $R6 $5 0 FileFunc_GetSize_findnext + StrCmp $6 '' +2 + IntCmp $R6 $6 0 0 FileFunc_GetSize_findnext + IntOp $R4 $R4 + 1 + System::Int64Op $R3 + $R6 + Pop $R3 + + FileFunc_GetSize_findnext: + FindNext $0 $R7 + IfErrors 0 FileFunc_GetSize_dir + FindClose $0 + + FileFunc_GetSize_show: + StrCmp $5$6 '' FileFunc_GetSize_nosize + System::Int64Op $R3 / $1 + Pop $9 + DetailPrint 'Size:$9 $2 Files:$R4 Folders:$R5' + goto FileFunc_GetSize_subdir + FileFunc_GetSize_nosize: + DetailPrint 'Files:$R4 Folders:$R5' + + FileFunc_GetSize_subdir: + StrCmp $7 0 FileFunc_GetSize_preend + FindFirst $0 $R7 '$R8\*.*' + StrCmp $R7 '.' 0 FileFunc_GetSize_pushdir + FindNext $0 $R7 + StrCmp $R7 '..' 0 FileFunc_GetSize_pushdir + FindNext $0 $R7 + IfErrors 0 FileFunc_GetSize_pushdir + FindClose $0 + StrCmp $8 0 FileFunc_GetSize_preend FileFunc_GetSize_nextdir + + FileFunc_GetSize_pushdir: + IfFileExists '$R8\$R7\*.*' 0 +3 + Push '$R8\$R7' + IntOp $8 $8 + 1 + FindNext $0 $R7 + IfErrors 0 FileFunc_GetSize_pushdir + FindClose $0 + StrCmp $8 0 FileFunc_GetSize_preend FileFunc_GetSize_nextdir + + FileFunc_GetSize_preend: + StrCmp $R3 '' FileFunc_GetSize_nosizeend + System::Int64Op $R3 / $1 + Pop $R3 + FileFunc_GetSize_nosizeend: + StrCpy $2 $R4 + StrCpy $1 $R5 + StrCpy $0 $R3 + goto FileFunc_GetSize_end + + FileFunc_GetSize_error: + SetErrors + StrCpy $0 '' + StrCpy $1 '' + StrCpy $2 '' + + FileFunc_GetSize_end: + SetDetailsPrint both + Pop $R9 + Pop $R8 + Pop $R7 + Pop $R6 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Exch $2 + Exch + Exch $1 + Exch 2 + Exch $0 + + !verbose pop +!macroend + +!define DriveSpace `!insertmacro DriveSpaceCall` +!define un.DriveSpace `!insertmacro DriveSpaceCall` + +!macro DriveSpace +!macroend + +!macro un.DriveSpace +!macroend + +!macro DriveSpace_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + ClearErrors + + StrCpy $2 $0 1 -1 + StrCmp $2 '\' 0 +3 + StrCpy $0 $0 -1 + goto -3 + IfFileExists '$0\NUL' 0 FileFunc_DriveSpace_error + + StrCpy $5 '' + StrCpy $6 '' + + FileFunc_DriveSpace_option: + StrCpy $2 $1 1 + StrCpy $1 $1 '' 1 + StrCmp $2 ' ' -2 + StrCmp $2 '' FileFunc_DriveSpace_default + StrCmp $2 '/' 0 -4 + StrCpy $3 -1 + IntOp $3 $3 + 1 + StrCpy $2 $1 1 $3 + StrCmp $2 '' +2 + StrCmp $2 '/' 0 -3 + StrCpy $4 $1 $3 + StrCpy $4 $4 '' 2 + StrCpy $2 $4 1 -1 + StrCmp $2 ' ' 0 +3 + StrCpy $4 $4 -1 + goto -3 + StrCpy $2 $1 2 + StrCpy $1 $1 '' $3 + + StrCmp $2 'D=' 0 FileFunc_DriveSpace_unit + StrCpy $5 $4 + StrCmp $5 '' +4 + StrCmp $5 'T' +3 + StrCmp $5 'O' +2 + StrCmp $5 'F' 0 FileFunc_DriveSpace_error + goto FileFunc_DriveSpace_option + + FileFunc_DriveSpace_unit: + StrCmp $2 'S=' 0 FileFunc_DriveSpace_error + StrCpy $6 $4 + goto FileFunc_DriveSpace_option + + FileFunc_DriveSpace_default: + StrCmp $5 '' 0 +2 + StrCpy $5 'T' + StrCmp $6 '' 0 +3 + StrCpy $6 '1' + goto FileFunc_DriveSpace_getspace + + StrCmp $6 'B' 0 +3 + StrCpy $6 1 + goto FileFunc_DriveSpace_getspace + StrCmp $6 'K' 0 +3 + StrCpy $6 1024 + goto FileFunc_DriveSpace_getspace + StrCmp $6 'M' 0 +3 + StrCpy $6 1048576 + goto FileFunc_DriveSpace_getspace + StrCmp $6 'G' 0 FileFunc_DriveSpace_error + StrCpy $6 1073741824 + + FileFunc_DriveSpace_getspace: + System::Call 'kernel32::GetDiskFreeSpaceExA(t, *l, *l, *l)i(r0,.r2,.r3,.)' + + StrCmp $5 T 0 +3 + StrCpy $0 $3 + goto FileFunc_DriveSpace_getsize + StrCmp $5 O 0 +4 + System::Int64Op $3 - $2 + Pop $0 + goto FileFunc_DriveSpace_getsize + StrCmp $5 F 0 +2 + StrCpy $0 $2 + + FileFunc_DriveSpace_getsize: + System::Int64Op $0 / $6 + Pop $0 + goto FileFunc_DriveSpace_end + + FileFunc_DriveSpace_error: + SetErrors + StrCpy $0 '' + + FileFunc_DriveSpace_end: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetDrives `!insertmacro GetDrivesCall` +!define un.GetDrives `!insertmacro GetDrivesCall` + +!macro GetDrives +!macroend + +!macro un.GetDrives +!macroend + +!macro GetDrives_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $8 + Push $9 + + System::Alloc 1024 + Pop $2 + System::Call 'kernel32::GetLogicalDriveStringsA(i,i) i(1024, r2)' + + StrCmp $0 ALL FileFunc_GetDrives_drivestring + StrCmp $0 '' 0 FileFunc_GetDrives_typeset + StrCpy $0 ALL + goto FileFunc_GetDrives_drivestring + + FileFunc_GetDrives_typeset: + StrCpy $6 -1 + IntOp $6 $6 + 1 + StrCpy $8 $0 1 $6 + StrCmp $8$0 '' FileFunc_GetDrives_enumex + StrCmp $8 '' +2 + StrCmp $8 '+' 0 -4 + StrCpy $8 $0 $6 + IntOp $6 $6 + 1 + StrCpy $0 $0 '' $6 + + StrCmp $8 'FDD' 0 +3 + StrCpy $6 2 + goto FileFunc_GetDrives_drivestring + StrCmp $8 'HDD' 0 +3 + StrCpy $6 3 + goto FileFunc_GetDrives_drivestring + StrCmp $8 'NET' 0 +3 + StrCpy $6 4 + goto FileFunc_GetDrives_drivestring + StrCmp $8 'CDROM' 0 +3 + StrCpy $6 5 + goto FileFunc_GetDrives_drivestring + StrCmp $8 'RAM' 0 FileFunc_GetDrives_typeset + StrCpy $6 6 + + FileFunc_GetDrives_drivestring: + StrCpy $3 $2 + + FileFunc_GetDrives_enumok: + System::Call 'kernel32::lstrlenA(t) i(i r3) .r4' + StrCmp $4$0 '0ALL' FileFunc_GetDrives_enumex + StrCmp $4 0 FileFunc_GetDrives_typeset + System::Call 'kernel32::GetDriveTypeA(t) i(i r3) .r5' + + StrCmp $0 ALL +2 + StrCmp $5 $6 FileFunc_GetDrives_letter FileFunc_GetDrives_enumnext + StrCmp $5 2 0 +3 + StrCpy $8 FDD + goto FileFunc_GetDrives_letter + StrCmp $5 3 0 +3 + StrCpy $8 HDD + goto FileFunc_GetDrives_letter + StrCmp $5 4 0 +3 + StrCpy $8 NET + goto FileFunc_GetDrives_letter + StrCmp $5 5 0 +3 + StrCpy $8 CDROM + goto FileFunc_GetDrives_letter + StrCmp $5 6 0 FileFunc_GetDrives_enumex + StrCpy $8 RAM + + FileFunc_GetDrives_letter: + System::Call '*$3(&t1024 .r9)' + + Push $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $8 + Call $1 + Pop $9 + Pop $8 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + StrCmp $9 'StopGetDrives' FileFunc_GetDrives_enumex + + FileFunc_GetDrives_enumnext: + IntOp $3 $3 + $4 + IntOp $3 $3 + 1 + goto FileFunc_GetDrives_enumok + + FileFunc_GetDrives_enumex: + System::Free $2 + + Pop $9 + Pop $8 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + !verbose pop +!macroend + +!define GetTime `!insertmacro GetTimeCall` +!define un.GetTime `!insertmacro GetTimeCall` + +!macro GetTime +!macroend + +!macro un.GetTime +!macroend + +!macro GetTime_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + ClearErrors + + StrCmp $1 'L' FileFunc_GetTime_gettime + StrCmp $1 'A' FileFunc_GetTime_getfile + StrCmp $1 'C' FileFunc_GetTime_getfile + StrCmp $1 'M' FileFunc_GetTime_getfile + StrCmp $1 'LS' FileFunc_GetTime_gettime + StrCmp $1 'AS' FileFunc_GetTime_getfile + StrCmp $1 'CS' FileFunc_GetTime_getfile + StrCmp $1 'MS' FileFunc_GetTime_getfile + goto FileFunc_GetTime_error + + FileFunc_GetTime_getfile: + IfFileExists $0 0 FileFunc_GetTime_error + System::Call '*(i,l,l,l,i,i,i,i,&t260,&t14) i .r6' + System::Call 'kernel32::FindFirstFileA(t,i)i(r0,r6) .r2' + System::Call 'kernel32::FindClose(i)i(r2)' + + FileFunc_GetTime_gettime: + System::Call '*(&i2,&i2,&i2,&i2,&i2,&i2,&i2,&i2) i .r7' + StrCmp $1 'L' 0 FileFunc_GetTime_systemtime + System::Call 'kernel32::GetLocalTime(i)i(r7)' + goto FileFunc_GetTime_convert + FileFunc_GetTime_systemtime: + StrCmp $1 'LS' 0 FileFunc_GetTime_filetime + System::Call 'kernel32::GetSystemTime(i)i(r7)' + goto FileFunc_GetTime_convert + + FileFunc_GetTime_filetime: + System::Call '*$6(i,l,l,l,i,i,i,i,&t260,&t14)i(,.r4,.r3,.r2)' + System::Free $6 + StrCmp $1 'A' 0 +3 + StrCpy $2 $3 + goto FileFunc_GetTime_tolocal + StrCmp $1 'C' 0 +3 + StrCpy $2 $4 + goto FileFunc_GetTime_tolocal + StrCmp $1 'M' FileFunc_GetTime_tolocal + + StrCmp $1 'AS' FileFunc_GetTime_tosystem + StrCmp $1 'CS' 0 +3 + StrCpy $3 $4 + goto FileFunc_GetTime_tosystem + StrCmp $1 'MS' 0 +3 + StrCpy $3 $2 + goto FileFunc_GetTime_tosystem + + FileFunc_GetTime_tolocal: + System::Call 'kernel32::FileTimeToLocalFileTime(*l,*l)i(r2,.r3)' + FileFunc_GetTime_tosystem: + System::Call 'kernel32::FileTimeToSystemTime(*l,i)i(r3,r7)' + + FileFunc_GetTime_convert: + System::Call '*$7(&i2,&i2,&i2,&i2,&i2,&i2,&i2,&i2)i(.r5,.r6,.r4,.r0,.r3,.r2,.r1,)' + System::Free $7 + + IntCmp $0 9 0 0 +2 + StrCpy $0 '0$0' + IntCmp $1 9 0 0 +2 + StrCpy $1 '0$1' + IntCmp $2 9 0 0 +2 + StrCpy $2 '0$2' + IntCmp $6 9 0 0 +2 + StrCpy $6 '0$6' + + StrCmp $4 0 0 +3 + StrCpy $4 Sunday + goto FileFunc_GetTime_end + StrCmp $4 1 0 +3 + StrCpy $4 Monday + goto FileFunc_GetTime_end + StrCmp $4 2 0 +3 + StrCpy $4 Tuesday + goto FileFunc_GetTime_end + StrCmp $4 3 0 +3 + StrCpy $4 Wednesday + goto FileFunc_GetTime_end + StrCmp $4 4 0 +3 + StrCpy $4 Thursday + goto FileFunc_GetTime_end + StrCmp $4 5 0 +3 + StrCpy $4 Friday + goto FileFunc_GetTime_end + StrCmp $4 6 0 FileFunc_GetTime_error + StrCpy $4 Saturday + goto FileFunc_GetTime_end + + FileFunc_GetTime_error: + SetErrors + StrCpy $0 '' + StrCpy $1 '' + StrCpy $2 '' + StrCpy $3 '' + StrCpy $4 '' + StrCpy $5 '' + StrCpy $6 '' + + FileFunc_GetTime_end: + Pop $7 + Exch $6 + Exch + Exch $5 + Exch 2 + Exch $4 + Exch 3 + Exch $3 + Exch 4 + Exch $2 + Exch 5 + Exch $1 + Exch 6 + Exch $0 + + !verbose pop +!macroend + +!define GetFileAttributes `!insertmacro GetFileAttributesCall` +!define un.GetFileAttributes `!insertmacro GetFileAttributesCall` + +!macro GetFileAttributes +!macroend + +!macro un.GetFileAttributes +!macroend + +!macro GetFileAttributes_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + + System::Call 'kernel32::GetFileAttributes(t r0)i .r2' + StrCmp $2 -1 FileFunc_GetFileAttributes_error + StrCpy $3 '' + + IntOp $0 $2 & 0x4000 + IntCmp $0 0 +2 + StrCpy $3 'ENCRYPTED|' + + IntOp $0 $2 & 0x2000 + IntCmp $0 0 +2 + StrCpy $3 'NOT_CONTENT_INDEXED|$3' + + IntOp $0 $2 & 0x1000 + IntCmp $0 0 +2 + StrCpy $3 'OFFLINE|$3' + + IntOp $0 $2 & 0x0800 + IntCmp $0 0 +2 + StrCpy $3 'COMPRESSED|$3' + + IntOp $0 $2 & 0x0400 + IntCmp $0 0 +2 + StrCpy $3 'REPARSE_POINT|$3' + + IntOp $0 $2 & 0x0200 + IntCmp $0 0 +2 + StrCpy $3 'SPARSE_FILE|$3' + + IntOp $0 $2 & 0x0100 + IntCmp $0 0 +2 + StrCpy $3 'TEMPORARY|$3' + + IntOp $0 $2 & 0x0080 + IntCmp $0 0 +2 + StrCpy $3 'NORMAL|$3' + + IntOp $0 $2 & 0x0040 + IntCmp $0 0 +2 + StrCpy $3 'DEVICE|$3' + + IntOp $0 $2 & 0x0020 + IntCmp $0 0 +2 + StrCpy $3 'ARCHIVE|$3' + + IntOp $0 $2 & 0x0010 + IntCmp $0 0 +2 + StrCpy $3 'DIRECTORY|$3' + + IntOp $0 $2 & 0x0004 + IntCmp $0 0 +2 + StrCpy $3 'SYSTEM|$3' + + IntOp $0 $2 & 0x0002 + IntCmp $0 0 +2 + StrCpy $3 'HIDDEN|$3' + + IntOp $0 $2 & 0x0001 + IntCmp $0 0 +2 + StrCpy $3 'READONLY|$3' + + StrCpy $0 $3 -1 + StrCmp $1 '' FileFunc_GetFileAttributes_end + StrCmp $1 'ALL' FileFunc_GetFileAttributes_end + + FileFunc_GetFileAttributes_attrcmp: + StrCpy $5 0 + IntOp $5 $5 + 1 + StrCpy $4 $1 1 $5 + StrCmp $4 '' +2 + StrCmp $4 '|' 0 -3 + StrCpy $2 $1 $5 + IntOp $5 $5 + 1 + StrCpy $1 $1 '' $5 + StrLen $3 $2 + StrCpy $5 -1 + IntOp $5 $5 + 1 + StrCpy $4 $0 $3 $5 + StrCmp $4 '' FileFunc_GetFileAttributes_notfound + StrCmp $4 $2 0 -3 + StrCmp $1 '' 0 FileFunc_GetFileAttributes_attrcmp + StrCpy $0 1 + goto FileFunc_GetFileAttributes_end + + FileFunc_GetFileAttributes_notfound: + StrCpy $0 0 + goto FileFunc_GetFileAttributes_end + + FileFunc_GetFileAttributes_error: + SetErrors + StrCpy $0 '' + + FileFunc_GetFileAttributes_end: + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetFileVersion `!insertmacro GetFileVersionCall` +!define un.GetFileVersion `!insertmacro GetFileVersionCall` + +!macro GetFileVersion +!macroend + +!macro un.GetFileVersion +!macroend + +!macro GetFileVersion_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + ClearErrors + + GetDllVersion '$0' $1 $2 + IfErrors FileFunc_GetFileVersion_error + IntOp $3 $1 >> 16 + IntOp $3 $3 & 0x0000FFFF + IntOp $4 $1 & 0x0000FFFF + IntOp $5 $2 >> 16 + IntOp $5 $5 & 0x0000FFFF + IntOp $6 $2 & 0x0000FFFF + StrCpy $0 '$3.$4.$5.$6' + goto FileFunc_GetFileVersion_end + + FileFunc_GetFileVersion_error: + SetErrors + StrCpy $0 '' + + FileFunc_GetFileVersion_end: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetExeName `!insertmacro GetExeNameCall` +!define un.GetExeName `!insertmacro GetExeNameCall` + +!macro GetExeName +!macroend + +!macro un.GetExeName +!macroend + +!macro GetExeName_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Push $0 + Push $1 + Push $2 + System::Call 'kernel32::GetModuleFileNameA(i 0, t .r0, i 1024)' + System::Call 'kernel32::GetLongPathNameA(t r0, t .r1, i 1024)i .r2' + StrCmp $2 error +2 + StrCpy $0 $1 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetExePath `!insertmacro GetExePathCall` +!define un.GetExePath `!insertmacro GetExePathCall` + +!macro GetExePath +!macroend + +!macro un.GetExePath +!macroend + +!macro GetExePath_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Push $0 + Push $1 + Push $2 + StrCpy $0 $EXEDIR + System::Call 'kernel32::GetLongPathNameA(t r0, t .r1, i 1024)i .r2' + StrCmp $2 error +2 + StrCpy $0 $1 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetParameters `!insertmacro GetParametersCall` +!define un.GetParameters `!insertmacro GetParametersCall` + +!macro GetParameters +!macroend + +!macro un.GetParameters +!macroend + +!macro GetParameters_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + ;cmdline-check + StrCmp $CMDLINE "" 0 +3 + Push "" + Return + + ;vars + Push $0 ;tmp + Push $1 ;length + Push $2 ;parameter offset + Push $3 ;separator + + ;length/offset + StrLen $1 $CMDLINE + StrCpy $2 2 ;start with third character + + ;separator + StrCpy $3 $CMDLINE 1 ;first character + StrCmp $3 '"' +2 + StrCpy $3 ' ' + + FileFunc_GetParameters_token: ;finding second separator + IntCmp $2 $1 FileFunc_GetParameters_strip 0 FileFunc_GetParameters_strip + StrCpy $0 $CMDLINE 1 $2 + IntOp $2 $2 + 1 + StrCmp $3 $0 0 FileFunc_GetParameters_token + + FileFunc_GetParameters_strip: ;strip white space + IntCmp $2 $1 FileFunc_GetParameters_copy 0 FileFunc_GetParameters_copy + StrCpy $0 $CMDLINE 1 $2 + StrCmp $0 ' ' 0 FileFunc_GetParameters_copy + IntOp $2 $2 + 1 + Goto FileFunc_GetParameters_strip + + FileFunc_GetParameters_copy: + StrCpy $0 $CMDLINE "" $2 + + ;strip white spaces from end + FileFunc_GetParameters_rstrip: + StrCpy $1 $0 1 -1 + StrCmp $1 ' ' 0 FileFunc_GetParameters_done + StrCpy $0 $0 -1 + Goto FileFunc_GetParameters_rstrip + + FileFunc_GetParameters_done: + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!macro GetOptionsBody _FILEFUNC_S + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + ClearErrors + + StrCpy $2 $1 '' 1 + StrCpy $1 $1 1 + StrLen $3 $2 + StrCpy $7 0 + + FileFunc_GetOptions${_FILEFUNC_S}_begin: + StrCpy $4 -1 + StrCpy $6 '' + + FileFunc_GetOptions${_FILEFUNC_S}_quote: + IntOp $4 $4 + 1 + StrCpy $5 $0 1 $4 + StrCmp${_FILEFUNC_S} $5$7 '0' FileFunc_GetOptions${_FILEFUNC_S}_notfound + StrCmp${_FILEFUNC_S} $5 '' FileFunc_GetOptions${_FILEFUNC_S}_trimright + StrCmp${_FILEFUNC_S} $5 '"' 0 +7 + StrCmp${_FILEFUNC_S} $6 '' 0 +3 + StrCpy $6 '"' + goto FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $6 '"' 0 +3 + StrCpy $6 '' + goto FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $5 `'` 0 +7 + StrCmp${_FILEFUNC_S} $6 `` 0 +3 + StrCpy $6 `'` + goto FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $6 `'` 0 +3 + StrCpy $6 `` + goto FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $5 '`' 0 +7 + StrCmp${_FILEFUNC_S} $6 '' 0 +3 + StrCpy $6 '`' + goto FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $6 '`' 0 +3 + StrCpy $6 '' + goto FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $6 '"' FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $6 `'` FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $6 '`' FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $5 $1 0 FileFunc_GetOptions${_FILEFUNC_S}_quote + StrCmp${_FILEFUNC_S} $7 0 FileFunc_GetOptions${_FILEFUNC_S}_trimleft FileFunc_GetOptions${_FILEFUNC_S}_trimright + + FileFunc_GetOptions${_FILEFUNC_S}_trimleft: + IntOp $4 $4 + 1 + StrCpy $5 $0 $3 $4 + StrCmp${_FILEFUNC_S} $5 '' FileFunc_GetOptions${_FILEFUNC_S}_notfound + StrCmp${_FILEFUNC_S} $5 $2 0 FileFunc_GetOptions${_FILEFUNC_S}_quote + IntOp $4 $4 + $3 + StrCpy $0 $0 '' $4 + StrCpy $4 $0 1 + StrCmp${_FILEFUNC_S} $4 ' ' 0 +3 + StrCpy $0 $0 '' 1 + goto -3 + StrCpy $7 1 + goto FileFunc_GetOptions${_FILEFUNC_S}_begin + + FileFunc_GetOptions${_FILEFUNC_S}_trimright: + StrCpy $0 $0 $4 + StrCpy $4 $0 1 -1 + StrCmp${_FILEFUNC_S} $4 ' ' 0 +3 + StrCpy $0 $0 -1 + goto -3 + StrCpy $3 $0 1 + StrCpy $4 $0 1 -1 + StrCmp${_FILEFUNC_S} $3 $4 0 FileFunc_GetOptions${_FILEFUNC_S}_end + StrCmp${_FILEFUNC_S} $3 '"' +3 + StrCmp${_FILEFUNC_S} $3 `'` +2 + StrCmp${_FILEFUNC_S} $3 '`' 0 FileFunc_GetOptions${_FILEFUNC_S}_end + StrCpy $0 $0 -1 1 + goto FileFunc_GetOptions${_FILEFUNC_S}_end + + FileFunc_GetOptions${_FILEFUNC_S}_notfound: + SetErrors + StrCpy $0 '' + + FileFunc_GetOptions${_FILEFUNC_S}_end: + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + +!macroend + +!define GetOptions `!insertmacro GetOptionsCall` +!define un.GetOptions `!insertmacro GetOptionsCall` + +!macro GetOptions +!macroend + +!macro un.GetOptions +!macroend + +!macro GetOptions_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + !insertmacro GetOptionsBody '' + + !verbose pop +!macroend + +!define GetOptionsS `!insertmacro GetOptionsSCall` +!define un.GetOptionsS `!insertmacro GetOptionsSCall` + +!macro GetOptionsS +!macroend + +!macro un.GetOptionsS +!macroend + +!macro GetOptionsS_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + !insertmacro GetOptionsBody 'S' + + !verbose pop +!macroend + +!define GetRoot `!insertmacro GetRootCall` +!define un.GetRoot `!insertmacro GetRootCall` + +!macro GetRoot +!macroend + +!macro un.GetRoot +!macroend + +!macro GetRoot_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + Push $3 + + StrCpy $1 $0 2 + StrCmp $1 '\\' FileFunc_GetRoot_UNC + StrCpy $2 $1 1 1 + StrCmp $2 ':' 0 FileFunc_GetRoot_empty + StrCpy $0 $1 + goto FileFunc_GetRoot_end + + FileFunc_GetRoot_UNC: + StrCpy $2 1 + StrCpy $3 '' + + FileFunc_GetRoot_loop: + IntOp $2 $2 + 1 + StrCpy $1 $0 1 $2 + StrCmp $1$3 '' FileFunc_GetRoot_empty + StrCmp $1 '' +5 + StrCmp $1 '\' 0 FileFunc_GetRoot_loop + StrCmp $3 '1' +3 + StrCpy $3 '1' + goto FileFunc_GetRoot_loop + StrCpy $0 $0 $2 + StrCpy $2 $0 1 -1 + StrCmp $2 '\' 0 FileFunc_GetRoot_end + + FileFunc_GetRoot_empty: + StrCpy $0 '' + + FileFunc_GetRoot_end: + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetParent `!insertmacro GetParentCall` +!define un.GetParent `!insertmacro GetParentCall` + +!macro GetParent +!macroend + +!macro un.GetParent +!macroend + +!macro GetParent_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + + StrCpy $2 $0 1 -1 + StrCmp $2 '\' 0 +3 + StrCpy $0 $0 -1 + goto -3 + + StrCpy $1 0 + IntOp $1 $1 - 1 + StrCpy $2 $0 1 $1 + StrCmp $2 '\' +2 + StrCmp $2 '' 0 -3 + StrCpy $0 $0 $1 + + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetFileName `!insertmacro GetFileNameCall` +!define un.GetFileName `!insertmacro GetFileNameCall` + +!macro GetFileName +!macroend + +!macro un.GetFileName +!macroend + +!macro GetFileName_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + + StrCpy $2 $0 1 -1 + StrCmp $2 '\' 0 +3 + StrCpy $0 $0 -1 + goto -3 + + StrCpy $1 0 + IntOp $1 $1 - 1 + StrCpy $2 $0 1 $1 + StrCmp $2 '' FileFunc_GetFileName_end + StrCmp $2 '\' 0 -3 + IntOp $1 $1 + 1 + StrCpy $0 $0 '' $1 + + FileFunc_GetFileName_end: + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetBaseName `!insertmacro GetBaseNameCall` +!define un.GetBaseName `!insertmacro GetBaseNameCall` + +!macro GetBaseName +!macroend + +!macro un.GetBaseName +!macroend + +!macro GetBaseName_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + Push $3 + + StrCpy $1 0 + StrCpy $3 '' + + FileFunc_GetBaseName_loop: + IntOp $1 $1 - 1 + StrCpy $2 $0 1 $1 + StrCmp $2 '' FileFunc_GetBaseName_trimpath + StrCmp $2 '\' FileFunc_GetBaseName_trimpath + StrCmp $3 'noext' FileFunc_GetBaseName_loop + StrCmp $2 '.' 0 FileFunc_GetBaseName_loop + StrCpy $0 $0 $1 + StrCpy $3 'noext' + StrCpy $1 0 + goto FileFunc_GetBaseName_loop + + FileFunc_GetBaseName_trimpath: + StrCmp $1 -1 FileFunc_GetBaseName_empty + IntOp $1 $1 + 1 + StrCpy $0 $0 '' $1 + goto FileFunc_GetBaseName_end + + FileFunc_GetBaseName_empty: + StrCpy $0 '' + + FileFunc_GetBaseName_end: + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define GetFileExt `!insertmacro GetFileExtCall` +!define un.GetFileExt `!insertmacro GetFileExtCall` + +!macro GetFileExt +!macroend + +!macro un.GetFileExt +!macroend + +!macro GetFileExt_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + + StrCpy $1 0 + + FileFunc_GetFileExt_loop: + IntOp $1 $1 - 1 + StrCpy $2 $0 1 $1 + StrCmp $2 '' FileFunc_GetFileExt_empty + StrCmp $2 '\' FileFunc_GetFileExt_empty + StrCmp $2 '.' 0 FileFunc_GetFileExt_loop + + StrCmp $1 -1 FileFunc_GetFileExt_empty + IntOp $1 $1 + 1 + StrCpy $0 $0 '' $1 + goto FileFunc_GetFileExt_end + + FileFunc_GetFileExt_empty: + StrCpy $0 '' + + FileFunc_GetFileExt_end: + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define BannerTrimPath `!insertmacro BannerTrimPathCall` +!define un.BannerTrimPath `!insertmacro BannerTrimPathCall` + +!macro BannerTrimPath +!macroend + +!macro un.BannerTrimPath +!macroend + +!macro BannerTrimPath_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + + StrCpy $3 $1 1 -1 + IntOp $1 $1 + 0 + StrLen $2 $0 + IntCmp $2 $1 FileFunc_BannerTrimPath_end FileFunc_BannerTrimPath_end + IntOp $1 $1 - 3 + IntCmp $1 0 FileFunc_BannerTrimPath_empty FileFunc_BannerTrimPath_empty + StrCmp $3 'A' FileFunc_BannerTrimPath_A-trim + StrCmp $3 'B' FileFunc_BannerTrimPath_B-trim + StrCmp $3 'C' FileFunc_BannerTrimPath_C-trim + StrCmp $3 'D' FileFunc_BannerTrimPath_D-trim + + FileFunc_BannerTrimPath_A-trim: + StrCpy $3 $0 1 1 + StrCpy $2 0 + StrCmp $3 ':' 0 +2 + IntOp $2 $2 + 2 + + FileFunc_BannerTrimPath_loopleft: + IntOp $2 $2 + 1 + StrCpy $3 $0 1 $2 + StrCmp $2 $1 FileFunc_BannerTrimPath_C-trim + StrCmp $3 '\' 0 FileFunc_BannerTrimPath_loopleft + StrCpy $3 $0 $2 + IntOp $2 $2 - $1 + IntCmp $2 0 FileFunc_BannerTrimPath_B-trim 0 FileFunc_BannerTrimPath_B-trim + + FileFunc_BannerTrimPath_loopright: + IntOp $2 $2 + 1 + StrCpy $4 $0 1 $2 + StrCmp $2 0 FileFunc_BannerTrimPath_B-trim + StrCmp $4 '\' 0 FileFunc_BannerTrimPath_loopright + StrCpy $4 $0 '' $2 + StrCpy $0 '$3\...$4' + goto FileFunc_BannerTrimPath_end + + FileFunc_BannerTrimPath_B-trim: + StrCpy $2 $1 + IntOp $2 $2 - 1 + StrCmp $2 -1 FileFunc_BannerTrimPath_C-trim + StrCpy $3 $0 1 $2 + StrCmp $3 '\' 0 -3 + StrCpy $0 $0 $2 + StrCpy $0 '$0\...' + goto FileFunc_BannerTrimPath_end + + FileFunc_BannerTrimPath_C-trim: + StrCpy $0 $0 $1 + StrCpy $0 '$0...' + goto FileFunc_BannerTrimPath_end + + FileFunc_BannerTrimPath_D-trim: + StrCpy $3 -1 + IntOp $3 $3 - 1 + StrCmp $3 -$2 FileFunc_BannerTrimPath_C-trim + StrCpy $4 $0 1 $3 + StrCmp $4 '\' 0 -3 + StrCpy $4 $0 '' $3 + IntOp $3 $1 + $3 + IntCmp $3 2 FileFunc_BannerTrimPath_C-trim FileFunc_BannerTrimPath_C-trim + StrCpy $0 $0 $3 + StrCpy $0 '$0...$4' + goto FileFunc_BannerTrimPath_end + + FileFunc_BannerTrimPath_empty: + StrCpy $0 '' + + FileFunc_BannerTrimPath_end: + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define DirState `!insertmacro DirStateCall` +!define un.DirState `!insertmacro DirStateCall` + +!macro DirState +!macroend + +!macro un.DirState +!macroend + +!macro DirState_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + Exch $0 + Push $1 + ClearErrors + + FindFirst $1 $0 '$0\*.*' + IfErrors 0 +3 + StrCpy $0 -1 + goto FileFunc_DirState_end + StrCmp $0 '.' 0 +4 + FindNext $1 $0 + StrCmp $0 '..' 0 +2 + FindNext $1 $0 + FindClose $1 + IfErrors 0 +3 + StrCpy $0 0 + goto FileFunc_DirState_end + StrCpy $0 1 + + FileFunc_DirState_end: + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define RefreshShellIcons `!insertmacro RefreshShellIconsCall` +!define un.RefreshShellIcons `!insertmacro RefreshShellIconsCall` + +!macro RefreshShellIcons +!macroend + +!macro un.RefreshShellIcons +!macroend + +!macro RefreshShellIcons_ + !verbose push + !verbose ${_FILEFUNC_VERBOSE} + + System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0, i 0, i 0)' + + !verbose pop +!macroend + +!endif diff --git a/installer/NSIS/Include/InstallOptions.nsh b/installer/NSIS/Include/InstallOptions.nsh new file mode 100644 index 0000000..9d0c15b --- /dev/null +++ b/installer/NSIS/Include/InstallOptions.nsh @@ -0,0 +1,240 @@ +/* + +InstallOptions.nsh +Macros and conversion functions for InstallOptions + +*/ + +!ifndef ___NSIS__INSTALL_OPTIONS__NSH___ +!define ___NSIS__INSTALL_OPTIONS__NSH___ + +!include LogicLib.nsh + +!macro INSTALLOPTIONS_FUNCTION_READ_CONVERT + !insertmacro INSTALLOPTIONS_FUNCTION_IO2NSIS "" +!macroend + +!macro INSTALLOPTIONS_UNFUNCTION_READ_CONVERT + !insertmacro INSTALLOPTIONS_FUNCTION_IO2NSIS un. +!macroend + +!macro INSTALLOPTIONS_FUNCTION_WRITE_CONVERT + !insertmacro INSTALLOPTIONS_FUNCTION_NSIS2IO "" +!macroend + +!macro INSTALLOPTIONS_UNFUNCTION_WRITE_CONVERT + !insertmacro INSTALLOPTIONS_FUNCTION_NSIS2IO un. +!macroend + +!macro INSTALLOPTIONS_FUNCTION_NSIS2IO UNINSTALLER_FUNCPREFIX + + ; Convert an NSIS string to a form suitable for use by InstallOptions + ; Usage: + ; Push + ; Call Nsis2Io + ; Pop + + Function ${UNINSTALLER_FUNCPREFIX}Nsis2Io + + Exch $0 ; The source + Push $1 ; The output + Push $2 ; Temporary char + Push $3 ; Length + Push $4 ; Loop index + StrCpy $1 "" ; Initialise the output + + StrLen $3 $0 + IntOp $3 $3 - 1 + + ${For} $4 0 $3 + StrCpy $2 $0 1 $4 + ${If} $2 == '\' + StrCpy $2 '\\' + ${ElseIf} $2 == '$\r' + StrCpy $2 '\r' + ${ElseIf} $2 == '$\n' + StrCpy $2 '\n' + ${ElseIf} $2 == '$\t' + StrCpy $2 '\t' + ${EndIf} + StrCpy $1 $1$2 + ${Next} + + StrCpy $0 $1 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + FunctionEnd + +!macroend + +!macro INSTALLOPTIONS_FUNCTION_IO2NSIS UNINSTALLER_FUNCPREFIX + + ; Convert an InstallOptions string to a form suitable for use by NSIS + ; Usage: + ; Push + ; Call Io2Nsis + ; Pop + + Function ${UNINSTALLER_FUNCPREFIX}Io2Nsis + + Exch $0 ; The source + Push $1 ; The output + Push $2 ; Temporary char + Push $3 ; Length + Push $4 ; Loop index + StrCpy $1 "" ; Initialise the output + + StrLen $3 $0 + IntOp $3 $3 - 1 + + ${For} $4 0 $3 + StrCpy $2 $0 2 $4 + ${If} $2 == '\\' + StrCpy $2 '\' + IntOp $4 $4 + 1 + ${ElseIf} $2 == '\r' + StrCpy $2 '$\r' + IntOp $4 $4 + 1 + ${ElseIf} $2 == '\n' + StrCpy $2 '$\n' + IntOp $4 $4 + 1 + ${ElseIf} $2 == '\t' + StrCpy $2 '$\t' + IntOp $4 $4 + 1 + ${EndIf} + StrCpy $2 $2 1 + StrCpy $1 $1$2 + ${Next} + + StrCpy $0 $1 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + FunctionEnd + +!macroend + +!macro INSTALLOPTIONS_EXTRACT FILE + + InitPluginsDir + File "/oname=$PLUGINSDIR\${FILE}" "${FILE}" + !insertmacro INSTALLOPTIONS_WRITE "${FILE}" "Settings" "RTL" "$(^RTL)" + + !verbose pop + +!macroend + +!macro INSTALLOPTIONS_EXTRACT_AS FILE FILENAME + + InitPluginsDir + File "/oname=$PLUGINSDIR\${FILENAME}" "${FILE}" + !insertmacro INSTALLOPTIONS_WRITE "${FILENAME}" "Settings" "RTL" "$(^RTL)" + +!macroend + +!macro INSTALLOPTIONS_DISPLAY FILE + + Push $0 + + InstallOptions::dialog "$PLUGINSDIR\${FILE}" + Pop $0 + + Pop $0 + +!macroend + +!macro INSTALLOPTIONS_DISPLAY_RETURN FILE + + InstallOptions::dialog "$PLUGINSDIR\${FILE}" + +!macroend + +!macro INSTALLOPTIONS_INITDIALOG FILE + + InstallOptions::initDialog "$PLUGINSDIR\${FILE}" + +!macroend + +!macro INSTALLOPTIONS_SHOW + + Push $0 + + InstallOptions::show + Pop $0 + + Pop $0 + +!macroend + +!macro INSTALLOPTIONS_SHOW_RETURN + + InstallOptions::show + +!macroend + +!macro INSTALLOPTIONS_READ VAR FILE SECTION KEY + + ReadIniStr ${VAR} "$PLUGINSDIR\${FILE}" "${SECTION}" "${KEY}" + +!macroend + +!macro INSTALLOPTIONS_WRITE FILE SECTION KEY VALUE + + WriteIniStr "$PLUGINSDIR\${FILE}" "${SECTION}" "${KEY}" "${VALUE}" + +!macroend + +!macro INSTALLOPTIONS_READ_CONVERT VAR FILE SECTION KEY + + ReadIniStr ${VAR} "$PLUGINSDIR\${FILE}" "${SECTION}" "${KEY}" + Push ${VAR} + Call Io2Nsis + Pop ${VAR} + +!macroend + +!macro INSTALLOPTIONS_READ_UNCONVERT VAR FILE SECTION KEY + + ReadIniStr ${VAR} "$PLUGINSDIR\${FILE}" "${SECTION}" "${KEY}" + Push ${VAR} + Call un.Io2Nsis + Pop ${VAR} + +!macroend + +!macro INSTALLOPTIONS_WRITE_CONVERT FILE SECTION KEY VALUE + + Push $0 + StrCpy $0 "${VALUE}" + Push $0 + Call Nsis2Io + Pop $0 + + WriteIniStr "$PLUGINSDIR\${FILE}" "${SECTION}" "${KEY}" $0 + + Pop $0 + +!macroend + +!macro INSTALLOPTIONS_WRITE_UNCONVERT FILE SECTION KEY VALUE + + Push $0 + StrCpy $0 "${VALUE}" + Push $0 + Call un.Nsis2Io + Pop $0 + + WriteIniStr "$PLUGINSDIR\${FILE}" "${SECTION}" "${KEY}" $0 + + Pop $0 + +!macroend + +!endif # ___NSIS__INSTALL_OPTIONS__NSH___ diff --git a/installer/NSIS/Include/LangFile.nsh b/installer/NSIS/Include/LangFile.nsh new file mode 100644 index 0000000..821bb29 --- /dev/null +++ b/installer/NSIS/Include/LangFile.nsh @@ -0,0 +1,133 @@ +/* + +LangFile.nsh + +Header file to create langauge files that can be +included with a single command. + +Copyright 2008-2009 Joost Verburg + +* Either LANGFILE_INCLUDE or LANGFILE_INCLUDE_WITHDEFAULT + can be called from the script to include a language + file. + + - LANGFILE_INCLUDE takes the language file name as parameter. + - LANGFILE_INCLUDE_WITHDEFAULT takes as additional second + parameter the default language file to load missing strings + from. + +* A language file start with: + !insertmacro LANGFILE_EXT "English" + using the same name as the standard NSIS language file. + +* Language strings in the language file have the format: + ${LangFileString} LANGSTRING_NAME "Text" + +*/ + +!ifndef LANGFILE_INCLUDED +!define LANGFILE_INCLUDED + +!macro LANGFILE_INCLUDE FILENAME + + ;Called from script: include a langauge file + + !ifdef LangFileString + !undef LangFileString + !endif + + !define LangFileString "!insertmacro LANGFILE_SETSTRING" + + !define LANGFILE_SETNAMES + !include "${FILENAME}" + !undef LANGFILE_SETNAMES + + ;Create language strings + + !undef LangFileString + !define LangFileString "!insertmacro LANGFILE_LANGSTRING" + !include "${FILENAME}" + +!macroend + +!macro LANGFILE_INCLUDE_WITHDEFAULT FILENAME FILENAME_DEFAULT + + ;Called from script: include a langauge file + ;Obtains missing strings from a default file + + !ifdef LangFileString + !undef LangFileString + !endif + + !define LangFileString "!insertmacro LANGFILE_SETSTRING" + + !define LANGFILE_SETNAMES + !include "${FILENAME}" + !undef LANGFILE_SETNAMES + + ;Include default language for missing strings + !include "${FILENAME_DEFAULT}" + + ;Create language strings + !undef LangFileString + !define LangFileString "!insertmacro LANGFILE_LANGSTRING" + !include "${FILENAME_DEFAULT}" + +!macroend + +!macro LANGFILE IDNAME NAME + + ;Start of standard NSIS language file + + !ifdef LANGFILE_SETNAMES + + !ifdef LANGFILE_IDNAME + !undef LANGFILE_IDNAME + !endif + + !define LANGFILE_IDNAME "${IDNAME}" + + !ifndef "LANGFILE_${IDNAME}_NAME" + !define "LANGFILE_${IDNAME}_NAME" "${NAME}" + !endif + + !endif + +!macroend + +!macro LANGFILE_EXT IDNAME + + ;Start of installer language file + + !ifdef LANGFILE_SETNAMES + + !ifdef LANGFILE_IDNAME + !undef LANGFILE_IDNAME + !endif + + !define LANGFILE_IDNAME "${IDNAME}" + + !endif + +!macroend + +!macro LANGFILE_SETSTRING NAME VALUE + + ;Set define with translated string + + !ifndef ${NAME} + !define "${NAME}" "${VALUE}" + !endif + +!macroend + +!macro LANGFILE_LANGSTRING NAME DUMMY + + ;Create a language string from a define and undefine + + LangString "${NAME}" "${LANG_${LANGFILE_IDNAME}}" "${${NAME}}" + !undef "${NAME}" + +!macroend + +!endif diff --git a/installer/NSIS/Include/Library.nsh b/installer/NSIS/Include/Library.nsh new file mode 100644 index 0000000..e316c95 --- /dev/null +++ b/installer/NSIS/Include/Library.nsh @@ -0,0 +1,870 @@ +# +# Library.nsh +# +# A system for the installation and uninstallation of dynamic +# link libraries (DLL) and type libraries (TLB). Using this +# system you can handle the complete setup with one single +# line of code: +# +# * File copying +# * File copying on reboot +# * Version checks +# * Registration and unregistration +# * Registration and unregistration on reboot +# * Shared DLL counting +# * Windows File Protection checks +# +# For more information, read appendix B in the documentation. +# + +!verbose push +!verbose 3 + +!ifndef LIB_INCLUDED + +!define LIB_INCLUDED + +!ifndef SHCNE_ASSOCCHANGED + !define SHCNE_ASSOCCHANGED 0x08000000 +!endif +!ifndef SHCNF_IDLIST + !define SHCNF_IDLIST 0x0000 +!endif + +!define REGTOOL_VERSION v3 +!define REGTOOL_KEY NSIS.Library.RegTool.${REGTOOL_VERSION} + +!include LogicLib.nsh +!include x64.nsh + +### GetParent macro, don't pass $1 or $2 as INTPUT or OUTPUT +!macro __InstallLib_Helper_GetParent INPUT OUTPUT + + # Copied from FileFunc.nsh + + StrCpy ${OUTPUT} ${INPUT} + + Push $1 + Push $2 + + StrCpy $2 ${OUTPUT} 1 -1 + StrCmp $2 '\' 0 +3 + StrCpy ${OUTPUT} ${OUTPUT} -1 + goto -3 + + StrCpy $1 0 + IntOp $1 $1 - 1 + StrCpy $2 ${OUTPUT} 1 $1 + StrCmp $2 '\' +2 + StrCmp $2 '' 0 -3 + StrCpy ${OUTPUT} ${OUTPUT} $1 + + Pop $2 + Pop $1 + +!macroend + +### Initialize session id (GUID) +!macro __InstallLib_Helper_InitSession + + !ifndef __InstallLib_SessionGUID_Defined + + !define __InstallLib_SessionGUID_Defined + + Var /GLOBAL __INSTALLLLIB_SESSIONGUID + + !endif + + !define __InstallLib_Helper_InitSession_Label "Library_${__FILE__}${__LINE__}" + + StrCmp $__INSTALLLLIB_SESSIONGUID '' 0 "${__InstallLib_Helper_InitSession_Label}" + + System::Call 'ole32::CoCreateGuid(g .s)' + Pop $__INSTALLLLIB_SESSIONGUID + + "${__InstallLib_Helper_InitSession_Label}:" + + !undef __InstallLib_Helper_InitSession_Label + +!macroend + +### Add a RegTool entry to register after reboot +!macro __InstallLib_Helper_AddRegToolEntry mode filename tempdir + + Push $R0 + Push $R1 + Push $R2 + Push $R3 + + ;------------------------ + ;Copy the parameters + + Push "${filename}" + Push "${tempdir}" + + Pop $R2 ; temporary directory + Pop $R1 ; file name to register + + ;------------------------ + ;Initialize session id + + !insertmacro __InstallLib_Helper_InitSession + + ;------------------------ + ;Advance counter + + StrCpy $R0 0 + ReadRegDWORD $R0 HKLM "Software\${REGTOOL_KEY}\$__INSTALLLLIB_SESSIONGUID" "count" + IntOp $R0 $R0 + 1 + WriteRegDWORD HKLM "Software\${REGTOOL_KEY}\$__INSTALLLLIB_SESSIONGUID" "count" "$R0" + + ;------------------------ + ;Setup RegTool + + ReadRegStr $R3 HKLM "Software\Microsoft\Windows\CurrentVersion\RunOnce" "${REGTOOL_KEY}" + StrCpy $R3 $R3 -4 1 + IfFileExists $R3 +3 + + File /oname=$R2\${REGTOOL_KEY}.$__INSTALLLLIB_SESSIONGUID.exe "${NSISDIR}\Bin\RegTool.bin" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\RunOnce" \ + "${REGTOOL_KEY}" '"$R2\${REGTOOL_KEY}.$__INSTALLLLIB_SESSIONGUID.exe" /S' + + ;------------------------ + ;Add RegTool entry + + WriteRegStr HKLM "Software\${REGTOOL_KEY}\$__INSTALLLLIB_SESSIONGUID" "$R0.file" "$R1" + WriteRegStr HKLM "Software\${REGTOOL_KEY}\$__INSTALLLLIB_SESSIONGUID" "$R0.mode" "${mode}" + + Pop $R3 + Pop $R2 + Pop $R1 + Pop $R0 + +!macroend + +### Get library version +!macro __InstallLib_Helper_GetVersion TYPE FILE + + !tempfile LIBRARY_TEMP_NSH + + !ifdef NSIS_WIN32_MAKENSIS + + !execute '"${NSISDIR}\Bin\LibraryLocal.exe" "${TYPE}" "${FILE}" "${LIBRARY_TEMP_NSH}"' + + !else + + !execute 'LibraryLocal "${TYPE}" "${FILE}" "${LIBRARY_TEMP_NSH}"' + + !if ${TYPE} == 'T' + + !warning "LibraryLocal currently supports TypeLibs version detection on Windows only" + + !endif + + !endif + + !include "${LIBRARY_TEMP_NSH}" + !delfile "${LIBRARY_TEMP_NSH}" + !undef LIBRARY_TEMP_NSH + +!macroend + +### Install library +!macro InstallLib libtype shared install localfile destfile tempbasedir + + !verbose push + !verbose 3 + + Push $R0 + Push $R1 + Push $R2 + Push $R3 + Push $R4 + Push $R5 + + ;------------------------ + ;Define + + !define INSTALLLIB_UNIQUE "${__FILE__}${__LINE__}" + + !define INSTALLLIB_LIBTYPE_${libtype} + !define INSTALLLIB_LIBTYPE_SET INSTALLLIB_LIBTYPE_${libtype} + !define INSTALLLIB_SHARED_${shared} + !define INSTALLLIB_SHARED_SET INSTALLLIB_SHARED_${shared} + !define INSTALLLIB_INSTALL_${install} + !define INSTALLLIB_INSTALL_SET INSTALLLIB_INSTALL_${install} + + ;------------------------ + ;Validate + + !ifndef INSTALLLIB_LIBTYPE_DLL & INSTALLLIB_LIBTYPE_REGDLL & INSTALLLIB_LIBTYPE_TLB & \ + INSTALLLIB_LIBTYPE_REGDLLTLB & INSTALLLIB_LIBTYPE_REGEXE + !error "InstallLib: Incorrect setting for parameter: libtype" + !endif + + !ifndef INSTALLLIB_INSTALL_REBOOT_PROTECTED & INSTALLLIB_INSTALL_REBOOT_NOTPROTECTED & \ + INSTALLLIB_INSTALL_NOREBOOT_PROTECTED & INSTALLLIB_INSTALL_NOREBOOT_NOTPROTECTED + !error "InstallLib: Incorrect setting for parameter: install" + !endif + + ;------------------------ + ;x64 settings + + !ifdef LIBRARY_X64 + + ${DisableX64FSRedirection} + + !endif + + ;------------------------ + ;Copy the parameters used on run-time to a variable + ;This allows the usage of variables as parameter + + StrCpy $R4 "${destfile}" + StrCpy $R5 "${tempbasedir}" + + ;------------------------ + ;Shared library count + + !ifndef INSTALLLIB_SHARED_NOTSHARED + + StrCmp ${shared} "" 0 "installlib.noshareddllincrease_${INSTALLLIB_UNIQUE}" + + !ifdef LIBRARY_X64 + + SetRegView 64 + + !endif + + ReadRegDword $R0 HKLM Software\Microsoft\Windows\CurrentVersion\SharedDLLs $R4 + ClearErrors + IntOp $R0 $R0 + 1 + WriteRegDWORD HKLM Software\Microsoft\Windows\CurrentVersion\SharedDLLs $R4 $R0 + + !ifdef LIBRARY_X64 + + SetRegView lastused + + !endif + + "installlib.noshareddllincrease_${INSTALLLIB_UNIQUE}:" + + !endif + + ;------------------------ + ;Check Windows File Protection + + !ifdef INSTALLLIB_INSTALL_REBOOT_PROTECTED | INSTALLLIB_INSTALL_NOREBOOT_PROTECTED + + !define LIBRARY_DEFINE_DONE_LABEL + + System::Call "sfc::SfcIsFileProtected(i 0, w R4) i.R0" + + StrCmp $R0 "error" "installlib.notprotected_${INSTALLLIB_UNIQUE}" + StrCmp $R0 "0" "installlib.notprotected_${INSTALLLIB_UNIQUE}" + + Goto "installlib.done_${INSTALLLIB_UNIQUE}" + + "installlib.notprotected_${INSTALLLIB_UNIQUE}:" + + !endif + + ;------------------------ + ;Check file + + IfFileExists $R4 0 "installlib.copy_${INSTALLLIB_UNIQUE}" + + ;------------------------ + ;Get version information + + !ifndef LIBRARY_IGNORE_VERSION + + !insertmacro __InstallLib_Helper_GetVersion D "${LOCALFILE}" + + !ifdef LIBRARY_VERSION_FILENOTFOUND + !error "InstallLib: The library ${LOCALFILE} could not be found." + !endif + + !ifndef LIBRARY_VERSION_NONE + + !define LIBRARY_DEFINE_UPGRADE_LABEL + !define LIBRARY_DEFINE_REGISTER_LABEL + + StrCpy $R0 ${LIBRARY_VERSION_HIGH} + StrCpy $R1 ${LIBRARY_VERSION_LOW} + + GetDLLVersion $R4 $R2 $R3 + + !undef LIBRARY_VERSION_HIGH + !undef LIBRARY_VERSION_LOW + + !ifndef INSTALLLIB_LIBTYPE_TLB & INSTALLLIB_LIBTYPE_REGDLLTLB + + IntCmpU $R0 $R2 0 "installlib.register_${INSTALLLIB_UNIQUE}" "installlib.upgrade_${INSTALLLIB_UNIQUE}" + IntCmpU $R1 $R3 "installlib.register_${INSTALLLIB_UNIQUE}" "installlib.register_${INSTALLLIB_UNIQUE}" \ + "installlib.upgrade_${INSTALLLIB_UNIQUE}" + + !else + + !insertmacro __InstallLib_Helper_GetVersion T "${LOCALFILE}" + + !ifdef LIBRARY_VERSION_FILENOTFOUND + !error "InstallLib: The library ${LOCALFILE} could not be found." + !endif + + !ifndef LIBRARY_VERSION_NONE + + IntCmpU $R0 $R2 0 "installlib.register_${INSTALLLIB_UNIQUE}" "installlib.upgrade_${INSTALLLIB_UNIQUE}" + IntCmpU $R1 $R3 0 "installlib.register_${INSTALLLIB_UNIQUE}" \ + "installlib.upgrade_${INSTALLLIB_UNIQUE}" + + !else + + IntCmpU $R0 $R2 0 "installlib.register_${INSTALLLIB_UNIQUE}" "installlib.upgrade_${INSTALLLIB_UNIQUE}" + IntCmpU $R1 $R3 "installlib.register_${INSTALLLIB_UNIQUE}" "installlib.register_${INSTALLLIB_UNIQUE}" \ + "installlib.upgrade_${INSTALLLIB_UNIQUE}" + + !endif + + !endif + + !else + + !undef LIBRARY_VERSION_NONE + + !ifdef INSTALLLIB_LIBTYPE_TLB | INSTALLLIB_LIBTYPE_REGDLLTLB + + !insertmacro __InstallLib_Helper_GetVersion T "${LOCALFILE}" + + !endif + + !endif + + !ifdef INSTALLLIB_LIBTYPE_TLB | INSTALLLIB_LIBTYPE_REGDLLTLB + + !ifndef LIBRARY_VERSION_NONE + + !ifndef LIBRARY_DEFINE_UPGRADE_LABEL + + !define LIBRARY_DEFINE_UPGRADE_LABEL + + !endif + + !ifndef LIBRARY_DEFINE_REGISTER_LABEL + + !define LIBRARY_DEFINE_REGISTER_LABEL + + !endif + + StrCpy $R0 ${LIBRARY_VERSION_HIGH} + StrCpy $R1 ${LIBRARY_VERSION_LOW} + + TypeLib::GetLibVersion $R4 + Pop $R3 + Pop $R2 + + IntCmpU $R0 $R2 0 "installlib.register_${INSTALLLIB_UNIQUE}" "installlib.upgrade_${INSTALLLIB_UNIQUE}" + IntCmpU $R1 $R3 "installlib.register_${INSTALLLIB_UNIQUE}" "installlib.register_${INSTALLLIB_UNIQUE}" \ + "installlib.upgrade_${INSTALLLIB_UNIQUE}" + + !undef LIBRARY_VERSION_HIGH + !undef LIBRARY_VERSION_LOW + + !else + + !undef LIBRARY_VERSION_NONE + + !endif + + !endif + + !endif + + ;------------------------ + ;Upgrade + + !ifdef LIBRARY_DEFINE_UPGRADE_LABEL + + !undef LIBRARY_DEFINE_UPGRADE_LABEL + + "installlib.upgrade_${INSTALLLIB_UNIQUE}:" + + !endif + + ;------------------------ + ;Copy + + !ifdef INSTALLLIB_INSTALL_NOREBOOT_PROTECTED | INSTALLLIB_INSTALL_NOREBOOT_NOTPROTECTED + + "installlib.copy_${INSTALLLIB_UNIQUE}:" + + StrCpy $R0 $R4 + Call ":installlib.file_${INSTALLLIB_UNIQUE}" + + !else + + !ifndef LIBRARY_DEFINE_REGISTER_LABEL + + !define LIBRARY_DEFINE_REGISTER_LABEL + + !endif + + !ifndef LIBRARY_DEFINE_DONE_LABEL + + !define LIBRARY_DEFINE_DONE_LABEL + + !endif + + ClearErrors + + StrCpy $R0 $R4 + Call ":installlib.file_${INSTALLLIB_UNIQUE}" + + IfErrors 0 "installlib.register_${INSTALLLIB_UNIQUE}" + + SetOverwrite lastused + + ;------------------------ + ;Copy on reboot + + GetTempFileName $R0 $R5 + Call ":installlib.file_${INSTALLLIB_UNIQUE}" + Rename /REBOOTOK $R0 $R4 + + ;------------------------ + ;Register on reboot + + Call ":installlib.regonreboot_${INSTALLLIB_UNIQUE}" + + Goto "installlib.done_${INSTALLLIB_UNIQUE}" + + "installlib.copy_${INSTALLLIB_UNIQUE}:" + StrCpy $R0 $R4 + Call ":installlib.file_${INSTALLLIB_UNIQUE}" + + !endif + + ;------------------------ + ;Register + + !ifdef LIBRARY_DEFINE_REGISTER_LABEL + + !undef LIBRARY_DEFINE_REGISTER_LABEL + + "installlib.register_${INSTALLLIB_UNIQUE}:" + + !endif + + !ifdef INSTALLLIB_LIBTYPE_REGDLL | INSTALLLIB_LIBTYPE_TLB | INSTALLLIB_LIBTYPE_REGDLLTLB | INSTALLLIB_LIBTYPE_REGEXE + + !ifdef INSTALLLIB_INSTALL_REBOOT_PROTECTED | INSTALLLIB_INSTALL_REBOOT_NOTPROTECTED + + IfRebootFlag 0 "installlib.regnoreboot_${INSTALLLIB_UNIQUE}" + + Call ":installlib.regonreboot_${INSTALLLIB_UNIQUE}" + + Goto "installlib.registerfinish_${INSTALLLIB_UNIQUE}" + + "installlib.regnoreboot_${INSTALLLIB_UNIQUE}:" + + !endif + + !ifdef INSTALLLIB_LIBTYPE_TLB | INSTALLLIB_LIBTYPE_REGDLLTLB + + TypeLib::Register $R4 + + !endif + + !ifdef INSTALLLIB_LIBTYPE_REGDLL | INSTALLLIB_LIBTYPE_REGDLLTLB + + !ifndef LIBRARY_X64 + + RegDll $R4 + + !else + + ExecWait '"$SYSDIR\regsvr32.exe" /s "$R4"' + + !endif + + !endif + + !ifdef INSTALLLIB_LIBTYPE_REGEXE + + ExecWait '"$R4" /regserver' + + !endif + + !ifdef INSTALLLIB_INSTALL_REBOOT_PROTECTED | INSTALLLIB_INSTALL_REBOOT_NOTPROTECTED + + "installlib.registerfinish_${INSTALLLIB_UNIQUE}:" + + !endif + + !endif + + !ifdef LIBRARY_SHELL_EXTENSION + + System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' + + !endif + + !ifdef LIBRARY_COM + + System::Call 'Ole32::CoFreeUnusedLibraries()' + + !endif + + ;------------------------ + ;Done + + !ifdef LIBRARY_DEFINE_DONE_LABEL + + !undef LIBRARY_DEFINE_DONE_LABEL + + "installlib.done_${INSTALLLIB_UNIQUE}:" + + !endif + + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Pop $R0 + + ;------------------------ + ;End + + Goto "installlib.end_${INSTALLLIB_UNIQUE}" + + ;------------------------ + ;Extract + + !ifdef INSTALLLIB_INSTALL_REBOOT_PROTECTED | INSTALLLIB_INSTALL_REBOOT_NOTPROTECTED + + SetOverwrite try + + !else + + SetOverwrite on + + !endif + + "installlib.file_${INSTALLLIB_UNIQUE}:" + SetFileAttributes $R0 FILE_ATTRIBUTE_NORMAL + ClearErrors + File /oname=$R0 "${LOCALFILE}" + Return + + SetOverwrite lastused + + ;------------------------ + ;Register on reboot + + !ifdef INSTALLLIB_INSTALL_REBOOT_PROTECTED | INSTALLLIB_INSTALL_REBOOT_NOTPROTECTED + + "installlib.regonreboot_${INSTALLLIB_UNIQUE}:" + + !ifdef INSTALLLIB_LIBTYPE_REGDLL | INSTALLLIB_LIBTYPE_REGDLLTLB + !ifndef LIBRARY_X64 + !insertmacro __InstallLib_Helper_AddRegToolEntry 'D' "$R4" "$R5" + !else + !insertmacro __InstallLib_Helper_AddRegToolEntry 'DX' "$R4" "$R5" + !endif + !endif + + !ifdef INSTALLLIB_LIBTYPE_TLB | INSTALLLIB_LIBTYPE_REGDLLTLB + !insertmacro __InstallLib_Helper_AddRegToolEntry 'T' "$R4" "$R5" + !endif + + !ifdef INSTALLLIB_LIBTYPE_REGEXE + !insertmacro __InstallLib_Helper_AddRegToolEntry 'E' "$R4" "$R5" + !endif + + Return + + !endif + + ;------------------------ + ;End label + + "installlib.end_${INSTALLLIB_UNIQUE}:" + + !ifdef LIBRARY_X64 + + ${EnableX64FSRedirection} + + !endif + + ;------------------------ + ;Undefine + + !undef INSTALLLIB_UNIQUE + + !undef ${INSTALLLIB_LIBTYPE_SET} + !undef INSTALLLIB_LIBTYPE_SET + !undef ${INSTALLLIB_SHARED_SET} + !undef INSTALLLIB_SHARED_SET + !undef ${INSTALLLIB_INSTALL_SET} + !undef INSTALLLIB_INSTALL_SET + + !verbose pop + +!macroend + +### Uninstall library +!macro UnInstallLib libtype shared uninstall file + + !verbose push + !verbose 3 + + Push $R0 + Push $R1 + + ;------------------------ + ;Define + + !define UNINSTALLLIB_UNIQUE "${__FILE__}${__LINE__}" + + !define UNINSTALLLIB_LIBTYPE_${libtype} + !define UNINSTALLLIB_LIBTYPE_SET UNINSTALLLIB_LIBTYPE_${libtype} + !define UNINSTALLLIB_SHARED_${shared} + !define UNINSTALLLIB_SHARED_SET UNINSTALLLIB_SHARED_${shared} + !define UNINSTALLLIB_UNINSTALL_${uninstall} + !define UNINSTALLLIB_UNINSTALL_SET UNINSTALLLIB_UNINSTALL_${uninstall} + + ;------------------------ + ;Validate + + !ifndef UNINSTALLLIB_LIBTYPE_DLL & UNINSTALLLIB_LIBTYPE_REGDLL & UNINSTALLLIB_LIBTYPE_TLB & \ + UNINSTALLLIB_LIBTYPE_REGDLLTLB & UNINSTALLLIB_LIBTYPE_REGEXE + !error "UnInstallLib: Incorrect setting for parameter: libtype" + !endif + + !ifndef UNINSTALLLIB_SHARED_NOTSHARED & UNINSTALLLIB_SHARED_SHARED + !error "UnInstallLib: Incorrect setting for parameter: shared" + !endif + + !ifndef UNINSTALLLIB_UNINSTALL_NOREMOVE & UNINSTALLLIB_UNINSTALL_REBOOT_PROTECTED & \ + UNINSTALLLIB_UNINSTALL_REBOOT_NOTPROTECTED & UNINSTALLLIB_UNINSTALL_NOREBOOT_PROTECTED & \ + UNINSTALLLIB_UNINSTALL_NOREBOOT_NOTPROTECTED + !error "UnInstallLib: Incorrect setting for parameter: uninstall" + !endif + + ;------------------------ + ;x64 settings + + !ifdef LIBRARY_X64 + + ${DisableX64FSRedirection} + + !endif + + ;------------------------ + ;Copy the parameters used on run-time to a variable + ;This allows the usage of variables as parameter + + StrCpy $R1 "${file}" + + ;------------------------ + ;Shared library count + + !ifdef UNINSTALLLIB_SHARED_SHARED + + !define UNINSTALLLIB_DONE_LABEL + + !ifdef LIBRARY_X64 + + SetRegView 64 + + !endif + + ReadRegDword $R0 HKLM Software\Microsoft\Windows\CurrentVersion\SharedDLLs $R1 + StrCmp $R0 "" "uninstalllib.shareddlldone_${UNINSTALLLIB_UNIQUE}" + + IntOp $R0 $R0 - 1 + IntCmp $R0 0 "uninstalllib.shareddllremove_${UNINSTALLLIB_UNIQUE}" \ + "uninstalllib.shareddllremove_${UNINSTALLLIB_UNIQUE}" "uninstalllib.shareddllinuse_${UNINSTALLLIB_UNIQUE}" + + "uninstalllib.shareddllremove_${UNINSTALLLIB_UNIQUE}:" + DeleteRegValue HKLM Software\Microsoft\Windows\CurrentVersion\SharedDLLs $R1 + !ifndef UNINSTALLLIB_SHARED_SHAREDNOREMOVE + Goto "uninstalllib.shareddlldone_${UNINSTALLLIB_UNIQUE}" + !endif + + "uninstalllib.shareddllinuse_${UNINSTALLLIB_UNIQUE}:" + WriteRegDWORD HKLM Software\Microsoft\Windows\CurrentVersion\SharedDLLs $R1 $R0 + + !ifdef LIBRARY_X64 + + SetRegView lastused + + !endif + + Goto "uninstalllib.done_${UNINSTALLLIB_UNIQUE}" + + "uninstalllib.shareddlldone_${UNINSTALLLIB_UNIQUE}:" + + !ifdef LIBRARY_X64 + + SetRegView lastused + + !endif + + !endif + + ;------------------------ + ;Remove + + !ifndef UNINSTALLLIB_UNINSTALL_NOREMOVE + + ;------------------------ + ;Check Windows File Protection + + !ifdef UNINSTALLLIB_UNINSTALL_REBOOT_PROTECTED | UNINSTALLLIB_UNINSTALL_NOREBOOT_PROTECTED + + !ifndef UNINSTALLLIB_DONE_LABEL + + !define UNINSTALLLIB_DONE_LABEL + + !endif + + System::Call "sfc::SfcIsFileProtected(i 0, w $R1) i.R0" + + StrCmp $R0 "error" "uninstalllib.notprotected_${UNINSTALLLIB_UNIQUE}" + StrCmp $R0 "0" "uninstalllib.notprotected_${UNINSTALLLIB_UNIQUE}" + + Goto "uninstalllib.done_${UNINSTALLLIB_UNIQUE}" + + "uninstalllib.notprotected_${UNINSTALLLIB_UNIQUE}:" + + !endif + + ;------------------------ + ;Unregister + + !ifdef UNINSTALLLIB_LIBTYPE_REGDLL | UNINSTALLLIB_LIBTYPE_REGDLLTLB + + !ifndef LIBRARY_X64 + + UnRegDLL $R1 + + !else + + ExecWait '"$SYSDIR\regsvr32.exe" /s /u "$R1"' + + !endif + + !endif + + !ifdef UNINSTALLLIB_LIBTYPE_REGEXE + + ExecWait '"$R1" /unregserver' + + !endif + + !ifdef UNINSTALLLIB_LIBTYPE_TLB | UNINSTALLLIB_LIBTYPE_REGDLLTLB + + TypeLib::UnRegister $R1 + + !endif + + !ifdef LIBRARY_SHELL_EXTENSION + + System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' + + !endif + + !ifdef LIBRARY_COM + + System::Call 'Ole32::CoFreeUnusedLibraries()' + + !endif + + ;------------------------ + ;Delete + + Delete $R1 + + !ifdef UNINSTALLLIB_UNINSTALL_REBOOT_PROTECTED | UNINSTALLLIB_UNINSTALL_REBOOT_NOTPROTECTED + + ${If} ${FileExists} $R1 + # File is in use, can't just delete. + # Move file to another location before using Delete /REBOOTOK. This way, if + # the user installs a new version of the DLL, it won't be deleted after + # reboot. See bug #1097642 for more information on this. + + # Try moving to $TEMP. + GetTempFileName $R0 + Delete $R0 + Rename $R1 $R0 + + ${If} ${FileExists} $R1 + # Still here, delete temporary file, in case the file was copied + # and not deleted. This happens when moving from network drives, + # for example. + Delete $R0 + + # Try moving to directory containing the file. + !insertmacro __InstallLib_Helper_GetParent $R1 $R0 + GetTempFileName $R0 $R0 + Delete $R0 + Rename $R1 $R0 + + ${If} ${FileExists} $R1 + # Still here, delete temporary file. + Delete $R0 + + # Give up moving, simply Delete /REBOOTOK the file. + StrCpy $R0 $R1 + ${EndIf} + ${EndIf} + + # Delete the moved file. + Delete /REBOOTOK $R0 + ${EndIf} + + !endif + + !endif + + ;------------------------ + ;Done + + !ifdef UNINSTALLLIB_DONE_LABEL + + !undef UNINSTALLLIB_DONE_LABEL + + "uninstalllib.done_${UNINSTALLLIB_UNIQUE}:" + + !endif + + !ifdef LIBRARY_X64 + + ${EnableX64FSRedirection} + + !endif + + Pop $R1 + Pop $R0 + + ;------------------------ + ;Undefine + + !undef UNINSTALLLIB_UNIQUE + + !undef ${UNINSTALLLIB_LIBTYPE_SET} + !undef UNINSTALLLIB_LIBTYPE_SET + !undef ${UNINSTALLLIB_SHARED_SET} + !undef UNINSTALLLIB_SHARED_SET + !undef ${UNINSTALLLIB_UNINSTALL_SET} + !undef UNINSTALLLIB_UNINSTALL_SET + + !verbose pop + +!macroend + +!endif + +!verbose pop diff --git a/installer/NSIS/Include/LogicLib.nsh b/installer/NSIS/Include/LogicLib.nsh new file mode 100644 index 0000000..9e78e42 --- /dev/null +++ b/installer/NSIS/Include/LogicLib.nsh @@ -0,0 +1,792 @@ +; NSIS LOGIC LIBRARY - LogicLib.nsh +; Version 2.6 - 08/12/2007 +; By dselkirk@hotmail.com +; and eccles@users.sf.net +; with IfNot support added by Message +; +; Questions/Comments - +; See http://forums.winamp.com/showthread.php?s=&postid=1116241 +; +; Description: +; Provides the use of various logic statements within NSIS. +; +; Usage: +; The following "statements" are available: +; If|IfNot|Unless..{ElseIf|ElseIfNot|ElseUnless}..[Else]..EndIf|EndUnless +; - Conditionally executes a block of statements, depending on the value +; of an expression. IfNot and Unless are equivalent and +; interchangeable, as are ElseIfNot and ElseUnless. +; AndIf|AndIfNot|AndUnless|OrIf|OrIfNot|OrUnless +; - Adds any number of extra conditions to If, IfNot, Unless, ElseIf, +; ElseIfNot and ElseUnless statements. +; IfThen|IfNotThen..|..| +; - Conditionally executes an inline statement, depending on the value +; of an expression. +; IfCmd..||..| +; - Conditionally executes an inline statement, depending on a true +; value of the provided NSIS function. +; Select..{Case[2|3|4|5]}..[CaseElse|Default]..EndSelect +; - Executes one of several blocks of statements, depending on the value +; of an expression. +; Switch..{Case|CaseElse|Default}..EndSwitch +; - Jumps to one of several labels, depending on the value of an +; expression. +; Do[While|Until]..{ExitDo|Continue|Break}..Loop[While|Until] +; - Repeats a block of statements until stopped, or depending on the +; value of an expression. +; While..{ExitWhile|Continue|Break}..EndWhile +; - An alias for DoWhile..Loop (for backwards-compatibility) +; For[Each]..{ExitFor|Continue|Break}..Next +; - Repeats a block of statements varying the value of a variable. +; +; The following "expressions" are available: +; Standard (built-in) string tests (which are case-insensitive): +; a == b; a != b +; Additional case-insensitive string tests (using System.dll): +; a S< b; a S>= b; a S> b; a S<= b +; Case-sensitive string tests: +; a S== b; a S!= b +; Standard (built-in) signed integer tests: +; a = b; a <> b; a < b; a >= b; a > b; a <= b +; Standard (built-in) unsigned integer tests: +; a U< b; a U>= b; a U> b; a U<= b +; 64-bit integer tests (using System.dll): +; a L= b; a L<> b; a L< b; a L>= b; a L> b; a L<= b +; Built-in NSIS flag tests: +; ${Abort}; ${Errors}; ${RebootFlag}; ${Silent} +; Built-in NSIS other tests: +; ${FileExists} a +; Any conditional NSIS instruction test: +; ${Cmd} a +; Section flag tests: +; ${SectionIsSelected} a; ${SectionIsSectionGroup} a; +; ${SectionIsSectionGroupEnd} a; ${SectionIsBold} a; +; ${SectionIsReadOnly} a; ${SectionIsExpanded} a; +; ${SectionIsPartiallySelected} a +; +; Examples: +; See LogicLib.nsi in the Examples folder for lots of example usage. + +!verbose push +!verbose 3 +!ifndef LOGICLIB_VERBOSITY + !define LOGICLIB_VERBOSITY 3 +!endif +!define _LOGICLIB_VERBOSITY ${LOGICLIB_VERBOSITY} +!undef LOGICLIB_VERBOSITY +!verbose ${_LOGICLIB_VERBOSITY} + +!ifndef LOGICLIB + !define LOGICLIB + !define | "'" + !define || "' '" + !define LOGICLIB_COUNTER 0 + + !include Sections.nsh + + !macro _LOGICLIB_TEMP + !ifndef _LOGICLIB_TEMP + !define _LOGICLIB_TEMP + Var /GLOBAL _LOGICLIB_TEMP ; Temporary variable to aid the more elaborate logic tests + !endif + !macroend + + !macro _IncreaseCounter + !define _LOGICLIB_COUNTER ${LOGICLIB_COUNTER} + !undef LOGICLIB_COUNTER + !define /math LOGICLIB_COUNTER ${_LOGICLIB_COUNTER} + 1 + !undef _LOGICLIB_COUNTER + !macroend + + !macro _PushLogic + !insertmacro _PushScope Logic _LogicLib_Label_${LOGICLIB_COUNTER} + !insertmacro _IncreaseCounter + !macroend + + !macro _PopLogic + !insertmacro _PopScope Logic + !macroend + + !macro _PushScope Type label + !ifdef _${Type} ; If we already have a statement + !define _Cur${Type} ${_${Type}} + !undef _${Type} + !define _${Type} ${label} + !define ${_${Type}}Prev${Type} ${_Cur${Type}} ; Save the current logic + !undef _Cur${Type} + !else + !define _${Type} ${label} ; Initialise for first statement + !endif + !macroend + + !macro _PopScope Type + !ifndef _${Type} + !error "Cannot use _Pop${Type} without a preceding _Push${Type}" + !endif + !ifdef ${_${Type}}Prev${Type} ; If a previous statment was active then restore it + !define _Cur${Type} ${_${Type}} + !undef _${Type} + !define _${Type} ${${_Cur${Type}}Prev${Type}} + !undef ${_Cur${Type}}Prev${Type} + !undef _Cur${Type} + !else + !undef _${Type} + !endif + !macroend + + ; String tests + !macro _== _a _b _t _f + StrCmp `${_a}` `${_b}` `${_t}` `${_f}` + !macroend + + !macro _!= _a _b _t _f + !insertmacro _== `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + ; Case-sensitive string tests + !macro _S== _a _b _t _f + StrCmpS `${_a}` `${_b}` `${_t}` `${_f}` + !macroend + + !macro _S!= _a _b _t _f + !insertmacro _S== `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + ; Extra string tests (cannot do these case-sensitively - I tried and lstrcmp still ignored the case) + !macro _StrCmpI _a _b _e _l _m + !insertmacro _LOGICLIB_TEMP + System::Call `kernel32::lstrcmpiA(ts, ts) i.s` `${_a}` `${_b}` + Pop $_LOGICLIB_TEMP + IntCmp $_LOGICLIB_TEMP 0 `${_e}` `${_l}` `${_m}` + !macroend + + !macro _S< _a _b _t _f + !insertmacro _StrCmpI `${_a}` `${_b}` `${_f}` `${_t}` `${_f}` + !macroend + + !macro _S>= _a _b _t _f + !insertmacro _S< `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + !macro _S> _a _b _t _f + !insertmacro _StrCmpI `${_a}` `${_b}` `${_f}` `${_f}` `${_t}` + !macroend + + !macro _S<= _a _b _t _f + !insertmacro _S> `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + ; Integer tests + !macro _= _a _b _t _f + IntCmp `${_a}` `${_b}` `${_t}` `${_f}` `${_f}` + !macroend + + !macro _<> _a _b _t _f + !insertmacro _= `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + !macro _< _a _b _t _f + IntCmp `${_a}` `${_b}` `${_f}` `${_t}` `${_f}` + !macroend + + !macro _>= _a _b _t _f + !insertmacro _< `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + !macro _> _a _b _t _f + IntCmp `${_a}` `${_b}` `${_f}` `${_f}` `${_t}` + !macroend + + !macro _<= _a _b _t _f + !insertmacro _> `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + ; Unsigned integer tests (NB: no need for extra equality tests) + !macro _U< _a _b _t _f + IntCmpU `${_a}` `${_b}` `${_f}` `${_t}` `${_f}` + !macroend + + !macro _U>= _a _b _t _f + !insertmacro _U< `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + !macro _U> _a _b _t _f + IntCmpU `${_a}` `${_b}` `${_f}` `${_f}` `${_t}` + !macroend + + !macro _U<= _a _b _t _f + !insertmacro _U> `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + ; Int64 tests + !macro _Int64Cmp _a _o _b _t _f + !insertmacro _LOGICLIB_TEMP + System::Int64Op `${_a}` `${_o}` `${_b}` + Pop $_LOGICLIB_TEMP + !insertmacro _= $_LOGICLIB_TEMP 0 `${_f}` `${_t}` + !macroend + + !macro _L= _a _b _t _f + !insertmacro _Int64Cmp `${_a}` = `${_b}` `${_t}` `${_f}` + !macroend + + !macro _L<> _a _b _t _f + !insertmacro _L= `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + !macro _L< _a _b _t _f + !insertmacro _Int64Cmp `${_a}` < `${_b}` `${_t}` `${_f}` + !macroend + + !macro _L>= _a _b _t _f + !insertmacro _L< `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + !macro _L> _a _b _t _f + !insertmacro _Int64Cmp `${_a}` > `${_b}` `${_t}` `${_f}` + !macroend + + !macro _L<= _a _b _t _f + !insertmacro _L> `${_a}` `${_b}` `${_f}` `${_t}` + !macroend + + ; Flag tests + !macro _Abort _a _b _t _f + IfAbort `${_t}` `${_f}` + !macroend + !define Abort `"" Abort ""` + + !macro _Errors _a _b _t _f + IfErrors `${_t}` `${_f}` + !macroend + !define Errors `"" Errors ""` + + !macro _FileExists _a _b _t _f + IfFileExists `${_b}` `${_t}` `${_f}` + !macroend + !define FileExists `"" FileExists` + + !macro _RebootFlag _a _b _t _f + IfRebootFlag `${_t}` `${_f}` + !macroend + !define RebootFlag `"" RebootFlag ""` + + !macro _Silent _a _b _t _f + IfSilent `${_t}` `${_f}` + !macroend + !define Silent `"" Silent ""` + + ; "Any instruction" test + !macro _Cmd _a _b _t _f + !define _t=${_t} + !ifdef _t= ; If no true label then make one + !define __t _LogicLib_Label_${LOGICLIB_COUNTER} + !insertmacro _IncreaseCounter + !else + !define __t ${_t} + !endif + ${_b} ${__t} + !define _f=${_f} + !ifndef _f= ; If a false label then go there + Goto ${_f} + !endif + !undef _f=${_f} + !ifdef _t= ; If we made our own true label then place it + ${__t}: + !endif + !undef __t + !undef _t=${_t} + !macroend + !define Cmd `"" Cmd` + + ; Section flag test + !macro _SectionFlagIsSet _a _b _t _f + !insertmacro _LOGICLIB_TEMP + SectionGetFlags `${_b}` $_LOGICLIB_TEMP + IntOp $_LOGICLIB_TEMP $_LOGICLIB_TEMP & `${_a}` + !insertmacro _= $_LOGICLIB_TEMP `${_a}` `${_t}` `${_f}` + !macroend + !define SectionIsSelected `${SF_SELECTED} SectionFlagIsSet` + !define SectionIsSubSection `${SF_SUBSEC} SectionFlagIsSet` + !define SectionIsSubSectionEnd `${SF_SUBSECEND} SectionFlagIsSet` + !define SectionIsSectionGroup `${SF_SECGRP} SectionFlagIsSet` + !define SectionIsSectionGroupEnd `${SF_SECGRPEND} SectionFlagIsSet` + !define SectionIsBold `${SF_BOLD} SectionFlagIsSet` + !define SectionIsReadOnly `${SF_RO} SectionFlagIsSet` + !define SectionIsExpanded `${SF_EXPAND} SectionFlagIsSet` + !define SectionIsPartiallySelected `${SF_PSELECTED} SectionFlagIsSet` + + !define IfCmd `!insertmacro _IfThen "" Cmd ${|}` + + !macro _If _c _a _o _b + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !insertmacro _PushLogic + !define ${_Logic}If + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the Else + !insertmacro _IncreaseCounter + !define _c=${_c} + !ifdef _c=true ; If is true + !insertmacro _${_o} `${_a}` `${_b}` "" ${${_Logic}Else} + !else ; If condition is false + !insertmacro _${_o} `${_a}` `${_b}` ${${_Logic}Else} "" + !endif + !undef _c=${_c} + !verbose pop + !macroend + !define If `!insertmacro _If true` + !define Unless `!insertmacro _If false` + !define IfNot `!insertmacro _If false` + + !macro _And _c _a _o _b + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}If + !error "Cannot use And without a preceding If or IfNot/Unless" + !endif + !ifndef ${_Logic}Else + !error "Cannot use And following an Else" + !endif + !define _c=${_c} + !ifdef _c=true ; If is true + !insertmacro _${_o} `${_a}` `${_b}` "" ${${_Logic}Else} + !else ; If condition is false + !insertmacro _${_o} `${_a}` `${_b}` ${${_Logic}Else} "" + !endif + !undef _c=${_c} + !verbose pop + !macroend + !define AndIf `!insertmacro _And true` + !define AndUnless `!insertmacro _And false` + !define AndIfNot `!insertmacro _And false` + + !macro _Or _c _a _o _b + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}If + !error "Cannot use Or without a preceding If or IfNot/Unless" + !endif + !ifndef ${_Logic}Else + !error "Cannot use Or following an Else" + !endif + !define _label _LogicLib_Label_${LOGICLIB_COUNTER} ; Skip this test as we already + !insertmacro _IncreaseCounter + Goto ${_label} ; have a successful result + ${${_Logic}Else}: ; Place the Else label + !undef ${_Logic}Else ; and remove it + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the next Else and perform the new If + !insertmacro _IncreaseCounter + !define _c=${_c} + !ifdef _c=true ; If is true + !insertmacro _${_o} `${_a}` `${_b}` "" ${${_Logic}Else} + !else ; If condition is false + !insertmacro _${_o} `${_a}` `${_b}` ${${_Logic}Else} "" + !endif + !undef _c=${_c} + ${_label}: + !undef _label + !verbose pop + !macroend + !define OrIf `!insertmacro _Or true` + !define OrUnless `!insertmacro _Or false` + !define OrIfNot `!insertmacro _Or false` + + !macro _Else + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}If + !error "Cannot use Else without a preceding If or IfNot/Unless" + !endif + !ifndef ${_Logic}Else + !error "Cannot use Else following an Else" + !endif + !ifndef ${_Logic}EndIf ; First Else for this If? + !define ${_Logic}EndIf _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the EndIf + !insertmacro _IncreaseCounter + !endif + Goto ${${_Logic}EndIf} ; Go to the EndIf + ${${_Logic}Else}: ; Place the Else label + !undef ${_Logic}Else ; and remove it + !verbose pop + !macroend + !define Else `!insertmacro _Else` + + !macro _ElseIf _c _a _o _b + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${Else} ; Perform the Else + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the next Else and perform the new If + !insertmacro _IncreaseCounter + !define _c=${_c} + !ifdef _c=true ; If is true + !insertmacro _${_o} `${_a}` `${_b}` "" ${${_Logic}Else} + !else ; If condition is false + !insertmacro _${_o} `${_a}` `${_b}` ${${_Logic}Else} "" + !endif + !undef _c=${_c} + !verbose pop + !macroend + !define ElseIf `!insertmacro _ElseIf true` + !define ElseUnless `!insertmacro _ElseIf false` + !define ElseIfNot `!insertmacro _ElseIf false` + + !macro _EndIf _n + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}If + !error "Cannot use End${_n} without a preceding If or IfNot/Unless" + !endif + !ifdef ${_Logic}Else + ${${_Logic}Else}: ; Place the Else label + !undef ${_Logic}Else ; and remove it + !endif + !ifdef ${_Logic}EndIf + ${${_Logic}EndIf}: ; Place the EndIf + !undef ${_Logic}EndIf ; and remove it + !endif + !undef ${_Logic}If + !insertmacro _PopLogic + !verbose pop + !macroend + !define EndIf `!insertmacro _EndIf If` + !define EndUnless `!insertmacro _EndIf Unless` + + !macro _IfThen _a _o _b _t + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${If} `${_a}` `${_o}` `${_b}` + ${_t} + ${EndIf} + !verbose pop + !macroend + !define IfThen `!insertmacro _IfThen` + + !macro _IfNotThen _a _o _b _t + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${IfNot} `${_a}` `${_o}` `${_b}` + ${_t} + ${EndIf} + !verbose pop + !macroend + !define IfNotThen `!insertmacro _IfNotThen` + + !macro _ForEach _v _f _t _o _s + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + StrCpy "${_v}" "${_f}" ; Assign the initial value + Goto +2 ; Skip the loop expression for the first iteration + !define _DoLoopExpression `IntOp "${_v}" "${_v}" "${_o}" "${_s}"` ; Define the loop expression + !define _o=${_o} + !ifdef _o=+ ; Check the loop expression operator + !define __o > ; to determine the correct loop condition + !else ifdef _o=- + !define __o < + !else + !error "Unsupported ForEach step operator (must be + or -)" + !endif + !undef _o=${_o} + !insertmacro _Do For false `${_v}` `${__o}` `${_t}` ; Let Do do the rest + !undef __o + !verbose pop + !macroend + !define ForEach `!insertmacro _ForEach` + + !macro _For _v _f _t + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${ForEach} `${_v}` `${_f}` `${_t}` + 1 ; Pass on to ForEach + !verbose pop + !macroend + !define For `!insertmacro _For` + + !define ExitFor `!insertmacro _Goto ExitFor For` + + !define Next `!insertmacro _Loop For Next "" "" "" ""` + + !define While `!insertmacro _Do While true` + + !define ExitWhile `!insertmacro _Goto ExitWhile While` + + !define EndWhile `!insertmacro _Loop While EndWhile "" "" "" ""` + + !macro _Do _n _c _a _o _b + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !insertmacro _PushLogic + !define ${_Logic}${_n} _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the start of the loop + !insertmacro _IncreaseCounter + ${${_Logic}${_n}}: + !insertmacro _PushScope Exit${_n} _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the end of the loop + !insertmacro _IncreaseCounter + !insertmacro _PushScope Break ${_Exit${_n}} ; Break goes to the end of the loop + !ifdef _DoLoopExpression + ${_DoLoopExpression} ; Special extra parameter for inserting code + !undef _DoLoopExpression ; between the Continue label and the loop condition + !endif + !define _c=${_c} + !ifdef _c= ; No starting condition + !insertmacro _PushScope Continue _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for Continue at the end of the loop + !insertmacro _IncreaseCounter + !else + !insertmacro _PushScope Continue ${${_Logic}${_n}} ; Continue goes to the start of the loop + !ifdef _c=true ; If is true + !insertmacro _${_o} `${_a}` `${_b}` "" ${_Exit${_n}} + !else ; If condition is false + !insertmacro _${_o} `${_a}` `${_b}` ${_Exit${_n}} "" + !endif + !endif + !undef _c=${_c} + !define ${_Logic}Condition ${_c} ; Remember the condition used + !verbose pop + !macroend + !define Do `!insertmacro _Do Do "" "" "" ""` + !define DoWhile `!insertmacro _Do Do true` + !define DoUntil `!insertmacro _Do Do false` + + !macro _Goto _n _s + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _${_n} + !error "Cannot use ${_n} without a preceding ${_s}" + !endif + Goto ${_${_n}} + !verbose pop + !macroend + !define ExitDo `!insertmacro _Goto ExitDo Do` + + !macro _Loop _n _e _c _a _o _b + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}${_n} + !error "Cannot use ${_e} without a preceding ${_n}" + !endif + !define _c=${${_Logic}Condition} + !ifdef _c= ; If Do had no condition place the Continue label + ${_Continue}: + !endif + !undef _c=${${_Logic}Condition} + !define _c=${_c} + !ifdef _c= ; No ending condition + Goto ${${_Logic}${_n}} + !else ifdef _c=true ; If condition is true + !insertmacro _${_o} `${_a}` `${_b}` ${${_Logic}${_n}} ${_Exit${_n}} + !else ; If condition is false + !insertmacro _${_o} `${_a}` `${_b}` ${_Exit${_n}} ${${_Logic}${_n}} + !endif + !undef _c=${_c} + Goto ${_Continue} ; Just to ensure it is referenced at least once + Goto ${_Exit${_n}} ; Just to ensure it is referenced at least once + ${_Exit${_n}}: ; Place the loop exit point + !undef ${_Logic}Condition + !insertmacro _PopScope Continue + !insertmacro _PopScope Break + !insertmacro _PopScope Exit${_n} + !undef ${_Logic}${_n} + !insertmacro _PopLogic + !verbose pop + !macroend + !define Loop `!insertmacro _Loop Do Loop "" "" "" ""` + !define LoopWhile `!insertmacro _Loop Do LoopWhile true` + !define LoopUntil `!insertmacro _Loop Do LoopUntil false` + + !define Continue `!insertmacro _Goto Continue "For or Do or While"` + !define Break `!insertmacro _Goto Break "For or Do or While"` + + !macro _Select _a + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !insertmacro _PushLogic + !define ${_Logic}Select `${_a}` ; Remember the left hand side of the comparison + !verbose pop + !macroend + !define Select `!insertmacro _Select` + + !macro _Select_CaseElse + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}Select + !error "Cannot use Case without a preceding Select" + !endif + !ifdef ${_Logic}EndSelect ; This is set only after the first case + !ifndef ${_Logic}Else + !error "Cannot use Case following a CaseElse" + !endif + Goto ${${_Logic}EndSelect} ; Go to the EndSelect + ${${_Logic}Else}: ; Place the Else label + !undef ${_Logic}Else ; and remove it + !else + !define ${_Logic}EndSelect _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the EndSelect + !insertmacro _IncreaseCounter + !endif + !verbose pop + !macroend + !define CaseElse `!insertmacro _CaseElse` + !define Case_Else `!insertmacro _CaseElse` ; Compatibility with 2.2 and earlier + !define Default `!insertmacro _CaseElse` ; For the C-minded + + !macro _Select_Case _a + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${CaseElse} ; Perform the CaseElse + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the next Else and perform the new Case + !insertmacro _IncreaseCounter + !insertmacro _== `${${_Logic}Select}` `${_a}` "" ${${_Logic}Else} + !verbose pop + !macroend + !define Case `!insertmacro _Case` + + !macro _Case2 _a _b + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${CaseElse} ; Perform the CaseElse + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the next Else and perform the new Case + !insertmacro _IncreaseCounter + !insertmacro _== `${${_Logic}Select}` `${_a}` +2 "" + !insertmacro _== `${${_Logic}Select}` `${_b}` "" ${${_Logic}Else} + !verbose pop + !macroend + !define Case2 `!insertmacro _Case2` + + !macro _Case3 _a _b _c + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${CaseElse} ; Perform the CaseElse + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the next Else and perform the new Case + !insertmacro _IncreaseCounter + !insertmacro _== `${${_Logic}Select}` `${_a}` +3 "" + !insertmacro _== `${${_Logic}Select}` `${_b}` +2 "" + !insertmacro _== `${${_Logic}Select}` `${_c}` "" ${${_Logic}Else} + !verbose pop + !macroend + !define Case3 `!insertmacro _Case3` + + !macro _Case4 _a _b _c _d + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${CaseElse} ; Perform the CaseElse + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the next Else and perform the new Case + !insertmacro _IncreaseCounter + !insertmacro _== `${${_Logic}Select}` `${_a}` +4 "" + !insertmacro _== `${${_Logic}Select}` `${_b}` +3 "" + !insertmacro _== `${${_Logic}Select}` `${_c}` +2 "" + !insertmacro _== `${${_Logic}Select}` `${_d}` "" ${${_Logic}Else} + !verbose pop + !macroend + !define Case4 `!insertmacro _Case4` + + !macro _Case5 _a _b _c _d _e + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + ${CaseElse} ; Perform the CaseElse + !define ${_Logic}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the next Else and perform the new Case + !insertmacro _IncreaseCounter + !insertmacro _== `${${_Logic}Select}` `${_a}` +5 "" + !insertmacro _== `${${_Logic}Select}` `${_b}` +4 "" + !insertmacro _== `${${_Logic}Select}` `${_c}` +3 "" + !insertmacro _== `${${_Logic}Select}` `${_d}` +2 "" + !insertmacro _== `${${_Logic}Select}` `${_e}` "" ${${_Logic}Else} + !verbose pop + !macroend + !define Case5 `!insertmacro _Case5` + + !macro _EndSelect + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}Select + !error "Cannot use EndSelect without a preceding Select" + !endif + !ifdef ${_Logic}Else + ${${_Logic}Else}: ; Place the Else label + !undef ${_Logic}Else ; and remove it + !endif + !ifdef ${_Logic}EndSelect ; This won't be set if there weren't any cases + ${${_Logic}EndSelect}: ; Place the EndSelect + !undef ${_Logic}EndSelect ; and remove it + !endif + !undef ${_Logic}Select + !insertmacro _PopLogic + !verbose pop + !macroend + !define EndSelect `!insertmacro _EndSelect` + + !macro _Switch _a + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !insertmacro _PushLogic + !insertmacro _PushScope Switch ${_Logic} ; Keep a separate stack for switch data + !insertmacro _PushScope Break _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a lable for beyond the end of the switch + !insertmacro _IncreaseCounter + !define ${_Switch}Var `${_a}` ; Remember the left hand side of the comparison + !tempfile ${_Switch}Tmp ; Create a temporary file + !define ${_Logic}Switch _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the end of the switch + !insertmacro _IncreaseCounter + Goto ${${_Logic}Switch} ; and go there + !verbose pop + !macroend + !define Switch `!insertmacro _Switch` + + !macro _Case _a + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifdef _Logic & ${_Logic}Select ; Check for an active Select + !insertmacro _Select_Case `${_a}` + !else ifndef _Switch ; If not then check for an active Switch + !error "Cannot use Case without a preceding Select or Switch" + !else + !define _label _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for this case, + !insertmacro _IncreaseCounter + ${_label}: ; place it and add it's check to the temp file + !appendfile "${${_Switch}Tmp}" `!insertmacro _== $\`${${_Switch}Var}$\` $\`${_a}$\` ${_label} ""$\n` + !undef _label + !endif + !verbose pop + !macroend + + !macro _CaseElse + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifdef _Logic & ${_Logic}Select ; Check for an active Select + !insertmacro _Select_CaseElse + !else ifndef _Switch ; If not then check for an active Switch + !error "Cannot use Case without a preceding Select or Switch" + !else ifdef ${_Switch}Else ; Already had a default case? + !error "Cannot use CaseElse following a CaseElse" + !else + !define ${_Switch}Else _LogicLib_Label_${LOGICLIB_COUNTER} ; Get a label for the default case, + !insertmacro _IncreaseCounter + ${${_Switch}Else}: ; and place it + !endif + !verbose pop + !macroend + + !macro _EndSwitch + !verbose push + !verbose ${LOGICLIB_VERBOSITY} + !ifndef _Logic | ${_Logic}Switch + !error "Cannot use EndSwitch without a preceding Switch" + !endif + Goto ${_Break} ; Skip the jump table + ${${_Logic}Switch}: ; Place the end of the switch + !undef ${_Logic}Switch + !include "${${_Switch}Tmp}" ; Include the jump table + !delfile "${${_Switch}Tmp}" ; and clear it up + !ifdef ${_Switch}Else ; Was there a default case? + Goto ${${_Switch}Else} ; then go there if all else fails + !undef ${_Switch}Else + !endif + !undef ${_Switch}Tmp + !undef ${_Switch}Var + ${_Break}: ; Place the break label + !insertmacro _PopScope Break + !insertmacro _PopScope Switch + !insertmacro _PopLogic + !verbose pop + !macroend + !define EndSwitch `!insertmacro _EndSwitch` + +!endif ; LOGICLIB +!verbose 3 +!define LOGICLIB_VERBOSITY ${_LOGICLIB_VERBOSITY} +!undef _LOGICLIB_VERBOSITY +!verbose pop diff --git a/installer/NSIS/Include/MUI.nsh b/installer/NSIS/Include/MUI.nsh new file mode 100644 index 0000000..c2b2a71 --- /dev/null +++ b/installer/NSIS/Include/MUI.nsh @@ -0,0 +1 @@ +!include "${NSISDIR}\Contrib\Modern UI\System.nsh" \ No newline at end of file diff --git a/installer/NSIS/Include/MUI2.nsh b/installer/NSIS/Include/MUI2.nsh new file mode 100644 index 0000000..0e76adb --- /dev/null +++ b/installer/NSIS/Include/MUI2.nsh @@ -0,0 +1 @@ +!include "${NSISDIR}\Contrib\Modern UI 2\MUI2.nsh" \ No newline at end of file diff --git a/installer/NSIS/Include/Memento.nsh b/installer/NSIS/Include/Memento.nsh new file mode 100644 index 0000000..6aa1843 --- /dev/null +++ b/installer/NSIS/Include/Memento.nsh @@ -0,0 +1,526 @@ +!verbose push +!verbose 3 + +!include LogicLib.nsh +!include Sections.nsh + +!ifndef ___MEMENTO_NSH___ +!define ___MEMENTO_NSH___ + +##################################### +### Memento ### +##################################### + +/* + +Memento is a set of macros that allow installers to remember user selection +across separate runs of the installer. Currently, it can remember the state +of sections and mark new sections as bold. In the future, it'll integrate +InstallOptions and maybe even the Modern UI. + +A usage example can be found in `Examples\Memento.nsi`. + +*/ + +##################################### +### Usage Instructions ### +##################################### + +/* + +1. Declare usage of Memento by including Memento.nsh at the top of the script. + + !include Memento.nsh + +2. Define MEMENTO_REGISTRY_ROOT and MEMENTO_REGISTRY_KEY with the a registry key + where sections' state should be saved. + + !define MEMENTO_REGISTRY_ROOT HKLM + !define MEMENTO_REGISTRY_KEY \ + Software\Microsoft\Windows\CurrentVersion\Uninstall\MyProgram + +3. Replace Section with ${MementoSection} and SectionEnd with ${MementoSectionEnd} + for sections that whose state should be remembered by Memento. + + For sections that should be unselected by default, use ${MementoSection}'s + brother - ${MementoUnselectedSection}. + + Sections that don't already have an identifier must be assigned one. + + Section identifiers must stay the same across different versions of the + installer or their state will be forgotten. + +4. Use ${MementoSectionDone} after the last ${MementoSection}. + +5. Add a call to ${MementoSectionRestore} to .onInit to restore the state + of all sections from the registry. + + Function .onInit + + ${MementoSectionRestore} + + FunctionEnd + +6. Add a call to ${MementoSectionSave} to .onInstSuccess to save the state + of all sections to the registry. + + Function .onInstSuccess + + ${MementoSectionSave} + + FunctionEnd + +7. Tattoo the location of the chosen registry key on your arm. + +*/ + +##################################### +### User API ### +##################################### + +; +; ${MementoSection} +; +; Defines a section whose state is remembered by Memento. +; +; Usage is similar to Section. +; +; ${MementoSection} "name" "some_id" +; + +!define MementoSection "!insertmacro MementoSection" + +; +; ${MementoSectionEnd} +; +; Ends a section previously opened using ${MementoSection}. +; +; Usage is similar to SectionEnd. +; +; ${MementoSection} "name" "some_id" +; # some code... +; ${MementoSectionEnd} +; + +; +; ${MementoUnselectedSection} +; +; Defines a section whose state is remembered by Memento and is +; unselected by default. +; +; Usage is similar to Section with the /o switch. +; +; ${MementoUnselectedSection} "name" "some_id" +; + +!define MementoUnselectedSection "!insertmacro MementoUnselectedSection" + +; +; ${MementoSectionEnd} +; +; Ends a section previously opened using ${MementoSection}. +; +; Usage is similar to SectionEnd. +; +; ${MementoSection} "name" "some_id" +; # some code... +; ${MementoSectionEnd} +; + +!define MementoSectionEnd "!insertmacro MementoSectionEnd" + +; +; ${MementoSectionDone} +; +; Used after all ${MementoSection} have been set. +; +; ${MementoSection} "name1" "some_id1" +; # some code... +; ${MementoSectionEnd} +; +; ${MementoSection} "name2" "some_id2" +; # some code... +; ${MementoSectionEnd} +; +; ${MementoSection} "name3" "some_id3" +; # some code... +; ${MementoSectionEnd} +; +; ${MementoSectionDone} +; + +!define MementoSectionDone "!insertmacro MementoSectionDone" + +; +; ${MementoSectionRestore} +; +; Restores the state of all Memento sections from the registry. +; +; Commonly used in .onInit. +; +; Function .onInit +; +; ${MementoSectionRestore} +; +; FunctionEnd +; + +!define MementoSectionRestore "!insertmacro MementoSectionRestore" + +; +; ${MementoSectionSave} +; +; Saves the state of all Memento sections to the registry. +; +; Commonly used in .onInstSuccess. +; +; Function .onInstSuccess +; +; ${MementoSectionSave} +; +; FunctionEnd +; + +!define MementoSectionSave "!insertmacro MementoSectionSave" + + +##################################### +### Internal Defines ### +##################################### + +!define __MementoSectionIndex 1 + +##################################### +### Internal Macros ### +##################################### + +!macro __MementoCheckSettings + + !ifndef MEMENTO_REGISTRY_ROOT | MEMENTO_REGISTRY_KEY + + !error "MEMENTO_REGISTRY_ROOT and MEMENTO_REGISTRY_KEY must be defined before using any of Memento's macros" + + !endif + +!macroend + +!macro __MementoSection flags name id + + !insertmacro __MementoCheckSettings + + !ifndef __MementoSectionIndex + + !error "MementoSectionDone already used!" + + !endif + + !define __MementoSectionLastSectionId `${id}` + + !verbose pop + + Section ${flags} `${name}` `${id}` + + !verbose push + !verbose 3 + +!macroend + +##################################### +### User Macros ### +##################################### + +!macro MementoSection name id + + !verbose push + !verbose 3 + + !insertmacro __MementoSection "" `${name}` `${id}` + + !verbose pop + +!macroend + +!macro MementoUnselectedSection name id + + !verbose push + !verbose 3 + + !insertmacro __MementoSection /o `${name}` `${id}` + + !define __MementoSectionUnselected + + !verbose pop + +!macroend + +!macro MementoSectionEnd + + SectionEnd + + !verbose push + !verbose 3 + + !insertmacro __MementoCheckSettings + + !ifndef __MementoSectionIndex + + !error "MementoSectionDone already used!" + + !endif + + !define /MATH __MementoSectionIndexNext \ + ${__MementoSectionIndex} + 1 + + Function __MementoSectionMarkNew${__MementoSectionIndex} + + ClearErrors + ReadRegDWORD $0 ${MEMENTO_REGISTRY_ROOT} `${MEMENTO_REGISTRY_KEY}` `MementoSection_${__MementoSectionLastSectionId}` + + ${If} ${Errors} + + !insertmacro SetSectionFlag `${${__MementoSectionLastSectionId}}` ${SF_BOLD} + + ${EndIf} + + GetFunctionAddress $0 __MementoSectionMarkNew${__MementoSectionIndexNext} + Goto $0 + + FunctionEnd + + Function __MementoSectionRestoreStatus${__MementoSectionIndex} + + ClearErrors + ReadRegDWORD $0 ${MEMENTO_REGISTRY_ROOT} `${MEMENTO_REGISTRY_KEY}` `MementoSection_${__MementoSectionLastSectionId}` + + !ifndef __MementoSectionUnselected + + ${If} ${Errors} + ${OrIf} $0 != 0 + + !insertmacro SelectSection `${${__MementoSectionLastSectionId}}` + + ${Else} + + !insertmacro UnselectSection `${${__MementoSectionLastSectionId}}` + + ${EndIf} + + !else + + !undef __MementoSectionUnselected + + ${If} ${Errors} + ${OrIf} $0 == 0 + + !insertmacro UnselectSection `${${__MementoSectionLastSectionId}}` + + ${Else} + + !insertmacro SelectSection `${${__MementoSectionLastSectionId}}` + + ${EndIf} + + !endif + + GetFunctionAddress $0 __MementoSectionRestoreStatus${__MementoSectionIndexNext} + Goto $0 + + FunctionEnd + + Function __MementoSectionSaveStatus${__MementoSectionIndex} + + ${If} ${SectionIsSelected} `${${__MementoSectionLastSectionId}}` + + WriteRegDWORD ${MEMENTO_REGISTRY_ROOT} `${MEMENTO_REGISTRY_KEY}` `MementoSection_${__MementoSectionLastSectionId}` 1 + + ${Else} + + WriteRegDWORD ${MEMENTO_REGISTRY_ROOT} `${MEMENTO_REGISTRY_KEY}` `MementoSection_${__MementoSectionLastSectionId}` 0 + + ${EndIf} + + GetFunctionAddress $0 __MementoSectionSaveStatus${__MementoSectionIndexNext} + Goto $0 + + FunctionEnd + + !undef __MementoSectionIndex + !define __MementoSectionIndex ${__MementoSectionIndexNext} + !undef __MementoSectionIndexNext + + !undef __MementoSectionLastSectionId + + !verbose pop + +!macroend + +!macro MementoSectionDone + + !verbose push + !verbose 3 + + !insertmacro __MementoCheckSettings + + Function __MementoSectionMarkNew${__MementoSectionIndex} + FunctionEnd + + Function __MementoSectionRestoreStatus${__MementoSectionIndex} + FunctionEnd + + Function __MementoSectionSaveStatus${__MementoSectionIndex} + FunctionEnd + + !undef __MementoSectionIndex + + !verbose pop + +!macroend + +!macro MementoSectionRestore + + !verbose push + !verbose 3 + + !insertmacro __MementoCheckSettings + + Push $0 + Push $1 + Push $2 + Push $3 + + # check for first usage + + ClearErrors + + ReadRegStr $0 ${MEMENTO_REGISTRY_ROOT} `${MEMENTO_REGISTRY_KEY}` MementoSectionUsed + + ${If} ${Errors} + + # use script defaults on first run + Goto done + + ${EndIf} + + # mark new components in bold + + Call __MementoSectionMarkNew1 + + # mark section groups in bold + + StrCpy $0 0 + StrCpy $1 "" + StrCpy $2 "" + StrCpy $3 "" + + loop: + + ClearErrors + + ${If} ${SectionIsBold} $0 + + ${If} $1 != "" + + !insertmacro SetSectionFlag $1 ${SF_BOLD} + + ${EndIf} + + ${If} $2 != "" + + !insertmacro SetSectionFlag $2 ${SF_BOLD} + + ${EndIf} + + ${If} $3 != "" + + !insertmacro SetSectionFlag $3 ${SF_BOLD} + + ${EndIf} + + ${ElseIf} ${Errors} + + Goto loop_end + + ${EndIf} + + ${If} ${SectionIsSectionGroup} $0 + + ${If} $1 == "" + + StrCpy $1 $0 + + ${ElseIf} $2 == "" + + StrCpy $2 $0 + + ${ElseIf} $3 == "" + + StrCpy $3 $0 + + ${EndIf} + + ${EndIf} + + ${If} ${SectionIsSectionGroupEnd} $0 + + ${If} $3 != "" + + StrCpy $3 "" + + ${ElseIf} $2 != "" + + StrCpy $2 "" + + ${ElseIf} $1 != "" + + StrCpy $1 "" + + ${EndIf} + + ${EndIf} + + IntOp $0 $0 + 1 + + Goto loop + loop_end: + + # restore sections' status + + Call __MementoSectionRestoreStatus1 + + # all done + + done: + + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + !verbose pop + +!macroend + +!macro MementoSectionSave + + !verbose push + !verbose 3 + + !insertmacro __MementoCheckSettings + + Push $0 + + WriteRegStr ${MEMENTO_REGISTRY_ROOT} `${MEMENTO_REGISTRY_KEY}` MementoSectionUsed "" + + Call __MementoSectionSaveStatus1 + + Pop $0 + + !verbose pop + +!macroend + + + +!endif # ___MEMENTO_NSH___ + +!verbose pop diff --git a/installer/NSIS/Include/MultiUser.nsh b/installer/NSIS/Include/MultiUser.nsh new file mode 100644 index 0000000..c584fb5 --- /dev/null +++ b/installer/NSIS/Include/MultiUser.nsh @@ -0,0 +1,469 @@ +/* + +MultiUser.nsh + +Installer configuration for multi-user Windows environments + +Copyright 2008-2009 Joost Verburg + +*/ + +!ifndef MULTIUSER_INCLUDED +!define MULTIUSER_INCLUDED +!verbose push +!verbose 3 + +;Standard NSIS header files + +!ifdef MULTIUSER_MUI + !include MUI2.nsh +!endif +!include nsDialogs.nsh +!include LogicLib.nsh +!include WinVer.nsh +!include FileFunc.nsh + +;Variables + +Var MultiUser.Privileges +Var MultiUser.InstallMode + +;Command line installation mode setting + +!ifdef MULTIUSER_INSTALLMODE_COMMANDLINE + !include StrFunc.nsh + !ifndef StrStr_INCLUDED + ${StrStr} + !endif + !ifndef MULTIUSER_NOUNINSTALL + !ifndef UnStrStr_INCLUDED + ${UnStrStr} + !endif + !endif + + Var MultiUser.Parameters + Var MultiUser.Result +!endif + +;Installation folder stored in registry + +!ifdef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY & MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME + Var MultiUser.InstDir +!endif + +!ifdef MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY & MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME + Var MultiUser.DefaultKeyValue +!endif + +;Windows Vista UAC setting + +!if "${MULTIUSER_EXECUTIONLEVEL}" == Admin + RequestExecutionLevel admin + !define MULTIUSER_EXECUTIONLEVEL_ALLUSERS +!else if "${MULTIUSER_EXECUTIONLEVEL}" == Power + RequestExecutionLevel admin + !define MULTIUSER_EXECUTIONLEVEL_ALLUSERS +!else if "${MULTIUSER_EXECUTIONLEVEL}" == Highest + RequestExecutionLevel highest + !define MULTIUSER_EXECUTIONLEVEL_ALLUSERS +!else + RequestExecutionLevel user +!endif + +/* + +Install modes + +*/ + +!macro MULTIUSER_INSTALLMODE_ALLUSERS UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + + ;Install mode initialization - per-machine + + ${ifnot} ${IsNT} + ${orif} $MultiUser.Privileges == "Admin" + ${orif} $MultiUser.Privileges == "Power" + + StrCpy $MultiUser.InstallMode AllUsers + + SetShellVarContext all + + !if "${UNINSTALLER_PREFIX}" != UN + ;Set default installation location for installer + !ifdef MULTIUSER_INSTALLMODE_INSTDIR + StrCpy $INSTDIR "$PROGRAMFILES\${MULTIUSER_INSTALLMODE_INSTDIR}" + !endif + !endif + + !ifdef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY & MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME + + ReadRegStr $MultiUser.InstDir HKLM "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" + + ${if} $MultiUser.InstDir != "" + StrCpy $INSTDIR $MultiUser.InstDir + ${endif} + + !endif + + !ifdef MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION + Call "${MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION}" + !endif + + ${endif} + +!macroend + +!macro MULTIUSER_INSTALLMODE_CURRENTUSER UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + + ;Install mode initialization - per-user + + ${if} ${IsNT} + + StrCpy $MultiUser.InstallMode CurrentUser + + SetShellVarContext current + + !if "${UNINSTALLER_PREFIX}" != UN + ;Set default installation location for installer + !ifdef MULTIUSER_INSTALLMODE_INSTDIR + ${if} ${AtLeastWin2000} + StrCpy $INSTDIR "$LOCALAPPDATA\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${else} + StrCpy $INSTDIR "$PROGRAMFILES\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${endif} + !endif + !endif + + !ifdef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY & MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME + + ReadRegStr $MultiUser.InstDir HKCU "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" + + ${if} $MultiUser.InstDir != "" + StrCpy $INSTDIR $MultiUser.InstDir + ${endif} + + !endif + + !ifdef MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION + Call "${MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION}" + !endif + + ${endif} + +!macroend + +Function MultiUser.InstallMode.AllUsers + !insertmacro MULTIUSER_INSTALLMODE_ALLUSERS "" "" +FunctionEnd + +Function MultiUser.InstallMode.CurrentUser + !insertmacro MULTIUSER_INSTALLMODE_CURRENTUSER "" "" +FunctionEnd + +!ifndef MULTIUSER_NOUNINSTALL + +Function un.MultiUser.InstallMode.AllUsers + !insertmacro MULTIUSER_INSTALLMODE_ALLUSERS UN .un +FunctionEnd + +Function un.MultiUser.InstallMode.CurrentUser + !insertmacro MULTIUSER_INSTALLMODE_CURRENTUSER UN .un +FunctionEnd + +!endif + +/* + +Installer/uninstaller initialization + +*/ + +!macro MULTIUSER_INIT_QUIT UNINSTALLER_FUNCPREFIX + + !ifdef MULTIUSER_INIT_${UNINSTALLER_FUNCPREFIX}FUNCTIONQUIT + Call "${MULTIUSER_INIT_${UNINSTALLER_FUNCPREFIX}FUCTIONQUIT} + !else + Quit + !endif + +!macroend + +!macro MULTIUSER_INIT_TEXTS + + !ifndef MULTIUSER_INIT_TEXT_ADMINREQUIRED + !define MULTIUSER_INIT_TEXT_ADMINREQUIRED "$(^Caption) requires administrator priviledges." + !endif + + !ifndef MULTIUSER_INIT_TEXT_POWERREQUIRED + !define MULTIUSER_INIT_TEXT_POWERREQUIRED "$(^Caption) requires at least Power User priviledges." + !endif + + !ifndef MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE + !define MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE "Your user account does not have sufficient privileges to install $(^Name) for all users of this compuetr." + !endif + +!macroend + +!macro MULTIUSER_INIT_CHECKS UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + + ;Installer initialization - check privileges and set install mode + + !insertmacro MULTIUSER_INIT_TEXTS + + UserInfo::GetAccountType + Pop $MultiUser.Privileges + + ${if} ${IsNT} + + ;Check privileges + + !if "${MULTIUSER_EXECUTIONLEVEL}" == Admin + + ${if} $MultiUser.Privileges != "Admin" + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_ADMINREQUIRED}" + !insertmacro MULTIUSER_INIT_QUIT "${UNINSTALLER_FUNCPREFIX}" + ${endif} + + !else if "${MULTIUSER_EXECUTIONLEVEL}" == Power + + ${if} $MultiUser.Privileges != "Power" + ${andif} $MultiUser.Privileges != "Admin" + ${if} ${AtMostWinXP} + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_POWERREQUIRED}" + ${else} + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_ADMINREQUIRED}" + ${endif} + !insertmacro MULTIUSER_INIT_QUIT "${UNINSTALLER_FUNCPREFIX}" + ${endif} + + !endif + + !ifdef MULTIUSER_EXECUTIONLEVEL_ALLUSERS + + ;Default to per-machine installation if possible + + ${if} $MultiUser.Privileges == "Admin" + ${orif} $MultiUser.Privileges == "Power" + !ifndef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + !else + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !endif + + !ifdef MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY & MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME + + ;Set installation mode to setting from a previous installation + + !ifndef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER + ReadRegStr $MultiUser.DefaultKeyValue HKLM "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue == "" + ReadRegStr $MultiUser.DefaultKeyValue HKCU "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue != "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + ${endif} + !else + ReadRegStr $MultiUser.DefaultKeyValue HKCU "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue == "" + ReadRegStr $MultiUser.DefaultKeyValue HKLM "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" + ${if} $MultiUser.DefaultKeyValue != "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${endif} + ${endif} + !endif + + !endif + + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + + !else + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + + !endif + + !ifdef MULTIUSER_INSTALLMODE_COMMANDLINE + + ;Check for install mode setting on command line + + ${${UNINSTALLER_FUNCPREFIX}GetParameters} $MultiUser.Parameters + + ${${UNINSTALLER_PREFIX}StrStr} $MultiUser.Result $MultiUser.Parameters "/CurrentUser" + + ${if} $MultiUser.Result != "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + + ${${UNINSTALLER_PREFIX}StrStr} $MultiUser.Result $MultiUser.Parameters "/AllUsers" + + ${if} $MultiUser.Result != "" + ${if} $MultiUser.Privileges == "Admin" + ${orif} $MultiUser.Privileges == "Power" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + MessageBox MB_OK|MB_ICONSTOP "${MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE}" + ${endif} + ${endif} + + !endif + + ${else} + + ;Not running Windows NT, per-user installation not supported + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + + ${endif} + +!macroend + +!macro MULTIUSER_INIT + !verbose push + !verbose 3 + + !insertmacro MULTIUSER_INIT_CHECKS "" "" + + !verbose pop +!macroend + +!ifndef MULTIUSER_NOUNINSTALL + +!macro MULTIUSER_UNINIT + !verbose push + !verbose 3 + + !insertmacro MULTIUSER_INIT_CHECKS Un un. + + !verbose pop +!macroend + +!endif + +/* + +Modern UI 2 page + +*/ + +!ifdef MULTIUSER_MUI + +!macro MULTIUSER_INSTALLMODEPAGE_INTERFACE + + !ifndef MULTIUSER_INSTALLMODEPAGE_INTERFACE + !define MULTIUSER_INSTALLMODEPAGE_INTERFACE + Var MultiUser.InstallModePage + + Var MultiUser.InstallModePage.Text + + Var MultiUser.InstallModePage.AllUsers + Var MultiUser.InstallModePage.CurrentUser + + Var MultiUser.InstallModePage.ReturnValue + !endif + +!macroend + +!macro MULTIUSER_PAGEDECLARATION_INSTALLMODE + + !insertmacro MUI_SET MULTIUSER_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLMODEPAGE "" + !insertmacro MULTIUSER_INSTALLMODEPAGE_INTERFACE + + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_TOP "$(MULTIUSER_INNERTEXT_INSTALLMODE_TOP)" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_ALLUSERS "$(MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS)" + !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODEPAGE_TEXT_CURRENTUSER "$(MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER)" + + PageEx custom + + PageCallbacks MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MULTIUSER_FUNCTION_INSTALLMODEPAGE MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} + + !undef MULTIUSER_INSTALLMODEPAGE_TEXT_TOP + !undef MULTIUSER_INSTALLMODEPAGE_TEXT_ALLUSERS + !undef MULTIUSER_INSTALLMODEPAGE_TEXT_CURRENTUSER + +!macroend + +!macro MULTIUSER_PAGE_INSTALLMODE + + ;Modern UI page for install mode + + !verbose push + !verbose 3 + + !ifndef MULTIUSER_EXECUTIONLEVEL_ALLUSERS + !error "A mixed-mode installation requires MULTIUSER_EXECUTIONLEVEL to be set to Admin, Power or Highest." + !endif + + !insertmacro MUI_PAGE_INIT + !insertmacro MULTIUSER_PAGEDECLARATION_INSTALLMODE + + !verbose pop + +!macroend + +!macro MULTIUSER_FUNCTION_INSTALLMODEPAGE PRE LEAVE + + ;Page functions of Modern UI page + + Function "${PRE}" + + ${ifnot} ${IsNT} + Abort + ${endif} + + ${if} $MultiUser.Privileges != "Power" + ${andif} $MultiUser.Privileges != "Admin" + Abort + ${endif} + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MULTIUSER_TEXT_INSTALLMODE_TITLE) $(MULTIUSER_TEXT_INSTALLMODE_SUBTITLE) + + nsDialogs::Create 1018 + Pop $MultiUser.InstallModePage + + ${NSD_CreateLabel} 0u 0u 300u 20u "${MULTIUSER_INSTALLMODEPAGE_TEXT_TOP}" + Pop $MultiUser.InstallModePage.Text + + ${NSD_CreateRadioButton} 20u 50u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_TEXT_ALLUSERS}" + Pop $MultiUser.InstallModePage.AllUsers + + ${NSD_CreateRadioButton} 20u 70u 280u 10u "${MULTIUSER_INSTALLMODEPAGE_TEXT_CURRENTUSER}" + Pop $MultiUser.InstallModePage.CurrentUser + + ${if} $MultiUser.InstallMode == "AllUsers" + SendMessage $MultiUser.InstallModePage.AllUsers ${BM_SETCHECK} ${BST_CHECKED} 0 + ${else} + SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_SETCHECK} ${BST_CHECKED} 0 + ${endif} + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + nsDialogs::Show + + FunctionEnd + + Function "${LEAVE}" + SendMessage $MultiUser.InstallModePage.AllUsers ${BM_GETCHECK} 0 0 $MultiUser.InstallModePage.ReturnValue + + ${if} $MultiUser.InstallModePage.ReturnValue = ${BST_CHECKED} + Call MultiUser.InstallMode.AllUsers + ${else} + Call MultiUser.InstallMode.CurrentUser + ${endif} + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + FunctionEnd + +!macroend + +!endif + +!verbose pop +!endif diff --git a/installer/NSIS/Include/Sections.nsh b/installer/NSIS/Include/Sections.nsh new file mode 100644 index 0000000..07aa47f --- /dev/null +++ b/installer/NSIS/Include/Sections.nsh @@ -0,0 +1,273 @@ +; Sections.nsh +; +; Defines and macros for section control +; +; Include in your script using: +; !include "Sections.nsh" + +;-------------------------------- + +!ifndef SECTIONS_INCLUDED + +!define SECTIONS_INCLUDED + +;-------------------------------- + +; Generic section defines + +# section or section group is selected +!define SF_SELECTED 1 +# section group +!define SF_SECGRP 2 +!define SF_SUBSEC 2 # deprecated +# section group end marker +!define SF_SECGRPEND 4 +!define SF_SUBSECEND 4 # deprecated +# bold text (Section !blah) +!define SF_BOLD 8 +# read only (SectionIn RO) +!define SF_RO 16 +# expanded section group (SectionGroup /e blah) +!define SF_EXPAND 32 +# section group is partially selected +!define SF_PSELECTED 64 # internal +# internal +!define SF_TOGGLED 128 # internal +!define SF_NAMECHG 256 # internal + +# mask to toggle off the selected flag +!define SECTION_OFF 0xFFFFFFFE + +;-------------------------------- + +; Select / unselect / reserve section + +!macro SelectSection SECTION + + Push $0 + Push $1 + StrCpy $1 "${SECTION}" + SectionGetFlags $1 $0 + IntOp $0 $0 | ${SF_SELECTED} + SectionSetFlags $1 $0 + Pop $1 + Pop $0 + +!macroend + +!macro UnselectSection SECTION + + Push $0 + Push $1 + StrCpy $1 "${SECTION}" + SectionGetFlags $1 $0 + IntOp $0 $0 & ${SECTION_OFF} + SectionSetFlags $1 $0 + Pop $1 + Pop $0 + +!macroend + +; If section selected, will unselect, if unselected, will select + +!macro ReverseSection SECTION + + Push $0 + Push $1 + StrCpy $1 "${SECTION}" + SectionGetFlags $1 $0 + IntOp $0 $0 ^ ${SF_SELECTED} + SectionSetFlags $1 $0 + Pop $1 + Pop $0 + +!macroend + +;-------------------------------- + +; Macros for mutually exclusive section selection +; Written by Tim Gallagher +; +; See one-section.nsi for an example of usage + +; Starts the Radio Button Block +; You should pass a variable that keeps the selected section +; as the first parameter for this macro. This variable should +; be initialized to the default section's index. +; +; As this macro uses $R0 and $R1 you can't use those two as the +; varible which will keep the selected section. + +!macro StartRadioButtons var + + !define StartRadioButtons_Var "${var}" + + Push $R0 + + SectionGetFlags "${StartRadioButtons_Var}" $R0 + IntOp $R0 $R0 & ${SECTION_OFF} + SectionSetFlags "${StartRadioButtons_Var}" $R0 + + Push $R1 + + StrCpy $R1 "${StartRadioButtons_Var}" + +!macroend + +; A radio button + +!macro RadioButton SECTION_NAME + + SectionGetFlags ${SECTION_NAME} $R0 + IntOp $R0 $R0 & ${SF_SELECTED} + IntCmp $R0 ${SF_SELECTED} 0 +2 +2 + StrCpy "${StartRadioButtons_Var}" ${SECTION_NAME} + +!macroend + +; Ends the radio button block + +!macro EndRadioButtons + + StrCmp $R1 "${StartRadioButtons_Var}" 0 +4 ; selection hasn't changed + SectionGetFlags "${StartRadioButtons_Var}" $R0 + IntOp $R0 $R0 | ${SF_SELECTED} + SectionSetFlags "${StartRadioButtons_Var}" $R0 + + Pop $R1 + Pop $R0 + + !undef StartRadioButtons_Var + +!macroend + +;-------------------------------- + +; These are two macros you can use to set a Section in an InstType +; or clear it from an InstType. +; +; Written by Robert Kehl +; +; For details, see http://nsis.sourceforge.net/wiki/SetSectionInInstType%2C_ClearSectionInInstType +; +; Use the defines below for the WANTED_INSTTYPE paramter. + +!define INSTTYPE_1 1 +!define INSTTYPE_2 2 +!define INSTTYPE_3 4 +!define INSTTYPE_4 8 +!define INSTTYPE_5 16 +!define INSTTYPE_6 32 +!define INSTTYPE_7 64 +!define INSTTYPE_8 128 +!define INSTTYPE_9 256 +!define INSTTYPE_10 512 +!define INSTTYPE_11 1024 +!define INSTTYPE_12 2048 +!define INSTTYPE_13 4096 +!define INSTTYPE_14 8192 +!define INSTTYPE_15 16384 +!define INSTTYPE_16 32768 +!define INSTTYPE_17 65536 +!define INSTTYPE_18 131072 +!define INSTTYPE_19 262144 +!define INSTTYPE_20 524288 +!define INSTTYPE_21 1048576 +!define INSTTYPE_22 2097152 +!define INSTTYPE_23 4194304 +!define INSTTYPE_24 8388608 +!define INSTTYPE_25 16777216 +!define INSTTYPE_26 33554432 +!define INSTTYPE_27 67108864 +!define INSTTYPE_28 134217728 +!define INSTTYPE_29 268435456 +!define INSTTYPE_30 536870912 +!define INSTTYPE_31 1073741824 +!define INSTTYPE_32 2147483648 + +!macro SetSectionInInstType SECTION_NAME WANTED_INSTTYPE + + Push $0 + Push $1 + StrCpy $1 "${SECTION_NAME}" + SectionGetInstTypes $1 $0 + IntOp $0 $0 | ${WANTED_INSTTYPE} + SectionSetInstTypes $1 $0 + Pop $1 + Pop $0 + +!macroend + +!macro ClearSectionInInstType SECTION_NAME WANTED_INSTTYPE + + Push $0 + Push $1 + Push $2 + StrCpy $2 "${SECTION_NAME}" + SectionGetInstTypes $2 $0 + StrCpy $1 ${WANTED_INSTTYPE} + IntOp $1 $1 ~ + IntOp $0 $0 & $1 + SectionSetInstTypes $2 $0 + Pop $2 + Pop $1 + Pop $0 + +!macroend + +;-------------------------------- + +; Set / clear / check bits in a section's flags +; Written by derekrprice + +; Set one or more bits in a sections's flags + +!macro SetSectionFlag SECTION BITS + + Push $R0 + Push $R1 + StrCpy $R1 "${SECTION}" + SectionGetFlags $R1 $R0 + IntOp $R0 $R0 | "${BITS}" + SectionSetFlags $R1 $R0 + Pop $R1 + Pop $R0 + +!macroend + +; Clear one or more bits in section's flags + +!macro ClearSectionFlag SECTION BITS + + Push $R0 + Push $R1 + Push $R2 + StrCpy $R2 "${SECTION}" + SectionGetFlags $R2 $R0 + IntOp $R1 "${BITS}" ~ + IntOp $R0 $R0 & $R1 + SectionSetFlags $R2 $R0 + Pop $R2 + Pop $R1 + Pop $R0 + +!macroend + +; Check if one or more bits in section's flags are set +; If they are, jump to JUMPIFSET +; If not, jump to JUMPIFNOTSET + +!macro SectionFlagIsSet SECTION BITS JUMPIFSET JUMPIFNOTSET + Push $R0 + SectionGetFlags "${SECTION}" $R0 + IntOp $R0 $R0 & "${BITS}" + IntCmp $R0 "${BITS}" +3 + Pop $R0 + StrCmp "" "${JUMPIFNOTSET}" +3 "${JUMPIFNOTSET}" + Pop $R0 + Goto "${JUMPIFSET}" +!macroend + +;-------------------------------- + +!endif \ No newline at end of file diff --git a/installer/NSIS/Include/StrFunc.nsh b/installer/NSIS/Include/StrFunc.nsh new file mode 100644 index 0000000..f6c4b3e --- /dev/null +++ b/installer/NSIS/Include/StrFunc.nsh @@ -0,0 +1,1784 @@ +/* +o-----------------------------------------------------------------------------o +|String Functions Header File 1.09 | +(-----------------------------------------------------------------------------) +| By deguix / A Header file for NSIS 2.01 | +| -------------------------------| +| | +| This header file contains NSIS functions for string manipulation. | +o-----------------------------------------------------------------------------o +*/ + +!verbose push +!verbose 3 +!ifndef STRFUNC_VERBOSITY + !define STRFUNC_VERBOSITY 3 +!endif +!define _STRFUNC_VERBOSITY ${STRFUNC_VERBOSITY} +!undef STRFUNC_VERBOSITY +!verbose ${_STRFUNC_VERBOSITY} + +!include LogicLib.nsh + +!ifndef STRFUNC + + !define FALSE 0 + !define TRUE 1 + + ;Header File Identification + + !define STRFUNC `String Functions Header File` + !define STRFUNC_SHORT `StrFunc` + !define STRFUNC_CREDITS `2004 Diego Pedroso` + + ;Header File Version + + !define STRFUNC_VERMAJ 1 + !define STRFUNC_VERMED 09 + ;!define STRFUNC_VERMIN 0 + ;!define STRFUNC_VERBLD 0 + + !define STRFUNC_VER `${STRFUNC_VERMAJ}.${STRFUNC_VERMED}` + + ;Header File Init Message Prefix and Postfix + + !define STRFUNC_INITMSGPRE `----------------------------------------------------------------------$\r$\n` + !define STRFUNC_INITMSGPOST `$\r$\n----------------------------------------------------------------------$\r$\n` + + ;Header File Init Message + + !verbose push + !verbose 4 + !echo `${STRFUNC_INITMSGPRE}NSIS ${STRFUNC} ${STRFUNC_VER} - Copyright ${STRFUNC_CREDITS}${STRFUNC_INITMSGPOST}` + !verbose pop + + ;Header File Function Init Message Prefix and Postfix + + !define STRFUNC_FUNCMSGPRE `` + !define STRFUNC_FUNCMSGPOST `` + + ;Header File Function Macros + + !macro STRFUNC_FUNCLIST_INSERT Name + !ifdef StrFunc_List + !define StrFunc_List2 `${StrFunc_List}` + !undef StrFunc_List + !define StrFunc_List `${StrFunc_List2}|${Name}` + !undef StrFunc_List2 + !else + !define StrFunc_List `${Name}` + !endif + !macroend + + !macro STRFUNC_DEFFUNC Name + !insertmacro STRFUNC_FUNCLIST_INSERT ${Name} + + !define `${Name}` `!insertmacro FUNCTION_STRING_${Name}` + !define `Un${Name}` `!insertmacro FUNCTION_STRING_Un${Name}` + !macroend + + !macro STRFUNC_FUNC ShortName Credits + !verbose push + !verbose 4 + + !ifndef `Un${ShortName}` + !echo `${STRFUNC_FUNCMSGPRE}$ {Un${ShortName}} - Copyright ${Credits}${STRFUNC_FUNCMSGPOST}` + !verbose pop + !define `Un${ShortName}` `!insertmacro FUNCTION_STRING_Un${ShortName}_Call` + !define `Un${ShortName}_INCLUDED` + Function `un.${ShortName}` + !else + !echo `${STRFUNC_FUNCMSGPRE}$ {${ShortName}} - Copyright ${Credits}${STRFUNC_FUNCMSGPOST}` + !verbose pop + !undef `${ShortName}` + !define `${ShortName}` `!insertmacro FUNCTION_STRING_${ShortName}_Call` + !define `${ShortName}_INCLUDED` + Function `${ShortName}` + !endif + !macroend + + ;Function Names Startup Definition + + !insertmacro STRFUNC_DEFFUNC StrCase + !define StrCase_List `ResultVar|String|Type` + !define StrCase_TypeList `Output|Text|Option U L T S <>` + !macro `FUNCTION_STRING_UnStrCase` + !undef UnStrCase + !insertmacro FUNCTION_STRING_StrCase + !macroend + + !insertmacro STRFUNC_DEFFUNC StrClb + !define StrClb_List `ResultVar|String|Action` + !define StrClb_TypeList `Output|Text|Option > < <>` + !macro `FUNCTION_STRING_UnStrClb` + !undef UnStrClb + !insertmacro FUNCTION_STRING_StrClb + !macroend + + !insertmacro STRFUNC_DEFFUNC StrIOToNSIS + !define StrIOToNSIS_List `ResultVar|String` + !define StrIOToNSIS_TypeList `Output|Text` + !macro `FUNCTION_STRING_UnStrIOToNSIS` + !undef UnStrIOToNSIS + !insertmacro FUNCTION_STRING_StrIOToNSIS + !macroend + + !insertmacro STRFUNC_DEFFUNC StrLoc + !define StrLoc_List `ResultVar|String|StrToSearchFor|CounterDirection` + !define StrLoc_TypeList `Output|Text|Text|Option > <` + !macro `FUNCTION_STRING_UnStrLoc` + !undef UnStrLoc + !insertmacro FUNCTION_STRING_StrLoc + !macroend + + !insertmacro STRFUNC_DEFFUNC StrNSISToIO + !define StrNSISToIO_List `ResultVar|String` + !define StrNSISToIO_TypeList `Output|Text` + !macro `FUNCTION_STRING_UnStrNSISToIO` + !undef UnStrNSISToIO + !insertmacro FUNCTION_STRING_StrNSISToIO + !macroend + + !insertmacro STRFUNC_DEFFUNC StrRep + !define StrRep_List `ResultVar|String|StrToReplace|ReplacementString` + !define StrRep_TypeList `Output|Text|Text|Text` + !macro `FUNCTION_STRING_UnStrRep` + !undef UnStrRep + !insertmacro FUNCTION_STRING_StrRep + !macroend + + !insertmacro STRFUNC_DEFFUNC StrSort + !define StrSort_List `ResultVar|String|LeftStr|CenterStr|RightStr|IncludeLeftStr|IncludeCenterStr|IncludeRightStr` + !define StrSort_TypeList `Output|Text|Text|Text|Text|Option 1 0|Option 1 0|Option 1 0` + !macro `FUNCTION_STRING_UnStrSort` + !undef UnStrSort + !insertmacro FUNCTION_STRING_StrSort + !macroend + + !insertmacro STRFUNC_DEFFUNC StrStr + !define StrStr_List `ResultVar|String|StrToSearchFor` + !define StrStr_TypeList `Output|Text|Text` + !macro `FUNCTION_STRING_UnStrStr` + !undef UnStrStr + !insertmacro FUNCTION_STRING_StrStr + !macroend + + !insertmacro STRFUNC_DEFFUNC StrStrAdv + !define StrStrAdv_List `ResultVar|String|StrToSearchFor|SearchDirection|ResultStrDirection|DisplayStrToSearch|Loops|CaseSensitive` + !define StrStrAdv_TypeList `Output|Text|Text|Option > <|Option > <|Option 1 0|Text|Option 0 1` + !macro `FUNCTION_STRING_UnStrStrAdv` + !undef UnStrStrAdv + !insertmacro FUNCTION_STRING_StrStrAdv + !macroend + + !insertmacro STRFUNC_DEFFUNC StrTok + !define StrTok_List `ResultVar|String|Separators|ResultPart|SkipEmptyParts` + !define StrTok_TypeList `Output|Text|Text|Mixed L|Option 1 0` + !macro `FUNCTION_STRING_UnStrTok` + !undef UnStrTok + !insertmacro FUNCTION_STRING_StrTok + !macroend + + !insertmacro STRFUNC_DEFFUNC StrTrimNewLines + !define StrTrimNewLines_List `ResultVar|String` + !define StrTrimNewLines_TypeList `Output|Text` + !macro `FUNCTION_STRING_UnStrTrimNewLines` + !undef UnStrTrimNewLines + !insertmacro FUNCTION_STRING_StrTrimNewLines + !macroend + + ;Function Codes for Install and Uninstall + + # Function StrCase + ################ + + !macro FUNCTION_STRING_StrCase + !insertmacro STRFUNC_FUNC `StrCase` `2004 Diego Pedroso - Based on functions by Dave Laundon` + + /*After this point: + ------------------------------------------ + $0 = String (input) + $1 = Type (input) + $2 = StrLength (temp) + $3 = StartChar (temp) + $4 = EndChar (temp) + $5 = ResultStr (temp) + $6 = CurrentChar (temp) + $7 = LastChar (temp) + $8 = Temp (temp)*/ + + ;Get input from user + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + + ;Initialize variables + StrCpy $2 "" + StrCpy $3 "" + StrCpy $4 "" + StrCpy $5 "" + StrCpy $6 "" + StrCpy $7 "" + StrCpy $8 "" + + ;Upper and lower cases are simple to use + ${If} $1 == "U" + + ;Upper Case System: + ;------------------ + ; Convert all characters to upper case. + + System::Call "User32::CharUpper(t r0 r5)i" + Goto StrCase_End + ${ElseIf} $1 == "L" + + ;Lower Case System: + ;------------------ + ; Convert all characters to lower case. + + System::Call "User32::CharLower(t r0 r5)i" + Goto StrCase_End + ${EndIf} + + ;For the rest of cases: + ;Get "String" length + StrLen $2 $0 + + ;Make a loop until the end of "String" + ${For} $3 0 $2 + ;Add 1 to "EndChar" counter also + IntOp $4 $3 + 1 + + # Step 1: Detect one character at a time + + ;Remove characters before "StartChar" except when + ;"StartChar" is the first character of "String" + ${If} $3 <> 0 + StrCpy $6 $0 `` $3 + ${EndIf} + + ;Remove characters after "EndChar" except when + ;"EndChar" is the last character of "String" + ${If} $4 <> $2 + ${If} $3 = 0 + StrCpy $6 $0 1 + ${Else} + StrCpy $6 $6 1 + ${EndIf} + ${EndIf} + + # Step 2: Convert to the advanced case user chose: + + ${If} $1 == "T" + + ;Title Case System: + ;------------------ + ; Convert all characters after a non-alphabetic character to upper case. + ; Else convert to lower case. + + ;Use "IsCharAlpha" for the job + System::Call "*(&t1 r7) i .r8" + System::Call "*$8(&i1 .r7)" + System::Free $8 + System::Call "user32::IsCharAlpha(i r7) i .r8" + + ;Verify "IsCharAlpha" result and convert the character + ${If} $8 = 0 + System::Call "User32::CharUpper(t r6 r6)i" + ${Else} + System::Call "User32::CharLower(t r6 r6)i" + ${EndIf} + ${ElseIf} $1 == "S" + + ;Sentence Case System: + ;------------------ + ; Convert all characters after a ".", "!" or "?" character to upper case. + ; Else convert to lower case. Spaces or tabs after these marks are ignored. + + ;Detect current characters and ignore if necessary + ${If} $6 == " " + ${OrIf} $6 == "$\t" + Goto IgnoreLetter + ${EndIf} + + ;Detect last characters and convert + ${If} $7 == "." + ${OrIf} $7 == "!" + ${OrIf} $7 == "?" + ${OrIf} $7 == "" + System::Call "User32::CharUpper(t r6 r6)i" + ${Else} + System::Call "User32::CharLower(t r6 r6)i" + ${EndIf} + ${ElseIf} $1 == "<>" + + ;Switch Case System: + ;------------------ + ; Switch all characters cases to their inverse case. + + ;Use "IsCharUpper" for the job + System::Call "*(&t1 r6) i .r8" + System::Call "*$8(&i1 .r7)" + System::Free $8 + System::Call "user32::IsCharUpper(i r7) i .r8" + + ;Verify "IsCharUpper" result and convert the character + ${If} $8 = 0 + System::Call "User32::CharUpper(t r6 r6)i" + ${Else} + System::Call "User32::CharLower(t r6 r6)i" + ${EndIf} + ${EndIf} + + ;Write the character to "LastChar" + StrCpy $7 $6 + + IgnoreLetter: + ;Add this character to "ResultStr" + StrCpy $5 `$5$6` + ${Next} + + StrCase_End: + + /*After this point: + ------------------------------------------ + $0 = OutVar (output)*/ + + ; Copy "ResultStr" to "OutVar" + StrCpy $0 $5 + + ;Return output to user + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + FunctionEnd + + !macroend + + !macro FUNCTION_STRING_StrClb + !insertmacro STRFUNC_FUNC `StrClb` `2004 Diego Pedroso - Based on functions by Nik Medved` + + /*After this point: + ------------------------------------------ + $0 = String (input) + $1 = Action (input) + $2 = Lock/Unlock (temp) + $3 = Temp (temp) + $4 = Temp2 (temp)*/ + + ;Get input from user + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + + StrCpy $2 "" + StrCpy $3 "" + StrCpy $4 "" + + ;Open the clipboard to do the operations the user chose (kichik's fix) + System::Call 'user32::OpenClipboard(i $HWNDPARENT)' + + ${If} $1 == ">" ;Set + + ;Step 1: Clear the clipboard + System::Call 'user32::EmptyClipboard()' + + ;Step 2: Allocate global heap + StrLen $2 $0 + IntOp $2 $2 + 1 + System::Call 'kernel32::GlobalAlloc(i 2, i r2) i.r2' + + ;Step 3: Lock the handle + System::Call 'kernel32::GlobalLock(i r2) i.r3' + + ;Step 4: Copy the text to locked clipboard buffer + System::Call 'kernel32::lstrcpyA(i r3, t r0)' + + ;Step 5: Unlock the handle again + System::Call 'kernel32::GlobalUnlock(i r2)' + + ;Step 6: Set the information to the clipboard + System::Call 'user32::SetClipboardData(i 1, i r2)' + + StrCpy $0 "" + + ${ElseIf} $1 == "<" ;Get + + ;Step 1: Get clipboard data + System::Call 'user32::GetClipboardData(i 1) i .r2' + + ;Step 2: Lock and copy data (kichik's fix) + System::Call 'kernel32::GlobalLock(i r2) t .r0' + + ;Step 3: Unlock (kichik's fix) + System::Call 'kernel32::GlobalUnlock(i r2)' + + ${ElseIf} $1 == "<>" ;Swap + + ;Step 1: Get clipboard data + System::Call 'user32::GetClipboardData(i 1) i .r2' + + ;Step 2: Lock and copy data (kichik's fix) + System::Call 'kernel32::GlobalLock(i r2) t .r4' + + ;Step 3: Unlock (kichik's fix) + System::Call 'kernel32::GlobalUnlock(i r2)' + + ;Step 4: Clear the clipboard + System::Call 'user32::EmptyClipboard()' + + ;Step 5: Allocate global heap + StrLen $2 $0 + IntOp $2 $2 + 1 + System::Call 'kernel32::GlobalAlloc(i 2, i r2) i.r2' + + ;Step 6: Lock the handle + System::Call 'kernel32::GlobalLock(i r2) i.r3' + + ;Step 7: Copy the text to locked clipboard buffer + System::Call 'kernel32::lstrcpyA(i r3, t r0)' + + ;Step 8: Unlock the handle again + System::Call 'kernel32::GlobalUnlock(i r2)' + + ;Step 9: Set the information to the clipboard + System::Call 'user32::SetClipboardData(i 1, i r2)' + + StrCpy $0 $4 + ${Else} ;Clear + + ;Step 1: Clear the clipboard + System::Call 'user32::EmptyClipboard()' + + StrCpy $0 "" + ${EndIf} + + ;Close the clipboard + System::Call 'user32::CloseClipboard()' + + /*After this point: + ------------------------------------------ + $0 = OutVar (output)*/ + + ;Return result to user + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + FunctionEnd + + !macroend + + # Function StrIOToNSIS + #################### + + !macro FUNCTION_STRING_StrIOToNSIS + !insertmacro STRFUNC_FUNC `StrIOToNSIS` `2004 "bluenet" - Based on functions by Amir Szekely, Joost Verburg, Dave Laundon and Diego Pedroso` + + /*After this point: + ------------------------------------------ + $R0 = String (input/output) + $R1 = StartCharPos (temp) + $R2 = StrLen (temp) + $R3 = TempStr (temp) + $R4 = TempRepStr (temp)*/ + + ;Get input from user + Exch $R0 + Push $R1 + Push $R2 + Push $R3 + Push $R4 + + ;Get "String" length + StrLen $R2 $R0 + + ;Loop until "String" end is reached + ${For} $R1 0 $R2 + ;Get the next "String" characters + StrCpy $R3 $R0 2 $R1 + + ;Detect if current character is: + ${If} $R3 == "\\" ;Back-slash + StrCpy $R4 "\" + ${ElseIf} $R3 == "\r" ;Carriage return + StrCpy $R4 "$\r" + ${ElseIf} $R3 == "\n" ;Line feed + StrCpy $R4 "$\n" + ${ElseIf} $R3 == "\t" ;Tab + StrCpy $R4 "$\t" + ${Else} ;Anything else + StrCpy $R4 "" + ${EndIf} + + ;Detect if "TempRepStr" is not empty + ${If} $R4 != "" + ;Replace the old characters with the new one + StrCpy $R3 $R0 $R1 + IntOp $R1 $R1 + 2 + StrCpy $R0 $R0 "" $R1 + StrCpy $R0 "$R3$R4$R0" + IntOp $R2 $R2 - 1 ;Decrease "StrLen" + IntOp $R1 $R1 - 2 ;Go back to the next character + ${EndIf} + ${Next} + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Exch $R0 + FunctionEnd + !macroend + + # Function StrLoc + ############### + + !macro FUNCTION_STRING_StrLoc + !insertmacro STRFUNC_FUNC `StrLoc` `2004 Diego Pedroso - Based on functions by Ximon Eighteen` + + /*After this point: + ------------------------------------------ + $R0 = OffsetDirection (input) + $R1 = StrToSearch (input) + $R2 = String (input) + $R3 = StrToSearchLen (temp) + $R4 = StrLen (temp) + $R5 = StartCharPos (temp) + $R6 = TempStr (temp)*/ + + ;Get input from user + Exch $R0 + Exch + Exch $R1 + Exch 2 + Exch $R2 + Push $R3 + Push $R4 + Push $R5 + Push $R6 + + ;Get "String" and "StrToSearch" length + StrLen $R3 $R1 + StrLen $R4 $R2 + ;Start "StartCharPos" counter + StrCpy $R5 0 + + ;Loop until "StrToSearch" is found or "String" reaches its end + ${Do} + ;Remove everything before and after the searched part ("TempStr") + StrCpy $R6 $R2 $R3 $R5 + + ;Compare "TempStr" with "StrToSearch" + ${If} $R6 == $R1 + ${If} $R0 == `<` + IntOp $R6 $R3 + $R5 + IntOp $R0 $R4 - $R6 + ${Else} + StrCpy $R0 $R5 + ${EndIf} + ${ExitDo} + ${EndIf} + ;If not "StrToSearch", this could be "String" end + ${If} $R5 >= $R4 + StrCpy $R0 `` + ${ExitDo} + ${EndIf} + ;If not, continue the loop + IntOp $R5 $R5 + 1 + ${Loop} + + ;Return output to user + Pop $R6 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch + Pop $R1 + Exch $R0 + FunctionEnd + + !macroend + + # Function StrNSISToIO + #################### + + !macro FUNCTION_STRING_StrNSISToIO + !insertmacro STRFUNC_FUNC `StrNSISToIO` `2004 "bluenet" - Based on functions by Amir Szekely, Joost Verburg, Dave Laundon and Diego Pedroso` + + /*After this point: + ------------------------------------------ + $R0 = String (input/output) + $R1 = StartCharPos (temp) + $R2 = StrLen (temp) + $R3 = TempStr (temp) + $R4 = TempRepStr (temp)*/ + + ;Get input from user + Exch $R0 + Push $R1 + Push $R2 + Push $R3 + Push $R4 + + ;Get "String" length + StrLen $R2 $R0 + + ;Loop until "String" end is reached + ${For} $R1 0 $R2 + ;Get the next "String" character + StrCpy $R3 $R0 1 $R1 + + ;Detect if current character is: + ${If} $R3 == "$\r" ;Back-slash + StrCpy $R4 "\r" + ${ElseIf} $R3 == "$\n" ;Carriage return + StrCpy $R4 "\n" + ${ElseIf} $R3 == "$\t" ;Line feed + StrCpy $R4 "\t" + ${ElseIf} $R3 == "\" ;Tab + StrCpy $R4 "\\" + ${Else} ;Anything else + StrCpy $R4 "" + ${EndIf} + + ;Detect if "TempRepStr" is not empty + ${If} $R4 != "" + ;Replace the old character with the new ones + StrCpy $R3 $R0 $R1 + IntOp $R1 $R1 + 1 + StrCpy $R0 $R0 "" $R1 + StrCpy $R0 "$R3$R4$R0" + IntOp $R2 $R2 + 1 ;Increase "StrLen" + ${EndIf} + ${Next} + + ;Return output to user + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Exch $R0 + FunctionEnd + !macroend + + # Function StrRep + ############### + + !macro FUNCTION_STRING_StrRep + !insertmacro STRFUNC_FUNC `StrRep` `2004 Diego Pedroso - Based on functions by Hendri Adriaens` + + /*After this point: + ------------------------------------------ + $R0 = ReplacementString (input) + $R1 = StrToSearch (input) + $R2 = String (input) + $R3 = RepStrLen (temp) + $R4 = StrToSearchLen (temp) + $R5 = StrLen (temp) + $R6 = StartCharPos (temp) + $R7 = TempStrL (temp) + $R8 = TempStrR (temp)*/ + + ;Get input from user + Exch $R0 + Exch + Exch $R1 + Exch + Exch 2 + Exch $R2 + Push $R3 + Push $R4 + Push $R5 + Push $R6 + Push $R7 + Push $R8 + + ;Return "String" if "StrToSearch" is "" + ${IfThen} $R1 == "" ${|} Goto Done ${|} + + ;Get "ReplacementString", "String" and "StrToSearch" length + StrLen $R3 $R0 + StrLen $R4 $R1 + StrLen $R5 $R2 + ;Start "StartCharPos" counter + StrCpy $R6 0 + + ;Loop until "StrToSearch" is found or "String" reaches its end + ${Do} + ;Remove everything before and after the searched part ("TempStrL") + StrCpy $R7 $R2 $R4 $R6 + + ;Compare "TempStrL" with "StrToSearch" + ${If} $R7 == $R1 + ;Split "String" to replace the string wanted + StrCpy $R7 $R2 $R6 ;TempStrL + + ;Calc: "StartCharPos" + "StrToSearchLen" = EndCharPos + IntOp $R8 $R6 + $R4 + + StrCpy $R8 $R2 "" $R8 ;TempStrR + + ;Insert the new string between the two separated parts of "String" + StrCpy $R2 $R7$R0$R8 + ;Now calculate the new "StrLen" and "StartCharPos" + StrLen $R5 $R2 + IntOp $R6 $R6 + $R3 + ${Continue} + ${EndIf} + + ;If not "StrToSearch", this could be "String" end + ${IfThen} $R6 >= $R5 ${|} ${ExitDo} ${|} + ;If not, continue the loop + IntOp $R6 $R6 + 1 + ${Loop} + + Done: + + /*After this point: + ------------------------------------------ + $R0 = OutVar (output)*/ + + ;Return output to user + StrCpy $R0 $R2 + Pop $R8 + Pop $R7 + Pop $R6 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Exch $R0 + FunctionEnd + + !macroend + + # Function StrSort + ################ + + !macro FUNCTION_STRING_StrSort + !insertmacro STRFUNC_FUNC `StrSort` `2004 Diego Pedroso - Based on functions by Stuart Welch` + + /*After this point: + ------------------------------------------ + $R0 = String (input) + $R1 = LeftStr (input) + $R2 = CenterStr (input) + $R3 = RightStr (input) + $R4 = IncludeLeftStr (input) + $R5 = IncludeCenterStr (input) + $R6 = IncludeRightStr (input) + + $0 = StrLen (temp) + $1 = LeftStrLen (temp) + $2 = CenterStrLen (temp) + $3 = RightStrLen (temp) + $4 = StartPos (temp) + $5 = EndPos (temp) + $6 = StartCharPos (temp) + $7 = EndCharPos (temp) + $8 = TempStr (temp)*/ + + ;Get input from user + Exch $R6 + Exch + Exch $R5 + Exch + Exch 2 + Exch $R4 + Exch 2 + Exch 3 + Exch $R3 + Exch 3 + Exch 4 + Exch $R2 + Exch 4 + Exch 5 + Exch $R1 + Exch 5 + Exch 6 + Exch $R0 + Exch 6 + Push $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + + ;Parameter defaults + ${IfThen} $R4 == `` ${|} StrCpy $R4 `1` ${|} + ${IfThen} $R5 == `` ${|} StrCpy $R5 `1` ${|} + ${IfThen} $R6 == `` ${|} StrCpy $R6 `1` ${|} + + ;Get "String", "CenterStr", "LeftStr" and "RightStr" length + StrLen $0 $R0 + StrLen $1 $R1 + StrLen $2 $R2 + StrLen $3 $R3 + ;Start "StartCharPos" counter + StrCpy $6 0 + ;Start "EndCharPos" counter based on "CenterStr" length + IntOp $7 $6 + $2 + + ;Loop until "CenterStr" is found or "String" reaches its end + ${Do} + ;Remove everything before and after the searched part ("TempStr") + StrCpy $8 $R0 $2 $6 + + ;Compare "TempStr" with "CenterStr" + ${IfThen} $8 == $R2 ${|} ${ExitDo} ${|} + ;If not, this could be "String" end + ${IfThen} $7 >= $0 ${|} Goto Done ${|} + ;If not, continue the loop + IntOp $6 $6 + 1 + IntOp $7 $7 + 1 + ${Loop} + + # "CenterStr" was found + + ;Remove "CenterStr" from "String" if the user wants + ${If} $R5 = ${FALSE} + StrCpy $8 $R0 $6 + StrCpy $R0 $R0 `` $7 + StrCpy $R0 $8$R0 + ${EndIf} + + ;"StartPos" and "EndPos" will record "CenterStr" coordinates for now + StrCpy $4 $6 + StrCpy $5 $7 + ;"StartCharPos" and "EndCharPos" should be before "CenterStr" + IntOp $6 $6 - $1 + IntOp $7 $6 + $1 + + ;Loop until "LeftStr" is found or "String" reaches its start + ${Do} + ;Remove everything before and after the searched part ("TempStr") + StrCpy $8 $R0 $1 $6 + + ;If "LeftStr" is empty + ${If} $R1 == `` + StrCpy $6 0 + StrCpy $7 0 + ${ExitDo} + ${EndIf} + + ;Compare "TempStr" with "LeftStr" + ${IfThen} $8 == $R1 ${|} ${ExitDo} ${|} + ;If not, this could be "String" start + ${IfThen} $6 <= 0 ${|} ${ExitDo} ${|} + ;If not, continue the loop + IntOp $6 $6 - 1 + IntOp $7 $7 - 1 + ${Loop} + + # "LeftStr" is found or "String" start was reached + + ;Remove "LeftStr" from "String" if the user wants + ${If} $R4 = ${FALSE} + IntOp $6 $6 + $1 + ${EndIf} + + ;Record "LeftStr" first character position on "TempStr" (temporarily) + StrCpy $8 $6 + + ;"StartCharPos" and "EndCharPos" should be after "CenterStr" + ${If} $R5 = ${FALSE} + StrCpy $6 $4 + ${Else} + IntOp $6 $4 + $2 + ${EndIf} + IntOp $7 $6 + $3 + + ;Record "LeftStr" first character position on "StartPos" + StrCpy $4 $8 + + ;Loop until "RightStr" is found or "String" reaches its end + ${Do} + ;Remove everything before and after the searched part ("TempStr") + StrCpy $8 $R0 $3 $6 + + ;If "RightStr" is empty + ${If} $R3 == `` + StrCpy $6 $0 + StrCpy $7 $0 + ${ExitDo} + ${EndIf} + + ;Compare "TempStr" with "RightStr" + ${IfThen} $8 == $R3 ${|} ${ExitDo} ${|} + ;If not, this could be "String" end + ${IfThen} $7 >= $0 ${|} ${ExitDo} ${|} + ;If not, continue the loop + IntOp $6 $6 + 1 + IntOp $7 $7 + 1 + ${Loop} + + ;Remove "RightStr" from "String" if the user wants + ${If} $R6 = ${FALSE} + IntOp $7 $7 - $3 + ${EndIf} + + ;Record "RightStr" last character position on "StartPos" + StrCpy $5 $7 + + ;As the positionment is relative... + IntOp $5 $5 - $4 + + ;Write the string and finish the job + StrCpy $R0 $R0 $5 $4 + Goto +2 + + Done: + StrCpy $R0 `` + + /*After this point: + ------------------------------------------ + $R0 = OutVar (output)*/ + + ;Return output to user + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Pop $R6 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Exch $R0 + FunctionEnd + + !macroend + + # Function StrStr + ############### + + !macro FUNCTION_STRING_StrStr + !insertmacro STRFUNC_FUNC `StrStr` `2004 Diego Pedroso - Based on functions by Ximon Eighteen` + + /*After this point: + ------------------------------------------ + $R0 = StrToSearch (input) + $R1 = String (input) + $R2 = StrToSearchLen (temp) + $R3 = StrLen (temp) + $R4 = StartCharPos (temp) + $R5 = TempStr (temp)*/ + + ;Get input from user + Exch $R0 + Exch + Exch $R1 + Push $R2 + Push $R3 + Push $R4 + Push $R5 + + ;Get "String" and "StrToSearch" length + StrLen $R2 $R0 + StrLen $R3 $R1 + ;Start "StartCharPos" counter + StrCpy $R4 0 + + ;Loop until "StrToSearch" is found or "String" reaches its end + ${Do} + ;Remove everything before and after the searched part ("TempStr") + StrCpy $R5 $R1 $R2 $R4 + + ;Compare "TempStr" with "StrToSearch" + ${IfThen} $R5 == $R0 ${|} ${ExitDo} ${|} + ;If not "StrToSearch", this could be "String" end + ${IfThen} $R4 >= $R3 ${|} ${ExitDo} ${|} + ;If not, continue the loop + IntOp $R4 $R4 + 1 + ${Loop} + + /*After this point: + ------------------------------------------ + $R0 = OutVar (output)*/ + + ;Remove part before "StrToSearch" on "String" (if there has one) + StrCpy $R0 $R1 `` $R4 + + ;Return output to user + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Exch $R0 + FunctionEnd + + !macroend + + # Function StrStrAdv + ################## + + !macro FUNCTION_STRING_StrStrAdv + !insertmacro STRFUNC_FUNC `StrStrAdv` `2003-2004 Diego Pedroso` + + /*After this point: + ------------------------------------------ + $0 = String (input) + $1 = StringToSearch (input) + $2 = DirectionOfSearch (input) + $3 = DirectionOfReturn (input) + $4 = ShowStrToSearch (input) + $5 = NumLoops (input) + $6 = CaseSensitive (input) + $7 = StringLength (temp) + $8 = StrToSearchLength (temp) + $9 = CurrentLoop (temp) + $R0 = EndCharPos (temp) + $R1 = StartCharPos (temp) + $R2 = OutVar (output) + $R3 = Temp (temp)*/ + + ;Get input from user + + Exch $6 + Exch + Exch $5 + Exch + Exch 2 + Exch $4 + Exch 2 + Exch 3 + Exch $3 + Exch 3 + Exch 4 + Exch $2 + Exch 4 + Exch 5 + Exch $1 + Exch 5 + Exch 6 + Exch $0 + Exch 6 + Push $7 + Push $8 + Push $9 + Push $R3 + Push $R2 + Push $R1 + Push $R0 + + ; Clean $R0-$R3 variables + StrCpy $R0 "" + StrCpy $R1 "" + StrCpy $R2 "" + StrCpy $R3 "" + + ; Verify if we have the correct values on the variables + ${If} $0 == `` + SetErrors ;AdvStrStr_StrToSearch not found + Goto AdvStrStr_End + ${EndIf} + + ${If} $1 == `` + SetErrors ;No text to search + Goto AdvStrStr_End + ${EndIf} + + ${If} $2 != < + StrCpy $2 > + ${EndIf} + + ${If} $3 != < + StrCpy $3 > + ${EndIf} + + ${If} $4 <> 0 + StrCpy $4 1 + ${EndIf} + + ${If} $5 <= 0 + StrCpy $5 0 + ${EndIf} + + ${If} $6 <> 1 + StrCpy $6 0 + ${EndIf} + + ; Find "AdvStrStr_String" length + StrLen $7 $0 + + ; Then find "AdvStrStr_StrToSearch" length + StrLen $8 $1 + + ; Now set up basic variables + + ${If} $2 == < + IntOp $R1 $7 - $8 + StrCpy $R2 $7 + ${Else} + StrCpy $R1 0 + StrCpy $R2 $8 + ${EndIf} + + StrCpy $9 0 ; First loop + + ;Let's begin the search + + ${Do} + ; Step 1: If the starting or ending numbers are negative + ; or more than AdvStrStr_StringLen, we return + ; error + + ${If} $R1 < 0 + StrCpy $R1 `` + StrCpy $R2 `` + StrCpy $R3 `` + SetErrors ;AdvStrStr_StrToSearch not found + Goto AdvStrStr_End + ${ElseIf} $R2 > $7 + StrCpy $R1 `` + StrCpy $R2 `` + StrCpy $R3 `` + SetErrors ;AdvStrStr_StrToSearch not found + Goto AdvStrStr_End + ${EndIf} + + ; Step 2: Start the search depending on + ; AdvStrStr_DirectionOfSearch. Chop down not needed + ; characters. + + ${If} $R1 <> 0 + StrCpy $R3 $0 `` $R1 + ${EndIf} + + ${If} $R2 <> $7 + ${If} $R1 = 0 + StrCpy $R3 $0 $8 + ${Else} + StrCpy $R3 $R3 $8 + ${EndIf} + ${EndIf} + + ; Step 3: Make sure that's the string we want + + ; Case-Sensitive Support <- Use "AdvStrStr_Temp" + ; variable because it won't be used anymore + + ${If} $6 == 1 + System::Call `kernel32::lstrcmpA(ts, ts) i.s` `$R3` `$1` + Pop $R3 + ${If} $R3 = 0 + StrCpy $R3 1 ; Continue + ${Else} + StrCpy $R3 0 ; Break + ${EndIf} + ${Else} + ${If} $R3 == $1 + StrCpy $R3 1 ; Continue + ${Else} + StrCpy $R3 0 ; Break + ${EndIf} + ${EndIf} + + ; After the comparasion, confirm that it is the + ; value we want. + + ${If} $R3 = 1 + + ;We found it, return except if the user has set up to + ;search for another one: + ${If} $9 >= $5 + + ;Now, let's see if the user wants + ;AdvStrStr_StrToSearch to appear: + ${If} $4 == 0 + ;Return depends on AdvStrStr_DirectionOfReturn + ${If} $3 == < + ; RTL + StrCpy $R0 $0 $R1 + ${Else} + ; LTR + StrCpy $R0 $0 `` $R2 + ${EndIf} + ${Break} + ${Else} + ;Return depends on AdvStrStr_DirectionOfReturn + ${If} $3 == < + ; RTL + StrCpy $R0 $0 $R2 + ${Else} + ; LTR + StrCpy $R0 $0 `` $R1 + ${EndIf} + ${Break} + ${EndIf} + ${Else} + ;If the user wants to have more loops, let's do it so! + IntOp $9 $9 + 1 + + ${If} $2 == < + IntOp $R1 $R1 - 1 + IntOp $R2 $R2 - 1 + ${Else} + IntOp $R1 $R1 + 1 + IntOp $R2 $R2 + 1 + ${EndIf} + ${EndIf} + ${Else} + ; Step 4: We didn't find it, so do steps 1 thru 3 again + + ${If} $2 == < + IntOp $R1 $R1 - 1 + IntOp $R2 $R2 - 1 + ${Else} + IntOp $R1 $R1 + 1 + IntOp $R2 $R2 + 1 + ${EndIf} + ${EndIf} + ${Loop} + + AdvStrStr_End: + + ;Add 1 to AdvStrStr_EndCharPos to be supportable + ;by "StrCpy" + + IntOp $R2 $R2 - 1 + + ;Return output to user + + Exch $R0 + Exch + Pop $R1 + Exch + Pop $R2 + Exch + Pop $R3 + Exch + Pop $9 + Exch + Pop $8 + Exch + Pop $7 + Exch + Pop $6 + Exch + Pop $5 + Exch + Pop $4 + Exch + Pop $3 + Exch + Pop $2 + Exch + Pop $1 + Exch + Pop $0 + + FunctionEnd + + !macroend + + # Function StrTok + ############### + + !macro FUNCTION_STRING_StrTok + !insertmacro STRFUNC_FUNC `StrTok` `2004 Diego Pedroso - Based on functions by "bigmac666"` + /*After this point: + ------------------------------------------ + $0 = SkipEmptyParts (input) + $1 = ResultPart (input) + $2 = Separators (input) + $3 = String (input) + $4 = StrToSearchLen (temp) + $5 = StrLen (temp) + $6 = StartCharPos (temp) + $7 = TempStr (temp) + $8 = CurrentLoop + $9 = CurrentSepChar + $R0 = CurrentSepCharNum + */ + + ;Get input from user + Exch $0 + Exch + Exch $1 + Exch + Exch 2 + Exch $2 + Exch 2 + Exch 3 + Exch $3 + Exch 3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R0 + + ;Parameter defaults + ${IfThen} $2 == `` ${|} StrCpy $2 `|` ${|} + ${IfThen} $1 == `` ${|} StrCpy $1 `L` ${|} + ${IfThen} $0 == `` ${|} StrCpy $0 `0` ${|} + + ;Get "String" and "StrToSearch" length + StrLen $4 $2 + StrLen $5 $3 + ;Start "StartCharPos" and "ResultPart" counters + StrCpy $6 0 + StrCpy $8 -1 + + ;Loop until "ResultPart" is met, "StrToSearch" is found or + ;"String" reaches its end + ResultPartLoop: ;"CurrentLoop" Loop + + ;Increase "CurrentLoop" counter + IntOp $8 $8 + 1 + + StrSearchLoop: + ${Do} ;"String" Loop + ;Remove everything before and after the searched part ("TempStr") + StrCpy $7 $3 1 $6 + + ;Verify if it's the "String" end + ${If} $6 >= $5 + ;If "CurrentLoop" is what the user wants, remove the part + ;after "TempStr" and itself and get out of here + ${If} $8 == $1 + ${OrIf} $1 == `L` + StrCpy $3 $3 $6 + ${Else} ;If not, empty "String" and get out of here + StrCpy $3 `` + ${EndIf} + StrCpy $R0 `End` + ${ExitDo} + ${EndIf} + + ;Start "CurrentSepCharNum" counter (for "Separators" Loop) + StrCpy $R0 0 + + ${Do} ;"Separators" Loop + ;Use one "Separators" character at a time + ${If} $R0 <> 0 + StrCpy $9 $2 1 $R0 + ${Else} + StrCpy $9 $2 1 + ${EndIf} + + ;Go to the next "String" char if it's "Separators" end + ${IfThen} $R0 >= $4 ${|} ${ExitDo} ${|} + + ;Or, if "TempStr" equals "CurrentSepChar", then... + ${If} $7 == $9 + StrCpy $7 $3 $6 + + ;If "String" is empty because this result part doesn't + ;contain data, verify if "SkipEmptyParts" is activated, + ;so we don't return the output to user yet + + ${If} $7 == `` + ${AndIf} $0 = ${TRUE} + IntOp $6 $6 + 1 + StrCpy $3 $3 `` $6 + StrCpy $6 0 + Goto StrSearchLoop + ${ElseIf} $8 == $1 + StrCpy $3 $3 $6 + StrCpy $R0 "End" + ${ExitDo} + ${EndIf} ;If not, go to the next result part + IntOp $6 $6 + 1 + StrCpy $3 $3 `` $6 + StrCpy $6 0 + Goto ResultPartLoop + ${EndIf} + + ;Increase "CurrentSepCharNum" counter + IntOp $R0 $R0 + 1 + ${Loop} + ${IfThen} $R0 == "End" ${|} ${ExitDo} ${|} + + ;Increase "StartCharPos" counter + IntOp $6 $6 + 1 + ${Loop} + + /*After this point: + ------------------------------------------ + $3 = OutVar (output)*/ + + ;Return output to user + + Pop $R0 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $0 + Pop $1 + Pop $2 + Exch $3 + FunctionEnd + + !macroend + + # Function StrTrimNewLines + ######################## + + !macro FUNCTION_STRING_StrTrimNewLines + !insertmacro STRFUNC_FUNC `StrTrimNewLines` `2004 Diego Pedroso - Based on functions by Ximon Eighteen` + + /*After this point: + ------------------------------------------ + $R0 = String (input) + $R1 = TrimCounter (temp) + $R2 = Temp (temp)*/ + + ;Get input from user + Exch $R0 + Push $R1 + Push $R2 + + ;Initialize trim counter + StrCpy $R1 0 + + loop: + ;Subtract to get "String"'s last characters + IntOp $R1 $R1 - 1 + + ;Verify if they are either $\r or $\n + StrCpy $R2 $R0 1 $R1 + ${If} $R2 == `$\r` + ${OrIf} $R2 == `$\n` + Goto loop + ${EndIf} + + ;Trim characters (if needed) + IntOp $R1 $R1 + 1 + ${If} $R1 < 0 + StrCpy $R0 $R0 $R1 + ${EndIf} + + /*After this point: + ------------------------------------------ + $R0 = OutVar (output)*/ + + ;Return output to user + Pop $R2 + Pop $R1 + Exch $R0 + FunctionEnd + + !macroend + + ;Function Calls for Install and Uninstall + + !macro FUNCTION_STRING_StrCase_Call ResultVar String Type + !verbose push + !verbose 4 + !echo `$ {StrCase} "${ResultVar}" "${String}" "${Type}"` + !verbose pop + + Push `${String}` + Push `${Type}` + Call StrCase + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrCase_Call ResultVar String Type + !verbose push + !verbose 4 + !echo `$ {UnStrCase} "${ResultVar}" "${String}" "${Type}"` + !verbose pop + + Push `${String}` + Push `${Type}` + Call un.StrCase + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrClb_Call ResultVar String Action + !verbose push + !verbose 4 + !echo `$ {StrClb} "${ResultVar}" "${String}" "${Action}"` + !verbose pop + + Push `${String}` + Push `${Action}` + Call StrClb + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrClb_Call ResultVar String Action + !verbose push + !verbose 4 + !echo `$ {UnStrClb} "${ResultVar}" "${String}" "${Action}"` + !verbose pop + + Push `${String}` + Push `${Action}` + Call un.StrClb + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrIOToNSIS_Call ResultVar String + !verbose push + !verbose 4 + !echo `$ {StrIOToNSIS} "${ResultVar}" "${String}"` + !verbose pop + + Push `${String}` + Call StrIOToNSIS + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrIOToNSIS_Call ResultVar String + !verbose push + !verbose 4 + !echo `$ {UnStrIOToNSIS} "${ResultVar}" "${String}"` + !verbose pop + + Push `${String}` + Call un.StrIOToNSIS + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrLoc_Call ResultVar String StrToSearchFor OffsetDirection + !verbose push + !verbose 4 + !echo `$ {StrLoc} "${ResultVar}" "${String}" "${StrToSearchFor}" "${OffsetDirection}"` + !verbose pop + + Push `${String}` + Push `${StrToSearchFor}` + Push `${OffsetDirection}` + Call StrLoc + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrLoc_Call ResultVar String StrToSearchFor OffsetDirection + !verbose push + !verbose 4 + !echo `$ {UnStrLoc} "${ResultVar}" "${String}" "${StrToSearchFor}" "${OffsetDirection}"` + !verbose pop + + Push `${String}` + Push `${StrToSearchFor}` + Push `${OffsetDirection}` + Call un.StrLoc + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrNSISToIO_Call ResultVar String + !verbose push + !verbose 4 + !echo `$ {StrNSISToIO} "${ResultVar}" "${String}"` + !verbose pop + + Push `${String}` + Call StrNSISToIO + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrNSISToIO_Call ResultVar String + !verbose push + !verbose 4 + !echo `$ {UnStrNSISToIO} "${ResultVar}" "${String}"` + !verbose pop + + Push `${String}` + Call un.StrNSISToIO + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrRep_Call ResultVar String StringToReplace ReplacementString + !verbose push + !verbose 4 + !echo `$ {StrRep} "${ResultVar}" "${String}" "${StringToReplace}" "${ReplacementString}"` + !verbose pop + + Push `${String}` + Push `${StringToReplace}` + Push `${ReplacementString}` + Call StrRep + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrRep_Call ResultVar String StringToReplace ReplacementString + !verbose push + !verbose 4 + !echo `$ {UnStrRep} "${ResultVar}" "${String}" "${StringToReplace}" "${ReplacementString}"` + !verbose pop + + Push `${String}` + Push `${StringToReplace}` + Push `${ReplacementString}` + Call un.StrRep + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrSort_Call ResultVar String CenterStr LeftStr RightStr IncludeCenterStr IncludeLeftStr IncludeRightStr + !verbose push + !verbose 4 + !echo `$ {StrSort} "${ResultVar}" "${String}" "${CenterStr}" "${LeftStr}" "${RightStr}" "${IncludeCenterStr}" "${IncludeLeftStr}" "${IncludeRightStr}"` + !verbose pop + + Push `${String}` + Push `${CenterStr}` + Push `${LeftStr}` + Push `${RightStr}` + Push `${IncludeCenterStr}` + Push `${IncludeLeftStr}` + Push `${IncludeRightStr}` + Call StrSort + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrSort_Call ResultVar String CenterStr LeftStr RightStr IncludeCenterStr IncludeLeftStr IncludeRightStr + !verbose push + !verbose 4 + !echo `$ {UnStrSort} "${ResultVar}" "${String}" "${CenterStr}" "${LeftStr}" "${RightStr}" "${IncludeCenterStr}" "${IncludeLeftStr}" "${IncludeRightStr}"` + !verbose pop + + Push `${String}` + Push `${CenterStr}` + Push `${LeftStr}` + Push `${RightStr}` + Push `${IncludeCenterStr}` + Push `${IncludeLeftStr}` + Push `${IncludeRightStr}` + Call un.StrSort + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrStr_Call ResultVar String StrToSearchFor + !verbose push + !verbose 4 + !echo `$ {StrStr} "${ResultVar}" "${String}" "${StrToSearchFor}"` + !verbose pop + + Push `${String}` + Push `${StrToSearchFor}` + Call StrStr + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrStr_Call ResultVar String StrToSearchFor + !verbose push + !verbose 4 + !echo `$ {UnStrStr} "${ResultVar}" "${String}" "${StrToSearchFor}"` + !verbose pop + + Push `${String}` + Push `${StrToSearchFor}` + Call un.StrStr + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrStrAdv_Call ResultVar String StrToSearchFor SearchDirection ResultStrDirection DisplayStrToSearch Loops CaseSensitive + !verbose push + !verbose 4 + !echo `$ {StrStrAdv} "${ResultVar}" "${String}" "${StrToSearchFor}" "${SearchDirection}" "${ResultStrDirection}" "${DisplayStrToSearch}" "${Loops}" "${CaseSensitive}"` + !verbose pop + + Push `${String}` + Push `${StrToSearchFor}` + Push `${SearchDirection}` + Push `${ResultStrDirection}` + Push `${DisplayStrToSearch}` + Push `${Loops}` + Push `${CaseSensitive}` + Call StrStrAdv + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrStrAdv_Call ResultVar String StrToSearchFor SearchDirection ResultStrDirection DisplayStrToSearch Loops CaseSensitive + !verbose push + !verbose 4 + !echo `$ {UnStrStrAdv} "${ResultVar}" "${String}" "${StrToSearchFor}" "${SearchDirection}" "${ResultStrDirection}" "${DisplayStrToSearch}" "${Loops}" "${CaseSensitive}"` + !verbose pop + + Push `${String}` + Push `${StrToSearchFor}` + Push `${SearchDirection}` + Push `${ResultStrDirection}` + Push `${DisplayStrToSearch}` + Push `${Loops}` + Push `${CaseSensitive}` + Call un.StrStrAdv + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrTok_Call ResultVar String Separators ResultPart SkipEmptyParts + !verbose push + !verbose 4 + !echo `$ {StrTok} "${ResultVar}" "${String}" "${Separators}" "${ResultPart}" "${SkipEmptyParts}"` + !verbose pop + + Push `${String}` + Push `${Separators}` + Push `${ResultPart}` + Push `${SkipEmptyParts}` + Call StrTok + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrTok_Call ResultVar String Separators ResultPart SkipEmptyParts + !verbose push + !verbose 4 + !echo `$ {UnStrTok} "${ResultVar}" "${String}" "${Separators}" "${ResultPart}" "${SkipEmptyParts}"` + !verbose pop + + Push `${String}` + Push `${Separators}` + Push `${ResultPart}` + Push `${SkipEmptyParts}` + Call un.StrTok + Pop `${ResultVar}` + !macroend + + !macro FUNCTION_STRING_StrTrimNewLines_Call ResultVar String + !verbose push + !verbose 4 + !echo `$ {StrTrimNewLines} "${ResultVar}" "${String}"` + !verbose pop + + Push `${String}` + Call StrTrimNewLines + Pop `${ResultVar}` + !macroend + !macro FUNCTION_STRING_UnStrTrimNewLines_Call ResultVar String + !verbose push + !verbose 4 + !echo `$ {UnStrTrimNewLines} "${ResultVar}" "${String}"` + !verbose pop + + Push `${String}` + Call un.StrTrimNewLines + Pop `${ResultVar}` + !macroend + +!endif +!verbose 3 +!define STRFUNC_VERBOSITY ${_STRFUNC_VERBOSITY} +!undef _STRFUNC_VERBOSITY +!verbose pop diff --git a/installer/NSIS/Include/TextFunc.nsh b/installer/NSIS/Include/TextFunc.nsh new file mode 100644 index 0000000..2a9c459 --- /dev/null +++ b/installer/NSIS/Include/TextFunc.nsh @@ -0,0 +1,1214 @@ +/* +_____________________________________________________________________________ + + Text Functions Header v2.4 +_____________________________________________________________________________ + + 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + + See documentation for more information about the following functions. + + Usage in script: + 1. !include "TextFunc.nsh" + 2. [Section|Function] + ${TextFunction} "File" "..." $var + [SectionEnd|FunctionEnd] + + + TextFunction=[LineFind|LineRead|FileReadFromEnd|LineSum|FileJoin| + TextCompare|TextCompareS|ConfigRead|ConfigReadS| + ConfigWrite|ConfigWriteS|FileRecode|TrimNewLines] + +_____________________________________________________________________________ + + Thanks to: +_____________________________________________________________________________ + +LineRead + Afrow UK (Based on his idea of Function "ReadFileLine") +LineSum + Afrow UK (Based on his idea of Function "LineCount") +FileJoin + Afrow UK (Based on his idea of Function "JoinFiles") +ConfigRead + vbgunz (His idea) +ConfigWrite + vbgunz (His idea) +TrimNewLines + sunjammer (Based on his Function "TrimNewLines") +*/ + + +;_____________________________________________________________________________ +; +; Macros +;_____________________________________________________________________________ +; +; Change log window verbosity (default: 3=no script) +; +; Example: +; !include "TextFunc.nsh" +; !insertmacro LineFind +; ${TEXTFUNC_VERBOSE} 4 # all verbosity +; !insertmacro LineSum +; ${TEXTFUNC_VERBOSE} 3 # no script + +!ifndef TEXTFUNC_INCLUDED +!define TEXTFUNC_INCLUDED + +!include FileFunc.nsh +!include Util.nsh + +!verbose push +!verbose 3 +!ifndef _TEXTFUNC_VERBOSE + !define _TEXTFUNC_VERBOSE 3 +!endif +!verbose ${_TEXTFUNC_VERBOSE} +!define TEXTFUNC_VERBOSE `!insertmacro TEXTFUNC_VERBOSE` +!verbose pop + +!macro TEXTFUNC_VERBOSE _VERBOSE + !verbose push + !verbose 3 + !undef _TEXTFUNC_VERBOSE + !define _TEXTFUNC_VERBOSE ${_VERBOSE} + !verbose pop +!macroend + +!macro LineFindCall _INPUT _OUTPUT _RANGE _FUNC + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push $0 + Push `${_INPUT}` + Push `${_OUTPUT}` + Push `${_RANGE}` + GetFunctionAddress $0 `${_FUNC}` + Push `$0` + ${CallArtificialFunction} LineFind_ + Pop $0 + !verbose pop +!macroend + +!macro LineReadCall _FILE _NUMBER _RESULT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + Push `${_NUMBER}` + ${CallArtificialFunction} LineRead_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro FileReadFromEndCall _FILE _FUNC + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push $0 + Push `${_FILE}` + GetFunctionAddress $0 `${_FUNC}` + Push `$0` + ${CallArtificialFunction} FileReadFromEnd_ + Pop $0 + !verbose pop +!macroend + +!macro LineSumCall _FILE _RESULT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + ${CallArtificialFunction} LineSum_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro FileJoinCall _FILE1 _FILE2 _FILE3 + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE1}` + Push `${_FILE2}` + Push `${_FILE3}` + ${CallArtificialFunction} FileJoin_ + !verbose pop +!macroend + +!macro TextCompareCall _FILE1 _FILE2 _OPTION _FUNC + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push $0 + Push `${_FILE1}` + Push `${_FILE2}` + Push `${_OPTION}` + GetFunctionAddress $0 `${_FUNC}` + Push `$0` + ${CallArtificialFunction} TextCompare_ + Pop $0 + !verbose pop +!macroend + +!macro TextCompareSCall _FILE1 _FILE2 _OPTION _FUNC + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push $0 + Push `${_FILE1}` + Push `${_FILE2}` + Push `${_OPTION}` + GetFunctionAddress $0 `${_FUNC}` + Push `$0` + ${CallArtificialFunction} TextCompareS_ + Pop $0 + !verbose pop +!macroend + +!macro ConfigReadCall _FILE _ENTRY _RESULT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + Push `${_ENTRY}` + ${CallArtificialFunction} ConfigRead_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro ConfigReadSCall _FILE _ENTRY _RESULT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + Push `${_ENTRY}` + ${CallArtificialFunction} ConfigReadS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro ConfigWriteCall _FILE _ENTRY _VALUE _RESULT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + Push `${_ENTRY}` + Push `${_VALUE}` + ${CallArtificialFunction} ConfigWrite_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro ConfigWriteSCall _FILE _ENTRY _VALUE _RESULT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + Push `${_ENTRY}` + Push `${_VALUE}` + ${CallArtificialFunction} ConfigWriteS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro FileRecodeCall _FILE _FORMAT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + Push `${_FORMAT}` + ${CallArtificialFunction} FileRecode_ + !verbose pop +!macroend + +!macro TrimNewLinesCall _FILE _RESULT + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + Push `${_FILE}` + ${CallArtificialFunction} TrimNewLines_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro _TextFunc_TempFileForFile _FILE _RESULT + # XXX replace with GetParent + Push `${_FILE}` + Exch $0 + Push $1 + Push $2 + + StrCpy $2 $0 1 -1 + StrCmp $2 '\' 0 +3 + StrCpy $0 $0 -1 + goto -3 + + StrCpy $1 0 + IntOp $1 $1 - 1 + StrCpy $2 $0 1 $1 + StrCmp $2 '\' +2 + StrCmp $2 '' 0 -3 + StrCpy $0 $0 $1 + + Pop $2 + Pop $1 + Exch $0 + Pop ${_RESULT} + # XXX + StrCmp ${_RESULT} "" 0 +2 + StrCpy ${_RESULT} $EXEDIR + GetTempFileName ${_RESULT} ${_RESULT} + StrCmp ${_RESULT} "" 0 +2 + GetTempFileName ${_RESULT} + ClearErrors +!macroend + +!define LineFind `!insertmacro LineFindCall` +!define un.LineFind `!insertmacro LineFindCall` + +!macro LineFind +!macroend + +!macro un.LineFind +!macroend + +!macro LineFind_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + Exch $3 + Exch + Exch $2 + Exch + Exch 2 + Exch $1 + Exch 2 + Exch 3 + Exch $0 + Exch 3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R4 + Push $R5 + Push $R6 + Push $R7 + Push $R8 + Push $R9 + ClearErrors + + IfFileExists '$0' 0 TextFunc_LineFind_error + StrCmp $1 '/NUL' TextFunc_LineFind_begin + StrCpy $8 0 + IntOp $8 $8 - 1 + StrCpy $9 $1 1 $8 + StrCmp $9 \ +2 + StrCmp $9 '' +3 -3 + StrCpy $9 $1 $8 + IfFileExists '$9\*.*' 0 TextFunc_LineFind_error + + TextFunc_LineFind_begin: + StrCpy $4 1 + StrCpy $5 -1 + StrCpy $6 0 + StrCpy $7 0 + StrCpy $R4 '' + StrCpy $R6 '' + StrCpy $R7 '' + StrCpy $R8 0 + + StrCpy $8 $2 1 + StrCmp $8 '{' 0 TextFunc_LineFind_delspaces + StrCpy $2 $2 '' 1 + StrCpy $8 $2 1 -1 + StrCmp $8 '}' 0 TextFunc_LineFind_delspaces + StrCpy $2 $2 -1 + StrCpy $R6 TextFunc_LineFind_cut + + TextFunc_LineFind_delspaces: + StrCpy $8 $2 1 + StrCmp $8 ' ' 0 +3 + StrCpy $2 $2 '' 1 + goto -3 + StrCmp $2$7 '0' TextFunc_LineFind_file + StrCpy $4 '' + StrCpy $5 '' + StrCmp $2 '' TextFunc_LineFind_writechk + + TextFunc_LineFind_range: + StrCpy $8 0 + StrCpy $9 $2 1 $8 + StrCmp $9 '' +5 + StrCmp $9 ' ' +4 + StrCmp $9 ':' +3 + IntOp $8 $8 + 1 + goto -5 + StrCpy $5 $2 $8 + IntOp $5 $5 + 0 + IntOp $8 $8 + 1 + StrCpy $2 $2 '' $8 + StrCmp $4 '' 0 +2 + StrCpy $4 $5 + StrCmp $9 ':' TextFunc_LineFind_range + + IntCmp $4 0 0 +2 + IntCmp $5 -1 TextFunc_LineFind_goto 0 TextFunc_LineFind_growthcmp + StrCmp $R7 '' 0 TextFunc_LineFind_minus2plus + StrCpy $R7 0 + FileOpen $8 $0 r + FileRead $8 $9 + IfErrors +3 + IntOp $R7 $R7 + 1 + Goto -3 + FileClose $8 + + TextFunc_LineFind_minus2plus: + IntCmp $4 0 +5 0 +5 + IntOp $4 $R7 + $4 + IntOp $4 $4 + 1 + IntCmp $4 0 +2 0 +2 + StrCpy $4 0 + IntCmp $5 -1 TextFunc_LineFind_goto 0 TextFunc_LineFind_growthcmp + IntOp $5 $R7 + $5 + IntOp $5 $5 + 1 + TextFunc_LineFind_growthcmp: + IntCmp $4 $5 TextFunc_LineFind_goto TextFunc_LineFind_goto + StrCpy $5 $4 + TextFunc_LineFind_goto: + goto $7 + + TextFunc_LineFind_file: + StrCmp $1 '/NUL' TextFunc_LineFind_notemp + !insertmacro _TextFunc_TempFileForFile $1 $R4 + Push $R4 + FileOpen $R4 $R4 w + TextFunc_LineFind_notemp: + FileOpen $R5 $0 r + IfErrors TextFunc_LineFind_preerror + + TextFunc_LineFind_loop: + IntOp $R8 $R8 + 1 + FileRead $R5 $R9 + IfErrors TextFunc_LineFind_handleclose + + TextFunc_LineFind_cmp: + StrCmp $2$4$5 '' TextFunc_LineFind_writechk + IntCmp $4 $R8 TextFunc_LineFind_call 0 TextFunc_LineFind_writechk + StrCmp $5 -1 TextFunc_LineFind_call + IntCmp $5 $R8 TextFunc_LineFind_call 0 TextFunc_LineFind_call + + GetLabelAddress $7 TextFunc_LineFind_cmp + goto TextFunc_LineFind_delspaces + + TextFunc_LineFind_call: + StrCpy $7 $R9 + Push $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $R4 + Push $R5 + Push $R6 + Push $R7 + Push $R8 + StrCpy $R6 '$4:$5' + StrCmp $R7 '' +3 + IntOp $R7 $R8 - $R7 + IntOp $R7 $R7 - 1 + Call $3 + Pop $9 + Pop $R8 + Pop $R7 + Pop $R6 + Pop $R5 + Pop $R4 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + IfErrors TextFunc_LineFind_preerror + StrCmp $9 'StopLineFind' 0 +3 + IntOp $6 $6 + 1 + goto TextFunc_LineFind_handleclose + StrCmp $1 '/NUL' TextFunc_LineFind_loop + StrCmp $9 'SkipWrite' 0 +3 + IntOp $6 $6 + 1 + goto TextFunc_LineFind_loop + StrCmp $7 $R9 TextFunc_LineFind_write + IntOp $6 $6 + 1 + goto TextFunc_LineFind_write + + TextFunc_LineFind_writechk: + StrCmp $1 '/NUL' TextFunc_LineFind_loop + StrCmp $R6 TextFunc_LineFind_cut 0 TextFunc_LineFind_write + IntOp $6 $6 + 1 + goto TextFunc_LineFind_loop + + TextFunc_LineFind_write: + FileWrite $R4 $R9 + goto TextFunc_LineFind_loop + + TextFunc_LineFind_preerror: + SetErrors + + TextFunc_LineFind_handleclose: + StrCmp $1 '/NUL' +3 + FileClose $R4 + Pop $R4 + FileClose $R5 + IfErrors TextFunc_LineFind_error + + StrCmp $1 '/NUL' TextFunc_LineFind_end + StrCmp $1 '' 0 +2 + StrCpy $1 $0 + StrCmp $6 0 0 TextFunc_LineFind_rename + FileOpen $7 $0 r + FileSeek $7 0 END $8 + FileClose $7 + FileOpen $7 $R4 r + FileSeek $7 0 END $9 + FileClose $7 + IntCmp $8 $9 0 TextFunc_LineFind_rename + Delete $R4 + StrCmp $1 $0 TextFunc_LineFind_end + CopyFiles /SILENT $0 $1 + goto TextFunc_LineFind_end + + TextFunc_LineFind_rename: + Delete '$EXEDIR\$1' + Rename $R4 '$EXEDIR\$1' + IfErrors 0 TextFunc_LineFind_end + Delete $1 + Rename $R4 $1 + IfErrors 0 TextFunc_LineFind_end + + TextFunc_LineFind_error: + SetErrors + + TextFunc_LineFind_end: + Pop $R9 + Pop $R8 + Pop $R7 + Pop $R6 + Pop $R5 + Pop $R4 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + !verbose pop +!macroend + +!define LineRead `!insertmacro LineReadCall` +!define un.LineRead `!insertmacro LineReadCall` + +!macro LineRead +!macroend + +!macro un.LineRead +!macroend + +!macro LineRead_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + ClearErrors + + IfFileExists $0 0 TextFunc_LineRead_error + IntOp $1 $1 + 0 + IntCmp $1 0 TextFunc_LineRead_error 0 TextFunc_LineRead_plus + StrCpy $4 0 + FileOpen $2 $0 r + IfErrors TextFunc_LineRead_error + FileRead $2 $3 + IfErrors +3 + IntOp $4 $4 + 1 + Goto -3 + FileClose $2 + IntOp $1 $4 + $1 + IntOp $1 $1 + 1 + IntCmp $1 0 TextFunc_LineRead_error TextFunc_LineRead_error + + TextFunc_LineRead_plus: + FileOpen $2 $0 r + IfErrors TextFunc_LineRead_error + StrCpy $3 0 + IntOp $3 $3 + 1 + FileRead $2 $0 + IfErrors +4 + StrCmp $3 $1 0 -3 + FileClose $2 + goto TextFunc_LineRead_end + FileClose $2 + + TextFunc_LineRead_error: + SetErrors + StrCpy $0 '' + + TextFunc_LineRead_end: + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define FileReadFromEnd `!insertmacro FileReadFromEndCall` +!define un.FileReadFromEnd `!insertmacro FileReadFromEndCall` + +!macro FileReadFromEnd +!macroend + +!macro un.FileReadFromEnd +!macroend + +!macro FileReadFromEnd_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $7 + Push $8 + Push $9 + ClearErrors + + StrCpy $7 -1 + StrCpy $8 0 + IfFileExists $0 0 TextFunc_FileReadFromEnd_error + FileOpen $0 $0 r + IfErrors TextFunc_FileReadFromEnd_error + FileRead $0 $9 + IfErrors +4 + Push $9 + IntOp $8 $8 + 1 + goto -4 + FileClose $0 + + TextFunc_FileReadFromEnd_nextline: + StrCmp $8 0 TextFunc_FileReadFromEnd_end + Pop $9 + Push $1 + Push $7 + Push $8 + Call $1 + Pop $0 + Pop $8 + Pop $7 + Pop $1 + IntOp $7 $7 - 1 + IntOp $8 $8 - 1 + IfErrors TextFunc_FileReadFromEnd_error + StrCmp $0 'StopFileReadFromEnd' TextFunc_FileReadFromEnd_clearstack TextFunc_FileReadFromEnd_nextline + + TextFunc_FileReadFromEnd_error: + SetErrors + + TextFunc_FileReadFromEnd_clearstack: + StrCmp $8 0 TextFunc_FileReadFromEnd_end + Pop $9 + IntOp $8 $8 - 1 + goto TextFunc_FileReadFromEnd_clearstack + + TextFunc_FileReadFromEnd_end: + Pop $9 + Pop $8 + Pop $7 + Pop $1 + Pop $0 + + !verbose pop +!macroend + +!define LineSum `!insertmacro LineSumCall` +!define un.LineSum `!insertmacro LineSumCall` + +!macro LineSum +!macroend + +!macro un.LineSum +!macroend + +!macro LineSum_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + ClearErrors + + IfFileExists $0 0 TextFunc_LineSum_error + StrCpy $2 0 + FileOpen $0 $0 r + IfErrors TextFunc_LineSum_error + FileRead $0 $1 + IfErrors +3 + IntOp $2 $2 + 1 + Goto -3 + FileClose $0 + StrCpy $0 $2 + goto TextFunc_LineSum_end + + TextFunc_LineSum_error: + SetErrors + StrCpy $0 '' + + TextFunc_LineSum_end: + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define FileJoin `!insertmacro FileJoinCall` +!define un.FileJoin `!insertmacro FileJoinCall` + +!macro FileJoin +!macroend + +!macro un.FileJoin +!macroend + +!macro FileJoin_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Push $3 + Push $4 + Push $5 + ClearErrors + + IfFileExists $0 0 TextFunc_FileJoin_error + IfFileExists $1 0 TextFunc_FileJoin_error + StrCpy $3 0 + IntOp $3 $3 - 1 + StrCpy $4 $2 1 $3 + StrCmp $4 \ +2 + StrCmp $4 '' +3 -3 + StrCpy $4 $2 $3 + IfFileExists '$4\*.*' 0 TextFunc_FileJoin_error + + StrCmp $2 $0 0 +2 + StrCpy $2 '' + StrCmp $2 '' 0 +3 + StrCpy $4 $0 + Goto TextFunc_FileJoin_notemp + !insertmacro _TextFunc_TempFileForFile $2 $4 + CopyFiles /SILENT $0 $4 + TextFunc_FileJoin_notemp: + FileOpen $3 $4 a + IfErrors TextFunc_FileJoin_error + FileSeek $3 -1 END + FileRead $3 $5 + StrCmp $5 '$\r' +3 + StrCmp $5 '$\n' +2 + FileWrite $3 '$\r$\n' + + ;FileWrite $3 '$\r$\n--Divider--$\r$\n' + + FileOpen $0 $1 r + IfErrors TextFunc_FileJoin_error + FileRead $0 $5 + IfErrors +3 + FileWrite $3 $5 + goto -3 + FileClose $0 + FileClose $3 + StrCmp $2 '' TextFunc_FileJoin_end + Delete '$EXEDIR\$2' + Rename $4 '$EXEDIR\$2' + IfErrors 0 TextFunc_FileJoin_end + Delete $2 + Rename $4 $2 + IfErrors 0 TextFunc_FileJoin_end + + TextFunc_FileJoin_error: + SetErrors + + TextFunc_FileJoin_end: + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + !verbose pop +!macroend + +!macro TextCompareBody _TEXTFUNC_S + Exch $3 + Exch + Exch $2 + Exch + Exch 2 + Exch $1 + Exch 2 + Exch 3 + Exch $0 + Exch 3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + ClearErrors + + IfFileExists $0 0 TextFunc_TextCompare${_TEXTFUNC_S}_error + IfFileExists $1 0 TextFunc_TextCompare${_TEXTFUNC_S}_error + StrCmp $2 'FastDiff' +5 + StrCmp $2 'FastEqual' +4 + StrCmp $2 'SlowDiff' +3 + StrCmp $2 'SlowEqual' +2 + goto TextFunc_TextCompare${_TEXTFUNC_S}_error + + FileOpen $4 $0 r + IfErrors TextFunc_TextCompare${_TEXTFUNC_S}_error + FileOpen $5 $1 r + IfErrors TextFunc_TextCompare${_TEXTFUNC_S}_error + SetDetailsPrint textonly + + StrCpy $6 0 + StrCpy $8 0 + + TextFunc_TextCompare${_TEXTFUNC_S}_nextline: + StrCmp${_TEXTFUNC_S} $4 '' TextFunc_TextCompare${_TEXTFUNC_S}_fast + IntOp $8 $8 + 1 + FileRead $4 $9 + IfErrors 0 +4 + FileClose $4 + StrCpy $4 '' + StrCmp${_TEXTFUNC_S} $5 '' TextFunc_TextCompare${_TEXTFUNC_S}_end + StrCmp $2 'FastDiff' TextFunc_TextCompare${_TEXTFUNC_S}_fast + StrCmp $2 'FastEqual' TextFunc_TextCompare${_TEXTFUNC_S}_fast TextFunc_TextCompare${_TEXTFUNC_S}_slow + + TextFunc_TextCompare${_TEXTFUNC_S}_fast: + StrCmp${_TEXTFUNC_S} $5 '' TextFunc_TextCompare${_TEXTFUNC_S}_call + IntOp $6 $6 + 1 + FileRead $5 $7 + IfErrors 0 +5 + FileClose $5 + StrCpy $5 '' + StrCmp${_TEXTFUNC_S} $4 '' TextFunc_TextCompare${_TEXTFUNC_S}_end + StrCmp $2 'FastDiff' TextFunc_TextCompare${_TEXTFUNC_S}_call TextFunc_TextCompare${_TEXTFUNC_S}_close + StrCmp $2 'FastDiff' 0 +2 + StrCmp${_TEXTFUNC_S} $7 $9 TextFunc_TextCompare${_TEXTFUNC_S}_nextline TextFunc_TextCompare${_TEXTFUNC_S}_call + StrCmp${_TEXTFUNC_S} $7 $9 TextFunc_TextCompare${_TEXTFUNC_S}_call TextFunc_TextCompare${_TEXTFUNC_S}_nextline + + TextFunc_TextCompare${_TEXTFUNC_S}_slow: + StrCmp${_TEXTFUNC_S} $4 '' TextFunc_TextCompare${_TEXTFUNC_S}_close + StrCpy $6 '' + DetailPrint '$8. $9' + FileSeek $5 0 + + TextFunc_TextCompare${_TEXTFUNC_S}_slownext: + FileRead $5 $7 + IfErrors 0 +2 + StrCmp $2 'SlowDiff' TextFunc_TextCompare${_TEXTFUNC_S}_call TextFunc_TextCompare${_TEXTFUNC_S}_nextline + StrCmp $2 'SlowDiff' 0 +2 + StrCmp${_TEXTFUNC_S} $7 $9 TextFunc_TextCompare${_TEXTFUNC_S}_nextline TextFunc_TextCompare${_TEXTFUNC_S}_slownext + IntOp $6 $6 + 1 + StrCmp${_TEXTFUNC_S} $7 $9 0 TextFunc_TextCompare${_TEXTFUNC_S}_slownext + + TextFunc_TextCompare${_TEXTFUNC_S}_call: + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Call $3 + Pop $0 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + StrCmp $0 'StopTextCompare' 0 TextFunc_TextCompare${_TEXTFUNC_S}_nextline + + TextFunc_TextCompare${_TEXTFUNC_S}_close: + FileClose $4 + FileClose $5 + goto TextFunc_TextCompare${_TEXTFUNC_S}_end + + TextFunc_TextCompare${_TEXTFUNC_S}_error: + SetErrors + + TextFunc_TextCompare${_TEXTFUNC_S}_end: + SetDetailsPrint both + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +!macroend + +!define TextCompare `!insertmacro TextCompareCall` +!define un.TextCompare `!insertmacro TextCompareCall` + +!macro TextCompare +!macroend + +!macro un.TextCompare +!macroend + +!macro TextCompare_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + !insertmacro TextCompareBody '' + + !verbose pop +!macroend + +!define TextCompareS `!insertmacro TextCompareSCall` +!define un.TextCompareS `!insertmacro TextCompareSCall` + +!macro TextCompareS +!macroend + +!macro un.TextCompareS +!macroend + +!macro TextCompareS_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + !insertmacro TextCompareBody 'S' + + !verbose pop +!macroend + +!macro ConfigReadBody _TEXTFUNC_S + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + ClearErrors + + FileOpen $2 $0 r + IfErrors TextFunc_ConfigRead${_TEXTFUNC_S}_error + StrLen $0 $1 + StrCmp${_TEXTFUNC_S} $0 0 TextFunc_ConfigRead${_TEXTFUNC_S}_error + + TextFunc_ConfigRead${_TEXTFUNC_S}_readnext: + FileRead $2 $3 + IfErrors TextFunc_ConfigRead${_TEXTFUNC_S}_error + StrCpy $4 $3 $0 + StrCmp${_TEXTFUNC_S} $4 $1 0 TextFunc_ConfigRead${_TEXTFUNC_S}_readnext + StrCpy $0 $3 '' $0 + StrCpy $4 $0 1 -1 + StrCmp${_TEXTFUNC_S} $4 '$\r' +2 + StrCmp${_TEXTFUNC_S} $4 '$\n' 0 TextFunc_ConfigRead${_TEXTFUNC_S}_close + StrCpy $0 $0 -1 + goto -4 + + TextFunc_ConfigRead${_TEXTFUNC_S}_error: + SetErrors + StrCpy $0 '' + + TextFunc_ConfigRead${_TEXTFUNC_S}_close: + FileClose $2 + + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 +!macroend + +!define ConfigRead `!insertmacro ConfigReadCall` +!define un.ConfigRead `!insertmacro ConfigReadCall` + +!macro ConfigRead +!macroend + +!macro un.ConfigRead +!macroend + +!macro ConfigRead_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + !insertmacro ConfigReadBody '' + + !verbose pop +!macroend + +!define ConfigReadS `!insertmacro ConfigReadSCall` +!define un.ConfigReadS `!insertmacro ConfigReadSCall` + +!macro ConfigReadS +!macroend + +!macro un.ConfigReadS +!macroend + +!macro ConfigReadS_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + !insertmacro ConfigReadBody 'S' + + !verbose pop +!macroend + +!macro ConfigWriteBody _TEXTFUNC_S + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Push $3 + Push $4 + Push $5 + Push $6 + ClearErrors + + IfFileExists $0 0 TextFunc_ConfigWrite${_TEXTFUNC_S}_error + FileOpen $3 $0 a + IfErrors TextFunc_ConfigWrite${_TEXTFUNC_S}_error + + StrLen $0 $1 + StrCmp${_TEXTFUNC_S} $0 0 0 TextFunc_ConfigWrite${_TEXTFUNC_S}_readnext + StrCpy $0 '' + goto TextFunc_ConfigWrite${_TEXTFUNC_S}_close + + TextFunc_ConfigWrite${_TEXTFUNC_S}_readnext: + FileRead $3 $4 + IfErrors TextFunc_ConfigWrite${_TEXTFUNC_S}_add + StrCpy $5 $4 $0 + StrCmp${_TEXTFUNC_S} $5 $1 0 TextFunc_ConfigWrite${_TEXTFUNC_S}_readnext + + StrCpy $5 0 + IntOp $5 $5 - 1 + StrCpy $6 $4 1 $5 + StrCmp${_TEXTFUNC_S} $6 '$\r' -2 + StrCmp${_TEXTFUNC_S} $6 '$\n' -3 + StrCpy $6 $4 + StrCmp${_TEXTFUNC_S} $5 -1 +3 + IntOp $5 $5 + 1 + StrCpy $6 $4 $5 + + StrCmp${_TEXTFUNC_S} $2 '' TextFunc_ConfigWrite${_TEXTFUNC_S}_change + StrCmp${_TEXTFUNC_S} $6 '$1$2' 0 TextFunc_ConfigWrite${_TEXTFUNC_S}_change + StrCpy $0 SAME + goto TextFunc_ConfigWrite${_TEXTFUNC_S}_close + + TextFunc_ConfigWrite${_TEXTFUNC_S}_change: + FileSeek $3 0 CUR $5 + StrLen $4 $4 + IntOp $4 $5 - $4 + FileSeek $3 0 END $6 + IntOp $6 $6 - $5 + + System::Alloc $6 + Pop $0 + FileSeek $3 $5 SET + System::Call 'kernel32::ReadFile(i r3, i r0, i $6, t.,)' + FileSeek $3 $4 SET + StrCmp${_TEXTFUNC_S} $2 '' +2 + FileWrite $3 '$1$2$\r$\n' + System::Call 'kernel32::WriteFile(i r3, i r0, i $6, t.,)' + System::Call 'kernel32::SetEndOfFile(i r3)' + System::Free $0 + StrCmp${_TEXTFUNC_S} $2 '' +3 + StrCpy $0 CHANGED + goto TextFunc_ConfigWrite${_TEXTFUNC_S}_close + StrCpy $0 DELETED + goto TextFunc_ConfigWrite${_TEXTFUNC_S}_close + + TextFunc_ConfigWrite${_TEXTFUNC_S}_add: + StrCmp${_TEXTFUNC_S} $2 '' 0 +3 + StrCpy $0 SAME + goto TextFunc_ConfigWrite${_TEXTFUNC_S}_close + FileSeek $3 -1 END + FileRead $3 $4 + IfErrors +4 + StrCmp${_TEXTFUNC_S} $4 '$\r' +3 + StrCmp${_TEXTFUNC_S} $4 '$\n' +2 + FileWrite $3 '$\r$\n' + FileWrite $3 '$1$2$\r$\n' + StrCpy $0 ADDED + + TextFunc_ConfigWrite${_TEXTFUNC_S}_close: + FileClose $3 + goto TextFunc_ConfigWrite${_TEXTFUNC_S}_end + + TextFunc_ConfigWrite${_TEXTFUNC_S}_error: + SetErrors + StrCpy $0 '' + + TextFunc_ConfigWrite${_TEXTFUNC_S}_end: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 +!macroend + +!define ConfigWrite `!insertmacro ConfigWriteCall` +!define un.ConfigWrite `!insertmacro ConfigWriteCall` + +!macro ConfigWrite +!macroend + +!macro un.ConfigWrite +!macroend + +!macro ConfigWrite_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + !insertmacro ConfigWriteBody '' + + !verbose pop +!macroend + +!define ConfigWriteS `!insertmacro ConfigWriteSCall` +!define un.ConfigWriteS `!insertmacro ConfigWriteSCall` + +!macro ConfigWriteS +!macroend + +!macro un.ConfigWriteS +!macroend + +!macro ConfigWriteS_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + !insertmacro ConfigWriteBody 'S' + + !verbose pop +!macroend + +!define FileRecode `!insertmacro FileRecodeCall` +!define un.FileRecode `!insertmacro FileRecodeCall` + +!macro FileRecode +!macroend + +!macro un.FileRecode +!macroend + +!macro FileRecode_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + + IfFileExists $0 0 TextFunc_FileRecode_error + StrCmp $1 OemToChar +2 + StrCmp $1 CharToOem 0 TextFunc_FileRecode_error + + FileOpen $2 $0 a + FileSeek $2 0 END $3 + System::Alloc $3 + Pop $4 + FileSeek $2 0 SET + System::Call 'kernel32::ReadFile(i r2, i r4, i $3, t.,)' + System::Call 'user32::$1Buff(i r4, i r4, i $3)' + FileSeek $2 0 SET + System::Call 'kernel32::WriteFile(i r2, i r4, i $3, t.,)' + System::Free $4 + FileClose $2 + goto TextFunc_FileRecode_end + + TextFunc_FileRecode_error: + SetErrors + + TextFunc_FileRecode_end: + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + !verbose pop +!macroend + +!define TrimNewLines `!insertmacro TrimNewLinesCall` +!define un.TrimNewLines `!insertmacro TrimNewLinesCall` + +!macro TrimNewLines +!macroend + +!macro un.TrimNewLines +!macroend + +!macro TrimNewLines_ + !verbose push + !verbose ${_TEXTFUNC_VERBOSE} + + Exch $0 + Push $1 + Push $2 + + StrCpy $1 0 + IntOp $1 $1 - 1 + StrCpy $2 $0 1 $1 + StrCmp $2 '$\r' -2 + StrCmp $2 '$\n' -3 + StrCmp $1 -1 +3 + IntOp $1 $1 + 1 + StrCpy $0 $0 $1 + + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!endif diff --git a/installer/NSIS/Include/UpgradeDLL.nsh b/installer/NSIS/Include/UpgradeDLL.nsh new file mode 100644 index 0000000..ba10674 --- /dev/null +++ b/installer/NSIS/Include/UpgradeDLL.nsh @@ -0,0 +1,203 @@ +/* + +NOTE: +----- +This macro is provided for backwards compatibility with NSIS 2.0 scripts. +It's recommended you update your scripts to use the new Library.nsh macros. + + +Macro - Upgrade DLL File +Written by Joost Verburg +------------------------ + +Parameters: +LOCALFILE Location of the new DLL file (on the compiler system) +DESTFILE Location of the DLL file that should be upgraded (on the user's system) +TEMPBASEDIR Directory on the user's system to store a temporary file when the system has + to be rebooted. + For Win9x/ME support, this should be on the same volume as DESTFILE. + The Windows temp directory could be located on any volume, so you cannot use + this directory. + +Define UPGRADEDLL_NOREGISTER if you want to upgrade a DLL that does not have to be registered. + +Notes: + +* If you want to support Windows 9x/ME, you can only use short filenames (8.3). + +* This macro uses the GetDLLVersionLocal command to retrieve the version of local libraries. + This command is only supported when compiling on a Windows system. + +------------------------ + +Example: + +!insertmacro UpgradeDLL "dllname.dll" "$SYSDIR\dllname.dll" "$SYSDIR" + +*/ + +!ifndef UPGRADEDLL_INCLUDED + +!define UPGRADEDLL_INCLUDED + +!macro __UpgradeDLL_Helper_AddRegToolEntry mode filename tempdir + + Push $R0 + Push $R1 + Push $R2 + Push $R3 + + ;------------------------ + ;Copy the parameters + + Push "${filename}" + Push "${tempdir}" + + Pop $R2 ; temporary directory + Pop $R1 ; file name to register + + ;------------------------ + ;Advance counter + + StrCpy $R0 0 + ReadRegDWORD $R0 HKLM "Software\NSIS.Library.RegTool.v2\UpgradeDLLSession" "count" + IntOp $R0 $R0 + 1 + WriteRegDWORD HKLM "Software\NSIS.Library.RegTool.v2\UpgradeDLLSession" "count" "$R0" + + ;------------------------ + ;Setup RegTool + + ReadRegStr $R3 HKLM "Software\Microsoft\Windows\CurrentVersion\RunOnce" "NSIS.Library.RegTool.v2" + StrCpy $R3 $R3 -4 1 + IfFileExists $R3 +3 + + File /oname=$R2\NSIS.Library.RegTool.v2.$HWNDPARENT.exe "${NSISDIR}\Bin\RegTool.bin" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\RunOnce" \ + "NSIS.Library.RegTool.v2" '"$R2\NSIS.Library.RegTool.v2.$HWNDPARENT.exe" /S' + + ;------------------------ + ;Add RegTool entry + + WriteRegStr HKLM "Software\NSIS.Library.RegTool.v2\UpgradeDLLSession" "$R0.file" "$R1" + WriteRegStr HKLM "Software\NSIS.Library.RegTool.v2\UpgradeDLLSession" "$R0.mode" "${mode}" + + Pop $R3 + Pop $R2 + Pop $R1 + Pop $R0 + +!macroend + +!macro UpgradeDLL LOCALFILE DESTFILE TEMPBASEDIR + + Push $R0 + Push $R1 + Push $R2 + Push $R3 + Push $R4 + Push $R5 + + !define UPGRADEDLL_UNIQUE "${__FILE__}${__LINE__}" + + SetOverwrite try + + ;------------------------ + ;Copy the parameters used on run-time to a variable + ;This allows the usage of variables as paramter + + StrCpy $R4 "${DESTFILE}" + StrCpy $R5 "${TEMPBASEDIR}" + + ;------------------------ + ;Get version information + + IfFileExists $R4 0 "upgradedll.copy_${UPGRADEDLL_UNIQUE}" + + ClearErrors + GetDLLVersionLocal "${LOCALFILE}" $R0 $R1 + GetDLLVersion $R4 $R2 $R3 + IfErrors "upgradedll.upgrade_${UPGRADEDLL_UNIQUE}" + + IntCmpU $R0 $R2 0 "upgradedll.done_${UPGRADEDLL_UNIQUE}" "upgradedll.upgrade_${UPGRADEDLL_UNIQUE}" + IntCmpU $R1 $R3 "upgradedll.done_${UPGRADEDLL_UNIQUE}" "upgradedll.done_${UPGRADEDLL_UNIQUE}" \ + "upgradedll.upgrade_${UPGRADEDLL_UNIQUE}" + + ;------------------------ + ;Upgrade + + "upgradedll.upgrade_${UPGRADEDLL_UNIQUE}:" + !ifndef UPGRADEDLL_NOREGISTER + ;Unregister the DLL + UnRegDLL $R4 + !endif + + ;------------------------ + ;Copy + + ClearErrors + StrCpy $R0 $R4 + Call ":upgradedll.file_${UPGRADEDLL_UNIQUE}" + IfErrors 0 "upgradedll.noreboot_${UPGRADEDLL_UNIQUE}" + + ;------------------------ + ;Copy on reboot + + GetTempFileName $R0 $R5 + Call ":upgradedll.file_${UPGRADEDLL_UNIQUE}" + Rename /REBOOTOK $R0 $R4 + + ;------------------------ + ;Register on reboot + + !insertmacro __UpgradeDLL_Helper_AddRegToolEntry 'D' $R4 $R5 + + Goto "upgradedll.done_${UPGRADEDLL_UNIQUE}" + + ;------------------------ + ;DLL does not exist + + "upgradedll.copy_${UPGRADEDLL_UNIQUE}:" + StrCpy $R0 $R4 + Call ":upgradedll.file_${UPGRADEDLL_UNIQUE}" + + ;------------------------ + ;Register + + "upgradedll.noreboot_${UPGRADEDLL_UNIQUE}:" + !ifndef UPGRADEDLL_NOREGISTER + RegDLL $R4 + !endif + + ;------------------------ + ;Done + + "upgradedll.done_${UPGRADEDLL_UNIQUE}:" + + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Pop $R0 + + ;------------------------ + ;End + + Goto "upgradedll.end_${UPGRADEDLL_UNIQUE}" + + ;------------------------ + ;Extract + + "upgradedll.file_${UPGRADEDLL_UNIQUE}:" + File /oname=$R0 "${LOCALFILE}" + Return + + "upgradedll.end_${UPGRADEDLL_UNIQUE}:" + + SetOverwrite lastused + + !undef UPGRADEDLL_UNIQUE + +!macroend + +!endif diff --git a/installer/NSIS/Include/Util.nsh b/installer/NSIS/Include/Util.nsh new file mode 100644 index 0000000..cb04636 --- /dev/null +++ b/installer/NSIS/Include/Util.nsh @@ -0,0 +1,56 @@ +; --------------------- +; Util.nsh +; --------------------- +; +; Voodoo macros to make end-user usage easier. This may be documented someday. + +!verbose push +!verbose 3 + +!ifndef ___UTIL__NSH___ +!define ___UTIL__NSH___ + +# see WinVer.nsh and *Func.nsh for usage examples +!macro CallArtificialFunction NAME + !ifndef __UNINSTALL__ + !define CallArtificialFunction_TYPE inst + !else + !define CallArtificialFunction_TYPE uninst + !endif + Call :.${NAME}${CallArtificialFunction_TYPE} + !ifndef ${NAME}${CallArtificialFunction_TYPE}_DEFINED + Goto ${NAME}${CallArtificialFunction_TYPE}_DONE + !define ${NAME}${CallArtificialFunction_TYPE}_DEFINED + .${NAME}${CallArtificialFunction_TYPE}: + !insertmacro ${NAME} + Return + ${NAME}${CallArtificialFunction_TYPE}_DONE: + !endif + !undef CallArtificialFunction_TYPE +!macroend +!define CallArtificialFunction `!insertmacro CallArtificialFunction` + +# for usage of artificial functions inside artificial functions +# macro recursion is prohibited +!macro CallArtificialFunction2 NAME + !ifndef __UNINSTALL__ + !define CallArtificialFunction2_TYPE inst + !else + !define CallArtificialFunction2_TYPE uninst + !endif + Call :.${NAME}${CallArtificialFunction2_TYPE} + !ifndef ${NAME}${CallArtificialFunction2_TYPE}_DEFINED + Goto ${NAME}${CallArtificialFunction2_TYPE}_DONE + !define ${NAME}${CallArtificialFunction2_TYPE}_DEFINED + .${NAME}${CallArtificialFunction2_TYPE}: + !insertmacro ${NAME} + Return + ${NAME}${CallArtificialFunction2_TYPE}_DONE: + !endif + !undef CallArtificialFunction2_TYPE +!macroend +!define CallArtificialFunction2 `!insertmacro CallArtificialFunction2` + +!endif # !___UTIL__NSH___ + +!verbose pop diff --git a/installer/NSIS/Include/VB6RunTime.nsh b/installer/NSIS/Include/VB6RunTime.nsh new file mode 100644 index 0000000..4822bad --- /dev/null +++ b/installer/NSIS/Include/VB6RunTime.nsh @@ -0,0 +1,90 @@ +/* + +VB6RunTime.nsh + +Setup of Visual Basic 6.0 run-time files, including the Oleaut32.dll security update + +Copyright 2008-2009 Joost Verburg + +To obtain the run-time files, download and extract +http://nsis.sourceforge.net/vb6runtime.zip + +Script code for installation: + +!insertmacro InstallVB6RunTime FOLDER ALREADY_INSTALLED + +in which FOLDER is the location of the run-time files and ALREADY_INSTALLED is the +name of a variable that is empty when the application is installed for the first time +and non-empty otherwise + +Script code for uninstallation: + +!insertmacro UnInstallVB6RunTime + +Remarks: + +* You may have to install additional files for such Visual Basic application to work, + such as OCX files for user interface controls. + +* Installation of the run-time files requires Administrator or Power User privileges. + Use the Multi-User header file to verify whether these privileges are available. + +* Add a Modern UI finish page or another check (see IfRebootFlag in the NSIS Users + Manual) to allow the user to restart the computer when necessary. + +*/ + +!ifndef VB6_INCLUDED +!define VB6_INCLUDED +!verbose push +!verbose 3 + +!include Library.nsh +!include WinVer.nsh + +!macro VB6RunTimeInstall FOLDER ALREADY_INSTALLED + + !insertmacro InstallLib REGDLL "${ALREADY_INSTALLED}" REBOOT_PROTECTED "${FOLDER}\msvbvm60.dll" "$SYSDIR\msvbvm60.dll" "$SYSDIR" + + ;The files below will only be installed on Win9x/NT4 + + !insertmacro InstallLib REGDLL "${ALREADY_INSTALLED}" REBOOT_PROTECTED "${FOLDER}\olepro32.dll" "$SYSDIR\olepro32.dll" "$SYSDIR" + !insertmacro InstallLib REGDLL "${ALREADY_INSTALLED}" REBOOT_PROTECTED "${FOLDER}\comcat.dll" "$SYSDIR\comcat.dll" "$SYSDIR" + !insertmacro InstallLib DLL "${ALREADY_INSTALLED}" REBOOT_PROTECTED "${FOLDER}\asycfilt.dll" "$SYSDIR\asycfilt.dll" "$SYSDIR" + !insertmacro InstallLib TLB "${ALREADY_INSTALLED}" REBOOT_PROTECTED "${FOLDER}\stdole2.tlb" "$SYSDIR\stdole2.tlb" "$SYSDIR" + + Push $R0 + + ${if} ${IsNT} + ${if} ${IsWinNT4} + ReadRegStr $R0 HKLM "System\CurrentControlSet\Control" "ProductOptions" + ${if} $R0 == "Terminal Server" + !insertmacro InstallLib REGDLL "${ALREADY_INSTALLED}" REBOOT_NOTPROTECTED "${FOLDER}\NT4TS\oleaut32.dll" "$SYSDIR\oleaut32.dll" "$SYSDIR" + ${else} + !insertmacro InstallLib REGDLL "${ALREADY_INSTALLED}" REBOOT_NOTPROTECTED "${FOLDER}\NT4\oleaut32.dll" "$SYSDIR\oleaut32.dll" "$SYSDIR" + ${endif} + ${endif} + ${else} + ;No Oleaut32.dll with the security update has been released for Windows 9x. + ;The NT4 version is used because NT4 and Win9x used to share the same 2.40 version + ;and version 2.40.4519.0 is reported to work fine on Win9x. + !insertmacro InstallLib REGDLL "${ALREADY_INSTALLED}" REBOOT_NOTPROTECTED "${FOLDER}\NT4\oleaut32.dll" "$SYSDIR\oleaut32.dll" "$SYSDIR" + ${endif} + + Pop $R0 + +!macroend + +!macro VB6RunTimeUnInstall + + !insertmacro UnInstallLib REGDLL SHARED NOREMOVE "$SYSDIR\msvbvm60.dll" + !insertmacro UnInstallLib REGDLL SHARED NOREMOVE "$SYSDIR\oleaut32.dll" + !insertmacro UnInstallLib REGDLL SHARED NOREMOVE "$SYSDIR\olepro32.dll" + !insertmacro UnInstallLib REGDLL SHARED NOREMOVE "$SYSDIR\comcat.dll" + !insertmacro UnInstallLib DLL SHARED NOREMOVE "$SYSDIR\asycfilt.dll" + !insertmacro UnInstallLib TLB SHARED NOREMOVE "$SYSDIR\stdole2.tlb" + +!macroend + +!verbose pop +!endif diff --git a/installer/NSIS/Include/VPatchLib.nsh b/installer/NSIS/Include/VPatchLib.nsh new file mode 100644 index 0000000..1e01bc8 --- /dev/null +++ b/installer/NSIS/Include/VPatchLib.nsh @@ -0,0 +1,47 @@ +; PatchLib v3.0 +; ============= +; +; Library with macro for use with VPatch (DLL version) in NSIS 2.0.5+ +; Created by Koen van de Sande + +!include LogicLib.nsh + +!macro VPatchFile PATCHDATA SOURCEFILE TEMPFILE + + Push $1 + Push $2 + Push $3 + Push $4 + + Push ${SOURCEFILE} + Push ${TEMPFILE} + + Pop $2 # temp file + Pop $3 # source file + + InitPluginsDir + GetTempFileName $1 $PLUGINSDIR + File /oname=$1 ${PATCHDATA} + + vpatch::vpatchfile $1 $3 $2 + Pop $4 + DetailPrint $4 + + StrCpy $4 $4 2 + ${Unless} $4 == "OK" + SetErrors + ${EndIf} + + ${If} ${FileExists} $2 + Delete $3 + Rename /REBOOTOK $2 $3 + ${EndIf} + + Delete $1 + + Pop $4 + Pop $3 + Pop $2 + Pop $1 + +!macroend diff --git a/installer/NSIS/Include/Win/WinDef.nsh b/installer/NSIS/Include/Win/WinDef.nsh new file mode 100644 index 0000000..14ee768 --- /dev/null +++ b/installer/NSIS/Include/Win/WinDef.nsh @@ -0,0 +1,74 @@ +!ifndef __WIN_WINDEF__INC +!define __WIN_WINDEF__INC +!verbose push +!verbose 3 +!ifndef __WIN_NOINC_WINDEF + + +!ifndef MAX_PATH +!define MAX_PATH 260 +!endif +#define NULL 0 + + +!macro _Win_MINMAX _intcmp _j1 _j2 _outvar _a _b +${_intcmp} "${_a}" "${_b}" ${_j1} ${_j1} ${_j2} +StrCpy ${_outvar} "${_a}" +goto +2 +StrCpy ${_outvar} "${_b}" +!macroend +!ifndef __WIN_MS_NOMINMAX & min & max & min_u & max_u +!define min "!insertmacro _Win_MINMAX IntCmp +1 +3 " +!define max "!insertmacro _Win_MINMAX IntCmp +3 +1 " +!define min_u "!insertmacro _Win_MINMAX IntCmpU +1 +3 " +!define max_u "!insertmacro _Win_MINMAX IntCmpU +3 +1 " +!endif + +!macro _Win_LOBYTE _outvar _in +IntOp ${_outvar} "${_in}" & 0xFF +!macroend +!define LOBYTE "!insertmacro _Win_LOBYTE " + +!macro _Win_HIBYTE _outvar _in +IntOp ${_outvar} "${_in}" >> 8 +${LOBYTE} ${_outvar} ${_outvar} +!macroend +!define HIBYTE "!insertmacro _Win_HIBYTE " + +!macro _Win_LOWORD _outvar _in +IntOp ${_outvar} "${_in}" & 0xFFFF +!macroend +!define LOWORD "!insertmacro _Win_LOWORD " + +!macro _Win_HIWORD _outvar _in +IntOp ${outvar} "${_in}" >> 16 ;sign extended :( +${LOWORD} ${_outvar} ${outvar} ;make sure we strip off the upper word +!macroend +!define HIWORD "!insertmacro _Win_HIWORD " + +!macro _Win_MAKEWORD _outvar _tmpvar _lo _hi +${LOBYTE} ${_outvar} "${_hi}" +${LOBYTE} ${_tmpvar} "${_lo}" +IntOp ${_outvar} ${_outvar} << 8 +IntOp ${_outvar} ${_outvar} | ${_tmpvar} +!macroend +!define MAKEWORD "!insertmacro _Win_MAKEWORD " + +!macro _Win_MAKELONG32 _outvar _tmpvar _wlo _whi +${LOWORD} ${_outvar} "${_wlo}" +IntOp ${_tmpvar} "${_whi}" << 16 +IntOp ${_outvar} ${_outvar} | ${_tmpvar} +!macroend +!define MAKELONG "!insertmacro _Win_MAKELONG32 " +!if "${__WIN_PTRSIZE}" <= 4 +!define MAKEWPARAM "${MAKELONG}" +!define MAKELPARAM "${MAKELONG}" +!define MAKELRESULT "${MAKELONG}" +!else +!error "Missing 64bit imp!" +!endif + + +!endif /* __WIN_NOINC_WINDEF */ +!verbose pop +!endif /* __WIN_WINDEF__INC */ \ No newline at end of file diff --git a/installer/NSIS/Include/Win/WinError.nsh b/installer/NSIS/Include/Win/WinError.nsh new file mode 100644 index 0000000..988c9eb --- /dev/null +++ b/installer/NSIS/Include/Win/WinError.nsh @@ -0,0 +1,64 @@ +!ifndef __WIN_WINERROR__INC +!define __WIN_WINERROR__INC +!verbose push +!verbose 3 +!ifndef __WIN_NOINC_WINERROR + +#define NO_ERROR 0 +!define ERROR_SUCCESS 0 +!define ERROR_INVALID_FUNCTION 1 +!define ERROR_FILE_NOT_FOUND 2 +!define ERROR_PATH_NOT_FOUND 3 +!define ERROR_TOO_MANY_OPEN_FILES 4 +!define ERROR_ACCESS_DENIED 5 +!define ERROR_INVALID_HANDLE 6 +!define ERROR_ARENA_TRASHED 7 +!define ERROR_NOT_ENOUGH_MEMORY 8 +!define ERROR_INVALID_BLOCK 9 +!define ERROR_BAD_ENVIRONMENT 10 +!define ERROR_BAD_FORMAT 11 +!define ERROR_INVALID_ACCESS 12 +!define ERROR_INVALID_DATA 13 +!define ERROR_OUTOFMEMORY 14 +!define ERROR_INVALID_DRIVE 15 +!define ERROR_CURRENT_DIRECTORY 16 +!define ERROR_NOT_SAME_DEVICE 17 +!define ERROR_NO_MORE_FILES 18 +!define ERROR_WRITE_PROTECT 19 +!define ERROR_BAD_UNIT 20 +!define ERROR_NOT_READY 21 +!define ERROR_BAD_COMMAND 22 +!define ERROR_CRC 23 +!define ERROR_BAD_LENGTH 24 +!define ERROR_SEEK 25 +!define ERROR_NOT_DOS_DISK 26 +!define ERROR_SECTOR_NOT_FOUND 27 +!define ERROR_OUT_OF_PAPER 28 +!define ERROR_WRITE_FAULT 29 +!define ERROR_READ_FAULT 30 +!define ERROR_GEN_FAILURE 31 +!define ERROR_SHARING_VIOLATION 32 +!define ERROR_LOCK_VIOLATION 33 +!define ERROR_WRONG_DISK 34 +!define ERROR_SHARING_BUFFER_EXCEEDED 36 +!define ERROR_HANDLE_EOF 38 +!define ERROR_HANDLE_DISK_FULL 39 +!define ERROR_NOT_SUPPORTED 50 + +!define SEVERITY_SUCCESS 0 +!define SEVERITY_ERROR 1 +!define E_UNEXPECTED 0x8000FFFF +!define E_NOTIMPL 0x80004001 +!define E_OUTOFMEMORY 0x8007000E +!define E_INVALIDARG 0x80070057 +!define E_NOINTERFACE 0x80004002 +!define E_POINTER 0x80004003 +!define E_HANDLE 0x80070006 +!define E_ABORT 0x80004004 +!define E_FAIL 0x80004005 +!define E_ACCESSDENIED 0x80070005 +!define E_PENDING 0x8000000A + +!endif /* __WIN_NOINC_WINERROR */ +!verbose pop +!endif /* __WIN_WINERROR__INC */ \ No newline at end of file diff --git a/installer/NSIS/Include/Win/WinNT.nsh b/installer/NSIS/Include/Win/WinNT.nsh new file mode 100644 index 0000000..629b32f --- /dev/null +++ b/installer/NSIS/Include/Win/WinNT.nsh @@ -0,0 +1,209 @@ +!ifndef __WIN_WINNT__INC +!define __WIN_WINNT__INC +!verbose push +!verbose 3 +!ifndef __WIN_NOINC_WINNT + + +#define MINCHAR 0x80 +#define MAXCHAR 0x7f +!define MINSHORT 0x8000 +!define MAXSHORT 0x7fff +!define MINLONG 0x80000000 +!define MAXLONG 0x7fffffff +!define MAXBYTE 0xff +!define MAXWORD 0xffff +!define MAXDWORD 0xffffffff + +!ifndef WIN32_NO_STATUS +!define STATUS_WAIT_0 0x00000000 +!define STATUS_ABANDONED_WAIT_0 0x00000080 +!define STATUS_USER_APC 0x000000C0 +!define STATUS_TIMEOUT 0x00000102 +!define STATUS_PENDING 0x00000103 +!define DBG_EXCEPTION_HANDLED 0x00010001 +!define DBG_CONTINUE 0x00010002 +!define STATUS_SEGMENT_NOTIFICATION 0x40000005 +!define DBG_TERMINATE_THREAD 0x40010003 +!define DBG_TERMINATE_PROCESS 0x40010004 +!define DBG_CONTROL_C 0x40010005 +!define DBG_CONTROL_BREAK 0x40010008 +!define DBG_COMMAND_EXCEPTION 0x40010009 +!define STATUS_GUARD_PAGE_VIOLATION 0x80000001 +!define STATUS_DATATYPE_MISALIGNMENT 0x80000002 +!define STATUS_BREAKPOINT 0x80000003 +!define STATUS_SINGLE_STEP 0x80000004 +!define DBG_EXCEPTION_NOT_HANDLED 0x80010001 +!define STATUS_ACCESS_VIOLATION 0xC0000005 +!define STATUS_IN_PAGE_ERROR 0xC0000006 +!define STATUS_INVALID_HANDLE 0xC0000008 +!define STATUS_NO_MEMORY 0xC0000017 +!define STATUS_ILLEGAL_INSTRUCTION 0xC000001D +!define STATUS_NONCONTINUABLE_EXCEPTION 0xC0000025 +!define STATUS_INVALID_DISPOSITION 0xC0000026 +!define STATUS_ARRAY_BOUNDS_EXCEEDED 0xC000008C +!define STATUS_FLOAT_DENORMAL_OPERAND 0xC000008D +!define STATUS_FLOAT_DIVIDE_BY_ZERO 0xC000008E +!define STATUS_FLOAT_INEXACT_RESULT 0xC000008F +!define STATUS_FLOAT_INVALID_OPERATION 0xC0000090 +!define STATUS_FLOAT_OVERFLOW 0xC0000091 +!define STATUS_FLOAT_STACK_CHECK 0xC0000092 +!define STATUS_FLOAT_UNDERFLOW 0xC0000093 +!define STATUS_INTEGER_DIVIDE_BY_ZERO 0xC0000094 +!define STATUS_INTEGER_OVERFLOW 0xC0000095 +!define STATUS_PRIVILEGED_INSTRUCTION 0xC0000096 +!define STATUS_STACK_OVERFLOW 0xC00000FD +!define STATUS_CONTROL_C_EXIT 0xC000013A +!define STATUS_FLOAT_MULTIPLE_FAULTS 0xC00002B4 +!define STATUS_FLOAT_MULTIPLE_TRAPS 0xC00002B5 +!define STATUS_REG_NAT_CONSUMPTION 0xC00002C9 +!define STATUS_SXS_EARLY_DEACTIVATION 0xC015000F +!define STATUS_SXS_INVALID_DEACTIVATION 0xC0150010 +!endif /*WIN32_NO_STATUS*/ + +#define MAXIMUM_WAIT_OBJECTS 64 + +!define DELETE 0x00010000 +!define READ_CONTROL 0x00020000 +!define WRITE_DAC 0x00040000 +!define WRITE_OWNER 0x00080000 +!define SYNCHRONIZE 0x00100000 +!define STANDARD_RIGHTS_REQUIRED 0x000F0000 +!define STANDARD_RIGHTS_READ ${READ_CONTROL} +!define STANDARD_RIGHTS_WRITE ${READ_CONTROL} +!define STANDARD_RIGHTS_EXECUTE ${READ_CONTROL} +!define STANDARD_RIGHTS_ALL 0x001F0000 +!define SPECIFIC_RIGHTS_ALL 0x0000FFFF +!define ACCESS_SYSTEM_SECURITY 0x01000000 +!define MAXIMUM_ALLOWED 0x02000000 +!define GENERIC_READ 0x80000000 +!define GENERIC_WRITE 0x40000000 +!define GENERIC_EXECUTE 0x20000000 +!define GENERIC_ALL 0x10000000 + +!define SE_PRIVILEGE_ENABLED_BY_DEFAULT 0x00000001 +!define SE_PRIVILEGE_ENABLED 0x00000002 +!define SE_PRIVILEGE_REMOVED 0x00000004 +!define SE_PRIVILEGE_USED_FOR_ACCESS 0x80000000 + +!define SE_CREATE_TOKEN_NAME "SeCreateTokenPrivilege" +!define SE_ASSIGNPRIMARYTOKEN_NAME "SeAssignPrimaryTokenPrivilege" +!define SE_LOCK_MEMORY_NAME "SeLockMemoryPrivilege" +!define SE_INCREASE_QUOTA_NAME "SeIncreaseQuotaPrivilege" +!define SE_UNSOLICITED_INPUT_NAME "SeUnsolicitedInputPrivilege" +!define SE_MACHINE_ACCOUNT_NAME "SeMachineAccountPrivilege" +!define SE_TCB_NAME "SeTcbPrivilege" +!define SE_SECURITY_NAME "SeSecurityPrivilege" +!define SE_TAKE_OWNERSHIP_NAME "SeTakeOwnershipPrivilege" +!define SE_LOAD_DRIVER_NAME "SeLoadDriverPrivilege" +!define SE_SYSTEM_PROFILE_NAME "SeSystemProfilePrivilege" +!define SE_SYSTEMTIME_NAME "SeSystemtimePrivilege" +!define SE_PROF_SINGLE_PROCESS_NAME "SeProfileSingleProcessPrivilege" +!define SE_INC_BASE_PRIORITY_NAME "SeIncreaseBasePriorityPrivilege" +!define SE_CREATE_PAGEFILE_NAME "SeCreatePagefilePrivilege" +!define SE_CREATE_PERMANENT_NAME "SeCreatePermanentPrivilege" +!define SE_BACKUP_NAME "SeBackupPrivilege" +!define SE_RESTORE_NAME "SeRestorePrivilege" +!define SE_SHUTDOWN_NAME "SeShutdownPrivilege" +!define SE_DEBUG_NAME "SeDebugPrivilege" +!define SE_AUDIT_NAME "SeAuditPrivilege" +!define SE_SYSTEM_ENVIRONMENT_NAME "SeSystemEnvironmentPrivilege" +!define SE_CHANGE_NOTIFY_NAME "SeChangeNotifyPrivilege" +!define SE_REMOTE_SHUTDOWN_NAME "SeRemoteShutdownPrivilege" +!define SE_UNDOCK_NAME "SeUndockPrivilege" +!define SE_SYNC_AGENT_NAME "SeSyncAgentPrivilege" +!define SE_ENABLE_DELEGATION_NAME "SeEnableDelegationPrivilege" +!define SE_MANAGE_VOLUME_NAME "SeManageVolumePrivilege" +!define SE_IMPERSONATE_NAME "SeImpersonatePrivilege" +!define SE_CREATE_GLOBAL_NAME "SeCreateGlobalPrivilege" + +!define TOKEN_ASSIGN_PRIMARY 0x0001 +!define TOKEN_DUPLICATE 0x0002 +!define TOKEN_IMPERSONATE 0x0004 +!define TOKEN_QUERY 0x0008 +!define TOKEN_QUERY_SOURCE 0x0010 +!define TOKEN_ADJUST_PRIVILEGES 0x0020 +!define TOKEN_ADJUST_GROUPS 0x0040 +!define TOKEN_ADJUST_DEFAULT 0x0080 +!define TOKEN_ADJUST_SESSIONID 0x0100 +!define TOKEN_ALL_ACCESS_P 0xF00FF +!define /math TOKEN_ALL_ACCESS ${TOKEN_ALL_ACCESS_P} | ${TOKEN_ADJUST_SESSIONID} +!define /math TOKEN_READ ${STANDARD_RIGHTS_READ} | ${TOKEN_QUERY} +!define TOKEN_WRITE 0x200E0 ;(STANDARD_RIGHTS_WRITE|TOKEN_ADJUST_PRIVILEGES|TOKEN_ADJUST_GROUPS|TOKEN_ADJUST_DEFAULT) +!define TOKEN_EXECUTE ${STANDARD_RIGHTS_EXECUTE} + +!define PROCESS_TERMINATE 0x0001 +!define PROCESS_CREATE_THREAD 0x0002 +!define PROCESS_SET_SESSIONID 0x0004 +!define PROCESS_VM_OPERATION 0x0008 +!define PROCESS_VM_READ 0x0010 +!define PROCESS_VM_WRITE 0x0020 +!define PROCESS_DUP_HANDLE 0x0040 +!define PROCESS_CREATE_PROCESS 0x0080 +!define PROCESS_SET_QUOTA 0x0100 +!define PROCESS_SET_INFORMATION 0x0200 +!define PROCESS_QUERY_INFORMATION 0x0400 +!define PROCESS_SUSPEND_RESUME 0x0800 +!define PROCESS_ALL_ACCESS 0x1F0FFF ;(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF) +!define THREAD_TERMINATE 0x0001 +!define THREAD_SUSPEND_RESUME 0x0002 +!define THREAD_GET_CONTEXT 0x0008 +!define THREAD_SET_CONTEXT 0x0010 +!define THREAD_SET_INFORMATION 0x0020 +!define THREAD_QUERY_INFORMATION 0x0040 +!define THREAD_SET_THREAD_TOKEN 0x0080 +!define THREAD_IMPERSONATE 0x0100 +!define THREAD_DIRECT_IMPERSONATION 0x0200 +!define THREAD_ALL_ACCESS 0x1F03FF ;(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3FF) +!define JOB_OBJECT_ASSIGN_PROCESS 0x0001 +!define JOB_OBJECT_SET_ATTRIBUTES 0x0002 +!define JOB_OBJECT_QUERY 0x0004 +!define JOB_OBJECT_TERMINATE 0x0008 +!define JOB_OBJECT_SET_SECURITY_ATTRIBUTES 0x0010 +!define JOB_OBJECT_ALL_ACCESS 0x1F001F ;(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1F ) +!define EVENT_MODIFY_STATE 0x0002 +!define EVENT_ALL_ACCESS 0x1F0003 ;(STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3) +!define MUTANT_QUERY_STATE 0x0001 +!define MUTANT_ALL_ACCESS 0x1F0001 ;(STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|MUTANT_QUERY_STATE) + +!define FILE_SHARE_READ 0x00000001 +!define FILE_SHARE_WRITE 0x00000002 +!define FILE_SHARE_DELETE 0x00000004 +!define FILE_ATTRIBUTE_READONLY 0x00000001 +!define FILE_ATTRIBUTE_HIDDEN 0x00000002 +!define FILE_ATTRIBUTE_SYSTEM 0x00000004 +!define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +!define FILE_ATTRIBUTE_ARCHIVE 0x00000020 +!define FILE_ATTRIBUTE_DEVICE 0x00000040 +!define FILE_ATTRIBUTE_NORMAL 0x00000080 +!define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +!define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 +!define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 +!define FILE_ATTRIBUTE_COMPRESSED 0x00000800 +!define FILE_ATTRIBUTE_OFFLINE 0x00001000 +!define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +!define FILE_ATTRIBUTE_ENCRYPTED 0x00004000 + +!define DUPLICATE_CLOSE_SOURCE 0x00000001 +!define DUPLICATE_SAME_ACCESS 0x00000002 + +!define VER_PLATFORM_WIN32s 0 +!define VER_PLATFORM_WIN32_WINDOWS 1 +!define VER_PLATFORM_WIN32_NT 2 + +!ifndef REG_SZ & NSIS_WINDOWS__NO_REGTYPES +!define REG_NONE 0 +!define REG_SZ 1 +!define REG_EXPAND_SZ 2 +!define REG_BINARY 3 +!define REG_DWORD 4 +!define REG_DWORD_LITTLE_ENDIAN 4 +!define REG_DWORD_BIG_ENDIAN 5 +!define REG_LINK 6 +!define REG_MULTI_SZ 7 +!endif + + +!endif /* __WIN_NOINC_WINNT */ +!verbose pop +!endif /* __WIN_WINNT__INC */ \ No newline at end of file diff --git a/installer/NSIS/Include/Win/WinUser.nsh b/installer/NSIS/Include/Win/WinUser.nsh new file mode 100644 index 0000000..1092848 --- /dev/null +++ b/installer/NSIS/Include/Win/WinUser.nsh @@ -0,0 +1,199 @@ +!ifndef __WIN_WINUSER__INC +!define __WIN_WINUSER__INC +!verbose push +!verbose 3 +!ifndef __WIN_MS_NOUSER & __WIN_NOINC_WINUSER + +!ifndef __WIN_MS_NOVIRTUALKEYCODES +!define VK_LBUTTON 0x01 +!define VK_RBUTTON 0x02 +!define VK_CANCEL 0x03 +!define VK_MBUTTON 0x04 /* NOT contiguous with L & RBUTTON */ +!define VK_XBUTTON1 0x05 /* NOT contiguous with L & RBUTTON */ +!define VK_XBUTTON2 0x06 /* NOT contiguous with L & RBUTTON */ +!define VK_BACK 0x08 +!define VK_TAB 0x09 +!define VK_CLEAR 0x0C +!define VK_RETURN 0x0D +!define VK_SHIFT 0x10 +!define VK_CONTROL 0x11 +!define VK_MENU 0x12 +!define VK_PAUSE 0x13 +!define VK_CAPITAL 0x14 +!define VK_ESCAPE 0x1B +!define VK_CONVERT 0x1C +!define VK_NONCONVERT 0x1D +!define VK_ACCEPT 0x1E +!define VK_MODECHANGE 0x1F +!define VK_SPACE 0x20 +!define VK_PRIOR 0x21 +!define VK_NEXT 0x22 +!define VK_END 0x23 +!define VK_HOME 0x24 +!define VK_LEFT 0x25 +!define VK_UP 0x26 +!define VK_RIGHT 0x27 +!define VK_DOWN 0x28 +!define VK_SELECT 0x29 +!define VK_PRINT 0x2A +!define VK_EXECUTE 0x2B +!define VK_SNAPSHOT 0x2C +!define VK_INSERT 0x2D +!define VK_DELETE 0x2E +!define VK_HELP 0x2F +; VK_0 - VK_9 are the same as ASCII '0' - '9' (0x30 - 0x39) +; VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A) +!define VK_LWIN 0x5B +!define VK_RWIN 0x5C +!define VK_APPS 0x5D +!define VK_SLEEP 0x5F +!define VK_NUMPAD0 0x60 +!define VK_NUMPAD1 0x61 +!define VK_NUMPAD2 0x62 +!define VK_NUMPAD3 0x63 +!define VK_NUMPAD4 0x64 +!define VK_NUMPAD5 0x65 +!define VK_NUMPAD6 0x66 +!define VK_NUMPAD7 0x67 +!define VK_NUMPAD8 0x68 +!define VK_NUMPAD9 0x69 +!define VK_MULTIPLY 0x6A +!define VK_ADD 0x6B +!define VK_SEPARATOR 0x6C +!define VK_SUBTRACT 0x6D +!define VK_DECIMAL 0x6E +!define VK_DIVIDE 0x6F +!define VK_F1 0x70 +!define VK_F2 0x71 +!define VK_F3 0x72 +!define VK_F4 0x73 +!define VK_F5 0x74 +!define VK_F6 0x75 +!define VK_F7 0x76 +!define VK_F8 0x77 +!define VK_F9 0x78 +!define VK_F10 0x79 +!define VK_F11 0x7A +!define VK_F12 0x7B +!define VK_NUMLOCK 0x90 +!define VK_SCROLL 0x91 +!define VK_OEM_NEC_EQUAL 0x92 ; '=' key on numpad +!define VK_LSHIFT 0xA0 +!define VK_RSHIFT 0xA1 +!define VK_LCONTROL 0xA2 +!define VK_RCONTROL 0xA3 +!define VK_LMENU 0xA4 +!define VK_RMENU 0xA5 +!endif + +!ifndef __WIN_MS_NOWINOFFSETS +/* in nsDialogs.nsh... +!define GWL_STYLE -16 +!define GWL_EXSTYLE -20 */ +!define GWLP_WNDPROC -4 +!define GWLP_HINSTANCE -6 +!define GWLP_HWNDPARENT -8 +!define GWLP_USERDATA -21 +!define GWLP_ID -12 +!define DWLP_MSGRESULT 0 +!define /math DWLP_DLGPROC ${DWLP_MSGRESULT} + ${__WIN_PTRSIZE} ;DWLP_MSGRESULT + sizeof(LRESULT) +!define /math DWLP_USER ${DWLP_DLGPROC} + ${__WIN_PTRSIZE} ;DWLP_DLGPROC + sizeof(DLGPROC) +!endif + +!ifndef __WIN_MS_NONCMESSAGES +!define HTERROR -2 +!define HTTRANSPARENT -1 +!define HTNOWHERE 0 +!define HTCLIENT 1 +!define HTCAPTION 2 +!define HTSYSMENU 3 +!define HTGROWBOX 4 +!define HTSIZE ${HTGROWBOX} +!define HTMENU 5 +!define HTHSCROLL 6 +!define HTVSCROLL 7 +!define HTMINBUTTON 8 +!define HTMAXBUTTON 9 +!define HTLEFT 10 +!define HTRIGHT 11 +!define HTTOP 12 +!define HTTOPLEFT 13 +!define HTTOPRIGHT 14 +!define HTBOTTOM 15 +!define HTBOTTOMLEFT 16 +!define HTBOTTOMRIGHT 17 +!define HTBORDER 18 +!define HTREDUCE ${HTMINBUTTON} +!define HTZOOM ${HTMAXBUTTON} +!define HTSIZEFIRST ${HTLEFT} +!define HTSIZELAST ${HTBOTTOMRIGHT} +!define HTOBJECT 19 +!define HTCLOSE 20 +!define HTHELP 21 +!endif + +!ifndef __WIN_MS_NOSYSCOMMANDS +!define SC_SIZE 0xF000 +!define SC_MOVE 0xF010 +!define SC_MINIMIZE 0xF020 +!define SC_MAXIMIZE 0xF030 +!define SC_NEXTWINDOW 0xF040 +!define SC_PREVWINDOW 0xF050 +!define SC_CLOSE 0xF060 +!define SC_VSCROLL 0xF070 +!define SC_HSCROLL 0xF080 +!define SC_MOUSEMENU 0xF090 +!define SC_KEYMENU 0xF100 +!define SC_ARRANGE 0xF110 +!define SC_RESTORE 0xF120 +!define SC_TASKLIST 0xF130 +!define SC_SCREENSAVE 0xF140 +!define SC_HOTKEY 0xF150 +!define SC_DEFAULT 0xF160 +!define SC_MONITORPOWER 0xF170 +!define SC_CONTEXTHELP 0xF180 +!define SC_SEPARATOR 0xF00F +!endif + +!define IDC_ARROW 32512 +!define IDC_IBEAM 32513 +!define IDC_WAIT 32514 +!define IDC_CROSS 32515 +!define IDC_UPARROW 32516 +!define IDC_SIZENWSE 32642 +!define IDC_SIZENESW 32643 +!define IDC_SIZEWE 32644 +!define IDC_SIZENS 32645 +!define IDC_SIZEALL 32646 +!define IDC_NO 32648 +!define IDC_HAND 32649 +!define IDC_APPSTARTING 32650 +!define IDC_HELP 32651 + +/* in nsDialogs.nsh... +!define IMAGE_BITMAP 0 +!define IMAGE_ICON 1 +!define IMAGE_CURSOR 2*/ + +/* in nsDialogs.nsh... +!define LR_DEFAULTCOLOR 0x0000 +!define LR_MONOCHROME 0x0001 +!define LR_COLOR 0x0002 +!define LR_COPYRETURNORG 0x0004 +!define LR_COPYDELETEORG 0x0008 +!define LR_LOADFROMFILE 0x0010 +!define LR_LOADTRANSPARENT 0x0020 +!define LR_DEFAULTSIZE 0x0040 +!define LR_VGACOLOR 0x0080 +!define LR_LOADMAP3DCOLORS 0x1000 +!define LR_CREATEDIBSECTION 0x2000 +!define LR_COPYFROMRESOURCE 0x4000 +!define LR_SHARED 0x8000*/ + +!define GA_PARENT 1 +!define GA_ROOT 2 +!define GA_ROOTOWNER 3 + +!endif /* __WIN_MS_NOUSER & __WIN_NOINC_WINUSER */ +!verbose pop +!endif /* __WIN_WINUSER__INC */ \ No newline at end of file diff --git a/installer/NSIS/Include/WinCore.nsh b/installer/NSIS/Include/WinCore.nsh new file mode 100644 index 0000000..0aa5ab0 --- /dev/null +++ b/installer/NSIS/Include/WinCore.nsh @@ -0,0 +1,214 @@ +/* + +WinCore.nsh & Win\*.nsh - Collection of common windows defines + +!define __WIN_NOINC_xxx to exclude a windows header file +!define __WIN_MS_xxx to exclude specific things (The original #ifdef xxx checks can be found in the official Microsoft headers) + +*/ + +!ifndef __WIN_WINDOWS__INC +!define __WIN_WINDOWS__INC +!verbose push +!verbose 3 + + +!define __WIN_PTRSIZE 4 ;will we ever see a 64 bit version? + + +!include Win\WinDef.nsh +!include Win\WinError.nsh +!include Win\WinNT.nsh +!include Win\WinUser.nsh + +!ifndef __WIN_MS_NOWINMESSAGES +!include WinMessages.nsh +!endif + + + + + +/************************************************** +WinBase.h +**************************************************/ +!ifndef __WIN_NOINC_WINBASE +!define INVALID_HANDLE_VALUE -1 +!define INVALID_FILE_SIZE 0xFFFFFFFF +!define INVALID_SET_FILE_POINTER -1 +!define INVALID_FILE_ATTRIBUTES -1 + +!define WAIT_FAILED 0xFFFFFFFF +!define WAIT_OBJECT_0 0 ;((STATUS_WAIT_0 ) + 0 ) + +!define WAIT_ABANDONED 0x80 ;((STATUS_ABANDONED_WAIT_0 ) + 0 ) +!define WAIT_ABANDONED_0 0x80 ;((STATUS_ABANDONED_WAIT_0 ) + 0 ) + +!define DRIVE_UNKNOWN 0 +!define DRIVE_NO_ROOT_DIR 1 +!define DRIVE_REMOVABLE 2 +!define DRIVE_FIXED 3 +!define DRIVE_REMOTE 4 +!define DRIVE_CDROM 5 +!define DRIVE_RAMDISK 6 + +!define FILE_TYPE_UNKNOWN 0x0000 +!define FILE_TYPE_DISK 0x0001 +!define FILE_TYPE_CHAR 0x0002 +!define FILE_TYPE_PIPE 0x0003 +!define FILE_TYPE_REMOTE 0x8000 + +!define STD_INPUT_HANDLE -10 +!define STD_OUTPUT_HANDLE -11 +!define STD_ERROR_HANDLE -12 + +#define IGNORE 0 ; Ignore signal +!define INFINITE 0xFFFFFFFF ; Infinite timeout + +!endif /* __WIN_NOINC_WINBASE */ + + + + + +/************************************************** +WinGDI.h +**************************************************/ +!ifndef __WIN_MS_NOGDI & __WIN_NOINC_WINGDI +!define HORZRES 8 +!define VERTRES 10 +!define BITSPIXEL 12 +!define LOGPIXELSX 88 +!define LOGPIXELSY 90 +!define COLORRES 108 +!define VREFRESH 116 +!define DESKTOPVERTRES 117 +!define DESKTOPHORZRES 118 +!endif /* __WIN_MS_NOGDI & __WIN_NOINC_WINGDI */ + + + + + +/************************************************** +WinReg.h +**************************************************/ +!ifndef __WIN_NOINC_WINREG +!ifndef __WIN_NOHKEY & HKEY_CLASSES_ROOT & HKEY_CURRENT_USER & HKEY_LOCAL_MACHINE & HKEY_USERS +!define HKEY_CLASSES_ROOT 0x80000000 +!define HKEY_CURRENT_USER 0x80000001 +!define HKEY_LOCAL_MACHINE 0x80000002 +!define HKEY_USERS 0x80000003 +!define HKEY_PERFORMANCE_DATA 0x80000004 +!define HKEY_PERFORMANCE_TEXT 0x80000050 +!define HKEY_PERFORMANCE_NLSTEXT 0x80000060 +!define HKEY_CURRENT_CONFIG 0x80000005 +!define HKEY_DYN_DATA 0x80000006 +!ifndef __WIN_NOSHORTHKEY & HKCR & HKCU & HKLM +!define HKCR ${HKEY_CLASSES_ROOT} +!define HKCU ${HKEY_CURRENT_USER} +!define HKLM ${HKEY_LOCAL_MACHINE} +!endif +!endif +!endif /* __WIN_NOINC_WINREG */ + + + + + +/************************************************** +WindowsX.h +**************************************************/ +!ifndef __WIN_NOINC_WINDOWSX +!ifndef GET_X_LPARAM & GET_Y_LPARAM +!macro _Win_GET_X_LPARAM _outvar _in +IntOp ${_outvar} "${_in}" << 16 ;We can't just use LOWORD, we need to keep the sign, +IntOp ${_outvar} ${_outvar} >> 16 ;so we let NSIS sign extend for us +!macroend +!define GET_X_LPARAM "!insertmacro _Win_GET_X_LPARAM " +!macro _Win_GET_Y_LPARAM _outvar _in +IntOp ${_outvar} "${_in}" >> 16 +!macroend +!define GET_Y_LPARAM "!insertmacro _Win_GET_Y_LPARAM " +!endif +!endif /* __WIN_NOINC_WINDOWSX */ + + + + + +/************************************************** +ShlObj.h +**************************************************/ +!ifndef __WIN_NOINC_SHLOBJ +!ifndef __WIN_NOSHELLFOLDERCSIDL +!define CSIDL_DESKTOP 0x0000 +!define CSIDL_INTERNET 0x0001 ;Internet Explorer (icon on desktop) +!define CSIDL_PROGRAMS 0x0002 ;Start Menu\Programs +!define CSIDL_CONTROLS 0x0003 ;My Computer\Control Panel +!define CSIDL_PRINTERS 0x0004 ;My Computer\Printers +!define CSIDL_PERSONAL 0x0005 ;My Documents +!define CSIDL_FAVORITES 0x0006 ;\Favorites +!define CSIDL_STARTUP 0x0007 ;Start Menu\Programs\Startup +!define CSIDL_RECENT 0x0008 ;\Recent +!define CSIDL_SENDTO 0x0009 ;\SendTo +!define CSIDL_BITBUCKET 0x000a ;\Recycle Bin +!define CSIDL_STARTMENU 0x000b ;\Start Menu +!define CSIDL_MYDOCUMENTS 0x000c ;logical "My Documents" desktop icon +!define CSIDL_MYMUSIC 0x000d ;"My Music" folder +!define CSIDL_MYVIDEO 0x000e ;"My Videos" folder +!define CSIDL_DESKTOPDIRECTORY 0x0010 ;\Desktop +!define CSIDL_DRIVES 0x0011 ;My Computer +!define CSIDL_NETWORK 0x0012 ;Network Neighborhood +!define CSIDL_NETHOOD 0x0013 ;\nethood +!define CSIDL_FONTS 0x0014 ;windows\fonts +!define CSIDL_TEMPLATES 0x0015 +!define CSIDL_COMMON_STARTMENU 0x0016 ;All Users\Start Menu +!define CSIDL_COMMON_PROGRAMS 0x0017 ;All Users\Start Menu\Programs +!define CSIDL_COMMON_STARTUP 0x0018 ;All Users\Startup +!define CSIDL_COMMON_DESKTOPDIRECTORY 0x0019 ;All Users\Desktop +!define CSIDL_APPDATA 0x001a ;\Application Data +!define CSIDL_PRINTHOOD 0x001b ;\PrintHood +!define CSIDL_LOCAL_APPDATA 0x001c ;\Local Settings\Applicaiton Data (non roaming) +!define CSIDL_ALTSTARTUP 0x001d ;non localized startup +!define CSIDL_COMMON_ALTSTARTUP 0x001e ;non localized common startup +!define CSIDL_COMMON_FAVORITES 0x001f +!define CSIDL_INTERNET_CACHE 0x0020 +!define CSIDL_COOKIES 0x0021 +!define CSIDL_HISTORY 0x0022 +!define CSIDL_COMMON_APPDATA 0x0023 ;All Users\Application Data +!define CSIDL_WINDOWS 0x0024 ;GetWindowsDirectory +!define CSIDL_SYSTEM 0x0025 ;GetSystemDirectory +!define CSIDL_PROGRAM_FILES 0x0026 ;C:\Program Files +!define CSIDL_MYPICTURES 0x0027 +!define CSIDL_PROFILE 0x0028 ;USERPROFILE +!define CSIDL_SYSTEMX86 0x0029 ;x86 system directory on RISC +!define CSIDL_PROGRAM_FILESX86 0x002a ;x86 C:\Program Files on RISC +!define CSIDL_PROGRAM_FILES_COMMON 0x002b ;C:\Program Files\Common +!define CSIDL_PROGRAM_FILES_COMMONX86 0x002c ;x86 Program Files\Common on RISC +!define CSIDL_COMMON_TEMPLATES 0x002d ;All Users\Templates +!define CSIDL_COMMON_DOCUMENTS 0x002e ;All Users\Documents +!define CSIDL_COMMON_ADMINTOOLS 0x002f ;All Users\Start Menu\Programs\Administrative Tools +!define CSIDL_ADMINTOOLS 0x0030 ;\Start Menu\Programs\Administrative Tools +!define CSIDL_CONNECTIONS 0x0031 ;Network and Dial-up Connections +!define CSIDL_COMMON_MUSIC 0x0035 ;All Users\My Music +!define CSIDL_COMMON_PICTURES 0x0036 ;All Users\My Pictures +!define CSIDL_COMMON_VIDEO 0x0037 ;All Users\My Video +!define CSIDL_RESOURCES 0x0038 ;Resource Direcotry +!define CSIDL_RESOURCES_LOCALIZED 0x0039 ;Localized Resource Direcotry +!define CSIDL_COMMON_OEM_LINKS 0x003a ;Links to All Users OEM specific apps +!define CSIDL_CDBURN_AREA 0x003b ;USERPROFILE\Local Settings\Application Data\Microsoft\CD Burning +!define CSIDL_COMPUTERSNEARME 0x003d ;Computers Near Me (computered from Workgroup membership) +!define CSIDL_FLAG_CREATE 0x8000 ;combine with CSIDL_ value to force folder creation in SHGetFolderPath() +!define CSIDL_FLAG_DONT_VERIFY 0x4000 ;combine with CSIDL_ value to return an unverified folder path +!define CSIDL_FLAG_NO_ALIAS 0x1000 ;combine with CSIDL_ value to insure non-alias versions of the pidl +!define CSIDL_FLAG_PER_USER_INIT 0x0800 ;combine with CSIDL_ value to indicate per-user init (eg. upgrade) +!define CSIDL_FLAG_MASK 0xFF00 +!endif /* __WIN_NOSHELLFOLDERCSIDL */ +!endif /* __WIN_NOINC_SHLOBJ */ + + + + +!verbose pop +!endif /* __WIN_WINDOWS__INC */ \ No newline at end of file diff --git a/installer/NSIS/Include/WinMessages.nsh b/installer/NSIS/Include/WinMessages.nsh new file mode 100644 index 0000000..e40c269 --- /dev/null +++ b/installer/NSIS/Include/WinMessages.nsh @@ -0,0 +1,592 @@ +/* +_____________________________________________________________________________ + + List of common Windows Messages +_____________________________________________________________________________ + + 2005 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + + +Usage example: +--------------------------------------------------- +Name "Output" +OutFile "Output.exe" + +!include "WinMessages.nsh" + +Section + FindWindow $0 '#32770' '' $HWNDPARENT + GetDlgItem $1 $0 1027 + SendMessage $1 ${WM_SETTEXT} 0 'STR:MyText' +SectionEnd +--------------------------------------------------- + + +Prefix Message category +------------------------- +SW ShowWindow Commands +BM Button control +CB Combo box control +EM Edit control +LB List box control +WM General window +ABM Application desktop toolbar +DBT Device +DM Default push button control +HDM Header control +LVM List view control +SB Status bar window +SBM Scroll bar control +STM Static control +TCM Tab control +PBM Progress bar +----------------------------------- + +NOT included messages (WM_USER + X) +----------------------------------- +CBEM Extended combo box control +CDM Common dialog box +DL Drag list box +DTM Date and time picker control +HKM Hot key control +IPM IP address control +MCM Month calendar control +PGM Pager control +PSM Property sheet +RB Rebar control +TB Toolbar +TBM Trackbar +TTM Tooltip control +TVM Tree-view control +UDM Up-down control +----------------------------------- +*/ + + +!ifndef WINMESSAGES_INCLUDED +!define WINMESSAGES_INCLUDED +!verbose push +!verbose 3 + +!define HWND_BROADCAST 0xFFFF + +#ShowWindow Commands# +!define SW_HIDE 0 +!define SW_SHOWNORMAL 1 +!define SW_NORMAL 1 +!define SW_SHOWMINIMIZED 2 +!define SW_SHOWMAXIMIZED 3 +!define SW_MAXIMIZE 3 +!define SW_SHOWNOACTIVATE 4 +!define SW_SHOW 5 +!define SW_MINIMIZE 6 +!define SW_SHOWMINNOACTIVE 7 +!define SW_SHOWNA 8 +!define SW_RESTORE 9 +!define SW_SHOWDEFAULT 10 +!define SW_FORCEMINIMIZE 11 +!define SW_MAX 11 + +#Button Control Messages# +!define BM_CLICK 0x00F5 +!define BM_GETCHECK 0x00F0 +!define BM_GETIMAGE 0x00F6 +!define BM_GETSTATE 0x00F2 +!define BM_SETCHECK 0x00F1 +!define BM_SETIMAGE 0x00F7 +!define BM_SETSTATE 0x00F3 +!define BM_SETSTYLE 0x00F4 + +!define BST_UNCHECKED 0 +!define BST_CHECKED 1 +!define BST_INDETERMINATE 2 +!define BST_PUSHED 4 +!define BST_FOCUS 8 + +#Combo Box Messages# +!define CB_ADDSTRING 0x0143 +!define CB_DELETESTRING 0x0144 +!define CB_DIR 0x0145 +!define CB_FINDSTRING 0x014C +!define CB_FINDSTRINGEXACT 0x0158 +!define CB_GETCOUNT 0x0146 +!define CB_GETCURSEL 0x0147 +!define CB_GETDROPPEDCONTROLRECT 0x0152 +!define CB_GETDROPPEDSTATE 0x0157 +!define CB_GETDROPPEDWIDTH 0x015f +!define CB_GETEDITSEL 0x0140 +!define CB_GETEXTENDEDUI 0x0156 +!define CB_GETHORIZONTALEXTENT 0x015d +!define CB_GETITEMDATA 0x0150 +!define CB_GETITEMHEIGHT 0x0154 +!define CB_GETLBTEXT 0x0148 +!define CB_GETLBTEXTLEN 0x0149 +!define CB_GETLOCALE 0x015A +!define CB_GETTOPINDEX 0x015b +!define CB_INITSTORAGE 0x0161 +!define CB_INSERTSTRING 0x014A +!define CB_LIMITTEXT 0x0141 +!define CB_MSGMAX 0x015B # 0x0162 0x0163 +!define CB_MULTIPLEADDSTRING 0x0163 +!define CB_RESETCONTENT 0x014B +!define CB_SELECTSTRING 0x014D +!define CB_SETCURSEL 0x014E +!define CB_SETDROPPEDWIDTH 0x0160 +!define CB_SETEDITSEL 0x0142 +!define CB_SETEXTENDEDUI 0x0155 +!define CB_SETHORIZONTALEXTENT 0x015e +!define CB_SETITEMDATA 0x0151 +!define CB_SETITEMHEIGHT 0x0153 +!define CB_SETLOCALE 0x0159 +!define CB_SETTOPINDEX 0x015c +!define CB_SHOWDROPDOWN 0x014F + +!define CB_ERR -1 + +#Edit Control Messages# +!define EM_CANUNDO 0x00C6 +!define EM_CHARFROMPOS 0x00D7 +!define EM_EMPTYUNDOBUFFER 0x00CD +!define EM_EXLIMITTEXT 0x0435 +!define EM_FMTLINES 0x00C8 +!define EM_GETFIRSTVISIBLELINE 0x00CE +!define EM_GETHANDLE 0x00BD +!define EM_GETIMESTATUS 0x00D9 +!define EM_GETLIMITTEXT 0x00D5 +!define EM_GETLINE 0x00C4 +!define EM_GETLINECOUNT 0x00BA +!define EM_GETMARGINS 0x00D4 +!define EM_GETMODIFY 0x00B8 +!define EM_GETPASSWORDCHAR 0x00D2 +!define EM_GETRECT 0x00B2 +!define EM_GETSEL 0x00B0 +!define EM_GETTHUMB 0x00BE +!define EM_GETWORDBREAKPROC 0x00D1 +!define EM_LIMITTEXT 0x00C5 +!define EM_LINEFROMCHAR 0x00C9 +!define EM_LINEINDEX 0x00BB +!define EM_LINELENGTH 0x00C1 +!define EM_LINESCROLL 0x00B6 +!define EM_POSFROMCHAR 0x00D6 +!define EM_REPLACESEL 0x00C2 +!define EM_SCROLL 0x00B5 +!define EM_SCROLLCARET 0x00B7 +!define EM_SETHANDLE 0x00BC +!define EM_SETIMESTATUS 0x00D8 +!define EM_SETLIMITTEXT 0x00C5 # Same as EM_LIMITTEXT +!define EM_SETMARGINS 0x00D3 +!define EM_SETMODIFY 0x00B9 +!define EM_SETPASSWORDCHAR 0x00CC +!define EM_SETREADONLY 0x00CF +!define EM_SETRECT 0x00B3 +!define EM_SETRECTNP 0x00B4 +!define EM_SETSEL 0x00B1 +!define EM_SETTABSTOPS 0x00CB +!define EM_SETWORDBREAKPROC 0x00D0 +!define EM_UNDO 0x00C7 + +#Listbox Messages# +!define LB_ADDFILE 0x0196 +!define LB_ADDSTRING 0x0180 +!define LB_DELETESTRING 0x0182 +!define LB_DIR 0x018D +!define LB_FINDSTRING 0x018F +!define LB_FINDSTRINGEXACT 0x01A2 +!define LB_GETANCHORINDEX 0x019D +!define LB_GETCARETINDEX 0x019F +!define LB_GETCOUNT 0x018B +!define LB_GETCURSEL 0x0188 +!define LB_GETHORIZONTALEXTENT 0x0193 +!define LB_GETITEMDATA 0x0199 +!define LB_GETITEMHEIGHT 0x01A1 +!define LB_GETITEMRECT 0x0198 +!define LB_GETLOCALE 0x01A6 +!define LB_GETSEL 0x0187 +!define LB_GETSELCOUNT 0x0190 +!define LB_GETSELITEMS 0x0191 +!define LB_GETTEXT 0x0189 +!define LB_GETTEXTLEN 0x018A +!define LB_GETTOPINDEX 0x018E +!define LB_INITSTORAGE 0x01A8 +!define LB_INSERTSTRING 0x0181 +!define LB_ITEMFROMPOINT 0x01A9 +!define LB_MSGMAX 0x01A8 # 0x01B0 0x01B1 +!define LB_MULTIPLEADDSTRING 0x01B1 +!define LB_RESETCONTENT 0x0184 +!define LB_SELECTSTRING 0x018C +!define LB_SELITEMRANGE 0x019B +!define LB_SELITEMRANGEEX 0x0183 +!define LB_SETANCHORINDEX 0x019C +!define LB_SETCARETINDEX 0x019E +!define LB_SETCOLUMNWIDTH 0x0195 +!define LB_SETCOUNT 0x01A7 +!define LB_SETCURSEL 0x0186 +!define LB_SETHORIZONTALEXTENT 0x0194 +!define LB_SETITEMDATA 0x019A +!define LB_SETITEMHEIGHT 0x01A0 +!define LB_SETLOCALE 0x01A5 +!define LB_SETSEL 0x0185 +!define LB_SETTABSTOPS 0x0192 +!define LB_SETTOPINDEX 0x0197 + +!define LB_ERR -1 + +#Window Messages# +!define WM_ACTIVATE 0x0006 +!define WM_ACTIVATEAPP 0x001C +!define WM_AFXFIRST 0x0360 +!define WM_AFXLAST 0x037F +!define WM_APP 0x8000 +!define WM_APPCOMMAND 0x0319 +!define WM_ASKCBFORMATNAME 0x030C +!define WM_CANCELJOURNAL 0x004B +!define WM_CANCELMODE 0x001F +!define WM_CAPTURECHANGED 0x0215 +!define WM_CHANGECBCHAIN 0x030D +!define WM_CHANGEUISTATE 0x0127 +!define WM_CHAR 0x0102 +!define WM_CHARTOITEM 0x002F +!define WM_CHILDACTIVATE 0x0022 +!define WM_CLEAR 0x0303 +!define WM_CLOSE 0x0010 +!define WM_COMMAND 0x0111 +!define WM_COMMNOTIFY 0x0044 # no longer suported +!define WM_COMPACTING 0x0041 +!define WM_COMPAREITEM 0x0039 +!define WM_CONTEXTMENU 0x007B +!define WM_CONVERTREQUESTEX 0x108 +!define WM_COPY 0x0301 +!define WM_COPYDATA 0x004A +!define WM_CREATE 0x0001 +!define WM_CTLCOLOR 0x0019 +!define WM_CTLCOLORBTN 0x0135 +!define WM_CTLCOLORDLG 0x0136 +!define WM_CTLCOLOREDIT 0x0133 +!define WM_CTLCOLORLISTBOX 0x0134 +!define WM_CTLCOLORMSGBOX 0x0132 +!define WM_CTLCOLORSCROLLBAR 0x0137 +!define WM_CTLCOLORSTATIC 0x0138 +!define WM_CUT 0x0300 +!define WM_DDE_FIRST 0x3E0 +!define WM_DEADCHAR 0x0103 +!define WM_DELETEITEM 0x002D +!define WM_DESTROY 0x0002 +!define WM_DESTROYCLIPBOARD 0x0307 +!define WM_DEVICECHANGE 0x0219 +!define WM_DEVMODECHANGE 0x001B +!define WM_DISPLAYCHANGE 0x007E +!define WM_DRAWCLIPBOARD 0x0308 +!define WM_DRAWITEM 0x002B +!define WM_DROPFILES 0x0233 +!define WM_ENABLE 0x000A +!define WM_ENDSESSION 0x0016 +!define WM_ENTERIDLE 0x0121 +!define WM_ENTERMENULOOP 0x0211 +!define WM_ENTERSIZEMOVE 0x0231 +!define WM_ERASEBKGND 0x0014 +!define WM_EXITMENULOOP 0x0212 +!define WM_EXITSIZEMOVE 0x0232 +!define WM_FONTCHANGE 0x001D +!define WM_GETDLGCODE 0x0087 +!define WM_GETFONT 0x0031 +!define WM_GETHOTKEY 0x0033 +!define WM_GETICON 0x007F +!define WM_GETMINMAXINFO 0x0024 +!define WM_GETOBJECT 0x003D +!define WM_GETTEXT 0x000D +!define WM_GETTEXTLENGTH 0x000E +!define WM_HANDHELDFIRST 0x0358 +!define WM_HANDHELDLAST 0x035F +!define WM_HELP 0x0053 +!define WM_HOTKEY 0x0312 +!define WM_HSCROLL 0x0114 +!define WM_HSCROLLCLIPBOARD 0x030E +!define WM_ICONERASEBKGND 0x0027 +!define WM_IME_CHAR 0x0286 +!define WM_IME_COMPOSITION 0x010F +!define WM_IME_COMPOSITIONFULL 0x0284 +!define WM_IME_CONTROL 0x0283 +!define WM_IME_ENDCOMPOSITION 0x010E +!define WM_IME_KEYDOWN 0x0290 +!define WM_IME_KEYLAST 0x010F +!define WM_IME_KEYUP 0x0291 +!define WM_IME_NOTIFY 0x0282 +!define WM_IME_REQUEST 0x0288 +!define WM_IME_SELECT 0x0285 +!define WM_IME_SETCONTEXT 0x0281 +!define WM_IME_STARTCOMPOSITION 0x010D +!define WM_INITDIALOG 0x0110 +!define WM_INITMENU 0x0116 +!define WM_INITMENUPOPUP 0x0117 +!define WM_INPUT 0x00FF +!define WM_INPUTLANGCHANGE 0x0051 +!define WM_INPUTLANGCHANGEREQUEST 0x0050 +!define WM_KEYDOWN 0x0100 +!define WM_KEYFIRST 0x0100 +!define WM_KEYLAST 0x0108 +!define WM_KEYUP 0x0101 +!define WM_KILLFOCUS 0x0008 +!define WM_LBUTTONDBLCLK 0x0203 +!define WM_LBUTTONDOWN 0x0201 +!define WM_LBUTTONUP 0x0202 +!define WM_MBUTTONDBLCLK 0x0209 +!define WM_MBUTTONDOWN 0x0207 +!define WM_MBUTTONUP 0x0208 +!define WM_MDIACTIVATE 0x0222 +!define WM_MDICASCADE 0x0227 +!define WM_MDICREATE 0x0220 +!define WM_MDIDESTROY 0x0221 +!define WM_MDIGETACTIVE 0x0229 +!define WM_MDIICONARRANGE 0x0228 +!define WM_MDIMAXIMIZE 0x0225 +!define WM_MDINEXT 0x0224 +!define WM_MDIREFRESHMENU 0x0234 +!define WM_MDIRESTORE 0x0223 +!define WM_MDISETMENU 0x0230 +!define WM_MDITILE 0x0226 +!define WM_MEASUREITEM 0x002C +!define WM_MENUCHAR 0x0120 +!define WM_MENUCOMMAND 0x0126 +!define WM_MENUDRAG 0x0123 +!define WM_MENUGETOBJECT 0x0124 +!define WM_MENURBUTTONUP 0x0122 +!define WM_MENUSELECT 0x011F +!define WM_MOUSEACTIVATE 0x0021 +!define WM_MOUSEFIRST 0x0200 +!define WM_MOUSEHOVER 0x02A1 +!define WM_MOUSELAST 0x0209 # 0x020A 0x020D +!define WM_MOUSELEAVE 0x02A3 +!define WM_MOUSEMOVE 0x0200 +!define WM_MOUSEWHEEL 0x020A +!define WM_MOVE 0x0003 +!define WM_MOVING 0x0216 +!define WM_NCACTIVATE 0x0086 +!define WM_NCCALCSIZE 0x0083 +!define WM_NCCREATE 0x0081 +!define WM_NCDESTROY 0x0082 +!define WM_NCHITTEST 0x0084 +!define WM_NCLBUTTONDBLCLK 0x00A3 +!define WM_NCLBUTTONDOWN 0x00A1 +!define WM_NCLBUTTONUP 0x00A2 +!define WM_NCMBUTTONDBLCLK 0x00A9 +!define WM_NCMBUTTONDOWN 0x00A7 +!define WM_NCMBUTTONUP 0x00A8 +!define WM_NCMOUSEHOVER 0x02A0 +!define WM_NCMOUSELEAVE 0x02A2 +!define WM_NCMOUSEMOVE 0x00A0 +!define WM_NCPAINT 0x0085 +!define WM_NCRBUTTONDBLCLK 0x00A6 +!define WM_NCRBUTTONDOWN 0x00A4 +!define WM_NCRBUTTONUP 0x00A5 +!define WM_NCXBUTTONDBLCLK 0x00AD +!define WM_NCXBUTTONDOWN 0x00AB +!define WM_NCXBUTTONUP 0x00AC +!define WM_NEXTDLGCTL 0x0028 +!define WM_NEXTMENU 0x0213 +!define WM_NOTIFY 0x004E +!define WM_NOTIFYFORMAT 0x0055 +!define WM_NULL 0x0000 +!define WM_PAINT 0x000F +!define WM_PAINTCLIPBOARD 0x0309 +!define WM_PAINTICON 0x0026 +!define WM_PALETTECHANGED 0x0311 +!define WM_PALETTEISCHANGING 0x0310 +!define WM_PARENTNOTIFY 0x0210 +!define WM_PASTE 0x0302 +!define WM_PENWINFIRST 0x0380 +!define WM_PENWINLAST 0x038F +!define WM_POWER 0x0048 +!define WM_POWERBROADCAST 0x0218 +!define WM_PRINT 0x0317 +!define WM_PRINTCLIENT 0x0318 +!define WM_QUERYDRAGICON 0x0037 +!define WM_QUERYENDSESSION 0x0011 +!define WM_QUERYNEWPALETTE 0x030F +!define WM_QUERYOPEN 0x0013 +!define WM_QUERYUISTATE 0x0129 +!define WM_QUEUESYNC 0x0023 +!define WM_QUIT 0x0012 +!define WM_RBUTTONDBLCLK 0x0206 +!define WM_RBUTTONDOWN 0x0204 +!define WM_RBUTTONUP 0x0205 +!define WM_RASDIALEVENT 0xCCCD +!define WM_RENDERALLFORMATS 0x0306 +!define WM_RENDERFORMAT 0x0305 +!define WM_SETCURSOR 0x0020 +!define WM_SETFOCUS 0x0007 +!define WM_SETFONT 0x0030 +!define WM_SETHOTKEY 0x0032 +!define WM_SETICON 0x0080 +!define WM_SETREDRAW 0x000B +!define WM_SETTEXT 0x000C +!define WM_SETTINGCHANGE 0x001A # Same as WM_WININICHANGE +!define WM_SHOWWINDOW 0x0018 +!define WM_SIZE 0x0005 +!define WM_SIZECLIPBOARD 0x030B +!define WM_SIZING 0x0214 +!define WM_SPOOLERSTATUS 0x002A +!define WM_STYLECHANGED 0x007D +!define WM_STYLECHANGING 0x007C +!define WM_SYNCPAINT 0x0088 +!define WM_SYSCHAR 0x0106 +!define WM_SYSCOLORCHANGE 0x0015 +!define WM_SYSCOMMAND 0x0112 +!define WM_SYSDEADCHAR 0x0107 +!define WM_SYSKEYDOWN 0x0104 +!define WM_SYSKEYUP 0x0105 +!define WM_TABLET_FIRST 0x02C0 +!define WM_TABLET_LAST 0x02DF +!define WM_THEMECHANGED 0x031A +!define WM_TCARD 0x0052 +!define WM_TIMECHANGE 0x001E +!define WM_TIMER 0x0113 +!define WM_UNDO 0x0304 +!define WM_UNICHAR 0x0109 +!define WM_UNINITMENUPOPUP 0x0125 +!define WM_UPDATEUISTATE 0x0128 +!define WM_USER 0x400 +!define WM_USERCHANGED 0x0054 +!define WM_VKEYTOITEM 0x002E +!define WM_VSCROLL 0x0115 +!define WM_VSCROLLCLIPBOARD 0x030A +!define WM_WINDOWPOSCHANGED 0x0047 +!define WM_WINDOWPOSCHANGING 0x0046 +!define WM_WININICHANGE 0x001A +!define WM_WTSSESSION_CHANGE 0x02B1 +!define WM_XBUTTONDBLCLK 0x020D +!define WM_XBUTTONDOWN 0x020B +!define WM_XBUTTONUP 0x020C + + +#Application desktop toolbar# +!define ABM_ACTIVATE 0x00000006 # lParam == TRUE/FALSE means activate/deactivate +!define ABM_GETAUTOHIDEBAR 0x00000007 +!define ABM_GETSTATE 0x00000004 +!define ABM_GETTASKBARPOS 0x00000005 +!define ABM_NEW 0x00000000 +!define ABM_QUERYPOS 0x00000002 +!define ABM_REMOVE 0x00000001 +!define ABM_SETAUTOHIDEBAR 0x00000008 # This can fail, you MUST check the result +!define ABM_SETPOS 0x00000003 +!define ABM_WINDOWPOSCHANGED 0x0000009 + +#Device# +!define DBT_APPYBEGIN 0x0000 +!define DBT_APPYEND 0x0001 +!define DBT_CONFIGCHANGECANCELED 0x0019 +!define DBT_CONFIGCHANGED 0x0018 +!define DBT_CONFIGMGAPI32 0x0022 +!define DBT_CONFIGMGPRIVATE 0x7FFF +!define DBT_CUSTOMEVENT 0x8006 # User-defined event +!define DBT_DEVICEARRIVAL 0x8000 # System detected a new device +!define DBT_DEVICEQUERYREMOVE 0x8001 # Wants to remove, may fail +!define DBT_DEVICEQUERYREMOVEFAILED 0x8002 # Removal aborted +!define DBT_DEVICEREMOVECOMPLETE 0x8004 # Device is gone +!define DBT_DEVICEREMOVEPENDING 0x8003 # About to remove, still avail. +!define DBT_DEVICETYPESPECIFIC 0x8005 # Type specific event +!define DBT_DEVNODES_CHANGED 0x0007 +!define DBT_DEVTYP_DEVICEINTERFACE 0x00000005 # Device interface class +!define DBT_DEVTYP_DEVNODE 0x00000001 # Devnode number +!define DBT_DEVTYP_HANDLE 0x00000006 # File system handle +!define DBT_DEVTYP_NET 0x00000004 # Network resource +!define DBT_DEVTYP_OEM 0x00000000 # Oem-defined device type +!define DBT_DEVTYP_PORT 0x00000003 # Serial, parallel +!define DBT_DEVTYP_VOLUME 0x00000002 # Logical volume +!define DBT_LOW_DISK_SPACE 0x0048 +!define DBT_MONITORCHANGE 0x001B +!define DBT_NO_DISK_SPACE 0x0047 +!define DBT_QUERYCHANGECONFIG 0x0017 +!define DBT_SHELLLOGGEDON 0x0020 +!define DBT_USERDEFINED 0xFFFF +!define DBT_VOLLOCKLOCKFAILED 0x8043 +!define DBT_VOLLOCKLOCKRELEASED 0x8045 +!define DBT_VOLLOCKLOCKTAKEN 0x8042 +!define DBT_VOLLOCKQUERYLOCK 0x8041 +!define DBT_VOLLOCKQUERYUNLOCK 0x8044 +!define DBT_VOLLOCKUNLOCKFAILED 0x8046 +!define DBT_VPOWERDAPI 0x8100 # VPOWERD API for Win95 +!define DBT_VXDINITCOMPLETE 0x0023 + +#Default push button control# +!define DM_BITSPERPEL 0x00040000 +!define DM_COLLATE 0x00008000 +!define DM_COLOR 0x00000800 +!define DM_COPIES 0x00000100 +!define DM_DEFAULTSOURCE 0x00000200 +!define DM_DISPLAYFLAGS 0x00200000 +!define DM_DISPLAYFREQUENCY 0x00400000 +!define DM_DITHERTYPE 0x04000000 +!define DM_DUPLEX 0x00001000 +!define DM_FORMNAME 0x00010000 +!define DM_GRAYSCALE 0x00000001 # This flag is no longer valid +!define DM_ICMINTENT 0x01000000 +!define DM_ICMMETHOD 0x00800000 +!define DM_INTERLACED 0x00000002 # This flag is no longer valid +!define DM_LOGPIXELS 0x00020000 +!define DM_MEDIATYPE 0x02000000 +!define DM_NUP 0x00000040 +!define DM_ORIENTATION 0x00000001 +!define DM_PANNINGHEIGHT 0x10000000 +!define DM_PANNINGWIDTH 0x08000000 +!define DM_PAPERLENGTH 0x00000004 +!define DM_PAPERSIZE 0x00000002 +!define DM_PAPERWIDTH 0x00000008 +!define DM_PELSHEIGHT 0x00100000 +!define DM_PELSWIDTH 0x00080000 +!define DM_POSITION 0x00000020 +!define DM_PRINTQUALITY 0x00000400 +!define DM_SCALE 0x00000010 +!define DM_SPECVERSION 0x0320 # 0x0400 0x0401 +!define DM_TTOPTION 0x00004000 +!define DM_YRESOLUTION 0x00002000 + +#Header control# +!define HDM_FIRST 0x1200 + +#List view control# +!define LVM_FIRST 0x1000 + +#Status bar window# +!define SB_CONST_ALPHA 0x00000001 +!define SB_GRAD_RECT 0x00000010 +!define SB_GRAD_TRI 0x00000020 +!define SB_NONE 0x00000000 +!define SB_PIXEL_ALPHA 0x00000002 +!define SB_PREMULT_ALPHA 0x00000004 +!define SB_SIMPLEID 0x00ff + +#Scroll bar control# +!define SBM_ENABLE_ARROWS 0x00E4 # Not in win3.1 +!define SBM_GETPOS 0x00E1 # Not in win3.1 +!define SBM_GETRANGE 0x00E3 # Not in win3.1 +!define SBM_GETSCROLLINFO 0x00EA +!define SBM_SETPOS 0x00E0 # Not in win3.1 +!define SBM_SETRANGE 0x00E2 # Not in win3.1 +!define SBM_SETRANGEREDRAW 0x00E6 # Not in win3.1 +!define SBM_SETSCROLLINFO 0x00E9 + +#Static control# +!define STM_GETICON 0x0171 +!define STM_GETIMAGE 0x0173 +!define STM_MSGMAX 0x0174 +!define STM_ONLY_THIS_INTERFACE 0x00000001 +!define STM_ONLY_THIS_NAME 0x00000008 +!define STM_ONLY_THIS_PROTOCOL 0x00000002 +!define STM_ONLY_THIS_TYPE 0x00000004 +!define STM_SETICON 0x0170 +!define STM_SETIMAGE 0x0172 + +#Tab control# +!define TCM_FIRST 0x1300 + +#Progress bar control# +!define PBM_SETRANGE 0x0401 +!define PBM_SETPOS 0x0402 +!define PBM_DELTAPOS 0x0403 +!define PBM_SETSTEP 0x0404 +!define PBM_STEPIT 0x0405 +!define PBM_GETPOS 0x0408 +!define PBM_SETMARQUEE 0x040a + +!verbose pop +!endif \ No newline at end of file diff --git a/installer/NSIS/Include/WinVer.nsh b/installer/NSIS/Include/WinVer.nsh new file mode 100644 index 0000000..cc860c1 --- /dev/null +++ b/installer/NSIS/Include/WinVer.nsh @@ -0,0 +1,480 @@ +; --------------------- +; WinVer.nsh +; --------------------- +; +; LogicLib extensions for handling Windows versions and service packs. +; +; IsNT checks if the installer is running on Windows NT family (NT4, 2000, XP, etc.) +; +; ${If} ${IsNT} +; DetailPrint "Running on NT. Installing Unicode enabled application." +; ${Else} +; DetailPrint "Not running on NT. Installing ANSI application." +; ${EndIf} +; +; IsServer checks if the installer is running on a server version of Windows (NT4, 2003, 2008, etc.) +; +; AtLeastWin checks if the installer is running on Windows version at least as specified. +; IsWin checks if the installer is running on Windows version exactly as specified. +; AtMostWin checks if the installer is running on Windows version at most as specified. +; +; can be replaced with the following values: +; +; 95 +; 98 +; ME +; +; NT4 +; 2000 +; XP +; 2003 +; Vista +; 2008 +; 7 +; 2008R2 +; +; AtLeastServicePack checks if the installer is running on Windows service pack version at least as specified. +; IsServicePack checks if the installer is running on Windows service pack version exactly as specified. +; AtMostServicePack checks if the installer is running on Windows service version pack at most as specified. +; +; Usage examples: +; +; ${If} ${IsNT} +; DetailPrint "Running on NT family." +; DetailPrint "Surely not running on 95, 98 or ME." +; ${AndIf} ${AtLeastWinNT4} +; DetailPrint "Running on NT4 or better. Could even be 2003." +; ${EndIf} +; +; ${If} ${AtLeastWinXP} +; DetailPrint "Running on XP or better." +; ${EndIf} +; +; ${If} ${IsWin2000} +; DetailPrint "Running on 2000." +; ${EndIf} +; +; ${If} ${IsWin2000} +; ${AndIf} ${AtLeastServicePack} 3 +; ${OrIf} ${AtLeastWinXP} +; DetailPrint "Running Win2000 SP3 or above" +; ${EndIf} +; +; ${If} ${AtMostWinXP} +; DetailPrint "Running on XP or older. Surely not running on Vista. Maybe 98, or even 95." +; ${EndIf} +; +; Warning: +; +; Windows 95 and NT both use the same version number. To avoid getting NT4 misidentified +; as Windows 95 and vice-versa or 98 as a version higher than NT4, always use IsNT to +; check if running on the NT family. +; +; ${If} ${AtLeastWin95} +; ${And} ${AtMostWinME} +; DetailPrint "Running 95, 98 or ME." +; DetailPrint "Actually, maybe it's NT4?" +; ${If} ${IsNT} +; DetailPrint "Yes, it's NT4! oops..." +; ${Else} +; DetailPrint "Nope, not NT4. phew..." +; ${EndIf} +; ${EndIf} +; +; +; Other useful extensions are: +; +; * IsWin2003R2 +; * IsStarterEdition +; * OSHasMediaCenter +; * OSHasTabletSupport +; + +!verbose push +!verbose 3 + +!ifndef ___WINVER__NSH___ +!define ___WINVER__NSH___ + +!include LogicLib.nsh +!include Util.nsh + +# masks for our variables + +!define _WINVER_VERXBIT 0x00000001 +!define _WINVER_MASKVMAJ 0x7F000000 +!define _WINVER_MASKVMIN 0x00FF0000 + +!define _WINVER_NTBIT 0x80000000 +!define _WINVER_NTMASK 0x7FFFFFFF +!define _WINVER_NTSRVBIT 0x40000000 +!define _WINVER_MASKVBLD 0x0000FFFF +!define _WINVER_MASKSP 0x000F0000 + +# possible variable values for different versions + +!define WINVER_95_NT 0x04000000 ;4.00.0950 +!define WINVER_95 0x04000000 ;4.00.0950 +!define WINVER_98_NT 0x040a0000 ;4.10.1998 +!define WINVER_98 0x040a0000 ;4.10.1998 +;define WINVER_98SE 0x040a0000 ;4.10.2222 +!define WINVER_ME_NT 0x045a0000 ;4.90.3000 +!define WINVER_ME 0x045a0000 ;4.90.3000 +;define WINVER_NT3d51 ;3.51.1057 +!define WINVER_NT4_NT 0x84000000 ;4.00.1381 +!define WINVER_NT4 0x04000000 ;4.00.1381 +!define WINVER_2000_NT 0x85000000 ;5.00.2195 +!define WINVER_2000 0x05000000 ;5.00.2195 +!define WINVER_XP_NT 0x85010000 ;5.01.2600 +!define WINVER_XP 0x05010000 ;5.01.2600 +;define WINVER_XP64 ;5.02.3790 +!define WINVER_2003_NT 0x85020000 ;5.02.3790 +!define WINVER_2003 0x05020000 ;5.02.3790 +!define WINVER_VISTA_NT 0x86000000 ;6.00.6000 +!define WINVER_VISTA 0x06000000 ;6.00.6000 +!define WINVER_2008_NT 0x86000001 ;6.00.6001 +!define WINVER_2008 0x06000001 ;6.00.6001 +!define WINVER_7_NT 0x86010000 ;6.01.???? +!define WINVER_7 0x06010000 ;6.01.???? +!define WINVER_2008R2_NT 0x86010001 ;6.01.???? +!define WINVER_2008R2 0x06010001 ;6.01.???? + + +# use this to make all nt > 9x + +!ifdef WINVER_NT4_OVER_W95 + !define __WINVERTMP ${WINVER_NT4} + !undef WINVER_NT4 + !define /math WINVER_NT4 ${__WINVERTMP} | ${_WINVER_VERXBIT} + !undef __WINVERTMP +!endif + +# some definitions from header files + +!define OSVERSIONINFOA_SIZE 148 +!define OSVERSIONINFOEXA_SIZE 156 +!define VER_PLATFORM_WIN32_NT 2 +!define VER_NT_WORKSTATION 1 + +!define SM_TABLETPC 86 +!define SM_MEDIACENTER 87 +!define SM_STARTER 88 +!define SM_SERVERR2 89 + +# variable declaration + +!macro __WinVer_DeclareVars + + !ifndef __WINVER_VARS_DECLARED + + !define __WINVER_VARS_DECLARED + + Var /GLOBAL __WINVERV + Var /GLOBAL __WINVERSP + + !endif + +!macroend + +# lazy initialization macro + +!ifmacrondef __WinVer_Call_GetVersionEx + + !macro __WinVer_Call_GetVersionEx STRUCT_SIZE + + System::Call '*$0(i ${STRUCT_SIZE})' + System::Call kernel32::GetVersionEx(ir0)i.r3 + + !macroend + +!endif + +!macro __WinVer_InitVars + # variables + !insertmacro __WinVer_DeclareVars + + # only calculate version once + StrCmp $__WINVERV "" _winver_noveryet + Return + _winver_noveryet: + + # push used registers on the stack + Push $0 + Push $1 ;maj + Push $2 ;min + Push $3 ;bld + Push $R0 ;temp + + # allocate memory + System::Alloc ${OSVERSIONINFOEXA_SIZE} + Pop $0 + + # use OSVERSIONINFOEX + !insertmacro __WinVer_Call_GetVersionEx ${OSVERSIONINFOEXA_SIZE} + + IntCmp $3 0 "" _winver_ex _winver_ex + # OSVERSIONINFOEX not allowed (Win9x or NT4 w/SP < 6), use OSVERSIONINFO + !insertmacro __WinVer_Call_GetVersionEx ${OSVERSIONINFOA_SIZE} + _winver_ex: + + # get results from struct + System::Call '*$0(i.s,i.r1,i.r2,i.r3,i.s,&t128.s,&i2.s,&i2,&i2,&i1.s,&i1)' + + # free struct + System::Free $0 + + # win9x has major and minor info in high word of dwBuildNumber - remove it + IntOp $3 $3 & 0xFFFF + + # get dwOSVersionInfoSize + Pop $R0 + + # get dwPlatformId + Pop $0 + + # NT? + IntCmp $0 ${VER_PLATFORM_WIN32_NT} "" _winver_notnt _winver_notnt + IntOp $__WINVERSP $__WINVERSP | ${_WINVER_NTBIT} + IntOp $__WINVERV $__WINVERV | ${_WINVER_NTBIT} + _winver_notnt: + + # get service pack information + IntCmp $0 ${VER_PLATFORM_WIN32_NT} _winver_nt "" _winver_nt # win9x + + # get szCSDVersion + Pop $0 + + # copy second char + StrCpy $0 $0 1 1 + + # discard invalid wServicePackMajor and wProductType + Pop $R0 + Pop $R0 + + # switch + StrCmp $0 'A' "" +3 + StrCpy $0 1 + Goto _winver_sp_done + StrCmp $0 'B' "" +3 + StrCpy $0 2 + Goto _winver_sp_done + StrCmp $0 'C' "" +3 + StrCpy $0 3 + Goto _winver_sp_done + StrCpy $0 0 + Goto _winver_sp_done + + _winver_nt: # nt + + IntCmp $R0 ${OSVERSIONINFOEXA_SIZE} "" _winver_sp_noex _winver_sp_noex + + # discard szCSDVersion + Pop $0 + + # get wProductType + Exch + Pop $0 + + # is server? + IntCmp $0 ${VER_NT_WORKSTATION} _winver_noserver _winver_noserver "" + IntOp $__WINVERSP $__WINVERSP | ${_WINVER_NTSRVBIT} + _winver_noserver: + + # get wServicePackMajor + Pop $0 + + # done with sp + Goto _winver_sp_done + + _winver_sp_noex: # OSVERSIONINFO, not OSVERSIONINFOEX + + #### TODO + ## For IsServer to support < NT4SP6, we need to check the registry + ## here to see if we are a server and/or DC + + # get szCSDVersion + Pop $0 + + # discard invalid wServicePackMajor and wProductType + Pop $R0 + Pop $R0 + + # get service pack number from text + StrCpy $R0 $0 13 + StrCmp $R0 "Service Pack " "" +3 + StrCpy $0 $0 "" 13 # cut "Service Pack " + Goto +2 + StrCpy $0 0 # no service pack + +!ifdef WINVER_NT4_OVER_W95 + IntOp $__WINVERV $__WINVERV | ${_WINVER_VERXBIT} +!endif + + _winver_sp_done: + + # store service pack + IntOp $0 $0 << 16 + IntOp $__WINVERSP $__WINVERSP | $0 + + ### now for the version + + # is server? + IntOp $0 $__WINVERSP & ${_WINVER_NTSRVBIT} + + # windows xp x64? + IntCmp $0 0 "" _winver_not_xp_x64 _winver_not_xp_x64 # not server + IntCmp $1 5 "" _winver_not_xp_x64 _winver_not_xp_x64 # maj 5 + IntCmp $2 2 "" _winver_not_xp_x64 _winver_not_xp_x64 # min 2 + # change XP x64 from 5.2 to 5.1 so it's still XP + StrCpy $2 1 + _winver_not_xp_x64: + + # server 2008? + IntCmp $0 0 _winver_not_ntserver # server + IntCmp 6 $1 "" "" _winver_not_ntserver # maj 6 + # extra bit so Server 2008 comes after Vista SP1 that has the same minor version, same for Win7 vs 2008R2 + IntOp $__WINVERV $__WINVERV | ${_WINVER_VERXBIT} + _winver_not_ntserver: + + # pack version + IntOp $1 $1 << 24 # VerMajor + IntOp $__WINVERV $__WINVERV | $1 + IntOp $0 $2 << 16 + IntOp $__WINVERV $__WINVERV | $0 # VerMinor + IntOp $__WINVERSP $__WINVERSP | $3 # VerBuild + + # restore registers + Pop $R0 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + +!macroend + +# version comparison LogicLib macros + +!macro _WinVerAtLeast _a _b _t _f + !insertmacro _LOGICLIB_TEMP + ${CallArtificialFunction} __WinVer_InitVars + IntOp $_LOGICLIB_TEMP $__WINVERV & ${_WINVER_NTMASK} + !insertmacro _>= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}` +!macroend +!macro _WinVerIs _a _b _t _f + ${CallArtificialFunction} __WinVer_InitVars + !insertmacro _= $__WINVERV `${_b}` `${_t}` `${_f}` +!macroend +!macro _WinVerAtMost _a _b _t _f + !insertmacro _LOGICLIB_TEMP + ${CallArtificialFunction} __WinVer_InitVars + IntOp $_LOGICLIB_TEMP $__WINVERV & ${_WINVER_NTMASK} + !insertmacro _<= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}` +!macroend + +!macro __WinVer_DefineOSTest Test OS Suffix + !define ${Test}Win${OS} `"" WinVer${Test} ${WINVER_${OS}${Suffix}}` +!macroend + +!macro __WinVer_DefineOSTests Test Suffix + !insertmacro __WinVer_DefineOSTest ${Test} 95 '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} 98 '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} ME '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} NT4 '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} 2000 '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} XP '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} 2003 '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} VISTA '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} 2008 '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} 7 '${Suffix}' + !insertmacro __WinVer_DefineOSTest ${Test} 2008R2 '${Suffix}' +!macroend + +!insertmacro __WinVer_DefineOSTests AtLeast "" +!insertmacro __WinVer_DefineOSTests Is _NT +!insertmacro __WinVer_DefineOSTests AtMost "" + +# version feature LogicLib macros + +!macro _IsNT _a _b _t _f + !insertmacro _LOGICLIB_TEMP + ${CallArtificialFunction} __WinVer_InitVars + IntOp $_LOGICLIB_TEMP $__WINVERSP & ${_WINVER_NTBIT} + !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}` +!macroend +!define IsNT `"" IsNT ""` + +!macro _IsServerOS _a _b _t _f + !insertmacro _LOGICLIB_TEMP + ${CallArtificialFunction} __WinVer_InitVars + IntOp $_LOGICLIB_TEMP $__WINVERSP & ${_WINVER_NTSRVBIT} + !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}` +!macroend +!define IsServerOS `"" IsServerOS ""` + +# service pack macros + +!macro _WinVer_GetServicePackLevel OUTVAR + ${CallArtificialFunction} __WinVer_InitVars + IntOp ${OUTVAR} $__WINVERSP & ${_WINVER_MASKSP} + IntOp ${OUTVAR} ${OUTVAR} >> 16 +!macroend +!define WinVerGetServicePackLevel '!insertmacro _WinVer_GetServicePackLevel ' + +!macro _AtLeastServicePack _a _b _t _f + !insertmacro _LOGICLIB_TEMP + ${WinVerGetServicePackLevel} $_LOGICLIB_TEMP + !insertmacro _>= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}` +!macroend +!define AtLeastServicePack `"" AtLeastServicePack` + +!macro _AtMostServicePack _a _b _t _f + !insertmacro _LOGICLIB_TEMP + ${WinVerGetServicePackLevel} $_LOGICLIB_TEMP + !insertmacro _<= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}` +!macroend +!define AtMostServicePack `"" AtMostServicePack` + +!macro _IsServicePack _a _b _t _f + !insertmacro _LOGICLIB_TEMP + ${WinVerGetServicePackLevel} $_LOGICLIB_TEMP + !insertmacro _= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}` +!macroend +!define IsServicePack `"" IsServicePack` + +# special feature LogicLib macros + +!macro _WinVer_SysMetricCheck m _b _t _f + !insertmacro _LOGICLIB_TEMP + System::Call user32::GetSystemMetrics(i${m})i.s + pop $_LOGICLIB_TEMP + !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}` +!macroend + +!define IsWin2003R2 `${SM_SERVERR2} WinVer_SysMetricCheck ""` +!define IsStarterEdition `${SM_STARTER} WinVer_SysMetricCheck ""` +!define OSHasMediaCenter `${SM_MEDIACENTER} WinVer_SysMetricCheck ""` +!define OSHasTabletSupport `${SM_TABLETPC} WinVer_SysMetricCheck ""` + +# version retrieval macros + +!macro __WinVer_GetVer var rshift mask outvar + ${CallArtificialFunction} __WinVer_InitVars + !if "${mask}" != "" + IntOp ${outvar} ${var} & ${mask} + !if "${rshift}" != "" + IntOp ${outvar} ${outvar} >> ${rshift} + !endif + !else + IntOp ${outvar} ${var} >> ${rshift} + !endif +!macroend + +!define WinVerGetMajor '!insertmacro __WinVer_GetVer $__WINVERV 24 ${_WINVER_MASKVMAJ}' +!define WinVerGetMinor '!insertmacro __WinVer_GetVer $__WINVERV 16 ${_WINVER_MASKVMIN}' +!define WinVerGetBuild '!insertmacro __WinVer_GetVer $__WINVERSP "" ${_WINVER_MASKVBLD}' + +# done + +!endif # !___WINVER__NSH___ + +!verbose pop diff --git a/installer/NSIS/Include/WordFunc.nsh b/installer/NSIS/Include/WordFunc.nsh new file mode 100644 index 0000000..2e62d40 --- /dev/null +++ b/installer/NSIS/Include/WordFunc.nsh @@ -0,0 +1,1803 @@ +/* +_____________________________________________________________________________ + + Word Functions Header v3.3 +_____________________________________________________________________________ + + 2006 Shengalts Aleksander aka Instructor (Shengalts@mail.ru) + + See documentation for more information about the following functions. + + Usage in script: + 1. !include "WordFunc.nsh" + 2. [Section|Function] + ${WordFunction} "Param1" "Param2" "..." $var + [SectionEnd|FunctionEnd] + + + WordFunction=[WordFind|WordFindS|WordFind2X|WordFind2XS|WordFind3X|WordFind3XS| + WordReplace|WordReplaceS|WordAdd|WordAddS|WordInsert|WordInsertS| + StrFilter|StrFilterS|VersionCompare|VersionConvert] + +_____________________________________________________________________________ + + Thanks to: +_____________________________________________________________________________ + +WordFind3X + Afrow UK (Based on his idea of Function "StrSortLR") +StrFilter + sunjammer (Function "StrUpper") +VersionCompare + Afrow UK (Based on his Function "VersionCheckNew2") +VersionConvert + Afrow UK (Based on his idea of Function "CharIndexReplace") +*/ + + +;_____________________________________________________________________________ +; +; Macros +;_____________________________________________________________________________ +; +; Change log window verbosity (default: 3=no script) +; +; Example: +; !include "WordFunc.nsh" +; !insertmacro WordFind +; ${WORDFUNC_VERBOSE} 4 # all verbosity +; !insertmacro WordReplace +; ${WORDFUNC_VERBOSE} 3 # no script + +!ifndef WORDFUNC_INCLUDED +!define WORDFUNC_INCLUDED + +!include Util.nsh + +!verbose push +!verbose 3 +!ifndef _WORDFUNC_VERBOSE + !define _WORDFUNC_VERBOSE 3 +!endif +!verbose ${_WORDFUNC_VERBOSE} +!define WORDFUNC_VERBOSE `!insertmacro WORDFUNC_VERBOSE` +!verbose pop + +!macro WORDFUNC_VERBOSE _VERBOSE + !verbose push + !verbose 3 + !undef _WORDFUNC_VERBOSE + !define _WORDFUNC_VERBOSE ${_VERBOSE} + !verbose pop +!macroend + + +!macro WordFindCall _ART _STRING _DELIMITER _OPTION _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER}` + Push `${_OPTION}` + ${CallArtificialFunction}${_ART} WordFind_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordFindSCall _ART _STRING _DELIMITER _OPTION _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER}` + Push `${_OPTION}` + ${CallArtificialFunction}${_ART} WordFindS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordFind2XCall _STRING _DELIMITER1 _DELIMITER2 _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER1}` + Push `${_DELIMITER2}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordFind2X_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordFind2XSCall _STRING _DELIMITER1 _DELIMITER2 _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER1}` + Push `${_DELIMITER2}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordFind2XS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordFind3XCall _STRING _DELIMITER1 _CENTER _DELIMITER2 _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER1}` + Push `${_CENTER}` + Push `${_DELIMITER2}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordFind3X_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordFind3XSCall _STRING _DELIMITER1 _CENTER _DELIMITER2 _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER1}` + Push `${_CENTER}` + Push `${_DELIMITER2}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordFind3XS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordReplaceCall _STRING _WORD1 _WORD2 _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_WORD1}` + Push `${_WORD2}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordReplace_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordReplaceSCall _STRING _WORD1 _WORD2 _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_WORD1}` + Push `${_WORD2}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordReplaceS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordAddCall _STRING1 _DELIMITER _STRING2 _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING1}` + Push `${_DELIMITER}` + Push `${_STRING2}` + ${CallArtificialFunction} WordAdd_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordAddSCall _STRING1 _DELIMITER _STRING2 _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING1}` + Push `${_DELIMITER}` + Push `${_STRING2}` + ${CallArtificialFunction} WordAddS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordInsertCall _STRING _DELIMITER _WORD _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER}` + Push `${_WORD}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordInsert_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordInsertSCall _STRING _DELIMITER _WORD _NUMBER _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_DELIMITER}` + Push `${_WORD}` + Push `${_NUMBER}` + ${CallArtificialFunction} WordInsertS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro StrFilterCall _STRING _FILTER _INCLUDE _EXCLUDE _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_FILTER}` + Push `${_INCLUDE}` + Push `${_EXCLUDE}` + ${CallArtificialFunction} StrFilter_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro StrFilterSCall _STRING _FILTER _INCLUDE _EXCLUDE _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_STRING}` + Push `${_FILTER}` + Push `${_INCLUDE}` + Push `${_EXCLUDE}` + ${CallArtificialFunction} StrFilterS_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro VersionCompareCall _VER1 _VER2 _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_VER1}` + Push `${_VER2}` + ${CallArtificialFunction} VersionCompare_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro VersionConvertCall _VERSION _CHARLIST _RESULT + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + Push `${_VERSION}` + Push `${_CHARLIST}` + ${CallArtificialFunction} VersionConvert_ + Pop ${_RESULT} + !verbose pop +!macroend + +!macro WordFindBody _WORDFUNC_S + Exch $1 + Exch + Exch $0 + Exch + Exch 2 + Exch $R0 + Exch 2 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R1 + Push $R2 + ClearErrors + + StrCpy $9 '' + StrCpy $2 $1 1 + StrCpy $1 $1 '' 1 + StrCmp $2 'E' 0 +3 + StrCpy $9 E + goto -4 + + StrCpy $3 '' + StrCmp${_WORDFUNC_S} $2 '+' +6 + StrCmp${_WORDFUNC_S} $2 '-' +5 + StrCmp${_WORDFUNC_S} $2 '/' WordFunc_WordFind${_WORDFUNC_S}_restart + StrCmp${_WORDFUNC_S} $2 '#' WordFunc_WordFind${_WORDFUNC_S}_restart + StrCmp${_WORDFUNC_S} $2 '*' WordFunc_WordFind${_WORDFUNC_S}_restart + goto WordFunc_WordFind${_WORDFUNC_S}_error3 + + StrCpy $4 $1 1 -1 + StrCmp${_WORDFUNC_S} $4 '*' +4 + StrCmp${_WORDFUNC_S} $4 '}' +3 + StrCmp${_WORDFUNC_S} $4 '{' +2 + goto +4 + StrCpy $1 $1 -1 + StrCpy $3 '$4$3' + goto -7 + StrCmp${_WORDFUNC_S} $3 '*' WordFunc_WordFind${_WORDFUNC_S}_error3 + StrCmp${_WORDFUNC_S} $3 '**' WordFunc_WordFind${_WORDFUNC_S}_error3 + StrCmp${_WORDFUNC_S} $3 '}{' WordFunc_WordFind${_WORDFUNC_S}_error3 + IntOp $1 $1 + 0 + StrCmp${_WORDFUNC_S} $1 0 WordFunc_WordFind${_WORDFUNC_S}_error2 + + WordFunc_WordFind${_WORDFUNC_S}_restart: + StrCmp${_WORDFUNC_S} $R0 '' WordFunc_WordFind${_WORDFUNC_S}_error1 + StrCpy $4 0 + StrCpy $5 0 + StrCpy $6 0 + StrLen $7 $0 + goto WordFunc_WordFind${_WORDFUNC_S}_loop + + WordFunc_WordFind${_WORDFUNC_S}_preloop: + IntOp $6 $6 + 1 + + WordFunc_WordFind${_WORDFUNC_S}_loop: + StrCpy $8 $R0 $7 $6 + StrCmp${_WORDFUNC_S} $8$5 0 WordFunc_WordFind${_WORDFUNC_S}_error1 + StrLen $R2 $8 + IntCmp $R2 0 +2 + StrCmp${_WORDFUNC_S} $8 $0 +5 WordFunc_WordFind${_WORDFUNC_S}_preloop + StrCmp${_WORDFUNC_S} $3 '{' WordFunc_WordFind${_WORDFUNC_S}_minus + StrCmp${_WORDFUNC_S} $3 '}' WordFunc_WordFind${_WORDFUNC_S}_minus + StrCmp${_WORDFUNC_S} $2 '*' WordFunc_WordFind${_WORDFUNC_S}_minus + StrCmp${_WORDFUNC_S} $5 $6 WordFunc_WordFind${_WORDFUNC_S}_minus +5 + StrCmp${_WORDFUNC_S} $3 '{' +4 + StrCmp${_WORDFUNC_S} $3 '}' +3 + StrCmp${_WORDFUNC_S} $2 '*' +2 + StrCmp${_WORDFUNC_S} $5 $6 WordFunc_WordFind${_WORDFUNC_S}_nextword + IntOp $4 $4 + 1 + StrCmp${_WORDFUNC_S} $2$4 +$1 WordFunc_WordFind${_WORDFUNC_S}_plus + StrCmp${_WORDFUNC_S} $2 '/' 0 WordFunc_WordFind${_WORDFUNC_S}_nextword + IntOp $8 $6 - $5 + StrCpy $8 $R0 $8 $5 + StrCmp${_WORDFUNC_S} $1 $8 0 WordFunc_WordFind${_WORDFUNC_S}_nextword + StrCpy $R1 $4 + goto WordFunc_WordFind${_WORDFUNC_S}_end + WordFunc_WordFind${_WORDFUNC_S}_nextword: + IntOp $6 $6 + $7 + StrCpy $5 $6 + goto WordFunc_WordFind${_WORDFUNC_S}_loop + + WordFunc_WordFind${_WORDFUNC_S}_minus: + StrCmp${_WORDFUNC_S} $2 '-' 0 WordFunc_WordFind${_WORDFUNC_S}_sum + StrCpy $2 '+' + IntOp $1 $4 - $1 + IntOp $1 $1 + 1 + IntCmp $1 0 WordFunc_WordFind${_WORDFUNC_S}_error2 WordFunc_WordFind${_WORDFUNC_S}_error2 WordFunc_WordFind${_WORDFUNC_S}_restart + WordFunc_WordFind${_WORDFUNC_S}_sum: + StrCmp${_WORDFUNC_S} $2 '#' 0 WordFunc_WordFind${_WORDFUNC_S}_sumdelim + StrCpy $R1 $4 + goto WordFunc_WordFind${_WORDFUNC_S}_end + WordFunc_WordFind${_WORDFUNC_S}_sumdelim: + StrCmp${_WORDFUNC_S} $2 '*' 0 WordFunc_WordFind${_WORDFUNC_S}_error2 + StrCpy $R1 $4 + goto WordFunc_WordFind${_WORDFUNC_S}_end + + WordFunc_WordFind${_WORDFUNC_S}_plus: + StrCmp${_WORDFUNC_S} $3 '' 0 +4 + IntOp $6 $6 - $5 + StrCpy $R1 $R0 $6 $5 + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $3 '{' 0 +3 + StrCpy $R1 $R0 $6 + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $3 '}' 0 +4 + IntOp $6 $6 + $7 + StrCpy $R1 $R0 '' $6 + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $3 '{*' +2 + StrCmp${_WORDFUNC_S} $3 '*{' 0 +3 + StrCpy $R1 $R0 $6 + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $3 '*}' +2 + StrCmp${_WORDFUNC_S} $3 '}*' 0 +3 + StrCpy $R1 $R0 '' $5 + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $3 '}}' 0 +3 + StrCpy $R1 $R0 '' $6 + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $3 '{{' 0 +3 + StrCpy $R1 $R0 $5 + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $3 '{}' 0 WordFunc_WordFind${_WORDFUNC_S}_error3 + StrLen $3 $R0 + StrCmp${_WORDFUNC_S} $3 $6 0 +3 + StrCpy $0 '' + goto +2 + IntOp $6 $6 + $7 + StrCpy $8 $R0 '' $6 + StrCmp${_WORDFUNC_S} $4$8 1 +6 + StrCmp${_WORDFUNC_S} $4 1 +2 +7 + IntOp $6 $6 + $7 + StrCpy $3 $R0 $7 $6 + StrCmp${_WORDFUNC_S} $3 '' +2 + StrCmp${_WORDFUNC_S} $3 $0 -3 +3 + StrCpy $R1 '' + goto WordFunc_WordFind${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $5 0 0 +3 + StrCpy $0 '' + goto +2 + IntOp $5 $5 - $7 + StrCpy $3 $R0 $5 + StrCpy $R1 '$3$0$8' + goto WordFunc_WordFind${_WORDFUNC_S}_end + + WordFunc_WordFind${_WORDFUNC_S}_error3: + StrCpy $R1 3 + goto WordFunc_WordFind${_WORDFUNC_S}_error + WordFunc_WordFind${_WORDFUNC_S}_error2: + StrCpy $R1 2 + goto WordFunc_WordFind${_WORDFUNC_S}_error + WordFunc_WordFind${_WORDFUNC_S}_error1: + StrCpy $R1 1 + WordFunc_WordFind${_WORDFUNC_S}_error: + StrCmp $9 'E' 0 +3 + SetErrors + + WordFunc_WordFind${_WORDFUNC_S}_end: + StrCpy $R0 $R1 + + Pop $R2 + Pop $R1 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Exch $R0 +!macroend + +!define WordFind `!insertmacro WordFindCall ''` +!define un.WordFind `!insertmacro WordFindCall ''` + +!macro WordFind +!macroend + +!macro un.WordFind +!macroend + +!macro WordFind_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordFindBody '' + + !verbose pop +!macroend + +!define WordFindS `!insertmacro WordFindSCall ''` +!define un.WordFindS `!insertmacro WordFindSCall ''` + +!macro WordFindS +!macroend + +!macro un.WordFindS +!macroend + +!macro WordFindS_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordFindBody 'S' + + !verbose pop +!macroend + +!macro WordFind2XBody _WORDFUNC_S + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Exch 3 + Exch $R0 + Exch 3 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R1 + Push $R2 + ClearErrors + + StrCpy $R2 '' + StrCpy $3 $2 1 + StrCpy $2 $2 '' 1 + StrCmp $3 'E' 0 +3 + StrCpy $R2 E + goto -4 + + StrCmp${_WORDFUNC_S} $3 '+' +5 + StrCmp${_WORDFUNC_S} $3 '-' +4 + StrCmp${_WORDFUNC_S} $3 '#' WordFunc_WordFind2X${_WORDFUNC_S}_restart + StrCmp${_WORDFUNC_S} $3 '/' WordFunc_WordFind2X${_WORDFUNC_S}_restart + goto WordFunc_WordFind2X${_WORDFUNC_S}_error3 + + StrCpy $4 $2 2 -2 + StrCmp${_WORDFUNC_S} $4 '{{' +9 + StrCmp${_WORDFUNC_S} $4 '}}' +8 + StrCmp${_WORDFUNC_S} $4 '{*' +7 + StrCmp${_WORDFUNC_S} $4 '*{' +6 + StrCmp${_WORDFUNC_S} $4 '*}' +5 + StrCmp${_WORDFUNC_S} $4 '}*' +4 + StrCmp${_WORDFUNC_S} $4 '{}' +3 + StrCpy $4 '' + goto +2 + StrCpy $2 $2 -2 + IntOp $2 $2 + 0 + StrCmp${_WORDFUNC_S} $2 0 WordFunc_WordFind2X${_WORDFUNC_S}_error2 + + WordFunc_WordFind2X${_WORDFUNC_S}_restart: + StrCmp${_WORDFUNC_S} $R0 '' WordFunc_WordFind2X${_WORDFUNC_S}_error1 + StrCpy $5 -1 + StrCpy $6 0 + StrCpy $7 '' + StrLen $8 $0 + StrLen $9 $1 + + WordFunc_WordFind2X${_WORDFUNC_S}_loop: + IntOp $5 $5 + 1 + + WordFunc_WordFind2X${_WORDFUNC_S}_delim1: + StrCpy $R1 $R0 $8 $5 + StrCmp${_WORDFUNC_S} $R1$6 0 WordFunc_WordFind2X${_WORDFUNC_S}_error1 + StrCmp${_WORDFUNC_S} $R1 '' WordFunc_WordFind2X${_WORDFUNC_S}_minus + StrCmp${_WORDFUNC_S} $R1 $0 +2 + StrCmp${_WORDFUNC_S} $7 '' WordFunc_WordFind2X${_WORDFUNC_S}_loop WordFunc_WordFind2X${_WORDFUNC_S}_delim2 + StrCmp${_WORDFUNC_S} $0 $1 0 +2 + StrCmp${_WORDFUNC_S} $7 '' 0 WordFunc_WordFind2X${_WORDFUNC_S}_delim2 + IntOp $7 $5 + $8 + StrCpy $5 $7 + goto WordFunc_WordFind2X${_WORDFUNC_S}_delim1 + + WordFunc_WordFind2X${_WORDFUNC_S}_delim2: + StrCpy $R1 $R0 $9 $5 + StrCmp${_WORDFUNC_S} $R1 $1 0 WordFunc_WordFind2X${_WORDFUNC_S}_loop + IntOp $6 $6 + 1 + StrCmp${_WORDFUNC_S} $3$6 '+$2' WordFunc_WordFind2X${_WORDFUNC_S}_plus + StrCmp${_WORDFUNC_S} $3 '/' 0 WordFunc_WordFind2X${_WORDFUNC_S}_nextword + IntOp $R1 $5 - $7 + StrCpy $R1 $R0 $R1 $7 + StrCmp${_WORDFUNC_S} $R1 $2 0 +3 + StrCpy $R1 $6 + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + WordFunc_WordFind2X${_WORDFUNC_S}_nextword: + IntOp $5 $5 + $9 + StrCpy $7 '' + goto WordFunc_WordFind2X${_WORDFUNC_S}_delim1 + + WordFunc_WordFind2X${_WORDFUNC_S}_minus: + StrCmp${_WORDFUNC_S} $3 '-' 0 WordFunc_WordFind2X${_WORDFUNC_S}_sum + StrCpy $3 + + IntOp $2 $6 - $2 + IntOp $2 $2 + 1 + IntCmp $2 0 WordFunc_WordFind2X${_WORDFUNC_S}_error2 WordFunc_WordFind2X${_WORDFUNC_S}_error2 WordFunc_WordFind2X${_WORDFUNC_S}_restart + WordFunc_WordFind2X${_WORDFUNC_S}_sum: + StrCmp${_WORDFUNC_S} $3 '#' 0 WordFunc_WordFind2X${_WORDFUNC_S}_error2 + StrCpy $R1 $6 + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + + WordFunc_WordFind2X${_WORDFUNC_S}_plus: + StrCmp${_WORDFUNC_S} $4 '' 0 +4 + IntOp $R1 $5 - $7 + StrCpy $R1 $R0 $R1 $7 + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + IntOp $5 $5 + $9 + IntOp $7 $7 - $8 + StrCmp${_WORDFUNC_S} $4 '{*' +2 + StrCmp${_WORDFUNC_S} $4 '*{' 0 +3 + StrCpy $R1 $R0 $5 + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $4 '*}' +2 + StrCmp${_WORDFUNC_S} $4 '}*' 0 +3 + StrCpy $R1 $R0 '' $7 + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $4 '}}' 0 +3 + StrCpy $R1 $R0 '' $5 + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $4 '{{' 0 +3 + StrCpy $R1 $R0 $7 + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $4 '{}' 0 WordFunc_WordFind2X${_WORDFUNC_S}_error3 + StrCpy $5 $R0 '' $5 + StrCpy $7 $R0 $7 + StrCpy $R1 '$7$5' + goto WordFunc_WordFind2X${_WORDFUNC_S}_end + + WordFunc_WordFind2X${_WORDFUNC_S}_error3: + StrCpy $R1 3 + goto WordFunc_WordFind2X${_WORDFUNC_S}_error + WordFunc_WordFind2X${_WORDFUNC_S}_error2: + StrCpy $R1 2 + goto WordFunc_WordFind2X${_WORDFUNC_S}_error + WordFunc_WordFind2X${_WORDFUNC_S}_error1: + StrCpy $R1 1 + WordFunc_WordFind2X${_WORDFUNC_S}_error: + StrCmp $R2 'E' 0 +3 + SetErrors + + WordFunc_WordFind2X${_WORDFUNC_S}_end: + StrCpy $R0 $R1 + + Pop $R2 + Pop $R1 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Exch $R0 +!macroend + +!define WordFind2X `!insertmacro WordFind2XCall` +!define un.WordFind2X `!insertmacro WordFind2XCall` + +!macro WordFind2X +!macroend + +!macro un.WordFind2X +!macroend + +!macro WordFind2X_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordFind2XBody '' + + !verbose pop +!macroend + +!define WordFind2XS `!insertmacro WordFind2XSCall` +!define un.WordFind2XS `!insertmacro WordFind2XSCall` + +!macro WordFind2XS +!macroend + +!macro un.WordFind2XS +!macroend + +!macro WordFind2XS_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordFind2XBody 'S' + + !verbose pop +!macroend + +!macro WordFind3XBody _WORDFUNC_S + Exch $3 + Exch + Exch $2 + Exch + Exch 2 + Exch $1 + Exch 2 + Exch 3 + Exch $0 + Exch 3 + Exch 4 + Exch $R0 + Exch 4 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R1 + Push $R2 + Push $R3 + Push $R4 + Push $R5 + ClearErrors + + StrCpy $R5 '' + StrCpy $4 $3 1 + StrCpy $3 $3 '' 1 + StrCmp $4 'E' 0 +3 + StrCpy $R5 E + goto -4 + + StrCmp${_WORDFUNC_S} $4 '+' +5 + StrCmp${_WORDFUNC_S} $4 '-' +4 + StrCmp${_WORDFUNC_S} $4 '#' WordFunc_WordFind3X${_WORDFUNC_S}_restart + StrCmp${_WORDFUNC_S} $4 '/' WordFunc_WordFind3X${_WORDFUNC_S}_restart + goto WordFunc_WordFind3X${_WORDFUNC_S}_error3 + + StrCpy $5 $3 2 -2 + StrCmp${_WORDFUNC_S} $5 '{{' +9 + StrCmp${_WORDFUNC_S} $5 '}}' +8 + StrCmp${_WORDFUNC_S} $5 '{*' +7 + StrCmp${_WORDFUNC_S} $5 '*{' +6 + StrCmp${_WORDFUNC_S} $5 '*}' +5 + StrCmp${_WORDFUNC_S} $5 '}*' +4 + StrCmp${_WORDFUNC_S} $5 '{}' +3 + StrCpy $5 '' + goto +2 + StrCpy $3 $3 -2 + IntOp $3 $3 + 0 + StrCmp${_WORDFUNC_S} $3 0 WordFunc_WordFind3X${_WORDFUNC_S}_error2 + + WordFunc_WordFind3X${_WORDFUNC_S}_restart: + StrCmp${_WORDFUNC_S} $R0 '' WordFunc_WordFind3X${_WORDFUNC_S}_error1 + StrCpy $6 -1 + StrCpy $7 0 + StrCpy $8 '' + StrCpy $9 '' + StrLen $R1 $0 + StrLen $R2 $1 + StrLen $R3 $2 + + WordFunc_WordFind3X${_WORDFUNC_S}_loop: + IntOp $6 $6 + 1 + + WordFunc_WordFind3X${_WORDFUNC_S}_delim1: + StrCpy $R4 $R0 $R1 $6 + StrCmp${_WORDFUNC_S} $R4$7 0 WordFunc_WordFind3X${_WORDFUNC_S}_error1 + StrCmp${_WORDFUNC_S} $R4 '' WordFunc_WordFind3X${_WORDFUNC_S}_minus + StrCmp${_WORDFUNC_S} $R4 $0 +2 + StrCmp${_WORDFUNC_S} $8 '' WordFunc_WordFind3X${_WORDFUNC_S}_loop WordFunc_WordFind3X${_WORDFUNC_S}_center + StrCmp${_WORDFUNC_S} $0 $1 +2 + StrCmp${_WORDFUNC_S} $0 $2 0 +2 + StrCmp${_WORDFUNC_S} $8 '' 0 WordFunc_WordFind3X${_WORDFUNC_S}_center + IntOp $8 $6 + $R1 + StrCpy $6 $8 + goto WordFunc_WordFind3X${_WORDFUNC_S}_delim1 + + WordFunc_WordFind3X${_WORDFUNC_S}_center: + StrCmp${_WORDFUNC_S} $9 '' 0 WordFunc_WordFind3X${_WORDFUNC_S}_delim2 + StrCpy $R4 $R0 $R2 $6 + StrCmp${_WORDFUNC_S} $R4 $1 0 WordFunc_WordFind3X${_WORDFUNC_S}_loop + IntOp $9 $6 + $R2 + StrCpy $6 $9 + goto WordFunc_WordFind3X${_WORDFUNC_S}_delim1 + + WordFunc_WordFind3X${_WORDFUNC_S}_delim2: + StrCpy $R4 $R0 $R3 $6 + StrCmp${_WORDFUNC_S} $R4 $2 0 WordFunc_WordFind3X${_WORDFUNC_S}_loop + IntOp $7 $7 + 1 + StrCmp${_WORDFUNC_S} $4$7 '+$3' WordFunc_WordFind3X${_WORDFUNC_S}_plus + StrCmp${_WORDFUNC_S} $4 '/' 0 WordFunc_WordFind3X${_WORDFUNC_S}_nextword + IntOp $R4 $6 - $8 + StrCpy $R4 $R0 $R4 $8 + StrCmp${_WORDFUNC_S} $R4 $3 0 +3 + StrCpy $R4 $7 + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + WordFunc_WordFind3X${_WORDFUNC_S}_nextword: + IntOp $6 $6 + $R3 + StrCpy $8 '' + StrCpy $9 '' + goto WordFunc_WordFind3X${_WORDFUNC_S}_delim1 + + WordFunc_WordFind3X${_WORDFUNC_S}_minus: + StrCmp${_WORDFUNC_S} $4 '-' 0 WordFunc_WordFind3X${_WORDFUNC_S}_sum + StrCpy $4 + + IntOp $3 $7 - $3 + IntOp $3 $3 + 1 + IntCmp $3 0 WordFunc_WordFind3X${_WORDFUNC_S}_error2 WordFunc_WordFind3X${_WORDFUNC_S}_error2 WordFunc_WordFind3X${_WORDFUNC_S}_restart + WordFunc_WordFind3X${_WORDFUNC_S}_sum: + StrCmp${_WORDFUNC_S} $4 '#' 0 WordFunc_WordFind3X${_WORDFUNC_S}_error2 + StrCpy $R4 $7 + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + + WordFunc_WordFind3X${_WORDFUNC_S}_plus: + StrCmp${_WORDFUNC_S} $5 '' 0 +4 + IntOp $R4 $6 - $8 + StrCpy $R4 $R0 $R4 $8 + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + IntOp $6 $6 + $R3 + IntOp $8 $8 - $R1 + StrCmp${_WORDFUNC_S} $5 '{*' +2 + StrCmp${_WORDFUNC_S} $5 '*{' 0 +3 + StrCpy $R4 $R0 $6 + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $5 '*}' +2 + StrCmp${_WORDFUNC_S} $5 '}*' 0 +3 + StrCpy $R4 $R0 '' $8 + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $5 '}}' 0 +3 + StrCpy $R4 $R0 '' $6 + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $5 '{{' 0 +3 + StrCpy $R4 $R0 $8 + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $5 '{}' 0 WordFunc_WordFind3X${_WORDFUNC_S}_error3 + StrCpy $6 $R0 '' $6 + StrCpy $8 $R0 $8 + StrCpy $R4 '$8$6' + goto WordFunc_WordFind3X${_WORDFUNC_S}_end + + WordFunc_WordFind3X${_WORDFUNC_S}_error3: + StrCpy $R4 3 + goto WordFunc_WordFind3X${_WORDFUNC_S}_error + WordFunc_WordFind3X${_WORDFUNC_S}_error2: + StrCpy $R4 2 + goto WordFunc_WordFind3X${_WORDFUNC_S}_error + WordFunc_WordFind3X${_WORDFUNC_S}_error1: + StrCpy $R4 1 + WordFunc_WordFind3X${_WORDFUNC_S}_error: + StrCmp $R5 'E' 0 +3 + SetErrors + + WordFunc_WordFind3X${_WORDFUNC_S}_end: + StrCpy $R0 $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Exch $R0 +!macroend + +!define WordFind3X `!insertmacro WordFind3XCall` +!define un.WordFind3X `!insertmacro WordFind3XCall` + +!macro WordFind3X +!macroend + +!macro un.WordFind3X +!macroend + +!macro WordFind3X_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordFind3XBody '' + + !verbose pop +!macroend + +!define WordFind3XS `!insertmacro WordFind3XSCall` +!define un.WordFind3XS `!insertmacro WordFind3XSCall` + +!macro WordFind3XS +!macroend + +!macro un.WordFind3XS +!macroend + +!macro WordFind3XS_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordFind3XBody 'S' + + !verbose pop +!macroend + +!macro WordReplaceBody _WORDFUNC_S + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Exch 3 + Exch $R0 + Exch 3 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R1 + ClearErrors + + StrCpy $R1 $R0 + StrCpy $9 '' + StrCpy $3 $2 1 + StrCpy $2 $2 '' 1 + StrCmp $3 'E' 0 +3 + StrCpy $9 E + goto -4 + + StrCpy $4 $2 1 -1 + StrCpy $5 '' + StrCpy $6 '' + StrLen $7 $0 + + StrCmp${_WORDFUNC_S} $7 0 WordFunc_WordReplace${_WORDFUNC_S}_error1 + StrCmp${_WORDFUNC_S} $R0 '' WordFunc_WordReplace${_WORDFUNC_S}_error1 + StrCmp${_WORDFUNC_S} $3 '{' WordFunc_WordReplace${_WORDFUNC_S}_beginning + StrCmp${_WORDFUNC_S} $3 '}' WordFunc_WordReplace${_WORDFUNC_S}_ending WordFunc_WordReplace${_WORDFUNC_S}_errorchk + + WordFunc_WordReplace${_WORDFUNC_S}_beginning: + StrCpy $8 $R0 $7 + StrCmp${_WORDFUNC_S} $8 $0 0 +4 + StrCpy $R0 $R0 '' $7 + StrCpy $5 '$5$1' + goto -4 + StrCpy $3 $2 1 + StrCmp${_WORDFUNC_S} $3 '}' 0 WordFunc_WordReplace${_WORDFUNC_S}_merge + + WordFunc_WordReplace${_WORDFUNC_S}_ending: + StrCpy $8 $R0 '' -$7 + StrCmp${_WORDFUNC_S} $8 $0 0 +4 + StrCpy $R0 $R0 -$7 + StrCpy $6 '$6$1' + goto -4 + + WordFunc_WordReplace${_WORDFUNC_S}_merge: + StrCmp${_WORDFUNC_S} $4 '*' 0 +5 + StrCmp${_WORDFUNC_S} $5 '' +2 + StrCpy $5 $1 + StrCmp${_WORDFUNC_S} $6 '' +2 + StrCpy $6 $1 + StrCpy $R0 '$5$R0$6' + goto WordFunc_WordReplace${_WORDFUNC_S}_end + + WordFunc_WordReplace${_WORDFUNC_S}_errorchk: + StrCmp${_WORDFUNC_S} $3 '+' +2 + StrCmp${_WORDFUNC_S} $3 '-' 0 WordFunc_WordReplace${_WORDFUNC_S}_error3 + + StrCpy $5 $2 1 + IntOp $2 $2 + 0 + StrCmp${_WORDFUNC_S} $2 0 0 WordFunc_WordReplace${_WORDFUNC_S}_one + StrCmp${_WORDFUNC_S} $5 0 WordFunc_WordReplace${_WORDFUNC_S}_error2 + StrCpy $3 '' + + WordFunc_WordReplace${_WORDFUNC_S}_all: + StrCpy $5 0 + StrCpy $2 $R0 $7 $5 + StrCmp${_WORDFUNC_S} $2 '' +4 + StrCmp${_WORDFUNC_S} $2 $0 +6 + IntOp $5 $5 + 1 + goto -4 + StrCmp${_WORDFUNC_S} $R0 $R1 WordFunc_WordReplace${_WORDFUNC_S}_error1 + StrCpy $R0 '$3$R0' + goto WordFunc_WordReplace${_WORDFUNC_S}_end + StrCpy $2 $R0 $5 + IntOp $5 $5 + $7 + StrCmp${_WORDFUNC_S} $4 '*' 0 +3 + StrCpy $6 $R0 $7 $5 + StrCmp${_WORDFUNC_S} $6 $0 -3 + StrCpy $R0 $R0 '' $5 + StrCpy $3 '$3$2$1' + goto WordFunc_WordReplace${_WORDFUNC_S}_all + + WordFunc_WordReplace${_WORDFUNC_S}_one: + StrCpy $5 0 + StrCpy $8 0 + goto WordFunc_WordReplace${_WORDFUNC_S}_loop + + WordFunc_WordReplace${_WORDFUNC_S}_preloop: + IntOp $5 $5 + 1 + + WordFunc_WordReplace${_WORDFUNC_S}_loop: + StrCpy $6 $R0 $7 $5 + StrCmp${_WORDFUNC_S} $6$8 0 WordFunc_WordReplace${_WORDFUNC_S}_error1 + StrCmp${_WORDFUNC_S} $6 '' WordFunc_WordReplace${_WORDFUNC_S}_minus + StrCmp${_WORDFUNC_S} $6 $0 0 WordFunc_WordReplace${_WORDFUNC_S}_preloop + IntOp $8 $8 + 1 + StrCmp${_WORDFUNC_S} $3$8 +$2 WordFunc_WordReplace${_WORDFUNC_S}_found + IntOp $5 $5 + $7 + goto WordFunc_WordReplace${_WORDFUNC_S}_loop + + WordFunc_WordReplace${_WORDFUNC_S}_minus: + StrCmp${_WORDFUNC_S} $3 '-' 0 WordFunc_WordReplace${_WORDFUNC_S}_error2 + StrCpy $3 + + IntOp $2 $8 - $2 + IntOp $2 $2 + 1 + IntCmp $2 0 WordFunc_WordReplace${_WORDFUNC_S}_error2 WordFunc_WordReplace${_WORDFUNC_S}_error2 WordFunc_WordReplace${_WORDFUNC_S}_one + + WordFunc_WordReplace${_WORDFUNC_S}_found: + StrCpy $3 $R0 $5 + StrCmp${_WORDFUNC_S} $4 '*' 0 +5 + StrCpy $6 $3 '' -$7 + StrCmp${_WORDFUNC_S} $6 $0 0 +3 + StrCpy $3 $3 -$7 + goto -3 + IntOp $5 $5 + $7 + StrCmp${_WORDFUNC_S} $4 '*' 0 +3 + StrCpy $6 $R0 $7 $5 + StrCmp${_WORDFUNC_S} $6 $0 -3 + StrCpy $R0 $R0 '' $5 + StrCpy $R0 '$3$1$R0' + goto WordFunc_WordReplace${_WORDFUNC_S}_end + + WordFunc_WordReplace${_WORDFUNC_S}_error3: + StrCpy $R0 3 + goto WordFunc_WordReplace${_WORDFUNC_S}_error + WordFunc_WordReplace${_WORDFUNC_S}_error2: + StrCpy $R0 2 + goto WordFunc_WordReplace${_WORDFUNC_S}_error + WordFunc_WordReplace${_WORDFUNC_S}_error1: + StrCpy $R0 1 + WordFunc_WordReplace${_WORDFUNC_S}_error: + StrCmp $9 'E' +3 + StrCpy $R0 $R1 + goto +2 + SetErrors + + WordFunc_WordReplace${_WORDFUNC_S}_end: + Pop $R1 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Exch $R0 +!macroend + +!define WordReplace `!insertmacro WordReplaceCall` +!define un.WordReplace `!insertmacro WordReplaceCall` + +!macro WordReplace +!macroend + +!macro un.WordReplace +!macroend + +!macro WordReplace_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordReplaceBody '' + + !verbose pop +!macroend + +!define WordReplaceS `!insertmacro WordReplaceSCall` +!define un.WordReplaceS `!insertmacro WordReplaceSCall` + +!macro WordReplaceS +!macroend + +!macro un.WordReplaceS +!macroend + +!macro WordReplaceS_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordReplaceBody 'S' + + !verbose pop +!macroend + +!macro WordAddBody _WORDFUNC_S + Exch $1 + Exch + Exch $0 + Exch + Exch 2 + Exch $R0 + Exch 2 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $R1 + ClearErrors + + StrCpy $7 '' + StrCpy $2 $1 1 + StrCmp $2 'E' 0 +4 + StrCpy $7 E + StrCpy $1 $1 '' 1 + goto -4 + + StrCpy $5 0 + StrCpy $R1 $R0 + StrCpy $2 $1 '' 1 + StrCpy $1 $1 1 + StrCmp${_WORDFUNC_S} $1 '+' +2 + StrCmp${_WORDFUNC_S} $1 '-' 0 WordFunc_WordAdd${_WORDFUNC_S}_error3 + + StrCmp${_WORDFUNC_S} $0 '' WordFunc_WordAdd${_WORDFUNC_S}_error1 + StrCmp${_WORDFUNC_S} $2 '' WordFunc_WordAdd${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $R0 '' 0 +5 + StrCmp${_WORDFUNC_S} $1 '-' WordFunc_WordAdd${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $1 '+' 0 +3 + StrCpy $R0 $2 + goto WordFunc_WordAdd${_WORDFUNC_S}_end + + WordFunc_WordAdd${_WORDFUNC_S}_loop: + IntOp $5 $5 + 1 + !insertmacro WordFind${_WORDFUNC_S}Call 2 $2 $0 E+$5 $3 + IfErrors 0 WordFunc_WordAdd${_WORDFUNC_S}_/word + StrCmp${_WORDFUNC_S} $3 2 +4 + StrCmp${_WORDFUNC_S} $3$5 11 0 +3 + StrCpy $3 $2 + goto WordFunc_WordAdd${_WORDFUNC_S}_/word + StrCmp${_WORDFUNC_S} $1 '-' WordFunc_WordAdd${_WORDFUNC_S}_end WordFunc_WordAdd${_WORDFUNC_S}_preend + + WordFunc_WordAdd${_WORDFUNC_S}_/word: + !insertmacro WordFind${_WORDFUNC_S}Call 2 $R0 $0 E/$3 $4 + IfErrors +2 + StrCmp${_WORDFUNC_S} $1 '-' WordFunc_WordAdd${_WORDFUNC_S}_delete WordFunc_WordAdd${_WORDFUNC_S}_loop + StrCmp${_WORDFUNC_S} $1$4 '-1' +2 + StrCmp${_WORDFUNC_S} $1 '-' WordFunc_WordAdd${_WORDFUNC_S}_loop +4 + StrCmp${_WORDFUNC_S} $R0 $3 0 WordFunc_WordAdd${_WORDFUNC_S}_loop + StrCpy $R0 '' + goto WordFunc_WordAdd${_WORDFUNC_S}_end + StrCmp${_WORDFUNC_S} $1$4 '+1' 0 +2 + StrCmp${_WORDFUNC_S} $R0 $3 WordFunc_WordAdd${_WORDFUNC_S}_loop + StrCmp${_WORDFUNC_S} $R0 $R1 +3 + StrCpy $R1 '$R1$0$3' + goto WordFunc_WordAdd${_WORDFUNC_S}_loop + StrLen $6 $0 + StrCpy $6 $R0 '' -$6 + StrCmp${_WORDFUNC_S} $6 $0 0 -4 + StrCpy $R1 '$R1$3' + goto WordFunc_WordAdd${_WORDFUNC_S}_loop + + WordFunc_WordAdd${_WORDFUNC_S}_delete: + !insertmacro WordFind${_WORDFUNC_S}Call 2 $R0 $0 E+$4{} $R0 + goto WordFunc_WordAdd${_WORDFUNC_S}_/word + + WordFunc_WordAdd${_WORDFUNC_S}_error3: + StrCpy $R1 3 + goto WordFunc_WordAdd${_WORDFUNC_S}_error + WordFunc_WordAdd${_WORDFUNC_S}_error1: + StrCpy $R1 1 + WordFunc_WordAdd${_WORDFUNC_S}_error: + StrCmp $7 'E' 0 WordFunc_WordAdd${_WORDFUNC_S}_end + SetErrors + + WordFunc_WordAdd${_WORDFUNC_S}_preend: + StrCpy $R0 $R1 + + WordFunc_WordAdd${_WORDFUNC_S}_end: + Pop $R1 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Exch $R0 +!macroend + +!define WordAdd `!insertmacro WordAddCall` +!define un.WordAdd `!insertmacro WordAddCall` + +!macro WordAdd +!macroend + +!macro un.WordAdd +!macroend + +!macro WordAdd_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordAddBody '' + + !verbose pop +!macroend + +!define WordAddS `!insertmacro WordAddSCall` +!define un.WordAddS `!insertmacro WordAddSCall` + +!macro WordAddS +!macroend + +!macro un.WordAddS +!macroend + +!macro WordAddS_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordAddBody 'S' + + !verbose pop +!macroend + +!macro WordInsertBody _WORDFUNC_S + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Exch 3 + Exch $R0 + Exch 3 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R1 + ClearErrors + + StrCpy $5 '' + StrCpy $6 $0 + StrCpy $7 } + + StrCpy $9 '' + StrCpy $R1 $R0 + StrCpy $3 $2 1 + StrCpy $2 $2 '' 1 + StrCmp $3 'E' 0 +3 + StrCpy $9 'E' + goto -4 + + StrCmp${_WORDFUNC_S} $3 '+' +2 + StrCmp${_WORDFUNC_S} $3 '-' 0 WordFunc_WordInsert${_WORDFUNC_S}_error3 + IntOp $2 $2 + 0 + StrCmp${_WORDFUNC_S} $2 0 WordFunc_WordInsert${_WORDFUNC_S}_error2 + StrCmp${_WORDFUNC_S} $0 '' WordFunc_WordInsert${_WORDFUNC_S}_error1 + + StrCmp${_WORDFUNC_S} $2 1 0 WordFunc_WordInsert${_WORDFUNC_S}_two + GetLabelAddress $8 WordFunc_WordInsert${_WORDFUNC_S}_oneback + StrCmp${_WORDFUNC_S} $3 '+' WordFunc_WordInsert${_WORDFUNC_S}_call + StrCpy $7 { + goto WordFunc_WordInsert${_WORDFUNC_S}_call + WordFunc_WordInsert${_WORDFUNC_S}_oneback: + IfErrors 0 +2 + StrCpy $4 $R0 + StrCmp${_WORDFUNC_S} $3 '+' 0 +3 + StrCpy $R0 '$1$0$4' + goto WordFunc_WordInsert${_WORDFUNC_S}_end + StrCpy $R0 '$4$0$1' + goto WordFunc_WordInsert${_WORDFUNC_S}_end + + WordFunc_WordInsert${_WORDFUNC_S}_two: + IntOp $2 $2 - 1 + GetLabelAddress $8 WordFunc_WordInsert${_WORDFUNC_S}_twoback + StrCmp${_WORDFUNC_S} $3 '+' 0 WordFunc_WordInsert${_WORDFUNC_S}_call + StrCpy $7 { + goto WordFunc_WordInsert${_WORDFUNC_S}_call + WordFunc_WordInsert${_WORDFUNC_S}_twoback: + IfErrors 0 WordFunc_WordInsert${_WORDFUNC_S}_tree + StrCmp${_WORDFUNC_S} $2$4 11 0 WordFunc_WordInsert${_WORDFUNC_S}_error2 + StrCmp${_WORDFUNC_S} $3 '+' 0 +3 + StrCpy $R0 '$R0$0$1' + goto WordFunc_WordInsert${_WORDFUNC_S}_end + StrCpy $R0 '$1$0$R0' + goto WordFunc_WordInsert${_WORDFUNC_S}_end + + WordFunc_WordInsert${_WORDFUNC_S}_tree: + StrCpy $7 } + StrCpy $5 $4 + IntOp $2 $2 + 1 + GetLabelAddress $8 WordFunc_WordInsert${_WORDFUNC_S}_treeback + StrCmp${_WORDFUNC_S} $3 '+' WordFunc_WordInsert${_WORDFUNC_S}_call + StrCpy $7 { + goto WordFunc_WordInsert${_WORDFUNC_S}_call + WordFunc_WordInsert${_WORDFUNC_S}_treeback: + IfErrors 0 +3 + StrCpy $4 '' + StrCpy $6 '' + StrCmp${_WORDFUNC_S} $3 '+' 0 +3 + StrCpy $R0 '$5$0$1$6$4' + goto WordFunc_WordInsert${_WORDFUNC_S}_end + StrCpy $R0 '$4$6$1$0$5' + goto WordFunc_WordInsert${_WORDFUNC_S}_end + + WordFunc_WordInsert${_WORDFUNC_S}_call: + !insertmacro WordFind${_WORDFUNC_S}Call 2 $R0 $0 E$3$2*$7 $4 + goto $8 + + WordFunc_WordInsert${_WORDFUNC_S}_error3: + StrCpy $R0 3 + goto WordFunc_WordInsert${_WORDFUNC_S}_error + WordFunc_WordInsert${_WORDFUNC_S}_error2: + StrCpy $R0 2 + goto WordFunc_WordInsert${_WORDFUNC_S}_error + WordFunc_WordInsert${_WORDFUNC_S}_error1: + StrCpy $R0 1 + WordFunc_WordInsert${_WORDFUNC_S}_error: + StrCmp $9 'E' +3 + StrCpy $R0 $R1 + goto +2 + SetErrors + + WordFunc_WordInsert${_WORDFUNC_S}_end: + Pop $R1 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Exch $R0 +!macroend + +!define WordInsert `!insertmacro WordInsertCall` +!define un.WordInsert `!insertmacro WordInsertCall` + +!macro WordInsert +!macroend + +!macro un.WordInsert +!macroend + +!macro WordInsert_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordInsertBody '' + + !verbose pop +!macroend + + +!define WordInsertS `!insertmacro WordInsertSCall` +!define un.WordInsertS `!insertmacro WordInsertSCall` + +!macro WordInsertS +!macroend + +!macro un.WordInsertS +!macroend + +!macro WordInsertS_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro WordInsertBody 'S' + + !verbose pop +!macroend + +!macro StrFilterBody _WORDFUNC_S + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Exch 3 + Exch $R0 + Exch 3 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $R1 + Push $R2 + Push $R3 + Push $R4 + Push $R5 + Push $R6 + Push $R7 + Push $R8 + ClearErrors + + StrCpy $R2 $0 '' -3 + StrCmp $R2 "eng" WordFunc_StrFilter${_WORDFUNC_S}_eng + StrCmp $R2 "rus" WordFunc_StrFilter${_WORDFUNC_S}_rus + WordFunc_StrFilter${_WORDFUNC_S}_eng: + StrCpy $4 65 + StrCpy $5 90 + StrCpy $6 97 + StrCpy $7 122 + goto WordFunc_StrFilter${_WORDFUNC_S}_langend + WordFunc_StrFilter${_WORDFUNC_S}_rus: + StrCpy $4 192 + StrCpy $5 223 + StrCpy $6 224 + StrCpy $7 255 + goto WordFunc_StrFilter${_WORDFUNC_S}_langend + ;... + + WordFunc_StrFilter${_WORDFUNC_S}_langend: + StrCpy $R7 '' + StrCpy $R8 '' + + StrCmp${_WORDFUNC_S} $2 '' 0 WordFunc_StrFilter${_WORDFUNC_S}_begin + + WordFunc_StrFilter${_WORDFUNC_S}_restart1: + StrCpy $2 '' + StrCpy $3 $0 1 + StrCmp${_WORDFUNC_S} $3 '+' +2 + StrCmp${_WORDFUNC_S} $3 '-' 0 +3 + StrCpy $0 $0 '' 1 + goto +2 + StrCpy $3 '' + + IntOp $0 $0 + 0 + StrCmp${_WORDFUNC_S} $0 0 +5 + StrCpy $R7 $0 1 0 + StrCpy $R8 $0 1 1 + StrCpy $R2 $0 1 2 + StrCmp${_WORDFUNC_S} $R2 '' WordFunc_StrFilter${_WORDFUNC_S}_filter WordFunc_StrFilter${_WORDFUNC_S}_error + + WordFunc_StrFilter${_WORDFUNC_S}_restart2: + StrCmp${_WORDFUNC_S} $3 '' WordFunc_StrFilter${_WORDFUNC_S}_end + StrCpy $R7 '' + StrCpy $R8 '+-' + goto WordFunc_StrFilter${_WORDFUNC_S}_begin + + WordFunc_StrFilter${_WORDFUNC_S}_filter: + StrCmp${_WORDFUNC_S} $R7 '1' +3 + StrCmp${_WORDFUNC_S} $R7 '2' +2 + StrCmp${_WORDFUNC_S} $R7 '3' 0 WordFunc_StrFilter${_WORDFUNC_S}_error + + StrCmp${_WORDFUNC_S} $R8 '' WordFunc_StrFilter${_WORDFUNC_S}_begin + StrCmp${_WORDFUNC_S} $R7$R8 '23' +2 + StrCmp${_WORDFUNC_S} $R7$R8 '32' 0 +3 + StrCpy $R7 -1 + goto WordFunc_StrFilter${_WORDFUNC_S}_begin + StrCmp${_WORDFUNC_S} $R7$R8 '13' +2 + StrCmp${_WORDFUNC_S} $R7$R8 '31' 0 +3 + StrCpy $R7 -2 + goto WordFunc_StrFilter${_WORDFUNC_S}_begin + StrCmp${_WORDFUNC_S} $R7$R8 '12' +2 + StrCmp${_WORDFUNC_S} $R7$R8 '21' 0 WordFunc_StrFilter${_WORDFUNC_S}_error + StrCpy $R7 -3 + + WordFunc_StrFilter${_WORDFUNC_S}_begin: + StrCpy $R6 0 + StrCpy $R1 '' + + WordFunc_StrFilter${_WORDFUNC_S}_loop: + StrCpy $R2 $R0 1 $R6 + StrCmp${_WORDFUNC_S} $R2 '' WordFunc_StrFilter${_WORDFUNC_S}_restartchk + + StrCmp${_WORDFUNC_S} $2 '' +7 + StrCpy $R4 0 + StrCpy $R5 $2 1 $R4 + StrCmp${_WORDFUNC_S} $R5 '' WordFunc_StrFilter${_WORDFUNC_S}_addsymbol + StrCmp${_WORDFUNC_S} $R5 $R2 WordFunc_StrFilter${_WORDFUNC_S}_skipsymbol + IntOp $R4 $R4 + 1 + goto -4 + + StrCmp${_WORDFUNC_S} $1 '' +7 + StrCpy $R4 0 + StrCpy $R5 $1 1 $R4 + StrCmp${_WORDFUNC_S} $R5 '' +4 + StrCmp${_WORDFUNC_S} $R5 $R2 WordFunc_StrFilter${_WORDFUNC_S}_addsymbol + IntOp $R4 $R4 + 1 + goto -4 + + StrCmp${_WORDFUNC_S} $R7 '1' +2 + StrCmp${_WORDFUNC_S} $R7 '-1' 0 +4 + StrCpy $R4 48 + StrCpy $R5 57 + goto WordFunc_StrFilter${_WORDFUNC_S}_loop2 + StrCmp${_WORDFUNC_S} $R8 '+-' 0 +2 + StrCmp${_WORDFUNC_S} $3 '+' 0 +4 + StrCpy $R4 $4 + StrCpy $R5 $5 + goto WordFunc_StrFilter${_WORDFUNC_S}_loop2 + StrCpy $R4 $6 + StrCpy $R5 $7 + + WordFunc_StrFilter${_WORDFUNC_S}_loop2: + IntFmt $R3 '%c' $R4 + StrCmp $R2 $R3 WordFunc_StrFilter${_WORDFUNC_S}_found + StrCmp $R4 $R5 WordFunc_StrFilter${_WORDFUNC_S}_notfound + IntOp $R4 $R4 + 1 + goto WordFunc_StrFilter${_WORDFUNC_S}_loop2 + + WordFunc_StrFilter${_WORDFUNC_S}_found: + StrCmp${_WORDFUNC_S} $R8 '+-' WordFunc_StrFilter${_WORDFUNC_S}_setcase + StrCmp${_WORDFUNC_S} $R7 '3' WordFunc_StrFilter${_WORDFUNC_S}_skipsymbol + StrCmp${_WORDFUNC_S} $R7 '-3' WordFunc_StrFilter${_WORDFUNC_S}_addsymbol + StrCmp${_WORDFUNC_S} $R8 '' WordFunc_StrFilter${_WORDFUNC_S}_addsymbol WordFunc_StrFilter${_WORDFUNC_S}_skipsymbol + + WordFunc_StrFilter${_WORDFUNC_S}_notfound: + StrCmp${_WORDFUNC_S} $R8 '+-' WordFunc_StrFilter${_WORDFUNC_S}_addsymbol + StrCmp${_WORDFUNC_S} $R7 '3' 0 +2 + StrCmp${_WORDFUNC_S} $R5 57 WordFunc_StrFilter${_WORDFUNC_S}_addsymbol +3 + StrCmp${_WORDFUNC_S} $R7 '-3' 0 +5 + StrCmp${_WORDFUNC_S} $R5 57 WordFunc_StrFilter${_WORDFUNC_S}_skipsymbol + StrCpy $R4 48 + StrCpy $R5 57 + goto WordFunc_StrFilter${_WORDFUNC_S}_loop2 + StrCmp${_WORDFUNC_S} $R8 '' WordFunc_StrFilter${_WORDFUNC_S}_skipsymbol WordFunc_StrFilter${_WORDFUNC_S}_addsymbol + + WordFunc_StrFilter${_WORDFUNC_S}_setcase: + StrCpy $R2 $R3 + WordFunc_StrFilter${_WORDFUNC_S}_addsymbol: + StrCpy $R1 $R1$R2 + WordFunc_StrFilter${_WORDFUNC_S}_skipsymbol: + IntOp $R6 $R6 + 1 + goto WordFunc_StrFilter${_WORDFUNC_S}_loop + + WordFunc_StrFilter${_WORDFUNC_S}_error: + SetErrors + StrCpy $R0 '' + goto WordFunc_StrFilter${_WORDFUNC_S}_end + + WordFunc_StrFilter${_WORDFUNC_S}_restartchk: + StrCpy $R0 $R1 + StrCmp${_WORDFUNC_S} $2 '' 0 WordFunc_StrFilter${_WORDFUNC_S}_restart1 + StrCmp${_WORDFUNC_S} $R8 '+-' 0 WordFunc_StrFilter${_WORDFUNC_S}_restart2 + + WordFunc_StrFilter${_WORDFUNC_S}_end: + Pop $R8 + Pop $R7 + Pop $R6 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + Exch $R0 +!macroend + +!define StrFilter `!insertmacro StrFilterCall` +!define un.StrFilter `!insertmacro StrFilterCall` + +!macro StrFilter +!macroend + +!macro un.StrFilter +!macroend + +!macro StrFilter_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro StrFilterBody '' + + !verbose pop +!macroend + + +!define StrFilterS `!insertmacro StrFilterSCall` +!define un.StrFilterS `!insertmacro StrFilterSCall` + +!macro StrFilterS +!macroend + +!macro un.StrFilterS +!macroend + +!macro StrFilterS_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + !insertmacro StrFilterBody 'S' + + !verbose pop +!macroend + +!define VersionCompare `!insertmacro VersionCompareCall` +!define un.VersionCompare `!insertmacro VersionCompareCall` + +!macro VersionCompare +!macroend + +!macro un.VersionCompare +!macroend + +!macro VersionCompare_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + + WordFunc_VersionCompare_begin: + StrCpy $2 -1 + IntOp $2 $2 + 1 + StrCpy $3 $0 1 $2 + StrCmp $3 '' +2 + StrCmp $3 '.' 0 -3 + StrCpy $4 $0 $2 + IntOp $2 $2 + 1 + StrCpy $0 $0 '' $2 + + StrCpy $2 -1 + IntOp $2 $2 + 1 + StrCpy $3 $1 1 $2 + StrCmp $3 '' +2 + StrCmp $3 '.' 0 -3 + StrCpy $5 $1 $2 + IntOp $2 $2 + 1 + StrCpy $1 $1 '' $2 + + StrCmp $4$5 '' WordFunc_VersionCompare_equal + + StrCpy $6 -1 + IntOp $6 $6 + 1 + StrCpy $3 $4 1 $6 + StrCmp $3 '0' -2 + StrCmp $3 '' 0 +2 + StrCpy $4 0 + + StrCpy $7 -1 + IntOp $7 $7 + 1 + StrCpy $3 $5 1 $7 + StrCmp $3 '0' -2 + StrCmp $3 '' 0 +2 + StrCpy $5 0 + + StrCmp $4 0 0 +2 + StrCmp $5 0 WordFunc_VersionCompare_begin WordFunc_VersionCompare_newer2 + StrCmp $5 0 WordFunc_VersionCompare_newer1 + IntCmp $6 $7 0 WordFunc_VersionCompare_newer1 WordFunc_VersionCompare_newer2 + + StrCpy $4 '1$4' + StrCpy $5 '1$5' + IntCmp $4 $5 WordFunc_VersionCompare_begin WordFunc_VersionCompare_newer2 WordFunc_VersionCompare_newer1 + + WordFunc_VersionCompare_equal: + StrCpy $0 0 + goto WordFunc_VersionCompare_end + WordFunc_VersionCompare_newer1: + StrCpy $0 1 + goto WordFunc_VersionCompare_end + WordFunc_VersionCompare_newer2: + StrCpy $0 2 + + WordFunc_VersionCompare_end: + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!define VersionConvert `!insertmacro VersionConvertCall` +!define un.VersionConvert `!insertmacro VersionConvertCall` + +!macro VersionConvert +!macroend + +!macro un.VersionConvert +!macroend + +!macro VersionConvert_ + !verbose push + !verbose ${_WORDFUNC_VERBOSE} + + Exch $1 + Exch + Exch $0 + Exch + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + + StrCmp $1 '' 0 +2 + StrCpy $1 'abcdefghijklmnopqrstuvwxyz' + StrCpy $1 $1 99 + + StrCpy $2 0 + StrCpy $7 'dot' + goto WordFunc_VersionConvert_loop + + WordFunc_VersionConvert_preloop: + IntOp $2 $2 + 1 + + WordFunc_VersionConvert_loop: + StrCpy $3 $0 1 $2 + StrCmp $3 '' WordFunc_VersionConvert_endcheck + StrCmp $3 '.' WordFunc_VersionConvert_dot + StrCmp $3 '0' WordFunc_VersionConvert_digit + IntCmp $3 '0' WordFunc_VersionConvert_letter WordFunc_VersionConvert_letter WordFunc_VersionConvert_digit + + WordFunc_VersionConvert_dot: + StrCmp $7 'dot' WordFunc_VersionConvert_replacespecial + StrCpy $7 'dot' + goto WordFunc_VersionConvert_preloop + + WordFunc_VersionConvert_digit: + StrCmp $7 'letter' WordFunc_VersionConvert_insertdot + StrCpy $7 'digit' + goto WordFunc_VersionConvert_preloop + + WordFunc_VersionConvert_letter: + StrCpy $5 0 + StrCpy $4 $1 1 $5 + IntOp $5 $5 + 1 + StrCmp $4 '' WordFunc_VersionConvert_replacespecial + StrCmp $4 $3 0 -3 + IntCmp $5 9 0 0 +2 + StrCpy $5 '0$5' + + StrCmp $7 'letter' +2 + StrCmp $7 'dot' 0 +3 + StrCpy $6 '' + goto +2 + StrCpy $6 '.' + + StrCpy $4 $0 $2 + IntOp $2 $2 + 1 + StrCpy $0 $0 '' $2 + StrCpy $0 '$4$6$5$0' + StrLen $4 '$6$5' + IntOp $2 $2 + $4 + IntOp $2 $2 - 1 + StrCpy $7 'letter' + goto WordFunc_VersionConvert_loop + + WordFunc_VersionConvert_replacespecial: + StrCmp $7 'dot' 0 +3 + StrCpy $6 '' + goto +2 + StrCpy $6 '.' + + StrCpy $4 $0 $2 + IntOp $2 $2 + 1 + StrCpy $0 $0 '' $2 + StrCpy $0 '$4$6$0' + StrLen $4 $6 + IntOp $2 $2 + $4 + IntOp $2 $2 - 1 + StrCpy $7 'dot' + goto WordFunc_VersionConvert_loop + + WordFunc_VersionConvert_insertdot: + StrCpy $4 $0 $2 + StrCpy $0 $0 '' $2 + StrCpy $0 '$4.$0' + StrCpy $7 'dot' + goto WordFunc_VersionConvert_preloop + + WordFunc_VersionConvert_endcheck: + StrCpy $4 $0 1 -1 + StrCmp $4 '.' 0 WordFunc_VersionConvert_end + StrCpy $0 $0 -1 + goto -3 + + WordFunc_VersionConvert_end: + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 + + !verbose pop +!macroend + +!endif diff --git a/installer/NSIS/Include/nsDialogs.nsh b/installer/NSIS/Include/nsDialogs.nsh new file mode 100644 index 0000000..68c03c9 --- /dev/null +++ b/installer/NSIS/Include/nsDialogs.nsh @@ -0,0 +1,860 @@ +/* + +nsDialogs.nsh +Header file for creating custom installer pages with nsDialogs + +*/ + +!ifndef NSDIALOGS_INCLUDED +!define NSDIALOGS_INCLUDED +!verbose push +!verbose 3 + +!include LogicLib.nsh +!include WinMessages.nsh + +!define WS_EX_DLGMODALFRAME 0x00000001 +!define WS_EX_NOPARENTNOTIFY 0x00000004 +!define WS_EX_TOPMOST 0x00000008 +!define WS_EX_ACCEPTFILES 0x00000010 +!define WS_EX_TRANSPARENT 0x00000020 +!define WS_EX_MDICHILD 0x00000040 +!define WS_EX_TOOLWINDOW 0x00000080 +!define WS_EX_WINDOWEDGE 0x00000100 +!define WS_EX_CLIENTEDGE 0x00000200 +!define WS_EX_CONTEXTHELP 0x00000400 +!define WS_EX_RIGHT 0x00001000 +!define WS_EX_LEFT 0x00000000 +!define WS_EX_RTLREADING 0x00002000 +!define WS_EX_LTRREADING 0x00000000 +!define WS_EX_LEFTSCROLLBAR 0x00004000 +!define WS_EX_RIGHTSCROLLBAR 0x00000000 +!define WS_EX_CONTROLPARENT 0x00010000 +!define WS_EX_STATICEDGE 0x00020000 +!define WS_EX_APPWINDOW 0x00040000 + +!define WS_CHILD 0x40000000 +!define WS_VISIBLE 0x10000000 +!define WS_DISABLED 0x08000000 +!define WS_CLIPSIBLINGS 0x04000000 +!define WS_CLIPCHILDREN 0x02000000 +!define WS_MAXIMIZE 0x01000000 +!define WS_VSCROLL 0x00200000 +!define WS_HSCROLL 0x00100000 +!define WS_GROUP 0x00020000 +!define WS_TABSTOP 0x00010000 + +!define ES_LEFT 0x00000000 +!define ES_CENTER 0x00000001 +!define ES_RIGHT 0x00000002 +!define ES_MULTILINE 0x00000004 +!define ES_UPPERCASE 0x00000008 +!define ES_LOWERCASE 0x00000010 +!define ES_PASSWORD 0x00000020 +!define ES_AUTOVSCROLL 0x00000040 +!define ES_AUTOHSCROLL 0x00000080 +!define ES_NOHIDESEL 0x00000100 +!define ES_OEMCONVERT 0x00000400 +!define ES_READONLY 0x00000800 +!define ES_WANTRETURN 0x00001000 +!define ES_NUMBER 0x00002000 + +!define SS_LEFT 0x00000000 +!define SS_CENTER 0x00000001 +!define SS_RIGHT 0x00000002 +!define SS_ICON 0x00000003 +!define SS_BLACKRECT 0x00000004 +!define SS_GRAYRECT 0x00000005 +!define SS_WHITERECT 0x00000006 +!define SS_BLACKFRAME 0x00000007 +!define SS_GRAYFRAME 0x00000008 +!define SS_WHITEFRAME 0x00000009 +!define SS_USERITEM 0x0000000A +!define SS_SIMPLE 0x0000000B +!define SS_LEFTNOWORDWRAP 0x0000000C +!define SS_OWNERDRAW 0x0000000D +!define SS_BITMAP 0x0000000E +!define SS_ENHMETAFILE 0x0000000F +!define SS_ETCHEDHORZ 0x00000010 +!define SS_ETCHEDVERT 0x00000011 +!define SS_ETCHEDFRAME 0x00000012 +!define SS_TYPEMASK 0x0000001F +!define SS_REALSIZECONTROL 0x00000040 +!define SS_NOPREFIX 0x00000080 +!define SS_NOTIFY 0x00000100 +!define SS_CENTERIMAGE 0x00000200 +!define SS_RIGHTJUST 0x00000400 +!define SS_REALSIZEIMAGE 0x00000800 +!define SS_SUNKEN 0x00001000 +!define SS_EDITCONTROL 0x00002000 +!define SS_ENDELLIPSIS 0x00004000 +!define SS_PATHELLIPSIS 0x00008000 +!define SS_WORDELLIPSIS 0x0000C000 +!define SS_ELLIPSISMASK 0x0000C000 + +!define BS_PUSHBUTTON 0x00000000 +!define BS_DEFPUSHBUTTON 0x00000001 +!define BS_CHECKBOX 0x00000002 +!define BS_AUTOCHECKBOX 0x00000003 +!define BS_RADIOBUTTON 0x00000004 +!define BS_3STATE 0x00000005 +!define BS_AUTO3STATE 0x00000006 +!define BS_GROUPBOX 0x00000007 +!define BS_USERBUTTON 0x00000008 +!define BS_AUTORADIOBUTTON 0x00000009 +!define BS_PUSHBOX 0x0000000A +!define BS_OWNERDRAW 0x0000000B +!define BS_TYPEMASK 0x0000000F +!define BS_LEFTTEXT 0x00000020 +!define BS_TEXT 0x00000000 +!define BS_ICON 0x00000040 +!define BS_BITMAP 0x00000080 +!define BS_LEFT 0x00000100 +!define BS_RIGHT 0x00000200 +!define BS_CENTER 0x00000300 +!define BS_TOP 0x00000400 +!define BS_BOTTOM 0x00000800 +!define BS_VCENTER 0x00000C00 +!define BS_PUSHLIKE 0x00001000 +!define BS_MULTILINE 0x00002000 +!define BS_NOTIFY 0x00004000 +!define BS_FLAT 0x00008000 +!define BS_RIGHTBUTTON ${BS_LEFTTEXT} + +!define CBS_SIMPLE 0x0001 +!define CBS_DROPDOWN 0x0002 +!define CBS_DROPDOWNLIST 0x0003 +!define CBS_OWNERDRAWFIXED 0x0010 +!define CBS_OWNERDRAWVARIABLE 0x0020 +!define CBS_AUTOHSCROLL 0x0040 +!define CBS_OEMCONVERT 0x0080 +!define CBS_SORT 0x0100 +!define CBS_HASSTRINGS 0x0200 +!define CBS_NOINTEGRALHEIGHT 0x0400 +!define CBS_DISABLENOSCROLL 0x0800 +!define CBS_UPPERCASE 0x2000 +!define CBS_LOWERCASE 0x4000 + +!define LBS_NOTIFY 0x0001 +!define LBS_SORT 0x0002 +!define LBS_NOREDRAW 0x0004 +!define LBS_MULTIPLESEL 0x0008 +!define LBS_OWNERDRAWFIXED 0x0010 +!define LBS_OWNERDRAWVARIABLE 0x0020 +!define LBS_HASSTRINGS 0x0040 +!define LBS_USETABSTOPS 0x0080 +!define LBS_NOINTEGRALHEIGHT 0x0100 +!define LBS_MULTICOLUMN 0x0200 +!define LBS_WANTKEYBOARDINPUT 0x0400 +!define LBS_EXTENDEDSEL 0x0800 +!define LBS_DISABLENOSCROLL 0x1000 +!define LBS_NODATA 0x2000 +!define LBS_NOSEL 0x4000 +!define LBS_COMBOBOX 0x8000 + +!define LR_DEFAULTCOLOR 0x0000 +!define LR_MONOCHROME 0x0001 +!define LR_COLOR 0x0002 +!define LR_COPYRETURNORG 0x0004 +!define LR_COPYDELETEORG 0x0008 +!define LR_LOADFROMFILE 0x0010 +!define LR_LOADTRANSPARENT 0x0020 +!define LR_DEFAULTSIZE 0x0040 +!define LR_VGACOLOR 0x0080 +!define LR_LOADMAP3DCOLORS 0x1000 +!define LR_CREATEDIBSECTION 0x2000 +!define LR_COPYFROMRESOURCE 0x4000 +!define LR_SHARED 0x8000 + +!define IMAGE_BITMAP 0 +!define IMAGE_ICON 1 +!define IMAGE_CURSOR 2 +!define IMAGE_ENHMETAFILE 3 + +!define GWL_STYLE -16 +!define GWL_EXSTYLE -20 + +!define DEFAULT_STYLES ${WS_CHILD}|${WS_VISIBLE}|${WS_CLIPSIBLINGS} + +!define __NSD_HLine_CLASS STATIC +!define __NSD_HLine_STYLE ${DEFAULT_STYLES}|${SS_ETCHEDHORZ}|${SS_SUNKEN} +!define __NSD_HLine_EXSTYLE ${WS_EX_TRANSPARENT} + +!define __NSD_VLine_CLASS STATIC +!define __NSD_VLine_STYLE ${DEFAULT_STYLES}|${SS_ETCHEDVERT}|${SS_SUNKEN} +!define __NSD_VLine_EXSTYLE ${WS_EX_TRANSPARENT} + +!define __NSD_Label_CLASS STATIC +!define __NSD_Label_STYLE ${DEFAULT_STYLES}|${SS_NOTIFY} +!define __NSD_Label_EXSTYLE ${WS_EX_TRANSPARENT} + +!define __NSD_Icon_CLASS STATIC +!define __NSD_Icon_STYLE ${DEFAULT_STYLES}|${SS_ICON}|${SS_NOTIFY} +!define __NSD_Icon_EXSTYLE 0 + +!define __NSD_Bitmap_CLASS STATIC +!define __NSD_Bitmap_STYLE ${DEFAULT_STYLES}|${SS_BITMAP}|${SS_NOTIFY} +!define __NSD_Bitmap_EXSTYLE 0 + +!define __NSD_BrowseButton_CLASS BUTTON +!define __NSD_BrowseButton_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP} +!define __NSD_BrowseButton_EXSTYLE 0 + +!define __NSD_Link_CLASS LINK +!define __NSD_Link_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${BS_OWNERDRAW} +!define __NSD_Link_EXSTYLE 0 + +!define __NSD_Button_CLASS BUTTON +!define __NSD_Button_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP} +!define __NSD_Button_EXSTYLE 0 + +!define __NSD_GroupBox_CLASS BUTTON +!define __NSD_GroupBox_STYLE ${DEFAULT_STYLES}|${BS_GROUPBOX} +!define __NSD_GroupBox_EXSTYLE ${WS_EX_TRANSPARENT} + +!define __NSD_CheckBox_CLASS BUTTON +!define __NSD_CheckBox_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${BS_TEXT}|${BS_TOP}|${BS_AUTOCHECKBOX}|${BS_MULTILINE} +!define __NSD_CheckBox_EXSTYLE 0 + +!define __NSD_RadioButton_CLASS BUTTON +!define __NSD_RadioButton_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${BS_TEXT}|${BS_VCENTER}|${BS_AUTORADIOBUTTON}|${BS_MULTILINE} +!define __NSD_RadioButton_EXSTYLE 0 + +!define __NSD_Text_CLASS EDIT +!define __NSD_Text_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${ES_AUTOHSCROLL} +!define __NSD_Text_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_Password_CLASS EDIT +!define __NSD_Password_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${ES_AUTOHSCROLL}|${ES_PASSWORD} +!define __NSD_Password_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_Number_CLASS EDIT +!define __NSD_Number_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${ES_AUTOHSCROLL}|${ES_NUMBER} +!define __NSD_Number_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_FileRequest_CLASS EDIT +!define __NSD_FileRequest_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${ES_AUTOHSCROLL} +!define __NSD_FileRequest_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_DirRequest_CLASS EDIT +!define __NSD_DirRequest_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${ES_AUTOHSCROLL} +!define __NSD_DirRequest_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_ComboBox_CLASS COMBOBOX +!define __NSD_ComboBox_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${WS_VSCROLL}|${WS_CLIPCHILDREN}|${CBS_AUTOHSCROLL}|${CBS_HASSTRINGS}|${CBS_DROPDOWN} +!define __NSD_ComboBox_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_DropList_CLASS COMBOBOX +!define __NSD_DropList_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${WS_VSCROLL}|${WS_CLIPCHILDREN}|${CBS_AUTOHSCROLL}|${CBS_HASSTRINGS}|${CBS_DROPDOWNLIST} +!define __NSD_DropList_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_ListBox_CLASS LISTBOX +!define __NSD_ListBox_STYLE ${DEFAULT_STYLES}|${WS_TABSTOP}|${WS_VSCROLL}|${LBS_DISABLENOSCROLL}|${LBS_HASSTRINGS}|${LBS_NOINTEGRALHEIGHT}|${LBS_NOTIFY} +!define __NSD_ListBox_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!define __NSD_ProgressBar_CLASS msctls_progress32 +!define __NSD_ProgressBar_STYLE ${DEFAULT_STYLES} +!define __NSD_ProgressBar_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + +!macro __NSD_DefineControl NAME + + !define NSD_Create${NAME} "nsDialogs::CreateControl ${__NSD_${Name}_CLASS} ${__NSD_${Name}_STYLE} ${__NSD_${Name}_EXSTYLE}" + +!macroend + +!insertmacro __NSD_DefineControl HLine +!insertmacro __NSD_DefineControl VLine +!insertmacro __NSD_DefineControl Label +!insertmacro __NSD_DefineControl Icon +!insertmacro __NSD_DefineControl Bitmap +!insertmacro __NSD_DefineControl BrowseButton +!insertmacro __NSD_DefineControl Link +!insertmacro __NSD_DefineControl Button +!insertmacro __NSD_DefineControl GroupBox +!insertmacro __NSD_DefineControl CheckBox +!insertmacro __NSD_DefineControl RadioButton +!insertmacro __NSD_DefineControl Text +!insertmacro __NSD_DefineControl Password +!insertmacro __NSD_DefineControl Number +!insertmacro __NSD_DefineControl FileRequest +!insertmacro __NSD_DefineControl DirRequest +!insertmacro __NSD_DefineControl ComboBox +!insertmacro __NSD_DefineControl DropList +!insertmacro __NSD_DefineControl ListBox +!insertmacro __NSD_DefineControl ProgressBar + +!macro __NSD_OnControlEvent EVENT HWND FUNCTION + + Push $0 + Push $1 + + StrCpy $1 "${HWND}" + + GetFunctionAddress $0 "${FUNCTION}" + nsDialogs::On${EVENT} $1 $0 + + Pop $1 + Pop $0 + +!macroend + +!macro __NSD_DefineControlCallback EVENT + + !define NSD_On${EVENT} `!insertmacro __NSD_OnControlEvent ${EVENT}` + +!macroend + +!macro __NSD_OnDialogEvent EVENT FUNCTION + + Push $0 + + GetFunctionAddress $0 "${FUNCTION}" + nsDialogs::On${EVENT} $0 + + Pop $0 + +!macroend + +!macro __NSD_DefineDialogCallback EVENT + + !define NSD_On${EVENT} `!insertmacro __NSD_OnDialogEvent ${EVENT}` + +!macroend + +!insertmacro __NSD_DefineControlCallback Click +!insertmacro __NSD_DefineControlCallback Change +!insertmacro __NSD_DefineControlCallback Notify +!insertmacro __NSD_DefineDialogCallback Back + +!macro _NSD_CreateTimer FUNCTION INTERVAL + + Push $0 + + GetFunctionAddress $0 "${FUNCTION}" + nsDialogs::CreateTimer $0 "${INTERVAL}" + + Pop $0 + +!macroend + +!define NSD_CreateTimer `!insertmacro _NSD_CreateTimer` + +!macro _NSD_KillTimer FUNCTION + + Push $0 + + GetFunctionAddress $0 "${FUNCTION}" + nsDialogs::KillTimer $0 + + Pop $0 + +!macroend + +!define NSD_KillTimer `!insertmacro _NSD_KillTimer` + +!macro _NSD_AddStyle CONTROL STYLE + + Push $0 + + System::Call "user32::GetWindowLong(i ${CONTROL}, i ${GWL_STYLE}) i .r0" + System::Call "user32::SetWindowLong(i ${CONTROL}, i ${GWL_STYLE}, i $0|${STYLE})" + + Pop $0 + +!macroend + +!define NSD_AddStyle "!insertmacro _NSD_AddStyle" + +!macro _NSD_AddExStyle CONTROL EXSTYLE + + Push $0 + + System::Call "user32::GetWindowLong(i ${CONTROL}, i ${GWL_EXSTYLE}) i .r0" + System::Call "user32::SetWindowLong(i ${CONTROL}, i ${GWL_EXSTYLE}, i $0|${EXSTYLE})" + + Pop $0 + +!macroend + +!define NSD_AddExStyle "!insertmacro _NSD_AddExStyle" + +!macro __NSD_GetText CONTROL VAR + + System::Call user32::GetWindowText(i${CONTROL},t.s,i${NSIS_MAX_STRLEN}) + Pop ${VAR} + +!macroend + +!define NSD_GetText `!insertmacro __NSD_GetText` + +!macro __NSD_SetText CONTROL TEXT + + SendMessage ${CONTROL} ${WM_SETTEXT} 0 `STR:${TEXT}` + +!macroend + +!define NSD_SetText `!insertmacro __NSD_SetText` + +!macro _NSD_SetTextLimit CONTROL LIMIT + + SendMessage ${CONTROL} ${EM_SETLIMITTEXT} ${LIMIT} 0 + +!macroend + +!define NSD_SetTextLimit "!insertmacro _NSD_SetTextLimit" + +!macro __NSD_GetState CONTROL VAR + + SendMessage ${CONTROL} ${BM_GETCHECK} 0 0 ${VAR} + +!macroend + +!define NSD_GetState `!insertmacro __NSD_GetState` + +!macro __NSD_SetState CONTROL STATE + + SendMessage ${CONTROL} ${BM_SETCHECK} ${STATE} 0 + +!macroend + +!define NSD_SetState `!insertmacro __NSD_SetState` + +!macro __NSD_Check CONTROL + + ${NSD_SetState} ${CONTROL} ${BST_CHECKED} + +!macroend + +!define NSD_Check `!insertmacro __NSD_Check` + +!macro __NSD_Uncheck CONTROL + + ${NSD_SetState} ${CONTROL} ${BST_UNCHECKED} + +!macroend + +!define NSD_Uncheck `!insertmacro __NSD_Uncheck` + +!macro __NSD_SetFocus HWND + + System::Call "user32::SetFocus(i${HWND})" + +!macroend + +!define NSD_SetFocus `!insertmacro __NSD_SetFocus` + +!macro _NSD_CB_AddString CONTROL STRING + + SendMessage ${CONTROL} ${CB_ADDSTRING} 0 `STR:${STRING}` + +!macroend + +!define NSD_CB_AddString "!insertmacro _NSD_CB_AddString" + +!macro _NSD_CB_SelectString CONTROL STRING + + SendMessage ${CONTROL} ${CB_SELECTSTRING} -1 `STR:${STRING}` + +!macroend + +!define NSD_CB_SelectString "!insertmacro _NSD_CB_SelectString" + +!macro _NSD_LB_AddString CONTROL STRING + + SendMessage ${CONTROL} ${LB_ADDSTRING} 0 `STR:${STRING}` + +!macroend + +!define NSD_LB_AddString "!insertmacro _NSD_LB_AddString" + +!macro __NSD_LB_DelString CONTROL STRING + + SendMessage ${CONTROL} ${LB_DELETESTRING} 0 `STR:${STRING}` + +!macroend + +!define NSD_LB_DelString `!insertmacro __NSD_LB_DelString` + +!macro __NSD_LB_Clear CONTROL VAR + + SendMessage ${CONTROL} ${LB_RESETCONTENT} 0 0 ${VAR} + +!macroend + +!define NSD_LB_Clear `!insertmacro __NSD_LB_Clear` + +!macro __NSD_LB_GetCount CONTROL VAR + + SendMessage ${CONTROL} ${LB_GETCOUNT} 0 0 ${VAR} + +!macroend + +!define NSD_LB_GetCount `!insertmacro __NSD_LB_GetCount` + +!macro _NSD_LB_SelectString CONTROL STRING + + SendMessage ${CONTROL} ${LB_SELECTSTRING} -1 `STR:${STRING}` + +!macroend + +!define NSD_LB_SelectString "!insertmacro _NSD_LB_SelectString" + +!macro __NSD_LB_GetSelection CONTROL VAR + + SendMessage ${CONTROL} ${LB_GETCURSEL} 0 0 ${VAR} + System::Call 'user32::SendMessage(i ${CONTROL}, i ${LB_GETTEXT}, i ${VAR}, t .s)' + Pop ${VAR} + +!macroend + +!define NSD_LB_GetSelection `!insertmacro __NSD_LB_GetSelection` + + +!macro __NSD_LoadAndSetImage _LIHINSTMODE _IMGTYPE _LIHINSTSRC _LIFLAGS CONTROL IMAGE HANDLE + + Push $0 + Push $R0 + + StrCpy $R0 ${CONTROL} # in case ${CONTROL} is $0 + + !if "${_LIHINSTMODE}" == "exeresource" + System::Call 'kernel32::GetModuleHandle(i0) i.r0' + !undef _LIHINSTSRC + !define _LIHINSTSRC r0 + !endif + + System::Call 'user32::LoadImage(i ${_LIHINSTSRC}, ts, i ${_IMGTYPE}, i0, i0, i${_LIFLAGS}) i.r0' "${IMAGE}" + SendMessage $R0 ${STM_SETIMAGE} ${_IMGTYPE} $0 + + Pop $R0 + Exch $0 + + Pop ${HANDLE} + +!macroend + +!macro __NSD_SetIconFromExeResource CONTROL IMAGE HANDLE + !insertmacro __NSD_LoadAndSetImage exeresource ${IMAGE_ICON} 0 ${LR_DEFAULTSIZE} "${CONTROL}" "${IMAGE}" ${HANDLE} +!macroend + +!macro __NSD_SetIconFromInstaller CONTROL HANDLE + !insertmacro __NSD_SetIconFromExeResource "${CONTROL}" "#103" ${HANDLE} +!macroend + +!define NSD_SetImage `!insertmacro __NSD_LoadAndSetImage file ${IMAGE_BITMAP} 0 "${LR_LOADFROMFILE}"` +!define NSD_SetBitmap `${NSD_SetImage}` + +!define NSD_SetIcon `!insertmacro __NSD_LoadAndSetImage file ${IMAGE_ICON} 0 "${LR_LOADFROMFILE}|${LR_DEFAULTSIZE}"` +!define NSD_SetIconFromExeResource `!insertmacro __NSD_SetIconFromExeResource` +!define NSD_SetIconFromInstaller `!insertmacro __NSD_SetIconFromInstaller` + + +!macro __NSD_SetStretchedImage CONTROL IMAGE HANDLE + + Push $0 + Push $1 + Push $2 + Push $R0 + + StrCpy $R0 ${CONTROL} # in case ${CONTROL} is $0 + + StrCpy $1 "" + StrCpy $2 "" + + System::Call '*(i, i, i, i) i.s' + Pop $0 + + ${If} $0 <> 0 + + System::Call 'user32::GetClientRect(iR0, ir0)' + System::Call '*$0(i, i, i .s, i .s)' + System::Free $0 + Pop $1 + Pop $2 + + ${EndIf} + + System::Call 'user32::LoadImage(i0, ts, i ${IMAGE_BITMAP}, ir1, ir2, i${LR_LOADFROMFILE}) i.s' "${IMAGE}" + Pop $0 + SendMessage $R0 ${STM_SETIMAGE} ${IMAGE_BITMAP} $0 + + Pop $R0 + Pop $2 + Pop $1 + Exch $0 + + Pop ${HANDLE} + +!macroend + +!define NSD_SetStretchedImage `!insertmacro __NSD_SetStretchedImage` + +!macro __NSD_FreeImage IMAGE + + ${If} ${IMAGE} <> 0 + + System::Call gdi32::DeleteObject(is) ${IMAGE} + + ${EndIf} + +!macroend + +!define NSD_FreeImage `!insertmacro __NSD_FreeImage` +!define NSD_FreeBitmap `${NSD_FreeImage}` + +!macro __NSD_FreeIcon IMAGE + System::Call user32::DestroyIcon(is) ${IMAGE} +!macroend + +!define NSD_FreeIcon `!insertmacro __NSD_FreeIcon` + +!macro __NSD_ClearImage _IMGTYPE CONTROL + + SendMessage ${CONTROL} ${STM_SETIMAGE} ${_IMGTYPE} 0 + +!macroend + +!define NSD_ClearImage `!insertmacro __NSD_ClearImage ${IMAGE_BITMAP}` +!define NSD_ClearIcon `!insertmacro __NSD_ClearImage ${IMAGE_ICON}` + + +!define DEBUG `System::Call kernel32::OutputDebugString(ts)` + +!macro __NSD_ControlCase TYPE + + ${Case} ${TYPE} + ${NSD_Create${TYPE}} $R3u $R4u $R5u $R6u $R7 + Pop $R9 + ${Break} + +!macroend + +!macro __NSD_ControlCaseEx TYPE + + ${Case} ${TYPE} + Call ${TYPE} + ${Break} + +!macroend + +!macro NSD_FUNCTION_INIFILE + + !insertmacro NSD_INIFILE "" + +!macroend + +!macro NSD_UNFUNCTION_INIFILE + + !insertmacro NSD_INIFILE un. + +!macroend + +!macro NSD_INIFILE UNINSTALLER_FUNCPREFIX + + ;Functions to create dialogs based on old InstallOptions INI files + + Function ${UNINSTALLER_FUNCPREFIX}CreateDialogFromINI + + # $0 = ini + + ReadINIStr $R0 $0 Settings RECT + ${If} $R0 == "" + StrCpy $R0 1018 + ${EndIf} + + nsDialogs::Create $R0 + Pop $R9 + + ReadINIStr $R0 $0 Settings RTL + nsDialogs::SetRTL $R0 + + ReadINIStr $R0 $0 Settings NumFields + + ${DEBUG} "NumFields = $R0" + + ${For} $R1 1 $R0 + ${DEBUG} "Creating field $R1" + ReadINIStr $R2 $0 "Field $R1" Type + ${DEBUG} " Type = $R2" + ReadINIStr $R3 $0 "Field $R1" Left + ${DEBUG} " Left = $R3" + ReadINIStr $R4 $0 "Field $R1" Top + ${DEBUG} " Top = $R4" + ReadINIStr $R5 $0 "Field $R1" Right + ${DEBUG} " Right = $R5" + ReadINIStr $R6 $0 "Field $R1" Bottom + ${DEBUG} " Bottom = $R6" + IntOp $R5 $R5 - $R3 + ${DEBUG} " Width = $R5" + IntOp $R6 $R6 - $R4 + ${DEBUG} " Height = $R6" + ReadINIStr $R7 $0 "Field $R1" Text + ${DEBUG} " Text = $R7" + ${Switch} $R2 + !insertmacro __NSD_ControlCase HLine + !insertmacro __NSD_ControlCase VLine + !insertmacro __NSD_ControlCase Label + !insertmacro __NSD_ControlCase Icon + !insertmacro __NSD_ControlCase Bitmap + !insertmacro __NSD_ControlCaseEx Link + !insertmacro __NSD_ControlCase Button + !insertmacro __NSD_ControlCase GroupBox + !insertmacro __NSD_ControlCase CheckBox + !insertmacro __NSD_ControlCase RadioButton + !insertmacro __NSD_ControlCase Text + !insertmacro __NSD_ControlCase Password + !insertmacro __NSD_ControlCaseEx FileRequest + !insertmacro __NSD_ControlCaseEx DirRequest + !insertmacro __NSD_ControlCase ComboBox + !insertmacro __NSD_ControlCase DropList + !insertmacro __NSD_ControlCase ListBox + ${EndSwitch} + + WriteINIStr $0 "Field $R1" HWND $R9 + ${Next} + + nsDialogs::Show + + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}UpdateINIState + + ${DEBUG} "Updating INI state" + + ReadINIStr $R0 $0 Settings NumFields + + ${DEBUG} "NumField = $R0" + + ${For} $R1 1 $R0 + ReadINIStr $R2 $0 "Field $R1" HWND + ReadINIStr $R3 $0 "Field $R1" "Type" + ${Switch} $R3 + ${Case} "CheckBox" + ${Case} "RadioButton" + ${DEBUG} " HWND = $R2" + ${NSD_GetState} $R2 $R2 + ${DEBUG} " Window selection = $R2" + ${Break} + ${CaseElse} + ${DEBUG} " HWND = $R2" + ${NSD_GetText} $R2 $R2 + ${DEBUG} " Window text = $R2" + ${Break} + ${EndSwitch} + WriteINIStr $0 "Field $R1" STATE $R2 + ${Next} + + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}FileRequest + + IntOp $R5 $R5 - 15 + IntOp $R8 $R3 + $R5 + + ${NSD_CreateBrowseButton} $R8u $R4u 15u $R6u ... + Pop $R8 + + nsDialogs::SetUserData $R8 $R1 # remember field id + + WriteINIStr $0 "Field $R1" HWND2 $R8 + + ${NSD_OnClick} $R8 ${UNINSTALLER_FUNCPREFIX}OnFileBrowseButton + + ReadINIStr $R9 $0 "Field $R1" State + + ${NSD_CreateFileRequest} $R3u $R4u $R5u $R6u $R9 + Pop $R9 + + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}DirRequest + + IntOp $R5 $R5 - 15 + IntOp $R8 $R3 + $R5 + + ${NSD_CreateBrowseButton} $R8u $R4u 15u $R6u ... + Pop $R8 + + nsDialogs::SetUserData $R8 $R1 # remember field id + + WriteINIStr $0 "Field $R1" HWND2 $R8 + + ${NSD_OnClick} $R8 ${UNINSTALLER_FUNCPREFIX}OnDirBrowseButton + + ReadINIStr $R9 $0 "Field $R1" State + + ${NSD_CreateFileRequest} $R3u $R4u $R5u $R6u $R9 + Pop $R9 + + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}OnFileBrowseButton + + Pop $R0 + + nsDialogs::GetUserData $R0 + Pop $R1 + + ReadINIStr $R2 $0 "Field $R1" HWND + ReadINIStr $R4 $0 "Field $R1" Filter + + ${NSD_GetText} $R2 $R3 + + nsDialogs::SelectFileDialog save $R3 $R4 + Pop $R3 + + ${If} $R3 != "" + SendMessage $R2 ${WM_SETTEXT} 0 STR:$R3 + ${EndIf} + + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}OnDirBrowseButton + + Pop $R0 + + nsDialogs::GetUserData $R0 + Pop $R1 + + ReadINIStr $R2 $0 "Field $R1" HWND + ReadINIStr $R3 $0 "Field $R1" Text + + ${NSD_GetText} $R2 $R4 + + nsDialogs::SelectFolderDialog $R3 $R4 + Pop $R3 + + ${If} $R3 != error + SendMessage $R2 ${WM_SETTEXT} 0 STR:$R3 + ${EndIf} + + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}Link + + ${NSD_CreateLink} $R3u $R4u $R5u $R6u $R7 + Pop $R9 + + nsDialogs::SetUserData $R9 $R1 # remember field id + + ${NSD_OnClick} $R9 ${UNINSTALLER_FUNCPREFIX}OnLink + + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}OnLink + + Pop $R0 + + nsDialogs::GetUserData $R0 + Pop $R1 + + ReadINIStr $R1 $0 "Field $R1" STATE + + ExecShell "" $R1 + + FunctionEnd + +!macroend + +!verbose pop +!endif diff --git a/installer/NSIS/Include/x64.nsh b/installer/NSIS/Include/x64.nsh new file mode 100644 index 0000000..e694c1e --- /dev/null +++ b/installer/NSIS/Include/x64.nsh @@ -0,0 +1,54 @@ +; --------------------- +; x64.nsh +; --------------------- +; +; A few simple macros to handle installations on x64 machines. +; +; RunningX64 checks if the installer is running on x64. +; +; ${If} ${RunningX64} +; MessageBox MB_OK "running on x64" +; ${EndIf} +; +; DisableX64FSRedirection disables file system redirection. +; EnableX64FSRedirection enables file system redirection. +; +; SetOutPath $SYSDIR +; ${DisableX64FSRedirection} +; File some.dll # extracts to C:\Windows\System32 +; ${EnableX64FSRedirection} +; File some.dll # extracts to C:\Windows\SysWOW64 +; + +!ifndef ___X64__NSH___ +!define ___X64__NSH___ + +!include LogicLib.nsh + +!macro _RunningX64 _a _b _t _f + !insertmacro _LOGICLIB_TEMP + System::Call kernel32::GetCurrentProcess()i.s + System::Call kernel32::IsWow64Process(is,*i.s) + Pop $_LOGICLIB_TEMP + !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}` +!macroend + +!define RunningX64 `"" RunningX64 ""` + +!macro DisableX64FSRedirection + + System::Call kernel32::Wow64EnableWow64FsRedirection(i0) + +!macroend + +!define DisableX64FSRedirection "!insertmacro DisableX64FSRedirection" + +!macro EnableX64FSRedirection + + System::Call kernel32::Wow64EnableWow64FsRedirection(i1) + +!macroend + +!define EnableX64FSRedirection "!insertmacro EnableX64FSRedirection" + +!endif # !___X64__NSH___ diff --git a/installer/NSIS/Menu/images/header.gif b/installer/NSIS/Menu/images/header.gif new file mode 100644 index 0000000..0fc92d6 Binary files /dev/null and b/installer/NSIS/Menu/images/header.gif differ diff --git a/installer/NSIS/Menu/images/line.gif b/installer/NSIS/Menu/images/line.gif new file mode 100644 index 0000000..ed78ca4 Binary files /dev/null and b/installer/NSIS/Menu/images/line.gif differ diff --git a/installer/NSIS/Menu/images/site.gif b/installer/NSIS/Menu/images/site.gif new file mode 100644 index 0000000..7569a96 Binary files /dev/null and b/installer/NSIS/Menu/images/site.gif differ diff --git a/installer/NSIS/Menu/index.html b/installer/NSIS/Menu/index.html new file mode 100644 index 0000000..d3a9bb9 --- /dev/null +++ b/installer/NSIS/Menu/index.html @@ -0,0 +1,63 @@ + + +
    +
    + + + + + + + +
    + +

    + Compiler
    + Compile NSI scripts
    + Installer based on ZIP file
    +
    +
    +
    +

    +

    + Developer Center
    + Many more examples, tutorials, plug-ins and NSIS-releted software are available + at the on-line Developer Center. +

    +
    +

    + Documentation
    + NSIS Users Manual
    + Example scripts
    + Modern UI 2
    + Multi-User Header File
    + StrFunc Header File

    +

    + On-line help
    + Forum
    + FAQ
    + IRC Channel
    + Bug Tracker
    +

    +

    + Plug-ins
    + AdvSplash - splash with fade in/out
    + Banner - banner with custom text
    + BgImage - background image
    + Dialer - internet connection
    + Math - math operations
    + nsDialogs - custom wizard pages
    + nsExec - launch command line tools
    + NSISdl - download files
    + Splash - splash screen
    + StartMenu - Start Menu folder selection
    + System - Windows API calls
    + VPatch - update existing files

    +
    +

    +

    +
    + +
    + + diff --git a/installer/NSIS/NSIS.chm b/installer/NSIS/NSIS.chm new file mode 100644 index 0000000..9897fd7 Binary files /dev/null and b/installer/NSIS/NSIS.chm differ diff --git a/installer/NSIS/NSIS.exe b/installer/NSIS/NSIS.exe new file mode 100644 index 0000000..d5c8d8c Binary files /dev/null and b/installer/NSIS/NSIS.exe differ diff --git a/installer/NSIS/Plugins/AdvSplash.dll b/installer/NSIS/Plugins/AdvSplash.dll new file mode 100644 index 0000000..1675529 Binary files /dev/null and b/installer/NSIS/Plugins/AdvSplash.dll differ diff --git a/installer/NSIS/Plugins/Banner.dll b/installer/NSIS/Plugins/Banner.dll new file mode 100644 index 0000000..d070480 Binary files /dev/null and b/installer/NSIS/Plugins/Banner.dll differ diff --git a/installer/NSIS/Plugins/BgImage.dll b/installer/NSIS/Plugins/BgImage.dll new file mode 100644 index 0000000..c4a124a Binary files /dev/null and b/installer/NSIS/Plugins/BgImage.dll differ diff --git a/installer/NSIS/Plugins/Dialer.dll b/installer/NSIS/Plugins/Dialer.dll new file mode 100644 index 0000000..b244ff1 Binary files /dev/null and b/installer/NSIS/Plugins/Dialer.dll differ diff --git a/installer/NSIS/Plugins/InstallOptions.dll b/installer/NSIS/Plugins/InstallOptions.dll new file mode 100644 index 0000000..3f7d9a2 Binary files /dev/null and b/installer/NSIS/Plugins/InstallOptions.dll differ diff --git a/installer/NSIS/Plugins/InstallOptionsEx.dll b/installer/NSIS/Plugins/InstallOptionsEx.dll new file mode 100644 index 0000000..d5abd82 Binary files /dev/null and b/installer/NSIS/Plugins/InstallOptionsEx.dll differ diff --git a/installer/NSIS/Plugins/LangDLL.dll b/installer/NSIS/Plugins/LangDLL.dll new file mode 100644 index 0000000..a2cf7c6 Binary files /dev/null and b/installer/NSIS/Plugins/LangDLL.dll differ diff --git a/installer/NSIS/Plugins/Math.dll b/installer/NSIS/Plugins/Math.dll new file mode 100644 index 0000000..a931656 Binary files /dev/null and b/installer/NSIS/Plugins/Math.dll differ diff --git a/installer/NSIS/Plugins/NSISdl.dll b/installer/NSIS/Plugins/NSISdl.dll new file mode 100644 index 0000000..bc4b6b3 Binary files /dev/null and b/installer/NSIS/Plugins/NSISdl.dll differ diff --git a/installer/NSIS/Plugins/Splash.dll b/installer/NSIS/Plugins/Splash.dll new file mode 100644 index 0000000..3c48d5e Binary files /dev/null and b/installer/NSIS/Plugins/Splash.dll differ diff --git a/installer/NSIS/Plugins/StartMenu.dll b/installer/NSIS/Plugins/StartMenu.dll new file mode 100644 index 0000000..e3f9ef2 Binary files /dev/null and b/installer/NSIS/Plugins/StartMenu.dll differ diff --git a/installer/NSIS/Plugins/System.dll b/installer/NSIS/Plugins/System.dll new file mode 100644 index 0000000..0b3d567 Binary files /dev/null and b/installer/NSIS/Plugins/System.dll differ diff --git a/installer/NSIS/Plugins/TypeLib.dll b/installer/NSIS/Plugins/TypeLib.dll new file mode 100644 index 0000000..4c499d2 Binary files /dev/null and b/installer/NSIS/Plugins/TypeLib.dll differ diff --git a/installer/NSIS/Plugins/UserInfo.dll b/installer/NSIS/Plugins/UserInfo.dll new file mode 100644 index 0000000..6382816 Binary files /dev/null and b/installer/NSIS/Plugins/UserInfo.dll differ diff --git a/installer/NSIS/Plugins/VPatch.dll b/installer/NSIS/Plugins/VPatch.dll new file mode 100644 index 0000000..8f0d85b Binary files /dev/null and b/installer/NSIS/Plugins/VPatch.dll differ diff --git a/installer/NSIS/Plugins/inetc.dll b/installer/NSIS/Plugins/inetc.dll new file mode 100644 index 0000000..7e93cad Binary files /dev/null and b/installer/NSIS/Plugins/inetc.dll differ diff --git a/installer/NSIS/Plugins/nsDialogs.dll b/installer/NSIS/Plugins/nsDialogs.dll new file mode 100644 index 0000000..7718167 Binary files /dev/null and b/installer/NSIS/Plugins/nsDialogs.dll differ diff --git a/installer/NSIS/Plugins/nsExec.dll b/installer/NSIS/Plugins/nsExec.dll new file mode 100644 index 0000000..fa2f8f0 Binary files /dev/null and b/installer/NSIS/Plugins/nsExec.dll differ diff --git a/installer/NSIS/Plugins/nsRandom.dll b/installer/NSIS/Plugins/nsRandom.dll new file mode 100644 index 0000000..4fb5ef7 Binary files /dev/null and b/installer/NSIS/Plugins/nsRandom.dll differ diff --git a/installer/NSIS/Plugins/nsisunz.dll b/installer/NSIS/Plugins/nsisunz.dll new file mode 100644 index 0000000..5466f15 Binary files /dev/null and b/installer/NSIS/Plugins/nsisunz.dll differ diff --git a/installer/NSIS/Stubs/bzip2 b/installer/NSIS/Stubs/bzip2 new file mode 100644 index 0000000..fdf50ab Binary files /dev/null and b/installer/NSIS/Stubs/bzip2 differ diff --git a/installer/NSIS/Stubs/bzip2_solid b/installer/NSIS/Stubs/bzip2_solid new file mode 100644 index 0000000..2c32e76 Binary files /dev/null and b/installer/NSIS/Stubs/bzip2_solid differ diff --git a/installer/NSIS/Stubs/lzma b/installer/NSIS/Stubs/lzma new file mode 100644 index 0000000..398fe72 Binary files /dev/null and b/installer/NSIS/Stubs/lzma differ diff --git a/installer/NSIS/Stubs/lzma_solid b/installer/NSIS/Stubs/lzma_solid new file mode 100644 index 0000000..659794f Binary files /dev/null and b/installer/NSIS/Stubs/lzma_solid differ diff --git a/installer/NSIS/Stubs/uninst b/installer/NSIS/Stubs/uninst new file mode 100644 index 0000000..90d7d22 Binary files /dev/null and b/installer/NSIS/Stubs/uninst differ diff --git a/installer/NSIS/Stubs/zlib b/installer/NSIS/Stubs/zlib new file mode 100644 index 0000000..6dcabf7 Binary files /dev/null and b/installer/NSIS/Stubs/zlib differ diff --git a/installer/NSIS/Stubs/zlib_solid b/installer/NSIS/Stubs/zlib_solid new file mode 100644 index 0000000..055ff20 Binary files /dev/null and b/installer/NSIS/Stubs/zlib_solid differ diff --git a/installer/NSIS/makensis.exe b/installer/NSIS/makensis.exe new file mode 100644 index 0000000..c0b93bc Binary files /dev/null and b/installer/NSIS/makensis.exe differ diff --git a/installer/NSIS/makensisw.exe b/installer/NSIS/makensisw.exe new file mode 100644 index 0000000..774d7ba Binary files /dev/null and b/installer/NSIS/makensisw.exe differ diff --git a/installer/NSIS/nsisconf.nsh b/installer/NSIS/nsisconf.nsh new file mode 100644 index 0000000..48a4915 --- /dev/null +++ b/installer/NSIS/nsisconf.nsh @@ -0,0 +1,62 @@ +;------------------------ +;DEFAULT NSIS CONFIG FILE +;------------------------ + +;This header file will be included when compiling any NSIS installer, +;you can use it to add script code to every installer you compile. + +;This file is treated as if it is in the directory of your script. +;When using relative paths, the files have to be in your build directory. + +;------------------------ +;EXAMPLES +;------------------------ + +;Compress installer exehead with an executable compressor (such as UPX / Petite). + +;Paths should be absolute to allow building from any location. +;Note that your executable compressor should not compress the first icon. + +;!packhdr temp.dat '"C:\Program Files\upx\upx" -9 -q temp.dat' +;!packhdr temp.dat '"C:\Program Files\petite\petite" -9 -b0 -r** -p0 -y temp.dat' + +;------------------------ + +;Set default compressor + +;SetCompressor bzip2 + +;------------------------ + +;Change the default icons + +;Icon "${NSISDIR}\Contrib\Graphics\Icons\arrow-install.ico" +;UninstallIcon "${NSISDIR}\Contrib\Graphics\Icons\arrow-uninstall.ico" + +;------------------------ + +;Define symbols + +;!define COMPANYNAME "bla" + +;------------------------ +;MODERN UI +;------------------------ + +;The Modern UI will insert the MUI_NSISCONF macro just before processing the settings. +;Here you can set default settings for the Modern UI. + +;------------------------ + +!define MUI_INSERT_NSISCONF + +!macro MUI_NSISCONF + + ;Example: Change the default Modern UI icons + + ;!ifndef MUI_ICON & MUI_UNICON + ; !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\arrow-install.ico" + ; !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\arrow-uninstall.ico" + ;!endif + +!macroend \ No newline at end of file diff --git a/installer/OCSetupHlp.dll b/installer/OCSetupHlp.dll new file mode 100644 index 0000000..ccc1e72 Binary files /dev/null and b/installer/OCSetupHlp.dll differ diff --git a/installer/OCSetupHlp.nsh b/installer/OCSetupHlp.nsh new file mode 100644 index 0000000..a686d14 --- /dev/null +++ b/installer/OCSetupHlp.nsh @@ -0,0 +1,1463 @@ +; +; OCSetupHlp.nsh +; -------------- +; +; OpenCandy Helper Include File +; +; This file defines a few macros that need to be called +; from your main installer script in order to initialize and +; setup OpenCandy. +; +; Copyright (c) 2008, 2010 - OpenCandy, Inc. +; + +!include FileFunc.nsh +!include nsDialogs.nsh +!insertmacro GetParameters + +; Local Variables + +Var OCUseOfferPage +Var OCPageTitle +Var OCPageDesc +Var OCDetached +Var OCDialog +Var OCPPLabel +Var OCTOULabel +Var OCProductKey +Var OCNoCandy +Var OCRemnant +Var OCOSSupported +Var OCInitcode + +; +; Local Functions +; ----------------- +; +; These functions are only used here, and +; are not part of the exposed OC API +; + +!ifndef NSIS_UNICODE + +; +; ConvertMultiByte (converts between codepages) +; ---------------- +; +; We use this function to convert from +; UTF-8 (codepage 65001) to the codepage +; NSIS is using ($LANGUAGE) for server-sent +; strings that need to be displayed +; +; Usage: +; ------ +; Push "Blah" # Input text +; Push "65001" # Input text code page (usually CP_UTF8) +; Push "1036" # Output text code page (Usually $LANGUAGE) +; Call ConvertMultiByte # Actual Function Call +; Pop $0 # Retrieve Output text +; + +Function ConvertMultiByte + + Exch $2 + Exch + Exch $1 + Exch + Exch 2 + Exch $0 + Exch 2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + + StrLen $3 $0 + IntOp $3 $3 + 1 + IntOp $4 $3 * 2 + IntOp $5 $3 * 3 + + System::Alloc /NOUNLOAD $4 + Pop $6 + System::Call /NOUNLOAD 'kernel32::MultiByteToWideChar(i $1, i 0, t r0, i r3, i r6, i r3) i .r8' + + System::Alloc /NOUNLOAD $5 + Pop $7 + System::Call /NOUNLOAD 'kernel32::WideCharToMultiByte(i $2, i 0, i r6, i r8, i r7, i r5, i 0, i 0) i .r9' + + ; Check if we got an error, which may happen if the desired codepage + ; is not installed in which case we will fallback to ANSI codepage (0) + + IntCmp $9 0 ANSI_Needed + Goto ANSI_Not_Needed + +ANSI_Needed: + + System::Call /NOUNLOAD 'kernel32::WideCharToMultiByte(i 0, i 0, i r6, i r8, i r7, i r5, i 0, i 0) i .r9' + +ANSI_Not_Needed: + + System::Call /NOUNLOAD '*$7(&t$5 .r0)' + System::Free /NOUNLOAD $6 + System::Free $7 + + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + + Exch $0 + +FunctionEnd + +!endif ; NSIS_UNICODE + +; +; GetLanguageString +; + +Function GetLanguageString + + ${If} $LANGUAGE == 1078 + StrCpy $0 "af" ; Afrikaans + ${ElseIf} $LANGUAGE == 1052 + StrCpy $0 "sq" ; Albanian + ${ElseIf} $LANGUAGE == 1118 + StrCpy $0 "am" ; Amharic + ${ElseIf} $LANGUAGE == 1025 + StrCpy $0 "ar" ; Arabic - Saudi Arabia + ${ElseIf} $LANGUAGE == 5121 + StrCpy $0 "ar" ; Arabic - Algeria + ${ElseIf} $LANGUAGE == 15361 + StrCpy $0 "ar" ; Arabic - Bahrain + ${ElseIf} $LANGUAGE == 3073 + StrCpy $0 "ar" ; Arabic - Egypt + ${ElseIf} $LANGUAGE == 2049 + StrCpy $0 "ar" ; Arabic - Iraq + ${ElseIf} $LANGUAGE == 11265 + StrCpy $0 "ar" ; Arabic - Jordan + ${ElseIf} $LANGUAGE == 13313 + StrCpy $0 "ar" ; Arabic - Kuwait + ${ElseIf} $LANGUAGE == 12289 + StrCpy $0 "ar" ; Arabic - Lebanon + ${ElseIf} $LANGUAGE == 4097 + StrCpy $0 "ar" ; Arabic - Libya + ${ElseIf} $LANGUAGE == 6145 + StrCpy $0 "ar" ; Arabic - Morocco + ${ElseIf} $LANGUAGE == 8193 + StrCpy $0 "ar" ; Arabic - Oman + ${ElseIf} $LANGUAGE == 16385 + StrCpy $0 "ar" ; Arabic - Qatar + ${ElseIf} $LANGUAGE == 10241 + StrCpy $0 "ar" ; Arabic - Syria + ${ElseIf} $LANGUAGE == 7169 + StrCpy $0 "ar" ; Arabic - Tunisia + ${ElseIf} $LANGUAGE == 14337 + StrCpy $0 "ar" ; Arabic - U.A.E. + ${ElseIf} $LANGUAGE == 9217 + StrCpy $0 "ar" ; Arabic - Yemen + ${ElseIf} $LANGUAGE == 1067 + StrCpy $0 "hy" ; Armenian - Armenia + ${ElseIf} $LANGUAGE == 1101 + StrCpy $0 "as" ; Assamese + ${ElseIf} $LANGUAGE == 2092 + StrCpy $0 "az" ; Azeri (Cyrillic) + ${ElseIf} $LANGUAGE == 1068 + StrCpy $0 "az" ; Azeri (Latin) + ${ElseIf} $LANGUAGE == 1069 + StrCpy $0 "eu" ; Basque + ${ElseIf} $LANGUAGE == 1059 + StrCpy $0 "be" ; Belarusian + ${ElseIf} $LANGUAGE == 1093 + StrCpy $0 "bn" ; Bengali (India) + ${ElseIf} $LANGUAGE == 2117 + StrCpy $0 "bn" ; Bengali (Bangladesh) + ${ElseIf} $LANGUAGE == 5146 + StrCpy $0 "bs" ; Bosnian (Bosnia/Herzegovina) + ${ElseIf} $LANGUAGE == 1026 + StrCpy $0 "bg" ; Bulgarian + ${ElseIf} $LANGUAGE == 1109 + StrCpy $0 "my" ; Burmese + ${ElseIf} $LANGUAGE == 1027 + StrCpy $0 "ca" ; Catalan + ${ElseIf} $LANGUAGE == 1116 + StrCpy $0 "en" ; Cherokee - United States (no twochar lang code) + ${ElseIf} $LANGUAGE == 2052 + StrCpy $0 "zh" ; Chinese - People's Republic of China + ${ElseIf} $LANGUAGE == 4100 + StrCpy $0 "zh" ; Chinese - Singapore + ${ElseIf} $LANGUAGE == 1028 + StrCpy $0 "zh" ; Chinese - Taiwan + ${ElseIf} $LANGUAGE == 3076 + StrCpy $0 "zh" ; Chinese - Hong Kong SAR + ${ElseIf} $LANGUAGE == 5124 + StrCpy $0 "zh" ; Chinese - Macao SAR + ${ElseIf} $LANGUAGE == 1050 + StrCpy $0 "hr" ; Croatian + ${ElseIf} $LANGUAGE == 4122 + StrCpy $0 "hr" ; Croatian (Bosnia/Herzegovina) + ${ElseIf} $LANGUAGE == 1029 + StrCpy $0 "cs" ; Czech + ${ElseIf} $LANGUAGE == 1030 + StrCpy $0 "da" ; Danish + ${ElseIf} $LANGUAGE == 1125 + StrCpy $0 "dv" ; Divehi + ${ElseIf} $LANGUAGE == 1043 + StrCpy $0 "nl" ; Dutch - Netherlands + ${ElseIf} $LANGUAGE == 2067 + StrCpy $0 "nl" ; Dutch - Belgium + ${ElseIf} $LANGUAGE == 1126 + StrCpy $0 "en" ; Edo (NOT FOUND) + ${ElseIf} $LANGUAGE == 1033 + StrCpy $0 "en" ; English - United States + ${ElseIf} $LANGUAGE == 2057 + StrCpy $0 "en" ; English - United Kingdom + ${ElseIf} $LANGUAGE == 3081 + StrCpy $0 "en" ; English - Australia + ${ElseIf} $LANGUAGE == 10249 + StrCpy $0 "en" ; English - Belize + ${ElseIf} $LANGUAGE == 4105 + StrCpy $0 "en" ; English - Canada + ${ElseIf} $LANGUAGE == 9225 + StrCpy $0 "en" ; English - Caribbean + ${ElseIf} $LANGUAGE == 15369 + StrCpy $0 "en" ; English - Hong Kong SAR + ${ElseIf} $LANGUAGE == 16393 + StrCpy $0 "en" ; English - India + ${ElseIf} $LANGUAGE == 14345 + StrCpy $0 "en" ; English - Indonesia + ${ElseIf} $LANGUAGE == 6153 + StrCpy $0 "en" ; English - Ireland + ${ElseIf} $LANGUAGE == 8201 + StrCpy $0 "en" ; English - Jamaica + ${ElseIf} $LANGUAGE == 17417 + StrCpy $0 "en" ; English - Malaysia + ${ElseIf} $LANGUAGE == 5129 + StrCpy $0 "en" ; English - New Zealand + ${ElseIf} $LANGUAGE == 13321 + StrCpy $0 "en" ; English - Philippines + ${ElseIf} $LANGUAGE == 18441 + StrCpy $0 "en" ; English - Singapore + ${ElseIf} $LANGUAGE == 7177 + StrCpy $0 "en" ; English - South Africa + ${ElseIf} $LANGUAGE == 11273 + StrCpy $0 "en" ; English - Trinidad + ${ElseIf} $LANGUAGE == 12297 + StrCpy $0 "en" ; English - Zimbabwe + ${ElseIf} $LANGUAGE == 1061 + StrCpy $0 "et" ; Estonian + ${ElseIf} $LANGUAGE == 1080 + StrCpy $0 "fo" ; Faroese + ${ElseIf} $LANGUAGE == 1065 + StrCpy $0 "en" ; Farsi (NOT FOUND) + ${ElseIf} $LANGUAGE == 1124 + StrCpy $0 "en" ; Filipino (NOT FOUND) + ${ElseIf} $LANGUAGE == 1035 + StrCpy $0 "fi" ; Finnish + ${ElseIf} $LANGUAGE == 1036 + StrCpy $0 "fr" ; French - France + ${ElseIf} $LANGUAGE == 2060 + StrCpy $0 "fr" ; French - Belgium + ${ElseIf} $LANGUAGE == 11276 + StrCpy $0 "fr" ; French - Cameroon + ${ElseIf} $LANGUAGE == 3084 + StrCpy $0 "fr" ; French - Canada + ${ElseIf} $LANGUAGE == 9228 + StrCpy $0 "fr" ; French - Democratic Rep. of Congo + ${ElseIf} $LANGUAGE == 12300 + StrCpy $0 "fr" ; French - Cote d'Ivoire + ${ElseIf} $LANGUAGE == 15372 + StrCpy $0 "fr" ; French - Haiti + ${ElseIf} $LANGUAGE == 5132 + StrCpy $0 "fr" ; French - Luxembourg + ${ElseIf} $LANGUAGE == 13324 + StrCpy $0 "fr" ; French - Mali + ${ElseIf} $LANGUAGE == 6156 + StrCpy $0 "fr" ; French - Monaco + ${ElseIf} $LANGUAGE == 14348 + StrCpy $0 "fr" ; French - Morocco + ${ElseIf} $LANGUAGE == 58380 + StrCpy $0 "fr" ; French - North Africa + ${ElseIf} $LANGUAGE == 8204 + StrCpy $0 "fr" ; French - Reunion + ${ElseIf} $LANGUAGE == 10252 + StrCpy $0 "fr" ; French - Senegal + ${ElseIf} $LANGUAGE == 4108 + StrCpy $0 "fr" ; French - Switzerland + ${ElseIf} $LANGUAGE == 7180 + StrCpy $0 "fr" ; French - West Indies + ${ElseIf} $LANGUAGE == 1127 + StrCpy $0 "ff" ; Fulfulde - Nigeria + ${ElseIf} $LANGUAGE == 1122 + StrCpy $0 "fy" ; Frisian - Netherlands + ${ElseIf} $LANGUAGE == 1071 + StrCpy $0 "mk" ; FYRO Macedonian + ${ElseIf} $LANGUAGE == 2108 + StrCpy $0 "gd" ; Gaelic (Ireland) + ${ElseIf} $LANGUAGE == 1084 + StrCpy $0 "gd" ; Gaelic (Scotland) + ${ElseIf} $LANGUAGE == 1110 + StrCpy $0 "gl" ; Galician + ${ElseIf} $LANGUAGE == 1079 + StrCpy $0 "ka" ; Georgian + ${ElseIf} $LANGUAGE == 1031 + StrCpy $0 "de" ; German - Germany + ${ElseIf} $LANGUAGE == 3079 + StrCpy $0 "de" ; German - Austria + ${ElseIf} $LANGUAGE == 5127 + StrCpy $0 "de" ; German - Liechtenstein + ${ElseIf} $LANGUAGE == 4103 + StrCpy $0 "de" ; German - Luxembourg + ${ElseIf} $LANGUAGE == 2055 + StrCpy $0 "de" ; German - Switzerland + ${ElseIf} $LANGUAGE == 1032 + StrCpy $0 "el" ; Greek + ${ElseIf} $LANGUAGE == 1140 + StrCpy $0 "gn" ; Guarani - Paraguay + ${ElseIf} $LANGUAGE == 1095 + StrCpy $0 "gu" ; Gujarati + ${ElseIf} $LANGUAGE == 1128 + StrCpy $0 "ha" ; Hausa - Nigeria + ${ElseIf} $LANGUAGE == 1141 + StrCpy $0 "en" ; Hawaiian - United States (NOT FOUND) + ${ElseIf} $LANGUAGE == 1037 + StrCpy $0 "he" ; Hebrew + ${ElseIf} $LANGUAGE == 1081 + StrCpy $0 "hi" ; Hindi + ${ElseIf} $LANGUAGE == 1038 + StrCpy $0 "hu" ; Hungarian + ${ElseIf} $LANGUAGE == 1129 + StrCpy $0 "ig" ; Ibibio - Nigeria + ${ElseIf} $LANGUAGE == 1039 + StrCpy $0 "is" ; Icelandic + ${ElseIf} $LANGUAGE == 1136 + StrCpy $0 "ig" ; Igbo - Nigeria + ${ElseIf} $LANGUAGE == 1057 + StrCpy $0 "id" ; Indonesian + ${ElseIf} $LANGUAGE == 1117 + StrCpy $0 "iu" ; Inuktitut + ${ElseIf} $LANGUAGE == 1040 + StrCpy $0 "it" ; Italian - Italy + ${ElseIf} $LANGUAGE == 2064 + StrCpy $0 "it" ; Italian - Switzerland + ${ElseIf} $LANGUAGE == 1041 + StrCpy $0 "ja" ; Japanese + ${ElseIf} $LANGUAGE == 1099 + StrCpy $0 "kn" ; Kannada + ${ElseIf} $LANGUAGE == 1137 + StrCpy $0 "kr" ; Kanuri - Nigeria + ${ElseIf} $LANGUAGE == 2144 + StrCpy $0 "ks" ; Kashmiri + ${ElseIf} $LANGUAGE == 1120 + StrCpy $0 "ks" ; Kashmiri (Arabic) + ${ElseIf} $LANGUAGE == 1087 + StrCpy $0 "kk" ; Kazakh + ${ElseIf} $LANGUAGE == 1107 + StrCpy $0 "km" ; Khmer + ${ElseIf} $LANGUAGE == 1111 + StrCpy $0 "ki" ; Konkani + ${ElseIf} $LANGUAGE == 1042 + StrCpy $0 "ko" ; Korean + ${ElseIf} $LANGUAGE == 1088 + StrCpy $0 "ky" ; Kyrgyz (Cyrillic) + ${ElseIf} $LANGUAGE == 1108 + StrCpy $0 "lo" ; Lao + ${ElseIf} $LANGUAGE == 1142 + StrCpy $0 "la" ; Latin + ${ElseIf} $LANGUAGE == 1062 + StrCpy $0 "lv" ; Latvian + ${ElseIf} $LANGUAGE == 1063 + StrCpy $0 "lt" ; Lithuanian + ${ElseIf} $LANGUAGE == 1086 + StrCpy $0 "ms" ; Malay - Malaysia + ${ElseIf} $LANGUAGE == 2110 + StrCpy $0 "ms" ; Malay - Brunei Darussalam + ${ElseIf} $LANGUAGE == 1100 + StrCpy $0 "ml" ; Malayalam + ${ElseIf} $LANGUAGE == 1082 + StrCpy $0 "mt" ; Maltese + ${ElseIf} $LANGUAGE == 1112 + StrCpy $0 "en" ; Manipuri (NOT FOUND) + ${ElseIf} $LANGUAGE == 1153 + StrCpy $0 "mi" ; Maori - New Zealand + ${ElseIf} $LANGUAGE == 1102 + StrCpy $0 "mr" ; Marathi + ${ElseIf} $LANGUAGE == 1104 + StrCpy $0 "mn" ; Mongolian (Cyrillic) + ${ElseIf} $LANGUAGE == 2128 + StrCpy $0 "mn" ; Mongolian (Mongolian) + ${ElseIf} $LANGUAGE == 1121 + StrCpy $0 "ne" ; Nepali + ${ElseIf} $LANGUAGE == 2145 + StrCpy $0 "ne" ; Nepali - India + ${ElseIf} $LANGUAGE == 1044 + StrCpy $0 "nb" ; Norwegian (Bokml) + ${ElseIf} $LANGUAGE == 2068 + StrCpy $0 "no" ; Norwegian (Nynorsk) + ${ElseIf} $LANGUAGE == 1096 + StrCpy $0 "or" ; Oriya + ${ElseIf} $LANGUAGE == 1138 + StrCpy $0 "om" ; Oromo + ${ElseIf} $LANGUAGE == 1145 + StrCpy $0 "en" ; Papiamentu (NOT FOUND) + ${ElseIf} $LANGUAGE == 1123 + StrCpy $0 "ps" ; Pashto + ${ElseIf} $LANGUAGE == 1045 + StrCpy $0 "pl" ; Polish + ${ElseIf} $LANGUAGE == 1046 + StrCpy $0 "pt" ; Portuguese - Brazil + ${ElseIf} $LANGUAGE == 2070 + StrCpy $0 "pt" ; Portuguese - Portugal + ${ElseIf} $LANGUAGE == 1094 + StrCpy $0 "pa" ; Punjabi + ${ElseIf} $LANGUAGE == 2118 + StrCpy $0 "pa" ; Punjabi (Pakistan) + ${ElseIf} $LANGUAGE == 1131 + StrCpy $0 "qu" ; Quecha - Bolivia + ${ElseIf} $LANGUAGE == 2155 + StrCpy $0 "qu" ; Quecha - Ecuador + ${ElseIf} $LANGUAGE == 3179 + StrCpy $0 "qu" ; Quecha - Peru + ${ElseIf} $LANGUAGE == 1047 + StrCpy $0 "rm" ; Rhaeto-Romanic + ${ElseIf} $LANGUAGE == 1048 + StrCpy $0 "ro" ; Romanian + ${ElseIf} $LANGUAGE == 2072 + StrCpy $0 "ro" ; Romanian - Moldava + ${ElseIf} $LANGUAGE == 1049 + StrCpy $0 "ru" ; Russian + ${ElseIf} $LANGUAGE == 2073 + StrCpy $0 "ru" ; Russian - Moldava + ${ElseIf} $LANGUAGE == 1083 + StrCpy $0 "se" ; Sami (Lappish) + ${ElseIf} $LANGUAGE == 1103 + StrCpy $0 "sa" ; Sanskrit + ${ElseIf} $LANGUAGE == 1132 + StrCpy $0 "en" ; Sepedi (NOT FOUND) + ${ElseIf} $LANGUAGE == 3098 + StrCpy $0 "sr" ; Serbian (Cyrillic) + ${ElseIf} $LANGUAGE == 2074 + StrCpy $0 "sr" ; Serbian (Latin) + ${ElseIf} $LANGUAGE == 1113 + StrCpy $0 "" ; Sindhi - India + ${ElseIf} $LANGUAGE == 2137 + StrCpy $0 "sd" ; Sindhi - Pakistan + ${ElseIf} $LANGUAGE == 1115 + StrCpy $0 "si" ; Sinhalese - Sri Lanka + ${ElseIf} $LANGUAGE == 1051 + StrCpy $0 "sk" ; Slovak + ${ElseIf} $LANGUAGE == 1060 + StrCpy $0 "sl" ; Slovenian + ${ElseIf} $LANGUAGE == 1143 + StrCpy $0 "so" ; Somali + ${ElseIf} $LANGUAGE == 1070 + StrCpy $0 "en" ; Sorbian (NOT FOUND) + ${ElseIf} $LANGUAGE == 3082 + StrCpy $0 "es" ; Spanish - Spain (Modern Sort) + ${ElseIf} $LANGUAGE == 1034 + StrCpy $0 "es" ; Spanish - Spain (Traditional Sort) + ${ElseIf} $LANGUAGE == 11274 + StrCpy $0 "es" ; Spanish - Argentina + ${ElseIf} $LANGUAGE == 16394 + StrCpy $0 "es" ; Spanish - Bolivia + ${ElseIf} $LANGUAGE == 13322 + StrCpy $0 "es" ; Spanish - Chile + ${ElseIf} $LANGUAGE == 9226 + StrCpy $0 "es" ; Spanish - Colombia + ${ElseIf} $LANGUAGE == 5130 + StrCpy $0 "es" ; Spanish - Costa Rica + ${ElseIf} $LANGUAGE == 7178 + StrCpy $0 "es" ; Spanish - Dominican Republic + ${ElseIf} $LANGUAGE == 12298 + StrCpy $0 "es" ; Spanish - Ecuador + ${ElseIf} $LANGUAGE == 17418 + StrCpy $0 "es" ; Spanish - El Salvador + ${ElseIf} $LANGUAGE == 4106 + StrCpy $0 "es" ; Spanish - Guatemala + ${ElseIf} $LANGUAGE == 18442 + StrCpy $0 "es" ; Spanish - Honduras + ${ElseIf} $LANGUAGE == 58378 + StrCpy $0 "es" ; Spanish - Latin America + ${ElseIf} $LANGUAGE == 2058 + StrCpy $0 "es" ; Spanish - Mexico + ${ElseIf} $LANGUAGE == 19466 + StrCpy $0 "es" ; Spanish - Nicaragua + ${ElseIf} $LANGUAGE == 6154 + StrCpy $0 "es" ; Spanish - Panama + ${ElseIf} $LANGUAGE == 15370 + StrCpy $0 "es" ; Spanish - Paraguay + ${ElseIf} $LANGUAGE == 10250 + StrCpy $0 "es" ; Spanish - Peru + ${ElseIf} $LANGUAGE == 20490 + StrCpy $0 "es" ; Spanish - Puerto Rico + ${ElseIf} $LANGUAGE == 21514 + StrCpy $0 "es" ; Spanish - United States + ${ElseIf} $LANGUAGE == 14346 + StrCpy $0 "es" ; Spanish - Uruguay + ${ElseIf} $LANGUAGE == 8202 + StrCpy $0 "es" ; Spanish - Venezuela + ${ElseIf} $LANGUAGE == 1072 + StrCpy $0 "en" ; Sutu (NOT FOUND) + ${ElseIf} $LANGUAGE == 1089 + StrCpy $0 "sw" ; Swahili + ${ElseIf} $LANGUAGE == 1053 + StrCpy $0 "sv" ; Swedish + ${ElseIf} $LANGUAGE == 2077 + StrCpy $0 "sv" ; Swedish - Finland + ${ElseIf} $LANGUAGE == 1114 + StrCpy $0 "en" ; Syriac (NOT FOUND) + ${ElseIf} $LANGUAGE == 1064 + StrCpy $0 "tg" ; Tajik + ${ElseIf} $LANGUAGE == 1119 + StrCpy $0 "en" ; Tamazight (Arabic) (NOT FOUND) + ${ElseIf} $LANGUAGE == 2143 + StrCpy $0 "en" ; Tamazight (Latin) (NOT FOUND) + ${ElseIf} $LANGUAGE == 1097 + StrCpy $0 "ta" ; Tamil + ${ElseIf} $LANGUAGE == 1092 + StrCpy $0 "tt" ; Tatar + ${ElseIf} $LANGUAGE == 1098 + StrCpy $0 "te" ; Telugu + ${ElseIf} $LANGUAGE == 1054 + StrCpy $0 "th" ; Thai + ${ElseIf} $LANGUAGE == 2129 + StrCpy $0 "bo" ; Tibetan - Bhutan + ${ElseIf} $LANGUAGE == 1105 + StrCpy $0 "bo" ; Tibetan - People's Republic of China + ${ElseIf} $LANGUAGE == 2163 + StrCpy $0 "ti" ; Tigrigna - Eritrea + ${ElseIf} $LANGUAGE == 1139 + StrCpy $0 "ti" ; Tigrigna - Ethiopia + ${ElseIf} $LANGUAGE == 1073 + StrCpy $0 "ts" ; Tsonga + ${ElseIf} $LANGUAGE == 1074 + StrCpy $0 "tn" ; Tswana + ${ElseIf} $LANGUAGE == 1055 + StrCpy $0 "tr" ; Turkish + ${ElseIf} $LANGUAGE == 1090 + StrCpy $0 "tk" ; Turkmen + ${ElseIf} $LANGUAGE == 1152 + StrCpy $0 "ug" ; Uighur - China + ${ElseIf} $LANGUAGE == 1058 + StrCpy $0 "ug" ; Ukrainian + ${ElseIf} $LANGUAGE == 1056 + StrCpy $0 "ur" ; Urdu + ${ElseIf} $LANGUAGE == 2080 + StrCpy $0 "ur" ; Urdu - India + ${ElseIf} $LANGUAGE == 2115 + StrCpy $0 "uz" ; Uzbek (Cyrillic) + ${ElseIf} $LANGUAGE == 1091 + StrCpy $0 "uz" ; Uzbek (Latin) + ${ElseIf} $LANGUAGE == 1075 + StrCpy $0 "ve" ; Venda + ${ElseIf} $LANGUAGE == 1066 + StrCpy $0 "vi" ; Vietnamese + ${ElseIf} $LANGUAGE == 1106 + StrCpy $0 "cy" ; Welsh + ${ElseIf} $LANGUAGE == 1076 + StrCpy $0 "xh" ; Xhosa + ${ElseIf} $LANGUAGE == 1144 + StrCpy $0 "en" ; Yi (NOT FOUND) + ${ElseIf} $LANGUAGE == 1085 + StrCpy $0 "yi" ; Yiddish + ${ElseIf} $LANGUAGE == 1130 + StrCpy $0 "yo" ; Yoruba + ${ElseIf} $LANGUAGE == 1077 + StrCpy $0 "zu" ; Zulu + ${Else} + StrCpy $0 "en" ; English is our backup + ${Endif} +FunctionEnd + +SetPluginUnload alwaysoff + +!macro CheckNoCandy + ;Fills $0 with 0 if not found 1 if found + push $1 + push $2 + push $3 + ${GetParameters} $0 + + StrLen $1 $0 ;$1 Len of str + IntOp $2 0 + 0 ;offset + +OCLookForNoCandy: + StrCpy $3 $0 8 $2 + StrCmp $3 "/NOCANDY" OCFoundNoCandy + IntCmp $2 $1 OCNoCandyNotHere 0 OCNoCandyNotHere + IntOp $2 $2 + 1 + Goto OCLookForNoCandy + +OCFoundNoCandy: + IntOp $0 0 + 1 + IntOp $OCNoCandy 0 + 1 + Goto OCNoCandyDone + +OCNoCandyNotHere: + IntOp $0 0 + 0 + IntOp $OCNoCandy 0 + 0 + +OCNoCandyDone: + pop $3 + pop $2 + pop $1 +!macroend + +!macro GetCandyParameter + ;Fills $0 with "" if not found OID if found + push $1 + push $2 + push $3 + push $4 + ${GetParameters} $0 + StrLen $1 $0 ;$1 Len of str + IntOp $2 0 + 0 ;offset + +OCLookForCandyRX: + StrCpy $3 $0 8 $2 + StrCmp $3 "/CANDYRX" OCFoundCandyRX + IntCmp $2 $1 OCCandyRXNotHere 0 OCCandyRXNotHere + IntOp $2 $2 + 1 + Goto OCLookForCandyRX + +OCFoundCandyRX: + IntOp $1 $1 - $2 + StrCpy $3 $0 $1 $2 + + ;trim anything past a space if found + IntOp $2 0 + 0 + StrCpy $0 "" + +OCCandyRxSpaceSearch: + StrCpy $4 $3 1 $2 + StrCmp $4 " " OCCandyRXDone + IntOp $2 $2 + 1 + + ;copy what we got so far + StrCpy $0 $3 $2 + + IntCmp $2 $1 OCCandyRXDone OCCandyRxSpaceSearch OCCandyRXDone + +OCCandyRXNotHere: + StrCpy $0 "" + +OCCandyRXDone: + + pop $4 + pop $3 + pop $2 + pop $1 + +!macroend + +!macro OpenCandyInitInternal PublisherName Key Secret Location + + ; -------------------------- OS CHECK ----------------------- + + Push $R0 + Push $R1 + + IntOp $OCOSSupported 0 + 0 + + ClearErrors + + ReadRegStr $R0 HKLM \ + "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + + IfErrors 0 lbl_winnt + + ; we are not NT + GoTo lbl_error + + lbl_winnt: + + StrCpy $R1 $R0 1 + + StrCmp $R1 '3' lbl_done + StrCmp $R1 '4' lbl_done + + StrCpy $R1 $R0 3 + + StrCmp $R1 '5.0' lbl_error + StrCmp $R1 '5.1' lbl_done + StrCmp $R1 '5.2' lbl_done + StrCmp $R1 '6.0' lbl_done + StrCmp $R1 '6.1' lbl_done + + lbl_error: + IntOp $OCOSSupported 0 + 1 + + lbl_done: + + Pop $R1 + Pop $R0 + IntOP $OCUseOfferPage 0 + 0 + IntOP $OCInitcode 0 + 0 + ; -------------------------- OS CHECK ----------------------- + + ${If} $OCOSSupported == 0 + + ; We need to be loaded throughout the setup + ; as we will uload ourselves when necessary + + Push $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + + IntOp $OCDetached 0 + 1 + StrCpy $OCProductKey "${Key}" + + !insertmacro CheckNoCandy + + IntOp $4 $0 + 0 + IntOp $5 $0 + 0 + + ${If} $OCRemnant == 0 + + ${If} $4 == 0 + StrCpy $0 "${PublisherName}" ; Publisher + + StrCpy $1 "${Key}" ; Product "Key" + StrCpy $2 "${Secret}" ; Secret + + ; Get installer language + + Push $0 + Call GetLanguageString + StrCpy $3 $0 + Pop $0 + + StrCpy $4 "${Location}" ;Registry location + + InitPluginsDir + SetOutPath "$PLUGINSDIR" + File "OCSetupHlp.dll" + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCInit2A(m, m, m, m, m)i(r0, r1, r2 ,r3 ,r4).r5? c' +!else + System::Call 'OCSetupHlp::OCInit2A(t, t, t, t, t)i(r0, r1, r2 ,r3 ,r4).r5? c' +!endif + ${Else} + + !insertmacro GetCandyParameter + StrCpy $1 $0 + + StrCpy $0 "${Location}" ;Registry location + StrCpy $2 "${Key}" + + InitPluginsDir + SetOutPath "$PLUGINSDIR" + File "OCSetupHlp.dll" + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCSetOfferLocation(m, m, m)i(r0, r1, r2).r3? c' +!else + System::Call 'OCSetupHlp::OCSetOfferLocation(t, t, t)i(r0, r1, r2).r3? c' +!endif + IntOp $5 0 + 1 + + ${endif} + IntOp $OCInitcode 0 + $5 + ${If} $5 == 0 + IntOp $OCUseOfferPage 1 + 0 + +!ifdef NSIS_UNICODE + + Push $3 + Push $4 + Push $5 + + System::Alloc /NOUNLOAD 1024 + Pop $4 + System::Alloc /NOUNLOAD 1024 + Pop $5 + + System::Call /NOUNLOAD 'OCSetupHlp::OCGetBannerInfo(i, i)i(r4, r5).r2? c' + + System::Call /NOUNLOAD 'kernel32::MultiByteToWideChar(i 65001, i 0, i r4, i -1, t .r0, i 1024) i .r3' + System::Call /NOUNLOAD 'kernel32::MultiByteToWideChar(i 65001, i 0, i r5, i -1, t .r1, i 1024) i .r3' + + System::Free /NOUNLOAD $4 + System::Free $5 + + Pop $5 + Pop $4 + Pop $3 + +!else + System::Call 'OCSetupHlp::OCGetBannerInfo(t, t)i(.r0, .r1).r2? c' +!endif + ${If} $2 == 3 + StrCpy $OCPageTitle $0 + StrCpy $OCPageDesc $1 + ${ElseIf} $2 == 1 + StrCpy $OCPageDesc " " + ${ElseIf} $2 == 2 + StrCpy $OCPageTitle " " + ${Else} + StrCpy $OCPageTitle " " + StrCpy $OCPageDesc " " + ${EndIf} + ${Else} + + IntOp $OCUseOfferPage 0 + 0 + SetPluginUnload manual + ; Do nothing (but let the installer unload the System dll) + System::Free 0 + ${EndIf} + + ${EndIf} + + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 + + ${endif} + +!macroend + +; +; Install Functions +; ----------------- +; + +; +; OpenCandyInit +; +; Performs initialization of the OpenCandy DLL +; and checks for available offers to present. +; +; Parameters are: +; +; PublisherName : Your publisher name (will be provided by OpenCandy) +; Key : Your product key (will be provided by OpenCandy) +; Secret : Your product code (will be provided by OpenCandy) +; Location : A registry path for us to store state information in (will be provided by OpenCandy) +; + +!macro OpenCandyInit PublisherName Key Secret Location + +; user warning messages +!if "${PublisherName}" == "Open Candy Sample" + !warning "Do not forget to change the product name from 'Open Candy Sample' to your company's product name before releasing this installer." +!endif +!if "${Key}" == "1401d0bd8048e1f0f4628dbec1a73092" + !warning "Do not forget to change the test key '1401d0bd8048e1f0f4628dbec1a73092' to your company's product key before releasing this installer." +!endif +!if "${Secret}" == "4564bdaf826bbe2115718d1643ecc19e" + !warning "Do not forget to change the test secret '4564bdaf826bbe2115718d1643ecc19e' to your company's product secret before releasing this installer." +!endif +!if "${Location}" == "Software\Your Company\OpenCandy" + !warning "Do not forget to change the test registry path 'Your Company' to your companies name before releasing this installer." +!endif + + IntOp $OCRemnant 0 + 0 + !insertmacro OpenCandyInitInternal "${PublisherName}" "${Key}" "${Secret}" "${Location}" + +!macroend + +; +; OpenCandyInitRemnant +; +; Performs initialization of the OpenCandy DLL +; and checks for available offers to present. +; +; This function is similar to the previous one +; but allow to specify whrether to show the offer +; page or not (last parameter). This should be used +; when a primary offer (such as a toolbar) also +; exists and OC should not be shown. +; +; Parameters are: +; +; PublisherName : Your publisher name (will be provided by OpenCandy) +; Key : Your product key (will be provided by OpenCandy) +; Secret : Your product code (will be provided by OpenCandy) +; Location : A registry path for us to store state information (will be provided by OpenCandy) +; DontShowOC : Pass 1 to NOT show the offer screen (primary offer is shown instead) +; Pass 0 to SHOW the OC page (primary offer is not shown) +; + +!macro OpenCandyInitRemnant PublisherName Key Secret Location DontShowOC + + IntOp $OCRemnant 0 + 0 + + ${If} $DontShowOC == 1 + IntOp $OCRemnant 1 + 0 + ${Endif} + + !insertmacro OpenCandyInitInternal "${PublisherName}" "${Key}" "${Secret}" "${Location}" + +!macroend + +; +; OpenCandyPageStartFn +; -------------------- +; +; Decides if there is an offer to show and +; if so, sets up the offer page for NSIS +; +; You do not need to call this function, it just +; needs to be a parameter to the custom page +; declared in your setup along with your other pages +; + +Function OpenCandyPageStartFn + + ${If} $OCOSSupported == 0 + Push $0 + + ${If} $OCRemnant == 1 + Abort + ${EndIf} + + ${If} $OCUseOfferPage == 1 + + ${If} $OCDetached == 0 + System::Call 'OCSetupHlp::OCDetach()i.r0? c' + IntOp $OCDetached 0 + 1 + ${EndIf} + + nsDialogs::Create /NOUNLOAD 1018 + Pop $OCDialog + + ${If} $OCDialog == error + Abort + ${Else} + +!ifndef NSIS_UNICODE + Push $OCPageTitle + Push "65001" + Push $LANGUAGE + Call ConvertMultiByte + Pop $OCPageTitle + + Push $OCPageDesc + Push "65001" + Push $LANGUAGE + Call ConvertMultiByte + Pop $OCPageDesc +!endif + + !insertmacro MUI_HEADER_TEXT $OCPageTitle $OCPageDesc + + IntOp $OCDetached 0 + 0 + + ; Check for PP and TOU links + + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + Push $7 + + StrCpy $1 "PP" + StrCpy $2 " " + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCCheckForLink(m,m)i(r1,.r2).r0? c' +!else + System::Call 'OCSetupHlp::OCCheckForLink(t,t)i(r1,.r2).r0? c' +!endif + + ; Create dummy label + ${NSD_CreateLink} 0 0 0 12u $2 + Pop $6 + + ${If} $0 == 1 + + ; Calculate the length of the text + + SendMessage $6 ${WM_GETFONT} 0 0 $5 + System::Call 'user32::GetDC(i)i(r6)i.r4' + System::Call 'gdi32::SelectObject(i,i)i(r4,r5)i.r5' + StrLen $3 $2 + System::Call *(i,i)i.r7 + System::Call 'gdi32::GetTextExtentPoint32(i,t,i,i)i(r4,r2,r3,r7)i.r0' + System::Call *$7(i.r3,i) + IntOp $3 $3 + 5 ;add a little padding + System::Free $7 + System::Call 'gdi32::SelectObject(i,i)i(r4,r5)i.r0' + System::Call 'user32::ReleaseDC(i,i)i(r6,r4)i.r0' + + ; Get positionning + + StrCpy $1 "PP" +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCGetLinkPlacementX(m)i(r1).r4? c' + System::Call 'OCSetupHlp::OCGetLinkPlacementY(m)i(r1).r5? c' +!else + System::Call 'OCSetupHlp::OCGetLinkPlacementX(t)i(r1).r4? c' + System::Call 'OCSetupHlp::OCGetLinkPlacementY(t)i(r1).r5? c' +!endif + ; And create the final label now + + ${If} $4 != -1 + ${AndIf} $5 != -1 + ${NSD_CreateLink} $4 $5 $3 12u $2 + ${Else} + ; Default positionning + ${NSD_CreateLink} 0 176 $3 12u $2 + ${EndIf} + Pop $OCPPLabel + ${NSD_OnClick} $OCPPLabel OCPPLabelClick + ${EndIf} + + StrCpy $1 "TOU" + StrCpy $2 " " +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCCheckForLink(m,m)i(r1,.r2).r0? c' +!else + System::Call 'OCSetupHlp::OCCheckForLink(t,t)i(r1,.r2).r0? c' +!endif + ${If} $0 == 1 + + ; Calculate the length of the text + + SendMessage $6 ${WM_GETFONT} 0 0 $5 + System::Call 'user32::GetDC(i)i(r6)i.r4' + System::Call 'gdi32::SelectObject(i,i)i(r4,r5)i.r5' + StrLen $3 $2 + System::Call *(i,i)i.r7 + System::Call 'gdi32::GetTextExtentPoint32(i,t,i,i)i(r4,r2,r3,r7)i.r0' + System::Call *$7(i.r3,i) + IntOp $3 $3 + 5 ;add a little padding + System::Free $7 + System::Call 'gdi32::SelectObject(i,i)i(r4,r5)i.r0' + System::Call 'user32::ReleaseDC(i,i)i(r6,r4)i.r0' + + ; Get positionning + + StrCpy $1 "TOU" + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCGetLinkPlacementX(m)i(r1).r4? c' + System::Call 'OCSetupHlp::OCGetLinkPlacementY(m)i(r1).r5? c' +!else + System::Call 'OCSetupHlp::OCGetLinkPlacementX(t)i(r1).r4? c' + System::Call 'OCSetupHlp::OCGetLinkPlacementY(t)i(r1).r5? c' +!endif + ; And create the link + + ${If} $4 != -1 + ${AndIf} $5 != -1 + ${NSD_CreateLink} $4 $5 $3 12u $2 + ${Else} + ; Default positionning + ${NSD_CreateLink} 54% 176 $3 12u $2 + ${EndIf} + Pop $OCTOULabel + ${NSD_OnClick} $OCTOULabel OCTOULabelClick + ${EndIf} + + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + + System::Call 'OCSetupHlp::OCNSISAdjust(i, i, i, i, i)i($OCDialog, 14,70, 470, 228).r0? c' + System::Call 'OCSetupHlp::OCRunDialog(i, i, i, i)i($OCDialog, 240, 240 ,240).r0? c' + + nsDialogs::Show + + ${EndIf} + + ${EndIf} + Pop $0 + ${EndIf} +FunctionEnd + +Function OCPPLabelClick + + Push $0 + Push $1 + StrCpy $1 "PP" +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCDisplay(m)i(r1).r0? c' +!else + System::Call 'OCSetupHlp::OCDisplay(t)i(r1).r0? c' +!endif + Pop $1 + Pop $0 + +FunctionEnd + +Function OCTOULabelClick + + Push $0 + Push $1 + StrCpy $1 "TOU" +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCDisplay(m)i(r1).r0? c' +!else + System::Call 'OCSetupHlp::OCDisplay(t)i(r1).r0? c' +!endif + Pop $1 + Pop $0 + +FunctionEnd + +; +; OpenCandyPageLeaveFn +; -------------------- +; +; Decides there if it is ok to leave the +; page and continues with setup +; +; You do not need to call this function, it just +; needs to be a parameter to the custom page +; declared in your setup along with your other pages +; + +Function OpenCandyPageLeaveFn + ${If} $OCOSSupported == 0 + Push $0 + Push $0 + Push $1 + Push $2 + System::Call 'OCSetupHlp::OCGetOfferState()i.r0? c' + ${If} $0 < 0 + StrCpy $1 "PleaseChoose" + StrCpy $2 " " + +!ifdef NSIS_UNICODE + + Push $4 + + System::Alloc /NOUNLOAD 1024 + Pop $4 + + System::Call /NOUNLOAD 'OCSetupHlp::OCGetMsg(m,i)i(r1, r4).r0? c' + System::Call /NOUNLOAD 'kernel32::MultiByteToWideChar(i 65001, i 0, i r4, i -1, t .r2, i 1024) i .r0' + + System::Free $4 + + Pop $4 + +!else + System::Call 'OCSetupHlp::OCGetMsg(t,t)i(r1,.r2).r0? c' + + ; Convert from utf8 to display + Push $2 + Push "65001" + Push $LANGUAGE + Call ConvertMultiByte + Pop $2 +!endif + + MessageBox MB_ICONINFORMATION $2 + Abort + ${Else} + System::Call 'OCSetupHlp::OCDetach()i.r0? c' + IntOp $OCDetached 0 + 1 + ${EndIf} + Pop $2 + Pop $1 + Pop $0 + ${EndIf} +FunctionEnd + +; +; OpenCandyOnInstSuccess +; ---------------------- +; +; This macro needs to be called from the +; NSIS function .onInstSuccess to signal +; a successful installation of the product +; and launch installation of the recommended +; software if any was selected by the user +; + +!macro OpenCandyOnInstSuccess +${If} $OCInitcode != -99 + ${If} $OCOSSupported == 0 + + ${If} $OCNoCandy == 0 + + ${If} $OCRemnant == 0 + + Push $0 + Push $1 + Push $2 + + StrCpy $2 " " +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCGetOfferType(m)i(.r2).r0? c' +!else + System::Call 'OCSetupHlp::OCGetOfferType(t)i(.r2).r0? c' +!endif + ; Check if we are in normal + ; or embedded mode and run accordingy + + ${If} $0 == 1 ; OC_OFFER_TYPE_NORMAL + GetFullPathName /SHORT $1 $INSTDIR\OpenCandy\OCSetupHlp.dll + StrCpy $0 "$1,_MgrCheck@16" + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCExecuteOffer(m)i(r0).r1? c' +!else + System::Call 'OCSetupHlp::OCExecuteOffer(t)i(r0).r1? c' +!endif + + ; Check if the offer was accepted + + Push $3 + System::Call 'OCSetupHlp::OCGetOfferState()i.r3? c' + + ${If} $3 == 1 ; Offer was accepted + + GetFullPathName /SHORT $1 $INSTDIR\OpenCandy\OCSetupHlp.dll + StrCpy $0 "RunDll32.exe $1,_MgrCheck@16 $2" + Exec $0 + + ${EndIf} + + Pop $3 + + ${EndIf} + + Pop $2 + Pop $1 + Pop $0 + + System::Call 'OCSetupHlp::OCSignalProductInstalled()i.r0? c' + + ${EndIf} + ${EndIf} + ${EndIf} +${EndIF} +!macroend + + +; +; OpenCandyOnInstFailed +; ---------------------- +; +; This macro needs to be called from the +; NSIS function .onInstFailed to signal +; a failed installation of the product. +; + +!macro OpenCandyOnInstFailed + ${If} $OCInitcode != -99 + ${If} $OCOSSupported == 0 + ${If} $OCNoCandy == 0 + + Push $0 + System::Call 'OCSetupHlp::OCSignalProductFailed()i.r0? c' + Pop $0 + + ${EndIf} + ${EndIf} + ${EndIF} +!macroend + + + + +; +; OpenCandyOnGuiEnd +; ----------------- +; +; This needs to be called from the NSIS +; function .onGUIEnd to properly unload +; the OpenCandy DLL. We need to have the DLL +; loaded until then as to be able to start +; the recommended software setup at the +; very end of the NSIS install process +; + +!macro OpenCandyOnGuiEnd + + ${If} $OCOSSupported == 0 + + ${If} $OCUseOfferPage != 0 + + ${If} $OCRemnant == 0 + + Push $0 + Push $1 + + StrCpy $1 " " + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCGetOfferType(m)i(.r1).r0? c' +!else + System::Call 'OCSetupHlp::OCGetOfferType(t)i(.r1).r0? c' +!endif + + ; Check if we are in normal + ; or embedded mode and run accordingy + + ${If} $0 == 2 ; OC_OFFER_TYPE_EMBEDDED + + ; We need to delete the OpenCandy folder + RMDir /REBOOTOK "$INSTDIR\OpenCandy" + + ${EndIf} + + ${If} $OCDetached == 0 + System::Call 'OCSetupHlp::OCDetach()i.r0? c' + IntOp $OCDetached 0 + 1 + ${EndIf} + + ; Always call shutdown to cleanup + System::Call 'OCSetupHlp::OCShutdown()i.r0? c' + + IntOp $OCUseOfferPage 0 + 0 + + ${EndIf} + + ${EndIf} + + ; In all cases, let's unload the DLL + ; by calling the system plugin with "u" + System::Call 'OCSetupHlp::OCDetach()i.r0? u' + + ; And clean up after ourselves + SetPluginUnload manual + ; do nothing (but let the installer unload the System dll) + System::Free 0 + + ${EndIf} + +!macroend + +; +; OpenCandyInstallDll +; ------------------------------- +; +; This macro performs the installation of OpenCandy's +; DLL in order to provide the recommended +; software package later on. You need to call this +; macro from a section during the install to make sure +; it is installed with your product +; The DLL will only be installed if the offer was +; previously accepted, and only until the offer is +; downloaded and installed (or cancelled) at which +; point it will be removed from the user's system +; +; + +!macro OpenCandyInstallDll + + ${If} $OCOSSupported == 0 + ${If} $OCNoCandy == 0 + ${If} $OCRemnant == 0 + ${If} $OCUseOfferPage == 1 + Push $0 + + System::Call 'OCSetupHlp::OCGetOfferState()i.r0? c' + + ${If} $0 == 1 + + ; Offer was accepted so let's install the DLL + + CreateDirectory "$INSTDIR\OpenCandy" + SetOutPath "$INSTDIR\OpenCandy" + SetOverwrite on + SetDateSave on + File OCSetupHlp.dll + File OpenCandy_Why_Is_This_Here.txt + + ; And run the Execute call + RunDLL + + Push $0 + Push $1 + Push $2 + + StrCpy $2 " " + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCGetOfferType(m)i(.r2).r0? c' +!else + System::Call 'OCSetupHlp::OCGetOfferType(t)i(.r2).r0? c' +!endif + + ${If} $0 == 2 ; OC_OFFER_TYPE_EMBEDDED + + GetFullPathName /SHORT $1 $INSTDIR\OpenCandy\OCSetupHlp.dll + StrCpy $0 "$1,_MgrCheck@16" + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCExecuteOffer(m)i(r0).r1? c' +!else + System::Call 'OCSetupHlp::OCExecuteOffer(t)i(r0).r1? c' +!endif + + GetFullPathName /SHORT $1 $INSTDIR\OpenCandy\OCSetupHlp.dll + StrCpy $0 "RunDll32.exe $1,_MgrCheck@16 $2 /S$OCProductKey" + ExecWait $0 + + ${EndIf} + + Pop $2 + Pop $1 + Pop $0 + + ${Else} + + ; Offer was rejected so we don't install the DLL + ; And run the Execute call ONLY + + Push $0 + Push $1 + + StrCpy $1 " " + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCGetOfferType(m)i(.r1).r0? c' +!else + System::Call 'OCSetupHlp::OCGetOfferType(t)i(.r1).r0? c' +!endif + + ${If} $0 == 2 ; OC_OFFER_TYPE_EMBEDDED + + GetFullPathName /SHORT $1 $INSTDIR\OpenCandy\OCSetupHlp.dll + StrCpy $0 "$1,_MgrCheck@16" + +!ifdef NSIS_UNICODE + System::Call 'OCSetupHlp::OCExecuteOffer(m)i(r0).r1? c' +!else + System::Call 'OCSetupHlp::OCExecuteOffer(t)i(r0).r1? c' +!endif + + ${EndIf} + + Pop $1 + Pop $0 + + ${EndIf} + + Pop $0 + + SetOutPath "$INSTDIR" + + ${EndIf} + ${EndIf} + ${EndIf} + ${EndIf} + +!macroend + +; END of OpenCandy Helper Include file diff --git a/installer/Offer-Aro.nsh b/installer/Offer-Aro.nsh new file mode 100644 index 0000000..07671dc --- /dev/null +++ b/installer/Offer-Aro.nsh @@ -0,0 +1,195 @@ +!define PAGE_NAME_ARO "aro" +!define PAGE_NUM_ARO "5" + +!define COUNTRY_LOOKUP_URL_ARO "http://digsby.com/cc" + +Var AroPage.Image +Var AroPage.Show +Var AroPage.Install +Var AroPage.NextText +Var AroPage.BackText +Var AroPage.DeclineClicked +Var AroPage.Results +Var AroPage.Visited + +!define IMAGE_NAME_ARO "aro_image.bmp" +!define IMAGE_PATH_ARO "${DIGSRES}\${IMAGE_NAME_ARO}" +!define INSTALLER_URL_ARO "http://partners.digsby.com/aro/aro_installer.exe" + +!define TITLE_TEXT_ARO "Is your computer slowing down?" +!define TEXT_AGREE_ARO "By clicking $\"Accept$\", I agree to the and and want to install ARO." +!define TEXT_TERMS_OF_USE_ARO "EULA" +!define TEXT_PRIVACY_ARO "Privacy Policy" + +!define URL_TERMS_OF_USE_ARO "http://www.sammsoft.com/ARO_EULA.aspx" +!define URL_PRIVACY_ARO "http://www.sammsoft.com/Privacy.aspx" + +!define BUTTONTEXT_NEXT_ARO "&Accept" +!define BUTTONTEXT_BACK_ARO "&Decline" + +!macro DIGSBY_PAGE_ARO + + Function AroPage.InitVars + StrCpy $AroPage.DeclineClicked "False" + StrCpy $AroPage.Install "False" + StrCpy $AroPage.Visited "False" + StrCpy $AroPage.Show 0 + IntOp $AroPage.Results 0 + 0 + FunctionEnd + + PageEx custom + PageCallbacks AroPage.OnEnter AroPage.OnExit + PageExEnd + + Function AroPage.ShouldShow + StrCpy $1 "" + + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Show == 1 + StrCpy $AroPage.Show 0 + Goto skip + ${EndIf} + !endif + + # Sending them all our traffic for now. + StrCpy $AroPage.Show 1 + +# inetc::get \ +# /TIMEOUT 5000 \ +# /SILENT \ +# /USERAGENT "DigsbyInstaller v${SVNREV}" \ +# "${COUNTRY_LOOKUP_URL_ARO}" \ +# "$PLUGINSDIR\country.txt" +# +# ClearErrors +# FileOpen $0 "$PLUGINSDIR\country.txt" r +# FileRead $0 $1 +# FileClose $0 +# ClearErrors +# +# # US only +# ${If} $1 == "US" +# StrCpy $AroPage.Show 1 +# ${Else} +# StrCpy $AroPage.Show 0 +# ${EndIf} + + skip: + FunctionEnd + + Function AroPage.DeclineButtonClicked + StrCpy $AroPage.DeclineClicked "True" + StrCpy $AroPage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function AroPage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_ARO}" + FunctionEnd + + Function AroPage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_ARO}" + FunctionEnd + + Function AroPage.OnEnter + IfSilent 0 +2 + Abort + + ${If} $AroPage.Show == 0 + StrCpy $AroPage.Install "False" + Abort + ${EndIf} + + ${If} $AroPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $AroPage.Visited "True" + ${EndIf} + + IntOp $AroPage.Results $AroPage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_ARO} + + File "/oname=$PLUGINSDIR\${IMAGE_NAME_ARO}" "${IMAGE_PATH_ARO}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_ARO}" "" + + nsDialogs::Create /NOUNLOAD 1018 + + ${NSD_CreateBitmap} 0 0 0 0 "" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\${IMAGE_NAME_ARO}" $AroPage.Image + + ${NSD_CreateLink} 111u 87% 18u 6% "${TEXT_TERMS_OF_USE_ARO}" "${URL_TERMS_OF_USE_ARO}" + Pop $1 + ${NSD_OnClick} $1 AroPage.TermsOfUseClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLink} 145u 87% 44u 6% "${TEXT_PRIVACY_ARO}" "${URL_PRIVACY_ARO}" + Pop $1 + ${NSD_OnClick} $1 AroPage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 0 87% 100% 12% "${TEXT_AGREE_ARO}" + Pop $1 + + GetFunctionAddress $1 AroPage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + # Next button + GetDlgItem $2 $HWNDPARENT 1 + + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $AroPage.NextText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_ARO}' + + # Back button + GetDlgItem $2 $HWNDPARENT 3 + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $AroPage.BackText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_ARO}' + + nsDialogs::Show + FunctionEnd + + Function AroPage.OnExit + ${NSD_FreeImage} $AroPage.Image + + FindWindow $1 "#32770" "" $HWNDPARENT + + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$AroPage.NextText' + + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$AroPage.BackText' + + ${If} $AroPage.DeclineClicked == "False" + StrCpy $AroPage.Install "True" + ${EndIf} + FunctionEnd + + Function AroPage.PerformInstall + IntOp $AroPage.Results $AroPage.Results | ${FLAG_OFFER_ACCEPT} + #StrCpy $3 '"$PLUGINSDIR\aro_install.exe" /S' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_ARO}" \ + "$PLUGINSDIR\aro_install.exe" + + ExecWait "$PLUGINSDIR\aro_install.exe /verysilent /nolaunch" $1 + IntOp $AroPage.Results $AroPage.Results | ${FLAG_OFFER_SUCCESS} + + end: + FunctionEnd + +!macroend + diff --git a/installer/Offer-AskToolbar.nsh b/installer/Offer-AskToolbar.nsh new file mode 100644 index 0000000..67b4756 --- /dev/null +++ b/installer/Offer-AskToolbar.nsh @@ -0,0 +1,208 @@ +!include "nsDialogs_createTextMultiline.nsh" + +!define PAGE_NAME_ASK "ask" +!define PAGE_NUM_ASK "4" +!define FLAG_ASKPAGE_SETSEARCH 8 + +Var AskPage.SearchCheck +Var AskPage.SearchCheck.Value # Holds the value so it stays the same between page views +!define SEARCHCHECK_INITIAL_VALUE_ASKPAGE "${BST_CHECKED}" + +Var AskPage.AcceptCheck +Var AskPage.AcceptCheck.Value # Holds the value so it stays the same between page views +!define ACCEPTCHECK_INITIAL_VALUE_ASKPAGE "${BST_CHECKED}" + +Var AskPage.ToolbarImage # needs to be freed later +Var AskPage.LicenseTextCtrl +Var AskPage.Show +Var AskPage.Install +Var AskPage.Visited +Var AskPage.Results + +!define TITLE_TEXT_ASKPAGE "Better Web Browsing with Digsby Ask Toolbar" +!define TOP_TEXT_ASKPAGE "The Digsby Ask Toolbar makes web browsing more convenient! You can search the web anytime, keep up to date on news, weather, sports, maps, and more!" +!define IMAGE_PATH_ASKPAGE "${DIGSRES}\toolbar_headimg.bmp" +!define SEARCHCHECK_TEXT_ASKPAGE "&Make Ask my default search provider" +!define LICENSE_FILE_ASKPAGE "${DIGSRES}\toolbar_license.txt" +!define ACCEPTCHECK_TEXT_ASKPAGE "I &accept the license agreement and want to install the free Digsby Ask Toolbar" +!define INSTALLCHECKER_EXE_ASKPAGE "${DIGSRES}\AskInstallChecker-1.2.0.0.exe" +!define TOOLBAR_ID "DGY" + +!define TOOLBAR_INSTALLER_URL "http://partners.digsby.com/ask/askToolbarInstaller-1.5.0.0.exe" + +!macro DIGSBY_PAGE_ASK_TOOLBAR + + Function AskPage.InitVars + StrCpy $AskPage.Install "False" + StrCpy $AskPage.Visited "False" + StrCpy $AskPage.Show 0 + IntOp $AskPage.Results 0 + 0 + FunctionEnd + + PageEx custom + + PageCallbacks AskPage.OnEnter AskPage.OnExit + + PageExEnd + + Function AskPage.ShouldShow + File "/oname=$PLUGINSDIR\install_checker.exe" "${INSTALLCHECKER_EXE_ASKPAGE}" + ExecWait '"$PLUGINSDIR\install_checker.exe" ${TOOLBAR_ID}' $1 + ${If} $1 == 0 # 0 means there is none already installed + StrCpy $AskPage.Show 1 + ${Else} + StrCpy $AskPage.Show 0 + ${EndIf} + FunctionEnd + + Function AskPage.AcceptCheckChanged + ${NSD_GetState} $AskPage.AcceptCheck $1 + ${If} $1 == ${BST_UNCHECKED} + EnableWindow $AskPage.SearchCheck 0 + ${NSD_SetState} $AskPage.SearchCheck ${BST_UNCHECKED} + StrCpy $AskPage.Install "False" + ${Else} + EnableWindow $AskPage.SearchCheck 1 + ${NSD_SetState} $AskPage.SearchCheck ${BST_CHECKED} + StrCpy $AskPage.Install "True" + ${EndIf} + Call AskPage.SaveCheckValues + FunctionEnd + + Function AskPage.SaveCheckValues + Pop $1 + ${NSD_GetState} $AskPage.AcceptCheck $1 + StrCpy $AskPage.AcceptCheck.Value $1 + + ${NSD_GetState} $AskPage.SearchCheck $1 + StrCpy $AskPage.SearchCheck.Value $1 + + FunctionEnd + + Function AskPage.SetLicenseText + ClearErrors + FileOpen $0 "$PLUGINSDIR\toolbar_license.txt" r + IfErrors exit + System::Call 'kernel32::GetFileSize(i r0, i 0) i .r1' + IntOp $1 $1 + 1 ; for terminating zero + System::Alloc $1 + Pop $2 + System::Call 'kernel32::ReadFile(i r0, i r2, i r1, *i .r3, i 0)' + FileClose $0 + SendMessage $AskPage.LicenseTextCtrl ${EM_SETLIMITTEXT} $1 0 + SendMessage $AskPage.LicenseTextCtrl ${WM_SETTEXT} 0 $2 + System::Free $2 + exit: + + FunctionEnd + + Function AskPage.OnEnter + IfSilent 0 +2 + Abort + + DetailPrint "Initializing..." + ${If} $AskPage.Show == 0 + StrCpy $AskPage.AcceptCheck.Value ${BST_UNCHECKED} + StrCpy $AskPage.SearchCheck.Value ${BST_UNCHECKED} + StrCpy $AskPage.Install "False" + Abort + ${EndIf} + + ${If} $AskPage.Visited == "False" + ${incr} $NumPagesVisited 1 + StrCpy $AskPage.Visited "True" + ${EndIf} + + StrCpy $LastSeenPage ${PAGE_NAME_ASK} + + IntOp $AskPage.Results $AskPage.Results | ${FLAG_OFFER_ASK} + + File "/oname=$PLUGINSDIR\toolbar_headimg.bmp" "${IMAGE_PATH_ASKPAGE}" + File "/oname=$PLUGINSDIR\toolbar_license.txt" "${LICENSE_FILE_ASKPAGE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_ASKPAGE}" "" + + nsDialogs::Create /NOUNLOAD 1018 + ${NSD_CreateLabel} 0 0% 100% 12% "${TOP_TEXT_ASKPAGE}" + Pop $1 + + ${NSD_CreateBitmap} 0 14% 100% 12% "Image goes here ${IMAGE_PATH_ASKPAGE}" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\toolbar_headimg.bmp" $AskPage.ToolbarImage + + ## 'Search' checkbox + ${NSD_CreateCheckbox} 0 28% 100% 6% "${SEARCHCHECK_TEXT_ASKPAGE}" + Pop $AskPage.SearchCheck + # Set checked + ${If} $AskPage.SearchCheck.Value == "" + StrCpy $AskPage.SearchCheck.Value ${SEARCHCHECK_INITIAL_VALUE_ASKPAGE} + ${EndIf} + ${NSD_SetState} $AskPage.SearchCheck $AskPage.SearchCheck.Value + ${NSD_OnClick} $AskPage.SearchCheck AskPage.SaveCheckValues + + ${NSD_CreateTextMultiline} 0 36% 100% 54% "" + Pop $AskPage.LicenseTextCtrl + Call AskPage.SetLicenseText + + # Set $1 to read only + SendMessage $1 ${EM_SETREADONLY} 1 0 + # Set it to be black-on-white-text (instead of "disabled" color background). + # (does not obey system color settings) + SetCtlColors $1 "000000" "FFFFFF" + + ## 'Accept' checkbox + ${NSD_CreateCheckbox} 0 92% 100% 6% "${ACCEPTCHECK_TEXT_ASKPAGE}" + Pop $AskPage.AcceptCheck + + # Set checked + ${If} $AskPage.AcceptCheck.Value == "" + StrCpy $AskPage.AcceptCheck.Value ${ACCEPTCHECK_INITIAL_VALUE_ASKPAGE} + ${EndIf} + + ${NSD_SetState} $AskPage.AcceptCheck $AskPage.AcceptCheck.Value + ${NSD_OnClick} $AskPage.AcceptCheck AskPage.AcceptCheckChanged + + nsDialogs::Show + FunctionEnd + + Function AskPage.OnExit + ${NSD_FreeImage} $AskPage.ToolbarImage + + ${NSD_GetState} $AskPage.AcceptCheck $1 + ${If} $1 == ${BST_UNCHECKED} + StrCpy $AskPage.Install "False" + ${Else} + StrCpy $AskPage.Install "True" + ${EndIf} + + FunctionEnd + + Function AskPage.PerformInstall + StrCpy $3 '"$PLUGINSDIR\ask_installer.exe" toolbar=${TOOLBAR_ID}' + ${If} $AskPage.Install == "True" + StrCpy $3 "$3 /tbr" + IntOp $AskPage.Results $AskPage.Results | ${FLAG_OFFER_ACCEPT} + ${EndIf} + + ${If} $AskPage.SearchCheck.Value == "${BST_CHECKED}" + StrCpy $3 "$3 /sa" + IntOp $AskPage.Results $AskPage.Results | ${FLAG_ASKPAGE_SETSEARCH} + ${EndIf} + + ${If} $AskPage.Install == "True" + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${TOOLBAR_INSTALLER_URL}" \ + "$PLUGINSDIR\ask_installer.exe" + + ExecWait $3 $1 + ${If} $1 == 0 + IntOp $AskPage.Results $AskPage.Results | ${FLAG_OFFER_SUCCESS} + ${EndIf} + ${EndIf} + FunctionEnd + +!macroend diff --git a/installer/Offer-Babylon.nsh b/installer/Offer-Babylon.nsh new file mode 100644 index 0000000..f82798b --- /dev/null +++ b/installer/Offer-Babylon.nsh @@ -0,0 +1,218 @@ +!define PAGE_NAME_BABYLON "babylon" +!define PAGE_NUM_BABYLON "5" + +!define COUNTRY_LOOKUP_URL_BABYLON "http://digsby.com/cc" + +Var BabylonPage.Image +Var BabylonPage.Show +Var BabylonPage.Install +Var BabylonPage.NextText +Var BabylonPage.BackText +Var BabylonPage.DeclineClicked +Var BabylonPage.Results +Var BabylonPage.Visited + +!define IMAGE_NAME_BABYLON "babylon_image.bmp" +!define IMAGE_PATH_BABYLON "${DIGSRES}\${IMAGE_NAME_BABYLON}" +!define INSTALLER_URL_BABYLON "http://partners.digsby.com/babylon/Babylon8_Setup_15000.exe" + +!define TITLE_TEXT_BABYLON "One click translation to any language with Babylon!" +!define TEXT_AGREE_BABYLON "By clicking $\"Accept$\", I agree to the and and want to install Babylon and the Babylon toolbar. I accept to change my home page and browser search to Babylon." +!define TEXT_TERMS_OF_USE_BABYLON "EULA" +!define TEXT_PRIVACY_BABYLON "Privacy Policy" + +!define URL_TERMS_OF_USE_BABYLON "http://www.babylon.com/info/terms.html" +!define URL_PRIVACY_BABYLON "http://www.babylon.com/info/privacy.html" + +!define BUTTONTEXT_NEXT_BABYLON "&Accept" +!define BUTTONTEXT_BACK_BABYLON "&Decline" + +!macro DIGSBY_PAGE_BABYLON + + Function BabylonPage.InitVars + StrCpy $BabylonPage.DeclineClicked "False" + StrCpy $BabylonPage.Install "False" + StrCpy $BabylonPage.Visited "False" + StrCpy $BabylonPage.Show 0 + IntOp $BabylonPage.Results 0 + 0 + FunctionEnd + + PageEx custom + PageCallbacks BabylonPage.OnEnter BabylonPage.OnExit + PageExEnd + + Function BabylonPage.ShouldShow + StrCpy $1 "" + + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${COUNTRY_LOOKUP_URL_BABYLON}" \ + "$PLUGINSDIR\country.txt" + + ClearErrors + FileOpen $0 "$PLUGINSDIR\country.txt" r + FileRead $0 $1 + FileClose $0 + ClearErrors + + # Temporary for babylon to see how other countries monetize + StrCpy $BabylonPage.Show 1 + Goto skip + + # AU, BE, BR, CA, CH, DE, ES, FR, GB, IL, IT, JP, MX, NL, PT, SE, ZA + ${If} $1 == "AU" + ${OrIf} $1 == "BE" + ${OrIf} $1 == "US" + ${OrIf} $1 == "BR" + ${OrIf} $1 == "CA" + ${OrIf} $1 == "CH" + ${OrIf} $1 == "DE" + ${OrIf} $1 == "ES" + ${OrIf} $1 == "FR" + ${OrIf} $1 == "GB" + ${OrIf} $1 == "IL" + ${OrIf} $1 == "IT" + ${OrIf} $1 == "JP" + ${OrIf} $1 == "MX" + ${OrIf} $1 == "NL" + ${OrIf} $1 == "PT" + ${OrIf} $1 == "SE" + ${OrIf} $1 == "ZA" + StrCpy $BabylonPage.Show 1 + ${Else} + StrCpy $BabylonPage.Show 0 + ${EndIf} + + skip: + FunctionEnd + + Function BabylonPage.DeclineButtonClicked + StrCpy $BabylonPage.DeclineClicked "True" + StrCpy $BabylonPage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function BabylonPage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_BABYLON}" + FunctionEnd + + Function BabylonPage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_BABYLON}" + FunctionEnd + + Function BabylonPage.OnEnter + IfSilent 0 +2 + Abort + + ${If} $BabylonPage.Show == 0 + StrCpy $BabylonPage.Install "False" + Abort + ${EndIf} + + ${If} $BabylonPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $BabylonPage.Visited "True" + ${EndIf} + + IntOp $BabylonPage.Results $BabylonPage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_BABYLON} + + File "/oname=$PLUGINSDIR\${IMAGE_NAME_BABYLON}" "${IMAGE_PATH_BABYLON}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_BABYLON}" "" + + nsDialogs::Create /NOUNLOAD 1018 + + ${NSD_CreateBitmap} 0 0 0 0 "" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\${IMAGE_NAME_BABYLON}" $BabylonPage.Image + + ${NSD_CreateLink} 112u 87% 18u 6% "${TEXT_TERMS_OF_USE_BABYLON}" "${URL_TERMS_OF_USE_BABYLON}" + Pop $1 + ${NSD_OnClick} $1 BabylonPage.TermsOfUseClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLink} 145u 87% 44u 6% "${TEXT_PRIVACY_BABYLON}" "${URL_PRIVACY_BABYLON}" + Pop $1 + ${NSD_OnClick} $1 BabylonPage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 0 87% 100% 12% "${TEXT_AGREE_BABYLON}" + Pop $1 + + GetFunctionAddress $1 BabylonPage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + # Next button + GetDlgItem $2 $HWNDPARENT 1 + + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $BabylonPage.NextText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_BABYLON}' + + # Back button + GetDlgItem $2 $HWNDPARENT 3 + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $BabylonPage.BackText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_BABYLON}' + + nsDialogs::Show + FunctionEnd + + Function BabylonPage.OnExit + ${NSD_FreeImage} $BabylonPage.Image + + FindWindow $1 "#32770" "" $HWNDPARENT + + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$BabylonPage.NextText' + + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$BabylonPage.BackText' + + ${If} $BabylonPage.DeclineClicked == "False" + StrCpy $BabylonPage.Install "True" + ${EndIf} + FunctionEnd + + Function BabylonPage.PerformInstall + IntOp $BabylonPage.Results $BabylonPage.Results | ${FLAG_OFFER_ACCEPT} + #StrCpy $3 '"$PLUGINSDIR\babylon_install.exe" /S' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_BABYLON}" \ + "$PLUGINSDIR\babylon_install.zip" + + # babylon_install.exe is a self-extracting zip, so we can extract it + # and run the setup.exe inside it + + CreateDirectory "$PLUGINSDIR\babylon" + nsisunz::Unzip "$PLUGINSDIR\babylon_install.zip" "$PLUGINSDIR\babylon" + + Pop $0 + ${If} $0 != "success" + Goto end + ${EndIf} + + ExecWait "$PLUGINSDIR\babylon\Setup32.exe /S" $1 + IntOp $BabylonPage.Results $BabylonPage.Results | ${FLAG_OFFER_SUCCESS} + + RMDir /r "$PLUGINSDIR\babylon" + end: + FunctionEnd + +!macroend + diff --git a/installer/Offer-Bccthis.nsh b/installer/Offer-Bccthis.nsh new file mode 100644 index 0000000..60e2407 --- /dev/null +++ b/installer/Offer-Bccthis.nsh @@ -0,0 +1,235 @@ +!define PAGE_NAME_BCCTHIS "bccthis" +!define PAGE_NUM_BCCTHIS "5" + +!define COUNTRY_LOOKUP_URL_BCCTHIS "http://digsby.com/cc" + +Var BccthisPage.Image +Var BccthisPage.Show +Var BccthisPage.Install +Var BccthisPage.NextText +Var BccthisPage.BackText +Var BccthisPage.DeclineClicked +Var BccthisPage.Results +Var BccthisPage.Visited + +!define IMAGE_NAME_BCCTHIS "bccthis_image.bmp" +!define IMAGE_PATH_BCCTHIS "${DIGSRES}\${IMAGE_NAME_BCCTHIS}" +!define INSTALLER_URL_BCCTHIS "http://partners.digsby.com/bccthis/Install_Bccthis.exe" + +!define TITLE_TEXT_BCCTHIS "Send Better Email. Guaranteed." +#!define TITLE_TEXT_BCCTHIS "Add relevant context to emails by sending the right message to the right person" +!define TEXT_AGREE_BCCTHIS "By clicking $\"Accept$\", I agree to the and and want to install bccthis." +!define TEXT_TERMS_OF_USE_BCCTHIS "Terms of Use" +!define TEXT_PRIVACY_BCCTHIS "Privacy Policy" + +!define URL_TERMS_OF_USE_BCCTHIS "http://bccthis.com/legal.php#tos" +!define URL_PRIVACY_BCCTHIS "http://bccthis.com/legal.php#privacy" + +!define BUTTONTEXT_NEXT_BCCTHIS "&Accept" +!define BUTTONTEXT_BACK_BCCTHIS "&Decline" + +!define BCCTHIS_AFFILIATE_ID "Digsby" + +!macro DIGSBY_PAGE_BCCTHIS + + Function BccthisPage.InitVars + StrCpy $BccthisPage.DeclineClicked "False" + StrCpy $BccthisPage.Install "False" + StrCpy $BccthisPage.Visited "False" + StrCpy $BccthisPage.Show 0 + IntOp $BccthisPage.Results 0 + 0 + FunctionEnd + + PageEx custom + PageCallbacks BccthisPage.OnEnter BccthisPage.OnExit + PageExEnd + + Function BccthisPage.ShouldShow + StrCpy $1 "" + + ${If} $XobniPage.Show == 1 + # 80% chance to not show + Push "100" + nsRandom::GetRandom + Pop $1 + ${If} $1 < 80 + StrCpy $InboxPage.Show 0 + Goto no + ${EndIf} + ${EndIf} + + ClearErrors + ReadRegStr $0 HKCU "Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles" "DefaultProfile" + + ${If} ${Errors} + ${OrIf} $0 == "" + Goto no + ${EndIf} + + ClearErrors + ReadRegDWORD $0 HKLM "Software\Microsoft\NET Framework Setup\NDP\v3.5" "SP" + + ${If} ${Errors} + ${OrIf} $0 < 1 + Goto no + ${EndIf} + + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${COUNTRY_LOOKUP_URL_BCCTHIS}" \ + "$PLUGINSDIR\country.txt" + + ClearErrors + FileOpen $0 "$PLUGINSDIR\country.txt" r + FileRead $0 $1 + FileClose $0 + ClearErrors + + ${If} $1 == "US" + ${OrIf} $1 == "GB" + ${OrIf} $1 == "CA" + ${OrIf} $1 == "AU" + ${OrIf} $1 == "IT" + ${OrIf} $1 == "DE" + ${OrIf} $1 == "FR" + ${OrIf} $1 == "ES" + ${OrIf} $1 == "MX" + ${OrIf} $1 == "BR" + Goto yes + ${Else} + Goto no + ${EndIf} + + no: + StrCpy $BccthisPage.Show 0 + Goto end + yes: + StrCpy $BccthisPage.Show 1 + Goto end + end: + ClearErrors + FunctionEnd + + Function BccthisPage.DeclineButtonClicked + StrCpy $BccthisPage.DeclineClicked "True" + StrCpy $BccthisPage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function BccthisPage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_BCCTHIS}" + FunctionEnd + + Function BccthisPage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_BCCTHIS}" + FunctionEnd + + Function BccthisPage.OnEnter + IfSilent 0 +2 + Abort + + ${If} $BccthisPage.Show == 0 + StrCpy $BccthisPage.Install "False" + Abort + ${EndIf} + + ${If} $BccthisPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $BccthisPage.Visited "True" + ${EndIf} + + IntOp $BccthisPage.Results $BccthisPage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_BCCTHIS} + + File "/oname=$PLUGINSDIR\${IMAGE_NAME_BCCTHIS}" "${IMAGE_PATH_BCCTHIS}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_BCCTHIS}" "" + + nsDialogs::Create /NOUNLOAD 1018 + + ${NSD_CreateBitmap} 0 0 0 0 "" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\${IMAGE_NAME_BCCTHIS}" $BccthisPage.Image + + ${NSD_CreateLink} 112u 87% 42u 6% "${TEXT_TERMS_OF_USE_BCCTHIS}" "${URL_TERMS_OF_USE_BCCTHIS}" + Pop $1 + ${NSD_OnClick} $1 BccthisPage.TermsOfUseClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLink} 170u 87% 44u 6% "${TEXT_PRIVACY_BCCTHIS}" "${URL_PRIVACY_BCCTHIS}" + Pop $1 + ${NSD_OnClick} $1 BccthisPage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 0 87% 100% 12% "${TEXT_AGREE_BCCTHIS}" + Pop $1 + + GetFunctionAddress $1 BccthisPage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + # Next button + GetDlgItem $2 $HWNDPARENT 1 + + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $BccthisPage.NextText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_BCCTHIS}' + + # Back button + GetDlgItem $2 $HWNDPARENT 3 + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $BccthisPage.BackText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_BCCTHIS}' + + nsDialogs::Show + FunctionEnd + + Function BccthisPage.OnExit + ${NSD_FreeImage} $BccthisPage.Image + + FindWindow $1 "#32770" "" $HWNDPARENT + + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$BccthisPage.NextText' + + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$BccthisPage.BackText' + + ${If} $BccthisPage.DeclineClicked == "False" + StrCpy $BccthisPage.Install "True" + ${EndIf} + FunctionEnd + + Function BccthisPage.PerformInstall + IntOp $BccthisPage.Results $BccthisPage.Results | ${FLAG_OFFER_ACCEPT} + StrCpy $3 '"$PLUGINSDIR\Install_Bccthis.exe" AFFILIATE="${BCCTHIS_AFFILIATE_ID}"' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_BCCTHIS}" \ + "$PLUGINSDIR\Install_Bccthis.exe" + + ExecWait $3 $1 + + ReadRegStr $0 HKCU "Software\Bccthis" "Affiliate" + ${If} $0 == "${BCCTHIS_AFFILIATE_ID}" + IntOp $BccthisPage.Results $BccthisPage.Results | ${FLAG_OFFER_SUCCESS} + ${EndIf} + + end: + + FunctionEnd + +!macroend + diff --git a/installer/Offer-Bing.nsh b/installer/Offer-Bing.nsh new file mode 100644 index 0000000..14ed200 --- /dev/null +++ b/installer/Offer-Bing.nsh @@ -0,0 +1,193 @@ +!define PAGE_NAME_BING "bing" +!define PAGE_NUM_BING "5" + +!define COUNTRY_LOOKUP_URL_BING "http://digsby.com/cc" + +Var BingPage.Image +Var BingPage.Show +Var BingPage.Install +Var BingPage.NextText +Var BingPage.BackText +Var BingPage.DeclineClicked +Var BingPage.Results +Var BingPage.Visited + +!define IMAGE_NAME_BING "bing_image.bmp" +!define IMAGE_PATH_BING "${DIGSRES}\${IMAGE_NAME_BING}" +!define INSTALLER_URL_BING "http://partners.digsby.com/bing/bing_install.exe" + +!define TITLE_TEXT_BING "Better Web Browsing with the Bing Bar" +!define TEXT_AGREE_BING "By clicking $\"Accept$\", I agree to the and and want to install the Search toolbar powered by Bing. I accept to change my home page and browser search to Bing." +!define TEXT_TERMS_OF_USE_BING "EULA" +!define TEXT_PRIVACY_BING "Privacy Policy" + +!define URL_TERMS_OF_USE_BING "http://www.zugosearch.com/terms/" +!define URL_PRIVACY_BING "http://www.zugosearch.com/privacy/" + +!define BUTTONTEXT_NEXT_BING "&Accept" +!define BUTTONTEXT_BACK_BING "&Decline" + +!macro DIGSBY_PAGE_BING + + Function BingPage.InitVars + StrCpy $BingPage.DeclineClicked "False" + StrCpy $BingPage.Install "False" + StrCpy $BingPage.Visited "False" + StrCpy $BingPage.Show 0 + IntOp $BingPage.Results 0 + 0 + FunctionEnd + + PageEx custom + PageCallbacks BingPage.OnEnter BingPage.OnExit + PageExEnd + + Function BingPage.ShouldShow + Push "100" + nsRandom::GetRandom + Pop $1 + ${If} $1 > 30 + StrCpy $BingPage.Show 0 + Goto skip + ${EndIf} + + StrCpy $1 "" + + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${COUNTRY_LOOKUP_URL_BING}" \ + "$PLUGINSDIR\country.txt" + + ClearErrors + FileOpen $0 "$PLUGINSDIR\country.txt" r + FileRead $0 $1 + FileClose $0 + ClearErrors + + ${If} $1 == "US" + StrCpy $BingPage.Show 1 + ${Else} + StrCpy $BingPage.Show 0 + ${EndIf} + + skip: + FunctionEnd + + Function BingPage.DeclineButtonClicked + StrCpy $BingPage.DeclineClicked "True" + StrCpy $BingPage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function BingPage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_BING}" + FunctionEnd + + Function BingPage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_BING}" + FunctionEnd + + Function BingPage.OnEnter + IfSilent 0 +2 + Abort + + ${If} $BingPage.Show == 0 + StrCpy $BingPage.Install "False" + Abort + ${EndIf} + + ${If} $BingPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $BingPage.Visited "True" + ${EndIf} + + IntOp $BingPage.Results $BingPage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_BING} + + File "/oname=$PLUGINSDIR\${IMAGE_NAME_BING}" "${IMAGE_PATH_BING}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_BING}" "" + + nsDialogs::Create /NOUNLOAD 1018 + + ${NSD_CreateBitmap} 0 0 0 0 "" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\${IMAGE_NAME_BING}" $BingPage.Image + + ${NSD_CreateLink} 112u 87% 18u 6% "${TEXT_TERMS_OF_USE_BING}" "${URL_TERMS_OF_USE_BING}" + Pop $1 + ${NSD_OnClick} $1 BingPage.TermsOfUseClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLink} 145u 87% 44u 6% "${TEXT_PRIVACY_BING}" "${URL_PRIVACY_BING}" + Pop $1 + ${NSD_OnClick} $1 BingPage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 0 87% 102% 12% "${TEXT_AGREE_BING}" + Pop $1 + + GetFunctionAddress $1 BingPage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + # Next button + GetDlgItem $2 $HWNDPARENT 1 + + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $BingPage.NextText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_BING}' + + # Back button + GetDlgItem $2 $HWNDPARENT 3 + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $BingPage.BackText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_BING}' + + nsDialogs::Show + FunctionEnd + + Function BingPage.OnExit + ${NSD_FreeImage} $BingPage.Image + + FindWindow $1 "#32770" "" $HWNDPARENT + + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$BingPage.NextText' + + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$BingPage.BackText' + + ${If} $BingPage.DeclineClicked == "False" + StrCpy $BingPage.Install "True" + ${EndIf} + FunctionEnd + + Function BingPage.PerformInstall + IntOp $BingPage.Results $BingPage.Results | ${FLAG_OFFER_ACCEPT} + StrCpy $3 '"$PLUGINSDIR\bing_install.exe" /TOOLBAR /DEFAULTSEARCH /DEFAULTSTART' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_BING}" \ + "$PLUGINSDIR\bing_install.exe" + + ExecWait $3 $1 + IntOp $BingPage.Results $BingPage.Results | ${FLAG_OFFER_SUCCESS} + + end: + + FunctionEnd + +!macroend + diff --git a/installer/Offer-Crawler.nsh b/installer/Offer-Crawler.nsh new file mode 100644 index 0000000..81233eb --- /dev/null +++ b/installer/Offer-Crawler.nsh @@ -0,0 +1,199 @@ +!define PAGE_NAME_CRAWLER "crawler" + +!define COUNTRY_LOOKUP_URL_CRAWLER "http://digsby.com/cc" + +Var CrawlerPage.Image +Var CrawlerPage.Show +Var CrawlerPage.Install +Var CrawlerPage.NextText +Var CrawlerPage.BackText +Var CrawlerPage.DeclineClicked +Var CrawlerPage.Results +Var CrawlerPage.Visited + +!define IMAGE_NAME_CRAWLER "crawler_image.bmp" +!define IMAGE_PATH_CRAWLER "${DIGSRES}\${IMAGE_NAME_CRAWLER}" +!define INSTALLER_URL_CRAWLER "http://partners.digsby.com/crawler/CrawlerSetup.exe" + +!define TITLE_TEXT_CRAWLER "Enhance Your Browser Experience with Crawler Toolbar" +!define TEXT_AGREE_CRAWLER "By clicking $\"Accept$\", I agree to the and and want to install the Crawler toolbar. I accept to change default search provider to Crawler Search." +!define TEXT_TERMS_OF_USE_CRAWLER "Terms of Service" +!define TEXT_PRIVACY_CRAWLER "Privacy Policy" +!define TEXT_HELP_CRAWLER "More Help" + +!define URL_TERMS_OF_USE_CRAWLER "http://www.crawler.com/terms_of_use.aspx" +!define URL_PRIVACY_CRAWLER "http://www.crawler.com/privacy_policy.aspx" +!define URL_HELP_CRAWLER "http://www.crawler.com/faqs.aspx" + +!define BUTTONTEXT_NEXT_CRAWLER "&Accept" +!define BUTTONTEXT_BACK_CRAWLER "&Decline" + +!macro DIGSBY_PAGE_CRAWLER + + Function CrawlerPage.InitVars + StrCpy $CrawlerPage.DeclineClicked "False" + StrCpy $CrawlerPage.Install "False" + StrCpy $CrawlerPage.Visited "False" + StrCpy $CrawlerPage.Show 0 + IntOp $CrawlerPage.Results 0 + 0 + FunctionEnd + + PageEx custom + PageCallbacks CrawlerPage.OnEnter CrawlerPage.OnExit + PageExEnd + + Function CrawlerPage.ShouldShow + # 50% chance to not show + Push "2" + nsRandom::GetRandom + Pop $1 + ${If} $1 = 0 + StrCpy $CrawlerPage.Show 0 + Goto skip + ${EndIf} + + # Check for previous install first + + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${COUNTRY_LOOKUP_URL_CRAWLER}" \ + "$PLUGINSDIR\country.txt" + + ClearErrors + FileOpen $0 "$PLUGINSDIR\country.txt" r + FileRead $0 $1 + FileClose $0 + ClearErrors + + ${If} $1 == "US" + ${OrIf} $1 == "GB" + ${OrIf} $1 == "CA" + ${OrIf} $1 == "AU" + StrCpy $CrawlerPage.Show 1 + ${Else} + StrCpy $CrawlerPage.Show 0 + ${EndIf} + + skip: + FunctionEnd + + Function CrawlerPage.DeclineButtonClicked + StrCpy $CrawlerPage.DeclineCLicked "True" + StrCpy $CrawlerPage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function CrawlerPage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_CRAWLER}" + FunctionEnd + + Function CrawlerPage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_CRAWLER}" + FunctionEnd + + Function CrawlerPage.MoreHelpClicked + ${OpenLinkNewWindow} "${URL_HELP_CRAWLER}" + FunctionEnd + + Function CrawlerPage.OnEnter + IfSilent 0 +2 + Abort + + ${If} $CrawlerPage.Show == 0 + StrCpy $CrawlerPage.Install "False" + Abort + ${EndIf} + + ${If} $CrawlerPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $CrawlerPage.Visited "True" + ${EndIf} + + IntOp $CrawlerPage.Results $CrawlerPage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_CRAWLER} + + File "/oname=$PLUGINSDIR\${IMAGE_NAME_CRAWLER}" "${IMAGE_PATH_CRAWLER}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_CRAWLER}" "" + + nsDialogs::Create /NOUNLOAD 1018 + ${NSD_CreateBitmap} 0 0 0 0 "" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\${IMAGE_NAME_CRAWLER}" $CrawlerPage.Image + + ${NSD_CreateLink} 112u 87% 54u 6% "${TEXT_TERMS_OF_USE_CRAWLER}" "${URL_TERMS_OF_USE_CRAWLER}" + Pop $1 + ${NSD_OnClick} $1 CrawlerPage.TermsOfUseClicked + SetCtlCOlors $1 "000080" transparent + + ${NSD_CreateLink} 181u 87% 44u 6% "${TEXT_PRIVACY_CRAWLER}" "${URL_PRIVACY_CRAWLER}" + Pop $1 + ${NSD_OnClick} $1 CrawlerPage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLink} 254u 130u 38u 6% "${TEXT_HELP_CRAWLER}" "${URL_HELP_CRAWLER}" + Pop $1 + ${NSD_OnClick} $1 CrawlerPage.MoreHelpClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 0 87% 102% 12% "${TEXT_AGREE_CRAWLER}" + Pop $1 + + GetFunctionAddress $1 CrawlerPage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + GetDlgItem $2 $HWNDPARENT 1 + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $CrawlerPage.NextText $0 + + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_CRAWLER}' + + GetDlgItem $2 $HWNDPARENT 3 + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $CrawlerPage.BackText $0 + + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_CRAWLER}' + + nsDialogs::Show + FunctionEnd + + Function CrawlerPage.OnExit + ${NSD_FreeImage} $CrawlerPage.Image + + FindWindow $1 "#32770" "" $HWNDPARENT + + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$CrawlerPage.NextText' + + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$CrawlerPage.BackText' + + ${If} $CrawlerPage.DeclineClicked == "False" + StrCpy $CrawlerPage.Install "True" + ${EndIf} + FunctionEnd + + Function CrawlerPage.PerformInstall + IntOp $CrawlerPage.Results $CrawlerPage.Results | ${FLAG_OFFER_ACCEPT} + + StrCpy $3 '"$PLUGINSDIR\crawler_install.exe" /VERYSILENT /NORESTART' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_CRAWLER}" \ + "$PLUGINSDIR\crawler_install.exe" + + ExecWait $3 $1 + + IntOp $CrawlerPage.Results $CrawlerPage.Results | ${FLAG_OFFER_SUCCESS} + end: + + FunctionEnd + +!macroend \ No newline at end of file diff --git a/installer/Offer-Inbox.nsh b/installer/Offer-Inbox.nsh new file mode 100644 index 0000000..ba1b201 --- /dev/null +++ b/installer/Offer-Inbox.nsh @@ -0,0 +1,188 @@ +!define PAGE_NAME_INBOX "inbox" + +!define COUNTRY_LOOKUP_URL_INBOX "http://digsby.com/cc" + +Var InboxPage.Image +Var InboxPage.Show +Var InboxPage.Install +Var InboxPage.NextText +Var InboxPage.BackText +Var InboxPage.DeclineClicked +Var InboxPage.Results +Var InboxPage.Visited + +!define IMAGE_NAME_INBOX "inbox_image.bmp" +!define IMAGE_PATH_INBOX "${DIGSRES}\${IMAGE_NAME_INBOX}" +!define INSTALLER_URL_INBOX "http://partners.digsby.com/inbox/inbox_install.exe" + +!define TITLE_TEXT_INBOX "Enhance Your Browser Experience with Inbox Toolbar" +!define TEXT_AGREE_INBOX "By clicking $\"Accept$\", I agree to the and and want to install the Inbox toolbar. I accept to change default search provider to Inbox Search." +!define TEXT_TERMS_OF_USE_INBOX "Terms of Service" +!define TEXT_PRIVACY_INBOX "Privacy Policy" + +!define URL_TERMS_OF_USE_INBOX "http://toolbar.inbox.com/legal/terms.aspx" +!define URL_PRIVACY_INBOX "http://toolbar.inbox.com/legal/privacy.aspx" + +!define BUTTONTEXT_NEXT_INBOX "&Accept" +!define BUTTONTEXT_BACK_INBOX "&Decline" + +!macro DIGSBY_PAGE_INBOX + + Function InboxPage.InitVars + StrCpy $InboxPage.DeclineClicked "False" + StrCpy $InboxPage.Install "False" + StrCpy $InboxPage.Visited "False" + StrCpy $InboxPage.Show 0 + IntOp $InboxPage.Results 0 + 0 + FunctionEnd + + PageEx custom + PageCallbacks InboxPage.OnEnter InboxPage.OnExit + PageExEnd + + Function InboxPage.ShouldShow + # Check for previous install first + + # 90% chance to not show + Push "100" + nsRandom::GetRandom + Pop $1 + ${If} $1 < 90 + StrCpy $InboxPage.Show 0 + Goto skip + ${EndIf} + + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${COUNTRY_LOOKUP_URL_INBOX}" \ + "$PLUGINSDIR\country.txt" + + ClearErrors + FileOpen $0 "$PLUGINSDIR\country.txt" r + FileRead $0 $1 + FileClose $0 + ClearErrors + + ${If} $1 == "US" + ${OrIf} $1 == "GB" + ${OrIf} $1 == "CA" + ${OrIf} $1 == "AU" + StrCpy $InboxPage.Show 1 + ${Else} + StrCpy $InboxPage.Show 0 + ${EndIf} + + skip: + FunctionEnd + + Function InboxPage.DeclineButtonClicked + StrCpy $InboxPage.DeclineCLicked "True" + StrCpy $InboxPage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function InboxPage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_INBOX}" + FunctionEnd + + Function InboxPage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_INBOX}" + FunctionEnd + + Function InboxPage.OnEnter + IfSilent 0 +2 + Abort + + ${If} $InboxPage.Show == 0 + StrCpy $InboxPage.Install "False" + Abort + ${EndIf} + + ${If} $InboxPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $InboxPage.Visited "True" + ${EndIf} + + IntOp $InboxPage.Results $InboxPage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_INBOX} + + File "/oname=$PLUGINSDIR\${IMAGE_NAME_INBOX}" "${IMAGE_PATH_INBOX}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_INBOX}" "" + + nsDialogs::Create /NOUNLOAD 1018 + ${NSD_CreateBitmap} 0 0 0 0 "" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\${IMAGE_NAME_INBOX}" $InboxPage.Image + + ${NSD_CreateLink} 112u 87% 54u 6% "${TEXT_TERMS_OF_USE_INBOX}" "${URL_TERMS_OF_USE_INBOX}" + Pop $1 + ${NSD_OnClick} $1 InboxPage.TermsOfUseClicked + SetCtlCOlors $1 "000080" transparent + + ${NSD_CreateLink} 181u 87% 44u 6% "${TEXT_PRIVACY_INBOX}" "${URL_PRIVACY_INBOX}" + Pop $1 + ${NSD_OnClick} $1 InboxPage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 0 87% 102% 12% "${TEXT_AGREE_INBOX}" + Pop $1 + + GetFunctionAddress $1 InboxPage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + GetDlgItem $2 $HWNDPARENT 1 + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $InboxPage.NextText $0 + + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_INBOX}' + + GetDlgItem $2 $HWNDPARENT 3 + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $InboxPage.BackText $0 + + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_INBOX}' + + nsDialogs::Show + FunctionEnd + + Function InboxPage.OnExit + ${NSD_FreeImage} $InboxPage.Image + + FindWindow $1 "#32770" "" $HWNDPARENT + + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$InboxPage.NextText' + + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$InboxPage.BackText' + + ${If} $InboxPage.DeclineClicked == "False" + StrCpy $InboxPage.Install "True" + ${EndIf} + FunctionEnd + + Function InboxPage.PerformInstall + IntOp $InboxPage.Results $InboxPage.Results | ${FLAG_OFFER_ACCEPT} + + StrCpy $3 '"$PLUGINSDIR\inbox_install.exe" /VERYSILENT /NORESTART' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_INBOX}" \ + "$PLUGINSDIR\inbox_install.exe" + + ExecWait $3 $1 + + IntOp $InboxPage.Results $InboxPage.Results | ${FLAG_OFFER_SUCCESS} + + end: + FunctionEnd + +!macroend \ No newline at end of file diff --git a/installer/Offer-OpenCandy.nsh b/installer/Offer-OpenCandy.nsh new file mode 100644 index 0000000..7c5f253 --- /dev/null +++ b/installer/Offer-OpenCandy.nsh @@ -0,0 +1,66 @@ +!ifdef USE_OPENCANDY +!define PAGE_NAME_OPENCANDY "opencandy" +Var OpencandyPage.Show +Var OpencandyPage.Install +Var OpencandyPage.Results +Var OpencandyPage.Visited +Var OpencandyPage.UserAborted + +!define OC_STR_MY_PRODUCT_NAME "${PRODUCT_NAME}" +!define OC_STR_KEY "3d3573561f09e1eb6542f202aa8fe6c3" +!define OC_STR_SECRET "ee24715bbeb7f798485820671b94355d" +!define OC_STR_REGISTRY_PATH "Software\dotSyntax\OpenCandy" + +!include "OCSetupHlp.nsh" + +; ****** OpenCandy START ****** + +!macro DIGSBY_PAGE_OPENCANDY + Function OpencandyPage.InitVars + StrCpy $OpencandyPage.Install "False" + StrCpy $OpencandyPage.Visited "False" + StrCpy $OpencandyPage.Show 0 + IntOp $OpencandyPage.Results 0 + 0 + + !insertmacro OpenCandyInit "${OC_STR_MY_PRODUCT_NAME}" "${OC_STR_KEY}" "${OC_STR_SECRET}" "${OC_STR_REGISTRY_PATH}" + IntOp $OpencandyPage.UserAborted 0 + 0 + FunctionEnd + + !define MUI_CUSTOMFUNCTION_ABORT "onUserAbort" + + PageEx custom + PageCallbacks OpencandyPage.OnEnter OpencandyPage.OnExit + PageExEnd + + Function OpencandyPage.ShouldShow + StrCpy $OpencandyPage.Show 1 + FunctionEnd + + Function OpencandyPage.OnEnter + ${If} $OpencandyPage.Show == 0 + StrCpy $OpencandyPage.Install "False" + Abort + ${EndIf} + ${If} $OpencandyPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $OpencandyPage.Visited "True" + ${EndIf} + + IntOp $OpencandyPage.Results $OpencandyPage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_OPENCANDY} + + Call OpenCandyPageStartFn + FunctionEnd + + Function OpencandyPage.OnExit + # OpencandyPage.Install = True (?) + Call OpenCandyPageLeaveFn + FunctionEnd + + Function OpencandyPage.PerformInstall + SetShellVarContext All + !insertmacro OpenCandyInstallDLL + FunctionEnd + +!macroend +!endif diff --git a/installer/Offer-Uniblue.nsh b/installer/Offer-Uniblue.nsh new file mode 100644 index 0000000..3641bce --- /dev/null +++ b/installer/Offer-Uniblue.nsh @@ -0,0 +1,195 @@ +!define PAGE_NAME_UNIBLUE "uniblue" +!define PAGE_NUM_UNIBLUE "5" + +!define COUNTRY_LOOKUP_URL_UNIBLUE "http://digsby.com/cc" + +Var UnibluePage.Image +Var UnibluePage.Show +Var UnibluePage.Install +Var UnibluePage.NextText +Var UnibluePage.BackText +Var UnibluePage.DeclineClicked +Var UnibluePage.Results +Var UnibluePage.Visited + +!define IMAGE_NAME_UNIBLUE "uniblue_image.bmp" +!define IMAGE_PATH_UNIBLUE "${DIGSRES}\${IMAGE_NAME_UNIBLUE}" +!define INSTALLER_URL_UNIBLUE "http://partners.digsby.com/uniblue/uniblue_install.exe" + +!define TITLE_TEXT_UNIBLUE "How many errors does your computer have?" +!define TEXT_AGREE_UNIBLUE "By clicking $\"Accept$\", I agree to the and and want to install RegistryBooster." +!define TEXT_TERMS_OF_USE_UNIBLUE "Terms of Service" +!define TEXT_PRIVACY_UNIBLUE "Privacy Policy" + +!define URL_TERMS_OF_USE_UNIBLUE "http://www.uniblue.com/terms/" +!define URL_PRIVACY_UNIBLUE "http://www.uniblue.com/privacy/" + +!define BUTTONTEXT_NEXT_UNIBLUE "&Accept" +!define BUTTONTEXT_BACK_UNIBLUE "&Decline" + +!macro DIGSBY_PAGE_UNIBLUE + + Function UnibluePage.InitVars + StrCpy $UnibluePage.DeclineClicked "False" + StrCpy $UnibluePage.Install "False" + StrCpy $UnibluePage.Visited "False" + StrCpy $UnibluePage.Show 0 + IntOp $UnibluePage.Results 0 + 0 + FunctionEnd + + PageEx custom + PageCallbacks UnibluePage.OnEnter UnibluePage.OnExit + PageExEnd + + Function UnibluePage.ShouldShow + StrCpy $1 "" + + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Show == 1 + StrCpy $UnibluePage.Show 0 + Goto skip + ${EndIf} + !endif + + # Sending them all our traffic for now. + StrCpy $UnibluePage.Show 1 + +# inetc::get \ +# /TIMEOUT 5000 \ +# /SILENT \ +# /USERAGENT "DigsbyInstaller v${SVNREV}" \ +# "${COUNTRY_LOOKUP_URL_UNIBLUE}" \ +# "$PLUGINSDIR\country.txt" +# +# ClearErrors +# FileOpen $0 "$PLUGINSDIR\country.txt" r +# FileRead $0 $1 +# FileClose $0 +# ClearErrors +# +# # US only +# ${If} $1 == "US" +# StrCpy $UnibluePage.Show 1 +# ${Else} +# StrCpy $UnibluePage.Show 0 +# ${EndIf} + + skip: + FunctionEnd + + Function UnibluePage.DeclineButtonClicked + StrCpy $UnibluePage.DeclineClicked "True" + StrCpy $UnibluePage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function UnibluePage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_UNIBLUE}" + FunctionEnd + + Function UnibluePage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_UNIBLUE}" + FunctionEnd + + Function UnibluePage.OnEnter + IfSilent 0 +2 + Abort + + ${If} $UnibluePage.Show == 0 + StrCpy $UnibluePage.Install "False" + Abort + ${EndIf} + + ${If} $UnibluePage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $UnibluePage.Visited "True" + ${EndIf} + + IntOp $UnibluePage.Results $UnibluePage.Results | ${FLAG_OFFER_ASK} + StrCpy $LastSeenPage ${PAGE_NAME_UNIBLUE} + + File "/oname=$PLUGINSDIR\${IMAGE_NAME_UNIBLUE}" "${IMAGE_PATH_UNIBLUE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_UNIBLUE}" "" + + nsDialogs::Create /NOUNLOAD 1018 + + ${NSD_CreateBitmap} 0 0 0 0 "" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\${IMAGE_NAME_UNIBLUE}" $UnibluePage.Image + + ${NSD_CreateLink} 111u 87% 55u 6% "${TEXT_TERMS_OF_USE_UNIBLUE}" "${URL_TERMS_OF_USE_UNIBLUE}" + Pop $1 + ${NSD_OnClick} $1 UnibluePage.TermsOfUseClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLink} 181u 87% 44u 6% "${TEXT_PRIVACY_UNIBLUE}" "${URL_PRIVACY_UNIBLUE}" + Pop $1 + ${NSD_OnClick} $1 UnibluePage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 0 87% 100% 12% "${TEXT_AGREE_UNIBLUE}" + Pop $1 + + GetFunctionAddress $1 UnibluePage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + # Next button + GetDlgItem $2 $HWNDPARENT 1 + + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $UnibluePage.NextText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_UNIBLUE}' + + # Back button + GetDlgItem $2 $HWNDPARENT 3 + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $UnibluePage.BackText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_UNIBLUE}' + + nsDialogs::Show + FunctionEnd + + Function UnibluePage.OnExit + ${NSD_FreeImage} $UnibluePage.Image + + FindWindow $1 "#32770" "" $HWNDPARENT + + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$UnibluePage.NextText' + + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$UnibluePage.BackText' + + ${If} $UnibluePage.DeclineClicked == "False" + StrCpy $UnibluePage.Install "True" + ${EndIf} + FunctionEnd + + Function UnibluePage.PerformInstall + IntOp $UnibluePage.Results $UnibluePage.Results | ${FLAG_OFFER_ACCEPT} + #StrCpy $3 '"$PLUGINSDIR\uniblue_install.exe" /S' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_UNIBLUE}" \ + "$PLUGINSDIR\uniblue_install.exe" + + ExecWait "$PLUGINSDIR\uniblue_install.exe /verysilent" $1 + IntOp $UnibluePage.Results $UnibluePage.Results | ${FLAG_OFFER_SUCCESS} + + end: + FunctionEnd + +!macroend + diff --git a/installer/Offer-Xobni.nsh b/installer/Offer-Xobni.nsh new file mode 100644 index 0000000..cef54fb --- /dev/null +++ b/installer/Offer-Xobni.nsh @@ -0,0 +1,203 @@ +!define PAGE_NAME_XOBNI "xobni" +!define PAGE_NUM_XOBNI 3 +Var XobniPage.Image # needs to be freed +Var XobniPage.Show +Var XobniPage.Install +Var XobniPage.InstallGuid +Var XobniPage.NextText +Var XobniPage.BackText +Var XobniPage.Results +Var XobniPage.Visited + +Var XobniPage.DeclineClicked + +!define TITLE_TEXT_XOBNI "Drowning in Email? Save Time with Xobni" +!define IMAGE_NAME_XOBNI "xobni_image.bmp" +!define IMAGE_PATH_XOBNI "${DIGSRES}\${IMAGE_NAME_XOBNI}" + +!define INSTALLCHECKER_EXE_XOBNI "${DIGSRES}\XobniDecide_100709.exe" +!define XOBNI_OUTPUT_KEY "Software\Xobni\output" + +!define TEXT_AGREE_1_XOBNI "By clicking $\"Accept$\", I agree to the" +!define TEXT_AGREE_2_XOBNI "and" +!define TEXT_AGREE_3_XOBNI "and want to install Xobni." + +!define BUTTONTEXT_NEXT_XOBNI "&Accept" +!define BUTTONTEXT_BACK_XOBNI "&Decline" + +!define CHECKER_ID_XOBNI "1462750" +!define INSTALLER_URL_XOBNI "http://partners.digsby.com/xobni/XobniMiniSetup.exe" + +!define TEXT_TERMS_OF_USE_XOBNI "Terms of Use" +!define TEXT_PRIVACY_XOBNI "Privacy Policy" + +!define URL_TERMS_OF_USE_XOBNI "http://www.xobni.com/legal/license" +!define URL_PRIVACY_XOBNI "http://www.xobni.com/legal/privacy" + +!macro DIGSBY_PAGE_XOBNI + + Function XobniPage.InitVars + StrCpy $XobniPage.DeclineClicked "False" + StrCpy $XobniPage.Install "False" + StrCpy $XobniPage.Visited "False" + StrCpy $XobniPage.Show 0 + IntOp $XobniPage.Results 0 + 0 + FunctionEnd + + PageEx custom + + PageCallbacks XobniPage.OnEnter XobniPage.OnExit + + PageExEnd + + Function XobniPage.ShouldShow + File "/oname=$PLUGINSDIR\xobni_install_checker.exe" "${INSTALLCHECKER_EXE_XOBNI}" + ExecWait '"$PLUGINSDIR\xobni_install_checker.exe" -decide -id ${CHECKER_ID_XOBNI}' + + ReadRegStr $1 HKCU ${XOBNI_OUTPUT_KEY} 'decision' + ${If} $1 == "yes" + StrCpy $XobniPage.Show 1 + ReadRegStr $2 HKCU ${XOBNI_OUTPUT_KEY} 'offerguid' + StrCpy $XobniPage.InstallGuid $2 + ${Else} + StrCpy $XobniPage.Show 0 + ${EndIf} + DeleteRegValue HKCU ${XOBNI_OUTPUT_KEY} 'decision' + FunctionEnd + + Function XobniPage.DeclineButtonClicked + StrCpy $XobniPage.DeclineClicked "True" + StrCpy $XobniPage.Install "False" + StrCpy $R9 1 + Call RelGotoPage + Abort + FunctionEnd + + Function XobniPage.TermsOfUseClicked + ${OpenLinkNewWindow} "${URL_TERMS_OF_USE_XOBNI}" + FunctionEnd + + Function XobniPage.PrivacyPolicyClicked + ${OpenLinkNewWindow} "${URL_PRIVACY_XOBNI}" + FunctionEnd + + Function XobniPage.OnEnter + IfSilent 0 +2 + Abort + + DetailPrint "Initializing..." + ${If} $XobniPage.Show == 0 + StrCpy $XobniPage.Install "False" + Abort + ${EndIf} + + ${If} $XobniPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $XobniPage.Visited "True" + ${EndIf} + + StrCpy $LastSeenPage ${PAGE_NAME_XOBNI} + IntOp $XobniPage.Results $XobniPage.Results | ${FLAG_OFFER_ASK} + + File "/oname=$PLUGINSDIR\xobni_image.bmp" "${IMAGE_PATH_XOBNI}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE "${TITLE_TEXT_XOBNI}" "" + + nsDialogs::Create /NOUNLOAD 1018 + ${NSD_CreateBitmap} 0 0 84% 12% "Image goes here ${IMAGE_PATH_XOBNI}" + Pop $1 + ${NSD_SetImage} $1 "$PLUGINSDIR\xobni_image.bmp" $XobniPage.Image + + ## non-checkbox GUI + ## Make sure to change functionality of back button to 'decline offer' + ${NSD_CreateLabel} 0 90% 112u 6% "${TEXT_AGREE_1_XOBNI}" + Pop $1 + + ${NSD_CreateLink} 112u 90% 44u 6% "${TEXT_TERMS_OF_USE_XOBNI}" "${URL_TERMS_OF_USE_XOBNI}" + Pop $1 + ${NSD_OnClick} $1 XobniPage.TermsOfUseClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 156u 90% 14u 6% "${TEXT_AGREE_2_XOBNI}" + Pop $1 + + ${NSD_CreateLink} 170u 90% 44u 6% "${TEXT_PRIVACY_XOBNI}" "${URL_PRIVACY_XOBNI}" + Pop $1 + ${NSD_OnClick} $1 XobniPage.PrivacyPolicyClicked + SetCtlColors $1 "000080" transparent + + ${NSD_CreateLabel} 215u 90% 95u 6% "${TEXT_AGREE_3_XOBNI}" + Pop $1 + + GetFunctionAddress $1 XobniPage.DeclineButtonClicked + nsDialogs::OnBack /NOUNLOAD $1 + + # Set next/back button text + # next is control ID 1, back is 3 + # Store old text in XobniPage.NextText / XobniPage.BackText + + # Next button + GetDlgItem $2 $HWNDPARENT 1 + + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $XobniPage.NextText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_NEXT_XOBNI}' + + # Back button + GetDlgItem $2 $HWNDPARENT 3 + # get the current text and set for later + System::Call 'User32::GetWindowText(p r2, t.r0, i 256)' + StrCpy $XobniPage.BackText $0 + + # set new label + SendMessage $2 ${WM_SETTEXT} 1 'STR:${BUTTONTEXT_BACK_XOBNI}' + + nsDialogs::Show + FunctionEnd + + Function XobniPage.OnExit + ${NSD_FreeImage} $XobniPage.Image + + # Revert the labels on the next/back buttons + + FindWindow $1 "#32770" "" $HWNDPARENT + # Next button + GetDlgItem $2 $1 1 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$XobniPage.NextText' + + # Back button + GetDlgItem $2 $1 3 + SendMessage $2 ${WM_SETTEXT} 1 'STR:$XobniPage.BackText' + + ${If} $XobniPage.DeclineClicked == "False" + #MessageBox MB_OK "Install Xobni -> True" + StrCpy $XobniPage.Install "True" + ${EndIf} + + FunctionEnd + + Function XobniPage.PerformInstall + IntOp $XobniPage.Results $XobniPage.Results | ${FLAG_OFFER_ACCEPT} + #MessageBox MB_OK "Downloading Xobni Installer" + StrCpy $3 '"$PLUGINSDIR\xobni_installer.exe" /S -offerguid $XobniPage.InstallGuid' + inetc::get \ + /TIMEOUT 5000 \ + /SILENT \ + /USERAGENT "DigsbyInstaller v${SVNREV}" \ + "${INSTALLER_URL_XOBNI}" \ + "$PLUGINSDIR\xobni_installer.exe" + + ExecWait $3 $1 + #MessageBox MB_OK "Xobni Installed, return code: $1" + ${If} $1 == 0 + IntOp $XobniPage.Results $XobniPage.Results | ${FLAG_OFFER_SUCCESS} + ${EndIf} + + end: + FunctionEnd + +!macroend diff --git a/installer/OpenCandy_Why_Is_This_Here.txt b/installer/OpenCandy_Why_Is_This_Here.txt new file mode 100644 index 0000000..17f0981 --- /dev/null +++ b/installer/OpenCandy_Why_Is_This_Here.txt @@ -0,0 +1,52 @@ +What is OpenCandy? + +OpenCandy is a platform and network which enables software publishers +to recommend other products, during installation of their software, they +believe you will find valuable. + +So What is this folder, its contents, and why is it here? + +These files are only on your computer because you accepted a software +recommendation. These files are TEMPORARILY copied to a folder within +the publisher's program files directory until the recommended software +is downloaded and installation is performed. + +This folder contains: + + * OpenCandy_Why_Is_This_Here.txt + - This text file youre reading. :) + + * OCSetupHlp.dll + - This file is used to display a recommendation during the + publishers software installation and to facilitate the download + and installation of the recommended software. This file also + allows us to provide software publishers with *non personally + identifiable* information such as statistics like install + initiated and completed, country, operating system and language, + and whether the software recommendation was accepted or not. + +Once the recommended softwares installation is started (and regardless +of whether it is successfully completed) these files and the 'OpenCandy' +folder will be automatically deleted. + +The possible reasons this folder and its contents remain are: + + A) You rebooted before installing the recommend software + B) You lost power before the recommended software was installed + C) Your computer exploded before the recommended software was + installed (Then how are you reading this?) + +Feel free to delete this OpenCandy folder (and its contents) at any +time; there is no harm in doing so. If you believe this folder and its +files should have been deleted and were not (and neither A, B, or C +happened), we'd appreciate if you contacted us (see below) and told us +what you think happened. If it happens to be a bug, we'll fix it so this +doesn't happen to anyone else. Oh yeah, and we'll send you an awesome +purple t-shirt for helping us out! + +If you have any questions, please dont hesitate to contact us at +www.opencandy.com/contact/. + +Thank you. + +- The OpenCandy team \ No newline at end of file diff --git a/installer/OpenLinkInBrowser.nsh b/installer/OpenLinkInBrowser.nsh new file mode 100644 index 0000000..aac75a0 --- /dev/null +++ b/installer/OpenLinkInBrowser.nsh @@ -0,0 +1,46 @@ + +!macro openLinkNewWindow_definition + Push $3 + Push $2 + Push $1 + Push $0 + ReadRegStr $0 HKCR "http\shell\open\command" "" + StrCpy $2 '"' + StrCpy $1 $0 1 + StrCmp $1 $2 +2 # if path is not enclosed in " look for space as final char + StrCpy $2 ' ' + StrCpy $3 1 + loop: + StrCpy $1 $0 1 $3 + StrCmp $1 $2 found + StrCmp $1 "" found + IntOp $3 $3 + 1 + Goto loop + + found: + StrCpy $1 $0 $3 + StrCmp $2 " " +2 + StrCpy $1 '$1"' + + Pop $0 + Exec '$1 $0' + Pop $1 + Pop $2 + Pop $3 +!macroend + +Function func_openLinkNewWindow + !insertmacro openLinkNewWindow_definition +FunctionEnd + +Function un.func_openLinkNewWindow + !insertmacro openLinkNewWindow_definition +FunctionEnd + +!macro _openLinkNewWindow url + StrCpy $0 ${url} + Call ${_UN_}func_openLinkNewWindow +!macroend + +!define OpenLinkNewWindow "!insertmacro _openLinkNewWindow" + diff --git a/installer/Page1-Welcome.nsh b/installer/Page1-Welcome.nsh new file mode 100644 index 0000000..b077d20 --- /dev/null +++ b/installer/Page1-Welcome.nsh @@ -0,0 +1,343 @@ +!ifndef DIGSBY_WELCOME_INCLUDED +!define DIGSBY_WELCOME_INCLDUED + +!include "MUI2.nsh" +!include "nsDialogs.nsh" +!include "LogicLib.nsh" + +!define PAGE_NAME_WELCOME "welcome" +!define WELCOME_PAGE_SPACING 2 +!define DIGSBY_WELCOME_TITLE_TEXT "Welcome to the ${PRODUCT_NAME} Setup Wizard" + +!define DIGSBY_WELCOME_INTRO_TEXT "This wizard will guide you through the installation of ${PRODUCT_NAME}. By clicking Next, I agree to the and consent to install ${PRODUCT_NAME}." +!define DIGSBY_WELCOME_INTRO_LINK_TEXT "Terms of Service" +!define DIGSBY_WELCOME_INTRO_LINK_URL "http://www.digsby.com/tos.php" +# Causes stuff +#!define PAGE_NAME_CAUSES "causes" +!define DIGSBY_CAUSES_BOX_TEXT "Digsby Donates" +!define DIGSBY_CAUSES_CHECK_TEXT "Enable Digsby Donates" +!define DIGSBY_CAUSES_LABEL_TEXT "A free browser plugin that helps support Digsby development and allows us to make a donation at no cost to you whenever you shop at one of over 1,800 participating merchants. [ ]" +#!define DIGSBY_CAUSES_STARTCHECKED +!define DIGSBY_CAUSES_LINK_TEXT "?" +!define DIGSBY_CAUSES_LINK_URL "http://wiki.digsby.com/doku.php?id=donate" + +!ifndef MUI_WELCOMEFINISHPAGE_BITMAP + !define MUI_WELCOMEFINISHPAGE_BITMAP "${DIGSRES}\wizard.bmp" +!endif + +!macro DIGSBY_WELCOME_VARS + !ifndef DIGSBY_WELCOME_VARS + !define DIGSBY_WELCOME_VARS + Var Digsby.Welcome + Var Digsby.Welcome.SidebarImage + Var Digsby.Welcome.SidebarImageCtrl + + Var Digsby.Welcome.CurTop + Var Digsby.Welcome.CurHeight + Var Digsby.Welcome.CurLeft + Var Digsby.Welcome.CurWidth + + Var Digsby.Welcome.CausesCheck + Var Digsby.Welcome.CausesCheck.Value + + Var CausesPage.Install + Var CausesPage.Results + Var CausesPage.Visited + Var CausesPage.Show # Controls if the causes check should show + !endif +!macroend + +!macro DIGSBY_WELCOME_VARS_INIT + !ifndef DIGSBY_WELCOME_VARS_INIT + !define DIGSBY_WELCOME_VARS_INIT + + !ifdef DIGSBY_CAUSES_STARTCHECKED + StrCpy $Digsby.Welcome.CausesCheck.Value "${BST_CHECKED}" + !else + StrCpy $Digsby.Welcome.CausesCheck.Value "${BST_UNCHECKED}" + !endif + !endif +!macroend + +!macro DIGSBY_WELCOME_INIT + !insertmacro DIGSBY_WELCOME_VARS +!macroend + +!macro DIGSBY_WELCOME_PAGEDECL + !define DIGSBY_WELCOME_PRE_FUNCNAME Digsby.WelcomePre_${MUI_UNIQUEID} + !define DIGSBY_WELCOME_LEAVE_FUNCNAME Digsby.WelcomeLeave_${MUI_UNIQUEID} + + !insertmacro DIGSBY_FUNCTION_WELCOMEPAGE "${DIGSBY_WELCOME_PRE_FUNCNAME}" "${DIGSBY_WELCOME_LEAVE_FUNCNAME}" + + PageEx custom + #PageCallbacks OnEnterWelcomePage OnExitWelcomePage + PageCallbacks "${DIGSBY_WELCOME_PRE_FUNCNAME}" "${DIGSBY_WELCOME_LEAVE_FUNCNAME}" + PageExEnd + +!macroend + +!macro DIGSBY_PAGE_WELCOME + !verbose push + !verbose 3 + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEPAGE + !insertmacro DIGSBY_WELCOME_PAGEDECL + + !verbose pop +!macroend + + +!macro _CreateControl_WelcomePage nsdmacro text + ${NSD_Create${nsdmacro}} "$Digsby.Welcome.CurLeftu" "$Digsby.Welcome.CurTopu" "$Digsby.Welcome.CurWidthu" "$Digsby.Welcome.CurHeightu" "${text}" +!macroend + +!define CreateControlW "!insertmacro _CreateControl_WelcomePage " + +!macro DIGSBY_FUNCTION_WELCOMEPAGE PRE LEAVE + + Function CausesPage.InitVars + StrCpy $CausesPage.Show "True" + StrCpy $CausesPage.Install "False" + StrCpy $CausesPage.Visited "False" + IntOp $CausesPage.Results 0 + 0 + FunctionEnd + + Function OpenWelcomeIntroLink + ${OpenLinkNewWindow} "${DIGSBY_WELCOME_INTRO_LINK_URL}" + FunctionEnd + + Function CausesPage.LearnMoreClicked + ${OpenLinkNewWindow} "${DIGSBY_CAUSES_LINK_URL}" + FunctionEnd + + Function "${PRE}" + + !ifdef PAGE_NAME_CAUSES + ${If} $CausesPage.Visited != "True" + ${incr} $NumPagesVisited 1 + StrCpy $CausesPage.Visited "True" + ${EndIf} + !endif + StrCpy $LastSeenPage ${PAGE_NAME_WELCOME} + IntOp $Digsby.Welcome.CurTop 0 + 10 + IntOp $Digsby.Welcome.CurLeft 0 + 120 + IntOp $Digsby.Welcome.CurWidth 0 + 201 + + !insertmacro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + # this file is used by welcome and finish page + File "/oname=$PLUGINSDIR\modern-wizard.bmp" "${MUI_WELCOMEFINISHPAGE_BITMAP}" + StrCpy $CausesPage.Visited "True" + + nsDialogs::Create /NOUNLOAD 1044 + Pop $Digsby.Welcome + SetCtlColors $Digsby.Welcome "" "${MUI_BGCOLOR}" + + ${NSD_CreateBitmap} 0 0 100% 100% "" + Pop $Digsby.Welcome.SidebarImageCtrl + ${NSD_SetImage} $Digsby.Welcome.SidebarImageCtrl "$PLUGINSDIR\modern-wizard.bmp" $Digsby.Welcome.SidebarImage + + LockWindow on + ### Title + IntOp $Digsby.Welcome.CurHeight 0 + 20 + ${CreateControlW} Label "${DIGSBY_WELCOME_TITLE_TEXT}" + Pop $1 + CreateFont $2 "$(^Font)" "12" "700" + SendMessage $1 ${WM_SETFONT} $2 0 + SetCtlColors $1 "" "${MUI_BGCOLOR}" + ### + + ${incr} $Digsby.Welcome.CurTop $Digsby.Welcome.CurHeight + ${incr} $Digsby.Welcome.CurTop ${WELCOME_PAGE_SPACING} + + ### Link, must be absolutely positioned. + ### also have to place it first, for z-order reasons. + ${NSD_CreateLink} 210u 40u 53u 10u "${DIGSBY_WELCOME_INTRO_LINK_TEXT}" + Pop $1 + + SetCtlColors $1 "000080" "${MUI_BGCOLOR}" + ${NSD_OnClick} $1 "OpenWelcomeIntroLink" + ### + + ### Intro + IntOp $Digsby.Welcome.CurHeight 0 + 40 + ${CreateControlW} Label "${DIGSBY_WELCOME_INTRO_TEXT}" + Pop $1 + SetCtlColors $1 "" "${MUI_BGCOLOR}" + ### + + !ifdef PAGE_NAME_CAUSES + ### Causes "Learn more" link + ${NSD_CreateLink} 311u 178u 3u 10u "${DIGSBY_CAUSES_LINK_TEXT}" + Pop $1 + + SetCtlColors $1 "000080" "${MUI_BGCOLOR}" + ${NSD_OnClick} $1 "CausesPage.LearnMoreClicked" + ### + + ### Causes check box + IntOp $Digsby.Welcome.CurHeight 0 + 10 + #IntOp $Digsby.Welcome.CurWidth $Digsby.Welcome.CurWidth - 5 + IntOp $Digsby.Welcome.CurTop 152 + 0 + IntOp $Digsby.Welcome.CurLeft 116 + 0 + + ${CreateControlW} CheckBox "${DIGSBY_CAUSES_CHECK_TEXT}" + Pop $Digsby.Welcome.CausesCheck + ${NSD_SetState} $Digsby.Welcome.CausesCheck $Digsby.Welcome.CausesCheck.Value + + CreateFont $2 "$(^Font)" "7" "700" + SendMessage $Digsby.Welcome.CausesCheck ${WM_SETFONT} $2 0 + SetCtlColors $Digsby.Welcome.CausesCheck "" "${MUI_BGCOLOR}" + + ${incr} $Digsby.Welcome.CurTop $Digsby.Welcome.CurHeight + #${incr} $Digsby.Welcome.CurTop ${WELCOME_PAGE_SPACING} + + ### + ### Causes text box + IntOp $Digsby.Welcome.CurHeight 0 + 30 + ${incr} $Digsby.Welcome.CurLeft 12 + ${incr} $Digsby.Welcome.CurWidth 8 + + ${CreateControlW} Label "${DIGSBY_CAUSES_LABEL_TEXT}" + Pop $1 + SetCtlColors $1 "" "${MUI_BGCOLOR}" + + # not setting an OnClick handler because this is the only checkbox. + # state can be grabbed on page exit + ### End causes + !endif + + GetDlgItem $1 $HWNDPARENT 1028 # 1028 is the "branding text" control + ShowWindow $1 ${SW_HIDE} + GetDlgItem $1 $HWNDPARENT 1256 # 1256 is ... something else that looks like the branding text? + ShowWindow $1 ${SW_HIDE} + GetDlgItem $1 $HWNDPARENT 1035 # 1035 is the horizontal line thingy + ShowWindow $1 ${SW_HIDE} + LockWindow off + + IntOp $CausesPage.Results $CausesPage.Results | ${FLAG_OFFER_ASK} + nsDialogs::Show + GetDlgItem $1 $HWNDPARENT 1028 # 1028 is the "branding text" control + ShowWindow $1 ${SW_SHOW} + GetDlgItem $1 $HWNDPARENT 1256 # 1256 is ... something else that looks like the branding text? + ShowWindow $1 ${SW_SHOW} + GetDlgItem $1 $HWNDPARENT 1035 # 1035 is the horizontal line thingy + ShowWindow $1 ${SW_SHOW} + + FunctionEnd + + Function "${LEAVE}" + + !ifdef PAGE_NAME_XOBNI + Call XobniPage.ShouldShow + ${If} $XobniPage.Show == 1 + #Goto foundone + ${EndIf} + !endif + + !ifdef PAGE_NAME_BCCTHIS + Call BccthisPage.ShouldShow + ${If} $BccthisPage.Show == 1 + ${If} $XobniPage.Show == 1 + Goto foundone + ${EndIf} + ${EndIf} + !endif + + !ifdef PAGE_NAME_OPENCANDY + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Show == 0 + !endif + Call OpencandyPage.ShouldShow + ${If} $OpencandyPage.Show == 1 + #Goto foundone + ${EndIf} + !ifdef PAGE_NAME_XOBNI + ${EndIf} + !endif + !endif + + !ifdef PAGE_NAME_UNIBLUE + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Show == 0 + !endif + Call UnibluePage.ShouldShow + ${If} $UnibluePage.Show == 1 + #Goto foundone + ${EndIf} + !ifdef PAGE_NAME_XOBNI + ${EndIf} + !endif + !endif + + !ifdef PAGE_NAME_ARO + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Show == 0 + !endif + Call AroPage.ShouldShow + ${If} $AroPage.Show == 1 + #Goto foundone + ${EndIf} + !ifdef PAGE_NAME_XOBNI + ${EndIf} + !endif + !endif + + !ifdef PAGE_NAME_BING + Call BingPage.ShouldShow + ${If} $BingPage.Show == 1 + Goto foundone + ${EndIf} + !endif + + !ifdef PAGE_NAME_CRAWLER + Call CrawlerPage.ShouldShow + ${If} $CrawlerPage.Show == 1 + Goto foundone + ${EndIf} + !endif + + !ifdef PAGE_NAME_INBOX + Call InboxPage.ShouldShow + ${If} $InboxPage.Show == 1 + Goto foundone + ${EndIf} + !endif + + !ifdef PAGE_NAME_BABYLON + Call BabylonPage.ShouldShow + ${If} $BabylonPage.Show == 1 + Goto foundone + ${EndIf} + !endif + + !ifdef PAGE_NAME_ASK + Call AskPage.ShouldShow + ${If} $AskPage.Show == 1 + Goto foundone + ${EndIf} + !endif + + foundone: + + ${NSD_GetState} $Digsby.Welcome.CausesCheck $Digsby.Welcome.CausesCheck.Value + ${If} $Digsby.Welcome.CausesCheck.Value == ${BST_CHECKED} + StrCpy $CausesPage.Install "True" + ${Else} + StrCpy $CausesPage.Install "False" + ${EndIf} + + FunctionEnd + + Function CausesPage.PerformInstall + IntOp $CausesPage.Results $CausesPage.Results | ${FLAG_OFFER_ACCEPT} + File "/oname=$PLUGINSDIR\Digsby_Donates.exe" "${DIGSRES}\Digsby_Donates.exe" + ExecWait "$PLUGINSDIR\Digsby_Donates.exe /S" $1 + ${If} $1 == 0 + IntOp $CausesPage.Results $CausesPage.Results | ${FLAG_OFFER_SUCCESS} + ${EndIf} + FunctionEnd +!macroend + +!endif \ No newline at end of file diff --git a/installer/Page5-Finish.nsh b/installer/Page5-Finish.nsh new file mode 100644 index 0000000..4b9fe1d --- /dev/null +++ b/installer/Page5-Finish.nsh @@ -0,0 +1,464 @@ +!ifndef DIGSBY_FINISH_INCLUDED +!define DIGSBY_FINISH_INCLUDED +!verbose push +!verbose 3 + +!include MUI2.nsh +!include nsDialogs.nsh +!include LogicLib.nsh + +!define PAGE_NAME_PLURA "plura" +!define PAGE_NAME_FINISH "finish" +!define FINISH_PAGE_SPACING 2 +!ifndef MUI_WELCOMEFINISHPAGE_BITMAP + !define MUI_WELCOMEFINISHPAGE_BITMAP "${DIGSRES}\wizard.bmp" +!endif + +!macro DIGSBY_FINISH_VARS + !ifndef DIGSBY_FINISH_VARS + !define DIGSBY_FINISH_VARS + + Var Digsby.FinishPage + Var Digsby.Finish.SidebarImage + Var Digsby.Finish.SidebarImageCtrl + Var Digsby.Finish.CurTop + Var Digsby.Finish.CurHeight + Var Digsby.Finish.CurLeft + Var Digsby.Finish.CurWidth + Var Digsby.Finish.Title + Var Digsby.Finish.Text + Var Digsby.Finish.LaunchCheck + + Var Digsby.Finish.PluraLink + Var Digsby.Finish.PluraCheck + Var Digsby.Finish.SearchCheck + Var Digsby.Finish.GoogleHomeCheck + Var Digsby.Finish.AdFreeGroupBox + + Var Digsby.Finish.LaunchCheck.Value + Var Digsby.Finish.PluraCheck.Value + Var Digsby.Finish.SearchCheck.Value + Var Digsby.Finish.GoogleHomeCheck.Value + + Var Digsby.Finish.ShowSearchCheck + Var Digsby.Finish.ShowGoogleHomeCheck + Var Digsby.Finish.ShowPluraCheck + + Var PluraPage.Install + Var PluraPage.Results + Var PluraPage.Visited + + # INIT VARS HERE + !endif +!macroend + +!macro DIGSBY_FINISH_VARS_INIT + !ifndef DIGSBY_FINISH_VARS_INIT + !define DIGSBY_FINISH_VARS_INIT + + !ifdef DIGSBY_FINISH_LAUNCH_STARTCHECKED + StrCpy $Digsby.Finish.LaunchCheck.Value "${BST_CHECKED}" + !else + StrCpy $Digsby.Finish.LaunchCheck.Value "${BST_UNCHECKED}" + !endif + + !ifdef DIGSBY_FINISH_PLURA_STARTCHECKED + StrCpy $Digsby.Finish.PluraCheck.Value "${BST_CHECKED}" + !else + StrCpy $Digsby.Finish.PluraCheck.Value "${BST_UNCHECKED}" + !endif + + !ifdef DIGSBY_FINISH_SEARCH_STARTCHECKED + StrCpy $Digsby.Finish.SearchCheck.Value "${BST_CHECKED}" + !else + StrCpy $Digsby.Finish.SearchCheck.Value "${BST_UNCHECKED}" + !endif + + !ifdef DIGSBY_FINISH_GOOGLE_STARTCHECKED + StrCpy $Digsby.Finish.GoogleHomeCheck.Value "${BST_CHECKED}" + !else + StrCpy $Digsby.Finish.GoogleHomeCheck.Value "${BST_UNCHECKED}" + !endif + + StrCpy $Digsby.Finish.ShowSearchCheck "False" + StrCpy $Digsby.Finish.ShowGoogleHomeCheck "False" + StrCpy $Digsby.Finish.ShowPluraCheck "False" + !endif +!macroend + +!macro DIGSBY_FINISH_INIT + !insertmacro DIGSBY_FINISH_VARS +!macroend + +!macro DIGSBY_FINISH_PAGEDECL + Function PluraPage.InitVars + StrCpy $PluraPage.Install "False" + StrCpy $PluraPage.Visited "False" + IntOp $PluraPage.Results 0 + 0 + FunctionEnd + + # lots of MUI_DEFAULT for string values here + + !define DIGSBY_FINISH_PRE_FUNCNAME Digsby.FinishPre_${MUI_UNIQUEID} + !define DIGSBY_FINISH_LEAVE_FUNCNAME Digsby.FinishLeave_${MUI_UNIQUEID} + PageEx custom + + PageCallbacks "${DIGSBY_FINISH_PRE_FUNCNAME}" "${DIGSBY_FINISH_LEAVE_FUNCNAME}" + Caption " " + PageExEnd + + !insertmacro DIGSBY_FUNCTION_FINISHPAGE "${DIGSBY_FINISH_PRE_FUNCNAME}" "${DIGSBY_FINISH_LEAVE_FUNCNAME}" + + # !undef all the things from MUI_DEFAULT section above +!macroend + +!macro DIGSBY_PAGE_FINISH + !verbose push + !verbose 3 + + !insertmacro MUI_PAGE_INIT + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}FINISHPAGE + !insertmacro DIGSBY_FINISH_PAGEDECL + + !verbose pop +!macroend + +!ifdef _CreateControl + !undef _CreateControl +!endif +!macro _CreateControl nsdmacro text + ${NSD_Create${nsdmacro}} "$Digsby.Finish.CurLeftu" "$Digsby.Finish.CurTopu" "$Digsby.Finish.CurWidthu" "$Digsby.Finish.CurHeightu" "${text}" +!macroend + +!ifdef CreateControl + !undef CreateControl +!endif +!define CreateControl "!insertmacro _CreateControl " + +!macro DIGSBY_FUNCTION_FINISHPAGE PRE LEAVE + Function "${PRE}" + ${incr} $NumPagesVisited 1 + StrCpy $LastSeenPage ${PAGE_NAME_FINISH} + IntOp $Digsby.Finish.CurTop 0 + 10 + IntOp $Digsby.Finish.CurLeft 0 + 120 + IntOp $Digsby.Finish.CurWidth 0 + 201 + + #StrCpy $IsPortable "False" + + !insertmacro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + nsDialogs::Create /NOUNLOAD 1044 + Pop $Digsby.FinishPage + SetCtlColors $Digsby.FinishPage "" "${MUI_BGCOLOR}" + + #${incr} $Digsby.Finish.CurTop ${MUI_FINISHPAGE_V_OFFSET} + + ${NSD_CreateBitmap} 0 0 100% 100% "" + Pop $Digsby.Finish.SidebarImageCtrl + ${NSD_SetImage} $Digsby.Finish.SidebarImageCtrl "$PLUGINSDIR\modern-wizard.bmp" $Digsby.Finish.SidebarImage + + LockWindow on + ### Finish page title + # First control is large bold text- height of 20 + IntOp $Digsby.Finish.CurHeight 0 + 20 + + ${CreateControl} Label $(MUI_TEXT_FINISH_INFO_TITLE) + Pop $Digsby.Finish.Title + + # Set bold and background color + CreateFont $2 "$(^Font)" "12" "700" + SendMessage $Digsby.Finish.Title ${WM_SETFONT} $2 0 + SetCtlColors $Digsby.Finish.Title "" "${MUI_BGCOLOR}" + ### + + ${incr} $Digsby.Finish.CurTop $Digsby.Finish.CurHeight + ${incr} $Digsby.Finish.CurTop ${FINISH_PAGE_SPACING} + + ### Next, three lines of descriptive text + IntOp $Digsby.Finish.CurHeight 0 + 30 + + ${CreateControl} Label "$(^NameDA) has been installed on your computer." + Pop $Digsby.Finish.Text + + # Set background color + SetCtlColors $Digsby.Finish.Text "" "${MUI_BGCOLOR}" + ### + + ${If} $IsPortable == "False" + + ${incr} $Digsby.Finish.CurTop $Digsby.Finish.CurHeight + ${incr} $Digsby.Finish.CurTop ${FINISH_PAGE_SPACING} + + ### "Launch Digsby" checkbox + IntOp $Digsby.Finish.CurHeight 0 + 10 + IntOp $Digsby.Finish.CurTop 0 + 90 + + ${CreateControl} CheckBox "${DIGSBY_FINISH_LAUNCHCHECK_TEXT}" + Pop $Digsby.Finish.LaunchCheck + + # Set background color + SetCtlColors $Digsby.Finish.LaunchCheck "" "${MUI_BGCOLOR}" + ### + ${EndIf} + + # Figure out which checkboxes we're suppose to show + + ${If} $IsPortable == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + StrCpy $Digsby.Finish.ShowGoogleHomeCheck "False" + ${EndIf} + + !ifdef PAGE_NAME_XOBNI + ${If} $XobniPage.Install == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + ${EndIf} + !endif + + !ifdef PAGE_NAME_BING + ${If} $BingPage.Install == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + StrCpy $Digsby.Finish.ShowGoogleHomeCheck "False" + ${EndIf} + !endif + + !ifdef PAGE_NAME_BABYLON + ${If} $BabylonPage.Install == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + StrCpy $Digsby.Finish.ShowGoogleHomeCheck "False" + ${EndIf} + !endif + + !ifdef PAGE_NAME_UNIBLUE + ${If} $UnibluePage.Install == "True" + # Not a search offer, doesn't change anything + ${EndIf} + !endif + + !ifdef PAGE_NAME_ARO + ${If} $AroPage.Install == "True" + # Not a search offer, doesn't change anything + ${EndIf} + !endif + + !ifdef PAGE_NAME_ASK + ${If} $AskPage.Install == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + ${EndIf} + !endif + + !ifdef PAGE_NAME_CRAWLER + ${If} $CrawlerPage.Install == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + ${EndIf} + !endif + + !ifdef PAGE_NAME_INBOX + ${If} $InboxPage.Install == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + ${EndIf} + !endif + + !ifdef PAGE_NAME_OPENCANDY + ${If} $OpencandyPage.Visited == "True" + StrCpy $Digsby.Finish.ShowSearchCheck "False" + StrCpy $Digsby.Finish.ShowGoogleHomeCheck "False" + StrCpy $Digsby.Finish.ShowPluraCheck "False" + ${EndIf} + !endif + + ${If} $Digsby.Finish.ShowSearchCheck == "False" + StrCpy $Digsby.Finish.SearchCheck.Value ${BST_UNCHECKED} + ${EndIf} + ${If} $Digsby.Finish.ShowPluraCheck == "False" + StrCpy $Digsby.Finish.PluraCheck.Value ${BST_UNCHECKED} + ${EndIf} + ${If} $Digsby.Finish.ShowGoogleHomeCheck == "False" + StrCpy $Digsby.Finish.GoogleHomeCheck.Value ${BST_UNCHECKED} + ${EndIf} + + + ## + # Now we start building up from the bottom. + ## + + IntOp $Digsby.Finish.CurHeight 0 + 8 + IntOp $Digsby.Finish.CurTop 0 + 180 + + ${If} $Digsby.Finish.ShowPluraCheck == "True" + ### Link + # move right 10ish pixels + ${incr} $Digsby.Finish.CurLeft 181 + ${decr} $Digsby.Finish.CurWidth 196 + ${CreateControl} Link "${DIGSBY_FINISH_PLURALINK_TEXT}" + Pop $Digsby.Finish.PluraLink + + SetCtlColors $Digsby.Finish.PluraLink "000080" "${MUI_BGCOLOR}" + + ${NSD_OnClick} $Digsby.Finish.PluraLink ${DIGSBY_FINISH_PLURALINK_ONCLICK} + + # move back to the left + ${decr} $Digsby.Finish.CurLeft 181 + ${incr} $Digsby.Finish.CurWidth 196 + + ${EndIf} + + IntOp $Digsby.Finish.CurHeight 0 + 10 + + ${If} $Digsby.Finish.ShowPluraCheck == "True" + + ### Checkbox + + #${decr} $Digsby.Finish.CurTop $Digsby.Finish.CurHeight + #${decr} $Digsby.Finish.CurTop ${FINISH_PAGE_SPACING} + + ### Enable Plura checkbox + ${CreateControl} CheckBox "${DIGSBY_FINISH_PLURACHECK_TEXT}" + Pop $Digsby.Finish.PluraCheck + + # Set background color + SetCtlColors $Digsby.Finish.PluraCheck "" "${MUI_BGCOLOR}" + IntOp $PluraPage.Results $PluraPage.Results | ${FLAG_OFFER_ASK} + StrCpy $PluraPage.Visited "True" + ### + ${EndIf} + + + ${If} $Digsby.Finish.ShowSearchCheck == "True" + ${decr} $Digsby.Finish.CurTop $Digsby.Finish.CurHeight + ${decr} $Digsby.Finish.CurTop ${FINISH_PAGE_SPACING} + + ### + ${CreateControl} CheckBox "${DIGSBY_FINISH_SEARCHCHECK_TEXT}" + Pop $Digsby.Finish.SearchCheck + + SetCtlColors $Digsby.Finish.SearchCheck "" "${MUI_BGCOLOR}" + ### + ${EndIf} + ${If} $Digsby.Finish.ShowGoogleHomeCheck == "True" + ${decr} $Digsby.Finish.CurTop $Digsby.Finish.CurHeight + ${decr} $Digsby.Finish.CurTop ${FINISH_PAGE_SPACING} + + ### + ${CreateControl} CheckBox "${DIGSBY_FINISH_GOOGLEHOMECHECK_TEXT}" + Pop $Digsby.Finish.GoogleHomeCheck + + SetCtlColors $Digsby.Finish.GoogleHomeCheck "" "${MUI_BGCOLOR}" + ### + ${EndIf} + + ${decr} $Digsby.Finish.CurTop $Digsby.Finish.CurHeight + #${decr} $Digsby.Finish.CurTop ${FINISH_PAGE_SPACING} + #${decr} $Digsby.Finish.CurTop ${FINISH_PAGE_SPACING} + ${decr} $Digsby.Finish.CurLeft 4 + ${incr} $Digsby.Finish.CurWidth 8 + + IntOp $Digsby.Finish.CurHeight 0 + 10 + ${If} $Digsby.Finish.ShowPluraCheck == "True" + IntOp $Digsby.Finish.CurHeight $Digsby.Finish.CurHeight + 12 + ${EndIf} + + ${If} $Digsby.Finish.ShowSearchCheck == "True" + IntOp $Digsby.Finish.CurHeight $Digsby.Finish.CurHeight + 12 + ${EndIf} + + ${If} $Digsby.Finish.ShowGoogleHomeCheck == "True" + IntOp $Digsby.Finish.CurHeight $Digsby.Finish.CurHeight + 12 + ${EndIf} + + ${If} $Digsby.Finish.ShowPluraCheck == "True" + ${OrIf} $Digsby.Finish.ShowSearchCheck == "True" + ${OrIf} $Digsby.Finish.ShowGoogleHomeCheck == "True" + + ${CreateControl} GroupBox "Help Support Digsby Development" + Pop $Digsby.Finish.AdFreeGroupBox + SetCtlColors $Digsby.Finish.AdFreeGroupBox "" "${MUI_BGCOLOR}" + CreateFont $2 "$(^Font)" "7" "700" + SendMessage $Digsby.Finish.AdFreeGroupBox ${WM_SETFONT} $2 0 + ${EndIf} + + LockWindow off + SendMessage $Digsby.Finish.LaunchCheck ${BM_SETCHECK} "$Digsby.Finish.LaunchCheck.Value" 0 + SendMessage $Digsby.Finish.PluraCheck ${BM_SETCHECK} "$Digsby.Finish.PluraCheck.Value" 0 + SendMessage $Digsby.Finish.SearchCheck ${BM_SETCHECK} "$Digsby.Finish.SearchCheck.Value" 0 + SendMessage $Digsby.Finish.GoogleHomeCheck ${BM_SETCHECK} "$Digsby.Finish.GoogleHomeCheck.Value" 0 + + GetDlgItem $1 $HWNDPARENT 1028 # 1028 is the "branding text" control + ShowWindow $1 ${SW_HIDE} + GetDlgItem $1 $HWNDPARENT 1256 # 1256 is ... something else that looks like the branding text? + ShowWindow $1 ${SW_HIDE} + GetDlgItem $1 $HWNDPARENT 1035 # 1035 is the horizontal line thingy + ShowWindow $1 ${SW_HIDE} + + !ifdef DIGSBY_FINISH_CANCEL_DISABLED + GetDlgItem $1 $HWNDPARENT 3 # cancel button + EnableWindow $1 0 + !endif + + !ifdef DIGSBY_FINISH_BACK_DISABLED + GetDlgItem $1 $HWNDPARENT 2 # Back button + EnableWindow $1 0 + !endif + + !ifdef DIGSBY_FINISH_NEXT_TEXT + GetDlgItem $1 $HWNDPARENT 1 # next button + SendMessage $1 ${WM_SETTEXT} 0 "STR:${DIGSBY_FINISH_NEXT_TEXT}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + nsDialogs::Show + GetDlgItem $1 $HWNDPARENT 1028 # 1028 is the "branding text" control + ShowWindow $1 ${SW_SHOW} + GetDlgItem $1 $HWNDPARENT 1256 # 1256 is ... something else that looks like the branding text? + ShowWindow $1 ${SW_SHOW} + GetDlgItem $1 $HWNDPARENT 1035 # 1035 is the horizontal line thingy + ShowWindow $1 ${SW_SHOW} + + ${NSD_FreeImage} $Digsby.Finish.SidebarImage + + FunctionEnd + + Function "${LEAVE}" + SetShellVarContext current + + ${If} $IsPortable == "True" + # Make sure these are disabled for portable mode. + StrCpy $Digsby.Finish.LaunchCheck.Value ${BST_UNCHECKED} + StrCpy $Digsby.Finish.SearchCheck.Value ${BST_UNCHECKED} + StrCpy $Digsby.Finish.GoogleHomeCheck.Value ${BST_UNCHECKED} + ${Else} + ${NSD_GetState} $Digsby.Finish.LaunchCheck $Digsby.Finish.LaunchCheck.Value + ${NSD_GetState} $Digsby.Finish.GoogleHomeCheck $Digsby.Finish.GoogleHomeCheck.Value + ${NSD_GetState} $Digsby.Finish.SearchCheck $Digsby.Finish.SearchCheck.Value + ${NSD_GetState} $Digsby.Finish.PluraCheck $Digsby.Finish.PluraCheck.Value + ${EndIf} + + ## Call functions for finish page check boxes + ${If} $Digsby.Finish.LaunchCheck.Value == ${BST_CHECKED} + Call ${DIGSBY_FINISH_LAUNCHCHECK_FUNC} + ${EndIf} + + ${If} $Digsby.Finish.PluraCheck.Value == ${BST_CHECKED} + StrCpy $PluraPage.Install "True" + IntOp $PluraPage.Results $PluraPage.Results | ${FLAG_OFFER_ACCEPT} + IntOp $PluraPage.Results $PluraPage.Results | ${FLAG_OFFER_SUCCESS} + Call ${DIGSBY_FINISH_PLURACHECK_FUNC} + ${EndIf} + + ${If} $Digsby.Finish.SearchCheck.Value == ${BST_CHECKED} + Call ${DIGSBY_FINISH_SEARCHCHECK_FUNC} + ${EndIf} + + ${If} $Digsby.Finish.GoogleHomeCheck.Value == ${BST_CHECKED} + Call ${DIGSBY_FINISH_GOOGLEHOMECHECK_FUNC} + ${EndIf} + ### + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + FunctionEnd + +!macroend + +!endif + diff --git a/installer/System.nsh b/installer/System.nsh new file mode 100644 index 0000000..20b323e --- /dev/null +++ b/installer/System.nsh @@ -0,0 +1,2775 @@ +/* + +NSIS Modern User Interface - Version 1.8 +Copyright 2002-2007 Joost Verburg + +*/ + +!echo "NSIS Modern User Interface version 1.8 - 2002-2007 Joost Verburg" + +;-------------------------------- + +!ifndef MUI_INCLUDED +!define MUI_INCLUDED + +!define MUI_SYSVERSION "1.8" + +!verbose push + +!ifndef MUI_VERBOSE + !define MUI_VERBOSE 3 +!endif + +!verbose ${MUI_VERBOSE} + +;-------------------------------- +;HEADER FILES, DECLARATIONS + +!include InstallOptions.nsh +!include LangFile.nsh +!include WinMessages.nsh + +!define LANGFILE_DEFAULT "${NSISDIR}\Contrib\Language files\English.nsh" + +Var MUI_TEMP1 +Var MUI_TEMP2 +Var MUI_LINK_ID + +!define MUI_FINISHPAGE_V_OFFSET 0 + +;-------------------------------- +;INSERT CODE + +!macro MUI_INSERT + + !ifndef MUI_INSERT + !define MUI_INSERT + + !ifdef MUI_PRODUCT | MUI_VERSION + !warning "The MUI_PRODUCT and MUI_VERSION defines have been removed. Use a normal Name command now." + !endif + + !insertmacro MUI_INTERFACE + + !insertmacro MUI_FUNCTION_GUIINIT + !insertmacro MUI_FUNCTION_ABORTWARNING + + !ifdef MUI_IOCONVERT_USED + !insertmacro INSTALLOPTIONS_FUNCTION_WRITE_CONVERT + !endif + + !ifdef MUI_UNINSTALLER + !insertmacro MUI_UNFUNCTION_GUIINIT + !insertmacro MUI_FUNCTION_UNABORTWARNING + + !ifdef MUI_UNIOCONVERT_USED + !insertmacro INSTALLOPTIONS_UNFUNCTION_WRITE_CONVERT + !endif + !endif + + !endif + +!macroend + +;-------------------------------- +;GENERAL + +!macro MUI_DEFAULT SYMBOL CONTENT + + !ifndef "${SYMBOL}" + !define "${SYMBOL}" "${CONTENT}" + !endif + +!macroend + +!macro MUI_DEFAULT_IOCONVERT SYMBOL CONTENT + + !ifndef "${SYMBOL}" + !define "${SYMBOL}" "${CONTENT}" + !insertmacro MUI_SET "${SYMBOL}_DEFAULTSET" + !insertmacro MUI_SET "MUI_${MUI_PAGE_UNINSTALLER_PREFIX}IOCONVERT_USED" + !else + !insertmacro MUI_UNSET "${SYMBOL}_DEFAULTSET" + !endif + +!macroend + +!macro MUI_SET SYMBOL + + !ifndef "${SYMBOL}" + !define "${SYMBOL}" + !endif + +!macroend + +!macro MUI_UNSET SYMBOL + + !ifdef "${SYMBOL}" + !undef "${SYMBOL}" + !endif + +!macroend + +;-------------------------------- +;INTERFACE - COMPILE TIME SETTINGS + +!macro MUI_INTERFACE + + !ifndef MUI_INTERFACE + !define MUI_INTERFACE + + !ifdef MUI_INSERT_NSISCONF + !insertmacro MUI_NSISCONF + !endif + + !insertmacro MUI_DEFAULT MUI_UI "${NSISDIR}\Contrib\UIs\modern.exe" + !insertmacro MUI_DEFAULT MUI_UI_HEADERIMAGE "${NSISDIR}\Contrib\UIs\modern_headerbmp.exe" + !insertmacro MUI_DEFAULT MUI_UI_HEADERIMAGE_RIGHT "${NSISDIR}\Contrib\UIs\modern_headerbmpr.exe" + !insertmacro MUI_DEFAULT MUI_UI_COMPONENTSPAGE_SMALLDESC "${NSISDIR}\Contrib\UIs\modern_smalldesc.exe" + !insertmacro MUI_DEFAULT MUI_UI_COMPONENTSPAGE_NODESC "${NSISDIR}\Contrib\UIs\modern_nodesc.exe" + !insertmacro MUI_DEFAULT MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" + !insertmacro MUI_DEFAULT MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_CHECKBITMAP "${NSISDIR}\Contrib\Graphics\Checks\modern.bmp" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_BGCOLOR "/windows" + !insertmacro MUI_DEFAULT MUI_INSTFILESPAGE_COLORS "/windows" + !insertmacro MUI_DEFAULT MUI_INSTFILESPAGE_PROGRESSBAR "smooth" + !insertmacro MUI_DEFAULT MUI_BGCOLOR "FFFFFF" + !insertmacro MUI_DEFAULT MUI_WELCOMEFINISHPAGE_INI "${NSISDIR}\Contrib\Modern UI\ioSpecial.ini" + !insertmacro MUI_DEFAULT MUI_UNWELCOMEFINISHPAGE_INI "${NSISDIR}\Contrib\Modern UI\ioSpecial.ini" + !insertmacro MUI_DEFAULT MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" + !insertmacro MUI_DEFAULT MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" + + !ifdef MUI_HEADERIMAGE + + !insertmacro MUI_DEFAULT MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp" + + !ifndef MUI_HEADERIMAGE_UNBITMAP + !define MUI_HEADERIMAGE_UNBITMAP "${MUI_HEADERIMAGE_BITMAP}" + !ifdef MUI_HEADERIMAGE_BITMAP_NOSTRETCH + !insertmacro MUI_SET MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH + !endif + !endif + + !ifdef MUI_HEADERIMAGE_BITMAP_RTL + !ifndef MUI_HEADERIMAGE_UNBITMAP_RTL + !define MUI_HEADERIMAGE_UNBITMAP_RTL "${MUI_HEADERIMAGE_BITMAP_RTL}" + !ifdef MUI_HEADERIMAGE_BITMAP_RTL_NOSTRETCH + !insertmacro MUI_SET MUI_HEADERIMAGE_UNBITMAP_RTL_NOSTRETCH + !endif + !endif + !endif + + !endif + + XPStyle On + + ChangeUI all "${MUI_UI}" + !ifdef MUI_HEADERIMAGE + !ifndef MUI_HEADERIMAGE_RIGHT + ChangeUI IDD_INST "${MUI_UI_HEADERIMAGE}" + !else + ChangeUI IDD_INST "${MUI_UI_HEADERIMAGE_RIGHT}" + !endif + !endif + !ifdef MUI_COMPONENTSPAGE_SMALLDESC + ChangeUI IDD_SELCOM "${MUI_UI_COMPONENTSPAGE_SMALLDESC}" + !else ifdef MUI_COMPONENTSPAGE_NODESC + ChangeUI IDD_SELCOM "${MUI_UI_COMPONENTSPAGE_NODESC}" + !endif + + Icon "${MUI_ICON}" + UninstallIcon "${MUI_UNICON}" + + CheckBitmap "${MUI_COMPONENTSPAGE_CHECKBITMAP}" + LicenseBkColor "${MUI_LICENSEPAGE_BGCOLOR}" + InstallColors ${MUI_INSTFILESPAGE_COLORS} + InstProgressFlags ${MUI_INSTFILESPAGE_PROGRESSBAR} + + SubCaption 4 " " + UninstallSubCaption 2 " " + + !insertmacro MUI_DEFAULT MUI_ABORTWARNING_TEXT "$(MUI_TEXT_ABORTWARNING)" + !insertmacro MUI_DEFAULT MUI_UNABORTWARNING_TEXT "$(MUI_UNTEXT_ABORTWARNING)" + + !endif + +!macroend + +;-------------------------------- +;INTERFACE - RUN-TIME + +!macro MUI_INNERDIALOG_TEXT CONTROL TEXT + + !verbose push + !verbose ${MUI_VERBOSE} + + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 ${CONTROL} + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:${TEXT}" + + !verbose pop + +!macroend + +!macro MUI_HEADER_TEXT_INTERNAL ID TEXT + + GetDlgItem $MUI_TEMP1 $HWNDPARENT "${ID}" + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + !endif + + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:${TEXT}" + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + ShowWindow $MUI_TEMP1 ${SW_SHOWNA} + + !endif + +!macroend + +!macro MUI_HEADER_TEXT TEXT SUBTEXT + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + LockWindow on + + !endif + + !insertmacro MUI_HEADER_TEXT_INTERNAL 1037 "${TEXT}" + !insertmacro MUI_HEADER_TEXT_INTERNAL 1038 "${SUBTEXT}" + + !ifdef MUI_HEADER_TRANSPARENT_TEXT + + LockWindow off + + !endif + + !verbose pop + +!macroend + +!macro MUI_HEADER_TEXT_PAGE TEXT SUBTEXT + + !ifdef MUI_PAGE_HEADER_TEXT & MUI_PAGE_HEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_PAGE_HEADER_TEXT}" "${MUI_PAGE_HEADER_SUBTEXT}" + !else ifdef MUI_PAGE_HEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_PAGE_HEADER_TEXT}" "${SUBTEXT}" + !else ifdef MUI_PAGE_HEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${TEXT}" "${MUI_PAGE_HEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "${TEXT}" "${SUBTEXT}" + !endif + + !insertmacro MUI_UNSET MUI_PAGE_HEADER_TEXT + !insertmacro MUI_UNSET MUI_PAGE_HEADER_SUBTEXT + +!macroend + +!macro MUI_DESCRIPTION_BEGIN + + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 1043 + + StrCmp $0 -1 0 mui.description_begin_done + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:" + EnableWindow $MUI_TEMP1 0 + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:$MUI_TEXT" + Goto mui.description_done + mui.description_begin_done: + +!macroend + +!macro MUI_DESCRIPTION_TEXT VAR TEXT + + !verbose push + !verbose ${MUI_VERBOSE} + + StrCmp $0 ${VAR} 0 mui.description_${VAR}_done + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:" + EnableWindow $MUI_TEMP1 1 + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:${TEXT}" + Goto mui.description_done + mui.description_${VAR}_done: + + !verbose pop + +!macroend + +!macro MUI_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + mui.description_done: + + !verbose pop + +!macroend + +!macro MUI_ENDHEADER + + IfAbort mui.endheader_abort + + !ifdef MUI_INSTFILESPAGE_FINISHHEADER_TEXT & MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_FINISHHEADER_TEXT}" "${MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT}" + !else ifdef MUI_INSTFILESPAGE_FINISHHEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_FINISHHEADER_TEXT}" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_SUBTITLE)" + !else ifdef MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_TITLE)" "${MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_TITLE)" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_SUBTITLE)" + !endif + + Goto mui.endheader_done + + mui.endheader_abort: + + !ifdef MUI_INSTFILESPAGE_ABORTHEADER_TEXT & MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_ABORTHEADER_TEXT}" "${MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT}" + !else ifdef MUI_INSTFILESPAGE_ABORTHEADER_TEXT + !insertmacro MUI_HEADER_TEXT "${MUI_INSTFILESPAGE_ABORTHEADER_TEXT}" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_SUBTITLE)" + !else ifdef MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_TITLE)" "${MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT}" + !else + !insertmacro MUI_HEADER_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_TITLE)" "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_ABORT_SUBTITLE)" + !endif + + mui.endheader_done: + +!macroend + +!macro MUI_ABORTWARNING + + !ifdef MUI_FINISHPAGE_ABORTWARNINGCHECK + StrCmp $MUI_NOABORTWARNING "1" mui.quit + !endif + + !ifdef MUI_ABORTWARNING_CANCEL_DEFAULT + MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 "${MUI_ABORTWARNING_TEXT}" IDYES mui.quit + !else + MessageBox MB_YESNO|MB_ICONEXCLAMATION "${MUI_ABORTWARNING_TEXT}" IDYES mui.quit + !endif + + Abort + mui.quit: + +!macroend + +!macro MUI_UNABORTWARNING + + !ifdef MUI_UNABORTWARNING_CANCEL_DEFAULT + MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 "${MUI_UNABORTWARNING_TEXT}" IDYES mui.quit + !else + MessageBox MB_YESNO|MB_ICONEXCLAMATION "${MUI_UNABORTWARNING_TEXT}" IDYES mui.quit + !endif + + Abort + mui.quit: + +!macroend + +!macro MUI_GUIINIT + + !insertmacro MUI_WELCOMEFINISHPAGE_INIT "" + !insertmacro MUI_HEADERIMAGE_INIT "" + + !insertmacro MUI_GUIINIT_BASIC + +!macroend + +!macro MUI_UNGUIINIT + + !insertmacro MUI_WELCOMEFINISHPAGE_INIT "UN" + !insertmacro MUI_HEADERIMAGE_INIT "UN" + + !insertmacro MUI_GUIINIT_BASIC + + !ifdef MUI_UNFINISHPAGE + !ifndef MUI_UNFINISHPAGE_NOAUTOCLOSE + SetAutoClose true + !endif + !endif + +!macroend + +!macro MUI_GUIINIT_BASIC + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + CreateFont $MUI_TEMP2 "$(^Font)" "$(^FontSize)" "700" + SendMessage $MUI_TEMP1 ${WM_SETFONT} $MUI_TEMP2 0 + + !ifndef MUI_HEADER_TRANSPARENT_TEXT + + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + !else + + SetCtlColors $MUI_TEMP1 "" "transparent" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + SetCtlColors $MUI_TEMP1 "" "transparent" + + !endif + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1034 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + SetCtlColors $MUI_TEMP1 /BRANDING + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + SetCtlColors $MUI_TEMP1 /BRANDING + SendMessage $MUI_TEMP1 ${WM_SETTEXT} 0 "STR:$(^Branding) " + +!macroend + +!macro MUI_WELCOMEFINISHPAGE_INIT UNINSTALLER + + !ifdef MUI_${UNINSTALLER}WELCOMEPAGE | MUI_${UNINSTALLER}FINISHPAGE + + !insertmacro INSTALLOPTIONS_EXTRACT_AS "${MUI_${UNINSTALLER}WELCOMEFINISHPAGE_INI}" "ioSpecial.ini" + File "/oname=$PLUGINSDIR\modern-wizard.bmp" "${MUI_${UNINSTALLER}WELCOMEFINISHPAGE_BITMAP}" + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 1" "Text" "$PLUGINSDIR\modern-wizard.bmp" + + !ifdef MUI_${UNINSTALLER}WELCOMEFINISHPAGE_BITMAP_NOSTRETCH + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 1" "Flags" "" + !endif + + !endif + +!macroend + +!macro MUI_HEADERIMAGE_INIT UNINSTALLER + + !ifdef MUI_HEADERIMAGE + + InitPluginsDir + + !ifdef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL + + StrCmp $(^RTL) 0 mui.headerimageinit_nortl + + File "/oname=$PLUGINSDIR\modern-header.bmp" "${MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL}" + + !ifndef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL_NOSTRETCH + SetBrandingImage /IMGID=1046 /RESIZETOFIT "$PLUGINSDIR\modern-header.bmp" + !else + SetBrandingImage /IMGID=1046 "$PLUGINSDIR\modern-header.bmp" + !endif + + Goto mui.headerimageinit_done + + mui.headerimageinit_nortl: + + !endif + + File "/oname=$PLUGINSDIR\modern-header.bmp" "${MUI_HEADERIMAGE_${UNINSTALLER}BITMAP}" + + !ifndef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_NOSTRETCH + SetBrandingImage /IMGID=1046 /RESIZETOFIT "$PLUGINSDIR\modern-header.bmp" + !else + SetBrandingImage /IMGID=1046 "$PLUGINSDIR\modern-header.bmp" + !endif + + !ifdef MUI_HEADERIMAGE_${UNINSTALLER}BITMAP_RTL + + mui.headerimageinit_done: + + !endif + + !endif + +!macroend + +;-------------------------------- +;INTERFACE - FUNCTIONS + +!macro MUI_FUNCTION_GUIINIT + + Function .onGUIInit + + !insertmacro MUI_GUIINIT + + !ifdef MUI_CUSTOMFUNCTION_GUIINIT + Call "${MUI_CUSTOMFUNCTION_GUIINIT}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_DESCRIPTION_BEGIN + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifndef MUI_VAR_TEXT + Var MUI_TEXT + !define MUI_VAR_TEXT + !endif + + Function .onMouseOverSection + !insertmacro MUI_DESCRIPTION_BEGIN + + !verbose pop + +!macroend + +!macro MUI_FUNCTION_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DESCRIPTION_END + !ifdef MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION + Call "${MUI_CUSTOMFUNCTION_ONMOUSEOVERSECTION}" + !endif + FunctionEnd + + !verbose pop + +!macroend + +!macro MUI_UNFUNCTION_DESCRIPTION_BEGIN + + !verbose push + !verbose ${MUI_VERBOSE} + + Function un.onMouseOverSection + !insertmacro MUI_DESCRIPTION_BEGIN + + !verbose pop + +!macroend + +!macro MUI_UNFUNCTION_DESCRIPTION_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DESCRIPTION_END + !ifdef MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION + Call "${MUI_CUSTOMFUNCTION_UNONMOUSEOVERSECTION}" + !endif + FunctionEnd + + !verbose pop + +!macroend + +!macro MUI_FUNCTION_ABORTWARNING + + Function .onUserAbort + !ifdef MUI_ABORTWARNING + !insertmacro MUI_ABORTWARNING + !endif + !ifdef MUI_CUSTOMFUNCTION_ABORT + Call "${MUI_CUSTOMFUNCTION_ABORT}" + !endif + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_UNABORTWARNING + + Function un.onUserAbort + !ifdef MUI_UNABORTWARNING + !insertmacro MUI_UNABORTWARNING + !endif + !ifdef MUI_CUSTOMFUNCTION_UNABORT + Call "${MUI_CUSTOMFUNCTION_UNABORT}" + !endif + FunctionEnd + +!macroend + +!macro MUI_UNFUNCTION_GUIINIT + + Function un.onGUIInit + + !insertmacro MUI_UNGUIINIT + + !ifdef MUI_CUSTOMFUNCTION_UNGUIINIT + Call "${MUI_CUSTOMFUNCTION_UNGUIINIT}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTIONS_DESCRIPTION_BEGIN + + ;1.65 compatibility + + !warning "Modern UI macro name has changed. Please change MUI_FUNCTIONS_DESCRIPTION_BEGIN to MUI_FUNCTION_DESCRIPTION_BEGIN." + + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + +!macroend + +!macro MUI_FUNCTIONS_DESCRIPTION_END + + ;1.65 compatibility + + !warning "Modern UI macro name has changed. Please change MUI_FUNCTIONS_DESCRIPTION_END to MUI_FUNCTION_DESCRIPTION_END." + + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +!macroend + +;-------------------------------- +;START MENU FOLDER + +!macro MUI_STARTMENU_GETFOLDER ID VAR + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT & MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY & MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME + + ReadRegStr $MUI_TEMP1 "${MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" +3 + StrCpy "${VAR}" $MUI_TEMP1 + Goto +2 + + StrCpy "${VAR}" "${MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER}" + + !else + + StrCpy "${VAR}" "${MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER}" + + !endif + + !verbose pop + +!macroend + +!macro MUI_STARTMENU_WRITE_BEGIN ID + + !verbose push + !verbose ${MUI_VERBOSE} + + !define MUI_STARTMENUPAGE_CURRENT_ID "${ID}" + + StrCpy $MUI_TEMP1 "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" 1 + StrCmp $MUI_TEMP1 ">" mui.startmenu_write_${MUI_STARTMENUPAGE_CURRENT_ID}_done + + StrCmp "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" "" 0 mui.startmenu_writebegin_${MUI_STARTMENUPAGE_CURRENT_ID}_notempty + + !insertmacro MUI_STARTMENU_GETFOLDER "${MUI_STARTMENUPAGE_CURRENT_ID}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" + + mui.startmenu_writebegin_${MUI_STARTMENUPAGE_CURRENT_ID}_notempty: + + !verbose pop + +!macroend + +!macro MUI_STARTMENU_WRITE_END + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifdef MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_ROOT & MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_KEY & MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_VALUENAME + WriteRegStr "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_REGISTRY_VALUENAME}" "${MUI_STARTMENUPAGE_${MUI_STARTMENUPAGE_CURRENT_ID}_VARIABLE}" + !endif + + mui.startmenu_write_${MUI_STARTMENUPAGE_CURRENT_ID}_done: + + !undef MUI_STARTMENUPAGE_CURRENT_ID + + !verbose pop + +!macroend + +;-------------------------------- +;PAGES + +!macro MUI_PAGE_INIT + + !insertmacro MUI_INTERFACE + + !insertmacro MUI_DEFAULT MUI_PAGE_UNINSTALLER_PREFIX "" + !insertmacro MUI_DEFAULT MUI_PAGE_UNINSTALLER_FUNCPREFIX "" + + !insertmacro MUI_UNSET MUI_UNIQUEID + + !define MUI_UNIQUEID ${__LINE__} + +!macroend + +!macro MUI_UNPAGE_INIT + + !ifndef MUI_UNINSTALLER + !define MUI_UNINSTALLER + !endif + + !define MUI_PAGE_UNINSTALLER + + !insertmacro MUI_UNSET MUI_PAGE_UNINSTALLER_PREFIX + !insertmacro MUI_UNSET MUI_PAGE_UNINSTALLER_FUNCPREFIX + + !define MUI_PAGE_UNINSTALLER_PREFIX "UN" + !define MUI_PAGE_UNINSTALLER_FUNCPREFIX "un." + +!macroend + +!macro MUI_UNPAGE_END + + !undef MUI_PAGE_UNINSTALLER + !undef MUI_PAGE_UNINSTALLER_PREFIX + !undef MUI_PAGE_UNINSTALLER_FUNCPREFIX + +!macroend + +!macro MUI_PAGE_WELCOME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}WELCOMEPAGE + + !insertmacro MUI_DEFAULT_IOCONVERT MUI_WELCOMEPAGE_TITLE "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_WELCOME_INFO_TITLE)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_WELCOMEPAGE_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_WELCOME_INFO_TEXT)" + !insertmacro MUI_DEFAULT MUI_WELCOMEPAGE_LINK_COLOR "000080" + + !ifndef MUI_VAR_HWND + Var MUI_HWND + !define MUI_VAR_HWND + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomeLeave_${MUI_UNIQUEID} + + PageExEnd + + !insertmacro MUI_FUNCTION_WELCOMEPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.WelcomeLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TITLE + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TITLE_3LINES + !insertmacro MUI_UNSET MUI_WELCOMEPAGE_TEXT + #!insertmacro MUI_UNSET MUI_WELCOMEPAGE_IMAGE + + !verbose pop + +!macroend + +!macro MUI_PAGE_LICENSE LICENSEDATA + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}LICENSEPAGE + + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_TEXT_TOP "$(MUI_INNERTEXT_LICENSE_TOP)" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_BUTTON "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_CHECKBOX_TEXT "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT "" + !insertmacro MUI_DEFAULT MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE "" + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}license + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicensePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseLeave_${MUI_UNIQUEID} + + Caption " " + + LicenseData "${LICENSEDATA}" + + !ifndef MUI_LICENSEPAGE_TEXT_BOTTOM + !ifndef MUI_LICENSEPAGE_CHECKBOX & MUI_LICENSEPAGE_RADIOBUTTONS + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM)" "${MUI_LICENSEPAGE_BUTTON}" + !else ifdef MUI_LICENSEPAGE_CHECKBOX + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM_CHECKBOX)" "${MUI_LICENSEPAGE_BUTTON}" + !else + LicenseText "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS)" "${MUI_LICENSEPAGE_BUTTON}" + !endif + !else + LicenseText "${MUI_LICENSEPAGE_TEXT_BOTTOM}" "${MUI_LICENSEPAGE_BUTTON}" + !endif + + !ifdef MUI_LICENSEPAGE_CHECKBOX + LicenseForceSelection checkbox "${MUI_LICENSEPAGE_CHECKBOX_TEXT}" + !else ifdef MUI_LICENSEPAGE_RADIOBUTTONS + LicenseForceSelection radiobuttons "${MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT}" "${MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE}" + !endif + + PageExEnd + + !insertmacro MUI_FUNCTION_LICENSEPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicensePre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.LicenseLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_LICENSEPAGE_TEXT_TOP + !insertmacro MUI_UNSET MUI_LICENSEPAGE_TEXT_BOTTOM + !insertmacro MUI_UNSET MUI_LICENSEPAGE_BUTTON + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT + !insertmacro MUI_UNSET MUI_LICENSEPAGE_RADIOBUTTONS + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT_ACCEPT + !insertmacro MUI_UNSET MUI_LICENSEPAGE_CHECKBOX_TEXT_DECLINE + + !verbose pop + +!macroend + +!macro MUI_PAGE_COMPONENTS + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}COMPONENTSPAGE + + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_COMPLIST "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_INSTTYPE "" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE "$(MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE)" + !insertmacro MUI_DEFAULT MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO "$(MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO)" + + !ifndef MUI_VAR_TEXT + Var MUI_TEXT + !define MUI_VAR_TEXT + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}components + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsLeave_${MUI_UNIQUEID} + + Caption " " + + ComponentText "${MUI_COMPONENTSPAGE_TEXT_TOP}" "${MUI_COMPONENTSPAGE_TEXT_INSTTYPE}" "${MUI_COMPONENTSPAGE_TEXT_COMPLIST}" + + PageExEnd + + !insertmacro MUI_FUNCTION_COMPONENTSPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.ComponentsLeave_${MUI_UNIQUEID} + + !undef MUI_COMPONENTSPAGE_TEXT_TOP + !undef MUI_COMPONENTSPAGE_TEXT_COMPLIST + !undef MUI_COMPONENTSPAGE_TEXT_INSTTYPE + !insertmacro MUI_UNSET MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE + !insertmacro MUI_UNSET MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO + + !verbose pop + +!macroend + +!macro MUI_PAGE_DIRECTORY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}DIRECTORYPAGE + + !insertmacro MUI_DEFAULT MUI_DIRECTORYPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_DIRECTORYPAGE_TEXT_DESTINATION "" + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}directory + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryLeave_${MUI_UNIQUEID} + + Caption " " + + DirText "${MUI_DIRECTORYPAGE_TEXT_TOP}" "${MUI_DIRECTORYPAGE_TEXT_DESTINATION}" + + !ifdef MUI_DIRECTORYPAGE_VARIABLE + DirVar "${MUI_DIRECTORYPAGE_VARIABLE}" + !endif + + !ifdef MUI_DIRECTORYPAGE_VERIFYONLEAVE + DirVerify leave + !endif + + PageExEnd + + !insertmacro MUI_FUNCTION_DIRECTORYPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.DirectoryLeave_${MUI_UNIQUEID} + + !undef MUI_DIRECTORYPAGE_TEXT_TOP + !undef MUI_DIRECTORYPAGE_TEXT_DESTINATION + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_BGCOLOR + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_VARIABLE + !insertmacro MUI_UNSET MUI_DIRECTORYPAGE_VERIFYONLEAVE + + !verbose pop + +!macroend + +!macro MUI_PAGE_STARTMENU ID VAR + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}STARTMENUPAGE + + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_DEFAULTFOLDER "$(^Name)" + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_TEXT_TOP "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_STARTMENU_TOP)" + !insertmacro MUI_DEFAULT MUI_STARTMENUPAGE_TEXT_CHECKBOX "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INNERTEXT_STARTMENU_CHECKBOX)" + + !define MUI_STARTMENUPAGE_VARIABLE "${VAR}" + !define "MUI_STARTMENUPAGE_${ID}_VARIABLE" "${MUI_STARTMENUPAGE_VARIABLE}" + !define "MUI_STARTMENUPAGE_${ID}_DEFAULTFOLDER" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !ifdef MUI_STARTMENUPAGE_REGISTRY_ROOT + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_ROOT" "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" + !endif + !ifdef MUI_STARTMENUPAGE_REGISTRY_KEY + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_KEY" "${MUI_STARTMENUPAGE_REGISTRY_KEY}" + !endif + !ifdef MUI_STARTMENUPAGE_REGISTRY_VALUENAME + !define "MUI_STARTMENUPAGE_${ID}_REGISTRY_VALUENAME" "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}" + !endif + + !ifndef MUI_VAR_HWND + Var MUI_HWND + !define MUI_VAR_HWND + !endif + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_STARTMENUPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.StartmenuLeave_${MUI_UNIQUEID} + + !undef MUI_STARTMENUPAGE_VARIABLE + !undef MUI_STARTMENUPAGE_TEXT_TOP + !undef MUI_STARTMENUPAGE_TEXT_CHECKBOX + !undef MUI_STARTMENUPAGE_DEFAULTFOLDER + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_NODISABLE + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_ROOT + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_KEY + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_REGISTRY_VALUENAME + !insertmacro MUI_UNSET MUI_STARTMENUPAGE_BGCOLOR + + !verbose pop + +!macroend + +!macro MUI_PAGE_INSTFILES + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}INSTFILESPAGE + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}instfiles + + PageCallbacks ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesLeave_${MUI_UNIQUEID} + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_INSTFILESPAGE ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesPre_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesShow_${MUI_UNIQUEID} ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.InstFilesLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_FINISHHEADER_TEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_ABORTWARNING_TEXT + !insertmacro MUI_UNSET MUI_INSTFILESPAGE_ABORTWARNING_SUBTEXT + + !verbose pop + +!macroend + +!macro MUI_PAGE_FINISH + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_${MUI_PAGE_UNINSTALLER_PREFIX}FINISHPAGE + + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TITLE "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_TITLE)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_TEXT)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_BUTTON "$(MUI_BUTTONTEXT_FINISH)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT_REBOOT "$(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_FINISH_INFO_REBOOT)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT_REBOOTNOW "$(MUI_TEXT_FINISH_REBOOTNOW)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_TEXT_REBOOTLATER "$(MUI_TEXT_FINISH_REBOOTLATER)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_RUN_TEXT "$(MUI_TEXT_FINISH_RUN)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_SHOWREADME_TEXT "$(MUI_TEXT_FINISH_SHOWREADME)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_CHECKBOX3_TEXT "$(MUI_TEXT_FINISH_CHECKBOX3)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_CHECKBOX4_TEXT "$(MUI_TEXT_FINISH_CHECKBOX4)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_CHECKBOX5_TEXT "$(MUI_TEXT_FINISH_CHECKBOX5)" + !insertmacro MUI_DEFAULT_IOCONVERT MUI_FINISHPAGE_CHECKBOX6_TEXT "$(MUI_TEXT_FINISH_CHECKBOX6)" + !insertmacro MUI_DEFAULT MUI_FINISHPAGE_LINK_COLOR "000080" + + !ifndef MUI_VAR_HWND + Var MUI_HWND + !define MUI_VAR_HWND + !endif + + !ifndef MUI_PAGE_UNINSTALLER + !ifndef MUI_FINISHPAGE_NOAUTOCLOSE + AutoCloseWindow true + !endif + !endif + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + !ifndef MUI_VAR_NOABORTWARNING + !define MUI_VAR_NOABORTWARNING + Var MUI_NOABORTWARNING + !endif + !endif + + !define FINISH_PRE_FUNCNAME ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishPre_${MUI_UNIQUEID} + !define FINISH_LEAVE_FUNCNAME ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}mui.FinishLeave_${MUI_UNIQUEID} + + PageEx ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}custom + + PageCallbacks "${FINISH_PRE_FUNCNAME}" "${FINISH_LEAVE_FUNCNAME}" + + Caption " " + + PageExEnd + + !insertmacro MUI_FUNCTION_FINISHPAGE "${FINISH_PRE_FUNCNAME}" "${FINISH_LEAVE_FUNCNAME}" + + !insertmacro MUI_UNSET MUI_FINISHPAGE_TITLE + !insertmacro MUI_UNSET MUI_FINISHPAGE_TITLE_3LINES + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_LARGE + !insertmacro MUI_UNSET MUI_FINISHPAGE_BUTTON + !insertmacro MUI_UNSET MUI_FINISHPAGE_CANCEL_ENABLED + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOT + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOTNOW + !insertmacro MUI_UNSET MUI_FINISHPAGE_TEXT_REBOOTLATER + !insertmacro MUI_UNSET MUI_FINISHPAGE_REBOOTLATER_DEFAULT + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_PARAMETERS + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_RUN_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_SHOWREADME_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3 + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX3_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4 + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX4_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5 + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX5_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX6 + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX6_TEXT + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX6_NOTCHECKED + !insertmacro MUI_UNSET MUI_FINISHPAGE_CHECKBOX6_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK_LOCATION + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK_FUNCTION + !insertmacro MUI_UNSET MUI_FINISHPAGE_LINK_COLOR + !insertmacro MUI_UNSET MUI_FINISHPAGE_NOREBOOTSUPPORT + + !insertmacro MUI_UNSET MUI_FINISHPAGE_CURFIELD_TOP + !insertmacro MUI_UNSET MUI_FINISHPAGE_CURFIELD_BOTTOM + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_WELCOME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_WELCOME + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_CONFIRM + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifndef MUI_UNINSTALLER + !define MUI_UNINSTALLER + !endif + + !insertmacro MUI_PAGE_INIT + + !insertmacro MUI_SET MUI_UNCONFIRMPAGE + + !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_TOP "" + !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_LOCATION "" + + PageEx un.uninstConfirm + + PageCallbacks un.mui.ConfirmPre_${MUI_UNIQUEID} un.mui.ConfirmShow_${MUI_UNIQUEID} un.mui.ConfirmLeave_${MUI_UNIQUEID} + + Caption " " + + UninstallText "${MUI_UNCONFIRMPAGE_TEXT_TOP}" "${MUI_UNCONFIRMPAGE_TEXT_LOCATION}" + + PageExEnd + + !insertmacro MUI_UNFUNCTION_CONFIRMPAGE un.mui.ConfirmPre_${MUI_UNIQUEID} un.mui.ConfirmShow_${MUI_UNIQUEID} un.mui.ConfirmLeave_${MUI_UNIQUEID} + + !insertmacro MUI_UNSET MUI_UNCONFIRMPAGE_TEXT_TOP + !insertmacro MUI_UNSET MUI_UNCONFIRMPAGE_TEXT_LOCATION + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_LICENSE LICENSEDATA + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_LICENSE "${LICENSEDATA}" + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_COMPONENTS + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_COMPONENTS + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_DIRECTORY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_DIRECTORY + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_INSTFILES + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +!macro MUI_UNPAGE_FINISH + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_UNPAGE_INIT + + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_END + + !verbose pop + +!macroend + +;-------------------------------- +;PAGE FUNCTIONS + +!macro MUI_PAGE_FUNCTION_CUSTOM TYPE + !ifdef MUI_PAGE_CUSTOMFUNCTION_${TYPE} + Call "${MUI_PAGE_CUSTOMFUNCTION_${TYPE}}" + !undef MUI_PAGE_CUSTOMFUNCTION_${TYPE} + !endif + +!macroend + +!macro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + !ifdef MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT + Call "${MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT}" + !undef MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT + !endif + +!macroend + +!macro MUI_FUNCTION_WELCOMEPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + !ifdef MUI_WELCOMEPAGE_IMAGE + !ifdef MUI_WELCOMEPAGE_LINK + !define MUI_WELCOME_PAGE_NUMFIELDS 5 + !define MUI_WELCOMEPAGE_LINKFIELD 5 + !else + !define MUI_WELCOME_PAGE_NUMFIELDS 4 + !endif + !else + !ifdef MUI_WELCOMEPAGE_LINK + !define MUI_WELCOMEPAGE_LINKFIELD 4 + !define MUI_WELCOME_PAGE_NUMFIELDS 4 + !else + !define MUI_WELCOME_PAGE_NUMFIELDS 3 + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "NumFields" "${MUI_WELCOME_PAGE_NUMFIELDS}" + !undef MUI_WELCOME_PAGE_NUMFIELDS + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "NextButtonText" "" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "CancelEnabled" "" + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 2" "Text" MUI_WELCOMEPAGE_TITLE + + !ifndef MUI_WELCOMEPAGE_TITLE_3LINES + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "38" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Top" "45" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "48" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Top" "55" + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "95" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 3" "Text" MUI_WELCOMEPAGE_TEXT + + !ifdef MUI_WELCOMEPAGE_IMAGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Type" "Bitmap" + File "/oname=$PLUGINSDIR\welcome_image.bmp" "${MUI_WELCOMEPAGE_IMAGE_FILENAME}" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Text" "$PLUGINSDIR\welcome_image.bmp" + !ifdef MUI_WELCOMEPAGE_IMAGE_NOSTRETCH + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Flags" "" + !endif + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "${MUI_WELCOMEPAGE_IMAGE_TOP}" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "${MUI_WELCOMEPAGE_IMAGE_BOTTOM}" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Left" "${MUI_WELCOMEPAGE_IMAGE_LEFT}" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Right" "${MUI_WELCOMEPAGE_IMAGE_RIGHT}" + !endif + + !ifdef MUI_WELCOMEPAGE_LINK + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_WELCOMEPAGE_LINKFIELD}" "Type" "Link" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_WELCOMEPAGE_LINKFIELD}" "Text" MUI_WELCOMEPAGE_LINK_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_WELCOMEPAGE_LINKFIELD}" "Top" "100" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_WELCOMEPAGE_LINKFIELD}" "Bottom" "110" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_WELCOMEPAGE_LINKFIELD}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_WELCOMEPAGE_LINKFIELD}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_WELCOMEPAGE_LINKFIELD}" "State" "${MUI_WELCOMEPAGE_LINK}" + !undef MUI_WELCOMEPAGE_LINKFIELD + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + LockWindow off + + !insertmacro INSTALLOPTIONS_INITDIALOG "ioSpecial.ini" + Pop $MUI_HWND + SetCtlColors $MUI_HWND "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1201 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + CreateFont $MUI_TEMP2 "$(^Font)" "12" "700" + SendMessage $MUI_TEMP1 ${WM_SETFONT} $MUI_TEMP2 0 + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1202 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + !ifdef MUI_WELCOMEPAGE_LINK + !ifdef MUI_WELCOMEPAGE_IMAGE + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + !endif + SetCtlColors $MUI_TEMP1 "${MUI_WELCOMEPAGE_LINK_COLOR}" "${MUI_BGCOLOR}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + !insertmacro INSTALLOPTIONS_SHOW + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + LockWindow off + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_WELCOME_CUSTOMLEAVE_FUNC + Call "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_WELCOME_CUSTOMLEAVE_FUNC}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_LICENSEPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_LICENSE_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_LICENSE_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_INNERDIALOG_TEXT 1040 "${MUI_LICENSEPAGE_TEXT_TOP}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_LICENSE_CUSTOMLEAVE_FUNC + Call "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_LICENSE_CUSTOMLEAVE_FUNC}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_COMPONENTSPAGE PRE SHOW LEAVE + + Function "${PRE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_COMPONENTS_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_COMPONENTS_SUBTITLE) + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_INNERDIALOG_TEXT 1042 "${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE}" + + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 1043 + EnableWindow $MUI_TEMP1 0 + + !insertmacro MUI_INNERDIALOG_TEXT 1043 "${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO}" + StrCpy $MUI_TEXT "${MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_DIRECTORYPAGE PRE SHOW LEAVE + + Function "${PRE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_SUBTITLE) + FunctionEnd + + Function "${SHOW}" + !ifdef MUI_DIRECTORYPAGE_BGCOLOR + FindWindow $MUI_TEMP1 "#32770" "" $HWNDPARENT + GetDlgItem $MUI_TEMP1 $MUI_TEMP1 1019 + SetCtlColors $MUI_TEMP1 "" "${MUI_DIRECTORYPAGE_BGCOLOR}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + FunctionEnd + + Function "${LEAVE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_DIRECTORY_CUSTOMLEAVE_FUNC + Call "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_DIRECTORY_CUSTOMLEAVE_FUNC}" + !endif + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_STARTMENUPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + !ifdef MUI_STARTMENUPAGE_REGISTRY_ROOT & MUI_STARTMENUPAGE_REGISTRY_KEY & MUI_STARTMENUPAGE_REGISTRY_VALUENAME + + StrCmp "${MUI_STARTMENUPAGE_VARIABLE}" "" 0 +4 + + ReadRegStr $MUI_TEMP1 "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" "${MUI_STARTMENUPAGE_REGISTRY_KEY}" "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" +2 + StrCpy "${MUI_STARTMENUPAGE_VARIABLE}" $MUI_TEMP1 + + !endif + + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_TEXT_STARTMENU_TITLE) $(MUI_TEXT_STARTMENU_SUBTITLE) + + StrCmp $(^RTL) 0 mui.startmenu_nortl + !ifndef MUI_STARTMENUPAGE_NODISABLE + StartMenu::Init /NOUNLOAD /rtl /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" /checknoshortcuts "${MUI_STARTMENUPAGE_TEXT_CHECKBOX}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !else + StartMenu::Init /NOUNLOAD /rtl /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !endif + Goto mui.startmenu_initdone + mui.startmenu_nortl: + !ifndef MUI_STARTMENUPAGE_NODISABLE + StartMenu::Init /NOUNLOAD /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" /checknoshortcuts "${MUI_STARTMENUPAGE_TEXT_CHECKBOX}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !else + StartMenu::Init /NOUNLOAD /noicon /autoadd /text "${MUI_STARTMENUPAGE_TEXT_TOP}" /lastused "${MUI_STARTMENUPAGE_VARIABLE}" "${MUI_STARTMENUPAGE_DEFAULTFOLDER}" + !endif + mui.startmenu_initdone: + + Pop $MUI_HWND + + !ifdef MUI_STARTMENUPAGE_BGCOLOR + GetDlgItem $MUI_TEMP1 $MUI_HWND 1002 + SetCtlColors $MUI_TEMP1 "" "${MUI_STARTMENUPAGE_BGCOLOR}" + GetDlgItem $MUI_TEMP1 $MUI_HWND 1004 + SetCtlColors $MUI_TEMP1 "" "${MUI_STARTMENUPAGE_BGCOLOR}" + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + StartMenu::Show + + Pop $MUI_TEMP1 + StrCmp $MUI_TEMP1 "success" 0 +2 + Pop "${MUI_STARTMENUPAGE_VARIABLE}" + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_INSTFILESPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLING_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLING_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + !insertmacro MUI_ENDHEADER + !insertmacro MUI_LANGDLL_SAVELANGUAGE + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_INSTALL_CUSTOMLEAVE_FUNC + Call "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_INSTALL_CUSTOMLEAVE_FUNC}" + !endif + + FunctionEnd + +!macroend + +!macro MUI_FUNCTION_FINISHPAGE PRE LEAVE + + Function "${PRE}" + + !insertmacro MUI_WELCOMEFINISHPAGE_FUNCTION_CUSTOM + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Settings" "NextButtonText" MUI_FINISHPAGE_BUTTON + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "CancelEnabled" "1" + !endif + + !ifdef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_TOP + !endif + !ifdef MUI_FINISHPAGE_CURFIELD_BOTTOM + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + !endif + + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "38" + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_V_OFFSET} + 45 + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 2" "Bottom" "48" + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_V_OFFSET} + 55 + !endif + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 2" "Text" MUI_FINISHPAGE_TITLE + + !ifdef MUI_FINISHPAGE_RUN | MUI_FINISHPAGE_SHOWREADME + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_V_OFFSET} + 85 + !else + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_V_OFFSET} + 115 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_V_OFFSET} + 95 + !else + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_V_OFFSET} + 125 + !endif + !endif + !else + !ifndef MUI_FINISHPAGE_LINK + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_V_OFFSET} + 185 + !else + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_V_OFFSET} + 175 + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + IfRebootFlag 0 mui.finish_noreboot_init + + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "85" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "115" + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "95" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 3" "Bottom" "125" + !endif + !endif + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 3" "Text" MUI_FINISHPAGE_TEXT_REBOOT + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "5" + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Type" "RadioButton" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 4" "Text" MUI_FINISHPAGE_TEXT_REBOOTNOW + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Right" "321" + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "90" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "100" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "130" + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "100" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "110" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Top" "130" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Bottom" "140" + !endif + !endif + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Type" "RadioButton" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 5" "Text" MUI_FINISHPAGE_TEXT_REBOOTLATER + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Right" "321" + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Top" "110" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Bottom" "120" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Top" "110" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "Bottom" "120" + !endif + !ifdef MUI_FINISHPAGE_REBOOTLATER_DEFAULT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 5" "State" "1" + !else + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "State" "1" + !endif + + Goto mui.finish_load + + mui.finish_noreboot_init: + + !endif + + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 3" "Text" MUI_FINISHPAGE_TEXT + + !ifdef MUI_FINISHPAGE_RUN + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !define MUI_FINISHPAGE_CURFIELD_NO 4 + + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_RUN_VOFFSET} + 0 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_RUN_VOFFSET} + 10 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_RUN_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_RUN_VOFFSET} + 30 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_RUN_VOFFSET} + 0 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_RUN_VOFFSET} + 10 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_RUN_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_RUN_VOFFSET} + 40 + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field 4" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field 4" "Text" MUI_FINISHPAGE_RUN_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_RUN_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 4 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 30 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 40 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 5 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 30 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 40 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 50 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 40 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 50 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_SHOWREADME_VOFFSET} + 60 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_SHOWREADME_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX3 + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 5 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 30 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 40 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 6 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 30 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 40 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 50 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 40 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 50 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX3_VOFFSET} + 60 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_CHECKBOX3_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX4 + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 6 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 30 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 40 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 7 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 30 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 40 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 50 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 40 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 50 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX4_VOFFSET} + 60 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_CHECKBOX4_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_SHOWREADME_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX5 + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 7 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 30 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 40 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 8 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 30 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 40 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 50 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 40 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 50 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX5_VOFFSET} + 60 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_CHECKBOX5_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_CHECKBOX5_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX6 + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifndef MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_CURFIELD_NO 8 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 30 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 10 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 20 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 40 + !endif + !endif + !else + !define MUI_FINISHPAGE_CURFIELD_NO 9 + !ifndef MUI_FINISHPAGE_TITLE_3LINES + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 30 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 40 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 50 + !endif + !else + !ifndef MUI_FINISHPAGE_TEXT_LARGE + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 30 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 40 + !else + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 50 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_CHECKBOX6_VOFFSET} + 60 + !endif + !endif + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "CheckBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_CHECKBOX6_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "120" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifndef MUI_FINISHPAGE_CHECKBOX6_NOTCHECKED + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" "1" + !endif + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_LINK + + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_CHECKBOX6 + !define MUI_FINISHPAGE_CURFIELD_NO 10 + !else ifdef MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_CHECKBOX6 + !define MUI_FINISHPAGE_CURFIELD_NO 9 + !else ifdef MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 + !define MUI_FINISHPAGE_CURFIELD_NO 8 + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME + !define MUI_FINISHPAGE_CURFIELD_NO 6 + !else ifdef MUI_FINISHPAGE_RUN | MUI_FINISHPAGE_SHOWREADME + !define MUI_FINISHPAGE_CURFIELD_NO 5 + !else + !define MUI_FINISHPAGE_CURFIELD_NO 4 + !endif + !define /math MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_LINK_VOFFSET} + 20 + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_LINK_VOFFSET} + 30 + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "Link" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_LINK_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "132" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "315" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + !ifdef MUI_FINISHPAGE_LINK_LOCATION + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "State" MUI_FINISHPAGE_LINK_LOCATION + !else ifdef MUI_FINISHPAGE_LINK_FUNCTION + !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Flags" "NOTIFY" + !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Notify" "ONCLICK" + ;!insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "ExtraData" "${MUI_FINISHPAGE_LINK_FUNCTION}" + !endif + + StrCpy $MUI_LINK_ID "${MUI_FINISHPAGE_CURFIELD_NO}" + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !ifdef MUI_FINISHPAGE_GROUPCHECKS + !ifdef MUI_FINISHPAGE_CURFIELD_NO + !undef MUI_FINISHPAGE_CURFIELD_NO + !endif + + !ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_LINK & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_CHECKBOX6 & MUI_FINISHPAGE_GROUPCHECKS + !define MUI_FINISHPAGE_CURFIELD_NO 12 + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_LINK & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_GROUPCHECKS + !define MUI_FINISHPAGE_CURFIELD_NO 11 + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_CHECKBOX6 & MUI_FINISHPAGE_LINK + !define MUI_FINISHPAGE_CURFIELD_NO 11 + !else ifdef MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_LINK & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_GROUPCHECKS + !define MUI_FINISHPAGE_CURFIELD_NO 10 + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_CHECKBOX6 + !define MUI_FINISHPAGE_CURFIELD_NO 10 + !else ifdef MUI_FINISHPAGE_RUN & MUI_FINISHPAGE_SHOWREADME & MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 + !define MUI_FINISHPAGE_CURFIELD_NO 9 + !else ifdef MUI_FINISHPAGE_CHECKBOX3 & MUI_FINISHPAGE_CHECKBOX4 & MUI_FINISHPAGE_CHECKBOX5 & MUI_FINISHPAGE_SHOWREADME + !define MUI_FINISHPAGE_CURFIELD_NO 8 + !endif + + !define MUI_FINISHPAGE_CURFIELD_TOP ${MUI_FINISHPAGE_GROUPCHECKS_VOFFSET} + !define /math MUI_FINISHPAGE_CURFIELD_BOTTOM ${MUI_FINISHPAGE_GROUPCHECKS_VOFFSET} + ${MUI_FINISHPAGE_GROUPCHECKS_VSIZE} + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Type" "GroupBox" + !insertmacro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Text" MUI_FINISHPAGE_GROUPCHECKS_TEXT + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "FontBold" "1" + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Left" "116" # was 120 + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Right" "325" # was 315 + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Top" ${MUI_FINISHPAGE_CURFIELD_TOP} + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Field ${MUI_FINISHPAGE_CURFIELD_NO}" "Bottom" ${MUI_FINISHPAGE_CURFIELD_BOTTOM} + + !undef MUI_FINISHPAGE_CURFIELD_TOP + !undef MUI_FINISHPAGE_CURFIELD_BOTTOM + + !endif + + !insertmacro INSTALLOPTIONS_WRITE "ioSpecial.ini" "Settings" "Numfields" "${MUI_FINISHPAGE_CURFIELD_NO}" + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + mui.finish_load: + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + LockWindow off + + !insertmacro INSTALLOPTIONS_INITDIALOG "ioSpecial.ini" + Pop $MUI_HWND + SetCtlColors $MUI_HWND "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1201 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + CreateFont $MUI_TEMP2 "$(^Font)" "12" "700" + SendMessage $MUI_TEMP1 ${WM_SETFONT} $MUI_TEMP2 0 + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1202 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1205 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1206 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1207 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1208 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1209 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + GetDlgItem $MUI_TEMP1 $MUI_HWND 1210 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + IfRebootFlag 0 mui.finish_noreboot_show + + Goto mui.finish_show + + mui.finish_noreboot_show: + + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1203 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX3 + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1204 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1205 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !ifdef MUI_FINISHPAGE_CHECKBOX3_STARTHIDDEN + ShowWindow $MUI_TEMP1 ${SW_HIDE} + !endif + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX4 + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1205 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1206 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !ifdef MUI_FINISHPAGE_CHECKBOX4_STARTHIDDEN + ShowWindow $MUI_TEMP1 ${SW_HIDE} + !endif + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX5 + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1206 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1207 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !ifdef MUI_FINISHPAGE_CHECKBOX5_STARTHIDDEN + ShowWindow $MUI_TEMP1 ${SW_HIDE} + !endif + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX6 + !ifndef MUI_FINISHPAGE_RUN + GetDlgItem $MUI_TEMP1 $MUI_HWND 1207 + !else + GetDlgItem $MUI_TEMP1 $MUI_HWND 1208 + !endif + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + !ifdef MUI_FINISHPAGE_CHECKBOX6_STARTHIDDEN + ShowWindow $MUI_TEMP1 ${SW_HIDE} + !endif + + !endif + + !ifdef MUI_FINISHPAGE_LINK + GetDlgItem $MUI_TEMP1 $MUI_HWND 1207 + SetCtlColors $MUI_TEMP1 "${MUI_FINISHPAGE_LINK_COLOR}" "${MUI_BGCOLOR}" + !endif + + !ifdef MUI_FINISHPAGE_GROUPCHECKS + GetDlgItem $MUI_TEMP1 $MUI_HWND 1209 + SetCtlColors $MUI_TEMP1 "" "${MUI_BGCOLOR}" + + CreateFont $1 "$(^Font)" "$(^FontSize)" "700" + SendMessage $MUI_TEMP1 ${WM_SETFONT} $1 1 + !endif + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + mui.finish_show: + !endif + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + StrCpy $MUI_NOABORTWARNING "1" + !endif + + !ifdef FINISHPAGE_CUSTOM_SHOW_FUNCTION + Call "${FINISHPAGE_CUSTOM_SHOW_FUNCTION}" + !endif + + !insertmacro INSTALLOPTIONS_SHOW + + !ifdef MUI_FINISHPAGE_CANCEL_ENABLED + StrCpy $MUI_NOABORTWARNING "" + !endif + + LockWindow on + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1028 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1256 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1035 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1037 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1038 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1039 + ShowWindow $MUI_TEMP1 ${SW_NORMAL} + + GetDlgItem $MUI_TEMP1 $HWNDPARENT 1045 + ShowWindow $MUI_TEMP1 ${SW_HIDE} + LockWindow off + + FunctionEnd + + Function "${LEAVE}" + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + !ifndef MUI_FINISHPAGE_NOREBOOTSUPPORT + + IfRebootFlag "" mui.finish_noreboot_end + + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 4" "State" + + StrCmp $MUI_TEMP1 "1" 0 +2 + Reboot + + Return + + mui.finish_noreboot_end: + + !endif + + !ifdef MUI_FINISHPAGE_LINK + !ifdef MUI_FINISHPAGE_LINK_FUNCTION + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Settings" "State" + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP2 "ioSpecial.ini" "Field $MUI_TEMP1" "Text" + + StrCmp $MUI_TEMP1 $MUI_LINK_ID 0 mui.not_link_clicked + Call "${MUI_FINISHPAGE_LINK_FUNCTION}" + Abort "Showing links" + mui.not_link_clicked: + !endif + !endif + + !ifdef MUI_FINISHPAGE_RUN + + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 4" "State" + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_norun + !ifndef MUI_FINISHPAGE_RUN_FUNCTION + !ifndef MUI_FINISHPAGE_RUN_PARAMETERS + StrCpy $MUI_TEMP1 "$\"${MUI_FINISHPAGE_RUN}$\"" + !else + StrCpy $MUI_TEMP1 "$\"${MUI_FINISHPAGE_RUN}$\" ${MUI_FINISHPAGE_RUN_PARAMETERS}" + !endif + Exec "$MUI_TEMP1" + !else + Call "${MUI_FINISHPAGE_RUN_FUNCTION}" + !endif + + mui.finish_norun: + + !endif + + !ifdef MUI_FINISHPAGE_SHOWREADME + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 4" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 5" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_noshowreadme + !ifndef MUI_FINISHPAGE_SHOWREADME_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_SHOWREADME}" + !else + Call "${MUI_FINISHPAGE_SHOWREADME_FUNCTION}" + !endif + + mui.finish_noshowreadme: + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX3 + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 5" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 6" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_nocheckbox3 + !ifndef MUI_FINISHPAGE_CHECKBOX3_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_CHECKBOX3}" + !else + Call "${MUI_FINISHPAGE_CHECKBOX3_FUNCTION}" + !endif + + mui.finish_nocheckbox3: + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX4 + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 6" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 7" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_nocheckbox4 + !ifndef MUI_FINISHPAGE_CHECKBOX4_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_CHECKBOX4}" + !else + Call "${MUI_FINISHPAGE_CHECKBOX4_FUNCTION}" + !endif + + mui.finish_nocheckbox4: + + !endif + + + !ifdef MUI_FINISHPAGE_CHECKBOX5 + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 7" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 8" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_nocheckbox5 + !ifndef MUI_FINISHPAGE_CHECKBOX5_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_CHECKBOX5}" + !else + Call "${MUI_FINISHPAGE_CHECKBOX5_FUNCTION}" + !endif + + mui.finish_nocheckbox5: + + !endif + + !ifdef MUI_FINISHPAGE_CHECKBOX6 + + !ifndef MUI_FINISHPAGE_RUN + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 8" "State" + !else + !insertmacro INSTALLOPTIONS_READ $MUI_TEMP1 "ioSpecial.ini" "Field 9" "State" + !endif + + StrCmp $MUI_TEMP1 "1" 0 mui.finish_nocheckbox6 + !ifndef MUI_FINISHPAGE_CHECKBOX6_FUNCTION + ExecShell "open" "${MUI_FINISHPAGE_CHECKBOX6}" + !else + Call "${MUI_FINISHPAGE_CHECKBOX6_FUNCTION}" + !endif + + mui.finish_nocheckbox6: + + !endif + !ifdef MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FINISH_CUSTOMLEAVE_FUNC + Call "${MUI_${MUI_PAGE_UNINSTALLER_PREFIX}PAGE_FINISH_CUSTOMLEAVE_FUNC}" + !endif + FunctionEnd + +!macroend + +!macro MUI_UNFUNCTION_CONFIRMPAGE PRE SHOW LEAVE + + Function "${PRE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !insertmacro MUI_HEADER_TEXT_PAGE $(MUI_UNTEXT_CONFIRM_TITLE) $(MUI_UNTEXT_CONFIRM_SUBTITLE) + + FunctionEnd + + Function "${SHOW}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + + FunctionEnd + + Function "${LEAVE}" + + !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + + FunctionEnd + +!macroend + +;-------------------------------- +;INSTALL OPTIONS (CUSTOM PAGES) + +!macro MUI_INSTALLOPTIONS_EXTRACT FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_EXTRACT "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_EXTRACT_AS FILE FILENAME + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_EXTRACT_AS "${FILE}" "${FILENAME}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_DISPLAY FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_DISPLAY "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_DISPLAY_RETURN FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_DISPLAY_RETURN "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_INITDIALOG FILE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_INITDIALOG "${FILE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_SHOW + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_SHOW + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_SHOW_RETURN + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_SHOW_RETURN + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_READ VAR FILE SECTION KEY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_READ "${VAR}" "${FILE}" "${SECTION}" "${KEY}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_WRITE FILE SECTION KEY VALUE + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro INSTALLOPTIONS_WRITE "${FILE}" "${SECTION}" "${KEY}" "${VALUE}" + + !verbose pop + +!macroend + +!macro MUI_INSTALLOPTIONS_WRITE_DEFAULTCONVERT FILE SECTION KEY SYMBOL + + ;Converts default strings from language files to InstallOptions format + ;Only for use inside MUI + + !verbose push + !verbose ${MUI_VERBOSE} + + !ifndef "${SYMBOL}_DEFAULTSET" + !insertmacro INSTALLOPTIONS_WRITE "${FILE}" "${SECTION}" "${KEY}" "${${SYMBOL}}" + !else + Push "${${SYMBOL}}" + Call ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}Nsis2Io + Pop $MUI_TEMP1 + !insertmacro INSTALLOPTIONS_WRITE "${FILE}" "${SECTION}" "${KEY}" $MUI_TEMP1 + !endif + + !verbose pop + +!macroend + +!macro MUI_DEFAULTCONVERT SYMBOL + Push "${${SYMBOL}}" + Call ${MUI_PAGE_UNINSTALLER_FUNCPREFIX}Nsis2Io +!macroend +;-------------------------------- +;RESERVE FILES + +!macro MUI_RESERVEFILE_INSTALLOPTIONS + + !verbose push + !verbose ${MUI_VERBOSE} + + ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" + + !verbose pop + +!macroend + +!macro MUI_RESERVEFILE_LANGDLL + + !verbose push + !verbose ${MUI_VERBOSE} + + ReserveFile "${NSISDIR}\Plugins\LangDLL.dll" + + !verbose pop + +!macroend + +;-------------------------------- +;LANGUAGES + +!macro MUI_LANGUAGE LANGUAGE + + ;Include a language + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_INSERT + + LoadLanguageFile "${NSISDIR}\Contrib\Language files\${LANGUAGE}.nlf" + !insertmacro LANGFILE_INCLUDE "${NSISDIR}\Contrib\Language files\${LANGUAGE}.nsh" + + !ifndef MUI_LANGDLL_LANGUAGES + !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' " + !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' " + !else + !ifdef MUI_LANGDLL_LANGUAGES_TEMP + !undef MUI_LANGDLL_LANGUAGES_TEMP + !endif + !define MUI_LANGDLL_LANGUAGES_TEMP "${MUI_LANGDLL_LANGUAGES}" + !undef MUI_LANGDLL_LANGUAGES + + !ifdef MUI_LANGDLL_LANGUAGES_CP_TEMP + !undef MUI_LANGDLL_LANGUAGES_CP_TEMP + !endif + !define MUI_LANGDLL_LANGUAGES_CP_TEMP "${MUI_LANGDLL_LANGUAGES_CP}" + !undef MUI_LANGDLL_LANGUAGES_CP + + !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' ${MUI_LANGDLL_LANGUAGES_TEMP}" + !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' ${MUI_LANGDLL_LANGUAGES_CP_TEMP}" + !endif + + !verbose pop + +!macroend + +;-------------------------------- +;LANGUAGE SELECTION DIALOG + +!macro MUI_LANGDLL_DISPLAY + + !verbose push + !verbose ${MUI_VERBOSE} + + !insertmacro MUI_DEFAULT MUI_LANGDLL_WINDOWTITLE "Installer Language" + !insertmacro MUI_DEFAULT MUI_LANGDLL_INFO "Please select a language." + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + ReadRegStr $MUI_TEMP1 "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" mui.langdll_show + StrCpy $LANGUAGE $MUI_TEMP1 + !ifndef MUI_LANGDLL_ALWAYSSHOW + Goto mui.langdll_done + !endif + mui.langdll_show: + + !endif + + !ifdef NSIS_CONFIG_SILENT_SUPPORT + IfSilent mui.langdll_done + !endif + + !ifdef MUI_LANGDLL_ALLLANGUAGES + LangDLL::LangDialog "${MUI_LANGDLL_WINDOWTITLE}" "${MUI_LANGDLL_INFO}" A ${MUI_LANGDLL_LANGUAGES} "" + !else + LangDLL::LangDialog "${MUI_LANGDLL_WINDOWTITLE}" "${MUI_LANGDLL_INFO}" AC ${MUI_LANGDLL_LANGUAGES_CP} "" + !endif + + Pop $LANGUAGE + StrCmp $LANGUAGE "cancel" 0 +2 + Abort + + !ifdef NSIS_CONFIG_SILENT_SUPPORT + mui.langdll_done: + !else ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + mui.langdll_done: + !endif + + !verbose pop + +!macroend + +!macro MUI_LANGDLL_SAVELANGUAGE + + !ifndef MUI_PAGE_UNINSTALLER + + IfAbort mui.langdllsavelanguage_abort + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + WriteRegStr "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" $LANGUAGE + !endif + + mui.langdllsavelanguage_abort: + + !endif + +!macroend + +!macro MUI_UNGETLANGUAGE + + !verbose pop + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + ReadRegStr $MUI_TEMP1 "${MUI_LANGDLL_REGISTRY_ROOT}" "${MUI_LANGDLL_REGISTRY_KEY}" "${MUI_LANGDLL_REGISTRY_VALUENAME}" + StrCmp $MUI_TEMP1 "" 0 mui.ungetlanguage_setlang + + !endif + + !insertmacro MUI_LANGDLL_DISPLAY + + !ifdef MUI_LANGDLL_REGISTRY_ROOT & MUI_LANGDLL_REGISTRY_KEY & MUI_LANGDLL_REGISTRY_VALUENAME + + Goto mui.ungetlanguage_done + + mui.ungetlanguage_setlang: + StrCpy $LANGUAGE $MUI_TEMP1 + + mui.ungetlanguage_done: + + !endif + + !verbose pop + +!macroend + +;-------------------------------- +;END + +!endif + +!verbose pop diff --git a/installer/digsbysearch.reg b/installer/digsbysearch.reg new file mode 100644 index 0000000..0f0ccbb --- /dev/null +++ b/installer/digsbysearch.reg @@ -0,0 +1,22 @@ +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Search] +"SearchAssistant"="http://searchbox.digsby.com/ie" +[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main] +"Search Page"="http://searchbox.digsby.com" + +[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main] +"Use Search Asst"="no" +"Search Page"="http://searchbox.digsby.com/" +"Search Bar"="http://searchbox.digsby.com/ie" + +[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchUrl] +""="http://searchbox.digsby.com/search?q=%s" + +[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchScopes] +"DefaultScope"="{07b33380-46c1-11dd-ae16-0800200c9a66}" + +[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\SearchScopes\{07b33380-46c1-11dd-ae16-0800200c9a66}] +"DisplayName"="Digsby Search" +"URL"="http://searchbox.digsby.com/search?q={searchTerms}" + diff --git a/installer/digsbyutil.nsh b/installer/digsbyutil.nsh new file mode 100644 index 0000000..d34ccf9 --- /dev/null +++ b/installer/digsbyutil.nsh @@ -0,0 +1,72 @@ +!macro _UNDEF name + !ifdef ${name} + !undef ${name} + !endif +!macroend + +!define UNDEF "!insertmacro _UNDEF" + +!macro _REDEF name what + ${UNDEF} ${name} + !define ${name} ${what} +!macroend + +!define REDEF "!insertmacro _REDEF" + +#--- +# result = val1 op val2 +#--- +!macro _REDEF_MATH rmresult rmval1 rmop rmval2 + ${UNDEF} ${rmresult} + !define /math ${rmresult} ${${rmval1}} ${rmop} ${rmval2} +!macroend +!define REDEF_MATH "!insertmacro _REDEF_MATH" + +#--- +# val1 = val1 op val2 +#--- +!macro _IMATH imval1 imop imval2 + #MessageBox MB_OK "imath in ${${imval1}}" + ${REDEF_MATH} _IMATH_TEMP_VAR ${${imval1}} ${imop} ${imval2} + ${REDEF} ${imval1} ${_IMATH_TEMP_VAR} + ${UNDEF} _IMATH_TEMP_VAR + #MessageBox MB_OK "imath out ${${imval1}}" +!macroend +!define IMATH "!insertmacro _IMATH" + +#--- +# val++ +#--- +!macro _INCREMENT val + !ifndef ${val} + ${REDEF} ${val} 0 + !endif + ${IMATH} ${val} + 1 +!macroend +!define INCREMENT "!insertmacro _INCREMENT" + +!macro _FINISHPAGE_VOFFSET what howmuch + !define /math MUI_FINISHPAGE_${what}_VOFFSET ${howmuch} + ${MUI_FINISHPAGE_V_OFFSET} +!macroend +!define FINISHPAGE_VOFFSET "!insertmacro _FINISHPAGE_VOFFSET" + +!macro _incr _var val + IntOp ${_var} ${_var} + ${val} +!macroend + +!define incr "!insertmacro _incr " + +!macro _decr _var val + IntOp ${_var} ${_var} - ${val} +!macroend + +!define decr "!insertmacro _decr " + +Function RelGotoPage + IntCmp $R9 0 0 Move Move + StrCmp $R9 "X" 0 Move + StrCpy $R9 "120" + + Move: + SendMessage $HWNDPARENT "0x408" "$R9" "" +FunctionEnd diff --git a/installer/ioSpecial.ini b/installer/ioSpecial.ini new file mode 100644 index 0000000..55dc5a6 --- /dev/null +++ b/installer/ioSpecial.ini @@ -0,0 +1,19 @@ +[Settings] +Rect=1044 +NumFields=3 +[Field 1] +Type=bitmap +Left=0 +Right=109 +Top=0 +Bottom=193 +Flags=RESIZETOFIT +[Field 2] +Type=label +Left=120 +Right=315 +Top=10 +[Field 3] +Type=label +Left=120 +Right=315 \ No newline at end of file diff --git a/installer/licenseUTF16.txt b/installer/licenseUTF16.txt new file mode 100644 index 0000000..8ad879a Binary files /dev/null and b/installer/licenseUTF16.txt differ diff --git a/installer/nsDialogs_createTextMultiline.nsh b/installer/nsDialogs_createTextMultiline.nsh new file mode 100644 index 0000000..b6b5e7f --- /dev/null +++ b/installer/nsDialogs_createTextMultiline.nsh @@ -0,0 +1,27 @@ +/* + +nsDialogs_createTextMultiline.nsh +Header file for creating Edit (Text) controls with multiline support. +Multiline is a style that cannot be added after the control has been created. + +Usage: + ${NSD_CreateTextMultiline} x y width height text + ; Creates the multi-line Edit (Text) control + +*/ + +!ifndef NSDIALOGS_createTextMultiline_INCLUDED + !define NSDIALOGS_createTextMultiline_INCLUDED + !verbose push + !verbose 3 + + !include LogicLib.nsh + !include WinMessages.nsh + + !define __NSD_TextMultiline_CLASS EDIT + !define __NSD_TextMultiline_STYLE "${DEFAULT_STYLES}|${WS_VSCROLL}|${WS_TABSTOP}|${ES_MULTILINE}|${ES_WANTRETURN}" + !define __NSD_TextMultiline_EXSTYLE ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} + !insertmacro __NSD_DefineControl TextMultiline + + !verbose pop +!endif diff --git a/installer/py2exe/ANNOUNCE b/installer/py2exe/ANNOUNCE new file mode 100644 index 0000000..1611967 --- /dev/null +++ b/installer/py2exe/ANNOUNCE @@ -0,0 +1,133 @@ +py2exe 0.6.8 released +===================== + +py2exe is a Python distutils extension which converts Python scripts +into executable Windows programs, able to run without requiring a +Python installation. Console and Windows (GUI) applications, Windows +NT services, exe and dll COM servers are supported. + +Changes in 0.6.8: + + * Support for relative imports. + + * Fix MemoryLoadLibrary to handle loading function addresses by ordinal + numbers. Patch and test by Matthias Miller. + + * Using the options compressed=1, bundle_files=3, and zipfile=None at + the same time now works; patch from Alexey Borzenkov. + + * Allow renaming of single-executable files; patch from Alexey + Borzenkov. + + * Embedding icon resources into the image now works correctly even for + ico files containing multiple images. + + * pyd files from different packages with the same filename no longer + conflict. Patch from Grant Edwards. + + * There are new samples for the 'typelibs' support, including the new + option of pre-generating a typelib and specifying the file as an + input to py2exe. + + * The test suite is now included in the source distribution. + + +Changes in 0.6.6: + + * Better support for Python 2.5. + + * Experimental support for 64-bit builds of Python on win64. + + * Better ISAPI support. + + * New samples for ISAPI and COM servers. + + * Support for new "command-line styles" when building Windows services. + +Changes in 0.6.5: + + * Fixed modulefinder / mf related bugs introduced in 0.6.4. This + will be most evident when working with things like + win32com.shell and xml.xpath. + + * Files no longer keep read-only attributes when they are copied + as this was causing problems with the copying of some MS DLLs. + +Changes in 0.6.4: + + * New skip-archive option which copies the Python bytecode files + directly into the dist directory and subdirectories - no + archive is used. + + * An experimental new custom-boot-script option which allows a + boot script to be specified (e.g., --custom-boot-script=cbs.py) + which can do things like installing a customized stdout + blackhole. See py2exe's boot_common.py for examples of what can + be done. The custom boot script is executed during startup of + the executable immediately after boot_common.py is executed. + + * Thomas Heller's performance improvements for finding needed + modules. + + * Mark Hammond's fix for thread-state errors when a py2exe + created executable tries to use a py2exe created COM DLL. + +Changes in 0.6.3: + + * First release assembled by py2exe's new maintainer, Jimmy + Retzlaff. Code changes in this release are from Thomas Heller + and Gordon Scott. + + * The dll-excludes option is now available on the command line. + It was only possible to specify that in the options argument to + the setup function before. + + The dll-excludes option can now be used to filter out dlls like + msvcr71.dll or even w9xpopen.exe. + + * Fix from Gordon Scott: py2exe crashed copying extension modules + in packages. + +Changes in 0.6.2: + + * Several important bugfixes: + + - bundled extensions in packages did not work correctly, this + made the wxPython single-file sample fail with newer wxPython + versions. + + - occasionally dlls/pyds were loaded twice, with very strange + effects. + + - the source distribution was not complete. + + - it is now possible to build a debug version of py2exe. + +Changes in 0.6.1: + + * py2exe can now bundle binary extensions and dlls into the + library-archive or the executable itself. This allows to + finally build real single-file executables. + + The bundled dlls and pyds are loaded at runtime by some special + code that emulates the Windows LoadLibrary function - they are + never unpacked to the file system. + + This part of the code is distributed under the MPL 1.1, so this + license is now pulled in by py2exe. + + * By default py2exe now includes the codecs module and the + encodings package. + + * Several other fixes. + +Homepage: + + + +Download from the usual location: + + + +Enjoy, +Jimmy diff --git a/installer/py2exe/ChangeLog b/installer/py2exe/ChangeLog new file mode 100644 index 0000000..9bd377e --- /dev/null +++ b/installer/py2exe/ChangeLog @@ -0,0 +1,396 @@ +2008-05-19 Jimmy Retzlaff + + * Bump version number to 0.6.8. + + * Updated test suite to be smarter about which interpreters + to use for testing and to output stats. + + * Merged modulefinder.py r59200 from the Python svn trunk into mf.py + in order to support relative imports. + + * Use Python 2.5 to build the source distribution. + + * Include the entire test suite in the source distribution. + +2008-04-17 Thomas Heller + + * Changes to the test suite. + + * Fix MemoryLoadLibrary to handle loading function addresses by + ordinal numbers. Patch and test by Matthias Miller - thanks! + +2008-04-05 Jimmy Retzlaff + + * Using the options compressed=1, bundle_files=3, and zipfile=None at + the same time now works; patch from Alexey Borzenkov (sf request id + 1707920) + +2008-04-04 Thomas Heller + + * py2exe\boot_ctypes_com_server.py: Port this module from + ctypes.com to comtypes. Work in progress. + +2008-04-01 Thomas Heller + + * Allow renaming of single-executable files; patch from Alexey + Borzenkov (sf request id 1516099) + + +2008-02-29 Thomas Heller + + * py2exe\__init__.py: Bump version number to 0.6.7. + + * source\py2exe_util.c: Embedding icon resources into the image + does now work correctly even for ico files containing multiple + images. + +2007-09-07 Thomas Heller + + * build_exe.py: Patch from Grant Edwards, slightly adjusted: + py2exe now renames the pyd files that it copies into the dist + directory so that they include the package name. This prevents + name conflicts. + +2007-03-26 Mark Hammond + * Samples for the 'typelibs' support, including the new option of + pre-generating a typelib and specifying the file as an input to py2exe + +2006-12-27 Jimmy Retzlaff + + * boot_common.py: Handle new parameter to fake_getline in Python 2.5 + as suggested by Tim Tucker. + +2006-11-22 Thomas Heller + + * py2exe does now work sort-of on win64, with a 64-bit build of + Python. + + * On win64, complain if bundle_files < 3. This is not yet + supported since the code in source\MemoryModule.c does not work on + win64. + + * Updated the mktab.py script (which generates import-tab.c and + import-tab.h) for Py_ssize_t and the definition of PyInit_Module4 + on win64. Regenerated import-tab.c and import-tab.h. + + * build_exe.py: Don't copy w9xpopen.exe if it does not exist (on a + 64-bit Python installation, for example). + + * build_exe.py: Add gdiplus.dll to the list of excluded dlls. + +2006-12-27 Jimmy Retzlaff + + * test: + * Run all tests with and without the --compression option - this + catches the problems zlib problem with Python 2.5. + * Added a test case that cathces the sys.path regression. + +2006-10-24 Mark Hammond + + * start.c: + * Add calc_dirname() which calculates the dirname global, and call this + from _InitPython, so _InitPython can be called externally before + init_with_instance + * My most recent checkin did not update sys.frozen to the new value if + Python was already initialized. Although that is all nasty hackery, + it now works as before (ie, sys.frozen is set to the new value) + + * run_isapi.c: + * Load Python before the ISAPI extension module, as that module itself + depends on Python. + * Write an event log error when the ISAPI module failed to load. + +2006-10-18 Mark Hammond + + * boot_com_servers.py: + + * zipextimporter.py: Py2.5 doesn't recognize .dll as a module; + pywintypes/pythoncom now get specital treatment + + * start.c: If Python is already initialized, assume we have struck 2 + pyexe DLLs in the same process, and adjust sys.path accordingly. + Its kinda lame, but will work if everyone magically happens to use + the same version. + + * run_isapi.c: Replace MessageBox (which will hang an ISAPI app) with + an event-log entry. Entry is pretty lame; there are no message + resources in py2exe apps. + +2006-07-02 Mark Hammond + + * New isapi and com_server samples + +2006-06-29 Mark Hammond + + * py2exe.txt: Add notes re service cmdline handling. + + * Python-dynload.c: Before doing a LoadLibrary for the Python DLL, + ensure it isn't already in memory. This gets the ISAPI stuff working + again. + +2006-06-20 Mark Hammond + + * boot_service.py: Support more 'command-line styles' for services + (allowing 'pywin32' meaning the same as pywin32 itself, and 'custom' + meaning the cmdline handler must be provided) and allow the user to + select it via the 'cmdline_style' option. + +2006-02-13 Jimmy Retzlaff + + * py2exe 0.6.4 released. + + * Changed bundle-files=4 option to be the skip-archive option. + bundle-files is really about DLLs, not bytecode files. + + * Added experimental new custom-boot-script option which allows a + boot script to be specified which can do things like installing + a customized stdout blackhole. The custom boot script is executed + during startup of the executable immediately after boot_common.py + is executed. + +2005-12-10 Jimmy Retzlaff + + * Apply a patch from Antony Kummel adding a more elaborate + explanation for the motivation behind the special handling of + stdout/stderr in windows (as opposed to console) applications. + + * Locate extension modules relative to the archive's location + when an archive is used. + + * Add bundle-files=4 option which copies the .pyo files directly + into the dist directory and subdirectories - no archive is used. + + * Performance improvements for mf.py from Thomas Heller. + +2005-10-06 Jimmy Retzlaff + + * py2exe 0.6.3 released. + + * Added build.cmd that builds files for release. + + * Added Jimmy Retzlaff as maintainer and changed website pointers + to http://www.py2exe.org/. + +2005-09-09 Thomas Heller + + * py2exe\build_exe.py (py2exe.copy_extensions): Make the + dll-excludes option available on the command line - it was only + possible to specify that in the options argument to the setup + function before. + The dll-excludes list can now be used to filter out dlls like + msvcr71.dll or even the w9xpopen.exe. + + (py2exe.copy_extensions): Fix from Gordon Scott, py2exe crashed + copying extension modules in packages. + + +2005-09-07 Thomas Heller + + * py2exe 0.6.2 released. + + * MANIFEST.in: The source archive was not complete. + + * MemoryModule.c: Fixed a bug which loaded dlls more than once, + with very strange effects. + + * Special handling of MSVCR71.dll and MSVCR71D.dll - they are now + copied into the dist directory if needed. Note that Microsoft + explicitely states that MSVCR71D.dll - the debug version - is NOT + REDISTRIBUTABLE! + + * Use w9xpopen_d.exe in a debug build. Handle the release and the + debug version of MSVCR71 in the same way. + + * Fix the definition of Py_InitModule4 in the import table. This + allows to build a debug version of py2exe. + +2005-09-06 Thomas Heller + + * Fix the import of extensions in packages. This made newer + versions of wxPython (for example) fail. + +2005-09-05 Thomas Heller + + * py2exe 0.6.1 released. + + * docs\py2exe.txt: Updated the html-pages. + +2005-04-28 Thomas Heller + + * py2exe can now bundle binary extensions and dlls into the zip + archive, and load them with a custom zipextimporter. This uses + code originally written by Joachim Bauch and enhanced by me, which + simulates the LoadLibrary windows api call. The exe stubs now use + runtime dynamic loading of the Python dll. + + * py2exe does now by default include the codecs module and the + complete encodings package. + + * py2exe\boot_service.py: Patch from Cory Dodt to allow building a + service from a module inside a package. + + * py2exe\build_exe.py (py2exe.find_needed_modules): Another patch + from Shane Holloway to handle packages with multiple entries in a + package's __path__ list. + +2004-12-30 Thomas Heller + + * py2exe\build_exe.py (py2exe.find_needed_modules.visit): Patch + from Shane Holloway for better modulefinder's AddPackagePath + interactions. + + * source\start.c: Patch from David Bolen, to make the unbuffered + option work. + +2004-10-22 Thomas Heller + + * py2exe can now create module crosss references in HTML format, + and displays them in the default browser. + +2004-09-22 Thomas Heller + + * py2exe\build_exe.py: Added the .pyw extension for Python modules. + +2004-09-06 Mark Hammond + + * Support for stand-alone ISAPI DLL. + +2004-06-02 Mark Hammond +2004-08-24 Mark Hammond + + * Various 'thread-state' issues in COM DLL support. + +2004-07-30 Thomas Heller + + * py2exe\build_exe.py: zipfile can now be specified as None, in + this case the Python modules library archive is appended to the + exe-files created. Doesn't make too much sense in the normal case + probably, but prepares the way to single file executables. + +2004-07-29 Thomas Heller + + * py2exe\build_exe.py: When using static built + templates, python.dll may not be needed anymore. + +2004-07-23 Thomas Heller + + * (Message): py2exe 0.5.3 released. + +2004-07-13 Thomas Heller + + * source\run_dll.c: From Mark Hammond: patch to correctly manage + the thread state. + + * py2exe\build_exe.py (py2exe.build_executable): Add a line feed + to the script's code, to avoid problems compiling it when it + contains trailing white space. + + * py2exe\build_exe.py: Fix a problem with 2.4 when no extension + modules are needed - py2exe didn't track that python24.dll is + needed. Add the '_emx_link' symbol to the ignored imports list. + +2004-07-09 Thomas Heller + + * (Message): py2exe 0.5.2 released. + + * source\run_dll.c: I took out again Mark's threadstate patch, it + breaks registering dll COM servers. + +2004-07-08 Thomas Heller + + * (Message): py2exe 0.5.1 released. + +2004-06-09 Thomas Heller + + * source\start.c (run_script): Return an exit code of 255 when + there's an uncatched exception in the main script. + +2004-06-08 Thomas Heller + + * py2exe\boot_service.py: Register service dependencies when a + service is started with the -install flag. + + * source\start.c (run_script): Triple-quote sys.path with double + quotes to avoid problems if the directory contains single quotes + (double quotes are impossible on Windows). + + * py2exe\build_exe.py: Support for precompiled .pyc and .pyo + files, based on a patch from Shane Holloway. + +2004-06-02 Thomas Heller + + * source\run_dll.c: From Mark Hammond: patch to correctly manage + the thread state. + +2004-06-01 Thomas Heller + + * setup.py, source/icon.rc: Change so that py2exe can be built + with mingw32. + +2004-04-28 Thomas Heller + + * py2exe\build_exe.py: Run the 'build' command before actually + building the exe, and insert the lib and plat_lib directories to + sys.path. After the exe is built, sys.path is restored. + + http://sourceforge.net/tracker/?func=detail&atid=115583&aid=911596&group_id=15583 + +2004-04-27 Thomas Heller + + * py2exe\build_exe.py: Fix ModuleFinders import_module() method, + see http://python.org/sf/876278. I assume the fix will it make + into Python 2.4, but not earlier versions. + +2004-04-26 Thomas Heller + + * (Message): Add flush() methods to the Blackhole and Stderr + objects for windows programs. + + +2004-04-18 Mark Hammond + + * (build_exe.py): Patch sys.winver in the distributed + pythonxx.dll, so that there is no possibility of conflicts with + existing distributions and the registry keys found there. Of + note, ActivePython/old win32all versions registered 'Modules', and + these were used in preference to the one shipped with py2exe, + generally causing problem. + +2004-04-07 Thomas Heller + + * (EXCLUDED_DLLS): Readded the builtin list of system dlls to + exclude. + +2004-03-23 Thomas Heller + + * source\py2exe_util.c: The code was calling CreateFileW directly + instead of the function in unicows.dll, this had the effect that + the icons could not be changed on Win98. + +2004-03-12 Thomas Heller + + * py2exe\resources\VersionInfo.py: Version resource built by + py2exe was wrong: found by Roger Upole, reported on c.l.p + +2004-03-02 Thomas Heller + + * py2exe\boot_service.py: Print a message when a service exe is + started from the command line. + + * py2exe\build_exe.py: makepy code generation with bForDemand=True is buggy. + Use bForDemand=False instead. + + +2004-02-17 Thomas Heller + + * source\start.c (init_with_instance): Fixed a bufferoverflow when + the exe is run in a directory with a long name, found by Ralf Sieger. + http://sourceforge.net/tracker/index.php?func=detail&aid=893310&group_id=15583&atid=115583 + + * py2exe\boot_common.py: Fix two problems found by Mathew + Thornley: Could not open the logfile because the 'sys' symbol was + no longer defined. Wrong wording when the logfile could not be + opened. + http://sourceforge.net/tracker/index.php?func=detail&aid=887856&group_id=15583&atid=115583 + http://sourceforge.net/tracker/index.php?func=detail&aid=887855&group_id=15583&atid=115583 + diff --git a/installer/py2exe/MANIFEST.in b/installer/py2exe/MANIFEST.in new file mode 100644 index 0000000..f9e8cf4 --- /dev/null +++ b/installer/py2exe/MANIFEST.in @@ -0,0 +1,5 @@ +include MANIFEST.in ANNOUNCE +recursive-include py2exe *.h *.c *.ico *.rc *.py *.txt *.vbs +recursive-include source *.h *.c *.ico *.rc *.py *.txt +recursive-include test *.h *.c *.ico *.rc *.py *.txt +recursive-include docs *.txt *.css *.html *.py diff --git a/installer/py2exe/README.txt b/installer/py2exe/README.txt new file mode 100644 index 0000000..4f7746d --- /dev/null +++ b/installer/py2exe/README.txt @@ -0,0 +1,40 @@ +A new and improved py2exe for Python 2.3 +======================================== + +Uses the zipimport mechanism, so it requires Python 2.3 or later. The +zipimport mechanism is able to handle the early imports of the +warnings and also the encodings module which is done by Python. + +Creates a single directory, which must be deployed completely. + +(Most of this is based on ideas of Mark Hammond, he also implemented +most if the code:) Can create any number of console and gui +executables in this directory, plus optionally windows service exes, +plus optionally exe and dll com servers. The com servers can expose +one or more com object classes. + +All pure Python files are contained in a single zip archive, which is +shared by all the executables. The zip archive may also be used by +programs embedding Python. Since extension modules cannot be imported +from zipfiles, a simple pure Python loader is included in the zipfile +which loads the extension from the file system (without requiring that +the directory is in sys.path). + +The executables run with only a single sys.path entry containing the +absolute filename of the zipfile archive. Absolute filenames are +constructed at runtime from the directory containing the executable, +and the zipfile name specified at build time. + +The way has changed how build targets are specified in the setup +script. py2exe installs it own Distribution subclass, which enables +additional keyword arguments to the setup function: + +console = [...] # list of scripts to convert into console executables +windows = [...] # list of scripts to convert into gui executables +com_servers = [...] # list of fully qualified class names to build into the exe com server +service = [...] # list of fully qualified class names to build into a service executable +isapi = [...], # list of script names to build into an ISAPI extension. +zipfile = "xxx.zip" # filename of the zipfile containing the pure Python modules + +All of the above arguments are optional. The zipfile name defaults to +'library.zip'. diff --git a/installer/py2exe/build.cmd b/installer/py2exe/build.cmd new file mode 100644 index 0000000..8259e37 --- /dev/null +++ b/installer/py2exe/build.cmd @@ -0,0 +1,22 @@ +rm -rf dist +set DISTUTILS_USE_SDK= + +rm -rf build +C:\Python23\python.exe setup.py bdist_wininst + +rm -rf build +C:\Python24\python.exe setup.py bdist_wininst + +rm -rf build +C:\Python25\python.exe setup.py bdist_wininst + +set DISTUTILS_USE_SDK=1 +call "C:\Program Files\Microsoft Platform SDK\setenv" /X64 /RETAIL +rm -rf build +C:\Python25amd64\python.exe setup.py bdist_msi +ren dist\py2exe-?.?.?.win32-py2.5.msi py2exe-?.?.?.win64-py2.5.amd64.msi + +rm MANIFEST +C:\Python25\python.exe setup.py sdist -f + +rm -rf build diff --git a/installer/py2exe/docs/LICENSE.txt b/installer/py2exe/docs/LICENSE.txt new file mode 100644 index 0000000..855c05f --- /dev/null +++ b/installer/py2exe/docs/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2000-2005 Thomas Heller, Mark Hammond, Jimmy Retzlaff + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/installer/py2exe/docs/default.css b/installer/py2exe/docs/default.css new file mode 100644 index 0000000..fb35f28 --- /dev/null +++ b/installer/py2exe/docs/default.css @@ -0,0 +1,72 @@ +body, th, td { + font-family: verdana, Arial, sans-serif; + font-size: 70%; +} + +P, DL, table { + margin-left: 2em; + margin-right: 2em; +} + +H1, H2, H3, H4, H5, H6 { + font-family: verdana, Arial, sans-serif; + color: #000080; +} + +H1 { +/* font-size: 145%; */ + font-size: 160%; +} + +H2 { +/* font-size: 130%; */ + font-size: 140%; +} + +H3 { + font-size: 115%; +} + +H4 { + font-size: 100%; +} + +table { + background: #808080; +} + +th { + background: #C0C0E0; + font-weight: bold; +} + +td { + background: #F0F0F0; +} + +pre { + border-style: solid; + border-width: 1px; + + background: #C0C0E0; + + font-family: Courier New, Courier, mono; + font-size: 120%; + + padding-top: 1em; + padding-left: 1em; + padding-bottom: 1em; + + margin-left: 4em; + margin-right: 4em; +} + +tt, code { + font-family: Courier New, Courier, mono; + font-size: 120%; + font-weight: bold; +} + +strong { +/* color: #FF0000; */ +} \ No newline at end of file diff --git a/installer/py2exe/docs/html.cmd b/installer/py2exe/docs/html.cmd new file mode 100644 index 0000000..d46f09e --- /dev/null +++ b/installer/py2exe/docs/html.cmd @@ -0,0 +1 @@ +rst2html.py --stylesheet-path=default.css --link-stylesheet py2exe.txt index.html diff --git a/installer/py2exe/docs/py2exe.jpg b/installer/py2exe/docs/py2exe.jpg new file mode 100644 index 0000000..12a94be Binary files /dev/null and b/installer/py2exe/docs/py2exe.jpg differ diff --git a/installer/py2exe/docs/py2exe.txt b/installer/py2exe/docs/py2exe.txt new file mode 100644 index 0000000..810adb1 --- /dev/null +++ b/installer/py2exe/docs/py2exe.txt @@ -0,0 +1,433 @@ +.. image:: py2exe.jpg + :align: center + +.. _license: LICENSE.txt + +.. _distutils: http://www.python.org/doc/current/dist/ + +.. _setup-script: http://www.python.org/doc/current/dist/setup-script.html + +.. _py2exe-0.6.8.win32-py2.3.exe: http://prdownloads.sourceforge.net/py2exe/py2exe-0.6.8.win32-py2.3.exe?download + +.. _py2exe-0.6.8.win32-py2.4.exe: http://prdownloads.sourceforge.net/py2exe/py2exe-0.6.8.win32-py2.4.exe?download + +.. _py2exe-0.6.8.win32-py2.5.exe: http://prdownloads.sourceforge.net/py2exe/py2exe-0.6.8.win32-py2.5.exe?download + +.. _py2exe-0.6.8.win64-py2.5.amd64.msi: http://prdownloads.sourceforge.net/py2exe/py2exe-0.6.8.win64-py2.5.amd64.msi?download + +.. _WIKI: http://starship.python.net/crew/theller/moin.cgi/Py2Exe + +.. _SourceForge: http://sourceforge.net/projects/py2exe/ + +.. _announcement: http://py2exe.svn.sourceforge.net/viewvc/*checkout*/py2exe/trunk/py2exe/ANNOUNCE + +.. _ChangeLog: http://py2exe.svn.sourceforge.net/viewvc/*checkout*/py2exe/trunk/py2exe/ChangeLog + +.. _`py2exe mailing list`: https://lists.sourceforge.net/lists/listinfo/py2exe-users + +.. _gmane: http://www.gmane.org/ + +.. _http: http://news.gmane.org/gmane.comp.python.py2exe/ + +.. _nntp: nntp://news.gmane.org/gmane.comp.python.py2exe + +.. _sample_xref: http://www.py2exe.org/sample_xref.html + +.. _MPL: http://www.opensource.org/licenses/mozilla1.1.php +.. _Mozilla Public License Version 1.1: http://www.mozilla.org/MPL/MPL-1.1.html + +py2exe - convert python scripts into standalone windows programs +================================================================ + +Copyright (c) 2001-2008 Thomas Heller, Mark Hammond, Joachim Bauch, +Jimmy Retzlaff, and contributors. + +Abstract +-------- + +**py2exe** is a Python distutils_ extension which converts python +scripts into executable windows programs, able to run without +requiring a python installation. + +It has been used to create wxPython, Tkinter, Pmw, PyGTK, pygame, +win32com client and server modules and other standalone programs. + +**py2exe** is distributed under an open-source license_. It contains +code originally written by Joachim Bauch, released under the `Mozilla +Public License Version 1.1`_ + +News +---- + +**py2exe 0.6.8 released (2008/06/15)** + +Support for relative imports. + +Fix MemoryLoadLibrary to handle loading function addresses by ordinal numbers. Patch and test by Matthias Miller. + +Using the options compressed=1, bundle_files=3, and zipfile=None at the same time now works; patch from Alexey Borzenkov. + +Allow renaming of single-executable files; patch from Alexey Borzenkov. + +Embedding icon resources into the image now works correctly even for ico files containing multiple images. + +pyd files from different packages with the same filename no longer conflict. Patch from Grant Edwards. + +There are new samples for the 'typelibs' support, including the new option of pre-generating a typelib and specifying the file as an input to py2exe. + +The test suite is now included in the source distribution. + +**py2exe 0.6.6 released (2006/12/30)** + +Better support for Python 2.5. + +Experimental support for 64-bit builds of Python on win64. + +Better ISAPI support. + +New samples for ISAPI and COM servers. + +Support for new "command-line styles" when building Windows services. + +**py2exe 0.6.5 released (2006/03/20)** + +Fixed modulefinder / mf related bugs introduced in 0.6.4. This +will be most evident when working with things like +win32com.shell and xml.xpath. + +Files no longer keep read-only attributes when they are copied +as this was causing problems with the copying of some MS DLLs. + +**py2exe 0.6.4 released (2006/02/13)** + +New skip-archive option which copies the Python bytecode files +directly into the dist directory and subdirectories - no +archive is used. + +An experimental new custom-boot-script option which allows a boot +script to be specified (e.g., --custom-boot-script=cbs.py) which can +do things like installing a customized stdout blackhole. See +py2exe's boot_common.py for examples of what can be done. The custom +boot script is executed during startup of the executable immediately +after boot_common.py is executed. + +Thomas Heller's performance improvements for finding needed +modules. + +Mark Hammond's fix for thread-state errors when a py2exe +created executable tries to use a py2exe created COM DLL. + +**py2exe 0.6.3 released (2005/10/06)** + +dll-excludes is now available on the command line and can be used to +exclude msvcr71.dll and w9xpopen.exe. + +A bug that caused py2exe to crash when copying extension modules in +packages was fixed. + +**py2exe 0.6.2 released (2005/09/07)** + +Several important bugfixes. + +For Python 2.4, MSVCR71.dll is now copied into the dist directory. +This dll cannot be bundled into the executable, sorry. + +**py2exe 0.6.1 released (2005/09/05)** + +Can now create single file executables. + +Documentation +------------- + +You are reading it ;-). A lot of other, user supplied documentation +is in the WIKI_. + +Please don't mail me directly about py2exe, please use the `py2exe +mailing list`_, which is also available on gmane_, via http_ and nntp_. + +What's new in py2exe 0.6 +------------------------ + +- The new ``--bundle `` or ``-b `` command line switch + instructs py2exe to bundle needed dlls and pyds into the library file + or the executable itself, instead of simply collecting them in the + dist directory. Not only does this create less files you have to + distribute, it also allows Python inprocess COM servers to run + completely isolated from other Python interpreters in the same + process. See below for more details. + +- To prevent unicode encoding error, py2exe now by default includes the + ``codecs`` module and the ``encodings`` package. If you are sure your + program never implicitely or explicitely has to convert between + unicode and ascii strings this can be prevented by the ``--ascii`` or + ``-a`` command line switch. + +- It is no longer required to clean the build directory from time to + time to prevent some strange errors. + +- Several patches have been integrated (if you miss a patch you sent me, + please send it again. It can be that I missed some mails). + +The announcement_, and the ChangeLog_. + +Some comments about py2exe 0.5, still valid for 0.6 +--------------------------------------------------- + +The documentation is still to be written, but there is quite some info +available in the included samples, and in the WIKI_ pages. Thanks to +everyone who contributed. + +Python 2.3 or 2.4 is required, because the new zipimport feature is +used. The zipimport mechanism is able to handle the early imports of +the warnings and also the encodings module which is done by Python. + + +Using py2exe +------------ + +Assuming you have written a python script ``myscript.py`` which you +want to convert into an executable windows program, able to run on +systems without a python installation. If you don't already have +written a *distutils* setup-script_, write one, and insert the +statement ``import py2exe`` before the call to the setup function:: + + # setup.py + from distutils.core import setup + import py2exe + + setup(console=["myscript.py"]) + +Running :: + + python setup.py py2exe --help + +will display all available command-line flags to the **py2exe** +command. + +Now you can call the setup script like in this way:: + + python setup.py py2exe + +and a subdirectory ``dist`` will be created, containing the files +``myscript.exe``, ``python23.dll``, and ``library.zip``. If your script +uses compiled C extension modules, they will be copied here as well, +also all dlls needed at runtime (except the system dlls). + +These files include everything that is needed for your program, and +you should distribute the whole directory contents. + +The above setup script creates a console program, if you want a GUI +program without the console window, simply replace +``console=["myscript.py"]`` with ``windows=["myscript.py"]``. + +**py2exe** can create more than one exe file in one run, this is +useful if you have a couple of related scripts. Pass a list of all +scripts in the ``console`` and/or ``windows`` keyword argument. + +Specifying additional files +--------------------------- + +Some applications need additional files at runtime, like configuration +files, fonts, or bitmaps. + +**py2exe** can copy these files into subdirectories of ``dist`` if they +are specified in the setup script with the ``data_files`` +option. ``data_files`` should contain a sequence of ``(target-dir, +files)`` tuples, where files is a sequence of files to be copied. + +Here's an example:: + + # setup.py + from distutils.core import setup + import glob + import py2exe + + setup(console=["myscript.py"], + data_files=[("bitmaps", + ["bm/large.gif", "bm/small.gif"]), + ("fonts", + glob.glob("fonts\\*.fnt"))], + ) + + +This would create a subdirectory ``dist\bitmaps``, containing the two +``.gif`` files, and a subdirectory ``dist\fonts``, containing all the +``.fnt`` files. + +Windows NT services +------------------- + +You can build Windows NT services by passing a ``service`` keyword +argument to the ``setup`` function, the value must be a list of Python +module names containing a service class (identified by the ``_svc_name_`` +attribute):: + + # setup.py + from distutils.core import setup + import py2exe + + setup(service=["MyService"]) + +Optionally, you can specify a 'cmdline-style' attribute to py2exe, with +valid values being 'py2exe' (the default), 'pywin32' or 'custom'. 'py2exe' +specifies the traditional command-line always supported by py2exe. 'pywin32' +supports the exact same command-line arguments as pywin32 supports (ie, the +same arguments supported when running the service from the .py file.) +'custom' means that your module is expected to provide a 'HandleCommandLine' +function which is responsible for all command-line handling. + +The built service executables are able to install and remove +themselves by calling them with certain command line flags, run the +exe with the ``-help`` argument to find out more. + +COM servers +----------- + +COM servers are built by passing a ``com_server`` keyword argument to +the setup function, again the value must be a list of Python module +names containing one or more COM server classes (identified by their +``_reg_progid_`` attribute):: + + # setup.py + from distutils.core import setup + import py2exe + + setup(com_server=["win32com.server.interp"]) + +By default both DLL and EXE servers are built, you should simply +delete those you don't need. + +The bundle option +----------------- + +By default py2exe creates these files in the ``dist`` directory which +you must deploy: + + 1. One (or more) .exe files + + 2. The python##.dll + + 3. A couple of .pyd files which are the compiled extensions that the + exe files need, plus any other .dll files that the extensions need. + + 4. A ``library.zip`` file which contains the compiled pure python + modules as .pyc or .pyo files (if you have specified 'zipfile=None' + in the setup script this file is append to the .exe files and not + present in the dist-directory). + + +The ``--bundle `` or ``-b `` command line switch will +create less files because binary extensions, runtime dlls, and even +the Python-dll itself is bundled into the executable itself, or inside +the library-archive if you prefer that. + +The bundled pyds and dlls are *never* unpacked to the file system, +instead they are transparently loaded at runtime from the bundle. The +resulting executable *appears* to be statically linked. + +Specifying a level of ``2`` includes the .pyd and .dll files into the +zip-archive or the executable. Thus, the dist directory will contain +your exe file(s), the library.zip file (if you haven't specified +'zipfile=None'), and the python dll. The advantage of this scheme is +that the application can still load extension modules from the file +system if you extend ``sys.path`` at runtime. + +Using a level of ``1`` includes the .pyd and .dll files into the +zip-archive or the executable itself, and does the same for +pythonXY.dll. The advantage is that you only need to distribute one +file per exe, which will however be quite large. Another advantage is +that inproc COM servers will run completely isolated from other Python +interpreters in the same exe. The disadvantage of this scheme is that +it is impossible to load other extensions from the file system, the +application will crash with a fatal Python error if you try this. I +have still to find a way to prevent this and raise an ImportError +instead - any suggestions how this can be implemented would be very +welcome. + +The bundle-option has been tested with some popular extensions, but of +course there's no guarantee that any extension will work in bundled +form - be sure to test the executable (which you should do anyway). + +The bundle option achieves its magic by code which emulates the +Windows LoadLibrary api, it is compiled into the exe-stubs that py2exe +uses. For experimentation, it is also installed as a normal Python +extension ``_memimporter.pyd`` in the ``lib\site-packages`` directory. +The Python module ``zipextimported.py`` in the same directory +demonstrates how it can be used to load binary extensions from +zip-files. + +Samples +------- + +The ``py2exe``-installer installs some examples into the +``lib\site-packages\py2exe\samples`` directory, demonstrating several +simple and advances features. + +The ``singlefile`` subdirectory contains two samples which are built +as single-file executables: a trivial wxPython gui program, and a +pywin32 dll COM server module. + +How does it work? +----------------- + +**py2exe** uses python's ``modulefinder`` to examine your script and +find all python and extension modules needed to run it. Pure python +modules are compiled into ``.pyc`` or ``.pyo`` files in a temporary +directory. Compiled extension modules (``.pyd``) are also found and +parsed for binary dependencies. + +A zip-compatible archive is built, containing all python files from +this directory. Your main script is inserted as a resource into a +custom embedded python interpreter supplied with py2exe, and the +zip-archive is installed as the only item on ``sys.path``. + +In simple cases, only ``pythonxx.dll`` is needed in addition to +``myscript.exe``. If, however, your script needs extension modules, +unfortunately those cannot be included or imported from the +zip-archive, so they are needed as separate files (and are copied into +the ``dist`` directory). + +*Attention*: **py2exe** tries to track down all binary dependencies +for all pyds and dlls copied to the dist directory recursively, and +copies all these dependend files into the dist directory. **py2exe** +has a builtin list of some system dlls which are not copied, but this +list can never be complete. + +Installing py2exe +----------------- + +Download and run the installer py2exe-0.6.8.win32-py2.3.exe_ (for +Python 2.3), py2exe-0.6.8.win32-py2.4.exe_ (for +Python 2.4), py2exe-0.6.8.win32-py2.5.exe_ (for Python 2.5), or py2exe-0.6.8.win64-py2.5.amd64.msi_ (for 64-bit Python 2.5). + +This installs **py2exe** together with some samples, the samples are in +the ``lib\site-packages\py2exe\samples`` subdirectory. + +For **Windows 95/98/Me**, you need the *Microsoft Layer for Unicode on +Windows 95/98/ME Systems (MSLU)* dll from here_ (Internet Explorer is +required to download it: Scroll down to the Win95/98/Me section). + +Download and run the self-extracting ``unicows.exe`` file, and copy +the unpacked ``unicows.dll`` file in the directory which contains your +``python.exe``. Note that this is only needed on the machine where +you want to build executables with **py2exe**, it is not required on +the machine where you want to run the created programs. + +If you use ``py2exe`` to build COM clients or servers, win32all build +163 (or later) is strongly recommened - it contains much better support +for frozen executables. + +.. _here: http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdkredist.htm + +Project information +------------------- + +The project is hosted at |SourceForge|_. + +.. |SourceForge| image:: + http://sourceforge.net/sflogo.php?group_id=15583&;type=1 + :alt: SourceForge Logo + :align: middle + :class: borderless + :width: 88 + :height: 31 diff --git a/installer/py2exe/docs/updateweb.cmd b/installer/py2exe/docs/updateweb.cmd new file mode 100644 index 0000000..3c0d60b --- /dev/null +++ b/installer/py2exe/docs/updateweb.cmd @@ -0,0 +1,2 @@ +scp index.html default.css LICENSE.txt py2exe.jpg jretz@py2exe.org:~/py2exe.org/old +scp index.html default.css LICENSE.txt py2exe.jpg jretz@shell.sourceforge.net:/home/groups/p/py/py2exe/htdocs diff --git a/installer/py2exe/py2exe/__init__.py b/installer/py2exe/py2exe/__init__.py new file mode 100644 index 0000000..0a880c5 --- /dev/null +++ b/installer/py2exe/py2exe/__init__.py @@ -0,0 +1,86 @@ +""" +builds windows executables from Python scripts + +New keywords for distutils' setup function specify what to build: + + console + list of scripts to convert into console exes + + windows + list of scripts to convert into gui exes + + service + list of module names containing win32 service classes + + com_server + list of module names containing com server classes + + ctypes_com_server + list of module names containing com server classes + + zipfile + name of shared zipfile to generate, may specify a subdirectory, + defaults to 'library.zip' + + +py2exe options, to be specified in the options keyword to the setup function: + + unbuffered - if true, use unbuffered binary stdout and stderr + optimize - string or int (0, 1, or 2) + + includes - list of module names to include + packages - list of packages to include with subpackages + ignores - list of modules to ignore if they are not found + excludes - list of module names to exclude + dll_excludes - list of dlls to exclude + + dist_dir - directory where to build the final files + typelibs - list of gen_py generated typelibs to include (XXX more text needed) + +Items in the console, windows, service or com_server list can also be +dictionaries to further customize the build process. The following +keys in the dictionary are recognized, most are optional: + + modules (SERVICE, COM) - list of module names (required) + script (EXE) - list of python scripts (required) + dest_base - directory and basename for the executable + if a directory is contained, must be the same for all targets + create_exe (COM) - boolean, if false, don't build a server exe + create_dll (COM) - boolean, if false, don't build a server dll + bitmap_resources - list of 2-tuples (id, pathname) + icon_resources - list of 2-tuples (id, pathname) + other_resources - list of 3-tuples (resource_type, id, datastring) +""" +# py2exe/__init__.py + +# 'import py2exe' imports this package, and two magic things happen: +# +# - the 'py2exe.build_exe' submodule is imported and installed as +# 'distutils.commands.py2exe' command +# +# - the default distutils Distribution class is replaced by the +# special one contained in this module. +# + +__version__ = "0.6.8" + +import distutils.dist, distutils.core, distutils.command, build_exe, sys + +class Distribution(distutils.dist.Distribution): + + def __init__(self, attrs): + self.ctypes_com_server = attrs.pop("ctypes_com_server", []) + self.com_server = attrs.pop("com_server", []) + self.service = attrs.pop("service", []) + self.windows = attrs.pop("windows", []) + self.console = attrs.pop("console", []) + self.isapi = attrs.pop("isapi", []) + self.zipfile = attrs.pop("zipfile", "library.zip") + + distutils.dist.Distribution.__init__(self, attrs) + +distutils.core.Distribution = Distribution + +distutils.command.__all__.append('py2exe') + +sys.modules['distutils.command.py2exe'] = build_exe diff --git a/installer/py2exe/py2exe/boot_com_servers.py b/installer/py2exe/py2exe/boot_com_servers.py new file mode 100644 index 0000000..08e8588 --- /dev/null +++ b/installer/py2exe/py2exe/boot_com_servers.py @@ -0,0 +1,121 @@ +# This support script is executed as the entry point for py2exe. + +import sys +import win32api + +if 0: + ################################################################ + # XXX Remove later! + class LOGGER: + def __init__(self): + self.softspace = None + self.ofi = open(r"c:\comerror.txt", "w") + def write(self, text): + self.ofi.write(text) + self.ofi.flush() + sys.stderr = sys.stdout = LOGGER() + sys.stderr.write("PATH is %s\n" % sys.path) +################################################################ +# tell the win32 COM registering/unregistering code that we're inside +# of an EXE/DLL +import pythoncom +if not hasattr(sys, "frozen"): + # standard exes have none. + sys.frozen = pythoncom.frozen = 1 +else: + # com DLLs already have sys.frozen set to 'dll' + pythoncom.frozen = sys.frozen + +# Add some extra imports here, just to avoid putting them as "hidden imports" +# anywhere else - this script has the best idea about what it needs. +# (and hidden imports are currently disabled :) +import win32com.server.policy, win32com.server.util + +# Patchup sys.argv for our DLL +if sys.frozen=="dll" and not hasattr(sys, "argv"): + sys.argv = [win32api.GetModuleFileName(sys.frozendllhandle)] +# We assume that py2exe has magically set com_module_names +# to the module names that expose the COM objects we host. +# Note that here all the COM modules for the app are imported - hence any +# environment changes (such as sys.stderr redirection) will happen now. +com_modules = [] +try: + for name in com_module_names: + __import__(name) + com_modules.append(sys.modules[name]) +except NameError: + print "This script is designed to be run from inside py2exe" + sys.exit(1) +del name + +def get_classes(module): + return [ob + for ob in module.__dict__.values() + if hasattr(ob, "_reg_progid_") + ] + +def DllRegisterServer(): + # Enumerate each module implementing an object + import win32com.server.register + for mod in com_modules: + # register each class + win32com.server.register.RegisterClasses(*get_classes(mod)) + # see if the module has a custom registration function. + extra_fun = getattr(mod, "DllRegisterServer", None) + if extra_fun is not None: + extra_fun() + +def DllUnregisterServer(): + # Enumerate each module implementing an object + import win32com.server.register + for mod in com_modules: + # see if the module has a custom unregister function. + extra_fun = getattr(mod, "DllUnregisterServer", None) + if extra_fun is not None: + extra_fun() + # and unregister each class + win32com.server.register.UnregisterClasses(*get_classes(mod)) + +def DllInstall(bInstall, cmdline): + # Enumerate each module implementing an object + for mod in com_modules: + # see if the module has the function. + extra_fun = getattr(mod, "DllInstall", None) + if extra_fun is not None: + extra_fun(bInstall, cmdline) + +# Mainline code - executed always +# If we are running as a .EXE, check and process command-line args +if sys.frozen != "dll": + import win32com.server.localserver + for i in range(1, len(sys.argv)): + arg = sys.argv[i].lower() + # support "exe /regserver" + if arg.find("/reg") > -1 or arg.find("--reg") > -1 \ + or arg.find("-regserver") > -1: + DllRegisterServer() + break + + # support "exe /unreg...r" + if arg.find("/unreg") > -1 or arg.find("--unreg") > -1 \ + or arg.find("-unregserver") > -1: + DllUnregisterServer() + break + + # MS seems to like /automate to run the class factories. + if arg.find("/automate") > -1: + clsids = [] + for m in com_modules: + for k in get_classes(m): + clsids.append(k._reg_clsid_) + # Fire up the class factories, and run the servers + win32com.server.localserver.serve(clsids) + # All servers dead - we are done! + break + else: + # You could do something else useful here. + win32api.MessageBox(0, + "This program hosts a COM Object and\r\n" + "is started automatically\r\n" + "(or maybe you want /register or /unregister?)", + "COM Object") diff --git a/installer/py2exe/py2exe/boot_common.py b/installer/py2exe/py2exe/boot_common.py new file mode 100644 index 0000000..2fba9c8 --- /dev/null +++ b/installer/py2exe/py2exe/boot_common.py @@ -0,0 +1,93 @@ +# Common py2exe boot script - executed for all target types. + +# When we are a windows_exe we have no console, and writing to +# sys.stderr or sys.stdout will sooner or later raise an exception, +# and tracebacks will be lost anyway (see explanation below). +# +# We assume that output to sys.stdout can go to the bitsink, but we +# *want* to see tracebacks. So we redirect sys.stdout into an object +# with a write method doing nothing, and sys.stderr into a logfile +# having the same name as the executable, with '.log' appended. +# +# We only open the logfile if something is written to sys.stderr. +# +# If the logfile cannot be opened for *any* reason, we have no choice +# but silently ignore the error. +# +# It remains to be seen if the 'a' flag for opening the logfile is a +# good choice, or 'w' would be better. +# +# More elaborate explanation on why this is needed: +# +# The sys.stdout and sys.stderr that GUI programs get (from Windows) are +# more than useless. This is not a py2exe problem, pythonw.exe behaves +# in the same way. +# +# To demonstrate, run this program with pythonw.exe: +# +# import sys +# sys.stderr = open("out.log", "w") +# for i in range(10000): +# print i +# +# and open the 'out.log' file. It contains this: +# +# Traceback (most recent call last): +# File "out.py", line 6, in ? +# print i +# IOError: [Errno 9] Bad file descriptor +# +# In other words, after printing a certain number of bytes to the +# system-supplied sys.stdout (or sys.stderr) an exception will be raised. +# + +import sys +if sys.frozen == "windows_exe": + class Stderr(object): + softspace = 0 + _file = None + _error = None + def write(self, text, alert=sys._MessageBox, fname=sys.executable + '.log'): + if self._file is None and self._error is None: + try: + self._file = open(fname, 'a') + except Exception, details: + self._error = details + import atexit + atexit.register(alert, 0, + "The logfile '%s' could not be opened:\n %s" % \ + (fname, details), + "Errors occurred") + if self._file is not None: + self._file.write(text) + self._file.flush() + def flush(self): + if self._file is not None: + self._file.flush() + sys.stderr = Stderr() + del sys._MessageBox + del Stderr + + class Blackhole(object): + softspace = 0 + def write(self, text): + pass + def flush(self): + pass + sys.stdout = Blackhole() + del Blackhole +del sys + +# Disable linecache.getline() which is called by +# traceback.extract_stack() when an exception occurs to try and read +# the filenames embedded in the packaged python code. This is really +# annoying on windows when the d: or e: on our build box refers to +# someone elses removable or network drive so the getline() call +# causes it to ask them to insert a disk in that drive. +import linecache +def fake_getline(filename, lineno, module_globals=None): + return '' +linecache.orig_getline = linecache.getline +linecache.getline = fake_getline + +del linecache, fake_getline diff --git a/installer/py2exe/py2exe/boot_ctypes_com_server.py b/installer/py2exe/py2exe/boot_ctypes_com_server.py new file mode 100644 index 0000000..e979e0e --- /dev/null +++ b/installer/py2exe/py2exe/boot_ctypes_com_server.py @@ -0,0 +1,89 @@ +# This support script is executed as the entry point for ctypes com servers. +# XXX Currently, this is always run as part of a dll. + +import sys +import _ctypes + +if 1: + ################################################################ + # XXX Remove later! + import ctypes + class LOGGER: + def __init__(self): + self.softspace = None + def write(self, text): + if isinstance(text, unicode): + ctypes.windll.kernel32.OutputDebugStringW(text) + else: + ctypes.windll.kernel32.OutputDebugStringA(text) + sys.stderr = sys.stdout = LOGGER() +## sys.stderr.write("PATH is %s\n" % sys.path) + +################################################################ +# tell the win32 COM registering/unregistering code that we're inside +# of an EXE/DLL + +if not hasattr(sys, "frozen"): + # standard exes have none. + sys.frozen = _ctypes.frozen = 1 +else: + # com DLLs already have sys.frozen set to 'dll' + _ctypes.frozen = sys.frozen + +# Add some extra imports here, just to avoid putting them as "hidden imports" +# anywhere else - this script has the best idea about what it needs. +# (and hidden imports are currently disabled :) +#... + +# We assume that py2exe has magically set com_module_names +# to the module names that expose the COM objects we host. +# Note that here all the COM modules for the app are imported - hence any +# environment changes (such as sys.stderr redirection) will happen now. +try: + com_module_names +except NameError: + print "This script is designed to be run from inside py2exe % s" % str(details) + sys.exit(1) + +com_modules = [] +for name in com_module_names: + __import__(name) + com_modules.append(sys.modules[name]) + +def get_classes(module): + return [ob + for ob in module.__dict__.values() + if hasattr(ob, "_reg_progid_") + ] + +def build_class_map(): + # Set _clsid_to_class in comtypes.server.inprocserver. + # + # This avoids the need to have registry entries pointing to the + # COM server class. + classmap = {} + for mod in com_modules: + # dump each class + for cls in get_classes(mod): + classmap[cls._reg_clsid_] = cls + import comtypes.server.inprocserver + comtypes.server.inprocserver._clsid_to_class = classmap +build_class_map() +del build_class_map + +def DllRegisterServer(): + # Enumerate each module implementing an object + from comtypes.server.register import register + for mod in com_modules: + # register each class + for cls in get_classes(mod): + register(cls) + + +def DllUnregisterServer(): + # Enumerate each module implementing an object + from comtypes.server.register import unregister + for mod in com_modules: + # unregister each class + for cls in get_classes(mod): + unregister(cls) diff --git a/installer/py2exe/py2exe/boot_service.py b/installer/py2exe/py2exe/boot_service.py new file mode 100644 index 0000000..5f092d6 --- /dev/null +++ b/installer/py2exe/py2exe/boot_service.py @@ -0,0 +1,200 @@ +# boot_service.py +import sys +import os +import servicemanager +import win32service +import win32serviceutil +import winerror +# We assume that py2exe has magically set service_module_names +# to the module names that expose the services we host. +service_klasses = [] +try: + service_module_names +except NameError: + print "This script is designed to be run from inside py2exe" + sys.exit(1) + +for name in service_module_names: + # Use the documented fact that when a fromlist is present, + # __import__ returns the innermost module in 'name'. + # This makes it possible to have a dotted name work the + # way you'd expect. + mod = __import__(name, globals(), locals(), ['DUMMY']) + for ob in mod.__dict__.values(): + if hasattr(ob, "_svc_name_"): + service_klasses.append(ob) + +if not service_klasses: + raise RuntimeError, "No service classes found" + +# Event source records come from servicemanager +evtsrc_dll = os.path.abspath(servicemanager.__file__) + +# Tell the Python servicemanager what classes we host. +if len(service_klasses)==1: + k = service_klasses[0] + # One service - make the event name the same as the service. + servicemanager.Initialize(k._svc_name_, evtsrc_dll) + # And the class that hosts it. + servicemanager.PrepareToHostSingle(k) +else: + # Multiple services (NOTE - this hasn't been tested!) + # Use the base name of the exe as the event source + servicemanager.Initialize(os.path.basename(sys.executable), evtsrc_dll) + for k in service_klasses: + servicemanager.PrepareToHostMultiple(k._svc_name_, k) + +################################################################ + +if cmdline_style == "py2exe": + # Simulate the old py2exe service command line handling (to some extent) + # This could do with some re-thought + + class GetoptError(Exception): + pass + + def w_getopt(args, options): + """A getopt for Windows style command lines. + + Options may start with either '-' or '/', the option names may + have more than one letter (examples are /tlb or -RegServer), and + option names are case insensitive. + + Returns two elements, just as getopt.getopt. The first is a list + of (option, value) pairs in the same way getopt.getopt does, but + there is no '-' or '/' prefix to the option name, and the option + name is always lower case. The second is the list of arguments + which do not belong to any option. + + Different from getopt.getopt, a single argument not belonging to an option + does not terminate parsing. + """ + opts = [] + arguments = [] + while args: + if args[0][:1] in "/-": + arg = args[0][1:] # strip the '-' or '/' + arg = arg.lower() + if arg + ':' in options: + try: + opts.append((arg, args[1])) + except IndexError: + raise GetoptError, "option '%s' requires an argument" % args[0] + args = args[1:] + elif arg in options: + opts.append((arg, '')) + else: + raise GetoptError, "invalid option '%s'" % args[0] + args = args[1:] + else: + arguments.append(args[0]) + args = args[1:] + + return opts, arguments + + options = "help install remove auto disabled interactive user: password:".split() + + def usage(): + print "Services are supposed to be run by the system after they have been installed." + print "These command line options are available for (de)installation:" + for opt in options: + if opt.endswith(":"): + print "\t-%s " % opt + else: + print "\t-%s" % opt + print + + try: + opts, args = w_getopt(sys.argv[1:], options) + except GetoptError, detail: + print detail + usage() + sys.exit(1) + + if opts: + startType = None + bRunInteractive = 0 + serviceDeps = None + userName = None + password = None + + do_install = False + do_remove = False + + done = False + + for o, a in opts: + if o == "help": + usage() + done = True + elif o == "install": + do_install = True + elif o == "remove": + do_remove = True + elif o == "auto": + startType = win32service.SERVICE_AUTO_START + elif o == "disabled": + startType = win32service.SERVICE_DISABLED + elif o == "user": + userName = a + elif o == "password": + password = a + elif o == "interactive": + bRunInteractive = True + + if do_install: + for k in service_klasses: + svc_display_name = getattr(k, "_svc_display_name_", k._svc_name_) + svc_deps = getattr(k, "_svc_deps_", None) + win32serviceutil.InstallService(None, + k._svc_name_, + svc_display_name, + exeName = sys.executable, + userName = userName, + password = password, + startType = startType, + bRunInteractive = bRunInteractive, + serviceDeps = svc_deps, + description = getattr(k, "_svc_description_", None), + ) + done = True + + if do_remove: + for k in service_klasses: + win32serviceutil.RemoveService(k._svc_name_) + done = True + + if done: + sys.exit(0) + else: + usage() + + print "Connecting to the Service Control Manager" + servicemanager.StartServiceCtrlDispatcher() + +elif cmdline_style == "pywin32": + assert len(service_klasses) == 1, "Can only handle 1 service!" + k = service_klasses[0] + if len(sys.argv) == 1: + try: + servicemanager.StartServiceCtrlDispatcher() + except win32service.error, details: + if details[0] == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: + win32serviceutil.usage() + else: + win32serviceutil.HandleCommandLine(k) + +elif cmdline_style == "custom": + assert len(service_module_names) == 1, "Can only handle 1 service!" + # Unlike services implemented in .py files, when a py2exe service exe is + # executed without args, it may mean the service is being started. + if len(sys.argv) == 1: + try: + servicemanager.StartServiceCtrlDispatcher() + except win32service.error, details: + if details[0] == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: + win32serviceutil.usage() + else: + # assume/insist that the module provides a HandleCommandLine function. + mod = sys.modules[service_module_names[0]] + mod.HandleCommandLine() diff --git a/installer/py2exe/py2exe/build_exe.py b/installer/py2exe/py2exe/build_exe.py new file mode 100644 index 0000000..f96bb4f --- /dev/null +++ b/installer/py2exe/py2exe/build_exe.py @@ -0,0 +1,1632 @@ +# Changes: +# +# can now specify 'zipfile = None', in this case the Python module +# library archive is appended to the exe. + +# Todo: +# +# Make 'unbuffered' a per-target option + +from distutils.core import Command +from distutils.spawn import spawn +from distutils.errors import * +import sys, os, imp, types, stat +import marshal +import zipfile +import sets +import tempfile +import struct + +is_win64 = struct.calcsize("P") == 8 + +def _is_debug_build(): + for ext, _, _ in imp.get_suffixes(): + if ext == "_d.pyd": + return True + return False + +is_debug_build = _is_debug_build() + +if is_debug_build: + python_dll = "python%d%d_d.dll" % sys.version_info[:2] +else: + python_dll = "python%d%d.dll" % sys.version_info[:2] + +# resource constants +RT_BITMAP=2 + +# note: we cannot use the list from imp.get_suffixes() because we want +# .pyc and .pyo, independent of the optimize flag. +_py_suffixes = ['.py', '.pyo', '.pyc', '.pyw'] +_c_suffixes = [_triple[0] for _triple in imp.get_suffixes() + if _triple[2] == imp.C_EXTENSION] + +def imp_find_module(name): + # same as imp.find_module, but handles dotted names + names = name.split('.') + path = None + for name in names: + result = imp.find_module(name, path) + path = [result[1]] + return result + +def fancy_split(str, sep=","): + # a split which also strips whitespace from the items + # passing a list or tuple will return it unchanged + if str is None: + return [] + if hasattr(str, "split"): + return [item.strip() for item in str.split(sep)] + return str + +def ensure_unicode(text): + if isinstance(text, unicode): + return text + return text.decode("mbcs") + +# This loader locates extension modules relative to the library.zip +# file when an archive is used (i.e., skip_archive is not used), otherwise +# it locates extension modules relative to sys.prefix. +LOADER = """ +def __load(): + import imp, os, sys + try: + dirname = os.path.dirname(__loader__.archive) + except NameError: + dirname = sys.prefix + path = os.path.join(dirname, '%s') + #print "py2exe extension module", __name__, "->", path + mod = imp.load_dynamic(__name__, path) +## mod.frozen = 1 +__load() +del __load +""" + +# A very loosely defined "target". We assume either a "script" or "modules" +# attribute. Some attributes will be target specific. +class Target: + def __init__(self, **kw): + self.__dict__.update(kw) + # If modules is a simple string, assume they meant list + m = self.__dict__.get("modules") + if m and type(m) in types.StringTypes: + self.modules = [m] + def get_dest_base(self): + dest_base = getattr(self, "dest_base", None) + if dest_base: return dest_base + script = getattr(self, "script", None) + if script: + return os.path.basename(os.path.splitext(script)[0]) + modules = getattr(self, "modules", None) + assert modules, "no script, modules or dest_base specified" + return modules[0].split(".")[-1] + + def validate(self): + resources = getattr(self, "bitmap_resources", []) + \ + getattr(self, "icon_resources", []) + for r_id, r_filename in resources: + if type(r_id) != type(0): + raise DistutilsOptionError, "Resource ID must be an integer" + if not os.path.isfile(r_filename): + raise DistutilsOptionError, "Resource filename '%s' does not exist" % r_filename + +def FixupTargets(targets, default_attribute): + if not targets: + return targets + ret = [] + for target_def in targets: + if type(target_def) in types.StringTypes : + # Create a default target object, with the string as the attribute + target = Target(**{default_attribute: target_def}) + else: + d = getattr(target_def, "__dict__", target_def) + if not d.has_key(default_attribute): + raise DistutilsOptionError, \ + "This target class requires an attribute '%s'" % default_attribute + target = Target(**d) + target.validate() + ret.append(target) + return ret + +class py2exe(Command): + description = "" + # List of option tuples: long name, short name (None if no short + # name), and help string. + user_options = [ + ('optimize=', 'O', + "optimization level: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('dist-dir=', 'd', + "directory to put final built distributions in (default is dist)"), + + ("excludes=", 'e', + "comma-separated list of modules to exclude"), + ("dll-excludes=", None, + "comma-separated list of DLLs to exclude"), + ("ignores=", None, + "comma-separated list of modules to ignore if they are not found"), + ("includes=", 'i', + "comma-separated list of modules to include"), + ("packages=", 'p', + "comma-separated list of packages to include"), + + ("compressed", 'c', + "create a compressed zipfile"), + + ("xref", 'x', + "create and show a module cross reference"), + + ("bundle-files=", 'b', + "bundle dlls in the zipfile or the exe. Valid levels are 1, 2, or 3 (default)"), + + ("skip-archive", None, + "do not place Python bytecode files in an archive, put them directly in the file system"), + + ("ascii", 'a', + "do not automatically include encodings and codecs"), + + ('custom-boot-script=', None, + "Python file that will be run when setting up the runtime environment"), + ] + + boolean_options = ["compressed", "xref", "ascii", "skip-archive", "retain_times"] + + def initialize_options (self): + self.xref =0 + self.compressed = 0 + self.unbuffered = 0 + self.optimize = 0 + self.includes = None + self.excludes = None + self.ignores = None + self.packages = None + self.dist_dir = None + self.dll_excludes = 'msvcr90.dll msvcp90.dll' + self.typelibs = None + self.bundle_files = 3 + self.skip_archive = 0 + self.ascii = 0 + self.custom_boot_script = None + self.retain_times = 0 + + def finalize_options (self): + self.optimize = int(self.optimize) + self.excludes = fancy_split(self.excludes) + self.includes = fancy_split(self.includes) + self.ignores = fancy_split(self.ignores) + self.bundle_files = int(self.bundle_files) + if self.bundle_files < 1 or self.bundle_files > 3: + raise DistutilsOptionError, \ + "bundle-files must be 1, 2, or 3, not %s" % self.bundle_files + if is_win64 and self.bundle_files < 3: + raise DistutilsOptionError, \ + "bundle-files %d not yet supported on win64" % self.bundle_files + if self.skip_archive: + if self.compressed: + raise DistutilsOptionError, \ + "can't compress when skipping archive" + if self.distribution.zipfile is None: + raise DistutilsOptionError, \ + "zipfile cannot be None when skipping archive" + # includes is stronger than excludes + for m in self.includes: + if m in self.excludes: + self.excludes.remove(m) + self.packages = fancy_split(self.packages) + self.set_undefined_options('bdist', + ('dist_dir', 'dist_dir')) + self.dll_excludes = [x.lower() for x in fancy_split(self.dll_excludes)] + + def run(self): + build = self.reinitialize_command('build') + build.run() + sys_old_path = sys.path[:] + if build.build_platlib is not None: + sys.path.insert(0, build.build_platlib) + if build.build_lib is not None: + sys.path.insert(0, build.build_lib) + try: + self._run() + finally: + sys.path = sys_old_path + + def _run(self): + self.create_directories() + self.plat_prepare() + self.fixup_distribution() + + dist = self.distribution + + # all of these contain module names + required_modules = [] + for target in dist.com_server + dist.service + dist.ctypes_com_server: + required_modules.extend(target.modules) + # and these contains file names + required_files = [target.script + for target in dist.windows + dist.console] + + mf = self.create_modulefinder() + + # These are the name of a script, but used as a module! + for f in dist.isapi: + mf.load_file(f.script) + + if self.typelibs: + print "*** generate typelib stubs ***" + from distutils.dir_util import mkpath + genpy_temp = os.path.join(self.temp_dir, "win32com", "gen_py") + mkpath(genpy_temp) + num_stubs = collect_win32com_genpy(genpy_temp, + self.typelibs, + verbose=self.verbose, + dry_run=self.dry_run) + print "collected %d stubs from %d type libraries" \ + % (num_stubs, len(self.typelibs)) + mf.load_package("win32com.gen_py", genpy_temp) + self.packages.append("win32com.gen_py") + + # monkey patching the compile builtin. + # The idea is to include the filename in the error message + orig_compile = compile + import __builtin__ + def my_compile(source, filename, *args): + try: + result = orig_compile(source, filename, *args) + except Exception, details: + raise DistutilsError, "compiling '%s' failed\n %s: %s" % \ + (filename, details.__class__.__name__, details) + return result + __builtin__.compile = my_compile + + print "*** searching for required modules ***" + self.find_needed_modules(mf, required_files, required_modules) + + print "*** parsing results ***" + py_files, extensions, builtins = self.parse_mf_results(mf) + + if self.xref: + mf.create_xref() + + print "*** finding dlls needed ***" + dlls = self.find_dlls(extensions) + self.plat_finalize(mf.modules, py_files, extensions, dlls) + dlls = [item for item in dlls + if os.path.basename(item).lower() not in self.dll_excludes] + # should we filter self.other_depends in the same way? + + print "*** create binaries ***" + self.create_binaries(py_files, extensions, dlls) + + self.fix_badmodules(mf) + + if mf.any_missing(): + print "The following modules appear to be missing" + print mf.any_missing() + + if self.other_depends: + print + print "*** binary dependencies ***" + print "Your executable(s) also depend on these dlls which are not included," + print "you may or may not need to distribute them." + print + print "Make sure you have the license if you distribute any of them, and" + print "make sure you don't distribute files belonging to the operating system." + print + for fnm in self.other_depends: + print " ", os.path.basename(fnm), "-", fnm.strip() + + def create_modulefinder(self): + from modulefinder import ReplacePackage + from py2exe.mf import ModuleFinder + ReplacePackage("_xmlplus", "xml") + return ModuleFinder(excludes=self.excludes) + + def fix_badmodules(self, mf): + # This dictionary maps additional builtin module names to the + # module that creates them. + # For example, 'wxPython.misc' creates a builtin module named + # 'miscc'. + builtins = {"clip_dndc": "wxPython.clip_dnd", + "cmndlgsc": "wxPython.cmndlgs", + "controls2c": "wxPython.controls2", + "controlsc": "wxPython.controls", + "eventsc": "wxPython.events", + "filesysc": "wxPython.filesys", + "fontsc": "wxPython.fonts", + "framesc": "wxPython.frames", + "gdic": "wxPython.gdi", + "imagec": "wxPython.image", + "mdic": "wxPython.mdi", + "misc2c": "wxPython.misc2", + "miscc": "wxPython.misc", + "printfwc": "wxPython.printfw", + "sizersc": "wxPython.sizers", + "stattoolc": "wxPython.stattool", + "streamsc": "wxPython.streams", + "utilsc": "wxPython.utils", + "windows2c": "wxPython.windows2", + "windows3c": "wxPython.windows3", + "windowsc": "wxPython.windows", + } + + # Somewhat hackish: change modulefinder's badmodules dictionary in place. + bad = mf.badmodules + # mf.badmodules is a dictionary mapping unfound module names + # to another dictionary, the keys of this are the module names + # importing the unknown module. For the 'miscc' module + # mentioned above, it looks like this: + # mf.badmodules["miscc"] = { "wxPython.miscc": 1 } + for name in mf.any_missing(): + if name in self.ignores: + del bad[name] + continue + mod = builtins.get(name, None) + if mod is not None: + if mod in bad[name] and bad[name] == {mod: 1}: + del bad[name] + + def find_dlls(self, extensions): + dlls = [item.__file__ for item in extensions] +## extra_path = ["."] # XXX + extra_path = [] + dlls, unfriendly_dlls, other_depends = \ + self.find_dependend_dlls(dlls, + extra_path + sys.path, + self.dll_excludes) + self.other_depends = other_depends + # dlls contains the path names of all dlls we need. + # If a dll uses a function PyImport_ImportModule (or what was it?), + # it's name is additionally in unfriendly_dlls. + for item in extensions: + if item.__file__ in dlls: + dlls.remove(item.__file__) + return dlls + + def create_directories(self): + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'winexe') + + collect_name = "collect-%d.%d" % sys.version_info[:2] + self.collect_dir = os.path.abspath(os.path.join(self.bdist_dir, collect_name)) + self.mkpath(self.collect_dir) + + bundle_name = "bundle-%d.%d" % sys.version_info[:2] + self.bundle_dir = os.path.abspath(os.path.join(self.bdist_dir, bundle_name)) + self.mkpath(self.bundle_dir) + + self.temp_dir = os.path.abspath(os.path.join(self.bdist_dir, "temp")) + self.mkpath(self.temp_dir) + + self.dist_dir = os.path.abspath(self.dist_dir) + self.mkpath(self.dist_dir) + + if self.distribution.zipfile is None: + self.lib_dir = self.dist_dir + else: + self.lib_dir = os.path.join(self.dist_dir, + os.path.dirname(self.distribution.zipfile)) + self.mkpath(self.lib_dir) + + def copy_extensions(self, extensions): + print "*** copy extensions ***" + # copy the extensions to the target directory + for item in extensions: + src = item.__file__ + if self.bundle_files > 2: # don't bundle pyds and dlls + dst = os.path.join(self.lib_dir, (item.__pydfile__)) + self.copy_file(src, dst, preserve_mode=0) + self.lib_files.append(dst) + else: + # we have to preserve the packages + package = "\\".join(item.__name__.split(".")[:-1]) + if package: + dst = os.path.join(package, os.path.basename(src)) + else: + dst = os.path.basename(src) + self.copy_file(src, os.path.join(self.collect_dir, dst), preserve_mode=0) + self.compiled_files.append(dst) + + def copy_dlls(self, dlls): + # copy needed dlls where they belong. + print "*** copy dlls ***" + if self.bundle_files < 3: + self.copy_dlls_bundle_files(dlls) + return + # dlls belong into the lib_dir, except those listed in dlls_in_exedir, + # which have to go into exe_dir (pythonxy.dll, w9xpopen.exe). + for dll in dlls: + base = os.path.basename(dll) + if base.lower() in self.dlls_in_exedir: + # These special dlls cannot be in the lib directory, + # they must go into the exe directory. + dst = os.path.join(self.exe_dir, base) + else: + dst = os.path.join(self.lib_dir, base) + _, copied = self.copy_file(dll, dst, preserve_mode=0) + if not self.dry_run and copied and base.lower() == python_dll.lower(): + # If we actually copied pythonxy.dll, we have to patch it. + # + # Previously, the code did it every time, but this + # breaks if, for example, someone runs UPX over the + # dist directory. Patching an UPX'd dll seems to work + # (no error is detected when patching), but the + # resulting dll does not work anymore. + # + # The function restores the file times so + # dependencies still work correctly. + self.patch_python_dll_winver(dst) + + self.lib_files.append(dst) + + def copy_dlls_bundle_files(self, dlls): + # If dlls have to be bundled, they are copied into the + # collect_dir and will be added to the list of files to + # include in the zip archive 'self.compiled_files'. + # + # dlls listed in dlls_in_exedir have to be treated differently: + # + for dll in dlls: + base = os.path.basename(dll) + if base.lower() in self.dlls_in_exedir: + # pythonXY.dll must be bundled as resource. + # w9xpopen.exe must be copied to self.exe_dir. + if base.lower() == python_dll.lower() and self.bundle_files < 2: + dst = os.path.join(self.bundle_dir, base) + else: + dst = os.path.join(self.exe_dir, base) + _, copied = self.copy_file(dll, dst, preserve_mode=0) + if not self.dry_run and copied and base.lower() == python_dll.lower(): + # If we actually copied pythonxy.dll, we have to + # patch it. Well, since it's impossible to load + # resources from the bundled dlls it probably + # doesn't matter. + self.patch_python_dll_winver(dst) + self.lib_files.append(dst) + continue + + dst = os.path.join(self.collect_dir, os.path.basename(dll)) + self.copy_file(dll, dst, preserve_mode=0) + # Make sure they will be included into the zipfile. + self.compiled_files.append(os.path.basename(dst)) + + def create_binaries(self, py_files, extensions, dlls): + dist = self.distribution + + # byte compile the python modules into the target directory + print "*** byte compile python files ***" + self.compiled_files = byte_compile(py_files, + target_dir=self.collect_dir, + optimize=self.optimize, + force=0, + verbose=self.verbose, + dry_run=self.dry_run) + + self.lib_files = [] + self.console_exe_files = [] + self.windows_exe_files = [] + self.service_exe_files = [] + self.comserver_files = [] + + self.copy_extensions(extensions) + self.copy_dlls(dlls) + + # create the shared zipfile containing all Python modules + if dist.zipfile is None: + fd, archive_name = tempfile.mkstemp() + os.close(fd) + else: + archive_name = os.path.join(self.lib_dir, + os.path.basename(dist.zipfile)) + + arcname = self.make_lib_archive(archive_name, + base_dir=self.collect_dir, + files=self.compiled_files, + verbose=self.verbose, + dry_run=self.dry_run) + if dist.zipfile is not None: + self.lib_files.append(arcname) + + for target in self.distribution.isapi: + print "*** copy isapi support DLL ***" + # Locate the support DLL, and copy as "_script.dll", just like + # isapi itself + import isapi + src_name = is_debug_build and "PyISAPI_loader_d.dll" or \ + "PyISAPI_loader.dll" + src = os.path.join(isapi.__path__[0], src_name) + # destination name is "_{module_name}.dll" just like pyisapi does. + script_base = os.path.splitext(os.path.basename(target.script))[0] + dst = os.path.join(self.exe_dir, "_" + script_base + ".dll") + self.copy_file(src, dst, preserve_mode=0) + + if self.distribution.has_data_files(): + print "*** copy data files ***" + install_data = self.reinitialize_command('install_data') + install_data.install_dir = self.dist_dir + install_data.ensure_finalized() + install_data.run() + + self.lib_files.extend(install_data.get_outputs()) + + # build the executables + for target in dist.console: + dst = self.build_executable(target, self.get_console_template(), + arcname, target.script) + self.console_exe_files.append(dst) + for target in dist.windows: + dst = self.build_executable(target, self.get_windows_template(), + arcname, target.script) + self.windows_exe_files.append(dst) + for target in dist.service: + dst = self.build_service(target, self.get_service_template(), + arcname) + self.service_exe_files.append(dst) + + for target in dist.isapi: + dst = self.build_isapi(target, self.get_isapi_template(), arcname) + + for target in dist.com_server: + if getattr(target, "create_exe", True): + dst = self.build_comserver(target, self.get_comexe_template(), + arcname) + self.comserver_files.append(dst) + if getattr(target, "create_dll", True): + dst = self.build_comserver(target, self.get_comdll_template(), + arcname) + self.comserver_files.append(dst) + + for target in dist.ctypes_com_server: + dst = self.build_comserver(target, self.get_ctypes_comdll_template(), + arcname, boot_script="ctypes_com_server") + self.comserver_files.append(dst) + + if dist.zipfile is None: + os.unlink(arcname) + else: + if self.bundle_files < 3 or self.compressed: + arcbytes = open(arcname, "rb").read() + arcfile = open(arcname, "wb") + + if self.bundle_files < 2: # bundle pythonxy.dll also + print "Adding %s to %s" % (python_dll, arcname) + arcfile.write("") + bytes = open(os.path.join(self.bundle_dir, python_dll), "rb").read() + arcfile.write(struct.pack("i", len(bytes))) + arcfile.write(bytes) # python dll + + if self.compressed: + # prepend zlib.pyd also + zlib_file = imp.find_module("zlib")[0] + if zlib_file: + print "Adding zlib%s.pyd to %s" % (is_debug_build and "_d" or "", arcname) + arcfile.write("") + bytes = zlib_file.read() + arcfile.write(struct.pack("i", len(bytes))) + arcfile.write(bytes) # zlib.pyd + + arcfile.write(arcbytes) + +#### if self.bundle_files < 2: +#### # remove python dll from the exe_dir, since it is now bundled. +#### os.remove(os.path.join(self.exe_dir, python_dll)) + + + # for user convenience, let subclasses override the templates to use + def get_console_template(self): + return is_debug_build and "run_d.exe" or "run.exe" + + def get_windows_template(self): + return is_debug_build and "run_w_d.exe" or "run_w.exe" + + def get_service_template(self): + return is_debug_build and "run_d.exe" or "run.exe" + + def get_isapi_template(self): + return is_debug_build and "run_isapi_d.dll" or "run_isapi.dll" + + def get_comexe_template(self): + return is_debug_build and "run_w_d.exe" or "run_w.exe" + + def get_comdll_template(self): + return is_debug_build and "run_dll_d.dll" or "run_dll.dll" + + def get_ctypes_comdll_template(self): + return is_debug_build and "run_ctypes_dll_d.dll" or "run_ctypes_dll.dll" + + def fixup_distribution(self): + dist = self.distribution + + # Convert our args into target objects. + dist.com_server = FixupTargets(dist.com_server, "modules") + dist.ctypes_com_server = FixupTargets(dist.ctypes_com_server, "modules") + dist.service = FixupTargets(dist.service, "modules") + dist.windows = FixupTargets(dist.windows, "script") + dist.console = FixupTargets(dist.console, "script") + dist.isapi = FixupTargets(dist.isapi, "script") + + # make sure all targets use the same directory, this is + # also the directory where the pythonXX.dll must reside + paths = sets.Set() + for target in dist.com_server + dist.service \ + + dist.windows + dist.console + dist.isapi: + paths.add(os.path.dirname(target.get_dest_base())) + + if len(paths) > 1: + raise DistutilsOptionError, \ + "all targets must use the same directory: %s" % \ + [p for p in paths] + if paths: + exe_dir = paths.pop() # the only element + if os.path.isabs(exe_dir): + raise DistutilsOptionError, \ + "exe directory must be relative: %s" % exe_dir + self.exe_dir = os.path.join(self.dist_dir, exe_dir) + self.mkpath(self.exe_dir) + else: + # Do we allow to specify no targets? + # We can at least build a zipfile... + self.exe_dir = self.lib_dir + + def get_boot_script(self, boot_type): + # return the filename of the script to use for com servers. + thisfile = sys.modules['py2exe.build_exe'].__file__ + return os.path.join(os.path.dirname(thisfile), + "boot_" + boot_type + ".py") + + def build_comserver(self, target, template, arcname, boot_script="com_servers"): + # Build a dll and an exe executable hosting all the com + # objects listed in module_names. + # The basename of the dll/exe is the last part of the first module. + # Do we need a way to specify the name of the files to be built? + + # Setup the variables our boot script needs. + vars = {"com_module_names" : target.modules} + boot = self.get_boot_script(boot_script) + # and build it + return self.build_executable(target, template, arcname, boot, vars) + + def get_service_names(self, module_name): + # import the script with every side effect :) + __import__(module_name) + mod = sys.modules[module_name] + for name, klass in mod.__dict__.items(): + if hasattr(klass, "_svc_name_"): + break + else: + raise RuntimeError, "No services in module" + deps = () + if hasattr(klass, "_svc_deps_"): + deps = klass._svc_deps_ + return klass.__name__, klass._svc_name_, klass._svc_display_name_, deps + + def build_service(self, target, template, arcname): + # It should be possible to host many modules in a single service - + # but this is yet to be tested. + assert len(target.modules)==1, "We only support one service module" + + cmdline_style = getattr(target, "cmdline_style", "py2exe") + if cmdline_style not in ["py2exe", "pywin32", "custom"]: + raise RuntimeError, "cmdline_handler invalid" + + vars = {"service_module_names" : target.modules, + "cmdline_style": cmdline_style, + } + boot = self.get_boot_script("service") + return self.build_executable(target, template, arcname, boot, vars) + + def build_isapi(self, target, template, arcname): + target_module = os.path.splitext(os.path.basename(target.script))[0] + vars = {"isapi_module_name" : target_module, + } + return self.build_executable(target, template, arcname, None, vars) + + def build_executable(self, target, template, arcname, script, vars={}): + # Build an executable for the target + # template is the exe-stub to use, and arcname is the zipfile + # containing the python modules. + from py2exe_util import add_resource, add_icon + ext = os.path.splitext(template)[1] + exe_base = target.get_dest_base() + exe_path = os.path.join(self.dist_dir, exe_base + ext) + # The user may specify a sub-directory for the exe - that's fine, we + # just specify the parent directory for the .zip + parent_levels = len(os.path.normpath(exe_base).split(os.sep))-1 + lib_leaf = self.lib_dir[len(self.dist_dir)+1:] + relative_arcname = ((".." + os.sep) * parent_levels) + if lib_leaf: relative_arcname += lib_leaf + os.sep + relative_arcname += os.path.basename(arcname) + + src = os.path.join(os.path.dirname(__file__), template) + # We want to force the creation of this file, as otherwise distutils + # will see the earlier time of our 'template' file versus the later + # time of our modified template file, and consider our old file OK. + old_force = self.force + self.force = True + self.copy_file(src, exe_path, preserve_mode=0) + self.force = old_force + + # Make sure the file is writeable... + os.chmod(exe_path, stat.S_IREAD | stat.S_IWRITE) + try: + f = open(exe_path, "a+b") + f.close() + except IOError, why: + print "WARNING: File %s could not be opened - %s" % (exe_path, why) + + # We create a list of code objects, and write it as a marshaled + # stream. The framework code then just exec's these in order. + # First is our common boot script. + boot = self.get_boot_script("common") + boot_code = compile(file(boot, "U").read(), + os.path.abspath(boot), "exec") + code_objects = [boot_code] + if self.bundle_files < 3: + code_objects.append( + compile("import zipextimporter; zipextimporter.install()", + "", "exec")) + for var_name, var_val in vars.items(): + code_objects.append( + compile("%s=%r\n" % (var_name, var_val), var_name, "exec") + ) + if self.custom_boot_script: + code_object = compile(file(self.custom_boot_script, "U").read() + "\n", + os.path.abspath(self.custom_boot_script), "exec") + code_objects.append(code_object) + if script: + code_object = compile(open(script, "U").read() + "\n", + os.path.basename(script), "exec") + code_objects.append(code_object) + code_bytes = marshal.dumps(code_objects) + + if self.distribution.zipfile is None: + relative_arcname = "" + + si = struct.pack("iiii", + 0x78563412, # a magic value, + self.optimize, + self.unbuffered, + len(code_bytes), + ) + relative_arcname + "\000" + + script_bytes = si + code_bytes + '\000\000' + self.announce("add script resource, %d bytes" % len(script_bytes)) + if not self.dry_run: + add_resource(ensure_unicode(exe_path), script_bytes, u"PYTHONSCRIPT", 1, True) + + # add the pythondll as resource, and delete in self.exe_dir + if self.bundle_files < 2 and self.distribution.zipfile is None: + # bundle pythonxy.dll + dll_path = os.path.join(self.bundle_dir, python_dll) + bytes = open(dll_path, "rb").read() + # image, bytes, lpName, lpType + + print "Adding %s as resource to %s" % (python_dll, exe_path) + add_resource(ensure_unicode(exe_path), bytes, + # for some reason, the 3. argument MUST BE UPPER CASE, + # otherwise the resource will not be found. + ensure_unicode(python_dll).upper(), 1, False) + + if self.compressed and self.bundle_files < 3 and self.distribution.zipfile is None: + zlib_file = imp.find_module("zlib")[0] + if zlib_file: + print "Adding zlib.pyd as resource to %s" % exe_path + zlib_bytes = zlib_file.read() + add_resource(ensure_unicode(exe_path), zlib_bytes, + # for some reason, the 3. argument MUST BE UPPER CASE, + # otherwise the resource will not be found. + u"ZLIB.PYD", 1, False) + + # Handle all resources specified by the target + bitmap_resources = getattr(target, "bitmap_resources", []) + for bmp_id, bmp_filename in bitmap_resources: + bmp_data = open(bmp_filename, "rb").read() + # skip the 14 byte bitmap header. + if not self.dry_run: + add_resource(ensure_unicode(exe_path), bmp_data[14:], RT_BITMAP, bmp_id, False) + icon_resources = getattr(target, "icon_resources", []) + for ico_id, ico_filename in icon_resources: + if not self.dry_run: + add_icon(ensure_unicode(exe_path), ensure_unicode(ico_filename), ico_id) + + for res_type, res_id, data in getattr(target, "other_resources", []): + if not self.dry_run: + if isinstance(res_type, basestring): + res_type = ensure_unicode(res_type) + add_resource(ensure_unicode(exe_path), data, res_type, res_id, False) + + typelib = getattr(target, "typelib", None) + if typelib is not None: + data = open(typelib, "rb").read() + add_resource(ensure_unicode(exe_path), data, u"TYPELIB", 1, False) + + self.add_versioninfo(target, exe_path) + + # Hm, this doesn't make sense with normal executables, which are + # already small (around 20 kB). + # + # But it would make sense with static build pythons, but not + # if the zipfile is appended to the exe - it will be too slow + # then (although it is a wonder it works at all in this case). + # + # Maybe it would be faster to use the frozen modules machanism + # instead of the zip-import? +## if self.compressed: +## import gc +## gc.collect() # to close all open files! +## os.system("upx -9 %s" % exe_path) + + if self.distribution.zipfile is None: + zip_data = open(arcname, "rb").read() + open(exe_path, "a+b").write(zip_data) + + return exe_path + + def add_versioninfo(self, target, exe_path): + # Try to build and add a versioninfo resource + + def get(name, md = self.distribution.metadata): + # Try to get an attribute from the target, if not defined + # there, from the distribution's metadata, or None. Note + # that only *some* attributes are allowed by distutils on + # the distribution's metadata: version, description, and + # name. + return getattr(target, name, getattr(md, name, None)) + + version = get("version") + if version is None: + return + + from py2exe.resources.VersionInfo import Version, RT_VERSION, VersionError + version = Version(version, + file_description = get("description"), + comments = get("comments"), + company_name = get("company_name"), + legal_copyright = get("copyright"), + legal_trademarks = get("trademarks"), + original_filename = os.path.basename(exe_path), + product_name = get("name"), + product_version = get("product_version") or version) + try: + bytes = version.resource_bytes() + except VersionError, detail: + self.warn("Version Info will not be included:\n %s" % detail) + return + + from py2exe_util import add_resource + add_resource(ensure_unicode(exe_path), bytes, RT_VERSION, 1, False) + + def patch_python_dll_winver(self, dll_name, new_winver = None): + from py2exe.resources.StringTables import StringTable, RT_STRING + from py2exe_util import add_resource + + new_winver = new_winver or self.distribution.metadata.name or "py2exe" + if self.verbose: + print "setting sys.winver for '%s' to '%s'" % (dll_name, new_winver) + if self.dry_run: + return + + # We preserve the times on the file, so the dependency tracker works. + st = os.stat(dll_name) + # and as the resource functions silently fail if the open fails, + # check it explicitly. + os.chmod(dll_name, stat.S_IREAD | stat.S_IWRITE) + try: + f = open(dll_name, "a+b") + f.close() + except IOError, why: + print "WARNING: File %s could not be opened - %s" % (dll_name, why) + # OK - do it. + s = StringTable() + # 1000 is the resource ID Python loads for its winver. + s.add_string(1000, new_winver) + delete = True + for id, data in s.binary(): + add_resource(ensure_unicode(dll_name), data, RT_STRING, id, delete) + delete = False + + # restore the time. + os.utime(dll_name, (st[stat.ST_ATIME], st[stat.ST_MTIME])) + + def find_dependend_dlls(self, dlls, pypath, dll_excludes): + import py2exe_util + sysdir = py2exe_util.get_sysdir() + windir = py2exe_util.get_windir() + # This is the tail of the path windows uses when looking for dlls + # XXX On Windows NT, the SYSTEM directory is also searched + exedir = os.path.dirname(sys.executable) + syspath = os.environ['PATH'] + loadpath = ';'.join([exedir, sysdir, windir, syspath]) + + # Found by Duncan Booth: + # It may be possible that bin_depends needs extension modules, + # so the loadpath must be extended by our python path. + loadpath = loadpath + ';' + ';'.join(pypath) + + templates = sets.Set() + if self.distribution.console: + templates.add(self.get_console_template()) + if self.distribution.windows: + templates.add(self.get_windows_template()) + if self.distribution.service: + templates.add(self.get_service_template()) + for target in self.distribution.com_server: + if getattr(target, "create_exe", True): + templates.add(self.get_comexe_template()) + if getattr(target, "create_dll", True): + templates.add(self.get_comdll_template()) + + templates = [os.path.join(os.path.dirname(__file__), t) for t in templates] + + # We use Python.exe to track the dependencies of our run stubs ... + images = dlls + templates + + self.announce("Resolving binary dependencies:") + + # we add python.exe (aka sys.executable) to the list of images + # to scan for dependencies, but remove it later again from the + # results list. In this way pythonXY.dll is collected, and + # also the libraries it depends on. + alldlls, warnings, other_depends = \ + bin_depends(loadpath, images + [sys.executable], dll_excludes) + alldlls.remove(sys.executable) + for dll in alldlls: + self.announce(" %s" % dll) + # ... but we don't need the exe stubs run_xxx.exe + for t in templates: + alldlls.remove(t) + + return alldlls, warnings, other_depends + # find_dependend_dlls() + + def get_hidden_imports(self): + # imports done from builtin modules in C code (untrackable by py2exe) + return {"time": ["_strptime"], +## "datetime": ["time"], + "cPickle": ["copy_reg"], + "parser": ["copy_reg"], + "codecs": ["encodings"], + + "cStringIO": ["copy_reg"], + "_sre": ["copy", "string", "sre"], + } + + def parse_mf_results(self, mf): + for name, imports in self.get_hidden_imports().items(): + if name in mf.modules.keys(): + for mod in imports: + mf.import_hook(mod) + + tcl_src_dir = tcl_dst_dir = None + if "Tkinter" in mf.modules.keys(): + import Tkinter + import _tkinter + tk = _tkinter.create() + tcl_dir = tk.call("info", "library") + tcl_src_dir = os.path.split(tcl_dir)[0] + tcl_dst_dir = os.path.join(self.lib_dir, "tcl") + + self.announce("Copying TCL files from %s..." % tcl_src_dir) + self.copy_tree(os.path.join(tcl_src_dir, "tcl%s" % _tkinter.TCL_VERSION), + os.path.join(tcl_dst_dir, "tcl%s" % _tkinter.TCL_VERSION)) + self.copy_tree(os.path.join(tcl_src_dir, "tk%s" % _tkinter.TK_VERSION), + os.path.join(tcl_dst_dir, "tk%s" % _tkinter.TK_VERSION)) + del tk, _tkinter, Tkinter + + # Retrieve modules from modulefinder + py_files = [] + extensions = [] + builtins = [] + + for item in mf.modules.values(): + # There may be __main__ modules (from mf.run_script), but + # we don't need them in the zipfile we build. + if item.__name__ == "__main__": + continue + if self.bundle_files < 3 and item.__name__ in ("pythoncom", "pywintypes"): + # these are handled specially in zipextimporter. + continue + src = item.__file__ + if src: + base, ext = os.path.splitext(src) + suffix = ext + if sys.platform.startswith("win") and ext in [".dll", ".pyd"] \ + and base.endswith("_d"): + suffix = "_d" + ext + + if suffix in _py_suffixes: + py_files.append(item) + elif suffix in _c_suffixes: + extensions.append(item) + if not self.bundle_files < 3: + loader = self.create_loader(item) + if loader: + py_files.append(loader) + else: + raise RuntimeError \ + ("Don't know how to handle '%s'" % repr(src)) + else: + builtins.append(item.__name__) + + # sort on the file names, the output is nicer to read + py_files.sort(lambda a, b: cmp(a.__file__, b.__file__)) + extensions.sort(lambda a, b: cmp(a.__file__, b.__file__)) + builtins.sort() + return py_files, extensions, builtins + + def plat_finalize(self, modules, py_files, extensions, dlls): + # platform specific code for final adjustments to the file + # lists + if sys.platform == "win32": + # pythoncom and pywintypes are imported via LoadLibrary calls, + # help py2exe to include the dlls: + if "pythoncom" in modules.keys(): + import pythoncom + dlls.add(pythoncom.__file__) + if "pywintypes" in modules.keys(): + import pywintypes + dlls.add(pywintypes.__file__) + self.copy_w9xpopen(modules, dlls) + else: + raise DistutilsError, "Platform %s not yet implemented" % sys.platform + + def copy_w9xpopen(self, modules, dlls): + # Using popen requires (on Win9X) the w9xpopen.exe helper executable. + if "os" in modules.keys() or "popen2" in modules.keys(): + if is_debug_build: + fname = os.path.join(os.path.dirname(sys.executable), "w9xpopen_d.exe") + else: + fname = os.path.join(os.path.dirname(sys.executable), "w9xpopen.exe") + # Don't copy w9xpopen.exe if it doesn't exist (64-bit + # Python build, for example) + if os.path.exists(fname): + dlls.add(fname) + + def create_loader(self, item): + # Hm, how to avoid needless recreation of this file? + pathname = os.path.join(self.temp_dir, "%s.py" % item.__name__) + if self.bundle_files > 2: # don't bundle pyds and dlls + # all dlls are copied into the same directory, so modify + # names to include the package name to avoid name + # conflicts and tuck it away for future reference + fname = item.__name__ + os.path.splitext(item.__file__)[1] + item.__pydfile__ = fname + else: + fname = os.path.basename(item.__file__) + + # and what about dry_run? + if self.verbose: + print "creating python loader for extension '%s' (%s -> %s)" % (item.__name__,item.__file__,fname) + + source = LOADER % fname + if not self.dry_run: + open(pathname, "w").write(source) + if self.retain_times: # Restore the times. + st = os.stat(item.__file__) + os.utime(pathname, (st[stat.ST_ATIME], st[stat.ST_MTIME])) + else: + return None + from modulefinder import Module + return Module(item.__name__, pathname) + + def plat_prepare(self): + self.includes.append("warnings") # needed by Python itself + if not self.ascii: + self.packages.append("encodings") + self.includes.append("codecs") + if self.bundle_files < 3: + self.includes.append("zipextimporter") + self.excludes.append("_memimporter") # builtin in run_*.exe and run_*.dll + if self.compressed: + self.includes.append("zlib") + + # os.path will never be found ;-) + self.ignores.append('os.path') + + # update the self.ignores list to ignore platform specific + # modules. + if sys.platform == "win32": + self.ignores += ['AL', + 'Audio_mac', + 'Carbon.File', + 'Carbon.Folder', + 'Carbon.Folders', + 'EasyDialogs', + 'MacOS', + 'Mailman', + 'SOCKS', + 'SUNAUDIODEV', + '_dummy_threading', + '_emx_link', + '_xmlplus', + '_xmlrpclib', + 'al', + 'bundlebuilder', + 'ce', + 'cl', + 'dbm', + 'dos', + 'fcntl', + 'gestalt', + 'grp', + 'ic', + 'java.lang', + 'mac', + 'macfs', + 'macostools', + 'mkcwproject', + 'org.python.core', + 'os.path', + 'os2', + 'poll', + 'posix', + 'pwd', + 'readline', + 'riscos', + 'riscosenviron', + 'riscospath', + 'rourl2path', + 'sgi', + 'sgmlop', + 'sunaudiodev', + 'termios', + 'vms_lib'] + # special dlls which must be copied to the exe_dir, not the lib_dir + self.dlls_in_exedir = [python_dll, + "w9xpopen%s.exe" % (is_debug_build and "_d" or ""), + "msvcr71%s.dll" % (is_debug_build and "d" or "")] + else: + raise DistutilsError, "Platform %s not yet implemented" % sys.platform + + def find_needed_modules(self, mf, files, modules): + # feed Modulefinder with everything, and return it. + for mod in modules: + mf.import_hook(mod) + + for path in files: + mf.run_script(path) + + mf.run_script(self.get_boot_script("common")) + + if self.distribution.com_server: + mf.run_script(self.get_boot_script("com_servers")) + + if self.distribution.ctypes_com_server: + mf.run_script(self.get_boot_script("ctypes_com_server")) + + if self.distribution.service: + mf.run_script(self.get_boot_script("service")) + + if self.custom_boot_script: + mf.run_script(self.custom_boot_script) + + for mod in self.includes: + if mod[-2:] == '.*': + mf.import_hook(mod[:-2], None, ['*']) + else: + try: + mf.import_hook(mod) + except ImportError: + for exc in self.excludes: + if mod.startswith(exc): + print >> sys.stderr, "Ignoring ImportError for %r" % mod + break + else: + raise + + for f in self.packages: + def visit(arg, dirname, names): + if '__init__.py' in names: + arg.append(dirname) + + # Try to find the package using ModuleFinders's method to + # allow for modulefinder.AddPackagePath interactions + mf.import_hook(f) + + # If modulefinder has seen a reference to the package, then + # we prefer to believe that (imp_find_module doesn't seem to locate + # sub-packages) + if f in mf.modules: + module = mf.modules[f] + if module.__path__ is None: + # it's a module, not a package, so paths contains just the + # file entry + paths = [module.__file__] + else: + # it is a package because __path__ is available. __path__ + # is actually a list of paths that are searched to import + # sub-modules and sub-packages + paths = module.__path__ + else: + # Find path of package + try: + paths = [imp_find_module(f)[1]] + except ImportError: + self.warn("No package named %s" % f) + continue + + packages = [] + for path in paths: + # walk the path to find subdirs containing __init__.py files + os.path.walk(path, visit, packages) + + # scan the results (directory of __init__.py files) + # first trim the path (of the head package), + # then convert directory name in package name, + # finally push into modulefinder. + for p in packages: + if p.startswith(path): + package = f + '.' + p[len(path)+1:].replace('\\', '.') + mf.import_hook(package, None, ["*"]) + + return mf + + def make_lib_archive(self, zip_filename, base_dir, files, + verbose=0, dry_run=0): + from distutils.dir_util import mkpath + if not self.skip_archive: + # Like distutils "make_archive", but we can specify the files + # to include, and the compression to use - default is + # ZIP_STORED to keep the runtime performance up. Also, we + # don't append '.zip' to the filename. + mkpath(os.path.dirname(zip_filename), dry_run=dry_run) + + if self.compressed: + compression = zipfile.ZIP_DEFLATED + else: + compression = zipfile.ZIP_STORED + + if not dry_run: + z = zipfile.ZipFile(zip_filename, "w", + compression=compression) + for f in files: + z.write(os.path.join(base_dir, f), f) + z.close() + + return zip_filename + else: + # Don't really produce an archive, just copy the files. + from distutils.file_util import copy_file + + destFolder = os.path.dirname(zip_filename) + + for f in files: + d = os.path.dirname(f) + if d: + mkpath(os.path.join(destFolder, d), verbose=verbose, dry_run=dry_run) + copy_file( + os.path.join(base_dir, f), + os.path.join(destFolder, f), + preserve_mode=0, + verbose=verbose, + dry_run=dry_run + ) + return '.' + + +################################################################ + +class FileSet: + # A case insensitive but case preserving set of files + def __init__(self, iterable=None): + self._dict = {} + if iterable is not None: + for arg in iterable: + self.add(arg) + + def __repr__(self): + return "" % (self._dict.values(), id(self)) + + def add(self, fname): + self._dict[fname.upper()] = fname + + def remove(self, fname): + del self._dict[fname.upper()] + + def __contains__(self, fname): + return fname.upper() in self._dict.keys() + + def __getitem__(self, index): + key = self._dict.keys()[index] + return self._dict[key] + + def __len__(self): + return len(self._dict) + + def copy(self): + res = FileSet() + res._dict.update(self._dict) + return res + +# class FileSet() + +def bin_depends(path, images, excluded_dlls): + import py2exe_util + warnings = FileSet() + images = FileSet(images) + dependents = FileSet() + others = FileSet() + while images: + for image in images.copy(): + images.remove(image) + if not image in dependents: + dependents.add(image) + abs_image = os.path.abspath(image) + loadpath = os.path.dirname(abs_image) + ';' + path + for result in py2exe_util.depends(image, loadpath).items(): + try: + dll, uses_import_module = result + if os.path.basename(dll).lower() not in excluded_dlls: + if isSystemDLL(dll): + others.add(dll) + continue + if dll not in images and dll not in dependents: + images.add(dll) + if uses_import_module: + warnings.add(dll) + except Exception: + print >> sys.stderr, 'Error finding dependency for %s' % image + raise + return dependents, warnings, others + +# DLLs to be excluded +# XXX This list is NOT complete (it cannot be) +# Note: ALL ENTRIES MUST BE IN LOWER CASE! +EXCLUDED_DLLS = ( + "advapi32.dll", + "comctl32.dll", + "comdlg32.dll", + "crtdll.dll", + "gdi32.dll", + "glu32.dll", + "opengl32.dll", + "imm32.dll", + "kernel32.dll", + "mfc42.dll", + "msvcirt.dll", + "msvcrt.dll", + "msvcrtd.dll", + "ntdll.dll", + "odbc32.dll", + "ole32.dll", + "oleaut32.dll", + "rpcrt4.dll", + "shell32.dll", + "shlwapi.dll", + "user32.dll", + "version.dll", + "winmm.dll", + "winspool.drv", + "ws2_32.dll", + "ws2help.dll", + "wsock32.dll", + "netapi32.dll", + + "gdiplus.dll", + + "msvcr90.dll", + "msvcp90.dll", + ) + +# XXX Perhaps it would be better to assume dlls from the systemdir are system dlls, +# and make some exceptions for known dlls, like msvcr71, pythonXY.dll, and so on? +def isSystemDLL(pathname): + if os.path.basename(pathname).lower() in ("msvcr71.dll", "msvcr71d.dll"): + return 0 + if os.path.basename(pathname).lower() in EXCLUDED_DLLS: + return 1 + # How can we determine whether a dll is a 'SYSTEM DLL'? + # Is it sufficient to use the Image Load Address? + import struct + file = open(pathname, "rb") + if file.read(2) != "MZ": + raise Exception, "Seems not to be an exe-file" + file.seek(0x3C) + pe_ofs = struct.unpack("i", file.read(4))[0] + file.seek(pe_ofs) + if file.read(4) != "PE\000\000": + raise Exception, ("Seems not to be an exe-file", pathname) + file.read(20 + 28) # COFF File Header, offset of ImageBase in Optional Header + imagebase = struct.unpack("I", file.read(4))[0] + return not (imagebase < 0x70000000) + +def byte_compile(py_files, optimize=0, force=0, + target_dir=None, verbose=1, dry_run=0, + direct=None): + + if direct is None: + direct = (__debug__ and optimize == 0) + + # "Indirect" byte-compilation: write a temporary script and then + # run it with the appropriate flags. + if not direct: + from tempfile import mktemp + from distutils.util import execute + script_name = mktemp(".py") + if verbose: + print "writing byte-compilation script '%s'" % script_name + if not dry_run: + script = open(script_name, "w") + script.write("""\ +from py2exe.build_exe import byte_compile +from modulefinder import Module +files = [ +""") + + for f in py_files: + script.write("Module(%s, %s, %s),\n" % \ + (`f.__name__`, `f.__file__`, `f.__path__`)) + script.write("]\n") + script.write(""" +byte_compile(files, optimize=%s, force=%s, + target_dir=%s, + verbose=%s, dry_run=0, + direct=1) +""" % (`optimize`, `force`, `target_dir`, `verbose`)) + + script.close() + + cmd = [sys.executable, script_name] + if optimize == 1: + cmd.insert(1, "-O") + elif optimize == 2: + cmd.insert(1, "-OO") + spawn(cmd, verbose=verbose, dry_run=dry_run) + execute(os.remove, (script_name,), "removing %s" % script_name, + verbose=verbose, dry_run=dry_run) + + + else: + from py_compile import compile + from distutils.dir_util import mkpath + from distutils.dep_util import newer + from distutils.file_util import copy_file + + for file in py_files: + # Terminology from the py_compile module: + # cfile - byte-compiled file + # dfile - purported source filename (same as 'file' by default) + cfile = file.__name__.replace('.', '\\') + + if file.__path__: + dfile = cfile + '\\__init__.py' + (__debug__ and 'c' or 'o') + else: + dfile = cfile + '.py' + (__debug__ and 'c' or 'o') + if target_dir: + cfile = os.path.join(target_dir, dfile) + + if force or newer(file.__file__, cfile): + if verbose: + print "byte-compiling %s to %s" % (file.__file__, dfile) + if not dry_run: + mkpath(os.path.dirname(cfile)) + suffix = os.path.splitext(file.__file__)[1] + if suffix in (".py", ".pyw"): + compile(file.__file__, cfile, dfile) + elif suffix in _py_suffixes: + # Minor problem: This will happily copy a file + # .pyo to .pyc or .pyc to + # .pyo, but it does seem to work. + copy_file(file.__file__, cfile, preserve_mode=0) + else: + raise RuntimeError \ + ("Don't know how to handle %r" % file.__file__) + else: + if verbose: + print "skipping byte-compilation of %s to %s" % \ + (file.__file__, dfile) + compiled_files = [] + for file in py_files: + cfile = file.__name__.replace('.', '\\') + + if file.__path__: + dfile = cfile + '\\__init__.py' + (optimize and 'o' or 'c') + else: + dfile = cfile + '.py' + (optimize and 'o' or 'c') + compiled_files.append(dfile) + return compiled_files + +# byte_compile() + +# win32com makepy helper. +def collect_win32com_genpy(path, typelibs, verbose=0, dry_run=0): + import win32com + from win32com.client import gencache, makepy + from distutils.file_util import copy_file + + old_gen_path = win32com.__gen_path__ + num = 0 + try: + win32com.__gen_path__ = path + win32com.gen_py.__path__ = [path] + gencache.__init__() + for info in typelibs: + guid, lcid, major, minor = info[:4] + # They may provide an input filename in the tuple - in which case + # they will have pre-generated it on a machine with the typelibs + # installed, and just want us to include it. + fname_in = None + if len(info) > 4: + fname_in = info[4] + if fname_in is not None: + base = gencache.GetGeneratedFileName(guid, lcid, major, minor) + fname_out = os.path.join(path, base) + ".py" + copy_file(fname_in, fname_out, verbose=verbose, dry_run=dry_run) + num += 1 + # That's all we gotta do! + continue + + # It seems bForDemand=True generates code which is missing + # at least sometimes an import of DispatchBaseClass. + # Until this is resolved, set it to false. + # What's the purpose of bForDemand=True? Thomas + # bForDemand is supposed to only generate stubs when each + # individual object is referenced. A side-effect of that is + # that each object gets its own source file. The intent of + # this code was to set bForDemand=True, meaning we get the + # 'file per object' behaviour, but then explicitly walk all + # children forcing them to be built - so the entire object model + # is included, but not in a huge .pyc. + # I'm not sure why its not working :) I'll debug later. + # bForDemand=False isn't really important here - the overhead for + # monolithic typelib stubs is in the compilation, not the loading + # of an existing .pyc. Mark. +## makepy.GenerateFromTypeLibSpec(info, bForDemand = True) + tlb_info = (guid, lcid, major, minor) + makepy.GenerateFromTypeLibSpec(tlb_info, bForDemand = False) + # Now get the module, and build all sub-modules. + mod = gencache.GetModuleForTypelib(*tlb_info) + for clsid, name in mod.CLSIDToPackageMap.items(): + try: + gencache.GetModuleForCLSID(clsid) + num += 1 + #print "", name + except ImportError: + pass + return num + finally: + # restore win32com, just in case. + win32com.__gen_path__ = old_gen_path + win32com.gen_py.__path__ = [old_gen_path] + gencache.__init__() + +# utilities hacked from distutils.dir_util + +def _chmod(file): + os.chmod(file, 0777) + +# Helper for force_remove_tree() +def _build_cmdtuple(path, cmdtuples): + for f in os.listdir(path): + real_f = os.path.join(path,f) + if os.path.isdir(real_f) and not os.path.islink(real_f): + _build_cmdtuple(real_f, cmdtuples) + else: + cmdtuples.append((_chmod, real_f)) + cmdtuples.append((os.remove, real_f)) + cmdtuples.append((os.rmdir, path)) + +def force_remove_tree (directory, verbose=0, dry_run=0): + """Recursively remove an entire directory tree. Any errors are ignored + (apart from being reported to stdout if 'verbose' is true). + """ + import distutils + from distutils.util import grok_environment_error + _path_created = distutils.dir_util._path_created + + if verbose: + print "removing '%s' (and everything under it)" % directory + if dry_run: + return + cmdtuples = [] + _build_cmdtuple(directory, cmdtuples) + for cmd in cmdtuples: + try: + cmd[0](cmd[1]) + # remove dir from cache if it's already there + abspath = os.path.abspath(cmd[1]) + if _path_created.has_key(abspath): + del _path_created[abspath] + except (IOError, OSError), exc: + if verbose: + print grok_environment_error( + exc, "error removing %s: " % directory) diff --git a/installer/py2exe/py2exe/mf.py b/installer/py2exe/py2exe/mf.py new file mode 100644 index 0000000..5c094e8 --- /dev/null +++ b/installer/py2exe/py2exe/mf.py @@ -0,0 +1,811 @@ +"""Find modules used by a script, using introspection.""" +# This module should be kept compatible with Python 2.2, see PEP 291. + +from __future__ import generators +import dis +import imp +import marshal +import os +import sys +import types +import struct + +if hasattr(sys.__stdout__, "newlines"): + READ_MODE = "U" # universal line endings +else: + # remain compatible with Python < 2.3 + READ_MODE = "r" + +LOAD_CONST = chr(dis.opname.index('LOAD_CONST')) +IMPORT_NAME = chr(dis.opname.index('IMPORT_NAME')) +STORE_NAME = chr(dis.opname.index('STORE_NAME')) +STORE_GLOBAL = chr(dis.opname.index('STORE_GLOBAL')) +STORE_OPS = [STORE_NAME, STORE_GLOBAL] +HAVE_ARGUMENT = chr(dis.HAVE_ARGUMENT) + +# !!! NOTE BEFORE INCLUDING IN PYTHON DISTRIBUTION !!! +# To clear up issues caused by the duplication of data structures between +# the real Python modulefinder and this duplicate version, packagePathMap +# and replacePackageMap are imported from the actual modulefinder. This +# should be changed back to the assigments that are commented out below. +# There are also py2exe specific pieces at the bottom of this file. + + +# Modulefinder does a good job at simulating Python's, but it can not +# handle __path__ modifications packages make at runtime. Therefore there +# is a mechanism whereby you can register extra paths in this map for a +# package, and it will be honored. + +# Note this is a mapping is lists of paths. +#~ packagePathMap = {} +from modulefinder import packagePathMap + +# A Public interface +def AddPackagePath(packagename, path): + paths = packagePathMap.get(packagename, []) + paths.append(path) + packagePathMap[packagename] = paths + +#~ replacePackageMap = {} +from modulefinder import replacePackageMap + +# This ReplacePackage mechanism allows modulefinder to work around the +# way the _xmlplus package injects itself under the name "xml" into +# sys.modules at runtime by calling ReplacePackage("_xmlplus", "xml") +# before running ModuleFinder. + +def ReplacePackage(oldname, newname): + replacePackageMap[oldname] = newname + + +class Module: + + def __init__(self, name, file=None, path=None): + self.__name__ = name + self.__file__ = file + self.__path__ = path + self.__code__ = None + # The set of global names that are assigned to in the module. + # This includes those names imported through starimports of + # Python modules. + self.globalnames = {} + # The set of starimports this module did that could not be + # resolved, ie. a starimport from a non-Python module. + self.starimports = {} + + def __repr__(self): + s = "Module(%r" % (self.__name__,) + if self.__file__ is not None: + s = s + ", %r" % (self.__file__,) + if self.__path__ is not None: + s = s + ", %r" % (self.__path__,) + s = s + ")" + return s + +class ModuleFinder: + + def __init__(self, path=None, debug=0, excludes=[], replace_paths=[]): + if path is None: + path = sys.path + self.path = path + self.modules = {} + self.badmodules = {} + self.debug = 1#sys.maxint + self.indent = 0 + self.excludes = excludes + self.replace_paths = replace_paths + self.processed_paths = [] # Used in debugging only + + def msg(self, level, str, *args): + if level <= self.debug: + for i in range(self.indent): + print " ", + print str, + for arg in args: + print repr(arg), + print + + def msgin(self, *args): + level = args[0] + if level <= self.debug: + self.indent = self.indent + 1 + self.msg(*args) + + def msgout(self, *args): + level = args[0] + if level <= self.debug: + self.indent = self.indent - 1 + self.msg(*args) + + def run_script(self, pathname): + self.msg(2, "run_script", pathname) + fp = open(pathname, READ_MODE) + stuff = ("", "r", imp.PY_SOURCE) + self.load_module('__main__', fp, pathname, stuff) + + def load_file(self, pathname): + dir, name = os.path.split(pathname) + name, ext = os.path.splitext(name) + fp = open(pathname, READ_MODE) + stuff = (ext, "r", imp.PY_SOURCE) + self.load_module(name, fp, pathname, stuff) + + def import_hook(self, name, caller=None, fromlist=None, level=-1): + self.msg(3, "import_hook", name, caller, fromlist, level) + parent = self.determine_parent(caller, level=level) + q, tail = self.find_head_package(parent, name) + m = self.load_tail(q, tail) + if not fromlist: + return q + if m.__path__: + self.ensure_fromlist(m, fromlist) + return None + + def determine_parent(self, caller, level=-1): + self.msgin(4, "determine_parent", caller, level) + if not caller or level == 0: + self.msgout(4, "determine_parent -> None") + return None + pname = caller.__name__ + if level >= 1: # relative import + if caller.__path__: + level -= 1 + if level == 0: + parent = self.modules[pname] + assert parent is caller + self.msgout(4, "determine_parent ->", parent) + return parent + if pname.count(".") < level: + raise ImportError, "relative importpath too deep" + pname = ".".join(pname.split(".")[:-level]) + parent = self.modules[pname] + self.msgout(4, "determine_parent ->", parent) + return parent + if caller.__path__: + parent = self.modules[pname] + assert caller is parent + self.msgout(4, "determine_parent ->", parent) + return parent + if '.' in pname: + i = pname.rfind('.') + pname = pname[:i] + parent = self.modules[pname] + assert parent.__name__ == pname + self.msgout(4, "determine_parent ->", parent) + return parent + self.msgout(4, "determine_parent -> None") + return None + + def find_head_package(self, parent, name): + self.msgin(4, "find_head_package", parent, name) + if '.' in name: + i = name.find('.') + head = name[:i] + tail = name[i+1:] + else: + head = name + tail = "" + if parent: + qname = "%s.%s" % (parent.__name__, head) + else: + qname = head + q = self.import_module(head, qname, parent) + if q: + self.msgout(4, "find_head_package ->", (q, tail)) + return q, tail + if parent: + qname = head + parent = None + q = self.import_module(head, qname, parent) + if q: + self.msgout(4, "find_head_package ->", (q, tail)) + return q, tail + self.msgout(4, "raise ImportError: No module named", qname) + raise ImportError, "No module named " + qname + + def load_tail(self, q, tail): + self.msgin(4, "load_tail", q, tail) + m = q + while tail: + i = tail.find('.') + if i < 0: i = len(tail) + head, tail = tail[:i], tail[i+1:] + mname = "%s.%s" % (m.__name__, head) + m = self.import_module(head, mname, m) + if not m: + self.msgout(4, "raise ImportError: No module named", mname) + raise ImportError, "No module named " + mname + self.msgout(4, "load_tail ->", m) + return m + + def ensure_fromlist(self, m, fromlist, recursive=0): + self.msg(4, "ensure_fromlist", m, fromlist, recursive) + for sub in fromlist: + if sub == "*": + if not recursive: + all = self.find_all_submodules(m) + if all: + self.ensure_fromlist(m, all, 1) + elif not hasattr(m, sub): + subname = "%s.%s" % (m.__name__, sub) + submod = self.import_module(sub, subname, m) + if not submod: + raise ImportError, "No module named " + subname + + def find_all_submodules(self, m): + if not m.__path__: + return + modules = {} + # 'suffixes' used to be a list hardcoded to [".py", ".pyc", ".pyo"]. + # But we must also collect Python extension modules - although + # we cannot separate normal dlls from Python extensions. + suffixes = [] + for triple in imp.get_suffixes(): + suffixes.append(triple[0]) + for dir in m.__path__: + try: + names = os.listdir(dir) + except os.error: + self.msg(2, "can't list directory", dir) + continue + for name in names: + mod = None + for suff in suffixes: + n = len(suff) + if name[-n:] == suff: + mod = name[:-n] + break + if mod and mod != "__init__": + modules[mod] = mod + return modules.keys() + + def import_module(self, partname, fqname, parent): + self.msgin(3, "import_module", partname, fqname, parent) + try: + m = self.modules[fqname] + except KeyError: + pass + else: + self.msgout(3, "import_module ->", m) + return m + if self.badmodules.has_key(fqname): + self.msgout(3, "import_module -> None") + return None + if parent and parent.__path__ is None: + self.msgout(3, "import_module -> None") + return None + try: + fp, pathname, stuff = self.find_module(partname, + parent and parent.__path__, parent) + except ImportError: + self.msgout(3, "import_module ->", None) + return None + try: + m = self.load_module(fqname, fp, pathname, stuff) + finally: + if fp: fp.close() + if parent: + setattr(parent, partname, m) + self.msgout(3, "import_module ->", m) + return m + + def load_module(self, fqname, fp, pathname, (suffix, mode, type)): + self.msgin(2, "load_module", fqname, fp and "fp", pathname) + if type == imp.PKG_DIRECTORY: + m = self.load_package(fqname, pathname) + self.msgout(2, "load_module ->", m) + return m + if type == imp.PY_SOURCE: + co = compile(fp.read()+'\n', pathname, 'exec') + elif type == imp.PY_COMPILED: + if fp.read(4) != imp.get_magic(): + self.msgout(2, "raise ImportError: Bad magic number", pathname) + raise ImportError, "Bad magic number in %s" % pathname + fp.read(4) + co = marshal.load(fp) + else: + co = None + m = self.add_module(fqname) + m.__file__ = pathname + if co: + if self.replace_paths: + co = self.replace_paths_in_code(co) + m.__code__ = co + self.scan_code(co, m) + self.msgout(2, "load_module ->", m) + return m + + def _add_badmodule(self, name, caller): + if name not in self.badmodules: + self.badmodules[name] = {} + + if caller is None: + print 'Skipping _add_badmodule for %r because caller was None' % name + return + + self.badmodules[name][caller.__name__] = 1 + + def _safe_import_hook(self, name, caller, fromlist, level=-1): + # wrapper for self.import_hook() that won't raise ImportError + if name in self.badmodules: + self._add_badmodule(name, caller) + return + try: + self.import_hook(name, caller, level=level) + except ImportError, msg: + self.msg(2, "ImportError:", str(msg)) + self._add_badmodule(name, caller) + else: + if fromlist: + for sub in fromlist: + if sub in self.badmodules: + self._add_badmodule(sub, caller) + continue + try: + self.import_hook(name, caller, [sub], level=level) + except ImportError, msg: + self.msg(2, "ImportError:", str(msg)) + fullname = name + "." + sub + self._add_badmodule(fullname, caller) + + def scan_opcodes(self, co, + unpack = struct.unpack): + # Scan the code, and yield 'interesting' opcode combinations + # Version for Python 2.4 and older + code = co.co_code + names = co.co_names + consts = co.co_consts + while code: + c = code[0] + if c in STORE_OPS: + oparg, = unpack('= HAVE_ARGUMENT: + code = code[3:] + else: + code = code[1:] + + def scan_opcodes_25(self, co, + unpack = struct.unpack): + # Scan the code, and yield 'interesting' opcode combinations + # Python 2.5 version (has absolute and relative imports) + code = co.co_code + names = co.co_names + consts = co.co_consts + LOAD_LOAD_AND_IMPORT = LOAD_CONST + LOAD_CONST + IMPORT_NAME + while code: + c = code[0] + if c in STORE_OPS: + oparg, = unpack('= HAVE_ARGUMENT: + code = code[3:] + else: + code = code[1:] + + def scan_code(self, co, m): + code = co.co_code + if sys.version_info >= (2, 5): + scanner = self.scan_opcodes_25 + else: + scanner = self.scan_opcodes + for what, args in scanner(co): + if what == "store": + name, = args + m.globalnames[name] = 1 + elif what in ("import", "absolute_import"): + fromlist, name = args + have_star = 0 + if fromlist is not None: + if "*" in fromlist: + have_star = 1 + fromlist = [f for f in fromlist if f != "*"] + if what == "absolute_import": level = 0 + else: level = -1 + self._safe_import_hook(name, m, fromlist, level=level) + if have_star: + # We've encountered an "import *". If it is a Python module, + # the code has already been parsed and we can suck out the + # global names. + mm = None + if m.__path__: + # At this point we don't know whether 'name' is a + # submodule of 'm' or a global module. Let's just try + # the full name first. + mm = self.modules.get(m.__name__ + "." + name) + if mm is None: + mm = self.modules.get(name) + if mm is not None: + m.globalnames.update(mm.globalnames) + m.starimports.update(mm.starimports) + if mm.__code__ is None: + m.starimports[name] = 1 + else: + m.starimports[name] = 1 + elif what == "relative_import": + level, fromlist, name = args + if name: + self._safe_import_hook(name, m, fromlist, level=level) + else: + parent = self.determine_parent(m, level=level) + self._safe_import_hook(parent.__name__, None, fromlist, level=0) + else: + # We don't expect anything else from the generator. + raise RuntimeError(what) + + for c in co.co_consts: + if isinstance(c, type(co)): + self.scan_code(c, m) + + def load_package(self, fqname, pathname): + self.msgin(2, "load_package", fqname, pathname) + newname = replacePackageMap.get(fqname) + if newname: + fqname = newname + m = self.add_module(fqname) + m.__file__ = pathname + m.__path__ = [pathname] + + # As per comment at top of file, simulate runtime __path__ additions. + m.__path__ = m.__path__ + packagePathMap.get(fqname, []) + + fp, buf, stuff = self.find_module("__init__", m.__path__) + self.load_module(fqname, fp, buf, stuff) + self.msgout(2, "load_package ->", m) + return m + + def add_module(self, fqname): + if self.modules.has_key(fqname): + return self.modules[fqname] + self.modules[fqname] = m = Module(fqname) + return m + + def find_module(self, name, path, parent=None): + if parent is not None: + # assert path is not None + fullname = parent.__name__+'.'+name + else: + fullname = name + if fullname in self.excludes: + self.msgout(3, "find_module -> Excluded", fullname) + raise ImportError, name + + if path is None: + if name in sys.builtin_module_names: + return (None, None, ("", "", imp.C_BUILTIN)) + + path = self.path + return imp.find_module(name, path) + + def report(self): + """Print a report to stdout, listing the found modules with their + paths, as well as modules that are missing, or seem to be missing. + """ + print + print " %-25s %s" % ("Name", "File") + print " %-25s %s" % ("----", "----") + # Print modules found + keys = self.modules.keys() + keys.sort() + for key in keys: + m = self.modules[key] + if m.__path__: + print "P", + else: + print "m", + print "%-25s" % key, m.__file__ or "" + + # Print missing modules + missing, maybe = self.any_missing_maybe() + if missing: + print + print "Missing modules:" + for name in missing: + mods = self.badmodules[name].keys() + mods.sort() + print "?", name, "imported from", ', '.join(mods) + # Print modules that may be missing, but then again, maybe not... + if maybe: + print + print "Submodules thay appear to be missing, but could also be", + print "global names in the parent package:" + for name in maybe: + mods = self.badmodules[name].keys() + mods.sort() + print "?", name, "imported from", ', '.join(mods) + + def any_missing(self): + """Return a list of modules that appear to be missing. Use + any_missing_maybe() if you want to know which modules are + certain to be missing, and which *may* be missing. + """ + missing, maybe = self.any_missing_maybe() + return missing + maybe + + def any_missing_maybe(self): + """Return two lists, one with modules that are certainly missing + and one with modules that *may* be missing. The latter names could + either be submodules *or* just global names in the package. + + The reason it can't always be determined is that it's impossible to + tell which names are imported when "from module import *" is done + with an extension module, short of actually importing it. + """ + missing = [] + maybe = [] + for name in self.badmodules: + if name in self.excludes: + continue + i = name.rfind(".") + if i < 0: + missing.append(name) + continue + subname = name[i+1:] + pkgname = name[:i] + pkg = self.modules.get(pkgname) + if pkg is not None: + if pkgname in self.badmodules[name]: + # The package tried to import this module itself and + # failed. It's definitely missing. + missing.append(name) + elif subname in pkg.globalnames: + # It's a global in the package: definitely not missing. + pass + elif pkg.starimports: + # It could be missing, but the package did an "import *" + # from a non-Python module, so we simply can't be sure. + maybe.append(name) + else: + # It's not a global in the package, the package didn't + # do funny star imports, it's very likely to be missing. + # The symbol could be inserted into the package from the + # outside, but since that's not good style we simply list + # it missing. + missing.append(name) + else: + missing.append(name) + missing.sort() + maybe.sort() + return missing, maybe + + def replace_paths_in_code(self, co): + new_filename = original_filename = os.path.normpath(co.co_filename) + for f, r in self.replace_paths: + if original_filename.startswith(f): + new_filename = r + original_filename[len(f):] + break + + if self.debug and original_filename not in self.processed_paths: + if new_filename != original_filename: + self.msgout(2, "co_filename %r changed to %r" \ + % (original_filename,new_filename,)) + else: + self.msgout(2, "co_filename %r remains unchanged" \ + % (original_filename,)) + self.processed_paths.append(original_filename) + + consts = list(co.co_consts) + for i in range(len(consts)): + if isinstance(consts[i], type(co)): + consts[i] = self.replace_paths_in_code(consts[i]) + + return types.CodeType(co.co_argcount, co.co_nlocals, co.co_stacksize, + co.co_flags, co.co_code, tuple(consts), co.co_names, + co.co_varnames, new_filename, co.co_name, + co.co_firstlineno, co.co_lnotab, + co.co_freevars, co.co_cellvars) + + +def test(): + # Parse command line + import getopt + try: + opts, args = getopt.getopt(sys.argv[1:], "dmp:qx:") + except getopt.error, msg: + print msg + return + + # Process options + debug = 1 + domods = 0 + addpath = [] + exclude = [] + for o, a in opts: + if o == '-d': + debug = debug + 1 + if o == '-m': + domods = 1 + if o == '-p': + addpath = addpath + a.split(os.pathsep) + if o == '-q': + debug = 0 + if o == '-x': + exclude.append(a) + + # Provide default arguments + if not args: + script = "hello.py" + else: + script = args[0] + + # Set the path based on sys.path and the script directory + path = sys.path[:] + path[0] = os.path.dirname(script) + path = addpath + path + if debug > 1: + print "path:" + for item in path: + print " ", repr(item) + + # Create the module finder and turn its crank + mf = ModuleFinder(path, debug, exclude) + for arg in args[1:]: + if arg == '-m': + domods = 1 + continue + if domods: + if arg[-2:] == '.*': + mf.import_hook(arg[:-2], None, ["*"]) + else: + mf.import_hook(arg) + else: + mf.load_file(arg) + mf.run_script(script) + mf.report() + return mf # for -i debugging + + +if __name__ == '__main__': + try: + mf = test() + except KeyboardInterrupt: + print "\n[interrupt]" + + + +# py2exe specific portion - this should be removed before inclusion in the +# Python distribution + +import tempfile +import urllib + +try: + set +except NameError: + from sets import Set as set + +Base = ModuleFinder +del ModuleFinder + +# Much inspired by Toby Dickenson's code: +# http://www.tarind.com/depgraph.html +class ModuleFinder(Base): + def __init__(self, *args, **kw): + self._depgraph = {} + self._types = {} + self._last_caller = None + self._scripts = set() + Base.__init__(self, *args, **kw) + + def run_script(self, pathname): + # Scripts always end in the __main__ module, but we possibly + # have more than one script in py2exe, so we want to keep + # *all* the pathnames. + self._scripts.add(pathname) + Base.run_script(self, pathname) + + def import_hook(self, name, caller=None, fromlist=None, level=-1): + old_last_caller = self._last_caller + try: + self._last_caller = caller + return Base.import_hook(self,name,caller,fromlist,level) + finally: + self._last_caller = old_last_caller + + def import_module(self,partnam,fqname,parent): + r = Base.import_module(self,partnam,fqname,parent) + if r is not None and self._last_caller: + self._depgraph.setdefault(self._last_caller.__name__, set()).add(r.__name__) + return r + + def load_module(self, fqname, fp, pathname, (suffix, mode, typ)): + r = Base.load_module(self, fqname, fp, pathname, (suffix, mode, typ)) + if r is not None: + self._types[r.__name__] = typ + return r + + def create_xref(self): + # this code probably needs cleanup + depgraph = {} + importedby = {} + for name, value in self._depgraph.items(): + depgraph[name] = list(value) + for needs in value: + importedby.setdefault(needs, set()).add(name) + + names = self._types.keys() + names.sort() + + fd, htmlfile = tempfile.mkstemp(".html") + ofi = open(htmlfile, "w") + os.close(fd) + print >> ofi, "py2exe cross reference for %s" % sys.argv[0] + + print >> ofi, "

    py2exe cross reference for %s

    " % sys.argv[0] + + for name in names: + if self._types[name] in (imp.PY_SOURCE, imp.PKG_DIRECTORY): + print >> ofi, '%s' % (name, name) + if name == "__main__": + for fname in self._scripts: + path = urllib.pathname2url(os.path.abspath(fname)) + print >> ofi, '%s ' \ + % (path, fname) + print >> ofi, '
    imports:' + else: + fname = urllib.pathname2url(self.modules[name].__file__) + print >> ofi, '%s
    imports:' \ + % (fname, self.modules[name].__file__) + else: + fname = self.modules[name].__file__ + if fname: + print >> ofi, '%s %s
    imports:' \ + % (name, name, fname) + else: + print >> ofi, '%s %s
    imports:' \ + % (name, name, TYPES[self._types[name]]) + + if name in depgraph: + needs = depgraph[name] + for n in needs: + print >> ofi, '%s ' % (n, n) + print >> ofi, "
    \n" + + print >> ofi, 'imported by:' + if name in importedby: + for i in importedby[name]: + print >> ofi, '%s ' % (i, i) + + print >> ofi, "
    \n" + + print >> ofi, "
    \n" + + print >> ofi, "" + ofi.close() + os.startfile(htmlfile) + # how long does it take to start the browser? + import threading + threading.Timer(5, os.remove, args=[htmlfile]) + + +TYPES = {imp.C_BUILTIN: "(builtin module)", + imp.C_EXTENSION: "extension module", + imp.IMP_HOOK: "IMP_HOOK", + imp.PKG_DIRECTORY: "package directory", + imp.PY_CODERESOURCE: "PY_CODERESOURCE", + imp.PY_COMPILED: "compiled python module", + imp.PY_FROZEN: "frozen module", + imp.PY_RESOURCE: "PY_RESOURCE", + imp.PY_SOURCE: "python module", + imp.SEARCH_ERROR: "SEARCH_ERROR" + } diff --git a/installer/py2exe/py2exe/resources/StringTables.py b/installer/py2exe/py2exe/resources/StringTables.py new file mode 100644 index 0000000..5c04bc8 --- /dev/null +++ b/installer/py2exe/py2exe/resources/StringTables.py @@ -0,0 +1,120 @@ +## +## Copyright (c) 2000, 2001, 2002 Thomas Heller +## +## Permission is hereby granted, free of charge, to any person obtaining +## a copy of this software and associated documentation files (the +## "Software"), to deal in the Software without restriction, including +## without limitation the rights to use, copy, modify, merge, publish, +## distribute, sublicense, and/or sell copies of the Software, and to +## permit persons to whom the Software is furnished to do so, subject to +## the following conditions: +## +## The above copyright notice and this permission notice shall be +## included in all copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +## LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +## OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## + +# String Tables. +# See Knowledge Base, Q200893 +# +# +# $Id: StringTables.py 380 2004-01-16 10:47:02Z theller $ +# +# $Log$ +# Revision 1.1 2003/08/29 12:30:52 mhammond +# New py2exe now uses the old resource functions :) +# +# Revision 1.1 2002/01/29 09:30:55 theller +# version 0.3.0 +# +# +# + +RT_STRING = 6 # win32 resource type + +_use_unicode = 0 + +try: + _use_unicode = unicode +except NameError: + try: + import pywintypes + except ImportError: + raise ImportError, "Could not import StringTables, no unicode available" + +if _use_unicode: + + def w32_uc(text): + """convert a string into unicode, then encode it into UTF-16 + little endian, ready to use for win32 apis""" + return unicode(text, "unicode-escape").encode("utf-16-le") + +else: + def w32_uc(text): + return pywintypes.Unicode(text).raw + +class StringTable: + + """Collects (id, string) pairs and allows to build Win32 + StringTable resources from them.""" + + def __init__(self): + self.strings = {} + + def add_string(self, id, text): + self.strings[id] = text + + def sections(self): + ids = self.strings.keys() + ids.sort() + sections = {} + + for id in ids: + sectnum = id / 16 + 1 +## Sigh. 1.5 doesn't have setdefault! +## table = sections.setdefault(sectnum, {}) + table = sections.get(sectnum) + if table is None: + table = sections[sectnum] = {} + table[id % 16] = self.strings[id] + + return sections + + def binary(self): + import struct + sections = [] + for key, sect in self.sections().items(): + data = "" + for i in range(16): + ustr = w32_uc(sect.get(i, "")) + fmt = "h%ds" % len(ustr) + data = data + struct.pack(fmt, len(ustr)/2, ustr) + sections.append((key, data)) + return sections + + +if __name__ == '__main__': + st = StringTable() + st.add_string(32, "Hallo") + st.add_string(33, "Hallo1") + st.add_string(34, "Hallo2") + st.add_string(35, "Hallo3") + st.add_string(1023, "__service__.VCULogService") + st.add_string(1024, "__service__.VCULogService") + st.add_string(1025, "__service__.VCULogService") + st.add_string(1026, "__service__.VCULogService") + + import sys + sys.path.append("c:/tmp") + from hexdump import hexdump + + for sectnum, data in st.binary(): + print "ID", sectnum + hexdump(data) diff --git a/installer/py2exe/py2exe/resources/VersionInfo.py b/installer/py2exe/py2exe/resources/VersionInfo.py new file mode 100644 index 0000000..b7c90f5 --- /dev/null +++ b/installer/py2exe/py2exe/resources/VersionInfo.py @@ -0,0 +1,291 @@ +# -*- coding: latin-1 -*- +## +## Copyright (c) 2000, 2001, 2002, 2003 Thomas Heller +## +## Permission is hereby granted, free of charge, to any person obtaining +## a copy of this software and associated documentation files (the +## "Software"), to deal in the Software without restriction, including +## without limitation the rights to use, copy, modify, merge, publish, +## distribute, sublicense, and/or sell copies of the Software, and to +## permit persons to whom the Software is furnished to do so, subject to +## the following conditions: +## +## The above copyright notice and this permission notice shall be +## included in all copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +## LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +## OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## + +# +# $Id: VersionInfo.py 392 2004-03-12 17:00:21Z theller $ +# +# $Log$ +# Revision 1.3 2004/01/16 10:45:31 theller +# Move py2exe from the sandbox directory up to the root dir. +# +# Revision 1.3 2003/12/29 13:44:57 theller +# Adapt for Python 2.3. +# +# Revision 1.2 2003/09/18 20:19:57 theller +# Remove a 2.3 warning, but mostly this checkin is to test the brand new +# py2exe-checkins mailing list. +# +# Revision 1.1 2003/08/29 12:30:52 mhammond +# New py2exe now uses the old resource functions :) +# +# Revision 1.1 2002/01/29 09:30:55 theller +# version 0.3.0 +# +# Revision 1.2 2002/01/14 19:08:05 theller +# Better (?) Unicode handling. +# +# Revision 1.1 2002/01/07 10:30:32 theller +# Create a version resource. +# +# +import struct + +VOS_NT_WINDOWS32 = 0x00040004 +VFT_APP = 0x00000001 + +RT_VERSION = 16 + +class VersionError(Exception): + pass + +def w32_uc(text): + """convert a string into unicode, then encode it into UTF-16 + little endian, ready to use for win32 apis""" + if type(text) is str: + return unicode(text, "unicode-escape").encode("utf-16-le") + return unicode(text).encode("utf-16-le") + +class VS_FIXEDFILEINFO: + dwSignature = 0xFEEF04BDL + dwStrucVersion = 0x00010000 + dwFileVersionMS = 0x00010000 + dwFileVersionLS = 0x00000001 + dwProductVersionMS = 0x00010000 + dwProductVersionLS = 0x00000001 + dwFileFlagsMask = 0x3F + dwFileFlags = 0 + dwFileOS = VOS_NT_WINDOWS32 + dwFileType = VFT_APP + dwFileSubtype = 0 + dwFileDateMS = 0 + dwFileDateLS = 0 + + fmt = "13L" + + def __init__(self, version): + import string + version = string.replace(version, ",", ".") + fields = string.split(version + '.0.0.0.0', ".")[:4] + fields = map(string.strip, fields) + try: + self.dwFileVersionMS = int(fields[0]) * 65536 + int(fields[1]) + self.dwFileVersionLS = int(fields[2]) * 65536 + int(fields[3]) + except ValueError: + raise VersionError, "could not parse version number '%s'" % version + + def __str__(self): + return struct.pack(self.fmt, + self.dwSignature, + self.dwStrucVersion, + self.dwFileVersionMS, + self.dwFileVersionLS, + self.dwProductVersionMS, + self.dwProductVersionLS, + self.dwFileFlagsMask, + self.dwFileFlags, + self.dwFileOS, + self.dwFileType, + self.dwFileSubtype, + self.dwFileDateMS, + self.dwFileDateLS) + +def align(data): + pad = - len(data) % 4 + return data + '\000' * pad + +class VS_STRUCT: + items = () + + def __str__(self): + szKey = w32_uc(self.name) + ulen = len(szKey)+2 + + value = self.get_value() + data = struct.pack("h%ss0i" % ulen, self.wType, szKey) + value + + data = align(data) + + for item in self.items: + data = data + str(item) + + wLength = len(data) + 4 # 4 bytes for wLength and wValueLength + wValueLength = len(value) + + return self.pack("hh", wLength, wValueLength, data) + + def pack(self, fmt, len, vlen, data): + return struct.pack(fmt, len, vlen) + data + + def get_value(self): + return "" + + +class String(VS_STRUCT): + wType = 1 + items = () + + def __init__(self, (name, value)): + self.name = name + if value: + self.value = value + '\000' # strings must be zero terminated + else: + self.value = value + + def pack(self, fmt, len, vlen, data): + # ValueLength is measured in WORDS, not in BYTES! + return struct.pack(fmt, len, vlen/2) + data + + def get_value(self): + return w32_uc(self.value) + + +class StringTable(VS_STRUCT): + wType = 1 + + def __init__(self, name, strings): + self.name = name + self.items = map(String, strings) + + +class StringFileInfo(VS_STRUCT): + wType = 1 + name = "StringFileInfo" + + def __init__(self, name, strings): + self.items = [StringTable(name, strings)] + +class Var(VS_STRUCT): + # MSDN says: + # If you use the Var structure to list the languages your + # application or DLL supports instead of using multiple version + # resources, use the Value member to contain an array of DWORD + # values indicating the language and code page combinations + # supported by this file. The low-order word of each DWORD must + # contain a Microsoft language identifier, and the high-order word + # must contain the IBM code page number. Either high-order or + # low-order word can be zero, indicating that the file is language + # or code page independent. If the Var structure is omitted, the + # file will be interpreted as both language and code page + # independent. + wType = 0 + name = "Translation" + + def __init__(self, value): + self.value = value + + def get_value(self): + return struct.pack("l", self.value) + +class VarFileInfo(VS_STRUCT): + wType = 1 + name = "VarFileInfo" + + def __init__(self, *names): + self.items = map(Var, names) + + def get_value(self): + return "" + +class VS_VERSIONINFO(VS_STRUCT): + wType = 0 # 0: binary data, 1: text data + name = "VS_VERSION_INFO" + + def __init__(self, version, items): + self.value = VS_FIXEDFILEINFO(version) + self.items = items + + def get_value(self): + return str(self.value) + +class Version(object): + def __init__(self, + version, + comments = None, + company_name = None, + file_description = None, + internal_name = None, + legal_copyright = None, + legal_trademarks = None, + original_filename = None, + private_build = None, + product_name = None, + product_version = None, + special_build = None): + self.version = version + strings = [] + if comments is not None: + strings.append(("Comments", comments)) + if company_name is not None: + strings.append(("CompanyName", company_name)) + if file_description is not None: + strings.append(("FileDescription", file_description)) + strings.append(("FileVersion", version)) + if internal_name is not None: + strings.append(("InternalName", internal_name)) + if legal_copyright is not None: + strings.append(("LegalCopyright", legal_copyright)) + if legal_trademarks is not None: + strings.append(("LegalTrademarks", legal_trademarks)) + if original_filename is not None: + strings.append(("OriginalFilename", original_filename)) + if private_build is not None: + strings.append(("PrivateBuild", private_build)) + if product_name is not None: + strings.append(("ProductName", product_name)) + strings.append(("ProductVersion", product_version or version)) + if special_build is not None: + strings.append(("SpecialBuild", special_build)) + self.strings = strings + + def resource_bytes(self): + vs = VS_VERSIONINFO(self.version, + [StringFileInfo("040904B0", + self.strings), + VarFileInfo(0x04B00409)]) + return str(vs) + +def test(): + import sys + sys.path.append("c:/tmp") + from hexdump import hexdump + version = Version("1, 0, 0, 1", + comments = "mlut comments", + company_name = "No Company", + file_description = "silly application", + internal_name = "silly", + legal_copyright = u"Copyright 2003", +## legal_trademark = "", + original_filename = "silly.exe", + private_build = "test build", + product_name = "silly product", + product_version = None, +## special_build = "" + ) + hexdump(version.resource_bytes()) + +if __name__ == '__main__': + import sys + sys.path.append("d:/nbalt/tmp") + from hexdump import hexdump + test() diff --git a/installer/py2exe/py2exe/resources/__init__.py b/installer/py2exe/py2exe/resources/__init__.py new file mode 100644 index 0000000..eb1e0d1 --- /dev/null +++ b/installer/py2exe/py2exe/resources/__init__.py @@ -0,0 +1 @@ +# package to build resources. diff --git a/installer/py2exe/py2exe/samples/advanced/MyService.py b/installer/py2exe/py2exe/samples/advanced/MyService.py new file mode 100644 index 0000000..673ac62 --- /dev/null +++ b/installer/py2exe/py2exe/samples/advanced/MyService.py @@ -0,0 +1,52 @@ +# +# A sample service to be 'compiled' into an exe-file with py2exe. +# +# See also +# setup.py - the distutils' setup script +# setup.cfg - the distutils' config file for this +# README.txt - detailed usage notes +# +# A minimal service, doing nothing else than +# - write 'start' and 'stop' entries into the NT event log +# - when started, waits to be stopped again. +# +import win32serviceutil +import win32service +import win32event +import win32evtlogutil + +class MyService(win32serviceutil.ServiceFramework): + _svc_name_ = "MyService" + _svc_display_name_ = "My Service" + _svc_deps_ = ["EventLog"] + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) + + def SvcStop(self): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + win32event.SetEvent(self.hWaitStop) + + def SvcDoRun(self): + import servicemanager + # Write a 'started' event to the event log... + win32evtlogutil.ReportEvent(self._svc_name_, + servicemanager.PYS_SERVICE_STARTED, + 0, # category + servicemanager.EVENTLOG_INFORMATION_TYPE, + (self._svc_name_, '')) + + # wait for beeing stopped... + win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) + + # and write a 'stopped' event to the event log. + win32evtlogutil.ReportEvent(self._svc_name_, + servicemanager.PYS_SERVICE_STOPPED, + 0, # category + servicemanager.EVENTLOG_INFORMATION_TYPE, + (self._svc_name_, '')) + + +if __name__ == '__main__': + # Note that this code will not be run in the 'frozen' exe-file!!! + win32serviceutil.HandleCommandLine(MyService) diff --git a/installer/py2exe/py2exe/samples/advanced/icon.ico b/installer/py2exe/py2exe/samples/advanced/icon.ico new file mode 100644 index 0000000..b55d82b Binary files /dev/null and b/installer/py2exe/py2exe/samples/advanced/icon.ico differ diff --git a/installer/py2exe/py2exe/samples/advanced/setup.py b/installer/py2exe/py2exe/samples/advanced/setup.py new file mode 100644 index 0000000..74c4cb4 --- /dev/null +++ b/installer/py2exe/py2exe/samples/advanced/setup.py @@ -0,0 +1,141 @@ +# A setup script showing advanced features. +# +# Note that for the NT service to build correctly, you need at least +# win32all build 161, for the COM samples, you need build 163. +# Requires wxPython, and Tim Golden's WMI module. + +# Note: WMI is probably NOT a good example for demonstrating how to +# include a pywin32 typelib wrapper into the exe: wmi uses different +# typelib versions on win2k and winXP. The resulting exe will only +# run on the same windows version as the one used to build the exe. +# So, the newest version of wmi.py doesn't use any typelib anymore. + +from distutils.core import setup +import py2exe +import sys + +# If run without args, build executables, in quiet mode. +if len(sys.argv) == 1: + sys.argv.append("py2exe") + sys.argv.append("-q") + +class Target: + def __init__(self, **kw): + self.__dict__.update(kw) + # for the versioninfo resources + self.version = "0.5.0" + self.company_name = "No Company" + self.copyright = "no copyright" + self.name = "py2exe sample files" + +################################################################ +# A program using wxPython + +# The manifest will be inserted as resource into test_wx.exe. This +# gives the controls the Windows XP appearance (if run on XP ;-) +# +# Another option would be to store it in a file named +# test_wx.exe.manifest, and copy it with the data_files option into +# the dist-dir. +# +manifest_template = ''' + + + +%(prog)s Program + + + + + + +''' + +RT_MANIFEST = 24 + +test_wx = Target( + # used for the versioninfo resource + description = "A sample GUI app", + + # what to build + script = "test_wx.py", + other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="test_wx"))], +## icon_resources = [(1, "icon.ico")], + dest_base = "test_wx") + +test_wx_console = Target( + # used for the versioninfo resource + description = "A sample GUI app with console", + + # what to build + script = "test_wx.py", + other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="test_wx"))], + dest_base = "test_wx_console") + +################################################################ +# A program using early bound COM, needs the typelibs option below +test_wmi = Target( + description = "Early bound COM client example", + script = "test_wmi.py", + ) + +################################################################ +# a NT service, modules is required +myservice = Target( + # used for the versioninfo resource + description = "A sample Windows NT service", + # what to build. For a service, the module name (not the + # filename) must be specified! + modules = ["MyService"] + ) + +################################################################ +# a COM server (exe and dll), modules is required +# +# If you only want a dll or an exe, comment out one of the create_xxx +# lines below. + +interp = Target( + description = "Python Interpreter as COM server module", + # what to build. For COM servers, the module name (not the + # filename) must be specified! + modules = ["win32com.servers.interp"], +## create_exe = False, +## create_dll = False, + ) + +################################################################ +# COM pulls in a lot of stuff which we don't want or need. + +excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon", + "pywin.dialogs", "pywin.dialogs.list"] + +setup( + options = {"py2exe": {"typelibs": + # typelib for WMI + [('{565783C6-CB41-11D1-8B02-00600806D9B6}', 0, 1, 2)], + # create a compressed zip archive + "compressed": 1, + "optimize": 2, + "excludes": excludes}}, + # The lib directory contains everything except the executables and the python dll. + # Can include a subdirectory name. + zipfile = "lib/shared.zip", + + service = [myservice], + com_server = [interp], + console = [test_wx_console, test_wmi], + windows = [test_wx], + ) diff --git a/installer/py2exe/py2exe/samples/advanced/test_wmi.py b/installer/py2exe/py2exe/samples/advanced/test_wmi.py new file mode 100644 index 0000000..d434174 --- /dev/null +++ b/installer/py2exe/py2exe/samples/advanced/test_wmi.py @@ -0,0 +1,6 @@ +import wmi # Tim Golden's wmi module. + +computer = wmi.WMI() + +for item in computer.Win32_Process()[:2]: + print item diff --git a/installer/py2exe/py2exe/samples/advanced/test_wx.py b/installer/py2exe/py2exe/samples/advanced/test_wx.py new file mode 100644 index 0000000..e53e23b --- /dev/null +++ b/installer/py2exe/py2exe/samples/advanced/test_wx.py @@ -0,0 +1,36 @@ +from wxPython.wx import * + +class MyFrame(wxFrame): + def __init__(self, parent, ID, title, pos=wxDefaultPosition, + size=(200, 200), style=wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, parent, ID, title, pos, size, style) + panel = wxPanel(self, -1) + + button = wxButton(panel, 1003, "Close Me") + button.SetPosition(wxPoint(15, 15)) + EVT_BUTTON(self, 1003, self.OnCloseMe) + EVT_CLOSE(self, self.OnCloseWindow) + + button = wxButton(panel, 1004, "Press Me") + button.SetPosition(wxPoint(15, 45)) + EVT_BUTTON(self, 1004, self.OnPressMe) + + def OnCloseMe(self, event): + self.Close(True) + + def OnPressMe(self, event): + # This raises an exception + x = 1 / 0 + + def OnCloseWindow(self, event): + self.Destroy() + +class MyApp(wxApp): + def OnInit(self): + frame = MyFrame(NULL, -1, "Hello from wxPython") + frame.Show(true) + self.SetTopWindow(frame) + return true + +app = MyApp(0) +app.MainLoop() diff --git a/installer/py2exe/py2exe/samples/extending/setup.py b/installer/py2exe/py2exe/samples/extending/setup.py new file mode 100644 index 0000000..ec37219 --- /dev/null +++ b/installer/py2exe/py2exe/samples/extending/setup.py @@ -0,0 +1,172 @@ +# A setup script showing how to extend py2exe. +# +# In this case, the py2exe command is subclassed to create an installation +# script for InnoSetup, which can be compiled with the InnoSetup compiler +# to a single file windows installer. +# +# By default, the installer will be created as dist\Output\setup.exe. + +from distutils.core import setup +import py2exe +import sys + +################################################################ +# A program using wxPython + +# The manifest will be inserted as resource into test_wx.exe. This +# gives the controls the Windows XP appearance (if run on XP ;-) +# +# Another option would be to store if in a file named +# test_wx.exe.manifest, and probably copy it with the data_files +# option. +# +manifest_template = ''' + + + +%(prog)s Program + + + + + + +''' + +RT_MANIFEST = 24 + +################################################################ +# arguments for the setup() call + +test_wx = dict( + script = "test_wx.py", + other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="test_wx"))], + dest_base = r"prog\test_wx") + +zipfile = r"lib\shardlib" + +options = {"py2exe": {"compressed": 1, + "optimize": 2}} + +################################################################ +import os + +class InnoScript: + def __init__(self, + name, + lib_dir, + dist_dir, + windows_exe_files = [], + lib_files = [], + version = "1.0"): + self.lib_dir = lib_dir + self.dist_dir = dist_dir + if not self.dist_dir[-1] in "\\/": + self.dist_dir += "\\" + self.name = name + self.version = version + self.windows_exe_files = [self.chop(p) for p in windows_exe_files] + self.lib_files = [self.chop(p) for p in lib_files] + + def chop(self, pathname): + assert pathname.startswith(self.dist_dir) + return pathname[len(self.dist_dir):] + + def create(self, pathname="dist\\test_wx.iss"): + self.pathname = pathname + ofi = self.file = open(pathname, "w") + print >> ofi, "; WARNING: This script has been created by py2exe. Changes to this script" + print >> ofi, "; will be overwritten the next time py2exe is run!" + print >> ofi, r"[Setup]" + print >> ofi, r"AppName=%s" % self.name + print >> ofi, r"AppVerName=%s %s" % (self.name, self.version) + print >> ofi, r"DefaultDirName={pf}\%s" % self.name + print >> ofi, r"DefaultGroupName=%s" % self.name + print >> ofi + + print >> ofi, r"[Files]" + for path in self.windows_exe_files + self.lib_files: + print >> ofi, r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion' % (path, os.path.dirname(path)) + print >> ofi + + print >> ofi, r"[Icons]" + for path in self.windows_exe_files: + print >> ofi, r'Name: "{group}\%s"; Filename: "{app}\%s"' % \ + (self.name, path) + print >> ofi, 'Name: "{group}\Uninstall %s"; Filename: "{uninstallexe}"' % self.name + + def compile(self): + try: + import ctypes + except ImportError: + try: + import win32api + except ImportError: + import os + os.startfile(self.pathname) + else: + print "Ok, using win32api." + win32api.ShellExecute(0, "compile", + self.pathname, + None, + None, + 0) + else: + print "Cool, you have ctypes installed." + res = ctypes.windll.shell32.ShellExecuteA(0, "compile", + self.pathname, + None, + None, + 0) + if res < 32: + raise RuntimeError, "ShellExecute failed, error %d" % res + + +################################################################ + +from py2exe.build_exe import py2exe + +class build_installer(py2exe): + # This class first builds the exe file(s), then creates a Windows installer. + # You need InnoSetup for it. + def run(self): + # First, let py2exe do it's work. + py2exe.run(self) + + lib_dir = self.lib_dir + dist_dir = self.dist_dir + + # create the Installer, using the files py2exe has created. + script = InnoScript("test_wx", + lib_dir, + dist_dir, + self.windows_exe_files, + self.lib_files) + print "*** creating the inno setup script***" + script.create() + print "*** compiling the inno setup script***" + script.compile() + # Note: By default the final setup.exe will be in an Output subdirectory. + +################################################################ + +setup( + options = options, + # The lib directory contains everything except the executables and the python dll. + zipfile = zipfile, + windows = [test_wx], + # use out build_installer class as extended py2exe build command + cmdclass = {"py2exe": build_installer}, + ) diff --git a/installer/py2exe/py2exe/samples/extending/test_wx.py b/installer/py2exe/py2exe/samples/extending/test_wx.py new file mode 100644 index 0000000..e53e23b --- /dev/null +++ b/installer/py2exe/py2exe/samples/extending/test_wx.py @@ -0,0 +1,36 @@ +from wxPython.wx import * + +class MyFrame(wxFrame): + def __init__(self, parent, ID, title, pos=wxDefaultPosition, + size=(200, 200), style=wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, parent, ID, title, pos, size, style) + panel = wxPanel(self, -1) + + button = wxButton(panel, 1003, "Close Me") + button.SetPosition(wxPoint(15, 15)) + EVT_BUTTON(self, 1003, self.OnCloseMe) + EVT_CLOSE(self, self.OnCloseWindow) + + button = wxButton(panel, 1004, "Press Me") + button.SetPosition(wxPoint(15, 45)) + EVT_BUTTON(self, 1004, self.OnPressMe) + + def OnCloseMe(self, event): + self.Close(True) + + def OnPressMe(self, event): + # This raises an exception + x = 1 / 0 + + def OnCloseWindow(self, event): + self.Destroy() + +class MyApp(wxApp): + def OnInit(self): + frame = MyFrame(NULL, -1, "Hello from wxPython") + frame.Show(true) + self.SetTopWindow(frame) + return true + +app = MyApp(0) +app.MainLoop() diff --git a/installer/py2exe/py2exe/samples/pywin32/com_server/README.txt b/installer/py2exe/py2exe/samples/pywin32/com_server/README.txt new file mode 100644 index 0000000..770f61f --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_server/README.txt @@ -0,0 +1,16 @@ +This is a sample of a COM object ('server') implemented in Python. + +This builds the pywin32 sample COM object 'interp' - see the win32com\servers +directory. + +Execute: + + setup.py py2exe + +And in the dist directory you will find interp.exe and inter.dll. + +You can register the objects with 'interp.exe /regserver' or +'regsvr32 interp.dll'. 'interp.exe /unregister' and 'regsvr32 /u interp.dll' +will unregister the objects. + +Once registered, test the object using 'test_interp.py' or 'test_interp.vbs' \ No newline at end of file diff --git a/installer/py2exe/py2exe/samples/pywin32/com_server/setup.py b/installer/py2exe/py2exe/samples/pywin32/com_server/setup.py new file mode 100644 index 0000000..1b3055c --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_server/setup.py @@ -0,0 +1,13 @@ +# setup_interp.py +# A distutils setup script for the "interp" sample. + +from distutils.core import setup +import py2exe + +# Don't pull in all this MFC stuff used by the makepy UI. +py2exe_options = dict(excludes="pywin,pywin.dialogs,pywin.dialogs.list,win32ui") + +setup(name="win32com 'interp' sample", + com_server=["win32com.servers.interp"], + options = dict(py2exe=py2exe_options) +) diff --git a/installer/py2exe/py2exe/samples/pywin32/com_server/test_interp.py b/installer/py2exe/py2exe/samples/pywin32/com_server/test_interp.py new file mode 100644 index 0000000..e33fd6b --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_server/test_interp.py @@ -0,0 +1,12 @@ +# A Python program that uses our binaries! +# (Note that this is *not* transformed into a .exe - its an example consumer +# of our object, not part of our object) + +from win32com.client import Dispatch +import pprint + +interp = Dispatch("Python.Interpreter") +interp.Exec("import sys") + +print "The COM object is being hosted in ", interp.Eval("sys.executable") +print "Path is:", interp.Eval("sys.path") diff --git a/installer/py2exe/py2exe/samples/pywin32/com_server/test_interp.vbs b/installer/py2exe/py2exe/samples/pywin32/com_server/test_interp.vbs new file mode 100644 index 0000000..13b2b23 --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_server/test_interp.vbs @@ -0,0 +1,8 @@ +rem A VBScript program that uses the py2exe created COM object. +rem Register the object (see README.txt), then execute: +rem cscript.exe test_interp.vbs +set interp = CreateObject("Python.Interpreter") +interp.Exec "import sys" + +WScript.Echo("The COM object is being hosted in " & interp.Eval("sys.executable")) +WScript.Echo("Path is:" & interp.Eval("str(sys.path)")) diff --git a/installer/py2exe/py2exe/samples/pywin32/com_typelib/README.txt b/installer/py2exe/py2exe/samples/pywin32/com_typelib/README.txt new file mode 100644 index 0000000..9b0adfc --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_typelib/README.txt @@ -0,0 +1,24 @@ +Some py2exe samples relating to the use of typelibs and pywin32. + +pywin32's COM support takes advantage of COM typelibs by generating Python +stubs for the objects in these typelibs. This generation is often known as +a 'makepy' process, from the name of the script that performs the generation, +but it is not always necessary to explcitly invoke makepy.py - the use of +win32com.client.gencache will often cause implicit generation of these stubs. + +This directory contains samples showing how to use these techniques with +py2exe apps. It contains the following samples. + +build_gen: contains samples that demonstrate how to build a typelib as py2exe + is run. This means the machine running py2exe must have the + typelibs installed locally, but the target machines need not. + py2exe generates the typelib stubs as it is run. + + There is currently a single sample which assumes MSWord is + installed. Please contribute samples for other common objects! + +pre_gen: contains samples that demonstrate how to package typelib stubs + previously generated. Such stubs will have come from a previous + invocation of makepy, possibly on another computer and possibly + from a source control system. In this case, the computer running + py2exe does *not* need to have the relevant typelibs installed. diff --git a/installer/py2exe/py2exe/samples/pywin32/com_typelib/build_gen/word/docmaker.py b/installer/py2exe/py2exe/samples/pywin32/com_typelib/build_gen/word/docmaker.py new file mode 100644 index 0000000..3f73911 --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_typelib/build_gen/word/docmaker.py @@ -0,0 +1,32 @@ +# A test program that generates a word document. +import sys +import os +from win32com.client import gencache + +# When built with py2exe, it assumes 'Word.Application.9' is installed +# on the machine performing the build. The typelibs from the local machine +# will be used to generate makepy files, and those generated files will +# be included in the py2exe library (generally in a .zip file) + +# The resulting application should run without referencing any typelibs on +# the target system. + +# It will create a file: +filename = os.path.abspath( + os.path.join(os.path.dirname(sys.argv[0]), "output.doc")) + +word = gencache.EnsureDispatch("Word.Application.9") + +# For the sake of ensuring the correct module is used... +mod = sys.modules[word.__module__] +print "The module hosting the object is", mod + + +word.Visible = 1 +doc = word.Documents.Add() +wrange = doc.Range() +for i in range(10): + wrange.InsertAfter("Hello from py2exe %d\n" % i) +doc.SaveAs(filename) +word.Quit() +print "Done - saved to", os.path.abspath(filename) diff --git a/installer/py2exe/py2exe/samples/pywin32/com_typelib/build_gen/word/setup.py b/installer/py2exe/py2exe/samples/pywin32/com_typelib/build_gen/word/setup.py new file mode 100644 index 0000000..2c7aa1e --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_typelib/build_gen/word/setup.py @@ -0,0 +1,16 @@ +# Execute this as 'setup.py py2exe' to create a .exe from docmaker.py +from distutils.core import setup +import py2exe + +py2exe_options = dict( + typelibs = [ + # typelib for 'Word.Application.8' - execute + # 'win32com/client/makepy.py -i' to find a typelib. + ('{00020905-0000-0000-C000-000000000046}', 0, 8, 1), + ] +) + +setup(name="SpamBayes", + console=["docmaker.py"], + options = {"py2exe" : py2exe_options}, +) diff --git a/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/setup.py b/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/setup.py new file mode 100644 index 0000000..de4ba39 --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/setup.py @@ -0,0 +1,16 @@ +# Execute this as 'setup.py py2exe' to create a .exe from docmaker.py +from distutils.core import setup +import py2exe + +py2exe_options = dict( + typelibs = [ + # typelib for 'Windows Script Host Object Model', which we + # have pre-generated into wsh-typelib-stubs.py + ('{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}', 0, 1, 0, 'wsh-typelib-stubs.py'), + ] +) + +setup(name="SpamBayes", + console=["show_info.py"], + options = {"py2exe" : py2exe_options}, +) diff --git a/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/show_info.py b/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/show_info.py new file mode 100644 index 0000000..33d9116 --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/show_info.py @@ -0,0 +1,17 @@ +# Print some simple information using the WScript.Network object. +import sys +from win32com.client.gencache import EnsureDispatch + +ob = EnsureDispatch('WScript.Network') + +# For the sake of ensuring the correct module is used... +mod = sys.modules[ob.__module__] +print "The module hosting the object is", mod + +# Now use the object. +print "About this computer:" +print "Domain =", ob.UserDomain +print "Computer Name =", ob.ComputerName +print "User Name =", ob.UserName + + diff --git a/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/wsh-typelib-stubs.py b/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/wsh-typelib-stubs.py new file mode 100644 index 0000000..7f8ed2f --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/com_typelib/pre_gen/wscript/wsh-typelib-stubs.py @@ -0,0 +1,1886 @@ +# -*- coding: mbcs -*- + +# This file is part of the py2exe pywin32\com_typelib\pre_gen\word sample. +# It was generated by the command-line: +# 'makepy.py -o wsh-typelib-stubs.py "Windows Script Host Object Model"' +# It will be included in the py2exe generated library and used by win32com +# at runtime in the generated program. +# The file has not been modified below this line: + + +# Created by makepy.py version 0.4.95 +# By python version 2.4.4 (#71, Feb 5 2007, 15:24:39) [MSC v.1310 32 bit (Intel)] +# From type library 'wshom.ocx' +# On Mon Mar 26 14:31:22 2007 +"""Windows Script Host Object Model""" +makepy_version = '0.4.95' +python_version = 0x20404f0 + +import win32com.client.CLSIDToClass, pythoncom +import win32com.client.util +from pywintypes import IID +from win32com.client import Dispatch + +# The following 3 lines may need tweaking for the particular server +# Candidates are pythoncom.Missing, .Empty and .ArgNotFound +defaultNamedOptArg=pythoncom.Empty +defaultNamedNotOptArg=pythoncom.Empty +defaultUnnamedArg=pythoncom.Empty + +CLSID = IID('{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}') +MajorVersion = 1 +MinorVersion = 0 +LibraryFlags = 8 +LCID = 0x0 + +class constants: + BinaryCompare =0x0 # from enum CompareMethod + DatabaseCompare =0x2 # from enum CompareMethod + TextCompare =0x1 # from enum CompareMethod + ForAppending =0x8 # from enum IOMode + ForReading =0x1 # from enum IOMode + ForWriting =0x2 # from enum IOMode + TristateFalse =0x0 # from enum Tristate + TristateMixed =-2 # from enum Tristate + TristateTrue =-1 # from enum Tristate + TristateUseDefault =-2 # from enum Tristate + Alias =0x400 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + Archive =0x20 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + Compressed =0x800 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + Directory =0x10 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + Hidden =0x2 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + Normal =0x0 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + ReadOnly =0x1 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + System =0x4 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + Volume =0x8 # from enum __MIDL___MIDL_itf_iwshom_0000_0001 + CDRom =0x4 # from enum __MIDL___MIDL_itf_iwshom_0000_0002 + Fixed =0x2 # from enum __MIDL___MIDL_itf_iwshom_0000_0002 + RamDisk =0x5 # from enum __MIDL___MIDL_itf_iwshom_0000_0002 + Remote =0x3 # from enum __MIDL___MIDL_itf_iwshom_0000_0002 + Removable =0x1 # from enum __MIDL___MIDL_itf_iwshom_0000_0002 + UnknownType =0x0 # from enum __MIDL___MIDL_itf_iwshom_0000_0002 + SystemFolder =0x1 # from enum __MIDL___MIDL_itf_iwshom_0000_0003 + TemporaryFolder =0x2 # from enum __MIDL___MIDL_itf_iwshom_0000_0003 + WindowsFolder =0x0 # from enum __MIDL___MIDL_itf_iwshom_0000_0003 + StdErr =0x2 # from enum __MIDL___MIDL_itf_iwshom_0000_0004 + StdIn =0x0 # from enum __MIDL___MIDL_itf_iwshom_0000_0004 + StdOut =0x1 # from enum __MIDL___MIDL_itf_iwshom_0000_0004 + WshHide =0x0 # from enum __MIDL___MIDL_itf_iwshom_0119_0001 + WshMaximizedFocus =0x3 # from enum __MIDL___MIDL_itf_iwshom_0119_0001 + WshMinimizedFocus =0x2 # from enum __MIDL___MIDL_itf_iwshom_0119_0001 + WshMinimizedNoFocus =0x6 # from enum __MIDL___MIDL_itf_iwshom_0119_0001 + WshNormalFocus =0x1 # from enum __MIDL___MIDL_itf_iwshom_0119_0001 + WshNormalNoFocus =0x4 # from enum __MIDL___MIDL_itf_iwshom_0119_0001 + WshFailed =0x2 # from enum __MIDL___MIDL_itf_iwshom_0128_0001 + WshFinished =0x1 # from enum __MIDL___MIDL_itf_iwshom_0128_0001 + WshRunning =0x0 # from enum __MIDL___MIDL_itf_iwshom_0128_0001 + +from win32com.client import DispatchBaseClass +class IDrive(DispatchBaseClass): + CLSID = IID('{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_clsid = IID('{C7C3F5B1-88A3-11D0-ABCB-00A0C90FFFC0}') + + _prop_map_get_ = { + "AvailableSpace": (10005, 2, (12, 0), (), "AvailableSpace", None), + "DriveLetter": (10000, 2, (8, 0), (), "DriveLetter", None), + "DriveType": (10002, 2, (3, 0), (), "DriveType", None), + "FileSystem": (10008, 2, (8, 0), (), "FileSystem", None), + "FreeSpace": (10004, 2, (12, 0), (), "FreeSpace", None), + "IsReady": (10010, 2, (11, 0), (), "IsReady", None), + "Path": (0, 2, (8, 0), (), "Path", None), + # Method 'RootFolder' returns object of type 'IFolder' + "RootFolder": (10003, 2, (9, 0), (), "RootFolder", '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}'), + "SerialNumber": (10009, 2, (3, 0), (), "SerialNumber", None), + "ShareName": (10001, 2, (8, 0), (), "ShareName", None), + "TotalSize": (10006, 2, (12, 0), (), "TotalSize", None), + "VolumeName": (10007, 2, (8, 0), (), "VolumeName", None), + } + _prop_map_put_ = { + "VolumeName": ((10007, LCID, 4, 0),()), + } + # Default property for this class is 'Path' + def __call__(self): + return self._ApplyTypes_(*(0, 2, (8, 0), (), "Path", None)) + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + +class IDriveCollection(DispatchBaseClass): + CLSID = IID('{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_clsid = IID('{C7C3F5B2-88A3-11D0-ABCB-00A0C90FFFC0}') + + # Result is of type IDrive + # The method Item is actually a property, but must be used as a method to correctly pass the arguments + def Item(self, Key=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(0, LCID, 2, (9, 0), ((12, 1),),Key + ) + if ret is not None: + ret = Dispatch(ret, 'Item', '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + _prop_map_get_ = { + "Count": (1, 2, (3, 0), (), "Count", None), + } + _prop_map_put_ = { + } + # Default method for this class is 'Item' + def __call__(self, Key=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(0, LCID, 2, (9, 0), ((12, 1),),Key + ) + if ret is not None: + ret = Dispatch(ret, '__call__', '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + def __iter__(self): + "Return a Python iterator for this object" + ob = self._oleobj_.InvokeTypes(-4,LCID,2,(13, 10),()) + return win32com.client.util.Iterator(ob) + def _NewEnum(self): + "Create an enumerator from this object" + return win32com.client.util.WrapEnum(self._oleobj_.InvokeTypes(-4,LCID,2,(13, 10),()),'{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}') + def __getitem__(self, index): + "Allow this class to be accessed as a collection" + if not self.__dict__.has_key('_enum_'): + self.__dict__['_enum_'] = self._NewEnum() + return self._enum_.__getitem__(index) + #This class has Count() property - allow len(ob) to provide this + def __len__(self): + return self._ApplyTypes_(*(1, 2, (3, 0), (), "Count", None)) + #This class has a __len__ - this is needed so 'if object:' always returns TRUE. + def __nonzero__(self): + return True + +class IFile(DispatchBaseClass): + CLSID = IID('{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_clsid = IID('{C7C3F5B5-88A3-11D0-ABCB-00A0C90FFFC0}') + + def Copy(self, Destination=defaultNamedNotOptArg, OverWriteFiles=True): + return self._oleobj_.InvokeTypes(1202, LCID, 1, (24, 0), ((8, 1), (11, 49)),Destination + , OverWriteFiles) + + def Delete(self, Force=False): + return self._oleobj_.InvokeTypes(1200, LCID, 1, (24, 0), ((11, 49),),Force + ) + + def Move(self, Destination=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(1204, LCID, 1, (24, 0), ((8, 1),),Destination + ) + + # Result is of type ITextStream + def OpenAsTextStream(self, IOMode=1, Format=0): + ret = self._oleobj_.InvokeTypes(1100, LCID, 1, (9, 0), ((3, 49), (3, 49)),IOMode + , Format) + if ret is not None: + ret = Dispatch(ret, 'OpenAsTextStream', '{53BAD8C1-E718-11CF-893D-00A0C9054228}', UnicodeToString=0) + return ret + + _prop_map_get_ = { + "Attributes": (1003, 2, (3, 0), (), "Attributes", None), + "DateCreated": (1006, 2, (7, 0), (), "DateCreated", None), + "DateLastAccessed": (1008, 2, (7, 0), (), "DateLastAccessed", None), + "DateLastModified": (1007, 2, (7, 0), (), "DateLastModified", None), + # Method 'Drive' returns object of type 'IDrive' + "Drive": (1004, 2, (9, 0), (), "Drive", '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}'), + "Name": (1000, 2, (8, 0), (), "Name", None), + # Method 'ParentFolder' returns object of type 'IFolder' + "ParentFolder": (1005, 2, (9, 0), (), "ParentFolder", '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}'), + "Path": (0, 2, (8, 0), (), "Path", None), + "ShortName": (1001, 2, (8, 0), (), "ShortName", None), + "ShortPath": (1002, 2, (8, 0), (), "ShortPath", None), + "Size": (1009, 2, (12, 0), (), "Size", None), + "Type": (1010, 2, (8, 0), (), "Type", None), + } + _prop_map_put_ = { + "Attributes": ((1003, LCID, 4, 0),()), + "Name": ((1000, LCID, 4, 0),()), + } + # Default property for this class is 'Path' + def __call__(self): + return self._ApplyTypes_(*(0, 2, (8, 0), (), "Path", None)) + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + +class IFileCollection(DispatchBaseClass): + CLSID = IID('{C7C3F5A5-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_clsid = IID('{C7C3F5B6-88A3-11D0-ABCB-00A0C90FFFC0}') + + # Result is of type IFile + # The method Item is actually a property, but must be used as a method to correctly pass the arguments + def Item(self, Key=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(0, LCID, 2, (9, 0), ((12, 1),),Key + ) + if ret is not None: + ret = Dispatch(ret, 'Item', '{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + _prop_map_get_ = { + "Count": (1, 2, (3, 0), (), "Count", None), + } + _prop_map_put_ = { + } + # Default method for this class is 'Item' + def __call__(self, Key=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(0, LCID, 2, (9, 0), ((12, 1),),Key + ) + if ret is not None: + ret = Dispatch(ret, '__call__', '{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + def __iter__(self): + "Return a Python iterator for this object" + ob = self._oleobj_.InvokeTypes(-4,LCID,2,(13, 10),()) + return win32com.client.util.Iterator(ob) + def _NewEnum(self): + "Create an enumerator from this object" + return win32com.client.util.WrapEnum(self._oleobj_.InvokeTypes(-4,LCID,2,(13, 10),()),'{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}') + def __getitem__(self, index): + "Allow this class to be accessed as a collection" + if not self.__dict__.has_key('_enum_'): + self.__dict__['_enum_'] = self._NewEnum() + return self._enum_.__getitem__(index) + #This class has Count() property - allow len(ob) to provide this + def __len__(self): + return self._ApplyTypes_(*(1, 2, (3, 0), (), "Count", None)) + #This class has a __len__ - this is needed so 'if object:' always returns TRUE. + def __nonzero__(self): + return True + +class IFileSystem(DispatchBaseClass): + CLSID = IID('{0AB5A3D0-E5B6-11D0-ABF5-00A0C90FFFC0}') + coclass_clsid = None + + def BuildPath(self, Path=defaultNamedNotOptArg, Name=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10000, LCID, 1, (8, 0), ((8, 1), (8, 1)),Path + , Name) + + def CopyFile(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg, OverWriteFiles=True): + return self._oleobj_.InvokeTypes(1202, LCID, 1, (24, 0), ((8, 1), (8, 1), (11, 49)),Source + , Destination, OverWriteFiles) + + def CopyFolder(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg, OverWriteFiles=True): + return self._oleobj_.InvokeTypes(1203, LCID, 1, (24, 0), ((8, 1), (8, 1), (11, 49)),Source + , Destination, OverWriteFiles) + + # Result is of type IFolder + def CreateFolder(self, Path=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(1120, LCID, 1, (9, 0), ((8, 1),),Path + ) + if ret is not None: + ret = Dispatch(ret, 'CreateFolder', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + # Result is of type ITextStream + def CreateTextFile(self, FileName=defaultNamedNotOptArg, Overwrite=True, Unicode=False): + ret = self._oleobj_.InvokeTypes(1101, LCID, 1, (9, 0), ((8, 1), (11, 49), (11, 49)),FileName + , Overwrite, Unicode) + if ret is not None: + ret = Dispatch(ret, 'CreateTextFile', '{53BAD8C1-E718-11CF-893D-00A0C9054228}', UnicodeToString=0) + return ret + + def DeleteFile(self, FileSpec=defaultNamedNotOptArg, Force=False): + return self._oleobj_.InvokeTypes(1200, LCID, 1, (24, 0), ((8, 1), (11, 49)),FileSpec + , Force) + + def DeleteFolder(self, FolderSpec=defaultNamedNotOptArg, Force=False): + return self._oleobj_.InvokeTypes(1201, LCID, 1, (24, 0), ((8, 1), (11, 49)),FolderSpec + , Force) + + def DriveExists(self, DriveSpec=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10015, LCID, 1, (11, 0), ((8, 1),),DriveSpec + ) + + def FileExists(self, FileSpec=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10016, LCID, 1, (11, 0), ((8, 1),),FileSpec + ) + + def FolderExists(self, FolderSpec=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10017, LCID, 1, (11, 0), ((8, 1),),FolderSpec + ) + + def GetAbsolutePathName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10002, LCID, 1, (8, 0), ((8, 1),),Path + ) + + def GetBaseName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10007, LCID, 1, (8, 0), ((8, 1),),Path + ) + + # Result is of type IDrive + def GetDrive(self, DriveSpec=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10011, LCID, 1, (9, 0), ((8, 1),),DriveSpec + ) + if ret is not None: + ret = Dispatch(ret, 'GetDrive', '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + def GetDriveName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10004, LCID, 1, (8, 0), ((8, 1),),Path + ) + + def GetExtensionName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10008, LCID, 1, (8, 0), ((8, 1),),Path + ) + + # Result is of type IFile + def GetFile(self, FilePath=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10012, LCID, 1, (9, 0), ((8, 1),),FilePath + ) + if ret is not None: + ret = Dispatch(ret, 'GetFile', '{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + def GetFileName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10006, LCID, 1, (8, 0), ((8, 1),),Path + ) + + # Result is of type IFolder + def GetFolder(self, FolderPath=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10013, LCID, 1, (9, 0), ((8, 1),),FolderPath + ) + if ret is not None: + ret = Dispatch(ret, 'GetFolder', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + def GetParentFolderName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10005, LCID, 1, (8, 0), ((8, 1),),Path + ) + + # Result is of type IFolder + def GetSpecialFolder(self, SpecialFolder=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10014, LCID, 1, (9, 0), ((3, 1),),SpecialFolder + ) + if ret is not None: + ret = Dispatch(ret, 'GetSpecialFolder', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + def GetTempName(self): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10003, LCID, 1, (8, 0), (),) + + def MoveFile(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(1204, LCID, 1, (24, 0), ((8, 1), (8, 1)),Source + , Destination) + + def MoveFolder(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(1205, LCID, 1, (24, 0), ((8, 1), (8, 1)),Source + , Destination) + + # Result is of type ITextStream + def OpenTextFile(self, FileName=defaultNamedNotOptArg, IOMode=1, Create=False, Format=0): + ret = self._oleobj_.InvokeTypes(1100, LCID, 1, (9, 0), ((8, 1), (3, 49), (11, 49), (3, 49)),FileName + , IOMode, Create, Format) + if ret is not None: + ret = Dispatch(ret, 'OpenTextFile', '{53BAD8C1-E718-11CF-893D-00A0C9054228}', UnicodeToString=0) + return ret + + _prop_map_get_ = { + # Method 'Drives' returns object of type 'IDriveCollection' + "Drives": (10010, 2, (9, 0), (), "Drives", '{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}'), + } + _prop_map_put_ = { + } + +class IFileSystem3(DispatchBaseClass): + CLSID = IID('{2A0B9D10-4B87-11D3-A97A-00104B365C9F}') + coclass_clsid = IID('{0D43FE01-F093-11CF-8940-00A0C9054228}') + + def BuildPath(self, Path=defaultNamedNotOptArg, Name=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10000, LCID, 1, (8, 0), ((8, 1), (8, 1)),Path + , Name) + + def CopyFile(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg, OverWriteFiles=True): + return self._oleobj_.InvokeTypes(1202, LCID, 1, (24, 0), ((8, 1), (8, 1), (11, 49)),Source + , Destination, OverWriteFiles) + + def CopyFolder(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg, OverWriteFiles=True): + return self._oleobj_.InvokeTypes(1203, LCID, 1, (24, 0), ((8, 1), (8, 1), (11, 49)),Source + , Destination, OverWriteFiles) + + # Result is of type IFolder + def CreateFolder(self, Path=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(1120, LCID, 1, (9, 0), ((8, 1),),Path + ) + if ret is not None: + ret = Dispatch(ret, 'CreateFolder', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + # Result is of type ITextStream + def CreateTextFile(self, FileName=defaultNamedNotOptArg, Overwrite=True, Unicode=False): + ret = self._oleobj_.InvokeTypes(1101, LCID, 1, (9, 0), ((8, 1), (11, 49), (11, 49)),FileName + , Overwrite, Unicode) + if ret is not None: + ret = Dispatch(ret, 'CreateTextFile', '{53BAD8C1-E718-11CF-893D-00A0C9054228}', UnicodeToString=0) + return ret + + def DeleteFile(self, FileSpec=defaultNamedNotOptArg, Force=False): + return self._oleobj_.InvokeTypes(1200, LCID, 1, (24, 0), ((8, 1), (11, 49)),FileSpec + , Force) + + def DeleteFolder(self, FolderSpec=defaultNamedNotOptArg, Force=False): + return self._oleobj_.InvokeTypes(1201, LCID, 1, (24, 0), ((8, 1), (11, 49)),FolderSpec + , Force) + + def DriveExists(self, DriveSpec=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10015, LCID, 1, (11, 0), ((8, 1),),DriveSpec + ) + + def FileExists(self, FileSpec=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10016, LCID, 1, (11, 0), ((8, 1),),FileSpec + ) + + def FolderExists(self, FolderSpec=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10017, LCID, 1, (11, 0), ((8, 1),),FolderSpec + ) + + def GetAbsolutePathName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10002, LCID, 1, (8, 0), ((8, 1),),Path + ) + + def GetBaseName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10007, LCID, 1, (8, 0), ((8, 1),),Path + ) + + # Result is of type IDrive + def GetDrive(self, DriveSpec=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10011, LCID, 1, (9, 0), ((8, 1),),DriveSpec + ) + if ret is not None: + ret = Dispatch(ret, 'GetDrive', '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + def GetDriveName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10004, LCID, 1, (8, 0), ((8, 1),),Path + ) + + def GetExtensionName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10008, LCID, 1, (8, 0), ((8, 1),),Path + ) + + # Result is of type IFile + def GetFile(self, FilePath=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10012, LCID, 1, (9, 0), ((8, 1),),FilePath + ) + if ret is not None: + ret = Dispatch(ret, 'GetFile', '{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + def GetFileName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10006, LCID, 1, (8, 0), ((8, 1),),Path + ) + + def GetFileVersion(self, FileName=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(20010, LCID, 1, (8, 0), ((8, 1),),FileName + ) + + # Result is of type IFolder + def GetFolder(self, FolderPath=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10013, LCID, 1, (9, 0), ((8, 1),),FolderPath + ) + if ret is not None: + ret = Dispatch(ret, 'GetFolder', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + def GetParentFolderName(self, Path=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10005, LCID, 1, (8, 0), ((8, 1),),Path + ) + + # Result is of type IFolder + def GetSpecialFolder(self, SpecialFolder=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(10014, LCID, 1, (9, 0), ((3, 1),),SpecialFolder + ) + if ret is not None: + ret = Dispatch(ret, 'GetSpecialFolder', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + # Result is of type ITextStream + def GetStandardStream(self, StandardStreamType=defaultNamedNotOptArg, Unicode=False): + ret = self._oleobj_.InvokeTypes(20000, LCID, 1, (9, 0), ((3, 1), (11, 49)),StandardStreamType + , Unicode) + if ret is not None: + ret = Dispatch(ret, 'GetStandardStream', '{53BAD8C1-E718-11CF-893D-00A0C9054228}', UnicodeToString=0) + return ret + + def GetTempName(self): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10003, LCID, 1, (8, 0), (),) + + def MoveFile(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(1204, LCID, 1, (24, 0), ((8, 1), (8, 1)),Source + , Destination) + + def MoveFolder(self, Source=defaultNamedNotOptArg, Destination=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(1205, LCID, 1, (24, 0), ((8, 1), (8, 1)),Source + , Destination) + + # Result is of type ITextStream + def OpenTextFile(self, FileName=defaultNamedNotOptArg, IOMode=1, Create=False, Format=0): + ret = self._oleobj_.InvokeTypes(1100, LCID, 1, (9, 0), ((8, 1), (3, 49), (11, 49), (3, 49)),FileName + , IOMode, Create, Format) + if ret is not None: + ret = Dispatch(ret, 'OpenTextFile', '{53BAD8C1-E718-11CF-893D-00A0C9054228}', UnicodeToString=0) + return ret + + _prop_map_get_ = { + # Method 'Drives' returns object of type 'IDriveCollection' + "Drives": (10010, 2, (9, 0), (), "Drives", '{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}'), + } + _prop_map_put_ = { + } + +class IFolder(DispatchBaseClass): + CLSID = IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_clsid = IID('{C7C3F5B3-88A3-11D0-ABCB-00A0C90FFFC0}') + + def Copy(self, Destination=defaultNamedNotOptArg, OverWriteFiles=True): + return self._oleobj_.InvokeTypes(1203, LCID, 1, (24, 0), ((8, 1), (11, 49)),Destination + , OverWriteFiles) + + # Result is of type ITextStream + def CreateTextFile(self, FileName=defaultNamedNotOptArg, Overwrite=True, Unicode=False): + ret = self._oleobj_.InvokeTypes(1101, LCID, 1, (9, 0), ((8, 1), (11, 49), (11, 49)),FileName + , Overwrite, Unicode) + if ret is not None: + ret = Dispatch(ret, 'CreateTextFile', '{53BAD8C1-E718-11CF-893D-00A0C9054228}', UnicodeToString=0) + return ret + + def Delete(self, Force=False): + return self._oleobj_.InvokeTypes(1201, LCID, 1, (24, 0), ((11, 49),),Force + ) + + def Move(self, Destination=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(1205, LCID, 1, (24, 0), ((8, 1),),Destination + ) + + _prop_map_get_ = { + "Attributes": (1003, 2, (3, 0), (), "Attributes", None), + "DateCreated": (1006, 2, (7, 0), (), "DateCreated", None), + "DateLastAccessed": (1008, 2, (7, 0), (), "DateLastAccessed", None), + "DateLastModified": (1007, 2, (7, 0), (), "DateLastModified", None), + # Method 'Drive' returns object of type 'IDrive' + "Drive": (1004, 2, (9, 0), (), "Drive", '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}'), + # Method 'Files' returns object of type 'IFileCollection' + "Files": (10002, 2, (9, 0), (), "Files", '{C7C3F5A5-88A3-11D0-ABCB-00A0C90FFFC0}'), + "IsRootFolder": (10000, 2, (11, 0), (), "IsRootFolder", None), + "Name": (1000, 2, (8, 0), (), "Name", None), + # Method 'ParentFolder' returns object of type 'IFolder' + "ParentFolder": (1005, 2, (9, 0), (), "ParentFolder", '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}'), + "Path": (0, 2, (8, 0), (), "Path", None), + "ShortName": (1001, 2, (8, 0), (), "ShortName", None), + "ShortPath": (1002, 2, (8, 0), (), "ShortPath", None), + "Size": (1009, 2, (12, 0), (), "Size", None), + # Method 'SubFolders' returns object of type 'IFolderCollection' + "SubFolders": (10001, 2, (9, 0), (), "SubFolders", '{C7C3F5A3-88A3-11D0-ABCB-00A0C90FFFC0}'), + "Type": (1010, 2, (8, 0), (), "Type", None), + } + _prop_map_put_ = { + "Attributes": ((1003, LCID, 4, 0),()), + "Name": ((1000, LCID, 4, 0),()), + } + # Default property for this class is 'Path' + def __call__(self): + return self._ApplyTypes_(*(0, 2, (8, 0), (), "Path", None)) + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + +class IFolderCollection(DispatchBaseClass): + CLSID = IID('{C7C3F5A3-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_clsid = IID('{C7C3F5B4-88A3-11D0-ABCB-00A0C90FFFC0}') + + # Result is of type IFolder + def Add(self, Name=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(2, LCID, 1, (9, 0), ((8, 1),),Name + ) + if ret is not None: + ret = Dispatch(ret, 'Add', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + # Result is of type IFolder + # The method Item is actually a property, but must be used as a method to correctly pass the arguments + def Item(self, Key=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(0, LCID, 2, (9, 0), ((12, 1),),Key + ) + if ret is not None: + ret = Dispatch(ret, 'Item', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + _prop_map_get_ = { + "Count": (1, 2, (3, 0), (), "Count", None), + } + _prop_map_put_ = { + } + # Default method for this class is 'Item' + def __call__(self, Key=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(0, LCID, 2, (9, 0), ((12, 1),),Key + ) + if ret is not None: + ret = Dispatch(ret, '__call__', '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', UnicodeToString=0) + return ret + + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + def __iter__(self): + "Return a Python iterator for this object" + ob = self._oleobj_.InvokeTypes(-4,LCID,2,(13, 10),()) + return win32com.client.util.Iterator(ob) + def _NewEnum(self): + "Create an enumerator from this object" + return win32com.client.util.WrapEnum(self._oleobj_.InvokeTypes(-4,LCID,2,(13, 10),()),'{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}') + def __getitem__(self, index): + "Allow this class to be accessed as a collection" + if not self.__dict__.has_key('_enum_'): + self.__dict__['_enum_'] = self._NewEnum() + return self._enum_.__getitem__(index) + #This class has Count() property - allow len(ob) to provide this + def __len__(self): + return self._ApplyTypes_(*(1, 2, (3, 0), (), "Count", None)) + #This class has a __len__ - this is needed so 'if object:' always returns TRUE. + def __nonzero__(self): + return True + +class ITextStream(DispatchBaseClass): + CLSID = IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}') + coclass_clsid = IID('{0BB02EC0-EF49-11CF-8940-00A0C9054228}') + + def Close(self): + return self._oleobj_.InvokeTypes(10012, LCID, 1, (24, 0), (),) + + def Read(self, Characters=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10004, LCID, 1, (8, 0), ((3, 1),),Characters + ) + + def ReadAll(self): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10006, LCID, 1, (8, 0), (),) + + def ReadLine(self): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(10005, LCID, 1, (8, 0), (),) + + def Skip(self, Characters=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10010, LCID, 1, (24, 0), ((3, 1),),Characters + ) + + def SkipLine(self): + return self._oleobj_.InvokeTypes(10011, LCID, 1, (24, 0), (),) + + def Write(self, Text=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10007, LCID, 1, (24, 0), ((8, 1),),Text + ) + + def WriteBlankLines(self, Lines=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(10009, LCID, 1, (24, 0), ((3, 1),),Lines + ) + + def WriteLine(self, Text=''): + return self._ApplyTypes_(10008, 1, (24, 32), ((8, 49),), 'WriteLine', None,Text + ) + + _prop_map_get_ = { + "AtEndOfLine": (10003, 2, (11, 0), (), "AtEndOfLine", None), + "AtEndOfStream": (10002, 2, (11, 0), (), "AtEndOfStream", None), + "Column": (-529, 2, (3, 0), (), "Column", None), + "Line": (10000, 2, (3, 0), (), "Line", None), + } + _prop_map_put_ = { + } + +class IWshCollection(DispatchBaseClass): + """Generic Collection Object""" + CLSID = IID('{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_clsid = IID('{F935DC28-1CF0-11D0-ADB9-00C04FD58A0B}') + + def Count(self): + return self._oleobj_.InvokeTypes(1, LCID, 1, (3, 0), (),) + + def Item(self, Index=defaultNamedNotOptArg): + return self._ApplyTypes_(0, 1, (12, 0), ((16396, 1),), 'Item', None,Index + ) + + _prop_map_get_ = { + "length": (2, 2, (3, 0), (), "length", None), + } + _prop_map_put_ = { + } + # Default method for this class is 'Item' + def __call__(self, Index=defaultNamedNotOptArg): + return self._ApplyTypes_(0, 1, (12, 0), ((16396, 1),), '__call__', None,Index + ) + + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + def __iter__(self): + "Return a Python iterator for this object" + ob = self._oleobj_.InvokeTypes(-4,LCID,1,(13, 10),()) + return win32com.client.util.Iterator(ob) + def _NewEnum(self): + "Create an enumerator from this object" + return win32com.client.util.WrapEnum(self._oleobj_.InvokeTypes(-4,LCID,1,(13, 10),()),None) + def __getitem__(self, index): + "Allow this class to be accessed as a collection" + if not self.__dict__.has_key('_enum_'): + self.__dict__['_enum_'] = self._NewEnum() + return self._enum_.__getitem__(index) + #This class has Count() method - allow len(ob) to provide this + def __len__(self): + return self._oleobj_.InvokeTypes(1, LCID, 1, (3, 0), (),) + + #This class has a __len__ - this is needed so 'if object:' always returns TRUE. + def __nonzero__(self): + return True + +class IWshEnvironment(DispatchBaseClass): + """Environment Variables Collection Object""" + CLSID = IID('{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_clsid = IID('{F935DC2A-1CF0-11D0-ADB9-00C04FD58A0B}') + + def Count(self): + return self._oleobj_.InvokeTypes(1, LCID, 1, (3, 0), (),) + + # The method Item is actually a property, but must be used as a method to correctly pass the arguments + def Item(self, Name=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(0, LCID, 2, (8, 0), ((8, 1),),Name + ) + + def Remove(self, Name=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(1001, LCID, 1, (24, 0), ((8, 1),),Name + ) + + # The method SetItem is actually a property, but must be used as a method to correctly pass the arguments + def SetItem(self, Name=defaultNamedNotOptArg, arg1=defaultUnnamedArg): + return self._oleobj_.InvokeTypes(0, LCID, 4, (24, 0), ((8, 1), (8, 1)),Name + , arg1) + + _prop_map_get_ = { + "length": (2, 2, (3, 0), (), "length", None), + } + _prop_map_put_ = { + } + # Default method for this class is 'Item' + def __call__(self, Name=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(0, LCID, 2, (8, 0), ((8, 1),),Name + ) + + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + def __iter__(self): + "Return a Python iterator for this object" + ob = self._oleobj_.InvokeTypes(-4,LCID,1,(13, 10),()) + return win32com.client.util.Iterator(ob) + def _NewEnum(self): + "Create an enumerator from this object" + return win32com.client.util.WrapEnum(self._oleobj_.InvokeTypes(-4,LCID,1,(13, 10),()),None) + def __getitem__(self, index): + "Allow this class to be accessed as a collection" + if not self.__dict__.has_key('_enum_'): + self.__dict__['_enum_'] = self._NewEnum() + return self._enum_.__getitem__(index) + #This class has Count() method - allow len(ob) to provide this + def __len__(self): + return self._oleobj_.InvokeTypes(1, LCID, 1, (3, 0), (),) + + #This class has a __len__ - this is needed so 'if object:' always returns TRUE. + def __nonzero__(self): + return True + +class IWshExec(DispatchBaseClass): + """WSH Exec Object""" + CLSID = IID('{08FED190-BE19-11D3-A28B-00104BD35090}') + coclass_clsid = IID('{08FED191-BE19-11D3-A28B-00104BD35090}') + + def Terminate(self): + return self._oleobj_.InvokeTypes(8, LCID, 1, (24, 0), (),) + + _prop_map_get_ = { + "ExitCode": (7, 2, (3, 0), (), "ExitCode", None), + "ProcessID": (6, 2, (3, 0), (), "ProcessID", None), + "Status": (1, 2, (3, 0), (), "Status", None), + # Method 'StdErr' returns object of type 'ITextStream' + "StdErr": (5, 2, (9, 0), (), "StdErr", '{53BAD8C1-E718-11CF-893D-00A0C9054228}'), + # Method 'StdIn' returns object of type 'ITextStream' + "StdIn": (3, 2, (9, 0), (), "StdIn", '{53BAD8C1-E718-11CF-893D-00A0C9054228}'), + # Method 'StdOut' returns object of type 'ITextStream' + "StdOut": (4, 2, (9, 0), (), "StdOut", '{53BAD8C1-E718-11CF-893D-00A0C9054228}'), + } + _prop_map_put_ = { + } + +class IWshNetwork(DispatchBaseClass): + """Network Object""" + CLSID = IID('{F935DC25-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_clsid = None + + def AddPrinterConnection(self, LocalName=defaultNamedNotOptArg, RemoteName=defaultNamedNotOptArg, UpdateProfile=defaultNamedOptArg, UserName=defaultNamedOptArg + , Password=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(2000, LCID, 1, (24, 0), ((8, 1), (8, 1), (16396, 17), (16396, 17), (16396, 17)),LocalName + , RemoteName, UpdateProfile, UserName, Password) + + # Result is of type IWshCollection + def EnumNetworkDrives(self): + ret = self._oleobj_.InvokeTypes(1002, LCID, 1, (9, 0), (),) + if ret is not None: + ret = Dispatch(ret, 'EnumNetworkDrives', '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}', UnicodeToString=0) + return ret + + # Result is of type IWshCollection + def EnumPrinterConnections(self): + ret = self._oleobj_.InvokeTypes(2002, LCID, 1, (9, 0), (),) + if ret is not None: + ret = Dispatch(ret, 'EnumPrinterConnections', '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}', UnicodeToString=0) + return ret + + def MapNetworkDrive(self, LocalName=defaultNamedNotOptArg, RemoteName=defaultNamedNotOptArg, UpdateProfile=defaultNamedOptArg, UserName=defaultNamedOptArg + , Password=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1000, LCID, 1, (24, 0), ((8, 1), (8, 1), (16396, 17), (16396, 17), (16396, 17)),LocalName + , RemoteName, UpdateProfile, UserName, Password) + + def RemoveNetworkDrive(self, Name=defaultNamedNotOptArg, Force=defaultNamedOptArg, UpdateProfile=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1001, LCID, 1, (24, 0), ((8, 1), (16396, 17), (16396, 17)),Name + , Force, UpdateProfile) + + def RemovePrinterConnection(self, Name=defaultNamedNotOptArg, Force=defaultNamedOptArg, UpdateProfile=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(2001, LCID, 1, (24, 0), ((8, 1), (16396, 17), (16396, 17)),Name + , Force, UpdateProfile) + + def SetDefaultPrinter(self, Name=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(2003, LCID, 1, (24, 0), ((8, 1),),Name + ) + + _prop_map_get_ = { + "ComputerName": (1610743811, 2, (8, 0), (), "ComputerName", None), + "Organization": (1610743812, 2, (8, 0), (), "Organization", None), + "Site": (1610743813, 2, (8, 0), (), "Site", None), + "UserDomain": (1610743808, 2, (8, 0), (), "UserDomain", None), + "UserName": (1610743809, 2, (8, 0), (), "UserName", None), + "UserProfile": (1610743810, 2, (8, 0), (), "UserProfile", None), + } + _prop_map_put_ = { + } + +class IWshNetwork2(DispatchBaseClass): + """Network Object""" + CLSID = IID('{24BE5A31-EDFE-11D2-B933-00104B365C9F}') + coclass_clsid = IID('{F935DC26-1CF0-11D0-ADB9-00C04FD58A0B}') + + def AddPrinterConnection(self, LocalName=defaultNamedNotOptArg, RemoteName=defaultNamedNotOptArg, UpdateProfile=defaultNamedOptArg, UserName=defaultNamedOptArg + , Password=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(2000, LCID, 1, (24, 0), ((8, 1), (8, 1), (16396, 17), (16396, 17), (16396, 17)),LocalName + , RemoteName, UpdateProfile, UserName, Password) + + def AddWindowsPrinterConnection(self, PrinterName=defaultNamedNotOptArg, DriverName='', Port='LPT1'): + return self._ApplyTypes_(2004, 1, (24, 32), ((8, 1), (8, 49), (8, 49)), 'AddWindowsPrinterConnection', None,PrinterName + , DriverName, Port) + + # Result is of type IWshCollection + def EnumNetworkDrives(self): + ret = self._oleobj_.InvokeTypes(1002, LCID, 1, (9, 0), (),) + if ret is not None: + ret = Dispatch(ret, 'EnumNetworkDrives', '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}', UnicodeToString=0) + return ret + + # Result is of type IWshCollection + def EnumPrinterConnections(self): + ret = self._oleobj_.InvokeTypes(2002, LCID, 1, (9, 0), (),) + if ret is not None: + ret = Dispatch(ret, 'EnumPrinterConnections', '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}', UnicodeToString=0) + return ret + + def MapNetworkDrive(self, LocalName=defaultNamedNotOptArg, RemoteName=defaultNamedNotOptArg, UpdateProfile=defaultNamedOptArg, UserName=defaultNamedOptArg + , Password=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1000, LCID, 1, (24, 0), ((8, 1), (8, 1), (16396, 17), (16396, 17), (16396, 17)),LocalName + , RemoteName, UpdateProfile, UserName, Password) + + def RemoveNetworkDrive(self, Name=defaultNamedNotOptArg, Force=defaultNamedOptArg, UpdateProfile=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1001, LCID, 1, (24, 0), ((8, 1), (16396, 17), (16396, 17)),Name + , Force, UpdateProfile) + + def RemovePrinterConnection(self, Name=defaultNamedNotOptArg, Force=defaultNamedOptArg, UpdateProfile=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(2001, LCID, 1, (24, 0), ((8, 1), (16396, 17), (16396, 17)),Name + , Force, UpdateProfile) + + def SetDefaultPrinter(self, Name=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(2003, LCID, 1, (24, 0), ((8, 1),),Name + ) + + _prop_map_get_ = { + "ComputerName": (1610743811, 2, (8, 0), (), "ComputerName", None), + "Organization": (1610743812, 2, (8, 0), (), "Organization", None), + "Site": (1610743813, 2, (8, 0), (), "Site", None), + "UserDomain": (1610743808, 2, (8, 0), (), "UserDomain", None), + "UserName": (1610743809, 2, (8, 0), (), "UserName", None), + "UserProfile": (1610743810, 2, (8, 0), (), "UserProfile", None), + } + _prop_map_put_ = { + } + +class IWshShell(DispatchBaseClass): + """Shell Object Interface""" + CLSID = IID('{F935DC21-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_clsid = None + + def CreateShortcut(self, PathLink=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(1002, LCID, 1, (9, 0), ((8, 1),),PathLink + ) + if ret is not None: + ret = Dispatch(ret, 'CreateShortcut', None, UnicodeToString=0) + return ret + + def ExpandEnvironmentStrings(self, Src=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(1006, LCID, 1, (8, 0), ((8, 1),),Src + ) + + # Result is of type IWshEnvironment + # The method GetEnvironment is actually a property, but must be used as a method to correctly pass the arguments + def GetEnvironment(self, Type=defaultNamedOptArg): + ret = self._oleobj_.InvokeTypes(200, LCID, 2, (9, 0), ((16396, 17),),Type + ) + if ret is not None: + ret = Dispatch(ret, 'GetEnvironment', '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}', UnicodeToString=0) + return ret + + def Popup(self, Text=defaultNamedNotOptArg, SecondsToWait=defaultNamedOptArg, Title=defaultNamedOptArg, Type=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1001, LCID, 1, (3, 0), ((8, 1), (16396, 17), (16396, 17), (16396, 17)),Text + , SecondsToWait, Title, Type) + + def RegDelete(self, Name=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(2002, LCID, 1, (24, 0), ((8, 1),),Name + ) + + def RegRead(self, Name=defaultNamedNotOptArg): + return self._ApplyTypes_(2000, 1, (12, 0), ((8, 1),), 'RegRead', None,Name + ) + + def RegWrite(self, Name=defaultNamedNotOptArg, Value=defaultNamedNotOptArg, Type=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(2001, LCID, 1, (24, 0), ((8, 1), (16396, 1), (16396, 17)),Name + , Value, Type) + + def Run(self, Command=defaultNamedNotOptArg, WindowStyle=defaultNamedOptArg, WaitOnReturn=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1000, LCID, 1, (3, 0), ((8, 1), (16396, 17), (16396, 17)),Command + , WindowStyle, WaitOnReturn) + + _prop_map_get_ = { + # Method 'Environment' returns object of type 'IWshEnvironment' + "Environment": (200, 2, (9, 0), ((16396, 17),), "Environment", '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}'), + # Method 'SpecialFolders' returns object of type 'IWshCollection' + "SpecialFolders": (100, 2, (9, 0), (), "SpecialFolders", '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}'), + } + _prop_map_put_ = { + } + +class IWshShell2(DispatchBaseClass): + """Shell Object Interface""" + CLSID = IID('{24BE5A30-EDFE-11D2-B933-00104B365C9F}') + coclass_clsid = None + + def AppActivate(self, App=defaultNamedNotOptArg, Wait=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(3010, LCID, 1, (11, 0), ((16396, 1), (16396, 17)),App + , Wait) + + def CreateShortcut(self, PathLink=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(1002, LCID, 1, (9, 0), ((8, 1),),PathLink + ) + if ret is not None: + ret = Dispatch(ret, 'CreateShortcut', None, UnicodeToString=0) + return ret + + def ExpandEnvironmentStrings(self, Src=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(1006, LCID, 1, (8, 0), ((8, 1),),Src + ) + + # Result is of type IWshEnvironment + # The method GetEnvironment is actually a property, but must be used as a method to correctly pass the arguments + def GetEnvironment(self, Type=defaultNamedOptArg): + ret = self._oleobj_.InvokeTypes(200, LCID, 2, (9, 0), ((16396, 17),),Type + ) + if ret is not None: + ret = Dispatch(ret, 'GetEnvironment', '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}', UnicodeToString=0) + return ret + + def LogEvent(self, Type=defaultNamedNotOptArg, Message=defaultNamedNotOptArg, Target=''): + return self._ApplyTypes_(3000, 1, (11, 32), ((16396, 1), (8, 1), (8, 49)), 'LogEvent', None,Type + , Message, Target) + + def Popup(self, Text=defaultNamedNotOptArg, SecondsToWait=defaultNamedOptArg, Title=defaultNamedOptArg, Type=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1001, LCID, 1, (3, 0), ((8, 1), (16396, 17), (16396, 17), (16396, 17)),Text + , SecondsToWait, Title, Type) + + def RegDelete(self, Name=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(2002, LCID, 1, (24, 0), ((8, 1),),Name + ) + + def RegRead(self, Name=defaultNamedNotOptArg): + return self._ApplyTypes_(2000, 1, (12, 0), ((8, 1),), 'RegRead', None,Name + ) + + def RegWrite(self, Name=defaultNamedNotOptArg, Value=defaultNamedNotOptArg, Type=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(2001, LCID, 1, (24, 0), ((8, 1), (16396, 1), (16396, 17)),Name + , Value, Type) + + def Run(self, Command=defaultNamedNotOptArg, WindowStyle=defaultNamedOptArg, WaitOnReturn=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1000, LCID, 1, (3, 0), ((8, 1), (16396, 17), (16396, 17)),Command + , WindowStyle, WaitOnReturn) + + def SendKeys(self, Keys=defaultNamedNotOptArg, Wait=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(3011, LCID, 1, (24, 0), ((8, 1), (16396, 17)),Keys + , Wait) + + _prop_map_get_ = { + # Method 'Environment' returns object of type 'IWshEnvironment' + "Environment": (200, 2, (9, 0), ((16396, 17),), "Environment", '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}'), + # Method 'SpecialFolders' returns object of type 'IWshCollection' + "SpecialFolders": (100, 2, (9, 0), (), "SpecialFolders", '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}'), + } + _prop_map_put_ = { + } + +class IWshShell3(DispatchBaseClass): + """Shell Object Interface""" + CLSID = IID('{41904400-BE18-11D3-A28B-00104BD35090}') + coclass_clsid = IID('{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}') + + def AppActivate(self, App=defaultNamedNotOptArg, Wait=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(3010, LCID, 1, (11, 0), ((16396, 1), (16396, 17)),App + , Wait) + + def CreateShortcut(self, PathLink=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(1002, LCID, 1, (9, 0), ((8, 1),),PathLink + ) + if ret is not None: + ret = Dispatch(ret, 'CreateShortcut', None, UnicodeToString=0) + return ret + + # Result is of type IWshExec + def Exec(self, Command=defaultNamedNotOptArg): + ret = self._oleobj_.InvokeTypes(3012, LCID, 1, (9, 0), ((8, 1),),Command + ) + if ret is not None: + ret = Dispatch(ret, 'Exec', '{08FED190-BE19-11D3-A28B-00104BD35090}', UnicodeToString=0) + return ret + + def ExpandEnvironmentStrings(self, Src=defaultNamedNotOptArg): + # Result is a Unicode object - return as-is for this version of Python + return self._oleobj_.InvokeTypes(1006, LCID, 1, (8, 0), ((8, 1),),Src + ) + + # Result is of type IWshEnvironment + # The method GetEnvironment is actually a property, but must be used as a method to correctly pass the arguments + def GetEnvironment(self, Type=defaultNamedOptArg): + ret = self._oleobj_.InvokeTypes(200, LCID, 2, (9, 0), ((16396, 17),),Type + ) + if ret is not None: + ret = Dispatch(ret, 'GetEnvironment', '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}', UnicodeToString=0) + return ret + + def LogEvent(self, Type=defaultNamedNotOptArg, Message=defaultNamedNotOptArg, Target=''): + return self._ApplyTypes_(3000, 1, (11, 32), ((16396, 1), (8, 1), (8, 49)), 'LogEvent', None,Type + , Message, Target) + + def Popup(self, Text=defaultNamedNotOptArg, SecondsToWait=defaultNamedOptArg, Title=defaultNamedOptArg, Type=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1001, LCID, 1, (3, 0), ((8, 1), (16396, 17), (16396, 17), (16396, 17)),Text + , SecondsToWait, Title, Type) + + def RegDelete(self, Name=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(2002, LCID, 1, (24, 0), ((8, 1),),Name + ) + + def RegRead(self, Name=defaultNamedNotOptArg): + return self._ApplyTypes_(2000, 1, (12, 0), ((8, 1),), 'RegRead', None,Name + ) + + def RegWrite(self, Name=defaultNamedNotOptArg, Value=defaultNamedNotOptArg, Type=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(2001, LCID, 1, (24, 0), ((8, 1), (16396, 1), (16396, 17)),Name + , Value, Type) + + def Run(self, Command=defaultNamedNotOptArg, WindowStyle=defaultNamedOptArg, WaitOnReturn=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(1000, LCID, 1, (3, 0), ((8, 1), (16396, 17), (16396, 17)),Command + , WindowStyle, WaitOnReturn) + + def SendKeys(self, Keys=defaultNamedNotOptArg, Wait=defaultNamedOptArg): + return self._oleobj_.InvokeTypes(3011, LCID, 1, (24, 0), ((8, 1), (16396, 17)),Keys + , Wait) + + _prop_map_get_ = { + "CurrentDirectory": (3013, 2, (8, 0), (), "CurrentDirectory", None), + # Method 'Environment' returns object of type 'IWshEnvironment' + "Environment": (200, 2, (9, 0), ((16396, 17),), "Environment", '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}'), + # Method 'SpecialFolders' returns object of type 'IWshCollection' + "SpecialFolders": (100, 2, (9, 0), (), "SpecialFolders", '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}'), + } + _prop_map_put_ = { + "CurrentDirectory": ((3013, LCID, 4, 0),()), + } + +class IWshShortcut(DispatchBaseClass): + """Shortcut Object""" + CLSID = IID('{F935DC23-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_clsid = IID('{F935DC24-1CF0-11D0-ADB9-00C04FD58A0B}') + + def Load(self, PathLink=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(2000, LCID, 1, (24, 0), ((8, 1),),PathLink + ) + + def Save(self): + return self._oleobj_.InvokeTypes(2001, LCID, 1, (24, 0), (),) + + _prop_map_get_ = { + "Arguments": (1000, 2, (8, 0), (), "Arguments", None), + "Description": (1001, 2, (8, 0), (), "Description", None), + "FullName": (0, 2, (8, 0), (), "FullName", None), + "Hotkey": (1002, 2, (8, 0), (), "Hotkey", None), + "IconLocation": (1003, 2, (8, 0), (), "IconLocation", None), + "TargetPath": (1005, 2, (8, 0), (), "TargetPath", None), + "WindowStyle": (1006, 2, (3, 0), (), "WindowStyle", None), + "WorkingDirectory": (1007, 2, (8, 0), (), "WorkingDirectory", None), + } + _prop_map_put_ = { + "Arguments": ((1000, LCID, 4, 0),()), + "Description": ((1001, LCID, 4, 0),()), + "Hotkey": ((1002, LCID, 4, 0),()), + "IconLocation": ((1003, LCID, 4, 0),()), + "RelativePath": ((1004, LCID, 4, 0),()), + "TargetPath": ((1005, LCID, 4, 0),()), + "WindowStyle": ((1006, LCID, 4, 0),()), + "WorkingDirectory": ((1007, LCID, 4, 0),()), + } + # Default property for this class is 'FullName' + def __call__(self): + return self._ApplyTypes_(*(0, 2, (8, 0), (), "FullName", None)) + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + +class IWshURLShortcut(DispatchBaseClass): + """URLShortcut Object""" + CLSID = IID('{F935DC2B-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_clsid = IID('{F935DC2C-1CF0-11D0-ADB9-00C04FD58A0B}') + + def Load(self, PathLink=defaultNamedNotOptArg): + return self._oleobj_.InvokeTypes(2000, LCID, 1, (24, 0), ((8, 1),),PathLink + ) + + def Save(self): + return self._oleobj_.InvokeTypes(2001, LCID, 1, (24, 0), (),) + + _prop_map_get_ = { + "FullName": (0, 2, (8, 0), (), "FullName", None), + "TargetPath": (1005, 2, (8, 0), (), "TargetPath", None), + } + _prop_map_put_ = { + "TargetPath": ((1005, LCID, 4, 0),()), + } + # Default property for this class is 'FullName' + def __call__(self): + return self._ApplyTypes_(*(0, 2, (8, 0), (), "FullName", None)) + # str(ob) and int(ob) will use __call__ + def __unicode__(self, *args): + try: + return unicode(self.__call__(*args)) + except pythoncom.com_error: + return repr(self) + def __str__(self, *args): + return str(self.__unicode__(*args)) + def __int__(self, *args): + return int(self.__call__(*args)) + +from win32com.client import CoClassBaseClass +class Drive(CoClassBaseClass): # A CoClass + CLSID = IID('{C7C3F5B1-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_sources = [ + ] + coclass_interfaces = [ + IDrive, + ] + default_interface = IDrive + +class Drives(CoClassBaseClass): # A CoClass + CLSID = IID('{C7C3F5B2-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_sources = [ + ] + coclass_interfaces = [ + IDriveCollection, + ] + default_interface = IDriveCollection + +class File(CoClassBaseClass): # A CoClass + CLSID = IID('{C7C3F5B5-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_sources = [ + ] + coclass_interfaces = [ + IFile, + ] + default_interface = IFile + +# This CoClass is known by the name 'Scripting.FileSystemObject' +class FileSystemObject(CoClassBaseClass): # A CoClass + CLSID = IID('{0D43FE01-F093-11CF-8940-00A0C9054228}') + coclass_sources = [ + ] + coclass_interfaces = [ + IFileSystem3, + ] + default_interface = IFileSystem3 + +class Files(CoClassBaseClass): # A CoClass + CLSID = IID('{C7C3F5B6-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_sources = [ + ] + coclass_interfaces = [ + IFileCollection, + ] + default_interface = IFileCollection + +class Folder(CoClassBaseClass): # A CoClass + CLSID = IID('{C7C3F5B3-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_sources = [ + ] + coclass_interfaces = [ + IFolder, + ] + default_interface = IFolder + +class Folders(CoClassBaseClass): # A CoClass + CLSID = IID('{C7C3F5B4-88A3-11D0-ABCB-00A0C90FFFC0}') + coclass_sources = [ + ] + coclass_interfaces = [ + IFolderCollection, + ] + default_interface = IFolderCollection + +class IWshCollection_Class(CoClassBaseClass): # A CoClass + # Generic Collection Object + CLSID = IID('{F935DC28-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshCollection, + ] + default_interface = IWshCollection + +class IWshEnvironment_Class(CoClassBaseClass): # A CoClass + # Environment Variables Collection Object + CLSID = IID('{F935DC2A-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshEnvironment, + ] + default_interface = IWshEnvironment + +# This CoClass is known by the name 'WScript.Network.1' +class IWshNetwork_Class(CoClassBaseClass): # A CoClass + # Network Object + CLSID = IID('{F935DC26-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshNetwork2, + ] + default_interface = IWshNetwork2 + +# This CoClass is known by the name 'WScript.Shell.1' +class IWshShell_Class(CoClassBaseClass): # A CoClass + # Shell Object + CLSID = IID('{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshShell3, + ] + default_interface = IWshShell3 + +class IWshShortcut_Class(CoClassBaseClass): # A CoClass + # Shortcut Object + CLSID = IID('{F935DC24-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshShortcut, + ] + default_interface = IWshShortcut + +class IWshURLShortcut_Class(CoClassBaseClass): # A CoClass + # URLShortcut Object + CLSID = IID('{F935DC2C-1CF0-11D0-ADB9-00C04FD58A0B}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshURLShortcut, + ] + default_interface = IWshURLShortcut + +class TextStream(CoClassBaseClass): # A CoClass + CLSID = IID('{0BB02EC0-EF49-11CF-8940-00A0C9054228}') + coclass_sources = [ + ] + coclass_interfaces = [ + ITextStream, + ] + default_interface = ITextStream + +class WshCollection(CoClassBaseClass): # A CoClass + # Generic Collection Object + CLSID = IID('{387DAFF4-DA03-44D2-B0D1-80C927C905AC}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshCollection, + ] + default_interface = IWshCollection + +class WshEnvironment(CoClassBaseClass): # A CoClass + # Environment Variables Collection Object + CLSID = IID('{F48229AF-E28C-42B5-BB92-E114E62BDD54}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshEnvironment, + ] + default_interface = IWshEnvironment + +class WshExec(CoClassBaseClass): # A CoClass + # WSHExec object + CLSID = IID('{08FED191-BE19-11D3-A28B-00104BD35090}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshExec, + ] + default_interface = IWshExec + +# This CoClass is known by the name 'WScript.Network.1' +class WshNetwork(CoClassBaseClass): # A CoClass + # Network Object + CLSID = IID('{093FF999-1EA0-4079-9525-9614C3504B74}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshNetwork2, + ] + default_interface = IWshNetwork2 + +# This CoClass is known by the name 'WScript.Shell.1' +class WshShell(CoClassBaseClass): # A CoClass + # Shell Object + CLSID = IID('{72C24DD5-D70A-438B-8A42-98424B88AFB8}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshShell3, + ] + default_interface = IWshShell3 + +class WshShortcut(CoClassBaseClass): # A CoClass + # Shortcut Object + CLSID = IID('{A548B8E4-51D5-4661-8824-DAA1D893DFB2}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshShortcut, + ] + default_interface = IWshShortcut + +class WshURLShortcut(CoClassBaseClass): # A CoClass + # URLShortcut Object + CLSID = IID('{50E13488-6F1E-4450-96B0-873755403955}') + coclass_sources = [ + ] + coclass_interfaces = [ + IWshURLShortcut, + ] + default_interface = IWshURLShortcut + +IDrive_vtables_dispatch_ = 1 +IDrive_vtables_ = [ + (( 'Path' , 'pbstrPath' , ), 0, (0, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'DriveLetter' , 'pbstrLetter' , ), 10000, (10000, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'ShareName' , 'pbstrShareName' , ), 10001, (10001, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'DriveType' , 'pdt' , ), 10002, (10002, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'RootFolder' , 'ppfolder' , ), 10003, (10003, (), [ (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'AvailableSpace' , 'pvarAvail' , ), 10005, (10005, (), [ (16396, 10, None, None) , ], 1 , 2 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'FreeSpace' , 'pvarFree' , ), 10004, (10004, (), [ (16396, 10, None, None) , ], 1 , 2 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), + (( 'TotalSize' , 'pvarTotal' , ), 10006, (10006, (), [ (16396, 10, None, None) , ], 1 , 2 , 4 , 0 , 56 , (3, 0, None, None) , 0 , )), + (( 'VolumeName' , 'pbstrName' , ), 10007, (10007, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 60 , (3, 0, None, None) , 0 , )), + (( 'VolumeName' , 'pbstrName' , ), 10007, (10007, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 64 , (3, 0, None, None) , 0 , )), + (( 'FileSystem' , 'pbstrFileSystem' , ), 10008, (10008, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 68 , (3, 0, None, None) , 0 , )), + (( 'SerialNumber' , 'pulSerialNumber' , ), 10009, (10009, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 72 , (3, 0, None, None) , 0 , )), + (( 'IsReady' , 'pfReady' , ), 10010, (10010, (), [ (16395, 10, None, None) , ], 1 , 2 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), +] + +IDriveCollection_vtables_dispatch_ = 1 +IDriveCollection_vtables_ = [ + (( 'Item' , 'Key' , 'ppdrive' , ), 0, (0, (), [ (12, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( '_NewEnum' , 'ppenum' , ), -4, (-4, (), [ (16397, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 65 , )), + (( 'Count' , 'plCount' , ), 1, (1, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), +] + +IFile_vtables_dispatch_ = 1 +IFile_vtables_ = [ + (( 'Path' , 'pbstrPath' , ), 0, (0, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Name' , 'pbstrName' , ), 1000, (1000, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'Name' , 'pbstrName' , ), 1000, (1000, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'ShortPath' , 'pbstrPath' , ), 1002, (1002, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'ShortName' , 'pbstrName' , ), 1001, (1001, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'Drive' , 'ppdrive' , ), 1004, (1004, (), [ (16393, 10, None, "IID('{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'ParentFolder' , 'ppfolder' , ), 1005, (1005, (), [ (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), + (( 'Attributes' , 'pfa' , ), 1003, (1003, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 56 , (3, 0, None, None) , 0 , )), + (( 'Attributes' , 'pfa' , ), 1003, (1003, (), [ (3, 1, None, None) , ], 1 , 4 , 4 , 0 , 60 , (3, 0, None, None) , 0 , )), + (( 'DateCreated' , 'pdate' , ), 1006, (1006, (), [ (16391, 10, None, None) , ], 1 , 2 , 4 , 0 , 64 , (3, 0, None, None) , 0 , )), + (( 'DateLastModified' , 'pdate' , ), 1007, (1007, (), [ (16391, 10, None, None) , ], 1 , 2 , 4 , 0 , 68 , (3, 0, None, None) , 0 , )), + (( 'DateLastAccessed' , 'pdate' , ), 1008, (1008, (), [ (16391, 10, None, None) , ], 1 , 2 , 4 , 0 , 72 , (3, 0, None, None) , 0 , )), + (( 'Size' , 'pvarSize' , ), 1009, (1009, (), [ (16396, 10, None, None) , ], 1 , 2 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), + (( 'Type' , 'pbstrType' , ), 1010, (1010, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 80 , (3, 0, None, None) , 0 , )), + (( 'Delete' , 'Force' , ), 1200, (1200, (), [ (11, 49, 'False', None) , ], 1 , 1 , 4 , 0 , 84 , (3, 0, None, None) , 0 , )), + (( 'Copy' , 'Destination' , 'OverWriteFiles' , ), 1202, (1202, (), [ (8, 1, None, None) , + (11, 49, 'True', None) , ], 1 , 1 , 4 , 0 , 88 , (3, 0, None, None) , 0 , )), + (( 'Move' , 'Destination' , ), 1204, (1204, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 92 , (3, 0, None, None) , 0 , )), + (( 'OpenAsTextStream' , 'IOMode' , 'Format' , 'ppts' , ), 1100, (1100, (), [ + (3, 49, '1', None) , (3, 49, '0', None) , (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 1 , 4 , 0 , 96 , (3, 0, None, None) , 0 , )), +] + +IFileCollection_vtables_dispatch_ = 1 +IFileCollection_vtables_ = [ + (( 'Item' , 'Key' , 'ppfile' , ), 0, (0, (), [ (12, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( '_NewEnum' , 'ppenum' , ), -4, (-4, (), [ (16397, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 65 , )), + (( 'Count' , 'plCount' , ), 1, (1, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), +] + +IFileSystem_vtables_dispatch_ = 1 +IFileSystem_vtables_ = [ + (( 'Drives' , 'ppdrives' , ), 10010, (10010, (), [ (16393, 10, None, "IID('{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'BuildPath' , 'Path' , 'Name' , 'pbstrResult' , ), 10000, (10000, (), [ + (8, 1, None, None) , (8, 1, None, None) , (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'GetDriveName' , 'Path' , 'pbstrResult' , ), 10004, (10004, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'GetParentFolderName' , 'Path' , 'pbstrResult' , ), 10005, (10005, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'GetFileName' , 'Path' , 'pbstrResult' , ), 10006, (10006, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'GetBaseName' , 'Path' , 'pbstrResult' , ), 10007, (10007, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'GetExtensionName' , 'Path' , 'pbstrResult' , ), 10008, (10008, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), + (( 'GetAbsolutePathName' , 'Path' , 'pbstrResult' , ), 10002, (10002, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 56 , (3, 0, None, None) , 0 , )), + (( 'GetTempName' , 'pbstrResult' , ), 10003, (10003, (), [ (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 60 , (3, 0, None, None) , 0 , )), + (( 'DriveExists' , 'DriveSpec' , 'pfExists' , ), 10015, (10015, (), [ (8, 1, None, None) , + (16395, 10, None, None) , ], 1 , 1 , 4 , 0 , 64 , (3, 0, None, None) , 0 , )), + (( 'FileExists' , 'FileSpec' , 'pfExists' , ), 10016, (10016, (), [ (8, 1, None, None) , + (16395, 10, None, None) , ], 1 , 1 , 4 , 0 , 68 , (3, 0, None, None) , 0 , )), + (( 'FolderExists' , 'FolderSpec' , 'pfExists' , ), 10017, (10017, (), [ (8, 1, None, None) , + (16395, 10, None, None) , ], 1 , 1 , 4 , 0 , 72 , (3, 0, None, None) , 0 , )), + (( 'GetDrive' , 'DriveSpec' , 'ppdrive' , ), 10011, (10011, (), [ (8, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 1 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), + (( 'GetFile' , 'FilePath' , 'ppfile' , ), 10012, (10012, (), [ (8, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 1 , 4 , 0 , 80 , (3, 0, None, None) , 0 , )), + (( 'GetFolder' , 'FolderPath' , 'ppfolder' , ), 10013, (10013, (), [ (8, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 1 , 4 , 0 , 84 , (3, 0, None, None) , 0 , )), + (( 'GetSpecialFolder' , 'SpecialFolder' , 'ppfolder' , ), 10014, (10014, (), [ (3, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 1 , 4 , 0 , 88 , (3, 0, None, None) , 0 , )), + (( 'DeleteFile' , 'FileSpec' , 'Force' , ), 1200, (1200, (), [ (8, 1, None, None) , + (11, 49, 'False', None) , ], 1 , 1 , 4 , 0 , 92 , (3, 0, None, None) , 0 , )), + (( 'DeleteFolder' , 'FolderSpec' , 'Force' , ), 1201, (1201, (), [ (8, 1, None, None) , + (11, 49, 'False', None) , ], 1 , 1 , 4 , 0 , 96 , (3, 0, None, None) , 0 , )), + (( 'MoveFile' , 'Source' , 'Destination' , ), 1204, (1204, (), [ (8, 1, None, None) , + (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 100 , (3, 0, None, None) , 0 , )), + (( 'MoveFolder' , 'Source' , 'Destination' , ), 1205, (1205, (), [ (8, 1, None, None) , + (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 104 , (3, 0, None, None) , 0 , )), + (( 'CopyFile' , 'Source' , 'Destination' , 'OverWriteFiles' , ), 1202, (1202, (), [ + (8, 1, None, None) , (8, 1, None, None) , (11, 49, 'True', None) , ], 1 , 1 , 4 , 0 , 108 , (3, 0, None, None) , 0 , )), + (( 'CopyFolder' , 'Source' , 'Destination' , 'OverWriteFiles' , ), 1203, (1203, (), [ + (8, 1, None, None) , (8, 1, None, None) , (11, 49, 'True', None) , ], 1 , 1 , 4 , 0 , 112 , (3, 0, None, None) , 0 , )), + (( 'CreateFolder' , 'Path' , 'ppfolder' , ), 1120, (1120, (), [ (8, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 1 , 4 , 0 , 116 , (3, 0, None, None) , 0 , )), + (( 'CreateTextFile' , 'FileName' , 'Overwrite' , 'Unicode' , 'ppts' , + ), 1101, (1101, (), [ (8, 1, None, None) , (11, 49, 'True', None) , (11, 49, 'False', None) , (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 1 , 4 , 0 , 120 , (3, 0, None, None) , 0 , )), + (( 'OpenTextFile' , 'FileName' , 'IOMode' , 'Create' , 'Format' , + 'ppts' , ), 1100, (1100, (), [ (8, 1, None, None) , (3, 49, '1', None) , (11, 49, 'False', None) , + (3, 49, '0', None) , (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 1 , 4 , 0 , 124 , (3, 0, None, None) , 0 , )), +] + +IFileSystem3_vtables_dispatch_ = 1 +IFileSystem3_vtables_ = [ + (( 'GetStandardStream' , 'StandardStreamType' , 'Unicode' , 'ppts' , ), 20000, (20000, (), [ + (3, 1, None, None) , (11, 49, 'False', None) , (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 1 , 4 , 0 , 128 , (3, 0, None, None) , 0 , )), + (( 'GetFileVersion' , 'FileName' , 'FileVersion' , ), 20010, (20010, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 132 , (3, 0, None, None) , 0 , )), +] + +IFolder_vtables_dispatch_ = 1 +IFolder_vtables_ = [ + (( 'Path' , 'pbstrPath' , ), 0, (0, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Name' , 'pbstrName' , ), 1000, (1000, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'Name' , 'pbstrName' , ), 1000, (1000, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'ShortPath' , 'pbstrPath' , ), 1002, (1002, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'ShortName' , 'pbstrName' , ), 1001, (1001, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'Drive' , 'ppdrive' , ), 1004, (1004, (), [ (16393, 10, None, "IID('{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'ParentFolder' , 'ppfolder' , ), 1005, (1005, (), [ (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), + (( 'Attributes' , 'pfa' , ), 1003, (1003, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 56 , (3, 0, None, None) , 0 , )), + (( 'Attributes' , 'pfa' , ), 1003, (1003, (), [ (3, 1, None, None) , ], 1 , 4 , 4 , 0 , 60 , (3, 0, None, None) , 0 , )), + (( 'DateCreated' , 'pdate' , ), 1006, (1006, (), [ (16391, 10, None, None) , ], 1 , 2 , 4 , 0 , 64 , (3, 0, None, None) , 0 , )), + (( 'DateLastModified' , 'pdate' , ), 1007, (1007, (), [ (16391, 10, None, None) , ], 1 , 2 , 4 , 0 , 68 , (3, 0, None, None) , 0 , )), + (( 'DateLastAccessed' , 'pdate' , ), 1008, (1008, (), [ (16391, 10, None, None) , ], 1 , 2 , 4 , 0 , 72 , (3, 0, None, None) , 0 , )), + (( 'Type' , 'pbstrType' , ), 1010, (1010, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), + (( 'Delete' , 'Force' , ), 1201, (1201, (), [ (11, 49, 'False', None) , ], 1 , 1 , 4 , 0 , 80 , (3, 0, None, None) , 0 , )), + (( 'Copy' , 'Destination' , 'OverWriteFiles' , ), 1203, (1203, (), [ (8, 1, None, None) , + (11, 49, 'True', None) , ], 1 , 1 , 4 , 0 , 84 , (3, 0, None, None) , 0 , )), + (( 'Move' , 'Destination' , ), 1205, (1205, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 88 , (3, 0, None, None) , 0 , )), + (( 'IsRootFolder' , 'pfRootFolder' , ), 10000, (10000, (), [ (16395, 10, None, None) , ], 1 , 2 , 4 , 0 , 92 , (3, 0, None, None) , 0 , )), + (( 'Size' , 'pvarSize' , ), 1009, (1009, (), [ (16396, 10, None, None) , ], 1 , 2 , 4 , 0 , 96 , (3, 0, None, None) , 0 , )), + (( 'SubFolders' , 'ppfolders' , ), 10001, (10001, (), [ (16393, 10, None, "IID('{C7C3F5A3-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 100 , (3, 0, None, None) , 0 , )), + (( 'Files' , 'ppfiles' , ), 10002, (10002, (), [ (16393, 10, None, "IID('{C7C3F5A5-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 104 , (3, 0, None, None) , 0 , )), + (( 'CreateTextFile' , 'FileName' , 'Overwrite' , 'Unicode' , 'ppts' , + ), 1101, (1101, (), [ (8, 1, None, None) , (11, 49, 'True', None) , (11, 49, 'False', None) , (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 1 , 4 , 0 , 108 , (3, 0, None, None) , 0 , )), +] + +IFolderCollection_vtables_dispatch_ = 1 +IFolderCollection_vtables_ = [ + (( 'Add' , 'Name' , 'ppfolder' , ), 2, (2, (), [ (8, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 1 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Item' , 'Key' , 'ppfolder' , ), 0, (0, (), [ (12, 1, None, None) , + (16393, 10, None, "IID('{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}')") , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( '_NewEnum' , 'ppenum' , ), -4, (-4, (), [ (16397, 10, None, None) , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 65 , )), + (( 'Count' , 'plCount' , ), 1, (1, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), +] + +ITextStream_vtables_dispatch_ = 1 +ITextStream_vtables_ = [ + (( 'Line' , 'Line' , ), 10000, (10000, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Column' , 'Column' , ), -529, (-529, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'AtEndOfStream' , 'EOS' , ), 10002, (10002, (), [ (16395, 10, None, None) , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'AtEndOfLine' , 'EOL' , ), 10003, (10003, (), [ (16395, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'Read' , 'Characters' , 'Text' , ), 10004, (10004, (), [ (3, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'ReadLine' , 'Text' , ), 10005, (10005, (), [ (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'ReadAll' , 'Text' , ), 10006, (10006, (), [ (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), + (( 'Write' , 'Text' , ), 10007, (10007, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 56 , (3, 0, None, None) , 0 , )), + (( 'WriteLine' , 'Text' , ), 10008, (10008, (), [ (8, 49, "''", None) , ], 1 , 1 , 4 , 0 , 60 , (3, 32, None, None) , 0 , )), + (( 'WriteBlankLines' , 'Lines' , ), 10009, (10009, (), [ (3, 1, None, None) , ], 1 , 1 , 4 , 0 , 64 , (3, 0, None, None) , 0 , )), + (( 'Skip' , 'Characters' , ), 10010, (10010, (), [ (3, 1, None, None) , ], 1 , 1 , 4 , 0 , 68 , (3, 0, None, None) , 0 , )), + (( 'SkipLine' , ), 10011, (10011, (), [ ], 1 , 1 , 4 , 0 , 72 , (3, 0, None, None) , 0 , )), + (( 'Close' , ), 10012, (10012, (), [ ], 1 , 1 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), +] + +IWshCollection_vtables_dispatch_ = 1 +IWshCollection_vtables_ = [ + (( 'Item' , 'Index' , 'out_Value' , ), 0, (0, (), [ (16396, 1, None, None) , + (16396, 10, None, None) , ], 1 , 1 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Count' , 'out_Count' , ), 1, (1, (), [ (16387, 10, None, None) , ], 1 , 1 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'length' , 'out_Count' , ), 2, (2, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( '_NewEnum' , 'out_Enum' , ), -4, (-4, (), [ (16397, 10, None, None) , ], 1 , 1 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), +] + +IWshEnvironment_vtables_dispatch_ = 1 +IWshEnvironment_vtables_ = [ + (( 'Item' , 'Name' , 'out_Value' , ), 0, (0, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Item' , 'Name' , 'out_Value' , ), 0, (0, (), [ (8, 1, None, None) , + (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'Count' , 'out_Count' , ), 1, (1, (), [ (16387, 10, None, None) , ], 1 , 1 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'length' , 'out_Count' , ), 2, (2, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( '_NewEnum' , 'out_Enum' , ), -4, (-4, (), [ (16397, 10, None, None) , ], 1 , 1 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'Remove' , 'Name' , ), 1001, (1001, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), +] + +IWshExec_vtables_dispatch_ = 1 +IWshExec_vtables_ = [ + (( 'Status' , 'Status' , ), 1, (1, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'StdIn' , 'ppts' , ), 3, (3, (), [ (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'StdOut' , 'ppts' , ), 4, (4, (), [ (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'StdErr' , 'ppts' , ), 5, (5, (), [ (16393, 10, None, "IID('{53BAD8C1-E718-11CF-893D-00A0C9054228}')") , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'ProcessID' , 'PID' , ), 6, (6, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'ExitCode' , 'ExitCode' , ), 7, (7, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'Terminate' , ), 8, (8, (), [ ], 1 , 1 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), +] + +IWshNetwork_vtables_dispatch_ = 1 +IWshNetwork_vtables_ = [ + (( 'UserDomain' , 'out_UserDomain' , ), 1610743808, (1610743808, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'UserName' , 'out_UserName' , ), 1610743809, (1610743809, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'UserProfile' , 'out_UserProfile' , ), 1610743810, (1610743810, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 36 , (3, 0, None, None) , 64 , )), + (( 'ComputerName' , 'out_ComputerName' , ), 1610743811, (1610743811, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'Organization' , 'out_Organization' , ), 1610743812, (1610743812, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 44 , (3, 0, None, None) , 64 , )), + (( 'Site' , 'out_Site' , ), 1610743813, (1610743813, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 48 , (3, 0, None, None) , 64 , )), + (( 'MapNetworkDrive' , 'LocalName' , 'RemoteName' , 'UpdateProfile' , 'UserName' , + 'Password' , ), 1000, (1000, (), [ (8, 1, None, None) , (8, 1, None, None) , (16396, 17, None, None) , + (16396, 17, None, None) , (16396, 17, None, None) , ], 1 , 1 , 4 , 3 , 52 , (3, 0, None, None) , 0 , )), + (( 'RemoveNetworkDrive' , 'Name' , 'Force' , 'UpdateProfile' , ), 1001, (1001, (), [ + (8, 1, None, None) , (16396, 17, None, None) , (16396, 17, None, None) , ], 1 , 1 , 4 , 2 , 56 , (3, 0, None, None) , 0 , )), + (( 'EnumNetworkDrives' , 'out_Enum' , ), 1002, (1002, (), [ (16393, 10, None, "IID('{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}')") , ], 1 , 1 , 4 , 0 , 60 , (3, 0, None, None) , 0 , )), + (( 'AddPrinterConnection' , 'LocalName' , 'RemoteName' , 'UpdateProfile' , 'UserName' , + 'Password' , ), 2000, (2000, (), [ (8, 1, None, None) , (8, 1, None, None) , (16396, 17, None, None) , + (16396, 17, None, None) , (16396, 17, None, None) , ], 1 , 1 , 4 , 3 , 64 , (3, 0, None, None) , 0 , )), + (( 'RemovePrinterConnection' , 'Name' , 'Force' , 'UpdateProfile' , ), 2001, (2001, (), [ + (8, 1, None, None) , (16396, 17, None, None) , (16396, 17, None, None) , ], 1 , 1 , 4 , 2 , 68 , (3, 0, None, None) , 0 , )), + (( 'EnumPrinterConnections' , 'out_Enum' , ), 2002, (2002, (), [ (16393, 10, None, "IID('{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}')") , ], 1 , 1 , 4 , 0 , 72 , (3, 0, None, None) , 0 , )), + (( 'SetDefaultPrinter' , 'Name' , ), 2003, (2003, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), +] + +IWshNetwork2_vtables_dispatch_ = 1 +IWshNetwork2_vtables_ = [ + (( 'AddWindowsPrinterConnection' , 'PrinterName' , 'DriverName' , 'Port' , ), 2004, (2004, (), [ + (8, 1, None, None) , (8, 49, "''", None) , (8, 49, "'LPT1'", None) , ], 1 , 1 , 4 , 0 , 80 , (3, 32, None, None) , 0 , )), +] + +IWshShell_vtables_dispatch_ = 1 +IWshShell_vtables_ = [ + (( 'SpecialFolders' , 'out_Folders' , ), 100, (100, (), [ (16393, 10, None, "IID('{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}')") , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Environment' , 'Type' , 'out_Env' , ), 200, (200, (), [ (16396, 17, None, None) , + (16393, 10, None, "IID('{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}')") , ], 1 , 2 , 4 , 1 , 32 , (3, 0, None, None) , 0 , )), + (( 'Environment' , 'Type' , 'out_Env' , ), 200, (200, (), [ (16396, 17, None, None) , + (16393, 10, None, "IID('{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}')") , ], 1 , 2 , 4 , 1 , 32 , (3, 0, None, None) , 0 , )), + (( 'Run' , 'Command' , 'WindowStyle' , 'WaitOnReturn' , 'out_ExitCode' , + ), 1000, (1000, (), [ (8, 1, None, None) , (16396, 17, None, None) , (16396, 17, None, None) , (16387, 10, None, None) , ], 1 , 1 , 4 , 2 , 36 , (3, 0, None, None) , 0 , )), + (( 'Popup' , 'Text' , 'SecondsToWait' , 'Title' , 'Type' , + 'out_Button' , ), 1001, (1001, (), [ (8, 1, None, None) , (16396, 17, None, None) , (16396, 17, None, None) , + (16396, 17, None, None) , (16387, 10, None, None) , ], 1 , 1 , 4 , 3 , 40 , (3, 0, None, None) , 0 , )), + (( 'CreateShortcut' , 'PathLink' , 'out_Shortcut' , ), 1002, (1002, (), [ (8, 1, None, None) , + (16393, 10, None, None) , ], 1 , 1 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'ExpandEnvironmentStrings' , 'Src' , 'out_Dst' , ), 1006, (1006, (), [ (8, 1, None, None) , + (16392, 10, None, None) , ], 1 , 1 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'RegRead' , 'Name' , 'out_Value' , ), 2000, (2000, (), [ (8, 1, None, None) , + (16396, 10, None, None) , ], 1 , 1 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), + (( 'RegWrite' , 'Name' , 'Value' , 'Type' , ), 2001, (2001, (), [ + (8, 1, None, None) , (16396, 1, None, None) , (16396, 17, None, None) , ], 1 , 1 , 4 , 1 , 56 , (3, 0, None, None) , 0 , )), + (( 'RegDelete' , 'Name' , ), 2002, (2002, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 60 , (3, 0, None, None) , 0 , )), +] + +IWshShell2_vtables_dispatch_ = 1 +IWshShell2_vtables_ = [ + (( 'LogEvent' , 'Type' , 'Message' , 'Target' , 'out_Success' , + ), 3000, (3000, (), [ (16396, 1, None, None) , (8, 1, None, None) , (8, 49, "''", None) , (16395, 10, None, None) , ], 1 , 1 , 4 , 0 , 64 , (3, 32, None, None) , 0 , )), + (( 'AppActivate' , 'App' , 'Wait' , 'out_Success' , ), 3010, (3010, (), [ + (16396, 1, None, None) , (16396, 17, None, None) , (16395, 10, None, None) , ], 1 , 1 , 4 , 1 , 68 , (3, 0, None, None) , 0 , )), + (( 'SendKeys' , 'Keys' , 'Wait' , ), 3011, (3011, (), [ (8, 1, None, None) , + (16396, 17, None, None) , ], 1 , 1 , 4 , 1 , 72 , (3, 0, None, None) , 0 , )), +] + +IWshShell3_vtables_dispatch_ = 1 +IWshShell3_vtables_ = [ + (( 'Exec' , 'Command' , 'ppExec' , ), 3012, (3012, (), [ (8, 1, None, None) , + (16393, 10, None, "IID('{08FED190-BE19-11D3-A28B-00104BD35090}')") , ], 1 , 1 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), + (( 'CurrentDirectory' , 'out_Directory' , ), 3013, (3013, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 80 , (3, 0, None, None) , 0 , )), + (( 'CurrentDirectory' , 'out_Directory' , ), 3013, (3013, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 84 , (3, 0, None, None) , 0 , )), +] + +IWshShortcut_vtables_dispatch_ = 1 +IWshShortcut_vtables_ = [ + (( 'FullName' , 'out_FullName' , ), 0, (0, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'Arguments' , 'out_Arguments' , ), 1000, (1000, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'Arguments' , 'out_Arguments' , ), 1000, (1000, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'Description' , 'out_Description' , ), 1001, (1001, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 40 , (3, 0, None, None) , 0 , )), + (( 'Description' , 'out_Description' , ), 1001, (1001, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), + (( 'Hotkey' , 'out_HotKey' , ), 1002, (1002, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 48 , (3, 0, None, None) , 0 , )), + (( 'Hotkey' , 'out_HotKey' , ), 1002, (1002, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 52 , (3, 0, None, None) , 0 , )), + (( 'IconLocation' , 'out_IconPath' , ), 1003, (1003, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 56 , (3, 0, None, None) , 0 , )), + (( 'IconLocation' , 'out_IconPath' , ), 1003, (1003, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 60 , (3, 0, None, None) , 0 , )), + (( 'RelativePath' , ), 1004, (1004, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 64 , (3, 0, None, None) , 0 , )), + (( 'TargetPath' , 'out_Path' , ), 1005, (1005, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 68 , (3, 0, None, None) , 0 , )), + (( 'TargetPath' , 'out_Path' , ), 1005, (1005, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 72 , (3, 0, None, None) , 0 , )), + (( 'WindowStyle' , 'out_ShowCmd' , ), 1006, (1006, (), [ (16387, 10, None, None) , ], 1 , 2 , 4 , 0 , 76 , (3, 0, None, None) , 0 , )), + (( 'WindowStyle' , 'out_ShowCmd' , ), 1006, (1006, (), [ (3, 1, None, None) , ], 1 , 4 , 4 , 0 , 80 , (3, 0, None, None) , 0 , )), + (( 'WorkingDirectory' , 'out_WorkingDirectory' , ), 1007, (1007, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 84 , (3, 0, None, None) , 0 , )), + (( 'WorkingDirectory' , 'out_WorkingDirectory' , ), 1007, (1007, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 88 , (3, 0, None, None) , 0 , )), + (( 'Load' , 'PathLink' , ), 2000, (2000, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 92 , (3, 0, None, None) , 64 , )), + (( 'Save' , ), 2001, (2001, (), [ ], 1 , 1 , 4 , 0 , 96 , (3, 0, None, None) , 0 , )), +] + +IWshURLShortcut_vtables_dispatch_ = 1 +IWshURLShortcut_vtables_ = [ + (( 'FullName' , 'out_FullName' , ), 0, (0, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 28 , (3, 0, None, None) , 0 , )), + (( 'TargetPath' , 'out_Path' , ), 1005, (1005, (), [ (16392, 10, None, None) , ], 1 , 2 , 4 , 0 , 32 , (3, 0, None, None) , 0 , )), + (( 'TargetPath' , 'out_Path' , ), 1005, (1005, (), [ (8, 1, None, None) , ], 1 , 4 , 4 , 0 , 36 , (3, 0, None, None) , 0 , )), + (( 'Load' , 'PathLink' , ), 2000, (2000, (), [ (8, 1, None, None) , ], 1 , 1 , 4 , 0 , 40 , (3, 0, None, None) , 64 , )), + (( 'Save' , ), 2001, (2001, (), [ ], 1 , 1 , 4 , 0 , 44 , (3, 0, None, None) , 0 , )), +] + +RecordMap = { +} + +CLSIDToClassMap = { + '{41904400-BE18-11D3-A28B-00104BD35090}' : IWshShell3, + '{2A0B9D10-4B87-11D3-A97A-00104B365C9F}' : IFileSystem3, + '{F935DC21-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshShell, + '{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshShell_Class, + '{F935DC23-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshShortcut, + '{F935DC24-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshShortcut_Class, + '{F935DC25-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshNetwork, + '{F935DC26-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshNetwork_Class, + '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshCollection, + '{F935DC28-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshCollection_Class, + '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshEnvironment, + '{08FED190-BE19-11D3-A28B-00104BD35090}' : IWshExec, + '{F935DC2B-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshURLShortcut, + '{F935DC2C-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshURLShortcut_Class, + '{A548B8E4-51D5-4661-8824-DAA1D893DFB2}' : WshShortcut, + '{50E13488-6F1E-4450-96B0-873755403955}' : WshURLShortcut, + '{53BAD8C1-E718-11CF-893D-00A0C9054228}' : ITextStream, + '{08FED191-BE19-11D3-A28B-00104BD35090}' : WshExec, + '{24BE5A30-EDFE-11D2-B933-00104B365C9F}' : IWshShell2, + '{24BE5A31-EDFE-11D2-B933-00104B365C9F}' : IWshNetwork2, + '{72C24DD5-D70A-438B-8A42-98424B88AFB8}' : WshShell, + '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}' : IDrive, + '{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}' : IDriveCollection, + '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}' : IFolder, + '{C7C3F5A3-88A3-11D0-ABCB-00A0C90FFFC0}' : IFolderCollection, + '{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}' : IFile, + '{C7C3F5A5-88A3-11D0-ABCB-00A0C90FFFC0}' : IFileCollection, + '{C7C3F5B1-88A3-11D0-ABCB-00A0C90FFFC0}' : Drive, + '{C7C3F5B2-88A3-11D0-ABCB-00A0C90FFFC0}' : Drives, + '{C7C3F5B3-88A3-11D0-ABCB-00A0C90FFFC0}' : Folder, + '{C7C3F5B4-88A3-11D0-ABCB-00A0C90FFFC0}' : Folders, + '{C7C3F5B5-88A3-11D0-ABCB-00A0C90FFFC0}' : File, + '{C7C3F5B6-88A3-11D0-ABCB-00A0C90FFFC0}' : Files, + '{387DAFF4-DA03-44D2-B0D1-80C927C905AC}' : WshCollection, + '{F935DC2A-1CF0-11D0-ADB9-00C04FD58A0B}' : IWshEnvironment_Class, + '{0BB02EC0-EF49-11CF-8940-00A0C9054228}' : TextStream, + '{F48229AF-E28C-42B5-BB92-E114E62BDD54}' : WshEnvironment, + '{0D43FE01-F093-11CF-8940-00A0C9054228}' : FileSystemObject, + '{093FF999-1EA0-4079-9525-9614C3504B74}' : WshNetwork, + '{0AB5A3D0-E5B6-11D0-ABF5-00A0C90FFFC0}' : IFileSystem, +} +CLSIDToPackageMap = {} +win32com.client.CLSIDToClass.RegisterCLSIDsFromDict( CLSIDToClassMap ) +VTablesToPackageMap = {} +VTablesToClassMap = { + '{24BE5A31-EDFE-11D2-B933-00104B365C9F}' : 'IWshNetwork2', + '{2A0B9D10-4B87-11D3-A97A-00104B365C9F}' : 'IFileSystem3', + '{41904400-BE18-11D3-A28B-00104BD35090}' : 'IWshShell3', + '{53BAD8C1-E718-11CF-893D-00A0C9054228}' : 'ITextStream', + '{F935DC21-1CF0-11D0-ADB9-00C04FD58A0B}' : 'IWshShell', + '{F935DC23-1CF0-11D0-ADB9-00C04FD58A0B}' : 'IWshShortcut', + '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}' : 'IDrive', + '{F935DC25-1CF0-11D0-ADB9-00C04FD58A0B}' : 'IWshNetwork', + '{24BE5A30-EDFE-11D2-B933-00104B365C9F}' : 'IWshShell2', + '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}' : 'IWshCollection', + '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}' : 'IWshEnvironment', + '{08FED190-BE19-11D3-A28B-00104BD35090}' : 'IWshExec', + '{F935DC2B-1CF0-11D0-ADB9-00C04FD58A0B}' : 'IWshURLShortcut', + '{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}' : 'IDriveCollection', + '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}' : 'IFolder', + '{0AB5A3D0-E5B6-11D0-ABF5-00A0C90FFFC0}' : 'IFileSystem', + '{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}' : 'IFile', + '{C7C3F5A5-88A3-11D0-ABCB-00A0C90FFFC0}' : 'IFileCollection', + '{C7C3F5A3-88A3-11D0-ABCB-00A0C90FFFC0}' : 'IFolderCollection', +} + + +NamesToIIDMap = { + 'IWshShell2' : '{24BE5A30-EDFE-11D2-B933-00104B365C9F}', + 'IFolder' : '{C7C3F5A2-88A3-11D0-ABCB-00A0C90FFFC0}', + 'IFolderCollection' : '{C7C3F5A3-88A3-11D0-ABCB-00A0C90FFFC0}', + 'ITextStream' : '{53BAD8C1-E718-11CF-893D-00A0C9054228}', + 'IWshURLShortcut' : '{F935DC2B-1CF0-11D0-ADB9-00C04FD58A0B}', + 'IFileSystem' : '{0AB5A3D0-E5B6-11D0-ABF5-00A0C90FFFC0}', + 'IDriveCollection' : '{C7C3F5A1-88A3-11D0-ABCB-00A0C90FFFC0}', + 'IDrive' : '{C7C3F5A0-88A3-11D0-ABCB-00A0C90FFFC0}', + 'IWshNetwork' : '{F935DC25-1CF0-11D0-ADB9-00C04FD58A0B}', + 'IFile' : '{C7C3F5A4-88A3-11D0-ABCB-00A0C90FFFC0}', + 'IWshShell' : '{F935DC21-1CF0-11D0-ADB9-00C04FD58A0B}', + 'IWshNetwork2' : '{24BE5A31-EDFE-11D2-B933-00104B365C9F}', + 'IWshShell3' : '{41904400-BE18-11D3-A28B-00104BD35090}', + 'IFileSystem3' : '{2A0B9D10-4B87-11D3-A97A-00104B365C9F}', + 'IFileCollection' : '{C7C3F5A5-88A3-11D0-ABCB-00A0C90FFFC0}', + 'IWshExec' : '{08FED190-BE19-11D3-A28B-00104BD35090}', + 'IWshCollection' : '{F935DC27-1CF0-11D0-ADB9-00C04FD58A0B}', + 'IWshShortcut' : '{F935DC23-1CF0-11D0-ADB9-00C04FD58A0B}', + 'IWshEnvironment' : '{F935DC29-1CF0-11D0-ADB9-00C04FD58A0B}', +} + +win32com.client.constants.__dicts__.append(constants.__dict__) + diff --git a/installer/py2exe/py2exe/samples/pywin32/isapi/README.txt b/installer/py2exe/py2exe/samples/pywin32/isapi/README.txt new file mode 100644 index 0000000..98bb073 --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/isapi/README.txt @@ -0,0 +1,12 @@ +A pywin32 ISAPI sample. + +This builds the pywin32 isapi 'redirector' sample (see +site-packages\isapi\samples) into a py2exe distribution. Execute: + + setup.py py2exe + +and in the 'dist' directory you will find 'redirector.exe' (used to +register/unregister the ISAPI extension), and 'redirector.dll' (the ISAPI +filter and extension loaded by IIS.) + +See the pywin32 sample for more details about how to use the sample. diff --git a/installer/py2exe/py2exe/samples/pywin32/isapi/setup.py b/installer/py2exe/py2exe/samples/pywin32/isapi/setup.py new file mode 100644 index 0000000..af9e07a --- /dev/null +++ b/installer/py2exe/py2exe/samples/pywin32/isapi/setup.py @@ -0,0 +1,16 @@ +# setup_interp.py +# A distutils setup script for the ISAPI "redirector" sample. +import os +from distutils.core import setup +import py2exe + +# Find the ISAPI sample - the redirector. +import isapi +script = os.path.join(isapi.__path__[0], "samples", "redirector.py") + +setup(name="ISAPI sample", + # The ISAPI dll. + isapi = [script], + # command-line installation tool. + console=[script], +) diff --git a/installer/py2exe/py2exe/samples/simple/hello.py b/installer/py2exe/py2exe/samples/simple/hello.py new file mode 100644 index 0000000..82360e5 --- /dev/null +++ b/installer/py2exe/py2exe/samples/simple/hello.py @@ -0,0 +1,11 @@ +import sys + +print "Hello from py2exe" + +print "frozen", repr(getattr(sys, "frozen", None)) + +print "sys.path", sys.path +print "sys.executable", sys.executable +print "sys.prefix", sys.prefix +print "sys.argv", sys.argv + diff --git a/installer/py2exe/py2exe/samples/simple/setup.py b/installer/py2exe/py2exe/samples/simple/setup.py new file mode 100644 index 0000000..a1c7181 --- /dev/null +++ b/installer/py2exe/py2exe/samples/simple/setup.py @@ -0,0 +1,35 @@ +# A very simple setup script to create 2 executables. +# +# hello.py is a simple "hello, world" type program, which alse allows +# to explore the environment in which the script runs. +# +# test_wx.py is a simple wxPython program, it will be converted into a +# console-less program. +# +# If you don't have wxPython installed, you should comment out the +# windows = ["test_wx.py"] +# line below. +# +# +# Run the build process by entering 'setup.py py2exe' or +# 'python setup.py py2exe' in a console prompt. +# +# If everything works well, you should find a subdirectory named 'dist' +# containing some files, among them hello.exe and test_wx.exe. + + +from distutils.core import setup +import py2exe + +setup( + # The first three parameters are not required, if at least a + # 'version' is given, then a versioninfo resource is built from + # them and added to the executables. + version = "0.5.0", + description = "py2exe sample script", + name = "py2exe samples", + + # targets to build + windows = ["test_wx.py"], + console = ["hello.py"], + ) diff --git a/installer/py2exe/py2exe/samples/simple/test_wx.py b/installer/py2exe/py2exe/samples/simple/test_wx.py new file mode 100644 index 0000000..cb5b296 --- /dev/null +++ b/installer/py2exe/py2exe/samples/simple/test_wx.py @@ -0,0 +1,35 @@ +from wxPython.wx import * + +class MyFrame(wxFrame): + def __init__(self, parent, ID, title, pos=wxDefaultPosition, + size=(200, 200), style=wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, parent, ID, title, pos, size, style) + panel = wxPanel(self, -1) + + button = wxButton(panel, 1003, "Close Me") + button.SetPosition(wxPoint(15, 15)) + EVT_BUTTON(self, 1003, self.OnCloseMe) + EVT_CLOSE(self, self.OnCloseWindow) + + button = wxButton(panel, 1004, "Press Me") + button.SetPosition(wxPoint(15, 45)) + EVT_BUTTON(self, 1004, self.OnPressMe) + + def OnCloseMe(self, event): + self.Close(True) + + def OnPressMe(self, event): + x = 1 / 0 + + def OnCloseWindow(self, event): + self.Destroy() + +class MyApp(wxApp): + def OnInit(self): + frame = MyFrame(NULL, -1, "Hello from wxPython") + frame.Show(true) + self.SetTopWindow(frame) + return true + +app = MyApp(0) +app.MainLoop() diff --git a/installer/py2exe/py2exe/samples/singlefile/comserver/setup.py b/installer/py2exe/py2exe/samples/singlefile/comserver/setup.py new file mode 100644 index 0000000..d54d2ce --- /dev/null +++ b/installer/py2exe/py2exe/samples/singlefile/comserver/setup.py @@ -0,0 +1,51 @@ +# This setup script builds a single-file Python inprocess COM server. +# +from distutils.core import setup +import py2exe +import sys + +# If run without args, build executables, in quiet mode. +if len(sys.argv) == 1: + sys.argv.append("py2exe") + sys.argv.append("-q") + +class Target: + def __init__(self, **kw): + self.__dict__.update(kw) + # for the versioninfo resources + self.version = "0.6.0" + self.company_name = "No Company" + self.copyright = "no copyright" + self.name = "py2exe sample files" + +################################################################ +# a COM server dll, modules is required +# + +interp = Target( + description = "Python Interpreter as COM server module", + # what to build. For COM servers, the module name (not the + # filename) must be specified! + modules = ["win32com.servers.interp"], + # we only want the inproc server. + create_exe = False, + ) + +################################################################ +# pywin32 COM pulls in a lot of stuff which we don't want or need. + +excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon", + "pywin.dialogs", "pywin.dialogs.list", "win32com.client"] + +options = { + "bundle_files": 1, + "ascii": 1, # to make a smaller executable, don't include the encodings + "compressed": 1, # compress the library archive + "excludes": excludes, # COM stuff we don't want + } + +setup( + options = {"py2exe": options}, + zipfile = None, # append zip-archive to the executable. + com_server = [interp], + ) diff --git a/installer/py2exe/py2exe/samples/singlefile/comserver/setup_client.py b/installer/py2exe/py2exe/samples/singlefile/comserver/setup_client.py new file mode 100644 index 0000000..65fd381 --- /dev/null +++ b/installer/py2exe/py2exe/samples/singlefile/comserver/setup_client.py @@ -0,0 +1,29 @@ +# This setup script builds a single-file Python inprocess COM server. +# +from distutils.core import setup +import py2exe +import sys + +# If run without args, build executables, in quiet mode. +if len(sys.argv) == 1: + sys.argv.append("py2exe") + sys.argv.append("-q") + +################################################################ +# pywin32 COM pulls in a lot of stuff which we don't want or need. + +excludes = ["pywin", "pywin.debugger", "pywin.debugger.dbgcon", + "pywin.dialogs", "pywin.dialogs.list", "win32com.server"] + +options = { + "bundle_files": 1, # create singlefile exe + "compressed": 1, # compress the library archive + "excludes": excludes, + "dll_excludes": ["w9xpopen.exe"] # we don't need this + } + +setup( + options = {"py2exe": options}, + zipfile = None, # append zip-archive to the executable. + console = ["test.py"] + ) diff --git a/installer/py2exe/py2exe/samples/singlefile/comserver/test.py b/installer/py2exe/py2exe/samples/singlefile/comserver/test.py new file mode 100644 index 0000000..2b06dc5 --- /dev/null +++ b/installer/py2exe/py2exe/samples/singlefile/comserver/test.py @@ -0,0 +1,14 @@ +import sys +from win32com.client import Dispatch + +d = Dispatch("Python.Interpreter") +print "2 + 5 =", d.Eval("2 + 5") +d.Exec("print 'hi via COM'") + +d.Exec("import sys") + +print "COM server sys.version", d.Eval("sys.version") +print "Client sys.version", sys.version + +print "COM server sys.path", d.Eval("sys.path") +print "Client sys.path", sys.path diff --git a/installer/py2exe/py2exe/samples/singlefile/gui/setup.py b/installer/py2exe/py2exe/samples/singlefile/gui/setup.py new file mode 100644 index 0000000..dfe8711 --- /dev/null +++ b/installer/py2exe/py2exe/samples/singlefile/gui/setup.py @@ -0,0 +1,79 @@ +# Requires wxPython. This sample demonstrates: +# +# - single file exe using wxPython as GUI. + +from distutils.core import setup +import py2exe +import sys + +# If run without args, build executables, in quiet mode. +if len(sys.argv) == 1: + sys.argv.append("py2exe") + sys.argv.append("-q") + +class Target: + def __init__(self, **kw): + self.__dict__.update(kw) + # for the versioninfo resources + self.version = "0.6.1" + self.company_name = "No Company" + self.copyright = "no copyright" + self.name = "py2exe sample files" + +################################################################ +# A program using wxPython + +# The manifest will be inserted as resource into test_wx.exe. This +# gives the controls the Windows XP appearance (if run on XP ;-) +# +# Another option would be to store it in a file named +# test_wx.exe.manifest, and copy it with the data_files option into +# the dist-dir. +# +manifest_template = ''' + + + +%(prog)s Program + + + + + + +''' + +RT_MANIFEST = 24 + +test_wx = Target( + # used for the versioninfo resource + description = "A sample GUI app", + + # what to build + script = "test_wx.py", + other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog="test_wx"))], +## icon_resources = [(1, "icon.ico")], + dest_base = "test_wx") + +################################################################ + +setup( + options = {"py2exe": {"compressed": 1, + "optimize": 2, + "ascii": 1, + "bundle_files": 1}}, + zipfile = None, + windows = [test_wx], + ) diff --git a/installer/py2exe/py2exe/samples/singlefile/gui/test_wx.py b/installer/py2exe/py2exe/samples/singlefile/gui/test_wx.py new file mode 100644 index 0000000..0ac7baf --- /dev/null +++ b/installer/py2exe/py2exe/samples/singlefile/gui/test_wx.py @@ -0,0 +1,48 @@ +from wxPython.wx import * + +# By default the executables created by py2exe write output to stderr +# into a logfile and display a messagebox at program end when there +# has been any output. This logger silently overrides the default +# behaviour by silently shallowing everything. + +class logger: + def write(self, text): + pass # silently ignore everything + +import sys +sys.stdout = sys.stderr = logger() + +class MyFrame(wxFrame): + def __init__(self, parent, ID, title, pos=wxDefaultPosition, + size=(200, 200), style=wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, parent, ID, title, pos, size, style) + panel = wxPanel(self, -1) + + button = wxButton(panel, 1003, "Close Me") + button.SetPosition(wxPoint(15, 15)) + EVT_BUTTON(self, 1003, self.OnCloseMe) + EVT_CLOSE(self, self.OnCloseWindow) + + button = wxButton(panel, 1004, "Press Me") + button.SetPosition(wxPoint(15, 45)) + EVT_BUTTON(self, 1004, self.OnPressMe) + + def OnCloseMe(self, event): + self.Close(True) + + def OnPressMe(self, event): + # This raises an exception + x = 1 / 0 + + def OnCloseWindow(self, event): + self.Destroy() + +class MyApp(wxApp): + def OnInit(self): + frame = MyFrame(NULL, -1, "Hello from wxPython") + frame.Show(true) + self.SetTopWindow(frame) + return true + +app = MyApp(0) +app.MainLoop() diff --git a/installer/py2exe/py2exe_postinstall.py b/installer/py2exe/py2exe_postinstall.py new file mode 100644 index 0000000..3973190 --- /dev/null +++ b/installer/py2exe/py2exe_postinstall.py @@ -0,0 +1,8 @@ +"""py2exe is now installed on your machine. + +There are some samples in the 'samples' subdirectory.""" + +import sys + +if len(sys.argv) == 2 and sys.argv[1] == "-install": + print __doc__ diff --git a/installer/py2exe/sandbox/test/hello.py b/installer/py2exe/sandbox/test/hello.py new file mode 100644 index 0000000..2b32acb --- /dev/null +++ b/installer/py2exe/sandbox/test/hello.py @@ -0,0 +1,26 @@ +from __future__ import division + +import sys + +for name in "argv path executable".split(): + print "sys.%s = " % name, getattr(sys, name) + +##print "__file__", __file__ +print "Hello, World" + +import encodings +print "encodings.__file__", encodings.__file__ + +print u"Umlaute: ܵ" +print "1 / 2", 1/2 +print 0x80000000 + +raw_input("Weiter?") + +##from win32com.client.dynamic import Dispatch +##d = Dispatch("Word.Application") +##d.Visible = 1 + +##import time +##time.sleep(2) +##d.Quit() diff --git a/installer/py2exe/sandbox/test/setup.cfg b/installer/py2exe/sandbox/test/setup.cfg new file mode 100644 index 0000000..a8adc47 --- /dev/null +++ b/installer/py2exe/sandbox/test/setup.cfg @@ -0,0 +1,4 @@ +[py2exe] +packages = encodings +includes = win32com.server.policy +excludes = pywin diff --git a/installer/py2exe/sandbox/test/setup_all.py b/installer/py2exe/sandbox/test/setup_all.py new file mode 100644 index 0000000..9fedd26 --- /dev/null +++ b/installer/py2exe/sandbox/test/setup_all.py @@ -0,0 +1,10 @@ +from distutils.core import setup +import py2exe + +setup(#name="name", + windows=["test_wx.py"], +## service=["a.b.c.d"], + com_server = ["win32com.servers.interp"], + console=["hello.py"], + zipfile="application", + ) diff --git a/installer/py2exe/sandbox/test/test_interp.vbs b/installer/py2exe/sandbox/test/test_interp.vbs new file mode 100644 index 0000000..f781558 --- /dev/null +++ b/installer/py2exe/sandbox/test/test_interp.vbs @@ -0,0 +1,8 @@ +' A VBScript test harness for our test interpreter +' Running under cscript.exe is more useful than wscript.exe +' eg: cscript.exe test_interp.vbs + +set interp = CreateObject("Python.Interpreter") +interp.Exec("import sys") +WScript.Echo "This Python object is being hosted in " & interp.Eval("sys.executable") +WScript.Echo "Path is " & interp.Eval("str(sys.path)") \ No newline at end of file diff --git a/installer/py2exe/sandbox/test/test_wx.py b/installer/py2exe/sandbox/test/test_wx.py new file mode 100644 index 0000000..7753c7c --- /dev/null +++ b/installer/py2exe/sandbox/test/test_wx.py @@ -0,0 +1,14 @@ +import sys +print "PATH", sys.path + +from wxPython.wx import * + +class MyApp(wxApp): + def OnInit(self): + frame = wxFrame(NULL, -1, "Hello from wxPython") + frame.Show(true) + self.SetTopWindow(frame) + return true + +app = MyApp(0) +app.MainLoop() diff --git a/installer/py2exe/setup.py b/installer/py2exe/setup.py new file mode 100644 index 0000000..a6dd7db --- /dev/null +++ b/installer/py2exe/setup.py @@ -0,0 +1,462 @@ +#!/usr/bin/python +## +## Copyright (c) 2000-2005 Thomas Heller, Jimmy Retzlaff +## +## Permission is hereby granted, free of charge, to any person obtaining +## a copy of this software and associated documentation files (the +## "Software"), to deal in the Software without restriction, including +## without limitation the rights to use, copy, modify, merge, publish, +## distribute, sublicense, and/or sell copies of the Software, and to +## permit persons to whom the Software is furnished to do so, subject to +## the following conditions: +## +## The above copyright notice and this permission notice shall be +## included in all copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +## LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +## OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## + +"""This package is a distutils extension to build +standalone Windows executable programs from +Python scripts. +""" + +from py2exe import __version__ + +# $Id: setup.py 657 2008-04-17 18:23:29Z theller $ + +import sys, os, string +from distutils.core import setup, Extension, Command +from distutils.dist import Distribution +from distutils.command import build_ext, build +from distutils.command.install_data import install_data +from distutils.sysconfig import customize_compiler +from distutils.dep_util import newer_group +from distutils.errors import * + +if sys.version_info < (2, 3): + raise DistutilsError, "This package requires Python 2.3 or later" + +class Interpreter(Extension): + def __init__(self, *args, **kw): + # Add a custom 'target_desc' option, which matches CCompiler + # (is there a better way? + if kw.has_key("target_desc"): + self.target_desc = kw['target_desc'] + del kw['target_desc'] + else: + self.target_desc = "executable" + Extension.__init__(self, *args, **kw) + + +class Dist(Distribution): + def __init__(self,attrs): + self.interpreters = None + Distribution.__init__(self,attrs) + + def has_interpreters(self): + return self.interpreters and len(self.interpreters) > 0 + + +class BuildInterpreters(build_ext.build_ext): + description = "build special python interpreter stubs" + + def finalize_options(self): + build_ext.build_ext.finalize_options(self) + self.interpreters = self.distribution.interpreters + + def run (self): + if not self.interpreters: + return + + self.setup_compiler() + + # Now actually compile and link everything. + for inter in self.interpreters: + sources = inter.sources + if sources is None or type(sources) not in (type([]), type(())): + raise DistutilsSetupError, \ + ("in 'interpreters' option ('%s'), " + + "'sources' must be present and must be " + + "a list of source filenames") % inter.name + sources = list(sources) + + fullname = self.get_exe_fullname(inter.name) + if self.inplace: + # ignore build-lib -- put the compiled extension into + # the source tree along with pure Python modules + modpath = string.split(fullname, '.') + package = string.join(modpath[0:-1], '.') + base = modpath[-1] + + build_py = self.get_finalized_command('build_py') + package_dir = build_py.get_package_dir(package) + exe_filename = os.path.join(package_dir, + self.get_exe_filename(base)) + else: + exe_filename = os.path.join(self.build_lib, + self.get_exe_filename(fullname)) + if inter.target_desc == "executable": + exe_filename += ".exe" + else: + exe_filename += ".dll" + + if not (self.force or \ + newer_group(sources + inter.depends, exe_filename, 'newer')): + self.announce("skipping '%s' interpreter (up-to-date)" % + inter.name) + continue # 'for' loop over all interpreters + else: + self.announce("building '%s' interpreter" % inter.name) + + # fortunately, we don't have swig files. The signature of + # swig_sources is different in Python 2.3 and 2.4, the 2.4 + # version has an additional parameter. +## sources = self.swig_sources(sources) + + extra_args = inter.extra_compile_args or [] + + macros = inter.define_macros[:] + for undef in inter.undef_macros: + macros.append((undef,)) + + objects = self.compiler.compile(sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=inter.include_dirs, + debug=self.debug, + extra_postargs=extra_args, + depends=inter.depends) + + if inter.extra_objects: + objects.extend(inter.extra_objects) + extra_args = inter.extra_link_args or [] + + if inter.export_symbols: + # The mingw32 compiler writes a .def file containing + # the export_symbols. Since py2exe uses symbols in + # the extended form 'DllCanUnloadNow,PRIVATE' (to + # avoid MS linker warnings), we have to replace the + # comma(s) with blanks, so that the .def file can be + # properly parsed. + # XXX MingW32CCompiler, or CygwinCCompiler ? + from distutils.cygwinccompiler import Mingw32CCompiler + if isinstance(self.compiler, Mingw32CCompiler): + inter.export_symbols = [s.replace(",", " ") for s in inter.export_symbols] + inter.export_symbols = [s.replace("=", "\t") for s in inter.export_symbols] + + # XXX - is msvccompiler.link broken? From what I can see, the + # following should work, instead of us checking the param: + self.compiler.link(inter.target_desc, + objects, exe_filename, + libraries=self.get_libraries(inter), + library_dirs=inter.library_dirs, + runtime_library_dirs=inter.runtime_library_dirs, + export_symbols=inter.export_symbols, + extra_postargs=extra_args, + debug=self.debug) + # build_extensions () + + def get_exe_fullname (self, inter_name): + if self.package is None: + return inter_name + else: + return self.package + '.' + inter_name + + def get_exe_filename (self, inter_name): + ext_path = string.split(inter_name, '.') + if self.debug: + return apply(os.path.join, ext_path) + '_d' + return apply(os.path.join, ext_path) + + def setup_compiler(self): + # This method *should* be available separately in build_ext! + from distutils.ccompiler import new_compiler + + # If we were asked to build any C/C++ libraries, make sure that the + # directory where we put them is in the library search path for + # linking interpreters. + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.libraries.extend(build_clib.get_library_names() or []) + self.library_dirs.append(build_clib.build_clib) + + # Setup the CCompiler object that we'll use to do all the + # compiling and linking + self.compiler = new_compiler(compiler=self.compiler, + verbose=self.verbose, + dry_run=self.dry_run, + force=self.force) + try: + self.compiler.initialize() + except AttributeError: + pass # initialize doesn't exist before 2.5 + customize_compiler(self.compiler) + + # And make sure that any compile/link-related options (which might + # come from the command-line or from the setup script) are set in + # that CCompiler object -- that way, they automatically apply to + # all compiling and linking done here. + if self.include_dirs is not None: + self.compiler.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for (name,value) in self.define: + self.compiler.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + self.compiler.undefine_macro(macro) + if self.libraries is not None: + self.compiler.set_libraries(self.libraries) + if self.library_dirs is not None: + self.compiler.set_library_dirs(self.library_dirs) + if self.rpath is not None: + self.compiler.set_runtime_library_dirs(self.rpath) + if self.link_objects is not None: + self.compiler.set_link_objects(self.link_objects) + + # setup_compiler() + +# class BuildInterpreters + +def InstallSubCommands(): + """Adds our own sub-commands to build and install""" + has_interpreters = lambda self: self.distribution.has_interpreters() + buildCmds = [('build_interpreters', has_interpreters)] + build.build.sub_commands.extend(buildCmds) + +InstallSubCommands() + +############################################################################ + +class deinstall(Command): + description = "Remove all installed files" + + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + self.run_command('build') + build = self.get_finalized_command('build') + install = self.get_finalized_command('install') + self.announce("removing files") + for n in 'platlib', 'purelib', 'headers', 'scripts', 'data': + dstdir = getattr(install, 'install_' + n) + try: + srcdir = getattr(build, 'build_' + n) + except AttributeError: + pass + else: + self._removefiles(dstdir, srcdir) + + def _removefiles(self, dstdir, srcdir): + # Remove all files in dstdir which are present in srcdir + assert dstdir != srcdir + if not os.path.isdir(srcdir): + return + for n in os.listdir(srcdir): + name = os.path.join(dstdir, n) + if os.path.isfile(name): + self.announce("removing '%s'" % name) + if not self.dry_run: + try: + os.remove(name) + except OSError, details: + self.warn("Could not remove file: %s" % details) + if os.path.splitext(name)[1] == '.py': + # Try to remove .pyc and -pyo files also + try: + os.remove(name + 'c') + except OSError: + pass + try: + os.remove(name + 'o') + except OSError: + pass + elif os.path.isdir(name): + self._removefiles(name, os.path.join(srcdir, n)) + if not self.dry_run: + try: + os.rmdir(name) + except OSError, details: + self.warn("Are there additional user files?\n"\ + " Could not remove directory: %s" % details) + else: + self.announce("skipping removal of '%s' (does not exist)" %\ + name) + +############################################################################ + +# This ensures that data files are copied into site_packages rather than +# the main Python directory. +class smart_install_data(install_data): + def run(self): + #need to change self.install_dir to the library dir + install_cmd = self.get_finalized_command('install') + self.install_dir = getattr(install_cmd, 'install_lib') + return install_data.run(self) + +def iter_samples(): + excludedDirs = ['CVS', 'build', 'dist'] + for dirpath, dirnames, filenames in os.walk(r'py2exe\samples'): + for dir in dirnames: + if dir.startswith('.') and dir not in excludedDirs: + excludedDirs.append(dir) + for dir in excludedDirs: + if dir in dirnames: + dirnames.remove(dir) + qualifiedFiles = [] + for filename in filenames: + if not filename.startswith('.'): + qualifiedFiles.append(os.path.join(dirpath, filename)) + if qualifiedFiles: + yield (dirpath, qualifiedFiles) + +def _is_debug_build(): + import imp + for ext, _, _ in imp.get_suffixes(): + if ext == "_d.pyd": + return True + return False + +if _is_debug_build(): + macros = [("PYTHONDLL", '\\"PYTHON%d%d_d.DLL\\"' % sys.version_info[:2]), + ("PYTHONCOM", '\\"pythoncom%d%d_d.dll\\"' % sys.version_info[:2])] +else: + macros = [("PYTHONDLL", '\\"PYTHON%d%d.DLL\\"' % sys.version_info[:2]), + ("PYTHONCOM", '\\"pythoncom%d%d.dll\\"' % sys.version_info[:2])] + +##macros.append(("AS_PY2EXE_BUILTIN", "1")) # for runtime linking python.dll in _memimporter.c +depends = ["source/import-tab.c", "source/import-tab.h"] + +_memimporter = Extension("_memimporter", + ["source/MemoryModule.c", + "source/_memimporter_module.c"], + depends=depends + ["source/_memimporter.c"], + define_macros=macros, + ) + +run = Interpreter("py2exe.run", + ["source/run.c", "source/start.c", "source/icon.rc", + "source/Python-dynload.c", + "source/MemoryModule.c", + "source/_memimporter.c", + ], + depends=depends, + define_macros=macros, + ) + +run_w = Interpreter("py2exe.run_w", + ["source/run_w.c", "source/start.c", "source/icon.rc", + "source/Python-dynload.c", + "source/MemoryModule.c", + "source/_memimporter.c", + ], + libraries=["user32"], + depends=depends, + define_macros=macros, + ) + +run_dll = Interpreter("py2exe.run_dll", + ["source/run_dll.c", "source/start.c", "source/icon.rc", + "source/Python-dynload.c", + "source/MemoryModule.c", + "source/_memimporter.c", + ], + libraries=["user32"], + export_symbols=["DllCanUnloadNow,PRIVATE", + "DllGetClassObject,PRIVATE", + "DllRegisterServer,PRIVATE", + "DllUnregisterServer,PRIVATE", + ], + target_desc = "shared_library", + depends=depends, + define_macros=macros, + ) + +run_ctypes_dll = Interpreter("py2exe.run_ctypes_dll", + ["source/run_ctypes_dll.c", "source/start.c", "source/icon.rc", + "source/Python-dynload.c", + "source/MemoryModule.c", + "source/_memimporter.c", + ], + libraries=["user32"], + export_symbols=["DllCanUnloadNow,PRIVATE", + "DllGetClassObject,PRIVATE", + "DllRegisterServer,PRIVATE", + "DllUnregisterServer,PRIVATE", + ], + target_desc = "shared_library", + depends=depends, + define_macros=macros, + ) + +run_isapi = Interpreter("py2exe.run_isapi", + ["source/run_isapi.c", "source/start.c", + "source/Python-dynload.c", + "source/MemoryModule.c", + "source/_memimporter.c", + "source/icon.rc"], + libraries=["user32", "advapi32"], + export_symbols=["HttpExtensionProc", + "GetExtensionVersion", + "TerminateExtension", + "GetFilterVersion", "HttpFilterProc", + "TerminateFilter"], + target_desc = "shared_library", + depends=depends, + define_macros=macros, + ) + +interpreters = [run, run_w, run_dll, run_ctypes_dll, run_isapi] + +options = {"bdist_wininst": {"install_script": "py2exe_postinstall.py"}} + +setup(name="py2exe", + version=__version__, + description="Build standalone executables for Windows", + long_description=__doc__, + author="Thomas Heller", + author_email="theller@python.net", + maintainer="Jimmy Retzlaff", + maintainer_email="jimmy@retzlaff.com", + url="http://www.py2exe.org/", + license="MIT/X11, MPL 1.1", + platforms="Windows", + download_url="http://sourceforge.net/project/showfiles.php?group_id=15583", + classifiers=["Development Status :: 5 - Production/Stable"], + distclass = Dist, + cmdclass = {'build_interpreters': BuildInterpreters, + 'deinstall': deinstall, + 'install_data': smart_install_data, + }, + + data_files = list(iter_samples()), + ext_modules = [_memimporter, + Extension("py2exe.py2exe_util", + sources=["source/py2exe_util.c"], + libraries=["imagehlp"]), + ], + py_modules = ["zipextimporter"], + scripts = ["py2exe_postinstall.py"], + interpreters = interpreters, + packages=['py2exe', + 'py2exe.resources', + ], + options = options, + ) + +# Local Variables: +# compile-command: "setup.py install" +# End: diff --git a/installer/py2exe/source/MemoryModule.c b/installer/py2exe/source/MemoryModule.c new file mode 100644 index 0000000..3346e3c --- /dev/null +++ b/installer/py2exe/source/MemoryModule.c @@ -0,0 +1,688 @@ +/* + * Memory DLL loading code + * Version 0.0.2 with additions from Thomas Heller + * + * Copyright (c) 2004-2005 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.c + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * Portions created by Joachim Bauch are Copyright (C) 2004-2005 + * Joachim Bauch. All Rights Reserved. + * + * Portions Copyright (C) 2005 Thomas Heller. + * + */ + +// disable warnings about pointer <-> DWORD conversions +#pragma warning( disable : 4311 4312 ) + +#include +#include +#if DEBUG_OUTPUT +#include +#endif + +#include "MemoryModule.h" +#ifndef IMAGE_SIZEOF_BASE_RELOCATION +#define IMAGE_SIZEOF_BASE_RELOCATION 8 +#endif + +/* + XXX We need to protect at least walking the 'loaded' linked list with a lock! +*/ + +/******************************************************************/ +FINDPROC findproc; +void *findproc_data; + +struct NAME_TABLE { + char *name; + DWORD ordinal; +}; + +typedef struct tagMEMORYMODULE { + PIMAGE_NT_HEADERS headers; + unsigned char *codeBase; + HMODULE *modules; + int numModules; + int initialized; + + struct NAME_TABLE *name_table; + + char *name; + int refcount; + struct tagMEMORYMODULE *next, *prev; +} MEMORYMODULE, *PMEMORYMODULE; + +typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); + +#define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx] + +MEMORYMODULE *loaded; /* linked list of loaded memory modules */ + +/* private - insert a loaded library in a linked list */ +static void _Register(char *name, MEMORYMODULE *module) +{ + module->next = loaded; + if (loaded) + loaded->prev = module; + module->prev = NULL; + loaded = module; +} + +/* private - remove a loaded library from a linked list */ +static void _Unregister(MEMORYMODULE *module) +{ + free(module->name); + if (module->prev) + module->prev->next = module->next; + if (module->next) + module->next->prev = module->prev; + if (module == loaded) + loaded = module->next; +} + +/* public - replacement for GetModuleHandle() */ +HMODULE MyGetModuleHandle(LPCTSTR lpModuleName) +{ + MEMORYMODULE *p = loaded; + while (p) { + // If already loaded, only increment the reference count + if (0 == stricmp(lpModuleName, p->name)) { + return (HMODULE)p; + } + p = p->next; + } + return GetModuleHandle(lpModuleName); +} + +/* public - replacement for LoadLibrary, but searches FIRST for memory + libraries, then for normal libraries. So, it will load libraries AS memory + module if they are found by findproc(). +*/ +HMODULE MyLoadLibrary(char *lpFileName) +{ + MEMORYMODULE *p = loaded; + HMODULE hMod; + + while (p) { + // If already loaded, only increment the reference count + if (0 == stricmp(lpFileName, p->name)) { + p->refcount++; + return (HMODULE)p; + } + p = p->next; + } + if (findproc) { + void *pdata = findproc(lpFileName, findproc_data); + if (pdata) { + hMod = MemoryLoadLibrary(lpFileName, pdata); + free(p); + return hMod; + } + } + hMod = LoadLibrary(lpFileName); + return hMod; +} + +/* public - replacement for GetProcAddress() */ +FARPROC MyGetProcAddress(HMODULE hModule, LPCSTR lpProcName) +{ + MEMORYMODULE *p = loaded; + while (p) { + if ((HMODULE)p == hModule) + return MemoryGetProcAddress(p, lpProcName); + p = p->next; + } + return GetProcAddress(hModule, lpProcName); +} + +/* public - replacement for FreeLibrary() */ +BOOL MyFreeLibrary(HMODULE hModule) +{ + MEMORYMODULE *p = loaded; + while (p) { + if ((HMODULE)p == hModule) { + if (--p->refcount == 0) { + _Unregister(p); + MemoryFreeLibrary(p); + } + return TRUE; + } + p = p->next; + } + return FreeLibrary(hModule); +} + +#if DEBUG_OUTPUT +static void +OutputLastError(const char *msg) +{ + LPVOID tmp; + char *tmpmsg; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL); + tmpmsg = (char *)LocalAlloc(LPTR, strlen(msg) + strlen(tmp) + 3); + sprintf(tmpmsg, "%s: %s", msg, tmp); + OutputDebugString(tmpmsg); + LocalFree(tmpmsg); + LocalFree(tmp); +} +#endif + +/* +static int dprintf(char *fmt, ...) +{ + char Buffer[4096]; + va_list marker; + int result; + + va_start(marker, fmt); + result = vsprintf(Buffer, fmt, marker); + OutputDebugString(Buffer); + return result; +} +*/ + +static void +CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module) +{ + int i, size; + unsigned char *codeBase = module->codeBase; + unsigned char *dest; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + for (i=0; iheaders->FileHeader.NumberOfSections; i++, section++) + { + if (section->SizeOfRawData == 0) + { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + size = old_headers->OptionalHeader.SectionAlignment; + if (size > 0) + { + dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress, + size, + MEM_COMMIT, + PAGE_READWRITE); + + section->Misc.PhysicalAddress = (DWORD)dest; + memset(dest, 0, size); + } + + // section is empty + continue; + } + + // commit memory block and copy data from dll + dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress, + section->SizeOfRawData, + MEM_COMMIT, + PAGE_READWRITE); + memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData); + section->Misc.PhysicalAddress = (DWORD)dest; + } +} + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = { + { + // not executable + {PAGE_NOACCESS, PAGE_WRITECOPY}, + {PAGE_READONLY, PAGE_READWRITE}, + }, { + // executable + {PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY}, + {PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE}, + }, +}; + +static void +FinalizeSections(PMEMORYMODULE module) +{ + int i; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + + // loop through all sections and change access flags + for (i=0; iheaders->FileHeader.NumberOfSections; i++, section++) + { + DWORD protect, oldProtect, size; + int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; + int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; + + if (section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) + { + // section is not needed any more and can safely be freed + VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); + continue; + } + + // determine protection flags based on characteristics + protect = ProtectionFlags[executable][readable][writeable]; + if (section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED) + protect |= PAGE_NOCACHE; + + // determine size of region + size = section->SizeOfRawData; + if (size == 0) + { + if (section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) + size = module->headers->OptionalHeader.SizeOfInitializedData; + else if (section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + + if (size > 0) + { + // change memory access flags + if (VirtualProtect((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, protect, &oldProtect) == 0) +#if DEBUG_OUTPUT + OutputLastError("Error protecting memory page") +#endif + ; + } + } +} + +static void +PerformBaseRelocation(PMEMORYMODULE module, DWORD delta) +{ + DWORD i; + unsigned char *codeBase = module->codeBase; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_BASERELOC); + if (directory->Size > 0) + { + PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)(codeBase + directory->VirtualAddress); + for (; relocation->VirtualAddress > 0; ) + { + unsigned char *dest = (unsigned char *)(codeBase + relocation->VirtualAddress); + unsigned short *relInfo = (unsigned short *)((unsigned char *)relocation + IMAGE_SIZEOF_BASE_RELOCATION); + for (i=0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++) + { + DWORD *patchAddrHL; + int type, offset; + + // the upper 4 bits define the type of relocation + type = *relInfo >> 12; + // the lower 12 bits define the offset + offset = *relInfo & 0xfff; + + switch (type) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + patchAddrHL = (DWORD *)(dest + offset); + *patchAddrHL += delta; + break; + + default: + //printf("Unknown relocation: %d\n", type); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION)(((DWORD)relocation) + relocation->SizeOfBlock); + } + } +} + +static int +BuildImportTable(PMEMORYMODULE module) +{ + int result=1; + unsigned char *codeBase = module->codeBase; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_IMPORT); + if (directory->Size > 0) + { + PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)(codeBase + directory->VirtualAddress); + for (; !IsBadReadPtr(importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) + { + DWORD *thunkRef, *funcRef; + HMODULE handle; + + handle = MyLoadLibrary(codeBase + importDesc->Name); + if (handle == INVALID_HANDLE_VALUE) + { + //LastError should already be set +#if DEBUG_OUTPUT + OutputLastError("Can't load library"); +#endif + result = 0; + break; + } + + module->modules = (HMODULE *)realloc(module->modules, (module->numModules+1)*(sizeof(HMODULE))); + if (module->modules == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + result = 0; + break; + } + + module->modules[module->numModules++] = handle; + if (importDesc->OriginalFirstThunk) + { + thunkRef = (DWORD *)(codeBase + importDesc->OriginalFirstThunk); + funcRef = (DWORD *)(codeBase + importDesc->FirstThunk); + } else { + // no hint table + thunkRef = (DWORD *)(codeBase + importDesc->FirstThunk); + funcRef = (DWORD *)(codeBase + importDesc->FirstThunk); + } + for (; *thunkRef; thunkRef++, funcRef++) + { + if IMAGE_SNAP_BY_ORDINAL(*thunkRef) { + *funcRef = (DWORD)MyGetProcAddress(handle, (LPCSTR)IMAGE_ORDINAL(*thunkRef)); + } else { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)(codeBase + *thunkRef); + *funcRef = (DWORD)MyGetProcAddress(handle, (LPCSTR)&thunkData->Name); + } + if (*funcRef == 0) + { + SetLastError(ERROR_PROC_NOT_FOUND); + result = 0; + break; + } + } + + if (!result) + break; + } + } + + return result; +} + +/* + MemoryLoadLibrary - load a library AS MEMORY MODULE, or return + existing MEMORY MODULE with increased refcount. + + This allows to load a library AGAIN as memory module which is + already loaded as HMODULE! + +*/ +HMEMORYMODULE MemoryLoadLibrary(char *name, const void *data) +{ + PMEMORYMODULE result; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + unsigned char *code, *headers; + DWORD locationDelta; + DllEntryProc DllEntry; + BOOL successfull; + MEMORYMODULE *p = loaded; + + while (p) { + // If already loaded, only increment the reference count + if (0 == stricmp(name, p->name)) { + p->refcount++; + return (HMODULE)p; + } + p = p->next; + } + + /* Do NOT check for GetModuleHandle here! */ + + dos_header = (PIMAGE_DOS_HEADER)data; + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + { + SetLastError(ERROR_BAD_FORMAT); +#if DEBUG_OUTPUT + OutputDebugString("Not a valid executable file.\n"); +#endif + return NULL; + } + + old_header = (PIMAGE_NT_HEADERS)&((const unsigned char *)(data))[dos_header->e_lfanew]; + if (old_header->Signature != IMAGE_NT_SIGNATURE) + { + SetLastError(ERROR_BAD_FORMAT); +#if DEBUG_OUTPUT + OutputDebugString("No PE header found.\n"); +#endif + return NULL; + } + + // reserve memory for image of library + code = (unsigned char *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), + old_header->OptionalHeader.SizeOfImage, + MEM_RESERVE, + PAGE_READWRITE); + + if (code == NULL) + // try to allocate memory at arbitrary position + code = (unsigned char *)VirtualAlloc(NULL, + old_header->OptionalHeader.SizeOfImage, + MEM_RESERVE, + PAGE_READWRITE); + + if (code == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); +#if DEBUG_OUTPUT + OutputLastError("Can't reserve memory"); +#endif + return NULL; + } + + result = (PMEMORYMODULE)HeapAlloc(GetProcessHeap(), 0, sizeof(MEMORYMODULE)); + result->codeBase = code; + result->numModules = 0; + result->modules = NULL; + result->initialized = 0; + result->next = result->prev = NULL; + result->refcount = 1; + result->name = strdup(name); + result->name_table = NULL; + + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + VirtualAlloc(code, + old_header->OptionalHeader.SizeOfImage, + MEM_COMMIT, + PAGE_READWRITE); + + // commit memory for headers + headers = (unsigned char *)VirtualAlloc(code, + old_header->OptionalHeader.SizeOfHeaders, + MEM_COMMIT, + PAGE_READWRITE); + + // copy PE header to code + memcpy(headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders); + result->headers = (PIMAGE_NT_HEADERS)&((const unsigned char *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (DWORD)code; + + // copy sections from DLL file block to new memory location + CopySections(data, old_header, result); + + // adjust base address of imported data + locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); + if (locationDelta != 0) + PerformBaseRelocation(result, locationDelta); + + // load required dlls and adjust function table of imports + if (!BuildImportTable(result)) + goto error; + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + FinalizeSections(result); + + // get entry point of loaded library + if (result->headers->OptionalHeader.AddressOfEntryPoint != 0) + { + DllEntry = (DllEntryProc)(code + result->headers->OptionalHeader.AddressOfEntryPoint); + if (DllEntry == 0) + { + SetLastError(ERROR_BAD_FORMAT); /* XXX ? */ +#if DEBUG_OUTPUT + OutputDebugString("Library has no entry point.\n"); +#endif + goto error; + } + + // notify library about attaching to process + successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0); + if (!successfull) + { +#if DEBUG_OUTPUT + OutputDebugString("Can't attach library.\n"); +#endif + goto error; + } + result->initialized = 1; + } + + _Register(name, result); + + return (HMEMORYMODULE)result; + +error: + // cleanup + free(result->name); + MemoryFreeLibrary(result); + return NULL; +} + +int _compare(const struct NAME_TABLE *p1, const struct NAME_TABLE *p2) +{ + return stricmp(p1->name, p2->name); +} + +int _find(const char **name, const struct NAME_TABLE *p) +{ + return stricmp(*name, p->name); +} + +struct NAME_TABLE *GetNameTable(PMEMORYMODULE module) +{ + unsigned char *codeBase; + PIMAGE_EXPORT_DIRECTORY exports; + PIMAGE_DATA_DIRECTORY directory; + DWORD i, *nameRef; + WORD *ordinal; + struct NAME_TABLE *p, *ptab; + + if (module->name_table) + return module->name_table; + + codeBase = module->codeBase; + directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_EXPORT); + exports = (PIMAGE_EXPORT_DIRECTORY)(codeBase + directory->VirtualAddress); + + nameRef = (DWORD *)(codeBase + exports->AddressOfNames); + ordinal = (WORD *)(codeBase + exports->AddressOfNameOrdinals); + + p = ((PMEMORYMODULE)module)->name_table = (struct NAME_TABLE *)malloc(sizeof(struct NAME_TABLE) + * exports->NumberOfNames); + if (p == NULL) + return NULL; + ptab = p; + for (i=0; iNumberOfNames; ++i) { + p->name = (char *)(codeBase + *nameRef++); + p->ordinal = *ordinal++; + ++p; + } + qsort(ptab, exports->NumberOfNames, sizeof(struct NAME_TABLE), _compare); + return ptab; +} + +FARPROC MemoryGetProcAddress(HMEMORYMODULE module, const char *name) +{ + unsigned char *codeBase = ((PMEMORYMODULE)module)->codeBase; + int idx=-1; + PIMAGE_EXPORT_DIRECTORY exports; + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((PMEMORYMODULE)module, IMAGE_DIRECTORY_ENTRY_EXPORT); + + if (directory->Size == 0) + // no export table found + return NULL; + + exports = (PIMAGE_EXPORT_DIRECTORY)(codeBase + directory->VirtualAddress); + if (exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0) + // DLL doesn't export anything + return NULL; + + if (HIWORD(name)) { + struct NAME_TABLE *ptab; + struct NAME_TABLE *found; + ptab = GetNameTable((PMEMORYMODULE)module); + if (ptab == NULL) + // some failure + return NULL; + found = bsearch(&name, ptab, exports->NumberOfNames, sizeof(struct NAME_TABLE), _find); + if (found == NULL) + // exported symbol not found + return NULL; + + idx = found->ordinal; + } + else + idx = LOWORD(name) - exports->Base; + + if ((DWORD)idx > exports->NumberOfFunctions) + // name <-> ordinal number don't match + return NULL; + + // AddressOfFunctions contains the RVAs to the "real" functions + return (FARPROC)(codeBase + *(DWORD *)(codeBase + exports->AddressOfFunctions + (idx*4))); +} + +void MemoryFreeLibrary(HMEMORYMODULE mod) +{ + int i; + PMEMORYMODULE module = (PMEMORYMODULE)mod; + + if (module != NULL) + { + if (module->initialized != 0) + { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc)(module->codeBase + module->headers->OptionalHeader.AddressOfEntryPoint); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0); + module->initialized = 0; + } + + if (module->modules != NULL) + { + // free previously opened libraries + for (i=0; inumModules; i++) + if (module->modules[i] != INVALID_HANDLE_VALUE) + MyFreeLibrary(module->modules[i]); + + free(module->modules); + } + + if (module->codeBase != NULL) + // release memory of library + VirtualFree(module->codeBase, 0, MEM_RELEASE); + + if (module->name_table != NULL) + free(module->name_table); + + HeapFree(GetProcessHeap(), 0, module); + } +} diff --git a/installer/py2exe/source/MemoryModule.h b/installer/py2exe/source/MemoryModule.h new file mode 100644 index 0000000..20674e5 --- /dev/null +++ b/installer/py2exe/source/MemoryModule.h @@ -0,0 +1,58 @@ +/* + * Memory DLL loading code + * Version 0.0.2 + * + * Copyright (c) 2004-2005 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.h + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * Portions created by Joachim Bauch are Copyright (C) 2004-2005 + * Joachim Bauch. All Rights Reserved. + * + */ + +#ifndef __MEMORY_MODULE_HEADER +#define __MEMORY_MODULE_HEADER + +#include + +typedef void *HMEMORYMODULE; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void *(*FINDPROC)(); + +extern FINDPROC findproc; +extern void *findproc_data; + +HMEMORYMODULE MemoryLoadLibrary(char *, const void *); + +FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *); + +void MemoryFreeLibrary(HMEMORYMODULE); + +BOOL MyFreeLibrary(HMODULE hModule); +HMODULE MyLoadLibrary(char *lpFileName); +FARPROC MyGetProcAddress(HMODULE hModule, LPCSTR lpProcName); +HMODULE MyGetModuleHandle(LPCTSTR lpModuleName); + +#ifdef __cplusplus +} +#endif + +#endif // __MEMORY_MODULE_HEADER diff --git a/installer/py2exe/source/Python-dynload.c b/installer/py2exe/source/Python-dynload.c new file mode 100644 index 0000000..85d2f6e --- /dev/null +++ b/installer/py2exe/source/Python-dynload.c @@ -0,0 +1,85 @@ +/* **************** Python-dynload.c **************** */ +#include "Python-dynload.h" +#include "MemoryModule.h" +#include + +struct IMPORT imports[] = { +#include "import-tab.c" + { NULL, NULL }, /* sentinel */ +}; + +void Py_XDECREF(PyObject *ob) +{ + static PyObject *tup; + if (tup == NULL) + tup = PyTuple_New(1); + /* Let the tuple take the refcount */ + PyTuple_SetItem(tup, 0, ob); + /* and overwrite it */ + PyTuple_SetItem(tup, 0, PyInt_FromLong(0)); +} + +void Py_XINCREF(PyObject *ob) +{ + if (ob) + Py_BuildValue("O", ob); +} + +int _load_python_FromFile(char *dllname) +{ + int i; + struct IMPORT *p = imports; + HMODULE hmod; + + // In some cases (eg, ISAPI filters), Python may already be + // in our process. If so, we don't want it to try and + // load a new one! (Actually, we probably should not even attempt + // to load an 'embedded' Python should GetModuleHandle work - but + // that is less clear than this straight-forward case) + // Get the basename of the DLL. + char *dllbase = dllname + strlen(dllname); + while (dllbase != dllname && *dllbase != '\\') + dllbase--; + if (*dllbase=='\\') + ++dllbase; + hmod = GetModuleHandle(dllbase); + if (hmod == NULL) + hmod = LoadLibrary(dllname); + if (hmod == NULL) { + return 0; + } + for (i = 0; p->name; ++i, ++p) { + p->proc = (void (*)())GetProcAddress(hmod, p->name); + if (p->proc == NULL) { + OutputDebugString("undef symbol"); + fprintf(stderr, "undefined symbol %s -> exit(-1)\n", p->name); + return 0; + } + } + return 1; +} + +int _load_python(char *dllname, char *bytes) +{ + int i; + struct IMPORT *p = imports; + HMODULE hmod; + + if (!bytes) + return _load_python_FromFile(dllname); + + hmod = MemoryLoadLibrary(dllname, bytes); + if (hmod == NULL) { + return 0; + } + for (i = 0; p->name; ++i, ++p) { + p->proc = (void (*)())MemoryGetProcAddress(hmod, p->name); + if (p->proc == NULL) { + OutputDebugString("undef symbol"); + fprintf(stderr, "undefined symbol %s -> exit(-1)\n", p->name); + return 0; + } + } + return 1; +} + diff --git a/installer/py2exe/source/Python-dynload.h b/installer/py2exe/source/Python-dynload.h new file mode 100644 index 0000000..8fa1ea2 --- /dev/null +++ b/installer/py2exe/source/Python-dynload.h @@ -0,0 +1,58 @@ +/* **************** Python-dynload.h **************** */ +#include "Python-version.h" + +typedef void *PyObject; +typedef void *PyCodeObject; + +typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); + +typedef + enum {PyGILState_LOCKED, PyGILState_UNLOCKED} + PyGILState_STATE; + +typedef struct { + char *ml_name; + PyCFunction ml_meth; + int ml_flags; + char *ml_doc; +} PyMethodDef; + +struct IMPORT { + char *name; + void (*proc)(); +}; +extern int _load_python(char *dllname, char *dllbytes); +extern struct IMPORT imports[]; + +#include "import-tab.h" + +extern void Py_XINCREF(PyObject *); +#define snprintf _snprintf +#define Py_DECREF(x) Py_XDECREF(x) +#define Py_INCREF(x) Py_XINCREF(x) + +extern void Py_XDECREF(PyObject *ob); + +#define METH_OLDARGS 0x0000 +#define METH_VARARGS 0x0001 +#define METH_KEYWORDS 0x0002 +/* METH_NOARGS and METH_O must not be combined with the flags above. */ +#define METH_NOARGS 0x0004 +#define METH_O 0x0008 + +/* METH_CLASS and METH_STATIC are a little different; these control + the construction of methods for a class. These cannot be used for + functions in modules. */ +#define METH_CLASS 0x0010 +#define METH_STATIC 0x0020 + +#define PyCFunction_New(ML, SELF) PyCFunction_NewEx((ML), (SELF), NULL) + +#define PyInt_Check(op) PyObject_IsInstance(op, &PyInt_Type) /* ??? */ +#define Py_None (&_Py_NoneStruct) + +#define DL_EXPORT(x) x + +#define Py_InitModule3(name, methods, doc) \ + Py_InitModule4(name, methods, doc, (PyObject *)NULL, \ + PYTHON_API_VERSION) diff --git a/installer/py2exe/source/Python-version.h b/installer/py2exe/source/Python-version.h new file mode 100644 index 0000000..42e9b16 --- /dev/null +++ b/installer/py2exe/source/Python-version.h @@ -0,0 +1,15 @@ +#include +#if (PY_VERSION_HEX < 0x02050000) +# define PYTHON_API_VERSION 1012 + typedef int Py_ssize_t; +#else +# define PYTHON_API_VERSION 1013 +/* The check for _WIN64 must come first, because on win64 both _WIN64 and + * _WIN32 are defined! + */ +# if defined (_WIN64) + typedef __int64 Py_ssize_t; +# elif defined (_WIN32) + typedef int Py_ssize_t; +# endif +#endif diff --git a/installer/py2exe/source/README-MemoryModule.txt b/installer/py2exe/source/README-MemoryModule.txt new file mode 100644 index 0000000..7bbe672 --- /dev/null +++ b/installer/py2exe/source/README-MemoryModule.txt @@ -0,0 +1,548 @@ +:Author: Joachim Bauch +:Contact: mail@joachim-bauch.de + + +.. contents:: + + +Overview +========= + +The default windows API functions to load external libraries into a program +(LoadLibrary, LoadLibraryEx) only work with files on the filesystem. It's +therefore impossible to load a DLL from memory. +But sometimes, you need exactly this functionality (e.g. you don't want to +distribute a lot of files or want to make disassembling harder). Common +workarounds for this problems are to write the DLL into a temporary file +first and import it from there. When the program terminates, the temporary +file gets deleted. + +In this tutorial, I will describe first, how DLL files are structured and +will present some code that can be used to load a DLL completely from memory - +without storing on the disk first. + + +Windows executables - the PE format +==================================== + +Most windows binaries that can contain executable code (.exe, .dll, .sys) +share a common file format that consists of the following parts: + ++----------------+ +| DOS header | +| | +| DOS stub | ++----------------+ +| PE header | ++----------------+ +| Section header | ++----------------+ +| Section 1 | ++----------------+ +| Section 2 | ++----------------+ +| . . . | ++----------------+ +| Section n | ++----------------+ + +All structures given below can be found in the header file `winnt.h`. + + +DOS header / stub +------------------ + +The DOS header is only used for backwards compatibility. It precedes the DOS +stub that normally just displays an error message about the program not being +able to be run from DOS mode. + +Microsoft defines the DOS header as follows:: + + typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header + WORD e_magic; // Magic number + WORD e_cblp; // Bytes on last page of file + WORD e_cp; // Pages in file + WORD e_crlc; // Relocations + WORD e_cparhdr; // Size of header in paragraphs + WORD e_minalloc; // Minimum extra paragraphs needed + WORD e_maxalloc; // Maximum extra paragraphs needed + WORD e_ss; // Initial (relative) SS value + WORD e_sp; // Initial SP value + WORD e_csum; // Checksum + WORD e_ip; // Initial IP value + WORD e_cs; // Initial (relative) CS value + WORD e_lfarlc; // File address of relocation table + WORD e_ovno; // Overlay number + WORD e_res[4]; // Reserved words + WORD e_oemid; // OEM identifier (for e_oeminfo) + WORD e_oeminfo; // OEM information; e_oemid specific + WORD e_res2[10]; // Reserved words + LONG e_lfanew; // File address of new exe header + } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; + + +PE header +---------- + +The PE header contains informations about the different sections inside the +executable that are used to store code and data or to define imports from other +libraries or exports this libraries provides. + +It's defined as follows:: + + typedef struct _IMAGE_NT_HEADERS { + DWORD Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER32 OptionalHeader; + } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; + +The `FileHeader` describes the *physical* format of the file, i.e. contents, informations +about symbols, etc:: + + typedef struct _IMAGE_FILE_HEADER { + WORD Machine; + WORD NumberOfSections; + DWORD TimeDateStamp; + DWORD PointerToSymbolTable; + DWORD NumberOfSymbols; + WORD SizeOfOptionalHeader; + WORD Characteristics; + } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; + +.. _OptionalHeader: + +The `OptionalHeader` contains informations about the *logical* format of the library, +including required OS version, memory requirements and entry points:: + + typedef struct _IMAGE_OPTIONAL_HEADER { + // + // Standard fields. + // + + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + + // + // NT additional fields. + // + + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; + +.. _DataDirectory: + +The `DataDirectory` contains 16 (`IMAGE_NUMBEROF_DIRECTORY_ENTRIES`) entries +defining the logical components of the library: + +===== ========================== +Index Description +===== ========================== +0 Exported functions +----- -------------------------- +1 Imported functions +----- -------------------------- +2 Resources +----- -------------------------- +3 Exception informations +----- -------------------------- +4 Security informations +----- -------------------------- +5 Base relocation table +----- -------------------------- +6 Debug informations +----- -------------------------- +7 Architecture specific data +----- -------------------------- +8 Global pointer +----- -------------------------- +9 Thread local storage +----- -------------------------- +10 Load configuration +----- -------------------------- +11 Bound imports +----- -------------------------- +12 Import address table +----- -------------------------- +13 Delay load imports +----- -------------------------- +14 COM runtime descriptor +===== ========================== + +For importing the DLL we only need the entries describing the imports and the +base relocation table. In order to provide access to the exported functions, +the exports entry is required. + + +Section header +--------------- + +The section header is stored after the OptionalHeader_ structure in the PE +header. Microsoft provides the macro `IMAGE_FIRST_SECTION` to get the start +address based on the PE header. + +Actually, the section header is a list of informations about each section in +the file:: + + typedef struct _IMAGE_SECTION_HEADER { + BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; + union { + DWORD PhysicalAddress; + DWORD VirtualSize; + } Misc; + DWORD VirtualAddress; + DWORD SizeOfRawData; + DWORD PointerToRawData; + DWORD PointerToRelocations; + DWORD PointerToLinenumbers; + WORD NumberOfRelocations; + WORD NumberOfLinenumbers; + DWORD Characteristics; + } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; + +A section can contain code, data, relocation informations, resources, export or +import definitions, etc. + + +Loading the library +==================== + +To emulate the PE loader, we must first understand, which steps are neccessary +to load the file to memory and prepare the structures so they can be called from +other programs. + +When issuing the API call `LoadLibrary`, Windows basically performs these tasks: + +1. Open the given file and check the DOS and PE headers. + +2. Try to allocate a memory block of `PEHeader.OptionalHeader.SizeOfImage` bytes + at position `PEHeader.OptionalHeader.ImageBase`. + +3. Parse section headers and copy sections to their addresses. The destination + address for each section, relative to the base of the allocated memory block, + is stored in the `VirtualAddress` attribute of the `IMAGE_SECTION_HEADER` + structure. + +4. If the allocated memory block differs from `ImageBase`, various references in + the code and/or data sections must be adjusted. This is called *Base + relocation*. + +5. The required imports for the library must be resolved by loading the + corresponding libraries. + +6. The memory regions of the different sections must be protected depending on + the section's characteristics. Some sections are marked as *discardable* + and therefore can be safely freed at this point. These sections normally + contain temporary data that is only needed during the import, like the + informations for the base relocation. + +7. Now the library is loaded completely. It must be notified about this by + calling the entry point using the flag `DLL_PROCESS_ATTACH`. + +In the following paragraphs, each step is described. + + +Allocate memory +---------------- + +All memory required for the library must be reserved / allocated using +`VirtualAlloc`, as Windows provides functions to protect these memory blocks. +This is required to restrict access to the memory, like blocking write access +to the code or constant data. + +The OptionalHeader_ structure defines the size of the required memory block +for the library. It must be reserved at the address specified by `ImageBase` +if possible:: + + memory = VirtualAlloc((LPVOID)(PEHeader->OptionalHeader.ImageBase), + PEHeader->OptionalHeader.SizeOfImage, + MEM_RESERVE, + PAGE_READWRITE); + +If the reserved memory differs from the address given in `ImageBase`, base +relocation as described below must be done. + + +Copy sections +-------------- + +Once the memory has been reserved, the file contents can be copied to the +system. The section header must get evaluated in order to determine the +position in the file and the target area in memory. + +Before copying the data, the memory block must get committed:: + + dest = VirtualAlloc(baseAddress + section->VirtualAddress, + section->SizeOfRawData, + MEM_COMMIT, + PAGE_READWRITE); + +Sections without data in the file (like data sections for the used variables) +have a `SizeOfRawData` of `0`, so you can use the `SizeOfInitializedData` +or `SizeOfUninitializedData` of the OptionalHeader_. Which one must get +choosen depending on the bit flags `IMAGE_SCN_CNT_INITIALIZED_DATA` and +`IMAGE_SCN_CNT_UNINITIALIZED_DATA` that may be set in the section`s +characteristics. + + +Base relocation +---------------- + +All memory addresses in the code / data sections of a library are stored relative +to the address defined by `ImageBase` in the OptionalHeader_. If the library +can't be imported to this memory address, the references must get adjusted +=> *relocated*. The file format helps for this by storing informations about +all these references in the base relocation table, which can be found in the +directory entry 5 of the DataDirectory_ in the OptionalHeader_. + +This table consists of a series of this structure + +:: + + typedef struct _IMAGE_BASE_RELOCATION { + DWORD VirtualAddress; + DWORD SizeOfBlock; + } IMAGE_BASE_RELOCATION; + +It contains `(SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2` entries of 16 bits +each. The upper 4 bits define the type of relocation, the lower 12 bits define +the offset relative to the `VirtualAddress`. + +The only types that seem to be used in DLLs are + +IMAGE_REL_BASED_ABSOLUTE + No operation relocation. Used for padding. +IMAGE_REL_BASED_HIGHLOW + Add the delta between the `ImageBase` and the allocated memory block to the + 32 bits found at the offset. + + +Resolve imports +---------------- + +The directory entry 1 of the DataDirectory_ in the OptionalHeader_ specifies +a list of libraries to import symbols from. Each entry in this list is defined +as follows:: + + typedef struct _IMAGE_IMPORT_DESCRIPTOR { + union { + DWORD Characteristics; // 0 for terminating null import descriptor + DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) + }; + DWORD TimeDateStamp; // 0 if not bound, + // -1 if bound, and real date\time stamp + // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) + // O.W. date/time stamp of DLL bound to (Old BIND) + + DWORD ForwarderChain; // -1 if no forwarders + DWORD Name; + DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) + } IMAGE_IMPORT_DESCRIPTOR; + +The `Name` entry describes the offset to the NULL-terminated string of the library +name (e.g. `KERNEL32.DLL`). The `OriginalFirstThunk` entry points to a list +of references to the function names to import from the external library. +`FirstThunk` points to a list of addresses that gets filled with pointers to +the imported symbols. + +When we resolve the imports, we walk both lists in parallel, import the function +defined by the name in the first list and store the pointer to the symbol in the +second list:: + + nameRef = (DWORD *)(baseAddress + importDesc->OriginalFirstThunk); + symbolRef = (DWORD *)(baseAddress + importDesc->FirstThunk); + for (; *nameRef; nameRef++, symbolRef++) + { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)(codeBase + *nameRef); + *symbolRef = (DWORD)GetProcAddress(handle, (LPCSTR)&thunkData->Name); + if (*funcRef == 0) + { + handleImportError(); + return; + } + } + + +Protect memory +--------------- + +Every section specifies permission flags in it's `Characteristics` entry. +These flags can be one or a combination of + +IMAGE_SCN_MEM_EXECUTE + The section contains data that can be executed. + +IMAGE_SCN_MEM_READ + The section contains data that is readable. + +IMAGE_SCN_MEM_WRITE + The section contains data that is writeable. + +These flags must get mapped to the protection flags + +- PAGE_NOACCESS +- PAGE_WRITECOPY +- PAGE_READONLY +- PAGE_READWRITE +- PAGE_EXECUTE +- PAGE_EXECUTE_WRITECOPY +- PAGE_EXECUTE_READ +- PAGE_EXECUTE_READWRITE + +Now, the function `VirtualProtect` can be used to limit access to the memory. +If the program tries to access it in a unauthorized way, an exception gets +raised by Windows. + +In addition the section flags above, the following can be added: + +IMAGE_SCN_MEM_DISCARDABLE + The data in this section can be freed after the import. Usually this is + specified for relocation data. + +IMAGE_SCN_MEM_NOT_CACHED + The data in this section must not get cached by Windows. Add the bit + flag `PAGE_NOCACHE` to the protection flags above. + + +Notify library +--------------- + +The last thing to do is to call the DLL entry point (defined by +`AddressOfEntryPoint`) and so notifying the library about being attached +to a process. + +The function at the entry point is defined as + +:: + + typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); + +So the last code we need to execute is + +:: + + DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint); + (*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0); + +Afterwards we can use the exported functions as with any normal library. + + +Exported functions +=================== + +If you want to access the functions that are exported by the library, you need to find the entry +point to a symbol, i.e. the name of the function to call. + +The directory entry 0 of the DataDirectory_ in the OptionalHeader_ contains informations about +the exported functions. It's defined as follows:: + + typedef struct _IMAGE_EXPORT_DIRECTORY { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD Name; + DWORD Base; + DWORD NumberOfFunctions; + DWORD NumberOfNames; + DWORD AddressOfFunctions; // RVA from base of image + DWORD AddressOfNames; // RVA from base of image + DWORD AddressOfNameOrdinals; // RVA from base of image + } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; + +First thing to do, is to map the name of the function to the ordinal number of the exported +symbol. Therefore, just walk the arrays defined by `AddressOfNames` and `AddressOfNameOrdinals` +parallel until you found the required name. + +Now you can use the ordinal number to read the address by evaluating the n-th element of the +`AddressOfFunctions` array. + + +Freeing the library +==================== + +To free the custom loaded library, perform the steps + +- Call entry point to notify library about being detached:: + + DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint); + (*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0); + +- Free external libraries used to resolve imports. +- Free allocated memory. + + +MemoryModule +============= + +MemoryModule is a C-library that can be used to load a DLL from memory. + +The interface is very similar to the standard methods for loading of libraries:: + + typedef void *HMEMORYMODULE; + + HMEMORYMODULE MemoryLoadLibrary(const void *); + FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *); + void MemoryFreeLibrary(HMEMORYMODULE); + + +Downloads +---------- + +The latest development release can always be grabbed from my development SVN-Server at +https://leviathan.joachim-bauch.de/cgi-bin/viewcvs.cgi/MemoryModule/trunk/?root=misc + +Please note that it's located in my room so it doesn't run 24/7 and is often offline during +nights or on weekends. If you encounter problems connecting, please try again some other +time of day. + +All released versions can be downloaded from the list below. + + +Known issues +------------- + +- All memory that is not protected by section flags is gets committed using `PAGE_READWRITE`. + I don't know if this is correct. + + +License +-------- + +The MemoryModule library is released under the Lesser General Public License (LGPL). + +It is provided as-is without ANY warranty. You may use it at your own risk. + + +Copyright +========== + +The MemoryModule library and this tutorial are +Copyright (c) 2004 by Joachim Bauch. diff --git a/installer/py2exe/source/_memimporter.c b/installer/py2exe/source/_memimporter.c new file mode 100644 index 0000000..622023b --- /dev/null +++ b/installer/py2exe/source/_memimporter.c @@ -0,0 +1,130 @@ +/* + For the _memimporter compiled into py2exe exe-stubs we need "Python-dynload.h". + For the standalone .pyd we need +*/ + +#ifdef STANDALONE +# include +# include "Python-version.h" +#else +# include "Python-dynload.h" +# include +#endif +#include + +static char module_doc[] = +"Importer which can load extension modules from memory"; + +#include "MemoryModule.h" + +static void *memdup(void *ptr, Py_ssize_t size) +{ + void *p = malloc(size); + if (p == NULL) + return NULL; + memcpy(p, ptr, size); + return p; +} + +/* + Be sure to detect errors in FindLibrary - undetected errors lead to + very strange bahaviour. +*/ +static void* FindLibrary(char *name, PyObject *callback) +{ + PyObject *result; + char *p; + Py_ssize_t size; + + if (callback == NULL) + return NULL; + result = PyObject_CallFunction(callback, "s", name); + if (result == NULL) { + PyErr_Clear(); + return NULL; + } + if (-1 == PyString_AsStringAndSize(result, &p, &size)) { + PyErr_Clear(); + Py_DECREF(result); + return NULL; + } + p = memdup(p, size); + Py_DECREF(result); + return p; +} + +static PyObject *set_find_proc(PyObject *self, PyObject *args) +{ + PyObject *callback = NULL; + if (!PyArg_ParseTuple(args, "|O:set_find_proc", &callback)) + return NULL; + Py_XDECREF((PyObject *)findproc_data); + Py_XINCREF(callback); + findproc_data = (void *)callback; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +import_module(PyObject *self, PyObject *args) +{ + char *data; + int size; + char *initfuncname; + char *modname; + char *pathname; + HMEMORYMODULE hmem; + FARPROC do_init; + + char *oldcontext; + + /* code, initfuncname, fqmodulename, path */ + if (!PyArg_ParseTuple(args, "s#sss:import_module", + &data, &size, + &initfuncname, &modname, &pathname)) + return NULL; + hmem = MemoryLoadLibrary(pathname, data); + if (!hmem) { + PyErr_Format(PyExc_ImportError, + "MemoryLoadLibrary failed loading %s", pathname); + return NULL; + } + do_init = MemoryGetProcAddress(hmem, initfuncname); + if (!do_init) { + MemoryFreeLibrary(hmem); + PyErr_Format(PyExc_ImportError, + "Could not find function %s", initfuncname); + return NULL; + } + + oldcontext = _Py_PackageContext; + _Py_PackageContext = modname; + do_init(); + _Py_PackageContext = oldcontext; + if (PyErr_Occurred()) + return NULL; + /* Retrieve from sys.modules */ + return PyImport_ImportModule(modname); +} + +static PyObject * +get_verbose_flag(PyObject *self, PyObject *args) +{ + return PyInt_FromLong(Py_VerboseFlag); +} + +static PyMethodDef methods[] = { + { "import_module", import_module, METH_VARARGS, + "import_module(code, initfunc, dllname[, finder]) -> module" }, + { "get_verbose_flag", get_verbose_flag, METH_NOARGS, + "Return the Py_Verbose flag" }, + { "set_find_proc", set_find_proc, METH_VARARGS }, + { NULL, NULL }, /* Sentinel */ +}; + +DL_EXPORT(void) +init_memimporter(void) +{ + findproc = FindLibrary; + Py_InitModule3("_memimporter", methods, module_doc); +} diff --git a/installer/py2exe/source/_memimporter_module.c b/installer/py2exe/source/_memimporter_module.c new file mode 100644 index 0000000..305f39c --- /dev/null +++ b/installer/py2exe/source/_memimporter_module.c @@ -0,0 +1,2 @@ +#define STANDALONE +#include "_memimporter.c" diff --git a/installer/py2exe/source/icon.rc b/installer/py2exe/source/icon.rc new file mode 100644 index 0000000..49e0fa6 --- /dev/null +++ b/installer/py2exe/source/icon.rc @@ -0,0 +1,53 @@ +//Microsoft Developer Studio generated resource script. +//Hacked by theller to make it compile with mingw32, too. +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON DISCARDABLE "source\\icon1.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// diff --git a/installer/py2exe/source/icon1.ico b/installer/py2exe/source/icon1.ico new file mode 100644 index 0000000..b55d82b Binary files /dev/null and b/installer/py2exe/source/icon1.ico differ diff --git a/installer/py2exe/source/import-tab.c b/installer/py2exe/source/import-tab.c new file mode 100644 index 0000000..a2bae4b --- /dev/null +++ b/installer/py2exe/source/import-tab.c @@ -0,0 +1,64 @@ + { "Py_Initialize", NULL }, + { "PyRun_SimpleString", NULL }, + { "Py_Finalize", NULL }, + { "Py_GetPath", NULL }, + { "Py_SetPythonHome", NULL }, + { "Py_SetProgramName", NULL }, + { "PyMarshal_ReadObjectFromString", NULL }, + { "PyObject_CallFunction", NULL }, + { "PyString_AsStringAndSize", NULL }, + { "PyString_AsString", NULL }, + { "PyArg_ParseTuple", NULL }, + { "PyErr_Format", NULL }, + { "PyImport_ImportModule", NULL }, + { "PyInt_FromLong", NULL }, + { "PyInt_AsLong", NULL }, + { "PyLong_FromVoidPtr", NULL }, +#ifdef _DEBUG + { "Py_InitModule4TraceRefs", NULL }, +#else +# if defined (_WIN64) + { "Py_InitModule4_64", NULL }, +# else + { "Py_InitModule4", NULL }, +# endif +#endif + { "PyTuple_New", NULL }, + { "PyTuple_SetItem", NULL }, + { "Py_IsInitialized", NULL }, + { "PyObject_SetAttrString", NULL }, + { "PyCFunction_NewEx", NULL }, + { "PyObject_GetAttrString", NULL }, + { "Py_BuildValue", NULL }, + { "PyObject_Call", NULL }, + { "PySys_WriteStderr", NULL }, + { "PyErr_Occurred", NULL }, + { "PyErr_Clear", NULL }, + { "PyObject_IsInstance", NULL }, + { "PyInt_Type", NULL }, + { "_Py_NoneStruct", NULL }, + { "PyExc_ImportError", NULL }, + { "_Py_PackageContext", NULL }, + { "PyGILState_Ensure", NULL }, + { "PyGILState_Release", NULL }, + { "PySys_SetObject", NULL }, + { "PySys_GetObject", NULL }, + { "PyString_FromString", NULL }, + { "Py_FdIsInteractive", NULL }, + { "PyRun_InteractiveLoop", NULL }, + { "PySys_SetArgv", NULL }, + { "PyImport_AddModule", NULL }, + { "PyModule_GetDict", NULL }, + { "PySequence_Length", NULL }, + { "PySequence_GetItem", NULL }, + { "PyEval_EvalCode", NULL }, + { "PyErr_Print", NULL }, + { "PyBool_FromLong", NULL }, + { "Py_VerboseFlag", NULL }, + { "Py_NoSiteFlag", NULL }, + { "Py_OptimizeFlag", NULL }, + { "Py_IgnoreEnvironmentFlag", NULL }, + { "PyObject_Str", NULL }, + { "PyList_New", NULL }, + { "PyList_SetItem", NULL }, + { "PyList_Append", NULL }, diff --git a/installer/py2exe/source/import-tab.h b/installer/py2exe/source/import-tab.h new file mode 100644 index 0000000..f51ba0b --- /dev/null +++ b/installer/py2exe/source/import-tab.h @@ -0,0 +1,56 @@ +#define Py_Initialize ((void(*)(void))imports[0].proc) +#define PyRun_SimpleString ((int(*)(char *))imports[1].proc) +#define Py_Finalize ((void(*)(void))imports[2].proc) +#define Py_GetPath ((char *(*)(void))imports[3].proc) +#define Py_SetPythonHome ((void(*)(char *))imports[4].proc) +#define Py_SetProgramName ((void(*)(char *))imports[5].proc) +#define PyMarshal_ReadObjectFromString ((PyObject *(*)(char *, Py_ssize_t))imports[6].proc) +#define PyObject_CallFunction ((PyObject *(*)(PyObject *, char *, ...))imports[7].proc) +#define PyString_AsStringAndSize ((int(*)(PyObject *, char **, Py_ssize_t *))imports[8].proc) +#define PyString_AsString ((char *(*)(PyObject *))imports[9].proc) +#define PyArg_ParseTuple ((int(*)(PyObject *, char *, ...))imports[10].proc) +#define PyErr_Format ((PyObject *(*)(PyObject *, const char *, ...))imports[11].proc) +#define PyImport_ImportModule ((PyObject *(*)(char *))imports[12].proc) +#define PyInt_FromLong ((PyObject *(*)(long))imports[13].proc) +#define PyInt_AsLong ((long(*)(PyObject *))imports[14].proc) +#define PyLong_FromVoidPtr ((PyObject *(*)(void *))imports[15].proc) +#define Py_InitModule4 ((PyObject *(*)(char *, PyMethodDef *, char *, PyObject *, int))imports[16].proc) +#define PyTuple_New ((PyObject *(*)(Py_ssize_t))imports[17].proc) +#define PyTuple_SetItem ((int(*)(PyObject*, Py_ssize_t, PyObject *))imports[18].proc) +#define Py_IsInitialized ((int(*)(void))imports[19].proc) +#define PyObject_SetAttrString ((int(*)(PyObject *, char *, PyObject *))imports[20].proc) +#define PyCFunction_NewEx ((PyObject *(*)(PyMethodDef *, PyObject *, PyObject *))imports[21].proc) +#define PyObject_GetAttrString ((PyObject *(*)(PyObject *, char *))imports[22].proc) +#define Py_BuildValue ((PyObject *(*)(char *, ...))imports[23].proc) +#define PyObject_Call ((PyObject *(*)(PyObject *, PyObject *, PyObject *))imports[24].proc) +#define PySys_WriteStderr ((void(*)(const char *, ...))imports[25].proc) +#define PyErr_Occurred ((PyObject *(*)(void))imports[26].proc) +#define PyErr_Clear ((void(*)(void))imports[27].proc) +#define PyObject_IsInstance ((int(*)(PyObject *, PyObject *))imports[28].proc) +#define PyInt_Type (*(PyObject(*))imports[29].proc) +#define _Py_NoneStruct (*(PyObject(*))imports[30].proc) +#define PyExc_ImportError (*(PyObject *(*))imports[31].proc) +#define _Py_PackageContext (*(char *(*))imports[32].proc) +#define PyGILState_Ensure ((PyGILState_STATE(*)(void))imports[33].proc) +#define PyGILState_Release ((void(*)(PyGILState_STATE))imports[34].proc) +#define PySys_SetObject ((void(*)(char *, PyObject *))imports[35].proc) +#define PySys_GetObject ((PyObject *(*)(char *))imports[36].proc) +#define PyString_FromString ((PyObject *(*)(char *))imports[37].proc) +#define Py_FdIsInteractive ((int(*)(FILE *, char *))imports[38].proc) +#define PyRun_InteractiveLoop ((int(*)(FILE *, char *))imports[39].proc) +#define PySys_SetArgv ((void(*)(int, char **))imports[40].proc) +#define PyImport_AddModule ((PyObject *(*)(char *))imports[41].proc) +#define PyModule_GetDict ((PyObject *(*)(PyObject *))imports[42].proc) +#define PySequence_Length ((Py_ssize_t(*)(PyObject *))imports[43].proc) +#define PySequence_GetItem ((PyObject *(*)(PyObject *, Py_ssize_t))imports[44].proc) +#define PyEval_EvalCode ((PyObject *(*)(PyCodeObject *, PyObject *, PyObject *))imports[45].proc) +#define PyErr_Print ((void(*)(void))imports[46].proc) +#define PyBool_FromLong ((PyObject *(*)(long))imports[47].proc) +#define Py_VerboseFlag (*(int(*))imports[48].proc) +#define Py_NoSiteFlag (*(int(*))imports[49].proc) +#define Py_OptimizeFlag (*(int(*))imports[50].proc) +#define Py_IgnoreEnvironmentFlag (*(int(*))imports[51].proc) +#define PyObject_Str ((PyObject *(*)(PyObject *))imports[52].proc) +#define PyList_New ((PyObject *(*)(Py_ssize_t))imports[53].proc) +#define PyList_SetItem ((int(*)(PyObject *, Py_ssize_t, PyObject *))imports[54].proc) +#define PyList_Append ((int(*)(PyObject *, PyObject *))imports[55].proc) diff --git a/installer/py2exe/source/mktab.py b/installer/py2exe/source/mktab.py new file mode 100644 index 0000000..c1563dc --- /dev/null +++ b/installer/py2exe/source/mktab.py @@ -0,0 +1,102 @@ +# A script to generate helper files for dynamic linking to the Python dll +# +decls = ''' +void, Py_Initialize, (void) +int, PyRun_SimpleString, (char *) +void, Py_Finalize, (void) +char *, Py_GetPath, (void) +void, Py_SetPythonHome, (char *) +void, Py_SetProgramName, (char *) +PyObject *, PyMarshal_ReadObjectFromString, (char *, Py_ssize_t) +PyObject *, PyObject_CallFunction, (PyObject *, char *, ...) +int, PyString_AsStringAndSize, (PyObject *, char **, Py_ssize_t *) +char *, PyString_AsString, (PyObject *) +int, PyArg_ParseTuple, (PyObject *, char *, ...) +PyObject *, PyErr_Format, (PyObject *, const char *, ...) +PyObject *, PyImport_ImportModule, (char *) +PyObject *, PyInt_FromLong, (long) +long, PyInt_AsLong, (PyObject *) +PyObject *, PyLong_FromVoidPtr, (void *) +PyObject *, Py_InitModule4, (char *, PyMethodDef *, char *, PyObject *, int) +PyObject *, PyTuple_New, (Py_ssize_t) +int, PyTuple_SetItem, (PyObject*, Py_ssize_t, PyObject *) +int, Py_IsInitialized, (void) +int, PyObject_SetAttrString, (PyObject *, char *, PyObject *) +PyObject *, PyCFunction_NewEx, (PyMethodDef *, PyObject *, PyObject *) +PyObject *, PyObject_GetAttrString, (PyObject *, char *) +PyObject *, Py_BuildValue, (char *, ...) +PyObject *, PyObject_Call, (PyObject *, PyObject *, PyObject *) +void, PySys_WriteStderr, (const char *, ...) +PyObject *, PyErr_Occurred, (void) +void, PyErr_Clear, (void) +int, PyObject_IsInstance, (PyObject *, PyObject *) + +PyObject, PyInt_Type +PyObject, _Py_NoneStruct +PyObject *, PyExc_ImportError +char *, _Py_PackageContext + +PyGILState_STATE, PyGILState_Ensure, (void) +void, PyGILState_Release, (PyGILState_STATE) + +void, PySys_SetObject, (char *, PyObject *) +PyObject *, PySys_GetObject, (char *) +PyObject *, PyString_FromString, (char *) +int, Py_FdIsInteractive, (FILE *, char *) +int, PyRun_InteractiveLoop, (FILE *, char *) +void, PySys_SetArgv, (int, char **) +PyObject *, PyImport_AddModule, (char *) +PyObject *, PyModule_GetDict, (PyObject *) +Py_ssize_t, PySequence_Length, (PyObject *) +PyObject *, PySequence_GetItem, (PyObject *, Py_ssize_t) +//int, PyCode_Check, (PyObject *) +PyObject *, PyEval_EvalCode, (PyCodeObject *, PyObject *, PyObject *) +void, PyErr_Print, (void) +PyObject *, PyBool_FromLong, (long) +int, Py_VerboseFlag +int, Py_NoSiteFlag +int, Py_OptimizeFlag +int, Py_IgnoreEnvironmentFlag +PyObject *, PyObject_Str, (PyObject *) +PyObject *, PyList_New, (Py_ssize_t) +int, PyList_SetItem, (PyObject *, Py_ssize_t, PyObject *) +int, PyList_Append, (PyObject *, PyObject *) +'''.strip().splitlines() + +import string + +hfile = open("import-tab.h", "w") +cfile = open("import-tab.c", "w") + +index = 0 +for decl in decls: + if not decl or decl.startswith("//"): + continue + items = decl.split(',', 2) + if len(items) == 3: + # exported function with argument list + restype, name, argtypes = map(string.strip, items) + print >> hfile, '#define %(name)s ((%(restype)s(*)%(argtypes)s)imports[%(index)d].proc)' % locals() + elif len(items) == 2: + # exported data + typ, name = map(string.strip, items) + print >> hfile, '#define %(name)s (*(%(typ)s(*))imports[%(index)s].proc)' % locals() + else: + raise ValueError, "could not parse %r" % decl + if name == "Py_InitModule4": + print >> cfile, '#ifdef _DEBUG' + print >> cfile, '\t{ "Py_InitModule4TraceRefs", NULL },' % locals() + print >> cfile, '#else' + print >> cfile, '# if defined (_WIN64)' + print >> cfile, '\t{ "Py_InitModule4_64", NULL },' % locals() + print >> cfile, '# else' + print >> cfile, '\t{ "Py_InitModule4", NULL },' % locals() + print >> cfile, '# endif' + print >> cfile, '#endif' + else: + print >> cfile, '\t{ "%(name)s", NULL },' % locals() + + index += 1 + +hfile.close() +cfile.close() diff --git a/installer/py2exe/source/py2exe_util.c b/installer/py2exe/source/py2exe_util.c new file mode 100644 index 0000000..aa90811 --- /dev/null +++ b/installer/py2exe/source/py2exe_util.c @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2000, 2001, 2002, 2003 Thomas Heller + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "Python.h" +#include +#include + +static char module_doc[] = +"Utility functions for the py2exe package"; + +HANDLE (__stdcall *pfn_BeginUpdateResource)(LPCWSTR, BOOL); +BOOL (__stdcall* pfn_EndUpdateResource)(HANDLE, BOOL); +BOOL (__stdcall* pfn_UpdateResource)(HANDLE, LPCWSTR, LPCWSTR, WORD, LPVOID, DWORD); +HANDLE (__stdcall* pfn_CreateFileW)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); + +static PyObject *SystemError(int code, char *msg) +{ + LPVOID lpMsgBuf; + char Buffer[4096]; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR) &lpMsgBuf, + 0, + NULL); + sprintf(Buffer, "%s: %s", msg, lpMsgBuf); + LocalFree (lpMsgBuf); + PyErr_SetString(PyExc_RuntimeError, Buffer); + return NULL; +} + +Py_UNICODE *PyObject_AsRESOURCEID(PyObject *py_res_type) +{ + if (PyInt_Check(py_res_type)) + return (Py_UNICODE *)MAKEINTRESOURCE(PyInt_AsLong(py_res_type)); + if (PyUnicode_Check(py_res_type)) + return PyUnicode_AS_UNICODE(py_res_type); + PyErr_Format(PyExc_ValueError, + "resource argument must be int or unicode (not '%s')", + py_res_type->ob_type->tp_name); + return NULL; +} + +/* + * Ref for the icon code, from MSDN: + * Icons in Win32 + * John Hornick + * Microsoft Corporation + * Created: September 29, 1995 + */ + +#pragma pack(2) + +/* Structure of .ico files */ + +typedef struct { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + DWORD dwImageOffset; +} ICONDIRENTRY; + +typedef struct { + WORD idReserved; /* Must be 0 */ + WORD idType; /* Should check that this is 1 for icons */ + WORD idCount; /* Number os ICONDIRENTRYs to follow */ + ICONDIRENTRY idEntries[0]; +} ICONDIRHEADER; + +/* Format of RT_GROUP_ICON resources */ + +typedef struct { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + WORD nID; +} GRPICONDIRENTRY; + +typedef struct { + WORD idReserved; + WORD idType; + WORD idCount; + GRPICONDIRENTRY idEntries[0]; +} GRPICONDIRHEADER; + +#pragma pack() + +/* + * Map a file into memory for reading. + * + * Pointer returned must be freed with UnmapViewOfFile(). + */ +static char *MapExistingFile (Py_UNICODE *pathname, DWORD *psize) +{ + HANDLE hFile, hFileMapping; + DWORD nSizeLow, nSizeHigh; + char *data; + + if (pfn_CreateFileW == NULL) { + SetLastError(1); /* Incorrect function */ + return NULL; + } + hFile = pfn_CreateFileW(pathname, + GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return NULL; + nSizeLow = GetFileSize(hFile, &nSizeHigh); + hFileMapping = CreateFileMapping(hFile, + NULL, PAGE_READONLY, 0, 0, NULL); + CloseHandle (hFile); + + if (hFileMapping == INVALID_HANDLE_VALUE) + return NULL; + + data = MapViewOfFile(hFileMapping, + FILE_MAP_READ, 0, 0, 0); + + CloseHandle(hFileMapping); + *psize = nSizeLow; + return data; +} + +/* + * Create a GRPICONDIRHEADER from an ICONDIRHEADER. + * + * Returns malloc()'d memory. + */ +static GRPICONDIRHEADER *CreateGrpIconDirHeader(ICONDIRHEADER *pidh, int icoid) +{ + GRPICONDIRHEADER *pgidh; + size_t size; + int i; + + size = sizeof(GRPICONDIRHEADER) + sizeof(GRPICONDIRENTRY) * pidh->idCount; + pgidh = (GRPICONDIRHEADER *)malloc(size); + pgidh->idReserved = pidh->idReserved; + pgidh->idType = pidh->idType; + pgidh->idCount = pidh->idCount; + + for (i = 0; i < pidh->idCount; ++i) { + pgidh->idEntries[i].bWidth = pidh->idEntries[i].bWidth; + pgidh->idEntries[i].bHeight = pidh->idEntries[i].bHeight; + pgidh->idEntries[i].bColorCount = pidh->idEntries[i].bColorCount; + pgidh->idEntries[i].bReserved = pidh->idEntries[i].bReserved; + pgidh->idEntries[i].wPlanes = pidh->idEntries[i].wPlanes; + pgidh->idEntries[i].wBitCount = pidh->idEntries[i].wBitCount; + pgidh->idEntries[i].dwBytesInRes = pidh->idEntries[i].dwBytesInRes; + pgidh->idEntries[i].nID = icoid + i ; + } + return pgidh; +} + +static PyObject* do_add_icon(Py_UNICODE *exename, Py_UNICODE *iconame, int icoid, BOOL bDelete) +{ + static rt_icon_id = 0; + + /* from the .ico file */ + ICONDIRHEADER *pidh; + WORD idh_size; + /* for the resources */ + GRPICONDIRHEADER *pgidh = NULL; + WORD gidh_size; + HANDLE hUpdate = NULL; + int i; + char *icodata; + DWORD icosize; + icodata = MapExistingFile(iconame, &icosize); + if (!icodata) { + return SystemError(GetLastError(), "MapExistingFile"); + } + + pidh = (ICONDIRHEADER *)icodata; + idh_size = sizeof(ICONDIRHEADER) + sizeof(ICONDIRENTRY) * pidh->idCount; + + pgidh = CreateGrpIconDirHeader(pidh, icoid); + gidh_size = sizeof(GRPICONDIRHEADER) + sizeof(GRPICONDIRENTRY) * pgidh->idCount; + + if (pfn_BeginUpdateResource == NULL + || pfn_UpdateResource == NULL + || pfn_EndUpdateResource == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "this function requires unicows.dll in the Python directory on Win 95/98/Me"); + return NULL; + } + + hUpdate = pfn_BeginUpdateResource(exename, bDelete); + if (!hUpdate) { + SystemError(GetLastError(), "BeginUpdateResource"); + goto failed; + } + + /* Each RT_ICON resource in an image file (containing the icon for one + specific resolution and number of colors) must have a unique id, and + the id must be in the GRPICONDIRHEADER's nID member. + + So, we use a *static* variable rt_icon_id which is incremented for each + RT_ICON resource and written into the GRPICONDIRHEADER's nID member. + + XXX Do we need a way to reset the rt_icon_id variable to zero? If we + are building a lot of images in one setup script? + */ + for (i = 0; i < pidh->idCount; ++i) { + pgidh->idEntries[i].nID = rt_icon_id++; + } + if (!pfn_UpdateResource(hUpdate, + (Py_UNICODE *)MAKEINTRESOURCE(RT_GROUP_ICON), + (Py_UNICODE *)MAKEINTRESOURCE(icoid), + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), + pgidh, + gidh_size)) { + SystemError(GetLastError(), "UpdateResource"); + goto failed; + } + for (i = 0; i < pidh->idCount; ++i) { + char *cp = &icodata[pidh->idEntries[i].dwImageOffset]; + int cBytes = pidh->idEntries[i].dwBytesInRes; + if (!pfn_UpdateResource(hUpdate, + (Py_UNICODE *)MAKEINTRESOURCE(RT_ICON), + (Py_UNICODE *)MAKEINTRESOURCE(pgidh->idEntries[i].nID), + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), + cp, + cBytes)) { + SystemError(GetLastError(), "UpdateResource"); + goto failed; + } + } + + free(pgidh); + UnmapViewOfFile(icodata); + + if (!pfn_EndUpdateResource(hUpdate, FALSE)) + return SystemError(GetLastError(), "EndUpdateResource"); + Py_INCREF(Py_None); + return Py_None; + + failed: + if (pgidh) + free(pgidh); + if (hUpdate) + pfn_EndUpdateResource(hUpdate, TRUE); + if (icodata) + UnmapViewOfFile(icodata); + return NULL; +} + +static PyObject *update_icon(PyObject *self, PyObject *args) +{ + Py_UNICODE *exename; + Py_UNICODE *iconame; + BOOL bDelete = 0; + + if (!PyArg_ParseTuple(args, "uu|i", &exename, &iconame, &bDelete)) + return NULL; + return do_add_icon(exename, iconame, 1, bDelete); +} + +static PyObject *add_icon(PyObject *self, PyObject *args) +{ + Py_UNICODE *exename; + Py_UNICODE *iconame; + int resid; + BOOL bDelete = 0; + + if (!PyArg_ParseTuple(args, "uui|i", &exename, &iconame, &resid, &bDelete)) + return NULL; + + return do_add_icon(exename, iconame, resid, bDelete); +} + +static PyObject *add_resource(PyObject *self, PyObject *args) +{ + Py_UNICODE *exename; + HANDLE hUpdate = NULL; + BOOL bDelete = 0; + char *res_data; + int res_size; + PyObject *py_res_type, *py_res_id; + Py_UNICODE *res_type; + Py_UNICODE *res_id; + + if (!PyArg_ParseTuple(args, "us#OO|i", + &exename, &res_data, &res_size, + &py_res_type, &py_res_id, &bDelete)) + return NULL; + + if (pfn_BeginUpdateResource == NULL + || pfn_UpdateResource == NULL + || pfn_EndUpdateResource == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "this function requires unicows.dll in the Python directory on Win 95/98/Me"); + return NULL; + } + + res_type = PyObject_AsRESOURCEID(py_res_type); + if (res_type==NULL) + return NULL; + + res_id = PyObject_AsRESOURCEID(py_res_id); + if (res_id==NULL) + return NULL; + + hUpdate = pfn_BeginUpdateResource(exename, bDelete); + if (!hUpdate) { + SystemError(GetLastError(), "BeginUpdateResource"); + goto failed; + } + + if (!pfn_UpdateResource(hUpdate, + res_type, + res_id, + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), + res_data, + res_size)) { + SystemError(GetLastError(), "UpdateResource"); + goto failed; + } + + if (!pfn_EndUpdateResource(hUpdate, FALSE)) + return SystemError(GetLastError(), "EndUpdateResource"); + Py_INCREF(Py_None); + return Py_None; + + failed: + if (hUpdate) + pfn_EndUpdateResource(hUpdate, TRUE); + return NULL; +} + +/*********************************************************************************** + * + * Dependency tracker + * + */ + +static char* searchpath; + +static PyObject *py_result; + +/* Exception */ +static PyObject *BindError; + + +BOOL __stdcall StatusRoutine(IMAGEHLP_STATUS_REASON reason, + PSTR ImageName, + PSTR DllName, + ULONG Va, + ULONG Parameter) +{ + DWORD result; + + char fname[_MAX_PATH+1]; + + switch(reason) { + case BindOutOfMemory: + case BindRvaToVaFailed: + case BindNoRoomInImage: + case BindImportProcedureFailed: + break; + + case BindImportProcedure: + if (0 == strcmp((LPSTR)Parameter, "PyImport_ImportModule")) { + PyDict_SetItemString(py_result, ImageName, PyInt_FromLong(1)); + } + break; + + case BindForwarder: + case BindForwarderNOT: + case BindImageModified: + case BindExpandFileHeaders: + case BindImageComplete: + case BindSymbolsNotUpdated: + case BindMismatchedSymbols: + break; + + case BindImportModuleFailed: + case BindImportModule: + if (!py_result) + return FALSE; + result = SearchPath(searchpath, DllName, NULL, sizeof(fname), fname, NULL); + if (result) + PyDict_SetItemString(py_result, fname, PyInt_FromLong(0)); + else + PyDict_SetItemString(py_result, DllName, PyInt_FromLong(0)); + break; + } + return TRUE; +} + +static PyObject *depends(PyObject *self, PyObject *args) +{ + char *imagename; + PyObject *pdict; + HINSTANCE hLib; + BOOL(__stdcall *pBindImageEx)( + DWORD Flags, + LPSTR ImageName, + LPSTR DllPath, + LPSTR SymbolPath, + PIMAGEHLP_STATUS_ROUTINE StatusRoutine + ); + + searchpath = NULL; + + if (!PyArg_ParseTuple(args, "s|s", &imagename, &searchpath)) + return NULL; + hLib = LoadLibrary("imagehlp"); + if (!hLib) { + PyErr_SetString(PyExc_SystemError, + "imagehlp.dll not found"); + return NULL; + } + pBindImageEx = (BOOL(__stdcall *)(DWORD, LPSTR, LPSTR, LPSTR, PIMAGEHLP_STATUS_ROUTINE)) + GetProcAddress(hLib, "BindImageEx"); + if (!pBindImageEx) { + PyErr_SetString(PyExc_SystemError, + "imagehlp.dll does not export BindImageEx function"); + FreeLibrary(hLib); + return NULL; + } + + py_result = PyDict_New(); + if (!pBindImageEx(BIND_NO_BOUND_IMPORTS | BIND_NO_UPDATE | BIND_ALL_IMAGES, + imagename, + searchpath, + NULL, + StatusRoutine)) { + FreeLibrary(hLib); + Py_DECREF(py_result); + PyErr_SetExcFromWindowsErrWithFilename(BindError, GetLastError(), imagename); + return NULL; + } + FreeLibrary(hLib); + if (PyErr_Occurred()) { + Py_DECREF(py_result); + return NULL; + } + pdict = py_result; + py_result = NULL; + return (PyObject *)pdict; +} + +static PyObject *get_windir(PyObject *self, PyObject *args) +{ + char windir[_MAX_PATH]; + if (!PyArg_ParseTuple(args, "")) + return NULL; + if (GetWindowsDirectory(windir, sizeof(windir))) + return PyString_FromString(windir); + PyErr_SetString(PyExc_RuntimeError, "could not get windows directory"); + return NULL; +} + +static PyObject *get_sysdir(PyObject *self, PyObject *args) +{ + char sysdir[_MAX_PATH]; + if (!PyArg_ParseTuple(args, "")) + return NULL; + if (GetSystemDirectory(sysdir, sizeof(sysdir))) + return PyString_FromString(sysdir); + PyErr_SetString(PyExc_RuntimeError, "could not get system directory"); + return NULL; +} + +static PyMethodDef methods[] = { + { "add_resource", add_resource, METH_VARARGS, + "add_resource(exe, res_data, res_size, res_type, res_id [, delete=0]) - add resource to an exe file\n"}, + { "add_icon", add_icon, METH_VARARGS, + "add_icon(exe, ico, ico_id[, delete=0]) - add icon to an exe file\n" + "If the delete flag is 1, delete all existing resources", }, + { "update_icon", update_icon, METH_VARARGS, + "update_icon(exe, ico[, delete=0]) - add icon to an exe file\n" + "If the delete flag is 1, delete all existing resources", }, + { "get_sysdir", get_sysdir, METH_VARARGS, + "get_sysdir() - Return the windows system directory"}, + { "get_windir", get_windir, METH_VARARGS, + "get_windir() - Return the windows directory"}, + { "depends", depends, METH_VARARGS, + "depends(executable[, loadpath]) -> list\n\n" + "Return a list containing the dlls needed to run 'executable'.\n" + "The dlls are searched along 'loadpath'\n" + "or windows default loadpath", }, + { NULL, NULL }, /* Sentinel */ +}; + +DL_EXPORT(void) +initpy2exe_util(void) +{ + PyObject *m, *d; + HMODULE hmod = NULL; + + if (GetVersion() & 0x80000000) + /* Win 95, 98, Me */ + /* We don't check *here* if this fails. We check later! */ + hmod = LoadLibrary("unicows.dll"); + else + /* Win NT, 2000, XP */ + hmod = LoadLibrary("kernel32.dll"); + + pfn_BeginUpdateResource = (HANDLE (__stdcall *)(LPCWSTR, BOOL)) + GetProcAddress(hmod, "BeginUpdateResourceW"); + pfn_EndUpdateResource = (BOOL (__stdcall*)(HANDLE, BOOL)) + GetProcAddress(hmod, "EndUpdateResourceW"); + pfn_UpdateResource = (BOOL (__stdcall*)(HANDLE, LPCWSTR, LPCWSTR, WORD, LPVOID, DWORD)) + GetProcAddress(hmod, "UpdateResourceW"); + pfn_CreateFileW = (HANDLE (__stdcall*)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, + DWORD, DWORD, HANDLE)) + GetProcAddress(hmod, "CreateFileW"); + + + m = Py_InitModule3("py2exe_util", methods, module_doc); + if (m) { + d = PyModule_GetDict(m); + + BindError = PyErr_NewException("py2exe_util.bind_error", NULL, NULL); + if (BindError) + PyDict_SetItemString(d, "bind_error", BindError); + } +} diff --git a/installer/py2exe/source/resource.h b/installer/py2exe/source/resource.h new file mode 100644 index 0000000..ec4ebcb --- /dev/null +++ b/installer/py2exe/source/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by icon.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/installer/py2exe/source/run.c b/installer/py2exe/source/run.c new file mode 100644 index 0000000..181768d --- /dev/null +++ b/installer/py2exe/source/run.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2001 Thomas Heller + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +void SystemError(int error, char *msg) +{ + char Buffer[1024]; + + if (msg) + fprintf(stderr, msg); + if (error) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&lpMsgBuf, + 0, + NULL + ); + strncpy(Buffer, lpMsgBuf, sizeof(Buffer)); + LocalFree(lpMsgBuf); + fprintf(stderr, Buffer); + } +} + +extern int init(char *); +extern int start(int argc, char **argv); +extern void init_memimporter(void); + +int main (int argc, char **argv) +{ + int result; + result = init("console_exe"); + if (result) + return result; + init_memimporter(); + return start(argc, argv); +} diff --git a/installer/py2exe/source/run_ctypes_dll.c b/installer/py2exe/source/run_ctypes_dll.c new file mode 100644 index 0000000..6fd74bf --- /dev/null +++ b/installer/py2exe/source/run_ctypes_dll.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2000, 2001 Thomas Heller + * Copyright (c) 2003 Mark Hammond + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include +#include "Python-dynload.h" +#include "MemoryModule.h" + +// Function pointers we load from _ctypes.pyd +typedef int (__stdcall *__PROC__DllCanUnloadNow) (void); +typedef HRESULT (__stdcall *__PROC__DllGetClassObject) (REFCLSID, REFIID, LPVOID *); + +CRITICAL_SECTION csInit; // protecting our init code + +__PROC__DllCanUnloadNow Pyc_DllCanUnloadNow = NULL; +__PROC__DllGetClassObject Pyc_DllGetClassObject = NULL; + +extern void init_memimporter(void); + +void SystemError(int error, char *msg) +{ + char Buffer[1024]; + int n; + + if (error) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&lpMsgBuf, + 0, + NULL + ); + strncpy(Buffer, lpMsgBuf, sizeof(Buffer)); + LocalFree(lpMsgBuf); + } else + Buffer[0] = '\0'; + n = lstrlen(Buffer); + _snprintf(Buffer+n, sizeof(Buffer)-n, msg); + MessageBox(GetFocus(), Buffer, NULL, MB_OK | MB_ICONSTOP); +} + +BOOL have_init = FALSE; +HANDLE g_ctypes = 0; +HMODULE gInstance = 0; + +extern int init_with_instance(HMODULE, char *); +extern void fini(); +extern int run_script(void); + +int load_ctypes(void) +{ + char dll_path[_MAX_PATH+_MAX_FNAME+1]; + + // shouldn't do this twice + assert(g_ctypes == NULL); +#ifdef _DEBUG + strcpy(dll_path, "_ctypes_d.pyd"); +#else + strcpy(dll_path, "_ctypes.pyd"); +#endif + g_ctypes = MyGetModuleHandle(dll_path); +/* + if (g_ctypes == NULL) { + // not already loaded - try and load from the current dir + char *temp; + GetModuleFileNameA(gInstance, dll_path, sizeof(dll_path)); + temp = dll_path + strlen(dll_path); + while (temp>dll_path && *temp != '\\') + temp--; + // and printf directly in the buffer. + // temp points to '\\filename.ext'! +#ifdef _DEBUG + g_ctypes = MyGetModuleHandle("_ctypes_d.pyd"); +#else + g_ctypes = MyGetModuleHandle("_ctypes.pyd"); +#endif + g_ctypes = LoadLibraryEx(dll_path, // points to name of executable module + NULL, // HANDLE hFile, // reserved, must be NULL + LOAD_WITH_ALTERED_SEARCH_PATH // DWORD dwFlags // entry-point execution flag + ); + } +*/ + if (g_ctypes == NULL) { + OutputDebugString("GetModuleHandle _ctypes?.pyd failed"); + // give up in disgust + return -1; + } + + Pyc_DllCanUnloadNow = (__PROC__DllCanUnloadNow)MyGetProcAddress(g_ctypes, "DllCanUnloadNow"); + Pyc_DllGetClassObject = (__PROC__DllGetClassObject)MyGetProcAddress(g_ctypes, "DllGetClassObject"); + return 0; +} + +int check_init() +{ + if (!have_init) { + EnterCriticalSection(&csInit); + // Check the flag again - another thread may have beat us to it! + if (!have_init) { + PyObject *frozen; + /* If Python had previously been initialized, we must use + PyGILState_Ensure normally. If Python has not been initialized, + we must leave the thread state unlocked, so other threads can + call in. + */ + PyGILState_STATE restore_state = PyGILState_UNLOCKED; + if (Py_IsInitialized && Py_IsInitialized()) { + restore_state = PyGILState_Ensure(); + } + // a little DLL magic. Set sys.frozen='dll' + init_with_instance(gInstance, "dll"); + init_memimporter(); + frozen = PyInt_FromLong((LONG)gInstance); + if (frozen) { + PySys_SetObject("frozendllhandle", frozen); + Py_DECREF(frozen); + } + // Now run the generic script - this always returns in a DLL. + run_script(); + have_init = TRUE; + if (g_ctypes == NULL) + load_ctypes(); + // Reset the thread-state, so any thread can call in + PyGILState_Release(restore_state); + } + LeaveCriticalSection(&csInit); + } + return g_ctypes != NULL; +} + + +// ***************************************************************** +// All the public entry points needed for COM, Windows, and anyone +// else who wants their piece of the action. +// ***************************************************************** +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + if ( dwReason == DLL_PROCESS_ATTACH) { + gInstance = hInstance; + InitializeCriticalSection(&csInit); + } + else if ( dwReason == DLL_PROCESS_DETACH ) { + gInstance = 0; + DeleteCriticalSection(&csInit); + // not much else safe to do here + } + return TRUE; +} + +HRESULT __stdcall DllCanUnloadNow(void) +{ + HRESULT rc; + check_init(); + assert(Pyc_DllCanUnloadNow); + if (!Pyc_DllCanUnloadNow) return E_UNEXPECTED; + rc = Pyc_DllCanUnloadNow(); + return rc; +} + +//__declspec(dllexport) int __stdcall DllGetClassObject(void *rclsid, void *riid, void *ppv) +HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + HRESULT rc; + check_init(); + assert(Pyc_DllGetClassObject); + if (!Pyc_DllGetClassObject) return E_UNEXPECTED; + rc = Pyc_DllGetClassObject(rclsid, riid, ppv); + return rc; +} + + +STDAPI DllRegisterServer() +{ + int rc=0; + PyGILState_STATE state; + check_init(); + state = PyGILState_Ensure(); + rc = PyRun_SimpleString("DllRegisterServer()\n"); + if (rc != 0) + PyErr_Print(); + PyGILState_Release(state); + return rc==0 ? 0 : SELFREG_E_CLASS; +} + +STDAPI DllUnregisterServer() +{ + int rc=0; + PyGILState_STATE state; + check_init(); + state = PyGILState_Ensure(); + rc = PyRun_SimpleString("DllUnregisterServer()\n"); + if (rc != 0) + PyErr_Print(); + PyGILState_Release(state); + return rc==0 ? 0 : SELFREG_E_CLASS; +} diff --git a/installer/py2exe/source/run_dll.c b/installer/py2exe/source/run_dll.c new file mode 100644 index 0000000..5ad905e --- /dev/null +++ b/installer/py2exe/source/run_dll.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2000, 2001 Thomas Heller + * Copyright (c) 2003 Mark Hammond + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "Python-dynload.h" +#include +#include +#include +#include +//#include // XXX +#include "MemoryModule.h" + +// Function pointers we load from pythoncom +typedef int (__stdcall *__PROC__DllCanUnloadNow) (void); +typedef HRESULT (__stdcall *__PROC__DllGetClassObject) (REFCLSID, REFIID, LPVOID *); +typedef void (__cdecl *__PROC__PyCom_CoUninitialize) (void); + +extern BOOL _LoadPythonDLL(HMODULE hmod); +extern BOOL _LocateScript(HMODULE hmod); + +CRITICAL_SECTION csInit; // protecting our init code + +__PROC__DllCanUnloadNow Pyc_DllCanUnloadNow = NULL; +__PROC__DllGetClassObject Pyc_DllGetClassObject = NULL; +__PROC__PyCom_CoUninitialize PyCom_CoUninitialize = NULL; + +void SystemError(int error, char *msg) +{ + char Buffer[1024]; + int n; + + if (error) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&lpMsgBuf, + 0, + NULL + ); + strncpy(Buffer, lpMsgBuf, sizeof(Buffer)); + LocalFree(lpMsgBuf); + } else + Buffer[0] = '\0'; + n = lstrlen(Buffer); + _snprintf(Buffer+n, sizeof(Buffer)-n, msg); + MessageBox(GetFocus(), Buffer, NULL, MB_OK | MB_ICONSTOP); +} + +BOOL have_init = FALSE; +HANDLE gPythoncom = 0; +HMODULE gInstance = 0; + +extern int init_with_instance(HMODULE, char *); +extern void fini(); +extern int run_script(void); +extern void init_memimporter(void); + +int load_pythoncom(void) +{ + char dll_path[_MAX_PATH+_MAX_FNAME+1]; + char major[35] = "x"; // itoa max size is 33. + char minor[35] = "y"; +#ifdef _DEBUG + char *suffix = "_d"; +#else + char *suffix = ""; +#endif +/* + // no reference added by PySys_GetObject + PyObject *ob_vi = PySys_GetObject("version_info"); + if (ob_vi && PyTuple_Check(ob_vi) && PyTuple_Size(ob_vi)>1) { + itoa(PyInt_AsLong(PyTuple_GET_ITEM(ob_vi, 0)), major, 10); + itoa(PyInt_AsLong(PyTuple_GET_ITEM(ob_vi, 1)), minor, 10); + } +*/ + // shouldn't do this twice + assert(gPythoncom == NULL); + + snprintf(dll_path, sizeof(dll_path), PYTHONCOM); + gPythoncom = MyGetModuleHandle(dll_path); + // GetModuleFileNameA and LoadLibraryEx would not work in single-file dlls +/* + if (gPythoncom == NULL) { + // not already loaded - try and load from the current dir + char *temp; + GetModuleFileNameA(gInstance, dll_path, sizeof(dll_path)); + temp = dll_path + strlen(dll_path); + while (temp>dll_path && *temp != '\\') + temp--; + // and printf directly in the buffer. + // don't overwrite the backslash, so temp+1! + snprintf(temp+1, sizeof(dll_path)-strlen(temp+1), + "pythoncom%s%s%s.dll", major, minor, suffix); + gPythoncom = LoadLibraryEx(dll_path, // points to name of executable module + NULL, // HANDLE hFile, // reserved, must be NULL + LOAD_WITH_ALTERED_SEARCH_PATH // DWORD dwFlags // entry-point execution flag + ); + } +*/ + if (gPythoncom == NULL) { + OutputDebugString("GetModuleHandle pythoncom failed"); + // give up in disgust + return -1; + } + + Pyc_DllCanUnloadNow = (__PROC__DllCanUnloadNow)MyGetProcAddress(gPythoncom, "DllCanUnloadNow"); + Pyc_DllGetClassObject = (__PROC__DllGetClassObject)MyGetProcAddress(gPythoncom, "DllGetClassObject"); + PyCom_CoUninitialize = (__PROC__PyCom_CoUninitialize)MyGetProcAddress(gPythoncom, "PyCom_CoUninitialize"); + return 0; +} + +/* for debugging +#define ODS(s) OutputDebugString(s) + +static int dprintf(char *fmt, ...) +{ + char Buffer[4096]; + va_list marker; + int result; + + va_start(marker, fmt); + result = wvsprintf(Buffer, fmt, marker); + OutputDebugString(Buffer); + return result; +} +*/ + +int check_init() +{ + if (!have_init) { + EnterCriticalSection(&csInit); + // Check the flag again - another thread may have beat us to it! + if (!have_init) { + PyObject *frozen; + /* Consider a py2exe generated COM DLL, and a py2exe + generated .EXE that makes use of the COM object (both + in the same distribution) + + When VB, for example, creates the COM object, this + code has not previously loaded Python. Python is + therefore not initialized, so a normal Python init + routine works fine. + + However - consider when our .exe attempts to create + the COM object. Python has already been initialized + in this process (as the .exe started up), but the + function pointers (eg, Py_IsInitialized) are yet to + be loaded in this DLLs private data. Worse, in this + case the GIL is not held, so calling + init_with_instance in this state will do things like + call PySys_SetObject, which will abort() as there is + no thread-state. + + In all cases, we must exit this function with the GIL + released. + */ + PyGILState_STATE restore_state = PyGILState_UNLOCKED; + /* Before we call Python functions, we must make sure + that the python dll is loaded - we are loading it + at runtime now. MyGetModuleHandle() should work, + but we can aso check if the functions are + available. + */ + if (!Py_IsInitialized) { + // Python function pointers are yet to be loaded + // - force that now. See above for why we *must* + // know about the initialized state of Python so + // we can setup the thread-state correctly + // before calling init_with_instance(); This is + // wasteful, but fixing it would involve a + // restructure of init_with_instance() + _LocateScript(gInstance); + _LoadPythonDLL(gInstance); + } + if (Py_IsInitialized && Py_IsInitialized()) { + restore_state = PyGILState_Ensure(); + } + // a little DLL magic. Set sys.frozen='dll' + init_with_instance(gInstance, "dll"); + init_memimporter(); + frozen = PyInt_FromLong((LONG)gInstance); + if (frozen) { + PySys_SetObject("frozendllhandle", frozen); + Py_DECREF(frozen); + } + // Now run the generic script - this always returns in a DLL. + run_script(); + have_init = TRUE; + if (gPythoncom == NULL) + load_pythoncom(); + // Reset the thread-state, so any thread can call in + PyGILState_Release(restore_state); + } + LeaveCriticalSection(&csInit); + } + return gPythoncom != NULL; +} + + +// ***************************************************************** +// All the public entry points needed for COM, Windows, and anyone +// else who wants their piece of the action. +// ***************************************************************** +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + if ( dwReason == DLL_PROCESS_ATTACH) { + gInstance = hInstance; + InitializeCriticalSection(&csInit); + } + else if ( dwReason == DLL_PROCESS_DETACH ) { + gInstance = 0; + DeleteCriticalSection(&csInit); + // not much else safe to do here + } + return TRUE; +} + +HRESULT __stdcall DllCanUnloadNow(void) +{ + HRESULT rc; + check_init(); + assert(Pyc_DllCanUnloadNow); + if (!Pyc_DllCanUnloadNow) return E_UNEXPECTED; + rc = Pyc_DllCanUnloadNow(); + //if (rc == S_OK && PyCom_CoUninitialize) + // PyCom_CoUninitialize(); + return rc; +} + +//__declspec(dllexport) int __stdcall DllGetClassObject(void *rclsid, void *riid, void *ppv) +HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + HRESULT rc; + check_init(); + assert(Pyc_DllGetClassObject); + if (!Pyc_DllGetClassObject) return E_UNEXPECTED; + rc = Pyc_DllGetClassObject(rclsid, riid, ppv); + return rc; +} + + +STDAPI DllRegisterServer() +{ + int rc=0; + PyGILState_STATE state; + check_init(); + state = PyGILState_Ensure(); + rc = PyRun_SimpleString("DllRegisterServer()\n"); + if (rc != 0) + PyErr_Print(); + PyGILState_Release(state); + return rc==0 ? 0 : SELFREG_E_CLASS; +} + +STDAPI DllUnregisterServer() +{ + int rc=0; + PyGILState_STATE state; + check_init(); + state = PyGILState_Ensure(); + rc = PyRun_SimpleString("DllUnregisterServer()\n"); + if (rc != 0) + PyErr_Print(); + PyGILState_Release(state); + return rc==0 ? 0 : SELFREG_E_CLASS; +} + +STDAPI DllInstall(BOOL install, LPCWSTR cmdline) +{ + PyObject *m = NULL, *func = NULL, *args = NULL, *result = NULL; + PyGILState_STATE state; + int rc=SELFREG_E_CLASS; + check_init(); + state = PyGILState_Ensure(); + m = PyImport_AddModule("__main__"); + if (!m) goto done; + func = PyObject_GetAttrString(m, "DllInstall"); + if (!func) goto done; + args = Py_BuildValue("(Oi)", install ? 1 : 0, cmdline); + if (!args) goto done; + result = PyObject_Call(func, args, NULL); + if (result==NULL) goto done; + rc = 0; + /* but let them return a custom rc (even though we don't above!) */ + if (PyInt_Check(result)) + rc = PyInt_AsLong(result); + else if (result == Py_None) + ; /* do nothing */ + else + /* We cannot access internals of PyObject anymore */ + PySys_WriteStderr("Unexpected return type for DllInstall\n"); +// result->ob_type->tp_name); +done: + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + // no ref added to m + Py_XDECREF(func); + Py_XDECREF(args); + Py_XDECREF(result); + PyGILState_Release(state); + return rc; +} diff --git a/installer/py2exe/source/run_isapi.c b/installer/py2exe/source/run_isapi.c new file mode 100644 index 0000000..d5f6794 --- /dev/null +++ b/installer/py2exe/source/run_isapi.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2000, 2001 Thomas Heller + * Copyright (c) 2003 Mark Hammond + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include "httpext.h" +#include +//#include "Python.h" +#include +#include "Python-dynload.h" + + +typedef BOOL (__stdcall *__PROC__GetExtensionVersion)(HSE_VERSION_INFO *pVer); +typedef BOOL (__stdcall *__PROC__HttpExtensionProc)(EXTENSION_CONTROL_BLOCK *pECB); +typedef BOOL (__stdcall *__PROC__TerminateExtension)(DWORD dwFlags); +typedef BOOL (__stdcall *__PROC__GetFilterVersion)(HTTP_FILTER_VERSION *pVer); +typedef DWORD (__stdcall *__PROC__HttpFilterProc)(HTTP_FILTER_CONTEXT *phfc, DWORD NotificationType, VOID *pvData); +typedef BOOL (__stdcall *__PROC__TerminateFilter)(DWORD status); +typedef void (__stdcall *__PROC__PyISAPISetOptions)(const char *modname, BOOL is_frozen); +typedef BOOL (__stdcall *__PROC__WriteEventLogMessage)(WORD eventType, DWORD eventID, WORD num_inserts, const char **inserts); + + +CRITICAL_SECTION csInit; // protecting our init code +HMODULE gInstance = 0; +HMODULE hmodPyISAPI = 0; +BOOL have_init = FALSE; + +__PROC__GetExtensionVersion pGetExtensionVersion = NULL; +__PROC__HttpExtensionProc pHttpExtensionProc = NULL; +__PROC__TerminateExtension pTerminateExtension = NULL; +__PROC__GetFilterVersion pGetFilterVersion = NULL; +__PROC__HttpFilterProc pHttpFilterProc = NULL; +__PROC__TerminateFilter pTerminateFilter = NULL; +__PROC__PyISAPISetOptions pPyISAPISetOptions = NULL; +__PROC__WriteEventLogMessage pWriteEventLogMessage = NULL; + +extern int init_with_instance(HMODULE, char *); +extern BOOL _LoadPythonDLL(HMODULE); + +extern void fini(); +extern int run_script(void); +extern void init_memimporter(void); + +void SystemError(int error, char *msg) +{ + // From pywin32's pyisapi_messages.h: + // #define E_PYISAPI_STARTUP_FAILED ((DWORD)0xC0001100L) + // 0x1100 == E_PYISAPI_STARTUP_FAILED + // We use this even when we can't use pyisapi to log the record. + const DWORD evt_id = (DWORD)0xC0001100L; + + char Buffer[1024]; + int n; + HANDLE hEventSource; + + if (error) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&lpMsgBuf, + 0, + NULL + ); + strncpy(Buffer, lpMsgBuf, sizeof(Buffer)); + LocalFree(lpMsgBuf); + } else + Buffer[0] = '\0'; + + // Can't display messages in a service. Write to the event log. + // Later pywin32 versions export a function for writing to the + // event log - useful as it also has the message resources necessary + // to get reasonable looking messages. + // If that exists, use it - otherwise write an "ugly" message. + + if (pWriteEventLogMessage) { + // Can use the one exported! + TCHAR * inserts[] = {msg, Buffer}; + pWriteEventLogMessage(EVENTLOG_ERROR_TYPE, evt_id, + 2, inserts); + return; // all done! + } + + n = lstrlen(Buffer); + _snprintf(Buffer+n, sizeof(Buffer)-n, msg); + + // We have no message resources, so the message will be somewhat + // ugly - but so long as the info is there, that's ok. + hEventSource = RegisterEventSource(NULL, "ISAPI Filter or Extension"); + if (hEventSource) { + // a big sucky - windows error already in string - so we + // send "see above" + TCHAR * inserts[] = {Buffer, "see above"}; + ReportEvent(hEventSource, // handle of event source + EVENTLOG_ERROR_TYPE, // event type + 0, // event category + evt_id, // event ID + NULL, // current user's SID + 2, // strings in lpszStrings + 0, // no bytes of raw data + inserts, // array of error strings + NULL); // no raw data + DeregisterEventSource(hEventSource); + } +} + +BOOL check_init() +{ + BOOL ok = TRUE; // if already initialized all is good. + if (!have_init) { + EnterCriticalSection(&csInit); + // Check the flag again - another thread may have beat us to it! + if (!have_init) { + + PyGILState_STATE restore_state = PyGILState_UNLOCKED; + PyObject *frozen; + char dll_path[1024]; + char *slash; + + ok = FALSE; // must be reset after good init. + // We must ensure Python is loaded, and therefore the + // function pointers are non-NULL, before we can check + // if Python is initialized! Further, as our + // ISAPI dll depends on python, we must load Python + // before loading our module. + if (!_LoadPythonDLL(gInstance)) + goto done; + + // Find and load the pyisapi DLL. + GetModuleFileName(gInstance, dll_path, sizeof(dll_path)/sizeof(dll_path[0])); + slash = strrchr(dll_path, '\\'); + if (slash) { + // insert an underscore. + char *pos_move = dll_path + strlen(dll_path); + while (pos_move > slash) { + *(pos_move+1) = *pos_move; + pos_move --; + } + *(slash+1) = '_'; + hmodPyISAPI = LoadLibrary(dll_path); + if (hmodPyISAPI) { + pGetExtensionVersion = (__PROC__GetExtensionVersion)GetProcAddress(hmodPyISAPI, "GetExtensionVersion"); + pHttpExtensionProc = (__PROC__HttpExtensionProc)GetProcAddress(hmodPyISAPI, "HttpExtensionProc"); + pTerminateExtension = (__PROC__TerminateExtension)GetProcAddress(hmodPyISAPI, "TerminateExtension"); + pGetFilterVersion = (__PROC__GetFilterVersion)GetProcAddress(hmodPyISAPI, "GetFilterVersion"); + pHttpFilterProc = (__PROC__HttpFilterProc)GetProcAddress(hmodPyISAPI, "HttpFilterProc"); + pTerminateFilter = (__PROC__TerminateFilter)GetProcAddress(hmodPyISAPI, "TerminateFilter"); + pPyISAPISetOptions = (__PROC__PyISAPISetOptions)GetProcAddress(hmodPyISAPI, "PyISAPISetOptions"); + pWriteEventLogMessage = (__PROC__WriteEventLogMessage)GetProcAddress(hmodPyISAPI, "WriteEventLogMessage"); + } else { + SystemError(GetLastError(), "Failed to load the extension DLL"); + } + } else { + SystemError(GetLastError(), "Failed to locate my own DLL"); + } + + if (Py_IsInitialized && Py_IsInitialized()) + restore_state = PyGILState_Ensure(); + // a little DLL magic. Set sys.frozen='dll' + if (init_with_instance(gInstance, "dll") != 0) { + PyGILState_Release(restore_state); + goto done; + } + init_memimporter(); + frozen = PyInt_FromLong((LONG)gInstance); + if (frozen) { + PySys_SetObject("frozendllhandle", frozen); + Py_DECREF(frozen); + } + // Now run the generic script - this always returns in a DLL. + run_script(); + // Let the ISAPI extension know about the frozen environment. + // (the ISAPI boot code currently has no way of talking directly + // to the pyISAPI dll, so we fetch it directly from the py2exe + // 'variable' which is already in module '__main__'. + if (pPyISAPISetOptions) { + PyObject *main = PyImport_ImportModule("__main__"); + if (main) { + PyObject *name = PyObject_GetAttrString(main, "isapi_module_name"); + char *str; + if (name && (str = PyString_AsString(name))) + (*pPyISAPISetOptions)(str, TRUE); + else + PyErr_Clear(); // In case PyString_AsString fails + Py_XDECREF(name); + } + Py_DECREF(main); + } + have_init = TRUE; + PyGILState_Release(restore_state); + ok = TRUE; + } +done: + LeaveCriticalSection(&csInit); + } + return ok; +} + +// ***************************************************************** +// All the public entry points needed for COM, Windows, and anyone +// else who wants their piece of the action. +// ***************************************************************** + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + if ( dwReason == DLL_PROCESS_ATTACH) { + gInstance = hInstance; + InitializeCriticalSection(&csInit); + } + else if ( dwReason == DLL_PROCESS_DETACH ) { + gInstance = 0; + if (hmodPyISAPI) + FreeLibrary(hmodPyISAPI); + DeleteCriticalSection(&csInit); + } + return TRUE; +} + +BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer) +{ + if (!check_init() || !pGetExtensionVersion) return FALSE; + return (*pGetExtensionVersion)(pVer); +} +DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) +{ + if (!check_init() || !pHttpExtensionProc) return HSE_STATUS_ERROR; + return (*pHttpExtensionProc)(pECB); +} +BOOL WINAPI TerminateExtension(DWORD dwFlags) +{ + if (!check_init() || !pTerminateExtension) return FALSE; + return (*pTerminateExtension)(dwFlags); +} + +BOOL WINAPI GetFilterVersion(HTTP_FILTER_VERSION *pVer) +{ + if (!check_init() || !pGetFilterVersion) return FALSE; + return (*pGetFilterVersion)(pVer); +} + +DWORD WINAPI HttpFilterProc(HTTP_FILTER_CONTEXT *phfc, DWORD NotificationType, VOID *pvData) +{ + if (!check_init() || !pHttpFilterProc) return SF_STATUS_REQ_NEXT_NOTIFICATION; + return (*pHttpFilterProc)(phfc, NotificationType, pvData); +} + +BOOL WINAPI TerminateFilter(DWORD status) +{ + if (!check_init() || !pTerminateFilter) return FALSE; + return (*pTerminateFilter)(status); +} diff --git a/installer/py2exe/source/run_w.c b/installer/py2exe/source/run_w.c new file mode 100644 index 0000000..7e8b973 --- /dev/null +++ b/installer/py2exe/source/run_w.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2000, 2001 Thomas Heller + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include "Python-dynload.h" + +void SystemError(int error, char *msg) +{ + char Buffer[1024]; + int n; + + if (error) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&lpMsgBuf, + 0, + NULL + ); + strncpy(Buffer, lpMsgBuf, sizeof(Buffer)); + LocalFree(lpMsgBuf); + } else + Buffer[0] = '\0'; + n = lstrlen(Buffer); + _snprintf(Buffer+n, sizeof(Buffer)-n, msg); + MessageBox(GetFocus(), Buffer, NULL, MB_OK | MB_ICONSTOP); +} + +extern int init(char *); +extern int start(int argc, char **argv); +extern void init_memimporter(void); + +static PyObject *Py_MessageBox(PyObject *self, PyObject *args) +{ + HWND hwnd; + char *message; + char *title = NULL; + int flags = MB_OK; + + if (!PyArg_ParseTuple(args, "is|zi", &hwnd, &message, &title, &flags)) + return NULL; + return PyInt_FromLong(MessageBox(hwnd, message, title, flags)); +} + +PyMethodDef method[] = { + { "_MessageBox", Py_MessageBox, METH_VARARGS } +}; + +int WINAPI +WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, + LPSTR lpCmdLine, int nCmdShow) +{ + int result; + PyObject *mod; + + result = init("windows_exe"); + if (result) + return result; + + init_memimporter(); + mod = PyImport_ImportModule("sys"); + if (mod) + PyObject_SetAttrString(mod, + "_MessageBox", + PyCFunction_New(&method[0], NULL)); + return start(__argc, __argv); +} diff --git a/installer/py2exe/source/start.c b/installer/py2exe/source/start.c new file mode 100644 index 0000000..beae0ad --- /dev/null +++ b/installer/py2exe/source/start.c @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2000, 2001 Thomas Heller + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * $Id: start.c 651 2008-04-05 11:26:42Z jretz $ + * + */ + +/* +#include +#include +#include +#include +*/ +#include "Python-dynload.h" +#include +#include +#include "MemoryModule.h" + +#if defined(MS_WINDOWS) || defined(__CYGWIN__) +#include +#endif + +struct scriptinfo { + int tag; + int optimize; + int unbuffered; + int data_bytes; + + char zippath[0]; +}; + +extern void SystemError(int error, char *msg); +int run_script(void); +void fini(void); +char *pScript; +char *pZipBaseName; +int numScriptBytes; +char modulename[_MAX_PATH + _MAX_FNAME + _MAX_EXT]; // from GetModuleName() +char dirname[_MAX_PATH]; // directory part of GetModuleName() +char libdirname[_MAX_PATH]; // library directory - probably same as above. +char libfilename[_MAX_PATH + _MAX_FNAME + _MAX_EXT]; // library filename +struct scriptinfo *p_script_info; + + +/* +static int dprintf(char *fmt, ...) +{ + char Buffer[4096]; + va_list marker; + int result; + + va_start(marker, fmt); + result = vsprintf(Buffer, fmt, marker); + OutputDebugString(Buffer); + return result; +} +*/ + +BOOL calc_dirname(HMODULE hmod) +{ + int is_special; + char *modulename_start; + char *cp; + // get module filename + if (!GetModuleFileName(hmod, modulename, sizeof(modulename))) { + SystemError(GetLastError(), "Retrieving module name"); + return FALSE; + } + // get directory of modulename. Note that in some cases + // (eg, ISAPI), GetModuleFileName may return a leading "\\?\" + // (which is a special format you can pass to the Unicode API + // to avoid MAX_PATH limitations). Python currently can't understand + // such names, and as it uses the ANSI API, neither does Windows! + // So fix that up here. + is_special = strlen(modulename) > 4 && + strncmp(modulename, "\\\\?\\", 4)==0; + modulename_start = is_special ? modulename + 4 : modulename; + strcpy(dirname, modulename_start); + cp = strrchr(dirname, '\\'); + *cp = '\0'; + return TRUE; +} + +BOOL _LocateScript(HMODULE hmod) +{ + HRSRC hrsrc = FindResource(hmod, MAKEINTRESOURCE(1), "PYTHONSCRIPT"); + HGLOBAL hgbl; + + if (!dirname[0]) + calc_dirname(hmod); + + // load the script resource + if (!hrsrc) { + SystemError(GetLastError(), "Could not locate script resource:"); + return FALSE; + } + hgbl = LoadResource(hmod, hrsrc); + if (!hgbl) { + SystemError(GetLastError(), "Could not load script resource:"); + return FALSE; + } + p_script_info = (struct scriptinfo *)pScript = LockResource(hgbl); + if (!pScript) { + SystemError(GetLastError(), "Could not lock script resource:"); + return FALSE; + } + // validate script resource + numScriptBytes = p_script_info->data_bytes; + pScript += sizeof(struct scriptinfo); + if (p_script_info->tag != 0x78563412) { + SystemError (0, "Bug: Invalid script resource"); + return FALSE; + } + // let pScript point to the start of the python script resource + pScript += strlen(p_script_info->zippath) + 1; + + // get full pathname of the 'library.zip' file + if(p_script_info->zippath[0]) { + snprintf(libfilename, sizeof(libfilename), + "%s\\%s", dirname, p_script_info->zippath); + } else { + GetModuleFileName(hmod, libfilename, sizeof(libfilename)); + } + return TRUE; // success +} + +static char *MapExistingFile(char *pathname, DWORD *psize) +{ + HANDLE hFile, hFileMapping; + DWORD nSizeLow, nSizeHigh; + char *data; + + hFile = CreateFile(pathname, + GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return NULL; + nSizeLow = GetFileSize(hFile, &nSizeHigh); + hFileMapping = CreateFileMapping(hFile, + NULL, PAGE_READONLY, 0, 0, NULL); + CloseHandle(hFile); + + if (hFileMapping == INVALID_HANDLE_VALUE) + return NULL; + + data = MapViewOfFile(hFileMapping, + FILE_MAP_READ, 0, 0, 0); + + CloseHandle(hFileMapping); + if (psize) + *psize = nSizeLow; + return data; +} + +BOOL _LoadPythonDLL(HMODULE hmod) +{ + HRSRC hrsrc; + char *pBaseAddress; + int size; + + if (!dirname[0]) + calc_dirname(hmod); + + // Try to locate pythonxy.dll as resource in the exe + hrsrc = FindResource(hmod, MAKEINTRESOURCE(1), PYTHONDLL); + if (hrsrc) { + HGLOBAL hgbl = LoadResource(hmod, hrsrc); + if (!_load_python(PYTHONDLL, LockResource(hgbl))) { + SystemError(GetLastError(), "Could not load python dll"); + return FALSE; + } +// dprintf("Loaded pythondll as RESOURCE\n"); + return TRUE; + } + + // try to load pythonxy.dll as bytes at the start of the zipfile + pBaseAddress = MapExistingFile(libfilename, &size); + if (pBaseAddress) { + int res = 0; + if (0 == strncmp(pBaseAddress, "", 11)) + res = _load_python(PYTHONDLL, pBaseAddress + 11 + sizeof(int)); + UnmapViewOfFile(pBaseAddress); + if (res) { +// dprintf("Loaded pythondll as from %s\n", libfilename); + return TRUE; + } + } + + // try to load pythonxy.dll from the file system + { + char buffer[_MAX_PATH + _MAX_FNAME + _MAX_EXT]; + snprintf(buffer, sizeof(buffer), "%s\\%s", dirname, PYTHONDLL); + + if (!_load_python(buffer, NULL)) { + SystemError(GetLastError(), "LoadLibrary(pythondll) failed"); + SystemError(0, buffer); + return FALSE; + } +// dprintf("Loaded pythondll from file %s\n", buffer); + } + return TRUE; +} + +void _Import_Zlib(char *pdata) +{ + HMODULE hlib; + hlib = MemoryLoadLibrary("zlib.pyd", pdata); + if (hlib) { + void (*proc)(void); + proc = (void(*)(void))MemoryGetProcAddress(hlib, "initzlib"); + if (proc) + proc(); + } +} + +void _TryLoadZlib(HMODULE hmod) +{ + char *pBaseAddress; + char *pdata; + HRSRC hrsrc; + + // Try to locate pythonxy.dll as resource in the exe + hrsrc = FindResource(hmod, MAKEINTRESOURCE(1), "ZLIB.PYD"); + if (hrsrc) { + HGLOBAL hglb = LoadResource(hmod, hrsrc); + if (hglb) { + _Import_Zlib(LockResource(hglb)); + } + return; + } + + // try to load zlib.pyd from the file system + { + HMODULE hlib; + char buffer[_MAX_PATH + _MAX_FNAME + _MAX_EXT]; + snprintf(buffer, sizeof(buffer), "%s\\%s", dirname, "zlib.pyd"); + + hlib = LoadLibrary(buffer); + if(hlib) { + void (*proc)(void); + proc = (void(*)(void))GetProcAddress(hlib, "initzlib"); + if(proc) { + proc(); + return; + } + } + } + + // try to load zlib.pyd as bytes at the start of the zipfile + pdata = pBaseAddress = MapExistingFile(libfilename, NULL); + if (pBaseAddress) { + if (0 == strncmp(pBaseAddress, "", 11)) { + pdata += 11; + pdata += *(int *)pdata + sizeof(int); + } + if (0 == strncmp(pdata, "", 10)) { + pdata += 10 + sizeof(int); + _Import_Zlib(pdata); + } + UnmapViewOfFile(pBaseAddress); + } +} + +static void calc_path() +{ + /* If the zip path has any path component, then build our Python + home directory from that. + */ + char *fname; + size_t lib_dir_len; + pZipBaseName = libfilename + strlen(libfilename); + /* let pZipBaseName point to the basename of the zippath */ + while (pZipBaseName > libfilename && \ + *(pZipBaseName-1) != '\\') + pZipBaseName--; + + /* length of lib director name */ + lib_dir_len = pZipBaseName-libfilename; /* incl. tail slash */ + if (lib_dir_len) { + char *p = libdirname; + strncpy(p, libfilename, lib_dir_len-1); + p += lib_dir_len-1; + *p++ = '\0'; + } else { + libdirname[0] = '\0'; + } + /* Fully-qualify it */ + GetFullPathName(libdirname, sizeof(libdirname), libdirname, &fname); +} + +// Set the Python path before initialization +static int set_path_early() +{ + char *ppath; + Py_SetPythonHome(libdirname); + /* Let Python calculate its initial path, according to the + builtin rules */ + ppath = Py_GetPath(); +// printf("Initial path: %s\n", ppath); + + /* We know that Py_GetPath points to writeable memory, + so we copy our own path into it. + */ + if (strlen(ppath) <= strlen(libdirname) + strlen(pZipBaseName) + 1) { + /* Um. Not enough space. What now? */ + SystemError(0, "Not enough space for new sys.path"); + return -1; + } + + strcpy(ppath, libdirname); + strcat(ppath, "\\"); + strcat(ppath, pZipBaseName); + return 0; +} + +// Set the Python path after initialization +static int set_path_late() +{ + size_t buflen = strlen(libdirname) + strlen(pZipBaseName) + 2; + char *ppath = (char *)malloc(buflen); + PyObject *syspath, *newEntry; + if (!ppath) { + SystemError(ERROR_NOT_ENOUGH_MEMORY, "no mem for late sys.path"); + return -1; + } + strcpy(ppath, libdirname); + strcat(ppath, "\\"); + strcat(ppath, pZipBaseName); + syspath = PySys_GetObject("path"); + newEntry = PyString_FromString(ppath); + if (newEntry) { + PyList_Append(syspath, newEntry); + Py_DECREF(newEntry); + } + free(ppath); + return 0; +} + + +/* + * returns an error code if initialization fails + */ +int init_with_instance(HMODULE hmod, char *frozen) +{ + int rc; + if (!_LocateScript(hmod)) + return 255; + + if (!_LoadPythonDLL(hmod)) + return 255; + if (p_script_info->unbuffered) { +#if defined(MS_WINDOWS) || defined(__CYGWIN__) + _setmode(fileno(stdin), O_BINARY); + _setmode(fileno(stdout), O_BINARY); +#endif +#ifdef HAVE_SETVBUF + setvbuf(stdin, (char *)NULL, _IONBF, BUFSIZ); + setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ); + setvbuf(stderr, (char *)NULL, _IONBF, BUFSIZ); +#else /* !HAVE_SETVBUF */ + setbuf(stdin, (char *)NULL); + setbuf(stdout, (char *)NULL); + setbuf(stderr, (char *)NULL); +#endif /* !HAVE_SETVBUF */ + } + + if (getenv("PY2EXE_VERBOSE")) + Py_VerboseFlag = atoi(getenv("PY2EXE_VERBOSE")); + else + Py_VerboseFlag = 0; + + Py_IgnoreEnvironmentFlag = 1; + Py_NoSiteFlag = 1; + Py_OptimizeFlag = p_script_info->optimize; + + Py_SetProgramName(modulename); + + calc_path(); + + if (!Py_IsInitialized()) { + // First time round and the usual case - set sys.path + // statically. + rc = set_path_early(); + if (rc != 0) + return rc; + + // printf("Path before Py_Initialize(): %s\n", Py_GetPath()); + + Py_Initialize(); + // printf("Path after Py_Initialize(): %s\n", PyString_AsString(PyObject_Str(PySys_GetObject("path")))); + } else { + // Python already initialized. This likely means there are + // 2 py2exe based apps in the same process (eg, 2 COM objects + // in a single host, 2 ISAPI filters in the same site, ...) + // Until we get a better answer, add what we need to sys.path + rc = set_path_late(); + if (rc != 0) + return rc; + } + /* Set sys.frozen so apps that care can tell. + If the caller did pass NULL, sys.frozen will be set zo True. + If a string is passed this is used as the frozen attribute. + run.c passes "console_exe", run_w.c passes "windows_exe", + run_dll.c passes "dll" + This falls apart when you consider that in some cases, a single + process may end up with two py2exe generated apps - but still, we + reset frozen to the correct 'current' value for the newly + initializing app. + */ + if (frozen == NULL) + PySys_SetObject("frozen", PyBool_FromLong(1)); + else { + PyObject *o = PyString_FromString(frozen); + if (o) { + PySys_SetObject("frozen", o); + Py_DECREF(o); + } + } + + _TryLoadZlib(hmod); + + return 0; +} + +int init(char *frozen) +{ + return init_with_instance(NULL, frozen); +} + +void fini(void) +{ + /* The standard Python 2.3 does also allow this: Set PYTHONINSPECT + in the script and examine it afterwards + */ + if (getenv("PYTHONINSPECT") && Py_FdIsInteractive(stdin, "")) + PyRun_InteractiveLoop(stdin, ""); + /* Clean up */ + Py_Finalize(); +} + +int start (int argc, char **argv) +{ + int rc; + PyObject *new_path; + PySys_SetArgv(argc, argv); + // PySys_SetArgv munged the path - specifically, it added the + // directory of argv[0] at the start of sys.path. + // Create a new list object for the path, and rely on our + // implementation knowledge of set_path above, which writes into + // the static Py_GetPath() buffer (Note: Py_GetPath() does *not* + // return the current sys.path value - its just a static buffer + // holding the initial Python paths) + new_path = PyList_New(1); + if (new_path) { + PyObject *entry = PyString_FromString(Py_GetPath()); + if (entry && (0==PyList_SetItem(new_path, 0, entry))) + PySys_SetObject("path", new_path); + Py_DECREF(new_path); + } + rc = run_script(); + fini(); + return rc; +} + +int run_script(void) +{ + int rc = 0; + + /* load the code objects to execute */ + PyObject *m=NULL, *d=NULL, *seq=NULL; + /* We execute then in the context of '__main__' */ + m = PyImport_AddModule("__main__"); + if (m) d = PyModule_GetDict(m); + if (d) seq = PyMarshal_ReadObjectFromString(pScript, numScriptBytes); + if (seq) { + Py_ssize_t i, max = PySequence_Length(seq); + for (i=0;i>> import zipextimporter +>>> zipextimporter.install() +>>> import sys +>>> sys.path.insert(0, "lib.zip") +>>> import _socket +>>> print _socket + +>>> _socket.__file__ +'lib.zip\\_socket.pyd' +>>> _socket.__loader__ + +>>> # Reloading also works correctly: +>>> _socket is reload(_socket) +True +>>> + +""" +import imp, sys +import zipimport +import _memimporter + +class ZipExtensionImporter(zipimport.zipimporter): + _suffixes = [s[0] for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION] + + def find_module(self, fullname, path=None): + result = zipimport.zipimporter.find_module(self, fullname, path) + if result: + return result + if fullname in ("pywintypes", "pythoncom"): + fullname = fullname + "%d%d" % sys.version_info[:2] + fullname = fullname.replace(".", "\\") + ".dll" + if fullname in self._files: + return self + else: + fullname = fullname.replace(".", "\\") + for s in self._suffixes: + if (fullname + s) in self._files: + return self + return None + + def locate_dll_image(self, name): + # A callback function for_memimporter.import_module. Tries to + # locate additional dlls. Returns the image as Python string, + # or None if not found. + if name in self._files: + return self.get_data(name) + return None + + def load_module(self, fullname): + if sys.modules.has_key(fullname): + mod = sys.modules[fullname] + if _memimporter.get_verbose_flag(): + sys.stderr.write("import %s # previously loaded from zipfile %s\n" % (fullname, self.archive)) + return mod + _memimporter.set_find_proc(self.locate_dll_image) + try: + return zipimport.zipimporter.load_module(self, fullname) + except zipimport.ZipImportError: + pass + initname = "init" + fullname.split(".")[-1] # name of initfunction + filename = fullname.replace(".", "\\") + if filename in ("pywintypes", "pythoncom"): + filename = filename + "%d%d" % sys.version_info[:2] + suffixes = ('.dll',) + else: + suffixes = self._suffixes + for s in suffixes: + path = filename + s + if path in self._files: + if _memimporter.get_verbose_flag(): + sys.stderr.write("# found %s in zipfile %s\n" % (path, self.archive)) + code = self.get_data(path) + mod = _memimporter.import_module(code, initname, fullname, path) + mod.__file__ = "%s\\%s" % (self.archive, path) + mod.__loader__ = self + if _memimporter.get_verbose_flag(): + sys.stderr.write("import %s # loaded from zipfile %s\n" % (fullname, mod.__file__)) + return mod + raise zipimport.ZipImportError, "can't find module %s" % fullname + + def __repr__(self): + return "<%s object %r>" % (self.__class__.__name__, self.archive) + +def install(): + "Install the zipextimporter" + sys.path_hooks.insert(0, ZipExtensionImporter) + sys.path_importer_cache.clear() + +##if __name__ == "__main__": +## import doctest +## doctest.testmod() diff --git a/installer/res/AskInstallChecker-1.2.0.0.exe b/installer/res/AskInstallChecker-1.2.0.0.exe new file mode 100644 index 0000000..5a77d7a Binary files /dev/null and b/installer/res/AskInstallChecker-1.2.0.0.exe differ diff --git a/installer/res/Babylon8_Setup_15000.exe b/installer/res/Babylon8_Setup_15000.exe new file mode 100644 index 0000000..ca3e5cf Binary files /dev/null and b/installer/res/Babylon8_Setup_15000.exe differ diff --git a/installer/res/CrawlerSetup.exe b/installer/res/CrawlerSetup.exe new file mode 100644 index 0000000..39b8728 Binary files /dev/null and b/installer/res/CrawlerSetup.exe differ diff --git a/installer/res/DigsbyDonates.xpi b/installer/res/DigsbyDonates.xpi new file mode 100644 index 0000000..d83418c Binary files /dev/null and b/installer/res/DigsbyDonates.xpi differ diff --git a/installer/res/Digsby_Donates.exe b/installer/res/Digsby_Donates.exe new file mode 100644 index 0000000..ebd5237 Binary files /dev/null and b/installer/res/Digsby_Donates.exe differ diff --git a/installer/res/InboxSetup.exe b/installer/res/InboxSetup.exe new file mode 100644 index 0000000..35dce33 Binary files /dev/null and b/installer/res/InboxSetup.exe differ diff --git a/installer/res/Install_Bccthis.exe b/installer/res/Install_Bccthis.exe new file mode 100644 index 0000000..8327e64 Binary files /dev/null and b/installer/res/Install_Bccthis.exe differ diff --git a/installer/res/XobniDecide_100709.exe b/installer/res/XobniDecide_100709.exe new file mode 100644 index 0000000..37e9f07 Binary files /dev/null and b/installer/res/XobniDecide_100709.exe differ diff --git a/installer/res/XobniMiniSetup.exe b/installer/res/XobniMiniSetup.exe new file mode 100644 index 0000000..5e10238 Binary files /dev/null and b/installer/res/XobniMiniSetup.exe differ diff --git a/installer/res/adwarefree.bmp b/installer/res/adwarefree.bmp new file mode 100644 index 0000000..b5b6a94 Binary files /dev/null and b/installer/res/adwarefree.bmp differ diff --git a/installer/res/adwarefree.png b/installer/res/adwarefree.png new file mode 100644 index 0000000..e2b90f2 Binary files /dev/null and b/installer/res/adwarefree.png differ diff --git a/installer/res/aro_image.bmp b/installer/res/aro_image.bmp new file mode 100644 index 0000000..8f38dcd Binary files /dev/null and b/installer/res/aro_image.bmp differ diff --git a/installer/res/aro_image.png b/installer/res/aro_image.png new file mode 100644 index 0000000..3529bbb Binary files /dev/null and b/installer/res/aro_image.png differ diff --git a/installer/res/aro_installer.exe b/installer/res/aro_installer.exe new file mode 100644 index 0000000..70e065f Binary files /dev/null and b/installer/res/aro_installer.exe differ diff --git a/installer/res/babylon_image.bmp b/installer/res/babylon_image.bmp new file mode 100644 index 0000000..33a0460 Binary files /dev/null and b/installer/res/babylon_image.bmp differ diff --git a/installer/res/bccthis_image.bmp b/installer/res/bccthis_image.bmp new file mode 100644 index 0000000..8c4bfee Binary files /dev/null and b/installer/res/bccthis_image.bmp differ diff --git a/installer/res/bing_image.bmp b/installer/res/bing_image.bmp new file mode 100644 index 0000000..b90704a Binary files /dev/null and b/installer/res/bing_image.bmp differ diff --git a/installer/res/bing_install.exe b/installer/res/bing_install.exe new file mode 100644 index 0000000..ae08515 Binary files /dev/null and b/installer/res/bing_install.exe differ diff --git a/installer/res/crawler_image.bmp b/installer/res/crawler_image.bmp new file mode 100644 index 0000000..7a97984 Binary files /dev/null and b/installer/res/crawler_image.bmp differ diff --git a/installer/res/crawler_offer.png b/installer/res/crawler_offer.png new file mode 100644 index 0000000..4f26255 Binary files /dev/null and b/installer/res/crawler_offer.png differ diff --git a/installer/res/digsby.ico b/installer/res/digsby.ico new file mode 100644 index 0000000..63b2b4f Binary files /dev/null and b/installer/res/digsby.ico differ diff --git a/installer/res/inbox_image.bmp b/installer/res/inbox_image.bmp new file mode 100644 index 0000000..c5441f1 Binary files /dev/null and b/installer/res/inbox_image.bmp differ diff --git a/installer/res/new_right_header.bmp b/installer/res/new_right_header.bmp new file mode 100644 index 0000000..32750d7 Binary files /dev/null and b/installer/res/new_right_header.bmp differ diff --git a/installer/res/portable.yaml b/installer/res/portable.yaml new file mode 100644 index 0000000..699840e --- /dev/null +++ b/installer/res/portable.yaml @@ -0,0 +1,19 @@ +--- +#executablepath: # this is set to the .exe file and shouldn't be changed +#temp: # don't use flash drives for temp stuff + +documents: "./env/documents" + +data: "./env/data" +config: "./env/config" +localdata: "./env/localdata" + +userdata: "./env/userdata" +userconfig: "./env/userconfig" +userlocaldata: "./env/userlocaldata" + +userstartup: "./env/userstartup" +startup: "./env/startup" + +userdesktop: "./env/userdesktop" +desktop: "./env/desktop" \ No newline at end of file diff --git a/installer/res/toolbar_headimg.bmp b/installer/res/toolbar_headimg.bmp new file mode 100644 index 0000000..f5d19d0 Binary files /dev/null and b/installer/res/toolbar_headimg.bmp differ diff --git a/installer/res/toolbar_license.txt b/installer/res/toolbar_license.txt new file mode 100644 index 0000000..ec90b00 --- /dev/null +++ b/installer/res/toolbar_license.txt @@ -0,0 +1,90 @@ +ASK TOOLBAR END USER LICENSE AGREEMENT + +This end User License Agreement applies to the Ask Toolbar and versions of the Ask Toolbar branded with third party brands. Upon the Installation of the Ask Toolbar, the following features will be added to your Internet browser (depending on how you customize the toolbar): + +Ask Toolbar :This browser toolbar is customizable and will help you search the Internet with Ask and may provide other search features. Use of the Ask services is subject to the Ask Terms of Service and Privacy Policy that you can find at http://about.ask.com/en/docs/about/site_policies.shtml. + +Default Search: If you consent to make Ask your default search,your Internet browser default search feature will be set to Ask and you will access Ask web search services (i) by entering queries in your browser chrome search box; (ii) by entering queries in your browser address bar, and (iii) in response to misspelled, incorrectly formatted or unresolved DNS submissions in your browser address bar. + +Ask Home Page: If you consent to making Ask.com your home page, Ask.com will become your default home page that will appear each time you open your Internet browser. + +If you are an AskEraser user, please note that AskEraser does not work for the search queries submitted through the Ask Toolbar, including queries submitted through your browser with the Default Search feature. To see more information about Ask Eraser, please see http://about.ask.com/en/docs/about/askeraser.shtml. + +You can easily uninstall the Ask Toolbar by clicking on the "Start" button; then clicking on the "Settings" button, then clicking on the "Control Panel" button, then clicking on the "Add or Remove Programs" button, then selecting "Ask Toolbar", then clicking on the "Change/Remove" button. Make sure all your browser windows are closed before starting the uninstallation process. + +THIS PRODUCT AND ALL THE FEATURES LISTED ABOVE ARE FREE. NO REGISTRATION OR PERSONAL INFORMATION IS REQUIRED. THIS PRODUCT IS NOT SPYWARE OR ADWARE. + +This End User License Agreement is a legal contract between you and Ask.com. You must agree to this contract and abide by its terms in order to download and use the Ask Toolbar. You must be 18 years of age in order to agree to this contract and download this product. If you are not yet 18, please ask your parent or Legal guardian to install the Ask Toolbar for you. This End User License Agreement applies to the Ask Toolbar and all features and functionalities developed by Ask. In the event where you are installing a version of the Ask Toolbar that has third party features and functionalities, such third party features and functionalities are subject to such third partys terms of service. + +In this End User License Agreement (this "Agreement"), "Toolbar" shall refer to the Ask Toolbar, including any Toolbar owned and operated by Ask and branded with third party brands. "Web Sites" shall refer to the web pages in the www.ask.com domain and "Services" shall refer to the search and other services offered through the Toolbar. + +1. License Grant +Subject to the terms and conditions of this Agreement, Ask.com("we" or "us") grants you a non-exclusive, revocable, limited license, to (a) download and install the most current generally available version of the Toolbar, and (b) use the Toolbar you download and install solely for your personal, non-commercial purposes. The term Toolbar includes all updates thereto which we make available to you. Your use of the Ask search services through the Toolbar is subject to the Ask Terms of Service and Privacy Policy available here: http://about.ask.com/en/docs/about/site_policies.shtml. + +2. License Conditions +You may not rent, sell, lease, sublicense, distribute, assign, copy, or in any way transfer the Toolbar or this Agreement or use the Toolbar for the benefit of any third party in any manner. You may not modify, decompile, disassemble, or otherwise reverse-engineer the source code of the Toolbar, or attempt to do so for any reason. Further, you may not access, create or modify the source code of the Toolbar in any way. You do not have the right to and may not create derivative works of the Toolbar. All modifications or enhancements to the Toolbar remain our sole property. You understand that we, in our sole discretion, may modify or discontinue or suspend your right to access any of our services or to use the Toolbar at any time, and we may at any time suspend or terminate any license hereunder and disable the Toolbar or any of its component features. + +We reserve the right to add features or functions to the Toolbar. When installed on your computer, the Toolbar periodically communicates with our servers to request automatic updates when we release a new version of the Toolbar, or when we make new features available. + +3. Ownership +You acknowledge and agree that the Toolbar is licensed, not sold to you. You acknowledge that the Toolbar, including all code, protocols, software and documentation provided to you by us in conjunction with the Toolbar or our services are our property or the property of our licensors, and are protected by U.S. and international copyright, trademarks, patents and other proprietary rights and laws relating to trade secrets, and any other intellectual property or proprietary rights recognized in any country or jurisdiction worldwide, including, without limitation, moral or similar rights. You may not delete, alter, or remove any copyright, trademark, or other proprietary rights notice we have placed on the Toolbar. All rights not expressly granted hereunder are expressly reserved to us and our licensors. + +You represent and warrant that you are either the owner or an authorized user of the computer where the Toolbar is installed. You may use the Toolbar, Web Sites or Services only for lawful purposes. The Services are subject to, and you agree that you shall at all times comply with, all local, state, national, and international laws, statutes, rules, regulations, ordinances and the like applicable to the use of our Toolbar, Web Sites and Services. You agree not to use the Toolbar, Web Sites or Services to conduct any business or activity or solicit the performance of any activity, which is prohibited by law or by any contractual provision by which you are bound. + +4. Privacy + +When you install the Toolbar, we assign a unique identification code to your copy of the Toolbar, and such code is written to your computers registry. + +When you use the Toolbar to submit a query, the Toolbar forwards such query information to Ask servers. Ask.com uses this information to process your web search requests and to return a search results page to you. For information on additional uses of your query information by Ask.com, please see the Ask Privacy Policy at http://about.ask.com/en/docs/about/privacy.shtml. + +When you access the Web Site through the Toolbar, the Web Site sets a "cookie" that collects information about your use of the Toolbar including the unique identification code of your Toolbar, the queries you submit through the Toolbar, your IP address, and whether you clicked on search results or advertising links. When you start your browser, the Toolbar also sends a configuration request which also includes your computers IP address, browser type, and information about the specific release date and distribution source of your Toolbar. This information allows Ask.com to distinguish your Toolbar for purposes of compensating third parties who distribute its products and to analyze retention and usage on an aggregated basis. Such "cookies" and configuration settings are reset every 24 hours, but the information collected is stored in a query log for up to six months. For more information on the use of cookies and on how to block or remove them, please see the Ask Privacy Policy at http://about.ask.com/en/docs/about/privacy.shtml. + +The Toolbar does not: (a) collect or report back to us (or anyone else) any information about sites you visit on the; (b) collect or "screen-scrape" any search queries or information that you provide to any other web sites; (c) serve pop-ups when you are on other websites; or (d) collect or report back to us (or anyone else) any data unrelated to the Services. + +If the Web Site or the Toolbar is ever involved in a corporate restructuring or a sale, merger or other transfer of assets, we may transfer all information provided by or collected from you in order to ensure continuity of service. + +5. Access and Interference; Passwords + +You agree that you will not use any robot, spider, other automatic or manual device or process to interfere or attempt to interfere with the proper working of any of the Toolbar, Web Sites, Services, except to remove our Toolbar from a computer of which you are an owner or authorized user. You may not violate or attempt to violate the security of our Toolbar. We reserve the right to investigate occurrences which may involve such violations, and may involve, and cooperate with, law enforcement authorities in prosecuting users who have participated in such violations. You agree that it is your responsibility to install anti-virus software and related protections against viruses, Trojan horses, worms, time bombs, cancelbots or other techniques that may have the effect of damaging, destroying, disrupting or otherwise impairing a computer's functionality or operation. + +6. Disclaimer of Warranty + +YOU ACCESS AND USE THE TOOLBAR AT YOUR SOLE RISK. WE PROVIDE THE TOOLBAR ON AN "AS IS," AND "AS AVAILABLE" BASIS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, CUSTOM, TRADE, QUIET ENJOYMENT, ACCURACY OF INFORMATIONAL CONTENT, SYSTEM INTEGRATION OR NON-INFRINGEMENT. WE MAKE NO REPRESENTATIONS OR WARRANTIES CONCERNING THE TOOLBAR, AND YOU WILL BE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR LOSS OF DATA THAT RESULTS FROM THE DOWNLOAD OF THE TOOLBAR. + +WE DO NOT MAKE ANY REPRESENTATION OR WARRANTY: (A) AS TO THE TIMELINESS, SEQUENCE, ACCURACY, COMPLETENESS, OR RELIABILITY OF THE TOOLBAR, (B) THAT THE TOOLBAR WILL BE AVAILABLE OR WILL OPERATE IN AN UNINTERRUPTED OR ERROR-FREE MANNER, OR (C) THAT ERRORS OR DEFECTS RELATED TO THE TOOLBAR WILL BE CORRECTED.. WE ALSO DO NOT WARRANT THAT THE TOOLBAR, OR THE INFORMATION AVAILABLE THROUGH TOOLBAR, IS APPROPRIATE, ACCURATE OR AVAILABLE FOR USE IN ANY PARTICULAR JURISDICTION. + +SOME JURISDICTIONS DO NOT ALLOW THE DISCLAIMER OF IMPLIED WARRANTIES. IN SUCH JURISDICTIONS, THE FOREGOING DISCLAIMERS MAY NOT APPLY TO YOU INSOFAR AS THEY RELATE TO IMPLIED WARRANTIES. + +THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS AGREEMENT. + +7. Termination + +You may terminate this Agreement at any time by uninstalling and destroying all copies of the Toolbar in your possession or control. + +If you do not comply with this Agreement, at any time, we reserve the right to terminate your access to the Web Sites and Services through the Toolbar. We may discontinue or alter any aspect of the Services, including, but not limited to, (i) restricting the time the Services are available, (ii) restricting the amount of use permitted, and (iii) restricting or terminating any user's right to use the Services, at our sole discretion and without prior notice or liability. We may also, in our sole discretion, terminate your use of the Services, and disable your use of the Toolbar, for any reason, including, without limitation, for lack of use or if we believe that you have violated or acted inconsistently with the letter or spirit of these license terms. Further, you agree that we shall not be liable to you or any third-party for any termination of your access to the Services. + +8. Limitation of Liability + +YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT IN NO EVENT WILL ASK.COM BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING BUT NOT LIMITED TO, DAMAGES FOR LOST PROFITS, LOST BUSINESS OR LOST OPPORTUNITY, GOODWILL, OR OTHER INTANGIBLE LOSSES (EVEN IF ASK.COM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES) OR OTHER RELIEF ARISING OUT OF, OR RELATED TO, THIS AGREEMENT OR TO: (i) YOUR USE OR THE INABILITY TO USE THE TOOLBAR. + +BECAUSE SOME STATES OR JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR THE LIMITATION OF LIABILITY FOR DAMAGES, IN SUCH STATE OR JURISDICTIONS, OUR LIABILITY SHALL BE LIMITED TO THE EXTENT PERMITTED BY LAW. + +9. Export Controls + +The Toolbar and the underlying information and technology may not be downloaded or otherwise exported or re-exported (a) into (or to a national or resident of) any country to which the U.S. has currently embargoed goods; or (b) to anyone on the U.S. Treasury Departments list of Specially Designated Nationals or the U.S. Commerce Departments Table of Deny Orders. By downloading or using the Toolbar, you agree to the foregoing and you represent and warrant that you are not located in, under the control of, or a national or resident of any such country or on any such list, and that you will otherwise comply with all applicable export control laws. + +10. Notice to Government End Users + +The Toolbar and associated software, programs and documentation hereunder downloaded or otherwise installed for or on behalf of the United States of America, its agencies and/or instrumentalities ("U.S. Government"), is provided with Restricted Rights as "commercial Items," as that terms is defined at 48 C.F.R. 2.101, consisting of "Commercial Computer Software" and "Commercial Computer Software Documentation," as such terms are used in 48 C.F.R. 12.212 or 48 C.F.R. 227.7202, as applicable. Pursuant to Federal Acquisition Regulation 12.212 (48 C.F.R. 12.212), the U.S. Government shall have only those rights specified in the license contained herein. The U.S. Government shall not be entitled to (i) technical information that is not customarily provided to the public or to (ii) use, modify, reproduce, release, perform, display, or disclose commercial computer software or commercial computer software documentation except as specified herein. Use, duplication, or disclosure by the U.S. Government is subject to restrictions as set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer Software clause at DFARS 252.227-7013 or subparagraphs (c)(1) and (2) of the Commercial Computer Software - Restricted Rights at 48 C.F.R. 52.227-19, as applicable. + +11. Applicable Law + +The substantive laws of the State of California in the United States of America, without regard to conflict of laws principles, shall govern all matters relating to or arising from this Agreement, and the use (or inability to use) the Software. You hereby submit to the exclusive jurisdiction and venue of the appropriate State and Federal courts located in San Francisco County, California, with respect to all matters arising out of or relating to this Agreement. + +12. Customer Concerns. + +If you encounter any problem with the Toolbar, or would like to send us your feedback, our Customer Service may be reached at http://about.ask.com/en/docs/about/customer_service.shtml + +Revised: March 1, 2009 + + 2009 Ask.com, All rights reserved. diff --git a/installer/res/uniblue_image.bmp b/installer/res/uniblue_image.bmp new file mode 100644 index 0000000..62426f8 Binary files /dev/null and b/installer/res/uniblue_image.bmp differ diff --git a/installer/res/uniblue_install.exe b/installer/res/uniblue_install.exe new file mode 100644 index 0000000..fe55c45 Binary files /dev/null and b/installer/res/uniblue_install.exe differ diff --git a/installer/res/wizard-un.bmp b/installer/res/wizard-un.bmp new file mode 100644 index 0000000..147f64f Binary files /dev/null and b/installer/res/wizard-un.bmp differ diff --git a/installer/res/wizard.bmp b/installer/res/wizard.bmp new file mode 100644 index 0000000..395354e Binary files /dev/null and b/installer/res/wizard.bmp differ diff --git a/installer/res/xobni_image.bmp b/installer/res/xobni_image.bmp new file mode 100644 index 0000000..b1dd075 Binary files /dev/null and b/installer/res/xobni_image.bmp differ diff --git a/installer/test_DigsbySearch.nsi b/installer/test_DigsbySearch.nsi new file mode 100644 index 0000000..82065d4 --- /dev/null +++ b/installer/test_DigsbySearch.nsi @@ -0,0 +1,14 @@ +SetCompressor /SOLID /FINAL lzma +OutFile "D:\digsbytest\nsis_test.exe" + +!include "DigsbyRegister.nsh" +!include "DigsbySearch.nsh" + +!insertmacro _FFX_RewriteSearchPlugin "ebay.xml" "ChangeEbaySearchEngineFFX" +!insertmacro _FFX_RewriteSearchPlugin "amazondotcom.xml" "ChangeAmazonSearchEngineFFX" + +Section "Install" + !insertmacro FFX_RewriteSearchPlugin "ebay.xml" + !insertmacro FFX_RewriteSearchPlugin "amazondotcom.xml" +SectionEnd + diff --git a/installer/vc8/vcredist.exe b/installer/vc8/vcredist.exe new file mode 100644 index 0000000..1caa062 Binary files /dev/null and b/installer/vc8/vcredist.exe differ diff --git a/installer/vc9/vcredist.exe b/installer/vc9/vcredist.exe new file mode 100644 index 0000000..b8a3da8 Binary files /dev/null and b/installer/vc9/vcredist.exe differ diff --git a/installer/vc9sp1/vcredist.exe b/installer/vc9sp1/vcredist.exe new file mode 100644 index 0000000..05d0a05 Binary files /dev/null and b/installer/vc9sp1/vcredist.exe differ diff --git a/sanity.py b/sanity.py new file mode 100644 index 0000000..36cce59 --- /dev/null +++ b/sanity.py @@ -0,0 +1,183 @@ +from __future__ import print_function +import sys + + +class SanityException(Exception): + def __init__(self, name, message): + self.component_name = name + super(Exception, self).__init__(message) + + +def insane(name, message): + raise SanityException(name, message) + + +def module_check(name): + try: + module = __import__(name) + for part in name.split('.')[1:]: + module = getattr(module, part) + return module + except (ImportError, AttributeError): + insane(name, 'not found') + + +def sanity(name): + if name == 'all': + _print = lambda s, *a, **k: None + else: + _print = print + + _print("{name:.<20}".format(name = name), end = '') + try: + globals().get('sanity_%s' % name, lambda: insane(name, "sanity check not found"))() + except: + _print("FAIL") + raise + else: + _print("OK") + + +def sanity_path(): + path = module_check('path') + if not hasattr(path.path, 'openfolder'): + insane('path.py', 'not patched for Digsby') + + +def sanity_ZSI(): + ZSI = module_check('ZSI') + Namespaces = module_check('ZSI.wstools.Namespaces') + + if getattr(getattr(Namespaces, 'SOAP12'), 'ENC12', None) != 'http://www.w3.org/2003/05/soap-encoding': + insane('ZSI', 'namespace modifications for Digsby not found') + + test_script = 'import ZSI.generate.pyclass as pyclass;\nif hasattr(pyclass, "pydoc"): raise Exception' + try: + if __debug__: + import subprocess + if subprocess.call([sys.executable, '-O', '-c', test_script]) != 0: + raise Exception + else: + exec(test_script) + except: + insane('ZSI', 'pydoc is imported in non-debug mode') + + +def sanity_M2Crypto(): + M2Crypto = module_check('M2Crypto') + RC4 = module_check('M2Crypto.RC4') + + try: + if 'testdata' != RC4.RC4('key').update(RC4.RC4('key').update('testdata')): + raise Exception + except: + insane('M2Crypto', 'crypto test failed') + + +def sanity_syck(): + syck = module_check('syck') + + try: + if syck.load('---\ntest: works\n').get('test') != 'works': + raise Exception + except: + insane('syck', 'failed to parse sample document') + + +def sanity_libxml2(): + libxml2 = module_check('libxml2') + + doc = None + try: + doc = libxml2.parseDoc('') + if doc.children.name != 'root' or doc.children.children.name != 'child': + raise Exception + except: + insane('libxml2', 'failed to process sample document') + finally: + if doc is not None: + doc.freeDoc() + + +def sanity_PIL(): + from StringIO import StringIO + Image = module_check('PIL.Image') + + image = None + try: + image = Image.new('RGB', (1, 1)) + except: + insane('PIL', 'failed to create test image') + + try: + image.save(StringIO(), 'jpeg') + except: + insane('PIL', 'does not have jpeg support') + + try: + image.save(StringIO(), 'png') + except: + insane('PIL', 'does not have png support') + + try: + image.save(StringIO(), 'ppm') + except: + insane('PIL', 'does not have ppm (freetype) suport') + + +def sanity_lxml(): + html = module_check('lxml.html') + etree = module_check('lxml.etree') + objectify = module_check('lxml.objectify') + + try: + etree.tostring(etree.fromstring('')) + except: + insane('lxml', 'failed to process sample document') + + +def sanity_simplejson(): + json = module_check('simplejson') + speedups = module_check('simplejson._speedups') + + try: + json.dumps({}, use_speedups = False) + except TypeError: + insane('simplejson', 'does not allow disabling speedups') + +def sanity_protocols(): + import inspect + protocols = module_check('protocols') + speedups = module_check('protocols._speedups') + + Adapter = protocols.Adapter + if inspect.getargspec(Adapter.__init__).args != ['self', 'ob']: + insane('protocols', 'constructor for Adapter is incorrect') + +# TODO: More verification for these modules +for simple_module in set(('blist', 'cgui', 'babel', 'socks', 'tenjin', 'certifi', + 'dns', 'rauth', 'ClientForm', 'peak', '_xmlextra', + 'sip', 'wx', 'wx.py', 'wx.calendar', 'wx.webview', + 'wx.lib', 'wx.stc', 'feedparser', 'pkg_resources')): + globals()['sanity_' + simple_module] = lambda _name=simple_module: module_check(_name) + + +def main(*args): + dont_check = set(arg[1:] for arg in args if arg.startswith('-')) + to_check = set(arg for arg in args if arg != 'all' and not arg.startswith('-')) + + if not to_check or 'all' in args: + for func_name, sanity_check in globals().items(): + if func_name.startswith('sanity_') and callable(sanity_check): + name = func_name[len('sanity_'):] + to_check.add(name) + + for name in sorted(to_check - dont_check): + try: + sanity(name) + except SanityException as e: + print("SanityException: %s: %s" % (e.component_name, e), file = sys.stderr) + + +if __name__ == '__main__': + main(*sys.argv[1:]) diff --git a/updater/.project b/updater/.project new file mode 100644 index 0000000..fe715bb --- /dev/null +++ b/updater/.project @@ -0,0 +1,18 @@ + + + AutoUpdate + + + Digsby + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/updater/.pydevproject b/updater/.pydevproject new file mode 100644 index 0000000..9818896 --- /dev/null +++ b/updater/.pydevproject @@ -0,0 +1,9 @@ + + + + + +/AutoUpdate/src + +python 2.5 + diff --git a/updater/AsInvoker.manifest b/updater/AsInvoker.manifest new file mode 100644 index 0000000..530f021 --- /dev/null +++ b/updater/AsInvoker.manifest @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/updater/Digsby PreUpdater/digsby_preupdater.cpp b/updater/Digsby PreUpdater/digsby_preupdater.cpp new file mode 100644 index 0000000..4582b71 --- /dev/null +++ b/updater/Digsby PreUpdater/digsby_preupdater.cpp @@ -0,0 +1,622 @@ +#include +#include +#include +#include +#include + +#include "splash.h" + +#define UPDATE_STR L"Updating Digsby. Please wait..." +#define IMAGE_FILENAME L"res\\digsbybig.bmp" +#define LOCKFILE L"LOCKFILE" + +#define POST_UPDATE_FILE L"Digsby Updater.exe" +#define DIGSBY_CALL_WIN L"\"%ws\" --updated" +#define DIGSBY_CALL_FAIL L"\"%ws\" --update_failed" + +#define WRITE_RIGHTS ((STANDARD_RIGHTS_ALL)|(FILE_GENERIC_WRITE)) + +//=============================================================================== + +#include +#include +#include +#define SLEEP_MS 100 + +int KillPID(DWORD pid){ + + HANDLE parent = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, (DWORD) pid); + // May return NULL if the parent process has already gone away. + // Otherwise, wait for the parent process to exit before starting the + // update. + if (parent) { + + BOOL termResult = TerminateProcess(parent, -1); + + CloseHandle(parent); + } + + Sleep(SLEEP_MS); + + return 0; +} + +void KillDigsbys(){ + + + // Get the list of process identifiers. + DWORD aProcesses[5000], cBytes, cProcesses; + unsigned int i; + + if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cBytes ) ) + return; + + // Calculate how many process identifiers were returned. + cProcesses = cBytes / sizeof(DWORD); + + // Print the name and process identifier for each process. + for ( i = 0; i < cProcesses; i++ ) { + if( aProcesses[i] != 0 ) { + DWORD processID = aProcesses[i]; + + TCHAR szProcessName[MAX_PATH] = TEXT(""); + + // Get a handle to the process. + HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ, + FALSE, processID ); + // Get the process name. + if (hProcess) { + HMODULE hMod; + DWORD cBytesNeeded; + + if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cBytesNeeded) ) + GetModuleBaseName( hProcess, hMod, szProcessName, + sizeof(szProcessName)/sizeof(TCHAR) ); + + } + + // kill the process if it is "digsby.exe" or "Digsby.exe" + if (0 == wcscmp(L"digsby.exe", _wcslwr(szProcessName)) || 0 == wcscmp(L"digsby-app.exe", _wcslwr(szProcessName))) + KillPID(processID); + + + CloseHandle( hProcess ); + } + } +} + +//=============================================================================== + +DWORD ErrPop(DWORD errCode, const wchar_t *msg = 0, ...){ + + va_list args; + va_start( args, msg ); + + //if error code look up error message and log it + LPWSTR errStr = NULL; + if(errCode){ + //look up error message + DWORD success = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_IGNORE_INSERTS + | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errCode, + 0, + (LPWSTR) &errStr, + 0, + NULL); + } + + // Show Dialog + + wchar_t title[256]; + title[0] = 0; + + wsprintf(title, L"Error Code %d", errCode); + + wchar_t errmsg[2000]; + errmsg[0] = 0; + if(msg){ + wvsprintf(errmsg,msg,args); + } + + if(msg && errStr){ + wcscat(errmsg,L"\n\n"); + } + + + if(errStr){ + wcscat(errmsg,errStr); + } + + MessageBox(NULL, + errmsg, + title, + MB_OK|MB_ICONERROR); + + + //cleanup + va_end(args); + if(errStr) + LocalFree(errStr); + + return errCode; + +} + +/** + * Returns true if Vista or newer + */ +bool IsVista(){ + OSVERSIONINFO osver; + + osver.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + + return GetVersionEx(&osver) && osver.dwPlatformId == VER_PLATFORM_WIN32_NT && osver.dwMajorVersion >= 6; + +} + +bool HasUAC(){ + //HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA + + HKEY key; + BYTE *uac; + DWORD sizeofuac = sizeof(uac); + + DWORD errCode; + + errCode = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", + 0, + KEY_READ, + &key); + + if(errCode != ERROR_SUCCESS){ + ErrPop(errCode,L"Error opening registry key for UAC check!"); + } + + RegQueryValueEx(key, + L"EnableLUA", + NULL, + NULL, + NULL, + &sizeofuac); + + uac = (BYTE*)malloc(sizeof(BYTE)*sizeofuac); + + errCode = RegQueryValueEx(key, + L"EnableLUA", + NULL, + NULL, + uac, + &sizeofuac); + + if(errCode != ERROR_SUCCESS){ + ErrPop(errCode,L"Error Querying registry value for UAC check!"); + } + + RegCloseKey(key); + + return *uac? true : false; +} + +int HasWriteAccess(HINSTANCE hInstance, wchar_t *dir = 0){ + +//==Find Directory============================================================= + if(!dir){ + int dirLen = GetCurrentDirectory(NULL,0) + 1; + dir = (wchar_t*)alloca(sizeof(wchar_t) * dirLen); + GetCurrentDirectory(dirLen,dir); + } + +//==Get a Security Description================================================= + + + BOOL success = true; + BYTE *secDesc; + DWORD sizeNeeded = 0; + + success = GetFileSecurityW(dir, + DACL_SECURITY_INFORMATION, + NULL, + 0, + &sizeNeeded); + + /*if(!success) + return ErrPop(GetLastError());*/ + + secDesc = (BYTE*)alloca(sizeof(BYTE) * sizeNeeded); + + success = GetFileSecurityW(dir, + DACL_SECURITY_INFORMATION, + secDesc, + sizeNeeded, + &sizeNeeded); + + + if(!success) + return ErrPop(GetLastError(),L"Error getting permision info for directory: \n%s",dir); + +//==Get DACL from Security Description========================================= + + PACL dacl; + BOOL daclPresent, daclDefaulted; + + success = GetSecurityDescriptorDacl((SECURITY_DESCRIPTOR*)secDesc, + &daclPresent, + &dacl, + &daclDefaulted); + if(!success) + return ErrPop(GetLastError(), L"Error getting DACL for directory: \n%s",dir); + + if (dacl == NULL) // a NULL dacl allows all access to an object + return true; + +//==Get Security Token========================================================= + + HANDLE *token = 0; + + success = OpenProcessToken(hInstance, TOKEN_READ, token); + + + /*if(!success) + return ErrPop(GetLastError());*/ + +//==Get and check ACEs from DACL=============================================== + + DWORD allowed = 0; + + for(USHORT i = 0; i < dacl->AceCount; ++i){ + + void *ace; + BOOL isRelavent; + + success = GetAce(dacl, i, &ace); + + if(!success) + continue; + + + + if(((ACE_HEADER*)ace)->AceType == ACCESS_ALLOWED_ACE_TYPE){ + + ACCESS_ALLOWED_ACE *aAce = (ACCESS_ALLOWED_ACE*)ace; + + CheckTokenMembership(token,(PSID)&aAce->SidStart,&isRelavent); + + if(isRelavent){ + ACCESS_MASK mask = aAce->Mask; + allowed |= mask; + if ((allowed & WRITE_RIGHTS) == WRITE_RIGHTS) + return true; + } + + + }else if(((ACE_HEADER*)ace)->AceType == ACCESS_DENIED_ACE_TYPE){ + + ACCESS_DENIED_ACE *dAce = (ACCESS_DENIED_ACE*)ace; + + + CheckTokenMembership(token,(PSID)&dAce->SidStart,&isRelavent); + + if(isRelavent){ + ACCESS_MASK mask = dAce->Mask; + if (mask & WRITE_RIGHTS) + return false; + } + + } + + } + + return false; +} + +int RunCmd(wchar_t *cmd, bool wait = true){ + + STARTUPINFO si = { sizeof(STARTUPINFO) }; + PROCESS_INFORMATION pi; + + BOOL success = CreateProcessW( NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + + if(!success) + return ErrPop(GetLastError(), L"Could not start update process."); + + DWORD result = 0; + if(wait){ + WaitForSingleObject( pi.hProcess, INFINITE ); + GetExitCodeProcess(pi.hProcess, &result); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return result; +} + +// Instead of linking against credui.lib, we get its functions via LoadLibrary +// and GetProcAddress so that this executable can run on Windows 2000. + +typedef DWORD (WINAPI *pCredUIPromptForCredentials_t)(PCREDUI_INFO, PCTSTR, PCtxtHandle, DWORD, PCTSTR, ULONG, PCTSTR, ULONG, PBOOL, DWORD); +typedef DWORD (WINAPI *pCredUIParseUserName_t)( + PCTSTR pszUserName, + PTSTR pszUser, + ULONG ulUserMaxChars, + PTSTR pszDomain, + ULONG ulDomainMaxChars +); + +static pCredUIPromptForCredentials_t pCredUIPromptForCredentials = 0; +static pCredUIParseUserName_t pCredUIParseUserName = 0; + +DWORD RunAsAdmin( HWND hWnd, LPTSTR file, LPTSTR parameters ){ + + //http://msdn2.microsoft.com/en-us/library/bb756922.aspx + + SHELLEXECUTEINFO sei; + ZeroMemory(&sei, sizeof(sei)); + + sei.cbSize = sizeof(SHELLEXECUTEINFOW); + sei.hwnd = hWnd; + sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI; + sei.lpVerb = L"runas"; + sei.lpFile = file; + sei.lpParameters = parameters; + sei.nShow = SW_SHOWNORMAL; + + + DWORD workingDirLen = GetCurrentDirectory(0,NULL); + wchar_t *workingDir = (wchar_t*)alloca(sizeof(wchar_t) * workingDirLen); + GetCurrentDirectory(workingDirLen,workingDir); + + + sei.lpDirectory = workingDir; + + if (!ShellExecuteEx(&sei)){ + return ErrPop(GetLastError(), L"Could not start update process."); + } + + + DWORD result = 0; + DWORD errCode = WaitForSingleObject( sei.hProcess, INFINITE ); + if(errCode){ + if(errCode == -1) + errCode = GetLastError(); + + return ErrPop(errCode,L"Error waiting for update process."); + } + GetExitCodeProcess(sei.hProcess, &result); + + CloseHandle(sei.hProcess); + + return result; +} + + +struct UserCredentials{ + wchar_t username[CREDUI_MAX_USERNAME_LENGTH + 1]; + wchar_t password[CREDUI_MAX_PASSWORD_LENGTH + 1]; +}; + +DWORD UserPasswordPrompt(UserCredentials* creds){ + // thanks http://msdn2.microsoft.com/en-us/library/ms717794(VS.85).aspx + + + BOOL fSave; + DWORD dwErr; + + CREDUI_INFO cui; + + cui.cbSize = sizeof(cui); + cui.hwndParent = NULL; + cui.pszMessageText = L"Please enter administrator account information"; + cui.pszCaptionText = L"Digsby Autoupdate"; + cui.hbmBanner = NULL; + + fSave = FALSE; + + wcscpy(creds->username,L""); + wcscpy(creds->password,L""); + //SecureZeroMemory(creds->username, sizeof(creds->username)); + //SecureZeroMemory(creds->password, sizeof(creds->password)); + + dwErr = pCredUIPromptForCredentials(&cui, // CREDUI_INFO structure + L"DigsbyUpdater", // Target for credentials (usually a server) + NULL, // Reserved + 0, // Reason + creds->username, // User name + CREDUI_MAX_USERNAME_LENGTH+1, // Max number of char for user name + creds->password, // Password + CREDUI_MAX_PASSWORD_LENGTH+1, // Max number of char for password + &fSave, // State of save check box + CREDUI_FLAGS_GENERIC_CREDENTIALS | // flags + CREDUI_FLAGS_ALWAYS_SHOW_UI | + CREDUI_FLAGS_DO_NOT_PERSIST | + CREDUI_FLAGS_REQUEST_ADMINISTRATOR); + + return dwErr; +} + + +/** + * Run a command as the user specified in creds. + * + * Returns nonzero if successful. Use GetLastError to determine an error state. + */ +int RunCmdAs(wchar_t *cmd, UserCredentials *creds, bool wait = true){ + // thanks http://msdn2.microsoft.com/en-us/library/ms682431(VS.85).aspx + + DWORD result; + + wchar_t user[CREDUI_MAX_USERNAME_LENGTH], domain[CREDUI_MAX_DOMAIN_TARGET_LENGTH]; + + DWORD err = pCredUIParseUserName(creds->username, + user, + CREDUI_MAX_USERNAME_LENGTH, + domain, + CREDUI_MAX_DOMAIN_TARGET_LENGTH); + + if(err){ + wcscpy(user,creds->username); + wcscpy(domain,L"."); + } + + + + PROCESS_INFORMATION pi; + STARTUPINFO si = { sizeof(STARTUPINFO) }; + si.cb = sizeof(STARTUPINFO); + + + DWORD workingDirLen = GetCurrentDirectory(0,NULL); + wchar_t *workingDir = (wchar_t*)alloca(sizeof(wchar_t) * workingDirLen); + GetCurrentDirectory(workingDirLen,workingDir); + + BOOL success = CreateProcessWithLogonW(user, + domain, + creds->password, + 0, // logon flags + NULL, // application name + cmd, // command line + 0, // creation flags + 0, // environment (0 means inherit) + workingDir, // working directory (0 means inherit) + &si, + &pi); + + // on success, we must clean up opened handles + if (success) { + if(wait){ + WaitForSingleObject( pi.hProcess, INFINITE ); + GetExitCodeProcess(pi.hProcess, &result); + }else{ + result = 0; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + }else{ + + result = ErrPop(GetLastError(),L"Could not start update process as %ws.",creds->username); + + } + + return result; +} + +bool GetSILock(HANDLE &lockHandle, wchar_t *lockfilepath){ + lockHandle = CreateFile(lockfilepath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + return lockHandle != INVALID_HANDLE_VALUE; +} + +bool ReleaseSILock(HANDLE &lockHandle){ + return CloseHandle(lockHandle) == TRUE; +} + +int APIENTRY wWinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPWSTR lpwCmdLine, + int nCmdShow){ + +//==Show the updating banner================================================== + InitSplash(hInstance, (wchar_t*)&IMAGE_FILENAME, (wchar_t*)&UPDATE_STR); + +//==Handling the command line args============================================ + LPWSTR *argv; + int argc; + + //get traditional arguements from the string (filename is not part of the + //command line) + argv = CommandLineToArgvW(lpwCmdLine, &argc); + + if (!argv) { + return ErrPop(GetLastError()); + } + + //Make sure there's enough arguments + if ( argc != 3 ) { // supersecret USERDIR DIGSBYEXE + return ErrPop(0,L"Incorect parameters"); + } + + KillDigsbys(); + + const wchar_t *secretArg = argv[0]; + const wchar_t *userDir = argv[1]; + const wchar_t *digsbyexe = argv[2]; + + wchar_t *lockfilePath = (wchar_t*)alloca(sizeof(wchar_t) * (wcslen(userDir) + wcslen(LOCKFILE) + 2)); + wcscpy(lockfilePath, userDir); + wcscat(lockfilePath, L"\\"); + wcscat(lockfilePath, LOCKFILE); + + HANDLE lockHandle; + if(!GetSILock(lockHandle, lockfilePath)){ + ErrPop(0, L"Update lock already reserved, aborting! If you see this message and update fails, please contact bugs@digsby.com for assistance."); + return -1; + } + + +//==Determine Elevation Requirements=========================================== + bool needsElevation = !HasWriteAccess(hInstance); + + // try getting CredUIPromptForCredentials + HMODULE credUI = LoadLibrary(L"credui.dll"); + if (credUI) { + pCredUIPromptForCredentials = (pCredUIPromptForCredentials_t)GetProcAddress(credUI, "CredUIPromptForCredentialsW"); + pCredUIParseUserName = (pCredUIParseUserName_t)GetProcAddress(credUI, "CredUIParseUserNameW"); + } + +//==Run Post Update============================================================ + size_t postUpdateLen = wcslen(userDir) + wcslen(POST_UPDATE_FILE) + 3; + wchar_t *postUpdate = (wchar_t*)alloca(sizeof(wchar_t) * postUpdateLen); + wcscpy(postUpdate, userDir); + wcscat(postUpdate, L"\\"); + wcscat(postUpdate, POST_UPDATE_FILE); + + + + wchar_t cmd_buffer[1024]; + cmd_buffer[0] = 0; + swprintf_s(cmd_buffer, L"\"%ws\" \"%ws\" \"%ws\" \"%ws\"", postUpdate, secretArg, userDir, digsbyexe); + + int err = NO_ERROR; + + //TODO: Only really need pCredUIPromptForCredentials if UAC is not availible + if(pCredUIPromptForCredentials && needsElevation){ + + if(IsVista() && HasUAC()){ + + swprintf_s(cmd_buffer, L"\"%ws\" \"%ws\" \"%ws\"", secretArg, userDir, digsbyexe); + + err = RunAsAdmin(NULL,postUpdate,cmd_buffer); + + }else{ + UserCredentials creds; + DWORD errCode = UserPasswordPrompt(&creds); + if (errCode) { + return ErrPop(errCode); + } + + err = RunCmdAs(cmd_buffer, &creds); + + // ensure username and password are securely erased from memory + SecureZeroMemory(&creds, sizeof(creds)); + } + }else{ + err = RunCmd(cmd_buffer); + } + + +//==Run Digsby================================================================= + + swprintf_s(cmd_buffer, err? DIGSBY_CALL_FAIL : DIGSBY_CALL_WIN, digsbyexe); + RunCmd(cmd_buffer, false); + +//==Close GUI and Exit========================================================= + CloseSplash(); + + ReleaseSILock(lockHandle); + + return 0; + +} diff --git a/updater/Digsby PreUpdater/splash.cpp b/updater/Digsby PreUpdater/splash.cpp new file mode 100644 index 0000000..4e9093a --- /dev/null +++ b/updater/Digsby PreUpdater/splash.cpp @@ -0,0 +1,205 @@ +#include "splash.h" + +#define SHOW_DELAY_MS 1000 // milliseconds to wait before showing + +HBITMAP digsbyBmp; // the Bitmap to show on the banner +HFONT mainFont; +SIZE digsbyBmpSize; +POINT digsbyBmpPos; +POINT msgPos; +HWND hwnd; +UINT_PTR timer; + +wchar_t *theMsg; //The string to show in the banner + +#define ID_TIMER 1 + +// show delay timer callback +VOID CALLBACK TimerProc(HWND hParent, UINT uMsg, UINT uEventID, DWORD dwTimer){ + ShowWindow(hwnd, SW_SHOWNORMAL); + KillTimer(hwnd, timer); + timer = 0; +} + + +// handles the paint messages for the window +DWORD OnPaint(HWND hwnd){ + + //Device Contexts + PAINTSTRUCT paintStruct; + HDC hDC = BeginPaint(hwnd, &paintStruct); + + if(digsbyBmp){ + HDC digsbyBmpDC = CreateCompatibleDC(0); + SelectObject(digsbyBmpDC, digsbyBmp); + + // Draw Image + BitBlt(hDC, digsbyBmpPos.x, digsbyBmpPos.y, digsbyBmpSize.cx, digsbyBmpSize.cy, digsbyBmpDC, 0, 0, SRCCOPY); + //cleanup + DeleteDC(digsbyBmpDC); + } + + // Draw Text + SetTextColor(hDC, COLORREF(0x00000000)); + SelectObject(hDC, mainFont); + TextOut(hDC, msgPos.x, msgPos.y, theMsg, (int)wcslen(theMsg)); + + // Cleanup + EndPaint(hwnd, &paintStruct); + return 0; +} + +//Message handerler callback for the window +LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ + + switch(message){ + case WM_CREATE: + return 0; + case WM_CLOSE: + return 0; + case WM_PAINT: + OnPaint(hwnd); + break; + default: + break; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +//Create and show updateing banner +DWORD InitSplash(HINSTANCE hInstance, const wchar_t *imagePath, const wchar_t *msg){ + + //Create global for the string so Onpaint can get to it + theMsg = (wchar_t*)malloc(sizeof(wchar_t) * (wcslen(msg)+1)); + wcscpy(theMsg, msg); + +//==Load Image Form Harddrive================================================== + + //TODO: Needs error branch + //Load a .bmp file from the harddrive + digsbyBmp = (HBITMAP)LoadImage(0, imagePath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTCOLOR); + + + //Get the information for the bitmap, needed for size info + BITMAP digsbyBmpInfo; + GetObject(digsbyBmp, sizeof(digsbyBmpInfo), &digsbyBmpInfo); + + + //Sets the global size for painting and window size calculations + digsbyBmpSize.cx = digsbyBmp? digsbyBmpInfo.bmWidth : 0; + digsbyBmpSize.cy = digsbyBmp? digsbyBmpInfo.bmHeight : 0; + +//==Font Measurements, and window width and height======================== + + int winWidth, winHeight; + HDC screenDC = GetDC(0); //a DC is needed + SIZE strSize; + strSize.cx = 0; + strSize.cy = 0; + + mainFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + SelectObject(screenDC, mainFont); + + //Get the measurements of the fonts from the system + GetTextExtentPoint32(screenDC, msg, (int)wcslen(msg), &strSize); + + winWidth = max(digsbyBmpSize.cx, strSize.cx) + 2*PADDING; + winHeight = strSize.cy + digsbyBmpSize.cy + 3*PADDING; + + ReleaseDC(0, screenDC); //The DC is no longer needed now that the font is measured + +//==Image and text positions=================================================== + + //Calculate digsby image position + digsbyBmpPos.x = winWidth/2 - digsbyBmpSize.cx/2; + digsbyBmpPos.y = PADDING; + + //Calculate text position + msgPos.x = winWidth/2 - strSize.cx/2; + msgPos.y = winHeight - strSize.cy - PADDING; + +//==Window Position============================================================ + + POINT pt; //point used to determine primary display + MONITORINFO monInfo; //storage to store monitor info + HMONITOR hmon; //handle for the monitor + int scrWidth, scrHeight; //screen width and height + int winPosX, winPosY; //position for the window + + pt.x = 1; //point (1,1) is always on primary display + pt.y = 1; + + //Find monitor that has point(1,1) on it + hmon = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY); + + //Get the monitor info structure to get size information + monInfo.cbSize = sizeof(monInfo); + GetMonitorInfo(hmon, &monInfo); + + //Get Screen width and height based off the rect in the monitor info + scrWidth = monInfo.rcWork.right - monInfo.rcWork.left; + scrHeight = monInfo.rcWork.bottom - monInfo.rcWork.top; + + //Determine center position for the window to be placed + winPosX = scrWidth/2 - winWidth/2; + winPosY = scrHeight/2 - winHeight/2; + +//==Create Window============================================================== + + //create, setup, and register a custom class for a window + WNDCLASSEX windowClass; + windowClass.cbSize = sizeof(WNDCLASSEX); + windowClass.style = CS_HREDRAW | CS_VREDRAW; //always do a full redraw + windowClass.lpfnWndProc = &WndProc; //callback for windows messages + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = 0; + windowClass.hInstance = hInstance; //The application instance handle + windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //Icon for Alt+Tab + windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); //Mouse cursor + windowClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //Background brush + windowClass.lpszMenuName = NULL; //menubar + windowClass.lpszClassName = L"DigsbyUpdateWindow"; //Classname... + windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); //icon in upper left hand corner + + //TODO: needs error branch + //Registering the class just defined by the WNDCLASSEX struct + RegisterClassEx(&windowClass); + + //Create window + hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, //extended style - don't show in taskbar + L"DigsbyUpdateWindow", //class name + L"Digsby Post Update", //window name + WS_VISIBLE | + WS_POPUPWINDOW, //window style + winPosX, winPosY, //x/y coords + winWidth, winHeight, //width,height + NULL, //handle to parent + NULL, //handle to menu + hInstance, //application instance + NULL); //no extra parameters + + //if (hwnd) { + // // Show the window after a small delay. + // timer = SetTimer(hwnd, ID_TIMER, SHOW_DELAY_MS, &TimerProc); + + // // If creating the timer failed, just show the window now. + // if (!timer) ShowWindow(hwnd, SW_SHOWNORMAL); + // + // + //} + + //Manually tell window to draw + UpdateWindow(hwnd); + + return 0; +} + +//Close the window and general cleanup +void CloseSplash() { + + DestroyWindow(hwnd); + DeleteObject(digsbyBmp); + DeleteObject(mainFont); + free(theMsg); +} \ No newline at end of file diff --git a/updater/Digsby PreUpdater/splash.h b/updater/Digsby PreUpdater/splash.h new file mode 100644 index 0000000..2a949a3 --- /dev/null +++ b/updater/Digsby PreUpdater/splash.h @@ -0,0 +1,17 @@ +#ifndef __SPLASH__ +#define __SPLASH__ + +#include +#include + +#define PADDING 10 + + +DWORD OnPaint(HWND hwnd); +DWORD InitSplash(HINSTANCE hInstance, const wchar_t *imagePath, const wchar_t *msg); +void CloseSplash(); + + + + +#endif //__SPLASH__ \ No newline at end of file diff --git a/updater/Digsby Updater/digsby_updater.cpp b/updater/Digsby Updater/digsby_updater.cpp new file mode 100644 index 0000000..d27f5f3 --- /dev/null +++ b/updater/Digsby Updater/digsby_updater.cpp @@ -0,0 +1,779 @@ +#define WIN32_LEAN_AND_MEAN + + +#include +#include +#include +#include + +#include +#include +#include + +#include +using std::wifstream; + +#include "logging.h" + +#define SLEEP_MS 100 +#define BU_EXT L".updateback" + + +#define DEL_STR L"cmd.exe /D /C del /F /Q /S *%ws" +#define XCOPY_STR L"xcopy \"%ws\\*\" . /S /E /Q /Y" +#define RMDIR_STR L"cmd.exe /D /C rmdir /s /q \"%ws\"" + +#define DELETE_FILE L"deleteme.txt" +#define UPDATE_FILE L"updateme.txt" + +#define LOG_FILE L"digsby_updater.log" +#define LOG_FILE_2 L"digsby_updater(2).log" + +#define CLONE_FILE L"digsby.clone" +#define DUMMY_FILE L"lib\\digsby.dummy" + +#define MOVE_ARGS (MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) + + +//#define UPDATE_STR L"Please wait, updating Digsby!" +//#define IMAGE_FILENAME L"res\\digsbybig.bmp" + +// thanks http://www.kickingdragon.com/2006/07/04/run-batch-files-without-a-console-popup/ and others + + + +int KillPID(DWORD pid){ + LogMsg(L"KillPID(%d)\n", pid); + LogMsg(L" calling OpenProcess(SYNCHRONIZE, FALSE, %d)\n", pid); + + HANDLE parent = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, (DWORD) pid); + // May return NULL if the parent process has already gone away. + // Otherwise, wait for the parent process to exit before starting the + // update. + if (parent) { + LogMsg(L" OpenProcess returned HANDLE\n"); + + //fwprintf(log, L" Calling WaitForSingleObject(HANDLE, 5000)...\n"); + //DWORD result = WaitForSingleObject(parent, 1000); + //fwprintf(log, L" result: %d\n", result); + //fwprintf(log, L" Calling CloseHandle(parent)\n"); + + LogMsg(L" Calling TerminateProcess\n"); + + //TODO: We should probably check this for failure + //returns a bool, nonzero is success + BOOL termResult = TerminateProcess(parent, -1); + LogMsg(L" TerminateProcess returned %d\n", termResult); + + CloseHandle(parent); + } else { + LogMsg(L" OpenProcess did not find a process handle for PID %d\n", pid); + } + + LogMsg(L" sleeping for %d ms\n", SLEEP_MS); + Sleep(SLEEP_MS); + LogMsg(L" done sleeping.\n"); + + return 0; +} + +void KillDigsbys(){ + + /*MessageBox(NULL, + L"KillDigsbys() bypassed for testing!", + L"WARNING", + MB_OK|MB_ICONINFORMATION); + return;*/ + + + // Get the list of process identifiers. + DWORD aProcesses[5000], cBytes, cProcesses; + unsigned int i; + + if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cBytes ) ) + return; + + // Calculate how many process identifiers were returned. + cProcesses = cBytes / sizeof(DWORD); + + // Print the name and process identifier for each process. + for ( i = 0; i < cProcesses; i++ ) { + if( aProcesses[i] != 0 ) { + DWORD processID = aProcesses[i]; + + TCHAR szProcessName[MAX_PATH] = TEXT(""); + + // Get a handle to the process. + HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ, + FALSE, processID ); + // Get the process name. + if (hProcess) { + HMODULE hMod; + DWORD cBytesNeeded; + + if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cBytesNeeded) ) + GetModuleBaseName( hProcess, hMod, szProcessName, + sizeof(szProcessName)/sizeof(TCHAR) ); + + } + + // kill the process if it is "digsby.exe" or "Digsby.exe" + if (0 == wcscmp(L"digsby.exe", _wcslwr(szProcessName)) || 0 == wcscmp(L"digsby-app.exe", _wcslwr(szProcessName))) + KillPID(processID); + + + CloseHandle( hProcess ); + } + } +} + +int RunProcess(wchar_t* cmd, bool show = false, bool wait = true) +{ + LogMsg(L"\nRunProcess(cmd = \"%ws\", show = %d, wait = %d)\n", cmd, show, wait); + + STARTUPINFO si = { sizeof(STARTUPINFO) }; + + if (!show) { + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + } + + PROCESS_INFORMATION pi; + + int retVal = 0; + + LogMsg(L" Calling CreateProcessA\n"); + if (!CreateProcessW( NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + LogErr(GetLastError(),0,L" error: Could not run \"%ws\"\n", cmd); + return 1; + } else { + LogMsg(L" CreateProcessA ok\n"); + } + + if (wait) { + LogMsg(L" wait specified, calling WaitForSingleObject\n"); + DWORD result = WaitForSingleObject( pi.hProcess, INFINITE ); + LogMsg(L" WaitForSingleObject returned with code %d\n", result); + + LogMsg(L" CloseHandle( pi.hProcess )\n"); + CloseHandle( pi.hProcess ); + LogMsg(L" CloseHandle( pi.hThread )\n"); + CloseHandle( pi.hThread ); + } + + return 0; +} + +bool IsDirectory(wchar_t *path) { + WIN32_FIND_DATA filedata; + + HANDLE h = FindFirstFile(path, &filedata); + if (h == INVALID_HANDLE_VALUE) + return false; + + FindClose(h); + + return filedata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; +} + + +DWORD ForgePath(const wchar_t *path, bool hasFileName = true){ + + wchar_t *fullPath=0, *workingPath=0, *current=0; + + int endex = wcslen(path); + + //strip the filename if it has one + if (hasFileName) + while (!(path[endex] == L'\\')) + if (--endex < 0) + return LogErr(0, 0, L"Invalid path: hasFileName was given, but path did not contain a slash.\n"); + + //local copy of the path becsause wcstok() modifies it + fullPath = (wchar_t*)alloca((endex+1)*sizeof(wchar_t)); + + //This is the path that is curently being created + workingPath = (wchar_t*)alloca((endex+1)*sizeof(wchar_t)); + + //copy the relevant path to the local copy + wcsncpy(fullPath, path, endex); + + //manualy set null bytes because they aren't there yet + fullPath[endex] = L'\0'; + workingPath[0] = L'\0'; + + //while the path still has more depth, keep creating directories + for(current = wcstok(fullPath, L"\\"); + current; + current = wcstok(0, L"\\"), wcsncat(workingPath, L"\\", endex - wcslen(workingPath))){ + + + wcscat(workingPath, current); + if(!IsDirectory(workingPath)){ + LogMsg(L"Creating dir: %ws;", workingPath); + if(!CreateDirectoryW(workingPath, 0)){ + DWORD errCode = GetLastError(); + return LogErr(errCode, 0, L" Fail\n"); + }else{ + LogMsg(L" OK\n"); + } + } + } + + return 0; +} + + + +DWORD StripDACL(const wchar_t *filename){ + + PACL acl = (ACL*)alloca(sizeof(ACL)); + InitializeAcl(acl, sizeof(ACL), ACL_REVISION); + + DWORD errCode = SetNamedSecurityInfo((LPWSTR)filename, + SE_FILE_OBJECT, + UNPROTECTED_DACL_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, + NULL, + NULL, + acl, + NULL); + + if(errCode) + LogErr(errCode,LE_POPUP,L"Could not update permissions."); + + + return errCode; +} + + + +//Move file from temp directory to the working directory +DWORD UpdateFile(const wchar_t* filename, const wchar_t* tmpDir){ + + DWORD errCode = 0; + + //reserve space for new string + size_t l = wcslen(filename) + wcslen(tmpDir) + 2; + wchar_t *tmpPath = (wchar_t*) alloca(sizeof(wchar_t)*l); + + //merge strings into knew string + wcscpy(tmpPath, tmpDir); + wcscat(tmpPath, L"\\"); + wcscat(tmpPath, filename); + + //Try moving file, if fail Log error and return errCode + for(bool tryagain = true; true; tryagain = false){ + LogMsg(L"Updating from temp dir: %ws; ", filename); + if(!MoveFileExW(tmpPath, filename, MOVE_ARGS)){ + errCode = GetLastError(); + if(errCode == ERROR_PATH_NOT_FOUND && tryagain){ + LogMsg(L" FAIL\n\tDirectory not found, creating path\n"); + errCode = ForgePath(filename); + if (errCode) + return LogErr(errCode, 0, L"Failed creating path.\n"); + + continue; + } + return LogErr(errCode); + }else{ + errCode = StripDACL(filename); + if(errCode){ + return LogErr(errCode,0,L"Failed to reset DACL\n"); + }else{ + LogMsg(L"OK\n"); + } + } + + break; + } + + return 0; +} + +//opens a text file and calls UpdateFile for every filename in it +DWORD UpdateFiles(const wchar_t* updateFileName, const wchar_t* tmpDir){ + + LogMsg(L"\nreading %ws for files to be updated...\n", updateFileName); + + //open file (must be ASCII) + wifstream fin(updateFileName); + if (!fin.is_open()) + return LogErr(GetLastError(), 0, L"Error opening %ws\n", updateFileName); + + //for filename in file, update file + wchar_t filename[MAX_PATH]; + while (fin.getline(filename, MAX_PATH)){ + DWORD errCode = UpdateFile(filename, tmpDir); + if (errCode){ + fin.close(); + return errCode; + } + } + + fin.close(); + return 0; +} + + +//restores file from filename.extension.backup to filename.extension +DWORD RestoreFile(const wchar_t* filename) { + + //reserve space for backup filename + size_t l = wcslen(filename) + wcslen(BU_EXT) + 1; + wchar_t *backupName = (wchar_t*) alloca(sizeof(wchar_t)*l); + + //create backupfilename + wcscpy(backupName, filename); + wcscat(backupName, BU_EXT); + + LogMsg(L"restoring from backup: %ws; ", filename); + + //move file to new name + if (!MoveFileExW(backupName, filename, MOVE_ARGS)){ + DWORD errCode = LogErr(GetLastError()); + if(errCode == ERROR_FILE_NOT_FOUND) + LogMsg(L"\tLikely not a real error. Most probably a new file.\n\n"); + else + return errCode; + } else + LogMsg(L"OK\n"); + + return 0; +} + +//opens a text file and calls RestoreFile for every filename in it +DWORD RestoreFiles(const wchar_t* restoreFileName){ + + LogMsg(L"\nreading %ws for files to be restored...\n", restoreFileName); + + //Open file + wifstream fin(restoreFileName); + if(!fin.is_open()) + return LogErr(0, 0, L"Error opening %ws\n", restoreFileName); + + //call RestoreFile for filename in the textfile + wchar_t filename[MAX_PATH]; + while (fin.getline(filename, MAX_PATH)){ + DWORD errCode = RestoreFile(filename); + if(errCode) { + fin.close(); + return errCode; + } + } + + fin.close(); + return 0; +} + +//Back move a file, changing it's extension to .backup +DWORD BackupFile(const wchar_t* filename){ + + //Resrve space for backup name + size_t l = wcslen(filename) + wcslen(BU_EXT) + 1; + wchar_t *backupName = (wchar_t*) alloca(sizeof(wchar_t)*l); + + //create new filename + wcscpy(backupName, filename); + wcscat(backupName, BU_EXT); + + //Move file to new filename, if failed once try again, if faild twice return with error + for (bool tryagain = true; true; tryagain = false) { + LogMsg(L"backing up file %ws; ", filename); + if (!MoveFileExW(filename, backupName, MOVE_ARGS)){ + DWORD errCode = LogErr(GetLastError()); + if (errCode != ERROR_FILE_NOT_FOUND && errCode != ERROR_PATH_NOT_FOUND && errCode != ERROR_SUCCESS){ + if (tryagain) { + KillDigsbys(); + LogMsg(L"Attempt 2 of "); + continue; + } + LogMsg(L"Failed all attempts, returning\n"); + + return errCode; + } + LogMsg(L"\tLikely not a real error. Most probably a new file.\n\n"); + } else + LogMsg(L"OK\n"); + + break; + } + + return 0; +} + +//Call backup for all filenames listed in the txt file +DWORD BackupFiles(const wchar_t* backupFileName){ + + LogMsg(L"\nreading %ws for files to be backed up...\n", backupFileName); + + //open file + wifstream fin(backupFileName); + if (!fin.is_open()) + return LogErr(GetLastError(), 0, L"Error opening %ws\n", backupFileName); + + //call BackupFile for filename in text file + wchar_t filename[MAX_PATH]; + while (fin.getline(filename,MAX_PATH)){ + DWORD errCode = BackupFile(filename); + if (errCode) { + fin.close(); + return errCode; + } + } + fin.close(); + return 0; +} + +//2 strings enter, one string leaves +//takes a 2 strings, and merges them into a new sring separated by '\', returns the new string +//requirs memory be freed later +wchar_t* MakePathStr(const wchar_t *dir,const wchar_t *file){ + + //allocates space for string + size_t dirLen = wcslen(dir); + wchar_t *path = (wchar_t*) malloc(sizeof(wchar_t) * (dirLen + wcslen(file) + 2) ); + + //merges the other strings into this string + wcscpy(path, dir); + wsprintf(&path[dirLen], L"\\%s", file); + + return path; +} + +bool ReplaceFileXtreme(const wchar_t *oldFile, const wchar_t *newFile, const wchar_t *backupFile = 0, bool copy = false){ + if(backupFile){ + if(!MoveFileExW(oldFile, backupFile, MOVE_ARGS)){ + LogErr(0, 0, L"Error moving %ws to %ws", oldFile, backupFile); + return false; + } + } + + if(!(copy? CopyFile(newFile, oldFile, FALSE) : MoveFileExW(newFile, oldFile, MOVE_ARGS))){ + LogErr(0, 0, L"Error moving %ws to %ws", newFile, oldFile); + if(backupFile){ + if(!MoveFileExW(backupFile, oldFile, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)){ + LogErr(0, 0, L"Interesting error with ReplaceFileXtreme(), backup of %ws to %ws was successfull, but an error occured moving %ws to %ws and then another error restoring %ws to %ws",oldFile,backupFile,newFile,oldFile,backupFile,oldFile); + } + } + return false; + } + + return true; + +} + +//swaps the executible for a simple dummy one, perventing the user from starting another digsby instance +DWORD SwapExe(const wchar_t *digsbyexe) { + + //Try to replace exe with dummy, if fails twice error out + for (bool tryagain = true; true; tryagain = false) { + LogMsg(L"\nSwapping out digsby.exe for digsby.dummy;"); + + //digsby.exe is renamed to digsby.clone + //digsby.dummy is renamed to digsby.exe + if(!ReplaceFileXtreme(digsbyexe, DUMMY_FILE, CLONE_FILE, true)){ + DWORD errCode = LogErr(GetLastError(), 0, L"\nCould not back up %ws", digsbyexe); + if(tryagain){ + KillDigsbys(); + LogMsg(L"\n\tTrying again...\n"); + continue; + } + LogMsg(L"\n\tAborting!\n"); + return errCode; + } + LogMsg(L" OK!\n"); + return 0; + } +} + +//Swaps Back exe to be the normal one and the dummy back to .dummy +DWORD ReSwapExe(const wchar_t *digsbyexe){ + + //try to swap exe back in for dummy, if fail twice error out + for(bool tryagain = true; true; tryagain = false){ + LogMsg(L"\nEmergency restore of digsby.exe;"); + + // digsby.clone is renamed to digsby.exe + if(!ReplaceFileXtreme(digsbyexe, CLONE_FILE)){ + DWORD errCode = LogErr(GetLastError(), 0, L"\nCould not restore %ws", digsbyexe); + if (tryagain) { + KillDigsbys(); + LogMsg(L"\n\tTrying again...\n"); + continue; + } + LogMsg(L"\n\tAborting!\n"); + return errCode; + } + LogMsg(L" OK!\n"); + return 0; + } +} + +//if a new exe is provided, swaps that with the old one, then swaps the dummy out with the real exe +DWORD TryUpdateAndSwapExe(const wchar_t *tempDir, const wchar_t *digsbyexe){ + + //allocate space for string + size_t l = wcslen(tempDir) + wcslen(digsbyexe) + 2; + wchar_t *newExe = (wchar_t*) alloca( sizeof(wchar_t) * l ); + + //merge strings into the new one + wcscpy(newExe, tempDir); + wcscat(newExe, L"\\"); + wcscat(newExe, digsbyexe); + + DWORD errCode = 0; + + //Try updating digsby.exe + LogMsg(L"\nAttemping to update %ws;", digsbyexe); + if(!ReplaceFileXtreme(CLONE_FILE, newExe)) + errCode = LogErr(GetLastError(), 0, L"\nCan not copy %ws;", newExe); + if (errCode == ERROR_FILE_NOT_FOUND) { + errCode = 0; + LogMsg(L"\tLikely not a real error. Most probably no update needed.\n\n"); + } + else + errCode = StripDACL(CLONE_FILE); + if(errCode){ + return LogErr(errCode,0,L"Failed to reset DACL."); + }else{ + LogMsg(L" OK\n"); + } + + //Restore Exe from clone + for (bool tryagain = true; true; tryagain=false) { + LogMsg(L"\nRestoring %ws;", digsbyexe); + if (!ReplaceFileXtreme(digsbyexe, CLONE_FILE)) { + errCode = LogErr(GetLastError(), 0, L"\nFailed to restore %ws;", digsbyexe); + if (tryagain) { + errCode = 0; + LogMsg(L"Make sure Digsby process is dead:\n"); + KillDigsbys(); + LogMsg(L"Trying again...\n"); + continue; + } else + LogMsg(L"Aborting!\n"); + } else + LogMsg(L" OK\n"); + break; + } + + + return errCode; +} + +//==Restore block============================================================== +DWORD Restore(const wchar_t* digsbyexe, const wchar_t* updateBuffer, const wchar_t* deleteBuffer) { + DWORD revErrCode = 0; + + // If updateBuffer is given, we need to revert ALL files. + if (updateBuffer) + revErrCode = RestoreFiles(updateBuffer); + + if (!revErrCode) + revErrCode = RestoreFiles(deleteBuffer); + + if (!revErrCode) + revErrCode = ReSwapExe(digsbyexe); + + if (revErrCode) + LogErr(revErrCode, LE_POPUP, L"Error reverting to original files.\nA reinstall may be necessary."); + else + LogPop(L"Exiting Safely", L"Revert successful, you can restart Digsby.\nTo ensure a successful update, please update Digsby as a user with write permissions to the Digsby install directory and be sure no other logged in users currently have Digsby running."); + + return revErrCode; +} + + + +int APIENTRY wWinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPWSTR lpwCmdLine, + int nCmdShow){ + + DWORD errCode = 0, revErrCode = 0; + + bool verbose = false; + + +//==Handling the command line args============================================ + LPWSTR *argv; + int argc; + + //get traditional arguements from the string (filename is not part of the + //command line) + argv = CommandLineToArgvW(lpwCmdLine, &argc); + if (!argv) { + OpenLog(LOG_FILE); + LogMsg(L"digsby_updater %ws\n\n", lpwCmdLine); + errCode = LogErr(GetLastError(), 0, L"CommandLineToArgvW failed\n"); + LogErr(errCode,LE_POPUP,L"Update failed, please check your digsby directory for %ws and email it to bugs@digsby.com", LOG_FILE); + goto close_log_and_exit; + } + + //Make sure there's enough arguments + if ( argc != 3 ) { // supersecret USERDIR DIGSBYEXE + OpenLog(LOG_FILE); + LogMsg(L"digsby_updater %ws\n\n", lpwCmdLine); + errCode = LogErr(0, 0, L"invalid number of arguments: you gave %d\n", argc - 1); + LogErr(errCode,LE_POPUP,L"Update failed, please check your digsby directory for %ws and email it to bugs@digsby.com", LOG_FILE); + goto close_log_and_exit; + } + + //make sure the super seceret arguement is correct + if (wcscmp(argv[0], L"supersecret") && wcscmp(argv[0], L"supersecretverbose")) { + OpenLog(LOG_FILE); + LogMsg(L"digsby_updater %ws\n\n", lpwCmdLine); + errCode = LogErr(0, 0, L"invalid secret argument\n"); + LogErr(errCode,LE_POPUP,L"Update failed, please check your digsby directory for %ws and email it to bugs@digsby.com", LOG_FILE); + goto close_log_and_exit; + } + +//==Set up directories from the args=========================================== + + const wchar_t *userDir = argv[1]; + const wchar_t *digsbyexe = argv[2]; + + size_t tempDirLen = wcslen(userDir) + wcslen(L"\\temp") + 1; + wchar_t *tempDir = (wchar_t*)alloca(sizeof(wchar_t) * tempDirLen); + wcscpy(tempDir,userDir); + wcscat(tempDir,L"\\temp"); + + size_t logDirLen = wcslen(userDir) + wcslen(L"\\Logs")+ 1; + wchar_t *logDir = (wchar_t*)alloca(sizeof(wchar_t) * logDirLen); + wcscpy(logDir,userDir); + wcscat(logDir,L"\\Logs"); + +//==Open log file in user dir================================================== + + if(!OpenLog(logDir, LOG_FILE)){ + errCode = ForgePath(logDir, false); + if(errCode){ + if(OpenLog(LOG_FILE)){ + LogMsg(L"logdir: %ws\n\n", logDir); + LogErr(errCode, 0, L"Unable to create primary log directory. Secondary log created. Continuing anyway.\n"); + }else{ + LogErr(0, LE_POPUP, L"Unable to create log files. Continuing anyway.\n"); + } + errCode = 0; + } + else if(!OpenLog(logDir, LOG_FILE)){ + if(!OpenLog(logDir, LOG_FILE_2)){ + if(OpenLog(LOG_FILE)){ + LogMsg(L"logdir: %ws\n\n", logDir); + LogErr(0, 0, L"Unable to create primary log file. Secondary log created. Continuing anyway.\n"); + }else{ + LogErr(0, LE_POPUP, L"Unable to create log files. Continuing anyway.\n"); + } + } + } + } + + + DWORD workingDirLen = GetCurrentDirectory(0,NULL); + wchar_t *workingDir = (wchar_t*)alloca(sizeof(wchar_t)*workingDirLen); + GetCurrentDirectory(workingDirLen,workingDir); + + LogMsg(L"digsby_updater %ws\n", lpwCmdLine); + LogMsg(L"Working Directory: %ws\n\n",workingDir); + + +//==Create a console if verbose mode=========================================== + + //Set verbose + verbose = !wcscmp(argv[0], L"supersecretverbose"); + SetVerbose(verbose); + + if (verbose) { + // show a console in verbose mode + AllocConsole(); + freopen("CONIN$", "rb", stdin); + freopen("CONOUT$", "wb", stdout); + freopen("CONOUT$", "wb", stderr); + LogMsg(L"verbose... yes\n"); + } else { + LogMsg(L"verbose... no\n"); + } + +//==Kill all running digsby instances========================================== + + KillDigsbys(); + +//==Swap our the digsby exetcutabel for a dummy one============================ + + errCode = SwapExe(digsbyexe); + if (errCode) { + LogMsg(L"\n\n"); + LogErr(errCode, LE_POPUP, L"Critical error in backing up %ws. Exiting.", digsbyexe); + goto close_log_and_exit;//close_splash_and_exit + } + +//==Backup all files that will be deleted or replaced========================== + wchar_t *deleteBuffer = MakePathStr(tempDir, DELETE_FILE); + wchar_t *updateBuffer = MakePathStr(tempDir, UPDATE_FILE); + + errCode = BackupFiles(deleteBuffer); + if (errCode) { + LogMsg(L"\n\n"); + LogErr(errCode, LE_POPUP, L"Critical error in backing up files. Click OK to revert."); + + Restore(digsbyexe, 0, deleteBuffer);// Restore partial + goto cleanup; + } + + errCode = BackupFiles(updateBuffer); + if (errCode) { + LogMsg(L"\n\n"); + LogErr(errCode, LE_POPUP, L"Critical error in backing up files. Click OK to revert."); + + Restore(digsbyexe, updateBuffer, deleteBuffer); + goto cleanup; + } + +//==Update files and executable, swap excutable back in for dummy============== + + errCode = UpdateFiles(updateBuffer, tempDir); + if (errCode) { + LogMsg(L"\n\n"); + LogErr(errCode, LE_POPUP, L"Critical error in writing new files. Click OK to revert."); + Restore(digsbyexe, updateBuffer, deleteBuffer); + goto cleanup; + } + + errCode = TryUpdateAndSwapExe(tempDir, digsbyexe); + if (errCode) { + LogMsg(L"\n\n"); + LogErr(errCode, LE_POPUP, L"Critical error in restoring %ws.\nTry renaming %ws to %ws. A reinstall might be needed.", digsbyexe, CLONE_FILE, digsbyexe); + goto cleanup; + } + +//==Cleanup temp directory and backuped files, then start program============== + + wchar_t cmd_buffer[1024]; + //removing temp diretory + swprintf_s(cmd_buffer, RMDIR_STR, tempDir); + RunProcess(cmd_buffer); + + //deleting backup files + swprintf_s(cmd_buffer, DEL_STR, BU_EXT); + RunProcess(cmd_buffer); + +//==Cleanup==================================================================== + + LogMsg(L"\nFinished all tasks, exiting cleanly\n"); + +cleanup: //Start cleanup here if the Buffer strings have been created + LogMsg(L"\nCleanup and Exit\n"); + + free(deleteBuffer); + free(updateBuffer); + +close_log_and_exit: //Start cleanup here if log is the only thing + CloseLog(); + + if (verbose) + system("pause"); + + return errCode; +} + diff --git a/updater/Digsby Updater/logging.cpp b/updater/Digsby Updater/logging.cpp new file mode 100644 index 0000000..55a9195 --- /dev/null +++ b/updater/Digsby Updater/logging.cpp @@ -0,0 +1,194 @@ +#include "logging.h" + +FILE *log = 0; //file used for log +bool verbose = true; //print to console + +//turn on verbose mode +void SetVerbose(bool on){ + verbose = on; +} + +//print to log and console if verbose +void vLogMsg(const wchar_t *msg, va_list args){ + if(!log){ + //OpenLog("log.log"); + if(verbose) + printf("Error: no log file not open"); + return; + } + + //ZOMG!! variable arg list-file-wide character-print-formated to log + vfwprintf(log, msg, args); + + if(verbose){ + //Convert string form wide to ascii? + char *msgASCII = (char*)malloc(sizeof(char)*(wcslen(msg)+1)); + sprintf(msgASCII, "%ws", msg); + + //Print to console + vprintf(msgASCII, args ); + + //free memory + free(msgASCII); + } +} + +//Log a simple message +void LogMsg(const wchar_t *msg, ...){ + + va_list args; + va_start(args, msg); + + vLogMsg(msg, args); + + va_end(args); +} + +//Log a message and display an info dialog +void LogPop(const wchar_t *title, const wchar_t *msg, ...){ + + //args for a variable argument method + va_list args; + va_start( args, msg ); + + //Log it + vLogMsg(msg,args); + + //format message for dialog + wchar_t popmsg[2000]; + if(msg){ + wvsprintf(popmsg,msg,args); + //wcscat(popmsg,L"\n\n"); //TODO: Remove this line??? + } + + //Display Dialog + MessageBox(NULL, + popmsg, + title, + MB_OK|MB_ICONINFORMATION); + + //end the args list + va_end(args); +} + +//log an error, optional msg and error code, flag can be used to display error dialog +DWORD LogErr(DWORD errCode, DWORD flags, const wchar_t *msg, ...){ + + //Get varialble args list + va_list args; + va_start( args, msg ); + + //if mesage, log it + if(msg){ + vLogMsg(msg,args); + } + + //if error code look up error message and log it + LPWSTR errStr = NULL; + if(errCode){ + LogMsg(L"\n\tError %d: ", errCode); + + //look up error message + DWORD success = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_IGNORE_INSERTS + | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errCode, + 0, + (LPWSTR) &errStr, + 0, + NULL); + + //if error message log it, else log "Unknown Error" + if (success){ + fwprintf(log, L"%ws", errStr); + if(verbose) printf("%ws", errStr); + } + else{ + fwprintf(log, L"Unknown Error\n\n"); + if(verbose) printf("Unknown Error\n\n"); + } + } + + // Show Dialog if popup flag + if(flags & LE_POPUP){ + + wchar_t title[256]; + title[0] = 0; + + wsprintf(title, L"Error Code %d", errCode); + + wchar_t errmsg[2000]; + errmsg[0] = 0; + if(msg){ + wvsprintf(errmsg,msg,args); + } + + if(msg && errStr){ + wcscat(errmsg,L"\n\n"); + } + + + if(errStr){ + wcscat(errmsg,errStr); + } + + MessageBox(NULL, + errmsg, + title, + MB_OK|MB_ICONERROR); + + } + + + //cleanup + va_end(args); + if(errStr) + LocalFree(errStr); + + return errCode; + +} + +//open log file +bool OpenLog(const wchar_t *logDir, const wchar_t *logFile){ + //if there already is an open log file, close it + if(log && log != stderr) + fclose(log); + + size_t logPathLen = wcslen(logDir) + wcslen(logFile) + 2; + wchar_t *logPath = (wchar_t*)alloca(sizeof(wchar_t) * logPathLen); + wcscpy(logPath,logDir); + wcscat(logPath,L"\\"); + wcscat(logPath,logFile); + + //open file + log = _wfopen(logPath, L"w,ccs=UTF-16LE"); + if (!log){ + log = stderr; + return false; + } + + return true; +} + +bool OpenLog(const wchar_t *logFile){ + //if there already is an open log file, close it + if(log && log != stderr) + fclose(log); + + //open file + log = _wfopen(logFile, L"w,ccs=UTF-16LE"); + if (!log){ + log = stderr; + return false; + } + + return true; +} + +//close log file +void CloseLog(){ + if(log && log != stderr) + fclose(log); +} diff --git a/updater/Digsby Updater/logging.h b/updater/Digsby Updater/logging.h new file mode 100644 index 0000000..c8ed66f --- /dev/null +++ b/updater/Digsby Updater/logging.h @@ -0,0 +1,21 @@ +#ifndef __LOGGING__ +#define __LOGGING__ + +#include +#include +#include +#include +#include + +#define LE_POPUP 0x00000010 + +void SetVerbose(bool on); +void vLogMsg(const wchar_t *msg, va_list args); +void LogMsg(const wchar_t *msg, ...); +void LogPop(const wchar_t *title, const wchar_t *msg, ...); +DWORD LogErr(DWORD errCode = 0, DWORD flags=0, const wchar_t *msg = 0, ...); +bool OpenLog(const wchar_t *logDir, const wchar_t *logfile); +bool OpenLog(const wchar_t *logFile); +void CloseLog(); + +#endif //__LOGGING__ \ No newline at end of file diff --git a/updater/digsby.ico b/updater/digsby.ico new file mode 100644 index 0000000..63b2b4f Binary files /dev/null and b/updater/digsby.ico differ diff --git a/updater/msvc2008/Digsby PreUpdater.vcproj b/updater/msvc2008/Digsby PreUpdater.vcproj new file mode 100644 index 0000000..088eaea --- /dev/null +++ b/updater/msvc2008/Digsby PreUpdater.vcproj @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/updater/msvc2008/Digsby Update MSVC2008.sln b/updater/msvc2008/Digsby Update MSVC2008.sln new file mode 100644 index 0000000..f4696fc --- /dev/null +++ b/updater/msvc2008/Digsby Update MSVC2008.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Digsby PreUpdater", "Digsby PreUpdater.vcproj", "{696DCC74-D29C-417C-8157-7E0628328A44}" + ProjectSection(ProjectDependencies) = postProject + {E6E9F116-C55B-4D04-8B4A-6815E0E9ADB6} = {E6E9F116-C55B-4D04-8B4A-6815E0E9ADB6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Digsby Updater", "Digsby Updater.vcproj", "{E6E9F116-C55B-4D04-8B4A-6815E0E9ADB6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {696DCC74-D29C-417C-8157-7E0628328A44}.Debug|Win32.ActiveCfg = Debug|Win32 + {696DCC74-D29C-417C-8157-7E0628328A44}.Debug|Win32.Build.0 = Debug|Win32 + {696DCC74-D29C-417C-8157-7E0628328A44}.Release|Win32.ActiveCfg = Release|Win32 + {696DCC74-D29C-417C-8157-7E0628328A44}.Release|Win32.Build.0 = Release|Win32 + {E6E9F116-C55B-4D04-8B4A-6815E0E9ADB6}.Debug|Win32.ActiveCfg = Debug|Win32 + {E6E9F116-C55B-4D04-8B4A-6815E0E9ADB6}.Debug|Win32.Build.0 = Debug|Win32 + {E6E9F116-C55B-4D04-8B4A-6815E0E9ADB6}.Release|Win32.ActiveCfg = Release|Win32 + {E6E9F116-C55B-4D04-8B4A-6815E0E9ADB6}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/updater/msvc2008/Digsby Updater.rc b/updater/msvc2008/Digsby Updater.rc new file mode 100644 index 0000000..0969ecb --- /dev/null +++ b/updater/msvc2008/Digsby Updater.rc @@ -0,0 +1,72 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "..\\digsby.ico" +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/updater/msvc2008/Digsby Updater.vcproj b/updater/msvc2008/Digsby Updater.vcproj new file mode 100644 index 0000000..22d0c32 --- /dev/null +++ b/updater/msvc2008/Digsby Updater.vcproj @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/updater/msvc2008/resource.h b/updater/msvc2008/resource.h new file mode 100644 index 0000000..3b56b47 --- /dev/null +++ b/updater/msvc2008/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Digsby Updater.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif